tint->dawn: Shuffle source tree in preperation of merging repos

docs/    -> docs/tint/
fuzzers/ -> src/tint/fuzzers/
samples/ -> src/tint/cmd/
src/     -> src/tint/
test/    -> test/tint/

BUG=tint:1418,tint:1433

Change-Id: Id2aa79f989aef3245b80ef4aa37a27ff16cd700b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/80482
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 56b73e1..f73c1da 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -16,9 +16,9 @@
 group("default") {
   testonly = true
   deps = [
-    "fuzzers",
-    "samples:tint",
-    "src:libtint",
-    "test:tint_unittests",
+    "src/tint:libtint",
+    "src/tint/cmd:tint",
+    "src/tint/fuzzers",
+    "test/tint:tint_unittests",
   ]
 }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 769a0c6..eadb633 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -389,13 +389,13 @@
 endfunction()
 
 add_subdirectory(third_party)
-add_subdirectory(src)
+add_subdirectory(src/tint)
 if (TINT_BUILD_SAMPLES)
-  add_subdirectory(samples)
+  add_subdirectory(src/tint/cmd)
 endif()
 
 if (TINT_BUILD_FUZZERS)
-  add_subdirectory(fuzzers)
+  add_subdirectory(src/tint/fuzzers)
 endif()
 
 add_custom_target(tint-lint
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4d59e35..329011e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,7 +20,7 @@
 All submissions, including submissions by project members, require review. We
 use [Dawn's Gerrit](https://dawn-review.googlesource.com/) for this purpose.
 
-Submissions should follow the [Tint style guide](docs/style_guide.md).
+Submissions should follow the [Tint style guide](docs/tint/style_guide.md).
 
 ## Pushing to Gerrit
 
diff --git a/Doxyfile b/Doxyfile
index 211e3b8..27e04bd 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -786,11 +786,11 @@
 # Note: If this tag is empty the current directory is searched.
 
 INPUT                  = CODE_OF_CONDUCT.md \
-                         fuzzers/tint_spirv_tools_fuzzer \
+                         src/tint/fuzzers/tint_spirv_tools_fuzzer \
                          src \
                          tools/src \
-                         fuzzers/tint_spirv_tools_fuzzer \
-                         fuzzers/tint_ast_fuzzer
+                         src/tint/fuzzers/tint_spirv_tools_fuzzer \
+                         src/tint/fuzzers/tint_ast_fuzzer
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/README.md b/README.md
index c632356..fbe6cfb 100644
--- a/README.md
+++ b/README.md
@@ -103,4 +103,4 @@
 Please see the CONTRIBUTING and CODE_OF_CONDUCT files on how to contribute to
 Tint.
 
-Tint has a process for supporting [experimental extensions](docs/experimental_extensions.md).
+Tint has a process for supporting [experimental extensions](docs/tint/experimental_extensions.md).
diff --git a/docs/arch.md b/docs/tint/arch.md
similarity index 100%
rename from docs/arch.md
rename to docs/tint/arch.md
diff --git a/docs/compound_statements.md b/docs/tint/compound_statements.md
similarity index 100%
rename from docs/compound_statements.md
rename to docs/tint/compound_statements.md
diff --git a/docs/coverage-info.md b/docs/tint/coverage-info.md
similarity index 100%
rename from docs/coverage-info.md
rename to docs/tint/coverage-info.md
diff --git a/docs/diagnostics_guide.md b/docs/tint/diagnostics_guide.md
similarity index 100%
rename from docs/diagnostics_guide.md
rename to docs/tint/diagnostics_guide.md
diff --git a/docs/end-to-end-tests.md b/docs/tint/end-to-end-tests.md
similarity index 100%
rename from docs/end-to-end-tests.md
rename to docs/tint/end-to-end-tests.md
diff --git a/docs/experimental_extensions.md b/docs/tint/experimental_extensions.md
similarity index 100%
rename from docs/experimental_extensions.md
rename to docs/tint/experimental_extensions.md
diff --git a/docs/origin-trial-changes.md b/docs/tint/origin-trial-changes.md
similarity index 100%
rename from docs/origin-trial-changes.md
rename to docs/tint/origin-trial-changes.md
diff --git a/docs/spirv-input-output-variables.md b/docs/tint/spirv-input-output-variables.md
similarity index 100%
rename from docs/spirv-input-output-variables.md
rename to docs/tint/spirv-input-output-variables.md
diff --git a/docs/spirv-ptr-ref.md b/docs/tint/spirv-ptr-ref.md
similarity index 100%
rename from docs/spirv-ptr-ref.md
rename to docs/tint/spirv-ptr-ref.md
diff --git a/docs/style_guide.md b/docs/tint/style_guide.md
similarity index 100%
rename from docs/style_guide.md
rename to docs/tint/style_guide.md
diff --git a/docs/translations.md b/docs/tint/translations.md
similarity index 100%
rename from docs/translations.md
rename to docs/tint/translations.md
diff --git a/fuzzers/BUILD.gn b/fuzzers/BUILD.gn
deleted file mode 100644
index e674ab1..0000000
--- a/fuzzers/BUILD.gn
+++ /dev/null
@@ -1,334 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("../tint_overrides_with_defaults.gni")
-
-# Fuzzers - Libfuzzer based fuzzing targets for Chromium
-# To run the fuzzers outside of Chromium, use the CMake based builds.
-
-if (build_with_chromium) {
-  import("//testing/libfuzzer/fuzzer_test.gni")
-
-  fuzzer_corpus_wgsl_dir = "${target_gen_dir}/fuzzer_corpus_wgsl"
-  action("tint_generate_wgsl_corpus") {
-    script = "generate_wgsl_corpus.py"
-    sources = [ "generate_wgsl_corpus.py" ]
-    args = [
-      rebase_path("${tint_root_dir}/test", root_build_dir),
-      rebase_path(fuzzer_corpus_wgsl_dir, root_build_dir),
-    ]
-    outputs = [ fuzzer_corpus_wgsl_dir ]
-  }
-
-  tint_fuzzer_common_libfuzzer_options = [
-    "only_ascii=1",
-    "max_len=10000",
-  ]
-
-  tint_ast_fuzzer_common_libfuzzer_options =
-      tint_fuzzer_common_libfuzzer_options + [
-        "cross_over=0",
-        "mutate_depth=1",
-        "tint_enable_all_mutations=false",
-        "tint_mutation_batch_size=5",
-      ]
-
-  tint_regex_fuzzer_common_libfuzzer_options =
-      tint_fuzzer_common_libfuzzer_options + [
-        "cross_over=0",
-        "mutate_depth=1",
-      ]
-
-  # fuzzer_test doesn't have configs members, so need to define them in an empty
-  # source_set.
-
-  source_set("tint_fuzzer_common_src") {
-    public_configs = [
-      "${tint_root_dir}/src:tint_config",
-      "${tint_root_dir}/src:tint_common_config",
-    ]
-
-    public_deps = [
-      "${tint_root_dir}/src:libtint",
-      "${tint_spirv_tools_dir}/:spvtools_val",
-    ]
-
-    sources = [
-      "data_builder.h",
-      "mersenne_twister_engine.cc",
-      "mersenne_twister_engine.h",
-      "random_generator.cc",
-      "random_generator.h",
-      "random_generator_engine.cc",
-      "random_generator_engine.h",
-      "shuffle_transform.cc",
-      "shuffle_transform.h",
-      "tint_common_fuzzer.cc",
-      "tint_common_fuzzer.h",
-      "tint_reader_writer_fuzzer.h",
-      "transform_builder.h",
-    ]
-  }
-
-  source_set("tint_fuzzer_common_with_init_src") {
-    public_deps = [ ":tint_fuzzer_common_src" ]
-
-    sources = [
-      "cli.cc",
-      "cli.h",
-      "fuzzer_init.cc",
-      "fuzzer_init.h",
-    ]
-  }
-
-  if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
-    fuzzer_test("tint_ast_clone_fuzzer") {
-      sources = [ "tint_ast_clone_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_ast_wgsl_writer_fuzzer") {
-      sources = [ "tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc" ]
-      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
-      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_regex_wgsl_writer_fuzzer") {
-      sources = [ "tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc" ]
-      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
-      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_wgsl_reader_wgsl_writer_fuzzer") {
-      sources = [ "tint_wgsl_reader_wgsl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-  }
-
-  if (tint_build_wgsl_reader && tint_build_spv_writer) {
-    fuzzer_test("tint_all_transforms_fuzzer") {
-      sources = [ "tint_all_transforms_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_ast_spv_writer_fuzzer") {
-      sources = [ "tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc" ]
-      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
-      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_binding_remapper_fuzzer") {
-      sources = [ "tint_binding_remapper_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_first_index_offset_fuzzer") {
-      sources = [ "tint_first_index_offset_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_regex_spv_writer_fuzzer") {
-      sources = [ "tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc" ]
-      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
-      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_renamer_fuzzer") {
-      sources = [ "tint_renamer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_robustness_fuzzer") {
-      sources = [ "tint_robustness_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_single_entry_point_fuzzer") {
-      sources = [ "tint_single_entry_point_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_vertex_pulling_fuzzer") {
-      sources = [ "tint_vertex_pulling_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") {
-      sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-  }
-
-  if (tint_build_wgsl_reader && tint_build_hlsl_writer) {
-    fuzzer_test("tint_ast_hlsl_writer_fuzzer") {
-      sources = [ "tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc" ]
-      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
-      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_regex_hlsl_writer_fuzzer") {
-      sources = [ "tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc" ]
-      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
-      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_wgsl_reader_hlsl_writer_fuzzer") {
-      sources = [ "tint_wgsl_reader_hlsl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-  }
-
-  if (tint_build_wgsl_reader && tint_build_msl_writer) {
-    fuzzer_test("tint_ast_msl_writer_fuzzer") {
-      sources = [ "tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc" ]
-      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
-      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_regex_msl_writer_fuzzer") {
-      sources = [ "tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc" ]
-      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
-      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-
-    fuzzer_test("tint_wgsl_reader_msl_writer_fuzzer") {
-      sources = [ "tint_wgsl_reader_msl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init_src" ]
-      dict = "dictionary.txt"
-      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
-      seed_corpus = fuzzer_corpus_wgsl_dir
-      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
-    }
-  }
-
-  if (tint_build_wgsl_reader && tint_build_hlsl_writer &&
-      tint_build_msl_writer && tint_build_spv_writer &&
-      tint_build_wgsl_writer) {
-    executable("tint_black_box_fuzz_target") {
-      sources = [ "tint_black_box_fuzz_target.cc" ]
-      deps = [ ":tint_fuzzer_common_src" ]
-    }
-  }
-
-  group("fuzzers") {
-    testonly = true
-    deps = []
-
-    if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
-      deps += [
-        ":tint_ast_clone_fuzzer",
-        ":tint_ast_wgsl_writer_fuzzer",
-        ":tint_regex_wgsl_writer_fuzzer",
-        ":tint_wgsl_reader_wgsl_writer_fuzzer",
-      ]
-    }
-    if (tint_build_wgsl_reader && tint_build_spv_writer) {
-      deps += [
-        ":tint_all_transforms_fuzzer",
-        ":tint_ast_spv_writer_fuzzer",
-        ":tint_binding_remapper_fuzzer",
-        ":tint_first_index_offset_fuzzer",
-        ":tint_regex_spv_writer_fuzzer",
-        ":tint_renamer_fuzzer",
-        ":tint_robustness_fuzzer",
-        ":tint_single_entry_point_fuzzer",
-        ":tint_vertex_pulling_fuzzer",
-        ":tint_wgsl_reader_spv_writer_fuzzer",
-      ]
-    }
-    if (tint_build_wgsl_reader && tint_build_hlsl_writer) {
-      deps += [
-        ":tint_ast_hlsl_writer_fuzzer",
-        ":tint_regex_hlsl_writer_fuzzer",
-        ":tint_wgsl_reader_hlsl_writer_fuzzer",
-      ]
-    }
-    if (tint_build_wgsl_reader && tint_build_msl_writer) {
-      deps += [
-        ":tint_ast_msl_writer_fuzzer",
-        ":tint_regex_msl_writer_fuzzer",
-        ":tint_wgsl_reader_msl_writer_fuzzer",
-      ]
-    }
-    if (tint_build_wgsl_reader && tint_build_hlsl_writer &&
-        tint_build_msl_writer && tint_build_spv_writer &&
-        tint_build_wgsl_writer) {
-      deps += [ ":tint_black_box_fuzz_target" ]
-    }
-  }
-} else {
-  group("fuzzers") {
-  }
-}
diff --git a/fuzzers/cli.cc b/fuzzers/cli.cc
deleted file mode 100644
index 2f8c3ba..0000000
--- a/fuzzers/cli.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/cli.h"
-
-#include <cstring>
-#include <iostream>
-#include <limits>
-#include <sstream>
-#include <string>
-#include <utility>
-
-namespace tint {
-namespace fuzzers {
-namespace {
-
-const char* const kHelpMessage = R"(
-This is a fuzzer for the Tint compiler that works by mutating the AST.
-
-Below is a list of all supported parameters for this fuzzer. You may want to
-run it with -help=1 to check out libfuzzer parameters.
-
-  -tint_dump_input=
-                       If `true`, the fuzzer will dump input data to a file with
-                       name tint_input_<hash>.spv/wgsl, where the hash is the hash
-                       of the input data.
-
-  -tint_help
-                       Show this message. Note that there is also a -help=1
-                       parameter that will display libfuzzer's help message.
-
-  -tint_enforce_validity=
-                       If `true`, the fuzzer will enforce that Tint does not
-                       generate invalid shaders. Currently `false` by default
-                       since options provided by the fuzzer are not guaranteed
-                       to be correct.
-                       See https://bugs.chromium.org/p/tint/issues/detail?id=1356
-)";
-
-[[noreturn]] void InvalidParam(const std::string& param) {
-  std::cout << "Invalid value for " << param << std::endl;
-  std::cout << kHelpMessage << std::endl;
-  exit(1);
-}
-
-bool ParseBool(const std::string& value, bool* out) {
-  if (value.compare("true") == 0) {
-    *out = true;
-  } else if (value.compare("false") == 0) {
-    *out = false;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-CliParams ParseCliParams(int* argc, char** argv) {
-  CliParams cli_params;
-  auto help = false;
-
-  for (int i = *argc - 1; i > 0; --i) {
-    std::string param(argv[i]);
-    auto recognized_parameter = true;
-
-    if (std::string::npos != param.find("-tint_dump_input=")) {
-      if (!ParseBool(param.substr(std::string("-tint_dump_input=").length()),
-                     &cli_params.dump_input)) {
-        InvalidParam(param);
-      }
-    } else if (std::string::npos != param.find("-tint_help")) {
-      help = true;
-    } else if (std::string::npos != param.find("-tint_enforce_validity=")) {
-      if (!ParseBool(
-              param.substr(std::string("-tint_enforce_validity=").length()),
-              &cli_params.enforce_validity)) {
-        InvalidParam(param);
-      }
-    } else {
-      recognized_parameter = false;
-    }
-
-    if (recognized_parameter) {
-      // Remove the recognized parameter from the list of all parameters by
-      // swapping it with the last one. This will suppress warnings in the
-      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
-      // that all user-defined parameters start with two dashes. However, we are
-      // forced to use a single one to make the fuzzer compatible with the
-      // ClusterFuzz.
-      std::swap(argv[i], argv[*argc - 1]);
-      *argc -= 1;
-    }
-  }
-
-  if (help) {
-    std::cout << kHelpMessage << std::endl;
-    exit(0);
-  }
-
-  return cli_params;
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/cli.h b/fuzzers/cli.h
deleted file mode 100644
index 6765748..0000000
--- a/fuzzers/cli.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_CLI_H_
-#define FUZZERS_CLI_H_
-
-#include <cstdint>
-
-namespace tint {
-namespace fuzzers {
-
-/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
-/// help message
-struct CliParams {
-  bool dump_input = false;
-  bool enforce_validity = false;
-};
-
-/// @brief Parses CLI parameters.
-///
-/// This function will exit the process with non-zero return code if some
-/// parameters are invalid. This function will remove recognized parameters from
-/// `argv` and adjust `argc` accordingly.
-///
-/// @param argc - the total number of parameters.
-/// @param argv - array of all CLI parameters.
-/// @return parsed parameters.
-CliParams ParseCliParams(int* argc, char** argv);
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_CLI_H_
diff --git a/fuzzers/data_builder.h b/fuzzers/data_builder.h
deleted file mode 100644
index 265f5dd..0000000
--- a/fuzzers/data_builder.h
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_DATA_BUILDER_H_
-#define FUZZERS_DATA_BUILDER_H_
-
-#include <cassert>
-#include <functional>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-#include "src/writer/hlsl/generator.h"
-#include "src/writer/msl/generator.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Builder for generic pseudo-random data
-class DataBuilder {
- public:
-  /// @brief Initializes the internal engine using a seed value
-  /// @param seed - seed value passed to engine
-  explicit DataBuilder(uint64_t seed) : generator_(seed) {}
-
-  /// @brief Initializes the internal engine using seed data
-  /// @param data - data fuzzer to calculate seed from
-  /// @param size - size of data buffer
-  explicit DataBuilder(const uint8_t* data, size_t size)
-      : generator_(RandomGenerator::CalculateSeed(data, size)) {
-    assert(data != nullptr && "|data| must be !nullptr");
-  }
-
-  ~DataBuilder() = default;
-  DataBuilder(DataBuilder&&) = default;
-
-  /// Generate pseudo-random data of a specific type
-  /// @tparam T - type of data to produce
-  /// @returns pseudo-random data of type T
-  template <typename T>
-  T build() {
-    return BuildImpl<T>::impl(this);
-  }
-
-  /// Generate pseudo-random data of a specific type in a vector
-  /// @tparam T - data type held vector
-  /// @returns pseudo-random data of type std::vector<T>
-  template <typename T>
-  std::vector<T> vector() {
-    auto count = build<uint8_t>();
-    std::vector<T> out(count);
-    for (uint8_t i = 0; i < count; i++) {
-      out[i] = build<T>();
-    }
-    return out;
-  }
-
-  /// Generate complex pseudo-random data of a specific type in a vector
-  /// @tparam T - data type held vector
-  /// @tparam Callback - callback that takes in a DataBuilder* and returns a T
-  /// @param generate - callback for generating each instance of T
-  /// @returns pseudo-random data of type std::vector<T>
-  template <typename T, typename Callback>
-  std::vector<T> vector(Callback generate) {
-    auto count = build<uint8_t>();
-    std::vector<T> out(count);
-    for (size_t i = 0; i < count; i++) {
-      out[i] = generate(this);
-    }
-    return out;
-  }
-
-  /// Generate an pseudo-random entry to a enum class.
-  /// Assumes enum is tightly packed starting at 0.
-  /// @tparam T - type of enum class
-  /// @param count - number of entries in enum class
-  /// @returns a random enum class entry
-  template <typename T>
-  T enum_class(uint32_t count) {
-    return static_cast<T>(generator_.Get4Bytes() % count);
-  }
-
- private:
-  RandomGenerator generator_;
-
-  // Disallow copy & assign
-  DataBuilder(const DataBuilder&) = delete;
-  DataBuilder& operator=(const DataBuilder&) = delete;
-
-  /// Get N bytes of pseudo-random data
-  /// @param out - pointer to location to save data
-  /// @param n - number of bytes to get
-  void build(void* out, size_t n) {
-    assert(out != nullptr && "|out| cannot be nullptr");
-    assert(n > 0 && "|n| must be > 0");
-
-    generator_.GetNBytes(reinterpret_cast<uint8_t*>(out), n);
-  }
-
-  /// Generate pseudo-random data of a specific type into an output var
-  /// @tparam T - type of data to produce
-  /// @param out - output var to generate into
-  template <typename T>
-  void build(T& out) {
-    out = build<T>();
-  }
-
-  /// Implementation of ::build<T>()
-  /// @tparam T - type of data to produce
-  template <typename T>
-  struct BuildImpl {
-    /// Generate a pseudo-random variable of type T
-    /// @param b - data builder to use
-    /// @returns a variable of type T filled with pseudo-random data
-    static T impl(DataBuilder* b) {
-      T out{};
-      b->build(&out, sizeof(T));
-      return out;
-    }
-  };
-
-  /// Specialization for std::string
-  template <>
-  struct BuildImpl<std::string> {
-    /// Generate a pseudo-random string
-    /// @param b - data builder to use
-    /// @returns a string filled with pseudo-random data
-    static std::string impl(DataBuilder* b) {
-      auto count = b->build<uint8_t>();
-      if (count == 0) {
-        return "";
-      }
-      std::vector<uint8_t> source(count);
-      b->build(source.data(), count);
-      return {source.begin(), source.end()};
-    }
-  };
-
-  /// Specialization for bool
-  template <>
-  struct BuildImpl<bool> {
-    /// Generate a pseudo-random bool
-    /// @param b - data builder to use
-    /// @returns a boolean with even odds of being true or false
-    static bool impl(DataBuilder* b) { return b->generator_.GetBool(); }
-  };
-
-  /// Specialization for writer::msl::Options
-  template <>
-  struct BuildImpl<writer::msl::Options> {
-    /// Generate a pseudo-random writer::msl::Options struct
-    /// @param b - data builder to use
-    /// @returns writer::msl::Options filled with pseudo-random data
-    static writer::msl::Options impl(DataBuilder* b) {
-      writer::msl::Options out{};
-      b->build(out.buffer_size_ubo_index);
-      b->build(out.fixed_sample_mask);
-      b->build(out.emit_vertex_point_size);
-      b->build(out.disable_workgroup_init);
-      b->build(out.array_length_from_uniform);
-      return out;
-    }
-  };
-
-  /// Specialization for writer::hlsl::Options
-  template <>
-  struct BuildImpl<writer::hlsl::Options> {
-    /// Generate a pseudo-random writer::hlsl::Options struct
-    /// @param b - data builder to use
-    /// @returns writer::hlsl::Options filled with pseudo-random data
-    static writer::hlsl::Options impl(DataBuilder* b) {
-      writer::hlsl::Options out{};
-      b->build(out.root_constant_binding_point);
-      b->build(out.disable_workgroup_init);
-      b->build(out.array_length_from_uniform);
-      return out;
-    }
-  };
-
-  /// Specialization for writer::spirv::Options
-  template <>
-  struct BuildImpl<writer::spirv::Options> {
-    /// Generate a pseudo-random writer::spirv::Options struct
-    /// @param b - data builder to use
-    /// @returns writer::spirv::Options filled with pseudo-random data
-    static writer::spirv::Options impl(DataBuilder* b) {
-      writer::spirv::Options out{};
-      b->build(out.emit_vertex_point_size);
-      b->build(out.disable_workgroup_init);
-      return out;
-    }
-  };
-
-  /// Specialization for writer::ArrayLengthFromUniformOptions
-  template <>
-  struct BuildImpl<writer::ArrayLengthFromUniformOptions> {
-    /// Generate a pseudo-random writer::ArrayLengthFromUniformOptions struct
-    /// @param b - data builder to use
-    /// @returns writer::ArrayLengthFromUniformOptions filled with pseudo-random
-    /// data
-    static writer::ArrayLengthFromUniformOptions impl(DataBuilder* b) {
-      writer::ArrayLengthFromUniformOptions out{};
-      b->build(out.ubo_binding);
-      b->build(out.bindpoint_to_size_index);
-      return out;
-    }
-  };
-
-  /// Specialization for std::unordered_map<K, V>
-  template <typename K, typename V>
-  struct BuildImpl<std::unordered_map<K, V>> {
-    /// Generate a pseudo-random std::unordered_map<K, V>
-    /// @param b - data builder to use
-    /// @returns std::unordered_map<K, V> filled with
-    /// pseudo-random data
-    static std::unordered_map<K, V> impl(DataBuilder* b) {
-      std::unordered_map<K, V> out;
-      uint8_t count = b->build<uint8_t>();
-      for (uint8_t i = 0; i < count; ++i) {
-        out.emplace(b->build<K>(), b->build<V>());
-      }
-      return out;
-    }
-  };
-};
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_DATA_BUILDER_H_
diff --git a/fuzzers/fuzzer_init.cc b/fuzzers/fuzzer_init.cc
deleted file mode 100644
index f651713..0000000
--- a/fuzzers/fuzzer_init.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/cli.h"
-
-namespace tint {
-namespace fuzzers {
-
-namespace {
-CliParams cli_params;
-}
-
-const CliParams& GetCliParams() {
-  return cli_params;
-}
-
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
-  cli_params = ParseCliParams(argc, *argv);
-  return 0;
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/fuzzer_init.h b/fuzzers/fuzzer_init.h
deleted file mode 100644
index fa96ce5..0000000
--- a/fuzzers/fuzzer_init.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_FUZZER_INIT_H_
-#define FUZZERS_FUZZER_INIT_H_
-
-#include "fuzzers/cli.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Returns the common CliParams parsed and populated by LLVMFuzzerInitialize()
-const CliParams& GetCliParams();
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_FUZZER_INIT_H_
diff --git a/fuzzers/generate_spirv_corpus.py b/fuzzers/generate_spirv_corpus.py
deleted file mode 100644
index 93d24a2..0000000
--- a/fuzzers/generate_spirv_corpus.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python3
-
-# 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.
-
-# Collect all .spvasm files under a given directory, assemble them using
-# spirv-as, and emit the assembled binaries to a given corpus directory,
-# flattening their file names by replacing path separators with underscores.
-# If the output directory already exists, it will be deleted and re-created.
-# Files ending with ".expected.spvasm" are skipped.
-#
-# The intended use of this script is to generate a corpus of SPIR-V
-# binaries for fuzzing.
-#
-# Usage:
-#    generate_spirv_corpus.py <input_dir> <corpus_dir> <path to spirv-as>
-
-
-import os
-import pathlib
-import shutil
-import subprocess
-import sys
-
-
-def list_spvasm_files(root_search_dir):
-    for root, folders, files in os.walk(root_search_dir):
-        for filename in folders + files:
-            if pathlib.Path(filename).suffix == ".spvasm":
-                yield os.path.join(root, filename)
-
-
-def main():
-    if len(sys.argv) != 4:
-        print("Usage: " + sys.argv[0] +
-              " <input dir> <output dir> <spirv-as path>")
-        return 1
-    input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep))
-    corpus_dir: str = os.path.abspath(sys.argv[2])
-    spirv_as_path: str = os.path.abspath(sys.argv[3])
-    if os.path.exists(corpus_dir):
-        shutil.rmtree(corpus_dir)
-    os.makedirs(corpus_dir)
-
-    # It might be that some of the attempts to convert SPIR-V assembly shaders
-    # into SPIR-V binaries go wrong. It is sensible to tolerate a small number
-    # of such errors, to avoid fuzzer preparation failing due to bugs in
-    # spirv-as. But it is important to know when a large number of failures
-    # occur, in case something is more deeply wrong.
-    num_errors = 0
-    max_tolerated_errors = 10
-    logged_errors = ""
-
-    for in_file in list_spvasm_files(input_dir):
-        if in_file.endswith(".expected.spvasm"):
-            continue
-        out_file = os.path.splitext(corpus_dir + os.sep +
-                                    in_file[len(input_dir) + 1:]
-                                    .replace(os.sep, '_'))[0] + ".spv"
-        cmd = [spirv_as_path,
-               "--target-env",
-               "spv1.3",
-               in_file,
-               "-o",
-               out_file]
-        proc = subprocess.Popen(cmd,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.PIPE)
-        stdout, stderr = proc.communicate()
-        if proc.returncode != 0:
-            num_errors += 1
-            logged_errors += "Error running " + " ".join(cmd) + ": " + stdout.decode('utf-8') + stderr.decode('utf-8')
-
-    if num_errors > max_tolerated_errors:
-        print("Too many (" + str(num_errors) + ") errors occured while generating the SPIR-V corpus.")
-        print(logged_errors)
-        return 1
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/fuzzers/generate_wgsl_corpus.py b/fuzzers/generate_wgsl_corpus.py
deleted file mode 100644
index c07d045..0000000
--- a/fuzzers/generate_wgsl_corpus.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-
-# 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.
-
-# Collect all .wgsl files under a given directory and copy them to a given
-# corpus directory, flattening their file names by replacing path
-# separators with underscores. If the output directory already exists, it
-# will be deleted and re-created. Files ending with ".expected.spvasm" are
-# skipped.
-#
-# The intended use of this script is to generate a corpus of WGSL shaders
-# for fuzzing.
-#
-# Usage:
-#    generate_wgsl_corpus.py <input_dir> <corpus_dir>
-
-
-import os
-import pathlib
-import shutil
-import sys
-
-
-def list_wgsl_files(root_search_dir):
-    for root, folders, files in os.walk(root_search_dir):
-        for filename in folders + files:
-            if pathlib.Path(filename).suffix == '.wgsl':
-                yield os.path.join(root, filename)
-
-
-def main():
-    if len(sys.argv) != 3:
-        print("Usage: " + sys.argv[0] + " <input dir> <output dir>")
-        return 1
-    input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep))
-    corpus_dir: str = os.path.abspath(sys.argv[2])
-    if os.path.exists(corpus_dir):
-        shutil.rmtree(corpus_dir)
-    os.makedirs(corpus_dir)
-    for in_file in list_wgsl_files(input_dir):
-        if in_file.endswith(".expected.wgsl"):
-            continue
-        out_file = in_file[len(input_dir) + 1:].replace(os.sep, '_')
-        shutil.copy(in_file, corpus_dir + os.sep + out_file)
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/fuzzers/mersenne_twister_engine.cc b/fuzzers/mersenne_twister_engine.cc
deleted file mode 100644
index 951b706..0000000
--- a/fuzzers/mersenne_twister_engine.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/mersenne_twister_engine.h"
-
-#include <algorithm>
-#include <cassert>
-
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace fuzzers {
-
-namespace {
-
-/// Generate integer from uniform distribution
-/// @tparam I - integer type
-/// @param engine - random number engine to use
-/// @param lower - Lower bound of integer generated
-/// @param upper - Upper bound of integer generated
-/// @returns i, where lower <= i < upper
-template <typename I>
-I RandomInteger(std::mt19937_64* engine, I lower, I upper) {
-  assert(lower < upper && "|lower| must be strictly less than |upper|");
-  return std::uniform_int_distribution<I>(lower, upper - 1)(*engine);
-}
-
-}  // namespace
-
-MersenneTwisterEngine::MersenneTwisterEngine(uint64_t seed) : engine_(seed) {}
-
-uint32_t MersenneTwisterEngine::RandomUInt32(uint32_t lower, uint32_t upper) {
-  return RandomInteger(&engine_, lower, upper);
-}
-
-uint64_t MersenneTwisterEngine::RandomUInt64(uint64_t lower, uint64_t upper) {
-  return RandomInteger(&engine_, lower, upper);
-}
-
-void MersenneTwisterEngine::RandomNBytes(uint8_t* dest, size_t n) {
-  assert(dest && "|dest| must not be nullptr");
-  std::generate(
-      dest, dest + n,
-      std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_));
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/mersenne_twister_engine.h b/fuzzers/mersenne_twister_engine.h
deleted file mode 100644
index f384cac..0000000
--- a/fuzzers/mersenne_twister_engine.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_MERSENNE_TWISTER_ENGINE_H_
-#define FUZZERS_MERSENNE_TWISTER_ENGINE_H_
-
-#include <random>
-
-#include "fuzzers/random_generator_engine.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Standard MT based random number generation
-class MersenneTwisterEngine : public RandomGeneratorEngine {
- public:
-  /// @brief Initializes using provided seed
-  /// @param seed - seed value to use
-  explicit MersenneTwisterEngine(uint64_t seed);
-  ~MersenneTwisterEngine() override = default;
-
-  /// Generate random uint32_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  uint32_t RandomUInt32(uint32_t lower, uint32_t upper) override;
-
-  /// Get random uint64_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  uint64_t RandomUInt64(uint64_t lower, uint64_t upper) override;
-
-  /// Get N bytes of pseudo-random data
-  /// @param dest - memory location to store data
-  /// @param n - number of bytes of data to generate
-  void RandomNBytes(uint8_t* dest, size_t n) override;
-
- private:
-  // Disallow copy & assign
-  MersenneTwisterEngine(const MersenneTwisterEngine&) = delete;
-  MersenneTwisterEngine& operator=(const MersenneTwisterEngine&) = delete;
-
-  std::mt19937_64 engine_;
-
-};  // class MersenneTwisterEngine
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_MERSENNE_TWISTER_ENGINE_H_
diff --git a/fuzzers/random_generator.cc b/fuzzers/random_generator.cc
deleted file mode 100644
index c535450..0000000
--- a/fuzzers/random_generator.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/random_generator.h"
-
-#include <algorithm>
-#include <cassert>
-#include <utility>
-
-#include "fuzzers/mersenne_twister_engine.h"
-#include "fuzzers/random_generator_engine.h"
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace fuzzers {
-
-namespace {
-
-/// Calculate the hash for the contents of a c-style data buffer
-/// This is intentionally not implemented as a generic override of HashCombine
-/// in "src/utils/hash.h", because it conflicts with the vardiac override for
-/// the case where a pointer and an integer are being hashed.
-/// @param data - pointer to buffer to be hashed
-/// @param size - number of elements in buffer
-/// @returns hash of the data in the buffer
-size_t HashBuffer(const uint8_t* data, const size_t size) {
-  size_t hash = 102931;
-  utils::HashCombine(&hash, size);
-  for (size_t i = 0; i < size; i++) {
-    utils::HashCombine(&hash, data[i]);
-  }
-  return hash;
-}
-
-}  // namespace
-
-RandomGenerator::RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine)
-    : engine_(std::move(engine)) {}
-
-RandomGenerator::RandomGenerator(uint64_t seed)
-    : RandomGenerator(std::make_unique<MersenneTwisterEngine>(seed)) {}
-
-uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) {
-  assert(lower < upper && "|lower| must be strictly less than |upper|");
-  return engine_->RandomUInt32(lower, upper);
-}
-
-uint32_t RandomGenerator::GetUInt32(uint32_t bound) {
-  assert(bound > 0 && "|bound| must be greater than 0");
-  return engine_->RandomUInt32(0u, bound);
-}
-
-uint64_t RandomGenerator::GetUInt64(uint64_t lower, uint64_t upper) {
-  assert(lower < upper && "|lower| must be strictly less than |upper|");
-  return engine_->RandomUInt64(lower, upper);
-}
-
-uint64_t RandomGenerator::GetUInt64(uint64_t bound) {
-  assert(bound > 0 && "|bound| must be greater than 0");
-  return engine_->RandomUInt64(static_cast<uint64_t>(0), bound);
-}
-
-uint8_t RandomGenerator::GetByte() {
-  uint8_t result;
-  engine_->RandomNBytes(&result, 1);
-  return result;
-}
-
-uint32_t RandomGenerator::Get4Bytes() {
-  uint32_t result;
-  engine_->RandomNBytes(reinterpret_cast<uint8_t*>(&result), 4);
-  return result;
-}
-
-void RandomGenerator::GetNBytes(uint8_t* dest, size_t n) {
-  assert(dest && "|dest| must not be nullptr");
-  engine_->RandomNBytes(dest, n);
-}
-
-bool RandomGenerator::GetBool() {
-  return engine_->RandomUInt32(0u, 2u);
-}
-
-bool RandomGenerator::GetWeightedBool(uint32_t percentage) {
-  static const uint32_t kMaxPercentage = 100;
-  assert(percentage <= kMaxPercentage &&
-         "|percentage| needs to be within [0, 100]");
-  return engine_->RandomUInt32(0u, kMaxPercentage) < percentage;
-}
-
-uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) {
-  assert(data != nullptr && "|data| must be !nullptr");
-
-  // Number of bytes we want to skip at the start of data for the hash.
-  // Fewer bytes may be skipped when `size` is small.
-  // Has lower precedence than kHashDesiredMinBytes.
-  static const int64_t kHashDesiredLeadingSkipBytes = 5;
-  // Minimum number of bytes we want to use in the hash.
-  // Used for short buffers.
-  static const int64_t kHashDesiredMinBytes = 4;
-  // Maximum number of bytes we want to use in the hash.
-  static const int64_t kHashDesiredMaxBytes = 32;
-  auto size_i64 = static_cast<int64_t>(size);
-  auto hash_begin_i64 =
-      std::min(kHashDesiredLeadingSkipBytes,
-               std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0));
-  auto hash_end_i64 = std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64);
-  auto hash_begin = static_cast<size_t>(hash_begin_i64);
-  auto hash_size = static_cast<size_t>(hash_end_i64) - hash_begin;
-  return HashBuffer(data + hash_begin, hash_size);
-}
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/random_generator.h b/fuzzers/random_generator.h
deleted file mode 100644
index 6f37f8e..0000000
--- a/fuzzers/random_generator.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_RANDOM_GENERATOR_H_
-#define FUZZERS_RANDOM_GENERATOR_H_
-
-#include <memory>
-#include <random>
-#include <vector>
-
-#include "fuzzers/random_generator_engine.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Pseudo random generator utility class for fuzzing
-class RandomGenerator {
- public:
-  /// @brief Initializes using provided engine
-  /// @param engine - engine implementation to use
-  explicit RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine);
-
-  /// @brief Creates a MersenneTwisterEngine and initializes using that
-  /// @param seed - seed value to use for engine
-  explicit RandomGenerator(uint64_t seed);
-
-  ~RandomGenerator() = default;
-  RandomGenerator(RandomGenerator&&) = default;
-
-  /// Get uint32_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  uint32_t GetUInt32(uint32_t lower, uint32_t upper);
-
-  /// Get uint32_t value from uniform distribution.
-  /// @param bound - Upper bound of integer generated
-  /// @returns i, where 0 <= i < bound
-  uint32_t GetUInt32(uint32_t bound);
-
-  /// Get uint32_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  uint64_t GetUInt64(uint64_t lower, uint64_t upper);
-
-  /// Get uint64_t value from uniform distribution.
-  /// @param bound - Upper bound of integer generated
-  /// @returns i, where 0 <= i < bound
-  uint64_t GetUInt64(uint64_t bound);
-
-  /// Get 1 byte of pseudo-random data
-  /// Should be more efficient then calling GetNBytes(1);
-  /// @returns 1-byte of random data
-  uint8_t GetByte();
-
-  /// Get 4 bytes of pseudo-random data
-  /// Should be more efficient then calling GetNBytes(4);
-  /// @returns 4-bytes of random data
-  uint32_t Get4Bytes();
-
-  /// Get N bytes of pseudo-random data
-  /// @param dest - memory location to store data
-  /// @param n - number of bytes of data to get
-  void GetNBytes(uint8_t* dest, size_t n);
-
-  /// Get random bool with even odds
-  /// @returns true 50% of the time and false %50 of time.
-  bool GetBool();
-
-  /// Get random bool with weighted odds
-  /// @param percentage - likelihood of true being returned
-  /// @returns true |percentage|% of the time, and false (100 - |percentage|)%
-  /// of the time.
-  bool GetWeightedBool(uint32_t percentage);
-
-  /// Returns a randomly-chosen element from vector v.
-  /// @param v - the vector from which the random element will be selected.
-  /// @return a random element of vector v.
-  template <typename T>
-  inline T GetRandomElement(const std::vector<T>& v) {
-    return v[GetUInt64(0, v.size())];
-  }
-
-  /// Calculate a seed value based on a blob of data.
-  /// Currently hashes bytes near the front of the buffer, after skipping N
-  /// bytes.
-  /// @param data - pointer to data to base calculation off of, must be !nullptr
-  /// @param size - number of elements in |data|, must be > 0
-  static uint64_t CalculateSeed(const uint8_t* data, size_t size);
-
- private:
-  // Disallow copy & assign
-  RandomGenerator(const RandomGenerator&) = delete;
-  RandomGenerator& operator=(const RandomGenerator&) = delete;
-
-  std::unique_ptr<RandomGeneratorEngine> engine_;
-
-};  // class RandomGenerator
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_RANDOM_GENERATOR_H_
diff --git a/fuzzers/random_generator_engine.cc b/fuzzers/random_generator_engine.cc
deleted file mode 100644
index 6c14178..0000000
--- a/fuzzers/random_generator_engine.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/random_generator_engine.h"
-
-namespace tint {
-namespace fuzzers {
-
-// Not in header to avoid weak vtable warnings from clang
-RandomGeneratorEngine::RandomGeneratorEngine() = default;
-RandomGeneratorEngine::~RandomGeneratorEngine() = default;
-RandomGeneratorEngine::RandomGeneratorEngine(RandomGeneratorEngine&&) = default;
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/random_generator_engine.h b/fuzzers/random_generator_engine.h
deleted file mode 100644
index 5df64fa..0000000
--- a/fuzzers/random_generator_engine.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_RANDOM_GENERATOR_ENGINE_H_
-#define FUZZERS_RANDOM_GENERATOR_ENGINE_H_
-
-#include <memory>
-#include <random>
-#include <vector>
-
-namespace tint {
-namespace fuzzers {
-
-/// Wrapper interface around STL random number engine
-class RandomGeneratorEngine {
- public:
-  RandomGeneratorEngine();
-  virtual ~RandomGeneratorEngine();
-  RandomGeneratorEngine(RandomGeneratorEngine&&);
-
-  /// Generate random uint32_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  virtual uint32_t RandomUInt32(uint32_t lower, uint32_t upper) = 0;
-
-  /// Get random uint64_t value from uniform distribution.
-  /// @param lower - lower bound of integer generated
-  /// @param upper - upper bound of integer generated
-  /// @returns i, where lower <= i < upper
-  virtual uint64_t RandomUInt64(uint64_t lower, uint64_t upper) = 0;
-
-  /// Get N bytes of pseudo-random data
-  /// @param dest - memory location to store data
-  /// @param n - number of bytes of data to generate
-  virtual void RandomNBytes(uint8_t* dest, size_t n) = 0;
-
- private:
-  // Disallow copy & assign
-  RandomGeneratorEngine(const RandomGeneratorEngine&) = delete;
-  RandomGeneratorEngine& operator=(const RandomGeneratorEngine&) = delete;
-};  // class RandomGeneratorEngine
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_RANDOM_GENERATOR_ENGINE_H_
diff --git a/fuzzers/random_generator_test.cc b/fuzzers/random_generator_test.cc
deleted file mode 100644
index 53bc5cb..0000000
--- a/fuzzers/random_generator_test.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/random_generator.h"
-
-#include <memory>
-
-#include "gtest/gtest.h"
-
-#include "fuzzers/mersenne_twister_engine.h"
-
-namespace tint {
-namespace fuzzers {
-namespace {
-
-/// Implementation of RandomGeneratorEngine that just returns a stream of
-/// monotonically increasing numbers.
-class MonotonicEngine : public RandomGeneratorEngine {
- public:
-  uint32_t RandomUInt32(uint32_t, uint32_t) override { return next_++; }
-
-  uint64_t RandomUInt64(uint64_t, uint64_t) override { return next_++; }
-
-  void RandomNBytes(uint8_t*, size_t) override {
-    assert(false && "MonotonicDelegate does not implement RandomNBytes");
-  }
-
- private:
-  uint32_t next_ = 0;
-};
-
-class RandomGeneratorTest : public testing::Test {
- public:
-  void SetUp() override { rng_ = std::make_unique<RandomGenerator>(0); }
-
-  void TearDown() override {}
-
- protected:
-  std::unique_ptr<RandomGenerator> rng_;
-};
-
-#ifndef NDEBUG
-TEST_F(RandomGeneratorTest, GetUInt32ReversedBoundsCrashes) {
-  EXPECT_DEATH(rng_->GetUInt32(10, 5), ".*");
-}
-
-TEST_F(RandomGeneratorTest, GetUInt32EmptyBoundsCrashes) {
-  EXPECT_DEATH(rng_->GetUInt32(5, 5), ".*");
-}
-
-TEST_F(RandomGeneratorTest, GetUInt32ZeroBoundCrashes) {
-  EXPECT_DEATH(rng_->GetUInt32(0u), ".*");
-}
-#endif  // NDEBUG
-
-TEST_F(RandomGeneratorTest, GetUInt32SingularReturnsOneValue) {
-  {
-    uint32_t result = rng_->GetUInt32(5u, 6u);
-    ASSERT_EQ(5u, result);
-  }
-  {
-    uint32_t result = rng_->GetUInt32(1u);
-    ASSERT_EQ(0u, result);
-  }
-}
-
-TEST_F(RandomGeneratorTest, GetUInt32StaysInBounds) {
-  {
-    uint32_t result = rng_->GetUInt32(5u, 10u);
-    ASSERT_LE(5u, result);
-    ASSERT_GT(10u, result);
-  }
-  {
-    uint32_t result = rng_->GetUInt32(10u);
-    ASSERT_LE(0u, result);
-    ASSERT_GT(10u, result);
-  }
-}
-
-#ifndef NDEBUG
-TEST_F(RandomGeneratorTest, GetUInt64ReversedBoundsCrashes) {
-  EXPECT_DEATH(rng_->GetUInt64(10, 5), ".*");
-}
-
-TEST_F(RandomGeneratorTest, GetUInt64EmptyBoundsCrashes) {
-  EXPECT_DEATH(rng_->GetUInt64(5, 5), ".*");
-}
-
-TEST_F(RandomGeneratorTest, GetUInt64ZeroBoundCrashes) {
-  EXPECT_DEATH(rng_->GetUInt64(0u), ".*");
-}
-#endif  // NDEBUG
-
-TEST_F(RandomGeneratorTest, GetUInt64SingularReturnsOneValue) {
-  {
-    uint64_t result = rng_->GetUInt64(5u, 6u);
-    ASSERT_EQ(5u, result);
-  }
-  {
-    uint64_t result = rng_->GetUInt64(1u);
-    ASSERT_EQ(0u, result);
-  }
-}
-
-TEST_F(RandomGeneratorTest, GetUInt64StaysInBounds) {
-  {
-    uint64_t result = rng_->GetUInt64(5u, 10u);
-    ASSERT_LE(5u, result);
-    ASSERT_GT(10u, result);
-  }
-  {
-    uint64_t result = rng_->GetUInt64(10u);
-    ASSERT_LE(0u, result);
-    ASSERT_GT(10u, result);
-  }
-}
-
-TEST_F(RandomGeneratorTest, GetByte) {
-  rng_->GetByte();
-}
-
-#ifndef NDEBUG
-TEST_F(RandomGeneratorTest, GetNBytesNullDataBufferCrashes) {
-  EXPECT_DEATH(rng_->GetNBytes(nullptr, 5), ".*");
-}
-#endif  // NDEBUG
-
-TEST_F(RandomGeneratorTest, GetNBytes) {
-  std::vector<uint8_t> data;
-  for (uint32_t i = 25; i < 1000u; i = i + 25) {
-    data.resize(i);
-    rng_->GetNBytes(data.data(), data.size());
-  }
-}
-
-TEST_F(RandomGeneratorTest, GetBool) {
-  rng_->GetBool();
-}
-
-TEST_F(RandomGeneratorTest, GetWeightedBoolZeroAlwaysFalse) {
-  ASSERT_FALSE(rng_->GetWeightedBool(0));
-}
-
-TEST_F(RandomGeneratorTest, GetWeightedBoolHundredAlwaysTrue) {
-  ASSERT_TRUE(rng_->GetWeightedBool(100));
-}
-
-#ifndef NDEBUG
-TEST_F(RandomGeneratorTest, GetWeightedBoolAboveHundredCrashes) {
-  EXPECT_DEATH(rng_->GetWeightedBool(101), ".*");
-  EXPECT_DEATH(rng_->GetWeightedBool(500), ".*");
-}
-#endif  // NDEBUG
-
-TEST_F(RandomGeneratorTest, GetWeightedBool) {
-  for (uint32_t i = 0; i <= 100; i++) {
-    rng_ =
-        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
-    for (uint32_t j = 0; j <= 100; j++) {
-      if (j < i) {
-        ASSERT_TRUE(rng_->GetWeightedBool(i));
-      } else {
-        ASSERT_FALSE(rng_->GetWeightedBool(i));
-      }
-    }
-  }
-}
-
-#ifndef NDEBUG
-TEST_F(RandomGeneratorTest, GetRandomElementEmptyVectorCrashes) {
-  std::vector<uint8_t> v;
-  EXPECT_DEATH(rng_->GetRandomElement(v), ".*");
-}
-#endif  // NDEBUG
-
-TEST_F(RandomGeneratorTest, GetRandomElement) {
-  std::vector<uint32_t> v;
-  for (uint32_t i = 25; i < 100u; i = i + 25) {
-    rng_ =
-        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
-    v.resize(i);
-    std::iota(v.begin(), v.end(), 0);
-    for (uint32_t j = 0; j < i; j++) {
-      EXPECT_EQ(j, rng_->GetRandomElement(v));
-    }
-  }
-}
-
-}  // namespace
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/shuffle_transform.cc b/fuzzers/shuffle_transform.cc
deleted file mode 100644
index 1407f10..0000000
--- a/fuzzers/shuffle_transform.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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 "fuzzers/shuffle_transform.h"
-
-#include <random>
-
-#include "src/program_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-ShuffleTransform::ShuffleTransform(size_t seed) : seed_(seed) {}
-
-void ShuffleTransform::Run(CloneContext& ctx,
-                           const tint::transform::DataMap&,
-                           tint::transform::DataMap&) const {
-  auto decls = ctx.src->AST().GlobalDeclarations();
-  auto rng = std::mt19937_64{seed_};
-  std::shuffle(std::begin(decls), std::end(decls), rng);
-  for (auto* decl : decls) {
-    ctx.dst->AST().AddGlobalDeclaration(ctx.Clone(decl));
-  }
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/shuffle_transform.h b/fuzzers/shuffle_transform.h
deleted file mode 100644
index 3e1ca1e..0000000
--- a/fuzzers/shuffle_transform.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 FUZZERS_SHUFFLE_TRANSFORM_H_
-#define FUZZERS_SHUFFLE_TRANSFORM_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// ShuffleTransform reorders the module scope declarations into a random order
-class ShuffleTransform : public tint::transform::Transform {
- public:
-  /// Constructor
-  /// @param seed the random seed to use for the shuffling
-  explicit ShuffleTransform(size_t seed);
-
- protected:
-  void Run(CloneContext& ctx,
-           const tint::transform::DataMap&,
-           tint::transform::DataMap&) const override;
-
- private:
-  size_t seed_;
-};
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_SHUFFLE_TRANSFORM_H_
diff --git a/fuzzers/tint_all_transforms_fuzzer.cc b/fuzzers/tint_all_transforms_fuzzer.cc
deleted file mode 100644
index 5b1b154..0000000
--- a/fuzzers/tint_all_transforms_fuzzer.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  {
-    TransformBuilder tb(data, size);
-    tb.AddTransform<ShuffleTransform>();
-    tb.AddPlatformIndependentPasses();
-
-    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-    fuzzer.SetDumpInput(GetCliParams().dump_input);
-    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-    fuzzer.Run(data, size);
-  }
-
-#if TINT_BUILD_HLSL_WRITER
-  {
-    TransformBuilder tb(data, size);
-    tb.AddTransform<ShuffleTransform>();
-    tb.AddPlatformIndependentPasses();
-
-    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kHLSL);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-    fuzzer.SetDumpInput(GetCliParams().dump_input);
-    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-    fuzzer.Run(data, size);
-  }
-#endif  // TINT_BUILD_HLSL_WRITER
-
-#if TINT_BUILD_MSL_WRITER
-  {
-    TransformBuilder tb(data, size);
-    tb.AddTransform<ShuffleTransform>();
-    tb.AddPlatformIndependentPasses();
-
-    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kMSL);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-    fuzzer.SetDumpInput(GetCliParams().dump_input);
-    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-    fuzzer.Run(data, size);
-  }
-#endif  // TINT_BUILD_MSL_WRITER
-#if TINT_BUILD_SPV_WRITER
-  {
-    TransformBuilder tb(data, size);
-    tb.AddTransform<ShuffleTransform>();
-    tb.AddPlatformIndependentPasses();
-
-    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-    fuzzer.SetDumpInput(GetCliParams().dump_input);
-    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-    fuzzer.Run(data, size);
-  }
-#endif  // TINT_BUILD_SPV_WRITER
-
-  return 0;
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_clone_fuzzer.cc b/fuzzers/tint_ast_clone_fuzzer.cc
deleted file mode 100644
index 85d7a17..0000000
--- a/fuzzers/tint_ast_clone_fuzzer.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2020 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 <iostream>
-#include <string>
-#include <unordered_set>
-
-#include "src/reader/wgsl/parser_impl.h"
-#include "src/writer/wgsl/generator.h"
-
-#define ASSERT_EQ(A, B)                                  \
-  do {                                                   \
-    decltype(A) assert_a = (A);                          \
-    decltype(B) assert_b = (B);                          \
-    if (assert_a != assert_b) {                          \
-      std::cerr << "ASSERT_EQ(" #A ", " #B ") failed:\n" \
-                << #A << " was: " << assert_a << "\n"    \
-                << #B << " was: " << assert_b << "\n";   \
-      __builtin_trap();                                  \
-    }                                                    \
-  } while (false)
-
-#define ASSERT_TRUE(A)                                 \
-  do {                                                 \
-    decltype(A) assert_a = (A);                        \
-    if (!assert_a) {                                   \
-      std::cerr << "ASSERT_TRUE(" #A ") failed:\n"     \
-                << #A << " was: " << assert_a << "\n"; \
-      __builtin_trap();                                \
-    }                                                  \
-  } while (false)
-
-[[noreturn]] void TintInternalCompilerErrorReporter(
-    const tint::diag::List& diagnostics) {
-  auto printer = tint::diag::Printer::create(stderr, true);
-  tint::diag::Formatter{}.format(diagnostics, printer.get());
-  __builtin_trap();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  std::string str(reinterpret_cast<const char*>(data), size);
-
-  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
-
-  tint::Source::File file("test.wgsl", str);
-
-  // Parse the wgsl, create the src program
-  tint::reader::wgsl::ParserImpl parser(&file);
-  parser.set_max_errors(1);
-  if (!parser.Parse()) {
-    return 0;
-  }
-  auto src = parser.program();
-  if (!src.IsValid()) {
-    return 0;
-  }
-
-  // Clone the src program to dst
-  tint::Program dst(src.Clone());
-
-  // Expect the printed strings to match
-  ASSERT_EQ(tint::Program::printer(&src), tint::Program::printer(&dst));
-
-  // Check that none of the AST nodes or type pointers in dst are found in src
-  std::unordered_set<const tint::ast::Node*> src_nodes;
-  for (auto* src_node : src.ASTNodes().Objects()) {
-    src_nodes.emplace(src_node);
-  }
-  std::unordered_set<const tint::sem::Type*> src_types;
-  for (auto* src_type : src.Types()) {
-    src_types.emplace(src_type);
-  }
-  for (auto* dst_node : dst.ASTNodes().Objects()) {
-    ASSERT_EQ(src_nodes.count(dst_node), 0u);
-  }
-  for (auto* dst_type : dst.Types()) {
-    ASSERT_EQ(src_types.count(dst_type), 0u);
-  }
-
-  // Regenerate the wgsl for the src program. We use this instead of the
-  // original source so that reformatting doesn't impact the final wgsl
-  // comparison.
-  std::string src_wgsl;
-  tint::writer::wgsl::Options wgsl_options;
-  {
-    auto result = tint::writer::wgsl::Generate(&src, wgsl_options);
-    ASSERT_TRUE(result.success);
-    src_wgsl = result.wgsl;
-
-    // Move the src program to a temporary that'll be dropped, so that the src
-    // program is released before we attempt to print the dst program. This
-    // guarantee that all the source program nodes and types are destructed and
-    // freed. ASAN should error if there's any remaining references in dst when
-    // we try to reconstruct the WGSL.
-    auto tmp = std::move(src);
-  }
-
-  // Print the dst program, check it matches the original source
-  auto result = tint::writer::wgsl::Generate(&dst, wgsl_options);
-  ASSERT_TRUE(result.success);
-  auto dst_wgsl = result.wgsl;
-  ASSERT_EQ(src_wgsl, dst_wgsl);
-
-  return 0;
-}
diff --git a/fuzzers/tint_ast_fuzzer/BUILD.gn b/fuzzers/tint_ast_fuzzer/BUILD.gn
deleted file mode 100644
index aedb1ab..0000000
--- a/fuzzers/tint_ast_fuzzer/BUILD.gn
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("../../tint_overrides_with_defaults.gni")
-
-if (build_with_chromium) {
-  import("//third_party/protobuf/proto_library.gni")
-
-  proto_library("tint_ast_fuzzer_proto") {
-    sources = [ "protobufs/tint_ast_fuzzer.proto" ]
-    generate_python = false
-    use_protobuf_full = true
-  }
-
-  source_set("tint_ast_fuzzer") {
-    public_configs = [
-      "${tint_root_dir}/src:tint_config",
-      "${tint_root_dir}/src:tint_common_config",
-    ]
-
-    include_dirs = [ "${target_gen_dir}/../.." ]
-
-    deps = [
-      ":tint_ast_fuzzer_proto",
-      "${tint_root_dir}/fuzzers:tint_fuzzer_common_src",
-      "//third_party/protobuf:protobuf_full",
-    ]
-
-    sources = [
-      "cli.cc",
-      "cli.h",
-      "fuzzer.cc",
-      "mutation.cc",
-      "mutation.h",
-      "mutation_finder.cc",
-      "mutation_finder.h",
-      "mutation_finders/replace_identifiers.cc",
-      "mutation_finders/replace_identifiers.h",
-      "mutations/replace_identifier.cc",
-      "mutations/replace_identifier.h",
-      "mutator.cc",
-      "mutator.h",
-      "node_id_map.cc",
-      "node_id_map.h",
-      "override_cli_params.h",
-      "probability_context.cc",
-      "probability_context.h",
-      "protobufs/tint_ast_fuzzer.h",
-      "util.h",
-    ]
-  }
-}
diff --git a/fuzzers/tint_ast_fuzzer/cli.cc b/fuzzers/tint_ast_fuzzer/cli.cc
deleted file mode 100644
index fd6f3fa..0000000
--- a/fuzzers/tint_ast_fuzzer/cli.cc
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-
-#include <cstring>
-#include <iostream>
-#include <limits>
-#include <sstream>
-#include <string>
-#include <utility>
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace {
-
-const char* const kHelpMessage = R"(
-This is a fuzzer for the Tint compiler that works by mutating the AST.
-
-Below is a list of all supported parameters for this fuzzer. You may want to
-run it with -help=1 to check out libfuzzer parameters.
-
-  -tint_enable_all_mutations=
-                       If `false`, the fuzzer will only apply mutations from a
-                       randomly selected subset of mutation types. Otherwise,
-                       all mutation types will be considered. This must be one
-                       of `true` or `false` (without `). By default it's `false`.
-
-  -tint_fuzzing_target=
-                       Specifies the shading language to target during fuzzing.
-                       This must be one or a combination of `wgsl`, `spv`, `hlsl`,
-                       `msl` (without `) separated by commas. By default it's
-                       `wgsl,msl,hlsl,spv`.
-
-  -tint_help
-                       Show this message. Note that there is also a -help=1
-                       parameter that will display libfuzzer's help message.
-
-  -tint_mutation_batch_size=
-                       The number of mutations to apply in a single libfuzzer
-                       mutation session. This must be a numeric value that fits
-                       in type `uint32_t`. By default it's 5.
-)";
-
-bool HasPrefix(const char* str, const char* prefix) {
-  return strncmp(str, prefix, strlen(prefix)) == 0;
-}
-
-[[noreturn]] void InvalidParam(const char* param) {
-  std::cout << "Invalid value for " << param << std::endl;
-  std::cout << kHelpMessage << std::endl;
-  exit(1);
-}
-
-bool ParseBool(const char* value, bool* out) {
-  if (!strcmp(value, "true")) {
-    *out = true;
-  } else if (!strcmp(value, "false")) {
-    *out = false;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool ParseUint32(const char* value, uint32_t* out) {
-  auto parsed = strtoul(value, nullptr, 10);
-  if (parsed > std::numeric_limits<uint32_t>::max()) {
-    return false;
-  }
-  *out = static_cast<uint32_t>(parsed);
-  return true;
-}
-
-bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
-  if (!strcmp(value, "wgsl")) {
-    *out = FuzzingTarget::kWgsl;
-  } else if (!strcmp(value, "spv")) {
-    *out = FuzzingTarget::kSpv;
-  } else if (!strcmp(value, "msl")) {
-    *out = FuzzingTarget::kMsl;
-  } else if (!strcmp(value, "hlsl")) {
-    *out = FuzzingTarget::kHlsl;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-CliParams ParseCliParams(int* argc, char** argv) {
-  CliParams cli_params;
-  auto help = false;
-
-  for (int i = *argc - 1; i > 0; --i) {
-    auto param = argv[i];
-    auto recognized_parameter = true;
-
-    if (HasPrefix(param, "-tint_enable_all_mutations=")) {
-      if (!ParseBool(param + sizeof("-tint_enable_all_mutations=") - 1,
-                     &cli_params.enable_all_mutations)) {
-        InvalidParam(param);
-      }
-    } else if (HasPrefix(param, "-tint_mutation_batch_size=")) {
-      if (!ParseUint32(param + sizeof("-tint_mutation_batch_size=") - 1,
-                       &cli_params.mutation_batch_size)) {
-        InvalidParam(param);
-      }
-    } else if (HasPrefix(param, "-tint_fuzzing_target=")) {
-      auto result = FuzzingTarget::kNone;
-
-      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
-      for (std::string value; std::getline(ss, value, ',');) {
-        auto tmp = FuzzingTarget::kNone;
-        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
-          InvalidParam(param);
-        }
-        result = result | tmp;
-      }
-
-      if (result == FuzzingTarget::kNone) {
-        InvalidParam(param);
-      }
-
-      cli_params.fuzzing_target = result;
-    } else if (!strcmp(param, "-tint_help")) {
-      help = true;
-    } else {
-      recognized_parameter = false;
-    }
-
-    if (recognized_parameter) {
-      // Remove the recognized parameter from the list of all parameters by
-      // swapping it with the last one. This will suppress warnings in the
-      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
-      // that all user-defined parameters start with two dashes. However, we are
-      // forced to use a single one to make the fuzzer compatible with the
-      // ClusterFuzz.
-      std::swap(argv[i], argv[*argc - 1]);
-      *argc -= 1;
-    }
-  }
-
-  if (help) {
-    std::cout << kHelpMessage << std::endl;
-    exit(0);
-  }
-
-  return cli_params;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/cli.h b/fuzzers/tint_ast_fuzzer/cli.h
deleted file mode 100644
index 480772c..0000000
--- a/fuzzers/tint_ast_fuzzer/cli.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_CLI_H_
-#define FUZZERS_TINT_AST_FUZZER_CLI_H_
-
-#include <cstdint>
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// The backend this fuzzer will test.
-enum class FuzzingTarget {
-  kNone = 0,
-  kHlsl = 1 << 0,
-  kMsl = 1 << 1,
-  kSpv = 1 << 2,
-  kWgsl = 1 << 3,
-  kAll = kHlsl | kMsl | kSpv | kWgsl
-};
-
-inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
-}
-
-inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
-}
-
-/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
-/// help message
-struct CliParams {
-  /// Whether to use all mutation finders or only a randomly selected subset of
-  /// them.
-  bool enable_all_mutations = false;
-
-  /// The maximum number of mutations applied during a single mutation session
-  /// (i.e. a call to `ast_fuzzer::Mutate` function).
-  uint32_t mutation_batch_size = 5;
-
-  /// Compiler backends we want to fuzz.
-  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
-};
-
-/// @brief Parses CLI parameters.
-///
-/// This function will exit the process with non-zero return code if some
-/// parameters are invalid. This function will remove recognized parameters from
-/// `argv` and adjust `argc` accordingly.
-///
-/// @param argc - the total number of parameters.
-/// @param argv - array of all CLI parameters.
-/// @return parsed parameters.
-CliParams ParseCliParams(int* argc, char** argv);
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_CLI_H_
diff --git a/fuzzers/tint_ast_fuzzer/fuzzer.cc b/fuzzers/tint_ast_fuzzer/fuzzer.cc
deleted file mode 100644
index a0befa5..0000000
--- a/fuzzers/tint_ast_fuzzer/fuzzer.cc
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstddef>
-#include <cstdint>
-
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/mutator.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-#include "src/reader/wgsl/parser.h"
-#include "src/writer/wgsl/generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace {
-
-CliParams cli_params{};
-
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
-  // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
-  // is invalid.
-  cli_params = ParseCliParams(argc, *argv);
-  // For some fuzz targets it is desirable to force the values of certain CLI
-  // parameters after parsing.
-  OverrideCliParams(cli_params);
-  return 0;
-}
-
-extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
-                                          size_t size,
-                                          size_t max_size,
-                                          unsigned seed) {
-  Source::File file("test.wgsl", {reinterpret_cast<char*>(data), size});
-  auto program = reader::wgsl::Parse(&file);
-  if (!program.IsValid()) {
-    std::cout << "Trying to mutate an invalid program:" << std::endl
-              << program.Diagnostics().str() << std::endl;
-    return 0;
-  }
-
-  // Run the mutator.
-  RandomGenerator generator(seed);
-  ProbabilityContext probability_context(&generator);
-  program = Mutate(std::move(program), &probability_context,
-                   cli_params.enable_all_mutations,
-                   cli_params.mutation_batch_size, nullptr);
-
-  if (!program.IsValid()) {
-    std::cout << "Mutator produced invalid WGSL:" << std::endl
-              << "  seed: " << seed << std::endl
-              << program.Diagnostics().str() << std::endl;
-    return 0;
-  }
-
-  auto result = writer::wgsl::Generate(&program, writer::wgsl::Options());
-  if (!result.success) {
-    std::cout << "Can't generate WGSL for a valid tint::Program:" << std::endl
-              << result.error << std::endl;
-    return 0;
-  }
-
-  if (result.wgsl.size() > max_size) {
-    return 0;
-  }
-
-  // No need to worry about the \0 here. The reason is that if \0 is included by
-  // developer by mistake, it will be considered a part of the string and will
-  // cause all sorts of strange bugs. Thus, unless `data` below is used as a raw
-  // C string, the \0 symbol should be ignored.
-  std::memcpy(  // NOLINT - clang-tidy warns about lack of null termination.
-      data, result.wgsl.data(), result.wgsl.size());
-  return result.wgsl.size();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size == 0) {
-    return 0;
-  }
-
-  struct Target {
-    FuzzingTarget fuzzing_target;
-    OutputFormat output_format;
-    const char* name;
-  };
-
-  Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"},
-                      {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"},
-                      {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"},
-                      {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}};
-
-  for (auto target : targets) {
-    if ((target.fuzzing_target & cli_params.fuzzing_target) !=
-        target.fuzzing_target) {
-      continue;
-    }
-
-    TransformBuilder tb(data, size);
-    tb.AddTransform<tint::transform::Robustness>();
-
-    CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-
-    fuzzer.Run(data, size);
-    if (fuzzer.HasErrors()) {
-      std::cout << "Fuzzing " << target.name << " produced an error"
-                << std::endl;
-      auto printer = tint::diag::Printer::create(stderr, true);
-      tint::diag::Formatter{}.format(fuzzer.Diagnostics(), printer.get());
-    }
-  }
-
-  return 0;
-}
-
-}  // namespace
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutation.cc b/fuzzers/tint_ast_fuzzer/mutation.cc
deleted file mode 100644
index 59c8878..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/mutation.h"
-
-#include <cassert>
-
-#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-Mutation::~Mutation() = default;
-
-std::unique_ptr<Mutation> Mutation::FromMessage(
-    const protobufs::Mutation& message) {
-  switch (message.mutation_case()) {
-    case protobufs::Mutation::kReplaceIdentifier:
-      return std::make_unique<MutationReplaceIdentifier>(
-          message.replace_identifier());
-    case protobufs::Mutation::MUTATION_NOT_SET:
-      assert(false && "Mutation is not set");
-      break;
-  }
-  return nullptr;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutation.h b/fuzzers/tint_ast_fuzzer/mutation.h
deleted file mode 100644
index 77f29f3..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_H_
-#define FUZZERS_TINT_AST_FUZZER_MUTATION_H_
-
-#include <memory>
-#include <vector>
-
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-#include "fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
-
-#include "src/clone_context.h"
-#include "src/program.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// The base class for all the mutations in the fuzzer. Children must override
-/// three methods:
-/// - `IsApplicable` - checks whether it is possible to apply the mutation
-///   in a manner that will lead to a valid program.
-/// - `Apply` - applies the mutation.
-/// - `ToMessage` - converts the mutation data into a protobuf message.
-class Mutation {
- public:
-  /// Virtual destructor.
-  virtual ~Mutation();
-
-  /// @brief Determines whether this mutation is applicable to the `program`.
-  ///
-  /// @param program - the program this mutation will be applied to. The program
-  ///     must be valid.
-  /// @param node_id_map - the map from `tint::ast::` nodes to their ids.
-  /// @return `true` if `Apply` method can be called without breaking the
-  ///     semantics of the `program`.
-  /// @return `false` otherwise.
-  virtual bool IsApplicable(const tint::Program& program,
-                            const NodeIdMap& node_id_map) const = 0;
-
-  /// @brief Applies this mutation to the `clone_context`.
-  ///
-  /// Precondition: `IsApplicable` must return `true` when invoked on the same
-  /// `node_id_map` and `clone_context->src` instance of `tint::Program`. A new
-  /// `tint::Program` that arises in `clone_context` must be valid.
-  ///
-  /// @param node_id_map - the map from `tint::ast::` nodes to their ids.
-  /// @param clone_context - the context that will clone the program with some
-  ///     changes introduced by this mutation.
-  /// @param new_node_id_map - this map will store ids for the mutated and
-  ///     cloned program. This argument cannot be a `nullptr` nor can it point
-  ///     to the same object as `node_id_map`.
-  virtual void Apply(const NodeIdMap& node_id_map,
-                     tint::CloneContext* clone_context,
-                     NodeIdMap* new_node_id_map) const = 0;
-
-  /// @return a protobuf message for this mutation.
-  virtual protobufs::Mutation ToMessage() const = 0;
-
-  /// @brief Converts a protobuf message into the mutation instance.
-  ///
-  /// @param message - a protobuf message.
-  /// @return the instance of this class.
-  static std::unique_ptr<Mutation> FromMessage(
-      const protobufs::Mutation& message);
-};
-
-using MutationList = std::vector<std::unique_ptr<Mutation>>;
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_MUTATION_H_
diff --git a/fuzzers/tint_ast_fuzzer/mutation_finder.cc b/fuzzers/tint_ast_fuzzer/mutation_finder.cc
deleted file mode 100644
index daf1d6d..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation_finder.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/mutation_finder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-MutationFinder::~MutationFinder() = default;
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutation_finder.h b/fuzzers/tint_ast_fuzzer/mutation_finder.h
deleted file mode 100644
index 1dd8732..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation_finder.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
-#define FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
-
-#include <memory>
-#include <vector>
-
-#include "fuzzers/tint_ast_fuzzer/mutation.h"
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-#include "fuzzers/tint_ast_fuzzer/probability_context.h"
-
-#include "src/program.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// Instances of this class traverse the `tint::Program`, looking for
-/// opportunities to apply mutations and return them to the caller.
-///
-/// Ideally, the behaviour of this class (precisely, its `FindMutations` method)
-/// should not be probabilistic. This is useful when mutation finders are used
-/// for test case reduction, because it enables the test case reducer to
-/// systematically explore all available mutations. There may be some
-/// exceptions, however. For example, if a huge number of mutations is returned,
-/// it would make sense to apply only a probabilistically selected subset of
-/// them.
-class MutationFinder {
- public:
-  /// Virtual destructor.
-  virtual ~MutationFinder();
-
-  /// @brief Traverses the `program`, looking for opportunities to apply
-  /// mutations.
-  ///
-  /// @param program - the program being fuzzed.
-  /// @param node_id_map - a map from `tint::ast::` nodes in the `program` to
-  ///     their unique ids.
-  /// @param probability_context - determines various probabilistic stuff in the
-  ///     mutator. This should ideally be used as less as possible.
-  /// @return all the found mutations.
-  virtual MutationList FindMutations(
-      const tint::Program& program,
-      NodeIdMap* node_id_map,
-      ProbabilityContext* probability_context) const = 0;
-
-  /// @brief Compute a probability of applying a single mutation, returned by
-  /// this class.
-  ///
-  /// @param probability_context - contains information about various
-  ///     non-deterministic stuff in the fuzzer.
-  /// @return a number in the range [0; 100] which is a chance of applying a
-  ///     mutation.
-  virtual uint32_t GetChanceOfApplyingMutation(
-      ProbabilityContext* probability_context) const = 0;
-};
-
-using MutationFinderList = std::vector<std::unique_ptr<MutationFinder>>;
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
diff --git a/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc b/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
deleted file mode 100644
index aa23dab..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
-
-#include <memory>
-
-#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
-#include "fuzzers/tint_ast_fuzzer/util.h"
-
-#include "src/sem/expression.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-MutationList MutationFinderReplaceIdentifiers::FindMutations(
-    const tint::Program& program,
-    NodeIdMap* node_id_map,
-    ProbabilityContext* probability_context) const {
-  MutationList result;
-
-  // Go through each variable in the AST and for each user of that variable, try
-  // to replace it with some other variable usage.
-
-  for (const auto* node : program.SemNodes().Objects()) {
-    const auto* sem_variable = tint::As<sem::Variable>(node);
-    if (!sem_variable) {
-      continue;
-    }
-
-    // Iterate over all users of `sem_variable`.
-    for (const auto* user : sem_variable->Users()) {
-      // Get all variables that can be used to replace the `user` of
-      // `sem_variable`.
-      auto candidate_variables = util::GetAllVarsInScope(
-          program, user->Stmt(), [user](const sem::Variable* var) {
-            return var != user->Variable() && var->Type() == user->Type();
-          });
-
-      if (candidate_variables.empty()) {
-        // No suitable replacements have been found.
-        continue;
-      }
-
-      const auto* replacement =
-          candidate_variables[probability_context->GetRandomIndex(
-              candidate_variables)];
-
-      result.push_back(std::make_unique<MutationReplaceIdentifier>(
-          node_id_map->GetId(user->Declaration()),
-          node_id_map->GetId(replacement->Declaration())));
-    }
-  }
-
-  return result;
-}
-
-uint32_t MutationFinderReplaceIdentifiers::GetChanceOfApplyingMutation(
-    ProbabilityContext* probability_context) const {
-  return probability_context->GetChanceOfReplacingIdentifiers();
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h b/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h
deleted file mode 100644
index 70a0b10..0000000
--- a/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
-#define FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
-
-#include "fuzzers/tint_ast_fuzzer/mutation_finder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// Looks for opportunities to apply `MutationReplaceIdentifier`.
-///
-/// Concretely, for each variable in the module, tries to replace its users with
-/// the uses of some other variables.
-class MutationFinderReplaceIdentifiers : public MutationFinder {
- public:
-  MutationList FindMutations(
-      const tint::Program& program,
-      NodeIdMap* node_id_map,
-      ProbabilityContext* probability_context) const override;
-  uint32_t GetChanceOfApplyingMutation(
-      ProbabilityContext* probability_context) const override;
-};
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
diff --git a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc b/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc
deleted file mode 100644
index 6823d21..0000000
--- a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
-
-#include <utility>
-
-#include "fuzzers/tint_ast_fuzzer/util.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-MutationReplaceIdentifier::MutationReplaceIdentifier(
-    protobufs::MutationReplaceIdentifier message)
-    : message_(std::move(message)) {}
-
-MutationReplaceIdentifier::MutationReplaceIdentifier(uint32_t use_id,
-                                                     uint32_t replacement_id) {
-  message_.set_use_id(use_id);
-  message_.set_replacement_id(replacement_id);
-}
-
-bool MutationReplaceIdentifier::IsApplicable(
-    const tint::Program& program,
-    const NodeIdMap& node_id_map) const {
-  const auto* use_ast_node = tint::As<ast::IdentifierExpression>(
-      node_id_map.GetNode(message_.use_id()));
-  if (!use_ast_node) {
-    // Either the `use_id` is invalid or the node is not an
-    // `IdentifierExpression`.
-    return false;
-  }
-
-  const auto* use_sem_node =
-      tint::As<sem::VariableUser>(program.Sem().Get(use_ast_node));
-  if (!use_sem_node) {
-    // Either the semantic information is not present for a `use_node` or that
-    // node is not a variable user.
-    return false;
-  }
-
-  const auto* replacement_ast_node =
-      tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
-  if (!replacement_ast_node) {
-    // Either the `replacement_id` is invalid or is not an id of a variable.
-    return false;
-  }
-
-  const auto* replacement_sem_node = program.Sem().Get(replacement_ast_node);
-  if (!replacement_sem_node) {
-    return false;
-  }
-
-  if (replacement_sem_node == use_sem_node->Variable()) {
-    return false;
-  }
-
-  auto in_scope =
-      util::GetAllVarsInScope(program, use_sem_node->Stmt(),
-                              [replacement_sem_node](const sem::Variable* var) {
-                                return var == replacement_sem_node;
-                              });
-  if (in_scope.empty()) {
-    // The replacement variable is not in scope.
-    return false;
-  }
-
-  return use_sem_node->Type() == replacement_sem_node->Type();
-}
-
-void MutationReplaceIdentifier::Apply(const NodeIdMap& node_id_map,
-                                      tint::CloneContext* clone_context,
-                                      NodeIdMap* new_node_id_map) const {
-  const auto* use_node = node_id_map.GetNode(message_.use_id());
-  const auto* replacement_var =
-      tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
-
-  auto* cloned_replacement =
-      clone_context->dst->Expr(clone_context->Clone(use_node->source),
-                               clone_context->Clone(replacement_var->symbol));
-  clone_context->Replace(use_node, cloned_replacement);
-  new_node_id_map->Add(cloned_replacement, message_.use_id());
-}
-
-protobufs::Mutation MutationReplaceIdentifier::ToMessage() const {
-  protobufs::Mutation mutation;
-  *mutation.mutable_replace_identifier() = message_;
-  return mutation;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h b/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h
deleted file mode 100644
index fa16715..0000000
--- a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
-#define FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
-
-#include "fuzzers/tint_ast_fuzzer/mutation.h"
-
-#include "src/sem/variable.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// @see MutationReplaceIdentifier::Apply
-class MutationReplaceIdentifier : public Mutation {
- public:
-  /// @brief Constructs an instance of this mutation from a protobuf message.
-  /// @param message - protobuf message
-  explicit MutationReplaceIdentifier(
-      protobufs::MutationReplaceIdentifier message);
-
-  /// @brief Constructor.
-  /// @param use_id - the id of a variable user.
-  /// @param replacement_id - the id of a variable to replace the `use_id`.
-  MutationReplaceIdentifier(uint32_t use_id, uint32_t replacement_id);
-
-  /// @copybrief Mutation::IsApplicable
-  ///
-  /// The mutation is applicable iff:
-  /// - `use_id` is a valid id of an `ast::IdentifierExpression`, that
-  ///   references a variable.
-  /// - `replacement_id` is a valid id of an `ast::Variable`.
-  /// - The identifier expression doesn't reference the variable of a
-  ///   `replacement_id`.
-  /// - The variable with `replacement_id` is in scope of an identifier
-  ///   expression with `use_id`.
-  /// - The identifier expression and the variable have the same type.
-  ///
-  /// @copydetails Mutation::IsApplicable
-  bool IsApplicable(const tint::Program& program,
-                    const NodeIdMap& node_id_map) const override;
-
-  /// @copybrief Mutation::Apply
-  ///
-  /// Replaces the use of an identifier expression with `use_id` with a newly
-  /// created identifier expression, that references a variable with
-  /// `replacement_id`. The newly created identifier expression will have the
-  /// same id as the old one (i.e. `use_id`).
-  ///
-  /// @copydetails Mutation::Apply
-  void Apply(const NodeIdMap& node_id_map,
-             tint::CloneContext* clone_context,
-             NodeIdMap* new_node_id_map) const override;
-
-  protobufs::Mutation ToMessage() const override;
-
- private:
-  protobufs::MutationReplaceIdentifier message_;
-};
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
diff --git a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc b/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
deleted file mode 100644
index 707b15a..0000000
--- a/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
+++ /dev/null
@@ -1,670 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "gtest/gtest.h"
-
-#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
-#include "fuzzers/tint_ast_fuzzer/mutator.h"
-#include "fuzzers/tint_ast_fuzzer/probability_context.h"
-
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-
-#include "src/ast/call_statement.h"
-#include "src/program_builder.h"
-#include "src/reader/wgsl/parser.h"
-#include "src/writer/wgsl/generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace {
-
-TEST(ReplaceIdentifierTest, NotApplicable_Simple) {
-  std::string content = R"(
-    fn main() {
-      let a = 5;
-      let c = 6;
-      let b = a + 5;
-
-      let d = vec2<i32>(1, 2);
-      let e = d.x;
-    }
-  )";
-  Source::File file("test.wgsl", content);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements;
-
-  const auto* a_var =
-      main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable;
-  ASSERT_NE(a_var, nullptr);
-
-  const auto* b_var =
-      main_fn_stmts[2]->As<ast::VariableDeclStatement>()->variable;
-  ASSERT_NE(b_var, nullptr);
-
-  const auto* e_var =
-      main_fn_stmts[4]->As<ast::VariableDeclStatement>()->variable;
-  ASSERT_NE(e_var, nullptr);
-
-  auto a_var_id = node_id_map.GetId(a_var);
-  ASSERT_NE(a_var_id, 0);
-
-  auto b_var_id = node_id_map.GetId(b_var);
-  ASSERT_NE(b_var_id, 0);
-
-  const auto* sum_expr = b_var->constructor->As<ast::BinaryExpression>();
-  ASSERT_NE(sum_expr, nullptr);
-
-  auto a_ident_id = node_id_map.GetId(sum_expr->lhs);
-  ASSERT_NE(a_ident_id, 0);
-
-  auto sum_expr_id = node_id_map.GetId(sum_expr);
-  ASSERT_NE(sum_expr_id, 0);
-
-  auto e_var_id = node_id_map.GetId(e_var);
-  ASSERT_NE(e_var_id, 0);
-
-  auto vec_member_access_id = node_id_map.GetId(
-      e_var->constructor->As<ast::MemberAccessorExpression>()->member);
-  ASSERT_NE(vec_member_access_id, 0);
-
-  // use_id is invalid.
-  EXPECT_FALSE(MutationReplaceIdentifier(0, a_var_id)
-                   .IsApplicable(program, node_id_map));
-
-  // use_id is not an identifier expression.
-  EXPECT_FALSE(MutationReplaceIdentifier(sum_expr_id, a_var_id)
-                   .IsApplicable(program, node_id_map));
-
-  // use_id is an identifier but not a variable user.
-  EXPECT_FALSE(MutationReplaceIdentifier(vec_member_access_id, a_var_id)
-                   .IsApplicable(program, node_id_map));
-
-  // replacement_id is invalid.
-  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, 0)
-                   .IsApplicable(program, node_id_map));
-
-  // replacement_id is not a variable.
-  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, sum_expr_id)
-                   .IsApplicable(program, node_id_map));
-
-  // Can't replace a variable with itself.
-  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, a_var_id)
-                   .IsApplicable(program, node_id_map));
-
-  // Replacement is not in scope.
-  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, b_var_id)
-                   .IsApplicable(program, node_id_map));
-  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, e_var_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, GlobalVarNotInScope) {
-  // Can't use the global variable if it's not in scope.
-  std::string shader = R"(
-var<private> a: i32;
-
-fn f() {
-  a = 3;
-}
-
-var<private> b: i32;
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
-  ASSERT_NE(replacement_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable1) {
-  // Can't replace `a` with `b` since the store type is wrong (the same storage
-  // class though).
-  std::string shader = R"(
-var<private> a: i32;
-var<private> b: u32;
-fn f() {
-  *&a = 4;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable2) {
-  // Can't replace `a` with `b` since the store type is wrong (the storage
-  // class is different though).
-  std::string shader = R"(
-var<private> a: i32;
-fn f() {
-  var b: u32;
-  *&a = 4;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST()
-                                              .Functions()[0]
-                                              ->body->statements[0]
-                                              ->As<ast::VariableDeclStatement>()
-                                              ->variable);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[1]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable3) {
-  // Can't replace `a` with `b` since the latter is not a reference (the store
-  // type is the same, though).
-  std::string shader = R"(
-var<private> a: i32;
-fn f() {
-  let b = 45;
-  *&a = 4;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST()
-                                              .Functions()[0]
-                                              ->body->statements[0]
-                                              ->As<ast::VariableDeclStatement>()
-                                              ->variable);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[1]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable4) {
-  // Can't replace `a` with `b` since the latter is not a reference (the store
-  // type is the same, though).
-  std::string shader = R"(
-var<private> a: i32;
-fn f(b: i32) {
-  *&a = 4;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id =
-      node_id_map.GetId(program.AST().Functions()[0]->params[0]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable5) {
-  // Can't replace `a` with `b` since the latter has a wrong access mode
-  // (`read` for uniform storage class).
-  std::string shader = R"(
-struct S {
-  a: i32;
-};
-
-var<private> a: S;
-@group(1) @binding(1) var<uniform> b: S;
-fn f() {
-  *&a = S(4);
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable6) {
-  // Can't replace `ptr_b` with `a` since the latter is not a pointer.
-  std::string shader = R"(
-struct S {
-  a: i32;
-};
-
-var<private> a: S;
-@group(1) @binding(1) var<uniform> b: S;
-fn f() {
-  let ptr_b = &b;
-  *&a = *ptr_b;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[1]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->rhs->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable8) {
-  // Can't replace `ptr_b` with `c` since the latter has a wrong access mode and
-  // storage class.
-  std::string shader = R"(
-struct S {
-  a: i32;
-};
-
-var<private> a: S;
-@group(1) @binding(1) var<uniform> b: S;
-@group(1) @binding(2) var<storage, write> c: S;
-fn f() {
-  let ptr_b = &b;
-  *&a = *ptr_b;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[2]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[1]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->rhs->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable9) {
-  // Can't replace `b` with `e` since the latter is not a reference.
-  std::string shader = R"(
-struct S {
-  a: i32;
-};
-
-var<private> a: S;
-let e = 3;
-@group(1) @binding(1) var<uniform> b: S;
-fn f() {
-  *&a = *&b;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->rhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable10) {
-  // Can't replace `b` with `e` since the latter has a wrong access mode.
-  std::string shader = R"(
-struct S {
-  a: i32;
-};
-
-var<private> a: S;
-@group(0) @binding(0) var<storage, write> e: S;
-@group(1) @binding(1) var<uniform> b: S;
-fn f() {
-  *&a = *&b;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
-  ASSERT_NE(replacement_id, 0);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[0]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->rhs->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, Applicable1) {
-  // Can replace `a` with `b` (same storage class).
-  std::string shader = R"(
-fn f() {
-  var b : vec2<u32>;
-  var a = vec2<u32>(34u, 45u);
-  (*&a)[1] = 3u;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[2]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::IndexAccessorExpression>()
-                                      ->object->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id = node_id_map.GetId(program.AST()
-                                              .Functions()[0]
-                                              ->body->statements[0]
-                                              ->As<ast::VariableDeclStatement>()
-                                              ->variable);
-  ASSERT_NE(replacement_id, 0);
-
-  ASSERT_TRUE(MaybeApplyMutation(
-      program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map,
-      &program, &node_id_map, nullptr));
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  writer::wgsl::Options options;
-  auto result = writer::wgsl::Generate(&program, options);
-  ASSERT_TRUE(result.success) << result.error;
-
-  std::string expected_shader = R"(fn f() {
-  var b : vec2<u32>;
-  var a = vec2<u32>(34u, 45u);
-  (*(&(b)))[1] = 3u;
-}
-)";
-  ASSERT_EQ(expected_shader, result.wgsl);
-}
-
-TEST(ReplaceIdentifierTest, Applicable2) {
-  // Can replace `ptr_a` with `b` - the function parameter.
-  std::string shader = R"(
-fn f(b: ptr<function, vec2<u32>>) {
-  var a = vec2<u32>(34u, 45u);
-  let ptr_a = &a;
-  (*ptr_a)[1] = 3u;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[2]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::IndexAccessorExpression>()
-                                      ->object->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id =
-      node_id_map.GetId(program.AST().Functions()[0]->params[0]);
-  ASSERT_NE(replacement_id, 0);
-
-  ASSERT_TRUE(MaybeApplyMutation(
-      program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map,
-      &program, &node_id_map, nullptr));
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  writer::wgsl::Options options;
-  auto result = writer::wgsl::Generate(&program, options);
-  ASSERT_TRUE(result.success) << result.error;
-
-  std::string expected_shader = R"(fn f(b : ptr<function, vec2<u32>>) {
-  var a = vec2<u32>(34u, 45u);
-  let ptr_a = &(a);
-  (*(b))[1] = 3u;
-}
-)";
-  ASSERT_EQ(expected_shader, result.wgsl);
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable12) {
-  // Can't replace `a` with `b` (both are references with different storage
-  // class).
-  std::string shader = R"(
-var<private> b : vec2<u32>;
-fn f() {
-  var a = vec2<u32>(34u, 45u);
-  (*&a)[1] = 3u;
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(program.AST()
-                                      .Functions()[0]
-                                      ->body->statements[1]
-                                      ->As<ast::AssignmentStatement>()
-                                      ->lhs->As<ast::IndexAccessorExpression>()
-                                      ->object->As<ast::UnaryOpExpression>()
-                                      ->expr->As<ast::UnaryOpExpression>()
-                                      ->expr);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
-  ASSERT_NE(replacement_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable13) {
-  // Can't replace `a` with `b` (both are references with different storage
-  // class).
-  std::string shader = R"(
-var<private> b : vec2<u32>;
-fn f() {
-  var a = vec2<u32>(34u, 45u);
-  let c = (*&a)[1];
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(
-      program.AST()
-          .Functions()[0]
-          ->body->statements[1]
-          ->As<ast::VariableDeclStatement>()
-          ->variable->constructor->As<ast::IndexAccessorExpression>()
-          ->object->As<ast::UnaryOpExpression>()
-          ->expr->As<ast::UnaryOpExpression>()
-          ->expr);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
-  ASSERT_NE(replacement_id, 0);
-
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-TEST(ReplaceIdentifierTest, NotApplicable14) {
-  // Can't replace `ptr_a` with `ptr_b` (both are pointers with different
-  // storage class).
-  std::string shader = R"(
-var<private> b: vec2<u32>;
-fn f() {
-  var a = vec2<u32>(34u, 45u);
-  let ptr_a = &a;
-  let ptr_b = &b;
-  let c = (*ptr_a)[1];
-}
-)";
-  Source::File file("test.wgsl", shader);
-  auto program = reader::wgsl::Parse(&file);
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-
-  NodeIdMap node_id_map(program);
-
-  auto use_id = node_id_map.GetId(
-      program.AST()
-          .Functions()[0]
-          ->body->statements[3]
-          ->As<ast::VariableDeclStatement>()
-          ->variable->constructor->As<ast::IndexAccessorExpression>()
-          ->object->As<ast::UnaryOpExpression>()
-          ->expr);
-  ASSERT_NE(use_id, 0);
-
-  auto replacement_id = node_id_map.GetId(program.AST()
-                                              .Functions()[0]
-                                              ->body->statements[2]
-                                              ->As<ast::VariableDeclStatement>()
-                                              ->variable);
-  ASSERT_NE(replacement_id, 0);
-  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
-                   .IsApplicable(program, node_id_map));
-}
-
-}  // namespace
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutator.cc b/fuzzers/tint_ast_fuzzer/mutator.cc
deleted file mode 100644
index d1637db..0000000
--- a/fuzzers/tint_ast_fuzzer/mutator.cc
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/mutator.h"
-
-#include <cassert>
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-
-#include "src/program_builder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace {
-
-template <typename T, typename... Args>
-void MaybeAddFinder(bool enable_all_mutations,
-                    ProbabilityContext* probability_context,
-                    MutationFinderList* finders,
-                    Args&&... args) {
-  if (enable_all_mutations || probability_context->RandomBool()) {
-    finders->push_back(std::make_unique<T>(std::forward<Args>(args)...));
-  }
-}
-
-MutationFinderList CreateMutationFinders(
-    ProbabilityContext* probability_context,
-    bool enable_all_mutations) {
-  MutationFinderList result;
-  do {
-    MaybeAddFinder<MutationFinderReplaceIdentifiers>(
-        enable_all_mutations, probability_context, &result);
-  } while (result.empty());
-  return result;
-}
-
-}  // namespace
-
-bool MaybeApplyMutation(const tint::Program& program,
-                        const Mutation& mutation,
-                        const NodeIdMap& node_id_map,
-                        tint::Program* out_program,
-                        NodeIdMap* out_node_id_map,
-                        protobufs::MutationSequence* mutation_sequence) {
-  assert(out_program && "`out_program` may not be a nullptr");
-  assert(out_node_id_map && "`out_node_id_map` may not be a nullptr");
-
-  if (!mutation.IsApplicable(program, node_id_map)) {
-    return false;
-  }
-
-  // The mutated `program` will be copied into the `mutated` program builder.
-  tint::ProgramBuilder mutated;
-  tint::CloneContext clone_context(&mutated, &program);
-  NodeIdMap new_node_id_map;
-  clone_context.ReplaceAll(
-      [&node_id_map, &new_node_id_map, &clone_context](const ast::Node* node) {
-        // Make sure all `tint::ast::` nodes' ids are preserved.
-        auto* cloned = tint::As<ast::Node>(node->Clone(&clone_context));
-        new_node_id_map.Add(cloned, node_id_map.GetId(node));
-        return cloned;
-      });
-
-  mutation.Apply(node_id_map, &clone_context, &new_node_id_map);
-  if (mutation_sequence) {
-    *mutation_sequence->add_mutation() = mutation.ToMessage();
-  }
-
-  clone_context.Clone();
-  *out_program = tint::Program(std::move(mutated));
-  *out_node_id_map = std::move(new_node_id_map);
-  return true;
-}
-
-tint::Program Replay(tint::Program program,
-                     const protobufs::MutationSequence& mutation_sequence) {
-  assert(program.IsValid() && "Initial program is invalid");
-
-  NodeIdMap node_id_map(program);
-  for (const auto& mutation_message : mutation_sequence.mutation()) {
-    auto mutation = Mutation::FromMessage(mutation_message);
-    auto status = MaybeApplyMutation(program, *mutation, node_id_map, &program,
-                                     &node_id_map, nullptr);
-    (void)status;  // `status` will be unused in release mode.
-    assert(status && "`mutation` is inapplicable - it's most likely a bug");
-    if (!program.IsValid()) {
-      // `mutation` has a bug.
-      break;
-    }
-  }
-
-  return program;
-}
-
-tint::Program Mutate(tint::Program program,
-                     ProbabilityContext* probability_context,
-                     bool enable_all_mutations,
-                     uint32_t max_applied_mutations,
-                     protobufs::MutationSequence* mutation_sequence) {
-  assert(max_applied_mutations != 0 &&
-         "Maximum number of mutations is invalid");
-  assert(program.IsValid() && "Initial program is invalid");
-
-  // The number of allowed failed attempts to apply mutations. If this number is
-  // exceeded, the mutator is considered stuck and the mutation session is
-  // stopped.
-  const uint32_t kMaxFailureToApply = 10;
-
-  auto finders =
-      CreateMutationFinders(probability_context, enable_all_mutations);
-  NodeIdMap node_id_map(program);
-
-  // Total number of applied mutations during this call to `Mutate`.
-  uint32_t applied_mutations = 0;
-
-  // The number of consecutively failed attempts to apply mutations.
-  uint32_t failure_to_apply = 0;
-
-  // Apply mutations as long as the `program` is valid, the limit on the number
-  // of mutations is not reached and the mutator is not stuck (i.e. unable to
-  // apply any mutations for some time).
-  while (program.IsValid() && applied_mutations < max_applied_mutations &&
-         failure_to_apply < kMaxFailureToApply) {
-    // Get all applicable mutations from some mutation finder.
-    const auto& mutation_finder =
-        finders[probability_context->GetRandomIndex(finders)];
-    auto mutations = mutation_finder->FindMutations(program, &node_id_map,
-                                                    probability_context);
-
-    const auto old_applied_mutations = applied_mutations;
-    for (const auto& mutation : mutations) {
-      if (!probability_context->ChoosePercentage(
-              mutation_finder->GetChanceOfApplyingMutation(
-                  probability_context))) {
-        // Skip this `mutation` probabilistically.
-        continue;
-      }
-
-      if (!MaybeApplyMutation(program, *mutation, node_id_map, &program,
-                              &node_id_map, mutation_sequence)) {
-        // This `mutation` is inapplicable. This may happen if some of the
-        // earlier mutations cancelled this one.
-        continue;
-      }
-
-      applied_mutations++;
-      if (!program.IsValid()) {
-        // This `mutation` has a bug.
-        return program;
-      }
-    }
-
-    if (old_applied_mutations == applied_mutations) {
-      // No mutation was applied. Increase the counter to prevent an infinite
-      // loop.
-      failure_to_apply++;
-    } else {
-      failure_to_apply = 0;
-    }
-  }
-
-  return program;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/mutator.h b/fuzzers/tint_ast_fuzzer/mutator.h
deleted file mode 100644
index 59aa5b7..0000000
--- a/fuzzers/tint_ast_fuzzer/mutator.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
-#define FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
-
-#include "fuzzers/tint_ast_fuzzer/mutation.h"
-#include "fuzzers/tint_ast_fuzzer/mutation_finder.h"
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-#include "fuzzers/tint_ast_fuzzer/probability_context.h"
-#include "fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
-
-#include "src/program.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-/// @file
-
-/// @brief Tries to apply a `mutation` to the `program`.
-///
-/// If the `mutation` is inapplicable, this function will return `false` and
-/// `out_program`, `out_node_id_map` and `mutation_sequence` won't be modified.
-///
-/// The `mutation` is required to produce a valid program when the
-/// `Mutation::Apply` method is called. This guarantees that this function
-/// returns a valid program as well.
-///
-/// @param program - the initial program (must be valid).
-/// @param mutation - the mutation that will be applied.
-/// @param node_id_map - a map from `tint::ast::` nodes in the `program` to
-///     their unique ids.
-/// @param out_program - the resulting mutated program will be written through
-///     this pointer. It may *not* be a `nullptr`. It _may_ point to `program`,
-///     so that a program can be updated in place.
-/// @param out_node_id_map - will contain new ids for the AST nodes in the
-///     mutated program. It may *not* be a `nullptr`. It _may_ point to
-///     `node_id_map`, so that a map can be updated in place.
-/// @param mutation_sequence - the message about this mutation will be recorded
-///     here. It may be a `nullptr`, in which case it's ignored.
-/// @return `true` if the `mutation` was applied.
-/// @return `false` if the `mutation` is inapplicable.
-bool MaybeApplyMutation(const tint::Program& program,
-                        const Mutation& mutation,
-                        const NodeIdMap& node_id_map,
-                        tint::Program* out_program,
-                        NodeIdMap* out_node_id_map,
-                        protobufs::MutationSequence* mutation_sequence);
-
-/// @brief Applies mutations from `mutations_sequence` to the `program`.
-///
-/// All mutations in `mutation_sequence` must be applicable. Additionally, all
-/// mutations must produce a valid program when the `Mutation::Apply` method is
-/// called. This guarantees that this function returns a valid program as well.
-///
-/// @param program - the initial program - must be valid.
-/// @param mutation_sequence - a sequence of mutations.
-/// @return the mutated program.
-tint::Program Replay(tint::Program program,
-                     const protobufs::MutationSequence& mutation_sequence);
-
-/// @brief Applies up to `max_applied_mutations` mutations to the `program`.
-///
-/// All applied mutations must produce valid programs. This guarantees that the
-/// returned program is valid as well. The returned program may be identical to
-/// the initial `program` if no mutation was applied.
-///
-/// @param program - initial program - must be valid.
-/// @param probability_context - contains information about various
-///     probabilistic behaviour of the fuzzer.
-/// @param enable_all_mutations - if `false`, only mutations from a
-///     probabilistically selected set of mutation types are applied. If `true`,
-///     all mutation types are considered.
-/// @param max_applied_mutations - the maximum number of applied mutations. This
-///     may not be 0.
-/// @param mutation_sequence - applied mutations will be recorded into this
-///     protobuf message. This argument may be `nullptr`, in which case it's
-///     ignored.
-/// @return the mutated program.
-tint::Program Mutate(tint::Program program,
-                     ProbabilityContext* probability_context,
-                     bool enable_all_mutations,
-                     uint32_t max_applied_mutations,
-                     protobufs::MutationSequence* mutation_sequence);
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
diff --git a/fuzzers/tint_ast_fuzzer/node_id_map.cc b/fuzzers/tint_ast_fuzzer/node_id_map.cc
deleted file mode 100644
index f86b4f4..0000000
--- a/fuzzers/tint_ast_fuzzer/node_id_map.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
-
-#include <cassert>
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-NodeIdMap::NodeIdMap() = default;
-
-NodeIdMap::NodeIdMap(const Program& program) : NodeIdMap() {
-  for (const auto* node : program.ASTNodes().Objects()) {
-    Add(node, TakeFreshId());
-  }
-}
-
-NodeIdMap::IdType NodeIdMap::GetId(const ast::Node* node) const {
-  auto it = node_to_id_.find(node);
-  return it == node_to_id_.end() ? 0 : it->second;
-}
-
-const ast::Node* NodeIdMap::GetNode(IdType id) const {
-  auto it = id_to_node_.find(id);
-  return it == id_to_node_.end() ? nullptr : it->second;
-}
-
-void NodeIdMap::Add(const ast::Node* node, IdType id) {
-  assert(!node_to_id_.count(node) && "The node already exists in the map");
-  assert(IdIsFreshAndValid(id) && "Id already exists in the map or Id is zero");
-  assert(node && "`node` can't be a nullptr");
-
-  node_to_id_[node] = id;
-  id_to_node_[id] = node;
-
-  if (id >= fresh_id_) {
-    fresh_id_ = id + 1;
-  }
-}
-
-bool NodeIdMap::IdIsFreshAndValid(IdType id) {
-  return id && !id_to_node_.count(id);
-}
-
-NodeIdMap::IdType NodeIdMap::TakeFreshId() {
-  assert(fresh_id_ != 0 && "`NodeIdMap` id has overflowed");
-  return fresh_id_++;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/node_id_map.h b/fuzzers/tint_ast_fuzzer/node_id_map.h
deleted file mode 100644
index 2e0131d..0000000
--- a/fuzzers/tint_ast_fuzzer/node_id_map.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
-#define FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
-
-#include <unordered_map>
-
-#include "src/program.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// Contains a one-to-one mapping between the nodes in the AST of the program
-/// and their ids.
-///
-/// The motivation for having this mapping is:
-/// - To be able to uniquely identify a node in the AST. This will be used
-///   to record transformations in the protobuf messages.
-/// - When the AST is being modified, only the mapping for the modified nodes
-///   must be affected. That is, if some node is unchanged, it must have the
-///   same id defined in this class.
-///
-/// This class achieves these goals partially. Concretely, the only way to
-/// change the AST is by cloning it since all instances of `tint::ast::` classes
-/// are immutable. This will invalidate all the pointers to the AST nodes which
-/// are used in this class. To overcome this, a new instance of this class is
-/// created with all the cloned nodes and the old instance is discarded.
-class NodeIdMap {
- public:
-  /// Type of the id used by this map.
-  using IdType = uint32_t;
-
-  /// Creates an empty map.
-  NodeIdMap();
-
-  /// @brief Initializes this instance with all the nodes in the `program`.
-  /// @param program - must be valid.
-  explicit NodeIdMap(const Program& program);
-
-  /// @brief Returns a node for the given `id`.
-  /// @param id - any value is accepted.
-  /// @return a pointer to some node if `id` exists in this map.
-  /// @return `nullptr` otherwise.
-  const ast::Node* GetNode(IdType id) const;
-
-  /// @brief Returns an id of the given `node`.
-  /// @param node - can be a `nullptr`.
-  /// @return not equal to 0 if `node` exists in this map.
-  /// @return 0 otherwise.
-  IdType GetId(const ast::Node* node) const;
-
-  /// @brief Adds a mapping from `node` to `id` to this map.
-  /// @param node - may not be a `nullptr` and can't be present in this map.
-  /// @param id - may not be 0 and can't be present in this map.
-  void Add(const ast::Node* node, IdType id);
-
-  /// @brief Returns whether the id is fresh by checking if it exists in
-  /// the id map and the id is not 0.
-  /// @param id - an id that is used to check in the map.
-  /// @return true the given id is fresh and valid (non-zero).
-  /// @return false otherwise.
-  bool IdIsFreshAndValid(IdType id);
-
-  /// @brief Returns an id that is guaranteed to be unoccupied in this map.
-  ///
-  /// This will effectively increase the counter. This means that two
-  /// consecutive calls to this method will return different ids.
-  ///
-  /// @return an unoccupied id.
-  IdType TakeFreshId();
-
- private:
-  IdType fresh_id_ = 1;
-
-  std::unordered_map<const ast::Node*, IdType> node_to_id_;
-  std::unordered_map<IdType, const ast::Node*> id_to_node_;
-};
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
diff --git a/fuzzers/tint_ast_fuzzer/override_cli_params.h b/fuzzers/tint_ast_fuzzer/override_cli_params.h
deleted file mode 100644
index ff7569c..0000000
--- a/fuzzers/tint_ast_fuzzer/override_cli_params.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
-#define FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// @brief Allows CLI parameters to be overridden.
-///
-/// This function allows fuzz targets to override particular CLI parameters,
-/// for example forcing a particular back-end to be targeted.
-///
-/// @param cli_params - the parsed CLI parameters to be updated.
-void OverrideCliParams(CliParams& cli_params);
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/fuzzers/tint_ast_fuzzer/probability_context.cc b/fuzzers/tint_ast_fuzzer/probability_context.cc
deleted file mode 100644
index 8db9557..0000000
--- a/fuzzers/tint_ast_fuzzer/probability_context.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/probability_context.h"
-
-#include <cassert>
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace {
-
-const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
-
-}  // namespace
-
-ProbabilityContext::ProbabilityContext(RandomGenerator* generator)
-    : generator_(generator),
-      chance_of_replacing_identifiers_(
-          RandomFromRange(kChanceOfReplacingIdentifiers)) {
-  assert(generator != nullptr && "generator must not be nullptr");
-}
-
-uint32_t ProbabilityContext::RandomFromRange(
-    std::pair<uint32_t, uint32_t> range) {
-  assert(range.first <= range.second && "Range must be non-decreasing");
-  return generator_->GetUInt32(
-      range.first, range.second + 1);  // + 1 need since range is inclusive.
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/probability_context.h b/fuzzers/tint_ast_fuzzer/probability_context.h
deleted file mode 100644
index c96b7ba..0000000
--- a/fuzzers/tint_ast_fuzzer/probability_context.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
-#define FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
-
-#include <utility>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-/// This class is intended to be used by the `MutationFinder`s to introduce some
-/// variance to the mutation process.
-class ProbabilityContext {
- public:
-  /// Initializes this instance with a random number generator.
-  /// @param generator - must not be a `nullptr`. Must remain in scope as long
-  /// as this
-  ///     instance exists.
-  explicit ProbabilityContext(RandomGenerator* generator);
-
-  /// Get random bool with even odds
-  /// @returns true 50% of the time and false %50 of time.
-  bool RandomBool() { return generator_->GetBool(); }
-
-  /// Get random bool with weighted odds
-  /// @param percentage - likelihood of true being returned
-  /// @returns true |percentage|% of the time, and false (100 - |percentage|)%
-  /// of the time.
-  bool ChoosePercentage(uint32_t percentage) {
-    return generator_->GetWeightedBool(percentage);
-  }
-
-  /// Returns a random value in the range `[0; arr.size())`.
-  /// @tparam T - type of the elements in the vector.
-  /// @param arr - may not be empty.
-  /// @return the random index in the `arr`.
-  template <typename T>
-  size_t GetRandomIndex(const std::vector<T>& arr) {
-    return static_cast<size_t>(generator_->GetUInt64(arr.size()));
-  }
-
-  /// @return the probability of replacing some identifier with some other one.
-  uint32_t GetChanceOfReplacingIdentifiers() const {
-    return chance_of_replacing_identifiers_;
-  }
-
- private:
-  /// @param range - a pair of integers `a` and `b` s.t. `a <= b`.
-  /// @return an random number in the range `[a; b]`.
-  uint32_t RandomFromRange(std::pair<uint32_t, uint32_t> range);
-
-  RandomGenerator* generator_;
-
-  uint32_t chance_of_replacing_identifiers_;
-};
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
diff --git a/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h b/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h
deleted file mode 100644
index 45d7ca5..0000000
--- a/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
-#define FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
-
-// Compilation of the protobuf library and its autogenerated code can produce
-// warnings. Ignore them since we can't control them.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-parameter"
-#pragma clang diagnostic ignored "-Wreserved-id-macro"
-#pragma clang diagnostic ignored "-Wsign-conversion"
-#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
-#pragma clang diagnostic ignored "-Wextra-semi-stmt"
-#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override"
-#pragma clang diagnostic ignored "-Wweak-vtables"
-#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
-#pragma clang diagnostic ignored "-Wreserved-identifier"
-
-#include "fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.pb.h"
-
-#pragma clang diagnostic pop
-
-#endif  // FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
diff --git a/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc b/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc
deleted file mode 100644
index 1ce867a..0000000
--- a/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-void OverrideCliParams(CliParams& /*unused*/) {
-  // Leave the CLI parameters unchanged.
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc b/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc
deleted file mode 100644
index 6e802dc..0000000
--- a/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc b/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc
deleted file mode 100644
index 6af93e7..0000000
--- a/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kMsl;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc b/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc
deleted file mode 100644
index 0e37231..0000000
--- a/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kSpv;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc b/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc
deleted file mode 100644
index 03eccae..0000000
--- a/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_ast_fuzzer/cli.h"
-#include "fuzzers/tint_ast_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
-}
-
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/util.h b/fuzzers/tint_ast_fuzzer/util.h
deleted file mode 100644
index 0b6f449..0000000
--- a/fuzzers/tint_ast_fuzzer/util.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_AST_FUZZER_UTIL_H_
-#define FUZZERS_TINT_AST_FUZZER_UTIL_H_
-
-#include <vector>
-
-#include "src/ast/module.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/castable.h"
-#include "src/program.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-namespace tint {
-namespace fuzzers {
-namespace ast_fuzzer {
-namespace util {
-/// @file
-
-/// @brief Returns all in-scope variables (including formal function parameters)
-/// related to statement `curr_stmt`.
-///
-/// These variables are additionally filtered by applying a predicate `pred`.
-///
-/// @tparam Pred - a predicate that accepts a `const sem::Variable*` and returns
-///     `bool`.
-/// @param program - the program to look for variables in.
-/// @param curr_stmt - the current statement. Everything below it is not in
-///     scope.
-/// @param pred - a predicate (e.g. a function pointer, functor, lambda etc) of
-///     type `Pred`.
-/// @return a vector of all variables that can be accessed from `curr_stmt`.
-template <typename Pred>
-std::vector<const sem::Variable*> GetAllVarsInScope(
-    const tint::Program& program,
-    const sem::Statement* curr_stmt,
-    Pred&& pred) {
-  std::vector<const sem::Variable*> result;
-
-  // Walk up the hierarchy of blocks in which `curr_stmt` is contained.
-  for (const auto* block = curr_stmt->Block(); block;
-       block = tint::As<sem::BlockStatement>(block->Parent())) {
-    for (const auto* stmt : block->Declaration()->statements) {
-      if (stmt == curr_stmt->Declaration()) {
-        // `curr_stmt` was found. This is only possible if `block is the
-        // enclosing block of `curr_stmt` since the AST nodes are not shared.
-        // Because of all this, skip the iteration of the inner loop since
-        // the rest of the instructions in the `block` are not visible from the
-        // `curr_stmt`.
-        break;
-      }
-
-      if (const auto* var_node = tint::As<ast::VariableDeclStatement>(stmt)) {
-        const auto* sem_var = program.Sem().Get(var_node->variable);
-        if (pred(sem_var)) {
-          result.push_back(sem_var);
-        }
-      }
-    }
-  }
-
-  // Process function parameters.
-  for (const auto* param : curr_stmt->Function()->Parameters()) {
-    if (pred(param)) {
-      result.push_back(param);
-    }
-  }
-
-  // Global variables do not belong to any ast::BlockStatement.
-  for (const auto* global_decl : program.AST().GlobalDeclarations()) {
-    if (global_decl == curr_stmt->Function()->Declaration()) {
-      // The same situation as in the previous loop. The current function has
-      // been reached. If there are any variables declared below, they won't be
-      // visible in this function. Thus, exit the loop.
-      break;
-    }
-
-    if (const auto* global_var = tint::As<ast::Variable>(global_decl)) {
-      const auto* sem_node = program.Sem().Get(global_var);
-      if (pred(sem_node)) {
-        result.push_back(sem_node);
-      }
-    }
-  }
-
-  return result;
-}
-
-}  // namespace util
-}  // namespace ast_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_AST_FUZZER_UTIL_H_
diff --git a/fuzzers/tint_binding_remapper_fuzzer.cc b/fuzzers/tint_binding_remapper_fuzzer.cc
deleted file mode 100644
index 61d2994..0000000
--- a/fuzzers/tint_binding_remapper_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<transform::BindingRemapper>();
-
-  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_black_box_fuzz_target.cc b/fuzzers/tint_black_box_fuzz_target.cc
deleted file mode 100644
index f2f2f68..0000000
--- a/fuzzers/tint_black_box_fuzz_target.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-#include <cstdio>
-#include <fstream>
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include "fuzzers/tint_common_fuzzer.h"
-
-namespace {
-
-/// Controls the target language in which code will be generated.
-enum class TargetLanguage {
-  kHlsl,
-  kMsl,
-  kSpv,
-  kWgsl,
-  kTargetLanguageMax,
-};
-
-/// Copies the content from the file named `input_file` to `buffer`,
-/// assuming each element in the file is of type `T`.  If any error occurs,
-/// writes error messages to the standard error stream and returns false.
-/// Assumes the size of a `T` object is divisible by its required alignment.
-/// @returns true if we successfully read the file.
-template <typename T>
-bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
-  if (!buffer) {
-    std::cerr << "The buffer pointer was null" << std::endl;
-    return false;
-  }
-
-  FILE* file = nullptr;
-#if defined(_MSC_VER)
-  fopen_s(&file, input_file.c_str(), "rb");
-#else
-  file = fopen(input_file.c_str(), "rb");
-#endif
-  if (!file) {
-    std::cerr << "Failed to open " << input_file << std::endl;
-    return false;
-  }
-
-  fseek(file, 0, SEEK_END);
-  const auto file_size = static_cast<size_t>(ftell(file));
-  if (0 != (file_size % sizeof(T))) {
-    std::cerr << "File " << input_file
-              << " does not contain an integral number of objects: "
-              << file_size << " bytes in the file, require " << sizeof(T)
-              << " bytes per object" << std::endl;
-    fclose(file);
-    return false;
-  }
-  fseek(file, 0, SEEK_SET);
-
-  buffer->clear();
-  buffer->resize(file_size / sizeof(T));
-
-  size_t bytes_read = fread(buffer->data(), 1, file_size, file);
-  fclose(file);
-  if (bytes_read != file_size) {
-    std::cerr << "Failed to read " << input_file << std::endl;
-    return false;
-  }
-
-  return true;
-}
-
-}  // namespace
-
-int main(int argc, const char** argv) {
-  if (argc < 2 || argc > 3) {
-    std::cerr << "Usage: " << argv[0] << " <input file> [hlsl|msl|spv|wgsl]"
-              << std::endl;
-    return 1;
-  }
-
-  std::string input_filename(argv[1]);
-
-  std::vector<uint8_t> data;
-  if (!ReadFile<uint8_t>(input_filename, &data)) {
-    return 1;
-  }
-
-  if (data.empty()) {
-    return 0;
-  }
-
-  tint::fuzzers::DataBuilder builder(data.data(), data.size());
-
-  TargetLanguage target_language;
-
-  if (argc == 3) {
-    std::string target_language_string = argv[2];
-    if (target_language_string == "hlsl") {
-      target_language = TargetLanguage::kHlsl;
-    } else if (target_language_string == "msl") {
-      target_language = TargetLanguage::kMsl;
-    } else if (target_language_string == "spv") {
-      target_language = TargetLanguage::kSpv;
-    } else {
-      assert(target_language_string == "wgsl" && "Unknown target language.");
-      target_language = TargetLanguage::kWgsl;
-    }
-  } else {
-    target_language = builder.enum_class<TargetLanguage>(
-        static_cast<uint32_t>(TargetLanguage::kTargetLanguageMax));
-  }
-
-  switch (target_language) {
-    case TargetLanguage::kHlsl: {
-      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
-                                         tint::fuzzers::OutputFormat::kHLSL);
-      return fuzzer.Run(data.data(), data.size());
-    }
-    case TargetLanguage::kMsl: {
-      tint::writer::msl::Options options;
-      GenerateMslOptions(&builder, &options);
-      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
-                                         tint::fuzzers::OutputFormat::kMSL);
-      fuzzer.SetOptionsMsl(options);
-      return fuzzer.Run(data.data(), data.size());
-    }
-    case TargetLanguage::kSpv: {
-      tint::writer::spirv::Options options;
-      GenerateSpirvOptions(&builder, &options);
-      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
-                                         tint::fuzzers::OutputFormat::kSpv);
-      fuzzer.SetOptionsSpirv(options);
-      return fuzzer.Run(data.data(), data.size());
-    }
-    case TargetLanguage::kWgsl: {
-      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
-                                         tint::fuzzers::OutputFormat::kWGSL);
-      return fuzzer.Run(data.data(), data.size());
-    }
-    default:
-      std::cerr << "Aborting due to unknown target language; fuzzer must be "
-                   "misconfigured."
-                << std::endl;
-      abort();
-  }
-}
diff --git a/fuzzers/tint_common_fuzzer.cc b/fuzzers/tint_common_fuzzer.cc
deleted file mode 100644
index fad1ee1..0000000
--- a/fuzzers/tint_common_fuzzer.cc
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_common_fuzzer.h"
-
-#include <cassert>
-#include <cstring>
-#include <fstream>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#if TINT_BUILD_SPV_READER
-#include "spirv-tools/libspirv.hpp"
-#endif  // TINT_BUILD_SPV_READER
-
-#include "src/ast/module.h"
-#include "src/diagnostic/formatter.h"
-#include "src/program.h"
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace fuzzers {
-
-namespace {
-
-// A macro is used to avoid FATAL_ERROR creating its own stack frame. This leads
-// to better de-duplication of bug reports, because ClusterFuzz only uses the
-// top few stack frames for de-duplication, and a FATAL_ERROR stack frame
-// provides no useful information.
-#define FATAL_ERROR(diags, msg_string)                        \
-  do {                                                        \
-    std::string msg = msg_string;                             \
-    auto printer = tint::diag::Printer::create(stderr, true); \
-    if (!msg.empty()) {                                       \
-      printer->write(msg + "\n", {diag::Color::kRed, true});  \
-    }                                                         \
-    tint::diag::Formatter().format(diags, printer.get());     \
-    __builtin_trap();                                         \
-  } while (false)
-
-[[noreturn]] void TintInternalCompilerErrorReporter(
-    const tint::diag::List& diagnostics) {
-  FATAL_ERROR(diagnostics, "");
-}
-
-// Wrapping in a macro, so it can be a one-liner in the code, but not
-// introduce another level in the stack trace. This will help with de-duping
-// ClusterFuzz issues.
-#define CHECK_INSPECTOR(program, inspector)                    \
-  do {                                                         \
-    if ((inspector).has_error()) {                             \
-      if (!enforce_validity) {                                 \
-        return;                                                \
-      }                                                        \
-      FATAL_ERROR((program)->Diagnostics(),                    \
-                  "Inspector failed: " + (inspector).error()); \
-    }                                                          \
-  } while (false)
-
-// Wrapping in a macro to make code more readable and help with issue de-duping.
-#define VALIDITY_ERROR(diags, msg_string) \
-  do {                                    \
-    if (!enforce_validity) {              \
-      return 0;                           \
-    }                                     \
-    FATAL_ERROR(diags, msg_string);       \
-  } while (false)
-
-bool SPIRVToolsValidationCheck(const tint::Program& program,
-                               const std::vector<uint32_t>& spirv) {
-  spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
-  const tint::diag::List& diags = program.Diagnostics();
-  tools.SetMessageConsumer([diags](spv_message_level_t, const char*,
-                                   const spv_position_t& pos, const char* msg) {
-    std::stringstream out;
-    out << "Unexpected spirv-val error:\n"
-        << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
-        << std::endl;
-
-    auto printer = tint::diag::Printer::create(stderr, true);
-    printer->write(out.str(), {diag::Color::kYellow, false});
-    tint::diag::Formatter().format(diags, printer.get());
-  });
-
-  return tools.Validate(spirv.data(), spirv.size(),
-                        spvtools::ValidatorOptions());
-}
-
-}  // namespace
-
-void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options) {
-  *options = b->build<writer::spirv::Options>();
-}
-
-void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options) {
-  *options = b->build<writer::wgsl::Options>();
-}
-
-void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options) {
-  *options = b->build<writer::hlsl::Options>();
-}
-
-void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options) {
-  *options = b->build<writer::msl::Options>();
-}
-
-CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output)
-    : input_(input), output_(output) {}
-
-CommonFuzzer::~CommonFuzzer() = default;
-
-int CommonFuzzer::Run(const uint8_t* data, size_t size) {
-  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
-
-#if TINT_BUILD_WGSL_WRITER
-  tint::Program::printer = [](const tint::Program* program) {
-    auto result = tint::writer::wgsl::Generate(program, {});
-    if (!result.error.empty()) {
-      return "error: " + result.error;
-    }
-    return result.wgsl;
-  };
-#endif  //  TINT_BUILD_WGSL_WRITER
-
-  Program program;
-
-#if TINT_BUILD_SPV_READER
-  std::vector<uint32_t> spirv_input(size / sizeof(uint32_t));
-
-#endif  // TINT_BUILD_SPV_READER
-
-#if TINT_BUILD_WGSL_READER || TINT_BUILD_SPV_READER
-  auto dump_input_data = [&](auto& content, const char* extension) {
-    size_t hash = utils::Hash(content);
-    auto filename = "fuzzer_input_" + std::to_string(hash) + extension;  //
-    std::ofstream fout(filename, std::ios::binary);
-    fout.write(reinterpret_cast<const char*>(data),
-               static_cast<std::streamsize>(size));
-    std::cout << "Dumped input data to " << filename << std::endl;
-  };
-#endif
-
-  switch (input_) {
-#if TINT_BUILD_WGSL_READER
-    case InputFormat::kWGSL: {
-      // Clear any existing diagnostics, as these will hold pointers to file_,
-      // which we are about to release.
-      diagnostics_ = {};
-      std::string str(reinterpret_cast<const char*>(data), size);
-      file_ = std::make_unique<Source::File>("test.wgsl", str);
-      if (dump_input_) {
-        dump_input_data(str, ".wgsl");
-      }
-      program = reader::wgsl::Parse(file_.get());
-      break;
-    }
-#endif  // TINT_BUILD_WGSL_READER
-#if TINT_BUILD_SPV_READER
-    case InputFormat::kSpv: {
-      // `spirv_input` has been initialized with the capacity to store `size /
-      // sizeof(uint32_t)` uint32_t values. If `size` is not a multiple of
-      // sizeof(uint32_t) then not all of `data` can be copied into
-      // `spirv_input`, and any trailing bytes are discarded.
-      std::memcpy(spirv_input.data(), data,
-                  spirv_input.size() * sizeof(uint32_t));
-      if (spirv_input.empty()) {
-        return 0;
-      }
-      if (dump_input_) {
-        dump_input_data(spirv_input, ".spv");
-      }
-      program = reader::spirv::Parse(spirv_input);
-      break;
-    }
-#endif  // TINT_BUILD_SPV_READER
-  }
-
-  if (!program.IsValid()) {
-    diagnostics_ = program.Diagnostics();
-    return 0;
-  }
-
-#if TINT_BUILD_SPV_READER
-  if (input_ == InputFormat::kSpv &&
-      !SPIRVToolsValidationCheck(program, spirv_input)) {
-    FATAL_ERROR(
-        program.Diagnostics(),
-        "Fuzzing detected invalid input spirv not being caught by Tint");
-  }
-#endif  // TINT_BUILD_SPV_READER
-
-  RunInspector(&program);
-  diagnostics_ = program.Diagnostics();
-
-  if (transform_manager_) {
-    auto out = transform_manager_->Run(&program, *transform_inputs_);
-    if (!out.program.IsValid()) {
-      // Transforms can produce error messages for bad input.
-      // Catch ICEs and errors from non transform systems.
-      for (const auto& diag : out.program.Diagnostics()) {
-        if (diag.severity > diag::Severity::Error ||
-            diag.system != diag::System::Transform) {
-          VALIDITY_ERROR(program.Diagnostics(),
-                         "Fuzzing detected valid input program being "
-                         "transformed into an invalid output program");
-        }
-      }
-    }
-
-    program = std::move(out.program);
-    RunInspector(&program);
-  }
-
-  switch (output_) {
-    case OutputFormat::kWGSL: {
-#if TINT_BUILD_WGSL_WRITER
-      auto result = writer::wgsl::Generate(&program, options_wgsl_);
-      generated_wgsl_ = std::move(result.wgsl);
-      if (!result.success) {
-        VALIDITY_ERROR(
-            program.Diagnostics(),
-            "WGSL writer errored on validated input:\n" + result.error);
-      }
-#endif  // TINT_BUILD_WGSL_WRITER
-      break;
-    }
-    case OutputFormat::kSpv: {
-#if TINT_BUILD_SPV_WRITER
-      auto result = writer::spirv::Generate(&program, options_spirv_);
-      generated_spirv_ = std::move(result.spirv);
-      if (!result.success) {
-        VALIDITY_ERROR(
-            program.Diagnostics(),
-            "SPIR-V writer errored on validated input:\n" + result.error);
-      }
-
-      if (!SPIRVToolsValidationCheck(program, generated_spirv_)) {
-        VALIDITY_ERROR(program.Diagnostics(),
-                       "Fuzzing detected invalid spirv being emitted by Tint");
-      }
-
-#endif  // TINT_BUILD_SPV_WRITER
-      break;
-    }
-    case OutputFormat::kHLSL: {
-#if TINT_BUILD_HLSL_WRITER
-      auto result = writer::hlsl::Generate(&program, options_hlsl_);
-      generated_hlsl_ = std::move(result.hlsl);
-      if (!result.success) {
-        VALIDITY_ERROR(
-            program.Diagnostics(),
-            "HLSL writer errored on validated input:\n" + result.error);
-      }
-#endif  // TINT_BUILD_HLSL_WRITER
-      break;
-    }
-    case OutputFormat::kMSL: {
-#if TINT_BUILD_MSL_WRITER
-      auto result = writer::msl::Generate(&program, options_msl_);
-      generated_msl_ = std::move(result.msl);
-      if (!result.success) {
-        VALIDITY_ERROR(
-            program.Diagnostics(),
-            "MSL writer errored on validated input:\n" + result.error);
-      }
-#endif  // TINT_BUILD_MSL_WRITER
-      break;
-    }
-  }
-
-  return 0;
-}
-
-void CommonFuzzer::RunInspector(Program* program) {
-  inspector::Inspector inspector(program);
-  diagnostics_ = program->Diagnostics();
-
-  auto entry_points = inspector.GetEntryPoints();
-  CHECK_INSPECTOR(program, inspector);
-
-  auto constant_ids = inspector.GetConstantIDs();
-  CHECK_INSPECTOR(program, inspector);
-
-  auto constant_name_to_id = inspector.GetConstantNameToIdMap();
-  CHECK_INSPECTOR(program, inspector);
-
-  for (auto& ep : entry_points) {
-    inspector.GetRemappedNameForEntryPoint(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetStorageSize(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetUniformBufferResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetStorageBufferResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetSamplerResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetComparisonSamplerResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetSampledTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetMultisampledTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetWriteOnlyStorageTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetDepthTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetDepthMultisampledTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetExternalTextureResourceBindings(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetSamplerTextureUses(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-
-    inspector.GetWorkgroupStorageSize(ep.name);
-    CHECK_INSPECTOR(program, inspector);
-  }
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_common_fuzzer.h b/fuzzers/tint_common_fuzzer.h
deleted file mode 100644
index e4cbc82..0000000
--- a/fuzzers/tint_common_fuzzer.h
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_COMMON_FUZZER_H_
-#define FUZZERS_TINT_COMMON_FUZZER_H_
-
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "include/tint/tint.h"
-
-#include "fuzzers/data_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-// TODO(crbug.com/tint/1356): Add using shader reflection to generate options
-//                            that are potentially valid for Generate*Options
-//                            functions.
-/// Generates random set of options for SPIRV generation
-void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options);
-
-/// Generates random set of options for WGSL generation
-void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options);
-
-/// Generates random set of options for HLSL generation
-void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options);
-
-/// Generates random set of options for MSL generation
-void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options);
-
-/// Shader language the fuzzer is reading
-enum class InputFormat { kWGSL, kSpv };
-
-/// Shader language the fuzzer is emitting
-enum class OutputFormat { kWGSL, kSpv, kHLSL, kMSL };
-
-/// Generic runner for reading and emitting shaders using Tint, used by most
-/// fuzzers to share common code.
-class CommonFuzzer {
- public:
-  /// Constructor
-  /// @param input shader language being read
-  /// @param output shader language being emitted
-  CommonFuzzer(InputFormat input, OutputFormat output);
-
-  /// Destructor
-  ~CommonFuzzer();
-
-  /// Setter for the transform manager and the data map to be used
-  void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) {
-    assert((!tm || inputs) && "DataMap must be !nullptr if Manager !nullptr");
-    transform_manager_ = tm;
-    transform_inputs_ = inputs;
-  }
-
-  /// Controls if the input shader for run should be outputted to the log
-  void SetDumpInput(bool enabled) { dump_input_ = enabled; }
-
-  /// Controls if the shader being valid after parsing is being enforced.
-  /// If false, invalidation of the shader will cause an early exit, but not
-  /// throw an error.
-  /// If true invalidation will throw an error that is caught by libFuzzer and
-  /// will generate a crash report.
-  void SetEnforceValidity(bool enabled) { enforce_validity = enabled; }
-
-  /// Convert given shader from input to output format.
-  /// Will also apply provided transforms and run the inspector over the result.
-  /// @param data buffer of data that will interpreted as a byte array or string
-  ///             depending on the shader input format.
-  /// @param size number of elements in buffer
-  /// @returns 0, this is what libFuzzer expects
-  int Run(const uint8_t* data, size_t size);
-
-  /// Diagnostic messages generated while Run() is executed.
-  const tint::diag::List& Diagnostics() const { return diagnostics_; }
-
-  /// Are there any errors in the diagnostic messages?
-  bool HasErrors() const { return diagnostics_.contains_errors(); }
-
-  /// Generated SPIR-V binary, if SPIR-V was emitted.
-  const std::vector<uint32_t>& GetGeneratedSpirv() const {
-    return generated_spirv_;
-  }
-
-  /// Generated WGSL string, if WGSL was emitted.
-  const std::string& GetGeneratedWgsl() const { return generated_wgsl_; }
-
-  /// Generated HLSL string, if HLSL was emitted.
-  const std::string& GetGeneratedHlsl() const { return generated_hlsl_; }
-
-  /// Generated MSL string, if HLSL was emitted.
-  const std::string& GetGeneratedMsl() const { return generated_msl_; }
-
-  /// Setter for SPIR-V emission options
-  void SetOptionsSpirv(const writer::spirv::Options& options) {
-    options_spirv_ = options;
-  }
-
-  /// Setter for WGSL emission options
-  void SetOptionsWgsl(const writer::wgsl::Options& options) {
-    options_wgsl_ = options;
-  }
-
-  /// Setter for HLSL emission options
-  void SetOptionsHlsl(const writer::hlsl::Options& options) {
-    options_hlsl_ = options;
-  }
-
-  /// Setter for MSL emission options
-  void SetOptionsMsl(const writer::msl::Options& options) {
-    options_msl_ = options;
-  }
-
- private:
-  InputFormat input_;
-  OutputFormat output_;
-  transform::Manager* transform_manager_ = nullptr;
-  transform::DataMap* transform_inputs_ = nullptr;
-  bool dump_input_ = false;
-  tint::diag::List diagnostics_;
-  bool enforce_validity = false;
-
-  std::vector<uint32_t> generated_spirv_;
-  std::string generated_wgsl_;
-  std::string generated_hlsl_;
-  std::string generated_msl_;
-
-  writer::spirv::Options options_spirv_;
-  writer::wgsl::Options options_wgsl_;
-  writer::hlsl::Options options_hlsl_;
-  writer::msl::Options options_msl_;
-
-#if TINT_BUILD_WGSL_READER
-  /// The source file needs to live at least as long as #diagnostics_
-  std::unique_ptr<Source::File> file_;
-#endif  // TINT_BUILD_WGSL_READER
-
-  /// Runs a series of reflection operations to exercise the Inspector API.
-  void RunInspector(Program* program);
-};
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_COMMON_FUZZER_H_
diff --git a/fuzzers/tint_first_index_offset_fuzzer.cc b/fuzzers/tint_first_index_offset_fuzzer.cc
deleted file mode 100644
index 1dd6993..0000000
--- a/fuzzers/tint_first_index_offset_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<transform::FirstIndexOffset>();
-
-  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_reader_writer_fuzzer.h b/fuzzers/tint_reader_writer_fuzzer.h
deleted file mode 100644
index 24a4bd9..0000000
--- a/fuzzers/tint_reader_writer_fuzzer.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_READER_WRITER_FUZZER_H_
-#define FUZZERS_TINT_READER_WRITER_FUZZER_H_
-
-#include <memory>
-
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Wrapper around the common fuzzing class for tint_*_reader_*_writter fuzzers
-class ReaderWriterFuzzer : public CommonFuzzer {
- public:
-  /// Constructor
-  /// Pass through to the CommonFuzzer constructor
-  ReaderWriterFuzzer(InputFormat input, OutputFormat output)
-      : CommonFuzzer(input, output) {}
-
-  /// Destructor
-  ~ReaderWriterFuzzer() {}
-
-  /// Pass through to the CommonFuzzer setter, but records if it has been
-  /// invoked.
-  void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) {
-    tm_set_ = true;
-    CommonFuzzer::SetTransformManager(tm, inputs);
-  }
-
-  /// Pass through to the CommonFuzzer implementation, but will setup a
-  /// robustness transform, if no other transforms have been set.
-  int Run(const uint8_t* data, size_t size) {
-    if (!tm_set_) {
-      tb_ = std::make_unique<TransformBuilder>(data, size);
-      tb_->AddTransform<tint::transform::Robustness>();
-      SetTransformManager(tb_->manager(), tb_->data_map());
-    }
-
-    return CommonFuzzer::Run(data, size);
-  }
-
- private:
-  bool tm_set_ = false;
-  std::unique_ptr<TransformBuilder> tb_;
-};
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_READER_WRITER_FUZZER_H_
diff --git a/fuzzers/tint_regex_fuzzer/BUILD.gn b/fuzzers/tint_regex_fuzzer/BUILD.gn
deleted file mode 100644
index 646c11a..0000000
--- a/fuzzers/tint_regex_fuzzer/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("../../tint_overrides_with_defaults.gni")
-
-if (build_with_chromium) {
-  source_set("tint_regex_fuzzer") {
-    public_configs = [
-      "${tint_root_dir}/src:tint_config",
-      "${tint_root_dir}/src:tint_common_config",
-    ]
-
-    deps = [ "${tint_root_dir}/fuzzers:tint_fuzzer_common_src" ]
-
-    sources = [
-      "cli.cc",
-      "cli.h",
-      "fuzzer.cc",
-      "override_cli_params.h",
-      "wgsl_mutator.cc",
-      "wgsl_mutator.h",
-    ]
-  }
-}
diff --git a/fuzzers/tint_regex_fuzzer/cli.cc b/fuzzers/tint_regex_fuzzer/cli.cc
deleted file mode 100644
index 3e96689..0000000
--- a/fuzzers/tint_regex_fuzzer/cli.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-
-#include <cstring>
-#include <iostream>
-#include <limits>
-#include <sstream>
-#include <string>
-#include <utility>
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-namespace {
-
-const char* const kHelpMessage = R"(
-This is a fuzzer for the Tint compiler that works by mutating a WGSL shader.
-
-Below is a list of all supported parameters for this fuzzer. You may want to
-run it with -help=1 to check out libfuzzer parameters.
-
-  -tint_fuzzing_target=
-                       Specifies the shading language to target during fuzzing.
-                       This must be one or a combination of `wgsl`, `spv`, `hlsl`,
-                       `msl` (without `) separated by commas. By default it's
-                       `wgsl,msl,hlsl,spv`.
-
-  -tint_help
-                       Show this message. Note that there is also a -help=1
-                       parameter that will display libfuzzer's help message.
-)";
-
-bool HasPrefix(const char* str, const char* prefix) {
-  return strncmp(str, prefix, strlen(prefix)) == 0;
-}
-
-[[noreturn]] void InvalidParam(const char* param) {
-  std::cout << "Invalid value for " << param << std::endl;
-  std::cout << kHelpMessage << std::endl;
-  exit(1);
-}
-
-bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
-  if (!strcmp(value, "wgsl")) {
-    *out = FuzzingTarget::kWgsl;
-  } else if (!strcmp(value, "spv")) {
-    *out = FuzzingTarget::kSpv;
-  } else if (!strcmp(value, "msl")) {
-    *out = FuzzingTarget::kMsl;
-  } else if (!strcmp(value, "hlsl")) {
-    *out = FuzzingTarget::kHlsl;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-CliParams ParseCliParams(int* argc, char** argv) {
-  CliParams cli_params;
-  auto help = false;
-
-  for (int i = *argc - 1; i > 0; --i) {
-    auto param = argv[i];
-    auto recognized_parameter = true;
-
-    if (HasPrefix(param, "-tint_fuzzing_target=")) {
-      auto result = FuzzingTarget::kNone;
-
-      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
-      for (std::string value; std::getline(ss, value, ',');) {
-        auto tmp = FuzzingTarget::kNone;
-        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
-          InvalidParam(param);
-        }
-        result = result | tmp;
-      }
-
-      if (result == FuzzingTarget::kNone) {
-        InvalidParam(param);
-      }
-
-      cli_params.fuzzing_target = result;
-    } else if (!strcmp(param, "-tint_help")) {
-      help = true;
-    } else {
-      recognized_parameter = false;
-    }
-
-    if (recognized_parameter) {
-      // Remove the recognized parameter from the list of all parameters by
-      // swapping it with the last one. This will suppress warnings in the
-      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
-      // that all user-defined parameters start with two dashes. However, we are
-      // forced to use a single one to make the fuzzer compatible with the
-      // ClusterFuzz.
-      std::swap(argv[i], argv[*argc - 1]);
-      *argc -= 1;
-    }
-  }
-
-  if (help) {
-    std::cout << kHelpMessage << std::endl;
-    exit(0);
-  }
-
-  return cli_params;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/cli.h b/fuzzers/tint_regex_fuzzer/cli.h
deleted file mode 100644
index 9bad9d2..0000000
--- a/fuzzers/tint_regex_fuzzer/cli.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_REGEX_FUZZER_CLI_H_
-#define FUZZERS_TINT_REGEX_FUZZER_CLI_H_
-
-#include <cstdint>
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-/// The backend this fuzzer will test.
-enum class FuzzingTarget {
-  kNone = 0,
-  kHlsl = 1 << 0,
-  kMsl = 1 << 1,
-  kSpv = 1 << 2,
-  kWgsl = 1 << 3,
-  kAll = kHlsl | kMsl | kSpv | kWgsl
-};
-
-inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
-}
-
-inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
-}
-
-/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
-/// help message
-struct CliParams {
-  /// Compiler backends we want to fuzz.
-  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
-};
-
-/// @brief Parses CLI parameters.
-///
-/// This function will exit the process with non-zero return code if some
-/// parameters are invalid. This function will remove recognized parameters from
-/// `argv` and adjust `argc` accordingly.
-///
-/// @param argc - the total number of parameters.
-/// @param argv - array of all CLI parameters.
-/// @return parsed parameters.
-CliParams ParseCliParams(int* argc, char** argv);
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_REGEX_FUZZER_CLI_H_
diff --git a/fuzzers/tint_regex_fuzzer/fuzzer.cc b/fuzzers/tint_regex_fuzzer/fuzzer.cc
deleted file mode 100644
index 6d79a0b..0000000
--- a/fuzzers/tint_regex_fuzzer/fuzzer.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
-#include "fuzzers/transform_builder.h"
-#include "src/reader/wgsl/parser.h"
-#include "src/writer/wgsl/generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-namespace {
-
-CliParams cli_params{};
-
-enum class MutationKind {
-  kSwapIntervals,
-  kDeleteInterval,
-  kDuplicateInterval,
-  kReplaceIdentifier,
-  kReplaceLiteral,
-  kInsertReturnStatement,
-  kNumMutationKinds
-};
-
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
-  // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
-  // is invalid.
-  cli_params = ParseCliParams(argc, *argv);
-  // For some fuzz targets it is desirable to force the values of certain CLI
-  // parameters after parsing.
-  OverrideCliParams(cli_params);
-  return 0;
-}
-
-extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
-                                          size_t size,
-                                          size_t max_size,
-                                          unsigned seed) {
-  std::string wgsl_code(data, data + size);
-  const std::vector<std::string> delimiters{";"};
-  RandomGenerator generator(seed);
-
-  std::string delimiter =
-      delimiters[generator.GetUInt32(static_cast<uint32_t>(delimiters.size()))];
-
-  MutationKind mutation_kind = static_cast<MutationKind>(generator.GetUInt32(
-      static_cast<uint32_t>(MutationKind::kNumMutationKinds)));
-
-  switch (mutation_kind) {
-    case MutationKind::kSwapIntervals:
-      if (!SwapRandomIntervals(delimiter, wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    case MutationKind::kDeleteInterval:
-      if (!DeleteRandomInterval(delimiter, wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    case MutationKind::kDuplicateInterval:
-      if (!DuplicateRandomInterval(delimiter, wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    case MutationKind::kReplaceIdentifier:
-      if (!ReplaceRandomIdentifier(wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    case MutationKind::kReplaceLiteral:
-      if (!ReplaceRandomIntLiteral(wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    case MutationKind::kInsertReturnStatement:
-      if (!InsertReturnStatement(wgsl_code, generator)) {
-        return 0;
-      }
-      break;
-
-    default:
-      assert(false && "Unreachable");
-      return 0;
-  }
-
-  if (wgsl_code.size() > max_size) {
-    return 0;
-  }
-
-  memcpy(data, wgsl_code.c_str(), wgsl_code.size());
-  return wgsl_code.size();
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size == 0) {
-    return 0;
-  }
-
-  struct Target {
-    FuzzingTarget fuzzing_target;
-    OutputFormat output_format;
-    const char* name;
-  };
-
-  Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"},
-                      {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"},
-                      {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"},
-                      {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}};
-
-  for (auto target : targets) {
-    if ((target.fuzzing_target & cli_params.fuzzing_target) !=
-        target.fuzzing_target) {
-      continue;
-    }
-
-    TransformBuilder tb(data, size);
-    tb.AddTransform<tint::transform::Robustness>();
-
-    CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format);
-    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-
-    fuzzer.Run(data, size);
-  }
-
-  return 0;
-}
-
-}  // namespace
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/override_cli_params.h b/fuzzers/tint_regex_fuzzer/override_cli_params.h
deleted file mode 100644
index f5025fc..0000000
--- a/fuzzers/tint_regex_fuzzer/override_cli_params.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
-#define FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-/// @brief Allows CLI parameters to be overridden.
-///
-/// This function allows fuzz targets to override particular CLI parameters,
-/// for example forcing a particular back-end to be targeted.
-///
-/// @param cli_params - the parsed CLI parameters to be updated.
-void OverrideCliParams(CliParams& cli_params);
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc b/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
deleted file mode 100644
index e5f175b..0000000
--- a/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
+++ /dev/null
@@ -1,521 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "gtest/gtest.h"
-
-#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-namespace {
-
-// Swaps two non-consecutive regions in the edge
-TEST(SwapRegionsTest, SwapIntervalsEdgeNonConsecutive) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;";
-  std::string all_regions = R1 + R2 + R3;
-
-  // this call should swap R1 with R3.
-  SwapIntervals(0, R1.length(), R1.length() + R2.length(), R3.length(),
-                all_regions);
-
-  ASSERT_EQ(R3 + R2 + R1, all_regions);
-}
-
-// Swaps two non-consecutive regions not in the edge
-TEST(SwapRegionsTest, SwapIntervalsNonConsecutiveNonEdge) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // this call should swap R2 with R4.
-  SwapIntervals(R1.length(), R2.length(),
-                R1.length() + R2.length() + R3.length(), R4.length(),
-                all_regions);
-
-  ASSERT_EQ(R1 + R4 + R3 + R2 + R5, all_regions);
-}
-
-// Swaps two consecutive regions not in the edge (sorrounded by other
-// regions)
-TEST(SwapRegionsTest, SwapIntervalsConsecutiveEdge) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4;
-
-  // this call should swap R2 with R3.
-  SwapIntervals(R1.length(), R2.length(), R1.length() + R2.length(),
-                R3.length(), all_regions);
-
-  ASSERT_EQ(R1 + R3 + R2 + R4, all_regions);
-}
-
-// Swaps two consecutive regions not in the edge (not sorrounded by other
-// regions)
-TEST(SwapRegionsTest, SwapIntervalsConsecutiveNonEdge) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // this call should swap R4 with R5.
-  SwapIntervals(R1.length() + R2.length() + R3.length(), R4.length(),
-                R1.length() + R2.length() + R3.length() + R4.length(),
-                R5.length(), all_regions);
-
-  ASSERT_EQ(R1 + R2 + R3 + R5 + R4, all_regions);
-}
-
-// Deletes the first region.
-TEST(DeleteRegionTest, DeleteFirstRegion) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should delete R1.
-  DeleteInterval(0, R1.length(), all_regions);
-
-  ASSERT_EQ(";" + R2 + R3 + R4 + R5, all_regions);
-}
-
-// Deletes the last region.
-TEST(DeleteRegionTest, DeleteLastRegion) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should delete R5.
-  DeleteInterval(R1.length() + R2.length() + R3.length() + R4.length(),
-                 R5.length(), all_regions);
-
-  ASSERT_EQ(R1 + R2 + R3 + R4 + ";", all_regions);
-}
-
-// Deletes the middle region.
-TEST(DeleteRegionTest, DeleteMiddleRegion) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should delete R3.
-  DeleteInterval(R1.length() + R2.length(), R3.length(), all_regions);
-
-  ASSERT_EQ(R1 + R2 + ";" + R4 + R5, all_regions);
-}
-
-TEST(InsertRegionTest, InsertRegionTest1) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should insert R2 after R4.
-  DuplicateInterval(R1.length(), R2.length(),
-                    R1.length() + R2.length() + R3.length() + R4.length() - 1,
-                    all_regions);
-
-  ASSERT_EQ(R1 + R2 + R3 + R4 + R2.substr(1, R2.size() - 1) + R5, all_regions);
-}
-
-TEST(InsertRegionTest, InsertRegionTest2) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should insert R3 after R1.
-  DuplicateInterval(R1.length() + R2.length(), R3.length(), R1.length() - 1,
-                    all_regions);
-
-  ASSERT_EQ(R1 + R3.substr(1, R3.length() - 1) + R2 + R3 + R4 + R5,
-            all_regions);
-}
-
-TEST(InsertRegionTest, InsertRegionTest3) {
-  std::string R1 = ";region1;", R2 = ";regionregion2;",
-              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
-              R5 = ";regionregionregionregionregion5;";
-
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // This call should insert R2 after R5.
-  DuplicateInterval(R1.length(), R2.length(), all_regions.length() - 1,
-                    all_regions);
-
-  ASSERT_EQ(R1 + R2 + R3 + R4 + R5 + R2.substr(1, R2.length() - 1),
-            all_regions);
-}
-
-TEST(ReplaceIdentifierTest, ReplaceIdentifierTest1) {
-  std::string R1 = "|region1|", R2 = "; region2;",
-              R3 = "---------region3---------", R4 = "++region4++",
-              R5 = "***region5***";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // Replaces R3 with R1.
-  ReplaceRegion(0, R1.length(), R1.length() + R2.length(), R3.length(),
-                all_regions);
-
-  ASSERT_EQ(R1 + R2 + R1 + R4 + R5, all_regions);
-}
-
-TEST(ReplaceIdentifierTest, ReplaceIdentifierTest2) {
-  std::string R1 = "|region1|", R2 = "; region2;",
-              R3 = "---------region3---------", R4 = "++region4++",
-              R5 = "***region5***";
-  std::string all_regions = R1 + R2 + R3 + R4 + R5;
-
-  // Replaces R5 with R3.
-  ReplaceRegion(R1.length() + R2.length(), R3.length(),
-                R1.length() + R2.length() + R3.length() + R4.length(),
-                R5.length(), all_regions);
-
-  ASSERT_EQ(R1 + R2 + R3 + R4 + R3, all_regions);
-}
-
-TEST(GetIdentifierTest, GetIdentifierTest1) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-      }
-      @stage(vertex)
-      fn vertex_main() -> @builtin(position) vec4<f32> {
-         clamp_0acf8f();"
-         return vec4<f32>();
-      }
-      @stage(fragment)
-      fn fragment_main() {
-        clamp_0acf8f();
-      }
-      @stage(compute) @workgroup_size(1)
-      fn compute_main() {"
-        var<private> foo: f32 = 0.0;
-        clamp_0acf8f();
-      })";
-
-  std::vector<std::pair<size_t, size_t>> identifiers_pos =
-      GetIdentifiers(wgsl_code);
-
-  std::vector<std::pair<size_t, size_t>> ground_truth = {
-      std::make_pair(3, 12),   std::make_pair(28, 3),  std::make_pair(37, 4),
-      std::make_pair(49, 5),   std::make_pair(60, 3),  std::make_pair(68, 4),
-      std::make_pair(81, 4),   std::make_pair(110, 5), std::make_pair(130, 2),
-      std::make_pair(140, 4),  std::make_pair(151, 7), std::make_pair(169, 4),
-      std::make_pair(190, 12), std::make_pair(216, 6), std::make_pair(228, 3),
-      std::make_pair(251, 5),  std::make_pair(273, 2), std::make_pair(285, 4),
-      std::make_pair(302, 12), std::make_pair(333, 5), std::make_pair(349, 14),
-      std::make_pair(373, 2),  std::make_pair(384, 4), std::make_pair(402, 3),
-      std::make_pair(415, 3),  std::make_pair(420, 3), std::make_pair(439, 12)};
-
-  ASSERT_EQ(ground_truth, identifiers_pos);
-}
-
-TEST(TestGetLiteralsValues, TestGetLiteralsValues1) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-      }
-      @stage(vertex)
-      fn vertex_main() -> @builtin(position) vec4<f32> {
-        clamp_0acf8f();
-        var foo_1: i32 = 3;
-        return vec4<f32>();
-      }
-      @stage(fragment)
-      fn fragment_main() {
-        clamp_0acf8f();
-      }
-      @stage(compute) @workgroup_size(1)
-      fn compute_main() {
-        var<private> foo: f32 = 0.0;
-        var foo_2: i32 = 10;
-        clamp_0acf8f();
-      }
-      foo_1 = 5 + 7;
-      var foo_3 : i32 = -20;)";
-
-  std::vector<std::pair<size_t, size_t>> literals_pos =
-      GetIntLiterals(wgsl_code);
-
-  std::vector<std::string> ground_truth = {"3", "10", "5", "7", "-20"};
-
-  std::vector<std::string> result;
-
-  for (auto pos : literals_pos) {
-    result.push_back(wgsl_code.substr(pos.first, pos.second));
-  }
-
-  ASSERT_EQ(ground_truth, result);
-}
-
-TEST(InsertReturnTest, FindClosingBrace) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-        if(false){
-
-        } else{
-          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-          }
-        }
-        @stage(vertex)
-        fn vertex_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f();
-          var foo_1: i32 = 3;
-          return vec4<f32>();
-        }
-        @stage(fragment)
-        fn fragment_main() {
-          clamp_0acf8f();
-        }
-        @stage(compute) @workgroup_size(1)
-        fn compute_main() {
-          var<private> foo: f32 = 0.0;
-          var foo_2: i32 = 10;
-          clamp_0acf8f();
-        }
-        foo_1 = 5 + 7;
-        var foo_3 : i32 = -20;
-      )";
-  size_t opening_bracket_pos = 18;
-  size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code);
-
-  // The -1 is needed since the function body starts after the left bracket.
-  std::string function_body = wgsl_code.substr(
-      opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1);
-  std::string expected =
-      R"(
-        if(false){
-
-        } else{
-          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-          }
-        )";
-  ASSERT_EQ(expected, function_body);
-}
-
-TEST(InsertReturnTest, FindClosingBraceFailing) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-      // This comment } causes the test to fail.
-      "if(false){
-
-      } else{
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-        }
-      }
-      @stage(vertex)
-      fn vertex_main() -> @builtin(position) vec4<f32> {
-        clamp_0acf8f();
-        var foo_1: i32 = 3;
-        return vec4<f32>();
-      }
-      @stage(fragment)
-      fn fragment_main() {
-        clamp_0acf8f();
-      }
-      @stage(compute) @workgroup_size(1)
-      fn compute_main() {
-        var<private> foo: f32 = 0.0;
-        var foo_2: i32 = 10;
-        clamp_0acf8f();
-      }
-      foo_1 = 5 + 7;
-      var foo_3 : i32 = -20;)";
-  size_t opening_bracket_pos = 18;
-  size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code);
-
-  // The -1 is needed since the function body starts after the left bracket.
-  std::string function_body = wgsl_code.substr(
-      opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1);
-  std::string expected =
-      R"(// This comment } causes the test to fail.
-      "if(false){
-
-      } else{
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-        })";
-  ASSERT_NE(expected, function_body);
-}
-
-TEST(TestInsertReturn, TestInsertReturn1) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-      }
-      @stage(vertex)
-      fn vertex_main() -> @builtin(position) vec4<f32> {
-        clamp_0acf8f();
-        var foo_1: i32 = 3;
-        return vec4<f32>();
-      }
-      @stage(fragment)
-      fn fragment_main() {
-        clamp_0acf8f();
-      }
-      @stage(compute) @workgroup_size(1)
-      fn compute_main() {
-        var<private> foo: f32 = 0.0;
-        var foo_2: i32 = 10;
-        clamp_0acf8f();
-      }
-      foo_1 = 5 + 7;
-      var foo_3 : i32 = -20;)";
-
-  std::vector<size_t> semicolon_pos;
-  for (size_t pos = wgsl_code.find(";", 0); pos != std::string::npos;
-       pos = wgsl_code.find(";", pos + 1)) {
-    semicolon_pos.push_back(pos);
-  }
-
-  // should insert a return true statement after the first semicolon of the
-  // first function the the WGSL-like string above.
-  wgsl_code.insert(semicolon_pos[0] + 1, "return true;");
-
-  std::string expected_wgsl_code =
-      R"(fn clamp_0acf8f() {
-        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());return true;
-      }
-      @stage(vertex)
-      fn vertex_main() -> @builtin(position) vec4<f32> {
-        clamp_0acf8f();
-        var foo_1: i32 = 3;
-        return vec4<f32>();
-      }
-      @stage(fragment)
-      fn fragment_main() {
-        clamp_0acf8f();
-      }
-      @stage(compute) @workgroup_size(1)
-      fn compute_main() {
-        var<private> foo: f32 = 0.0;
-        var foo_2: i32 = 10;
-        clamp_0acf8f();
-      }
-      foo_1 = 5 + 7;
-      var foo_3 : i32 = -20;)";
-
-  ASSERT_EQ(expected_wgsl_code, wgsl_code);
-}
-
-TEST(TestInsertReturn, TestFunctionPositions) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
-        }
-        @stage(vertex)
-        fn vertex_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f();
-          var foo_1: i32 = 3;
-          return vec4<f32>();
-        }
-        @stage(fragment)
-        fn fragment_main() {
-          clamp_0acf8f();
-        }
-        @stage(compute) @workgroup_size(1)
-        fn compute_main() {
-          var<private> foo: f32 = 0.0;
-          var foo_2: i32 = 10;
-          clamp_0acf8f();
-        }
-        fn vert_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f();
-          var foo_1: i32 = 3;
-          return vec4<f32>();
-        }
-        foo_1 = 5 + 7;
-        var foo_3 : i32 = -20;)";
-
-  std::vector<size_t> function_positions = GetFunctionBodyPositions(wgsl_code);
-  std::vector<size_t> expected_positions = {187, 607};
-  ASSERT_EQ(expected_positions, function_positions);
-}
-
-TEST(TestInsertReturn, TestMissingSemicolon) {
-  std::string wgsl_code =
-      R"(fn clamp_0acf8f() {
-          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>())
-        }
-        @stage(vertex)
-        fn vertex_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f()
-          var foo_1: i32 = 3
-          return vec4<f32>()
-        }
-        @stage(fragment)
-        fn fragment_main() {
-          clamp_0acf8f();
-        }
-        @stage(compute) @workgroup_size(1)
-        fn compute_main() {
-          var<private> foo: f32 = 0.0;
-          var foo_2: i32 = 10;
-          clamp_0acf8f();
-        }
-        fn vert_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f()
-          var foo_1: i32 = 3
-          return vec4<f32>()
-        }
-        foo_1 = 5 + 7;
-        var foo_3 : i32 = -20;)";
-
-  RandomGenerator generator(0);
-  InsertReturnStatement(wgsl_code, generator);
-
-  // No semicolons found in the function's body, so wgsl_code
-  // should remain unchanged.
-  std::string expected_wgsl_code =
-      R"(fn clamp_0acf8f() {
-          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>())
-        }
-        @stage(vertex)
-        fn vertex_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f()
-          var foo_1: i32 = 3
-          return vec4<f32>()
-        }
-        @stage(fragment)
-        fn fragment_main() {
-          clamp_0acf8f();
-        }
-        @stage(compute) @workgroup_size(1)
-        fn compute_main() {
-          var<private> foo: f32 = 0.0;
-          var foo_2: i32 = 10;
-          clamp_0acf8f();
-        }
-        fn vert_main() -> @builtin(position) vec4<f32> {
-          clamp_0acf8f()
-          var foo_1: i32 = 3
-          return vec4<f32>()
-        }
-        foo_1 = 5 + 7;
-        var foo_3 : i32 = -20;)";
-  ASSERT_EQ(expected_wgsl_code, wgsl_code);
-}
-
-}  // namespace
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc b/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc
deleted file mode 100644
index 1714393..0000000
--- a/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-void OverrideCliParams(CliParams& /*unused*/) {
-  // Leave the CLI parameters unchanged.
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc b/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc
deleted file mode 100644
index 54a78c5..0000000
--- a/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc b/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc
deleted file mode 100644
index 0d059a7..0000000
--- a/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kMsl;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc b/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc
deleted file mode 100644
index 34a4ee3..0000000
--- a/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kSpv;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc b/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc
deleted file mode 100644
index 4de33b6..0000000
--- a/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_regex_fuzzer/cli.h"
-#include "fuzzers/tint_regex_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-void OverrideCliParams(CliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc b/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
deleted file mode 100644
index 511f621..0000000
--- a/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
+++ /dev/null
@@ -1,358 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
-
-#include <cassert>
-#include <cstring>
-#include <map>
-#include <regex>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
-                                         const std::string& wgsl_code) {
-  std::vector<size_t> result;
-  for (size_t pos = wgsl_code.find(delimiter, 0); pos != std::string::npos;
-       pos = wgsl_code.find(delimiter, pos + 1)) {
-    result.push_back(pos);
-  }
-
-  return result;
-}
-
-std::vector<std::pair<size_t, size_t>> GetIdentifiers(
-    const std::string& wgsl_code) {
-  std::vector<std::pair<size_t, size_t>> result;
-
-  // This regular expression works by looking for a character that
-  // is not part of an identifier followed by a WGSL identifier, followed
-  // by a character which cannot be part of a WGSL identifer. The regex
-  // for the WGSL identifier is obtained from:
-  // https://www.w3.org/TR/WGSL/#identifiers.
-  std::regex wgsl_identifier_regex(
-      "[^a-zA-Z]([a-zA-Z][0-9a-zA-Z_]*)[^0-9a-zA-Z_]");
-
-  std::smatch match;
-
-  std::string::const_iterator search_start(wgsl_code.cbegin());
-  std::string prefix;
-
-  while (regex_search(search_start, wgsl_code.cend(), match,
-                      wgsl_identifier_regex) == true) {
-    prefix += match.prefix();
-    result.push_back(std::make_pair(prefix.size() + 1, match.str(1).size()));
-    prefix += match.str(0);
-    search_start = match.suffix().first;
-  }
-  return result;
-}
-
-std::vector<std::pair<size_t, size_t>> GetIntLiterals(const std::string& s) {
-  std::vector<std::pair<size_t, size_t>> result;
-
-  // Looks for integer literals in decimal or hexadecimal form.
-  // Regex obtained here: https://www.w3.org/TR/WGSL/#literals
-  std::regex int_literal_regex("-?0x[0-9a-fA-F]+ | 0 | -?[1-9][0-9]*");
-  std::regex uint_literal_regex("0x[0-9a-fA-F]+u | 0u | [1-9][0-9]*u");
-  std::smatch match;
-
-  std::string::const_iterator search_start(s.cbegin());
-  std::string prefix = "";
-
-  while (regex_search(search_start, s.cend(), match, int_literal_regex) ||
-         regex_search(search_start, s.cend(), match, uint_literal_regex)) {
-    prefix += match.prefix();
-    result.push_back(
-        std::make_pair(prefix.size() + 1, match.str(0).size() - 1));
-    prefix += match.str(0);
-    search_start = match.suffix().first;
-  }
-  return result;
-}
-
-size_t FindClosingBrace(size_t opening_bracket_pos,
-                        const std::string& wgsl_code) {
-  size_t open_bracket_count = 1;
-  size_t pos = opening_bracket_pos + 1;
-  while (open_bracket_count >= 1 && pos < wgsl_code.size()) {
-    if (wgsl_code[pos] == '{') {
-      ++open_bracket_count;
-    } else if (wgsl_code[pos] == '}') {
-      --open_bracket_count;
-    }
-    ++pos;
-  }
-  return (pos == wgsl_code.size() && open_bracket_count >= 1) ? 0 : pos - 1;
-}
-
-std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code) {
-  // Finds all the functions with a non-void return value.
-  std::regex function_regex("fn.*?->.*?\\{");
-  std::smatch match;
-  std::vector<size_t> result;
-
-  auto search_start(wgsl_code.cbegin());
-  std::string prefix = "";
-
-  while (std::regex_search(search_start, wgsl_code.cend(), match,
-                           function_regex)) {
-    result.push_back(
-        static_cast<size_t>(match.suffix().first - wgsl_code.cbegin() - 1L));
-    search_start = match.suffix().first;
-  }
-  return result;
-}
-
-bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator) {
-  std::vector<size_t> function_body_positions =
-      GetFunctionBodyPositions(wgsl_code);
-
-  // No function was found in wgsl_code.
-  if (function_body_positions.empty()) {
-    return false;
-  }
-
-  // Pick a random function's opening bracket, find the corresponding closing
-  // bracket, and find a semi-colon within the function body.
-  size_t left_bracket_pos = generator.GetRandomElement(function_body_positions);
-
-  size_t right_bracket_pos = FindClosingBrace(left_bracket_pos, wgsl_code);
-
-  if (right_bracket_pos == 0) {
-    return false;
-  }
-
-  std::vector<size_t> semicolon_positions;
-  for (size_t pos = wgsl_code.find(";", left_bracket_pos + 1);
-       pos < right_bracket_pos; pos = wgsl_code.find(";", pos + 1)) {
-    semicolon_positions.push_back(pos);
-  }
-
-  if (semicolon_positions.empty()) {
-    return false;
-  }
-
-  size_t semicolon_position = generator.GetRandomElement(semicolon_positions);
-
-  // Get all identifiers and integer literals to use as potential return values.
-  std::vector<std::pair<size_t, size_t>> identifiers =
-      GetIdentifiers(wgsl_code);
-  auto return_values = identifiers;
-  std::vector<std::pair<size_t, size_t>> int_literals =
-      GetIntLiterals(wgsl_code);
-  return_values.insert(return_values.end(), int_literals.begin(),
-                       int_literals.end());
-  std::pair<size_t, size_t> return_value =
-      generator.GetRandomElement(return_values);
-  std::string return_statement =
-      "return " + wgsl_code.substr(return_value.first, return_value.second) +
-      ";";
-
-  // Insert the return statement immediately after the semicolon.
-  wgsl_code.insert(semicolon_position + 1, return_statement);
-  return true;
-}
-
-void SwapIntervals(size_t idx1,
-                   size_t reg1_len,
-                   size_t idx2,
-                   size_t reg2_len,
-                   std::string& wgsl_code) {
-  std::string region_1 = wgsl_code.substr(idx1 + 1, reg1_len - 1);
-
-  std::string region_2 = wgsl_code.substr(idx2 + 1, reg2_len - 1);
-
-  // The second transformation is done first as it doesn't affect idx2.
-  wgsl_code.replace(idx2 + 1, region_2.size(), region_1);
-
-  wgsl_code.replace(idx1 + 1, region_1.size(), region_2);
-}
-
-void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code) {
-  wgsl_code.erase(idx1 + 1, reg_len - 1);
-}
-
-void DuplicateInterval(size_t idx1,
-                       size_t reg1_len,
-                       size_t idx2,
-                       std::string& wgsl_code) {
-  std::string region = wgsl_code.substr(idx1 + 1, reg1_len - 1);
-  wgsl_code.insert(idx2 + 1, region);
-}
-
-void ReplaceRegion(size_t idx1,
-                   size_t id1_len,
-                   size_t idx2,
-                   size_t id2_len,
-                   std::string& wgsl_code) {
-  std::string region_1 = wgsl_code.substr(idx1, id1_len);
-  std::string region_2 = wgsl_code.substr(idx2, id2_len);
-  wgsl_code.replace(idx2, region_2.size(), region_1);
-}
-
-void ReplaceInterval(size_t start_index,
-                     size_t length,
-                     std::string replacement_text,
-                     std::string& wgsl_code) {
-  std::string region_1 = wgsl_code.substr(start_index, length);
-  wgsl_code.replace(start_index, length, replacement_text);
-}
-
-bool SwapRandomIntervals(const std::string& delimiter,
-                         std::string& wgsl_code,
-                         RandomGenerator& generator) {
-  std::vector<size_t> delimiter_positions =
-      FindDelimiterIndices(delimiter, wgsl_code);
-
-  // Need to have at least 3 indices.
-  if (delimiter_positions.size() < 3) {
-    return false;
-  }
-
-  // Choose indices:
-  //   interval_1_start < interval_1_end <= interval_2_start < interval_2_end
-  uint32_t interval_1_start = generator.GetUInt32(
-      static_cast<uint32_t>(delimiter_positions.size()) - 2u);
-  uint32_t interval_1_end = generator.GetUInt32(
-      interval_1_start + 1u,
-      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
-  uint32_t interval_2_start = generator.GetUInt32(
-      interval_1_end, static_cast<uint32_t>(delimiter_positions.size()) - 1u);
-  uint32_t interval_2_end = generator.GetUInt32(
-      interval_2_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
-
-  SwapIntervals(delimiter_positions[interval_1_start],
-                delimiter_positions[interval_1_end] -
-                    delimiter_positions[interval_1_start],
-                delimiter_positions[interval_2_start],
-                delimiter_positions[interval_2_end] -
-                    delimiter_positions[interval_2_start],
-                wgsl_code);
-
-  return true;
-}
-
-bool DeleteRandomInterval(const std::string& delimiter,
-                          std::string& wgsl_code,
-                          RandomGenerator& generator) {
-  std::vector<size_t> delimiter_positions =
-      FindDelimiterIndices(delimiter, wgsl_code);
-
-  // Need to have at least 2 indices.
-  if (delimiter_positions.size() < 2) {
-    return false;
-  }
-
-  uint32_t interval_start = generator.GetUInt32(
-      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
-  uint32_t interval_end = generator.GetUInt32(
-      interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
-
-  DeleteInterval(
-      delimiter_positions[interval_start],
-      delimiter_positions[interval_end] - delimiter_positions[interval_start],
-      wgsl_code);
-
-  return true;
-}
-
-bool DuplicateRandomInterval(const std::string& delimiter,
-                             std::string& wgsl_code,
-                             RandomGenerator& generator) {
-  std::vector<size_t> delimiter_positions =
-      FindDelimiterIndices(delimiter, wgsl_code);
-
-  // Need to have at least 2 indices
-  if (delimiter_positions.size() < 2) {
-    return false;
-  }
-
-  uint32_t interval_start = generator.GetUInt32(
-      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
-  uint32_t interval_end = generator.GetUInt32(
-      interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
-  uint32_t duplication_point =
-      generator.GetUInt32(static_cast<uint32_t>(delimiter_positions.size()));
-
-  DuplicateInterval(
-      delimiter_positions[interval_start],
-      delimiter_positions[interval_end] - delimiter_positions[interval_start],
-      delimiter_positions[duplication_point], wgsl_code);
-
-  return true;
-}
-
-bool ReplaceRandomIdentifier(std::string& wgsl_code,
-                             RandomGenerator& generator) {
-  std::vector<std::pair<size_t, size_t>> identifiers =
-      GetIdentifiers(wgsl_code);
-
-  // Need at least 2 identifiers
-  if (identifiers.size() < 2) {
-    return false;
-  }
-
-  uint32_t id1_index =
-      generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
-  uint32_t id2_index =
-      generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
-
-  // The two identifiers must be different
-  while (id1_index == id2_index) {
-    id2_index = generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
-  }
-
-  ReplaceRegion(identifiers[id1_index].first, identifiers[id1_index].second,
-                identifiers[id2_index].first, identifiers[id2_index].second,
-                wgsl_code);
-
-  return true;
-}
-
-bool ReplaceRandomIntLiteral(std::string& wgsl_code,
-                             RandomGenerator& generator) {
-  std::vector<std::pair<size_t, size_t>> literals = GetIntLiterals(wgsl_code);
-
-  // Need at least one integer literal
-  if (literals.size() < 1) {
-    return false;
-  }
-
-  uint32_t literal_index =
-      generator.GetUInt32(static_cast<uint32_t>(literals.size()));
-
-  // INT_MAX = 2147483647, INT_MIN = -2147483648
-  std::vector<std::string> boundary_values = {
-      "2147483647", "-2147483648", "1", "-1", "0", "4294967295"};
-
-  uint32_t boundary_index =
-      generator.GetUInt32(static_cast<uint32_t>(boundary_values.size()));
-
-  ReplaceInterval(literals[literal_index].first, literals[literal_index].second,
-                  boundary_values[boundary_index], wgsl_code);
-
-  return true;
-}
-
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/wgsl_mutator.h b/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
deleted file mode 100644
index 5663646..0000000
--- a/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
-#define FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace regex_fuzzer {
-
-/// A function that given a delimiter, returns a vector that contains
-/// all the positions of the delimiter in the WGSL code.
-/// @param delimiter - the delimiter of the enclosed region.
-/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
-/// @return a vector with the positions of the delimiter in the WGSL code.
-std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
-                                         const std::string& wgsl_code);
-
-/// A function that finds all the identifiers in a WGSL-like string.
-/// @param wgsl_code - the WGSL-like string where the identifiers will be found.
-/// @return a vector with the positions and the length of all the
-/// identifiers in wgsl_code.
-std::vector<std::pair<size_t, size_t>> GetIdentifiers(
-    const std::string& wgsl_code);
-
-/// A function that returns returns the starting position
-/// and the length of all the integer literals in a WGSL-like string.
-/// @param wgsl_code - the WGSL-like string where the int literals
-/// will be found.
-/// @return a vector with the starting positions and the length
-/// of all the integer literals.
-std::vector<std::pair<size_t, size_t>> GetIntLiterals(
-    const std::string& wgsl_code);
-
-/// Finds a possible closing brace corresponding to the opening
-/// brace at position opening_bracket_pos.
-/// @param opening_bracket_pos - the position of the opening brace.
-/// @param wgsl_code - the WGSL-like string where the closing brace.
-/// @return the position of the closing bracket or 0 if there is no closing
-/// brace.
-size_t FindClosingBrace(size_t opening_bracket_pos,
-                        const std::string& wgsl_code);
-
-/// Returns the starting_position of the bodies of the functions
-/// that follow the regular expression: fn.*?->.*?\\{, which searches for the
-/// keyword fn followed by the function name, its return type and opening brace.
-/// @param wgsl_code - the WGSL-like string where the functions will be
-/// searched.
-/// @return a vector with the starting position of the function bodies in
-/// wgsl_code.
-std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code);
-
-/// Given 4 indices, idx1, idx2, idx3 and idx4 it swaps the regions
-/// in the interval (idx1, idx2] with the region in the interval (idx3, idx4]
-/// in wgsl_text.
-/// @param idx1 - starting index of the first region.
-/// @param reg1_len - length of the first region.
-/// @param idx2 - starting index of the second region.
-/// @param reg2_len - length of the second region.
-/// @param wgsl_code - the string where the swap will occur.
-void SwapIntervals(size_t idx1,
-                   size_t reg1_len,
-                   size_t idx2,
-                   size_t reg2_len,
-                   std::string& wgsl_code);
-
-/// Given index idx1 it delets the region of length interval_len
-/// starting at index idx1;
-/// @param idx1 - starting index of the first region.
-/// @param reg_len - terminating index of the second region.
-/// @param wgsl_code - the string where the swap will occur.
-void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code);
-
-/// Given 2 indices, idx1, idx2, it inserts the region of length
-/// reg1_len starting at idx1 after idx2.
-/// @param idx1 - starting index of region.
-/// @param reg1_len - length of the region.
-/// @param idx2 - the position where the region will be inserted.
-/// @param wgsl_code - the string where the swap will occur.
-void DuplicateInterval(size_t idx1,
-                       size_t reg1_len,
-                       size_t idx2,
-                       std::string& wgsl_code);
-
-/// Replaces a region of a WGSL-like string of length id2_len starting
-/// at position idx2 with a region of length id1_len starting at
-/// position idx1.
-/// @param idx1 - starting position of the first region.
-/// @param id1_len - length of the first region.
-/// @param idx2 - starting position of the second region.
-/// @param id2_len - length of the second region.
-/// @param wgsl_code - the string where the replacement will occur.
-void ReplaceRegion(size_t idx1,
-                   size_t id1_len,
-                   size_t idx2,
-                   size_t id2_len,
-                   std::string& wgsl_code);
-
-/// Replaces an interval of length `length` starting at start_index
-/// with the `replacement_text`.
-/// @param start_index - starting position of the interval to be replaced.
-/// @param length - length of the interval to be replaced.
-/// @param replacement_text - the interval that will be used as a replacement.
-/// @param wgsl_code - the WGSL-like string where the replacement will occur.
-void ReplaceInterval(size_t start_index,
-                     size_t length,
-                     std::string replacement_text,
-                     std::string& wgsl_code);
-
-/// A function that, given WGSL-like string and a delimiter,
-/// generates another WGSL-like string by picking two random regions
-/// enclosed by the delimiter and swapping them.
-/// @param delimiter - the delimiter that will be used to find enclosed regions.
-/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
-/// @param generator - the random number generator.
-/// @return true if a swap happened or false otherwise.
-bool SwapRandomIntervals(const std::string& delimiter,
-                         std::string& wgsl_code,
-                         RandomGenerator& generator);
-
-/// A function that, given a WGSL-like string and a delimiter,
-/// generates another WGSL-like string by deleting a random
-/// region enclosed by the delimiter.
-/// @param delimiter - the delimiter that will be used to find enclosed regions.
-/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
-/// @param generator - the random number generator.
-/// @return true if a deletion happened or false otherwise.
-bool DeleteRandomInterval(const std::string& delimiter,
-                          std::string& wgsl_code,
-                          RandomGenerator& generator);
-
-/// A function that, given a WGSL-like string and a delimiter,
-/// generates another WGSL-like string by duplicating a random
-/// region enclosed by the delimiter.
-/// @param delimiter - the delimiter that will be used to find enclosed regions.
-/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
-/// @param generator - the random number generator.
-/// @return true if a duplication happened or false otherwise.
-bool DuplicateRandomInterval(const std::string& delimiter,
-                             std::string& wgsl_code,
-                             RandomGenerator& generator);
-
-/// Replaces a randomly-chosen identifier in wgsl_code.
-/// @param wgsl_code - WGSL-like string where the replacement will occur.
-/// @param generator - the random number generator.
-/// @return true if a replacement happened or false otherwise.
-bool ReplaceRandomIdentifier(std::string& wgsl_code,
-                             RandomGenerator& generator);
-
-/// Replaces the value of a randomly-chosen integer with one of
-/// the values in the set {INT_MAX, INT_MIN, 0, -1}.
-/// @param wgsl_code - WGSL-like string where the replacement will occur.
-/// @param generator - the random number generator.
-/// @return true if a replacement happened or false otherwise.
-bool ReplaceRandomIntLiteral(std::string& wgsl_code,
-                             RandomGenerator& generator);
-
-/// Inserts a return statement in a randomly chosen function of a
-/// WGSL-like string. The return value is a randomly-chosen identifier
-/// or literal in the string.
-/// @param wgsl_code - WGSL-like string that will be mutated.
-/// @param generator - the random number generator.
-/// @return true if the mutation was succesful or false otherwise.
-bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator);
-}  // namespace regex_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
diff --git a/fuzzers/tint_renamer_fuzzer.cc b/fuzzers/tint_renamer_fuzzer.cc
deleted file mode 100644
index bdb6ea4..0000000
--- a/fuzzers/tint_renamer_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<transform::Renamer>();
-
-  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_robustness_fuzzer.cc b/fuzzers/tint_robustness_fuzzer.cc
deleted file mode 100644
index 3ec7237..0000000
--- a/fuzzers/tint_robustness_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<tint::transform::Robustness>();
-
-  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_single_entry_point_fuzzer.cc b/fuzzers/tint_single_entry_point_fuzzer.cc
deleted file mode 100644
index f5e6702..0000000
--- a/fuzzers/tint_single_entry_point_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<transform::SingleEntryPoint>();
-
-  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/cli.cc b/fuzzers/tint_spirv_tools_fuzzer/cli.cc
deleted file mode 100644
index b942421..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/cli.cc
+++ /dev/null
@@ -1,484 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-
-#include <fstream>
-#include <limits>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-#include "source/opt/build_module.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-namespace {
-
-const char* const kMutatorParameters = R"(
-Mutators' parameters:
-
-  -tint_donors=
-                       A path to the text file with a list of paths to the
-                       SPIR-V donor files. Check out the doc for the spirv-fuzz
-                       to learn more about donor binaries. Donors are not used
-                       by default.
-
-  -tint_enable_all_fuzzer_passes=
-                       Whether to use all fuzzer passes or a randomly selected subset
-                       of them. This must be one of `true` or `false` (without `).
-                       By default it's `false`.
-
-  -tint_enable_all_reduce_passes=
-                       Whether to use all reduction passes or a randomly selected subset
-                       of them. This must be one of `true` or `false` (without `).
-                       By default it's `false`.
-
-  -tint_opt_batch_size=
-                       The maximum number of spirv-opt optimizations that
-                       will be applied in a single mutation session (i.e.
-                       a call to LLVMFuzzerCustomMutator). This must fit in
-                       uint32_t. By default it's 6.
-
-  -tint_reduction_batch_size=
-                       The maximum number of spirv-reduce reductions that
-                       will be applied in a single mutation session (i.e.
-                       a call to LLVMFuzzerCustomMutator). This must fit in
-                       uint32_t. By default it's 3.
-
-  -tint_repeated_pass_strategy=
-                       The strategy that will be used to recommend the next fuzzer
-                       pass. This must be one of `simple`, `looped` or `random`
-                       (without `). By default it's `simple`. Check out the doc for
-                       spirv-fuzz to learn more.
-
-  -tint_transformation_batch_size=
-                       The maximum number of spirv-fuzz transformations
-                       that will be applied during a single mutation
-                       session (i.e. a call to LLVMFuzzerCustomMutator).
-                       This must fit in uint32_t. By default it's 3.
-
-  -tint_validate_after_each_fuzzer_pass=
-                       Whether to validate SPIR-V binary after each fuzzer pass.
-                       This must be one of `true` or `false` (without `).
-                       By default it's `true`. Switch this to `false` if you experience
-                       bad performance.
-
-  -tint_validate_after_each_opt_pass=
-                       Whether to validate SPIR-V binary after each optimization pass.
-                       This must be one of `true` or `false` (without `).
-                       By default it's `true`. Switch this to `false` if you experience
-                       bad performance.
-
-  -tint_validate_after_each_reduce_pass=
-                       Whether to validate SPIR-V binary after each reduction pass.
-                       This must be one of `true` or `false` (without `).
-                       By default it's `true`. Switch this to `false` if you experience
-                       bad performance.
-)";
-
-const char* const kFuzzerHelpMessage = R"(
-This fuzzer uses SPIR-V binaries to fuzz the Tint compiler. It uses SPIRV-Tools
-to mutate those binaries. The fuzzer works on a corpus of SPIR-V shaders.
-For each shader from the corpus it uses one of `spirv-fuzz`, `spirv-reduce` or
-`spirv-opt` to mutate it and then runs the shader through the Tint compiler in
-two steps:
-- Converts the mutated shader to WGSL.
-- Converts WGSL to some target language specified in the CLI arguments.
-
-Below is a list of all supported parameters for this fuzzer. You may want to
-run it with -help=1 to check out libfuzzer parameters.
-
-Fuzzer parameters:
-
-  -tint_error_dir
-                       The directory that will be used to output invalid SPIR-V
-                       binaries to. This is especially useful during debugging
-                       mutators. The directory must have the following subdirectories:
-                       - spv/ - will be used to output errors, produced during
-                         the conversion from the SPIR-V to WGSL.
-                       - wgsl/ - will be used to output errors, produced during
-                         the conversion from the WGSL to `--fuzzing_target`.
-                       - mutator/ - will be used to output errors, produced by
-                         the mutators.
-                       By default invalid files are not printed out.
-
-  -tint_fuzzing_target
-                       The type of backend to target during fuzzing. This must
-                       be one or a combination of `wgsl`, `spv`, `msl` or `hlsl`
-                       (without `) separated by commas. By default it's
-                       `wgsl,spv,msl,hlsl`.
-
-  -tint_help
-                       Show this message. Note that there is also a -help=1
-                       parameter that will display libfuzzer's help message.
-
-  -tint_mutator_cache_size=
-                       The maximum size of the cache that stores
-                       mutation sessions. This must fit in uint32_t.
-                       By default it's 20.
-
-  -tint_mutator_type=
-                       Determines types of the mutators to run. This must be one or
-                       a combination of `fuzz`, `opt`, `reduce` (without `) separated by
-                       comma. If a combination is specified, each element in the
-                       combination will have an equal chance of mutating a SPIR-V
-                       binary during a mutation session (i.e. if no mutator exists
-                       for that binary in the mutator cache). By default, the
-                       parameter's value is `fuzz,opt,reduce`.
-)";
-
-const char* const kMutatorDebuggerHelpMessage = R"(
-This tool is used to debug *mutators*. It uses CLI arguments similar to the
-ones used by the fuzzer. To debug some mutator you just need to specify the
-mutator type, the seed and the path to the SPIR-V binary that triggered the
-error. This tool will run the mutator on the binary until the error is
-produced or the mutator returns `kLimitReached`.
-
-Note that this is different from debugging the fuzzer by specifying input
-files to test. The difference is that the latter will not execute any
-mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
-tool is useful when one of the SPIRV-Tools mutators crashes or produces an
-invalid binary in LLVMFuzzerCustomMutator.
-
-Debugger parameters:
-
-  --help
-                       Show this message.
-
-  --mutator_type=
-                       Determines the type of the mutator to debug. This must be
-                       one of `fuzz`, `reduce` or `opt` (without `). This parameter
-                       is REQUIRED.
-
-  --original_binary=
-                       The path to the SPIR-V binary that the faulty mutator was
-                       initialized with. This will be dumped on errors by the fuzzer
-                       if `--error_dir` is specified. This parameter is REQUIRED.
-
-  --seed=
-                       The seed for the random number generator that was used to
-                       initialize the mutator. This value is usually printed to
-                       the console when the mutator produces an invalid binary.
-                       It is also dumped into the log file if `--error_dir` is
-                       specified. This must fit in uint32_t. This parameter is
-                       REQUIRED.
-)";
-
-void PrintHelpMessage(const char* help_message) {
-  std::cout << help_message << std::endl << kMutatorParameters << std::endl;
-}
-
-[[noreturn]] void InvalidParameter(const char* help_message,
-                                   const char* param) {
-  std::cout << "Invalid value for " << param << std::endl;
-  PrintHelpMessage(help_message);
-  exit(1);
-}
-
-bool ParseUint32(const char* param, uint32_t* out) {
-  uint64_t value = static_cast<uint64_t>(strtoul(param, nullptr, 10));
-  if (value > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
-    return false;
-  }
-  *out = static_cast<uint32_t>(value);
-  return true;
-}
-
-std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> ParseDonors(
-    const char* file_name) {
-  std::ifstream fin(file_name);
-  if (!fin) {
-    std::cout << "Can't open donors list file: " << file_name << std::endl;
-    exit(1);
-  }
-
-  std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> result;
-  for (std::string donor_file_name; fin >> donor_file_name;) {
-    if (!std::ifstream(donor_file_name)) {
-      std::cout << "Can't open donor file: " << donor_file_name << std::endl;
-      exit(1);
-    }
-
-    result.emplace_back([donor_file_name] {
-      std::vector<uint32_t> binary;
-      if (!util::ReadBinary(donor_file_name, &binary)) {
-        std::cout << "Failed to read donor from: " << donor_file_name
-                  << std::endl;
-        exit(1);
-      }
-      return spvtools::BuildModule(
-          kDefaultTargetEnv, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
-          binary.data(), binary.size());
-    });
-  }
-
-  return result;
-}
-
-bool ParseRepeatedPassStrategy(const char* param,
-                               spvtools::fuzz::RepeatedPassStrategy* out) {
-  if (!strcmp(param, "simple")) {
-    *out = spvtools::fuzz::RepeatedPassStrategy::kSimple;
-  } else if (!strcmp(param, "looped")) {
-    *out = spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations;
-  } else if (!strcmp(param, "random")) {
-    *out = spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool ParseBool(const char* param, bool* out) {
-  if (!strcmp(param, "true")) {
-    *out = true;
-  } else if (!strcmp(param, "false")) {
-    *out = false;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool ParseMutatorType(const char* param, MutatorType* out) {
-  if (!strcmp(param, "fuzz")) {
-    *out = MutatorType::kFuzz;
-  } else if (!strcmp(param, "opt")) {
-    *out = MutatorType::kOpt;
-  } else if (!strcmp(param, "reduce")) {
-    *out = MutatorType::kReduce;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool ParseFuzzingTarget(const char* param, FuzzingTarget* out) {
-  if (!strcmp(param, "wgsl")) {
-    *out = FuzzingTarget::kWgsl;
-  } else if (!strcmp(param, "spv")) {
-    *out = FuzzingTarget::kSpv;
-  } else if (!strcmp(param, "msl")) {
-    *out = FuzzingTarget::kMsl;
-  } else if (!strcmp(param, "hlsl")) {
-    *out = FuzzingTarget::kHlsl;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool HasPrefix(const char* str, const char* prefix) {
-  return strncmp(str, prefix, strlen(prefix)) == 0;
-}
-
-bool ParseMutatorCliParam(const char* param,
-                          const char* help_message,
-                          MutatorCliParams* out) {
-  if (HasPrefix(param, "-tint_transformation_batch_size=")) {
-    if (!ParseUint32(param + sizeof("-tint_transformation_batch_size=") - 1,
-                     &out->transformation_batch_size)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_reduction_batch_size=")) {
-    if (!ParseUint32(param + sizeof("-tint_reduction_batch_size=") - 1,
-                     &out->reduction_batch_size)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_opt_batch_size=")) {
-    if (!ParseUint32(param + sizeof("-tint_opt_batch_size=") - 1,
-                     &out->opt_batch_size)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_donors=")) {
-    out->donors = ParseDonors(param + sizeof("-tint_donors=") - 1);
-  } else if (HasPrefix(param, "-tint_repeated_pass_strategy=")) {
-    if (!ParseRepeatedPassStrategy(
-            param + sizeof("-tint_repeated_pass_strategy=") - 1,
-            &out->repeated_pass_strategy)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_enable_all_fuzzer_passes=")) {
-    if (!ParseBool(param + sizeof("-tint_enable_all_fuzzer_passes=") - 1,
-                   &out->enable_all_fuzzer_passes)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_enable_all_reduce_passes=")) {
-    if (!ParseBool(param + sizeof("-tint_enable_all_reduce_passes=") - 1,
-                   &out->enable_all_reduce_passes)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_validate_after_each_opt_pass=")) {
-    if (!ParseBool(param + sizeof("-tint_validate_after_each_opt_pass=") - 1,
-                   &out->validate_after_each_opt_pass)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_validate_after_each_fuzzer_pass=")) {
-    if (!ParseBool(param + sizeof("-tint_validate_after_each_fuzzer_pass=") - 1,
-                   &out->validate_after_each_fuzzer_pass)) {
-      InvalidParameter(help_message, param);
-    }
-  } else if (HasPrefix(param, "-tint_validate_after_each_reduce_pass=")) {
-    if (!ParseBool(param + sizeof("-tint_validate_after_each_reduce_pass=") - 1,
-                   &out->validate_after_each_reduce_pass)) {
-      InvalidParameter(help_message, param);
-    }
-  } else {
-    return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv) {
-  FuzzerCliParams cli_params;
-  const auto* help_message = kFuzzerHelpMessage;
-  auto help = false;
-
-  for (int i = *argc - 1; i > 0; --i) {
-    auto param = argv[i];
-    auto recognized_param = true;
-
-    if (HasPrefix(param, "-tint_mutator_cache_size=")) {
-      if (!ParseUint32(param + sizeof("-tint_mutator_cache_size=") - 1,
-                       &cli_params.mutator_cache_size)) {
-        InvalidParameter(help_message, param);
-      }
-    } else if (HasPrefix(param, "-tint_mutator_type=")) {
-      auto result = MutatorType::kNone;
-
-      std::stringstream ss(param + sizeof("-tint_mutator_type=") - 1);
-      for (std::string value; std::getline(ss, value, ',');) {
-        auto out = MutatorType::kNone;
-        if (!ParseMutatorType(value.c_str(), &out)) {
-          InvalidParameter(help_message, param);
-        }
-        result = result | out;
-      }
-
-      if (result == MutatorType::kNone) {
-        InvalidParameter(help_message, param);
-      }
-
-      cli_params.mutator_type = result;
-    } else if (HasPrefix(param, "-tint_fuzzing_target=")) {
-      auto result = FuzzingTarget::kNone;
-
-      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
-      for (std::string value; std::getline(ss, value, ',');) {
-        auto tmp = FuzzingTarget::kNone;
-        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
-          InvalidParameter(help_message, param);
-        }
-        result = result | tmp;
-      }
-
-      if (result == FuzzingTarget::kNone) {
-        InvalidParameter(help_message, param);
-      }
-
-      cli_params.fuzzing_target = result;
-    } else if (HasPrefix(param, "-tint_error_dir=")) {
-      cli_params.error_dir = param + sizeof("-tint_error_dir=") - 1;
-    } else if (!strcmp(param, "-tint_help")) {
-      help = true;
-    } else {
-      recognized_param = ParseMutatorCliParam(param, help_message,
-                                              &cli_params.mutator_params);
-    }
-
-    if (recognized_param) {
-      // Remove the recognized parameter from the list of all parameters by
-      // swapping it with the last one. This will suppress warnings in the
-      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
-      // that all user-defined parameters start with two dashes. However, we are
-      // forced to use a single one to make the fuzzer compatible with the
-      // ClusterFuzz.
-      std::swap(argv[i], argv[*argc - 1]);
-      *argc -= 1;
-    }
-  }
-
-  if (help) {
-    PrintHelpMessage(help_message);
-    exit(0);
-  }
-
-  return cli_params;
-}
-
-MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(
-    int argc,
-    const char* const* argv) {
-  MutatorDebuggerCliParams cli_params;
-  bool seed_param_present = false;
-  bool original_binary_param_present = false;
-  bool mutator_type_param_present = false;
-  const auto* help_message = kMutatorDebuggerHelpMessage;
-  auto help = false;
-
-  for (int i = 0; i < argc; ++i) {
-    auto param = argv[i];
-    ParseMutatorCliParam(param, help_message, &cli_params.mutator_params);
-
-    if (HasPrefix(param, "--mutator_type=")) {
-      if (!ParseMutatorType(param + sizeof("--mutator_type=") - 1,
-                            &cli_params.mutator_type)) {
-        InvalidParameter(help_message, param);
-      }
-      mutator_type_param_present = true;
-    } else if (HasPrefix(param, "--original_binary=")) {
-      if (!util::ReadBinary(param + sizeof("--original_binary=") - 1,
-                            &cli_params.original_binary)) {
-        InvalidParameter(help_message, param);
-      }
-      original_binary_param_present = true;
-    } else if (HasPrefix(param, "--seed=")) {
-      if (!ParseUint32(param + sizeof("--seed=") - 1, &cli_params.seed)) {
-        InvalidParameter(help_message, param);
-      }
-      seed_param_present = true;
-    } else if (!strcmp(param, "--help")) {
-      help = true;
-    }
-  }
-
-  if (help) {
-    PrintHelpMessage(help_message);
-    exit(0);
-  }
-
-  std::pair<bool, const char*> required_params[] = {
-      {seed_param_present, "--seed"},
-      {original_binary_param_present, "--original_binary"},
-      {mutator_type_param_present, "--mutator_type"}};
-
-  for (auto required_param : required_params) {
-    if (!required_param.first) {
-      std::cout << required_param.second << " is missing" << std::endl;
-      exit(1);
-    }
-  }
-
-  return cli_params;
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/cli.h b/fuzzers/tint_spirv_tools_fuzzer/cli.h
deleted file mode 100644
index 9d4dd46..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/cli.h
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
-
-#include <string>
-#include <vector>
-
-#include "source/fuzz/fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// Default SPIR-V environment that will be used during fuzzing.
-const auto kDefaultTargetEnv = SPV_ENV_VULKAN_1_1;
-
-/// The type of the mutator to run.
-enum class MutatorType {
-  kNone = 0,
-  kFuzz = 1 << 0,
-  kReduce = 1 << 1,
-  kOpt = 1 << 2,
-  kAll = kFuzz | kReduce | kOpt
-};
-
-inline MutatorType operator|(MutatorType a, MutatorType b) {
-  return static_cast<MutatorType>(static_cast<int>(a) | static_cast<int>(b));
-}
-
-inline MutatorType operator&(MutatorType a, MutatorType b) {
-  return static_cast<MutatorType>(static_cast<int>(a) & static_cast<int>(b));
-}
-
-/// Shading language to target during fuzzing.
-enum class FuzzingTarget {
-  kNone = 0,
-  kHlsl = 1 << 0,
-  kMsl = 1 << 1,
-  kSpv = 1 << 2,
-  kWgsl = 1 << 3,
-  kAll = kHlsl | kMsl | kSpv | kWgsl
-};
-
-inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
-}
-
-inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
-  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
-}
-
-/// These parameters are accepted by various mutators and thus they are accepted
-/// by both the fuzzer and the mutator debugger.
-struct MutatorCliParams {
-  /// SPIR-V target environment for fuzzing.
-  spv_target_env target_env = kDefaultTargetEnv;
-
-  /// The number of spirv-fuzz transformations to apply at a time.
-  uint32_t transformation_batch_size = 3;
-
-  /// The number of spirv-reduce reductions to apply at a time.
-  uint32_t reduction_batch_size = 3;
-
-  /// The number of spirv-opt optimizations to apply at a time.
-  uint32_t opt_batch_size = 6;
-
-  /// The vector of donors to use in spirv-fuzz (see the doc for spirv-fuzz to
-  /// learn more).
-  std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donors = {};
-
-  /// The strategy to use during fuzzing in spirv-fuzz (see the doc for
-  /// spirv-fuzz to learn more).
-  spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy =
-      spvtools::fuzz::RepeatedPassStrategy::kSimple;
-
-  /// Whether to use all fuzzer passes or a randomly selected subset of them.
-  bool enable_all_fuzzer_passes = false;
-
-  /// Whether to use all reduction passes or a randomly selected subset of them.
-  bool enable_all_reduce_passes = false;
-
-  /// Whether to validate the SPIR-V binary after each optimization pass.
-  bool validate_after_each_opt_pass = true;
-
-  /// Whether to validate the SPIR-V binary after each fuzzer pass.
-  bool validate_after_each_fuzzer_pass = true;
-
-  /// Whether to validate the SPIR-V binary after each reduction pass.
-  bool validate_after_each_reduce_pass = true;
-};
-
-/// Parameters specific to the fuzzer. Type `-tint_help` in the CLI to learn
-/// more.
-struct FuzzerCliParams {
-  /// The size of the cache that records ongoing mutation sessions.
-  uint32_t mutator_cache_size = 20;
-
-  /// The type of the mutator to run.
-  MutatorType mutator_type = MutatorType::kAll;
-
-  /// Tint backend to fuzz.
-  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
-
-  /// The path to the directory, that will be used to output buggy shaders.
-  std::string error_dir = "";
-
-  /// Parameters for various mutators.
-  MutatorCliParams mutator_params;
-};
-
-/// Parameters specific to the mutator debugger. Type `--help` in the CLI to
-/// learn more.
-struct MutatorDebuggerCliParams {
-  /// The type of the mutator to debug.
-  MutatorType mutator_type = MutatorType::kNone;
-
-  /// The seed that was used to initialize the mutator.
-  uint32_t seed = 0;
-
-  /// The binary that triggered a bug in the mutator.
-  std::vector<uint32_t> original_binary;
-
-  /// Parameters for various mutators.
-  MutatorCliParams mutator_params;
-};
-
-/// Parses CLI parameters for the fuzzer. This function exits with an error code
-/// and a message is printed to the console if some parameter has invalid
-/// format. You can pass `-tint_help` to check out all available parameters.
-/// This function will remove recognized parameters from the `argv` and adjust
-/// the `argc` accordingly.
-///
-/// @param argc - the number of parameters (identical to the `argc` in `main`
-///     function).
-/// @param argv - array of C strings of parameters.
-/// @return the parsed parameters.
-FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv);
-
-/// Parses CLI parameters for the mutator debugger. This function exits with an
-/// error code and a message is printed to the console if some parameter has
-/// invalid format. You can pass `--help` to check out all available parameters.
-///
-/// @param argc - the number of parameters (identical to the `argc` in `main`
-///     function).
-/// @param argv - array of C strings of parameters.
-/// @return the parsed parameters.
-MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(int argc,
-                                                       const char* const* argv);
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
deleted file mode 100644
index 2cc1293..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-#include "spirv-tools/libspirv.hpp"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-namespace {
-
-struct Context {
-  FuzzerCliParams params;
-  std::unique_ptr<MutatorCache> mutator_cache;
-};
-
-Context* context = nullptr;
-
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
-  auto params = ParseFuzzerCliParams(argc, *argv);
-  auto mutator_cache =
-      params.mutator_cache_size
-          ? std::make_unique<MutatorCache>(params.mutator_cache_size)
-          : nullptr;
-  context = new Context{std::move(params), std::move(mutator_cache)};
-  OverrideCliParams(context->params);
-  return 0;
-}
-
-std::unique_ptr<Mutator> CreateMutator(const std::vector<uint32_t>& binary,
-                                       unsigned seed) {
-  std::vector<MutatorType> types;
-  types.reserve(3);
-
-  // Determine which mutator we will be using for `binary` at random.
-  auto cli_mutator_type = context->params.mutator_type;
-  if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) {
-    types.push_back(MutatorType::kFuzz);
-  }
-  if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) {
-    types.push_back(MutatorType::kReduce);
-  }
-  if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) {
-    types.push_back(MutatorType::kOpt);
-  }
-
-  assert(!types.empty() && "At least one mutator type must be specified");
-  RandomGenerator generator(seed);
-  auto mutator_type =
-      types[generator.GetUInt32(static_cast<uint32_t>(types.size()))];
-
-  const auto& mutator_params = context->params.mutator_params;
-  switch (mutator_type) {
-    case MutatorType::kFuzz:
-      return std::make_unique<SpirvFuzzMutator>(
-          mutator_params.target_env, binary, seed, mutator_params.donors,
-          mutator_params.enable_all_fuzzer_passes,
-          mutator_params.repeated_pass_strategy,
-          mutator_params.validate_after_each_fuzzer_pass,
-          mutator_params.transformation_batch_size);
-    case MutatorType::kReduce:
-      return std::make_unique<SpirvReduceMutator>(
-          mutator_params.target_env, binary, seed,
-          mutator_params.reduction_batch_size,
-          mutator_params.enable_all_reduce_passes,
-          mutator_params.validate_after_each_reduce_pass);
-    case MutatorType::kOpt:
-      return std::make_unique<SpirvOptMutator>(
-          mutator_params.target_env, seed, binary,
-          mutator_params.validate_after_each_opt_pass,
-          mutator_params.opt_batch_size);
-    default:
-      assert(false && "All mutator types must be handled above");
-      return nullptr;
-  }
-}
-
-void CLIMessageConsumer(spv_message_level_t level,
-                        const char*,
-                        const spv_position_t& position,
-                        const char* message) {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cout << "warning: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cout << "info: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    default:
-      break;
-  }
-}
-
-bool IsValid(const std::vector<uint32_t>& binary) {
-  spvtools::SpirvTools tools(context->params.mutator_params.target_env);
-  tools.SetMessageConsumer(CLIMessageConsumer);
-  return tools.IsValid() && tools.Validate(binary.data(), binary.size(),
-                                           spvtools::ValidatorOptions());
-}
-
-extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
-                                          size_t size,
-                                          size_t max_size,
-                                          unsigned seed) {
-  if ((size % sizeof(uint32_t)) != 0) {
-    // A valid SPIR-V binary's size must be a multiple of the size of a 32-bit
-    // word, and the SPIR-V Tools fuzzer is only designed to work with valid
-    // binaries.
-    return 0;
-  }
-
-  std::vector<uint32_t> binary(size / sizeof(uint32_t));
-  std::memcpy(binary.data(), data, size);
-
-  MutatorCache placeholder_cache(1);
-  auto* mutator_cache = context->mutator_cache.get();
-  if (!mutator_cache) {
-    // Use a placeholder cache if the user has decided not to use a real cache.
-    // The placeholder cache will be destroyed when we return from this function
-    // but it will save us from writing all the `if (mutator_cache)` below.
-    mutator_cache = &placeholder_cache;
-  }
-
-  if (!mutator_cache->Get(binary)) {
-    // This is an unknown binary, so its validity must be checked before
-    // proceeding.
-    if (!IsValid(binary)) {
-      return 0;
-    }
-    // Assign a mutator to the binary if it doesn't have one yet.
-    mutator_cache->Put(binary, CreateMutator(binary, seed));
-  }
-
-  auto* mutator = mutator_cache->Get(binary);
-  assert(mutator && "Mutator must be present in the cache");
-
-  auto result = mutator->Mutate();
-
-  if (result.GetStatus() == Mutator::Status::kInvalid) {
-    // The binary is invalid - log the error and remove the mutator from the
-    // cache.
-    util::LogMutatorError(*mutator, context->params.error_dir);
-    mutator_cache->Remove(binary);
-    return 0;
-  }
-
-  if (!result.IsChanged()) {
-    // The mutator didn't change the binary this time. This could be due to the
-    // fact that we've reached the number of mutations we can apply (e.g. the
-    // number of transformations in spirv-fuzz) or the mutator was just unlucky.
-    // Either way, there is no harm in destroying mutator and maybe trying again
-    // later (i.e. if libfuzzer decides to do so).
-    mutator_cache->Remove(binary);
-    return 0;
-  }
-
-  // At this point the binary is valid and was changed by the mutator.
-
-  auto mutated = mutator->GetBinary();
-  auto mutated_bytes_size = mutated.size() * sizeof(uint32_t);
-  if (mutated_bytes_size > max_size) {
-    // The binary is too big. It's unlikely that we'll reduce its size by
-    // applying the mutator one more time.
-    mutator_cache->Remove(binary);
-    return 0;
-  }
-
-  if (result.GetStatus() == Mutator::Status::kComplete) {
-    // Reassign the mutator to the mutated binary in the cache so that we can
-    // access later.
-    mutator_cache->Put(mutated, mutator_cache->Remove(binary));
-  } else {
-    // If the binary is valid and was changed but is not `kComplete`, then the
-    // mutator has reached some limit on the number of mutations.
-    mutator_cache->Remove(binary);
-  }
-
-  std::memcpy(data, mutated.data(), mutated_bytes_size);
-  return mutated_bytes_size;
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size == 0) {
-    return 0;
-  }
-
-  if ((size % sizeof(uint32_t)) != 0) {
-    // The SPIR-V Tools fuzzer has been designed to work with valid
-    // SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit
-    // word.
-    return 0;
-  }
-
-  CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL);
-  spv_to_wgsl.Run(data, size);
-  if (spv_to_wgsl.HasErrors()) {
-    auto error = spv_to_wgsl.Diagnostics().str();
-    util::LogSpvError(error, data, size,
-                      context ? context->params.error_dir : "");
-    return 0;
-  }
-
-  const auto& wgsl = spv_to_wgsl.GetGeneratedWgsl();
-
-  std::pair<FuzzingTarget, OutputFormat> targets[] = {
-      {FuzzingTarget::kHlsl, OutputFormat::kHLSL},
-      {FuzzingTarget::kMsl, OutputFormat::kMSL},
-      {FuzzingTarget::kSpv, OutputFormat::kSpv},
-      {FuzzingTarget::kWgsl, OutputFormat::kWGSL}};
-
-  for (auto target : targets) {
-    if ((target.first & context->params.fuzzing_target) != target.first) {
-      continue;
-    }
-
-    CommonFuzzer fuzzer(InputFormat::kWGSL, target.second);
-    fuzzer.Run(reinterpret_cast<const uint8_t*>(wgsl.data()), wgsl.size());
-    if (fuzzer.HasErrors()) {
-      auto error = spv_to_wgsl.Diagnostics().str();
-      util::LogWgslError(error, data, size, wgsl, target.second,
-                         context->params.error_dir);
-    }
-  }
-
-  return 0;
-}
-
-}  // namespace
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
deleted file mode 100644
index 76cae68..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-// We need to define constructor here so that vtable is produced in this
-// translation unit (see -Wweak-vtables clang flag).
-Mutator::~Mutator() = default;
-
-Mutator::Result::Result(Status status, bool is_changed)
-    : status_(status), is_changed_(is_changed) {
-  assert((is_changed || status == Status::kStuck ||
-          status == Status::kLimitReached) &&
-         "Returning invalid result state");
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator.h b/fuzzers/tint_spirv_tools_fuzzer/mutator.h
deleted file mode 100644
index ee3eecf..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/mutator.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
-
-#include <cassert>
-#include <cstdint>
-#include <string>
-#include <vector>
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// This is an interface that is used to define custom mutators based on the
-/// SPIR-V tools.
-class Mutator {
- public:
-  /// The status of the mutation.
-  enum class Status {
-    /// Binary is valid, the limit is not reached - can mutate further.
-    kComplete,
-
-    /// The binary is valid, the limit of mutations has been reached -
-    /// can't mutate further.
-    kLimitReached,
-
-    /// The binary is valid, the limit is not reached but the mutator has spent
-    /// too much time without mutating anything - better to restart to make sure
-    /// we can make any progress.
-    kStuck,
-
-    /// The binary is invalid - this is likely a bug in the mutator - must
-    /// abort.
-    kInvalid
-  };
-
-  /// Represents the result of the mutation. The following states are possible:
-  /// - if `IsChanged() == false`, then `GetStatus()` can be either
-  ///   `kLimitReached` or `kStuck`.
-  /// - otherwise, any value of `Status` is possible.
-  class Result {
-   public:
-    /// Constructor.
-    /// @param status - the status of the mutation.
-    /// @param is_changed - whether the module was changed during mutation.
-    Result(Status status, bool is_changed);
-
-    /// @return the status of the mutation.
-    Status GetStatus() const { return status_; }
-
-    /// @return whether the module was changed during mutation.
-    bool IsChanged() const { return is_changed_; }
-
-   private:
-    Status status_;
-    bool is_changed_;
-  };
-
-  /// Virtual destructor.
-  virtual ~Mutator();
-
-  /// Causes the mutator to apply a mutation. This method can be called
-  /// multiple times as long as the previous call didn't return
-  /// `Status::kInvalid`.
-  ///
-  /// @return the status of the mutation (e.g. success, error etc) and whether
-  ///     the binary was changed during mutation.
-  virtual Result Mutate() = 0;
-
-  /// Returns the mutated binary. The returned binary is guaranteed to be valid
-  /// iff the previous call to the `Mutate` method returned didn't return
-  /// `Status::kInvalid`.
-  ///
-  /// @return the mutated SPIR-V binary. It might be identical to the original
-  ///     binary if `Result::IsChanged` returns `false`.
-  virtual std::vector<uint32_t> GetBinary() const = 0;
-
-  /// Returns errors, produced by the mutator.
-  ///
-  /// @param path - the directory to which the errors are printed to. No files
-  ///     are created if the `path` is nullptr.
-  /// @param count - the number of the error. Files for this error will be
-  ///     prefixed with `count`.
-  virtual void LogErrors(const std::string* path, uint32_t count) const = 0;
-
-  /// @return errors encountered during the mutation. The returned string is
-  ///     if there were no errors during mutation.
-  virtual std::string GetErrors() const = 0;
-};
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
deleted file mode 100644
index 03849f7..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-MutatorCache::MutatorCache(size_t max_size)
-    : map_(), entries_(), max_size_(max_size) {
-  assert(max_size && "`max_size` may not be 0");
-}
-
-MutatorCache::Value::pointer MutatorCache::Get(const Key& key) {
-  auto it = map_.find(key);
-  if (it == map_.end()) {
-    return nullptr;
-  }
-  UpdateUsage(it);
-  return entries_.front().second.get();
-}
-
-void MutatorCache::Put(const Key& key, Value value) {
-  assert(value && "Mutator cache can't have nullptr unique_ptr");
-  auto it = map_.find(key);
-  if (it != map_.end()) {
-    it->second->second = std::move(value);
-    UpdateUsage(it);
-  } else {
-    if (map_.size() == max_size_) {
-      Remove(*entries_.back().first);
-    }
-
-    entries_.emplace_front(nullptr, std::move(value));
-    auto pair = map_.emplace(key, entries_.begin());
-    assert(pair.second && "The key must be unique");
-    entries_.front().first = &pair.first->first;
-  }
-}
-
-MutatorCache::Value MutatorCache::Remove(const Key& key) {
-  auto it = map_.find(key);
-  if (it == map_.end()) {
-    return nullptr;
-  }
-  auto result = std::move(it->second->second);
-  entries_.erase(it->second);
-  map_.erase(it);
-  return result;
-}
-
-size_t MutatorCache::KeyHash::operator()(
-    const std::vector<uint32_t>& vec) const {
-  return std::hash<std::u32string>()({vec.begin(), vec.end()});
-}
-
-void MutatorCache::UpdateUsage(Map::iterator it) {
-  auto entry = std::move(*it->second);
-  entries_.erase(it->second);
-  entries_.push_front(std::move(entry));
-  it->second = entries_.begin();
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
deleted file mode 100644
index 90dd5ce..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
-
-#include <cassert>
-#include <list>
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// Implementation of a fixed size LRU cache. That is, when the number of
-/// elements reaches a certain threshold, the element that wasn't used for the
-/// longest period of time is removed from the cache when a new element is
-/// inserted. All operations have amortized constant time complexity.
-class MutatorCache {
- public:
-  /// SPIR-V binary that is being mutated.
-  using Key = std::vector<uint32_t>;
-
-  /// Mutator that is used to mutate the `Key`.
-  using Value = std::unique_ptr<Mutator>;
-
-  /// Constructor.
-  /// @param max_size - the maximum number of elements the cache can store. May
-  ///     not be equal to 0.
-  explicit MutatorCache(size_t max_size);
-
-  /// Retrieves a pointer to a value, associated with a given `key`.
-  ///
-  /// If the key is present in the cache, its usage is updated and the
-  /// (non-null) pointer to the value is returned. Otherwise, `nullptr` is
-  /// returned.
-  ///
-  /// @param key - may not exist in this cache.
-  /// @return non-`nullptr` pointer to a value if `key` exists in the cache.
-  /// @return `nullptr` if `key` doesn't exist in this cache.
-  Value::pointer Get(const Key& key);
-
-  /// Inserts a `key`-`value` pair into the cache.
-  ///
-  /// If the `key` is already present, the `value` replaces the old value and
-  /// the usage of `key` is updated. If the `key` is not present, then:
-  /// - if the number of elements in the cache is equal to `max_size`, the
-  ///   key-value pair, where the usage of the key wasn't updated for the
-  ///   longest period of time, is removed from the cache.
-  /// - a new `key`-`value` pair is inserted into the cache.
-  ///
-  /// @param key - a key.
-  /// @param value - may not be a `nullptr`.
-  void Put(const Key& key, Value value);
-
-  /// Removes `key` and an associated value from the cache.
-  ///
-  /// @param key - a key.
-  /// @return a non-`nullptr` pointer to the removed value, associated with
-  ///     `key`.
-  /// @return `nullptr` if `key` is not present in the cache.
-  Value Remove(const Key& key);
-
- private:
-  struct KeyHash {
-    size_t operator()(const std::vector<uint32_t>& vec) const;
-  };
-
-  using Entry = std::pair<const Key*, Value>;
-  using Map = std::unordered_map<Key, std::list<Entry>::iterator, KeyHash>;
-
-  void UpdateUsage(Map::iterator it);
-
-  Map map_;
-  std::list<Entry> entries_;
-  const size_t max_size_;
-};
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
deleted file mode 100644
index 26f0419..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <memory>
-#include <string>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-
-/// This tool is used to debug *mutators*. It uses CLI arguments similar to the
-/// ones used by the fuzzer. To debug some mutator you just need to specify the
-/// mutator type, the seed and the path to the SPIR-V binary that triggered the
-/// error. This tool will run the mutator on the binary until the error is
-/// produced or the mutator returns `kLimitReached`.
-///
-/// Note that this is different from debugging the fuzzer by specifying input
-/// files to test. The difference is that the latter will not execute any
-/// mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
-/// tool is useful when one of the spirv-tools mutators crashes or produces an
-/// invalid binary in LLVMFuzzerCustomMutator.
-int main(int argc, const char** argv) {
-  auto params =
-      tint::fuzzers::spvtools_fuzzer::ParseMutatorDebuggerCliParams(argc, argv);
-
-  std::unique_ptr<tint::fuzzers::spvtools_fuzzer::Mutator> mutator;
-  const auto& mutator_params = params.mutator_params;
-  switch (params.mutator_type) {
-    case tint::fuzzers::spvtools_fuzzer::MutatorType::kFuzz:
-      mutator =
-          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvFuzzMutator>(
-              mutator_params.target_env, params.original_binary, params.seed,
-              mutator_params.donors, mutator_params.enable_all_fuzzer_passes,
-              mutator_params.repeated_pass_strategy,
-              mutator_params.validate_after_each_fuzzer_pass,
-              mutator_params.transformation_batch_size);
-      break;
-    case tint::fuzzers::spvtools_fuzzer::MutatorType::kReduce:
-      mutator =
-          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvReduceMutator>(
-              mutator_params.target_env, params.original_binary, params.seed,
-              mutator_params.reduction_batch_size,
-              mutator_params.enable_all_reduce_passes,
-              mutator_params.validate_after_each_reduce_pass);
-      break;
-    case tint::fuzzers::spvtools_fuzzer::MutatorType::kOpt:
-      mutator =
-          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvOptMutator>(
-              mutator_params.target_env, params.seed, params.original_binary,
-              mutator_params.validate_after_each_opt_pass,
-              mutator_params.opt_batch_size);
-      break;
-    default:
-      assert(false && "All mutator types must've been handled");
-      return 1;
-  }
-
-  while (true) {
-    auto result = mutator->Mutate();
-    if (result.GetStatus() ==
-        tint::fuzzers::spvtools_fuzzer::Mutator::Status::kInvalid) {
-      std::cerr << mutator->GetErrors() << std::endl;
-      return 0;
-    }
-    if (result.GetStatus() ==
-        tint::fuzzers::spvtools_fuzzer::Mutator::Status::kLimitReached) {
-      break;
-    }
-  }
-}
diff --git a/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h b/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h
deleted file mode 100644
index 95218b7..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// @brief Allows CLI parameters to be overridden.
-///
-/// This function allows fuzz targets to override particular CLI parameters,
-/// for example forcing a particular back-end to be targeted.
-///
-/// @param cli_params - the parsed CLI parameters to be updated.
-void OverrideCliParams(FuzzerCliParams& cli_params);
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
deleted file mode 100644
index dca10f1..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
-
-#include <fstream>
-#include <utility>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-#include "source/opt/build_module.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-SpirvFuzzMutator::SpirvFuzzMutator(
-    spv_target_env target_env,
-    std::vector<uint32_t> binary,
-    unsigned seed,
-    const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
-    bool enable_all_passes,
-    spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
-    bool validate_after_each_pass,
-    uint32_t transformation_batch_size)
-    : transformation_batch_size_(transformation_batch_size),
-      errors_(std::make_unique<std::stringstream>()),
-      fuzzer_(nullptr),
-      validator_options_(),
-      original_binary_(std::move(binary)),
-      seed_(seed) {
-  auto ir_context = spvtools::BuildModule(
-      target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
-      original_binary_.data(), original_binary_.size());
-  assert(ir_context && "|binary| is invalid");
-
-  auto transformation_context =
-      std::make_unique<spvtools::fuzz::TransformationContext>(
-          std::make_unique<spvtools::fuzz::FactManager>(ir_context.get()),
-          validator_options_);
-
-  auto fuzzer_context = std::make_unique<spvtools::fuzz::FuzzerContext>(
-      std::make_unique<spvtools::fuzz::PseudoRandomGenerator>(seed),
-      spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false);
-  fuzzer_ = std::make_unique<spvtools::fuzz::Fuzzer>(
-      std::move(ir_context), std::move(transformation_context),
-      std::move(fuzzer_context), util::GetBufferMessageConsumer(errors_.get()),
-      donors, enable_all_passes, repeated_pass_strategy,
-      validate_after_each_pass, validator_options_);
-}
-
-Mutator::Result SpirvFuzzMutator::Mutate() {
-  // The assertion will fail in |fuzzer_->Run| if the previous fuzzing led to
-  // invalid module.
-  auto result = fuzzer_->Run(transformation_batch_size_);
-  switch (result.status) {
-    case spvtools::fuzz::Fuzzer::Status::kComplete:
-      return {Mutator::Status::kComplete, result.is_changed};
-    case spvtools::fuzz::Fuzzer::Status::kModuleTooBig:
-    case spvtools::fuzz::Fuzzer::Status::kTransformationLimitReached:
-      return {Mutator::Status::kLimitReached, result.is_changed};
-    case spvtools::fuzz::Fuzzer::Status::kFuzzerStuck:
-      return {Mutator::Status::kStuck, result.is_changed};
-    case spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule:
-      return {Mutator::Status::kInvalid, result.is_changed};
-  }
-}
-
-std::vector<uint32_t> SpirvFuzzMutator::GetBinary() const {
-  std::vector<uint32_t> result;
-  fuzzer_->GetIRContext()->module()->ToBinary(&result, true);
-  return result;
-}
-
-std::string SpirvFuzzMutator::GetErrors() const {
-  return errors_->str();
-}
-
-void SpirvFuzzMutator::LogErrors(const std::string* path,
-                                 uint32_t count) const {
-  auto message = GetErrors();
-  std::cout << count << " | SpirvFuzzMutator (seed: " << seed_ << ")"
-            << std::endl;
-  std::cout << message << std::endl;
-
-  if (path) {
-    auto prefix = *path + std::to_string(count);
-
-    // Write errors to file.
-    std::ofstream(prefix + ".fuzzer.log") << "seed: " << seed_ << std::endl
-                                          << message << std::endl;
-
-    // Write the invalid SPIR-V binary.
-    util::WriteBinary(prefix + ".fuzzer.invalid.spv", GetBinary());
-
-    // Write the original SPIR-V binary.
-    util::WriteBinary(prefix + ".fuzzer.original.spv", original_binary_);
-
-    // Write transformations.
-    google::protobuf::util::JsonOptions options;
-    options.add_whitespace = true;
-    std::string json;
-    google::protobuf::util::MessageToJsonString(
-        fuzzer_->GetTransformationSequence(), &json, options);
-    std::ofstream(prefix + ".fuzzer.transformations.json") << json << std::endl;
-
-    std::ofstream binary_transformations(
-        prefix + ".fuzzer.transformations.binary",
-        std::ios::binary | std::ios::out);
-    fuzzer_->GetTransformationSequence().SerializeToOstream(
-        &binary_transformations);
-  }
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
deleted file mode 100644
index 79aa07d..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
-
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-
-#include "source/fuzz/fuzzer.h"
-#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
-#include "source/fuzz/pseudo_random_generator.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// The mutator that uses spirv-fuzz to mutate SPIR-V.
-///
-/// The initial `binary` must be valid according to `target_env`. All other
-/// parameters (except for the `seed` which just initializes the RNG) are from
-/// the `spvtools::fuzz::Fuzzer` class.
-class SpirvFuzzMutator : public Mutator {
- public:
-  /// Constructor.
-  /// @param target_env - the target environment for the `binary`.
-  /// @param binary - the SPIR-V binary. Must be valid.
-  /// @param seed - seed for the RNG.
-  /// @param donors - vector of donor suppliers.
-  /// @param enable_all_passes - whether to use all fuzzer passes.
-  /// @param repeated_pass_strategy - the strategy to use when selecting the
-  ///     next fuzzer pass.
-  /// @param validate_after_each_pass - whether to validate the binary after
-  ///     each fuzzer pass.
-  /// @param transformation_batch_size - the maximum number of transformations
-  ///     that will be applied during a single call to `Mutate`. It it's equal
-  ///     to 0 then we apply as much transformations as we can until the
-  ///     threshold in the spvtools::fuzz::Fuzzer is reached (see the doc for
-  ///     that class for more info).
-  SpirvFuzzMutator(
-      spv_target_env target_env,
-      std::vector<uint32_t> binary,
-      uint32_t seed,
-      const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
-      bool enable_all_passes,
-      spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
-      bool validate_after_each_pass,
-      uint32_t transformation_batch_size);
-
-  Result Mutate() override;
-  std::vector<uint32_t> GetBinary() const override;
-  void LogErrors(const std::string* path, uint32_t count) const override;
-  std::string GetErrors() const override;
-
- private:
-  // The number of transformations that will be applied during a single call to
-  // the `Mutate` method. Is this only a lower bound since transformations are
-  // applied in batches by fuzzer passes (see docs for the
-  // `spvtools::fuzz::Fuzzer` for more info).
-  const uint32_t transformation_batch_size_;
-
-  // The errors produced by the `spvtools::fuzz::Fuzzer`.
-  std::unique_ptr<std::stringstream> errors_;
-  std::unique_ptr<spvtools::fuzz::Fuzzer> fuzzer_;
-  spvtools::ValidatorOptions validator_options_;
-
-  // The following fields are useful for debugging.
-
-  // The binary that the mutator is constructed with.
-  const std::vector<uint32_t> original_binary_;
-
-  // The seed that the mutator is constructed with.
-  const uint32_t seed_;
-};
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
deleted file mode 100644
index d52d8ef..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
-
-#include <fstream>
-#include <iostream>
-#include <unordered_set>
-#include <utility>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-#include "spirv-tools/optimizer.hpp"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-SpirvOptMutator::SpirvOptMutator(spv_target_env target_env,
-                                 uint32_t seed,
-                                 std::vector<uint32_t> binary,
-                                 bool validate_after_each_opt,
-                                 uint32_t opt_batch_size)
-    : num_executions_(0),
-      is_valid_(true),
-      target_env_(target_env),
-      original_binary_(std::move(binary)),
-      seed_(seed),
-      opt_passes_({"--combine-access-chains",
-                   "--loop-unroll",
-                   "--merge-blocks",
-                   "--cfg-cleanup",
-                   "--eliminate-dead-functions",
-                   "--merge-return",
-                   "--wrap-opkill",
-                   "--eliminate-dead-code-aggressive",
-                   "--if-conversion",
-                   "--eliminate-local-single-store",
-                   "--eliminate-local-single-block",
-                   "--eliminate-dead-branches",
-                   "--scalar-replacement=0",
-                   "--eliminate-dead-inserts",
-                   "--eliminate-dead-members",
-                   "--simplify-instructions",
-                   "--private-to-local",
-                   "--ssa-rewrite",
-                   "--ccp",
-                   "--reduce-load-size",
-                   "--vector-dce",
-                   "--scalar-replacement=100",
-                   "--inline-entry-points-exhaustive",
-                   "--redundancy-elimination",
-                   "--convert-local-access-chains",
-                   "--copy-propagate-arrays",
-                   "--fix-storage-class"}),
-      optimized_binary_(),
-      validate_after_each_opt_(validate_after_each_opt),
-      opt_batch_size_(opt_batch_size),
-      generator_(seed) {
-  assert(spvtools::SpirvTools(target_env).Validate(original_binary_) &&
-         "Initial binary is invalid");
-  assert(!opt_passes_.empty() && "Must be at least one pass");
-}
-
-SpirvOptMutator::Result SpirvOptMutator::Mutate() {
-  assert(is_valid_ && "The optimizer is not longer valid");
-
-  const uint32_t kMaxNumExecutions = 100;
-  const uint32_t kMaxNumStuck = 10;
-
-  if (num_executions_ == kMaxNumExecutions) {
-    // We've applied this mutator many times already. Indicate to the user that
-    // it might be better to try a different mutator.
-    return {Status::kLimitReached, false};
-  }
-
-  num_executions_++;
-
-  // Get the input binary. If this is the first time we run this mutator, use
-  // the `original_binary_`. Otherwise, one of the following will be true:
-  // - the `optimized_binary_` is not empty.
-  // - the previous call to the `Mutate` method returned `kStuck`.
-  auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_;
-  optimized_binary_.clear();
-
-  assert(!binary.empty() && "Can't run the optimizer on an empty binary");
-
-  // Number of times spirv-opt wasn't able to produce any new result.
-  uint32_t num_stuck = 0;
-  do {
-    // Randomly select `opt_batch_size` optimization passes. If `opt_batch_size`
-    // is equal to 0, we will use the number of passes equal to the number of
-    // all available passes.
-    auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size();
-    std::vector<std::string> passes;
-
-    while (passes.size() < num_of_passes) {
-      auto idx =
-          generator_.GetUInt32(static_cast<uint32_t>(opt_passes_.size()));
-      passes.push_back(opt_passes_[idx]);
-    }
-
-    // Run the `binary` into the `optimized_binary_`.
-    spvtools::Optimizer optimizer(target_env_);
-    optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_));
-    optimizer.SetValidateAfterAll(validate_after_each_opt_);
-    optimizer.RegisterPassesFromFlags(passes);
-    if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) {
-      is_valid_ = false;
-      return {Status::kInvalid, true};
-    }
-  } while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck);
-
-  return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete,
-          !optimized_binary_.empty()};
-}
-
-std::vector<uint32_t> SpirvOptMutator::GetBinary() const {
-  return optimized_binary_;
-}
-
-std::string SpirvOptMutator::GetErrors() const {
-  return errors_.str();
-}
-
-void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const {
-  auto message = GetErrors();
-  std::cout << count << " | SpirvOptMutator (seed: " << seed_ << ")"
-            << std::endl;
-  std::cout << message << std::endl;
-
-  if (path) {
-    auto prefix = *path + std::to_string(count);
-
-    // Write errors to file.
-    std::ofstream(prefix + ".opt.log") << "seed: " << seed_ << std::endl
-                                       << message << std::endl;
-
-    // Write the invalid SPIR-V binary.
-    util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_);
-
-    // Write the original SPIR-V binary.
-    util::WriteBinary(prefix + ".opt.original.spv", original_binary_);
-  }
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
deleted file mode 100644
index 51514fa..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
-
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-#include "spirv-tools/libspirv.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// Mutates the SPIR-V module using the spirv-opt tool.
-///
-/// The initial `binary` must be valid according to `target_env`. On each call
-/// to the `Mutate` method the mutator selects `opt_batch_size` random
-/// optimization passes (with substitutions) and applies them to the binary.
-class SpirvOptMutator : public Mutator {
- public:
-  /// Constructor.
-  /// @param target_env - target environment for the `binary`.
-  /// @param seed - seed for the RNG.
-  /// @param binary - SPIR-V binary. Must be valid.
-  /// @param validate_after_each_opt - whether to validate the binary after each
-  ///     optimization pass.
-  /// @param opt_batch_size - the maximum number of optimization passes that
-  ///     will be applied in a single call to `Mutate`. If it's equal to 0 then
-  ///     all available optimization passes are applied.
-  SpirvOptMutator(spv_target_env target_env,
-                  uint32_t seed,
-                  std::vector<uint32_t> binary,
-                  bool validate_after_each_opt,
-                  uint32_t opt_batch_size);
-
-  Result Mutate() override;
-  std::vector<uint32_t> GetBinary() const override;
-  void LogErrors(const std::string* path, uint32_t count) const override;
-  std::string GetErrors() const override;
-
- private:
-  // Number of times this mutator was executed.
-  uint32_t num_executions_;
-
-  // Whether the last execution left it in a valid state.
-  bool is_valid_;
-
-  // Target environment for the SPIR-V binary.
-  const spv_target_env target_env_;
-
-  // The original SPIR-V binary. Useful for debugging.
-  const std::vector<uint32_t> original_binary_;
-
-  // The seed for the RNG. Useful for debugging.
-  const uint32_t seed_;
-
-  // All the optimization passes available.
-  const std::vector<std::string> opt_passes_;
-
-  // The result of the optimization.
-  std::vector<uint32_t> optimized_binary_;
-
-  // Whether we need to validate the binary after each optimization pass.
-  const bool validate_after_each_opt_;
-
-  // The number of optimization passes to apply at once.
-  const uint32_t opt_batch_size_;
-
-  // All the errors produced by the optimizer.
-  std::stringstream errors_;
-
-  // The random number generator initialized with `seed_`.
-  RandomGenerator generator_;
-};
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
deleted file mode 100644
index e95a8de..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
-
-#include <fstream>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-#include "source/fuzz/fuzzer_util.h"
-#include "source/opt/build_module.h"
-#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
-#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
-#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
-#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
-#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
-#include "source/reduce/remove_block_reduction_opportunity_finder.h"
-#include "source/reduce/remove_function_reduction_opportunity_finder.h"
-#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
-#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
-#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
-#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
-#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env,
-                                       std::vector<uint32_t> binary,
-                                       uint32_t seed,
-                                       uint32_t reductions_batch_size,
-                                       bool enable_all_reductions,
-                                       bool validate_after_each_reduction)
-    : ir_context_(nullptr),
-      finders_(),
-      generator_(seed),
-      errors_(),
-      is_valid_(true),
-      reductions_batch_size_(reductions_batch_size),
-      total_applied_reductions_(0),
-      enable_all_reductions_(enable_all_reductions),
-      validate_after_each_reduction_(validate_after_each_reduction),
-      original_binary_(std::move(binary)),
-      seed_(seed) {
-  ir_context_ = spvtools::BuildModule(
-      target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
-      original_binary_.data(), original_binary_.size());
-  assert(ir_context_ && "|binary| is invalid");
-
-  do {
-    MaybeAddFinder<
-        spvtools::reduce::
-            ConditionalBranchToSimpleConditionalBranchOpportunityFinder>();
-    MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::OperandToConstReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::OperandToUndefReductionOpportunityFinder>();
-    MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::RemoveFunctionReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::RemoveSelectionReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>(
-        true);
-    MaybeAddFinder<
-        spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>();
-    MaybeAddFinder<
-        spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>();
-    MaybeAddFinder<spvtools::reduce::
-                       StructuredLoopToSelectionReductionOpportunityFinder>();
-  } while (finders_.empty());
-}
-
-Mutator::Result SpirvReduceMutator::Mutate() {
-  assert(is_valid_ && "Can't mutate invalid module");
-
-  // The upper limit on the number of applied reduction passes.
-  const uint32_t kMaxAppliedReductions = 500;
-  const auto old_applied_reductions = total_applied_reductions_;
-
-  // The upper limit on the number of failed attempts to apply reductions (i.e.
-  // when no reduction was returned by the reduction finder).
-  const uint32_t kMaxConsecutiveFailures = 10;
-  uint32_t num_consecutive_failures = 0;
-
-  // Iterate while we haven't exceeded the limit on the total number of applied
-  // reductions, the limit on the number of reductions applied at once and limit
-  // on the number of consecutive failed attempts.
-  while (total_applied_reductions_ < kMaxAppliedReductions &&
-         (reductions_batch_size_ == 0 ||
-          total_applied_reductions_ - old_applied_reductions <
-              reductions_batch_size_) &&
-         num_consecutive_failures < kMaxConsecutiveFailures) {
-    // Select an opportunity finder and get some reduction opportunities from
-    // it.
-    auto finder = GetRandomElement(&finders_);
-    auto reduction_opportunities =
-        finder->GetAvailableOpportunities(ir_context_.get(), 0);
-
-    if (reduction_opportunities.empty()) {
-      // There is nothing to reduce. We increase the counter to make sure we
-      // don't stuck in this situation.
-      num_consecutive_failures++;
-    } else {
-      // Apply a random reduction opportunity. The latter should be applicable.
-      auto opportunity = GetRandomElement(&reduction_opportunities);
-      assert(opportunity->PreconditionHolds() && "Preconditions should hold");
-      total_applied_reductions_++;
-      num_consecutive_failures = 0;
-      if (!ApplyReduction(opportunity)) {
-        // The module became invalid as a result of the applied reduction.
-        is_valid_ = false;
-        return {Mutator::Status::kInvalid,
-                total_applied_reductions_ != old_applied_reductions};
-      }
-    }
-  }
-
-  auto is_changed = total_applied_reductions_ != old_applied_reductions;
-  if (total_applied_reductions_ == kMaxAppliedReductions) {
-    return {Mutator::Status::kLimitReached, is_changed};
-  }
-
-  if (num_consecutive_failures == kMaxConsecutiveFailures) {
-    return {Mutator::Status::kStuck, is_changed};
-  }
-
-  assert(is_changed && "This is the only way left to break the loop");
-  return {Mutator::Status::kComplete, is_changed};
-}
-
-bool SpirvReduceMutator::ApplyReduction(
-    spvtools::reduce::ReductionOpportunity* reduction_opportunity) {
-  reduction_opportunity->TryToApply();
-  return !validate_after_each_reduction_ ||
-         spvtools::fuzz::fuzzerutil::IsValidAndWellFormed(
-             ir_context_.get(), spvtools::ValidatorOptions(),
-             util::GetBufferMessageConsumer(&errors_));
-}
-
-std::vector<uint32_t> SpirvReduceMutator::GetBinary() const {
-  std::vector<uint32_t> result;
-  ir_context_->module()->ToBinary(&result, true);
-  return result;
-}
-
-std::string SpirvReduceMutator::GetErrors() const {
-  return errors_.str();
-}
-
-void SpirvReduceMutator::LogErrors(const std::string* path,
-                                   uint32_t count) const {
-  auto message = GetErrors();
-  std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")"
-            << std::endl;
-  std::cout << message << std::endl;
-
-  if (path) {
-    auto prefix = *path + std::to_string(count);
-
-    // Write errors to file.
-    std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl
-                                           << message << std::endl;
-
-    // Write the invalid SPIR-V binary.
-    util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary());
-
-    // Write the original SPIR-V binary.
-    util::WriteBinary(prefix + ".reducer.original.spv", original_binary_);
-  }
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
deleted file mode 100644
index df5cf9b..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
-
-#include <memory>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "fuzzers/random_generator.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-
-#include "source/reduce/reduction_opportunity_finder.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-/// Mutates SPIR-V binary by running spirv-reduce tool.
-///
-/// The initial `binary` must be valid according to `target_env`. Applies at
-/// most `reductions_batch_size` reductions at a time. This parameter is ignored
-/// if its value is 0. Uses a random subset of reduction opportunity finders by
-/// default. This can be overridden with the `enable_all_reductions` parameter.
-class SpirvReduceMutator : public Mutator {
- public:
-  /// Constructor.
-  /// @param target_env - the target environment for the `binary`.
-  /// @param binary - SPIR-V binary. Must be valid.
-  /// @param seed - the seed for the RNG.
-  /// @param reductions_batch_size - the number of reduction passes that will be
-  ///     applied during a single call to `Mutate`. If it's equal to 0 then we
-  ///     apply the passes until we reach the threshold for the total number of
-  ///     applied passes.
-  /// @param enable_all_reductions - whether to use all reduction passes or only
-  ///     a randomly selected subset of them.
-  /// @param validate_after_each_reduction - whether to validate after each
-  ///     applied reduction.
-  SpirvReduceMutator(spv_target_env target_env,
-                     std::vector<uint32_t> binary,
-                     uint32_t seed,
-                     uint32_t reductions_batch_size,
-                     bool enable_all_reductions,
-                     bool validate_after_each_reduction);
-
-  Result Mutate() override;
-  std::vector<uint32_t> GetBinary() const override;
-  void LogErrors(const std::string* path, uint32_t count) const override;
-  std::string GetErrors() const override;
-
- private:
-  template <typename T, typename... Args>
-  void MaybeAddFinder(Args&&... args) {
-    if (enable_all_reductions_ || generator_.GetBool()) {
-      finders_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
-    }
-  }
-
-  template <typename T>
-  T* GetRandomElement(std::vector<T>* arr) {
-    assert(!arr->empty() && "Can't get random element from an empty vector");
-    auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size()));
-    return &(*arr)[index];
-  }
-
-  template <typename T>
-  T* GetRandomElement(std::vector<std::unique_ptr<T>>* arr) {
-    assert(!arr->empty() && "Can't get random element from an empty vector");
-    auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size()));
-    return (*arr)[index].get();
-  }
-
-  bool ApplyReduction(
-      spvtools::reduce::ReductionOpportunity* reduction_opportunity);
-
-  // The SPIR-V binary that is being reduced.
-  std::unique_ptr<spvtools::opt::IRContext> ir_context_;
-
-  // The selected subset of reduction opportunity finders.
-  std::vector<std::unique_ptr<spvtools::reduce::ReductionOpportunityFinder>>
-      finders_;
-
-  // Random number generator initialized with `seed_`.
-  RandomGenerator generator_;
-
-  // All the errors produced by the reducer.
-  std::stringstream errors_;
-
-  // Whether the last call to the `Mutate` method produced the valid binary.
-  bool is_valid_;
-
-  // The number of reductions to apply on a single call to `Mutate`.
-  const uint32_t reductions_batch_size_;
-
-  // The total number of applied reductions.
-  uint32_t total_applied_reductions_;
-
-  // Whether we want to use all the reduction opportunity finders and not just a
-  // subset of them.
-  const bool enable_all_reductions_;
-
-  // Whether we want to validate all the binary after each reduction.
-  const bool validate_after_each_reduction_;
-
-  // The original binary that was used to initialize this mutator.
-  // Useful for debugging.
-  const std::vector<uint32_t> original_binary_;
-
-  // The seed that was used to initialize the random number generator.
-  // Useful for debugging.
-  const uint32_t seed_;
-};
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc
deleted file mode 100644
index 406d9fd..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-void OverrideCliParams(FuzzerCliParams& /*unused*/) {
-  // Leave the CLI parameters unchanged.
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc
deleted file mode 100644
index d71777f..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-void OverrideCliParams(FuzzerCliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc
deleted file mode 100644
index 91ab2a7..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-void OverrideCliParams(FuzzerCliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kMsl;
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc
deleted file mode 100644
index 4cbc955..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-void OverrideCliParams(FuzzerCliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kSpv;
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc
deleted file mode 100644
index f1370e0..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cassert>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-
-void OverrideCliParams(FuzzerCliParams& cli_params) {
-  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
-         "The fuzzing target should not have been set by a CLI parameter: it "
-         "should have its default value.");
-  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
-}
-
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/util.cc b/fuzzers/tint_spirv_tools_fuzzer/util.cc
deleted file mode 100644
index 2a82f33..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/util.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fstream>
-#include <iostream>
-
-#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-namespace util {
-namespace {
-
-bool WriteBinary(const std::string& path, const uint8_t* data, size_t size) {
-  std::ofstream spv(path, std::ios::binary);
-  return spv && spv.write(reinterpret_cast<const char*>(data),
-                          static_cast<std::streamsize>(size));
-}
-
-void LogError(uint32_t index,
-              const std::string& type,
-              const std::string& message,
-              const std::string* path,
-              const uint8_t* data,
-              size_t size,
-              const std::string* wgsl) {
-  std::cout << index << " | " << type << ": " << message << std::endl;
-
-  if (path) {
-    auto prefix = *path + std::to_string(index);
-    std::ofstream(prefix + ".log") << message << std::endl;
-
-    WriteBinary(prefix + ".spv", data, size);
-
-    if (wgsl) {
-      std::ofstream(prefix + ".wgsl") << *wgsl << std::endl;
-    }
-  }
-}
-
-}  // namespace
-
-spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer) {
-  return [buffer](spv_message_level_t level, const char*,
-                  const spv_position_t& position, const char* message) {
-    std::string status;
-    switch (level) {
-      case SPV_MSG_FATAL:
-      case SPV_MSG_INTERNAL_ERROR:
-      case SPV_MSG_ERROR:
-        status = "ERROR";
-        break;
-      case SPV_MSG_WARNING:
-      case SPV_MSG_INFO:
-      case SPV_MSG_DEBUG:
-        status = "INFO";
-        break;
-    }
-    *buffer << status << " " << position.line << ":" << position.column << ":"
-            << position.index << ": " << message << std::endl;
-  };
-}
-
-void LogMutatorError(const Mutator& mutator, const std::string& error_dir) {
-  static uint32_t mutator_count = 0;
-  auto error_path = error_dir.empty() ? error_dir : error_dir + "/mutator/";
-  mutator.LogErrors(error_dir.empty() ? nullptr : &error_path, mutator_count++);
-}
-
-void LogWgslError(const std::string& message,
-                  const uint8_t* data,
-                  size_t size,
-                  const std::string& wgsl,
-                  OutputFormat output_format,
-                  const std::string& error_dir) {
-  static uint32_t wgsl_count = 0;
-  std::string error_type;
-  switch (output_format) {
-    case OutputFormat::kSpv:
-      error_type = "WGSL -> SPV";
-      break;
-    case OutputFormat::kMSL:
-      error_type = "WGSL -> MSL";
-      break;
-    case OutputFormat::kHLSL:
-      error_type = "WGSL -> HLSL";
-      break;
-    case OutputFormat::kWGSL:
-      error_type = "WGSL -> WGSL";
-      break;
-  }
-  auto error_path = error_dir.empty() ? error_dir : error_dir + "/wgsl/";
-  LogError(wgsl_count++, error_type, message,
-           error_dir.empty() ? nullptr : &error_path, data, size, &wgsl);
-}
-
-void LogSpvError(const std::string& message,
-                 const uint8_t* data,
-                 size_t size,
-                 const std::string& error_dir) {
-  static uint32_t spv_count = 0;
-  auto error_path = error_dir.empty() ? error_dir : error_dir + "/spv/";
-  LogError(spv_count++, "SPV -> WGSL", message,
-           error_dir.empty() ? nullptr : &error_path, data, size, nullptr);
-}
-
-bool ReadBinary(const std::string& path, std::vector<uint32_t>* out) {
-  if (!out) {
-    return false;
-  }
-
-  std::ifstream file(path, std::ios::binary | std::ios::ate);
-  if (!file) {
-    return false;
-  }
-
-  size_t size = static_cast<size_t>(file.tellg());
-  if (!file) {
-    return false;
-  }
-
-  file.seekg(0);
-  if (!file) {
-    return false;
-  }
-
-  std::vector<char> binary(size);
-  if (!file.read(binary.data(), size)) {
-    return false;
-  }
-
-  out->resize(binary.size() / sizeof(uint32_t));
-  std::memcpy(out->data(), binary.data(), binary.size());
-  return true;
-}
-
-bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary) {
-  return WriteBinary(path, reinterpret_cast<const uint8_t*>(binary.data()),
-                     binary.size() * sizeof(uint32_t));
-}
-
-}  // namespace util
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/util.h b/fuzzers/tint_spirv_tools_fuzzer/util.h
deleted file mode 100644
index b5abe47..0000000
--- a/fuzzers/tint_spirv_tools_fuzzer/util.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
-#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
-
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
-
-#include "spirv-tools/libspirv.hpp"
-
-namespace tint {
-namespace fuzzers {
-namespace spvtools_fuzzer {
-namespace util {
-
-/// @param buffer will be used to output errors by the returned message
-///     consumer. Must remain in scope as long as the returned consumer is in
-///     scope.
-/// @return the message consumer that will print errors to the `buffer`.
-spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer);
-
-/// Output errors from the SPV -> WGSL conversion.
-///
-/// @param message - the error message.
-/// @param data - invalid SPIR-V binary.
-/// @param size - the size of `data`.
-/// @param error_dir - the directory, to which the binary will be printed to.
-///     If it's empty, the invalid binary and supplemental files will not be
-///     printed. Otherwise, it must have a `spv/` subdirectory.
-void LogSpvError(const std::string& message,
-                 const uint8_t* data,
-                 size_t size,
-                 const std::string& error_dir);
-
-/// Output errors from the WGSL -> `output_format` conversion.
-///
-/// @param message - the error message.
-/// @param data - the SPIR-V binary that generated the WGSL binary.
-/// @param size - the size of `data`.
-/// @param wgsl - the invalid WGSL binary.
-/// @param output_format - the format which we attempted to convert `wgsl` to.
-/// @param error_dir - the directory, to which the binary will be printed out.
-///     If it's empty, the invalid binary and supplemental files will not be
-///     printed. Otherwise, it must have a `wgsl/` subdirectory.
-void LogWgslError(const std::string& message,
-                  const uint8_t* data,
-                  size_t size,
-                  const std::string& wgsl,
-                  OutputFormat output_format,
-                  const std::string& error_dir);
-
-/// Output errors produced by the mutator.
-///
-/// @param mutator - the mutator with invalid state.
-/// @param error_dir - the directory, to which invalid files will be printed to.
-///     If it's empty, the invalid binary and supplemental files will not be
-///     printed. Otherwise, it must have a `mutator/` subdirectory.
-void LogMutatorError(const Mutator& mutator, const std::string& error_dir);
-
-/// Reads SPIR-V binary from `path` into `out`. Returns `true` if successful and
-/// `false` otherwise (in this case, `out` is unchanged).
-///
-/// @param path - the path to the SPIR-V binary.
-/// @param out - may be a `nullptr`. In this case, `false` is returned.
-/// @return `true` if successful and `false` otherwise.
-bool ReadBinary(const std::string& path, std::vector<uint32_t>* out);
-
-/// Writes `binary` into `path`.
-///
-/// @param path - the path to write `binary` to.
-/// @param binary - SPIR-V binary.
-/// @return whether the operation was successful.
-bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary);
-
-}  // namespace util
-}  // namespace spvtools_fuzzer
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
diff --git a/fuzzers/tint_spv_reader_fuzzer.cc b/fuzzers/tint_spv_reader_fuzzer.cc
deleted file mode 100644
index 945b0f6..0000000
--- a/fuzzers/tint_spv_reader_fuzzer.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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 <vector>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kSpv, OutputFormat::kNone);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc b/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc
deleted file mode 100644
index bc9dfe0..0000000
--- a/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
-                                           OutputFormat::kHLSL);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc b/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc
deleted file mode 100644
index a810250..0000000
--- a/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DataBuilder db(data, size);
-  writer::msl::Options options;
-  GenerateMslOptions(&db, &options);
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
-                                           OutputFormat::kMSL);
-  fuzzer.SetOptionsMsl(options);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc b/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc
deleted file mode 100644
index 05d66a9..0000000
--- a/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DataBuilder db(data, size);
-  writer::spirv::Options options;
-  GenerateSpirvOptions(&db, &options);
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
-                                           OutputFormat::kSpv);
-  fuzzer.SetOptionsSpirv(options);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc b/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc
deleted file mode 100644
index fb18698..0000000
--- a/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
-                                           OutputFormat::kWGSL);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_vertex_pulling_fuzzer.cc b/fuzzers/tint_vertex_pulling_fuzzer.cc
deleted file mode 100644
index f634004..0000000
--- a/fuzzers/tint_vertex_pulling_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-#include "fuzzers/transform_builder.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TransformBuilder tb(data, size);
-  tb.AddTransform<transform::VertexPulling>();
-
-  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
-  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_wgsl_reader_fuzzer.cc b/fuzzers/tint_wgsl_reader_fuzzer.cc
deleted file mode 100644
index 71e9026..0000000
--- a/fuzzers/tint_wgsl_reader_fuzzer.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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 <string>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_common_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kNone);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc b/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc
deleted file mode 100644
index a63ff0f..0000000
--- a/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
-                                           OutputFormat::kHLSL);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc b/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc
deleted file mode 100644
index 409b9a3..0000000
--- a/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DataBuilder db(data, size);
-  writer::msl::Options options;
-  GenerateMslOptions(&db, &options);
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
-                                           OutputFormat::kMSL);
-  fuzzer.SetOptionsMsl(options);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc b/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc
deleted file mode 100644
index 3bacc15..0000000
--- a/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DataBuilder db(data, size);
-  writer::spirv::Options options;
-  GenerateSpirvOptions(&db, &options);
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
-                                           OutputFormat::kSpv);
-  fuzzer.SetOptionsSpirv(options);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc b/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc
deleted file mode 100644
index 7bf9e2c..0000000
--- a/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-
-#include "fuzzers/fuzzer_init.h"
-#include "fuzzers/tint_reader_writer_fuzzer.h"
-
-namespace tint {
-namespace fuzzers {
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
-                                           OutputFormat::kWGSL);
-  fuzzer.SetDumpInput(GetCliParams().dump_input);
-  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
-
-  return fuzzer.Run(data, size);
-}
-
-}  // namespace fuzzers
-}  // namespace tint
diff --git a/fuzzers/transform_builder.h b/fuzzers/transform_builder.h
deleted file mode 100644
index b74a405..0000000
--- a/fuzzers/transform_builder.h
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef FUZZERS_TRANSFORM_BUILDER_H_
-#define FUZZERS_TRANSFORM_BUILDER_H_
-
-#include <string>
-#include <vector>
-
-#include "include/tint/tint.h"
-
-#include "fuzzers/data_builder.h"
-#include "fuzzers/shuffle_transform.h"
-
-namespace tint {
-namespace fuzzers {
-
-/// Fuzzer utility class to build inputs for transforms and setup the transform
-/// manager.
-class TransformBuilder {
- public:
-  /// @brief Initializes the internal builder using a seed value
-  /// @param seed - seed value passed to engine
-  explicit TransformBuilder(uint64_t seed) : builder_(seed) {}
-
-  /// @brief Initializes the internal builder using seed data
-  /// @param data - data fuzzer to calculate seed from
-  /// @param size - size of data buffer
-  explicit TransformBuilder(const uint8_t* data, size_t size)
-      : builder_(data, size) {
-    assert(data != nullptr && "|data| must be !nullptr");
-  }
-
-  ~TransformBuilder() = default;
-
-  transform::Manager* manager() { return &manager_; }
-  transform::DataMap* data_map() { return &data_map_; }
-
-  /// Adds a transform and needed data to |manager_| and |data_map_|.
-  /// @tparam T - A class that inherits from transform::Transform and has an
-  ///             explicit specialization in AddTransformImpl.
-  template <typename T>
-  void AddTransform() {
-    static_assert(std::is_base_of<transform::Transform, T>::value,
-                  "T is not a transform::Transform");
-    AddTransformImpl<T>::impl(this);
-  }
-
-  /// Helper that invokes Add*Transform for all of the platform independent
-  /// passes.
-  void AddPlatformIndependentPasses() {
-    AddTransform<transform::Robustness>();
-    AddTransform<transform::FirstIndexOffset>();
-    AddTransform<transform::BindingRemapper>();
-    AddTransform<transform::Renamer>();
-    AddTransform<transform::SingleEntryPoint>();
-    AddTransform<transform::VertexPulling>();
-  }
-
- private:
-  DataBuilder builder_;
-  transform::Manager manager_;
-  transform::DataMap data_map_;
-
-  DataBuilder* builder() { return &builder_; }
-
-  /// Implementation of AddTransform, specialized for each transform that is
-  /// implemented. Default implementation intentionally deleted to cause compile
-  /// error if unimplemented type passed in.
-  /// @tparam T - A fuzzer transform
-  template <typename T>
-  struct AddTransformImpl;
-
-  /// Implementation of AddTransform for ShuffleTransform
-  template <>
-  struct AddTransformImpl<ShuffleTransform> {
-    /// Add instance of ShuffleTransform to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      tb->manager()->Add<ShuffleTransform>(tb->builder_.build<size_t>());
-    }
-  };
-
-  /// Implementation of AddTransform for transform::Robustness
-  template <>
-  struct AddTransformImpl<transform::Robustness> {
-    /// Add instance of transform::Robustness to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      tb->manager()->Add<transform::Robustness>();
-    }
-  };
-
-  /// Implementation of AddTransform for transform::FirstIndexOffset
-  template <>
-  struct AddTransformImpl<transform::FirstIndexOffset> {
-    /// Add instance of transform::FirstIndexOffset to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      struct Config {
-        uint32_t group;
-        uint32_t binding;
-      };
-
-      Config config = tb->builder()->build<Config>();
-
-      tb->data_map()->Add<tint::transform::FirstIndexOffset::BindingPoint>(
-          config.binding, config.group);
-      tb->manager()->Add<transform::FirstIndexOffset>();
-    }
-  };
-
-  /// Implementation of AddTransform for transform::BindingRemapper
-  template <>
-  struct AddTransformImpl<transform::BindingRemapper> {
-    /// Add instance of transform::BindingRemapper to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      struct Config {
-        uint8_t old_group;
-        uint8_t old_binding;
-        uint8_t new_group;
-        uint8_t new_binding;
-        ast::Access new_access;
-      };
-
-      std::vector<Config> configs = tb->builder()->vector<Config>();
-      transform::BindingRemapper::BindingPoints binding_points;
-      transform::BindingRemapper::AccessControls accesses;
-      for (const auto& config : configs) {
-        binding_points[{config.old_binding, config.old_group}] = {
-            config.new_binding, config.new_group};
-        accesses[{config.old_binding, config.old_group}] = config.new_access;
-      }
-
-      tb->data_map()->Add<transform::BindingRemapper::Remappings>(
-          binding_points, accesses, tb->builder()->build<bool>());
-      tb->manager()->Add<transform::BindingRemapper>();
-    }
-  };
-
-  /// Implementation of AddTransform for transform::Renamer
-  template <>
-  struct AddTransformImpl<transform::Renamer> {
-    /// Add instance of transform::Renamer to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      tb->manager()->Add<transform::Renamer>();
-    }
-  };
-
-  /// Implementation of AddTransform for transform::SingleEntryPoint
-  template <>
-  struct AddTransformImpl<transform::SingleEntryPoint> {
-    /// Add instance of transform::SingleEntryPoint to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      auto input = tb->builder()->build<std::string>();
-      transform::SingleEntryPoint::Config cfg(input);
-
-      tb->data_map()->Add<transform::SingleEntryPoint::Config>(cfg);
-      tb->manager()->Add<transform::SingleEntryPoint>();
-    }
-  };  // struct AddTransformImpl<transform::SingleEntryPoint>
-
-  /// Implementation of AddTransform for transform::VertexPulling
-  template <>
-  struct AddTransformImpl<transform::VertexPulling> {
-    /// Add instance of transform::VertexPulling to TransformBuilder
-    /// @param tb - TransformBuilder to add transform to
-    static void impl(TransformBuilder* tb) {
-      transform::VertexPulling::Config cfg;
-      cfg.entry_point_name = tb->builder()->build<std::string>();
-      cfg.vertex_state =
-          tb->builder()->vector<transform::VertexBufferLayoutDescriptor>(
-              GenerateVertexBufferLayoutDescriptor);
-      cfg.pulling_group = tb->builder()->build<uint32_t>();
-
-      tb->data_map()->Add<transform::VertexPulling::Config>(cfg);
-      tb->manager()->Add<transform::VertexPulling>();
-    }
-
-   private:
-    /// Generate an instance of transform::VertexAttributeDescriptor
-    /// @param b - DataBuilder to use
-    static transform::VertexAttributeDescriptor
-    GenerateVertexAttributeDescriptor(DataBuilder* b) {
-      transform::VertexAttributeDescriptor desc{};
-      desc.format = b->enum_class<transform::VertexFormat>(
-          static_cast<uint8_t>(transform::VertexFormat::kLastEntry) + 1);
-      desc.offset = b->build<uint32_t>();
-      desc.shader_location = b->build<uint32_t>();
-      return desc;
-    }
-
-    /// Generate an instance of VertexBufferLayoutDescriptor
-    /// @param b - DataBuilder to use
-    static transform::VertexBufferLayoutDescriptor
-    GenerateVertexBufferLayoutDescriptor(DataBuilder* b) {
-      transform::VertexBufferLayoutDescriptor desc;
-      desc.array_stride = b->build<uint32_t>();
-      desc.step_mode = b->enum_class<transform::VertexStepMode>(
-          static_cast<uint8_t>(transform::VertexStepMode::kLastEntry) + 1);
-      desc.attributes = b->vector<transform::VertexAttributeDescriptor>(
-          GenerateVertexAttributeDescriptor);
-      return desc;
-    }
-  };
-};  // class TransformBuilder
-
-}  // namespace fuzzers
-}  // namespace tint
-
-#endif  // FUZZERS_TRANSFORM_BUILDER_H_
diff --git a/include/tint/tint.h b/include/tint/tint.h
index f959cbf..1a04196 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -18,51 +18,51 @@
 // TODO(tint:88): When implementing support for an install target, all of these
 //                headers will need to be moved to include/tint/.
 
-#include "src/ast/pipeline_stage.h"
-#include "src/demangler.h"
-#include "src/diagnostic/printer.h"
-#include "src/inspector/inspector.h"
-#include "src/reader/reader.h"
-#include "src/sem/type_manager.h"
-#include "src/transform/binding_remapper.h"
-#include "src/transform/first_index_offset.h"
-#include "src/transform/fold_trivial_single_use_lets.h"
-#include "src/transform/manager.h"
-#include "src/transform/multiplanar_external_texture.h"
-#include "src/transform/renamer.h"
-#include "src/transform/robustness.h"
-#include "src/transform/single_entry_point.h"
-#include "src/transform/vertex_pulling.h"
-#include "src/writer/writer.h"
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/demangler.h"
+#include "src/tint/diagnostic/printer.h"
+#include "src/tint/inspector/inspector.h"
+#include "src/tint/reader/reader.h"
+#include "src/tint/sem/type_manager.h"
+#include "src/tint/transform/binding_remapper.h"
+#include "src/tint/transform/first_index_offset.h"
+#include "src/tint/transform/fold_trivial_single_use_lets.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/multiplanar_external_texture.h"
+#include "src/tint/transform/renamer.h"
+#include "src/tint/transform/robustness.h"
+#include "src/tint/transform/single_entry_point.h"
+#include "src/tint/transform/vertex_pulling.h"
+#include "src/tint/writer/writer.h"
 
 #if TINT_BUILD_SPV_READER
-#include "src/reader/spirv/parser.h"
+#include "src/tint/reader/spirv/parser.h"
 #endif  // TINT_BUILD_SPV_READER
 
 #if TINT_BUILD_WGSL_READER
-#include "src/reader/wgsl/parser.h"
+#include "src/tint/reader/wgsl/parser.h"
 #endif  // TINT_BUILD_WGSL_READER
 
 #if TINT_BUILD_SPV_WRITER
 #include "spirv-tools/libspirv.hpp"
-#include "src/writer/spirv/generator.h"
+#include "src/tint/writer/spirv/generator.h"
 #endif  // TINT_BUILD_SPV_WRITER
 
 #if TINT_BUILD_WGSL_WRITER
-#include "src/writer/wgsl/generator.h"
+#include "src/tint/writer/wgsl/generator.h"
 #endif  // TINT_BUILD_WGSL_WRITER
 
 #if TINT_BUILD_MSL_WRITER
-#include "src/writer/msl/generator.h"
+#include "src/tint/writer/msl/generator.h"
 #endif  // TINT_BUILD_MSL_WRITER
 
 #if TINT_BUILD_HLSL_WRITER
-#include "src/writer/hlsl/generator.h"
+#include "src/tint/writer/hlsl/generator.h"
 #endif  // TINT_BUILD_HLSL_WRITER
 
 #if TINT_BUILD_GLSL_WRITER
-#include "src/transform/glsl.h"
-#include "src/writer/glsl/generator.h"
+#include "src/tint/transform/glsl.h"
+#include "src/tint/writer/glsl/generator.h"
 #endif  // TINT_BUILD_GLSL_WRITER
 
 #endif  // INCLUDE_TINT_TINT_H_
diff --git a/kokoro/linux/docker.sh b/kokoro/linux/docker.sh
index 22a15ac..d0ec4c5 100755
--- a/kokoro/linux/docker.sh
+++ b/kokoro/linux/docker.sh
@@ -157,9 +157,9 @@
         hide_cmds
     fi
 
-    status "Testing test/test-all.sh"
+    status "Testing test/tint/test-all.sh"
     show_cmds
-        ${SRC_DIR}/test/test-all.sh "${BUILD_DIR}/tint" --verbose
+        ${SRC_DIR}/test/tint/test-all.sh "${BUILD_DIR}/tint" --verbose
     hide_cmds
 
     status "Checking _other.cc files also build"
diff --git a/kokoro/windows/build.bat b/kokoro/windows/build.bat
index f2795f6..c6df367 100644
--- a/kokoro/windows/build.bat
+++ b/kokoro/windows/build.bat
@@ -139,7 +139,7 @@
 tint_unittests.exe || goto :error

 @echo off

 

-call :status "Testing test/test-all.sh"

+call :status "Testing test/tint/test-all.sh"

 @echo on

 cd /d %SRC_DIR% || goto :error

 rem Run tests with DXC and Metal validation

@@ -148,13 +148,13 @@
 where metal.exe

 set PATH=%DXC_PATH%;%OLD_PATH%

 where dxc.exe dxil.dll

-call git bash -- ./test/test-all.sh ../tint-build/tint.exe --verbose || goto :error

+call git bash -- ./test/tint/test-all.sh ../tint-build/tint.exe --verbose || goto :error

 @echo on

 set PATH=%OLD_PATH%

 rem Run again to test with FXC validation

 set PATH=%D3DCOMPILER_PATH%;%OLD_PATH%

 where d3dcompiler_47.dll

-call git bash -- ./test/test-all.sh ../tint-build/tint.exe --verbose --format hlsl --fxc || goto :error

+call git bash -- ./test/tint/test-all.sh ../tint-build/tint.exe --verbose --format hlsl --fxc || goto :error

 @echo on

 set PATH=%OLD_PATH%

 @echo off

diff --git a/samples/BUILD.gn b/samples/BUILD.gn
deleted file mode 100644
index 51be8be..0000000
--- a/samples/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("../tint_overrides_with_defaults.gni")
-
-executable("tint") {
-  sources = [ "main.cc" ]
-  deps = [
-    "${tint_root_dir}/src:libtint",
-    "${tint_root_dir}/src:tint_val",
-    "${tint_spirv_tools_dir}/:spvtools",
-    "${tint_spirv_tools_dir}/:spvtools_opt",
-    "${tint_spirv_tools_dir}/:spvtools_val",
-  ]
-
-  if (tint_build_glsl_writer) {
-    deps += [
-      "${tint_root_dir}/third_party/glslang:glslang_default_resource_limits_sources",
-      "${tint_root_dir}/third_party/glslang:glslang_lib_sources",
-    ]
-  }
-
-  configs += [
-    "${tint_root_dir}/src:tint_common_config",
-    "${tint_root_dir}/src:tint_config",
-  ]
-
-  if (build_with_chromium) {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-  }
-}
diff --git a/samples/main.cc b/samples/main.cc
deleted file mode 100644
index 8a8484a..0000000
--- a/samples/main.cc
+++ /dev/null
@@ -1,1216 +0,0 @@
-// Copyright 2020 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 <cstdio>
-#include <fstream>
-#include <iostream>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#if TINT_BUILD_GLSL_WRITER
-#include "StandAlone/ResourceLimits.h"
-#include "glslang/Public/ShaderLang.h"
-#endif
-
-#if TINT_BUILD_SPV_READER
-#include "spirv-tools/libspirv.hpp"
-#endif  // TINT_BUILD_SPV_READER
-
-#include "src/utils/io/command.h"
-#include "src/utils/string.h"
-#include "src/val/val.h"
-#include "tint/tint.h"
-
-namespace {
-
-[[noreturn]] void TintInternalCompilerErrorReporter(
-    const tint::diag::List& diagnostics) {
-  auto printer = tint::diag::Printer::create(stderr, true);
-  tint::diag::Formatter{}.format(diagnostics, printer.get());
-  tint::diag::Style bold_red{tint::diag::Color::kRed, true};
-  constexpr const char* please_file_bug = R"(
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
-)";
-  printer->write(please_file_bug, bold_red);
-  exit(1);
-}
-
-enum class Format {
-  kNone = -1,
-  kSpirv,
-  kSpvAsm,
-  kWgsl,
-  kMsl,
-  kHlsl,
-  kGlsl,
-};
-
-struct Options {
-  bool show_help = false;
-
-  std::string input_filename;
-  std::string output_file = "-";  // Default to stdout
-
-  bool parse_only = false;
-  bool disable_workgroup_init = false;
-  bool validate = false;
-  bool demangle = false;
-  bool dump_inspector_bindings = false;
-
-  Format format = Format::kNone;
-
-  bool emit_single_entry_point = false;
-  std::string ep_name;
-
-  std::vector<std::string> transforms;
-
-  bool use_fxc = false;
-  std::string dxc_path;
-  std::string xcrun_path;
-};
-
-const char kUsage[] = R"(Usage: tint [options] <input-file>
-
- options:
-  --format <spirv|spvasm|wgsl|msl|hlsl>  -- Output format.
-                               If not provided, will be inferred from output
-                               filename extension:
-                                   .spvasm -> spvasm
-                                   .spv    -> spirv
-                                   .wgsl   -> wgsl
-                                   .metal  -> msl
-                                   .hlsl   -> hlsl
-                               If none matches, then default to SPIR-V assembly.
-  -ep <name>                -- Output single entry point
-  --output-file <name>      -- Output file name.  Use "-" for standard output
-  -o <name>                 -- Output file name.  Use "-" for standard output
-  --transform <name list>   -- Runs transforms, name list is comma separated
-                               Available transforms:
-${transforms}
-  --parse-only              -- Stop after parsing the input
-  --disable-workgroup-init  -- Disable workgroup memory zero initialization.
-  --demangle                -- Preserve original source names. Demangle them.
-                               Affects AST dumping, and text-based output languages.
-  --dump-inspector-bindings -- Dump reflection data about bindins to stdout.
-  -h                        -- This help text
-  --validate                -- Validates the generated shader
-  --fxc                     -- Ask to validate HLSL output using FXC instead of DXC.
-                               When specified, automatically enables --validate
-  --dxc                     -- Path to DXC executable, used to validate HLSL output.
-                               When specified, automatically enables --validate
-  --xcrun                   -- Path to xcrun executable, used to validate MSL output.
-                               When specified, automatically enables --validate)";
-
-Format parse_format(const std::string& fmt) {
-  (void)fmt;
-
-#if TINT_BUILD_SPV_WRITER
-  if (fmt == "spirv")
-    return Format::kSpirv;
-  if (fmt == "spvasm")
-    return Format::kSpvAsm;
-#endif  // TINT_BUILD_SPV_WRITER
-
-#if TINT_BUILD_WGSL_WRITER
-  if (fmt == "wgsl")
-    return Format::kWgsl;
-#endif  // TINT_BUILD_WGSL_WRITER
-
-#if TINT_BUILD_MSL_WRITER
-  if (fmt == "msl")
-    return Format::kMsl;
-#endif  // TINT_BUILD_MSL_WRITER
-
-#if TINT_BUILD_HLSL_WRITER
-  if (fmt == "hlsl")
-    return Format::kHlsl;
-#endif  // TINT_BUILD_HLSL_WRITER
-
-#if TINT_BUILD_GLSL_WRITER
-  if (fmt == "glsl")
-    return Format::kGlsl;
-#endif  // TINT_BUILD_GLSL_WRITER
-
-  return Format::kNone;
-}
-
-#if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || \
-    TINT_BUILD_MSL_WRITER || TINT_BUILD_HLSL_WRITER
-/// @param input input string
-/// @param suffix potential suffix string
-/// @returns true if input ends with the given suffix.
-bool ends_with(const std::string& input, const std::string& suffix) {
-  const auto input_len = input.size();
-  const auto suffix_len = suffix.size();
-  // Avoid integer overflow.
-  return (input_len >= suffix_len) &&
-         (input_len - suffix_len == input.rfind(suffix));
-}
-#endif
-
-/// @param filename the filename to inspect
-/// @returns the inferred format for the filename suffix
-Format infer_format(const std::string& filename) {
-  (void)filename;
-
-#if TINT_BUILD_SPV_WRITER
-  if (ends_with(filename, ".spv")) {
-    return Format::kSpirv;
-  }
-  if (ends_with(filename, ".spvasm")) {
-    return Format::kSpvAsm;
-  }
-#endif  // TINT_BUILD_SPV_WRITER
-
-#if TINT_BUILD_WGSL_WRITER
-  if (ends_with(filename, ".wgsl")) {
-    return Format::kWgsl;
-  }
-#endif  // TINT_BUILD_WGSL_WRITER
-
-#if TINT_BUILD_MSL_WRITER
-  if (ends_with(filename, ".metal")) {
-    return Format::kMsl;
-  }
-#endif  // TINT_BUILD_MSL_WRITER
-
-#if TINT_BUILD_HLSL_WRITER
-  if (ends_with(filename, ".hlsl")) {
-    return Format::kHlsl;
-  }
-#endif  // TINT_BUILD_HLSL_WRITER
-
-  return Format::kNone;
-}
-
-std::vector<std::string> split_transform_names(std::string list) {
-  std::vector<std::string> res;
-
-  std::stringstream str(list);
-  while (str.good()) {
-    std::string substr;
-    getline(str, substr, ',');
-    res.push_back(substr);
-  }
-  return res;
-}
-
-std::string TextureDimensionToString(
-    tint::inspector::ResourceBinding::TextureDimension dim) {
-  switch (dim) {
-    case tint::inspector::ResourceBinding::TextureDimension::kNone:
-      return "None";
-    case tint::inspector::ResourceBinding::TextureDimension::k1d:
-      return "1d";
-    case tint::inspector::ResourceBinding::TextureDimension::k2d:
-      return "2d";
-    case tint::inspector::ResourceBinding::TextureDimension::k2dArray:
-      return "2dArray";
-    case tint::inspector::ResourceBinding::TextureDimension::k3d:
-      return "3d";
-    case tint::inspector::ResourceBinding::TextureDimension::kCube:
-      return "Cube";
-    case tint::inspector::ResourceBinding::TextureDimension::kCubeArray:
-      return "CubeArray";
-  }
-
-  return "Unknown";
-}
-
-std::string SampledKindToString(
-    tint::inspector::ResourceBinding::SampledKind kind) {
-  switch (kind) {
-    case tint::inspector::ResourceBinding::SampledKind::kFloat:
-      return "Float";
-    case tint::inspector::ResourceBinding::SampledKind::kUInt:
-      return "UInt";
-    case tint::inspector::ResourceBinding::SampledKind::kSInt:
-      return "SInt";
-    case tint::inspector::ResourceBinding::SampledKind::kUnknown:
-      break;
-  }
-
-  return "Unknown";
-}
-
-std::string TexelFormatToString(
-    tint::inspector::ResourceBinding::TexelFormat format) {
-  switch (format) {
-    case tint::inspector::ResourceBinding::TexelFormat::kR32Uint:
-      return "R32Uint";
-    case tint::inspector::ResourceBinding::TexelFormat::kR32Sint:
-      return "R32Sint";
-    case tint::inspector::ResourceBinding::TexelFormat::kR32Float:
-      return "R32Float";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm:
-      return "Rgba8Unorm";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm:
-      return "Rgba8Snorm";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint:
-      return "Rgba8Uint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint:
-      return "Rgba8Sint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint:
-      return "Rg32Uint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint:
-      return "Rg32Sint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRg32Float:
-      return "Rg32Float";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint:
-      return "Rgba16Uint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint:
-      return "Rgba16Sint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float:
-      return "Rgba16Float";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint:
-      return "Rgba32Uint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint:
-      return "Rgba32Sint";
-    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float:
-      return "Rgba32Float";
-    case tint::inspector::ResourceBinding::TexelFormat::kNone:
-      return "None";
-  }
-  return "Unknown";
-}
-
-std::string ResourceTypeToString(
-    tint::inspector::ResourceBinding::ResourceType type) {
-  switch (type) {
-    case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
-      return "UniformBuffer";
-    case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
-      return "StorageBuffer";
-    case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer:
-      return "ReadOnlyStorageBuffer";
-    case tint::inspector::ResourceBinding::ResourceType::kSampler:
-      return "Sampler";
-    case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
-      return "ComparisonSampler";
-    case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
-      return "SampledTexture";
-    case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture:
-      return "MultisampledTexture";
-    case tint::inspector::ResourceBinding::ResourceType::
-        kWriteOnlyStorageTexture:
-      return "WriteOnlyStorageTexture";
-    case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
-      return "DepthTexture";
-    case tint::inspector::ResourceBinding::ResourceType::
-        kDepthMultisampledTexture:
-      return "DepthMultisampledTexture";
-    case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
-      return "ExternalTexture";
-  }
-
-  return "Unknown";
-}
-
-bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
-  for (size_t i = 1; i < args.size(); ++i) {
-    const std::string& arg = args[i];
-    if (arg == "--format") {
-      ++i;
-      if (i >= args.size()) {
-        std::cerr << "Missing value for --format argument." << std::endl;
-        return false;
-      }
-      opts->format = parse_format(args[i]);
-
-      if (opts->format == Format::kNone) {
-        std::cerr << "Unknown output format: " << args[i] << std::endl;
-        return false;
-      }
-    } else if (arg == "-ep") {
-      if (i + 1 >= args.size()) {
-        std::cerr << "Missing value for -ep" << std::endl;
-        return false;
-      }
-      i++;
-      opts->ep_name = args[i];
-      opts->emit_single_entry_point = true;
-
-    } else if (arg == "-o" || arg == "--output-name") {
-      ++i;
-      if (i >= args.size()) {
-        std::cerr << "Missing value for " << arg << std::endl;
-        return false;
-      }
-      opts->output_file = args[i];
-
-    } else if (arg == "-h" || arg == "--help") {
-      opts->show_help = true;
-    } else if (arg == "--transform") {
-      ++i;
-      if (i >= args.size()) {
-        std::cerr << "Missing value for " << arg << std::endl;
-        return false;
-      }
-      opts->transforms = split_transform_names(args[i]);
-    } else if (arg == "--parse-only") {
-      opts->parse_only = true;
-    } else if (arg == "--disable-workgroup-init") {
-      opts->disable_workgroup_init = true;
-    } else if (arg == "--demangle") {
-      opts->demangle = true;
-    } else if (arg == "--dump-inspector-bindings") {
-      opts->dump_inspector_bindings = true;
-    } else if (arg == "--validate") {
-      opts->validate = true;
-    } else if (arg == "--fxc") {
-      opts->validate = true;
-      opts->use_fxc = true;
-    } else if (arg == "--dxc") {
-      ++i;
-      if (i >= args.size()) {
-        std::cerr << "Missing value for " << arg << std::endl;
-        return false;
-      }
-      opts->dxc_path = args[i];
-      opts->validate = true;
-    } else if (arg == "--xcrun") {
-      ++i;
-      if (i >= args.size()) {
-        std::cerr << "Missing value for " << arg << std::endl;
-        return false;
-      }
-      opts->xcrun_path = args[i];
-      opts->validate = true;
-    } else if (!arg.empty()) {
-      if (arg[0] == '-') {
-        std::cerr << "Unrecognized option: " << arg << std::endl;
-        return false;
-      }
-      if (!opts->input_filename.empty()) {
-        std::cerr << "More than one input file specified: '"
-                  << opts->input_filename << "' and '" << arg << "'"
-                  << std::endl;
-        return false;
-      }
-      opts->input_filename = arg;
-    }
-  }
-  return true;
-}
-
-/// Copies the content from the file named `input_file` to `buffer`,
-/// assuming each element in the file is of type `T`.  If any error occurs,
-/// writes error messages to the standard error stream and returns false.
-/// Assumes the size of a `T` object is divisible by its required alignment.
-/// @returns true if we successfully read the file.
-template <typename T>
-bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
-  if (!buffer) {
-    std::cerr << "The buffer pointer was null" << std::endl;
-    return false;
-  }
-
-  FILE* file = nullptr;
-#if defined(_MSC_VER)
-  fopen_s(&file, input_file.c_str(), "rb");
-#else
-  file = fopen(input_file.c_str(), "rb");
-#endif
-  if (!file) {
-    std::cerr << "Failed to open " << input_file << std::endl;
-    return false;
-  }
-
-  fseek(file, 0, SEEK_END);
-  const auto file_size = static_cast<size_t>(ftell(file));
-  if (0 != (file_size % sizeof(T))) {
-    std::cerr << "File " << input_file
-              << " does not contain an integral number of objects: "
-              << file_size << " bytes in the file, require " << sizeof(T)
-              << " bytes per object" << std::endl;
-    fclose(file);
-    return false;
-  }
-  fseek(file, 0, SEEK_SET);
-
-  buffer->clear();
-  buffer->resize(file_size / sizeof(T));
-
-  size_t bytes_read = fread(buffer->data(), 1, file_size, file);
-  fclose(file);
-  if (bytes_read != file_size) {
-    std::cerr << "Failed to read " << input_file << std::endl;
-    return false;
-  }
-
-  return true;
-}
-
-/// Writes the given `buffer` into the file named as `output_file` using the
-/// given `mode`.  If `output_file` is empty or "-", writes to standard
-/// output. If any error occurs, returns false and outputs error message to
-/// standard error. The ContainerT type must have data() and size() methods,
-/// like `std::string` and `std::vector` do.
-/// @returns true on success
-template <typename ContainerT>
-bool WriteFile(const std::string& output_file,
-               const std::string mode,
-               const ContainerT& buffer) {
-  const bool use_stdout = output_file.empty() || output_file == "-";
-  FILE* file = stdout;
-
-  if (!use_stdout) {
-#if defined(_MSC_VER)
-    fopen_s(&file, output_file.c_str(), mode.c_str());
-#else
-    file = fopen(output_file.c_str(), mode.c_str());
-#endif
-    if (!file) {
-      std::cerr << "Could not open file " << output_file << " for writing"
-                << std::endl;
-      return false;
-    }
-  }
-
-  size_t written =
-      fwrite(buffer.data(), sizeof(typename ContainerT::value_type),
-             buffer.size(), file);
-  if (buffer.size() != written) {
-    if (use_stdout) {
-      std::cerr << "Could not write all output to standard output" << std::endl;
-    } else {
-      std::cerr << "Could not write to file " << output_file << std::endl;
-      fclose(file);
-    }
-    return false;
-  }
-  if (!use_stdout) {
-    fclose(file);
-  }
-
-  return true;
-}
-
-#if TINT_BUILD_SPV_WRITER
-std::string Disassemble(const std::vector<uint32_t>& data) {
-  std::string spv_errors;
-  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
-
-  auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
-                                    const spv_position_t& position,
-                                    const char* message) {
-    switch (level) {
-      case SPV_MSG_FATAL:
-      case SPV_MSG_INTERNAL_ERROR:
-      case SPV_MSG_ERROR:
-        spv_errors += "error: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_WARNING:
-        spv_errors += "warning: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_INFO:
-        spv_errors += "info: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_DEBUG:
-        break;
-    }
-  };
-
-  spvtools::SpirvTools tools(target_env);
-  tools.SetMessageConsumer(msg_consumer);
-
-  std::string result;
-  if (!tools.Disassemble(data, &result,
-                         SPV_BINARY_TO_TEXT_OPTION_INDENT |
-                             SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) {
-    std::cerr << spv_errors << std::endl;
-  }
-  return result;
-}
-#endif  // TINT_BUILD_SPV_WRITER
-
-/// PrintWGSL writes the WGSL of the program to the provided ostream, if the
-/// WGSL writer is enabled, otherwise it does nothing.
-/// @param out the output stream to write the WGSL to
-/// @param program the program
-void PrintWGSL(std::ostream& out, const tint::Program& program) {
-#if TINT_BUILD_WGSL_WRITER
-  tint::writer::wgsl::Options options;
-  auto result = tint::writer::wgsl::Generate(&program, options);
-  out << std::endl << result.wgsl << std::endl;
-#else
-  (void)out;
-  (void)program;
-#endif
-}
-
-/// Generate SPIR-V code for a program.
-/// @param program the program to generate
-/// @param options the options that Tint was invoked with
-/// @returns true on success
-bool GenerateSpirv(const tint::Program* program, const Options& options) {
-#if TINT_BUILD_SPV_WRITER
-  // TODO(jrprice): Provide a way for the user to set non-default options.
-  tint::writer::spirv::Options gen_options;
-  gen_options.disable_workgroup_init = options.disable_workgroup_init;
-  auto result = tint::writer::spirv::Generate(program, gen_options);
-  if (!result.success) {
-    PrintWGSL(std::cerr, *program);
-    std::cerr << "Failed to generate: " << result.error << std::endl;
-    return false;
-  }
-
-  if (options.format == Format::kSpvAsm) {
-    if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) {
-      return false;
-    }
-  } else {
-    if (!WriteFile(options.output_file, "wb", result.spirv)) {
-      return false;
-    }
-  }
-
-  if (options.validate) {
-    // Use Vulkan 1.1, since this is what Tint, internally, uses.
-    spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
-    tools.SetMessageConsumer([](spv_message_level_t, const char*,
-                                const spv_position_t& pos, const char* msg) {
-      std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
-                << std::endl;
-    });
-    if (!tools.Validate(result.spirv.data(), result.spirv.size(),
-                        spvtools::ValidatorOptions())) {
-      return false;
-    }
-  }
-
-  return true;
-#else
-  (void)program;
-  (void)options;
-  std::cerr << "SPIR-V writer not enabled in tint build" << std::endl;
-  return false;
-#endif  // TINT_BUILD_SPV_WRITER
-}
-
-/// Generate WGSL code for a program.
-/// @param program the program to generate
-/// @param options the options that Tint was invoked with
-/// @returns true on success
-bool GenerateWgsl(const tint::Program* program, const Options& options) {
-#if TINT_BUILD_WGSL_WRITER
-  // TODO(jrprice): Provide a way for the user to set non-default options.
-  tint::writer::wgsl::Options gen_options;
-  auto result = tint::writer::wgsl::Generate(program, gen_options);
-  if (!result.success) {
-    std::cerr << "Failed to generate: " << result.error << std::endl;
-    return false;
-  }
-
-  if (!WriteFile(options.output_file, "w", result.wgsl)) {
-    return false;
-  }
-
-  if (options.validate) {
-    // Attempt to re-parse the output program with Tint's WGSL reader.
-    auto source = std::make_unique<tint::Source::File>(options.input_filename,
-                                                       result.wgsl);
-    auto reparsed_program = tint::reader::wgsl::Parse(source.get());
-    if (!reparsed_program.IsValid()) {
-      auto diag_printer = tint::diag::Printer::create(stderr, true);
-      tint::diag::Formatter diag_formatter;
-      diag_formatter.format(reparsed_program.Diagnostics(), diag_printer.get());
-      return false;
-    }
-  }
-
-  return true;
-#else
-  (void)program;
-  (void)options;
-  std::cerr << "WGSL writer not enabled in tint build" << std::endl;
-  return false;
-#endif  // TINT_BUILD_WGSL_WRITER
-}
-
-/// Generate MSL code for a program.
-/// @param program the program to generate
-/// @param options the options that Tint was invoked with
-/// @returns true on success
-bool GenerateMsl(const tint::Program* program, const Options& options) {
-#if TINT_BUILD_MSL_WRITER
-  const tint::Program* input_program = program;
-
-  // Remap resource numbers to a flat namespace.
-  // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points.
-  using BindingPoint = tint::transform::BindingPoint;
-  tint::transform::BindingRemapper::BindingPoints binding_points;
-  uint32_t next_buffer_idx = 0;
-  uint32_t next_sampler_idx = 0;
-  uint32_t next_texture_idx = 0;
-
-  tint::inspector::Inspector inspector(program);
-  auto entry_points = inspector.GetEntryPoints();
-  for (auto& entry_point : entry_points) {
-    auto bindings = inspector.GetResourceBindings(entry_point.name);
-    for (auto& binding : bindings) {
-      BindingPoint src = {binding.bind_group, binding.binding};
-      if (binding_points.count(src)) {
-        continue;
-      }
-      switch (binding.resource_type) {
-        case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
-        case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
-        case tint::inspector::ResourceBinding::ResourceType::
-            kReadOnlyStorageBuffer:
-          binding_points.emplace(src, BindingPoint{0, next_buffer_idx++});
-          break;
-        case tint::inspector::ResourceBinding::ResourceType::kSampler:
-        case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
-          binding_points.emplace(src, BindingPoint{0, next_sampler_idx++});
-          break;
-        case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
-        case tint::inspector::ResourceBinding::ResourceType::
-            kMultisampledTexture:
-        case tint::inspector::ResourceBinding::ResourceType::
-            kWriteOnlyStorageTexture:
-        case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
-        case tint::inspector::ResourceBinding::ResourceType::
-            kDepthMultisampledTexture:
-        case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
-          binding_points.emplace(src, BindingPoint{0, next_texture_idx++});
-          break;
-      }
-    }
-  }
-
-  // Run the binding remapper transform.
-  tint::transform::Output transform_output;
-  if (!binding_points.empty()) {
-    tint::transform::Manager manager;
-    tint::transform::DataMap inputs;
-    inputs.Add<tint::transform::BindingRemapper::Remappings>(
-        std::move(binding_points),
-        tint::transform::BindingRemapper::AccessControls{},
-        /* mayCollide */ true);
-    manager.Add<tint::transform::BindingRemapper>();
-    transform_output = manager.Run(program, inputs);
-    input_program = &transform_output.program;
-  }
-
-  // TODO(jrprice): Provide a way for the user to set non-default options.
-  tint::writer::msl::Options gen_options;
-  gen_options.disable_workgroup_init = options.disable_workgroup_init;
-  auto result = tint::writer::msl::Generate(input_program, gen_options);
-  if (!result.success) {
-    PrintWGSL(std::cerr, *program);
-    std::cerr << "Failed to generate: " << result.error << std::endl;
-    return false;
-  }
-
-  if (!WriteFile(options.output_file, "w", result.msl)) {
-    return false;
-  }
-
-  if (options.validate) {
-    tint::val::Result res;
-#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
-    res = tint::val::MslUsingMetalAPI(result.msl);
-#else
-#ifdef _WIN32
-    const char* default_xcrun_exe = "metal.exe";
-#else
-    const char* default_xcrun_exe = "xcrun";
-#endif
-    auto xcrun = tint::utils::Command::LookPath(
-        options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path);
-    if (xcrun.Found()) {
-      res = tint::val::Msl(xcrun.Path(), result.msl);
-    } else {
-      res.output = "xcrun executable not found. Cannot validate.";
-      res.failed = true;
-    }
-#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
-    if (res.failed) {
-      std::cerr << res.output << std::endl;
-      return false;
-    }
-  }
-
-  return true;
-#else
-  (void)program;
-  (void)options;
-  std::cerr << "MSL writer not enabled in tint build" << std::endl;
-  return false;
-#endif  // TINT_BUILD_MSL_WRITER
-}
-
-/// Generate HLSL code for a program.
-/// @param program the program to generate
-/// @param options the options that Tint was invoked with
-/// @returns true on success
-bool GenerateHlsl(const tint::Program* program, const Options& options) {
-#if TINT_BUILD_HLSL_WRITER
-  // TODO(jrprice): Provide a way for the user to set non-default options.
-  tint::writer::hlsl::Options gen_options;
-  gen_options.disable_workgroup_init = options.disable_workgroup_init;
-  auto result = tint::writer::hlsl::Generate(program, gen_options);
-  if (!result.success) {
-    PrintWGSL(std::cerr, *program);
-    std::cerr << "Failed to generate: " << result.error << std::endl;
-    return false;
-  }
-
-  if (!WriteFile(options.output_file, "w", result.hlsl)) {
-    return false;
-  }
-
-  if (options.validate) {
-    tint::val::Result res;
-    if (options.use_fxc) {
-#ifdef _WIN32
-      res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points);
-#else
-      res.failed = true;
-      res.output = "FXC can only be used on Windows. Sorry :X";
-#endif  // _WIN32
-    } else {
-      auto dxc = tint::utils::Command::LookPath(
-          options.dxc_path.empty() ? "dxc" : options.dxc_path);
-      if (dxc.Found()) {
-        res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl,
-                                      result.entry_points);
-      } else {
-        res.failed = true;
-        res.output = "DXC executable not found. Cannot validate";
-      }
-    }
-    if (res.failed) {
-      std::cerr << res.output << std::endl;
-      return false;
-    }
-  }
-
-  return true;
-#else
-  (void)program;
-  (void)options;
-  std::cerr << "HLSL writer not enabled in tint build" << std::endl;
-  return false;
-#endif  // TINT_BUILD_HLSL_WRITER
-}
-
-#if TINT_BUILD_GLSL_WRITER
-EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) {
-  switch (stage) {
-    case tint::ast::PipelineStage::kFragment:
-      return EShLangFragment;
-    case tint::ast::PipelineStage::kVertex:
-      return EShLangVertex;
-    case tint::ast::PipelineStage::kCompute:
-      return EShLangCompute;
-    default:
-      TINT_ASSERT(AST, false);
-      return EShLangVertex;
-  }
-}
-#endif
-
-/// Generate GLSL code for a program.
-/// @param program the program to generate
-/// @param options the options that Tint was invoked with
-/// @returns true on success
-bool GenerateGlsl(const tint::Program* program, const Options& options) {
-#if TINT_BUILD_GLSL_WRITER
-  if (options.validate) {
-    glslang::InitializeProcess();
-  }
-
-  auto generate = [&](const tint::Program* prg,
-                      const std::string entry_point_name) -> bool {
-    tint::writer::glsl::Options gen_options;
-    auto result =
-        tint::writer::glsl::Generate(prg, gen_options, entry_point_name);
-    if (!result.success) {
-      PrintWGSL(std::cerr, *prg);
-      std::cerr << "Failed to generate: " << result.error << std::endl;
-      return false;
-    }
-
-    if (!WriteFile(options.output_file, "w", result.glsl)) {
-      return false;
-    }
-
-    if (options.validate) {
-      for (auto entry_pt : result.entry_points) {
-        EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second);
-        glslang::TShader shader(lang);
-        const char* strings[1] = {result.glsl.c_str()};
-        int lengths[1] = {static_cast<int>(result.glsl.length())};
-        shader.setStringsWithLengths(strings, lengths, 1);
-        shader.setEntryPoint("main");
-        bool glslang_result =
-            shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile,
-                         false, false, EShMsgDefault);
-        if (!glslang_result) {
-          std::cerr << "Error parsing GLSL shader:\n"
-                    << shader.getInfoLog() << "\n"
-                    << shader.getInfoDebugLog() << "\n";
-          return false;
-        }
-      }
-    }
-    return true;
-  };
-
-  tint::inspector::Inspector inspector(program);
-
-  if (inspector.GetEntryPoints().empty()) {
-    // Pass empty string here so that the GLSL generator will generate
-    // code for all functions, reachable or not.
-    return generate(program, "");
-  }
-
-  bool success = true;
-  for (auto& entry_point : inspector.GetEntryPoints()) {
-    success &= generate(program, entry_point.name);
-  }
-  return success;
-#else
-  (void)program;
-  (void)options;
-  std::cerr << "GLSL writer not enabled in tint build" << std::endl;
-  return false;
-#endif  // TINT_BUILD_GLSL_WRITER
-}
-
-}  // namespace
-
-int main(int argc, const char** argv) {
-  std::vector<std::string> args(argv, argv + argc);
-  Options options;
-
-  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
-
-#if TINT_BUILD_WGSL_WRITER
-  tint::Program::printer = [](const tint::Program* program) {
-    auto result = tint::writer::wgsl::Generate(program, {});
-    if (!result.error.empty()) {
-      return "error: " + result.error;
-    }
-    return result.wgsl;
-  };
-#endif  //  TINT_BUILD_WGSL_WRITER
-
-  if (!ParseArgs(args, &options)) {
-    std::cerr << "Failed to parse arguments." << std::endl;
-    return 1;
-  }
-
-  struct TransformFactory {
-    const char* name;
-    std::function<void(tint::transform::Manager& manager,
-                       tint::transform::DataMap& inputs)>
-        make;
-  };
-  std::vector<TransformFactory> transforms = {
-      {"first_index_offset",
-       [](tint::transform::Manager& m, tint::transform::DataMap& i) {
-         i.Add<tint::transform::FirstIndexOffset::BindingPoint>(0, 0);
-         m.Add<tint::transform::FirstIndexOffset>();
-       }},
-      {"fold_trivial_single_use_lets",
-       [](tint::transform::Manager& m, tint::transform::DataMap&) {
-         m.Add<tint::transform::FoldTrivialSingleUseLets>();
-       }},
-      {"renamer",
-       [](tint::transform::Manager& m, tint::transform::DataMap&) {
-         m.Add<tint::transform::Renamer>();
-       }},
-      {"robustness",
-       [](tint::transform::Manager& m, tint::transform::DataMap&) {
-         m.Add<tint::transform::Robustness>();
-       }},
-  };
-  auto transform_names = [&] {
-    std::stringstream names;
-    for (auto& t : transforms) {
-      names << "   " << t.name << std::endl;
-    }
-    return names.str();
-  };
-
-  if (options.show_help) {
-    std::string usage =
-        tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names());
-    std::cout << usage << std::endl;
-    return 0;
-  }
-
-  // Implement output format defaults.
-  if (options.format == Format::kNone) {
-    // Try inferring from filename.
-    options.format = infer_format(options.output_file);
-  }
-  if (options.format == Format::kNone) {
-    // Ultimately, default to SPIR-V assembly. That's nice for interactive use.
-    options.format = Format::kSpvAsm;
-  }
-
-  auto diag_printer = tint::diag::Printer::create(stderr, true);
-  tint::diag::Formatter diag_formatter;
-
-  std::unique_ptr<tint::Program> program;
-  std::unique_ptr<tint::Source::File> source_file;
-
-  enum class InputFormat {
-    kUnknown,
-    kWgsl,
-    kSpirvBin,
-    kSpirvAsm,
-  };
-  auto input_format = InputFormat::kUnknown;
-
-  if (options.input_filename.size() > 5 &&
-      options.input_filename.substr(options.input_filename.size() - 5) ==
-          ".wgsl") {
-    input_format = InputFormat::kWgsl;
-  } else if (options.input_filename.size() > 4 &&
-             options.input_filename.substr(options.input_filename.size() - 4) ==
-                 ".spv") {
-    input_format = InputFormat::kSpirvBin;
-  } else if (options.input_filename.size() > 7 &&
-             options.input_filename.substr(options.input_filename.size() - 7) ==
-                 ".spvasm") {
-    input_format = InputFormat::kSpirvAsm;
-  }
-
-  switch (input_format) {
-    case InputFormat::kUnknown: {
-      std::cerr << "Unknown input format" << std::endl;
-      return 1;
-    }
-    case InputFormat::kWgsl: {
-#if TINT_BUILD_WGSL_READER
-      std::vector<uint8_t> data;
-      if (!ReadFile<uint8_t>(options.input_filename, &data)) {
-        return 1;
-      }
-      source_file = std::make_unique<tint::Source::File>(
-          options.input_filename, std::string(data.begin(), data.end()));
-      program = std::make_unique<tint::Program>(
-          tint::reader::wgsl::Parse(source_file.get()));
-      break;
-#else
-      std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
-      return 1;
-#endif  // TINT_BUILD_WGSL_READER
-    }
-    case InputFormat::kSpirvBin: {
-#if TINT_BUILD_SPV_READER
-      std::vector<uint32_t> data;
-      if (!ReadFile<uint32_t>(options.input_filename, &data)) {
-        return 1;
-      }
-      program =
-          std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
-      break;
-#else
-      std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
-      return 1;
-#endif  // TINT_BUILD_SPV_READER
-    }
-    case InputFormat::kSpirvAsm: {
-#if TINT_BUILD_SPV_READER
-      std::vector<char> text;
-      if (!ReadFile<char>(options.input_filename, &text)) {
-        return 1;
-      }
-      // Use Vulkan 1.1, since this is what Tint, internally, is expecting.
-      spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
-      tools.SetMessageConsumer([](spv_message_level_t, const char*,
-                                  const spv_position_t& pos, const char* msg) {
-        std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
-                  << std::endl;
-      });
-      std::vector<uint32_t> data;
-      if (!tools.Assemble(text.data(), text.size(), &data,
-                          SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
-        return 1;
-      }
-      program =
-          std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
-      break;
-#else
-      std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
-      return 1;
-#endif  // TINT_BUILD_SPV_READER
-    }
-  }
-
-  if (!program) {
-    std::cerr << "Failed to parse input file: " << options.input_filename
-              << std::endl;
-    return 1;
-  }
-  if (program->Diagnostics().count() > 0) {
-    if (!program->IsValid() && input_format != InputFormat::kWgsl) {
-      // Invalid program from a non-wgsl source. Print the WGSL, to help
-      // understand the diagnostics.
-      PrintWGSL(std::cout, *program);
-    }
-    diag_formatter.format(program->Diagnostics(), diag_printer.get());
-  }
-
-  if (!program->IsValid()) {
-    return 1;
-  }
-  if (options.parse_only) {
-    return 1;
-  }
-
-  tint::transform::Manager transform_manager;
-  tint::transform::DataMap transform_inputs;
-  for (const auto& name : options.transforms) {
-    // TODO(dsinclair): The vertex pulling transform requires setup code to
-    // be run that needs user input. Should we find a way to support that here
-    // maybe through a provided file?
-
-    bool found = false;
-    for (auto& t : transforms) {
-      if (t.name == name) {
-        t.make(transform_manager, transform_inputs);
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      std::cerr << "Unknown transform: " << name << std::endl;
-      std::cerr << "Available transforms: " << std::endl << transform_names();
-      return 1;
-    }
-  }
-
-  if (options.emit_single_entry_point) {
-    transform_manager.append(
-        std::make_unique<tint::transform::SingleEntryPoint>());
-    transform_inputs.Add<tint::transform::SingleEntryPoint::Config>(
-        options.ep_name);
-  }
-
-  switch (options.format) {
-    case Format::kMsl: {
-#if TINT_BUILD_MSL_WRITER
-      transform_inputs.Add<tint::transform::Renamer::Config>(
-          tint::transform::Renamer::Target::kMslKeywords,
-          /* preserve_unicode */ false);
-      transform_manager.Add<tint::transform::Renamer>();
-#endif  // TINT_BUILD_MSL_WRITER
-      break;
-    }
-#if TINT_BUILD_GLSL_WRITER
-    case Format::kGlsl: {
-      break;
-    }
-#endif  // TINT_BUILD_GLSL_WRITER
-    case Format::kHlsl: {
-#if TINT_BUILD_HLSL_WRITER
-      transform_inputs.Add<tint::transform::Renamer::Config>(
-          tint::transform::Renamer::Target::kHlslKeywords,
-          /* preserve_unicode */ false);
-      transform_manager.Add<tint::transform::Renamer>();
-#endif  // TINT_BUILD_HLSL_WRITER
-      break;
-    }
-    default:
-      break;
-  }
-
-  auto out = transform_manager.Run(program.get(), std::move(transform_inputs));
-  if (!out.program.IsValid()) {
-    PrintWGSL(std::cerr, out.program);
-    diag_formatter.format(out.program.Diagnostics(), diag_printer.get());
-    return 1;
-  }
-
-  *program = std::move(out.program);
-
-  if (options.dump_inspector_bindings) {
-    std::cout << std::string(80, '-') << std::endl;
-    tint::inspector::Inspector inspector(program.get());
-    auto entry_points = inspector.GetEntryPoints();
-    if (!inspector.error().empty()) {
-      std::cerr << "Failed to get entry points from Inspector: "
-                << inspector.error() << std::endl;
-      return 1;
-    }
-
-    for (auto& entry_point : entry_points) {
-      auto bindings = inspector.GetResourceBindings(entry_point.name);
-      if (!inspector.error().empty()) {
-        std::cerr << "Failed to get bindings from Inspector: "
-                  << inspector.error() << std::endl;
-        return 1;
-      }
-      std::cout << "Entry Point = " << entry_point.name << std::endl;
-      for (auto& binding : bindings) {
-        std::cout << "\t[" << binding.bind_group << "][" << binding.binding
-                  << "]:" << std::endl;
-        std::cout << "\t\t resource_type = "
-                  << ResourceTypeToString(binding.resource_type) << std::endl;
-        std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim)
-                  << std::endl;
-        std::cout << "\t\t sampled_kind = "
-                  << SampledKindToString(binding.sampled_kind) << std::endl;
-        std::cout << "\t\t image_format = "
-                  << TexelFormatToString(binding.image_format) << std::endl;
-      }
-    }
-    std::cout << std::string(80, '-') << std::endl;
-  }
-
-  bool success = false;
-  switch (options.format) {
-    case Format::kSpirv:
-    case Format::kSpvAsm:
-      success = GenerateSpirv(program.get(), options);
-      break;
-    case Format::kWgsl:
-      success = GenerateWgsl(program.get(), options);
-      break;
-    case Format::kMsl:
-      success = GenerateMsl(program.get(), options);
-      break;
-    case Format::kHlsl:
-      success = GenerateHlsl(program.get(), options);
-      break;
-    case Format::kGlsl:
-      success = GenerateGlsl(program.get(), options);
-      break;
-    default:
-      std::cerr << "Unknown output format specified" << std::endl;
-      return 1;
-  }
-  if (!success) {
-    return 1;
-  }
-
-  return 0;
-}
diff --git a/src/BUILD.gn b/src/BUILD.gn
deleted file mode 100644
index a6f20c4..0000000
--- a/src/BUILD.gn
+++ /dev/null
@@ -1,788 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("../tint_overrides_with_defaults.gni")
-
-###############################################################################
-# Common - Configs, etc. shared across targets
-###############################################################################
-
-config("tint_common_config") {
-  include_dirs = [
-    "${target_gen_dir}",
-    "${tint_root_dir}/",
-    "${tint_spirv_headers_dir}/include",
-    "${tint_spirv_tools_dir}/",
-    "${tint_spirv_tools_dir}/include",
-  ]
-}
-
-config("tint_public_config") {
-  defines = []
-  if (tint_build_spv_reader) {
-    defines += [ "TINT_BUILD_SPV_READER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_SPV_READER=0" ]
-  }
-
-  if (tint_build_spv_writer) {
-    defines += [ "TINT_BUILD_SPV_WRITER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_SPV_WRITER=0" ]
-  }
-
-  if (tint_build_wgsl_reader) {
-    defines += [ "TINT_BUILD_WGSL_READER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_WGSL_READER=0" ]
-  }
-
-  if (tint_build_wgsl_writer) {
-    defines += [ "TINT_BUILD_WGSL_WRITER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_WGSL_WRITER=0" ]
-  }
-
-  if (tint_build_msl_writer) {
-    defines += [ "TINT_BUILD_MSL_WRITER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_MSL_WRITER=0" ]
-  }
-
-  if (tint_build_hlsl_writer) {
-    defines += [ "TINT_BUILD_HLSL_WRITER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_HLSL_WRITER=0" ]
-  }
-
-  if (tint_build_glsl_writer) {
-    defines += [ "TINT_BUILD_GLSL_WRITER=1" ]
-  } else {
-    defines += [ "TINT_BUILD_GLSL_WRITER=0" ]
-  }
-
-  include_dirs = [
-    "${tint_root_dir}/",
-    "${tint_root_dir}/include/",
-    "${tint_spirv_headers_dir}/include",
-  ]
-}
-
-config("tint_config") {
-  include_dirs = []
-  if (tint_build_spv_reader || tint_build_spv_writer) {
-    include_dirs += [ "${tint_spirv_tools_dir}/include/" ]
-  }
-}
-
-###############################################################################
-# Helper library for IO operations
-# Only to be used by tests and sample executable
-###############################################################################
-source_set("tint_utils_io") {
-  sources = [
-    "utils/io/command.h",
-    "utils/io/tmpfile.h",
-  ]
-
-  if (is_linux || is_mac) {
-    sources += [ "utils/io/command_posix.cc" ]
-    sources += [ "utils/io/tmpfile_posix.cc" ]
-  } else if (is_win) {
-    sources += [ "utils/io/command_windows.cc" ]
-    sources += [ "utils/io/tmpfile_windows.cc" ]
-  } else {
-    sources += [ "utils/io/command_other.cc" ]
-    sources += [ "utils/io/tmpfile_other.cc" ]
-  }
-
-  public_deps = [ ":libtint_core_all_src" ]
-}
-
-###############################################################################
-# Helper library for validating generated shaders
-# As this depends on tint_utils_io, this is only to be used by tests and sample
-# executable
-###############################################################################
-source_set("tint_val") {
-  sources = [
-    "val/hlsl.cc",
-    "val/msl.cc",
-    "val/val.h",
-  ]
-  public_deps = [ ":tint_utils_io" ]
-}
-
-###############################################################################
-# Library - Tint core and optional modules of libtint
-###############################################################################
-# libtint source sets are divided into a non-optional core in :libtint_core_src
-# and optional :libtint_*_src subsets, because ninja does not like having
-# multiple source files with the same name, like function.cc, in the same
-# source set
-# target.
-#
-# Targets that want to use tint as a library should depend on ":libtint" and
-# use the build flags to control what is included, instead of trying to specify
-# the subsets that they want.
-
-template("libtint_source_set") {
-  source_set(target_name) {
-    forward_variables_from(invoker, "*", [ "configs" ])
-
-    if (!defined(invoker.deps)) {
-      deps = []
-    }
-    deps += [
-      "${tint_spirv_headers_dir}:spv_headers",
-      "${tint_spirv_tools_dir}:spvtools_core_enums_unified1",
-      "${tint_spirv_tools_dir}:spvtools_core_tables_unified1",
-      "${tint_spirv_tools_dir}:spvtools_headers",
-      "${tint_spirv_tools_dir}:spvtools_language_header_cldebuginfo100",
-      "${tint_spirv_tools_dir}:spvtools_language_header_debuginfo",
-      "${tint_spirv_tools_dir}:spvtools_language_header_vkdebuginfo100",
-    ]
-
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-    configs += [ ":tint_common_config" ]
-    if (build_with_chromium) {
-      configs -= [ "//build/config/compiler:chromium_code" ]
-      configs += [ "//build/config/compiler:no_chromium_code" ]
-    }
-
-    if (!defined(invoker.public_configs)) {
-      public_configs = []
-    }
-    public_configs += [ ":tint_public_config" ]
-  }
-}
-
-libtint_source_set("libtint_core_all_src") {
-  sources = [
-    "ast/access.cc",
-    "ast/access.h",
-    "ast/alias.cc",
-    "ast/alias.h",
-    "ast/array.cc",
-    "ast/array.h",
-    "ast/assignment_statement.cc",
-    "ast/assignment_statement.h",
-    "ast/ast_type.cc",  # TODO(bclayton) - rename to type.cc
-    "ast/atomic.cc",
-    "ast/atomic.h",
-    "ast/attribute.cc",
-    "ast/attribute.h",
-    "ast/binary_expression.cc",
-    "ast/binary_expression.h",
-    "ast/binding_attribute.cc",
-    "ast/binding_attribute.h",
-    "ast/bitcast_expression.cc",
-    "ast/bitcast_expression.h",
-    "ast/block_statement.cc",
-    "ast/block_statement.h",
-    "ast/bool.cc",
-    "ast/bool.h",
-    "ast/bool_literal_expression.cc",
-    "ast/bool_literal_expression.h",
-    "ast/break_statement.cc",
-    "ast/break_statement.h",
-    "ast/builtin.cc",
-    "ast/builtin.h",
-    "ast/builtin_attribute.cc",
-    "ast/builtin_attribute.h",
-    "ast/call_expression.cc",
-    "ast/call_expression.h",
-    "ast/call_statement.cc",
-    "ast/call_statement.h",
-    "ast/case_statement.cc",
-    "ast/case_statement.h",
-    "ast/continue_statement.cc",
-    "ast/continue_statement.h",
-    "ast/depth_multisampled_texture.cc",
-    "ast/depth_multisampled_texture.h",
-    "ast/depth_texture.cc",
-    "ast/depth_texture.h",
-    "ast/disable_validation_attribute.cc",
-    "ast/disable_validation_attribute.h",
-    "ast/discard_statement.cc",
-    "ast/discard_statement.h",
-    "ast/else_statement.cc",
-    "ast/else_statement.h",
-    "ast/expression.cc",
-    "ast/expression.h",
-    "ast/external_texture.cc",
-    "ast/external_texture.h",
-    "ast/f32.cc",
-    "ast/f32.h",
-    "ast/fallthrough_statement.cc",
-    "ast/fallthrough_statement.h",
-    "ast/float_literal_expression.cc",
-    "ast/float_literal_expression.h",
-    "ast/for_loop_statement.cc",
-    "ast/for_loop_statement.h",
-    "ast/function.cc",
-    "ast/function.h",
-    "ast/group_attribute.cc",
-    "ast/group_attribute.h",
-    "ast/i32.cc",
-    "ast/i32.h",
-    "ast/id_attribute.cc",
-    "ast/id_attribute.h",
-    "ast/identifier_expression.cc",
-    "ast/identifier_expression.h",
-    "ast/if_statement.cc",
-    "ast/if_statement.h",
-    "ast/index_accessor_expression.cc",
-    "ast/index_accessor_expression.h",
-    "ast/int_literal_expression.cc",
-    "ast/int_literal_expression.h",
-    "ast/internal_attribute.cc",
-    "ast/internal_attribute.h",
-    "ast/interpolate_attribute.cc",
-    "ast/interpolate_attribute.h",
-    "ast/invariant_attribute.cc",
-    "ast/invariant_attribute.h",
-    "ast/literal_expression.cc",
-    "ast/literal_expression.h",
-    "ast/location_attribute.cc",
-    "ast/location_attribute.h",
-    "ast/loop_statement.cc",
-    "ast/loop_statement.h",
-    "ast/matrix.cc",
-    "ast/matrix.h",
-    "ast/member_accessor_expression.cc",
-    "ast/member_accessor_expression.h",
-    "ast/module.cc",
-    "ast/module.h",
-    "ast/multisampled_texture.cc",
-    "ast/multisampled_texture.h",
-    "ast/node.cc",
-    "ast/node.h",
-    "ast/phony_expression.cc",
-    "ast/phony_expression.h",
-    "ast/pipeline_stage.cc",
-    "ast/pipeline_stage.h",
-    "ast/pointer.cc",
-    "ast/pointer.h",
-    "ast/return_statement.cc",
-    "ast/return_statement.h",
-    "ast/sampled_texture.cc",
-    "ast/sampled_texture.h",
-    "ast/sampler.cc",
-    "ast/sampler.h",
-    "ast/sint_literal_expression.cc",
-    "ast/sint_literal_expression.h",
-    "ast/stage_attribute.cc",
-    "ast/stage_attribute.h",
-    "ast/statement.cc",
-    "ast/statement.h",
-    "ast/storage_class.cc",
-    "ast/storage_class.h",
-    "ast/storage_texture.cc",
-    "ast/storage_texture.h",
-    "ast/stride_attribute.cc",
-    "ast/stride_attribute.h",
-    "ast/struct.cc",
-    "ast/struct.h",
-    "ast/struct_block_attribute.cc",
-    "ast/struct_block_attribute.h",
-    "ast/struct_member.cc",
-    "ast/struct_member.h",
-    "ast/struct_member_align_attribute.cc",
-    "ast/struct_member_align_attribute.h",
-    "ast/struct_member_offset_attribute.cc",
-    "ast/struct_member_offset_attribute.h",
-    "ast/struct_member_size_attribute.cc",
-    "ast/struct_member_size_attribute.h",
-    "ast/switch_statement.cc",
-    "ast/switch_statement.h",
-    "ast/texture.cc",
-    "ast/texture.h",
-    "ast/traverse_expressions.h",
-    "ast/type.h",
-    "ast/type_decl.cc",
-    "ast/type_decl.h",
-    "ast/type_name.cc",
-    "ast/type_name.h",
-    "ast/u32.cc",
-    "ast/u32.h",
-    "ast/uint_literal_expression.cc",
-    "ast/uint_literal_expression.h",
-    "ast/unary_op.cc",
-    "ast/unary_op.h",
-    "ast/unary_op_expression.cc",
-    "ast/unary_op_expression.h",
-    "ast/variable.cc",
-    "ast/variable.h",
-    "ast/variable_decl_statement.cc",
-    "ast/variable_decl_statement.h",
-    "ast/vector.cc",
-    "ast/vector.h",
-    "ast/void.cc",
-    "ast/void.h",
-    "ast/workgroup_attribute.cc",
-    "ast/workgroup_attribute.h",
-    "block_allocator.h",
-    "builtin_table.cc",
-    "builtin_table.h",
-    "builtin_table.inl",
-    "castable.cc",
-    "castable.h",
-    "clone_context.cc",
-    "clone_context.h",
-    "debug.cc",
-    "debug.h",
-    "demangler.cc",
-    "demangler.h",
-    "diagnostic/diagnostic.cc",
-    "diagnostic/diagnostic.h",
-    "diagnostic/formatter.cc",
-    "diagnostic/formatter.h",
-    "diagnostic/printer.cc",
-    "diagnostic/printer.h",
-    "inspector/entry_point.cc",
-    "inspector/entry_point.h",
-    "inspector/inspector.cc",
-    "inspector/inspector.h",
-    "inspector/resource_binding.cc",
-    "inspector/resource_binding.h",
-    "inspector/scalar.cc",
-    "inspector/scalar.h",
-    "program.cc",
-    "program.h",
-    "program_builder.cc",
-    "program_builder.h",
-    "program_id.cc",
-    "program_id.h",
-    "reader/reader.cc",
-    "reader/reader.h",
-    "resolver/dependency_graph.cc",
-    "resolver/dependency_graph.h",
-    "resolver/resolver.cc",
-    "resolver/resolver.h",
-    "resolver/resolver_constants.cc",
-    "resolver/resolver_validation.cc",
-    "scope_stack.h",
-    "sem/array.h",
-    "sem/atomic_type.h",
-    "sem/behavior.h",
-    "sem/binding_point.h",
-    "sem/bool_type.h",
-    "sem/builtin.h",
-    "sem/builtin_type.h",
-    "sem/call.h",
-    "sem/call_target.h",
-    "sem/constant.h",
-    "sem/depth_multisampled_texture_type.h",
-    "sem/depth_texture_type.h",
-    "sem/expression.h",
-    "sem/external_texture_type.h",
-    "sem/f32_type.h",
-    "sem/for_loop_statement.h",
-    "sem/i32_type.h",
-    "sem/if_statement.h",
-    "sem/info.h",
-    "sem/loop_statement.h",
-    "sem/matrix_type.h",
-    "sem/module.h",
-    "sem/multisampled_texture_type.h",
-    "sem/node.h",
-    "sem/parameter_usage.h",
-    "sem/pipeline_stage_set.h",
-    "sem/pointer_type.h",
-    "sem/reference_type.h",
-    "sem/sampled_texture_type.h",
-    "sem/sampler_texture_pair.h",
-    "sem/sampler_type.h",
-    "sem/storage_texture_type.h",
-    "sem/switch_statement.h",
-    "sem/texture_type.h",
-    "sem/type.h",
-    "sem/type_constructor.h",
-    "sem/type_conversion.h",
-    "sem/type_manager.h",
-    "sem/type_mappings.h",
-    "sem/u32_type.h",
-    "sem/vector_type.h",
-    "sem/void_type.h",
-    "source.cc",
-    "source.h",
-    "symbol.cc",
-    "symbol.h",
-    "symbol_table.cc",
-    "symbol_table.h",
-    "text/unicode.cc",
-    "text/unicode.h",
-    "traits.h",
-    "transform/add_empty_entry_point.cc",
-    "transform/add_empty_entry_point.h",
-    "transform/add_spirv_block_attribute.cc",
-    "transform/add_spirv_block_attribute.h",
-    "transform/array_length_from_uniform.cc",
-    "transform/array_length_from_uniform.h",
-    "transform/binding_remapper.cc",
-    "transform/binding_remapper.h",
-    "transform/calculate_array_length.cc",
-    "transform/calculate_array_length.h",
-    "transform/canonicalize_entry_point_io.cc",
-    "transform/canonicalize_entry_point_io.h",
-    "transform/combine_samplers.cc",
-    "transform/combine_samplers.h",
-    "transform/decompose_memory_access.cc",
-    "transform/decompose_memory_access.h",
-    "transform/decompose_strided_array.cc",
-    "transform/decompose_strided_array.h",
-    "transform/decompose_strided_matrix.cc",
-    "transform/decompose_strided_matrix.h",
-    "transform/external_texture_transform.cc",
-    "transform/external_texture_transform.h",
-    "transform/first_index_offset.cc",
-    "transform/first_index_offset.h",
-    "transform/fold_constants.cc",
-    "transform/fold_constants.h",
-    "transform/fold_trivial_single_use_lets.cc",
-    "transform/fold_trivial_single_use_lets.h",
-    "transform/for_loop_to_loop.cc",
-    "transform/for_loop_to_loop.h",
-    "transform/localize_struct_array_assignment.cc",
-    "transform/localize_struct_array_assignment.h",
-    "transform/loop_to_for_loop.cc",
-    "transform/loop_to_for_loop.h",
-    "transform/manager.cc",
-    "transform/manager.h",
-    "transform/module_scope_var_to_entry_point_param.cc",
-    "transform/module_scope_var_to_entry_point_param.h",
-    "transform/multiplanar_external_texture.cc",
-    "transform/multiplanar_external_texture.h",
-    "transform/num_workgroups_from_uniform.cc",
-    "transform/num_workgroups_from_uniform.h",
-    "transform/pad_array_elements.cc",
-    "transform/pad_array_elements.h",
-    "transform/promote_initializers_to_const_var.cc",
-    "transform/promote_initializers_to_const_var.h",
-    "transform/remove_phonies.cc",
-    "transform/remove_phonies.h",
-    "transform/remove_unreachable_statements.cc",
-    "transform/remove_unreachable_statements.h",
-    "transform/renamer.cc",
-    "transform/renamer.h",
-    "transform/robustness.cc",
-    "transform/robustness.h",
-    "transform/simplify_pointers.cc",
-    "transform/simplify_pointers.h",
-    "transform/single_entry_point.cc",
-    "transform/single_entry_point.h",
-    "transform/transform.cc",
-    "transform/transform.h",
-    "transform/unshadow.cc",
-    "transform/unshadow.h",
-    "transform/utils/hoist_to_decl_before.cc",
-    "transform/utils/hoist_to_decl_before.h",
-    "transform/var_for_dynamic_index.cc",
-    "transform/var_for_dynamic_index.h",
-    "transform/vectorize_scalar_matrix_constructors.cc",
-    "transform/vectorize_scalar_matrix_constructors.h",
-    "transform/vertex_pulling.cc",
-    "transform/vertex_pulling.h",
-    "transform/wrap_arrays_in_structs.cc",
-    "transform/wrap_arrays_in_structs.h",
-    "transform/zero_init_workgroup_memory.cc",
-    "transform/zero_init_workgroup_memory.h",
-    "utils/crc32.h",
-    "utils/debugger.cc",
-    "utils/debugger.h",
-    "utils/enum_set.h",
-    "utils/hash.h",
-    "utils/map.h",
-    "utils/math.h",
-    "utils/scoped_assignment.h",
-    "utils/string.h",
-    "utils/unique_vector.h",
-    "writer/append_vector.cc",
-    "writer/append_vector.h",
-    "writer/array_length_from_uniform_options.cc",
-    "writer/array_length_from_uniform_options.h",
-    "writer/float_to_string.cc",
-    "writer/float_to_string.h",
-    "writer/text.cc",
-    "writer/text.h",
-    "writer/text_generator.cc",
-    "writer/text_generator.h",
-    "writer/writer.cc",
-    "writer/writer.h",
-  ]
-
-  if (is_linux) {
-    sources += [ "diagnostic/printer_linux.cc" ]
-  } else if (is_win) {
-    sources += [ "diagnostic/printer_windows.cc" ]
-  } else {
-    sources += [ "diagnostic/printer_other.cc" ]
-  }
-}
-
-libtint_source_set("libtint_sem_src") {
-  sources = [
-    "sem/array.cc",
-    "sem/array.h",
-    "sem/atomic_type.cc",
-    "sem/atomic_type.h",
-    "sem/behavior.cc",
-    "sem/behavior.h",
-    "sem/binding_point.h",
-    "sem/block_statement.cc",
-    "sem/bool_type.cc",
-    "sem/bool_type.h",
-    "sem/builtin.cc",
-    "sem/builtin.h",
-    "sem/builtin_type.cc",
-    "sem/builtin_type.h",
-    "sem/call.cc",
-    "sem/call.h",
-    "sem/call_target.cc",
-    "sem/call_target.h",
-    "sem/constant.cc",
-    "sem/constant.h",
-    "sem/depth_multisampled_texture_type.cc",
-    "sem/depth_multisampled_texture_type.h",
-    "sem/depth_texture_type.cc",
-    "sem/depth_texture_type.h",
-    "sem/expression.cc",
-    "sem/expression.h",
-    "sem/external_texture_type.cc",
-    "sem/external_texture_type.h",
-    "sem/f32_type.cc",
-    "sem/f32_type.h",
-    "sem/for_loop_statement.cc",
-    "sem/for_loop_statement.h",
-    "sem/function.cc",
-    "sem/i32_type.cc",
-    "sem/i32_type.h",
-    "sem/if_statement.cc",
-    "sem/if_statement.h",
-    "sem/info.cc",
-    "sem/info.h",
-    "sem/loop_statement.cc",
-    "sem/loop_statement.h",
-    "sem/matrix_type.cc",
-    "sem/matrix_type.h",
-    "sem/member_accessor_expression.cc",
-    "sem/module.cc",
-    "sem/module.h",
-    "sem/multisampled_texture_type.cc",
-    "sem/multisampled_texture_type.h",
-    "sem/node.cc",
-    "sem/node.h",
-    "sem/parameter_usage.cc",
-    "sem/parameter_usage.h",
-    "sem/pipeline_stage_set.h",
-    "sem/pointer_type.cc",
-    "sem/pointer_type.h",
-    "sem/reference_type.cc",
-    "sem/reference_type.h",
-    "sem/sampled_texture_type.cc",
-    "sem/sampled_texture_type.h",
-    "sem/sampler_type.cc",
-    "sem/sampler_type.h",
-    "sem/statement.cc",
-    "sem/storage_texture_type.cc",
-    "sem/storage_texture_type.h",
-    "sem/struct.cc",
-    "sem/switch_statement.cc",
-    "sem/switch_statement.h",
-    "sem/texture_type.cc",
-    "sem/texture_type.h",
-    "sem/type.cc",
-    "sem/type.h",
-    "sem/type_constructor.cc",
-    "sem/type_constructor.h",
-    "sem/type_conversion.cc",
-    "sem/type_conversion.h",
-    "sem/type_manager.cc",
-    "sem/type_manager.h",
-    "sem/type_mappings.h",
-    "sem/u32_type.cc",
-    "sem/u32_type.h",
-    "sem/variable.cc",
-    "sem/vector_type.cc",
-    "sem/vector_type.h",
-    "sem/void_type.cc",
-    "sem/void_type.h",
-  ]
-
-  public_deps = [ ":libtint_core_all_src" ]
-}
-
-libtint_source_set("libtint_core_src") {
-  public_deps = [
-    ":libtint_core_all_src",
-    ":libtint_sem_src",
-  ]
-}
-
-libtint_source_set("libtint_spv_reader_src") {
-  sources = [
-    "reader/spirv/construct.cc",
-    "reader/spirv/construct.h",
-    "reader/spirv/entry_point_info.cc",
-    "reader/spirv/entry_point_info.h",
-    "reader/spirv/enum_converter.cc",
-    "reader/spirv/enum_converter.h",
-    "reader/spirv/fail_stream.h",
-    "reader/spirv/function.cc",
-    "reader/spirv/function.h",
-    "reader/spirv/namer.cc",
-    "reader/spirv/namer.h",
-    "reader/spirv/parser.cc",
-    "reader/spirv/parser.h",
-    "reader/spirv/parser_impl.cc",
-    "reader/spirv/parser_impl.h",
-    "reader/spirv/parser_type.cc",
-    "reader/spirv/parser_type.h",
-    "reader/spirv/usage.cc",
-    "reader/spirv/usage.h",
-  ]
-
-  public_deps = [
-    ":libtint_core_src",
-    "${tint_spirv_tools_dir}/:spvtools_opt",
-  ]
-
-  public_configs = [ "${tint_spirv_tools_dir}/:spvtools_internal_config" ]
-}
-
-libtint_source_set("libtint_spv_writer_src") {
-  sources = [
-    "writer/spirv/binary_writer.cc",
-    "writer/spirv/binary_writer.h",
-    "writer/spirv/builder.cc",
-    "writer/spirv/builder.h",
-    "writer/spirv/function.cc",
-    "writer/spirv/function.h",
-    "writer/spirv/generator.cc",
-    "writer/spirv/generator.h",
-    "writer/spirv/instruction.cc",
-    "writer/spirv/instruction.h",
-    "writer/spirv/operand.cc",
-    "writer/spirv/operand.h",
-    "writer/spirv/scalar_constant.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-libtint_source_set("libtint_wgsl_reader_src") {
-  sources = [
-    "reader/wgsl/lexer.cc",
-    "reader/wgsl/lexer.h",
-    "reader/wgsl/parser.cc",
-    "reader/wgsl/parser.h",
-    "reader/wgsl/parser_impl.cc",
-    "reader/wgsl/parser_impl.h",
-    "reader/wgsl/parser_impl_detail.h",
-    "reader/wgsl/token.cc",
-    "reader/wgsl/token.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-libtint_source_set("libtint_wgsl_writer_src") {
-  sources = [
-    "writer/wgsl/generator.cc",
-    "writer/wgsl/generator.h",
-    "writer/wgsl/generator_impl.cc",
-    "writer/wgsl/generator_impl.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-libtint_source_set("libtint_msl_writer_src") {
-  sources = [
-    "writer/msl/generator.cc",
-    "writer/msl/generator.h",
-    "writer/msl/generator_impl.cc",
-    "writer/msl/generator_impl.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-libtint_source_set("libtint_hlsl_writer_src") {
-  sources = [
-    "writer/hlsl/generator.cc",
-    "writer/hlsl/generator.h",
-    "writer/hlsl/generator_impl.cc",
-    "writer/hlsl/generator_impl.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-libtint_source_set("libtint_glsl_writer_src") {
-  sources = [
-    "transform/glsl.cc",
-    "transform/glsl.h",
-    "writer/glsl/generator.cc",
-    "writer/glsl/generator.h",
-    "writer/glsl/generator_impl.cc",
-    "writer/glsl/generator_impl.h",
-  ]
-
-  public_deps = [ ":libtint_core_src" ]
-}
-
-source_set("libtint") {
-  public_deps = [ ":libtint_core_src" ]
-
-  if (tint_build_spv_reader) {
-    public_deps += [ ":libtint_spv_reader_src" ]
-  }
-
-  if (tint_build_spv_writer) {
-    public_deps += [ ":libtint_spv_writer_src" ]
-  }
-
-  if (tint_build_wgsl_reader) {
-    public_deps += [ ":libtint_wgsl_reader_src" ]
-  }
-
-  if (tint_build_wgsl_writer) {
-    public_deps += [ ":libtint_wgsl_writer_src" ]
-  }
-
-  if (tint_build_msl_writer) {
-    public_deps += [ ":libtint_msl_writer_src" ]
-  }
-
-  if (tint_build_hlsl_writer) {
-    public_deps += [ ":libtint_hlsl_writer_src" ]
-  }
-
-  if (tint_build_glsl_writer) {
-    public_deps += [ ":libtint_glsl_writer_src" ]
-  }
-
-  configs += [ ":tint_common_config" ]
-  public_configs = [ ":tint_public_config" ]
-
-  if (build_with_chromium) {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-  }
-}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index 84a0b27..0000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,1221 +0,0 @@
-# Copyright 2020 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.
-
-function(tint_spvtools_compile_options TARGET)
-  # We'll use the optimizer for its nice SPIR-V in-memory representation
-  target_link_libraries(${TARGET} SPIRV-Tools-opt SPIRV-Tools)
-
-  # We'll be cheating: using internal interfaces to the SPIRV-Tools
-  # optimizer.
-  target_include_directories(${TARGET} PRIVATE
-    ${spirv-tools_SOURCE_DIR}
-    ${spirv-tools_BINARY_DIR}
-  )
-
-  if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
-    # The SPIRV-Tools code is conditioned against C++ and an older version of Clang.
-    # Suppress warnings triggered in our current compilation environment.
-    # TODO(dneto): Fix the issues upstream.
-    target_compile_options(${TARGET} PRIVATE
-      -Wno-newline-eof
-      -Wno-sign-conversion
-      -Wno-old-style-cast
-      -Wno-weak-vtables
-    )
-  endif()
-endfunction()
-
-## Tint diagnostic utilities. Used by libtint and tint_utils_io.
-add_library(tint_diagnostic_utils
-  debug.cc
-  debug.h
-  source.h
-  source.cc
-  diagnostic/diagnostic.cc
-  diagnostic/diagnostic.h
-  diagnostic/formatter.cc
-  diagnostic/formatter.h
-  diagnostic/printer.cc
-  diagnostic/printer.h
-  utils/debugger.cc
-  utils/debugger.h
-)
-tint_default_compile_options(tint_diagnostic_utils)
-
-if (TINT_ENABLE_BREAK_IN_DEBUGGER)
-  set_source_files_properties(utils/debugger.cc
-    PROPERTIES COMPILE_DEFINITIONS "TINT_ENABLE_BREAK_IN_DEBUGGER=1" )
-endif()
-
-set(TINT_LIB_SRCS
-  ../include/tint/tint.h
-  ast/access.cc
-  ast/access.h
-  ast/attribute.cc
-  ast/attribute.h
-  ast/alias.cc
-  ast/alias.h
-  ast/index_accessor_expression.cc
-  ast/index_accessor_expression.h
-  ast/array.cc
-  ast/array.h
-  ast/assignment_statement.cc
-  ast/assignment_statement.h
-  ast/atomic.cc
-  ast/atomic.h
-  ast/binary_expression.cc
-  ast/binary_expression.h
-  ast/binding_attribute.cc
-  ast/binding_attribute.h
-  ast/bitcast_expression.cc
-  ast/bitcast_expression.h
-  ast/block_statement.cc
-  ast/block_statement.h
-  ast/bool_literal_expression.cc
-  ast/bool_literal_expression.h
-  ast/bool.cc
-  ast/bool.h
-  ast/break_statement.cc
-  ast/break_statement.h
-  ast/builtin_attribute.cc
-  ast/builtin_attribute.h
-  ast/builtin.cc
-  ast/builtin.h
-  ast/call_expression.cc
-  ast/call_expression.h
-  ast/call_statement.cc
-  ast/call_statement.h
-  ast/case_statement.cc
-  ast/case_statement.h
-  ast/continue_statement.cc
-  ast/continue_statement.h
-  ast/depth_multisampled_texture.cc
-  ast/depth_multisampled_texture.h
-  ast/disable_validation_attribute.cc
-  ast/disable_validation_attribute.h
-  ast/depth_texture.cc
-  ast/depth_texture.h
-  ast/discard_statement.cc
-  ast/discard_statement.h
-  ast/else_statement.cc
-  ast/else_statement.h
-  ast/expression.cc
-  ast/expression.h
-  ast/external_texture.cc
-  ast/external_texture.h
-  ast/f32.cc
-  ast/f32.h
-  ast/fallthrough_statement.cc
-  ast/fallthrough_statement.h
-  ast/float_literal_expression.cc
-  ast/float_literal_expression.h
-  ast/for_loop_statement.cc
-  ast/for_loop_statement.h
-  ast/function.cc
-  ast/function.h
-  ast/group_attribute.cc
-  ast/group_attribute.h
-  ast/i32.cc
-  ast/i32.h
-  ast/id_attribute.cc
-  ast/id_attribute.h
-  ast/identifier_expression.cc
-  ast/identifier_expression.h
-  ast/if_statement.cc
-  ast/if_statement.h
-  ast/int_literal_expression.cc
-  ast/int_literal_expression.h
-  ast/internal_attribute.cc
-  ast/internal_attribute.h
-  ast/interpolate_attribute.cc
-  ast/interpolate_attribute.h
-  ast/invariant_attribute.cc
-  ast/invariant_attribute.h
-  ast/literal_expression.cc
-  ast/literal_expression.h
-  ast/location_attribute.cc
-  ast/location_attribute.h
-  ast/loop_statement.cc
-  ast/loop_statement.h
-  ast/matrix.cc
-  ast/matrix.h
-  ast/member_accessor_expression.cc
-  ast/member_accessor_expression.h
-  ast/module.cc
-  ast/module.h
-  ast/multisampled_texture.cc
-  ast/multisampled_texture.h
-  ast/node.cc
-  ast/node.h
-  ast/phony_expression.cc
-  ast/phony_expression.h
-  ast/pipeline_stage.cc
-  ast/pipeline_stage.h
-  ast/pointer.cc
-  ast/pointer.h
-  ast/return_statement.cc
-  ast/return_statement.h
-  ast/sampled_texture.cc
-  ast/sampled_texture.h
-  ast/sampler.cc
-  ast/sampler.h
-  ast/sint_literal_expression.cc
-  ast/sint_literal_expression.h
-  ast/stage_attribute.cc
-  ast/stage_attribute.h
-  ast/statement.cc
-  ast/statement.h
-  ast/storage_class.cc
-  ast/storage_class.h
-  ast/storage_texture.cc
-  ast/storage_texture.h
-  ast/stride_attribute.cc
-  ast/stride_attribute.h
-  ast/struct_block_attribute.cc
-  ast/struct_block_attribute.h
-  ast/struct_member_align_attribute.cc
-  ast/struct_member_align_attribute.h
-  ast/struct_member_offset_attribute.cc
-  ast/struct_member_offset_attribute.h
-  ast/struct_member_size_attribute.cc
-  ast/struct_member_size_attribute.h
-  ast/struct_member.cc
-  ast/struct_member.h
-  ast/struct.cc
-  ast/struct.h
-  ast/switch_statement.cc
-  ast/switch_statement.h
-  ast/texture.cc
-  ast/texture.h
-  ast/traverse_expressions.h
-  ast/type_name.cc
-  ast/type_name.h
-  ast/ast_type.cc  # TODO(bclayton) - rename to type.cc
-  ast/type.h
-  ast/type_decl.cc
-  ast/type_decl.h
-  ast/type_name.cc
-  ast/type_name.h
-  ast/u32.cc
-  ast/u32.h
-  ast/uint_literal_expression.cc
-  ast/uint_literal_expression.h
-  ast/unary_op_expression.cc
-  ast/unary_op_expression.h
-  ast/unary_op.cc
-  ast/unary_op.h
-  ast/variable_decl_statement.cc
-  ast/variable_decl_statement.h
-  ast/variable.cc
-  ast/variable.h
-  ast/vector.cc
-  ast/vector.h
-  ast/void.cc
-  ast/void.h
-  ast/workgroup_attribute.cc
-  ast/workgroup_attribute.h
-  block_allocator.h
-  builtin_table.cc
-  builtin_table.h
-  builtin_table.inl
-  castable.cc
-  castable.h
-  clone_context.cc
-  clone_context.h
-  demangler.cc
-  demangler.h
-  inspector/entry_point.cc
-  inspector/entry_point.h
-  inspector/inspector.cc
-  inspector/inspector.h
-  inspector/resource_binding.cc
-  inspector/resource_binding.h
-  inspector/scalar.cc
-  inspector/scalar.h
-  program_builder.cc
-  program_builder.h
-  program_id.cc
-  program_id.h
-  program.cc
-  program.h
-  reader/reader.cc
-  reader/reader.h
-  resolver/dependency_graph.cc
-  resolver/dependency_graph.h
-  resolver/resolver.cc
-  resolver/resolver_constants.cc
-  resolver/resolver_validation.cc
-  resolver/resolver.h
-  scope_stack.h
-  sem/array.cc
-  sem/array.h
-  sem/atomic_type.cc
-  sem/atomic_type.h
-  sem/behavior.cc
-  sem/behavior.h
-  sem/binding_point.h
-  sem/block_statement.cc
-  sem/block_statement.h
-  sem/builtin_type.cc
-  sem/builtin_type.h
-  sem/builtin.cc
-  sem/builtin.h
-  sem/call_target.cc
-  sem/call_target.h
-  sem/call.cc
-  sem/call.h
-  sem/constant.cc
-  sem/constant.h
-  sem/depth_multisampled_texture_type.cc
-  sem/depth_multisampled_texture_type.h
-  sem/expression.cc
-  sem/expression.h
-  sem/function.cc
-  sem/info.cc
-  sem/info.h
-  sem/member_accessor_expression.cc
-  sem/parameter_usage.cc
-  sem/parameter_usage.h
-  sem/pipeline_stage_set.h
-  sem/node.cc
-  sem/node.h
-  sem/module.cc
-  sem/module.h
-  sem/sampler_texture_pair.h
-  sem/statement.cc
-  sem/struct.cc
-  sem/type_mappings.h
-  sem/variable.cc
-  symbol_table.cc
-  symbol_table.h
-  symbol.cc
-  symbol.h
-  text/unicode.cc
-  text/unicode.h
-  traits.h
-  transform/add_empty_entry_point.cc
-  transform/add_empty_entry_point.h
-  transform/add_spirv_block_attribute.cc
-  transform/add_spirv_block_attribute.h
-  transform/array_length_from_uniform.cc
-  transform/array_length_from_uniform.h
-  transform/binding_remapper.cc
-  transform/binding_remapper.h
-  transform/calculate_array_length.cc
-  transform/calculate_array_length.h
-  transform/combine_samplers.cc
-  transform/combine_samplers.h
-  transform/canonicalize_entry_point_io.cc
-  transform/canonicalize_entry_point_io.h
-  transform/decompose_memory_access.cc
-  transform/decompose_memory_access.h
-  transform/decompose_strided_array.cc
-  transform/decompose_strided_array.h
-  transform/decompose_strided_matrix.cc
-  transform/decompose_strided_matrix.h
-  transform/external_texture_transform.cc
-  transform/external_texture_transform.h
-  transform/first_index_offset.cc
-  transform/first_index_offset.h
-  transform/fold_constants.cc
-  transform/fold_constants.h
-  transform/fold_trivial_single_use_lets.cc
-  transform/fold_trivial_single_use_lets.h
-  transform/localize_struct_array_assignment.cc
-  transform/localize_struct_array_assignment.h
-  transform/for_loop_to_loop.cc
-  transform/for_loop_to_loop.h
-  transform/glsl.cc
-  transform/glsl.h
-  transform/loop_to_for_loop.cc
-  transform/loop_to_for_loop.h
-  transform/manager.cc
-  transform/manager.h
-  transform/module_scope_var_to_entry_point_param.cc
-  transform/module_scope_var_to_entry_point_param.h
-  transform/multiplanar_external_texture.cc
-  transform/multiplanar_external_texture.h
-  transform/num_workgroups_from_uniform.cc
-  transform/num_workgroups_from_uniform.h
-  transform/pad_array_elements.cc
-  transform/pad_array_elements.h
-  transform/promote_initializers_to_const_var.cc
-  transform/promote_initializers_to_const_var.h
-  transform/remove_phonies.cc
-  transform/remove_phonies.h
-  transform/remove_unreachable_statements.cc
-  transform/remove_unreachable_statements.h
-  transform/renamer.cc
-  transform/renamer.h
-  transform/robustness.cc
-  transform/robustness.h
-  transform/simplify_pointers.cc
-  transform/simplify_pointers.h
-  transform/single_entry_point.cc
-  transform/single_entry_point.h
-  transform/transform.cc
-  transform/transform.h
-  transform/unshadow.cc
-  transform/unshadow.h
-  transform/vectorize_scalar_matrix_constructors.cc
-  transform/vectorize_scalar_matrix_constructors.h
-  transform/var_for_dynamic_index.cc
-  transform/var_for_dynamic_index.h
-  transform/vertex_pulling.cc
-  transform/vertex_pulling.h
-  transform/wrap_arrays_in_structs.cc
-  transform/wrap_arrays_in_structs.h
-  transform/zero_init_workgroup_memory.cc
-  transform/zero_init_workgroup_memory.h
-  transform/utils/hoist_to_decl_before.cc
-  transform/utils/hoist_to_decl_before.h
-  sem/bool_type.cc
-  sem/bool_type.h
-  sem/depth_texture_type.cc
-  sem/depth_texture_type.h
-  sem/external_texture_type.cc
-  sem/external_texture_type.h
-  sem/f32_type.cc
-  sem/f32_type.h
-  sem/for_loop_statement.cc
-  sem/for_loop_statement.h
-  sem/i32_type.cc
-  sem/i32_type.h
-  sem/if_statement.cc
-  sem/if_statement.h
-  sem/loop_statement.cc
-  sem/loop_statement.h
-  sem/matrix_type.cc
-  sem/matrix_type.h
-  sem/multisampled_texture_type.cc
-  sem/multisampled_texture_type.h
-  sem/pointer_type.cc
-  sem/pointer_type.h
-  sem/reference_type.cc
-  sem/reference_type.h
-  sem/sampled_texture_type.cc
-  sem/sampled_texture_type.h
-  sem/sampler_type.cc
-  sem/sampler_type.h
-  sem/storage_texture_type.cc
-  sem/storage_texture_type.h
-  sem/switch_statement.cc
-  sem/switch_statement.h
-  sem/texture_type.cc
-  sem/texture_type.h
-  sem/type_constructor.cc
-  sem/type_constructor.h
-  sem/type_conversion.cc
-  sem/type_conversion.h
-  sem/type.cc
-  sem/type.h
-  sem/type_manager.cc
-  sem/type_manager.h
-  sem/u32_type.cc
-  sem/u32_type.h
-  sem/vector_type.cc
-  sem/vector_type.h
-  sem/void_type.cc
-  sem/void_type.h
-  utils/crc32.h
-  utils/enum_set.h
-  utils/hash.h
-  utils/map.h
-  utils/math.h
-  utils/scoped_assignment.h
-  utils/string.h
-  utils/unique_vector.h
-  writer/append_vector.cc
-  writer/append_vector.h
-  writer/array_length_from_uniform_options.cc
-  writer/array_length_from_uniform_options.h
-  writer/float_to_string.cc
-  writer/float_to_string.h
-  writer/text_generator.cc
-  writer/text_generator.h
-  writer/text.cc
-  writer/text.h
-  writer/writer.cc
-  writer/writer.h
-)
-
-if(UNIX)
-  list(APPEND TINT_LIB_SRCS diagnostic/printer_linux.cc)
-elseif(WIN32)
-  list(APPEND TINT_LIB_SRCS diagnostic/printer_windows.cc)
-else()
-  list(APPEND TINT_LIB_SRCS diagnostic/printer_other.cc)
-endif()
-
-if(${TINT_BUILD_SPV_READER})
-  list(APPEND TINT_LIB_SRCS
-    reader/spirv/construct.h
-    reader/spirv/construct.cc
-    reader/spirv/entry_point_info.h
-    reader/spirv/entry_point_info.cc
-    reader/spirv/enum_converter.h
-    reader/spirv/enum_converter.cc
-    reader/spirv/fail_stream.h
-    reader/spirv/function.cc
-    reader/spirv/function.h
-    reader/spirv/namer.cc
-    reader/spirv/namer.h
-    reader/spirv/parser_type.cc
-    reader/spirv/parser_type.h
-    reader/spirv/parser.cc
-    reader/spirv/parser.h
-    reader/spirv/parser_impl.cc
-    reader/spirv/parser_impl.h
-    reader/spirv/usage.cc
-    reader/spirv/usage.h
-  )
-endif()
-
-if(${TINT_BUILD_WGSL_READER})
-  list(APPEND TINT_LIB_SRCS
-    reader/wgsl/lexer.cc
-    reader/wgsl/lexer.h
-    reader/wgsl/parser.cc
-    reader/wgsl/parser.h
-    reader/wgsl/parser_impl.cc
-    reader/wgsl/parser_impl.h
-    reader/wgsl/parser_impl_detail.h
-    reader/wgsl/token.cc
-    reader/wgsl/token.h
-  )
-endif()
-
-if(${TINT_BUILD_SPV_WRITER})
-  list(APPEND TINT_LIB_SRCS
-    writer/spirv/binary_writer.cc
-    writer/spirv/binary_writer.h
-    writer/spirv/builder.cc
-    writer/spirv/builder.h
-    writer/spirv/function.cc
-    writer/spirv/function.h
-    writer/spirv/generator.cc
-    writer/spirv/generator.h
-    writer/spirv/instruction.cc
-    writer/spirv/instruction.h
-    writer/spirv/operand.cc
-    writer/spirv/operand.h
-    writer/spirv/scalar_constant.h
-  )
-endif()
-
-if(${TINT_BUILD_WGSL_WRITER})
-  list(APPEND TINT_LIB_SRCS
-    writer/wgsl/generator.cc
-    writer/wgsl/generator.h
-    writer/wgsl/generator_impl.cc
-    writer/wgsl/generator_impl.h
-  )
-endif()
-
-if(${TINT_BUILD_MSL_WRITER})
-  list(APPEND TINT_LIB_SRCS
-    writer/msl/generator.cc
-    writer/msl/generator.h
-    writer/msl/generator_impl.cc
-    writer/msl/generator_impl.h
-  )
-endif()
-
-if(${TINT_BUILD_GLSL_WRITER})
-  list(APPEND TINT_LIB_SRCS
-    writer/glsl/generator.cc
-    writer/glsl/generator.h
-    writer/glsl/generator_impl.cc
-    writer/glsl/generator_impl.h
-    writer/glsl/version.h
-  )
-endif()
-
-if(${TINT_BUILD_HLSL_WRITER})
-  list(APPEND TINT_LIB_SRCS
-    writer/hlsl/generator.cc
-    writer/hlsl/generator.h
-    writer/hlsl/generator_impl.cc
-    writer/hlsl/generator_impl.h
-  )
-endif()
-
-if(MSVC)
-  list(APPEND TINT_LIB_SRCS
-    tint.natvis
-  )
-endif()
-
-## Tint IO utilities. Used by tint_val.
-add_library(tint_utils_io
-  utils/io/command_${TINT_OS_CC_SUFFIX}.cc
-  utils/io/command.h
-  utils/io/tmpfile_${TINT_OS_CC_SUFFIX}.cc
-  utils/io/tmpfile.h
-)
-tint_default_compile_options(tint_utils_io)
-target_link_libraries(tint_utils_io tint_diagnostic_utils)
-
-## Tint validation utilities. Used by tests and the tint executable.
-add_library(tint_val
-  val/hlsl.cc
-  val/msl.cc
-  val/val.h
-)
-
-# If we're building on mac / ios and we have CoreGraphics, then we can use the
-# metal API to validate our shaders. This is roughly 4x faster than invoking
-# the metal shader compiler executable.
-if(APPLE)
-  find_library(LIB_CORE_GRAPHICS CoreGraphics)
-  if(LIB_CORE_GRAPHICS)
-    target_sources(tint_val PRIVATE "val/msl_metal.mm")
-    target_compile_definitions(tint_val PUBLIC "-DTINT_ENABLE_MSL_VALIDATION_USING_METAL_API=1")
-    target_compile_options(tint_val PRIVATE "-fmodules" "-fcxx-modules")
-    target_link_options(tint_val PUBLIC "-framework" "CoreGraphics")
-  endif()
-endif()
-
-tint_default_compile_options(tint_val)
-target_link_libraries(tint_val tint_utils_io)
-
-## Tint library
-add_library(libtint ${TINT_LIB_SRCS})
-tint_default_compile_options(libtint)
-target_link_libraries(libtint tint_diagnostic_utils)
-if (${COMPILER_IS_LIKE_GNU})
-  target_compile_options(libtint PRIVATE -fvisibility=hidden)
-endif()
-if (${TINT_SYMBOL_STORE_DEBUG_NAME})
-    target_compile_definitions(libtint PUBLIC "TINT_SYMBOL_STORE_DEBUG_NAME=1")
-endif()
-set_target_properties(libtint PROPERTIES OUTPUT_NAME "tint")
-
-if (${TINT_BUILD_FUZZERS})
-  # Tint library with fuzzer instrumentation
-  add_library(libtint-fuzz ${TINT_LIB_SRCS})
-  tint_default_compile_options(libtint-fuzz)
-  target_link_libraries(libtint-fuzz tint_diagnostic_utils)
-  if (${COMPILER_IS_LIKE_GNU})
-    target_compile_options(libtint-fuzz PRIVATE -fvisibility=hidden)
-  endif()
-
-  if (NOT ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "")
-    # This is set when the fuzzers are being built by OSS-Fuzz. In this case the
-    # variable provides the necessary linker flags, and OSS-Fuzz will take care
-    # of passing suitable compiler flags.
-    target_link_options(libtint-fuzz PUBLIC ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS})
-  else()
-    # When the fuzzers are being built outside of OSS-Fuzz, specific libFuzzer
-    # arguments to enable fuzzing are used.
-    target_compile_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp)
-    target_link_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp)
-  endif()
-endif()
-
-if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
-  tint_spvtools_compile_options(libtint)
-  if (${TINT_BUILD_FUZZERS})
-    tint_spvtools_compile_options(libtint-fuzz)
-  endif()
-endif()
-
-################################################################################
-# Tests
-################################################################################
-if(TINT_BUILD_TESTS)
-  set(TINT_TEST_SRCS
-    ast/alias_test.cc
-    ast/array_test.cc
-    ast/assignment_statement_test.cc
-    ast/atomic_test.cc
-    ast/binary_expression_test.cc
-    ast/binding_attribute_test.cc
-    ast/bitcast_expression_test.cc
-    ast/block_statement_test.cc
-    ast/bool_literal_expression_test.cc
-    ast/bool_test.cc
-    ast/break_statement_test.cc
-    ast/builtin_attribute_test.cc
-    ast/builtin_texture_helper_test.cc
-    ast/builtin_texture_helper_test.h
-    ast/call_expression_test.cc
-    ast/call_statement_test.cc
-    ast/case_statement_test.cc
-    ast/continue_statement_test.cc
-    ast/depth_multisampled_texture_test.cc
-    ast/depth_texture_test.cc
-    ast/discard_statement_test.cc
-    ast/else_statement_test.cc
-    ast/external_texture_test.cc
-    ast/f32_test.cc
-    ast/fallthrough_statement_test.cc
-    ast/float_literal_expression_test.cc
-    ast/for_loop_statement_test.cc
-    ast/function_test.cc
-    ast/group_attribute_test.cc
-    ast/i32_test.cc
-    ast/id_attribute_test.cc
-    ast/identifier_expression_test.cc
-    ast/if_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
-    ast/matrix_test.cc
-    ast/member_accessor_expression_test.cc
-    ast/module_clone_test.cc
-    ast/module_test.cc
-    ast/multisampled_texture_test.cc
-    ast/phony_expression_test.cc
-    ast/pointer_test.cc
-    ast/return_statement_test.cc
-    ast/sampled_texture_test.cc
-    ast/sampler_test.cc
-    ast/sint_literal_expression_test.cc
-    ast/stage_attribute_test.cc
-    ast/storage_texture_test.cc
-    ast/stride_attribute_test.cc
-    ast/struct_member_align_attribute_test.cc
-    ast/struct_member_offset_attribute_test.cc
-    ast/struct_member_size_attribute_test.cc
-    ast/struct_member_test.cc
-    ast/struct_test.cc
-    ast/switch_statement_test.cc
-    ast/test_helper.h
-    ast/texture_test.cc
-    ast/traverse_expressions_test.cc
-    ast/u32_test.cc
-    ast/uint_literal_expression_test.cc
-    ast/unary_op_expression_test.cc
-    ast/variable_decl_statement_test.cc
-    ast/variable_test.cc
-    ast/vector_test.cc
-    ast/workgroup_attribute_test.cc
-    block_allocator_test.cc
-    builtin_table_test.cc
-    castable_test.cc
-    clone_context_test.cc
-    debug_test.cc
-    demangler_test.cc
-    diagnostic/diagnostic_test.cc
-    diagnostic/formatter_test.cc
-    diagnostic/printer_test.cc
-    program_test.cc
-    resolver/array_accessor_test.cc
-    resolver/assignment_validation_test.cc
-    resolver/atomics_test.cc
-    resolver/atomics_validation_test.cc
-    resolver/bitcast_validation_test.cc
-    resolver/builtins_validation_test.cc
-    resolver/builtin_test.cc
-    resolver/builtin_validation_test.cc
-    resolver/call_test.cc
-    resolver/call_validation_test.cc
-    resolver/compound_statement_test.cc
-    resolver/control_block_validation_test.cc
-    resolver/attribute_validation_test.cc
-    resolver/dependency_graph_test.cc
-    resolver/entry_point_validation_test.cc
-    resolver/function_validation_test.cc
-    resolver/host_shareable_validation_test.cc
-    resolver/inferred_type_test.cc
-    resolver/is_host_shareable_test.cc
-    resolver/is_storeable_test.cc
-    resolver/pipeline_overridable_constant_test.cc
-    resolver/ptr_ref_test.cc
-    resolver/ptr_ref_validation_test.cc
-    resolver/resolver_behavior_test.cc
-    resolver/resolver_constants_test.cc
-    resolver/resolver_test_helper.cc
-    resolver/resolver_test_helper.h
-    resolver/resolver_test.cc
-    resolver/side_effects_test.cc
-    resolver/storage_class_layout_validation_test.cc
-    resolver/storage_class_validation_test.cc
-    resolver/struct_layout_test.cc
-    resolver/struct_pipeline_stage_use_test.cc
-    resolver/struct_storage_class_use_test.cc
-    resolver/type_constructor_validation_test.cc
-    resolver/type_validation_test.cc
-    resolver/validation_test.cc
-    resolver/var_let_test.cc
-    resolver/var_let_validation_test.cc
-    scope_stack_test.cc
-    sem/atomic_type_test.cc
-    sem/bool_type_test.cc
-    sem/builtin_test.cc
-    sem/depth_multisampled_texture_type_test.cc
-    sem/depth_texture_type_test.cc
-    sem/external_texture_type_test.cc
-    sem/f32_type_test.cc
-    sem/i32_type_test.cc
-    sem/matrix_type_test.cc
-    sem/multisampled_texture_type_test.cc
-    sem/pointer_type_test.cc
-    sem/reference_type_test.cc
-    sem/sampled_texture_type_test.cc
-    sem/sampler_type_test.cc
-    sem/sem_array_test.cc
-    sem/sem_struct_test.cc
-    sem/storage_texture_type_test.cc
-    sem/texture_type_test.cc
-    sem/type_manager_test.cc
-    sem/u32_type_test.cc
-    sem/vector_type_test.cc
-    source_test.cc
-    symbol_table_test.cc
-    symbol_test.cc
-    test_main.cc
-    text/unicode_test.cc
-    traits_test.cc
-    transform/transform_test.cc
-    utils/crc32_test.cc
-    utils/defer_test.cc
-    utils/enum_set_test.cc
-    utils/hash_test.cc
-    utils/io/command_test.cc
-    utils/io/tmpfile_test.cc
-    utils/map_test.cc
-    utils/math_test.cc
-    utils/reverse_test.cc
-    utils/scoped_assignment_test.cc
-    utils/string_test.cc
-    utils/transform_test.cc
-    utils/unique_vector_test.cc
-    writer/append_vector_test.cc
-    writer/float_to_string_test.cc
-    writer/text_generator_test.cc
-  )
-
-  # Inspector tests depend on WGSL reader
-  if(${TINT_BUILD_WGSL_READER})
-    list(APPEND TINT_TEST_SRCS
-      inspector/inspector_test.cc
-      inspector/test_inspector_builder.cc
-      inspector/test_inspector_builder.h
-      inspector/test_inspector_runner.cc
-      inspector/test_inspector_runner.h
-    )
-  endif()
-
-  if(${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_WGSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      reader/spirv/enum_converter_test.cc
-      reader/spirv/fail_stream_test.cc
-      reader/spirv/function_arithmetic_test.cc
-      reader/spirv/function_bit_test.cc
-      reader/spirv/function_cfg_test.cc
-      reader/spirv/function_call_test.cc
-      reader/spirv/function_composite_test.cc
-      reader/spirv/function_conversion_test.cc
-      reader/spirv/function_decl_test.cc
-      reader/spirv/function_glsl_std_450_test.cc
-      reader/spirv/function_logical_test.cc
-      reader/spirv/function_memory_test.cc
-      reader/spirv/function_misc_test.cc
-      reader/spirv/function_var_test.cc
-      reader/spirv/namer_test.cc
-      reader/spirv/parser_impl_barrier_test.cc
-      reader/spirv/parser_impl_convert_member_decoration_test.cc
-      reader/spirv/parser_impl_convert_type_test.cc
-      reader/spirv/parser_impl_function_decl_test.cc
-      reader/spirv/parser_impl_get_decorations_test.cc
-      reader/spirv/parser_impl_handle_test.cc
-      reader/spirv/parser_impl_import_test.cc
-      reader/spirv/parser_impl_module_var_test.cc
-      reader/spirv/parser_impl_named_types_test.cc
-      reader/spirv/parser_impl_test_helper.cc
-      reader/spirv/parser_impl_test_helper.h
-      reader/spirv/parser_impl_test.cc
-      reader/spirv/parser_impl_user_name_test.cc
-      reader/spirv/parser_type_test.cc
-      reader/spirv/parser_test.cc
-      reader/spirv/spirv_tools_helpers_test.cc
-      reader/spirv/spirv_tools_helpers_test.h
-      reader/spirv/usage_test.cc
-    )
-  endif()
-
-  if(${TINT_BUILD_WGSL_READER})
-    list(APPEND TINT_TEST_SRCS
-      reader/wgsl/lexer_test.cc
-      reader/wgsl/parser_test.cc
-      reader/wgsl/parser_impl_additive_expression_test.cc
-      reader/wgsl/parser_impl_and_expression_test.cc
-      reader/wgsl/parser_impl_argument_expression_list_test.cc
-      reader/wgsl/parser_impl_assignment_stmt_test.cc
-      reader/wgsl/parser_impl_body_stmt_test.cc
-      reader/wgsl/parser_impl_break_stmt_test.cc
-      reader/wgsl/parser_impl_bug_cases_test.cc
-      reader/wgsl/parser_impl_call_stmt_test.cc
-      reader/wgsl/parser_impl_case_body_test.cc
-      reader/wgsl/parser_impl_const_expr_test.cc
-      reader/wgsl/parser_impl_const_literal_test.cc
-      reader/wgsl/parser_impl_continue_stmt_test.cc
-      reader/wgsl/parser_impl_continuing_stmt_test.cc
-      reader/wgsl/parser_impl_depth_texture_type_test.cc
-      reader/wgsl/parser_impl_external_texture_type_test.cc
-      reader/wgsl/parser_impl_elseif_stmt_test.cc
-      reader/wgsl/parser_impl_equality_expression_test.cc
-      reader/wgsl/parser_impl_error_msg_test.cc
-      reader/wgsl/parser_impl_error_resync_test.cc
-      reader/wgsl/parser_impl_exclusive_or_expression_test.cc
-      reader/wgsl/parser_impl_for_stmt_test.cc
-      reader/wgsl/parser_impl_function_decl_test.cc
-      reader/wgsl/parser_impl_function_attribute_list_test.cc
-      reader/wgsl/parser_impl_function_attribute_test.cc
-      reader/wgsl/parser_impl_function_header_test.cc
-      reader/wgsl/parser_impl_global_constant_decl_test.cc
-      reader/wgsl/parser_impl_global_decl_test.cc
-      reader/wgsl/parser_impl_global_variable_decl_test.cc
-      reader/wgsl/parser_impl_if_stmt_test.cc
-      reader/wgsl/parser_impl_inclusive_or_expression_test.cc
-      reader/wgsl/parser_impl_logical_and_expression_test.cc
-      reader/wgsl/parser_impl_logical_or_expression_test.cc
-      reader/wgsl/parser_impl_loop_stmt_test.cc
-      reader/wgsl/parser_impl_multiplicative_expression_test.cc
-      reader/wgsl/parser_impl_param_list_test.cc
-      reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
-      reader/wgsl/parser_impl_pipeline_stage_test.cc
-      reader/wgsl/parser_impl_primary_expression_test.cc
-      reader/wgsl/parser_impl_relational_expression_test.cc
-      reader/wgsl/parser_impl_reserved_keyword_test.cc
-      reader/wgsl/parser_impl_sampled_texture_type_test.cc
-      reader/wgsl/parser_impl_sampler_type_test.cc
-      reader/wgsl/parser_impl_shift_expression_test.cc
-      reader/wgsl/parser_impl_singular_expression_test.cc
-      reader/wgsl/parser_impl_statement_test.cc
-      reader/wgsl/parser_impl_statements_test.cc
-      reader/wgsl/parser_impl_storage_class_test.cc
-      reader/wgsl/parser_impl_storage_texture_type_test.cc
-      reader/wgsl/parser_impl_struct_body_decl_test.cc
-      reader/wgsl/parser_impl_struct_decl_test.cc
-      reader/wgsl/parser_impl_struct_attribute_decl_test.cc
-      reader/wgsl/parser_impl_struct_attribute_test.cc
-      reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
-      reader/wgsl/parser_impl_struct_member_attribute_test.cc
-      reader/wgsl/parser_impl_struct_member_test.cc
-      reader/wgsl/parser_impl_switch_body_test.cc
-      reader/wgsl/parser_impl_switch_stmt_test.cc
-      reader/wgsl/parser_impl_test.cc
-      reader/wgsl/parser_impl_test_helper.cc
-      reader/wgsl/parser_impl_test_helper.h
-      reader/wgsl/parser_impl_texel_format_test.cc
-      reader/wgsl/parser_impl_texture_sampler_types_test.cc
-      reader/wgsl/parser_impl_type_alias_test.cc
-      reader/wgsl/parser_impl_type_decl_test.cc
-      reader/wgsl/parser_impl_unary_expression_test.cc
-      reader/wgsl/parser_impl_variable_decl_test.cc
-      reader/wgsl/parser_impl_variable_attribute_list_test.cc
-      reader/wgsl/parser_impl_variable_attribute_test.cc
-      reader/wgsl/parser_impl_variable_ident_decl_test.cc
-      reader/wgsl/parser_impl_variable_stmt_test.cc
-      reader/wgsl/parser_impl_variable_qualifier_test.cc
-      reader/wgsl/token_test.cc
-    )
-  endif()
-
-  if(${TINT_BUILD_SPV_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      writer/spirv/binary_writer_test.cc
-      writer/spirv/builder_accessor_expression_test.cc
-      writer/spirv/builder_assign_test.cc
-      writer/spirv/builder_binary_expression_test.cc
-      writer/spirv/builder_bitcast_expression_test.cc
-      writer/spirv/builder_block_test.cc
-      writer/spirv/builder_builtin_test.cc
-      writer/spirv/builder_builtin_texture_test.cc
-      writer/spirv/builder_call_test.cc
-      writer/spirv/builder_constructor_expression_test.cc
-      writer/spirv/builder_discard_test.cc
-      writer/spirv/builder_entry_point_test.cc
-      writer/spirv/builder_format_conversion_test.cc
-      writer/spirv/builder_function_attribute_test.cc
-      writer/spirv/builder_function_test.cc
-      writer/spirv/builder_function_variable_test.cc
-      writer/spirv/builder_global_variable_test.cc
-      writer/spirv/builder_ident_expression_test.cc
-      writer/spirv/builder_if_test.cc
-      writer/spirv/builder_literal_test.cc
-      writer/spirv/builder_loop_test.cc
-      writer/spirv/builder_return_test.cc
-      writer/spirv/builder_switch_test.cc
-      writer/spirv/builder_test.cc
-      writer/spirv/builder_type_test.cc
-      writer/spirv/builder_unary_op_expression_test.cc
-      writer/spirv/instruction_test.cc
-      writer/spirv/operand_test.cc
-      writer/spirv/scalar_constant_test.cc
-      writer/spirv/spv_dump.cc
-      writer/spirv/spv_dump.h
-      writer/spirv/test_helper.h
-    )
-  endif()
-
-  if(${TINT_BUILD_WGSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      writer/wgsl/generator_impl_test.cc
-      writer/wgsl/generator_impl_alias_type_test.cc
-      writer/wgsl/generator_impl_array_accessor_test.cc
-      writer/wgsl/generator_impl_assign_test.cc
-      writer/wgsl/generator_impl_binary_test.cc
-      writer/wgsl/generator_impl_bitcast_test.cc
-      writer/wgsl/generator_impl_block_test.cc
-      writer/wgsl/generator_impl_break_test.cc
-      writer/wgsl/generator_impl_call_test.cc
-      writer/wgsl/generator_impl_case_test.cc
-      writer/wgsl/generator_impl_cast_test.cc
-      writer/wgsl/generator_impl_constructor_test.cc
-      writer/wgsl/generator_impl_continue_test.cc
-      writer/wgsl/generator_impl_discard_test.cc
-      writer/wgsl/generator_impl_fallthrough_test.cc
-      writer/wgsl/generator_impl_function_test.cc
-      writer/wgsl/generator_impl_global_decl_test.cc
-      writer/wgsl/generator_impl_identifier_test.cc
-      writer/wgsl/generator_impl_if_test.cc
-      writer/wgsl/generator_impl_loop_test.cc
-      writer/wgsl/generator_impl_literal_test.cc
-      writer/wgsl/generator_impl_member_accessor_test.cc
-      writer/wgsl/generator_impl_return_test.cc
-      writer/wgsl/generator_impl_switch_test.cc
-      writer/wgsl/generator_impl_type_test.cc
-      writer/wgsl/generator_impl_unary_op_test.cc
-      writer/wgsl/generator_impl_variable_decl_statement_test.cc
-      writer/wgsl/generator_impl_variable_test.cc
-      writer/wgsl/test_helper.h
-    )
-  endif()
-
-  if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      transform/add_empty_entry_point_test.cc
-      transform/add_spirv_block_attribute_test.cc
-      transform/array_length_from_uniform_test.cc
-      transform/binding_remapper_test.cc
-      transform/calculate_array_length_test.cc
-      transform/canonicalize_entry_point_io_test.cc
-      transform/combine_samplers_test.cc
-      transform/decompose_memory_access_test.cc
-      transform/decompose_strided_array_test.cc
-      transform/decompose_strided_matrix_test.cc
-      transform/external_texture_transform_test.cc
-      transform/first_index_offset_test.cc
-      transform/fold_constants_test.cc
-      transform/fold_trivial_single_use_lets_test.cc
-      transform/for_loop_to_loop_test.cc
-      transform/localize_struct_array_assignment_test.cc
-      transform/loop_to_for_loop_test.cc
-      transform/module_scope_var_to_entry_point_param_test.cc
-      transform/multiplanar_external_texture_test.cc
-      transform/num_workgroups_from_uniform_test.cc
-      transform/pad_array_elements_test.cc
-      transform/promote_initializers_to_const_var_test.cc
-      transform/remove_phonies_test.cc
-      transform/remove_unreachable_statements_test.cc
-      transform/renamer_test.cc
-      transform/robustness_test.cc
-      transform/simplify_pointers_test.cc
-      transform/single_entry_point_test.cc
-      transform/test_helper.h
-      transform/unshadow_test.cc
-      transform/var_for_dynamic_index_test.cc
-      transform/vectorize_scalar_matrix_constructors_test.cc
-      transform/vertex_pulling_test.cc
-      transform/wrap_arrays_in_structs_test.cc
-      transform/zero_init_workgroup_memory_test.cc
-      transform/utils/hoist_to_decl_before_test.cc
-    )
-  endif()
-
-  if(${TINT_BUILD_MSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      writer/msl/generator_impl_array_accessor_test.cc
-      writer/msl/generator_impl_assign_test.cc
-      writer/msl/generator_impl_binary_test.cc
-      writer/msl/generator_impl_bitcast_test.cc
-      writer/msl/generator_impl_block_test.cc
-      writer/msl/generator_impl_break_test.cc
-      writer/msl/generator_impl_builtin_test.cc
-      writer/msl/generator_impl_builtin_texture_test.cc
-      writer/msl/generator_impl_call_test.cc
-      writer/msl/generator_impl_case_test.cc
-      writer/msl/generator_impl_cast_test.cc
-      writer/msl/generator_impl_constructor_test.cc
-      writer/msl/generator_impl_continue_test.cc
-      writer/msl/generator_impl_discard_test.cc
-      writer/msl/generator_impl_function_test.cc
-      writer/msl/generator_impl_identifier_test.cc
-      writer/msl/generator_impl_if_test.cc
-      writer/msl/generator_impl_import_test.cc
-      writer/msl/generator_impl_loop_test.cc
-      writer/msl/generator_impl_member_accessor_test.cc
-      writer/msl/generator_impl_module_constant_test.cc
-      writer/msl/generator_impl_return_test.cc
-      writer/msl/generator_impl_sanitizer_test.cc
-      writer/msl/generator_impl_switch_test.cc
-      writer/msl/generator_impl_test.cc
-      writer/msl/generator_impl_type_test.cc
-      writer/msl/generator_impl_unary_op_test.cc
-      writer/msl/generator_impl_variable_decl_statement_test.cc
-      writer/msl/test_helper.h
-    )
-  endif()
-
-  if (${TINT_BUILD_GLSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      writer/glsl/generator_impl_array_accessor_test.cc
-      writer/glsl/generator_impl_assign_test.cc
-      writer/glsl/generator_impl_binary_test.cc
-      writer/glsl/generator_impl_bitcast_test.cc
-      writer/glsl/generator_impl_block_test.cc
-      writer/glsl/generator_impl_break_test.cc
-      writer/glsl/generator_impl_builtin_test.cc
-      writer/glsl/generator_impl_builtin_texture_test.cc
-      writer/glsl/generator_impl_call_test.cc
-      writer/glsl/generator_impl_case_test.cc
-      writer/glsl/generator_impl_cast_test.cc
-      writer/glsl/generator_impl_constructor_test.cc
-      writer/glsl/generator_impl_continue_test.cc
-      writer/glsl/generator_impl_discard_test.cc
-      writer/glsl/generator_impl_function_test.cc
-      writer/glsl/generator_impl_identifier_test.cc
-      writer/glsl/generator_impl_if_test.cc
-      writer/glsl/generator_impl_import_test.cc
-      writer/glsl/generator_impl_loop_test.cc
-      writer/glsl/generator_impl_member_accessor_test.cc
-      writer/glsl/generator_impl_module_constant_test.cc
-      writer/glsl/generator_impl_return_test.cc
-      writer/glsl/generator_impl_sanitizer_test.cc
-      writer/glsl/generator_impl_storage_buffer_test.cc
-      writer/glsl/generator_impl_switch_test.cc
-      writer/glsl/generator_impl_test.cc
-      writer/glsl/generator_impl_type_test.cc
-      writer/glsl/generator_impl_unary_op_test.cc
-      writer/glsl/generator_impl_uniform_buffer_test.cc
-      writer/glsl/generator_impl_variable_decl_statement_test.cc
-      writer/glsl/generator_impl_workgroup_var_test.cc
-      writer/glsl/test_helper.h
-    )
-  endif()
-
-  if (${TINT_BUILD_HLSL_WRITER})
-    list(APPEND TINT_TEST_SRCS
-      writer/hlsl/generator_impl_array_accessor_test.cc
-      writer/hlsl/generator_impl_assign_test.cc
-      writer/hlsl/generator_impl_binary_test.cc
-      writer/hlsl/generator_impl_bitcast_test.cc
-      writer/hlsl/generator_impl_block_test.cc
-      writer/hlsl/generator_impl_break_test.cc
-      writer/hlsl/generator_impl_builtin_test.cc
-      writer/hlsl/generator_impl_builtin_texture_test.cc
-      writer/hlsl/generator_impl_call_test.cc
-      writer/hlsl/generator_impl_case_test.cc
-      writer/hlsl/generator_impl_cast_test.cc
-      writer/hlsl/generator_impl_constructor_test.cc
-      writer/hlsl/generator_impl_continue_test.cc
-      writer/hlsl/generator_impl_discard_test.cc
-      writer/hlsl/generator_impl_function_test.cc
-      writer/hlsl/generator_impl_identifier_test.cc
-      writer/hlsl/generator_impl_if_test.cc
-      writer/hlsl/generator_impl_import_test.cc
-      writer/hlsl/generator_impl_loop_test.cc
-      writer/hlsl/generator_impl_member_accessor_test.cc
-      writer/hlsl/generator_impl_module_constant_test.cc
-      writer/hlsl/generator_impl_return_test.cc
-      writer/hlsl/generator_impl_sanitizer_test.cc
-      writer/hlsl/generator_impl_switch_test.cc
-      writer/hlsl/generator_impl_test.cc
-      writer/hlsl/generator_impl_type_test.cc
-      writer/hlsl/generator_impl_unary_op_test.cc
-      writer/hlsl/generator_impl_variable_decl_statement_test.cc
-      writer/hlsl/generator_impl_workgroup_var_test.cc
-      writer/hlsl/test_helper.h
-    )
-  endif()
-
-  if (${TINT_BUILD_FUZZERS})
-    list(APPEND TINT_TEST_SRCS
-      ../fuzzers/mersenne_twister_engine.cc
-      ../fuzzers/mersenne_twister_engine.h
-      ../fuzzers/random_generator.cc
-      ../fuzzers/random_generator.h
-      ../fuzzers/random_generator_engine.cc
-      ../fuzzers/random_generator_engine.h
-      ../fuzzers/random_generator_test.cc
-    )
-  endif()
-
-  add_executable(tint_unittests ${TINT_TEST_SRCS})
-  set_target_properties(${target} PROPERTIES FOLDER "Tests")
-
-  if(NOT MSVC)
-    target_compile_options(tint_unittests PRIVATE
-      -Wno-global-constructors
-      -Wno-weak-vtables
-    )
-  endif()
-
-  ## Test executable
-  target_include_directories(
-      tint_unittests PRIVATE ${gmock_SOURCE_DIR}/include)
-  target_link_libraries(tint_unittests libtint gmock tint_utils_io)
-  tint_default_compile_options(tint_unittests)
-
-  if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
-    tint_spvtools_compile_options(tint_unittests)
-  endif()
-
-  add_test(NAME tint_unittests COMMAND tint_unittests)
-endif(TINT_BUILD_TESTS)
-
-################################################################################
-# Benchmarks
-################################################################################
-if(TINT_BUILD_BENCHMARKS)
-  if(NOT TINT_BUILD_WGSL_READER)
-    message(FATAL_ERROR "TINT_BUILD_BENCHMARKS requires TINT_BUILD_WGSL_READER")
-  endif()
-
-  set(TINT_BENCHMARK_SRC
-    "castable_bench.cc"
-    "bench/benchmark.cc"
-    "reader/wgsl/parser_bench.cc"
-  )
-
-  if (${TINT_BUILD_GLSL_WRITER})
-    list(APPEND TINT_BENCHMARK_SRC writer/glsl/generator_bench.cc)
-  endif()
-  if (${TINT_BUILD_HLSL_WRITER})
-    list(APPEND TINT_BENCHMARK_SRC writer/hlsl/generator_bench.cc)
-  endif()
-  if (${TINT_BUILD_MSL_WRITER})
-    list(APPEND TINT_BENCHMARK_SRC writer/msl/generator_bench.cc)
-  endif()
-  if (${TINT_BUILD_SPV_WRITER})
-    list(APPEND TINT_BENCHMARK_SRC writer/spirv/generator_bench.cc)
-  endif()
-  if (${TINT_BUILD_WGSL_WRITER})
-    list(APPEND TINT_BENCHMARK_SRC writer/wgsl/generator_bench.cc)
-  endif()
-
-  add_executable(tint-benchmark ${TINT_BENCHMARK_SRC})
-  set_target_properties(${target} PROPERTIES FOLDER "Benchmarks")
-
-  tint_core_compile_options(tint-benchmark)
-
-  target_link_libraries(tint-benchmark PRIVATE benchmark::benchmark libtint)
-endif(TINT_BUILD_BENCHMARKS)
diff --git a/src/ast/access.cc b/src/ast/access.cc
deleted file mode 100644
index a95753f..0000000
--- a/src/ast/access.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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/ast/access.h"
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, Access access) {
-  switch (access) {
-    case ast::Access::kUndefined: {
-      out << "undefined";
-      break;
-    }
-    case ast::Access::kRead: {
-      out << "read";
-      break;
-    }
-    case ast::Access::kReadWrite: {
-      out << "read_write";
-      break;
-    }
-    case ast::Access::kWrite: {
-      out << "write";
-      break;
-    }
-  }
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/access.h b/src/ast/access.h
deleted file mode 100644
index 34d93e8..0000000
--- a/src/ast/access.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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_AST_ACCESS_H_
-#define SRC_AST_ACCESS_H_
-
-#include <ostream>
-#include <string>
-
-namespace tint {
-namespace ast {
-
-/// The access control settings
-enum Access {
-  /// Not declared in the source
-  kUndefined = 0,
-  /// Read only
-  kRead,
-  /// Write only
-  kWrite,
-  /// Read write
-  kReadWrite,
-  // Last valid access mode
-  kLastValid = kReadWrite,
-};
-
-/// @param out the std::ostream to write to
-/// @param access the Access
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, Access access);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ACCESS_H_
diff --git a/src/ast/alias.cc b/src/ast/alias.cc
deleted file mode 100644
index 8a6a568..0000000
--- a/src/ast/alias.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/alias.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Alias);
-
-namespace tint {
-namespace ast {
-
-Alias::Alias(ProgramID pid,
-             const Source& src,
-             const Symbol& n,
-             const Type* subtype)
-    : Base(pid, src, n), type(subtype) {
-  TINT_ASSERT(AST, type);
-}
-
-Alias::Alias(Alias&&) = default;
-
-Alias::~Alias() = default;
-
-const Alias* Alias::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto sym = ctx->Clone(name);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<Alias>(src, sym, ty);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/alias.h b/src/ast/alias.h
deleted file mode 100644
index 495e470..0000000
--- a/src/ast/alias.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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_AST_ALIAS_H_
-#define SRC_AST_ALIAS_H_
-
-#include <string>
-
-#include "src/ast/type_decl.h"
-
-namespace tint {
-namespace ast {
-
-/// A type alias type. Holds a name and pointer to another type.
-class Alias : public Castable<Alias, TypeDecl> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param name the symbol for the alias
-  /// @param subtype the alias'd type
-  Alias(ProgramID pid,
-        const Source& src,
-        const Symbol& name,
-        const Type* subtype);
-  /// Move constructor
-  Alias(Alias&&);
-  /// Destructor
-  ~Alias() override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Alias* Clone(CloneContext* ctx) const override;
-
-  /// the alias type
-  const Type* const type;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ALIAS_H_
diff --git a/src/ast/alias_test.cc b/src/ast/alias_test.cc
deleted file mode 100644
index 4e72967..0000000
--- a/src/ast/alias_test.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/alias.h"
-#include "src/ast/access.h"
-#include "src/ast/array.h"
-#include "src/ast/bool.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampler.h"
-#include "src/ast/struct.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/texture.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstAliasTest = TestHelper;
-
-TEST_F(AstAliasTest, Create) {
-  auto* u32 = create<U32>();
-  auto* a = Alias("a_type", u32);
-  EXPECT_EQ(a->name, Symbol(1, ID()));
-  EXPECT_EQ(a->type, u32);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/array.cc b/src/ast/array.cc
deleted file mode 100644
index c48a0ac..0000000
--- a/src/ast/array.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 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/ast/array.h"
-
-#include <cmath>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Array);
-
-namespace tint {
-namespace ast {
-
-namespace {
-// Returns the string representation of an array size expression.
-std::string SizeExprToString(const Expression* size,
-                             const SymbolTable& symbols) {
-  if (auto* ident = size->As<IdentifierExpression>()) {
-    return symbols.NameFor(ident->symbol);
-  }
-  if (auto* literal = size->As<IntLiteralExpression>()) {
-    return std::to_string(literal->ValueAsU32());
-  }
-  // This will never be exposed to the user as the Resolver will reject this
-  // expression for array size.
-  return "<invalid>";
-}
-}  // namespace
-
-Array::Array(ProgramID pid,
-             const Source& src,
-             const Type* subtype,
-             const Expression* cnt,
-             AttributeList attrs)
-    : Base(pid, src), type(subtype), count(cnt), attributes(attrs) {}
-
-Array::Array(Array&&) = default;
-
-Array::~Array() = default;
-
-std::string Array::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  for (auto* attr : attributes) {
-    if (auto* stride = attr->As<ast::StrideAttribute>()) {
-      out << "@stride(" << stride->stride << ") ";
-    }
-  }
-  out << "array<" << type->FriendlyName(symbols);
-  if (!IsRuntimeArray()) {
-    out << ", " << SizeExprToString(count, symbols);
-  }
-  out << ">";
-  return out.str();
-}
-
-const Array* Array::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  auto* cnt = ctx->Clone(count);
-  auto attrs = ctx->Clone(attributes);
-  return ctx->dst->create<Array>(src, ty, cnt, attrs);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/array.h b/src/ast/array.h
deleted file mode 100644
index b8e6573..0000000
--- a/src/ast/array.h
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2020 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_AST_ARRAY_H_
-#define SRC_AST_ARRAY_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declarations.
-class Expression;
-
-/// An array type. If size is zero then it is a runtime array.
-class Array : public Castable<Array, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param subtype the type of the array elements
-  /// @param count the number of elements in the array. nullptr represents a
-  /// runtime-sized array.
-  /// @param attributes the array attributes
-  Array(ProgramID pid,
-        const Source& src,
-        const Type* subtype,
-        const Expression* count,
-        AttributeList attributes);
-  /// Move constructor
-  Array(Array&&);
-  ~Array() override;
-
-  /// @returns true if this is a runtime array.
-  /// i.e. the size is determined at runtime
-  bool IsRuntimeArray() const { return count == nullptr; }
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Array* Clone(CloneContext* ctx) const override;
-
-  /// the array element type
-  const Type* const type;
-
-  /// the array size in elements, or nullptr for a runtime array
-  const Expression* const count;
-
-  /// the array attributes
-  const AttributeList attributes;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ARRAY_H_
diff --git a/src/ast/array_test.cc b/src/ast/array_test.cc
deleted file mode 100644
index b463c9c..0000000
--- a/src/ast/array_test.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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/ast/array.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstArrayTest = TestHelper;
-
-TEST_F(AstArrayTest, CreateSizedArray) {
-  auto* u32 = create<U32>();
-  auto* count = Expr(3);
-  auto* arr = create<Array>(u32, count, AttributeList{});
-  EXPECT_EQ(arr->type, u32);
-  EXPECT_EQ(arr->count, count);
-  EXPECT_TRUE(arr->Is<Array>());
-  EXPECT_FALSE(arr->IsRuntimeArray());
-}
-
-TEST_F(AstArrayTest, CreateRuntimeArray) {
-  auto* u32 = create<U32>();
-  auto* arr = create<Array>(u32, nullptr, AttributeList{});
-  EXPECT_EQ(arr->type, u32);
-  EXPECT_EQ(arr->count, nullptr);
-  EXPECT_TRUE(arr->Is<Array>());
-  EXPECT_TRUE(arr->IsRuntimeArray());
-}
-
-TEST_F(AstArrayTest, FriendlyName_RuntimeSized) {
-  auto* i32 = create<I32>();
-  auto* arr = create<Array>(i32, nullptr, AttributeList{});
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_LiteralSized) {
-  auto* i32 = create<I32>();
-  auto* arr = create<Array>(i32, Expr(5), AttributeList{});
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_ConstantSized) {
-  auto* i32 = create<I32>();
-  auto* arr = create<Array>(i32, Expr("size"), AttributeList{});
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, size>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_WithStride) {
-  auto* i32 = create<I32>();
-  auto* arr =
-      create<Array>(i32, Expr(5), AttributeList{create<StrideAttribute>(32)});
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/assignment_statement.cc b/src/ast/assignment_statement.cc
deleted file mode 100644
index b6a2907..0000000
--- a/src/ast/assignment_statement.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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/ast/assignment_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::AssignmentStatement);
-
-namespace tint {
-namespace ast {
-
-AssignmentStatement::AssignmentStatement(ProgramID pid,
-                                         const Source& src,
-                                         const Expression* l,
-                                         const Expression* r)
-    : Base(pid, src), lhs(l), rhs(r) {
-  TINT_ASSERT(AST, lhs);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
-  TINT_ASSERT(AST, rhs);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
-}
-
-AssignmentStatement::AssignmentStatement(AssignmentStatement&&) = default;
-
-AssignmentStatement::~AssignmentStatement() = default;
-
-const AssignmentStatement* AssignmentStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* l = ctx->Clone(lhs);
-  auto* r = ctx->Clone(rhs);
-  return ctx->dst->create<AssignmentStatement>(src, l, r);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/assignment_statement.h b/src/ast/assignment_statement.h
deleted file mode 100644
index ee8fd3d..0000000
--- a/src/ast/assignment_statement.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_ASSIGNMENT_STATEMENT_H_
-#define SRC_AST_ASSIGNMENT_STATEMENT_H_
-
-#include "src/ast/expression.h"
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// An assignment statement
-class AssignmentStatement : public Castable<AssignmentStatement, Statement> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the assignment statement source
-  /// @param lhs the left side of the expression
-  /// @param rhs the right side of the expression
-  AssignmentStatement(ProgramID program_id,
-                      const Source& source,
-                      const Expression* lhs,
-                      const Expression* rhs);
-  /// Move constructor
-  AssignmentStatement(AssignmentStatement&&);
-  ~AssignmentStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const AssignmentStatement* Clone(CloneContext* ctx) const override;
-
-  /// left side expression
-  const Expression* const lhs;
-
-  /// right side expression
-  const Expression* const rhs;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ASSIGNMENT_STATEMENT_H_
diff --git a/src/ast/assignment_statement_test.cc b/src/ast/assignment_statement_test.cc
deleted file mode 100644
index 6c1a716..0000000
--- a/src/ast/assignment_statement_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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/ast/assignment_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AssignmentStatementTest = TestHelper;
-
-TEST_F(AssignmentStatementTest, Creation) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* stmt = create<AssignmentStatement>(lhs, rhs);
-  EXPECT_EQ(stmt->lhs, lhs);
-  EXPECT_EQ(stmt->rhs, rhs);
-}
-
-TEST_F(AssignmentStatementTest, CreationWithSource) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* stmt =
-      create<AssignmentStatement>(Source{Source::Location{20, 2}}, lhs, rhs);
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(AssignmentStatementTest, IsAssign) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* stmt = create<AssignmentStatement>(lhs, rhs);
-  EXPECT_TRUE(stmt->Is<AssignmentStatement>());
-}
-
-TEST_F(AssignmentStatementTest, Assert_Null_LHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<AssignmentStatement>(nullptr, b.Expr(1));
-      },
-      "internal compiler error");
-}
-
-TEST_F(AssignmentStatementTest, Assert_Null_RHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<AssignmentStatement>(b.Expr(1), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_LHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<AssignmentStatement>(b2.Expr("lhs"), b1.Expr("rhs"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_RHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<AssignmentStatement>(b1.Expr("lhs"), b2.Expr("rhs"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/ast_type.cc b/src/ast/ast_type.cc
deleted file mode 100644
index cd6d4bb..0000000
--- a/src/ast/ast_type.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/ast/type.h"
-
-#include "src/ast/alias.h"
-#include "src/ast/bool.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampler.h"
-#include "src/ast/texture.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-#include "src/symbol_table.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Type);
-
-namespace tint {
-namespace ast {
-
-Type::Type(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Type::Type(Type&&) = default;
-
-Type::~Type() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/atomic.cc b/src/ast/atomic.cc
deleted file mode 100644
index af35e4f..0000000
--- a/src/ast/atomic.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/atomic.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Atomic);
-
-namespace tint {
-namespace ast {
-
-Atomic::Atomic(ProgramID pid, const Source& src, const Type* const subtype)
-    : Base(pid, src), type(subtype) {}
-
-std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "atomic<" << type->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-Atomic::Atomic(Atomic&&) = default;
-
-Atomic::~Atomic() = default;
-
-const Atomic* Atomic::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<Atomic>(src, ty);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/atomic.h b/src/ast/atomic.h
deleted file mode 100644
index 4dcc7a0..0000000
--- a/src/ast/atomic.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_ATOMIC_H_
-#define SRC_AST_ATOMIC_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// An atomic type.
-class Atomic : public Castable<Atomic, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param subtype the pointee type
-  Atomic(ProgramID pid, const Source& src, const Type* const subtype);
-  /// Move constructor
-  Atomic(Atomic&&);
-  ~Atomic() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Atomic* Clone(CloneContext* ctx) const override;
-
-  /// the pointee type
-  const Type* const type;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ATOMIC_H_
diff --git a/src/ast/atomic_test.cc b/src/ast/atomic_test.cc
deleted file mode 100644
index 9618827..0000000
--- a/src/ast/atomic_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/atomic.h"
-
-#include "src/ast/i32.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstAtomicTest = TestHelper;
-
-TEST_F(AstAtomicTest, Creation) {
-  auto* i32 = create<I32>();
-  auto* p = create<Atomic>(i32);
-  EXPECT_EQ(p->type, i32);
-}
-
-TEST_F(AstAtomicTest, FriendlyName) {
-  auto* i32 = create<I32>();
-  auto* p = create<Atomic>(i32);
-  EXPECT_EQ(p->FriendlyName(Symbols()), "atomic<i32>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/attribute.cc b/src/ast/attribute.cc
deleted file mode 100644
index a12de14..0000000
--- a/src/ast/attribute.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 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/ast/attribute.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Attribute);
-
-namespace tint {
-namespace ast {
-
-Attribute::~Attribute() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/attribute.h b/src/ast/attribute.h
deleted file mode 100644
index d7437e4..0000000
--- a/src/ast/attribute.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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_AST_ATTRIBUTE_H_
-#define SRC_AST_ATTRIBUTE_H_
-
-#include <string>
-#include <vector>
-
-#include "src/ast/node.h"
-
-namespace tint {
-namespace ast {
-
-/// The base class for all attributes
-class Attribute : public Castable<Attribute, Node> {
- public:
-  ~Attribute() override;
-
-  /// @returns the WGSL name for the attribute
-  virtual std::string Name() const = 0;
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Attribute(ProgramID pid, const Source& src) : Base(pid, src) {}
-};
-
-/// A list of attributes
-using AttributeList = std::vector<const Attribute*>;
-
-/// @param attributes the list of attributes to search
-/// @returns true if `attributes` includes a attribute of type `T`
-template <typename T>
-bool HasAttribute(const AttributeList& attributes) {
-  for (auto* attr : attributes) {
-    if (attr->Is<T>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
-/// @param attributes the list of attributes to search
-/// @returns a pointer to `T` from `attributes` if found, otherwise nullptr.
-template <typename T>
-const T* GetAttribute(const AttributeList& attributes) {
-  for (auto* attr : attributes) {
-    if (attr->Is<T>()) {
-      return attr->As<T>();
-    }
-  }
-  return nullptr;
-}
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ATTRIBUTE_H_
diff --git a/src/ast/binary_expression.cc b/src/ast/binary_expression.cc
deleted file mode 100644
index f025f44..0000000
--- a/src/ast/binary_expression.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/binary_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BinaryExpression);
-
-namespace tint {
-namespace ast {
-
-BinaryExpression::BinaryExpression(ProgramID pid,
-                                   const Source& src,
-                                   BinaryOp o,
-                                   const Expression* l,
-                                   const Expression* r)
-    : Base(pid, src), op(o), lhs(l), rhs(r) {
-  TINT_ASSERT(AST, lhs);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
-  TINT_ASSERT(AST, rhs);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
-  TINT_ASSERT(AST, op != BinaryOp::kNone);
-}
-
-BinaryExpression::BinaryExpression(BinaryExpression&&) = default;
-
-BinaryExpression::~BinaryExpression() = default;
-
-const BinaryExpression* BinaryExpression::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* l = ctx->Clone(lhs);
-  auto* r = ctx->Clone(rhs);
-  return ctx->dst->create<BinaryExpression>(src, op, l, r);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/binary_expression.h b/src/ast/binary_expression.h
deleted file mode 100644
index 73b4337..0000000
--- a/src/ast/binary_expression.h
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2020 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_AST_BINARY_EXPRESSION_H_
-#define SRC_AST_BINARY_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// The operator type
-enum class BinaryOp {
-  kNone = 0,
-  kAnd,  // &
-  kOr,   // |
-  kXor,
-  kLogicalAnd,  // &&
-  kLogicalOr,   // ||
-  kEqual,
-  kNotEqual,
-  kLessThan,
-  kGreaterThan,
-  kLessThanEqual,
-  kGreaterThanEqual,
-  kShiftLeft,
-  kShiftRight,
-  kAdd,
-  kSubtract,
-  kMultiply,
-  kDivide,
-  kModulo,
-};
-
-/// An binary expression
-class BinaryExpression : public Castable<BinaryExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the binary expression source
-  /// @param op the operation type
-  /// @param lhs the left side of the expression
-  /// @param rhs the right side of the expression
-  BinaryExpression(ProgramID program_id,
-                   const Source& source,
-                   BinaryOp op,
-                   const Expression* lhs,
-                   const Expression* rhs);
-  /// Move constructor
-  BinaryExpression(BinaryExpression&&);
-  ~BinaryExpression() override;
-
-  /// @returns true if the op is and
-  bool IsAnd() const { return op == BinaryOp::kAnd; }
-  /// @returns true if the op is or
-  bool IsOr() const { return op == BinaryOp::kOr; }
-  /// @returns true if the op is xor
-  bool IsXor() const { return op == BinaryOp::kXor; }
-  /// @returns true if the op is logical and
-  bool IsLogicalAnd() const { return op == BinaryOp::kLogicalAnd; }
-  /// @returns true if the op is logical or
-  bool IsLogicalOr() const { return op == BinaryOp::kLogicalOr; }
-  /// @returns true if the op is equal
-  bool IsEqual() const { return op == BinaryOp::kEqual; }
-  /// @returns true if the op is not equal
-  bool IsNotEqual() const { return op == BinaryOp::kNotEqual; }
-  /// @returns true if the op is less than
-  bool IsLessThan() const { return op == BinaryOp::kLessThan; }
-  /// @returns true if the op is greater than
-  bool IsGreaterThan() const { return op == BinaryOp::kGreaterThan; }
-  /// @returns true if the op is less than equal
-  bool IsLessThanEqual() const { return op == BinaryOp::kLessThanEqual; }
-  /// @returns true if the op is greater than equal
-  bool IsGreaterThanEqual() const { return op == BinaryOp::kGreaterThanEqual; }
-  /// @returns true if the op is shift left
-  bool IsShiftLeft() const { return op == BinaryOp::kShiftLeft; }
-  /// @returns true if the op is shift right
-  bool IsShiftRight() const { return op == BinaryOp::kShiftRight; }
-  /// @returns true if the op is add
-  bool IsAdd() const { return op == BinaryOp::kAdd; }
-  /// @returns true if the op is subtract
-  bool IsSubtract() const { return op == BinaryOp::kSubtract; }
-  /// @returns true if the op is multiply
-  bool IsMultiply() const { return op == BinaryOp::kMultiply; }
-  /// @returns true if the op is divide
-  bool IsDivide() const { return op == BinaryOp::kDivide; }
-  /// @returns true if the op is modulo
-  bool IsModulo() const { return op == BinaryOp::kModulo; }
-  /// @returns true if the op is an arithmetic operation
-  bool IsArithmetic() const;
-  /// @returns true if the op is a comparison operation
-  bool IsComparison() const;
-  /// @returns true if the op is a bitwise operation
-  bool IsBitwise() const;
-  /// @returns true if the op is a bit shift operation
-  bool IsBitshift() const;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BinaryExpression* Clone(CloneContext* ctx) const override;
-
-  /// the binary op type
-  const BinaryOp op;
-  /// the left side expression
-  const Expression* const lhs;
-  /// the right side expression
-  const Expression* const rhs;
-};
-
-inline bool BinaryExpression::IsArithmetic() const {
-  switch (op) {
-    case ast::BinaryOp::kAdd:
-    case ast::BinaryOp::kSubtract:
-    case ast::BinaryOp::kMultiply:
-    case ast::BinaryOp::kDivide:
-    case ast::BinaryOp::kModulo:
-      return true;
-    default:
-      return false;
-  }
-}
-
-inline bool BinaryExpression::IsComparison() const {
-  switch (op) {
-    case ast::BinaryOp::kEqual:
-    case ast::BinaryOp::kNotEqual:
-    case ast::BinaryOp::kLessThan:
-    case ast::BinaryOp::kLessThanEqual:
-    case ast::BinaryOp::kGreaterThan:
-    case ast::BinaryOp::kGreaterThanEqual:
-      return true;
-    default:
-      return false;
-  }
-}
-
-inline bool BinaryExpression::IsBitwise() const {
-  switch (op) {
-    case ast::BinaryOp::kAnd:
-    case ast::BinaryOp::kOr:
-    case ast::BinaryOp::kXor:
-      return true;
-    default:
-      return false;
-  }
-}
-
-inline bool BinaryExpression::IsBitshift() const {
-  switch (op) {
-    case ast::BinaryOp::kShiftLeft:
-    case ast::BinaryOp::kShiftRight:
-      return true;
-    default:
-      return false;
-  }
-}
-
-/// @returns the human readable name of the given BinaryOp
-/// @param op the BinaryOp
-constexpr const char* FriendlyName(BinaryOp op) {
-  switch (op) {
-    case BinaryOp::kNone:
-      return "none";
-    case BinaryOp::kAnd:
-      return "and";
-    case BinaryOp::kOr:
-      return "or";
-    case BinaryOp::kXor:
-      return "xor";
-    case BinaryOp::kLogicalAnd:
-      return "logical_and";
-    case BinaryOp::kLogicalOr:
-      return "logical_or";
-    case BinaryOp::kEqual:
-      return "equal";
-    case BinaryOp::kNotEqual:
-      return "not_equal";
-    case BinaryOp::kLessThan:
-      return "less_than";
-    case BinaryOp::kGreaterThan:
-      return "greater_than";
-    case BinaryOp::kLessThanEqual:
-      return "less_than_equal";
-    case BinaryOp::kGreaterThanEqual:
-      return "greater_than_equal";
-    case BinaryOp::kShiftLeft:
-      return "shift_left";
-    case BinaryOp::kShiftRight:
-      return "shift_right";
-    case BinaryOp::kAdd:
-      return "add";
-    case BinaryOp::kSubtract:
-      return "subtract";
-    case BinaryOp::kMultiply:
-      return "multiply";
-    case BinaryOp::kDivide:
-      return "divide";
-    case BinaryOp::kModulo:
-      return "modulo";
-  }
-  return "INVALID";
-}
-
-/// @param out the std::ostream to write to
-/// @param op the BinaryOp
-/// @return the std::ostream so calls can be chained
-inline std::ostream& operator<<(std::ostream& out, BinaryOp op) {
-  out << FriendlyName(op);
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BINARY_EXPRESSION_H_
diff --git a/src/ast/binary_expression_test.cc b/src/ast/binary_expression_test.cc
deleted file mode 100644
index 379385e..0000000
--- a/src/ast/binary_expression_test.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BinaryExpressionTest = TestHelper;
-
-TEST_F(BinaryExpressionTest, Creation) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs);
-  EXPECT_EQ(r->lhs, lhs);
-  EXPECT_EQ(r->rhs, rhs);
-  EXPECT_EQ(r->op, BinaryOp::kEqual);
-}
-
-TEST_F(BinaryExpressionTest, Creation_WithSource) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* r = create<BinaryExpression>(Source{Source::Location{20, 2}},
-                                     BinaryOp::kEqual, lhs, rhs);
-  auto src = r->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(BinaryExpressionTest, IsBinary) {
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs);
-  EXPECT_TRUE(r->Is<BinaryExpression>());
-}
-
-TEST_F(BinaryExpressionTest, Assert_Null_LHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<BinaryExpression>(BinaryOp::kEqual, nullptr, b.Expr("rhs"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(BinaryExpressionTest, Assert_Null_RHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<BinaryExpression>(BinaryOp::kEqual, b.Expr("lhs"), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_LHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<BinaryExpression>(BinaryOp::kEqual, b2.Expr("lhs"),
-                                    b1.Expr("rhs"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_RHS) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<BinaryExpression>(BinaryOp::kEqual, b1.Expr("lhs"),
-                                    b2.Expr("rhs"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/binding_attribute.cc b/src/ast/binding_attribute.cc
deleted file mode 100644
index c7bbc60..0000000
--- a/src/ast/binding_attribute.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/binding_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BindingAttribute);
-
-namespace tint {
-namespace ast {
-
-BindingAttribute::BindingAttribute(ProgramID pid,
-                                   const Source& src,
-                                   uint32_t val)
-    : Base(pid, src), value(val) {}
-
-BindingAttribute::~BindingAttribute() = default;
-
-std::string BindingAttribute::Name() const {
-  return "binding";
-}
-
-const BindingAttribute* BindingAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<BindingAttribute>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/binding_attribute.h b/src/ast/binding_attribute.h
deleted file mode 100644
index 6560a94..0000000
--- a/src/ast/binding_attribute.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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_AST_BINDING_ATTRIBUTE_H_
-#define SRC_AST_BINDING_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A binding attribute
-class BindingAttribute : public Castable<BindingAttribute, Attribute> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the binding value
-  BindingAttribute(ProgramID pid, const Source& src, uint32_t value);
-  ~BindingAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BindingAttribute* Clone(CloneContext* ctx) const override;
-
-  /// the binding value
-  const uint32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BINDING_ATTRIBUTE_H_
diff --git a/src/ast/binding_attribute_test.cc b/src/ast/binding_attribute_test.cc
deleted file mode 100644
index de71368..0000000
--- a/src/ast/binding_attribute_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BindingAttributeTest = TestHelper;
-
-TEST_F(BindingAttributeTest, Creation) {
-  auto* d = create<BindingAttribute>(2);
-  EXPECT_EQ(2u, d->value);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
deleted file mode 100644
index 5a76492..0000000
--- a/src/ast/bitcast_expression.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/ast/bitcast_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BitcastExpression);
-
-namespace tint {
-namespace ast {
-
-BitcastExpression::BitcastExpression(ProgramID pid,
-                                     const Source& src,
-                                     const Type* t,
-                                     const Expression* e)
-    : Base(pid, src), type(t), expr(e) {
-  TINT_ASSERT(AST, type);
-  TINT_ASSERT(AST, expr);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
-}
-
-BitcastExpression::BitcastExpression(BitcastExpression&&) = default;
-BitcastExpression::~BitcastExpression() = default;
-
-const BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* t = ctx->Clone(type);
-  auto* e = ctx->Clone(expr);
-  return ctx->dst->create<BitcastExpression>(src, t, e);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bitcast_expression.h b/src/ast/bitcast_expression.h
deleted file mode 100644
index fe2c134..0000000
--- a/src/ast/bitcast_expression.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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_AST_BITCAST_EXPRESSION_H_
-#define SRC_AST_BITCAST_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declaration
-class Type;
-
-/// A bitcast expression
-class BitcastExpression : public Castable<BitcastExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the bitcast expression source
-  /// @param type the type
-  /// @param expr the expr
-  BitcastExpression(ProgramID program_id,
-                    const Source& source,
-                    const Type* type,
-                    const Expression* expr);
-  /// Move constructor
-  BitcastExpression(BitcastExpression&&);
-  ~BitcastExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BitcastExpression* Clone(CloneContext* ctx) const override;
-
-  /// the target cast type
-  const Type* const type;
-  /// the expression
-  const Expression* const expr;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BITCAST_EXPRESSION_H_
diff --git a/src/ast/bitcast_expression_test.cc b/src/ast/bitcast_expression_test.cc
deleted file mode 100644
index df0015f..0000000
--- a/src/ast/bitcast_expression_test.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 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/ast/bitcast_expression.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BitcastExpressionTest = TestHelper;
-
-TEST_F(BitcastExpressionTest, Create) {
-  auto* expr = Expr("expr");
-
-  auto* exp = create<BitcastExpression>(ty.f32(), expr);
-  EXPECT_TRUE(exp->type->Is<ast::F32>());
-  ASSERT_EQ(exp->expr, expr);
-}
-
-TEST_F(BitcastExpressionTest, CreateWithSource) {
-  auto* expr = Expr("expr");
-
-  auto* exp = create<BitcastExpression>(Source{Source::Location{20, 2}},
-                                        ty.f32(), expr);
-  auto src = exp->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(BitcastExpressionTest, IsBitcast) {
-  auto* expr = Expr("expr");
-
-  auto* exp = create<BitcastExpression>(ty.f32(), expr);
-  EXPECT_TRUE(exp->Is<BitcastExpression>());
-}
-
-TEST_F(BitcastExpressionTest, Assert_Null_Type) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<BitcastExpression>(nullptr, b.Expr("idx"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(BitcastExpressionTest, Assert_Null_Expr) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<BitcastExpression>(b.ty.f32(), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(BitcastExpressionTest, Assert_DifferentProgramID_Expr) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<BitcastExpression>(b1.ty.f32(), b2.Expr("idx"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/block_statement.cc b/src/ast/block_statement.cc
deleted file mode 100644
index 395cc5b..0000000
--- a/src/ast/block_statement.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/ast/block_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BlockStatement);
-
-namespace tint {
-namespace ast {
-
-BlockStatement::BlockStatement(ProgramID pid,
-                               const Source& src,
-                               const StatementList& stmts)
-    : Base(pid, src), statements(std::move(stmts)) {
-  for (auto* stmt : statements) {
-    TINT_ASSERT(AST, stmt);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id);
-  }
-}
-
-BlockStatement::BlockStatement(BlockStatement&&) = default;
-
-BlockStatement::~BlockStatement() = default;
-
-const BlockStatement* BlockStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto stmts = ctx->Clone(statements);
-  return ctx->dst->create<BlockStatement>(src, stmts);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/block_statement.h b/src/ast/block_statement.h
deleted file mode 100644
index 56bdc43..0000000
--- a/src/ast/block_statement.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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_AST_BLOCK_STATEMENT_H_
-#define SRC_AST_BLOCK_STATEMENT_H_
-
-#include <utility>
-
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// A block statement
-class BlockStatement : public Castable<BlockStatement, Statement> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the block statement source
-  /// @param statements the statements
-  BlockStatement(ProgramID program_id,
-                 const Source& source,
-                 const StatementList& statements);
-  /// Move constructor
-  BlockStatement(BlockStatement&&);
-  ~BlockStatement() override;
-
-  /// @returns true if the block has no statements
-  bool Empty() const { return statements.empty(); }
-
-  /// @returns the last statement in the block or nullptr if block empty
-  const Statement* Last() const {
-    return statements.empty() ? nullptr : statements.back();
-  }
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BlockStatement* Clone(CloneContext* ctx) const override;
-
-  /// the statement list
-  const StatementList statements;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BLOCK_STATEMENT_H_
diff --git a/src/ast/block_statement_test.cc b/src/ast/block_statement_test.cc
deleted file mode 100644
index 70f44d5..0000000
--- a/src/ast/block_statement_test.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BlockStatementTest = TestHelper;
-
-TEST_F(BlockStatementTest, Creation) {
-  auto* d = create<DiscardStatement>();
-  auto* ptr = d;
-
-  auto* b = create<BlockStatement>(StatementList{d});
-
-  ASSERT_EQ(b->statements.size(), 1u);
-  EXPECT_EQ(b->statements[0], ptr);
-}
-
-TEST_F(BlockStatementTest, Creation_WithSource) {
-  auto* b = create<BlockStatement>(Source{Source::Location{20, 2}},
-                                   ast::StatementList{});
-  auto src = b->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(BlockStatementTest, IsBlock) {
-  auto* b = create<BlockStatement>(ast::StatementList{});
-  EXPECT_TRUE(b->Is<BlockStatement>());
-}
-
-TEST_F(BlockStatementTest, Assert_Null_Statement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<BlockStatement>(ast::StatementList{nullptr});
-      },
-      "internal compiler error");
-}
-
-TEST_F(BlockStatementTest, Assert_DifferentProgramID_Statement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<BlockStatement>(
-            ast::StatementList{b2.create<DiscardStatement>()});
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bool.cc b/src/ast/bool.cc
deleted file mode 100644
index 1148107..0000000
--- a/src/ast/bool.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/ast/bool.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Bool);
-
-namespace tint {
-namespace ast {
-
-Bool::Bool(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Bool::Bool(Bool&&) = default;
-
-Bool::~Bool() = default;
-
-std::string Bool::FriendlyName(const SymbolTable&) const {
-  return "bool";
-}
-
-const Bool* Bool::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<Bool>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bool.h b/src/ast/bool.h
deleted file mode 100644
index 41625d4..0000000
--- a/src/ast/bool.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_BOOL_H_
-#define SRC_AST_BOOL_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-// X11 likes to #define Bool leading to confusing error messages.
-// If its defined, undefine it.
-#ifdef Bool
-#undef Bool
-#endif
-
-namespace tint {
-namespace ast {
-
-/// A boolean type
-class Bool : public Castable<Bool, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Bool(ProgramID pid, const Source& src);
-  /// Move constructor
-  Bool(Bool&&);
-  ~Bool() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Bool* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BOOL_H_
diff --git a/src/ast/bool_literal_expression.cc b/src/ast/bool_literal_expression.cc
deleted file mode 100644
index 8bb4e2b..0000000
--- a/src/ast/bool_literal_expression.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/ast/bool_literal_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BoolLiteralExpression);
-
-namespace tint {
-namespace ast {
-
-BoolLiteralExpression::BoolLiteralExpression(ProgramID pid,
-                                             const Source& src,
-                                             bool val)
-    : Base(pid, src), value(val) {}
-
-BoolLiteralExpression::~BoolLiteralExpression() = default;
-
-const BoolLiteralExpression* BoolLiteralExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<BoolLiteralExpression>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bool_literal_expression.h b/src/ast/bool_literal_expression.h
deleted file mode 100644
index a41d3cb..0000000
--- a/src/ast/bool_literal_expression.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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_AST_BOOL_LITERAL_EXPRESSION_H_
-#define SRC_AST_BOOL_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/ast/literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A boolean literal
-class BoolLiteralExpression
-    : public Castable<BoolLiteralExpression, LiteralExpression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the bool literals value
-  BoolLiteralExpression(ProgramID pid, const Source& src, bool value);
-  ~BoolLiteralExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BoolLiteralExpression* Clone(CloneContext* ctx) const override;
-
-  /// The boolean literal value
-  const bool value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BOOL_LITERAL_EXPRESSION_H_
diff --git a/src/ast/bool_literal_expression_test.cc b/src/ast/bool_literal_expression_test.cc
deleted file mode 100644
index d8093db..0000000
--- a/src/ast/bool_literal_expression_test.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BoolLiteralExpressionTest = TestHelper;
-
-TEST_F(BoolLiteralExpressionTest, True) {
-  auto* b = create<BoolLiteralExpression>(true);
-  ASSERT_TRUE(b->Is<BoolLiteralExpression>());
-  ASSERT_TRUE(b->value);
-}
-
-TEST_F(BoolLiteralExpressionTest, False) {
-  auto* b = create<BoolLiteralExpression>(false);
-  ASSERT_TRUE(b->Is<BoolLiteralExpression>());
-  ASSERT_FALSE(b->value);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/bool_test.cc b/src/ast/bool_test.cc
deleted file mode 100644
index b4b0108..0000000
--- a/src/ast/bool_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/bool.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstBoolTest = TestHelper;
-
-TEST_F(AstBoolTest, FriendlyName) {
-  auto* b = create<Bool>();
-  EXPECT_EQ(b->FriendlyName(Symbols()), "bool");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/break_statement.cc b/src/ast/break_statement.cc
deleted file mode 100644
index 1a515cc..0000000
--- a/src/ast/break_statement.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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/ast/break_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BreakStatement);
-
-namespace tint {
-namespace ast {
-
-BreakStatement::BreakStatement(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-BreakStatement::BreakStatement(BreakStatement&&) = default;
-
-BreakStatement::~BreakStatement() = default;
-
-const BreakStatement* BreakStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<BreakStatement>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/break_statement.h b/src/ast/break_statement.h
deleted file mode 100644
index f88212c..0000000
--- a/src/ast/break_statement.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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_AST_BREAK_STATEMENT_H_
-#define SRC_AST_BREAK_STATEMENT_H_
-
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// An break statement
-class BreakStatement : public Castable<BreakStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  BreakStatement(ProgramID pid, const Source& src);
-  /// Move constructor
-  BreakStatement(BreakStatement&&);
-  ~BreakStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BreakStatement* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BREAK_STATEMENT_H_
diff --git a/src/ast/break_statement_test.cc b/src/ast/break_statement_test.cc
deleted file mode 100644
index ed56b7b..0000000
--- a/src/ast/break_statement_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/ast/break_statement.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BreakStatementTest = TestHelper;
-
-TEST_F(BreakStatementTest, Creation_WithSource) {
-  auto* stmt = create<BreakStatement>(Source{Source::Location{20, 2}});
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(BreakStatementTest, IsBreak) {
-  auto* stmt = create<BreakStatement>();
-  EXPECT_TRUE(stmt->Is<BreakStatement>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builtin.cc b/src/ast/builtin.cc
deleted file mode 100644
index e0f8ecc..0000000
--- a/src/ast/builtin.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2020 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/ast/builtin.h"
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, Builtin builtin) {
-  switch (builtin) {
-    case Builtin::kNone: {
-      out << "none";
-      break;
-    }
-    case Builtin::kPosition: {
-      out << "position";
-      break;
-    }
-    case Builtin::kVertexIndex: {
-      out << "vertex_index";
-      break;
-    }
-    case Builtin::kInstanceIndex: {
-      out << "instance_index";
-      break;
-    }
-    case Builtin::kFrontFacing: {
-      out << "front_facing";
-      break;
-    }
-    case Builtin::kFragDepth: {
-      out << "frag_depth";
-      break;
-    }
-    case Builtin::kLocalInvocationId: {
-      out << "local_invocation_id";
-      break;
-    }
-    case Builtin::kLocalInvocationIndex: {
-      out << "local_invocation_index";
-      break;
-    }
-    case Builtin::kGlobalInvocationId: {
-      out << "global_invocation_id";
-      break;
-    }
-    case Builtin::kWorkgroupId: {
-      out << "workgroup_id";
-      break;
-    }
-    case Builtin::kNumWorkgroups: {
-      out << "num_workgroups";
-      break;
-    }
-    case Builtin::kSampleIndex: {
-      out << "sample_index";
-      break;
-    }
-    case Builtin::kSampleMask: {
-      out << "sample_mask";
-      break;
-    }
-    case Builtin::kPointSize: {
-      out << "pointsize";
-    }
-  }
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builtin.h b/src/ast/builtin.h
deleted file mode 100644
index 3e03148..0000000
--- a/src/ast/builtin.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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_AST_BUILTIN_H_
-#define SRC_AST_BUILTIN_H_
-
-#include <ostream>
-
-namespace tint {
-namespace ast {
-
-/// The builtin identifiers
-enum class Builtin {
-  kNone = -1,
-  kPosition,
-  kVertexIndex,
-  kInstanceIndex,
-  kFrontFacing,
-  kFragDepth,
-  kLocalInvocationId,
-  kLocalInvocationIndex,
-  kGlobalInvocationId,
-  kWorkgroupId,
-  kNumWorkgroups,
-  kSampleIndex,
-  kSampleMask,
-
-  // Below are not currently WGSL builtins, but are included in this enum as
-  // they are used by certain backends.
-  kPointSize,
-};
-
-/// @param out the std::ostream to write to
-/// @param builtin the Builtin
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, Builtin builtin);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BUILTIN_H_
diff --git a/src/ast/builtin_attribute.cc b/src/ast/builtin_attribute.cc
deleted file mode 100644
index d76c570..0000000
--- a/src/ast/builtin_attribute.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/ast/builtin_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BuiltinAttribute);
-
-namespace tint {
-namespace ast {
-
-BuiltinAttribute::BuiltinAttribute(ProgramID pid, const Source& src, Builtin b)
-    : Base(pid, src), builtin(b) {}
-
-BuiltinAttribute::~BuiltinAttribute() = default;
-
-std::string BuiltinAttribute::Name() const {
-  return "builtin";
-}
-
-const BuiltinAttribute* BuiltinAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<BuiltinAttribute>(src, builtin);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builtin_attribute.h b/src/ast/builtin_attribute.h
deleted file mode 100644
index 0151ac4..0000000
--- a/src/ast/builtin_attribute.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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_AST_BUILTIN_ATTRIBUTE_H_
-#define SRC_AST_BUILTIN_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-#include "src/ast/builtin.h"
-
-namespace tint {
-namespace ast {
-
-/// A builtin attribute
-class BuiltinAttribute : public Castable<BuiltinAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param builtin the builtin value
-  BuiltinAttribute(ProgramID pid, const Source& src, Builtin builtin);
-  ~BuiltinAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const BuiltinAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The builtin value
-  const Builtin builtin;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BUILTIN_ATTRIBUTE_H_
diff --git a/src/ast/builtin_attribute_test.cc b/src/ast/builtin_attribute_test.cc
deleted file mode 100644
index 5f750ba..0000000
--- a/src/ast/builtin_attribute_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using BuiltinAttributeTest = TestHelper;
-
-TEST_F(BuiltinAttributeTest, Creation) {
-  auto* d = create<BuiltinAttribute>(Builtin::kFragDepth);
-  EXPECT_EQ(Builtin::kFragDepth, d->builtin);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builtin_texture_helper_test.cc b/src/ast/builtin_texture_helper_test.cc
deleted file mode 100644
index f272608..0000000
--- a/src/ast/builtin_texture_helper_test.cc
+++ /dev/null
@@ -1,2286 +0,0 @@
-// Copyright 2020 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/ast/builtin_texture_helper_test.h"
-
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-
-namespace tint {
-namespace ast {
-namespace builtin {
-namespace test {
-
-using u32 = ProgramBuilder::u32;
-using i32 = ProgramBuilder::i32;
-using f32 = ProgramBuilder::f32;
-
-TextureOverloadCase::TextureOverloadCase(
-    ValidTextureOverload o,
-    const char* desc,
-    TextureKind tk,
-    ast::SamplerKind sk,
-    ast::TextureDimension dims,
-    TextureDataType datatype,
-    const char* f,
-    std::function<ExpressionList(ProgramBuilder*)> a)
-    : overload(o),
-      description(desc),
-      texture_kind(tk),
-      sampler_kind(sk),
-      texture_dimension(dims),
-      texture_data_type(datatype),
-      function(f),
-      args(std::move(a)) {}
-TextureOverloadCase::TextureOverloadCase(
-    ValidTextureOverload o,
-    const char* desc,
-    TextureKind tk,
-    ast::TextureDimension dims,
-    TextureDataType datatype,
-    const char* f,
-    std::function<ExpressionList(ProgramBuilder*)> a)
-    : overload(o),
-      description(desc),
-      texture_kind(tk),
-      texture_dimension(dims),
-      texture_data_type(datatype),
-      function(f),
-      args(std::move(a)) {}
-TextureOverloadCase::TextureOverloadCase(
-    ValidTextureOverload o,
-    const char* d,
-    Access acc,
-    ast::TexelFormat fmt,
-    ast::TextureDimension dims,
-    TextureDataType datatype,
-    const char* f,
-    std::function<ExpressionList(ProgramBuilder*)> a)
-    : overload(o),
-      description(d),
-      texture_kind(TextureKind::kStorage),
-      access(acc),
-      texel_format(fmt),
-      texture_dimension(dims),
-      texture_data_type(datatype),
-      function(f),
-      args(std::move(a)) {}
-TextureOverloadCase::TextureOverloadCase(const TextureOverloadCase&) = default;
-TextureOverloadCase::~TextureOverloadCase() = default;
-
-std::ostream& operator<<(std::ostream& out, const TextureKind& kind) {
-  switch (kind) {
-    case TextureKind::kRegular:
-      out << "regular";
-      break;
-    case TextureKind::kDepth:
-      out << "depth";
-      break;
-    case TextureKind::kDepthMultisampled:
-      out << "depth-multisampled";
-      break;
-    case TextureKind::kMultisampled:
-      out << "multisampled";
-      break;
-    case TextureKind::kStorage:
-      out << "storage";
-      break;
-  }
-  return out;
-}
-
-std::ostream& operator<<(std::ostream& out, const TextureDataType& ty) {
-  switch (ty) {
-    case TextureDataType::kF32:
-      out << "f32";
-      break;
-    case TextureDataType::kU32:
-      out << "u32";
-      break;
-    case TextureDataType::kI32:
-      out << "i32";
-      break;
-  }
-  return out;
-}
-
-std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data) {
-  out << "TextureOverloadCase " << static_cast<int>(data.overload) << "\n";
-  out << data.description << "\n";
-  out << "texture_kind:      " << data.texture_kind << "\n";
-  out << "sampler_kind:      ";
-  if (data.texture_kind != TextureKind::kStorage) {
-    out << data.sampler_kind;
-  } else {
-    out << "<unused>";
-  }
-  out << "\n";
-  out << "access:            " << data.access << "\n";
-  out << "texel_format:      " << data.texel_format << "\n";
-  out << "texture_dimension: " << data.texture_dimension << "\n";
-  out << "texture_data_type: " << data.texture_data_type << "\n";
-  return out;
-}
-
-const ast::Type* TextureOverloadCase::BuildResultVectorComponentType(
-    ProgramBuilder* b) const {
-  switch (texture_data_type) {
-    case ast::builtin::test::TextureDataType::kF32:
-      return b->ty.f32();
-    case ast::builtin::test::TextureDataType::kU32:
-      return b->ty.u32();
-    case ast::builtin::test::TextureDataType::kI32:
-      return b->ty.i32();
-  }
-
-  TINT_UNREACHABLE(AST, b->Diagnostics());
-  return {};
-}
-
-const ast::Variable* TextureOverloadCase::BuildTextureVariable(
-    ProgramBuilder* b) const {
-  AttributeList attrs = {
-      b->create<ast::GroupAttribute>(0),
-      b->create<ast::BindingAttribute>(0),
-  };
-  switch (texture_kind) {
-    case ast::builtin::test::TextureKind::kRegular:
-      return b->Global("texture",
-                       b->ty.sampled_texture(texture_dimension,
-                                             BuildResultVectorComponentType(b)),
-                       attrs);
-
-    case ast::builtin::test::TextureKind::kDepth:
-      return b->Global("texture", b->ty.depth_texture(texture_dimension),
-                       attrs);
-
-    case ast::builtin::test::TextureKind::kDepthMultisampled:
-      return b->Global("texture",
-                       b->ty.depth_multisampled_texture(texture_dimension),
-                       attrs);
-
-    case ast::builtin::test::TextureKind::kMultisampled:
-      return b->Global(
-          "texture",
-          b->ty.multisampled_texture(texture_dimension,
-                                     BuildResultVectorComponentType(b)),
-          attrs);
-
-    case ast::builtin::test::TextureKind::kStorage: {
-      auto* st = b->ty.storage_texture(texture_dimension, texel_format, access);
-      return b->Global("texture", st, attrs);
-    }
-  }
-
-  TINT_UNREACHABLE(AST, b->Diagnostics());
-  return nullptr;
-}
-
-const ast::Variable* TextureOverloadCase::BuildSamplerVariable(
-    ProgramBuilder* b) const {
-  AttributeList attrs = {
-      b->create<ast::GroupAttribute>(0),
-      b->create<ast::BindingAttribute>(1),
-  };
-  return b->Global("sampler", b->ty.sampler(sampler_kind), attrs);
-}
-
-std::vector<TextureOverloadCase> TextureOverloadCase::ValidCases() {
-  return {
-      {
-          ValidTextureOverload::kDimensions1d,
-          "textureDimensions(t : texture_1d<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k1d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensions2d,
-          "textureDimensions(t : texture_2d<f32>) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensions2dLevel,
-          "textureDimensions(t     : texture_2d<f32>,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensions2dArray,
-          "textureDimensions(t : texture_2d_array<f32>) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensions2dArrayLevel,
-          "textureDimensions(t     : texture_2d_array<f32>,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensions3d,
-          "textureDimensions(t : texture_3d<f32>) -> vec3<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensions3dLevel,
-          "textureDimensions(t     : texture_3d<f32>,\n"
-          "                  level : i32) -> vec3<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsCube,
-          "textureDimensions(t : texture_cube<f32>) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsCubeLevel,
-          "textureDimensions(t     : texture_cube<f32>,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsCubeArray,
-          "textureDimensions(t : texture_cube_array<f32>) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsCubeArrayLevel,
-          "textureDimensions(t     : texture_cube_array<f32>,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsMultisampled2d,
-          "textureDimensions(t : texture_multisampled_2d<f32>)-> vec2<i32>",
-          TextureKind::kMultisampled,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepth2d,
-          "textureDimensions(t : texture_depth_2d) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepth2dLevel,
-          "textureDimensions(t     : texture_depth_2d,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepth2dArray,
-          "textureDimensions(t : texture_depth_2d_array) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepth2dArrayLevel,
-          "textureDimensions(t     : texture_depth_2d_array,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepthCube,
-          "textureDimensions(t : texture_depth_cube) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepthCubeLevel,
-          "textureDimensions(t     : texture_depth_cube,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepthCubeArray,
-          "textureDimensions(t : texture_depth_cube_array) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepthCubeArrayLevel,
-          "textureDimensions(t     : texture_depth_cube_array,\n"
-          "                  level : i32) -> vec2<i32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
-      },
-      {
-          ValidTextureOverload::kDimensionsDepthMultisampled2d,
-          "textureDimensions(t : texture_depth_multisampled_2d) -> vec2<i32>",
-          TextureKind::kDepthMultisampled,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsStorageWO1d,
-          "textureDimensions(t : texture_storage_1d<rgba32float>) -> i32",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k1d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsStorageWO2d,
-          "textureDimensions(t : texture_storage_2d<rgba32float>) -> "
-          "vec2<i32>",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsStorageWO2dArray,
-          "textureDimensions(t : texture_storage_2d_array<rgba32float>) -> "
-          "vec2<i32>",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kDimensionsStorageWO3d,
-          "textureDimensions(t : texture_storage_3d<rgba32float>) -> "
-          "vec3<i32>",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureDimensions",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-
-      {
-          ValidTextureOverload::kGather2dF32,
-          "textureGather(component : i32,\n"
-          "              t         : texture_2d<T>,\n"
-          "              s         : sampler,\n"
-          "              coords    : vec2<f32>) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                        // component
-                               "texture",                // t
-                               "sampler",                // s
-                               b->vec2<f32>(1.f, 2.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kGather2dOffsetF32,
-          "textureGather(component : i32,\n"
-          "              t         : texture_2d<T>,\n"
-          "              s         : sampler,\n"
-          "              coords    : vec2<f32>,\n"
-          "              offset    : vec2<i32>) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                       // component
-                               "texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               b->vec2<i32>(3, 4));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGather2dArrayF32,
-          "textureGather(component   : i32,\n"
-          "              t           : texture_2d_array<T>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                       // component
-                               "texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3);                      // array index
-          },
-      },
-      {
-          ValidTextureOverload::kGather2dArrayOffsetF32,
-          "textureGather(component   : i32,\n"
-          "              t           : texture_2d_array<T>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32,\n"
-          "              offset      : vec2<i32>) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                       // component
-                               "texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCubeF32,
-          "textureGather(component : i32,\n"
-          "              t         : texture_cube<T>,\n"
-          "              s         : sampler,\n"
-          "              coords    : vec3<f32>) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                             // component
-                               "texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCubeArrayF32,
-          "textureGather(component   : i32,\n"
-          "              t           : texture_cube_array<T>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec3<f32>,\n"
-          "              array_index : i32) -> vec4<T>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList(0,                            // component
-                               "texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4);                           // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepth2dF32,
-          "textureGather(t      : texture_depth_2d,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                // t
-                               "sampler",                // s
-                               b->vec2<f32>(1.f, 2.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepth2dOffsetF32,
-          "textureGather(t      : texture_depth_2d,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>,\n"
-          "              offset : vec2<i32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               b->vec2<i32>(3, 4));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepth2dArrayF32,
-          "textureGather(t           : texture_depth_2d_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3);                      // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepth2dArrayOffsetF32,
-          "textureGather(t           : texture_depth_2d_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32,\n"
-          "              offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepthCubeF32,
-          "textureGather(t      : texture_depth_cube,\n"
-          "              s      : sampler,\n"
-          "              coords : vec3<f32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kGatherDepthCubeArrayF32,
-          "textureGather(t           : texture_depth_cube_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec3<f32>,\n"
-          "              array_index : i32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureGather",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4);                           // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepth2dF32,
-          "textureGatherCompare(t         : texture_depth_2d,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec2<f32>,\n"
-          "                     depth_ref : f32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f);                    // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepth2dOffsetF32,
-          "textureGatherCompare(t         : texture_depth_2d,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec2<f32>,\n"
-          "                     depth_ref : f32,\n"
-          "                     offset    : vec2<i32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f,                     // depth_ref
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepth2dArrayF32,
-          "textureGatherCompare(t           : texture_depth_2d_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec2<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4.f);                    // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32,
-          "textureGatherCompare(t           : texture_depth_2d_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec2<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32,\n"
-          "                     offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4.f,                     // depth_ref
-                               b->vec2<i32>(5, 6));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepthCubeF32,
-          "textureGatherCompare(t         : texture_depth_cube,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec3<f32>,\n"
-          "                     depth_ref : f32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kGatherCompareDepthCubeArrayF32,
-          "textureGatherCompare(t           : texture_depth_cube_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec3<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32) -> vec4<f32>",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureGatherCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4,                            // array_index
-                               5.f);                         // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kNumLayers2dArray,
-          "textureNumLayers(t : texture_2d_array<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureNumLayers",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLayersCubeArray,
-          "textureNumLayers(t : texture_cube_array<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureNumLayers",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLayersDepth2dArray,
-          "textureNumLayers(t : texture_depth_2d_array) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureNumLayers",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLayersDepthCubeArray,
-          "textureNumLayers(t : texture_depth_cube_array) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureNumLayers",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLayersStorageWO2dArray,
-          "textureNumLayers(t : texture_storage_2d_array<rgba32float>) -> i32",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureNumLayers",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevels2d,
-          "textureNumLevels(t : texture_2d<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevels2dArray,
-          "textureNumLevels(t : texture_2d_array<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevels3d,
-          "textureNumLevels(t : texture_3d<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsCube,
-          "textureNumLevels(t : texture_cube<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsCubeArray,
-          "textureNumLevels(t : texture_cube_array<f32>) -> i32",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsDepth2d,
-          "textureNumLevels(t : texture_depth_2d) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsDepth2dArray,
-          "textureNumLevels(t : texture_depth_2d_array) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsDepthCube,
-          "textureNumLevels(t : texture_depth_cube) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumLevelsDepthCubeArray,
-          "textureNumLevels(t : texture_depth_cube_array) -> i32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureNumLevels",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kNumSamplesMultisampled2d,
-          "textureNumSamples(t : texture_multisampled_2d<f32>) -> i32",
-          TextureKind::kMultisampled,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureNumSamples",
-          [](ProgramBuilder* b) { return b->ExprList("texture"); },
-      },
-      {
-          ValidTextureOverload::kSample1dF32,
-          "textureSample(t      : texture_1d<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k1d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",  // t
-                               "sampler",  // s
-                               1.0f);      // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSample2dF32,
-          "textureSample(t      : texture_2d<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                // t
-                               "sampler",                // s
-                               b->vec2<f32>(1.f, 2.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSample2dOffsetF32,
-          "textureSample(t      : texture_2d<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>\n"
-          "              offset : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               b->vec2<i32>(3, 4));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSample2dArrayF32,
-          "textureSample(t           : texture_2d_array<f32>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3);                      // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kSample2dArrayOffsetF32,
-          "textureSample(t           : texture_2d_array<f32>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32\n"
-          "              offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSample3dF32,
-          "textureSample(t      : texture_3d<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : vec3<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSample3dOffsetF32,
-          "textureSample(t      : texture_3d<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : vec3<f32>\n"
-          "              offset : vec3<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               b->vec3<i32>(4, 5, 6));       // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCubeF32,
-          "textureSample(t      : texture_cube<f32>,\n"
-          "              s      : sampler,\n"
-          "              coords : vec3<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCubeArrayF32,
-          "textureSample(t           : texture_cube_array<f32>,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec3<f32>,\n"
-          "              array_index : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4);                           // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepth2dF32,
-          "textureSample(t      : texture_depth_2d,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                // t
-                               "sampler",                // s
-                               b->vec2<f32>(1.f, 2.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepth2dOffsetF32,
-          "textureSample(t      : texture_depth_2d,\n"
-          "              s      : sampler,\n"
-          "              coords : vec2<f32>\n"
-          "              offset : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               b->vec2<i32>(3, 4));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepth2dArrayF32,
-          "textureSample(t           : texture_depth_2d_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3);                      // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepth2dArrayOffsetF32,
-          "textureSample(t           : texture_depth_2d_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec2<f32>,\n"
-          "              array_index : i32\n"
-          "              offset      : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepthCubeF32,
-          "textureSample(t      : texture_depth_cube,\n"
-          "              s      : sampler,\n"
-          "              coords : vec3<f32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
-          },
-      },
-      {
-          ValidTextureOverload::kSampleDepthCubeArrayF32,
-          "textureSample(t           : texture_depth_cube_array,\n"
-          "              s           : sampler,\n"
-          "              coords      : vec3<f32>,\n"
-          "              array_index : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSample",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4);                           // array_index
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias2dF32,
-          "textureSampleBias(t      : texture_2d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec2<f32>,\n"
-          "                  bias   : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f);                    // bias
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias2dOffsetF32,
-          "textureSampleBias(t      : texture_2d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec2<f32>,\n"
-          "                  bias   : f32,\n"
-          "                  offset : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f,                     // bias
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias2dArrayF32,
-          "textureSampleBias(t           : texture_2d_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec2<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  bias        : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               4,                       // array_index
-                               3.f);                    // bias
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias2dArrayOffsetF32,
-          "textureSampleBias(t           : texture_2d_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec2<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  bias        : f32,\n"
-          "                  offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4.f,                     // bias
-                               b->vec2<i32>(5, 6));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias3dF32,
-          "textureSampleBias(t      : texture_3d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  bias   : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // bias
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBias3dOffsetF32,
-          "textureSampleBias(t      : texture_3d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  bias   : f32,\n"
-          "                  offset : vec3<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f,                          // bias
-                               b->vec3<i32>(5, 6, 7));       // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBiasCubeF32,
-          "textureSampleBias(t      : texture_cube<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  bias   : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // bias
-          },
-      },
-      {
-          ValidTextureOverload::kSampleBiasCubeArrayF32,
-          "textureSampleBias(t           : texture_cube_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec3<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  bias        : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSampleBias",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               3,                            // array_index
-                               4.f);                         // bias
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel2dF32,
-          "textureSampleLevel(t      : texture_2d<f32>,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec2<f32>,\n"
-          "                   level  : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f);                    // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel2dOffsetF32,
-          "textureSampleLevel(t      : texture_2d<f32>,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec2<f32>,\n"
-          "                   level  : f32,\n"
-          "                   offset : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f,                     // level
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel2dArrayF32,
-          "textureSampleLevel(t           : texture_2d_array<f32>,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec2<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4.f);                    // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel2dArrayOffsetF32,
-          "textureSampleLevel(t           : texture_2d_array<f32>,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec2<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : f32,\n"
-          "                   offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4.f,                     // level
-                               b->vec2<i32>(5, 6));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel3dF32,
-          "textureSampleLevel(t      : texture_3d<f32>,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec3<f32>,\n"
-          "                   level  : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevel3dOffsetF32,
-          "textureSampleLevel(t      : texture_3d<f32>,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec3<f32>,\n"
-          "                   level  : f32,\n"
-          "                   offset : vec3<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f,                          // level
-                               b->vec3<i32>(5, 6, 7));       // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelCubeF32,
-          "textureSampleLevel(t      : texture_cube<f32>,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec3<f32>,\n"
-          "                   level  : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelCubeArrayF32,
-          "textureSampleLevel(t           : texture_cube_array<f32>,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec3<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : f32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4,                            // array_index
-                               5.f);                         // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepth2dF32,
-          "textureSampleLevel(t      : texture_depth_2d,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec2<f32>,\n"
-          "                   level  : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3);                      // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepth2dOffsetF32,
-          "textureSampleLevel(t      : texture_depth_2d,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec2<f32>,\n"
-          "                   level  : i32,\n"
-          "                   offset : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // level
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepth2dArrayF32,
-          "textureSampleLevel(t           : texture_depth_2d_array,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec2<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4);                      // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32,
-          "textureSampleLevel(t           : texture_depth_2d_array,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec2<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : i32,\n"
-          "                   offset      : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               4,                       // level
-                               b->vec2<i32>(5, 6));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepthCubeF32,
-          "textureSampleLevel(t      : texture_depth_cube,\n"
-          "                   s      : sampler,\n"
-          "                   coords : vec3<f32>,\n"
-          "                   level  : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4);                           // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleLevelDepthCubeArrayF32,
-          "textureSampleLevel(t           : texture_depth_cube_array,\n"
-          "                   s           : sampler,\n"
-          "                   coords      : vec3<f32>,\n"
-          "                   array_index : i32,\n"
-          "                   level       : i32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSampleLevel",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4,                            // array_index
-                               5);                           // level
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad2dF32,
-          "textureSampleGrad(t      : texture_2d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec2<f32>\n"
-          "                  ddx    : vec2<f32>,\n"
-          "                  ddy    : vec2<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                  // t
-                               "sampler",                  // s
-                               b->vec2<f32>(1.0f, 2.0f),   // coords
-                               b->vec2<f32>(3.0f, 4.0f),   // ddx
-                               b->vec2<f32>(5.0f, 6.0f));  // ddy
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad2dOffsetF32,
-          "textureSampleGrad(t      : texture_2d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec2<f32>,\n"
-          "                  ddx    : vec2<f32>,\n"
-          "                  ddy    : vec2<f32>,\n"
-          "                  offset : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               b->vec2<f32>(3.f, 4.f),  // ddx
-                               b->vec2<f32>(5.f, 6.f),  // ddy
-                               b->vec2<i32>(7, 7));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad2dArrayF32,
-          "textureSampleGrad(t           : texture_2d_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec2<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  ddx         : vec2<f32>,\n"
-          "                  ddy         : vec2<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                // t
-                               "sampler",                // s
-                               b->vec2<f32>(1.f, 2.f),   // coords
-                               3,                        // array_index
-                               b->vec2<f32>(4.f, 5.f),   // ddx
-                               b->vec2<f32>(6.f, 7.f));  // ddy
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad2dArrayOffsetF32,
-          "textureSampleGrad(t           : texture_2d_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec2<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  ddx         : vec2<f32>,\n"
-          "                  ddy         : vec2<f32>,\n"
-          "                  offset      : vec2<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3,                       // array_index
-                               b->vec2<f32>(4.f, 5.f),  // ddx
-                               b->vec2<f32>(6.f, 7.f),  // ddy
-                               b->vec2<i32>(6, 7));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad3dF32,
-          "textureSampleGrad(t      : texture_3d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  ddx    : vec3<f32>,\n"
-          "                  ddy    : vec3<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),   // coords
-                               b->vec3<f32>(4.f, 5.f, 6.f),   // ddx
-                               b->vec3<f32>(7.f, 8.f, 9.f));  // ddy
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGrad3dOffsetF32,
-          "textureSampleGrad(t      : texture_3d<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  ddx    : vec3<f32>,\n"
-          "                  ddy    : vec3<f32>,\n"
-          "                  offset : vec3<i32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               b->vec3<f32>(4.f, 5.f, 6.f),  // ddx
-                               b->vec3<f32>(7.f, 8.f, 9.f),  // ddy
-                               b->vec3<i32>(0, 1, 2));       // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGradCubeF32,
-          "textureSampleGrad(t      : texture_cube<f32>,\n"
-          "                  s      : sampler,\n"
-          "                  coords : vec3<f32>,\n"
-          "                  ddx    : vec3<f32>,\n"
-          "                  ddy    : vec3<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                     // t
-                               "sampler",                     // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),   // coords
-                               b->vec3<f32>(4.f, 5.f, 6.f),   // ddx
-                               b->vec3<f32>(7.f, 8.f, 9.f));  // ddy
-          },
-      },
-      {
-          ValidTextureOverload::kSampleGradCubeArrayF32,
-          "textureSampleGrad(t           : texture_cube_array<f32>,\n"
-          "                  s           : sampler,\n"
-          "                  coords      : vec3<f32>,\n"
-          "                  array_index : i32,\n"
-          "                  ddx         : vec3<f32>,\n"
-          "                  ddy         : vec3<f32>) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::SamplerKind::kSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSampleGrad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                      // t
-                               "sampler",                      // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),    // coords
-                               4,                              // array_index
-                               b->vec3<f32>(5.f, 6.f, 7.f),    // ddx
-                               b->vec3<f32>(8.f, 9.f, 10.f));  // ddy
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepth2dF32,
-          "textureSampleCompare(t         : texture_depth_2d,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec2<f32>,\n"
-          "                     depth_ref : f32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f);                    // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepth2dOffsetF32,
-          "textureSampleCompare(t         : texture_depth_2d,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec2<f32>,\n"
-          "                     depth_ref : f32,\n"
-          "                     offset    : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               3.f,                     // depth_ref
-                               b->vec2<i32>(4, 5));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepth2dArrayF32,
-          "textureSampleCompare(t           : texture_depth_2d_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec2<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               4,                       // array_index
-                               3.f);                    // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32,
-          "textureSampleCompare(t           : texture_depth_2d_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec2<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32,\n"
-          "                     offset      : vec2<i32>) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",               // t
-                               "sampler",               // s
-                               b->vec2<f32>(1.f, 2.f),  // coords
-                               4,                       // array_index
-                               3.f,                     // depth_ref
-                               b->vec2<i32>(5, 6));     // offset
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepthCubeF32,
-          "textureSampleCompare(t         : texture_depth_cube,\n"
-          "                     s         : sampler_comparison,\n"
-          "                     coords    : vec3<f32>,\n"
-          "                     depth_ref : f32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::kCube,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4.f);                         // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kSampleCompareDepthCubeArrayF32,
-          "textureSampleCompare(t           : texture_depth_cube_array,\n"
-          "                     s           : sampler_comparison,\n"
-          "                     coords      : vec3<f32>,\n"
-          "                     array_index : i32,\n"
-          "                     depth_ref   : f32) -> f32",
-          TextureKind::kDepth,
-          ast::SamplerKind::kComparisonSampler,
-          ast::TextureDimension::kCubeArray,
-          TextureDataType::kF32,
-          "textureSampleCompare",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                    // t
-                               "sampler",                    // s
-                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
-                               4,                            // array_index
-                               5.f);                         // depth_ref
-          },
-      },
-      {
-          ValidTextureOverload::kLoad1dLevelF32,
-          "textureLoad(t      : texture_1d<f32>,\n"
-          "            coords : i32,\n"
-          "            level  : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k1d,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",  // t
-                               1,          // coords
-                               3);         // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad1dLevelU32,
-          "textureLoad(t      : texture_1d<u32>,\n"
-          "            coords : i32,\n"
-          "            level  : i32) -> vec4<u32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k1d,
-          TextureDataType::kU32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",  // t
-                               1,          // coords
-                               3);         // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad1dLevelI32,
-          "textureLoad(t      : texture_1d<i32>,\n"
-          "            coords : i32,\n"
-          "            level  : i32) -> vec4<i32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k1d,
-          TextureDataType::kI32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",  // t
-                               1,          // coords
-                               3);         // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dLevelF32,
-          "textureLoad(t      : texture_2d<f32>,\n"
-          "            coords : vec2<i32>,\n"
-          "            level  : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dLevelU32,
-          "textureLoad(t      : texture_2d<u32>,\n"
-          "            coords : vec2<i32>,\n"
-          "            level  : i32) -> vec4<u32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2d,
-          TextureDataType::kU32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dLevelI32,
-          "textureLoad(t      : texture_2d<i32>,\n"
-          "            coords : vec2<i32>,\n"
-          "            level  : i32) -> vec4<i32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2d,
-          TextureDataType::kI32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dArrayLevelF32,
-          "textureLoad(t           : texture_2d_array<f32>,\n"
-          "            coords      : vec2<i32>,\n"
-          "            array_index : i32,\n"
-          "            level       : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3,                   // array_index
-                               4);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dArrayLevelU32,
-          "textureLoad(t           : texture_2d_array<u32>,\n"
-          "            coords      : vec2<i32>,\n"
-          "            array_index : i32,\n"
-          "            level       : i32) -> vec4<u32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kU32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3,                   // array_index
-                               4);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad2dArrayLevelI32,
-          "textureLoad(t           : texture_2d_array<i32>,\n"
-          "            coords      : vec2<i32>,\n"
-          "            array_index : i32,\n"
-          "            level       : i32) -> vec4<i32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kI32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3,                   // array_index
-                               4);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad3dLevelF32,
-          "textureLoad(t      : texture_3d<f32>,\n"
-          "            coords : vec3<i32>,\n"
-          "            level  : i32) -> vec4<f32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",              // t
-                               b->vec3<i32>(1, 2, 3),  // coords
-                               4);                     // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad3dLevelU32,
-          "textureLoad(t      : texture_3d<u32>,\n"
-          "            coords : vec3<i32>,\n"
-          "            level  : i32) -> vec4<u32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k3d,
-          TextureDataType::kU32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",              // t
-                               b->vec3<i32>(1, 2, 3),  // coords
-                               4);                     // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoad3dLevelI32,
-          "textureLoad(t      : texture_3d<i32>,\n"
-          "            coords : vec3<i32>,\n"
-          "            level  : i32) -> vec4<i32>",
-          TextureKind::kRegular,
-          ast::TextureDimension::k3d,
-          TextureDataType::kI32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",              // t
-                               b->vec3<i32>(1, 2, 3),  // coords
-                               4);                     // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoadMultisampled2dF32,
-          "textureLoad(t            : texture_multisampled_2d<f32>,\n"
-          "            coords       : vec2<i32>,\n"
-          "            sample_index : i32) -> vec4<f32>",
-          TextureKind::kMultisampled,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // sample_index
-          },
-      },
-      {
-          ValidTextureOverload::kLoadMultisampled2dU32,
-          "textureLoad(t            : texture_multisampled_2d<u32>,\n"
-          "            coords       : vec2<i32>,\n"
-          "            sample_index : i32) -> vec4<u32>",
-          TextureKind::kMultisampled,
-          ast::TextureDimension::k2d,
-          TextureDataType::kU32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // sample_index
-          },
-      },
-      {
-          ValidTextureOverload::kLoadMultisampled2dI32,
-          "textureLoad(t            : texture_multisampled_2d<i32>,\n"
-          "            coords       : vec2<i32>,\n"
-          "            sample_index : i32) -> vec4<i32>",
-          TextureKind::kMultisampled,
-          ast::TextureDimension::k2d,
-          TextureDataType::kI32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // sample_index
-          },
-      },
-      {
-          ValidTextureOverload::kLoadDepth2dLevelF32,
-          "textureLoad(t      : texture_depth_2d,\n"
-          "            coords : vec2<i32>,\n"
-          "            level  : i32) -> f32",
-          TextureKind::kDepth,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kLoadDepth2dArrayLevelF32,
-          "textureLoad(t           : texture_depth_2d_array,\n"
-          "            coords      : vec2<i32>,\n"
-          "            array_index : i32,\n"
-          "            level       : i32) -> f32",
-          TextureKind::kDepth,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureLoad",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3,                   // array_index
-                               4);                  // level
-          },
-      },
-      {
-          ValidTextureOverload::kStoreWO1dRgba32float,
-          "textureStore(t      : texture_storage_1d<rgba32float>,\n"
-          "             coords : i32,\n"
-          "             value  : vec4<T>)",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k1d,
-          TextureDataType::kF32,
-          "textureStore",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                          // t
-                               1,                                  // coords
-                               b->vec4<f32>(2.f, 3.f, 4.f, 5.f));  // value
-          },
-      },
-      {
-          ValidTextureOverload::kStoreWO2dRgba32float,
-          "textureStore(t      : texture_storage_2d<rgba32float>,\n"
-          "             coords : vec2<i32>,\n"
-          "             value  : vec4<T>)",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k2d,
-          TextureDataType::kF32,
-          "textureStore",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                          // t
-                               b->vec2<i32>(1, 2),                 // coords
-                               b->vec4<f32>(3.f, 4.f, 5.f, 6.f));  // value
-          },
-      },
-      {
-          ValidTextureOverload::kStoreWO2dArrayRgba32float,
-          "textureStore(t           : texture_storage_2d_array<rgba32float>,\n"
-          "             coords      : vec2<i32>,\n"
-          "             array_index : i32,\n"
-          "             value       : vec4<T>)",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k2dArray,
-          TextureDataType::kF32,
-          "textureStore",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",           // t
-                               b->vec2<i32>(1, 2),  // coords
-                               3,                   // array_index
-                               b->vec4<f32>(4.f, 5.f, 6.f, 7.f));  // value
-          },
-      },
-      {
-          ValidTextureOverload::kStoreWO3dRgba32float,
-          "textureStore(t      : texture_storage_3d<rgba32float>,\n"
-          "             coords : vec3<i32>,\n"
-          "             value  : vec4<T>)",
-          ast::Access::kWrite,
-          ast::TexelFormat::kRgba32Float,
-          ast::TextureDimension::k3d,
-          TextureDataType::kF32,
-          "textureStore",
-          [](ProgramBuilder* b) {
-            return b->ExprList("texture",                          // t
-                               b->vec3<i32>(1, 2, 3),              // coords
-                               b->vec4<f32>(4.f, 5.f, 6.f, 7.f));  // value
-          },
-      },
-  };
-}
-
-bool ReturnsVoid(ValidTextureOverload texture_overload) {
-  switch (texture_overload) {
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return true;
-    default:
-      return false;
-  }
-}
-
-}  // namespace test
-}  // namespace builtin
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builtin_texture_helper_test.h b/src/ast/builtin_texture_helper_test.h
deleted file mode 100644
index 64151c9..0000000
--- a/src/ast/builtin_texture_helper_test.h
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2020 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_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
-#define SRC_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
-
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/program_builder.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace ast {
-namespace builtin {
-namespace test {
-
-enum class TextureKind {
-  kRegular,
-  kDepth,
-  kDepthMultisampled,
-  kMultisampled,
-  kStorage
-};
-enum class TextureDataType { kF32, kU32, kI32 };
-
-std::ostream& operator<<(std::ostream& out, const TextureKind& kind);
-std::ostream& operator<<(std::ostream& out, const TextureDataType& ty);
-
-/// Non-exhaustive list of valid texture overloads
-enum class ValidTextureOverload {
-  kDimensions1d,
-  kDimensions2d,
-  kDimensions2dLevel,
-  kDimensions2dArray,
-  kDimensions2dArrayLevel,
-  kDimensions3d,
-  kDimensions3dLevel,
-  kDimensionsCube,
-  kDimensionsCubeLevel,
-  kDimensionsCubeArray,
-  kDimensionsCubeArrayLevel,
-  kDimensionsMultisampled2d,
-  kDimensionsDepth2d,
-  kDimensionsDepth2dLevel,
-  kDimensionsDepth2dArray,
-  kDimensionsDepth2dArrayLevel,
-  kDimensionsDepthCube,
-  kDimensionsDepthCubeLevel,
-  kDimensionsDepthCubeArray,
-  kDimensionsDepthCubeArrayLevel,
-  kDimensionsDepthMultisampled2d,
-  kDimensionsStorageWO1d,
-  kDimensionsStorageWO2d,
-  kDimensionsStorageWO2dArray,
-  kDimensionsStorageWO3d,
-  kGather2dF32,
-  kGather2dOffsetF32,
-  kGather2dArrayF32,
-  kGather2dArrayOffsetF32,
-  kGatherCubeF32,
-  kGatherCubeArrayF32,
-  kGatherDepth2dF32,
-  kGatherDepth2dOffsetF32,
-  kGatherDepth2dArrayF32,
-  kGatherDepth2dArrayOffsetF32,
-  kGatherDepthCubeF32,
-  kGatherDepthCubeArrayF32,
-  kGatherCompareDepth2dF32,
-  kGatherCompareDepth2dOffsetF32,
-  kGatherCompareDepth2dArrayF32,
-  kGatherCompareDepth2dArrayOffsetF32,
-  kGatherCompareDepthCubeF32,
-  kGatherCompareDepthCubeArrayF32,
-  kNumLayers2dArray,
-  kNumLayersCubeArray,
-  kNumLayersDepth2dArray,
-  kNumLayersDepthCubeArray,
-  kNumLayersStorageWO2dArray,
-  kNumLevels2d,
-  kNumLevels2dArray,
-  kNumLevels3d,
-  kNumLevelsCube,
-  kNumLevelsCubeArray,
-  kNumLevelsDepth2d,
-  kNumLevelsDepth2dArray,
-  kNumLevelsDepthCube,
-  kNumLevelsDepthCubeArray,
-  kNumSamplesMultisampled2d,
-  kNumSamplesDepthMultisampled2d,
-  kSample1dF32,
-  kSample2dF32,
-  kSample2dOffsetF32,
-  kSample2dArrayF32,
-  kSample2dArrayOffsetF32,
-  kSample3dF32,
-  kSample3dOffsetF32,
-  kSampleCubeF32,
-  kSampleCubeArrayF32,
-  kSampleDepth2dF32,
-  kSampleDepth2dOffsetF32,
-  kSampleDepth2dArrayF32,
-  kSampleDepth2dArrayOffsetF32,
-  kSampleDepthCubeF32,
-  kSampleDepthCubeArrayF32,
-  kSampleBias2dF32,
-  kSampleBias2dOffsetF32,
-  kSampleBias2dArrayF32,
-  kSampleBias2dArrayOffsetF32,
-  kSampleBias3dF32,
-  kSampleBias3dOffsetF32,
-  kSampleBiasCubeF32,
-  kSampleBiasCubeArrayF32,
-  kSampleLevel2dF32,
-  kSampleLevel2dOffsetF32,
-  kSampleLevel2dArrayF32,
-  kSampleLevel2dArrayOffsetF32,
-  kSampleLevel3dF32,
-  kSampleLevel3dOffsetF32,
-  kSampleLevelCubeF32,
-  kSampleLevelCubeArrayF32,
-  kSampleLevelDepth2dF32,
-  kSampleLevelDepth2dOffsetF32,
-  kSampleLevelDepth2dArrayF32,
-  kSampleLevelDepth2dArrayOffsetF32,
-  kSampleLevelDepthCubeF32,
-  kSampleLevelDepthCubeArrayF32,
-  kSampleGrad2dF32,
-  kSampleGrad2dOffsetF32,
-  kSampleGrad2dArrayF32,
-  kSampleGrad2dArrayOffsetF32,
-  kSampleGrad3dF32,
-  kSampleGrad3dOffsetF32,
-  kSampleGradCubeF32,
-  kSampleGradCubeArrayF32,
-  kSampleCompareDepth2dF32,
-  kSampleCompareDepth2dOffsetF32,
-  kSampleCompareDepth2dArrayF32,
-  kSampleCompareDepth2dArrayOffsetF32,
-  kSampleCompareDepthCubeF32,
-  kSampleCompareDepthCubeArrayF32,
-  kSampleCompareLevelDepth2dF32,
-  kSampleCompareLevelDepth2dOffsetF32,
-  kSampleCompareLevelDepth2dArrayF32,
-  kSampleCompareLevelDepth2dArrayOffsetF32,
-  kSampleCompareLevelDepthCubeF32,
-  kSampleCompareLevelDepthCubeArrayF32,
-  kLoad1dLevelF32,
-  kLoad1dLevelU32,
-  kLoad1dLevelI32,
-  kLoad2dLevelF32,
-  kLoad2dLevelU32,
-  kLoad2dLevelI32,
-  kLoad2dArrayLevelF32,
-  kLoad2dArrayLevelU32,
-  kLoad2dArrayLevelI32,
-  kLoad3dLevelF32,
-  kLoad3dLevelU32,
-  kLoad3dLevelI32,
-  kLoadMultisampled2dF32,
-  kLoadMultisampled2dU32,
-  kLoadMultisampled2dI32,
-  kLoadDepth2dLevelF32,
-  kLoadDepth2dArrayLevelF32,
-  kLoadDepthMultisampled2dF32,
-  kStoreWO1dRgba32float,       // Not permutated for all texel formats
-  kStoreWO2dRgba32float,       // Not permutated for all texel formats
-  kStoreWO2dArrayRgba32float,  // Not permutated for all texel formats
-  kStoreWO3dRgba32float,       // Not permutated for all texel formats
-};
-
-/// @param texture_overload the ValidTextureOverload
-/// @returns true if the ValidTextureOverload builtin returns no value.
-bool ReturnsVoid(ValidTextureOverload texture_overload);
-
-/// Describes a texture builtin overload
-struct TextureOverloadCase {
-  /// Constructor for textureSample...() functions
-  TextureOverloadCase(ValidTextureOverload,
-                      const char*,
-                      TextureKind,
-                      ast::SamplerKind,
-                      ast::TextureDimension,
-                      TextureDataType,
-                      const char*,
-                      std::function<ExpressionList(ProgramBuilder*)>);
-  /// Constructor for textureLoad() functions with non-storage textures
-  TextureOverloadCase(ValidTextureOverload,
-                      const char*,
-                      TextureKind,
-                      ast::TextureDimension,
-                      TextureDataType,
-                      const char*,
-                      std::function<ExpressionList(ProgramBuilder*)>);
-  /// Constructor for textureLoad() with storage textures
-  TextureOverloadCase(ValidTextureOverload,
-                      const char*,
-                      Access,
-                      ast::TexelFormat,
-                      ast::TextureDimension,
-                      TextureDataType,
-                      const char*,
-                      std::function<ExpressionList(ProgramBuilder*)>);
-  /// Copy constructor
-  TextureOverloadCase(const TextureOverloadCase&);
-  /// Destructor
-  ~TextureOverloadCase();
-
-  /// @return a vector containing a large number (non-exhaustive) of valid
-  /// texture overloads.
-  static std::vector<TextureOverloadCase> ValidCases();
-
-  /// @param builder the AST builder used for the test
-  /// @returns the vector component type of the texture function return value
-  const ast::Type* BuildResultVectorComponentType(
-      ProgramBuilder* builder) const;
-  /// @param builder the AST builder used for the test
-  /// @returns a variable holding the test texture, automatically registered as
-  /// a global variable.
-  const ast::Variable* BuildTextureVariable(ProgramBuilder* builder) const;
-  /// @param builder the AST builder used for the test
-  /// @returns a Variable holding the test sampler, automatically registered as
-  /// a global variable.
-  const ast::Variable* BuildSamplerVariable(ProgramBuilder* builder) const;
-
-  /// The enumerator for this overload
-  const ValidTextureOverload overload;
-  /// A human readable description of the overload
-  const char* const description;
-  /// The texture kind for the texture parameter
-  const TextureKind texture_kind;
-  /// The sampler kind for the sampler parameter
-  /// Used only when texture_kind is not kStorage
-  ast::SamplerKind const sampler_kind = ast::SamplerKind::kSampler;
-  /// The access control for the storage texture
-  /// Used only when texture_kind is kStorage
-  Access const access = Access::kReadWrite;
-  /// The image format for the storage texture
-  /// Used only when texture_kind is kStorage
-  ast::TexelFormat const texel_format = ast::TexelFormat::kNone;
-  /// The dimensions of the texture parameter
-  ast::TextureDimension const texture_dimension;
-  /// The data type of the texture parameter
-  const TextureDataType texture_data_type;
-  /// Name of the function. e.g. `textureSample`, `textureSampleGrad`, etc
-  const char* const function;
-  /// A function that builds the AST arguments for the overload
-  std::function<ExpressionList(ProgramBuilder*)> const args;
-};
-
-std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data);
-
-}  // namespace test
-}  // namespace builtin
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
diff --git a/src/ast/call_expression.cc b/src/ast/call_expression.cc
deleted file mode 100644
index c3fc629..0000000
--- a/src/ast/call_expression.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 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/ast/call_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::CallExpression);
-
-namespace tint {
-namespace ast {
-
-namespace {
-CallExpression::Target ToTarget(const IdentifierExpression* name) {
-  CallExpression::Target target;
-  target.name = name;
-  return target;
-}
-CallExpression::Target ToTarget(const Type* type) {
-  CallExpression::Target target;
-  target.type = type;
-  return target;
-}
-}  // namespace
-
-CallExpression::CallExpression(ProgramID pid,
-                               const Source& src,
-                               const IdentifierExpression* name,
-                               ExpressionList a)
-    : Base(pid, src), target(ToTarget(name)), args(a) {
-  TINT_ASSERT(AST, name);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
-  for (auto* arg : args) {
-    TINT_ASSERT(AST, arg);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
-  }
-}
-
-CallExpression::CallExpression(ProgramID pid,
-                               const Source& src,
-                               const Type* type,
-                               ExpressionList a)
-    : Base(pid, src), target(ToTarget(type)), args(a) {
-  TINT_ASSERT(AST, type);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
-  for (auto* arg : args) {
-    TINT_ASSERT(AST, arg);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
-  }
-}
-
-CallExpression::CallExpression(CallExpression&&) = default;
-
-CallExpression::~CallExpression() = default;
-
-const CallExpression* CallExpression::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto p = ctx->Clone(args);
-  return target.name
-             ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), p)
-             : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type),
-                                                p);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/call_expression.h b/src/ast/call_expression.h
deleted file mode 100644
index 25d2079..0000000
--- a/src/ast/call_expression.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 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_AST_CALL_EXPRESSION_H_
-#define SRC_AST_CALL_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declarations.
-class Type;
-class IdentifierExpression;
-
-/// A call expression - represents either a:
-/// * sem::Function
-/// * sem::Builtin
-/// * sem::TypeConstructor
-/// * sem::TypeConversion
-class CallExpression : public Castable<CallExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the call expression source
-  /// @param name the function or type name
-  /// @param args the arguments
-  CallExpression(ProgramID program_id,
-                 const Source& source,
-                 const IdentifierExpression* name,
-                 ExpressionList args);
-
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the call expression source
-  /// @param type the type
-  /// @param args the arguments
-  CallExpression(ProgramID program_id,
-                 const Source& source,
-                 const Type* type,
-                 ExpressionList args);
-
-  /// Move constructor
-  CallExpression(CallExpression&&);
-  ~CallExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const CallExpression* Clone(CloneContext* ctx) const override;
-
-  /// Target is either an identifier, or a Type.
-  /// One of these must be nullptr and the other a non-nullptr.
-  struct Target {
-    /// name is a function or builtin to call, or type name to construct or
-    /// cast-to
-    const IdentifierExpression* name = nullptr;
-    /// type to construct or cast-to
-    const Type* type = nullptr;
-  };
-
-  /// The target function
-  const Target target;
-
-  /// The arguments
-  const ExpressionList args;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_CALL_EXPRESSION_H_
diff --git a/src/ast/call_expression_test.cc b/src/ast/call_expression_test.cc
deleted file mode 100644
index ea91b25..0000000
--- a/src/ast/call_expression_test.cc
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using CallExpressionTest = TestHelper;
-
-TEST_F(CallExpressionTest, CreationIdentifier) {
-  auto* func = Expr("func");
-  ExpressionList params;
-  params.push_back(Expr("param1"));
-  params.push_back(Expr("param2"));
-
-  auto* stmt = create<CallExpression>(func, params);
-  EXPECT_EQ(stmt->target.name, func);
-  EXPECT_EQ(stmt->target.type, nullptr);
-
-  const auto& vec = stmt->args;
-  ASSERT_EQ(vec.size(), 2u);
-  EXPECT_EQ(vec[0], params[0]);
-  EXPECT_EQ(vec[1], params[1]);
-}
-
-TEST_F(CallExpressionTest, CreationIdentifier_WithSource) {
-  auto* func = Expr("func");
-  auto* stmt = create<CallExpression>(Source{{20, 2}}, func, ExpressionList{});
-  EXPECT_EQ(stmt->target.name, func);
-  EXPECT_EQ(stmt->target.type, nullptr);
-
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(CallExpressionTest, CreationType) {
-  auto* type = ty.f32();
-  ExpressionList params;
-  params.push_back(Expr("param1"));
-  params.push_back(Expr("param2"));
-
-  auto* stmt = create<CallExpression>(type, params);
-  EXPECT_EQ(stmt->target.name, nullptr);
-  EXPECT_EQ(stmt->target.type, type);
-
-  const auto& vec = stmt->args;
-  ASSERT_EQ(vec.size(), 2u);
-  EXPECT_EQ(vec[0], params[0]);
-  EXPECT_EQ(vec[1], params[1]);
-}
-
-TEST_F(CallExpressionTest, CreationType_WithSource) {
-  auto* type = ty.f32();
-  auto* stmt = create<CallExpression>(Source{{20, 2}}, type, ExpressionList{});
-  EXPECT_EQ(stmt->target.name, nullptr);
-  EXPECT_EQ(stmt->target.type, type);
-
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(CallExpressionTest, IsCall) {
-  auto* func = Expr("func");
-  auto* stmt = create<CallExpression>(func, ExpressionList{});
-  EXPECT_TRUE(stmt->Is<CallExpression>());
-}
-
-TEST_F(CallExpressionTest, Assert_Null_Identifier) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<CallExpression>(static_cast<IdentifierExpression*>(nullptr),
-                                 ExpressionList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallExpressionTest, Assert_Null_Type) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<CallExpression>(static_cast<Type*>(nullptr), ExpressionList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallExpressionTest, Assert_Null_Param) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        ExpressionList params;
-        params.push_back(b.Expr("param1"));
-        params.push_back(nullptr);
-        params.push_back(b.Expr("param2"));
-        b.create<CallExpression>(b.Expr("func"), params);
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallExpressionTest, Assert_DifferentProgramID_Identifier) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CallExpression>(b2.Expr("func"), ExpressionList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallExpressionTest, Assert_DifferentProgramID_Type) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CallExpression>(b2.ty.f32(), ExpressionList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallExpressionTest, Assert_DifferentProgramID_Param) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CallExpression>(b1.Expr("func"),
-                                  ExpressionList{b2.Expr("param1")});
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/call_statement.cc b/src/ast/call_statement.cc
deleted file mode 100644
index f8a24b8..0000000
--- a/src/ast/call_statement.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::CallStatement);
-
-namespace tint {
-namespace ast {
-
-CallStatement::CallStatement(ProgramID pid,
-                             const Source& src,
-                             const CallExpression* call)
-    : Base(pid, src), expr(call) {
-  TINT_ASSERT(AST, expr);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
-}
-
-CallStatement::CallStatement(CallStatement&&) = default;
-
-CallStatement::~CallStatement() = default;
-
-const CallStatement* CallStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* call = ctx->Clone(expr);
-  return ctx->dst->create<CallStatement>(src, call);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/call_statement.h b/src/ast/call_statement.h
deleted file mode 100644
index 946e7ea..0000000
--- a/src/ast/call_statement.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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_AST_CALL_STATEMENT_H_
-#define SRC_AST_CALL_STATEMENT_H_
-
-#include "src/ast/call_expression.h"
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// A call expression
-class CallStatement : public Castable<CallStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node for the statement
-  /// @param call the function
-  CallStatement(ProgramID pid, const Source& src, const CallExpression* call);
-  /// Move constructor
-  CallStatement(CallStatement&&);
-  ~CallStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const CallStatement* Clone(CloneContext* ctx) const override;
-
-  /// The call expression
-  const CallExpression* const expr;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_CALL_STATEMENT_H_
diff --git a/src/ast/call_statement_test.cc b/src/ast/call_statement_test.cc
deleted file mode 100644
index b5bd7e9..0000000
--- a/src/ast/call_statement_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using CallStatementTest = TestHelper;
-
-TEST_F(CallStatementTest, Creation) {
-  auto* expr = create<CallExpression>(Expr("func"), ExpressionList{});
-
-  auto* c = create<CallStatement>(expr);
-  EXPECT_EQ(c->expr, expr);
-}
-
-TEST_F(CallStatementTest, IsCall) {
-  auto* c = create<CallStatement>(Call("f"));
-  EXPECT_TRUE(c->Is<CallStatement>());
-}
-
-TEST_F(CallStatementTest, Assert_Null_Call) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<CallStatement>(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(CallStatementTest, Assert_DifferentProgramID_Call) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CallStatement>(
-            b2.create<CallExpression>(b2.Expr("func"), ExpressionList{}));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/case_statement.cc b/src/ast/case_statement.cc
deleted file mode 100644
index 580f477..0000000
--- a/src/ast/case_statement.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/case_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::CaseStatement);
-
-namespace tint {
-namespace ast {
-
-CaseStatement::CaseStatement(ProgramID pid,
-                             const Source& src,
-                             CaseSelectorList s,
-                             const BlockStatement* b)
-    : Base(pid, src), selectors(s), body(b) {
-  TINT_ASSERT(AST, body);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-  for (auto* selector : selectors) {
-    TINT_ASSERT(AST, selector);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, selector, program_id);
-  }
-}
-
-CaseStatement::CaseStatement(CaseStatement&&) = default;
-
-CaseStatement::~CaseStatement() = default;
-
-const CaseStatement* CaseStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto sel = ctx->Clone(selectors);
-  auto* b = ctx->Clone(body);
-  return ctx->dst->create<CaseStatement>(src, sel, b);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/case_statement.h b/src/ast/case_statement.h
deleted file mode 100644
index c1e5688..0000000
--- a/src/ast/case_statement.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 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_AST_CASE_STATEMENT_H_
-#define SRC_AST_CASE_STATEMENT_H_
-
-#include <vector>
-
-#include "src/ast/block_statement.h"
-#include "src/ast/int_literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A list of case literals
-using CaseSelectorList = std::vector<const IntLiteralExpression*>;
-
-/// A case statement
-class CaseStatement : public Castable<CaseStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param selectors the case selectors
-  /// @param body the case body
-  CaseStatement(ProgramID pid,
-                const Source& src,
-                CaseSelectorList selectors,
-                const BlockStatement* body);
-  /// Move constructor
-  CaseStatement(CaseStatement&&);
-  ~CaseStatement() override;
-
-  /// @returns true if this is a default statement
-  bool IsDefault() const { return selectors.empty(); }
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const CaseStatement* Clone(CloneContext* ctx) const override;
-
-  /// The case selectors, empty if none set
-  const CaseSelectorList selectors;
-
-  /// The case body
-  const BlockStatement* const body;
-};
-
-/// A list of case statements
-using CaseStatementList = std::vector<const CaseStatement*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_CASE_STATEMENT_H_
diff --git a/src/ast/case_statement_test.cc b/src/ast/case_statement_test.cc
deleted file mode 100644
index a46161b..0000000
--- a/src/ast/case_statement_test.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2020 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/ast/case_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using CaseStatementTest = TestHelper;
-
-TEST_F(CaseStatementTest, Creation_i32) {
-  CaseSelectorList b;
-  auto* selector = create<SintLiteralExpression>(2);
-  b.push_back(selector);
-
-  auto* discard = create<DiscardStatement>();
-  auto* body = create<BlockStatement>(StatementList{discard});
-
-  auto* c = create<CaseStatement>(b, body);
-  ASSERT_EQ(c->selectors.size(), 1u);
-  EXPECT_EQ(c->selectors[0], selector);
-  ASSERT_EQ(c->body->statements.size(), 1u);
-  EXPECT_EQ(c->body->statements[0], discard);
-}
-
-TEST_F(CaseStatementTest, Creation_u32) {
-  CaseSelectorList b;
-  auto* selector = create<UintLiteralExpression>(2u);
-  b.push_back(selector);
-
-  auto* discard = create<DiscardStatement>();
-  auto* body = create<BlockStatement>(StatementList{discard});
-
-  auto* c = create<CaseStatement>(b, body);
-  ASSERT_EQ(c->selectors.size(), 1u);
-  EXPECT_EQ(c->selectors[0], selector);
-  ASSERT_EQ(c->body->statements.size(), 1u);
-  EXPECT_EQ(c->body->statements[0], discard);
-}
-
-TEST_F(CaseStatementTest, Creation_WithSource) {
-  CaseSelectorList b;
-  b.push_back(create<SintLiteralExpression>(2));
-
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-  auto* c = create<CaseStatement>(Source{Source::Location{20, 2}}, b, body);
-  auto src = c->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(CaseStatementTest, IsDefault_WithoutSelectors) {
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-  auto* c = create<CaseStatement>(CaseSelectorList{}, body);
-  EXPECT_TRUE(c->IsDefault());
-}
-
-TEST_F(CaseStatementTest, IsDefault_WithSelectors) {
-  CaseSelectorList b;
-  b.push_back(create<SintLiteralExpression>(2));
-
-  auto* c = create<CaseStatement>(b, create<BlockStatement>(StatementList{}));
-  EXPECT_FALSE(c->IsDefault());
-}
-
-TEST_F(CaseStatementTest, IsCase) {
-  auto* c = create<CaseStatement>(CaseSelectorList{},
-                                  create<BlockStatement>(StatementList{}));
-  EXPECT_TRUE(c->Is<CaseStatement>());
-}
-
-TEST_F(CaseStatementTest, Assert_Null_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<CaseStatement>(CaseSelectorList{}, nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(CaseStatementTest, Assert_Null_Selector) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<CaseStatement>(CaseSelectorList{nullptr},
-                                b.create<BlockStatement>(StatementList{}));
-      },
-      "internal compiler error");
-}
-
-TEST_F(CaseStatementTest, Assert_DifferentProgramID_Call) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CaseStatement>(CaseSelectorList{},
-                                 b2.create<BlockStatement>(StatementList{}));
-      },
-      "internal compiler error");
-}
-
-TEST_F(CaseStatementTest, Assert_DifferentProgramID_Selector) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<CaseStatement>(
-            CaseSelectorList{b2.create<SintLiteralExpression>(2)},
-            b1.create<BlockStatement>(StatementList{}));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/continue_statement.cc b/src/ast/continue_statement.cc
deleted file mode 100644
index 9337f7b..0000000
--- a/src/ast/continue_statement.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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/ast/continue_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::ContinueStatement);
-
-namespace tint {
-namespace ast {
-
-ContinueStatement::ContinueStatement(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-ContinueStatement::ContinueStatement(ContinueStatement&&) = default;
-
-ContinueStatement::~ContinueStatement() = default;
-
-const ContinueStatement* ContinueStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<ContinueStatement>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/continue_statement.h b/src/ast/continue_statement.h
deleted file mode 100644
index 580c630..0000000
--- a/src/ast/continue_statement.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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_AST_CONTINUE_STATEMENT_H_
-#define SRC_AST_CONTINUE_STATEMENT_H_
-
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// An continue statement
-class ContinueStatement : public Castable<ContinueStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  ContinueStatement(ProgramID pid, const Source& src);
-  /// Move constructor
-  ContinueStatement(ContinueStatement&&);
-  ~ContinueStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const ContinueStatement* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_CONTINUE_STATEMENT_H_
diff --git a/src/ast/continue_statement_test.cc b/src/ast/continue_statement_test.cc
deleted file mode 100644
index 1d3b490..0000000
--- a/src/ast/continue_statement_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/ast/continue_statement.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ContinueStatementTest = TestHelper;
-
-TEST_F(ContinueStatementTest, Creation_WithSource) {
-  auto* stmt = create<ContinueStatement>(Source{Source::Location{20, 2}});
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(ContinueStatementTest, IsContinue) {
-  auto* stmt = create<ContinueStatement>();
-  EXPECT_TRUE(stmt->Is<ContinueStatement>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/depth_multisampled_texture.cc b/src/ast/depth_multisampled_texture.cc
deleted file mode 100644
index c870585..0000000
--- a/src/ast/depth_multisampled_texture.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/depth_multisampled_texture.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthMultisampledTexture);
-
-namespace tint {
-namespace ast {
-namespace {
-
-bool IsValidDepthDimension(TextureDimension dim) {
-  return dim == TextureDimension::k2d;
-}
-
-}  // namespace
-
-DepthMultisampledTexture::DepthMultisampledTexture(ProgramID pid,
-                                                   const Source& src,
-                                                   TextureDimension d)
-    : Base(pid, src, d) {
-  TINT_ASSERT(AST, IsValidDepthDimension(dim));
-}
-
-DepthMultisampledTexture::DepthMultisampledTexture(DepthMultisampledTexture&&) =
-    default;
-
-DepthMultisampledTexture::~DepthMultisampledTexture() = default;
-
-std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_depth_multisampled_" << dim;
-  return out.str();
-}
-
-const DepthMultisampledTexture* DepthMultisampledTexture::Clone(
-    CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<DepthMultisampledTexture>(src, dim);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/depth_multisampled_texture.h b/src/ast/depth_multisampled_texture.h
deleted file mode 100644
index 4797e03..0000000
--- a/src/ast/depth_multisampled_texture.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
-#define SRC_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// A multisampled depth texture type.
-class DepthMultisampledTexture
-    : public Castable<DepthMultisampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  DepthMultisampledTexture(ProgramID pid,
-                           const Source& src,
-                           TextureDimension dim);
-  /// Move constructor
-  DepthMultisampledTexture(DepthMultisampledTexture&&);
-  ~DepthMultisampledTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const DepthMultisampledTexture* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
diff --git a/src/ast/depth_multisampled_texture_test.cc b/src/ast/depth_multisampled_texture_test.cc
deleted file mode 100644
index b75d2a6..0000000
--- a/src/ast/depth_multisampled_texture_test.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/depth_multisampled_texture.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstDepthMultisampledTextureTest = TestHelper;
-
-TEST_F(AstDepthMultisampledTextureTest, Dim) {
-  auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d);
-  EXPECT_EQ(d->dim, TextureDimension::k2d);
-}
-
-TEST_F(AstDepthMultisampledTextureTest, FriendlyName) {
-  auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d);
-  EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_multisampled_2d");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/depth_texture.cc b/src/ast/depth_texture.cc
deleted file mode 100644
index 56f222a..0000000
--- a/src/ast/depth_texture.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/ast/depth_texture.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthTexture);
-
-namespace tint {
-namespace ast {
-namespace {
-
-bool IsValidDepthDimension(TextureDimension dim) {
-  return dim == TextureDimension::k2d || dim == TextureDimension::k2dArray ||
-         dim == TextureDimension::kCube || dim == TextureDimension::kCubeArray;
-}
-
-}  // namespace
-
-DepthTexture::DepthTexture(ProgramID pid, const Source& src, TextureDimension d)
-    : Base(pid, src, d) {
-  TINT_ASSERT(AST, IsValidDepthDimension(dim));
-}
-
-DepthTexture::DepthTexture(DepthTexture&&) = default;
-
-DepthTexture::~DepthTexture() = default;
-
-std::string DepthTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_depth_" << dim;
-  return out.str();
-}
-
-const DepthTexture* DepthTexture::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<DepthTexture>(src, dim);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/depth_texture.h b/src/ast/depth_texture.h
deleted file mode 100644
index 530dea2..0000000
--- a/src/ast/depth_texture.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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_AST_DEPTH_TEXTURE_H_
-#define SRC_AST_DEPTH_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// A depth texture type.
-class DepthTexture : public Castable<DepthTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  DepthTexture(ProgramID pid, const Source& src, TextureDimension dim);
-  /// Move constructor
-  DepthTexture(DepthTexture&&);
-  ~DepthTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const DepthTexture* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_DEPTH_TEXTURE_H_
diff --git a/src/ast/depth_texture_test.cc b/src/ast/depth_texture_test.cc
deleted file mode 100644
index 2a0ec2b..0000000
--- a/src/ast/depth_texture_test.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/depth_texture.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstDepthTextureTest = TestHelper;
-
-TEST_F(AstDepthTextureTest, IsTexture) {
-  Texture* ty = create<DepthTexture>(TextureDimension::kCube);
-  EXPECT_TRUE(ty->Is<DepthTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstDepthTextureTest, Dim) {
-  auto* d = create<DepthTexture>(TextureDimension::kCube);
-  EXPECT_EQ(d->dim, TextureDimension::kCube);
-}
-
-TEST_F(AstDepthTextureTest, FriendlyName) {
-  auto* d = create<DepthTexture>(TextureDimension::kCube);
-  EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_cube");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/disable_validation_attribute.cc b/src/ast/disable_validation_attribute.cc
deleted file mode 100644
index 06382db..0000000
--- a/src/ast/disable_validation_attribute.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/clone_context.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::DisableValidationAttribute);
-
-namespace tint {
-namespace ast {
-
-DisableValidationAttribute::DisableValidationAttribute(ProgramID pid,
-                                                       DisabledValidation val)
-    : Base(pid), validation(val) {}
-
-DisableValidationAttribute::~DisableValidationAttribute() = default;
-
-std::string DisableValidationAttribute::InternalName() const {
-  switch (validation) {
-    case DisabledValidation::kFunctionHasNoBody:
-      return "disable_validation__function_has_no_body";
-    case DisabledValidation::kBindingPointCollision:
-      return "disable_validation__binding_point_collision";
-    case DisabledValidation::kIgnoreStorageClass:
-      return "disable_validation__ignore_storage_class";
-    case DisabledValidation::kEntryPointParameter:
-      return "disable_validation__entry_point_parameter";
-    case DisabledValidation::kIgnoreConstructibleFunctionParameter:
-      return "disable_validation__ignore_constructible_function_parameter";
-    case DisabledValidation::kIgnoreStrideAttribute:
-      return "disable_validation__ignore_stride";
-    case DisabledValidation::kIgnoreInvalidPointerArgument:
-      return "disable_validation__ignore_invalid_pointer_argument";
-  }
-  return "<invalid>";
-}
-
-const DisableValidationAttribute* DisableValidationAttribute::Clone(
-    CloneContext* ctx) const {
-  return ctx->dst->ASTNodes().Create<DisableValidationAttribute>(ctx->dst->ID(),
-                                                                 validation);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/disable_validation_attribute.h b/src/ast/disable_validation_attribute.h
deleted file mode 100644
index 2f50ee80..0000000
--- a/src/ast/disable_validation_attribute.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
-#define SRC_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/internal_attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// Enumerator of validation features that can be disabled with a
-/// DisableValidationAttribute attribute.
-enum class DisabledValidation {
-  /// When applied to a function, the validator will not complain there is no
-  /// body to a function.
-  kFunctionHasNoBody,
-  /// When applied to a module-scoped variable, the validator will not complain
-  /// if two resource variables have the same binding points.
-  kBindingPointCollision,
-  /// When applied to a variable, the validator will not complain about the
-  /// declared storage class.
-  kIgnoreStorageClass,
-  /// When applied to an entry-point function parameter, the validator will not
-  /// check for entry IO attributes.
-  kEntryPointParameter,
-  /// When applied to a function parameter, the validator will not
-  /// check if parameter type is constructible
-  kIgnoreConstructibleFunctionParameter,
-  /// When applied to a member attribute, a stride attribute may be applied to
-  /// non-array types.
-  kIgnoreStrideAttribute,
-  /// When applied to a pointer function parameter, the validator will not
-  /// require a function call argument passed for that parameter to have a
-  /// certain form.
-  kIgnoreInvalidPointerArgument,
-};
-
-/// An internal attribute used to tell the validator to ignore specific
-/// violations. Typically generated by transforms that need to produce ASTs that
-/// would otherwise cause validation errors.
-class DisableValidationAttribute
-    : public Castable<DisableValidationAttribute, InternalAttribute> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param validation the validation to disable
-  explicit DisableValidationAttribute(ProgramID program_id,
-                                      DisabledValidation validation);
-
-  /// Destructor
-  ~DisableValidationAttribute() override;
-
-  /// @return a short description of the internal attribute which will be
-  /// displayed in WGSL as `@internal(<name>)` (but is not parsable).
-  std::string InternalName() const override;
-
-  /// Performs a deep clone of this object using the CloneContext `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned object
-  const DisableValidationAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The validation that this attribute disables
-  const DisabledValidation validation;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
diff --git a/src/ast/discard_statement.cc b/src/ast/discard_statement.cc
deleted file mode 100644
index c50b989..0000000
--- a/src/ast/discard_statement.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::DiscardStatement);
-
-namespace tint {
-namespace ast {
-
-DiscardStatement::DiscardStatement(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-DiscardStatement::DiscardStatement(DiscardStatement&&) = default;
-
-DiscardStatement::~DiscardStatement() = default;
-
-const DiscardStatement* DiscardStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<DiscardStatement>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/discard_statement.h b/src/ast/discard_statement.h
deleted file mode 100644
index c670656..0000000
--- a/src/ast/discard_statement.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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_AST_DISCARD_STATEMENT_H_
-#define SRC_AST_DISCARD_STATEMENT_H_
-
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// A discard statement
-class DiscardStatement : public Castable<DiscardStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  DiscardStatement(ProgramID pid, const Source& src);
-  /// Move constructor
-  DiscardStatement(DiscardStatement&&);
-  ~DiscardStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const DiscardStatement* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_DISCARD_STATEMENT_H_
diff --git a/src/ast/discard_statement_test.cc b/src/ast/discard_statement_test.cc
deleted file mode 100644
index b83e69b..0000000
--- a/src/ast/discard_statement_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using DiscardStatementTest = TestHelper;
-
-TEST_F(DiscardStatementTest, Creation) {
-  auto* stmt = create<DiscardStatement>();
-  EXPECT_EQ(stmt->source.range.begin.line, 0u);
-  EXPECT_EQ(stmt->source.range.begin.column, 0u);
-  EXPECT_EQ(stmt->source.range.end.line, 0u);
-  EXPECT_EQ(stmt->source.range.end.column, 0u);
-}
-
-TEST_F(DiscardStatementTest, Creation_WithSource) {
-  auto* stmt = create<DiscardStatement>(
-      Source{Source::Range{Source::Location{20, 2}, Source::Location{20, 5}}});
-  EXPECT_EQ(stmt->source.range.begin.line, 20u);
-  EXPECT_EQ(stmt->source.range.begin.column, 2u);
-  EXPECT_EQ(stmt->source.range.end.line, 20u);
-  EXPECT_EQ(stmt->source.range.end.column, 5u);
-}
-
-TEST_F(DiscardStatementTest, IsDiscard) {
-  auto* stmt = create<DiscardStatement>();
-  EXPECT_TRUE(stmt->Is<DiscardStatement>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/else_statement.cc b/src/ast/else_statement.cc
deleted file mode 100644
index 02ed656..0000000
--- a/src/ast/else_statement.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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/ast/else_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::ElseStatement);
-
-namespace tint {
-namespace ast {
-
-ElseStatement::ElseStatement(ProgramID pid,
-                             const Source& src,
-                             const Expression* cond,
-                             const BlockStatement* b)
-    : Base(pid, src), condition(cond), body(b) {
-  TINT_ASSERT(AST, body);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
-}
-
-ElseStatement::ElseStatement(ElseStatement&&) = default;
-
-ElseStatement::~ElseStatement() = default;
-
-const ElseStatement* ElseStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* cond = ctx->Clone(condition);
-  auto* b = ctx->Clone(body);
-  return ctx->dst->create<ElseStatement>(src, cond, b);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/else_statement.h b/src/ast/else_statement.h
deleted file mode 100644
index 53a4ba4..0000000
--- a/src/ast/else_statement.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 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_AST_ELSE_STATEMENT_H_
-#define SRC_AST_ELSE_STATEMENT_H_
-
-#include <vector>
-
-#include "src/ast/block_statement.h"
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// An else statement
-class ElseStatement : public Castable<ElseStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param condition the else condition
-  /// @param body the else body
-  ElseStatement(ProgramID pid,
-                const Source& src,
-                const Expression* condition,
-                const BlockStatement* body);
-  /// Move constructor
-  ElseStatement(ElseStatement&&);
-  ~ElseStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const ElseStatement* Clone(CloneContext* ctx) const override;
-
-  /// The else condition or nullptr if none set
-  const Expression* const condition;
-
-  /// The else body
-  const BlockStatement* const body;
-};
-
-/// A list of else statements
-using ElseStatementList = std::vector<const ElseStatement*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ELSE_STATEMENT_H_
diff --git a/src/ast/else_statement_test.cc b/src/ast/else_statement_test.cc
deleted file mode 100644
index 11c4828..0000000
--- a/src/ast/else_statement_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ElseStatementTest = TestHelper;
-
-TEST_F(ElseStatementTest, Creation) {
-  auto* cond = Expr(true);
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-  auto* discard = body->statements[0];
-
-  auto* e = create<ElseStatement>(cond, body);
-  EXPECT_EQ(e->condition, cond);
-  ASSERT_EQ(e->body->statements.size(), 1u);
-  EXPECT_EQ(e->body->statements[0], discard);
-}
-
-TEST_F(ElseStatementTest, Creation_WithSource) {
-  auto* e = create<ElseStatement>(Source{Source::Location{20, 2}}, Expr(true),
-                                  Block());
-  auto src = e->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(ElseStatementTest, IsElse) {
-  auto* e = create<ElseStatement>(nullptr, Block());
-  EXPECT_TRUE(e->Is<ElseStatement>());
-}
-
-TEST_F(ElseStatementTest, HasCondition) {
-  auto* cond = Expr(true);
-  auto* e = create<ElseStatement>(cond, Block());
-  EXPECT_TRUE(e->condition);
-}
-
-TEST_F(ElseStatementTest, HasContition_NullCondition) {
-  auto* e = create<ElseStatement>(nullptr, Block());
-  EXPECT_FALSE(e->condition);
-}
-
-TEST_F(ElseStatementTest, Assert_Null_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<ElseStatement>(b.Expr(true), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ElseStatementTest, Assert_DifferentProgramID_Condition) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<ElseStatement>(b2.Expr(true), b1.Block());
-      },
-      "internal compiler error");
-}
-
-TEST_F(ElseStatementTest, Assert_DifferentProgramID_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<ElseStatement>(b1.Expr(true), b2.Block());
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/expression.cc b/src/ast/expression.cc
deleted file mode 100644
index 304bc48..0000000
--- a/src/ast/expression.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/expression.h"
-
-#include "src/sem/expression.h"
-#include "src/sem/info.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Expression);
-
-namespace tint {
-namespace ast {
-
-Expression::Expression(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Expression::Expression(Expression&&) = default;
-
-Expression::~Expression() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/expression.h b/src/ast/expression.h
deleted file mode 100644
index 408c55a..0000000
--- a/src/ast/expression.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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_AST_EXPRESSION_H_
-#define SRC_AST_EXPRESSION_H_
-
-#include <string>
-#include <vector>
-
-#include "src/ast/node.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace ast {
-
-/// Base expression class
-class Expression : public Castable<Expression, Node> {
- public:
-  ~Expression() override;
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Expression(ProgramID pid, const Source& src);
-  /// Move constructor
-  Expression(Expression&&);
-};
-
-/// A list of expressions
-using ExpressionList = std::vector<const Expression*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_EXPRESSION_H_
diff --git a/src/ast/external_texture.cc b/src/ast/external_texture.cc
deleted file mode 100644
index ded9580..0000000
--- a/src/ast/external_texture.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/external_texture.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::ExternalTexture);
-
-namespace tint {
-namespace ast {
-
-// ExternalTexture::ExternalTexture() : Base(ast::TextureDimension::k2d) {}
-ExternalTexture::ExternalTexture(ProgramID pid, const Source& src)
-    : Base(pid, src, ast::TextureDimension::k2d) {}
-
-ExternalTexture::ExternalTexture(ExternalTexture&&) = default;
-
-ExternalTexture::~ExternalTexture() = default;
-
-std::string ExternalTexture::FriendlyName(const SymbolTable&) const {
-  return "texture_external";
-}
-
-const ExternalTexture* ExternalTexture::Clone(CloneContext* ctx) const {
-  return ctx->dst->create<ExternalTexture>();
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/external_texture.h b/src/ast/external_texture.h
deleted file mode 100644
index 734ba8e..0000000
--- a/src/ast/external_texture.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_EXTERNAL_TEXTURE_H_
-#define SRC_AST_EXTERNAL_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// An external texture type
-class ExternalTexture : public Castable<ExternalTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  ExternalTexture(ProgramID pid, const Source& src);
-
-  /// Move constructor
-  ExternalTexture(ExternalTexture&&);
-  ~ExternalTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const ExternalTexture* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_EXTERNAL_TEXTURE_H_
diff --git a/src/ast/external_texture_test.cc b/src/ast/external_texture_test.cc
deleted file mode 100644
index a9a80fb..0000000
--- a/src/ast/external_texture_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/external_texture.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstExternalTextureTest = TestHelper;
-
-TEST_F(AstExternalTextureTest, IsTexture) {
-  Texture* ty = create<ExternalTexture>();
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_TRUE(ty->Is<ExternalTexture>());
-  EXPECT_FALSE(ty->Is<MultisampledTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstExternalTextureTest, Dim) {
-  auto* ty = create<ExternalTexture>();
-  EXPECT_EQ(ty->dim, ast::TextureDimension::k2d);
-}
-
-TEST_F(AstExternalTextureTest, FriendlyName) {
-  auto* ty = create<ExternalTexture>();
-  EXPECT_EQ(ty->FriendlyName(Symbols()), "texture_external");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/f32.cc b/src/ast/f32.cc
deleted file mode 100644
index b455036..0000000
--- a/src/ast/f32.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/ast/f32.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::F32);
-
-namespace tint {
-namespace ast {
-
-F32::F32(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-F32::F32(F32&&) = default;
-
-F32::~F32() = default;
-
-std::string F32::FriendlyName(const SymbolTable&) const {
-  return "f32";
-}
-
-const F32* F32::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<F32>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/f32.h b/src/ast/f32.h
deleted file mode 100644
index 4643edc..0000000
--- a/src/ast/f32.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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_AST_F32_H_
-#define SRC_AST_F32_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A float 32 type
-class F32 : public Castable<F32, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  F32(ProgramID pid, const Source& src);
-  /// Move constructor
-  F32(F32&&);
-  ~F32() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const F32* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_F32_H_
diff --git a/src/ast/f32_test.cc b/src/ast/f32_test.cc
deleted file mode 100644
index f8ea6b8..0000000
--- a/src/ast/f32_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/f32.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstF32Test = TestHelper;
-
-TEST_F(AstF32Test, FriendlyName) {
-  auto* f = create<F32>();
-  EXPECT_EQ(f->FriendlyName(Symbols()), "f32");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/fallthrough_statement.cc b/src/ast/fallthrough_statement.cc
deleted file mode 100644
index 316405c..0000000
--- a/src/ast/fallthrough_statement.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::FallthroughStatement);
-
-namespace tint {
-namespace ast {
-
-FallthroughStatement::FallthroughStatement(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-FallthroughStatement::FallthroughStatement(FallthroughStatement&&) = default;
-
-FallthroughStatement::~FallthroughStatement() = default;
-
-const FallthroughStatement* FallthroughStatement::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<FallthroughStatement>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/fallthrough_statement.h b/src/ast/fallthrough_statement.h
deleted file mode 100644
index e715710..0000000
--- a/src/ast/fallthrough_statement.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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_AST_FALLTHROUGH_STATEMENT_H_
-#define SRC_AST_FALLTHROUGH_STATEMENT_H_
-
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// An fallthrough statement
-class FallthroughStatement : public Castable<FallthroughStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  FallthroughStatement(ProgramID pid, const Source& src);
-  /// Move constructor
-  FallthroughStatement(FallthroughStatement&&);
-  ~FallthroughStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const FallthroughStatement* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_FALLTHROUGH_STATEMENT_H_
diff --git a/src/ast/fallthrough_statement_test.cc b/src/ast/fallthrough_statement_test.cc
deleted file mode 100644
index 95f136e..0000000
--- a/src/ast/fallthrough_statement_test.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using FallthroughStatementTest = TestHelper;
-
-TEST_F(FallthroughStatementTest, Creation) {
-  auto* stmt = create<FallthroughStatement>();
-  EXPECT_EQ(stmt->source.range.begin.line, 0u);
-  EXPECT_EQ(stmt->source.range.begin.column, 0u);
-  EXPECT_EQ(stmt->source.range.end.line, 0u);
-  EXPECT_EQ(stmt->source.range.end.column, 0u);
-}
-
-TEST_F(FallthroughStatementTest, Creation_WithSource) {
-  auto* stmt = create<FallthroughStatement>(Source{Source::Location{20, 2}});
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(FallthroughStatementTest, IsFallthrough) {
-  auto* stmt = create<FallthroughStatement>();
-  EXPECT_TRUE(stmt->Is<FallthroughStatement>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/float_literal_expression.cc b/src/ast/float_literal_expression.cc
deleted file mode 100644
index 4a1f45b..0000000
--- a/src/ast/float_literal_expression.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/ast/float_literal_expression.h"
-
-#include <limits>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::FloatLiteralExpression);
-
-namespace tint {
-namespace ast {
-
-FloatLiteralExpression::FloatLiteralExpression(ProgramID pid,
-                                               const Source& src,
-                                               float val)
-    : Base(pid, src), value(val) {}
-
-FloatLiteralExpression::~FloatLiteralExpression() = default;
-
-const FloatLiteralExpression* FloatLiteralExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<FloatLiteralExpression>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/float_literal_expression.h b/src/ast/float_literal_expression.h
deleted file mode 100644
index 1765d53..0000000
--- a/src/ast/float_literal_expression.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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_AST_FLOAT_LITERAL_EXPRESSION_H_
-#define SRC_AST_FLOAT_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/ast/literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A float literal
-class FloatLiteralExpression
-    : public Castable<FloatLiteralExpression, LiteralExpression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the float literals value
-  FloatLiteralExpression(ProgramID pid, const Source& src, float value);
-  ~FloatLiteralExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const FloatLiteralExpression* Clone(CloneContext* ctx) const override;
-
-  /// The float literal value
-  const float value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_FLOAT_LITERAL_EXPRESSION_H_
diff --git a/src/ast/float_literal_expression_test.cc b/src/ast/float_literal_expression_test.cc
deleted file mode 100644
index cd1e445..0000000
--- a/src/ast/float_literal_expression_test.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using FloatLiteralExpressionTest = TestHelper;
-
-TEST_F(FloatLiteralExpressionTest, Value) {
-  auto* f = create<FloatLiteralExpression>(47.2f);
-  ASSERT_TRUE(f->Is<FloatLiteralExpression>());
-  EXPECT_EQ(f->value, 47.2f);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/for_loop_statement.cc b/src/ast/for_loop_statement.cc
deleted file mode 100644
index ec54edf..0000000
--- a/src/ast/for_loop_statement.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/for_loop_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::ForLoopStatement);
-
-namespace tint {
-namespace ast {
-
-ForLoopStatement::ForLoopStatement(ProgramID pid,
-                                   const Source& src,
-                                   const Statement* init,
-                                   const Expression* cond,
-                                   const Statement* cont,
-                                   const BlockStatement* b)
-    : Base(pid, src),
-      initializer(init),
-      condition(cond),
-      continuing(cont),
-      body(b) {
-  TINT_ASSERT(AST, body);
-
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-}
-
-ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default;
-
-ForLoopStatement::~ForLoopStatement() = default;
-
-const ForLoopStatement* ForLoopStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-
-  auto* init = ctx->Clone(initializer);
-  auto* cond = ctx->Clone(condition);
-  auto* cont = ctx->Clone(continuing);
-  auto* b = ctx->Clone(body);
-  return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/for_loop_statement.h b/src/ast/for_loop_statement.h
deleted file mode 100644
index 5684c12..0000000
--- a/src/ast/for_loop_statement.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_FOR_LOOP_STATEMENT_H_
-#define SRC_AST_FOR_LOOP_STATEMENT_H_
-
-#include "src/ast/block_statement.h"
-
-namespace tint {
-namespace ast {
-
-class Expression;
-
-/// A for loop statement
-class ForLoopStatement : public Castable<ForLoopStatement, Statement> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the for loop statement source
-  /// @param initializer the optional loop initializer statement
-  /// @param condition the optional loop condition expression
-  /// @param continuing the optional continuing statement
-  /// @param body the loop body
-  ForLoopStatement(ProgramID program_id,
-                   Source const& source,
-                   const Statement* initializer,
-                   const Expression* condition,
-                   const Statement* continuing,
-                   const BlockStatement* body);
-  /// Move constructor
-  ForLoopStatement(ForLoopStatement&&);
-  ~ForLoopStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const ForLoopStatement* Clone(CloneContext* ctx) const override;
-
-  /// The initializer statement
-  const Statement* const initializer;
-
-  /// The condition expression
-  const Expression* const condition;
-
-  /// The continuing statement
-  const Statement* const continuing;
-
-  /// The loop body block
-  const BlockStatement* const body;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_FOR_LOOP_STATEMENT_H_
diff --git a/src/ast/for_loop_statement_test.cc b/src/ast/for_loop_statement_test.cc
deleted file mode 100644
index 73f70fa..0000000
--- a/src/ast/for_loop_statement_test.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ForLoopStatementTest = TestHelper;
-
-TEST_F(ForLoopStatementTest, Creation) {
-  auto* init = Decl(Var("i", ty.u32()));
-  auto* cond =
-      create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5u));
-  auto* cont = Assign("i", Add("i", 1));
-  auto* body = Block(Return());
-  auto* l = For(init, cond, cont, body);
-
-  EXPECT_EQ(l->initializer, init);
-  EXPECT_EQ(l->condition, cond);
-  EXPECT_EQ(l->continuing, cont);
-  EXPECT_EQ(l->body, body);
-}
-
-TEST_F(ForLoopStatementTest, Creation_WithSource) {
-  auto* body = Block(Return());
-  auto* l = For(Source{{20u, 2u}}, nullptr, nullptr, nullptr, body);
-  auto src = l->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(ForLoopStatementTest, Creation_Null_InitCondCont) {
-  auto* body = Block(Return());
-  auto* l = For(nullptr, nullptr, nullptr, body);
-  EXPECT_EQ(l->body, body);
-}
-
-TEST_F(ForLoopStatementTest, Assert_Null_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.For(nullptr, nullptr, nullptr, nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Initializer) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.For(b2.Block(), nullptr, nullptr, b1.Block());
-      },
-      "internal compiler error");
-}
-
-TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Condition) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.For(nullptr, b2.Expr(true), nullptr, b1.Block());
-      },
-      "internal compiler error");
-}
-
-TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Continuing) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.For(nullptr, nullptr, b2.Block(), b1.Block());
-      },
-      "internal compiler error");
-}
-
-TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.For(nullptr, nullptr, nullptr, b2.Block());
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/function.cc b/src/ast/function.cc
deleted file mode 100644
index 8fadd47..0000000
--- a/src/ast/function.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2020 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/ast/function.h"
-
-#include "src/ast/stage_attribute.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Function);
-
-namespace tint {
-namespace ast {
-
-Function::Function(ProgramID pid,
-                   const Source& src,
-                   Symbol sym,
-                   VariableList parameters,
-                   const Type* return_ty,
-                   const BlockStatement* b,
-                   AttributeList attrs,
-                   AttributeList return_type_attrs)
-    : Base(pid, src),
-      symbol(sym),
-      params(std::move(parameters)),
-      return_type(return_ty),
-      body(b),
-      attributes(std::move(attrs)),
-      return_type_attributes(std::move(return_type_attrs)) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-  for (auto* param : params) {
-    TINT_ASSERT(AST, param && param->is_const);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, param, program_id);
-  }
-  TINT_ASSERT(AST, symbol.IsValid());
-  TINT_ASSERT(AST, return_type);
-  for (auto* attr : attributes) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
-  }
-  for (auto* attr : return_type_attributes) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
-  }
-}
-
-Function::Function(Function&&) = default;
-
-Function::~Function() = default;
-
-PipelineStage Function::PipelineStage() const {
-  if (auto* stage = GetAttribute<StageAttribute>(attributes)) {
-    return stage->stage;
-  }
-  return PipelineStage::kNone;
-}
-
-const Function* Function::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto sym = ctx->Clone(symbol);
-  auto p = ctx->Clone(params);
-  auto* ret = ctx->Clone(return_type);
-  auto* b = ctx->Clone(body);
-  auto attrs = ctx->Clone(attributes);
-  auto ret_attrs = ctx->Clone(return_type_attributes);
-  return ctx->dst->create<Function>(src, sym, p, ret, b, attrs, ret_attrs);
-}
-
-const Function* FunctionList::Find(Symbol sym) const {
-  for (auto* func : *this) {
-    if (func->symbol == sym) {
-      return func;
-    }
-  }
-  return nullptr;
-}
-
-const Function* FunctionList::Find(Symbol sym, PipelineStage stage) const {
-  for (auto* func : *this) {
-    if (func->symbol == sym && func->PipelineStage() == stage) {
-      return func;
-    }
-  }
-  return nullptr;
-}
-
-bool FunctionList::HasStage(ast::PipelineStage stage) const {
-  for (auto* func : *this) {
-    if (func->PipelineStage() == stage) {
-      return true;
-    }
-  }
-  return false;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/function.h b/src/ast/function.h
deleted file mode 100644
index ed45313..0000000
--- a/src/ast/function.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 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_AST_FUNCTION_H_
-#define SRC_AST_FUNCTION_H_
-
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "src/ast/attribute.h"
-#include "src/ast/binding_attribute.h"
-#include "src/ast/block_statement.h"
-#include "src/ast/builtin_attribute.h"
-#include "src/ast/group_attribute.h"
-#include "src/ast/location_attribute.h"
-#include "src/ast/pipeline_stage.h"
-#include "src/ast/variable.h"
-
-namespace tint {
-namespace ast {
-
-/// A Function statement.
-class Function : public Castable<Function, Node> {
- public:
-  /// Create a function
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the variable source
-  /// @param symbol the function symbol
-  /// @param params the function parameters
-  /// @param return_type the return type
-  /// @param body the function body
-  /// @param attributes the function attributes
-  /// @param return_type_attributes the return type attributes
-  Function(ProgramID program_id,
-           const Source& source,
-           Symbol symbol,
-           VariableList params,
-           const Type* return_type,
-           const BlockStatement* body,
-           AttributeList attributes,
-           AttributeList return_type_attributes);
-  /// Move constructor
-  Function(Function&&);
-
-  ~Function() override;
-
-  /// @returns the functions pipeline stage or None if not set
-  ast::PipelineStage PipelineStage() const;
-
-  /// @returns true if this function is an entry point
-  bool IsEntryPoint() const { return PipelineStage() != PipelineStage::kNone; }
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const Function* Clone(CloneContext* ctx) const override;
-
-  /// The function symbol
-  const Symbol symbol;
-
-  /// The function params
-  const VariableList params;
-
-  /// The function return type
-  const Type* const return_type;
-
-  /// The function body
-  const BlockStatement* const body;
-
-  /// The attributes attached to this function
-  const AttributeList attributes;
-
-  /// The attributes attached to the function return type.
-  const AttributeList return_type_attributes;
-};
-
-/// A list of functions
-class FunctionList : public std::vector<const Function*> {
- public:
-  /// Appends f to the end of the list
-  /// @param f the function to append to this list
-  void Add(const Function* f) { this->emplace_back(f); }
-
-  /// Returns the function with the given name
-  /// @param sym the function symbol to search for
-  /// @returns the associated function or nullptr if none exists
-  const Function* Find(Symbol sym) const;
-
-  /// Returns the function with the given name
-  /// @param sym the function symbol to search for
-  /// @param stage the pipeline stage
-  /// @returns the associated function or nullptr if none exists
-  const Function* Find(Symbol sym, PipelineStage stage) const;
-
-  /// @param stage the pipeline stage
-  /// @returns true if the Builder contains an entrypoint function with
-  /// the given stage
-  bool HasStage(PipelineStage stage) const;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_FUNCTION_H_
diff --git a/src/ast/function_test.cc b/src/ast/function_test.cc
deleted file mode 100644
index d6ca026..0000000
--- a/src/ast/function_test.cc
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/workgroup_attribute.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using FunctionTest = TestHelper;
-
-TEST_F(FunctionTest, Creation) {
-  VariableList params;
-  params.push_back(Param("var", ty.i32()));
-  auto* var = params[0];
-
-  auto* f = Func("func", params, ty.void_(), StatementList{}, AttributeList{});
-  EXPECT_EQ(f->symbol, Symbols().Get("func"));
-  ASSERT_EQ(f->params.size(), 1u);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-  EXPECT_EQ(f->params[0], var);
-}
-
-TEST_F(FunctionTest, Creation_WithSource) {
-  VariableList params;
-  params.push_back(Param("var", ty.i32()));
-
-  auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_(),
-                 StatementList{}, AttributeList{});
-  auto src = f->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(FunctionTest, Assert_InvalidName) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Func("", VariableList{}, b.ty.void_(), StatementList{},
-               AttributeList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_Null_ReturnType) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Func("f", VariableList{}, nullptr, StatementList{}, AttributeList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_Null_Param) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        VariableList params;
-        params.push_back(b.Param("var", b.ty.i32()));
-        params.push_back(nullptr);
-
-        b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_DifferentProgramID_Symbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Func(b2.Sym("func"), VariableList{}, b1.ty.void_(), StatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_DifferentProgramID_Param) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Func("func", VariableList{b2.Param("var", b2.ty.i32())},
-                b1.ty.void_(), StatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_DifferentProgramID_Attr) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{},
-                AttributeList{
-                    b2.WorkgroupSize(2, 4, 6),
-                });
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_DifferentProgramID_ReturnAttr) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{},
-                AttributeList{},
-                AttributeList{
-                    b2.WorkgroupSize(2, 4, 6),
-                });
-      },
-      "internal compiler error");
-}
-
-TEST_F(FunctionTest, Assert_NonConstParam) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        VariableList params;
-        params.push_back(b.Var("var", b.ty.i32(), ast::StorageClass::kNone));
-
-        b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{});
-      },
-      "internal compiler error");
-}
-
-using FunctionListTest = TestHelper;
-
-TEST_F(FunctionListTest, FindSymbol) {
-  auto* func = Func("main", VariableList{}, ty.f32(), StatementList{},
-                    ast::AttributeList{});
-  FunctionList list;
-  list.Add(func);
-  EXPECT_EQ(func, list.Find(Symbols().Register("main")));
-}
-
-TEST_F(FunctionListTest, FindSymbolMissing) {
-  FunctionList list;
-  EXPECT_EQ(nullptr, list.Find(Symbols().Register("Missing")));
-}
-
-TEST_F(FunctionListTest, FindSymbolStage) {
-  auto* fs = Func("main", VariableList{}, ty.f32(), StatementList{},
-                  ast::AttributeList{
-                      Stage(PipelineStage::kFragment),
-                  });
-  auto* vs = Func("main", VariableList{}, ty.f32(), StatementList{},
-                  ast::AttributeList{
-                      Stage(PipelineStage::kVertex),
-                  });
-  FunctionList list;
-  list.Add(fs);
-  list.Add(vs);
-  EXPECT_EQ(fs,
-            list.Find(Symbols().Register("main"), PipelineStage::kFragment));
-  EXPECT_EQ(vs, list.Find(Symbols().Register("main"), PipelineStage::kVertex));
-}
-
-TEST_F(FunctionListTest, FindSymbolStageMissing) {
-  FunctionList list;
-  list.Add(Func("main", VariableList{}, ty.f32(), StatementList{},
-                ast::AttributeList{
-                    Stage(PipelineStage::kFragment),
-                }));
-  EXPECT_EQ(nullptr,
-            list.Find(Symbols().Register("main"), PipelineStage::kVertex));
-}
-
-TEST_F(FunctionListTest, HasStage) {
-  FunctionList list;
-  list.Add(Func("main", VariableList{}, ty.f32(), StatementList{},
-                ast::AttributeList{
-                    Stage(PipelineStage::kFragment),
-                }));
-  EXPECT_TRUE(list.HasStage(PipelineStage::kFragment));
-  EXPECT_FALSE(list.HasStage(PipelineStage::kVertex));
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/group_attribute.cc b/src/ast/group_attribute.cc
deleted file mode 100644
index b327b6e..0000000
--- a/src/ast/group_attribute.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/ast/group_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::GroupAttribute);
-
-namespace tint {
-namespace ast {
-
-GroupAttribute::GroupAttribute(ProgramID pid, const Source& src, uint32_t val)
-    : Base(pid, src), value(val) {}
-
-GroupAttribute::~GroupAttribute() = default;
-
-std::string GroupAttribute::Name() const {
-  return "group";
-}
-
-const GroupAttribute* GroupAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<GroupAttribute>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/group_attribute.h b/src/ast/group_attribute.h
deleted file mode 100644
index b9d51b4..0000000
--- a/src/ast/group_attribute.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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_AST_GROUP_ATTRIBUTE_H_
-#define SRC_AST_GROUP_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A group attribute
-class GroupAttribute : public Castable<GroupAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the group value
-  GroupAttribute(ProgramID pid, const Source& src, uint32_t value);
-  ~GroupAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const GroupAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The group value
-  const uint32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_GROUP_ATTRIBUTE_H_
diff --git a/src/ast/group_attribute_test.cc b/src/ast/group_attribute_test.cc
deleted file mode 100644
index a124170..0000000
--- a/src/ast/group_attribute_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using GroupAttributeTest = TestHelper;
-
-TEST_F(GroupAttributeTest, Creation) {
-  auto* d = create<GroupAttribute>(2);
-  EXPECT_EQ(2u, d->value);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/i32.cc b/src/ast/i32.cc
deleted file mode 100644
index 50207c3..0000000
--- a/src/ast/i32.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/ast/i32.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::I32);
-
-namespace tint {
-namespace ast {
-
-I32::I32(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-I32::I32(I32&&) = default;
-
-I32::~I32() = default;
-
-std::string I32::FriendlyName(const SymbolTable&) const {
-  return "i32";
-}
-
-const I32* I32::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<I32>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/i32.h b/src/ast/i32.h
deleted file mode 100644
index 8a76cf2..0000000
--- a/src/ast/i32.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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_AST_I32_H_
-#define SRC_AST_I32_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A signed int 32 type.
-class I32 : public Castable<I32, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  I32(ProgramID pid, const Source& src);
-  /// Move constructor
-  I32(I32&&);
-  ~I32() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const I32* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_I32_H_
diff --git a/src/ast/i32_test.cc b/src/ast/i32_test.cc
deleted file mode 100644
index df8bca8..0000000
--- a/src/ast/i32_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/i32.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstI32Test = TestHelper;
-
-TEST_F(AstI32Test, FriendlyName) {
-  auto* i = create<I32>();
-  EXPECT_EQ(i->FriendlyName(Symbols()), "i32");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/id_attribute.cc b/src/ast/id_attribute.cc
deleted file mode 100644
index 608625f..0000000
--- a/src/ast/id_attribute.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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/ast/id_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::IdAttribute);
-
-namespace tint {
-namespace ast {
-
-IdAttribute::IdAttribute(ProgramID pid, const Source& src, uint32_t val)
-    : Base(pid, src), value(val) {}
-
-IdAttribute::~IdAttribute() = default;
-
-std::string IdAttribute::Name() const {
-  return "id";
-}
-
-const IdAttribute* IdAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<IdAttribute>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/id_attribute.h b/src/ast/id_attribute.h
deleted file mode 100644
index db12aa1..0000000
--- a/src/ast/id_attribute.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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_AST_ID_ATTRIBUTE_H_
-#define SRC_AST_ID_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// An id attribute for pipeline-overridable constants
-class IdAttribute : public Castable<IdAttribute, Attribute> {
- public:
-  /// Create an id attribute.
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param val the numeric id value
-  IdAttribute(ProgramID pid, const Source& src, uint32_t val);
-  ~IdAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const IdAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The id value
-  const uint32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_ID_ATTRIBUTE_H_
diff --git a/src/ast/id_attribute_test.cc b/src/ast/id_attribute_test.cc
deleted file mode 100644
index d40db86..0000000
--- a/src/ast/id_attribute_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/id_attribute.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IdAttributeTest = TestHelper;
-
-TEST_F(IdAttributeTest, Creation) {
-  auto* d = create<IdAttribute>(12);
-  EXPECT_EQ(12u, d->value);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/identifier_expression.cc b/src/ast/identifier_expression.cc
deleted file mode 100644
index f0c50c4..0000000
--- a/src/ast/identifier_expression.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/identifier_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::IdentifierExpression);
-
-namespace tint {
-namespace ast {
-
-IdentifierExpression::IdentifierExpression(ProgramID pid,
-                                           const Source& src,
-                                           Symbol sym)
-    : Base(pid, src), symbol(sym) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
-  TINT_ASSERT(AST, symbol.IsValid());
-}
-
-IdentifierExpression::IdentifierExpression(IdentifierExpression&&) = default;
-
-IdentifierExpression::~IdentifierExpression() = default;
-
-const IdentifierExpression* IdentifierExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto sym = ctx->Clone(symbol);
-  return ctx->dst->create<IdentifierExpression>(src, sym);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/identifier_expression.h b/src/ast/identifier_expression.h
deleted file mode 100644
index e238cde..0000000
--- a/src/ast/identifier_expression.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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_AST_IDENTIFIER_EXPRESSION_H_
-#define SRC_AST_IDENTIFIER_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// An identifier expression
-class IdentifierExpression : public Castable<IdentifierExpression, Expression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param sym the symbol for the identifier
-  IdentifierExpression(ProgramID pid, const Source& src, Symbol sym);
-  /// Move constructor
-  IdentifierExpression(IdentifierExpression&&);
-  ~IdentifierExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const IdentifierExpression* Clone(CloneContext* ctx) const override;
-
-  /// The symbol for the identifier
-  const Symbol symbol;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_IDENTIFIER_EXPRESSION_H_
diff --git a/src/ast/identifier_expression_test.cc b/src/ast/identifier_expression_test.cc
deleted file mode 100644
index ad6dff5..0000000
--- a/src/ast/identifier_expression_test.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IdentifierExpressionTest = TestHelper;
-
-TEST_F(IdentifierExpressionTest, Creation) {
-  auto* i = Expr("ident");
-  EXPECT_EQ(i->symbol, Symbol(1, ID()));
-}
-
-TEST_F(IdentifierExpressionTest, Creation_WithSource) {
-  auto* i = Expr(Source{Source::Location{20, 2}}, "ident");
-  EXPECT_EQ(i->symbol, Symbol(1, ID()));
-
-  auto src = i->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(IdentifierExpressionTest, IsIdentifier) {
-  auto* i = Expr("ident");
-  EXPECT_TRUE(i->Is<IdentifierExpression>());
-}
-
-TEST_F(IdentifierExpressionTest, Assert_InvalidSymbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Expr("");
-      },
-      "internal compiler error");
-}
-
-TEST_F(IdentifierExpressionTest, Assert_DifferentProgramID_Symbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Expr(b2.Sym("b2"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/if_statement.cc b/src/ast/if_statement.cc
deleted file mode 100644
index 31708d0..0000000
--- a/src/ast/if_statement.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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/ast/if_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::IfStatement);
-
-namespace tint {
-namespace ast {
-
-IfStatement::IfStatement(ProgramID pid,
-                         const Source& src,
-                         const Expression* cond,
-                         const BlockStatement* b,
-                         ElseStatementList else_stmts)
-    : Base(pid, src),
-      condition(cond),
-      body(b),
-      else_statements(std::move(else_stmts)) {
-  TINT_ASSERT(AST, condition);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
-  TINT_ASSERT(AST, body);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-  for (auto* el : else_statements) {
-    TINT_ASSERT(AST, el);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, el, program_id);
-  }
-}
-
-IfStatement::IfStatement(IfStatement&&) = default;
-
-IfStatement::~IfStatement() = default;
-
-const IfStatement* IfStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* cond = ctx->Clone(condition);
-  auto* b = ctx->Clone(body);
-  auto el = ctx->Clone(else_statements);
-  return ctx->dst->create<IfStatement>(src, cond, b, el);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/if_statement.h b/src/ast/if_statement.h
deleted file mode 100644
index d4c54e1..0000000
--- a/src/ast/if_statement.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 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_AST_IF_STATEMENT_H_
-#define SRC_AST_IF_STATEMENT_H_
-
-#include <utility>
-
-#include "src/ast/else_statement.h"
-
-namespace tint {
-namespace ast {
-
-/// An if statement
-class IfStatement : public Castable<IfStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param condition the if condition
-  /// @param body the if body
-  /// @param else_stmts the else statements
-  IfStatement(ProgramID pid,
-              const Source& src,
-              const Expression* condition,
-              const BlockStatement* body,
-              ElseStatementList else_stmts);
-  /// Move constructor
-  IfStatement(IfStatement&&);
-  ~IfStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const IfStatement* Clone(CloneContext* ctx) const override;
-
-  /// The if condition or nullptr if none set
-  const Expression* const condition;
-
-  /// The if body
-  const BlockStatement* const body;
-
-  /// The else statements
-  const ElseStatementList else_statements;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_IF_STATEMENT_H_
diff --git a/src/ast/if_statement_test.cc b/src/ast/if_statement_test.cc
deleted file mode 100644
index 99903ae..0000000
--- a/src/ast/if_statement_test.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2020 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/ast/if_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IfStatementTest = TestHelper;
-
-TEST_F(IfStatementTest, Creation) {
-  auto* cond = Expr("cond");
-  auto* stmt = create<IfStatement>(Source{Source::Location{20, 2}}, cond,
-                                   Block(create<DiscardStatement>()),
-                                   ElseStatementList{});
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(IfStatementTest, IsIf) {
-  auto* stmt = create<IfStatement>(Expr(true), Block(), ElseStatementList{});
-  EXPECT_TRUE(stmt->Is<IfStatement>());
-}
-
-TEST_F(IfStatementTest, Assert_Null_Condition) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<IfStatement>(nullptr, b.Block(), ElseStatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(IfStatementTest, Assert_Null_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<IfStatement>(b.Expr(true), nullptr, ElseStatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(IfStatementTest, Assert_Null_ElseStatement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        auto* body = b.create<BlockStatement>(StatementList{});
-        b.create<IfStatement>(b.Expr(true), body, ElseStatementList{nullptr});
-      },
-      "internal compiler error");
-}
-
-TEST_F(IfStatementTest, Assert_DifferentProgramID_Cond) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<IfStatement>(b2.Expr(true), b1.Block(), ElseStatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(IfStatementTest, Assert_DifferentProgramID_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<IfStatement>(b1.Expr(true), b2.Block(), ElseStatementList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(IfStatementTest, Assert_DifferentProgramID_ElseStatement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<IfStatement>(
-            b1.Expr(true), b1.Block(),
-            ElseStatementList{
-                b2.create<ElseStatement>(b2.Expr("ident"), b2.Block()),
-            });
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/index_accessor_expression.cc b/src/ast/index_accessor_expression.cc
deleted file mode 100644
index b37ab00..0000000
--- a/src/ast/index_accessor_expression.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/index_accessor_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::IndexAccessorExpression);
-
-namespace tint {
-namespace ast {
-
-IndexAccessorExpression::IndexAccessorExpression(ProgramID pid,
-                                                 const Source& src,
-                                                 const Expression* obj,
-                                                 const Expression* idx)
-    : Base(pid, src), object(obj), index(idx) {
-  TINT_ASSERT(AST, object);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, object, program_id);
-  TINT_ASSERT(AST, idx);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, idx, program_id);
-}
-
-IndexAccessorExpression::IndexAccessorExpression(IndexAccessorExpression&&) =
-    default;
-
-IndexAccessorExpression::~IndexAccessorExpression() = default;
-
-const IndexAccessorExpression* IndexAccessorExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* obj = ctx->Clone(object);
-  auto* idx = ctx->Clone(index);
-  return ctx->dst->create<IndexAccessorExpression>(src, obj, idx);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/index_accessor_expression.h b/src/ast/index_accessor_expression.h
deleted file mode 100644
index b808b35..0000000
--- a/src/ast/index_accessor_expression.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_INDEX_ACCESSOR_EXPRESSION_H_
-#define SRC_AST_INDEX_ACCESSOR_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// An index accessor expression
-class IndexAccessorExpression
-    : public Castable<IndexAccessorExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the index accessor source
-  /// @param obj the object
-  /// @param idx the index expression
-  IndexAccessorExpression(ProgramID program_id,
-                          const Source& source,
-                          const Expression* obj,
-                          const Expression* idx);
-  /// Move constructor
-  IndexAccessorExpression(IndexAccessorExpression&&);
-  ~IndexAccessorExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const IndexAccessorExpression* Clone(CloneContext* ctx) const override;
-
-  /// the array, vector or matrix
-  const Expression* const object;
-
-  /// the index expression
-  const Expression* const index;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_INDEX_ACCESSOR_EXPRESSION_H_
diff --git a/src/ast/index_accessor_expression_test.cc b/src/ast/index_accessor_expression_test.cc
deleted file mode 100644
index 1509655..0000000
--- a/src/ast/index_accessor_expression_test.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IndexAccessorExpressionTest = TestHelper;
-
-TEST_F(IndexAccessorExpressionTest, Create) {
-  auto* obj = Expr("obj");
-  auto* idx = Expr("idx");
-
-  auto* exp = IndexAccessor(obj, idx);
-  ASSERT_EQ(exp->object, obj);
-  ASSERT_EQ(exp->index, idx);
-}
-
-TEST_F(IndexAccessorExpressionTest, CreateWithSource) {
-  auto* obj = Expr("obj");
-  auto* idx = Expr("idx");
-
-  auto* exp = IndexAccessor(Source{{20, 2}}, obj, idx);
-  auto src = exp->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(IndexAccessorExpressionTest, IsIndexAccessor) {
-  auto* obj = Expr("obj");
-  auto* idx = Expr("idx");
-
-  auto* exp = IndexAccessor(obj, idx);
-  EXPECT_TRUE(exp->Is<IndexAccessorExpression>());
-}
-
-TEST_F(IndexAccessorExpressionTest, Assert_Null_Array) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.IndexAccessor(nullptr, b.Expr("idx"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(IndexAccessorExpressionTest, Assert_Null_Index) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.IndexAccessor(b.Expr("arr"), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Array) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.IndexAccessor(b2.Expr("arr"), b1.Expr("idx"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Index) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.IndexAccessor(b1.Expr("arr"), b2.Expr("idx"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/int_literal_expression.cc b/src/ast/int_literal_expression.cc
deleted file mode 100644
index b610de9..0000000
--- a/src/ast/int_literal_expression.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 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/ast/int_literal_expression.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::IntLiteralExpression);
-
-namespace tint {
-namespace ast {
-
-IntLiteralExpression::IntLiteralExpression(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-IntLiteralExpression::~IntLiteralExpression() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/int_literal_expression.h b/src/ast/int_literal_expression.h
deleted file mode 100644
index 88aa1de..0000000
--- a/src/ast/int_literal_expression.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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_AST_INT_LITERAL_EXPRESSION_H_
-#define SRC_AST_INT_LITERAL_EXPRESSION_H_
-
-#include "src/ast/literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// An integer literal. This could be either signed or unsigned.
-class IntLiteralExpression
-    : public Castable<IntLiteralExpression, LiteralExpression> {
- public:
-  ~IntLiteralExpression() override;
-
-  /// @returns the literal value as a u32
-  virtual uint32_t ValueAsU32() const = 0;
-
-  /// @returns the literal value as an i32
-  int32_t ValueAsI32() const { return static_cast<int32_t>(ValueAsU32()); }
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  IntLiteralExpression(ProgramID pid, const Source& src);
-};  // namespace ast
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_INT_LITERAL_EXPRESSION_H_
diff --git a/src/ast/int_literal_expression_test.cc b/src/ast/int_literal_expression_test.cc
deleted file mode 100644
index d6d9f1c..0000000
--- a/src/ast/int_literal_expression_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IntLiteralExpressionTest = TestHelper;
-
-TEST_F(IntLiteralExpressionTest, Sint_IsInt) {
-  auto* i = create<SintLiteralExpression>(47);
-  ASSERT_TRUE(i->Is<IntLiteralExpression>());
-}
-
-TEST_F(IntLiteralExpressionTest, Uint_IsInt) {
-  auto* i = create<UintLiteralExpression>(42);
-  EXPECT_TRUE(i->Is<IntLiteralExpression>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/internal_attribute.cc b/src/ast/internal_attribute.cc
deleted file mode 100644
index 8adf24d..0000000
--- a/src/ast/internal_attribute.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/internal_attribute.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::InternalAttribute);
-
-namespace tint {
-namespace ast {
-
-InternalAttribute::InternalAttribute(ProgramID pid) : Base(pid, Source{}) {}
-
-InternalAttribute::~InternalAttribute() = default;
-
-std::string InternalAttribute::Name() const {
-  return "internal";
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/internal_attribute.h b/src/ast/internal_attribute.h
deleted file mode 100644
index 3b33139..0000000
--- a/src/ast/internal_attribute.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_INTERNAL_ATTRIBUTE_H_
-#define SRC_AST_INTERNAL_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// An attribute used to indicate that a function is tint-internal.
-/// These attributes are not produced by generators, but instead are usually
-/// created by transforms for consumption by a particular backend.
-class InternalAttribute : public Castable<InternalAttribute, Attribute> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  explicit InternalAttribute(ProgramID program_id);
-
-  /// Destructor
-  ~InternalAttribute() override;
-
-  /// @return a short description of the internal attribute which will be
-  /// displayed in WGSL as `@internal(<name>)` (but is not parsable).
-  virtual std::string InternalName() const = 0;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_INTERNAL_ATTRIBUTE_H_
diff --git a/src/ast/interpolate_attribute.cc b/src/ast/interpolate_attribute.cc
deleted file mode 100644
index bb4cd52..0000000
--- a/src/ast/interpolate_attribute.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/interpolate_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::InterpolateAttribute);
-
-namespace tint {
-namespace ast {
-
-InterpolateAttribute::InterpolateAttribute(ProgramID pid,
-                                           const Source& src,
-                                           InterpolationType ty,
-                                           InterpolationSampling smpl)
-    : Base(pid, src), type(ty), sampling(smpl) {}
-
-InterpolateAttribute::~InterpolateAttribute() = default;
-
-std::string InterpolateAttribute::Name() const {
-  return "interpolate";
-}
-
-const InterpolateAttribute* InterpolateAttribute::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<InterpolateAttribute>(src, type, sampling);
-}
-
-std::ostream& operator<<(std::ostream& out, InterpolationType type) {
-  switch (type) {
-    case InterpolationType::kPerspective: {
-      out << "perspective";
-      break;
-    }
-    case InterpolationType::kLinear: {
-      out << "linear";
-      break;
-    }
-    case InterpolationType::kFlat: {
-      out << "flat";
-      break;
-    }
-  }
-  return out;
-}
-
-std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling) {
-  switch (sampling) {
-    case InterpolationSampling::kNone: {
-      out << "none";
-      break;
-    }
-    case InterpolationSampling::kCenter: {
-      out << "center";
-      break;
-    }
-    case InterpolationSampling::kCentroid: {
-      out << "centroid";
-      break;
-    }
-    case InterpolationSampling::kSample: {
-      out << "sample";
-      break;
-    }
-  }
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/interpolate_attribute.h b/src/ast/interpolate_attribute.h
deleted file mode 100644
index 879162c..0000000
--- a/src/ast/interpolate_attribute.h
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_INTERPOLATE_ATTRIBUTE_H_
-#define SRC_AST_INTERPOLATE_ATTRIBUTE_H_
-
-#include <ostream>
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// The interpolation type.
-enum class InterpolationType { kPerspective, kLinear, kFlat };
-
-/// The interpolation sampling.
-enum class InterpolationSampling { kNone = -1, kCenter, kCentroid, kSample };
-
-/// An interpolate attribute
-class InterpolateAttribute : public Castable<InterpolateAttribute, Attribute> {
- public:
-  /// Create an interpolate attribute.
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  InterpolateAttribute(ProgramID pid,
-                       const Source& src,
-                       InterpolationType type,
-                       InterpolationSampling sampling);
-  ~InterpolateAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const InterpolateAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The interpolation type
-  const InterpolationType type;
-
-  /// The interpolation sampling
-  const InterpolationSampling sampling;
-};
-
-/// @param out the std::ostream to write to
-/// @param type the interpolation type
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, InterpolationType type);
-
-/// @param out the std::ostream to write to
-/// @param sampling the interpolation sampling
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_INTERPOLATE_ATTRIBUTE_H_
diff --git a/src/ast/interpolate_attribute_test.cc b/src/ast/interpolate_attribute_test.cc
deleted file mode 100644
index fed0a76..0000000
--- a/src/ast/interpolate_attribute_test.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/interpolate_attribute.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace 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
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/invariant_attribute.cc b/src/ast/invariant_attribute.cc
deleted file mode 100644
index fb97b55..0000000
--- a/src/ast/invariant_attribute.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/invariant_attribute.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::InvariantAttribute);
-
-namespace tint {
-namespace ast {
-
-InvariantAttribute::InvariantAttribute(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-InvariantAttribute::~InvariantAttribute() = default;
-
-std::string InvariantAttribute::Name() const {
-  return "invariant";
-}
-
-const InvariantAttribute* InvariantAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<InvariantAttribute>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/invariant_attribute.h b/src/ast/invariant_attribute.h
deleted file mode 100644
index 68933e6..0000000
--- a/src/ast/invariant_attribute.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_INVARIANT_ATTRIBUTE_H_
-#define SRC_AST_INVARIANT_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// The invariant attribute
-class InvariantAttribute : public Castable<InvariantAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  InvariantAttribute(ProgramID pid, const Source& src);
-  ~InvariantAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const InvariantAttribute* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_INVARIANT_ATTRIBUTE_H_
diff --git a/src/ast/invariant_attribute_test.cc b/src/ast/invariant_attribute_test.cc
deleted file mode 100644
index 4ff10c7..0000000
--- a/src/ast/invariant_attribute_test.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/invariant_attribute.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using InvariantAttributeTest = TestHelper;
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/literal_expression.cc b/src/ast/literal_expression.cc
deleted file mode 100644
index e9ab969..0000000
--- a/src/ast/literal_expression.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 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/ast/literal_expression.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::LiteralExpression);
-
-namespace tint {
-namespace ast {
-
-LiteralExpression::LiteralExpression(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-LiteralExpression::~LiteralExpression() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/literal_expression.h b/src/ast/literal_expression.h
deleted file mode 100644
index 40114e1..0000000
--- a/src/ast/literal_expression.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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_AST_LITERAL_EXPRESSION_H_
-#define SRC_AST_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// Base class for a literal value expressions
-class LiteralExpression : public Castable<LiteralExpression, Expression> {
- public:
-  ~LiteralExpression() override;
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the input source
-  LiteralExpression(ProgramID pid, const Source& src);
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_LITERAL_EXPRESSION_H_
diff --git a/src/ast/location_attribute.cc b/src/ast/location_attribute.cc
deleted file mode 100644
index 2c14bc7..0000000
--- a/src/ast/location_attribute.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/location_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::LocationAttribute);
-
-namespace tint {
-namespace ast {
-
-LocationAttribute::LocationAttribute(ProgramID pid,
-                                     const Source& src,
-                                     uint32_t val)
-    : Base(pid, src), value(val) {}
-
-LocationAttribute::~LocationAttribute() = default;
-
-std::string LocationAttribute::Name() const {
-  return "location";
-}
-
-const LocationAttribute* LocationAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<LocationAttribute>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/location_attribute.h b/src/ast/location_attribute.h
deleted file mode 100644
index 28a46b1..0000000
--- a/src/ast/location_attribute.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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_AST_LOCATION_ATTRIBUTE_H_
-#define SRC_AST_LOCATION_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A location attribute
-class LocationAttribute : public Castable<LocationAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the location value
-  LocationAttribute(ProgramID pid, const Source& src, uint32_t value);
-  ~LocationAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const LocationAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The location value
-  const uint32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_LOCATION_ATTRIBUTE_H_
diff --git a/src/ast/location_attribute_test.cc b/src/ast/location_attribute_test.cc
deleted file mode 100644
index 60941f7..0000000
--- a/src/ast/location_attribute_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using LocationAttributeTest = TestHelper;
-
-TEST_F(LocationAttributeTest, Creation) {
-  auto* d = create<LocationAttribute>(2);
-  EXPECT_EQ(2u, d->value);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/loop_statement.cc b/src/ast/loop_statement.cc
deleted file mode 100644
index 84f1249..0000000
--- a/src/ast/loop_statement.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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/ast/loop_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::LoopStatement);
-
-namespace tint {
-namespace ast {
-
-LoopStatement::LoopStatement(ProgramID pid,
-                             const Source& src,
-                             const BlockStatement* b,
-                             const BlockStatement* cont)
-    : Base(pid, src), body(b), continuing(cont) {
-  TINT_ASSERT(AST, body);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
-}
-
-LoopStatement::LoopStatement(LoopStatement&&) = default;
-
-LoopStatement::~LoopStatement() = default;
-
-const LoopStatement* LoopStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* b = ctx->Clone(body);
-  auto* cont = ctx->Clone(continuing);
-  return ctx->dst->create<LoopStatement>(src, b, cont);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/loop_statement.h b/src/ast/loop_statement.h
deleted file mode 100644
index 90a6ddf..0000000
--- a/src/ast/loop_statement.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 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_AST_LOOP_STATEMENT_H_
-#define SRC_AST_LOOP_STATEMENT_H_
-
-#include "src/ast/block_statement.h"
-
-namespace tint {
-namespace ast {
-
-/// A loop statement
-class LoopStatement : public Castable<LoopStatement, Statement> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the loop statement source
-  /// @param body the body statements
-  /// @param continuing the continuing statements
-  LoopStatement(ProgramID program_id,
-                const Source& source,
-                const BlockStatement* body,
-                const BlockStatement* continuing);
-  /// Move constructor
-  LoopStatement(LoopStatement&&);
-  ~LoopStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const LoopStatement* Clone(CloneContext* ctx) const override;
-
-  /// The loop body
-  const BlockStatement* const body;
-
-  /// The continuing statements
-  const BlockStatement* const continuing;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_LOOP_STATEMENT_H_
diff --git a/src/ast/loop_statement_test.cc b/src/ast/loop_statement_test.cc
deleted file mode 100644
index 6d8521a..0000000
--- a/src/ast/loop_statement_test.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 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/ast/loop_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using LoopStatementTest = TestHelper;
-
-TEST_F(LoopStatementTest, Creation) {
-  auto* body = Block(create<DiscardStatement>());
-  auto* b = body->Last();
-
-  auto* continuing = Block(create<DiscardStatement>());
-
-  auto* l = create<LoopStatement>(body, continuing);
-  ASSERT_EQ(l->body->statements.size(), 1u);
-  EXPECT_EQ(l->body->statements[0], b);
-  ASSERT_EQ(l->continuing->statements.size(), 1u);
-  EXPECT_EQ(l->continuing->statements[0], continuing->Last());
-}
-
-TEST_F(LoopStatementTest, Creation_WithSource) {
-  auto* body = Block(create<DiscardStatement>());
-
-  auto* continuing = Block(create<DiscardStatement>());
-
-  auto* l =
-      create<LoopStatement>(Source{Source::Location{20, 2}}, body, continuing);
-  auto src = l->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(LoopStatementTest, IsLoop) {
-  auto* l = create<LoopStatement>(Block(), Block());
-  EXPECT_TRUE(l->Is<LoopStatement>());
-}
-
-TEST_F(LoopStatementTest, HasContinuing_WithoutContinuing) {
-  auto* body = Block(create<DiscardStatement>());
-
-  auto* l = create<LoopStatement>(body, nullptr);
-  EXPECT_FALSE(l->continuing);
-}
-
-TEST_F(LoopStatementTest, HasContinuing_WithContinuing) {
-  auto* body = Block(create<DiscardStatement>());
-
-  auto* continuing = Block(create<DiscardStatement>());
-
-  auto* l = create<LoopStatement>(body, continuing);
-  EXPECT_TRUE(l->continuing);
-}
-
-TEST_F(LoopStatementTest, Assert_Null_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<LoopStatement>(nullptr, nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(LoopStatementTest, Assert_DifferentProgramID_Body) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<LoopStatement>(b2.Block(), b1.Block());
-      },
-      "internal compiler error");
-}
-
-TEST_F(LoopStatementTest, Assert_DifferentProgramID_Continuing) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<LoopStatement>(b1.Block(), b2.Block());
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/matrix.cc b/src/ast/matrix.cc
deleted file mode 100644
index 2530e80..0000000
--- a/src/ast/matrix.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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/ast/matrix.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Matrix);
-
-namespace tint {
-namespace ast {
-
-Matrix::Matrix(ProgramID pid,
-               const Source& src,
-               const Type* subtype,
-               uint32_t r,
-               uint32_t c)
-    : Base(pid, src), type(subtype), rows(r), columns(c) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
-  TINT_ASSERT(AST, rows > 1);
-  TINT_ASSERT(AST, rows < 5);
-  TINT_ASSERT(AST, columns > 1);
-  TINT_ASSERT(AST, columns < 5);
-}
-
-Matrix::Matrix(Matrix&&) = default;
-
-Matrix::~Matrix() = default;
-
-std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "mat" << columns << "x" << rows << "<" << type->FriendlyName(symbols)
-      << ">";
-  return out.str();
-}
-
-const Matrix* Matrix::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<Matrix>(src, ty, rows, columns);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/matrix.h b/src/ast/matrix.h
deleted file mode 100644
index 1f24e96..0000000
--- a/src/ast/matrix.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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_AST_MATRIX_H_
-#define SRC_AST_MATRIX_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A matrix type
-class Matrix : public Castable<Matrix, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param subtype the declared type of the matrix components. May be null for
-  ///        matrix constructors, where the element type will be inferred from
-  ///        the constructor arguments
-  /// @param rows the number of rows in the matrix
-  /// @param columns the number of columns in the matrix
-  Matrix(ProgramID pid,
-         const Source& src,
-         const Type* subtype,
-         uint32_t rows,
-         uint32_t columns);
-  /// Move constructor
-  Matrix(Matrix&&);
-  ~Matrix() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Matrix* Clone(CloneContext* ctx) const override;
-
-  /// The declared type of the matrix components. May be null for matrix
-  /// constructors, where the element type will be inferred from the constructor
-  /// arguments
-  const Type* const type;
-
-  /// The number of rows in the matrix
-  const uint32_t rows;
-
-  /// The number of columns in the matrix
-  const uint32_t columns;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_MATRIX_H_
diff --git a/src/ast/matrix_test.cc b/src/ast/matrix_test.cc
deleted file mode 100644
index f85ac60..0000000
--- a/src/ast/matrix_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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/ast/matrix.h"
-#include "src/ast/access.h"
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/bool.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampler.h"
-#include "src/ast/struct.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/texture.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstMatrixTest = TestHelper;
-
-TEST_F(AstMatrixTest, Creation) {
-  auto* i32 = create<I32>();
-  auto* m = create<Matrix>(i32, 2, 4);
-  EXPECT_EQ(m->type, i32);
-  EXPECT_EQ(m->rows, 2u);
-  EXPECT_EQ(m->columns, 4u);
-}
-
-TEST_F(AstMatrixTest, FriendlyName) {
-  auto* i32 = create<I32>();
-  auto* m = create<Matrix>(i32, 3, 2);
-  EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3<i32>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/member_accessor_expression.cc b/src/ast/member_accessor_expression.cc
deleted file mode 100644
index 12b2a3d..0000000
--- a/src/ast/member_accessor_expression.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/ast/member_accessor_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::MemberAccessorExpression);
-
-namespace tint {
-namespace ast {
-
-MemberAccessorExpression::MemberAccessorExpression(
-    ProgramID pid,
-    const Source& src,
-    const Expression* str,
-    const IdentifierExpression* mem)
-    : Base(pid, src), structure(str), member(mem) {
-  TINT_ASSERT(AST, structure);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, structure, program_id);
-  TINT_ASSERT(AST, member);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, member, program_id);
-}
-
-MemberAccessorExpression::MemberAccessorExpression(MemberAccessorExpression&&) =
-    default;
-
-MemberAccessorExpression::~MemberAccessorExpression() = default;
-
-const MemberAccessorExpression* MemberAccessorExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* str = ctx->Clone(structure);
-  auto* mem = ctx->Clone(member);
-  return ctx->dst->create<MemberAccessorExpression>(src, str, mem);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/member_accessor_expression.h b/src/ast/member_accessor_expression.h
deleted file mode 100644
index 58f93d0..0000000
--- a/src/ast/member_accessor_expression.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_MEMBER_ACCESSOR_EXPRESSION_H_
-#define SRC_AST_MEMBER_ACCESSOR_EXPRESSION_H_
-
-#include "src/ast/identifier_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A member accessor expression
-class MemberAccessorExpression
-    : public Castable<MemberAccessorExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the member accessor expression source
-  /// @param structure the structure
-  /// @param member the member
-  MemberAccessorExpression(ProgramID program_id,
-                           const Source& source,
-                           const Expression* structure,
-                           const IdentifierExpression* member);
-  /// Move constructor
-  MemberAccessorExpression(MemberAccessorExpression&&);
-  ~MemberAccessorExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const MemberAccessorExpression* Clone(CloneContext* ctx) const override;
-
-  /// The structure
-  const Expression* const structure;
-
-  /// The member expression
-  const IdentifierExpression* const member;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_MEMBER_ACCESSOR_EXPRESSION_H_
diff --git a/src/ast/member_accessor_expression_test.cc b/src/ast/member_accessor_expression_test.cc
deleted file mode 100644
index 8b33624..0000000
--- a/src/ast/member_accessor_expression_test.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using MemberAccessorExpressionTest = TestHelper;
-
-TEST_F(MemberAccessorExpressionTest, Creation) {
-  auto* str = Expr("structure");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
-  EXPECT_EQ(stmt->structure, str);
-  EXPECT_EQ(stmt->member, mem);
-}
-
-TEST_F(MemberAccessorExpressionTest, Creation_WithSource) {
-  auto* stmt = create<MemberAccessorExpression>(
-      Source{Source::Location{20, 2}}, Expr("structure"), Expr("member"));
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(MemberAccessorExpressionTest, IsMemberAccessor) {
-  auto* stmt =
-      create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
-  EXPECT_TRUE(stmt->Is<MemberAccessorExpression>());
-}
-
-TEST_F(MemberAccessorExpressionTest, Assert_Null_Struct) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<MemberAccessorExpression>(nullptr, b.Expr("member"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(MemberAccessorExpressionTest, Assert_Null_Member) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<MemberAccessorExpression>(b.Expr("struct"), nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Struct) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<MemberAccessorExpression>(b2.Expr("structure"),
-                                            b1.Expr("member"));
-      },
-      "internal compiler error");
-}
-
-TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Member) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<MemberAccessorExpression>(b1.Expr("structure"),
-                                            b2.Expr("member"));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/module.cc b/src/ast/module.cc
deleted file mode 100644
index bde9967..0000000
--- a/src/ast/module.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/module.h"
-
-#include <utility>
-
-#include "src/ast/type_decl.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Module);
-
-namespace tint {
-namespace ast {
-
-Module::Module(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Module::Module(ProgramID pid,
-               const Source& src,
-               std::vector<const ast::Node*> global_decls)
-    : Base(pid, src), global_declarations_(std::move(global_decls)) {
-  for (auto* decl : global_declarations_) {
-    if (decl == nullptr) {
-      continue;
-    }
-    diag::List diags;
-    BinGlobalDeclaration(decl, diags);
-  }
-}
-
-Module::~Module() = default;
-
-const ast::TypeDecl* Module::LookupType(Symbol name) const {
-  for (auto* ty : TypeDecls()) {
-    if (ty->name == name) {
-      return ty;
-    }
-  }
-  return nullptr;
-}
-
-void Module::AddGlobalDeclaration(const tint::ast::Node* decl) {
-  diag::List diags;
-  BinGlobalDeclaration(decl, diags);
-  global_declarations_.emplace_back(decl);
-}
-
-void Module::BinGlobalDeclaration(const tint::ast::Node* decl,
-                                  diag::List& diags) {
-  Switch(
-      decl,  //
-      [&](const ast::TypeDecl* type) {
-        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
-        type_decls_.push_back(type);
-      },
-      [&](const Function* func) {
-        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
-        functions_.push_back(func);
-      },
-      [&](const Variable* var) {
-        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
-        global_variables_.push_back(var);
-      },
-      [&](Default) {
-        TINT_ICE(AST, diags) << "Unknown global declaration type";
-      });
-}
-
-void Module::AddGlobalVariable(const ast::Variable* var) {
-  TINT_ASSERT(AST, var);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
-  global_variables_.push_back(var);
-  global_declarations_.push_back(var);
-}
-
-void Module::AddTypeDecl(const ast::TypeDecl* type) {
-  TINT_ASSERT(AST, type);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
-  type_decls_.push_back(type);
-  global_declarations_.push_back(type);
-}
-
-void Module::AddFunction(const ast::Function* func) {
-  TINT_ASSERT(AST, func);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
-  functions_.push_back(func);
-  global_declarations_.push_back(func);
-}
-
-const Module* Module::Clone(CloneContext* ctx) const {
-  auto* out = ctx->dst->create<Module>();
-  out->Copy(ctx, this);
-  return out;
-}
-
-void Module::Copy(CloneContext* ctx, const Module* src) {
-  ctx->Clone(global_declarations_, src->global_declarations_);
-
-  // During the clone, declarations may have been placed into the module.
-  // Clear everything out, as we're about to re-bin the declarations.
-  type_decls_.clear();
-  functions_.clear();
-  global_variables_.clear();
-
-  for (auto* decl : global_declarations_) {
-    if (!decl) {
-      TINT_ICE(AST, ctx->dst->Diagnostics())
-          << "src global declaration was nullptr";
-      continue;
-    }
-    BinGlobalDeclaration(decl, ctx->dst->Diagnostics());
-  }
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/module.h b/src/ast/module.h
deleted file mode 100644
index 5a1d8e8..0000000
--- a/src/ast/module.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_MODULE_H_
-#define SRC_AST_MODULE_H_
-
-#include <string>
-#include <vector>
-
-#include "src/ast/function.h"
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-class TypeDecl;
-
-/// Module holds the top-level AST types, functions and global variables used by
-/// a Program.
-class Module : public Castable<Module, Node> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Module(ProgramID pid, const Source& src);
-
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param global_decls the list of global types, functions, and variables, in
-  /// the order they were declared in the source program
-  Module(ProgramID pid,
-         const Source& src,
-         std::vector<const Node*> global_decls);
-
-  /// Destructor
-  ~Module() override;
-
-  /// @returns the declaration-ordered global declarations for the module
-  const std::vector<const Node*>& GlobalDeclarations() const {
-    return global_declarations_;
-  }
-
-  /// Add a global variable to the Builder
-  /// @param var the variable to add
-  void AddGlobalVariable(const Variable* var);
-
-  /// @returns true if the module has the global declaration `decl`
-  /// @param decl the declaration to check
-  bool HasGlobalDeclaration(Node* decl) const {
-    for (auto* d : global_declarations_) {
-      if (d == decl) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /// Adds a global declaration to the Builder.
-  /// @param decl the declaration to add
-  void AddGlobalDeclaration(const tint::ast::Node* decl);
-
-  /// @returns the global variables for the module
-  const VariableList& GlobalVariables() const { return global_variables_; }
-
-  /// @returns the global variables for the module
-  VariableList& GlobalVariables() { return global_variables_; }
-
-  /// Adds a type declaration to the Builder.
-  /// @param decl the type declaration to add
-  void AddTypeDecl(const TypeDecl* decl);
-
-  /// @returns the TypeDecl registered as a TypeDecl()
-  /// @param name the name of the type to search for
-  const TypeDecl* LookupType(Symbol name) const;
-
-  /// @returns the declared types in the module
-  const std::vector<const TypeDecl*>& TypeDecls() const { return type_decls_; }
-
-  /// Add a function to the Builder
-  /// @param func the function to add
-  void AddFunction(const Function* func);
-
-  /// @returns the functions declared in the module
-  const FunctionList& Functions() const { return functions_; }
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const Module* Clone(CloneContext* ctx) const override;
-
-  /// Copy copies the content of the Module src into this module.
-  /// @param ctx the clone context
-  /// @param src the module to copy into this module
-  void Copy(CloneContext* ctx, const Module* src);
-
- private:
-  /// Adds `decl` to either:
-  /// * #global_declarations_
-  /// * #type_decls_
-  /// * #functions_
-  void BinGlobalDeclaration(const tint::ast::Node* decl, diag::List& diags);
-
-  std::vector<const Node*> global_declarations_;
-  std::vector<const TypeDecl*> type_decls_;
-  FunctionList functions_;
-  VariableList global_variables_;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_MODULE_H_
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
deleted file mode 100644
index 70b9116..0000000
--- a/src/ast/module_clone_test.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2020 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 <unordered_set>
-
-#include "gtest/gtest.h"
-#include "src/reader/wgsl/parser.h"
-#include "src/writer/wgsl/generator.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-TEST(ModuleCloneTest, Clone) {
-#if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER
-  // Shader that exercises the bulk of the AST nodes and types.
-  // See also fuzzers/tint_ast_clone_fuzzer.cc for further coverage of cloning.
-  Source::File file("test.wgsl", R"([[block]]
-struct S0 {
-  @size(4)
-  m0 : u32;
-  m1 : array<u32>;
-};
-
-[[block]] struct S1 {
-  @size(4)
-  m0 : u32;
-  m1 : array<u32, 6>;
-};
-
-let c0 : i32 = 10;
-let c1 : bool = true;
-
-type t0 = @stride(16) array<vec4<f32>>;
-type t1 = array<vec4<f32>>;
-
-var<private> g0 : u32 = 20u;
-var<private> g1 : f32 = 123.0;
-@group(0) @binding(0) var g2 : texture_2d<f32>;
-@group(1) @binding(0) var g3 : texture_depth_2d;
-@group(2) @binding(0) var g4 : texture_storage_2d<rg32float, write>;
-@group(3) @binding(0) var g5 : texture_depth_cube_array;
-@group(4) @binding(0) var g6 : texture_external;
-
-var<private> g7 : vec3<f32>;
-@group(0) @binding(1) var<storage, write> g8 : S0;
-@group(1) @binding(1) var<storage, read> g9 : S0;
-@group(2) @binding(1) var<storage, read_write> g10 : S0;
-
-fn f0(p0 : bool) -> f32 {
-  if (p0) {
-    return 1.0;
-  }
-  return 0.0;
-}
-
-fn f1(p0 : f32, p1 : i32) -> f32 {
-  var l0 : i32 = 3;
-  var l1 : f32 = 8.0;
-  var l2 : u32 = bitcast<u32>(4);
-  var l3 : vec2<u32> = vec2<u32>(u32(l0), u32(l1));
-  var l4 : S1;
-  var l5 : u32 = l4.m1[5];
-  let l6 : ptr<private, u32> = &g0;
-  loop {
-    l0 = (p1 + 2);
-    if (((l0 % 4) == 0)) {
-      break;
-    }
-
-    continuing {
-      if (1 == 2) {
-        l0 = l0 - 1;
-      } else {
-        l0 = l0 - 2;
-      }
-    }
-  }
-  switch(l2) {
-    case 0u: {
-      break;
-    }
-    case 1u: {
-      return f0(true);
-    }
-    default: {
-      discard;
-    }
-  }
-  return 1.0;
-}
-
-@stage(fragment)
-fn main() {
-  f1(1.0, 2);
-}
-
-let declaration_order_check_0 : i32 = 1;
-
-type declaration_order_check_1 = f32;
-
-fn declaration_order_check_2() {}
-
-type declaration_order_check_3 = f32;
-
-let declaration_order_check_4 : i32 = 1;
-
-)");
-
-  // Parse the wgsl, create the src program
-  auto src = reader::wgsl::Parse(&file);
-
-  ASSERT_TRUE(src.IsValid()) << diag::Formatter().format(src.Diagnostics());
-
-  // Clone the src program to dst
-  Program dst(src.Clone());
-
-  ASSERT_TRUE(dst.IsValid()) << diag::Formatter().format(dst.Diagnostics());
-
-  // Expect the printed strings to match
-  EXPECT_EQ(Program::printer(&src), Program::printer(&dst));
-
-  // Check that none of the AST nodes or type pointers in dst are found in src
-  std::unordered_set<const ast::Node*> src_nodes;
-  for (auto* src_node : src.ASTNodes().Objects()) {
-    src_nodes.emplace(src_node);
-  }
-  std::unordered_set<const sem::Type*> src_types;
-  for (auto* src_type : src.Types()) {
-    src_types.emplace(src_type);
-  }
-  for (auto* dst_node : dst.ASTNodes().Objects()) {
-    ASSERT_EQ(src_nodes.count(dst_node), 0u);
-  }
-  for (auto* dst_type : dst.Types()) {
-    ASSERT_EQ(src_types.count(dst_type), 0u);
-  }
-
-  // Regenerate the wgsl for the src program. We use this instead of the
-  // original source so that reformatting doesn't impact the final wgsl
-  // comparison.
-  writer::wgsl::Options options;
-  std::string src_wgsl;
-  {
-    auto result = writer::wgsl::Generate(&src, options);
-    ASSERT_TRUE(result.success) << result.error;
-    src_wgsl = result.wgsl;
-
-    // Move the src program to a temporary that'll be dropped, so that the src
-    // program is released before we attempt to print the dst program. This
-    // guarantee that all the source program nodes and types are destructed and
-    // freed. ASAN should error if there's any remaining references in dst when
-    // we try to reconstruct the WGSL.
-    auto tmp = std::move(src);
-  }
-
-  // Print the dst module, check it matches the original source
-  auto result = writer::wgsl::Generate(&dst, options);
-  ASSERT_TRUE(result.success);
-  auto dst_wgsl = result.wgsl;
-  ASSERT_EQ(src_wgsl, dst_wgsl);
-
-#else  // #if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER
-  GTEST_SKIP() << "ModuleCloneTest requires TINT_BUILD_WGSL_READER and "
-                  "TINT_BUILD_WGSL_WRITER to be enabled";
-#endif
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
deleted file mode 100644
index c29b626..0000000
--- a/src/ast/module_test.cc
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-#include "src/clone_context.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ModuleTest = TestHelper;
-
-TEST_F(ModuleTest, Creation) {
-  EXPECT_EQ(Program(std::move(*this)).AST().Functions().size(), 0u);
-}
-
-TEST_F(ModuleTest, LookupFunction) {
-  auto* func = Func("main", VariableList{}, ty.f32(), StatementList{},
-                    ast::AttributeList{});
-
-  Program program(std::move(*this));
-  EXPECT_EQ(func,
-            program.AST().Functions().Find(program.Symbols().Get("main")));
-}
-
-TEST_F(ModuleTest, LookupFunctionMissing) {
-  Program program(std::move(*this));
-  EXPECT_EQ(nullptr,
-            program.AST().Functions().Find(program.Symbols().Get("Missing")));
-}
-
-TEST_F(ModuleTest, Assert_Null_GlobalVariable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder builder;
-        builder.AST().AddGlobalVariable(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ModuleTest, Assert_Null_TypeDecl) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder builder;
-        builder.AST().AddTypeDecl(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ModuleTest, Assert_DifferentProgramID_Function) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.AST().AddFunction(b2.create<ast::Function>(
-            b2.Symbols().Register("func"), VariableList{}, b2.ty.f32(),
-            b2.Block(), AttributeList{}, AttributeList{}));
-      },
-      "internal compiler error");
-}
-
-TEST_F(ModuleTest, Assert_DifferentProgramID_GlobalVariable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.AST().AddGlobalVariable(
-            b2.Var("var", b2.ty.i32(), ast::StorageClass::kPrivate));
-      },
-      "internal compiler error");
-}
-
-TEST_F(ModuleTest, Assert_Null_Function) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder builder;
-        builder.AST().AddFunction(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ModuleTest, CloneOrder) {
-  // Create a program with a function, alias decl and var decl.
-  Program p = [] {
-    ProgramBuilder b;
-    b.Func("F", {}, b.ty.void_(), {});
-    b.Alias("A", b.ty.u32());
-    b.Global("V", b.ty.i32(), ast::StorageClass::kPrivate);
-    return Program(std::move(b));
-  }();
-
-  // Clone the program, using ReplaceAll() to create new module-scope
-  // declarations. We want to test that these are added just before the
-  // declaration that triggered the ReplaceAll().
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &p);
-  ctx.ReplaceAll([&](const ast::Function*) -> const ast::Function* {
-    ctx.dst->Alias("inserted_before_F", cloned.ty.u32());
-    return nullptr;
-  });
-  ctx.ReplaceAll([&](const ast::Alias*) -> const ast::Alias* {
-    ctx.dst->Alias("inserted_before_A", cloned.ty.u32());
-    return nullptr;
-  });
-  ctx.ReplaceAll([&](const ast::Variable*) -> const ast::Variable* {
-    ctx.dst->Alias("inserted_before_V", cloned.ty.u32());
-    return nullptr;
-  });
-  ctx.Clone();
-
-  auto& decls = cloned.AST().GlobalDeclarations();
-  ASSERT_EQ(decls.size(), 6u);
-  EXPECT_TRUE(decls[1]->Is<ast::Function>());
-  EXPECT_TRUE(decls[3]->Is<ast::Alias>());
-  EXPECT_TRUE(decls[5]->Is<ast::Variable>());
-
-  ASSERT_TRUE(decls[0]->Is<ast::Alias>());
-  ASSERT_TRUE(decls[2]->Is<ast::Alias>());
-  ASSERT_TRUE(decls[4]->Is<ast::Alias>());
-
-  ASSERT_EQ(cloned.Symbols().NameFor(decls[0]->As<ast::Alias>()->name),
-            "inserted_before_F");
-  ASSERT_EQ(cloned.Symbols().NameFor(decls[2]->As<ast::Alias>()->name),
-            "inserted_before_A");
-  ASSERT_EQ(cloned.Symbols().NameFor(decls[4]->As<ast::Alias>()->name),
-            "inserted_before_V");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/multisampled_texture.cc b/src/ast/multisampled_texture.cc
deleted file mode 100644
index b1aa36c..0000000
--- a/src/ast/multisampled_texture.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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/ast/multisampled_texture.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::MultisampledTexture);
-
-namespace tint {
-namespace ast {
-
-MultisampledTexture::MultisampledTexture(ProgramID pid,
-                                         const Source& src,
-                                         TextureDimension d,
-                                         const Type* ty)
-    : Base(pid, src, d), type(ty) {
-  TINT_ASSERT(AST, type);
-}
-
-MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
-
-MultisampledTexture::~MultisampledTexture() = default;
-
-std::string MultisampledTexture::FriendlyName(
-    const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "texture_multisampled_" << dim << "<" << type->FriendlyName(symbols)
-      << ">";
-  return out.str();
-}
-
-const MultisampledTexture* MultisampledTexture::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<MultisampledTexture>(src, dim, ty);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/multisampled_texture.h b/src/ast/multisampled_texture.h
deleted file mode 100644
index 8cf65ab..0000000
--- a/src/ast/multisampled_texture.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 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_AST_MULTISAMPLED_TEXTURE_H_
-#define SRC_AST_MULTISAMPLED_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// A multisampled texture type.
-class MultisampledTexture : public Castable<MultisampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  /// @param type the data type of the multisampled texture
-  MultisampledTexture(ProgramID pid,
-                      const Source& src,
-                      TextureDimension dim,
-                      const Type* type);
-  /// Move constructor
-  MultisampledTexture(MultisampledTexture&&);
-  ~MultisampledTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const MultisampledTexture* Clone(CloneContext* ctx) const override;
-
-  /// The subtype of the multisampled texture
-  const Type* const type;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_MULTISAMPLED_TEXTURE_H_
diff --git a/src/ast/multisampled_texture_test.cc b/src/ast/multisampled_texture_test.cc
deleted file mode 100644
index 699d76b..0000000
--- a/src/ast/multisampled_texture_test.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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/ast/multisampled_texture.h"
-
-#include "src/ast/access.h"
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/bool.h"
-#include "src/ast/depth_texture.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampled_texture.h"
-#include "src/ast/sampler.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/struct.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/texture.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstMultisampledTextureTest = TestHelper;
-
-TEST_F(AstMultisampledTextureTest, IsTexture) {
-  auto* f32 = create<F32>();
-  Texture* ty = create<MultisampledTexture>(TextureDimension::kCube, f32);
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_TRUE(ty->Is<MultisampledTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstMultisampledTextureTest, Dim) {
-  auto* f32 = create<F32>();
-  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->dim, TextureDimension::k3d);
-}
-
-TEST_F(AstMultisampledTextureTest, Type) {
-  auto* f32 = create<F32>();
-  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->type, f32);
-}
-
-TEST_F(AstMultisampledTextureTest, FriendlyName) {
-  auto* f32 = create<F32>();
-  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/node.cc b/src/ast/node.cc
deleted file mode 100644
index 92ac290..0000000
--- a/src/ast/node.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 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/ast/node.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Node);
-
-namespace tint {
-namespace ast {
-
-Node::Node(ProgramID pid, const Source& src) : program_id(pid), source(src) {}
-
-Node::Node(Node&&) = default;
-
-Node::~Node() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/node.h b/src/ast/node.h
deleted file mode 100644
index fc0bc7f..0000000
--- a/src/ast/node.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 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_AST_NODE_H_
-#define SRC_AST_NODE_H_
-
-#include <string>
-
-#include "src/clone_context.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-namespace sem {
-class Type;
-}
-namespace sem {
-class Info;
-}
-
-namespace ast {
-
-/// AST base class node
-class Node : public Castable<Node, Cloneable> {
- public:
-  ~Node() override;
-
-  /// The identifier of the program that owns this node
-  const ProgramID program_id;
-
-  /// The node source data
-  const Source source;
-
- protected:
-  /// Create a new node
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the input source for the node
-  Node(ProgramID pid, const Source& src);
-  /// Move constructor
-  Node(Node&&);
-
- private:
-  Node(const Node&) = delete;
-};
-
-}  // namespace ast
-
-/// @param node a pointer to an AST node
-/// @returns the ProgramID of the given AST node.
-inline ProgramID ProgramIDOf(const ast::Node* node) {
-  return node ? node->program_id : ProgramID();
-}
-
-}  // namespace tint
-
-#endif  // SRC_AST_NODE_H_
diff --git a/src/ast/phony_expression.cc b/src/ast/phony_expression.cc
deleted file mode 100644
index dc01f03..0000000
--- a/src/ast/phony_expression.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/phony_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::PhonyExpression);
-
-namespace tint {
-namespace ast {
-
-PhonyExpression::PhonyExpression(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-PhonyExpression::PhonyExpression(PhonyExpression&&) = default;
-
-PhonyExpression::~PhonyExpression() = default;
-
-const PhonyExpression* PhonyExpression::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<PhonyExpression>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/phony_expression.h b/src/ast/phony_expression.h
deleted file mode 100644
index edfc7d0..0000000
--- a/src/ast/phony_expression.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_PHONY_EXPRESSION_H_
-#define SRC_AST_PHONY_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// Represents the `_` of a phony assignment `_ = <expr>`
-/// @see https://www.w3.org/TR/WGSL/#phony-assignment-section
-class PhonyExpression : public Castable<PhonyExpression, Expression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  PhonyExpression(ProgramID pid, const Source& src);
-  /// Move constructor
-  PhonyExpression(PhonyExpression&&);
-  ~PhonyExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const PhonyExpression* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_PHONY_EXPRESSION_H_
diff --git a/src/ast/phony_expression_test.cc b/src/ast/phony_expression_test.cc
deleted file mode 100644
index 15ec654..0000000
--- a/src/ast/phony_expression_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using IdentifierExpressionTest = TestHelper;
-
-TEST_F(IdentifierExpressionTest, Creation) {
-  EXPECT_NE(Phony(), nullptr);
-}
-
-TEST_F(IdentifierExpressionTest, Creation_WithSource) {
-  auto* p = Phony(Source{{20, 2}});
-
-  auto src = p->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(IdentifierExpressionTest, IsPhony) {
-  auto* p = Phony();
-  EXPECT_TRUE(p->Is<PhonyExpression>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/pipeline_stage.cc b/src/ast/pipeline_stage.cc
deleted file mode 100644
index ec2682b..0000000
--- a/src/ast/pipeline_stage.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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/ast/pipeline_stage.h"
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, PipelineStage stage) {
-  switch (stage) {
-    case PipelineStage::kNone: {
-      out << "none";
-      break;
-    }
-    case PipelineStage::kVertex: {
-      out << "vertex";
-      break;
-    }
-    case PipelineStage::kFragment: {
-      out << "fragment";
-      break;
-    }
-    case PipelineStage::kCompute: {
-      out << "compute";
-      break;
-    }
-  }
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/pipeline_stage.h b/src/ast/pipeline_stage.h
deleted file mode 100644
index ccaa8c9..0000000
--- a/src/ast/pipeline_stage.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2020 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_AST_PIPELINE_STAGE_H_
-#define SRC_AST_PIPELINE_STAGE_H_
-
-#include <ostream>
-
-namespace tint {
-namespace ast {
-
-/// The pipeline stage
-enum class PipelineStage { kNone = -1, kVertex, kFragment, kCompute };
-
-/// @param out the std::ostream to write to
-/// @param stage the PipelineStage
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, PipelineStage stage);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_PIPELINE_STAGE_H_
diff --git a/src/ast/pointer.cc b/src/ast/pointer.cc
deleted file mode 100644
index 2ba6d1b..0000000
--- a/src/ast/pointer.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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/ast/pointer.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Pointer);
-
-namespace tint {
-namespace ast {
-
-Pointer::Pointer(ProgramID pid,
-                 const Source& src,
-                 const Type* const subtype,
-                 ast::StorageClass sc,
-                 ast::Access ac)
-    : Base(pid, src), type(subtype), storage_class(sc), access(ac) {}
-
-std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "ptr<";
-  if (storage_class != ast::StorageClass::kNone) {
-    out << storage_class << ", ";
-  }
-  out << type->FriendlyName(symbols);
-  if (access != ast::Access::kUndefined) {
-    out << ", " << access;
-  }
-  out << ">";
-  return out.str();
-}
-
-Pointer::Pointer(Pointer&&) = default;
-
-Pointer::~Pointer() = default;
-
-const Pointer* Pointer::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<Pointer>(src, ty, storage_class, access);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/pointer.h b/src/ast/pointer.h
deleted file mode 100644
index 1cba57b..0000000
--- a/src/ast/pointer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 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_AST_POINTER_H_
-#define SRC_AST_POINTER_H_
-
-#include <string>
-
-#include "src/ast/access.h"
-#include "src/ast/storage_class.h"
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A pointer type.
-class Pointer : public Castable<Pointer, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param subtype the pointee type
-  /// @param storage_class the storage class of the pointer
-  /// @param access the access control of the pointer
-  Pointer(ProgramID pid,
-          const Source& src,
-          const Type* const subtype,
-          ast::StorageClass storage_class,
-          ast::Access access);
-  /// Move constructor
-  Pointer(Pointer&&);
-  ~Pointer() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Pointer* Clone(CloneContext* ctx) const override;
-
-  /// The pointee type
-  const Type* const type;
-
-  /// The storage class of the pointer
-  ast::StorageClass const storage_class;
-
-  /// The access control of the pointer
-  ast::Access const access;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_POINTER_H_
diff --git a/src/ast/pointer_test.cc b/src/ast/pointer_test.cc
deleted file mode 100644
index a8dff94..0000000
--- a/src/ast/pointer_test.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/pointer.h"
-
-#include "src/ast/i32.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstPointerTest = TestHelper;
-
-TEST_F(AstPointerTest, Creation) {
-  auto* i32 = create<I32>();
-  auto* p = create<Pointer>(i32, ast::StorageClass::kStorage, Access::kRead);
-  EXPECT_EQ(p->type, i32);
-  EXPECT_EQ(p->storage_class, ast::StorageClass::kStorage);
-  EXPECT_EQ(p->access, Access::kRead);
-}
-
-TEST_F(AstPointerTest, FriendlyName) {
-  auto* i32 = create<I32>();
-  auto* p =
-      create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kUndefined);
-  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>");
-}
-
-TEST_F(AstPointerTest, FriendlyNameWithAccess) {
-  auto* i32 = create<I32>();
-  auto* p =
-      create<Pointer>(i32, ast::StorageClass::kStorage, Access::kReadWrite);
-  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<storage, i32, read_write>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/return_statement.cc b/src/ast/return_statement.cc
deleted file mode 100644
index fe0c946..0000000
--- a/src/ast/return_statement.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/ast/return_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::ReturnStatement);
-
-namespace tint {
-namespace ast {
-
-ReturnStatement::ReturnStatement(ProgramID pid, const Source& src)
-    : Base(pid, src), value(nullptr) {}
-
-ReturnStatement::ReturnStatement(ProgramID pid,
-                                 const Source& src,
-                                 const Expression* val)
-    : Base(pid, src), value(val) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, value, program_id);
-}
-
-ReturnStatement::ReturnStatement(ReturnStatement&&) = default;
-
-ReturnStatement::~ReturnStatement() = default;
-
-const ReturnStatement* ReturnStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ret = ctx->Clone(value);
-  return ctx->dst->create<ReturnStatement>(src, ret);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/return_statement.h b/src/ast/return_statement.h
deleted file mode 100644
index e00f8ba..0000000
--- a/src/ast/return_statement.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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_AST_RETURN_STATEMENT_H_
-#define SRC_AST_RETURN_STATEMENT_H_
-
-#include "src/ast/expression.h"
-#include "src/ast/statement.h"
-
-namespace tint {
-namespace ast {
-
-/// A return statement
-class ReturnStatement : public Castable<ReturnStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  ReturnStatement(ProgramID pid, const Source& src);
-
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the return value
-  ReturnStatement(ProgramID pid, const Source& src, const Expression* value);
-  /// Move constructor
-  ReturnStatement(ReturnStatement&&);
-  ~ReturnStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const ReturnStatement* Clone(CloneContext* ctx) const override;
-
-  /// The value returned. May be null.
-  const Expression* const value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_RETURN_STATEMENT_H_
diff --git a/src/ast/return_statement_test.cc b/src/ast/return_statement_test.cc
deleted file mode 100644
index 3d2403a..0000000
--- a/src/ast/return_statement_test.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 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/ast/return_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ReturnStatementTest = TestHelper;
-
-TEST_F(ReturnStatementTest, Creation) {
-  auto* expr = Expr("expr");
-
-  auto* r = create<ReturnStatement>(expr);
-  EXPECT_EQ(r->value, expr);
-}
-
-TEST_F(ReturnStatementTest, Creation_WithSource) {
-  auto* r = create<ReturnStatement>(Source{Source::Location{20, 2}});
-  auto src = r->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(ReturnStatementTest, IsReturn) {
-  auto* r = create<ReturnStatement>();
-  EXPECT_TRUE(r->Is<ReturnStatement>());
-}
-
-TEST_F(ReturnStatementTest, WithoutValue) {
-  auto* r = create<ReturnStatement>();
-  EXPECT_EQ(r->value, nullptr);
-}
-
-TEST_F(ReturnStatementTest, WithValue) {
-  auto* expr = Expr("expr");
-  auto* r = create<ReturnStatement>(expr);
-  EXPECT_NE(r->value, nullptr);
-}
-
-TEST_F(ReturnStatementTest, Assert_DifferentProgramID_Expr) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<ReturnStatement>(b2.Expr(true));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sampled_texture.cc b/src/ast/sampled_texture.cc
deleted file mode 100644
index da010a0..0000000
--- a/src/ast/sampled_texture.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/sampled_texture.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::SampledTexture);
-
-namespace tint {
-namespace ast {
-
-SampledTexture::SampledTexture(ProgramID pid,
-                               const Source& src,
-                               TextureDimension d,
-                               const Type* ty)
-    : Base(pid, src, d), type(ty) {
-  TINT_ASSERT(AST, type);
-}
-
-SampledTexture::SampledTexture(SampledTexture&&) = default;
-
-SampledTexture::~SampledTexture() = default;
-
-std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "texture_" << dim << "<" << type->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-const SampledTexture* SampledTexture::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<SampledTexture>(src, dim, ty);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sampled_texture.h b/src/ast/sampled_texture.h
deleted file mode 100644
index 4cae225..0000000
--- a/src/ast/sampled_texture.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 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_AST_SAMPLED_TEXTURE_H_
-#define SRC_AST_SAMPLED_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// A sampled texture type.
-class SampledTexture : public Castable<SampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  /// @param type the data type of the sampled texture
-  SampledTexture(ProgramID pid,
-                 const Source& src,
-                 TextureDimension dim,
-                 const Type* type);
-  /// Move constructor
-  SampledTexture(SampledTexture&&);
-  ~SampledTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const SampledTexture* Clone(CloneContext* ctx) const override;
-
-  /// The subtype of the sampled texture
-  const Type* const type;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_SAMPLED_TEXTURE_H_
diff --git a/src/ast/sampled_texture_test.cc b/src/ast/sampled_texture_test.cc
deleted file mode 100644
index 74ade1e..0000000
--- a/src/ast/sampled_texture_test.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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/ast/sampled_texture.h"
-
-#include "src/ast/f32.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstSampledTextureTest = TestHelper;
-
-TEST_F(AstSampledTextureTest, IsTexture) {
-  auto* f32 = create<F32>();
-  Texture* ty = create<SampledTexture>(TextureDimension::kCube, f32);
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_TRUE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstSampledTextureTest, Dim) {
-  auto* f32 = create<F32>();
-  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->dim, TextureDimension::k3d);
-}
-
-TEST_F(AstSampledTextureTest, Type) {
-  auto* f32 = create<F32>();
-  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->type, f32);
-}
-
-TEST_F(AstSampledTextureTest, FriendlyName) {
-  auto* f32 = create<F32>();
-  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
-  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_3d<f32>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sampler.cc b/src/ast/sampler.cc
deleted file mode 100644
index a0b9261..0000000
--- a/src/ast/sampler.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/ast/sampler.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Sampler);
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, SamplerKind kind) {
-  switch (kind) {
-    case SamplerKind::kSampler:
-      out << "sampler";
-      break;
-    case SamplerKind::kComparisonSampler:
-      out << "comparison_sampler";
-      break;
-  }
-  return out;
-}
-
-Sampler::Sampler(ProgramID pid, const Source& src, SamplerKind k)
-    : Base(pid, src), kind(k) {}
-
-Sampler::Sampler(Sampler&&) = default;
-
-Sampler::~Sampler() = default;
-
-std::string Sampler::FriendlyName(const SymbolTable&) const {
-  return kind == SamplerKind::kSampler ? "sampler" : "sampler_comparison";
-}
-
-const Sampler* Sampler::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<Sampler>(src, kind);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sampler.h b/src/ast/sampler.h
deleted file mode 100644
index a2eacc4..0000000
--- a/src/ast/sampler.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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_AST_SAMPLER_H_
-#define SRC_AST_SAMPLER_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// The different kinds of samplers
-enum class SamplerKind {
-  /// A regular sampler
-  kSampler,
-  /// A comparison sampler
-  kComparisonSampler
-};
-
-/// @param out the std::ostream to write to
-/// @param kind the SamplerKind
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, SamplerKind kind);
-
-/// A sampler type.
-class Sampler : public Castable<Sampler, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param kind the kind of sampler
-  Sampler(ProgramID pid, const Source& src, SamplerKind kind);
-  /// Move constructor
-  Sampler(Sampler&&);
-  ~Sampler() override;
-
-  /// @returns true if this is a comparison sampler
-  bool IsComparison() const { return kind == SamplerKind::kComparisonSampler; }
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Sampler* Clone(CloneContext* ctx) const override;
-
-  /// The sampler type
-  const SamplerKind kind;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_SAMPLER_H_
diff --git a/src/ast/sampler_test.cc b/src/ast/sampler_test.cc
deleted file mode 100644
index 12de46e..0000000
--- a/src/ast/sampler_test.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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/ast/sampler.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstSamplerTest = TestHelper;
-
-TEST_F(AstSamplerTest, Creation) {
-  auto* s = create<Sampler>(SamplerKind::kSampler);
-  EXPECT_EQ(s->kind, SamplerKind::kSampler);
-}
-
-TEST_F(AstSamplerTest, Creation_ComparisonSampler) {
-  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
-  EXPECT_EQ(s->kind, SamplerKind::kComparisonSampler);
-  EXPECT_TRUE(s->IsComparison());
-}
-
-TEST_F(AstSamplerTest, FriendlyNameSampler) {
-  auto* s = create<Sampler>(SamplerKind::kSampler);
-  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler");
-}
-
-TEST_F(AstSamplerTest, FriendlyNameComparisonSampler) {
-  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
-  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler_comparison");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sint_literal_expression.cc b/src/ast/sint_literal_expression.cc
deleted file mode 100644
index 7288250..0000000
--- a/src/ast/sint_literal_expression.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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/ast/sint_literal_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::SintLiteralExpression);
-
-namespace tint {
-namespace ast {
-
-SintLiteralExpression::SintLiteralExpression(ProgramID pid,
-                                             const Source& src,
-                                             int32_t val)
-    : Base(pid, src), value(val) {}
-
-SintLiteralExpression::~SintLiteralExpression() = default;
-
-uint32_t SintLiteralExpression::ValueAsU32() const {
-  return static_cast<uint32_t>(value);
-}
-
-const SintLiteralExpression* SintLiteralExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<SintLiteralExpression>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/sint_literal_expression.h b/src/ast/sint_literal_expression.h
deleted file mode 100644
index dac1b3f..0000000
--- a/src/ast/sint_literal_expression.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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_AST_SINT_LITERAL_EXPRESSION_H_
-#define SRC_AST_SINT_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/ast/int_literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A signed int literal
-class SintLiteralExpression
-    : public Castable<SintLiteralExpression, IntLiteralExpression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the signed int literals value
-  SintLiteralExpression(ProgramID pid, const Source& src, int32_t value);
-  ~SintLiteralExpression() override;
-
-  /// @returns the literal value as a u32
-  uint32_t ValueAsU32() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const SintLiteralExpression* Clone(CloneContext* ctx) const override;
-
-  /// The int literal value
-  const int32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_SINT_LITERAL_EXPRESSION_H_
diff --git a/src/ast/sint_literal_expression_test.cc b/src/ast/sint_literal_expression_test.cc
deleted file mode 100644
index 5f3652e..0000000
--- a/src/ast/sint_literal_expression_test.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using SintLiteralExpressionTest = TestHelper;
-
-TEST_F(SintLiteralExpressionTest, Value) {
-  auto* i = create<SintLiteralExpression>(47);
-  ASSERT_TRUE(i->Is<SintLiteralExpression>());
-  EXPECT_EQ(i->value, 47);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/stage_attribute.cc b/src/ast/stage_attribute.cc
deleted file mode 100644
index 1efa991..0000000
--- a/src/ast/stage_attribute.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StageAttribute);
-
-namespace tint {
-namespace ast {
-
-StageAttribute::StageAttribute(ProgramID pid,
-                               const Source& src,
-                               PipelineStage s)
-    : Base(pid, src), stage(s) {}
-
-StageAttribute::~StageAttribute() = default;
-
-std::string StageAttribute::Name() const {
-  return "stage";
-}
-
-const StageAttribute* StageAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StageAttribute>(src, stage);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/stage_attribute.h b/src/ast/stage_attribute.h
deleted file mode 100644
index 687f491..0000000
--- a/src/ast/stage_attribute.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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_AST_STAGE_ATTRIBUTE_H_
-#define SRC_AST_STAGE_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-#include "src/ast/pipeline_stage.h"
-
-namespace tint {
-namespace ast {
-
-/// A workgroup attribute
-class StageAttribute : public Castable<StageAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param stage the pipeline stage
-  /// @param source the source of this attribute
-  StageAttribute(ProgramID program_id,
-                 const Source& source,
-                 PipelineStage stage);
-  ~StageAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StageAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The pipeline stage
-  const PipelineStage stage;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STAGE_ATTRIBUTE_H_
diff --git a/src/ast/stage_attribute_test.cc b/src/ast/stage_attribute_test.cc
deleted file mode 100644
index 4d447e5..0000000
--- a/src/ast/stage_attribute_test.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-
-#include "src/ast/test_helper.h"
-#include "src/ast/workgroup_attribute.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StageAttributeTest = TestHelper;
-
-TEST_F(StageAttributeTest, Creation_1param) {
-  auto* d = create<StageAttribute>(PipelineStage::kFragment);
-  EXPECT_EQ(d->stage, PipelineStage::kFragment);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/statement.cc b/src/ast/statement.cc
deleted file mode 100644
index 0121d54..0000000
--- a/src/ast/statement.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 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/ast/statement.h"
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/variable_decl_statement.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Statement);
-
-namespace tint {
-namespace ast {
-
-Statement::Statement(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Statement::Statement(Statement&&) = default;
-
-Statement::~Statement() = default;
-
-const char* Statement::Name() const {
-  if (Is<AssignmentStatement>()) {
-    return "assignment statement";
-  }
-  if (Is<BlockStatement>()) {
-    return "block statement";
-  }
-  if (Is<BreakStatement>()) {
-    return "break statement";
-  }
-  if (Is<CaseStatement>()) {
-    return "case statement";
-  }
-  if (Is<CallStatement>()) {
-    return "function call";
-  }
-  if (Is<ContinueStatement>()) {
-    return "continue statement";
-  }
-  if (Is<DiscardStatement>()) {
-    return "discard statement";
-  }
-  if (Is<ElseStatement>()) {
-    return "else statement";
-  }
-  if (Is<FallthroughStatement>()) {
-    return "fallthrough statement";
-  }
-  if (Is<IfStatement>()) {
-    return "if statement";
-  }
-  if (Is<LoopStatement>()) {
-    return "loop statement";
-  }
-  if (Is<ReturnStatement>()) {
-    return "return statement";
-  }
-  if (Is<SwitchStatement>()) {
-    return "switch statement";
-  }
-  if (Is<VariableDeclStatement>()) {
-    return "variable declaration";
-  }
-  return "statement";
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/statement.h b/src/ast/statement.h
deleted file mode 100644
index 567545b..0000000
--- a/src/ast/statement.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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_AST_STATEMENT_H_
-#define SRC_AST_STATEMENT_H_
-
-#include <vector>
-
-#include "src/ast/node.h"
-
-namespace tint {
-namespace ast {
-
-/// Base statement class
-class Statement : public Castable<Statement, Node> {
- public:
-  ~Statement() override;
-
-  /// @returns the human readable name for the statement type.
-  const char* Name() const;
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of the expression
-  Statement(ProgramID pid, const Source& src);
-  /// Move constructor
-  Statement(Statement&&);
-};
-
-/// A list of statements
-using StatementList = std::vector<const Statement*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STATEMENT_H_
diff --git a/src/ast/storage_class.cc b/src/ast/storage_class.cc
deleted file mode 100644
index 9cbb4e0..0000000
--- a/src/ast/storage_class.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/ast/storage_class.h"
-
-namespace tint {
-namespace ast {
-
-const char* ToString(StorageClass sc) {
-  switch (sc) {
-    case StorageClass::kInvalid:
-      return "invalid";
-    case StorageClass::kNone:
-      return "none";
-    case StorageClass::kInput:
-      return "in";
-    case StorageClass::kOutput:
-      return "out";
-    case StorageClass::kUniform:
-      return "uniform";
-    case StorageClass::kWorkgroup:
-      return "workgroup";
-    case StorageClass::kUniformConstant:
-      return "uniform_constant";
-    case StorageClass::kStorage:
-      return "storage";
-    case StorageClass::kPrivate:
-      return "private";
-    case StorageClass::kFunction:
-      return "function";
-  }
-  return "<unknown>";
-}
-std::ostream& operator<<(std::ostream& out, StorageClass sc) {
-  out << ToString(sc);
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/storage_class.h b/src/ast/storage_class.h
deleted file mode 100644
index e3009d3..0000000
--- a/src/ast/storage_class.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_STORAGE_CLASS_H_
-#define SRC_AST_STORAGE_CLASS_H_
-
-#include <ostream>
-
-namespace tint {
-namespace ast {
-
-/// Storage class of a given pointer.
-enum class StorageClass {
-  kInvalid = -1,
-  kNone,
-  kInput,
-  kOutput,
-  kUniform,
-  kWorkgroup,
-  kUniformConstant,
-  kStorage,
-  kPrivate,
-  kFunction
-};
-
-/// @returns true if the StorageClass is host-shareable
-/// @param sc the StorageClass
-/// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable
-inline bool IsHostShareable(StorageClass sc) {
-  return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
-}
-
-/// @param sc the StorageClass
-/// @return the name of the given storage class
-const char* ToString(StorageClass sc);
-
-/// @param out the std::ostream to write to
-/// @param sc the StorageClass
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, StorageClass sc);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STORAGE_CLASS_H_
diff --git a/src/ast/storage_texture.cc b/src/ast/storage_texture.cc
deleted file mode 100644
index 82105ce..0000000
--- a/src/ast/storage_texture.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2020 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/ast/storage_texture.h"
-
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/u32.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StorageTexture);
-
-namespace tint {
-namespace ast {
-
-// Note, these names match the names in the WGSL spec. This behaviour is used
-// in the WGSL writer to emit the texture format names.
-std::ostream& operator<<(std::ostream& out, TexelFormat format) {
-  switch (format) {
-    case TexelFormat::kNone:
-      out << "none";
-      break;
-    case TexelFormat::kR32Uint:
-      out << "r32uint";
-      break;
-    case TexelFormat::kR32Sint:
-      out << "r32sint";
-      break;
-    case TexelFormat::kR32Float:
-      out << "r32float";
-      break;
-    case TexelFormat::kRgba8Unorm:
-      out << "rgba8unorm";
-      break;
-    case TexelFormat::kRgba8Snorm:
-      out << "rgba8snorm";
-      break;
-    case TexelFormat::kRgba8Uint:
-      out << "rgba8uint";
-      break;
-    case TexelFormat::kRgba8Sint:
-      out << "rgba8sint";
-      break;
-    case TexelFormat::kRg32Uint:
-      out << "rg32uint";
-      break;
-    case TexelFormat::kRg32Sint:
-      out << "rg32sint";
-      break;
-    case TexelFormat::kRg32Float:
-      out << "rg32float";
-      break;
-    case TexelFormat::kRgba16Uint:
-      out << "rgba16uint";
-      break;
-    case TexelFormat::kRgba16Sint:
-      out << "rgba16sint";
-      break;
-    case TexelFormat::kRgba16Float:
-      out << "rgba16float";
-      break;
-    case TexelFormat::kRgba32Uint:
-      out << "rgba32uint";
-      break;
-    case TexelFormat::kRgba32Sint:
-      out << "rgba32sint";
-      break;
-    case TexelFormat::kRgba32Float:
-      out << "rgba32float";
-      break;
-  }
-  return out;
-}
-
-StorageTexture::StorageTexture(ProgramID pid,
-                               const Source& src,
-                               TextureDimension d,
-                               TexelFormat fmt,
-                               const Type* subtype,
-                               Access ac)
-    : Base(pid, src, d), format(fmt), type(subtype), access(ac) {}
-
-StorageTexture::StorageTexture(StorageTexture&&) = default;
-
-StorageTexture::~StorageTexture() = default;
-
-std::string StorageTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_storage_" << dim << "<" << format << ", " << access << ">";
-  return out.str();
-}
-
-const StorageTexture* StorageTexture::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<StorageTexture>(src, dim, format, ty, access);
-}
-
-Type* StorageTexture::SubtypeFor(TexelFormat format, ProgramBuilder& builder) {
-  switch (format) {
-    case TexelFormat::kR32Uint:
-    case TexelFormat::kRgba8Uint:
-    case TexelFormat::kRg32Uint:
-    case TexelFormat::kRgba16Uint:
-    case TexelFormat::kRgba32Uint: {
-      return builder.create<U32>();
-    }
-
-    case TexelFormat::kR32Sint:
-    case TexelFormat::kRgba8Sint:
-    case TexelFormat::kRg32Sint:
-    case TexelFormat::kRgba16Sint:
-    case TexelFormat::kRgba32Sint: {
-      return builder.create<I32>();
-    }
-
-    case TexelFormat::kRgba8Unorm:
-    case TexelFormat::kRgba8Snorm:
-    case TexelFormat::kR32Float:
-    case TexelFormat::kRg32Float:
-    case TexelFormat::kRgba16Float:
-    case TexelFormat::kRgba32Float: {
-      return builder.create<F32>();
-    }
-
-    case TexelFormat::kNone:
-      break;
-  }
-
-  return nullptr;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/storage_texture.h b/src/ast/storage_texture.h
deleted file mode 100644
index 30078ca..0000000
--- a/src/ast/storage_texture.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2020 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_AST_STORAGE_TEXTURE_H_
-#define SRC_AST_STORAGE_TEXTURE_H_
-
-#include <string>
-
-#include "src/ast/access.h"
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace ast {
-
-/// The texel format in the storage texture
-enum class TexelFormat {
-  kNone = -1,
-  kRgba8Unorm,
-  kRgba8Snorm,
-  kRgba8Uint,
-  kRgba8Sint,
-  kRgba16Uint,
-  kRgba16Sint,
-  kRgba16Float,
-  kR32Uint,
-  kR32Sint,
-  kR32Float,
-  kRg32Uint,
-  kRg32Sint,
-  kRg32Float,
-  kRgba32Uint,
-  kRgba32Sint,
-  kRgba32Float,
-};
-
-/// @param out the std::ostream to write to
-/// @param format the TexelFormat
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, TexelFormat format);
-
-/// A storage texture type.
-class StorageTexture : public Castable<StorageTexture, Texture> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  /// @param format the image format of the texture
-  /// @param subtype the storage subtype. Use SubtypeFor() to calculate this.
-  /// @param access_control the access control for the texture.
-  StorageTexture(ProgramID pid,
-                 const Source& src,
-                 TextureDimension dim,
-                 TexelFormat format,
-                 const Type* subtype,
-                 Access access_control);
-
-  /// Move constructor
-  StorageTexture(StorageTexture&&);
-  ~StorageTexture() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const StorageTexture* Clone(CloneContext* ctx) const override;
-
-  /// @param format the storage texture image format
-  /// @param builder the ProgramBuilder used to build the returned type
-  /// @returns the storage texture subtype for the given TexelFormat
-  static Type* SubtypeFor(TexelFormat format, ProgramBuilder& builder);
-
-  /// The image format
-  const TexelFormat format;
-
-  /// The storage subtype
-  const Type* const type;
-
-  /// The access control
-  const Access access;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STORAGE_TEXTURE_H_
diff --git a/src/ast/storage_texture_test.cc b/src/ast/storage_texture_test.cc
deleted file mode 100644
index 2fbf8cc..0000000
--- a/src/ast/storage_texture_test.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2020 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/ast/storage_texture.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstStorageTextureTest = TestHelper;
-
-TEST_F(AstStorageTextureTest, IsTexture) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
-  Texture* ty =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Float, subtype, Access::kRead);
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_TRUE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstStorageTextureTest, Dim) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
-  auto* s =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Float, subtype, Access::kRead);
-  EXPECT_EQ(s->dim, TextureDimension::k2dArray);
-}
-
-TEST_F(AstStorageTextureTest, Format) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
-  auto* s =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Float, subtype, Access::kRead);
-  EXPECT_EQ(s->format, TexelFormat::kRgba32Float);
-}
-
-TEST_F(AstStorageTextureTest, FriendlyName) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
-  auto* s =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Float, subtype, Access::kRead);
-  EXPECT_EQ(s->FriendlyName(Symbols()),
-            "texture_storage_2d_array<rgba32float, read>");
-}
-
-TEST_F(AstStorageTextureTest, F32) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
-  Type* s =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Float, subtype, Access::kRead);
-
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<F32>());
-}
-
-TEST_F(AstStorageTextureTest, U32) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRg32Uint, *this);
-  Type* s =
-      create<StorageTexture>(TextureDimension::k2dArray, TexelFormat::kRg32Uint,
-                             subtype, Access::kRead);
-
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<U32>());
-}
-
-TEST_F(AstStorageTextureTest, I32) {
-  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Sint, *this);
-  Type* s =
-      create<StorageTexture>(TextureDimension::k2dArray,
-                             TexelFormat::kRgba32Sint, subtype, Access::kRead);
-
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<I32>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/stride_attribute.cc b/src/ast/stride_attribute.cc
deleted file mode 100644
index eea604c..0000000
--- a/src/ast/stride_attribute.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/ast/stride_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StrideAttribute);
-
-namespace tint {
-namespace ast {
-
-StrideAttribute::StrideAttribute(ProgramID pid, const Source& src, uint32_t s)
-    : Base(pid, src), stride(s) {}
-
-StrideAttribute::~StrideAttribute() = default;
-
-std::string StrideAttribute::Name() const {
-  return "stride";
-}
-
-const StrideAttribute* StrideAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StrideAttribute>(src, stride);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/stride_attribute.h b/src/ast/stride_attribute.h
deleted file mode 100644
index 933ee1c..0000000
--- a/src/ast/stride_attribute.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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_AST_STRIDE_ATTRIBUTE_H_
-#define SRC_AST_STRIDE_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A stride attribute
-class StrideAttribute : public Castable<StrideAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param stride the stride value
-  StrideAttribute(ProgramID pid, const Source& src, uint32_t stride);
-  ~StrideAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StrideAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The stride value
-  const uint32_t stride;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRIDE_ATTRIBUTE_H_
diff --git a/src/ast/stride_attribute_test.cc b/src/ast/stride_attribute_test.cc
deleted file mode 100644
index 816b673..0000000
--- a/src/ast/stride_attribute_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StrideAttributeTest = TestHelper;
-
-TEST_F(StrideAttributeTest, Creation) {
-  auto* d = create<StrideAttribute>(2);
-  EXPECT_EQ(2u, d->stride);
-}
-
-TEST_F(StrideAttributeTest, Source) {
-  auto* d = create<StrideAttribute>(
-      Source{Source::Range{Source::Location{1, 2}, Source::Location{3, 4}}}, 2);
-  EXPECT_EQ(d->source.range.begin.line, 1u);
-  EXPECT_EQ(d->source.range.begin.column, 2u);
-  EXPECT_EQ(d->source.range.end.line, 3u);
-  EXPECT_EQ(d->source.range.end.column, 4u);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
deleted file mode 100644
index d3d5d41..0000000
--- a/src/ast/struct.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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/ast/struct.h"
-
-#include <string>
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Struct);
-
-namespace tint {
-namespace ast {
-
-Struct::Struct(ProgramID pid,
-               const Source& src,
-               Symbol n,
-               StructMemberList m,
-               AttributeList attrs)
-    : Base(pid, src, n), members(std::move(m)), attributes(std::move(attrs)) {
-  for (auto* mem : members) {
-    TINT_ASSERT(AST, mem);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, mem, program_id);
-  }
-  for (auto* attr : attributes) {
-    TINT_ASSERT(AST, attr);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
-  }
-}
-
-Struct::Struct(Struct&&) = default;
-
-Struct::~Struct() = default;
-
-const Struct* Struct::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto n = ctx->Clone(name);
-  auto mem = ctx->Clone(members);
-  auto attrs = ctx->Clone(attributes);
-  return ctx->dst->create<Struct>(src, n, mem, attrs);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct.h b/src/ast/struct.h
deleted file mode 100644
index 7f26661..0000000
--- a/src/ast/struct.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 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_AST_STRUCT_H_
-#define SRC_AST_STRUCT_H_
-
-#include <string>
-#include <utility>
-
-#include "src/ast/attribute.h"
-#include "src/ast/struct_member.h"
-#include "src/ast/type_decl.h"
-
-namespace tint {
-namespace ast {
-
-/// A struct statement.
-class Struct : public Castable<Struct, TypeDecl> {
- public:
-  /// Create a new struct statement
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node for the import statement
-  /// @param name The name of the structure
-  /// @param members The struct members
-  /// @param attributes The struct attributes
-  Struct(ProgramID pid,
-         const Source& src,
-         Symbol name,
-         StructMemberList members,
-         AttributeList attributes);
-  /// Move constructor
-  Struct(Struct&&);
-
-  ~Struct() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const Struct* Clone(CloneContext* ctx) const override;
-
-  /// The members
-  const StructMemberList members;
-
-  /// The struct attributes
-  const AttributeList attributes;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_H_
diff --git a/src/ast/struct_block_attribute.cc b/src/ast/struct_block_attribute.cc
deleted file mode 100644
index 0e21e9c..0000000
--- a/src/ast/struct_block_attribute.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StructBlockAttribute);
-
-namespace tint {
-namespace ast {
-
-StructBlockAttribute::StructBlockAttribute(ProgramID pid, const Source& src)
-    : Base(pid, src) {}
-
-StructBlockAttribute::~StructBlockAttribute() = default;
-
-std::string StructBlockAttribute::Name() const {
-  return "block";
-}
-
-const StructBlockAttribute* StructBlockAttribute::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StructBlockAttribute>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_block_attribute.h b/src/ast/struct_block_attribute.h
deleted file mode 100644
index 6f65c4e..0000000
--- a/src/ast/struct_block_attribute.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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_AST_STRUCT_BLOCK_ATTRIBUTE_H_
-#define SRC_AST_STRUCT_BLOCK_ATTRIBUTE_H_
-
-#include <string>
-#include <vector>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// The struct block attribute
-class StructBlockAttribute : public Castable<StructBlockAttribute, Attribute> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  StructBlockAttribute(ProgramID pid, const Source& src);
-  ~StructBlockAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StructBlockAttribute* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_BLOCK_ATTRIBUTE_H_
diff --git a/src/ast/struct_member.cc b/src/ast/struct_member.cc
deleted file mode 100644
index 8d6a94a..0000000
--- a/src/ast/struct_member.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/ast/struct_member.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMember);
-
-namespace tint {
-namespace ast {
-
-StructMember::StructMember(ProgramID pid,
-                           const Source& src,
-                           const Symbol& sym,
-                           const ast::Type* ty,
-                           AttributeList attrs)
-    : Base(pid, src), symbol(sym), type(ty), attributes(std::move(attrs)) {
-  TINT_ASSERT(AST, type);
-  TINT_ASSERT(AST, symbol.IsValid());
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
-  for (auto* attr : attributes) {
-    TINT_ASSERT(AST, attr);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
-  }
-}
-
-StructMember::StructMember(StructMember&&) = default;
-
-StructMember::~StructMember() = default;
-
-const StructMember* StructMember::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto sym = ctx->Clone(symbol);
-  auto* ty = ctx->Clone(type);
-  auto attrs = ctx->Clone(attributes);
-  return ctx->dst->create<StructMember>(src, sym, ty, attrs);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member.h b/src/ast/struct_member.h
deleted file mode 100644
index 3dd1600..0000000
--- a/src/ast/struct_member.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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_AST_STRUCT_MEMBER_H_
-#define SRC_AST_STRUCT_MEMBER_H_
-
-#include <utility>
-#include <vector>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declaration
-class Type;
-
-/// A struct member statement.
-class StructMember : public Castable<StructMember, Node> {
- public:
-  /// Create a new struct member statement
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node for the struct member statement
-  /// @param sym The struct member symbol
-  /// @param type The struct member type
-  /// @param attributes The struct member attributes
-  StructMember(ProgramID pid,
-               const Source& src,
-               const Symbol& sym,
-               const ast::Type* type,
-               AttributeList attributes);
-  /// Move constructor
-  StructMember(StructMember&&);
-
-  ~StructMember() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StructMember* Clone(CloneContext* ctx) const override;
-
-  /// The symbol
-  const Symbol symbol;
-
-  /// The type
-  const ast::Type* const type;
-
-  /// The attributes
-  const AttributeList attributes;
-};
-
-/// A list of struct members
-using StructMemberList = std::vector<const StructMember*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_MEMBER_H_
diff --git a/src/ast/struct_member_align_attribute.cc b/src/ast/struct_member_align_attribute.cc
deleted file mode 100644
index 8b33375..0000000
--- a/src/ast/struct_member_align_attribute.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_member_align_attribute.h"
-
-#include <string>
-
-#include "src/clone_context.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberAlignAttribute);
-
-namespace tint {
-namespace ast {
-
-StructMemberAlignAttribute::StructMemberAlignAttribute(ProgramID pid,
-                                                       const Source& src,
-                                                       uint32_t a)
-    : Base(pid, src), align(a) {}
-
-StructMemberAlignAttribute::~StructMemberAlignAttribute() = default;
-
-std::string StructMemberAlignAttribute::Name() const {
-  return "align";
-}
-
-const StructMemberAlignAttribute* StructMemberAlignAttribute::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StructMemberAlignAttribute>(src, align);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_align_attribute.h b/src/ast/struct_member_align_attribute.h
deleted file mode 100644
index 705c6c0..0000000
--- a/src/ast/struct_member_align_attribute.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
-#define SRC_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
-
-#include <stddef.h>
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A struct member align attribute
-class StructMemberAlignAttribute
-    : public Castable<StructMemberAlignAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param align the align value
-  StructMemberAlignAttribute(ProgramID pid, const Source& src, uint32_t align);
-  ~StructMemberAlignAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StructMemberAlignAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The align value
-  const uint32_t align;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
diff --git a/src/ast/struct_member_align_attribute_test.cc b/src/ast/struct_member_align_attribute_test.cc
deleted file mode 100644
index 8a39160..0000000
--- a/src/ast/struct_member_align_attribute_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_member_align_attribute.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StructMemberAlignAttributeTest = TestHelper;
-
-TEST_F(StructMemberAlignAttributeTest, Creation) {
-  auto* d = create<StructMemberAlignAttribute>(2);
-  EXPECT_EQ(2u, d->align);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_offset_attribute.cc b/src/ast/struct_member_offset_attribute.cc
deleted file mode 100644
index 0be408d..0000000
--- a/src/ast/struct_member_offset_attribute.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/struct_member_offset_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberOffsetAttribute);
-
-namespace tint {
-namespace ast {
-
-StructMemberOffsetAttribute::StructMemberOffsetAttribute(ProgramID pid,
-                                                         const Source& src,
-                                                         uint32_t o)
-    : Base(pid, src), offset(o) {}
-
-StructMemberOffsetAttribute::~StructMemberOffsetAttribute() = default;
-
-std::string StructMemberOffsetAttribute::Name() const {
-  return "offset";
-}
-
-const StructMemberOffsetAttribute* StructMemberOffsetAttribute::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StructMemberOffsetAttribute>(src, offset);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_offset_attribute.h b/src/ast/struct_member_offset_attribute.h
deleted file mode 100644
index e477d4d..0000000
--- a/src/ast/struct_member_offset_attribute.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 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_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
-#define SRC_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A struct member offset attribute
-/// @note The WGSL spec removed the `@offset(n)` attribute for `@size(n)`
-/// and `@align(n)` in https://github.com/gpuweb/gpuweb/pull/1447. However
-/// this attribute is kept because the SPIR-V reader has to deal with absolute
-/// offsets, and transforming these to size / align is complex and can be done
-/// in a number of ways. The Resolver is responsible for consuming the size and
-/// align attributes and transforming these into absolute offsets. It is
-/// trivial for the Resolver to handle `@offset(n)` or `@size(n)` /
-/// `@align(n)` attributes, so this is what we do, keeping all the layout
-/// logic in one place.
-class StructMemberOffsetAttribute
-    : public Castable<StructMemberOffsetAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param offset the offset value
-  StructMemberOffsetAttribute(ProgramID pid,
-                              const Source& src,
-                              uint32_t offset);
-  ~StructMemberOffsetAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StructMemberOffsetAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The offset value
-  const uint32_t offset;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
diff --git a/src/ast/struct_member_offset_attribute_test.cc b/src/ast/struct_member_offset_attribute_test.cc
deleted file mode 100644
index c1d64dc..0000000
--- a/src/ast/struct_member_offset_attribute_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StructMemberOffsetAttributeTest = TestHelper;
-
-TEST_F(StructMemberOffsetAttributeTest, Creation) {
-  auto* d = create<StructMemberOffsetAttribute>(2);
-  EXPECT_EQ(2u, d->offset);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_size_attribute.cc b/src/ast/struct_member_size_attribute.cc
deleted file mode 100644
index 44225bd..0000000
--- a/src/ast/struct_member_size_attribute.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_member_size_attribute.h"
-
-#include <string>
-
-#include "src/clone_context.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberSizeAttribute);
-
-namespace tint {
-namespace ast {
-
-StructMemberSizeAttribute::StructMemberSizeAttribute(ProgramID pid,
-                                                     const Source& src,
-                                                     uint32_t sz)
-    : Base(pid, src), size(sz) {}
-
-StructMemberSizeAttribute::~StructMemberSizeAttribute() = default;
-
-std::string StructMemberSizeAttribute::Name() const {
-  return "size";
-}
-
-const StructMemberSizeAttribute* StructMemberSizeAttribute::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<StructMemberSizeAttribute>(src, size);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_size_attribute.h b/src/ast/struct_member_size_attribute.h
deleted file mode 100644
index 8ba42d2..0000000
--- a/src/ast/struct_member_size_attribute.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
-#define SRC_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
-
-#include <stddef.h>
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-/// A struct member size attribute
-class StructMemberSizeAttribute
-    : public Castable<StructMemberSizeAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param size the size value
-  StructMemberSizeAttribute(ProgramID pid, const Source& src, uint32_t size);
-  ~StructMemberSizeAttribute() override;
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const StructMemberSizeAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The size value
-  const uint32_t size;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
diff --git a/src/ast/struct_member_size_attribute_test.cc b/src/ast/struct_member_size_attribute_test.cc
deleted file mode 100644
index 1adb8ae..0000000
--- a/src/ast/struct_member_size_attribute_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_member_size_attribute.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StructMemberSizeAttributeTest = TestHelper;
-
-TEST_F(StructMemberSizeAttributeTest, Creation) {
-  auto* d = create<StructMemberSizeAttribute>(2);
-  EXPECT_EQ(2u, d->size);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_member_test.cc b/src/ast/struct_member_test.cc
deleted file mode 100644
index 4987150..0000000
--- a/src/ast/struct_member_test.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using StructMemberTest = TestHelper;
-
-TEST_F(StructMemberTest, Creation) {
-  auto* st = Member("a", ty.i32(), {MemberSize(4)});
-  EXPECT_EQ(st->symbol, Symbol(1, ID()));
-  EXPECT_TRUE(st->type->Is<ast::I32>());
-  EXPECT_EQ(st->attributes.size(), 1u);
-  EXPECT_TRUE(st->attributes[0]->Is<StructMemberSizeAttribute>());
-  EXPECT_EQ(st->source.range.begin.line, 0u);
-  EXPECT_EQ(st->source.range.begin.column, 0u);
-  EXPECT_EQ(st->source.range.end.line, 0u);
-  EXPECT_EQ(st->source.range.end.column, 0u);
-}
-
-TEST_F(StructMemberTest, CreationWithSource) {
-  auto* st = Member(
-      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
-      "a", ty.i32());
-  EXPECT_EQ(st->symbol, Symbol(1, ID()));
-  EXPECT_TRUE(st->type->Is<ast::I32>());
-  EXPECT_EQ(st->attributes.size(), 0u);
-  EXPECT_EQ(st->source.range.begin.line, 27u);
-  EXPECT_EQ(st->source.range.begin.column, 4u);
-  EXPECT_EQ(st->source.range.end.line, 27u);
-  EXPECT_EQ(st->source.range.end.column, 8u);
-}
-
-TEST_F(StructMemberTest, Assert_Empty_Symbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Member("", b.ty.i32());
-      },
-      "internal compiler error");
-}
-
-TEST_F(StructMemberTest, Assert_Null_Type) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Member("a", nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(StructMemberTest, Assert_Null_Attribute) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Member("a", b.ty.i32(), {b.MemberSize(4), nullptr});
-      },
-      "internal compiler error");
-}
-
-TEST_F(StructMemberTest, Assert_DifferentProgramID_Symbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Member(b2.Sym("a"), b1.ty.i32(), {b1.MemberSize(4)});
-      },
-      "internal compiler error");
-}
-
-TEST_F(StructMemberTest, Assert_DifferentProgramID_Attribute) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Member("a", b1.ty.i32(), {b2.MemberSize(4)});
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
deleted file mode 100644
index 8d35914..0000000
--- a/src/ast/struct_test.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2020 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/ast/struct.h"
-#include "gtest/gtest-spi.h"
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/bool.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampler.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/texture.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstStructTest = TestHelper;
-
-TEST_F(AstStructTest, Creation) {
-  auto name = Sym("s");
-  auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())},
-                           AttributeList{});
-  EXPECT_EQ(s->name, name);
-  EXPECT_EQ(s->members.size(), 1u);
-  EXPECT_TRUE(s->attributes.empty());
-  EXPECT_EQ(s->source.range.begin.line, 0u);
-  EXPECT_EQ(s->source.range.begin.column, 0u);
-  EXPECT_EQ(s->source.range.end.line, 0u);
-  EXPECT_EQ(s->source.range.end.column, 0u);
-}
-
-TEST_F(AstStructTest, Creation_WithAttributes) {
-  auto name = Sym("s");
-  AttributeList attrs;
-  attrs.push_back(create<StructBlockAttribute>());
-
-  auto* s =
-      create<Struct>(name, StructMemberList{Member("a", ty.i32())}, attrs);
-  EXPECT_EQ(s->name, name);
-  EXPECT_EQ(s->members.size(), 1u);
-  ASSERT_EQ(s->attributes.size(), 1u);
-  EXPECT_TRUE(s->attributes[0]->Is<StructBlockAttribute>());
-  EXPECT_EQ(s->source.range.begin.line, 0u);
-  EXPECT_EQ(s->source.range.begin.column, 0u);
-  EXPECT_EQ(s->source.range.end.line, 0u);
-  EXPECT_EQ(s->source.range.end.column, 0u);
-}
-
-TEST_F(AstStructTest, CreationWithSourceAndAttributes) {
-  auto name = Sym("s");
-  auto* s = create<Struct>(
-      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
-      name, StructMemberList{Member("a", ty.i32())},
-      AttributeList{create<StructBlockAttribute>()});
-  EXPECT_EQ(s->name, name);
-  EXPECT_EQ(s->members.size(), 1u);
-  ASSERT_EQ(s->attributes.size(), 1u);
-  EXPECT_TRUE(s->attributes[0]->Is<StructBlockAttribute>());
-  EXPECT_EQ(s->source.range.begin.line, 27u);
-  EXPECT_EQ(s->source.range.begin.column, 4u);
-  EXPECT_EQ(s->source.range.end.line, 27u);
-  EXPECT_EQ(s->source.range.end.column, 8u);
-}
-
-TEST_F(AstStructTest, Assert_Null_StructMember) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<Struct>(b.Sym("S"),
-                         StructMemberList{b.Member("a", b.ty.i32()), nullptr},
-                         AttributeList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(AstStructTest, Assert_Null_Attribute) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<Struct>(b.Sym("S"),
-                         StructMemberList{b.Member("a", b.ty.i32())},
-                         AttributeList{nullptr});
-      },
-      "internal compiler error");
-}
-
-TEST_F(AstStructTest, Assert_DifferentProgramID_StructMember) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<Struct>(b1.Sym("S"),
-                          StructMemberList{b2.Member("a", b2.ty.i32())},
-                          AttributeList{});
-      },
-      "internal compiler error");
-}
-
-TEST_F(AstStructTest, Assert_DifferentProgramID_Attribute) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<Struct>(b1.Sym("S"),
-                          StructMemberList{b1.Member("a", b1.ty.i32())},
-                          AttributeList{b2.create<StructBlockAttribute>()});
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/switch_statement.cc b/src/ast/switch_statement.cc
deleted file mode 100644
index eeb74bf..0000000
--- a/src/ast/switch_statement.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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/ast/switch_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::SwitchStatement);
-
-namespace tint {
-namespace ast {
-
-SwitchStatement::SwitchStatement(ProgramID pid,
-                                 const Source& src,
-                                 const Expression* cond,
-                                 CaseStatementList b)
-    : Base(pid, src), condition(cond), body(b) {
-  TINT_ASSERT(AST, condition);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
-  for (auto* stmt : body) {
-    TINT_ASSERT(AST, stmt);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id);
-  }
-}
-
-SwitchStatement::SwitchStatement(SwitchStatement&&) = default;
-
-SwitchStatement::~SwitchStatement() = default;
-
-const SwitchStatement* SwitchStatement::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* cond = ctx->Clone(condition);
-  auto b = ctx->Clone(body);
-  return ctx->dst->create<SwitchStatement>(src, cond, b);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/switch_statement.h b/src/ast/switch_statement.h
deleted file mode 100644
index 711e553..0000000
--- a/src/ast/switch_statement.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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_AST_SWITCH_STATEMENT_H_
-#define SRC_AST_SWITCH_STATEMENT_H_
-
-#include "src/ast/case_statement.h"
-#include "src/ast/expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A switch statement
-class SwitchStatement : public Castable<SwitchStatement, Statement> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param condition the switch condition
-  /// @param body the switch body
-  SwitchStatement(ProgramID pid,
-                  const Source& src,
-                  const Expression* condition,
-                  CaseStatementList body);
-  /// Move constructor
-  SwitchStatement(SwitchStatement&&);
-  ~SwitchStatement() override;
-
-  /// @returns true if this is a default statement
-  bool IsDefault() const { return condition == nullptr; }
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const SwitchStatement* Clone(CloneContext* ctx) const override;
-
-  /// The switch condition or nullptr if none set
-  const Expression* const condition;
-
-  /// The Switch body
-  const CaseStatementList body;
-  SwitchStatement(const SwitchStatement&) = delete;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_SWITCH_STATEMENT_H_
diff --git a/src/ast/switch_statement_test.cc b/src/ast/switch_statement_test.cc
deleted file mode 100644
index 593f0aa..0000000
--- a/src/ast/switch_statement_test.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 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/ast/switch_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using SwitchStatementTest = TestHelper;
-
-TEST_F(SwitchStatementTest, Creation) {
-  CaseSelectorList lit;
-  lit.push_back(create<SintLiteralExpression>(1));
-
-  auto* ident = Expr("ident");
-  CaseStatementList body;
-  auto* case_stmt = create<CaseStatement>(lit, Block());
-  body.push_back(case_stmt);
-
-  auto* stmt = create<SwitchStatement>(ident, body);
-  EXPECT_EQ(stmt->condition, ident);
-  ASSERT_EQ(stmt->body.size(), 1u);
-  EXPECT_EQ(stmt->body[0], case_stmt);
-}
-
-TEST_F(SwitchStatementTest, Creation_WithSource) {
-  auto* ident = Expr("ident");
-
-  auto* stmt = create<SwitchStatement>(Source{Source::Location{20, 2}}, ident,
-                                       CaseStatementList());
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(SwitchStatementTest, IsSwitch) {
-  CaseSelectorList lit;
-  lit.push_back(create<SintLiteralExpression>(2));
-
-  auto* ident = Expr("ident");
-  CaseStatementList body;
-  body.push_back(create<CaseStatement>(lit, Block()));
-
-  auto* stmt = create<SwitchStatement>(ident, body);
-  EXPECT_TRUE(stmt->Is<SwitchStatement>());
-}
-
-TEST_F(SwitchStatementTest, Assert_Null_Condition) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        CaseStatementList cases;
-        cases.push_back(
-            b.create<CaseStatement>(CaseSelectorList{b.Expr(1)}, b.Block()));
-        b.create<SwitchStatement>(nullptr, cases);
-      },
-      "internal compiler error");
-}
-
-TEST_F(SwitchStatementTest, Assert_Null_CaseStatement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr});
-      },
-      "internal compiler error");
-}
-
-TEST_F(SwitchStatementTest, Assert_DifferentProgramID_Condition) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<SwitchStatement>(b2.Expr(true), CaseStatementList{
-                                                      b1.create<CaseStatement>(
-                                                          CaseSelectorList{
-                                                              b1.Expr(1),
-                                                          },
-                                                          b1.Block()),
-                                                  });
-      },
-      "internal compiler error");
-}
-
-TEST_F(SwitchStatementTest, Assert_DifferentProgramID_CaseStatement) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<SwitchStatement>(b1.Expr(true), CaseStatementList{
-                                                      b2.create<CaseStatement>(
-                                                          CaseSelectorList{
-                                                              b2.Expr(1),
-                                                          },
-                                                          b2.Block()),
-                                                  });
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/test_helper.h b/src/ast/test_helper.h
deleted file mode 100644
index 2bfc79b..0000000
--- a/src/ast/test_helper.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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_AST_TEST_HELPER_H_
-#define SRC_AST_TEST_HELPER_H_
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace ast {
-
-/// Helper base class for testing
-template <typename BASE>
-class TestHelperBase : public BASE, public ProgramBuilder {};
-
-/// Helper class for testing that derives from testing::Test.
-using TestHelper = TestHelperBase<testing::Test>;
-
-/// Helper class for testing that derives from `T`.
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TEST_HELPER_H_
diff --git a/src/ast/texture.cc b/src/ast/texture.cc
deleted file mode 100644
index 6c54e24..0000000
--- a/src/ast/texture.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 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/ast/texture.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Texture);
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, TextureDimension dim) {
-  switch (dim) {
-    case TextureDimension::kNone:
-      out << "None";
-      break;
-    case TextureDimension::k1d:
-      out << "1d";
-      break;
-    case TextureDimension::k2d:
-      out << "2d";
-      break;
-    case TextureDimension::k2dArray:
-      out << "2d_array";
-      break;
-    case TextureDimension::k3d:
-      out << "3d";
-      break;
-    case TextureDimension::kCube:
-      out << "cube";
-      break;
-    case TextureDimension::kCubeArray:
-      out << "cube_array";
-      break;
-  }
-  return out;
-}
-
-bool IsTextureArray(TextureDimension dim) {
-  switch (dim) {
-    case TextureDimension::k2dArray:
-    case TextureDimension::kCubeArray:
-      return true;
-    case TextureDimension::k2d:
-    case TextureDimension::kNone:
-    case TextureDimension::k1d:
-    case TextureDimension::k3d:
-    case TextureDimension::kCube:
-      return false;
-  }
-  return false;
-}
-
-int NumCoordinateAxes(TextureDimension dim) {
-  switch (dim) {
-    case TextureDimension::kNone:
-      return 0;
-    case TextureDimension::k1d:
-      return 1;
-    case TextureDimension::k2d:
-    case TextureDimension::k2dArray:
-      return 2;
-    case TextureDimension::k3d:
-    case TextureDimension::kCube:
-    case TextureDimension::kCubeArray:
-      return 3;
-  }
-  return 0;
-}
-
-Texture::Texture(ProgramID pid, const Source& src, TextureDimension d)
-    : Base(pid, src), dim(d) {}
-
-Texture::Texture(Texture&&) = default;
-
-Texture::~Texture() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/texture.h b/src/ast/texture.h
deleted file mode 100644
index e3383a7..0000000
--- a/src/ast/texture.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 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_AST_TEXTURE_H_
-#define SRC_AST_TEXTURE_H_
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// The dimensionality of the texture
-enum class TextureDimension {
-  /// Invalid texture
-  kNone = -1,
-  /// 1 dimensional texture
-  k1d,
-  /// 2 dimensional texture
-  k2d,
-  /// 2 dimensional array texture
-  k2dArray,
-  /// 3 dimensional texture
-  k3d,
-  /// cube texture
-  kCube,
-  /// cube array texture
-  kCubeArray,
-};
-
-/// @param out the std::ostream to write to
-/// @param dim the TextureDimension
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, TextureDimension dim);
-
-/// @param dim the TextureDimension to query
-/// @return true if the given TextureDimension is an array texture
-bool IsTextureArray(TextureDimension dim);
-
-/// Returns the number of axes in the coordinate used for accessing
-/// the texture, where an access is one of: sampling, fetching, load,
-/// or store.
-///  None -> 0
-///  1D -> 1
-///  2D, 2DArray -> 2
-///  3D, Cube, CubeArray -> 3
-/// Note: To sample a cube texture, the coordinate has 3 dimensions,
-/// but textureDimensions on a cube or cube array returns a 2-element
-/// size, representing the (x,y) size of each cube face, in texels.
-/// @param dim the TextureDimension to query
-/// @return number of dimensions in a coordinate for the dimensionality
-int NumCoordinateAxes(TextureDimension dim);
-
-/// A texture type.
-class Texture : public Castable<Texture, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param dim the dimensionality of the texture
-  Texture(ProgramID pid, const Source& src, TextureDimension dim);
-  /// Move constructor
-  Texture(Texture&&);
-  ~Texture() override;
-
-  /// The texture dimension
-  const TextureDimension dim;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TEXTURE_H_
diff --git a/src/ast/texture_test.cc b/src/ast/texture_test.cc
deleted file mode 100644
index 10ff52b..0000000
--- a/src/ast/texture_test.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/texture.h"
-
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/bool.h"
-#include "src/ast/f32.h"
-#include "src/ast/i32.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampler.h"
-#include "src/ast/struct.h"
-#include "src/ast/test_helper.h"
-#include "src/ast/u32.h"
-#include "src/ast/vector.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstTextureTypeTest = TestHelper;
-
-TEST_F(AstTextureTypeTest, IsTextureArray) {
-  EXPECT_EQ(false, IsTextureArray(TextureDimension::kNone));
-  EXPECT_EQ(false, IsTextureArray(TextureDimension::k1d));
-  EXPECT_EQ(false, IsTextureArray(TextureDimension::k2d));
-  EXPECT_EQ(true, IsTextureArray(TextureDimension::k2dArray));
-  EXPECT_EQ(false, IsTextureArray(TextureDimension::k3d));
-  EXPECT_EQ(false, IsTextureArray(TextureDimension::kCube));
-  EXPECT_EQ(true, IsTextureArray(TextureDimension::kCubeArray));
-}
-
-TEST_F(AstTextureTypeTest, NumCoordinateAxes) {
-  EXPECT_EQ(0, NumCoordinateAxes(TextureDimension::kNone));
-  EXPECT_EQ(1, NumCoordinateAxes(TextureDimension::k1d));
-  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2d));
-  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2dArray));
-  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::k3d));
-  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCube));
-  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCubeArray));
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/traverse_expressions.h b/src/ast/traverse_expressions.h
deleted file mode 100644
index b578941..0000000
--- a/src/ast/traverse_expressions.h
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_TRAVERSE_EXPRESSIONS_H_
-#define SRC_AST_TRAVERSE_EXPRESSIONS_H_
-
-#include <vector>
-
-#include "src/ast/binary_expression.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/call_expression.h"
-#include "src/ast/index_accessor_expression.h"
-#include "src/ast/literal_expression.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/phony_expression.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/utils/reverse.h"
-
-namespace tint {
-namespace ast {
-
-/// The action to perform after calling the TraverseExpressions() callback
-/// function.
-enum class TraverseAction {
-  /// Stop traversal immediately.
-  Stop,
-  /// Descend into this expression.
-  Descend,
-  /// Do not descend into this expression.
-  Skip,
-};
-
-/// The order TraverseExpressions() will traverse expressions
-enum class TraverseOrder {
-  /// Expressions will be traversed from left to right
-  LeftToRight,
-  /// Expressions will be traversed from right to left
-  RightToLeft,
-};
-
-/// TraverseExpressions performs a depth-first traversal of the expression nodes
-/// from `root`, calling `callback` for each of the visited expressions that
-/// match the predicate parameter type, in pre-ordering (root first).
-/// @param root the root expression node
-/// @param diags the diagnostics used for error messages
-/// @param callback the callback function. Must be of the signature:
-///        `TraverseAction(const T*)` where T is an ast::Expression type.
-/// @return true on success, false on error
-template <TraverseOrder ORDER = TraverseOrder::LeftToRight, typename CALLBACK>
-bool TraverseExpressions(const ast::Expression* root,
-                         diag::List& diags,
-                         CALLBACK&& callback) {
-  using EXPR_TYPE = std::remove_pointer_t<traits::ParameterType<CALLBACK, 0>>;
-  std::vector<const ast::Expression*> to_visit{root};
-
-  auto push_pair = [&](const ast::Expression* left,
-                       const ast::Expression* right) {
-    if (ORDER == TraverseOrder::LeftToRight) {
-      to_visit.push_back(right);
-      to_visit.push_back(left);
-    } else {
-      to_visit.push_back(left);
-      to_visit.push_back(right);
-    }
-  };
-  auto push_list = [&](const std::vector<const ast::Expression*>& exprs) {
-    if (ORDER == TraverseOrder::LeftToRight) {
-      for (auto* expr : utils::Reverse(exprs)) {
-        to_visit.push_back(expr);
-      }
-    } else {
-      for (auto* expr : exprs) {
-        to_visit.push_back(expr);
-      }
-    }
-  };
-
-  while (!to_visit.empty()) {
-    auto* expr = to_visit.back();
-    to_visit.pop_back();
-
-    if (auto* filtered = expr->As<EXPR_TYPE>()) {
-      switch (callback(filtered)) {
-        case TraverseAction::Stop:
-          return true;
-        case TraverseAction::Skip:
-          continue;
-        case TraverseAction::Descend:
-          break;
-      }
-    }
-
-    bool ok = Switch(
-        expr,
-        [&](const IndexAccessorExpression* idx) {
-          push_pair(idx->object, idx->index);
-          return true;
-        },
-        [&](const BinaryExpression* bin_op) {
-          push_pair(bin_op->lhs, bin_op->rhs);
-          return true;
-        },
-        [&](const BitcastExpression* bitcast) {
-          to_visit.push_back(bitcast->expr);
-          return true;
-        },
-        [&](const CallExpression* call) {
-          // TODO(crbug.com/tint/1257): Resolver breaks if we actually include
-          // the function name in the traversal. to_visit.push_back(call->func);
-          push_list(call->args);
-          return true;
-        },
-        [&](const MemberAccessorExpression* member) {
-          // TODO(crbug.com/tint/1257): Resolver breaks if we actually include
-          // the member name in the traversal. push_pair(member->structure,
-          // member->member);
-          to_visit.push_back(member->structure);
-          return true;
-        },
-        [&](const UnaryOpExpression* unary) {
-          to_visit.push_back(unary->expr);
-          return true;
-        },
-        [&](Default) {
-          if (expr->IsAnyOf<LiteralExpression, IdentifierExpression,
-                            PhonyExpression>()) {
-            return true;  // Leaf expression
-          }
-          TINT_ICE(AST, diags)
-              << "unhandled expression type: " << expr->TypeInfo().name;
-          return false;
-        });
-    if (!ok) {
-      return false;
-    }
-  }
-  return true;
-}
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TRAVERSE_EXPRESSIONS_H_
diff --git a/src/ast/traverse_expressions_test.cc b/src/ast/traverse_expressions_test.cc
deleted file mode 100644
index f5f3324..0000000
--- a/src/ast/traverse_expressions_test.cc
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/traverse_expressions.h"
-#include "gmock/gmock.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using ::testing::ElementsAre;
-
-using TraverseExpressionsTest = TestHelper;
-
-TEST_F(TraverseExpressionsTest, DescendIndexAccessor) {
-  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
-  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
-                                           IndexAccessor(e[2], e[3])};
-  auto* root = IndexAccessor(i[0], i[1]);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3]));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0]));
-  }
-}
-
-TEST_F(TraverseExpressionsTest, DescendBinaryExpression) {
-  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
-  std::vector<const ast::Expression*> i = {Add(e[0], e[1]), Sub(e[2], e[3])};
-  auto* root = Mul(i[0], i[1]);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3]));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0]));
-  }
-}
-
-TEST_F(TraverseExpressionsTest, DescendBitcastExpression) {
-  auto* e = Expr(1);
-  auto* b0 = Bitcast<i32>(e);
-  auto* b1 = Bitcast<i32>(b0);
-  auto* b2 = Bitcast<i32>(b1);
-  auto* root = Bitcast<i32>(b2);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, b2, b1, b0, e));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, b2, b1, b0, e));
-  }
-}
-
-TEST_F(TraverseExpressionsTest, DescendCallExpression) {
-  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
-  std::vector<const ast::Expression*> c = {Call("a", e[0], e[1]),
-                                           Call("b", e[2], e[3])};
-  auto* root = Call("c", c[0], c[1]);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, c[0], e[0], e[1], c[1], e[2], e[3]));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, c[1], e[3], e[2], c[0], e[1], e[0]));
-  }
-}
-
-// TODO(crbug.com/tint/1257): Test ignores member accessor 'member' field.
-// Replace with the test below when fixed.
-TEST_F(TraverseExpressionsTest, DescendMemberIndexExpression) {
-  auto* e = Expr(1);
-  auto* m = MemberAccessor(e, Expr("a"));
-  auto* root = MemberAccessor(m, Expr("b"));
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, m, e));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, m, e));
-  }
-}
-
-// TODO(crbug.com/tint/1257): The correct test for DescendMemberIndexExpression.
-TEST_F(TraverseExpressionsTest, DISABLED_DescendMemberIndexExpression) {
-  auto* e = Expr(1);
-  std::vector<const ast::IdentifierExpression*> i = {Expr("a"), Expr("b")};
-  auto* m = MemberAccessor(e, i[0]);
-  auto* root = MemberAccessor(m, i[1]);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, m, e, i[0], i[1]));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, i[1], m, i[0], e));
-  }
-}
-
-TEST_F(TraverseExpressionsTest, DescendUnaryExpression) {
-  auto* e = Expr(1);
-  auto* u0 = AddressOf(e);
-  auto* u1 = Deref(u0);
-  auto* u2 = AddressOf(u1);
-  auto* root = Deref(u2);
-  {
-    std::vector<const ast::Expression*> l2r;
-    TraverseExpressions<TraverseOrder::LeftToRight>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          l2r.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(l2r, ElementsAre(root, u2, u1, u0, e));
-  }
-  {
-    std::vector<const ast::Expression*> r2l;
-    TraverseExpressions<TraverseOrder::RightToLeft>(
-        root, Diagnostics(), [&](const ast::Expression* expr) {
-          r2l.push_back(expr);
-          return ast::TraverseAction::Descend;
-        });
-    EXPECT_THAT(r2l, ElementsAre(root, u2, u1, u0, e));
-  }
-}
-
-TEST_F(TraverseExpressionsTest, Skip) {
-  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
-  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
-                                           IndexAccessor(e[2], e[3])};
-  auto* root = IndexAccessor(i[0], i[1]);
-  std::vector<const ast::Expression*> order;
-  TraverseExpressions<TraverseOrder::LeftToRight>(
-      root, Diagnostics(), [&](const ast::Expression* expr) {
-        order.push_back(expr);
-        return expr == i[0] ? ast::TraverseAction::Skip
-                            : ast::TraverseAction::Descend;
-      });
-  EXPECT_THAT(order, ElementsAre(root, i[0], i[1], e[2], e[3]));
-}
-
-TEST_F(TraverseExpressionsTest, Stop) {
-  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
-  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
-                                           IndexAccessor(e[2], e[3])};
-  auto* root = IndexAccessor(i[0], i[1]);
-  std::vector<const ast::Expression*> order;
-  TraverseExpressions<TraverseOrder::LeftToRight>(
-      root, Diagnostics(), [&](const ast::Expression* expr) {
-        order.push_back(expr);
-        return expr == i[0] ? ast::TraverseAction::Stop
-                            : ast::TraverseAction::Descend;
-      });
-  EXPECT_THAT(order, ElementsAre(root, i[0]));
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/type.h b/src/ast/type.h
deleted file mode 100644
index 3f60261..0000000
--- a/src/ast/type.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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_AST_TYPE_H_
-#define SRC_AST_TYPE_H_
-
-#include <string>
-
-#include "src/ast/node.h"
-#include "src/clone_context.h"
-
-namespace tint {
-
-// Forward declarations
-class ProgramBuilder;
-class SymbolTable;
-
-namespace ast {
-
-/// Base class for a type in the system
-class Type : public Castable<Type, Node> {
- public:
-  /// Move constructor
-  Type(Type&&);
-  ~Type() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
-
- protected:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Type(ProgramID pid, const Source& src);
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TYPE_H_
diff --git a/src/ast/type_decl.cc b/src/ast/type_decl.cc
deleted file mode 100644
index 94c94af..0000000
--- a/src/ast/type_decl.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/type_decl.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeDecl);
-
-namespace tint {
-namespace ast {
-
-TypeDecl::TypeDecl(ProgramID pid, const Source& src, Symbol n)
-    : Base(pid, src), name(n) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
-}
-
-TypeDecl::TypeDecl(TypeDecl&&) = default;
-
-TypeDecl::~TypeDecl() = default;
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/type_decl.h b/src/ast/type_decl.h
deleted file mode 100644
index d0d49e4..0000000
--- a/src/ast/type_decl.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_TYPE_DECL_H_
-#define SRC_AST_TYPE_DECL_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// The base class for type declarations.
-class TypeDecl : public Castable<TypeDecl, Node> {
- public:
-  /// Create a new struct statement
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node for the import statement
-  /// @param name The name of the structure
-  TypeDecl(ProgramID pid, const Source& src, Symbol name);
-  /// Move constructor
-  TypeDecl(TypeDecl&&);
-
-  ~TypeDecl() override;
-
-  /// The name of the type declaration
-  const Symbol name;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TYPE_DECL_H_
diff --git a/src/ast/type_name.cc b/src/ast/type_name.cc
deleted file mode 100644
index 66da084..0000000
--- a/src/ast/type_name.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/type_name.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeName);
-
-namespace tint {
-namespace ast {
-
-TypeName::TypeName(ProgramID pid, const Source& src, Symbol n)
-    : Base(pid, src), name(n) {}
-
-TypeName::~TypeName() = default;
-
-TypeName::TypeName(TypeName&&) = default;
-
-std::string TypeName::FriendlyName(const SymbolTable& symbols) const {
-  return symbols.NameFor(name);
-}
-
-const TypeName* TypeName::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  auto n = ctx->Clone(name);
-  return ctx->dst->create<TypeName>(src, n);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/type_name.h b/src/ast/type_name.h
deleted file mode 100644
index 5e177ab..0000000
--- a/src/ast/type_name.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_TYPE_NAME_H_
-#define SRC_AST_TYPE_NAME_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A named type (i.e. struct or alias)
-class TypeName : public Castable<TypeName, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param name the type name
-  TypeName(ProgramID pid, const Source& src, Symbol name);
-  /// Move constructor
-  TypeName(TypeName&&);
-  /// Destructor
-  ~TypeName() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const TypeName* Clone(CloneContext* ctx) const override;
-
-  /// The type name
-  Symbol name;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_TYPE_NAME_H_
diff --git a/src/ast/u32.cc b/src/ast/u32.cc
deleted file mode 100644
index efe3624..0000000
--- a/src/ast/u32.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/ast/u32.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::U32);
-
-namespace tint {
-namespace ast {
-
-U32::U32(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-U32::~U32() = default;
-
-U32::U32(U32&&) = default;
-
-std::string U32::FriendlyName(const SymbolTable&) const {
-  return "u32";
-}
-
-const U32* U32::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<U32>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/u32.h b/src/ast/u32.h
deleted file mode 100644
index 5d7ba44..0000000
--- a/src/ast/u32.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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_AST_U32_H_
-#define SRC_AST_U32_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A unsigned int 32 type.
-class U32 : public Castable<U32, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  U32(ProgramID pid, const Source& src);
-  /// Move constructor
-  U32(U32&&);
-  ~U32() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const U32* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_U32_H_
diff --git a/src/ast/u32_test.cc b/src/ast/u32_test.cc
deleted file mode 100644
index 04c17de..0000000
--- a/src/ast/u32_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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/ast/u32.h"
-
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstU32Test = TestHelper;
-
-TEST_F(AstU32Test, FriendlyName) {
-  auto* u = create<U32>();
-  EXPECT_EQ(u->FriendlyName(Symbols()), "u32");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/uint_literal_expression.cc b/src/ast/uint_literal_expression.cc
deleted file mode 100644
index 0fc837c..0000000
--- a/src/ast/uint_literal_expression.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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/ast/uint_literal_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::UintLiteralExpression);
-
-namespace tint {
-namespace ast {
-
-UintLiteralExpression::UintLiteralExpression(ProgramID pid,
-                                             const Source& src,
-                                             uint32_t val)
-    : Base(pid, src), value(val) {}
-
-UintLiteralExpression::~UintLiteralExpression() = default;
-
-uint32_t UintLiteralExpression::ValueAsU32() const {
-  return value;
-}
-
-const UintLiteralExpression* UintLiteralExpression::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<UintLiteralExpression>(src, value);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/uint_literal_expression.h b/src/ast/uint_literal_expression.h
deleted file mode 100644
index 61ed4f0..0000000
--- a/src/ast/uint_literal_expression.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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_AST_UINT_LITERAL_EXPRESSION_H_
-#define SRC_AST_UINT_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/ast/int_literal_expression.h"
-
-namespace tint {
-namespace ast {
-
-/// A uint literal
-class UintLiteralExpression
-    : public Castable<UintLiteralExpression, IntLiteralExpression> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param value the uint literals value
-  UintLiteralExpression(ProgramID pid, const Source& src, uint32_t value);
-  ~UintLiteralExpression() override;
-
-  /// @returns the literal value as a u32
-  uint32_t ValueAsU32() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const UintLiteralExpression* Clone(CloneContext* ctx) const override;
-
-  /// The int literal value
-  const uint32_t value;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_UINT_LITERAL_EXPRESSION_H_
diff --git a/src/ast/uint_literal_expression_test.cc b/src/ast/uint_literal_expression_test.cc
deleted file mode 100644
index 15d6243..0000000
--- a/src/ast/uint_literal_expression_test.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2020 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/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using UintLiteralExpressionTest = TestHelper;
-
-TEST_F(UintLiteralExpressionTest, Value) {
-  auto* u = create<UintLiteralExpression>(47);
-  ASSERT_TRUE(u->Is<UintLiteralExpression>());
-  EXPECT_EQ(u->value, 47u);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/unary_op.cc b/src/ast/unary_op.cc
deleted file mode 100644
index 23a496d..0000000
--- a/src/ast/unary_op.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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/ast/unary_op.h"
-
-namespace tint {
-namespace ast {
-
-std::ostream& operator<<(std::ostream& out, UnaryOp mod) {
-  switch (mod) {
-    case UnaryOp::kAddressOf: {
-      out << "address-of";
-      break;
-    }
-    case UnaryOp::kComplement: {
-      out << "complement";
-      break;
-    }
-    case UnaryOp::kIndirection: {
-      out << "indirection";
-      break;
-    }
-    case UnaryOp::kNegation: {
-      out << "negation";
-      break;
-    }
-    case UnaryOp::kNot: {
-      out << "not";
-      break;
-    }
-  }
-  return out;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/unary_op.h b/src/ast/unary_op.h
deleted file mode 100644
index 9baa452..0000000
--- a/src/ast/unary_op.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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_AST_UNARY_OP_H_
-#define SRC_AST_UNARY_OP_H_
-
-#include <ostream>
-
-namespace tint {
-namespace ast {
-
-/// The unary op
-enum class UnaryOp {
-  kAddressOf,    // &EXPR
-  kComplement,   // ~EXPR
-  kIndirection,  // *EXPR
-  kNegation,     // -EXPR
-  kNot,          // !EXPR
-};
-
-/// @param out the std::ostream to write to
-/// @param mod the UnaryOp
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, UnaryOp mod);
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_UNARY_OP_H_
diff --git a/src/ast/unary_op_expression.cc b/src/ast/unary_op_expression.cc
deleted file mode 100644
index c80241a..0000000
--- a/src/ast/unary_op_expression.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/unary_op_expression.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::UnaryOpExpression);
-
-namespace tint {
-namespace ast {
-
-UnaryOpExpression::UnaryOpExpression(ProgramID pid,
-                                     const Source& src,
-                                     UnaryOp o,
-                                     const Expression* e)
-    : Base(pid, src), op(o), expr(e) {
-  TINT_ASSERT(AST, expr);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
-}
-
-UnaryOpExpression::UnaryOpExpression(UnaryOpExpression&&) = default;
-
-UnaryOpExpression::~UnaryOpExpression() = default;
-
-const UnaryOpExpression* UnaryOpExpression::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* e = ctx->Clone(expr);
-  return ctx->dst->create<UnaryOpExpression>(src, op, e);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/unary_op_expression.h b/src/ast/unary_op_expression.h
deleted file mode 100644
index 1d3e25a..0000000
--- a/src/ast/unary_op_expression.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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_AST_UNARY_OP_EXPRESSION_H_
-#define SRC_AST_UNARY_OP_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-#include "src/ast/unary_op.h"
-
-namespace tint {
-namespace ast {
-
-/// A unary op expression
-class UnaryOpExpression : public Castable<UnaryOpExpression, Expression> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the unary op expression source
-  /// @param op the op
-  /// @param expr the expr
-  UnaryOpExpression(ProgramID program_id,
-                    const Source& source,
-                    UnaryOp op,
-                    const Expression* expr);
-  /// Move constructor
-  UnaryOpExpression(UnaryOpExpression&&);
-  ~UnaryOpExpression() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const UnaryOpExpression* Clone(CloneContext* ctx) const override;
-
-  /// The op
-  const UnaryOp op;
-
-  /// The expression
-  const Expression* const expr;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_UNARY_OP_EXPRESSION_H_
diff --git a/src/ast/unary_op_expression_test.cc b/src/ast/unary_op_expression_test.cc
deleted file mode 100644
index 47f870a..0000000
--- a/src/ast/unary_op_expression_test.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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/ast/unary_op_expression.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using UnaryOpExpressionTest = TestHelper;
-
-TEST_F(UnaryOpExpressionTest, Creation) {
-  auto* ident = Expr("ident");
-
-  auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident);
-  EXPECT_EQ(u->op, UnaryOp::kNot);
-  EXPECT_EQ(u->expr, ident);
-}
-
-TEST_F(UnaryOpExpressionTest, Creation_WithSource) {
-  auto* ident = Expr("ident");
-  auto* u = create<UnaryOpExpression>(Source{Source::Location{20, 2}},
-                                      UnaryOp::kNot, ident);
-  auto src = u->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(UnaryOpExpressionTest, IsUnaryOp) {
-  auto* ident = Expr("ident");
-  auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident);
-  EXPECT_TRUE(u->Is<UnaryOpExpression>());
-}
-
-TEST_F(UnaryOpExpressionTest, Assert_Null_Expression) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<UnaryOpExpression>(UnaryOp::kNot, nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(UnaryOpExpressionTest, Assert_DifferentProgramID_Expression) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<UnaryOpExpression>(UnaryOp::kNot, b2.Expr(true));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
deleted file mode 100644
index 6e3f97d..0000000
--- a/src/ast/variable.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2020 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/ast/variable.h"
-
-#include "src/program_builder.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Variable);
-
-namespace tint {
-namespace ast {
-
-Variable::Variable(ProgramID pid,
-                   const Source& src,
-                   const Symbol& sym,
-                   StorageClass dsc,
-                   Access da,
-                   const ast::Type* ty,
-                   bool constant,
-                   bool overridable,
-                   const Expression* ctor,
-                   AttributeList attrs)
-    : Base(pid, src),
-      symbol(sym),
-      type(ty),
-      is_const(constant),
-      is_overridable(overridable),
-      constructor(ctor),
-      attributes(std::move(attrs)),
-      declared_storage_class(dsc),
-      declared_access(da) {
-  TINT_ASSERT(AST, symbol.IsValid());
-  TINT_ASSERT(AST, is_overridable ? is_const : true);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, constructor, program_id);
-}
-
-Variable::Variable(Variable&&) = default;
-
-Variable::~Variable() = default;
-
-VariableBindingPoint Variable::BindingPoint() const {
-  const GroupAttribute* group = nullptr;
-  const BindingAttribute* binding = nullptr;
-  for (auto* attr : attributes) {
-    if (auto* g = attr->As<GroupAttribute>()) {
-      group = g;
-    } else if (auto* b = attr->As<BindingAttribute>()) {
-      binding = b;
-    }
-  }
-  return VariableBindingPoint{group, binding};
-}
-
-const Variable* Variable::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  auto sym = ctx->Clone(symbol);
-  auto* ty = ctx->Clone(type);
-  auto* ctor = ctx->Clone(constructor);
-  auto attrs = ctx->Clone(attributes);
-  return ctx->dst->create<Variable>(src, sym, declared_storage_class,
-                                    declared_access, ty, is_const,
-                                    is_overridable, ctor, attrs);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/variable.h b/src/ast/variable.h
deleted file mode 100644
index 7a40171..0000000
--- a/src/ast/variable.h
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2020 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_AST_VARIABLE_H_
-#define SRC_AST_VARIABLE_H_
-
-#include <utility>
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/ast/attribute.h"
-#include "src/ast/expression.h"
-#include "src/ast/storage_class.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declarations
-class BindingAttribute;
-class GroupAttribute;
-class LocationAttribute;
-class Type;
-
-/// VariableBindingPoint holds a group and binding attribute.
-struct VariableBindingPoint {
-  /// The `@group` part of the binding point
-  const GroupAttribute* group = nullptr;
-  /// The `@binding` part of the binding point
-  const BindingAttribute* binding = nullptr;
-
-  /// @returns true if the BindingPoint has a valid group and binding
-  /// attribute.
-  inline operator bool() const { return group && binding; }
-};
-
-/// A Variable statement.
-///
-/// An instance of this class represents one of four constructs in WGSL: "var"
-/// declaration, "let" declaration, "override" declaration, or formal parameter
-/// to a function.
-///
-/// 1. A "var" declaration is a name for typed storage.  Examples:
-///
-///       // Declared outside a function, i.e. at module scope, requires
-///       // a storage class.
-///       var<workgroup> width : i32;     // no initializer
-///       var<private> height : i32 = 3;  // with initializer
-///
-///       // A variable declared inside a function doesn't take a storage class,
-///       // and maps to SPIR-V Function storage.
-///       var computed_depth : i32;
-///       var area : i32 = compute_area(width, height);
-///
-/// 2. A "let" declaration is a name for a typed value.  Examples:
-///
-///       let twice_depth : i32 = width + width;  // Must have initializer
-///
-/// 3. An "override" declaration is a name for a pipeline-overridable constant.
-/// Examples:
-///
-///       override radius : i32 = 2;       // Can be overridden by name.
-///       @id(5) override width : i32 = 2; // Can be overridden by ID.
-///       override scale : f32;            // No default - must be overridden.
-///
-/// 4. A formal parameter to a function is a name for a typed value to
-///    be passed into a function.  Example:
-///
-///       fn twice(a: i32) -> i32 {  // "a:i32" is the formal parameter
-///         return a + a;
-///       }
-///
-/// From the WGSL draft, about "var"::
-///
-///   A variable is a named reference to storage that can contain a value of a
-///   particular type.
-///
-///   Two types are associated with a variable: its store type (the type of
-///   value that may be placed in the referenced storage) and its reference
-///   type (the type of the variable itself).  If a variable has store type T
-///   and storage class S, then its reference type is pointer-to-T-in-S.
-///
-/// This class uses the term "type" to refer to:
-///     the value type of a "let",
-///     the value type of an "override",
-///     the value type of the formal parameter,
-///     or the store type of the "var".
-//
-/// Setting is_const:
-///   - "var" gets false
-///   - "let" gets true
-///   - "override" gets true
-///   - formal parameter gets true
-///
-/// Setting is_overrideable:
-///   - "var" gets false
-///   - "let" gets false
-///   - "override" gets true
-///   - formal parameter gets false
-///
-/// Setting storage class:
-///   - "var" is StorageClass::kNone when using the
-///     defaulting syntax for a "var" declared inside a function.
-///   - "let" is always StorageClass::kNone.
-///   - formal parameter is always StorageClass::kNone.
-class Variable : public Castable<Variable, Node> {
- public:
-  /// Create a variable
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the variable source
-  /// @param sym the variable symbol
-  /// @param declared_storage_class the declared storage class
-  /// @param declared_access the declared access control
-  /// @param type the declared variable type
-  /// @param is_const true if the variable is const
-  /// @param is_overridable true if the variable is pipeline-overridable
-  /// @param constructor the constructor expression
-  /// @param attributes the variable attributes
-  Variable(ProgramID program_id,
-           const Source& source,
-           const Symbol& sym,
-           StorageClass declared_storage_class,
-           Access declared_access,
-           const ast::Type* type,
-           bool is_const,
-           bool is_overridable,
-           const Expression* constructor,
-           AttributeList attributes);
-  /// Move constructor
-  Variable(Variable&&);
-
-  ~Variable() override;
-
-  /// @returns the binding point information for the variable
-  VariableBindingPoint BindingPoint() const;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const Variable* Clone(CloneContext* ctx) const override;
-
-  /// The variable symbol
-  const Symbol symbol;
-
-  /// The declared variable type. This is null if the type is inferred, e.g.:
-  ///   let f = 1.0;
-  ///   var i = 1;
-  const ast::Type* const type;
-
-  /// True if this is a constant, false otherwise
-  const bool is_const;
-
-  /// True if this is a pipeline-overridable constant, false otherwise
-  const bool is_overridable;
-
-  /// The constructor expression or nullptr if none set
-  const Expression* const constructor;
-
-  /// The attributes attached to this variable
-  const AttributeList attributes;
-
-  /// The declared storage class
-  const StorageClass declared_storage_class;
-
-  /// The declared access control
-  const Access declared_access;
-};
-
-/// A list of variables
-using VariableList = std::vector<const Variable*>;
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_VARIABLE_H_
diff --git a/src/ast/variable_decl_statement.cc b/src/ast/variable_decl_statement.cc
deleted file mode 100644
index 8e65919..0000000
--- a/src/ast/variable_decl_statement.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/ast/variable_decl_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::VariableDeclStatement);
-
-namespace tint {
-namespace ast {
-
-VariableDeclStatement::VariableDeclStatement(ProgramID pid,
-                                             const Source& src,
-                                             const Variable* var)
-    : Base(pid, src), variable(var) {
-  TINT_ASSERT(AST, variable);
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, variable, program_id);
-}
-
-VariableDeclStatement::VariableDeclStatement(VariableDeclStatement&&) = default;
-
-VariableDeclStatement::~VariableDeclStatement() = default;
-
-const VariableDeclStatement* VariableDeclStatement::Clone(
-    CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* var = ctx->Clone(variable);
-  return ctx->dst->create<VariableDeclStatement>(src, var);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/variable_decl_statement.h b/src/ast/variable_decl_statement.h
deleted file mode 100644
index 8a148b2..0000000
--- a/src/ast/variable_decl_statement.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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_AST_VARIABLE_DECL_STATEMENT_H_
-#define SRC_AST_VARIABLE_DECL_STATEMENT_H_
-
-#include "src/ast/statement.h"
-#include "src/ast/variable.h"
-
-namespace tint {
-namespace ast {
-
-/// A variable declaration statement
-class VariableDeclStatement
-    : public Castable<VariableDeclStatement, Statement> {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source the variable statement source
-  /// @param variable the variable
-  VariableDeclStatement(ProgramID program_id,
-                        const Source& source,
-                        const Variable* variable);
-  /// Move constructor
-  VariableDeclStatement(VariableDeclStatement&&);
-  ~VariableDeclStatement() override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const VariableDeclStatement* Clone(CloneContext* ctx) const override;
-
-  /// The variable
-  const Variable* const variable;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_VARIABLE_DECL_STATEMENT_H_
diff --git a/src/ast/variable_decl_statement_test.cc b/src/ast/variable_decl_statement_test.cc
deleted file mode 100644
index bcf92ad..0000000
--- a/src/ast/variable_decl_statement_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 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/ast/variable_decl_statement.h"
-
-#include "gtest/gtest-spi.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using VariableDeclStatementTest = TestHelper;
-
-TEST_F(VariableDeclStatementTest, Creation) {
-  auto* var = Var("a", ty.f32(), StorageClass::kNone);
-
-  auto* stmt = create<VariableDeclStatement>(var);
-  EXPECT_EQ(stmt->variable, var);
-}
-
-TEST_F(VariableDeclStatementTest, Creation_WithSource) {
-  auto* var = Var("a", ty.f32(), StorageClass::kNone);
-
-  auto* stmt =
-      create<VariableDeclStatement>(Source{Source::Location{20, 2}}, var);
-  auto src = stmt->source;
-  EXPECT_EQ(src.range.begin.line, 20u);
-  EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(VariableDeclStatementTest, IsVariableDecl) {
-  auto* var = Var("a", ty.f32(), StorageClass::kNone);
-
-  auto* stmt = create<VariableDeclStatement>(var);
-  EXPECT_TRUE(stmt->Is<VariableDeclStatement>());
-}
-
-TEST_F(VariableDeclStatementTest, Assert_Null_Variable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.create<VariableDeclStatement>(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(VariableDeclStatementTest, Assert_DifferentProgramID_Variable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.create<VariableDeclStatement>(
-            b2.Var("a", b2.ty.f32(), StorageClass::kNone));
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/variable_test.cc b/src/ast/variable_test.cc
deleted file mode 100644
index f126f1b..0000000
--- a/src/ast/variable_test.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using VariableTest = TestHelper;
-
-TEST_F(VariableTest, Creation) {
-  auto* v = Var("my_var", ty.i32(), StorageClass::kFunction);
-
-  EXPECT_EQ(v->symbol, Symbol(1, ID()));
-  EXPECT_EQ(v->declared_storage_class, StorageClass::kFunction);
-  EXPECT_TRUE(v->type->Is<ast::I32>());
-  EXPECT_EQ(v->source.range.begin.line, 0u);
-  EXPECT_EQ(v->source.range.begin.column, 0u);
-  EXPECT_EQ(v->source.range.end.line, 0u);
-  EXPECT_EQ(v->source.range.end.column, 0u);
-}
-
-TEST_F(VariableTest, CreationWithSource) {
-  auto* v = Var(
-      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 5}}},
-      "i", ty.f32(), StorageClass::kPrivate, nullptr, AttributeList{});
-
-  EXPECT_EQ(v->symbol, Symbol(1, ID()));
-  EXPECT_EQ(v->declared_storage_class, StorageClass::kPrivate);
-  EXPECT_TRUE(v->type->Is<ast::F32>());
-  EXPECT_EQ(v->source.range.begin.line, 27u);
-  EXPECT_EQ(v->source.range.begin.column, 4u);
-  EXPECT_EQ(v->source.range.end.line, 27u);
-  EXPECT_EQ(v->source.range.end.column, 5u);
-}
-
-TEST_F(VariableTest, CreationEmpty) {
-  auto* v = Var(
-      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 7}}},
-      "a_var", ty.i32(), StorageClass::kWorkgroup, nullptr, AttributeList{});
-
-  EXPECT_EQ(v->symbol, Symbol(1, ID()));
-  EXPECT_EQ(v->declared_storage_class, StorageClass::kWorkgroup);
-  EXPECT_TRUE(v->type->Is<ast::I32>());
-  EXPECT_EQ(v->source.range.begin.line, 27u);
-  EXPECT_EQ(v->source.range.begin.column, 4u);
-  EXPECT_EQ(v->source.range.end.line, 27u);
-  EXPECT_EQ(v->source.range.end.column, 7u);
-}
-
-TEST_F(VariableTest, Assert_MissingSymbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Var("", b.ty.i32(), StorageClass::kNone);
-      },
-      "internal compiler error");
-}
-
-TEST_F(VariableTest, Assert_DifferentProgramID_Symbol) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Var(b2.Sym("x"), b1.ty.f32(), StorageClass::kNone);
-      },
-      "internal compiler error");
-}
-
-TEST_F(VariableTest, Assert_DifferentProgramID_Constructor) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b1;
-        ProgramBuilder b2;
-        b1.Var("x", b1.ty.f32(), StorageClass::kNone, b2.Expr(1.2f));
-      },
-      "internal compiler error");
-}
-
-TEST_F(VariableTest, WithAttributes) {
-  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                  AttributeList{
-                      create<LocationAttribute>(1),
-                      create<BuiltinAttribute>(Builtin::kPosition),
-                      create<IdAttribute>(1200),
-                  });
-
-  auto& attributes = var->attributes;
-  EXPECT_TRUE(ast::HasAttribute<ast::LocationAttribute>(attributes));
-  EXPECT_TRUE(ast::HasAttribute<ast::BuiltinAttribute>(attributes));
-  EXPECT_TRUE(ast::HasAttribute<ast::IdAttribute>(attributes));
-
-  auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes);
-  ASSERT_NE(nullptr, location);
-  EXPECT_EQ(1u, location->value);
-}
-
-TEST_F(VariableTest, BindingPoint) {
-  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                  AttributeList{
-                      create<BindingAttribute>(2),
-                      create<GroupAttribute>(1),
-                  });
-  EXPECT_TRUE(var->BindingPoint());
-  ASSERT_NE(var->BindingPoint().binding, nullptr);
-  ASSERT_NE(var->BindingPoint().group, nullptr);
-  EXPECT_EQ(var->BindingPoint().binding->value, 2u);
-  EXPECT_EQ(var->BindingPoint().group->value, 1u);
-}
-
-TEST_F(VariableTest, BindingPointAttributes) {
-  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                  AttributeList{});
-  EXPECT_FALSE(var->BindingPoint());
-  EXPECT_EQ(var->BindingPoint().group, nullptr);
-  EXPECT_EQ(var->BindingPoint().binding, nullptr);
-}
-
-TEST_F(VariableTest, BindingPointMissingGroupAttribute) {
-  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                  AttributeList{
-                      create<BindingAttribute>(2),
-                  });
-  EXPECT_FALSE(var->BindingPoint());
-  ASSERT_NE(var->BindingPoint().binding, nullptr);
-  EXPECT_EQ(var->BindingPoint().binding->value, 2u);
-  EXPECT_EQ(var->BindingPoint().group, nullptr);
-}
-
-TEST_F(VariableTest, BindingPointMissingBindingAttribute) {
-  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                  AttributeList{create<GroupAttribute>(1)});
-  EXPECT_FALSE(var->BindingPoint());
-  ASSERT_NE(var->BindingPoint().group, nullptr);
-  EXPECT_EQ(var->BindingPoint().group->value, 1u);
-  EXPECT_EQ(var->BindingPoint().binding, nullptr);
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/vector.cc b/src/ast/vector.cc
deleted file mode 100644
index 50b7712..0000000
--- a/src/ast/vector.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 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/ast/vector.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Vector);
-
-namespace tint {
-namespace ast {
-
-Vector::Vector(ProgramID pid,
-               Source const& src,
-               const Type* subtype,
-               uint32_t w)
-    : Base(pid, src), type(subtype), width(w) {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
-  TINT_ASSERT(AST, width > 1);
-  TINT_ASSERT(AST, width < 5);
-}
-
-Vector::Vector(Vector&&) = default;
-
-Vector::~Vector() = default;
-
-std::string Vector::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "vec" << width;
-  if (type) {
-    out << "<" << type->FriendlyName(symbols) << ">";
-  }
-  return out.str();
-}
-
-const Vector* Vector::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* ty = ctx->Clone(type);
-  return ctx->dst->create<Vector>(src, ty, width);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/vector.h b/src/ast/vector.h
deleted file mode 100644
index 1d64666..0000000
--- a/src/ast/vector.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 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_AST_VECTOR_H_
-#define SRC_AST_VECTOR_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A vector type.
-class Vector : public Castable<Vector, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param subtype the declared type of the vector components. May be null
-  ///        for vector constructors, where the element type will be inferred
-  ///        from the constructor arguments
-  /// @param width the number of elements in the vector
-  Vector(ProgramID pid, Source const& src, const Type* subtype, uint32_t width);
-  /// Move constructor
-  Vector(Vector&&);
-  ~Vector() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Vector* Clone(CloneContext* ctx) const override;
-
-  /// The declared type of the vector components. May be null for vector
-  /// constructors, where the element type will be inferred from the constructor
-  /// arguments
-  const Type* const type;
-
-  /// The number of elements in the vector
-  const uint32_t width;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_VECTOR_H_
diff --git a/src/ast/vector_test.cc b/src/ast/vector_test.cc
deleted file mode 100644
index 87cff03..0000000
--- a/src/ast/vector_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/ast/vector.h"
-
-#include "src/ast/i32.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using AstVectorTest = TestHelper;
-
-TEST_F(AstVectorTest, Creation) {
-  auto* i32 = create<I32>();
-  auto* v = create<Vector>(i32, 2);
-  EXPECT_EQ(v->type, i32);
-  EXPECT_EQ(v->width, 2u);
-}
-
-TEST_F(AstVectorTest, FriendlyName) {
-  auto* f32 = create<F32>();
-  auto* v = create<Vector>(f32, 3);
-  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/void.cc b/src/ast/void.cc
deleted file mode 100644
index 59c4670..0000000
--- a/src/ast/void.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/ast/void.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Void);
-
-namespace tint {
-namespace ast {
-
-Void::Void(ProgramID pid, const Source& src) : Base(pid, src) {}
-
-Void::Void(Void&&) = default;
-
-Void::~Void() = default;
-
-std::string Void::FriendlyName(const SymbolTable&) const {
-  return "void";
-}
-
-const Void* Void::Clone(CloneContext* ctx) const {
-  auto src = ctx->Clone(source);
-  return ctx->dst->create<Void>(src);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/void.h b/src/ast/void.h
deleted file mode 100644
index a2b7af4..0000000
--- a/src/ast/void.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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_AST_VOID_H_
-#define SRC_AST_VOID_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// A void type
-class Void : public Castable<Void, Type> {
- public:
-  /// Constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  Void(ProgramID pid, const Source& src);
-  /// Move constructor
-  Void(Void&&);
-  ~Void() override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned type
-  const Void* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_VOID_H_
diff --git a/src/ast/workgroup_attribute.cc b/src/ast/workgroup_attribute.cc
deleted file mode 100644
index e8ecc19..0000000
--- a/src/ast/workgroup_attribute.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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/ast/workgroup_attribute.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::WorkgroupAttribute);
-
-namespace tint {
-namespace ast {
-
-WorkgroupAttribute::WorkgroupAttribute(ProgramID pid,
-                                       const Source& src,
-                                       const ast::Expression* x_,
-                                       const ast::Expression* y_,
-                                       const ast::Expression* z_)
-    : Base(pid, src), x(x_), y(y_), z(z_) {}
-
-WorkgroupAttribute::~WorkgroupAttribute() = default;
-
-std::string WorkgroupAttribute::Name() const {
-  return "workgroup_size";
-}
-
-const WorkgroupAttribute* WorkgroupAttribute::Clone(CloneContext* ctx) const {
-  // Clone arguments outside of create() call to have deterministic ordering
-  auto src = ctx->Clone(source);
-  auto* x_ = ctx->Clone(x);
-  auto* y_ = ctx->Clone(y);
-  auto* z_ = ctx->Clone(z);
-  return ctx->dst->create<WorkgroupAttribute>(src, x_, y_, z_);
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/workgroup_attribute.h b/src/ast/workgroup_attribute.h
deleted file mode 100644
index a49db3d..0000000
--- a/src/ast/workgroup_attribute.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 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_AST_WORKGROUP_ATTRIBUTE_H_
-#define SRC_AST_WORKGROUP_ATTRIBUTE_H_
-
-#include <array>
-#include <string>
-
-#include "src/ast/attribute.h"
-
-namespace tint {
-namespace ast {
-
-// Forward declaration
-class Expression;
-
-/// A workgroup attribute
-class WorkgroupAttribute : public Castable<WorkgroupAttribute, Attribute> {
- public:
-  /// constructor
-  /// @param pid the identifier of the program that owns this node
-  /// @param src the source of this node
-  /// @param x the workgroup x dimension expression
-  /// @param y the optional workgroup y dimension expression
-  /// @param z the optional workgroup z dimension expression
-  WorkgroupAttribute(ProgramID pid,
-                     const Source& src,
-                     const ast::Expression* x,
-                     const ast::Expression* y = nullptr,
-                     const ast::Expression* z = nullptr);
-
-  ~WorkgroupAttribute() override;
-
-  /// @returns the workgroup dimensions
-  std::array<const ast::Expression*, 3> Values() const { return {x, y, z}; }
-
-  /// @returns the WGSL name for the attribute
-  std::string Name() const override;
-
-  /// Clones this node and all transitive child nodes using the `CloneContext`
-  /// `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned node
-  const WorkgroupAttribute* Clone(CloneContext* ctx) const override;
-
-  /// The workgroup x dimension.
-  const ast::Expression* const x;
-  /// The optional workgroup y dimension. May be null.
-  const ast::Expression* const y = nullptr;
-  /// The optional workgroup z dimension. May be null.
-  const ast::Expression* const z = nullptr;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_WORKGROUP_ATTRIBUTE_H_
diff --git a/src/ast/workgroup_attribute_test.cc b/src/ast/workgroup_attribute_test.cc
deleted file mode 100644
index 8c285b0..0000000
--- a/src/ast/workgroup_attribute_test.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 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/ast/workgroup_attribute.h"
-
-#include "src/ast/stage_attribute.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-using WorkgroupAttributeTest = TestHelper;
-
-TEST_F(WorkgroupAttributeTest, Creation_1param) {
-  auto* d = WorkgroupSize(2);
-  auto values = d->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  EXPECT_EQ(values[1], nullptr);
-  EXPECT_EQ(values[2], nullptr);
-}
-TEST_F(WorkgroupAttributeTest, Creation_2param) {
-  auto* d = WorkgroupSize(2, 4);
-  auto values = d->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  EXPECT_EQ(values[2], nullptr);
-}
-
-TEST_F(WorkgroupAttributeTest, Creation_3param) {
-  auto* d = WorkgroupSize(2, 4, 6);
-  auto values = d->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
-}
-
-TEST_F(WorkgroupAttributeTest, Creation_WithIdentifier) {
-  auto* d = WorkgroupSize(2, 4, "depth");
-  auto values = d->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  auto* z_ident = As<ast::IdentifierExpression>(values[2]);
-  ASSERT_TRUE(z_ident);
-  EXPECT_EQ(Symbols().NameFor(z_ident->symbol), "depth");
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/bench/benchmark.cc b/src/bench/benchmark.cc
deleted file mode 100644
index c33f94e..0000000
--- a/src/bench/benchmark.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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/bench/benchmark.h"
-
-#include <filesystem>
-#include <sstream>
-#include <utility>
-#include <vector>
-
-namespace tint::bench {
-namespace {
-
-std::filesystem::path kInputFileDir;
-
-/// Copies the content from the file named `input_file` to `buffer`,
-/// assuming each element in the file is of type `T`.  If any error occurs,
-/// writes error messages to the standard error stream and returns false.
-/// Assumes the size of a `T` object is divisible by its required alignment.
-/// @returns true if we successfully read the file.
-template <typename T>
-std::variant<std::vector<T>, Error> ReadFile(const std::string& input_file) {
-  FILE* file = nullptr;
-#if defined(_MSC_VER)
-  fopen_s(&file, input_file.c_str(), "rb");
-#else
-  file = fopen(input_file.c_str(), "rb");
-#endif
-  if (!file) {
-    return Error{"Failed to open " + input_file};
-  }
-
-  fseek(file, 0, SEEK_END);
-  const auto file_size = static_cast<size_t>(ftell(file));
-  if (0 != (file_size % sizeof(T))) {
-    std::stringstream err;
-    err << "File " << input_file
-        << " does not contain an integral number of objects: " << file_size
-        << " bytes in the file, require " << sizeof(T) << " bytes per object";
-    fclose(file);
-    return Error{err.str()};
-  }
-  fseek(file, 0, SEEK_SET);
-
-  std::vector<T> buffer;
-  buffer.resize(file_size / sizeof(T));
-
-  size_t bytes_read = fread(buffer.data(), 1, file_size, file);
-  fclose(file);
-  if (bytes_read != file_size) {
-    return Error{"Failed to read " + input_file};
-  }
-
-  return buffer;
-}
-
-bool FindBenchmarkInputDir() {
-  // Attempt to find the benchmark input files by searching up from the current
-  // working directory.
-  auto path = std::filesystem::current_path();
-  while (std::filesystem::is_directory(path)) {
-    auto test = path / "test" / "benchmark";
-    if (std::filesystem::is_directory(test)) {
-      kInputFileDir = test;
-      return true;
-    }
-    auto parent = path.parent_path();
-    if (path == parent) {
-      break;
-    }
-    path = parent;
-  }
-  return false;
-}
-
-}  // namespace
-
-std::variant<tint::Source::File, Error> LoadInputFile(std::string name) {
-  auto path = (kInputFileDir / name).string();
-  auto data = ReadFile<uint8_t>(path);
-  if (auto* buf = std::get_if<std::vector<uint8_t>>(&data)) {
-    return tint::Source::File(path, std::string(buf->begin(), buf->end()));
-  }
-  return std::get<Error>(data);
-}
-
-std::variant<ProgramAndFile, Error> LoadProgram(std::string name) {
-  auto res = bench::LoadInputFile(name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    return *err;
-  }
-  auto& file = std::get<Source::File>(res);
-  auto program = reader::wgsl::Parse(&file);
-  if (program.Diagnostics().contains_errors()) {
-    return Error{program.Diagnostics().str()};
-  }
-  return ProgramAndFile{std::move(program), std::move(file)};
-}
-
-}  // namespace tint::bench
-
-int main(int argc, char** argv) {
-  benchmark::Initialize(&argc, argv);
-  if (benchmark::ReportUnrecognizedArguments(argc, argv)) {
-    return 1;
-  }
-  if (!tint::bench::FindBenchmarkInputDir()) {
-    std::cerr << "failed to locate benchmark input files" << std::endl;
-    return 1;
-  }
-  benchmark::RunSpecifiedBenchmarks();
-}
diff --git a/src/bench/benchmark.h b/src/bench/benchmark.h
deleted file mode 100644
index 91bd631..0000000
--- a/src/bench/benchmark.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// 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_BENCH_BENCHMARK_H_
-#define SRC_BENCH_BENCHMARK_H_
-
-#include <memory>
-#include <string>
-#include <variant>  // NOLINT: Found C system header after C++ system header.
-
-#include "benchmark/benchmark.h"
-#include "src/utils/concat.h"
-#include "tint/tint.h"
-
-namespace tint::bench {
-
-/// Error indicates an operation did not complete successfully.
-struct Error {
-  /// The error message.
-  std::string msg;
-};
-
-/// ProgramAndFile holds a Program and a Source::File.
-struct ProgramAndFile {
-  /// The tint program parsed from file.
-  Program program;
-  /// The source file
-  Source::File file;
-};
-
-/// LoadInputFile attempts to load a benchmark input file with the given file
-/// name.
-/// @param name the file name
-/// @returns either the loaded Source::File or an Error
-std::variant<Source::File, Error> LoadInputFile(std::string name);
-
-/// LoadInputFile attempts to load a benchmark input program with the given file
-/// name.
-/// @param name the file name
-/// @returns either the loaded Program or an Error
-std::variant<ProgramAndFile, Error> LoadProgram(std::string name);
-
-/// Declares a benchmark with the given function and WGSL file name
-#define TINT_BENCHMARK_WGSL_PROGRAM(FUNC, WGSL_NAME) \
-  BENCHMARK_CAPTURE(FUNC, WGSL_NAME, WGSL_NAME);
-
-/// Declares a set of benchmarks for the given function using a list of WGSL
-/// files in `<tint>/test/benchmark`.
-#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC)                                 \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "animometer.wgsl");                    \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "bloom-vertical-blur.wgsl");           \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "cluster-lights.wgsl");                \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "empty.wgsl");                         \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "metaball-isosurface.wgsl");           \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl");                     \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "shadow-fragment.wgsl");               \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-compute.wgsl");                \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-fragment.wgsl");               \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-vertex.wgsl");                 \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-fragment.wgsl"); \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-vertex.wgsl");
-
-}  // namespace tint::bench
-
-#endif  // SRC_BENCH_BENCHMARK_H_
diff --git a/src/block_allocator.h b/src/block_allocator.h
deleted file mode 100644
index 4a8fbf2..0000000
--- a/src/block_allocator.h
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_BLOCK_ALLOCATOR_H_
-#define SRC_BLOCK_ALLOCATOR_H_
-
-#include <array>
-#include <utility>
-
-#include "src/utils/math.h"
-
-namespace tint {
-
-/// A container and allocator of objects of (or deriving from) the template
-/// type `T`. Objects are allocated by calling Create(), and are owned by the
-/// BlockAllocator. When the BlockAllocator is destructed, all constructed
-/// objects are automatically destructed and freed.
-///
-/// Objects held by the BlockAllocator can be iterated over using a View.
-template <typename T,
-          size_t BLOCK_SIZE = 64 * 1024,
-          size_t BLOCK_ALIGNMENT = 16>
-class BlockAllocator {
-  /// Pointers is a chunk of T* pointers, forming a linked list.
-  /// The list of Pointers are used to maintain the list of allocated objects.
-  /// Pointers are allocated out of the block memory.
-  struct Pointers {
-    static constexpr size_t kMax = 32;
-    std::array<T*, kMax> ptrs;
-    Pointers* next;
-  };
-
-  /// Block is linked list of memory blocks.
-  /// Blocks are allocated out of heap memory.
-  ///
-  /// Note: We're not using std::aligned_storage here as this warns / errors
-  /// on MSVC.
-  struct alignas(BLOCK_ALIGNMENT) Block {
-    uint8_t data[BLOCK_SIZE];
-    Block* next;
-  };
-
-  // Forward declaration
-  template <bool IS_CONST>
-  class TView;
-
-  /// An iterator for the objects owned by the BlockAllocator.
-  template <bool IS_CONST>
-  class TIterator {
-    using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
-
-   public:
-    /// Equality operator
-    /// @param other the iterator to compare this iterator to
-    /// @returns true if this iterator is equal to other
-    bool operator==(const TIterator& other) const {
-      return ptrs == other.ptrs && idx == other.idx;
-    }
-
-    /// Inequality operator
-    /// @param other the iterator to compare this iterator to
-    /// @returns true if this iterator is not equal to other
-    bool operator!=(const TIterator& other) const { return !(*this == other); }
-
-    /// Advances the iterator
-    /// @returns this iterator
-    TIterator& operator++() {
-      if (ptrs != nullptr) {
-        ++idx;
-        if (idx == Pointers::kMax) {
-          idx = 0;
-          ptrs = ptrs->next;
-        }
-      }
-      return *this;
-    }
-
-    /// @returns the pointer to the object at the current iterator position
-    PointerTy operator*() const { return ptrs ? ptrs->ptrs[idx] : nullptr; }
-
-   private:
-    friend TView<IS_CONST>;  // Keep internal iterator impl private.
-    explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
-
-    const Pointers* ptrs;
-    size_t idx;
-  };
-
-  /// View provides begin() and end() methods for looping over the objects
-  /// owned by the BlockAllocator.
-  template <bool IS_CONST>
-  class TView {
-   public:
-    /// @returns an iterator to the beginning of the view
-    TIterator<IS_CONST> begin() const {
-      return TIterator<IS_CONST>{allocator_->pointers_.root, 0};
-    }
-
-    /// @returns an iterator to the end of the view
-    TIterator<IS_CONST> end() const {
-      return allocator_->pointers_.current_index >= Pointers::kMax
-                 ? TIterator<IS_CONST>(nullptr, 0)
-                 : TIterator<IS_CONST>(allocator_->pointers_.current,
-                                       allocator_->pointers_.current_index);
-    }
-
-   private:
-    friend BlockAllocator;  // For BlockAllocator::operator View()
-    explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
-    BlockAllocator const* const allocator_;
-  };
-
- public:
-  /// An iterator type over the objects of the BlockAllocator
-  using Iterator = TIterator<false>;
-
-  /// An immutable iterator type over the objects of the BlockAllocator
-  using ConstIterator = TIterator<true>;
-
-  /// View provides begin() and end() methods for looping over the objects
-  /// owned by the BlockAllocator.
-  using View = TView<false>;
-
-  /// ConstView provides begin() and end() methods for looping over the objects
-  /// owned by the BlockAllocator.
-  using ConstView = TView<true>;
-
-  /// Constructor
-  BlockAllocator() = default;
-
-  /// Move constructor
-  /// @param rhs the BlockAllocator to move
-  BlockAllocator(BlockAllocator&& rhs) {
-    std::swap(block_, rhs.block_);
-    std::swap(pointers_, rhs.pointers_);
-  }
-
-  /// Move assignment operator
-  /// @param rhs the BlockAllocator to move
-  /// @return this BlockAllocator
-  BlockAllocator& operator=(BlockAllocator&& rhs) {
-    if (this != &rhs) {
-      Reset();
-      std::swap(block_, rhs.block_);
-      std::swap(pointers_, rhs.pointers_);
-    }
-    return *this;
-  }
-
-  /// Destructor
-  ~BlockAllocator() { Reset(); }
-
-  /// @return a View of all objects owned by this BlockAllocator
-  View Objects() { return View(this); }
-
-  /// @return a ConstView of all objects owned by this BlockAllocator
-  ConstView Objects() const { return ConstView(this); }
-
-  /// Creates a new `TYPE` owned by the BlockAllocator.
-  /// When the BlockAllocator is destructed the object will be destructed and
-  /// freed.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the pointer to the constructed object
-  template <typename TYPE = T, typename... ARGS>
-  TYPE* Create(ARGS&&... args) {
-    static_assert(
-        std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
-        "TYPE does not derive from T");
-    static_assert(
-        std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
-        "TYPE requires a virtual destructor when calling Create() for a type "
-        "that is not T");
-
-    auto* ptr = Allocate<TYPE>();
-    new (ptr) TYPE(std::forward<ARGS>(args)...);
-    AddObjectPointer(ptr);
-
-    return ptr;
-  }
-
-  /// Frees all allocations from the allocator.
-  void Reset() {
-    for (auto ptr : Objects()) {
-      ptr->~T();
-    }
-    auto* block = block_.root;
-    while (block != nullptr) {
-      auto* next = block->next;
-      delete block;
-      block = next;
-    }
-    block_ = {};
-    pointers_ = {};
-  }
-
- private:
-  BlockAllocator(const BlockAllocator&) = delete;
-  BlockAllocator& operator=(const BlockAllocator&) = delete;
-
-  /// Allocates an instance of TYPE from the current block, or from a newly
-  /// allocated block if the current block is full.
-  template <typename TYPE>
-  TYPE* Allocate() {
-    static_assert(sizeof(TYPE) <= BLOCK_SIZE,
-                  "Cannot construct TYPE with size greater than BLOCK_SIZE");
-    static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT,
-                  "alignof(TYPE) is greater than ALIGNMENT");
-
-    block_.current_offset =
-        utils::RoundUp(alignof(TYPE), block_.current_offset);
-    if (block_.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
-      // Allocate a new block from the heap
-      auto* prev_block = block_.current;
-      block_.current = new Block;
-      if (!block_.current) {
-        return nullptr;  // out of memory
-      }
-      block_.current->next = nullptr;
-      block_.current_offset = 0;
-      if (prev_block) {
-        prev_block->next = block_.current;
-      } else {
-        block_.root = block_.current;
-      }
-    }
-
-    auto* base = &block_.current->data[0];
-    auto* ptr = reinterpret_cast<TYPE*>(base + block_.current_offset);
-    block_.current_offset += sizeof(TYPE);
-    return ptr;
-  }
-
-  /// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
-  /// Once added, `ptr` will be tracked for destruction when the BlockAllocator
-  /// is destructed.
-  void AddObjectPointer(T* ptr) {
-    if (pointers_.current_index >= Pointers::kMax) {
-      auto* prev_pointers = pointers_.current;
-      pointers_.current = Allocate<Pointers>();
-      if (!pointers_.current) {
-        return;  // out of memory
-      }
-      pointers_.current->next = nullptr;
-      pointers_.current_index = 0;
-
-      if (prev_pointers) {
-        prev_pointers->next = pointers_.current;
-      } else {
-        pointers_.root = pointers_.current;
-      }
-    }
-
-    pointers_.current->ptrs[pointers_.current_index++] = ptr;
-  }
-
-  struct {
-    /// The root block of the block linked list
-    Block* root = nullptr;
-    /// The current (end) block of the blocked linked list.
-    /// New allocations come from this block
-    Block* current = nullptr;
-    /// The byte offset in #current for the next allocation.
-    /// Initialized with BLOCK_SIZE so that the first allocation triggers a
-    /// block allocation.
-    size_t current_offset = BLOCK_SIZE;
-  } block_;
-
-  struct {
-    /// The root Pointers structure of the pointers linked list
-    Pointers* root = nullptr;
-    /// The current (end) Pointers structure of the pointers linked list.
-    /// AddObjectPointer() adds to this structure.
-    Pointers* current = nullptr;
-    /// The array index in #current for the next append.
-    /// Initialized with Pointers::kMax so that the first append triggers a
-    /// allocation of the Pointers structure.
-    size_t current_index = Pointers::kMax;
-  } pointers_;
-};
-
-}  // namespace tint
-
-#endif  // SRC_BLOCK_ALLOCATOR_H_
diff --git a/src/block_allocator_test.cc b/src/block_allocator_test.cc
deleted file mode 100644
index 69834da..0000000
--- a/src/block_allocator_test.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/block_allocator.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-struct LifetimeCounter {
-  explicit LifetimeCounter(size_t* count) : count_(count) { (*count)++; }
-  ~LifetimeCounter() { (*count_)--; }
-
-  size_t* const count_;
-};
-
-using BlockAllocatorTest = testing::Test;
-
-TEST_F(BlockAllocatorTest, Empty) {
-  using Allocator = BlockAllocator<int>;
-
-  Allocator allocator;
-
-  for (int* i : allocator.Objects()) {
-    (void)i;
-    if ((true)) {  // Workaround for "error: loop will run at most once"
-      FAIL() << "BlockAllocator should be empty";
-    }
-  }
-  for (const int* i : static_cast<const Allocator&>(allocator).Objects()) {
-    (void)i;
-    if ((true)) {  // Workaround for "error: loop will run at most once"
-      FAIL() << "BlockAllocator should be empty";
-    }
-  }
-}
-
-TEST_F(BlockAllocatorTest, ObjectLifetime) {
-  using Allocator = BlockAllocator<LifetimeCounter>;
-
-  size_t count = 0;
-  {
-    Allocator allocator;
-    EXPECT_EQ(count, 0u);
-    allocator.Create(&count);
-    EXPECT_EQ(count, 1u);
-    allocator.Create(&count);
-    EXPECT_EQ(count, 2u);
-    allocator.Create(&count);
-    EXPECT_EQ(count, 3u);
-  }
-  EXPECT_EQ(count, 0u);
-}
-
-TEST_F(BlockAllocatorTest, MoveConstruct) {
-  using Allocator = BlockAllocator<LifetimeCounter>;
-
-  for (size_t n :
-       {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
-    size_t count = 0;
-    {
-      Allocator allocator_a;
-      for (size_t i = 0; i < n; i++) {
-        allocator_a.Create(&count);
-      }
-      EXPECT_EQ(count, n);
-
-      Allocator allocator_b{std::move(allocator_a)};
-      EXPECT_EQ(count, n);
-    }
-
-    EXPECT_EQ(count, 0u);
-  }
-}
-
-TEST_F(BlockAllocatorTest, MoveAssign) {
-  using Allocator = BlockAllocator<LifetimeCounter>;
-
-  for (size_t n :
-       {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
-    size_t count_a = 0;
-    size_t count_b = 0;
-
-    {
-      Allocator allocator_a;
-      for (size_t i = 0; i < n; i++) {
-        allocator_a.Create(&count_a);
-      }
-      EXPECT_EQ(count_a, n);
-
-      Allocator allocator_b;
-      for (size_t i = 0; i < n; i++) {
-        allocator_b.Create(&count_b);
-      }
-      EXPECT_EQ(count_b, n);
-
-      allocator_b = std::move(allocator_a);
-      EXPECT_EQ(count_a, n);
-      EXPECT_EQ(count_b, 0u);
-    }
-
-    EXPECT_EQ(count_a, 0u);
-    EXPECT_EQ(count_b, 0u);
-  }
-}
-
-TEST_F(BlockAllocatorTest, ObjectOrder) {
-  using Allocator = BlockAllocator<int>;
-
-  Allocator allocator;
-  constexpr int N = 10000;
-  for (int i = 0; i < N; i++) {
-    allocator.Create(i);
-  }
-
-  {
-    int i = 0;
-    for (int* p : allocator.Objects()) {
-      EXPECT_EQ(*p, i);
-      i++;
-    }
-    EXPECT_EQ(i, N);
-  }
-  {
-    int i = 0;
-    for (const int* p : static_cast<const Allocator&>(allocator).Objects()) {
-      EXPECT_EQ(*p, i);
-      i++;
-    }
-    EXPECT_EQ(i, N);
-  }
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/builtin_table.cc b/src/builtin_table.cc
deleted file mode 100644
index 569d057..0000000
--- a/src/builtin_table.cc
+++ /dev/null
@@ -1,1166 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/builtin_table.h"
-
-#include <algorithm>
-#include <limits>
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/pipeline_stage_set.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-#include "src/utils/math.h"
-#include "src/utils/scoped_assignment.h"
-
-namespace tint {
-namespace {
-
-// Forward declarations
-struct OverloadInfo;
-class Matchers;
-class NumberMatcher;
-class TypeMatcher;
-
-/// A special type that matches all TypeMatchers
-class Any : public Castable<Any, sem::Type> {
- public:
-  Any() = default;
-  ~Any() override = default;
-  std::string type_name() const override { return "<any>"; }
-  std::string FriendlyName(const SymbolTable&) const override {
-    return "<any>";
-  }
-};
-
-/// Number is an 32 bit unsigned integer, which can be in one of three states:
-/// * Invalid - Number has not been assigned a value
-/// * Valid   - a fixed integer value
-/// * Any     - matches any other non-invalid number
-struct Number {
-  static const Number any;
-  static const Number invalid;
-
-  /// Constructed as a valid number with the value v
-  explicit Number(uint32_t v) : value_(v), state_(kValid) {}
-
-  /// @returns the value of the number
-  inline uint32_t Value() const { return value_; }
-
-  /// @returns the true if the number is valid
-  inline bool IsValid() const { return state_ == kValid; }
-
-  /// @returns the true if the number is any
-  inline bool IsAny() const { return state_ == kAny; }
-
-  /// Assignment operator.
-  /// The number becomes valid, with the value n
-  inline Number& operator=(uint32_t n) {
-    value_ = n;
-    state_ = kValid;
-    return *this;
-  }
-
- private:
-  enum State {
-    kInvalid,
-    kValid,
-    kAny,
-  };
-
-  constexpr explicit Number(State state) : state_(state) {}
-
-  uint32_t value_ = 0;
-  State state_ = kInvalid;
-};
-
-const Number Number::any{Number::kAny};
-const Number Number::invalid{Number::kInvalid};
-
-/// ClosedState holds the state of the open / closed numbers and types.
-/// Used by the MatchState.
-class ClosedState {
- public:
-  explicit ClosedState(ProgramBuilder& b) : builder(b) {}
-
-  /// If the type with index `idx` is open, then it is closed with type `ty` and
-  /// Type() returns true. If the type is closed, then `Type()` returns true iff
-  /// it is equal to `ty`.
-  bool Type(uint32_t idx, const sem::Type* ty) {
-    auto res = types_.emplace(idx, ty);
-    return res.second || res.first->second == ty;
-  }
-
-  /// If the number with index `idx` is open, then it is closed with number
-  /// `number` and Num() returns true. If the number is closed, then `Num()`
-  /// returns true iff it is equal to `ty`.
-  bool Num(uint32_t idx, Number number) {
-    auto res = numbers_.emplace(idx, number.Value());
-    return res.second || res.first->second == number.Value();
-  }
-
-  /// Type returns the closed type with index `idx`.
-  /// An ICE is raised if the type is not closed.
-  const sem::Type* Type(uint32_t idx) const {
-    auto it = types_.find(idx);
-    if (it == types_.end()) {
-      TINT_ICE(Resolver, builder.Diagnostics())
-          << "type with index " << idx << " is not closed";
-      return nullptr;
-    }
-    TINT_ASSERT(Resolver, it != types_.end());
-    return it->second;
-  }
-
-  /// Type returns the number type with index `idx`.
-  /// An ICE is raised if the number is not closed.
-  Number Num(uint32_t idx) const {
-    auto it = numbers_.find(idx);
-    if (it == numbers_.end()) {
-      TINT_ICE(Resolver, builder.Diagnostics())
-          << "number with index " << idx << " is not closed";
-      return Number::invalid;
-    }
-    return Number(it->second);
-  }
-
- private:
-  ProgramBuilder& builder;
-  std::unordered_map<uint32_t, const sem::Type*> types_;
-  std::unordered_map<uint32_t, uint32_t> numbers_;
-};
-
-/// Index type used for matcher indices
-using MatcherIndex = uint8_t;
-
-/// Index value used for open types / numbers that do not have a constraint
-constexpr MatcherIndex kNoMatcher = std::numeric_limits<MatcherIndex>::max();
-
-/// MatchState holds the state used to match an overload.
-class MatchState {
- public:
-  MatchState(ProgramBuilder& b,
-             ClosedState& c,
-             const Matchers& m,
-             const OverloadInfo& o,
-             MatcherIndex const* matcher_indices)
-      : builder(b),
-        closed(c),
-        matchers(m),
-        overload(o),
-        matcher_indices_(matcher_indices) {}
-
-  /// The program builder
-  ProgramBuilder& builder;
-  /// The open / closed types and numbers
-  ClosedState& closed;
-  /// The type and number matchers
-  Matchers const& matchers;
-  /// The current overload being evaluated
-  OverloadInfo const& overload;
-
-  /// Type uses the next TypeMatcher from the matcher indices to match the type
-  /// `ty`. If the type matches, the canonical expected type is returned. If the
-  /// type `ty` does not match, then nullptr is returned.
-  /// @note: The matcher indices are progressed on calling.
-  const sem::Type* Type(const sem::Type* ty);
-
-  /// Num uses the next NumMatcher from the matcher indices to match the number
-  /// `num`. If the number matches, the canonical expected number is returned.
-  /// If the number `num` does not match, then an invalid number is returned.
-  /// @note: The matcher indices are progressed on calling.
-  Number Num(Number num);
-
-  /// @returns a string representation of the next TypeMatcher from the matcher
-  /// indices.
-  /// @note: The matcher indices are progressed on calling.
-  std::string TypeName();
-
-  /// @returns a string representation of the next NumberMatcher from the
-  /// matcher indices.
-  /// @note: The matcher indices are progressed on calling.
-  std::string NumName();
-
- private:
-  MatcherIndex const* matcher_indices_ = nullptr;
-};
-
-/// A TypeMatcher is the interface used to match an type used as part of an
-/// overload's parameter or return type.
-class TypeMatcher {
- public:
-  /// Destructor
-  virtual ~TypeMatcher() = default;
-
-  /// Checks whether the given type matches the matcher rules, and returns the
-  /// expected, canonicalized type on success.
-  /// Match may close open types and numbers in state.
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  virtual const sem::Type* Match(MatchState& state,
-                                 const sem::Type* type) const = 0;
-
-  /// @return a string representation of the matcher. Used for printing error
-  /// messages when no overload is found.
-  virtual std::string String(MatchState& state) const = 0;
-};
-
-/// A NumberMatcher is the interface used to match a number or enumerator used
-/// as part of an overload's parameter or return type.
-class NumberMatcher {
- public:
-  /// Destructor
-  virtual ~NumberMatcher() = default;
-
-  /// Checks whether the given number matches the matcher rules.
-  /// Match may close open numbers in state.
-  /// @param number the number to match
-  /// @returns true if the argument type is as expected.
-  virtual Number Match(MatchState& state, Number number) const = 0;
-
-  /// @return a string representation of the matcher. Used for printing error
-  /// messages when no overload is found.
-  virtual std::string String(MatchState& state) const = 0;
-};
-
-/// OpenTypeMatcher is a Matcher for an open type.
-/// The OpenTypeMatcher will match against any type (so long as it is consistent
-/// across all uses in the overload)
-class OpenTypeMatcher : public TypeMatcher {
- public:
-  /// Constructor
-  explicit OpenTypeMatcher(uint32_t index) : index_(index) {}
-
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override {
-    if (type->Is<Any>()) {
-      return state.closed.Type(index_);
-    }
-    return state.closed.Type(index_, type) ? type : nullptr;
-  }
-
-  std::string String(MatchState& state) const override;
-
- private:
-  uint32_t index_;
-};
-
-/// OpenNumberMatcher is a Matcher for an open number.
-/// The OpenNumberMatcher will match against any number (so long as it is
-/// consistent for the overload)
-class OpenNumberMatcher : public NumberMatcher {
- public:
-  explicit OpenNumberMatcher(uint32_t index) : index_(index) {}
-
-  Number Match(MatchState& state, Number number) const override {
-    if (number.IsAny()) {
-      return state.closed.Num(index_);
-    }
-    return state.closed.Num(index_, number) ? number : Number::invalid;
-  }
-
-  std::string String(MatchState& state) const override;
-
- private:
-  uint32_t index_;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// Binding functions for use in the generated builtin_table.inl
-// TODO(bclayton): See if we can move more of this hand-rolled code to the
-// template
-////////////////////////////////////////////////////////////////////////////////
-using TexelFormat = ast::TexelFormat;
-using Access = ast::Access;
-using StorageClass = ast::StorageClass;
-using ParameterUsage = sem::ParameterUsage;
-using PipelineStageSet = sem::PipelineStageSet;
-using PipelineStage = ast::PipelineStage;
-
-bool match_bool(const sem::Type* ty) {
-  return ty->IsAnyOf<Any, sem::Bool>();
-}
-
-const sem::Bool* build_bool(MatchState& state) {
-  return state.builder.create<sem::Bool>();
-}
-
-bool match_f32(const sem::Type* ty) {
-  return ty->IsAnyOf<Any, sem::F32>();
-}
-
-const sem::I32* build_i32(MatchState& state) {
-  return state.builder.create<sem::I32>();
-}
-
-bool match_i32(const sem::Type* ty) {
-  return ty->IsAnyOf<Any, sem::I32>();
-}
-
-const sem::U32* build_u32(MatchState& state) {
-  return state.builder.create<sem::U32>();
-}
-
-bool match_u32(const sem::Type* ty) {
-  return ty->IsAnyOf<Any, sem::U32>();
-}
-
-const sem::F32* build_f32(MatchState& state) {
-  return state.builder.create<sem::F32>();
-}
-
-bool match_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    N = Number::any;
-    T = ty;
-    return true;
-  }
-
-  if (auto* v = ty->As<sem::Vector>()) {
-    N = v->Width();
-    T = v->type();
-    return true;
-  }
-  return false;
-}
-
-const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
-  return state.builder.create<sem::Vector>(el, N.Value());
-}
-
-template <int N>
-bool match_vec(const sem::Type* ty, const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    T = ty;
-    return true;
-  }
-
-  if (auto* v = ty->As<sem::Vector>()) {
-    if (v->Width() == N) {
-      T = v->type();
-      return true;
-    }
-  }
-  return false;
-}
-
-bool match_vec2(const sem::Type* ty, const sem::Type*& T) {
-  return match_vec<2>(ty, T);
-}
-
-const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) {
-  return build_vec(state, Number(2), T);
-}
-
-bool match_vec3(const sem::Type* ty, const sem::Type*& T) {
-  return match_vec<3>(ty, T);
-}
-
-const sem::Vector* build_vec3(MatchState& state, const sem::Type* T) {
-  return build_vec(state, Number(3), T);
-}
-
-bool match_vec4(const sem::Type* ty, const sem::Type*& T) {
-  return match_vec<4>(ty, T);
-}
-
-const sem::Vector* build_vec4(MatchState& state, const sem::Type* T) {
-  return build_vec(state, Number(4), T);
-}
-
-bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    M = Number::any;
-    N = Number::any;
-    T = ty;
-    return true;
-  }
-  if (auto* m = ty->As<sem::Matrix>()) {
-    M = m->columns();
-    N = m->ColumnType()->Width();
-    T = m->type();
-    return true;
-  }
-  return false;
-}
-
-const sem::Matrix* build_mat(MatchState& state,
-                             Number N,
-                             Number M,
-                             const sem::Type* T) {
-  auto* column_type = state.builder.create<sem::Vector>(T, M.Value());
-  return state.builder.create<sem::Matrix>(column_type, N.Value());
-}
-
-bool match_array(const sem::Type* ty, const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    T = ty;
-    return true;
-  }
-
-  if (auto* a = ty->As<sem::Array>()) {
-    if (a->Count() == 0) {
-      T = a->ElemType();
-      return true;
-    }
-  }
-  return false;
-}
-
-const sem::Array* build_array(MatchState& state, const sem::Type* el) {
-  return state.builder.create<sem::Array>(el,
-                                          /* count */ 0,
-                                          /* align */ 0,
-                                          /* size */ 0,
-                                          /* stride */ 0,
-                                          /* stride_implicit */ 0);
-}
-
-bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
-  if (ty->Is<Any>()) {
-    S = Number::any;
-    T = ty;
-    A = Number::any;
-    return true;
-  }
-
-  if (auto* p = ty->As<sem::Pointer>()) {
-    S = Number(static_cast<uint32_t>(p->StorageClass()));
-    T = p->StoreType();
-    A = Number(static_cast<uint32_t>(p->Access()));
-    return true;
-  }
-  return false;
-}
-
-const sem::Pointer* build_ptr(MatchState& state,
-                              Number S,
-                              const sem::Type* T,
-                              Number& A) {
-  return state.builder.create<sem::Pointer>(
-      T, static_cast<ast::StorageClass>(S.Value()),
-      static_cast<ast::Access>(A.Value()));
-}
-
-bool match_atomic(const sem::Type* ty, const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    T = ty;
-    return true;
-  }
-
-  if (auto* a = ty->As<sem::Atomic>()) {
-    T = a->Type();
-    return true;
-  }
-  return false;
-}
-
-const sem::Atomic* build_atomic(MatchState& state, const sem::Type* T) {
-  return state.builder.create<sem::Atomic>(T);
-}
-
-bool match_sampler(const sem::Type* ty) {
-  if (ty->Is<Any>()) {
-    return true;
-  }
-  return ty->Is([](const sem::Sampler* s) {
-    return s->kind() == ast::SamplerKind::kSampler;
-  });
-}
-
-const sem::Sampler* build_sampler(MatchState& state) {
-  return state.builder.create<sem::Sampler>(ast::SamplerKind::kSampler);
-}
-
-bool match_sampler_comparison(const sem::Type* ty) {
-  if (ty->Is<Any>()) {
-    return true;
-  }
-  return ty->Is([](const sem::Sampler* s) {
-    return s->kind() == ast::SamplerKind::kComparisonSampler;
-  });
-}
-
-const sem::Sampler* build_sampler_comparison(MatchState& state) {
-  return state.builder.create<sem::Sampler>(
-      ast::SamplerKind::kComparisonSampler);
-}
-
-bool match_texture(const sem::Type* ty,
-                   ast::TextureDimension dim,
-                   const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    T = ty;
-    return true;
-  }
-  if (auto* v = ty->As<sem::SampledTexture>()) {
-    if (v->dim() == dim) {
-      T = v->type();
-      return true;
-    }
-  }
-  return false;
-}
-
-#define JOIN(a, b) a##b
-
-#define DECLARE_SAMPLED_TEXTURE(suffix, dim)                  \
-  bool JOIN(match_texture_, suffix)(const sem::Type* ty,      \
-                                    const sem::Type*& T) {    \
-    return match_texture(ty, dim, T);                         \
-  }                                                           \
-  const sem::SampledTexture* JOIN(build_texture_, suffix)(    \
-      MatchState & state, const sem::Type* T) {               \
-    return state.builder.create<sem::SampledTexture>(dim, T); \
-  }
-
-DECLARE_SAMPLED_TEXTURE(1d, ast::TextureDimension::k1d)
-DECLARE_SAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
-DECLARE_SAMPLED_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
-DECLARE_SAMPLED_TEXTURE(3d, ast::TextureDimension::k3d)
-DECLARE_SAMPLED_TEXTURE(cube, ast::TextureDimension::kCube)
-DECLARE_SAMPLED_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
-#undef DECLARE_SAMPLED_TEXTURE
-
-bool match_texture_multisampled(const sem::Type* ty,
-                                ast::TextureDimension dim,
-                                const sem::Type*& T) {
-  if (ty->Is<Any>()) {
-    T = ty;
-    return true;
-  }
-  if (auto* v = ty->As<sem::MultisampledTexture>()) {
-    if (v->dim() == dim) {
-      T = v->type();
-      return true;
-    }
-  }
-  return false;
-}
-
-#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim)                            \
-  bool JOIN(match_texture_multisampled_, suffix)(const sem::Type* ty,        \
-                                                 const sem::Type*& T) {      \
-    return match_texture_multisampled(ty, dim, T);                           \
-  }                                                                          \
-  const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \
-      MatchState & state, const sem::Type* T) {                              \
-    return state.builder.create<sem::MultisampledTexture>(dim, T);           \
-  }
-
-DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
-#undef DECLARE_MULTISAMPLED_TEXTURE
-
-bool match_texture_depth(const sem::Type* ty, ast::TextureDimension dim) {
-  if (ty->Is<Any>()) {
-    return true;
-  }
-  return ty->Is([&](const sem::DepthTexture* t) { return t->dim() == dim; });
-}
-
-#define DECLARE_DEPTH_TEXTURE(suffix, dim)                       \
-  bool JOIN(match_texture_depth_, suffix)(const sem::Type* ty) { \
-    return match_texture_depth(ty, dim);                         \
-  }                                                              \
-  const sem::DepthTexture* JOIN(build_texture_depth_,            \
-                                suffix)(MatchState & state) {    \
-    return state.builder.create<sem::DepthTexture>(dim);         \
-  }
-
-DECLARE_DEPTH_TEXTURE(2d, ast::TextureDimension::k2d)
-DECLARE_DEPTH_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
-DECLARE_DEPTH_TEXTURE(cube, ast::TextureDimension::kCube)
-DECLARE_DEPTH_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
-#undef DECLARE_DEPTH_TEXTURE
-
-bool match_texture_depth_multisampled_2d(const sem::Type* ty) {
-  if (ty->Is<Any>()) {
-    return true;
-  }
-  return ty->Is([&](const sem::DepthMultisampledTexture* t) {
-    return t->dim() == ast::TextureDimension::k2d;
-  });
-}
-
-sem::DepthMultisampledTexture* build_texture_depth_multisampled_2d(
-    MatchState& state) {
-  return state.builder.create<sem::DepthMultisampledTexture>(
-      ast::TextureDimension::k2d);
-}
-
-bool match_texture_storage(const sem::Type* ty,
-                           ast::TextureDimension dim,
-                           Number& F,
-                           Number& A) {
-  if (ty->Is<Any>()) {
-    F = Number::any;
-    A = Number::any;
-    return true;
-  }
-  if (auto* v = ty->As<sem::StorageTexture>()) {
-    if (v->dim() == dim) {
-      F = Number(static_cast<uint32_t>(v->texel_format()));
-      A = Number(static_cast<uint32_t>(v->access()));
-      return true;
-    }
-  }
-  return false;
-}
-
-#define DECLARE_STORAGE_TEXTURE(suffix, dim)                                  \
-  bool JOIN(match_texture_storage_, suffix)(const sem::Type* ty, Number& F,   \
-                                            Number& A) {                      \
-    return match_texture_storage(ty, dim, F, A);                              \
-  }                                                                           \
-  const sem::StorageTexture* JOIN(build_texture_storage_, suffix)(            \
-      MatchState & state, Number F, Number A) {                               \
-    auto format = static_cast<TexelFormat>(F.Value());                        \
-    auto access = static_cast<Access>(A.Value());                             \
-    auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types()); \
-    return state.builder.create<sem::StorageTexture>(dim, format, access, T); \
-  }
-
-DECLARE_STORAGE_TEXTURE(1d, ast::TextureDimension::k1d)
-DECLARE_STORAGE_TEXTURE(2d, ast::TextureDimension::k2d)
-DECLARE_STORAGE_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
-DECLARE_STORAGE_TEXTURE(3d, ast::TextureDimension::k3d)
-#undef DECLARE_STORAGE_TEXTURE
-
-bool match_texture_external(const sem::Type* ty) {
-  return ty->IsAnyOf<Any, sem::ExternalTexture>();
-}
-
-const sem::ExternalTexture* build_texture_external(MatchState& state) {
-  return state.builder.create<sem::ExternalTexture>();
-}
-
-// Builtin types starting with a _ prefix cannot be declared in WGSL, so they
-// can only be used as return types. Because of this, they must only match Any,
-// which is used as the return type matcher.
-bool match_modf_result(const sem::Type* ty) {
-  return ty->Is<Any>();
-}
-bool match_modf_result_vec(const sem::Type* ty, Number& N) {
-  if (!ty->Is<Any>()) {
-    return false;
-  }
-  N = Number::any;
-  return true;
-}
-bool match_frexp_result(const sem::Type* ty) {
-  return ty->Is<Any>();
-}
-bool match_frexp_result_vec(const sem::Type* ty, Number& N) {
-  if (!ty->Is<Any>()) {
-    return false;
-  }
-  N = Number::any;
-  return true;
-}
-
-struct NameAndType {
-  std::string name;
-  sem::Type* type;
-};
-const sem::Struct* build_struct(
-    MatchState& state,
-    std::string name,
-    std::initializer_list<NameAndType> member_names_and_types) {
-  uint32_t offset = 0;
-  uint32_t max_align = 0;
-  sem::StructMemberList members;
-  for (auto& m : member_names_and_types) {
-    uint32_t align = m.type->Align();
-    uint32_t size = m.type->Size();
-    offset = utils::RoundUp(align, offset);
-    max_align = std::max(max_align, align);
-    members.emplace_back(state.builder.create<sem::StructMember>(
-        /* declaration */ nullptr,
-        /* name */ state.builder.Sym(m.name),
-        /* type */ m.type,
-        /* index */ static_cast<uint32_t>(members.size()),
-        /* offset */ offset,
-        /* align */ align,
-        /* size */ size));
-    offset += size;
-  }
-  uint32_t size_without_padding = offset;
-  uint32_t size_with_padding = utils::RoundUp(max_align, offset);
-  return state.builder.create<sem::Struct>(
-      /* declaration */ nullptr,
-      /* name */ state.builder.Sym(name),
-      /* members */ members,
-      /* align */ max_align,
-      /* size */ size_with_padding,
-      /* size_no_padding */ size_without_padding);
-}
-
-const sem::Struct* build_modf_result(MatchState& state) {
-  auto* f32 = state.builder.create<sem::F32>();
-  return build_struct(state, "__modf_result", {{"fract", f32}, {"whole", f32}});
-}
-const sem::Struct* build_modf_result_vec(MatchState& state, Number& n) {
-  auto* vec_f32 = state.builder.create<sem::Vector>(
-      state.builder.create<sem::F32>(), n.Value());
-  return build_struct(state, "__modf_result_vec" + std::to_string(n.Value()),
-                      {{"fract", vec_f32}, {"whole", vec_f32}});
-}
-const sem::Struct* build_frexp_result(MatchState& state) {
-  auto* f32 = state.builder.create<sem::F32>();
-  auto* i32 = state.builder.create<sem::I32>();
-  return build_struct(state, "__frexp_result", {{"sig", f32}, {"exp", i32}});
-}
-const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n) {
-  auto* vec_f32 = state.builder.create<sem::Vector>(
-      state.builder.create<sem::F32>(), n.Value());
-  auto* vec_i32 = state.builder.create<sem::Vector>(
-      state.builder.create<sem::I32>(), n.Value());
-  return build_struct(state, "__frexp_result_vec" + std::to_string(n.Value()),
-                      {{"sig", vec_f32}, {"exp", vec_i32}});
-}
-
-/// ParameterInfo describes a parameter
-struct ParameterInfo {
-  /// The parameter usage (parameter name in definition file)
-  const ParameterUsage usage;
-
-  /// Pointer to a list of indices that are used to match the parameter type.
-  /// The matcher indices index on Matchers::type and / or Matchers::number.
-  /// These indices are consumed by the matchers themselves.
-  /// The first index is always a TypeMatcher.
-  MatcherIndex const* const matcher_indices;
-};
-
-/// OpenTypeInfo describes an open type
-struct OpenTypeInfo {
-  /// Name of the open type (e.g. 'T')
-  const char* name;
-  /// Optional type matcher constraint.
-  /// Either an index in Matchers::type, or kNoMatcher
-  const MatcherIndex matcher_index;
-};
-
-/// OpenNumberInfo describes an open number
-struct OpenNumberInfo {
-  /// Name of the open number (e.g. 'N')
-  const char* name;
-  /// Optional number matcher constraint.
-  /// Either an index in Matchers::number, or kNoMatcher
-  const MatcherIndex matcher_index;
-};
-
-/// OverloadInfo describes a single function overload
-struct OverloadInfo {
-  /// Total number of parameters for the overload
-  const uint8_t num_parameters;
-  /// Total number of open types for the overload
-  const uint8_t num_open_types;
-  /// Total number of open numbers for the overload
-  const uint8_t num_open_numbers;
-  /// Pointer to the first open type
-  OpenTypeInfo const* const open_types;
-  /// Pointer to the first open number
-  OpenNumberInfo const* const open_numbers;
-  /// Pointer to the first parameter
-  ParameterInfo const* const parameters;
-  /// Pointer to a list of matcher indices that index on Matchers::type and
-  /// Matchers::number, used to build the return type. If the function has no
-  /// return type then this is null
-  MatcherIndex const* const return_matcher_indices;
-  /// The pipeline stages that this overload can be used in
-  PipelineStageSet supported_stages;
-  /// True if the overload is marked as deprecated
-  bool is_deprecated;
-};
-
-/// BuiltinInfo describes a builtin function
-struct BuiltinInfo {
-  /// Number of overloads of the builtin function
-  const uint8_t num_overloads;
-  /// Pointer to the start of the overloads for the function
-  OverloadInfo const* const overloads;
-};
-
-#include "builtin_table.inl"
-
-/// BuiltinPrototype describes a fully matched builtin function, which is
-/// used as a lookup for building unique sem::Builtin instances.
-struct BuiltinPrototype {
-  /// Parameter describes a single parameter
-  struct Parameter {
-    /// Parameter type
-    const sem::Type* const type;
-    /// Parameter usage
-    ParameterUsage const usage = ParameterUsage::kNone;
-  };
-
-  /// Hasher provides a hash function for the BuiltinPrototype
-  struct Hasher {
-    /// @param i the BuiltinPrototype to create a hash for
-    /// @return the hash value
-    inline std::size_t operator()(const BuiltinPrototype& i) const {
-      size_t hash = utils::Hash(i.parameters.size());
-      for (auto& p : i.parameters) {
-        utils::HashCombine(&hash, p.type, p.usage);
-      }
-      return utils::Hash(hash, i.type, i.return_type, i.supported_stages,
-                         i.is_deprecated);
-    }
-  };
-
-  sem::BuiltinType type = sem::BuiltinType::kNone;
-  std::vector<Parameter> parameters;
-  sem::Type const* return_type = nullptr;
-  PipelineStageSet supported_stages;
-  bool is_deprecated = false;
-};
-
-/// Equality operator for BuiltinPrototype
-bool operator==(const BuiltinPrototype& a, const BuiltinPrototype& b) {
-  if (a.type != b.type || a.supported_stages != b.supported_stages ||
-      a.return_type != b.return_type || a.is_deprecated != b.is_deprecated ||
-      a.parameters.size() != b.parameters.size()) {
-    return false;
-  }
-  for (size_t i = 0; i < a.parameters.size(); i++) {
-    auto& pa = a.parameters[i];
-    auto& pb = b.parameters[i];
-    if (pa.type != pb.type || pa.usage != pb.usage) {
-      return false;
-    }
-  }
-  return true;
-}
-
-/// Impl is the private implementation of the BuiltinTable interface.
-class Impl : public BuiltinTable {
- public:
-  explicit Impl(ProgramBuilder& builder);
-
-  const sem::Builtin* Lookup(sem::BuiltinType builtin_type,
-                             const std::vector<const sem::Type*>& args,
-                             const Source& source) override;
-
- private:
-  const sem::Builtin* Match(sem::BuiltinType builtin_type,
-                            const OverloadInfo& overload,
-                            const std::vector<const sem::Type*>& args,
-                            int& match_score);
-
-  MatchState Match(ClosedState& closed,
-                   const OverloadInfo& overload,
-                   MatcherIndex const* matcher_indices) const;
-
-  void PrintOverload(std::ostream& ss,
-                     const OverloadInfo& overload,
-                     sem::BuiltinType builtin_type) const;
-
-  ProgramBuilder& builder;
-  Matchers matchers;
-  std::unordered_map<BuiltinPrototype, sem::Builtin*, BuiltinPrototype::Hasher>
-      builtins;
-};
-
-/// @return a string representing a call to a builtin with the given argument
-/// types.
-std::string CallSignature(ProgramBuilder& builder,
-                          sem::BuiltinType builtin_type,
-                          const std::vector<const sem::Type*>& args) {
-  std::stringstream ss;
-  ss << sem::str(builtin_type) << "(";
-  {
-    bool first = true;
-    for (auto* arg : args) {
-      if (!first) {
-        ss << ", ";
-      }
-      first = false;
-      ss << arg->UnwrapRef()->FriendlyName(builder.Symbols());
-    }
-  }
-  ss << ")";
-
-  return ss.str();
-}
-
-std::string OpenTypeMatcher::String(MatchState& state) const {
-  return state.overload.open_types[index_].name;
-}
-
-std::string OpenNumberMatcher::String(MatchState& state) const {
-  return state.overload.open_numbers[index_].name;
-}
-
-Impl::Impl(ProgramBuilder& b) : builder(b) {}
-
-const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
-                                 const std::vector<const sem::Type*>& args,
-                                 const Source& source) {
-  // Candidate holds information about a mismatched overload that could be what
-  // the user intended to call.
-  struct Candidate {
-    const OverloadInfo* overload;
-    int score;
-  };
-
-  // The list of failed matches that had promise.
-  std::vector<Candidate> candidates;
-
-  auto& builtin = kBuiltins[static_cast<uint32_t>(builtin_type)];
-  for (uint32_t o = 0; o < builtin.num_overloads; o++) {
-    int match_score = 1000;
-    auto& overload = builtin.overloads[o];
-    if (auto* match = Match(builtin_type, overload, args, match_score)) {
-      return match;
-    }
-    if (match_score > 0) {
-      candidates.emplace_back(Candidate{&overload, match_score});
-    }
-  }
-
-  // Sort the candidates with the most promising first
-  std::stable_sort(
-      candidates.begin(), candidates.end(),
-      [](const Candidate& a, const Candidate& b) { return a.score > b.score; });
-
-  // Generate an error message
-  std::stringstream ss;
-  ss << "no matching call to " << CallSignature(builder, builtin_type, args)
-     << std::endl;
-  if (!candidates.empty()) {
-    ss << std::endl;
-    ss << candidates.size() << " candidate function"
-       << (candidates.size() > 1 ? "s:" : ":") << std::endl;
-    for (auto& candidate : candidates) {
-      ss << "  ";
-      PrintOverload(ss, *candidate.overload, builtin_type);
-      ss << std::endl;
-    }
-  }
-  builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
-  return nullptr;
-}
-
-const sem::Builtin* Impl::Match(sem::BuiltinType builtin_type,
-                                const OverloadInfo& overload,
-                                const std::vector<const sem::Type*>& args,
-                                int& match_score) {
-  // Score wait for argument <-> parameter count matches / mismatches
-  constexpr int kScorePerParamArgMismatch = -1;
-  constexpr int kScorePerMatchedParam = 2;
-  constexpr int kScorePerMatchedOpenType = 1;
-  constexpr int kScorePerMatchedOpenNumber = 1;
-
-  auto num_parameters = overload.num_parameters;
-  auto num_arguments = static_cast<decltype(num_parameters)>(args.size());
-
-  bool overload_matched = true;
-
-  if (num_parameters != num_arguments) {
-    match_score +=
-        kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
-                                     std::min(num_parameters, num_arguments));
-    overload_matched = false;
-  }
-
-  ClosedState closed(builder);
-
-  std::vector<BuiltinPrototype::Parameter> parameters;
-
-  auto num_params = std::min(num_parameters, num_arguments);
-  for (uint32_t p = 0; p < num_params; p++) {
-    auto& parameter = overload.parameters[p];
-    auto* indices = parameter.matcher_indices;
-    auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef());
-    if (type) {
-      parameters.emplace_back(
-          BuiltinPrototype::Parameter{type, parameter.usage});
-      match_score += kScorePerMatchedParam;
-    } else {
-      overload_matched = false;
-    }
-  }
-
-  if (overload_matched) {
-    // Check all constrained open types matched
-    for (uint32_t ot = 0; ot < overload.num_open_types; ot++) {
-      auto& open_type = overload.open_types[ot];
-      if (open_type.matcher_index != kNoMatcher) {
-        auto* index = &open_type.matcher_index;
-        if (Match(closed, overload, index).Type(closed.Type(ot))) {
-          match_score += kScorePerMatchedOpenType;
-        } else {
-          overload_matched = false;
-        }
-      }
-    }
-  }
-
-  if (overload_matched) {
-    // Check all constrained open numbers matched
-    for (uint32_t on = 0; on < overload.num_open_numbers; on++) {
-      auto& open_number = overload.open_numbers[on];
-      if (open_number.matcher_index != kNoMatcher) {
-        auto* index = &open_number.matcher_index;
-        if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) {
-          match_score += kScorePerMatchedOpenNumber;
-        } else {
-          overload_matched = false;
-        }
-      }
-    }
-  }
-
-  if (!overload_matched) {
-    return nullptr;
-  }
-
-  // Build the return type
-  const sem::Type* return_type = nullptr;
-  if (auto* indices = overload.return_matcher_indices) {
-    Any any;
-    return_type = Match(closed, overload, indices).Type(&any);
-    if (!return_type) {
-      std::stringstream ss;
-      PrintOverload(ss, overload, builtin_type);
-      TINT_ICE(Resolver, builder.Diagnostics())
-          << "MatchState.Match() returned null for " << ss.str();
-      return nullptr;
-    }
-  } else {
-    return_type = builder.create<sem::Void>();
-  }
-
-  BuiltinPrototype builtin;
-  builtin.type = builtin_type;
-  builtin.return_type = return_type;
-  builtin.parameters = std::move(parameters);
-  builtin.supported_stages = overload.supported_stages;
-  builtin.is_deprecated = overload.is_deprecated;
-
-  // De-duplicate builtins that are identical.
-  return utils::GetOrCreate(builtins, builtin, [&] {
-    std::vector<sem::Parameter*> params;
-    params.reserve(builtin.parameters.size());
-    for (auto& p : builtin.parameters) {
-      params.emplace_back(builder.create<sem::Parameter>(
-          nullptr, static_cast<uint32_t>(params.size()), p.type,
-          ast::StorageClass::kNone, ast::Access::kUndefined, p.usage));
-    }
-    return builder.create<sem::Builtin>(
-        builtin.type, builtin.return_type, std::move(params),
-        builtin.supported_stages, builtin.is_deprecated);
-  });
-}
-
-MatchState Impl::Match(ClosedState& closed,
-                       const OverloadInfo& overload,
-                       MatcherIndex const* matcher_indices) const {
-  return MatchState(builder, closed, matchers, overload, matcher_indices);
-}
-
-void Impl::PrintOverload(std::ostream& ss,
-                         const OverloadInfo& overload,
-                         sem::BuiltinType builtin_type) const {
-  ClosedState closed(builder);
-
-  ss << builtin_type << "(";
-  for (uint32_t p = 0; p < overload.num_parameters; p++) {
-    auto& parameter = overload.parameters[p];
-    if (p > 0) {
-      ss << ", ";
-    }
-    if (parameter.usage != ParameterUsage::kNone) {
-      ss << sem::str(parameter.usage) << ": ";
-    }
-    auto* indices = parameter.matcher_indices;
-    ss << Match(closed, overload, indices).TypeName();
-  }
-  ss << ")";
-  if (overload.return_matcher_indices) {
-    ss << " -> ";
-    auto* indices = overload.return_matcher_indices;
-    ss << Match(closed, overload, indices).TypeName();
-  }
-
-  bool first = true;
-  auto separator = [&] {
-    ss << (first ? "  where: " : ", ");
-    first = false;
-  };
-  for (uint32_t i = 0; i < overload.num_open_types; i++) {
-    auto& open_type = overload.open_types[i];
-    if (open_type.matcher_index != kNoMatcher) {
-      separator();
-      ss << open_type.name;
-      auto* index = &open_type.matcher_index;
-      ss << " is " << Match(closed, overload, index).TypeName();
-    }
-  }
-  for (uint32_t i = 0; i < overload.num_open_numbers; i++) {
-    auto& open_number = overload.open_numbers[i];
-    if (open_number.matcher_index != kNoMatcher) {
-      separator();
-      ss << open_number.name;
-      auto* index = &open_number.matcher_index;
-      ss << " is " << Match(closed, overload, index).NumName();
-    }
-  }
-}
-
-const sem::Type* MatchState::Type(const sem::Type* ty) {
-  MatcherIndex matcher_index = *matcher_indices_++;
-  auto* matcher = matchers.type[matcher_index];
-  return matcher->Match(*this, ty);
-}
-
-Number MatchState::Num(Number number) {
-  MatcherIndex matcher_index = *matcher_indices_++;
-  auto* matcher = matchers.number[matcher_index];
-  return matcher->Match(*this, number);
-}
-
-std::string MatchState::TypeName() {
-  MatcherIndex matcher_index = *matcher_indices_++;
-  auto* matcher = matchers.type[matcher_index];
-  return matcher->String(*this);
-}
-
-std::string MatchState::NumName() {
-  MatcherIndex matcher_index = *matcher_indices_++;
-  auto* matcher = matchers.number[matcher_index];
-  return matcher->String(*this);
-}
-
-}  // namespace
-
-std::unique_ptr<BuiltinTable> BuiltinTable::Create(ProgramBuilder& builder) {
-  return std::make_unique<Impl>(builder);
-}
-
-BuiltinTable::~BuiltinTable() = default;
-
-/// TypeInfo for the Any type declared in the anonymous namespace above
-TINT_INSTANTIATE_TYPEINFO(Any);
-
-}  // namespace tint
diff --git a/src/builtin_table.h b/src/builtin_table.h
deleted file mode 100644
index c681fe7..0000000
--- a/src/builtin_table.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_BUILTIN_TABLE_H_
-#define SRC_BUILTIN_TABLE_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "src/sem/builtin.h"
-
-namespace tint {
-
-// Forward declarations
-class ProgramBuilder;
-
-/// BuiltinTable is a lookup table of all the WGSL builtin functions
-class BuiltinTable {
- public:
-  /// @param builder the program builder
-  /// @return a pointer to a newly created BuiltinTable
-  static std::unique_ptr<BuiltinTable> Create(ProgramBuilder& builder);
-
-  /// Destructor
-  virtual ~BuiltinTable();
-
-  /// Lookup looks for the builtin overload with the given signature, raising
-  /// an error diagnostic if the builtin was not found.
-  /// @param type the builtin type
-  /// @param args the argument types passed to the builtin function
-  /// @param source the source of the builtin call
-  /// @return the semantic builtin if found, otherwise nullptr
-  virtual const sem::Builtin* Lookup(sem::BuiltinType type,
-                                     const std::vector<const sem::Type*>& args,
-                                     const Source& source) = 0;
-};
-
-}  // namespace tint
-
-#endif  // SRC_BUILTIN_TABLE_H_
diff --git a/src/builtin_table_test.cc b/src/builtin_table_test.cc
deleted file mode 100644
index a9ceff0..0000000
--- a/src/builtin_table_test.cc
+++ /dev/null
@@ -1,601 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/builtin_table.h"
-
-#include "gmock/gmock.h"
-#include "src/program_builder.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace {
-
-using ::testing::HasSubstr;
-
-using BuiltinType = sem::BuiltinType;
-using Parameter = sem::Parameter;
-using ParameterUsage = sem::ParameterUsage;
-
-class BuiltinTableTest : public testing::Test, public ProgramBuilder {
- public:
-  std::unique_ptr<BuiltinTable> table = BuiltinTable::Create(*this);
-};
-
-TEST_F(BuiltinTableTest, MatchF32) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kCos, {f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kCos);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
-}
-
-TEST_F(BuiltinTableTest, MismatchF32) {
-  auto* i32 = create<sem::I32>();
-  auto* result = table->Lookup(BuiltinType::kCos, {i32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchU32) {
-  auto* f32 = create<sem::F32>();
-  auto* u32 = create<sem::U32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {u32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kUnpack2x16float);
-  EXPECT_EQ(result->ReturnType(), vec2_f32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
-}
-
-TEST_F(BuiltinTableTest, MismatchU32) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchI32) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), vec4_f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
-}
-
-TEST_F(BuiltinTableTest, MismatchI32) {
-  auto* f32 = create<sem::F32>();
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
-  auto* result = table->Lookup(BuiltinType::kTextureLoad, {tex, f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchIU32AsI32) {
-  auto* i32 = create<sem::I32>();
-  auto* result = table->Lookup(BuiltinType::kCountOneBits, {i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits);
-  EXPECT_EQ(result->ReturnType(), i32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), i32);
-}
-
-TEST_F(BuiltinTableTest, MatchIU32AsU32) {
-  auto* u32 = create<sem::U32>();
-  auto* result = table->Lookup(BuiltinType::kCountOneBits, {u32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits);
-  EXPECT_EQ(result->ReturnType(), u32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
-}
-
-TEST_F(BuiltinTableTest, MismatchIU32) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kCountOneBits, {f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchFIU32AsI32) {
-  auto* i32 = create<sem::I32>();
-  auto* result = table->Lookup(BuiltinType::kClamp, {i32, i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
-  EXPECT_EQ(result->ReturnType(), i32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-}
-
-TEST_F(BuiltinTableTest, MatchFIU32AsU32) {
-  auto* u32 = create<sem::U32>();
-  auto* result = table->Lookup(BuiltinType::kClamp, {u32, u32, u32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
-  EXPECT_EQ(result->ReturnType(), u32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), u32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), u32);
-}
-
-TEST_F(BuiltinTableTest, MatchFIU32AsF32) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), f32);
-}
-
-TEST_F(BuiltinTableTest, MismatchFIU32) {
-  auto* bool_ = create<sem::Bool>();
-  auto* result =
-      table->Lookup(BuiltinType::kClamp, {bool_, bool_, bool_}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchBool) {
-  auto* f32 = create<sem::F32>();
-  auto* bool_ = create<sem::Bool>();
-  auto* result =
-      table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kSelect);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), bool_);
-}
-
-TEST_F(BuiltinTableTest, MismatchBool) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kSelect, {f32, f32, f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchPointer) {
-  auto* i32 = create<sem::I32>();
-  auto* atomicI32 = create<sem::Atomic>(i32);
-  auto* ptr = create<sem::Pointer>(atomicI32, ast::StorageClass::kWorkgroup,
-                                   ast::Access::kReadWrite);
-  auto* result = table->Lookup(BuiltinType::kAtomicLoad, {ptr}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kAtomicLoad);
-  EXPECT_EQ(result->ReturnType(), i32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), ptr);
-}
-
-TEST_F(BuiltinTableTest, MismatchPointer) {
-  auto* i32 = create<sem::I32>();
-  auto* atomicI32 = create<sem::Atomic>(i32);
-  auto* result = table->Lookup(BuiltinType::kAtomicLoad, {atomicI32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchArray) {
-  auto* arr = create<sem::Array>(create<sem::U32>(), 0, 4, 4, 4, 4);
-  auto* arr_ptr = create<sem::Pointer>(arr, ast::StorageClass::kStorage,
-                                       ast::Access::kReadWrite);
-  auto* result = table->Lookup(BuiltinType::kArrayLength, {arr_ptr}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kArrayLength);
-  EXPECT_TRUE(result->ReturnType()->Is<sem::U32>());
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  auto* param_type = result->Parameters()[0]->Type();
-  ASSERT_TRUE(param_type->Is<sem::Pointer>());
-  EXPECT_TRUE(param_type->As<sem::Pointer>()->StoreType()->Is<sem::Array>());
-}
-
-TEST_F(BuiltinTableTest, MismatchArray) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kArrayLength, {f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchSampler) {
-  auto* f32 = create<sem::F32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
-  auto* result = table->Lookup(BuiltinType::kTextureSample,
-                               {tex, sampler, vec2_f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureSample);
-  EXPECT_EQ(result->ReturnType(), vec4_f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), sampler);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kSampler);
-  EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kCoords);
-}
-
-TEST_F(BuiltinTableTest, MismatchSampler) {
-  auto* f32 = create<sem::F32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
-  auto* result = table->Lookup(BuiltinType::kTextureSample,
-                               {tex, f32, vec2_f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchSampledTexture) {
-  auto* i32 = create<sem::I32>();
-  auto* f32 = create<sem::F32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), vec4_f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
-}
-
-TEST_F(BuiltinTableTest, MatchMultisampledTexture) {
-  auto* i32 = create<sem::I32>();
-  auto* f32 = create<sem::F32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* tex = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), vec4_f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex);
-}
-
-TEST_F(BuiltinTableTest, MatchDepthTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
-}
-
-TEST_F(BuiltinTableTest, MatchDepthMultisampledTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* tex = create<sem::DepthMultisampledTexture>(ast::TextureDimension::k2d);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex);
-}
-
-TEST_F(BuiltinTableTest, MatchExternalTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* tex = create<sem::ExternalTexture>();
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
-  EXPECT_EQ(result->ReturnType(), vec4_f32);
-  ASSERT_EQ(result->Parameters().size(), 2u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-}
-
-TEST_F(BuiltinTableTest, MatchWOStorageTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* vec4_f32 = create<sem::Vector>(f32, 4);
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kR32Float, Types());
-  auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                          ast::TexelFormat::kR32Float,
-                                          ast::Access::kWrite, subtype);
-
-  auto* result = table->Lookup(BuiltinType::kTextureStore,
-                               {tex, vec2_i32, vec4_f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kTextureStore);
-  EXPECT_TRUE(result->ReturnType()->Is<sem::Void>());
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
-  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
-  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
-  EXPECT_EQ(result->Parameters()[2]->Type(), vec4_f32);
-  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kValue);
-}
-
-TEST_F(BuiltinTableTest, MismatchTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-  auto* vec2_i32 = create<sem::Vector>(i32, 2);
-  auto* result =
-      table->Lookup(BuiltinType::kTextureLoad, {f32, vec2_i32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, ImplicitLoadOnReference) {
-  auto* f32 = create<sem::F32>();
-  auto* result =
-      table->Lookup(BuiltinType::kCos,
-                    {create<sem::Reference>(f32, ast::StorageClass::kFunction,
-                                            ast::Access::kReadWrite)},
-                    Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kCos);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
-}
-
-TEST_F(BuiltinTableTest, MatchOpenType) {
-  auto* f32 = create<sem::F32>();
-  auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
-  EXPECT_EQ(result->ReturnType(), f32);
-  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), f32);
-}
-
-TEST_F(BuiltinTableTest, MismatchOpenType) {
-  auto* f32 = create<sem::F32>();
-  auto* u32 = create<sem::U32>();
-  auto* result = table->Lookup(BuiltinType::kClamp, {f32, u32, f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchOpenSizeVector) {
-  auto* f32 = create<sem::F32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* result = table->Lookup(BuiltinType::kClamp,
-                               {vec2_f32, vec2_f32, vec2_f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
-  EXPECT_EQ(result->ReturnType(), vec2_f32);
-  ASSERT_EQ(result->Parameters().size(), 3u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), vec2_f32);
-  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_f32);
-  EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32);
-}
-
-TEST_F(BuiltinTableTest, MismatchOpenSizeVector) {
-  auto* f32 = create<sem::F32>();
-  auto* u32 = create<sem::U32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* result =
-      table->Lookup(BuiltinType::kClamp, {vec2_f32, u32, vec2_f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, MatchOpenSizeMatrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3_f32 = create<sem::Vector>(f32, 3);
-  auto* mat3_f32 = create<sem::Matrix>(vec3_f32, 3);
-  auto* result = table->Lookup(BuiltinType::kDeterminant, {mat3_f32}, Source{});
-  ASSERT_NE(result, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-  EXPECT_EQ(result->Type(), BuiltinType::kDeterminant);
-  EXPECT_EQ(result->ReturnType(), f32);
-  ASSERT_EQ(result->Parameters().size(), 1u);
-  EXPECT_EQ(result->Parameters()[0]->Type(), mat3_f32);
-}
-
-TEST_F(BuiltinTableTest, MismatchOpenSizeMatrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec2_f32 = create<sem::Vector>(f32, 2);
-  auto* mat3x2_f32 = create<sem::Matrix>(vec2_f32, 3);
-  auto* result =
-      table->Lookup(BuiltinType::kDeterminant, {mat3x2_f32}, Source{});
-  ASSERT_EQ(result, nullptr);
-  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
-}
-
-TEST_F(BuiltinTableTest, OverloadOrderByNumberOfParameters) {
-  // None of the arguments match, so expect the overloads with 2 parameters to
-  // come first
-  auto* bool_ = create<sem::Bool>();
-  table->Lookup(BuiltinType::kTextureDimensions, {bool_, bool_}, Source{});
-  ASSERT_EQ(Diagnostics().str(),
-            R"(error: no matching call to textureDimensions(bool, bool)
-
-27 candidate functions:
-  textureDimensions(texture: texture_1d<T>, level: i32) -> i32  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_1d<T>) -> i32  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_3d<T>) -> vec3<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_depth_2d) -> vec2<i32>
-  textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube_array) -> vec2<i32>
-  textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32>
-  textureDimensions(texture: texture_storage_1d<F, A>) -> i32  where: A is write
-  textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>  where: A is write
-  textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>  where: A is write
-  textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>  where: A is write
-  textureDimensions(texture: texture_external) -> vec2<i32>
-)");
-}
-
-TEST_F(BuiltinTableTest, OverloadOrderByMatchingParameter) {
-  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
-  auto* bool_ = create<sem::Bool>();
-  table->Lookup(BuiltinType::kTextureDimensions, {tex, bool_}, Source{});
-  ASSERT_EQ(
-      Diagnostics().str(),
-      R"(error: no matching call to textureDimensions(texture_depth_2d, bool)
-
-27 candidate functions:
-  textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_2d) -> vec2<i32>
-  textureDimensions(texture: texture_1d<T>, level: i32) -> i32  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32>
-  textureDimensions(texture: texture_1d<T>) -> i32  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_3d<T>) -> vec3<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_cube_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
-  textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube) -> vec2<i32>
-  textureDimensions(texture: texture_depth_cube_array) -> vec2<i32>
-  textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32>
-  textureDimensions(texture: texture_storage_1d<F, A>) -> i32  where: A is write
-  textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>  where: A is write
-  textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>  where: A is write
-  textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>  where: A is write
-  textureDimensions(texture: texture_external) -> vec2<i32>
-)");
-}
-
-TEST_F(BuiltinTableTest, SameOverloadReturnsSameBuiltinPointer) {
-  auto* f32 = create<sem::F32>();
-  auto* vec2_f32 = create<sem::Vector>(create<sem::F32>(), 2);
-  auto* bool_ = create<sem::Bool>();
-  auto* a = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
-  ASSERT_NE(a, nullptr) << Diagnostics().str();
-
-  auto* b = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
-  ASSERT_NE(b, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-
-  auto* c = table->Lookup(BuiltinType::kSelect, {vec2_f32, vec2_f32, bool_},
-                          Source{});
-  ASSERT_NE(c, nullptr) << Diagnostics().str();
-  ASSERT_EQ(Diagnostics().str(), "");
-
-  EXPECT_EQ(a, b);
-  EXPECT_NE(a, c);
-  EXPECT_NE(b, c);
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/castable.cc b/src/castable.cc
deleted file mode 100644
index 02c3ebc..0000000
--- a/src/castable.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 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/castable.h"
-
-namespace tint {
-
-/// The unique TypeInfo for the CastableBase type
-/// @return doxygen-thinks-this-static-field-is-a-function :(
-template <>
-const TypeInfo detail::TypeInfoOf<CastableBase>::info{
-    nullptr,
-    "CastableBase",
-    tint::TypeInfo::HashCodeOf<CastableBase>(),
-    tint::TypeInfo::FullHashCodeOf<CastableBase>(),
-};
-
-}  // namespace tint
diff --git a/src/castable.h b/src/castable.h
deleted file mode 100644
index 2906563..0000000
--- a/src/castable.h
+++ /dev/null
@@ -1,736 +0,0 @@
-// Copyright 2020 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_CASTABLE_H_
-#define SRC_CASTABLE_H_
-
-#include <stdint.h>
-#include <functional>
-#include <tuple>
-#include <utility>
-
-#include "src/traits.h"
-#include "src/utils/crc32.h"
-
-#if defined(__clang__)
-/// Temporarily disable certain warnings when using Castable API
-#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS()                               \
-  _Pragma("clang diagnostic push")                                     /**/ \
-      _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") /**/ \
-      static_assert(true, "require extra semicolon")
-
-/// Restore disabled warnings
-#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \
-  _Pragma("clang diagnostic pop") /**/       \
-      static_assert(true, "require extra semicolon")
-#else
-#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() \
-  static_assert(true, "require extra semicolon")
-#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \
-  static_assert(true, "require extra semicolon")
-#endif
-
-TINT_CASTABLE_PUSH_DISABLE_WARNINGS();
-
-namespace tint {
-
-// Forward declaration
-class CastableBase;
-
-/// Ignore is used as a special type used for skipping over types for trait
-/// helper functions.
-class Ignore {};
-
-namespace detail {
-template <typename T>
-struct TypeInfoOf;
-
-}  // namespace detail
-
-/// True if all template types that are not Ignore derive from CastableBase
-template <typename... TYPES>
-static constexpr bool IsCastable =
-    ((traits::IsTypeOrDerived<TYPES, CastableBase> ||
-      std::is_same_v<TYPES, Ignore>)&&...) &&
-    !(std::is_same_v<TYPES, Ignore> && ...);
-
-/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
-#define TINT_INSTANTIATE_TYPEINFO(CLASS)                      \
-  TINT_CASTABLE_PUSH_DISABLE_WARNINGS();                      \
-  template <>                                                 \
-  const tint::TypeInfo tint::detail::TypeInfoOf<CLASS>::info{ \
-      &tint::detail::TypeInfoOf<CLASS::TrueBase>::info,       \
-      #CLASS,                                                 \
-      tint::TypeInfo::HashCodeOf<CLASS>(),                    \
-      tint::TypeInfo::FullHashCodeOf<CLASS>(),                \
-  };                                                          \
-  TINT_CASTABLE_POP_DISABLE_WARNINGS()
-
-/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and
-/// As().
-enum CastFlags {
-  /// Disables the static_assert() inside Is(), that compile-time-verifies that
-  /// the cast is possible. This flag may be useful for highly-generic template
-  /// code that needs to compile for template permutations that generate
-  /// impossible casts.
-  kDontErrorOnImpossibleCast = 1,
-};
-
-/// TypeInfo holds type information for a Castable type.
-struct TypeInfo {
-  /// The type of a hash code
-  using HashCode = uint64_t;
-
-  /// The base class of this type
-  const TypeInfo* base;
-  /// The type name
-  const char* name;
-  /// The type hash code
-  const HashCode hashcode;
-  /// The type hash code bitwise-or'd with all ancestor's hashcodes.
-  const HashCode full_hashcode;
-
-  /// @param type the test type info
-  /// @returns true if the class with this TypeInfo is of, or derives from the
-  /// class with the given TypeInfo.
-  inline bool Is(const tint::TypeInfo* type) const {
-    // Optimization: Check whether the all the bits of the type's hashcode can
-    // be found in the full_hashcode. If a single bit is missing, then we
-    // can quickly tell that that this TypeInfo does not derive from `type`.
-    if ((full_hashcode & type->hashcode) != type->hashcode) {
-      return false;
-    }
-
-    // Walk the base types, starting with this TypeInfo, to see if any of the
-    // pointers match `type`.
-    for (auto* ti = this; ti != nullptr; ti = ti->base) {
-      if (ti == type) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /// @returns true if `type` derives from the class `TO`
-  /// @param type the object type to test from, which must be, or derive from
-  /// type `FROM`.
-  /// @see CastFlags
-  template <typename TO, typename FROM, int FLAGS = 0>
-  static inline bool Is(const tint::TypeInfo* type) {
-    constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
-    constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
-    constexpr const bool nocast = std::is_same<FROM, TO>::value;
-    constexpr const bool assert_is_castable =
-        (FLAGS & kDontErrorOnImpossibleCast) == 0;
-
-    static_assert(upcast || downcast || nocast || !assert_is_castable,
-                  "impossible cast");
-
-    if (upcast || nocast) {
-      return true;
-    }
-
-    return type->Is(&Of<std::remove_cv_t<TO>>());
-  }
-
-  /// @returns the static TypeInfo for the type T
-  template <typename T>
-  static const TypeInfo& Of() {
-    return detail::TypeInfoOf<std::remove_cv_t<T>>::info;
-  }
-
-  /// @returns a compile-time hashcode for the type `T`.
-  /// @note the returned hashcode will have at most 2 bits set, as the hashes
-  /// are expected to be used in bloom-filters which will quickly saturate when
-  /// multiple hashcodes are bitwise-or'd together.
-  template <typename T>
-  static constexpr HashCode HashCodeOf() {
-    static_assert(IsCastable<T>, "T is not Castable");
-    static_assert(
-        std::is_same_v<T, std::remove_cv_t<T>>,
-        "Strip const / volatile decorations before calling HashCodeOf");
-    /// Use the compiler's "pretty" function name, which includes the template
-    /// type, to obtain a unique hash value.
-#ifdef _MSC_VER
-    constexpr uint32_t crc = utils::CRC32(__FUNCSIG__);
-#else
-    constexpr uint32_t crc = utils::CRC32(__PRETTY_FUNCTION__);
-#endif
-    constexpr uint32_t bit_a = (crc & 63);
-    constexpr uint32_t bit_b = ((crc >> 6) & 63);
-    return (static_cast<HashCode>(1) << bit_a) |
-           (static_cast<HashCode>(1) << bit_b);
-  }
-
-  /// @returns the hashcode of the given type, bitwise-or'd with the hashcodes
-  /// of all base classes.
-  template <typename T>
-  static constexpr HashCode FullHashCodeOf() {
-    if constexpr (std::is_same_v<T, CastableBase>) {
-      return HashCodeOf<CastableBase>();
-    } else {
-      return HashCodeOf<T>() | FullHashCodeOf<typename T::TrueBase>();
-    }
-  }
-
-  /// @returns the bitwise-or'd hashcodes of all the types of the tuple `TUPLE`.
-  /// @see HashCodeOf
-  template <typename TUPLE>
-  static constexpr HashCode CombinedHashCodeOfTuple() {
-    constexpr auto kCount = std::tuple_size_v<TUPLE>;
-    if constexpr (kCount == 0) {
-      return 0;
-    } else if constexpr (kCount == 1) {
-      return HashCodeOf<std::remove_cv_t<std::tuple_element_t<0, TUPLE>>>();
-    } else {
-      constexpr auto kMid = kCount / 2;
-      return CombinedHashCodeOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() |
-             CombinedHashCodeOfTuple<
-                 traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
-    }
-  }
-
-  /// @returns the bitwise-or'd hashcodes of all the template parameter types.
-  /// @see HashCodeOf
-  template <typename... TYPES>
-  static constexpr HashCode CombinedHashCodeOf() {
-    return CombinedHashCodeOfTuple<std::tuple<TYPES...>>();
-  }
-
-  /// @returns true if this TypeInfo is of, or derives from any of the types in
-  /// `TUPLE`.
-  template <typename TUPLE>
-  inline bool IsAnyOfTuple() const {
-    constexpr auto kCount = std::tuple_size_v<TUPLE>;
-    if constexpr (kCount == 0) {
-      return false;
-    } else if constexpr (kCount == 1) {
-      return Is(&Of<std::tuple_element_t<0, TUPLE>>());
-    } else if constexpr (kCount == 2) {
-      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
-             Is(&Of<std::tuple_element_t<1, TUPLE>>());
-    } else if constexpr (kCount == 3) {
-      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
-             Is(&Of<std::tuple_element_t<1, TUPLE>>()) ||
-             Is(&Of<std::tuple_element_t<2, TUPLE>>());
-    } else {
-      // Optimization: Compare the object's hashcode to the bitwise-or of all
-      // the tested type's hashcodes. If there's no intersection of bits in
-      // the two masks, then we can guarantee that the type is not in `TO`.
-      if (full_hashcode & TypeInfo::CombinedHashCodeOfTuple<TUPLE>()) {
-        // Possibly one of the types in `TUPLE`.
-        // Split the search in two, and scan each block.
-        static constexpr auto kMid = kCount / 2;
-        return IsAnyOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() ||
-               IsAnyOfTuple<traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
-      }
-      return false;
-    }
-  }
-
-  /// @returns true if this TypeInfo is of, or derives from any of the types in
-  /// `TYPES`.
-  template <typename... TYPES>
-  inline bool IsAnyOf() const {
-    return IsAnyOfTuple<std::tuple<TYPES...>>();
-  }
-};
-
-namespace detail {
-
-/// TypeInfoOf contains a single TypeInfo field for the type T.
-/// TINT_INSTANTIATE_TYPEINFO() must be defined in a .cpp file for each type
-/// `T`.
-template <typename T>
-struct TypeInfoOf {
-  /// The unique TypeInfo for the type T.
-  static const TypeInfo info;
-};
-
-/// A placeholder structure used for template parameters that need a default
-/// type, but can always be automatically inferred.
-struct Infer;
-
-}  // namespace detail
-
-/// @returns true if `obj` is a valid pointer, and is of, or derives from the
-/// class `TO`
-/// @param obj the object to test from
-/// @see CastFlags
-template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
-inline bool Is(FROM* obj) {
-  if (obj == nullptr) {
-    return false;
-  }
-  return TypeInfo::Is<TO, FROM, FLAGS>(&obj->TypeInfo());
-}
-
-/// @returns true if `obj` is a valid pointer, and is of, or derives from the
-/// type `TYPE`, and pred(const TYPE*) returns true
-/// @param obj the object to test from
-/// @param pred predicate function with signature `bool(const TYPE*)` called iff
-/// object is of, or derives from the class `TYPE`.
-/// @see CastFlags
-template <typename TYPE,
-          int FLAGS = 0,
-          typename OBJ = detail::Infer,
-          typename Pred = detail::Infer>
-inline bool Is(OBJ* obj, Pred&& pred) {
-  return Is<TYPE, FLAGS, OBJ>(obj) &&
-         pred(static_cast<std::add_const_t<TYPE>*>(obj));
-}
-
-/// @returns true if `obj` is a valid pointer, and is of, or derives from any of
-/// the types in `TYPES`.OBJ
-/// @param obj the object to query.
-template <typename... TYPES, typename OBJ>
-inline bool IsAnyOf(OBJ* obj) {
-  if (!obj) {
-    return false;
-  }
-  return obj->TypeInfo().template IsAnyOf<TYPES...>();
-}
-
-/// @returns obj dynamically cast to the type `TO` or `nullptr` if
-/// this object does not derive from `TO`.
-/// @param obj the object to cast from
-/// @see CastFlags
-template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
-inline TO* As(FROM* obj) {
-  auto* as_castable = static_cast<CastableBase*>(obj);
-  return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr;
-}
-
-/// @returns obj dynamically cast to the type `TO` or `nullptr` if
-/// this object does not derive from `TO`.
-/// @param obj the object to cast from
-/// @see CastFlags
-template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
-inline const TO* As(const FROM* obj) {
-  auto* as_castable = static_cast<const CastableBase*>(obj);
-  return Is<TO, FLAGS>(obj) ? static_cast<const TO*>(as_castable) : nullptr;
-}
-
-/// CastableBase is the base class for all Castable objects.
-/// It is not encouraged to directly derive from CastableBase without using the
-/// Castable helper template.
-/// @see Castable
-class CastableBase {
- public:
-  /// Copy constructor
-  CastableBase(const CastableBase&) = default;
-
-  /// Destructor
-  virtual ~CastableBase() = default;
-
-  /// Copy assignment
-  /// @param other the CastableBase to copy
-  /// @returns the new CastableBase
-  CastableBase& operator=(const CastableBase& other) = default;
-
-  /// @returns the TypeInfo of the object
-  virtual const tint::TypeInfo& TypeInfo() const = 0;
-
-  /// @returns true if this object is of, or derives from the class `TO`
-  template <typename TO>
-  inline bool Is() const {
-    return tint::Is<TO>(this);
-  }
-
-  /// @returns true if this object is of, or derives from the class `TO` and
-  /// pred(const TO*) returns true
-  /// @param pred predicate function with signature `bool(const TO*)` called iff
-  /// object is of, or derives from the class `TO`.
-  template <typename TO, int FLAGS = 0, typename Pred = detail::Infer>
-  inline bool Is(Pred&& pred) const {
-    return tint::Is<TO, FLAGS>(this, std::forward<Pred>(pred));
-  }
-
-  /// @returns true if this object is of, or derives from any of the `TO`
-  /// classes.
-  template <typename... TO>
-  inline bool IsAnyOf() const {
-    return tint::IsAnyOf<TO...>(this);
-  }
-
-  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
-  /// this object does not derive from `TO`.
-  /// @see CastFlags
-  template <typename TO, int FLAGS = 0>
-  inline TO* As() {
-    return tint::As<TO, FLAGS>(this);
-  }
-
-  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
-  /// this object does not derive from `TO`.
-  /// @see CastFlags
-  template <typename TO, int FLAGS = 0>
-  inline const TO* As() const {
-    return tint::As<const TO, FLAGS>(this);
-  }
-
- protected:
-  CastableBase() = default;
-};
-
-/// Castable is a helper to derive `CLASS` from `BASE`, automatically
-/// implementing the Is() and As() methods, along with a #Base type alias.
-///
-/// Example usage:
-///
-/// ```
-/// class Animal : public Castable<Animal> {};
-///
-/// class Sheep : public Castable<Sheep, Animal> {};
-///
-/// Sheep* cast_to_sheep(Animal* animal) {
-///    // You can query whether a Castable is of the given type with Is<T>():
-///    printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no");
-///
-///    // You can always just try the cast with As<T>().
-///    // If the object is not of the correct type, As<T>() will return nullptr:
-///    return animal->As<Sheep>();
-/// }
-/// ```
-template <typename CLASS, typename BASE = CastableBase>
-class Castable : public BASE {
- public:
-  // Inherit the `BASE` class constructors.
-  using BASE::BASE;
-
-  /// A type alias for `CLASS` to easily access the `BASE` class members.
-  /// Base actually aliases to the Castable instead of `BASE` so that you can
-  /// use Base in the `CLASS` constructor.
-  using Base = Castable;
-
-  /// A type alias for `BASE`.
-  using TrueBase = BASE;
-
-  /// @returns the TypeInfo of the object
-  const tint::TypeInfo& TypeInfo() const override {
-    return TypeInfo::Of<CLASS>();
-  }
-
-  /// @returns true if this object is of, or derives from the class `TO`
-  /// @see CastFlags
-  template <typename TO, int FLAGS = 0>
-  inline bool Is() const {
-    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this));
-  }
-
-  /// @returns true if this object is of, or derives from the class `TO` and
-  /// pred(const TO*) returns true
-  /// @param pred predicate function with signature `bool(const TO*)` called iff
-  /// object is of, or derives from the class `TO`.
-  template <int FLAGS = 0, typename Pred = detail::Infer>
-  inline bool Is(Pred&& pred) const {
-    using TO =
-        typename std::remove_pointer<traits::ParameterType<Pred, 0>>::type;
-    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
-                               std::forward<Pred>(pred));
-  }
-
-  /// @returns true if this object is of, or derives from any of the `TO`
-  /// classes.
-  template <typename... TO>
-  inline bool IsAnyOf() const {
-    return tint::IsAnyOf<TO...>(static_cast<const CLASS*>(this));
-  }
-
-  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
-  /// this object does not derive from `TO`.
-  /// @see CastFlags
-  template <typename TO, int FLAGS = 0>
-  inline TO* As() {
-    return tint::As<TO, FLAGS>(this);
-  }
-
-  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
-  /// this object does not derive from `TO`.
-  /// @see CastFlags
-  template <typename TO, int FLAGS = 0>
-  inline const TO* As() const {
-    return tint::As<const TO, FLAGS>(this);
-  }
-};
-
-namespace detail {
-/// <code>typename CastableCommonBaseImpl<TYPES>::type</code> resolves to the
-/// common base class for all of TYPES.
-template <typename... TYPES>
-struct CastableCommonBaseImpl {};
-
-/// Alias to typename CastableCommonBaseImpl<TYPES>::type
-template <typename... TYPES>
-using CastableCommonBase =
-    typename detail::CastableCommonBaseImpl<TYPES...>::type;
-
-/// CastableCommonBaseImpl template specialization for a single type
-template <typename T>
-struct CastableCommonBaseImpl<T> {
-  /// Common base class of a single type is itself
-  using type = T;
-};
-
-/// CastableCommonBaseImpl A <-> CastableBase specialization
-template <typename A>
-struct CastableCommonBaseImpl<A, CastableBase> {
-  /// Common base class for A and CastableBase is CastableBase
-  using type = CastableBase;
-};
-
-/// CastableCommonBaseImpl T <-> Ignore specialization
-template <typename T>
-struct CastableCommonBaseImpl<T, Ignore> {
-  /// Resolves to T as the other type is ignored
-  using type = T;
-};
-
-/// CastableCommonBaseImpl Ignore <-> T specialization
-template <typename T>
-struct CastableCommonBaseImpl<Ignore, T> {
-  /// Resolves to T as the other type is ignored
-  using type = T;
-};
-
-/// CastableCommonBaseImpl A <-> B specialization
-template <typename A, typename B>
-struct CastableCommonBaseImpl<A, B> {
-  /// The common base class for A, B and OTHERS
-  using type = std::conditional_t<traits::IsTypeOrDerived<A, B>,
-                                  B,  // A derives from B
-                                  CastableCommonBase<A, typename B::TrueBase>>;
-};
-
-/// CastableCommonBaseImpl 3+ types specialization
-template <typename A, typename B, typename... OTHERS>
-struct CastableCommonBaseImpl<A, B, OTHERS...> {
-  /// The common base class for A, B and OTHERS
-  using type = CastableCommonBase<CastableCommonBase<A, B>, OTHERS...>;
-};
-
-}  // namespace detail
-
-/// Resolves to the common most derived type that each of the types in `TYPES`
-/// derives from.
-template <typename... TYPES>
-using CastableCommonBase = detail::CastableCommonBase<TYPES...>;
-
-/// Default can be used as the default case for a Switch(), when all previous
-/// cases failed to match.
-///
-/// Example:
-/// ```
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ },
-///     [&](Default) { /* If not TypeA or TypeB */ });
-/// ```
-struct Default {};
-
-namespace detail {
-
-/// Evaluates to the Switch case type being matched by the switch case function
-/// `FN`.
-/// @note does not handle the Default case
-/// @see Switch().
-template <typename FN>
-using SwitchCaseType = std::remove_pointer_t<
-    traits::ParameterType<std::remove_reference_t<FN>, 0>>;
-
-/// Evaluates to true if the function `FN` has the signature of a Default case
-/// in a Switch().
-/// @see Switch().
-template <typename FN>
-inline constexpr bool IsDefaultCase =
-    std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>,
-                   Default>;
-
-/// Searches the list of Switch cases for a Default case, returning the index of
-/// the Default case. If the a Default case is not found in the tuple, then -1
-/// is returned.
-template <typename TUPLE, std::size_t START_IDX = 0>
-constexpr int IndexOfDefaultCase() {
-  if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
-    return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
-               ? static_cast<int>(START_IDX)
-               : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
-  } else {
-    return -1;
-  }
-}
-
-/// The implementation of Switch() for non-Default cases.
-/// Switch splits the cases into two a low and high block of cases, and quickly
-/// rules out blocks that cannot match by comparing the TypeInfo::HashCode of
-/// the object and the cases in the block. If a block of cases may match the
-/// given object's type, then that block is split into two, and the process
-/// recurses. When NonDefaultCases() is called with a single case, then As<>
-/// will be used to dynamically cast to the case type and if the cast succeeds,
-/// then the case handler is called.
-/// @returns true if a case handler was found, otherwise false.
-template <typename T, typename RETURN_TYPE, typename... CASES>
-inline bool NonDefaultCases(T* object,
-                            const TypeInfo* type,
-                            RETURN_TYPE* result,
-                            std::tuple<CASES...>&& cases) {
-  using Cases = std::tuple<CASES...>;
-
-  (void)result;  // Not always used, avoid warning.
-
-  static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
-  static constexpr size_t kNumCases = sizeof...(CASES);
-
-  if constexpr (kNumCases == 0) {
-    // No cases. Nothing to do.
-    return false;
-  } else if constexpr (kNumCases == 1) {  // NOLINT: cpplint doesn't understand
-                                          // `else if constexpr`
-    // Single case.
-    using CaseFunc = std::tuple_element_t<0, Cases>;
-    static_assert(!IsDefaultCase<CaseFunc>,
-                  "NonDefaultCases called with a Default case");
-    // Attempt to dynamically cast the object to the handler type. If that
-    // succeeds, call the case handler with the cast object.
-    using CaseType = SwitchCaseType<CaseFunc>;
-    if (type->Is(&TypeInfo::Of<CaseType>())) {
-      auto* ptr = static_cast<CaseType*>(object);
-      if constexpr (kHasReturnType) {
-        *result = std::get<0>(cases)(ptr);
-      } else {
-        std::get<0>(cases)(ptr);
-      }
-      return true;
-    }
-    return false;
-  } else {
-    // Multiple cases.
-    // Check the hashcode bits to see if there's any possibility of a case
-    // matching in these cases. If there isn't, we can skip all these cases.
-    if (type->full_hashcode &
-        TypeInfo::CombinedHashCodeOf<SwitchCaseType<CASES>...>()) {
-      // There's a possibility. We need to scan further.
-      // Split the cases into two, and recurse.
-      constexpr size_t kMid = kNumCases / 2;
-      return NonDefaultCases(object, type, result,
-                             traits::Slice<0, kMid>(cases)) ||
-             NonDefaultCases(object, type, result,
-                             traits::Slice<kMid, kNumCases - kMid>(cases));
-    } else {
-      return false;
-    }
-  }
-}
-
-/// The implementation of Switch() for all cases.
-/// @see NonDefaultCases
-template <typename T, typename RETURN_TYPE, typename... CASES>
-inline void SwitchCases(T* object,
-                        RETURN_TYPE* result,
-                        std::tuple<CASES...>&& cases) {
-  using Cases = std::tuple<CASES...>;
-  static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<Cases>();
-  static_assert(kDefaultIndex == -1 || std::tuple_size_v<Cases> - 1,
-                "Default case must be last in Switch()");
-  static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
-  static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
-
-  if (object) {
-    auto* type = &object->TypeInfo();
-    if constexpr (kHasDefaultCase) {
-      // Evaluate non-default cases.
-      if (!detail::NonDefaultCases<T>(object, type, result,
-                                      traits::Slice<0, kDefaultIndex>(cases))) {
-        // Nothing matched. Evaluate default case.
-        if constexpr (kHasReturnType) {
-          *result = std::get<kDefaultIndex>(cases)({});
-        } else {
-          std::get<kDefaultIndex>(cases)({});
-        }
-      }
-    } else {
-      detail::NonDefaultCases<T>(object, type, result, std::move(cases));
-    }
-  } else {
-    // Object is nullptr, so no cases can match
-    if constexpr (kHasDefaultCase) {
-      // Evaluate default case.
-      if constexpr (kHasReturnType) {
-        *result = std::get<kDefaultIndex>(cases)({});
-      } else {
-        std::get<kDefaultIndex>(cases)({});
-      }
-    }
-  }
-}
-
-}  // namespace detail
-
-/// Switch is used to dispatch one of the provided callback case handler
-/// functions based on the type of `object` and the parameter type of the case
-/// handlers. Switch will sequentially check the type of `object` against each
-/// of the switch case handler functions, and will invoke the first case handler
-/// function which has a parameter type that matches the object type. When a
-/// case handler is matched, it will be called with the single argument of
-/// `object` cast to the case handler's parameter type. Switch will invoke at
-/// most one case handler. Each of the case functions must have the signature
-/// `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
-/// is the return type, consistent across all case handlers.
-///
-/// An optional default case function with the signature `R(Default)` can be
-/// used as the last case. This default case will be called if all previous
-/// cases failed to match.
-///
-/// If `object` is nullptr and a default case is provided, then the default case
-/// will be called. If `object` is nullptr and no default case is provided, then
-/// no cases will be called.
-///
-/// Example:
-/// ```
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ });
-///
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ },
-///     [&](Default) { /* Called if object is not TypeA or TypeB */ });
-/// ```
-///
-/// @param object the object who's type is used to
-/// @param cases the switch cases
-/// @return the value returned by the called case. If no cases matched, then the
-/// zero value for the consistent case type.
-template <typename T, typename... CASES>
-inline auto Switch(T* object, CASES&&... cases) {
-  using Cases = std::tuple<CASES...>;
-  using ReturnType = traits::ReturnType<std::tuple_element_t<0, Cases>>;
-  static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
-
-  if constexpr (kHasReturnType) {
-    ReturnType res = {};
-    detail::SwitchCases(object, &res,
-                        std::forward_as_tuple(std::forward<CASES>(cases)...));
-    return res;
-  } else {
-    detail::SwitchCases<T, void>(
-        object, nullptr, std::forward_as_tuple(std::forward<CASES>(cases)...));
-  }
-}
-
-}  // namespace tint
-
-TINT_CASTABLE_POP_DISABLE_WARNINGS();
-
-#endif  // SRC_CASTABLE_H_
diff --git a/src/castable_test.cc b/src/castable_test.cc
deleted file mode 100644
index 4975bef..0000000
--- a/src/castable_test.cc
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2020 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/castable.h"
-
-#include <memory>
-#include <string>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-struct Animal : public tint::Castable<Animal> {};
-struct Amphibian : public tint::Castable<Amphibian, Animal> {};
-struct Mammal : public tint::Castable<Mammal, Animal> {};
-struct Reptile : public tint::Castable<Reptile, Animal> {};
-struct Frog : public tint::Castable<Frog, Amphibian> {};
-struct Bear : public tint::Castable<Bear, Mammal> {};
-struct Lizard : public tint::Castable<Lizard, Reptile> {};
-struct Gecko : public tint::Castable<Gecko, Lizard> {};
-struct Iguana : public tint::Castable<Iguana, Lizard> {};
-
-TEST(CastableBase, Is) {
-  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
-  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
-  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
-
-  ASSERT_TRUE(frog->Is<Animal>());
-  ASSERT_TRUE(bear->Is<Animal>());
-  ASSERT_TRUE(gecko->Is<Animal>());
-
-  ASSERT_TRUE(frog->Is<Amphibian>());
-  ASSERT_FALSE(bear->Is<Amphibian>());
-  ASSERT_FALSE(gecko->Is<Amphibian>());
-
-  ASSERT_FALSE(frog->Is<Mammal>());
-  ASSERT_TRUE(bear->Is<Mammal>());
-  ASSERT_FALSE(gecko->Is<Mammal>());
-
-  ASSERT_FALSE(frog->Is<Reptile>());
-  ASSERT_FALSE(bear->Is<Reptile>());
-  ASSERT_TRUE(gecko->Is<Reptile>());
-}
-
-TEST(CastableBase, Is_kDontErrorOnImpossibleCast) {
-  // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without
-  // going via CastableBase.
-  auto frog = std::make_unique<Frog>();
-  auto bear = std::make_unique<Bear>();
-  auto gecko = std::make_unique<Gecko>();
-
-  ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>()));
-  ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>()));
-  ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>()));
-
-  ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>()));
-  ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>()));
-  ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>()));
-
-  ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>()));
-  ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>()));
-  ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>()));
-
-  ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>()));
-  ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>()));
-  ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>()));
-}
-
-TEST(CastableBase, IsWithPredicate) {
-  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
-
-  frog->Is<Animal>([&frog](const Animal* a) {
-    EXPECT_EQ(a, frog.get());
-    return true;
-  });
-
-  ASSERT_TRUE((frog->Is<Animal>([](const Animal*) { return true; })));
-  ASSERT_FALSE((frog->Is<Animal>([](const Animal*) { return false; })));
-
-  // Predicate not called if cast is invalid
-  auto expect_not_called = [] { FAIL() << "Should not be called"; };
-  ASSERT_FALSE((frog->Is<Bear>([&](const Animal*) {
-    expect_not_called();
-    return true;
-  })));
-}
-
-TEST(CastableBase, IsAnyOf) {
-  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
-  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
-  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
-
-  ASSERT_TRUE((frog->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
-  ASSERT_TRUE((frog->IsAnyOf<Mammal, Amphibian>()));
-  ASSERT_TRUE((frog->IsAnyOf<Amphibian, Reptile>()));
-  ASSERT_FALSE((frog->IsAnyOf<Mammal, Reptile>()));
-
-  ASSERT_TRUE((bear->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
-  ASSERT_TRUE((bear->IsAnyOf<Mammal, Amphibian>()));
-  ASSERT_TRUE((bear->IsAnyOf<Mammal, Reptile>()));
-  ASSERT_FALSE((bear->IsAnyOf<Amphibian, Reptile>()));
-
-  ASSERT_TRUE((gecko->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
-  ASSERT_TRUE((gecko->IsAnyOf<Mammal, Reptile>()));
-  ASSERT_TRUE((gecko->IsAnyOf<Amphibian, Reptile>()));
-  ASSERT_FALSE((gecko->IsAnyOf<Mammal, Amphibian>()));
-}
-
-TEST(CastableBase, As) {
-  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
-  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
-  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
-
-  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
-  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
-  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
-
-  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
-  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
-  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
-
-  ASSERT_EQ(frog->As<Mammal>(), nullptr);
-  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
-  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
-
-  ASSERT_EQ(frog->As<Reptile>(), nullptr);
-  ASSERT_EQ(bear->As<Reptile>(), nullptr);
-  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
-}
-
-TEST(CastableBase, As_kDontErrorOnImpossibleCast) {
-  // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without
-  // going via CastableBase.
-  auto frog = std::make_unique<Frog>();
-  auto bear = std::make_unique<Bear>();
-  auto gecko = std::make_unique<Gecko>();
-
-  ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()),
-            static_cast<Animal*>(frog.get()));
-  ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()),
-            static_cast<Animal*>(bear.get()));
-  ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()),
-            static_cast<Animal*>(gecko.get()));
-
-  ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()),
-            static_cast<Amphibian*>(frog.get()));
-  ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
-  ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
-
-  ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
-  ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()),
-            static_cast<Mammal*>(bear.get()));
-  ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
-
-  ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
-  ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
-  ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()),
-            static_cast<Reptile*>(gecko.get()));
-}
-
-TEST(Castable, Is) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-
-  ASSERT_TRUE(frog->Is<Animal>());
-  ASSERT_TRUE(bear->Is<Animal>());
-  ASSERT_TRUE(gecko->Is<Animal>());
-
-  ASSERT_TRUE(frog->Is<Amphibian>());
-  ASSERT_FALSE(bear->Is<Amphibian>());
-  ASSERT_FALSE(gecko->Is<Amphibian>());
-
-  ASSERT_FALSE(frog->Is<Mammal>());
-  ASSERT_TRUE(bear->Is<Mammal>());
-  ASSERT_FALSE(gecko->Is<Mammal>());
-
-  ASSERT_FALSE(frog->Is<Reptile>());
-  ASSERT_FALSE(bear->Is<Reptile>());
-  ASSERT_TRUE(gecko->Is<Reptile>());
-}
-
-TEST(Castable, IsWithPredicate) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-
-  frog->Is([&frog](const Animal* a) {
-    EXPECT_EQ(a, frog.get());
-    return true;
-  });
-
-  ASSERT_TRUE((frog->Is([](const Animal*) { return true; })));
-  ASSERT_FALSE((frog->Is([](const Animal*) { return false; })));
-
-  // Predicate not called if cast is invalid
-  auto expect_not_called = [] { FAIL() << "Should not be called"; };
-  ASSERT_FALSE((frog->Is([&](const Bear*) {
-    expect_not_called();
-    return true;
-  })));
-}
-
-TEST(Castable, As) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-
-  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
-  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
-  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
-
-  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
-  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
-  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
-
-  ASSERT_EQ(frog->As<Mammal>(), nullptr);
-  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
-  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
-
-  ASSERT_EQ(frog->As<Reptile>(), nullptr);
-  ASSERT_EQ(bear->As<Reptile>(), nullptr);
-  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
-}
-
-TEST(Castable, SwitchNoDefault) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-  {
-    bool frog_matched_amphibian = false;
-    Switch(
-        frog.get(),  //
-        [&](Reptile*) { FAIL() << "frog is not reptile"; },
-        [&](Mammal*) { FAIL() << "frog is not mammal"; },
-        [&](Amphibian* amphibian) {
-          EXPECT_EQ(amphibian, frog.get());
-          frog_matched_amphibian = true;
-        });
-    EXPECT_TRUE(frog_matched_amphibian);
-  }
-  {
-    bool bear_matched_mammal = false;
-    Switch(
-        bear.get(),  //
-        [&](Reptile*) { FAIL() << "bear is not reptile"; },
-        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-        [&](Mammal* mammal) {
-          EXPECT_EQ(mammal, bear.get());
-          bear_matched_mammal = true;
-        });
-    EXPECT_TRUE(bear_matched_mammal);
-  }
-  {
-    bool gecko_matched_reptile = false;
-    Switch(
-        gecko.get(),  //
-        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-        [&](Reptile* reptile) {
-          EXPECT_EQ(reptile, gecko.get());
-          gecko_matched_reptile = true;
-        });
-    EXPECT_TRUE(gecko_matched_reptile);
-  }
-}
-
-TEST(Castable, SwitchWithUnusedDefault) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-  {
-    bool frog_matched_amphibian = false;
-    Switch(
-        frog.get(),  //
-        [&](Reptile*) { FAIL() << "frog is not reptile"; },
-        [&](Mammal*) { FAIL() << "frog is not mammal"; },
-        [&](Amphibian* amphibian) {
-          EXPECT_EQ(amphibian, frog.get());
-          frog_matched_amphibian = true;
-        },
-        [&](Default) { FAIL() << "default should not have been selected"; });
-    EXPECT_TRUE(frog_matched_amphibian);
-  }
-  {
-    bool bear_matched_mammal = false;
-    Switch(
-        bear.get(),  //
-        [&](Reptile*) { FAIL() << "bear is not reptile"; },
-        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-        [&](Mammal* mammal) {
-          EXPECT_EQ(mammal, bear.get());
-          bear_matched_mammal = true;
-        },
-        [&](Default) { FAIL() << "default should not have been selected"; });
-    EXPECT_TRUE(bear_matched_mammal);
-  }
-  {
-    bool gecko_matched_reptile = false;
-    Switch(
-        gecko.get(),  //
-        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-        [&](Reptile* reptile) {
-          EXPECT_EQ(reptile, gecko.get());
-          gecko_matched_reptile = true;
-        },
-        [&](Default) { FAIL() << "default should not have been selected"; });
-    EXPECT_TRUE(gecko_matched_reptile);
-  }
-}
-
-TEST(Castable, SwitchDefault) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-  {
-    bool frog_matched_default = false;
-    Switch(
-        frog.get(),  //
-        [&](Reptile*) { FAIL() << "frog is not reptile"; },
-        [&](Mammal*) { FAIL() << "frog is not mammal"; },
-        [&](Default) { frog_matched_default = true; });
-    EXPECT_TRUE(frog_matched_default);
-  }
-  {
-    bool bear_matched_default = false;
-    Switch(
-        bear.get(),  //
-        [&](Reptile*) { FAIL() << "bear is not reptile"; },
-        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-        [&](Default) { bear_matched_default = true; });
-    EXPECT_TRUE(bear_matched_default);
-  }
-  {
-    bool gecko_matched_default = false;
-    Switch(
-        gecko.get(),  //
-        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-        [&](Default) { gecko_matched_default = true; });
-    EXPECT_TRUE(gecko_matched_default);
-  }
-}
-
-TEST(Castable, SwitchMatchFirst) {
-  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-  {
-    bool frog_matched_animal = false;
-    Switch(
-        frog.get(),
-        [&](Animal* animal) {
-          EXPECT_EQ(animal, frog.get());
-          frog_matched_animal = true;
-        },
-        [&](Amphibian*) { FAIL() << "animal should have been matched first"; });
-    EXPECT_TRUE(frog_matched_animal);
-  }
-  {
-    bool frog_matched_amphibian = false;
-    Switch(
-        frog.get(),
-        [&](Amphibian* amphibain) {
-          EXPECT_EQ(amphibain, frog.get());
-          frog_matched_amphibian = true;
-        },
-        [&](Animal*) { FAIL() << "amphibian should have been matched first"; });
-    EXPECT_TRUE(frog_matched_amphibian);
-  }
-}
-
-TEST(Castable, SwitchNull) {
-  Animal* null = nullptr;
-  Switch(
-      null,  //
-      [&](Amphibian*) { FAIL() << "should not be called"; },
-      [&](Animal*) { FAIL() << "should not be called"; });
-}
-
-TEST(Castable, SwitchNullNoDefault) {
-  Animal* null = nullptr;
-  bool default_called = false;
-  Switch(
-      null,  //
-      [&](Amphibian*) { FAIL() << "should not be called"; },
-      [&](Animal*) { FAIL() << "should not be called"; },
-      [&](Default) { default_called = true; });
-  EXPECT_TRUE(default_called);
-}
-
-// IsCastable static tests
-static_assert(IsCastable<CastableBase>);
-static_assert(IsCastable<Animal>);
-static_assert(IsCastable<Ignore, Frog, Bear>);
-static_assert(IsCastable<Mammal, Ignore, Amphibian, Gecko>);
-static_assert(!IsCastable<Mammal, int, Amphibian, Ignore, Gecko>);
-static_assert(!IsCastable<bool>);
-static_assert(!IsCastable<int, float>);
-static_assert(!IsCastable<Ignore>);
-
-// CastableCommonBase static tests
-static_assert(std::is_same_v<Animal, CastableCommonBase<Animal>>);
-static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian>>);
-static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal>>);
-static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile>>);
-static_assert(std::is_same_v<Frog, CastableCommonBase<Frog>>);
-static_assert(std::is_same_v<Bear, CastableCommonBase<Bear>>);
-static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard>>);
-static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko>>);
-static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana>>);
-
-static_assert(std::is_same_v<Animal, CastableCommonBase<Animal, Animal>>);
-static_assert(
-    std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Amphibian>>);
-static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal, Mammal>>);
-static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile, Reptile>>);
-static_assert(std::is_same_v<Frog, CastableCommonBase<Frog, Frog>>);
-static_assert(std::is_same_v<Bear, CastableCommonBase<Bear, Bear>>);
-static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Lizard>>);
-static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko, Gecko>>);
-static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana, Iguana>>);
-
-static_assert(
-    std::is_same_v<CastableBase, CastableCommonBase<CastableBase, Animal>>);
-static_assert(
-    std::is_same_v<CastableBase, CastableCommonBase<Animal, CastableBase>>);
-static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Frog>>);
-static_assert(std::is_same_v<Amphibian, CastableCommonBase<Frog, Amphibian>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Reptile, Frog>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Reptile>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Bear>>);
-static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana>>);
-
-static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog, Iguana>>);
-static_assert(
-    std::is_same_v<Lizard, CastableCommonBase<Lizard, Gecko, Iguana>>);
-static_assert(
-    std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana, Lizard>>);
-static_assert(
-    std::is_same_v<Lizard, CastableCommonBase<Gecko, Lizard, Iguana>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Gecko, Iguana>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Iguana, Frog>>);
-static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Frog, Iguana>>);
-
-static_assert(
-    std::is_same_v<CastableBase,
-                   CastableCommonBase<Bear, Frog, Iguana, CastableBase>>);
-
-}  // namespace
-
-TINT_INSTANTIATE_TYPEINFO(Animal);
-TINT_INSTANTIATE_TYPEINFO(Amphibian);
-TINT_INSTANTIATE_TYPEINFO(Mammal);
-TINT_INSTANTIATE_TYPEINFO(Reptile);
-TINT_INSTANTIATE_TYPEINFO(Frog);
-TINT_INSTANTIATE_TYPEINFO(Bear);
-TINT_INSTANTIATE_TYPEINFO(Lizard);
-TINT_INSTANTIATE_TYPEINFO(Gecko);
-
-}  // namespace tint
diff --git a/src/clone_context.cc b/src/clone_context.cc
deleted file mode 100644
index 71d8851..0000000
--- a/src/clone_context.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2020 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/clone_context.h"
-
-#include <string>
-
-#include "src/program_builder.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::Cloneable);
-
-namespace tint {
-
-CloneContext::ListTransforms::ListTransforms() = default;
-CloneContext::ListTransforms::~ListTransforms() = default;
-
-CloneContext::CloneContext(ProgramBuilder* to,
-                           Program const* from,
-                           bool auto_clone_symbols)
-    : dst(to), src(from) {
-  if (auto_clone_symbols) {
-    // Almost all transforms will want to clone all symbols before doing any
-    // work, to avoid any newly created symbols clashing with existing symbols
-    // in the source program and causing them to be renamed.
-    from->Symbols().Foreach([&](Symbol s, const std::string&) { Clone(s); });
-  }
-}
-
-CloneContext::CloneContext(ProgramBuilder* builder)
-    : CloneContext(builder, nullptr, false) {}
-
-CloneContext::~CloneContext() = default;
-
-Symbol CloneContext::Clone(Symbol s) {
-  if (!src) {
-    return s;  // In-place clone
-  }
-  return utils::GetOrCreate(cloned_symbols_, s, [&]() -> Symbol {
-    if (symbol_transform_) {
-      return symbol_transform_(s);
-    }
-    return dst->Symbols().New(src->Symbols().NameFor(s));
-  });
-}
-
-void CloneContext::Clone() {
-  dst->AST().Copy(this, &src->AST());
-}
-
-ast::FunctionList CloneContext::Clone(const ast::FunctionList& v) {
-  ast::FunctionList out;
-  out.reserve(v.size());
-  for (const ast::Function* el : v) {
-    out.Add(Clone(el));
-  }
-  return out;
-}
-
-const tint::Cloneable* CloneContext::CloneCloneable(const Cloneable* object) {
-  // If the input is nullptr, there's nothing to clone - just return nullptr.
-  if (object == nullptr) {
-    return nullptr;
-  }
-
-  // Was Replace() called for this object?
-  auto it = replacements_.find(object);
-  if (it != replacements_.end()) {
-    return it->second();
-  }
-
-  // Attempt to clone using the registered replacer functions.
-  auto& typeinfo = object->TypeInfo();
-  for (auto& transform : transforms_) {
-    if (typeinfo.Is(transform.typeinfo)) {
-      if (auto* transformed = transform.function(object)) {
-        return transformed;
-      }
-      break;
-    }
-  }
-
-  // No transform for this type, or the transform returned nullptr.
-  // Clone with T::Clone().
-  return object->Clone(this);
-}
-
-void CloneContext::CheckedCastFailure(const Cloneable* got,
-                                      const TypeInfo& expected) {
-  TINT_ICE(Clone, Diagnostics())
-      << "Cloned object was not of the expected type\n"
-      << "got:      " << got->TypeInfo().name << "\n"
-      << "expected: " << expected.name;
-}
-
-diag::List& CloneContext::Diagnostics() const {
-  return dst->Diagnostics();
-}
-
-CloneContext::CloneableTransform::CloneableTransform() = default;
-CloneContext::CloneableTransform::CloneableTransform(
-    const CloneableTransform&) = default;
-CloneContext::CloneableTransform::~CloneableTransform() = default;
-
-}  // namespace tint
diff --git a/src/clone_context.h b/src/clone_context.h
deleted file mode 100644
index 4037e80..0000000
--- a/src/clone_context.h
+++ /dev/null
@@ -1,584 +0,0 @@
-// Copyright 2020 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_CLONE_CONTEXT_H_
-#define SRC_CLONE_CONTEXT_H_
-
-#include <algorithm>
-#include <functional>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/castable.h"
-#include "src/debug.h"
-#include "src/program_id.h"
-#include "src/symbol.h"
-#include "src/traits.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-class Program;
-class ProgramBuilder;
-namespace ast {
-class FunctionList;
-class Node;
-}  // namespace ast
-
-ProgramID ProgramIDOf(const Program*);
-ProgramID ProgramIDOf(const ProgramBuilder*);
-
-/// Cloneable is the base class for all objects that can be cloned
-class Cloneable : public Castable<Cloneable> {
- public:
-  /// Performs a deep clone of this object using the CloneContext `ctx`.
-  /// @param ctx the clone context
-  /// @return the newly cloned object
-  virtual const Cloneable* Clone(CloneContext* ctx) const = 0;
-};
-
-/// @returns an invalid ProgramID
-inline ProgramID ProgramIDOf(const Cloneable*) {
-  return ProgramID();
-}
-
-/// CloneContext holds the state used while cloning AST nodes.
-class CloneContext {
-  /// ParamTypeIsPtrOf<F, T> is true iff the first parameter of
-  /// F is a pointer of (or derives from) type T.
-  template <typename F, typename T>
-  static constexpr bool ParamTypeIsPtrOf = traits::IsTypeOrDerived<
-      typename std::remove_pointer<traits::ParameterType<F, 0>>::type,
-      T>;
-
- public:
-  /// SymbolTransform is a function that takes a symbol and returns a new
-  /// symbol.
-  using SymbolTransform = std::function<Symbol(Symbol)>;
-
-  /// Constructor for cloning objects from `from` into `to`.
-  /// @param to the target ProgramBuilder to clone into
-  /// @param from the source Program to clone from
-  /// @param auto_clone_symbols clone all symbols in `from` before returning
-  CloneContext(ProgramBuilder* to,
-               Program const* from,
-               bool auto_clone_symbols = true);
-
-  /// Constructor for cloning objects from and to the ProgramBuilder `builder`.
-  /// @param builder the ProgramBuilder
-  explicit CloneContext(ProgramBuilder* builder);
-
-  /// Destructor
-  ~CloneContext();
-
-  /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
-  /// not null. If `a` is null, then Clone() returns null.
-  ///
-  /// Clone() may use a function registered with ReplaceAll() to create a
-  /// transformed version of the object. See ReplaceAll() for more information.
-  ///
-  /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
-  /// the Node or sem::Type `a` must be owned by the Program #src.
-  ///
-  /// @param object the type deriving from Cloneable to clone
-  /// @return the cloned node
-  template <typename T>
-  const T* Clone(const T* object) {
-    if (src) {
-      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
-    }
-    if (auto* cloned = CloneCloneable(object)) {
-      auto* out = CheckedCast<T>(cloned);
-      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out);
-      return out;
-    }
-    return nullptr;
-  }
-
-  /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
-  /// not null. If `a` is null, then Clone() returns null.
-  ///
-  /// Unlike Clone(), this method does not invoke or use any transformations
-  /// registered by ReplaceAll().
-  ///
-  /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
-  /// the Node or sem::Type `a` must be owned by the Program #src.
-  ///
-  /// @param a the type deriving from Cloneable to clone
-  /// @return the cloned node
-  template <typename T>
-  const T* CloneWithoutTransform(const T* a) {
-    // If the input is nullptr, there's nothing to clone - just return nullptr.
-    if (a == nullptr) {
-      return nullptr;
-    }
-    if (src) {
-      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, a);
-    }
-    auto* c = a->Clone(this);
-    return CheckedCast<T>(c);
-  }
-
-  /// Clones the Source `s` into #dst
-  /// TODO(bclayton) - Currently this 'clone' is a shallow copy. If/when
-  /// `Source.File`s are owned by the Program this should make a copy of the
-  /// file.
-  /// @param s the `Source` to clone
-  /// @return the cloned source
-  Source Clone(const Source& s) const { return s; }
-
-  /// Clones the Symbol `s` into #dst
-  ///
-  /// The Symbol `s` must be owned by the Program #src.
-  ///
-  /// @param s the Symbol to clone
-  /// @return the cloned source
-  Symbol Clone(Symbol s);
-
-  /// Clones each of the elements of the vector `v` into the ProgramBuilder
-  /// #dst.
-  ///
-  /// All the elements of the vector `v` must be owned by the Program #src.
-  ///
-  /// @param v the vector to clone
-  /// @return the cloned vector
-  template <typename T>
-  std::vector<T> Clone(const std::vector<T>& v) {
-    std::vector<T> out;
-    out.reserve(v.size());
-    for (auto& el : v) {
-      out.emplace_back(Clone(el));
-    }
-    return out;
-  }
-
-  /// Clones each of the elements of the vector `v` using the ProgramBuilder
-  /// #dst, inserting any additional elements into the list that were registered
-  /// with calls to InsertBefore().
-  ///
-  /// All the elements of the vector `v` must be owned by the Program #src.
-  ///
-  /// @param v the vector to clone
-  /// @return the cloned vector
-  template <typename T>
-  std::vector<T*> Clone(const std::vector<T*>& v) {
-    std::vector<T*> out;
-    Clone(out, v);
-    return out;
-  }
-
-  /// Clones each of the elements of the vector `from` into the vector `to`,
-  /// inserting any additional elements into the list that were registered with
-  /// calls to InsertBefore().
-  ///
-  /// All the elements of the vector `from` must be owned by the Program #src.
-  ///
-  /// @param from the vector to clone
-  /// @param to the cloned result
-  template <typename T>
-  void Clone(std::vector<T*>& to, const std::vector<T*>& from) {
-    to.reserve(from.size());
-
-    auto list_transform_it = list_transforms_.find(&from);
-    if (list_transform_it != list_transforms_.end()) {
-      const auto& transforms = list_transform_it->second;
-      for (auto* o : transforms.insert_front_) {
-        to.emplace_back(CheckedCast<T>(o));
-      }
-      for (auto& el : from) {
-        auto insert_before_it = transforms.insert_before_.find(el);
-        if (insert_before_it != transforms.insert_before_.end()) {
-          for (auto insert : insert_before_it->second) {
-            to.emplace_back(CheckedCast<T>(insert));
-          }
-        }
-        if (transforms.remove_.count(el) == 0) {
-          to.emplace_back(Clone(el));
-        }
-        auto insert_after_it = transforms.insert_after_.find(el);
-        if (insert_after_it != transforms.insert_after_.end()) {
-          for (auto insert : insert_after_it->second) {
-            to.emplace_back(CheckedCast<T>(insert));
-          }
-        }
-      }
-      for (auto* o : transforms.insert_back_) {
-        to.emplace_back(CheckedCast<T>(o));
-      }
-    } else {
-      for (auto& el : from) {
-        to.emplace_back(Clone(el));
-
-        // Clone(el) may have inserted after
-        list_transform_it = list_transforms_.find(&from);
-        if (list_transform_it != list_transforms_.end()) {
-          const auto& transforms = list_transform_it->second;
-
-          auto insert_after_it = transforms.insert_after_.find(el);
-          if (insert_after_it != transforms.insert_after_.end()) {
-            for (auto insert : insert_after_it->second) {
-              to.emplace_back(CheckedCast<T>(insert));
-            }
-          }
-        }
-      }
-
-      // Clone(el)s may have inserted back
-      list_transform_it = list_transforms_.find(&from);
-      if (list_transform_it != list_transforms_.end()) {
-        const auto& transforms = list_transform_it->second;
-
-        for (auto* o : transforms.insert_back_) {
-          to.emplace_back(CheckedCast<T>(o));
-        }
-      }
-    }
-  }
-
-  /// Clones each of the elements of the vector `v` into the ProgramBuilder
-  /// #dst.
-  ///
-  /// All the elements of the vector `v` must be owned by the Program #src.
-  ///
-  /// @param v the vector to clone
-  /// @return the cloned vector
-  ast::FunctionList Clone(const ast::FunctionList& v);
-
-  /// ReplaceAll() registers `replacer` to be called whenever the Clone() method
-  /// is called with a Cloneable type that matches (or derives from) the type of
-  /// the single parameter of `replacer`.
-  /// The returned Cloneable of `replacer` will be used as the replacement for
-  /// all references to the object that's being cloned. This returned Cloneable
-  /// must be owned by the Program #dst.
-  ///
-  /// `replacer` must be function-like with the signature: `T* (T*)`
-  ///  where `T` is a type deriving from Cloneable.
-  ///
-  /// If `replacer` returns a nullptr then Clone() will call `T::Clone()` to
-  /// clone the object.
-  ///
-  /// Example:
-  ///
-  /// ```
-  ///   // Replace all ast::UintLiteralExpressions with the number 42
-  ///   CloneCtx ctx(&out, in);
-  ///   ctx.ReplaceAll([&] (ast::UintLiteralExpression* l) {
-  ///       return ctx->dst->create<ast::UintLiteralExpression>(
-  ///           ctx->Clone(l->source),
-  ///           ctx->Clone(l->type),
-  ///           42);
-  ///     });
-  ///   ctx.Clone();
-  /// ```
-  ///
-  /// @warning a single handler can only be registered for any given type.
-  /// Attempting to register two handlers for the same type will result in an
-  /// ICE.
-  /// @warning The replacement object must be of the correct type for all
-  /// references of the original object. A type mismatch will result in an
-  /// assertion in debug builds, and undefined behavior in release builds.
-  /// @param replacer a function or function-like object with the signature
-  ///        `T* (T*)`, where `T` derives from Cloneable
-  /// @returns this CloneContext so calls can be chained
-  template <typename F>
-  traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>, CloneContext>& ReplaceAll(
-      F&& replacer) {
-    using TPtr = traits::ParameterType<F, 0>;
-    using T = typename std::remove_pointer<TPtr>::type;
-    for (auto& transform : transforms_) {
-      if (transform.typeinfo->Is(&TypeInfo::Of<T>()) ||
-          TypeInfo::Of<T>().Is(transform.typeinfo)) {
-        TINT_ICE(Clone, Diagnostics())
-            << "ReplaceAll() called with a handler for type "
-            << TypeInfo::Of<T>().name
-            << " that is already handled by a handler for type "
-            << transform.typeinfo->name;
-        return *this;
-      }
-    }
-    CloneableTransform transform;
-    transform.typeinfo = &TypeInfo::Of<T>();
-    transform.function = [=](const Cloneable* in) {
-      return replacer(in->As<T>());
-    };
-    transforms_.emplace_back(std::move(transform));
-    return *this;
-  }
-
-  /// ReplaceAll() registers `replacer` to be called whenever the Clone() method
-  /// is called with a Symbol.
-  /// The returned symbol of `replacer` will be used as the replacement for
-  /// all references to the symbol that's being cloned. This returned Symbol
-  /// must be owned by the Program #dst.
-  /// @param replacer a function the signature `Symbol(Symbol)`.
-  /// @warning a SymbolTransform can only be registered once. Attempting to
-  /// register a SymbolTransform more than once will result in an ICE.
-  /// @returns this CloneContext so calls can be chained
-  CloneContext& ReplaceAll(const SymbolTransform& replacer) {
-    if (symbol_transform_) {
-      TINT_ICE(Clone, Diagnostics())
-          << "ReplaceAll(const SymbolTransform&) called "
-             "multiple times on the same CloneContext";
-      return *this;
-    }
-    symbol_transform_ = replacer;
-    return *this;
-  }
-
-  /// Replace replaces all occurrences of `what` in #src with the pointer `with`
-  /// in #dst when calling Clone().
-  /// [DEPRECATED]: This function cannot handle nested replacements. Use the
-  /// overload of Replace() that take a function for the `WITH` argument.
-  /// @param what a pointer to the object in #src that will be replaced with
-  /// `with`
-  /// @param with a pointer to the replacement object owned by #dst that will be
-  /// used as a replacement for `what`
-  /// @warning The replacement object must be of the correct type for all
-  /// references of the original object. A type mismatch will result in an
-  /// assertion in debug builds, and undefined behavior in release builds.
-  /// @returns this CloneContext so calls can be chained
-  template <typename WHAT,
-            typename WITH,
-            typename = traits::EnableIfIsType<WITH, Cloneable>>
-  CloneContext& Replace(const WHAT* what, const WITH* with) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, with);
-    replacements_[what] = [with]() -> const Cloneable* { return with; };
-    return *this;
-  }
-
-  /// Replace replaces all occurrences of `what` in #src with the result of the
-  /// function `with` in #dst when calling Clone(). `with` will be called each
-  /// time `what` is cloned by this context. If `what` is not cloned, then
-  /// `with` may never be called.
-  /// @param what a pointer to the object in #src that will be replaced with
-  /// `with`
-  /// @param with a function that takes no arguments and returns a pointer to
-  /// the replacement object owned by #dst. The returned pointer will be used as
-  /// a replacement for `what`.
-  /// @warning The replacement object must be of the correct type for all
-  /// references of the original object. A type mismatch will result in an
-  /// assertion in debug builds, and undefined behavior in release builds.
-  /// @returns this CloneContext so calls can be chained
-  template <typename WHAT, typename WITH, typename = std::result_of_t<WITH()>>
-  CloneContext& Replace(const WHAT* what, WITH&& with) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what);
-    replacements_[what] = with;
-    return *this;
-  }
-
-  /// Removes `object` from the cloned copy of `vector`.
-  /// @param vector the vector in #src
-  /// @param object a pointer to the object in #src that will be omitted from
-  /// the cloned vector.
-  /// @returns this CloneContext so calls can be chained
-  template <typename T, typename OBJECT>
-  CloneContext& Remove(const std::vector<T>& vector, OBJECT* object) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
-    if (std::find(vector.begin(), vector.end(), object) == vector.end()) {
-      TINT_ICE(Clone, Diagnostics())
-          << "CloneContext::Remove() vector does not contain object";
-      return *this;
-    }
-
-    list_transforms_[&vector].remove_.emplace(object);
-    return *this;
-  }
-
-  /// Inserts `object` before any other objects of `vector`, when it is cloned.
-  /// @param vector the vector in #src
-  /// @param object a pointer to the object in #dst that will be inserted at the
-  /// front of the vector
-  /// @returns this CloneContext so calls can be chained
-  template <typename T, typename OBJECT>
-  CloneContext& InsertFront(const std::vector<T>& vector, OBJECT* object) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
-    auto& transforms = list_transforms_[&vector];
-    auto& list = transforms.insert_front_;
-    list.emplace_back(object);
-    return *this;
-  }
-
-  /// Inserts `object` after any other objects of `vector`, when it is cloned.
-  /// @param vector the vector in #src
-  /// @param object a pointer to the object in #dst that will be inserted at the
-  /// end of the vector
-  /// @returns this CloneContext so calls can be chained
-  template <typename T, typename OBJECT>
-  CloneContext& InsertBack(const std::vector<T>& vector, OBJECT* object) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
-    auto& transforms = list_transforms_[&vector];
-    auto& list = transforms.insert_back_;
-    list.emplace_back(object);
-    return *this;
-  }
-
-  /// Inserts `object` before `before` whenever `vector` is cloned.
-  /// @param vector the vector in #src
-  /// @param before a pointer to the object in #src
-  /// @param object a pointer to the object in #dst that will be inserted before
-  /// any occurrence of the clone of `before`
-  /// @returns this CloneContext so calls can be chained
-  template <typename T, typename BEFORE, typename OBJECT>
-  CloneContext& InsertBefore(const std::vector<T>& vector,
-                             const BEFORE* before,
-                             const OBJECT* object) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, before);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
-    if (std::find(vector.begin(), vector.end(), before) == vector.end()) {
-      TINT_ICE(Clone, Diagnostics())
-          << "CloneContext::InsertBefore() vector does not contain before";
-      return *this;
-    }
-
-    auto& transforms = list_transforms_[&vector];
-    auto& list = transforms.insert_before_[before];
-    list.emplace_back(object);
-    return *this;
-  }
-
-  /// Inserts `object` after `after` whenever `vector` is cloned.
-  /// @param vector the vector in #src
-  /// @param after a pointer to the object in #src
-  /// @param object a pointer to the object in #dst that will be inserted after
-  /// any occurrence of the clone of `after`
-  /// @returns this CloneContext so calls can be chained
-  template <typename T, typename AFTER, typename OBJECT>
-  CloneContext& InsertAfter(const std::vector<T>& vector,
-                            const AFTER* after,
-                            const OBJECT* object) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, after);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
-    if (std::find(vector.begin(), vector.end(), after) == vector.end()) {
-      TINT_ICE(Clone, Diagnostics())
-          << "CloneContext::InsertAfter() vector does not contain after";
-      return *this;
-    }
-
-    auto& transforms = list_transforms_[&vector];
-    auto& list = transforms.insert_after_[after];
-    list.emplace_back(object);
-    return *this;
-  }
-
-  /// Clone performs the clone of the Program's AST nodes, types and symbols
-  /// from #src to #dst. Semantic nodes are not cloned, as these will be rebuilt
-  /// when the ProgramBuilder #dst builds its Program.
-  void Clone();
-
-  /// The target ProgramBuilder to clone into.
-  ProgramBuilder* const dst;
-
-  /// The source Program to clone from.
-  Program const* const src;
-
- private:
-  struct CloneableTransform {
-    /// Constructor
-    CloneableTransform();
-    /// Copy constructor
-    /// @param other the CloneableTransform to copy
-    CloneableTransform(const CloneableTransform& other);
-    /// Destructor
-    ~CloneableTransform();
-
-    // TypeInfo of the Cloneable that the transform operates on
-    const TypeInfo* typeinfo;
-    std::function<const Cloneable*(const Cloneable*)> function;
-  };
-
-  CloneContext(const CloneContext&) = delete;
-  CloneContext& operator=(const CloneContext&) = delete;
-
-  /// Cast `obj` from type `FROM` to type `TO`, returning the cast object.
-  /// Reports an internal compiler error if the cast failed.
-  template <typename TO, typename FROM>
-  const TO* CheckedCast(const FROM* obj) {
-    if (obj == nullptr) {
-      return nullptr;
-    }
-    if (const TO* cast = obj->template As<TO>()) {
-      return cast;
-    }
-    CheckedCastFailure(obj, TypeInfo::Of<TO>());
-    return nullptr;
-  }
-
-  /// Clones a Cloneable object, using any replacements or transforms that have
-  /// been configured.
-  const Cloneable* CloneCloneable(const Cloneable* object);
-
-  /// Adds an error diagnostic to Diagnostics() that the cloned object was not
-  /// of the expected type.
-  void CheckedCastFailure(const Cloneable* got, const TypeInfo& expected);
-
-  /// @returns the diagnostic list of #dst
-  diag::List& Diagnostics() const;
-
-  /// A vector of const Cloneable*
-  using CloneableList = std::vector<const Cloneable*>;
-
-  /// Transformations to be applied to a list (vector)
-  struct ListTransforms {
-    /// Constructor
-    ListTransforms();
-    /// Destructor
-    ~ListTransforms();
-
-    /// A map of object in #src to omit when cloned into #dst.
-    std::unordered_set<const Cloneable*> remove_;
-
-    /// A list of objects in #dst to insert before any others when the vector is
-    /// cloned.
-    CloneableList insert_front_;
-
-    /// A list of objects in #dst to insert befor after any others when the
-    /// vector is cloned.
-    CloneableList insert_back_;
-
-    /// A map of object in #src to the list of cloned objects in #dst.
-    /// Clone(const std::vector<T*>& v) will use this to insert the map-value
-    /// list into the target vector before cloning and inserting the map-key.
-    std::unordered_map<const Cloneable*, CloneableList> insert_before_;
-
-    /// A map of object in #src to the list of cloned objects in #dst.
-    /// Clone(const std::vector<T*>& v) will use this to insert the map-value
-    /// list into the target vector after cloning and inserting the map-key.
-    std::unordered_map<const Cloneable*, CloneableList> insert_after_;
-  };
-
-  /// A map of object in #src to functions that create their replacement in
-  /// #dst
-  std::unordered_map<const Cloneable*, std::function<const Cloneable*()>>
-      replacements_;
-
-  /// A map of symbol in #src to their cloned equivalent in #dst
-  std::unordered_map<Symbol, Symbol> cloned_symbols_;
-
-  /// Cloneable transform functions registered with ReplaceAll()
-  std::vector<CloneableTransform> transforms_;
-
-  /// Map of std::vector pointer to transforms for that list
-  std::unordered_map<const void*, ListTransforms> list_transforms_;
-
-  /// Symbol transform registered with ReplaceAll()
-  SymbolTransform symbol_transform_;
-};
-
-}  // namespace tint
-
-#endif  // SRC_CLONE_CONTEXT_H_
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
deleted file mode 100644
index a8e45ef..0000000
--- a/src/clone_context_test.cc
+++ /dev/null
@@ -1,950 +0,0 @@
-// Copyright 2020 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 <unordered_set>
-
-#include "gtest/gtest-spi.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace {
-
-struct Allocator {
-  template <typename T, typename... ARGS>
-  T* Create(ARGS&&... args) {
-    return alloc.Create<T>(this, std::forward<ARGS>(args)...);
-  }
-
- private:
-  BlockAllocator<Cloneable> alloc;
-};
-
-struct Node : public Castable<Node, Cloneable> {
-  Node(Allocator* alloc,
-       Symbol n,
-       const Node* node_a = nullptr,
-       const Node* node_b = nullptr,
-       const Node* node_c = nullptr)
-      : allocator(alloc), name(n), a(node_a), b(node_b), c(node_c) {}
-  Allocator* const allocator;
-  Symbol name;
-  const Node* a = nullptr;
-  const Node* b = nullptr;
-  const Node* c = nullptr;
-  std::vector<const Node*> vec;
-
-  Node* Clone(CloneContext* ctx) const override {
-    auto* out = allocator->Create<Node>(ctx->Clone(name));
-    out->a = ctx->Clone(a);
-    out->b = ctx->Clone(b);
-    out->c = ctx->Clone(c);
-    out->vec = ctx->Clone(vec);
-    return out;
-  }
-};
-
-struct Replaceable : public Castable<Replaceable, Node> {
-  Replaceable(Allocator* alloc,
-              Symbol n,
-              const Node* node_a = nullptr,
-              const Node* node_b = nullptr,
-              const Node* node_c = nullptr)
-      : Base(alloc, n, node_a, node_b, node_c) {}
-};
-
-struct Replacement : public Castable<Replacement, Replaceable> {
-  Replacement(Allocator* alloc, Symbol n) : Base(alloc, n) {}
-};
-
-struct NotANode : public Castable<NotANode, Cloneable> {
-  explicit NotANode(Allocator* alloc) : allocator(alloc) {}
-
-  Allocator* const allocator;
-  NotANode* Clone(CloneContext*) const override {
-    return allocator->Create<NotANode>();
-  }
-};
-
-struct ProgramNode : public Castable<ProgramNode, Cloneable> {
-  ProgramNode(Allocator* alloc, ProgramID id, ProgramID cloned_id)
-      : allocator(alloc), program_id(id), cloned_program_id(cloned_id) {}
-
-  Allocator* const allocator;
-  const ProgramID program_id;
-  const ProgramID cloned_program_id;
-
-  ProgramNode* Clone(CloneContext*) const override {
-    return allocator->Create<ProgramNode>(cloned_program_id, cloned_program_id);
-  }
-};
-
-ProgramID ProgramIDOf(const ProgramNode* node) {
-  return node->program_id;
-}
-
-using CloneContextNodeTest = ::testing::Test;
-
-TEST_F(CloneContextNodeTest, Clone) {
-  Allocator alloc;
-
-  ProgramBuilder builder;
-  Node* original_root;
-  {
-    auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b"));
-    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
-    auto* b_a = a;  // Aliased
-    auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b"));
-    auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b);
-    auto* c = b;  // Aliased
-    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
-  }
-  Program original(std::move(builder));
-
-  //                          root
-  //        ╭──────────────────┼──────────────────╮
-  //       (a)                (b)                (c)
-  //        N  <──────┐        N  <───────────────┘
-  //   ╭────┼────╮    │   ╭────┼────╮
-  //  (a)  (b)  (c)   │  (a)  (b)  (c)
-  //        N         └───┘    N
-  //
-  // N: Node
-
-  ProgramBuilder cloned;
-  auto* cloned_root = CloneContext(&cloned, &original).Clone(original_root);
-
-  EXPECT_NE(cloned_root->a, nullptr);
-  EXPECT_EQ(cloned_root->a->a, nullptr);
-  EXPECT_NE(cloned_root->a->b, nullptr);
-  EXPECT_EQ(cloned_root->a->c, nullptr);
-  EXPECT_NE(cloned_root->b, nullptr);
-  EXPECT_NE(cloned_root->b->a, nullptr);
-  EXPECT_NE(cloned_root->b->b, nullptr);
-  EXPECT_EQ(cloned_root->b->c, nullptr);
-  EXPECT_NE(cloned_root->c, nullptr);
-
-  EXPECT_NE(cloned_root->a, original_root->a);
-  EXPECT_NE(cloned_root->a->b, original_root->a->b);
-  EXPECT_NE(cloned_root->b, original_root->b);
-  EXPECT_NE(cloned_root->b->a, original_root->b->a);
-  EXPECT_NE(cloned_root->b->b, original_root->b->b);
-  EXPECT_NE(cloned_root->c, original_root->c);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("a->b"));
-  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("b->b"));
-
-  EXPECT_NE(cloned_root->b->a, cloned_root->a);  // De-aliased
-  EXPECT_NE(cloned_root->c, cloned_root->b);     // De-aliased
-
-  EXPECT_EQ(cloned_root->b->a->name, cloned_root->a->name);
-  EXPECT_EQ(cloned_root->c->name, cloned_root->b->name);
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Cloneable) {
-  Allocator alloc;
-
-  ProgramBuilder builder;
-  Node* original_root;
-  {
-    auto* a_b = alloc.Create<Replaceable>(builder.Symbols().New("a->b"));
-    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
-    auto* b_a = a;  // Aliased
-    auto* b =
-        alloc.Create<Replaceable>(builder.Symbols().New("b"), b_a, nullptr);
-    auto* c = b;  // Aliased
-    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
-  }
-  Program original(std::move(builder));
-
-  //                          root
-  //        ╭──────────────────┼──────────────────╮
-  //       (a)                (b)                (c)
-  //        N  <──────┐        R  <───────────────┘
-  //   ╭────┼────╮    │   ╭────┼────╮
-  //  (a)  (b)  (c)   │  (a)  (b)  (c)
-  //        R         └───┘
-  //
-  // N: Node
-  // R: Replaceable
-
-  ProgramBuilder cloned;
-
-  CloneContext ctx(&cloned, &original);
-  ctx.ReplaceAll([&](const Replaceable* in) {
-    auto out_name = cloned.Symbols().Register(
-        "replacement:" + original.Symbols().NameFor(in->name));
-    auto b_name = cloned.Symbols().Register(
-        "replacement-child:" + original.Symbols().NameFor(in->name));
-    auto* out = alloc.Create<Replacement>(out_name);
-    out->b = alloc.Create<Node>(b_name);
-    out->c = ctx.Clone(in->a);
-    return out;
-  });
-  auto* cloned_root = ctx.Clone(original_root);
-
-  //                         root
-  //        ╭─────────────────┼──────────────────╮
-  //       (a)               (b)                (c)
-  //        N  <──────┐       R  <───────────────┘
-  //   ╭────┼────╮    │  ╭────┼────╮
-  //  (a)  (b)  (c)   │ (a)  (b)  (c)
-  //        R         │       N    |
-  //   ╭────┼────╮    └────────────┘
-  //  (a)  (b)  (c)
-  //        N
-  //
-  // N: Node
-  // R: Replacement
-
-  EXPECT_NE(cloned_root->a, nullptr);
-  EXPECT_EQ(cloned_root->a->a, nullptr);
-  EXPECT_NE(cloned_root->a->b, nullptr);     // Replaced
-  EXPECT_EQ(cloned_root->a->b->a, nullptr);  // From replacement
-  EXPECT_NE(cloned_root->a->b->b, nullptr);  // From replacement
-  EXPECT_EQ(cloned_root->a->b->c, nullptr);  // From replacement
-  EXPECT_EQ(cloned_root->a->c, nullptr);
-  EXPECT_NE(cloned_root->b, nullptr);
-  EXPECT_EQ(cloned_root->b->a, nullptr);  // From replacement
-  EXPECT_NE(cloned_root->b->b, nullptr);  // From replacement
-  EXPECT_NE(cloned_root->b->c, nullptr);  // From replacement
-  EXPECT_NE(cloned_root->c, nullptr);
-
-  EXPECT_NE(cloned_root->a, original_root->a);
-  EXPECT_NE(cloned_root->a->b, original_root->a->b);
-  EXPECT_NE(cloned_root->b, original_root->b);
-  EXPECT_NE(cloned_root->b->a, original_root->b->a);
-  EXPECT_NE(cloned_root->c, original_root->c);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("replacement:a->b"));
-  EXPECT_EQ(cloned_root->a->b->b->name,
-            cloned.Symbols().Get("replacement-child:a->b"));
-  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement:b"));
-  EXPECT_EQ(cloned_root->b->b->name,
-            cloned.Symbols().Get("replacement-child:b"));
-
-  EXPECT_NE(cloned_root->b->c, cloned_root->a);  // De-aliased
-  EXPECT_NE(cloned_root->c, cloned_root->b);     // De-aliased
-
-  EXPECT_EQ(cloned_root->b->c->name, cloned_root->a->name);
-  EXPECT_EQ(cloned_root->c->name, cloned_root->b->name);
-
-  EXPECT_FALSE(Is<Replacement>(cloned_root->a));
-  EXPECT_TRUE(Is<Replacement>(cloned_root->a->b));
-  EXPECT_FALSE(Is<Replacement>(cloned_root->a->b->b));
-  EXPECT_TRUE(Is<Replacement>(cloned_root->b));
-  EXPECT_FALSE(Is<Replacement>(cloned_root->b->b));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Symbols) {
-  Allocator alloc;
-
-  ProgramBuilder builder;
-  Node* original_root;
-  {
-    auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b"));
-    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
-    auto* b_a = a;  // Aliased
-    auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b"));
-    auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b);
-    auto* c = b;  // Aliased
-    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
-  }
-  Program original(std::move(builder));
-
-  //                          root
-  //        ╭──────────────────┼──────────────────╮
-  //       (a)                (b)                (c)
-  //        N  <──────┐        N  <───────────────┘
-  //   ╭────┼────╮    │   ╭────┼────╮
-  //  (a)  (b)  (c)   │  (a)  (b)  (c)
-  //        N         └───┘    N
-  //
-  // N: Node
-
-  ProgramBuilder cloned;
-  auto* cloned_root = CloneContext(&cloned, &original, false)
-                          .ReplaceAll([&](Symbol sym) {
-                            auto in = original.Symbols().NameFor(sym);
-                            auto out = "transformed<" + in + ">";
-                            return cloned.Symbols().New(out);
-                          })
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("transformed<root>"));
-  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("transformed<a>"));
-  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("transformed<a->b>"));
-  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("transformed<b>"));
-  EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("transformed<b->b>"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithoutTransform) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_node = a.Create<Node>(builder.Symbols().New("root"));
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original);
-  ctx.ReplaceAll([&](const Node*) {
-    return a.Create<Replacement>(builder.Symbols().New("<unexpected-node>"));
-  });
-
-  auto* cloned_node = ctx.CloneWithoutTransform(original_node);
-  EXPECT_NE(cloned_node, original_node);
-  EXPECT_EQ(cloned_node->name, cloned.Symbols().Get("root"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplacePointer) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().New("root"));
-  original_root->a = a.Create<Node>(builder.Symbols().New("a"));
-  original_root->b = a.Create<Node>(builder.Symbols().New("b"));
-  original_root->c = a.Create<Node>(builder.Symbols().New("c"));
-  Program original(std::move(builder));
-
-  //                          root
-  //        ╭──────────────────┼──────────────────╮
-  //       (a)                (b)                (c)
-  //                        Replaced
-
-  ProgramBuilder cloned;
-  auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .Replace(original_root->b, replacement)
-                          .Clone(original_root);
-
-  EXPECT_NE(cloned_root->a, replacement);
-  EXPECT_EQ(cloned_root->b, replacement);
-  EXPECT_NE(cloned_root->c, replacement);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement"));
-  EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceFunction) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().New("root"));
-  original_root->a = a.Create<Node>(builder.Symbols().New("a"));
-  original_root->b = a.Create<Node>(builder.Symbols().New("b"));
-  original_root->c = a.Create<Node>(builder.Symbols().New("c"));
-  Program original(std::move(builder));
-
-  //                          root
-  //        ╭──────────────────┼──────────────────╮
-  //       (a)                (b)                (c)
-  //                        Replaced
-
-  ProgramBuilder cloned;
-  auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement"));
-
-  auto* cloned_root =
-      CloneContext(&cloned, &original)
-          .Replace(original_root->b, [=] { return replacement; })
-          .Clone(original_root);
-
-  EXPECT_NE(cloned_root->a, replacement);
-  EXPECT_EQ(cloned_root->b, replacement);
-  EXPECT_NE(cloned_root->c, replacement);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement"));
-  EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithRemove) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .Remove(original_root->vec, original_root->vec[1])
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 2u);
-
-  EXPECT_NE(cloned_root->vec[0], cloned_root->a);
-  EXPECT_NE(cloned_root->vec[1], cloned_root->c);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertFront) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertFront(original_root->vec, insertion)
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_NE(cloned_root->vec[0], cloned_root->a);
-  EXPECT_NE(cloned_root->vec[1], cloned_root->b);
-  EXPECT_NE(cloned_root->vec[2], cloned_root->c);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertFront_Empty) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {};
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertFront(original_root->vec, insertion)
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 1u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertBack) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertBack(original_root->vec, insertion)
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertBack_Empty) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {};
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertBack(original_root->vec, insertion)
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 1u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertFrontAndBack_Empty) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {};
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion_front =
-      a.Create<Node>(cloned.Symbols().New("insertion_front"));
-  auto* insertion_back = a.Create<Node>(cloned.Symbols().New("insertion_back"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertBack(original_root->vec, insertion_back)
-                          .InsertFront(original_root->vec, insertion_front)
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 2u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion_front"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion_back"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertBefore) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root =
-      CloneContext(&cloned, &original)
-          .InsertBefore(original_root->vec, original_root->vec[1], insertion)
-          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertAfter) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-
-  auto* cloned_root =
-      CloneContext(&cloned, &original)
-          .InsertAfter(original_root->vec, original_root->vec[1], insertion)
-          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertAfterInVectorNodeClone) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Replaceable>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original);
-  ctx.ReplaceAll([&](const Replaceable* r) {
-    auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-    ctx.InsertAfter(original_root->vec, r, insertion);
-    return nullptr;
-  });
-
-  auto* cloned_root = ctx.Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertBackInVectorNodeClone) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Replaceable>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original);
-  ctx.ReplaceAll([&](const Replaceable* /*r*/) {
-    auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
-    ctx.InsertBack(original_root->vec, insertion);
-    return nullptr;
-  });
-
-  auto* cloned_root = ctx.Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion"));
-}
-
-TEST_F(CloneContextNodeTest, CloneWithInsertBeforeAndAfterRemoved) {
-  Allocator a;
-
-  ProgramBuilder builder;
-  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-  original_root->vec = {
-      a.Create<Node>(builder.Symbols().Register("a")),
-      a.Create<Node>(builder.Symbols().Register("b")),
-      a.Create<Node>(builder.Symbols().Register("c")),
-  };
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  auto* insertion_before =
-      a.Create<Node>(cloned.Symbols().New("insertion_before"));
-  auto* insertion_after =
-      a.Create<Node>(cloned.Symbols().New("insertion_after"));
-
-  auto* cloned_root = CloneContext(&cloned, &original)
-                          .InsertBefore(original_root->vec,
-                                        original_root->vec[1], insertion_before)
-                          .InsertAfter(original_root->vec,
-                                       original_root->vec[1], insertion_after)
-                          .Remove(original_root->vec, original_root->vec[1])
-                          .Clone(original_root);
-
-  EXPECT_EQ(cloned_root->vec.size(), 4u);
-
-  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
-  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
-  EXPECT_EQ(cloned_root->vec[1]->name,
-            cloned.Symbols().Get("insertion_before"));
-  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion_after"));
-  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
-}
-
-TEST_F(CloneContextNodeTest, CloneIntoSameBuilder) {
-  ProgramBuilder builder;
-  CloneContext ctx(&builder);
-  Allocator allocator;
-  auto* original = allocator.Create<Node>(builder.Symbols().New());
-  auto* cloned_a = ctx.Clone(original);
-  auto* cloned_b = ctx.Clone(original);
-  EXPECT_NE(original, cloned_a);
-  EXPECT_NE(original, cloned_b);
-
-  EXPECT_NE(cloned_a, cloned_b);
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceAll_SameTypeTwice) {
-  std::string node_name = TypeInfo::Of<Node>().name;
-
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder cloned;
-        Program original;
-        CloneContext ctx(&cloned, &original);
-        ctx.ReplaceAll([](const Node*) { return nullptr; });
-        ctx.ReplaceAll([](const Node*) { return nullptr; });
-      },
-      "internal compiler error: ReplaceAll() called with a handler for type " +
-          node_name + " that is already handled by a handler for type " +
-          node_name);
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceAll_BaseThenDerived) {
-  std::string node_name = TypeInfo::Of<Node>().name;
-  std::string replaceable_name = TypeInfo::Of<Replaceable>().name;
-
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder cloned;
-        Program original;
-        CloneContext ctx(&cloned, &original);
-        ctx.ReplaceAll([](const Node*) { return nullptr; });
-        ctx.ReplaceAll([](const Replaceable*) { return nullptr; });
-      },
-      "internal compiler error: ReplaceAll() called with a handler for type " +
-          replaceable_name + " that is already handled by a handler for type " +
-          node_name);
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceAll_DerivedThenBase) {
-  std::string node_name = TypeInfo::Of<Node>().name;
-  std::string replaceable_name = TypeInfo::Of<Replaceable>().name;
-
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder cloned;
-        Program original;
-        CloneContext ctx(&cloned, &original);
-        ctx.ReplaceAll([](const Replaceable*) { return nullptr; });
-        ctx.ReplaceAll([](const Node*) { return nullptr; });
-      },
-      "internal compiler error: ReplaceAll() called with a handler for type " +
-          node_name + " that is already handled by a handler for type " +
-          replaceable_name);
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplacePointer_WithNotANode) {
-  EXPECT_FATAL_FAILURE(
-      {
-        Allocator allocator;
-        ProgramBuilder builder;
-        auto* original_root =
-            allocator.Create<Node>(builder.Symbols().New("root"));
-        original_root->a = allocator.Create<Node>(builder.Symbols().New("a"));
-        original_root->b = allocator.Create<Node>(builder.Symbols().New("b"));
-        original_root->c = allocator.Create<Node>(builder.Symbols().New("c"));
-        Program original(std::move(builder));
-
-        //                          root
-        //        ╭──────────────────┼──────────────────╮
-        //       (a)                (b)                (c)
-        //                        Replaced
-
-        ProgramBuilder cloned;
-        auto* replacement = allocator.Create<NotANode>();
-
-        CloneContext ctx(&cloned, &original);
-        ctx.Replace(original_root->b, replacement);
-
-        ctx.Clone(original_root);
-      },
-      "internal compiler error");
-}
-
-TEST_F(CloneContextNodeTest, CloneWithReplaceFunction_WithNotANode) {
-  EXPECT_FATAL_FAILURE(
-      {
-        Allocator allocator;
-        ProgramBuilder builder;
-        auto* original_root =
-            allocator.Create<Node>(builder.Symbols().New("root"));
-        original_root->a = allocator.Create<Node>(builder.Symbols().New("a"));
-        original_root->b = allocator.Create<Node>(builder.Symbols().New("b"));
-        original_root->c = allocator.Create<Node>(builder.Symbols().New("c"));
-        Program original(std::move(builder));
-
-        //                          root
-        //        ╭──────────────────┼──────────────────╮
-        //       (a)                (b)                (c)
-        //                        Replaced
-
-        ProgramBuilder cloned;
-        auto* replacement = allocator.Create<NotANode>();
-
-        CloneContext ctx(&cloned, &original);
-        ctx.Replace(original_root->b, [=] { return replacement; });
-
-        ctx.Clone(original_root);
-      },
-      "internal compiler error");
-}
-
-using CloneContextTest = ::testing::Test;
-
-TEST_F(CloneContextTest, CloneWithReplaceAll_SymbolsTwice) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder cloned;
-        Program original;
-        CloneContext ctx(&cloned, &original);
-        ctx.ReplaceAll([](const Symbol s) { return s; });
-        ctx.ReplaceAll([](const Symbol s) { return s; });
-      },
-      "internal compiler error: ReplaceAll(const SymbolTransform&) called "
-      "multiple times on the same CloneContext");
-}
-
-TEST_F(CloneContextTest, CloneNewUnnamedSymbols) {
-  ProgramBuilder builder;
-  Symbol old_a = builder.Symbols().New();
-  Symbol old_b = builder.Symbols().New();
-  Symbol old_c = builder.Symbols().New();
-  EXPECT_EQ(builder.Symbols().NameFor(old_a), "tint_symbol");
-  EXPECT_EQ(builder.Symbols().NameFor(old_b), "tint_symbol_1");
-  EXPECT_EQ(builder.Symbols().NameFor(old_c), "tint_symbol_2");
-
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original, false);
-  Symbol new_x = cloned.Symbols().New();
-  Symbol new_a = ctx.Clone(old_a);
-  Symbol new_y = cloned.Symbols().New();
-  Symbol new_b = ctx.Clone(old_b);
-  Symbol new_z = cloned.Symbols().New();
-  Symbol new_c = ctx.Clone(old_c);
-
-  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "tint_symbol");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "tint_symbol_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "tint_symbol_2");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "tint_symbol_1_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "tint_symbol_3");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "tint_symbol_2_1");
-}
-
-TEST_F(CloneContextTest, CloneNewSymbols) {
-  ProgramBuilder builder;
-  Symbol old_a = builder.Symbols().New("a");
-  Symbol old_b = builder.Symbols().New("b");
-  Symbol old_c = builder.Symbols().New("c");
-  EXPECT_EQ(builder.Symbols().NameFor(old_a), "a");
-  EXPECT_EQ(builder.Symbols().NameFor(old_b), "b");
-  EXPECT_EQ(builder.Symbols().NameFor(old_c), "c");
-
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original, false);
-  Symbol new_x = cloned.Symbols().New("a");
-  Symbol new_a = ctx.Clone(old_a);
-  Symbol new_y = cloned.Symbols().New("b");
-  Symbol new_b = ctx.Clone(old_b);
-  Symbol new_z = cloned.Symbols().New("c");
-  Symbol new_c = ctx.Clone(old_c);
-
-  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c_1");
-}
-
-TEST_F(CloneContextTest, CloneNewSymbols_AfterCloneSymbols) {
-  ProgramBuilder builder;
-  Symbol old_a = builder.Symbols().New("a");
-  Symbol old_b = builder.Symbols().New("b");
-  Symbol old_c = builder.Symbols().New("c");
-  EXPECT_EQ(builder.Symbols().NameFor(old_a), "a");
-  EXPECT_EQ(builder.Symbols().NameFor(old_b), "b");
-  EXPECT_EQ(builder.Symbols().NameFor(old_c), "c");
-
-  Program original(std::move(builder));
-
-  ProgramBuilder cloned;
-  CloneContext ctx(&cloned, &original);
-  Symbol new_x = cloned.Symbols().New("a");
-  Symbol new_a = ctx.Clone(old_a);
-  Symbol new_y = cloned.Symbols().New("b");
-  Symbol new_b = ctx.Clone(old_b);
-  Symbol new_z = cloned.Symbols().New("c");
-  Symbol new_c = ctx.Clone(old_c);
-
-  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c_1");
-  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c");
-}
-
-TEST_F(CloneContextTest, ProgramIDs) {
-  ProgramBuilder dst;
-  Program src(ProgramBuilder{});
-  CloneContext ctx(&dst, &src);
-  Allocator allocator;
-  auto* cloned = ctx.Clone(allocator.Create<ProgramNode>(src.ID(), dst.ID()));
-  EXPECT_EQ(cloned->program_id, dst.ID());
-}
-
-TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedBySrc) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder dst;
-        Program src(ProgramBuilder{});
-        CloneContext ctx(&dst, &src);
-        Allocator allocator;
-        ctx.Clone(allocator.Create<ProgramNode>(ProgramID::New(), dst.ID()));
-      },
-      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object))");
-}
-
-TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedByDst) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder dst;
-        Program src(ProgramBuilder{});
-        CloneContext ctx(&dst, &src);
-        Allocator allocator;
-        ctx.Clone(allocator.Create<ProgramNode>(src.ID(), ProgramID::New()));
-      },
-      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out))");
-}
-
-}  // namespace
-
-TINT_INSTANTIATE_TYPEINFO(Node);
-TINT_INSTANTIATE_TYPEINFO(Replaceable);
-TINT_INSTANTIATE_TYPEINFO(Replacement);
-TINT_INSTANTIATE_TYPEINFO(NotANode);
-TINT_INSTANTIATE_TYPEINFO(ProgramNode);
-
-}  // namespace tint
diff --git a/src/debug.cc b/src/debug.cc
deleted file mode 100644
index 2abf82c..0000000
--- a/src/debug.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/debug.h"
-
-#include <memory>
-
-#include "src/utils/debugger.h"
-
-namespace tint {
-namespace {
-
-InternalCompilerErrorReporter* ice_reporter = nullptr;
-
-}  // namespace
-
-void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter) {
-  ice_reporter = reporter;
-}
-
-InternalCompilerError::InternalCompilerError(const char* file,
-                                             size_t line,
-                                             diag::System system,
-                                             diag::List& diagnostics)
-    : file_(file), line_(line), system_(system), diagnostics_(diagnostics) {}
-
-InternalCompilerError::~InternalCompilerError() {
-  auto file = std::make_shared<Source::File>(file_, "");
-  Source source{Source::Range{{line_}}, file.get()};
-  diagnostics_.add_ice(system_, msg_.str(), source, std::move(file));
-
-  if (ice_reporter) {
-    ice_reporter(diagnostics_);
-  }
-
-  debugger::Break();
-}
-
-}  // namespace tint
diff --git a/src/debug.h b/src/debug.h
deleted file mode 100644
index f17afaf..0000000
--- a/src/debug.h
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_DEBUG_H_
-#define SRC_DEBUG_H_
-
-#include <utility>
-
-#include "src/diagnostic/diagnostic.h"
-#include "src/diagnostic/formatter.h"
-#include "src/diagnostic/printer.h"
-
-namespace tint {
-
-/// Function type used for registering an internal compiler error reporter
-using InternalCompilerErrorReporter = void(const diag::List&);
-
-/// Sets the global error reporter to be called in case of internal compiler
-/// errors.
-/// @param reporter the error reporter
-void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter);
-
-/// InternalCompilerError is a helper for reporting internal compiler errors.
-/// Construct the InternalCompilerError with the source location of the ICE
-/// fault and append any error details with the `<<` operator.
-/// When the InternalCompilerError is destructed, the concatenated error message
-/// is appended to the diagnostics list with the severity of
-/// tint::diag::Severity::InternalCompilerError, and if a
-/// InternalCompilerErrorReporter is set, then it is called with the diagnostic
-/// list.
-class InternalCompilerError {
- public:
-  /// Constructor
-  /// @param file the file containing the ICE
-  /// @param line the line containing the ICE
-  /// @param system the Tint system that has raised the ICE
-  /// @param diagnostics the list of diagnostics to append the ICE message to
-  InternalCompilerError(const char* file,
-                        size_t line,
-                        diag::System system,
-                        diag::List& diagnostics);
-
-  /// Destructor.
-  /// Adds the internal compiler error message to the diagnostics list, and then
-  /// calls the InternalCompilerErrorReporter if one is set.
-  ~InternalCompilerError();
-
-  /// Appends `arg` to the ICE message.
-  /// @param arg the argument to append to the ICE message
-  /// @returns this object so calls can be chained
-  template <typename T>
-  InternalCompilerError& operator<<(T&& arg) {
-    msg_ << std::forward<T>(arg);
-    return *this;
-  }
-
- private:
-  char const* const file_;
-  const size_t line_;
-  diag::System system_;
-  diag::List& diagnostics_;
-  std::stringstream msg_;
-};
-
-}  // namespace tint
-
-/// TINT_ICE() is a macro for appending an internal compiler error message
-/// to the diagnostics list `diagnostics`, and calling the
-/// InternalCompilerErrorReporter with the full diagnostic list if a reporter is
-/// set.
-/// The ICE message contains the callsite's file and line.
-/// Use the `<<` operator to append an error message to the ICE.
-#define TINT_ICE(system, diagnostics)             \
-  tint::InternalCompilerError(__FILE__, __LINE__, \
-                              ::tint::diag::System::system, diagnostics)
-
-/// TINT_UNREACHABLE() is a macro for appending a "TINT_UNREACHABLE"
-/// internal compiler error message to the diagnostics list `diagnostics`, and
-/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
-/// reporter is set.
-/// The ICE message contains the callsite's file and line.
-/// Use the `<<` operator to append an error message to the ICE.
-#define TINT_UNREACHABLE(system, diagnostics) \
-  TINT_ICE(system, diagnostics) << "TINT_UNREACHABLE "
-
-/// TINT_UNIMPLEMENTED() is a macro for appending a "TINT_UNIMPLEMENTED"
-/// internal compiler error message to the diagnostics list `diagnostics`, and
-/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
-/// reporter is set.
-/// The ICE message contains the callsite's file and line.
-/// Use the `<<` operator to append an error message to the ICE.
-#define TINT_UNIMPLEMENTED(system, diagnostics) \
-  TINT_ICE(system, diagnostics) << "TINT_UNIMPLEMENTED "
-
-/// TINT_ASSERT() is a macro for checking the expression is true, triggering a
-/// TINT_ICE if it is not.
-/// The ICE message contains the callsite's file and line.
-/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT() does not
-/// append a message to an existing tint::diag::List. As such, TINT_ASSERT()
-/// may silently fail in builds where SetInternalCompilerErrorReporter() is not
-/// called. Only use in places where there's no sensible place to put proper
-/// error handling.
-#define TINT_ASSERT(system, condition)                   \
-  do {                                                   \
-    if (!(condition)) {                                  \
-      tint::diag::List diagnostics;                      \
-      TINT_ICE(system, diagnostics)                      \
-          << "TINT_ASSERT(" #system ", " #condition ")"; \
-    }                                                    \
-  } while (false)
-
-#endif  // SRC_DEBUG_H_
diff --git a/src/debug_test.cc b/src/debug_test.cc
deleted file mode 100644
index 6d17c5c..0000000
--- a/src/debug_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/debug.h"
-
-#include "gtest/gtest-spi.h"
-
-namespace tint {
-namespace {
-
-TEST(DebugTest, Unreachable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        diag::List diagnostics;
-        TINT_UNREACHABLE(Test, diagnostics);
-      },
-      "internal compiler error");
-}
-
-TEST(DebugTest, AssertTrue) {
-  TINT_ASSERT(Test, true);
-}
-
-TEST(DebugTest, AssertFalse) {
-  EXPECT_FATAL_FAILURE({ TINT_ASSERT(Test, false); },
-                       "internal compiler error");
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/demangler.cc b/src/demangler.cc
deleted file mode 100644
index 762db42..0000000
--- a/src/demangler.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 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/demangler.h"
-
-#include "src/program.h"
-
-namespace tint {
-namespace {
-
-constexpr char kSymbol[] = "$";
-constexpr size_t kSymbolLen = sizeof(kSymbol) - 1;
-
-}  // namespace
-
-Demangler::Demangler() = default;
-
-Demangler::~Demangler() = default;
-
-std::string Demangler::Demangle(const SymbolTable& symbols,
-                                const std::string& str) const {
-  std::stringstream out;
-
-  size_t pos = 0;
-  for (;;) {
-    auto idx = str.find(kSymbol, pos);
-    if (idx == std::string::npos) {
-      out << str.substr(pos);
-      break;
-    }
-
-    out << str.substr(pos, idx - pos);
-
-    auto start_idx = idx + kSymbolLen;
-    auto end_idx = start_idx;
-    while (str[end_idx] >= '0' && str[end_idx] <= '9') {
-      end_idx++;
-    }
-    auto len = end_idx - start_idx;
-
-    auto id = str.substr(start_idx, len);
-    Symbol sym(std::stoi(id), symbols.ProgramID());
-    out << symbols.NameFor(sym);
-
-    pos = end_idx;
-  }
-
-  return out.str();
-}
-
-}  // namespace tint
diff --git a/src/demangler.h b/src/demangler.h
deleted file mode 100644
index 6f28e93..0000000
--- a/src/demangler.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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_DEMANGLER_H_
-#define SRC_DEMANGLER_H_
-
-#include <string>
-
-namespace tint {
-
-class SymbolTable;
-
-/// Helper to demangle strings and replace symbols with original names
-class Demangler {
- public:
-  /// Constructor
-  Demangler();
-  /// Destructor
-  ~Demangler();
-
-  /// Transforms given string and replaces any symbols with original names
-  /// @param symbols the symbol table
-  /// @param str the string to replace
-  /// @returns the string with any symbol replacements performed.
-  std::string Demangle(const SymbolTable& symbols,
-                       const std::string& str) const;
-};
-
-}  // namespace tint
-
-#endif  // SRC_DEMANGLER_H_
diff --git a/src/demangler_test.cc b/src/demangler_test.cc
deleted file mode 100644
index e1ccdbf..0000000
--- a/src/demangler_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/demangler.h"
-#include "src/symbol_table.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-using DemanglerTest = testing::Test;
-
-TEST_F(DemanglerTest, NoSymbols) {
-  SymbolTable t{ProgramID::New()};
-  t.Register("sym1");
-
-  Demangler d;
-  EXPECT_EQ("test str", d.Demangle(t, "test str"));
-}
-
-TEST_F(DemanglerTest, Symbol) {
-  SymbolTable t{ProgramID::New()};
-  t.Register("sym1");
-
-  Demangler d;
-  EXPECT_EQ("test sym1 str", d.Demangle(t, "test $1 str"));
-}
-
-TEST_F(DemanglerTest, MultipleSymbols) {
-  SymbolTable t{ProgramID::New()};
-  t.Register("sym1");
-  t.Register("sym2");
-
-  Demangler d;
-  EXPECT_EQ("test sym1 sym2 sym1 str", d.Demangle(t, "test $1 $2 $1 str"));
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/diagnostic/diagnostic.cc b/src/diagnostic/diagnostic.cc
deleted file mode 100644
index 5d2a853..0000000
--- a/src/diagnostic/diagnostic.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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/diagnostic/diagnostic.h"
-
-#include <unordered_map>
-
-#include "src/diagnostic/formatter.h"
-
-namespace tint {
-namespace diag {
-
-Diagnostic::Diagnostic() = default;
-Diagnostic::Diagnostic(const Diagnostic&) = default;
-Diagnostic::~Diagnostic() = default;
-Diagnostic& Diagnostic::operator=(const Diagnostic&) = default;
-
-List::List() = default;
-List::List(std::initializer_list<Diagnostic> list) : entries_(list) {}
-List::List(const List& rhs) = default;
-
-List::List(List&& rhs) = default;
-
-List::~List() = default;
-
-List& List::operator=(const List& rhs) = default;
-
-List& List::operator=(List&& rhs) = default;
-
-std::string List::str() const {
-  diag::Formatter::Style style;
-  style.print_newline_at_end = false;
-  return Formatter{style}.format(*this);
-}
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/diagnostic.h b/src/diagnostic/diagnostic.h
deleted file mode 100644
index c2c30fc..0000000
--- a/src/diagnostic/diagnostic.h
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2020 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_DIAGNOSTIC_DIAGNOSTIC_H_
-#define SRC_DIAGNOSTIC_DIAGNOSTIC_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "src/source.h"
-
-namespace tint {
-namespace diag {
-
-/// Severity is an enumerator of diagnostic severities.
-enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal };
-
-/// @return true iff `a` is more than, or of equal severity to `b`
-inline bool operator>=(Severity a, Severity b) {
-  return static_cast<int>(a) >= static_cast<int>(b);
-}
-
-/// System is an enumerator of Tint systems that can be the originator of a
-/// diagnostic message.
-enum class System {
-  AST,
-  Clone,
-  Inspector,
-  Program,
-  ProgramBuilder,
-  Reader,
-  Resolver,
-  Semantic,
-  Symbol,
-  Test,
-  Transform,
-  Utils,
-  Writer,
-};
-
-/// Diagnostic holds all the information for a single compiler diagnostic
-/// message.
-class Diagnostic {
- public:
-  /// Constructor
-  Diagnostic();
-  /// Copy constructor
-  Diagnostic(const Diagnostic&);
-  /// Destructor
-  ~Diagnostic();
-
-  /// Copy assignment operator
-  /// @return this diagnostic
-  Diagnostic& operator=(const Diagnostic&);
-
-  /// severity is the severity of the diagnostic message.
-  Severity severity = Severity::Error;
-  /// source is the location of the diagnostic.
-  Source source;
-  /// message is the text associated with the diagnostic.
-  std::string message;
-  /// system is the Tint system that raised the diagnostic.
-  System system;
-  /// code is the error code, for example a validation error might have the code
-  /// `"v-0001"`.
-  const char* code = nullptr;
-  /// A shared pointer to a Source::File. Only used if the diagnostic Source
-  /// points to a file that was created specifically for this diagnostic
-  /// (usually an ICE).
-  std::shared_ptr<Source::File> owned_file = nullptr;
-};
-
-/// List is a container of Diagnostic messages.
-class List {
- public:
-  /// iterator is the type used for range based iteration.
-  using iterator = std::vector<Diagnostic>::const_iterator;
-
-  /// Constructs the list with no elements.
-  List();
-
-  /// Copy constructor. Copies the diagnostics from `list` into this list.
-  /// @param list the list of diagnostics to copy into this list.
-  List(std::initializer_list<Diagnostic> list);
-
-  /// Copy constructor. Copies the diagnostics from `list` into this list.
-  /// @param list the list of diagnostics to copy into this list.
-  List(const List& list);
-
-  /// Move constructor. Moves the diagnostics from `list` into this list.
-  /// @param list the list of diagnostics to move into this list.
-  List(List&& list);
-
-  /// Destructor
-  ~List();
-
-  /// Assignment operator. Copies the diagnostics from `list` into this list.
-  /// @param list the list to copy into this list.
-  /// @return this list.
-  List& operator=(const List& list);
-
-  /// Assignment move operator. Moves the diagnostics from `list` into this
-  /// list.
-  /// @param list the list to move into this list.
-  /// @return this list.
-  List& operator=(List&& list);
-
-  /// adds a diagnostic to the end of this list.
-  /// @param diag the diagnostic to append to this list.
-  void add(Diagnostic&& diag) {
-    if (diag.severity >= Severity::Error) {
-      error_count_++;
-    }
-    entries_.emplace_back(std::move(diag));
-  }
-
-  /// adds a list of diagnostics to the end of this list.
-  /// @param list the diagnostic to append to this list.
-  void add(const List& list) {
-    for (auto diag : list) {
-      add(std::move(diag));
-    }
-  }
-
-  /// adds the note message with the given Source to the end of this list.
-  /// @param system the system raising the note message
-  /// @param note_msg the note message
-  /// @param source the source of the note diagnostic
-  void add_note(System system,
-                const std::string& note_msg,
-                const Source& source) {
-    diag::Diagnostic note{};
-    note.severity = diag::Severity::Note;
-    note.system = system;
-    note.source = source;
-    note.message = note_msg;
-    add(std::move(note));
-  }
-
-  /// adds the warning message with the given Source to the end of this list.
-  /// @param system the system raising the warning message
-  /// @param warning_msg the warning message
-  /// @param source the source of the warning diagnostic
-  void add_warning(System system,
-                   const std::string& warning_msg,
-                   const Source& source) {
-    diag::Diagnostic warning{};
-    warning.severity = diag::Severity::Warning;
-    warning.system = system;
-    warning.source = source;
-    warning.message = warning_msg;
-    add(std::move(warning));
-  }
-
-  /// adds the error message without a source to the end of this list.
-  /// @param system the system raising the error message
-  /// @param err_msg the error message
-  void add_error(System system, std::string err_msg) {
-    diag::Diagnostic error{};
-    error.severity = diag::Severity::Error;
-    error.system = system;
-    error.message = std::move(err_msg);
-    add(std::move(error));
-  }
-
-  /// adds the error message with the given Source to the end of this list.
-  /// @param system the system raising the error message
-  /// @param err_msg the error message
-  /// @param source the source of the error diagnostic
-  void add_error(System system, std::string err_msg, const Source& source) {
-    diag::Diagnostic error{};
-    error.severity = diag::Severity::Error;
-    error.system = system;
-    error.source = source;
-    error.message = std::move(err_msg);
-    add(std::move(error));
-  }
-
-  /// adds the error message with the given code and Source to the end of this
-  /// list.
-  /// @param system the system raising the error message
-  /// @param code the error code
-  /// @param err_msg the error message
-  /// @param source the source of the error diagnostic
-  void add_error(System system,
-                 const char* code,
-                 std::string err_msg,
-                 const Source& source) {
-    diag::Diagnostic error{};
-    error.code = code;
-    error.severity = diag::Severity::Error;
-    error.system = system;
-    error.source = source;
-    error.message = std::move(err_msg);
-    add(std::move(error));
-  }
-
-  /// adds an internal compiler error message to the end of this list.
-  /// @param system the system raising the error message
-  /// @param err_msg the error message
-  /// @param source the source of the internal compiler error
-  /// @param file the Source::File owned by this diagnostic
-  void add_ice(System system,
-               const std::string& err_msg,
-               const Source& source,
-               std::shared_ptr<Source::File> file) {
-    diag::Diagnostic ice{};
-    ice.severity = diag::Severity::InternalCompilerError;
-    ice.system = system;
-    ice.source = source;
-    ice.message = err_msg;
-    ice.owned_file = std::move(file);
-    add(std::move(ice));
-  }
-
-  /// @returns true iff the diagnostic list contains errors diagnostics (or of
-  /// higher severity).
-  bool contains_errors() const { return error_count_ > 0; }
-  /// @returns the number of error diagnostics (or of higher severity).
-  size_t error_count() const { return error_count_; }
-  /// @returns the number of entries in the list.
-  size_t count() const { return entries_.size(); }
-  /// @returns the first diagnostic in the list.
-  iterator begin() const { return entries_.begin(); }
-  /// @returns the last diagnostic in the list.
-  iterator end() const { return entries_.end(); }
-
-  /// @returns a formatted string of all the diagnostics in this list.
-  std::string str() const;
-
- private:
-  std::vector<Diagnostic> entries_;
-  size_t error_count_ = 0;
-};
-
-}  // namespace diag
-}  // namespace tint
-
-#endif  // SRC_DIAGNOSTIC_DIAGNOSTIC_H_
diff --git a/src/diagnostic/diagnostic_test.cc b/src/diagnostic/diagnostic_test.cc
deleted file mode 100644
index b904b6f..0000000
--- a/src/diagnostic/diagnostic_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/diagnostic/formatter.h"
-
-#include "gtest/gtest.h"
-#include "src/diagnostic/diagnostic.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-TEST(DiagListTest, OwnedFilesShared) {
-  auto file = std::make_shared<Source::File>("path", "content");
-
-  diag::List list_a, list_b;
-  {
-    diag::Diagnostic diag{};
-    diag.source = Source{Source::Range{{0, 0}}, file.get()};
-    list_a.add(std::move(diag));
-  }
-
-  list_b = list_a;
-
-  ASSERT_EQ(list_b.count(), list_a.count());
-  EXPECT_EQ(list_b.begin()->source.file, file.get());
-}
-
-}  // namespace
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/formatter.cc b/src/diagnostic/formatter.cc
deleted file mode 100644
index 1998523..0000000
--- a/src/diagnostic/formatter.cc
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2020 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/diagnostic/formatter.h"
-
-#include <algorithm>
-#include <iterator>
-#include <vector>
-
-#include "src/diagnostic/diagnostic.h"
-#include "src/diagnostic/printer.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-const char* to_str(Severity severity) {
-  switch (severity) {
-    case Severity::Note:
-      return "note";
-    case Severity::Warning:
-      return "warning";
-    case Severity::Error:
-      return "error";
-    case Severity::InternalCompilerError:
-      return "internal compiler error";
-    case Severity::Fatal:
-      return "fatal";
-  }
-  return "";
-}
-
-std::string to_str(const Source::Location& location) {
-  std::stringstream ss;
-  if (location.line > 0) {
-    ss << location.line;
-    if (location.column > 0) {
-      ss << ":" << location.column;
-    }
-  }
-  return ss.str();
-}
-
-}  // namespace
-
-/// State holds the internal formatter state for a format() call.
-struct Formatter::State {
-  /// Constructs a State associated with the given printer.
-  /// @param p the printer to write formatted messages to.
-  explicit State(Printer* p) : printer(p) {}
-  ~State() { flush(); }
-
-  /// set_style() sets the current style to new_style, flushing any pending
-  /// messages to the printer if the style changed.
-  /// @param new_style the new style to apply for future written messages.
-  void set_style(const diag::Style& new_style) {
-    if (style.color != new_style.color || style.bold != new_style.bold) {
-      flush();
-      style = new_style;
-    }
-  }
-
-  /// flush writes any pending messages to the printer, clearing the buffer.
-  void flush() {
-    auto str = stream.str();
-    if (str.length() > 0) {
-      printer->write(str, style);
-      std::stringstream reset;
-      stream.swap(reset);
-    }
-  }
-
-  /// operator<< queues msg to be written to the printer.
-  /// @param msg the value or string to write to the printer
-  /// @returns this State so that calls can be chained
-  template <typename T>
-  State& operator<<(const T& msg) {
-    stream << msg;
-    return *this;
-  }
-
-  /// newline queues a newline to be written to the printer.
-  void newline() { stream << std::endl; }
-
-  /// repeat queues the character c to be written to the printer n times.
-  /// @param c the character to print `n` times
-  /// @param n the number of times to print character `c`
-  void repeat(char c, size_t n) {
-    std::fill_n(std::ostream_iterator<char>(stream), n, c);
-  }
-
- private:
-  Printer* printer;
-  diag::Style style;
-  std::stringstream stream;
-};
-
-Formatter::Formatter() {}
-Formatter::Formatter(const Style& style) : style_(style) {}
-
-void Formatter::format(const List& list, Printer* printer) const {
-  State state{printer};
-
-  bool first = true;
-  for (auto diag : list) {
-    state.set_style({});
-    if (!first) {
-      state.newline();
-    }
-    format(diag, state);
-    first = false;
-  }
-
-  if (style_.print_newline_at_end) {
-    state.newline();
-  }
-}
-
-void Formatter::format(const Diagnostic& diag, State& state) const {
-  auto const& src = diag.source;
-  auto const& rng = src.range;
-  bool has_code = diag.code != nullptr && diag.code[0] != '\0';
-
-  state.set_style({Color::kDefault, true});
-
-  struct TextAndColor {
-    std::string text;
-    Color color;
-    bool bold = false;
-  };
-  std::vector<TextAndColor> prefix;
-  prefix.reserve(6);
-
-  if (style_.print_file && src.file != nullptr) {
-    if (rng.begin.line > 0) {
-      prefix.emplace_back(TextAndColor{src.file->path + ":" + to_str(rng.begin),
-                                       Color::kDefault});
-    } else {
-      prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault});
-    }
-  } else if (rng.begin.line > 0) {
-    prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
-  }
-
-  Color severity_color = Color::kDefault;
-  switch (diag.severity) {
-    case Severity::Note:
-      break;
-    case Severity::Warning:
-      severity_color = Color::kYellow;
-      break;
-    case Severity::Error:
-      severity_color = Color::kRed;
-      break;
-    case Severity::Fatal:
-    case Severity::InternalCompilerError:
-      severity_color = Color::kMagenta;
-      break;
-  }
-  if (style_.print_severity) {
-    prefix.emplace_back(
-        TextAndColor{to_str(diag.severity), severity_color, true});
-  }
-  if (has_code) {
-    prefix.emplace_back(TextAndColor{diag.code, severity_color});
-  }
-
-  for (size_t i = 0; i < prefix.size(); i++) {
-    if (i > 0) {
-      state << " ";
-    }
-    state.set_style({prefix[i].color, prefix[i].bold});
-    state << prefix[i].text;
-  }
-
-  state.set_style({Color::kDefault, true});
-  if (!prefix.empty()) {
-    state << ": ";
-  }
-  state << diag.message;
-
-  if (style_.print_line && src.file && rng.begin.line > 0) {
-    state.newline();
-    state.set_style({Color::kDefault, false});
-
-    for (size_t line_num = rng.begin.line;
-         (line_num <= rng.end.line) &&
-         (line_num <= src.file->content.lines.size());
-         line_num++) {
-      auto& line = src.file->content.lines[line_num - 1];
-      auto line_len = line.size();
-
-      bool is_ascii = true;
-      for (auto c : line) {
-        if (c == '\t') {
-          state.repeat(' ', style_.tab_width);
-        } else {
-          state << c;
-        }
-        if (c & 0x80) {
-          is_ascii = false;
-        }
-      }
-
-      state.newline();
-
-      // If the line contains non-ascii characters, then we cannot assume that
-      // a single utf8 code unit represents a single glyph, so don't attempt to
-      // draw squiggles.
-      if (!is_ascii) {
-        continue;
-      }
-
-      state.set_style({Color::kCyan, false});
-
-      // Count the number of glyphs in the line span.
-      // start and end use 1-based indexing.
-      auto num_glyphs = [&](size_t start, size_t end) {
-        size_t count = 0;
-        start = (start > 0) ? (start - 1) : 0;
-        end = (end > 0) ? (end - 1) : 0;
-        for (size_t i = start; (i < end) && (i < line_len); i++) {
-          count += (line[i] == '\t') ? style_.tab_width : 1;
-        }
-        return count;
-      };
-
-      if (line_num == rng.begin.line && line_num == rng.end.line) {
-        // Single line
-        state.repeat(' ', num_glyphs(1, rng.begin.column));
-        state.repeat('^', std::max<size_t>(
-                              num_glyphs(rng.begin.column, rng.end.column), 1));
-      } else if (line_num == rng.begin.line) {
-        // Start of multi-line
-        state.repeat(' ', num_glyphs(1, rng.begin.column));
-        state.repeat('^', num_glyphs(rng.begin.column, line_len + 1));
-      } else if (line_num == rng.end.line) {
-        // End of multi-line
-        state.repeat('^', num_glyphs(1, rng.end.column));
-      } else {
-        // Middle of multi-line
-        state.repeat('^', num_glyphs(1, line_len + 1));
-      }
-      state.newline();
-    }
-
-    state.set_style({});
-  }
-}
-
-std::string Formatter::format(const List& list) const {
-  StringPrinter printer;
-  format(list, &printer);
-  return printer.str();
-}
-
-Formatter::~Formatter() = default;
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/formatter.h b/src/diagnostic/formatter.h
deleted file mode 100644
index 91afd81..0000000
--- a/src/diagnostic/formatter.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 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_DIAGNOSTIC_FORMATTER_H_
-#define SRC_DIAGNOSTIC_FORMATTER_H_
-
-#include <string>
-
-namespace tint {
-namespace diag {
-
-class Diagnostic;
-class List;
-class Printer;
-
-/// Formatter are used to print a list of diagnostics messages.
-class Formatter {
- public:
-  /// Style controls the formatter's output style.
-  struct Style {
-    /// include the file path for each diagnostic
-    bool print_file = true;
-    /// include the severity for each diagnostic
-    bool print_severity = true;
-    /// include the source line(s) for the diagnostic
-    bool print_line = true;
-    /// print a newline at the end of a diagnostic list
-    bool print_newline_at_end = true;
-    /// width of a tab character
-    size_t tab_width = 2u;
-  };
-
-  /// Constructor for the formatter using a default style.
-  Formatter();
-
-  /// Constructor for the formatter using the custom style.
-  /// @param style the style used for the formatter.
-  explicit Formatter(const Style& style);
-
-  ~Formatter();
-
-  /// @param list the list of diagnostic messages to format
-  /// @param printer the printer used to display the formatted diagnostics
-  void format(const List& list, Printer* printer) const;
-
-  /// @return the list of diagnostics `list` formatted to a string.
-  /// @param list the list of diagnostic messages to format
-  std::string format(const List& list) const;
-
- private:
-  struct State;
-
-  void format(const Diagnostic& diag, State& state) const;
-
-  const Style style_;
-};
-
-}  // namespace diag
-}  // namespace tint
-
-#endif  // SRC_DIAGNOSTIC_FORMATTER_H_
diff --git a/src/diagnostic/formatter_test.cc b/src/diagnostic/formatter_test.cc
deleted file mode 100644
index a97f57e..0000000
--- a/src/diagnostic/formatter_test.cc
+++ /dev/null
@@ -1,310 +0,0 @@
-// Copyright 2020 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/diagnostic/formatter.h"
-
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/diagnostic/diagnostic.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-Diagnostic Diag(Severity severity,
-                Source source,
-                std::string message,
-                System system,
-                const char* code = nullptr) {
-  Diagnostic d;
-  d.severity = severity;
-  d.source = source;
-  d.message = std::move(message);
-  d.system = system;
-  d.code = code;
-  return d;
-}
-
-constexpr const char* ascii_content =  // Note: words are tab-delimited
-    R"(the	cat	says	meow
-the	dog	says	woof
-the	snake	says	quack
-the	snail	says	???
-)";
-
-constexpr const char* utf8_content =  // Note: words are tab-delimited
-    "the	\xf0\x9f\x90\xb1	says	meow\n"   // NOLINT: tabs
-    "the	\xf0\x9f\x90\x95	says	woof\n"   // NOLINT: tabs
-    "the	\xf0\x9f\x90\x8d	says	quack\n"  // NOLINT: tabs
-    "the	\xf0\x9f\x90\x8c	says	???\n";   // NOLINT: tabs
-
-class DiagFormatterTest : public testing::Test {
- public:
-  Source::File ascii_file{"file.name", ascii_content};
-  Source::File utf8_file{"file.name", utf8_content};
-  Diagnostic ascii_diag_note =
-      Diag(Severity::Note,
-           Source{Source::Range{Source::Location{1, 14}}, &ascii_file},
-           "purr",
-           System::Test);
-  Diagnostic ascii_diag_warn =
-      Diag(Severity::Warning,
-           Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file},
-           "grrr",
-           System::Test);
-  Diagnostic ascii_diag_err =
-      Diag(Severity::Error,
-           Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file},
-           "hiss",
-           System::Test,
-           "abc123");
-  Diagnostic ascii_diag_ice =
-      Diag(Severity::InternalCompilerError,
-           Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
-           "unreachable",
-           System::Test);
-  Diagnostic ascii_diag_fatal =
-      Diag(Severity::Fatal,
-           Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
-           "nothing",
-           System::Test);
-
-  Diagnostic utf8_diag_note =
-      Diag(Severity::Note,
-           Source{Source::Range{Source::Location{1, 15}}, &utf8_file},
-           "purr",
-           System::Test);
-  Diagnostic utf8_diag_warn =
-      Diag(Severity::Warning,
-           Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file},
-           "grrr",
-           System::Test);
-  Diagnostic utf8_diag_err =
-      Diag(Severity::Error,
-           Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file},
-           "hiss",
-           System::Test,
-           "abc123");
-  Diagnostic utf8_diag_ice =
-      Diag(Severity::InternalCompilerError,
-           Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
-           "unreachable",
-           System::Test);
-  Diagnostic utf8_diag_fatal =
-      Diag(Severity::Fatal,
-           Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
-           "nothing",
-           System::Test);
-};
-
-TEST_F(DiagFormatterTest, Simple) {
-  Formatter fmt{{false, false, false, false}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(1:14: purr
-2:14: grrr
-3:16 abc123: hiss)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, SimpleNewlineAtEnd) {
-  Formatter fmt{{false, false, false, true}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(1:14: purr
-2:14: grrr
-3:16 abc123: hiss
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, SimpleNoSource) {
-  Formatter fmt{{false, false, false, false}};
-  auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test);
-  auto got = fmt.format(List{diag});
-  auto* expect = "no source!";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, WithFile) {
-  Formatter fmt{{true, false, false, false}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(file.name:1:14: purr
-file.name:2:14: grrr
-file.name:3:16 abc123: hiss)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, WithSeverity) {
-  Formatter fmt{{false, true, false, false}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(1:14 note: purr
-2:14 warning: grrr
-3:16 error abc123: hiss)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, WithLine) {
-  Formatter fmt{{false, false, true, false}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(1:14: purr
-the  cat  says  meow
-                ^
-
-2:14: grrr
-the  dog  says  woof
-                ^^^^
-
-3:16 abc123: hiss
-the  snake  says  quack
-                  ^^^^^
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, UnicodeWithLine) {
-  Formatter fmt{{false, false, true, false}};
-  auto got = fmt.format(List{utf8_diag_note, utf8_diag_warn, utf8_diag_err});
-  auto* expect =
-      "1:15: purr\n"
-      "the  \xf0\x9f\x90\xb1  says  meow\n"
-      "\n"
-      "2:15: grrr\n"
-      "the  \xf0\x9f\x90\x95  says  woof\n"
-      "\n"
-      "3:15 abc123: hiss\n"
-      "the  \xf0\x9f\x90\x8d  says  quack\n";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
-  Formatter fmt{{true, true, true, false}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(file.name:1:14 note: purr
-the  cat  says  meow
-                ^
-
-file.name:2:14 warning: grrr
-the  dog  says  woof
-                ^^^^
-
-file.name:3:16 error abc123: hiss
-the  snake  says  quack
-                  ^^^^^
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, BasicWithMultiLine) {
-  auto multiline = Diag(Severity::Warning,
-                        Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
-                        "multiline", System::Test);
-  Formatter fmt{{false, false, true, false}};
-  auto got = fmt.format(List{multiline});
-  auto* expect = R"(2:9: multiline
-the  dog  says  woof
-          ^^^^^^^^^^
-the  snake  says  quack
-^^^^^^^^^^^^^^^^^^^^^^^
-the  snail  says  ???
-^^^^^^^^^^^^^^^^
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, UnicodeWithMultiLine) {
-  auto multiline = Diag(Severity::Warning,
-                        Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file},
-                        "multiline", System::Test);
-  Formatter fmt{{false, false, true, false}};
-  auto got = fmt.format(List{multiline});
-  auto* expect =
-      "2:9: multiline\n"
-      "the  \xf0\x9f\x90\x95  says  woof\n"
-      "the  \xf0\x9f\x90\x8d  says  quack\n"
-      "the  \xf0\x9f\x90\x8c  says  ???\n";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, BasicWithFileSeverityLineTab4) {
-  Formatter fmt{{true, true, true, false, 4u}};
-  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
-  auto* expect = R"(file.name:1:14 note: purr
-the    cat    says    meow
-                      ^
-
-file.name:2:14 warning: grrr
-the    dog    says    woof
-                      ^^^^
-
-file.name:3:16 error abc123: hiss
-the    snake    says    quack
-                        ^^^^^
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) {
-  auto multiline = Diag(Severity::Warning,
-                        Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
-                        "multiline", System::Test);
-  Formatter fmt{{false, false, true, false, 4u}};
-  auto got = fmt.format(List{multiline});
-  auto* expect = R"(2:9: multiline
-the    dog    says    woof
-              ^^^^^^^^^^^^
-the    snake    says    quack
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-the    snail    says    ???
-^^^^^^^^^^^^^^^^^^^^
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, ICE) {
-  Formatter fmt{{}};
-  auto got = fmt.format(List{ascii_diag_ice});
-  auto* expect = R"(file.name:4:16 internal compiler error: unreachable
-the  snail  says  ???
-                  ^^^
-
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, Fatal) {
-  Formatter fmt{{}};
-  auto got = fmt.format(List{ascii_diag_fatal});
-  auto* expect = R"(file.name:4:16 fatal: nothing
-the  snail  says  ???
-                  ^^^
-
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(DiagFormatterTest, RangeOOB) {
-  Formatter fmt{{true, true, true, true}};
-  diag::List list;
-  list.add_error(System::Test, "oob",
-                 Source{{{10, 20}, {30, 20}}, &ascii_file});
-  auto got = fmt.format(list);
-  auto* expect = R"(file.name:10:20 error: oob
-
-)";
-  ASSERT_EQ(expect, got);
-}
-
-}  // namespace
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/printer.cc b/src/diagnostic/printer.cc
deleted file mode 100644
index adb4b1b..0000000
--- a/src/diagnostic/printer.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2020 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/diagnostic/printer.h"
-
-namespace tint {
-namespace diag {
-
-Printer::~Printer() = default;
-
-StringPrinter::StringPrinter() = default;
-StringPrinter::~StringPrinter() = default;
-
-std::string StringPrinter::str() const {
-  return stream.str();
-}
-
-void StringPrinter::write(const std::string& str, const Style&) {
-  stream << str;
-}
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/printer.h b/src/diagnostic/printer.h
deleted file mode 100644
index 447226c..0000000
--- a/src/diagnostic/printer.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 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_DIAGNOSTIC_PRINTER_H_
-#define SRC_DIAGNOSTIC_PRINTER_H_
-
-#include <memory>
-#include <sstream>
-#include <string>
-
-namespace tint {
-namespace diag {
-
-class List;
-
-/// Color is an enumerator of colors used by Style.
-enum class Color {
-  kDefault,
-  kBlack,
-  kRed,
-  kGreen,
-  kYellow,
-  kBlue,
-  kMagenta,
-  kCyan,
-  kWhite,
-};
-
-/// Style describes how a diagnostic message should be printed.
-struct Style {
-  /// The foreground text color
-  Color color = Color::kDefault;
-  /// If true the text will be displayed with a strong weight
-  bool bold = false;
-};
-
-/// Printers are used to print formatted diagnostic messages to a terminal.
-class Printer {
- public:
-  /// @returns a diagnostic Printer
-  /// @param out the file to print to.
-  /// @param use_colors if true, the printer will use colors if `out` is a
-  /// terminal and supports them.
-  static std::unique_ptr<Printer> create(FILE* out, bool use_colors);
-
-  virtual ~Printer();
-
-  /// writes the string str to the printer with the given style.
-  /// @param str the string to write to the printer
-  /// @param style the style used to print `str`
-  virtual void write(const std::string& str, const Style& style) = 0;
-};
-
-/// StringPrinter is an implementation of Printer that writes to a std::string.
-class StringPrinter : public Printer {
- public:
-  StringPrinter();
-  ~StringPrinter() override;
-
-  /// @returns the printed string.
-  std::string str() const;
-
-  void write(const std::string& str, const Style&) override;
-
- private:
-  std::stringstream stream;
-};
-
-}  // namespace diag
-}  // namespace tint
-
-#endif  // SRC_DIAGNOSTIC_PRINTER_H_
diff --git a/src/diagnostic/printer_linux.cc b/src/diagnostic/printer_linux.cc
deleted file mode 100644
index 42c5264..0000000
--- a/src/diagnostic/printer_linux.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2020 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 <unistd.h>
-
-#include <cstring>
-
-#include "src/diagnostic/printer.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-bool supports_colors(FILE* f) {
-  if (!isatty(fileno(f))) {
-    return false;
-  }
-
-  const char* cterm = getenv("TERM");
-  if (cterm == nullptr) {
-    return false;
-  }
-
-  std::string term = getenv("TERM");
-  if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" &&
-      term != "rxvt-unicode" && term != "screen-256color" && term != "screen" &&
-      term != "tmux-256color" && term != "tmux" && term != "xterm-256color" &&
-      term != "xterm-color" && term != "xterm") {
-    return false;
-  }
-
-  return true;
-}
-
-class PrinterLinux : public Printer {
- public:
-  PrinterLinux(FILE* f, bool colors)
-      : file(f), use_colors(colors && supports_colors(f)) {}
-
-  void write(const std::string& str, const Style& style) override {
-    write_color(style.color, style.bold);
-    fwrite(str.data(), 1, str.size(), file);
-    write_color(Color::kDefault, false);
-  }
-
- private:
-  constexpr const char* color_code(Color color, bool bold) {
-    switch (color) {
-      case Color::kDefault:
-        return bold ? "\u001b[1m" : "\u001b[0m";
-      case Color::kBlack:
-        return bold ? "\u001b[30;1m" : "\u001b[30m";
-      case Color::kRed:
-        return bold ? "\u001b[31;1m" : "\u001b[31m";
-      case Color::kGreen:
-        return bold ? "\u001b[32;1m" : "\u001b[32m";
-      case Color::kYellow:
-        return bold ? "\u001b[33;1m" : "\u001b[33m";
-      case Color::kBlue:
-        return bold ? "\u001b[34;1m" : "\u001b[34m";
-      case Color::kMagenta:
-        return bold ? "\u001b[35;1m" : "\u001b[35m";
-      case Color::kCyan:
-        return bold ? "\u001b[36;1m" : "\u001b[36m";
-      case Color::kWhite:
-        return bold ? "\u001b[37;1m" : "\u001b[37m";
-    }
-    return "";  // unreachable
-  }
-
-  void write_color(Color color, bool bold) {
-    if (use_colors) {
-      auto* code = color_code(color, bold);
-      fwrite(code, 1, strlen(code), file);
-    }
-  }
-
-  FILE* const file;
-  const bool use_colors;
-};
-
-}  // namespace
-
-std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
-  return std::make_unique<PrinterLinux>(out, use_colors);
-}
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/printer_other.cc b/src/diagnostic/printer_other.cc
deleted file mode 100644
index bde534a..0000000
--- a/src/diagnostic/printer_other.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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 <cstring>
-
-#include "src/diagnostic/printer.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-class PrinterOther : public Printer {
- public:
-  explicit PrinterOther(FILE* f) : file(f) {}
-
-  void write(const std::string& str, const Style&) override {
-    fwrite(str.data(), 1, str.size(), file);
-  }
-
- private:
-  FILE* file;
-};
-
-}  // namespace
-
-std::unique_ptr<Printer> Printer::create(FILE* out, bool) {
-  return std::make_unique<PrinterOther>(out);
-}
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/printer_test.cc b/src/diagnostic/printer_test.cc
deleted file mode 100644
index ce9fd14..0000000
--- a/src/diagnostic/printer_test.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2020 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/diagnostic/printer.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace diag {
-namespace {
-
-// Actually verifying that the expected colors are printed is exceptionally
-// difficult as:
-// a) The color emission varies by OS.
-// b) The logic checks to see if the printer is writing to a terminal, making
-//    mocking hard.
-// c) Actually probing what gets written to a FILE* is notoriously tricky.
-//
-// The least we can do is to exersice the code - which is what we do here.
-// The test will print each of the colors, and can be examined with human
-// eyeballs.
-// This can be enabled or disabled with ENABLE_PRINTER_TESTS
-#define ENABLE_PRINTER_TESTS 0
-#if ENABLE_PRINTER_TESTS
-
-using PrinterTest = testing::Test;
-
-TEST_F(PrinterTest, WithColors) {
-  auto printer = Printer::create(stdout, true);
-  printer->write("Default", Style{Color::kDefault, false});
-  printer->write("Black", Style{Color::kBlack, false});
-  printer->write("Red", Style{Color::kRed, false});
-  printer->write("Green", Style{Color::kGreen, false});
-  printer->write("Yellow", Style{Color::kYellow, false});
-  printer->write("Blue", Style{Color::kBlue, false});
-  printer->write("Magenta", Style{Color::kMagenta, false});
-  printer->write("Cyan", Style{Color::kCyan, false});
-  printer->write("White", Style{Color::kWhite, false});
-  printf("\n");
-}
-
-TEST_F(PrinterTest, BoldWithColors) {
-  auto printer = Printer::create(stdout, true);
-  printer->write("Default", Style{Color::kDefault, true});
-  printer->write("Black", Style{Color::kBlack, true});
-  printer->write("Red", Style{Color::kRed, true});
-  printer->write("Green", Style{Color::kGreen, true});
-  printer->write("Yellow", Style{Color::kYellow, true});
-  printer->write("Blue", Style{Color::kBlue, true});
-  printer->write("Magenta", Style{Color::kMagenta, true});
-  printer->write("Cyan", Style{Color::kCyan, true});
-  printer->write("White", Style{Color::kWhite, true});
-  printf("\n");
-}
-
-TEST_F(PrinterTest, WithoutColors) {
-  auto printer = Printer::create(stdout, false);
-  printer->write("Default", Style{Color::kDefault, false});
-  printer->write("Black", Style{Color::kBlack, false});
-  printer->write("Red", Style{Color::kRed, false});
-  printer->write("Green", Style{Color::kGreen, false});
-  printer->write("Yellow", Style{Color::kYellow, false});
-  printer->write("Blue", Style{Color::kBlue, false});
-  printer->write("Magenta", Style{Color::kMagenta, false});
-  printer->write("Cyan", Style{Color::kCyan, false});
-  printer->write("White", Style{Color::kWhite, false});
-  printf("\n");
-}
-
-TEST_F(PrinterTest, BoldWithoutColors) {
-  auto printer = Printer::create(stdout, false);
-  printer->write("Default", Style{Color::kDefault, true});
-  printer->write("Black", Style{Color::kBlack, true});
-  printer->write("Red", Style{Color::kRed, true});
-  printer->write("Green", Style{Color::kGreen, true});
-  printer->write("Yellow", Style{Color::kYellow, true});
-  printer->write("Blue", Style{Color::kBlue, true});
-  printer->write("Magenta", Style{Color::kMagenta, true});
-  printer->write("Cyan", Style{Color::kCyan, true});
-  printer->write("White", Style{Color::kWhite, true});
-  printf("\n");
-}
-
-#endif  // ENABLE_PRINTER_TESTS
-}  // namespace
-}  // namespace diag
-}  // namespace tint
diff --git a/src/diagnostic/printer_windows.cc b/src/diagnostic/printer_windows.cc
deleted file mode 100644
index eba1dda..0000000
--- a/src/diagnostic/printer_windows.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2020 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 <cstring>
-
-#include "src/diagnostic/printer.h"
-
-#define WIN32_LEAN_AND_MEAN 1
-#include <Windows.h>
-
-namespace tint {
-namespace diag {
-namespace {
-
-struct ConsoleInfo {
-  HANDLE handle = INVALID_HANDLE_VALUE;
-  WORD default_attributes = 0;
-  operator bool() const { return handle != INVALID_HANDLE_VALUE; }
-};
-
-ConsoleInfo console_info(FILE* file) {
-  if (file == nullptr) {
-    return {};
-  }
-
-  ConsoleInfo console{};
-  if (file == stdout) {
-    console.handle = GetStdHandle(STD_OUTPUT_HANDLE);
-  } else if (file == stderr) {
-    console.handle = GetStdHandle(STD_ERROR_HANDLE);
-  } else {
-    return {};
-  }
-
-  CONSOLE_SCREEN_BUFFER_INFO info{};
-  if (GetConsoleScreenBufferInfo(console.handle, &info) == 0) {
-    return {};
-  }
-
-  console.default_attributes = info.wAttributes;
-  return console;
-}
-
-class PrinterWindows : public Printer {
- public:
-  PrinterWindows(FILE* f, bool use_colors)
-      : file(f), console(console_info(use_colors ? f : nullptr)) {}
-
-  void write(const std::string& str, const Style& style) override {
-    write_color(style.color, style.bold);
-    fwrite(str.data(), 1, str.size(), file);
-    write_color(Color::kDefault, false);
-  }
-
- private:
-  WORD attributes(Color color, bool bold) {
-    switch (color) {
-      case Color::kDefault:
-        return console.default_attributes;
-      case Color::kBlack:
-        return 0;
-      case Color::kRed:
-        return FOREGROUND_RED | (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kGreen:
-        return FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kYellow:
-        return FOREGROUND_RED | FOREGROUND_GREEN |
-               (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kBlue:
-        return FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kMagenta:
-        return FOREGROUND_RED | FOREGROUND_BLUE |
-               (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kCyan:
-        return FOREGROUND_GREEN | FOREGROUND_BLUE |
-               (bold ? FOREGROUND_INTENSITY : 0);
-      case Color::kWhite:
-        return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
-               (bold ? FOREGROUND_INTENSITY : 0);
-    }
-    return 0;  // unreachable
-  }
-
-  void write_color(Color color, bool bold) {
-    if (console) {
-      SetConsoleTextAttribute(console.handle, attributes(color, bold));
-      fflush(file);
-    }
-  }
-
-  FILE* const file;
-  const ConsoleInfo console;
-};
-
-}  // namespace
-
-std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
-  return std::make_unique<PrinterWindows>(out, use_colors);
-}
-
-}  // namespace diag
-}  // namespace tint
diff --git a/src/inspector/entry_point.cc b/src/inspector/entry_point.cc
deleted file mode 100644
index 7cce84c..0000000
--- a/src/inspector/entry_point.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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/inspector/entry_point.h"
-
-namespace tint {
-namespace inspector {
-
-StageVariable::StageVariable() = default;
-StageVariable::StageVariable(const StageVariable& other)
-    : name(other.name),
-      has_location_attribute(other.has_location_attribute),
-      location_attribute(other.location_attribute),
-      has_location_decoration(has_location_attribute),
-      location_decoration(location_attribute),
-      component_type(other.component_type),
-      composition_type(other.composition_type),
-      interpolation_type(other.interpolation_type),
-      interpolation_sampling(other.interpolation_sampling) {}
-
-StageVariable::~StageVariable() = default;
-
-EntryPoint::EntryPoint() = default;
-EntryPoint::EntryPoint(EntryPoint&) = default;
-EntryPoint::EntryPoint(EntryPoint&&) = default;
-EntryPoint::~EntryPoint() = default;
-
-InterpolationType ASTToInspectorInterpolationType(
-    ast::InterpolationType ast_type) {
-  switch (ast_type) {
-    case ast::InterpolationType::kPerspective:
-      return InterpolationType::kPerspective;
-    case ast::InterpolationType::kLinear:
-      return InterpolationType::kLinear;
-    case ast::InterpolationType::kFlat:
-      return InterpolationType::kFlat;
-  }
-
-  return InterpolationType::kUnknown;
-}
-
-InterpolationSampling ASTToInspectorInterpolationSampling(
-    ast::InterpolationSampling sampling) {
-  switch (sampling) {
-    case ast::InterpolationSampling::kNone:
-      return InterpolationSampling::kNone;
-    case ast::InterpolationSampling::kCenter:
-      return InterpolationSampling::kCenter;
-    case ast::InterpolationSampling::kCentroid:
-      return InterpolationSampling::kCentroid;
-    case ast::InterpolationSampling::kSample:
-      return InterpolationSampling::kSample;
-  }
-
-  return InterpolationSampling::kUnknown;
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/entry_point.h b/src/inspector/entry_point.h
deleted file mode 100644
index f7bb23a..0000000
--- a/src/inspector/entry_point.h
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2020 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_INSPECTOR_ENTRY_POINT_H_
-#define SRC_INSPECTOR_ENTRY_POINT_H_
-
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/pipeline_stage.h"
-
-namespace tint {
-namespace inspector {
-
-/// Base component type of a stage variable.
-enum class ComponentType {
-  kUnknown = -1,
-  kFloat,
-  kUInt,
-  kSInt,
-};
-
-/// Composition of components of a stage variable.
-enum class CompositionType {
-  kUnknown = -1,
-  kScalar,
-  kVec2,
-  kVec3,
-  kVec4,
-};
-
-/// Type of interpolation of a stage variable.
-enum class InterpolationType { kUnknown = -1, kPerspective, kLinear, kFlat };
-
-/// Type of interpolation sampling of a stage variable.
-enum class InterpolationSampling {
-  kUnknown = -1,
-  kNone,
-  kCenter,
-  kCentroid,
-  kSample
-};
-
-/// Reflection data about an entry point input or output.
-struct StageVariable {
-  /// Constructor
-  StageVariable();
-  /// Copy constructor
-  /// @param other the StageVariable to copy
-  StageVariable(const StageVariable& other);
-  /// Destructor
-  ~StageVariable();
-
-  /// Name of the variable in the shader.
-  std::string name;
-  /// Is location attribute present
-  bool has_location_attribute = false;
-  /// Value of the location attribute, only valid if #has_location_attribute is
-  /// true.
-  uint32_t location_attribute;
-  /// Is Location attribute present
-  /// [DEPRECATED]: Use #has_location_attribute
-  bool& has_location_decoration = has_location_attribute;
-  /// Value of Location Decoration, only valid if #has_location_decoration is
-  /// true.
-  /// [DEPRECATED]: Use #location_attribute
-  uint32_t& location_decoration = location_attribute;
-  /// Scalar type that the variable is composed of.
-  ComponentType component_type = ComponentType::kUnknown;
-  /// How the scalars are composed for the variable.
-  CompositionType composition_type = CompositionType::kUnknown;
-  /// Interpolation type of the variable.
-  InterpolationType interpolation_type = InterpolationType::kUnknown;
-  /// Interpolation sampling of the variable.
-  InterpolationSampling interpolation_sampling =
-      InterpolationSampling::kUnknown;
-};
-
-/// Convert from internal ast::InterpolationType to public ::InterpolationType.
-/// @param ast_type internal value to convert from
-/// @returns the publicly visible equivalent
-InterpolationType ASTToInspectorInterpolationType(
-    ast::InterpolationType ast_type);
-
-/// Convert from internal ast::InterpolationSampling to public
-/// ::InterpolationSampling
-/// @param sampling internal value to convert from
-/// @returns the publicly visible equivalent
-InterpolationSampling ASTToInspectorInterpolationSampling(
-    ast::InterpolationSampling sampling);
-
-/// Reflection data about a pipeline overridable constant referenced by an entry
-/// point
-struct OverridableConstant {
-  /// Name of the constant
-  std::string name;
-
-  /// ID of the constant
-  uint16_t numeric_id;
-
-  /// Type of the scalar
-  enum class Type {
-    kBool,
-    kFloat32,
-    kUint32,
-    kInt32,
-  };
-
-  /// Type of the scalar
-  Type type;
-
-  /// Does this pipeline overridable constant have an initializer?
-  bool is_initialized = false;
-
-  /// Does this pipeline overridable constant have a numeric ID specified
-  /// explicitly?
-  bool is_numeric_id_specified = false;
-};
-
-/// Reflection data for an entry point in the shader.
-struct EntryPoint {
-  /// Constructors
-  EntryPoint();
-  /// Copy Constructor
-  EntryPoint(EntryPoint&);
-  /// Move Constructor
-  EntryPoint(EntryPoint&&);
-  ~EntryPoint();
-
-  /// The entry point name
-  std::string name;
-  /// Remapped entry point name in the backend
-  std::string remapped_name;
-  /// The entry point stage
-  ast::PipelineStage stage = ast::PipelineStage::kNone;
-  /// The workgroup x size
-  uint32_t workgroup_size_x = 0;
-  /// The workgroup y size
-  uint32_t workgroup_size_y = 0;
-  /// The workgroup z size
-  uint32_t workgroup_size_z = 0;
-  /// List of the input variable accessed via this entry point.
-  std::vector<StageVariable> input_variables;
-  /// List of the output variable accessed via this entry point.
-  std::vector<StageVariable> output_variables;
-  /// List of the pipeline overridable constants accessed via this entry point.
-  std::vector<OverridableConstant> overridable_constants;
-  /// Does the entry point use the sample_mask builtin as an input builtin
-  /// variable.
-  bool input_sample_mask_used = false;
-  /// Does the entry point use the sample_mask builtin as an output builtin
-  /// variable.
-  bool output_sample_mask_used = false;
-  /// Does the entry point use the position builtin as an input builtin
-  /// variable.
-  bool input_position_used = false;
-  /// Does the entry point use the front_facing builtin
-  bool front_facing_used = false;
-  /// Does the entry point use the sample_index builtin
-  bool sample_index_used = false;
-  /// Does the entry point use the num_workgroups builtin
-  bool num_workgroups_used = false;
-
-  /// @returns the size of the workgroup in {x,y,z} format
-  std::tuple<uint32_t, uint32_t, uint32_t> workgroup_size() {
-    return std::tuple<uint32_t, uint32_t, uint32_t>(
-        workgroup_size_x, workgroup_size_y, workgroup_size_z);
-  }
-};
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_ENTRY_POINT_H_
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
deleted file mode 100644
index 975e73d..0000000
--- a/src/inspector/inspector.cc
+++ /dev/null
@@ -1,969 +0,0 @@
-// Copyright 2020 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/inspector/inspector.h"
-
-#include <limits>
-#include <utility>
-
-#include "src/ast/bool_literal_expression.h"
-#include "src/ast/call_expression.h"
-#include "src/ast/float_literal_expression.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/location_attribute.h"
-#include "src/ast/module.h"
-#include "src/ast/sint_literal_expression.h"
-#include "src/ast/uint_literal_expression.h"
-#include "src/sem/array.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/function.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/matrix_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/u32_type.h"
-#include "src/sem/variable.h"
-#include "src/sem/vector_type.h"
-#include "src/sem/void_type.h"
-#include "src/utils/math.h"
-#include "src/utils/unique_vector.h"
-
-namespace tint {
-namespace inspector {
-
-namespace {
-
-void AppendResourceBindings(std::vector<ResourceBinding>* dest,
-                            const std::vector<ResourceBinding>& orig) {
-  TINT_ASSERT(Inspector, dest);
-  if (!dest) {
-    return;
-  }
-
-  dest->reserve(dest->size() + orig.size());
-  dest->insert(dest->end(), orig.begin(), orig.end());
-}
-
-std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(
-    const sem::Type* type) {
-  if (type->is_float_scalar()) {
-    return {ComponentType::kFloat, CompositionType::kScalar};
-  } else if (type->is_float_vector()) {
-    auto* vec = type->As<sem::Vector>();
-    if (vec->Width() == 2) {
-      return {ComponentType::kFloat, CompositionType::kVec2};
-    } else if (vec->Width() == 3) {
-      return {ComponentType::kFloat, CompositionType::kVec3};
-    } else if (vec->Width() == 4) {
-      return {ComponentType::kFloat, CompositionType::kVec4};
-    }
-  } else if (type->is_unsigned_integer_scalar()) {
-    return {ComponentType::kUInt, CompositionType::kScalar};
-  } else if (type->is_unsigned_integer_vector()) {
-    auto* vec = type->As<sem::Vector>();
-    if (vec->Width() == 2) {
-      return {ComponentType::kUInt, CompositionType::kVec2};
-    } else if (vec->Width() == 3) {
-      return {ComponentType::kUInt, CompositionType::kVec3};
-    } else if (vec->Width() == 4) {
-      return {ComponentType::kUInt, CompositionType::kVec4};
-    }
-  } else if (type->is_signed_integer_scalar()) {
-    return {ComponentType::kSInt, CompositionType::kScalar};
-  } else if (type->is_signed_integer_vector()) {
-    auto* vec = type->As<sem::Vector>();
-    if (vec->Width() == 2) {
-      return {ComponentType::kSInt, CompositionType::kVec2};
-    } else if (vec->Width() == 3) {
-      return {ComponentType::kSInt, CompositionType::kVec3};
-    } else if (vec->Width() == 4) {
-      return {ComponentType::kSInt, CompositionType::kVec4};
-    }
-  }
-  return {ComponentType::kUnknown, CompositionType::kUnknown};
-}
-
-std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
-    const sem::Type* type,
-    const ast::AttributeList& attributes) {
-  auto* interpolation_attribute =
-      ast::GetAttribute<ast::InterpolateAttribute>(attributes);
-  if (type->is_integer_scalar_or_vector()) {
-    return {InterpolationType::kFlat, InterpolationSampling::kNone};
-  }
-
-  if (!interpolation_attribute) {
-    return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
-  }
-
-  auto interpolation_type = interpolation_attribute->type;
-  auto sampling = interpolation_attribute->sampling;
-  if (interpolation_type != ast::InterpolationType::kFlat &&
-      sampling == ast::InterpolationSampling::kNone) {
-    sampling = ast::InterpolationSampling::kCenter;
-  }
-  return {ASTToInspectorInterpolationType(interpolation_type),
-          ASTToInspectorInterpolationSampling(sampling)};
-}
-
-}  // namespace
-
-Inspector::Inspector(const Program* program) : program_(program) {}
-
-Inspector::~Inspector() = default;
-
-std::vector<EntryPoint> Inspector::GetEntryPoints() {
-  std::vector<EntryPoint> result;
-
-  for (auto* func : program_->AST().Functions()) {
-    if (!func->IsEntryPoint()) {
-      continue;
-    }
-
-    auto* sem = program_->Sem().Get(func);
-
-    EntryPoint entry_point;
-    entry_point.name = program_->Symbols().NameFor(func->symbol);
-    entry_point.remapped_name = program_->Symbols().NameFor(func->symbol);
-    entry_point.stage = func->PipelineStage();
-
-    auto wgsize = sem->WorkgroupSize();
-    entry_point.workgroup_size_x = wgsize[0].value;
-    entry_point.workgroup_size_y = wgsize[1].value;
-    entry_point.workgroup_size_z = wgsize[2].value;
-    if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
-        wgsize[2].overridable_const) {
-      // TODO(crbug.com/tint/713): Handle overridable constants.
-      TINT_ASSERT(Inspector, false);
-    }
-
-    for (auto* param : sem->Parameters()) {
-      AddEntryPointInOutVariables(
-          program_->Symbols().NameFor(param->Declaration()->symbol),
-          param->Type(), param->Declaration()->attributes,
-          entry_point.input_variables);
-
-      entry_point.input_position_used |=
-          ContainsBuiltin(ast::Builtin::kPosition, param->Type(),
-                          param->Declaration()->attributes);
-      entry_point.front_facing_used |=
-          ContainsBuiltin(ast::Builtin::kFrontFacing, param->Type(),
-                          param->Declaration()->attributes);
-      entry_point.sample_index_used |=
-          ContainsBuiltin(ast::Builtin::kSampleIndex, param->Type(),
-                          param->Declaration()->attributes);
-      entry_point.input_sample_mask_used |=
-          ContainsBuiltin(ast::Builtin::kSampleMask, param->Type(),
-                          param->Declaration()->attributes);
-      entry_point.num_workgroups_used |=
-          ContainsBuiltin(ast::Builtin::kNumWorkgroups, param->Type(),
-                          param->Declaration()->attributes);
-    }
-
-    if (!sem->ReturnType()->Is<sem::Void>()) {
-      AddEntryPointInOutVariables("<retval>", sem->ReturnType(),
-                                  func->return_type_attributes,
-                                  entry_point.output_variables);
-
-      entry_point.output_sample_mask_used =
-          ContainsBuiltin(ast::Builtin::kSampleMask, sem->ReturnType(),
-                          func->return_type_attributes);
-    }
-
-    for (auto* var : sem->TransitivelyReferencedGlobals()) {
-      auto* decl = var->Declaration();
-
-      auto name = program_->Symbols().NameFor(decl->symbol);
-
-      auto* global = var->As<sem::GlobalVariable>();
-      if (global && global->IsOverridable()) {
-        OverridableConstant overridable_constant;
-        overridable_constant.name = name;
-        overridable_constant.numeric_id = global->ConstantId();
-        auto* type = var->Type();
-        TINT_ASSERT(Inspector, type->is_scalar());
-        if (type->is_bool_scalar_or_vector()) {
-          overridable_constant.type = OverridableConstant::Type::kBool;
-        } else if (type->is_float_scalar()) {
-          overridable_constant.type = OverridableConstant::Type::kFloat32;
-        } else if (type->is_signed_integer_scalar()) {
-          overridable_constant.type = OverridableConstant::Type::kInt32;
-        } else if (type->is_unsigned_integer_scalar()) {
-          overridable_constant.type = OverridableConstant::Type::kUint32;
-        } else {
-          TINT_UNREACHABLE(Inspector, diagnostics_);
-        }
-
-        overridable_constant.is_initialized =
-            global->Declaration()->constructor;
-        overridable_constant.is_numeric_id_specified =
-            ast::HasAttribute<ast::IdAttribute>(
-                global->Declaration()->attributes);
-
-        entry_point.overridable_constants.push_back(overridable_constant);
-      }
-    }
-
-    result.push_back(std::move(entry_point));
-  }
-
-  return result;
-}
-
-std::string Inspector::GetRemappedNameForEntryPoint(
-    const std::string& entry_point) {
-  // TODO(rharrison): Reenable once all of the backends are using the renamed
-  //                  entry points.
-
-  //  auto* func = FindEntryPointByName(entry_point);
-  //  if (!func) {
-  //    return {};
-  //  }
-  //  return func->name();
-  return entry_point;
-}
-
-std::map<uint32_t, Scalar> Inspector::GetConstantIDs() {
-  std::map<uint32_t, Scalar> result;
-  for (auto* var : program_->AST().GlobalVariables()) {
-    auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
-    if (!global || !global->IsOverridable()) {
-      continue;
-    }
-
-    // If there are conflicting defintions for a constant id, that is invalid
-    // WGSL, so the resolver should catch it. Thus here the inspector just
-    // assumes all definitions of the constant id are the same, so only needs
-    // to find the first reference to constant id.
-    uint32_t constant_id = global->ConstantId();
-    if (result.find(constant_id) != result.end()) {
-      continue;
-    }
-
-    if (!var->constructor) {
-      result[constant_id] = Scalar();
-      continue;
-    }
-
-    auto* literal = var->constructor->As<ast::LiteralExpression>();
-    if (!literal) {
-      // This is invalid WGSL, but handling gracefully.
-      result[constant_id] = Scalar();
-      continue;
-    }
-
-    if (auto* l = literal->As<ast::BoolLiteralExpression>()) {
-      result[constant_id] = Scalar(l->value);
-      continue;
-    }
-
-    if (auto* l = literal->As<ast::UintLiteralExpression>()) {
-      result[constant_id] = Scalar(l->value);
-      continue;
-    }
-
-    if (auto* l = literal->As<ast::SintLiteralExpression>()) {
-      result[constant_id] = Scalar(l->value);
-      continue;
-    }
-
-    if (auto* l = literal->As<ast::FloatLiteralExpression>()) {
-      result[constant_id] = Scalar(l->value);
-      continue;
-    }
-
-    result[constant_id] = Scalar();
-  }
-
-  return result;
-}
-
-std::map<std::string, uint32_t> Inspector::GetConstantNameToIdMap() {
-  std::map<std::string, uint32_t> result;
-  for (auto* var : program_->AST().GlobalVariables()) {
-    auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
-    if (global && global->IsOverridable()) {
-      auto name = program_->Symbols().NameFor(var->symbol);
-      result[name] = global->ConstantId();
-    }
-  }
-  return result;
-}
-
-uint32_t Inspector::GetStorageSize(const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return 0;
-  }
-
-  size_t size = 0;
-  auto* func_sem = program_->Sem().Get(func);
-  for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
-    size += ruv.first->Type()->UnwrapRef()->Size();
-  }
-  for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
-    size += rsv.first->Type()->UnwrapRef()->Size();
-  }
-
-  if (static_cast<uint64_t>(size) >
-      static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
-    return std::numeric_limits<uint32_t>::max();
-  }
-  return static_cast<uint32_t>(size);
-}
-
-std::vector<ResourceBinding> Inspector::GetResourceBindings(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-  for (auto fn : {
-           &Inspector::GetUniformBufferResourceBindings,
-           &Inspector::GetStorageBufferResourceBindings,
-           &Inspector::GetReadOnlyStorageBufferResourceBindings,
-           &Inspector::GetSamplerResourceBindings,
-           &Inspector::GetComparisonSamplerResourceBindings,
-           &Inspector::GetSampledTextureResourceBindings,
-           &Inspector::GetMultisampledTextureResourceBindings,
-           &Inspector::GetWriteOnlyStorageTextureResourceBindings,
-           &Inspector::GetDepthTextureResourceBindings,
-           &Inspector::GetDepthMultisampledTextureResourceBindings,
-           &Inspector::GetExternalTextureResourceBindings,
-       }) {
-    AppendResourceBindings(&result, (this->*fn)(entry_point));
-  }
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetUniformBufferResourceBindings(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-
-  auto* func_sem = program_->Sem().Get(func);
-  for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
-    auto* var = ruv.first;
-    auto binding_info = ruv.second;
-
-    auto* unwrapped_type = var->Type()->UnwrapRef();
-
-    ResourceBinding entry;
-    entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-    entry.size = unwrapped_type->Size();
-    entry.size_no_padding = entry.size;
-    if (auto* str = unwrapped_type->As<sem::Struct>()) {
-      entry.size_no_padding = str->SizeNoPadding();
-    } else {
-      entry.size_no_padding = entry.size;
-    }
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings(
-    const std::string& entry_point) {
-  return GetStorageBufferResourceBindingsImpl(entry_point, false);
-}
-
-std::vector<ResourceBinding>
-Inspector::GetReadOnlyStorageBufferResourceBindings(
-    const std::string& entry_point) {
-  return GetStorageBufferResourceBindingsImpl(entry_point, true);
-}
-
-std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-
-  auto* func_sem = program_->Sem().Get(func);
-  for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) {
-    auto binding_info = rs.second;
-
-    ResourceBinding entry;
-    entry.resource_type = ResourceBinding::ResourceType::kSampler;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-
-  auto* func_sem = program_->Sem().Get(func);
-  for (auto& rcs :
-       func_sem->TransitivelyReferencedComparisonSamplerVariables()) {
-    auto binding_info = rcs.second;
-
-    ResourceBinding entry;
-    entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetSampledTextureResourceBindingsImpl(entry_point, false);
-}
-
-std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetSampledTextureResourceBindingsImpl(entry_point, true);
-}
-
-std::vector<ResourceBinding>
-Inspector::GetWriteOnlyStorageTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetStorageTextureResourceBindingsImpl(entry_point);
-}
-
-std::vector<ResourceBinding> Inspector::GetTextureResourceBindings(
-    const std::string& entry_point,
-    const tint::TypeInfo* texture_type,
-    ResourceBinding::ResourceType resource_type) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-  auto* func_sem = program_->Sem().Get(func);
-  for (auto& ref :
-       func_sem->TransitivelyReferencedVariablesOfType(texture_type)) {
-    auto* var = ref.first;
-    auto binding_info = ref.second;
-
-    ResourceBinding entry;
-    entry.resource_type = resource_type;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-
-    auto* tex = var->Type()->UnwrapRef()->As<sem::Texture>();
-    entry.dim =
-        TypeTextureDimensionToResourceBindingTextureDimension(tex->dim());
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetTextureResourceBindings(
-      entry_point, &TypeInfo::Of<sem::DepthTexture>(),
-      ResourceBinding::ResourceType::kDepthTexture);
-}
-
-std::vector<ResourceBinding>
-Inspector::GetDepthMultisampledTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetTextureResourceBindings(
-      entry_point, &TypeInfo::Of<sem::DepthMultisampledTexture>(),
-      ResourceBinding::ResourceType::kDepthMultisampledTexture);
-}
-
-std::vector<ResourceBinding> Inspector::GetExternalTextureResourceBindings(
-    const std::string& entry_point) {
-  return GetTextureResourceBindings(
-      entry_point, &TypeInfo::Of<sem::ExternalTexture>(),
-      ResourceBinding::ResourceType::kExternalTexture);
-}
-
-std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  GenerateSamplerTargets();
-
-  auto it = sampler_targets_->find(entry_point);
-  if (it == sampler_targets_->end()) {
-    return {};
-  }
-  return it->second;
-}
-
-std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses(
-    const std::string& entry_point,
-    const sem::BindingPoint& placeholder) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-  auto* func_sem = program_->Sem().Get(func);
-
-  std::vector<sem::SamplerTexturePair> new_pairs;
-  for (auto pair : func_sem->TextureSamplerPairs()) {
-    auto* texture = pair.first->As<sem::GlobalVariable>();
-    auto* sampler =
-        pair.second ? pair.second->As<sem::GlobalVariable>() : nullptr;
-    SamplerTexturePair new_pair;
-    new_pair.sampler_binding_point =
-        sampler ? sampler->BindingPoint() : placeholder;
-    new_pair.texture_binding_point = texture->BindingPoint();
-    new_pairs.push_back(new_pair);
-  }
-  return new_pairs;
-}
-
-uint32_t Inspector::GetWorkgroupStorageSize(const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return 0;
-  }
-
-  uint32_t total_size = 0;
-  auto* func_sem = program_->Sem().Get(func);
-  for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) {
-    if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
-      auto* ty = var->Type()->UnwrapRef();
-      uint32_t align = ty->Align();
-      uint32_t size = ty->Size();
-
-      // This essentially matches std430 layout rules from GLSL, which are in
-      // turn specified as an upper bound for Vulkan layout sizing. Since D3D
-      // and Metal are even less specific, we assume Vulkan behavior as a
-      // good-enough approximation everywhere.
-      total_size += utils::RoundUp(align, size);
-    }
-  }
-
-  return total_size;
-}
-
-const ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
-  auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name));
-  if (!func) {
-    diagnostics_.add_error(diag::System::Inspector, name + " was not found!");
-    return nullptr;
-  }
-
-  if (!func->IsEntryPoint()) {
-    diagnostics_.add_error(diag::System::Inspector,
-                           name + " is not an entry point!");
-    return nullptr;
-  }
-
-  return func;
-}
-
-void Inspector::AddEntryPointInOutVariables(
-    std::string name,
-    const sem::Type* type,
-    const ast::AttributeList& attributes,
-    std::vector<StageVariable>& variables) const {
-  // Skip builtins.
-  if (ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
-    return;
-  }
-
-  auto* unwrapped_type = type->UnwrapRef();
-
-  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, variables);
-    }
-    return;
-  }
-
-  // Base case: add the variable.
-
-  StageVariable stage_variable;
-  stage_variable.name = name;
-  std::tie(stage_variable.component_type, stage_variable.composition_type) =
-      CalculateComponentAndComposition(type);
-
-  auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes);
-  TINT_ASSERT(Inspector, location != nullptr);
-  stage_variable.has_location_attribute = true;
-  stage_variable.location_attribute = location->value;
-
-  std::tie(stage_variable.interpolation_type,
-           stage_variable.interpolation_sampling) =
-      CalculateInterpolationData(type, attributes);
-
-  variables.push_back(stage_variable);
-}
-
-bool Inspector::ContainsBuiltin(ast::Builtin builtin,
-                                const sem::Type* type,
-                                const ast::AttributeList& attributes) const {
-  auto* unwrapped_type = type->UnwrapRef();
-
-  if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
-    // Recurse into members.
-    for (auto* member : struct_ty->Members()) {
-      if (ContainsBuiltin(builtin, member->Type(),
-                          member->Declaration()->attributes)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  // Base case: check for builtin
-  auto* builtin_declaration =
-      ast::GetAttribute<ast::BuiltinAttribute>(attributes);
-  if (!builtin_declaration || builtin_declaration->builtin != builtin) {
-    return false;
-  }
-
-  return true;
-}
-
-std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl(
-    const std::string& entry_point,
-    bool read_only) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  auto* func_sem = program_->Sem().Get(func);
-  std::vector<ResourceBinding> result;
-  for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
-    auto* var = rsv.first;
-    auto binding_info = rsv.second;
-
-    if (read_only != (var->Access() == ast::Access::kRead)) {
-      continue;
-    }
-
-    auto* unwrapped_type = var->Type()->UnwrapRef();
-
-    ResourceBinding entry;
-    entry.resource_type =
-        read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer
-                  : ResourceBinding::ResourceType::kStorageBuffer;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-    entry.size = unwrapped_type->Size();
-    if (auto* str = unwrapped_type->As<sem::Struct>()) {
-      entry.size_no_padding = str->SizeNoPadding();
-    } else {
-      entry.size_no_padding = entry.size;
-    }
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl(
-    const std::string& entry_point,
-    bool multisampled_only) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  std::vector<ResourceBinding> result;
-  auto* func_sem = program_->Sem().Get(func);
-  auto referenced_variables =
-      multisampled_only
-          ? func_sem->TransitivelyReferencedMultisampledTextureVariables()
-          : func_sem->TransitivelyReferencedSampledTextureVariables();
-  for (auto& ref : referenced_variables) {
-    auto* var = ref.first;
-    auto binding_info = ref.second;
-
-    ResourceBinding entry;
-    entry.resource_type =
-        multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture
-                          : ResourceBinding::ResourceType::kSampledTexture;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-
-    auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>();
-    entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
-        texture_type->dim());
-
-    const sem::Type* base_type = nullptr;
-    if (multisampled_only) {
-      base_type = texture_type->As<sem::MultisampledTexture>()->type();
-    } else {
-      base_type = texture_type->As<sem::SampledTexture>()->type();
-    }
-    entry.sampled_kind = BaseTypeToSampledKind(base_type);
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl(
-    const std::string& entry_point) {
-  auto* func = FindEntryPointByName(entry_point);
-  if (!func) {
-    return {};
-  }
-
-  auto* func_sem = program_->Sem().Get(func);
-  std::vector<ResourceBinding> result;
-  for (auto& ref :
-       func_sem->TransitivelyReferencedVariablesOfType<sem::StorageTexture>()) {
-    auto* var = ref.first;
-    auto binding_info = ref.second;
-
-    auto* texture_type = var->Type()->UnwrapRef()->As<sem::StorageTexture>();
-
-    ResourceBinding entry;
-    entry.resource_type =
-        ResourceBinding::ResourceType::kWriteOnlyStorageTexture;
-    entry.bind_group = binding_info.group->value;
-    entry.binding = binding_info.binding->value;
-
-    entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
-        texture_type->dim());
-
-    auto* base_type = texture_type->type();
-    entry.sampled_kind = BaseTypeToSampledKind(base_type);
-    entry.image_format = TypeTexelFormatToResourceBindingTexelFormat(
-        texture_type->texel_format());
-
-    result.push_back(entry);
-  }
-
-  return result;
-}
-
-void Inspector::GenerateSamplerTargets() {
-  // Do not re-generate, since |program_| should not change during the lifetime
-  // of the inspector.
-  if (sampler_targets_ != nullptr) {
-    return;
-  }
-
-  sampler_targets_ = std::make_unique<std::unordered_map<
-      std::string, utils::UniqueVector<sem::SamplerTexturePair>>>();
-
-  auto& sem = program_->Sem();
-
-  for (auto* node : program_->ASTNodes().Objects()) {
-    auto* c = node->As<ast::CallExpression>();
-    if (!c) {
-      continue;
-    }
-
-    auto* call = sem.Get(c);
-    if (!call) {
-      continue;
-    }
-
-    auto* i = call->Target()->As<sem::Builtin>();
-    if (!i) {
-      continue;
-    }
-
-    const auto& signature = i->Signature();
-    int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
-    if (sampler_index == -1) {
-      continue;
-    }
-
-    int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
-    if (texture_index == -1) {
-      continue;
-    }
-
-    auto* call_func = call->Stmt()->Function();
-    std::vector<const sem::Function*> entry_points;
-    if (call_func->Declaration()->IsEntryPoint()) {
-      entry_points = {call_func};
-    } else {
-      entry_points = call_func->AncestorEntryPoints();
-    }
-
-    if (entry_points.empty()) {
-      continue;
-    }
-
-    auto* t = c->args[texture_index];
-    auto* s = c->args[sampler_index];
-
-    GetOriginatingResources(
-        std::array<const ast::Expression*, 2>{t, s},
-        [&](std::array<const sem::GlobalVariable*, 2> globals) {
-          auto* texture = globals[0];
-          sem::BindingPoint texture_binding_point = {
-              texture->Declaration()->BindingPoint().group->value,
-              texture->Declaration()->BindingPoint().binding->value};
-
-          auto* sampler = globals[1];
-          sem::BindingPoint sampler_binding_point = {
-              sampler->Declaration()->BindingPoint().group->value,
-              sampler->Declaration()->BindingPoint().binding->value};
-
-          for (auto* entry_point : entry_points) {
-            const auto& ep_name =
-                program_->Symbols().NameFor(entry_point->Declaration()->symbol);
-            (*sampler_targets_)[ep_name].add(
-                {sampler_binding_point, texture_binding_point});
-          }
-        });
-  }
-}
-
-template <size_t N, typename F>
-void Inspector::GetOriginatingResources(
-    std::array<const ast::Expression*, N> exprs,
-    F&& callback) {
-  if (!program_->IsValid()) {
-    TINT_ICE(Inspector, diagnostics_)
-        << "attempting to get originating resources in invalid program";
-    return;
-  }
-
-  auto& sem = program_->Sem();
-
-  std::array<const sem::GlobalVariable*, N> globals{};
-  std::array<const sem::Parameter*, N> parameters{};
-  utils::UniqueVector<const ast::CallExpression*> callsites;
-
-  for (size_t i = 0; i < N; i++) {
-    auto*& expr = exprs[i];
-    // Resolve each of the expressions
-    while (true) {
-      if (auto* user = sem.Get<sem::VariableUser>(expr)) {
-        auto* var = user->Variable();
-
-        if (auto* global = tint::As<sem::GlobalVariable>(var)) {
-          // Found the global resource declaration.
-          globals[i] = global;
-          break;  // Done with this expression.
-        }
-
-        if (auto* local = tint::As<sem::LocalVariable>(var)) {
-          // Chase the variable
-          expr = local->Declaration()->constructor;
-          if (!expr) {
-            TINT_ICE(Inspector, diagnostics_)
-                << "resource variable had no initializer";
-            return;
-          }
-          continue;  // Continue chasing the expression in this function
-        }
-
-        if (auto* param = tint::As<sem::Parameter>(var)) {
-          // Gather each of the callers of this function
-          auto* func = tint::As<sem::Function>(param->Owner());
-          if (func->CallSites().empty()) {
-            // One or more of the expressions is a parameter, but this function
-            // is not called. Ignore.
-            return;
-          }
-          for (auto* call : func->CallSites()) {
-            callsites.add(call->Declaration());
-          }
-          // Need to evaluate each function call with the group of
-          // expressions, so move on to the next expression.
-          parameters[i] = param;
-          break;
-        }
-
-        TINT_ICE(Inspector, diagnostics_)
-            << "unexpected variable type " << var->TypeInfo().name;
-      }
-
-      if (auto* unary = tint::As<ast::UnaryOpExpression>(expr)) {
-        switch (unary->op) {
-          case ast::UnaryOp::kAddressOf:
-          case ast::UnaryOp::kIndirection:
-            // `*` and `&` are the only valid unary ops for a resource type,
-            // and must be balanced in order for the program to have passed
-            // validation. Just skip past these.
-            expr = unary->expr;
-            continue;
-          default: {
-            TINT_ICE(Inspector, diagnostics_)
-                << "unexpected unary op on resource: " << unary->op;
-            return;
-          }
-        }
-      }
-
-      TINT_ICE(Inspector, diagnostics_)
-          << "cannot resolve originating resource with expression type "
-          << expr->TypeInfo().name;
-      return;
-    }
-  }
-
-  if (callsites.size()) {
-    for (auto* call_expr : callsites) {
-      // Make a copy of the expressions for this callsite
-      std::array<const ast::Expression*, N> call_exprs = exprs;
-      // Patch all the parameter expressions with their argument
-      for (size_t i = 0; i < N; i++) {
-        if (auto* param = parameters[i]) {
-          call_exprs[i] = call_expr->args[param->Index()];
-        }
-      }
-      // Now call GetOriginatingResources() with from the callsite
-      GetOriginatingResources(call_exprs, callback);
-    }
-  } else {
-    // All the expressions resolved to globals
-    callback(globals);
-  }
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/inspector.h b/src/inspector/inspector.h
deleted file mode 100644
index 4bfac51..0000000
--- a/src/inspector/inspector.h
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 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_INSPECTOR_INSPECTOR_H_
-#define SRC_INSPECTOR_INSPECTOR_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <unordered_map>
-#include <vector>
-
-#include "src/inspector/entry_point.h"
-#include "src/inspector/resource_binding.h"
-#include "src/inspector/scalar.h"
-#include "src/program.h"
-#include "src/sem/sampler_texture_pair.h"
-#include "src/utils/unique_vector.h"
-
-namespace tint {
-namespace inspector {
-
-/// A temporary alias to sem::SamplerTexturePair. [DEPRECATED]
-using SamplerTexturePair = sem::SamplerTexturePair;
-
-/// Extracts information from a program
-class Inspector {
- public:
-  /// Constructor
-  /// @param program Shader program to extract information from.
-  explicit Inspector(const Program* program);
-
-  /// Destructor
-  ~Inspector();
-
-  /// @returns error messages from the Inspector
-  std::string error() { return diagnostics_.str(); }
-  /// @returns true if an error was encountered
-  bool has_error() const { return diagnostics_.contains_errors(); }
-
-  /// @returns vector of entry point information
-  std::vector<EntryPoint> GetEntryPoints();
-
-  /// @param entry_point name of the entry point to get the remapped version of
-  /// @returns the remapped name of the entry point, or the empty string if it
-  ///          isn't a known entry point.
-  std::string GetRemappedNameForEntryPoint(const std::string& entry_point);
-
-  /// @returns map of const_id to initial value
-  std::map<uint32_t, Scalar> GetConstantIDs();
-
-  /// @returns map of module-constant name to pipeline constant ID
-  std::map<std::string, uint32_t> GetConstantNameToIdMap();
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns the total size of shared storage required by an entry point,
-  ///          including all uniform storage buffers.
-  uint32_t GetStorageSize(const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the resource bindings.
-  std::vector<ResourceBinding> GetResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for uniform buffers.
-  std::vector<ResourceBinding> GetUniformBufferResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for storage buffers.
-  std::vector<ResourceBinding> GetStorageBufferResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for read-only storage buffers.
-  std::vector<ResourceBinding> GetReadOnlyStorageBufferResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for regular samplers.
-  std::vector<ResourceBinding> GetSamplerResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for comparison samplers.
-  std::vector<ResourceBinding> GetComparisonSamplerResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for sampled textures.
-  std::vector<ResourceBinding> GetSampledTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for multisampled textures.
-  std::vector<ResourceBinding> GetMultisampledTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for write-only storage textures.
-  std::vector<ResourceBinding> GetWriteOnlyStorageTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for depth textures.
-  std::vector<ResourceBinding> GetDepthTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for depth textures.
-  std::vector<ResourceBinding> GetDepthMultisampledTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for external textures.
-  std::vector<ResourceBinding> GetExternalTextureResourceBindings(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the sampler/texture sampling pairs that are used
-  /// by that entry point.
-  std::vector<sem::SamplerTexturePair> GetSamplerTextureUses(
-      const std::string& entry_point);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @param placeholder the sampler binding point to use for texture-only
-  /// access (e.g., textureLoad)
-  /// @returns vector of all of the sampler/texture sampling pairs that are used
-  /// by that entry point.
-  std::vector<sem::SamplerTexturePair> GetSamplerTextureUses(
-      const std::string& entry_point,
-      const sem::BindingPoint& placeholder);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns the total size in bytes of all Workgroup storage-class storage
-  /// referenced transitively by the entry point.
-  uint32_t GetWorkgroupStorageSize(const std::string& entry_point);
-
- private:
-  const Program* program_;
-  diag::List diagnostics_;
-  std::unique_ptr<
-      std::unordered_map<std::string,
-                         utils::UniqueVector<sem::SamplerTexturePair>>>
-      sampler_targets_;
-
-  /// @param name name of the entry point to find
-  /// @returns a pointer to the entry point if it exists, otherwise returns
-  ///          nullptr and sets the error string.
-  const ast::Function* FindEntryPointByName(const std::string& name);
-
-  /// Recursively add entry point IO variables.
-  /// If `type` is a struct, recurse into members, appending the member name.
-  /// Otherwise, add the variable unless it is a builtin.
-  /// @param name the name of the variable being added
-  /// @param type the type of the variable
-  /// @param attributes the variable attributes
-  /// @param variables the list to add the variables to
-  void AddEntryPointInOutVariables(std::string name,
-                                   const sem::Type* type,
-                                   const ast::AttributeList& attributes,
-                                   std::vector<StageVariable>& variables) const;
-
-  /// Recursively determine if the type contains builtin.
-  /// If `type` is a struct, recurse into members to check for the attribute.
-  /// Otherwise, check `attributes` for the attribute.
-  bool ContainsBuiltin(ast::Builtin builtin,
-                       const sem::Type* type,
-                       const ast::AttributeList& attributes) const;
-
-  /// Gathers all the texture resource bindings of the given type for the given
-  /// entry point.
-  /// @param entry_point name of the entry point to get information about.
-  /// @param texture_type the type of the textures to gather.
-  /// @param resource_type the ResourceBinding::ResourceType for the given
-  /// texture type.
-  /// @returns vector of all of the bindings for depth textures.
-  std::vector<ResourceBinding> GetTextureResourceBindings(
-      const std::string& entry_point,
-      const tint::TypeInfo* texture_type,
-      ResourceBinding::ResourceType resource_type);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @param read_only if true get only read-only bindings, if false get
-  ///                  write-only bindings.
-  /// @returns vector of all of the bindings for the requested storage buffers.
-  std::vector<ResourceBinding> GetStorageBufferResourceBindingsImpl(
-      const std::string& entry_point,
-      bool read_only);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @param multisampled_only only get multisampled textures if true, otherwise
-  ///                          only get sampled textures.
-  /// @returns vector of all of the bindings for the request storage buffers.
-  std::vector<ResourceBinding> GetSampledTextureResourceBindingsImpl(
-      const std::string& entry_point,
-      bool multisampled_only);
-
-  /// @param entry_point name of the entry point to get information about.
-  /// @returns vector of all of the bindings for the requested storage textures.
-  std::vector<ResourceBinding> GetStorageTextureResourceBindingsImpl(
-      const std::string& entry_point);
-
-  /// Constructs |sampler_targets_| if it hasn't already been instantiated.
-  void GenerateSamplerTargets();
-
-  /// For a N-uple of expressions, resolve to the appropriate global resources
-  /// and call 'cb'.
-  /// 'cb' may be called multiple times.
-  /// Assumes that not being able to resolve the resources is an error, so will
-  /// invoke TINT_ICE when that occurs.
-  /// @tparam N number of expressions in the n-uple
-  /// @tparam F type of the callback provided.
-  /// @param exprs N-uple of expressions to resolve.
-  /// @param cb is a callback function with the signature:
-  /// `void(std::array<const sem::GlobalVariable*, N>)`, which is invoked
-  /// whenever a set of expressions are resolved to globals.
-  template <size_t N, typename F>
-  void GetOriginatingResources(std::array<const ast::Expression*, N> exprs,
-                               F&& cb);
-};
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_INSPECTOR_H_
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
deleted file mode 100644
index f1a4232..0000000
--- a/src/inspector/inspector_test.cc
+++ /dev/null
@@ -1,3105 +0,0 @@
-// Copyright 2020 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 "gtest/gtest.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/inspector/test_inspector_builder.h"
-#include "src/inspector/test_inspector_runner.h"
-#include "src/program_builder.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/variable.h"
-#include "tint/tint.h"
-
-namespace tint {
-namespace inspector {
-namespace {
-
-// All the tests that descend from InspectorBuilder are expected to define their
-// test state via building up the AST through InspectorBuilder and then generate
-// the program with ::Build.
-// The returned Inspector from ::Build can then be used to test expecations.
-//
-// All the tests that descend from InspectorRunner are expected to define their
-// test state via a WGSL shader, which will be parsed to generate a Program and
-// Inspector in ::Initialize.
-// The returned Inspector from ::Initialize can then be used to test
-// expecations.
-
-class InspectorGetEntryPointTest : public InspectorBuilder,
-                                   public testing::Test {};
-
-typedef std::tuple<inspector::ComponentType, inspector::CompositionType>
-    InspectorGetEntryPointComponentAndCompositionTestParams;
-class InspectorGetEntryPointComponentAndCompositionTest
-    : public InspectorBuilder,
-      public testing::TestWithParam<
-          InspectorGetEntryPointComponentAndCompositionTestParams> {};
-struct InspectorGetEntryPointInterpolateTestParams {
-  ast::InterpolationType in_type;
-  ast::InterpolationSampling in_sampling;
-  inspector::InterpolationType out_type;
-  inspector::InterpolationSampling out_sampling;
-};
-class InspectorGetEntryPointInterpolateTest
-    : public InspectorBuilder,
-      public testing::TestWithParam<
-          InspectorGetEntryPointInterpolateTestParams> {};
-class InspectorGetRemappedNameForEntryPointTest : public InspectorBuilder,
-                                                  public testing::Test {};
-class InspectorGetConstantIDsTest : public InspectorBuilder,
-                                    public testing::Test {};
-class InspectorGetConstantNameToIdMapTest : public InspectorBuilder,
-                                            public testing::Test {};
-class InspectorGetStorageSizeTest : public InspectorBuilder,
-                                    public testing::Test {};
-class InspectorGetResourceBindingsTest : public InspectorBuilder,
-                                         public testing::Test {};
-class InspectorGetUniformBufferResourceBindingsTest : public InspectorBuilder,
-                                                      public testing::Test {};
-class InspectorGetStorageBufferResourceBindingsTest : public InspectorBuilder,
-                                                      public testing::Test {};
-class InspectorGetReadOnlyStorageBufferResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-class InspectorGetSamplerResourceBindingsTest : public InspectorBuilder,
-                                                public testing::Test {};
-class InspectorGetComparisonSamplerResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-class InspectorGetSampledTextureResourceBindingsTest : public InspectorBuilder,
-                                                       public testing::Test {};
-class InspectorGetSampledArrayTextureResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-struct GetSampledTextureTestParams {
-  ast::TextureDimension type_dim;
-  inspector::ResourceBinding::TextureDimension inspector_dim;
-  inspector::ResourceBinding::SampledKind sampled_kind;
-};
-class InspectorGetSampledTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetSampledTextureTestParams> {};
-class InspectorGetSampledArrayTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetSampledTextureTestParams> {};
-class InspectorGetMultisampledTextureResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-class InspectorGetMultisampledArrayTextureResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-typedef GetSampledTextureTestParams GetMultisampledTextureTestParams;
-class InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetMultisampledTextureTestParams> {};
-class InspectorGetMultisampledTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetMultisampledTextureTestParams> {};
-class InspectorGetStorageTextureResourceBindingsTest : public InspectorBuilder,
-                                                       public testing::Test {};
-struct GetDepthTextureTestParams {
-  ast::TextureDimension type_dim;
-  inspector::ResourceBinding::TextureDimension inspector_dim;
-};
-class InspectorGetDepthTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetDepthTextureTestParams> {};
-
-class InspectorGetDepthMultisampledTextureResourceBindingsTest
-    : public InspectorBuilder,
-      public testing::Test {};
-
-typedef std::tuple<ast::TextureDimension, ResourceBinding::TextureDimension>
-    DimensionParams;
-typedef std::tuple<ast::TexelFormat,
-                   ResourceBinding::TexelFormat,
-                   ResourceBinding::SampledKind>
-    TexelFormatParams;
-typedef std::tuple<DimensionParams, TexelFormatParams>
-    GetStorageTextureTestParams;
-class InspectorGetStorageTextureResourceBindingsTestWithParam
-    : public InspectorBuilder,
-      public testing::TestWithParam<GetStorageTextureTestParams> {};
-
-class InspectorGetExternalTextureResourceBindingsTest : public InspectorBuilder,
-                                                        public testing::Test {};
-
-class InspectorGetSamplerTextureUsesTest : public InspectorRunner,
-                                           public testing::Test {};
-
-class InspectorGetWorkgroupStorageSizeTest : public InspectorBuilder,
-                                             public testing::Test {};
-
-// This is a catch all for shaders that have demonstrated regressions/crashes in
-// the wild.
-class InspectorRegressionTest : public InspectorRunner, public testing::Test {};
-
-TEST_F(InspectorGetEntryPointTest, NoFunctions) {
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, NoEntryPoints) {
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kFragment),
-                               });
-
-  // TODO(dsinclair): Update to run the namer transform when available.
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ("foo", result[0].name);
-  EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
-}
-
-TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kFragment),
-                               });
-
-  MakeEmptyBodyFunction("bar",
-                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                           WorkgroupSize(1)});
-
-  // TODO(dsinclair): Update to run the namer transform when available.
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(2u, result.size());
-  EXPECT_EQ("foo", result[0].name);
-  EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
-  EXPECT_EQ("bar", result[1].name);
-  EXPECT_EQ("bar", result[1].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kCompute, result[1].stage);
-}
-
-TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) {
-  MakeEmptyBodyFunction("func", {});
-
-  MakeCallerBodyFunction("foo", {"func"},
-                         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                            WorkgroupSize(1)});
-
-  MakeCallerBodyFunction("bar", {"func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  // TODO(dsinclair): Update to run the namer transform when available.
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  EXPECT_FALSE(inspector.has_error());
-
-  ASSERT_EQ(2u, result.size());
-  EXPECT_EQ("foo", result[0].name);
-  EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kCompute, result[0].stage);
-  EXPECT_EQ("bar", result[1].name);
-  EXPECT_EQ("bar", result[1].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kFragment, result[1].stage);
-}
-
-TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) {
-  MakeEmptyBodyFunction("foo",
-                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                           WorkgroupSize(8, 2, 1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-  uint32_t x, y, z;
-  std::tie(x, y, z) = result[0].workgroup_size();
-  EXPECT_EQ(8u, x);
-  EXPECT_EQ(2u, y);
-  EXPECT_EQ(1u, z);
-}
-
-TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) {
-  MakeEmptyBodyFunction(
-      "foo", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(8, 2, 1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-  uint32_t x, y, z;
-  std::tie(x, y, z) = result[0].workgroup_size();
-  EXPECT_EQ(8u, x);
-  EXPECT_EQ(2u, y);
-  EXPECT_EQ(1u, z);
-}
-
-TEST_F(InspectorGetEntryPointTest, NoInOutVariables) {
-  MakeEmptyBodyFunction("func", {});
-
-  MakeCallerBodyFunction("foo", {"func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].input_variables.size());
-  EXPECT_EQ(0u, result[0].output_variables.size());
-}
-
-TEST_P(InspectorGetEntryPointComponentAndCompositionTest, Test) {
-  ComponentType component;
-  CompositionType composition;
-  std::tie(component, composition) = GetParam();
-  std::function<const ast::Type*()> tint_type =
-      GetTypeFunction(component, composition);
-
-  auto* in_var = Param("in_var", tint_type(), {Location(0u), Flat()});
-  Func("foo", {in_var}, tint_type(), {Return("in_var")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(component, result[0].input_variables[0].component_type);
-
-  ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(component, result[0].output_variables[0].component_type);
-}
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetEntryPointTest,
-    InspectorGetEntryPointComponentAndCompositionTest,
-    testing::Combine(testing::Values(ComponentType::kFloat,
-                                     ComponentType::kSInt,
-                                     ComponentType::kUInt),
-                     testing::Values(CompositionType::kScalar,
-                                     CompositionType::kVec2,
-                                     CompositionType::kVec3,
-                                     CompositionType::kVec4)));
-
-TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) {
-  auto* in_var0 = Param("in_var0", ty.u32(), {Location(0u), Flat()});
-  auto* in_var1 = Param("in_var1", ty.u32(), {Location(1u), Flat()});
-  auto* in_var4 = Param("in_var4", ty.u32(), {Location(4u), Flat()});
-  Func("foo", {in_var0, in_var1, in_var4}, ty.u32(), {Return("in_var0")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  ASSERT_EQ(3u, result[0].input_variables.size());
-  EXPECT_EQ("in_var0", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(InterpolationType::kFlat,
-            result[0].input_variables[0].interpolation_type);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
-  EXPECT_EQ("in_var1", result[0].input_variables[1].name);
-  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
-  EXPECT_EQ(InterpolationType::kFlat,
-            result[0].input_variables[1].interpolation_type);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
-  EXPECT_EQ("in_var4", result[0].input_variables[2].name);
-  EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
-  EXPECT_EQ(4u, result[0].input_variables[2].location_attribute);
-  EXPECT_EQ(InterpolationType::kFlat,
-            result[0].input_variables[2].interpolation_type);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
-
-  ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
-}
-
-TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
-  auto* in_var_foo = Param("in_var_foo", ty.u32(), {Location(0u), Flat()});
-  Func("foo", {in_var_foo}, ty.u32(), {Return("in_var_foo")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
-
-  auto* in_var_bar = Param("in_var_bar", ty.u32(), {Location(0u), Flat()});
-  Func("bar", {in_var_bar}, ty.u32(), {Return("in_var_bar")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(1u)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(2u, result.size());
-
-  ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var_foo", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(InterpolationType::kFlat,
-            result[0].input_variables[0].interpolation_type);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
-
-  ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
-
-  ASSERT_EQ(1u, result[1].input_variables.size());
-  EXPECT_EQ("in_var_bar", result[1].input_variables[0].name);
-  EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
-  EXPECT_EQ(InterpolationType::kFlat,
-            result[1].input_variables[0].interpolation_type);
-  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
-
-  ASSERT_EQ(1u, result[1].output_variables.size());
-  EXPECT_EQ("<retval>", result[1].output_variables[0].name);
-  EXPECT_TRUE(result[1].output_variables[0].has_location_attribute);
-  EXPECT_EQ(1u, result[1].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[1].output_variables[0].component_type);
-}
-
-TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
-  auto* in_var0 =
-      Param("in_var0", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-  auto* in_var1 = Param("in_var1", ty.f32(), {Location(0u)});
-  Func("foo", {in_var0, in_var1}, ty.f32(), {Return("in_var1")},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kFragDepth)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var1", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[0].component_type);
-
-  ASSERT_EQ(0u, result[0].output_variables.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, InOutStruct) {
-  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
-  Func("foo", {Param("param", ty.Of(interface))}, ty.Of(interface),
-       {Return("param")}, {Stage(ast::PipelineStage::kFragment)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  ASSERT_EQ(2u, result[0].input_variables.size());
-  EXPECT_EQ("param.a", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
-  EXPECT_EQ("param.b", result[0].input_variables[1].name);
-  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
-
-  ASSERT_EQ(2u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
-  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
-  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
-}
-
-TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
-  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
-  Func("foo", {}, ty.Of(interface), {Return(Construct(ty.Of(interface)))},
-       {Stage(ast::PipelineStage::kFragment)});
-  Func("bar", {Param("param", ty.Of(interface))}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(2u, result.size());
-
-  ASSERT_EQ(0u, result[0].input_variables.size());
-
-  ASSERT_EQ(2u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
-  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
-  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
-
-  ASSERT_EQ(2u, result[1].input_variables.size());
-  EXPECT_EQ("param.a", result[1].input_variables[0].name);
-  EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
-  EXPECT_EQ("param.b", result[1].input_variables[1].name);
-  EXPECT_TRUE(result[1].input_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[1].input_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[1].component_type);
-
-  ASSERT_EQ(0u, result[1].output_variables.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) {
-  auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
-  auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
-  Func("foo",
-       {Param("param_a", ty.Of(struct_a)), Param("param_b", ty.Of(struct_b)),
-        Param("param_c", ty.f32(), {Location(3u)}),
-        Param("param_d", ty.f32(), {Location(4u)})},
-       ty.Of(struct_a), {Return("param_a")},
-       {Stage(ast::PipelineStage::kFragment)});
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  ASSERT_EQ(5u, result[0].input_variables.size());
-  EXPECT_EQ("param_a.a", result[0].input_variables[0].name);
-  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
-  EXPECT_EQ("param_a.b", result[0].input_variables[1].name);
-  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
-  EXPECT_EQ("param_b.a", result[0].input_variables[2].name);
-  EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
-  EXPECT_EQ(2u, result[0].input_variables[2].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
-  EXPECT_EQ("param_c", result[0].input_variables[3].name);
-  EXPECT_TRUE(result[0].input_variables[3].has_location_attribute);
-  EXPECT_EQ(3u, result[0].input_variables[3].location_attribute);
-  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[3].component_type);
-  EXPECT_EQ("param_d", result[0].input_variables[4].name);
-  EXPECT_TRUE(result[0].input_variables[4].has_location_attribute);
-  EXPECT_EQ(4u, result[0].input_variables[4].location_attribute);
-  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[4].component_type);
-
-  ASSERT_EQ(2u, result[0].output_variables.size());
-  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
-  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
-  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
-  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
-  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
-  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantUnreferenced) {
-  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
-  MakeEmptyBodyFunction(
-      "ep_func", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].overridable_constants.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByEntryPoint) {
-  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
-  MakePlainGlobalReferenceBodyFunction(
-      "ep_func", "foo", ty.f32(),
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByCallee) {
-  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
-  MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
-  MakeCallerBodyFunction(
-      "ep_func", {"callee_func"},
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantSomeReferenced) {
-  AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr);
-  AddOverridableConstantWithID("bar", 2, ty.f32(), nullptr);
-  MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
-  MakeCallerBodyFunction(
-      "ep_func", {"callee_func"},
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
-  EXPECT_EQ(1, result[0].overridable_constants[0].numeric_id);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantTypes) {
-  AddOverridableConstantWithoutID("bool_var", ty.bool_(), nullptr);
-  AddOverridableConstantWithoutID("float_var", ty.f32(), nullptr);
-  AddOverridableConstantWithoutID("u32_var", ty.u32(), nullptr);
-  AddOverridableConstantWithoutID("i32_var", ty.i32(), nullptr);
-
-  MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), {});
-  MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), {});
-  MakePlainGlobalReferenceBodyFunction("u32_func", "u32_var", ty.u32(), {});
-  MakePlainGlobalReferenceBodyFunction("i32_func", "i32_var", ty.i32(), {});
-
-  MakeCallerBodyFunction(
-      "ep_func", {"bool_func", "float_func", "u32_func", "i32_func"},
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(4u, result[0].overridable_constants.size());
-  EXPECT_EQ("bool_var", result[0].overridable_constants[0].name);
-  EXPECT_EQ(inspector::OverridableConstant::Type::kBool,
-            result[0].overridable_constants[0].type);
-  EXPECT_EQ("float_var", result[0].overridable_constants[1].name);
-  EXPECT_EQ(inspector::OverridableConstant::Type::kFloat32,
-            result[0].overridable_constants[1].type);
-  EXPECT_EQ("u32_var", result[0].overridable_constants[2].name);
-  EXPECT_EQ(inspector::OverridableConstant::Type::kUint32,
-            result[0].overridable_constants[2].type);
-  EXPECT_EQ("i32_var", result[0].overridable_constants[3].name);
-  EXPECT_EQ(inspector::OverridableConstant::Type::kInt32,
-            result[0].overridable_constants[3].type);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantInitialized) {
-  AddOverridableConstantWithoutID("foo", ty.f32(), Expr(0.0f));
-  MakePlainGlobalReferenceBodyFunction(
-      "ep_func", "foo", ty.f32(),
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
-  EXPECT_TRUE(result[0].overridable_constants[0].is_initialized);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantUninitialized) {
-  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
-  MakePlainGlobalReferenceBodyFunction(
-      "ep_func", "foo", ty.f32(),
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
-
-  EXPECT_FALSE(result[0].overridable_constants[0].is_initialized);
-}
-
-TEST_F(InspectorGetEntryPointTest, OverridableConstantNumericIDSpecified) {
-  AddOverridableConstantWithoutID("foo_no_id", ty.f32(), nullptr);
-  AddOverridableConstantWithID("foo_id", 1234, ty.f32(), nullptr);
-
-  MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), {});
-  MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), {});
-
-  MakeCallerBodyFunction(
-      "ep_func", {"no_id_func", "id_func"},
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(2u, result[0].overridable_constants.size());
-  EXPECT_EQ("foo_no_id", result[0].overridable_constants[0].name);
-  EXPECT_EQ("foo_id", result[0].overridable_constants[1].name);
-  EXPECT_EQ(1234, result[0].overridable_constants[1].numeric_id);
-
-  EXPECT_FALSE(result[0].overridable_constants[0].is_numeric_id_specified);
-  EXPECT_TRUE(result[0].overridable_constants[1].is_numeric_id_specified);
-}
-
-TEST_F(InspectorGetEntryPointTest, NonOverridableConstantSkipped) {
-  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         {Stage(ast::PipelineStage::kFragment)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].overridable_constants.size());
-}
-
-TEST_F(InspectorGetEntryPointTest, BuiltinNotReferenced) {
-  MakeEmptyBodyFunction("ep_func", {Stage(ast::PipelineStage::kFragment)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_FALSE(result[0].input_sample_mask_used);
-  EXPECT_FALSE(result[0].output_sample_mask_used);
-  EXPECT_FALSE(result[0].input_position_used);
-  EXPECT_FALSE(result[0].front_facing_used);
-  EXPECT_FALSE(result[0].sample_index_used);
-  EXPECT_FALSE(result[0].num_workgroups_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].input_sample_mask_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(
-      Member("inner_position", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].input_sample_mask_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
-  Func("ep_func", {in_var}, ty.u32(), {Return("in_var")},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kSampleMask)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].output_sample_mask_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(Member("inner_sample_mask", ty.u32(),
-                           {Builtin(ast::Builtin::kSampleMask)}));
-  Structure("out_struct", members, {});
-
-  Func("ep_func", {}, ty.type_name("out_struct"),
-       {Decl(Var("out_var", ty.type_name("out_struct"))), Return("out_var")},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].output_sample_mask_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, InputPositionSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].input_position_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, InputPositionStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(Member("inner_position", ty.vec4<f32>(),
-                           {Builtin(ast::Builtin::kPosition)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].input_position_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)});
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].front_facing_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(Member("inner_position", ty.bool_(),
-                           {Builtin(ast::Builtin::kFrontFacing)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].front_facing_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].sample_index_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(Member("inner_position", ty.u32(),
-                           {Builtin(ast::Builtin::kSampleIndex)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].sample_index_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) {
-  auto* in_var =
-      Param("in_var", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)});
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].num_workgroups_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) {
-  ast::StructMemberList members;
-  members.push_back(Member("inner_position", ty.vec3<u32>(),
-                           {Builtin(ast::Builtin::kNumWorkgroups)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_TRUE(result[0].num_workgroups_used);
-}
-
-TEST_F(InspectorGetEntryPointTest, ImplicitInterpolate) {
-  ast::StructMemberList members;
-  members.push_back(Member("struct_inner", ty.f32(), {Location(0)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ(InterpolationType::kPerspective,
-            result[0].input_variables[0].interpolation_type);
-  EXPECT_EQ(InterpolationSampling::kCenter,
-            result[0].input_variables[0].interpolation_sampling);
-}
-
-TEST_P(InspectorGetEntryPointInterpolateTest, Test) {
-  auto& params = GetParam();
-  ast::StructMemberList members;
-  members.push_back(
-      Member("struct_inner", ty.f32(),
-             {Interpolate(params.in_type, params.in_sampling), Location(0)}));
-  Structure("in_struct", members, {});
-  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
-
-  Func("ep_func", {in_var}, ty.void_(), {Return()},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetEntryPoints();
-
-  ASSERT_EQ(1u, result.size());
-  ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ(params.out_type, result[0].input_variables[0].interpolation_type);
-  EXPECT_EQ(params.out_sampling,
-            result[0].input_variables[0].interpolation_sampling);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetEntryPointTest,
-    InspectorGetEntryPointInterpolateTest,
-    testing::Values(
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kPerspective,
-            ast::InterpolationSampling::kCenter,
-            InterpolationType::kPerspective, InterpolationSampling::kCenter},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kPerspective,
-            ast::InterpolationSampling::kCentroid,
-            InterpolationType::kPerspective, InterpolationSampling::kCentroid},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kPerspective,
-            ast::InterpolationSampling::kSample,
-            InterpolationType::kPerspective, InterpolationSampling::kSample},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kPerspective,
-            ast::InterpolationSampling::kNone, InterpolationType::kPerspective,
-            InterpolationSampling::kCenter},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kLinear,
-            ast::InterpolationSampling::kCenter, InterpolationType::kLinear,
-            InterpolationSampling::kCenter},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kLinear,
-            ast::InterpolationSampling::kCentroid, InterpolationType::kLinear,
-            InterpolationSampling::kCentroid},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kLinear,
-            ast::InterpolationSampling::kSample, InterpolationType::kLinear,
-            InterpolationSampling::kSample},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kLinear, ast::InterpolationSampling::kNone,
-            InterpolationType::kLinear, InterpolationSampling::kCenter},
-        InspectorGetEntryPointInterpolateTestParams{
-            ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone,
-            InterpolationType::kFlat, InterpolationSampling::kNone}));
-
-// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
-// through
-TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoFunctions) {
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetRemappedNameForEntryPoint("foo");
-  ASSERT_TRUE(inspector.has_error());
-
-  EXPECT_EQ("", result);
-}
-
-// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
-// through
-TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoEntryPoints) {
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetRemappedNameForEntryPoint("foo");
-  ASSERT_TRUE(inspector.has_error());
-
-  EXPECT_EQ("", result);
-}
-
-// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
-// through
-TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_OneEntryPoint) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kVertex),
-                               });
-
-  // TODO(dsinclair): Update to run the namer transform when
-  // available.
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetRemappedNameForEntryPoint("foo");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ("foo", result);
-}
-
-// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
-// through
-TEST_F(InspectorGetRemappedNameForEntryPointTest,
-       DISABLED_MultipleEntryPoints) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kVertex),
-                               });
-
-  // TODO(dsinclair): Update to run the namer transform when
-  // available.
-
-  MakeEmptyBodyFunction("bar",
-                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                           WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  {
-    auto result = inspector.GetRemappedNameForEntryPoint("foo");
-    ASSERT_FALSE(inspector.has_error()) << inspector.error();
-    EXPECT_EQ("foo", result);
-  }
-  {
-    auto result = inspector.GetRemappedNameForEntryPoint("bar");
-    ASSERT_FALSE(inspector.has_error()) << inspector.error();
-    EXPECT_EQ("bar", result);
-  }
-}
-
-TEST_F(InspectorGetConstantIDsTest, Bool) {
-  AddOverridableConstantWithID("foo", 1, ty.bool_(), nullptr);
-  AddOverridableConstantWithID("bar", 20, ty.bool_(), Expr(true));
-  AddOverridableConstantWithID("baz", 300, ty.bool_(), Expr(false));
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetConstantIDs();
-  ASSERT_EQ(3u, result.size());
-
-  ASSERT_TRUE(result.find(1) != result.end());
-  EXPECT_TRUE(result[1].IsNull());
-
-  ASSERT_TRUE(result.find(20) != result.end());
-  EXPECT_TRUE(result[20].IsBool());
-  EXPECT_TRUE(result[20].AsBool());
-
-  ASSERT_TRUE(result.find(300) != result.end());
-  EXPECT_TRUE(result[300].IsBool());
-  EXPECT_FALSE(result[300].AsBool());
-}
-
-TEST_F(InspectorGetConstantIDsTest, U32) {
-  AddOverridableConstantWithID("foo", 1, ty.u32(), nullptr);
-  AddOverridableConstantWithID("bar", 20, ty.u32(), Expr(42u));
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetConstantIDs();
-  ASSERT_EQ(2u, result.size());
-
-  ASSERT_TRUE(result.find(1) != result.end());
-  EXPECT_TRUE(result[1].IsNull());
-
-  ASSERT_TRUE(result.find(20) != result.end());
-  EXPECT_TRUE(result[20].IsU32());
-  EXPECT_EQ(42u, result[20].AsU32());
-}
-
-TEST_F(InspectorGetConstantIDsTest, I32) {
-  AddOverridableConstantWithID("foo", 1, ty.i32(), nullptr);
-  AddOverridableConstantWithID("bar", 20, ty.i32(), Expr(-42));
-  AddOverridableConstantWithID("baz", 300, ty.i32(), Expr(42));
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetConstantIDs();
-  ASSERT_EQ(3u, result.size());
-
-  ASSERT_TRUE(result.find(1) != result.end());
-  EXPECT_TRUE(result[1].IsNull());
-
-  ASSERT_TRUE(result.find(20) != result.end());
-  EXPECT_TRUE(result[20].IsI32());
-  EXPECT_EQ(-42, result[20].AsI32());
-
-  ASSERT_TRUE(result.find(300) != result.end());
-  EXPECT_TRUE(result[300].IsI32());
-  EXPECT_EQ(42, result[300].AsI32());
-}
-
-TEST_F(InspectorGetConstantIDsTest, Float) {
-  AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr);
-  AddOverridableConstantWithID("bar", 20, ty.f32(), Expr(0.0f));
-  AddOverridableConstantWithID("baz", 300, ty.f32(), Expr(-10.0f));
-  AddOverridableConstantWithID("x", 4000, ty.f32(), Expr(15.0f));
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetConstantIDs();
-  ASSERT_EQ(4u, result.size());
-
-  ASSERT_TRUE(result.find(1) != result.end());
-  EXPECT_TRUE(result[1].IsNull());
-
-  ASSERT_TRUE(result.find(20) != result.end());
-  EXPECT_TRUE(result[20].IsFloat());
-  EXPECT_FLOAT_EQ(0.0, result[20].AsFloat());
-
-  ASSERT_TRUE(result.find(300) != result.end());
-  EXPECT_TRUE(result[300].IsFloat());
-  EXPECT_FLOAT_EQ(-10.0, result[300].AsFloat());
-
-  ASSERT_TRUE(result.find(4000) != result.end());
-  EXPECT_TRUE(result[4000].IsFloat());
-  EXPECT_FLOAT_EQ(15.0, result[4000].AsFloat());
-}
-
-TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) {
-  AddOverridableConstantWithID("v1", 1, ty.f32(), nullptr);
-  AddOverridableConstantWithID("v20", 20, ty.f32(), nullptr);
-  AddOverridableConstantWithID("v300", 300, ty.f32(), nullptr);
-  auto* a = AddOverridableConstantWithoutID("a", ty.f32(), nullptr);
-  auto* b = AddOverridableConstantWithoutID("b", ty.f32(), nullptr);
-  auto* c = AddOverridableConstantWithoutID("c", ty.f32(), nullptr);
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetConstantNameToIdMap();
-  ASSERT_EQ(6u, result.size());
-
-  ASSERT_TRUE(result.count("v1"));
-  EXPECT_EQ(result["v1"], 1u);
-
-  ASSERT_TRUE(result.count("v20"));
-  EXPECT_EQ(result["v20"], 20u);
-
-  ASSERT_TRUE(result.count("v300"));
-  EXPECT_EQ(result["v300"], 300u);
-
-  ASSERT_TRUE(result.count("a"));
-  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(a));
-  EXPECT_EQ(result["a"],
-            program_->Sem().Get<sem::GlobalVariable>(a)->ConstantId());
-
-  ASSERT_TRUE(result.count("b"));
-  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(b));
-  EXPECT_EQ(result["b"],
-            program_->Sem().Get<sem::GlobalVariable>(b)->ConstantId());
-
-  ASSERT_TRUE(result.count("c"));
-  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(c));
-  EXPECT_EQ(result["c"],
-            program_->Sem().Get<sem::GlobalVariable>(c)->ConstantId());
-}
-
-TEST_F(InspectorGetStorageSizeTest, Empty) {
-  MakeEmptyBodyFunction("ep_func",
-                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                           WorkgroupSize(1)});
-  Inspector& inspector = Build();
-  EXPECT_EQ(0u, inspector.GetStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetStorageSizeTest, Simple_NonStruct) {
-  AddUniformBuffer("ub_var", ty.i32(), 0, 0);
-  AddStorageBuffer("sb_var", ty.i32(), ast::Access::kReadWrite, 1, 0);
-  AddStorageBuffer("rosb_var", ty.i32(), ast::Access::kRead, 1, 1);
-  Func("ep_func", {}, ty.void_(),
-       {
-           Decl(Const("ub", nullptr, Expr("ub_var"))),
-           Decl(Const("sb", nullptr, Expr("sb_var"))),
-           Decl(Const("rosb", nullptr, Expr("rosb_var"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  EXPECT_EQ(12u, inspector.GetStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetStorageSizeTest, Simple_Struct) {
-  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32(), ty.i32()});
-  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-  MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
-
-  auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
-  AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
-  MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
-
-  auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
-  AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
-  MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
-                                          {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func", "sb_func", "rosb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kCompute),
-                             WorkgroupSize(1),
-                         });
-
-  Inspector& inspector = Build();
-
-  EXPECT_EQ(16u, inspector.GetStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetStorageSizeTest, NonStructVec3) {
-  AddUniformBuffer("ub_var", ty.vec3<f32>(), 0, 0);
-  Func("ep_func", {}, ty.void_(),
-       {
-           Decl(Const("ub", nullptr, Expr("ub_var"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  EXPECT_EQ(12u, inspector.GetStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetStorageSizeTest, StructVec3) {
-  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.vec3<f32>()});
-  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-  Func("ep_func", {}, ty.void_(),
-       {
-           Decl(Const("ub", nullptr, Expr("ub_var"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Inspector& inspector = Build();
-
-  EXPECT_EQ(16u, inspector.GetStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetResourceBindingsTest, Empty) {
-  MakeCallerBodyFunction("ep_func", {},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetResourceBindingsTest, Simple) {
-  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()});
-  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-  MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
-
-  auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
-  AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
-  MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
-
-  auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
-  AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
-  MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
-                                          {{0, ty.i32()}});
-
-  auto* s_texture_type =
-      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  AddResource("s_texture", s_texture_type, 2, 0);
-  AddSampler("s_var", 3, 0);
-  AddGlobalVariable("s_coords", ty.f32());
-  MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords",
-                                   ty.f32(), {});
-
-  auto* cs_depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
-  AddResource("cs_texture", cs_depth_texture_type, 3, 1);
-  AddComparisonSampler("cs_var", 3, 2);
-  AddGlobalVariable("cs_coords", ty.vec2<f32>());
-  AddGlobalVariable("cs_depth", ty.f32());
-  MakeComparisonSamplerReferenceBodyFunction(
-      "cs_func", "cs_texture", "cs_var", "cs_coords", "cs_depth", ty.f32(), {});
-
-  auto* depth_ms_texture_type =
-      ty.depth_multisampled_texture(ast::TextureDimension::k2d);
-  AddResource("depth_ms_texture", depth_ms_texture_type, 3, 3);
-  Func("depth_ms_func", {}, ty.void_(), {Ignore("depth_ms_texture")});
-
-  auto* st_type = MakeStorageTextureTypes(ast::TextureDimension::k2d,
-                                          ast::TexelFormat::kR32Uint);
-  AddStorageTexture("st_var", st_type, 4, 0);
-  MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), {});
-
-  MakeCallerBodyFunction("ep_func",
-                         {"ub_func", "sb_func", "rosb_func", "s_func",
-                          "cs_func", "depth_ms_func", "st_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(9u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[1].resource_type);
-  EXPECT_EQ(1u, result[1].bind_group);
-  EXPECT_EQ(0u, result[1].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[2].resource_type);
-  EXPECT_EQ(1u, result[2].bind_group);
-  EXPECT_EQ(1u, result[2].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[3].resource_type);
-  EXPECT_EQ(3u, result[3].bind_group);
-  EXPECT_EQ(0u, result[3].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
-            result[4].resource_type);
-  EXPECT_EQ(3u, result[4].bind_group);
-  EXPECT_EQ(2u, result[4].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
-            result[5].resource_type);
-  EXPECT_EQ(2u, result[5].bind_group);
-  EXPECT_EQ(0u, result[5].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture,
-            result[6].resource_type);
-  EXPECT_EQ(4u, result[6].bind_group);
-  EXPECT_EQ(0u, result[6].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture,
-            result[7].resource_type);
-  EXPECT_EQ(3u, result[7].bind_group);
-  EXPECT_EQ(1u, result[7].binding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture,
-            result[8].resource_type);
-  EXPECT_EQ(3u, result[8].bind_group);
-  EXPECT_EQ(3u, result[8].binding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingEntryPoint) {
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_TRUE(inspector.has_error());
-  std::string error = inspector.error();
-  EXPECT_TRUE(error.find("not found") != std::string::npos);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
-  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ub_func");
-  std::string error = inspector.error();
-  EXPECT_TRUE(error.find("not an entry point") != std::string::npos);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_NonStruct) {
-  AddUniformBuffer("foo_ub", ty.i32(), 0, 0);
-  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.i32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].size);
-  EXPECT_EQ(4u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_Struct) {
-  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].size);
-  EXPECT_EQ(4u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
-  auto* foo_struct_type =
-      MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-
-  MakeStructVariableReferenceBodyFunction(
-      "ub_func", "foo_ub", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) {
-  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.vec3<f32>()});
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
-                                          {{0, ty.vec3<f32>()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(16u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonStructVec3) {
-  AddUniformBuffer("foo_ub", ty.vec3<f32>(), 0, 0);
-  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
-  auto* ub_struct_type =
-      MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddUniformBuffer("ub_foo", ty.Of(ub_struct_type), 0, 0);
-  AddUniformBuffer("ub_bar", ty.Of(ub_struct_type), 0, 1);
-  AddUniformBuffer("ub_baz", ty.Of(ub_struct_type), 2, 0);
-
-  auto AddReferenceFunc = [this](const std::string& func_name,
-                                 const std::string& var_name) {
-    MakeStructVariableReferenceBodyFunction(
-        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
-  };
-  AddReferenceFunc("ub_foo_func", "ub_foo");
-  AddReferenceFunc("ub_bar_func", "ub_bar");
-  AddReferenceFunc("ub_baz_func", "ub_baz");
-
-  auto FuncCall = [&](const std::string& callee) {
-    return create<ast::CallStatement>(Call(callee));
-  };
-
-  Func("ep_func", ast::VariableList(), ty.void_(),
-       ast::StatementList{FuncCall("ub_foo_func"), FuncCall("ub_bar_func"),
-                          FuncCall("ub_baz_func"), Return()},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(3u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[1].resource_type);
-  EXPECT_EQ(0u, result[1].bind_group);
-  EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(12u, result[1].size);
-  EXPECT_EQ(12u, result[1].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[2].resource_type);
-  EXPECT_EQ(2u, result[2].bind_group);
-  EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(12u, result[2].size);
-  EXPECT_EQ(12u, result[2].size_no_padding);
-}
-
-TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) {
-  // Manually create uniform buffer to make sure it had a valid layout (array
-  // with elem stride of 16, and that is 16-byte aligned within the struct)
-  auto* foo_struct_type = Structure(
-      "foo_type",
-      {Member("0i32", ty.i32()),
-       Member("b", ty.array(ty.u32(), 4, /*stride*/ 16), {MemberAlign(16)})},
-      {create<ast::StructBlockAttribute>()});
-
-  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(80u, result[0].size);
-  EXPECT_EQ(80u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_NonStruct) {
-  AddStorageBuffer("foo_sb", ty.i32(), ast::Access::kReadWrite, 0, 0);
-  MakePlainGlobalReferenceBodyFunction("sb_func", "foo_sb", ty.i32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].size);
-  EXPECT_EQ(4u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_Struct) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].size);
-  EXPECT_EQ(4u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
-                                                                ty.i32(),
-                                                                ty.u32(),
-                                                                ty.f32(),
-                                                            });
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction(
-      "sb_func", "foo_sb", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-  auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
-                                                              ty.i32(),
-                                                              ty.u32(),
-                                                              ty.f32(),
-                                                          });
-  AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kReadWrite, 0, 0);
-  AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kReadWrite, 0, 1);
-  AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kReadWrite, 2, 0);
-
-  auto AddReferenceFunc = [this](const std::string& func_name,
-                                 const std::string& var_name) {
-    MakeStructVariableReferenceBodyFunction(
-        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
-  };
-  AddReferenceFunc("sb_foo_func", "sb_foo");
-  AddReferenceFunc("sb_bar_func", "sb_bar");
-  AddReferenceFunc("sb_baz_func", "sb_baz");
-
-  auto FuncCall = [&](const std::string& callee) {
-    return create<ast::CallStatement>(Call(callee));
-  };
-
-  Func("ep_func", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           FuncCall("sb_foo_func"),
-           FuncCall("sb_bar_func"),
-           FuncCall("sb_baz_func"),
-           Return(),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(3u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[1].resource_type);
-  EXPECT_EQ(0u, result[1].bind_group);
-  EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(12u, result[1].size);
-  EXPECT_EQ(12u, result[1].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[2].resource_type);
-  EXPECT_EQ(2u, result[2].bind_group);
-  EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(12u, result[2].size);
-  EXPECT_EQ(12u, result[2].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
-  auto foo_struct_type =
-      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(20u, result[0].size);
-  EXPECT_EQ(20u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
-                                                                ty.i32(),
-                                                                ty.array<u32>(),
-                                                            });
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(8u, result[0].size);
-  EXPECT_EQ(8u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
-                                          {{0, ty.vec3<f32>()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(16u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, NonStructVec3) {
-  AddStorageBuffer("foo_ub", ty.vec3<f32>(), ast::Access::kReadWrite, 0, 0);
-  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
-
-  MakeCallerBodyFunction("ep_func", {"ub_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].size);
-  EXPECT_EQ(4u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
-       MultipleStorageBuffers) {
-  auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
-                                                              ty.i32(),
-                                                              ty.u32(),
-                                                              ty.f32(),
-                                                          });
-  AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kRead, 0, 0);
-  AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kRead, 0, 1);
-  AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kRead, 2, 0);
-
-  auto AddReferenceFunc = [this](const std::string& func_name,
-                                 const std::string& var_name) {
-    MakeStructVariableReferenceBodyFunction(
-        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
-  };
-  AddReferenceFunc("sb_foo_func", "sb_foo");
-  AddReferenceFunc("sb_bar_func", "sb_bar");
-  AddReferenceFunc("sb_baz_func", "sb_baz");
-
-  auto FuncCall = [&](const std::string& callee) {
-    return create<ast::CallStatement>(Call(callee));
-  };
-
-  Func("ep_func", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           FuncCall("sb_foo_func"),
-           FuncCall("sb_bar_func"),
-           FuncCall("sb_baz_func"),
-           Return(),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(3u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].size);
-  EXPECT_EQ(12u, result[0].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[1].resource_type);
-  EXPECT_EQ(0u, result[1].bind_group);
-  EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(12u, result[1].size);
-  EXPECT_EQ(12u, result[1].size_no_padding);
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[2].resource_type);
-  EXPECT_EQ(2u, result[2].bind_group);
-  EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(12u, result[2].size);
-  EXPECT_EQ(12u, result[2].size_no_padding);
-}
-
-TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
-  auto foo_struct_type =
-      MakeStorageBufferTypes("foo_type", {
-                                             ty.i32(),
-                                             ty.array<u32, 4>(),
-                                         });
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(20u, result[0].size);
-  EXPECT_EQ(20u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
-       ContainingRuntimeArray) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
-                                                                ty.i32(),
-                                                                ty.array<u32>(),
-                                                            });
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(8u, result[0].size);
-  EXPECT_EQ(8u, result[0].size_no_padding);
-}
-
-TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
-  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
-
-  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
-
-  MakeCallerBodyFunction("ep_func", {"sb_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) {
-  auto* sampled_texture_type =
-      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.f32());
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords", ty.f32(),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(1u, result[0].binding);
-}
-
-TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
-  MakeEmptyBodyFunction("ep_func", ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) {
-  auto* sampled_texture_type =
-      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.f32());
-
-  MakeSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler",
-                                   "foo_coords", ty.f32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"foo_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(1u, result[0].binding);
-}
-
-TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) {
-  auto* sampled_texture_type =
-      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.f32());
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords", ty.f32(),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("foo");
-  ASSERT_TRUE(inspector.has_error()) << inspector.error();
-}
-
-TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) {
-  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
-  AddResource("foo_texture", depth_texture_type, 0, 0);
-  AddComparisonSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.vec2<f32>());
-  AddGlobalVariable("foo_depth", ty.f32());
-
-  MakeComparisonSamplerReferenceBodyFunction(
-      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
-      ast::AttributeList{
-          Stage(ast::PipelineStage::kFragment),
-      });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) {
-  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
-  AddResource("foo_texture", depth_texture_type, 0, 0);
-  AddComparisonSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.vec2<f32>());
-  AddGlobalVariable("foo_depth", ty.f32());
-
-  MakeComparisonSamplerReferenceBodyFunction(
-      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
-      ast::AttributeList{
-          Stage(ast::PipelineStage::kFragment),
-      });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
-            result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(1u, result[0].binding);
-}
-
-TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) {
-  MakeEmptyBodyFunction("ep_func", ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) {
-  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
-  AddResource("foo_texture", depth_texture_type, 0, 0);
-  AddComparisonSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.vec2<f32>());
-  AddGlobalVariable("foo_depth", ty.f32());
-
-  MakeComparisonSamplerReferenceBodyFunction("foo_func", "foo_texture",
-                                             "foo_sampler", "foo_coords",
-                                             "foo_depth", ty.f32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"foo_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kFragment),
-                         });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
-            result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(1u, result[0].binding);
-}
-
-TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) {
-  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
-  AddResource("foo_texture", depth_texture_type, 0, 0);
-  AddComparisonSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.vec2<f32>());
-  AddGlobalVariable("foo_depth", ty.f32());
-
-  MakeComparisonSamplerReferenceBodyFunction(
-      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
-      ast::AttributeList{
-          Stage(ast::PipelineStage::kFragment),
-      });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSamplerResourceBindings("foo");
-  ASSERT_TRUE(inspector.has_error()) << inspector.error();
-}
-
-TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) {
-  auto* sampled_texture_type =
-      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  AddGlobalVariable("foo_coords", ty.f32());
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords", ty.f32(),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kFragment),
-                               });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSampledTextureResourceBindings("foo");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(0u, result.size());
-}
-
-TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) {
-  auto* sampled_texture_type = ty.sampled_texture(
-      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
-  AddGlobalVariable("foo_coords", coord_type);
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords",
-                                   GetBaseType(GetParam().sampled_kind),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
-            result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
-  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
-
-  // Prove that sampled and multi-sampled bindings are accounted
-  // for separately.
-  auto multisampled_result =
-      inspector.GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_TRUE(multisampled_result.empty());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetSampledTextureResourceBindingsTest,
-    InspectorGetSampledTextureResourceBindingsTestWithParam,
-    testing::Values(
-        GetSampledTextureTestParams{
-            ast::TextureDimension::k1d,
-            inspector::ResourceBinding::TextureDimension::k1d,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetSampledTextureTestParams{
-            ast::TextureDimension::k2d,
-            inspector::ResourceBinding::TextureDimension::k2d,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetSampledTextureTestParams{
-            ast::TextureDimension::k3d,
-            inspector::ResourceBinding::TextureDimension::k3d,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetSampledTextureTestParams{
-            ast::TextureDimension::kCube,
-            inspector::ResourceBinding::TextureDimension::kCube,
-            inspector::ResourceBinding::SampledKind::kFloat}));
-
-TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam,
-       textureSample) {
-  auto* sampled_texture_type = ty.sampled_texture(
-      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
-  AddResource("foo_texture", sampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
-  AddGlobalVariable("foo_coords", coord_type);
-  AddGlobalVariable("foo_array_index", ty.i32());
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords", "foo_array_index",
-                                   GetBaseType(GetParam().sampled_kind),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
-  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetSampledArrayTextureResourceBindingsTest,
-    InspectorGetSampledArrayTextureResourceBindingsTestWithParam,
-    testing::Values(
-        GetSampledTextureTestParams{
-            ast::TextureDimension::k2dArray,
-            inspector::ResourceBinding::TextureDimension::k2dArray,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetSampledTextureTestParams{
-            ast::TextureDimension::kCubeArray,
-            inspector::ResourceBinding::TextureDimension::kCubeArray,
-            inspector::ResourceBinding::SampledKind::kFloat}));
-
-TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam,
-       textureLoad) {
-  auto* multisampled_texture_type = ty.multisampled_texture(
-      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
-  AddResource("foo_texture", multisampled_texture_type, 0, 0);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
-  AddGlobalVariable("foo_coords", coord_type);
-  AddGlobalVariable("foo_sample_index", ty.i32());
-
-  Func("ep", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("textureLoad", "foo_texture", "foo_coords",
-                         "foo_sample_index")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
-  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
-
-  // Prove that sampled and multi-sampled bindings are accounted
-  // for separately.
-  auto single_sampled_result =
-      inspector.GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_TRUE(single_sampled_result.empty());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetMultisampledTextureResourceBindingsTest,
-    InspectorGetMultisampledTextureResourceBindingsTestWithParam,
-    testing::Values(
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2d,
-            inspector::ResourceBinding::TextureDimension::k2d,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2d,
-            inspector::ResourceBinding::TextureDimension::k2d,
-            inspector::ResourceBinding::SampledKind::kSInt},
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2d,
-            inspector::ResourceBinding::TextureDimension::k2d,
-            inspector::ResourceBinding::SampledKind::kUInt}));
-
-TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) {
-  MakeEmptyBodyFunction("foo", ast::AttributeList{
-                                   Stage(ast::PipelineStage::kFragment),
-                               });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetSampledTextureResourceBindings("foo");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(0u, result.size());
-}
-
-TEST_P(InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam,
-       DISABLED_textureSample) {
-  auto* multisampled_texture_type = ty.multisampled_texture(
-      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
-  AddResource("foo_texture", multisampled_texture_type, 0, 0);
-  AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
-  AddGlobalVariable("foo_coords", coord_type);
-  AddGlobalVariable("foo_array_index", ty.i32());
-
-  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
-                                   "foo_coords", "foo_array_index",
-                                   GetBaseType(GetParam().sampled_kind),
-                                   ast::AttributeList{
-                                       Stage(ast::PipelineStage::kFragment),
-                                   });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
-  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetMultisampledArrayTextureResourceBindingsTest,
-    InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam,
-    testing::Values(
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2dArray,
-            inspector::ResourceBinding::TextureDimension::k2dArray,
-            inspector::ResourceBinding::SampledKind::kFloat},
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2dArray,
-            inspector::ResourceBinding::TextureDimension::k2dArray,
-            inspector::ResourceBinding::SampledKind::kSInt},
-        GetMultisampledTextureTestParams{
-            ast::TextureDimension::k2dArray,
-            inspector::ResourceBinding::TextureDimension::k2dArray,
-            inspector::ResourceBinding::SampledKind::kUInt}));
-
-TEST_F(InspectorGetStorageTextureResourceBindingsTest, Empty) {
-  MakeEmptyBodyFunction("ep", ast::AttributeList{
-                                  Stage(ast::PipelineStage::kFragment),
-                              });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  EXPECT_EQ(0u, result.size());
-}
-
-TEST_P(InspectorGetStorageTextureResourceBindingsTestWithParam, Simple) {
-  DimensionParams dim_params;
-  TexelFormatParams format_params;
-  std::tie(dim_params, format_params) = GetParam();
-
-  ast::TextureDimension dim;
-  ResourceBinding::TextureDimension expected_dim;
-  std::tie(dim, expected_dim) = dim_params;
-
-  ast::TexelFormat format;
-  ResourceBinding::TexelFormat expected_format;
-  ResourceBinding::SampledKind expected_kind;
-  std::tie(format, expected_format, expected_kind) = format_params;
-
-  auto* st_type = MakeStorageTextureTypes(dim, format);
-  AddStorageTexture("st_var", st_type, 0, 0);
-
-  const ast::Type* dim_type = nullptr;
-  switch (dim) {
-    case ast::TextureDimension::k1d:
-      dim_type = ty.i32();
-      break;
-    case ast::TextureDimension::k2d:
-    case ast::TextureDimension::k2dArray:
-      dim_type = ty.vec2<i32>();
-      break;
-    case ast::TextureDimension::k3d:
-      dim_type = ty.vec3<i32>();
-      break;
-    default:
-      break;
-  }
-
-  ASSERT_FALSE(dim_type == nullptr);
-
-  MakeStorageTextureBodyFunction(
-      "ep", "st_var", dim_type,
-      ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture,
-            result[0].resource_type);
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(expected_dim, result[0].dim);
-  EXPECT_EQ(expected_format, result[0].image_format);
-  EXPECT_EQ(expected_kind, result[0].sampled_kind);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetStorageTextureResourceBindingsTest,
-    InspectorGetStorageTextureResourceBindingsTestWithParam,
-    testing::Combine(
-        testing::Values(
-            std::make_tuple(ast::TextureDimension::k1d,
-                            ResourceBinding::TextureDimension::k1d),
-            std::make_tuple(ast::TextureDimension::k2d,
-                            ResourceBinding::TextureDimension::k2d),
-            std::make_tuple(ast::TextureDimension::k2dArray,
-                            ResourceBinding::TextureDimension::k2dArray),
-            std::make_tuple(ast::TextureDimension::k3d,
-                            ResourceBinding::TextureDimension::k3d)),
-        testing::Values(
-            std::make_tuple(ast::TexelFormat::kR32Float,
-                            ResourceBinding::TexelFormat::kR32Float,
-                            ResourceBinding::SampledKind::kFloat),
-            std::make_tuple(ast::TexelFormat::kR32Sint,
-                            ResourceBinding::TexelFormat::kR32Sint,
-                            ResourceBinding::SampledKind::kSInt),
-            std::make_tuple(ast::TexelFormat::kR32Uint,
-                            ResourceBinding::TexelFormat::kR32Uint,
-                            ResourceBinding::SampledKind::kUInt),
-            std::make_tuple(ast::TexelFormat::kRg32Float,
-                            ResourceBinding::TexelFormat::kRg32Float,
-                            ResourceBinding::SampledKind::kFloat),
-            std::make_tuple(ast::TexelFormat::kRg32Sint,
-                            ResourceBinding::TexelFormat::kRg32Sint,
-                            ResourceBinding::SampledKind::kSInt),
-            std::make_tuple(ast::TexelFormat::kRg32Uint,
-                            ResourceBinding::TexelFormat::kRg32Uint,
-                            ResourceBinding::SampledKind::kUInt),
-            std::make_tuple(ast::TexelFormat::kRgba16Float,
-                            ResourceBinding::TexelFormat::kRgba16Float,
-                            ResourceBinding::SampledKind::kFloat),
-            std::make_tuple(ast::TexelFormat::kRgba16Sint,
-                            ResourceBinding::TexelFormat::kRgba16Sint,
-                            ResourceBinding::SampledKind::kSInt),
-            std::make_tuple(ast::TexelFormat::kRgba16Uint,
-                            ResourceBinding::TexelFormat::kRgba16Uint,
-                            ResourceBinding::SampledKind::kUInt),
-            std::make_tuple(ast::TexelFormat::kRgba32Float,
-                            ResourceBinding::TexelFormat::kRgba32Float,
-                            ResourceBinding::SampledKind::kFloat),
-            std::make_tuple(ast::TexelFormat::kRgba32Sint,
-                            ResourceBinding::TexelFormat::kRgba32Sint,
-                            ResourceBinding::SampledKind::kSInt),
-            std::make_tuple(ast::TexelFormat::kRgba32Uint,
-                            ResourceBinding::TexelFormat::kRgba32Uint,
-                            ResourceBinding::SampledKind::kUInt),
-            std::make_tuple(ast::TexelFormat::kRgba8Sint,
-                            ResourceBinding::TexelFormat::kRgba8Sint,
-                            ResourceBinding::SampledKind::kSInt),
-            std::make_tuple(ast::TexelFormat::kRgba8Snorm,
-                            ResourceBinding::TexelFormat::kRgba8Snorm,
-                            ResourceBinding::SampledKind::kFloat),
-            std::make_tuple(ast::TexelFormat::kRgba8Uint,
-                            ResourceBinding::TexelFormat::kRgba8Uint,
-                            ResourceBinding::SampledKind::kUInt),
-            std::make_tuple(ast::TexelFormat::kRgba8Unorm,
-                            ResourceBinding::TexelFormat::kRgba8Unorm,
-                            ResourceBinding::SampledKind::kFloat))));
-
-TEST_P(InspectorGetDepthTextureResourceBindingsTestWithParam,
-       textureDimensions) {
-  auto* depth_texture_type = ty.depth_texture(GetParam().type_dim);
-  AddResource("dt", depth_texture_type, 0, 0);
-
-  Func("ep", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("textureDimensions", "dt")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetDepthTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture,
-            result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    InspectorGetDepthTextureResourceBindingsTest,
-    InspectorGetDepthTextureResourceBindingsTestWithParam,
-    testing::Values(
-        GetDepthTextureTestParams{
-            ast::TextureDimension::k2d,
-            inspector::ResourceBinding::TextureDimension::k2d},
-        GetDepthTextureTestParams{
-            ast::TextureDimension::k2dArray,
-            inspector::ResourceBinding::TextureDimension::k2dArray},
-        GetDepthTextureTestParams{
-            ast::TextureDimension::kCube,
-            inspector::ResourceBinding::TextureDimension::kCube},
-        GetDepthTextureTestParams{
-            ast::TextureDimension::kCubeArray,
-            inspector::ResourceBinding::TextureDimension::kCubeArray}));
-
-TEST_F(InspectorGetDepthMultisampledTextureResourceBindingsTest,
-       textureDimensions) {
-  auto* depth_ms_texture_type =
-      ty.depth_multisampled_texture(ast::TextureDimension::k2d);
-  AddResource("tex", depth_ms_texture_type, 0, 0);
-
-  Func("ep", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("textureDimensions", "tex")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetDepthMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture,
-            result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(ResourceBinding::TextureDimension::k2d, result[0].dim);
-}
-
-TEST_F(InspectorGetExternalTextureResourceBindingsTest, Simple) {
-  auto* external_texture_type = ty.external_texture();
-  AddResource("et", external_texture_type, 0, 0);
-
-  Func("ep", ast::VariableList(), ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("textureDimensions", "et")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Inspector& inspector = Build();
-
-  auto result = inspector.GetExternalTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-  EXPECT_EQ(ResourceBinding::ResourceType::kExternalTexture,
-            result[0].resource_type);
-
-  ASSERT_EQ(1u, result.size());
-  EXPECT_EQ(0u, result[0].bind_group);
-  EXPECT_EQ(0u, result[0].binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, None) {
-  std::string shader = R"(
-@stage(fragment)
-fn main() {
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(0u, result.size());
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, Simple) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-  EXPECT_EQ(0u, result[0].texture_binding_point.group);
-  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, UnknownEntryPoint) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("foo");
-  ASSERT_TRUE(inspector.has_error()) << inspector.error();
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, MultipleCalls) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result_0 = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  auto result_1 = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  EXPECT_EQ(result_0, result_1);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, BothIndirect) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, uv);
-}
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return doSample(myTexture, mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-  EXPECT_EQ(0u, result[0].texture_binding_point.group);
-  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, SamplerIndirect) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-fn doSample(s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(myTexture, s, uv);
-}
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return doSample(mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-  EXPECT_EQ(0u, result[0].texture_binding_point.group);
-  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, TextureIndirect) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-fn doSample(t: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(t, mySampler, uv);
-}
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return doSample(myTexture, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-  EXPECT_EQ(0u, result[0].texture_binding_point.group);
-  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, NeitherIndirect) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-fn doSample(uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(myTexture, mySampler, uv);
-}
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return doSample(fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-  ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-  ASSERT_EQ(1u, result.size());
-
-  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-  EXPECT_EQ(0u, result[0].texture_binding_point.group);
-  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-}
-
-TEST_F(InspectorGetSamplerTextureUsesTest, Complex) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-
-fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, uv);
-}
-
-fn X(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return doSample(t, s, uv);
-}
-
-fn Y(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return doSample(t, s, uv);
-}
-
-fn Z(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return X(t, s, uv) + Y(t, s, uv);
-}
-
-@stage(fragment)
-fn via_call(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return Z(myTexture, mySampler, fragUV) * fragPosition;
-}
-
-@stage(fragment)
-fn via_ptr(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
-}
-
-@stage(fragment)
-fn direct(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-
-  {
-    auto result = inspector.GetSamplerTextureUses("via_call");
-    ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-    ASSERT_EQ(1u, result.size());
-
-    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-    EXPECT_EQ(0u, result[0].texture_binding_point.group);
-    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-  }
-
-  {
-    auto result = inspector.GetSamplerTextureUses("via_ptr");
-    ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-    ASSERT_EQ(1u, result.size());
-
-    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-    EXPECT_EQ(0u, result[0].texture_binding_point.group);
-    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-  }
-
-  {
-    auto result = inspector.GetSamplerTextureUses("direct");
-    ASSERT_FALSE(inspector.has_error()) << inspector.error();
-
-    ASSERT_EQ(1u, result.size());
-
-    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
-    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
-    EXPECT_EQ(0u, result[0].texture_binding_point.group);
-    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
-  }
-}
-
-TEST_F(InspectorGetWorkgroupStorageSizeTest, Empty) {
-  MakeEmptyBodyFunction("ep_func",
-                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                           WorkgroupSize(1)});
-  Inspector& inspector = Build();
-  EXPECT_EQ(0u, inspector.GetWorkgroupStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetWorkgroupStorageSizeTest, Simple) {
-  AddWorkgroupStorage("wg_f32", ty.f32());
-  MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"f32_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kCompute),
-                             WorkgroupSize(1),
-                         });
-
-  Inspector& inspector = Build();
-  EXPECT_EQ(4u, inspector.GetWorkgroupStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetWorkgroupStorageSizeTest, CompoundTypes) {
-  // This struct should occupy 68 bytes. 4 from the i32 field, and another 64
-  // from the 4-element array with 16-byte stride.
-  auto* wg_struct_type = MakeStructType(
-      "WgStruct", {ty.i32(), ty.array(ty.i32(), 4, /*stride=*/16)},
-      /*is_block=*/false);
-  AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
-  MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
-                                          {{0, ty.i32()}});
-
-  // Plus another 4 bytes from this other workgroup-class f32.
-  AddWorkgroupStorage("wg_f32", ty.f32());
-  MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
-
-  MakeCallerBodyFunction("ep_func", {"wg_struct_func", "f32_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kCompute),
-                             WorkgroupSize(1),
-                         });
-
-  Inspector& inspector = Build();
-  EXPECT_EQ(72u, inspector.GetWorkgroupStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetWorkgroupStorageSizeTest, AlignmentPadding) {
-  // vec3<f32> has an alignment of 16 but a size of 12. We leverage this to test
-  // that our padded size calculation for workgroup storage is accurate.
-  AddWorkgroupStorage("wg_vec3", ty.vec3<f32>());
-  MakePlainGlobalReferenceBodyFunction("wg_func", "wg_vec3", ty.vec3<f32>(),
-                                       {});
-
-  MakeCallerBodyFunction("ep_func", {"wg_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kCompute),
-                             WorkgroupSize(1),
-                         });
-
-  Inspector& inspector = Build();
-  EXPECT_EQ(16u, inspector.GetWorkgroupStorageSize("ep_func"));
-}
-
-TEST_F(InspectorGetWorkgroupStorageSizeTest, StructAlignment) {
-  // Per WGSL spec, a struct's size is the offset its last member plus the size
-  // of its last member, rounded up to the alignment of its largest member. So
-  // here the struct is expected to occupy 1024 bytes of workgroup storage.
-  const auto* wg_struct_type = MakeStructTypeFromMembers(
-      "WgStruct",
-      {MakeStructMember(0, ty.f32(),
-                        {create<ast::StructMemberAlignAttribute>(1024)})},
-      /*is_block=*/false);
-
-  AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
-  MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
-                                          {{0, ty.f32()}});
-
-  MakeCallerBodyFunction("ep_func", {"wg_struct_func"},
-                         ast::AttributeList{
-                             Stage(ast::PipelineStage::kCompute),
-                             WorkgroupSize(1),
-                         });
-
-  Inspector& inspector = Build();
-  EXPECT_EQ(1024u, inspector.GetWorkgroupStorageSize("ep_func"));
-}
-
-// Crash was occuring in ::GenerateSamplerTargets, when
-// ::GetSamplerTextureUses was called.
-TEST_F(InspectorRegressionTest, tint967) {
-  std::string shader = R"(
-@group(0) @binding(1) var mySampler: sampler;
-@group(0) @binding(2) var myTexture: texture_2d<f32>;
-
-fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, uv);
-}
-
-@stage(fragment)
-fn main(@location(0) fragUV: vec2<f32>,
-        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
-  return doSample(myTexture, mySampler, fragUV) * fragPosition;
-})";
-
-  Inspector& inspector = Initialize(shader);
-  auto result = inspector.GetSamplerTextureUses("main");
-}
-
-}  // namespace
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/resource_binding.cc b/src/inspector/resource_binding.cc
deleted file mode 100644
index 4589ea0..0000000
--- a/src/inspector/resource_binding.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/inspector/resource_binding.h"
-
-#include "src/sem/array.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/matrix_type.h"
-#include "src/sem/type.h"
-#include "src/sem/u32_type.h"
-#include "src/sem/vector_type.h"
-
-namespace tint {
-namespace inspector {
-
-ResourceBinding::TextureDimension
-TypeTextureDimensionToResourceBindingTextureDimension(
-    const ast::TextureDimension& type_dim) {
-  switch (type_dim) {
-    case ast::TextureDimension::k1d:
-      return ResourceBinding::TextureDimension::k1d;
-    case ast::TextureDimension::k2d:
-      return ResourceBinding::TextureDimension::k2d;
-    case ast::TextureDimension::k2dArray:
-      return ResourceBinding::TextureDimension::k2dArray;
-    case ast::TextureDimension::k3d:
-      return ResourceBinding::TextureDimension::k3d;
-    case ast::TextureDimension::kCube:
-      return ResourceBinding::TextureDimension::kCube;
-    case ast::TextureDimension::kCubeArray:
-      return ResourceBinding::TextureDimension::kCubeArray;
-    case ast::TextureDimension::kNone:
-      return ResourceBinding::TextureDimension::kNone;
-  }
-  return ResourceBinding::TextureDimension::kNone;
-}
-
-ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type) {
-  if (!base_type) {
-    return ResourceBinding::SampledKind::kUnknown;
-  }
-
-  if (auto* at = base_type->As<sem::Array>()) {
-    base_type = at->ElemType();
-  } else if (auto* mt = base_type->As<sem::Matrix>()) {
-    base_type = mt->type();
-  } else if (auto* vt = base_type->As<sem::Vector>()) {
-    base_type = vt->type();
-  }
-
-  if (base_type->Is<sem::F32>()) {
-    return ResourceBinding::SampledKind::kFloat;
-  } else if (base_type->Is<sem::U32>()) {
-    return ResourceBinding::SampledKind::kUInt;
-  } else if (base_type->Is<sem::I32>()) {
-    return ResourceBinding::SampledKind::kSInt;
-  } else {
-    return ResourceBinding::SampledKind::kUnknown;
-  }
-}
-
-ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat(
-    const ast::TexelFormat& image_format) {
-  switch (image_format) {
-    case ast::TexelFormat::kR32Uint:
-      return ResourceBinding::TexelFormat::kR32Uint;
-    case ast::TexelFormat::kR32Sint:
-      return ResourceBinding::TexelFormat::kR32Sint;
-    case ast::TexelFormat::kR32Float:
-      return ResourceBinding::TexelFormat::kR32Float;
-    case ast::TexelFormat::kRgba8Unorm:
-      return ResourceBinding::TexelFormat::kRgba8Unorm;
-    case ast::TexelFormat::kRgba8Snorm:
-      return ResourceBinding::TexelFormat::kRgba8Snorm;
-    case ast::TexelFormat::kRgba8Uint:
-      return ResourceBinding::TexelFormat::kRgba8Uint;
-    case ast::TexelFormat::kRgba8Sint:
-      return ResourceBinding::TexelFormat::kRgba8Sint;
-    case ast::TexelFormat::kRg32Uint:
-      return ResourceBinding::TexelFormat::kRg32Uint;
-    case ast::TexelFormat::kRg32Sint:
-      return ResourceBinding::TexelFormat::kRg32Sint;
-    case ast::TexelFormat::kRg32Float:
-      return ResourceBinding::TexelFormat::kRg32Float;
-    case ast::TexelFormat::kRgba16Uint:
-      return ResourceBinding::TexelFormat::kRgba16Uint;
-    case ast::TexelFormat::kRgba16Sint:
-      return ResourceBinding::TexelFormat::kRgba16Sint;
-    case ast::TexelFormat::kRgba16Float:
-      return ResourceBinding::TexelFormat::kRgba16Float;
-    case ast::TexelFormat::kRgba32Uint:
-      return ResourceBinding::TexelFormat::kRgba32Uint;
-    case ast::TexelFormat::kRgba32Sint:
-      return ResourceBinding::TexelFormat::kRgba32Sint;
-    case ast::TexelFormat::kRgba32Float:
-      return ResourceBinding::TexelFormat::kRgba32Float;
-    case ast::TexelFormat::kNone:
-      return ResourceBinding::TexelFormat::kNone;
-  }
-  return ResourceBinding::TexelFormat::kNone;
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/resource_binding.h b/src/inspector/resource_binding.h
deleted file mode 100644
index b6457e2..0000000
--- a/src/inspector/resource_binding.h
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_INSPECTOR_RESOURCE_BINDING_H_
-#define SRC_INSPECTOR_RESOURCE_BINDING_H_
-
-#include <cstdint>
-
-#include "src/ast/storage_texture.h"
-#include "src/ast/texture.h"
-
-namespace tint {
-namespace inspector {
-
-/// Container for information about how a resource is bound
-struct ResourceBinding {
-  /// The dimensionality of a texture
-  enum class TextureDimension {
-    /// Invalid texture
-    kNone = -1,
-    /// 1 dimensional texture
-    k1d,
-    /// 2 dimensional texture
-    k2d,
-    /// 2 dimensional array texture
-    k2dArray,
-    /// 3 dimensional texture
-    k3d,
-    /// cube texture
-    kCube,
-    /// cube array texture
-    kCubeArray,
-  };
-
-  /// Component type of the texture's data. Same as the Sampled Type parameter
-  /// in SPIR-V OpTypeImage.
-  enum class SampledKind { kUnknown = -1, kFloat, kUInt, kSInt };
-
-  /// Enumerator of texel image formats
-  enum class TexelFormat {
-    kNone = -1,
-
-    kRgba8Unorm,
-    kRgba8Snorm,
-    kRgba8Uint,
-    kRgba8Sint,
-    kRgba16Uint,
-    kRgba16Sint,
-    kRgba16Float,
-    kR32Uint,
-    kR32Sint,
-    kR32Float,
-    kRg32Uint,
-    kRg32Sint,
-    kRg32Float,
-    kRgba32Uint,
-    kRgba32Sint,
-    kRgba32Float,
-  };
-
-  /// kXXX maps to entries returned by GetXXXResourceBindings call.
-  enum class ResourceType {
-    kUniformBuffer,
-    kStorageBuffer,
-    kReadOnlyStorageBuffer,
-    kSampler,
-    kComparisonSampler,
-    kSampledTexture,
-    kMultisampledTexture,
-    kWriteOnlyStorageTexture,
-    kDepthTexture,
-    kDepthMultisampledTexture,
-    kExternalTexture
-  };
-
-  /// Type of resource that is bound.
-  ResourceType resource_type;
-  /// Bind group the binding belongs
-  uint32_t bind_group;
-  /// Identifier to identify this binding within the bind group
-  uint32_t binding;
-  /// Size for this binding, in bytes, if defined.
-  uint64_t size;
-  /// Size for this binding without trailing structure padding, in bytes, if
-  /// defined.
-  uint64_t size_no_padding;
-  /// Dimensionality of this binding, if defined.
-  TextureDimension dim;
-  /// Kind of data being sampled, if defined.
-  SampledKind sampled_kind;
-  /// Format of data, if defined.
-  TexelFormat image_format;
-};
-
-/// Convert from internal ast::TextureDimension to public
-/// ResourceBinding::TextureDimension
-/// @param type_dim internal value to convert from
-/// @returns the publicly visible equivalent
-ResourceBinding::TextureDimension
-TypeTextureDimensionToResourceBindingTextureDimension(
-    const ast::TextureDimension& type_dim);
-
-/// Infer ResourceBinding::SampledKind for a given sem::Type
-/// @param base_type internal type to infer from
-/// @returns the publicly visible equivalent
-ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type);
-
-/// Convert from internal ast::TexelFormat to public
-/// ResourceBinding::TexelFormat
-/// @param image_format internal value to convert from
-/// @returns the publicly visible equivalent
-ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat(
-    const ast::TexelFormat& image_format);
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_RESOURCE_BINDING_H_
diff --git a/src/inspector/scalar.cc b/src/inspector/scalar.cc
deleted file mode 100644
index cc1ce78..0000000
--- a/src/inspector/scalar.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2020 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/inspector/scalar.h"
-
-namespace tint {
-namespace inspector {
-
-Scalar::Scalar() : type_(kNull) {}
-
-Scalar::Scalar(bool val) : type_(kBool) {
-  value_.b = val;
-}
-
-Scalar::Scalar(uint32_t val) : type_(kU32) {
-  value_.u = val;
-}
-
-Scalar::Scalar(int32_t val) : type_(kI32) {
-  value_.i = val;
-}
-
-Scalar::Scalar(float val) : type_(kFloat) {
-  value_.f = val;
-}
-
-bool Scalar::IsNull() const {
-  return type_ == kNull;
-}
-
-bool Scalar::IsBool() const {
-  return type_ == kBool;
-}
-
-bool Scalar::IsU32() const {
-  return type_ == kU32;
-}
-
-bool Scalar::IsI32() const {
-  return type_ == kI32;
-}
-
-bool Scalar::IsFloat() const {
-  return type_ == kFloat;
-}
-
-bool Scalar::AsBool() const {
-  return value_.b;
-}
-
-uint32_t Scalar::AsU32() const {
-  return value_.u;
-}
-
-int32_t Scalar::AsI32() const {
-  return value_.i;
-}
-
-float Scalar::AsFloat() const {
-  return value_.f;
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/scalar.h b/src/inspector/scalar.h
deleted file mode 100644
index 4768b94..0000000
--- a/src/inspector/scalar.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 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_INSPECTOR_SCALAR_H_
-#define SRC_INSPECTOR_SCALAR_H_
-
-#include <cstdint>
-
-namespace tint {
-namespace inspector {
-
-/// Contains a literal scalar value
-class Scalar {
- public:
-  /// Null Constructor
-  Scalar();
-  /// @param val literal scalar value to contain
-  explicit Scalar(bool val);
-  /// @param val literal scalar value to contain
-  explicit Scalar(uint32_t val);
-  /// @param val literal scalar value to contain
-  explicit Scalar(int32_t val);
-  /// @param val literal scalar value to contain
-  explicit Scalar(float val);
-
-  /// @returns true if this is a null
-  bool IsNull() const;
-  /// @returns true if this is a bool
-  bool IsBool() const;
-  /// @returns true if this is a unsigned integer.
-  bool IsU32() const;
-  /// @returns true if this is a signed integer.
-  bool IsI32() const;
-  /// @returns true if this is a float.
-  bool IsFloat() const;
-
-  /// @returns scalar value if bool, otherwise undefined behaviour.
-  bool AsBool() const;
-  /// @returns scalar value if unsigned integer, otherwise undefined behaviour.
-  uint32_t AsU32() const;
-  /// @returns scalar value if signed integer, otherwise undefined behaviour.
-  int32_t AsI32() const;
-  /// @returns scalar value if float, otherwise undefined behaviour.
-  float AsFloat() const;
-
- private:
-  typedef enum {
-    kNull,
-    kBool,
-    kU32,
-    kI32,
-    kFloat,
-  } Type;
-
-  typedef union {
-    bool b;
-    uint32_t u;
-    int32_t i;
-    float f;
-  } Value;
-
-  Type type_;
-  Value value_;
-};
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_SCALAR_H_
diff --git a/src/inspector/test_inspector_builder.cc b/src/inspector/test_inspector_builder.cc
deleted file mode 100644
index a0b84b2..0000000
--- a/src/inspector/test_inspector_builder.cc
+++ /dev/null
@@ -1,405 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/inspector/test_inspector_builder.h"
-
-#include <memory>
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace inspector {
-
-InspectorBuilder::InspectorBuilder() = default;
-InspectorBuilder::~InspectorBuilder() = default;
-
-void InspectorBuilder::MakeEmptyBodyFunction(std::string name,
-                                             ast::AttributeList attributes) {
-  Func(name, ast::VariableList(), ty.void_(), ast::StatementList{Return()},
-       attributes);
-}
-
-void InspectorBuilder::MakeCallerBodyFunction(std::string caller,
-                                              std::vector<std::string> callees,
-                                              ast::AttributeList attributes) {
-  ast::StatementList body;
-  body.reserve(callees.size() + 1);
-  for (auto callee : callees) {
-    body.push_back(CallStmt(Call(callee)));
-  }
-  body.push_back(Return());
-
-  Func(caller, ast::VariableList(), ty.void_(), body, attributes);
-}
-
-const ast::Struct* InspectorBuilder::MakeInOutStruct(
-    std::string name,
-    std::vector<std::tuple<std::string, uint32_t>> inout_vars) {
-  ast::StructMemberList members;
-  for (auto var : inout_vars) {
-    std::string member_name;
-    uint32_t location;
-    std::tie(member_name, location) = var;
-    members.push_back(
-        Member(member_name, ty.u32(), {Location(location), Flat()}));
-  }
-  return Structure(name, members);
-}
-
-const ast::Function* InspectorBuilder::MakePlainGlobalReferenceBodyFunction(
-    std::string func,
-    std::string var,
-    const ast::Type* type,
-    ast::AttributeList attributes) {
-  ast::StatementList stmts;
-  stmts.emplace_back(Decl(Var("local_" + var, type)));
-  stmts.emplace_back(Assign("local_" + var, var));
-  stmts.emplace_back(Return());
-
-  return Func(func, ast::VariableList(), ty.void_(), stmts, attributes);
-}
-
-bool InspectorBuilder::ContainsName(const std::vector<StageVariable>& vec,
-                                    const std::string& name) {
-  for (auto& s : vec) {
-    if (s.name == name) {
-      return true;
-    }
-  }
-  return false;
-}
-
-std::string InspectorBuilder::StructMemberName(size_t idx,
-                                               const ast::Type* type) {
-  return std::to_string(idx) + type->FriendlyName(Symbols());
-}
-
-const ast::Struct* InspectorBuilder::MakeStructType(
-    const std::string& name,
-    std::vector<const ast::Type*> member_types,
-    bool is_block) {
-  ast::StructMemberList members;
-  for (auto* type : member_types) {
-    members.push_back(MakeStructMember(members.size(), type, {}));
-  }
-  return MakeStructTypeFromMembers(name, std::move(members), is_block);
-}
-
-const ast::Struct* InspectorBuilder::MakeStructTypeFromMembers(
-    const std::string& name,
-    ast::StructMemberList members,
-    bool is_block) {
-  ast::AttributeList attrs;
-  if (is_block) {
-    attrs.push_back(create<ast::StructBlockAttribute>());
-  }
-  return Structure(name, std::move(members), attrs);
-}
-
-const ast::StructMember* InspectorBuilder::MakeStructMember(
-    size_t index,
-    const ast::Type* type,
-    ast::AttributeList attributes) {
-  return Member(StructMemberName(index, type), type, std::move(attributes));
-}
-
-const ast::Struct* InspectorBuilder::MakeUniformBufferType(
-    const std::string& name,
-    std::vector<const ast::Type*> member_types) {
-  return MakeStructType(name, member_types, true);
-}
-
-std::function<const ast::TypeName*()> InspectorBuilder::MakeStorageBufferTypes(
-    const std::string& name,
-    std::vector<const ast::Type*> member_types) {
-  MakeStructType(name, member_types, true);
-  return [this, name] { return ty.type_name(name); };
-}
-
-void InspectorBuilder::AddUniformBuffer(const std::string& name,
-                                        const ast::Type* type,
-                                        uint32_t group,
-                                        uint32_t binding) {
-  Global(name, type, ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-void InspectorBuilder::AddWorkgroupStorage(const std::string& name,
-                                           const ast::Type* type) {
-  Global(name, type, ast::StorageClass::kWorkgroup);
-}
-
-void InspectorBuilder::AddStorageBuffer(const std::string& name,
-                                        const ast::Type* type,
-                                        ast::Access access,
-                                        uint32_t group,
-                                        uint32_t binding) {
-  Global(name, type, ast::StorageClass::kStorage, access,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
-    std::string func_name,
-    std::string struct_name,
-    std::vector<std::tuple<size_t, const ast::Type*>> members) {
-  ast::StatementList stmts;
-  for (auto member : members) {
-    size_t member_idx;
-    const ast::Type* member_type;
-    std::tie(member_idx, member_type) = member;
-    std::string member_name = StructMemberName(member_idx, member_type);
-
-    stmts.emplace_back(Decl(Var("local" + member_name, member_type)));
-  }
-
-  for (auto member : members) {
-    size_t member_idx;
-    const ast::Type* member_type;
-    std::tie(member_idx, member_type) = member;
-    std::string member_name = StructMemberName(member_idx, member_type);
-
-    stmts.emplace_back(Assign("local" + member_name,
-                              MemberAccessor(struct_name, member_name)));
-  }
-
-  stmts.emplace_back(Return());
-
-  Func(func_name, ast::VariableList(), ty.void_(), stmts, ast::AttributeList{});
-}
-
-void InspectorBuilder::AddSampler(const std::string& name,
-                                  uint32_t group,
-                                  uint32_t binding) {
-  Global(name, sampler_type(),
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-void InspectorBuilder::AddComparisonSampler(const std::string& name,
-                                            uint32_t group,
-                                            uint32_t binding) {
-  Global(name, comparison_sampler_type(),
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-void InspectorBuilder::AddResource(const std::string& name,
-                                   const ast::Type* type,
-                                   uint32_t group,
-                                   uint32_t binding) {
-  Global(name, type,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-void InspectorBuilder::AddGlobalVariable(const std::string& name,
-                                         const ast::Type* type) {
-  Global(name, type, ast::StorageClass::kPrivate);
-}
-
-const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction(
-    const std::string& func_name,
-    const std::string& texture_name,
-    const std::string& sampler_name,
-    const std::string& coords_name,
-    const ast::Type* base_type,
-    ast::AttributeList attributes) {
-  std::string result_name = "sampler_result";
-
-  ast::StatementList stmts;
-  stmts.emplace_back(Decl(Var(result_name, ty.vec(base_type, 4))));
-
-  stmts.emplace_back(Assign(result_name, Call("textureSample", texture_name,
-                                              sampler_name, coords_name)));
-  stmts.emplace_back(Return());
-
-  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
-}
-
-const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction(
-    const std::string& func_name,
-    const std::string& texture_name,
-    const std::string& sampler_name,
-    const std::string& coords_name,
-    const std::string& array_index,
-    const ast::Type* base_type,
-    ast::AttributeList attributes) {
-  std::string result_name = "sampler_result";
-
-  ast::StatementList stmts;
-
-  stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4))));
-
-  stmts.emplace_back(
-      Assign("sampler_result", Call("textureSample", texture_name, sampler_name,
-                                    coords_name, array_index)));
-  stmts.emplace_back(Return());
-
-  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
-}
-
-const ast::Function*
-InspectorBuilder::MakeComparisonSamplerReferenceBodyFunction(
-    const std::string& func_name,
-    const std::string& texture_name,
-    const std::string& sampler_name,
-    const std::string& coords_name,
-    const std::string& depth_name,
-    const ast::Type* base_type,
-    ast::AttributeList attributes) {
-  std::string result_name = "sampler_result";
-
-  ast::StatementList stmts;
-
-  stmts.emplace_back(Decl(Var("sampler_result", base_type)));
-  stmts.emplace_back(
-      Assign("sampler_result", Call("textureSampleCompare", texture_name,
-                                    sampler_name, coords_name, depth_name)));
-  stmts.emplace_back(Return());
-
-  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
-}
-
-const ast::Type* InspectorBuilder::GetBaseType(
-    ResourceBinding::SampledKind sampled_kind) {
-  switch (sampled_kind) {
-    case ResourceBinding::SampledKind::kFloat:
-      return ty.f32();
-    case ResourceBinding::SampledKind::kSInt:
-      return ty.i32();
-    case ResourceBinding::SampledKind::kUInt:
-      return ty.u32();
-    default:
-      return nullptr;
-  }
-}
-
-const ast::Type* InspectorBuilder::GetCoordsType(ast::TextureDimension dim,
-                                                 const ast::Type* scalar) {
-  switch (dim) {
-    case ast::TextureDimension::k1d:
-      return scalar;
-    case ast::TextureDimension::k2d:
-    case ast::TextureDimension::k2dArray:
-      return create<ast::Vector>(scalar, 2);
-    case ast::TextureDimension::k3d:
-    case ast::TextureDimension::kCube:
-    case ast::TextureDimension::kCubeArray:
-      return create<ast::Vector>(scalar, 3);
-    default:
-      [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
-  }
-  return nullptr;
-}
-
-const ast::Type* InspectorBuilder::MakeStorageTextureTypes(
-    ast::TextureDimension dim,
-    ast::TexelFormat format) {
-  return ty.storage_texture(dim, format, ast::Access::kWrite);
-}
-
-void InspectorBuilder::AddStorageTexture(const std::string& name,
-                                         const ast::Type* type,
-                                         uint32_t group,
-                                         uint32_t binding) {
-  Global(name, type,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(binding),
-             create<ast::GroupAttribute>(group),
-         });
-}
-
-const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction(
-    const std::string& func_name,
-    const std::string& st_name,
-    const ast::Type* dim_type,
-    ast::AttributeList attributes) {
-  ast::StatementList stmts;
-
-  stmts.emplace_back(Decl(Var("dim", dim_type)));
-  stmts.emplace_back(Assign("dim", Call("textureDimensions", st_name)));
-  stmts.emplace_back(Return());
-
-  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
-}
-
-std::function<const ast::Type*()> InspectorBuilder::GetTypeFunction(
-    ComponentType component,
-    CompositionType composition) {
-  std::function<const ast::Type*()> func;
-  switch (component) {
-    case ComponentType::kFloat:
-      func = [this]() -> const ast::Type* { return ty.f32(); };
-      break;
-    case ComponentType::kSInt:
-      func = [this]() -> const ast::Type* { return ty.i32(); };
-      break;
-    case ComponentType::kUInt:
-      func = [this]() -> const ast::Type* { return ty.u32(); };
-      break;
-    case ComponentType::kUnknown:
-      return []() -> const ast::Type* { return nullptr; };
-  }
-
-  uint32_t n;
-  switch (composition) {
-    case CompositionType::kScalar:
-      return func;
-    case CompositionType::kVec2:
-      n = 2;
-      break;
-    case CompositionType::kVec3:
-      n = 3;
-      break;
-    case CompositionType::kVec4:
-      n = 4;
-      break;
-    default:
-      return []() -> ast::Type* { return nullptr; };
-  }
-
-  return [this, func, n]() -> const ast::Type* { return ty.vec(func(), n); };
-}
-
-Inspector& InspectorBuilder::Build() {
-  if (inspector_) {
-    return *inspector_;
-  }
-  program_ = std::make_unique<Program>(std::move(*this));
-  [&]() {
-    ASSERT_TRUE(program_->IsValid())
-        << diag::Formatter().format(program_->Diagnostics());
-  }();
-  inspector_ = std::make_unique<Inspector>(program_.get());
-  return *inspector_;
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/test_inspector_builder.h b/src/inspector/test_inspector_builder.h
deleted file mode 100644
index 64f5c00..0000000
--- a/src/inspector/test_inspector_builder.h
+++ /dev/null
@@ -1,391 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
-#define SRC_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
-
-#include <memory>
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/variable.h"
-#include "tint/tint.h"
-
-namespace tint {
-namespace inspector {
-
-/// Utility class for building programs in inspector tests
-class InspectorBuilder : public ProgramBuilder {
- public:
-  InspectorBuilder();
-  ~InspectorBuilder() override;
-
-  /// Generates an empty function
-  /// @param name name of the function created
-  /// @param attributes the function attributes
-  void MakeEmptyBodyFunction(std::string name, ast::AttributeList attributes);
-
-  /// Generates a function that calls other functions
-  /// @param caller name of the function created
-  /// @param callees names of the functions to be called
-  /// @param attributes the function attributes
-  void MakeCallerBodyFunction(std::string caller,
-                              std::vector<std::string> callees,
-                              ast::AttributeList attributes);
-
-  /// Generates a struct that contains user-defined IO members
-  /// @param name the name of the generated struct
-  /// @param inout_vars tuples of {name, loc} that will be the struct members
-  /// @returns a structure object
-  const ast::Struct* MakeInOutStruct(
-      std::string name,
-      std::vector<std::tuple<std::string, uint32_t>> inout_vars);
-
-  // TODO(crbug.com/tint/697): Remove this.
-  /// Add In/Out variables to the global variables
-  /// @param inout_vars tuples of {in, out} that will be added as entries to the
-  ///                   global variables
-  void AddInOutVariables(
-      std::vector<std::tuple<std::string, std::string>> inout_vars);
-
-  // TODO(crbug.com/tint/697): Remove this.
-  /// Generates a function that references in/out variables
-  /// @param name name of the function created
-  /// @param inout_vars tuples of {in, out} that will be converted into out = in
-  ///                   calls in the function body
-  /// @param attributes the function attributes
-  void MakeInOutVariableBodyFunction(
-      std::string name,
-      std::vector<std::tuple<std::string, std::string>> inout_vars,
-      ast::AttributeList attributes);
-
-  // TODO(crbug.com/tint/697): Remove this.
-  /// Generates a function that references in/out variables and calls another
-  /// function.
-  /// @param caller name of the function created
-  /// @param callee name of the function to be called
-  /// @param inout_vars tuples of {in, out} that will be converted into out = in
-  ///                   calls in the function body
-  /// @param attributes the function attributes
-  /// @returns a function object
-  const ast::Function* MakeInOutVariableCallerBodyFunction(
-      std::string caller,
-      std::string callee,
-      std::vector<std::tuple<std::string, std::string>> inout_vars,
-      ast::AttributeList attributes);
-
-  /// Add a pipeline constant to the global variables, with a specific ID.
-  /// @param name name of the variable to add
-  /// @param id id number for the constant id
-  /// @param type type of the variable
-  /// @param constructor val to initialize the constant with, if NULL no
-  ///             constructor will be added.
-  /// @returns the constant that was created
-  const ast::Variable* AddOverridableConstantWithID(
-      std::string name,
-      uint32_t id,
-      const ast::Type* type,
-      const ast::Expression* constructor) {
-    return Override(name, type, constructor, {Id(id)});
-  }
-
-  /// Add a pipeline constant to the global variables, without a specific ID.
-  /// @param name name of the variable to add
-  /// @param type type of the variable
-  /// @param constructor val to initialize the constant with, if NULL no
-  ///             constructor will be added.
-  /// @returns the constant that was created
-  const ast::Variable* AddOverridableConstantWithoutID(
-      std::string name,
-      const ast::Type* type,
-      const ast::Expression* constructor) {
-    return Override(name, type, constructor);
-  }
-
-  /// Generates a function that references module-scoped, plain-typed constant
-  /// or variable.
-  /// @param func name of the function created
-  /// @param var name of the constant to be reference
-  /// @param type type of the const being referenced
-  /// @param attributes the function attributes
-  /// @returns a function object
-  const ast::Function* MakePlainGlobalReferenceBodyFunction(
-      std::string func,
-      std::string var,
-      const ast::Type* type,
-      ast::AttributeList attributes);
-
-  /// @param vec Vector of StageVariable to be searched
-  /// @param name Name to be searching for
-  /// @returns true if name is in vec, otherwise false
-  bool ContainsName(const std::vector<StageVariable>& vec,
-                    const std::string& name);
-
-  /// Builds a string for accessing a member in a generated struct
-  /// @param idx index of member
-  /// @param type type of member
-  /// @returns a string for the member
-  std::string StructMemberName(size_t idx, const ast::Type* type);
-
-  /// Generates a struct type
-  /// @param name name for the type
-  /// @param member_types a vector of member types
-  /// @param is_block whether or not to decorate as a Block
-  /// @returns a struct type
-  const ast::Struct* MakeStructType(const std::string& name,
-                                    std::vector<const ast::Type*> member_types,
-                                    bool is_block);
-
-  /// Generates a struct type from a list of member nodes.
-  /// @param name name for the struct type
-  /// @param members a vector of members
-  /// @param is_block whether or not to decorate as a Block
-  /// @returns a struct type
-  const ast::Struct* MakeStructTypeFromMembers(const std::string& name,
-                                               ast::StructMemberList members,
-                                               bool is_block);
-
-  /// Generates a struct member with a specified index and type.
-  /// @param index index of the field within the struct
-  /// @param type the type of the member field
-  /// @param attributes a list of attributes to apply to the member field
-  /// @returns a struct member
-  const ast::StructMember* MakeStructMember(size_t index,
-                                            const ast::Type* type,
-                                            ast::AttributeList attributes);
-
-  /// Generates types appropriate for using in an uniform buffer
-  /// @param name name for the type
-  /// @param member_types a vector of member types
-  /// @returns a struct type that has the layout for an uniform buffer.
-  const ast::Struct* MakeUniformBufferType(
-      const std::string& name,
-      std::vector<const ast::Type*> member_types);
-
-  /// Generates types appropriate for using in a storage buffer
-  /// @param name name for the type
-  /// @param member_types a vector of member types
-  /// @returns a function that returns the created structure.
-  std::function<const ast::TypeName*()> MakeStorageBufferTypes(
-      const std::string& name,
-      std::vector<const ast::Type*> member_types);
-
-  /// Adds an uniform buffer variable to the program
-  /// @param name the name of the variable
-  /// @param type the type to use
-  /// @param group the binding/group/ to use for the uniform buffer
-  /// @param binding the binding number to use for the uniform buffer
-  void AddUniformBuffer(const std::string& name,
-                        const ast::Type* type,
-                        uint32_t group,
-                        uint32_t binding);
-
-  /// Adds a workgroup storage variable to the program
-  /// @param name the name of the variable
-  /// @param type the type of the variable
-  void AddWorkgroupStorage(const std::string& name, const ast::Type* type);
-
-  /// Adds a storage buffer variable to the program
-  /// @param name the name of the variable
-  /// @param type the type to use
-  /// @param access the storage buffer access control
-  /// @param group the binding/group to use for the storage buffer
-  /// @param binding the binding number to use for the storage buffer
-  void AddStorageBuffer(const std::string& name,
-                        const ast::Type* type,
-                        ast::Access access,
-                        uint32_t group,
-                        uint32_t binding);
-
-  /// Generates a function that references a specific struct variable
-  /// @param func_name name of the function created
-  /// @param struct_name name of the struct variabler to be accessed
-  /// @param members list of members to access, by index and type
-  void MakeStructVariableReferenceBodyFunction(
-      std::string func_name,
-      std::string struct_name,
-      std::vector<std::tuple<size_t, const ast::Type*>> members);
-
-  /// Adds a regular sampler variable to the program
-  /// @param name the name of the variable
-  /// @param group the binding/group to use for the storage buffer
-  /// @param binding the binding number to use for the storage buffer
-  void AddSampler(const std::string& name, uint32_t group, uint32_t binding);
-
-  /// Adds a comparison sampler variable to the program
-  /// @param name the name of the variable
-  /// @param group the binding/group to use for the storage buffer
-  /// @param binding the binding number to use for the storage buffer
-  void AddComparisonSampler(const std::string& name,
-                            uint32_t group,
-                            uint32_t binding);
-
-  /// Adds a sampler or texture variable to the program
-  /// @param name the name of the variable
-  /// @param type the type to use
-  /// @param group the binding/group to use for the resource
-  /// @param binding the binding number to use for the resource
-  void AddResource(const std::string& name,
-                   const ast::Type* type,
-                   uint32_t group,
-                   uint32_t binding);
-
-  /// Add a module scope private variable to the progames
-  /// @param name the name of the variable
-  /// @param type the type to use
-  void AddGlobalVariable(const std::string& name, const ast::Type* type);
-
-  /// Generates a function that references a specific sampler variable
-  /// @param func_name name of the function created
-  /// @param texture_name name of the texture to be sampled
-  /// @param sampler_name name of the sampler to use
-  /// @param coords_name name of the coords variable to use
-  /// @param base_type sampler base type
-  /// @param attributes the function attributes
-  /// @returns a function that references all of the values specified
-  const ast::Function* MakeSamplerReferenceBodyFunction(
-      const std::string& func_name,
-      const std::string& texture_name,
-      const std::string& sampler_name,
-      const std::string& coords_name,
-      const ast::Type* base_type,
-      ast::AttributeList attributes);
-
-  /// Generates a function that references a specific sampler variable
-  /// @param func_name name of the function created
-  /// @param texture_name name of the texture to be sampled
-  /// @param sampler_name name of the sampler to use
-  /// @param coords_name name of the coords variable to use
-  /// @param array_index name of the array index variable to use
-  /// @param base_type sampler base type
-  /// @param attributes the function attributes
-  /// @returns a function that references all of the values specified
-  const ast::Function* MakeSamplerReferenceBodyFunction(
-      const std::string& func_name,
-      const std::string& texture_name,
-      const std::string& sampler_name,
-      const std::string& coords_name,
-      const std::string& array_index,
-      const ast::Type* base_type,
-      ast::AttributeList attributes);
-
-  /// Generates a function that references a specific comparison sampler
-  /// variable.
-  /// @param func_name name of the function created
-  /// @param texture_name name of the depth texture to  use
-  /// @param sampler_name name of the sampler to use
-  /// @param coords_name name of the coords variable to use
-  /// @param depth_name name of the depth reference to use
-  /// @param base_type sampler base type
-  /// @param attributes the function attributes
-  /// @returns a function that references all of the values specified
-  const ast::Function* MakeComparisonSamplerReferenceBodyFunction(
-      const std::string& func_name,
-      const std::string& texture_name,
-      const std::string& sampler_name,
-      const std::string& coords_name,
-      const std::string& depth_name,
-      const ast::Type* base_type,
-      ast::AttributeList attributes);
-
-  /// Gets an appropriate type for the data in a given texture type.
-  /// @param sampled_kind type of in the texture
-  /// @returns a pointer to a type appropriate for the coord param
-  const ast::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind);
-
-  /// Gets an appropriate type for the coords parameter depending the the
-  /// dimensionality of the texture being sampled.
-  /// @param dim dimensionality of the texture being sampled
-  /// @param scalar the scalar type
-  /// @returns a pointer to a type appropriate for the coord param
-  const ast::Type* GetCoordsType(ast::TextureDimension dim,
-                                 const ast::Type* scalar);
-
-  /// Generates appropriate types for a Read-Only StorageTexture
-  /// @param dim the texture dimension of the storage texture
-  /// @param format the texel format of the storage texture
-  /// @returns the storage texture type
-  const ast::Type* MakeStorageTextureTypes(ast::TextureDimension dim,
-                                           ast::TexelFormat format);
-
-  /// Adds a storage texture variable to the program
-  /// @param name the name of the variable
-  /// @param type the type to use
-  /// @param group the binding/group to use for the sampled texture
-  /// @param binding the binding57 number to use for the sampled texture
-  void AddStorageTexture(const std::string& name,
-                         const ast::Type* type,
-                         uint32_t group,
-                         uint32_t binding);
-
-  /// Generates a function that references a storage texture variable.
-  /// @param func_name name of the function created
-  /// @param st_name name of the storage texture to use
-  /// @param dim_type type expected by textureDimensons to return
-  /// @param attributes the function attributes
-  /// @returns a function that references all of the values specified
-  const ast::Function* MakeStorageTextureBodyFunction(
-      const std::string& func_name,
-      const std::string& st_name,
-      const ast::Type* dim_type,
-      ast::AttributeList attributes);
-
-  /// Get a generator function that returns a type appropriate for a stage
-  /// variable with the given combination of component and composition type.
-  /// @param component component type of the stage variable
-  /// @param composition composition type of the stage variable
-  /// @returns a generator function for the stage variable's type.
-  std::function<const ast::Type*()> GetTypeFunction(
-      ComponentType component,
-      CompositionType composition);
-
-  /// Build the Program given all of the previous methods called and return an
-  /// Inspector for it.
-  /// Should only be called once per test.
-  /// @returns a reference to the Inspector for the built Program.
-  Inspector& Build();
-
-  /// @returns the type for a SamplerKind::kSampler
-  const ast::Sampler* sampler_type() {
-    return ty.sampler(ast::SamplerKind::kSampler);
-  }
-
-  /// @returns the type for a SamplerKind::kComparison
-  const ast::Sampler* comparison_sampler_type() {
-    return ty.sampler(ast::SamplerKind::kComparisonSampler);
-  }
-
- protected:
-  /// Program built by this builder.
-  std::unique_ptr<Program> program_;
-  /// Inspector for |program_|
-  std::unique_ptr<Inspector> inspector_;
-};
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
diff --git a/src/inspector/test_inspector_runner.cc b/src/inspector/test_inspector_runner.cc
deleted file mode 100644
index b7c6c7a..0000000
--- a/src/inspector/test_inspector_runner.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/inspector/test_inspector_runner.h"
-
-namespace tint {
-namespace inspector {
-
-InspectorRunner::InspectorRunner() = default;
-InspectorRunner::~InspectorRunner() = default;
-
-Inspector& InspectorRunner::Initialize(std::string shader) {
-  if (inspector_) {
-    return *inspector_;
-  }
-
-  file_ = std::make_unique<Source::File>("test", shader);
-  program_ = std::make_unique<Program>(reader::wgsl::Parse(file_.get()));
-  [&]() {
-    ASSERT_TRUE(program_->IsValid())
-        << diag::Formatter().format(program_->Diagnostics());
-  }();
-  inspector_ = std::make_unique<Inspector>(program_.get());
-  return *inspector_;
-}
-
-}  // namespace inspector
-}  // namespace tint
diff --git a/src/inspector/test_inspector_runner.h b/src/inspector/test_inspector_runner.h
deleted file mode 100644
index d405442..0000000
--- a/src/inspector/test_inspector_runner.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
-#define SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
-
-#include <memory>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "tint/tint.h"
-
-namespace tint {
-namespace inspector {
-
-/// Utility class for running shaders in inspector tests
-class InspectorRunner {
- public:
-  InspectorRunner();
-  virtual ~InspectorRunner();
-
-  /// Create a Program with Inspector from the provided WGSL shader.
-  /// Should only be called once per test.
-  /// @param shader a WGSL shader
-  /// @returns a reference to the Inspector for the built Program.
-  Inspector& Initialize(std::string shader);
-
- protected:
-  /// File created from input shader and used to create Program.
-  std::unique_ptr<Source::File> file_;
-  /// Program created by this runner.
-  std::unique_ptr<Program> program_;
-  /// Inspector for |program_|
-  std::unique_ptr<Inspector> inspector_;
-};
-
-}  // namespace inspector
-}  // namespace tint
-
-#endif  // SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
diff --git a/src/program.cc b/src/program.cc
deleted file mode 100644
index e1fdafb..0000000
--- a/src/program.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/program.h"
-
-#include <utility>
-
-#include "src/demangler.h"
-#include "src/resolver/resolver.h"
-#include "src/sem/expression.h"
-
-namespace tint {
-namespace {
-
-std::string DefaultPrinter(const Program*) {
-  return "<no program printer assigned>";
-}
-
-}  // namespace
-
-Program::Printer Program::printer = DefaultPrinter;
-
-Program::Program() = default;
-
-Program::Program(Program&& program)
-    : id_(std::move(program.id_)),
-      types_(std::move(program.types_)),
-      ast_nodes_(std::move(program.ast_nodes_)),
-      sem_nodes_(std::move(program.sem_nodes_)),
-      ast_(std::move(program.ast_)),
-      sem_(std::move(program.sem_)),
-      symbols_(std::move(program.symbols_)),
-      diagnostics_(std::move(program.diagnostics_)),
-      is_valid_(program.is_valid_) {
-  program.AssertNotMoved();
-  program.moved_ = true;
-}
-
-Program::Program(ProgramBuilder&& builder) {
-  id_ = builder.ID();
-
-  is_valid_ = builder.IsValid();
-  if (builder.ResolveOnBuild() && builder.IsValid()) {
-    resolver::Resolver resolver(&builder);
-    if (!resolver.Resolve()) {
-      is_valid_ = false;
-    }
-  }
-
-  // The above must be called *before* the calls to std::move() below
-  types_ = std::move(builder.Types());
-  ast_nodes_ = std::move(builder.ASTNodes());
-  sem_nodes_ = std::move(builder.SemNodes());
-  ast_ = &builder.AST();  // ast::Module is actually a heap allocation.
-  sem_ = std::move(builder.Sem());
-  symbols_ = std::move(builder.Symbols());
-  diagnostics_.add(std::move(builder.Diagnostics()));
-  builder.MarkAsMoved();
-
-  if (!is_valid_ && !diagnostics_.contains_errors()) {
-    // If the builder claims to be invalid, then we really should have an error
-    // message generated. If we find a situation where the program is not valid
-    // and there are no errors reported, add one here.
-    diagnostics_.add_error(diag::System::Program, "invalid program generated");
-  }
-}
-
-Program::~Program() = default;
-
-Program& Program::operator=(Program&& program) {
-  program.AssertNotMoved();
-  program.moved_ = true;
-  moved_ = false;
-  id_ = std::move(program.id_);
-  types_ = std::move(program.types_);
-  ast_nodes_ = std::move(program.ast_nodes_);
-  sem_nodes_ = std::move(program.sem_nodes_);
-  ast_ = std::move(program.ast_);
-  sem_ = std::move(program.sem_);
-  symbols_ = std::move(program.symbols_);
-  diagnostics_ = std::move(program.diagnostics_);
-  is_valid_ = program.is_valid_;
-  return *this;
-}
-
-Program Program::Clone() const {
-  AssertNotMoved();
-  return Program(CloneAsBuilder());
-}
-
-ProgramBuilder Program::CloneAsBuilder() const {
-  AssertNotMoved();
-  ProgramBuilder out;
-  CloneContext(&out, this).Clone();
-  return out;
-}
-
-bool Program::IsValid() const {
-  AssertNotMoved();
-  return is_valid_;
-}
-
-const sem::Type* Program::TypeOf(const ast::Expression* expr) const {
-  auto* sem = Sem().Get(expr);
-  return sem ? sem->Type() : nullptr;
-}
-
-const sem::Type* Program::TypeOf(const ast::Type* type) const {
-  return Sem().Get(type);
-}
-
-const sem::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
-  return Sem().Get(type_decl);
-}
-
-void Program::AssertNotMoved() const {
-  TINT_ASSERT(Program, !moved_);
-}
-
-}  // namespace tint
diff --git a/src/program.h b/src/program.h
deleted file mode 100644
index a44c692..0000000
--- a/src/program.h
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_PROGRAM_H_
-#define SRC_PROGRAM_H_
-
-#include <string>
-#include <unordered_set>
-
-#include "src/ast/function.h"
-#include "src/program_id.h"
-#include "src/sem/info.h"
-#include "src/sem/type_manager.h"
-#include "src/symbol_table.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace ast {
-
-class Module;
-
-}  // namespace ast
-
-/// Program holds the AST, Type information and SymbolTable for a tint program.
-class Program {
- public:
-  /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node>
-  using ASTNodeAllocator = BlockAllocator<ast::Node>;
-
-  /// SemNodeAllocator is an alias to BlockAllocator<sem::Node>
-  using SemNodeAllocator = BlockAllocator<sem::Node>;
-
-  /// Constructor
-  Program();
-
-  /// Move constructor
-  /// @param rhs the Program to move
-  Program(Program&& rhs);
-
-  /// Move constructor from builder
-  /// @param builder the builder used to construct the program
-  explicit Program(ProgramBuilder&& builder);
-
-  /// Destructor
-  ~Program();
-
-  /// Move assignment operator
-  /// @param rhs the Program to move
-  /// @return this Program
-  Program& operator=(Program&& rhs);
-
-  /// @returns the unique identifier for this program
-  ProgramID ID() const { return id_; }
-
-  /// @returns a reference to the program's types
-  const sem::Manager& Types() const {
-    AssertNotMoved();
-    return types_;
-  }
-
-  /// @returns a reference to the program's AST nodes storage
-  const ASTNodeAllocator& ASTNodes() const {
-    AssertNotMoved();
-    return ast_nodes_;
-  }
-
-  /// @returns a reference to the program's semantic nodes storage
-  const SemNodeAllocator& SemNodes() const {
-    AssertNotMoved();
-    return sem_nodes_;
-  }
-
-  /// @returns a reference to the program's AST root Module
-  const ast::Module& AST() const {
-    AssertNotMoved();
-    return *ast_;
-  }
-
-  /// @returns a reference to the program's semantic info
-  const sem::Info& Sem() const {
-    AssertNotMoved();
-    return sem_;
-  }
-
-  /// @returns a reference to the program's SymbolTable
-  const SymbolTable& Symbols() const {
-    AssertNotMoved();
-    return symbols_;
-  }
-
-  /// @returns a reference to the program's diagnostics
-  const diag::List& Diagnostics() const {
-    AssertNotMoved();
-    return diagnostics_;
-  }
-
-  /// Performs a deep clone of this program.
-  /// The returned Program will contain no pointers to objects owned by this
-  /// Program, and so after calling, this Program can be safely destructed.
-  /// @return a new Program copied from this Program
-  Program Clone() const;
-
-  /// Performs a deep clone of this Program's AST nodes, types and symbols into
-  /// a new ProgramBuilder. Semantic nodes are not cloned, as these will be
-  /// rebuilt when the ProgramBuilder builds its Program.
-  /// The returned ProgramBuilder will contain no pointers to objects owned by
-  /// this Program, and so after calling, this Program can be safely destructed.
-  /// @return a new ProgramBuilder copied from this Program
-  ProgramBuilder CloneAsBuilder() const;
-
-  /// @returns true if the program has no error diagnostics and is not missing
-  /// information
-  bool IsValid() const;
-
-  /// Helper for returning the resolved semantic type of the expression `expr`.
-  /// @param expr the AST expression
-  /// @return the resolved semantic type for the expression, or nullptr if the
-  /// expression has no resolved type.
-  const sem::Type* TypeOf(const ast::Expression* expr) const;
-
-  /// Helper for returning the resolved semantic type of the AST type `type`.
-  /// @param type the AST type
-  /// @return the resolved semantic type for the type, or nullptr if the type
-  /// has no resolved type.
-  const sem::Type* TypeOf(const ast::Type* type) const;
-
-  /// Helper for returning the resolved semantic type of the AST type
-  /// declaration `type_decl`.
-  /// @param type_decl the AST type declaration
-  /// @return the resolved semantic type for the type declaration, or nullptr if
-  /// the type declaration has no resolved type.
-  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
-
-  /// A function that can be used to print a program
-  using Printer = std::string (*)(const Program*);
-
-  /// The Program printer used for testing and debugging.
-  static Printer printer;
-
- private:
-  Program(const Program&) = delete;
-
-  /// Asserts that the program has not been moved.
-  void AssertNotMoved() const;
-
-  ProgramID id_;
-  sem::Manager types_;
-  ASTNodeAllocator ast_nodes_;
-  SemNodeAllocator sem_nodes_;
-  ast::Module* ast_ = nullptr;
-  sem::Info sem_;
-  SymbolTable symbols_{id_};
-  diag::List diagnostics_;
-  bool is_valid_ = false;  // Not valid until it is built
-  bool moved_ = false;
-};
-
-/// @param program the Program
-/// @returns the ProgramID of the Program
-inline ProgramID ProgramIDOf(const Program* program) {
-  return program->ID();
-}
-
-}  // namespace tint
-
-#endif  // SRC_PROGRAM_H_
diff --git a/src/program_builder.cc b/src/program_builder.cc
deleted file mode 100644
index 270f463..0000000
--- a/src/program_builder.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/program_builder.h"
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/debug.h"
-#include "src/demangler.h"
-#include "src/sem/expression.h"
-#include "src/sem/variable.h"
-
-namespace tint {
-
-ProgramBuilder::VarOptionals::~VarOptionals() = default;
-
-ProgramBuilder::ProgramBuilder()
-    : id_(ProgramID::New()),
-      ast_(ast_nodes_.Create<ast::Module>(id_, Source{})) {}
-
-ProgramBuilder::ProgramBuilder(ProgramBuilder&& rhs)
-    : id_(std::move(rhs.id_)),
-      types_(std::move(rhs.types_)),
-      ast_nodes_(std::move(rhs.ast_nodes_)),
-      sem_nodes_(std::move(rhs.sem_nodes_)),
-      ast_(rhs.ast_),
-      sem_(std::move(rhs.sem_)),
-      symbols_(std::move(rhs.symbols_)),
-      diagnostics_(std::move(rhs.diagnostics_)) {
-  rhs.MarkAsMoved();
-}
-
-ProgramBuilder::~ProgramBuilder() = default;
-
-ProgramBuilder& ProgramBuilder::operator=(ProgramBuilder&& rhs) {
-  rhs.MarkAsMoved();
-  AssertNotMoved();
-  id_ = std::move(rhs.id_);
-  types_ = std::move(rhs.types_);
-  ast_nodes_ = std::move(rhs.ast_nodes_);
-  sem_nodes_ = std::move(rhs.sem_nodes_);
-  ast_ = rhs.ast_;
-  sem_ = std::move(rhs.sem_);
-  symbols_ = std::move(rhs.symbols_);
-  diagnostics_ = std::move(rhs.diagnostics_);
-
-  return *this;
-}
-
-ProgramBuilder ProgramBuilder::Wrap(const Program* program) {
-  ProgramBuilder builder;
-  builder.id_ = program->ID();
-  builder.types_ = sem::Manager::Wrap(program->Types());
-  builder.ast_ = builder.create<ast::Module>(
-      program->AST().source, program->AST().GlobalDeclarations());
-  builder.sem_ = sem::Info::Wrap(program->Sem());
-  builder.symbols_ = program->Symbols();
-  builder.diagnostics_ = program->Diagnostics();
-  return builder;
-}
-
-bool ProgramBuilder::IsValid() const {
-  return !diagnostics_.contains_errors();
-}
-
-void ProgramBuilder::MarkAsMoved() {
-  AssertNotMoved();
-  moved_ = true;
-}
-
-void ProgramBuilder::AssertNotMoved() const {
-  if (moved_) {
-    TINT_ICE(ProgramBuilder, const_cast<ProgramBuilder*>(this)->diagnostics_)
-        << "Attempting to use ProgramBuilder after it has been moved";
-  }
-}
-
-const sem::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
-  auto* sem = Sem().Get(expr);
-  return sem ? sem->Type() : nullptr;
-}
-
-const sem::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const {
-  auto* sem = Sem().Get(var);
-  return sem ? sem->Type() : nullptr;
-}
-
-const sem::Type* ProgramBuilder::TypeOf(const ast::Type* type) const {
-  return Sem().Get(type);
-}
-
-const sem::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
-  return Sem().Get(type_decl);
-}
-
-const ast::TypeName* ProgramBuilder::TypesBuilder::Of(
-    const ast::TypeDecl* decl) const {
-  return type_name(decl->name);
-}
-
-ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
-
-const ast::Statement* ProgramBuilder::WrapInStatement(
-    const ast::Expression* expr) {
-  // Create a temporary variable of inferred type from expr.
-  return Decl(Const(symbols_.New(), nullptr, expr));
-}
-
-const ast::VariableDeclStatement* ProgramBuilder::WrapInStatement(
-    const ast::Variable* v) {
-  return create<ast::VariableDeclStatement>(v);
-}
-
-const ast::Statement* ProgramBuilder::WrapInStatement(
-    const ast::Statement* stmt) {
-  return stmt;
-}
-
-const ast::Function* ProgramBuilder::WrapInFunction(
-    const ast::StatementList stmts) {
-  return Func("test_function", {}, ty.void_(), std::move(stmts),
-              {create<ast::StageAttribute>(ast::PipelineStage::kCompute),
-               WorkgroupSize(1, 1, 1)});
-}
-
-}  // namespace tint
diff --git a/src/program_builder.h b/src/program_builder.h
deleted file mode 100644
index 787b64e..0000000
--- a/src/program_builder.h
+++ /dev/null
@@ -1,2666 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_PROGRAM_BUILDER_H_
-#define SRC_PROGRAM_BUILDER_H_
-
-#include <string>
-#include <unordered_set>
-#include <utility>
-
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/atomic.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/binding_attribute.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/bool.h"
-#include "src/ast/bool_literal_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/call_expression.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/case_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/depth_multisampled_texture.h"
-#include "src/ast/depth_texture.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/external_texture.h"
-#include "src/ast/f32.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/float_literal_expression.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/i32.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/index_accessor_expression.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/invariant_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/matrix.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/module.h"
-#include "src/ast/multisampled_texture.h"
-#include "src/ast/phony_expression.h"
-#include "src/ast/pointer.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/sampled_texture.h"
-#include "src/ast/sampler.h"
-#include "src/ast/sint_literal_expression.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/stride_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/struct_member_align_attribute.h"
-#include "src/ast/struct_member_offset_attribute.h"
-#include "src/ast/struct_member_size_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/type_name.h"
-#include "src/ast/u32.h"
-#include "src/ast/uint_literal_expression.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/vector.h"
-#include "src/ast/void.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/program.h"
-#include "src/program_id.h"
-#include "src/sem/array.h"
-#include "src/sem/bool_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/matrix_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/pointer_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/u32_type.h"
-#include "src/sem/vector_type.h"
-#include "src/sem/void_type.h"
-
-#ifdef INCLUDE_TINT_TINT_H_
-#error "internal tint header being #included from tint.h"
-#endif
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class VariableDeclStatement;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-class CloneContext;
-
-/// ProgramBuilder is a mutable builder for a Program.
-/// To construct a Program, populate the builder and then `std::move` it to a
-/// Program.
-class ProgramBuilder {
-  /// A helper used to disable overloads if the first type in `TYPES` is a
-  /// Source. Used to avoid ambiguities in overloads that take a Source as the
-  /// first parameter and those that perfectly-forward the first argument.
-  template <typename... TYPES>
-  using DisableIfSource = traits::EnableIfIsNotType<
-      traits::Decay<traits::NthTypeOf<0, TYPES..., void>>,
-      Source>;
-
-  /// VarOptionals is a helper for accepting a number of optional, extra
-  /// arguments for Var() and Global().
-  struct VarOptionals {
-    template <typename... ARGS>
-    explicit VarOptionals(ARGS&&... args) {
-      Apply(std::forward<ARGS>(args)...);
-    }
-    ~VarOptionals();
-
-    ast::StorageClass storage = ast::StorageClass::kNone;
-    ast::Access access = ast::Access::kUndefined;
-    const ast::Expression* constructor = nullptr;
-    ast::AttributeList attributes = {};
-
-   private:
-    void Set(ast::StorageClass sc) { storage = sc; }
-    void Set(ast::Access ac) { access = ac; }
-    void Set(const ast::Expression* c) { constructor = c; }
-    void Set(const ast::AttributeList& l) { attributes = l; }
-
-    template <typename FIRST, typename... ARGS>
-    void Apply(FIRST&& first, ARGS&&... args) {
-      Set(std::forward<FIRST>(first));
-      Apply(std::forward<ARGS>(args)...);
-    }
-    void Apply() {}
-  };
-
- public:
-  /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node>
-  using ASTNodeAllocator = BlockAllocator<ast::Node>;
-
-  /// SemNodeAllocator is an alias to BlockAllocator<sem::Node>
-  using SemNodeAllocator = BlockAllocator<sem::Node>;
-
-  /// `i32` is a type alias to `int`.
-  /// Useful for passing to template methods such as `vec2<i32>()` to imitate
-  /// WGSL syntax.
-  /// Note: this is intentionally not aliased to uint32_t as we want integer
-  /// literals passed to the builder to match WGSL's integer literal types.
-  using i32 = decltype(1);
-  /// `u32` is a type alias to `unsigned int`.
-  /// Useful for passing to template methods such as `vec2<u32>()` to imitate
-  /// WGSL syntax.
-  /// Note: this is intentionally not aliased to uint32_t as we want integer
-  /// literals passed to the builder to match WGSL's integer literal types.
-  using u32 = decltype(1u);
-  /// `f32` is a type alias to `float`
-  /// Useful for passing to template methods such as `vec2<f32>()` to imitate
-  /// WGSL syntax.
-  using f32 = float;
-
-  /// Constructor
-  ProgramBuilder();
-
-  /// Move constructor
-  /// @param rhs the builder to move
-  ProgramBuilder(ProgramBuilder&& rhs);
-
-  /// Destructor
-  virtual ~ProgramBuilder();
-
-  /// Move assignment operator
-  /// @param rhs the builder to move
-  /// @return this builder
-  ProgramBuilder& operator=(ProgramBuilder&& rhs);
-
-  /// Wrap returns a new ProgramBuilder wrapping the Program `program` without
-  /// making a deep clone of the Program contents.
-  /// ProgramBuilder returned by Wrap() is intended to temporarily extend an
-  /// existing immutable program.
-  /// As the returned ProgramBuilder wraps `program`, `program` must not be
-  /// destructed or assigned while using the returned ProgramBuilder.
-  /// TODO(bclayton) - Evaluate whether there are safer alternatives to this
-  /// function. See crbug.com/tint/460.
-  /// @param program the immutable Program to wrap
-  /// @return the ProgramBuilder that wraps `program`
-  static ProgramBuilder Wrap(const Program* program);
-
-  /// @returns the unique identifier for this program
-  ProgramID ID() const { return id_; }
-
-  /// @returns a reference to the program's types
-  sem::Manager& Types() {
-    AssertNotMoved();
-    return types_;
-  }
-
-  /// @returns a reference to the program's types
-  const sem::Manager& Types() const {
-    AssertNotMoved();
-    return types_;
-  }
-
-  /// @returns a reference to the program's AST nodes storage
-  ASTNodeAllocator& ASTNodes() {
-    AssertNotMoved();
-    return ast_nodes_;
-  }
-
-  /// @returns a reference to the program's AST nodes storage
-  const ASTNodeAllocator& ASTNodes() const {
-    AssertNotMoved();
-    return ast_nodes_;
-  }
-
-  /// @returns a reference to the program's semantic nodes storage
-  SemNodeAllocator& SemNodes() {
-    AssertNotMoved();
-    return sem_nodes_;
-  }
-
-  /// @returns a reference to the program's semantic nodes storage
-  const SemNodeAllocator& SemNodes() const {
-    AssertNotMoved();
-    return sem_nodes_;
-  }
-
-  /// @returns a reference to the program's AST root Module
-  ast::Module& AST() {
-    AssertNotMoved();
-    return *ast_;
-  }
-
-  /// @returns a reference to the program's AST root Module
-  const ast::Module& AST() const {
-    AssertNotMoved();
-    return *ast_;
-  }
-
-  /// @returns a reference to the program's semantic info
-  sem::Info& Sem() {
-    AssertNotMoved();
-    return sem_;
-  }
-
-  /// @returns a reference to the program's semantic info
-  const sem::Info& Sem() const {
-    AssertNotMoved();
-    return sem_;
-  }
-
-  /// @returns a reference to the program's SymbolTable
-  SymbolTable& Symbols() {
-    AssertNotMoved();
-    return symbols_;
-  }
-
-  /// @returns a reference to the program's SymbolTable
-  const SymbolTable& Symbols() const {
-    AssertNotMoved();
-    return symbols_;
-  }
-
-  /// @returns a reference to the program's diagnostics
-  diag::List& Diagnostics() {
-    AssertNotMoved();
-    return diagnostics_;
-  }
-
-  /// @returns a reference to the program's diagnostics
-  const diag::List& Diagnostics() const {
-    AssertNotMoved();
-    return diagnostics_;
-  }
-
-  /// Controls whether the Resolver will be run on the program when it is built.
-  /// @param enable the new flag value (defaults to true)
-  void SetResolveOnBuild(bool enable) { resolve_on_build_ = enable; }
-
-  /// @return true if the Resolver will be run on the program when it is
-  /// built.
-  bool ResolveOnBuild() const { return resolve_on_build_; }
-
-  /// @returns true if the program has no error diagnostics and is not missing
-  /// information
-  bool IsValid() const;
-
-  /// Creates a new ast::Node owned by the ProgramBuilder. When the
-  /// ProgramBuilder is destructed, the ast::Node will also be destructed.
-  /// @param source the Source of the node
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, ast::Node>* create(const Source& source,
-                                               ARGS&&... args) {
-    AssertNotMoved();
-    return ast_nodes_.Create<T>(id_, source, std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
-  /// Source as set by the last call to SetSource() as the only argument to the
-  /// constructor.
-  /// When the ProgramBuilder is destructed, the ast::Node will also be
-  /// destructed.
-  /// @returns the node pointer
-  template <typename T>
-  traits::EnableIfIsType<T, ast::Node>* create() {
-    AssertNotMoved();
-    return ast_nodes_.Create<T>(id_, source_);
-  }
-
-  /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
-  /// Source as set by the last call to SetSource() as the first argument to the
-  /// constructor.
-  /// When the ProgramBuilder is destructed, the ast::Node will also be
-  /// destructed.
-  /// @param arg0 the first arguments to pass to the type constructor
-  /// @param args the remaining arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename ARG0, typename... ARGS>
-  traits::EnableIf</* T is ast::Node and ARG0 is not Source */
-                   traits::IsTypeOrDerived<T, ast::Node> &&
-                       !traits::IsTypeOrDerived<ARG0, Source>,
-                   T>*
-  create(ARG0&& arg0, ARGS&&... args) {
-    AssertNotMoved();
-    return ast_nodes_.Create<T>(id_, source_, std::forward<ARG0>(arg0),
-                                std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new sem::Node owned by the ProgramBuilder.
-  /// When the ProgramBuilder is destructed, the sem::Node will also be
-  /// destructed.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> &&
-                       !traits::IsTypeOrDerived<T, sem::Type>,
-                   T>*
-  create(ARGS&&... args) {
-    AssertNotMoved();
-    return sem_nodes_.Create<T>(std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new sem::Type owned by the ProgramBuilder.
-  /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
-  /// 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.
-  /// @warning Use this method to acquire a type only if all of its type
-  /// information is provided in the constructor arguments `args`.<br>
-  /// If the type requires additional configuration after construction that
-  /// affect its fundamental type, build the type with `std::make_unique`, make
-  /// any necessary alterations and then call unique_type() instead.
-  /// @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)...);
-  }
-
-  /// Marks this builder as moved, preventing any further use of the builder.
-  void MarkAsMoved();
-
-  //////////////////////////////////////////////////////////////////////////////
-  // TypesBuilder
-  //////////////////////////////////////////////////////////////////////////////
-
-  /// TypesBuilder holds basic `tint` types and methods for constructing
-  /// complex types.
-  class TypesBuilder {
-   public:
-    /// Constructor
-    /// @param builder the program builder
-    explicit TypesBuilder(ProgramBuilder* builder);
-
-    /// @return the tint AST type for the C type `T`.
-    template <typename T>
-    const ast::Type* Of() const {
-      return CToAST<T>::get(this);
-    }
-
-    /// @returns a boolean type
-    const ast::Bool* bool_() const { return builder->create<ast::Bool>(); }
-
-    /// @param source the Source of the node
-    /// @returns a boolean type
-    const ast::Bool* bool_(const Source& source) const {
-      return builder->create<ast::Bool>(source);
-    }
-
-    /// @returns a f32 type
-    const ast::F32* f32() const { return builder->create<ast::F32>(); }
-
-    /// @param source the Source of the node
-    /// @returns a f32 type
-    const ast::F32* f32(const Source& source) const {
-      return builder->create<ast::F32>(source);
-    }
-
-    /// @returns a i32 type
-    const ast::I32* i32() const { return builder->create<ast::I32>(); }
-
-    /// @param source the Source of the node
-    /// @returns a i32 type
-    const ast::I32* i32(const Source& source) const {
-      return builder->create<ast::I32>(source);
-    }
-
-    /// @returns a u32 type
-    const ast::U32* u32() const { return builder->create<ast::U32>(); }
-
-    /// @param source the Source of the node
-    /// @returns a u32 type
-    const ast::U32* u32(const Source& source) const {
-      return builder->create<ast::U32>(source);
-    }
-
-    /// @returns a void type
-    const ast::Void* void_() const { return builder->create<ast::Void>(); }
-
-    /// @param source the Source of the node
-    /// @returns a void type
-    const ast::Void* void_(const Source& source) const {
-      return builder->create<ast::Void>(source);
-    }
-
-    /// @param type vector subtype
-    /// @param n vector width in elements
-    /// @return the tint AST type for a `n`-element vector of `type`.
-    const ast::Vector* vec(const ast::Type* type, uint32_t n) const {
-      return builder->create<ast::Vector>(type, n);
-    }
-
-    /// @param source the Source of the node
-    /// @param type vector subtype
-    /// @param n vector width in elements
-    /// @return the tint AST type for a `n`-element vector of `type`.
-    const ast::Vector* vec(const Source& source,
-                           const ast::Type* type,
-                           uint32_t n) const {
-      return builder->create<ast::Vector>(source, type, n);
-    }
-
-    /// @param type vector subtype
-    /// @return the tint AST type for a 2-element vector of `type`.
-    const ast::Vector* vec2(const ast::Type* type) const {
-      return vec(type, 2u);
-    }
-
-    /// @param type vector subtype
-    /// @return the tint AST type for a 3-element vector of `type`.
-    const ast::Vector* vec3(const ast::Type* type) const {
-      return vec(type, 3u);
-    }
-
-    /// @param type vector subtype
-    /// @return the tint AST type for a 4-element vector of `type`.
-    const ast::Vector* vec4(const ast::Type* type) const {
-      return vec(type, 4u);
-    }
-
-    /// @param n vector width in elements
-    /// @return the tint AST type for a `n`-element vector of `type`.
-    template <typename T>
-    const ast::Vector* vec(uint32_t n) const {
-      return vec(Of<T>(), n);
-    }
-
-    /// @return the tint AST type for a 2-element vector of the C type `T`.
-    template <typename T>
-    const ast::Vector* vec2() const {
-      return vec2(Of<T>());
-    }
-
-    /// @return the tint AST type for a 3-element vector of the C type `T`.
-    template <typename T>
-    const ast::Vector* vec3() const {
-      return vec3(Of<T>());
-    }
-
-    /// @return the tint AST type for a 4-element vector of the C type `T`.
-    template <typename T>
-    const ast::Vector* vec4() const {
-      return vec4(Of<T>());
-    }
-
-    /// @param type matrix subtype
-    /// @param columns number of columns for the matrix
-    /// @param rows number of rows for the matrix
-    /// @return the tint AST type for a matrix of `type`
-    const ast::Matrix* mat(const ast::Type* type,
-                           uint32_t columns,
-                           uint32_t rows) const {
-      return builder->create<ast::Matrix>(type, rows, columns);
-    }
-
-    /// @param source the Source of the node
-    /// @param type matrix subtype
-    /// @param columns number of columns for the matrix
-    /// @param rows number of rows for the matrix
-    /// @return the tint AST type for a matrix of `type`
-    const ast::Matrix* mat(const Source& source,
-                           const ast::Type* type,
-                           uint32_t columns,
-                           uint32_t rows) const {
-      return builder->create<ast::Matrix>(source, type, rows, columns);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 2x3 matrix of `type`.
-    const ast::Matrix* mat2x2(const ast::Type* type) const {
-      return mat(type, 2u, 2u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 2x3 matrix of `type`.
-    const ast::Matrix* mat2x3(const ast::Type* type) const {
-      return mat(type, 2u, 3u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 2x4 matrix of `type`.
-    const ast::Matrix* mat2x4(const ast::Type* type) const {
-      return mat(type, 2u, 4u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 3x2 matrix of `type`.
-    const ast::Matrix* mat3x2(const ast::Type* type) const {
-      return mat(type, 3u, 2u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 3x3 matrix of `type`.
-    const ast::Matrix* mat3x3(const ast::Type* type) const {
-      return mat(type, 3u, 3u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 3x4 matrix of `type`.
-    const ast::Matrix* mat3x4(const ast::Type* type) const {
-      return mat(type, 3u, 4u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 4x2 matrix of `type`.
-    const ast::Matrix* mat4x2(const ast::Type* type) const {
-      return mat(type, 4u, 2u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 4x3 matrix of `type`.
-    const ast::Matrix* mat4x3(const ast::Type* type) const {
-      return mat(type, 4u, 3u);
-    }
-
-    /// @param type matrix subtype
-    /// @return the tint AST type for a 4x4 matrix of `type`.
-    const ast::Matrix* mat4x4(const ast::Type* type) const {
-      return mat(type, 4u, 4u);
-    }
-
-    /// @param columns number of columns for the matrix
-    /// @param rows number of rows for the matrix
-    /// @return the tint AST type for a matrix of `type`
-    template <typename T>
-    const ast::Matrix* mat(uint32_t columns, uint32_t rows) const {
-      return mat(Of<T>(), columns, rows);
-    }
-
-    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat2x2() const {
-      return mat2x2(Of<T>());
-    }
-
-    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat2x3() const {
-      return mat2x3(Of<T>());
-    }
-
-    /// @return the tint AST type for a 2x4 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat2x4() const {
-      return mat2x4(Of<T>());
-    }
-
-    /// @return the tint AST type for a 3x2 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat3x2() const {
-      return mat3x2(Of<T>());
-    }
-
-    /// @return the tint AST type for a 3x3 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat3x3() const {
-      return mat3x3(Of<T>());
-    }
-
-    /// @return the tint AST type for a 3x4 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat3x4() const {
-      return mat3x4(Of<T>());
-    }
-
-    /// @return the tint AST type for a 4x2 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat4x2() const {
-      return mat4x2(Of<T>());
-    }
-
-    /// @return the tint AST type for a 4x3 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat4x3() const {
-      return mat4x3(Of<T>());
-    }
-
-    /// @return the tint AST type for a 4x4 matrix of the C type `T`.
-    template <typename T>
-    const ast::Matrix* mat4x4() const {
-      return mat4x4(Of<T>());
-    }
-
-    /// @param subtype the array element type
-    /// @param n the array size. nullptr represents a runtime-array
-    /// @param attrs the optional attributes for the array
-    /// @return the tint AST type for a array of size `n` of type `T`
-    template <typename EXPR = ast::Expression*>
-    const ast::Array* array(const ast::Type* subtype,
-                            EXPR&& n = nullptr,
-                            ast::AttributeList attrs = {}) const {
-      return builder->create<ast::Array>(
-          subtype, builder->Expr(std::forward<EXPR>(n)), attrs);
-    }
-
-    /// @param source the Source of the node
-    /// @param subtype the array element type
-    /// @param n the array size. nullptr represents a runtime-array
-    /// @param attrs the optional attributes for the array
-    /// @return the tint AST type for a array of size `n` of type `T`
-    template <typename EXPR = ast::Expression*>
-    const ast::Array* array(const Source& source,
-                            const ast::Type* subtype,
-                            EXPR&& n = nullptr,
-                            ast::AttributeList attrs = {}) const {
-      return builder->create<ast::Array>(
-          source, subtype, builder->Expr(std::forward<EXPR>(n)), attrs);
-    }
-
-    /// @param subtype the array element type
-    /// @param n the array size. nullptr represents a runtime-array
-    /// @param stride the array stride. 0 represents implicit stride
-    /// @return the tint AST type for a array of size `n` of type `T`
-    template <typename EXPR>
-    const ast::Array* array(const ast::Type* subtype,
-                            EXPR&& n,
-                            uint32_t stride) const {
-      ast::AttributeList attrs;
-      if (stride) {
-        attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
-      }
-      return array(subtype, std::forward<EXPR>(n), std::move(attrs));
-    }
-
-    /// @param source the Source of the node
-    /// @param subtype the array element type
-    /// @param n the array size. nullptr represents a runtime-array
-    /// @param stride the array stride. 0 represents implicit stride
-    /// @return the tint AST type for a array of size `n` of type `T`
-    template <typename EXPR>
-    const ast::Array* array(const Source& source,
-                            const ast::Type* subtype,
-                            EXPR&& n,
-                            uint32_t stride) const {
-      ast::AttributeList attrs;
-      if (stride) {
-        attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
-      }
-      return array(source, subtype, std::forward<EXPR>(n), std::move(attrs));
-    }
-
-    /// @return the tint AST type for a runtime-sized array of type `T`
-    template <typename T>
-    const ast::Array* array() const {
-      return array(Of<T>(), nullptr);
-    }
-
-    /// @return the tint AST type for an array of size `N` of type `T`
-    template <typename T, int N>
-    const ast::Array* array() const {
-      return array(Of<T>(), builder->Expr(N));
-    }
-
-    /// @param stride the array stride
-    /// @return the tint AST type for a runtime-sized array of type `T`
-    template <typename T>
-    const ast::Array* array(uint32_t stride) const {
-      return array(Of<T>(), nullptr, stride);
-    }
-
-    /// @param stride the array stride
-    /// @return the tint AST type for an array of size `N` of type `T`
-    template <typename T, int N>
-    const ast::Array* array(uint32_t stride) const {
-      return array(Of<T>(), builder->Expr(N), stride);
-    }
-
-    /// Creates a type name
-    /// @param name the name
-    /// @returns the type name
-    template <typename NAME>
-    const ast::TypeName* type_name(NAME&& name) const {
-      return builder->create<ast::TypeName>(
-          builder->Sym(std::forward<NAME>(name)));
-    }
-
-    /// Creates a type name
-    /// @param source the Source of the node
-    /// @param name the name
-    /// @returns the type name
-    template <typename NAME>
-    const ast::TypeName* type_name(const Source& source, NAME&& name) const {
-      return builder->create<ast::TypeName>(
-          source, builder->Sym(std::forward<NAME>(name)));
-    }
-
-    /// Creates an alias type
-    /// @param name the alias name
-    /// @param type the alias type
-    /// @returns the alias pointer
-    template <typename NAME>
-    const ast::Alias* alias(NAME&& name, const ast::Type* type) const {
-      auto sym = builder->Sym(std::forward<NAME>(name));
-      return builder->create<ast::Alias>(sym, type);
-    }
-
-    /// Creates an alias type
-    /// @param source the Source of the node
-    /// @param name the alias name
-    /// @param type the alias type
-    /// @returns the alias pointer
-    template <typename NAME>
-    const ast::Alias* alias(const Source& source,
-                            NAME&& name,
-                            const ast::Type* type) const {
-      auto sym = builder->Sym(std::forward<NAME>(name));
-      return builder->create<ast::Alias>(source, sym, type);
-    }
-
-    /// @param type the type of the pointer
-    /// @param storage_class the storage class of the pointer
-    /// @param access the optional access control of the pointer
-    /// @return the pointer to `type` with the given ast::StorageClass
-    const ast::Pointer* pointer(
-        const ast::Type* type,
-        ast::StorageClass storage_class,
-        ast::Access access = ast::Access::kUndefined) const {
-      return builder->create<ast::Pointer>(type, storage_class, access);
-    }
-
-    /// @param source the Source of the node
-    /// @param type the type of the pointer
-    /// @param storage_class the storage class of the pointer
-    /// @param access the optional access control of the pointer
-    /// @return the pointer to `type` with the given ast::StorageClass
-    const ast::Pointer* pointer(
-        const Source& source,
-        const ast::Type* type,
-        ast::StorageClass storage_class,
-        ast::Access access = ast::Access::kUndefined) const {
-      return builder->create<ast::Pointer>(source, type, storage_class, access);
-    }
-
-    /// @param storage_class the storage class of the pointer
-    /// @param access the optional access control of the pointer
-    /// @return the pointer to type `T` with the given ast::StorageClass.
-    template <typename T>
-    const ast::Pointer* pointer(
-        ast::StorageClass storage_class,
-        ast::Access access = ast::Access::kUndefined) const {
-      return pointer(Of<T>(), storage_class, access);
-    }
-
-    /// @param source the Source of the node
-    /// @param type the type of the atomic
-    /// @return the atomic to `type`
-    const ast::Atomic* atomic(const Source& source,
-                              const ast::Type* type) const {
-      return builder->create<ast::Atomic>(source, type);
-    }
-
-    /// @param type the type of the atomic
-    /// @return the atomic to `type`
-    const ast::Atomic* atomic(const ast::Type* type) const {
-      return builder->create<ast::Atomic>(type);
-    }
-
-    /// @return the atomic to type `T`
-    template <typename T>
-    const ast::Atomic* atomic() const {
-      return atomic(Of<T>());
-    }
-
-    /// @param kind the kind of sampler
-    /// @returns the sampler
-    const ast::Sampler* sampler(ast::SamplerKind kind) const {
-      return builder->create<ast::Sampler>(kind);
-    }
-
-    /// @param source the Source of the node
-    /// @param kind the kind of sampler
-    /// @returns the sampler
-    const ast::Sampler* sampler(const Source& source,
-                                ast::SamplerKind kind) const {
-      return builder->create<ast::Sampler>(source, kind);
-    }
-
-    /// @param dims the dimensionality of the texture
-    /// @returns the depth texture
-    const ast::DepthTexture* depth_texture(ast::TextureDimension dims) const {
-      return builder->create<ast::DepthTexture>(dims);
-    }
-
-    /// @param source the Source of the node
-    /// @param dims the dimensionality of the texture
-    /// @returns the depth texture
-    const ast::DepthTexture* depth_texture(const Source& source,
-                                           ast::TextureDimension dims) const {
-      return builder->create<ast::DepthTexture>(source, dims);
-    }
-
-    /// @param dims the dimensionality of the texture
-    /// @returns the multisampled depth texture
-    const ast::DepthMultisampledTexture* depth_multisampled_texture(
-        ast::TextureDimension dims) const {
-      return builder->create<ast::DepthMultisampledTexture>(dims);
-    }
-
-    /// @param source the Source of the node
-    /// @param dims the dimensionality of the texture
-    /// @returns the multisampled depth texture
-    const ast::DepthMultisampledTexture* depth_multisampled_texture(
-        const Source& source,
-        ast::TextureDimension dims) const {
-      return builder->create<ast::DepthMultisampledTexture>(source, dims);
-    }
-
-    /// @param dims the dimensionality of the texture
-    /// @param subtype the texture subtype.
-    /// @returns the sampled texture
-    const ast::SampledTexture* sampled_texture(ast::TextureDimension dims,
-                                               const ast::Type* subtype) const {
-      return builder->create<ast::SampledTexture>(dims, subtype);
-    }
-
-    /// @param source the Source of the node
-    /// @param dims the dimensionality of the texture
-    /// @param subtype the texture subtype.
-    /// @returns the sampled texture
-    const ast::SampledTexture* sampled_texture(const Source& source,
-                                               ast::TextureDimension dims,
-                                               const ast::Type* subtype) const {
-      return builder->create<ast::SampledTexture>(source, dims, subtype);
-    }
-
-    /// @param dims the dimensionality of the texture
-    /// @param subtype the texture subtype.
-    /// @returns the multisampled texture
-    const ast::MultisampledTexture* multisampled_texture(
-        ast::TextureDimension dims,
-        const ast::Type* subtype) const {
-      return builder->create<ast::MultisampledTexture>(dims, subtype);
-    }
-
-    /// @param source the Source of the node
-    /// @param dims the dimensionality of the texture
-    /// @param subtype the texture subtype.
-    /// @returns the multisampled texture
-    const ast::MultisampledTexture* multisampled_texture(
-        const Source& source,
-        ast::TextureDimension dims,
-        const ast::Type* subtype) const {
-      return builder->create<ast::MultisampledTexture>(source, dims, subtype);
-    }
-
-    /// @param dims the dimensionality of the texture
-    /// @param format the texel format of the texture
-    /// @param access the access control of the texture
-    /// @returns the storage texture
-    const ast::StorageTexture* storage_texture(ast::TextureDimension dims,
-                                               ast::TexelFormat format,
-                                               ast::Access access) const {
-      auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
-      return builder->create<ast::StorageTexture>(dims, format, subtype,
-                                                  access);
-    }
-
-    /// @param source the Source of the node
-    /// @param dims the dimensionality of the texture
-    /// @param format the texel format of the texture
-    /// @param access the access control of the texture
-    /// @returns the storage texture
-    const ast::StorageTexture* storage_texture(const Source& source,
-                                               ast::TextureDimension dims,
-                                               ast::TexelFormat format,
-                                               ast::Access access) const {
-      auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
-      return builder->create<ast::StorageTexture>(source, dims, format, subtype,
-                                                  access);
-    }
-
-    /// @returns the external texture
-    const ast::ExternalTexture* external_texture() const {
-      return builder->create<ast::ExternalTexture>();
-    }
-
-    /// @param source the Source of the node
-    /// @returns the external texture
-    const ast::ExternalTexture* external_texture(const Source& source) const {
-      return builder->create<ast::ExternalTexture>(source);
-    }
-
-    /// Constructs a TypeName for the type declaration.
-    /// @param type the type
-    /// @return either type or a pointer to a new ast::TypeName
-    const ast::TypeName* Of(const ast::TypeDecl* type) const;
-
-    /// The ProgramBuilder
-    ProgramBuilder* const builder;
-
-   private:
-    /// CToAST<T> is specialized for various `T` types and each specialization
-    /// contains a single static `get()` method for obtaining the corresponding
-    /// AST type for the C type `T`.
-    /// `get()` has the signature:
-    ///    `static const ast::Type* get(Types* t)`
-    template <typename T>
-    struct CToAST {};
-  };
-
-  //////////////////////////////////////////////////////////////////////////////
-  // AST helper methods
-  //////////////////////////////////////////////////////////////////////////////
-
-  /// @return a new unnamed symbol
-  Symbol Sym() { return Symbols().New(); }
-
-  /// @param name the symbol string
-  /// @return a Symbol with the given name
-  Symbol Sym(const std::string& name) { return Symbols().Register(name); }
-
-  /// @param sym the symbol
-  /// @return `sym`
-  Symbol Sym(Symbol sym) { return sym; }
-
-  /// @param expr the expression
-  /// @return expr
-  template <typename T>
-  traits::EnableIfIsType<T, ast::Expression>* Expr(T* expr) {
-    return expr;
-  }
-
-  /// Passthrough for nullptr
-  /// @return nullptr
-  const ast::IdentifierExpression* Expr(std::nullptr_t) { return nullptr; }
-
-  /// @param source the source information
-  /// @param symbol the identifier symbol
-  /// @return an ast::IdentifierExpression with the given symbol
-  const ast::IdentifierExpression* Expr(const Source& source, Symbol symbol) {
-    return create<ast::IdentifierExpression>(source, symbol);
-  }
-
-  /// @param symbol the identifier symbol
-  /// @return an ast::IdentifierExpression with the given symbol
-  const ast::IdentifierExpression* Expr(Symbol symbol) {
-    return create<ast::IdentifierExpression>(symbol);
-  }
-
-  /// @param source the source information
-  /// @param variable the AST variable
-  /// @return an ast::IdentifierExpression with the variable's symbol
-  const ast::IdentifierExpression* Expr(const Source& source,
-                                        const ast::Variable* variable) {
-    return create<ast::IdentifierExpression>(source, variable->symbol);
-  }
-
-  /// @param variable the AST variable
-  /// @return an ast::IdentifierExpression with the variable's symbol
-  const ast::IdentifierExpression* Expr(const ast::Variable* variable) {
-    return create<ast::IdentifierExpression>(variable->symbol);
-  }
-
-  /// @param source the source information
-  /// @param name the identifier name
-  /// @return an ast::IdentifierExpression with the given name
-  const ast::IdentifierExpression* Expr(const Source& source,
-                                        const char* name) {
-    return create<ast::IdentifierExpression>(source, Symbols().Register(name));
-  }
-
-  /// @param name the identifier name
-  /// @return an ast::IdentifierExpression with the given name
-  const ast::IdentifierExpression* Expr(const char* name) {
-    return create<ast::IdentifierExpression>(Symbols().Register(name));
-  }
-
-  /// @param source the source information
-  /// @param name the identifier name
-  /// @return an ast::IdentifierExpression with the given name
-  const ast::IdentifierExpression* Expr(const Source& source,
-                                        const std::string& name) {
-    return create<ast::IdentifierExpression>(source, Symbols().Register(name));
-  }
-
-  /// @param name the identifier name
-  /// @return an ast::IdentifierExpression with the given name
-  const ast::IdentifierExpression* Expr(const std::string& name) {
-    return create<ast::IdentifierExpression>(Symbols().Register(name));
-  }
-
-  /// @param source the source information
-  /// @param value the boolean value
-  /// @return a Scalar constructor for the given value
-  const ast::BoolLiteralExpression* Expr(const Source& source, bool value) {
-    return create<ast::BoolLiteralExpression>(source, value);
-  }
-
-  /// @param value the boolean value
-  /// @return a Scalar constructor for the given value
-  const ast::BoolLiteralExpression* Expr(bool value) {
-    return create<ast::BoolLiteralExpression>(value);
-  }
-
-  /// @param source the source information
-  /// @param value the float value
-  /// @return a Scalar constructor for the given value
-  const ast::FloatLiteralExpression* Expr(const Source& source, f32 value) {
-    return create<ast::FloatLiteralExpression>(source, value);
-  }
-
-  /// @param value the float value
-  /// @return a Scalar constructor for the given value
-  const ast::FloatLiteralExpression* Expr(f32 value) {
-    return create<ast::FloatLiteralExpression>(value);
-  }
-
-  /// @param source the source information
-  /// @param value the integer value
-  /// @return a Scalar constructor for the given value
-  const ast::SintLiteralExpression* Expr(const Source& source, i32 value) {
-    return create<ast::SintLiteralExpression>(source, value);
-  }
-
-  /// @param value the integer value
-  /// @return a Scalar constructor for the given value
-  const ast::SintLiteralExpression* Expr(i32 value) {
-    return create<ast::SintLiteralExpression>(value);
-  }
-
-  /// @param source the source information
-  /// @param value the unsigned int value
-  /// @return a Scalar constructor for the given value
-  const ast::UintLiteralExpression* Expr(const Source& source, u32 value) {
-    return create<ast::UintLiteralExpression>(source, value);
-  }
-
-  /// @param value the unsigned int value
-  /// @return a Scalar constructor for the given value
-  const ast::UintLiteralExpression* Expr(u32 value) {
-    return create<ast::UintLiteralExpression>(value);
-  }
-
-  /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to
-  /// `list`.
-  /// @param list the list to append too
-  /// @param arg the arg to create
-  template <typename ARG>
-  void Append(ast::ExpressionList& list, ARG&& arg) {
-    list.emplace_back(Expr(std::forward<ARG>(arg)));
-  }
-
-  /// Converts `arg0` and `args` to `ast::Expression`s using `Expr()`,
-  /// then appends them to `list`.
-  /// @param list the list to append too
-  /// @param arg0 the first argument
-  /// @param args the rest of the arguments
-  template <typename ARG0, typename... ARGS>
-  void Append(ast::ExpressionList& list, ARG0&& arg0, ARGS&&... args) {
-    Append(list, std::forward<ARG0>(arg0));
-    Append(list, std::forward<ARGS>(args)...);
-  }
-
-  /// @return an empty list of expressions
-  ast::ExpressionList ExprList() { return {}; }
-
-  /// @param args the list of expressions
-  /// @return the list of expressions converted to `ast::Expression`s using
-  /// `Expr()`,
-  template <typename... ARGS>
-  ast::ExpressionList ExprList(ARGS&&... args) {
-    ast::ExpressionList list;
-    list.reserve(sizeof...(args));
-    Append(list, std::forward<ARGS>(args)...);
-    return list;
-  }
-
-  /// @param list the list of expressions
-  /// @return `list`
-  ast::ExpressionList ExprList(ast::ExpressionList list) { return list; }
-
-  /// @param args the arguments for the type constructor
-  /// @return an `ast::CallExpression` of type `ty`, with the values
-  /// of `args` converted to `ast::Expression`s using `Expr()`
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* Construct(ARGS&&... args) {
-    return Construct(ty.Of<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param type the type to construct
-  /// @param args the arguments for the constructor
-  /// @return an `ast::CallExpression` of `type` constructed with the
-  /// values `args`.
-  template <typename... ARGS>
-  const ast::CallExpression* Construct(const ast::Type* type, ARGS&&... args) {
-    return Construct(source_, type, std::forward<ARGS>(args)...);
-  }
-
-  /// @param source the source information
-  /// @param type the type to construct
-  /// @param args the arguments for the constructor
-  /// @return an `ast::CallExpression` of `type` constructed with the
-  /// values `args`.
-  template <typename... ARGS>
-  const ast::CallExpression* Construct(const Source& source,
-                                       const ast::Type* type,
-                                       ARGS&&... args) {
-    return create<ast::CallExpression>(source, type,
-                                       ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param expr the expression for the bitcast
-  /// @return an `ast::BitcastExpression` of type `ty`, with the values of
-  /// `expr` converted to `ast::Expression`s using `Expr()`
-  template <typename T, typename EXPR>
-  const ast::BitcastExpression* Bitcast(EXPR&& expr) {
-    return Bitcast(ty.Of<T>(), std::forward<EXPR>(expr));
-  }
-
-  /// @param type the type to cast to
-  /// @param expr the expression for the bitcast
-  /// @return an `ast::BitcastExpression` of `type` constructed with the values
-  /// `expr`.
-  template <typename EXPR>
-  const ast::BitcastExpression* Bitcast(const ast::Type* type, EXPR&& expr) {
-    return create<ast::BitcastExpression>(type, Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param source the source information
-  /// @param type the type to cast to
-  /// @param expr the expression for the bitcast
-  /// @return an `ast::BitcastExpression` of `type` constructed with the values
-  /// `expr`.
-  template <typename EXPR>
-  const ast::BitcastExpression* Bitcast(const Source& source,
-                                        const ast::Type* type,
-                                        EXPR&& expr) {
-    return create<ast::BitcastExpression>(source, type,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @param type the vector type
-  /// @param size the vector size
-  /// @return an `ast::CallExpression` of a `size`-element vector of
-  /// type `type`, constructed with the values `args`.
-  template <typename... ARGS>
-  const ast::CallExpression* vec(const ast::Type* type,
-                                 uint32_t size,
-                                 ARGS&&... args) {
-    return Construct(ty.vec(type, size), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `ast::CallExpression` of a 2-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* vec2(ARGS&&... args) {
-    return Construct(ty.vec2<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `ast::CallExpression` of a 3-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* vec3(ARGS&&... args) {
-    return Construct(ty.vec3<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `ast::CallExpression` of a 4-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* vec4(ARGS&&... args) {
-    return Construct(ty.vec4<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 2x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat2x2(ARGS&&... args) {
-    return Construct(ty.mat2x2<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 2x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat2x3(ARGS&&... args) {
-    return Construct(ty.mat2x3<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 2x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat2x4(ARGS&&... args) {
-    return Construct(ty.mat2x4<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 3x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat3x2(ARGS&&... args) {
-    return Construct(ty.mat3x2<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 3x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat3x3(ARGS&&... args) {
-    return Construct(ty.mat3x3<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 3x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat3x4(ARGS&&... args) {
-    return Construct(ty.mat3x4<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 4x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat4x2(ARGS&&... args) {
-    return Construct(ty.mat4x2<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 4x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat4x3(ARGS&&... args) {
-    return Construct(ty.mat4x3<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `ast::CallExpression` of a 4x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  const ast::CallExpression* mat4x4(ARGS&&... args) {
-    return Construct(ty.mat4x4<T>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param args the arguments for the array constructor
-  /// @return an `ast::CallExpression` of an array with element type
-  /// `T` and size `N`, constructed with the values `args`.
-  template <typename T, int N, typename... ARGS>
-  const ast::CallExpression* array(ARGS&&... args) {
-    return Construct(ty.array<T, N>(), std::forward<ARGS>(args)...);
-  }
-
-  /// @param subtype the array element type
-  /// @param n the array size. nullptr represents a runtime-array.
-  /// @param args the arguments for the array constructor
-  /// @return an `ast::CallExpression` of an array with element type
-  /// `subtype`, constructed with the values `args`.
-  template <typename EXPR, typename... ARGS>
-  const ast::CallExpression* array(const ast::Type* subtype,
-                                   EXPR&& n,
-                                   ARGS&&... args) {
-    return Construct(ty.array(subtype, std::forward<EXPR>(n)),
-                     std::forward<ARGS>(args)...);
-  }
-
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param optional the optional variable settings.
-  /// Can be any of the following, in any order:
-  ///   * ast::StorageClass   - specifies the variable storage class
-  ///   * ast::Access         - specifies the variable's access control
-  ///   * ast::Expression*    - specifies the variable's initializer expression
-  ///   * ast::AttributeList - specifies the variable's attributes
-  /// Note that repeated arguments of the same type will use the last argument's
-  /// value.
-  /// @returns a `ast::Variable` with the given name, type and additional
-  /// options
-  template <typename NAME, typename... OPTIONAL>
-  const ast::Variable* Var(NAME&& name,
-                           const ast::Type* type,
-                           OPTIONAL&&... optional) {
-    VarOptionals opts(std::forward<OPTIONAL>(optional)...);
-    return create<ast::Variable>(Sym(std::forward<NAME>(name)), opts.storage,
-                                 opts.access, type, false /* is_const */,
-                                 false /* is_overridable */, opts.constructor,
-                                 std::move(opts.attributes));
-  }
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param optional the optional variable settings.
-  /// Can be any of the following, in any order:
-  ///   * ast::StorageClass   - specifies the variable storage class
-  ///   * ast::Access         - specifies the variable's access control
-  ///   * ast::Expression*    - specifies the variable's initializer expression
-  ///   * ast::AttributeList - specifies the variable's attributes
-  /// Note that repeated arguments of the same type will use the last argument's
-  /// value.
-  /// @returns a `ast::Variable` with the given name, storage and type
-  template <typename NAME, typename... OPTIONAL>
-  const ast::Variable* Var(const Source& source,
-                           NAME&& name,
-                           const ast::Type* type,
-                           OPTIONAL&&... optional) {
-    VarOptionals opts(std::forward<OPTIONAL>(optional)...);
-    return create<ast::Variable>(
-        source, Sym(std::forward<NAME>(name)), opts.storage, opts.access, type,
-        false /* is_const */, false /* is_overridable */, opts.constructor,
-        std::move(opts.attributes));
-  }
-
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns a constant `ast::Variable` with the given name and type
-  template <typename NAME>
-  const ast::Variable* Const(NAME&& name,
-                             const ast::Type* type,
-                             const ast::Expression* constructor,
-                             ast::AttributeList attributes = {}) {
-    return create<ast::Variable>(
-        Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        false /* is_overridable */, constructor, attributes);
-  }
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns a constant `ast::Variable` with the given name and type
-  template <typename NAME>
-  const ast::Variable* Const(const Source& source,
-                             NAME&& name,
-                             const ast::Type* type,
-                             const ast::Expression* constructor,
-                             ast::AttributeList attributes = {}) {
-    return create<ast::Variable>(
-        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        false /* is_overridable */, constructor, attributes);
-  }
-
-  /// @param name the parameter name
-  /// @param type the parameter type
-  /// @param attributes optional parameter attributes
-  /// @returns a constant `ast::Variable` with the given name and type
-  template <typename NAME>
-  const ast::Variable* Param(NAME&& name,
-                             const ast::Type* type,
-                             ast::AttributeList attributes = {}) {
-    return create<ast::Variable>(
-        Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        false /* is_overridable */, nullptr, attributes);
-  }
-
-  /// @param source the parameter source
-  /// @param name the parameter name
-  /// @param type the parameter type
-  /// @param attributes optional parameter attributes
-  /// @returns a constant `ast::Variable` with the given name and type
-  template <typename NAME>
-  const ast::Variable* Param(const Source& source,
-                             NAME&& name,
-                             const ast::Type* type,
-                             ast::AttributeList attributes = {}) {
-    return create<ast::Variable>(
-        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        false /* is_overridable */, nullptr, attributes);
-  }
-
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param optional the optional variable settings.
-  /// Can be any of the following, in any order:
-  ///   * ast::StorageClass   - specifies the variable storage class
-  ///   * ast::Access         - specifies the variable's access control
-  ///   * ast::Expression*    - specifies the variable's initializer expression
-  ///   * ast::AttributeList - specifies the variable's attributes
-  /// Note that repeated arguments of the same type will use the last argument's
-  /// value.
-  /// @returns a new `ast::Variable`, which is automatically registered as a
-  /// global variable with the ast::Module.
-  template <typename NAME,
-            typename... OPTIONAL,
-            typename = DisableIfSource<NAME>>
-  const ast::Variable* Global(NAME&& name,
-                              const ast::Type* type,
-                              OPTIONAL&&... optional) {
-    auto* var = Var(std::forward<NAME>(name), type,
-                    std::forward<OPTIONAL>(optional)...);
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param optional the optional variable settings.
-  /// Can be any of the following, in any order:
-  ///   * ast::StorageClass   - specifies the variable storage class
-  ///   * ast::Access         - specifies the variable's access control
-  ///   * ast::Expression*    - specifies the variable's initializer expression
-  ///   * ast::AttributeList - specifies the variable's attributes
-  /// Note that repeated arguments of the same type will use the last argument's
-  /// value.
-  /// @returns a new `ast::Variable`, which is automatically registered as a
-  /// global variable with the ast::Module.
-  template <typename NAME, typename... OPTIONAL>
-  const ast::Variable* Global(const Source& source,
-                              NAME&& name,
-                              const ast::Type* type,
-                              OPTIONAL&&... optional) {
-    auto* var = Var(source, std::forward<NAME>(name), type,
-                    std::forward<OPTIONAL>(optional)...);
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns a const `ast::Variable` constructed by calling Var() with the
-  /// arguments of `args`, which is automatically registered as a global
-  /// variable with the ast::Module.
-  template <typename NAME>
-  const ast::Variable* GlobalConst(NAME&& name,
-                                   const ast::Type* type,
-                                   const ast::Expression* constructor,
-                                   ast::AttributeList attributes = {}) {
-    auto* var = Const(std::forward<NAME>(name), type, constructor,
-                      std::move(attributes));
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns a const `ast::Variable` constructed by calling Var() with the
-  /// arguments of `args`, which is automatically registered as a global
-  /// variable with the ast::Module.
-  template <typename NAME>
-  const ast::Variable* GlobalConst(const Source& source,
-                                   NAME&& name,
-                                   const ast::Type* type,
-                                   const ast::Expression* constructor,
-                                   ast::AttributeList attributes = {}) {
-    auto* var = Const(source, std::forward<NAME>(name), type, constructor,
-                      std::move(attributes));
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor optional constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns an overridable const `ast::Variable` which is automatically
-  /// registered as a global variable with the ast::Module.
-  template <typename NAME>
-  const ast::Variable* Override(NAME&& name,
-                                const ast::Type* type,
-                                const ast::Expression* constructor,
-                                ast::AttributeList attributes = {}) {
-    auto* var = create<ast::Variable>(
-        source_, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        true /* is_overridable */, constructor, std::move(attributes));
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param attributes optional variable attributes
-  /// @returns a const `ast::Variable` constructed by calling Var() with the
-  /// arguments of `args`, which is automatically registered as a global
-  /// variable with the ast::Module.
-  template <typename NAME>
-  const ast::Variable* Override(const Source& source,
-                                NAME&& name,
-                                const ast::Type* type,
-                                const ast::Expression* constructor,
-                                ast::AttributeList attributes = {}) {
-    auto* var = create<ast::Variable>(
-        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
-        ast::Access::kUndefined, type, true /* is_const */,
-        true /* is_overridable */, constructor, std::move(attributes));
-    AST().AddGlobalVariable(var);
-    return var;
-  }
-
-  /// @param source the source information
-  /// @param expr the expression to take the address of
-  /// @return an ast::UnaryOpExpression that takes the address of `expr`
-  template <typename EXPR>
-  const ast::UnaryOpExpression* AddressOf(const Source& source, EXPR&& expr) {
-    return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kAddressOf,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param expr the expression to take the address of
-  /// @return an ast::UnaryOpExpression that takes the address of `expr`
-  template <typename EXPR>
-  const ast::UnaryOpExpression* AddressOf(EXPR&& expr) {
-    return create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param source the source information
-  /// @param expr the expression to perform an indirection on
-  /// @return an ast::UnaryOpExpression that dereferences the pointer `expr`
-  template <typename EXPR>
-  const ast::UnaryOpExpression* Deref(const Source& source, EXPR&& expr) {
-    return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kIndirection,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param expr the expression to perform an indirection on
-  /// @return an ast::UnaryOpExpression that dereferences the pointer `expr`
-  template <typename EXPR>
-  const ast::UnaryOpExpression* Deref(EXPR&& expr) {
-    return create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param expr the expression to perform a unary not on
-  /// @return an ast::UnaryOpExpression that is the unary not of the input
-  /// expression
-  template <typename EXPR>
-  const ast::UnaryOpExpression* Not(EXPR&& expr) {
-    return create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
-                                          Expr(std::forward<EXPR>(expr)));
-  }
-
-  /// @param source the source information
-  /// @param func the function name
-  /// @param args the function call arguments
-  /// @returns a `ast::CallExpression` to the function `func`, with the
-  /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
-  template <typename NAME, typename... ARGS>
-  const ast::CallExpression* Call(const Source& source,
-                                  NAME&& func,
-                                  ARGS&&... args) {
-    return create<ast::CallExpression>(source, Expr(func),
-                                       ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param func the function name
-  /// @param args the function call arguments
-  /// @returns a `ast::CallExpression` to the function `func`, with the
-  /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
-  template <typename NAME, typename... ARGS, typename = DisableIfSource<NAME>>
-  const ast::CallExpression* Call(NAME&& func, ARGS&&... args) {
-    return create<ast::CallExpression>(Expr(func),
-                                       ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param source the source information
-  /// @param call the call expression to wrap in a call statement
-  /// @returns a `ast::CallStatement` for the given call expression
-  const ast::CallStatement* CallStmt(const Source& source,
-                                     const ast::CallExpression* call) {
-    return create<ast::CallStatement>(source, call);
-  }
-
-  /// @param call the call expression to wrap in a call statement
-  /// @returns a `ast::CallStatement` for the given call expression
-  const ast::CallStatement* CallStmt(const ast::CallExpression* call) {
-    return create<ast::CallStatement>(call);
-  }
-
-  /// @param source the source information
-  /// @returns a `ast::PhonyExpression`
-  const ast::PhonyExpression* Phony(const Source& source) {
-    return create<ast::PhonyExpression>(source);
-  }
-
-  /// @returns a `ast::PhonyExpression`
-  const ast::PhonyExpression* Phony() { return create<ast::PhonyExpression>(); }
-
-  /// @param expr the expression to ignore
-  /// @returns a `ast::AssignmentStatement` that assigns 'expr' to the phony
-  /// (underscore) variable.
-  template <typename EXPR>
-  const ast::AssignmentStatement* Ignore(EXPR&& expr) {
-    return create<ast::AssignmentStatement>(Phony(), Expr(expr));
-  }
-
-  /// @param lhs the left hand argument to the addition operation
-  /// @param rhs the right hand argument to the addition operation
-  /// @returns a `ast::BinaryExpression` summing the arguments `lhs` and `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Add(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kAdd,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the and operation
-  /// @param rhs the right hand argument to the and operation
-  /// @returns a `ast::BinaryExpression` bitwise anding `lhs` and `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* And(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kAnd,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the or operation
-  /// @param rhs the right hand argument to the or operation
-  /// @returns a `ast::BinaryExpression` bitwise or-ing `lhs` and `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Or(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kOr,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the subtraction operation
-  /// @param rhs the right hand argument to the subtraction operation
-  /// @returns a `ast::BinaryExpression` subtracting `rhs` from `lhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Sub(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kSubtract,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the multiplication operation
-  /// @param rhs the right hand argument to the multiplication operation
-  /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Mul(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param source the source information
-  /// @param lhs the left hand argument to the multiplication operation
-  /// @param rhs the right hand argument to the multiplication operation
-  /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Mul(const Source& source, LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(source, ast::BinaryOp::kMultiply,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the division operation
-  /// @param rhs the right hand argument to the division operation
-  /// @returns a `ast::BinaryExpression` dividing `lhs` by `rhs`
-  template <typename LHS, typename RHS>
-  const ast::Expression* Div(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kDivide,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the modulo operation
-  /// @param rhs the right hand argument to the modulo operation
-  /// @returns a `ast::BinaryExpression` applying modulo of `lhs` by `rhs`
-  template <typename LHS, typename RHS>
-  const ast::Expression* Mod(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kModulo,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the bit shift right operation
-  /// @param rhs the right hand argument to the bit shift right operation
-  /// @returns a `ast::BinaryExpression` bit shifting right `lhs` by `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Shr(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kShiftRight,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the bit shift left operation
-  /// @param rhs the right hand argument to the bit shift left operation
-  /// @returns a `ast::BinaryExpression` bit shifting left `lhs` by `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Shl(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kShiftLeft,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the equal expression
-  /// @param rhs the right hand argument to the equal expression
-  /// @returns a `ast::BinaryExpression` comparing `lhs` equal to `rhs`
-  template <typename LHS, typename RHS>
-  const ast::BinaryExpression* Equal(LHS&& lhs, RHS&& rhs) {
-    return create<ast::BinaryExpression>(ast::BinaryOp::kEqual,
-                                         Expr(std::forward<LHS>(lhs)),
-                                         Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param source the source information
-  /// @param obj the object for the index accessor expression
-  /// @param idx the index argument for the index accessor expression
-  /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx`
-  template <typename OBJ, typename IDX>
-  const ast::IndexAccessorExpression* IndexAccessor(const Source& source,
-                                                    OBJ&& obj,
-                                                    IDX&& idx) {
-    return create<ast::IndexAccessorExpression>(
-        source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx)));
-  }
-
-  /// @param obj the object for the index accessor expression
-  /// @param idx the index argument for the index accessor expression
-  /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx`
-  template <typename OBJ, typename IDX>
-  const ast::IndexAccessorExpression* IndexAccessor(OBJ&& obj, IDX&& idx) {
-    return create<ast::IndexAccessorExpression>(Expr(std::forward<OBJ>(obj)),
-                                                Expr(std::forward<IDX>(idx)));
-  }
-
-  /// @param source the source information
-  /// @param obj the object for the member accessor expression
-  /// @param idx the index argument for the member accessor expression
-  /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx`
-  template <typename OBJ, typename IDX>
-  const ast::MemberAccessorExpression* MemberAccessor(const Source& source,
-                                                      OBJ&& obj,
-                                                      IDX&& idx) {
-    return create<ast::MemberAccessorExpression>(
-        source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx)));
-  }
-
-  /// @param obj the object for the member accessor expression
-  /// @param idx the index argument for the member accessor expression
-  /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx`
-  template <typename OBJ, typename IDX>
-  const ast::MemberAccessorExpression* MemberAccessor(OBJ&& obj, IDX&& idx) {
-    return create<ast::MemberAccessorExpression>(Expr(std::forward<OBJ>(obj)),
-                                                 Expr(std::forward<IDX>(idx)));
-  }
-
-  /// Creates a ast::StructMemberOffsetAttribute
-  /// @param val the offset value
-  /// @returns the offset attribute pointer
-  const ast::StructMemberOffsetAttribute* MemberOffset(uint32_t val) {
-    return create<ast::StructMemberOffsetAttribute>(source_, val);
-  }
-
-  /// Creates a ast::StructMemberSizeAttribute
-  /// @param source the source information
-  /// @param val the size value
-  /// @returns the size attribute pointer
-  const ast::StructMemberSizeAttribute* MemberSize(const Source& source,
-                                                   uint32_t val) {
-    return create<ast::StructMemberSizeAttribute>(source, val);
-  }
-
-  /// Creates a ast::StructMemberSizeAttribute
-  /// @param val the size value
-  /// @returns the size attribute pointer
-  const ast::StructMemberSizeAttribute* MemberSize(uint32_t val) {
-    return create<ast::StructMemberSizeAttribute>(source_, val);
-  }
-
-  /// Creates a ast::StructMemberAlignAttribute
-  /// @param source the source information
-  /// @param val the align value
-  /// @returns the align attribute pointer
-  const ast::StructMemberAlignAttribute* MemberAlign(const Source& source,
-                                                     uint32_t val) {
-    return create<ast::StructMemberAlignAttribute>(source, val);
-  }
-
-  /// Creates a ast::StructMemberAlignAttribute
-  /// @param val the align value
-  /// @returns the align attribute pointer
-  const ast::StructMemberAlignAttribute* MemberAlign(uint32_t val) {
-    return create<ast::StructMemberAlignAttribute>(source_, val);
-  }
-
-  /// Creates a ast::StructBlockAttribute
-  /// @returns the struct block attribute pointer
-  const ast::StructBlockAttribute* StructBlock() {
-    return create<ast::StructBlockAttribute>();
-  }
-
-  /// Creates the ast::GroupAttribute
-  /// @param value group attribute index
-  /// @returns the group attribute pointer
-  const ast::GroupAttribute* Group(uint32_t value) {
-    return create<ast::GroupAttribute>(value);
-  }
-
-  /// Creates the ast::BindingAttribute
-  /// @param value the binding index
-  /// @returns the binding deocration pointer
-  const ast::BindingAttribute* Binding(uint32_t value) {
-    return create<ast::BindingAttribute>(value);
-  }
-
-  /// Convenience function to create both a ast::GroupAttribute and
-  /// ast::BindingAttribute
-  /// @param group the group index
-  /// @param binding the binding index
-  /// @returns a attribute list with both the group and binding attributes
-  ast::AttributeList GroupAndBinding(uint32_t group, uint32_t binding) {
-    return {Group(group), Binding(binding)};
-  }
-
-  /// Creates an ast::Function and registers it with the ast::Module.
-  /// @param source the source information
-  /// @param name the function name
-  /// @param params the function parameters
-  /// @param type the function return type
-  /// @param body the function body
-  /// @param attributes the optional function attributes
-  /// @param return_type_attributes the optional function return type
-  /// attributes
-  /// @returns the function pointer
-  template <typename NAME>
-  const ast::Function* Func(const Source& source,
-                            NAME&& name,
-                            ast::VariableList params,
-                            const ast::Type* type,
-                            ast::StatementList body,
-                            ast::AttributeList attributes = {},
-                            ast::AttributeList return_type_attributes = {}) {
-    auto* func = create<ast::Function>(
-        source, Sym(std::forward<NAME>(name)), params, type,
-        create<ast::BlockStatement>(body), attributes, return_type_attributes);
-    AST().AddFunction(func);
-    return func;
-  }
-
-  /// Creates an ast::Function and registers it with the ast::Module.
-  /// @param name the function name
-  /// @param params the function parameters
-  /// @param type the function return type
-  /// @param body the function body
-  /// @param attributes the optional function attributes
-  /// @param return_type_attributes the optional function return type
-  /// attributes
-  /// @returns the function pointer
-  template <typename NAME>
-  const ast::Function* Func(NAME&& name,
-                            ast::VariableList params,
-                            const ast::Type* type,
-                            ast::StatementList body,
-                            ast::AttributeList attributes = {},
-                            ast::AttributeList return_type_attributes = {}) {
-    auto* func = create<ast::Function>(Sym(std::forward<NAME>(name)), params,
-                                       type, create<ast::BlockStatement>(body),
-                                       attributes, return_type_attributes);
-    AST().AddFunction(func);
-    return func;
-  }
-
-  /// Creates an ast::BreakStatement
-  /// @param source the source information
-  /// @returns the break statement pointer
-  const ast::BreakStatement* Break(const Source& source) {
-    return create<ast::BreakStatement>(source);
-  }
-
-  /// Creates an ast::BreakStatement
-  /// @returns the break statement pointer
-  const ast::BreakStatement* Break() { return create<ast::BreakStatement>(); }
-
-  /// Creates an ast::ContinueStatement
-  /// @param source the source information
-  /// @returns the continue statement pointer
-  const ast::ContinueStatement* Continue(const Source& source) {
-    return create<ast::ContinueStatement>(source);
-  }
-
-  /// Creates an ast::ContinueStatement
-  /// @returns the continue statement pointer
-  const ast::ContinueStatement* Continue() {
-    return create<ast::ContinueStatement>();
-  }
-
-  /// Creates an ast::ReturnStatement with no return value
-  /// @param source the source information
-  /// @returns the return statement pointer
-  const ast::ReturnStatement* Return(const Source& source) {
-    return create<ast::ReturnStatement>(source);
-  }
-
-  /// Creates an ast::ReturnStatement with no return value
-  /// @returns the return statement pointer
-  const ast::ReturnStatement* Return() {
-    return create<ast::ReturnStatement>();
-  }
-
-  /// Creates an ast::ReturnStatement with the given return value
-  /// @param source the source information
-  /// @param val the return value
-  /// @returns the return statement pointer
-  template <typename EXPR>
-  const ast::ReturnStatement* Return(const Source& source, EXPR&& val) {
-    return create<ast::ReturnStatement>(source, Expr(std::forward<EXPR>(val)));
-  }
-
-  /// Creates an ast::ReturnStatement with the given return value
-  /// @param val the return value
-  /// @returns the return statement pointer
-  template <typename EXPR, typename = DisableIfSource<EXPR>>
-  const ast::ReturnStatement* Return(EXPR&& val) {
-    return create<ast::ReturnStatement>(Expr(std::forward<EXPR>(val)));
-  }
-
-  /// Creates an ast::DiscardStatement
-  /// @param source the source information
-  /// @returns the discard statement pointer
-  const ast::DiscardStatement* Discard(const Source& source) {
-    return create<ast::DiscardStatement>(source);
-  }
-
-  /// Creates an ast::DiscardStatement
-  /// @returns the discard statement pointer
-  const ast::DiscardStatement* Discard() {
-    return create<ast::DiscardStatement>();
-  }
-
-  /// Creates a ast::Alias registering it with the AST().TypeDecls().
-  /// @param source the source information
-  /// @param name the alias name
-  /// @param type the alias target type
-  /// @returns the alias type
-  template <typename NAME>
-  const ast::Alias* Alias(const Source& source,
-                          NAME&& name,
-                          const ast::Type* type) {
-    auto* out = ty.alias(source, std::forward<NAME>(name), type);
-    AST().AddTypeDecl(out);
-    return out;
-  }
-
-  /// Creates a ast::Alias registering it with the AST().TypeDecls().
-  /// @param name the alias name
-  /// @param type the alias target type
-  /// @returns the alias type
-  template <typename NAME>
-  const ast::Alias* Alias(NAME&& name, const ast::Type* type) {
-    auto* out = ty.alias(std::forward<NAME>(name), type);
-    AST().AddTypeDecl(out);
-    return out;
-  }
-
-  /// Creates a ast::Struct registering it with the AST().TypeDecls().
-  /// @param source the source information
-  /// @param name the struct name
-  /// @param members the struct members
-  /// @param attributes the optional struct attributes
-  /// @returns the struct type
-  template <typename NAME>
-  const ast::Struct* Structure(const Source& source,
-                               NAME&& name,
-                               ast::StructMemberList members,
-                               ast::AttributeList attributes = {}) {
-    auto sym = Sym(std::forward<NAME>(name));
-    auto* type = create<ast::Struct>(source, sym, std::move(members),
-                                     std::move(attributes));
-    AST().AddTypeDecl(type);
-    return type;
-  }
-
-  /// Creates a ast::Struct registering it with the AST().TypeDecls().
-  /// @param name the struct name
-  /// @param members the struct members
-  /// @param attributes the optional struct attributes
-  /// @returns the struct type
-  template <typename NAME>
-  const ast::Struct* Structure(NAME&& name,
-                               ast::StructMemberList members,
-                               ast::AttributeList attributes = {}) {
-    auto sym = Sym(std::forward<NAME>(name));
-    auto* type =
-        create<ast::Struct>(sym, std::move(members), std::move(attributes));
-    AST().AddTypeDecl(type);
-    return type;
-  }
-
-  /// Creates a ast::StructMember
-  /// @param source the source information
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @param attributes the optional struct member attributes
-  /// @returns the struct member pointer
-  template <typename NAME>
-  const ast::StructMember* Member(const Source& source,
-                                  NAME&& name,
-                                  const ast::Type* type,
-                                  ast::AttributeList attributes = {}) {
-    return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)),
-                                     type, std::move(attributes));
-  }
-
-  /// Creates a ast::StructMember
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @param attributes the optional struct member attributes
-  /// @returns the struct member pointer
-  template <typename NAME>
-  const ast::StructMember* Member(NAME&& name,
-                                  const ast::Type* type,
-                                  ast::AttributeList attributes = {}) {
-    return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)),
-                                     type, std::move(attributes));
-  }
-
-  /// Creates a ast::StructMember with the given byte offset
-  /// @param offset the offset to use in the StructMemberOffsetattribute
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @returns the struct member pointer
-  template <typename NAME>
-  const ast::StructMember* Member(uint32_t offset,
-                                  NAME&& name,
-                                  const ast::Type* type) {
-    return create<ast::StructMember>(
-        source_, Sym(std::forward<NAME>(name)), type,
-        ast::AttributeList{
-            create<ast::StructMemberOffsetAttribute>(offset),
-        });
-  }
-
-  /// Creates a ast::BlockStatement with input statements
-  /// @param source the source information for the block
-  /// @param statements statements of block
-  /// @returns the block statement pointer
-  template <typename... Statements>
-  const ast::BlockStatement* Block(const Source& source,
-                                   Statements&&... statements) {
-    return create<ast::BlockStatement>(
-        source, ast::StatementList{std::forward<Statements>(statements)...});
-  }
-
-  /// Creates a ast::BlockStatement with input statements
-  /// @param statements statements of block
-  /// @returns the block statement pointer
-  template <typename... STATEMENTS, typename = DisableIfSource<STATEMENTS...>>
-  const ast::BlockStatement* Block(STATEMENTS&&... statements) {
-    return create<ast::BlockStatement>(
-        ast::StatementList{std::forward<STATEMENTS>(statements)...});
-  }
-
-  /// Creates a ast::ElseStatement with input condition and body
-  /// @param condition the else condition expression
-  /// @param body the else body
-  /// @returns the else statement pointer
-  template <typename CONDITION>
-  const ast::ElseStatement* Else(CONDITION&& condition,
-                                 const ast::BlockStatement* body) {
-    return create<ast::ElseStatement>(Expr(std::forward<CONDITION>(condition)),
-                                      body);
-  }
-
-  /// Creates a ast::ElseStatement with no condition and body
-  /// @param body the else body
-  /// @returns the else statement pointer
-  const ast::ElseStatement* Else(const ast::BlockStatement* body) {
-    return create<ast::ElseStatement>(nullptr, body);
-  }
-
-  /// Creates a ast::IfStatement with input condition, body, and optional
-  /// variadic else statements
-  /// @param source the source information for the if statement
-  /// @param condition the if statement condition expression
-  /// @param body the if statement body
-  /// @param elseStatements optional variadic else statements
-  /// @returns the if statement pointer
-  template <typename CONDITION, typename... ELSE_STATEMENTS>
-  const ast::IfStatement* If(const Source& source,
-                             CONDITION&& condition,
-                             const ast::BlockStatement* body,
-                             ELSE_STATEMENTS&&... elseStatements) {
-    return create<ast::IfStatement>(
-        source, Expr(std::forward<CONDITION>(condition)), body,
-        ast::ElseStatementList{
-            std::forward<ELSE_STATEMENTS>(elseStatements)...});
-  }
-
-  /// Creates a ast::IfStatement with input condition, body, and optional
-  /// variadic else statements
-  /// @param condition the if statement condition expression
-  /// @param body the if statement body
-  /// @param elseStatements optional variadic else statements
-  /// @returns the if statement pointer
-  template <typename CONDITION, typename... ELSE_STATEMENTS>
-  const ast::IfStatement* If(CONDITION&& condition,
-                             const ast::BlockStatement* body,
-                             ELSE_STATEMENTS&&... elseStatements) {
-    return create<ast::IfStatement>(
-        Expr(std::forward<CONDITION>(condition)), body,
-        ast::ElseStatementList{
-            std::forward<ELSE_STATEMENTS>(elseStatements)...});
-  }
-
-  /// Creates a ast::AssignmentStatement with input lhs and rhs expressions
-  /// @param source the source information
-  /// @param lhs the left hand side expression initializer
-  /// @param rhs the right hand side expression initializer
-  /// @returns the assignment statement pointer
-  template <typename LhsExpressionInit, typename RhsExpressionInit>
-  const ast::AssignmentStatement* Assign(const Source& source,
-                                         LhsExpressionInit&& lhs,
-                                         RhsExpressionInit&& rhs) {
-    return create<ast::AssignmentStatement>(
-        source, Expr(std::forward<LhsExpressionInit>(lhs)),
-        Expr(std::forward<RhsExpressionInit>(rhs)));
-  }
-
-  /// Creates a ast::AssignmentStatement with input lhs and rhs expressions
-  /// @param lhs the left hand side expression initializer
-  /// @param rhs the right hand side expression initializer
-  /// @returns the assignment statement pointer
-  template <typename LhsExpressionInit, typename RhsExpressionInit>
-  const ast::AssignmentStatement* Assign(LhsExpressionInit&& lhs,
-                                         RhsExpressionInit&& rhs) {
-    return create<ast::AssignmentStatement>(
-        Expr(std::forward<LhsExpressionInit>(lhs)),
-        Expr(std::forward<RhsExpressionInit>(rhs)));
-  }
-
-  /// Creates a ast::LoopStatement with input body and optional continuing
-  /// @param source the source information
-  /// @param body the loop body
-  /// @param continuing the optional continuing block
-  /// @returns the loop statement pointer
-  const ast::LoopStatement* Loop(
-      const Source& source,
-      const ast::BlockStatement* body,
-      const ast::BlockStatement* continuing = nullptr) {
-    return create<ast::LoopStatement>(source, body, continuing);
-  }
-
-  /// Creates a ast::LoopStatement with input body and optional continuing
-  /// @param body the loop body
-  /// @param continuing the optional continuing block
-  /// @returns the loop statement pointer
-  const ast::LoopStatement* Loop(
-      const ast::BlockStatement* body,
-      const ast::BlockStatement* continuing = nullptr) {
-    return create<ast::LoopStatement>(body, continuing);
-  }
-
-  /// Creates a ast::ForLoopStatement with input body and optional initializer,
-  /// condition and continuing.
-  /// @param source the source information
-  /// @param init the optional loop initializer
-  /// @param cond the optional loop condition
-  /// @param cont the optional loop continuing
-  /// @param body the loop body
-  /// @returns the for loop statement pointer
-  template <typename COND>
-  const ast::ForLoopStatement* For(const Source& source,
-                                   const ast::Statement* init,
-                                   COND&& cond,
-                                   const ast::Statement* cont,
-                                   const ast::BlockStatement* body) {
-    return create<ast::ForLoopStatement>(
-        source, init, Expr(std::forward<COND>(cond)), cont, body);
-  }
-
-  /// Creates a ast::ForLoopStatement with input body and optional initializer,
-  /// condition and continuing.
-  /// @param init the optional loop initializer
-  /// @param cond the optional loop condition
-  /// @param cont the optional loop continuing
-  /// @param body the loop body
-  /// @returns the for loop statement pointer
-  template <typename COND>
-  const ast::ForLoopStatement* For(const ast::Statement* init,
-                                   COND&& cond,
-                                   const ast::Statement* cont,
-                                   const ast::BlockStatement* body) {
-    return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)),
-                                         cont, body);
-  }
-
-  /// Creates a ast::VariableDeclStatement for the input variable
-  /// @param source the source information
-  /// @param var the variable to wrap in a decl statement
-  /// @returns the variable decl statement pointer
-  const ast::VariableDeclStatement* Decl(const Source& source,
-                                         const ast::Variable* var) {
-    return create<ast::VariableDeclStatement>(source, var);
-  }
-
-  /// Creates a ast::VariableDeclStatement for the input variable
-  /// @param var the variable to wrap in a decl statement
-  /// @returns the variable decl statement pointer
-  const ast::VariableDeclStatement* Decl(const ast::Variable* var) {
-    return create<ast::VariableDeclStatement>(var);
-  }
-
-  /// Creates a ast::SwitchStatement with input expression and cases
-  /// @param source the source information
-  /// @param condition the condition expression initializer
-  /// @param cases case statements
-  /// @returns the switch statement pointer
-  template <typename ExpressionInit, typename... Cases>
-  const ast::SwitchStatement* Switch(const Source& source,
-                                     ExpressionInit&& condition,
-                                     Cases&&... cases) {
-    return create<ast::SwitchStatement>(
-        source, Expr(std::forward<ExpressionInit>(condition)),
-        ast::CaseStatementList{std::forward<Cases>(cases)...});
-  }
-
-  /// Creates a ast::SwitchStatement with input expression and cases
-  /// @param condition the condition expression initializer
-  /// @param cases case statements
-  /// @returns the switch statement pointer
-  template <typename ExpressionInit,
-            typename... Cases,
-            typename = DisableIfSource<ExpressionInit>>
-  const ast::SwitchStatement* Switch(ExpressionInit&& condition,
-                                     Cases&&... cases) {
-    return create<ast::SwitchStatement>(
-        Expr(std::forward<ExpressionInit>(condition)),
-        ast::CaseStatementList{std::forward<Cases>(cases)...});
-  }
-
-  /// Creates a ast::CaseStatement with input list of selectors, and body
-  /// @param source the source information
-  /// @param selectors list of selectors
-  /// @param body the case body
-  /// @returns the case statement pointer
-  const ast::CaseStatement* Case(const Source& source,
-                                 ast::CaseSelectorList selectors,
-                                 const ast::BlockStatement* body = nullptr) {
-    return create<ast::CaseStatement>(source, std::move(selectors),
-                                      body ? body : Block());
-  }
-
-  /// Creates a ast::CaseStatement with input list of selectors, and body
-  /// @param selectors list of selectors
-  /// @param body the case body
-  /// @returns the case statement pointer
-  const ast::CaseStatement* Case(ast::CaseSelectorList selectors,
-                                 const ast::BlockStatement* body = nullptr) {
-    return create<ast::CaseStatement>(std::move(selectors),
-                                      body ? body : Block());
-  }
-
-  /// Convenient overload that takes a single selector
-  /// @param selector a single case selector
-  /// @param body the case body
-  /// @returns the case statement pointer
-  const ast::CaseStatement* Case(const ast::IntLiteralExpression* selector,
-                                 const ast::BlockStatement* body = nullptr) {
-    return Case(ast::CaseSelectorList{selector}, body);
-  }
-
-  /// Convenience function that creates a 'default' ast::CaseStatement
-  /// @param source the source information
-  /// @param body the case body
-  /// @returns the case statement pointer
-  const ast::CaseStatement* DefaultCase(
-      const Source& source,
-      const ast::BlockStatement* body = nullptr) {
-    return Case(source, ast::CaseSelectorList{}, body);
-  }
-
-  /// Convenience function that creates a 'default' ast::CaseStatement
-  /// @param body the case body
-  /// @returns the case statement pointer
-  const ast::CaseStatement* DefaultCase(
-      const ast::BlockStatement* body = nullptr) {
-    return Case(ast::CaseSelectorList{}, body);
-  }
-
-  /// Creates an ast::FallthroughStatement
-  /// @param source the source information
-  /// @returns the fallthrough statement pointer
-  const ast::FallthroughStatement* Fallthrough(const Source& source) {
-    return create<ast::FallthroughStatement>(source);
-  }
-
-  /// Creates an ast::FallthroughStatement
-  /// @returns the fallthrough statement pointer
-  const ast::FallthroughStatement* Fallthrough() {
-    return create<ast::FallthroughStatement>();
-  }
-
-  /// Creates an ast::BuiltinAttribute
-  /// @param source the source information
-  /// @param builtin the builtin value
-  /// @returns the builtin attribute pointer
-  const ast::BuiltinAttribute* Builtin(const Source& source,
-                                       ast::Builtin builtin) {
-    return create<ast::BuiltinAttribute>(source, builtin);
-  }
-
-  /// Creates an ast::BuiltinAttribute
-  /// @param builtin the builtin value
-  /// @returns the builtin attribute pointer
-  const ast::BuiltinAttribute* Builtin(ast::Builtin builtin) {
-    return create<ast::BuiltinAttribute>(source_, builtin);
-  }
-
-  /// Creates an ast::InterpolateAttribute
-  /// @param source the source information
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  /// @returns the interpolate attribute pointer
-  const ast::InterpolateAttribute* Interpolate(
-      const Source& source,
-      ast::InterpolationType type,
-      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) {
-    return create<ast::InterpolateAttribute>(source, type, sampling);
-  }
-
-  /// Creates an ast::InterpolateAttribute
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  /// @returns the interpolate attribute pointer
-  const ast::InterpolateAttribute* Interpolate(
-      ast::InterpolationType type,
-      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) {
-    return create<ast::InterpolateAttribute>(source_, type, sampling);
-  }
-
-  /// Creates an ast::InterpolateAttribute using flat interpolation
-  /// @param source the source information
-  /// @returns the interpolate attribute pointer
-  const ast::InterpolateAttribute* Flat(const Source& source) {
-    return Interpolate(source, ast::InterpolationType::kFlat);
-  }
-
-  /// Creates an ast::InterpolateAttribute using flat interpolation
-  /// @returns the interpolate attribute pointer
-  const ast::InterpolateAttribute* Flat() {
-    return Interpolate(ast::InterpolationType::kFlat);
-  }
-
-  /// Creates an ast::InvariantAttribute
-  /// @param source the source information
-  /// @returns the invariant attribute pointer
-  const ast::InvariantAttribute* Invariant(const Source& source) {
-    return create<ast::InvariantAttribute>(source);
-  }
-
-  /// Creates an ast::InvariantAttribute
-  /// @returns the invariant attribute pointer
-  const ast::InvariantAttribute* Invariant() {
-    return create<ast::InvariantAttribute>(source_);
-  }
-
-  /// Creates an ast::LocationAttribute
-  /// @param source the source information
-  /// @param location the location value
-  /// @returns the location attribute pointer
-  const ast::LocationAttribute* Location(const Source& source,
-                                         uint32_t location) {
-    return create<ast::LocationAttribute>(source, location);
-  }
-
-  /// Creates an ast::LocationAttribute
-  /// @param location the location value
-  /// @returns the location attribute pointer
-  const ast::LocationAttribute* Location(uint32_t location) {
-    return create<ast::LocationAttribute>(source_, location);
-  }
-
-  /// Creates an ast::IdAttribute
-  /// @param source the source information
-  /// @param id the id value
-  /// @returns the override attribute pointer
-  const ast::IdAttribute* Id(const Source& source, uint32_t id) {
-    return create<ast::IdAttribute>(source, id);
-  }
-
-  /// Creates an ast::IdAttribute with a constant ID
-  /// @param id the optional id value
-  /// @returns the override attribute pointer
-  const ast::IdAttribute* Id(uint32_t id) { return Id(source_, id); }
-
-  /// Creates an ast::StageAttribute
-  /// @param source the source information
-  /// @param stage the pipeline stage
-  /// @returns the stage attribute pointer
-  const ast::StageAttribute* Stage(const Source& source,
-                                   ast::PipelineStage stage) {
-    return create<ast::StageAttribute>(source, stage);
-  }
-
-  /// Creates an ast::StageAttribute
-  /// @param stage the pipeline stage
-  /// @returns the stage attribute pointer
-  const ast::StageAttribute* Stage(ast::PipelineStage stage) {
-    return create<ast::StageAttribute>(source_, stage);
-  }
-
-  /// Creates an ast::WorkgroupAttribute
-  /// @param x the x dimension expression
-  /// @returns the workgroup attribute pointer
-  template <typename EXPR_X>
-  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x) {
-    return WorkgroupSize(std::forward<EXPR_X>(x), nullptr, nullptr);
-  }
-
-  /// Creates an ast::WorkgroupAttribute
-  /// @param x the x dimension expression
-  /// @param y the y dimension expression
-  /// @returns the workgroup attribute pointer
-  template <typename EXPR_X, typename EXPR_Y>
-  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x, EXPR_Y&& y) {
-    return WorkgroupSize(std::forward<EXPR_X>(x), std::forward<EXPR_Y>(y),
-                         nullptr);
-  }
-
-  /// Creates an ast::WorkgroupAttribute
-  /// @param source the source information
-  /// @param x the x dimension expression
-  /// @param y the y dimension expression
-  /// @param z the z dimension expression
-  /// @returns the workgroup attribute pointer
-  template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z>
-  const ast::WorkgroupAttribute* WorkgroupSize(const Source& source,
-                                               EXPR_X&& x,
-                                               EXPR_Y&& y,
-                                               EXPR_Z&& z) {
-    return create<ast::WorkgroupAttribute>(
-        source, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)),
-        Expr(std::forward<EXPR_Z>(z)));
-  }
-
-  /// Creates an ast::WorkgroupAttribute
-  /// @param x the x dimension expression
-  /// @param y the y dimension expression
-  /// @param z the z dimension expression
-  /// @returns the workgroup attribute pointer
-  template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z>
-  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x,
-                                               EXPR_Y&& y,
-                                               EXPR_Z&& z) {
-    return create<ast::WorkgroupAttribute>(
-        source_, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)),
-        Expr(std::forward<EXPR_Z>(z)));
-  }
-
-  /// Creates an ast::DisableValidationAttribute
-  /// @param validation the validation to disable
-  /// @returns the disable validation attribute pointer
-  const ast::DisableValidationAttribute* Disable(
-      ast::DisabledValidation validation) {
-    return ASTNodes().Create<ast::DisableValidationAttribute>(ID(), validation);
-  }
-
-  /// Sets the current builder source to `src`
-  /// @param src the Source used for future create() calls
-  void SetSource(const Source& src) {
-    AssertNotMoved();
-    source_ = src;
-  }
-
-  /// Sets the current builder source to `loc`
-  /// @param loc the Source used for future create() calls
-  void SetSource(const Source::Location& loc) {
-    AssertNotMoved();
-    source_ = Source(loc);
-  }
-
-  /// Helper for returning the resolved semantic type of the expression `expr`.
-  /// @note As the Resolver is run when the Program is built, this will only be
-  /// useful for the Resolver itself and tests that use their own Resolver.
-  /// @param expr the AST expression
-  /// @return the resolved semantic type for the expression, or nullptr if the
-  /// expression has no resolved type.
-  const sem::Type* TypeOf(const ast::Expression* expr) const;
-
-  /// Helper for returning the resolved semantic type of the variable `var`.
-  /// @note As the Resolver is run when the Program is built, this will only be
-  /// useful for the Resolver itself and tests that use their own Resolver.
-  /// @param var the AST variable
-  /// @return the resolved semantic type for the variable, or nullptr if the
-  /// variable has no resolved type.
-  const sem::Type* TypeOf(const ast::Variable* var) const;
-
-  /// Helper for returning the resolved semantic type of the AST type `type`.
-  /// @note As the Resolver is run when the Program is built, this will only be
-  /// useful for the Resolver itself and tests that use their own Resolver.
-  /// @param type the AST type
-  /// @return the resolved semantic type for the type, or nullptr if the type
-  /// has no resolved type.
-  const sem::Type* TypeOf(const ast::Type* type) const;
-
-  /// Helper for returning the resolved semantic type of the AST type
-  /// declaration `type_decl`.
-  /// @note As the Resolver is run when the Program is built, this will only be
-  /// useful for the Resolver itself and tests that use their own Resolver.
-  /// @param type_decl the AST type declaration
-  /// @return the resolved semantic type for the type declaration, or nullptr if
-  /// the type declaration has no resolved type.
-  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
-
-  /// Wraps the ast::Expression in a statement. This is used by tests that
-  /// construct a partial AST and require the Resolver to reach these
-  /// nodes.
-  /// @param expr the ast::Expression to be wrapped by an ast::Statement
-  /// @return the ast::Statement that wraps the ast::Expression
-  const ast::Statement* WrapInStatement(const ast::Expression* expr);
-  /// Wraps the ast::Variable in a ast::VariableDeclStatement. This is used by
-  /// tests that construct a partial AST and require the Resolver to reach
-  /// these nodes.
-  /// @param v the ast::Variable to be wrapped by an ast::VariableDeclStatement
-  /// @return the ast::VariableDeclStatement that wraps the ast::Variable
-  const ast::VariableDeclStatement* WrapInStatement(const ast::Variable* v);
-  /// Returns the statement argument. Used as a passthrough-overload by
-  /// WrapInFunction().
-  /// @param stmt the ast::Statement
-  /// @return `stmt`
-  const ast::Statement* WrapInStatement(const ast::Statement* stmt);
-  /// Wraps the list of arguments in a simple function so that each is reachable
-  /// by the Resolver.
-  /// @param args a mix of ast::Expression, ast::Statement, ast::Variables.
-  /// @returns the function
-  template <typename... ARGS>
-  const ast::Function* WrapInFunction(ARGS&&... args) {
-    ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...};
-    return WrapInFunction(std::move(stmts));
-  }
-  /// @param stmts a list of ast::Statement that will be wrapped by a function,
-  /// so that each statement is reachable by the Resolver.
-  /// @returns the function
-  const ast::Function* WrapInFunction(ast::StatementList stmts);
-
-  /// The builder types
-  TypesBuilder const ty{this};
-
- protected:
-  /// Asserts that the builder has not been moved.
-  void AssertNotMoved() const;
-
- private:
-  ProgramID id_;
-  sem::Manager types_;
-  ASTNodeAllocator ast_nodes_;
-  SemNodeAllocator sem_nodes_;
-  ast::Module* ast_;
-  sem::Info sem_;
-  SymbolTable symbols_{id_};
-  diag::List diagnostics_;
-
-  /// The source to use when creating AST nodes without providing a Source as
-  /// the first argument.
-  Source source_;
-
-  /// Set by SetResolveOnBuild(). If set, the Resolver will be run on the
-  /// program when built.
-  bool resolve_on_build_ = true;
-
-  /// Set by MarkAsMoved(). Once set, no methods may be called on this builder.
-  bool moved_ = false;
-};
-
-//! @cond Doxygen_Suppress
-// Various template specializations for ProgramBuilder::TypesBuilder::CToAST.
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::i32> {
-  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
-    return t->i32();
-  }
-};
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::u32> {
-  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
-    return t->u32();
-  }
-};
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::f32> {
-  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
-    return t->f32();
-  }
-};
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<bool> {
-  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
-    return t->bool_();
-  }
-};
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<void> {
-  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
-    return t->void_();
-  }
-};
-//! @endcond
-
-/// @param builder the ProgramBuilder
-/// @returns the ProgramID of the ProgramBuilder
-inline ProgramID ProgramIDOf(const ProgramBuilder* builder) {
-  return builder->ID();
-}
-
-}  // namespace tint
-
-#endif  // SRC_PROGRAM_BUILDER_H_
diff --git a/src/program_builder_test.cc b/src/program_builder_test.cc
deleted file mode 100644
index 5367cdd..0000000
--- a/src/program_builder_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/program_builder.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-using ProgramBuilderTest = testing::Test;
-
-TEST_F(ProgramBuilderTest, IDsAreUnique) {
-  Program program_a(ProgramBuilder{});
-  Program program_b(ProgramBuilder{});
-  Program program_c(ProgramBuilder{});
-  EXPECT_NE(program_a.ID(), program_b.ID());
-  EXPECT_NE(program_b.ID(), program_c.ID());
-  EXPECT_NE(program_c.ID(), program_a.ID());
-}
-
-TEST_F(ProgramBuilderTest, WrapDoesntAffectInner) {
-  Program inner([] {
-    ProgramBuilder builder;
-    auto* ty = builder.ty.f32();
-    builder.Func("a", {}, ty, {}, {});
-    return builder;
-  }());
-
-  ASSERT_EQ(inner.AST().Functions().size(), 1u);
-  ASSERT_TRUE(inner.Symbols().Get("a").IsValid());
-  ASSERT_FALSE(inner.Symbols().Get("b").IsValid());
-
-  ProgramBuilder outer = ProgramBuilder::Wrap(&inner);
-
-  ASSERT_EQ(inner.AST().Functions().size(), 1u);
-  ASSERT_EQ(outer.AST().Functions().size(), 1u);
-  EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
-  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
-  EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
-  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
-  EXPECT_TRUE(outer.Symbols().Get("a").IsValid());
-  EXPECT_FALSE(inner.Symbols().Get("b").IsValid());
-  EXPECT_FALSE(outer.Symbols().Get("b").IsValid());
-
-  auto* ty = outer.ty.f32();
-  outer.Func("b", {}, ty, {}, {});
-
-  ASSERT_EQ(inner.AST().Functions().size(), 1u);
-  ASSERT_EQ(outer.AST().Functions().size(), 2u);
-  EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
-  EXPECT_EQ(outer.AST().Functions()[1]->symbol, outer.Symbols().Get("b"));
-  EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
-  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
-  EXPECT_TRUE(outer.Symbols().Get("a").IsValid());
-  EXPECT_FALSE(inner.Symbols().Get("b").IsValid());
-  EXPECT_TRUE(outer.Symbols().Get("b").IsValid());
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/program_id.cc b/src/program_id.cc
deleted file mode 100644
index 13fdc53..0000000
--- a/src/program_id.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/program_id.h"
-
-#include <atomic>
-
-namespace tint {
-
-namespace {
-
-std::atomic<uint32_t> next_program_id{1};
-
-}  // namespace
-
-ProgramID::ProgramID() = default;
-
-ProgramID::ProgramID(uint32_t id) : val(id) {}
-
-ProgramID ProgramID::New() {
-  return ProgramID(next_program_id++);
-}
-
-namespace detail {
-
-/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and
-/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs
-/// `a` and `b` are equal.
-void AssertProgramIDsEqual(ProgramID a,
-                           ProgramID b,
-                           bool if_valid,
-                           diag::System system,
-                           const char* msg,
-                           const char* file,
-                           size_t line) {
-  if (a == b) {
-    return;  // matched
-  }
-  if (if_valid && (!a || !b)) {
-    return;  //  a or b were not valid
-  }
-  diag::List diagnostics;
-  tint::InternalCompilerError(file, line, system, diagnostics) << msg;
-}
-
-}  // namespace detail
-}  // namespace tint
diff --git a/src/program_id.h b/src/program_id.h
deleted file mode 100644
index 92e341a..0000000
--- a/src/program_id.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_PROGRAM_ID_H_
-#define SRC_PROGRAM_ID_H_
-
-#include <stdint.h>
-#include <iostream>
-#include <utility>
-
-#include "src/debug.h"
-
-namespace tint {
-
-/// If 1 then checks are enabled that AST nodes are not leaked from one program
-/// to another.
-/// TODO(bclayton): We'll want to disable this in production builds. For now we
-/// always check.
-#define TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS 1
-
-/// A ProgramID is a unique identifier of a Program.
-/// ProgramID can be used to ensure that objects referenced by the Program are
-/// owned exclusively by that Program and have accidentally not leaked from
-/// another Program.
-class ProgramID {
- public:
-  /// Constructor
-  ProgramID();
-
-  /// @returns a new. globally unique ProgramID
-  static ProgramID New();
-
-  /// Equality operator
-  /// @param rhs the other ProgramID
-  /// @returns true if the ProgramIDs are equal
-  bool operator==(const ProgramID& rhs) const { return val == rhs.val; }
-
-  /// Inequality operator
-  /// @param rhs the other ProgramID
-  /// @returns true if the ProgramIDs are not equal
-  bool operator!=(const ProgramID& rhs) const { return val != rhs.val; }
-
-  /// @returns the numerical identifier value
-  uint32_t Value() const { return val; }
-
-  /// @returns true if this ProgramID is valid
-  operator bool() const { return val != 0; }
-
- private:
-  explicit ProgramID(uint32_t);
-
-  uint32_t val = 0;
-};
-
-/// A simple pass-through function for ProgramID. Intended to be overloaded for
-/// other types.
-/// @param id a ProgramID
-/// @returns id. Simple pass-through function
-inline ProgramID ProgramIDOf(ProgramID id) {
-  return id;
-}
-
-/// Writes the ProgramID to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param id the program identifier to write
-/// @returns out so calls can be chained
-inline std::ostream& operator<<(std::ostream& out, ProgramID id) {
-  out << "Program<" << id.Value() << ">";
-  return out;
-}
-
-namespace detail {
-
-/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and
-/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs
-/// `a` and `b` are equal.
-void AssertProgramIDsEqual(ProgramID a,
-                           ProgramID b,
-                           bool if_valid,
-                           diag::System system,
-                           const char* msg,
-                           const char* file,
-                           size_t line);
-
-}  // namespace detail
-
-/// TINT_ASSERT_PROGRAM_IDS_EQUAL(SYSTEM, A, B) is a macro that asserts that the
-/// program identifiers for A and B are equal.
-///
-/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(SYSTEM, A, B) is a macro that asserts
-/// that the program identifiers for A and B are equal, if both A and B have
-/// valid program identifiers.
-#if TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS
-#define TINT_ASSERT_PROGRAM_IDS_EQUAL(system, a, b)                          \
-  detail::AssertProgramIDsEqual(                                             \
-      ProgramIDOf(a), ProgramIDOf(b), false, tint::diag::System::system,     \
-      "TINT_ASSERT_PROGRAM_IDS_EQUAL(" #system "," #a ", " #b ")", __FILE__, \
-      __LINE__)
-#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(system, a, b)                 \
-  detail::AssertProgramIDsEqual(                                             \
-      ProgramIDOf(a), ProgramIDOf(b), true, tint::diag::System::system,      \
-      "TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(" #system ", " #a ", " #b ")", \
-      __FILE__, __LINE__)
-#else
-#define TINT_ASSERT_PROGRAM_IDS_EQUAL(a, b) \
-  do {                                      \
-  } while (false)
-#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(a, b) \
-  do {                                               \
-  } while (false)
-#endif
-
-}  // namespace tint
-
-#endif  // SRC_PROGRAM_ID_H_
diff --git a/src/program_test.cc b/src/program_test.cc
deleted file mode 100644
index afedbf5..0000000
--- a/src/program_test.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2020 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 "gtest/gtest-spi.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/test_helper.h"
-
-namespace tint {
-namespace {
-
-using ProgramTest = ast::TestHelper;
-
-TEST_F(ProgramTest, Unbuilt) {
-  Program program;
-  EXPECT_FALSE(program.IsValid());
-}
-
-TEST_F(ProgramTest, Creation) {
-  Program program(std::move(*this));
-  EXPECT_EQ(program.AST().Functions().size(), 0u);
-}
-
-TEST_F(ProgramTest, EmptyIsValid) {
-  Program program(std::move(*this));
-  EXPECT_TRUE(program.IsValid());
-}
-
-TEST_F(ProgramTest, IDsAreUnique) {
-  Program program_a(ProgramBuilder{});
-  Program program_b(ProgramBuilder{});
-  Program program_c(ProgramBuilder{});
-  EXPECT_NE(program_a.ID(), program_b.ID());
-  EXPECT_NE(program_b.ID(), program_c.ID());
-  EXPECT_NE(program_c.ID(), program_a.ID());
-}
-
-TEST_F(ProgramTest, Assert_GlobalVariable) {
-  Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  Program program(std::move(*this));
-  EXPECT_TRUE(program.IsValid());
-}
-
-TEST_F(ProgramTest, Assert_NullGlobalVariable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.AST().AddGlobalVariable(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ProgramTest, Assert_NullTypeDecl) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.AST().AddTypeDecl(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ProgramTest, Assert_Null_Function) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.AST().AddFunction(nullptr);
-      },
-      "internal compiler error");
-}
-
-TEST_F(ProgramTest, DiagnosticsMove) {
-  Diagnostics().add_error(diag::System::Program, "an error message");
-
-  Program program_a(std::move(*this));
-  EXPECT_FALSE(program_a.IsValid());
-  EXPECT_EQ(program_a.Diagnostics().count(), 1u);
-  EXPECT_EQ(program_a.Diagnostics().error_count(), 1u);
-  EXPECT_EQ(program_a.Diagnostics().begin()->message, "an error message");
-
-  Program program_b(std::move(program_a));
-  EXPECT_FALSE(program_b.IsValid());
-  EXPECT_EQ(program_b.Diagnostics().count(), 1u);
-  EXPECT_EQ(program_b.Diagnostics().error_count(), 1u);
-  EXPECT_EQ(program_b.Diagnostics().begin()->message, "an error message");
-}
-
-TEST_F(ProgramTest, ReuseMovedFromVariable) {
-  Program a(std::move(*this));
-  EXPECT_TRUE(a.IsValid());
-
-  Program b = std::move(a);
-  EXPECT_TRUE(b.IsValid());
-
-  a = std::move(b);
-  EXPECT_TRUE(a.IsValid());
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/reader/reader.cc b/src/reader/reader.cc
deleted file mode 100644
index 6d90273..0000000
--- a/src/reader/reader.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 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/reader/reader.h"
-
-namespace tint {
-namespace reader {
-
-Reader::Reader() = default;
-
-Reader::~Reader() = default;
-
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/reader.h b/src/reader/reader.h
deleted file mode 100644
index 6ea79d1..0000000
--- a/src/reader/reader.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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_READER_READER_H_
-#define SRC_READER_READER_H_
-
-#include <string>
-
-#include "src/program.h"
-
-namespace tint {
-namespace reader {
-
-/// Base class for input readers
-class Reader {
- public:
-  virtual ~Reader();
-
-  /// Parses the input data
-  /// @returns true if the parse was successful
-  virtual bool Parse() = 0;
-
-  /// @returns true if an error was encountered.
-  bool has_error() const { return diags_.contains_errors(); }
-
-  /// @returns the parser error string
-  std::string error() const {
-    diag::Formatter formatter{{false, false, false, false}};
-    return formatter.format(diags_);
-  }
-
-  /// @returns the full list of diagnostic messages.
-  const diag::List& diagnostics() const { return diags_; }
-
-  /// @returns the program. The program builder in the parser will be reset
-  /// after this.
-  virtual Program program() = 0;
-
- protected:
-  /// Constructor
-  Reader();
-
-  /// Sets the diagnostic messages
-  /// @param diags the list of diagnostic messages
-  void set_diagnostics(const diag::List& diags) { diags_ = diags; }
-
-  /// All diagnostic messages from the reader.
-  diag::List diags_;
-};
-
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_READER_H_
diff --git a/src/reader/spirv/construct.cc b/src/reader/spirv/construct.cc
deleted file mode 100644
index 2b5c7fc..0000000
--- a/src/reader/spirv/construct.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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/reader/spirv/construct.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-Construct::Construct(const Construct* the_parent,
-                     int the_depth,
-                     Kind the_kind,
-                     uint32_t the_begin_id,
-                     uint32_t the_end_id,
-                     uint32_t the_begin_pos,
-                     uint32_t the_end_pos,
-                     uint32_t the_scope_end_pos)
-    : parent(the_parent),
-      enclosing_loop(
-          // Compute the enclosing loop construct. Doing this in the
-          // constructor member list lets us make the member const.
-          // Compare parent depth because loop and continue are siblings and
-          // it's incidental which will appear on the stack first.
-          the_kind == kLoop
-              ? this
-              : ((parent && parent->depth < the_depth) ? parent->enclosing_loop
-                                                       : nullptr)),
-      enclosing_continue(
-          // Compute the enclosing continue construct. Doing this in the
-          // constructor member list lets us make the member const.
-          // Compare parent depth because loop and continue are siblings and
-          // it's incidental which will appear on the stack first.
-          the_kind == kContinue ? this
-                                : ((parent && parent->depth < the_depth)
-                                       ? parent->enclosing_continue
-                                       : nullptr)),
-      enclosing_loop_or_continue_or_switch(
-          // Compute the enclosing loop or continue or switch construct.
-          // Doing this in the constructor member list lets us make the
-          // member const.
-          // Compare parent depth because loop and continue are siblings and
-          // it's incidental which will appear on the stack first.
-          (the_kind == kLoop || the_kind == kContinue ||
-           the_kind == kSwitchSelection)
-              ? this
-              : ((parent && parent->depth < the_depth)
-                     ? parent->enclosing_loop_or_continue_or_switch
-                     : nullptr)),
-      depth(the_depth),
-      kind(the_kind),
-      begin_id(the_begin_id),
-      end_id(the_end_id),
-      begin_pos(the_begin_pos),
-      end_pos(the_end_pos),
-      scope_end_pos(the_scope_end_pos) {}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/construct.h b/src/reader/spirv/construct.h
deleted file mode 100644
index 0347bc3..0000000
--- a/src/reader/spirv/construct.h
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_CONSTRUCT_H_
-#define SRC_READER_SPIRV_CONSTRUCT_H_
-
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// A structured control flow construct, consisting of a set of basic blocks.
-/// A construct is a span of blocks in the computed block order,
-/// and will appear contiguously in the WGSL source.
-///
-/// SPIR-V (2.11 Structured Control Flow) defines:
-///   - loop construct
-///   - continue construct
-///   - selection construct
-/// We also define a "function construct" consisting of all the basic blocks in
-/// the function.
-///
-/// The first block in a construct (by computed block order) is called a
-/// "header". For the constructs defined by SPIR-V, the header block is the
-/// basic block containing the merge instruction.  The header for the function
-/// construct is the entry block of the function.
-///
-/// Given two constructs A and B, we say "A encloses B" if B is a subset of A,
-/// i.e. if every basic block in B is also in A.  Note that a construct encloses
-/// itself.
-///
-/// In a valid SPIR-V module, constructs will nest, meaning given
-/// constructs A and B, either A encloses B, or B encloses A, or
-/// or they are disjoint (have no basic blocks in commont).
-///
-/// A loop in a high level language translates into either:
-//
-///  - a single-block loop, where the loop header branches back to itself.
-///     In this case this single-block loop consists only of the *continue
-///     construct*.  There is no "loop construct" for this case.
-//
-///  - a multi-block loop, where the loop back-edge is different from the loop
-///     header.
-///     This case has both a non-empty loop construct containing at least the
-///     loop header, and a non-empty continue construct, containing at least the
-///     back-edge block.
-///
-/// We care about two kinds of selection constructs:
-///
-///  - if-selection: where the header block ends in OpBranchConditional
-///
-///  - switch-selection: where the header block ends in OpSwitch
-///
-struct Construct {
-  /// Enumeration for the kinds of structured constructs.
-  enum Kind {
-    /// The whole function.
-    kFunction,
-    /// A SPIR-V selection construct, header basic block ending in
-    /// OpBrancConditional.
-    kIfSelection,
-    /// A SPIR-V selection construct, header basic block ending in OpSwitch.
-    kSwitchSelection,
-    /// A SPIR-V loop construct.
-    kLoop,
-    /// A SPIR-V continue construct.
-    kContinue,
-  };
-
-  /// Constructor
-  /// @param the_parent parent construct
-  /// @param the_depth construct nesting depth
-  /// @param the_kind construct kind
-  /// @param the_begin_id block id of the first block in the construct
-  /// @param the_end_id block id of the first block after the construct, or 0
-  /// @param the_begin_pos block order position of the_begin_id
-  /// @param the_end_pos block order position of the_end_id or a too-large value
-  /// @param the_scope_end_pos block position of the first block past the end of
-  /// the WGSL scope
-  Construct(const Construct* the_parent,
-            int the_depth,
-            Kind the_kind,
-            uint32_t the_begin_id,
-            uint32_t the_end_id,
-            uint32_t the_begin_pos,
-            uint32_t the_end_pos,
-            uint32_t the_scope_end_pos);
-
-  /// @param pos a block position
-  /// @returns true if the given block position is inside this construct.
-  bool ContainsPos(uint32_t pos) const {
-    return begin_pos <= pos && pos < end_pos;
-  }
-  /// Returns true if the given block position is inside the WGSL scope
-  /// corresponding to this construct. A loop construct's WGSL scope encloses
-  /// the associated continue construct. Otherwise the WGSL scope extent is the
-  /// same as the block extent.
-  /// @param pos a block position
-  /// @returns true if the given block position is inside the WGSL scope.
-  bool ScopeContainsPos(uint32_t pos) const {
-    return begin_pos <= pos && pos < scope_end_pos;
-  }
-
-  /// The nearest enclosing construct other than itself, or nullptr if
-  /// this construct represents the entire function.
-  const Construct* const parent = nullptr;
-  /// The nearest enclosing loop construct, if one exists.  Points to `this`
-  /// when this is a loop construct.
-  const Construct* const enclosing_loop = nullptr;
-  /// The nearest enclosing continue construct, if one exists.  Points to
-  /// `this` when this is a contnue construct.
-  const Construct* const enclosing_continue = nullptr;
-  /// The nearest enclosing loop construct or continue construct or
-  /// switch-selection construct, if one exists. The signficance is
-  /// that a high level language "break" will branch to the merge block
-  /// of such an enclosing construct. Points to `this` when this is
-  /// a loop construct, a continue construct, or a switch-selection construct.
-  const Construct* const enclosing_loop_or_continue_or_switch = nullptr;
-
-  /// Control flow nesting depth. The entry block is at nesting depth 0.
-  const int depth = 0;
-  /// The construct kind
-  const Kind kind = kFunction;
-  /// The id of the first block in this structure.
-  const uint32_t begin_id = 0;
-  /// 0 for kFunction, or the id of the block immediately after this construct
-  /// in the computed block order.
-  const uint32_t end_id = 0;
-  /// The position of block #begin_id in the computed block order.
-  const uint32_t begin_pos = 0;
-  /// The position of block #end_id in the block order, or the number of
-  /// block order elements if #end_id is 0.
-  const uint32_t end_pos = 0;
-  /// The position of the first block after the WGSL scope corresponding to
-  /// this construct.
-  const uint32_t scope_end_pos = 0;
-};
-
-using ConstructList = std::vector<std::unique_ptr<Construct>>;
-
-/// Converts a construct kind to a string.
-/// @param kind the construct kind to convert
-/// @returns the string representation
-inline std::string ToString(Construct::Kind kind) {
-  switch (kind) {
-    case Construct::kFunction:
-      return "Function";
-    case Construct::kIfSelection:
-      return "IfSelection";
-    case Construct::kSwitchSelection:
-      return "SwitchSelection";
-    case Construct::kLoop:
-      return "Loop";
-    case Construct::kContinue:
-      return "Continue";
-  }
-  return "NONE";
-}
-
-/// Converts a construct into a short summary string.
-/// @param c the construct, which can be null
-/// @returns a short summary string
-inline std::string ToStringBrief(const Construct* c) {
-  if (c) {
-    std::stringstream ss;
-    ss << ToString(c->kind) << "@" << c->begin_id;
-    return ss.str();
-  }
-  return "null";
-}
-
-/// Emits a construct to a stream.
-/// @param o the stream
-/// @param c the structured construct
-/// @returns the stream
-inline std::ostream& operator<<(std::ostream& o, const Construct& c) {
-  o << "Construct{ " << ToString(c.kind) << " [" << c.begin_pos << ","
-    << c.end_pos << ")"
-    << " begin_id:" << c.begin_id << " end_id:" << c.end_id
-    << " depth:" << c.depth;
-
-  o << " parent:" << ToStringBrief(c.parent);
-
-  if (c.scope_end_pos != c.end_pos) {
-    o << " scope:[" << c.begin_pos << "," << c.scope_end_pos << ")";
-  }
-
-  if (c.enclosing_loop) {
-    o << " in-l:" << ToStringBrief(c.enclosing_loop);
-  }
-
-  if (c.enclosing_continue) {
-    o << " in-c:" << ToStringBrief(c.enclosing_continue);
-  }
-
-  if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) &&
-      (c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) {
-    o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch);
-  }
-
-  o << " }";
-  return o;
-}
-
-/// Emits a construct to a stream.
-/// @param o the stream
-/// @param c the structured construct
-/// @returns the stream
-inline std::ostream& operator<<(std::ostream& o,
-                                const std::unique_ptr<Construct>& c) {
-  return o << *(c.get());
-}
-
-/// Converts a construct to a string.
-/// @param c the construct
-/// @returns the string representation
-inline std::string ToString(const Construct& c) {
-  std::stringstream ss;
-  ss << c;
-  return ss.str();
-}
-
-/// Converts a construct to a string.
-/// @param c the construct
-/// @returns the string representation
-inline std::string ToString(const Construct* c) {
-  return c ? ToString(*c) : ToStringBrief(c);
-}
-
-/// Converts a unique pointer to a construct to a string.
-/// @param c the construct
-/// @returns the string representation
-inline std::string ToString(const std::unique_ptr<Construct>& c) {
-  return ToString(*(c.get()));
-}
-
-/// Emits a construct list to a stream.
-/// @param o the stream
-/// @param cl the construct list
-/// @returns the stream
-inline std::ostream& operator<<(std::ostream& o, const ConstructList& cl) {
-  o << "ConstructList{\n";
-  for (const auto& c : cl) {
-    o << "  " << c << "\n";
-  }
-  o << "}";
-  return o;
-}
-
-/// Converts a construct list to a string.
-/// @param cl the construct list
-/// @returns the string representation
-inline std::string ToString(const ConstructList& cl) {
-  std::stringstream ss;
-  ss << cl;
-  return ss.str();
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_CONSTRUCT_H_
diff --git a/src/reader/spirv/entry_point_info.cc b/src/reader/spirv/entry_point_info.cc
deleted file mode 100644
index 63e55a2..0000000
--- a/src/reader/spirv/entry_point_info.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/spirv/entry_point_info.h"
-
-#include <utility>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-EntryPointInfo::EntryPointInfo(std::string the_name,
-                               ast::PipelineStage the_stage,
-                               bool the_owns_inner_implementation,
-                               std::string the_inner_name,
-                               std::vector<uint32_t>&& the_inputs,
-                               std::vector<uint32_t>&& the_outputs,
-                               GridSize the_wg_size)
-    : name(the_name),
-      stage(the_stage),
-      owns_inner_implementation(the_owns_inner_implementation),
-      inner_name(std::move(the_inner_name)),
-      inputs(std::move(the_inputs)),
-      outputs(std::move(the_outputs)),
-      workgroup_size(the_wg_size) {}
-
-EntryPointInfo::EntryPointInfo(const EntryPointInfo&) = default;
-
-EntryPointInfo::~EntryPointInfo() = default;
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/entry_point_info.h b/src/reader/spirv/entry_point_info.h
deleted file mode 100644
index 09a4e41..0000000
--- a/src/reader/spirv/entry_point_info.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_ENTRY_POINT_INFO_H_
-#define SRC_READER_SPIRV_ENTRY_POINT_INFO_H_
-
-#include <string>
-#include <vector>
-
-#include "src/ast/pipeline_stage.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// The size of an integer-coordinate grid, in the x, y, and z dimensions.
-struct GridSize {
-  /// x value
-  uint32_t x = 0;
-  /// y value
-  uint32_t y = 0;
-  /// z value
-  uint32_t z = 0;
-};
-
-/// Entry point information for a function
-struct EntryPointInfo {
-  /// Constructor.
-  /// @param the_name the name of the entry point
-  /// @param the_stage the pipeline stage
-  /// @param the_owns_inner_implementation if true, this entry point is
-  /// responsible for generating the inner implementation function.
-  /// @param the_inner_name the name of the inner implementation function of the
-  /// entry point
-  /// @param the_inputs list of IDs for Input variables used by the shader
-  /// @param the_outputs list of IDs for Output variables used by the shader
-  /// @param the_wg_size the workgroup_size, for a compute shader
-  EntryPointInfo(std::string the_name,
-                 ast::PipelineStage the_stage,
-                 bool the_owns_inner_implementation,
-                 std::string the_inner_name,
-                 std::vector<uint32_t>&& the_inputs,
-                 std::vector<uint32_t>&& the_outputs,
-                 GridSize the_wg_size);
-  /// Copy constructor
-  /// @param other the other entry point info to be built from
-  EntryPointInfo(const EntryPointInfo& other);
-  /// Destructor
-  ~EntryPointInfo();
-
-  /// The entry point name.
-  /// In the WGSL output, this function will have pipeline inputs and outputs
-  /// as parameters. This function will store them into Private variables,
-  /// and then call the "inner" function, named by the next memeber.
-  /// Then outputs are copied from the private variables to the return value.
-  std::string name;
-  /// The entry point stage
-  ast::PipelineStage stage = ast::PipelineStage::kNone;
-
-  /// True when this entry point is responsible for generating the
-  /// inner implementation function.  False when this is the second entry
-  /// point encountered for the same function in SPIR-V. It's unusual, but
-  /// possible for the same function to be the implementation for multiple
-  /// entry points.
-  bool owns_inner_implementation;
-  /// The name of the inner implementation function of the entry point.
-  std::string inner_name;
-  /// IDs of pipeline input variables, sorted and without duplicates.
-  std::vector<uint32_t> inputs;
-  /// IDs of pipeline output variables, sorted and without duplicates.
-  std::vector<uint32_t> outputs;
-
-  /// If this is a compute shader, this is the workgroup size in the x, y,
-  /// and z dimensions set via LocalSize, or via the composite value
-  /// decorated as the WorkgroupSize BuiltIn.  The WorkgroupSize builtin
-  /// takes priority.
-  GridSize workgroup_size;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_ENTRY_POINT_INFO_H_
diff --git a/src/reader/spirv/enum_converter.cc b/src/reader/spirv/enum_converter.cc
deleted file mode 100644
index 7f9961e..0000000
--- a/src/reader/spirv/enum_converter.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2020 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/reader/spirv/enum_converter.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-EnumConverter::EnumConverter(const FailStream& fs) : fail_stream_(fs) {}
-
-EnumConverter::~EnumConverter() = default;
-
-ast::PipelineStage EnumConverter::ToPipelineStage(SpvExecutionModel model) {
-  switch (model) {
-    case SpvExecutionModelVertex:
-      return ast::PipelineStage::kVertex;
-    case SpvExecutionModelFragment:
-      return ast::PipelineStage::kFragment;
-    case SpvExecutionModelGLCompute:
-      return ast::PipelineStage::kCompute;
-    default:
-      break;
-  }
-
-  Fail() << "unknown SPIR-V execution model: " << uint32_t(model);
-  return ast::PipelineStage::kNone;
-}
-
-ast::StorageClass EnumConverter::ToStorageClass(const SpvStorageClass sc) {
-  switch (sc) {
-    case SpvStorageClassInput:
-      return ast::StorageClass::kInput;
-    case SpvStorageClassOutput:
-      return ast::StorageClass::kOutput;
-    case SpvStorageClassUniform:
-      return ast::StorageClass::kUniform;
-    case SpvStorageClassWorkgroup:
-      return ast::StorageClass::kWorkgroup;
-    case SpvStorageClassUniformConstant:
-      return ast::StorageClass::kNone;
-    case SpvStorageClassStorageBuffer:
-      return ast::StorageClass::kStorage;
-    case SpvStorageClassPrivate:
-      return ast::StorageClass::kPrivate;
-    case SpvStorageClassFunction:
-      return ast::StorageClass::kFunction;
-    default:
-      break;
-  }
-
-  Fail() << "unknown SPIR-V storage class: " << uint32_t(sc);
-  return ast::StorageClass::kInvalid;
-}
-
-ast::Builtin EnumConverter::ToBuiltin(SpvBuiltIn b) {
-  switch (b) {
-    case SpvBuiltInPosition:
-      return ast::Builtin::kPosition;
-    case SpvBuiltInVertexIndex:
-      return ast::Builtin::kVertexIndex;
-    case SpvBuiltInInstanceIndex:
-      return ast::Builtin::kInstanceIndex;
-    case SpvBuiltInFrontFacing:
-      return ast::Builtin::kFrontFacing;
-    case SpvBuiltInFragCoord:
-      return ast::Builtin::kPosition;
-    case SpvBuiltInFragDepth:
-      return ast::Builtin::kFragDepth;
-    case SpvBuiltInLocalInvocationId:
-      return ast::Builtin::kLocalInvocationId;
-    case SpvBuiltInLocalInvocationIndex:
-      return ast::Builtin::kLocalInvocationIndex;
-    case SpvBuiltInGlobalInvocationId:
-      return ast::Builtin::kGlobalInvocationId;
-    case SpvBuiltInWorkgroupId:
-      return ast::Builtin::kWorkgroupId;
-    case SpvBuiltInSampleId:
-      return ast::Builtin::kSampleIndex;
-    case SpvBuiltInSampleMask:
-      return ast::Builtin::kSampleMask;
-    default:
-      break;
-  }
-
-  Fail() << "unknown SPIR-V builtin: " << uint32_t(b);
-  return ast::Builtin::kNone;
-}
-
-ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) {
-  if (arrayed) {
-    switch (dim) {
-      case SpvDim2D:
-        return ast::TextureDimension::k2dArray;
-      case SpvDimCube:
-        return ast::TextureDimension::kCubeArray;
-      default:
-        break;
-    }
-    Fail() << "arrayed dimension must be 2D or Cube. Got " << int(dim);
-    return ast::TextureDimension::kNone;
-  }
-  // Assume non-arrayed
-  switch (dim) {
-    case SpvDim1D:
-      return ast::TextureDimension::k1d;
-    case SpvDim2D:
-      return ast::TextureDimension::k2d;
-    case SpvDim3D:
-      return ast::TextureDimension::k3d;
-    case SpvDimCube:
-      return ast::TextureDimension::kCube;
-    default:
-      break;
-  }
-  Fail() << "invalid dimension: " << int(dim);
-  return ast::TextureDimension::kNone;
-}
-
-ast::TexelFormat EnumConverter::ToTexelFormat(SpvImageFormat fmt) {
-  switch (fmt) {
-    case SpvImageFormatUnknown:
-      return ast::TexelFormat::kNone;
-
-    // 8 bit channels
-    case SpvImageFormatRgba8:
-      return ast::TexelFormat::kRgba8Unorm;
-    case SpvImageFormatRgba8Snorm:
-      return ast::TexelFormat::kRgba8Snorm;
-    case SpvImageFormatRgba8ui:
-      return ast::TexelFormat::kRgba8Uint;
-    case SpvImageFormatRgba8i:
-      return ast::TexelFormat::kRgba8Sint;
-
-    // 16 bit channels
-    case SpvImageFormatRgba16ui:
-      return ast::TexelFormat::kRgba16Uint;
-    case SpvImageFormatRgba16i:
-      return ast::TexelFormat::kRgba16Sint;
-    case SpvImageFormatRgba16f:
-      return ast::TexelFormat::kRgba16Float;
-
-    // 32 bit channels
-    case SpvImageFormatR32ui:
-      return ast::TexelFormat::kR32Uint;
-    case SpvImageFormatR32i:
-      return ast::TexelFormat::kR32Sint;
-    case SpvImageFormatR32f:
-      return ast::TexelFormat::kR32Float;
-    case SpvImageFormatRg32ui:
-      return ast::TexelFormat::kRg32Uint;
-    case SpvImageFormatRg32i:
-      return ast::TexelFormat::kRg32Sint;
-    case SpvImageFormatRg32f:
-      return ast::TexelFormat::kRg32Float;
-    case SpvImageFormatRgba32ui:
-      return ast::TexelFormat::kRgba32Uint;
-    case SpvImageFormatRgba32i:
-      return ast::TexelFormat::kRgba32Sint;
-    case SpvImageFormatRgba32f:
-      return ast::TexelFormat::kRgba32Float;
-    default:
-      break;
-  }
-  Fail() << "invalid image format: " << int(fmt);
-  return ast::TexelFormat::kNone;
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/enum_converter.h b/src/reader/spirv/enum_converter.h
deleted file mode 100644
index 66f0c7b..0000000
--- a/src/reader/spirv/enum_converter.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_ENUM_CONVERTER_H_
-#define SRC_READER_SPIRV_ENUM_CONVERTER_H_
-
-#include "spirv/unified1/spirv.h"
-#include "src/ast/builtin.h"
-#include "src/ast/pipeline_stage.h"
-#include "src/ast/storage_class.h"
-#include "src/reader/spirv/fail_stream.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// A converter from SPIR-V enums to Tint AST enums.
-class EnumConverter {
- public:
-  /// Creates a new enum converter.
-  /// @param fail_stream the error reporting stream.
-  explicit EnumConverter(const FailStream& fail_stream);
-  /// Destructor
-  ~EnumConverter();
-
-  /// Converts a SPIR-V execution model to a Tint pipeline stage.
-  /// On failure, logs an error and returns kNone
-  /// @param model the SPIR-V entry point execution model
-  /// @returns a Tint AST pipeline stage
-  ast::PipelineStage ToPipelineStage(SpvExecutionModel model);
-
-  /// Converts a SPIR-V storage class to a Tint storage class.
-  /// On failure, logs an error and returns kNone
-  /// @param sc the SPIR-V storage class
-  /// @returns a Tint AST storage class
-  ast::StorageClass ToStorageClass(const SpvStorageClass sc);
-
-  /// Converts a SPIR-V Builtin value a Tint Builtin.
-  /// On failure, logs an error and returns kNone
-  /// @param b the SPIR-V builtin
-  /// @returns a Tint AST builtin
-  ast::Builtin ToBuiltin(SpvBuiltIn b);
-
-  /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension.
-  /// On failure, logs an error and returns kNone
-  /// @param dim the SPIR-V Dim value
-  /// @param arrayed true if the texture is arrayed
-  /// @returns a Tint AST texture dimension
-  ast::TextureDimension ToDim(SpvDim dim, bool arrayed);
-
-  /// Converts a SPIR-V Image Format to a TexelFormat
-  /// On failure, logs an error and returns kNone
-  /// @param fmt the SPIR-V format
-  /// @returns a Tint AST format
-  ast::TexelFormat ToTexelFormat(SpvImageFormat fmt);
-
- private:
-  /// Registers a failure and returns a stream for log diagnostics.
-  /// @returns a failure stream
-  FailStream Fail() { return fail_stream_.Fail(); }
-
-  FailStream fail_stream_;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_ENUM_CONVERTER_H_
diff --git a/src/reader/spirv/enum_converter_test.cc b/src/reader/spirv/enum_converter_test.cc
deleted file mode 100644
index d80f249..0000000
--- a/src/reader/spirv/enum_converter_test.cc
+++ /dev/null
@@ -1,429 +0,0 @@
-// Copyright 2020 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/reader/spirv/enum_converter.h"
-
-#include <string>
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-// Pipeline stage
-
-struct PipelineStageCase {
-  SpvExecutionModel model;
-  bool expect_success;
-  ast::PipelineStage expected;
-};
-inline std::ostream& operator<<(std::ostream& out, PipelineStageCase psc) {
-  out << "PipelineStageCase{ SpvExecutionModel:" << int(psc.model)
-      << " expect_success?:" << int(psc.expect_success)
-      << " expected:" << int(psc.expected) << "}";
-  return out;
-}
-
-class SpvPipelineStageTest : public testing::TestWithParam<PipelineStageCase> {
- public:
-  SpvPipelineStageTest()
-      : success_(true),
-        fail_stream_(&success_, &errors_),
-        converter_(fail_stream_) {}
-
-  std::string error() const { return errors_.str(); }
-
- protected:
-  bool success_ = true;
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  EnumConverter converter_;
-};
-
-TEST_P(SpvPipelineStageTest, Samples) {
-  const auto params = GetParam();
-
-  const auto result = converter_.ToPipelineStage(params.model);
-  EXPECT_EQ(success_, params.expect_success);
-  if (params.expect_success) {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_TRUE(error().empty());
-  } else {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_THAT(error(),
-                ::testing::StartsWith("unknown SPIR-V execution model:"));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood,
-    SpvPipelineStageTest,
-    testing::Values(PipelineStageCase{SpvExecutionModelVertex, true,
-                                      ast::PipelineStage::kVertex},
-                    PipelineStageCase{SpvExecutionModelFragment, true,
-                                      ast::PipelineStage::kFragment},
-                    PipelineStageCase{SpvExecutionModelGLCompute, true,
-                                      ast::PipelineStage::kCompute}));
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterBad,
-    SpvPipelineStageTest,
-    testing::Values(PipelineStageCase{static_cast<SpvExecutionModel>(9999),
-                                      false, ast::PipelineStage::kNone},
-                    PipelineStageCase{SpvExecutionModelTessellationControl,
-                                      false, ast::PipelineStage::kNone}));
-
-// Storage class
-
-struct StorageClassCase {
-  SpvStorageClass sc;
-  bool expect_success;
-  ast::StorageClass expected;
-};
-inline std::ostream& operator<<(std::ostream& out, StorageClassCase scc) {
-  out << "StorageClassCase{ SpvStorageClass:" << int(scc.sc)
-      << " expect_success?:" << int(scc.expect_success)
-      << " expected:" << int(scc.expected) << "}";
-  return out;
-}
-
-class SpvStorageClassTest : public testing::TestWithParam<StorageClassCase> {
- public:
-  SpvStorageClassTest()
-      : success_(true),
-        fail_stream_(&success_, &errors_),
-        converter_(fail_stream_) {}
-
-  std::string error() const { return errors_.str(); }
-
- protected:
-  bool success_ = true;
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  EnumConverter converter_;
-};
-
-TEST_P(SpvStorageClassTest, Samples) {
-  const auto params = GetParam();
-
-  const auto result = converter_.ToStorageClass(params.sc);
-  EXPECT_EQ(success_, params.expect_success);
-  if (params.expect_success) {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_TRUE(error().empty());
-  } else {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_THAT(error(),
-                ::testing::StartsWith("unknown SPIR-V storage class: "));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood,
-    SpvStorageClassTest,
-    testing::Values(StorageClassCase{SpvStorageClassInput, true,
-                                     ast::StorageClass::kInput},
-                    StorageClassCase{SpvStorageClassOutput, true,
-                                     ast::StorageClass::kOutput},
-                    StorageClassCase{SpvStorageClassUniform, true,
-                                     ast::StorageClass::kUniform},
-                    StorageClassCase{SpvStorageClassWorkgroup, true,
-                                     ast::StorageClass::kWorkgroup},
-                    StorageClassCase{SpvStorageClassUniformConstant, true,
-                                     ast::StorageClass::kNone},
-                    StorageClassCase{SpvStorageClassStorageBuffer, true,
-                                     ast::StorageClass::kStorage},
-                    StorageClassCase{SpvStorageClassPrivate, true,
-                                     ast::StorageClass::kPrivate},
-                    StorageClassCase{SpvStorageClassFunction, true,
-                                     ast::StorageClass::kFunction}));
-
-INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
-                         SpvStorageClassTest,
-                         testing::Values(StorageClassCase{
-                             static_cast<SpvStorageClass>(9999), false,
-                             ast::StorageClass::kInvalid}));
-
-// Builtin
-
-struct BuiltinCase {
-  SpvBuiltIn builtin;
-  bool expect_success;
-  ast::Builtin expected;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinCase bc) {
-  out << "BuiltinCase{ SpvBuiltIn:" << int(bc.builtin)
-      << " expect_success?:" << int(bc.expect_success)
-      << " expected:" << int(bc.expected) << "}";
-  return out;
-}
-
-class SpvBuiltinTest : public testing::TestWithParam<BuiltinCase> {
- public:
-  SpvBuiltinTest()
-      : success_(true),
-        fail_stream_(&success_, &errors_),
-        converter_(fail_stream_) {}
-
-  std::string error() const { return errors_.str(); }
-
- protected:
-  bool success_ = true;
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  EnumConverter converter_;
-};
-
-TEST_P(SpvBuiltinTest, Samples) {
-  const auto params = GetParam();
-
-  const auto result = converter_.ToBuiltin(params.builtin);
-  EXPECT_EQ(success_, params.expect_success);
-  if (params.expect_success) {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_TRUE(error().empty());
-  } else {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_THAT(error(), ::testing::StartsWith("unknown SPIR-V builtin: "));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood_Input,
-    SpvBuiltinTest,
-    testing::Values(
-        BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
-        BuiltinCase{SpvBuiltInInstanceIndex, true,
-                    ast::Builtin::kInstanceIndex},
-        BuiltinCase{SpvBuiltInFrontFacing, true, ast::Builtin::kFrontFacing},
-        BuiltinCase{SpvBuiltInFragCoord, true, ast::Builtin::kPosition},
-        BuiltinCase{SpvBuiltInLocalInvocationId, true,
-                    ast::Builtin::kLocalInvocationId},
-        BuiltinCase{SpvBuiltInLocalInvocationIndex, true,
-                    ast::Builtin::kLocalInvocationIndex},
-        BuiltinCase{SpvBuiltInGlobalInvocationId, true,
-                    ast::Builtin::kGlobalInvocationId},
-        BuiltinCase{SpvBuiltInWorkgroupId, true, ast::Builtin::kWorkgroupId},
-        BuiltinCase{SpvBuiltInSampleId, true, ast::Builtin::kSampleIndex},
-        BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood_Output,
-    SpvBuiltinTest,
-    testing::Values(
-        BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
-        BuiltinCase{SpvBuiltInFragDepth, true, ast::Builtin::kFragDepth},
-        BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterBad,
-    SpvBuiltinTest,
-    testing::Values(
-        BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
-        BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
-        BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::Builtin::kNone}));
-
-// Dim
-
-struct DimCase {
-  SpvDim dim;
-  bool arrayed;
-  bool expect_success;
-  ast::TextureDimension expected;
-};
-inline std::ostream& operator<<(std::ostream& out, DimCase dc) {
-  out << "DimCase{ SpvDim:" << int(dc.dim) << " arrayed?:" << int(dc.arrayed)
-      << " expect_success?:" << int(dc.expect_success)
-      << " expected:" << int(dc.expected) << "}";
-  return out;
-}
-
-class SpvDimTest : public testing::TestWithParam<DimCase> {
- public:
-  SpvDimTest()
-      : success_(true),
-        fail_stream_(&success_, &errors_),
-        converter_(fail_stream_) {}
-
-  std::string error() const { return errors_.str(); }
-
- protected:
-  bool success_ = true;
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  EnumConverter converter_;
-};
-
-TEST_P(SpvDimTest, Samples) {
-  const auto params = GetParam();
-
-  const auto result = converter_.ToDim(params.dim, params.arrayed);
-  EXPECT_EQ(success_, params.expect_success);
-  if (params.expect_success) {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_TRUE(error().empty());
-  } else {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_THAT(error(), ::testing::HasSubstr("dimension"));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood,
-    SpvDimTest,
-    testing::Values(
-        // Non-arrayed
-        DimCase{SpvDim1D, false, true, ast::TextureDimension::k1d},
-        DimCase{SpvDim2D, false, true, ast::TextureDimension::k2d},
-        DimCase{SpvDim3D, false, true, ast::TextureDimension::k3d},
-        DimCase{SpvDimCube, false, true, ast::TextureDimension::kCube},
-        // Arrayed
-        DimCase{SpvDim2D, true, true, ast::TextureDimension::k2dArray},
-        DimCase{SpvDimCube, true, true, ast::TextureDimension::kCubeArray}));
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterBad,
-    SpvDimTest,
-    testing::Values(
-        // Invalid SPIR-V dimensionality.
-        DimCase{SpvDimMax, false, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimMax, true, false, ast::TextureDimension::kNone},
-        // Vulkan non-arrayed dimensionalities not supported by WGSL.
-        DimCase{SpvDimRect, false, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimBuffer, false, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimSubpassData, false, false, ast::TextureDimension::kNone},
-        // Arrayed dimensionalities not supported by WGSL
-        DimCase{SpvDim3D, true, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimRect, true, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimBuffer, true, false, ast::TextureDimension::kNone},
-        DimCase{SpvDimSubpassData, true, false, ast::TextureDimension::kNone}));
-
-// TexelFormat
-
-struct TexelFormatCase {
-  SpvImageFormat format;
-  bool expect_success;
-  ast::TexelFormat expected;
-};
-inline std::ostream& operator<<(std::ostream& out, TexelFormatCase ifc) {
-  out << "TexelFormatCase{ SpvImageFormat:" << int(ifc.format)
-      << " expect_success?:" << int(ifc.expect_success)
-      << " expected:" << int(ifc.expected) << "}";
-  return out;
-}
-
-class SpvImageFormatTest : public testing::TestWithParam<TexelFormatCase> {
- public:
-  SpvImageFormatTest()
-      : success_(true),
-        fail_stream_(&success_, &errors_),
-        converter_(fail_stream_) {}
-
-  std::string error() const { return errors_.str(); }
-
- protected:
-  bool success_ = true;
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  EnumConverter converter_;
-};
-
-TEST_P(SpvImageFormatTest, Samples) {
-  const auto params = GetParam();
-
-  const auto result = converter_.ToTexelFormat(params.format);
-  EXPECT_EQ(success_, params.expect_success) << params;
-  if (params.expect_success) {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_TRUE(error().empty());
-  } else {
-    EXPECT_EQ(result, params.expected);
-    EXPECT_THAT(error(), ::testing::StartsWith("invalid image format: "));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterGood,
-    SpvImageFormatTest,
-    testing::Values(
-        // Unknown.  This is used for sampled images.
-        TexelFormatCase{SpvImageFormatUnknown, true, ast::TexelFormat::kNone},
-        // 8 bit channels
-        TexelFormatCase{SpvImageFormatRgba8, true,
-                        ast::TexelFormat::kRgba8Unorm},
-        TexelFormatCase{SpvImageFormatRgba8Snorm, true,
-                        ast::TexelFormat::kRgba8Snorm},
-        TexelFormatCase{SpvImageFormatRgba8ui, true,
-                        ast::TexelFormat::kRgba8Uint},
-        TexelFormatCase{SpvImageFormatRgba8i, true,
-                        ast::TexelFormat::kRgba8Sint},
-        // 16 bit channels
-        TexelFormatCase{SpvImageFormatRgba16ui, true,
-                        ast::TexelFormat::kRgba16Uint},
-        TexelFormatCase{SpvImageFormatRgba16i, true,
-                        ast::TexelFormat::kRgba16Sint},
-        TexelFormatCase{SpvImageFormatRgba16f, true,
-                        ast::TexelFormat::kRgba16Float},
-        // 32 bit channels
-        // ... 1 channel
-        TexelFormatCase{SpvImageFormatR32ui, true, ast::TexelFormat::kR32Uint},
-        TexelFormatCase{SpvImageFormatR32i, true, ast::TexelFormat::kR32Sint},
-        TexelFormatCase{SpvImageFormatR32f, true, ast::TexelFormat::kR32Float},
-        // ... 2 channels
-        TexelFormatCase{SpvImageFormatRg32ui, true,
-                        ast::TexelFormat::kRg32Uint},
-        TexelFormatCase{SpvImageFormatRg32i, true, ast::TexelFormat::kRg32Sint},
-        TexelFormatCase{SpvImageFormatRg32f, true,
-                        ast::TexelFormat::kRg32Float},
-        // ... 4 channels
-        TexelFormatCase{SpvImageFormatRgba32ui, true,
-                        ast::TexelFormat::kRgba32Uint},
-        TexelFormatCase{SpvImageFormatRgba32i, true,
-                        ast::TexelFormat::kRgba32Sint},
-        TexelFormatCase{SpvImageFormatRgba32f, true,
-                        ast::TexelFormat::kRgba32Float}));
-
-INSTANTIATE_TEST_SUITE_P(
-    EnumConverterBad,
-    SpvImageFormatTest,
-    testing::Values(
-        // Scanning in order from the SPIR-V spec.
-        TexelFormatCase{SpvImageFormatRg16f, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatR11fG11fB10f, false,
-                        ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatR16f, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRgb10A2, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg16, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg8, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatR16, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatR8, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRgba16Snorm, false,
-                        ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg16Snorm, false,
-                        ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg8Snorm, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg16i, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg8i, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatR8i, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRgb10a2ui, false,
-                        ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg16ui, false, ast::TexelFormat::kNone},
-        TexelFormatCase{SpvImageFormatRg8ui, false, ast::TexelFormat::kNone}));
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/fail_stream.h b/src/reader/spirv/fail_stream.h
deleted file mode 100644
index eaa6f21..0000000
--- a/src/reader/spirv/fail_stream.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_FAIL_STREAM_H_
-#define SRC_READER_SPIRV_FAIL_STREAM_H_
-
-#include <ostream>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// A FailStream object accumulates values onto a given std::ostream,
-/// and can be used to record failure by writing the false value
-/// to given a pointer-to-bool.
-class FailStream {
- public:
-  /// Creates a new fail stream
-  /// @param status_ptr where we will write false to indicate failure. Assumed
-  /// to be a valid pointer to bool.
-  /// @param out output stream where a message should be written to explain
-  /// the failure
-  FailStream(bool* status_ptr, std::ostream* out)
-      : status_ptr_(status_ptr), out_(out) {}
-  /// Copy constructor
-  /// @param other the fail stream to clone
-  FailStream(const FailStream& other) = default;
-
-  /// Converts to a boolean status. A true result indicates success,
-  /// and a false result indicates failure.
-  /// @returns the status
-  operator bool() const { return *status_ptr_; }
-  /// Returns the current status value.  This can be more readable
-  /// the conversion operator.
-  /// @returns the status
-  bool status() const { return *status_ptr_; }
-
-  /// Records failure.
-  /// @returns a FailStream
-  FailStream& Fail() {
-    *status_ptr_ = false;
-    return *this;
-  }
-
-  /// Appends the given value to the message output stream.
-  /// @param val the value to write to the output stream.
-  /// @returns this object
-  template <typename T>
-  FailStream& operator<<(const T& val) {
-    *out_ << val;
-    return *this;
-  }
-
- private:
-  bool* status_ptr_;
-  std::ostream* out_;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_FAIL_STREAM_H_
diff --git a/src/reader/spirv/fail_stream_test.cc b/src/reader/spirv/fail_stream_test.cc
deleted file mode 100644
index b533bdb..0000000
--- a/src/reader/spirv/fail_stream_test.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2020 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/reader/spirv/fail_stream.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-using FailStreamTest = ::testing::Test;
-
-TEST_F(FailStreamTest, ConversionToBoolIsSameAsStatusMethod) {
-  bool flag = true;
-  FailStream fs(&flag, nullptr);
-
-  EXPECT_TRUE(fs.status());
-  EXPECT_TRUE(bool(fs));  // NOLINT
-  flag = false;
-  EXPECT_FALSE(fs.status());
-  EXPECT_FALSE(bool(fs));  // NOLINT
-  flag = true;
-  EXPECT_TRUE(fs.status());
-  EXPECT_TRUE(bool(fs));  // NOLINT
-}
-
-TEST_F(FailStreamTest, FailMethodChangesStatusToFalse) {
-  bool flag = true;
-  FailStream fs(&flag, nullptr);
-  EXPECT_TRUE(flag);
-  EXPECT_TRUE(bool(fs));  // NOLINT
-  fs.Fail();
-  EXPECT_FALSE(flag);
-  EXPECT_FALSE(bool(fs));  // NOLINT
-}
-
-TEST_F(FailStreamTest, FailMethodReturnsSelf) {
-  bool flag = true;
-  FailStream fs(&flag, nullptr);
-  FailStream& result = fs.Fail();
-  EXPECT_THAT(&result, Eq(&fs));
-}
-
-TEST_F(FailStreamTest, ShiftOperatorAccumulatesValues) {
-  bool flag = true;
-  std::stringstream ss;
-  FailStream fs(&flag, &ss);
-
-  ss << "prefix ";
-  fs << "cat " << 42;
-
-  EXPECT_THAT(ss.str(), Eq("prefix cat 42"));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
deleted file mode 100644
index 33d7da2..0000000
--- a/src/reader/spirv/function.cc
+++ /dev/null
@@ -1,6148 +0,0 @@
-// Copyright 2020 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/reader/spirv/function.h"
-
-#include <algorithm>
-#include <array>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/builtin.h"
-#include "src/ast/builtin_attribute.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/sem/builtin_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-
-// Terms:
-//    CFG: the control flow graph of the function, where basic blocks are the
-//    nodes, and branches form the directed arcs.  The function entry block is
-//    the root of the CFG.
-//
-//    Suppose H is a header block (i.e. has an OpSelectionMerge or OpLoopMerge).
-//    Then:
-//    - Let M(H) be the merge block named by the merge instruction in H.
-//    - If H is a loop header, i.e. has an OpLoopMerge instruction, then let
-//      CT(H) be the continue target block named by the OpLoopMerge
-//      instruction.
-//    - If H is a selection construct whose header ends in
-//      OpBranchConditional with true target %then and false target %else,
-//      then  TT(H) = %then and FT(H) = %else
-//
-// Determining output block order:
-//    The "structured post-order traversal" of the CFG is a post-order traversal
-//    of the basic blocks in the CFG, where:
-//      We visit the entry node of the function first.
-//      When visiting a header block:
-//        We next visit its merge block
-//        Then if it's a loop header, we next visit the continue target,
-//      Then we visit the block's successors (whether it's a header or not)
-//        If the block ends in an OpBranchConditional, we visit the false target
-//        before the true target.
-//
-//    The "reverse structured post-order traversal" of the CFG is the reverse
-//    of the structured post-order traversal.
-//    This is the order of basic blocks as they should be emitted to the WGSL
-//    function. It is the order computed by ComputeBlockOrder, and stored in
-//    the |FunctionEmiter::block_order_|.
-//    Blocks not in this ordering are ignored by the rest of the algorithm.
-//
-//    Note:
-//     - A block D in the function might not appear in this order because
-//       no block in the order branches to D.
-//     - An unreachable block D might still be in the order because some header
-//       block in the order names D as its continue target, or merge block,
-//       or D is reachable from one of those otherwise-unreachable continue
-//       targets or merge blocks.
-//
-// Terms:
-//    Let Pos(B) be the index position of a block B in the computed block order.
-//
-// CFG intervals and valid nesting:
-//
-//    A correctly structured CFG satisfies nesting rules that we can check by
-//    comparing positions of related blocks.
-//
-//    If header block H is in the block order, then the following holds:
-//
-//      Pos(H) < Pos(M(H))
-//
-//      If CT(H) exists, then:
-//
-//         Pos(H) <= Pos(CT(H))
-//         Pos(CT(H)) < Pos(M)
-//
-//    This gives us the fundamental ordering of blocks in relation to a
-//    structured construct:
-//      The blocks before H in the block order, are not in the construct
-//      The blocks at M(H) or later in the block order, are not in the construct
-//      The blocks in a selection headed at H are in positions [ Pos(H),
-//      Pos(M(H)) ) The blocks in a loop construct headed at H are in positions
-//      [ Pos(H), Pos(CT(H)) ) The blocks in the continue construct for loop
-//      headed at H are in
-//        positions [ Pos(CT(H)), Pos(M(H)) )
-//
-//      Schematically, for a selection construct headed by H, the blocks are in
-//      order from left to right:
-//
-//                 ...a-b-c H d-e-f M(H) n-o-p...
-//
-//           where ...a-b-c: blocks before the selection construct
-//           where H and d-e-f: blocks in the selection construct
-//           where M(H) and n-o-p...: blocks after the selection construct
-//
-//      Schematically, for a loop construct headed by H that is its own
-//      continue construct, the blocks in order from left to right:
-//
-//                 ...a-b-c H=CT(H) d-e-f M(H) n-o-p...
-//
-//           where ...a-b-c: blocks before the loop
-//           where H is the continue construct; CT(H)=H, and the loop construct
-//           is *empty*
-//           where d-e-f... are other blocks in the continue construct
-//           where M(H) and n-o-p...: blocks after the continue construct
-//
-//      Schematically, for a multi-block loop construct headed by H, there are
-//      blocks in order from left to right:
-//
-//                 ...a-b-c H d-e-f CT(H) j-k-l M(H) n-o-p...
-//
-//           where ...a-b-c: blocks before the loop
-//           where H and d-e-f: blocks in the loop construct
-//           where CT(H) and j-k-l: blocks in the continue construct
-//           where M(H) and n-o-p...: blocks after the loop and continue
-//           constructs
-//
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-namespace {
-
-constexpr uint32_t kMaxVectorLen = 4;
-
-// Gets the AST unary opcode for the given SPIR-V opcode, if any
-// @param opcode SPIR-V opcode
-// @param ast_unary_op return parameter
-// @returns true if it was a unary operation
-bool GetUnaryOp(SpvOp opcode, ast::UnaryOp* ast_unary_op) {
-  switch (opcode) {
-    case SpvOpSNegate:
-    case SpvOpFNegate:
-      *ast_unary_op = ast::UnaryOp::kNegation;
-      return true;
-    case SpvOpLogicalNot:
-      *ast_unary_op = ast::UnaryOp::kNot;
-      return true;
-    case SpvOpNot:
-      *ast_unary_op = ast::UnaryOp::kComplement;
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-/// Converts a SPIR-V opcode for a WGSL builtin function, if there is a
-/// direct translation. Returns nullptr otherwise.
-/// @returns the WGSL builtin function name for the given opcode, or nullptr.
-const char* GetUnaryBuiltInFunctionName(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpAny:
-      return "any";
-    case SpvOpAll:
-      return "all";
-    case SpvOpIsNan:
-      return "isNan";
-    case SpvOpIsInf:
-      return "isInf";
-    case SpvOpTranspose:
-      return "transpose";
-    default:
-      break;
-  }
-  return nullptr;
-}
-
-// Converts a SPIR-V opcode to its corresponding AST binary opcode, if any
-// @param opcode SPIR-V opcode
-// @returns the AST binary op for the given opcode, or kNone
-ast::BinaryOp ConvertBinaryOp(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpIAdd:
-    case SpvOpFAdd:
-      return ast::BinaryOp::kAdd;
-    case SpvOpISub:
-    case SpvOpFSub:
-      return ast::BinaryOp::kSubtract;
-    case SpvOpIMul:
-    case SpvOpFMul:
-    case SpvOpVectorTimesScalar:
-    case SpvOpMatrixTimesScalar:
-    case SpvOpVectorTimesMatrix:
-    case SpvOpMatrixTimesVector:
-    case SpvOpMatrixTimesMatrix:
-      return ast::BinaryOp::kMultiply;
-    case SpvOpUDiv:
-    case SpvOpSDiv:
-    case SpvOpFDiv:
-      return ast::BinaryOp::kDivide;
-    case SpvOpUMod:
-    case SpvOpSMod:
-    case SpvOpFRem:
-      return ast::BinaryOp::kModulo;
-    case SpvOpLogicalEqual:
-    case SpvOpIEqual:
-    case SpvOpFOrdEqual:
-      return ast::BinaryOp::kEqual;
-    case SpvOpLogicalNotEqual:
-    case SpvOpINotEqual:
-    case SpvOpFOrdNotEqual:
-      return ast::BinaryOp::kNotEqual;
-    case SpvOpBitwiseAnd:
-      return ast::BinaryOp::kAnd;
-    case SpvOpBitwiseOr:
-      return ast::BinaryOp::kOr;
-    case SpvOpBitwiseXor:
-      return ast::BinaryOp::kXor;
-    case SpvOpLogicalAnd:
-      return ast::BinaryOp::kAnd;
-    case SpvOpLogicalOr:
-      return ast::BinaryOp::kOr;
-    case SpvOpUGreaterThan:
-    case SpvOpSGreaterThan:
-    case SpvOpFOrdGreaterThan:
-      return ast::BinaryOp::kGreaterThan;
-    case SpvOpUGreaterThanEqual:
-    case SpvOpSGreaterThanEqual:
-    case SpvOpFOrdGreaterThanEqual:
-      return ast::BinaryOp::kGreaterThanEqual;
-    case SpvOpULessThan:
-    case SpvOpSLessThan:
-    case SpvOpFOrdLessThan:
-      return ast::BinaryOp::kLessThan;
-    case SpvOpULessThanEqual:
-    case SpvOpSLessThanEqual:
-    case SpvOpFOrdLessThanEqual:
-      return ast::BinaryOp::kLessThanEqual;
-    default:
-      break;
-  }
-  // It's not clear what OpSMod should map to.
-  // https://bugs.chromium.org/p/tint/issues/detail?id=52
-  return ast::BinaryOp::kNone;
-}
-
-// If the given SPIR-V opcode is a floating point unordered comparison,
-// then returns the binary float comparison for which it is the negation.
-// Othewrise returns BinaryOp::kNone.
-// @param opcode SPIR-V opcode
-// @returns operation corresponding to negated version of the SPIR-V opcode
-ast::BinaryOp NegatedFloatCompare(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpFUnordEqual:
-      return ast::BinaryOp::kNotEqual;
-    case SpvOpFUnordNotEqual:
-      return ast::BinaryOp::kEqual;
-    case SpvOpFUnordLessThan:
-      return ast::BinaryOp::kGreaterThanEqual;
-    case SpvOpFUnordLessThanEqual:
-      return ast::BinaryOp::kGreaterThan;
-    case SpvOpFUnordGreaterThan:
-      return ast::BinaryOp::kLessThanEqual;
-    case SpvOpFUnordGreaterThanEqual:
-      return ast::BinaryOp::kLessThan;
-    default:
-      break;
-  }
-  return ast::BinaryOp::kNone;
-}
-
-// Returns the WGSL standard library function for the given
-// GLSL.std.450 extended instruction operation code.  Unknown
-// and invalid opcodes map to the empty string.
-// @returns the WGSL standard function name, or an empty string.
-std::string GetGlslStd450FuncName(uint32_t ext_opcode) {
-  switch (ext_opcode) {
-    case GLSLstd450FAbs:
-    case GLSLstd450SAbs:
-      return "abs";
-    case GLSLstd450Acos:
-      return "acos";
-    case GLSLstd450Asin:
-      return "asin";
-    case GLSLstd450Atan:
-      return "atan";
-    case GLSLstd450Atan2:
-      return "atan2";
-    case GLSLstd450Ceil:
-      return "ceil";
-    case GLSLstd450UClamp:
-    case GLSLstd450SClamp:
-    case GLSLstd450NClamp:
-    case GLSLstd450FClamp:  // FClamp is less prescriptive about NaN operands
-      return "clamp";
-    case GLSLstd450Cos:
-      return "cos";
-    case GLSLstd450Cosh:
-      return "cosh";
-    case GLSLstd450Cross:
-      return "cross";
-    case GLSLstd450Degrees:
-      return "degrees";
-    case GLSLstd450Distance:
-      return "distance";
-    case GLSLstd450Exp:
-      return "exp";
-    case GLSLstd450Exp2:
-      return "exp2";
-    case GLSLstd450FaceForward:
-      return "faceForward";
-    case GLSLstd450Floor:
-      return "floor";
-    case GLSLstd450Fma:
-      return "fma";
-    case GLSLstd450Fract:
-      return "fract";
-    case GLSLstd450InverseSqrt:
-      return "inverseSqrt";
-    case GLSLstd450Ldexp:
-      return "ldexp";
-    case GLSLstd450Length:
-      return "length";
-    case GLSLstd450Log:
-      return "log";
-    case GLSLstd450Log2:
-      return "log2";
-    case GLSLstd450NMax:
-    case GLSLstd450FMax:  // FMax is less prescriptive about NaN operands
-    case GLSLstd450UMax:
-    case GLSLstd450SMax:
-      return "max";
-    case GLSLstd450NMin:
-    case GLSLstd450FMin:  // FMin is less prescriptive about NaN operands
-    case GLSLstd450UMin:
-    case GLSLstd450SMin:
-      return "min";
-    case GLSLstd450FMix:
-      return "mix";
-    case GLSLstd450Normalize:
-      return "normalize";
-    case GLSLstd450PackSnorm4x8:
-      return "pack4x8snorm";
-    case GLSLstd450PackUnorm4x8:
-      return "pack4x8unorm";
-    case GLSLstd450PackSnorm2x16:
-      return "pack2x16snorm";
-    case GLSLstd450PackUnorm2x16:
-      return "pack2x16unorm";
-    case GLSLstd450PackHalf2x16:
-      return "pack2x16float";
-    case GLSLstd450Pow:
-      return "pow";
-    case GLSLstd450FSign:
-      return "sign";
-    case GLSLstd450Radians:
-      return "radians";
-    case GLSLstd450Reflect:
-      return "reflect";
-    case GLSLstd450Refract:
-      return "refract";
-    case GLSLstd450Round:
-    case GLSLstd450RoundEven:
-      return "round";
-    case GLSLstd450Sin:
-      return "sin";
-    case GLSLstd450Sinh:
-      return "sinh";
-    case GLSLstd450SmoothStep:
-      return "smoothStep";
-    case GLSLstd450Sqrt:
-      return "sqrt";
-    case GLSLstd450Step:
-      return "step";
-    case GLSLstd450Tan:
-      return "tan";
-    case GLSLstd450Tanh:
-      return "tanh";
-    case GLSLstd450Trunc:
-      return "trunc";
-    case GLSLstd450UnpackSnorm4x8:
-      return "unpack4x8snorm";
-    case GLSLstd450UnpackUnorm4x8:
-      return "unpack4x8unorm";
-    case GLSLstd450UnpackSnorm2x16:
-      return "unpack2x16snorm";
-    case GLSLstd450UnpackUnorm2x16:
-      return "unpack2x16unorm";
-    case GLSLstd450UnpackHalf2x16:
-      return "unpack2x16float";
-
-    default:
-      // TODO(dneto) - The following are not implemented.
-      // They are grouped semantically, as in GLSL.std.450.h.
-
-    case GLSLstd450SSign:
-
-    case GLSLstd450Asinh:
-    case GLSLstd450Acosh:
-    case GLSLstd450Atanh:
-
-    case GLSLstd450Determinant:
-    case GLSLstd450MatrixInverse:
-
-    case GLSLstd450Modf:
-    case GLSLstd450ModfStruct:
-    case GLSLstd450IMix:
-
-    case GLSLstd450Frexp:
-    case GLSLstd450FrexpStruct:
-
-    case GLSLstd450PackDouble2x32:
-    case GLSLstd450UnpackDouble2x32:
-
-    case GLSLstd450FindILsb:
-    case GLSLstd450FindSMsb:
-    case GLSLstd450FindUMsb:
-
-    case GLSLstd450InterpolateAtCentroid:
-    case GLSLstd450InterpolateAtSample:
-    case GLSLstd450InterpolateAtOffset:
-      break;
-  }
-  return "";
-}
-
-// Returns the WGSL standard library function builtin for the
-// given instruction, or sem::BuiltinType::kNone
-sem::BuiltinType GetBuiltin(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpBitCount:
-      return sem::BuiltinType::kCountOneBits;
-    case SpvOpBitReverse:
-      return sem::BuiltinType::kReverseBits;
-    case SpvOpDot:
-      return sem::BuiltinType::kDot;
-    case SpvOpDPdx:
-      return sem::BuiltinType::kDpdx;
-    case SpvOpDPdy:
-      return sem::BuiltinType::kDpdy;
-    case SpvOpFwidth:
-      return sem::BuiltinType::kFwidth;
-    case SpvOpDPdxFine:
-      return sem::BuiltinType::kDpdxFine;
-    case SpvOpDPdyFine:
-      return sem::BuiltinType::kDpdyFine;
-    case SpvOpFwidthFine:
-      return sem::BuiltinType::kFwidthFine;
-    case SpvOpDPdxCoarse:
-      return sem::BuiltinType::kDpdxCoarse;
-    case SpvOpDPdyCoarse:
-      return sem::BuiltinType::kDpdyCoarse;
-    case SpvOpFwidthCoarse:
-      return sem::BuiltinType::kFwidthCoarse;
-    default:
-      break;
-  }
-  return sem::BuiltinType::kNone;
-}
-
-// @param opcode a SPIR-V opcode
-// @returns true if the given instruction is an image access instruction
-// whose first input operand is an OpSampledImage value.
-bool IsSampledImageAccess(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpImageSampleImplicitLod:
-    case SpvOpImageSampleExplicitLod:
-    case SpvOpImageSampleDrefImplicitLod:
-    case SpvOpImageSampleDrefExplicitLod:
-    // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
-    case SpvOpImageSampleProjImplicitLod:
-    case SpvOpImageSampleProjExplicitLod:
-    case SpvOpImageSampleProjDrefImplicitLod:
-    case SpvOpImageSampleProjDrefExplicitLod:
-    case SpvOpImageGather:
-    case SpvOpImageDrefGather:
-    case SpvOpImageQueryLod:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// @param opcode a SPIR-V opcode
-// @returns true if the given instruction is an image sampling, gather,
-// or gather-compare operation.
-bool IsImageSamplingOrGatherOrDrefGather(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpImageSampleImplicitLod:
-    case SpvOpImageSampleExplicitLod:
-    case SpvOpImageSampleDrefImplicitLod:
-    case SpvOpImageSampleDrefExplicitLod:
-      // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
-    case SpvOpImageSampleProjImplicitLod:
-    case SpvOpImageSampleProjExplicitLod:
-    case SpvOpImageSampleProjDrefImplicitLod:
-    case SpvOpImageSampleProjDrefExplicitLod:
-    case SpvOpImageGather:
-    case SpvOpImageDrefGather:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// @param opcode a SPIR-V opcode
-// @returns true if the given instruction is an image access instruction
-// whose first input operand is an OpImage value.
-bool IsRawImageAccess(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpImageRead:
-    case SpvOpImageWrite:
-    case SpvOpImageFetch:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// @param opcode a SPIR-V opcode
-// @returns true if the given instruction is an image query instruction
-bool IsImageQuery(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpImageQuerySize:
-    case SpvOpImageQuerySizeLod:
-    case SpvOpImageQueryLevels:
-    case SpvOpImageQuerySamples:
-    case SpvOpImageQueryLod:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// @returns the merge block ID for the given basic block, or 0 if there is none.
-uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) {
-  // Get the OpSelectionMerge or OpLoopMerge instruction, if any.
-  auto* inst = bb.GetMergeInst();
-  return inst == nullptr ? 0 : inst->GetSingleWordInOperand(0);
-}
-
-// @returns the continue target ID for the given basic block, or 0 if there
-// is none.
-uint32_t ContinueTargetFor(const spvtools::opt::BasicBlock& bb) {
-  // Get the OpLoopMerge instruction, if any.
-  auto* inst = bb.GetLoopMergeInst();
-  return inst == nullptr ? 0 : inst->GetSingleWordInOperand(1);
-}
-
-// A structured traverser produces the reverse structured post-order of the
-// CFG of a function.  The blocks traversed are the transitive closure (minimum
-// fixed point) of:
-//  - the entry block
-//  - a block reached by a branch from another block in the set
-//  - a block mentioned as a merge block or continue target for a block in the
-//  set
-class StructuredTraverser {
- public:
-  explicit StructuredTraverser(const spvtools::opt::Function& function)
-      : function_(function) {
-    for (auto& block : function_) {
-      id_to_block_[block.id()] = &block;
-    }
-  }
-
-  // Returns the reverse postorder traversal of the CFG, where:
-  //  - a merge block always follows its associated constructs
-  //  - a continue target always follows the associated loop construct, if any
-  // @returns the IDs of blocks in reverse structured post order
-  std::vector<uint32_t> ReverseStructuredPostOrder() {
-    visit_order_.clear();
-    visited_.clear();
-    VisitBackward(function_.entry()->id());
-
-    std::vector<uint32_t> order(visit_order_.rbegin(), visit_order_.rend());
-    return order;
-  }
-
- private:
-  // Executes a depth first search of the CFG, where right after we visit a
-  // header, we will visit its merge block, then its continue target (if any).
-  // Also records the post order ordering.
-  void VisitBackward(uint32_t id) {
-    if (id == 0)
-      return;
-    if (visited_.count(id))
-      return;
-    visited_.insert(id);
-
-    const spvtools::opt::BasicBlock* bb =
-        id_to_block_[id];  // non-null for valid modules
-    VisitBackward(MergeFor(*bb));
-    VisitBackward(ContinueTargetFor(*bb));
-
-    // Visit successors. We will naturally skip the continue target and merge
-    // blocks.
-    auto* terminator = bb->terminator();
-    auto opcode = terminator->opcode();
-    if (opcode == SpvOpBranchConditional) {
-      // Visit the false branch, then the true branch, to make them come
-      // out in the natural order for an "if".
-      VisitBackward(terminator->GetSingleWordInOperand(2));
-      VisitBackward(terminator->GetSingleWordInOperand(1));
-    } else if (opcode == SpvOpBranch) {
-      VisitBackward(terminator->GetSingleWordInOperand(0));
-    } else if (opcode == SpvOpSwitch) {
-      // TODO(dneto): Consider visiting the labels in literal-value order.
-      std::vector<uint32_t> successors;
-      bb->ForEachSuccessorLabel([&successors](const uint32_t succ_id) {
-        successors.push_back(succ_id);
-      });
-      for (auto succ_id : successors) {
-        VisitBackward(succ_id);
-      }
-    }
-
-    visit_order_.push_back(id);
-  }
-
-  const spvtools::opt::Function& function_;
-  std::unordered_map<uint32_t, const spvtools::opt::BasicBlock*> id_to_block_;
-  std::vector<uint32_t> visit_order_;
-  std::unordered_set<uint32_t> visited_;
-};
-
-/// A StatementBuilder for ast::SwitchStatement
-/// @see StatementBuilder
-struct SwitchStatementBuilder
-    : public Castable<SwitchStatementBuilder, StatementBuilder> {
-  /// Constructor
-  /// @param cond the switch statement condition
-  explicit SwitchStatementBuilder(const ast::Expression* cond)
-      : condition(cond) {}
-
-  /// @param builder the program builder
-  /// @returns the built ast::SwitchStatement
-  const ast::SwitchStatement* Build(ProgramBuilder* builder) const override {
-    // We've listed cases in reverse order in the switch statement.
-    // Reorder them to match the presentation order in WGSL.
-    auto reversed_cases = cases;
-    std::reverse(reversed_cases.begin(), reversed_cases.end());
-
-    return builder->create<ast::SwitchStatement>(Source{}, condition,
-                                                 reversed_cases);
-  }
-
-  /// Switch statement condition
-  const ast::Expression* const condition;
-  /// Switch statement cases
-  ast::CaseStatementList cases;
-};
-
-/// A StatementBuilder for ast::IfStatement
-/// @see StatementBuilder
-struct IfStatementBuilder
-    : public Castable<IfStatementBuilder, StatementBuilder> {
-  /// Constructor
-  /// @param c the if-statement condition
-  explicit IfStatementBuilder(const ast::Expression* c) : cond(c) {}
-
-  /// @param builder the program builder
-  /// @returns the built ast::IfStatement
-  const ast::IfStatement* Build(ProgramBuilder* builder) const override {
-    return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmts);
-  }
-
-  /// If-statement condition
-  const ast::Expression* const cond;
-  /// If-statement block body
-  const ast::BlockStatement* body = nullptr;
-  /// Optional if-statement else statements
-  ast::ElseStatementList else_stmts;
-};
-
-/// A StatementBuilder for ast::LoopStatement
-/// @see StatementBuilder
-struct LoopStatementBuilder
-    : public Castable<LoopStatementBuilder, StatementBuilder> {
-  /// @param builder the program builder
-  /// @returns the built ast::LoopStatement
-  ast::LoopStatement* Build(ProgramBuilder* builder) const override {
-    return builder->create<ast::LoopStatement>(Source{}, body, continuing);
-  }
-
-  /// Loop-statement block body
-  const ast::BlockStatement* body = nullptr;
-  /// Loop-statement continuing body
-  /// @note the mutable keyword here is required as all non-StatementBuilders
-  /// `ast::Node`s are immutable and are referenced with `const` pointers.
-  /// StatementBuilders however exist to provide mutable state while the
-  /// FunctionEmitter is building the function. All StatementBuilders are
-  /// replaced with immutable AST nodes when Finalize() is called.
-  mutable const ast::BlockStatement* continuing = nullptr;
-};
-
-/// @param decos a list of parsed decorations
-/// @returns true if the decorations include a SampleMask builtin
-bool HasBuiltinSampleMask(const ast::AttributeList& decos) {
-  if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(decos)) {
-    return builtin->builtin == ast::Builtin::kSampleMask;
-  }
-  return false;
-}
-
-}  // namespace
-
-BlockInfo::BlockInfo(const spvtools::opt::BasicBlock& bb)
-    : basic_block(&bb), id(bb.id()) {}
-
-BlockInfo::~BlockInfo() = default;
-
-DefInfo::DefInfo(const spvtools::opt::Instruction& def_inst,
-                 uint32_t the_block_pos,
-                 size_t the_index)
-    : inst(def_inst), block_pos(the_block_pos), index(the_index) {}
-
-DefInfo::~DefInfo() = default;
-
-ast::Node* StatementBuilder::Clone(CloneContext*) const {
-  return nullptr;
-}
-
-FunctionEmitter::FunctionEmitter(ParserImpl* pi,
-                                 const spvtools::opt::Function& function,
-                                 const EntryPointInfo* ep_info)
-    : parser_impl_(*pi),
-      ty_(pi->type_manager()),
-      builder_(pi->builder()),
-      ir_context_(*(pi->ir_context())),
-      def_use_mgr_(ir_context_.get_def_use_mgr()),
-      constant_mgr_(ir_context_.get_constant_mgr()),
-      type_mgr_(ir_context_.get_type_mgr()),
-      fail_stream_(pi->fail_stream()),
-      namer_(pi->namer()),
-      function_(function),
-      sample_mask_in_id(0u),
-      sample_mask_out_id(0u),
-      ep_info_(ep_info) {
-  PushNewStatementBlock(nullptr, 0, nullptr);
-}
-
-FunctionEmitter::FunctionEmitter(ParserImpl* pi,
-                                 const spvtools::opt::Function& function)
-    : FunctionEmitter(pi, function, nullptr) {}
-
-FunctionEmitter::FunctionEmitter(FunctionEmitter&& other)
-    : parser_impl_(other.parser_impl_),
-      ty_(other.ty_),
-      builder_(other.builder_),
-      ir_context_(other.ir_context_),
-      def_use_mgr_(ir_context_.get_def_use_mgr()),
-      constant_mgr_(ir_context_.get_constant_mgr()),
-      type_mgr_(ir_context_.get_type_mgr()),
-      fail_stream_(other.fail_stream_),
-      namer_(other.namer_),
-      function_(other.function_),
-      sample_mask_in_id(other.sample_mask_out_id),
-      sample_mask_out_id(other.sample_mask_in_id),
-      ep_info_(other.ep_info_) {
-  other.statements_stack_.clear();
-  PushNewStatementBlock(nullptr, 0, nullptr);
-}
-
-FunctionEmitter::~FunctionEmitter() = default;
-
-FunctionEmitter::StatementBlock::StatementBlock(
-    const Construct* construct,
-    uint32_t end_id,
-    FunctionEmitter::CompletionAction completion_action)
-    : construct_(construct),
-      end_id_(end_id),
-      completion_action_(completion_action) {}
-
-FunctionEmitter::StatementBlock::StatementBlock(StatementBlock&& other) =
-    default;
-
-FunctionEmitter::StatementBlock::~StatementBlock() = default;
-
-void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) {
-  TINT_ASSERT(Reader, !finalized_ /* Finalize() must only be called once */);
-
-  for (size_t i = 0; i < statements_.size(); i++) {
-    if (auto* sb = statements_[i]->As<StatementBuilder>()) {
-      statements_[i] = sb->Build(pb);
-    }
-  }
-
-  if (completion_action_ != nullptr) {
-    completion_action_(statements_);
-  }
-
-  finalized_ = true;
-}
-
-void FunctionEmitter::StatementBlock::Add(const ast::Statement* statement) {
-  TINT_ASSERT(Reader,
-              !finalized_ /* Add() must not be called after Finalize() */);
-  statements_.emplace_back(statement);
-}
-
-void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
-                                            uint32_t end_id,
-                                            CompletionAction action) {
-  statements_stack_.emplace_back(StatementBlock{construct, end_id, action});
-}
-
-void FunctionEmitter::PushGuard(const std::string& guard_name,
-                                uint32_t end_id) {
-  TINT_ASSERT(Reader, !statements_stack_.empty());
-  TINT_ASSERT(Reader, !guard_name.empty());
-  // Guard control flow by the guard variable.  Introduce a new
-  // if-selection with a then-clause ending at the same block
-  // as the statement block at the top of the stack.
-  const auto& top = statements_stack_.back();
-
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(guard_name));
-  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
-
-  PushNewStatementBlock(
-      top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
-        builder->body = create<ast::BlockStatement>(Source{}, stmts);
-      });
-}
-
-void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
-  TINT_ASSERT(Reader, !statements_stack_.empty());
-  const auto& top = statements_stack_.back();
-
-  auto* cond = MakeTrue(Source{});
-  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
-
-  PushNewStatementBlock(
-      top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
-        builder->body = create<ast::BlockStatement>(Source{}, stmts);
-      });
-}
-
-const ast::StatementList FunctionEmitter::ast_body() {
-  TINT_ASSERT(Reader, !statements_stack_.empty());
-  auto& entry = statements_stack_[0];
-  entry.Finalize(&builder_);
-  return entry.GetStatements();
-}
-
-const ast::Statement* FunctionEmitter::AddStatement(
-    const ast::Statement* statement) {
-  TINT_ASSERT(Reader, !statements_stack_.empty());
-  if (statement != nullptr) {
-    statements_stack_.back().Add(statement);
-  }
-  return statement;
-}
-
-const ast::Statement* FunctionEmitter::LastStatement() {
-  TINT_ASSERT(Reader, !statements_stack_.empty());
-  auto& statement_list = statements_stack_.back().GetStatements();
-  TINT_ASSERT(Reader, !statement_list.empty());
-  return statement_list.back();
-}
-
-bool FunctionEmitter::Emit() {
-  if (failed()) {
-    return false;
-  }
-  // We only care about functions with bodies.
-  if (function_.cbegin() == function_.cend()) {
-    return true;
-  }
-
-  // The function declaration, corresponding to how it's written in SPIR-V,
-  // and without regard to whether it's an entry point.
-  FunctionDeclaration decl;
-  if (!ParseFunctionDeclaration(&decl)) {
-    return false;
-  }
-
-  bool make_body_function = true;
-  if (ep_info_) {
-    TINT_ASSERT(Reader, !ep_info_->inner_name.empty());
-    if (ep_info_->owns_inner_implementation) {
-      // This is an entry point, and we want to emit it as a wrapper around
-      // an implementation function.
-      decl.name = ep_info_->inner_name;
-    } else {
-      // This is a second entry point that shares an inner implementation
-      // function.
-      make_body_function = false;
-    }
-  }
-
-  if (make_body_function) {
-    auto* body = MakeFunctionBody();
-    if (!body) {
-      return false;
-    }
-
-    builder_.AST().AddFunction(create<ast::Function>(
-        decl.source, builder_.Symbols().Register(decl.name),
-        std::move(decl.params), decl.return_type->Build(builder_), body,
-        std::move(decl.attributes), ast::AttributeList{}));
-  }
-
-  if (ep_info_ && !ep_info_->inner_name.empty()) {
-    return EmitEntryPointAsWrapper();
-  }
-
-  return success();
-}
-
-const ast::BlockStatement* FunctionEmitter::MakeFunctionBody() {
-  TINT_ASSERT(Reader, statements_stack_.size() == 1);
-
-  if (!EmitBody()) {
-    return nullptr;
-  }
-
-  // Set the body of the AST function node.
-  if (statements_stack_.size() != 1) {
-    Fail() << "internal error: statement-list stack should have 1 "
-              "element but has "
-           << statements_stack_.size();
-    return nullptr;
-  }
-
-  statements_stack_[0].Finalize(&builder_);
-  auto& statements = statements_stack_[0].GetStatements();
-  auto* body = create<ast::BlockStatement>(Source{}, statements);
-
-  // Maintain the invariant by repopulating the one and only element.
-  statements_stack_.clear();
-  PushNewStatementBlock(constructs_[0].get(), 0, nullptr);
-
-  return body;
-}
-
-bool FunctionEmitter::EmitPipelineInput(std::string var_name,
-                                        const Type* var_type,
-                                        ast::AttributeList* attrs,
-                                        std::vector<int> index_prefix,
-                                        const Type* tip_type,
-                                        const Type* forced_param_type,
-                                        ast::VariableList* params,
-                                        ast::StatementList* statements) {
-  // TODO(dneto): Handle structs where the locations are annotated on members.
-  tip_type = tip_type->UnwrapAlias();
-  if (auto* ref_type = tip_type->As<Reference>()) {
-    tip_type = ref_type->type;
-  }
-
-  // Recursively flatten matrices, arrays, and structures.
-  return Switch(
-      tip_type,
-      [&](const Matrix* matrix_type) -> bool {
-        index_prefix.push_back(0);
-        const auto num_columns = static_cast<int>(matrix_type->columns);
-        const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
-        for (int col = 0; col < num_columns; col++) {
-          index_prefix.back() = col;
-          if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix,
-                                 vec_ty, forced_param_type, params,
-                                 statements)) {
-            return false;
-          }
-        }
-        return success();
-      },
-      [&](const Array* array_type) -> bool {
-        if (array_type->size == 0) {
-          return Fail() << "runtime-size array not allowed on pipeline IO";
-        }
-        index_prefix.push_back(0);
-        const Type* elem_ty = array_type->type;
-        for (int i = 0; i < static_cast<int>(array_type->size); i++) {
-          index_prefix.back() = i;
-          if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix,
-                                 elem_ty, forced_param_type, params,
-                                 statements)) {
-            return false;
-          }
-        }
-        return success();
-      },
-      [&](const Struct* struct_type) -> bool {
-        const auto& members = struct_type->members;
-        index_prefix.push_back(0);
-        for (int i = 0; i < static_cast<int>(members.size()); ++i) {
-          index_prefix.back() = i;
-          ast::AttributeList member_attrs(*attrs);
-          if (!parser_impl_.ConvertPipelineDecorations(
-                  struct_type,
-                  parser_impl_.GetMemberPipelineDecorations(*struct_type, i),
-                  &member_attrs)) {
-            return false;
-          }
-          if (!EmitPipelineInput(var_name, var_type, &member_attrs,
-                                 index_prefix, members[i], forced_param_type,
-                                 params, statements)) {
-            return false;
-          }
-          // Copy the location as updated by nested expansion of the member.
-          parser_impl_.SetLocation(attrs, GetLocation(member_attrs));
-        }
-        return success();
-      },
-      [&](Default) {
-        const bool is_builtin =
-            ast::HasAttribute<ast::BuiltinAttribute>(*attrs);
-
-        const Type* param_type = is_builtin ? forced_param_type : tip_type;
-
-        const auto param_name = namer_.MakeDerivedName(var_name + "_param");
-        // Create the parameter.
-        // TODO(dneto): Note: If the parameter has non-location decorations,
-        // then those decoration AST nodes will be reused between multiple
-        // elements of a matrix, array, or structure.  Normally that's
-        // disallowed but currently the SPIR-V reader will make duplicates when
-        // the entire AST is cloned at the top level of the SPIR-V reader flow.
-        // Consider rewriting this to avoid this node-sharing.
-        params->push_back(
-            builder_.Param(param_name, param_type->Build(builder_), *attrs));
-
-        // Add a body statement to copy the parameter to the corresponding
-        // private variable.
-        const ast::Expression* param_value = builder_.Expr(param_name);
-        const ast::Expression* store_dest = builder_.Expr(var_name);
-
-        // Index into the LHS as needed.
-        auto* current_type =
-            var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias();
-        for (auto index : index_prefix) {
-          Switch(
-              current_type,
-              [&](const Matrix* matrix_type) {
-                store_dest =
-                    builder_.IndexAccessor(store_dest, builder_.Expr(index));
-                current_type = ty_.Vector(matrix_type->type, matrix_type->rows);
-              },
-              [&](const Array* array_type) {
-                store_dest =
-                    builder_.IndexAccessor(store_dest, builder_.Expr(index));
-                current_type = array_type->type->UnwrapAlias();
-              },
-              [&](const Struct* struct_type) {
-                store_dest = builder_.MemberAccessor(
-                    store_dest, builder_.Expr(parser_impl_.GetMemberName(
-                                    *struct_type, index)));
-                current_type = struct_type->members[index];
-              });
-        }
-
-        if (is_builtin && (tip_type != forced_param_type)) {
-          // The parameter will have the WGSL type, but we need bitcast to
-          // the variable store type.
-          param_value = create<ast::BitcastExpression>(
-              tip_type->Build(builder_), param_value);
-        }
-
-        statements->push_back(builder_.Assign(store_dest, param_value));
-
-        // Increment the location attribute, in case more parameters will
-        // follow.
-        IncrementLocation(attrs);
-
-        return success();
-      });
-}
-
-void FunctionEmitter::IncrementLocation(ast::AttributeList* attributes) {
-  for (auto*& attr : *attributes) {
-    if (auto* loc_attr = attr->As<ast::LocationAttribute>()) {
-      // Replace this location attribute with a new one with one higher index.
-      // The old one doesn't leak because it's kept in the builder's AST node
-      // list.
-      attr = builder_.Location(loc_attr->source, loc_attr->value + 1);
-    }
-  }
-}
-
-const ast::Attribute* FunctionEmitter::GetLocation(
-    const ast::AttributeList& attributes) {
-  for (auto* const& attr : attributes) {
-    if (attr->Is<ast::LocationAttribute>()) {
-      return attr;
-    }
-  }
-  return nullptr;
-}
-
-bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
-                                         const Type* var_type,
-                                         ast::AttributeList* decos,
-                                         std::vector<int> index_prefix,
-                                         const Type* tip_type,
-                                         const Type* forced_member_type,
-                                         ast::StructMemberList* return_members,
-                                         ast::ExpressionList* return_exprs) {
-  tip_type = tip_type->UnwrapAlias();
-  if (auto* ref_type = tip_type->As<Reference>()) {
-    tip_type = ref_type->type;
-  }
-
-  // Recursively flatten matrices, arrays, and structures.
-  return Switch(
-      tip_type,
-      [&](const Matrix* matrix_type) -> bool {
-        index_prefix.push_back(0);
-        const auto num_columns = static_cast<int>(matrix_type->columns);
-        const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
-        for (int col = 0; col < num_columns; col++) {
-          index_prefix.back() = col;
-          if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix,
-                                  vec_ty, forced_member_type, return_members,
-                                  return_exprs)) {
-            return false;
-          }
-        }
-        return success();
-      },
-      [&](const Array* array_type) -> bool {
-        if (array_type->size == 0) {
-          return Fail() << "runtime-size array not allowed on pipeline IO";
-        }
-        index_prefix.push_back(0);
-        const Type* elem_ty = array_type->type;
-        for (int i = 0; i < static_cast<int>(array_type->size); i++) {
-          index_prefix.back() = i;
-          if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix,
-                                  elem_ty, forced_member_type, return_members,
-                                  return_exprs)) {
-            return false;
-          }
-        }
-        return success();
-      },
-      [&](const Struct* struct_type) -> bool {
-        const auto& members = struct_type->members;
-        index_prefix.push_back(0);
-        for (int i = 0; i < static_cast<int>(members.size()); ++i) {
-          index_prefix.back() = i;
-          ast::AttributeList member_attrs(*decos);
-          if (!parser_impl_.ConvertPipelineDecorations(
-                  struct_type,
-                  parser_impl_.GetMemberPipelineDecorations(*struct_type, i),
-                  &member_attrs)) {
-            return false;
-          }
-          if (!EmitPipelineOutput(var_name, var_type, &member_attrs,
-                                  index_prefix, members[i], forced_member_type,
-                                  return_members, return_exprs)) {
-            return false;
-          }
-          // Copy the location as updated by nested expansion of the member.
-          parser_impl_.SetLocation(decos, GetLocation(member_attrs));
-        }
-        return success();
-      },
-      [&](Default) {
-        const bool is_builtin =
-            ast::HasAttribute<ast::BuiltinAttribute>(*decos);
-
-        const Type* member_type = is_builtin ? forced_member_type : tip_type;
-        // Derive the member name directly from the variable name.  They can't
-        // collide.
-        const auto member_name = namer_.MakeDerivedName(var_name);
-        // Create the member.
-        // TODO(dneto): Note: If the parameter has non-location decorations,
-        // then those decoration AST nodes  will be reused between multiple
-        // elements of a matrix, array, or structure.  Normally that's
-        // disallowed but currently the SPIR-V reader will make duplicates when
-        // the entire AST is cloned at the top level of the SPIR-V reader flow.
-        // Consider rewriting this to avoid this node-sharing.
-        return_members->push_back(
-            builder_.Member(member_name, member_type->Build(builder_), *decos));
-
-        // Create an expression to evaluate the part of the variable indexed by
-        // the index_prefix.
-        const ast::Expression* load_source = builder_.Expr(var_name);
-
-        // Index into the variable as needed to pick out the flattened member.
-        auto* current_type =
-            var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias();
-        for (auto index : index_prefix) {
-          Switch(
-              current_type,
-              [&](const Matrix* matrix_type) {
-                load_source =
-                    builder_.IndexAccessor(load_source, builder_.Expr(index));
-                current_type = ty_.Vector(matrix_type->type, matrix_type->rows);
-              },
-              [&](const Array* array_type) {
-                load_source =
-                    builder_.IndexAccessor(load_source, builder_.Expr(index));
-                current_type = array_type->type->UnwrapAlias();
-              },
-              [&](const Struct* struct_type) {
-                load_source = builder_.MemberAccessor(
-                    load_source, builder_.Expr(parser_impl_.GetMemberName(
-                                     *struct_type, index)));
-                current_type = struct_type->members[index];
-              });
-        }
-
-        if (is_builtin && (tip_type != forced_member_type)) {
-          // The member will have the WGSL type, but we need bitcast to
-          // the variable store type.
-          load_source = create<ast::BitcastExpression>(
-              forced_member_type->Build(builder_), load_source);
-        }
-        return_exprs->push_back(load_source);
-
-        // Increment the location attribute, in case more parameters will
-        // follow.
-        IncrementLocation(decos);
-
-        return success();
-      });
-}
-
-bool FunctionEmitter::EmitEntryPointAsWrapper() {
-  Source source;
-
-  // The statements in the body.
-  ast::StatementList stmts;
-
-  FunctionDeclaration decl;
-  decl.source = source;
-  decl.name = ep_info_->name;
-  const ast::Type* return_type = nullptr;  // Populated below.
-
-  // Pipeline inputs become parameters to the wrapper function, and
-  // their values are saved into the corresponding private variables that
-  // have already been created.
-  for (uint32_t var_id : ep_info_->inputs) {
-    const auto* var = def_use_mgr_->GetDef(var_id);
-    TINT_ASSERT(Reader, var != nullptr);
-    TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
-    auto* store_type = GetVariableStoreType(*var);
-    auto* forced_param_type = store_type;
-    ast::AttributeList param_decos;
-    if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_param_type,
-                                                    &param_decos, true)) {
-      // This occurs, and is not an error, for the PointSize builtin.
-      if (!success()) {
-        // But exit early if an error was logged.
-        return false;
-      }
-      continue;
-    }
-
-    // We don't have to handle initializers because in Vulkan SPIR-V, Input
-    // variables must not have them.
-
-    const auto var_name = namer_.GetName(var_id);
-
-    bool ok = true;
-    if (HasBuiltinSampleMask(param_decos)) {
-      // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a scalar.
-      // Use the first element only.
-      auto* sample_mask_array_type =
-          store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
-      TINT_ASSERT(Reader, sample_mask_array_type);
-      ok = EmitPipelineInput(var_name, store_type, &param_decos, {0},
-                             sample_mask_array_type->type, forced_param_type,
-                             &(decl.params), &stmts);
-    } else {
-      // The normal path.
-      ok = EmitPipelineInput(var_name, store_type, &param_decos, {}, store_type,
-                             forced_param_type, &(decl.params), &stmts);
-    }
-    if (!ok) {
-      return false;
-    }
-  }
-
-  // Call the inner function.  It has no parameters.
-  stmts.push_back(create<ast::CallStatement>(
-      source,
-      create<ast::CallExpression>(
-          source,
-          create<ast::IdentifierExpression>(
-              source, builder_.Symbols().Register(ep_info_->inner_name)),
-          ast::ExpressionList{})));
-
-  // Pipeline outputs are mapped to the return value.
-  if (ep_info_->outputs.empty()) {
-    // There is nothing to return.
-    return_type = ty_.Void()->Build(builder_);
-  } else {
-    // Pipeline outputs are converted to a structure that is written
-    // to just before returning.
-
-    const auto return_struct_name =
-        namer_.MakeDerivedName(ep_info_->name + "_out");
-    const auto return_struct_sym =
-        builder_.Symbols().Register(return_struct_name);
-
-    // Define the structure.
-    std::vector<const ast::StructMember*> return_members;
-    ast::ExpressionList return_exprs;
-
-    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
-
-    for (uint32_t var_id : ep_info_->outputs) {
-      if (var_id == builtin_position_info.per_vertex_var_id) {
-        // The SPIR-V gl_PerVertex variable has already been remapped to
-        // a gl_Position variable.  Substitute the type.
-        const Type* param_type = ty_.Vector(ty_.F32(), 4);
-        ast::AttributeList out_decos{
-            create<ast::BuiltinAttribute>(source, ast::Builtin::kPosition)};
-
-        const auto var_name = namer_.GetName(var_id);
-        return_members.push_back(
-            builder_.Member(var_name, param_type->Build(builder_), out_decos));
-        return_exprs.push_back(builder_.Expr(var_name));
-
-      } else {
-        const auto* var = def_use_mgr_->GetDef(var_id);
-        TINT_ASSERT(Reader, var != nullptr);
-        TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
-        const Type* store_type = GetVariableStoreType(*var);
-        const Type* forced_member_type = store_type;
-        ast::AttributeList out_decos;
-        if (!parser_impl_.ConvertDecorationsForVariable(
-                var_id, &forced_member_type, &out_decos, true)) {
-          // This occurs, and is not an error, for the PointSize builtin.
-          if (!success()) {
-            // But exit early if an error was logged.
-            return false;
-          }
-          continue;
-        }
-
-        const auto var_name = namer_.GetName(var_id);
-        bool ok = true;
-        if (HasBuiltinSampleMask(out_decos)) {
-          // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a
-          // scalar. Use the first element only.
-          auto* sample_mask_array_type =
-              store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
-          TINT_ASSERT(Reader, sample_mask_array_type);
-          ok = EmitPipelineOutput(var_name, store_type, &out_decos, {0},
-                                  sample_mask_array_type->type,
-                                  forced_member_type, &return_members,
-                                  &return_exprs);
-        } else {
-          // The normal path.
-          ok = EmitPipelineOutput(var_name, store_type, &out_decos, {},
-                                  store_type, forced_member_type,
-                                  &return_members, &return_exprs);
-        }
-        if (!ok) {
-          return false;
-        }
-      }
-    }
-
-    if (return_members.empty()) {
-      // This can occur if only the PointSize member is accessed, because we
-      // never emit it.
-      return_type = ty_.Void()->Build(builder_);
-    } else {
-      // Create and register the result type.
-      auto* str = create<ast::Struct>(Source{}, return_struct_sym,
-                                      return_members, ast::AttributeList{});
-      parser_impl_.AddTypeDecl(return_struct_sym, str);
-      return_type = builder_.ty.Of(str);
-
-      // Add the return-value statement.
-      stmts.push_back(create<ast::ReturnStatement>(
-          source,
-          builder_.Construct(source, return_type, std::move(return_exprs))));
-    }
-  }
-
-  auto* body = create<ast::BlockStatement>(source, stmts);
-  ast::AttributeList fn_attrs;
-  fn_attrs.emplace_back(create<ast::StageAttribute>(source, ep_info_->stage));
-
-  if (ep_info_->stage == ast::PipelineStage::kCompute) {
-    auto& size = ep_info_->workgroup_size;
-    if (size.x != 0 && size.y != 0 && size.z != 0) {
-      const ast::Expression* x = builder_.Expr(static_cast<int>(size.x));
-      const ast::Expression* y =
-          size.y ? builder_.Expr(static_cast<int>(size.y)) : nullptr;
-      const ast::Expression* z =
-          size.z ? builder_.Expr(static_cast<int>(size.z)) : nullptr;
-      fn_attrs.emplace_back(create<ast::WorkgroupAttribute>(Source{}, x, y, z));
-    }
-  }
-
-  builder_.AST().AddFunction(
-      create<ast::Function>(source, builder_.Symbols().Register(ep_info_->name),
-                            std::move(decl.params), return_type, body,
-                            std::move(fn_attrs), ast::AttributeList{}));
-
-  return true;
-}
-
-bool FunctionEmitter::ParseFunctionDeclaration(FunctionDeclaration* decl) {
-  if (failed()) {
-    return false;
-  }
-
-  const std::string name = namer_.Name(function_.result_id());
-
-  // Surprisingly, the "type id" on an OpFunction is the result type of the
-  // function, not the type of the function.  This is the one exceptional case
-  // in SPIR-V where the type ID is not the type of the result ID.
-  auto* ret_ty = parser_impl_.ConvertType(function_.type_id());
-  if (failed()) {
-    return false;
-  }
-  if (ret_ty == nullptr) {
-    return Fail()
-           << "internal error: unregistered return type for function with ID "
-           << function_.result_id();
-  }
-
-  ast::VariableList ast_params;
-  function_.ForEachParam(
-      [this, &ast_params](const spvtools::opt::Instruction* param) {
-        auto* type = parser_impl_.ConvertType(param->type_id());
-        if (type != nullptr) {
-          auto* ast_param = parser_impl_.MakeVariable(
-              param->result_id(), ast::StorageClass::kNone, type, true, false,
-              nullptr, ast::AttributeList{});
-          // Parameters are treated as const declarations.
-          ast_params.emplace_back(ast_param);
-          // The value is accessible by name.
-          identifier_types_.emplace(param->result_id(), type);
-        } else {
-          // We've already logged an error and emitted a diagnostic. Do nothing
-          // here.
-        }
-      });
-  if (failed()) {
-    return false;
-  }
-  decl->name = name;
-  decl->params = std::move(ast_params);
-  decl->return_type = ret_ty;
-  decl->attributes.clear();
-
-  return success();
-}
-
-const Type* FunctionEmitter::GetVariableStoreType(
-    const spvtools::opt::Instruction& var_decl_inst) {
-  const auto type_id = var_decl_inst.type_id();
-  // Normally we use the SPIRV-Tools optimizer to manage types.
-  // But when two struct types have the same member types and decorations,
-  // but differ only in member names, the two struct types will be
-  // represented by a single common internal struct type.
-  // So avoid the optimizer's representation and instead follow the
-  // SPIR-V instructions themselves.
-  const auto* ptr_ty = def_use_mgr_->GetDef(type_id);
-  const auto store_ty_id = ptr_ty->GetSingleWordInOperand(1);
-  const auto* result = parser_impl_.ConvertType(store_ty_id);
-  return result;
-}
-
-bool FunctionEmitter::EmitBody() {
-  RegisterBasicBlocks();
-
-  if (!TerminatorsAreValid()) {
-    return false;
-  }
-  if (!RegisterMerges()) {
-    return false;
-  }
-
-  ComputeBlockOrderAndPositions();
-  if (!VerifyHeaderContinueMergeOrder()) {
-    return false;
-  }
-  if (!LabelControlFlowConstructs()) {
-    return false;
-  }
-  if (!FindSwitchCaseHeaders()) {
-    return false;
-  }
-  if (!ClassifyCFGEdges()) {
-    return false;
-  }
-  if (!FindIfSelectionInternalHeaders()) {
-    return false;
-  }
-
-  if (!RegisterSpecialBuiltInVariables()) {
-    return false;
-  }
-  if (!RegisterLocallyDefinedValues()) {
-    return false;
-  }
-  FindValuesNeedingNamedOrHoistedDefinition();
-
-  if (!EmitFunctionVariables()) {
-    return false;
-  }
-  if (!EmitFunctionBodyStatements()) {
-    return false;
-  }
-  return success();
-}
-
-void FunctionEmitter::RegisterBasicBlocks() {
-  for (auto& block : function_) {
-    block_info_[block.id()] = std::make_unique<BlockInfo>(block);
-  }
-}
-
-bool FunctionEmitter::TerminatorsAreValid() {
-  if (failed()) {
-    return false;
-  }
-
-  const auto entry_id = function_.begin()->id();
-  for (const auto& block : function_) {
-    if (!block.terminator()) {
-      return Fail() << "Block " << block.id() << " has no terminator";
-    }
-  }
-  for (const auto& block : function_) {
-    block.WhileEachSuccessorLabel(
-        [this, &block, entry_id](const uint32_t succ_id) -> bool {
-          if (succ_id == entry_id) {
-            return Fail() << "Block " << block.id()
-                          << " branches to function entry block " << entry_id;
-          }
-          if (!GetBlockInfo(succ_id)) {
-            return Fail() << "Block " << block.id() << " in function "
-                          << function_.DefInst().result_id() << " branches to "
-                          << succ_id << " which is not a block in the function";
-          }
-          return true;
-        });
-  }
-  return success();
-}
-
-bool FunctionEmitter::RegisterMerges() {
-  if (failed()) {
-    return false;
-  }
-
-  const auto entry_id = function_.begin()->id();
-  for (const auto& block : function_) {
-    const auto block_id = block.id();
-    auto* block_info = GetBlockInfo(block_id);
-    if (!block_info) {
-      return Fail() << "internal error: block " << block_id
-                    << " missing; blocks should already "
-                       "have been registered";
-    }
-
-    if (const auto* inst = block.GetMergeInst()) {
-      auto terminator_opcode = block.terminator()->opcode();
-      switch (inst->opcode()) {
-        case SpvOpSelectionMerge:
-          if ((terminator_opcode != SpvOpBranchConditional) &&
-              (terminator_opcode != SpvOpSwitch)) {
-            return Fail() << "Selection header " << block_id
-                          << " does not end in an OpBranchConditional or "
-                             "OpSwitch instruction";
-          }
-          break;
-        case SpvOpLoopMerge:
-          if ((terminator_opcode != SpvOpBranchConditional) &&
-              (terminator_opcode != SpvOpBranch)) {
-            return Fail() << "Loop header " << block_id
-                          << " does not end in an OpBranch or "
-                             "OpBranchConditional instruction";
-          }
-          break;
-        default:
-          break;
-      }
-
-      const uint32_t header = block.id();
-      auto* header_info = block_info;
-      const uint32_t merge = inst->GetSingleWordInOperand(0);
-      auto* merge_info = GetBlockInfo(merge);
-      if (!merge_info) {
-        return Fail() << "Structured header block " << header
-                      << " declares invalid merge block " << merge;
-      }
-      if (merge == header) {
-        return Fail() << "Structured header block " << header
-                      << " cannot be its own merge block";
-      }
-      if (merge_info->header_for_merge) {
-        return Fail() << "Block " << merge
-                      << " declared as merge block for more than one header: "
-                      << merge_info->header_for_merge << ", " << header;
-      }
-      merge_info->header_for_merge = header;
-      header_info->merge_for_header = merge;
-
-      if (inst->opcode() == SpvOpLoopMerge) {
-        if (header == entry_id) {
-          return Fail() << "Function entry block " << entry_id
-                        << " cannot be a loop header";
-        }
-        const uint32_t ct = inst->GetSingleWordInOperand(1);
-        auto* ct_info = GetBlockInfo(ct);
-        if (!ct_info) {
-          return Fail() << "Structured header " << header
-                        << " declares invalid continue target " << ct;
-        }
-        if (ct == merge) {
-          return Fail() << "Invalid structured header block " << header
-                        << ": declares block " << ct
-                        << " as both its merge block and continue target";
-        }
-        if (ct_info->header_for_continue) {
-          return Fail()
-                 << "Block " << ct
-                 << " declared as continue target for more than one header: "
-                 << ct_info->header_for_continue << ", " << header;
-        }
-        ct_info->header_for_continue = header;
-        header_info->continue_for_header = ct;
-      }
-    }
-
-    // Check single-block loop cases.
-    bool is_single_block_loop = false;
-    block_info->basic_block->ForEachSuccessorLabel(
-        [&is_single_block_loop, block_id](const uint32_t succ) {
-          if (block_id == succ)
-            is_single_block_loop = true;
-        });
-    const auto ct = block_info->continue_for_header;
-    block_info->is_continue_entire_loop = ct == block_id;
-    if (is_single_block_loop && !block_info->is_continue_entire_loop) {
-      return Fail() << "Block " << block_id
-                    << " branches to itself but is not its own continue target";
-    }
-    // It's valid for a the header of a multi-block loop header to declare
-    // itself as its own continue target.
-  }
-  return success();
-}
-
-void FunctionEmitter::ComputeBlockOrderAndPositions() {
-  block_order_ = StructuredTraverser(function_).ReverseStructuredPostOrder();
-
-  for (uint32_t i = 0; i < block_order_.size(); ++i) {
-    GetBlockInfo(block_order_[i])->pos = i;
-  }
-  // The invalid block position is not the position of any block that is in the
-  // order.
-  assert(block_order_.size() <= kInvalidBlockPos);
-}
-
-bool FunctionEmitter::VerifyHeaderContinueMergeOrder() {
-  // Verify interval rules for a structured header block:
-  //
-  //    If the CFG satisfies structured control flow rules, then:
-  //    If header H is reachable, then the following "interval rules" hold,
-  //    where M(H) is H's merge block, and CT(H) is H's continue target:
-  //
-  //      Pos(H) < Pos(M(H))
-  //
-  //      If CT(H) exists, then:
-  //         Pos(H) <= Pos(CT(H))
-  //         Pos(CT(H)) < Pos(M)
-  //
-  for (auto block_id : block_order_) {
-    const auto* block_info = GetBlockInfo(block_id);
-    const auto merge = block_info->merge_for_header;
-    if (merge == 0) {
-      continue;
-    }
-    // This is a header.
-    const auto header = block_id;
-    const auto* header_info = block_info;
-    const auto header_pos = header_info->pos;
-    const auto merge_pos = GetBlockInfo(merge)->pos;
-
-    // Pos(H) < Pos(M(H))
-    // Note: When recording merges we made sure H != M(H)
-    if (merge_pos <= header_pos) {
-      return Fail() << "Header " << header
-                    << " does not strictly dominate its merge block " << merge;
-      // TODO(dneto): Report a path from the entry block to the merge block
-      // without going through the header block.
-    }
-
-    const auto ct = block_info->continue_for_header;
-    if (ct == 0) {
-      continue;
-    }
-    // Furthermore, this is a loop header.
-    const auto* ct_info = GetBlockInfo(ct);
-    const auto ct_pos = ct_info->pos;
-    // Pos(H) <= Pos(CT(H))
-    if (ct_pos < header_pos) {
-      Fail() << "Loop header " << header
-             << " does not dominate its continue target " << ct;
-    }
-    // Pos(CT(H)) < Pos(M(H))
-    // Note: When recording merges we made sure CT(H) != M(H)
-    if (merge_pos <= ct_pos) {
-      return Fail() << "Merge block " << merge << " for loop headed at block "
-                    << header
-                    << " appears at or before the loop's continue "
-                       "construct headed by "
-                       "block "
-                    << ct;
-    }
-  }
-  return success();
-}
-
-bool FunctionEmitter::LabelControlFlowConstructs() {
-  // Label each block in the block order with its nearest enclosing structured
-  // control flow construct. Populates the |construct| member of BlockInfo.
-
-  //  Keep a stack of enclosing structured control flow constructs.  Start
-  //  with the synthetic construct representing the entire function.
-  //
-  //  Scan from left to right in the block order, and check conditions
-  //  on each block in the following order:
-  //
-  //        a. When you reach a merge block, the top of the stack should
-  //           be the associated header. Pop it off.
-  //        b. When you reach a header, push it on the stack.
-  //        c. When you reach a continue target, push it on the stack.
-  //           (A block can be both a header and a continue target.)
-  //        c. When you reach a block with an edge branching backward (in the
-  //           structured order) to block T:
-  //            T should be a loop header, and the top of the stack should be a
-  //            continue target associated with T.
-  //            This is the end of the continue construct. Pop the continue
-  //            target off the stack.
-  //
-  //       Note: A loop header can declare itself as its own continue target.
-  //
-  //       Note: For a single-block loop, that block is a header, its own
-  //       continue target, and its own backedge block.
-  //
-  //       Note: We pop the merge off first because a merge block that marks
-  //       the end of one construct can be a single-block loop.  So that block
-  //       is a merge, a header, a continue target, and a backedge block.
-  //       But we want to finish processing of the merge before dealing with
-  //       the loop.
-  //
-  //      In the same scan, mark each basic block with the nearest enclosing
-  //      header: the most recent header for which we haven't reached its merge
-  //      block. Also mark the the most recent continue target for which we
-  //      haven't reached the backedge block.
-
-  TINT_ASSERT(Reader, block_order_.size() > 0);
-  constructs_.clear();
-  const auto entry_id = block_order_[0];
-
-  // The stack of enclosing constructs.
-  std::vector<Construct*> enclosing;
-
-  // Creates a control flow construct and pushes it onto the stack.
-  // Its parent is the top of the stack, or nullptr if the stack is empty.
-  // Returns the newly created construct.
-  auto push_construct = [this, &enclosing](size_t depth, Construct::Kind k,
-                                           uint32_t begin_id,
-                                           uint32_t end_id) -> Construct* {
-    const auto begin_pos = GetBlockInfo(begin_id)->pos;
-    const auto end_pos =
-        end_id == 0 ? uint32_t(block_order_.size()) : GetBlockInfo(end_id)->pos;
-    const auto* parent = enclosing.empty() ? nullptr : enclosing.back();
-    auto scope_end_pos = end_pos;
-    // A loop construct is added right after its associated continue construct.
-    // In that case, adjust the parent up.
-    if (k == Construct::kLoop) {
-      TINT_ASSERT(Reader, parent);
-      TINT_ASSERT(Reader, parent->kind == Construct::kContinue);
-      scope_end_pos = parent->end_pos;
-      parent = parent->parent;
-    }
-    constructs_.push_back(std::make_unique<Construct>(
-        parent, static_cast<int>(depth), k, begin_id, end_id, begin_pos,
-        end_pos, scope_end_pos));
-    Construct* result = constructs_.back().get();
-    enclosing.push_back(result);
-    return result;
-  };
-
-  // Make a synthetic kFunction construct to enclose all blocks in the function.
-  push_construct(0, Construct::kFunction, entry_id, 0);
-  // The entry block can be a selection construct, so be sure to process
-  // it anyway.
-
-  for (uint32_t i = 0; i < block_order_.size(); ++i) {
-    const auto block_id = block_order_[i];
-    TINT_ASSERT(Reader, block_id > 0);
-    auto* block_info = GetBlockInfo(block_id);
-    TINT_ASSERT(Reader, block_info);
-
-    if (enclosing.empty()) {
-      return Fail() << "internal error: too many merge blocks before block "
-                    << block_id;
-    }
-    const Construct* top = enclosing.back();
-
-    while (block_id == top->end_id) {
-      // We've reached a predeclared end of the construct.  Pop it off the
-      // stack.
-      enclosing.pop_back();
-      if (enclosing.empty()) {
-        return Fail() << "internal error: too many merge blocks before block "
-                      << block_id;
-      }
-      top = enclosing.back();
-    }
-
-    const auto merge = block_info->merge_for_header;
-    if (merge != 0) {
-      // The current block is a header.
-      const auto header = block_id;
-      const auto* header_info = block_info;
-      const auto depth = 1 + top->depth;
-      const auto ct = header_info->continue_for_header;
-      if (ct != 0) {
-        // The current block is a loop header.
-        // We should see the continue construct after the loop construct, so
-        // push the loop construct last.
-
-        // From the interval rule, the continue construct consists of blocks
-        // in the block order, starting at the continue target, until just
-        // before the merge block.
-        top = push_construct(depth, Construct::kContinue, ct, merge);
-        // A loop header that is its own continue target will have an
-        // empty loop construct. Only create a loop construct when
-        // the continue target is *not* the same as the loop header.
-        if (header != ct) {
-          // From the interval rule, the loop construct consists of blocks
-          // in the block order, starting at the header, until just
-          // before the continue target.
-          top = push_construct(depth, Construct::kLoop, header, ct);
-
-          // If the loop header branches to two different blocks inside the loop
-          // construct, then the loop body should be modeled as an if-selection
-          // construct
-          std::vector<uint32_t> targets;
-          header_info->basic_block->ForEachSuccessorLabel(
-              [&targets](const uint32_t target) { targets.push_back(target); });
-          if ((targets.size() == 2u) && targets[0] != targets[1]) {
-            const auto target0_pos = GetBlockInfo(targets[0])->pos;
-            const auto target1_pos = GetBlockInfo(targets[1])->pos;
-            if (top->ContainsPos(target0_pos) &&
-                top->ContainsPos(target1_pos)) {
-              // Insert a synthetic if-selection
-              top = push_construct(depth + 1, Construct::kIfSelection, header,
-                                   ct);
-            }
-          }
-        }
-      } else {
-        // From the interval rule, the selection construct consists of blocks
-        // in the block order, starting at the header, until just before the
-        // merge block.
-        const auto branch_opcode =
-            header_info->basic_block->terminator()->opcode();
-        const auto kind = (branch_opcode == SpvOpBranchConditional)
-                              ? Construct::kIfSelection
-                              : Construct::kSwitchSelection;
-        top = push_construct(depth, kind, header, merge);
-      }
-    }
-
-    TINT_ASSERT(Reader, top);
-    block_info->construct = top;
-  }
-
-  // At the end of the block list, we should only have the kFunction construct
-  // left.
-  if (enclosing.size() != 1) {
-    return Fail() << "internal error: unbalanced structured constructs when "
-                     "labeling structured constructs: ended with "
-                  << enclosing.size() - 1 << " unterminated constructs";
-  }
-  const auto* top = enclosing[0];
-  if (top->kind != Construct::kFunction || top->depth != 0) {
-    return Fail() << "internal error: outermost construct is not a function?!";
-  }
-
-  return success();
-}
-
-bool FunctionEmitter::FindSwitchCaseHeaders() {
-  if (failed()) {
-    return false;
-  }
-  for (auto& construct : constructs_) {
-    if (construct->kind != Construct::kSwitchSelection) {
-      continue;
-    }
-    const auto* branch =
-        GetBlockInfo(construct->begin_id)->basic_block->terminator();
-
-    // Mark the default block
-    const auto default_id = branch->GetSingleWordInOperand(1);
-    auto* default_block = GetBlockInfo(default_id);
-    // A default target can't be a backedge.
-    if (construct->begin_pos >= default_block->pos) {
-      // An OpSwitch must dominate its cases.  Also, it can't be a self-loop
-      // as that would be a backedge, and backedges can only target a loop,
-      // and loops use an OpLoopMerge instruction, which can't precede an
-      // OpSwitch.
-      return Fail() << "Switch branch from block " << construct->begin_id
-                    << " to default target block " << default_id
-                    << " can't be a back-edge";
-    }
-    // A default target can be the merge block, but can't go past it.
-    if (construct->end_pos < default_block->pos) {
-      return Fail() << "Switch branch from block " << construct->begin_id
-                    << " to default block " << default_id
-                    << " escapes the selection construct";
-    }
-    if (default_block->default_head_for) {
-      // An OpSwitch must dominate its cases, including the default target.
-      return Fail() << "Block " << default_id
-                    << " is declared as the default target for two OpSwitch "
-                       "instructions, at blocks "
-                    << default_block->default_head_for->begin_id << " and "
-                    << construct->begin_id;
-    }
-    if ((default_block->header_for_merge != 0) &&
-        (default_block->header_for_merge != construct->begin_id)) {
-      // The switch instruction for this default block is an alternate path to
-      // the merge block, and hence the merge block is not dominated by its own
-      // (different) header.
-      return Fail() << "Block " << default_block->id
-                    << " is the default block for switch-selection header "
-                    << construct->begin_id << " and also the merge block for "
-                    << default_block->header_for_merge
-                    << " (violates dominance rule)";
-    }
-
-    default_block->default_head_for = construct.get();
-    default_block->default_is_merge = default_block->pos == construct->end_pos;
-
-    // Map a case target to the list of values selecting that case.
-    std::unordered_map<uint32_t, std::vector<uint64_t>> block_to_values;
-    std::vector<uint32_t> case_targets;
-    std::unordered_set<uint64_t> case_values;
-
-    // Process case targets.
-    for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
-      const auto value = branch->GetInOperand(iarg).AsLiteralUint64();
-      const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
-
-      if (case_values.count(value)) {
-        return Fail() << "Duplicate case value " << value
-                      << " in OpSwitch in block " << construct->begin_id;
-      }
-      case_values.insert(value);
-      if (block_to_values.count(case_target_id) == 0) {
-        case_targets.push_back(case_target_id);
-      }
-      block_to_values[case_target_id].push_back(value);
-    }
-
-    for (uint32_t case_target_id : case_targets) {
-      auto* case_block = GetBlockInfo(case_target_id);
-
-      case_block->case_values = std::make_unique<std::vector<uint64_t>>(
-          std::move(block_to_values[case_target_id]));
-
-      // A case target can't be a back-edge.
-      if (construct->begin_pos >= case_block->pos) {
-        // An OpSwitch must dominate its cases.  Also, it can't be a self-loop
-        // as that would be a backedge, and backedges can only target a loop,
-        // and loops use an OpLoopMerge instruction, which can't preceded an
-        // OpSwitch.
-        return Fail() << "Switch branch from block " << construct->begin_id
-                      << " to case target block " << case_target_id
-                      << " can't be a back-edge";
-      }
-      // A case target can be the merge block, but can't go past it.
-      if (construct->end_pos < case_block->pos) {
-        return Fail() << "Switch branch from block " << construct->begin_id
-                      << " to case target block " << case_target_id
-                      << " escapes the selection construct";
-      }
-      if (case_block->header_for_merge != 0 &&
-          case_block->header_for_merge != construct->begin_id) {
-        // The switch instruction for this case block is an alternate path to
-        // the merge block, and hence the merge block is not dominated by its
-        // own (different) header.
-        return Fail() << "Block " << case_block->id
-                      << " is a case block for switch-selection header "
-                      << construct->begin_id << " and also the merge block for "
-                      << case_block->header_for_merge
-                      << " (violates dominance rule)";
-      }
-
-      // Mark the target as a case target.
-      if (case_block->case_head_for) {
-        // An OpSwitch must dominate its cases.
-        return Fail()
-               << "Block " << case_target_id
-               << " is declared as the switch case target for two OpSwitch "
-                  "instructions, at blocks "
-               << case_block->case_head_for->begin_id << " and "
-               << construct->begin_id;
-      }
-      case_block->case_head_for = construct.get();
-    }
-  }
-  return success();
-}
-
-BlockInfo* FunctionEmitter::HeaderIfBreakable(const Construct* c) {
-  if (c == nullptr) {
-    return nullptr;
-  }
-  switch (c->kind) {
-    case Construct::kLoop:
-    case Construct::kSwitchSelection:
-      return GetBlockInfo(c->begin_id);
-    case Construct::kContinue: {
-      const auto* continue_target = GetBlockInfo(c->begin_id);
-      return GetBlockInfo(continue_target->header_for_continue);
-    }
-    default:
-      break;
-  }
-  return nullptr;
-}
-
-const Construct* FunctionEmitter::SiblingLoopConstruct(
-    const Construct* c) const {
-  if (c == nullptr || c->kind != Construct::kContinue) {
-    return nullptr;
-  }
-  const uint32_t continue_target_id = c->begin_id;
-  const auto* continue_target = GetBlockInfo(continue_target_id);
-  const uint32_t header_id = continue_target->header_for_continue;
-  if (continue_target_id == header_id) {
-    // The continue target is the whole loop.
-    return nullptr;
-  }
-  const auto* candidate = GetBlockInfo(header_id)->construct;
-  // Walk up the construct tree until we hit the loop.  In future
-  // we might handle the corner case where the same block is both a
-  // loop header and a selection header. For example, where the
-  // loop header block has a conditional branch going to distinct
-  // targets inside the loop body.
-  while (candidate && candidate->kind != Construct::kLoop) {
-    candidate = candidate->parent;
-  }
-  return candidate;
-}
-
-bool FunctionEmitter::ClassifyCFGEdges() {
-  if (failed()) {
-    return false;
-  }
-
-  // Checks validity of CFG edges leaving each basic block.  This implicitly
-  // checks dominance rules for headers and continue constructs.
-  //
-  // For each branch encountered, classify each edge (S,T) as:
-  //    - a back-edge
-  //    - a structured exit (specific ways of branching to enclosing construct)
-  //    - a normal (forward) edge, either natural control flow or a case
-  //    fallthrough
-  //
-  // If more than one block is targeted by a normal edge, then S must be a
-  // structured header.
-  //
-  // Term: NEC(B) is the nearest enclosing construct for B.
-  //
-  // If edge (S,T) is a normal edge, and NEC(S) != NEC(T), then
-  //    T is the header block of its NEC(T), and
-  //    NEC(S) is the parent of NEC(T).
-
-  for (const auto src : block_order_) {
-    TINT_ASSERT(Reader, src > 0);
-    auto* src_info = GetBlockInfo(src);
-    TINT_ASSERT(Reader, src_info);
-    const auto src_pos = src_info->pos;
-    const auto& src_construct = *(src_info->construct);
-
-    // Compute the ordered list of unique successors.
-    std::vector<uint32_t> successors;
-    {
-      std::unordered_set<uint32_t> visited;
-      src_info->basic_block->ForEachSuccessorLabel(
-          [&successors, &visited](const uint32_t succ) {
-            if (visited.count(succ) == 0) {
-              successors.push_back(succ);
-              visited.insert(succ);
-            }
-          });
-    }
-
-    // There should only be one backedge per backedge block.
-    uint32_t num_backedges = 0;
-
-    // Track destinations for normal forward edges, either kForward
-    // or kCaseFallThrough. These count toward the need
-    // to have a merge instruction.  We also track kIfBreak edges
-    // because when used with normal forward edges, we'll need
-    // to generate a flow guard variable.
-    std::vector<uint32_t> normal_forward_edges;
-    std::vector<uint32_t> if_break_edges;
-
-    if (successors.empty() && src_construct.enclosing_continue) {
-      // Kill and return are not allowed in a continue construct.
-      return Fail() << "Invalid function exit at block " << src
-                    << " from continue construct starting at "
-                    << src_construct.enclosing_continue->begin_id;
-    }
-
-    for (const auto dest : successors) {
-      const auto* dest_info = GetBlockInfo(dest);
-      // We've already checked terminators are valid.
-      TINT_ASSERT(Reader, dest_info);
-      const auto dest_pos = dest_info->pos;
-
-      // Insert the edge kind entry and keep a handle to update
-      // its classification.
-      EdgeKind& edge_kind = src_info->succ_edge[dest];
-
-      if (src_pos >= dest_pos) {
-        // This is a backedge.
-        edge_kind = EdgeKind::kBack;
-        num_backedges++;
-        const auto* continue_construct = src_construct.enclosing_continue;
-        if (!continue_construct) {
-          return Fail() << "Invalid backedge (" << src << "->" << dest
-                        << "): " << src << " is not in a continue construct";
-        }
-        if (src_pos != continue_construct->end_pos - 1) {
-          return Fail() << "Invalid exit (" << src << "->" << dest
-                        << ") from continue construct: " << src
-                        << " is not the last block in the continue construct "
-                           "starting at "
-                        << src_construct.begin_id
-                        << " (violates post-dominance rule)";
-        }
-        const auto* ct_info = GetBlockInfo(continue_construct->begin_id);
-        TINT_ASSERT(Reader, ct_info);
-        if (ct_info->header_for_continue != dest) {
-          return Fail()
-                 << "Invalid backedge (" << src << "->" << dest
-                 << "): does not branch to the corresponding loop header, "
-                    "expected "
-                 << ct_info->header_for_continue;
-        }
-      } else {
-        // This is a forward edge.
-        // For now, classify it that way, but we might update it.
-        edge_kind = EdgeKind::kForward;
-
-        // Exit from a continue construct can only be from the last block.
-        const auto* continue_construct = src_construct.enclosing_continue;
-        if (continue_construct != nullptr) {
-          if (continue_construct->ContainsPos(src_pos) &&
-              !continue_construct->ContainsPos(dest_pos) &&
-              (src_pos != continue_construct->end_pos - 1)) {
-            return Fail() << "Invalid exit (" << src << "->" << dest
-                          << ") from continue construct: " << src
-                          << " is not the last block in the continue construct "
-                             "starting at "
-                          << continue_construct->begin_id
-                          << " (violates post-dominance rule)";
-          }
-        }
-
-        // Check valid structured exit cases.
-
-        if (edge_kind == EdgeKind::kForward) {
-          // Check for a 'break' from a loop or from a switch.
-          const auto* breakable_header = HeaderIfBreakable(
-              src_construct.enclosing_loop_or_continue_or_switch);
-          if (breakable_header != nullptr) {
-            if (dest == breakable_header->merge_for_header) {
-              // It's a break.
-              edge_kind = (breakable_header->construct->kind ==
-                           Construct::kSwitchSelection)
-                              ? EdgeKind::kSwitchBreak
-                              : EdgeKind::kLoopBreak;
-            }
-          }
-        }
-
-        if (edge_kind == EdgeKind::kForward) {
-          // Check for a 'continue' from within a loop.
-          const auto* loop_header =
-              HeaderIfBreakable(src_construct.enclosing_loop);
-          if (loop_header != nullptr) {
-            if (dest == loop_header->continue_for_header) {
-              // It's a continue.
-              edge_kind = EdgeKind::kLoopContinue;
-            }
-          }
-        }
-
-        if (edge_kind == EdgeKind::kForward) {
-          const auto& header_info = *GetBlockInfo(src_construct.begin_id);
-          if (dest == header_info.merge_for_header) {
-            // Branch to construct's merge block.  The loop break and
-            // switch break cases have already been covered.
-            edge_kind = EdgeKind::kIfBreak;
-          }
-        }
-
-        // A forward edge into a case construct that comes from something
-        // other than the OpSwitch is actually a fallthrough.
-        if (edge_kind == EdgeKind::kForward) {
-          const auto* switch_construct =
-              (dest_info->case_head_for ? dest_info->case_head_for
-                                        : dest_info->default_head_for);
-          if (switch_construct != nullptr) {
-            if (src != switch_construct->begin_id) {
-              edge_kind = EdgeKind::kCaseFallThrough;
-            }
-          }
-        }
-
-        // The edge-kind has been finalized.
-
-        if ((edge_kind == EdgeKind::kForward) ||
-            (edge_kind == EdgeKind::kCaseFallThrough)) {
-          normal_forward_edges.push_back(dest);
-        }
-        if (edge_kind == EdgeKind::kIfBreak) {
-          if_break_edges.push_back(dest);
-        }
-
-        if ((edge_kind == EdgeKind::kForward) ||
-            (edge_kind == EdgeKind::kCaseFallThrough)) {
-          // Check for an invalid forward exit out of this construct.
-          if (dest_info->pos > src_construct.end_pos) {
-            // In most cases we're bypassing the merge block for the source
-            // construct.
-            auto end_block = src_construct.end_id;
-            const char* end_block_desc = "merge block";
-            if (src_construct.kind == Construct::kLoop) {
-              // For a loop construct, we have two valid places to go: the
-              // continue target or the merge for the loop header, which is
-              // further down.
-              const auto loop_merge =
-                  GetBlockInfo(src_construct.begin_id)->merge_for_header;
-              if (dest_info->pos >= GetBlockInfo(loop_merge)->pos) {
-                // We're bypassing the loop's merge block.
-                end_block = loop_merge;
-              } else {
-                // We're bypassing the loop's continue target, and going into
-                // the middle of the continue construct.
-                end_block_desc = "continue target";
-              }
-            }
-            return Fail()
-                   << "Branch from block " << src << " to block " << dest
-                   << " is an invalid exit from construct starting at block "
-                   << src_construct.begin_id << "; branch bypasses "
-                   << end_block_desc << " " << end_block;
-          }
-
-          // Check dominance.
-
-          //      Look for edges that violate the dominance condition: a branch
-          //      from X to Y where:
-          //        If Y is in a nearest enclosing continue construct headed by
-          //        CT:
-          //          Y is not CT, and
-          //          In the structured order, X appears before CT order or
-          //          after CT's backedge block.
-          //        Otherwise, if Y is in a nearest enclosing construct
-          //        headed by H:
-          //          Y is not H, and
-          //          In the structured order, X appears before H or after H's
-          //          merge block.
-
-          const auto& dest_construct = *(dest_info->construct);
-          if (dest != dest_construct.begin_id &&
-              !dest_construct.ContainsPos(src_pos)) {
-            return Fail() << "Branch from " << src << " to " << dest
-                          << " bypasses "
-                          << (dest_construct.kind == Construct::kContinue
-                                  ? "continue target "
-                                  : "header ")
-                          << dest_construct.begin_id
-                          << " (dominance rule violated)";
-          }
-        }
-      }  // end forward edge
-    }    // end successor
-
-    if (num_backedges > 1) {
-      return Fail() << "Block " << src
-                    << " has too many backedges: " << num_backedges;
-    }
-    if ((normal_forward_edges.size() > 1) &&
-        (src_info->merge_for_header == 0)) {
-      return Fail() << "Control flow diverges at block " << src << " (to "
-                    << normal_forward_edges[0] << ", "
-                    << normal_forward_edges[1]
-                    << ") but it is not a structured header (it has no merge "
-                       "instruction)";
-    }
-    if ((normal_forward_edges.size() + if_break_edges.size() > 1) &&
-        (src_info->merge_for_header == 0)) {
-      // There is a branch to the merge of an if-selection combined
-      // with an other normal forward branch.  Control within the
-      // if-selection needs to be gated by a flow predicate.
-      for (auto if_break_dest : if_break_edges) {
-        auto* head_info =
-            GetBlockInfo(GetBlockInfo(if_break_dest)->header_for_merge);
-        // Generate a guard name, but only once.
-        if (head_info->flow_guard_name.empty()) {
-          const std::string guard = "guard" + std::to_string(head_info->id);
-          head_info->flow_guard_name = namer_.MakeDerivedName(guard);
-        }
-      }
-    }
-  }
-
-  return success();
-}
-
-bool FunctionEmitter::FindIfSelectionInternalHeaders() {
-  if (failed()) {
-    return false;
-  }
-  for (auto& construct : constructs_) {
-    if (construct->kind != Construct::kIfSelection) {
-      continue;
-    }
-    auto* if_header_info = GetBlockInfo(construct->begin_id);
-    const auto* branch = if_header_info->basic_block->terminator();
-    const auto true_head = branch->GetSingleWordInOperand(1);
-    const auto false_head = branch->GetSingleWordInOperand(2);
-
-    auto* true_head_info = GetBlockInfo(true_head);
-    auto* false_head_info = GetBlockInfo(false_head);
-    const auto true_head_pos = true_head_info->pos;
-    const auto false_head_pos = false_head_info->pos;
-
-    const bool contains_true = construct->ContainsPos(true_head_pos);
-    const bool contains_false = construct->ContainsPos(false_head_pos);
-
-    // The cases for each edge are:
-    //  - kBack: invalid because it's an invalid exit from the selection
-    //  - kSwitchBreak ; record this for later special processing
-    //  - kLoopBreak ; record this for later special processing
-    //  - kLoopContinue ; record this for later special processing
-    //  - kIfBreak; normal case, may require a guard variable.
-    //  - kFallThrough; invalid exit from the selection
-    //  - kForward; normal case
-
-    if_header_info->true_kind = if_header_info->succ_edge[true_head];
-    if_header_info->false_kind = if_header_info->succ_edge[false_head];
-    if (contains_true) {
-      if_header_info->true_head = true_head;
-    }
-    if (contains_false) {
-      if_header_info->false_head = false_head;
-    }
-
-    if (contains_true && (true_head_info->header_for_merge != 0) &&
-        (true_head_info->header_for_merge != construct->begin_id)) {
-      // The OpBranchConditional instruction for the true head block is an
-      // alternate path to the merge block of a construct nested inside the
-      // selection, and hence the merge block is not dominated by its own
-      // (different) header.
-      return Fail() << "Block " << true_head
-                    << " is the true branch for if-selection header "
-                    << construct->begin_id
-                    << " and also the merge block for header block "
-                    << true_head_info->header_for_merge
-                    << " (violates dominance rule)";
-    }
-    if (contains_false && (false_head_info->header_for_merge != 0) &&
-        (false_head_info->header_for_merge != construct->begin_id)) {
-      // The OpBranchConditional instruction for the false head block is an
-      // alternate path to the merge block of a construct nested inside the
-      // selection, and hence the merge block is not dominated by its own
-      // (different) header.
-      return Fail() << "Block " << false_head
-                    << " is the false branch for if-selection header "
-                    << construct->begin_id
-                    << " and also the merge block for header block "
-                    << false_head_info->header_for_merge
-                    << " (violates dominance rule)";
-    }
-
-    if (contains_true && contains_false && (true_head_pos != false_head_pos)) {
-      // This construct has both a "then" clause and an "else" clause.
-      //
-      // We have this structure:
-      //
-      //   Option 1:
-      //
-      //     * condbranch
-      //        * true-head (start of then-clause)
-      //        ...
-      //        * end-then-clause
-      //        * false-head (start of else-clause)
-      //        ...
-      //        * end-false-clause
-      //        * premerge-head
-      //        ...
-      //     * selection merge
-      //
-      //   Option 2:
-      //
-      //     * condbranch
-      //        * true-head (start of then-clause)
-      //        ...
-      //        * end-then-clause
-      //        * false-head (start of else-clause) and also premerge-head
-      //        ...
-      //        * end-false-clause
-      //     * selection merge
-      //
-      //   Option 3:
-      //
-      //     * condbranch
-      //        * false-head (start of else-clause)
-      //        ...
-      //        * end-else-clause
-      //        * true-head (start of then-clause) and also premerge-head
-      //        ...
-      //        * end-then-clause
-      //     * selection merge
-      //
-      // The premerge-head exists if there is a kForward branch from the end
-      // of the first clause to a block within the surrounding selection.
-      // The first clause might be a then-clause or an else-clause.
-      const auto second_head = std::max(true_head_pos, false_head_pos);
-      const auto end_first_clause_pos = second_head - 1;
-      TINT_ASSERT(Reader, end_first_clause_pos < block_order_.size());
-      const auto end_first_clause = block_order_[end_first_clause_pos];
-      uint32_t premerge_id = 0;
-      uint32_t if_break_id = 0;
-      for (auto& then_succ_iter : GetBlockInfo(end_first_clause)->succ_edge) {
-        const uint32_t dest_id = then_succ_iter.first;
-        const auto edge_kind = then_succ_iter.second;
-        switch (edge_kind) {
-          case EdgeKind::kIfBreak:
-            if_break_id = dest_id;
-            break;
-          case EdgeKind::kForward: {
-            if (construct->ContainsPos(GetBlockInfo(dest_id)->pos)) {
-              // It's a premerge.
-              if (premerge_id != 0) {
-                // TODO(dneto): I think this is impossible to trigger at this
-                // point in the flow. It would require a merge instruction to
-                // get past the check of "at-most-one-forward-edge".
-                return Fail()
-                       << "invalid structure: then-clause headed by block "
-                       << true_head << " ending at block " << end_first_clause
-                       << " has two forward edges to within selection"
-                       << " going to " << premerge_id << " and " << dest_id;
-              }
-              premerge_id = dest_id;
-              auto* dest_block_info = GetBlockInfo(dest_id);
-              if_header_info->premerge_head = dest_id;
-              if (dest_block_info->header_for_merge != 0) {
-                // Premerge has two edges coming into it, from the then-clause
-                // and the else-clause. It's also, by construction, not the
-                // merge block of the if-selection.  So it must not be a merge
-                // block itself. The OpBranchConditional instruction for the
-                // false head block is an alternate path to the merge block, and
-                // hence the merge block is not dominated by its own (different)
-                // header.
-                return Fail()
-                       << "Block " << premerge_id << " is the merge block for "
-                       << dest_block_info->header_for_merge
-                       << " but has alternate paths reaching it, starting from"
-                       << " blocks " << true_head << " and " << false_head
-                       << " which are the true and false branches for the"
-                       << " if-selection header block " << construct->begin_id
-                       << " (violates dominance rule)";
-              }
-            }
-            break;
-          }
-          default:
-            break;
-        }
-      }
-      if (if_break_id != 0 && premerge_id != 0) {
-        return Fail() << "Block " << end_first_clause
-                      << " in if-selection headed at block "
-                      << construct->begin_id
-                      << " branches to both the merge block " << if_break_id
-                      << " and also to block " << premerge_id
-                      << " later in the selection";
-      }
-    }
-  }
-  return success();
-}
-
-bool FunctionEmitter::EmitFunctionVariables() {
-  if (failed()) {
-    return false;
-  }
-  for (auto& inst : *function_.entry()) {
-    if (inst.opcode() != SpvOpVariable) {
-      continue;
-    }
-    auto* var_store_type = GetVariableStoreType(inst);
-    if (failed()) {
-      return false;
-    }
-    const ast::Expression* constructor = nullptr;
-    if (inst.NumInOperands() > 1) {
-      // SPIR-V initializers are always constants.
-      // (OpenCL also allows the ID of an OpVariable, but we don't handle that
-      // here.)
-      constructor =
-          parser_impl_.MakeConstantExpression(inst.GetSingleWordInOperand(1))
-              .expr;
-      if (!constructor) {
-        return false;
-      }
-    }
-    auto* var = parser_impl_.MakeVariable(
-        inst.result_id(), ast::StorageClass::kNone, var_store_type, false,
-        false, constructor, ast::AttributeList{});
-    auto* var_decl_stmt = create<ast::VariableDeclStatement>(Source{}, var);
-    AddStatement(var_decl_stmt);
-    auto* var_type = ty_.Reference(var_store_type, ast::StorageClass::kNone);
-    identifier_types_.emplace(inst.result_id(), var_type);
-  }
-  return success();
-}
-
-TypedExpression FunctionEmitter::AddressOfIfNeeded(
-    TypedExpression expr,
-    const spvtools::opt::Instruction* inst) {
-  if (inst && expr) {
-    if (auto* spirv_type = type_mgr_->GetType(inst->type_id())) {
-      if (expr.type->Is<Reference>() && spirv_type->AsPointer()) {
-        return AddressOf(expr);
-      }
-    }
-  }
-  return expr;
-}
-
-TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
-  if (failed()) {
-    return {};
-  }
-  switch (GetSkipReason(id)) {
-    case SkipReason::kDontSkip:
-      break;
-    case SkipReason::kOpaqueObject:
-      Fail() << "internal error: unhandled use of opaque object with ID: "
-             << id;
-      return {};
-    case SkipReason::kSinkPointerIntoUse: {
-      // Replace the pointer with its source reference expression.
-      auto source_expr = GetDefInfo(id)->sink_pointer_source_expr;
-      TINT_ASSERT(Reader, source_expr.type->Is<Reference>());
-      return source_expr;
-    }
-    case SkipReason::kPointSizeBuiltinValue: {
-      return {ty_.F32(), create<ast::FloatLiteralExpression>(Source{}, 1.0f)};
-    }
-    case SkipReason::kPointSizeBuiltinPointer:
-      Fail() << "unhandled use of a pointer to the PointSize builtin, with ID: "
-             << id;
-      return {};
-    case SkipReason::kSampleMaskInBuiltinPointer:
-      Fail()
-          << "unhandled use of a pointer to the SampleMask builtin, with ID: "
-          << id;
-      return {};
-    case SkipReason::kSampleMaskOutBuiltinPointer: {
-      // The result type is always u32.
-      auto name = namer_.Name(sample_mask_out_id);
-      return TypedExpression{ty_.U32(),
-                             create<ast::IdentifierExpression>(
-                                 Source{}, builder_.Symbols().Register(name))};
-    }
-  }
-  auto type_it = identifier_types_.find(id);
-  if (type_it != identifier_types_.end()) {
-    auto name = namer_.Name(id);
-    auto* type = type_it->second;
-    return TypedExpression{type,
-                           create<ast::IdentifierExpression>(
-                               Source{}, builder_.Symbols().Register(name))};
-  }
-  if (parser_impl_.IsScalarSpecConstant(id)) {
-    auto name = namer_.Name(id);
-    return TypedExpression{
-        parser_impl_.ConvertType(def_use_mgr_->GetDef(id)->type_id()),
-        create<ast::IdentifierExpression>(Source{},
-                                          builder_.Symbols().Register(name))};
-  }
-  if (singly_used_values_.count(id)) {
-    auto expr = std::move(singly_used_values_[id]);
-    singly_used_values_.erase(id);
-    return expr;
-  }
-  const auto* spirv_constant = constant_mgr_->FindDeclaredConstant(id);
-  if (spirv_constant) {
-    return parser_impl_.MakeConstantExpression(id);
-  }
-  const auto* inst = def_use_mgr_->GetDef(id);
-  if (inst == nullptr) {
-    Fail() << "ID " << id << " does not have a defining SPIR-V instruction";
-    return {};
-  }
-  switch (inst->opcode()) {
-    case SpvOpVariable: {
-      // This occurs for module-scope variables.
-      auto name = namer_.Name(inst->result_id());
-      return TypedExpression{
-          parser_impl_.ConvertType(inst->type_id(), PtrAs::Ref),
-          create<ast::IdentifierExpression>(Source{},
-                                            builder_.Symbols().Register(name))};
-    }
-    case SpvOpUndef:
-      // Substitute a null value for undef.
-      // This case occurs when OpUndef appears at module scope, as if it were
-      // a constant.
-      return parser_impl_.MakeNullExpression(
-          parser_impl_.ConvertType(inst->type_id()));
-
-    default:
-      break;
-  }
-  if (const spvtools::opt::BasicBlock* const bb =
-          ir_context_.get_instr_block(id)) {
-    if (auto* block = GetBlockInfo(bb->id())) {
-      if (block->pos == kInvalidBlockPos) {
-        // The value came from a block not in the block order.
-        // Substitute a null value.
-        return parser_impl_.MakeNullExpression(
-            parser_impl_.ConvertType(inst->type_id()));
-      }
-    }
-  }
-  Fail() << "unhandled expression for ID " << id << "\n" << inst->PrettyPrint();
-  return {};
-}
-
-bool FunctionEmitter::EmitFunctionBodyStatements() {
-  // Dump the basic blocks in order, grouped by construct.
-
-  // We maintain a stack of StatementBlock objects, where new statements
-  // are always written to the topmost entry of the stack. By this point in
-  // processing, we have already recorded the interesting control flow
-  // boundaries in the BlockInfo and associated Construct objects. As we
-  // enter a new statement grouping, we push onto the stack, and also schedule
-  // the statement block's completion and removal at a future block's ID.
-
-  // Upon entry, the statement stack has one entry representing the whole
-  // function.
-  TINT_ASSERT(Reader, !constructs_.empty());
-  Construct* function_construct = constructs_[0].get();
-  TINT_ASSERT(Reader, function_construct != nullptr);
-  TINT_ASSERT(Reader, function_construct->kind == Construct::kFunction);
-  // Make the first entry valid by filling in the construct field, which
-  // had not been computed at the time the entry was first created.
-  // TODO(dneto): refactor how the first construct is created vs.
-  // this statements stack entry is populated.
-  TINT_ASSERT(Reader, statements_stack_.size() == 1);
-  statements_stack_[0].SetConstruct(function_construct);
-
-  for (auto block_id : block_order()) {
-    if (!EmitBasicBlock(*GetBlockInfo(block_id))) {
-      return false;
-    }
-  }
-  return success();
-}
-
-bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
-  // Close off previous constructs.
-  while (!statements_stack_.empty() &&
-         (statements_stack_.back().GetEndId() == block_info.id)) {
-    statements_stack_.back().Finalize(&builder_);
-    statements_stack_.pop_back();
-  }
-  if (statements_stack_.empty()) {
-    return Fail() << "internal error: statements stack empty at block "
-                  << block_info.id;
-  }
-
-  // Enter new constructs.
-
-  std::vector<const Construct*> entering_constructs;  // inner most comes first
-  {
-    auto* here = block_info.construct;
-    auto* const top_construct = statements_stack_.back().GetConstruct();
-    while (here != top_construct) {
-      // Only enter a construct at its header block.
-      if (here->begin_id == block_info.id) {
-        entering_constructs.push_back(here);
-      }
-      here = here->parent;
-    }
-  }
-  // What constructs can we have entered?
-  // - It can't be kFunction, because there is only one of those, and it was
-  //   already on the stack at the outermost level.
-  // - We have at most one of kSwitchSelection, or kLoop because each of those
-  //   is headed by a block with a merge instruction (OpLoopMerge for kLoop,
-  //   and OpSelectionMerge for kSwitchSelection).
-  // - When there is a kIfSelection, it can't contain another construct,
-  //   because both would have to have their own distinct merge instructions
-  //   and distinct terminators.
-  // - A kContinue can contain a kContinue
-  //   This is possible in Vulkan SPIR-V, but Tint disallows this by the rule
-  //   that a block can be continue target for at most one header block. See
-  //   test DISABLED_BlockIsContinueForMoreThanOneHeader. If we generalize this,
-  //   then by a dominance argument, the inner loop continue target can only be
-  //   a single-block loop.
-  // TODO(dneto): Handle this case.
-  // - If a kLoop is on the outside, its terminator is either:
-  //   - an OpBranch, in which case there is no other construct.
-  //   - an OpBranchConditional, in which case there is either an kIfSelection
-  //     (when both branch targets are different and are inside the loop),
-  //     or no other construct (because the branch targets are the same,
-  //     or one of them is a break or continue).
-  // - All that's left is a kContinue on the outside, and one of
-  //   kIfSelection, kSwitchSelection, kLoop on the inside.
-  //
-  //   The kContinue can be the parent of the other.  For example, a selection
-  //   starting at the first block of a continue construct.
-  //
-  //   The kContinue can't be the child of the other because either:
-  //     - The other can't be kLoop because:
-  //        - If the kLoop is for a different loop then the kContinue, then
-  //          the kContinue must be its own loop header, and so the same
-  //          block is two different loops. That's a contradiction.
-  //        - If the kLoop is for a the same loop, then this is a contradiction
-  //          because a kContinue and its kLoop have disjoint block sets.
-  //     - The other construct can't be a selection because:
-  //       - The kContinue construct is the entire loop, i.e. the continue
-  //         target is its own loop header block.  But then the continue target
-  //         has an OpLoopMerge instruction, which contradicts this block being
-  //         a selection header.
-  //       - The kContinue is in a multi-block loop that is has a non-empty
-  //         kLoop; and the selection contains the kContinue block but not the
-  //         loop block. That breaks dominance rules. That is, the continue
-  //         target is dominated by that loop header, and so gets found by the
-  //         block traversal on the outside before the selection is found. The
-  //         selection is inside the outer loop.
-  //
-  // So we fall into one of the following cases:
-  //  - We are entering 0 or 1 constructs, or
-  //  - We are entering 2 constructs, with the outer one being a kContinue or
-  //    kLoop, the inner one is not a continue.
-  if (entering_constructs.size() > 2) {
-    return Fail() << "internal error: bad construct nesting found";
-  }
-  if (entering_constructs.size() == 2) {
-    auto inner_kind = entering_constructs[0]->kind;
-    auto outer_kind = entering_constructs[1]->kind;
-    if (outer_kind != Construct::kContinue && outer_kind != Construct::kLoop) {
-      return Fail()
-             << "internal error: bad construct nesting. Only a Continue "
-                "or a Loop construct can be outer construct on same block.  "
-                "Got outer kind "
-             << int(outer_kind) << " inner kind " << int(inner_kind);
-    }
-    if (inner_kind == Construct::kContinue) {
-      return Fail() << "internal error: unsupported construct nesting: "
-                       "Continue around Continue";
-    }
-    if (inner_kind != Construct::kIfSelection &&
-        inner_kind != Construct::kSwitchSelection &&
-        inner_kind != Construct::kLoop) {
-      return Fail() << "internal error: bad construct nesting. Continue around "
-                       "something other than if, switch, or loop";
-    }
-  }
-
-  // Enter constructs from outermost to innermost.
-  // kLoop and kContinue push a new statement-block onto the stack before
-  // emitting statements in the block.
-  // kIfSelection and kSwitchSelection emit statements in the block and then
-  // emit push a new statement-block. Only emit the statements in the block
-  // once.
-
-  // Have we emitted the statements for this block?
-  bool emitted = false;
-
-  // When entering an if-selection or switch-selection, we will emit the WGSL
-  // construct to cause the divergent branching.  But otherwise, we will
-  // emit a "normal" block terminator, which occurs at the end of this method.
-  bool has_normal_terminator = true;
-
-  for (auto iter = entering_constructs.rbegin();
-       iter != entering_constructs.rend(); ++iter) {
-    const Construct* construct = *iter;
-
-    switch (construct->kind) {
-      case Construct::kFunction:
-        return Fail() << "internal error: nested function construct";
-
-      case Construct::kLoop:
-        if (!EmitLoopStart(construct)) {
-          return false;
-        }
-        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
-          return false;
-        }
-        break;
-
-      case Construct::kContinue:
-        if (block_info.is_continue_entire_loop) {
-          if (!EmitLoopStart(construct)) {
-            return false;
-          }
-          if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
-            return false;
-          }
-        } else {
-          if (!EmitContinuingStart(construct)) {
-            return false;
-          }
-        }
-        break;
-
-      case Construct::kIfSelection:
-        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
-          return false;
-        }
-        if (!EmitIfStart(block_info)) {
-          return false;
-        }
-        has_normal_terminator = false;
-        break;
-
-      case Construct::kSwitchSelection:
-        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
-          return false;
-        }
-        if (!EmitSwitchStart(block_info)) {
-          return false;
-        }
-        has_normal_terminator = false;
-        break;
-    }
-  }
-
-  // If we aren't starting or transitioning, then emit the normal
-  // statements now.
-  if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
-    return false;
-  }
-
-  if (has_normal_terminator) {
-    if (!EmitNormalTerminator(block_info)) {
-      return false;
-    }
-  }
-  return success();
-}
-
-bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
-  // The block is the if-header block.  So its construct is the if construct.
-  auto* construct = block_info.construct;
-  TINT_ASSERT(Reader, construct->kind == Construct::kIfSelection);
-  TINT_ASSERT(Reader, construct->begin_id == block_info.id);
-
-  const uint32_t true_head = block_info.true_head;
-  const uint32_t false_head = block_info.false_head;
-  const uint32_t premerge_head = block_info.premerge_head;
-
-  const std::string guard_name = block_info.flow_guard_name;
-  if (!guard_name.empty()) {
-    // Declare the guard variable just before the "if", initialized to true.
-    auto* guard_var =
-        builder_.Var(guard_name, builder_.ty.bool_(), MakeTrue(Source{}));
-    auto* guard_decl = create<ast::VariableDeclStatement>(Source{}, guard_var);
-    AddStatement(guard_decl);
-  }
-
-  const auto condition_id =
-      block_info.basic_block->terminator()->GetSingleWordInOperand(0);
-  auto* cond = MakeExpression(condition_id).expr;
-  if (!cond) {
-    return false;
-  }
-  // Generate the code for the condition.
-  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
-
-  // Compute the block IDs that should end the then-clause and the else-clause.
-
-  // We need to know where the *emitted* selection should end, i.e. the intended
-  // merge block id.  That should be the current premerge block, if it exists,
-  // or otherwise the declared merge block.
-  //
-  // This is another way to think about it:
-  //   If there is a premerge, then there are three cases:
-  //    - premerge_head is different from the true_head and false_head:
-  //      - Premerge comes last. In effect, move the selection merge up
-  //        to where the premerge begins.
-  //    - premerge_head is the same as the false_head
-  //      - This is really an if-then without an else clause.
-  //        Move the merge up to where the premerge is.
-  //    - premerge_head is the same as the true_head
-  //      - This is really an if-else without an then clause.
-  //        Emit it as:   if (cond) {} else {....}
-  //        Move the merge up to where the premerge is.
-  const uint32_t intended_merge =
-      premerge_head ? premerge_head : construct->end_id;
-
-  // then-clause:
-  //   If true_head exists:
-  //     spans from true head to the earlier of the false head (if it exists)
-  //     or the selection merge.
-  //   Otherwise:
-  //     ends at from the false head (if it exists), otherwise the selection
-  //     end.
-  const uint32_t then_end = false_head ? false_head : intended_merge;
-
-  // else-clause:
-  //   ends at the premerge head (if it exists) or at the selection end.
-  const uint32_t else_end = premerge_head ? premerge_head : intended_merge;
-
-  const bool true_is_break = (block_info.true_kind == EdgeKind::kSwitchBreak) ||
-                             (block_info.true_kind == EdgeKind::kLoopBreak);
-  const bool false_is_break =
-      (block_info.false_kind == EdgeKind::kSwitchBreak) ||
-      (block_info.false_kind == EdgeKind::kLoopBreak);
-  const bool true_is_continue = block_info.true_kind == EdgeKind::kLoopContinue;
-  const bool false_is_continue =
-      block_info.false_kind == EdgeKind::kLoopContinue;
-
-  // Push statement blocks for the then-clause and the else-clause.
-  // But make sure we do it in the right order.
-  auto push_else = [this, builder, else_end, construct, false_is_break,
-                    false_is_continue]() {
-    // Push the else clause onto the stack first.
-    PushNewStatementBlock(
-        construct, else_end, [=](const ast::StatementList& stmts) {
-          // Only set the else-clause if there are statements to fill it.
-          if (!stmts.empty()) {
-            // The "else" consists of the statement list from the top of
-            // statements stack, without an elseif condition.
-            auto* else_body = create<ast::BlockStatement>(Source{}, stmts);
-            builder->else_stmts.emplace_back(
-                create<ast::ElseStatement>(Source{}, nullptr, else_body));
-          }
-        });
-    if (false_is_break) {
-      AddStatement(create<ast::BreakStatement>(Source{}));
-    }
-    if (false_is_continue) {
-      AddStatement(create<ast::ContinueStatement>(Source{}));
-    }
-  };
-
-  if (!true_is_break && !true_is_continue &&
-      (GetBlockInfo(else_end)->pos < GetBlockInfo(then_end)->pos)) {
-    // Process the else-clause first.  The then-clause will be empty so avoid
-    // pushing onto the stack at all.
-    push_else();
-  } else {
-    // Blocks for the then-clause appear before blocks for the else-clause.
-    // So push the else-clause handling onto the stack first. The else-clause
-    // might be empty, but this works anyway.
-
-    // Handle the premerge, if it exists.
-    if (premerge_head) {
-      // The top of the stack is the statement block that is the parent of the
-      // if-statement. Adding statements now will place them after that 'if'.
-      if (guard_name.empty()) {
-        // We won't have a flow guard for the premerge.
-        // Insert a trivial if(true) { ... } around the blocks from the
-        // premerge head until the end of the if-selection.  This is needed
-        // to ensure uniform reconvergence occurs at the end of the if-selection
-        // just like in the original SPIR-V.
-        PushTrueGuard(construct->end_id);
-      } else {
-        // Add a flow guard around the blocks in the premerge area.
-        PushGuard(guard_name, construct->end_id);
-      }
-    }
-
-    push_else();
-    if (true_head && false_head && !guard_name.empty()) {
-      // There are non-trivial then and else clauses.
-      // We have to guard the start of the else.
-      PushGuard(guard_name, else_end);
-    }
-
-    // Push the then clause onto the stack.
-    PushNewStatementBlock(
-        construct, then_end, [=](const ast::StatementList& stmts) {
-          builder->body = create<ast::BlockStatement>(Source{}, stmts);
-        });
-    if (true_is_break) {
-      AddStatement(create<ast::BreakStatement>(Source{}));
-    }
-    if (true_is_continue) {
-      AddStatement(create<ast::ContinueStatement>(Source{}));
-    }
-  }
-
-  return success();
-}
-
-bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
-  // The block is the if-header block.  So its construct is the if construct.
-  auto* construct = block_info.construct;
-  TINT_ASSERT(Reader, construct->kind == Construct::kSwitchSelection);
-  TINT_ASSERT(Reader, construct->begin_id == block_info.id);
-  const auto* branch = block_info.basic_block->terminator();
-
-  const auto selector_id = branch->GetSingleWordInOperand(0);
-  // Generate the code for the selector.
-  auto selector = MakeExpression(selector_id);
-  if (!selector) {
-    return false;
-  }
-  // First, push the statement block for the entire switch.
-  auto* swch = AddStatementBuilder<SwitchStatementBuilder>(selector.expr);
-
-  // Grab a pointer to the case list.  It will get buried in the statement block
-  // stack.
-  PushNewStatementBlock(construct, construct->end_id, nullptr);
-
-  // We will push statement-blocks onto the stack to gather the statements in
-  // the default clause and cases clauses. Determine the list of blocks
-  // that start each clause.
-  std::vector<const BlockInfo*> clause_heads;
-
-  // Collect the case clauses, even if they are just the merge block.
-  // First the default clause.
-  const auto default_id = branch->GetSingleWordInOperand(1);
-  const auto* default_info = GetBlockInfo(default_id);
-  clause_heads.push_back(default_info);
-  // Now the case clauses.
-  for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
-    const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
-    clause_heads.push_back(GetBlockInfo(case_target_id));
-  }
-
-  std::stable_sort(clause_heads.begin(), clause_heads.end(),
-                   [](const BlockInfo* lhs, const BlockInfo* rhs) {
-                     return lhs->pos < rhs->pos;
-                   });
-  // Remove duplicates
-  {
-    // Use read index r, and write index w.
-    // Invariant: w <= r;
-    size_t w = 0;
-    for (size_t r = 0; r < clause_heads.size(); ++r) {
-      if (clause_heads[r] != clause_heads[w]) {
-        ++w;  // Advance the write cursor.
-      }
-      clause_heads[w] = clause_heads[r];
-    }
-    // We know it's not empty because it always has at least a default clause.
-    TINT_ASSERT(Reader, !clause_heads.empty());
-    clause_heads.resize(w + 1);
-  }
-
-  // Push them on in reverse order.
-  const auto last_clause_index = clause_heads.size() - 1;
-  for (size_t i = last_clause_index;; --i) {
-    // Create a list of integer literals for the selector values leading to
-    // this case clause.
-    ast::CaseSelectorList selectors;
-    const auto* values_ptr = clause_heads[i]->case_values.get();
-    const bool has_selectors = (values_ptr && !values_ptr->empty());
-    if (has_selectors) {
-      std::vector<uint64_t> values(values_ptr->begin(), values_ptr->end());
-      std::stable_sort(values.begin(), values.end());
-      for (auto value : values) {
-        // The rest of this module can handle up to 64 bit switch values.
-        // The Tint AST handles 32-bit values.
-        const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
-        if (selector.type->IsUnsignedScalarOrVector()) {
-          selectors.emplace_back(
-              create<ast::UintLiteralExpression>(Source{}, value32));
-        } else {
-          selectors.emplace_back(
-              create<ast::SintLiteralExpression>(Source{}, value32));
-        }
-      }
-    }
-
-    // Where does this clause end?
-    const auto end_id = (i + 1 < clause_heads.size()) ? clause_heads[i + 1]->id
-                                                      : construct->end_id;
-
-    // Reserve the case clause slot in swch->cases, push the new statement block
-    // for the case, and fill the case clause once the block is generated.
-    auto case_idx = swch->cases.size();
-    swch->cases.emplace_back(nullptr);
-    PushNewStatementBlock(
-        construct, end_id, [=](const ast::StatementList& stmts) {
-          auto* body = create<ast::BlockStatement>(Source{}, stmts);
-          swch->cases[case_idx] =
-              create<ast::CaseStatement>(Source{}, selectors, body);
-        });
-
-    if ((default_info == clause_heads[i]) && has_selectors &&
-        construct->ContainsPos(default_info->pos)) {
-      // Generate a default clause with a just fallthrough.
-      auto* stmts = create<ast::BlockStatement>(
-          Source{}, ast::StatementList{
-                        create<ast::FallthroughStatement>(Source{}),
-                    });
-      auto* case_stmt =
-          create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{}, stmts);
-      swch->cases.emplace_back(case_stmt);
-    }
-
-    if (i == 0) {
-      break;
-    }
-  }
-
-  return success();
-}
-
-bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
-  auto* builder = AddStatementBuilder<LoopStatementBuilder>();
-  PushNewStatementBlock(
-      construct, construct->end_id, [=](const ast::StatementList& stmts) {
-        builder->body = create<ast::BlockStatement>(Source{}, stmts);
-      });
-  return success();
-}
-
-bool FunctionEmitter::EmitContinuingStart(const Construct* construct) {
-  // A continue construct has the same depth as its associated loop
-  // construct. Start a continue construct.
-  auto* loop_candidate = LastStatement();
-  auto* loop = loop_candidate->As<LoopStatementBuilder>();
-  if (loop == nullptr) {
-    return Fail() << "internal error: starting continue construct, "
-                     "expected loop on top of stack";
-  }
-  PushNewStatementBlock(
-      construct, construct->end_id, [=](const ast::StatementList& stmts) {
-        loop->continuing = create<ast::BlockStatement>(Source{}, stmts);
-      });
-
-  return success();
-}
-
-bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
-  const auto& terminator = *(block_info.basic_block->terminator());
-  switch (terminator.opcode()) {
-    case SpvOpReturn:
-      AddStatement(create<ast::ReturnStatement>(Source{}));
-      return true;
-    case SpvOpReturnValue: {
-      auto value = MakeExpression(terminator.GetSingleWordInOperand(0));
-      if (!value) {
-        return false;
-      }
-      AddStatement(create<ast::ReturnStatement>(Source{}, value.expr));
-    }
-      return true;
-    case SpvOpKill:
-      // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
-      // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
-      AddStatement(create<ast::DiscardStatement>(Source{}));
-      return true;
-    case SpvOpUnreachable:
-      // Translate as if it's a return. This avoids the problem where WGSL
-      // requires a return statement at the end of the function body.
-      {
-        const auto* result_type = type_mgr_->GetType(function_.type_id());
-        if (result_type->AsVoid() != nullptr) {
-          AddStatement(create<ast::ReturnStatement>(Source{}));
-        } else {
-          auto* ast_type = parser_impl_.ConvertType(function_.type_id());
-          AddStatement(create<ast::ReturnStatement>(
-              Source{}, parser_impl_.MakeNullValue(ast_type)));
-        }
-      }
-      return true;
-    case SpvOpBranch: {
-      const auto dest_id = terminator.GetSingleWordInOperand(0);
-      AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
-      return true;
-    }
-    case SpvOpBranchConditional: {
-      // If both destinations are the same, then do the same as we would
-      // for an unconditional branch (OpBranch).
-      const auto true_dest = terminator.GetSingleWordInOperand(1);
-      const auto false_dest = terminator.GetSingleWordInOperand(2);
-      if (true_dest == false_dest) {
-        // This is like an unconditional branch.
-        AddStatement(MakeBranch(block_info, *GetBlockInfo(true_dest)));
-        return true;
-      }
-
-      const EdgeKind true_kind = block_info.succ_edge.find(true_dest)->second;
-      const EdgeKind false_kind = block_info.succ_edge.find(false_dest)->second;
-      auto* const true_info = GetBlockInfo(true_dest);
-      auto* const false_info = GetBlockInfo(false_dest);
-      auto* cond = MakeExpression(terminator.GetSingleWordInOperand(0)).expr;
-      if (!cond) {
-        return false;
-      }
-
-      // We have two distinct destinations. But we only get here if this
-      // is a normal terminator; in particular the source block is *not* the
-      // start of an if-selection or a switch-selection.  So at most one branch
-      // is a kForward, kCaseFallThrough, or kIfBreak.
-
-      // The fallthrough case is special because WGSL requires the fallthrough
-      // statement to be last in the case clause.
-      if (true_kind == EdgeKind::kCaseFallThrough) {
-        return EmitConditionalCaseFallThrough(block_info, cond, false_kind,
-                                              *false_info, true);
-      } else if (false_kind == EdgeKind::kCaseFallThrough) {
-        return EmitConditionalCaseFallThrough(block_info, cond, true_kind,
-                                              *true_info, false);
-      }
-
-      // At this point, at most one edge is kForward or kIfBreak.
-
-      // Emit an 'if' statement to express the *other* branch as a conditional
-      // break or continue.  Either or both of these could be nullptr.
-      // (A nullptr is generated for kIfBreak, kForward, or kBack.)
-      // Also if one of the branches is an if-break out of an if-selection
-      // requiring a flow guard, then get that flow guard name too.  It will
-      // come from at most one of these two branches.
-      std::string flow_guard;
-      auto* true_branch =
-          MakeBranchDetailed(block_info, *true_info, false, &flow_guard);
-      auto* false_branch =
-          MakeBranchDetailed(block_info, *false_info, false, &flow_guard);
-
-      AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
-      if (!flow_guard.empty()) {
-        PushGuard(flow_guard, statements_stack_.back().GetEndId());
-      }
-      return true;
-    }
-    case SpvOpSwitch:
-      // An OpSelectionMerge must precede an OpSwitch.  That is clarified
-      // in the resolution to Khronos-internal SPIR-V issue 115.
-      // A new enough version of the SPIR-V validator checks this case.
-      // But issue an error in this case, as a defensive measure.
-      return Fail() << "invalid structured control flow: found an OpSwitch "
-                       "that is not preceded by an "
-                       "OpSelectionMerge: "
-                    << terminator.PrettyPrint();
-    default:
-      break;
-  }
-  return success();
-}
-
-const ast::Statement* FunctionEmitter::MakeBranchDetailed(
-    const BlockInfo& src_info,
-    const BlockInfo& dest_info,
-    bool forced,
-    std::string* flow_guard_name_ptr) const {
-  auto kind = src_info.succ_edge.find(dest_info.id)->second;
-  switch (kind) {
-    case EdgeKind::kBack:
-      // Nothing to do. The loop backedge is implicit.
-      break;
-    case EdgeKind::kSwitchBreak: {
-      if (forced) {
-        return create<ast::BreakStatement>(Source{});
-      }
-      // Unless forced, don't bother with a break at the end of a case/default
-      // clause.
-      const auto header = dest_info.header_for_merge;
-      TINT_ASSERT(Reader, header != 0);
-      const auto* exiting_construct = GetBlockInfo(header)->construct;
-      TINT_ASSERT(Reader,
-                  exiting_construct->kind == Construct::kSwitchSelection);
-      const auto candidate_next_case_pos = src_info.pos + 1;
-      // Leaving the last block from the last case?
-      if (candidate_next_case_pos == dest_info.pos) {
-        // No break needed.
-        return nullptr;
-      }
-      // Leaving the last block from not-the-last-case?
-      if (exiting_construct->ContainsPos(candidate_next_case_pos)) {
-        const auto* candidate_next_case =
-            GetBlockInfo(block_order_[candidate_next_case_pos]);
-        if (candidate_next_case->case_head_for == exiting_construct ||
-            candidate_next_case->default_head_for == exiting_construct) {
-          // No break needed.
-          return nullptr;
-        }
-      }
-      // We need a break.
-      return create<ast::BreakStatement>(Source{});
-    }
-    case EdgeKind::kLoopBreak:
-      return create<ast::BreakStatement>(Source{});
-    case EdgeKind::kLoopContinue:
-      // An unconditional continue to the next block is redundant and ugly.
-      // Skip it in that case.
-      if (dest_info.pos == 1 + src_info.pos) {
-        break;
-      }
-      // Otherwise, emit a regular continue statement.
-      return create<ast::ContinueStatement>(Source{});
-    case EdgeKind::kIfBreak: {
-      const auto& flow_guard =
-          GetBlockInfo(dest_info.header_for_merge)->flow_guard_name;
-      if (!flow_guard.empty()) {
-        if (flow_guard_name_ptr != nullptr) {
-          *flow_guard_name_ptr = flow_guard;
-        }
-        // Signal an exit from the branch.
-        return create<ast::AssignmentStatement>(
-            Source{},
-            create<ast::IdentifierExpression>(
-                Source{}, builder_.Symbols().Register(flow_guard)),
-            MakeFalse(Source{}));
-      }
-
-      // For an unconditional branch, the break out to an if-selection
-      // merge block is implicit.
-      break;
-    }
-    case EdgeKind::kCaseFallThrough:
-      return create<ast::FallthroughStatement>(Source{});
-    case EdgeKind::kForward:
-      // Unconditional forward branch is implicit.
-      break;
-  }
-  return nullptr;
-}
-
-const ast::Statement* FunctionEmitter::MakeSimpleIf(
-    const ast::Expression* condition,
-    const ast::Statement* then_stmt,
-    const ast::Statement* else_stmt) const {
-  if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
-    return nullptr;
-  }
-  ast::ElseStatementList else_stmts;
-  if (else_stmt != nullptr) {
-    ast::StatementList stmts{else_stmt};
-    else_stmts.emplace_back(create<ast::ElseStatement>(
-        Source{}, nullptr, create<ast::BlockStatement>(Source{}, stmts)));
-  }
-  ast::StatementList if_stmts;
-  if (then_stmt != nullptr) {
-    if_stmts.emplace_back(then_stmt);
-  }
-  auto* if_block = create<ast::BlockStatement>(Source{}, if_stmts);
-  auto* if_stmt =
-      create<ast::IfStatement>(Source{}, condition, if_block, else_stmts);
-
-  return if_stmt;
-}
-
-bool FunctionEmitter::EmitConditionalCaseFallThrough(
-    const BlockInfo& src_info,
-    const ast::Expression* cond,
-    EdgeKind other_edge_kind,
-    const BlockInfo& other_dest,
-    bool fall_through_is_true_branch) {
-  // In WGSL, the fallthrough statement must come last in the case clause.
-  // So we'll emit an if statement for the other branch, and then emit
-  // the fallthrough.
-
-  // We have two distinct destinations. But we only get here if this
-  // is a normal terminator; in particular the source block is *not* the
-  // start of an if-selection.  So at most one branch is a kForward or
-  // kCaseFallThrough.
-  if (other_edge_kind == EdgeKind::kForward) {
-    return Fail()
-           << "internal error: normal terminator OpBranchConditional has "
-              "both forward and fallthrough edges";
-  }
-  if (other_edge_kind == EdgeKind::kIfBreak) {
-    return Fail()
-           << "internal error: normal terminator OpBranchConditional has "
-              "both IfBreak and fallthrough edges.  Violates nesting rule";
-  }
-  if (other_edge_kind == EdgeKind::kBack) {
-    return Fail()
-           << "internal error: normal terminator OpBranchConditional has "
-              "both backedge and fallthrough edges.  Violates nesting rule";
-  }
-  auto* other_branch = MakeForcedBranch(src_info, other_dest);
-  if (other_branch == nullptr) {
-    return Fail() << "internal error: expected a branch for edge-kind "
-                  << int(other_edge_kind);
-  }
-  if (fall_through_is_true_branch) {
-    AddStatement(MakeSimpleIf(cond, nullptr, other_branch));
-  } else {
-    AddStatement(MakeSimpleIf(cond, other_branch, nullptr));
-  }
-  AddStatement(create<ast::FallthroughStatement>(Source{}));
-
-  return success();
-}
-
-bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
-                                                 bool* already_emitted) {
-  if (*already_emitted) {
-    // Only emit this part of the basic block once.
-    return true;
-  }
-  // Returns the given list of local definition IDs, sorted by their index.
-  auto sorted_by_index = [this](const std::vector<uint32_t>& ids) {
-    auto sorted = ids;
-    std::stable_sort(sorted.begin(), sorted.end(),
-                     [this](const uint32_t lhs, const uint32_t rhs) {
-                       return GetDefInfo(lhs)->index < GetDefInfo(rhs)->index;
-                     });
-    return sorted;
-  };
-
-  // Emit declarations of hoisted variables, in index order.
-  for (auto id : sorted_by_index(block_info.hoisted_ids)) {
-    const auto* def_inst = def_use_mgr_->GetDef(id);
-    TINT_ASSERT(Reader, def_inst);
-    auto* storage_type =
-        RemapStorageClass(parser_impl_.ConvertType(def_inst->type_id()), id);
-    AddStatement(create<ast::VariableDeclStatement>(
-        Source{}, parser_impl_.MakeVariable(id, ast::StorageClass::kNone,
-                                            storage_type, false, false, nullptr,
-                                            ast::AttributeList{})));
-    auto* type = ty_.Reference(storage_type, ast::StorageClass::kNone);
-    identifier_types_.emplace(id, type);
-  }
-  // Emit declarations of phi state variables, in index order.
-  for (auto id : sorted_by_index(block_info.phis_needing_state_vars)) {
-    const auto* def_inst = def_use_mgr_->GetDef(id);
-    TINT_ASSERT(Reader, def_inst);
-    const auto phi_var_name = GetDefInfo(id)->phi_var;
-    TINT_ASSERT(Reader, !phi_var_name.empty());
-    auto* var = builder_.Var(
-        phi_var_name,
-        parser_impl_.ConvertType(def_inst->type_id())->Build(builder_));
-    AddStatement(create<ast::VariableDeclStatement>(Source{}, var));
-  }
-
-  // Emit regular statements.
-  const spvtools::opt::BasicBlock& bb = *(block_info.basic_block);
-  const auto* terminator = bb.terminator();
-  const auto* merge = bb.GetMergeInst();  // Might be nullptr
-  for (auto& inst : bb) {
-    if (&inst == terminator || &inst == merge || inst.opcode() == SpvOpLabel ||
-        inst.opcode() == SpvOpVariable) {
-      continue;
-    }
-    if (!EmitStatement(inst)) {
-      return false;
-    }
-  }
-
-  // Emit assignments to carry values to phi nodes in potential destinations.
-  // Do it in index order.
-  if (!block_info.phi_assignments.empty()) {
-    auto sorted = block_info.phi_assignments;
-    std::stable_sort(sorted.begin(), sorted.end(),
-                     [this](const BlockInfo::PhiAssignment& lhs,
-                            const BlockInfo::PhiAssignment& rhs) {
-                       return GetDefInfo(lhs.phi_id)->index <
-                              GetDefInfo(rhs.phi_id)->index;
-                     });
-    for (auto assignment : block_info.phi_assignments) {
-      const auto var_name = GetDefInfo(assignment.phi_id)->phi_var;
-      auto expr = MakeExpression(assignment.value);
-      if (!expr) {
-        return false;
-      }
-      AddStatement(create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, builder_.Symbols().Register(var_name)),
-          expr.expr));
-    }
-  }
-
-  *already_emitted = true;
-  return true;
-}
-
-bool FunctionEmitter::EmitConstDefinition(
-    const spvtools::opt::Instruction& inst,
-    TypedExpression expr) {
-  if (!expr) {
-    return false;
-  }
-
-  // Do not generate pointers that we want to sink.
-  if (GetDefInfo(inst.result_id())->skip == SkipReason::kSinkPointerIntoUse) {
-    return true;
-  }
-
-  expr = AddressOfIfNeeded(expr, &inst);
-  auto* ast_const = parser_impl_.MakeVariable(
-      inst.result_id(), ast::StorageClass::kNone, expr.type, true, false,
-      expr.expr, ast::AttributeList{});
-  if (!ast_const) {
-    return false;
-  }
-  AddStatement(create<ast::VariableDeclStatement>(Source{}, ast_const));
-  identifier_types_.emplace(inst.result_id(), expr.type);
-  return success();
-}
-
-bool FunctionEmitter::EmitConstDefOrWriteToHoistedVar(
-    const spvtools::opt::Instruction& inst,
-    TypedExpression expr) {
-  return WriteIfHoistedVar(inst, expr) || EmitConstDefinition(inst, expr);
-}
-
-bool FunctionEmitter::WriteIfHoistedVar(const spvtools::opt::Instruction& inst,
-                                        TypedExpression expr) {
-  const auto result_id = inst.result_id();
-  const auto* def_info = GetDefInfo(result_id);
-  if (def_info && def_info->requires_hoisted_def) {
-    auto name = namer_.Name(result_id);
-    // Emit an assignment of the expression to the hoisted variable.
-    AddStatement(create<ast::AssignmentStatement>(
-        Source{},
-        create<ast::IdentifierExpression>(Source{},
-                                          builder_.Symbols().Register(name)),
-        expr.expr));
-    return true;
-  }
-  return false;
-}
-
-bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
-  if (failed()) {
-    return false;
-  }
-  const auto result_id = inst.result_id();
-  const auto type_id = inst.type_id();
-
-  if (type_id != 0) {
-    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
-    if (type_id == builtin_position_info.struct_type_id) {
-      return Fail() << "operations producing a per-vertex structure are not "
-                       "supported: "
-                    << inst.PrettyPrint();
-    }
-    if (type_id == builtin_position_info.pointer_type_id) {
-      return Fail() << "operations producing a pointer to a per-vertex "
-                       "structure are not "
-                       "supported: "
-                    << inst.PrettyPrint();
-    }
-  }
-
-  // Handle combinatorial instructions.
-  const auto* def_info = GetDefInfo(result_id);
-  if (def_info) {
-    TypedExpression combinatorial_expr;
-    if (def_info->skip == SkipReason::kDontSkip) {
-      combinatorial_expr = MaybeEmitCombinatorialValue(inst);
-      if (!success()) {
-        return false;
-      }
-    }
-    // An access chain or OpCopyObject can generate a skip.
-    if (def_info->skip != SkipReason::kDontSkip) {
-      return true;
-    }
-
-    if (combinatorial_expr.expr != nullptr) {
-      if (def_info->requires_hoisted_def ||
-          def_info->requires_named_const_def || def_info->num_uses != 1) {
-        // Generate a const definition or an assignment to a hoisted definition
-        // now and later use the const or variable name at the uses of this
-        // value.
-        return EmitConstDefOrWriteToHoistedVar(inst, combinatorial_expr);
-      }
-      // It is harmless to defer emitting the expression until it's used.
-      // Any supporting statements have already been emitted.
-      singly_used_values_.insert(std::make_pair(result_id, combinatorial_expr));
-      return success();
-    }
-  }
-  if (failed()) {
-    return false;
-  }
-
-  if (IsImageQuery(inst.opcode())) {
-    return EmitImageQuery(inst);
-  }
-
-  if (IsSampledImageAccess(inst.opcode()) || IsRawImageAccess(inst.opcode())) {
-    return EmitImageAccess(inst);
-  }
-
-  switch (inst.opcode()) {
-    case SpvOpNop:
-      return true;
-
-    case SpvOpStore: {
-      auto ptr_id = inst.GetSingleWordInOperand(0);
-      const auto value_id = inst.GetSingleWordInOperand(1);
-
-      const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id();
-      const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
-      if (ptr_type_id == builtin_position_info.pointer_type_id) {
-        return Fail()
-               << "storing to the whole per-vertex structure is not supported: "
-               << inst.PrettyPrint();
-      }
-
-      TypedExpression rhs = MakeExpression(value_id);
-      if (!rhs) {
-        return false;
-      }
-
-      TypedExpression lhs;
-
-      // Handle exceptional cases
-      switch (GetSkipReason(ptr_id)) {
-        case SkipReason::kPointSizeBuiltinPointer:
-          if (IsFloatOne(value_id)) {
-            // Don't store to PointSize
-            return true;
-          }
-          return Fail() << "cannot store a value other than constant 1.0 to "
-                           "PointSize builtin: "
-                        << inst.PrettyPrint();
-
-        case SkipReason::kSampleMaskOutBuiltinPointer:
-          lhs = MakeExpression(sample_mask_out_id);
-          if (lhs.type->Is<Pointer>()) {
-            // LHS of an assignment must be a reference type.
-            // Convert the LHS to a reference by dereferencing it.
-            lhs = Dereference(lhs);
-          }
-          // The private variable is an array whose element type is already of
-          // the same type as the value being stored into it.  Form the
-          // reference into the first element.
-          lhs.expr = create<ast::IndexAccessorExpression>(
-              Source{}, lhs.expr, parser_impl_.MakeNullValue(ty_.I32()));
-          if (auto* ref = lhs.type->As<Reference>()) {
-            lhs.type = ref->type;
-          }
-          if (auto* arr = lhs.type->As<Array>()) {
-            lhs.type = arr->type;
-          }
-          TINT_ASSERT(Reader, lhs.type);
-          break;
-        default:
-          break;
-      }
-
-      // Handle an ordinary store as an assignment.
-      if (!lhs) {
-        lhs = MakeExpression(ptr_id);
-      }
-      if (!lhs) {
-        return false;
-      }
-
-      if (lhs.type->Is<Pointer>()) {
-        // LHS of an assignment must be a reference type.
-        // Convert the LHS to a reference by dereferencing it.
-        lhs = Dereference(lhs);
-      }
-
-      AddStatement(
-          create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr));
-      return success();
-    }
-
-    case SpvOpLoad: {
-      // Memory accesses must be issued in SPIR-V program order.
-      // So represent a load by a new const definition.
-      const auto ptr_id = inst.GetSingleWordInOperand(0);
-      const auto skip_reason = GetSkipReason(ptr_id);
-
-      switch (skip_reason) {
-        case SkipReason::kPointSizeBuiltinPointer:
-          GetDefInfo(inst.result_id())->skip =
-              SkipReason::kPointSizeBuiltinValue;
-          return true;
-        case SkipReason::kSampleMaskInBuiltinPointer: {
-          auto name = namer_.Name(sample_mask_in_id);
-          const ast::Expression* id_expr = create<ast::IdentifierExpression>(
-              Source{}, builder_.Symbols().Register(name));
-          // SampleMask is an array in Vulkan SPIR-V. Always access the first
-          // element.
-          id_expr = create<ast::IndexAccessorExpression>(
-              Source{}, id_expr, parser_impl_.MakeNullValue(ty_.I32()));
-
-          auto* loaded_type = parser_impl_.ConvertType(inst.type_id());
-
-          if (!loaded_type->IsIntegerScalar()) {
-            return Fail() << "loading the whole SampleMask input array is not "
-                             "supported: "
-                          << inst.PrettyPrint();
-          }
-
-          auto expr = TypedExpression{loaded_type, id_expr};
-          return EmitConstDefinition(inst, expr);
-        }
-        default:
-          break;
-      }
-      auto expr = MakeExpression(ptr_id);
-      if (!expr) {
-        return false;
-      }
-
-      // The load result type is the storage type of its operand.
-      if (expr.type->Is<Pointer>()) {
-        expr = Dereference(expr);
-      } else if (auto* ref = expr.type->As<Reference>()) {
-        expr.type = ref->type;
-      } else {
-        Fail() << "OpLoad expression is not a pointer or reference";
-        return false;
-      }
-
-      return EmitConstDefOrWriteToHoistedVar(inst, expr);
-    }
-
-    case SpvOpCopyMemory: {
-      // Generate an assignment.
-      auto lhs = MakeOperand(inst, 0);
-      auto rhs = MakeOperand(inst, 1);
-      // Ignore any potential memory operands. Currently they are all for
-      // concepts not in WGSL:
-      //   Volatile
-      //   Aligned
-      //   Nontemporal
-      //   MakePointerAvailable ; Vulkan memory model
-      //   MakePointerVisible   ; Vulkan memory model
-      //   NonPrivatePointer    ; Vulkan memory model
-
-      if (!success()) {
-        return false;
-      }
-
-      // LHS and RHS pointers must be reference types in WGSL.
-      if (lhs.type->Is<Pointer>()) {
-        lhs = Dereference(lhs);
-      }
-      if (rhs.type->Is<Pointer>()) {
-        rhs = Dereference(rhs);
-      }
-
-      AddStatement(
-          create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr));
-      return success();
-    }
-
-    case SpvOpCopyObject: {
-      // Arguably, OpCopyObject is purely combinatorial. On the other hand,
-      // it exists to make a new name for something. So we choose to make
-      // a new named constant definition.
-      auto value_id = inst.GetSingleWordInOperand(0);
-      const auto skip = GetSkipReason(value_id);
-      if (skip != SkipReason::kDontSkip) {
-        GetDefInfo(inst.result_id())->skip = skip;
-        GetDefInfo(inst.result_id())->sink_pointer_source_expr =
-            GetDefInfo(value_id)->sink_pointer_source_expr;
-        return true;
-      }
-      auto expr = AddressOfIfNeeded(MakeExpression(value_id), &inst);
-      if (!expr) {
-        return false;
-      }
-      expr.type = RemapStorageClass(expr.type, result_id);
-      return EmitConstDefOrWriteToHoistedVar(inst, expr);
-    }
-
-    case SpvOpPhi: {
-      // Emit a read from the associated state variable.
-      TypedExpression expr{
-          parser_impl_.ConvertType(inst.type_id()),
-          create<ast::IdentifierExpression>(
-              Source{}, builder_.Symbols().Register(def_info->phi_var))};
-      return EmitConstDefOrWriteToHoistedVar(inst, expr);
-    }
-
-    case SpvOpOuterProduct:
-      // Synthesize an outer product expression in its own statement.
-      return EmitConstDefOrWriteToHoistedVar(inst, MakeOuterProduct(inst));
-
-    case SpvOpVectorInsertDynamic:
-      // Synthesize a vector insertion in its own statements.
-      return MakeVectorInsertDynamic(inst);
-
-    case SpvOpCompositeInsert:
-      // Synthesize a composite insertion in its own statements.
-      return MakeCompositeInsert(inst);
-
-    case SpvOpFunctionCall:
-      return EmitFunctionCall(inst);
-
-    case SpvOpControlBarrier:
-      return EmitControlBarrier(inst);
-
-    case SpvOpExtInst:
-      if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
-        return true;
-      }
-      break;
-
-    case SpvOpIAddCarry:
-    case SpvOpISubBorrow:
-    case SpvOpUMulExtended:
-    case SpvOpSMulExtended:
-      return Fail() << "extended arithmetic is not finalized for WGSL: "
-                       "https://github.com/gpuweb/gpuweb/issues/1565: "
-                    << inst.PrettyPrint();
-
-    default:
-      break;
-  }
-  return Fail() << "unhandled instruction with opcode " << inst.opcode() << ": "
-                << inst.PrettyPrint();
-}
-
-TypedExpression FunctionEmitter::MakeOperand(
-    const spvtools::opt::Instruction& inst,
-    uint32_t operand_index) {
-  auto expr = MakeExpression(inst.GetSingleWordInOperand(operand_index));
-  if (!expr) {
-    return {};
-  }
-  return parser_impl_.RectifyOperandSignedness(inst, std::move(expr));
-}
-
-TypedExpression FunctionEmitter::InferFunctionStorageClass(
-    TypedExpression expr) {
-  TypedExpression result(expr);
-  if (const auto* ref = expr.type->UnwrapAlias()->As<Reference>()) {
-    if (ref->storage_class == ast::StorageClass::kNone) {
-      expr.type = ty_.Reference(ref->type, ast::StorageClass::kFunction);
-    }
-  } else if (const auto* ptr = expr.type->UnwrapAlias()->As<Pointer>()) {
-    if (ptr->storage_class == ast::StorageClass::kNone) {
-      expr.type = ty_.Pointer(ptr->type, ast::StorageClass::kFunction);
-    }
-  }
-  return expr;
-}
-
-TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
-    const spvtools::opt::Instruction& inst) {
-  if (inst.result_id() == 0) {
-    return {};
-  }
-
-  const auto opcode = inst.opcode();
-
-  const Type* ast_type = nullptr;
-  if (inst.type_id()) {
-    ast_type = parser_impl_.ConvertType(inst.type_id());
-    if (!ast_type) {
-      Fail() << "couldn't convert result type for: " << inst.PrettyPrint();
-      return {};
-    }
-  }
-
-  auto binary_op = ConvertBinaryOp(opcode);
-  if (binary_op != ast::BinaryOp::kNone) {
-    auto arg0 = MakeOperand(inst, 0);
-    auto arg1 = parser_impl_.RectifySecondOperandSignedness(
-        inst, arg0.type, MakeOperand(inst, 1));
-    if (!arg0 || !arg1) {
-      return {};
-    }
-    auto* binary_expr = create<ast::BinaryExpression>(Source{}, binary_op,
-                                                      arg0.expr, arg1.expr);
-    TypedExpression result{ast_type, binary_expr};
-    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
-  }
-
-  auto unary_op = ast::UnaryOp::kNegation;
-  if (GetUnaryOp(opcode, &unary_op)) {
-    auto arg0 = MakeOperand(inst, 0);
-    auto* unary_expr =
-        create<ast::UnaryOpExpression>(Source{}, unary_op, arg0.expr);
-    TypedExpression result{ast_type, unary_expr};
-    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
-  }
-
-  const char* unary_builtin_name = GetUnaryBuiltInFunctionName(opcode);
-  if (unary_builtin_name != nullptr) {
-    ast::ExpressionList params;
-    params.emplace_back(MakeOperand(inst, 0).expr);
-    return {ast_type,
-            create<ast::CallExpression>(
-                Source{},
-                create<ast::IdentifierExpression>(
-                    Source{}, builder_.Symbols().Register(unary_builtin_name)),
-                std::move(params))};
-  }
-
-  const auto builtin = GetBuiltin(opcode);
-  if (builtin != sem::BuiltinType::kNone) {
-    return MakeBuiltinCall(inst);
-  }
-
-  if (opcode == SpvOpFMod) {
-    return MakeFMod(inst);
-  }
-
-  if (opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) {
-    return MakeAccessChain(inst);
-  }
-
-  if (opcode == SpvOpBitcast) {
-    return {ast_type,
-            create<ast::BitcastExpression>(Source{}, ast_type->Build(builder_),
-                                           MakeOperand(inst, 0).expr)};
-  }
-
-  if (opcode == SpvOpShiftLeftLogical || opcode == SpvOpShiftRightLogical ||
-      opcode == SpvOpShiftRightArithmetic) {
-    auto arg0 = MakeOperand(inst, 0);
-    // The second operand must be unsigned. It's ok to wrap the shift amount
-    // since the shift is modulo the bit width of the first operand.
-    auto arg1 = parser_impl_.AsUnsigned(MakeOperand(inst, 1));
-
-    switch (opcode) {
-      case SpvOpShiftLeftLogical:
-        binary_op = ast::BinaryOp::kShiftLeft;
-        break;
-      case SpvOpShiftRightLogical:
-        arg0 = parser_impl_.AsUnsigned(arg0);
-        binary_op = ast::BinaryOp::kShiftRight;
-        break;
-      case SpvOpShiftRightArithmetic:
-        arg0 = parser_impl_.AsSigned(arg0);
-        binary_op = ast::BinaryOp::kShiftRight;
-        break;
-      default:
-        break;
-    }
-    TypedExpression result{
-        ast_type, create<ast::BinaryExpression>(Source{}, binary_op, arg0.expr,
-                                                arg1.expr)};
-    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
-  }
-
-  auto negated_op = NegatedFloatCompare(opcode);
-  if (negated_op != ast::BinaryOp::kNone) {
-    auto arg0 = MakeOperand(inst, 0);
-    auto arg1 = MakeOperand(inst, 1);
-    auto* binary_expr = create<ast::BinaryExpression>(Source{}, negated_op,
-                                                      arg0.expr, arg1.expr);
-    auto* negated_expr = create<ast::UnaryOpExpression>(
-        Source{}, ast::UnaryOp::kNot, binary_expr);
-    return {ast_type, negated_expr};
-  }
-
-  if (opcode == SpvOpExtInst) {
-    if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
-      // Ignore it but don't error out.
-      return {};
-    }
-    if (!parser_impl_.IsGlslExtendedInstruction(inst)) {
-      Fail() << "unhandled extended instruction import with ID "
-             << inst.GetSingleWordInOperand(0);
-      return {};
-    }
-    return EmitGlslStd450ExtInst(inst);
-  }
-
-  if (opcode == SpvOpCompositeConstruct) {
-    ast::ExpressionList operands;
-    for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
-      operands.emplace_back(MakeOperand(inst, iarg).expr);
-    }
-    return {ast_type, builder_.Construct(Source{}, ast_type->Build(builder_),
-                                         std::move(operands))};
-  }
-
-  if (opcode == SpvOpCompositeExtract) {
-    return MakeCompositeExtract(inst);
-  }
-
-  if (opcode == SpvOpVectorShuffle) {
-    return MakeVectorShuffle(inst);
-  }
-
-  if (opcode == SpvOpVectorExtractDynamic) {
-    return {ast_type, create<ast::IndexAccessorExpression>(
-                          Source{}, MakeOperand(inst, 0).expr,
-                          MakeOperand(inst, 1).expr)};
-  }
-
-  if (opcode == SpvOpConvertSToF || opcode == SpvOpConvertUToF ||
-      opcode == SpvOpConvertFToS || opcode == SpvOpConvertFToU) {
-    return MakeNumericConversion(inst);
-  }
-
-  if (opcode == SpvOpUndef) {
-    // Replace undef with the null value.
-    return parser_impl_.MakeNullExpression(ast_type);
-  }
-
-  if (opcode == SpvOpSelect) {
-    return MakeSimpleSelect(inst);
-  }
-
-  if (opcode == SpvOpArrayLength) {
-    return MakeArrayLength(inst);
-  }
-
-  // builtin readonly function
-  // glsl.std.450 readonly function
-
-  // Instructions:
-  //    OpSatConvertSToU // Only in Kernel (OpenCL), not in WebGPU
-  //    OpSatConvertUToS // Only in Kernel (OpenCL), not in WebGPU
-  //    OpUConvert // Only needed when multiple widths supported
-  //    OpSConvert // Only needed when multiple widths supported
-  //    OpFConvert // Only needed when multiple widths supported
-  //    OpConvertPtrToU // Not in WebGPU
-  //    OpConvertUToPtr // Not in WebGPU
-  //    OpPtrCastToGeneric // Not in Vulkan
-  //    OpGenericCastToPtr // Not in Vulkan
-  //    OpGenericCastToPtrExplicit // Not in Vulkan
-
-  return {};
-}
-
-TypedExpression FunctionEmitter::EmitGlslStd450ExtInst(
-    const spvtools::opt::Instruction& inst) {
-  const auto ext_opcode = inst.GetSingleWordInOperand(1);
-
-  if (ext_opcode == GLSLstd450Ldexp) {
-    // WGSL requires the second argument to be signed.
-    // Use a type constructor to convert it, which is the same as a bitcast.
-    // If the value would go from very large positive to negative, then the
-    // original result would have been infinity.  And since WGSL
-    // implementations may assume that infinities are not present, then we
-    // don't have to worry about that case.
-    auto e1 = MakeOperand(inst, 2);
-    auto e2 = ToSignedIfUnsigned(MakeOperand(inst, 3));
-
-    return {e1.type, builder_.Call(Source{}, "ldexp",
-                                   ast::ExpressionList{e1.expr, e2.expr})};
-  }
-
-  auto* result_type = parser_impl_.ConvertType(inst.type_id());
-
-  if (result_type->IsScalar()) {
-    // Some GLSLstd450 builtins have scalar forms not supported by WGSL.
-    // Emulate them.
-    switch (ext_opcode) {
-      case GLSLstd450Normalize:
-        // WGSL does not have scalar form of the normalize builtin.
-        // The answer would be 1 anyway, so return that directly.
-        return {ty_.F32(), builder_.Expr(1.0f)};
-
-      case GLSLstd450FaceForward: {
-        // If dot(Nref, Incident) < 0, the result is Normal, otherwise -Normal.
-        // Also: select(-normal,normal, Incident*Nref < 0)
-        // (The dot product of scalars is their product.)
-        // Use a multiply instead of comparing floating point signs. It should
-        // be among the fastest operations on a GPU.
-        auto normal = MakeOperand(inst, 2);
-        auto incident = MakeOperand(inst, 3);
-        auto nref = MakeOperand(inst, 4);
-        TINT_ASSERT(Reader, normal.type->Is<F32>());
-        TINT_ASSERT(Reader, incident.type->Is<F32>());
-        TINT_ASSERT(Reader, nref.type->Is<F32>());
-        return {ty_.F32(),
-                builder_.Call(
-                    Source{}, "select",
-                    ast::ExpressionList{
-                        create<ast::UnaryOpExpression>(
-                            Source{}, ast::UnaryOp::kNegation, normal.expr),
-                        normal.expr,
-                        create<ast::BinaryExpression>(
-                            Source{}, ast::BinaryOp::kLessThan,
-                            builder_.Mul({}, incident.expr, nref.expr),
-                            builder_.Expr(0.0f))})};
-      }
-
-      case GLSLstd450Reflect: {
-        // Compute  Incident - 2 * Normal * Normal * Incident
-        auto incident = MakeOperand(inst, 2);
-        auto normal = MakeOperand(inst, 3);
-        TINT_ASSERT(Reader, incident.type->Is<F32>());
-        TINT_ASSERT(Reader, normal.type->Is<F32>());
-        return {
-            ty_.F32(),
-            builder_.Sub(
-                incident.expr,
-                builder_.Mul(2.0f, builder_.Mul(normal.expr,
-                                                builder_.Mul(normal.expr,
-                                                             incident.expr))))};
-      }
-
-      case GLSLstd450Refract: {
-        // It's a complicated expression. Compute it in two dimensions, but
-        // with a 0-valued y component in both the incident and normal vectors,
-        // then take the x component of that result.
-        auto incident = MakeOperand(inst, 2);
-        auto normal = MakeOperand(inst, 3);
-        auto eta = MakeOperand(inst, 4);
-        TINT_ASSERT(Reader, incident.type->Is<F32>());
-        TINT_ASSERT(Reader, normal.type->Is<F32>());
-        TINT_ASSERT(Reader, eta.type->Is<F32>());
-        if (!success()) {
-          return {};
-        }
-        const Type* f32 = eta.type;
-        return {f32,
-                builder_.MemberAccessor(
-                    builder_.Call(
-                        Source{}, "refract",
-                        ast::ExpressionList{
-                            builder_.vec2<float>(incident.expr, 0.0f),
-                            builder_.vec2<float>(normal.expr, 0.0f), eta.expr}),
-                    "x")};
-      }
-      default:
-        break;
-    }
-  }
-
-  const auto name = GetGlslStd450FuncName(ext_opcode);
-  if (name.empty()) {
-    Fail() << "unhandled GLSL.std.450 instruction " << ext_opcode;
-    return {};
-  }
-
-  auto* func = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(name));
-  ast::ExpressionList operands;
-  const Type* first_operand_type = nullptr;
-  // All parameters to GLSL.std.450 extended instructions are IDs.
-  for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) {
-    TypedExpression operand = MakeOperand(inst, iarg);
-    if (first_operand_type == nullptr) {
-      first_operand_type = operand.type;
-    }
-    operands.emplace_back(operand.expr);
-  }
-  auto* call = create<ast::CallExpression>(Source{}, func, std::move(operands));
-  TypedExpression call_expr{result_type, call};
-  return parser_impl_.RectifyForcedResultType(call_expr, inst,
-                                              first_operand_type);
-}
-
-ast::IdentifierExpression* FunctionEmitter::Swizzle(uint32_t i) {
-  if (i >= kMaxVectorLen) {
-    Fail() << "vector component index is larger than " << kMaxVectorLen - 1
-           << ": " << i;
-    return nullptr;
-  }
-  const char* names[] = {"x", "y", "z", "w"};
-  return create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(names[i & 3]));
-}
-
-ast::IdentifierExpression* FunctionEmitter::PrefixSwizzle(uint32_t n) {
-  switch (n) {
-    case 1:
-      return create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register("x"));
-    case 2:
-      return create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register("xy"));
-    case 3:
-      return create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register("xyz"));
-    default:
-      break;
-  }
-  Fail() << "invalid swizzle prefix count: " << n;
-  return nullptr;
-}
-
-TypedExpression FunctionEmitter::MakeFMod(
-    const spvtools::opt::Instruction& inst) {
-  auto x = MakeOperand(inst, 0);
-  auto y = MakeOperand(inst, 1);
-  if (!x || !y) {
-    return {};
-  }
-  // Emulated with: x - y * floor(x / y)
-  auto* div = builder_.Div(x.expr, y.expr);
-  auto* floor = builder_.Call("floor", div);
-  auto* y_floor = builder_.Mul(y.expr, floor);
-  auto* res = builder_.Sub(x.expr, y_floor);
-  return {x.type, res};
-}
-
-TypedExpression FunctionEmitter::MakeAccessChain(
-    const spvtools::opt::Instruction& inst) {
-  if (inst.NumInOperands() < 1) {
-    // Binary parsing will fail on this anyway.
-    Fail() << "invalid access chain: has no input operands";
-    return {};
-  }
-
-  const auto base_id = inst.GetSingleWordInOperand(0);
-  const auto base_skip = GetSkipReason(base_id);
-  if (base_skip != SkipReason::kDontSkip) {
-    // This can occur for AccessChain with no indices.
-    GetDefInfo(inst.result_id())->skip = base_skip;
-    GetDefInfo(inst.result_id())->sink_pointer_source_expr =
-        GetDefInfo(base_id)->sink_pointer_source_expr;
-    return {};
-  }
-
-  auto ptr_ty_id = def_use_mgr_->GetDef(base_id)->type_id();
-  uint32_t first_index = 1;
-  const auto num_in_operands = inst.NumInOperands();
-
-  bool sink_pointer = false;
-  TypedExpression current_expr;
-
-  // If the variable was originally gl_PerVertex, then in the AST we
-  // have instead emitted a gl_Position variable.
-  // If computing the pointer to the Position builtin, then emit the
-  // pointer to the generated gl_Position variable.
-  // If computing the pointer to the PointSize builtin, then mark the
-  // result as skippable due to being the point-size pointer.
-  // If computing the pointer to the ClipDistance or CullDistance builtins,
-  // then error out.
-  {
-    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
-    if (base_id == builtin_position_info.per_vertex_var_id) {
-      // We only support the Position member.
-      const auto* member_index_inst =
-          def_use_mgr_->GetDef(inst.GetSingleWordInOperand(first_index));
-      if (member_index_inst == nullptr) {
-        Fail()
-            << "first index of access chain does not reference an instruction: "
-            << inst.PrettyPrint();
-        return {};
-      }
-      const auto* member_index_const =
-          constant_mgr_->GetConstantFromInst(member_index_inst);
-      if (member_index_const == nullptr) {
-        Fail() << "first index of access chain into per-vertex structure is "
-                  "not a constant: "
-               << inst.PrettyPrint();
-        return {};
-      }
-      const auto* member_index_const_int = member_index_const->AsIntConstant();
-      if (member_index_const_int == nullptr) {
-        Fail() << "first index of access chain into per-vertex structure is "
-                  "not a constant integer: "
-               << inst.PrettyPrint();
-        return {};
-      }
-      const auto member_index_value =
-          member_index_const_int->GetZeroExtendedValue();
-      if (member_index_value != builtin_position_info.position_member_index) {
-        if (member_index_value ==
-            builtin_position_info.pointsize_member_index) {
-          if (auto* def_info = GetDefInfo(inst.result_id())) {
-            def_info->skip = SkipReason::kPointSizeBuiltinPointer;
-            return {};
-          }
-        } else {
-          // TODO(dneto): Handle ClipDistance and CullDistance
-          Fail() << "accessing per-vertex member " << member_index_value
-                 << " is not supported. Only Position is supported, and "
-                    "PointSize is ignored";
-          return {};
-        }
-      }
-
-      // Skip past the member index that gets us to Position.
-      first_index = first_index + 1;
-      // Replace the gl_PerVertex reference with the gl_Position reference
-      ptr_ty_id = builtin_position_info.position_member_pointer_type_id;
-
-      auto name = namer_.Name(base_id);
-      current_expr.expr = create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register(name));
-      current_expr.type = parser_impl_.ConvertType(ptr_ty_id, PtrAs::Ref);
-    }
-  }
-
-  // A SPIR-V access chain is a single instruction with multiple indices
-  // walking down into composites.  The Tint AST represents this as
-  // ever-deeper nested indexing expressions. Start off with an expression
-  // for the base, and then bury that inside nested indexing expressions.
-  if (!current_expr) {
-    current_expr = InferFunctionStorageClass(MakeOperand(inst, 0));
-    if (current_expr.type->Is<Pointer>()) {
-      current_expr = Dereference(current_expr);
-    }
-  }
-  const auto constants = constant_mgr_->GetOperandConstants(&inst);
-
-  const auto* ptr_type_inst = def_use_mgr_->GetDef(ptr_ty_id);
-  if (!ptr_type_inst || (ptr_type_inst->opcode() != SpvOpTypePointer)) {
-    Fail() << "Access chain %" << inst.result_id()
-           << " base pointer is not of pointer type";
-    return {};
-  }
-  SpvStorageClass storage_class =
-      static_cast<SpvStorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
-  uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
-
-  // Build up a nested expression for the access chain by walking down the type
-  // hierarchy, maintaining |pointee_type_id| as the SPIR-V ID of the type of
-  // the object pointed to after processing the previous indices.
-  for (uint32_t index = first_index; index < num_in_operands; ++index) {
-    const auto* index_const =
-        constants[index] ? constants[index]->AsIntConstant() : nullptr;
-    const int64_t index_const_val =
-        index_const ? index_const->GetSignExtendedValue() : 0;
-    const ast::Expression* next_expr = nullptr;
-
-    const auto* pointee_type_inst = def_use_mgr_->GetDef(pointee_type_id);
-    if (!pointee_type_inst) {
-      Fail() << "pointee type %" << pointee_type_id
-             << " is invalid after following " << (index - first_index)
-             << " indices: " << inst.PrettyPrint();
-      return {};
-    }
-    switch (pointee_type_inst->opcode()) {
-      case SpvOpTypeVector:
-        if (index_const) {
-          // Try generating a MemberAccessor expression
-          const auto num_elems = pointee_type_inst->GetSingleWordInOperand(1);
-          if (index_const_val < 0 || num_elems <= index_const_val) {
-            Fail() << "Access chain %" << inst.result_id() << " index %"
-                   << inst.GetSingleWordInOperand(index) << " value "
-                   << index_const_val << " is out of bounds for vector of "
-                   << num_elems << " elements";
-            return {};
-          }
-          if (uint64_t(index_const_val) >= kMaxVectorLen) {
-            Fail() << "internal error: swizzle index " << index_const_val
-                   << " is too big. Max handled index is " << kMaxVectorLen - 1;
-          }
-          next_expr = create<ast::MemberAccessorExpression>(
-              Source{}, current_expr.expr, Swizzle(uint32_t(index_const_val)));
-        } else {
-          // Non-constant index. Use array syntax
-          next_expr = create<ast::IndexAccessorExpression>(
-              Source{}, current_expr.expr, MakeOperand(inst, index).expr);
-        }
-        // All vector components are the same type.
-        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
-        // Sink pointers to vector components.
-        sink_pointer = true;
-        break;
-      case SpvOpTypeMatrix:
-        // Use array syntax.
-        next_expr = create<ast::IndexAccessorExpression>(
-            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
-        // All matrix components are the same type.
-        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpTypeArray:
-        next_expr = create<ast::IndexAccessorExpression>(
-            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
-        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpTypeRuntimeArray:
-        next_expr = create<ast::IndexAccessorExpression>(
-            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
-        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpTypeStruct: {
-        if (!index_const) {
-          Fail() << "Access chain %" << inst.result_id() << " index %"
-                 << inst.GetSingleWordInOperand(index)
-                 << " is a non-constant index into a structure %"
-                 << pointee_type_id;
-          return {};
-        }
-        const auto num_members = pointee_type_inst->NumInOperands();
-        if ((index_const_val < 0) || num_members <= uint64_t(index_const_val)) {
-          Fail() << "Access chain %" << inst.result_id() << " index value "
-                 << index_const_val << " is out of bounds for structure %"
-                 << pointee_type_id << " having " << num_members << " members";
-          return {};
-        }
-        auto name =
-            namer_.GetMemberName(pointee_type_id, uint32_t(index_const_val));
-        auto* member_access = create<ast::IdentifierExpression>(
-            Source{}, builder_.Symbols().Register(name));
-
-        next_expr = create<ast::MemberAccessorExpression>(
-            Source{}, current_expr.expr, member_access);
-        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(
-            static_cast<uint32_t>(index_const_val));
-        break;
-      }
-      default:
-        Fail() << "Access chain with unknown or invalid pointee type %"
-               << pointee_type_id << ": " << pointee_type_inst->PrettyPrint();
-        return {};
-    }
-    const auto pointer_type_id =
-        type_mgr_->FindPointerToType(pointee_type_id, storage_class);
-    auto* type = parser_impl_.ConvertType(pointer_type_id, PtrAs::Ref);
-    TINT_ASSERT(Reader, type && type->Is<Reference>());
-    current_expr = TypedExpression{type, next_expr};
-  }
-
-  if (sink_pointer) {
-    // Capture the reference so that we can sink it into the point of use.
-    GetDefInfo(inst.result_id())->skip = SkipReason::kSinkPointerIntoUse;
-    GetDefInfo(inst.result_id())->sink_pointer_source_expr = current_expr;
-  }
-
-  return current_expr;
-}
-
-TypedExpression FunctionEmitter::MakeCompositeExtract(
-    const spvtools::opt::Instruction& inst) {
-  // This is structurally similar to creating an access chain, but
-  // the SPIR-V instruction has literal indices instead of IDs for indices.
-
-  auto composite_index = 0;
-  auto first_index_position = 1;
-  TypedExpression current_expr(MakeOperand(inst, composite_index));
-  if (!current_expr) {
-    return {};
-  }
-
-  const auto composite_id = inst.GetSingleWordInOperand(composite_index);
-  auto current_type_id = def_use_mgr_->GetDef(composite_id)->type_id();
-
-  return MakeCompositeValueDecomposition(inst, current_expr, current_type_id,
-                                         first_index_position);
-}
-
-TypedExpression FunctionEmitter::MakeCompositeValueDecomposition(
-    const spvtools::opt::Instruction& inst,
-    TypedExpression composite,
-    uint32_t composite_type_id,
-    int index_start) {
-  // This is structurally similar to creating an access chain, but
-  // the SPIR-V instruction has literal indices instead of IDs for indices.
-
-  // A SPIR-V composite extract is a single instruction with multiple
-  // literal indices walking down into composites.
-  // A SPIR-V composite insert is similar but also tells you what component
-  // to inject. This function is responsible for the the walking-into part
-  // of composite-insert.
-  //
-  // The Tint AST represents this as ever-deeper nested indexing expressions.
-  // Start off with an expression for the composite, and then bury that inside
-  // nested indexing expressions.
-
-  auto current_expr = composite;
-  auto current_type_id = composite_type_id;
-
-  auto make_index = [this](uint32_t literal) {
-    return create<ast::UintLiteralExpression>(Source{}, literal);
-  };
-
-  // Build up a nested expression for the decomposition by walking down the type
-  // hierarchy, maintaining |current_type_id| as the SPIR-V ID of the type of
-  // the object pointed to after processing the previous indices.
-  const auto num_in_operands = inst.NumInOperands();
-  for (uint32_t index = index_start; index < num_in_operands; ++index) {
-    const uint32_t index_val = inst.GetSingleWordInOperand(index);
-
-    const auto* current_type_inst = def_use_mgr_->GetDef(current_type_id);
-    if (!current_type_inst) {
-      Fail() << "composite type %" << current_type_id
-             << " is invalid after following " << (index - index_start)
-             << " indices: " << inst.PrettyPrint();
-      return {};
-    }
-    const char* operation_name = nullptr;
-    switch (inst.opcode()) {
-      case SpvOpCompositeExtract:
-        operation_name = "OpCompositeExtract";
-        break;
-      case SpvOpCompositeInsert:
-        operation_name = "OpCompositeInsert";
-        break;
-      default:
-        Fail() << "internal error: unhandled " << inst.PrettyPrint();
-        return {};
-    }
-    const ast::Expression* next_expr = nullptr;
-    switch (current_type_inst->opcode()) {
-      case SpvOpTypeVector: {
-        // Try generating a MemberAccessor expression. That result in something
-        // like  "foo.z", which is more idiomatic than "foo[2]".
-        const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
-        if (num_elems <= index_val) {
-          Fail() << operation_name << " %" << inst.result_id()
-                 << " index value " << index_val
-                 << " is out of bounds for vector of " << num_elems
-                 << " elements";
-          return {};
-        }
-        if (index_val >= kMaxVectorLen) {
-          Fail() << "internal error: swizzle index " << index_val
-                 << " is too big. Max handled index is " << kMaxVectorLen - 1;
-          return {};
-        }
-        next_expr = create<ast::MemberAccessorExpression>(
-            Source{}, current_expr.expr, Swizzle(index_val));
-        // All vector components are the same type.
-        current_type_id = current_type_inst->GetSingleWordInOperand(0);
-        break;
-      }
-      case SpvOpTypeMatrix: {
-        // Check bounds
-        const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
-        if (num_elems <= index_val) {
-          Fail() << operation_name << " %" << inst.result_id()
-                 << " index value " << index_val
-                 << " is out of bounds for matrix of " << num_elems
-                 << " elements";
-          return {};
-        }
-        if (index_val >= kMaxVectorLen) {
-          Fail() << "internal error: swizzle index " << index_val
-                 << " is too big. Max handled index is " << kMaxVectorLen - 1;
-        }
-        // Use array syntax.
-        next_expr = create<ast::IndexAccessorExpression>(
-            Source{}, current_expr.expr, make_index(index_val));
-        // All matrix components are the same type.
-        current_type_id = current_type_inst->GetSingleWordInOperand(0);
-        break;
-      }
-      case SpvOpTypeArray:
-        // The array size could be a spec constant, and so it's not always
-        // statically checkable.  Instead, rely on a runtime index clamp
-        // or runtime check to keep this safe.
-        next_expr = create<ast::IndexAccessorExpression>(
-            Source{}, current_expr.expr, make_index(index_val));
-        current_type_id = current_type_inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpTypeRuntimeArray:
-        Fail() << "can't do " << operation_name
-               << " on a runtime array: " << inst.PrettyPrint();
-        return {};
-      case SpvOpTypeStruct: {
-        const auto num_members = current_type_inst->NumInOperands();
-        if (num_members <= index_val) {
-          Fail() << operation_name << " %" << inst.result_id()
-                 << " index value " << index_val
-                 << " is out of bounds for structure %" << current_type_id
-                 << " having " << num_members << " members";
-          return {};
-        }
-        auto name = namer_.GetMemberName(current_type_id, uint32_t(index_val));
-        auto* member_access = create<ast::IdentifierExpression>(
-            Source{}, builder_.Symbols().Register(name));
-
-        next_expr = create<ast::MemberAccessorExpression>(
-            Source{}, current_expr.expr, member_access);
-        current_type_id = current_type_inst->GetSingleWordInOperand(index_val);
-        break;
-      }
-      default:
-        Fail() << operation_name << " with bad type %" << current_type_id
-               << ": " << current_type_inst->PrettyPrint();
-        return {};
-    }
-    current_expr =
-        TypedExpression{parser_impl_.ConvertType(current_type_id), next_expr};
-  }
-  return current_expr;
-}
-
-const ast::Expression* FunctionEmitter::MakeTrue(const Source& source) const {
-  return create<ast::BoolLiteralExpression>(source, true);
-}
-
-const ast::Expression* FunctionEmitter::MakeFalse(const Source& source) const {
-  return create<ast::BoolLiteralExpression>(source, false);
-}
-
-TypedExpression FunctionEmitter::MakeVectorShuffle(
-    const spvtools::opt::Instruction& inst) {
-  const auto vec0_id = inst.GetSingleWordInOperand(0);
-  const auto vec1_id = inst.GetSingleWordInOperand(1);
-  const spvtools::opt::Instruction& vec0 = *(def_use_mgr_->GetDef(vec0_id));
-  const spvtools::opt::Instruction& vec1 = *(def_use_mgr_->GetDef(vec1_id));
-  const auto vec0_len =
-      type_mgr_->GetType(vec0.type_id())->AsVector()->element_count();
-  const auto vec1_len =
-      type_mgr_->GetType(vec1.type_id())->AsVector()->element_count();
-
-  // Idiomatic vector accessors.
-
-  // Generate an ast::TypeConstructor expression.
-  // Assume the literal indices are valid, and there is a valid number of them.
-  auto source = GetSourceForInst(inst);
-  const Vector* result_type =
-      As<Vector>(parser_impl_.ConvertType(inst.type_id()));
-  ast::ExpressionList values;
-  for (uint32_t i = 2; i < inst.NumInOperands(); ++i) {
-    const auto index = inst.GetSingleWordInOperand(i);
-    if (index < vec0_len) {
-      auto expr = MakeExpression(vec0_id);
-      if (!expr) {
-        return {};
-      }
-      values.emplace_back(create<ast::MemberAccessorExpression>(
-          source, expr.expr, Swizzle(index)));
-    } else if (index < vec0_len + vec1_len) {
-      const auto sub_index = index - vec0_len;
-      TINT_ASSERT(Reader, sub_index < kMaxVectorLen);
-      auto expr = MakeExpression(vec1_id);
-      if (!expr) {
-        return {};
-      }
-      values.emplace_back(create<ast::MemberAccessorExpression>(
-          source, expr.expr, Swizzle(sub_index)));
-    } else if (index == 0xFFFFFFFF) {
-      // By rule, this maps to OpUndef.  Instead, make it zero.
-      values.emplace_back(parser_impl_.MakeNullValue(result_type->type));
-    } else {
-      Fail() << "invalid vectorshuffle ID %" << inst.result_id()
-             << ": index too large: " << index;
-      return {};
-    }
-  }
-  return {result_type,
-          builder_.Construct(source, result_type->Build(builder_), values)};
-}
-
-bool FunctionEmitter::RegisterSpecialBuiltInVariables() {
-  size_t index = def_info_.size();
-  for (auto& special_var : parser_impl_.special_builtins()) {
-    const auto id = special_var.first;
-    const auto builtin = special_var.second;
-    const auto* var = def_use_mgr_->GetDef(id);
-    def_info_[id] = std::make_unique<DefInfo>(*var, 0, index);
-    ++index;
-    auto& def = def_info_[id];
-    switch (builtin) {
-      case SpvBuiltInPointSize:
-        def->skip = SkipReason::kPointSizeBuiltinPointer;
-        break;
-      case SpvBuiltInSampleMask: {
-        // Distinguish between input and output variable.
-        const auto storage_class =
-            static_cast<SpvStorageClass>(var->GetSingleWordInOperand(0));
-        if (storage_class == SpvStorageClassInput) {
-          sample_mask_in_id = id;
-          def->skip = SkipReason::kSampleMaskInBuiltinPointer;
-        } else {
-          sample_mask_out_id = id;
-          def->skip = SkipReason::kSampleMaskOutBuiltinPointer;
-        }
-        break;
-      }
-      case SpvBuiltInSampleId:
-      case SpvBuiltInInstanceIndex:
-      case SpvBuiltInVertexIndex:
-      case SpvBuiltInLocalInvocationIndex:
-      case SpvBuiltInLocalInvocationId:
-      case SpvBuiltInGlobalInvocationId:
-      case SpvBuiltInWorkgroupId:
-      case SpvBuiltInNumWorkgroups:
-        break;
-      default:
-        return Fail() << "unrecognized special builtin: " << int(builtin);
-    }
-  }
-  return true;
-}
-
-bool FunctionEmitter::RegisterLocallyDefinedValues() {
-  // Create a DefInfo for each value definition in this function.
-  size_t index = def_info_.size();
-  for (auto block_id : block_order_) {
-    const auto* block_info = GetBlockInfo(block_id);
-    const auto block_pos = block_info->pos;
-    for (const auto& inst : *(block_info->basic_block)) {
-      const auto result_id = inst.result_id();
-      if ((result_id == 0) || inst.opcode() == SpvOpLabel) {
-        continue;
-      }
-      def_info_[result_id] = std::make_unique<DefInfo>(inst, block_pos, index);
-      ++index;
-      auto& info = def_info_[result_id];
-
-      // Determine storage class for pointer values. Do this in order because
-      // we might rely on the storage class for a previously-visited definition.
-      // Logical pointers can't be transmitted through OpPhi, so remaining
-      // pointer definitions are SSA values, and their definitions must be
-      // visited before their uses.
-      const auto* type = type_mgr_->GetType(inst.type_id());
-      if (type) {
-        if (type->AsPointer()) {
-          if (auto* ast_type = parser_impl_.ConvertType(inst.type_id())) {
-            if (auto* ptr = ast_type->As<Pointer>()) {
-              info->storage_class = ptr->storage_class;
-            }
-          }
-          switch (inst.opcode()) {
-            case SpvOpUndef:
-              return Fail()
-                     << "undef pointer is not valid: " << inst.PrettyPrint();
-            case SpvOpVariable:
-              // Keep the default decision based on the result type.
-              break;
-            case SpvOpAccessChain:
-            case SpvOpInBoundsAccessChain:
-            case SpvOpCopyObject:
-              // Inherit from the first operand. We need this so we can pick up
-              // a remapped storage buffer.
-              info->storage_class = GetStorageClassForPointerValue(
-                  inst.GetSingleWordInOperand(0));
-              break;
-            default:
-              return Fail()
-                     << "pointer defined in function from unknown opcode: "
-                     << inst.PrettyPrint();
-          }
-        }
-        auto* unwrapped = type;
-        while (auto* ptr = unwrapped->AsPointer()) {
-          unwrapped = ptr->pointee_type();
-        }
-        if (unwrapped->AsSampler() || unwrapped->AsImage() ||
-            unwrapped->AsSampledImage()) {
-          // Defer code generation until the instruction that actually acts on
-          // the image.
-          info->skip = SkipReason::kOpaqueObject;
-        }
-      }
-    }
-  }
-  return true;
-}
-
-ast::StorageClass FunctionEmitter::GetStorageClassForPointerValue(uint32_t id) {
-  auto where = def_info_.find(id);
-  if (where != def_info_.end()) {
-    auto candidate = where->second.get()->storage_class;
-    if (candidate != ast::StorageClass::kInvalid) {
-      return candidate;
-    }
-  }
-  const auto type_id = def_use_mgr_->GetDef(id)->type_id();
-  if (type_id) {
-    auto* ast_type = parser_impl_.ConvertType(type_id);
-    if (auto* ptr = As<Pointer>(ast_type)) {
-      return ptr->storage_class;
-    }
-  }
-  return ast::StorageClass::kInvalid;
-}
-
-const Type* FunctionEmitter::RemapStorageClass(const Type* type,
-                                               uint32_t result_id) {
-  if (auto* ast_ptr_type = As<Pointer>(type)) {
-    // Remap an old-style storage buffer pointer to a new-style storage
-    // buffer pointer.
-    const auto sc = GetStorageClassForPointerValue(result_id);
-    if (ast_ptr_type->storage_class != sc) {
-      return ty_.Pointer(ast_ptr_type->type, sc);
-    }
-  }
-  return type;
-}
-
-void FunctionEmitter::FindValuesNeedingNamedOrHoistedDefinition() {
-  // Mark vector operands of OpVectorShuffle as needing a named definition,
-  // but only if they are defined in this function as well.
-  auto require_named_const_def = [&](const spvtools::opt::Instruction& inst,
-                                     int in_operand_index) {
-    const auto id = inst.GetSingleWordInOperand(in_operand_index);
-    auto* const operand_def = GetDefInfo(id);
-    if (operand_def) {
-      operand_def->requires_named_const_def = true;
-    }
-  };
-  for (auto& id_def_info_pair : def_info_) {
-    const auto& inst = id_def_info_pair.second->inst;
-    const auto opcode = inst.opcode();
-    if ((opcode == SpvOpVectorShuffle) || (opcode == SpvOpOuterProduct)) {
-      // We might access the vector operands multiple times. Make sure they
-      // are evaluated only once.
-      require_named_const_def(inst, 0);
-      require_named_const_def(inst, 1);
-    }
-    if (parser_impl_.IsGlslExtendedInstruction(inst)) {
-      // Some emulations of GLSLstd450 instructions evaluate certain operands
-      // multiple times. Ensure their expressions are evaluated only once.
-      switch (inst.GetSingleWordInOperand(1)) {
-        case GLSLstd450FaceForward:
-          // The "normal" operand expression is used twice in code generation.
-          require_named_const_def(inst, 2);
-          break;
-        case GLSLstd450Reflect:
-          require_named_const_def(inst, 2);  // Incident
-          require_named_const_def(inst, 3);  // Normal
-          break;
-        default:
-          break;
-      }
-    }
-  }
-
-  // Scan uses of locally defined IDs, in function block order.
-  for (auto block_id : block_order_) {
-    const auto* block_info = GetBlockInfo(block_id);
-    const auto block_pos = block_info->pos;
-    for (const auto& inst : *(block_info->basic_block)) {
-      // Update bookkeeping for locally-defined IDs used by this instruction.
-      inst.ForEachInId([this, block_pos, block_info](const uint32_t* id_ptr) {
-        auto* def_info = GetDefInfo(*id_ptr);
-        if (def_info) {
-          // Update usage count.
-          def_info->num_uses++;
-          // Update usage span.
-          def_info->last_use_pos = std::max(def_info->last_use_pos, block_pos);
-
-          // Determine whether this ID is defined in a different construct
-          // from this use.
-          const auto defining_block = block_order_[def_info->block_pos];
-          const auto* def_in_construct =
-              GetBlockInfo(defining_block)->construct;
-          if (def_in_construct != block_info->construct) {
-            def_info->used_in_another_construct = true;
-          }
-        }
-      });
-
-      if (inst.opcode() == SpvOpPhi) {
-        // Declare a name for the variable used to carry values to a phi.
-        const auto phi_id = inst.result_id();
-        auto* phi_def_info = GetDefInfo(phi_id);
-        phi_def_info->phi_var =
-            namer_.MakeDerivedName(namer_.Name(phi_id) + "_phi");
-        // Track all the places where we need to mention the variable,
-        // so we can place its declaration.  First, record the location of
-        // the read from the variable.
-        uint32_t first_pos = block_pos;
-        uint32_t last_pos = block_pos;
-        // Record the assignments that will propagate values from predecessor
-        // blocks.
-        for (uint32_t i = 0; i + 1 < inst.NumInOperands(); i += 2) {
-          const uint32_t value_id = inst.GetSingleWordInOperand(i);
-          const uint32_t pred_block_id = inst.GetSingleWordInOperand(i + 1);
-          auto* pred_block_info = GetBlockInfo(pred_block_id);
-          // The predecessor might not be in the block order at all, so we
-          // need this guard.
-          if (IsInBlockOrder(pred_block_info)) {
-            // Record the assignment that needs to occur at the end
-            // of the predecessor block.
-            pred_block_info->phi_assignments.push_back({phi_id, value_id});
-            first_pos = std::min(first_pos, pred_block_info->pos);
-            last_pos = std::max(last_pos, pred_block_info->pos);
-          }
-        }
-
-        // Schedule the declaration of the state variable.
-        const auto* enclosing_construct =
-            GetEnclosingScope(first_pos, last_pos);
-        GetBlockInfo(enclosing_construct->begin_id)
-            ->phis_needing_state_vars.push_back(phi_id);
-      }
-    }
-  }
-
-  // For an ID defined in this function, determine if its evaluation and
-  // potential declaration needs special handling:
-  // - Compensate for the fact that dominance does not map directly to scope.
-  //   A definition could dominate its use, but a named definition in WGSL
-  //   at the location of the definition could go out of scope by the time
-  //   you reach the use.  In that case, we hoist the definition to a basic
-  //   block at the smallest scope enclosing both the definition and all
-  //   its uses.
-  // - If value is used in a different construct than its definition, then it
-  //   needs a named constant definition.  Otherwise we might sink an
-  //   expensive computation into control flow, and hence change performance.
-  for (auto& id_def_info_pair : def_info_) {
-    const auto def_id = id_def_info_pair.first;
-    auto* def_info = id_def_info_pair.second.get();
-    if (def_info->num_uses == 0) {
-      // There is no need to adjust the location of the declaration.
-      continue;
-    }
-    // The first use must be the at the SSA definition, because block order
-    // respects dominance.
-    const auto first_pos = def_info->block_pos;
-    const auto last_use_pos = def_info->last_use_pos;
-
-    const auto* def_in_construct =
-        GetBlockInfo(block_order_[first_pos])->construct;
-    // A definition in the first block of an kIfSelection or kSwitchSelection
-    // occurs before the branch, and so that definition should count as
-    // having been defined at the scope of the parent construct.
-    if (first_pos == def_in_construct->begin_pos) {
-      if ((def_in_construct->kind == Construct::kIfSelection) ||
-          (def_in_construct->kind == Construct::kSwitchSelection)) {
-        def_in_construct = def_in_construct->parent;
-      }
-    }
-
-    bool should_hoist = false;
-    if (!def_in_construct->ContainsPos(last_use_pos)) {
-      // To satisfy scoping, we have to hoist the definition out to an enclosing
-      // construct.
-      should_hoist = true;
-    } else {
-      // Avoid moving combinatorial values across constructs.  This is a
-      // simple heuristic to avoid changing the cost of an operation
-      // by moving it into or out of a loop, for example.
-      if ((def_info->storage_class == ast::StorageClass::kInvalid) &&
-          def_info->used_in_another_construct) {
-        should_hoist = true;
-      }
-    }
-
-    if (should_hoist) {
-      const auto* enclosing_construct =
-          GetEnclosingScope(first_pos, last_use_pos);
-      if (enclosing_construct == def_in_construct) {
-        // We can use a plain 'const' definition.
-        def_info->requires_named_const_def = true;
-      } else {
-        // We need to make a hoisted variable definition.
-        // TODO(dneto): Handle non-storable types, particularly pointers.
-        def_info->requires_hoisted_def = true;
-        auto* hoist_to_block = GetBlockInfo(enclosing_construct->begin_id);
-        hoist_to_block->hoisted_ids.push_back(def_id);
-      }
-    }
-  }
-}
-
-const Construct* FunctionEmitter::GetEnclosingScope(uint32_t first_pos,
-                                                    uint32_t last_pos) const {
-  const auto* enclosing_construct =
-      GetBlockInfo(block_order_[first_pos])->construct;
-  TINT_ASSERT(Reader, enclosing_construct != nullptr);
-  // Constructs are strictly nesting, so follow parent pointers
-  while (enclosing_construct &&
-         !enclosing_construct->ScopeContainsPos(last_pos)) {
-    // The scope of a continue construct is enclosed in its associated loop
-    // construct, but they are siblings in our construct tree.
-    const auto* sibling_loop = SiblingLoopConstruct(enclosing_construct);
-    // Go to the sibling loop if it exists, otherwise walk up to the parent.
-    enclosing_construct =
-        sibling_loop ? sibling_loop : enclosing_construct->parent;
-  }
-  // At worst, we go all the way out to the function construct.
-  TINT_ASSERT(Reader, enclosing_construct != nullptr);
-  return enclosing_construct;
-}
-
-TypedExpression FunctionEmitter::MakeNumericConversion(
-    const spvtools::opt::Instruction& inst) {
-  const auto opcode = inst.opcode();
-  auto* requested_type = parser_impl_.ConvertType(inst.type_id());
-  auto arg_expr = MakeOperand(inst, 0);
-  if (!arg_expr) {
-    return {};
-  }
-  arg_expr.type = arg_expr.type->UnwrapRef();
-
-  const Type* expr_type = nullptr;
-  if ((opcode == SpvOpConvertSToF) || (opcode == SpvOpConvertUToF)) {
-    if (arg_expr.type->IsIntegerScalarOrVector()) {
-      expr_type = requested_type;
-    } else {
-      Fail() << "operand for conversion to floating point must be integral "
-                "scalar or vector: "
-             << inst.PrettyPrint();
-    }
-  } else if (inst.opcode() == SpvOpConvertFToU) {
-    if (arg_expr.type->IsFloatScalarOrVector()) {
-      expr_type = parser_impl_.GetUnsignedIntMatchingShape(arg_expr.type);
-    } else {
-      Fail() << "operand for conversion to unsigned integer must be floating "
-                "point scalar or vector: "
-             << inst.PrettyPrint();
-    }
-  } else if (inst.opcode() == SpvOpConvertFToS) {
-    if (arg_expr.type->IsFloatScalarOrVector()) {
-      expr_type = parser_impl_.GetSignedIntMatchingShape(arg_expr.type);
-    } else {
-      Fail() << "operand for conversion to signed integer must be floating "
-                "point scalar or vector: "
-             << inst.PrettyPrint();
-    }
-  }
-  if (expr_type == nullptr) {
-    // The diagnostic has already been emitted.
-    return {};
-  }
-
-  ast::ExpressionList params;
-  params.push_back(arg_expr.expr);
-  TypedExpression result{
-      expr_type,
-      builder_.Construct(GetSourceForInst(inst), expr_type->Build(builder_),
-                         std::move(params))};
-
-  if (requested_type == expr_type) {
-    return result;
-  }
-  return {requested_type, create<ast::BitcastExpression>(
-                              GetSourceForInst(inst),
-                              requested_type->Build(builder_), result.expr)};
-}
-
-bool FunctionEmitter::EmitFunctionCall(const spvtools::opt::Instruction& inst) {
-  // We ignore function attributes such as Inline, DontInline, Pure, Const.
-  auto name = namer_.Name(inst.GetSingleWordInOperand(0));
-  auto* function = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(name));
-
-  ast::ExpressionList args;
-  for (uint32_t iarg = 1; iarg < inst.NumInOperands(); ++iarg) {
-    auto expr = MakeOperand(inst, iarg);
-    if (!expr) {
-      return false;
-    }
-    // Functions cannot use references as parameters, so we need to pass by
-    // pointer if the operand is of pointer type.
-    expr = AddressOfIfNeeded(
-        expr, def_use_mgr_->GetDef(inst.GetSingleWordInOperand(iarg)));
-    args.emplace_back(expr.expr);
-  }
-  if (failed()) {
-    return false;
-  }
-  auto* call_expr =
-      create<ast::CallExpression>(Source{}, function, std::move(args));
-  auto* result_type = parser_impl_.ConvertType(inst.type_id());
-  if (!result_type) {
-    return Fail() << "internal error: no mapped type result of call: "
-                  << inst.PrettyPrint();
-  }
-
-  if (result_type->Is<Void>()) {
-    return nullptr !=
-           AddStatement(create<ast::CallStatement>(Source{}, call_expr));
-  }
-
-  return EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr});
-}
-
-bool FunctionEmitter::EmitControlBarrier(
-    const spvtools::opt::Instruction& inst) {
-  uint32_t operands[3];
-  for (int i = 0; i < 3; i++) {
-    auto id = inst.GetSingleWordInOperand(i);
-    if (auto* constant = constant_mgr_->FindDeclaredConstant(id)) {
-      operands[i] = constant->GetU32();
-    } else {
-      return Fail() << "invalid or missing operands for control barrier";
-    }
-  }
-
-  uint32_t execution = operands[0];
-  uint32_t memory = operands[1];
-  uint32_t semantics = operands[2];
-
-  if (execution != SpvScopeWorkgroup) {
-    return Fail() << "unsupported control barrier execution scope: "
-                  << "expected Workgroup (2), got: " << execution;
-  }
-  if (semantics & SpvMemorySemanticsAcquireReleaseMask) {
-    semantics &= ~SpvMemorySemanticsAcquireReleaseMask;
-  } else {
-    return Fail() << "control barrier semantics requires acquire and release";
-  }
-  if (semantics & SpvMemorySemanticsWorkgroupMemoryMask) {
-    if (memory != SpvScopeWorkgroup) {
-      return Fail() << "workgroupBarrier requires workgroup memory scope";
-    }
-    AddStatement(create<ast::CallStatement>(builder_.Call("workgroupBarrier")));
-    semantics &= ~SpvMemorySemanticsWorkgroupMemoryMask;
-  }
-  if (semantics & SpvMemorySemanticsUniformMemoryMask) {
-    if (memory != SpvScopeDevice) {
-      return Fail() << "storageBarrier requires device memory scope";
-    }
-    AddStatement(create<ast::CallStatement>(builder_.Call("storageBarrier")));
-    semantics &= ~SpvMemorySemanticsUniformMemoryMask;
-  }
-  if (semantics) {
-    return Fail() << "unsupported control barrier semantics: " << semantics;
-  }
-  return true;
-}
-
-TypedExpression FunctionEmitter::MakeBuiltinCall(
-    const spvtools::opt::Instruction& inst) {
-  const auto builtin = GetBuiltin(inst.opcode());
-  auto* name = sem::str(builtin);
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(name));
-
-  ast::ExpressionList params;
-  const Type* first_operand_type = nullptr;
-  for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
-    TypedExpression operand = MakeOperand(inst, iarg);
-    if (first_operand_type == nullptr) {
-      first_operand_type = operand.type;
-    }
-    params.emplace_back(operand.expr);
-  }
-  auto* call_expr =
-      create<ast::CallExpression>(Source{}, ident, std::move(params));
-  auto* result_type = parser_impl_.ConvertType(inst.type_id());
-  if (!result_type) {
-    Fail() << "internal error: no mapped type result of call: "
-           << inst.PrettyPrint();
-    return {};
-  }
-  TypedExpression call{result_type, call_expr};
-  return parser_impl_.RectifyForcedResultType(call, inst, first_operand_type);
-}
-
-TypedExpression FunctionEmitter::MakeSimpleSelect(
-    const spvtools::opt::Instruction& inst) {
-  auto condition = MakeOperand(inst, 0);
-  auto true_value = MakeOperand(inst, 1);
-  auto false_value = MakeOperand(inst, 2);
-
-  // SPIR-V validation requires:
-  // - the condition to be bool or bool vector, so we don't check it here.
-  // - true_value false_value, and result type to match.
-  // - you can't select over pointers or pointer vectors, unless you also have
-  //   a VariablePointers* capability, which is not allowed in by WebGPU.
-  auto* op_ty = true_value.type;
-  if (op_ty->Is<Vector>() || op_ty->IsFloatScalar() ||
-      op_ty->IsIntegerScalar() || op_ty->Is<Bool>()) {
-    ast::ExpressionList params;
-    params.push_back(false_value.expr);
-    params.push_back(true_value.expr);
-    // The condition goes last.
-    params.push_back(condition.expr);
-    return {op_ty, create<ast::CallExpression>(
-                       Source{},
-                       create<ast::IdentifierExpression>(
-                           Source{}, builder_.Symbols().Register("select")),
-                       std::move(params))};
-  }
-  return {};
-}
-
-Source FunctionEmitter::GetSourceForInst(
-    const spvtools::opt::Instruction& inst) const {
-  return parser_impl_.GetSourceForInst(&inst);
-}
-
-const spvtools::opt::Instruction* FunctionEmitter::GetImage(
-    const spvtools::opt::Instruction& inst) {
-  if (inst.NumInOperands() == 0) {
-    Fail() << "not an image access instruction: " << inst.PrettyPrint();
-    return nullptr;
-  }
-  // The image or sampled image operand is always the first operand.
-  const auto image_or_sampled_image_operand_id = inst.GetSingleWordInOperand(0);
-  const auto* image = parser_impl_.GetMemoryObjectDeclarationForHandle(
-      image_or_sampled_image_operand_id, true);
-  if (!image) {
-    Fail() << "internal error: couldn't find image for " << inst.PrettyPrint();
-    return nullptr;
-  }
-  return image;
-}
-
-const Texture* FunctionEmitter::GetImageType(
-    const spvtools::opt::Instruction& image) {
-  const Pointer* ptr_type = parser_impl_.GetTypeForHandleVar(image);
-  if (!parser_impl_.success()) {
-    Fail();
-    return {};
-  }
-  if (!ptr_type) {
-    Fail() << "invalid texture type for " << image.PrettyPrint();
-    return {};
-  }
-  auto* result = ptr_type->type->UnwrapAll()->As<Texture>();
-  if (!result) {
-    Fail() << "invalid texture type for " << image.PrettyPrint();
-    return {};
-  }
-  return result;
-}
-
-const ast::Expression* FunctionEmitter::GetImageExpression(
-    const spvtools::opt::Instruction& inst) {
-  auto* image = GetImage(inst);
-  if (!image) {
-    return nullptr;
-  }
-  auto name = namer_.Name(image->result_id());
-  return create<ast::IdentifierExpression>(GetSourceForInst(inst),
-                                           builder_.Symbols().Register(name));
-}
-
-const ast::Expression* FunctionEmitter::GetSamplerExpression(
-    const spvtools::opt::Instruction& inst) {
-  // The sampled image operand is always the first operand.
-  const auto image_or_sampled_image_operand_id = inst.GetSingleWordInOperand(0);
-  const auto* image = parser_impl_.GetMemoryObjectDeclarationForHandle(
-      image_or_sampled_image_operand_id, false);
-  if (!image) {
-    Fail() << "internal error: couldn't find sampler for "
-           << inst.PrettyPrint();
-    return nullptr;
-  }
-  auto name = namer_.Name(image->result_id());
-  return create<ast::IdentifierExpression>(GetSourceForInst(inst),
-                                           builder_.Symbols().Register(name));
-}
-
-bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) {
-  ast::ExpressionList args;
-  const auto opcode = inst.opcode();
-
-  // Form the texture operand.
-  const spvtools::opt::Instruction* image = GetImage(inst);
-  if (!image) {
-    return false;
-  }
-  args.push_back(GetImageExpression(inst));
-
-  // Form the sampler operand, if needed.
-  if (IsSampledImageAccess(opcode)) {
-    // Form the sampler operand.
-    if (auto* sampler = GetSamplerExpression(inst)) {
-      args.push_back(sampler);
-    } else {
-      return false;
-    }
-  }
-
-  // Find the texture type.
-  const Pointer* texture_ptr_type = parser_impl_.GetTypeForHandleVar(*image);
-  if (!texture_ptr_type) {
-    return Fail();
-  }
-  const Texture* texture_type =
-      texture_ptr_type->type->UnwrapAll()->As<Texture>();
-
-  if (!texture_type) {
-    return Fail();
-  }
-
-  // This is the SPIR-V operand index.  We're done with the first operand.
-  uint32_t arg_index = 1;
-
-  // Push the coordinates operands.
-  auto coords = MakeCoordinateOperandsForImageAccess(inst);
-  if (coords.empty()) {
-    return false;
-  }
-  args.insert(args.end(), coords.begin(), coords.end());
-  // Skip the coordinates operand.
-  arg_index++;
-
-  const auto num_args = inst.NumInOperands();
-
-  // Consumes the depth-reference argument, pushing it onto the end of
-  // the parameter list. Issues a diagnostic and returns false on error.
-  auto consume_dref = [&]() -> bool {
-    if (arg_index < num_args) {
-      args.push_back(MakeOperand(inst, arg_index).expr);
-      arg_index++;
-    } else {
-      return Fail()
-             << "image depth-compare instruction is missing a Dref operand: "
-             << inst.PrettyPrint();
-    }
-    return true;
-  };
-
-  std::string builtin_name;
-  bool use_level_of_detail_suffix = true;
-  bool is_dref_sample = false;
-  bool is_gather_or_dref_gather = false;
-  bool is_non_dref_sample = false;
-  switch (opcode) {
-    case SpvOpImageSampleImplicitLod:
-    case SpvOpImageSampleExplicitLod:
-    case SpvOpImageSampleProjImplicitLod:
-    case SpvOpImageSampleProjExplicitLod:
-      is_non_dref_sample = true;
-      builtin_name = "textureSample";
-      break;
-    case SpvOpImageSampleDrefImplicitLod:
-    case SpvOpImageSampleDrefExplicitLod:
-    case SpvOpImageSampleProjDrefImplicitLod:
-    case SpvOpImageSampleProjDrefExplicitLod:
-      is_dref_sample = true;
-      builtin_name = "textureSampleCompare";
-      if (!consume_dref()) {
-        return false;
-      }
-      break;
-    case SpvOpImageGather:
-      is_gather_or_dref_gather = true;
-      builtin_name = "textureGather";
-      if (!texture_type->Is<DepthTexture>()) {
-        // The explicit component is the *first* argument in WGSL.
-        args.insert(args.begin(), ToI32(MakeOperand(inst, arg_index)).expr);
-      }
-      // Skip over the component operand, even for depth textures.
-      arg_index++;
-      break;
-    case SpvOpImageDrefGather:
-      is_gather_or_dref_gather = true;
-      builtin_name = "textureGatherCompare";
-      if (!consume_dref()) {
-        return false;
-      }
-      break;
-    case SpvOpImageFetch:
-    case SpvOpImageRead:
-      // Read a single texel from a sampled or storage image.
-      builtin_name = "textureLoad";
-      use_level_of_detail_suffix = false;
-      break;
-    case SpvOpImageWrite:
-      builtin_name = "textureStore";
-      use_level_of_detail_suffix = false;
-      if (arg_index < num_args) {
-        auto texel = MakeOperand(inst, arg_index);
-        auto* converted_texel =
-            ConvertTexelForStorage(inst, texel, texture_type);
-        if (!converted_texel) {
-          return false;
-        }
-
-        args.push_back(converted_texel);
-        arg_index++;
-      } else {
-        return Fail() << "image write is missing a Texel operand: "
-                      << inst.PrettyPrint();
-      }
-      break;
-    default:
-      return Fail() << "internal error: unrecognized image access: "
-                    << inst.PrettyPrint();
-  }
-
-  // Loop over the image operands, looking for extra operands to the builtin.
-  // Except we uroll the loop.
-  uint32_t image_operands_mask = 0;
-  if (arg_index < num_args) {
-    image_operands_mask = inst.GetSingleWordInOperand(arg_index);
-    arg_index++;
-  }
-  if (arg_index < num_args &&
-      (image_operands_mask & SpvImageOperandsBiasMask)) {
-    if (is_dref_sample) {
-      return Fail() << "WGSL does not support depth-reference sampling with "
-                       "level-of-detail bias: "
-                    << inst.PrettyPrint();
-    }
-    if (is_gather_or_dref_gather) {
-      return Fail() << "WGSL does not support image gather with "
-                       "level-of-detail bias: "
-                    << inst.PrettyPrint();
-    }
-    builtin_name += "Bias";
-    args.push_back(MakeOperand(inst, arg_index).expr);
-    image_operands_mask ^= SpvImageOperandsBiasMask;
-    arg_index++;
-  }
-  if (arg_index < num_args && (image_operands_mask & SpvImageOperandsLodMask)) {
-    if (use_level_of_detail_suffix) {
-      builtin_name += "Level";
-    }
-    if (is_dref_sample || is_gather_or_dref_gather) {
-      // Metal only supports Lod = 0 for comparison sampling without
-      // derivatives.
-      // Vulkan SPIR-V does not allow Lod with OpImageGather or
-      // OpImageDrefGather.
-      if (!IsFloatZero(inst.GetSingleWordInOperand(arg_index))) {
-        return Fail() << "WGSL comparison sampling without derivatives "
-                         "requires level-of-detail 0.0"
-                      << inst.PrettyPrint();
-      }
-      // Don't generate the Lod argument.
-    } else {
-      // Generate the Lod argument.
-      TypedExpression lod = MakeOperand(inst, arg_index);
-      // When sampling from a depth texture, the Lod operand must be an I32.
-      if (texture_type->Is<DepthTexture>()) {
-        // Convert it to a signed integer type.
-        lod = ToI32(lod);
-      }
-      args.push_back(lod.expr);
-    }
-
-    image_operands_mask ^= SpvImageOperandsLodMask;
-    arg_index++;
-  } else if ((opcode == SpvOpImageFetch || opcode == SpvOpImageRead) &&
-             !texture_type
-                  ->IsAnyOf<DepthMultisampledTexture, MultisampledTexture>()) {
-    // textureLoad requires an explicit level-of-detail parameter for
-    // non-multisampled texture types.
-    args.push_back(parser_impl_.MakeNullValue(ty_.I32()));
-  }
-  if (arg_index + 1 < num_args &&
-      (image_operands_mask & SpvImageOperandsGradMask)) {
-    if (is_dref_sample) {
-      return Fail() << "WGSL does not support depth-reference sampling with "
-                       "explicit gradient: "
-                    << inst.PrettyPrint();
-    }
-    if (is_gather_or_dref_gather) {
-      return Fail() << "WGSL does not support image gather with "
-                       "explicit gradient: "
-                    << inst.PrettyPrint();
-    }
-    builtin_name += "Grad";
-    args.push_back(MakeOperand(inst, arg_index).expr);
-    args.push_back(MakeOperand(inst, arg_index + 1).expr);
-    image_operands_mask ^= SpvImageOperandsGradMask;
-    arg_index += 2;
-  }
-  if (arg_index < num_args &&
-      (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
-    if (!IsImageSamplingOrGatherOrDrefGather(opcode)) {
-      return Fail() << "ConstOffset is only permitted for sampling, gather, or "
-                       "depth-reference gather operations: "
-                    << inst.PrettyPrint();
-    }
-    switch (texture_type->dims) {
-      case ast::TextureDimension::k2d:
-      case ast::TextureDimension::k2dArray:
-      case ast::TextureDimension::k3d:
-        break;
-      default:
-        return Fail() << "ConstOffset is only permitted for 2D, 2D Arrayed, "
-                         "and 3D textures: "
-                      << inst.PrettyPrint();
-    }
-
-    args.push_back(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
-    image_operands_mask ^= SpvImageOperandsConstOffsetMask;
-    arg_index++;
-  }
-  if (arg_index < num_args &&
-      (image_operands_mask & SpvImageOperandsSampleMask)) {
-    // TODO(dneto): only permitted with ImageFetch
-    args.push_back(ToI32(MakeOperand(inst, arg_index)).expr);
-    image_operands_mask ^= SpvImageOperandsSampleMask;
-    arg_index++;
-  }
-  if (image_operands_mask) {
-    return Fail() << "unsupported image operands (" << image_operands_mask
-                  << "): " << inst.PrettyPrint();
-  }
-
-  // If any of the arguments are nullptr, then we've failed.
-  if (std::any_of(args.begin(), args.end(),
-                  [](auto* expr) { return expr == nullptr; })) {
-    return false;
-  }
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(builtin_name));
-  auto* call_expr =
-      create<ast::CallExpression>(Source{}, ident, std::move(args));
-
-  if (inst.type_id() != 0) {
-    // It returns a value.
-    const ast::Expression* value = call_expr;
-
-    // The result type, derived from the SPIR-V instruction.
-    auto* result_type = parser_impl_.ConvertType(inst.type_id());
-    auto* result_component_type = result_type;
-    if (auto* result_vector_type = As<Vector>(result_type)) {
-      result_component_type = result_vector_type->type;
-    }
-
-    // For depth textures, the arity might mot match WGSL:
-    //  Operation           SPIR-V                     WGSL
-    //   normal sampling     vec4  ImplicitLod          f32
-    //   normal sampling     vec4  ExplicitLod          f32
-    //   compare sample      f32   DrefImplicitLod      f32
-    //   compare sample      f32   DrefExplicitLod      f32
-    //   texel load          vec4  ImageFetch           f32
-    //   normal gather       vec4  ImageGather          vec4
-    //   dref gather         vec4  ImageDrefGather      vec4
-    // Construct a 4-element vector with the result from the builtin in the
-    // first component.
-    if (texture_type->IsAnyOf<DepthTexture, DepthMultisampledTexture>()) {
-      if (is_non_dref_sample || (opcode == SpvOpImageFetch)) {
-        value = builder_.Construct(
-            Source{},
-            result_type->Build(builder_),  // a vec4
-            ast::ExpressionList{
-                value, parser_impl_.MakeNullValue(result_component_type),
-                parser_impl_.MakeNullValue(result_component_type),
-                parser_impl_.MakeNullValue(result_component_type)});
-      }
-    }
-
-    // If necessary, convert the result to the signedness of the instruction
-    // result type. Compare the SPIR-V image's sampled component type with the
-    // component of the result type of the SPIR-V instruction.
-    auto* spirv_image_type =
-        parser_impl_.GetSpirvTypeForHandleMemoryObjectDeclaration(*image);
-    if (!spirv_image_type || (spirv_image_type->opcode() != SpvOpTypeImage)) {
-      return Fail() << "invalid image type for image memory object declaration "
-                    << image->PrettyPrint();
-    }
-    auto* expected_component_type =
-        parser_impl_.ConvertType(spirv_image_type->GetSingleWordInOperand(0));
-    if (expected_component_type != result_component_type) {
-      // This occurs if one is signed integer and the other is unsigned integer,
-      // or vice versa. Perform a bitcast.
-      value = create<ast::BitcastExpression>(
-          Source{}, result_type->Build(builder_), call_expr);
-    }
-    if (!expected_component_type->Is<F32>() && IsSampledImageAccess(opcode)) {
-      // WGSL permits sampled image access only on float textures.
-      // Reject this case in the SPIR-V reader, at least until SPIR-V validation
-      // catches up with this rule and can reject it earlier in the workflow.
-      return Fail() << "sampled image must have float component type";
-    }
-
-    EmitConstDefOrWriteToHoistedVar(inst, {result_type, value});
-  } else {
-    // It's an image write. No value is returned, so make a statement out
-    // of the call.
-    AddStatement(create<ast::CallStatement>(Source{}, call_expr));
-  }
-  return success();
-}
-
-bool FunctionEmitter::EmitImageQuery(const spvtools::opt::Instruction& inst) {
-  // TODO(dneto): Reject cases that are valid in Vulkan but invalid in WGSL.
-  const spvtools::opt::Instruction* image = GetImage(inst);
-  if (!image) {
-    return false;
-  }
-  auto* texture_type = GetImageType(*image);
-  if (!texture_type) {
-    return false;
-  }
-
-  const auto opcode = inst.opcode();
-  switch (opcode) {
-    case SpvOpImageQuerySize:
-    case SpvOpImageQuerySizeLod: {
-      ast::ExpressionList exprs;
-      // Invoke textureDimensions.
-      // If the texture is arrayed, combine with the result from
-      // textureNumLayers.
-      auto* dims_ident = create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register("textureDimensions"));
-      ast::ExpressionList dims_args{GetImageExpression(inst)};
-      if (opcode == SpvOpImageQuerySizeLod) {
-        dims_args.push_back(ToI32(MakeOperand(inst, 1)).expr);
-      }
-      const ast::Expression* dims_call =
-          create<ast::CallExpression>(Source{}, dims_ident, dims_args);
-      auto dims = texture_type->dims;
-      if ((dims == ast::TextureDimension::kCube) ||
-          (dims == ast::TextureDimension::kCubeArray)) {
-        // textureDimension returns a 3-element vector but SPIR-V expects 2.
-        dims_call = create<ast::MemberAccessorExpression>(Source{}, dims_call,
-                                                          PrefixSwizzle(2));
-      }
-      exprs.push_back(dims_call);
-      if (ast::IsTextureArray(dims)) {
-        auto* layers_ident = create<ast::IdentifierExpression>(
-            Source{}, builder_.Symbols().Register("textureNumLayers"));
-        exprs.push_back(create<ast::CallExpression>(
-            Source{}, layers_ident,
-            ast::ExpressionList{GetImageExpression(inst)}));
-      }
-      auto* result_type = parser_impl_.ConvertType(inst.type_id());
-      TypedExpression expr = {
-          result_type,
-          builder_.Construct(Source{}, result_type->Build(builder_), exprs)};
-      return EmitConstDefOrWriteToHoistedVar(inst, expr);
-    }
-    case SpvOpImageQueryLod:
-      return Fail() << "WGSL does not support querying the level of detail of "
-                       "an image: "
-                    << inst.PrettyPrint();
-    case SpvOpImageQueryLevels:
-    case SpvOpImageQuerySamples: {
-      const auto* name = (opcode == SpvOpImageQueryLevels)
-                             ? "textureNumLevels"
-                             : "textureNumSamples";
-      auto* levels_ident = create<ast::IdentifierExpression>(
-          Source{}, builder_.Symbols().Register(name));
-      const ast::Expression* ast_expr = create<ast::CallExpression>(
-          Source{}, levels_ident,
-          ast::ExpressionList{GetImageExpression(inst)});
-      auto* result_type = parser_impl_.ConvertType(inst.type_id());
-      // The SPIR-V result type must be integer scalar. The WGSL bulitin
-      // returns i32. If they aren't the same then convert the result.
-      if (!result_type->Is<I32>()) {
-        ast_expr = builder_.Construct(Source{}, result_type->Build(builder_),
-                                      ast::ExpressionList{ast_expr});
-      }
-      TypedExpression expr{result_type, ast_expr};
-      return EmitConstDefOrWriteToHoistedVar(inst, expr);
-    }
-    default:
-      break;
-  }
-  return Fail() << "unhandled image query: " << inst.PrettyPrint();
-}
-
-ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess(
-    const spvtools::opt::Instruction& inst) {
-  if (!parser_impl_.success()) {
-    Fail();
-    return {};
-  }
-  const spvtools::opt::Instruction* image = GetImage(inst);
-  if (!image) {
-    return {};
-  }
-  if (inst.NumInOperands() < 1) {
-    Fail() << "image access is missing a coordinate parameter: "
-           << inst.PrettyPrint();
-    return {};
-  }
-
-  // In SPIR-V for Shader, coordinates are:
-  //  - floating point for sampling, dref sampling, gather, dref gather
-  //  - integral for fetch, read, write
-  // In WGSL:
-  //  - floating point for sampling, dref sampling, gather, dref gather
-  //  - signed integral for textureLoad, textureStore
-  //
-  // The only conversions we have to do for WGSL are:
-  //  - When the coordinates are unsigned integral, convert them to signed.
-  //  - Array index is always i32
-
-  // The coordinates parameter is always in position 1.
-  TypedExpression raw_coords(MakeOperand(inst, 1));
-  if (!raw_coords) {
-    return {};
-  }
-  const Texture* texture_type = GetImageType(*image);
-  if (!texture_type) {
-    return {};
-  }
-  ast::TextureDimension dim = texture_type->dims;
-  // Number of regular coordinates.
-  uint32_t num_axes = ast::NumCoordinateAxes(dim);
-  bool is_arrayed = ast::IsTextureArray(dim);
-  if ((num_axes == 0) || (num_axes > 3)) {
-    Fail() << "unsupported image dimensionality for "
-           << texture_type->TypeInfo().name << " prompted by "
-           << inst.PrettyPrint();
-  }
-  bool is_proj = false;
-  switch (inst.opcode()) {
-    case SpvOpImageSampleProjImplicitLod:
-    case SpvOpImageSampleProjExplicitLod:
-    case SpvOpImageSampleProjDrefImplicitLod:
-    case SpvOpImageSampleProjDrefExplicitLod:
-      is_proj = true;
-      break;
-    default:
-      break;
-  }
-
-  const auto num_coords_required =
-      num_axes + (is_arrayed ? 1 : 0) + (is_proj ? 1 : 0);
-  uint32_t num_coords_supplied = 0;
-  auto* component_type = raw_coords.type;
-  if (component_type->IsFloatScalar() || component_type->IsIntegerScalar()) {
-    num_coords_supplied = 1;
-  } else if (auto* vec_type = As<Vector>(raw_coords.type)) {
-    component_type = vec_type->type;
-    num_coords_supplied = vec_type->size;
-  }
-  if (num_coords_supplied == 0) {
-    Fail() << "bad or unsupported coordinate type for image access: "
-           << inst.PrettyPrint();
-    return {};
-  }
-  if (num_coords_required > num_coords_supplied) {
-    Fail() << "image access required " << num_coords_required
-           << " coordinate components, but only " << num_coords_supplied
-           << " provided, in: " << inst.PrettyPrint();
-    return {};
-  }
-
-  ast::ExpressionList result;
-
-  // Generates the expression for the WGSL coordinates, when it is a prefix
-  // swizzle with num_axes.  If the result would be unsigned, also converts
-  // it to a signed value of the same shape (scalar or vector).
-  // Use a lambda to make it easy to only generate the expressions when we
-  // will actually use them.
-  auto prefix_swizzle_expr = [this, num_axes, component_type, is_proj,
-                              raw_coords]() -> const ast::Expression* {
-    auto* swizzle_type =
-        (num_axes == 1) ? component_type : ty_.Vector(component_type, num_axes);
-    auto* swizzle = create<ast::MemberAccessorExpression>(
-        Source{}, raw_coords.expr, PrefixSwizzle(num_axes));
-    if (is_proj) {
-      auto* q = create<ast::MemberAccessorExpression>(Source{}, raw_coords.expr,
-                                                      Swizzle(num_axes));
-      auto* proj_div = builder_.Div(swizzle, q);
-      return ToSignedIfUnsigned({swizzle_type, proj_div}).expr;
-    } else {
-      return ToSignedIfUnsigned({swizzle_type, swizzle}).expr;
-    }
-  };
-
-  if (is_arrayed) {
-    // The source must be a vector. It has at least one coordinate component
-    // and it must have an array component.  Use a vector swizzle to get the
-    // first `num_axes` components.
-    result.push_back(prefix_swizzle_expr());
-
-    // Now get the array index.
-    const ast::Expression* array_index =
-        builder_.MemberAccessor(raw_coords.expr, Swizzle(num_axes));
-    if (component_type->IsFloatScalar()) {
-      // When converting from a float array layer to integer, Vulkan requires
-      // round-to-nearest, with preference for round-to-nearest-even.
-      // But i32(f32) in WGSL has unspecified rounding mode, so we have to
-      // explicitly specify the rounding.
-      array_index = builder_.Call("round", array_index);
-    }
-    // Convert it to a signed integer type, if needed.
-    result.push_back(ToI32({component_type, array_index}).expr);
-  } else {
-    if (num_coords_supplied == num_coords_required && !is_proj) {
-      // Pass the value through, with possible unsigned->signed conversion.
-      result.push_back(ToSignedIfUnsigned(raw_coords).expr);
-    } else {
-      // There are more coordinates supplied than needed. So the source type
-      // is a vector. Use a vector swizzle to get the first `num_axes`
-      // components.
-      result.push_back(prefix_swizzle_expr());
-    }
-  }
-  return result;
-}
-
-const ast::Expression* FunctionEmitter::ConvertTexelForStorage(
-    const spvtools::opt::Instruction& inst,
-    TypedExpression texel,
-    const Texture* texture_type) {
-  auto* storage_texture_type = As<StorageTexture>(texture_type);
-  auto* src_type = texel.type;
-  if (!storage_texture_type) {
-    Fail() << "writing to other than storage texture: " << inst.PrettyPrint();
-    return nullptr;
-  }
-  const auto format = storage_texture_type->format;
-  auto* dest_type = parser_impl_.GetTexelTypeForFormat(format);
-  if (!dest_type) {
-    Fail();
-    return nullptr;
-  }
-
-  // The texel type is always a 4-element vector.
-  const uint32_t dest_count = 4u;
-  TINT_ASSERT(Reader, dest_type->Is<Vector>() &&
-                          dest_type->As<Vector>()->size == dest_count);
-  TINT_ASSERT(Reader, dest_type->IsFloatVector() ||
-                          dest_type->IsUnsignedIntegerVector() ||
-                          dest_type->IsSignedIntegerVector());
-
-  if (src_type == dest_type) {
-    return texel.expr;
-  }
-
-  // Component type must match floatness, or integral signedness.
-  if ((src_type->IsFloatScalarOrVector() != dest_type->IsFloatVector()) ||
-      (src_type->IsUnsignedIntegerVector() !=
-       dest_type->IsUnsignedIntegerVector()) ||
-      (src_type->IsSignedIntegerVector() !=
-       dest_type->IsSignedIntegerVector())) {
-    Fail() << "invalid texel type for storage texture write: component must be "
-              "float, signed integer, or unsigned integer "
-              "to match the texture channel type: "
-           << inst.PrettyPrint();
-    return nullptr;
-  }
-
-  const auto required_count = parser_impl_.GetChannelCountForFormat(format);
-  TINT_ASSERT(Reader, 0 < required_count && required_count <= 4);
-
-  const uint32_t src_count =
-      src_type->IsScalar() ? 1 : src_type->As<Vector>()->size;
-  if (src_count < required_count) {
-    Fail() << "texel has too few components for storage texture: " << src_count
-           << " provided but " << required_count
-           << " required, in: " << inst.PrettyPrint();
-    return nullptr;
-  }
-
-  // It's valid for required_count < src_count. The extra components will
-  // be written out but the textureStore will ignore them.
-
-  if (src_count < dest_count) {
-    // Expand the texel to a 4 element vector.
-    auto* component_type =
-        texel.type->IsScalar() ? texel.type : texel.type->As<Vector>()->type;
-    texel.type = ty_.Vector(component_type, dest_count);
-    ast::ExpressionList exprs;
-    exprs.push_back(texel.expr);
-    for (auto i = src_count; i < dest_count; i++) {
-      exprs.push_back(parser_impl_.MakeNullExpression(component_type).expr);
-    }
-    texel.expr = builder_.Construct(Source{}, texel.type->Build(builder_),
-                                    std::move(exprs));
-  }
-
-  return texel.expr;
-}
-
-TypedExpression FunctionEmitter::ToI32(TypedExpression value) {
-  if (!value || value.type->Is<I32>()) {
-    return value;
-  }
-  return {ty_.I32(), builder_.Construct(Source{}, builder_.ty.i32(),
-                                        ast::ExpressionList{value.expr})};
-}
-
-TypedExpression FunctionEmitter::ToSignedIfUnsigned(TypedExpression value) {
-  if (!value || !value.type->IsUnsignedScalarOrVector()) {
-    return value;
-  }
-  if (auto* vec_type = value.type->As<Vector>()) {
-    auto* new_type = ty_.Vector(ty_.I32(), vec_type->size);
-    return {new_type, builder_.Construct(new_type->Build(builder_),
-                                         ast::ExpressionList{value.expr})};
-  }
-  return ToI32(value);
-}
-
-TypedExpression FunctionEmitter::MakeArrayLength(
-    const spvtools::opt::Instruction& inst) {
-  if (inst.NumInOperands() != 2) {
-    // Binary parsing will fail on this anyway.
-    Fail() << "invalid array length: requires 2 operands: "
-           << inst.PrettyPrint();
-    return {};
-  }
-  const auto struct_ptr_id = inst.GetSingleWordInOperand(0);
-  const auto field_index = inst.GetSingleWordInOperand(1);
-  const auto struct_ptr_type_id =
-      def_use_mgr_->GetDef(struct_ptr_id)->type_id();
-  // Trace through the pointer type to get to the struct type.
-  const auto struct_type_id =
-      def_use_mgr_->GetDef(struct_ptr_type_id)->GetSingleWordInOperand(1);
-  const auto field_name = namer_.GetMemberName(struct_type_id, field_index);
-  if (field_name.empty()) {
-    Fail() << "struct index out of bounds for array length: "
-           << inst.PrettyPrint();
-    return {};
-  }
-
-  auto member_expr = MakeExpression(struct_ptr_id);
-  if (!member_expr) {
-    return {};
-  }
-  if (member_expr.type->Is<Pointer>()) {
-    member_expr = Dereference(member_expr);
-  }
-  auto* member_ident = create<ast::IdentifierExpression>(
-      Source{}, builder_.Symbols().Register(field_name));
-  auto* member_access = create<ast::MemberAccessorExpression>(
-      Source{}, member_expr.expr, member_ident);
-
-  // Generate the builtin function call.
-  auto* call_expr =
-      builder_.Call(Source{}, "arrayLength", builder_.AddressOf(member_access));
-
-  return {parser_impl_.ConvertType(inst.type_id()), call_expr};
-}
-
-TypedExpression FunctionEmitter::MakeOuterProduct(
-    const spvtools::opt::Instruction& inst) {
-  // Synthesize the result.
-  auto col = MakeOperand(inst, 0);
-  auto row = MakeOperand(inst, 1);
-  auto* col_ty = As<Vector>(col.type);
-  auto* row_ty = As<Vector>(row.type);
-  auto* result_ty = As<Matrix>(parser_impl_.ConvertType(inst.type_id()));
-  if (!col_ty || !col_ty || !result_ty || result_ty->type != col_ty->type ||
-      result_ty->type != row_ty->type || result_ty->columns != row_ty->size ||
-      result_ty->rows != col_ty->size) {
-    Fail() << "invalid outer product instruction: bad types "
-           << inst.PrettyPrint();
-    return {};
-  }
-
-  // Example:
-  //    c : vec3 column vector
-  //    r : vec2 row vector
-  //    OuterProduct c r : mat2x3 (2 columns, 3 rows)
-  //    Result:
-  //      | c.x * r.x   c.x * r.y |
-  //      | c.y * r.x   c.y * r.y |
-  //      | c.z * r.x   c.z * r.y |
-
-  ast::ExpressionList result_columns;
-  for (uint32_t icol = 0; icol < result_ty->columns; icol++) {
-    ast::ExpressionList result_row;
-    auto* row_factor = create<ast::MemberAccessorExpression>(Source{}, row.expr,
-                                                             Swizzle(icol));
-    for (uint32_t irow = 0; irow < result_ty->rows; irow++) {
-      auto* column_factor = create<ast::MemberAccessorExpression>(
-          Source{}, col.expr, Swizzle(irow));
-      auto* elem = create<ast::BinaryExpression>(
-          Source{}, ast::BinaryOp::kMultiply, row_factor, column_factor);
-      result_row.push_back(elem);
-    }
-    result_columns.push_back(
-        builder_.Construct(Source{}, col_ty->Build(builder_), result_row));
-  }
-  return {result_ty, builder_.Construct(Source{}, result_ty->Build(builder_),
-                                        result_columns)};
-}
-
-bool FunctionEmitter::MakeVectorInsertDynamic(
-    const spvtools::opt::Instruction& inst) {
-  // For
-  //    %result = OpVectorInsertDynamic %type %src_vector %component %index
-  // there are two cases.
-  //
-  // Case 1:
-  //   The %src_vector value has already been hoisted into a variable.
-  //   In this case, assign %src_vector to that variable, then write the
-  //   component into the right spot:
-  //
-  //    hoisted = src_vector;
-  //    hoisted[index] = component;
-  //
-  // Case 2:
-  //   The %src_vector value is not hoisted. In this case, make a temporary
-  //   variable with the %src_vector contents, then write the component,
-  //   and then make a let-declaration that reads the value out:
-  //
-  //    var temp : type = src_vector;
-  //    temp[index] = component;
-  //    let result : type = temp;
-  //
-  //   Then use result everywhere the original SPIR-V id is used.  Using a const
-  //   like this avoids constantly reloading the value many times.
-
-  auto* type = parser_impl_.ConvertType(inst.type_id());
-  auto src_vector = MakeOperand(inst, 0);
-  auto component = MakeOperand(inst, 1);
-  auto index = MakeOperand(inst, 2);
-
-  std::string var_name;
-  auto original_value_name = namer_.Name(inst.result_id());
-  const bool hoisted = WriteIfHoistedVar(inst, src_vector);
-  if (hoisted) {
-    // The variable was already declared in an earlier block.
-    var_name = original_value_name;
-    // Assign the source vector value to it.
-    builder_.Assign({}, builder_.Expr(var_name), src_vector.expr);
-  } else {
-    // Synthesize the temporary variable.
-    // It doesn't correspond to a SPIR-V ID, so we don't use the ordinary
-    // API in parser_impl_.
-    var_name = namer_.MakeDerivedName(original_value_name);
-
-    auto* temp_var = builder_.Var(var_name, type->Build(builder_),
-                                  ast::StorageClass::kNone, src_vector.expr);
-
-    AddStatement(builder_.Decl({}, temp_var));
-  }
-
-  auto* lhs = create<ast::IndexAccessorExpression>(
-      Source{}, builder_.Expr(var_name), index.expr);
-  if (!lhs) {
-    return false;
-  }
-
-  AddStatement(builder_.Assign(lhs, component.expr));
-
-  if (hoisted) {
-    // The hoisted variable itself stands for this result ID.
-    return success();
-  }
-  // Create a new let-declaration that is initialized by the contents
-  // of the temporary variable.
-  return EmitConstDefinition(inst, {type, builder_.Expr(var_name)});
-}
-
-bool FunctionEmitter::MakeCompositeInsert(
-    const spvtools::opt::Instruction& inst) {
-  // For
-  //    %result = OpCompositeInsert %type %object %composite 1 2 3 ...
-  // there are two cases.
-  //
-  // Case 1:
-  //   The %composite value has already been hoisted into a variable.
-  //   In this case, assign %composite to that variable, then write the
-  //   component into the right spot:
-  //
-  //    hoisted = composite;
-  //    hoisted[index].x = object;
-  //
-  // Case 2:
-  //   The %composite value is not hoisted. In this case, make a temporary
-  //   variable with the %composite contents, then write the component,
-  //   and then make a let-declaration that reads the value out:
-  //
-  //    var temp : type = composite;
-  //    temp[index].x = object;
-  //    let result : type = temp;
-  //
-  //   Then use result everywhere the original SPIR-V id is used.  Using a const
-  //   like this avoids constantly reloading the value many times.
-  //
-  //   This technique is a combination of:
-  //   - making a temporary variable and constant declaration, like what we do
-  //     for VectorInsertDynamic, and
-  //   - building up an access-chain like access like for CompositeExtract, but
-  //     on the left-hand side of the assignment.
-
-  auto* type = parser_impl_.ConvertType(inst.type_id());
-  auto component = MakeOperand(inst, 0);
-  auto src_composite = MakeOperand(inst, 1);
-
-  std::string var_name;
-  auto original_value_name = namer_.Name(inst.result_id());
-  const bool hoisted = WriteIfHoistedVar(inst, src_composite);
-  if (hoisted) {
-    // The variable was already declared in an earlier block.
-    var_name = original_value_name;
-    // Assign the source composite value to it.
-    builder_.Assign({}, builder_.Expr(var_name), src_composite.expr);
-  } else {
-    // Synthesize a temporary variable.
-    // It doesn't correspond to a SPIR-V ID, so we don't use the ordinary
-    // API in parser_impl_.
-    var_name = namer_.MakeDerivedName(original_value_name);
-    auto* temp_var = builder_.Var(var_name, type->Build(builder_),
-                                  ast::StorageClass::kNone, src_composite.expr);
-    AddStatement(builder_.Decl({}, temp_var));
-  }
-
-  TypedExpression seed_expr{type, builder_.Expr(var_name)};
-
-  // The left-hand side of the assignment *looks* like a decomposition.
-  TypedExpression lhs =
-      MakeCompositeValueDecomposition(inst, seed_expr, inst.type_id(), 2);
-  if (!lhs) {
-    return false;
-  }
-
-  AddStatement(builder_.Assign(lhs.expr, component.expr));
-
-  if (hoisted) {
-    // The hoisted variable itself stands for this result ID.
-    return success();
-  }
-  // Create a new let-declaration that is initialized by the contents
-  // of the temporary variable.
-  return EmitConstDefinition(inst, {type, builder_.Expr(var_name)});
-}
-
-TypedExpression FunctionEmitter::AddressOf(TypedExpression expr) {
-  auto* ref = expr.type->As<Reference>();
-  if (!ref) {
-    Fail() << "AddressOf() called on non-reference type";
-    return {};
-  }
-  return {
-      ty_.Pointer(ref->type, ref->storage_class),
-      create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kAddressOf,
-                                     expr.expr),
-  };
-}
-
-TypedExpression FunctionEmitter::Dereference(TypedExpression expr) {
-  auto* ptr = expr.type->As<Pointer>();
-  if (!ptr) {
-    Fail() << "Dereference() called on non-pointer type";
-    return {};
-  }
-  return {
-      ptr->type,
-      create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kIndirection,
-                                     expr.expr),
-  };
-}
-
-bool FunctionEmitter::IsFloatZero(uint32_t value_id) {
-  if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) {
-    if (const auto* float_const = c->AsFloatConstant()) {
-      return 0.0f == float_const->GetFloatValue();
-    }
-    if (c->AsNullConstant()) {
-      // Valid SPIR-V requires it to be a float value anyway.
-      return true;
-    }
-  }
-  return false;
-}
-
-bool FunctionEmitter::IsFloatOne(uint32_t value_id) {
-  if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) {
-    if (const auto* float_const = c->AsFloatConstant()) {
-      return 1.0f == float_const->GetFloatValue();
-    }
-  }
-  return false;
-}
-
-FunctionEmitter::FunctionDeclaration::FunctionDeclaration() = default;
-FunctionEmitter::FunctionDeclaration::~FunctionDeclaration() = default;
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::StatementBuilder);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::SwitchStatementBuilder);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::IfStatementBuilder);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::LoopStatementBuilder);
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
deleted file mode 100644
index 6f01c19..0000000
--- a/src/reader/spirv/function.h
+++ /dev/null
@@ -1,1304 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_FUNCTION_H_
-#define SRC_READER_SPIRV_FUNCTION_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/reader/spirv/construct.h"
-#include "src/reader/spirv/parser_impl.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// Kinds of CFG edges.
-//
-// The edge kinds are used in many ways.
-//
-// For example, consider the edges leaving a basic block and going to distinct
-// targets. If the total number of kForward + kIfBreak + kCaseFallThrough edges
-// is more than 1, then the block must be a structured header, i.e. it needs
-// a merge instruction to declare the control flow divergence and associated
-// reconvergence point.  Those those edge kinds count toward divergence
-// because SPIR-v is designed to easily map back to structured control flow
-// in GLSL (and C).  In GLSL and C, those forward-flow edges don't have a
-// special statement to express them.  The other forward edges: kSwitchBreak,
-// kLoopBreak, and kLoopContinue directly map to 'break', 'break', and
-// 'continue', respectively.
-enum class EdgeKind {
-  // A back-edge: An edge from a node to one of its ancestors in a depth-first
-  // search from the entry block.
-  kBack,
-  // An edge from a node to the merge block of the nearest enclosing switch,
-  // where there is no intervening loop.
-  kSwitchBreak,
-  // An edge from a node to the merge block of the nearest enclosing loop, where
-  // there is no intervening switch.
-  // The source block is a "break block" as defined by SPIR-V.
-  kLoopBreak,
-  // An edge from a node in a loop body to the associated continue target, where
-  // there are no other intervening loops or switches.
-  // The source block is a "continue block" as defined by SPIR-V.
-  kLoopContinue,
-  // An edge from a node to the merge block of the nearest enclosing structured
-  // construct, but which is neither a kSwitchBreak or a kLoopBreak.
-  // This can only occur for an "if" selection, i.e. where the selection
-  // header ends in OpBranchConditional.
-  kIfBreak,
-  // An edge from one switch case to the next sibling switch case.
-  kCaseFallThrough,
-  // None of the above.
-  kForward
-};
-
-enum : uint32_t { kInvalidBlockPos = ~(0u) };
-
-/// Bookkeeping info for a basic block.
-struct BlockInfo {
-  /// Constructor
-  /// @param bb internal representation of the basic block
-  explicit BlockInfo(const spvtools::opt::BasicBlock& bb);
-  ~BlockInfo();
-
-  /// The internal representation of the basic block.
-  const spvtools::opt::BasicBlock* basic_block;
-
-  /// The ID of the OpLabel instruction that starts this block.
-  uint32_t id = 0;
-
-  /// The position of this block in the reverse structured post-order.
-  /// If the block is not in that order, then this remains the invalid value.
-  uint32_t pos = kInvalidBlockPos;
-
-  /// If this block is a header, then this is the ID of the merge block.
-  uint32_t merge_for_header = 0;
-  /// If this block is a loop header, then this is the ID of the continue
-  /// target.
-  uint32_t continue_for_header = 0;
-  /// If this block is a merge, then this is the ID of the header.
-  uint32_t header_for_merge = 0;
-  /// If this block is a continue target, then this is the ID of the loop
-  /// header.
-  uint32_t header_for_continue = 0;
-  /// Is this block a continue target which is its own loop header block?
-  /// In this case the continue construct is the entire loop.  The associated
-  /// "loop construct" is empty, and not represented.
-  bool is_continue_entire_loop = false;
-
-  /// The immediately enclosing structured construct. If this block is not
-  /// in the block order at all, then this is still nullptr.
-  const Construct* construct = nullptr;
-
-  /// Maps the ID of a successor block (in the CFG) to its edge classification.
-  std::unordered_map<uint32_t, EdgeKind> succ_edge;
-
-  /// The following fields record relationships among blocks in a selection
-  /// construct for an OpSwitch instruction.
-
-  /// If not null, then the pointed-at construct is a selection for an OpSwitch,
-  /// and this block is a case target for it.  We say this block "heads" the
-  /// case construct.
-  const Construct* case_head_for = nullptr;
-  /// If not null, then the pointed-at construct is a selection for an OpSwitch,
-  /// and this block is the default target for it.  We say this block "heads"
-  /// the default case construct.
-  const Construct* default_head_for = nullptr;
-  /// Is this a default target for a switch, and is it also the merge for its
-  /// switch?
-  bool default_is_merge = false;
-  /// The list of switch values that cause a branch to this block.
-  std::unique_ptr<std::vector<uint64_t>> case_values;
-
-  /// The following fields record relationships among blocks in a selection
-  /// construct for an OpBranchConditional instruction.
-
-  /// When this block is an if-selection header, this is the edge kind
-  /// for the true branch.
-  EdgeKind true_kind = EdgeKind::kForward;
-  /// When this block is an if-selection header, this is the edge kind
-  /// for the false branch.
-  EdgeKind false_kind = EdgeKind::kForward;
-  /// If not 0, then this block is an if-selection header, and `true_head` is
-  /// the target id of the true branch on the OpBranchConditional, and that
-  /// target is inside the if-selection.
-  uint32_t true_head = 0;
-  /// If not 0, then this block is an if-selection header, and `false_head`
-  /// is the target id of the false branch on the OpBranchConditional, and
-  /// that target is inside the if-selection.
-  uint32_t false_head = 0;
-  /// If not 0, then this block is an if-selection header, and when following
-  /// the flow via the true and false branches, control first reconverges at
-  /// the block with ID `premerge_head`, and `premerge_head` is still inside
-  /// the if-selection.
-  uint32_t premerge_head = 0;
-  /// If non-empty, then this block is an if-selection header, and control flow
-  /// in the body must be guarded by a boolean flow variable with this name.
-  /// This occurs when a block in this selection has both an if-break edge, and
-  /// also a different normal forward edge but without a merge instruction.
-  std::string flow_guard_name = "";
-
-  /// The result IDs that this block is responsible for declaring as a
-  /// hoisted variable.
-  /// @see DefInfo#requires_hoisted_def
-  std::vector<uint32_t> hoisted_ids;
-
-  /// A PhiAssignment represents the assignment of a value to the state
-  /// variable associated with an OpPhi in a successor block.
-  struct PhiAssignment {
-    /// The ID of an OpPhi receiving a value from this basic block.
-    uint32_t phi_id;
-    /// The the value carried to the given OpPhi.
-    uint32_t value;
-  };
-  /// If this basic block branches to a visited basic block containing phis,
-  /// then this is the list of writes to the variables associated those phis.
-  std::vector<PhiAssignment> phi_assignments;
-  /// The IDs of OpPhi instructions which require their associated state
-  /// variable to be declared in this basic block.
-  std::vector<uint32_t> phis_needing_state_vars;
-};
-
-inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
-  o << "BlockInfo{"
-    << " id: " << bi.id << " pos: " << bi.pos
-    << " merge_for_header: " << bi.merge_for_header
-    << " continue_for_header: " << bi.continue_for_header
-    << " header_for_merge: " << bi.header_for_merge
-    << " is_continue_entire_loop: " << int(bi.is_continue_entire_loop) << "}";
-  return o;
-}
-
-/// Reasons for avoiding generating an intermediate value.
-enum class SkipReason {
-  /// `kDontSkip`: The value should be generated. Used for most values.
-  kDontSkip,
-
-  /// For remaining cases, the value is not generated.
-
-  /// `kOpaqueObject`: used for any intermediate value which is an sampler,
-  /// image,
-  /// or sampled image, or any pointer to such object. Code is generated
-  /// for those objects only when emitting the image instructions that access
-  /// the image (read, write, sample, gather, fetch, or query). For example,
-  /// when encountering an OpImageSampleExplicitLod, a call to the
-  /// textureSampleLevel builtin function will be emitted, and the call will
-  /// directly reference the underlying texture and sampler (variable or
-  /// function parameter).
-  kOpaqueObject,
-
-  /// `kSinkPointerIntoUse`: used to avoid emitting certain pointer expressions,
-  /// by instead generating their reference expression directly at the point of
-  /// use. For example, we apply this to OpAccessChain when indexing into a
-  /// vector, to avoid generating address-of vector component expressions.
-  kSinkPointerIntoUse,
-
-  /// `kPointSizeBuiltinPointer`: the value is a pointer to the Position builtin
-  /// variable.  Don't generate its address.  Avoid generating stores to this
-  /// pointer.
-  kPointSizeBuiltinPointer,
-  /// `kPointSizeBuiltinValue`: the value is the value loaded from the
-  /// PointSize builtin. Use 1.0f instead, because that's the only value
-  /// supported by WebGPU.
-  kPointSizeBuiltinValue,
-
-  /// `kSampleMaskInBuiltinPointer`: the value is a pointer to the SampleMaskIn
-  /// builtin input variable.  Don't generate its address.
-  kSampleMaskInBuiltinPointer,
-
-  /// `kSampleMaskOutBuiltinPointer`: the value is a pointer to the SampleMask
-  /// builtin output variable.
-  kSampleMaskOutBuiltinPointer,
-};
-
-/// Bookkeeping info for a SPIR-V ID defined in the function, or some
-/// module-scope variables. This will be valid for result IDs that are:
-/// - defined in the function and:
-///    - instructions that are not OpLabel, and not OpFunctionParameter
-///    - are defined in a basic block visited in the block-order for the
-///    function.
-/// - certain module-scope builtin variables.
-struct DefInfo {
-  /// Constructor.
-  /// @param def_inst the SPIR-V instruction defining the ID
-  /// @param block_pos the position of the basic block where the ID is defined.
-  /// @param index an ordering index for this local definition
-  DefInfo(const spvtools::opt::Instruction& def_inst,
-          uint32_t block_pos,
-          size_t index);
-  /// Destructor.
-  ~DefInfo();
-
-  /// The SPIR-V instruction that defines the ID.
-  const spvtools::opt::Instruction& inst;
-  /// The position of the first block in which this ID is visible, in function
-  /// block order.  For IDs defined outside of the function, it is 0.
-  /// For IDs defined in the function, it is the position of the block
-  /// containing the definition of the ID.
-  /// See method `FunctionEmitter::ComputeBlockOrderAndPositions`
-  const uint32_t block_pos = 0;
-
-  /// An index for uniquely and deterministically ordering all DefInfo records
-  /// in a function.
-  const size_t index = 0;
-
-  /// The number of uses of this ID.
-  uint32_t num_uses = 0;
-
-  /// The block position of the last use of this ID, or 0 if it is not used
-  /// at all.  The "last" ordering is determined by the function block order.
-  uint32_t last_use_pos = 0;
-
-  /// Is this value used in a construct other than the one in which it was
-  /// defined?
-  bool used_in_another_construct = false;
-
-  /// True if this ID requires a WGSL 'const' definition, due to context. It
-  /// might get one anyway (so this is *not* an if-and-only-if condition).
-  bool requires_named_const_def = false;
-
-  /// True if this ID must map to a WGSL variable declaration before the
-  /// corresponding position of the ID definition in SPIR-V.  This compensates
-  /// for the difference between dominance and scoping. An SSA definition can
-  /// dominate all its uses, but the construct where it is defined does not
-  /// enclose all the uses, and so if it were declared as a WGSL constant
-  /// definition at the point of its SPIR-V definition, then the WGSL name
-  /// would go out of scope too early. Fix that by creating a variable at the
-  /// top of the smallest construct that encloses both the definition and all
-  /// its uses. Then the original SPIR-V definition maps to a WGSL assignment
-  /// to that variable, and each SPIR-V use becomes a WGSL read from the
-  /// variable.
-  /// TODO(dneto): This works for constants of storable type, but not, for
-  /// example, pointers. crbug.com/tint/98
-  bool requires_hoisted_def = false;
-
-  /// If the definition is an OpPhi, then `phi_var` is the name of the
-  /// variable that stores the value carried from parent basic blocks into
-  /// the basic block containing the OpPhi. Otherwise this is the empty string.
-  std::string phi_var;
-
-  /// The storage class to use for this value, if it is of pointer type.
-  /// This is required to carry a storage class override from a storage
-  /// buffer expressed in the old style (with Uniform storage class)
-  /// that needs to be remapped to StorageBuffer storage class.
-  /// This is kInvalid for non-pointers.
-  ast::StorageClass storage_class = ast::StorageClass::kInvalid;
-
-  /// The expression to use when sinking pointers into their use.
-  /// When encountering a use of this instruction, we will emit this expression
-  /// instead.
-  TypedExpression sink_pointer_source_expr = {};
-
-  /// The reason, if any, that this value should be ignored.
-  /// Normally no values are ignored.  This field can be updated while
-  /// generating code because sometimes we only discover necessary facts
-  /// in the middle of generating code.
-  SkipReason skip = SkipReason::kDontSkip;
-};
-
-inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) {
-  o << "DefInfo{"
-    << " inst.result_id: " << di.inst.result_id()
-    << " block_pos: " << di.block_pos << " num_uses: " << di.num_uses
-    << " last_use_pos: " << di.last_use_pos << " requires_named_const_def: "
-    << (di.requires_named_const_def ? "true" : "false")
-    << " requires_hoisted_def: " << (di.requires_hoisted_def ? "true" : "false")
-    << " phi_var: '" << di.phi_var << "'";
-  if (di.storage_class != ast::StorageClass::kNone) {
-    o << " sc:" << int(di.storage_class);
-  }
-  switch (di.skip) {
-    case SkipReason::kDontSkip:
-      break;
-    case SkipReason::kOpaqueObject:
-      o << " skip:opaque";
-      break;
-    case SkipReason::kSinkPointerIntoUse:
-      o << " skip:sink_pointer";
-      break;
-    case SkipReason::kPointSizeBuiltinPointer:
-      o << " skip:pointsize_pointer";
-      break;
-    case SkipReason::kPointSizeBuiltinValue:
-      o << " skip:pointsize_value";
-      break;
-    case SkipReason::kSampleMaskInBuiltinPointer:
-      o << " skip:samplemaskin_pointer";
-      break;
-    case SkipReason::kSampleMaskOutBuiltinPointer:
-      o << " skip:samplemaskout_pointer";
-      break;
-  }
-  o << "}";
-  return o;
-}
-
-/// A placeholder Statement that exists for the duration of building a
-/// StatementBlock. Once the StatementBlock is built, Build() will be called to
-/// construct the final AST node, which will be used in the place of this
-/// StatementBuilder.
-/// StatementBuilders are used to simplify construction of AST nodes that will
-/// become immutable. The builders may hold mutable state while the
-/// StatementBlock is being constructed, which becomes an immutable node on
-/// StatementBlock::Finalize().
-class StatementBuilder : public Castable<StatementBuilder, ast::Statement> {
- public:
-  /// Constructor
-  StatementBuilder() : Base(ProgramID(), Source{}) {}
-
-  /// @param builder the program builder
-  /// @returns the build AST node
-  virtual const ast::Statement* Build(ProgramBuilder* builder) const = 0;
-
- private:
-  Node* Clone(CloneContext*) const override;
-};
-
-/// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
-class FunctionEmitter {
- public:
-  /// Creates a FunctionEmitter, and prepares to write to the AST module
-  /// in `pi`
-  /// @param pi a ParserImpl which has already executed BuildInternalModule
-  /// @param function the function to emit
-  FunctionEmitter(ParserImpl* pi, const spvtools::opt::Function& function);
-  /// Creates a FunctionEmitter, and prepares to write to the AST module
-  /// in `pi`
-  /// @param pi a ParserImpl which has already executed BuildInternalModule
-  /// @param function the function to emit
-  /// @param ep_info entry point information for this function, or nullptr
-  FunctionEmitter(ParserImpl* pi,
-                  const spvtools::opt::Function& function,
-                  const EntryPointInfo* ep_info);
-  /// Move constructor. Only valid when the other object was newly created.
-  /// @param other the emitter to clone
-  FunctionEmitter(FunctionEmitter&& other);
-  /// Destructor
-  ~FunctionEmitter();
-
-  /// Emits the function to AST module.
-  /// @return whether emission succeeded
-  bool Emit();
-
-  /// @returns true if emission has not yet failed.
-  bool success() const { return fail_stream_.status(); }
-  /// @returns true if emission has failed.
-  bool failed() const { return !success(); }
-
-  /// Finalizes any StatementBuilders returns the body of the function.
-  /// Must only be called once, and to be used only for testing.
-  /// @returns the body of the function.
-  const ast::StatementList ast_body();
-
-  /// Records failure.
-  /// @returns a FailStream on which to emit diagnostics.
-  FailStream& Fail() { return fail_stream_.Fail(); }
-
-  /// @returns the parser implementation
-  ParserImpl* parser() { return &parser_impl_; }
-
-  /// Emits the entry point as a wrapper around its implementation function.
-  /// Pipeline inputs become formal parameters, and pipeline outputs become
-  /// return values.
-  /// @returns false if emission failed.
-  bool EmitEntryPointAsWrapper();
-
-  /// Creates one or more entry point input parameters corresponding to a
-  /// part of an input variable.  The part of the input variable is specfied
-  /// by the `index_prefix`, which successively indexes into the variable.
-  /// Also generates the assignment statements that copy the input parameter
-  /// to the corresponding part of the variable.  Assumes the variable
-  /// has already been created in the Private storage class.
-  /// @param var_name The name of the variable
-  /// @param var_type The store type of the variable
-  /// @param decos The variable's decorations
-  /// @param index_prefix Indices stepping into the variable, indicating
-  /// what part of the variable to populate.
-  /// @param tip_type The type of the component inside variable, after indexing
-  /// with the indices in `index_prefix`.
-  /// @param forced_param_type The type forced by WGSL, if the variable is a
-  /// builtin, otherwise the same as var_type.
-  /// @param params The parameter list where the new parameter is appended.
-  /// @param statements The statement list where the assignment is appended.
-  /// @returns false if emission failed
-  bool EmitPipelineInput(std::string var_name,
-                         const Type* var_type,
-                         ast::AttributeList* decos,
-                         std::vector<int> index_prefix,
-                         const Type* tip_type,
-                         const Type* forced_param_type,
-                         ast::VariableList* params,
-                         ast::StatementList* statements);
-
-  /// Creates one or more struct members from an output variable, and the
-  /// expressions that compute the value they contribute to the entry point
-  /// return value.  The part of the output variable is specfied
-  /// by the `index_prefix`, which successively indexes into the variable.
-  /// Assumes the variable has already been created in the Private storage
-  /// class.
-  /// @param var_name The name of the variable
-  /// @param var_type The store type of the variable
-  /// @param decos The variable's decorations
-  /// @param index_prefix Indices stepping into the variable, indicating
-  /// what part of the variable to populate.
-  /// @param tip_type The type of the component inside variable, after indexing
-  /// with the indices in `index_prefix`.
-  /// @param forced_member_type The type forced by WGSL, if the variable is a
-  /// builtin, otherwise the same as var_type.
-  /// @param return_members The struct member list where the new member is
-  /// added.
-  /// @param return_exprs The expression list where the return expression is
-  /// added.
-  /// @returns false if emission failed
-  bool EmitPipelineOutput(std::string var_name,
-                          const Type* var_type,
-                          ast::AttributeList* decos,
-                          std::vector<int> index_prefix,
-                          const Type* tip_type,
-                          const Type* forced_member_type,
-                          ast::StructMemberList* return_members,
-                          ast::ExpressionList* return_exprs);
-
-  /// Updates the attribute list, replacing an existing Location attribute
-  /// with another having one higher location value. Does nothing if no
-  /// location attribute exists.
-  /// Assumes the list contains at most one Location attribute.
-  /// @param attributes the attribute list to modify
-  void IncrementLocation(ast::AttributeList* attributes);
-
-  /// Returns the Location attribute, if it exists.
-  /// @param attributes the list of attributes to search
-  /// @returns the Location attribute, or nullptr if it doesn't exist
-  const ast::Attribute* GetLocation(const ast::AttributeList& attributes);
-
-  /// Create an ast::BlockStatement representing the body of the function.
-  /// This creates the statement stack, which is non-empty for the lifetime
-  /// of the function.
-  /// @returns the body of the function, or null on error
-  const ast::BlockStatement* MakeFunctionBody();
-
-  /// Emits the function body, populating the bottom entry of the statements
-  /// stack.
-  /// @returns false if emission failed.
-  bool EmitBody();
-
-  /// Records a mapping from block ID to a BlockInfo struct.
-  /// Populates `block_info_`
-  void RegisterBasicBlocks();
-
-  /// Verifies that terminators only branch to labels in the current function.
-  /// Assumes basic blocks have been registered.
-  /// @returns true if terminators are valid
-  bool TerminatorsAreValid();
-
-  /// Populates merge-header cross-links and BlockInfo#is_continue_entire_loop.
-  /// Also verifies that merge instructions go to blocks in the same function.
-  /// Assumes basic blocks have been registered, and terminators are valid.
-  /// @returns false if registration fails
-  bool RegisterMerges();
-
-  /// Determines the output order for the basic blocks in the function.
-  /// Populates `block_order_` and BlockInfo#pos.
-  /// Assumes basic blocks have been registered.
-  void ComputeBlockOrderAndPositions();
-
-  /// @returns the reverse structured post order of the basic blocks in
-  /// the function.
-  const std::vector<uint32_t>& block_order() const { return block_order_; }
-
-  /// Verifies that the orderings among a structured header, continue target,
-  /// and merge block are valid. Assumes block order has been computed, and
-  /// merges are valid and recorded.
-  /// @returns false if invalid nesting was detected
-  bool VerifyHeaderContinueMergeOrder();
-
-  /// Labels each basic block with its nearest enclosing structured construct.
-  /// Populates BlockInfo#construct and the `constructs_` list.
-  /// Assumes terminators are valid and merges have been registered, block
-  /// order has been computed, and each block is labeled with its position.
-  /// Checks nesting of structured control flow constructs.
-  /// @returns false if bad nesting has been detected
-  bool LabelControlFlowConstructs();
-
-  /// @returns the structured constructs
-  const ConstructList& constructs() const { return constructs_; }
-
-  /// Marks blocks targets of a switch, either as the head of a case or
-  /// as the default target.
-  /// @returns false on failure
-  bool FindSwitchCaseHeaders();
-
-  /// Classifies the successor CFG edges for the ordered basic blocks.
-  /// Also checks validity of each edge (populates BlockInfo#succ_edge).
-  /// Implicitly checks dominance rules for headers and continue constructs.
-  /// Assumes each block has been labeled with its control flow construct.
-  /// @returns false on failure
-  bool ClassifyCFGEdges();
-
-  /// Marks the blocks within a selection construct that are the first blocks
-  /// in the "then" clause, the "else" clause, and the "premerge" clause.
-  /// The head of the premerge clause is the block, if it exists, at which
-  /// control flow reconverges from the "then" and "else" clauses, but before
-  /// before the merge block for that selection.   The existence of a premerge
-  /// should be an exceptional case, but is allowed by the structured control
-  /// flow rules.
-  /// @returns false if bad nesting has been detected.
-  bool FindIfSelectionInternalHeaders();
-
-  /// Creates a DefInfo record for each module-scope builtin variable
-  /// that should be handled specially.  Either it's ignored, or its store
-  /// type is converted on load.
-  /// Populates the `def_info_` mapping for such IDs.
-  /// @returns false on failure
-  bool RegisterSpecialBuiltInVariables();
-
-  /// Creates a DefInfo record for each locally defined SPIR-V ID.
-  /// Populates the `def_info_` mapping with basic results for such IDs.
-  /// @returns false on failure
-  bool RegisterLocallyDefinedValues();
-
-  /// Returns the Tint storage class for the given SPIR-V ID that is a
-  /// pointer value.
-  /// @param id a SPIR-V ID for a pointer value
-  /// @returns the storage class
-  ast::StorageClass GetStorageClassForPointerValue(uint32_t id);
-
-  /// Remaps the storage class for the type of a locally-defined value,
-  /// if necessary. If it's not a pointer type, or if its storage class
-  /// already matches, then the result is a copy of the `type` argument.
-  /// @param type the AST type
-  /// @param result_id the SPIR-V ID for the locally defined value
-  /// @returns an possibly updated type
-  const Type* RemapStorageClass(const Type* type, uint32_t result_id);
-
-  /// Marks locally defined values when they should get a 'const'
-  /// definition in WGSL, or a 'var' definition at an outer scope.
-  /// This occurs in several cases:
-  ///  - When a SPIR-V instruction might use the dynamically computed value
-  ///    only once, but the WGSL code might reference it multiple times.
-  ///    For example, this occurs for the vector operands of OpVectorShuffle.
-  ///    In this case the definition's DefInfo#requires_named_const_def property
-  ///    is set to true.
-  ///  - When a definition and at least one of its uses are not in the
-  ///    same structured construct.
-  ///    In this case the definition's DefInfo#requires_named_const_def property
-  ///    is set to true.
-  ///  - When a definition is in a construct that does not enclose all the
-  ///    uses.  In this case the definition's DefInfo#requires_hoisted_def
-  ///    property is set to true.
-  /// Updates the `def_info_` mapping.
-  void FindValuesNeedingNamedOrHoistedDefinition();
-
-  /// Emits declarations of function variables.
-  /// @returns false if emission failed.
-  bool EmitFunctionVariables();
-
-  /// Emits statements in the body.
-  /// @returns false if emission failed.
-  bool EmitFunctionBodyStatements();
-
-  /// Emits a basic block.
-  /// @param block_info the block to emit
-  /// @returns false if emission failed.
-  bool EmitBasicBlock(const BlockInfo& block_info);
-
-  /// Emits an IfStatement, including its condition expression, and sets
-  /// up the statement stack to accumulate subsequent basic blocks into
-  /// the "then" and "else" clauses.
-  /// @param block_info the if-selection header block
-  /// @returns false if emission failed.
-  bool EmitIfStart(const BlockInfo& block_info);
-
-  /// Emits a SwitchStatement, including its condition expression, and sets
-  /// up the statement stack to accumulate subsequent basic blocks into
-  /// the default clause and case clauses.
-  /// @param block_info the switch-selection header block
-  /// @returns false if emission failed.
-  bool EmitSwitchStart(const BlockInfo& block_info);
-
-  /// Emits a LoopStatement, and pushes a new StatementBlock to accumulate
-  /// the remaining instructions in the current block and subsequent blocks
-  /// in the loop.
-  /// @param construct the loop construct
-  /// @returns false if emission failed.
-  bool EmitLoopStart(const Construct* construct);
-
-  /// Emits a ContinuingStatement, and pushes a new StatementBlock to accumulate
-  /// the remaining instructions in the current block and subsequent blocks
-  /// in the continue construct.
-  /// @param construct the continue construct
-  /// @returns false if emission failed.
-  bool EmitContinuingStart(const Construct* construct);
-
-  /// Emits the non-control-flow parts of a basic block, but only once.
-  /// The `already_emitted` parameter indicates whether the code has already
-  /// been emitted, and is used to signal that this invocation actually emitted
-  /// it.
-  /// @param block_info the block to emit
-  /// @param already_emitted the block to emit
-  /// @returns false if the code had not yet been emitted, but emission failed
-  bool EmitStatementsInBasicBlock(const BlockInfo& block_info,
-                                  bool* already_emitted);
-
-  /// Emits code for terminators, but that aren't part of entering or
-  /// resolving structured control flow. That is, if the basic block
-  /// terminator calls for it, emit the fallthrough, break, continue, return,
-  /// or kill commands.
-  /// @param block_info the block with the terminator to emit (if any)
-  /// @returns false if emission failed
-  bool EmitNormalTerminator(const BlockInfo& block_info);
-
-  /// Returns a new statement to represent the given branch representing a
-  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
-  /// WGSL statement is required, the statement will be nullptr. This method
-  /// tries to avoid emitting a 'break' statement when that would be redundant
-  /// in WGSL due to implicit breaking out of a switch.
-  /// @param src_info the source block
-  /// @param dest_info the destination block
-  /// @returns the new statement, or a null statement
-  const ast::Statement* MakeBranch(const BlockInfo& src_info,
-                                   const BlockInfo& dest_info) const {
-    return MakeBranchDetailed(src_info, dest_info, false, nullptr);
-  }
-
-  /// Returns a new statement to represent the given branch representing a
-  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
-  /// WGSL statement is required, the statement will be nullptr.
-  /// @param src_info the source block
-  /// @param dest_info the destination block
-  /// @returns the new statement, or a null statement
-  const ast::Statement* MakeForcedBranch(const BlockInfo& src_info,
-                                         const BlockInfo& dest_info) const {
-    return MakeBranchDetailed(src_info, dest_info, true, nullptr);
-  }
-
-  /// Returns a new statement to represent the given branch representing a
-  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
-  /// WGSL statement is required, the statement will be nullptr. When `forced`
-  /// is false, this method tries to avoid emitting a 'break' statement when
-  /// that would be redundant in WGSL due to implicit breaking out of a switch.
-  /// When `forced` is true, the method won't try to avoid emitting that break.
-  /// If the control flow edge is an if-break for an if-selection with a
-  /// control flow guard, then return that guard name via `flow_guard_name_ptr`
-  /// when that parameter is not null.
-  /// @param src_info the source block
-  /// @param dest_info the destination block
-  /// @param forced if true, always emit the branch (if it exists in WGSL)
-  /// @param flow_guard_name_ptr return parameter for control flow guard name
-  /// @returns the new statement, or a null statement
-  const ast::Statement* MakeBranchDetailed(
-      const BlockInfo& src_info,
-      const BlockInfo& dest_info,
-      bool forced,
-      std::string* flow_guard_name_ptr) const;
-
-  /// Returns a new if statement with the given statements as the then-clause
-  /// and the else-clause.  Either or both clauses might be nullptr. If both
-  /// are nullptr, then don't make a new statement and instead return nullptr.
-  /// @param condition the branching condition
-  /// @param then_stmt the statement for the then clause of the if, or nullptr
-  /// @param else_stmt the statement for the else clause of the if, or nullptr
-  /// @returns the new statement, or nullptr
-  const ast::Statement* MakeSimpleIf(const ast::Expression* condition,
-                                     const ast::Statement* then_stmt,
-                                     const ast::Statement* else_stmt) const;
-
-  /// Emits the statements for an normal-terminator OpBranchConditional
-  /// where one branch is a case fall through (the true branch if and only
-  /// if `fall_through_is_true_branch` is true), and the other branch is
-  /// goes to a different destination, named by `other_dest`.
-  /// @param src_info the basic block from which we're branching
-  /// @param cond the branching condition
-  /// @param other_edge_kind the edge kind from the source block to the other
-  /// destination
-  /// @param other_dest the other branching destination
-  /// @param fall_through_is_true_branch true when the fall-through is the true
-  /// branch
-  /// @returns the false if emission fails
-  bool EmitConditionalCaseFallThrough(const BlockInfo& src_info,
-                                      const ast::Expression* cond,
-                                      EdgeKind other_edge_kind,
-                                      const BlockInfo& other_dest,
-                                      bool fall_through_is_true_branch);
-
-  /// Emits a normal instruction: not a terminator, label, or variable
-  /// declaration.
-  /// @param inst the instruction
-  /// @returns false if emission failed.
-  bool EmitStatement(const spvtools::opt::Instruction& inst);
-
-  /// Emits a const definition for the typed value in `ast_expr`, and
-  /// records it as the translation for the result ID from `inst`.
-  /// @param inst the SPIR-V instruction defining the value
-  /// @param ast_expr the already-computed AST expression for the value
-  /// @returns false if emission failed.
-  bool EmitConstDefinition(const spvtools::opt::Instruction& inst,
-                           TypedExpression ast_expr);
-
-  /// Emits a write of the typed value in `ast_expr` to a hoisted variable
-  /// for the given SPIR-V ID, if that ID has a hoisted declaration. Otherwise,
-  /// emits a const definition instead.
-  /// @param inst the SPIR-V instruction defining the value
-  /// @param ast_expr the already-computed AST expression for the value
-  /// @returns false if emission failed.
-  bool EmitConstDefOrWriteToHoistedVar(const spvtools::opt::Instruction& inst,
-                                       TypedExpression ast_expr);
-
-  /// If the result ID of the given instruction is hoisted, then emits
-  /// a statement to write the expression to the hoisted variable, and
-  /// returns true.  Otherwise return false.
-  /// @param inst the SPIR-V instruction defining a value.
-  /// @param ast_expr the expression to assign.
-  /// @returns true if the instruction has an associated hoisted variable.
-  bool WriteIfHoistedVar(const spvtools::opt::Instruction& inst,
-                         TypedExpression ast_expr);
-
-  /// Makes an expression from a SPIR-V ID.
-  /// if the SPIR-V result type is a pointer.
-  /// @param id the SPIR-V ID of the value
-  /// @returns an AST expression for the instruction, or an invalid
-  /// TypedExpression on error.
-  TypedExpression MakeExpression(uint32_t id);
-
-  /// Creates an expression and supporting statements for a combinatorial
-  /// instruction, or returns null.  A SPIR-V instruction is combinatorial
-  /// if it has no side effects and its result depends only on its operands,
-  /// and not on accessing external state like memory or the state of other
-  /// invocations.  Statements are only created if required to provide values
-  /// to the expression. Supporting statements are not required to be
-  /// combinatorial.
-  /// @param inst a SPIR-V instruction representing an exrpression
-  /// @returns an AST expression for the instruction, or nullptr.
-  TypedExpression MaybeEmitCombinatorialValue(
-      const spvtools::opt::Instruction& inst);
-
-  /// Creates an expression and supporting statements for the a GLSL.std.450
-  /// extended instruction.
-  /// @param inst a SPIR-V OpExtInst instruction from GLSL.std.450
-  /// @returns an AST expression for the instruction, or nullptr.
-  TypedExpression EmitGlslStd450ExtInst(const spvtools::opt::Instruction& inst);
-
-  /// Creates an expression for OpCompositeExtract
-  /// @param inst an OpCompositeExtract instruction.
-  /// @returns an AST expression for the instruction, or nullptr.
-  TypedExpression MakeCompositeExtract(const spvtools::opt::Instruction& inst);
-
-  /// Creates an expression for indexing into a composite value.  The literal
-  /// indices that step into the value start at instruction input operand
-  /// `start_index` and run to the end of the instruction.
-  /// @param inst the original instruction
-  /// @param composite the typed expression for the composite
-  /// @param composite_type_id the SPIR-V type ID for the composite
-  /// @param index_start the index of the first operand in `inst` that is an
-  /// index into the composite type
-  /// @returns an AST expression for the decomposed composite, or {} on error
-  TypedExpression MakeCompositeValueDecomposition(
-      const spvtools::opt::Instruction& inst,
-      TypedExpression composite,
-      uint32_t composite_type_id,
-      int index_start);
-
-  /// Creates an expression for OpVectorShuffle
-  /// @param inst an OpVectorShuffle instruction.
-  /// @returns an AST expression for the instruction, or nullptr.
-  TypedExpression MakeVectorShuffle(const spvtools::opt::Instruction& inst);
-
-  /// Creates an expression for a numeric conversion.
-  /// @param inst a numeric conversion instruction
-  /// @returns an AST expression for the instruction, or nullptr.
-  TypedExpression MakeNumericConversion(const spvtools::opt::Instruction& inst);
-
-  /// Gets the block info for a block ID, if any exists
-  /// @param id the SPIR-V ID of the OpLabel instruction starting the block
-  /// @returns the block info for the given ID, if it exists, or nullptr
-  BlockInfo* GetBlockInfo(uint32_t id) const {
-    auto where = block_info_.find(id);
-    if (where == block_info_.end()) {
-      return nullptr;
-    }
-    return where->second.get();
-  }
-
-  /// Is the block, represented by info, in the structured block order?
-  /// @param info the block
-  /// @returns true if the block is in the structured block order.
-  bool IsInBlockOrder(const BlockInfo* info) const {
-    return info && info->pos != kInvalidBlockPos;
-  }
-
-  /// Gets the local definition info for a result ID.
-  /// @param id the SPIR-V ID of local definition.
-  /// @returns the definition info for the given ID, if it exists, or nullptr
-  DefInfo* GetDefInfo(uint32_t id) const {
-    auto where = def_info_.find(id);
-    if (where == def_info_.end()) {
-      return nullptr;
-    }
-    return where->second.get();
-  }
-  /// Returns the skip reason for a result ID.
-  /// @param id SPIR-V result ID
-  /// @returns the skip reason for the given ID, or SkipReason::kDontSkip
-  SkipReason GetSkipReason(uint32_t id) const {
-    if (auto* def_info = GetDefInfo(id)) {
-      return def_info->skip;
-    }
-    return SkipReason::kDontSkip;
-  }
-
-  /// Returns the most deeply nested structured construct which encloses the
-  /// WGSL scopes of names declared in both block positions. Each position must
-  /// be a valid index into the function block order array.
-  /// @param first_pos the first block position
-  /// @param last_pos the last block position
-  /// @returns the smallest construct containing both positions
-  const Construct* GetEnclosingScope(uint32_t first_pos,
-                                     uint32_t last_pos) const;
-
-  /// Finds loop construct associated with a continue construct, if it exists.
-  /// Returns nullptr if:
-  ///  - the given construct is not a continue construct
-  ///  - the continue construct does not have an associated loop construct
-  ///    (the continue target is also the loop header block)
-  /// @param c the continue construct
-  /// @returns the associated loop construct, or nullptr
-  const Construct* SiblingLoopConstruct(const Construct* c) const;
-
-  /// Returns an identifier expression for the swizzle name of the given
-  /// index into a vector.  Emits an error and returns nullptr if the
-  /// index is out of range, i.e. 4 or higher.
-  /// @param i index of the subcomponent
-  /// @returns the identifier expression for the `i`'th component
-  ast::IdentifierExpression* Swizzle(uint32_t i);
-
-  /// Returns an identifier expression for the swizzle name of the first
-  /// `n` elements of a vector.  Emits an error and returns nullptr if `n`
-  /// is out of range, i.e. 4 or higher.
-  /// @param n the number of components in the swizzle
-  /// @returns the swizzle identifier for the first n elements of a vector
-  ast::IdentifierExpression* PrefixSwizzle(uint32_t n);
-
-  /// Converts SPIR-V image coordinates from an image access instruction
-  /// (e.g. OpImageSampledImplicitLod) into an expression list consisting of
-  /// the texture coordinates, and an integral array index if the texture is
-  /// arrayed. The texture coordinate is a scalar for 1D textures, a vector of
-  /// 2 elements for a 2D texture, and a vector of 3 elements for a 3D or
-  /// Cube texture. Excess components are ignored, e.g. if the SPIR-V
-  /// coordinate is a 4-element vector but the image is a 2D non-arrayed
-  /// texture then the 3rd and 4th components are ignored.
-  /// On failure, issues an error and returns an empty expression list.
-  /// @param image_access the image access instruction
-  /// @returns an ExpressionList of the coordinate and array index (if any)
-  ast::ExpressionList MakeCoordinateOperandsForImageAccess(
-      const spvtools::opt::Instruction& image_access);
-
-  /// Returns the given value as an I32.  If it's already an I32 then this
-  /// return the given value.  Otherwise, wrap the value in a TypeConstructor
-  /// expression.
-  /// @param value the value to pass through or convert
-  /// @returns the value as an I32 value.
-  TypedExpression ToI32(TypedExpression value);
-
-  /// Returns the given value as a signed integer type of the same shape
-  /// if the value is unsigned scalar or vector, by wrapping the value
-  /// with a TypeConstructor expression.  Returns the value itself if the
-  /// value otherwise.
-  /// @param value the value to pass through or convert
-  /// @returns the value itself, or converted to signed integral
-  TypedExpression ToSignedIfUnsigned(TypedExpression value);
-
-  /// @param value_id the value identifier to check
-  /// @returns true if the given SPIR-V id represents a constant float 0.
-  bool IsFloatZero(uint32_t value_id);
-  /// @param value_id the value identifier to check
-  /// @returns true if the given SPIR-V id represents a constant float 1.
-  bool IsFloatOne(uint32_t value_id);
-
- private:
-  /// FunctionDeclaration contains the parsed information for a function header.
-  struct FunctionDeclaration {
-    /// Constructor
-    FunctionDeclaration();
-    /// Destructor
-    ~FunctionDeclaration();
-
-    /// Parsed header source
-    Source source;
-    /// Function name
-    std::string name;
-    /// Function parameters
-    ast::VariableList params;
-    /// Function return type
-    const Type* return_type;
-    /// Function attributes
-    ast::AttributeList attributes;
-  };
-
-  /// Parse the function declaration, which comprises the name, parameters, and
-  /// return type, populating `decl`.
-  /// @param decl the FunctionDeclaration to populate
-  /// @returns true if emission has not yet failed.
-  bool ParseFunctionDeclaration(FunctionDeclaration* decl);
-
-  /// @returns the store type for the OpVariable instruction, or
-  /// null on failure.
-  const Type* GetVariableStoreType(
-      const spvtools::opt::Instruction& var_decl_inst);
-
-  /// Returns an expression for an instruction operand. Signedness conversion is
-  /// performed to match the result type of the SPIR-V instruction.
-  /// @param inst the SPIR-V instruction
-  /// @param operand_index the index of the operand, counting 0 as the first
-  /// input operand
-  /// @returns a new expression node
-  TypedExpression MakeOperand(const spvtools::opt::Instruction& inst,
-                              uint32_t operand_index);
-
-  /// Copies a typed expression to the result, but when the type is a pointer
-  /// or reference type, ensures the storage class is not defaulted.  That is,
-  /// it changes a storage class of "none" to "function".
-  /// @param expr a typed expression
-  /// @results a copy of the expression, with possibly updated type
-  TypedExpression InferFunctionStorageClass(TypedExpression expr);
-
-  /// Returns an expression for a SPIR-V OpFMod instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  TypedExpression MakeFMod(const spvtools::opt::Instruction& inst);
-
-  /// Returns an expression for a SPIR-V OpAccessChain or OpInBoundsAccessChain
-  /// instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  TypedExpression MakeAccessChain(const spvtools::opt::Instruction& inst);
-
-  /// Emits a function call.  On failure, emits a diagnostic and returns false.
-  /// @param inst the SPIR-V function call instruction
-  /// @returns false if emission failed
-  bool EmitFunctionCall(const spvtools::opt::Instruction& inst);
-
-  /// Emits a control barrier builtin.  On failure, emits a diagnostic and
-  /// returns false.
-  /// @param inst the SPIR-V control barrier instruction
-  /// @returns false if emission failed
-  bool EmitControlBarrier(const spvtools::opt::Instruction& inst);
-
-  /// Returns an expression for a SPIR-V instruction that maps to a WGSL
-  /// builtin function call.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  TypedExpression MakeBuiltinCall(const spvtools::opt::Instruction& inst);
-
-  /// Returns an expression for a SPIR-V OpArrayLength instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  TypedExpression MakeArrayLength(const spvtools::opt::Instruction& inst);
-
-  /// Generates an expression for a SPIR-V OpOuterProduct instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  TypedExpression MakeOuterProduct(const spvtools::opt::Instruction& inst);
-
-  /// Generates statements for a SPIR-V OpVectorInsertDynamic instruction.
-  /// Registers a const declaration for the result.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  bool MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst);
-
-  /// Generates statements for a SPIR-V OpComposite instruction.
-  /// Registers a const declaration for the result.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  bool MakeCompositeInsert(const spvtools::opt::Instruction& inst);
-
-  /// Get the SPIR-V instruction for the image memory object declaration for
-  /// the image operand to the given instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns a SPIR-V OpVariable or OpFunctionParameter instruction, or null
-  /// on error
-  const spvtools::opt::Instruction* GetImage(
-      const spvtools::opt::Instruction& inst);
-
-  /// Get the AST texture the SPIR-V image memory object declaration.
-  /// @param inst the SPIR-V memory object declaration for the image.
-  /// @returns a texture type, or null on error
-  const Texture* GetImageType(const spvtools::opt::Instruction& inst);
-
-  /// Get the expression for the image operand from the first operand to the
-  /// given instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an identifier expression, or null on error
-  const ast::Expression* GetImageExpression(
-      const spvtools::opt::Instruction& inst);
-
-  /// Get the expression for the sampler operand from the first operand to the
-  /// given instruction.
-  /// @param inst the SPIR-V instruction
-  /// @returns an identifier expression, or null on error
-  const ast::Expression* GetSamplerExpression(
-      const spvtools::opt::Instruction& inst);
-
-  /// Emits a texture builtin function call for a SPIR-V instruction that
-  /// accesses an image or sampled image.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  bool EmitImageAccess(const spvtools::opt::Instruction& inst);
-
-  /// Emits statements to implement a SPIR-V image query.
-  /// @param inst the SPIR-V instruction
-  /// @returns an expression
-  bool EmitImageQuery(const spvtools::opt::Instruction& inst);
-
-  /// Converts the given texel to match the type required for the storage
-  /// texture with the given type. In WGSL the texel value is always provided
-  /// as a 4-element vector, but the component type is determined by the
-  /// texel channel type. See "Texel Formats for Storage Textures" in the WGSL
-  /// spec. Returns an expression, or emits an error and returns nullptr.
-  /// @param inst the image access instruction (used for diagnostics)
-  /// @param texel the texel
-  /// @param texture_type the type of the storage texture
-  /// @returns the texel, after necessary conversion.
-  const ast::Expression* ConvertTexelForStorage(
-      const spvtools::opt::Instruction& inst,
-      TypedExpression texel,
-      const Texture* texture_type);
-
-  /// Returns an expression for an OpSelect, if its operands are scalars
-  /// or vectors. These translate directly to WGSL select.  Otherwise, return
-  /// an expression with a null owned expression
-  /// @param inst the SPIR-V OpSelect instruction
-  /// @returns a typed expression, or one with a null owned expression
-  TypedExpression MakeSimpleSelect(const spvtools::opt::Instruction& inst);
-
-  /// Finds the header block for a structured construct that we can "break"
-  /// out from, from deeply nested control flow, if such a block exists.
-  /// If the construct is:
-  ///  - a switch selection: return the selection header (ending in OpSwitch)
-  ///  - a loop construct: return the loop header block
-  ///  - a continue construct: return the loop header block
-  /// Otherwise, return nullptr.
-  /// @param c a structured construct, or nullptr
-  /// @returns the block info for the structured header we can "break" from,
-  /// or nullptr
-  BlockInfo* HeaderIfBreakable(const Construct* c);
-
-  /// Appends a new statement to the top of the statement stack.
-  /// Does nothing if the statement is null.
-  /// @param statement the new statement
-  /// @returns a pointer to the statement.
-  const ast::Statement* AddStatement(const ast::Statement* statement);
-
-  /// AddStatementBuilder() constructs and adds the StatementBuilder of type
-  /// `T` to the top of the statement stack.
-  /// @param args the arguments forwarded to the T constructor
-  /// @return the built StatementBuilder
-  template <typename T, typename... ARGS>
-  T* AddStatementBuilder(ARGS&&... args) {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
-    return statements_stack_.back().AddStatementBuilder<T>(
-        std::forward<ARGS>(args)...);
-  }
-
-  /// Returns the source record for the given instruction.
-  /// @param inst the SPIR-V instruction
-  /// @return the Source record, or a default one
-  Source GetSourceForInst(const spvtools::opt::Instruction& inst) const;
-
-  /// @returns the last statetment in the top of the statement stack.
-  const ast::Statement* LastStatement();
-
-  using CompletionAction = std::function<void(const ast::StatementList&)>;
-
-  // A StatementBlock represents a braced-list of statements while it is being
-  // constructed.
-  class StatementBlock {
-   public:
-    StatementBlock(const Construct* construct,
-                   uint32_t end_id,
-                   CompletionAction completion_action);
-    StatementBlock(StatementBlock&&);
-    ~StatementBlock();
-
-    StatementBlock(const StatementBlock&) = delete;
-    StatementBlock& operator=(const StatementBlock&) = delete;
-
-    /// Replaces any StatementBuilders with the built result, and calls the
-    /// completion callback (if set). Must only be called once, after all
-    /// statements have been added with Add().
-    /// @param builder the program builder
-    void Finalize(ProgramBuilder* builder);
-
-    /// Add() adds `statement` to the block.
-    /// Add() must not be called after calling Finalize().
-    void Add(const ast::Statement* statement);
-
-    /// AddStatementBuilder() constructs and adds the StatementBuilder of type
-    /// `T` to the block.
-    /// Add() must not be called after calling Finalize().
-    /// @param args the arguments forwarded to the T constructor
-    /// @return the built StatementBuilder
-    template <typename T, typename... ARGS>
-    T* AddStatementBuilder(ARGS&&... args) {
-      auto builder = std::make_unique<T>(std::forward<ARGS>(args)...);
-      auto* ptr = builder.get();
-      Add(ptr);
-      builders_.emplace_back(std::move(builder));
-      return ptr;
-    }
-
-    /// @param construct the construct which this construct constributes to
-    void SetConstruct(const Construct* construct) { construct_ = construct; }
-
-    /// @return the construct to which this construct constributes
-    const Construct* GetConstruct() const { return construct_; }
-
-    /// @return the ID of the block at which the completion action should be
-    /// triggered and this statement block discarded. This is often the `end_id`
-    /// of `construct` itself.
-    uint32_t GetEndId() const { return end_id_; }
-
-    /// @return the list of statements being built, if this construct is not a
-    /// switch.
-    const ast::StatementList& GetStatements() const { return statements_; }
-
-   private:
-    /// The construct to which this construct constributes.
-    const Construct* construct_;
-    /// The ID of the block at which the completion action should be triggered
-    /// and this statement block discarded. This is often the `end_id` of
-    /// `construct` itself.
-    const uint32_t end_id_;
-    /// The completion action finishes processing this statement block.
-    FunctionEmitter::CompletionAction const completion_action_;
-    /// The list of statements being built, if this construct is not a switch.
-    ast::StatementList statements_;
-
-    /// Owned statement builders
-    std::vector<std::unique_ptr<StatementBuilder>> builders_;
-    /// True if Finalize() has been called.
-    bool finalized_ = false;
-  };
-
-  /// Pushes an empty statement block onto the statements stack.
-  /// @param action the completion action for this block
-  void PushNewStatementBlock(const Construct* construct,
-                             uint32_t end_id,
-                             CompletionAction action);
-
-  /// Emits an if-statement whose condition is the given flow guard
-  /// variable, and pushes onto the statement stack the corresponding
-  /// statement block ending (and not including) the given block.
-  /// @param flow_guard name of the flow guard variable
-  /// @param end_id first block after the if construct.
-  void PushGuard(const std::string& flow_guard, uint32_t end_id);
-
-  /// Emits an if-statement with 'true' condition, and pushes onto the
-  /// statement stack the corresponding statement block ending (and not
-  /// including) the given block.
-  /// @param end_id first block after the if construct.
-  void PushTrueGuard(uint32_t end_id);
-
-  /// @returns a boolean true expression.
-  const ast::Expression* MakeTrue(const Source&) const;
-
-  /// @returns a boolean false expression.
-  const ast::Expression* MakeFalse(const Source&) const;
-
-  /// @param expr the expression to take the address of
-  /// @returns a TypedExpression that is the address-of `expr` (`&expr`)
-  /// @note `expr` must be a reference type
-  TypedExpression AddressOf(TypedExpression expr);
-
-  /// Returns AddressOf(expr) if expr is has reference type and
-  /// the instruction has a pointer result type.  Otherwise returns expr.
-  /// @param expr the expression to take the address of
-  /// @returns a TypedExpression that is the address-of `expr` (`&expr`)
-  /// @note `expr` must be a reference type
-  TypedExpression AddressOfIfNeeded(TypedExpression expr,
-                                    const spvtools::opt::Instruction* inst);
-
-  /// @param expr the expression to dereference
-  /// @returns a TypedExpression that is the dereference-of `expr` (`*expr`)
-  /// @note `expr` must be a pointer type
-  TypedExpression Dereference(TypedExpression expr);
-
-  /// Creates a new `ast::Node` owned by the ProgramBuilder.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  T* create(ARGS&&... args) const {
-    return builder_.create<T>(std::forward<ARGS>(args)...);
-  }
-
-  using StatementsStack = std::vector<StatementBlock>;
-  using PtrAs = ParserImpl::PtrAs;
-
-  ParserImpl& parser_impl_;
-  TypeManager& ty_;
-  ProgramBuilder& builder_;
-  spvtools::opt::IRContext& ir_context_;
-  spvtools::opt::analysis::DefUseManager* def_use_mgr_;
-  spvtools::opt::analysis::ConstantManager* constant_mgr_;
-  spvtools::opt::analysis::TypeManager* type_mgr_;
-  FailStream& fail_stream_;
-  Namer& namer_;
-  const spvtools::opt::Function& function_;
-
-  // The SPIR-V ID for the SampleMask input variable.
-  uint32_t sample_mask_in_id;
-  // The SPIR-V ID for the SampleMask output variable.
-  uint32_t sample_mask_out_id;
-
-  // A stack of statement lists. Each list is contained in a construct in
-  // the next deeper element of stack. The 0th entry represents the statements
-  // for the entire function.  This stack is never empty.
-  // The `construct` member for the 0th element is only valid during the
-  // lifetime of the EmitFunctionBodyStatements method.
-  StatementsStack statements_stack_;
-
-  // The map of IDs that have already had an identifier name generated for it,
-  // to their Type.
-  std::unordered_map<uint32_t, const Type*> identifier_types_;
-  // Mapping from SPIR-V ID that is used at most once, to its AST expression.
-  std::unordered_map<uint32_t, TypedExpression> singly_used_values_;
-
-  // The IDs of basic blocks, in reverse structured post-order (RSPO).
-  // This is the output order for the basic blocks.
-  std::vector<uint32_t> block_order_;
-
-  // Mapping from block ID to its bookkeeping info.
-  std::unordered_map<uint32_t, std::unique_ptr<BlockInfo>> block_info_;
-
-  // Mapping from a locally-defined result ID to its bookkeeping info.
-  std::unordered_map<uint32_t, std::unique_ptr<DefInfo>> def_info_;
-
-  // Structured constructs, where enclosing constructs precede their children.
-  ConstructList constructs_;
-
-  // Information about entry point, if this function is referenced by one
-  const EntryPointInfo* ep_info_ = nullptr;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_FUNCTION_H_
diff --git a/src/reader/spirv/function_arithmetic_test.cc b/src/reader/spirv/function_arithmetic_test.cc
deleted file mode 100644
index 1114160..0000000
--- a/src/reader/spirv/function_arithmetic_test.cc
+++ /dev/null
@@ -1,1085 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint Fragment %100 "main"
-  OpExecutionMode %100 OriginUpperLeft
-
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %uint_10 = OpConstant %uint 10
-  %uint_20 = OpConstant %uint 20
-  %int_30 = OpConstant %int 30
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-  %float_70 = OpConstant %float 70
-
-  %ptr_uint = OpTypePointer Function %uint
-  %ptr_int = OpTypePointer Function %int
-  %ptr_float = OpTypePointer Function %float
-
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-  %v3float = OpTypeVector %float 3
-
-  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
-  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
-  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
-  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-  %v3float_50_60_70 = OpConstantComposite %v3float %float_50 %float_60 %float_70
-  %v3float_60_70_50 = OpConstantComposite %v3float %float_60 %float_70 %float_50
-
-  %m2v2float = OpTypeMatrix %v2float 2
-  %m2v3float = OpTypeMatrix %v3float 2
-  %m3v2float = OpTypeMatrix %v2float 3
-  %m2v2float_a = OpConstantComposite %m2v2float %v2float_50_60 %v2float_60_50
-  %m2v2float_b = OpConstantComposite %m2v2float %v2float_60_50 %v2float_50_60
-  %m3v2float_a = OpConstantComposite %m3v2float %v2float_50_60 %v2float_60_50 %v2float_50_60
-  %m2v3float_a = OpConstantComposite %m2v3float %v3float_50_60_70 %v3float_60_70_50
-)";
-}
-
-// Returns the AST dump for a given SPIR-V assembly constant.
-std::string AstFor(std::string assembly) {
-  if (assembly == "v2uint_10_20") {
-    return "vec2<u32>(10u, 20u)";
-  }
-  if (assembly == "v2uint_20_10") {
-    return "vec2<u32>(20u, 10u)";
-  }
-  if (assembly == "v2int_30_40") {
-    return "vec2<i32>(30, 40)";
-  }
-  if (assembly == "v2int_40_30") {
-    return "vec2<i32>(40, 30)";
-  }
-  if (assembly == "cast_int_v2uint_10_20") {
-    return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
-  }
-  if (assembly == "cast_uint_v2int_40_30") {
-    return "bitcast<vec2<u32>>(vec2<i32>(40, 30))";
-  }
-  if (assembly == "v2float_50_60") {
-    return "vec2<f32>(50.0, 60.0)";
-  }
-  if (assembly == "v2float_60_50") {
-    return "vec2<f32>(60.0, 50.0)";
-  }
-  return "bad case";
-}
-
-using SpvUnaryArithTest = SpvParserTestBase<::testing::Test>;
-
-TEST_F(SpvUnaryArithTest, SNegate_Int_Int) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %int %int_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : i32 = -(30);"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_Int_Uint) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %int %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : i32 = -(bitcast<i32>(10u));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_Uint_Int) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %uint %int_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>(-(30));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_Uint_Uint) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %uint %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>(-(bitcast<i32>(10u)));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_SignedVec_SignedVec) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %v2int %v2int_30_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<i32> = -(vec2<i32>(30, 40));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_SignedVec_UnsignedVec) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %v2int %v2uint_10_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          "let x_1 : vec2<i32> = -(bitcast<vec2<i32>>(vec2<u32>(10u, 20u)));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_SignedVec) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %v2uint %v2int_30_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(vec2<i32>(30, 40)));"));
-}
-
-TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_UnsignedVec) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSNegate %v2uint %v2uint_10_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(bitcast<vec2<i32>>(vec2<u32>(10u, 20u))));)"));
-}
-
-TEST_F(SpvUnaryArithTest, FNegate_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFNegate %float %float_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = -(50.0);"));
-}
-
-TEST_F(SpvUnaryArithTest, FNegate_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFNegate %v2float %v2float_50_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<f32> = -(vec2<f32>(50.0, 60.0));"));
-}
-
-struct BinaryData {
-  const std::string res_type;
-  const std::string lhs;
-  const std::string op;
-  const std::string rhs;
-  const std::string ast_type;
-  const std::string ast_lhs;
-  const std::string ast_op;
-  const std::string ast_rhs;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
-      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
-      << data.ast_op << "," << data.ast_rhs << "}";
-  return out;
-}
-
-using SpvBinaryArithTest =
-    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
-using SpvBinaryArithTestBasic = SpvParserTestBase<::testing::Test>;
-
-TEST_P(SpvBinaryArithTest, EmitExpression) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = )" + GetParam().op +
-                        " %" + GetParam().res_type + " %" + GetParam().lhs +
-                        " %" + GetParam().rhs + R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  std::ostringstream ss;
-  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
-     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
-}
-
-// Use this when the result might have extra bitcasts on the outside.
-struct BinaryDataGeneral {
-  const std::string res_type;
-  const std::string lhs;
-  const std::string op;
-  const std::string rhs;
-  const std::string wgsl_type;
-  const std::string expected;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryDataGeneral data) {
-  out << "BinaryDataGeneral{" << data.res_type << "," << data.lhs << ","
-      << data.op << "," << data.rhs << "," << data.wgsl_type << ","
-      << data.expected << "}";
-  return out;
-}
-
-using SpvBinaryArithGeneralTest =
-    SpvParserTestBase<::testing::TestWithParam<BinaryDataGeneral>>;
-
-TEST_P(SpvBinaryArithGeneralTest, EmitExpression) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = )" + GetParam().op +
-                        " %" + GetParam().res_type + " %" + GetParam().lhs +
-                        " %" + GetParam().rhs + R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  std::ostringstream ss;
-  ss << "let x_1 : " << GetParam().wgsl_type << " = " << GetParam().expected
-     << ";";
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_IAdd,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpIAdd", "uint_20", "u32", "10u", "+",
-                   "20u"},  // Both int
-        BinaryData{"int", "int_30", "OpIAdd", "int_40", "i32", "30", "+",
-                   "40"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpIAdd", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "+",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpIAdd", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "+", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_IAdd_MixedSignedness,
-    SpvBinaryArithGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpIAdd", "uint_10", "u32",
-                          "bitcast<u32>((30 + bitcast<i32>(10u)))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpIAdd", "uint_10", "i32",
-                          "(30 + bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpIAdd", "int_30", "u32",
-                          "(10u + bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpIAdd", "uint_10", "i32",
-                          "bitcast<i32>((20u + 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpIAdd", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) + bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpIAdd", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) + bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FAdd,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Scalar float
-        BinaryData{"float", "float_50", "OpFAdd", "float_60", "f32", "50.0",
-                   "+", "60.0"},  // Vector float
-        BinaryData{"v2float", "v2float_50_60", "OpFAdd", "v2float_60_50",
-                   "vec2<f32>", AstFor("v2float_50_60"), "+",
-                   AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ISub,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpISub", "uint_20", "u32", "10u", "-",
-                   "20u"},  // Both int
-        BinaryData{"int", "int_30", "OpISub", "int_40", "i32", "30", "-",
-                   "40"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpISub", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "-",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpISub", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "-", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ISub_MixedSignedness,
-    SpvBinaryArithGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpISub", "uint_10", "u32",
-                          R"(bitcast<u32>((30 - bitcast<i32>(10u))))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpISub", "uint_10", "i32",
-                          "(30 - bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpISub", "int_30", "u32",
-                          "(10u - bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpISub", "uint_10", "i32",
-                          "bitcast<i32>((20u - 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpISub", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) - bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpISub", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) - bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FSub,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Scalar float
-        BinaryData{"float", "float_50", "OpFSub", "float_60", "f32", "50.0",
-                   "-", "60.0"},  // Vector float
-        BinaryData{"v2float", "v2float_50_60", "OpFSub", "v2float_60_50",
-                   "vec2<f32>", AstFor("v2float_50_60"), "-",
-                   AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_IMul,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpIMul", "uint_20", "u32", "10u", "*",
-                   "20u"},  // Both int
-        BinaryData{"int", "int_30", "OpIMul", "int_40", "i32", "30", "*",
-                   "40"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpIMul", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "*",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpIMul", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "*", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_IMul_MixedSignedness,
-    SpvBinaryArithGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpIMul", "uint_10", "u32",
-                          "bitcast<u32>((30 * bitcast<i32>(10u)))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpIMul", "uint_10", "i32",
-                          "(30 * bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpIMul", "int_30", "u32",
-                          "(10u * bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpIMul", "uint_10", "i32",
-                          "bitcast<i32>((20u * 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpIMul", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) * bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpIMul", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) * bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FMul,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Scalar float
-        BinaryData{"float", "float_50", "OpFMul", "float_60", "f32", "50.0",
-                   "*", "60.0"},  // Vector float
-        BinaryData{"v2float", "v2float_50_60", "OpFMul", "v2float_60_50",
-                   "vec2<f32>", AstFor("v2float_50_60"), "*",
-                   AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_UDiv,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpUDiv", "uint_20", "u32", "10u", "/",
-                   "20u"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpUDiv", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "/",
-                   AstFor("v2uint_20_10")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SDiv,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both int
-        BinaryData{"int", "int_30", "OpSDiv", "int_40", "i32", "30", "/",
-                   "40"},  // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "/", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SDiv_MixedSignednessOperands,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Mixed, returning int, second arg uint
-        BinaryData{"int", "int_30", "OpSDiv", "uint_10", "i32", "30", "/",
-                   "bitcast<i32>(10u)"},
-        // Mixed, returning int, first arg uint
-        BinaryData{"int", "uint_10", "OpSDiv", "int_30", "i32",
-                   "bitcast<i32>(10u)", "/",
-                   "30"},  // Mixed, returning v2int, first arg v2uint
-        BinaryData{"v2int", "v2uint_10_20", "OpSDiv", "v2int_30_40",
-                   "vec2<i32>", AstFor("cast_int_v2uint_10_20"), "/",
-                   AstFor("v2int_30_40")},
-        // Mixed, returning v2int, second arg v2uint
-        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2uint_10_20",
-                   "vec2<i32>", AstFor("v2int_30_40"), "/",
-                   AstFor("cast_int_v2uint_10_20")}));
-
-TEST_F(SpvBinaryArithTestBasic, SDiv_Scalar_UnsignedResult) {
-  // The WGSL signed division operator expects both operands to be signed
-  // and the result is signed as well.
-  // In this test SPIR-V demands an unsigned result, so we have to
-  // wrap the result with an as-cast.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSDiv %uint %int_30 %int_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>((30 / 40));"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, SDiv_Vector_UnsignedResult) {
-  // The WGSL signed division operator expects both operands to be signed
-  // and the result is signed as well.
-  // In this test SPIR-V demands an unsigned result, so we have to
-  // wrap the result with an as-cast.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSDiv %v2uint %v2int_30_40 %v2int_40_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30, 40) / vec2<i32>(40, 30)));)"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FDiv,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Scalar float
-        BinaryData{"float", "float_50", "OpFDiv", "float_60", "f32", "50.0",
-                   "/", "60.0"},  // Vector float
-        BinaryData{"v2float", "v2float_50_60", "OpFDiv", "v2float_60_50",
-                   "vec2<f32>", AstFor("v2float_50_60"), "/",
-                   AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_UMod,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpUMod", "uint_20", "u32", "10u", "%",
-                   "20u"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpUMod", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "%",
-                   AstFor("v2uint_20_10")}));
-
-// Currently WGSL is missing a mapping for OpSRem
-// https://github.com/gpuweb/gpuweb/issues/702
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SMod,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both int
-        BinaryData{"int", "int_30", "OpSMod", "int_40", "i32", "30", "%",
-                   "40"},  // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "%", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SMod_MixedSignednessOperands,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Mixed, returning int, second arg uint
-        BinaryData{"int", "int_30", "OpSMod", "uint_10", "i32", "30", "%",
-                   "bitcast<i32>(10u)"},
-        // Mixed, returning int, first arg uint
-        BinaryData{"int", "uint_10", "OpSMod", "int_30", "i32",
-                   "bitcast<i32>(10u)", "%",
-                   "30"},  // Mixed, returning v2int, first arg v2uint
-        BinaryData{"v2int", "v2uint_10_20", "OpSMod", "v2int_30_40",
-                   "vec2<i32>", AstFor("cast_int_v2uint_10_20"), "%",
-                   AstFor("v2int_30_40")},
-        // Mixed, returning v2int, second arg v2uint
-        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2uint_10_20",
-                   "vec2<i32>", AstFor("v2int_30_40"), "%",
-                   AstFor("cast_int_v2uint_10_20")}));
-
-TEST_F(SpvBinaryArithTestBasic, SMod_Scalar_UnsignedResult) {
-  // The WGSL signed modulus operator expects both operands to be signed
-  // and the result is signed as well.
-  // In this test SPIR-V demands an unsigned result, so we have to
-  // wrap the result with an as-cast.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSMod %uint %int_30 %int_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>((30 % 40));"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, SMod_Vector_UnsignedResult) {
-  // The WGSL signed modulus operator expects both operands to be signed
-  // and the result is signed as well.
-  // In this test SPIR-V demands an unsigned result, so we have to
-  // wrap the result with an as-cast.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSMod %v2uint %v2int_30_40 %v2int_40_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30, 40) % vec2<i32>(40, 30)));)"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FRem,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Scalar float
-        BinaryData{"float", "float_50", "OpFRem", "float_60", "f32", "50.0",
-                   "%", "60.0"},  // Vector float
-        BinaryData{"v2float", "v2float_50_60", "OpFRem", "v2float_60_50",
-                   "vec2<f32>", AstFor("v2float_50_60"), "%",
-                   AstFor("v2float_60_50")}));
-
-TEST_F(SpvBinaryArithTestBasic, FMod_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFMod %float %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : f32 = (50.0 - (60.0 * floor((50.0 / 60.0))));"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, FMod_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFMod %v2float %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          R"(let x_1 : vec2<f32> = (vec2<f32>(50.0, 60.0) - (vec2<f32>(60.0, 50.0) * floor((vec2<f32>(50.0, 60.0) / vec2<f32>(60.0, 50.0)))));)"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, VectorTimesScalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2float %v2float_50_60
-     %2 = OpCopyObject %float %float_50
-     %10 = OpVectorTimesScalar %v2float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, MatrixTimesScalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v2float %m2v2float_a
-     %2 = OpCopyObject %float %float_50
-     %10 = OpMatrixTimesScalar %m2v2float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, VectorTimesMatrix) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v2float %m2v2float_a
-     %2 = OpCopyObject %v2float %v2float_50_60
-     %10 = OpMatrixTimesVector %v2float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, MatrixTimesVector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v2float %m2v2float_a
-     %2 = OpCopyObject %v2float %v2float_50_60
-     %10 = OpMatrixTimesVector %v2float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, MatrixTimesMatrix) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v2float %m2v2float_a
-     %2 = OpCopyObject %m2v2float %m2v2float_b
-     %10 = OpMatrixTimesMatrix %m2v2float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, Dot) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2float %v2float_50_60
-     %2 = OpCopyObject %v2float %v2float_60_50
-     %3 = OpDot %float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_3 : f32 = dot(x_1, x_2);"));
-}
-
-TEST_F(SpvBinaryArithTestBasic, OuterProduct) {
-  // OpOuterProduct is expanded to basic operations.
-  // The operands, even if used once, are given their own const definitions.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFAdd %v3float %v3float_50_60_70 %v3float_50_60_70 ; column vector
-     %2 = OpFAdd %v2float %v2float_60_50 %v2float_50_60 ; row vector
-     %3 = OpOuterProduct %m2v3float %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      got,
-      HasSubstr(
-          "let x_3 : mat2x3<f32> = mat2x3<f32>("
-          "vec3<f32>((x_2.x * x_1.x), (x_2.x * x_1.y), (x_2.x * x_1.z)), "
-          "vec3<f32>((x_2.y * x_1.x), (x_2.y * x_1.y), (x_2.y * x_1.z)));"))
-      << got;
-}
-
-struct BuiltinData {
-  const std::string spirv;
-  const std::string wgsl;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << "OpData{" << data.spirv << "," << data.wgsl << "}";
-  return out;
-}
-struct ArgAndTypeData {
-  const std::string spirv_type;
-  const std::string spirv_arg;
-  const std::string ast_type;
-};
-inline std::ostream& operator<<(std::ostream& out, ArgAndTypeData data) {
-  out << "ArgAndTypeData{" << data.spirv_type << "," << data.spirv_arg << ","
-      << data.ast_type << "}";
-  return out;
-}
-
-using SpvBinaryDerivativeTest = SpvParserTestBase<
-    ::testing::TestWithParam<std::tuple<BuiltinData, ArgAndTypeData>>>;
-
-TEST_P(SpvBinaryDerivativeTest, Derivatives) {
-  auto& builtin = std::get<0>(GetParam());
-  auto& arg = std::get<1>(GetParam());
-
-  const auto assembly = R"(
-     OpCapability DerivativeControl
-)" + Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %)" +
-                        arg.spirv_type + " %" + arg.spirv_arg + R"(
-     %2 = )" + builtin.spirv +
-                        " %" + arg.spirv_type + R"( %1
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_2 : " + arg.ast_type + " = " + builtin.wgsl + "(x_1);"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvBinaryDerivativeTest,
-    SpvBinaryDerivativeTest,
-    testing::Combine(
-        ::testing::Values(BuiltinData{"OpDPdx", "dpdx"},
-                          BuiltinData{"OpDPdy", "dpdy"},
-                          BuiltinData{"OpFwidth", "fwidth"},
-                          BuiltinData{"OpDPdxFine", "dpdxFine"},
-                          BuiltinData{"OpDPdyFine", "dpdyFine"},
-                          BuiltinData{"OpFwidthFine", "fwidthFine"},
-                          BuiltinData{"OpDPdxCoarse", "dpdxCoarse"},
-                          BuiltinData{"OpDPdyCoarse", "dpdyCoarse"},
-                          BuiltinData{"OpFwidthCoarse", "fwidthCoarse"}),
-        ::testing::Values(
-            ArgAndTypeData{"float", "float_50", "f32"},
-            ArgAndTypeData{"v2float", "v2float_50_60", "vec2<f32>"},
-            ArgAndTypeData{"v3float", "v3float_50_60_70", "vec3<f32>"})));
-
-TEST_F(SpvUnaryArithTest, Transpose_2x2) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v2float %m2v2float_a
-     %2 = OpTranspose %m2v2float %1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto* expected = "let x_2 : mat2x2<f32> = transpose(x_1);";
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvUnaryArithTest, Transpose_2x3) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m2v3float %m2v3float_a
-     %2 = OpTranspose %m3v2float %1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  // Note, in the AST dump mat_2_3 means 2 rows and 3 columns.
-  // So the column vectors have 2 elements.
-  // That is,   %m3v2float is __mat_2_3f32.
-  const auto* expected = "let x_2 : mat3x2<f32> = transpose(x_1);";
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvUnaryArithTest, Transpose_3x2) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %m3v2float %m3v2float_a
-     %2 = OpTranspose %m2v3float %1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto* expected = "let x_2 : mat2x3<f32> = transpose(x_1);";
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-// TODO(dneto): OpSRem. Missing from WGSL
-// https://github.com/gpuweb/gpuweb/issues/702
-
-// TODO(dneto): OpFRem. Missing from WGSL
-// https://github.com/gpuweb/gpuweb/issues/702
-
-// TODO(dneto): OpIAddCarry
-// TODO(dneto): OpISubBorrow
-// TODO(dneto): OpUMulExtended
-// TODO(dneto): OpSMulExtended
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_bit_test.cc b/src/reader/spirv/function_bit_test.cc
deleted file mode 100644
index d7cae0b..0000000
--- a/src/reader/spirv/function_bit_test.cc
+++ /dev/null
@@ -1,911 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string CommonTypes() {
-  return R"(
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %uint_10 = OpConstant %uint 10
-  %uint_20 = OpConstant %uint 20
-  %int_30 = OpConstant %int 30
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-
-  %ptr_uint = OpTypePointer Function %uint
-  %ptr_int = OpTypePointer Function %int
-  %ptr_float = OpTypePointer Function %float
-
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-
-  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
-  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
-  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
-  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-)";
-}
-
-std::string SimplePreamble() {
-  return R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint Fragment %100 "main"
-  OpExecutionMode %100 OriginUpperLeft
-)" + CommonTypes();
-}
-
-// Returns the AST dump for a given SPIR-V assembly constant.
-std::string AstFor(std::string assembly) {
-  if (assembly == "v2uint_10_20") {
-    return "vec2<u32>(10u, 20u)";
-  }
-  if (assembly == "v2uint_20_10") {
-    return "vec2<u32>(20u, 10u)";
-  }
-  if (assembly == "v2int_30_40") {
-    return "vec2<i32>(30, 40)";
-  }
-  if (assembly == "v2int_40_30") {
-    return "vec2<i32>(40, 30)";
-  }
-  if (assembly == "cast_int_v2uint_10_20") {
-    return "bitcast<vec2<i32>(vec2<u32>(10u, 20u))";
-  }
-  if (assembly == "v2float_50_60") {
-    return "vec2<f32>(50.0, 60.0))";
-  }
-  if (assembly == "v2float_60_50") {
-    return "vec2<f32>(60.0, 50.0))";
-  }
-  return "bad case";
-}
-
-using SpvUnaryBitTest = SpvParserTestBase<::testing::Test>;
-
-struct BinaryData {
-  const std::string res_type;
-  const std::string lhs;
-  const std::string op;
-  const std::string rhs;
-  const std::string ast_type;
-  const std::string ast_lhs;
-  const std::string ast_op;
-  const std::string ast_rhs;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
-      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
-      << data.ast_op << "," << data.ast_rhs << "}";
-  return out;
-}
-
-using SpvBinaryBitTest =
-    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
-using SpvBinaryBitTestBasic = SpvParserTestBase<::testing::Test>;
-
-TEST_P(SpvBinaryBitTest, EmitExpression) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = )" + GetParam().op +
-                        " %" + GetParam().res_type + " %" + GetParam().lhs +
-                        " %" + GetParam().rhs + R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  std::ostringstream ss;
-  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
-     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(ss.str()))
-      << assembly;
-}
-
-// Use this when the result might have extra bitcasts on the outside.
-struct BinaryDataGeneral {
-  const std::string res_type;
-  const std::string lhs;
-  const std::string op;
-  const std::string rhs;
-  const std::string wgsl_type;
-  const std::string expected;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryDataGeneral data) {
-  out << "BinaryDataGeneral{" << data.res_type << "," << data.lhs << ","
-      << data.op << "," << data.rhs << "," << data.wgsl_type << ","
-      << data.expected << "}";
-  return out;
-}
-
-using SpvBinaryBitGeneralTest =
-    SpvParserTestBase<::testing::TestWithParam<BinaryDataGeneral>>;
-
-TEST_P(SpvBinaryBitGeneralTest, EmitExpression) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = )" + GetParam().op +
-                        " %" + GetParam().res_type + " %" + GetParam().lhs +
-                        " %" + GetParam().rhs + R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
-  std::ostringstream ss;
-  ss << "let x_1 : " << GetParam().wgsl_type << " = " << GetParam().expected
-     << ";\nreturn;\n";
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftLeftLogical_Arg2Unsigned,
-    SpvBinaryBitTest,
-    ::testing::Values(
-        // uint uint -> uint
-        BinaryData{"uint", "uint_10", "OpShiftLeftLogical", "uint_20", "u32",
-                   "10u", "<<", "20u"},
-        // int, uint -> int
-        BinaryData{"int", "int_30", "OpShiftLeftLogical", "uint_20", "i32",
-                   "30", "<<", "20u"},
-        // v2uint v2uint -> v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpShiftLeftLogical",
-                   "v2uint_20_10", "vec2<u32>", AstFor("v2uint_10_20"), "<<",
-                   AstFor("v2uint_20_10")},
-        // v2int, v2uint -> v2int
-        BinaryData{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
-                   "vec2<i32>", AstFor("v2int_30_40"), "<<",
-                   AstFor("v2uint_20_10")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // WGSL requires second operand to be unsigned, so insert bitcasts
-    SpvParserTest_ShiftLeftLogical_Arg2Signed,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // int, int -> int
-        BinaryDataGeneral{"int", "int_30", "OpShiftLeftLogical", "int_40",
-                          "i32", "(30 << bitcast<u32>(40))"},
-        // uint, int -> uint
-        BinaryDataGeneral{"uint", "uint_10", "OpShiftLeftLogical", "int_40",
-                          "u32", "(10u << bitcast<u32>(40))"},
-        // v2uint, v2int -> v2uint
-        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftLeftLogical",
-                          "v2uint_20_10", "vec2<u32>",
-                          "(vec2<u32>(10u, 20u) << vec2<u32>(20u, 10u))"},
-        // v2int, v2int -> v2int
-        BinaryDataGeneral{
-            "v2int", "v2int_30_40", "OpShiftLeftLogical", "v2int_40_30",
-            "vec2<i32>",
-            "(vec2<i32>(30, 40) << bitcast<vec2<u32>>(vec2<i32>(40, 30)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftLeftLogical_BitcastResult,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // int, int -> uint
-        BinaryDataGeneral{"uint", "int_30", "OpShiftLeftLogical", "uint_10",
-                          "u32", "bitcast<u32>((30 << 10u))"},
-        // v2uint, v2int -> v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
-            "vec2<u32>",
-            "bitcast<vec2<u32>>((vec2<i32>(30, 40) << vec2<u32>(20u, 10u)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightLogical_Arg2Unsigned,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // uint, uint -> uint
-        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightLogical", "uint_20",
-                          "u32", "(10u >> 20u)"},
-        // int, uint -> int
-        BinaryDataGeneral{"int", "int_30", "OpShiftRightLogical", "uint_20",
-                          "i32", "bitcast<i32>((bitcast<u32>(30) >> 20u))"},
-        // v2uint, v2uint -> v2uint
-        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical",
-                          "v2uint_20_10", "vec2<u32>",
-                          "(vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))"},
-        // v2int, v2uint -> v2int
-        BinaryDataGeneral{
-            "v2int", "v2int_30_40", "OpShiftRightLogical", "v2uint_10_20",
-            "vec2<i32>",
-            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(30, 40)) >> vec2<u32>(10u, 20u))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightLogical_Arg2Signed,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // uint, int -> uint
-        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightLogical", "int_30",
-                          "u32", "(10u >> bitcast<u32>(30))"},
-        // int, int -> int
-        BinaryDataGeneral{
-            "int", "int_30", "OpShiftRightLogical", "int_40", "i32",
-            "bitcast<i32>((bitcast<u32>(30) >> bitcast<u32>(40)))"},
-        // v2uint, v2int -> v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2int_30_40",
-            "vec2<u32>",
-            "(vec2<u32>(10u, 20u) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))"},
-        // v2int, v2int -> v2int
-        BinaryDataGeneral{
-            "v2int", "v2int_40_30", "OpShiftRightLogical", "v2int_30_40",
-            "vec2<i32>",
-            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(40, 30)) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightLogical_BitcastResult,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // uint, uint -> int
-        BinaryDataGeneral{"int", "uint_20", "OpShiftRightLogical", "uint_10",
-                          "i32", "bitcast<i32>((20u >> 10u))"},
-        // v2uint, v2uint -> v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10",
-            "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightArithmetic_Arg2Unsigned,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // uint, uint -> uint
-        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightArithmetic",
-                          "uint_20", "u32",
-                          "bitcast<u32>((bitcast<i32>(10u) >> 20u))"},
-        // int, uint -> int
-        BinaryDataGeneral{"int", "int_30", "OpShiftRightArithmetic", "uint_10",
-                          "i32", "(30 >> 10u)"},
-        // v2uint, v2uint -> v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2uint_20_10",
-            "vec2<u32>",
-            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> vec2<u32>(20u, 10u))))"},
-        // v2int, v2uint -> v2int
-        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic",
-                          "v2uint_20_10", "vec2<i32>",
-                          "(vec2<i32>(40, 30) >> vec2<u32>(20u, 10u))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightArithmetic_Arg2Signed,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // uint, int -> uint
-        BinaryDataGeneral{
-            "uint", "uint_10", "OpShiftRightArithmetic", "int_30", "u32",
-            "bitcast<u32>((bitcast<i32>(10u) >> bitcast<u32>(30)))"},
-        // int, int -> int
-        BinaryDataGeneral{"int", "int_30", "OpShiftRightArithmetic", "int_40",
-                          "i32", "(30 >> bitcast<u32>(40))"},
-        // v2uint, v2int -> v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2int_30_40",
-            "vec2<u32>",
-            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))))"},
-        // v2int, v2int -> v2int
-        BinaryDataGeneral{
-            "v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2int_30_40",
-            "vec2<i32>",
-            "(vec2<i32>(40, 30) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightArithmetic_BitcastResult,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // int, uint -> uint
-        BinaryDataGeneral{"uint", "int_30", "OpShiftRightArithmetic", "uint_10",
-                          "u32", "bitcast<u32>((30 >> 10u))"},
-        // v2int, v2uint -> v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpShiftRightArithmetic", "v2uint_20_10",
-            "vec2<u32>",
-            "bitcast<vec2<u32>>((vec2<i32>(30, 40) >> vec2<u32>(20u, 10u)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseAnd,
-    SpvBinaryBitTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpBitwiseAnd", "uint_20", "u32", "10u",
-                   "&", "20u"},
-        // Both int
-        BinaryData{"int", "int_30", "OpBitwiseAnd", "int_40", "i32", "30", "&",
-                   "40"},
-        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
-        // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseAnd", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "&",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseAnd", "v2int_40_30",
-                   "vec2<i32>", AstFor("v2int_30_40"), "&",
-                   AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseAnd_MixedSignedness,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpBitwiseAnd", "uint_10", "u32",
-                          "bitcast<u32>((30 & bitcast<i32>(10u)))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpBitwiseAnd", "uint_10", "i32",
-                          "(30 & bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseAnd", "int_30", "u32",
-                          "(10u & bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpBitwiseAnd", "uint_10", "i32",
-                          "bitcast<i32>((20u & 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseAnd", "v2uint_10_20",
-            "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) & bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseAnd", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) & bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseOr,
-    SpvBinaryBitTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpBitwiseOr", "uint_20", "u32", "10u",
-                   "|", "20u"},
-        // Both int
-        BinaryData{"int", "int_30", "OpBitwiseOr", "int_40", "i32", "30", "|",
-                   "40"},
-        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
-        // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseOr", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "|",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseOr", "v2int_40_30",
-                   "vec2<i32>", AstFor("v2int_30_40"), "|",
-                   AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseOr_MixedSignedness,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpBitwiseOr", "uint_10", "u32",
-                          "bitcast<u32>((30 | bitcast<i32>(10u)))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpBitwiseOr", "uint_10", "i32",
-                          "(30 | bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseOr", "int_30", "u32",
-                          "(10u | bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpBitwiseOr", "uint_10", "i32",
-                          "bitcast<i32>((20u | 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseOr", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) | bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseOr", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) | bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseXor,
-    SpvBinaryBitTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"uint", "uint_10", "OpBitwiseXor", "uint_20", "u32", "10u",
-                   "^", "20u"},
-        // Both int
-        BinaryData{"int", "int_30", "OpBitwiseXor", "int_40", "i32", "30", "^",
-                   "40"},
-        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
-        // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseXor", "v2uint_20_10",
-                   "vec2<u32>", AstFor("v2uint_10_20"), "^",
-                   AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseXor", "v2int_40_30",
-                   "vec2<i32>", AstFor("v2int_30_40"), "^",
-                   AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_BitwiseXor_MixedSignedness,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // Mixed, uint <- int uint
-        BinaryDataGeneral{"uint", "int_30", "OpBitwiseXor", "uint_10", "u32",
-                          "bitcast<u32>((30 ^ bitcast<i32>(10u)))"},
-        // Mixed, int <- int uint
-        BinaryDataGeneral{"int", "int_30", "OpBitwiseXor", "uint_10", "i32",
-                          "(30 ^ bitcast<i32>(10u))"},
-        // Mixed, uint <- uint int
-        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseXor", "int_30", "u32",
-                          "(10u ^ bitcast<u32>(30))"},
-        // Mixed, int <- uint uint
-        BinaryDataGeneral{"int", "uint_20", "OpBitwiseXor", "uint_10", "i32",
-                          "bitcast<i32>((20u ^ 10u))"},
-        // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseXor", "v2uint_10_20",
-            "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) ^ bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
-        // Mixed, returning v2int
-        BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseXor", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) ^ bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
-
-TEST_F(SpvUnaryBitTest, Not_Int_Int) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %int %int_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = ~(30);"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_Int_Uint) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %int %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = bitcast<i32>(~(10u));"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_Uint_Int) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %uint %int_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = bitcast<u32>(~(30));"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_Uint_Uint) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %uint %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = ~(10u);"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_SignedVec_SignedVec) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %v2int %v2int_30_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = ~(vec2<i32>(30, 40));"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %v2int %v2uint_10_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          "let x_1 : vec2<i32> = bitcast<vec2<i32>>(~(vec2<u32>(10u, 20u)));"));
-}
-
-TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %v2uint %v2int_30_40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(~(vec2<i32>(30, 40)));"));
-}
-TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
-  const auto assembly = SimplePreamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpNot %v2uint %v2uint_10_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = ~(vec2<u32>(10u, 20u));"));
-}
-
-std::string BitTestPreamble() {
-  return R"(
-  OpCapability Shader
-  %glsl = OpExtInstImport "GLSL.std.450"
-  OpMemoryModel Logical GLSL450
-  OpEntryPoint GLCompute %100 "main"
-  OpExecutionMode %100 LocalSize 1 1 1
-
-  OpName %u1 "u1"
-  OpName %i1 "i1"
-  OpName %v2u1 "v2u1"
-  OpName %v2i1 "v2i1"
-
-)" + CommonTypes() +
-         R"(
-
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  %u1 = OpCopyObject %uint %uint_10
-  %i1 = OpCopyObject %int %int_30
-  %v2u1 = OpCopyObject %v2uint %v2uint_10_20
-  %v2i1 = OpCopyObject %v2int %v2int_30_40
-)";
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_Uint_Uint) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %uint %u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = countOneBits(u1);")) << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_Uint_Int) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %uint %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : u32 = bitcast<u32>(countOneBits(i1));"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_Int_Uint) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %int %u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : i32 = bitcast<i32>(countOneBits(u1));"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_Int_Int) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %int %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = countOneBits(i1);")) << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_UintVector_UintVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %v2uint %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = countOneBits(v2u1);"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_UintVector_IntVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %v2uint %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(countOneBits(v2i1));"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_IntVector_UintVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %v2int %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          "let x_1 : vec2<i32> = bitcast<vec2<i32>>(countOneBits(v2u1));"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitCount_IntVector_IntVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitCount %v2int %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = countOneBits(v2i1);"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_Uint_Uint) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %uint %u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = reverseBits(u1);")) << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_Uint_Int) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %uint %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_Int_Uint) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %int %u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_Int_Int) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %int %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = reverseBits(i1);")) << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_UintVector_UintVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %v2uint %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = reverseBits(v2u1);"))
-      << body;
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_UintVector_IntVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %v2uint %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_IntVector_UintVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %v2int %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
-}
-
-TEST_F(SpvUnaryBitTest, BitReverse_IntVector_IntVector) {
-  const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitReverse %v2int %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = reverseBits(v2i1);"))
-      << body;
-}
-
-// TODO(dneto): OpBitFieldInsert
-// TODO(dneto): OpBitFieldSExtract
-// TODO(dneto): OpBitFieldUExtract
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_call_test.cc b/src/reader/spirv/function_call_test.cc
deleted file mode 100644
index 1626fc8..0000000
--- a/src/reader/spirv/function_call_test.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "x_100"
-     OpExecutionMode %100 OriginUpperLeft
-)";
-}
-
-TEST_F(SpvParserTest, EmitStatement_VoidCallNoParams) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %50 = OpFunction %void None %voidfn
-     %entry_50 = OpLabel
-     OpReturn
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFunctionCall %void %50
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  const auto got = test::ToString(p->program());
-  const char* expect = R"(fn x_50() {
-  return;
-}
-
-fn x_100_1() {
-  x_50();
-  return;
-}
-
-@stage(fragment)
-fn x_100() {
-  x_100_1();
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParams) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %uintfn = OpTypeFunction %uint
-     %val = OpConstant %uint 42
-
-     %50 = OpFunction %uint None %uintfn
-     %entry_50 = OpLabel
-     OpReturnValue %val
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFunctionCall %uint %50
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  ast::StatementList f100;
-  {
-    auto fe = p->function_emitter(100);
-    EXPECT_TRUE(fe.EmitBody()) << p->error();
-    f100 = fe.ast_body();
-  }
-  ast::StatementList f50;
-  {
-    auto fe = p->function_emitter(50);
-    EXPECT_TRUE(fe.EmitBody()) << p->error();
-    f50 = fe.ast_body();
-  }
-  auto program = p->program();
-  EXPECT_THAT(test::ToString(program, f100),
-              HasSubstr("let x_1 : u32 = x_50();\nreturn;"));
-  EXPECT_THAT(test::ToString(program, f50), HasSubstr("return 42u;"));
-}
-
-TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParamsUsedTwice) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %uintfn = OpTypeFunction %uint
-     %val = OpConstant %uint 42
-     %ptr_uint = OpTypePointer Function %uint
-
-     %50 = OpFunction %uint None %uintfn
-     %entry_50 = OpLabel
-     OpReturnValue %val
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpVariable %ptr_uint Function
-     %1 = OpFunctionCall %uint %50
-     OpStore %10 %1
-     OpStore %10 %1
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  ast::StatementList f100;
-  {
-    auto fe = p->function_emitter(100);
-    EXPECT_TRUE(fe.EmitBody()) << p->error();
-    f100 = fe.ast_body();
-  }
-  ast::StatementList f50;
-  {
-    auto fe = p->function_emitter(50);
-    EXPECT_TRUE(fe.EmitBody()) << p->error();
-    f50 = fe.ast_body();
-  }
-  auto program = p->program();
-  EXPECT_EQ(test::ToString(program, f100), R"(var x_10 : u32;
-let x_1 : u32 = x_50();
-x_10 = x_1;
-x_10 = x_1;
-return;
-)");
-  EXPECT_THAT(test::ToString(program, f50), HasSubstr("return 42u;"));
-}
-
-TEST_F(SpvParserTest, EmitStatement_CallWithParams) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %uintfn_uint_uint = OpTypeFunction %uint %uint %uint
-     %val = OpConstant %uint 42
-     %val2 = OpConstant %uint 84
-
-     %50 = OpFunction %uint None %uintfn_uint_uint
-     %51 = OpFunctionParameter %uint
-     %52 = OpFunctionParameter %uint
-     %entry_50 = OpLabel
-     %sum = OpIAdd %uint %51 %52
-     OpReturnValue %sum
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFunctionCall %uint %50 %val %val2
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto program_ast_str = test::ToString(p->program());
-  const std::string expected = R"(fn x_50(x_51 : u32, x_52 : u32) -> u32 {
-  return (x_51 + x_52);
-}
-
-fn x_100_1() {
-  let x_1 : u32 = x_50(42u, 84u);
-  return;
-}
-
-@stage(fragment)
-fn x_100() {
-  x_100_1();
-}
-)";
-  EXPECT_EQ(program_ast_str, expected);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
deleted file mode 100644
index 8911f6f..0000000
--- a/src/reader/spirv/function_cfg_test.cc
+++ /dev/null
@@ -1,13229 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-// Make a local name so it's easier to triage errors.
-using SpvParserCFGTest = SpvParserTest;
-
-std::string Dump(const std::vector<uint32_t>& v) {
-  std::ostringstream o;
-  o << "{";
-  for (auto a : v) {
-    o << a << " ";
-  }
-  o << "}";
-  return o.str();
-}
-
-using ::testing::ElementsAre;
-using ::testing::Eq;
-using ::testing::UnorderedElementsAre;
-
-std::string CommonTypes() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %100 "main"
-    OpExecutionMode %100 OriginUpperLeft
-
-    OpName %var "var"
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-
-    %bool = OpTypeBool
-    %cond = OpConstantNull %bool
-    %cond2 = OpConstantTrue %bool
-    %cond3 = OpConstantFalse %bool
-
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %selector = OpConstant %uint 42
-    %signed_selector = OpConstant %int 42
-
-    %uintfn = OpTypeFunction %uint
-
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %uint_2 = OpConstant %uint 2
-    %uint_3 = OpConstant %uint 3
-    %uint_4 = OpConstant %uint 4
-    %uint_5 = OpConstant %uint 5
-    %uint_6 = OpConstant %uint 6
-    %uint_7 = OpConstant %uint 7
-    %uint_8 = OpConstant %uint 8
-    %uint_10 = OpConstant %uint 10
-    %uint_20 = OpConstant %uint 20
-    %uint_30 = OpConstant %uint 30
-    %uint_40 = OpConstant %uint 40
-    %uint_50 = OpConstant %uint 50
-    %uint_90 = OpConstant %uint 90
-    %uint_99 = OpConstant %uint 99
-
-    %ptr_Private_uint = OpTypePointer Private %uint
-    %var = OpVariable %ptr_Private_uint Private
-
-    %999 = OpConstant %uint 999
-  )";
-}
-
-/// Runs the necessary flow until and including labeling control
-/// flow constructs.
-/// @returns the result of labeling control flow constructs.
-bool FlowLabelControlFlowConstructs(FunctionEmitter* fe) {
-  fe->RegisterBasicBlocks();
-  EXPECT_TRUE(fe->RegisterMerges()) << fe->parser()->error();
-  fe->ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe->VerifyHeaderContinueMergeOrder()) << fe->parser()->error();
-  return fe->LabelControlFlowConstructs();
-}
-
-/// Runs the necessary flow until and including finding switch case
-/// headers.
-/// @returns the result of finding switch case headers.
-bool FlowFindSwitchCaseHeaders(FunctionEmitter* fe) {
-  EXPECT_TRUE(FlowLabelControlFlowConstructs(fe)) << fe->parser()->error();
-  return fe->FindSwitchCaseHeaders();
-}
-
-/// Runs the necessary flow until and including classify CFG edges,
-/// @returns the result of classify CFG edges.
-bool FlowClassifyCFGEdges(FunctionEmitter* fe) {
-  EXPECT_TRUE(FlowFindSwitchCaseHeaders(fe)) << fe->parser()->error();
-  return fe->ClassifyCFGEdges();
-}
-
-/// Runs the necessary flow until and including finding if-selection
-/// internal headers.
-/// @returns the result of classify CFG edges.
-bool FlowFindIfSelectionInternalHeaders(FunctionEmitter* fe) {
-  EXPECT_TRUE(FlowClassifyCFGEdges(fe)) << fe->parser()->error();
-  return fe->FindIfSelectionInternalHeaders();
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_SingleBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %42 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Sequence) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %20 = OpLabel
-     OpBranch %30
-
-     %30 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_If) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %40
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Switch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %80 20 %20 30 %30
-
-     %20 = OpLabel
-     OpBranch %30 ; fall through
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %80 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Loop_SingleBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Loop_Simple) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %20 ; back edge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Kill) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpKill
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_Unreachable) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpUnreachable
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.TerminatorsAreValid());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_MissingTerminator) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-
-     OpFunctionEnd
-  )"));
-  // The SPIRV-Tools internal representation rejects this case earlier.
-  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowLoopToEntryBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpBranch %10 ; not allowed
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.TerminatorsAreValid());
-  EXPECT_THAT(p->error(), Eq("Block 20 branches to function entry block 10"));
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowNonBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %999 ; definitely wrong
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.TerminatorsAreValid());
-  EXPECT_THAT(p->error(),
-              Eq("Block 10 in function 100 branches to 999 which is "
-                 "not a block in the function"));
-}
-
-TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowBlockInDifferentFunction) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %210
-
-     OpFunctionEnd
-
-
-     %200 = OpFunction %void None %voidfn
-
-     %210 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.TerminatorsAreValid());
-  EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 210 which "
-                             "is not a block in the function"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_NoMerges) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  const auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->merge_for_header, 0u);
-  EXPECT_EQ(bi->continue_for_header, 0u);
-  EXPECT_EQ(bi->header_for_merge, 0u);
-  EXPECT_EQ(bi->header_for_continue, 0u);
-  EXPECT_FALSE(bi->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_GoodSelectionMerge_BranchConditional) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Header points to the merge
-  const auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->merge_for_header, 99u);
-  EXPECT_EQ(bi10->continue_for_header, 0u);
-  EXPECT_EQ(bi10->header_for_merge, 0u);
-  EXPECT_EQ(bi10->header_for_continue, 0u);
-  EXPECT_FALSE(bi10->is_continue_entire_loop);
-
-  // Middle block is neither header nor merge
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 0u);
-  EXPECT_EQ(bi20->continue_for_header, 0u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 0u);
-  EXPECT_FALSE(bi20->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 10u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_GoodSelectionMerge_Switch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Header points to the merge
-  const auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->merge_for_header, 99u);
-  EXPECT_EQ(bi10->continue_for_header, 0u);
-  EXPECT_EQ(bi10->header_for_merge, 0u);
-  EXPECT_EQ(bi10->header_for_continue, 0u);
-  EXPECT_FALSE(bi10->is_continue_entire_loop);
-
-  // Middle block is neither header nor merge
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 0u);
-  EXPECT_EQ(bi20->continue_for_header, 0u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 0u);
-  EXPECT_FALSE(bi20->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 10u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_GoodLoopMerge_SingleBlockLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Entry block is not special
-  const auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->merge_for_header, 0u);
-  EXPECT_EQ(bi10->continue_for_header, 0u);
-  EXPECT_EQ(bi10->header_for_merge, 0u);
-  EXPECT_EQ(bi10->header_for_continue, 0u);
-  EXPECT_FALSE(bi10->is_continue_entire_loop);
-
-  // Single block loop is its own continue, and marked as single block loop.
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 99u);
-  EXPECT_EQ(bi20->continue_for_header, 20u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 20u);
-  EXPECT_TRUE(bi20->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 20u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest,
-       RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Loop header points to continue (itself) and merge
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 99u);
-  EXPECT_EQ(bi20->continue_for_header, 20u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 20u);
-  EXPECT_TRUE(bi20->is_continue_entire_loop);
-
-  // Backedge block, but is not a declared header, merge, or continue
-  const auto* bi40 = fe.GetBlockInfo(40);
-  ASSERT_NE(bi40, nullptr);
-  EXPECT_EQ(bi40->merge_for_header, 0u);
-  EXPECT_EQ(bi40->continue_for_header, 0u);
-  EXPECT_EQ(bi40->header_for_merge, 0u);
-  EXPECT_EQ(bi40->header_for_continue, 0u);
-  EXPECT_FALSE(bi40->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 20u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest,
-       RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranchConditional %cond %40 %99
-
-     %40 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Loop header points to continue and merge
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 99u);
-  EXPECT_EQ(bi20->continue_for_header, 40u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 0u);
-  EXPECT_FALSE(bi20->is_continue_entire_loop);
-
-  // Continue block points to header
-  const auto* bi40 = fe.GetBlockInfo(40);
-  ASSERT_NE(bi40, nullptr);
-  EXPECT_EQ(bi40->merge_for_header, 0u);
-  EXPECT_EQ(bi40->continue_for_header, 0u);
-  EXPECT_EQ(bi40->header_for_merge, 0u);
-  EXPECT_EQ(bi40->header_for_continue, 20u);
-  EXPECT_FALSE(bi40->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 20u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) {  // NOLINT
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_TRUE(fe.RegisterMerges());
-
-  // Loop header points to continue and merge
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->merge_for_header, 99u);
-  EXPECT_EQ(bi20->continue_for_header, 40u);
-  EXPECT_EQ(bi20->header_for_merge, 0u);
-  EXPECT_EQ(bi20->header_for_continue, 0u);
-  EXPECT_FALSE(bi20->is_continue_entire_loop);
-
-  // Continue block points to header
-  const auto* bi40 = fe.GetBlockInfo(40);
-  ASSERT_NE(bi40, nullptr);
-  EXPECT_EQ(bi40->merge_for_header, 0u);
-  EXPECT_EQ(bi40->continue_for_header, 0u);
-  EXPECT_EQ(bi40->header_for_merge, 0u);
-  EXPECT_EQ(bi40->header_for_continue, 20u);
-  EXPECT_FALSE(bi40->is_continue_entire_loop);
-
-  // Merge block points to the header
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->merge_for_header, 0u);
-  EXPECT_EQ(bi99->continue_for_header, 0u);
-  EXPECT_EQ(bi99->header_for_merge, 20u);
-  EXPECT_EQ(bi99->header_for_continue, 0u);
-  EXPECT_FALSE(bi99->is_continue_entire_loop);
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_SelectionMerge_BadTerminator) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(), Eq("Selection header 10 does not end in an "
-                             "OpBranchConditional or OpSwitch instruction"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_LoopMerge_BadTerminator) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpSwitch %selector %99 30 %30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(), Eq("Loop header 20 does not end in an OpBranch or "
-                             "OpBranchConditional instruction"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_BadMergeBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %void None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(),
-              Eq("Structured header block 10 declares invalid merge block 2"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_HeaderIsItsOwnMerge) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %10 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(),
-              Eq("Structured header block 10 cannot be its own merge block"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_MergeReused) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %20 %49
-
-     %20 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %49 None  ; can't reuse merge block
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 49 declared as merge block for more than one header: 10, 50"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_EntryBlockIsLoopHeader) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %10 %99
-
-     %30 = OpLabel
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(),
-              Eq("Function entry block 10 cannot be a loop header"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_BadContinueTarget) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %999 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(),
-              Eq("Structured header 20 declares invalid continue target 999"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_MergeSameAsContinue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %50 %50 None
-     OpBranchConditional %cond %20 %99
-
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(),
-              Eq("Invalid structured header block 20: declares block 50 as "
-                 "both its merge block and continue target"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_ContinueReused) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond %30 %49
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %20
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %70
-
-     %70 = OpLabel
-     OpBranch %50
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(), Eq("Block 40 declared as continue target for more "
-                             "than one header: 20, 50"));
-}
-
-TEST_F(SpvParserCFGTest, RegisterMerges_SingleBlockLoop_NotItsOwnContinue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %20 %99
-
-     %30 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 20 branches to itself but is not its own continue target"));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_OneBlock) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %42 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(42));
-
-  const auto* bi = fe.GetBlockInfo(42);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->pos, 0u);
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_IgnoreStaticalyUnreachable) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %15 = OpLabel ; statically dead
-     OpReturn
-
-     %20 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_KillIsDeadEnd) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %15 = OpLabel ; statically dead
-     OpReturn
-
-     %20 = OpLabel
-     OpKill        ; Kill doesn't lead anywhere
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_UnreachableIsDeadEnd) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %15 = OpLabel ; statically dead
-     OpReturn
-
-     %20 = OpLabel
-     OpUnreachable ; Unreachable doesn't lead anywhere
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_ReorderSequence) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %30 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %30 ; backtrack, but does dominate %30
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99));
-
-  const auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->pos, 0u);
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->pos, 1u);
-  const auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->pos, 2u);
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->pos, 3u);
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_DupConditionalBranch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectConditionalBranchOrder) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %30 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel ; dominated by %20, so follow %20
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_TrueOnlyBranch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_FalseOnlyBranch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_SwitchOrderNaturallyReversed) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %99 = OpLabel
-     OpReturn
-
-     %30 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 99));
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %80 20 %20 30 %30
-
-     %80 = OpLabel ; the default case
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     %30 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Switch_DefaultSameAsACase) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20 30 %30 40 %40
-
-     %99 = OpLabel
-     OpReturn
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 40, 20, 30, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectSwitchCaseFallthrough) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     ; SPIR-V validation requires a fallthrough destination to immediately
-     ; follow the source. So %20 -> %40, %30 -> %50
-     OpSwitch %selector %99 20 %20 40 %40 30 %30 50 %50
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %50 ; fallthrough
-
-     %20 = OpLabel
-     OpBranch %40 ; fallthrough
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 20, 40, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %80 20 %20 30 %30 40 %40
-
-     %80 = OpLabel ; the default case
-     OpBranch %30 ; fallthrough to another case
-
-     %99 = OpLabel
-     OpReturn
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %20 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 40, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %80 20 %20 30 %30
-
-     %20 = OpLabel
-     OpBranch %80 ; fallthrough to default
-
-     %80 = OpLabel ; the default case
-     OpBranch %30 ; fallthrough to 30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel ; dominated by %30, so follow %30
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_SwitchCasesFallthrough_OppositeDirections) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %30 ; forward
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     ; SPIR-V doesn't actually allow a fall-through that goes backward in the
-     ; module. But the block ordering algorithm tolerates it.
-     %50 = OpLabel
-     OpBranch %40 ; backward
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 50, 40, 20, 30, 99))
-      << assembly;
-
-  // We're deliberately testing a case that SPIR-V doesn't allow.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     ; SPIR-V validation requires a fallthrough destination to immediately
-     ; follow the source. So %20 -> %40
-     OpSwitch %selector %99 20 %20 40 %40 30 %30 50 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpBranch %40
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %40 = OpLabel
-     OpBranch %60
-
-     %50 = OpLabel
-     OpBranch %70
-
-     %60 = OpLabel
-     OpBranch %99
-
-     %70 = OpLabel
-     OpBranch %99
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 70, 20, 40, 60, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_If_Contains_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %30 %40
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %49
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %50 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond %60 %70
-
-     %79 = OpLabel
-     OpBranch %99
-
-     %60 = OpLabel
-     OpBranch %79
-
-     %70 = OpLabel
-     OpBranch %79
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_If_In_SwitchCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %50 20 %20 50 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %30 %40
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %49
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %50 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond %60 %70
-
-     %79 = OpLabel
-     OpBranch %99
-
-     %60 = OpLabel
-     OpBranch %79
-
-     %70 = OpLabel
-     OpBranch %79
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %50 20 %20 50 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %30 %40
-
-     %49 = OpLabel
-     OpBranchConditional %cond %99 %50 ; fallthrough
-
-     %30 = OpLabel
-     OpBranch %49
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %50 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond %60 %70
-
-     %79 = OpLabel
-     OpBranch %99
-
-     %60 = OpLabel
-     OpBranch %79
-
-     %70 = OpLabel
-     OpBranch %79
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_IfBreak_In_SwitchCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %50 20 %20 50 %50
-
-     %99 = OpLabel
-     OpReturn
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %99 %40 ; break-if
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond %60 %99 ; break-unless
-
-     %60 = OpLabel
-     OpBranch %79
-
-     %79 = OpLabel ; dominated by 60, so must follow 60
-     OpBranch %99
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 49, 50, 60, 79, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_Simple) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     ; The entry block can't be the target of a branch
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_Infinite) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     ; The entry block can't be the target of a branch
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_DupInfinite) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     ; The entry block can't be the target of a branch
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_HeaderHasBreakIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99 ; like While
-
-     %30 = OpLabel ; trivial body
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_HeaderHasBreakUnless) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %99 %30 ; has break-unless
-
-     %30 = OpLabel ; trivial body
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreak) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %99 ; break
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreakIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %99 %40 ; break-if
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreakUnless) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %40 %99 ; break-unless
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %40 %45 ; nested if
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %45 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 45, 49, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If_Break) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %40 %49 ; nested if
-
-     %40 = OpLabel
-     OpBranch %99   ; break from nested if
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasContinueIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %50 %40 ; continue-if
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasContinueUnless) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %40 %50 ; continue-unless
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If_Continue) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %40 %49 ; nested if
-
-     %40 = OpLabel
-     OpBranch %50   ; continue from nested if
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpSwitch %selector %49 40 %40 45 %45 ; fully nested switch
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %45 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
-      << assembly;
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch_CaseBreaks) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpSwitch %selector %49 40 %40 45 %45
-
-     %40 = OpLabel
-     ; This case breaks out of the loop. This is not possible in C
-     ; because "break" will escape the switch only.
-     OpBranch %99
-
-     %45 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
-      << assembly;
-
-  // Fails SPIR-V validation:
-  // Branch from block 40 to block 99 is an invalid exit from construct starting
-  // at block 30; branch bypasses merge block 49
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch_CaseContinues) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %49 None
-     OpSwitch %selector %49 40 %40 45 %45
-
-     %40 = OpLabel
-     OpBranch %50   ; continue bypasses switch merge
-
-     %45 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
-      << assembly;
-}
-
-// TODO(crbug.com/tint/1406): Re-enable with the typo fix (preceeded->preceded)
-// once that typo fix is rolled in Tint's SPIRV-Tools.
-TEST_F(SpvParserCFGTest,
-       DISABLED_ComputeBlockOrder_Loop_BodyHasSwitchContinueBreak) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     ; OpSwitch must be preceded by a selection merge
-     OpSwitch %selector %99 50 %50 ; default is break, 50 is continue
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(),
-              HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge"));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_Sequence) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %60
-
-     %60 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_ContainsIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond2 %60 %70
-
-     %89 = OpLabel
-     OpBranch %20 ; backedge
-
-     %60 = OpLabel
-     OpBranch %89
-
-     %70 = OpLabel
-     OpBranch %89
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 70, 89, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_HasBreakIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranchConditional %cond2 %99 %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_HasBreakUnless) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranchConditional %cond2 %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
-}
-
-// TODO(crbug.com/tint/1406): Re-enable with the typo fix (preceeded->preceded)
-// once that typo fix is rolled in Tint's SPIRV-Tools.
-TEST_F(SpvParserCFGTest, DISABLED_ComputeBlockOrder_Loop_Continue_SwitchBreak) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     ; Updated SPIR-V rule:
-     ; OpSwitch must be preceded by a selection.
-     OpSwitch %selector %20 99 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(),
-              HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge"));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranch %37
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     OpBranch %30 ; backedge
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerBreak) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranchConditional %cond3 %49 %37 ; break to inner merge
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     OpBranch %30 ; backedge
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinue) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranchConditional %cond3 %37 %49 ; continue to inner continue target
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     OpBranch %30 ; backedge
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinueBreaks) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranch %37
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     OpBranchConditional %cond3 %30 %49 ; backedge and inner break
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-}
-
-TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinueContinues) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranch %37
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     OpBranchConditional %cond3 %30 %50 ; backedge and continue to outer
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-
-  p->DeliberatelyInvalidSpirv();
-  // SPIR-V validation fails:
-  //    block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not
-  //    via a structured exit"
-}
-
-TEST_F(SpvParserCFGTest,
-       ComputeBlockOrder_Loop_Loop_SwitchBackedgeBreakContinue) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpLoopMerge %49 %40 None
-     OpBranchConditional %cond2 %35 %49
-
-     %35 = OpLabel
-     OpBranch %37
-
-     %37 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner loop's continue
-     ; This switch does triple duty:
-     ; default -> backedge
-     ; 49 -> loop break
-     ; 49 -> inner loop break
-     ; 50 -> outer loop continue
-     OpSwitch %selector %30 49 %49 50 %50
-
-     %49 = OpLabel ; inner loop's merge
-     OpBranch %50
-
-     %50 = OpLabel ; outer loop's continue
-     OpBranch %20 ; outer loop's backege
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-
-  EXPECT_THAT(fe.block_order(),
-              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
-
-  p->DeliberatelyInvalidSpirv();
-  // SPIR-V validation fails:
-  //    block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not
-  //    via a structured exit"
-}
-
-TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_Selection_Good) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-}
-
-TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()) << p->error();
-}
-
-TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-}
-
-TEST_F(SpvParserCFGTest,
-       VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %20 None ; this is backward
-     OpBranchConditional %cond2 %60 %99
-
-     %60 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_THAT(p->error(),
-              Eq("Header 50 does not strictly dominate its merge block 20"))
-      << *fe.GetBlockInfo(50) << std::endl
-      << *fe.GetBlockInfo(20) << std::endl
-      << Dump(fe.block_order());
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) {  // NOLINT
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpLoopMerge %99 %20 None ; this is backward
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %50
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_THAT(p->error(),
-              Eq("Loop header 50 does not dominate its continue target 20"))
-      << *fe.GetBlockInfo(50) << std::endl
-      << *fe.GetBlockInfo(20) << std::endl
-      << Dump(fe.block_order());
-}
-
-TEST_F(SpvParserCFGTest,
-       VerifyHeaderContinueMergeOrder_MergeInsideContinueTarget) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpLoopMerge %60 %70 None
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %70
-
-     %70 = OpLabel
-     OpBranch %50
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_THAT(p->error(),
-              Eq("Merge block 60 for loop headed at block 50 appears at or "
-                 "before the loop's continue construct headed by block 70"))
-      << Dump(fe.block_order());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  EXPECT_EQ(fe.constructs().size(), 1u);
-  auto& c = fe.constructs().front();
-  EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,1) begin_id:10 end_id:0 "
-                              "depth:0 parent:null }"));
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %5
-
-     %5 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  EXPECT_EQ(fe.constructs().size(), 1u);
-  auto& c = fe.constructs().front();
-  EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,2) begin_id:10 end_id:0 "
-                              "depth:0 parent:null }"));
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
-  EXPECT_EQ(fe.GetBlockInfo(5)->construct, c.get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 2u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpBranch %200
-
-     %200 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 2u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,6) begin_id:5 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [1,4) begin_id:10 end_id:99 depth:1 parent:Function@5 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(5)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(200)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_SwitchSelection) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %40 20 %20 30 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 2u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_SingleBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 2u);
-  // A single-block loop consists *only* of a continue target with one block in
-  // it.
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,3) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [1,2) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue) {
-  // In this case, we have a continue construct and a non-empty loop construct.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [3,5) begin_id:40 end_id:99 depth:1 parent:Function@10 in-c:Continue@40 }
-  Construct{ Loop [1,3) begin_id:20 end_id:40 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue) {
-  // In this case, we have only a continue construct and no loop construct.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [1,5) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpBranch %50
-
-     ; %50 is the merge block for the selection starting at 10,
-     ; and its own continue target.
-     %50 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %50 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 3u);
-  // A single-block loop consists *only* of a continue target with one block in
-  // it.
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
-  Construct{ Continue [2,3) begin_id:50 end_id:99 depth:1 parent:Function@10 in-c:Continue@50 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest,
-       LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpBranch %50
-
-     ; %50 is the merge block for the selection starting at 10,
-     ; and a loop block header but not its own continue target.
-     %50 = OpLabel
-     OpLoopMerge %99 %60 None
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %50
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
-  Construct{ Continue [3,4) begin_id:60 end_id:99 depth:1 parent:Function@10 in-c:Continue@60 }
-  Construct{ Loop [2,3) begin_id:50 end_id:60 depth:1 parent:Function@10 scope:[2,4) in-l:Loop@50 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %40 ;; true only
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; merge for first inner "if"
-     OpBranch %49
-
-     %49 = OpLabel ; an extra padding block
-     OpBranch %99
-
-     %50 = OpLabel
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond %89 %60 ;; false only
-
-     %60 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,9) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,8) begin_id:10 end_id:99 depth:1 parent:Function@10 }
-  Construct{ IfSelection [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 }
-  Construct{ IfSelection [5,7) begin_id:50 end_id:89 depth:2 parent:IfSelection@10 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Switch_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel ; if-then nested in case 20
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %30 %49
-
-     %30 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel ; unles-then nested in case 50
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond %89 %60
-
-     %60 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  // The ordering among siblings depends on the computed block order.
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ SwitchSelection [0,7) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
-  Construct{ IfSelection [1,3) begin_id:50 end_id:89 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
-  Construct{ IfSelection [4,6) begin_id:20 end_id:49 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_Switch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %89 20 %30
-
-     %30 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 3u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 }
-  Construct{ SwitchSelection [1,3) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c-l-s:SwitchSelection@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Loop_Loop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %89 %50 None
-     OpBranchConditional %cond %30 %89
-
-     %30 = OpLabel ; single block loop
-     OpLoopMerge %40 %30 None
-     OpBranchConditional %cond2 %30 %40
-
-     %40 = OpLabel ; padding block
-     OpBranch %50
-
-     %50 = OpLabel ; outer continue target
-     OpBranch %60
-
-     %60 = OpLabel
-     OpBranch %20
-
-     %89 = OpLabel ; outer merge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 }
-  Construct{ Loop [1,4) begin_id:20 end_id:50 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
-  Construct{ Continue [2,3) begin_id:30 end_id:40 depth:2 parent:Loop@20 in-l:Loop@20 in-c:Continue@30 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Loop_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; If, nested in the loop construct
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %40 %49
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel ; merge for inner if
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 }
-  Construct{ Loop [1,5) begin_id:20 end_id:80 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
-  Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Loop@20 in-l:Loop@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(80)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_LoopContinue_If) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; If, nested at the top of the continue construct head
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %40 %49
-
-     %40 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel ; merge for inner if, backedge
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 }
-  Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
-  Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Continue@30 in-c:Continue@30 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_SingleBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpLoopMerge %89 %20 None
-     OpBranchConditional %cond %20 %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 3u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
-  Construct{ Continue [1,2) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel ; start loop body
-     OpLoopMerge %89 %40 None
-     OpBranchConditional %cond %30 %89
-
-     %30 = OpLabel ; body block
-     OpBranch %40
-
-     %40 = OpLabel ; continue target
-     OpBranch %50
-
-     %50 = OpLabel ; backedge block
-     OpBranch %20
-
-     %89 = OpLabel ; merge for the loop
-     OpBranch %99
-
-     %99 = OpLabel ; merge for the if
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  fe.RegisterMerges();
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 }
-  Construct{ Continue [3,5) begin_id:40 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@40 }
-  Construct{ Loop [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 scope:[1,5) in-l:Loop@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
-  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_LoopInterallyDiverge) {
-  // In this case, insert a synthetic if-selection with the same blocks
-  // as the loop construct.
-  // crbug.com/tint/524
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranchConditional %cond %30 %40 ; divergence to distinct targets in the body
-
-       %30 = OpLabel
-       OpBranch %90
-
-       %40 = OpLabel
-       OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
-  const auto& constructs = fe.constructs();
-  EXPECT_EQ(constructs.size(), 4u);
-  ASSERT_THAT(ToString(constructs), Eq(R"(ConstructList{
-  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
-  Construct{ Continue [4,5) begin_id:90 end_id:99 depth:1 parent:Function@10 in-c:Continue@90 }
-  Construct{ Loop [1,4) begin_id:20 end_id:90 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
-  Construct{ IfSelection [1,4) begin_id:20 end_id:90 depth:2 parent:Loop@20 in-l:Loop@20 }
-})")) << constructs;
-  // The block records the nearest enclosing construct.
-  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
-  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
-  EXPECT_EQ(fe.GetBlockInfo(90)->construct, constructs[1].get());
-  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsLongRangeBackedge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %10 30 %30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to default target "
-                             "block 10 can't be a back-edge"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsSelfLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %20 30 %30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  // Self-loop that isn't its own continue target is already rejected with a
-  // different message.
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 20 branches to itself but is not its own continue target"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultCantEscapeSwitch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %50 None
-     OpSwitch %selector %99 30 %30 ; default goes past the merge
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel ; merge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to default block 99 "
-                             "escapes the selection construct"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultForTwoSwitches_AsMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %89 20 %20
-
-     %20 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %89 60 %60
-
-     %60 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(),
-              Eq("Block 89 is the default block for switch-selection header 10 "
-                 "and also the merge block for 50 (violates dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindSwitchCaseHeaders_DefaultForTwoSwitches_AsCaseClause) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %80 20 %20
-
-     %20 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %80 60 %60
-
-     %60 = OpLabel
-     OpBranch %89 ; fallthrough
-
-     %80 = OpLabel ; default for both switches
-     OpBranch %89
-
-     %89 = OpLabel ; inner selection merge
-     OpBranch %99
-
-     %99 = OpLabel ; outer selection mege
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Block 80 is declared as the default target for "
-                             "two OpSwitch instructions, at blocks 10 and 50"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsLongRangeBackedge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 10 %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target "
-                             "block 10 can't be a back-edge"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsSelfLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  // The error is caught earlier
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 20 branches to itself but is not its own continue target"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseCanBeSwitchMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  // TODO(crbug.com/tint/774) Re-enable after codegen bug fixed.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseCantEscapeSwitch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None ; force %99 to be very late in block order
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %89 20 %99
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target block "
-                             "99 escapes the selection construct"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseForMoreThanOneSwitch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %89 50 %50
-
-     %50 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(),
-              Eq("Block 50 is declared as the switch case target for two "
-                 "OpSwitch instructions, at blocks 10 and 20"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsMergeForAnotherConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %49 None
-     OpSwitch %selector %49 20 %20
-
-     %20 = OpLabel
-     OpBranch %49
-
-     %49 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpSelectionMerge %20 None ; points back to the case.
-     OpBranchConditional %cond %60 %99
-
-     %60 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to case target block "
-                             "20 escapes the selection construct"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_NoSwitch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->case_head_for, nullptr);
-  EXPECT_EQ(bi10->default_head_for, nullptr);
-  EXPECT_FALSE(bi10->default_is_merge);
-  EXPECT_EQ(bi10->case_values.get(), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->case_head_for, nullptr);
-  ASSERT_NE(bi99->default_head_for, nullptr);
-  EXPECT_EQ(bi99->default_head_for->begin_id, 10u);
-  EXPECT_TRUE(bi99->default_is_merge);
-  EXPECT_EQ(bi99->case_values.get(), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsNotMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->case_head_for, nullptr);
-  ASSERT_NE(bi30->default_head_for, nullptr);
-  EXPECT_EQ(bi30->default_head_for->begin_id, 10u);
-  EXPECT_FALSE(bi30->default_is_merge);
-  EXPECT_EQ(bi30->case_values.get(), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsNotDefault) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 200 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  ASSERT_NE(bi20->case_head_for, nullptr);
-  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
-  EXPECT_EQ(bi20->default_head_for, nullptr);
-  EXPECT_FALSE(bi20->default_is_merge);
-  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsDefault) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %20 200 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  ASSERT_NE(bi20->case_head_for, nullptr);
-  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
-  EXPECT_EQ(bi20->default_head_for, bi20->case_head_for);
-  EXPECT_FALSE(bi20->default_is_merge);
-  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_ManyCasesWithSameValue_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 200 %20 200 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-
-  EXPECT_THAT(p->error(),
-              Eq("Duplicate case value 200 in OpSwitch in block 10"));
-}
-
-TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_ManyValuesWithSameCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 200 %20 300 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  fe.RegisterMerges();
-  fe.LabelControlFlowConstructs();
-  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
-
-  const auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  ASSERT_NE(bi20->case_head_for, nullptr);
-  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
-  EXPECT_EQ(bi20->default_head_for, nullptr);
-  EXPECT_FALSE(bi20->default_is_merge);
-  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200, 300));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BranchEscapesIfConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %30 %50
-
-     %30 = OpLabel
-     OpBranch %80   ; bad exit to %80
-
-     %50 = OpLabel
-     OpBranch %80
-
-     %80 = OpLabel  ; bad target
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
-  // Some further processing
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 80 is an invalid exit from construct "
-         "starting at block 20; branch bypasses merge block 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_ReturnInContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; body
-     OpBranch %50
-
-     %50 = OpLabel
-     OpReturn
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
-  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
-                             "construct starting at 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_KillInContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; body
-     OpBranch %50
-
-     %50 = OpLabel
-     OpKill
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
-                             "construct starting at 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_UnreachableInContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; body
-     OpBranch %50
-
-     %50 = OpLabel
-     OpUnreachable
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
-                             "construct starting at 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_NotInContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; body
-     OpBranch %20  ; bad backedge
-
-     %50 = OpLabel ; continue target
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_BackEdge_NotInLastBlockOfContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; body
-     OpBranch %50
-
-     %50 = OpLabel ; continue target
-     OpBranchConditional %cond %20 %60 ; bad branch to %20
-
-     %60 = OpLabel ; end of continue construct
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Invalid exit (50->20) from continue construct: 50 is not the "
-                 "last block in the continue construct starting at 50 "
-                 "(violates post-dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_ToWrongHeader) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpLoopMerge %89 %50 None
-     OpBranchConditional %cond %30 %89
-
-     %30 = OpLabel ; loop body
-     OpBranch %50
-
-     %50 = OpLabel ; continue target
-     OpBranch %10
-
-     %89 = OpLabel ; inner merge
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(), Eq("Invalid backedge (50->10): does not branch to "
-                             "the corresponding loop header, expected 20"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_SingleBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi20->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; continue target
-     OpBranch %20  ; good back edge
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi40 = fe.GetBlockInfo(40);
-  ASSERT_NE(bi40, nullptr);
-  EXPECT_EQ(bi40->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) {  // NOLINT
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; continue target
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %20  ; good back edge
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi50 = fe.GetBlockInfo(50);
-  ASSERT_NE(bi50, nullptr);
-  EXPECT_EQ(bi50->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) {  // NOLINT
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None ; continue target
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranchConditional %cond %20 %99 ; good back edge
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
-
-  auto* bi50 = fe.GetBlockInfo(50);
-  ASSERT_NE(bi50, nullptr);
-  EXPECT_EQ(bi50->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_PrematureExitFromContinueConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; continue construct
-     OpBranchConditional %cond2 %99 %50 ; invalid early exit
-
-     %50 = OpLabel
-     OpBranch %20  ; back edge
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Invalid exit (40->99) from continue construct: 40 is not the "
-                 "last block in the continue construct starting at 40 "
-                 "(violates post-dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel    ; single block loop
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-  EXPECT_EQ(bi->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel    ; single block loop
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-  EXPECT_EQ(bi->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; Single block continue construct
-     OpBranchConditional %cond2 %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromIfHeader) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kIfBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromIfThenElse) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  // Then clause
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
-
-  // Else clause
-  auto* bi50 = fe.GetBlockInfo(50);
-  ASSERT_NE(bi50, nullptr);
-  EXPECT_EQ(bi50->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi50->succ_edge[99], EdgeKind::kIfBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_BypassesMerge_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel ; merge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 20 to block 99 is an invalid exit from "
-         "construct starting at block 10; branch bypasses merge block 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_EscapeSwitchCase_IsError) {
-  // Code generation assumes that you can't have kCaseFallThrough and kIfBreak
-  // from the same OpBranchConditional.
-  // This checks one direction of that, where the IfBreak is shown it can't
-  // escape a switch case.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None ; Set up if-break to %99
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpSelectionMerge %80 None ; switch-selection
-     OpSwitch %selector %80 30 %30 40 %40
-
-     %30 = OpLabel ; first case
-        ; branch to %99 would be an if-break, but it bypasess the switch merge
-        ; Also has case fall-through
-     OpBranchConditional %cond2 %99 %40
-
-     %40 = OpLabel ; second case
-     OpBranch %80
-
-     %80 = OpLabel ; switch-selection's merge
-     OpBranch %99
-
-     %99 = OpLabel ; if-selection's merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from "
-         "construct starting at block 20; branch bypasses merge block 80"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %99 ; directly to merge
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %80 None
-     OpBranchConditional %cond %30 %80
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %80 = OpLabel ; inner merge
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %80 None
-     OpBranchConditional %cond %30 %80
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %99 %80 ; break-if
-
-     %80 = OpLabel ; inner merge
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_BypassesMerge_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %50 None
-     OpSwitch %selector %50 20 %20
-
-     %20 = OpLabel
-     OpBranch %99 ; invalid exit
-
-     %50 = OpLabel ; switch merge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 20 to block 99 is an invalid exit from "
-         "construct starting at block 10; branch bypasses merge block 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromNestedLoop_IsError) {
-  // It's an error because the break can only go as far as the loop.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpLoopMerge %80 %70 None
-     OpBranchConditional %cond %30 %80
-
-     %30 = OpLabel ; in loop construct
-     OpBranch %99 ; break
-
-     %70 = OpLabel
-     OpBranch %20
-
-     %80 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from "
-         "construct starting at block 20; branch bypasses merge block 80"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_SwitchBreak_FromNestedSwitch_IsError) {
-  // It's an error because the break can only go as far as inner switch
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %80 None
-     OpSwitch %selector %80 30 %30
-
-     %30 = OpLabel
-     OpBranch %99 ; break
-
-     %80 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from "
-         "construct starting at block 20; branch bypasses merge block 80"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromLoopBody) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %50 %99 ; break-unless
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromContinueConstructTail) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel ; continue target
-     OpBranch %60
-
-     %60 = OpLabel ; continue construct tail
-     OpBranchConditional %cond2 %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(60);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %99  ; unconditional break
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpBranch %99 ; deeply nested break
-
-     %50 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranch %20  ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpBranchConditional %cond3 %99 %50 ; break-if
-
-     %50 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranch %20  ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromContinueConstructNestedFlow_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %40 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; continue construct
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond2 %50 %79
-
-     %50 = OpLabel
-     OpBranchConditional %cond3 %99 %79 ; attempt to break to 99 should fail
-
-     %79 = OpLabel
-     OpBranch %80  ; inner merge
-
-     %80 = OpLabel
-     OpBranch %20  ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Invalid exit (50->99) from continue construct: 50 is not the "
-                 "last block in the continue construct starting at 40 "
-                 "(violates post-dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopBypassesMerge_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %50 %40 None
-     OpBranchConditional %cond %30 %50
-
-     %30 = OpLabel
-     OpBranch %99 ; bad exit
-
-     %40 = OpLabel ; continue construct
-     OpBranch %20
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from "
-         "construct starting at block 20; branch bypasses merge block 50"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopBreak_FromContinueBypassesMerge_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %50 %40 None
-     OpBranchConditional %cond %30 %50
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; continue construct
-     OpBranch %45
-
-     %45 = OpLabel
-     OpBranchConditional %cond2 %20 %99 ; branch to %99 is bad exit
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 45 to block 99 is an invalid exit from "
-         "construct starting at block 40; branch bypasses merge block 50"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopContinue_LoopBodyToContinue) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %80 ; a forward edge
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopContinue_FromNestedIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond2 %40 %79
-
-     %40 = OpLabel
-     OpBranch %80 ; continue
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpBranchConditional %cond2 %40 %79
-
-     %40 = OpLabel
-     OpBranchConditional %cond2 %80 %79 ; continue-if
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40
-
-     %40 = OpLabel
-     OpBranch %80
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseDirect_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %80 ; continue here
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_TRUE(fe.RegisterMerges());
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to case target block "
-                             "80 escapes the selection construct"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultDirect_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %80 40 %79 ; continue here
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_TRUE(fe.RegisterMerges());
-  EXPECT_TRUE(fe.LabelControlFlowConstructs());
-  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to default block 80 "
-                             "escapes the selection construct"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %40 79 %79
-
-     %40 = OpLabel
-     OpBranchConditional %cond2 %80 %79
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %40 79 %79
-
-     %40 = OpLabel
-     OpBranch %80
-
-     %79 = OpLabel ; inner merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(40);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError) {
-  // Inner loop header tries to do continue to outer loop continue target.
-  // This is disallowed by the rule:
-  //    "a continue block is valid only for the innermost loop it is nested
-  //    inside of"
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; inner loop.
-     OpStore %var %uint_1
-     OpLoopMerge %59 %50 None
-     OpBranchConditional %cond %59 %80  ; break and outer continue
-
-     %50 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %30 ; inner backedge
-
-     %59 = OpLabel ; inner merge
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; outer continue
-     OpStore %var %uint_4
-     OpBranch %20 ; outer backedge
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 80 is an invalid exit from construct "
-         "starting at block 30; branch bypasses merge block 59"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Fallthrough_CaseTailToCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 40 %40
-
-     %20 = OpLabel ; case 20
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %40 ; fallthrough
-
-     %40 = OpLabel ; case 40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(40), 1u);
-  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %40 20 %20
-
-     %20 = OpLabel ; case 20
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %40 ; fallthrough
-
-     %40 = OpLabel ; case 40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(40), 1u);
-  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Fallthrough_DefaultToCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %20 40 %40
-
-     %20 = OpLabel ; default
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %40 ; fallthrough
-
-     %40 = OpLabel ; case 40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(30);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(40), 1u);
-  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_IfBreak_IsError) {
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kIfBreak.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None ; Set up if-break to %99
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpSelectionMerge %80 None ; switch-selection
-     OpSwitch %selector %80 30 %30 40 %40
-
-     %30 = OpLabel ; first case
-        ; branch to %99 would be an if-break, but it bypasess the switch merge
-        ; Also has case fall-through
-     OpBranchConditional %cond2 %99 %40
-
-     %40 = OpLabel ; second case
-     OpBranch %80
-
-     %80 = OpLabel ; switch-selection's merge
-     OpBranch %99
-
-     %99 = OpLabel ; if-selection's merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from "
-         "construct starting at block 20; branch bypasses merge block 80"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError) {
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kForward.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None ; switch-selection
-     OpSwitch %selector %99 20 %20 30 %30
-
-     ; Try to make branch to 35 a kForward branch
-     %20 = OpLabel ; first case
-     OpBranchConditional %cond2 %25 %30
-
-     %25 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel ; second case
-     OpBranch %99
-
-     %99 = OpLabel ; if-selection's merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Control flow diverges at block 20 (to 25, 30) but it is not "
-                 "a structured header (it has no merge instruction)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnOutside_IsError) {  // NOLINT
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kBack.
-  //
-  // This test has the loop on the outside. The backedge coming from a case
-  // clause means the switch is inside the continue construct, and the nesting
-  // of the switch's merge means the backedge is coming from a block that is not
-  // at the end of the continue construct.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranch %30
-
-     %30 = OpLabel  ; continue target and
-     OpSelectionMerge %80 None ; switch-selection
-     OpSwitch %selector %80 40 %40 50 %50
-
-     ; try to make a back edge with a fallthrough
-     %40 = OpLabel ; first case
-     OpBranchConditional %cond2 %20 %50
-
-     %50 = OpLabel ; second case
-     OpBranch %80
-
-     %80 = OpLabel ; switch merge
-     OpBranch %20  ; also backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Invalid exit (40->20) from continue construct: 40 is not the "
-                 "last block in the continue construct starting at 30 "
-                 "(violates post-dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    FindSwitchCaseSelectionHeaders_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsMerge_IsError) {  // NOLINT
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kBack.
-  //
-  // This test has the loop on the inside. The merge block is also the
-  // fallthrough target.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel  ; continue target and
-     OpSelectionMerge %99 None ; switch-selection
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel ; first case, and loop header
-     OpLoopMerge %50 %40 None
-     OpBranch %40
-
-     ; try to make a back edge with a fallthrough
-     %40 = OpLabel
-     OpBranchConditional %cond2 %20 %50
-
-     %50 = OpLabel ; second case.  also the loop merge ; header must dominate its merge block !
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 99));
-  EXPECT_THAT(p->error(),
-              Eq("Block 50 is a case block for switch-selection header 10 and "
-                 "also the merge block for 20 (violates dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsNotMerge_IsError) {  // NOLINT
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kBack.
-  //
-  // This test has the loop on the inside. The merge block is not the merge
-  // target But the block order gets messed up because of the weird
-  // connectivity.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel  ; continue target and
-     OpSelectionMerge %99 None ; switch-selection
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel ; first case, and loop header
-     OpLoopMerge %45 %40 None  ; move the merge to an unreachable block
-     OpBranch %40
-
-     ; try to make a back edge with a fallthrough
-     %40 = OpLabel
-     OpBranchConditional %cond2 %20 %50
-
-     %45 = OpLabel ; merge for the loop
-     OpUnreachable
-
-     %50 = OpLabel ; second case. target of fallthrough
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
-                             "(dominance rule violated)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_NestedMerge_IsError) {  // NOLINT
-  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
-  // with kBack.
-  //
-  // This test has the loop on the inside. The fallthrough is an invalid exit
-  // from the loop. However, the block order gets all messed up because going
-  // from 40 to 50 ends up pulling in 99
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel  ; continue target and
-     OpSelectionMerge %99 None ; switch-selection
-     OpSwitch %selector %99 20 %20 50 %50
-
-       %20 = OpLabel ; first case, and loop header
-       OpLoopMerge %49 %40 None
-       OpBranch %40
-
-       ; try to make a back edge with a fallthrough
-       %40 = OpLabel
-       OpBranchConditional %cond2 %20 %50
-
-       %49 = OpLabel ; loop merge
-       OpBranch %99
-
-     %50 = OpLabel ; second case
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 49, 99));
-  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
-                             "(dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_TrueBranch) {
-  // This is an unusual one, and is an error. Structurally it looks like this:
-  //   switch (val) {
-  //   case 0: {
-  //        if (cond) {
-  //          fallthrough;
-  //        }
-  //        something = 1;
-  //      }
-  //   case 1: { }
-  //   }
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %30 %49
-
-     %30 = OpLabel
-     OpBranch %50 ; attempt to fallthrough
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_FalseBranch) {
-  // Like previous test, but taking the false branch.
-
-  // This is an unusual one, and is an error. Structurally it looks like this:
-  //   switch (val) {
-  //   case 0: {
-  //        if (cond) {
-  //          fallthrough;
-  //        }
-  //        something = 1;
-  //      }
-  //   case 1: { }
-  //   }
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond %49 %30 ;; this is the difference
-
-     %30 = OpLabel
-     OpBranch %50 ; attempt to fallthrough
-
-     %49 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_IfToThen) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_IfToElse) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(30), 1u);
-  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_SwitchToCase) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(30), 1u);
-  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_LoopHeadToBody) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(30), 1u);
-  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_DomViolation_BeforeIfToSelectionInterior) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
-
-     %20 = OpLabel
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond %50 %89
-
-     %50 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_DomViolation_BeforeSwitchToSelectionInterior) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
-
-     %20 = OpLabel
-     OpSelectionMerge %89 None
-     OpSwitch %selector %89 50 %50
-
-     %50 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_DomViolation_BeforeLoopToLoopBodyInterior) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
-
-     %20 = OpLabel
-     OpLoopMerge %89 %80 None
-     OpBranchConditional %cond %50 %89
-
-     %50 = OpLabel
-     OpBranch %89
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              // Weird error, but still we caught it.
-              // Preferred: Eq("Branch from 10 to 50 bypasses header 20
-              // (dominance rule violated)"))
-              Eq("Branch from 10 to 50 bypasses continue target 80 (dominance "
-                 "rule violated)"))
-      << Dump(fe.block_order());
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_DomViolation_BeforeContinueToContinueInterior) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %60
-
-     %50 = OpLabel ; continue target
-     OpBranch %60
-
-     %60 = OpLabel
-     OpBranch %20
-
-     %89 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 60 is an invalid exit from "
-         "construct starting at block 20; branch bypasses continue target 50"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_DomViolation_AfterContinueToContinueInterior) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %80 %50 None
-     OpBranchConditional %cond %30 %80
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel
-     OpBranch %60
-
-     %60 = OpLabel
-     OpBranch %20
-
-     %80 = OpLabel
-     OpBranch %60 ; bad branch
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 50 to block 60 is an invalid exit from "
-         "construct starting at block 50; branch bypasses merge block 80"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {  // NOLINT
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 50 %50
-
-     %20 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %30 %50
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel ; case and merge block. Error
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Block 50 is a case block for switch-selection header 10 and "
-                 "also the merge block for 20 (violates dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_DomViolation_SwitchDefault_CantBeMergeForOtherConstruct) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %50 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %30 %50
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel ; default-case and merge block. Error
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Block 50 is the default block for switch-selection header 10 "
-                 "and also the merge block for 20 (violates dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_TooManyBackedges) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranchConditional %cond2 %20 %50
-
-     %50 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_NeededMerge_BranchConditional) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %20 = OpLabel
-     OpBranchConditional %cond %30 %40
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Control flow diverges at block 20 (to 30, 40) but it is not "
-                 "a structured header (it has no merge instruction)"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_NeededMerge_Switch) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Control flow diverges at block 10 (to 99, 20) but it is not "
-                 "a structured header (it has no merge instruction)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody) {
-  // In this case the branch-conditional in the loop header is really also a
-  // selection header.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %50 ; what to make of this?
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %50 = OpLabel
-     OpBranch %99
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(20);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(30), 1u);
-  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
-  EXPECT_EQ(bi->succ_edge.count(50), 1u);
-  EXPECT_EQ(bi->succ_edge[50], EdgeKind::kForward);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Pathological_Forward_Premerge) {
-  // Two arms of an if-selection converge early, before the merge block
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %50
-
-     %30 = OpLabel
-     OpBranch %50
-
-     %50 = OpLabel ; this is an early merge!
-     OpBranch %60
-
-     %60 = OpLabel ; still early merge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(50), 1u);
-  EXPECT_EQ(bi20->succ_edge[50], EdgeKind::kForward);
-
-  auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->succ_edge.count(50), 1u);
-  EXPECT_EQ(bi30->succ_edge[50], EdgeKind::kForward);
-
-  auto* bi50 = fe.GetBlockInfo(50);
-  ASSERT_NE(bi50, nullptr);
-  EXPECT_EQ(bi50->succ_edge.count(60), 1u);
-  EXPECT_EQ(bi50->succ_edge[60], EdgeKind::kForward);
-
-  auto* bi60 = fe.GetBlockInfo(60);
-  ASSERT_NE(bi60, nullptr);
-  EXPECT_EQ(bi60->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi60->succ_edge[99], EdgeKind::kIfBreak);
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Pathological_Forward_Regardless) {
-  // Both arms of an OpBranchConditional go to the same target.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %20 ; same target!
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->succ_edge.count(20), 1u);
-  EXPECT_EQ(bi10->succ_edge[20], EdgeKind::kForward);
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_NoIf) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-
-  auto* bi = fe.GetBlockInfo(10);
-  ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->true_head, 0u);
-  EXPECT_EQ(bi->false_head, 0u);
-  EXPECT_EQ(bi->premerge_head, 0u);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_ThenElse) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 20u);
-  EXPECT_EQ(bi10->false_head, 30u);
-  EXPECT_EQ(bi10->premerge_head, 0u);
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->true_head, 0u);
-  EXPECT_EQ(bi20->false_head, 0u);
-  EXPECT_EQ(bi20->premerge_head, 0u);
-
-  auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->true_head, 0u);
-  EXPECT_EQ(bi30->false_head, 0u);
-  EXPECT_EQ(bi30->premerge_head, 0u);
-
-  auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->true_head, 0u);
-  EXPECT_EQ(bi99->false_head, 0u);
-  EXPECT_EQ(bi99->premerge_head, 0u);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_IfOnly) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 30u);
-  EXPECT_EQ(bi10->false_head, 0u);
-  EXPECT_EQ(bi10->premerge_head, 0u);
-
-  auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->true_head, 0u);
-  EXPECT_EQ(bi30->false_head, 0u);
-  EXPECT_EQ(bi30->premerge_head, 0u);
-
-  auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->true_head, 0u);
-  EXPECT_EQ(bi99->false_head, 0u);
-  EXPECT_EQ(bi99->premerge_head, 0u);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_ElseOnly) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %30
-
-     %30 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 0u);
-  EXPECT_EQ(bi10->false_head, 30u);
-  EXPECT_EQ(bi10->premerge_head, 0u);
-
-  auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->true_head, 0u);
-  EXPECT_EQ(bi30->false_head, 0u);
-  EXPECT_EQ(bi30->premerge_head, 0u);
-
-  auto* bi99 = fe.GetBlockInfo(99);
-  ASSERT_NE(bi99, nullptr);
-  EXPECT_EQ(bi99->true_head, 0u);
-  EXPECT_EQ(bi99->false_head, 0u);
-  EXPECT_EQ(bi99->premerge_head, 0u);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_Regardless) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %20 ; same target
-
-     %20 = OpLabel
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 20u);
-  EXPECT_EQ(bi10->false_head, 20u);
-  EXPECT_EQ(bi10->premerge_head, 0u);
-}
-
-TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_Premerge_Simple) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %80
-
-     %30 = OpLabel
-     OpBranch %80
-
-     %80 = OpLabel ; premerge node
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 20u);
-  EXPECT_EQ(bi10->false_head, 30u);
-  EXPECT_EQ(bi10->premerge_head, 80u);
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranch %80
-
-     %80 = OpLabel ; premerge node
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 20u);
-  EXPECT_EQ(bi10->false_head, 30u);
-  EXPECT_EQ(bi10->premerge_head, 30u);
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %80 ; branches to premerge
-
-     %30 = OpLabel ; else
-     OpBranch %20  ; branches to then
-
-     %80 = OpLabel ; premerge node
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
-
-  auto* bi10 = fe.GetBlockInfo(10);
-  ASSERT_NE(bi10, nullptr);
-  EXPECT_EQ(bi10->true_head, 20u);
-  EXPECT_EQ(bi10->false_head, 30u);
-  EXPECT_EQ(bi10->premerge_head, 20u);
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_Premerge_MultiCandidate_IsError) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     ; Try to force several branches down into "else" territory,
-     ; but we error out earlier in the flow due to lack of merge
-     ; instruction.
-     OpBranchConditional %cond2  %70 %80
-
-     %30 = OpLabel
-     OpBranch %70
-
-     %70 = OpLabel ; candidate premerge
-     OpBranch %80
-
-     %80 = OpLabel ; canddiate premerge
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  // Error out sooner in the flow
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Control flow diverges at block 20 (to 70, 80) but it is not "
-                 "a structured header (it has no merge instruction)"));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
-  // SPIR-V allows this unusual configuration.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpBranchConditional %cond2 %99 %80 ; break with forward edge
-
-     %80 = OpLabel ; still in then clause
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
-
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
-  // SPIR-V allows this unusual configuration.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel
-     OpBranch %99
-
-     %30 = OpLabel ; else clause
-     OpBranchConditional %cond2 %99 %80 ; break with forward edge
-
-     %80 = OpLabel ; still in then clause
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
-
-  auto* bi30 = fe.GetBlockInfo(30);
-  ASSERT_NE(bi30, nullptr);
-  EXPECT_EQ(bi30->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi30->succ_edge[80], EdgeKind::kForward);
-  EXPECT_EQ(bi30->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
-
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
-
-     %20 = OpLabel ; then
-     OpBranchConditional %cond2 %99 %80 ; break with forward to premerge
-
-     %30 = OpLabel ; else
-     OpBranch %80
-
-     %80 = OpLabel ; premerge node
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
-
-  auto* bi20 = fe.GetBlockInfo(20);
-  ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(80), 1u);
-  EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
-  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
-
-  EXPECT_THAT(p->error(), Eq(""));
-
-  // TODO(crbug.com/tint/775): The SPIR-V reader errors out on this case.
-  // Remove this when it's fixed.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {  // NOLINT - line length
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %40 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond2 %30 %40
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner merge, and true-head for outer if-selection
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 40 is the true branch for if-selection header 10 and also the "
-         "merge block for header block 20 (violates dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {  // NOLINT - line length
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %40
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %40
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; inner merge, and true-head for outer if-selection
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Block 40 is the false branch for if-selection header 10 and also the "
-         "merge block for header block 20 (violates dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBePremerge) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel ; outer if-header
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpBranch %70
-
-     %50 = OpLabel ; inner if-header
-     OpSelectionMerge %70 None
-     OpBranchConditional %cond %60 %70
-
-     %60 = OpLabel
-     OpBranch %70
-
-     %70 = OpLabel ; inner merge, and premerge for outer if-selection
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranch %99
-
-     %99 = OpLabel ; outer merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(),
-              Eq("Block 70 is the merge block for 50 but has alternate paths "
-                 "reaching it, starting from blocks 20 and 50 which are the "
-                 "true and false branches for the if-selection header block 10 "
-                 "(violates dominance rule)"));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %99 %30 ; true branch breaking is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %90 %30 ; true branch continue is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %uint_20 %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %99 %30 ; true branch switch break is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; if-selection merge
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %99 ; false branch breaking is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %90 ; false branch continue is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %uint_20 %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %99 ; false branch switch break is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; if-selection merge
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
-  EXPECT_THAT(p->error(), Eq(""));
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
-  // Exercises the hard case where we a single OpBranchConditional has both
-  // IfBreak and Forward edges, within the true-branch clause.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpBranchConditional %cond2 %99 %30 ; kIfBreak with kForward
-
-     %30 = OpLabel ; still in then clause
-     OpStore %var %uint_3
-     OpBranch %99
-
-     %50 = OpLabel ; else clause
-     OpStore %var %uint_4
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-var guard10 : bool = true;
-if (false) {
-  var_1 = 2u;
-  if (true) {
-    guard10 = false;
-  }
-  if (guard10) {
-    var_1 = 3u;
-    guard10 = false;
-  }
-} else {
-  if (guard10) {
-    var_1 = 4u;
-    guard10 = false;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_IfBreak_FromElse_ForwardWithinElse) {
-  // Exercises the hard case where we a single OpBranchConditional has both
-  // IfBreak and Forward edges, within the false-branch clause.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %99
-
-     %50 = OpLabel ; else clause
-     OpStore %var %uint_3
-     OpBranchConditional %cond2 %99 %80 ; kIfBreak with kForward
-
-     %80 = OpLabel ; still in then clause
-     OpStore %var %uint_4
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-var guard10 : bool = true;
-if (false) {
-  var_1 = 2u;
-  guard10 = false;
-} else {
-  if (guard10) {
-    var_1 = 3u;
-    if (true) {
-      guard10 = false;
-    }
-    if (guard10) {
-      var_1 = 4u;
-      guard10 = false;
-    }
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
-  // This is a combination of the previous two, but also adding a premerge.
-  // We have IfBreak and Forward edges from the same OpBranchConditional, and
-  // this occurs in the true-branch clause, the false-branch clause, and within
-  // the premerge clause.  Flow guards have to be sprinkled in lots of places.
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %50
-
-     %20 = OpLabel ; then
-     OpStore %var %uint_2
-     OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
-
-     %21 = OpLabel ; still in then clause
-     OpStore %var %uint_3
-     OpBranch %80 ; to premerge
-
-     %50 = OpLabel ; else clause
-     OpStore %var %uint_4
-     OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
-
-     %51 = OpLabel ; still in else clause
-     OpStore %var %uint_5
-     OpBranch %80 ; to premerge
-
-     %80 = OpLabel ; premerge
-     OpStore %var %uint_6
-     OpBranchConditional %cond3 %81 %99
-
-     %81 = OpLabel ; premerge
-     OpStore %var %uint_7
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_8
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-var guard10 : bool = true;
-if (false) {
-  var_1 = 2u;
-  if (true) {
-  } else {
-    guard10 = false;
-  }
-  if (guard10) {
-    var_1 = 3u;
-  }
-} else {
-  if (guard10) {
-    var_1 = 4u;
-    if (true) {
-      guard10 = false;
-    }
-    if (guard10) {
-      var_1 = 5u;
-    }
-  }
-}
-if (guard10) {
-  var_1 = 6u;
-  if (false) {
-  } else {
-    guard10 = false;
-  }
-  if (guard10) {
-    var_1 = 7u;
-    guard10 = false;
-  }
-}
-var_1 = 8u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, BlockIsContinueForMoreThanOneHeader) {
-  // This is disallowed by the rule:
-  //    "a continue block is valid only for the innermost loop it is nested
-  //    inside of"
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel ; outer loop
-     OpLoopMerge %99 %50 None
-     OpBranchConditional %cond %50 %99
-
-     %50 = OpLabel ; continue target, but also single-block loop
-     OpLoopMerge %80 %50 None
-     OpBranchConditional %cond2 %50 %80
-
-     %80 = OpLabel
-     OpBranch %20 ; backedge for outer loop
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  fe.RegisterBasicBlocks();
-  fe.ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
-  EXPECT_FALSE(fe.RegisterMerges());
-  EXPECT_THAT(p->error(), Eq("Block 50 declared as continue target for more "
-                             "than one header: 20, 50"));
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Empty) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %99
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Then_NoElse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-  var_1 = 1u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_NoThen_Else) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %30
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-} else {
-  var_1 = 1u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Then_Else) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %40
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99
-
-     %40 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %99
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-  var_1 = 1u;
-} else {
-  var_1 = 2u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Then_Else_Premerge) {
-  // TODO(dneto): This should get an extra if(true) around
-  // the premerge code.
-  // See https://bugs.chromium.org/p/tint/issues/detail?id=82
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %40
-
-     %80 = OpLabel ; premerge
-     OpStore %var %uint_3
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80
-
-     %40 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %80
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-  var_1 = 1u;
-} else {
-  var_1 = 2u;
-}
-if (true) {
-  var_1 = 3u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Then_Premerge) {
-  // The premerge *is* the else.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %80
-
-     %80 = OpLabel ; premerge
-     OpStore %var %uint_3
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-  var_1 = 1u;
-}
-if (true) {
-  var_1 = 3u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Else_Premerge) {
-  // The premerge *is* the then-clause.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %80 %30
-
-     %80 = OpLabel ; premerge
-     OpStore %var %uint_3
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-} else {
-  var_1 = 1u;
-}
-if (true) {
-  var_1 = 3u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_If_Nest_If) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %40
-
-     %30 = OpLabel ;; inner if #1
-     OpStore %var %uint_1
-     OpSelectionMerge %39 None
-     OpBranchConditional %cond2 %33 %39
-
-     %33 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %39
-
-     %39 = OpLabel ;; inner merge
-     OpStore %var %uint_3
-     OpBranch %99
-
-     %40 = OpLabel ;; inner if #2
-     OpStore %var %uint_4
-     OpSelectionMerge %49 None
-     OpBranchConditional %cond2 %49 %43
-
-     %43 = OpLabel
-     OpStore %var %uint_5
-     OpBranch %49
-
-     %49 = OpLabel ;; 2nd inner merge
-     OpStore %var %uint_6
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-  var_1 = 1u;
-  if (true) {
-    var_1 = 2u;
-  }
-  var_1 = 3u;
-} else {
-  var_1 = 4u;
-  if (true) {
-  } else {
-    var_1 = 5u;
-  }
-  var_1 = 6u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_FalseBackedge) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (false) {
-    break;
-  }
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_BothBackedge) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_SingleBlockContinue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %50 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %50
-
-     %50 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-  }
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_MultiBlockContinue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %50 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %50
-
-     %50 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %60
-
-     %60 = OpLabel
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-    var_1 = 4u;
-  }
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_ContinueNestIf) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %50 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %50
-
-     %50 = OpLabel ; continue target; also if-header
-     OpStore %var %uint_3
-     OpSelectionMerge %80 None
-     OpBranchConditional %cond2 %60 %80
-
-     %60 = OpLabel
-     OpStore %var %uint_4
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %var %uint_5
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %999
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-    if (true) {
-      var_1 = 4u;
-    }
-    var_1 = 5u;
-  }
-}
-var_1 = 999u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_MultiBlockContinueIsEntireLoop) {
-  // Test case where both branches exit. e.g both go to merge.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel ; its own continue target
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel
-     OpStore %var %uint_3
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (false) {
-    break;
-  }
-}
-var_1 = 3u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_Never) {
-  // Test case where both branches exit. e.g both go to merge.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %99 %99
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_2
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_3
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  break;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-var_1 = 3u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_HeaderBreakAndContinue) {
-  // Header block branches to merge, and to an outer continue.
-  // This is disallowed by the rule:
-  //    "a continue block is valid only for the innermost loop it is nested
-  //    inside of"
-  // See test ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_TrueToBody_FalseBreaks) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_3
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_4
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-  }
-}
-var_1 = 4u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_FalseToBody_TrueBreaks) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_3
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_4
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-  }
-}
-var_1 = 4u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_NestedIfContinue) {
-  // By construction, it has to come from nested code.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80 ; continue edge
-
-     %50 = OpLabel ; inner selection merge
-     OpStore %var %uint_2
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_3
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-    var_1 = 1u;
-    continue;
-  }
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyAlwaysBreaks) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99 ; break is here
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  break;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue) {
-  // The else-branch has a continue but it's skipped because it's from a
-  // block that immediately precedes the continue construct.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranchConditional %cond %99 %80
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-    break;
-  }
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse) {
-  // The else-branch has a continue but it's skipped because it's from a
-  // block that immediately precedes the continue construct.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranchConditional %cond %80 %99
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranchConditional %cond %99 %70
-
-     %70 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-    break;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranchConditional %cond %70 %99
-
-     %70 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_NoCases) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-// First do no special control flow: no fallthroughs, breaks, continues.
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_OneCase) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_TwoCases) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 30u: {
-    var_1 = 30u;
-  }
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_CasesWithDup) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30 40 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 30u: {
-    var_1 = 30u;
-  }
-  case 20u, 40u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsCase_NoDupCases) {
-  // The default block is not the merge block. But not the same as a case
-  // either.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20 40 %40
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel ; the named default block
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 40u: {
-    var_1 = 40u;
-  }
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-    var_1 = 30u;
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsCase_WithDupCase) {
-  // The default block is not the merge block and is the same as a case.
-  // We emit the default case separately, but just before the labeled
-  // case, and with a fallthrough.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %30 20 %20 30 %30 40 %40
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel ; the named default block, also a case
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 40u: {
-    var_1 = 40u;
-  }
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-    fallthrough;
-  }
-  case 30u: {
-    var_1 = 30u;
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_Case_SintValue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     ; SPIR-V assembler doesn't support negative literals in switch
-     OpSwitch %signed_selector %99 20 %20 2000000000 %30 !4000000000 %40
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42) {
-  case -294967296: {
-    var_1 = 40u;
-  }
-  case 2000000000: {
-    var_1 = 30u;
-  }
-  case 20: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Switch_Case_UintValue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 2000000000 %30 50 %40
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 50u: {
-    var_1 = 40u;
-  }
-  case 2000000000u: {
-    var_1 = 30u;
-  }
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Return_TopLevel) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Return_InsideIf) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpReturn
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-  return;
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Return_InsideLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %30
-
-     %30 = OpLabel
-     OpReturn
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  return;
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_TopLevel) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %200 = OpFunction %uint None %uintfn
-
-     %210 = OpLabel
-     OpReturnValue %uint_2
-
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %11 = OpFunctionCall %uint %200
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(return 2u;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_InsideIf) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %200 = OpFunction %uint None %uintfn
-
-     %210 = OpLabel
-     OpSelectionMerge %299 None
-     OpBranchConditional %cond %220 %299
-
-     %220 = OpLabel
-     OpReturnValue %uint_2
-
-     %299 = OpLabel
-     OpReturnValue %uint_3
-
-     OpFunctionEnd
-
-
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %11 = OpFunctionCall %uint %200
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-  return 2u;
-}
-return 3u;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_Loop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %200 = OpFunction %uint None %uintfn
-
-     %210 = OpLabel
-     OpBranch %220
-
-     %220 = OpLabel
-     OpLoopMerge %299 %280 None
-     OpBranchConditional %cond %230 %230
-
-     %230 = OpLabel
-     OpReturnValue %uint_2
-
-     %280 = OpLabel
-     OpBranch %220
-
-     %299 = OpLabel
-     OpReturnValue %uint_3
-
-     OpFunctionEnd
-
-
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %11 = OpFunctionCall %uint %200
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  return 2u;
-}
-return 3u;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Kill_TopLevel) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpKill
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(discard;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Kill_InsideIf) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpKill
-
-     %99 = OpLabel
-     OpKill
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-  discard;
-}
-discard;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Kill_InsideLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %30
-
-     %30 = OpLabel
-     OpKill
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpKill
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  discard;
-}
-discard;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Unreachable_TopLevel) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpUnreachable
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InsideIf) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpUnreachable
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-  return;
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InsideLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %30 %30
-
-     %30 = OpLabel
-     OpUnreachable
-
-     %80 = OpLabel
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  return;
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InNonVoidFunction) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %200 = OpFunction %uint None %uintfn
-
-     %210 = OpLabel
-     OpUnreachable
-
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %11 = OpFunctionCall %uint %200
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(return 0u;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_BackEdge_MultiBlockLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %20 ; here is one
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-
-  continuing {
-    var_1 = 1u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_BackEdge_SingleBlockLoop) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranch %20 ; backedge in single block loop
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_SwitchBreak_LastInCase) {
-  // When the break is last in its case, we omit it because it's implicit in
-  // WGSL.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %99 ; branch to merge. Last in case
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_SwitchBreak_NotLastInCase) {
-  // When the break is not last in its case, we must emit a 'break'
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranch %99 ; branch to merge. Not last in case
-
-     %50 = OpLabel ; inner merge
-     OpStore %var %uint_50
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-      var_1 = 40u;
-      break;
-    }
-    var_1 = 50u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99 ; break is here
-
-     %80 = OpLabel
-     OpStore %var %uint_2
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-  break;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructConditional) {
-  // This case is invalid because the backedge block doesn't post-dominate the
-  // continue target.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranch %30
-
-     %30 = OpLabel ; continue target; also an if-header
-     OpSelectionMerge %80 None
-     OpBranchConditional %cond %40 %80
-
-     %40 = OpLabel
-     OpBranch %99 ; break, inside a nested if.
-
-     %80 = OpLabel
-     OpBranch %20 ; backedge
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(),
-              Eq("Invalid exit (40->99) from continue construct: 40 is not the "
-                 "last block in the continue construct starting at 30 "
-                 "(violates post-dominance rule)"));
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) {  // NOLINT - line length
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_1
-     OpBranch %99  ; should be a backedge
-     ; This is invalid as there must be a backedge to the loop header.
-     ; The SPIR-V allows this and understands how to emit it, even if it's not
-     ; permitted by the SPIR-V validator.
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-
-  p->DeliberatelyInvalidSpirv();
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-
-  continuing {
-    var_1 = 1u;
-    break;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) {  // NOLINT - line length
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_1
-     OpBranchConditional %cond %20 %99  ; backedge, and exit
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-
-  continuing {
-    var_1 = 1u;
-    if (false) {
-    } else {
-      break;
-    }
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_LastInLoopConstruct) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80 ; continue edge from last block before continue target
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_2
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var_1 = 1u;
-
-  continuing {
-    var_1 = 2u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_BeforeLast) {
-  // By construction, it has to come from nested code.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %80 ; continue edge
-
-     %50 = OpLabel ; inner selection merge
-     OpStore %var %uint_2
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_3
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-    var_1 = 1u;
-    continue;
-  }
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 3u;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_FromSwitch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_3
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_4
-     OpBranch %80 ; continue edge
-
-     %79 = OpLabel ; switch merge
-     OpStore %var %uint_5
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_6
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-loop {
-  var_1 = 2u;
-  var_1 = 3u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 4u;
-      continue;
-    }
-    default: {
-    }
-  }
-  var_1 = 5u;
-
-  continuing {
-    var_1 = 6u;
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_IfBreak_FromThen) {
-  // When unconditional, the if-break must be last in the then clause.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-  var_1 = 1u;
-}
-var_1 = 2u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_IfBreak_FromElse) {
-  // When unconditional, the if-break must be last in the else clause.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %30
-
-     %30 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (false) {
-} else {
-  var_1 = 1u;
-}
-var_1 = 2u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_Fallthrough) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranch %30 ; uncondtional fallthrough
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    fallthrough;
-  }
-  case 30u: {
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_Branch_Forward) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %99 ; forward
-
-     %99 = OpLabel
-     OpStore %var %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-var_1 = 2u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-// Test matrix for normal OpBranchConditional:
-//
-//    kBack with:
-//      kBack : TESTED dup general case
-//      kSwitchBreak: invalid (invalid escape, or invalid backedge)
-//      kLoopBreak: TESTED in single- and multi block loop configurations
-//      kLoopContinue: invalid
-//                      If single block loop, then the continue is backward
-//                      If continue is forward, then it's a continue from a
-//                      continue which is also invalid.
-//      kIfBreak: invalid: loop and if must have distinct merge blocks
-//      kCaseFallThrough: invalid: loop header must dominate its merge
-//      kForward: impossible; would be a loop break
-//
-//    kSwitchBreak with:
-//      kBack : symmetry
-//      kSwitchBreak: dup general case
-//      kLoopBreak: invalid; only one kind of break allowed
-//      kLoopContinue: TESTED
-//      kIfBreak: invalid: switch and if must have distinct merge blocks
-//      kCaseFallThrough: TESTED
-//      kForward: TESTED
-//
-//    kLoopBreak with:
-//      kBack : symmetry
-//      kSwitchBreak: symmetry
-//      kLoopBreak: dup general case
-//      kLoopContinue: TESTED
-//      kIfBreak: invalid: switch and if must have distinct merge blocks
-//      kCaseFallThrough: not possible, because switch break conflicts with loop
-//      break kForward: TESTED
-//
-//    kLoopContinue with:
-//      kBack : symmetry
-//      kSwitchBreak: symmetry
-//      kLoopBreak: symmetry
-//      kLoopContinue: dup general case
-//      kIfBreak: TESTED
-//      kCaseFallThrough: TESTED
-//      kForward: TESTED
-//
-//    kIfBreak with:
-//      kBack : symmetry
-//      kSwitchBreak: symmetry
-//      kLoopBreak: symmetry
-//      kLoopContinue: symmetry
-//      kIfBreak: dup general case
-//      kCaseFallThrough: invalid; violates nesting or unique merges
-//      kForward: invalid: needs a merge instruction
-//
-//    kCaseFallThrough with:
-//      kBack : symmetry
-//      kSwitchBreak: symmetry
-//      kLoopBreak: symmetry
-//      kLoopContinue: symmetry
-//      kIfBreak: symmetry
-//      kCaseFallThrough: dup general case
-//      kForward: invalid (tested)
-//
-//    kForward with:
-//      kBack : symmetry
-//      kSwitchBreak: symmetry
-//      kLoopBreak: symmetry
-//      kLoopContinue: symmetry
-//      kIfBreak: symmetry
-//      kCaseFallThrough: symmetry
-//      kForward: dup general case
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Back_SingleBlock_Back) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %20
-
-     %99 = OpLabel ; dead
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (false) {
-    break;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (false) {
-  } else {
-    break;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranchConditional %cond %99 %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-
-  continuing {
-    if (false) {
-      break;
-    }
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-
-  continuing {
-    if (false) {
-    } else {
-      break;
-    }
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase) {
-  // When the break is last in its case, we omit it because it's implicit in
-  // WGSL.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond2 %99 %99 ; branch to merge. Last in case
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase) {
-  // When the break is not last in its case, we must emit a 'break'
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranchConditional %cond2 %99 %99 ; branch to merge. Not last in case
-
-     %50 = OpLabel ; inner merge
-     OpStore %var %uint_50
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-      var_1 = 40u;
-      break;
-    }
-    var_1 = 50u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_3
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranchConditional %cond %80 %79 ; break; continue on true
-
-     %79 = OpLabel
-     OpStore %var %uint_6
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_7
-     OpBranch %20
-
-     %99 = OpLabel ; loop merge
-     OpStore %var %uint_8
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-loop {
-  var_1 = 2u;
-  var_1 = 3u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 40u;
-      if (false) {
-        continue;
-      }
-    }
-    default: {
-    }
-  }
-  var_1 = 6u;
-
-  continuing {
-    var_1 = 7u;
-  }
-}
-var_1 = 8u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_3
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranchConditional %cond %79 %80 ; break; continue on false
-
-     %79 = OpLabel
-     OpStore %var %uint_6
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_7
-     OpBranch %20
-
-     %99 = OpLabel ; loop merge
-     OpStore %var %uint_8
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-loop {
-  var_1 = 2u;
-  var_1 = 3u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 40u;
-      if (false) {
-      } else {
-        continue;
-      }
-    }
-    default: {
-    }
-  }
-  var_1 = 6u;
-
-  continuing {
-    var_1 = 7u;
-  }
-}
-var_1 = 8u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond %30 %99 ; break; forward on true
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpStore %var %uint_8
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-    } else {
-      break;
-    }
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 8u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond %99 %30 ; break; forward on false
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpStore %var %uint_8
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-      break;
-    }
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 8u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond %30 %99; fallthrough on true
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-    } else {
-      break;
-    }
-    fallthrough;
-  }
-  case 30u: {
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond %99 %30; fallthrough on false
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    if (false) {
-      break;
-    }
-    fallthrough;
-  }
-  case 30u: {
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %99 %99
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  break;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranchConditional %cond %99 %99
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  break;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Continue_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %25
-
-     ; Need this extra selection to make another block between
-     ; %30 and the continue target, so we actually induce a Continue
-     ; statement to exist.
-     %25 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond2 %30 %40
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; break; continue on true
-     OpBranchConditional %cond %80 %99
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (true) {
-    var_1 = 2u;
-    if (false) {
-      continue;
-    } else {
-      break;
-    }
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_LoopBreak_Continue_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %25
-
-     ; Need this extra selection to make another block between
-     ; %30 and the continue target, so we actually induce a Continue
-     ; statement to exist.
-     %25 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond2 %30 %40
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; break; continue on false
-     OpBranchConditional %cond %99 %80
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  if (true) {
-    var_1 = 2u;
-    if (false) {
-      break;
-    } else {
-      continue;
-    }
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_LoopBreak_Fallthrough_IsError) {
-  // It's an error because switch break conflicts with loop break.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40 50 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     ; error: branch to 99 bypasses switch's merge
-     OpBranchConditional %cond %99 %50 ; loop break; fall through
-
-     %50 = OpLabel
-     OpStore %var %uint_50
-     OpBranch %79
-
-     %79 = OpLabel ; switch merge
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 40 to block 99 is an invalid exit from construct "
-         "starting at block 30; branch bypasses merge block 79"));
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; break; forward on true
-     OpBranchConditional %cond %40 %99
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (false) {
-  } else {
-    break;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Forward_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; break; forward on false
-     OpBranchConditional %cond %99 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (false) {
-    break;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Continue_Continue_FromHeader) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranchConditional %cond %80 %80 ; to continue
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranchConditional %cond %80 %80 ; to continue
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional) {
-  // Create an intervening block so we actually require a "continue" statement
-  // instead of just an adjacent fallthrough to the continue target.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranchConditional %cond3 %80 %80 ; to continue
-
-     %50 = OpLabel ; merge for selection
-     OpStore %var %uint_4
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_5
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_6
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (true) {
-    var_1 = 3u;
-    continue;
-  }
-  var_1 = 4u;
-
-  continuing {
-    var_1 = 5u;
-  }
-}
-var_1 = 6u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) {  // NOLINT
-  // Like the previous tests, but with an empty continuing clause.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranchConditional %cond3 %80 %80 ; to continue
-
-     %50 = OpLabel ; merge for selection
-     OpStore %var %uint_4
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     ; no statements here.
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_6
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (true) {
-    var_1 = 3u;
-    continue;
-  }
-  var_1 = 4u;
-}
-var_1 = 6u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopContinue_FromSwitch) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_2
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_3
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_4
-     OpBranchConditional %cond2 %80 %80; dup continue edge
-
-     %79 = OpLabel ; switch merge
-     OpStore %var %uint_5
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_6
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-loop {
-  var_1 = 2u;
-  var_1 = 3u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 4u;
-      continue;
-    }
-    default: {
-    }
-  }
-  var_1 = 5u;
-
-  continuing {
-    var_1 = 6u;
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_3
- ; true to if's merge;  false to continue
-     OpBranchConditional %cond3 %50 %80
-
-     %50 = OpLabel ; merge for selection
-     OpStore %var %uint_4
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_5
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_6
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (true) {
-    var_1 = 3u;
-    if (false) {
-    } else {
-      continue;
-    }
-  }
-  var_1 = 4u;
-
-  continuing {
-    var_1 = 5u;
-  }
-}
-var_1 = 6u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_IfBreak_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %50 None
-     OpBranchConditional %cond2 %40 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_3
- ; false to if's merge;  true to continue
-     OpBranchConditional %cond3 %80 %50
-
-     %50 = OpLabel ; merge for selection
-     OpStore %var %uint_4
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_5
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_6
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (true) {
-    var_1 = 3u;
-    if (false) {
-      continue;
-    }
-  }
-  var_1 = 4u;
-
-  continuing {
-    var_1 = 5u;
-  }
-}
-var_1 = 6u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40 50 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranchConditional %cond %50 %80 ; loop continue; fall through on true
-
-     %50 = OpLabel
-     OpStore %var %uint_50
-     OpBranch %79
-
-     %79 = OpLabel ; switch merge
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 40u;
-      if (false) {
-      } else {
-        continue;
-      }
-      fallthrough;
-    }
-    case 50u: {
-      var_1 = 50u;
-    }
-    default: {
-    }
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpSelectionMerge %79 None
-     OpSwitch %selector %79 40 %40 50 %50
-
-     %40 = OpLabel
-     OpStore %var %uint_40
-     OpBranchConditional %cond %80 %50 ; loop continue; fall through on false
-
-     %50 = OpLabel
-     OpStore %var %uint_50
-     OpBranch %79
-
-     %79 = OpLabel ; switch merge
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  switch(42u) {
-    case 40u: {
-      var_1 = 40u;
-      if (false) {
-        continue;
-      }
-      fallthrough;
-    }
-    case 50u: {
-      var_1 = 50u;
-    }
-    default: {
-    }
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; continue; forward on true
-     OpBranchConditional %cond %40 %80
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (false) {
-  } else {
-    continue;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_Forward_OnFalse) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpLoopMerge %99 %80 None
-     OpBranch %30
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-; continue; forward on true
-     OpBranchConditional %cond %80 %40
-
-     %40 = OpLabel
-     OpStore %var %uint_3
-     OpBranch %80
-
-     %80 = OpLabel ; continue target
-     OpStore %var %uint_4
-     OpBranch %20
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-loop {
-  var_1 = 1u;
-  var_1 = 2u;
-  if (false) {
-    continue;
-  }
-  var_1 = 3u;
-
-  continuing {
-    var_1 = 4u;
-  }
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_IfBreak_IfBreak_Same) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %99 %99
-
-     %20 = OpLabel ; dead
-     OpStore %var %uint_1
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 0u;
-if (false) {
-}
-var_1 = 5u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_IfBreak_IfBreak_DifferentIsError) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_0
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond %30 %89
-
-     %30 = OpLabel
-     OpStore %var %uint_2
-     OpBranchConditional %cond %89 %99 ; invalid divergence
-
-     %89 = OpLabel ; inner if-merge
-     OpBranch %99
-
-     %99 = OpLabel ; outer if-merge
-     OpStore %var %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from block 30 to block 99 is an invalid exit from construct "
-         "starting at block 20; branch bypasses merge block 89"));
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 30 %30
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpBranchConditional %cond %30 %30 ; fallthrough fallthrough
-
-     %30 = OpLabel
-     OpStore %var %uint_30
-     OpBranch %99
-
-     %99 = OpLabel
-     OpStore %var %uint_7
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-switch(42u) {
-  case 20u: {
-    var_1 = 20u;
-    fallthrough;
-  }
-  case 30u: {
-    var_1 = 30u;
-  }
-  default: {
-  }
-}
-var_1 = 7u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Fallthrough_NotLastInCase_IsError) {
-  // See also
-  // ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError.
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %selector %99 20 %20 40 %40
-
-     %20 = OpLabel ; case 30
-     OpSelectionMerge %39 None
-     OpBranchConditional %cond %40 %30 ; fallthrough and forward
-
-     %30 = OpLabel
-     OpBranch %39
-
-     %39 = OpLabel
-     OpBranch %99
-
-     %40 = OpLabel  ; case 40
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  // The weird forward branch pulls in 40 as part of the selection rather than
-  // as a case.
-  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 30, 39, 99));
-  EXPECT_THAT(
-      p->error(),
-      Eq("Branch from 10 to 40 bypasses header 20 (dominance rule violated)"));
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Forward_Forward_Same) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpStore %var %uint_1
-     OpBranchConditional %cond %99 %99; forward
-
-     %99 = OpLabel
-     OpStore %var %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 1u;
-var_1 = 2u;
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel
-     OpReturn
-
-     %99 = OpLabel
-     OpStore %var %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              Eq("Control flow diverges at block 10 (to 20, 99) but it is not "
-                 "a structured header (it has no merge instruction)"));
-}
-
-TEST_F(SpvParserCFGTest, Switch_NotAsSelectionHeader_Simple) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSwitch %uint_0 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("invalid structured control flow: found an OpSwitch that "
-                "is not preceded by an OpSelectionMerge:"));
-}
-
-TEST_F(SpvParserCFGTest,
-       Switch_NotAsSelectionHeader_NonDefaultBranchesAreContinue) {
-  // Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
-  auto p = parser(test::Assemble(CommonTypes() + R"(
- %100 = OpFunction %void None %voidfn
- %entry = OpLabel
- OpBranch %loop
- %loop = OpLabel
- OpLoopMerge %merge %cont None
- OpBranchConditional %cond %merge %b1
-
- ; Here an OpSwitch is used with only one "unseen-so-far" target
- ; so it doesn't need an OpSelectionMerge.
- ; The %cont target can be implemented via "continue". So we can
- ; generate:
- ;    if ((selector != 1) && (selector != 3)) { continue; }
- %b1 = OpLabel
- OpSwitch %selector %b2 0 %b2 1 %cont 2 %b2 3 %cont
-
- %b2 = OpLabel ; the one unseen target
- OpBranch %cont
- %cont = OpLabel
- OpBranchConditional %cond2 %merge %loop
- %merge = OpLabel
- OpReturn
- OpFunctionEnd
-   )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("invalid structured control flow: found an OpSwitch that "
-                "is not preceded by an OpSelectionMerge:"));
-}
-
-TEST_F(SpvParserCFGTest, Switch_NotAsSelectionHeader_DefaultBranchIsContinue) {
-  // Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
-  auto p = parser(test::Assemble(CommonTypes() + R"(
- %100 = OpFunction %void None %voidfn
- %entry = OpLabel
- OpBranch %loop
- %loop = OpLabel
- OpLoopMerge %merge %cont None
- OpBranchConditional %cond %merge %b1
-
- ; Here an OpSwitch is used with only one "unseen-so-far" target
- ; so it doesn't need an OpSelectionMerge.
- ; The %cont target can be implemented via "continue". So we can
- ; generate:
- ;    if (!(selector == 0 || selector == 2)) {continue;}
- %b1 = OpLabel
- OpSwitch %selector %cont 0 %b2 1 %cont 2 %b2
-
- %b2 = OpLabel ; the one unseen target
- OpBranch %cont
- %cont = OpLabel
- OpBranchConditional %cond2 %merge %loop
- %merge = OpLabel
- OpReturn
- OpFunctionEnd
-   )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("invalid structured control flow: found an OpSwitch that "
-                "is not preceded by an OpSelectionMerge:"));
-}
-
-TEST_F(SpvParserCFGTest, SiblingLoopConstruct_Null) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %10 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_EQ(fe.SiblingLoopConstruct(nullptr), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, SiblingLoopConstruct_NotAContinue) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
-  const Construct* c = fe.GetBlockInfo(10)->construct;
-  EXPECT_NE(c, nullptr);
-  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, SiblingLoopConstruct_SingleBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
-  const Construct* c = fe.GetBlockInfo(20)->construct;
-  EXPECT_EQ(c->kind, Construct::kContinue);
-  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %20 None ; continue target is also loop header
-     OpBranch %30
-
-     %30 = OpLabel
-     OpBranchConditional %cond %20 %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
-  const Construct* c = fe.GetBlockInfo(20)->construct;
-  EXPECT_EQ(c->kind, Construct::kContinue);
-  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
-}
-
-TEST_F(SpvParserCFGTest, SiblingLoopConstruct_HasSiblingLoop) {
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpBranch %20
-
-     %20 = OpLabel
-     OpLoopMerge %99 %30 None
-     OpBranchConditional %cond %30 %99
-
-     %30 = OpLabel ; continue target
-     OpBranch %20
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
-  const Construct* c = fe.GetBlockInfo(30)->construct;
-  EXPECT_EQ(c->kind, Construct::kContinue);
-  EXPECT_THAT(ToString(fe.SiblingLoopConstruct(c)),
-              Eq("Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 "
-                 "parent:Function@10 scope:[1,3) in-l:Loop@20 }"));
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_IfSelection_TrueBranch_LoopBreak) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %99 %30 ; true branch breaking is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-    break;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_TrueBranch_LoopContinue) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %90 %30 ; true branch continue is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-    continue;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_TrueBranch_SwitchBreak) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %uint_20 %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %99 %30 ; true branch switch break is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; if-selection merge
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(switch(20u) {
-  case 20u: {
-    if (false) {
-      break;
-    }
-  }
-  default: {
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_LoopBreak) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %99 ; false branch breaking is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-  } else {
-    break;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_LoopContinue) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     %10 = OpLabel
-     OpLoopMerge %99 %90 None
-     OpBranch %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %90 ; false branch continue is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; selection merge
-     OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpBranch %10 ; backedge
-
-     %99 = OpLabel ; loop merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  if (false) {
-  } else {
-    continue;
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got) << p->error();
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_SwitchBreak) {
-  // crbug.com/tint/243
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %uint_20 %99 20 %20
-
-     %20 = OpLabel
-     OpSelectionMerge %40 None
-     OpBranchConditional %cond %30 %99 ; false branch switch break is ok
-
-     %30 = OpLabel
-     OpBranch %40
-
-     %40 = OpLabel ; if-selection merge
-     OpBranch %99
-
-     %99 = OpLabel ; switch merge
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(switch(20u) {
-  case 20u: {
-    if (false) {
-    } else {
-      break;
-    }
-  }
-  default: {
-  }
-}
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-TEST_F(SpvParserCFGTest, EmitBody_LoopInternallyDiverge_Simple) {
-  // crbug.com/tint/524
-  auto assembly = CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %10 = OpLabel
-     OpStore %var %uint_10
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %var %uint_20
-     OpLoopMerge %99 %90 None
-     OpBranchConditional %cond %30 %40 ; divergence
-
-       %30 = OpLabel
-       OpStore %var %uint_30
-       OpBranch %90
-
-       %40 = OpLabel
-       OpStore %var %uint_40
-       OpBranch %90
-
-     %90 = OpLabel ; continue target
-     OpStore %var %uint_90
-     OpBranch %20
-
-     %99 = OpLabel ; loop merge
-     OpStore %var %uint_99
-     OpReturn
-
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var_1 = 10u;
-loop {
-  var_1 = 20u;
-  if (false) {
-    var_1 = 30u;
-    continue;
-  } else {
-    var_1 = 40u;
-  }
-
-  continuing {
-    var_1 = 90u;
-  }
-}
-var_1 = 99u;
-return;
-)";
-  ASSERT_EQ(expect, got) << got;
-}
-
-TEST_F(SpvParserCFGTest,
-       EmitBody_ContinueFromSingleBlockLoopToOuterLoop_IsError) {
-  // crbug.com/tint/793
-  // This is invalid SPIR-V but the validator was only recently upgraded
-  // to catch it.
-  auto assembly = CommonTypes() + R"(
-  %100 = OpFunction %void None %voidfn
-  %5 = OpLabel
-  OpBranch %10
-
-  %10 = OpLabel ; outer loop header
-  OpLoopMerge %99 %89 None
-  OpBranchConditional %cond %99 %20
-
-  %20 = OpLabel ; inner loop single block loop
-  OpLoopMerge %79 %20 None
-
-  ; true -> continue to outer loop
-  ; false -> self-loop
-  ; The true branch is invalid because a "continue block", i.e. the block
-  ; containing the branch to the continue target, "is valid only for the
-  ; innermost loop it is nested inside of".
-  ; So it can't branch to the continue target of an outer loop.
-  OpBranchConditional %cond %89 %20
-
-  %79 = OpLabel ; merge for outer loop
-  OpUnreachable
-
-  %89 = OpLabel
-  OpBranch %10 ; backedge for outer loop
-
-  %99 = OpLabel ; merge for outer
-  OpReturn
-
-  OpFunctionEnd
-
-)";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(),
-              HasSubstr("block <ID> 20[%20] exits the continue headed by <ID> "
-                        "20[%20], but not via a structured exit"))
-      << p->error();
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
deleted file mode 100644
index cca7ceb..0000000
--- a/src/reader/spirv/function_composite_test.cc
+++ /dev/null
@@ -1,1075 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-std::string Caps() {
-  return R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %100 "main"
-  OpExecutionMode %100 LocalSize 1 1 1
-)";
-}
-
-std::string CommonTypes() {
-  return R"(
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %uint_10 = OpConstant %uint 10
-  %uint_20 = OpConstant %uint 20
-  %uint_1 = OpConstant %uint 1
-  %uint_3 = OpConstant %uint 3
-  %uint_4 = OpConstant %uint 4
-  %uint_5 = OpConstant %uint 5
-  %int_1 = OpConstant %int 1
-  %int_30 = OpConstant %int 30
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-  %float_70 = OpConstant %float 70
-
-  %v2uint = OpTypeVector %uint 2
-  %v3uint = OpTypeVector %uint 3
-  %v4uint = OpTypeVector %uint 4
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-
-  %m3v2float = OpTypeMatrix %v2float 3
-  %m3v2float_0 = OpConstantNull %m3v2float
-
-  %s_v2f_u_i = OpTypeStruct %v2float %uint %int
-  %a_u_5 = OpTypeArray %uint %uint_5
-
-  %v2uint_3_4 = OpConstantComposite %v2uint %uint_3 %uint_4
-  %v2uint_4_3 = OpConstantComposite %v2uint %uint_4 %uint_3
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-  %v2float_70_70 = OpConstantComposite %v2float %float_70 %float_70
-)";
-}
-
-std::string Preamble() {
-  return Caps() + CommonTypes();
-}
-
-using SpvParserTest_Composite_Construct = SpvParserTest;
-
-TEST_F(SpvParserTest_Composite_Construct, Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeConstruct %v2uint %uint_10 %uint_20
-     %2 = OpCompositeConstruct %v2int %int_30 %int_40
-     %3 = OpCompositeConstruct %v2float %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_1 : vec2<u32> = vec2<u32>(10u, 20u);
-let x_2 : vec2<i32> = vec2<i32>(30, 40);
-let x_3 : vec2<f32> = vec2<f32>(50.0, 60.0);
-)"));
-}
-
-TEST_F(SpvParserTest_Composite_Construct, Matrix) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeConstruct %m3v2float %v2float_50_60 %v2float_60_50 %v2float_70_70
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : mat3x2<f32> = mat3x2<f32>("
-                        "vec2<f32>(50.0, 60.0), "
-                        "vec2<f32>(60.0, 50.0), "
-                        "vec2<f32>(70.0, 70.0));"));
-}
-
-TEST_F(SpvParserTest_Composite_Construct, Array) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeConstruct %a_u_5 %uint_10 %uint_20 %uint_3 %uint_4 %uint_5
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          "let x_1 : array<u32, 5u> = array<u32, 5u>(10u, 20u, 3u, 4u, 5u);"));
-}
-
-TEST_F(SpvParserTest_Composite_Construct, Struct) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeConstruct %s_v2f_u_i %v2float_50_60 %uint_5 %int_30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : S = S(vec2<f32>(50.0, 60.0), 5u, 30);"));
-}
-
-TEST_F(SpvParserTest_Composite_Construct,
-       ConstantComposite_Struct_NoDeduplication) {
-  const auto assembly = Preamble() + R"(
-     %200 = OpTypeStruct %uint
-     %300 = OpTypeStruct %uint ; isomorphic structures
-
-     %201 = OpConstantComposite %200 %uint_10
-     %301 = OpConstantComposite %300 %uint_10  ; isomorphic constants
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpCopyObject %200 %201
-     %3 = OpCopyObject %300 %301
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const auto expected = std::string(
-      R"(let x_2 : S_1 = S_1(10u);
-let x_3 : S_2 = S_2(10u);
-return;
-)");
-  EXPECT_EQ(got, expected) << got;
-}
-
-using SpvParserTest_CompositeExtract = SpvParserTest;
-
-TEST_F(SpvParserTest_CompositeExtract, Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeExtract %float %v2float_50_60 1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = vec2<f32>(50.0, 60.0).y;"));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeExtract %float %v2float_50_60 900
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_EQ(p->error(),
-            "OpCompositeExtract %1 index value 900 is out of bounds for vector "
-            "of 2 elements");
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Matrix) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeExtract %v2float %1 2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : vec2<f32> = x_1[2u];"));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Matrix_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeExtract %v2float %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_EQ(p->error(),
-            "OpCompositeExtract %2 index value 3 is out of bounds for matrix "
-            "of 3 elements");
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Matrix_Vector) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeExtract %float %1 2 1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : f32 = x_1[2u].y;"));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Array) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %a_u_5
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %a_u_5 %var
-     %2 = OpCompositeExtract %uint %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : u32 = x_1[3u];"));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, RuntimeArray_IsError) {
-  const auto assembly = Preamble() + R"(
-     %rtarr = OpTypeRuntimeArray %uint
-     %ptr = OpTypePointer Function %rtarr
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %rtarr %var
-     %2 = OpCompositeExtract %uint %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(),
-              HasSubstr("can't do OpCompositeExtract on a runtime array: "));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Struct) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %s_v2f_u_i
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s_v2f_u_i %var
-     %2 = OpCompositeExtract %int %1 2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : i32 = x_1.field2;"));
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Struct_DifferOnlyInMemberName) {
-  const std::string assembly = R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-
-     OpMemberName %s0 0 "algo"
-     OpMemberName %s1 0 "rithm"
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %uint = OpTypeInt 32 0
-
-     %s0 = OpTypeStruct %uint
-     %s1 = OpTypeStruct %uint
-     %ptr0 = OpTypePointer Function %s0
-     %ptr1 = OpTypePointer Function %s1
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var0 = OpVariable %ptr0 Function
-     %var1 = OpVariable %ptr1 Function
-     %1 = OpLoad %s0 %var0
-     %2 = OpCompositeExtract %uint %1 0
-     %3 = OpLoad %s1 %var1
-     %4 = OpCompositeExtract %uint %3 0
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = fe.ast_body();
-  auto program = p->program();
-  EXPECT_THAT(test::ToString(program, got),
-              HasSubstr("let x_2 : u32 = x_1.algo;"))
-      << test::ToString(program, got);
-  EXPECT_THAT(test::ToString(program, got),
-              HasSubstr("let x_4 : u32 = x_3.rithm;"))
-      << test::ToString(program, got);
-  p->SkipDumpingPending("crbug.com/tint/863");
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Struct_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %s_v2f_u_i
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s_v2f_u_i %var
-     %2 = OpCompositeExtract %int %1 40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_EQ(p->error(),
-            "OpCompositeExtract %2 index value 40 is out of bounds for "
-            "structure %27 having 3 members");
-}
-
-TEST_F(SpvParserTest_CompositeExtract, Struct_Array_Matrix_Vector) {
-  const auto assembly = Preamble() + R"(
-     %a_mat = OpTypeArray %m3v2float %uint_3
-     %s = OpTypeStruct %uint %a_mat
-     %ptr = OpTypePointer Function %s
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s %var
-     %2 = OpCompositeExtract %float %1 1 2 0 1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : f32 = x_1.field1[2u][0u].y;"));
-}
-
-using SpvParserTest_CompositeInsert = SpvParserTest;
-
-TEST_F(SpvParserTest_CompositeInsert, Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  const auto* expected =
-      R"(var x_1_1 : vec2<f32> = vec2<f32>(50.0, 60.0);
-x_1_1.y = 70.0;
-let x_1 : vec2<f32> = x_1_1;
-return;
-)";
-  EXPECT_EQ(got, expected);
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Vector_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 900
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_EQ(p->error(),
-            "OpCompositeInsert %1 index value 900 is out of bounds for vector "
-            "of 2 elements");
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Matrix) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0, 60.0);
-let x_2 : mat3x2<f32> = x_2_1;
-)")) << body_str;
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Matrix_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_EQ(p->error(),
-            "OpCompositeInsert %2 index value 3 is out of bounds for matrix of "
-            "3 elements");
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Matrix_Vector) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %m3v2float %var
-     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0, 60.0);
-let x_2 : mat3x2<f32> = x_2_1;
-return;
-)")) << body_str;
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Array) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %a_u_5
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %a_u_5 %var
-     %2 = OpCompositeInsert %a_u_5 %uint_20 %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : array<u32, 5u> = x_1;
-x_2_1[3u] = 20u;
-let x_2 : array<u32, 5u> = x_2_1;
-)")) << body_str;
-}
-
-TEST_F(SpvParserTest_CompositeInsert, RuntimeArray_IsError) {
-  const auto assembly = Preamble() + R"(
-     %rtarr = OpTypeRuntimeArray %uint
-     %ptr = OpTypePointer Function %rtarr
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %rtarr %var
-     %2 = OpCompositeInsert %rtarr %uint_20 %1 3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(),
-              HasSubstr("can't do OpCompositeInsert on a runtime array: "));
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Struct) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %s_v2f_u_i
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s_v2f_u_i %var
-     %2 = OpCompositeInsert %s_v2f_u_i %int_30 %1 2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(var x_36 : S;
-let x_1 : S = x_36;
-var x_2_1 : S = x_1;
-x_2_1.field2 = 30;
-let x_2 : S = x_2_1;
-)")) << body_str;
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Struct_DifferOnlyInMemberName) {
-  const std::string assembly = R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-
-     OpName %var0 "var0"
-     OpName %var1 "var1"
-     OpMemberName %s0 0 "algo"
-     OpMemberName %s1 0 "rithm"
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %uint = OpTypeInt 32 0
-     %uint_10 = OpConstant %uint 10
-     %uint_11 = OpConstant %uint 11
-
-     %s0 = OpTypeStruct %uint
-     %s1 = OpTypeStruct %uint
-     %ptr0 = OpTypePointer Function %s0
-     %ptr1 = OpTypePointer Function %s1
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var0 = OpVariable %ptr0 Function
-     %var1 = OpVariable %ptr1 Function
-     %1 = OpLoad %s0 %var0
-     %2 = OpCompositeInsert %s0 %uint_10 %1 0
-     %3 = OpLoad %s1 %var1
-     %4 = OpCompositeInsert %s1 %uint_11 %3 0
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const std::string expected = R"(var var0 : S;
-var var1 : S_1;
-let x_1 : S = var0;
-var x_2_1 : S = x_1;
-x_2_1.algo = 10u;
-let x_2 : S = x_2_1;
-let x_3 : S_1 = var1;
-var x_4_1 : S_1 = x_3;
-x_4_1.rithm = 11u;
-let x_4 : S_1 = x_4_1;
-return;
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Struct_IndexTooBigError) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %s_v2f_u_i
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s_v2f_u_i %var
-     %2 = OpCompositeInsert %s_v2f_u_i %uint_10 %1 40
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_EQ(p->error(),
-            "OpCompositeInsert %2 index value 40 is out of bounds for "
-            "structure %27 having 3 members");
-}
-
-TEST_F(SpvParserTest_CompositeInsert, Struct_Array_Matrix_Vector) {
-  const auto assembly = Preamble() + R"(
-     %a_mat = OpTypeArray %m3v2float %uint_3
-     %s = OpTypeStruct %uint %a_mat
-     %ptr = OpTypePointer Function %s
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %var = OpVariable %ptr Function
-     %1 = OpLoad %s %var
-     %2 = OpCompositeInsert %s %float_70 %1 1 2 0 1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(var x_38 : S_1;
-let x_1 : S_1 = x_38;
-var x_2_1 : S_1 = x_1;
-x_2_1.field1[2u][0u].y = 70.0;
-let x_2 : S_1 = x_2_1;
-)")) << body_str;
-}
-
-using SpvParserTest_CopyObject = SpvParserTest;
-
-TEST_F(SpvParserTest_CopyObject, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %uint %uint_3
-     %2 = OpCopyObject %uint %1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_1 : u32 = 3u;
-let x_2 : u32 = x_1;
-)"));
-}
-
-TEST_F(SpvParserTest_CopyObject, Pointer) {
-  const auto assembly = Preamble() + R"(
-     %ptr = OpTypePointer Function %uint
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpVariable %ptr Function
-     %1 = OpCopyObject %ptr %10
-     %2 = OpCopyObject %ptr %1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_1 : ptr<function, u32> = &(x_10);
-let x_2 : ptr<function, u32> = x_1;
-)"));
-}
-
-using SpvParserTest_VectorShuffle = SpvParserTest;
-
-TEST_F(SpvParserTest_VectorShuffle, FunctionScopeOperands_UseBoth) {
-  // Note that variables are generated for the vector operands.
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_3_4
-     %2 = OpIAdd %v2uint %v2uint_4_3 %v2uint_3_4
-     %10 = OpVectorShuffle %v4uint %1 %2 3 2 1 0
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          "let x_10 : vec4<u32> = vec4<u32>(x_2.y, x_2.x, x_1.y, x_1.x);"));
-}
-
-TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_UseBoth) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpVectorShuffle %v4uint %v2uint_3_4 %v2uint_4_3 3 2 1 0
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec4<u32> = vec4<u32>("
-                        "vec2<u32>(4u, 3u).y, "
-                        "vec2<u32>(4u, 3u).x, "
-                        "vec2<u32>(3u, 4u).y, "
-                        "vec2<u32>(3u, 4u).x);"));
-}
-
-TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_AllOnesMapToNull) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_4_3
-     %10 = OpVectorShuffle %v2uint %1 %1 0xFFFFFFFF 1
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec2<u32> = vec2<u32>(0u, x_1.y);"));
-}
-
-TEST_F(SpvParserTest_VectorShuffle,
-       FunctionScopeOperands_MixedInputOperandSizes) {
-  // Note that variables are generated for the vector operands.
-  const auto assembly = Preamble() + R"(
-     %v3uint_3_4_5 = OpConstantComposite %v3uint %uint_3 %uint_4 %uint_5
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_3_4
-     %3 = OpCopyObject %v3uint %v3uint_3_4_5
-     %10 = OpVectorShuffle %v2uint %1 %3 1 4
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_10 : vec2<u32> = vec2<u32>(x_1.y, x_3.z);"));
-}
-
-TEST_F(SpvParserTest_VectorShuffle, IndexTooBig_IsError) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpVectorShuffle %v4uint %v2uint_3_4 %v2uint_4_3 9 2 1 0
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(),
-              Eq("invalid vectorshuffle ID %10: index too large: 9"));
-}
-
-using SpvParserTest_VectorExtractDynamic = SpvParserTest;
-
-TEST_F(SpvParserTest_VectorExtractDynamic, SignedIndex) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_3_4
-     %2 = OpCopyObject %int %int_1
-     %10 = OpVectorExtractDynamic %uint %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr("let x_10 : u32 = x_1[x_2];")) << got;
-}
-
-TEST_F(SpvParserTest_VectorExtractDynamic, UnsignedIndex) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_3_4
-     %2 = OpCopyObject %uint %uint_1
-     %10 = OpVectorExtractDynamic %uint %1 %2
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr("let x_10 : u32 = x_1[x_2];")) << got;
-}
-
-using SpvParserTest_VectorInsertDynamic = SpvParserTest;
-
-TEST_F(SpvParserTest_VectorInsertDynamic, Sample) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpCopyObject %v2uint %v2uint_3_4
-     %2 = OpCopyObject %uint %uint_3
-     %3 = OpCopyObject %int %int_1
-     %10 = OpVectorInsertDynamic %v2uint %1 %2 %3
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(R"(var x_10_1 : vec2<u32> = x_1;
-x_10_1[x_3] = x_2;
-let x_10 : vec2<u32> = x_10_1;
-)")) << got
-     << assembly;
-}
-
-TEST_F(SpvParserTest, DISABLED_WorkgroupSize_Overridable) {
-  // TODO(dneto): Support specializable workgroup size. crbug.com/tint/504
-  const auto* assembly = R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %100 "main"
-  OpDecorate %1 BuiltIn WorkgroupSize
-  OpDecorate %uint_2 SpecId 0
-  OpDecorate %uint_4 SpecId 1
-  OpDecorate %uint_8 SpecId 2
-
-  %uint = OpTypeInt 32 0
-  %uint_2 = OpSpecConstant %uint 2
-  %uint_4 = OpSpecConstant %uint 4
-  %uint_8 = OpSpecConstant %uint 8
-  %v3uint = OpTypeVector %uint 3
-  %1 = OpSpecConstantComposite %v3uint %uint_2 %uint_4 %uint_8
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpCopyObject %v3uint %1
-     %11 = OpCopyObject %uint %uint_2
-     %12 = OpCopyObject %uint %uint_4
-     %13 = OpCopyObject %uint %uint_8
-     OpReturn
-     OpFunctionEnd
-)";
-
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.Emit()) << p->error();
-  const auto got = test::ToString(p->program());
-  EXPECT_THAT(got, HasSubstr(R"(
-  VariableConst{
-    Decorations{
-      OverrideDecoration{0}
-    }
-    x_2
-    none
-    __u32
-    {
-      ScalarConstructor[not set]{2}
-    }
-  }
-  VariableConst{
-    Decorations{
-      OverrideDecoration{1}
-    }
-    x_3
-    none
-    __u32
-    {
-      ScalarConstructor[not set]{4}
-    }
-  }
-  VariableConst{
-    Decorations{
-      OverrideDecoration{2}
-    }
-    x_4
-    none
-    __u32
-    {
-      ScalarConstructor[not set]{8}
-    }
-  }
-)")) << got;
-  EXPECT_THAT(got, HasSubstr(R"(
-    VariableDeclStatement{
-      VariableConst{
-        x_10
-        none
-        __vec_3__u32
-        {
-          TypeConstructor[not set]{
-            __vec_3__u32
-            ScalarConstructor[not set]{2}
-            ScalarConstructor[not set]{4}
-            ScalarConstructor[not set]{8}
-          }
-        }
-      }
-    }
-    VariableDeclStatement{
-      VariableConst{
-        x_11
-        none
-        __u32
-        {
-          Identifier[not set]{x_2}
-        }
-      }
-    }
-    VariableDeclStatement{
-      VariableConst{
-        x_12
-        none
-        __u32
-        {
-          Identifier[not set]{x_3}
-        }
-      }
-    }
-    VariableDeclStatement{
-      VariableConst{
-        x_13
-        none
-        __u32
-        {
-          Identifier[not set]{x_4}
-        }
-      }
-    })"))
-      << got << assembly;
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_conversion_test.cc b/src/reader/spirv/function_conversion_test.cc
deleted file mode 100644
index cf0777a..0000000
--- a/src/reader/spirv/function_conversion_test.cc
+++ /dev/null
@@ -1,655 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint Fragment %100 "main"
-  OpExecutionMode %100 OriginUpperLeft
-
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %bool = OpTypeBool
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %true = OpConstantTrue %bool
-  %false = OpConstantFalse %bool
-  %v2bool = OpTypeVector %bool 2
-  %v2bool_t_f = OpConstantComposite %v2bool %true %false
-
-  %uint_10 = OpConstant %uint 10
-  %uint_20 = OpConstant %uint 20
-  %int_30 = OpConstant %int 30
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-
-  %ptr_uint = OpTypePointer Function %uint
-  %ptr_int = OpTypePointer Function %int
-  %ptr_float = OpTypePointer Function %float
-
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-
-  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
-  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
-  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
-  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-)";
-}
-
-using SpvUnaryConversionTest = SpvParserTestBase<::testing::Test>;
-
-TEST_F(SpvUnaryConversionTest, Bitcast_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpBitcast %uint %float_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>(50.0);"));
-}
-
-TEST_F(SpvUnaryConversionTest, Bitcast_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpBitcast %v2float %v2uint_10_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr(
-          "let x_1 : vec2<f32> = bitcast<vec2<f32>>(vec2<u32>(10u, 20u));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_BadArg) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertSToF %float %void
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_BadArg) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertUToF %float %void
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_BadArg) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToS %float %void
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_BadArg) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToU %float %void
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertSToF %float %false
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("operand for conversion to floating point must be "
-                        "integral scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertSToF %v2float %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to floating point must be integral "
-                "scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %int %int_30
-     %1 = OpConvertSToF %float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = f32(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %uint %uint_10
-     %1 = OpConvertSToF %float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = f32(bitcast<i32>(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2int %v2int_30_40
-     %1 = OpConvertSToF %v2float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2uint %v2uint_10_20
-     %1 = OpConvertSToF %v2float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<i32>>(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertUToF %float %false
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("operand for conversion to floating point must be "
-                        "integral scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertUToF %v2float %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to floating point must be integral "
-                "scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %int %int_30
-     %1 = OpConvertUToF %float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = f32(bitcast<u32>(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %uint %uint_10
-     %1 = OpConvertUToF %float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = f32(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2int %v2int_30_40
-     %1 = OpConvertUToF %v2float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<u32>>(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2uint %v2uint_10_20
-     %1 = OpConvertUToF %v2float %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToS %int %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to signed integer must be floating "
-                "point scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToS %v2float %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to signed integer must be floating "
-                "point scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %float %float_50
-     %1 = OpConvertFToS %int %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : i32 = i32(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %float %float_50
-     %1 = OpConvertFToS %uint %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = bitcast<u32>(i32(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToSigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2float %v2float_50_60
-     %1 = OpConvertFToS %v2int %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<i32> = vec2<i32>(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2float %v2float_50_60
-     %1 = OpConvertFToS %v2uint %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(vec2<i32>(x_30));"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToU %int %uint_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to unsigned integer must be floating "
-                "point scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_BadArgType) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpConvertFToU %v2float %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operand for conversion to unsigned integer must be floating "
-                "point scalar or vector"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToSigned_IsError) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %float %float_50
-     %1 = OpConvertFToU %int %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(), HasSubstr("Expected unsigned int scalar or vector "
-                                    "type as Result Type: ConvertFToU"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %float %float_50
-     %1 = OpConvertFToU %uint %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = u32(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToSigned_IsError) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2float %v2float_50_60
-     %1 = OpConvertFToU %v2int %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(), HasSubstr("Expected unsigned int scalar or vector "
-                                    "type as Result Type: ConvertFToU"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToUnsigned) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %30 = OpCopyObject %v2float %v2float_50_60
-     %1 = OpConvertFToU %v2uint %30
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<u32> = vec2<u32>(x_30);"));
-}
-
-TEST_F(SpvUnaryConversionTest, ConvertFToU_HoistedValue) {
-  // From crbug.com/tint/804
-  const auto assembly = Preamble() + R"(
-
-%100 = OpFunction %void None %voidfn
-%10 = OpLabel
-OpBranch %30
-
-%30 = OpLabel
-OpLoopMerge %90 %80 None
-OpBranchConditional %true %90 %40
-
-%40 = OpLabel
-OpSelectionMerge %50 None
-OpBranchConditional %true %45 %50
-
-%45 = OpLabel
-; This value is hoisted
-%600 = OpCopyObject %float %float_50
-OpBranch %50
-
-%50 = OpLabel
-OpBranch %90
-
-%80 = OpLabel ; unreachable continue target
-%82 = OpConvertFToU %uint %600
-OpBranch %30 ; backedge
-
-%90 = OpLabel
-OpReturn
-OpFunctionEnd
-
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_82 : u32 = u32(x_600);"));
-}
-
-// TODO(dneto): OpSConvert // only if multiple widths
-// TODO(dneto): OpUConvert // only if multiple widths
-// TODO(dneto): OpFConvert // only if multiple widths
-// TODO(dneto): OpQuantizeToF16 // only if f16 supported
-// TODO(dneto): OpSatConvertSToU // Kernel (OpenCL), not in WebGPU
-// TODO(dneto): OpSatConvertUToS // Kernel (OpenCL), not in WebGPU
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_decl_test.cc b/src/reader/spirv/function_decl_test.cc
deleted file mode 100644
index f3da892..0000000
--- a/src/reader/spirv/function_decl_test.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %100 "x_100"
-    OpExecutionMode %100 OriginUpperLeft
-  )";
-}
-
-/// @returns a SPIR-V assembly segment which assigns debug names
-/// to particular IDs.
-std::string Names(std::vector<std::string> ids) {
-  std::ostringstream outs;
-  for (auto& id : ids) {
-    outs << "    OpName %" << id << " \"" << id << "\"\n";
-  }
-  return outs.str();
-}
-
-std::string CommonTypes() {
-  return R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %float_0 = OpConstant %float 0.0
-  )";
-}
-
-std::string MainBody() {
-  return R"(
-    %100 = OpFunction %void None %voidfn
-    %entry_100 = OpLabel
-    OpReturn
-    OpFunctionEnd
-  )";
-}
-
-TEST_F(SpvParserTest, Emit_VoidFunctionWithoutParams) {
-  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.Emit());
-  auto got = test::ToString(p->program());
-  std::string expect = R"(fn x_100() {
-  return;
-}
-)";
-  EXPECT_EQ(got, expect);
-}
-
-TEST_F(SpvParserTest, Emit_NonVoidResultType) {
-  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
-     %fn_ret_float = OpTypeFunction %float
-     %200 = OpFunction %float None %fn_ret_float
-     %entry = OpLabel
-     OpReturnValue %float_0
-     OpFunctionEnd
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.Emit());
-
-  auto got = test::ToString(p->program());
-  std::string expect = R"(fn x_200() -> f32 {
-  return 0.0;
-}
-)";
-  EXPECT_THAT(got, HasSubstr(expect));
-}
-
-TEST_F(SpvParserTest, Emit_MixedParamTypes) {
-  auto p = parser(
-      test::Assemble(Preamble() + Names({"a", "b", "c"}) + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %void %uint %float %int
-
-     %200 = OpFunction %void None %fn_mixed_params
-     %a = OpFunctionParameter %uint
-     %b = OpFunctionParameter %float
-     %c = OpFunctionParameter %int
-     %mixed_entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.Emit());
-
-  auto got = test::ToString(p->program());
-  std::string expect = R"(fn x_200(a : u32, b : f32, c : i32) {
-  return;
-}
-)";
-  EXPECT_THAT(got, HasSubstr(expect));
-}
-
-TEST_F(SpvParserTest, Emit_GenerateParamNames) {
-  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %void %uint %float %int
-
-     %200 = OpFunction %void None %fn_mixed_params
-     %14 = OpFunctionParameter %uint
-     %15 = OpFunctionParameter %float
-     %16 = OpFunctionParameter %int
-     %mixed_entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(200);
-  EXPECT_TRUE(fe.Emit());
-
-  auto got = test::ToString(p->program());
-  std::string expect = R"(fn x_200(x_14 : u32, x_15 : f32, x_16 : i32) {
-  return;
-}
-)";
-  EXPECT_THAT(got, HasSubstr(expect));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_glsl_std_450_test.cc b/src/reader/spirv/function_glsl_std_450_test.cc
deleted file mode 100644
index 40accef..0000000
--- a/src/reader/spirv/function_glsl_std_450_test.cc
+++ /dev/null
@@ -1,1177 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-  OpCapability Shader
-  %glsl = OpExtInstImport "GLSL.std.450"
-  OpMemoryModel Logical GLSL450
-  OpEntryPoint GLCompute %100 "main"
-  OpExecutionMode %100 LocalSize 1 1 1
-
-  OpName %u1 "u1"
-  OpName %u2 "u2"
-  OpName %u3 "u3"
-  OpName %i1 "i1"
-  OpName %i2 "i2"
-  OpName %i3 "i3"
-  OpName %f1 "f1"
-  OpName %f2 "f2"
-  OpName %f3 "f3"
-  OpName %v2u1 "v2u1"
-  OpName %v2u2 "v2u2"
-  OpName %v2u3 "v2u3"
-  OpName %v2i1 "v2i1"
-  OpName %v2i2 "v2i2"
-  OpName %v2i3 "v2i3"
-  OpName %v2f1 "v2f1"
-  OpName %v2f2 "v2f2"
-  OpName %v2f3 "v2f3"
-  OpName %v3f1 "v3f1"
-  OpName %v3f2 "v3f2"
-  OpName %v4f1 "v4f1"
-  OpName %v4f2 "v4f2"
-
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %uint_10 = OpConstant %uint 10
-  %uint_15 = OpConstant %uint 15
-  %uint_20 = OpConstant %uint 20
-  %int_30 = OpConstant %int 30
-  %int_35 = OpConstant %int 35
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-  %float_70 = OpConstant %float 70
-
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-  %v3float = OpTypeVector %float 3
-  %v4float = OpTypeVector %float 4
-
-  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
-  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
-  %v2uint_15_15 = OpConstantComposite %v2uint %uint_15 %uint_15
-  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
-  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
-  %v2int_35_35 = OpConstantComposite %v2int %int_35 %int_35
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-  %v2float_70_70 = OpConstantComposite %v2float %float_70 %float_70
-
-  %v3float_50_60_70 = OpConstantComposite %v3float %float_50 %float_60 %float_70
-  %v3float_60_70_50 = OpConstantComposite %v3float %float_60 %float_70 %float_50
-
-  %v4float_50_50_50_50 = OpConstantComposite %v4float %float_50 %float_50 %float_50 %float_50
-
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  %u1 = OpCopyObject %uint %uint_10
-  %u2 = OpCopyObject %uint %uint_15
-  %u3 = OpCopyObject %uint %uint_20
-
-  %i1 = OpCopyObject %int %int_30
-  %i2 = OpCopyObject %int %int_35
-  %i3 = OpCopyObject %int %int_40
-
-  %f1 = OpCopyObject %float %float_50
-  %f2 = OpCopyObject %float %float_60
-  %f3 = OpCopyObject %float %float_70
-
-  %v2u1 = OpCopyObject %v2uint %v2uint_10_20
-  %v2u2 = OpCopyObject %v2uint %v2uint_20_10
-  %v2u3 = OpCopyObject %v2uint %v2uint_15_15
-
-  %v2i1 = OpCopyObject %v2int %v2int_30_40
-  %v2i2 = OpCopyObject %v2int %v2int_40_30
-  %v2i3 = OpCopyObject %v2int %v2int_35_35
-
-  %v2f1 = OpCopyObject %v2float %v2float_50_60
-  %v2f2 = OpCopyObject %v2float %v2float_60_50
-  %v2f3 = OpCopyObject %v2float %v2float_70_70
-
-  %v3f1 = OpCopyObject %v3float %v3float_50_60_70
-  %v3f2 = OpCopyObject %v3float %v3float_60_70_50
-
-  %v4f1 = OpCopyObject %v4float %v4float_50_50_50_50
-  %v4f2 = OpCopyObject %v4float %v4f1
-)";
-}
-
-struct GlslStd450Case {
-  std::string opcode;
-  std::string wgsl_func;
-};
-inline std::ostream& operator<<(std::ostream& out, GlslStd450Case c) {
-  out << "GlslStd450Case(" << c.opcode << " " << c.wgsl_func << ")";
-  return out;
-}
-
-// Nomenclature:
-// Float = scalar float
-// Floating = scalar float or vector-of-float
-// Float3 = 3-element vector of float
-// Int = scalar signed int
-// Inting = scalar int or vector-of-int
-// Uint = scalar unsigned int
-// Uinting = scalar unsigned or vector-of-unsigned
-
-using SpvParserTest_GlslStd450_Float_Floating =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Float_FloatingFloating =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Floating_Floating =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Floating_FloatingFloating =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Floating_FloatingInting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Float3_Float3Float3 =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-
-using SpvParserTest_GlslStd450_Inting_Inting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Inting_IntingInting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Inting_IntingIntingInting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Uinting_UintingUinting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-using SpvParserTest_GlslStd450_Uinting_UintingUintingUinting =
-    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
-
-TEST_P(SpvParserTest_GlslStd450_Float_Floating, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Float_Floating, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %v2f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(v2f1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1 %f2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, f2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %v2f1 %v2f2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func +
-                              "(v2f1, v2f2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl )" +
-                        GetParam().opcode + R"( %v2f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
-                              "(v2f1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1 %f2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, f2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl )" +
-                        GetParam().opcode + R"( %v2f1 %v2f2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
-                              "(v2f1, v2f2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1 %f2 %f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func +
-                              "(f1, f2, f3);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2f1 %v2f2 %v2f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
-                              "(v2f1, v2f2, v2f3);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl )" +
-                        GetParam().opcode + R"( %f1 %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, i1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2f1 %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
-                              "(v2f1, v2i1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Float3_Float3Float3, Samples) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v3float %glsl )" +
-                        GetParam().opcode +
-                        R"( %v3f1 %v3f2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = " + GetParam().wgsl_func +
-                              "(v3f1, v3f2);"))
-      << body;
-}
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Float_Floating,
-                         ::testing::Values(GlslStd450Case{"Length", "length"}));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Float_FloatingFloating,
-                         ::testing::Values(GlslStd450Case{"Distance",
-                                                          "distance"}));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Floating_Floating,
-                         ::testing::ValuesIn(std::vector<GlslStd450Case>{
-                             {"Acos", "acos"},                //
-                             {"Asin", "asin"},                //
-                             {"Atan", "atan"},                //
-                             {"Ceil", "ceil"},                //
-                             {"Cos", "cos"},                  //
-                             {"Cosh", "cosh"},                //
-                             {"Degrees", "degrees"},          //
-                             {"Exp", "exp"},                  //
-                             {"Exp2", "exp2"},                //
-                             {"FAbs", "abs"},                 //
-                             {"FSign", "sign"},               //
-                             {"Floor", "floor"},              //
-                             {"Fract", "fract"},              //
-                             {"InverseSqrt", "inverseSqrt"},  //
-                             {"Log", "log"},                  //
-                             {"Log2", "log2"},                //
-                             {"Radians", "radians"},          //
-                             {"Round", "round"},              //
-                             {"RoundEven", "round"},          //
-                             {"Sin", "sin"},                  //
-                             {"Sinh", "sinh"},                //
-                             {"Sqrt", "sqrt"},                //
-                             {"Tan", "tan"},                  //
-                             {"Tanh", "tanh"},                //
-                             {"Trunc", "trunc"},              //
-                         }));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Floating_FloatingFloating,
-                         ::testing::ValuesIn(std::vector<GlslStd450Case>{
-                             {"Atan2", "atan2"},
-                             {"NMax", "max"},
-                             {"NMin", "min"},
-                             {"FMax", "max"},  // WGSL max promises more for NaN
-                             {"FMin", "min"},  // WGSL min promises more for NaN
-                             {"Pow", "pow"},
-                             {"Step", "step"},
-                         }));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Floating_FloatingInting,
-                         ::testing::Values(GlslStd450Case{"Ldexp", "ldexp"}));
-// For ldexp with unsigned second argument, see below.
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Float3_Float3Float3,
-                         ::testing::Values(GlslStd450Case{"Cross", "cross"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    Samples,
-    SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating,
-    ::testing::ValuesIn(std::vector<GlslStd450Case>{
-        {"NClamp", "clamp"},
-        {"FClamp", "clamp"},  // WGSL FClamp promises more for NaN
-        {"Fma", "fma"},
-        {"FMix", "mix"},
-        {"SmoothStep", "smoothStep"}}));
-
-TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl )" +
-                        GetParam().opcode +
-                        R"( %i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body,
-              HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func + "(i1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2int %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2i1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
-                              "(v2i1);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl )" +
-                        GetParam().opcode +
-                        R"( %i1 %i2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body, HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func + "(i1, i2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2int %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2i1 %v2i2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
-                              "(v2i1, v2i2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl )" +
-                        GetParam().opcode +
-                        R"( %i1 %i2 %i3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func +
-                              "(i1, i2, i3);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2int %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2i1 %v2i2 %v2i3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
-                              "(v2i1, v2i2, v2i3);"))
-      << body;
-}
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Inting_Inting,
-                         ::testing::Values(GlslStd450Case{"SAbs", "abs"}));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Inting_IntingInting,
-                         ::testing::Values(GlslStd450Case{"SMax", "max"},
-                                           GlslStd450Case{"SMin", "min"}));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Inting_IntingIntingInting,
-                         ::testing::Values(GlslStd450Case{"SClamp", "clamp"}));
-
-TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl )" +
-                        GetParam().opcode + R"( %u1 %u2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body, HasSubstr("let x_1 : u32 = " + GetParam().wgsl_func + "(u1, u2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2uint %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2u1 %v2u2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func +
-                              "(v2u1, v2u2);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl )" +
-                        GetParam().opcode + R"( %u1 %u2 %u3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = " + GetParam().wgsl_func +
-                              "(u1, u2, u3);"))
-      << body;
-}
-
-TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2uint %glsl )" +
-                        GetParam().opcode +
-                        R"( %v2u1 %v2u2 %v2u3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func +
-                              "(v2u1, v2u2, v2u3);"))
-      << body;
-}
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Uinting_UintingUinting,
-                         ::testing::Values(GlslStd450Case{"UMax", "max"},
-                                           GlslStd450Case{"UMin", "min"}));
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_Uinting_UintingUintingUinting,
-                         ::testing::Values(GlslStd450Case{"UClamp", "clamp"}));
-
-// Test Normalize.  WGSL does not have a scalar form of the normalize builtin.
-// So we have to test it separately, as it does not fit the patterns tested
-// above.
-
-TEST_F(SpvParserTest, Normalize_Scalar) {
-  // Scalar normalize always results in 1.0
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl Normalize %f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = 1.0;")) << body;
-}
-
-TEST_F(SpvParserTest, Normalize_Vector2) {
-  // Scalar normalize always results in 1.0
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl Normalize %v2f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = normalize(v2f1);"))
-      << body;
-}
-
-TEST_F(SpvParserTest, Normalize_Vector3) {
-  // Scalar normalize always results in 1.0
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v3float %glsl Normalize %v3f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = normalize(v3f1);"))
-      << body;
-}
-
-TEST_F(SpvParserTest, Normalize_Vector4) {
-  // Scalar normalize always results in 1.0
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v4float %glsl Normalize %v4f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : vec4<f32> = normalize(v4f1);"))
-      << body;
-}
-
-// Check that we convert signedness of operands and result type.
-// This is needed for each of the integer-based extended instructions.
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_SAbs) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl SAbs %u1
-     %2 = OpExtInst %v2uint %glsl SAbs %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(R"(let x_1 : u32 = bitcast<u32>(abs(bitcast<i32>(u1)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(abs(bitcast<vec2<i32>>(v2u1)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_SMax) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl SMax %u1 %u2
-     %2 = OpExtInst %v2uint %glsl SMax %v2u1 %v2u2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : u32 = bitcast<u32>(max(bitcast<i32>(u1), bitcast<i32>(u2)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(max(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_SMin) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl SMin %u1 %u2
-     %2 = OpExtInst %v2uint %glsl SMin %v2u1 %v2u2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : u32 = bitcast<u32>(min(bitcast<i32>(u1), bitcast<i32>(u2)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(min(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_SClamp) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %uint %glsl SClamp %u1 %i2 %u3
-     %2 = OpExtInst %v2uint %glsl SClamp %v2u1 %v2i2 %v2u3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : u32 = bitcast<u32>(clamp(bitcast<i32>(u1), i2, bitcast<i32>(u3)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(clamp(bitcast<vec2<i32>>(v2u1), v2i2, bitcast<vec2<i32>>(v2u3)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_UMax) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl UMax %i1 %i2
-     %2 = OpExtInst %v2int %glsl UMax %v2i1 %v2i2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : i32 = bitcast<i32>(max(bitcast<u32>(i1), bitcast<u32>(i2)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(max(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_UMin) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl UMin %i1 %i2
-     %2 = OpExtInst %v2int %glsl UMin %v2i1 %v2i2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : i32 = bitcast<i32>(min(bitcast<u32>(i1), bitcast<u32>(i2)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(min(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
-      << body;
-}
-
-TEST_F(SpvParserTest, RectifyOperandsAndResult_UClamp) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %int %glsl UClamp %i1 %u2 %i3
-     %2 = OpExtInst %v2int %glsl UClamp %v2i1 %v2u2 %v2i3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_1 : i32 = bitcast<i32>(clamp(bitcast<u32>(i1), u2, bitcast<u32>(i3)));)"))
-      << body;
-  EXPECT_THAT(
-      body,
-      HasSubstr(
-          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(clamp(bitcast<vec2<u32>>(v2i1), v2u2, bitcast<vec2<u32>>(v2i3)));)"))
-      << body;
-}
-
-struct DataPackingCase {
-  std::string opcode;
-  std::string wgsl_func;
-  uint32_t vec_size;
-};
-
-inline std::ostream& operator<<(std::ostream& out, DataPackingCase c) {
-  out << "DataPacking(" << c.opcode << ")";
-  return out;
-}
-
-using SpvParserTest_GlslStd450_DataPacking =
-    SpvParserTestBase<::testing::TestWithParam<DataPackingCase>>;
-
-TEST_P(SpvParserTest_GlslStd450_DataPacking, Valid) {
-  auto param = GetParam();
-  const auto assembly = Preamble() + R"(
-  %1 = OpExtInst %uint %glsl )" +
-                        param.opcode +
-                        (param.vec_size == 2 ? " %v2f1" : " %v4f1") + R"(
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = " + param.wgsl_func + "(v" +
-                              std::to_string(param.vec_size) + "f1);"))
-      << body;
-}
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_DataPacking,
-                         ::testing::ValuesIn(std::vector<DataPackingCase>{
-                             {"PackSnorm4x8", "pack4x8snorm", 4},
-                             {"PackUnorm4x8", "pack4x8unorm", 4},
-                             {"PackSnorm2x16", "pack2x16snorm", 2},
-                             {"PackUnorm2x16", "pack2x16unorm", 2},
-                             {"PackHalf2x16", "pack2x16float", 2}}));
-
-using SpvParserTest_GlslStd450_DataUnpacking =
-    SpvParserTestBase<::testing::TestWithParam<DataPackingCase>>;
-
-TEST_P(SpvParserTest_GlslStd450_DataUnpacking, Valid) {
-  auto param = GetParam();
-  const auto assembly = Preamble() + R"(
-  %1 = OpExtInst )" + (param.vec_size == 2 ? "%v2float" : "%v4float") +
-                        std::string(" %glsl ") + param.opcode + R"( %u1
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body, HasSubstr("let x_1 : " +
-                              std::string(param.vec_size == 2 ? "vec2<f32>"
-                                                              : "vec4<f32>") +
-
-                              +" = " + param.wgsl_func + "(u1);"))
-      << body;
-}
-
-INSTANTIATE_TEST_SUITE_P(Samples,
-                         SpvParserTest_GlslStd450_DataUnpacking,
-                         ::testing::ValuesIn(std::vector<DataPackingCase>{
-                             {"UnpackSnorm4x8", "unpack4x8snorm", 4},
-                             {"UnpackUnorm4x8", "unpack4x8unorm", 4},
-                             {"UnpackSnorm2x16", "unpack2x16snorm", 2},
-                             {"UnpackUnorm2x16", "unpack2x16unorm", 2},
-                             {"UnpackHalf2x16", "unpack2x16float", 2}}));
-
-TEST_F(SpvParserTest, GlslStd450_Refract_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl Refract %f1 %f2 %f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected =
-      R"(let x_1 : f32 = refract(vec2<f32>(f1, 0.0), vec2<f32>(f2, 0.0), f3).x;)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_Refract_Vector) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl Refract %v2f1 %v2f2 %f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected = R"(let x_1 : vec2<f32> = refract(v2f1, v2f2, f3);)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_FaceForward_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %99 = OpFAdd %float %f1 %f1 ; normal operand has only one use
-     %1 = OpExtInst %float %glsl FaceForward %99 %f2 %f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  // The %99 sum only has one use.  Ensure it is evaluated only once by
-  // making a let-declaration for it, since it is the normal operand to
-  // the builtin function, and code generation uses it twice.
-  const auto* expected =
-      R"(let x_1 : f32 = select(-(x_99), x_99, ((f2 * f3) < 0.0));)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_FaceForward_Vector) {
-  const auto assembly = Preamble() + R"(
-     %99 = OpFAdd %v2float %v2f1 %v2f1
-     %1 = OpExtInst %v2float %glsl FaceForward %v2f1 %v2f2 %v2f3
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected =
-      R"(let x_1 : vec2<f32> = faceForward(v2f1, v2f2, v2f3);)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_Reflect_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %98 = OpFAdd %float %f1 %f1 ; has only one use
-     %99 = OpFAdd %float %f2 %f2 ; has only one use
-     %1 = OpExtInst %float %glsl Reflect %98 %99
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  // The %99 sum only has one use.  Ensure it is evaluated only once by
-  // making a let-declaration for it, since it is the normal operand to
-  // the builtin function, and code generation uses it twice.
-  const auto* expected =
-      R"(let x_1 : f32 = (x_98 - (2.0 * (x_99 * (x_99 * x_98))));)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_Reflect_Vector) {
-  const auto assembly = Preamble() + R"(
-     %98 = OpFAdd %v2float %v2f1 %v2f1
-     %99 = OpFAdd %v2float %v2f2 %v2f2
-     %1 = OpExtInst %v2float %glsl Reflect %98 %99
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected = R"(
-let x_98 : vec2<f32> = (v2f1 + v2f1);
-let x_99 : vec2<f32> = (v2f2 + v2f2);
-let x_1 : vec2<f32> = reflect(x_98, x_99);
-)";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-// For ldexp with signed second argument, see above.
-TEST_F(SpvParserTest, GlslStd450_Ldexp_Scalar_Float_Uint) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %float %glsl Ldexp %f1 %u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected = "let x_1 : f32 = ldexp(f1, i32(u1));";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-TEST_F(SpvParserTest, GlslStd450_Ldexp_Vector_Floatvec_Uintvec) {
-  const auto assembly = Preamble() + R"(
-     %1 = OpExtInst %v2float %glsl Ldexp %v2f1 %v2u1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body = test::ToString(p->program(), ast_body);
-  const auto* expected = "let x_1 : vec2<f32> = ldexp(v2f1, vec2<i32>(v2u1));";
-
-  EXPECT_THAT(body, HasSubstr(expected)) << body;
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_logical_test.cc b/src/reader/spirv/function_logical_test.cc
deleted file mode 100644
index a230c71..0000000
--- a/src/reader/spirv/function_logical_test.cc
+++ /dev/null
@@ -1,992 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint Fragment %100 "main"
-  OpExecutionMode %100 OriginUpperLeft
-
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %bool = OpTypeBool
-  %true = OpConstantTrue %bool
-  %false = OpConstantFalse %bool
-
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %uint_10 = OpConstant %uint 10
-  %uint_20 = OpConstant %uint 20
-  %int_30 = OpConstant %int 30
-  %int_40 = OpConstant %int 40
-  %float_50 = OpConstant %float 50
-  %float_60 = OpConstant %float 60
-
-  %ptr_uint = OpTypePointer Function %uint
-  %ptr_int = OpTypePointer Function %int
-  %ptr_float = OpTypePointer Function %float
-
-  %v2bool = OpTypeVector %bool 2
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-
-  %v2bool_t_f = OpConstantComposite %v2bool %true %false
-  %v2bool_f_t = OpConstantComposite %v2bool %false %true
-  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
-  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
-  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
-  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
-  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
-  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
-)";
-}
-
-// Returns the AST dump for a given SPIR-V assembly constant.
-std::string AstFor(std::string assembly) {
-  if (assembly == "v2bool_t_f") {
-    return "vec2<bool>(true, false)";
-  }
-  if (assembly == "v2bool_f_t") {
-    return "vec2<bool>(false, true)";
-  }
-  if (assembly == "v2uint_10_20") {
-    return "vec2<u32>(10u, 20u)";
-  }
-  if (assembly == "cast_uint_10") {
-    return "bitcast<i32>(10u)";
-  }
-  if (assembly == "cast_uint_20") {
-    return "bitcast<i32>(20u)";
-  }
-  if (assembly == "cast_v2uint_10_20") {
-    return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
-  }
-  if (assembly == "v2uint_20_10") {
-    return "vec2<u32>(20u, 10u)";
-  }
-  if (assembly == "cast_v2uint_20_10") {
-    return "bitcast<vec2<i32>>(vec2<u32>(20u, 10u))";
-  }
-  if (assembly == "cast_int_30") {
-    return "bitcast<u32>(30)";
-  }
-  if (assembly == "cast_int_40") {
-    return "bitcast<u32>(40)";
-  }
-  if (assembly == "v2int_30_40") {
-    return "vec2<i32>(30, 40)";
-  }
-  if (assembly == "cast_v2int_30_40") {
-    return "bitcast<vec2<u32>>(vec2<i32>(30, 40))";
-  }
-  if (assembly == "v2int_40_30") {
-    return "vec2<i32>(40, 30)";
-  }
-  if (assembly == "cast_v2int_40_30") {
-    return "bitcast<vec2<u32>>(vec2<i32>(40, 30))";
-  }
-  if (assembly == "v2float_50_60") {
-    return "vec2<f32>(50.0, 60.0)";
-  }
-  if (assembly == "v2float_60_50") {
-    return "vec2<f32>(60.0, 50.0)";
-  }
-  return "bad case";
-}
-
-using SpvUnaryLogicalTest = SpvParserTestBase<::testing::Test>;
-
-TEST_F(SpvUnaryLogicalTest, LogicalNot_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpLogicalNot %bool %true
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !(true);"));
-}
-
-TEST_F(SpvUnaryLogicalTest, LogicalNot_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpLogicalNot %v2bool %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<bool> = !(vec2<bool>(true, false));"));
-}
-
-struct BinaryData {
-  const std::string res_type;
-  const std::string lhs;
-  const std::string op;
-  const std::string rhs;
-  const std::string ast_type;
-  const std::string ast_lhs;
-  const std::string ast_op;
-  const std::string ast_rhs;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
-      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
-      << data.ast_op << "," << data.ast_rhs << "}";
-  return out;
-}
-
-using SpvBinaryLogicalTest =
-    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
-
-TEST_P(SpvBinaryLogicalTest, EmitExpression) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = )" + GetParam().op +
-                        " %" + GetParam().res_type + " %" + GetParam().lhs +
-                        " %" + GetParam().rhs + R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << "\n"
-      << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  std::ostringstream ss;
-  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
-     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(ss.str()))
-      << assembly;
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_IEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // uint uint
-        BinaryData{"bool", "uint_10", "OpIEqual", "uint_20", "bool", "10u",
-                   "==", "20u"},
-        // int int
-        BinaryData{"bool", "int_30", "OpIEqual", "int_40", "bool", "30",
-                   "==", "40"},
-        // uint int
-        BinaryData{"bool", "uint_10", "OpIEqual", "int_40", "bool", "10u",
-                   "==", "bitcast<u32>(40)"},
-        // int uint
-        BinaryData{"bool", "int_40", "OpIEqual", "uint_10", "bool", "40",
-                   "==", "bitcast<i32>(10u)"},
-        // v2uint v2uint
-        BinaryData{"v2bool", "v2uint_10_20", "OpIEqual", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2uint_10_20"),
-                   "==", AstFor("v2uint_20_10")},
-        // v2int v2int
-        BinaryData{"v2bool", "v2int_30_40", "OpIEqual", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2int_30_40"),
-                   "==", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdEqual", "float_60",
-                                 "bool", "50.0", "==", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdEqual",
-                                 "v2float_60_50", "vec2<bool>",
-                                 AstFor("v2float_50_60"),
-                                 "==", AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_INotEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both uint
-        BinaryData{"bool", "uint_10", "OpINotEqual", "uint_20", "bool", "10u",
-                   "!=", "20u"},
-        // Both int
-        BinaryData{"bool", "int_30", "OpINotEqual", "int_40", "bool", "30",
-                   "!=", "40"},
-        // uint int
-        BinaryData{"bool", "uint_10", "OpINotEqual", "int_40", "bool", "10u",
-                   "!=", "bitcast<u32>(40)"},
-        // int uint
-        BinaryData{"bool", "int_40", "OpINotEqual", "uint_10", "bool", "40",
-                   "!=", "bitcast<i32>(10u)"},
-        // Both v2uint
-        BinaryData{"v2bool", "v2uint_10_20", "OpINotEqual", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2uint_10_20"),
-                   "!=", AstFor("v2uint_20_10")},
-        // Both v2int
-        BinaryData{"v2bool", "v2int_30_40", "OpINotEqual", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2int_30_40"),
-                   "!=", AstFor("v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdNotEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdNotEqual",
-                                 "float_60", "bool", "50.0", "!=", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdNotEqual",
-                                 "v2float_60_50", "vec2<bool>",
-                                 AstFor("v2float_50_60"),
-                                 "!=", AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdLessThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThan",
-                                 "float_60", "bool", "50.0", "<", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdLessThan",
-                                 "v2float_60_50", "vec2<bool>",
-                                 AstFor("v2float_50_60"), "<",
-                                 AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdLessThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThanEqual",
-                                 "float_60", "bool", "50.0", "<=", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60",
-                                 "OpFOrdLessThanEqual", "v2float_60_50",
-                                 "vec2<bool>", AstFor("v2float_50_60"),
-                                 "<=", AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdGreaterThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThan",
-                                 "float_60", "bool", "50.0", ">", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdGreaterThan",
-                                 "v2float_60_50", "vec2<bool>",
-                                 AstFor("v2float_50_60"), ">",
-                                 AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_FOrdGreaterThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThanEqual",
-                                 "float_60", "bool", "50.0", ">=", "60.0"},
-                      BinaryData{"v2bool", "v2float_50_60",
-                                 "OpFOrdGreaterThanEqual", "v2float_60_50",
-                                 "vec2<bool>", AstFor("v2float_50_60"),
-                                 ">=", AstFor("v2float_60_50")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_LogicalAnd,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "true", "OpLogicalAnd", "false",
-                                 "bool", "true", "&", "false"},
-                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalAnd",
-                                 "v2bool_f_t", "vec2<bool>",
-                                 AstFor("v2bool_t_f"), "&",
-                                 AstFor("v2bool_f_t")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_LogicalOr,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "true", "OpLogicalOr", "false", "bool",
-                                 "true", "|", "false"},
-                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalOr",
-                                 "v2bool_f_t", "vec2<bool>",
-                                 AstFor("v2bool_t_f"), "|",
-                                 AstFor("v2bool_f_t")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_LogicalEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "true", "OpLogicalEqual", "false",
-                                 "bool", "true", "==", "false"},
-                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalEqual",
-                                 "v2bool_f_t", "vec2<bool>",
-                                 AstFor("v2bool_t_f"),
-                                 "==", AstFor("v2bool_f_t")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_LogicalNotEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(BinaryData{"bool", "true", "OpLogicalNotEqual", "false",
-                                 "bool", "true", "!=", "false"},
-                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalNotEqual",
-                                 "v2bool_f_t", "vec2<bool>",
-                                 AstFor("v2bool_t_f"),
-                                 "!=", AstFor("v2bool_f_t")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_UGreaterThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both unsigned
-        BinaryData{"bool", "uint_10", "OpUGreaterThan", "uint_20", "bool",
-                   "10u", ">", "20u"},
-        // First arg signed
-        BinaryData{"bool", "int_30", "OpUGreaterThan", "uint_20", "bool",
-                   AstFor("cast_int_30"), ">", "20u"},
-        // Second arg signed
-        BinaryData{"bool", "uint_10", "OpUGreaterThan", "int_40", "bool", "10u",
-                   ">", AstFor("cast_int_40")},
-        // Vector, both unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2uint_10_20"), ">",
-                   AstFor("v2uint_20_10")},
-        // First arg signed
-        BinaryData{"v2bool", "v2int_30_40", "OpUGreaterThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("cast_v2int_30_40"), ">",
-                   AstFor("v2uint_20_10")},
-        // Second arg signed
-        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2uint_10_20"), ">",
-                   AstFor("cast_v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_UGreaterThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both unsigned
-        BinaryData{"bool", "uint_10", "OpUGreaterThanEqual", "uint_20", "bool",
-                   "10u", ">=", "20u"},
-        // First arg signed
-        BinaryData{"bool", "int_30", "OpUGreaterThanEqual", "uint_20", "bool",
-                   AstFor("cast_int_30"), ">=", "20u"},
-        // Second arg signed
-        BinaryData{"bool", "uint_10", "OpUGreaterThanEqual", "int_40", "bool",
-                   "10u", ">=", AstFor("cast_int_40")},
-        // Vector, both unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThanEqual",
-                   "v2uint_20_10", "vec2<bool>", AstFor("v2uint_10_20"),
-                   ">=", AstFor("v2uint_20_10")},
-        // First arg signed
-        BinaryData{"v2bool", "v2int_30_40", "OpUGreaterThanEqual",
-                   "v2uint_20_10", "vec2<bool>", AstFor("cast_v2int_30_40"),
-                   ">=", AstFor("v2uint_20_10")},
-        // Second arg signed
-        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThanEqual",
-                   "v2int_40_30", "vec2<bool>", AstFor("v2uint_10_20"),
-                   ">=", AstFor("cast_v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ULessThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both unsigned
-        BinaryData{"bool", "uint_10", "OpULessThan", "uint_20", "bool", "10u",
-                   "<", "20u"},
-        // First arg signed
-        BinaryData{"bool", "int_30", "OpULessThan", "uint_20", "bool",
-                   AstFor("cast_int_30"), "<", "20u"},
-        // Second arg signed
-        BinaryData{"bool", "uint_10", "OpULessThan", "int_40", "bool", "10u",
-                   "<", AstFor("cast_int_40")},
-        // Vector, both unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpULessThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2uint_10_20"), "<",
-                   AstFor("v2uint_20_10")},
-        // First arg signed
-        BinaryData{"v2bool", "v2int_30_40", "OpULessThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("cast_v2int_30_40"), "<",
-                   AstFor("v2uint_20_10")},
-        // Second arg signed
-        BinaryData{"v2bool", "v2uint_10_20", "OpULessThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2uint_10_20"), "<",
-                   AstFor("cast_v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ULessThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both unsigned
-        BinaryData{"bool", "uint_10", "OpULessThanEqual", "uint_20", "bool",
-                   "10u", "<=", "20u"},
-        // First arg signed
-        BinaryData{"bool", "int_30", "OpULessThanEqual", "uint_20", "bool",
-                   AstFor("cast_int_30"), "<=", "20u"},
-        // Second arg signed
-        BinaryData{"bool", "uint_10", "OpULessThanEqual", "int_40", "bool",
-                   "10u", "<=", AstFor("cast_int_40")},
-        // Vector, both unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpULessThanEqual", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2uint_10_20"),
-                   "<=", AstFor("v2uint_20_10")},
-        // First arg signed
-        BinaryData{"v2bool", "v2int_30_40", "OpULessThanEqual", "v2uint_20_10",
-                   "vec2<bool>", AstFor("cast_v2int_30_40"),
-                   "<=", AstFor("v2uint_20_10")},
-        // Second arg signed
-        BinaryData{"v2bool", "v2uint_10_20", "OpULessThanEqual", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2uint_10_20"),
-                   "<=", AstFor("cast_v2int_40_30")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SGreaterThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both signed
-        BinaryData{"bool", "int_30", "OpSGreaterThan", "int_40", "bool", "30",
-                   ">", "40"},
-        // First arg unsigned
-        BinaryData{"bool", "uint_10", "OpSGreaterThan", "int_40", "bool",
-                   AstFor("cast_uint_10"), ">", "40"},
-        // Second arg unsigned
-        BinaryData{"bool", "int_30", "OpSGreaterThan", "uint_20", "bool", "30",
-                   ">", AstFor("cast_uint_20")},
-        // Vector, both signed
-        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2int_30_40"), ">",
-                   AstFor("v2int_40_30")},
-        // First arg unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpSGreaterThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("cast_v2uint_10_20"), ">",
-                   AstFor("v2int_40_30")},
-        // Second arg unsigned
-        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2int_30_40"), ">",
-                   AstFor("cast_v2uint_20_10")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SGreaterThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both signed
-        BinaryData{"bool", "int_30", "OpSGreaterThanEqual", "int_40", "bool",
-                   "30", ">=", "40"},
-        // First arg unsigned
-        BinaryData{"bool", "uint_10", "OpSGreaterThanEqual", "int_40", "bool",
-                   AstFor("cast_uint_10"), ">=", "40"},
-        // Second arg unsigned
-        BinaryData{"bool", "int_30", "OpSGreaterThanEqual", "uint_20", "bool",
-                   "30", ">=", AstFor("cast_uint_20")},
-        // Vector, both signed
-        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThanEqual",
-                   "v2int_40_30", "vec2<bool>", AstFor("v2int_30_40"),
-                   ">=", AstFor("v2int_40_30")},
-        // First arg unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpSGreaterThanEqual",
-                   "v2int_40_30", "vec2<bool>", AstFor("cast_v2uint_10_20"),
-                   ">=", AstFor("v2int_40_30")},
-        // Second arg unsigned
-        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThanEqual",
-                   "v2uint_20_10", "vec2<bool>", AstFor("v2int_30_40"),
-                   ">=", AstFor("cast_v2uint_20_10")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SLessThan,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both signed
-        BinaryData{"bool", "int_30", "OpSLessThan", "int_40", "bool", "30", "<",
-                   "40"},
-        // First arg unsigned
-        BinaryData{"bool", "uint_10", "OpSLessThan", "int_40", "bool",
-                   AstFor("cast_uint_10"), "<", "40"},
-        // Second arg unsigned
-        BinaryData{"bool", "int_30", "OpSLessThan", "uint_20", "bool", "30",
-                   "<", AstFor("cast_uint_20")},
-        // Vector, both signed
-        BinaryData{"v2bool", "v2int_30_40", "OpSLessThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2int_30_40"), "<",
-                   AstFor("v2int_40_30")},
-        // First arg unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpSLessThan", "v2int_40_30",
-                   "vec2<bool>", AstFor("cast_v2uint_10_20"), "<",
-                   AstFor("v2int_40_30")},
-        // Second arg unsigned
-        BinaryData{"v2bool", "v2int_30_40", "OpSLessThan", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2int_30_40"), "<",
-                   AstFor("cast_v2uint_20_10")}));
-
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SLessThanEqual,
-    SpvBinaryLogicalTest,
-    ::testing::Values(
-        // Both signed
-        BinaryData{"bool", "int_30", "OpSLessThanEqual", "int_40", "bool", "30",
-                   "<=", "40"},
-        // First arg unsigned
-        BinaryData{"bool", "uint_10", "OpSLessThanEqual", "int_40", "bool",
-                   AstFor("cast_uint_10"), "<=", "40"},
-        // Second arg unsigned
-        BinaryData{"bool", "int_30", "OpSLessThanEqual", "uint_20", "bool",
-                   "30", "<=", AstFor("cast_uint_20")},
-        // Vector, both signed
-        BinaryData{"v2bool", "v2int_30_40", "OpSLessThanEqual", "v2int_40_30",
-                   "vec2<bool>", AstFor("v2int_30_40"),
-                   "<=", AstFor("v2int_40_30")},
-        // First arg unsigned
-        BinaryData{"v2bool", "v2uint_10_20", "OpSLessThanEqual", "v2int_40_30",
-                   "vec2<bool>", AstFor("cast_v2uint_10_20"),
-                   "<=", AstFor("v2int_40_30")},
-        // Second arg unsigned
-        BinaryData{"v2bool", "v2int_30_40", "OpSLessThanEqual", "v2uint_20_10",
-                   "vec2<bool>", AstFor("v2int_30_40"),
-                   "<=", AstFor("cast_v2uint_20_10")}));
-
-using SpvFUnordTest = SpvParserTestBase<::testing::Test>;
-
-TEST_F(SpvFUnordTest, FUnordEqual_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordEqual %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 != 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordEqual %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = "
-                "!((vec2<f32>(50.0, 60.0) != vec2<f32>(60.0, 50.0)));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordNotEqual %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 == 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordNotEqual %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = "
-                "!((vec2<f32>(50.0, 60.0) == vec2<f32>(60.0, 50.0)));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordLessThan %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 >= 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordLessThan %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = "
-                "!((vec2<f32>(50.0, 60.0) >= vec2<f32>(60.0, 50.0)));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordLessThanEqual %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 > 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordLessThanEqual %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<bool> = "
-                        "!((vec2<f32>(50.0, 60.0) > vec2<f32>(60.0, 50.0)));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordGreaterThan %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 <= 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordGreaterThan %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = "
-                "!((vec2<f32>(50.0, 60.0) <= vec2<f32>(60.0, 50.0)));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordGreaterThanEqual %bool %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = !((50.0 < 60.0));"));
-}
-
-TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpFUnordGreaterThanEqual %v2bool %v2float_50_60 %v2float_60_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<bool> = !(("
-                        "vec2<f32>(50.0, 60.0) < vec2<f32>(60.0, 50.0)"
-                        "));"));
-}
-
-using SpvLogicalTest = SpvParserTestBase<::testing::Test>;
-
-TEST_F(SpvLogicalTest, Select_BoolCond_BoolParams) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSelect %bool %true %true %false
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = select(false, true, true);"));
-}
-
-TEST_F(SpvLogicalTest, Select_BoolCond_IntScalarParams) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSelect %uint %true %uint_10 %uint_20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : u32 = select(20u, 10u, true);"));
-}
-
-TEST_F(SpvLogicalTest, Select_BoolCond_FloatScalarParams) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSelect %float %true %float_50 %float_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : f32 = select(60.0, 50.0, true);"));
-}
-
-TEST_F(SpvLogicalTest, Select_BoolCond_VectorParams) {
-  // Prior to SPIR-V 1.4, the condition must be a vector of bools
-  // when the value operands are vectors.
-  // "Before version 1.4, results are only computed per component."
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSelect %v2uint %true %v2uint_10_20 %v2uint_20_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<u32> = select("
-                        "vec2<u32>(20u, 10u), "
-                        "vec2<u32>(10u, 20u), "
-                        "true);"));
-
-  // Fails validation prior to SPIR-V 1.4: If the value operands are vectors,
-  // then the condition must be a vector.
-  // "Expected vector sizes of Result Type and the condition to be equal:
-  // Select"
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvLogicalTest, Select_VecBoolCond_VectorParams) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpSelect %v2uint %v2bool_t_f %v2uint_10_20 %v2uint_20_10
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : vec2<u32> = select("
-                        "vec2<u32>(20u, 10u), "
-                        "vec2<u32>(10u, 20u), "
-                        "vec2<bool>(true, false));"));
-}
-
-TEST_F(SpvLogicalTest, Any) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpAny %bool %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = any(vec2<bool>(true, false));"));
-}
-
-TEST_F(SpvLogicalTest, All) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpAll %bool %v2bool_t_f
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = all(vec2<bool>(true, false));"));
-}
-
-TEST_F(SpvLogicalTest, IsNan_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpIsNan %bool %float_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = isNan(50.0);"));
-}
-
-TEST_F(SpvLogicalTest, IsNan_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpIsNan %v2bool %v2float_50_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = isNan(vec2<f32>(50.0, 60.0));"));
-}
-
-TEST_F(SpvLogicalTest, IsInf_Scalar) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpIsInf %bool %float_50
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_1 : bool = isInf(50.0);"));
-}
-
-TEST_F(SpvLogicalTest, IsInf_Vector) {
-  const auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpIsInf %v2bool %v2float_50_60
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("let x_1 : vec2<bool> = isInf(vec2<f32>(50.0, 60.0));"));
-}
-
-// TODO(dneto): Kernel-guarded instructions.
-// TODO(dneto): OpSelect over more general types, as in SPIR-V 1.4
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
deleted file mode 100644
index 9526586..0000000
--- a/src/reader/spirv/function_memory_test.cc
+++ /dev/null
@@ -1,1314 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-using SpvParserMemoryTest = SpvParserTest;
-
-std::string Preamble() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %100 "main"
-    OpExecutionMode %100 OriginUpperLeft
-)";
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_StoreBoolConst) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeBool
-     %true = OpConstantTrue %ty
-     %false = OpConstantFalse %ty
-     %null = OpConstantNull %ty
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function
-     OpStore %1 %true
-     OpStore %1 %false
-     OpStore %1 %null
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = true;
-x_1 = false;
-x_1 = false;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_StoreUintConst) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %val = OpConstant %ty 42
-     %null = OpConstantNull %ty
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function
-     OpStore %1 %val
-     OpStore %1 %null
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42u;
-x_1 = 0u;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_StoreIntConst) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 1
-     %val = OpConstant %ty 42
-     %null = OpConstantNull %ty
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function
-     OpStore %1 %val
-     OpStore %1 %null
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42;
-x_1 = 0;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_StoreFloatConst) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeFloat 32
-     %val = OpConstant %ty 42
-     %null = OpConstantNull %ty
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function
-     OpStore %1 %val
-     OpStore %1 %null
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42.0;
-x_1 = 0.0;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_LoadBool) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeBool
-     %true = OpConstantTrue %ty
-     %false = OpConstantFalse %ty
-     %null = OpConstantNull %ty
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function %true
-     %2 = OpLoad %ty %1
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_2 : bool = x_1;"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_LoadScalar) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %ty_42 = OpConstant %ty 42
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function %ty_42
-     %2 = OpLoad %ty %1
-     %3 = OpLoad %ty %1
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_2 : u32 = x_1;
-let x_3 : u32 = x_1;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_UseLoadedScalarTwice) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %ty_42 = OpConstant %ty 42
-     %ptr_ty = OpTypePointer Function %ty
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function %ty_42
-     %2 = OpLoad %ty %1
-     OpStore %1 %2
-     OpStore %1 %2
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_2 : u32 = x_1;
-x_1 = x_2;
-x_1 = x_2;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_StoreToModuleScopeVar) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %val = OpConstant %ty 42
-     %ptr_ty = OpTypePointer Private %ty
-     %1 = OpVariable %ptr_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpStore %1 %val
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("x_1 = 42u;"));
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_CopyMemory_Scalar_Function_To_Private) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %val = OpConstant %ty 42
-     %ptr_fn_ty = OpTypePointer Function %ty
-     %ptr_priv_ty = OpTypePointer Private %ty
-     %2 = OpVariable %ptr_priv_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_fn_ty Function
-     OpCopyMemory %2 %1
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const auto* expected = "x_2 = x_1;";
-  EXPECT_THAT(got, HasSubstr(expected));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_NoOperands) {
-  auto err = test::AssembleFailure(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %ty = OpTypeInt 32 0
-     %val = OpConstant %ty 42
-     %ptr_ty = OpTypePointer Private %ty
-     %1 = OpVariable %ptr_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %2 = OpAccessChain %ptr_ty  ; Needs a base operand
-     OpStore %1 %val
-     OpReturn
-  )");
-  EXPECT_THAT(err,
-              Eq("16:5: Expected operand, found next instruction instead."));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_BaseIsNotPointer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %10 = OpTypeInt 32 0
-     %val = OpConstant %10 42
-     %ptr_ty = OpTypePointer Private %10
-     %20 = OpVariable %10 Private ; bad pointer type
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpAccessChain %ptr_ty %20
-     OpStore %1 %val
-     OpReturn
-  )"));
-  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_THAT(p->error(), Eq("variable with ID 20 has non-pointer type 10"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorSwizzle) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %uint_2 = OpConstant %uint 2
-     %uint_42 = OpConstant %uint 42
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_2
-     OpStore %2 %uint_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar.z = 42u;"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorConstOutOfBounds) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %42 = OpConstant %uint 42
-     %uint_99 = OpConstant %uint 99
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %42
-     OpStore %2 %uint_99
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(), Eq("Access chain %2 index %42 value 42 is out of "
-                             "bounds for vector of 4 elements"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorNonConstIndex) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpName %13 "a_dynamic_index"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %uint_2 = OpConstant %uint 2
-     %uint_42 = OpConstant %uint 42
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %10 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %11 = OpLoad %store_ty %10
-     %12 = OpCompositeExtract %uint %11 2
-     %13 = OpCopyObject %uint %12
-     %2 = OpAccessChain %elem_ty %1 %13
-     OpStore %2 %uint_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar[a_dynamic_index] = 42u;"));
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_AccessChain_VectorComponent_MultiUse) {
-  // WGSL does not support pointer-to-vector-component, so test that we sink
-  // these pointers into the point of use.
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %uint_2 = OpConstant %uint 2
-     %uint_42 = OpConstant %uint 42
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %ptr = OpAccessChain %elem_ty %1 %uint_2
-     %load = OpLoad %uint %ptr
-     %result = OpIAdd %uint %load %uint_2
-     OpStore %ptr %result
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto wgsl = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
-  EXPECT_THAT(wgsl, HasSubstr(" = myvar.z;"));
-  EXPECT_THAT(wgsl, HasSubstr("myvar.z = "));
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_AccessChain_VectorComponent_MultiUse_NonConstIndex) {
-  // WGSL does not support pointer-to-vector-component, so test that we sink
-  // these pointers into the point of use.
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %uint_2 = OpConstant %uint 2
-     %uint_42 = OpConstant %uint 42
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %2 = OpVariable %elem_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %idx = OpLoad %uint %2
-     %ptr = OpAccessChain %elem_ty %1 %idx
-     %load = OpLoad %uint %ptr
-     %result = OpIAdd %uint %load %uint_2
-     OpStore %ptr %result
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto wgsl = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
-  EXPECT_THAT(wgsl, HasSubstr(" = myvar[x_12];"));
-  EXPECT_THAT(wgsl, HasSubstr("myvar[x_12] = "));
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_AccessChain_VectorComponent_SinkThroughChain) {
-  // Test that we can sink a pointer-to-vector-component through a chain of
-  // instructions that propagate it.
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-     %store_ty = OpTypeVector %uint 4
-     %uint_2 = OpConstant %uint 2
-     %uint_42 = OpConstant %uint 42
-     %elem_ty = OpTypePointer Private %uint
-     %var_ty = OpTypePointer Private %store_ty
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %ptr = OpAccessChain %elem_ty %1 %uint_2
-     %ptr2 = OpCopyObject %elem_ty %ptr
-     %ptr3 = OpInBoundsAccessChain %elem_ty %ptr2
-     %ptr4 = OpAccessChain %elem_ty %ptr3
-     %load = OpLoad %uint %ptr3
-     %result = OpIAdd %uint %load %uint_2
-     OpStore %ptr4 %result
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  auto wgsl = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
-  EXPECT_THAT(wgsl, HasSubstr(" = myvar.z;"));
-  EXPECT_THAT(wgsl, HasSubstr("myvar.z = "));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Matrix) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v4float = OpTypeVector %float 4
-     %m3v4float = OpTypeMatrix %v4float 3
-     %elem_ty = OpTypePointer Private %v4float
-     %var_ty = OpTypePointer Private %m3v4float
-     %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %float_42 = OpConstant %float 42
-     %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_2
-     OpStore %2 %v4float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Array) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v4float = OpTypeVector %float 4
-     %m3v4float = OpTypeMatrix %v4float 3
-     %elem_ty = OpTypePointer Private %v4float
-     %var_ty = OpTypePointer Private %m3v4float
-     %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %float_42 = OpConstant %float 42
-     %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_2
-     OpStore %2 %v4float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpMemberName %strct 1 "age"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %float_42 = OpConstant %float 42
-     %strct = OpTypeStruct %float %float
-     %elem_ty = OpTypePointer Private %float
-     %var_ty = OpTypePointer Private %strct
-     %uint = OpTypeInt 32 0
-     %uint_1 = OpConstant %uint 1
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_1
-     OpStore %2 %float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar.age = 42.0;"));
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_AccessChain_Struct_DifferOnlyMemberName) {
-  // The spirv-opt internal representation will map both structs to the
-  // same canonicalized type, because it doesn't care about member names.
-  // But we care about member names when producing a member-access expression.
-  // crbug.com/tint/213
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpName %10 "myvar2"
-     OpMemberName %strct 1 "age"
-     OpMemberName %strct2 1 "ancientness"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %float_42 = OpConstant %float 42
-     %float_420 = OpConstant %float 420
-     %strct = OpTypeStruct %float %float
-     %strct2 = OpTypeStruct %float %float
-     %elem_ty = OpTypePointer Private %float
-     %var_ty = OpTypePointer Private %strct
-     %var2_ty = OpTypePointer Private %strct2
-     %uint = OpTypeInt 32 0
-     %uint_1 = OpConstant %uint 1
-
-     %1 = OpVariable %var_ty Private
-     %10 = OpVariable %var2_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_1
-     OpStore %2 %float_42
-     %20 = OpAccessChain %elem_ty %10 %uint_1
-     OpStore %20 %float_420
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(myvar.age = 42.0;
-myvar2.ancientness = 420.0;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_StructNonConstIndex) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpMemberName %55 1 "age"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %float_42 = OpConstant %float 42
-     %55 = OpTypeStruct %float %float
-     %elem_ty = OpTypePointer Private %float
-     %var_ty = OpTypePointer Private %55
-     %uint = OpTypeInt 32 0
-     %uint_1 = OpConstant %uint 1
-     %uint_ptr = OpTypePointer Private %uint
-     %uintvar = OpVariable %uint_ptr Private
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %10 = OpLoad %uint %uintvar
-     %2 = OpAccessChain %elem_ty %1 %10
-     OpStore %2 %float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(), Eq("Access chain %2 index %10 is a non-constant "
-                             "index into a structure %55"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_StructConstOutOfBounds) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpMemberName %55 1 "age"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %float_42 = OpConstant %float 42
-     %55 = OpTypeStruct %float %float
-     %elem_ty = OpTypePointer Private %float
-     %var_ty = OpTypePointer Private %55
-     %uint = OpTypeInt 32 0
-     %uint_99 = OpConstant %uint 99
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_99
-     OpStore %2 %float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(), Eq("Access chain %2 index value 99 is out of bounds "
-                             "for structure %55 having 2 members"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct_RuntimeArray) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     OpMemberName %strct 1 "age"
-
-     OpDecorate %1 DescriptorSet 0
-     OpDecorate %1 Binding 0
-     OpDecorate %strct BufferBlock
-     OpMemberDecorate %strct 0 Offset 0
-     OpMemberDecorate %strct 1 Offset 4
-     OpDecorate %rtarr ArrayStride 4
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %float_42 = OpConstant %float 42
-     %rtarr = OpTypeRuntimeArray %float
-     %strct = OpTypeStruct %float %rtarr
-     %elem_ty = OpTypePointer Uniform %float
-     %var_ty = OpTypePointer Uniform %strct
-     %uint = OpTypeInt 32 0
-     %uint_1 = OpConstant %uint 1
-     %uint_2 = OpConstant %uint 2
-
-     %1 = OpVariable %var_ty Uniform
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_1 %uint_2
-     OpStore %2 %float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar.age[2u] = 42.0;"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Compound_Matrix_Vector) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v4float = OpTypeVector %float 4
-     %m3v4float = OpTypeMatrix %v4float 3
-     %elem_ty = OpTypePointer Private %float
-     %var_ty = OpTypePointer Private %m3v4float
-     %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_3 = OpConstant %uint 3
-     %float_42 = OpConstant %float 42
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_2 %uint_3
-     OpStore %2 %float_42
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody());
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar[2u].w = 42.0;"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_InvalidPointeeType) {
-  const std::string assembly = Preamble() + R"(
-     OpName %1 "myvar"
-     %55 = OpTypeVoid
-     %voidfn = OpTypeFunction %55
-     %float = OpTypeFloat 32
-     %60 = OpTypePointer Private %55
-     %var_ty = OpTypePointer Private %60
-     %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-
-     %1 = OpVariable %var_ty Private
-     %100 = OpFunction %55 None %voidfn
-     %entry = OpLabel
-     %2 = OpAccessChain %60 %1 %uint_2
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Access chain with unknown or invalid pointee type "
-                        "%60: %60 = OpTypePointer Private %55"));
-}
-
-TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_DereferenceBase) {
-  // The base operand to OpAccessChain may have to be dereferenced first.
-  // crbug.com/tint/737
-  const std::string assembly = Preamble() + R"(
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %uint = OpTypeInt 32 0
-     %v2uint = OpTypeVector %uint 2
-     %elem_ty = OpTypePointer Private %uint
-     %vec_ty = OpTypePointer Private %v2uint
-
-     %ptrfn = OpTypeFunction %void %vec_ty
-
-     %uint_0 = OpConstant %uint 0
-
-     ; The shortest way to make a pointer example is as a function parameter.
-     %200 = OpFunction %void None %ptrfn
-     %1 = OpFunctionParameter %vec_ty
-     %entry = OpLabel
-     %2 = OpAccessChain %elem_ty %1 %uint_0
-     %3 = OpLoad %uint %2
-     OpReturn
-     OpFunctionEnd
-
-     %100 = OpFunction %void None %voidfn
-     %main_entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(fn x_200(x_1 : ptr<private, vec2<u32>>) {
-  let x_3 : u32 = (*(x_1)).x;
-  return;
-}
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main() {
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvParserMemoryTest,
-       EmitStatement_AccessChain_InferFunctionStorageClass) {
-  // An access chain can have no indices. When the base is a Function variable,
-  // the reference type has no explicit storage class in the AST representation.
-  // But the pointer type for the let declaration must have an explicit
-  // 'function' storage class. From crbug.com/tint/807
-  const std::string assembly = R"(
-OpCapability Shader
-OpMemoryModel Logical Simple
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-
-%uint = OpTypeInt 32 0
-%ptr_ty = OpTypePointer Function %uint
-
-  %void = OpTypeVoid
-%voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
- %entry = OpLabel
-     %1 = OpVariable %ptr_ty Function
-     %2 = OpAccessChain %ptr_ty %1
-          OpReturn
-          OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly;
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(fn main_1() {
-  var x_1 : u32;
-  let x_2 : ptr<function, u32> = &(x_1);
-  return;
-}
-
-@stage(fragment)
-fn main() {
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-std::string OldStorageBufferPreamble() {
-  return Preamble() + R"(
-     OpName %myvar "myvar"
-
-     OpDecorate %myvar DescriptorSet 0
-     OpDecorate %myvar Binding 0
-
-     OpDecorate %struct BufferBlock
-     OpMemberDecorate %struct 0 Offset 0
-     OpMemberDecorate %struct 1 Offset 4
-     OpDecorate %arr ArrayStride 4
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-
-     %uint_0 = OpConstant %uint 0
-     %uint_1 = OpConstant %uint 1
-
-     %arr = OpTypeRuntimeArray %uint
-     %struct = OpTypeStruct %uint %arr
-     %ptr_struct = OpTypePointer Uniform %struct
-     %ptr_uint = OpTypePointer Uniform %uint
-
-     %myvar = OpVariable %ptr_struct Uniform
-  )";
-}
-
-TEST_F(SpvParserMemoryTest, RemapStorageBuffer_TypesAndVarDeclarations) {
-  // Enusure we get the right module-scope declaration.  This tests translation
-  // of the structure type, arrays of the structure, pointers to them, and
-  // OpVariable of these.
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  ; The preamble declared %100 to be an entry point, so supply it.
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(type RTArr = @stride(4) array<u32>;
-
-struct S {
-  field0 : u32;
-  field1 : RTArr;
-}
-
-@group(0) @binding(0) var<storage, read_write> myvar : S;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded) {
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  ; the scalar element
-  %1 = OpAccessChain %ptr_uint %myvar %uint_0
-  OpStore %1 %uint_0
-
-  ; element in the runtime array
-  %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
-  OpStore %2 %uint_0
-
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(R"(myvar.field0 = 0u;
-myvar.field1[1u] = 0u;
-)"));
-}
-
-TEST_F(SpvParserMemoryTest,
-       RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain) {
-  // Like the previous test, but using OpInBoundsAccessChain.
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  ; the scalar element
-  %1 = OpInBoundsAccessChain %ptr_uint %myvar %uint_0
-  OpStore %1 %uint_0
-
-  ; element in the runtime array
-  %2 = OpInBoundsAccessChain %ptr_uint %myvar %uint_1 %uint_1
-  OpStore %2 %uint_0
-
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(R"(myvar.field0 = 0u;
-myvar.field1[1u] = 0u;
-)")) << got
-     << p->error();
-}
-
-TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_Cascaded) {
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  %ptr_rtarr = OpTypePointer Uniform %arr
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  ; get the runtime array
-  %1 = OpAccessChain %ptr_rtarr %myvar %uint_1
-  ; now an element in it
-  %2 = OpAccessChain %ptr_uint %1 %uint_1
-  OpStore %2 %uint_0
-
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("myvar.field1[1u] = 0u;"))
-      << p->error();
-}
-
-TEST_F(SpvParserMemoryTest,
-       RemapStorageBuffer_ThroughCopyObject_WithoutHoisting) {
-  // Generates a const declaration directly.
-  // We have to do a bunch of storage class tracking for locally
-  // defined values in order to get the right pointer-to-storage-buffer
-  // value type for the const declration.
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-
-  %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
-  %2 = OpCopyObject %ptr_uint %1
-  OpStore %2 %uint_0
-
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_2 : ptr<storage, u32> = &(myvar.field1[1u]);
-*(x_2) = 0u;
-)")) << p->error();
-
-  p->SkipDumpingPending(
-      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
-}
-
-TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughCopyObject_WithHoisting) {
-  // TODO(dneto): Hoisting non-storable values (pointers) is not yet supported.
-  // It's debatable whether this test should run at all.
-  // crbug.com/tint/98
-
-  // Like the previous test, but the declaration for the copy-object
-  // has its declaration hoisted.
-  const auto assembly = OldStorageBufferPreamble() + R"(
-  %bool = OpTypeBool
-  %cond = OpConstantTrue %bool
-
-  %100 = OpFunction %void None %voidfn
-
-  %entry = OpLabel
-  OpSelectionMerge %99 None
-  OpBranchConditional %cond %20 %30
-
-  %20 = OpLabel
-  %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
-  ; this definintion dominates the use in %99
-  %2 = OpCopyObject %ptr_uint %1
-  OpBranch %99
-
-  %30 = OpLabel
-  OpReturn
-
-  %99 = OpLabel
-  OpStore %2 %uint_0
-  OpReturn
-
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_EQ(test::ToString(p->program(), ast_body),
-            R"(var x_2 : ptr<storage, u32>;
-if (true) {
-  x_2 = &(myvar.field1[1u]);
-} else {
-  return;
-}
-x_2 = 0u;
-return;
-)") << p->error();
-  p->SkipDumpingPending("crbug.com/tint/98");
-}
-
-TEST_F(SpvParserMemoryTest, DISABLED_RemapStorageBuffer_ThroughFunctionCall) {
-  // WGSL does not support pointer-to-storage-buffer as function parameter
-}
-TEST_F(SpvParserMemoryTest,
-       DISABLED_RemapStorageBuffer_ThroughFunctionParameter) {
-  // WGSL does not support pointer-to-storage-buffer as function parameter
-}
-
-std::string RuntimeArrayPreamble() {
-  return R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-
-     OpName %myvar "myvar"
-     OpMemberName %struct 0 "first"
-     OpMemberName %struct 1 "rtarr"
-
-     OpDecorate %struct Block
-     OpMemberDecorate %struct 0 Offset 0
-     OpMemberDecorate %struct 1 Offset 4
-     OpDecorate %arr ArrayStride 4
-
-     OpDecorate %myvar DescriptorSet 0
-     OpDecorate %myvar Binding 0
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %uint = OpTypeInt 32 0
-
-     %uint_0 = OpConstant %uint 0
-     %uint_1 = OpConstant %uint 1
-
-     %arr = OpTypeRuntimeArray %uint
-     %struct = OpTypeStruct %uint %arr
-     %ptr_struct = OpTypePointer StorageBuffer %struct
-     %ptr_uint = OpTypePointer StorageBuffer %uint
-
-     %myvar = OpVariable %ptr_struct StorageBuffer
-  )";
-}
-
-TEST_F(SpvParserMemoryTest, ArrayLength_FromVar) {
-  const auto assembly = RuntimeArrayPreamble() + R"(
-
-  %100 = OpFunction %void None %voidfn
-
-  %entry = OpLabel
-  %1 = OpArrayLength %uint %myvar 1
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str,
-              HasSubstr("let x_1 : u32 = arrayLength(&(myvar.rtarr));"))
-      << body_str;
-}
-
-TEST_F(SpvParserMemoryTest, ArrayLength_FromCopyObject) {
-  const auto assembly = RuntimeArrayPreamble() + R"(
-
-  %100 = OpFunction %void None %voidfn
-
-  %entry = OpLabel
-  %2 = OpCopyObject %ptr_struct %myvar
-  %1 = OpArrayLength %uint %2 1
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str, HasSubstr(R"(let x_2 : ptr<storage, S> = &(myvar);
-let x_1 : u32 = arrayLength(&((*(x_2)).rtarr));
-)")) << body_str;
-
-  p->SkipDumpingPending(
-      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
-}
-
-TEST_F(SpvParserMemoryTest, ArrayLength_FromAccessChain) {
-  const auto assembly = RuntimeArrayPreamble() + R"(
-
-  %100 = OpFunction %void None %voidfn
-
-  %entry = OpLabel
-  %2 = OpAccessChain %ptr_struct %myvar ; no indices
-  %1 = OpArrayLength %uint %2 1
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto body_str = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(body_str,
-              HasSubstr("let x_1 : u32 = arrayLength(&(myvar.rtarr));"))
-      << body_str;
-}
-
-std::string InvalidPointerPreamble() {
-  return R"(
-OpCapability Shader
-OpMemoryModel Logical Simple
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-
-%uint = OpTypeInt 32 0
-%ptr_ty = OpTypePointer Function %uint
-
-  %void = OpTypeVoid
-%voidfn = OpTypeFunction %void
-)";
-}
-
-TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_ModuleScope_IsError) {
-  const std::string assembly = InvalidPointerPreamble() + R"(
- %ptr = OpUndef %ptr_ty
-
-  %main = OpFunction %void None %voidfn
- %entry = OpLabel
-     %1 = OpCopyObject %ptr_ty %ptr
-     %2 = OpAccessChain %ptr_ty %ptr
-     %3 = OpInBoundsAccessChain %ptr_ty %ptr
-; now show the invalid pointer propagates
-     %10 = OpCopyObject %ptr_ty %1
-     %20 = OpAccessChain %ptr_ty %2
-     %30 = OpInBoundsAccessChain %ptr_ty %3
-          OpReturn
-          OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_EQ(p->error(), "undef pointer is not valid: %9 = OpUndef %6");
-}
-
-TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_FunctionScope_IsError) {
-  const std::string assembly = InvalidPointerPreamble() + R"(
-
-  %main = OpFunction %void None %voidfn
- %entry = OpLabel
-   %ptr = OpUndef %ptr_ty
-          OpReturn
-          OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_EQ(p->error(), "undef pointer is not valid: %7 = OpUndef %3");
-}
-
-TEST_F(SpvParserMemoryTest, InvalidPointer_ConstantNull_IsError) {
-  // OpConstantNull on logical pointer requires variable-pointers, which
-  // is not (yet) supported by WGSL features.
-  const std::string assembly = InvalidPointerPreamble() + R"(
- %ptr = OpConstantNull %ptr_ty
-
-  %main = OpFunction %void None %voidfn
- %entry = OpLabel
-     %1 = OpCopyObject %ptr_ty %ptr
-     %2 = OpAccessChain %ptr_ty %ptr
-     %3 = OpInBoundsAccessChain %ptr_ty %ptr
-; now show the invalid pointer propagates
-     %10 = OpCopyObject %ptr_ty %1
-     %20 = OpAccessChain %ptr_ty %2
-     %30 = OpInBoundsAccessChain %ptr_ty %3
-          OpReturn
-          OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_EQ(p->error(), "null pointer is not valid: %9 = OpConstantNull %6");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_misc_test.cc b/src/reader/spirv/function_misc_test.cc
deleted file mode 100644
index 2165b92..0000000
--- a/src/reader/spirv/function_misc_test.cc
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-std::string Preamble() {
-  return R"(
-   OpCapability Shader
-   OpMemoryModel Logical Simple
-   OpEntryPoint Fragment %100 "main"
-   OpExecutionMode %100 OriginUpperLeft
-)";
-}
-
-std::string CommonTypes() {
-  return R"(
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-
-  %bool = OpTypeBool
-  %uint = OpTypeInt 32 0
-  %int = OpTypeInt 32 1
-  %float = OpTypeFloat 32
-
-  %v2bool = OpTypeVector %bool 2
-  %v2uint = OpTypeVector %uint 2
-  %v2int = OpTypeVector %int 2
-  %v2float = OpTypeVector %float 2
-)";
-}
-
-using SpvParserTestMiscInstruction = SpvParserTest;
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_BeforeFunction_Scalar) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %1 = OpUndef %bool
-     %2 = OpUndef %uint
-     %3 = OpUndef %int
-     %4 = OpUndef %float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %11 = OpCopyObject %bool %1
-     %12 = OpCopyObject %uint %2
-     %13 = OpCopyObject %int %3
-     %14 = OpCopyObject %float %4
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_11 : bool = false;
-let x_12 : u32 = 0u;
-let x_13 : i32 = 0;
-let x_14 : f32 = 0.0;
-)"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_BeforeFunction_Vector) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %4 = OpUndef %v2bool
-     %1 = OpUndef %v2uint
-     %2 = OpUndef %v2int
-     %3 = OpUndef %v2float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %14 = OpCopyObject %v2bool %4
-     %11 = OpCopyObject %v2uint %1
-     %12 = OpCopyObject %v2int %2
-     %13 = OpCopyObject %v2float %3
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_14 : vec2<bool> = vec2<bool>();
-let x_11 : vec2<u32> = vec2<u32>();
-let x_12 : vec2<i32> = vec2<i32>();
-let x_13 : vec2<f32> = vec2<f32>();
-)"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Scalar) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpUndef %bool
-     %2 = OpUndef %uint
-     %3 = OpUndef %int
-     %4 = OpUndef %float
-
-     %11 = OpCopyObject %bool %1
-     %12 = OpCopyObject %uint %2
-     %13 = OpCopyObject %int %3
-     %14 = OpCopyObject %float %4
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_11 : bool = false;
-let x_12 : u32 = 0u;
-let x_13 : i32 = 0;
-let x_14 : f32 = 0.0;
-)"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Vector) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpUndef %v2uint
-     %2 = OpUndef %v2int
-     %3 = OpUndef %v2float
-
-     %11 = OpCopyObject %v2uint %1
-     %12 = OpCopyObject %v2int %2
-     %13 = OpCopyObject %v2float %3
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(let x_11 : vec2<u32> = vec2<u32>();
-let x_12 : vec2<i32> = vec2<i32>();
-let x_13 : vec2<f32> = vec2<f32>();
-)"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Matrix) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %mat = OpTypeMatrix %v2float 2
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpUndef %mat
-
-     %11 = OpCopyObject %mat %1
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_11 : mat2x2<f32> = mat2x2<f32>();"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Array) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %uint_2 = OpConstant %uint 2
-     %arr = OpTypeArray %uint %uint_2
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpUndef %arr
-
-     %11 = OpCopyObject %arr %1
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_11 : array<u32, 2u> = array<u32, 2u>();"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Struct) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %strct = OpTypeStruct %bool %uint %int %float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpUndef %strct
-
-     %11 = OpCopyObject %strct %1
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("let x_11 : S = S(false, 0u, 0, 0.0);"));
-}
-
-TEST_F(SpvParserTestMiscInstruction, OpNop) {
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpNop
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  EXPECT_EQ(test::ToString(p->program(), ast_body), "return;\n");
-}
-
-// Test swizzle generation.
-
-struct SwizzleCase {
-  uint32_t index;
-  std::string expected_expr;
-  std::string expected_error;
-};
-using SpvParserSwizzleTest =
-    SpvParserTestBase<::testing::TestWithParam<SwizzleCase>>;
-
-TEST_P(SpvParserSwizzleTest, Sample) {
-  // We need a function so we can get a FunctionEmitter.
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-
-  auto* result = fe.Swizzle(GetParam().index);
-  if (GetParam().expected_error.empty()) {
-    Program program(p->program());
-    EXPECT_TRUE(fe.success());
-    ASSERT_NE(result, nullptr);
-    auto got = test::ToString(program, result);
-    EXPECT_EQ(got, GetParam().expected_expr);
-  } else {
-    EXPECT_EQ(result, nullptr);
-    EXPECT_FALSE(fe.success());
-    EXPECT_EQ(p->error(), GetParam().expected_error);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ValidIndex,
-    SpvParserSwizzleTest,
-    ::testing::ValuesIn(std::vector<SwizzleCase>{
-        {0, "x", ""},
-        {1, "y", ""},
-        {2, "z", ""},
-        {3, "w", ""},
-        {4, "", "vector component index is larger than 3: 4"},
-        {99999, "", "vector component index is larger than 3: 99999"}}));
-
-TEST_F(SpvParserTest, ValueFromBlockNotInBlockOrder) {
-  // crbug.com/tint/804
-  const auto assembly = Preamble() + CommonTypes() + R"(
-     %float_42 = OpConstant %float 42.0
-     %cond = OpUndef %bool
-
-     %100 = OpFunction %void None %voidfn
-     %10 = OpLabel
-     OpBranch %30
-
-     ; unreachable
-     %20 = OpLabel
-     %499 = OpFAdd %float %float_42 %float_42
-     %500 = OpFAdd %float %499 %float_42
-     OpBranch %25
-
-     %25 = OpLabel
-     OpBranch %80
-
-
-     %30 = OpLabel
-     OpLoopMerge %90 %80 None
-     OpBranchConditional %cond %90 %40
-
-     %40 = OpLabel
-     OpBranch %90
-
-     %80 = OpLabel ; unreachable continue target
-                ; but "dominated" by %20 and %25
-     %81 = OpFMul %float %500 %float_42 ; %500 is defined in %20
-     OpBranch %30 ; backedge
-
-     %90 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr("let x_81 : f32 = (0.0 * 42.0);"));
-}
-
-// TODO(dneto): OpSizeof : requires Kernel (OpenCL)
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
deleted file mode 100644
index eb1d179..0000000
--- a/src/reader/spirv/function_var_test.cc
+++ /dev/null
@@ -1,1675 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-
-/// @returns a SPIR-V assembly segment which assigns debug names
-/// to particular IDs.
-std::string Names(std::vector<std::string> ids) {
-  std::ostringstream outs;
-  for (auto& id : ids) {
-    outs << "    OpName %" << id << " \"" << id << "\"\n";
-  }
-  return outs.str();
-}
-
-std::string CommonTypes() {
-  return
-      R"(
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-
-    %bool = OpTypeBool
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-
-    %ptr_bool = OpTypePointer Function %bool
-    %ptr_float = OpTypePointer Function %float
-    %ptr_uint = OpTypePointer Function %uint
-    %ptr_int = OpTypePointer Function %int
-
-    %true = OpConstantTrue %bool
-    %false = OpConstantFalse %bool
-    %float_0 = OpConstant %float 0.0
-    %float_1p5 = OpConstant %float 1.5
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %int_m1 = OpConstant %int -1
-    %int_0 = OpConstant %int 0
-    %int_1 = OpConstant %int 1
-    %int_3 = OpConstant %int 3
-    %uint_2 = OpConstant %uint 2
-    %uint_3 = OpConstant %uint 3
-    %uint_4 = OpConstant %uint 4
-    %uint_5 = OpConstant %uint 5
-
-    %v2int = OpTypeVector %int 2
-    %v2float = OpTypeVector %float 2
-    %m3v2float = OpTypeMatrix %v2float 3
-
-    %v2int_null = OpConstantNull %v2int
-
-    %arr2uint = OpTypeArray %uint %uint_2
-    %strct = OpTypeStruct %uint %float %arr2uint
-  )";
-}
-
-// Returns the SPIR-V assembly for capabilities, the memory model,
-// a vertex shader entry point declaration, and name declarations
-// for specified IDs.
-std::string Caps(std::vector<std::string> ids = {}) {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %100 "main"
-    OpExecutionMode %100 OriginUpperLeft
-)" + Names(ids);
-}
-
-// Returns the SPIR-V assembly for a vertex shader, optionally
-// with OpName decorations for certain SPIR-V IDs
-std::string PreambleNames(std::vector<std::string> ids) {
-  return Caps(ids) + CommonTypes();
-}
-
-std::string Preamble() {
-  return PreambleNames({});
-}
-
-using SpvParserFunctionVarTest = SpvParserTest;
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_AnonymousVars) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %1 = OpVariable %ptr_uint Function
-     %2 = OpVariable %ptr_uint Function
-     %3 = OpVariable %ptr_uint Function
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(var x_1 : u32;
-var x_2 : u32;
-var x_3 : u32;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_NamedVars) {
-  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c"}) + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %a = OpVariable %ptr_uint Function
-     %b = OpVariable %ptr_uint Function
-     %c = OpVariable %ptr_uint Function
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(var a : u32;
-var b : u32;
-var c : u32;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MixedTypes) {
-  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c"}) + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %a = OpVariable %ptr_uint Function
-     %b = OpVariable %ptr_int Function
-     %c = OpVariable %ptr_float Function
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(var a : u32;
-var b : i32;
-var c : f32;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ScalarInitializers) {
-  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c", "d", "e"}) + R"(
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %a = OpVariable %ptr_bool Function %true
-     %b = OpVariable %ptr_bool Function %false
-     %c = OpVariable %ptr_int Function %int_m1
-     %d = OpVariable %ptr_uint Function %uint_1
-     %e = OpVariable %ptr_float Function %float_1p5
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(var a : bool = true;
-var b : bool = false;
-var c : i32 = -1;
-var d : u32 = 1u;
-var e : f32 = 1.5;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ScalarNullInitializers) {
-  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c", "d"}) + R"(
-     %null_bool = OpConstantNull %bool
-     %null_int = OpConstantNull %int
-     %null_uint = OpConstantNull %uint
-     %null_float = OpConstantNull %float
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %a = OpVariable %ptr_bool Function %null_bool
-     %b = OpVariable %ptr_int Function %null_int
-     %c = OpVariable %ptr_uint Function %null_uint
-     %d = OpVariable %ptr_float Function %null_float
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr(R"(var a : bool = false;
-var b : i32 = 0;
-var c : u32 = 0u;
-var d : f32 = 0.0;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_VectorInitializer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %v2float
-     %two = OpConstant %float 2.0
-     %const = OpConstantComposite %v2float %float_1p5 %two
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MatrixInitializer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %m3v2float
-     %two = OpConstant %float 2.0
-     %three = OpConstant %float 3.0
-     %four = OpConstant %float 4.0
-     %v0 = OpConstantComposite %v2float %float_1p5 %two
-     %v1 = OpConstantComposite %v2float %two %three
-     %v2 = OpConstantComposite %v2float %three %four
-     %const = OpConstantComposite %m3v2float %v0 %v1 %v2
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : mat3x2<f32> = mat3x2<f32>("
-                        "vec2<f32>(1.5, 2.0), "
-                        "vec2<f32>(2.0, 3.0), "
-                        "vec2<f32>(3.0, 4.0));"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %arr2uint
-     %two = OpConstant %uint 2
-     %const = OpConstantComposite %arr2uint %uint_1 %two
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(
-      test::ToString(p->program(), ast_body),
-      HasSubstr("var x_200 : array<u32, 2u> = array<u32, 2u>(1u, 2u);"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer_Alias) {
-  auto p = parser(test::Assemble(R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-     OpDecorate %arr2uint ArrayStride 16
-)" + CommonTypes() + R"(
-     %ptr = OpTypePointer Function %arr2uint
-     %two = OpConstant %uint 2
-     %const = OpConstantComposite %arr2uint %uint_1 %two
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  const char* expect = "var x_200 : Arr = Arr(1u, 2u);\n";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer_Null) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %arr2uint
-     %two = OpConstant %uint 2
-     %const = OpConstantNull %arr2uint
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : array<u32, 2u> = array<u32, 2u>();"));
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitFunctionVariables_ArrayInitializer_Alias_Null) {
-  auto p = parser(test::Assemble(R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-     OpDecorate %arr2uint ArrayStride 16
-)" + CommonTypes() + R"(
-     %ptr = OpTypePointer Function %arr2uint
-     %two = OpConstant %uint 2
-     %const = OpConstantNull %arr2uint
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : Arr = @stride(16) array<u32, 2u>();"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_StructInitializer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %strct
-     %two = OpConstant %uint 2
-     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
-     %const = OpConstantComposite %strct %uint_1 %float_1p5 %arrconst
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"));
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_StructInitializer_Null) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-     %ptr = OpTypePointer Function %strct
-     %two = OpConstant %uint 2
-     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
-     %const = OpConstantNull %strct
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %200 = OpVariable %ptr Function %const
-     OpReturn
-     OpFunctionEnd
-  )"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  EXPECT_THAT(test::ToString(p->program(), ast_body),
-              HasSubstr("var x_200 : S = S(0u, 0.0, array<u32, 2u>());"));
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitFunctionVariables_Decorate_RelaxedPrecision) {
-  // RelaxedPrecisionis dropped
-  const auto assembly = Caps({"myvar"}) + R"(
-     OpDecorate %myvar RelaxedPrecision
-
-     %float = OpTypeFloat 32
-     %ptr = OpTypePointer Function %float
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %myvar = OpVariable %ptr Function
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_EQ(got, "var myvar : f32;\n") << got;
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitFunctionVariables_MemberDecorate_RelaxedPrecision) {
-  // RelaxedPrecisionis dropped
-  const auto assembly = Caps({"myvar", "strct"}) + R"(
-     OpMemberDecorate %strct 0 RelaxedPrecision
-
-     %float = OpTypeFloat 32
-     %strct = OpTypeStruct %float
-     %ptr = OpTypePointer Function %strct
-
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %myvar = OpVariable %ptr Function
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error() << std::endl;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_EQ(got, "var myvar : strct;\n") << got;
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitFunctionVariables_StructDifferOnlyInMemberName) {
-  auto p = parser(test::Assemble(R"(
-      OpCapability Shader
-      OpMemoryModel Logical Simple
-      OpEntryPoint Fragment %100 "main"
-      OpExecutionMode %100 OriginUpperLeft
-      OpName %_struct_5 "S"
-      OpName %_struct_6 "S"
-      OpMemberName %_struct_5 0 "algo"
-      OpMemberName %_struct_6 0 "rithm"
-
-      %void = OpTypeVoid
-      %voidfn = OpTypeFunction %void
-      %uint = OpTypeInt 32 0
-
-      %_struct_5 = OpTypeStruct %uint
-      %_struct_6 = OpTypeStruct %uint
-      %_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
-      %_ptr_Function__struct_6 = OpTypePointer Function %_struct_6
-      %100 = OpFunction %void None %voidfn
-      %39 = OpLabel
-      %40 = OpVariable %_ptr_Function__struct_5 Function
-      %41 = OpVariable %_ptr_Function__struct_6 Function
-      OpReturn
-      OpFunctionEnd)"));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitFunctionVariables());
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_THAT(got, HasSubstr(R"(var x_40 : S;
-var x_41 : S_1;
-)"));
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct) {
-  auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %25 = OpVariable %ptr_uint Function
-     %2 = OpIAdd %uint %uint_1 %uint_1
-     OpStore %25 %uint_1 ; Do initial store to mark source location
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %25 %2 ; defer emission of the addition until here.
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect =
-      R"(var x_25 : u32;
-x_25 = 1u;
-x_25 = (1u + 1u);
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_CombinatorialValue_Immediate_UsedTwice) {
-  auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %25 = OpVariable %ptr_uint Function
-     %2 = OpIAdd %uint %uint_1 %uint_1
-     OpStore %25 %uint_1 ; Do initial store to mark source location
-     OpBranch %20
-
-     %20 = OpLabel
-     OpStore %25 %2
-     OpStore %25 %2
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var x_25 : u32;
-let x_2 : u32 = (1u + 1u);
-x_25 = 1u;
-x_25 = x_2;
-x_25 = x_2;
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct) {
-  // Translation should not sink expensive operations into or out of control
-  // flow. As a simple heuristic, don't move *any* combinatorial operation
-  // across any control flow.
-  auto assembly = Preamble() + R"(
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     %25 = OpVariable %ptr_uint Function
-     %2 = OpIAdd %uint %uint_1 %uint_1
-     OpStore %25 %uint_1 ; Do initial store to mark source location
-     OpBranch %20
-
-     %20 = OpLabel  ; Introduce a new construct
-     OpLoopMerge %99 %80 None
-     OpBranch %80
-
-     %80 = OpLabel
-     OpStore %25 %2  ; store combinatorial value %2, inside the loop
-     OpBranch %20
-
-     %99 = OpLabel ; merge block
-     OpStore %25 %uint_2
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var x_25 : u32;
-let x_2 : u32 = (1u + 1u);
-x_25 = 1u;
-loop {
-
-  continuing {
-    x_25 = x_2;
-  }
-}
-x_25 = 2u;
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserFunctionVarTest,
-    EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses) {
-  // Compensate for the difference between dominance and scoping.
-  // Exercise hoisting of the constant definition to before its natural
-  // location.
-  //
-  // The definition of %2 should be hoisted
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %3 = OpLabel
-     OpStore %1 %uint_0
-     OpBranch %5
-
-     %5 = OpLabel
-     OpStore %1 %uint_1
-     OpLoopMerge  %99 %80 None
-     OpBranchConditional %false %99 %20
-
-     %20 = OpLabel
-     OpStore %1 %uint_3
-     OpSelectionMerge %50 None
-     OpBranchConditional %true %30 %40
-
-     %30 = OpLabel
-     ; This combinatorial definition in nested control flow dominates
-     ; the use in the merge block in %50
-     %2 = OpIAdd %uint %uint_1 %uint_1
-     OpBranch %50
-
-     %40 = OpLabel
-     OpReturn
-
-     %50 = OpLabel ; merge block for if-selection
-     OpStore %1 %2
-     OpBranch %80
-
-     %80 = OpLabel ; merge block
-     OpStore %1 %uint_4
-     OpBranchConditional %false %99 %5 ; loop backedge
-
-     %99 = OpLabel
-     OpStore %1 %uint_5
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(x_1 = 0u;
-loop {
-  var x_2 : u32;
-  x_1 = 1u;
-  if (false) {
-    break;
-  }
-  x_1 = 3u;
-  if (true) {
-    x_2 = (1u + 1u);
-  } else {
-    return;
-  }
-  x_1 = x_2;
-
-  continuing {
-    x_1 = 4u;
-    if (false) {
-      break;
-    }
-  }
-}
-x_1 = 5u;
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserFunctionVarTest,
-    EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction) {
-  // This is a hoisting case, where the definition is in the first block
-  // of an if selection construct. In this case the definition should count
-  // as being in the parent (enclosing) construct.
-  //
-  // The definition of %1 is in an IfSelection construct and also the enclosing
-  // Function construct, both of which start at block %10. For the purpose of
-  // determining the construct containing %10, go to the parent construct of
-  // the IfSelection.
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %200 = OpVariable %pty Private
-     %cond = OpConstantTrue %bool
-
-     %100 = OpFunction %void None %voidfn
-
-     ; in IfSelection construct, nested in Function construct
-     %10 = OpLabel
-     %1 = OpCopyObject %uint %uint_1
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel  ; in IfSelection construct
-     OpBranch %99
-
-     %99 = OpLabel
-     %3 = OpCopyObject %uint %1; in Function construct
-     OpStore %200 %3
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  // We don't hoist x_1 into its own mutable variable. It is emitted as
-  // a const definition.
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(let x_1 : u32 = 1u;
-if (true) {
-}
-let x_3 : u32 = x_1;
-x_200 = x_3;
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf) {
-  // This is like the previous case, but the IfSelection is nested inside
-  // another IfSelection.
-  // This tests that the hoisting algorithm goes to only one parent of
-  // the definining if-selection block, and doesn't jump all the way out
-  // to the Function construct that encloses everything.
-  //
-  // We should not hoist %1 because its definition should count as being
-  // in the outer IfSelection, not the inner IfSelection.
-  auto assembly = Preamble() + R"(
-
-     %pty = OpTypePointer Private %uint
-     %200 = OpVariable %pty Private
-     %cond = OpConstantTrue %bool
-
-     %100 = OpFunction %void None %voidfn
-
-     ; outer IfSelection
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     ; inner IfSelection
-     %20 = OpLabel
-     %1 = OpCopyObject %uint %uint_1
-     OpSelectionMerge %89 None
-     OpBranchConditional %cond %30 %89
-
-     %30 = OpLabel ; last block of inner IfSelection
-     OpBranch %89
-
-     ; in outer IfSelection
-     %89 = OpLabel
-     %3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
-     OpStore %200 %3
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (true) {
-  let x_1 : u32 = 1u;
-  if (true) {
-  }
-  let x_3 : u32 = x_1;
-  x_200 = x_3;
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(
-    SpvParserFunctionVarTest,
-    EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf) {
-  // This is like the previous case, but the definition is in a SwitchSelection
-  // inside another IfSelection.
-  // Tests that definitions in the first block of a switch count as being
-  // in the parent of the switch construct.
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %200 = OpVariable %pty Private
-     %cond = OpConstantTrue %bool
-
-     %100 = OpFunction %void None %voidfn
-
-     ; outer IfSelection
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     ; inner SwitchSelection
-     %20 = OpLabel
-     %1 = OpCopyObject %uint %uint_1
-     OpSelectionMerge %89 None
-     OpSwitch %uint_1 %89 0 %30
-
-     %30 = OpLabel ; last block of inner SwitchSelection
-     OpBranch %89
-
-     ; in outer IfSelection
-     %89 = OpLabel
-     %3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
-     OpStore %200 %3
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(if (true) {
-  let x_1 : u32 = 1u;
-  switch(1u) {
-    case 0u: {
-    }
-    default: {
-    }
-  }
-  let x_3 : u32 = x_1;
-  x_200 = x_3;
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf) {
-  // In this test, both the defintion and the use are in the first block
-  // of an IfSelection.  No hoisting occurs because hoisting is triggered
-  // on whether the defining construct contains the last use, rather than
-  // whether the two constructs are the same.
-  //
-  // This example has two SSA IDs which are tempting to hoist but should not:
-  //   %1 is defined and used in the first block of an IfSelection.
-  //       Do not hoist it.
-  auto assembly = Preamble() + R"(
-     %cond = OpConstantTrue %bool
-
-     %100 = OpFunction %void None %voidfn
-
-     ; in IfSelection construct, nested in Function construct
-     %10 = OpLabel
-     %1 = OpCopyObject %uint %uint_1
-     %2 = OpCopyObject %uint %1
-     OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
-
-     %20 = OpLabel  ; in IfSelection construct
-     OpBranch %99
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  // We don't hoist x_1 into its own mutable variable. It is emitted as
-  // a const definition.
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(let x_1 : u32 = 1u;
-let x_2 : u32 = x_1;
-if (true) {
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_SingleBlockLoopIndex) {
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %7 = OpVariable %boolpty Private
-     %8 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     ; Use an outer loop to show we put the new variable in the
-     ; smallest enclosing scope.
-     %10 = OpLabel
-     %101 = OpLoad %bool %7
-     %102 = OpLoad %bool %8
-     OpLoopMerge %99 %89 None
-     OpBranchConditional %101 %99 %20
-
-     %20 = OpLabel
-     %2 = OpPhi %uint %uint_0 %10 %4 %20  ; gets computed value
-     %3 = OpPhi %uint %uint_1 %10 %3 %20  ; gets itself
-     %4 = OpIAdd %uint %2 %uint_1
-     OpLoopMerge %79 %20 None
-     OpBranchConditional %102 %79 %20
-
-     %79 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var x_2_phi : u32;
-  var x_3_phi : u32;
-  let x_101 : bool = x_7;
-  let x_102 : bool = x_8;
-  x_2_phi = 0u;
-  x_3_phi = 1u;
-  if (x_101) {
-    break;
-  }
-  loop {
-    let x_2 : u32 = x_2_phi;
-    let x_3 : u32 = x_3_phi;
-    x_2_phi = (x_2 + 1u);
-    x_3_phi = x_3;
-    if (x_102) {
-      break;
-    }
-  }
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_MultiBlockLoopIndex) {
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %7 = OpVariable %boolpty Private
-     %8 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     OpBranch %10
-
-     ; Use an outer loop to show we put the new variable in the
-     ; smallest enclosing scope.
-     %10 = OpLabel
-     %101 = OpLoad %bool %7
-     %102 = OpLoad %bool %8
-     OpLoopMerge %99 %89 None
-     OpBranchConditional %101 %99 %20
-
-     %20 = OpLabel
-     %2 = OpPhi %uint %uint_0 %10 %4 %30  ; gets computed value
-     %3 = OpPhi %uint %uint_1 %10 %3 %30  ; gets itself
-     OpLoopMerge %79 %30 None
-     OpBranchConditional %102 %79 %30
-
-     %30 = OpLabel
-     %4 = OpIAdd %uint %2 %uint_1
-     OpBranch %20
-
-     %79 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel ; continue target for outer loop
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(loop {
-  var x_2_phi : u32;
-  var x_3_phi : u32;
-  let x_101 : bool = x_7;
-  let x_102 : bool = x_8;
-  x_2_phi = 0u;
-  x_3_phi = 1u;
-  if (x_101) {
-    break;
-  }
-  loop {
-    var x_4 : u32;
-    let x_2 : u32 = x_2_phi;
-    let x_3 : u32 = x_3_phi;
-    if (x_102) {
-      break;
-    }
-
-    continuing {
-      x_4 = (x_2 + 1u);
-      x_2_phi = x_4;
-      x_3_phi = x_3;
-    }
-  }
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_Phi_ValueFromLoopBodyAndContinuing) {
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %17 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %9 = OpLabel
-     %101 = OpLoad %bool %17
-     OpBranch %10
-
-     ; Use an outer loop to show we put the new variable in the
-     ; smallest enclosing scope.
-     %10 = OpLabel
-     OpLoopMerge %99 %89 None
-     OpBranch %20
-
-     %20 = OpLabel
-     %2 = OpPhi %uint %uint_0 %10 %4 %30  ; gets computed value
-     %5 = OpPhi %uint %uint_1 %10 %7 %30
-     %4 = OpIAdd %uint %2 %uint_1 ; define %4
-     %6 = OpIAdd %uint %4 %uint_1 ; use %4
-     OpLoopMerge %79 %30 None
-     OpBranchConditional %101 %79 %30
-
-     %30 = OpLabel
-     %7 = OpIAdd %uint %4 %6 ; use %4 again
-     OpBranch %20
-
-     %79 = OpLabel
-     OpBranch %89
-
-     %89 = OpLabel
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
-      << assembly << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(let x_101 : bool = x_17;
-loop {
-  var x_2_phi : u32;
-  var x_5_phi : u32;
-  x_2_phi = 0u;
-  x_5_phi = 1u;
-  loop {
-    var x_7 : u32;
-    let x_2 : u32 = x_2_phi;
-    let x_5 : u32 = x_5_phi;
-    let x_4 : u32 = (x_2 + 1u);
-    let x_6 : u32 = (x_4 + 1u);
-    if (x_101) {
-      break;
-    }
-
-    continuing {
-      x_7 = (x_4 + x_6);
-      x_2_phi = x_4;
-      x_5_phi = x_7;
-    }
-  }
-}
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_FromElseAndThen) {
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %7 = OpVariable %boolpty Private
-     %8 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     %101 = OpLoad %bool %7
-     %102 = OpLoad %bool %8
-     OpBranch %10
-
-     ; Use an outer loop to show we put the new variable in the
-     ; smallest enclosing scope.
-     %10 = OpLabel
-     OpLoopMerge %99 %89 None
-     OpBranchConditional %101 %99 %20
-
-     %20 = OpLabel ; if seleciton
-     OpSelectionMerge %79 None
-     OpBranchConditional %102 %30 %40
-
-     %30 = OpLabel
-     OpBranch %89
-
-     %40 = OpLabel
-     OpBranch %89
-
-     %79 = OpLabel ; disconnected selection merge node
-     OpBranch %89
-
-     %89 = OpLabel
-     %2 = OpPhi %uint %uint_0 %30 %uint_1 %40 %uint_0 %79
-     OpStore %1 %2
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(let x_101 : bool = x_7;
-let x_102 : bool = x_8;
-loop {
-  var x_2_phi : u32;
-  if (x_101) {
-    break;
-  }
-  if (x_102) {
-    x_2_phi = 0u;
-    continue;
-  } else {
-    x_2_phi = 1u;
-    continue;
-  }
-  x_2_phi = 0u;
-
-  continuing {
-    let x_2 : u32 = x_2_phi;
-    x_1 = x_2;
-  }
-}
-return;
-)";
-  EXPECT_EQ(expect, got) << got;
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_FromHeaderAndThen) {
-  auto assembly = Preamble() + R"(
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %7 = OpVariable %boolpty Private
-     %8 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %5 = OpLabel
-     %101 = OpLoad %bool %7
-     %102 = OpLoad %bool %8
-     OpBranch %10
-
-     ; Use an outer loop to show we put the new variable in the
-     ; smallest enclosing scope.
-     %10 = OpLabel
-     OpLoopMerge %99 %89 None
-     OpBranchConditional %101 %99 %20
-
-     %20 = OpLabel ; if seleciton
-     OpSelectionMerge %79 None
-     OpBranchConditional %102 %30 %89
-
-     %30 = OpLabel
-     OpBranch %89
-
-     %79 = OpLabel ; disconnected selection merge node
-     OpUnreachable
-
-     %89 = OpLabel
-     %2 = OpPhi %uint %uint_0 %20 %uint_1 %30
-     OpStore %1 %2
-     OpBranch %10
-
-     %99 = OpLabel
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(let x_101 : bool = x_7;
-let x_102 : bool = x_8;
-loop {
-  var x_2_phi : u32;
-  if (x_101) {
-    break;
-  }
-  x_2_phi = 0u;
-  if (x_102) {
-    x_2_phi = 1u;
-    continue;
-  } else {
-    continue;
-  }
-  return;
-
-  continuing {
-    let x_2 : u32 = x_2_phi;
-    x_1 = x_2;
-  }
-}
-return;
-)";
-  EXPECT_EQ(expect, got) << got;
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase) {
-  // This is the essence of the bug report from crbug.com/tint/495
-  auto assembly = Preamble() + R"(
-     %cond = OpConstantTrue %bool
-     %pty = OpTypePointer Private %uint
-     %1 = OpVariable %pty Private
-     %boolpty = OpTypePointer Private %bool
-     %7 = OpVariable %boolpty Private
-     %8 = OpVariable %boolpty Private
-
-     %100 = OpFunction %void None %voidfn
-
-     %10 = OpLabel
-     OpSelectionMerge %99 None
-     OpSwitch %uint_1 %20 0 %20 1 %30
-
-       %20 = OpLabel ; case 0
-       OpBranch %30 ;; fall through
-
-       %30 = OpLabel ; case 1
-       OpSelectionMerge %50 None
-       OpBranchConditional %true %40 %45
-
-         %40 = OpLabel
-         OpBranch %50
-
-         %45 = OpLabel
-         OpBranch %99 ; break
-
-       %50 = OpLabel ; end the case
-       OpBranch %99
-
-     %99 = OpLabel
-     ; predecessors are all dominated by case construct head at %30
-     %phi = OpPhi %uint %uint_0 %45 %uint_1 %50
-     OpReturn
-
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var x_41_phi : u32;
-switch(1u) {
-  default: {
-    fallthrough;
-  }
-  case 0u: {
-    fallthrough;
-  }
-  case 1u: {
-    if (true) {
-    } else {
-      x_41_phi = 0u;
-      break;
-    }
-    x_41_phi = 1u;
-  }
-}
-let x_41 : u32 = x_41_phi;
-return;
-)";
-  EXPECT_EQ(expect, got) << got << assembly;
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_UseInPhiCountsAsUse) {
-  // From crbug.com/215
-  // If the only use of a combinatorially computed ID is as the value
-  // in an OpPhi, then we still have to emit it.  The algorithm fix
-  // is to always count uses in Phis.
-  // This is the reduced case from the bug report.
-  //
-  // The only use of %12 is in the phi.
-  // The only use of %11 is in %12.
-  // Both definintions need to be emitted to the output.
-  auto assembly = Preamble() + R"(
-        %100 = OpFunction %void None %voidfn
-
-         %10 = OpLabel
-         %11 = OpLogicalAnd %bool %true %true
-         %12 = OpLogicalNot %bool %11  ;
-               OpSelectionMerge %99 None
-               OpBranchConditional %true %20 %99
-
-         %20 = OpLabel
-               OpBranch %99
-
-         %99 = OpLabel
-        %101 = OpPhi %bool %11 %10 %12 %20
-               OpReturn
-
-               OpFunctionEnd
-
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var x_101_phi : bool;
-let x_11 : bool = (true & true);
-let x_12 : bool = !(x_11);
-x_101_phi = x_11;
-if (true) {
-  x_101_phi = x_12;
-}
-let x_101 : bool = x_101_phi;
-return;
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(SpvParserFunctionVarTest,
-       EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored) {
-  // From crbug.com/tint/804
-  const auto assembly = Preamble() + R"(
-     %float_42 = OpConstant %float 42.0
-     %cond = OpUndef %bool
-
-     %100 = OpFunction %void None %voidfn
-     %10 = OpLabel
-     OpBranch %30
-
-     ; unreachable
-     %20 = OpLabel
-     %499 = OpFAdd %float %float_42 %float_42
-     %500 = OpFAdd %float %499 %float_42
-     OpBranch %25
-
-     %25 = OpLabel
-     OpBranch %80
-
-
-     %30 = OpLabel
-     OpLoopMerge %90 %80 None
-     OpBranchConditional %cond %90 %40
-
-     %40 = OpLabel
-     OpBranch %90
-
-     %80 = OpLabel ; unreachable continue target
-                ; but "dominated" by %20 and %25
-     %81 = OpPhi %float %500 %25
-     OpBranch %30 ; backedge
-
-     %90 = OpLabel
-     OpReturn
-     OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  const auto* expected = R"(loop {
-  if (false) {
-    break;
-  }
-  break;
-
-  continuing {
-    var x_81_phi_1 : f32;
-    let x_81 : f32 = x_81_phi_1;
-  }
-}
-return;
-)";
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_EQ(got, expected);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_CompositeInsert) {
-  // From crbug.com/tint/804
-  const auto assembly = Preamble() + R"(
-    %100 = OpFunction %void None %voidfn
-
-    %10 = OpLabel
-    OpSelectionMerge %50 None
-    OpBranchConditional %true %20 %30
-
-      %20 = OpLabel
-      %200 = OpCompositeInsert %v2int %int_0 %v2int_null 0
-      OpBranch %50
-
-      %30 = OpLabel
-      OpReturn
-
-    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
-    %201 = OpCopyObject %v2int %200
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  const auto* expected = R"(var x_200 : vec2<i32>;
-if (true) {
-  x_200 = vec2<i32>();
-  x_200.x = 0;
-} else {
-  return;
-}
-let x_201 : vec2<i32> = x_200;
-return;
-)";
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  EXPECT_EQ(got, expected);
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_VectorInsertDynamic) {
-  // Spawned from crbug.com/tint/804
-  const auto assembly = Preamble() + R"(
-    %100 = OpFunction %void None %voidfn
-
-    %10 = OpLabel
-    OpSelectionMerge %50 None
-    OpBranchConditional %true %20 %30
-
-      %20 = OpLabel
-      %200 = OpVectorInsertDynamic %v2int %v2int_null %int_3 %int_1
-      OpBranch %50
-
-      %30 = OpLabel
-      OpReturn
-
-    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
-    %201 = OpCopyObject %v2int %200
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const auto* expected = R"(var x_200 : vec2<i32>;
-if (true) {
-  x_200 = vec2<i32>();
-  x_200[1] = 3;
-} else {
-  return;
-}
-let x_201 : vec2<i32> = x_200;
-return;
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_UsedAsNonPtrArg) {
-  // Spawned from crbug.com/tint/804
-  const auto assembly = Preamble() + R"(
-    %fn_int = OpTypeFunction %void %int
-
-    %500 = OpFunction %void None %fn_int
-    %501 = OpFunctionParameter %int
-    %502 = OpLabel
-    OpReturn
-    OpFunctionEnd
-
-    %100 = OpFunction %void None %voidfn
-
-    %10 = OpLabel
-    OpSelectionMerge %50 None
-    OpBranchConditional %true %20 %30
-
-      %20 = OpLabel
-      %200 = OpCopyObject %int %int_1
-      OpBranch %50
-
-      %30 = OpLabel
-      OpReturn
-
-    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
-    %201 = OpFunctionCall %void %500 %200
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const auto* expected = R"(var x_200 : i32;
-if (true) {
-  x_200 = 1;
-} else {
-  return;
-}
-x_500(x_200);
-return;
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvParserFunctionVarTest, DISABLED_EmitStatement_Hoist_UsedAsPtrArg) {
-  // Spawned from crbug.com/tint/804
-  // Blocked by crbug.com/tint/98: hoisting pointer types
-  const auto assembly = Preamble() + R"(
-
-    %fn_int = OpTypeFunction %void %ptr_int
-
-    %500 = OpFunction %void None %fn_int
-    %501 = OpFunctionParameter %ptr_int
-    %502 = OpLabel
-    OpReturn
-    OpFunctionEnd
-
-    %100 = OpFunction %void None %voidfn
-
-    %10 = OpLabel
-    %199 = OpVariable %ptr_int Function
-    OpSelectionMerge %50 None
-    OpBranchConditional %true %20 %30
-
-      %20 = OpLabel
-      %200 = OpCopyObject %ptr_int %199
-      OpBranch %50
-
-      %30 = OpLabel
-      OpReturn
-
-    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
-    %201 = OpFunctionCall %void %500 %200
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  const auto* expected = R"(xxxxxxxxxxxxxxxxxxxxx)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/namer.cc b/src/reader/spirv/namer.cc
deleted file mode 100644
index c2b2e98..0000000
--- a/src/reader/spirv/namer.cc
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 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/reader/spirv/namer.h"
-
-#include <algorithm>
-#include <sstream>
-#include <unordered_set>
-
-#include "src/debug.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-namespace {
-
-const char* kWGSLReservedWords[] = {
-    // Please keep this list sorted
-    "array",      "as",          "asm",
-    "bf16",       "binding",     "block",
-    "bool",       "break",       "builtin",
-    "case",       "cast",        "compute",
-    "const",      "continue",    "default",
-    "discard",    "do",          "else",
-    "elseif",     "entry_point", "enum",
-    "f16",        "f32",         "fallthrough",
-    "false",      "fn",          "for",
-    "fragment",   "i16",         "i32",
-    "i64",        "i8",          "if",
-    "image",      "import",      "in",
-    "let",        "location",    "loop",
-    "mat2x2",     "mat2x3",      "mat2x4",
-    "mat3x2",     "mat3x3",      "mat3x4",
-    "mat4x2",     "mat4x3",      "mat4x4",
-    "offset",     "out",         "override",
-    "premerge",   "private",     "ptr",
-    "regardless", "return",      "set",
-    "storage",    "struct",      "switch",
-    "true",       "type",        "typedef",
-    "u16",        "u32",         "u64",
-    "u8",         "uniform",     "uniform_constant",
-    "unless",     "using",       "var",
-    "vec2",       "vec3",        "vec4",
-    "vertex",     "void",        "while",
-    "workgroup",
-};
-
-}  // namespace
-
-Namer::Namer(const FailStream& fail_stream) : fail_stream_(fail_stream) {
-  for (const auto* reserved : kWGSLReservedWords) {
-    name_to_id_[std::string(reserved)] = 0;
-  }
-}
-
-Namer::~Namer() = default;
-
-std::string Namer::Sanitize(const std::string& suggested_name) {
-  if (suggested_name.empty()) {
-    return "empty";
-  }
-  // Otherwise, replace invalid characters by '_'.
-  std::string result;
-  std::string invalid_as_first_char = "_0123456789";
-  std::string valid =
-      "abcdefghijklmnopqrstuvwxyz"
-      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-      "_0123456789";
-  // If the first character is invalid for starting a WGSL identifier, then
-  // prefix the result with "x".
-  if ((std::string::npos != invalid_as_first_char.find(suggested_name[0])) ||
-      (std::string::npos == valid.find(suggested_name[0]))) {
-    result = "x";
-  }
-  std::transform(suggested_name.begin(), suggested_name.end(),
-                 std::back_inserter(result), [&valid](const char c) {
-                   return (std::string::npos == valid.find(c)) ? '_' : c;
-                 });
-  return result;
-}
-
-std::string Namer::GetMemberName(uint32_t struct_id,
-                                 uint32_t member_index) const {
-  std::string result;
-  auto where = struct_member_names_.find(struct_id);
-  if (where != struct_member_names_.end()) {
-    auto& member_names = where->second;
-    if (member_index < member_names.size()) {
-      result = member_names[member_index];
-    }
-  }
-  return result;
-}
-
-std::string Namer::FindUnusedDerivedName(const std::string& base_name) {
-  // Ensure uniqueness among names.
-  std::string derived_name;
-  uint32_t& i = next_unusued_derived_name_id_[base_name];
-  while (i != 0xffffffff) {
-    std::stringstream new_name_stream;
-    new_name_stream << base_name;
-    if (i > 0) {
-      new_name_stream << "_" << i;
-    }
-    derived_name = new_name_stream.str();
-    if (!IsRegistered(derived_name)) {
-      return derived_name;
-    }
-    i++;
-  }
-  TINT_ASSERT(Reader, false /* FindUnusedDerivedName() overflowed u32 */);
-  return "<u32 overflow>";
-}
-
-std::string Namer::MakeDerivedName(const std::string& base_name) {
-  auto result = FindUnusedDerivedName(base_name);
-  const bool registered = RegisterWithoutId(result);
-  TINT_ASSERT(Reader, registered);
-  return result;
-}
-
-bool Namer::Register(uint32_t id, const std::string& name) {
-  if (HasName(id)) {
-    return Fail() << "internal error: ID " << id
-                  << " already has registered name: " << id_to_name_[id];
-  }
-  if (!RegisterWithoutId(name)) {
-    return false;
-  }
-  id_to_name_[id] = name;
-  name_to_id_[name] = id;
-  return true;
-}
-
-bool Namer::RegisterWithoutId(const std::string& name) {
-  if (IsRegistered(name)) {
-    return Fail() << "internal error: name already registered: " << name;
-  }
-  name_to_id_[name] = 0;
-  return true;
-}
-
-bool Namer::SuggestSanitizedName(uint32_t id,
-                                 const std::string& suggested_name) {
-  if (HasName(id)) {
-    return false;
-  }
-
-  return Register(id, FindUnusedDerivedName(Sanitize(suggested_name)));
-}
-
-bool Namer::SuggestSanitizedMemberName(uint32_t struct_id,
-                                       uint32_t member_index,
-                                       const std::string& suggested_name) {
-  // Creates an empty vector the first time we visit this struct.
-  auto& name_vector = struct_member_names_[struct_id];
-  // Resizing will set new entries to the empty string.
-  name_vector.resize(std::max(name_vector.size(), size_t(member_index + 1)));
-  auto& entry = name_vector[member_index];
-  if (entry.empty()) {
-    entry = Sanitize(suggested_name);
-    return true;
-  }
-  return false;
-}
-
-void Namer::ResolveMemberNamesForStruct(uint32_t struct_id,
-                                        uint32_t num_members) {
-  auto& name_vector = struct_member_names_[struct_id];
-  // Resizing will set new entries to the empty string.
-  // It would have been an error if the client had registered a name for
-  // an out-of-bounds member index, so toss those away.
-  name_vector.resize(num_members);
-
-  std::unordered_set<std::string> used_names;
-
-  // Returns a name, based on the suggestion, which does not equal
-  // any name in the used_names set.
-  auto disambiguate_name =
-      [&used_names](const std::string& suggestion) -> std::string {
-    if (used_names.find(suggestion) == used_names.end()) {
-      // There is no collision.
-      return suggestion;
-    }
-
-    uint32_t i = 1;
-    std::string new_name;
-    do {
-      std::stringstream new_name_stream;
-      new_name_stream << suggestion << "_" << i;
-      new_name = new_name_stream.str();
-      ++i;
-    } while (used_names.find(new_name) != used_names.end());
-    return new_name;
-  };
-
-  // First ensure uniqueness among names for which we have already taken
-  // suggestions.
-  for (auto& name : name_vector) {
-    if (!name.empty()) {
-      // This modifies the names in-place, i.e. update the name_vector
-      // entries.
-      name = disambiguate_name(name);
-      used_names.insert(name);
-    }
-  }
-
-  // Now ensure uniqueness among the rest.  Doing this in a second pass
-  // allows us to preserve suggestions as much as possible.  Otherwise
-  // a generated name such as 'field1' might collide with a user-suggested
-  // name of 'field1' attached to a later member.
-  uint32_t index = 0;
-  for (auto& name : name_vector) {
-    if (name.empty()) {
-      std::stringstream suggestion;
-      suggestion << "field" << index;
-      // Again, modify the name-vector in-place.
-      name = disambiguate_name(suggestion.str());
-      used_names.insert(name);
-    }
-    index++;
-  }
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/namer.h b/src/reader/spirv/namer.h
deleted file mode 100644
index c17d9f0..0000000
--- a/src/reader/spirv/namer.h
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_NAMER_H_
-#define SRC_READER_SPIRV_NAMER_H_
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "src/reader/spirv/fail_stream.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// A Namer maps SPIR-V IDs to strings.
-///
-/// Sanitization:
-/// Some names are user-suggested, but "sanitized" in the sense that an
-/// unusual character (e.g. invalid for use in WGSL identifiers) is remapped
-/// to a safer character such as an underscore.  Also, sanitized names
-/// never start with an underscore.
-class Namer {
- public:
-  /// Creates a new namer
-  /// @param fail_stream the error reporting stream
-  explicit Namer(const FailStream& fail_stream);
-  /// Destructor
-  ~Namer();
-
-  /// Sanitizes the given string, to replace unusual characters with
-  /// obviously-valid idenfier characters. An empy string yields "empty".
-  /// A sanitized name never starts with an underscore.
-  /// @param suggested_name input string
-  /// @returns sanitized name, suitable for use as an identifier
-  static std::string Sanitize(const std::string& suggested_name);
-
-  /// Registers a failure.
-  /// @returns a fail stream to accumulate diagnostics.
-  FailStream& Fail() { return fail_stream_.Fail(); }
-
-  /// @param id the SPIR-V ID
-  /// @returns true if we the given ID already has a registered name.
-  bool HasName(uint32_t id) {
-    return id_to_name_.find(id) != id_to_name_.end();
-  }
-
-  /// @param name a string
-  /// @returns true if the string has been registered as a name.
-  bool IsRegistered(const std::string& name) const {
-    return name_to_id_.find(name) != name_to_id_.end();
-  }
-
-  /// @param id the SPIR-V ID
-  /// @returns the name for the ID. It must have been registered.
-  const std::string& GetName(uint32_t id) const {
-    return id_to_name_.find(id)->second;
-  }
-
-  /// Gets a unique name for the ID. If one already exists, then return
-  /// that, otherwise synthesize a name and remember it for later.
-  /// @param id the SPIR-V ID
-  /// @returns a name for the given ID. Generates a name if non exists.
-  const std::string& Name(uint32_t id) {
-    if (!HasName(id)) {
-      SuggestSanitizedName(id, "x_" + std::to_string(id));
-    }
-    return GetName(id);
-  }
-
-  /// Gets the registered name for a struct member. If no name has
-  /// been registered for this member, then returns the empty string.
-  /// member index is in bounds.
-  /// @param id the SPIR-V ID of the struct type
-  /// @param member_index the index of the member, counting from 0
-  /// @returns the registered name for the ID, or an empty string if
-  /// nothing has been registered.
-  std::string GetMemberName(uint32_t id, uint32_t member_index) const;
-
-  /// Returns an unregistered name based on a given base name.
-  /// @param base_name the base name
-  /// @returns a new name
-  std::string FindUnusedDerivedName(const std::string& base_name);
-
-  /// Returns a newly registered name based on a given base name.
-  /// In the internal table `name_to_id_`, it is mapped to the invalid
-  /// SPIR-V ID 0.  It does not have an entry in `id_to_name_`.
-  /// @param base_name the base name
-  /// @returns a new name
-  std::string MakeDerivedName(const std::string& base_name);
-
-  /// Records a mapping from the given ID to a name. Emits a failure
-  /// if the ID already has a registered name.
-  /// @param id the SPIR-V ID
-  /// @param name the name to map to the ID
-  /// @returns true if the ID did not have a previously registered name.
-  bool Register(uint32_t id, const std::string& name);
-
-  /// Registers a name, but not associated to any ID. Fails and emits
-  /// a diagnostic if the name was already registered.
-  /// @param name the name to register
-  /// @returns true if the name was not already reegistered.
-  bool RegisterWithoutId(const std::string& name);
-
-  /// Saves a sanitized name for the given ID, if that ID does not yet
-  /// have a registered name, and if the sanitized name has not already
-  /// been registered to a different ID.
-  /// @param id the SPIR-V ID
-  /// @param suggested_name the suggested name
-  /// @returns true if a name was newly registered for the ID
-  bool SuggestSanitizedName(uint32_t id, const std::string& suggested_name);
-
-  /// Saves a sanitized name for a member of a struct, if that member
-  /// does not yet have a registered name.
-  /// @param struct_id the SPIR-V ID for the struct
-  /// @param member_index the index of the member inside the struct
-  /// @param suggested_name the suggested name
-  /// @returns true if a name was newly registered
-  bool SuggestSanitizedMemberName(uint32_t struct_id,
-                                  uint32_t member_index,
-                                  const std::string& suggested_name);
-
-  /// Ensure there are member names registered for members of the given struct
-  /// such that:
-  /// - Each member has a non-empty sanitized name.
-  /// - No two members in the struct have the same name.
-  /// @param struct_id the SPIR-V ID for the struct
-  /// @param num_members the number of members in the struct
-  void ResolveMemberNamesForStruct(uint32_t struct_id, uint32_t num_members);
-
- private:
-  FailStream fail_stream_;
-
-  // Maps an ID to its registered name.
-  std::unordered_map<uint32_t, std::string> id_to_name_;
-  // Maps a name to a SPIR-V ID, or 0 (the case for derived names).
-  std::unordered_map<std::string, uint32_t> name_to_id_;
-
-  // Maps a struct id and member index to a suggested sanitized name.
-  // If entry k in the vector is an empty string, then a suggestion
-  // was recorded for a higher-numbered index, but not for index k.
-  std::unordered_map<uint32_t, std::vector<std::string>> struct_member_names_;
-
-  // Saved search id suffix for a given base name. Used by
-  // FindUnusedDerivedName().
-  std::unordered_map<std::string, uint32_t> next_unusued_derived_name_id_;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_NAMER_H_
diff --git a/src/reader/spirv/namer_test.cc b/src/reader/spirv/namer_test.cc
deleted file mode 100644
index 28f0b8c..0000000
--- a/src/reader/spirv/namer_test.cc
+++ /dev/null
@@ -1,407 +0,0 @@
-// Copyright 2020 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/reader/spirv/namer.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-class SpvNamerTest : public testing::Test {
- public:
-  SpvNamerTest() : fail_stream_(&success_, &errors_) {}
-
-  /// @returns the accumulated diagnostic strings
-  std::string error() { return errors_.str(); }
-
- protected:
-  std::stringstream errors_;
-  bool success_ = true;
-  FailStream fail_stream_;
-};
-
-TEST_F(SpvNamerTest, SanitizeEmpty) {
-  EXPECT_THAT(Namer::Sanitize(""), Eq("empty"));
-}
-
-TEST_F(SpvNamerTest, SanitizeLeadingUnderscore) {
-  EXPECT_THAT(Namer::Sanitize("_"), Eq("x_"));
-}
-
-TEST_F(SpvNamerTest, SanitizeLeadingDigit) {
-  EXPECT_THAT(Namer::Sanitize("7zip"), Eq("x7zip"));
-}
-
-TEST_F(SpvNamerTest, SanitizeOkChars) {
-  EXPECT_THAT(Namer::Sanitize("_abcdef12345"), Eq("x_abcdef12345"));
-}
-
-TEST_F(SpvNamerTest, SanitizeNonIdentifierChars) {
-  EXPECT_THAT(Namer::Sanitize("a:1.2'f\n"), "a_1_2_f_");
-}
-
-TEST_F(SpvNamerTest, NoFailureToStart) {
-  Namer namer(fail_stream_);
-  EXPECT_TRUE(success_);
-  EXPECT_TRUE(error().empty());
-}
-
-TEST_F(SpvNamerTest, FailLogsError) {
-  Namer namer(fail_stream_);
-  const bool converted_result = namer.Fail() << "st. johns wood";
-  EXPECT_FALSE(converted_result);
-  EXPECT_EQ(error(), "st. johns wood");
-  EXPECT_FALSE(success_);
-}
-
-TEST_F(SpvNamerTest, NoNameRecorded) {
-  Namer namer(fail_stream_);
-
-  EXPECT_FALSE(namer.HasName(12));
-  EXPECT_TRUE(success_);
-  EXPECT_TRUE(error().empty());
-}
-
-TEST_F(SpvNamerTest, FindUnusedDerivedName_NoRecordedName) {
-  Namer namer(fail_stream_);
-  EXPECT_THAT(namer.FindUnusedDerivedName("eleanor"), Eq("eleanor"));
-  // Prove that it wasn't registered when first found.
-  EXPECT_THAT(namer.FindUnusedDerivedName("eleanor"), Eq("eleanor"));
-}
-
-TEST_F(SpvNamerTest, FindUnusedDerivedName_HasRecordedName) {
-  Namer namer(fail_stream_);
-  namer.Register(12, "rigby");
-  EXPECT_THAT(namer.FindUnusedDerivedName("rigby"), Eq("rigby_1"));
-}
-
-TEST_F(SpvNamerTest, FindUnusedDerivedName_HasMultipleConflicts) {
-  Namer namer(fail_stream_);
-  namer.Register(12, "rigby");
-  namer.Register(13, "rigby_1");
-  namer.Register(14, "rigby_3");
-  // It picks the first non-conflicting suffix.
-  EXPECT_THAT(namer.FindUnusedDerivedName("rigby"), Eq("rigby_2"));
-}
-
-TEST_F(SpvNamerTest, IsRegistered_NoRecordedName) {
-  Namer namer(fail_stream_);
-  EXPECT_FALSE(namer.IsRegistered("abbey"));
-}
-
-TEST_F(SpvNamerTest, IsRegistered_RegisteredById) {
-  Namer namer(fail_stream_);
-  namer.Register(1, "abbey");
-  EXPECT_TRUE(namer.IsRegistered("abbey"));
-}
-
-TEST_F(SpvNamerTest, IsRegistered_RegisteredByDerivation) {
-  Namer namer(fail_stream_);
-  const auto got = namer.MakeDerivedName("abbey");
-  EXPECT_TRUE(namer.IsRegistered("abbey"));
-  EXPECT_EQ(got, "abbey");
-}
-
-TEST_F(SpvNamerTest, MakeDerivedName_NoRecordedName) {
-  Namer namer(fail_stream_);
-  EXPECT_THAT(namer.MakeDerivedName("eleanor"), Eq("eleanor"));
-  // Prove that it was registered when first found.
-  EXPECT_THAT(namer.MakeDerivedName("eleanor"), Eq("eleanor_1"));
-}
-
-TEST_F(SpvNamerTest, MakeDerivedName_HasRecordedName) {
-  Namer namer(fail_stream_);
-  namer.Register(12, "rigby");
-  EXPECT_THAT(namer.MakeDerivedName("rigby"), Eq("rigby_1"));
-}
-
-TEST_F(SpvNamerTest, MakeDerivedName_HasMultipleConflicts) {
-  Namer namer(fail_stream_);
-  namer.Register(12, "rigby");
-  namer.Register(13, "rigby_1");
-  namer.Register(14, "rigby_3");
-  // It picks the first non-conflicting suffix.
-  EXPECT_THAT(namer.MakeDerivedName("rigby"), Eq("rigby_2"));
-}
-
-TEST_F(SpvNamerTest, RegisterWithoutId_Once) {
-  Namer namer(fail_stream_);
-
-  const std::string n("abbey");
-  EXPECT_FALSE(namer.IsRegistered(n));
-  EXPECT_TRUE(namer.RegisterWithoutId(n));
-  EXPECT_TRUE(namer.IsRegistered(n));
-  EXPECT_TRUE(success_);
-  EXPECT_TRUE(error().empty());
-}
-
-TEST_F(SpvNamerTest, RegisterWithoutId_Twice) {
-  Namer namer(fail_stream_);
-
-  const std::string n("abbey");
-  EXPECT_FALSE(namer.IsRegistered(n));
-  EXPECT_TRUE(namer.RegisterWithoutId(n));
-  // Fails on second attempt.
-  EXPECT_FALSE(namer.RegisterWithoutId(n));
-  EXPECT_FALSE(success_);
-  EXPECT_EQ(error(), "internal error: name already registered: abbey");
-}
-
-TEST_F(SpvNamerTest, RegisterWithoutId_ConflictsWithIdRegisteredName) {
-  Namer namer(fail_stream_);
-
-  const std::string n("abbey");
-  EXPECT_TRUE(namer.Register(1, n));
-  EXPECT_TRUE(namer.IsRegistered(n));
-  // Fails on attempt to register without ID.
-  EXPECT_FALSE(namer.RegisterWithoutId(n));
-  EXPECT_FALSE(success_);
-  EXPECT_EQ(error(), "internal error: name already registered: abbey");
-}
-
-TEST_F(SpvNamerTest, Register_Once) {
-  Namer namer(fail_stream_);
-
-  const uint32_t id = 9;
-  EXPECT_FALSE(namer.HasName(id));
-  const bool save_result = namer.Register(id, "abbey road");
-  EXPECT_TRUE(save_result);
-  EXPECT_TRUE(namer.HasName(id));
-  EXPECT_EQ(namer.GetName(id), "abbey road");
-  EXPECT_TRUE(success_);
-  EXPECT_TRUE(error().empty());
-}
-
-TEST_F(SpvNamerTest, Register_TwoIds) {
-  Namer namer(fail_stream_);
-
-  EXPECT_FALSE(namer.HasName(8));
-  EXPECT_FALSE(namer.HasName(9));
-  EXPECT_TRUE(namer.Register(8, "abbey road"));
-  EXPECT_TRUE(namer.Register(9, "rubber soul"));
-  EXPECT_TRUE(namer.HasName(8));
-  EXPECT_TRUE(namer.HasName(9));
-  EXPECT_EQ(namer.GetName(9), "rubber soul");
-  EXPECT_EQ(namer.GetName(8), "abbey road");
-  EXPECT_TRUE(success_);
-  EXPECT_TRUE(error().empty());
-}
-
-TEST_F(SpvNamerTest, Register_FailsDueToIdReuse) {
-  Namer namer(fail_stream_);
-
-  const uint32_t id = 9;
-  EXPECT_TRUE(namer.Register(id, "abbey road"));
-  EXPECT_FALSE(namer.Register(id, "rubber soul"));
-  EXPECT_TRUE(namer.HasName(id));
-  EXPECT_EQ(namer.GetName(id), "abbey road");
-  EXPECT_FALSE(success_);
-  EXPECT_FALSE(error().empty());
-}
-
-TEST_F(SpvNamerTest, SuggestSanitizedName_TakeSuggestionWhenNoConflict) {
-  Namer namer(fail_stream_);
-
-  EXPECT_TRUE(namer.SuggestSanitizedName(1, "father"));
-  EXPECT_THAT(namer.GetName(1), Eq("father"));
-}
-
-TEST_F(SpvNamerTest,
-       SuggestSanitizedName_RejectSuggestionWhenConflictOnSameId) {
-  Namer namer(fail_stream_);
-
-  namer.Register(1, "lennon");
-  EXPECT_FALSE(namer.SuggestSanitizedName(1, "mccartney"));
-  EXPECT_THAT(namer.GetName(1), Eq("lennon"));
-}
-
-TEST_F(SpvNamerTest, SuggestSanitizedName_SanitizeSuggestion) {
-  Namer namer(fail_stream_);
-
-  EXPECT_TRUE(namer.SuggestSanitizedName(9, "m:kenzie"));
-  EXPECT_THAT(namer.GetName(9), Eq("m_kenzie"));
-}
-
-TEST_F(SpvNamerTest,
-       SuggestSanitizedName_GenerateNewNameWhenConflictOnDifferentId) {
-  Namer namer(fail_stream_);
-
-  namer.Register(7, "rice");
-  EXPECT_TRUE(namer.SuggestSanitizedName(9, "rice"));
-  EXPECT_THAT(namer.GetName(9), Eq("rice_1"));
-}
-
-TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedStruct) {
-  Namer namer(fail_stream_);
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
-}
-
-TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedMember) {
-  Namer namer(fail_stream_);
-  namer.SuggestSanitizedMemberName(1, 2, "mother");
-  EXPECT_THAT(namer.GetMemberName(1, 0), Eq(""));
-}
-
-TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSuggestionWhenNoConflict) {
-  Namer namer(fail_stream_);
-  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
-}
-
-TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSanitizedSuggestion) {
-  Namer namer(fail_stream_);
-  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "m:t%er"));
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("m_t_er"));
-}
-
-TEST_F(
-    SpvNamerTest,
-    SuggestSanitizedMemberName_TakeSuggestionWhenNoConflictAfterSuggestionForLowerMember) {  // NOLINT
-  Namer namer(fail_stream_);
-  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 7, "mother"));
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
-  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mary"));
-}
-
-TEST_F(SpvNamerTest,
-       SuggestSanitizedMemberName_RejectSuggestionIfConflictOnMember) {
-  Namer namer(fail_stream_);
-  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
-  EXPECT_FALSE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
-  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
-}
-
-TEST_F(SpvNamerTest, Name_GeneratesNameIfNoneRegistered) {
-  Namer namer(fail_stream_);
-  EXPECT_THAT(namer.Name(14), Eq("x_14"));
-}
-
-TEST_F(SpvNamerTest, Name_GeneratesNameWithoutConflict) {
-  Namer namer(fail_stream_);
-  namer.Register(42, "x_14");
-  EXPECT_THAT(namer.Name(14), Eq("x_14_1"));
-}
-
-TEST_F(SpvNamerTest, Name_ReturnsRegisteredName) {
-  Namer namer(fail_stream_);
-  namer.Register(14, "hello");
-  EXPECT_THAT(namer.Name(14), Eq("hello"));
-}
-
-TEST_F(SpvNamerTest,
-       ResolveMemberNamesForStruct_GeneratesRegularNamesOnItsOwn) {
-  Namer namer(fail_stream_);
-  namer.ResolveMemberNamesForStruct(2, 4);
-  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
-  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1"));
-  EXPECT_THAT(namer.GetMemberName(2, 2), Eq("field2"));
-  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("field3"));
-}
-
-TEST_F(SpvNamerTest,
-       ResolveMemberNamesForStruct_ResolvesConflictBetweenSuggestedNames) {
-  Namer namer(fail_stream_);
-  namer.SuggestSanitizedMemberName(2, 0, "apple");
-  namer.SuggestSanitizedMemberName(2, 1, "apple");
-  namer.ResolveMemberNamesForStruct(2, 2);
-  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("apple"));
-  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("apple_1"));
-}
-
-TEST_F(SpvNamerTest, ResolveMemberNamesForStruct_FillsUnsuggestedGaps) {
-  Namer namer(fail_stream_);
-  namer.SuggestSanitizedMemberName(2, 1, "apple");
-  namer.SuggestSanitizedMemberName(2, 2, "core");
-  namer.ResolveMemberNamesForStruct(2, 4);
-  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
-  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("apple"));
-  EXPECT_THAT(namer.GetMemberName(2, 2), Eq("core"));
-  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("field3"));
-}
-
-TEST_F(SpvNamerTest,
-       ResolveMemberNamesForStruct_GeneratedNameAvoidsConflictWithSuggestion) {
-  Namer namer(fail_stream_);
-  namer.SuggestSanitizedMemberName(2, 0, "field1");
-  namer.ResolveMemberNamesForStruct(2, 2);
-  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field1"));
-  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1_1"));
-}
-
-TEST_F(SpvNamerTest,
-       ResolveMemberNamesForStruct_TruncatesOutOfBoundsSuggestion) {
-  Namer namer(fail_stream_);
-  namer.SuggestSanitizedMemberName(2, 3, "sitar");
-  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("sitar"));
-  namer.ResolveMemberNamesForStruct(2, 2);
-  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
-  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1"));
-  EXPECT_THAT(namer.GetMemberName(2, 3), Eq(""));
-}
-
-using SpvNamerReservedWordTest = ::testing::TestWithParam<std::string>;
-
-TEST_P(SpvNamerReservedWordTest, ReservedWordsAreUsed) {
-  bool success;
-  std::stringstream errors;
-  FailStream fail_stream(&success, &errors);
-  Namer namer(fail_stream);
-  const std::string reserved = GetParam();
-  // Since it's reserved, it's marked as used, and we can't register an ID
-  EXPECT_THAT(namer.FindUnusedDerivedName(reserved), Eq(reserved + "_1"));
-}
-
-INSTANTIATE_TEST_SUITE_P(SpvParserTest_ReservedWords,
-                         SpvNamerReservedWordTest,
-                         ::testing::ValuesIn(std::vector<std::string>{
-                             // Please keep this list sorted.
-                             "array",      "as",          "asm",
-                             "bf16",       "binding",     "block",
-                             "bool",       "break",       "builtin",
-                             "case",       "cast",        "compute",
-                             "const",      "continue",    "default",
-                             "discard",    "do",          "else",
-                             "elseif",     "entry_point", "enum",
-                             "f16",        "f32",         "fallthrough",
-                             "false",      "fn",          "for",
-                             "fragment",   "i16",         "i32",
-                             "i64",        "i8",          "if",
-                             "image",      "import",      "in",
-                             "let",        "location",    "loop",
-                             "mat2x2",     "mat2x3",      "mat2x4",
-                             "mat3x2",     "mat3x3",      "mat3x4",
-                             "mat4x2",     "mat4x3",      "mat4x4",
-                             "offset",     "out",         "override",
-                             "premerge",   "private",     "ptr",
-                             "regardless", "return",      "set",
-                             "storage",    "struct",      "switch",
-                             "true",       "type",        "typedef",
-                             "u16",        "u32",         "u64",
-                             "u8",         "uniform",     "uniform_constant",
-                             "unless",     "using",       "var",
-                             "vec2",       "vec3",        "vec4",
-                             "vertex",     "void",        "while",
-                             "workgroup",
-                         }));
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser.cc b/src/reader/spirv/parser.cc
deleted file mode 100644
index e48357e..0000000
--- a/src/reader/spirv/parser.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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/reader/spirv/parser.h"
-
-#include <utility>
-
-#include "src/reader/spirv/parser_impl.h"
-#include "src/transform/decompose_strided_array.h"
-#include "src/transform/decompose_strided_matrix.h"
-#include "src/transform/manager.h"
-#include "src/transform/remove_unreachable_statements.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-Program Parse(const std::vector<uint32_t>& input) {
-  ParserImpl parser(input);
-  bool parsed = parser.Parse();
-
-  ProgramBuilder& builder = parser.builder();
-  if (!parsed) {
-    // TODO(bclayton): Migrate spirv::ParserImpl to using diagnostics.
-    builder.Diagnostics().add_error(diag::System::Reader, parser.error());
-    return Program(std::move(builder));
-  }
-
-  // The SPIR-V parser can construct disjoint AST nodes, which is invalid for
-  // the Resolver. Clone the Program to clean these up.
-  builder.SetResolveOnBuild(false);
-  Program program_with_disjoint_ast(std::move(builder));
-
-  ProgramBuilder output;
-  CloneContext(&output, &program_with_disjoint_ast, false).Clone();
-  auto program = Program(std::move(output));
-  if (!program.IsValid()) {
-    return program;
-  }
-
-  transform::Manager manager;
-  manager.Add<transform::Unshadow>();
-  manager.Add<transform::SimplifyPointers>();
-  manager.Add<transform::DecomposeStridedMatrix>();
-  manager.Add<transform::DecomposeStridedArray>();
-  manager.Add<transform::RemoveUnreachableStatements>();
-  return manager.Run(&program).program;
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser.h b/src/reader/spirv/parser.h
deleted file mode 100644
index d22ad30..0000000
--- a/src/reader/spirv/parser.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_PARSER_H_
-#define SRC_READER_SPIRV_PARSER_H_
-
-#include <vector>
-
-#include "src/program.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// Parses the SPIR-V source data, returning the parsed program.
-/// If the source data fails to parse then the returned
-/// `program.Diagnostics.contains_errors()` will be true, and the
-/// `program.Diagnostics()` will describe the error.
-/// @param input the source data
-/// @returns the parsed program
-Program Parse(const std::vector<uint32_t>& input);
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_PARSER_H_
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
deleted file mode 100644
index d8c1a64..0000000
--- a/src/reader/spirv/parser_impl.cc
+++ /dev/null
@@ -1,2796 +0,0 @@
-// Copyright 2020 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/reader/spirv/parser_impl.h"
-
-#include <algorithm>
-#include <limits>
-#include <locale>
-#include <utility>
-
-#include "source/opt/build_module.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/type_name.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/reader/spirv/function.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/utils/unique_vector.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-namespace {
-
-// Input SPIR-V needs only to conform to Vulkan 1.1 requirements.
-// The combination of the SPIR-V reader and the semantics of WGSL
-// tighten up the code so that the output of the SPIR-V *writer*
-// will satisfy SPV_ENV_WEBGPU_0 validation.
-const spv_target_env kInputEnv = SPV_ENV_VULKAN_1_1;
-
-// A FunctionTraverser is used to compute an ordering of functions in the
-// module such that callees precede callers.
-class FunctionTraverser {
- public:
-  explicit FunctionTraverser(const spvtools::opt::Module& module)
-      : module_(module) {}
-
-  // @returns the functions in the modules such that callees precede callers.
-  std::vector<const spvtools::opt::Function*> TopologicallyOrderedFunctions() {
-    visited_.clear();
-    ordered_.clear();
-    id_to_func_.clear();
-    for (const auto& f : module_) {
-      id_to_func_[f.result_id()] = &f;
-    }
-    for (const auto& f : module_) {
-      Visit(f);
-    }
-    return ordered_;
-  }
-
- private:
-  void Visit(const spvtools::opt::Function& f) {
-    if (visited_.count(&f)) {
-      return;
-    }
-    visited_.insert(&f);
-    for (const auto& bb : f) {
-      for (const auto& inst : bb) {
-        if (inst.opcode() != SpvOpFunctionCall) {
-          continue;
-        }
-        const auto* callee = id_to_func_[inst.GetSingleWordInOperand(0)];
-        if (callee) {
-          Visit(*callee);
-        }
-      }
-    }
-    ordered_.push_back(&f);
-  }
-
-  const spvtools::opt::Module& module_;
-  std::unordered_set<const spvtools::opt::Function*> visited_;
-  std::unordered_map<uint32_t, const spvtools::opt::Function*> id_to_func_;
-  std::vector<const spvtools::opt::Function*> ordered_;
-};
-
-// Returns true if the opcode operates as if its operands are signed integral.
-bool AssumesSignedOperands(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpSNegate:
-    case SpvOpSDiv:
-    case SpvOpSRem:
-    case SpvOpSMod:
-    case SpvOpSLessThan:
-    case SpvOpSLessThanEqual:
-    case SpvOpSGreaterThan:
-    case SpvOpSGreaterThanEqual:
-    case SpvOpConvertSToF:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the GLSL extended instruction expects operands to be signed.
-// @param extended_opcode GLSL.std.450 opcode
-// @returns true if all operands must be signed integral type
-bool AssumesSignedOperands(GLSLstd450 extended_opcode) {
-  switch (extended_opcode) {
-    case GLSLstd450SAbs:
-    case GLSLstd450SSign:
-    case GLSLstd450SMin:
-    case GLSLstd450SMax:
-    case GLSLstd450SClamp:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the opcode operates as if its operands are unsigned integral.
-bool AssumesUnsignedOperands(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpUDiv:
-    case SpvOpUMod:
-    case SpvOpULessThan:
-    case SpvOpULessThanEqual:
-    case SpvOpUGreaterThan:
-    case SpvOpUGreaterThanEqual:
-    case SpvOpConvertUToF:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the GLSL extended instruction expects operands to be
-// unsigned.
-// @param extended_opcode GLSL.std.450 opcode
-// @returns true if all operands must be unsigned integral type
-bool AssumesUnsignedOperands(GLSLstd450 extended_opcode) {
-  switch (extended_opcode) {
-    case GLSLstd450UMin:
-    case GLSLstd450UMax:
-    case GLSLstd450UClamp:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the corresponding WGSL operation requires
-// the signedness of the second operand to match the signedness of the
-// first operand, and it's not one of the OpU* or OpS* instructions.
-// (Those are handled via MakeOperand.)
-bool AssumesSecondOperandSignednessMatchesFirstOperand(SpvOp opcode) {
-  switch (opcode) {
-    // All the OpI* integer binary operations.
-    case SpvOpIAdd:
-    case SpvOpISub:
-    case SpvOpIMul:
-    case SpvOpIEqual:
-    case SpvOpINotEqual:
-    // All the bitwise integer binary operations.
-    case SpvOpBitwiseAnd:
-    case SpvOpBitwiseOr:
-    case SpvOpBitwiseXor:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the corresponding WGSL operation requires
-// the signedness of the result to match the signedness of the first operand.
-bool AssumesResultSignednessMatchesFirstOperand(SpvOp opcode) {
-  switch (opcode) {
-    case SpvOpNot:
-    case SpvOpSNegate:
-    case SpvOpBitCount:
-    case SpvOpBitReverse:
-    case SpvOpSDiv:
-    case SpvOpSMod:
-    case SpvOpSRem:
-    case SpvOpIAdd:
-    case SpvOpISub:
-    case SpvOpIMul:
-    case SpvOpBitwiseAnd:
-    case SpvOpBitwiseOr:
-    case SpvOpBitwiseXor:
-    case SpvOpShiftLeftLogical:
-    case SpvOpShiftRightLogical:
-    case SpvOpShiftRightArithmetic:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// Returns true if the extended instruction requires the signedness of the
-// result to match the signedness of the first operand to the operation.
-// @param extended_opcode GLSL.std.450 opcode
-// @returns true if the result type must match the first operand type.
-bool AssumesResultSignednessMatchesFirstOperand(GLSLstd450 extended_opcode) {
-  switch (extended_opcode) {
-    case GLSLstd450SAbs:
-    case GLSLstd450SSign:
-    case GLSLstd450SMin:
-    case GLSLstd450SMax:
-    case GLSLstd450SClamp:
-    case GLSLstd450UMin:
-    case GLSLstd450UMax:
-    case GLSLstd450UClamp:
-      // TODO(dneto): FindSMsb?
-      // TODO(dneto): FindUMsb?
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-// @param a SPIR-V decoration
-// @return true when the given decoration is a pipeline decoration other than a
-// bulitin variable.
-bool IsPipelineDecoration(const Decoration& deco) {
-  if (deco.size() < 1) {
-    return false;
-  }
-  switch (deco[0]) {
-    case SpvDecorationLocation:
-    case SpvDecorationFlat:
-    case SpvDecorationNoPerspective:
-    case SpvDecorationCentroid:
-    case SpvDecorationSample:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-}  // namespace
-
-TypedExpression::TypedExpression() = default;
-
-TypedExpression::TypedExpression(const TypedExpression&) = default;
-
-TypedExpression& TypedExpression::operator=(const TypedExpression&) = default;
-
-TypedExpression::TypedExpression(const Type* type_in,
-                                 const ast::Expression* expr_in)
-    : type(type_in), expr(expr_in) {}
-
-ParserImpl::ParserImpl(const std::vector<uint32_t>& spv_binary)
-    : Reader(),
-      spv_binary_(spv_binary),
-      fail_stream_(&success_, &errors_),
-      namer_(fail_stream_),
-      enum_converter_(fail_stream_),
-      tools_context_(kInputEnv) {
-  // Create a message consumer to propagate error messages from SPIRV-Tools
-  // out as our own failures.
-  message_consumer_ = [this](spv_message_level_t level, const char* /*source*/,
-                             const spv_position_t& position,
-                             const char* message) {
-    switch (level) {
-      // Ignore info and warning message.
-      case SPV_MSG_WARNING:
-      case SPV_MSG_INFO:
-        break;
-      // Otherwise, propagate the error.
-      default:
-        // For binary validation errors, we only have the instruction
-        // number.  It's not text, so there is no column number.
-        this->Fail() << "line:" << position.index << ": " << message;
-    }
-  };
-}
-
-ParserImpl::~ParserImpl() = default;
-
-bool ParserImpl::Parse() {
-  // Set up use of SPIRV-Tools utilities.
-  spvtools::SpirvTools spv_tools(kInputEnv);
-
-  // Error messages from SPIRV-Tools are forwarded as failures, including
-  // setting |success_| to false.
-  spv_tools.SetMessageConsumer(message_consumer_);
-
-  if (!success_) {
-    return false;
-  }
-
-  // Only consider modules valid for Vulkan 1.0.  On failure, the message
-  // consumer will set the error status.
-  if (!spv_tools.Validate(spv_binary_)) {
-    success_ = false;
-    return false;
-  }
-  if (!BuildInternalModule()) {
-    return false;
-  }
-  if (!ParseInternalModule()) {
-    return false;
-  }
-
-  return success_;
-}
-
-Program ParserImpl::program() {
-  // TODO(dneto): Should we clear out spv_binary_ here, to reduce
-  // memory usage?
-  return tint::Program(std::move(builder_));
-}
-
-const Type* ParserImpl::ConvertType(uint32_t type_id, PtrAs ptr_as) {
-  if (!success_) {
-    return nullptr;
-  }
-
-  if (type_mgr_ == nullptr) {
-    Fail() << "ConvertType called when the internal module has not been built";
-    return nullptr;
-  }
-
-  auto* spirv_type = type_mgr_->GetType(type_id);
-  if (spirv_type == nullptr) {
-    Fail() << "ID is not a SPIR-V type: " << type_id;
-    return nullptr;
-  }
-
-  switch (spirv_type->kind()) {
-    case spvtools::opt::analysis::Type::kVoid:
-      return ty_.Void();
-    case spvtools::opt::analysis::Type::kBool:
-      return ty_.Bool();
-    case spvtools::opt::analysis::Type::kInteger:
-      return ConvertType(spirv_type->AsInteger());
-    case spvtools::opt::analysis::Type::kFloat:
-      return ConvertType(spirv_type->AsFloat());
-    case spvtools::opt::analysis::Type::kVector:
-      return ConvertType(spirv_type->AsVector());
-    case spvtools::opt::analysis::Type::kMatrix:
-      return ConvertType(spirv_type->AsMatrix());
-    case spvtools::opt::analysis::Type::kRuntimeArray:
-      return ConvertType(type_id, spirv_type->AsRuntimeArray());
-    case spvtools::opt::analysis::Type::kArray:
-      return ConvertType(type_id, spirv_type->AsArray());
-    case spvtools::opt::analysis::Type::kStruct:
-      return ConvertType(type_id, spirv_type->AsStruct());
-    case spvtools::opt::analysis::Type::kPointer:
-      return ConvertType(type_id, ptr_as, spirv_type->AsPointer());
-    case spvtools::opt::analysis::Type::kFunction:
-      // Tint doesn't have a Function type.
-      // We need to convert the result type and parameter types.
-      // But the SPIR-V defines those before defining the function
-      // type.  No further work is required here.
-      return nullptr;
-    case spvtools::opt::analysis::Type::kSampler:
-    case spvtools::opt::analysis::Type::kSampledImage:
-    case spvtools::opt::analysis::Type::kImage:
-      // Fake it for sampler and texture types.  These are handled in an
-      // entirely different way.
-      return ty_.Void();
-    default:
-      break;
-  }
-
-  Fail() << "unknown SPIR-V type with ID " << type_id << ": "
-         << def_use_mgr_->GetDef(type_id)->PrettyPrint();
-  return nullptr;
-}
-
-DecorationList ParserImpl::GetDecorationsFor(uint32_t id) const {
-  DecorationList result;
-  const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
-  std::unordered_set<uint32_t> visited;
-  for (const auto* inst : decorations) {
-    if (inst->opcode() != SpvOpDecorate) {
-      continue;
-    }
-    // Example: OpDecorate %struct_id Block
-    // Example: OpDecorate %array_ty ArrayStride 16
-    auto decoration_kind = inst->GetSingleWordInOperand(1);
-    switch (decoration_kind) {
-      // Restrict and RestrictPointer have no effect in graphics APIs.
-      case SpvDecorationRestrict:
-      case SpvDecorationRestrictPointer:
-        break;
-      default:
-        if (visited.emplace(decoration_kind).second) {
-          std::vector<uint32_t> inst_as_words;
-          inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
-          Decoration d(inst_as_words.begin() + 2, inst_as_words.end());
-          result.push_back(d);
-        }
-        break;
-    }
-  }
-  return result;
-}
-
-DecorationList ParserImpl::GetDecorationsForMember(
-    uint32_t id,
-    uint32_t member_index) const {
-  DecorationList result;
-  const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
-  std::unordered_set<uint32_t> visited;
-  for (const auto* inst : decorations) {
-    // Example: OpMemberDecorate %struct_id 1 Offset 16
-    if ((inst->opcode() != SpvOpMemberDecorate) ||
-        (inst->GetSingleWordInOperand(1) != member_index)) {
-      continue;
-    }
-    auto decoration_kind = inst->GetSingleWordInOperand(2);
-    switch (decoration_kind) {
-      // Restrict and RestrictPointer have no effect in graphics APIs.
-      case SpvDecorationRestrict:
-      case SpvDecorationRestrictPointer:
-        break;
-      default:
-        if (visited.emplace(decoration_kind).second) {
-          std::vector<uint32_t> inst_as_words;
-          inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
-          Decoration d(inst_as_words.begin() + 3, inst_as_words.end());
-          result.push_back(d);
-        }
-    }
-  }
-  return result;
-}
-
-std::string ParserImpl::ShowType(uint32_t type_id) {
-  if (def_use_mgr_) {
-    const auto* type_inst = def_use_mgr_->GetDef(type_id);
-    if (type_inst) {
-      return type_inst->PrettyPrint();
-    }
-  }
-  return "SPIR-V type " + std::to_string(type_id);
-}
-
-ast::AttributeList ParserImpl::ConvertMemberDecoration(
-    uint32_t struct_type_id,
-    uint32_t member_index,
-    const Type* member_ty,
-    const Decoration& decoration) {
-  if (decoration.empty()) {
-    Fail() << "malformed SPIR-V decoration: it's empty";
-    return {};
-  }
-  switch (decoration[0]) {
-    case SpvDecorationOffset:
-      if (decoration.size() != 2) {
-        Fail()
-            << "malformed Offset decoration: expected 1 literal operand, has "
-            << decoration.size() - 1 << ": member " << member_index << " of "
-            << ShowType(struct_type_id);
-        return {};
-      }
-      return {
-          create<ast::StructMemberOffsetAttribute>(Source{}, decoration[1]),
-      };
-    case SpvDecorationNonReadable:
-      // WGSL doesn't have a member decoration for this.  Silently drop it.
-      return {};
-    case SpvDecorationNonWritable:
-      // WGSL doesn't have a member decoration for this.
-      return {};
-    case SpvDecorationColMajor:
-      // WGSL only supports column major matrices.
-      return {};
-    case SpvDecorationRelaxedPrecision:
-      // WGSL doesn't support relaxed precision.
-      return {};
-    case SpvDecorationRowMajor:
-      Fail() << "WGSL does not support row-major matrices: can't "
-                "translate member "
-             << member_index << " of " << ShowType(struct_type_id);
-      return {};
-    case SpvDecorationMatrixStride: {
-      if (decoration.size() != 2) {
-        Fail() << "malformed MatrixStride decoration: expected 1 literal "
-                  "operand, has "
-               << decoration.size() - 1 << ": member " << member_index << " of "
-               << ShowType(struct_type_id);
-        return {};
-      }
-      uint32_t stride = decoration[1];
-      auto* ty = member_ty->UnwrapAlias();
-      while (auto* arr = ty->As<Array>()) {
-        ty = arr->type->UnwrapAlias();
-      }
-      auto* mat = ty->As<Matrix>();
-      if (!mat) {
-        Fail() << "MatrixStride cannot be applied to type " << ty->String();
-        return {};
-      }
-      uint32_t natural_stride = (mat->rows == 2) ? 8 : 16;
-      if (stride == natural_stride) {
-        return {};  // Decoration matches the natural stride for the matrix
-      }
-      if (!member_ty->Is<Matrix>()) {
-        Fail() << "custom matrix strides not currently supported on array of "
-                  "matrices";
-        return {};
-      }
-      return {
-          create<ast::StrideAttribute>(Source{}, decoration[1]),
-          builder_.ASTNodes().Create<ast::DisableValidationAttribute>(
-              builder_.ID(), ast::DisabledValidation::kIgnoreStrideAttribute),
-      };
-    }
-    default:
-      // TODO(dneto): Support the remaining member decorations.
-      break;
-  }
-  Fail() << "unhandled member decoration: " << decoration[0] << " on member "
-         << member_index << " of " << ShowType(struct_type_id);
-  return {};
-}
-
-bool ParserImpl::BuildInternalModule() {
-  if (!success_) {
-    return false;
-  }
-
-  const spv_context& context = tools_context_.CContext();
-  ir_context_ = spvtools::BuildModule(context->target_env, context->consumer,
-                                      spv_binary_.data(), spv_binary_.size());
-  if (!ir_context_) {
-    return Fail() << "internal error: couldn't build the internal "
-                     "representation of the module";
-  }
-  module_ = ir_context_->module();
-  def_use_mgr_ = ir_context_->get_def_use_mgr();
-  constant_mgr_ = ir_context_->get_constant_mgr();
-  type_mgr_ = ir_context_->get_type_mgr();
-  deco_mgr_ = ir_context_->get_decoration_mgr();
-
-  topologically_ordered_functions_ =
-      FunctionTraverser(*module_).TopologicallyOrderedFunctions();
-
-  return success_;
-}
-
-void ParserImpl::ResetInternalModule() {
-  ir_context_.reset(nullptr);
-  module_ = nullptr;
-  def_use_mgr_ = nullptr;
-  constant_mgr_ = nullptr;
-  type_mgr_ = nullptr;
-  deco_mgr_ = nullptr;
-
-  glsl_std_450_imports_.clear();
-}
-
-bool ParserImpl::ParseInternalModule() {
-  if (!success_) {
-    return false;
-  }
-  RegisterLineNumbers();
-  if (!ParseInternalModuleExceptFunctions()) {
-    return false;
-  }
-  if (!EmitFunctions()) {
-    return false;
-  }
-  return success_;
-}
-
-void ParserImpl::RegisterLineNumbers() {
-  Source::Location instruction_number{};
-
-  // Has there been an OpLine since the last OpNoLine or start of the module?
-  bool in_op_line_scope = false;
-  // The source location provided by the most recent OpLine instruction.
-  Source::Location op_line_source{};
-  const bool run_on_debug_insts = true;
-  module_->ForEachInst(
-      [this, &in_op_line_scope, &op_line_source,
-       &instruction_number](const spvtools::opt::Instruction* inst) {
-        ++instruction_number.line;
-        switch (inst->opcode()) {
-          case SpvOpLine:
-            in_op_line_scope = true;
-            // TODO(dneto): This ignores the File ID (operand 0), since the Tint
-            // Source concept doesn't represent that.
-            op_line_source.line = inst->GetSingleWordInOperand(1);
-            op_line_source.column = inst->GetSingleWordInOperand(2);
-            break;
-          case SpvOpNoLine:
-            in_op_line_scope = false;
-            break;
-          default:
-            break;
-        }
-        this->inst_source_[inst] =
-            in_op_line_scope ? op_line_source : instruction_number;
-      },
-      run_on_debug_insts);
-}
-
-Source ParserImpl::GetSourceForResultIdForTest(uint32_t id) const {
-  return GetSourceForInst(def_use_mgr_->GetDef(id));
-}
-
-Source ParserImpl::GetSourceForInst(
-    const spvtools::opt::Instruction* inst) const {
-  auto where = inst_source_.find(inst);
-  if (where == inst_source_.end()) {
-    return {};
-  }
-  return Source{where->second };
-}
-
-bool ParserImpl::ParseInternalModuleExceptFunctions() {
-  if (!success_) {
-    return false;
-  }
-  if (!RegisterExtendedInstructionImports()) {
-    return false;
-  }
-  if (!RegisterUserAndStructMemberNames()) {
-    return false;
-  }
-  if (!RegisterWorkgroupSizeBuiltin()) {
-    return false;
-  }
-  if (!RegisterEntryPoints()) {
-    return false;
-  }
-  if (!RegisterHandleUsage()) {
-    return false;
-  }
-  if (!RegisterTypes()) {
-    return false;
-  }
-  if (!RejectInvalidPointerRoots()) {
-    return false;
-  }
-  if (!EmitScalarSpecConstants()) {
-    return false;
-  }
-  if (!EmitModuleScopeVariables()) {
-    return false;
-  }
-  return success_;
-}
-
-bool ParserImpl::RegisterExtendedInstructionImports() {
-  for (const spvtools::opt::Instruction& import : module_->ext_inst_imports()) {
-    std::string name(
-        reinterpret_cast<const char*>(import.GetInOperand(0).words.data()));
-    // TODO(dneto): Handle other extended instruction sets when needed.
-    if (name == "GLSL.std.450") {
-      glsl_std_450_imports_.insert(import.result_id());
-    } else if (name.find("NonSemantic.") == 0) {
-      ignored_imports_.insert(import.result_id());
-    } else {
-      return Fail() << "Unrecognized extended instruction set: " << name;
-    }
-  }
-  return true;
-}
-
-bool ParserImpl::IsGlslExtendedInstruction(
-    const spvtools::opt::Instruction& inst) const {
-  return (inst.opcode() == SpvOpExtInst) &&
-         (glsl_std_450_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
-}
-
-bool ParserImpl::IsIgnoredExtendedInstruction(
-    const spvtools::opt::Instruction& inst) const {
-  return (inst.opcode() == SpvOpExtInst) &&
-         (ignored_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
-}
-
-bool ParserImpl::RegisterUserAndStructMemberNames() {
-  if (!success_) {
-    return false;
-  }
-  // Register entry point names. An entry point name is the point of contact
-  // between the API and the shader. It has the highest priority for
-  // preservation, so register it first.
-  for (const spvtools::opt::Instruction& entry_point :
-       module_->entry_points()) {
-    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
-    const std::string name = entry_point.GetInOperand(2).AsString();
-
-    // This translator requires the entry point to be a valid WGSL identifier.
-    // Allowing otherwise leads to difficulties in that the programmer needs
-    // to get a mapping from their original entry point name to the WGSL name,
-    // and we don't have a good mechanism for that.
-    if (!IsValidIdentifier(name)) {
-      return Fail() << "entry point name is not a valid WGSL identifier: "
-                    << name;
-    }
-
-    // SPIR-V allows a single function to be the implementation for more
-    // than one entry point.  In the common case, it's one-to-one, and we should
-    // try to name the function after the entry point.  Otherwise, give the
-    // function a name automatically derived from the entry point name.
-    namer_.SuggestSanitizedName(function_id, name);
-
-    // There is another many-to-one relationship to take care of:  In SPIR-V
-    // the same name can be used for multiple entry points, provided they are
-    // for different shader stages. Take action now to ensure we can use the
-    // entry point name later on, and not have it taken for another identifier
-    // by an accidental collision with a derived name made for a different ID.
-    if (!namer_.IsRegistered(name)) {
-      // The entry point name is "unoccupied" becase an earlier entry point
-      // grabbed the slot for the function that implements both entry points.
-      // Register this new entry point's name, to avoid accidental collisions
-      // with a future generated ID.
-      if (!namer_.RegisterWithoutId(name)) {
-        return false;
-      }
-    }
-  }
-
-  // Register names from OpName and OpMemberName
-  for (const auto& inst : module_->debugs2()) {
-    switch (inst.opcode()) {
-      case SpvOpName: {
-        const auto name = inst.GetInOperand(1).AsString();
-        if (!name.empty()) {
-          namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0), name);
-        }
-        break;
-      }
-      case SpvOpMemberName: {
-        const auto name = inst.GetInOperand(2).AsString();
-        if (!name.empty()) {
-          namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
-                                            inst.GetSingleWordInOperand(1),
-                                            name);
-        }
-        break;
-      }
-      default:
-        break;
-    }
-  }
-
-  // Fill in struct member names, and disambiguate them.
-  for (const auto* type_inst : module_->GetTypes()) {
-    if (type_inst->opcode() == SpvOpTypeStruct) {
-      namer_.ResolveMemberNamesForStruct(type_inst->result_id(),
-                                         type_inst->NumInOperands());
-    }
-  }
-
-  return true;
-}
-
-bool ParserImpl::IsValidIdentifier(const std::string& str) {
-  if (str.empty()) {
-    return false;
-  }
-  std::locale c_locale("C");
-  if (str[0] == '_') {
-    if (str.length() == 1u || str[1] == '_') {
-      // https://www.w3.org/TR/WGSL/#identifiers
-      // must not be '_' (a single underscore)
-      // must not start with two underscores
-      return false;
-    }
-  } else if (!std::isalpha(str[0], c_locale)) {
-    return false;
-  }
-  for (const char& ch : str) {
-    if ((ch != '_') && !std::isalnum(ch, c_locale)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool ParserImpl::RegisterWorkgroupSizeBuiltin() {
-  WorkgroupSizeInfo& info = workgroup_size_builtin_;
-  for (const spvtools::opt::Instruction& inst : module_->annotations()) {
-    if (inst.opcode() != SpvOpDecorate) {
-      continue;
-    }
-    if (inst.GetSingleWordInOperand(1) != SpvDecorationBuiltIn) {
-      continue;
-    }
-    if (inst.GetSingleWordInOperand(2) != SpvBuiltInWorkgroupSize) {
-      continue;
-    }
-    info.id = inst.GetSingleWordInOperand(0);
-  }
-  if (info.id == 0) {
-    return true;
-  }
-  // Gather the values.
-  const spvtools::opt::Instruction* composite_def =
-      def_use_mgr_->GetDef(info.id);
-  if (!composite_def) {
-    return Fail() << "Invalid WorkgroupSize builtin value";
-  }
-  // SPIR-V validation checks that the result is a 3-element vector of 32-bit
-  // integer scalars (signed or unsigned).  Rely on validation to check the
-  // type.  In theory the instruction could be OpConstantNull and still
-  // pass validation, but that would be non-sensical.  Be a little more
-  // stringent here and check for specific opcodes.  WGSL does not support
-  // const-expr yet, so avoid supporting OpSpecConstantOp here.
-  // TODO(dneto): See https://github.com/gpuweb/gpuweb/issues/1272 for WGSL
-  // const_expr proposals.
-  if ((composite_def->opcode() != SpvOpSpecConstantComposite &&
-       composite_def->opcode() != SpvOpConstantComposite)) {
-    return Fail() << "Invalid WorkgroupSize builtin.  Expected 3-element "
-                     "OpSpecConstantComposite or OpConstantComposite:  "
-                  << composite_def->PrettyPrint();
-  }
-  info.type_id = composite_def->type_id();
-  // Extract the component type from the vector type.
-  info.component_type_id =
-      def_use_mgr_->GetDef(info.type_id)->GetSingleWordInOperand(0);
-
-  /// Sets the ID and value of the index'th member of the composite constant.
-  /// Returns false and emits a diagnostic on error.
-  auto set_param = [this, composite_def](uint32_t* id_ptr, uint32_t* value_ptr,
-                                         int index) -> bool {
-    const auto id = composite_def->GetSingleWordInOperand(index);
-    const auto* def = def_use_mgr_->GetDef(id);
-    if (!def ||
-        (def->opcode() != SpvOpSpecConstant &&
-         def->opcode() != SpvOpConstant) ||
-        (def->NumInOperands() != 1)) {
-      return Fail() << "invalid component " << index << " of workgroupsize "
-                    << (def ? def->PrettyPrint()
-                            : std::string("no definition"));
-    }
-    *id_ptr = id;
-    // Use the default value of a spec constant.
-    *value_ptr = def->GetSingleWordInOperand(0);
-    return true;
-  };
-
-  return set_param(&info.x_id, &info.x_value, 0) &&
-         set_param(&info.y_id, &info.y_value, 1) &&
-         set_param(&info.z_id, &info.z_value, 2);
-}
-
-bool ParserImpl::RegisterEntryPoints() {
-  // Mapping from entry point ID to GridSize computed from LocalSize
-  // decorations.
-  std::unordered_map<uint32_t, GridSize> local_size;
-  for (const spvtools::opt::Instruction& inst : module_->execution_modes()) {
-    auto mode = static_cast<SpvExecutionMode>(inst.GetSingleWordInOperand(1));
-    if (mode == SpvExecutionModeLocalSize) {
-      if (inst.NumInOperands() != 5) {
-        // This won't even get past SPIR-V binary parsing.
-        return Fail() << "invalid LocalSize execution mode: "
-                      << inst.PrettyPrint();
-      }
-      uint32_t function_id = inst.GetSingleWordInOperand(0);
-      local_size[function_id] = GridSize{inst.GetSingleWordInOperand(2),
-                                         inst.GetSingleWordInOperand(3),
-                                         inst.GetSingleWordInOperand(4)};
-    }
-  }
-
-  for (const spvtools::opt::Instruction& entry_point :
-       module_->entry_points()) {
-    const auto stage = SpvExecutionModel(entry_point.GetSingleWordInOperand(0));
-    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
-
-    const std::string ep_name = entry_point.GetOperand(2).AsString();
-    if (!IsValidIdentifier(ep_name)) {
-      return Fail() << "entry point name is not a valid WGSL identifier: "
-                    << ep_name;
-    }
-
-    bool owns_inner_implementation = false;
-    std::string inner_implementation_name;
-
-    auto where = function_to_ep_info_.find(function_id);
-    if (where == function_to_ep_info_.end()) {
-      // If this is the first entry point to have function_id as its
-      // implementation, then this entry point is responsible for generating
-      // the inner implementation.
-      owns_inner_implementation = true;
-      inner_implementation_name = namer_.MakeDerivedName(ep_name);
-    } else {
-      // Reuse the inner implementation owned by the first entry point.
-      inner_implementation_name = where->second[0].inner_name;
-    }
-    TINT_ASSERT(Reader, !inner_implementation_name.empty());
-    TINT_ASSERT(Reader, ep_name != inner_implementation_name);
-
-    utils::UniqueVector<uint32_t> inputs;
-    utils::UniqueVector<uint32_t> outputs;
-    for (unsigned iarg = 3; iarg < entry_point.NumInOperands(); iarg++) {
-      const uint32_t var_id = entry_point.GetSingleWordInOperand(iarg);
-      if (const auto* var_inst = def_use_mgr_->GetDef(var_id)) {
-        switch (SpvStorageClass(var_inst->GetSingleWordInOperand(0))) {
-          case SpvStorageClassInput:
-            inputs.add(var_id);
-            break;
-          case SpvStorageClassOutput:
-            outputs.add(var_id);
-            break;
-          default:
-            break;
-        }
-      }
-    }
-    // Save the lists, in ID-sorted order.
-    std::vector<uint32_t> sorted_inputs(inputs);
-    std::sort(sorted_inputs.begin(), sorted_inputs.end());
-    std::vector<uint32_t> sorted_outputs(outputs);
-    std::sort(sorted_outputs.begin(), sorted_outputs.end());
-
-    const auto ast_stage = enum_converter_.ToPipelineStage(stage);
-    GridSize wgsize;
-    if (ast_stage == ast::PipelineStage::kCompute) {
-      if (workgroup_size_builtin_.id) {
-        // Store the default values.
-        // WGSL allows specializing these, but this code doesn't support that
-        // yet. https://github.com/gpuweb/gpuweb/issues/1442
-        wgsize = GridSize{workgroup_size_builtin_.x_value,
-                          workgroup_size_builtin_.y_value,
-                          workgroup_size_builtin_.z_value};
-      } else {
-        // Use the LocalSize execution mode.  This is the second choice.
-        auto where_local_size = local_size.find(function_id);
-        if (where_local_size != local_size.end()) {
-          wgsize = where_local_size->second;
-        }
-      }
-    }
-    function_to_ep_info_[function_id].emplace_back(
-        ep_name, ast_stage, owns_inner_implementation,
-        inner_implementation_name, std::move(sorted_inputs),
-        std::move(sorted_outputs), wgsize);
-  }
-
-  // The enum conversion could have failed, so return the existing status value.
-  return success_;
-}
-
-const Type* ParserImpl::ConvertType(
-    const spvtools::opt::analysis::Integer* int_ty) {
-  if (int_ty->width() == 32) {
-    return int_ty->IsSigned() ? static_cast<const Type*>(ty_.I32())
-                              : static_cast<const Type*>(ty_.U32());
-  }
-  Fail() << "unhandled integer width: " << int_ty->width();
-  return nullptr;
-}
-
-const Type* ParserImpl::ConvertType(
-    const spvtools::opt::analysis::Float* float_ty) {
-  if (float_ty->width() == 32) {
-    return ty_.F32();
-  }
-  Fail() << "unhandled float width: " << float_ty->width();
-  return nullptr;
-}
-
-const Type* ParserImpl::ConvertType(
-    const spvtools::opt::analysis::Vector* vec_ty) {
-  const auto num_elem = vec_ty->element_count();
-  auto* ast_elem_ty = ConvertType(type_mgr_->GetId(vec_ty->element_type()));
-  if (ast_elem_ty == nullptr) {
-    return ast_elem_ty;
-  }
-  return ty_.Vector(ast_elem_ty, num_elem);
-}
-
-const Type* ParserImpl::ConvertType(
-    const spvtools::opt::analysis::Matrix* mat_ty) {
-  const auto* vec_ty = mat_ty->element_type()->AsVector();
-  const auto* scalar_ty = vec_ty->element_type();
-  const auto num_rows = vec_ty->element_count();
-  const auto num_columns = mat_ty->element_count();
-  auto* ast_scalar_ty = ConvertType(type_mgr_->GetId(scalar_ty));
-  if (ast_scalar_ty == nullptr) {
-    return nullptr;
-  }
-  return ty_.Matrix(ast_scalar_ty, num_columns, num_rows);
-}
-
-const Type* ParserImpl::ConvertType(
-    uint32_t type_id,
-    const spvtools::opt::analysis::RuntimeArray* rtarr_ty) {
-  auto* ast_elem_ty = ConvertType(type_mgr_->GetId(rtarr_ty->element_type()));
-  if (ast_elem_ty == nullptr) {
-    return nullptr;
-  }
-  uint32_t array_stride = 0;
-  if (!ParseArrayDecorations(rtarr_ty, &array_stride)) {
-    return nullptr;
-  }
-  const Type* result = ty_.Array(ast_elem_ty, 0, array_stride);
-  return MaybeGenerateAlias(type_id, rtarr_ty, result);
-}
-
-const Type* ParserImpl::ConvertType(
-    uint32_t type_id,
-    const spvtools::opt::analysis::Array* arr_ty) {
-  // Get the element type. The SPIR-V optimizer's types representation
-  // deduplicates array types that have the same parameterization.
-  // We don't want that deduplication, so get the element type from
-  // the SPIR-V type directly.
-  const auto* inst = def_use_mgr_->GetDef(type_id);
-  const auto elem_type_id = inst->GetSingleWordInOperand(0);
-  auto* ast_elem_ty = ConvertType(elem_type_id);
-  if (ast_elem_ty == nullptr) {
-    return nullptr;
-  }
-  // Get the length.
-  const auto& length_info = arr_ty->length_info();
-  if (length_info.words.empty()) {
-    // The internal representation is invalid. The discriminant vector
-    // is mal-formed.
-    Fail() << "internal error: Array length info is invalid";
-    return nullptr;
-  }
-  if (length_info.words[0] !=
-      spvtools::opt::analysis::Array::LengthInfo::kConstant) {
-    Fail() << "Array type " << type_mgr_->GetId(arr_ty)
-           << " length is a specialization constant";
-    return nullptr;
-  }
-  const auto* constant = constant_mgr_->FindDeclaredConstant(length_info.id);
-  if (constant == nullptr) {
-    Fail() << "Array type " << type_mgr_->GetId(arr_ty) << " length ID "
-           << length_info.id << " does not name an OpConstant";
-    return nullptr;
-  }
-  const uint64_t num_elem = constant->GetZeroExtendedValue();
-  // For now, limit to only 32bits.
-  if (num_elem > std::numeric_limits<uint32_t>::max()) {
-    Fail() << "Array type " << type_mgr_->GetId(arr_ty)
-           << " has too many elements (more than can fit in 32 bits): "
-           << num_elem;
-    return nullptr;
-  }
-  uint32_t array_stride = 0;
-  if (!ParseArrayDecorations(arr_ty, &array_stride)) {
-    return nullptr;
-  }
-  if (remap_buffer_block_type_.count(elem_type_id)) {
-    remap_buffer_block_type_.insert(type_mgr_->GetId(arr_ty));
-  }
-  const Type* result =
-      ty_.Array(ast_elem_ty, static_cast<uint32_t>(num_elem), array_stride);
-  return MaybeGenerateAlias(type_id, arr_ty, result);
-}
-
-bool ParserImpl::ParseArrayDecorations(
-    const spvtools::opt::analysis::Type* spv_type,
-    uint32_t* array_stride) {
-  *array_stride = 0;  // Implicit stride case.
-  const auto type_id = type_mgr_->GetId(spv_type);
-  for (auto& decoration : this->GetDecorationsFor(type_id)) {
-    if (decoration.size() == 2 && decoration[0] == SpvDecorationArrayStride) {
-      const auto stride = decoration[1];
-      if (stride == 0) {
-        return Fail() << "invalid array type ID " << type_id
-                      << ": ArrayStride can't be 0";
-      }
-      *array_stride = stride;
-    } else {
-      return Fail() << "invalid array type ID " << type_id
-                    << ": unknown decoration "
-                    << (decoration.empty() ? "(empty)"
-                                           : std::to_string(decoration[0]))
-                    << " with " << decoration.size() << " total words";
-    }
-  }
-  return true;
-}
-
-const Type* ParserImpl::ConvertType(
-    uint32_t type_id,
-    const spvtools::opt::analysis::Struct* struct_ty) {
-  // Compute the struct decoration.
-  auto struct_decorations = this->GetDecorationsFor(type_id);
-  if (struct_decorations.size() == 1) {
-    const auto decoration = struct_decorations[0][0];
-    if (decoration == SpvDecorationBufferBlock) {
-      remap_buffer_block_type_.insert(type_id);
-    } else if (decoration != SpvDecorationBlock) {
-      Fail() << "struct with ID " << type_id
-             << " has unrecognized decoration: " << int(decoration);
-    }
-  } else if (struct_decorations.size() > 1) {
-    Fail() << "can't handle a struct with more than one decoration: struct "
-           << type_id << " has " << struct_decorations.size();
-    return nullptr;
-  }
-
-  // Compute members
-  ast::StructMemberList ast_members;
-  const auto members = struct_ty->element_types();
-  if (members.empty()) {
-    Fail() << "WGSL does not support empty structures. can't convert type: "
-           << def_use_mgr_->GetDef(type_id)->PrettyPrint();
-    return nullptr;
-  }
-  TypeList ast_member_types;
-  unsigned num_non_writable_members = 0;
-  for (uint32_t member_index = 0; member_index < members.size();
-       ++member_index) {
-    const auto member_type_id = type_mgr_->GetId(members[member_index]);
-    auto* ast_member_ty = ConvertType(member_type_id);
-    if (ast_member_ty == nullptr) {
-      // Already emitted diagnostics.
-      return nullptr;
-    }
-
-    ast_member_types.emplace_back(ast_member_ty);
-
-    // Scan member for built-in decorations. Some vertex built-ins are handled
-    // specially, and should not generate a structure member.
-    bool create_ast_member = true;
-    for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
-      if (decoration.empty()) {
-        Fail() << "malformed SPIR-V decoration: it's empty";
-        return nullptr;
-      }
-      if ((decoration[0] == SpvDecorationBuiltIn) && (decoration.size() > 1)) {
-        switch (decoration[1]) {
-          case SpvBuiltInPosition:
-            // Record this built-in variable specially.
-            builtin_position_.struct_type_id = type_id;
-            builtin_position_.position_member_index = member_index;
-            builtin_position_.position_member_type_id = member_type_id;
-            create_ast_member = false;  // Not part of the WGSL structure.
-            break;
-          case SpvBuiltInPointSize:  // not supported in WGSL, but ignore
-            builtin_position_.pointsize_member_index = member_index;
-            create_ast_member = false;  // Not part of the WGSL structure.
-            break;
-          case SpvBuiltInClipDistance:  // not supported in WGSL
-          case SpvBuiltInCullDistance:  // not supported in WGSL
-            create_ast_member = false;  // Not part of the WGSL structure.
-            break;
-          default:
-            Fail() << "unrecognized builtin " << decoration[1];
-            return nullptr;
-        }
-      }
-    }
-    if (!create_ast_member) {
-      // This member is decorated as a built-in, and is handled specially.
-      continue;
-    }
-
-    bool is_non_writable = false;
-    ast::AttributeList ast_member_decorations;
-    for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
-      if (IsPipelineDecoration(decoration)) {
-        // IO decorations are handled when emitting the entry point.
-        continue;
-      } else if (decoration[0] == SpvDecorationNonWritable) {
-        // WGSL doesn't represent individual members as non-writable. Instead,
-        // apply the ReadOnly access control to the containing struct if all
-        // the members are non-writable.
-        is_non_writable = true;
-      } else {
-        auto decos = ConvertMemberDecoration(type_id, member_index,
-                                             ast_member_ty, decoration);
-        for (auto* deco : decos) {
-          ast_member_decorations.emplace_back(deco);
-        }
-        if (!success_) {
-          return nullptr;
-        }
-      }
-    }
-
-    if (is_non_writable) {
-      // Count a member as non-writable only once, no matter how many
-      // NonWritable decorations are applied to it.
-      ++num_non_writable_members;
-    }
-    const auto member_name = namer_.GetMemberName(type_id, member_index);
-    auto* ast_struct_member = create<ast::StructMember>(
-        Source{}, builder_.Symbols().Register(member_name),
-        ast_member_ty->Build(builder_), std::move(ast_member_decorations));
-    ast_members.push_back(ast_struct_member);
-  }
-
-  if (ast_members.empty()) {
-    // All members were likely built-ins. Don't generate an empty AST structure.
-    return nullptr;
-  }
-
-  namer_.SuggestSanitizedName(type_id, "S");
-
-  auto name = namer_.GetName(type_id);
-
-  // Now make the struct.
-  auto sym = builder_.Symbols().Register(name);
-  auto* ast_struct = create<ast::Struct>(Source{}, sym, std::move(ast_members),
-                                         ast::AttributeList());
-  if (num_non_writable_members == members.size()) {
-    read_only_struct_types_.insert(ast_struct->name);
-  }
-  AddTypeDecl(sym, ast_struct);
-  const auto* result = ty_.Struct(sym, std::move(ast_member_types));
-  struct_id_for_symbol_[sym] = type_id;
-  return result;
-}
-
-void ParserImpl::AddTypeDecl(Symbol name, const ast::TypeDecl* decl) {
-  auto iter = declared_types_.insert(name);
-  if (iter.second) {
-    builder_.AST().AddTypeDecl(decl);
-  }
-}
-
-const Type* ParserImpl::ConvertType(uint32_t type_id,
-                                    PtrAs ptr_as,
-                                    const spvtools::opt::analysis::Pointer*) {
-  const auto* inst = def_use_mgr_->GetDef(type_id);
-  const auto pointee_type_id = inst->GetSingleWordInOperand(1);
-  const auto storage_class = SpvStorageClass(inst->GetSingleWordInOperand(0));
-
-  if (pointee_type_id == builtin_position_.struct_type_id) {
-    builtin_position_.pointer_type_id = type_id;
-    // Pipeline IO builtins map to private variables.
-    builtin_position_.storage_class = SpvStorageClassPrivate;
-    return nullptr;
-  }
-  auto* ast_elem_ty = ConvertType(pointee_type_id, PtrAs::Ptr);
-  if (ast_elem_ty == nullptr) {
-    Fail() << "SPIR-V pointer type with ID " << type_id
-           << " has invalid pointee type " << pointee_type_id;
-    return nullptr;
-  }
-
-  auto ast_storage_class = enum_converter_.ToStorageClass(storage_class);
-  if (ast_storage_class == ast::StorageClass::kInvalid) {
-    Fail() << "SPIR-V pointer type with ID " << type_id
-           << " has invalid storage class "
-           << static_cast<uint32_t>(storage_class);
-    return nullptr;
-  }
-  if (ast_storage_class == ast::StorageClass::kUniform &&
-      remap_buffer_block_type_.count(pointee_type_id)) {
-    ast_storage_class = ast::StorageClass::kStorage;
-    remap_buffer_block_type_.insert(type_id);
-  }
-
-  // Pipeline input and output variables map to private variables.
-  if (ast_storage_class == ast::StorageClass::kInput ||
-      ast_storage_class == ast::StorageClass::kOutput) {
-    ast_storage_class = ast::StorageClass::kPrivate;
-  }
-  switch (ptr_as) {
-    case PtrAs::Ref:
-      return ty_.Reference(ast_elem_ty, ast_storage_class);
-    case PtrAs::Ptr:
-      return ty_.Pointer(ast_elem_ty, ast_storage_class);
-  }
-  Fail() << "invalid value for ptr_as: " << static_cast<int>(ptr_as);
-  return nullptr;
-}
-
-bool ParserImpl::RegisterTypes() {
-  if (!success_) {
-    return false;
-  }
-
-  // First record the structure types that should have a `block` decoration
-  // in WGSL. In particular, exclude user-defined pipeline IO in a
-  // block-decorated struct.
-  for (const auto& type_or_value : module_->types_values()) {
-    if (type_or_value.opcode() != SpvOpVariable) {
-      continue;
-    }
-    const auto& var = type_or_value;
-    const auto spirv_storage_class =
-        SpvStorageClass(var.GetSingleWordInOperand(0));
-    if ((spirv_storage_class != SpvStorageClassStorageBuffer) &&
-        (spirv_storage_class != SpvStorageClassUniform)) {
-      continue;
-    }
-    const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
-    if (ptr_type->opcode() != SpvOpTypePointer) {
-      return Fail() << "OpVariable type expected to be a pointer: "
-                    << var.PrettyPrint();
-    }
-    const auto* store_type =
-        def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
-    if (store_type->opcode() == SpvOpTypeStruct) {
-      struct_types_for_buffers_.insert(store_type->result_id());
-    } else {
-      Fail() << "WGSL does not support arrays of buffers: "
-             << var.PrettyPrint();
-    }
-  }
-
-  // Now convert each type.
-  for (auto& type_or_const : module_->types_values()) {
-    const auto* type = type_mgr_->GetType(type_or_const.result_id());
-    if (type == nullptr) {
-      continue;
-    }
-    ConvertType(type_or_const.result_id());
-  }
-  // Manufacture a type for the gl_Position variable if we have to.
-  if ((builtin_position_.struct_type_id != 0) &&
-      (builtin_position_.position_member_pointer_type_id == 0)) {
-    builtin_position_.position_member_pointer_type_id =
-        type_mgr_->FindPointerToType(builtin_position_.position_member_type_id,
-                                     builtin_position_.storage_class);
-    ConvertType(builtin_position_.position_member_pointer_type_id);
-  }
-  return success_;
-}
-
-bool ParserImpl::RejectInvalidPointerRoots() {
-  if (!success_) {
-    return false;
-  }
-  for (auto& inst : module_->types_values()) {
-    if (const auto* result_type = type_mgr_->GetType(inst.type_id())) {
-      if (result_type->AsPointer()) {
-        switch (inst.opcode()) {
-          case SpvOpVariable:
-            // This is the only valid case.
-            break;
-          case SpvOpUndef:
-            return Fail() << "undef pointer is not valid: "
-                          << inst.PrettyPrint();
-          case SpvOpConstantNull:
-            return Fail() << "null pointer is not valid: "
-                          << inst.PrettyPrint();
-          default:
-            return Fail() << "module-scope pointer is not valid: "
-                          << inst.PrettyPrint();
-        }
-      }
-    }
-  }
-  return success();
-}
-
-bool ParserImpl::EmitScalarSpecConstants() {
-  if (!success_) {
-    return false;
-  }
-  // Generate a module-scope const declaration for each instruction
-  // that is OpSpecConstantTrue, OpSpecConstantFalse, or OpSpecConstant.
-  for (auto& inst : module_->types_values()) {
-    // These will be populated for a valid scalar spec constant.
-    const Type* ast_type = nullptr;
-    ast::LiteralExpression* ast_expr = nullptr;
-
-    switch (inst.opcode()) {
-      case SpvOpSpecConstantTrue:
-      case SpvOpSpecConstantFalse: {
-        ast_type = ConvertType(inst.type_id());
-        ast_expr = create<ast::BoolLiteralExpression>(
-            Source{}, inst.opcode() == SpvOpSpecConstantTrue);
-        break;
-      }
-      case SpvOpSpecConstant: {
-        ast_type = ConvertType(inst.type_id());
-        const uint32_t literal_value = inst.GetSingleWordInOperand(0);
-        if (ast_type->Is<I32>()) {
-          ast_expr = create<ast::SintLiteralExpression>(
-              Source{}, static_cast<int32_t>(literal_value));
-        } else if (ast_type->Is<U32>()) {
-          ast_expr = create<ast::UintLiteralExpression>(
-              Source{}, static_cast<uint32_t>(literal_value));
-        } else if (ast_type->Is<F32>()) {
-          float float_value;
-          // Copy the bits so we can read them as a float.
-          std::memcpy(&float_value, &literal_value, sizeof(float_value));
-          ast_expr = create<ast::FloatLiteralExpression>(Source{}, float_value);
-        } else {
-          return Fail() << " invalid result type for OpSpecConstant "
-                        << inst.PrettyPrint();
-        }
-        break;
-      }
-      default:
-        break;
-    }
-    if (ast_type && ast_expr) {
-      ast::AttributeList spec_id_decos;
-      for (const auto& deco : GetDecorationsFor(inst.result_id())) {
-        if ((deco.size() == 2) && (deco[0] == SpvDecorationSpecId)) {
-          const uint32_t id = deco[1];
-          if (id > 65535) {
-            return Fail() << "SpecId too large. WGSL override IDs must be "
-                             "between 0 and 65535: ID %"
-                          << inst.result_id() << " has SpecId " << id;
-          }
-          auto* cid = create<ast::IdAttribute>(Source{}, id);
-          spec_id_decos.push_back(cid);
-          break;
-        }
-      }
-      auto* ast_var =
-          MakeVariable(inst.result_id(), ast::StorageClass::kNone, ast_type,
-                       true, true, ast_expr, std::move(spec_id_decos));
-      if (ast_var) {
-        builder_.AST().AddGlobalVariable(ast_var);
-        scalar_spec_constants_.insert(inst.result_id());
-      }
-    }
-  }
-  return success_;
-}
-
-const Type* ParserImpl::MaybeGenerateAlias(
-    uint32_t type_id,
-    const spvtools::opt::analysis::Type* type,
-    const Type* ast_type) {
-  if (!success_) {
-    return nullptr;
-  }
-
-  // We only care about arrays, and runtime arrays.
-  switch (type->kind()) {
-    case spvtools::opt::analysis::Type::kRuntimeArray:
-      // Runtime arrays are always decorated with ArrayStride so always get a
-      // type alias.
-      namer_.SuggestSanitizedName(type_id, "RTArr");
-      break;
-    case spvtools::opt::analysis::Type::kArray:
-      // Only make a type aliase for arrays with decorations.
-      if (GetDecorationsFor(type_id).empty()) {
-        return ast_type;
-      }
-      namer_.SuggestSanitizedName(type_id, "Arr");
-      break;
-    default:
-      // Ignore constants, and any other types.
-      return ast_type;
-  }
-  auto* ast_underlying_type = ast_type;
-  if (ast_underlying_type == nullptr) {
-    Fail() << "internal error: no type registered for SPIR-V ID: " << type_id;
-    return nullptr;
-  }
-  const auto name = namer_.GetName(type_id);
-  const auto sym = builder_.Symbols().Register(name);
-  auto* ast_alias_type =
-      builder_.ty.alias(sym, ast_underlying_type->Build(builder_));
-
-  // Record this new alias as the AST type for this SPIR-V ID.
-  AddTypeDecl(sym, ast_alias_type);
-
-  return ty_.Alias(sym, ast_underlying_type);
-}
-
-bool ParserImpl::EmitModuleScopeVariables() {
-  if (!success_) {
-    return false;
-  }
-  for (const auto& type_or_value : module_->types_values()) {
-    if (type_or_value.opcode() != SpvOpVariable) {
-      continue;
-    }
-    const auto& var = type_or_value;
-    const auto spirv_storage_class =
-        SpvStorageClass(var.GetSingleWordInOperand(0));
-
-    uint32_t type_id = var.type_id();
-    if ((type_id == builtin_position_.pointer_type_id) &&
-        ((spirv_storage_class == SpvStorageClassInput) ||
-         (spirv_storage_class == SpvStorageClassOutput))) {
-      // Skip emitting gl_PerVertex.
-      builtin_position_.per_vertex_var_id = var.result_id();
-      builtin_position_.per_vertex_var_init_id =
-          var.NumInOperands() > 1 ? var.GetSingleWordInOperand(1) : 0u;
-      continue;
-    }
-    switch (enum_converter_.ToStorageClass(spirv_storage_class)) {
-      case ast::StorageClass::kNone:
-      case ast::StorageClass::kInput:
-      case ast::StorageClass::kOutput:
-      case ast::StorageClass::kUniform:
-      case ast::StorageClass::kUniformConstant:
-      case ast::StorageClass::kStorage:
-      case ast::StorageClass::kWorkgroup:
-      case ast::StorageClass::kPrivate:
-        break;
-      default:
-        return Fail() << "invalid SPIR-V storage class "
-                      << int(spirv_storage_class)
-                      << " for module scope variable: " << var.PrettyPrint();
-    }
-    if (!success_) {
-      return false;
-    }
-    const Type* ast_type = nullptr;
-    if (spirv_storage_class == SpvStorageClassUniformConstant) {
-      // These are opaque handles: samplers or textures
-      ast_type = GetTypeForHandleVar(var);
-      if (!ast_type) {
-        return false;
-      }
-    } else {
-      ast_type = ConvertType(type_id);
-      if (ast_type == nullptr) {
-        return Fail() << "internal error: failed to register Tint AST type for "
-                         "SPIR-V type with ID: "
-                      << var.type_id();
-      }
-      if (!ast_type->Is<Pointer>()) {
-        return Fail() << "variable with ID " << var.result_id()
-                      << " has non-pointer type " << var.type_id();
-      }
-    }
-
-    auto* ast_store_type = ast_type->As<Pointer>()->type;
-    auto ast_storage_class = ast_type->As<Pointer>()->storage_class;
-    const ast::Expression* ast_constructor = nullptr;
-    if (var.NumInOperands() > 1) {
-      // SPIR-V initializers are always constants.
-      // (OpenCL also allows the ID of an OpVariable, but we don't handle that
-      // here.)
-      ast_constructor =
-          MakeConstantExpression(var.GetSingleWordInOperand(1)).expr;
-    }
-    auto* ast_var =
-        MakeVariable(var.result_id(), ast_storage_class, ast_store_type, false,
-                     false, ast_constructor, ast::AttributeList{});
-    // TODO(dneto): initializers (a.k.a. constructor expression)
-    if (ast_var) {
-      builder_.AST().AddGlobalVariable(ast_var);
-    }
-  }
-
-  // Emit gl_Position instead of gl_PerVertex
-  if (builtin_position_.per_vertex_var_id) {
-    // Make sure the variable has a name.
-    namer_.SuggestSanitizedName(builtin_position_.per_vertex_var_id,
-                                "gl_Position");
-    const ast::Expression* ast_constructor = nullptr;
-    if (builtin_position_.per_vertex_var_init_id) {
-      // The initializer is complex.
-      const auto* init =
-          def_use_mgr_->GetDef(builtin_position_.per_vertex_var_init_id);
-      switch (init->opcode()) {
-        case SpvOpConstantComposite:
-        case SpvOpSpecConstantComposite:
-          ast_constructor = MakeConstantExpression(
-                                init->GetSingleWordInOperand(
-                                    builtin_position_.position_member_index))
-                                .expr;
-          break;
-        default:
-          return Fail() << "gl_PerVertex initializer too complex. only "
-                           "OpCompositeConstruct and OpSpecConstantComposite "
-                           "are supported: "
-                        << init->PrettyPrint();
-      }
-    }
-    auto* ast_var = MakeVariable(
-        builtin_position_.per_vertex_var_id,
-        enum_converter_.ToStorageClass(builtin_position_.storage_class),
-        ConvertType(builtin_position_.position_member_type_id), false, false,
-        ast_constructor, {});
-
-    builder_.AST().AddGlobalVariable(ast_var);
-  }
-  return success_;
-}
-
-// @param var_id SPIR-V id of an OpVariable, assumed to be pointer
-// to an array
-// @returns the IntConstant for the size of the array, or nullptr
-const spvtools::opt::analysis::IntConstant* ParserImpl::GetArraySize(
-    uint32_t var_id) {
-  auto* var = def_use_mgr_->GetDef(var_id);
-  if (!var || var->opcode() != SpvOpVariable) {
-    return nullptr;
-  }
-  auto* ptr_type = def_use_mgr_->GetDef(var->type_id());
-  if (!ptr_type || ptr_type->opcode() != SpvOpTypePointer) {
-    return nullptr;
-  }
-  auto* array_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
-  if (!array_type || array_type->opcode() != SpvOpTypeArray) {
-    return nullptr;
-  }
-  auto* size = constant_mgr_->FindDeclaredConstant(
-      array_type->GetSingleWordInOperand(1));
-  if (!size) {
-    return nullptr;
-  }
-  return size->AsIntConstant();
-}
-
-ast::Variable* ParserImpl::MakeVariable(uint32_t id,
-                                        ast::StorageClass sc,
-                                        const Type* storage_type,
-                                        bool is_const,
-                                        bool is_overridable,
-                                        const ast::Expression* constructor,
-                                        ast::AttributeList decorations) {
-  if (storage_type == nullptr) {
-    Fail() << "internal error: can't make ast::Variable for null type";
-    return nullptr;
-  }
-
-  ast::Access access = ast::Access::kUndefined;
-  if (sc == ast::StorageClass::kStorage) {
-    bool read_only = false;
-    if (auto* tn = storage_type->As<Named>()) {
-      read_only = read_only_struct_types_.count(tn->name) > 0;
-    }
-
-    // Apply the access(read) or access(read_write) modifier.
-    access = read_only ? ast::Access::kRead : ast::Access::kReadWrite;
-  }
-
-  // Handle variables (textures and samplers) are always in the handle
-  // storage class, so we don't mention the storage class.
-  if (sc == ast::StorageClass::kUniformConstant) {
-    sc = ast::StorageClass::kNone;
-  }
-
-  if (!ConvertDecorationsForVariable(id, &storage_type, &decorations,
-                                     sc != ast::StorageClass::kPrivate)) {
-    return nullptr;
-  }
-
-  std::string name = namer_.Name(id);
-
-  // Note: we're constructing the variable here with the *storage* type,
-  // regardless of whether this is a `let`, `override`, or `var` declaration.
-  // `var` declarations will have a resolved type of ref<storage>, but at the
-  // AST level all three are declared with the same type.
-  return create<ast::Variable>(Source{}, builder_.Symbols().Register(name), sc,
-                               access, storage_type->Build(builder_), is_const,
-                               is_overridable, constructor, decorations);
-}
-
-bool ParserImpl::ConvertDecorationsForVariable(uint32_t id,
-                                               const Type** store_type,
-                                               ast::AttributeList* decorations,
-                                               bool transfer_pipeline_io) {
-  DecorationList non_builtin_pipeline_decorations;
-  for (auto& deco : GetDecorationsFor(id)) {
-    if (deco.empty()) {
-      return Fail() << "malformed decoration on ID " << id << ": it is empty";
-    }
-    if (deco[0] == SpvDecorationBuiltIn) {
-      if (deco.size() == 1) {
-        return Fail() << "malformed BuiltIn decoration on ID " << id
-                      << ": has no operand";
-      }
-      const auto spv_builtin = static_cast<SpvBuiltIn>(deco[1]);
-      switch (spv_builtin) {
-        case SpvBuiltInPointSize:
-          special_builtins_[id] = spv_builtin;
-          return false;  // This is not an error
-        case SpvBuiltInSampleId:
-        case SpvBuiltInVertexIndex:
-        case SpvBuiltInInstanceIndex:
-        case SpvBuiltInLocalInvocationId:
-        case SpvBuiltInLocalInvocationIndex:
-        case SpvBuiltInGlobalInvocationId:
-        case SpvBuiltInWorkgroupId:
-        case SpvBuiltInNumWorkgroups:
-          // The SPIR-V variable may signed (because GLSL requires signed for
-          // some of these), but WGSL requires unsigned.  Handle specially
-          // so we always perform the conversion at load and store.
-          special_builtins_[id] = spv_builtin;
-          if (auto* forced_type = UnsignedTypeFor(*store_type)) {
-            // Requires conversion and special handling in code generation.
-            if (transfer_pipeline_io) {
-              *store_type = forced_type;
-            }
-          }
-          break;
-        case SpvBuiltInSampleMask: {
-          // In SPIR-V this is used for both input and output variable.
-          // The SPIR-V variable has store type of array of integer scalar,
-          // either signed or unsigned.
-          // WGSL requires the store type to be u32.
-          auto* size = GetArraySize(id);
-          if (!size || size->GetZeroExtendedValue() != 1) {
-            Fail() << "WGSL supports a sample mask of at most 32 bits. "
-                      "SampleMask must be an array of 1 element.";
-          }
-          special_builtins_[id] = spv_builtin;
-          if (transfer_pipeline_io) {
-            *store_type = ty_.U32();
-          }
-          break;
-        }
-        default:
-          break;
-      }
-      auto ast_builtin = enum_converter_.ToBuiltin(spv_builtin);
-      if (ast_builtin == ast::Builtin::kNone) {
-        // A diagnostic has already been emitted.
-        return false;
-      }
-      if (transfer_pipeline_io) {
-        decorations->emplace_back(
-            create<ast::BuiltinAttribute>(Source{}, ast_builtin));
-      }
-    }
-    if (transfer_pipeline_io && IsPipelineDecoration(deco)) {
-      non_builtin_pipeline_decorations.push_back(deco);
-    }
-    if (deco[0] == SpvDecorationDescriptorSet) {
-      if (deco.size() == 1) {
-        return Fail() << "malformed DescriptorSet decoration on ID " << id
-                      << ": has no operand";
-      }
-      decorations->emplace_back(create<ast::GroupAttribute>(Source{}, deco[1]));
-    }
-    if (deco[0] == SpvDecorationBinding) {
-      if (deco.size() == 1) {
-        return Fail() << "malformed Binding decoration on ID " << id
-                      << ": has no operand";
-      }
-      decorations->emplace_back(
-          create<ast::BindingAttribute>(Source{}, deco[1]));
-    }
-  }
-
-  if (transfer_pipeline_io) {
-    if (!ConvertPipelineDecorations(
-            *store_type, non_builtin_pipeline_decorations, decorations)) {
-      return false;
-    }
-  }
-
-  return success();
-}
-
-DecorationList ParserImpl::GetMemberPipelineDecorations(
-    const Struct& struct_type,
-    int member_index) {
-  // Yes, I could have used std::copy_if or std::copy_if.
-  DecorationList result;
-  for (const auto& deco : GetDecorationsForMember(
-           struct_id_for_symbol_[struct_type.name], member_index)) {
-    if (IsPipelineDecoration(deco)) {
-      result.emplace_back(deco);
-    }
-  }
-  return result;
-}
-
-const ast::Attribute* ParserImpl::SetLocation(
-    ast::AttributeList* attributes,
-    const ast::Attribute* replacement) {
-  if (!replacement) {
-    return nullptr;
-  }
-  for (auto*& attribute : *attributes) {
-    if (attribute->Is<ast::LocationAttribute>()) {
-      // Replace this location attribute with the replacement.
-      // The old one doesn't leak because it's kept in the builder's AST node
-      // list.
-      const ast::Attribute* result = nullptr;
-      result = attribute;
-      attribute = replacement;
-      return result;  // Assume there is only one such decoration.
-    }
-  }
-  // The list didn't have a location. Add it.
-  attributes->push_back(replacement);
-  return nullptr;
-}
-
-bool ParserImpl::ConvertPipelineDecorations(const Type* store_type,
-                                            const DecorationList& decorations,
-                                            ast::AttributeList* attributes) {
-  // Vulkan defaults to perspective-correct interpolation.
-  ast::InterpolationType type = ast::InterpolationType::kPerspective;
-  ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
-
-  for (const auto& deco : decorations) {
-    TINT_ASSERT(Reader, deco.size() > 0);
-    switch (deco[0]) {
-      case SpvDecorationLocation:
-        if (deco.size() != 2) {
-          return Fail() << "malformed Location decoration on ID requires one "
-                           "literal operand";
-        }
-        SetLocation(attributes,
-                    create<ast::LocationAttribute>(Source{}, deco[1]));
-        if (store_type->IsIntegerScalarOrVector()) {
-          // Default to flat interpolation for integral user-defined IO types.
-          type = ast::InterpolationType::kFlat;
-        }
-        break;
-      case SpvDecorationFlat:
-        type = ast::InterpolationType::kFlat;
-        break;
-      case SpvDecorationNoPerspective:
-        if (store_type->IsIntegerScalarOrVector()) {
-          // This doesn't capture the array or struct case.
-          return Fail() << "NoPerspective is invalid on integral IO";
-        }
-        type = ast::InterpolationType::kLinear;
-        break;
-      case SpvDecorationCentroid:
-        if (store_type->IsIntegerScalarOrVector()) {
-          // This doesn't capture the array or struct case.
-          return Fail()
-                 << "Centroid interpolation sampling is invalid on integral IO";
-        }
-        sampling = ast::InterpolationSampling::kCentroid;
-        break;
-      case SpvDecorationSample:
-        if (store_type->IsIntegerScalarOrVector()) {
-          // This doesn't capture the array or struct case.
-          return Fail()
-                 << "Sample interpolation sampling is invalid on integral IO";
-        }
-        sampling = ast::InterpolationSampling::kSample;
-        break;
-      default:
-        break;
-    }
-  }
-
-  // Apply interpolation.
-  if (type == ast::InterpolationType::kPerspective &&
-      sampling == ast::InterpolationSampling::kNone) {
-    // This is the default. Don't add a decoration.
-  } else {
-    attributes->emplace_back(create<ast::InterpolateAttribute>(type, sampling));
-  }
-
-  return success();
-}
-
-bool ParserImpl::CanMakeConstantExpression(uint32_t id) {
-  if ((id == workgroup_size_builtin_.id) ||
-      (id == workgroup_size_builtin_.x_id) ||
-      (id == workgroup_size_builtin_.y_id) ||
-      (id == workgroup_size_builtin_.z_id)) {
-    return true;
-  }
-  const auto* inst = def_use_mgr_->GetDef(id);
-  if (!inst) {
-    return false;
-  }
-  if (inst->opcode() == SpvOpUndef) {
-    return true;
-  }
-  return nullptr != constant_mgr_->FindDeclaredConstant(id);
-}
-
-TypedExpression ParserImpl::MakeConstantExpression(uint32_t id) {
-  if (!success_) {
-    return {};
-  }
-
-  // Handle the special cases for workgroup sizing.
-  if (id == workgroup_size_builtin_.id) {
-    auto x = MakeConstantExpression(workgroup_size_builtin_.x_id);
-    auto y = MakeConstantExpression(workgroup_size_builtin_.y_id);
-    auto z = MakeConstantExpression(workgroup_size_builtin_.z_id);
-    auto* ast_type = ty_.Vector(x.type, 3);
-    return {ast_type,
-            builder_.Construct(Source{}, ast_type->Build(builder_),
-                               ast::ExpressionList{x.expr, y.expr, z.expr})};
-  } else if (id == workgroup_size_builtin_.x_id) {
-    return MakeConstantExpressionForScalarSpirvConstant(
-        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
-        constant_mgr_->GetConstant(
-            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
-            {workgroup_size_builtin_.x_value}));
-  } else if (id == workgroup_size_builtin_.y_id) {
-    return MakeConstantExpressionForScalarSpirvConstant(
-        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
-        constant_mgr_->GetConstant(
-            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
-            {workgroup_size_builtin_.y_value}));
-  } else if (id == workgroup_size_builtin_.z_id) {
-    return MakeConstantExpressionForScalarSpirvConstant(
-        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
-        constant_mgr_->GetConstant(
-            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
-            {workgroup_size_builtin_.z_value}));
-  }
-
-  // Handle the general case where a constant is already registered
-  // with the SPIR-V optimizer's analysis framework.
-  const auto* inst = def_use_mgr_->GetDef(id);
-  if (inst == nullptr) {
-    Fail() << "ID " << id << " is not a registered instruction";
-    return {};
-  }
-  auto source = GetSourceForInst(inst);
-
-  // TODO(dneto): Handle spec constants too?
-
-  auto* original_ast_type = ConvertType(inst->type_id());
-  if (original_ast_type == nullptr) {
-    return {};
-  }
-
-  switch (inst->opcode()) {
-    case SpvOpUndef:  // Remap undef to null.
-    case SpvOpConstantNull:
-      return {original_ast_type, MakeNullValue(original_ast_type)};
-    case SpvOpConstantTrue:
-    case SpvOpConstantFalse:
-    case SpvOpConstant: {
-      const auto* spirv_const = constant_mgr_->FindDeclaredConstant(id);
-      if (spirv_const == nullptr) {
-        Fail() << "ID " << id << " is not a constant";
-        return {};
-      }
-      return MakeConstantExpressionForScalarSpirvConstant(
-          source, original_ast_type, spirv_const);
-    }
-    case SpvOpConstantComposite: {
-      // Handle vector, matrix, array, and struct
-
-      // Generate a composite from explicit components.
-      ast::ExpressionList ast_components;
-      if (!inst->WhileEachInId([&](const uint32_t* id_ref) -> bool {
-            auto component = MakeConstantExpression(*id_ref);
-            if (!component) {
-              this->Fail() << "invalid constant with ID " << *id_ref;
-              return false;
-            }
-            ast_components.emplace_back(component.expr);
-            return true;
-          })) {
-        // We've already emitted a diagnostic.
-        return {};
-      }
-      return {original_ast_type,
-              builder_.Construct(source, original_ast_type->Build(builder_),
-                                 std::move(ast_components))};
-    }
-    default:
-      break;
-  }
-  Fail() << "unhandled constant instruction " << inst->PrettyPrint();
-  return {};
-}
-
-TypedExpression ParserImpl::MakeConstantExpressionForScalarSpirvConstant(
-    Source source,
-    const Type* original_ast_type,
-    const spvtools::opt::analysis::Constant* spirv_const) {
-  auto* ast_type = original_ast_type->UnwrapAlias();
-
-  // TODO(dneto): Note: NullConstant for int, uint, float map to a regular 0.
-  // So canonicalization should map that way too.
-  // Currently "null<type>" is missing from the WGSL parser.
-  // See https://bugs.chromium.org/p/tint/issues/detail?id=34
-  if (ast_type->Is<U32>()) {
-    return {ty_.U32(),
-            create<ast::UintLiteralExpression>(source, spirv_const->GetU32())};
-  }
-  if (ast_type->Is<I32>()) {
-    return {ty_.I32(),
-            create<ast::SintLiteralExpression>(source, spirv_const->GetS32())};
-  }
-  if (ast_type->Is<F32>()) {
-    return {ty_.F32(), create<ast::FloatLiteralExpression>(
-                           source, spirv_const->GetFloat())};
-  }
-  if (ast_type->Is<Bool>()) {
-    const bool value = spirv_const->AsNullConstant()
-                           ? false
-                           : spirv_const->AsBoolConstant()->value();
-    return {ty_.Bool(), create<ast::BoolLiteralExpression>(source, value)};
-  }
-  Fail() << "expected scalar constant";
-  return {};
-}
-
-const ast::Expression* ParserImpl::MakeNullValue(const Type* type) {
-  // TODO(dneto): Use the no-operands constructor syntax when it becomes
-  // available in Tint.
-  // https://github.com/gpuweb/gpuweb/issues/685
-  // https://bugs.chromium.org/p/tint/issues/detail?id=34
-
-  if (!type) {
-    Fail() << "trying to create null value for a null type";
-    return nullptr;
-  }
-
-  auto* original_type = type;
-  type = type->UnwrapAlias();
-
-  if (type->Is<Bool>()) {
-    return create<ast::BoolLiteralExpression>(Source{}, false);
-  }
-  if (type->Is<U32>()) {
-    return create<ast::UintLiteralExpression>(Source{}, 0u);
-  }
-  if (type->Is<I32>()) {
-    return create<ast::SintLiteralExpression>(Source{}, 0);
-  }
-  if (type->Is<F32>()) {
-    return create<ast::FloatLiteralExpression>(Source{}, 0.0f);
-  }
-  if (type->IsAnyOf<Vector, Matrix, Array>()) {
-    return builder_.Construct(Source{}, type->Build(builder_));
-  }
-  if (auto* struct_ty = type->As<Struct>()) {
-    ast::ExpressionList ast_components;
-    for (auto* member : struct_ty->members) {
-      ast_components.emplace_back(MakeNullValue(member));
-    }
-    return builder_.Construct(Source{}, original_type->Build(builder_),
-                              std::move(ast_components));
-  }
-  Fail() << "can't make null value for type: " << type->TypeInfo().name;
-  return nullptr;
-}
-
-TypedExpression ParserImpl::MakeNullExpression(const Type* type) {
-  return {type, MakeNullValue(type)};
-}
-
-const Type* ParserImpl::UnsignedTypeFor(const Type* type) {
-  if (type->Is<I32>()) {
-    return ty_.U32();
-  }
-  if (auto* v = type->As<Vector>()) {
-    if (v->type->Is<I32>()) {
-      return ty_.Vector(ty_.U32(), v->size);
-    }
-  }
-  return {};
-}
-
-const Type* ParserImpl::SignedTypeFor(const Type* type) {
-  if (type->Is<U32>()) {
-    return ty_.I32();
-  }
-  if (auto* v = type->As<Vector>()) {
-    if (v->type->Is<U32>()) {
-      return ty_.Vector(ty_.I32(), v->size);
-    }
-  }
-  return {};
-}
-
-TypedExpression ParserImpl::RectifyOperandSignedness(
-    const spvtools::opt::Instruction& inst,
-    TypedExpression&& expr) {
-  bool requires_signed = false;
-  bool requires_unsigned = false;
-  if (IsGlslExtendedInstruction(inst)) {
-    const auto extended_opcode =
-        static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
-    requires_signed = AssumesSignedOperands(extended_opcode);
-    requires_unsigned = AssumesUnsignedOperands(extended_opcode);
-  } else {
-    const auto opcode = inst.opcode();
-    requires_signed = AssumesSignedOperands(opcode);
-    requires_unsigned = AssumesUnsignedOperands(opcode);
-  }
-  if (!requires_signed && !requires_unsigned) {
-    // No conversion is required, assuming our tables are complete.
-    return std::move(expr);
-  }
-  if (!expr) {
-    Fail() << "internal error: RectifyOperandSignedness given a null expr\n";
-    return {};
-  }
-  auto* type = expr.type;
-  if (!type) {
-    Fail() << "internal error: unmapped type for: "
-           << expr.expr->TypeInfo().name << "\n";
-    return {};
-  }
-  if (requires_unsigned) {
-    if (auto* unsigned_ty = UnsignedTypeFor(type)) {
-      // Conversion is required.
-      return {unsigned_ty,
-              create<ast::BitcastExpression>(
-                  Source{}, unsigned_ty->Build(builder_), expr.expr)};
-    }
-  } else if (requires_signed) {
-    if (auto* signed_ty = SignedTypeFor(type)) {
-      // Conversion is required.
-      return {signed_ty, create<ast::BitcastExpression>(
-                             Source{}, signed_ty->Build(builder_), expr.expr)};
-    }
-  }
-  // We should not reach here.
-  return std::move(expr);
-}
-
-TypedExpression ParserImpl::RectifySecondOperandSignedness(
-    const spvtools::opt::Instruction& inst,
-    const Type* first_operand_type,
-    TypedExpression&& second_operand_expr) {
-  if ((first_operand_type != second_operand_expr.type) &&
-      AssumesSecondOperandSignednessMatchesFirstOperand(inst.opcode())) {
-    // Conversion is required.
-    return {first_operand_type,
-            create<ast::BitcastExpression>(Source{},
-                                           first_operand_type->Build(builder_),
-                                           second_operand_expr.expr)};
-  }
-  // No conversion necessary.
-  return std::move(second_operand_expr);
-}
-
-const Type* ParserImpl::ForcedResultType(const spvtools::opt::Instruction& inst,
-                                         const Type* first_operand_type) {
-  const auto opcode = inst.opcode();
-  if (AssumesResultSignednessMatchesFirstOperand(opcode)) {
-    return first_operand_type;
-  }
-  if (IsGlslExtendedInstruction(inst)) {
-    const auto extended_opcode =
-        static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
-    if (AssumesResultSignednessMatchesFirstOperand(extended_opcode)) {
-      return first_operand_type;
-    }
-  }
-  return nullptr;
-}
-
-const Type* ParserImpl::GetSignedIntMatchingShape(const Type* other) {
-  if (other == nullptr) {
-    Fail() << "no type provided";
-  }
-  if (other->Is<F32>() || other->Is<U32>() || other->Is<I32>()) {
-    return ty_.I32();
-  }
-  if (auto* vec_ty = other->As<Vector>()) {
-    return ty_.Vector(ty_.I32(), vec_ty->size);
-  }
-  Fail() << "required numeric scalar or vector, but got "
-         << other->TypeInfo().name;
-  return nullptr;
-}
-
-const Type* ParserImpl::GetUnsignedIntMatchingShape(const Type* other) {
-  if (other == nullptr) {
-    Fail() << "no type provided";
-    return nullptr;
-  }
-  if (other->Is<F32>() || other->Is<U32>() || other->Is<I32>()) {
-    return ty_.U32();
-  }
-  if (auto* vec_ty = other->As<Vector>()) {
-    return ty_.Vector(ty_.U32(), vec_ty->size);
-  }
-  Fail() << "required numeric scalar or vector, but got "
-         << other->TypeInfo().name;
-  return nullptr;
-}
-
-TypedExpression ParserImpl::RectifyForcedResultType(
-    TypedExpression expr,
-    const spvtools::opt::Instruction& inst,
-    const Type* first_operand_type) {
-  auto* forced_result_ty = ForcedResultType(inst, first_operand_type);
-  if ((!forced_result_ty) || (forced_result_ty == expr.type)) {
-    return expr;
-  }
-  return {expr.type, create<ast::BitcastExpression>(
-                         Source{}, expr.type->Build(builder_), expr.expr)};
-}
-
-TypedExpression ParserImpl::AsUnsigned(TypedExpression expr) {
-  if (expr.type && expr.type->IsSignedScalarOrVector()) {
-    auto* new_type = GetUnsignedIntMatchingShape(expr.type);
-    return {new_type, create<ast::BitcastExpression>(
-                          Source{}, new_type->Build(builder_), expr.expr)};
-  }
-  return expr;
-}
-
-TypedExpression ParserImpl::AsSigned(TypedExpression expr) {
-  if (expr.type && expr.type->IsUnsignedScalarOrVector()) {
-    auto* new_type = GetSignedIntMatchingShape(expr.type);
-    return {new_type, create<ast::BitcastExpression>(
-                          Source{}, new_type->Build(builder_), expr.expr)};
-  }
-  return expr;
-}
-
-bool ParserImpl::EmitFunctions() {
-  if (!success_) {
-    return false;
-  }
-  for (const auto* f : topologically_ordered_functions_) {
-    if (!success_) {
-      return false;
-    }
-
-    auto id = f->result_id();
-    auto it = function_to_ep_info_.find(id);
-    if (it == function_to_ep_info_.end()) {
-      FunctionEmitter emitter(this, *f, nullptr);
-      success_ = emitter.Emit();
-    } else {
-      for (const auto& ep : it->second) {
-        FunctionEmitter emitter(this, *f, &ep);
-        success_ = emitter.Emit();
-        if (!success_) {
-          return false;
-        }
-      }
-    }
-  }
-  return success_;
-}
-
-const spvtools::opt::Instruction*
-ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id,
-                                                bool follow_image) {
-  auto saved_id = id;
-  auto local_fail = [this, saved_id, id,
-                     follow_image]() -> const spvtools::opt::Instruction* {
-    const auto* inst = def_use_mgr_->GetDef(id);
-    Fail() << "Could not find memory object declaration for the "
-           << (follow_image ? "image" : "sampler") << " underlying id " << id
-           << " (from original id " << saved_id << ") "
-           << (inst ? inst->PrettyPrint() : std::string());
-    return nullptr;
-  };
-
-  auto& memo_table =
-      (follow_image ? mem_obj_decl_image_ : mem_obj_decl_sampler_);
-
-  // Use a visited set to defend against bad input which might have long
-  // chains or even loops.
-  std::unordered_set<uint32_t> visited;
-
-  // Trace backward in the SSA data flow until we hit a memory object
-  // declaration.
-  while (true) {
-    auto where = memo_table.find(id);
-    if (where != memo_table.end()) {
-      return where->second;
-    }
-    // Protect against loops.
-    auto visited_iter = visited.find(id);
-    if (visited_iter != visited.end()) {
-      // We've hit a loop. Mark all the visited nodes
-      // as dead ends.
-      for (auto iter : visited) {
-        memo_table[iter] = nullptr;
-      }
-      return nullptr;
-    }
-    visited.insert(id);
-
-    const auto* inst = def_use_mgr_->GetDef(id);
-    if (inst == nullptr) {
-      return local_fail();
-    }
-    switch (inst->opcode()) {
-      case SpvOpFunctionParameter:
-      case SpvOpVariable:
-        // We found the memory object declaration.
-        // Remember it as the answer for the whole path.
-        for (auto iter : visited) {
-          memo_table[iter] = inst;
-        }
-        return inst;
-      case SpvOpLoad:
-        // Follow the pointer being loaded
-        id = inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpCopyObject:
-        // Follow the object being copied.
-        id = inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpAccessChain:
-      case SpvOpInBoundsAccessChain:
-      case SpvOpPtrAccessChain:
-      case SpvOpInBoundsPtrAccessChain:
-        // Follow the base pointer.
-        id = inst->GetSingleWordInOperand(0);
-        break;
-      case SpvOpSampledImage:
-        // Follow the image or the sampler, depending on the follow_image
-        // parameter.
-        id = inst->GetSingleWordInOperand(follow_image ? 0 : 1);
-        break;
-      case SpvOpImage:
-        // Follow the sampled image
-        id = inst->GetSingleWordInOperand(0);
-        break;
-      default:
-        // Can't trace further.
-        // Remember it as the answer for the whole path.
-        for (auto iter : visited) {
-          memo_table[iter] = nullptr;
-        }
-        return nullptr;
-    }
-  }
-}
-
-const spvtools::opt::Instruction*
-ParserImpl::GetSpirvTypeForHandleMemoryObjectDeclaration(
-    const spvtools::opt::Instruction& var) {
-  if (!success()) {
-    return nullptr;
-  }
-  // The WGSL handle type is determined by looking at information from
-  // several sources:
-  //    - the usage of the handle by image access instructions
-  //    - the SPIR-V type declaration
-  // Each source does not have enough information to completely determine
-  // the result.
-
-  // Messages are phrased in terms of images and samplers because those
-  // are the only SPIR-V handles supported by WGSL.
-
-  // Get the SPIR-V handle type.
-  const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
-  if (!ptr_type || (ptr_type->opcode() != SpvOpTypePointer)) {
-    Fail() << "Invalid type for variable or function parameter "
-           << var.PrettyPrint();
-    return nullptr;
-  }
-  const auto* raw_handle_type =
-      def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
-  if (!raw_handle_type) {
-    Fail() << "Invalid pointer type for variable or function parameter "
-           << var.PrettyPrint();
-    return nullptr;
-  }
-  switch (raw_handle_type->opcode()) {
-    case SpvOpTypeSampler:
-    case SpvOpTypeImage:
-      // The expected cases.
-      break;
-    case SpvOpTypeArray:
-    case SpvOpTypeRuntimeArray:
-      Fail()
-          << "arrays of textures or samplers are not supported in WGSL; can't "
-             "translate variable or function parameter: "
-          << var.PrettyPrint();
-      return nullptr;
-    case SpvOpTypeSampledImage:
-      Fail() << "WGSL does not support combined image-samplers: "
-             << var.PrettyPrint();
-      return nullptr;
-    default:
-      Fail() << "invalid type for image or sampler variable or function "
-                "parameter: "
-             << var.PrettyPrint();
-      return nullptr;
-  }
-  return raw_handle_type;
-}
-
-const Pointer* ParserImpl::GetTypeForHandleVar(
-    const spvtools::opt::Instruction& var) {
-  auto where = handle_type_.find(&var);
-  if (where != handle_type_.end()) {
-    return where->second;
-  }
-
-  const spvtools::opt::Instruction* raw_handle_type =
-      GetSpirvTypeForHandleMemoryObjectDeclaration(var);
-  if (!raw_handle_type) {
-    return nullptr;
-  }
-
-  // The variable could be a sampler or image.
-  // Where possible, determine which one it is from the usage inferred
-  // for the variable.
-  Usage usage = handle_usage_[&var];
-  if (!usage.IsValid()) {
-    Fail() << "Invalid sampler or texture usage for variable "
-           << var.PrettyPrint() << "\n"
-           << usage;
-    return nullptr;
-  }
-  // Infer a handle type, if usage didn't already tell us.
-  if (!usage.IsComplete()) {
-    // In SPIR-V you could statically reference a texture or sampler without
-    // using it in a way that gives us a clue on how to declare it.  Look inside
-    // the store type to infer a usage.
-    if (raw_handle_type->opcode() == SpvOpTypeSampler) {
-      usage.AddSampler();
-    } else {
-      // It's a texture.
-      if (raw_handle_type->NumInOperands() != 7) {
-        Fail() << "invalid SPIR-V image type: expected 7 operands: "
-               << raw_handle_type->PrettyPrint();
-        return nullptr;
-      }
-      const auto sampled_param = raw_handle_type->GetSingleWordInOperand(5);
-      const auto format_param = raw_handle_type->GetSingleWordInOperand(6);
-      // Only storage images have a format.
-      if ((format_param != SpvImageFormatUnknown) ||
-          sampled_param == 2 /* without sampler */) {
-        // Get NonWritable and NonReadable attributes of the variable.
-        bool is_nonwritable = false;
-        bool is_nonreadable = false;
-        for (const auto& deco : GetDecorationsFor(var.result_id())) {
-          if (deco.size() != 1) {
-            continue;
-          }
-          if (deco[0] == SpvDecorationNonWritable) {
-            is_nonwritable = true;
-          }
-          if (deco[0] == SpvDecorationNonReadable) {
-            is_nonreadable = true;
-          }
-        }
-        if (is_nonwritable && is_nonreadable) {
-          Fail() << "storage image variable is both NonWritable and NonReadable"
-                 << var.PrettyPrint();
-        }
-        if (!is_nonwritable && !is_nonreadable) {
-          Fail()
-              << "storage image variable is neither NonWritable nor NonReadable"
-              << var.PrettyPrint();
-        }
-        // Let's make it one of the storage textures.
-        if (is_nonwritable) {
-          usage.AddStorageReadTexture();
-        } else {
-          usage.AddStorageWriteTexture();
-        }
-      } else {
-        usage.AddSampledTexture();
-      }
-    }
-    if (!usage.IsComplete()) {
-      Fail()
-          << "internal error: should have inferred a complete handle type. got "
-          << usage.to_str();
-      return nullptr;
-    }
-  }
-
-  // Construct the Tint handle type.
-  const Type* ast_store_type = nullptr;
-  if (usage.IsSampler()) {
-    ast_store_type = ty_.Sampler(usage.IsComparisonSampler()
-                                     ? ast::SamplerKind::kComparisonSampler
-                                     : ast::SamplerKind::kSampler);
-  } else if (usage.IsTexture()) {
-    const spvtools::opt::analysis::Image* image_type =
-        type_mgr_->GetType(raw_handle_type->result_id())->AsImage();
-    if (!image_type) {
-      Fail() << "internal error: Couldn't look up image type"
-             << raw_handle_type->PrettyPrint();
-      return nullptr;
-    }
-
-    if (image_type->is_arrayed()) {
-      // Give a nicer error message here, where we have the offending variable
-      // in hand, rather than inside the enum converter.
-      switch (image_type->dim()) {
-        case SpvDim2D:
-        case SpvDimCube:
-          break;
-        default:
-          Fail() << "WGSL arrayed textures must be 2d_array or cube_array: "
-                    "invalid multisampled texture variable "
-                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
-          return nullptr;
-      }
-    }
-
-    const ast::TextureDimension dim =
-        enum_converter_.ToDim(image_type->dim(), image_type->is_arrayed());
-    if (dim == ast::TextureDimension::kNone) {
-      return nullptr;
-    }
-
-    // WGSL textures are always formatted.  Unformatted textures are always
-    // sampled.
-    if (usage.IsSampledTexture() || usage.IsStorageReadTexture() ||
-        (image_type->format() == SpvImageFormatUnknown)) {
-      // Make a sampled texture type.
-      auto* ast_sampled_component_type =
-          ConvertType(raw_handle_type->GetSingleWordInOperand(0));
-
-      // Vulkan ignores the depth parameter on OpImage, so pay attention to the
-      // usage as well.  That is, it's valid for a Vulkan shader to use an
-      // OpImage variable with an OpImage*Dref* instruction.  In WGSL we must
-      // treat that as a depth texture.
-      if (image_type->depth() || usage.IsDepthTexture()) {
-        if (image_type->is_multisampled()) {
-          ast_store_type = ty_.DepthMultisampledTexture(dim);
-        } else {
-          ast_store_type = ty_.DepthTexture(dim);
-        }
-      } else if (image_type->is_multisampled()) {
-        if (dim != ast::TextureDimension::k2d) {
-          Fail() << "WGSL multisampled textures must be 2d and non-arrayed: "
-                    "invalid multisampled texture variable "
-                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
-        }
-        // Multisampled textures are never depth textures.
-        ast_store_type =
-            ty_.MultisampledTexture(dim, ast_sampled_component_type);
-      } else {
-        ast_store_type = ty_.SampledTexture(dim, ast_sampled_component_type);
-      }
-    } else {
-      const auto access = ast::Access::kWrite;
-      const auto format = enum_converter_.ToTexelFormat(image_type->format());
-      if (format == ast::TexelFormat::kNone) {
-        return nullptr;
-      }
-      ast_store_type = ty_.StorageTexture(dim, format, access);
-    }
-  } else {
-    Fail() << "unsupported: UniformConstant variable is not a recognized "
-              "sampler or texture"
-           << var.PrettyPrint();
-    return nullptr;
-  }
-
-  // Form the pointer type.
-  auto* result =
-      ty_.Pointer(ast_store_type, ast::StorageClass::kUniformConstant);
-  // Remember it for later.
-  handle_type_[&var] = result;
-  return result;
-}
-
-const Type* ParserImpl::GetComponentTypeForFormat(ast::TexelFormat format) {
-  switch (format) {
-    case ast::TexelFormat::kR32Uint:
-    case ast::TexelFormat::kRgba8Uint:
-    case ast::TexelFormat::kRg32Uint:
-    case ast::TexelFormat::kRgba16Uint:
-    case ast::TexelFormat::kRgba32Uint:
-      return ty_.U32();
-
-    case ast::TexelFormat::kR32Sint:
-    case ast::TexelFormat::kRgba8Sint:
-    case ast::TexelFormat::kRg32Sint:
-    case ast::TexelFormat::kRgba16Sint:
-    case ast::TexelFormat::kRgba32Sint:
-      return ty_.I32();
-
-    case ast::TexelFormat::kRgba8Unorm:
-    case ast::TexelFormat::kRgba8Snorm:
-    case ast::TexelFormat::kR32Float:
-    case ast::TexelFormat::kRg32Float:
-    case ast::TexelFormat::kRgba16Float:
-    case ast::TexelFormat::kRgba32Float:
-      return ty_.F32();
-    default:
-      break;
-  }
-  Fail() << "unknown format " << int(format);
-  return nullptr;
-}
-
-unsigned ParserImpl::GetChannelCountForFormat(ast::TexelFormat format) {
-  switch (format) {
-    case ast::TexelFormat::kR32Float:
-    case ast::TexelFormat::kR32Sint:
-    case ast::TexelFormat::kR32Uint:
-      // One channel
-      return 1;
-
-    case ast::TexelFormat::kRg32Float:
-    case ast::TexelFormat::kRg32Sint:
-    case ast::TexelFormat::kRg32Uint:
-      // Two channels
-      return 2;
-
-    case ast::TexelFormat::kRgba16Float:
-    case ast::TexelFormat::kRgba16Sint:
-    case ast::TexelFormat::kRgba16Uint:
-    case ast::TexelFormat::kRgba32Float:
-    case ast::TexelFormat::kRgba32Sint:
-    case ast::TexelFormat::kRgba32Uint:
-    case ast::TexelFormat::kRgba8Sint:
-    case ast::TexelFormat::kRgba8Snorm:
-    case ast::TexelFormat::kRgba8Uint:
-    case ast::TexelFormat::kRgba8Unorm:
-      // Four channels
-      return 4;
-
-    default:
-      break;
-  }
-  Fail() << "unknown format " << int(format);
-  return 0;
-}
-
-const Type* ParserImpl::GetTexelTypeForFormat(ast::TexelFormat format) {
-  const auto* component_type = GetComponentTypeForFormat(format);
-  if (!component_type) {
-    return nullptr;
-  }
-  return ty_.Vector(component_type, 4);
-}
-
-bool ParserImpl::RegisterHandleUsage() {
-  if (!success_) {
-    return false;
-  }
-
-  // Map a function ID to the list of its function parameter instructions, in
-  // order.
-  std::unordered_map<uint32_t, std::vector<const spvtools::opt::Instruction*>>
-      function_params;
-  for (const auto* f : topologically_ordered_functions_) {
-    // Record the instructions defining this function's parameters.
-    auto& params = function_params[f->result_id()];
-    f->ForEachParam([&params](const spvtools::opt::Instruction* param) {
-      params.push_back(param);
-    });
-  }
-
-  // Returns the memory object declaration for an image underlying the first
-  // operand of the given image instruction.
-  auto get_image = [this](const spvtools::opt::Instruction& image_inst) {
-    return this->GetMemoryObjectDeclarationForHandle(
-        image_inst.GetSingleWordInOperand(0), true);
-  };
-  // Returns the memory object declaration for a sampler underlying the first
-  // operand of the given image instruction.
-  auto get_sampler = [this](const spvtools::opt::Instruction& image_inst) {
-    return this->GetMemoryObjectDeclarationForHandle(
-        image_inst.GetSingleWordInOperand(0), false);
-  };
-
-  // Scan the bodies of functions for image operations, recording their implied
-  // usage properties on the memory object declarations (i.e. variables or
-  // function parameters).  We scan the functions in an order so that callees
-  // precede callers. That way the usage on a function parameter is already
-  // computed before we see the call to that function.  So when we reach
-  // a function call, we can add the usage from the callee formal parameters.
-  for (const auto* f : topologically_ordered_functions_) {
-    for (const auto& bb : *f) {
-      for (const auto& inst : bb) {
-        switch (inst.opcode()) {
-            // Single texel reads and writes
-
-          case SpvOpImageRead:
-            handle_usage_[get_image(inst)].AddStorageReadTexture();
-            break;
-          case SpvOpImageWrite:
-            handle_usage_[get_image(inst)].AddStorageWriteTexture();
-            break;
-          case SpvOpImageFetch:
-            handle_usage_[get_image(inst)].AddSampledTexture();
-            break;
-
-            // Sampling and gathering from a sampled image.
-
-          case SpvOpImageSampleImplicitLod:
-          case SpvOpImageSampleExplicitLod:
-          case SpvOpImageSampleProjImplicitLod:
-          case SpvOpImageSampleProjExplicitLod:
-          case SpvOpImageGather:
-            handle_usage_[get_image(inst)].AddSampledTexture();
-            handle_usage_[get_sampler(inst)].AddSampler();
-            break;
-          case SpvOpImageSampleDrefImplicitLod:
-          case SpvOpImageSampleDrefExplicitLod:
-          case SpvOpImageSampleProjDrefImplicitLod:
-          case SpvOpImageSampleProjDrefExplicitLod:
-          case SpvOpImageDrefGather:
-            // Depth reference access implies usage as a depth texture, which
-            // in turn is a sampled texture.
-            handle_usage_[get_image(inst)].AddDepthTexture();
-            handle_usage_[get_sampler(inst)].AddComparisonSampler();
-            break;
-
-            // Image queries
-
-          case SpvOpImageQuerySizeLod:
-            // Vulkan requires Sampled=1 for this. SPIR-V already requires MS=0.
-            handle_usage_[get_image(inst)].AddSampledTexture();
-            break;
-          case SpvOpImageQuerySize:
-            // Applies to either MS=1 or Sampled=0 or 2.
-            // So we can't force it to be multisampled, or storage image.
-            break;
-          case SpvOpImageQueryLod:
-            handle_usage_[get_image(inst)].AddSampledTexture();
-            handle_usage_[get_sampler(inst)].AddSampler();
-            break;
-          case SpvOpImageQueryLevels:
-            // We can't tell anything more than that it's an image.
-            handle_usage_[get_image(inst)].AddTexture();
-            break;
-          case SpvOpImageQuerySamples:
-            handle_usage_[get_image(inst)].AddMultisampledTexture();
-            break;
-
-            // Function calls
-
-          case SpvOpFunctionCall: {
-            // Propagate handle usages from callee function formal parameters to
-            // the matching caller parameters.  This is where we rely on the
-            // fact that callees have been processed earlier in the flow.
-            const auto num_in_operands = inst.NumInOperands();
-            // The first operand of the call is the function ID.
-            // The remaining operands are the operands to the function.
-            if (num_in_operands < 1) {
-              return Fail() << "Call instruction must have at least one operand"
-                            << inst.PrettyPrint();
-            }
-            const auto function_id = inst.GetSingleWordInOperand(0);
-            const auto& formal_params = function_params[function_id];
-            if (formal_params.size() != (num_in_operands - 1)) {
-              return Fail() << "Called function has " << formal_params.size()
-                            << " parameters, but function call has "
-                            << (num_in_operands - 1) << " parameters"
-                            << inst.PrettyPrint();
-            }
-            for (uint32_t i = 1; i < num_in_operands; ++i) {
-              auto where = handle_usage_.find(formal_params[i - 1]);
-              if (where == handle_usage_.end()) {
-                // We haven't recorded any handle usage on the formal parameter.
-                continue;
-              }
-              const Usage& formal_param_usage = where->second;
-              const auto operand_id = inst.GetSingleWordInOperand(i);
-              const auto* operand_as_sampler =
-                  GetMemoryObjectDeclarationForHandle(operand_id, false);
-              const auto* operand_as_image =
-                  GetMemoryObjectDeclarationForHandle(operand_id, true);
-              if (operand_as_sampler) {
-                handle_usage_[operand_as_sampler].Add(formal_param_usage);
-              }
-              if (operand_as_image &&
-                  (operand_as_image != operand_as_sampler)) {
-                handle_usage_[operand_as_image].Add(formal_param_usage);
-              }
-            }
-            break;
-          }
-
-          default:
-            break;
-        }
-      }
-    }
-  }
-  return success_;
-}
-
-Usage ParserImpl::GetHandleUsage(uint32_t id) const {
-  const auto where = handle_usage_.find(def_use_mgr_->GetDef(id));
-  if (where != handle_usage_.end()) {
-    return where->second;
-  }
-  return Usage();
-}
-
-const spvtools::opt::Instruction* ParserImpl::GetInstructionForTest(
-    uint32_t id) const {
-  return def_use_mgr_ ? def_use_mgr_->GetDef(id) : nullptr;
-}
-
-std::string ParserImpl::GetMemberName(const Struct& struct_type,
-                                      int member_index) {
-  auto where = struct_id_for_symbol_.find(struct_type.name);
-  if (where == struct_id_for_symbol_.end()) {
-    Fail() << "no structure type registered for symbol";
-    return "";
-  }
-  return namer_.GetMemberName(where->second, member_index);
-}
-
-WorkgroupSizeInfo::WorkgroupSizeInfo() = default;
-
-WorkgroupSizeInfo::~WorkgroupSizeInfo() = default;
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
deleted file mode 100644
index f67d81a..0000000
--- a/src/reader/spirv/parser_impl.h
+++ /dev/null
@@ -1,888 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_PARSER_IMPL_H_
-#define SRC_READER_SPIRV_PARSER_IMPL_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#if TINT_BUILD_SPV_READER
-#include "source/opt/ir_context.h"
-#endif
-
-#include "src/program_builder.h"
-#include "src/reader/reader.h"
-#include "src/reader/spirv/entry_point_info.h"
-#include "src/reader/spirv/enum_converter.h"
-#include "src/reader/spirv/namer.h"
-#include "src/reader/spirv/parser_type.h"
-#include "src/reader/spirv/usage.h"
-
-/// This is the implementation of the SPIR-V parser for Tint.
-
-/// Notes on terminology:
-///
-/// A WGSL "handle" is an opaque object used for accessing a resource via
-/// special builtins.  In SPIR-V, a handle is stored a variable in the
-/// UniformConstant storage class.  The handles supported by SPIR-V are:
-///   - images, both sampled texture and storage image
-///   - samplers
-///   - combined image+sampler
-///   - acceleration structures for raytracing.
-///
-/// WGSL only supports samplers and images, but calls images "textures".
-/// When emitting errors, we aim to use terminology most likely to be
-/// familiar to Vulkan SPIR-V developers.  We will tend to use "image"
-/// and "sampler" instead of "handle".
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// The binary representation of a SPIR-V decoration enum followed by its
-/// operands, if any.
-/// Example:   { SpvDecorationBlock }
-/// Example:   { SpvDecorationArrayStride, 16 }
-using Decoration = std::vector<uint32_t>;
-using DecorationList = std::vector<Decoration>;
-
-/// An AST expression with its type.
-struct TypedExpression {
-  /// Constructor
-  TypedExpression();
-
-  /// Copy constructor
-  TypedExpression(const TypedExpression&);
-
-  /// Constructor
-  /// @param type_in the type of the expression
-  /// @param expr_in the expression
-  TypedExpression(const Type* type_in, const ast::Expression* expr_in);
-
-  /// Assignment operator
-  /// @returns this TypedExpression
-  TypedExpression& operator=(const TypedExpression&);
-
-  /// @returns true if both type and expr are not nullptr
-  operator bool() const { return type && expr; }
-
-  /// The type
-  const Type* type = nullptr;
-  /// The expression
-  const ast::Expression* expr = nullptr;
-};
-
-/// Info about the WorkgroupSize builtin.
-struct WorkgroupSizeInfo {
-  /// Constructor
-  WorkgroupSizeInfo();
-  /// Destructor
-  ~WorkgroupSizeInfo();
-  /// The SPIR-V ID of the WorkgroupSize builtin, if any.
-  uint32_t id = 0u;
-  /// The SPIR-V type ID of the WorkgroupSize builtin, if any.
-  uint32_t type_id = 0u;
-  /// The SPIR-V type IDs of the x, y, and z components.
-  uint32_t component_type_id = 0u;
-  /// The SPIR-V IDs of the X, Y, and Z components of the workgroup size
-  /// builtin.
-  uint32_t x_id = 0u;  /// X component ID
-  uint32_t y_id = 0u;  /// Y component ID
-  uint32_t z_id = 0u;  /// Z component ID
-  /// The effective workgroup size, if this is a compute shader.
-  uint32_t x_value = 0u;  /// X workgroup size
-  uint32_t y_value = 0u;  /// Y workgroup size
-  uint32_t z_value = 0u;  /// Z workgroup size
-};
-
-/// Parser implementation for SPIR-V.
-class ParserImpl : Reader {
- public:
-  /// Creates a new parser
-  /// @param input the input data to parse
-  explicit ParserImpl(const std::vector<uint32_t>& input);
-  /// Destructor
-  ~ParserImpl() override;
-
-  /// Run the parser
-  /// @returns true if the parse was successful, false otherwise.
-  bool Parse() override;
-
-  /// @returns the program. The program builder in the parser will be reset
-  /// after this.
-  Program program() override;
-
-  /// @returns a reference to the internal builder, without building the
-  /// program. To be used only for testing.
-  ProgramBuilder& builder() { return builder_; }
-
-  /// @returns the type manager
-  TypeManager& type_manager() { return ty_; }
-
-  /// Logs failure, ands return a failure stream to accumulate diagnostic
-  /// messages. By convention, a failure should only be logged along with
-  /// a non-empty string diagnostic.
-  /// @returns the failure stream
-  FailStream& Fail() {
-    success_ = false;
-    return fail_stream_;
-  }
-
-  /// @return true if failure has not yet occurred
-  bool success() const { return success_; }
-
-  /// @returns the accumulated error string
-  const std::string error() { return errors_.str(); }
-
-  /// Builds an internal representation of the SPIR-V binary,
-  /// and parses it into a Tint AST module.  Diagnostics are emitted
-  /// to the error stream.
-  /// @returns true if it was successful.
-  bool BuildAndParseInternalModule() {
-    return BuildInternalModule() && ParseInternalModule();
-  }
-  /// Builds an internal representation of the SPIR-V binary,
-  /// and parses the module, except functions, into a Tint AST module.
-  /// Diagnostics are emitted to the error stream.
-  /// @returns true if it was successful.
-  bool BuildAndParseInternalModuleExceptFunctions() {
-    return BuildInternalModule() && ParseInternalModuleExceptFunctions();
-  }
-
-  /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450"
-  /// extended instruction set.
-  const std::unordered_set<uint32_t>& glsl_std_450_imports() const {
-    return glsl_std_450_imports_;
-  }
-
-  /// Desired handling of SPIR-V pointers by ConvertType()
-  enum class PtrAs {
-    // SPIR-V pointer is converted to a spirv::Pointer
-    Ptr,
-    // SPIR-V pointer is converted to a spirv::Reference
-    Ref
-  };
-
-  /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup.
-  /// If the type is only used for builtins, then register that specially,
-  /// and return null.  If the type is a sampler, image, or sampled image, then
-  /// return the Void type, because those opaque types are handled in a
-  /// different way.
-  /// On failure, logs an error and returns null.  This should only be called
-  /// after the internal representation of the module has been built.
-  /// @param type_id the SPIR-V ID of a type.
-  /// @param ptr_as if the SPIR-V type is a pointer and ptr_as is equal to
-  /// PtrAs::Ref then a Reference will be returned, otherwise a Pointer will be
-  /// returned for a SPIR-V pointer
-  /// @returns a Tint type, or nullptr
-  const Type* ConvertType(uint32_t type_id, PtrAs ptr_as = PtrAs::Ptr);
-
-  /// Emits an alias type declaration for array or runtime-sized array type,
-  /// when needed to distinguish between differently-decorated underlying types.
-  /// Updates the mapping of the SPIR-V type ID to the alias type.
-  /// This is a no-op if the parser has already failed.
-  /// @param type_id the SPIR-V ID for the type
-  /// @param type the type that might get an alias
-  /// @param ast_type the ast type that might get an alias
-  /// @returns an alias type or `ast_type` if no alias was created
-  const Type* MaybeGenerateAlias(uint32_t type_id,
-                                 const spvtools::opt::analysis::Type* type,
-                                 const Type* ast_type);
-
-  /// Adds `decl` as a declared type if it hasn't been added yet.
-  /// @param name the type's unique name
-  /// @param decl the type declaration to add
-  void AddTypeDecl(Symbol name, const ast::TypeDecl* decl);
-
-  /// @returns the fail stream object
-  FailStream& fail_stream() { return fail_stream_; }
-  /// @returns the namer object
-  Namer& namer() { return namer_; }
-  /// @returns a borrowed pointer to the internal representation of the module.
-  /// This is null until BuildInternalModule has been called.
-  spvtools::opt::IRContext* ir_context() { return ir_context_.get(); }
-
-  /// Gets the list of unique decorations for a SPIR-V result ID.  Returns an
-  /// empty vector if the ID is not a result ID, or if no decorations target
-  /// that ID. The internal representation must have already been built.
-  /// Ignores decorations that have no effect in graphics APIs, e.g. Restrict
-  /// and RestrictPointer.
-  /// @param id SPIR-V ID
-  /// @returns the list of decorations on the given ID
-  DecorationList GetDecorationsFor(uint32_t id) const;
-  /// Gets the list of unique decorations for the member of a struct.  Returns
-  /// an empty list if the `id` is not the ID of a struct, or if the member
-  /// index is out of range, or if the target member has no decorations. The
-  /// internal representation must have already been built.
-  /// Ignores decorations that have no effect in graphics APIs, e.g. Restrict
-  /// and RestrictPointer.
-  /// @param id SPIR-V ID of a struct
-  /// @param member_index the member within the struct
-  /// @returns the list of decorations on the member
-  DecorationList GetDecorationsForMember(uint32_t id,
-                                         uint32_t member_index) const;
-
-  /// Converts SPIR-V decorations for the variable with the given ID.
-  /// Registers the IDs of variables that require special handling by code
-  /// generation.  If the WGSL type differs from the store type for SPIR-V,
-  /// then the `type` parameter is updated.  Returns false on failure (with
-  /// a diagnostic), or when the variable should not be emitted, e.g. for a
-  /// PointSize builtin.
-  /// @param id the ID of the SPIR-V variable
-  /// @param store_type the WGSL store type for the variable, which should be
-  /// prepopulatd
-  /// @param attributes the attribute list to populate
-  /// @param transfer_pipeline_io true if pipeline IO decorations (builtins,
-  /// or locations) will update the store type and the decorations list
-  /// @returns false when the variable should not be emitted as a variable
-  bool ConvertDecorationsForVariable(uint32_t id,
-                                     const Type** store_type,
-                                     ast::AttributeList* attributes,
-                                     bool transfer_pipeline_io);
-
-  /// Converts SPIR-V decorations for pipeline IO into AST decorations.
-  /// @param store_type the store type for the variable or member
-  /// @param decorations the SPIR-V interpolation decorations
-  /// @param attributes the attribute list to populate.
-  /// @returns false if conversion fails
-  bool ConvertPipelineDecorations(const Type* store_type,
-                                  const DecorationList& decorations,
-                                  ast::AttributeList* attributes);
-
-  /// Updates the attribute list, placing a non-null location decoration into
-  /// the list, replacing an existing one if it exists. Does nothing if the
-  /// replacement is nullptr.
-  /// Assumes the list contains at most one Location decoration.
-  /// @param decos the attribute list to modify
-  /// @param replacement the location decoration to place into the list
-  /// @returns the location decoration that was replaced, if one was replaced,
-  /// or null otherwise.
-  const ast::Attribute* SetLocation(ast::AttributeList* decos,
-                                    const ast::Attribute* replacement);
-
-  /// Converts a SPIR-V struct member decoration into a number of AST
-  /// decorations. If the decoration is recognized but deliberately dropped,
-  /// then returns an empty list without a diagnostic. On failure, emits a
-  /// diagnostic and returns an empty list.
-  /// @param struct_type_id the ID of the struct type
-  /// @param member_index the index of the member
-  /// @param member_ty the type of the member
-  /// @param decoration an encoded SPIR-V Decoration
-  /// @returns the AST decorations
-  ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
-                                             uint32_t member_index,
-                                             const Type* member_ty,
-                                             const Decoration& decoration);
-
-  /// Returns a string for the given type.  If the type ID is invalid,
-  /// then the resulting string only names the type ID.
-  /// @param type_id the SPIR-V ID for the type
-  /// @returns a string description of the type.
-  std::string ShowType(uint32_t type_id);
-
-  /// Builds the internal representation of the SPIR-V module.
-  /// Assumes the module is somewhat well-formed.  Normally you
-  /// would want to validate the SPIR-V module before attempting
-  /// to build this internal representation. Also computes a topological
-  /// ordering of the functions.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if the parser is still successful.
-  bool BuildInternalModule();
-
-  /// Walks the internal representation of the module to populate
-  /// the AST form of the module.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if the parser is still successful.
-  bool ParseInternalModule();
-
-  /// Records line numbers for each instruction.
-  void RegisterLineNumbers();
-
-  /// Walks the internal representation of the module, except for function
-  /// definitions, to populate the AST form of the module.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if the parser is still successful.
-  bool ParseInternalModuleExceptFunctions();
-
-  /// Destroys the internal representation of the SPIR-V module.
-  void ResetInternalModule();
-
-  /// Registers extended instruction imports.  Only "GLSL.std.450" is supported.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterExtendedInstructionImports();
-
-  /// Returns true when the given instruction is an extended instruction
-  /// for GLSL.std.450.
-  /// @param inst a SPIR-V instruction
-  /// @returns true if its an SpvOpExtInst for GLSL.std.450
-  bool IsGlslExtendedInstruction(const spvtools::opt::Instruction& inst) const;
-
-  /// Returns true when the given instruction is an extended instruction
-  /// from an ignored extended instruction set.
-  /// @param inst a SPIR-V instruction
-  /// @returns true if its an SpvOpExtInst for an ignored extended instruction
-  bool IsIgnoredExtendedInstruction(
-      const spvtools::opt::Instruction& inst) const;
-
-  /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
-  /// Also synthesizes struct field names.  Ensures uniqueness for names for
-  /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterUserAndStructMemberNames();
-
-  /// Register the WorkgroupSize builtin and its associated constant value.
-  /// @returns true if parser is still successful.
-  bool RegisterWorkgroupSizeBuiltin();
-
-  /// @returns the workgroup size builtin
-  const WorkgroupSizeInfo& workgroup_size_builtin() {
-    return workgroup_size_builtin_;
-  }
-
-  /// Register entry point information.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterEntryPoints();
-
-  /// Register Tint AST types for SPIR-V types, including type aliases as
-  /// needed.  This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterTypes();
-
-  /// Fail if there are any module-scope pointer values other than those
-  /// declared by OpVariable.
-  /// @returns true if parser is still successful.
-  bool RejectInvalidPointerRoots();
-
-  /// Register sampler and texture usage for memory object declarations.
-  /// This must be called after we've registered line numbers for all
-  /// instructions. This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterHandleUsage();
-
-  /// Emit const definitions for scalar specialization constants generated
-  /// by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool EmitScalarSpecConstants();
-
-  /// Emits module-scope variables.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool EmitModuleScopeVariables();
-
-  /// Emits functions, with callees preceding their callers.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool EmitFunctions();
-
-  /// Emits a single function, if it has a body.
-  /// This is a no-op if the parser has already failed.
-  /// @param f the function to emit
-  /// @returns true if parser is still successful.
-  bool EmitFunction(const spvtools::opt::Function& f);
-
-  /// Returns the integer constant for the array size of the given variable.
-  /// @param var_id SPIR-V ID for an array variable
-  /// @returns the integer constant for its array size, or nullptr.
-  const spvtools::opt::analysis::IntConstant* GetArraySize(uint32_t var_id);
-
-  /// Returns the member name for the struct member.
-  /// @param struct_type the parser's structure type.
-  /// @param member_index the member index
-  /// @returns the field name
-  std::string GetMemberName(const Struct& struct_type, int member_index);
-
-  /// Returns the SPIR-V decorations for pipeline IO, if any, on a struct
-  /// member.
-  /// @param struct_type the parser's structure type.
-  /// @param member_index the member index
-  /// @returns a list of SPIR-V decorations.
-  DecorationList GetMemberPipelineDecorations(const Struct& struct_type,
-                                              int member_index);
-
-  /// Creates an AST Variable node for a SPIR-V ID, including any attached
-  /// decorations, unless it's an ignorable builtin variable.
-  /// @param id the SPIR-V result ID
-  /// @param sc the storage class, which cannot be ast::StorageClass::kNone
-  /// @param storage_type the storage type of the variable
-  /// @param is_const if true, the variable is const
-  /// @param is_overridable if true, the variable is pipeline-overridable
-  /// @param constructor the variable constructor
-  /// @param decorations the variable decorations
-  /// @returns a new Variable node, or null in the ignorable variable case and
-  /// in the error case
-  ast::Variable* MakeVariable(uint32_t id,
-                              ast::StorageClass sc,
-                              const Type* storage_type,
-                              bool is_const,
-                              bool is_overridable,
-                              const ast::Expression* constructor,
-                              ast::AttributeList decorations);
-
-  /// Returns true if a constant expression can be generated.
-  /// @param id the SPIR-V ID of the value
-  /// @returns true if a constant expression can be generated
-  bool CanMakeConstantExpression(uint32_t id);
-
-  /// Creates an AST expression node for a SPIR-V ID.  This is valid to call
-  /// when `CanMakeConstantExpression` returns true.
-  /// @param id the SPIR-V ID of the constant
-  /// @returns a new expression
-  TypedExpression MakeConstantExpression(uint32_t id);
-
-  /// Creates an AST expression node for a scalar SPIR-V constant.
-  /// @param source the source location
-  /// @param ast_type the AST type for the value
-  /// @param spirv_const the internal representation of the SPIR-V constant.
-  /// @returns a new expression
-  TypedExpression MakeConstantExpressionForScalarSpirvConstant(
-      Source source,
-      const Type* ast_type,
-      const spvtools::opt::analysis::Constant* spirv_const);
-
-  /// Creates an AST expression node for the null value for the given type.
-  /// @param type the AST type
-  /// @returns a new expression
-  const ast::Expression* MakeNullValue(const Type* type);
-
-  /// Make a typed expression for the null value for the given type.
-  /// @param type the AST type
-  /// @returns a new typed expression
-  TypedExpression MakeNullExpression(const Type* type);
-
-  /// Converts a given expression to the signedness demanded for an operand
-  /// of the given SPIR-V instruction, if required.  If the instruction assumes
-  /// signed integer operands, and `expr` is unsigned, then return an
-  /// as-cast expression converting it to signed. Otherwise, return
-  /// `expr` itself.  Similarly, convert as required from unsigned
-  /// to signed. Assumes all SPIR-V types have been mapped to AST types.
-  /// @param inst the SPIR-V instruction
-  /// @param expr an expression
-  /// @returns expr, or a cast of expr
-  TypedExpression RectifyOperandSignedness(
-      const spvtools::opt::Instruction& inst,
-      TypedExpression&& expr);
-
-  /// Converts a second operand to the signedness of the first operand
-  /// of a binary operator, if the WGSL operator requires they be the same.
-  /// Returns the converted expression, or the original expression if the
-  /// conversion is not needed.
-  /// @param inst the SPIR-V instruction
-  /// @param first_operand_type the type of the first operand to the instruction
-  /// @param second_operand_expr the second operand of the instruction
-  /// @returns second_operand_expr, or a cast of it
-  TypedExpression RectifySecondOperandSignedness(
-      const spvtools::opt::Instruction& inst,
-      const Type* first_operand_type,
-      TypedExpression&& second_operand_expr);
-
-  /// Returns the "forced" result type for the given SPIR-V instruction.
-  /// If the WGSL result type for an operation has a more strict rule than
-  /// requried by SPIR-V, then we say the result type is "forced".  This occurs
-  /// for signed integer division (OpSDiv), for example, where the result type
-  /// in WGSL must match the operand types.
-  /// @param inst the SPIR-V instruction
-  /// @param first_operand_type the AST type for the first operand.
-  /// @returns the forced AST result type, or nullptr if no forcing is required.
-  const Type* ForcedResultType(const spvtools::opt::Instruction& inst,
-                               const Type* first_operand_type);
-
-  /// Returns a signed integer scalar or vector type matching the shape (scalar,
-  /// vector, and component bit width) of another type, which itself is a
-  /// numeric scalar or vector. Returns null if the other type does not meet the
-  /// requirement.
-  /// @param other the type whose shape must be matched
-  /// @returns the signed scalar or vector type
-  const Type* GetSignedIntMatchingShape(const Type* other);
-
-  /// Returns a signed integer scalar or vector type matching the shape (scalar,
-  /// vector, and component bit width) of another type, which itself is a
-  /// numeric scalar or vector. Returns null if the other type does not meet the
-  /// requirement.
-  /// @param other the type whose shape must be matched
-  /// @returns the unsigned scalar or vector type
-  const Type* GetUnsignedIntMatchingShape(const Type* other);
-
-  /// Wraps the given expression in an as-cast to the given expression's type,
-  /// when the underlying operation produces a forced result type different
-  /// from the expression's result type. Otherwise, returns the given expression
-  /// unchanged.
-  /// @param expr the expression to pass through or to wrap
-  /// @param inst the SPIR-V instruction
-  /// @param first_operand_type the AST type for the first operand.
-  /// @returns the forced AST result type, or nullptr if no forcing is required.
-  TypedExpression RectifyForcedResultType(
-      TypedExpression expr,
-      const spvtools::opt::Instruction& inst,
-      const Type* first_operand_type);
-
-  /// Returns the given expression, but ensuring it's an unsigned type of the
-  /// same shape as the operand. Wraps the expression with a bitcast if needed.
-  /// Assumes the given expresion is a integer scalar or vector.
-  /// @param expr an integer scalar or integer vector expression.
-  /// @return the potentially cast TypedExpression
-  TypedExpression AsUnsigned(TypedExpression expr);
-
-  /// Returns the given expression, but ensuring it's a signed type of the
-  /// same shape as the operand. Wraps the expression with a bitcast if needed.
-  /// Assumes the given expresion is a integer scalar or vector.
-  /// @param expr an integer scalar or integer vector expression.
-  /// @return the potentially cast TypedExpression
-  TypedExpression AsSigned(TypedExpression expr);
-
-  /// Bookkeeping used for tracking the "position" builtin variable.
-  struct BuiltInPositionInfo {
-    /// The ID for the gl_PerVertex struct containing the Position builtin.
-    uint32_t struct_type_id = 0;
-    /// The member index for the Position builtin within the struct.
-    uint32_t position_member_index = 0;
-    /// The member index for the PointSize builtin within the struct.
-    uint32_t pointsize_member_index = 0;
-    /// The ID for the member type, which should map to vec4<f32>.
-    uint32_t position_member_type_id = 0;
-    /// The ID of the type of a pointer to the struct in the Output storage
-    /// class class.
-    uint32_t pointer_type_id = 0;
-    /// The SPIR-V storage class.
-    SpvStorageClass storage_class = SpvStorageClassOutput;
-    /// The ID of the type of a pointer to the Position member.
-    uint32_t position_member_pointer_type_id = 0;
-    /// The ID of the gl_PerVertex variable, if it was declared.
-    /// We'll use this for the gl_Position variable instead.
-    uint32_t per_vertex_var_id = 0;
-    /// The ID of the initializer to gl_PerVertex, if any.
-    uint32_t per_vertex_var_init_id = 0;
-  };
-  /// @returns info about the gl_Position builtin variable.
-  const BuiltInPositionInfo& GetBuiltInPositionInfo() {
-    return builtin_position_;
-  }
-
-  /// Returns the source record for the SPIR-V instruction with the given
-  /// result ID.
-  /// @param id the SPIR-V result id.
-  /// @return the Source record, or a default one
-  Source GetSourceForResultIdForTest(uint32_t id) const;
-  /// Returns the source record for the given instruction.
-  /// @param inst the SPIR-V instruction
-  /// @return the Source record, or a default one
-  Source GetSourceForInst(const spvtools::opt::Instruction* inst) const;
-
-  /// @param str a candidate identifier
-  /// @returns true if the given string is a valid WGSL identifier.
-  static bool IsValidIdentifier(const std::string& str);
-
-  /// Returns true if the given SPIR-V ID is a declared specialization constant,
-  /// generated by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant
-  /// @param id a SPIR-V result ID
-  /// @returns true if the ID is a scalar spec constant.
-  bool IsScalarSpecConstant(uint32_t id) {
-    return scalar_spec_constants_.find(id) != scalar_spec_constants_.end();
-  }
-
-  /// For a SPIR-V ID that might define a sampler, image, or sampled image
-  /// value, return the SPIR-V instruction that represents the memory object
-  /// declaration for the object.  If we encounter an OpSampledImage along the
-  /// way, follow the image operand when follow_image is true; otherwise follow
-  /// the sampler operand. Returns nullptr if we can't trace back to a memory
-  /// object declaration.  Emits an error and returns nullptr when the scan
-  /// fails due to a malformed module. This method can be used any time after
-  /// BuildInternalModule has been invoked.
-  /// @param id the SPIR-V ID of the sampler, image, or sampled image
-  /// @param follow_image indicates whether to follow the image operand of
-  /// OpSampledImage
-  /// @returns the memory object declaration for the handle, or nullptr
-  const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(
-      uint32_t id,
-      bool follow_image);
-
-  /// Returns the handle usage for a memory object declaration.
-  /// @param id SPIR-V ID of a sampler or image OpVariable or
-  /// OpFunctionParameter
-  /// @returns the handle usage, or an empty usage object.
-  Usage GetHandleUsage(uint32_t id) const;
-
-  /// Returns the SPIR-V type for the sampler or image type for the given
-  /// variable in UniformConstant storage class, or function parameter pointing
-  /// into the UniformConstant storage class .  Returns null and emits an
-  /// error on failure.
-  /// @param var the OpVariable instruction or OpFunctionParameter
-  /// @returns the Tint AST type for the sampler or texture, or null on error
-  const spvtools::opt::Instruction*
-  GetSpirvTypeForHandleMemoryObjectDeclaration(
-      const spvtools::opt::Instruction& var);
-
-  /// Returns the AST type for the pointer-to-sampler or pointer-to-texture type
-  /// for the given variable in UniformConstant storage class.  Returns null and
-  /// emits an error on failure.
-  /// @param var the OpVariable instruction
-  /// @returns the Tint AST type for the poiner-to-{sampler|texture} or null on
-  /// error
-  const Pointer* GetTypeForHandleVar(const spvtools::opt::Instruction& var);
-
-  /// Returns the channel component type corresponding to the given image
-  /// format.
-  /// @param format image texel format
-  /// @returns the component type, one of f32, i32, u32
-  const Type* GetComponentTypeForFormat(ast::TexelFormat format);
-
-  /// Returns the number of channels in the given image format.
-  /// @param format image texel format
-  /// @returns the number of channels in the format
-  unsigned GetChannelCountForFormat(ast::TexelFormat format);
-
-  /// Returns the texel type corresponding to the given image format.
-  /// This the WGSL type used for the texel parameter to textureStore.
-  /// It's always a 4-element vector.
-  /// @param format image texel format
-  /// @returns the texel format
-  const Type* GetTexelTypeForFormat(ast::TexelFormat format);
-
-  /// Returns the SPIR-V instruction with the given ID, or nullptr.
-  /// @param id the SPIR-V result ID
-  /// @returns the instruction, or nullptr on error
-  const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const;
-
-  /// A map of SPIR-V identifiers to builtins
-  using BuiltInsMap = std::unordered_map<uint32_t, SpvBuiltIn>;
-
-  /// @returns a map of builtins that should be handled specially by code
-  /// generation. Either the builtin does not exist in WGSL, or a type
-  /// conversion must be implemented on load and store.
-  const BuiltInsMap& special_builtins() const { return special_builtins_; }
-
-  /// @param builtin the SPIR-V builtin variable kind
-  /// @returns the SPIR-V ID for the variable defining the given builtin, or 0
-  uint32_t IdForSpecialBuiltIn(SpvBuiltIn builtin) const {
-    // Do a linear search.
-    for (const auto& entry : special_builtins_) {
-      if (entry.second == builtin) {
-        return entry.first;
-      }
-    }
-    return 0;
-  }
-
-  /// @param entry_point the SPIR-V ID of an entry point.
-  /// @returns the entry point info for the given ID
-  const std::vector<EntryPointInfo>& GetEntryPointInfo(uint32_t entry_point) {
-    return function_to_ep_info_[entry_point];
-  }
-
-  /// @returns the SPIR-V binary.
-  const std::vector<uint32_t>& spv_binary() { return spv_binary_; }
-
- private:
-  /// Converts a specific SPIR-V type to a Tint type. Integer case
-  const Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Float case
-  const Type* ConvertType(const spvtools::opt::analysis::Float* float_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Vector case
-  const Type* ConvertType(const spvtools::opt::analysis::Vector* vec_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Matrix case
-  const Type* ConvertType(const spvtools::opt::analysis::Matrix* mat_ty);
-  /// Converts a specific SPIR-V type to a Tint type. RuntimeArray case
-  /// Distinct SPIR-V array types map to distinct Tint array types.
-  /// @param rtarr_ty the Tint type
-  const Type* ConvertType(
-      uint32_t type_id,
-      const spvtools::opt::analysis::RuntimeArray* rtarr_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Array case
-  /// Distinct SPIR-V array types map to distinct Tint array types.
-  /// @param arr_ty the Tint type
-  const Type* ConvertType(uint32_t type_id,
-                          const spvtools::opt::analysis::Array* arr_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Struct case.
-  /// SPIR-V allows distinct struct type definitions for two OpTypeStruct
-  /// that otherwise have the same set of members (and struct and member
-  /// decorations).  However, the SPIRV-Tools always produces a unique
-  /// `spvtools::opt::analysis::Struct` object in these cases. For this type
-  /// conversion, we need to have the original SPIR-V ID because we can't always
-  /// recover it from the optimizer's struct type object. This also lets us
-  /// preserve member names, which are given by OpMemberName which is normally
-  /// not significant to the optimizer's module representation.
-  /// @param type_id the SPIR-V ID for the type.
-  /// @param struct_ty the Tint type
-  const Type* ConvertType(uint32_t type_id,
-                          const spvtools::opt::analysis::Struct* struct_ty);
-  /// Converts a specific SPIR-V type to a Tint type. Pointer / Reference case
-  /// The pointer to gl_PerVertex maps to nullptr, and instead is recorded
-  /// in member #builtin_position_.
-  /// @param type_id the SPIR-V ID for the type.
-  /// @param ptr_as if PtrAs::Ref then a Reference will be returned, otherwise
-  /// Pointer
-  /// @param ptr_ty the Tint type
-  const Type* ConvertType(uint32_t type_id,
-                          PtrAs ptr_as,
-                          const spvtools::opt::analysis::Pointer* ptr_ty);
-
-  /// If `type` is a signed integral, or vector of signed integral,
-  /// returns the unsigned type, otherwise returns `type`.
-  /// @param type the possibly signed type
-  /// @returns the unsigned type
-  const Type* UnsignedTypeFor(const Type* type);
-
-  /// If `type` is a unsigned integral, or vector of unsigned integral,
-  /// returns the signed type, otherwise returns `type`.
-  /// @param type the possibly unsigned type
-  /// @returns the signed type
-  const Type* SignedTypeFor(const Type* type);
-
-  /// Parses the array or runtime-array decorations. Sets 0 if no explicit
-  /// stride was found, and therefore the implicit stride should be used.
-  /// @param spv_type the SPIR-V array or runtime-array type.
-  /// @param array_stride pointer to the array stride
-  /// @returns true on success.
-  bool ParseArrayDecorations(const spvtools::opt::analysis::Type* spv_type,
-                             uint32_t* array_stride);
-
-  /// Creates a new `ast::Node` owned by the ProgramBuilder.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  T* create(ARGS&&... args) {
-    return builder_.create<T>(std::forward<ARGS>(args)...);
-  }
-
-  // The SPIR-V binary we're parsing
-  std::vector<uint32_t> spv_binary_;
-
-  // The program builder.
-  ProgramBuilder builder_;
-
-  // The type manager.
-  TypeManager ty_;
-
-  // Is the parse successful?
-  bool success_ = true;
-  // Collector for diagnostic messages.
-  std::stringstream errors_;
-  FailStream fail_stream_;
-  spvtools::MessageConsumer message_consumer_;
-
-  // An object used to store and generate names for SPIR-V objects.
-  Namer namer_;
-  // An object used to convert SPIR-V enums to Tint enums
-  EnumConverter enum_converter_;
-
-  // The internal representation of the SPIR-V module and its context.
-  spvtools::Context tools_context_;
-  // All the state is owned by ir_context_.
-  std::unique_ptr<spvtools::opt::IRContext> ir_context_;
-  // The following are borrowed pointers to the internal state of ir_context_.
-  spvtools::opt::Module* module_ = nullptr;
-  spvtools::opt::analysis::DefUseManager* def_use_mgr_ = nullptr;
-  spvtools::opt::analysis::ConstantManager* constant_mgr_ = nullptr;
-  spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr;
-  spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr;
-
-  // The functions ordered so that callees precede their callers.
-  std::vector<const spvtools::opt::Function*> topologically_ordered_functions_;
-
-  // Maps an instruction to its source location. If no OpLine information
-  // is in effect for the instruction, map the instruction to its position
-  // in the SPIR-V module, counting by instructions, where the first
-  // instruction is line 1.
-  std::unordered_map<const spvtools::opt::Instruction*, Source::Location>
-      inst_source_;
-
-  // The set of IDs that are imports of the GLSL.std.450 extended instruction
-  // sets.
-  std::unordered_set<uint32_t> glsl_std_450_imports_;
-  // The set of IDs of imports that are ignored. For example, any
-  // "NonSemanticInfo." import is ignored.
-  std::unordered_set<uint32_t> ignored_imports_;
-
-  // The SPIR-V IDs of structure types that are the store type for buffer
-  // variables, either UBO or SSBO.
-  std::unordered_set<uint32_t> struct_types_for_buffers_;
-
-  // Bookkeeping for the gl_Position builtin.
-  // In Vulkan SPIR-V, it's the 0 member of the gl_PerVertex structure.
-  // But in WGSL we make a module-scope variable:
-  //    [[position]] var<in> gl_Position : vec4<f32>;
-  // The builtin variable was detected if and only if the struct_id is non-zero.
-  BuiltInPositionInfo builtin_position_;
-
-  // SPIR-V type IDs that are either:
-  // - a struct type decorated by BufferBlock
-  // - an array, runtime array containing one of these
-  // - a pointer type to one of these
-  // These are the types "enclosing" a buffer block with the old style
-  // representation: using Uniform storage class and BufferBlock decoration
-  // on the struct.  The new style is to use the StorageBuffer storage class
-  // and Block decoration.
-  std::unordered_set<uint32_t> remap_buffer_block_type_;
-
-  // The ast::Struct type names with only read-only members.
-  std::unordered_set<Symbol> read_only_struct_types_;
-
-  // The IDs of scalar spec constants
-  std::unordered_set<uint32_t> scalar_spec_constants_;
-
-  // Maps function_id to a list of entrypoint information
-  std::unordered_map<uint32_t, std::vector<EntryPointInfo>>
-      function_to_ep_info_;
-
-  // Maps from a SPIR-V ID to its underlying memory object declaration,
-  // following image paths. This a memoization table for
-  // GetMemoryObjectDeclarationForHandle. (A SPIR-V memory object declaration is
-  // an OpVariable or an OpFunctinParameter with pointer type).
-  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
-      mem_obj_decl_image_;
-  // Maps from a SPIR-V ID to its underlying memory object declaration,
-  // following sampler paths. This a memoization table for
-  // GetMemoryObjectDeclarationForHandle.
-  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
-      mem_obj_decl_sampler_;
-
-  // Maps a memory-object-declaration instruction to any sampler or texture
-  // usages implied by usages of the memory-object-declaration.
-  std::unordered_map<const spvtools::opt::Instruction*, Usage> handle_usage_;
-  // The inferred pointer type for the given handle variable.
-  std::unordered_map<const spvtools::opt::Instruction*, const Pointer*>
-      handle_type_;
-
-  // Set of symbols of declared type that have been added, used to avoid
-  // adding duplicates.
-  std::unordered_set<Symbol> declared_types_;
-
-  // Maps a struct type name to the SPIR-V ID for the structure type.
-  std::unordered_map<Symbol, uint32_t> struct_id_for_symbol_;
-
-  /// Maps the SPIR-V ID of a module-scope builtin variable that should be
-  /// ignored or type-converted, to its builtin kind.
-  /// See also BuiltInPositionInfo which is a separate mechanism for a more
-  /// complex case of replacing an entire structure.
-  BuiltInsMap special_builtins_;
-
-  /// Info about the WorkgroupSize builtin. If it's not present, then the 'id'
-  /// field will be 0. Sadly, in SPIR-V right now, there's only one workgroup
-  /// size object in the module.
-  WorkgroupSizeInfo workgroup_size_builtin_;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_PARSER_IMPL_H_
diff --git a/src/reader/spirv/parser_impl_barrier_test.cc b/src/reader/spirv/parser_impl_barrier_test.cc
deleted file mode 100644
index 69ebafd..0000000
--- a/src/reader/spirv/parser_impl_barrier_test.cc
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-#include "src/sem/call.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-using ::testing::Not;
-using ::testing::StartsWith;
-
-Program ParseAndBuild(std::string spirv) {
-  const char* preamble = R"(OpCapability Shader
-            OpMemoryModel Logical GLSL450
-            OpEntryPoint GLCompute %main "main"
-            OpExecutionMode %main LocalSize 1 1 1
-            OpName %main "main"
-)";
-
-  auto p = std::make_unique<ParserImpl>(test::Assemble(preamble + spirv));
-  if (!p->BuildAndParseInternalModule()) {
-    ProgramBuilder builder;
-    builder.Diagnostics().add_error(diag::System::Reader, p->error());
-    return Program(std::move(builder));
-  }
-  return p->program();
-}
-
-TEST_F(SpvParserTest, WorkgroupBarrier) {
-  auto program = ParseAndBuild(R"(
-               OpName %helper "helper"
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-   %uint_264 = OpConstant %uint 264
-     %helper = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_2 %uint_264
-               OpReturn
-               OpFunctionEnd
-     %main = OpFunction %void None %1
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )");
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-  auto* helper =
-      program.AST().Functions().Find(program.Symbols().Get("helper"));
-  ASSERT_NE(helper, nullptr);
-  ASSERT_GT(helper->body->statements.size(), 0u);
-  auto* call = helper->body->statements[0]->As<ast::CallStatement>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->expr->args.size(), 0u);
-  auto* sem_call = program.Sem().Get(call->expr);
-  ASSERT_NE(sem_call, nullptr);
-  auto* builtin = sem_call->Target()->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-  EXPECT_EQ(builtin->Type(), sem::BuiltinType::kWorkgroupBarrier);
-}
-
-TEST_F(SpvParserTest, StorageBarrier) {
-  auto program = ParseAndBuild(R"(
-               OpName %helper "helper"
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_1 = OpConstant %uint 1
-    %uint_72 = OpConstant %uint 72
-     %helper = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_1 %uint_72
-               OpReturn
-               OpFunctionEnd
-       %main = OpFunction %void None %1
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )");
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-  auto* helper =
-      program.AST().Functions().Find(program.Symbols().Get("helper"));
-  ASSERT_NE(helper, nullptr);
-  ASSERT_GT(helper->body->statements.size(), 0u);
-  auto* call = helper->body->statements[0]->As<ast::CallStatement>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->expr->args.size(), 0u);
-  auto* sem_call = program.Sem().Get(call->expr);
-  ASSERT_NE(sem_call, nullptr);
-  auto* builtin = sem_call->Target()->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-  EXPECT_EQ(builtin->Type(), sem::BuiltinType::kStorageBarrier);
-}
-
-TEST_F(SpvParserTest, ErrBarrierInvalidExecution) {
-  auto program = ParseAndBuild(R"(
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_0 = OpConstant %uint 0
-     %uint_2 = OpConstant %uint 2
-   %uint_264 = OpConstant %uint 264
-       %main = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_0 %uint_2 %uint_264
-               OpReturn
-               OpFunctionEnd
-  )");
-  EXPECT_FALSE(program.IsValid());
-  EXPECT_THAT(program.Diagnostics().str(),
-              HasSubstr("unsupported control barrier execution scope"));
-}
-
-TEST_F(SpvParserTest, ErrBarrierSemanticsMissingAcquireRelease) {
-  auto program = ParseAndBuild(R"(
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_0 = OpConstant %uint 0
-       %main = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_2 %uint_0
-               OpReturn
-               OpFunctionEnd
-  )");
-  EXPECT_FALSE(program.IsValid());
-  EXPECT_THAT(
-      program.Diagnostics().str(),
-      HasSubstr("control barrier semantics requires acquire and release"));
-}
-
-TEST_F(SpvParserTest, ErrBarrierInvalidSemantics) {
-  auto program = ParseAndBuild(R"(
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_9 = OpConstant %uint 9
-       %main = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_2 %uint_9
-               OpReturn
-               OpFunctionEnd
-  )");
-  EXPECT_FALSE(program.IsValid());
-  EXPECT_THAT(program.Diagnostics().str(),
-              HasSubstr("unsupported control barrier semantics"));
-}
-
-TEST_F(SpvParserTest, ErrWorkgroupBarrierInvalidMemory) {
-  auto program = ParseAndBuild(R"(
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_8 = OpConstant %uint 8
-   %uint_264 = OpConstant %uint 264
-       %main = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_8 %uint_264
-               OpReturn
-               OpFunctionEnd
-  )");
-  EXPECT_FALSE(program.IsValid());
-  EXPECT_THAT(program.Diagnostics().str(),
-              HasSubstr("workgroupBarrier requires workgroup memory scope"));
-}
-
-TEST_F(SpvParserTest, ErrStorageBarrierInvalidMemory) {
-  auto program = ParseAndBuild(R"(
-       %void = OpTypeVoid
-          %1 = OpTypeFunction %void
-       %uint = OpTypeInt 32 0
-     %uint_2 = OpConstant %uint 2
-     %uint_8 = OpConstant %uint 8
-    %uint_72 = OpConstant %uint 72
-       %main = OpFunction %void None %1
-          %4 = OpLabel
-               OpControlBarrier %uint_2 %uint_8 %uint_72
-               OpReturn
-               OpFunctionEnd
-  )");
-  EXPECT_FALSE(program.IsValid());
-  EXPECT_THAT(program.Diagnostics().str(),
-              HasSubstr("storageBarrier requires device memory scope"));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_convert_member_decoration_test.cc b/src/reader/spirv/parser_impl_convert_member_decoration_test.cc
deleted file mode 100644
index bdca0a5..0000000
--- a/src/reader/spirv/parser_impl_convert_member_decoration_test.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Empty) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result = p->ConvertMemberDecoration(1, 1, nullptr, {});
-  EXPECT_TRUE(result.empty());
-  EXPECT_THAT(p->error(), Eq("malformed SPIR-V decoration: it's empty"));
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithoutOperand) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result =
-      p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset});
-  EXPECT_TRUE(result.empty());
-  EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
-                             "operand, has 0: member 13 of SPIR-V type 12"));
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithTooManyOperands) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result =
-      p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset, 3, 4});
-  EXPECT_TRUE(result.empty());
-  EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
-                             "operand, has 2: member 13 of SPIR-V type 12"));
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Offset) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result =
-      p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationOffset, 8});
-  ASSERT_FALSE(result.empty());
-  EXPECT_TRUE(result[0]->Is<ast::StructMemberOffsetAttribute>());
-  auto* offset_deco = result[0]->As<ast::StructMemberOffsetAttribute>();
-  ASSERT_NE(offset_deco, nullptr);
-  EXPECT_EQ(offset_deco->offset, 8u);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x2_Stride_Natural) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  spirv::F32 f32;
-  spirv::Matrix matrix(&f32, 2, 2);
-  auto result =
-      p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 8});
-  EXPECT_TRUE(result.empty());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x2_Stride_Custom) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  spirv::F32 f32;
-  spirv::Matrix matrix(&f32, 2, 2);
-  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
-                                           {SpvDecorationMatrixStride, 16});
-  ASSERT_FALSE(result.empty());
-  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
-  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
-  ASSERT_NE(stride_deco, nullptr);
-  EXPECT_EQ(stride_deco->stride, 16u);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x4_Stride_Natural) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  spirv::F32 f32;
-  spirv::Matrix matrix(&f32, 2, 4);
-  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
-                                           {SpvDecorationMatrixStride, 16});
-  EXPECT_TRUE(result.empty());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x4_Stride_Custom) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  spirv::F32 f32;
-  spirv::Matrix matrix(&f32, 2, 4);
-  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
-                                           {SpvDecorationMatrixStride, 64});
-  ASSERT_FALSE(result.empty());
-  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
-  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
-  ASSERT_NE(stride_deco, nullptr);
-  EXPECT_EQ(stride_deco->stride, 64u);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x3_Stride_Custom) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  spirv::F32 f32;
-  spirv::Matrix matrix(&f32, 2, 3);
-  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
-                                           {SpvDecorationMatrixStride, 32});
-  ASSERT_FALSE(result.empty());
-  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
-  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
-  ASSERT_NE(stride_deco, nullptr);
-  EXPECT_EQ(stride_deco->stride, 32u);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_RelaxedPrecision) {
-  // WGSL does not support relaxed precision. Drop it.
-  // It's functionally correct to use full precision f32 instead of
-  // relaxed precision f32.
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result = p->ConvertMemberDecoration(1, 1, nullptr,
-                                           {SpvDecorationRelaxedPrecision});
-  EXPECT_TRUE(result.empty());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertMemberDecoration_UnhandledDecoration) {
-  auto p = parser(std::vector<uint32_t>{});
-
-  auto result = p->ConvertMemberDecoration(12, 13, nullptr, {12345678});
-  EXPECT_TRUE(result.empty());
-  EXPECT_THAT(p->error(), Eq("unhandled member decoration: 12345678 on member "
-                             "13 of SPIR-V type 12"));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
deleted file mode 100644
index 6303b1a..0000000
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ /dev/null
@@ -1,935 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-std::string Preamble() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %main "x_100"
-    OpExecutionMode %main OriginUpperLeft
-  )";
-}
-
-std::string MainBody() {
-  return R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %main = OpFunction %void None %voidfn
-    %main_entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-  )";
-}
-
-TEST_F(SpvParserTest, ConvertType_PreservesExistingFailure) {
-  auto p = parser(std::vector<uint32_t>{});
-  p->Fail() << "boing";
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(), Eq("boing"));
-}
-
-TEST_F(SpvParserTest, ConvertType_RequiresInternalRepresntation) {
-  auto p = parser(std::vector<uint32_t>{});
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(
-      p->error(),
-      Eq("ConvertType called when the internal module has not been built"));
-}
-
-TEST_F(SpvParserTest, ConvertType_NotAnId) {
-  auto assembly = Preamble() + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(900);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_EQ(nullptr, type);
-  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 900"));
-}
-
-TEST_F(SpvParserTest, ConvertType_IdExistsButIsNotAType) {
-  auto assembly = R"(
-     OpCapability Shader
-     %1 = OpExtInstImport "GLSL.std.450"
-     OpMemoryModel Logical Simple
-     OpEntryPoint Fragment %main "x_100"
-     OpExecutionMode %main OriginUpperLeft
-)" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(1);
-  EXPECT_EQ(nullptr, type);
-  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 1"));
-}
-
-TEST_F(SpvParserTest, ConvertType_UnhandledType) {
-  // Pipes are an OpenCL type. Tint doesn't support them.
-  auto p = parser(test::Assemble("%70 = OpTypePipe WriteOnly"));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(70);
-  EXPECT_EQ(nullptr, type);
-  EXPECT_THAT(p->error(),
-              Eq("unknown SPIR-V type with ID 70: %70 = OpTypePipe WriteOnly"));
-}
-
-TEST_F(SpvParserTest, ConvertType_Void) {
-  auto p = parser(test::Assemble(Preamble() + "%1 = OpTypeVoid" + R"(
-   %voidfn = OpTypeFunction %1
-   %main = OpFunction %1 None %voidfn
-   %entry = OpLabel
-   OpReturn
-   OpFunctionEnd
-  )"));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(1);
-  EXPECT_TRUE(type->Is<Void>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_Bool) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%100 = OpTypeBool" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(100);
-  EXPECT_TRUE(type->Is<Bool>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_I32) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%2 = OpTypeInt 32 1" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(2);
-  EXPECT_TRUE(type->Is<I32>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_U32) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%3 = OpTypeInt 32 0" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<U32>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_F32) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%4 = OpTypeFloat 32" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(4);
-  EXPECT_TRUE(type->Is<F32>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_BadIntWidth) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%5 = OpTypeInt 17 1" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(5);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(), Eq("unhandled integer width: 17"));
-}
-
-TEST_F(SpvParserTest, ConvertType_BadFloatWidth) {
-  auto p =
-      parser(test::Assemble(Preamble() + "%6 = OpTypeFloat 19" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(6);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(), Eq("unhandled float width: 19"));
-}
-
-TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidVectorElement) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %5 = OpTypePipe ReadOnly
-    %20 = OpTypeVector %5 2
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(20);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(), Eq("unknown SPIR-V type: 5"));
-}
-
-TEST_F(SpvParserTest, ConvertType_VecOverF32) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %float = OpTypeFloat 32
-    %20 = OpTypeVector %float 2
-    %30 = OpTypeVector %float 3
-    %40 = OpTypeVector %float 4
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* v2xf32 = p->ConvertType(20);
-  EXPECT_TRUE(v2xf32->Is<Vector>());
-  EXPECT_TRUE(v2xf32->As<Vector>()->type->Is<F32>());
-  EXPECT_EQ(v2xf32->As<Vector>()->size, 2u);
-
-  auto* v3xf32 = p->ConvertType(30);
-  EXPECT_TRUE(v3xf32->Is<Vector>());
-  EXPECT_TRUE(v3xf32->As<Vector>()->type->Is<F32>());
-  EXPECT_EQ(v3xf32->As<Vector>()->size, 3u);
-
-  auto* v4xf32 = p->ConvertType(40);
-  EXPECT_TRUE(v4xf32->Is<Vector>());
-  EXPECT_TRUE(v4xf32->As<Vector>()->type->Is<F32>());
-  EXPECT_EQ(v4xf32->As<Vector>()->size, 4u);
-
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_VecOverI32) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %int = OpTypeInt 32 1
-    %20 = OpTypeVector %int 2
-    %30 = OpTypeVector %int 3
-    %40 = OpTypeVector %int 4
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* v2xi32 = p->ConvertType(20);
-  EXPECT_TRUE(v2xi32->Is<Vector>());
-  EXPECT_TRUE(v2xi32->As<Vector>()->type->Is<I32>());
-  EXPECT_EQ(v2xi32->As<Vector>()->size, 2u);
-
-  auto* v3xi32 = p->ConvertType(30);
-  EXPECT_TRUE(v3xi32->Is<Vector>());
-  EXPECT_TRUE(v3xi32->As<Vector>()->type->Is<I32>());
-  EXPECT_EQ(v3xi32->As<Vector>()->size, 3u);
-
-  auto* v4xi32 = p->ConvertType(40);
-  EXPECT_TRUE(v4xi32->Is<Vector>());
-  EXPECT_TRUE(v4xi32->As<Vector>()->type->Is<I32>());
-  EXPECT_EQ(v4xi32->As<Vector>()->size, 4u);
-
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_VecOverU32) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %20 = OpTypeVector %uint 2
-    %30 = OpTypeVector %uint 3
-    %40 = OpTypeVector %uint 4
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* v2xu32 = p->ConvertType(20);
-  EXPECT_TRUE(v2xu32->Is<Vector>());
-  EXPECT_TRUE(v2xu32->As<Vector>()->type->Is<U32>());
-  EXPECT_EQ(v2xu32->As<Vector>()->size, 2u);
-
-  auto* v3xu32 = p->ConvertType(30);
-  EXPECT_TRUE(v3xu32->Is<Vector>());
-  EXPECT_TRUE(v3xu32->As<Vector>()->type->Is<U32>());
-  EXPECT_EQ(v3xu32->As<Vector>()->size, 3u);
-
-  auto* v4xu32 = p->ConvertType(40);
-  EXPECT_TRUE(v4xu32->Is<Vector>());
-  EXPECT_TRUE(v4xu32->As<Vector>()->type->Is<U32>());
-  EXPECT_EQ(v4xu32->As<Vector>()->size, 4u);
-
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidMatrixElement) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %5 = OpTypePipe ReadOnly
-    %10 = OpTypeVector %5 2
-    %20 = OpTypeMatrix %10 2
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(20);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(), Eq("unknown SPIR-V type: 5"));
-}
-
-TEST_F(SpvParserTest, ConvertType_MatrixOverF32) {
-  // Matrices are only defined over floats.
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %float = OpTypeFloat 32
-    %v2 = OpTypeVector %float 2
-    %v3 = OpTypeVector %float 3
-    %v4 = OpTypeVector %float 4
-    ; First digit is rows
-    ; Second digit is columns
-    %22 = OpTypeMatrix %v2 2
-    %23 = OpTypeMatrix %v2 3
-    %24 = OpTypeMatrix %v2 4
-    %32 = OpTypeMatrix %v3 2
-    %33 = OpTypeMatrix %v3 3
-    %34 = OpTypeMatrix %v3 4
-    %42 = OpTypeMatrix %v4 2
-    %43 = OpTypeMatrix %v4 3
-    %44 = OpTypeMatrix %v4 4
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* m22 = p->ConvertType(22);
-  EXPECT_TRUE(m22->Is<Matrix>());
-  EXPECT_TRUE(m22->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m22->As<Matrix>()->rows, 2u);
-  EXPECT_EQ(m22->As<Matrix>()->columns, 2u);
-
-  auto* m23 = p->ConvertType(23);
-  EXPECT_TRUE(m23->Is<Matrix>());
-  EXPECT_TRUE(m23->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m23->As<Matrix>()->rows, 2u);
-  EXPECT_EQ(m23->As<Matrix>()->columns, 3u);
-
-  auto* m24 = p->ConvertType(24);
-  EXPECT_TRUE(m24->Is<Matrix>());
-  EXPECT_TRUE(m24->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m24->As<Matrix>()->rows, 2u);
-  EXPECT_EQ(m24->As<Matrix>()->columns, 4u);
-
-  auto* m32 = p->ConvertType(32);
-  EXPECT_TRUE(m32->Is<Matrix>());
-  EXPECT_TRUE(m32->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m32->As<Matrix>()->rows, 3u);
-  EXPECT_EQ(m32->As<Matrix>()->columns, 2u);
-
-  auto* m33 = p->ConvertType(33);
-  EXPECT_TRUE(m33->Is<Matrix>());
-  EXPECT_TRUE(m33->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m33->As<Matrix>()->rows, 3u);
-  EXPECT_EQ(m33->As<Matrix>()->columns, 3u);
-
-  auto* m34 = p->ConvertType(34);
-  EXPECT_TRUE(m34->Is<Matrix>());
-  EXPECT_TRUE(m34->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m34->As<Matrix>()->rows, 3u);
-  EXPECT_EQ(m34->As<Matrix>()->columns, 4u);
-
-  auto* m42 = p->ConvertType(42);
-  EXPECT_TRUE(m42->Is<Matrix>());
-  EXPECT_TRUE(m42->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m42->As<Matrix>()->rows, 4u);
-  EXPECT_EQ(m42->As<Matrix>()->columns, 2u);
-
-  auto* m43 = p->ConvertType(43);
-  EXPECT_TRUE(m43->Is<Matrix>());
-  EXPECT_TRUE(m43->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m43->As<Matrix>()->rows, 4u);
-  EXPECT_EQ(m43->As<Matrix>()->columns, 3u);
-
-  auto* m44 = p->ConvertType(44);
-  EXPECT_TRUE(m44->Is<Matrix>());
-  EXPECT_TRUE(m44->As<Matrix>()->type->Is<F32>());
-  EXPECT_EQ(m44->As<Matrix>()->rows, 4u);
-  EXPECT_EQ(m44->As<Matrix>()->columns, 4u);
-
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_RuntimeArray) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeRuntimeArray %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->UnwrapAll()->Is<Array>());
-  auto* arr_type = type->UnwrapAll()->As<Array>();
-  ASSERT_NE(arr_type, nullptr);
-  EXPECT_EQ(arr_type->size, 0u);
-  EXPECT_EQ(arr_type->stride, 0u);
-  auto* elem_type = arr_type->type;
-  ASSERT_NE(elem_type, nullptr);
-  EXPECT_TRUE(elem_type->Is<U32>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_RuntimeArray_InvalidDecoration) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 Block
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeRuntimeArray %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(
-      p->error(),
-      Eq("invalid array type ID 10: unknown decoration 2 with 1 total words"));
-}
-
-TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_Valid) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 ArrayStride 64
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeRuntimeArray %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  auto* arr_type = type->UnwrapAll()->As<Array>();
-  ASSERT_NE(arr_type, nullptr);
-  EXPECT_EQ(arr_type->size, 0u);
-  EXPECT_EQ(arr_type->stride, 64u);
-}
-
-TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_ZeroIsError) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 ArrayStride 0
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeRuntimeArray %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(),
-              Eq("invalid array type ID 10: ArrayStride can't be 0"));
-}
-
-TEST_F(SpvParserTest, ConvertType_Array) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %uint_42 = OpConstant %uint 42
-    %10 = OpTypeArray %uint %uint_42
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->Is<Array>());
-  auto* arr_type = type->As<Array>();
-  ASSERT_NE(arr_type, nullptr);
-  EXPECT_EQ(arr_type->size, 42u);
-  EXPECT_EQ(arr_type->stride, 0u);
-  auto* elem_type = arr_type->type;
-  ASSERT_NE(elem_type, nullptr);
-  EXPECT_TRUE(elem_type->Is<U32>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantValue) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %uint_42 SpecId 12
-    %uint = OpTypeInt 32 0
-    %uint_42 = OpSpecConstant %uint 42
-    %10 = OpTypeArray %uint %uint_42
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(),
-              Eq("Array type 10 length is a specialization constant"));
-}
-
-TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantExpr) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %uint_42 = OpConstant %uint 42
-    %sum = OpSpecConstantOp %uint IAdd %uint_42 %uint_42
-    %10 = OpTypeArray %uint %sum
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(),
-              Eq("Array type 10 length is a specialization constant"));
-}
-
-// TODO(dneto): Maybe add a test where the length operand is not a constant.
-// E.g. it's the ID of a type.  That won't validate, and the SPIRV-Tools
-// optimizer representation doesn't handle it and asserts out instead.
-
-TEST_F(SpvParserTest, ConvertType_ArrayBadTooBig) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint64 = OpTypeInt 64 0
-    %uint64_big = OpConstant %uint64 5000000000
-    %10 = OpTypeArray %uint64 %uint64_big
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_EQ(type, nullptr);
-  // TODO(dneto): Right now it's rejected earlier in the flow because
-  // we can't even utter the uint64 type.
-  EXPECT_THAT(p->error(), Eq("unhandled integer width: 64"));
-}
-
-TEST_F(SpvParserTest, ConvertType_Array_InvalidDecoration) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 Block
-    %uint = OpTypeInt 32 0
-    %uint_5 = OpConstant %uint 5
-    %10 = OpTypeArray %uint %uint_5
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(
-      p->error(),
-      Eq("invalid array type ID 10: unknown decoration 2 with 1 total words"));
-}
-
-TEST_F(SpvParserTest, ConvertType_ArrayStride_Valid) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %uint_5 = OpConstant %uint 5
-    %10 = OpTypeArray %uint %uint_5
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->UnwrapAll()->Is<Array>());
-  auto* arr_type = type->UnwrapAll()->As<Array>();
-  ASSERT_NE(arr_type, nullptr);
-  EXPECT_EQ(arr_type->stride, 8u);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_ArrayStride_ZeroIsError) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 ArrayStride 0
-    %uint = OpTypeInt 32 0
-    %uint_5 = OpConstant %uint 5
-    %10 = OpTypeArray %uint %uint_5
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(),
-              Eq("invalid array type ID 10: ArrayStride can't be 0"));
-}
-
-TEST_F(SpvParserTest, ConvertType_StructEmpty) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %10 = OpTypeStruct
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(10);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_EQ(p->error(),
-            "WGSL does not support empty structures. can't convert type: %10 = "
-            "OpTypeStruct");
-}
-
-TEST_F(SpvParserTest, ConvertType_StructTwoMembers) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %uint %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->Is<Struct>());
-
-  auto* str = type->Build(p->builder());
-  Program program = p->program();
-  EXPECT_EQ(test::ToString(program, str), "S");
-}
-
-TEST_F(SpvParserTest, ConvertType_StructWithBlockDecoration) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpDecorate %10 Block
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeStruct %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->Is<Struct>());
-
-  auto* str = type->Build(p->builder());
-  Program program = p->program();
-  EXPECT_EQ(test::ToString(program, str), "S");
-}
-
-TEST_F(SpvParserTest, ConvertType_StructWithMemberDecorations) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpMemberDecorate %10 0 Offset 0
-    OpMemberDecorate %10 1 Offset 8
-    OpMemberDecorate %10 2 Offset 16
-    %float = OpTypeFloat 32
-    %vec = OpTypeVector %float 2
-    %mat = OpTypeMatrix %vec 2
-    %10 = OpTypeStruct %float %vec %mat
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
-
-  auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr);
-  EXPECT_TRUE(type->Is<Struct>());
-
-  auto* str = type->Build(p->builder());
-  Program program = p->program();
-  EXPECT_EQ(test::ToString(program, str), "S");
-}
-
-TEST_F(SpvParserTest, ConvertType_Struct_NoDeduplication) {
-  // Prove that distinct SPIR-V structs map to distinct WGSL types.
-  auto p = parser(test::Assemble(Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeStruct %uint
-    %11 = OpTypeStruct %uint
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-
-  auto* type10 = p->ConvertType(10);
-  ASSERT_NE(type10, nullptr);
-  EXPECT_TRUE(type10->Is<Struct>());
-  auto* struct_type10 = type10->As<Struct>();
-  ASSERT_NE(struct_type10, nullptr);
-  EXPECT_EQ(struct_type10->members.size(), 1u);
-  EXPECT_TRUE(struct_type10->members[0]->Is<U32>());
-
-  auto* type11 = p->ConvertType(11);
-  ASSERT_NE(type11, nullptr);
-  EXPECT_TRUE(type11->Is<Struct>());
-  auto* struct_type11 = type11->As<Struct>();
-  ASSERT_NE(struct_type11, nullptr);
-  EXPECT_EQ(struct_type11->members.size(), 1u);
-  EXPECT_TRUE(struct_type11->members[0]->Is<U32>());
-
-  // They map to distinct types in WGSL
-  EXPECT_NE(type11, type10);
-}
-
-TEST_F(SpvParserTest, ConvertType_Array_NoDeduplication) {
-  // Prove that distinct SPIR-V arrays map to distinct WGSL types.
-  auto assembly = Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeStruct %uint
-    %11 = OpTypeStruct %uint
-    %uint_1 = OpConstant %uint 1
-    %20 = OpTypeArray %10 %uint_1
-    %21 = OpTypeArray %11 %uint_1
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-
-  auto* type20 = p->ConvertType(20);
-  ASSERT_NE(type20, nullptr);
-  EXPECT_TRUE(type20->Is<Array>());
-
-  auto* type21 = p->ConvertType(21);
-  ASSERT_NE(type21, nullptr);
-  EXPECT_TRUE(type21->Is<Array>());
-
-  // They map to distinct types in WGSL
-  EXPECT_NE(type21, type20);
-}
-
-TEST_F(SpvParserTest, ConvertType_RuntimeArray_NoDeduplication) {
-  // Prove that distinct SPIR-V runtime arrays map to distinct WGSL types.
-  // The implementation already de-duplicates them because it knows
-  // runtime-arrays normally have stride decorations.
-  auto assembly = Preamble() + R"(
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeStruct %uint
-    %11 = OpTypeStruct %uint
-    %20 = OpTypeRuntimeArray %10
-    %21 = OpTypeRuntimeArray %11
-    %22 = OpTypeRuntimeArray %10
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-
-  auto* type20 = p->ConvertType(20);
-  ASSERT_NE(type20, nullptr);
-  EXPECT_TRUE(type20->Is<Alias>());
-  EXPECT_TRUE(type20->UnwrapAll()->Is<Array>());
-  EXPECT_EQ(type20->UnwrapAll()->As<Array>()->size, 0u);
-
-  auto* type21 = p->ConvertType(21);
-  ASSERT_NE(type21, nullptr);
-  EXPECT_TRUE(type21->Is<Alias>());
-  EXPECT_TRUE(type21->UnwrapAll()->Is<Array>());
-  EXPECT_EQ(type21->UnwrapAll()->As<Array>()->size, 0u);
-
-  auto* type22 = p->ConvertType(22);
-  ASSERT_NE(type22, nullptr);
-  EXPECT_TRUE(type22->Is<Alias>());
-  EXPECT_TRUE(type22->UnwrapAll()->Is<Array>());
-  EXPECT_EQ(type22->UnwrapAll()->As<Array>()->size, 0u);
-
-  // They map to distinct types in WGSL
-  EXPECT_NE(type21, type20);
-  EXPECT_NE(type22, type21);
-  EXPECT_NE(type22, type20);
-}
-
-// TODO(dneto): Demonstrate other member decorations. Blocked on
-// crbug.com/tint/30
-// TODO(dneto): Demonstrate multiple member deocrations. Blocked on
-// crbug.com/tint/30
-
-TEST_F(SpvParserTest, ConvertType_InvalidPointeetype) {
-  // Disallow pointer-to-function
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %void = OpTypeVoid
-  %42 = OpTypeFunction %void
-  %3 = OpTypePointer Input %42
-
-%voidfn = OpTypeFunction %void
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd
-  )"));
-  EXPECT_TRUE(p->BuildInternalModule()) << p->error();
-
-  auto* type = p->ConvertType(3);
-  EXPECT_EQ(type, nullptr);
-  EXPECT_THAT(p->error(),
-              Eq("SPIR-V pointer type with ID 3 has invalid pointee type 42"));
-}
-
-TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidStorageClass) {
-  // Disallow invalid storage class
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %1 = OpTypeFloat 32
-  %3 = OpTypePointer !999 %1   ; Special syntax to inject 999 as the storage class
-  )" + MainBody()));
-  // TODO(dneto): I can't get it past module building.
-  EXPECT_FALSE(p->BuildInternalModule()) << p->error();
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerInput) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Input %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerOutput) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Output %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerUniform) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Uniform %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kUniform);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerWorkgroup) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Workgroup %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kWorkgroup);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerUniformConstant) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer UniformConstant %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kNone);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerStorageBuffer) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer StorageBuffer %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kStorage);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerPrivate) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Private %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerFunction) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %3 = OpTypePointer Function %float
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_TRUE(type->Is<Pointer>());
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_TRUE(ptr_ty->type->Is<F32>());
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kFunction);
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_PointerToPointer) {
-  // FYI:  The reader suports pointer-to-pointer even while WebGPU does not.
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %42 = OpTypePointer Output %float
-  %3 = OpTypePointer Input %42
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(3);
-  EXPECT_NE(type, nullptr);
-  EXPECT_TRUE(type->Is<Pointer>());
-
-  auto* ptr_ty = type->As<Pointer>();
-  EXPECT_NE(ptr_ty, nullptr);
-  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
-  EXPECT_TRUE(ptr_ty->type->Is<Pointer>());
-
-  auto* ptr_ptr_ty = ptr_ty->type->As<Pointer>();
-  EXPECT_NE(ptr_ptr_ty, nullptr);
-  EXPECT_EQ(ptr_ptr_ty->storage_class, ast::StorageClass::kPrivate);
-  EXPECT_TRUE(ptr_ptr_ty->type->Is<F32>());
-
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_Sampler_PretendVoid) {
-  // We fake the type suport for samplers, images, and sampled images.
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %1 = OpTypeSampler
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(1);
-  EXPECT_TRUE(type->Is<Void>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_Image_PretendVoid) {
-  // We fake the type suport for samplers, images, and sampled images.
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %1 = OpTypeImage %float 2D 0 0 0 1 Unknown
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(1);
-  EXPECT_TRUE(type->Is<Void>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, ConvertType_SampledImage_PretendVoid) {
-  auto p = parser(test::Assemble(Preamble() + R"(
-  %float = OpTypeFloat 32
-  %im = OpTypeImage %float 2D 0 0 0 1 Unknown
-  %1 = OpTypeSampledImage %im
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-
-  auto* type = p->ConvertType(1);
-  EXPECT_TRUE(type->Is<Void>());
-  EXPECT_TRUE(p->error().empty());
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_function_decl_test.cc b/src/reader/spirv/parser_impl_function_decl_test.cc
deleted file mode 100644
index 925883d..0000000
--- a/src/reader/spirv/parser_impl_function_decl_test.cc
+++ /dev/null
@@ -1,475 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-std::string Caps() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-  )";
-}
-
-std::string Preamble() {
-  return Caps() + R"(
-    OpEntryPoint Fragment %main "x_100"
-    OpExecutionMode %main OriginUpperLeft
-  )";
-}
-
-std::string MainBody() {
-  return R"(
-    %main = OpFunction %void None %voidfn
-    %main_entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-  )";
-}
-
-/// @returns a SPIR-V assembly segment which assigns debug names
-/// to particular IDs.
-std::string Names(std::vector<std::string> ids) {
-  std::ostringstream outs;
-  for (auto& id : ids) {
-    outs << "    OpName %" << id << " \"" << id << "\"\n";
-  }
-  return outs.str();
-}
-
-std::string CommonTypes() {
-  return R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-  )";
-}
-
-std::string BuiltinPosition() {
-  return R"(OpDecorate %position BuiltIn Position
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %ptr = OpTypePointer Output %v4float
-    %position = OpVariable %ptr Output
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-)";
-}
-
-TEST_F(SpvParserTest, EmitFunctions_NoFunctions) {
-  auto p = parser(test::Assemble(
-      R"(
-     OpCapability Shader
-     OpMemoryModel Logical Simple
-)" + CommonTypes()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
-  p->SkipDumpingPending("Not valid for Vulkan: needs an entry point");
-}
-
-TEST_F(SpvParserTest, EmitFunctions_FunctionWithoutBody) {
-  auto p =
-      parser(test::Assemble(Preamble() + Names({"main"}) + CommonTypes() + R"(
-     %main = OpFunction %void None %voidfn
-     OpFunctionEnd
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
-  p->SkipDumpingPending("Missing an entry point body requires Linkage");
-}
-
-TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Vertex) {
-  std::string input = Caps() +
-                      R"(OpEntryPoint Vertex %main "main" %position )" +
-                      Names({"main"}) + BuiltinPosition() + R"(
-
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-)")) << program_ast;
-
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(vertex)
-fn main() -> main_out {
-)"));
-}
-
-TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Fragment) {
-  std::string input = Caps() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-)" + Names({"main"}) + CommonTypes() +
-                      MainBody();
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(fragment)
-fn main() {
-)"));
-}
-
-TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_GLCompute) {
-  std::string input = Caps() + R"(
-      OpEntryPoint GLCompute %main "main"
-      OpExecutionMode %main LocalSize 1 1 1
-)" + Names({"main"}) + CommonTypes() +
-                      MainBody();
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(1, 1, 1)
-fn main() {
-)"));
-}
-
-TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_MultipleEntryPoints) {
-  std::string input = Caps() +
-                      R"(
-OpEntryPoint Fragment %main "first_shader"
-OpEntryPoint Fragment %main "second_shader"
-OpExecutionMode %main OriginUpperLeft
-)" + Names({"main"}) + CommonTypes() +
-                      MainBody();
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(fragment)
-fn first_shader() {
-)"));
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(fragment)
-fn second_shader() {
-)"));
-}
-
-TEST_F(SpvParserTest,
-       EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only) {
-  std::string input = Caps() + R"(
-OpEntryPoint GLCompute %main "comp_main"
-OpExecutionMode %main LocalSize 2 4 8
-)" + Names({"main"}) + CommonTypes() +
-                      R"(
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(2, 4, 8)
-fn comp_main() {
-)")) << program_ast;
-}
-
-TEST_F(SpvParserTest,
-       EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only) {
-  std::string input = Caps() + R"(OpEntryPoint GLCompute %main "comp_main"
-OpDecorate %wgsize BuiltIn WorkgroupSize
-)" + CommonTypes() + R"(
-%uvec3 = OpTypeVector %uint 3
-%uint_3 = OpConstant %uint 3
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%wgsize = OpConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(3, 5, 7)
-fn comp_main() {
-)")) << program_ast;
-}
-
-TEST_F(
-    SpvParserTest,
-    EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only) {
-  std::string input = Caps() +
-                      R"(OpEntryPoint GLCompute %main "comp_main"
-OpDecorate %wgsize BuiltIn WorkgroupSize
-OpDecorate %uint_3 SpecId 0
-OpDecorate %uint_5 SpecId 1
-OpDecorate %uint_7 SpecId 2
-)" + CommonTypes() + R"(
-%uvec3 = OpTypeVector %uint 3
-%uint_3 = OpSpecConstant %uint 3
-%uint_5 = OpSpecConstant %uint 5
-%uint_7 = OpSpecConstant %uint 7
-%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(3, 5, 7)
-fn comp_main() {
-)")) << program_ast;
-}
-
-TEST_F(
-    SpvParserTest,
-    EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant) {
-  std::string input = Caps() +
-                      R"(OpEntryPoint GLCompute %main "comp_main"
-OpDecorate %wgsize BuiltIn WorkgroupSize
-OpDecorate %uint_3 SpecId 0
-OpDecorate %uint_7 SpecId 2
-)" + CommonTypes() + R"(
-%uvec3 = OpTypeVector %uint 3
-%uint_3 = OpSpecConstant %uint 3
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpSpecConstant %uint 7
-%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(3, 5, 7)
-fn comp_main() {
-)")) << program_ast;
-}
-
-TEST_F(
-    SpvParserTest,
-    // I had to shorten the name to pass the linter.
-    EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant) {
-  // WorkgroupSize builtin wins.
-  std::string input = Caps() +
-                      R"(OpEntryPoint GLCompute %main "comp_main"
-OpExecutionMode %main LocalSize 2 4 8
-OpDecorate %wgsize BuiltIn WorkgroupSize
-OpDecorate %uint_3 SpecId 0
-OpDecorate %uint_5 SpecId 1
-OpDecorate %uint_7 SpecId 2
-)" + CommonTypes() + R"(
-%uvec3 = OpTypeVector %uint 3
-%uint_3 = OpSpecConstant %uint 3
-%uint_5 = OpSpecConstant %uint 5
-%uint_7 = OpSpecConstant %uint 7
-%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
-
-  auto p = parser(test::Assemble(input));
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  ASSERT_TRUE(p->error().empty()) << p->error();
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(
-@stage(compute) @workgroup_size(3, 5, 7)
-fn comp_main() {
-)")) << program_ast;
-}
-
-TEST_F(SpvParserTest, EmitFunctions_VoidFunctionWithoutParams) {
-  auto p = parser(test::Assemble(Preamble() + Names({"another_function"}) +
-                                 CommonTypes() + R"(
-    %another_function = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(fn another_function() {
-)"));
-}
-
-TEST_F(SpvParserTest, EmitFunctions_CalleePrecedesCaller) {
-  auto p = parser(test::Assemble(
-      Preamble() +
-      Names({"root", "branch", "leaf", "leaf_result", "branch_result"}) +
-      CommonTypes() + R"(
-     %uintfn = OpTypeFunction %uint
-     %uint_0 = OpConstant %uint 0
-
-     %root = OpFunction %void None %voidfn
-     %root_entry = OpLabel
-     %branch_result = OpFunctionCall %uint %branch
-     OpReturn
-     OpFunctionEnd
-
-     %branch = OpFunction %uint None %uintfn
-     %branch_entry = OpLabel
-     %leaf_result = OpFunctionCall %uint %leaf
-     OpReturnValue %leaf_result
-     OpFunctionEnd
-
-     %leaf = OpFunction %uint None %uintfn
-     %leaf_entry = OpLabel
-     OpReturnValue %uint_0
-     OpFunctionEnd
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(fn leaf() -> u32 {
-  return 0u;
-}
-
-fn branch() -> u32 {
-  let leaf_result : u32 = leaf();
-  return leaf_result;
-}
-
-fn root() {
-  let branch_result : u32 = branch();
-  return;
-}
-)")) << program_ast;
-}
-
-TEST_F(SpvParserTest, EmitFunctions_NonVoidResultType) {
-  auto p = parser(
-      test::Assemble(Preamble() + Names({"ret_float"}) + CommonTypes() + R"(
-     %float_0 = OpConstant %float 0.0
-     %fn_ret_float = OpTypeFunction %float
-
-     %ret_float = OpFunction %float None %fn_ret_float
-     %ret_float_entry = OpLabel
-     OpReturnValue %float_0
-     OpFunctionEnd
-)" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast, HasSubstr(R"(fn ret_float() -> f32 {
-  return 0.0;
-}
-)")) << program_ast;
-}
-
-TEST_F(SpvParserTest, EmitFunctions_MixedParamTypes) {
-  auto p = parser(test::Assemble(
-      Preamble() + Names({"mixed_params", "a", "b", "c"}) + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %void %uint %float %int
-
-     %mixed_params = OpFunction %void None %fn_mixed_params
-     %a = OpFunctionParameter %uint
-     %b = OpFunctionParameter %float
-     %c = OpFunctionParameter %int
-     %mixed_entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast,
-              HasSubstr(R"(fn mixed_params(a : u32, b : f32, c : i32) {
-  return;
-}
-)"));
-}
-
-TEST_F(SpvParserTest, EmitFunctions_GenerateParamNames) {
-  auto p = parser(
-      test::Assemble(Preamble() + Names({"mixed_params"}) + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %void %uint %float %int
-
-     %mixed_params = OpFunction %void None %fn_mixed_params
-     %14 = OpFunctionParameter %uint
-     %15 = OpFunctionParameter %float
-     %16 = OpFunctionParameter %int
-     %mixed_entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  Program program = p->program();
-  const auto program_ast = test::ToString(program);
-  EXPECT_THAT(program_ast,
-              HasSubstr(R"(fn mixed_params(x_14 : u32, x_15 : f32, x_16 : i32) {
-  return;
-}
-)")) << program_ast;
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_get_decorations_test.cc b/src/reader/spirv/parser_impl_get_decorations_test.cc
deleted file mode 100644
index 7b2818e..0000000
--- a/src/reader/spirv/parser_impl_get_decorations_test.cc
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::UnorderedElementsAre;
-
-using SpvParserGetDecorationsTest = SpvParserTest;
-
-const char* kSkipReason = "This example is deliberately a SPIR-V fragment";
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NotAnId) {
-  auto p = parser(test::Assemble(""));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(42);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NoDecorations) {
-  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(1);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_OneDecoration) {
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %10 Block
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(10);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationBlock}));
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_Duplicate) {
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %10 Block
-    OpDecorate %10 Block
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(10);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationBlock}));
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_MultiDecoration) {
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %5 RelaxedPrecision
-    OpDecorate %5 Location 7      ; Invalid case made up for test
-    %float = OpTypeFloat 32
-    %5 = OpConstant %float 3.14
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(5);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision},
-                                   Decoration{SpvDecorationLocation, 7}));
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAnId) {
-  auto p = parser(test::Assemble(""));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsForMember(42, 9);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAStruct) {
-  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(1);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest,
-       GetDecorationsForMember_MemberWithoutDecoration) {
-  auto p = parser(test::Assemble(R"(
-    %uint = OpTypeInt 32 0
-    %10 = OpTypeStruct %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsForMember(10, 0);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_RelaxedPrecision) {
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %10 0 RelaxedPrecision
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  auto decorations = p->GetDecorationsForMember(10, 0);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_Duplicate) {
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %10 0 RelaxedPrecision
-    OpMemberDecorate %10 0 RelaxedPrecision
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  auto decorations = p->GetDecorationsForMember(10, 0);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-// TODO(dneto): Enable when ArrayStride is handled
-TEST_F(SpvParserGetDecorationsTest,
-       DISABLED_GetDecorationsForMember_OneDecoration) {
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %10 1 ArrayStride 12
-    %uint = OpTypeInt 32 0
-    %uint_2 = OpConstant %uint 2
-    %arr = OpTypeArray %uint %uint_2
-    %10 = OpTypeStruct %uint %arr
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  auto decorations = p->GetDecorationsForMember(10, 1);
-  EXPECT_THAT(decorations,
-              UnorderedElementsAre(Decoration{SpvDecorationArrayStride, 12}));
-  EXPECT_TRUE(p->error().empty());
-}
-
-// TODO(dneto): Enable when ArrayStride, MatrixStride, ColMajor are handled
-// crbug.com/tint/30 for ArrayStride
-// crbug.com/tint/31 for matrix layout
-TEST_F(SpvParserGetDecorationsTest,
-       DISABLED_GetDecorationsForMember_MultiDecoration) {
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %50 1 RelaxedPrecision
-    OpMemberDecorate %50 2 ArrayStride 16
-    OpMemberDecorate %50 2 MatrixStride 8
-    OpMemberDecorate %50 2 ColMajor
-    %float = OpTypeFloat 32
-    %vec = OpTypeVector %float 2
-    %mat = OpTypeMatrix %vec 2
-    %uint = OpTypeInt 32 0
-    %uint_2 = OpConstant %uint 2
-    %arr = OpTypeArray %mat %uint_2
-    %50 = OpTypeStruct %uint %float %arr
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-
-  EXPECT_TRUE(p->GetDecorationsForMember(50, 0).empty());
-  EXPECT_THAT(p->GetDecorationsForMember(50, 1),
-              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
-  EXPECT_THAT(p->GetDecorationsForMember(50, 2),
-              UnorderedElementsAre(Decoration{SpvDecorationColMajor},
-                                   Decoration{SpvDecorationMatrixStride, 8},
-                                   Decoration{SpvDecorationArrayStride, 16}));
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_Restrict) {
-  // RestrictPointer applies to a memory object declaration. Use a variable.
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %10 Restrict
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Workgroup %float
-    %10 = OpVariable %ptr Workgroup
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsFor(10);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_Restrict) {
-  // Restrict applies to a memory object declaration.
-  // But OpMemberDecorate can only be applied to a structure type.
-  // Test the reader's ability to be resilient to more than what SPIR-V allows.
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %10 0 Restrict
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  auto decorations = p->GetDecorationsForMember(10, 0);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_RestrictPointer) {
-  // RestrictPointer applies to a memory object declaration. Use a variable.
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %10 RestrictPointer
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Workgroup %float
-    %10 = OpVariable %ptr Workgroup
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  auto decorations = p->GetDecorationsFor(10);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_RestrictPointer) {
-  // RestrictPointer applies to a memory object declaration.
-  // But OpMemberDecorate can only be applied to a structure type.
-  // Test the reader's ability to be resilient to more than what SPIR-V allows.
-  auto p = parser(test::Assemble(R"(
-    OpMemberDecorate %10 0 RestrictPointer
-    %float = OpTypeFloat 32
-    %10 = OpTypeStruct %float
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  auto decorations = p->GetDecorationsFor(10);
-  EXPECT_TRUE(decorations.empty());
-  EXPECT_TRUE(p->error().empty());
-  p->SkipDumpingPending(kSkipReason);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
deleted file mode 100644
index 2e08b30..0000000
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ /dev/null
@@ -1,3955 +0,0 @@
-// Copyright 2020 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 <ostream>
-
-#include "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-using ::testing::HasSubstr;
-using ::testing::Not;
-using ::testing::StartsWith;
-
-using SpvParserHandleTest = SpvParserTest;
-
-std::string Preamble() {
-  return R"(
-    OpCapability Shader
-    OpCapability Sampled1D
-    OpCapability Image1D
-    OpCapability StorageImageExtendedFormats
-    OpCapability ImageQuery
-    OpMemoryModel Logical Simple
-  )";
-}
-
-std::string FragMain() {
-  return R"(
-    OpEntryPoint Fragment %main "main" ; assume no IO
-    OpExecutionMode %main OriginUpperLeft
-  )";
-}
-
-std::string MainBody() {
-  return R"(
-    %main = OpFunction %void None %voidfn
-    %main_entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-  )";
-}
-
-std::string CommonBasicTypes() {
-  return R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-
-    %int_0 = OpConstant %int 0
-    %int_1 = OpConstant %int 1
-    %int_2 = OpConstant %int 2
-    %int_3 = OpConstant %int 3
-    %int_4 = OpConstant %int 4
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %uint_2 = OpConstant %uint 2
-    %uint_3 = OpConstant %uint 3
-    %uint_4 = OpConstant %uint 4
-    %uint_100 = OpConstant %uint 100
-
-    %v2int = OpTypeVector %int 2
-    %v3int = OpTypeVector %int 3
-    %v4int = OpTypeVector %int 4
-    %v2uint = OpTypeVector %uint 2
-    %v3uint = OpTypeVector %uint 3
-    %v4uint = OpTypeVector %uint 4
-    %v2float = OpTypeVector %float 2
-    %v3float = OpTypeVector %float 3
-    %v4float = OpTypeVector %float 4
-
-    %float_null = OpConstantNull %float
-    %float_0 = OpConstant %float 0
-    %float_1 = OpConstant %float 1
-    %float_2 = OpConstant %float 2
-    %float_3 = OpConstant %float 3
-    %float_4 = OpConstant %float 4
-    %float_7 = OpConstant %float 7
-    %v2float_null = OpConstantNull %v2float
-    %v3float_null = OpConstantNull %v3float
-    %v4float_null = OpConstantNull %v4float
-
-    %the_vi12 = OpConstantComposite %v2int %int_1 %int_2
-    %the_vi123 = OpConstantComposite %v3int %int_1 %int_2 %int_3
-    %the_vi1234 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4
-
-    %the_vu12 = OpConstantComposite %v2uint %uint_1 %uint_2
-    %the_vu123 = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_3
-    %the_vu1234 = OpConstantComposite %v4uint %uint_1 %uint_2 %uint_3 %uint_4
-
-    %the_vf12 = OpConstantComposite %v2float %float_1 %float_2
-    %the_vf21 = OpConstantComposite %v2float %float_2 %float_1
-    %the_vf123 = OpConstantComposite %v3float %float_1 %float_2 %float_3
-    %the_vf1234 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
-
-
-    %depth = OpConstant %float 0.2
-  )";
-}
-
-std::string CommonImageTypes() {
-  return R"(
-
-; Define types for all sampler and texture types that can map to WGSL,
-; modulo texel formats for storage textures. For now, we limit
-; ourselves to 2-channel 32-bit texel formats.
-
-; Because the SPIR-V reader also already generalizes so it can work with
-; combined image-samplers, we also test that too.
-
-    %sampler = OpTypeSampler
-
-    ; sampled images
-    %f_texture_1d          = OpTypeImage %float 1D   0 0 0 1 Unknown
-    %f_texture_2d          = OpTypeImage %float 2D   0 0 0 1 Unknown
-    %f_texture_2d_ms       = OpTypeImage %float 2D   0 0 1 1 Unknown
-    %f_texture_2d_array    = OpTypeImage %float 2D   0 1 0 1 Unknown
-    %f_texture_2d_ms_array = OpTypeImage %float 2D   0 1 1 1 Unknown ; not in WebGPU
-    %f_texture_3d          = OpTypeImage %float 3D   0 0 0 1 Unknown
-    %f_texture_cube        = OpTypeImage %float Cube 0 0 0 1 Unknown
-    %f_texture_cube_array  = OpTypeImage %float Cube 0 1 0 1 Unknown
-
-    ; storage images
-    %f_storage_1d         = OpTypeImage %float 1D   0 0 0 2 Rg32f
-    %f_storage_2d         = OpTypeImage %float 2D   0 0 0 2 Rg32f
-    %f_storage_2d_array   = OpTypeImage %float 2D   0 1 0 2 Rg32f
-    %f_storage_3d         = OpTypeImage %float 3D   0 0 0 2 Rg32f
-
-    ; Now all the same, but for unsigned integer sampled type.
-
-    %u_texture_1d          = OpTypeImage %uint  1D   0 0 0 1 Unknown
-    %u_texture_2d          = OpTypeImage %uint  2D   0 0 0 1 Unknown
-    %u_texture_2d_ms       = OpTypeImage %uint  2D   0 0 1 1 Unknown
-    %u_texture_2d_array    = OpTypeImage %uint  2D   0 1 0 1 Unknown
-    %u_texture_2d_ms_array = OpTypeImage %uint  2D   0 1 1 1 Unknown ; not in WebGPU
-    %u_texture_3d          = OpTypeImage %uint  3D   0 0 0 1 Unknown
-    %u_texture_cube        = OpTypeImage %uint  Cube 0 0 0 1 Unknown
-    %u_texture_cube_array  = OpTypeImage %uint  Cube 0 1 0 1 Unknown
-
-    %u_storage_1d         = OpTypeImage %uint  1D   0 0 0 2 Rg32ui
-    %u_storage_2d         = OpTypeImage %uint  2D   0 0 0 2 Rg32ui
-    %u_storage_2d_array   = OpTypeImage %uint  2D   0 1 0 2 Rg32ui
-    %u_storage_3d         = OpTypeImage %uint  3D   0 0 0 2 Rg32ui
-
-    ; Now all the same, but for signed integer sampled type.
-
-    %i_texture_1d          = OpTypeImage %int  1D   0 0 0 1 Unknown
-    %i_texture_2d          = OpTypeImage %int  2D   0 0 0 1 Unknown
-    %i_texture_2d_ms       = OpTypeImage %int  2D   0 0 1 1 Unknown
-    %i_texture_2d_array    = OpTypeImage %int  2D   0 1 0 1 Unknown
-    %i_texture_2d_ms_array = OpTypeImage %int  2D   0 1 1 1 Unknown ; not in WebGPU
-    %i_texture_3d          = OpTypeImage %int  3D   0 0 0 1 Unknown
-    %i_texture_cube        = OpTypeImage %int  Cube 0 0 0 1 Unknown
-    %i_texture_cube_array  = OpTypeImage %int  Cube 0 1 0 1 Unknown
-
-    %i_storage_1d         = OpTypeImage %int  1D   0 0 0 2 Rg32i
-    %i_storage_2d         = OpTypeImage %int  2D   0 0 0 2 Rg32i
-    %i_storage_2d_array   = OpTypeImage %int  2D   0 1 0 2 Rg32i
-    %i_storage_3d         = OpTypeImage %int  3D   0 0 0 2 Rg32i
-
-    ;; Now pointers to each of the above, so we can declare variables for them.
-
-    %ptr_sampler = OpTypePointer UniformConstant %sampler
-
-    %ptr_f_texture_1d          = OpTypePointer UniformConstant %f_texture_1d
-    %ptr_f_texture_2d          = OpTypePointer UniformConstant %f_texture_2d
-    %ptr_f_texture_2d_ms       = OpTypePointer UniformConstant %f_texture_2d_ms
-    %ptr_f_texture_2d_array    = OpTypePointer UniformConstant %f_texture_2d_array
-    %ptr_f_texture_2d_ms_array = OpTypePointer UniformConstant %f_texture_2d_ms_array
-    %ptr_f_texture_3d          = OpTypePointer UniformConstant %f_texture_3d
-    %ptr_f_texture_cube        = OpTypePointer UniformConstant %f_texture_cube
-    %ptr_f_texture_cube_array  = OpTypePointer UniformConstant %f_texture_cube_array
-
-    ; storage images
-    %ptr_f_storage_1d         = OpTypePointer UniformConstant %f_storage_1d
-    %ptr_f_storage_2d         = OpTypePointer UniformConstant %f_storage_2d
-    %ptr_f_storage_2d_array   = OpTypePointer UniformConstant %f_storage_2d_array
-    %ptr_f_storage_3d         = OpTypePointer UniformConstant %f_storage_3d
-
-    ; Now all the same, but for unsigned integer sampled type.
-
-    %ptr_u_texture_1d          = OpTypePointer UniformConstant %u_texture_1d
-    %ptr_u_texture_2d          = OpTypePointer UniformConstant %u_texture_2d
-    %ptr_u_texture_2d_ms       = OpTypePointer UniformConstant %u_texture_2d_ms
-    %ptr_u_texture_2d_array    = OpTypePointer UniformConstant %u_texture_2d_array
-    %ptr_u_texture_2d_ms_array = OpTypePointer UniformConstant %u_texture_2d_ms_array
-    %ptr_u_texture_3d          = OpTypePointer UniformConstant %u_texture_3d
-    %ptr_u_texture_cube        = OpTypePointer UniformConstant %u_texture_cube
-    %ptr_u_texture_cube_array  = OpTypePointer UniformConstant %u_texture_cube_array
-
-    %ptr_u_storage_1d         = OpTypePointer UniformConstant %u_storage_1d
-    %ptr_u_storage_2d         = OpTypePointer UniformConstant %u_storage_2d
-    %ptr_u_storage_2d_array   = OpTypePointer UniformConstant %u_storage_2d_array
-    %ptr_u_storage_3d         = OpTypePointer UniformConstant %u_storage_3d
-
-    ; Now all the same, but for signed integer sampled type.
-
-    %ptr_i_texture_1d          = OpTypePointer UniformConstant %i_texture_1d
-    %ptr_i_texture_2d          = OpTypePointer UniformConstant %i_texture_2d
-    %ptr_i_texture_2d_ms       = OpTypePointer UniformConstant %i_texture_2d_ms
-    %ptr_i_texture_2d_array    = OpTypePointer UniformConstant %i_texture_2d_array
-    %ptr_i_texture_2d_ms_array = OpTypePointer UniformConstant %i_texture_2d_ms_array
-    %ptr_i_texture_3d          = OpTypePointer UniformConstant %i_texture_3d
-    %ptr_i_texture_cube        = OpTypePointer UniformConstant %i_texture_cube
-    %ptr_i_texture_cube_array  = OpTypePointer UniformConstant %i_texture_cube_array
-
-    %ptr_i_storage_1d         = OpTypePointer UniformConstant %i_storage_1d
-    %ptr_i_storage_2d         = OpTypePointer UniformConstant %i_storage_2d
-    %ptr_i_storage_2d_array   = OpTypePointer UniformConstant %i_storage_2d_array
-    %ptr_i_storage_3d         = OpTypePointer UniformConstant %i_storage_3d
-
-  )";
-}
-
-std::string CommonTypes() {
-  return CommonBasicTypes() + CommonImageTypes();
-}
-
-std::string Bindings(std::vector<uint32_t> ids) {
-  std::ostringstream os;
-  int binding = 0;
-  for (auto id : ids) {
-    os << "  OpDecorate %" << id << " DescriptorSet 0\n"
-       << "  OpDecorate %" << id << " Binding " << binding++ << "\n";
-  }
-  return os.str();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_WellFormedButNotAHandle) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %10 = OpConstantNull %ptr_sampler
-     %20 = OpConstantNull %ptr_f_texture_1d
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule()) << assembly;
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
-
-  EXPECT_EQ(sampler, nullptr);
-  EXPECT_EQ(image, nullptr);
-  EXPECT_TRUE(p->error().empty());
-
-  p->DeliberatelyInvalidSpirv();  // WGSL does not have null pointers.
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_Direct) {
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_1d UniformConstant
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_AccessChain) {
-  // Show that we would generalize to arrays of handles, even though that
-  // is not supported in WGSL MVP.
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %10 = OpVariable %ptr_sampler_array UniformConstant
-     %20 = OpVariable %ptr_image_array UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpAccessChain %ptr_sampler %10 %uint_1
-     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // WGSL does not support arrays of textures and samplers.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_InBoundsAccessChain) {
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %10 = OpVariable %ptr_sampler_array UniformConstant
-     %20 = OpVariable %ptr_image_array UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
-     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // WGSL does not support arrays of textures and samplers.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_PtrAccessChain) {
-  // Show that we would generalize to arrays of handles, even though that
-  // is not supported in WGSL MVP.
-  // Use VariablePointers for the OpInBoundsPtrAccessChain.
-  const auto assembly = "OpCapability VariablePointers " + Preamble() +
-                        FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %10 = OpVariable %ptr_sampler_array UniformConstant
-     %20 = OpVariable %ptr_image_array UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
-     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // Variable pointers is not allowed for WGSL. So don't dump it.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_InBoundsPtrAccessChain) {
-  // Use VariablePointers for the OpInBoundsPtrAccessChain.
-  const auto assembly = "OpCapability VariablePointers " + Preamble() +
-                        FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %10 = OpVariable %ptr_sampler_array UniformConstant
-     %20 = OpVariable %ptr_image_array UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
-     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // Variable pointers is not allowed for WGSL. So don't dump it.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_CopyObject) {
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_1d UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpCopyObject %ptr_sampler %10
-     %120 = OpCopyObject %ptr_f_texture_1d %20
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-}
-
-TEST_F(SpvParserHandleTest, GetMemoryObjectDeclarationForHandle_Variable_Load) {
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_1d UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %110 = OpLoad %sampler %10
-     %120 = OpLoad %f_texture_1d %20
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_SampledImage) {
-  // Trace through the sampled image instruction, but in two different
-  // directions.
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-     %sampled_image_type = OpTypeSampledImage %f_texture_1d
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_1d UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %s = OpLoad %sampler %10
-     %im = OpLoad %f_texture_1d %20
-     %100 = OpSampledImage %sampled_image_type %im %s
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_Variable_Image) {
-  const auto assembly =
-      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
-     %sampled_image_type = OpTypeSampledImage %f_texture_1d
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_1d UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %s = OpLoad %sampler %10
-     %im = OpLoad %f_texture_1d %20
-     %100 = OpSampledImage %sampled_image_type %im %s
-     %200 = OpImage %f_texture_1d %100
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_Direct) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler
-     %20 = OpFunctionParameter %ptr_f_texture_1d
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  p->SkipDumpingPending("crbug.com/tint/1039");
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_AccessChain) {
-  // Show that we would generalize to arrays of handles, even though that
-  // is not supported in WGSL MVP.
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler_array
-     %20 = OpFunctionParameter %ptr_image_array
-     %entry = OpLabel
-
-     %110 = OpAccessChain %ptr_sampler %10 %uint_1
-     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // WGSL does not support arrays of textures or samplers
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsAccessChain) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler_array
-     %20 = OpFunctionParameter %ptr_image_array
-     %entry = OpLabel
-
-     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
-     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // WGSL does not support arrays of textures or samplers
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_PtrAccessChain) {
-  // Show that we would generalize to arrays of handles, even though that
-  // is not supported in WGSL MVP.
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler_array
-     %20 = OpFunctionParameter %ptr_image_array
-     %entry = OpLabel
-
-     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
-     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // Variable pointers is not allowed for WGSL. So don't dump it.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsPtrAccessChain) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampler_array = OpTypeArray %sampler %uint_100
-     %image_array = OpTypeArray %f_texture_1d %uint_100
-
-     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
-     %ptr_image_array = OpTypePointer UniformConstant %image_array
-
-     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler_array
-     %20 = OpFunctionParameter %ptr_image_array
-     %entry = OpLabel
-
-     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
-     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  // Variable pointers is not allowed for WGSL. So don't dump it.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_CopyObject) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler
-     %20 = OpFunctionParameter %ptr_f_texture_1d
-     %entry = OpLabel
-
-     %110 = OpCopyObject %ptr_sampler %10
-     %120 = OpCopyObject %ptr_f_texture_1d %20
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  p->SkipDumpingPending("crbug.com/tint/1039");
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_Load) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler
-     %20 = OpFunctionParameter %ptr_f_texture_1d
-     %entry = OpLabel
-
-     %110 = OpLoad %sampler %10
-     %120 = OpLoad %f_texture_1d %20
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  p->SkipDumpingPending("crbug.com/tint/1039");
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_SampledImage) {
-  // Trace through the sampled image instruction, but in two different
-  // directions.
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampled_image_type = OpTypeSampledImage %f_texture_1d
-
-     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler
-     %20 = OpFunctionParameter %ptr_f_texture_1d
-     %entry = OpLabel
-
-     %s = OpLoad %sampler %10
-     %im = OpLoad %f_texture_1d %20
-     %100 = OpSampledImage %sampled_image_type %im %s
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
-
-  ASSERT_TRUE(sampler != nullptr);
-  EXPECT_EQ(sampler->result_id(), 10u);
-
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  p->SkipDumpingPending("crbug.com/tint/1039");
-}
-
-TEST_F(SpvParserHandleTest,
-       GetMemoryObjectDeclarationForHandle_FuncParam_Image) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %sampled_image_type = OpTypeSampledImage %f_texture_1d
-
-     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
-
-     %func = OpFunction %void None %fty
-     %10 = OpFunctionParameter %ptr_sampler
-     %20 = OpFunctionParameter %ptr_f_texture_1d
-     %entry = OpLabel
-
-     %s = OpLoad %sampler %10
-     %im = OpLoad %f_texture_1d %20
-     %100 = OpSampledImage %sampled_image_type %im %s
-     %200 = OpImage %f_texture_1d %100
-
-     OpReturn
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->error().empty());
-
-  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
-  ASSERT_TRUE(image != nullptr);
-  EXPECT_EQ(image->result_id(), 20u);
-
-  p->SkipDumpingPending("crbug.com/tint/1039");
-}
-
-// Test RegisterHandleUsage, sampled image cases
-
-struct UsageImageAccessCase {
-  std::string inst;
-  std::string expected_sampler_usage;
-  std::string expected_image_usage;
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                const UsageImageAccessCase& c) {
-  out << "UsageImageAccessCase(" << c.inst << ", " << c.expected_sampler_usage
-      << ", " << c.expected_image_usage << ")";
-  return out;
-}
-
-using SpvParserHandleTest_RegisterHandleUsage_SampledImage =
-    SpvParserTestBase<::testing::TestWithParam<UsageImageAccessCase>>;
-
-TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage, Variable) {
-  const std::string inst = GetParam().inst;
-  const auto assembly = Preamble() + FragMain() + Bindings({10, 20}) +
-                        CommonTypes() + R"(
-     %si_ty = OpTypeSampledImage %f_texture_2d
-     %coords = OpConstantNull %v2float
-     %coords3d = OpConstantNull %v3float ; needed for Proj variants
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_2d UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %sam = OpLoad %sampler %10
-     %im = OpLoad %f_texture_2d %20
-     %sampled_image = OpSampledImage %si_ty %im %sam
-)" + GetParam().inst + R"(
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterHandleUsage());
-  EXPECT_TRUE(p->error().empty());
-  Usage su = p->GetHandleUsage(10);
-  Usage iu = p->GetHandleUsage(20);
-
-  EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
-  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
-
-  if (inst.find("ImageQueryLod") != std::string::npos) {
-    // WGSL does not support querying image level of detail.
-    // So don't emit them as part of a "passing" corpus.
-    p->DeliberatelyInvalidSpirv();
-  }
-  if (inst.find("ImageSampleDrefExplicitLod") != std::string::npos) {
-    p->SkipDumpingPending("crbug.com/tint/425");  // gpuweb issue #1319
-  }
-}
-
-TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage, FunctionParam) {
-  const std::string inst = GetParam().inst;
-  const auto assembly = Preamble() + FragMain() + Bindings({10, 20}) +
-                        CommonTypes() + R"(
-     %f_ty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_2d
-     %si_ty = OpTypeSampledImage %f_texture_2d
-     %coords = OpConstantNull %v2float
-     %coords3d = OpConstantNull %v3float ; needed for Proj variants
-     %component = OpConstant %uint 1
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_f_texture_2d UniformConstant
-
-     %func = OpFunction %void None %f_ty
-     %110 = OpFunctionParameter %ptr_sampler
-     %120 = OpFunctionParameter %ptr_f_texture_2d
-     %func_entry = OpLabel
-     %sam = OpLoad %sampler %110
-     %im = OpLoad %f_texture_2d %120
-     %sampled_image = OpSampledImage %si_ty %im %sam
-
-)" + inst + R"(
-
-     OpReturn
-     OpFunctionEnd
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %foo = OpFunctionCall %void %func %10 %20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule()) << p->error() << assembly << std::endl;
-  EXPECT_TRUE(p->RegisterHandleUsage()) << p->error() << assembly << std::endl;
-  EXPECT_TRUE(p->error().empty()) << p->error() << assembly << std::endl;
-  Usage su = p->GetHandleUsage(10);
-  Usage iu = p->GetHandleUsage(20);
-
-  EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
-  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
-
-  if (inst.find("ImageQueryLod") != std::string::npos) {
-    // WGSL does not support querying image level of detail.
-    // So don't emit them as part of a "passing" corpus.
-    p->DeliberatelyInvalidSpirv();
-  }
-  p->SkipDumpingPending("crbug.com/tint/785");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Samples,
-    SpvParserHandleTest_RegisterHandleUsage_SampledImage,
-    ::testing::Values(
-
-        // OpImageGather
-        UsageImageAccessCase{"%result = OpImageGather "
-                             "%v4float %sampled_image %coords %uint_1",
-                             "Usage(Sampler( ))",
-                             "Usage(Texture( is_sampled ))"},
-        // OpImageDrefGather
-        UsageImageAccessCase{"%result = OpImageDrefGather "
-                             "%v4float %sampled_image %coords %depth",
-                             "Usage(Sampler( comparison ))",
-                             "Usage(Texture( is_sampled depth ))"},
-
-        // Sample the texture.
-
-        // OpImageSampleImplicitLod
-        UsageImageAccessCase{"%result = OpImageSampleImplicitLod "
-                             "%v4float %sampled_image %coords",
-                             "Usage(Sampler( ))",
-                             "Usage(Texture( is_sampled ))"},
-        // OpImageSampleExplicitLod
-        UsageImageAccessCase{"%result = OpImageSampleExplicitLod "
-                             "%v4float %sampled_image %coords Lod %float_null",
-                             "Usage(Sampler( ))",
-                             "Usage(Texture( is_sampled ))"},
-        // OpImageSampleDrefImplicitLod
-        UsageImageAccessCase{"%result = OpImageSampleDrefImplicitLod "
-                             "%float %sampled_image %coords %depth",
-                             "Usage(Sampler( comparison ))",
-                             "Usage(Texture( is_sampled depth ))"},
-        // OpImageSampleDrefExplicitLod
-        UsageImageAccessCase{
-            "%result = OpImageSampleDrefExplicitLod "
-            "%float %sampled_image %coords %depth Lod %float_null",
-            "Usage(Sampler( comparison ))",
-            "Usage(Texture( is_sampled depth ))"},
-
-        // Sample the texture, with *Proj* variants, even though WGSL doesn't
-        // support them.
-
-        // OpImageSampleProjImplicitLod
-        UsageImageAccessCase{"%result = OpImageSampleProjImplicitLod "
-                             "%v4float %sampled_image %coords3d",
-                             "Usage(Sampler( ))",
-                             "Usage(Texture( is_sampled ))"},
-        // OpImageSampleProjExplicitLod
-        UsageImageAccessCase{
-            "%result = OpImageSampleProjExplicitLod "
-            "%v4float %sampled_image %coords3d Lod %float_null",
-            "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"},
-        // OpImageSampleProjDrefImplicitLod
-        UsageImageAccessCase{"%result = OpImageSampleProjDrefImplicitLod "
-                             "%float %sampled_image %coords3d %depth",
-                             "Usage(Sampler( comparison ))",
-                             "Usage(Texture( is_sampled depth ))"},
-        // OpImageSampleProjDrefExplicitLod
-        UsageImageAccessCase{
-            "%result = OpImageSampleProjDrefExplicitLod "
-            "%float %sampled_image %coords3d %depth Lod %float_null",
-            "Usage(Sampler( comparison ))",
-            "Usage(Texture( is_sampled depth ))"},
-
-        // OpImageQueryLod
-        UsageImageAccessCase{
-            "%result = OpImageQueryLod %v2float %sampled_image %coords",
-            "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}));
-
-// Test RegisterHandleUsage, raw image cases.
-// For these we test the use of an image value directly, and not combined
-// with the sampler. The image still could be of sampled image type.
-
-struct UsageRawImageCase {
-  std::string type;  // Example: f_storage_1d or f_texture_1d
-  std::string inst;
-  std::string expected_image_usage;
-};
-inline std::ostream& operator<<(std::ostream& out, const UsageRawImageCase& c) {
-  out << "UsageRawImageCase(" << c.type << ", " << c.inst << ", "
-      << c.expected_image_usage << ")";
-  return out;
-}
-
-using SpvParserHandleTest_RegisterHandleUsage_RawImage =
-    SpvParserTestBase<::testing::TestWithParam<UsageRawImageCase>>;
-
-TEST_P(SpvParserHandleTest_RegisterHandleUsage_RawImage, Variable) {
-  const bool is_storage = GetParam().type.find("storage") != std::string::npos;
-  const bool is_write = GetParam().inst.find("ImageWrite") != std::string::npos;
-  const auto assembly = Preamble() + FragMain() + Bindings({20}) +
-                        (is_storage ? std::string("OpDecorate %20 ") +
-                                          std::string(is_write ? "NonReadable"
-                                                               : "NonWritable")
-                                    : std::string("")) +
-                        " " + CommonTypes() + R"(
-     %20 = OpVariable %ptr_)" +
-                        GetParam().type + R"( UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %im = OpLoad %)" + GetParam().type +
-                        R"( %20
-)" + GetParam().inst + R"(
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterHandleUsage());
-  EXPECT_TRUE(p->error().empty());
-
-  Usage iu = p->GetHandleUsage(20);
-  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
-
-  Usage su = p->GetHandleUsage(20);
-}
-
-TEST_P(SpvParserHandleTest_RegisterHandleUsage_RawImage, FunctionParam) {
-  const bool is_storage = GetParam().type.find("storage") != std::string::npos;
-  const bool is_write = GetParam().inst.find("ImageWrite") != std::string::npos;
-  const auto assembly = Preamble() + FragMain() + Bindings({20}) +
-                        (is_storage ? std::string("OpDecorate %20 ") +
-                                          std::string(is_write ? "NonReadable"
-                                                               : "NonWritable")
-                                    : std::string("")) +
-                        " " + CommonTypes() + R"(
-     %f_ty = OpTypeFunction %void %ptr_)" +
-                        GetParam().type + R"(
-
-     %20 = OpVariable %ptr_)" +
-                        GetParam().type + R"( UniformConstant
-
-     %func = OpFunction %void None %f_ty
-     %i_param = OpFunctionParameter %ptr_)" +
-                        GetParam().type + R"(
-     %func_entry = OpLabel
-     %im = OpLoad %)" + GetParam().type +
-                        R"( %i_param
-
-)" + GetParam().inst + R"(
-
-     OpReturn
-     OpFunctionEnd
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %foo = OpFunctionCall %void %func %20
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildInternalModule());
-  EXPECT_TRUE(p->RegisterHandleUsage());
-  EXPECT_TRUE(p->error().empty());
-  Usage iu = p->GetHandleUsage(20);
-
-  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
-
-  // Textures and samplers not yet supported as function parameters.
-  p->SkipDumpingPending("crbug.com/tint/785");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Samples,
-    SpvParserHandleTest_RegisterHandleUsage_RawImage,
-    ::testing::Values(
-
-        // OpImageRead
-        UsageRawImageCase{"f_storage_1d",
-                          "%result = OpImageRead %v4float %im %uint_1",
-                          "Usage(Texture( read ))"},
-
-        // OpImageWrite
-        UsageRawImageCase{"f_storage_1d",
-                          "OpImageWrite %im %uint_1 %v4float_null",
-                          "Usage(Texture( write ))"},
-
-        // OpImageFetch
-        UsageRawImageCase{"f_texture_1d",
-                          "%result = OpImageFetch "
-                          "%v4float %im %uint_0",
-                          "Usage(Texture( is_sampled ))"},
-
-        // Image queries
-
-        // OpImageQuerySizeLod
-        UsageRawImageCase{"f_texture_2d",
-                          "%result = OpImageQuerySizeLod "
-                          "%v2uint %im %uint_1",
-                          "Usage(Texture( is_sampled ))"},
-
-        // OpImageQuerySize
-        // Could be MS=1 or storage image. So it's non-committal.
-        UsageRawImageCase{"f_storage_2d",
-                          "%result = OpImageQuerySize "
-                          "%v2uint %im",
-                          "Usage()"},
-
-        // OpImageQueryLevels
-        UsageRawImageCase{"f_texture_2d",
-                          "%result = OpImageQueryLevels "
-                          "%uint %im",
-                          "Usage(Texture( ))"},
-
-        // OpImageQuerySamples
-        UsageRawImageCase{"f_texture_2d_ms",
-                          "%result = OpImageQuerySamples "
-                          "%uint %im",
-                          "Usage(Texture( is_sampled ms ))"}));
-
-// Test emission of handle variables.
-
-// Test emission of variables where we don't have enough clues from their
-// use in image access instructions in executable code.  For these we have
-// to infer usage from the SPIR-V sampler or image type.
-struct DeclUnderspecifiedHandleCase {
-  std::string decorations;  // SPIR-V decorations
-  std::string inst;         // SPIR-V variable declarations
-  std::string var_decl;     // WGSL variable declaration
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                const DeclUnderspecifiedHandleCase& c) {
-  out << "DeclUnderspecifiedHandleCase(" << c.inst << "\n" << c.var_decl << ")";
-  return out;
-}
-
-using SpvParserHandleTest_DeclUnderspecifiedHandle =
-    SpvParserTestBase<::testing::TestWithParam<DeclUnderspecifiedHandleCase>>;
-
-TEST_P(SpvParserHandleTest_DeclUnderspecifiedHandle, Variable) {
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %10 DescriptorSet 0
-     OpDecorate %10 Binding 0
-)" + GetParam().decorations +
-                        CommonTypes() + GetParam().inst +
-                        R"(
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = test::ToString(p->program());
-  EXPECT_THAT(program, HasSubstr(GetParam().var_decl)) << program;
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Samplers,
-    SpvParserHandleTest_DeclUnderspecifiedHandle,
-    ::testing::Values(
-
-        DeclUnderspecifiedHandleCase{
-            "", R"(
-         %ptr = OpTypePointer UniformConstant %sampler
-         %10 = OpVariable %ptr UniformConstant
-)",
-            R"(@group(0) @binding(0) var x_10 : sampler;)"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    Images,
-    SpvParserHandleTest_DeclUnderspecifiedHandle,
-    ::testing::Values(
-
-        DeclUnderspecifiedHandleCase{
-            "", R"(
-         %10 = OpVariable %ptr_f_texture_1d UniformConstant
-)",
-            R"(@group(0) @binding(0) var x_10 : texture_1d<f32>;)"},
-        DeclUnderspecifiedHandleCase{
-            R"(
-         OpDecorate %10 NonWritable
-)",
-            R"(
-         %10 = OpVariable %ptr_f_storage_1d UniformConstant
-)",
-            R"(@group(0) @binding(0) var x_10 : texture_1d<f32>;)"},
-        DeclUnderspecifiedHandleCase{
-            R"(
-         OpDecorate %10 NonReadable
-)",
-            R"(
-         %10 = OpVariable %ptr_f_storage_1d UniformConstant
-)",
-            R"(@group(0) @binding(0) var x_10 : texture_storage_1d<rg32float, write>;)"}));
-
-// Test handle declaration or error, when there is an image access.
-
-struct ImageDeclCase {
-  // SPIR-V image type, excluding result ID and opcode
-  std::string spirv_image_type_details;
-  std::string spirv_image_access;  // Optional instruction to provoke use
-  std::string expected_error;
-  std::string expected_decl;
-};
-
-inline std::ostream& operator<<(std::ostream& out, const ImageDeclCase& c) {
-  out << "ImageDeclCase(" << c.spirv_image_type_details << "\n"
-      << "access: " << c.spirv_image_access << "\n"
-      << "error: " << c.expected_error << "\n"
-      << "decl:" << c.expected_decl << "\n)";
-  return out;
-}
-
-using SpvParserHandleTest_ImageDeclTest =
-    SpvParserTestBase<::testing::TestWithParam<ImageDeclCase>>;
-
-TEST_P(SpvParserHandleTest_ImageDeclTest, DeclareAndUseHandle) {
-  // Only declare the sampled image type, and the associated variable
-  // if the requested image type is a sampled image type and not multisampled.
-  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
-                                         "0 1 Unknown") != std::string::npos;
-  const auto assembly =
-      Preamble() + R"(
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-     OpName %float_var "float_var"
-     OpName %ptr_float "ptr_float"
-     OpName %i1 "i1"
-     OpName %vi12 "vi12"
-     OpName %vi123 "vi123"
-     OpName %vi1234 "vi1234"
-     OpName %u1 "u1"
-     OpName %vu12 "vu12"
-     OpName %vu123 "vu123"
-     OpName %vu1234 "vu1234"
-     OpName %f1 "f1"
-     OpName %vf12 "vf12"
-     OpName %vf123 "vf123"
-     OpName %vf1234 "vf1234"
-     OpDecorate %10 DescriptorSet 0
-     OpDecorate %10 Binding 0
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-     OpDecorate %30 DescriptorSet 0
-     OpDecorate %30 Binding 1
-)" + CommonBasicTypes() +
-      R"(
-     %sampler = OpTypeSampler
-     %ptr_sampler = OpTypePointer UniformConstant %sampler
-     %im_ty = OpTypeImage )" +
-      GetParam().spirv_image_type_details + R"(
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
-      R"(
-
-     %ptr_float = OpTypePointer Function %float
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_im_ty UniformConstant
-     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %float_var = OpVariable %ptr_float Function
-
-     %i1 = OpCopyObject %int %int_1
-     %vi12 = OpCopyObject %v2int %the_vi12
-     %vi123 = OpCopyObject %v3int %the_vi123
-     %vi1234 = OpCopyObject %v4int %the_vi1234
-
-     %u1 = OpCopyObject %uint %uint_1
-     %vu12 = OpCopyObject %v2uint %the_vu12
-     %vu123 = OpCopyObject %v3uint %the_vu123
-     %vu1234 = OpCopyObject %v4uint %the_vu1234
-
-     %f1 = OpCopyObject %float %float_1
-     %vf12 = OpCopyObject %v2float %the_vf12
-     %vf123 = OpCopyObject %v3float %the_vf123
-     %vf1234 = OpCopyObject %v4float %the_vf1234
-
-     %sam = OpLoad %sampler %10
-     %im = OpLoad %im_ty %20
-
-)" +
-      (is_sampled_image_type
-           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
-           : "") +
-      GetParam().spirv_image_access +
-      R"(
-     ; Use an anchor for the cases when the image access doesn't have a result ID.
-     %1000 = OpCopyObject %uint %uint_0
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  const bool succeeded = p->BuildAndParseInternalModule();
-  if (succeeded) {
-    EXPECT_TRUE(GetParam().expected_error.empty());
-    const auto got = test::ToString(p->program());
-    EXPECT_THAT(got, HasSubstr(GetParam().expected_decl));
-  } else {
-    EXPECT_FALSE(GetParam().expected_error.empty());
-    EXPECT_THAT(p->error(), HasSubstr(GetParam().expected_error));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Multisampled_Only2DNonArrayedIsValid,
-    SpvParserHandleTest_ImageDeclTest,
-    ::testing::ValuesIn(std::vector<ImageDeclCase>{
-        {"%float 1D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
-        {"%float 1D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
-        {"%float 2D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "", "@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;"},
-        {"%float 2D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
-        {"%float 3D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
-        {"%float 3D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
-         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
-        {"%float Cube 0 0 1 1 Unknown",
-         "%result = OpImageQuerySamples %uint %im",
-         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
-        {"%float Cube 0 1 1 1 Unknown",
-         "%result = OpImageQuerySamples %uint %im",
-         "WGSL multisampled textures must be 2d and non-arrayed: ", ""}}));
-
-// Test emission of variables when we have image accesses in executable code.
-
-struct ImageAccessCase {
-  // SPIR-V image type, excluding result ID and opcode
-  std::string spirv_image_type_details;
-  std::string spirv_image_access;  // The provoking image access instruction.
-  std::string var_decl;            // WGSL variable declaration
-  std::string texture_builtin;     // WGSL texture usage.
-};
-inline std::ostream& operator<<(std::ostream& out, const ImageAccessCase& c) {
-  out << "ImageCase(" << c.spirv_image_type_details << "\n"
-      << c.spirv_image_access << "\n"
-      << c.var_decl << "\n"
-      << c.texture_builtin << ")";
-  return out;
-}
-
-using SpvParserHandleTest_SampledImageAccessTest =
-    SpvParserTestBase<::testing::TestWithParam<ImageAccessCase>>;
-
-TEST_P(SpvParserHandleTest_SampledImageAccessTest, Variable) {
-  // Only declare the sampled image type, and the associated variable
-  // if the requested image type is a sampled image type, and not a
-  // multisampled texture
-  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
-                                         "0 1 Unknown") != std::string::npos;
-  const auto assembly =
-      Preamble() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-     OpName %f1 "f1"
-     OpName %vf12 "vf12"
-     OpName %vf21 "vf21"
-     OpName %vf123 "vf123"
-     OpName %vf1234 "vf1234"
-     OpName %u1 "u1"
-     OpName %vu12 "vu12"
-     OpName %vu123 "vu123"
-     OpName %vu1234 "vu1234"
-     OpName %i1 "i1"
-     OpName %vi12 "vi12"
-     OpName %vi123 "vi123"
-     OpName %vi1234 "vi1234"
-     OpName %coords1 "coords1"
-     OpName %coords12 "coords12"
-     OpName %coords123 "coords123"
-     OpName %coords1234 "coords1234"
-     OpName %offsets2d "offsets2d"
-     OpName %u_offsets2d "u_offsets2d"
-     OpDecorate %10 DescriptorSet 0
-     OpDecorate %10 Binding 0
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-     OpDecorate %30 DescriptorSet 0
-     OpDecorate %30 Binding 1
-)" + CommonBasicTypes() +
-      R"(
-     %sampler = OpTypeSampler
-     %ptr_sampler = OpTypePointer UniformConstant %sampler
-     %im_ty = OpTypeImage )" +
-      GetParam().spirv_image_type_details + R"(
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
-      R"(
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_im_ty UniformConstant
-     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
-
-     ; ConstOffset operands must be constants
-     %offsets2d = OpConstantComposite %v2int %int_3 %int_4
-     %u_offsets2d = OpConstantComposite %v2uint %uint_3 %uint_4
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %f1 = OpCopyObject %float %float_1
-     %vf12 = OpCopyObject %v2float %the_vf12
-     %vf21 = OpCopyObject %v2float %the_vf21
-     %vf123 = OpCopyObject %v3float %the_vf123
-     %vf1234 = OpCopyObject %v4float %the_vf1234
-
-     %i1 = OpCopyObject %int %int_1
-     %vi12 = OpCopyObject %v2int %the_vi12
-     %vi123 = OpCopyObject %v3int %the_vi123
-     %vi1234 = OpCopyObject %v4int %the_vi1234
-
-     %u1 = OpCopyObject %uint %uint_1
-     %vu12 = OpCopyObject %v2uint %the_vu12
-     %vu123 = OpCopyObject %v3uint %the_vu123
-     %vu1234 = OpCopyObject %v4uint %the_vu1234
-
-     %coords1 = OpCopyObject %float %float_1
-     %coords12 = OpCopyObject %v2float %vf12
-     %coords123 = OpCopyObject %v3float %vf123
-     %coords1234 = OpCopyObject %v4float %vf1234
-
-     %sam = OpLoad %sampler %10
-     %im = OpLoad %im_ty %20
-)" +
-      (is_sampled_image_type
-           ? " %sampled_image = OpSampledImage %si_ty %im %sam\n"
-           : "") +
-      GetParam().spirv_image_access +
-      R"(
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = test::ToString(p->program());
-  EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
-      << "DECLARATIONS ARE BAD " << program;
-  EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
-      << "TEXTURE BUILTIN IS BAD " << program << assembly;
-
-  const bool is_query_size =
-      GetParam().spirv_image_access.find("ImageQuerySize") != std::string::npos;
-  const bool is_1d =
-      GetParam().spirv_image_type_details.find("1D") != std::string::npos;
-  if (is_query_size && is_1d) {
-    p->SkipDumpingPending("crbug.com/tint/788");
-  }
-}
-
-// TODO(dneto): Test variable declaration and texture builtins provoked by
-// use of an image access instruction inside helper function.
-TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage,
-       DISABLED_FunctionParam) {}
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageGather,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // OpImageGather 2D
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords12 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        "textureGather(1, x_20, x_10, coords12)"},
-        // OpImageGather 2D ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageGather "
-            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            "textureGather(1, x_20, x_10, coords12, vec2<i32>(3, 4))"},
-        // OpImageGather 2D ConstOffset unsigned
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
-                        "%u_offsets2d",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        "textureGather(1, x_20, x_10, coords12, "
-                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageGather 2D Array
-        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-                        "textureGather(1, x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)))"},
-        // OpImageGather 2D Array ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageGather "
-            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            "textureGather(1, x_20, x_10, coords123.xy, "
-            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
-        // OpImageGather 2D Array ConstOffset unsigned
-        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
-                        "%u_offsets2d",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-                        "textureGather(1, x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), "
-                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageGather Cube
-        ImageAccessCase{"%float Cube 0 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
-                        "textureGather(1, x_20, x_10, coords123)"},
-        // OpImageGather Cube Array
-        ImageAccessCase{"%float Cube 0 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords1234 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
-                        "textureGather(1, x_20, x_10, coords1234.xyz, "
-                        "i32(round(coords1234.w)))"},
-        // OpImageGather 2DDepth
-        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords12 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-                        "textureGather(x_20, x_10, coords12)"},
-        // OpImageGather 2DDepth ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageGather "
-            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-            "textureGather(x_20, x_10, coords12, vec2<i32>(3, 4))"},
-        // OpImageGather 2DDepth ConstOffset unsigned
-        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
-                        "%u_offsets2d",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-                        "textureGather(x_20, x_10, coords12, "
-                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageGather 2DDepth Array
-        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-                        "textureGather(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)))"},
-        // OpImageGather 2DDepth Array ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 1 1 0 1 Unknown",
-            "%result = OpImageGather "
-            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            "textureGather(x_20, x_10, coords123.xy, "
-            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
-        // OpImageGather 2DDepth Array ConstOffset unsigned
-        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
-                        "%u_offsets2d",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-                        "textureGather(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), "
-                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageGather DepthCube
-        ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords123 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-                        "textureGather(x_20, x_10, coords123)"},
-        // OpImageGather DepthCube Array
-        ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
-                        "%result = OpImageGather "
-                        "%v4float %sampled_image %coords1234 %int_1",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-                        "textureGather(x_20, x_10, coords1234.xyz, "
-                        "i32(round(coords1234.w)))"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageDrefGather,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // OpImageDrefGather 2DDepth
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageDrefGather "
-            "%v4float %sampled_image %coords12 %depth",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-            "textureGatherCompare(x_20, x_10, coords12, 0.200000003)"},
-        // OpImageDrefGather 2DDepth ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageDrefGather "
-            "%v4float %sampled_image %coords12 %depth ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-            "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
-            "vec2<i32>(3, 4))"},
-        // OpImageDrefGather 2DDepth ConstOffset unsigned
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageDrefGather "
-            "%v4float %sampled_image %coords12 %depth ConstOffset "
-            "%u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-            "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
-            "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageDrefGather 2DDepth Array
-        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
-                        "%result = OpImageDrefGather "
-                        "%v4float %sampled_image %coords123 %depth",
-                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-                        "textureGatherCompare(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), 0.200000003)"},
-        // OpImageDrefGather 2DDepth Array ConstOffset signed
-        ImageAccessCase{
-            "%float 2D 1 1 0 1 Unknown",
-            "%result = OpImageDrefGather "
-            "%v4float %sampled_image %coords123 %depth ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            "textureGatherCompare(x_20, x_10, coords123.xy, "
-            "i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4))"},
-        // OpImageDrefGather 2DDepth Array ConstOffset unsigned
-        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
-                        "%result = OpImageDrefGather "
-                        "%v4float %sampled_image %coords123 %depth ConstOffset "
-                        "%u_offsets2d",
-                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-                        "textureGatherCompare(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), 0.200000003, "
-                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageDrefGather DepthCube
-        ImageAccessCase{
-            "%float Cube 1 0 0 1 Unknown",
-            "%result = OpImageDrefGather "
-            "%v4float %sampled_image %coords123 %depth",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-            "textureGatherCompare(x_20, x_10, coords123, 0.200000003)"},
-        // OpImageDrefGather DepthCube Array
-        ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
-                        "%result = OpImageDrefGather "
-                        "%v4float %sampled_image %coords1234 %depth",
-                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-                        "textureGatherCompare(x_20, x_10, coords1234.xyz, "
-                        "i32(round(coords1234.w)), 0.200000003)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleImplicitLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleImplicitLod
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
-                        "%result = OpImageSampleImplicitLod "
-                        "%v4float %sampled_image %coords12",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        "textureSample(x_20, x_10, coords12)"},
-
-        // OpImageSampleImplicitLod arrayed
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords123",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            "textureSample(x_20, x_10, coords123.xy, i32(round(coords123.z)))"},
-
-        // OpImageSampleImplicitLod with ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords12 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            "textureSample(x_20, x_10, coords12, vec2<i32>(3, 4))"},
-
-        // OpImageSampleImplicitLod arrayed with ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords123 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSample(x_20, x_10, coords123.xy, i32(round(coords123.z)), vec2<i32>(3, 4)))"},
-
-        // OpImageSampleImplicitLod with Bias
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
-                        "%result = OpImageSampleImplicitLod "
-                        "%v4float %sampled_image %coords12 Bias %float_7",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        "textureSampleBias(x_20, x_10, coords12, 7.0)"},
-
-        // OpImageSampleImplicitLod arrayed with Bias
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords123 Bias %float_7",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0))"},
-
-        // OpImageSampleImplicitLod with Bias and signed ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords12 Bias|ConstOffset "
-            "%float_7 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(3, 4))"},
-
-        // OpImageSampleImplicitLod with Bias and unsigned ConstOffset
-        // Convert ConstOffset to signed
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords12 Bias|ConstOffset "
-            "%float_7 %u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
-        // OpImageSampleImplicitLod arrayed with Bias
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleImplicitLod "
-            "%v4float %sampled_image %coords123 Bias|ConstOffset "
-            "%float_7 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0, vec2<i32>(3, 4))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // This test shows the use of a sampled image used with both regular
-    // sampling and depth-reference sampling.  The texture is a depth-texture,
-    // and we use builtins textureSample and textureSampleCompare
-    ImageSampleImplicitLod_BothDrefAndNonDref,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleImplicitLod
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown", R"(
-     %sam_dref = OpLoad %sampler %30
-     %sampled_dref_image = OpSampledImage %si_ty %im %sam_dref
-
-     %200 = OpImageSampleImplicitLod %v4float %sampled_image %coords12
-     %210 = OpImageSampleDrefImplicitLod %float %sampled_dref_image %coords12 %depth
-)",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-
-@group(0) @binding(1) var x_30 : sampler_comparison;
-)",
-                        R"(
-  let x_200 : vec4<f32> = vec4<f32>(textureSample(x_20, x_10, coords12), 0.0, 0.0, 0.0);
-  let x_210 : f32 = textureSampleCompare(x_20, x_30, coords12, 0.200000003);
-)"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleDrefImplicitLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-        // ImageSampleDrefImplicitLod
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleDrefImplicitLod "
-            "%float %sampled_image %coords12 %depth",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003))"},
-        // ImageSampleDrefImplicitLod - arrayed
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleDrefImplicitLod "
-            "%float %sampled_image %coords123 %depth",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
-        // ImageSampleDrefImplicitLod with ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleDrefImplicitLod %float "
-            "%sampled_image %coords12 %depth ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003, vec2<i32>(3, 4)))"},
-        // ImageSampleDrefImplicitLod arrayed with ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleDrefImplicitLod %float "
-            "%sampled_image %coords123 %depth ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleDrefExplicitLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    // Lod must be float constant 0 due to a Metal constraint.
-    // Another test checks cases where the Lod is not float constant 0.
-    ::testing::Values(
-        // 2D
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod "
-            "%float %sampled_image %coords12 %depth Lod %float_0",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003))"},
-        // 2D array
-        ImageAccessCase{
-            "%float 2D 1 1 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod "
-            "%float %sampled_image %coords123 %depth Lod %float_0",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
-        // 2D, ConstOffset
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod %float "
-            "%sampled_image %coords12 %depth Lod|ConstOffset "
-            "%float_0 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003, vec2<i32>(3, 4)))"},
-        // 2D array, ConstOffset
-        ImageAccessCase{
-            "%float 2D 1 1 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod %float "
-            "%sampled_image %coords123 %depth Lod|ConstOffset "
-            "%float_0 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4)))"},
-        // Cube
-        ImageAccessCase{
-            "%float Cube 1 0 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod "
-            "%float %sampled_image %coords123 %depth Lod %float_0",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords123, 0.200000003))"},
-        // Cube array
-        ImageAccessCase{
-            "%float Cube 1 1 0 1 Unknown",
-            "%result = OpImageSampleDrefExplicitLod "
-            "%float %sampled_image %coords1234 %depth Lod %float_0",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords1234.xyz, i32(round(coords1234.w)), 0.200000003))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleExplicitLod_UsingLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleExplicitLod - using Lod
-        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
-                        "%result = OpImageSampleExplicitLod "
-                        "%v4float %sampled_image %coords12 Lod %float_null",
-                        R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0))"},
-
-        // OpImageSampleExplicitLod arrayed - using Lod
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords123 Lod %float_null",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0))"},
-
-        // OpImageSampleExplicitLod - using Lod and ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords12 Lod|ConstOffset "
-            "%float_null %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(3, 4)))"},
-
-        // OpImageSampleExplicitLod - using Lod and unsigned ConstOffset
-        // Convert the ConstOffset operand to signed
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords12 Lod|ConstOffset "
-            "%float_null %u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
-
-        // OpImageSampleExplicitLod arrayed - using Lod and ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords123 Lod|ConstOffset "
-            "%float_null %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0, vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleExplicitLod_UsingGrad,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleExplicitLod - using Grad
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords12 Grad %vf12 %vf21",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21))"},
-
-        // OpImageSampleExplicitLod arrayed - using Grad
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords123 Grad %vf12 %vf21",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21))"},
-
-        // OpImageSampleExplicitLod - using Grad and ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords12 Grad|ConstOffset "
-            "%vf12 %vf21 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2<i32>(3, 4)))"},
-
-        // OpImageSampleExplicitLod - using Grad and unsigned ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords12 Grad|ConstOffset "
-            "%vf12 %vf21 %u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2<i32>(vec2<u32>(3u, 4u)))"},
-
-        // OpImageSampleExplicitLod arrayed - using Grad and ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords123 Grad|ConstOffset "
-            "%vf12 %vf21 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2<i32>(3, 4)))"},
-
-        // OpImageSampleExplicitLod arrayed - using Grad and unsigned
-        // ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 1 0 1 Unknown",
-            "%result = OpImageSampleExplicitLod "
-            "%v4float %sampled_image %coords123 Grad|ConstOffset "
-            "%vf12 %vf21 %u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2<i32>(vec2<u32>(3u, 4u))))"}));
-
-// Test crbug.com/378:
-// In WGSL, sampling from depth texture with explicit level of detail
-// requires the Lod parameter as an unsigned integer.
-// This corresponds to SPIR-V OpSampleExplicitLod and WGSL textureSampleLevel.
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleExplicitLod_DepthTexture,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Test a non-depth case.
-        // (This is already tested above in the ImageSampleExplicitLod suite,
-        // but I'm repeating here for the contrast with the depth case.)
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4float "
-         "%sampled_image %vf12 Lod %f1",
-         R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(textureSampleLevel(x_20, x_10, vf12, f1))"},
-        // Test a depth case
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4float "
-         "%sampled_image %vf12 Lod %f1",
-         R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-         R"(vec4<f32>(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0, 0.0, 0.0))"}}));
-
-/////
-// Projection sampling
-/////
-
-// Test matrix for projection sampling:
-// sampling
-//   Dimensions: 1D, 2D, 3D, 2DShadow
-//   Variations: Proj { ImplicitLod { | Bias } | ExplicitLod { Lod | Grad | } }
-//   x { | ConstOffset }
-// depth-compare sampling
-//   Dimensions: 2D
-//   Variations: Proj Dref { ImplicitLod { | Bias } | ExplicitLod { Lod | Grad |
-//   } } x { | ConstOffset }
-//
-// Expanded:
-//    ImageSampleProjImplicitLod        // { | ConstOffset }
-//    ImageSampleProjImplicitLod_Bias   // { | ConstOffset }
-//    ImageSampleProjExplicitLod_Lod    // { | ConstOffset }
-//    ImageSampleProjExplicitLod_Grad   // { | ConstOffset }
-//
-//    ImageSampleProjImplicitLod_DepthTexture
-//
-//    ImageSampleProjDrefImplicitLod        // { | ConstOffset }
-//    ImageSampleProjDrefExplicitLod_Lod    // { | ConstOffset }
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjImplicitLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleProjImplicitLod 1D
-        ImageAccessCase{
-            "%float 1D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords12",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
-            R"(textureSample(x_20, x_10, (coords12.x / coords12.y)))"},
-
-        // OpImageSampleProjImplicitLod 2D
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSample(x_20, x_10, (coords123.xy / coords123.z)))"},
-
-        // OpImageSampleProjImplicitLod 3D
-        ImageAccessCase{
-            "%float 3D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords1234",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-            R"(textureSample(x_20, x_10, (coords1234.xyz / coords1234.w)))"},
-
-        // OpImageSampleProjImplicitLod 2D with ConstOffset
-        // (Don't need to test with 1D or 3D, as the hard part was the splatted
-        // division.) This case tests handling of the ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSample(x_20, x_10, (coords123.xy / coords123.z), vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjImplicitLod_Bias,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleProjImplicitLod with Bias
-        // Only testing 2D
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123 Bias %float_7",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0))"},
-
-        // OpImageSampleProjImplicitLod with Bias and signed ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123 Bias|ConstOffset "
-            "%float_7 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(3, 4)))"},
-
-        // OpImageSampleProjImplicitLod with Bias and unsigned ConstOffset
-        // Convert ConstOffset to signed
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123 Bias|ConstOffset "
-            "%float_7 %u_offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(vec2<u32>(3u, 4u))))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjExplicitLod_Lod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-        // OpImageSampleProjExplicitLod 2D
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjExplicitLod "
-            "%v4float %sampled_image %coords123 Lod %f1",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, (coords123.xy / coords123.z), f1))"},
-
-        // OpImageSampleProjExplicitLod 2D Lod with ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjExplicitLod "
-            "%v4float %sampled_image %coords123 Lod|ConstOffset %f1 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, (coords123.xy / coords123.z), f1, vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjExplicitLod_Grad,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-        // OpImageSampleProjExplicitLod 2D Grad
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjExplicitLod "
-            "%v4float %sampled_image %coords123 Grad %vf12 %vf21",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, (coords123.xy / coords123.z), vf12, vf21))"},
-
-        // OpImageSampleProjExplicitLod 2D Lod Grad ConstOffset
-        ImageAccessCase{
-            "%float 2D 0 0 0 1 Unknown",
-            "%result = OpImageSampleProjExplicitLod "
-            "%v4float %sampled_image %coords123 Grad|ConstOffset "
-            "%vf12 %vf21 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, (coords123.xy / coords123.z), vf12, vf21, vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Ordinary (non-comparison) sampling on a depth texture.
-    ImageSampleProjImplicitLod_DepthTexture,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-        // OpImageSampleProjImplicitLod 2D depth
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleProjImplicitLod "
-            "%v4float %sampled_image %coords123",
-            R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            // Sampling the depth texture yields an f32, but the
-            // SPIR-V operation yiedls vec4<f32>, so fill out the
-            // remaining components with 0.
-            R"(vec4<f32>(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0, 0.0, 0.0))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjDrefImplicitLod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // OpImageSampleProjDrefImplicitLod 2D depth-texture
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleProjDrefImplicitLod "
-            "%float %sampled_image %coords123 %f1",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), f1))"},
-
-        // OpImageSampleProjDrefImplicitLod 2D depth-texture, ConstOffset
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleProjDrefImplicitLod "
-            "%float %sampled_image %coords123 %f1 ConstOffset %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), f1, vec2<i32>(3, 4)))"}));
-
-INSTANTIATE_TEST_SUITE_P(
-    DISABLED_ImageSampleProjDrefExplicitLod_Lod,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::Values(
-
-        // Lod must be float constant 0 due to a Metal constraint.
-        // Another test checks cases where the Lod is not float constant 0.
-
-        // OpImageSampleProjDrefExplicitLod 2D depth-texture Lod
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleProjDrefExplicitLod "
-            "%float %sampled_image %coords123 %depth Lod %float_0",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0))"},
-
-        // OpImageSampleProjDrefImplicitLod 2D depth-texture, Lod ConstOffset
-        ImageAccessCase{
-            "%float 2D 1 0 0 1 Unknown",
-            "%result = OpImageSampleProjDrefExplicitLod "
-            "%float %sampled_image %coords123 %depth "
-            "Lod|ConstOffset %float_0 %offsets2d",
-            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
-
-@group(2) @binding(1) var x_20 : texture_depth_2d;
-)",
-            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0, vec2<i32>(3, 4)))"}));
-
-/////
-// End projection sampling
-/////
-
-using SpvParserHandleTest_ImageAccessTest =
-    SpvParserTestBase<::testing::TestWithParam<ImageAccessCase>>;
-
-TEST_P(SpvParserHandleTest_ImageAccessTest, Variable) {
-  // In this test harness, we only create an image.
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-     OpName %f1 "f1"
-     OpName %vf12 "vf12"
-     OpName %vf123 "vf123"
-     OpName %vf1234 "vf1234"
-     OpName %u1 "u1"
-     OpName %vu12 "vu12"
-     OpName %vu123 "vu123"
-     OpName %vu1234 "vu1234"
-     OpName %i1 "i1"
-     OpName %vi12 "vi12"
-     OpName %vi123 "vi123"
-     OpName %vi1234 "vi1234"
-     OpName %offsets2d "offsets2d"
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-)" + CommonBasicTypes() +
-                        R"(
-     %im_ty = OpTypeImage )" +
-                        GetParam().spirv_image_type_details + R"(
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-     %20 = OpVariable %ptr_im_ty UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %f1 = OpCopyObject %float %float_1
-     %vf12 = OpCopyObject %v2float %the_vf12
-     %vf123 = OpCopyObject %v3float %the_vf123
-     %vf1234 = OpCopyObject %v4float %the_vf1234
-
-     %i1 = OpCopyObject %int %int_1
-     %vi12 = OpCopyObject %v2int %the_vi12
-     %vi123 = OpCopyObject %v3int %the_vi123
-     %vi1234 = OpCopyObject %v4int %the_vi1234
-
-     %u1 = OpCopyObject %uint %uint_1
-     %vu12 = OpCopyObject %v2uint %the_vu12
-     %vu123 = OpCopyObject %v3uint %the_vu123
-     %vu1234 = OpCopyObject %v4uint %the_vu1234
-
-     %value_offset = OpCompositeConstruct %v2int %int_3 %int_4
-     %offsets2d = OpCopyObject %v2int %value_offset
-     %im = OpLoad %im_ty %20
-
-)" + GetParam().spirv_image_access +
-                        R"(
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = test::ToString(p->program());
-  EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
-      << "DECLARATIONS ARE BAD " << program;
-  EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
-      << "TEXTURE BUILTIN IS BAD " << program << assembly;
-}
-
-INSTANTIATE_TEST_SUITE_P(ImageWrite_OptionalParams,
-                         SpvParserHandleTest_ImageAccessTest,
-                         ::testing::ValuesIn(std::vector<ImageAccessCase>{
-                             // OpImageWrite with no extra params
-                             {"%float 2D 0 0 0 2 Rgba32f",
-                              "OpImageWrite %im %vi12 %vf1234",
-                              "@group(2) @binding(1) var x_20 : "
-                              "texture_storage_2d<rgba32float, write>;",
-                              "textureStore(x_20, vi12, vf1234);"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // SPIR-V's texel parameter is a scalar or vector with at least as many
-    // components as there are channels in the underlying format, and the
-    // componet type matches the sampled type (modulo signed/unsigned integer).
-    // WGSL's texel parameter is a 4-element vector scalar or vector, with
-    // component type equal to the 32-bit form of the channel type.
-    ImageWrite_ConvertTexelOperand_Arity,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Source 1 component
-        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %f1",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(f1, 0.0, 0.0, 0.0));"},
-        // Source 2 component, dest 1 component
-        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf12",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
-        // Source 3 component, dest 1 component
-        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf123",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
-        // Source 4 component, dest 1 component
-        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf1234",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vf1234);"},
-        // Source 2 component, dest 2 component
-        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf12",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
-        // Source 3 component, dest 2 component
-        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf123",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
-        // Source 4 component, dest 2 component
-        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf1234",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vf1234);"},
-        // WGSL does not support 3-component storage textures.
-        // Source 4 component, dest 4 component
-        {"%float 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vf1234",
-         "@group(2) @binding(1) var x_20 : "
-         "texture_storage_2d<rgba32float, write>;",
-         "textureStore(x_20, vi12, vf1234);"}}));
-
-TEST_F(SpvParserHandleTest, ImageWrite_TooFewSrcTexelComponents_1_vs_4) {
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-     OpName %f1 "f1"
-     OpName %coords12 "coords12"
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-)" + CommonBasicTypes() +
-                        R"(
-     %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgba32f
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-
-     %20 = OpVariable %ptr_im_ty UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %f1 = OpCopyObject %float %float_1
-
-     %coords12 = OpCopyObject %v2float %the_vf12
-
-     %im = OpLoad %im_ty %20
-     OpImageWrite %im %coords12 %f1
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              Eq("texel has too few components for storage texture: 1 provided "
-                 "but 4 required, in: OpImageWrite %54 %3 %2"))
-      << p->error();
-}
-
-TEST_F(SpvParserHandleTest, ImageWrite_ThreeComponentStorageTexture_IsError) {
-  // SPIR-V doesn't allow a 3-element storage texture format.
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %main "main"
-     OpExecutionMode %main OriginUpperLeft
-     OpName %vf123 "vf123"
-     OpName %coords12 "coords12"
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-)" + CommonBasicTypes() +
-                        R"(
-     %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgb32f
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-
-     %20 = OpVariable %ptr_im_ty UniformConstant
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %vf123 = OpCopyObject %v3float %the_vf123
-
-     %coords12 = OpCopyObject %v2float %the_vf12
-
-     %im = OpLoad %im_ty %20
-     OpImageWrite %im %coords12 %vf123
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto error = test::AssembleFailure(assembly);
-  EXPECT_THAT(error, HasSubstr("Invalid image format 'Rgb32f'"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    // The texel operand signedness must match the channel type signedness.
-    // SPIR-V validation checks that.
-    // This suite is for the cases where they are integral and the same
-    // signedness.
-    ImageWrite_ConvertTexelOperand_SameSignedness,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Sampled type is unsigned int, texel is unsigned int
-        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vu1234",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)",
-         R"(textureStore(x_20, vi12, vu1234))"},
-        // Sampled type is signed int, texel is signed int
-        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vi1234",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;)",
-         R"(textureStore(x_20, vi12, vi1234))"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Error out when OpImageWrite write texel differ in float vs. integral
-    ImageWrite_ConvertTexelOperand_DifferentFloatishness_IsError,
-    // Use the ImageDeclTest so we can check the error.
-    SpvParserHandleTest_ImageDeclTest,
-    ::testing::ValuesIn(std::vector<ImageDeclCase>{
-        // Sampled type is float, texel is signed int
-        {"%uint 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vi1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32float, write>;)"},
-        // Sampled type is float, texel is unsigned int
-        {"%int 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vu1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32float, write>;)"},
-        // Sampled type is unsigned int, texel is float
-        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vf1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)"},
-        // Sampled type is signed int, texel is float
-        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vf1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;
-  })"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Error out when OpImageWrite write texel signedness is different.
-    ImageWrite_ConvertTexelOperand_DifferentSignedness_IsError,
-    // Use the ImageDeclTest so we can check the error.
-    SpvParserHandleTest_ImageDeclTest,
-    ::testing::ValuesIn(std::vector<ImageDeclCase>{
-        // Sampled type is unsigned int, texel is signed int
-        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vi1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)"},
-        // Sampled type is signed int, texel is unsigned int
-        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vu1234",
-         "invalid texel type for storage texture write: component must be "
-         "float, signed integer, or unsigned integer to match the texture "
-         "channel type: OpImageWrite",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;
-  })"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Show that zeros of the correct integer signedness are
-    // created when expanding an integer vector.
-    ImageWrite_ConvertTexelOperand_Signedness_AndWidening,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Source unsigned, dest unsigned
-        {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu12",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
-         R"(textureStore(x_20, vi12, vec4<u32>(vu12, 0u, 0u)))"},
-        // Source signed, dest signed
-        {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
-         R"(textureStore(x_20, vi12, vec4<i32>(vi12, 0, 0)))"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageFetch_OptionalParams,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // OpImageFetch with no extra params, on sampled texture
-        // Level of detail is injected for sampled texture
-        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
-        // OpImageFetch with explicit level, on sampled texture
-        {"%float 2D 0 0 0 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 3);)"},
-        // OpImageFetch with no extra params, on depth texture
-        // Level of detail is injected for depth texture
-        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0), 0.0, 0.0, 0.0);)"},
-        // OpImageFetch with extra params, on depth texture
-        {"%float 2D 1 0 0 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 3), 0.0, 0.0, 0.0);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageFetch_Depth,
-    // In SPIR-V OpImageFetch always yields a vector of 4
-    // elements, even for depth images.  But in WGSL,
-    // textureLoad on a depth image yields f32.
-    // crbug.com/tint/439
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // ImageFetch on depth image.
-        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 ",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0), 0.0, 0.0, 0.0);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageFetch_DepthMultisampled,
-    // In SPIR-V OpImageFetch always yields a vector of 4
-    // elements, even for depth images.  But in WGSL,
-    // textureLoad on a depth image yields f32.
-    // crbug.com/tint/439
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // ImageFetch on multisampled depth image.
-        {"%float 2D 1 0 1 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_multisampled_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, i1), 0.0, 0.0, 0.0);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageFetch_Multisampled,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // SPIR-V requires a Sample image operand when operating on a
-        // multisampled image.
-
-        // ImageFetch arrayed
-        // Not in WebGPU
-
-        // ImageFetch non-arrayed
-        {"%float 2D 0 0 1 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
-         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i1);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageFetch_Multisampled_ConvertSampleOperand,
-    SpvParserHandleTest_ImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        {"%float 2D 0 0 1 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 Sample %u1",
-         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i32(u1));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ConvertResultSignedness,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Valid SPIR-V only has:
-        //      float scalar sampled type vs. floating result
-        //      integral scalar sampled type vs. integral result
-        // Any of the sampling, reading, or fetching use the same codepath.
-
-        // We'll test with:
-        //     OpImageFetch
-        //     OpImageRead
-        //     OpImageSampleImplicitLod - representative of sampling
-
-        //
-        // OpImageRead
-        //
-
-        // OpImageFetch requires no conversion, float -> v4float
-        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
-        // OpImageFetch requires no conversion, uint -> v4uint
-        {"%uint 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4uint %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
-         R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0);)"},
-        // OpImageFetch requires conversion, uint -> v4int
-        // is invalid SPIR-V:
-        // "Expected Image 'Sampled Type' to be the same as Result Type
-        // components"
-
-        // OpImageFetch requires no conversion, int -> v4int
-        {"%int 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4int %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
-         R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0);)"},
-        // OpImageFetch requires conversion, int -> v4uint
-        // is invalid SPIR-V:
-        // "Expected Image 'Sampled Type' to be the same as Result Type
-        // components"
-
-        //
-        // OpImageRead
-        //
-
-        // OpImageRead requires no conversion, float -> v4float
-        {"%float 2D 0 0 0 2 Rgba32f", "%99 = OpImageRead %v4float %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
-        // OpImageRead requires no conversion, uint -> v4uint
-        {"%uint 2D 0 0 0 2 Rgba32ui", "%99 = OpImageRead %v4uint %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
-         R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0);)"},
-
-        // OpImageRead requires conversion, uint -> v4int
-        // is invalid SPIR-V:
-        // "Expected Image 'Sampled Type' to be the same as Result Type
-        // components"
-
-        // OpImageRead requires no conversion, int -> v4int
-        {"%int 2D 0 0 0 2 Rgba32i", "%99 = OpImageRead %v4int %im %vi12",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
-         R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0);)"},
-
-        // OpImageRead requires conversion, int -> v4uint
-        // is invalid SPIR-V:
-        // "Expected Image 'Sampled Type' to be the same as Result Type
-        // components"
-
-        //
-        // Sampling operations, using OpImageSampleImplicitLod as an example.
-        // WGSL sampling operations only work on textures with a float sampled
-        // component.  So we can only test the float -> float (non-conversion)
-        // case.
-
-        // OpImageSampleImplicitLod requires no conversion, float -> v4float
-        {"%float 2D 0 0 0 1 Unknown",
-         "%99 = OpImageSampleImplicitLod %v4float %sampled_image %vf12",
-         R"(@group(0) @binding(0) var x_10 : sampler;
-
-@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureSample(x_20, x_10, vf12);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySize_NonArrayed_SignedResult,
-    // ImageQuerySize requires storage image or multisampled
-    // For storage image, use another instruction to indicate whether it
-    // is readonly or writeonly.
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // 1D storage image
-        {"%float 1D 0 0 0 2 Rgba32f",
-         "%99 = OpImageQuerySize %int %im \n"
-         "%98 = OpImageRead %v4float %im %i1\n",  // Implicitly mark as
-                                                  // NonWritable
-         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
-         R"(let x_99 : i32 = i32(textureDimensions(x_20));)"},
-        // 2D storage image
-        {"%float 2D 0 0 0 2 Rgba32f",
-         "%99 = OpImageQuerySize %v2int %im \n"
-         "%98 = OpImageRead %v4float %im %vi12\n",  // Implicitly mark as
-                                                    // NonWritable
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20))"},
-        // 3D storage image
-        {"%float 3D 0 0 0 2 Rgba32f",
-         "%99 = OpImageQuerySize %v3int %im \n"
-         "%98 = OpImageRead %v4float %im %vi123\n",  // Implicitly mark as
-                                                     // NonWritable
-         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20));)"},
-
-        // Multisampled
-        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySize %v2int %im \n",
-         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySize_Arrayed_SignedResult,
-    // ImageQuerySize requires storage image or multisampled
-    // For storage image, use another instruction to indicate whether it
-    // is readonly or writeonly.
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // 1D array storage image doesn't exist.
-
-        // 2D array storage image
-        {"%float 2D 0 1 0 2 Rgba32f",
-         "%99 = OpImageQuerySize %v3int %im \n"
-         "%98 = OpImageRead %v4float %im %vi123\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20), textureNumLayers(x_20));)"}
-        // 3D array storage image doesn't exist.
-
-        // Multisampled array
-        // Not in WebGPU
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel,
-    // From VUID-StandaloneSpirv-OpImageQuerySizeLod-04659:
-    //  ImageQuerySizeLod requires Sampled=1
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // 1D
-        {"%float 1D 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
-         R"(let x_99 : i32 = i32(textureDimensions(x_20, i1)))"},
-
-        // 2D
-        {"%float 2D 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
-
-        // 3D
-        {"%float 3D 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1));)"},
-
-        // Cube
-        {"%float Cube 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"},
-
-        // Depth 2D
-        {"%float 2D 1 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
-
-        // Depth Cube
-        {"%float Cube 1 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel,
-    // ImageQuerySize requires storage image or multisampled
-    // For storage image, use another instruction to indicate whether it
-    // is readonly or writeonly.
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-
-        // There is no 1D array
-
-        // 2D array
-        {"%float 2D 0 1 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1), textureNumLayers(x_20));)"},
-
-        // There is no 3D array
-
-        // Cube array
-        //
-        // Currently textureDimension on cube returns vec3 but maybe should
-        // return vec2
-        // https://github.com/gpuweb/gpuweb/issues/1345
-        {"%float Cube 0 1 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20));)"},
-
-        // Depth 2D array
-        {"%float 2D 1 1 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1), textureNumLayers(x_20));)"},
-
-        // Depth Cube Array
-        //
-        // Currently textureDimension on cube returns vec3 but maybe should
-        // return vec2
-        // https://github.com/gpuweb/gpuweb/issues/1345
-        {"%float Cube 1 1 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // When the level-of-detail value is given as an unsigned
-    // integer, we must convert it before using it as an argument
-    // to textureDimensions.
-    ImageQuerySizeLod_NonArrayed_SignedResult_UnsignedLevel,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-
-        {"%float 1D 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %int %im %u1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
-         R"(let x_99 : i32 = i32(textureDimensions(x_20, i32(u1)));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // When SPIR-V wants the result type to be unsigned, we have to
-    // insert a type constructor or bitcast for WGSL to do the type
-    // coercion. But the algorithm already does that as a matter
-    // of course.
-    ImageQuerySizeLod_NonArrayed_UnsignedResult_SignedLevel,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-
-        {"%float 1D 0 0 0 1 Unknown",
-         "%99 = OpImageQuerySizeLod %uint %im %i1\n",
-         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
-         R"(let x_99 : u32 = u32(textureDimensions(x_20, i1));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQueryLevels_SignedResult,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // In Vulkan:
-        //      Dim must be 1D, 2D, 3D, Cube
-        // WGSL allows 2d, 2d_array, 3d, cube, cube_array
-        // depth_2d, depth_2d_array, depth_cube, depth_cube_array
-
-        // 2D
-        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // 2D array
-        {"%float 2D 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // 3D
-        {"%float 3D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // Cube
-        {"%float Cube 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // Cube array
-        {"%float Cube 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // depth 2d
-        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // depth 2d array
-        {"%float 2D 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // depth cube
-        {"%float Cube 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
-
-        // depth cube array
-        {"%float Cube 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-         R"(let x_99 : i32 = textureNumLevels(x_20);)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Spot check that a type conversion is inserted when SPIR-V asks for
-    // an unsigned int result.
-    ImageQueryLevels_UnsignedResult,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %uint %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : u32 = u32(textureNumLevels(x_20));)"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySamples_SignedResult,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Multsample 2D
-        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySamples %int %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-         R"(let x_99 : i32 = textureNumSamples(x_20);)"}  // namespace
-
-        // Multisample 2D array
-        // Not in WebGPU
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    // Translation must inject a type coersion from signed to unsigned.
-    ImageQuerySamples_UnsignedResult,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Multsample 2D
-        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySamples %uint %im\n",
-         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-         R"(let x_99 : u32 = u32(textureNumSamples(x_20));)"}
-
-        // Multisample 2D array
-        // Not in WebGPU
-    }));
-
-struct ImageCoordsCase {
-  // SPIR-V image type, excluding result ID and opcode
-  std::string spirv_image_type_details;
-  std::string spirv_image_access;
-  std::string expected_error;
-  std::vector<std::string> expected_expressions;
-};
-
-inline std::ostream& operator<<(std::ostream& out, const ImageCoordsCase& c) {
-  out << "ImageCoordsCase(" << c.spirv_image_type_details << "\n"
-      << c.spirv_image_access << "\n"
-      << "expected_error(" << c.expected_error << ")\n";
-
-  for (auto e : c.expected_expressions) {
-    out << e << ",";
-  }
-  out << ")" << std::endl;
-  return out;
-}
-
-using SpvParserHandleTest_ImageCoordsTest =
-    SpvParserTestBase<::testing::TestWithParam<ImageCoordsCase>>;
-
-TEST_P(SpvParserHandleTest_ImageCoordsTest,
-       MakeCoordinateOperandsForImageAccess) {
-  // Only declare the sampled image type, and the associated variable
-  // if the requested image type is a sampled image type and not multisampled.
-  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
-                                         "0 1 Unknown") != std::string::npos;
-  const auto assembly =
-      Preamble() + R"(
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-     OpName %float_var "float_var"
-     OpName %ptr_float "ptr_float"
-     OpName %i1 "i1"
-     OpName %vi12 "vi12"
-     OpName %vi123 "vi123"
-     OpName %vi1234 "vi1234"
-     OpName %u1 "u1"
-     OpName %vu12 "vu12"
-     OpName %vu123 "vu123"
-     OpName %vu1234 "vu1234"
-     OpName %f1 "f1"
-     OpName %vf12 "vf12"
-     OpName %vf123 "vf123"
-     OpName %vf1234 "vf1234"
-     OpDecorate %10 DescriptorSet 0
-     OpDecorate %10 Binding 0
-     OpDecorate %20 DescriptorSet 2
-     OpDecorate %20 Binding 1
-     OpDecorate %30 DescriptorSet 0
-     OpDecorate %30 Binding 1
-)" + CommonBasicTypes() +
-      R"(
-     %sampler = OpTypeSampler
-     %ptr_sampler = OpTypePointer UniformConstant %sampler
-     %im_ty = OpTypeImage )" +
-      GetParam().spirv_image_type_details + R"(
-     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
-      R"(
-
-     %ptr_float = OpTypePointer Function %float
-
-     %10 = OpVariable %ptr_sampler UniformConstant
-     %20 = OpVariable %ptr_im_ty UniformConstant
-     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
-
-     %100 = OpFunction %void None %voidfn
-     %entry = OpLabel
-
-     %float_var = OpVariable %ptr_float Function
-
-     %i1 = OpCopyObject %int %int_1
-     %vi12 = OpCopyObject %v2int %the_vi12
-     %vi123 = OpCopyObject %v3int %the_vi123
-     %vi1234 = OpCopyObject %v4int %the_vi1234
-
-     %u1 = OpCopyObject %uint %uint_1
-     %vu12 = OpCopyObject %v2uint %the_vu12
-     %vu123 = OpCopyObject %v3uint %the_vu123
-     %vu1234 = OpCopyObject %v4uint %the_vu1234
-
-     %f1 = OpCopyObject %float %float_1
-     %vf12 = OpCopyObject %v2float %the_vf12
-     %vf123 = OpCopyObject %v3float %the_vf123
-     %vf1234 = OpCopyObject %v4float %the_vf1234
-
-     %sam = OpLoad %sampler %10
-     %im = OpLoad %im_ty %20
-
-)" +
-      (is_sampled_image_type
-           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
-           : "") +
-      GetParam().spirv_image_access +
-      R"(
-     ; Use an anchor for the cases when the image access doesn't have a result ID.
-     %1000 = OpCopyObject %uint %uint_0
-
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  if (!p->BuildAndParseInternalModule()) {
-    EXPECT_THAT(p->error(), StartsWith(GetParam().expected_error)) << assembly;
-  } else {
-    EXPECT_TRUE(p->error().empty()) << p->error();
-    auto fe = p->function_emitter(100);
-    // We actually have to generate the module to cache expressions for the
-    // result IDs, particularly the OpCopyObject
-    fe.Emit();
-
-    const spvtools::opt::Instruction* anchor = p->GetInstructionForTest(1000);
-    ASSERT_NE(anchor, nullptr);
-    const spvtools::opt::Instruction& image_access = *(anchor->PreviousNode());
-
-    ast::ExpressionList result =
-        fe.MakeCoordinateOperandsForImageAccess(image_access);
-    if (GetParam().expected_error.empty()) {
-      EXPECT_TRUE(fe.success()) << p->error();
-      EXPECT_TRUE(p->error().empty());
-      std::vector<std::string> result_strings;
-      Program program = p->program();
-      for (auto* expr : result) {
-        ASSERT_NE(expr, nullptr);
-        result_strings.push_back(test::ToString(program, expr));
-      }
-      EXPECT_THAT(result_strings,
-                  ::testing::ContainerEq(GetParam().expected_expressions));
-    } else {
-      EXPECT_FALSE(fe.success());
-      EXPECT_THAT(p->error(), Eq(GetParam().expected_error)) << assembly;
-      EXPECT_TRUE(result.empty());
-    }
-  }
-
-  const bool is_sample_level =
-      GetParam().spirv_image_access.find("ImageSampleExplicitLod") !=
-      std::string::npos;
-  const bool is_comparison_sample_level =
-      GetParam().spirv_image_access.find("ImageSampleDrefExplicitLod") !=
-      std::string::npos;
-  const bool is_1d =
-      GetParam().spirv_image_type_details.find("1D") != std::string::npos;
-  if (is_sample_level && is_1d) {
-    p->SkipDumpingPending("crbug.com/tint/789");
-  }
-  if (is_comparison_sample_level) {
-    p->SkipDumpingPending("crbug.com/tint/425");
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(Good_1D,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %f1",
-                              "",
-                              {"f1"}},
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf12",  // one excess arg
-                              "",
-                              {"vf12.x"}},
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf123",  // two excess args
-                              "",
-                              {"vf123.x"}},
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf1234",  // three excess args
-                              "",
-                              {"vf1234.x"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_1DArray,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 1D 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf12",
-                              "",
-                              {"vf12.x", "i32(round(vf12.y))"}},
-                             {"%float 1D 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf123",  // one excess arg
-                              "",
-                              {"vf123.x", "i32(round(vf123.y))"}},
-                             {"%float 1D 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf1234",  // two excess args
-                              "",
-                              {"vf1234.x", "i32(round(vf1234.y))"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_2D,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 2D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf12",
-                              "",
-                              {"vf12"}},
-                             {"%float 2D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf123",  // one excess arg
-                              "",
-                              {"vf123.xy"}},
-                             {"%float 2D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf1234",  // two excess args
-                              "",
-                              {"vf1234.xy"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_2DArray,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 2D 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf123",
-                              "",
-                              {"vf123.xy", "i32(round(vf123.z))"}},
-                             {"%float 2D 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod %v4float "
-                              "%sampled_image %vf1234",  // one excess arg
-                              "",
-                              {"vf1234.xy", "i32(round(vf1234.z))"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_3D,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 3D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod "
-                              "%v4float "
-                              "%sampled_image %vf123",
-                              "",
-                              {"vf123"}},
-                             {"%float 3D 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod "
-                              "%v4float "
-                              "%sampled_image %vf1234",  // one excess
-                                                         // arg
-                              "",
-                              {"vf1234.xyz"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_Cube,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float Cube 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod "
-                              "%v4float "
-                              "%sampled_image %vf123",
-                              "",
-                              {"vf123"}},
-                             {"%float Cube 0 0 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod "
-                              "%v4float "
-                              "%sampled_image %vf1234",  // one excess
-                                                         // arg
-                              "",
-                              {"vf1234.xyz"}}}));
-
-INSTANTIATE_TEST_SUITE_P(Good_CubeArray,
-                         SpvParserHandleTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float Cube 0 1 0 1 Unknown",
-                              "%result = OpImageSampleImplicitLod "
-                              "%v4float "
-                              "%sampled_image %vf1234",
-                              "",
-                              {"vf1234.xyz", "i32(round(vf1234.w))"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    PreserveFloatCoords_NonArrayed,
-    // In SPIR-V, sampling and dref sampling operations use floating point
-    // coordinates.  Prove that we preserve floating point-ness.
-    // Test across all such instructions.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // Scalar cases
-        {"%float 1D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %f1",
-         "",
-         {"f1"}},
-        {"%float 1D 0 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4float %sampled_image %f1 Lod "
-         "%f1",
-         "",
-         {"f1"}},
-        // WGSL does not support depth textures with 1D coordinates
-        // Vector cases
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf12",
-         "",
-         {"vf12"}},
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4float %sampled_image %vf12 Lod "
-         "%f1",
-         "",
-         {"vf12"}},
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf12 "
-         "%depth",
-         "",
-         {"vf12"}},
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf12 "
-         "%depth Lod %float_0",
-         "",
-         {"vf12"}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    PreserveFloatCoords_Arrayed,
-    // In SPIR-V, sampling and dref sampling operations use floating point
-    // coordinates.  Prove that we preserve floating point-ness of the
-    // coordinate part, but convert the array index to signed integer. Test
-    // across all such instructions.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 1 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf123",
-         "",
-         {"vf123.xy", "i32(round(vf123.z))"}},
-
-        {"%float 2D 0 1 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4float %sampled_image %vf123 "
-         "Lod %f1",
-         "",
-         {"vf123.xy", "i32(round(vf123.z))"}},
-        {"%float 2D 1 1 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %float %sampled_image "
-         "%vf123 %depth",
-         "",
-         {"vf123.xy", "i32(round(vf123.z))"}},
-        {"%float 2D 1 1 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image "
-         "%vf123 %depth Lod %float_0",
-         "",
-         {"vf123.xy", "i32(round(vf123.z))"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    PreserveIntCoords_NonArrayed,
-    // In SPIR-V, image read, fetch, and write use integer coordinates.
-    // Prove that we preserve signed integer coordinates.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // Scalar cases
-        {"%float 1D 0 0 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %i1",
-         "",
-         {"i1"}},
-        {"%float 1D 0 0 0 2 R32f",
-         "%result = OpImageRead %v4float %im %i1",
-         "",
-         {"i1"}},
-        {"%float 1D 0 0 0 2 R32f", "OpImageWrite %im %i1 %vf1234", "", {"i1"}},
-        // Vector cases
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %vi12",
-         "",
-         {"vi12"}},
-        {"%float 2D 0 0 0 2 R32f",
-         "%result = OpImageRead %v4float %im %vi12",
-         "",
-         {"vi12"}},
-        {"%float 2D 0 0 0 2 R32f",
-         "OpImageWrite %im %vi12 %vf1234",
-         "",
-         {"vi12"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    PreserveIntCoords_Arrayed,
-    // In SPIR-V, image read, fetch, and write use integer coordinates.
-    // Prove that we preserve signed integer coordinates.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 1 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %vi123",
-         "",
-         {"vi123.xy", "vi123.z"}},
-        {"%float 2D 0 1 0 2 R32f",
-         "%result = OpImageRead %v4float %im %vi123",
-         "",
-         {"vi123.xy", "vi123.z"}},
-        {"%float 2D 0 1 0 2 R32f",
-         "OpImageWrite %im %vi123 %vf1234",
-         "",
-         {"vi123.xy", "vi123.z"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ConvertUintCoords_NonArrayed,
-    // In SPIR-V, image read, fetch, and write use integer coordinates.
-    // Prove that we convert unsigned integer coordinates to signed.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // Scalar cases
-        {"%float 1D 0 0 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %u1",
-         "",
-         {"i32(u1)"}},
-        {"%float 1D 0 0 0 2 R32f",
-         "%result = OpImageRead %v4float %im %u1",
-         "",
-         {"i32(u1)"}},
-        {"%float 1D 0 0 0 2 R32f",
-         "OpImageWrite %im %u1 %vf1234",
-         "",
-         {"i32(u1)"}},
-        // Vector cases
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %vu12",
-         "",
-         {"vec2<i32>(vu12)"}},
-        {"%float 2D 0 0 0 2 R32f",
-         "%result = OpImageRead %v4float %im %vu12",
-         "",
-         {"vec2<i32>(vu12)"}},
-        {"%float 2D 0 0 0 2 R32f",
-         "OpImageWrite %im %vu12 %vf1234",
-         "",
-         {"vec2<i32>(vu12)"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ConvertUintCoords_Arrayed,
-    // In SPIR-V, image read, fetch, and write use integer coordinates.
-    // Prove that we convert unsigned integer coordinates to signed.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 1 0 1 Unknown",
-         "%result = OpImageFetch %v4float %im %vu123",
-         "",
-         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
-        {"%float 2D 0 1 0 2 R32f",
-         "%result = OpImageRead %v4float %im %vu123",
-         "",
-         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
-        {"%float 2D 0 1 0 2 R32f",
-         "OpImageWrite %im %vu123 %vf1234",
-         "",
-         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    BadInstructions,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 1D 0 0 0 1 Unknown",
-         "OpNop",
-         "not an image access instruction: OpNop",
-         {}},
-        {"%float 1D 0 0 0 1 Unknown",
-         "%50 = OpCopyObject %float %float_1",
-         "internal error: couldn't find image for "
-         "%50 = OpCopyObject %18 %45",
-         {}},
-        {"%float 1D 0 0 0 1 Unknown",
-         "OpStore %float_var %float_1",
-         "invalid type for image or sampler "
-         "variable or function parameter: %1 = OpVariable %2 Function",
-         {}},
-        // An example with a missing coordinate
-        // won't assemble, so we skip it.
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    Bad_Coordinate,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 1D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod "
-         // bad type for coordinate: not a number
-         "%v4float %sampled_image %float_var",
-         "bad or unsupported coordinate type for image access: %73 = "
-         "OpImageSampleImplicitLod %42 %72 %1",
-         {}},
-        {"%float 2D 0 0 0 1 Unknown",  // 2D
-         "%result = OpImageSampleImplicitLod "
-         // 1 component, but need 2
-         "%v4float %sampled_image %f1",
-         "image access required 2 coordinate components, but only 1 provided, "
-         "in: %73 = OpImageSampleImplicitLod %42 %72 %12",
-         {}},
-        {"%float 2D 0 1 0 1 Unknown",  // 2DArray
-         "%result = OpImageSampleImplicitLod "
-         // 2 component, but need 3
-         "%v4float %sampled_image %vf12",
-         "image access required 3 coordinate components, but only 2 provided, "
-         "in: %73 = OpImageSampleImplicitLod %42 %72 %13",
-         {}},
-        {"%float 3D 0 0 0 1 Unknown",  // 3D
-         "%result = OpImageSampleImplicitLod "
-         // 2 components, but need 3
-         "%v4float %sampled_image %vf12",
-         "image access required 3 coordinate components, but only 2 provided, "
-         "in: %73 = OpImageSampleImplicitLod %42 %72 %13",
-         {}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    SampleNonFloatTexture_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // ImageSampleImplicitLod
-        {"%uint 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4uint %sampled_image %vf12",
-         "sampled image must have float component type",
-         {}},
-        {"%int 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4int %sampled_image %vf12",
-         "sampled image must have float component type",
-         {}},
-        // ImageSampleExplicitLod
-        {"%uint 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4uint %sampled_image %vf12 "
-         "Lod %f1",
-         "sampled image must have float component type",
-         {}},
-        {"%int 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleExplicitLod %v4int %sampled_image %vf12 "
-         "Lod %f1",
-         "sampled image must have float component type",
-         {}},
-        // ImageSampleDrefImplicitLod
-        {"%uint 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %uint %sampled_image %vf12 "
-         "%f1",
-         "sampled image must have float component type",
-         {}},
-        {"%int 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %int %sampled_image %vf12 "
-         "%f1",
-         "sampled image must have float component type",
-         {}},
-        // ImageSampleDrefExplicitLod
-        {"%uint 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %uint %sampled_image %vf12 "
-         "%f1 Lod %float_0",
-         "sampled image must have float component type",
-         {}},
-        {"%int 2D 0 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %int %sampled_image %vf12 "
-         "%f1 Lod %float_0",
-         "sampled image must have float component type",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ConstOffset_BadInstruction_Errors,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // ImageFetch
-        {"%uint 2D 0 0 0 1 Unknown",
-         "%result = OpImageFetch %v4uint %sampled_image %vf12 ConstOffset "
-         "%the_vu12",
-         "ConstOffset is only permitted for sampling, gather, or "
-         "depth-reference gather operations: ",
-         {}},
-        // ImageRead
-        {"%uint 2D 0 0 0 2 Rgba32ui",
-         "%result = OpImageRead %v4uint %im %vu12 ConstOffset %the_vu12",
-         "ConstOffset is only permitted for sampling, gather, or "
-         "depth-reference gather operations: ",
-         {}},
-        // ImageWrite
-        {"%uint 2D 0 0 0 2 Rgba32ui",
-         "OpImageWrite %im %vu12 %vu1234 ConstOffset %the_vu12",
-         "ConstOffset is only permitted for sampling, gather, or "
-         "depth-reference gather operations: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ConstOffset_BadDim_Errors,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // 1D
-        {"%uint 1D 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
-         "ConstOffset %the_vu12",
-         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
-         {}},
-        // Cube
-        {"%uint Cube 0 0 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
-         "ConstOffset %the_vu12",
-         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
-         {}},
-        // Cube Array
-        {"%uint Cube 0 1 0 1 Unknown",
-         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
-         "ConstOffset %the_vu12",
-         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleDref_Bias_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // Implicit Lod
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf1234 "
-         "%depth Bias %float_null",
-         "WGSL does not support depth-reference sampling with level-of-detail "
-         "bias: ",
-         {}},
-        // Explicit Lod
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
-         "%depth Lod|Bias %float_null %float_null",
-         "WGSL does not support depth-reference sampling with level-of-detail "
-         "bias: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleDref_Grad_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // Implicit Lod
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf1234 "
-         "%depth Grad %float_1 %float_2",
-         "WGSL does not support depth-reference sampling with explicit "
-         "gradient: ",
-         {}},
-        // Explicit Lod
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
-         "%depth Lod|Grad %float_null %float_1  %float_2",
-         "WGSL does not support depth-reference sampling with explicit "
-         "gradient: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleDrefExplicitLod_CheckForLod0,
-    // Metal requires comparison sampling with explicit Level-of-detail to use
-    // Lod 0.  The SPIR-V reader requires the operand to be parsed as a constant
-    // 0 value. SPIR-V validation requires the Lod parameter to be a floating
-    // point value for non-fetch operations. So only test float values.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // float 0.0 works
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
-         "%depth Lod %float_0",
-         "",
-         {"vf1234.xy"}},
-        // float null works
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
-         "%depth Lod %float_0",
-         "",
-         {"vf1234.xy"}},
-        // float 1.0 fails.
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
-         "%depth Lod %float_1",
-         "WGSL comparison sampling without derivatives requires "
-         "level-of-detail "
-         "0.0",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageSampleProjDrefExplicitLod_CheckForLod0,
-    // This is like the previous test, but for Projection sampling.
-    //
-    // Metal requires comparison sampling with explicit Level-of-detail to use
-    // Lod 0.  The SPIR-V reader requires the operand to be parsed as a constant
-    // 0 value. SPIR-V validation requires the Lod parameter to be a floating
-    // point value for non-fetch operations. So only test float values.
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        // float 0.0 works
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
-         "%vf1234 %depth Lod %float_0",
-         "",
-         {"(vf1234.xy / vf1234.z)"}},
-        // float null works
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
-         "%vf1234 %depth Lod %float_0",
-         "",
-         {"(vf1234.xy / vf1234.z)"}},
-        // float 1.0 fails.
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
-         "%vf1234 %depth Lod %float_1",
-         "WGSL comparison sampling without derivatives requires "
-         "level-of-detail "
-         "0.0",
-         {}}}));
-
-TEST_F(SpvParserHandleTest, CombinedImageSampler_IsError) {
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-
-     OpDecorate %var DescriptorSet 0
-     OpDecorate %var Binding 0
-  %float = OpTypeFloat 32
-     %im = OpTypeImage %float 2D 0 0 0 1 Unknown
-     %si = OpTypeSampledImage %im
- %ptr_si = OpTypePointer UniformConstant %si
-    %var = OpVariable %ptr_si UniformConstant
-   %void = OpTypeVoid
- %voidfn = OpTypeFunction %void
-
-    %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-           OpReturn
-           OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_THAT(p->error(),
-              HasSubstr("WGSL does not support combined image-samplers: "));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageQueryLod_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageQueryLod %v2int %sampled_image %vf12",
-         "WGSL does not support querying the level of detail of an image: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageGather_Bias_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageGather %v4float %sampled_image %vf12 %int_1 "
-         "Bias %float_null",
-         "WGSL does not support image gather with level-of-detail bias: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageDrefGather_Bias_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageDrefGather %v4float %sampled_image %vf12 %depth "
-         "Bias %float_null",
-         "WGSL does not support image gather with level-of-detail bias: ",
-         {}}}));
-
-// Note: Vulkan SPIR-V ImageGather and ImageDrefGather do not allow explicit
-// Lod. The SPIR-V validator should reject those cases already.
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageGather_Grad_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 0 0 0 1 Unknown",
-         "%result = OpImageGather %v4float %sampled_image %vf12 %int_1 "
-         "Grad %vf12 %vf12",
-         "WGSL does not support image gather with explicit gradient: ",
-         {}}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    ImageDrefGather_Grad_IsError,
-    SpvParserHandleTest_ImageCoordsTest,
-    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-        {"%float 2D 1 0 0 1 Unknown",
-         "%result = OpImageDrefGather %v4float %sampled_image %vf12 %depth "
-         "Grad %vf12 %vf12",
-         "WGSL does not support image gather with explicit gradient: ",
-         {}}}));
-
-TEST_F(SpvParserHandleTest,
-       NeverGenerateConstDeclForHandle_UseVariableDirectly) {
-  // An ad-hoc test to prove we never had the issue
-  // feared in crbug.com/tint/265.
-  // Never create a const-declaration for a pointer to
-  // a texture or sampler. Code generation always
-  // traces back to the memory object declaration.
-  const auto assembly = Preamble() + R"(
-     OpEntryPoint Fragment %100 "main"
-     OpExecutionMode %100 OriginUpperLeft
-
-     OpName %var "var"
-     OpDecorate %var_im DescriptorSet 0
-     OpDecorate %var_im Binding 0
-     OpDecorate %var_s DescriptorSet 0
-     OpDecorate %var_s Binding 1
-  %float = OpTypeFloat 32
-  %v4float = OpTypeVector %float 4
-  %v2float = OpTypeVector %float 2
-  %v2_0 = OpConstantNull %v2float
-     %im = OpTypeImage %float 2D 0 0 0 1 Unknown
-     %si = OpTypeSampledImage %im
-      %s = OpTypeSampler
- %ptr_im = OpTypePointer UniformConstant %im
-  %ptr_s = OpTypePointer UniformConstant %s
- %var_im = OpVariable %ptr_im UniformConstant
-  %var_s = OpVariable %ptr_s UniformConstant
-   %void = OpTypeVoid
- %voidfn = OpTypeFunction %void
- %ptr_v4 = OpTypePointer Function %v4float
-
-    %100 = OpFunction %void None %voidfn
-  %entry = OpLabel
-    %var = OpVariable %ptr_v4 Function
-
-; Try to induce generating a const-declaration of a pointer to
-; a sampler or texture.
-
- %var_im_copy = OpCopyObject %ptr_im %var_im
-  %var_s_copy = OpCopyObject %ptr_s %var_s
-
-         %im0 = OpLoad %im %var_im_copy
-          %s0 = OpLoad %s %var_s_copy
-         %si0 = OpSampledImage %si %im0 %s0
-          %t0 = OpImageSampleImplicitLod %v4float %si0 %v2_0
-
-
-         %im1 = OpLoad %im %var_im_copy
-          %s1 = OpLoad %s %var_s_copy
-         %si1 = OpSampledImage %si %im1 %s1
-          %t1 = OpImageSampleImplicitLod %v4float %si1 %v2_0
-
-         %sum = OpFAdd %v4float %t0 %t1
-           OpStore %var %sum
-
-           OpReturn
-           OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << assembly;
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_TRUE(p->error().empty()) << p->error();
-  auto ast_body = fe.ast_body();
-  const auto got = test::ToString(p->program(), ast_body);
-  auto* expect = R"(var var_1 : vec4<f32>;
-let x_22 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
-let x_26 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
-var_1 = (x_22 + x_26);
-return;
-)";
-  ASSERT_EQ(expect, got);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_import_test.cc b/src/reader/spirv/parser_impl_import_test.cc
deleted file mode 100644
index 9995b0e..0000000
--- a/src/reader/spirv/parser_impl_import_test.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::ElementsAre;
-using ::testing::Eq;
-using ::testing::HasSubstr;
-using ::testing::Not;
-using ::testing::UnorderedElementsAre;
-
-using SpvParserImportTest = SpvParserTest;
-
-TEST_F(SpvParserImportTest, Import_NoImport) {
-  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto program_ast = test::ToString(p->program());
-  EXPECT_THAT(program_ast, Not(HasSubstr("Import")));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserImportTest, Import_ImportGlslStd450) {
-  auto p = parser(test::Assemble(R"(%1 = OpExtInstImport "GLSL.std.450")"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  EXPECT_THAT(p->glsl_std_450_imports(), ElementsAre(1));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserImportTest, Import_NonSemantic_IgnoredImport) {
-  auto p = parser(test::Assemble(
-      R"(%40 = OpExtInstImport "NonSemantic.ClspvReflection.1")"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserImportTest, Import_NonSemantic_IgnoredExtInsts) {
-  // This is the clspv-compiled output of this OpenCL C:
-  //    kernel void foo(global int*A) { A=A; }
-  // It emits NonSemantic.ClspvReflection.1 extended instructions.
-  // But *tweaked*:
-  //    - to remove gl_WorkgroupSize
-  //    - to add LocalSize execution mode
-  //    - to move one of the ExtInsts into the globals-and-constants
-  //      section
-  //    - to move one of the ExtInsts into the function body.
-  auto p = parser(test::Assemble(R"(
-               OpCapability Shader
-               OpExtension "SPV_KHR_storage_buffer_storage_class"
-               OpExtension "SPV_KHR_non_semantic_info"
-         %20 = OpExtInstImport "NonSemantic.ClspvReflection.1"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint GLCompute %15 "foo"
-               OpExecutionMode %15 LocalSize 1 1 1
-               OpSource OpenCL_C 120
-         %21 = OpString "foo"
-         %23 = OpString "A"
-               OpDecorate %_runtimearr_uint ArrayStride 4
-               OpMemberDecorate %_struct_3 0 Offset 0
-               OpDecorate %_struct_3 Block
-               OpDecorate %12 DescriptorSet 0
-               OpDecorate %12 Binding 0
-               OpDecorate %7 SpecId 0
-               OpDecorate %8 SpecId 1
-               OpDecorate %9 SpecId 2
-       %void = OpTypeVoid
-         %24 = OpExtInst %void %20 ArgumentInfo %23
-       %uint = OpTypeInt 32 0
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-  %_struct_3 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
-     %v3uint = OpTypeVector %uint 3
-%_ptr_Private_v3uint = OpTypePointer Private %v3uint
-          %7 = OpSpecConstant %uint 1
-          %8 = OpSpecConstant %uint 1
-          %9 = OpSpecConstant %uint 1
-         %14 = OpTypeFunction %void
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-     %uint_0 = OpConstant %uint 0
-     %uint_1 = OpConstant %uint 1
-     %uint_2 = OpConstant %uint 2
-         %12 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
-         %15 = OpFunction %void Const %14
-         %16 = OpLabel
-         %19 = OpAccessChain %_ptr_StorageBuffer_uint %12 %uint_0 %uint_0
-         %22 = OpExtInst %void %20 Kernel %15 %21
-               OpReturn
-               OpFunctionEnd
-         %25 = OpExtInst %void %20 ArgumentStorageBuffer %22 %uint_0 %uint_0 %uint_0 %24
-         %28 = OpExtInst %void %20 SpecConstantWorkgroupSize %uint_0 %uint_1 %uint_2
-)"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-
-  p->SkipDumpingPending(
-      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
-}
-
-// TODO(dneto): We don't currently support other kinds of extended instruction
-// imports.
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
deleted file mode 100644
index ccd11a7..0000000
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ /dev/null
@@ -1,5378 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-#include "src/utils/string.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using SpvModuleScopeVarParserTest = SpvParserTest;
-
-using ::testing::ElementsAre;
-using ::testing::Eq;
-using ::testing::HasSubstr;
-using ::testing::Not;
-
-std::string Preamble() {
-  return R"(
-   OpCapability Shader
-   OpMemoryModel Logical Simple
-)";
-}
-
-std::string FragMain() {
-  return R"(
-   OpEntryPoint Fragment %main "main"
-   OpExecutionMode %main OriginUpperLeft
-)";
-}
-
-std::string MainBody() {
-  return R"(
-   %main = OpFunction %void None %voidfn
-   %main_entry = OpLabel
-   OpReturn
-   OpFunctionEnd
-)";
-}
-
-std::string CommonCapabilities() {
-  return R"(
-    OpCapability Shader
-    OpCapability SampleRateShading
-    OpMemoryModel Logical Simple
-)";
-}
-
-std::string CommonTypes() {
-  return R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-
-    %bool = OpTypeBool
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-
-    %ptr_bool = OpTypePointer Private %bool
-    %ptr_float = OpTypePointer Private %float
-    %ptr_uint = OpTypePointer Private %uint
-    %ptr_int = OpTypePointer Private %int
-
-    %true = OpConstantTrue %bool
-    %false = OpConstantFalse %bool
-    %float_0 = OpConstant %float 0.0
-    %float_1p5 = OpConstant %float 1.5
-    %uint_1 = OpConstant %uint 1
-    %int_m1 = OpConstant %int -1
-    %int_14 = OpConstant %int 14
-    %uint_2 = OpConstant %uint 2
-
-    %v2bool = OpTypeVector %bool 2
-    %v2uint = OpTypeVector %uint 2
-    %v2int = OpTypeVector %int 2
-    %v2float = OpTypeVector %float 2
-    %v4float = OpTypeVector %float 4
-    %m3v2float = OpTypeMatrix %v2float 3
-
-    %arr2uint = OpTypeArray %uint %uint_2
-  )";
-}
-
-std::string StructTypes() {
-  return R"(
-    %strct = OpTypeStruct %uint %float %arr2uint
-)";
-}
-
-// Returns layout annotations for types in StructTypes()
-std::string CommonLayout() {
-  return R"(
-    OpMemberDecorate %strct 0 Offset 0
-    OpMemberDecorate %strct 1 Offset 4
-    OpMemberDecorate %strct 2 Offset 8
-    OpDecorate %arr2uint ArrayStride 4
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, NoVar) {
-  auto assembly = Preamble() + FragMain() + CommonTypes() + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_ast = test::ToString(p->program());
-  EXPECT_THAT(module_ast, Not(HasSubstr("Variable"))) << module_ast;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BadStorageClass_NotAWebGPUStorageClass) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer CrossWorkgroup %float
-    %52 = OpVariable %ptr CrossWorkgroup
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  // Normally we should run ParserImpl::RegisterTypes before emitting
-  // variables. But defensive coding in EmitModuleScopeVariables lets
-  // us catch this error.
-  EXPECT_FALSE(p->EmitModuleScopeVariables()) << p->error();
-  EXPECT_THAT(p->error(), HasSubstr("unknown SPIR-V storage class: 5"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BadStorageClass_Function) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Function %float
-    %52 = OpVariable %ptr Function
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  // Normally we should run ParserImpl::RegisterTypes before emitting
-  // variables. But defensive coding in EmitModuleScopeVariables lets
-  // us catch this error.
-  EXPECT_FALSE(p->EmitModuleScopeVariables()) << p->error();
-  EXPECT_THAT(p->error(),
-              HasSubstr("invalid SPIR-V storage class 7 for module scope "
-                        "variable: %52 = OpVariable %3 Function"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BadPointerType) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    %float = OpTypeFloat 32
-    %fn_ty = OpTypeFunction %float
-    %3 = OpTypePointer Private %fn_ty
-    %52 = OpVariable %3 Private
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  // Normally we should run ParserImpl::RegisterTypes before emitting
-  // variables. But defensive coding in EmitModuleScopeVariables lets
-  // us catch this error.
-  EXPECT_FALSE(p->EmitModuleScopeVariables());
-  EXPECT_THAT(p->error(), HasSubstr("internal error: failed to register Tint "
-                                    "AST type for SPIR-V type with ID: 3"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, NonPointerType) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    %float = OpTypeFloat 32
-    %5 = OpTypeFunction %float
-    %3 = OpTypePointer Private %5
-    %52 = OpVariable %float Private
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_TRUE(p->BuildInternalModule());
-  EXPECT_FALSE(p->RegisterTypes());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("SPIR-V pointer type with ID 3 has invalid pointee type 5"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, AnonWorkgroupVar) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Workgroup %float
-    %52 = OpVariable %ptr Workgroup
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("var<workgroup> x_52 : f32;"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, NamedWorkgroupVar) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    OpName %52 "the_counter"
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Workgroup %float
-    %52 = OpVariable %ptr Workgroup
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("var<workgroup> the_counter : f32;"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, PrivateVar) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-    OpName %52 "my_own_private_idaho"
-    %float = OpTypeFloat 32
-    %ptr = OpTypePointer Private %float
-    %52 = OpVariable %ptr Private
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> my_own_private_idaho : f32;"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinVertexIndex) {
-  // This is the simple case for the vertex_index builtin,
-  // where the SPIR-V uses the same store type as in WGSL.
-  // See later for tests where the SPIR-V store type is signed
-  // integer, as in GLSL.
-  auto p = parser(test::Assemble(Preamble() + R"(
-    OpEntryPoint Vertex %main "main" %52 %position
-    OpName %position "position"
-    OpDecorate %position BuiltIn Position
-    OpDecorate %52 BuiltIn VertexIndex
-    %uint = OpTypeInt 32 0
-    %ptr = OpTypePointer Input %uint
-    %52 = OpVariable %ptr Input
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %posty = OpTypePointer Output %v4float
-    %position = OpVariable %posty Output
-  )" + MainBody()));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("var<private> x_52 : u32;"));
-}
-
-std::string PerVertexPreamble() {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1
-
-    OpDecorate %10 Block
-    OpMemberDecorate %10 0 BuiltIn Position
-    OpMemberDecorate %10 1 BuiltIn PointSize
-    OpMemberDecorate %10 2 BuiltIn ClipDistance
-    OpMemberDecorate %10 3 BuiltIn CullDistance
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %12 = OpTypeVector %float 4
-    %uint = OpTypeInt 32 0
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %arr = OpTypeArray %float %uint_1
-    %10 = OpTypeStruct %12 %float %arr %arr
-    %11 = OpTypePointer Output %10
-    %1 = OpVariable %11 Output
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_StoreWholeStruct_NotSupported) {
-  // Glslang does not generate this code pattern.
-  const std::string assembly = PerVertexPreamble() + R"(
-  %nil = OpConstantNull %10 ; the whole struct
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpStore %1 %nil  ; store the whole struct
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_THAT(p->error(), Eq("storing to the whole per-vertex structure is not "
-                             "supported: OpStore %1 %13"))
-      << p->error();
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_IntermediateWholeStruct_NotSupported) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %1000 = OpUndef %10
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
-  EXPECT_THAT(p->error(), Eq("operations producing a per-vertex structure are "
-                             "not supported: %1000 = OpUndef %10"))
-      << p->error();
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_IntermediatePtrWholeStruct_NotSupported) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %1000 = OpCopyObject %11 %1
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              Eq("operations producing a pointer to a per-vertex structure are "
-                 "not supported: %1000 = OpCopyObject %11 %1"))
-      << p->error();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_StorePosition) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_v4float = OpTypePointer Output %12
-  %nil = OpConstantNull %12
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr_v4float %1 %uint_0 ; address of the Position member
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl) {
-  const std::string assembly = R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint Vertex %main "main" %1
-
- ;  scramble the member indices
-  OpDecorate %10 Block
-  OpMemberDecorate %10 0 BuiltIn ClipDistance
-  OpMemberDecorate %10 1 BuiltIn CullDistance
-  OpMemberDecorate %10 2 BuiltIn Position
-  OpMemberDecorate %10 3 BuiltIn PointSize
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %float = OpTypeFloat 32
-  %12 = OpTypeVector %float 4
-  %uint = OpTypeInt 32 0
-  %uint_0 = OpConstant %uint 0
-  %uint_1 = OpConstant %uint 1
-  %uint_2 = OpConstant %uint 2
-  %arr = OpTypeArray %float %uint_1
-  %10 = OpTypeStruct %arr %arr %12 %float
-  %11 = OpTypePointer Output %10
-  %1 = OpVariable %11 Output
-
-  %ptr_v4float = OpTypePointer Output %12
-  %nil = OpConstantNull %12
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr_v4float %1 %uint_2 ; address of the Position member
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_StorePositionMember_OneAccessChain) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr_float %1 %uint_0 %uint_1 ; address of the Position.y member
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_StorePositionMember_TwoAccessChain) {
-  // The algorithm is smart enough to collapse it down.
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %12
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr %1 %uint_0 ; address of the Position member
-  %101 = OpAccessChain %ptr_float %100 %uint_1 ; address of the Position.y member
-  OpStore %101 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Write1_IsErased) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %one = OpConstant %float 1.0
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
-  OpStore %100 %one
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  gl_Position : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(gl_Position);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_WriteNon1_IsError) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %999 = OpConstant %float 2.0
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
-  OpStore %100 %999
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              HasSubstr("cannot store a value other than constant 1.0 to "
-                        "PointSize builtin: OpStore %100 %999"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_ReadReplaced) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %nil = OpConstantNull %12
-  %private_ptr = OpTypePointer Private %float
-  %900 = OpVariable %private_ptr Private
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
-  %99 = OpLoad %float %100
-  OpStore %900 %99
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> x_900 : f32;
-
-var<private> gl_Position : vec4<f32>;
-
-fn main_1() {
-  x_900 = 1.0;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  gl_Position : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(gl_Position);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_WriteViaCopyObjectPriorAccess_Unsupported) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %nil = OpConstantNull %12
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %20 = OpCopyObject %11 %1
-  %100 = OpAccessChain %20 %1 %uint_1 ; address of the PointSize member
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("operations producing a pointer to a per-vertex structure are "
-                "not supported: %20 = OpCopyObject %11 %1"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %one = OpConstant %float 1.0
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
-  %101 = OpCopyObject %ptr %100
-  OpStore %101 %one
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  gl_Position : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(gl_Position);
-}
-)") << module_str;
-}
-
-std::string LoosePointSizePreamble(std::string stage = "Vertex") {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint )" +
-         stage + R"( %500 "main" %1
-)" + (stage == "Vertex" ? " %2 " : "") +
-         +(stage == "Fragment" ? "OpExecutionMode %500 OriginUpperLeft" : "") +
-         +(stage == "Vertex" ? " OpDecorate %2 BuiltIn Position " : "") +
-         R"(
-    OpDecorate %1 BuiltIn PointSize
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %uint = OpTypeInt 32 0
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %11 = OpTypePointer Output %float
-    %1 = OpVariable %11 Output
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_Write1_IsErased) {
-  const std::string assembly = LoosePointSizePreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %one = OpConstant %float 1.0
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpStore %1 %one
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_2);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_WriteNon1_IsError) {
-  const std::string assembly = LoosePointSizePreamble() + R"(
-  %ptr = OpTypePointer Output %float
-  %999 = OpConstant %float 2.0
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpStore %1 %999
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              HasSubstr("cannot store a value other than constant 1.0 to "
-                        "PointSize builtin: OpStore %1 %999"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_Loose_ReadReplaced_Vertex) {
-  const std::string assembly = LoosePointSizePreamble() + R"(
-  %ptr = OpTypePointer Private %float
-  %900 = OpVariable %ptr Private
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %99 = OpLoad %float %1
-  OpStore %900 %99
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
-
-var<private> x_900 : f32;
-
-fn main_1() {
-  x_900 = 1.0;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_2);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_Loose_ReadReplaced_Fragment) {
-  const std::string assembly = LoosePointSizePreamble("Fragment") + R"(
-  %ptr = OpTypePointer Private %float
-  %900 = OpVariable %ptr Private
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %99 = OpLoad %float %1
-  OpStore %900 %99
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  // This example is invalid because you PointSize is not valid in Vulkan
-  // Fragment shaders.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(), HasSubstr("VUID-PointSize-PointSize-04314"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased) {
-  const std::string assembly = LoosePointSizePreamble() + R"(
-  %one = OpConstant %float 1.0
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %20 = OpCopyObject %11 %1
-  %100 = OpAccessChain %11 %20
-  OpStore %100 %one
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_2);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased) {
-  const std::string assembly = LoosePointSizePreamble() + R"(
-  %one = OpConstant %float 1.0
-
-  %500 = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %100 = OpAccessChain %11 %1
-  %101 = OpCopyObject %11 %100
-  OpStore %101 %one
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto module_str = test::ToString(p->program());
-  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_2);
-}
-)") << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinClipDistance_NotSupported) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-  %uint_2 = OpConstant %uint 2
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-; address of the first entry in ClipDistance
-  %100 = OpAccessChain %ptr_float %1 %uint_2 %uint_0
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_EQ(p->error(),
-            "accessing per-vertex member 2 is not supported. Only Position is "
-            "supported, and PointSize is ignored");
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinCullDistance_NotSupported) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-  %uint_3 = OpConstant %uint 3
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-; address of the first entry in CullDistance
-  %100 = OpAccessChain %ptr_float %1 %uint_3 %uint_0
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_EQ(p->error(),
-            "accessing per-vertex member 3 is not supported. Only Position is "
-            "supported, and PointSize is ignored");
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPerVertex_MemberIndex_NotConstant) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  %sum = OpIAdd %uint %uint_0 %uint_0
-  %100 = OpAccessChain %ptr_float %1 %sum
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              Eq("first index of access chain into per-vertex structure is not "
-                 "a constant: %100 = OpAccessChain %13 %1 %16"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPerVertex_MemberIndex_NotConstantInteger) {
-  const std::string assembly = PerVertexPreamble() + R"(
-  %ptr_float = OpTypePointer Output %float
-  %nil = OpConstantNull %float
-
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-; nil is bad here!
-  %100 = OpAccessChain %ptr_float %1 %nil
-  OpStore %100 %nil
-  OpReturn
-  OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              Eq("first index of access chain into per-vertex structure is not "
-                 "a constant integer: %100 = OpAccessChain %13 %1 %14"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarInitializers) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %1 = OpVariable %ptr_bool Private %true
-     %2 = OpVariable %ptr_bool Private %false
-     %3 = OpVariable %ptr_int Private %int_m1
-     %4 = OpVariable %ptr_uint Private %uint_1
-     %5 = OpVariable %ptr_float Private %float_1p5
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = true;
-
-var<private> x_2 : bool = false;
-
-var<private> x_3 : i32 = -1;
-
-var<private> x_4 : u32 = 1u;
-
-var<private> x_5 : f32 = 1.5;
-)"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarNullInitializers) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %null_bool = OpConstantNull %bool
-     %null_int = OpConstantNull %int
-     %null_uint = OpConstantNull %uint
-     %null_float = OpConstantNull %float
-
-     %1 = OpVariable %ptr_bool Private %null_bool
-     %2 = OpVariable %ptr_int Private %null_int
-     %3 = OpVariable %ptr_uint Private %null_uint
-     %4 = OpVariable %ptr_float Private %null_float
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = false;
-
-var<private> x_2 : i32 = 0;
-
-var<private> x_3 : u32 = 0u;
-
-var<private> x_4 : f32 = 0.0;
-)"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarUndefInitializers) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %undef_bool = OpUndef %bool
-     %undef_int = OpUndef %int
-     %undef_uint = OpUndef %uint
-     %undef_float = OpUndef %float
-
-     %1 = OpVariable %ptr_bool Private %undef_bool
-     %2 = OpVariable %ptr_int Private %undef_int
-     %3 = OpVariable %ptr_uint Private %undef_uint
-     %4 = OpVariable %ptr_float Private %undef_float
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = false;
-
-var<private> x_2 : i32 = 0;
-
-var<private> x_3 : u32 = 0u;
-
-var<private> x_4 : f32 = 0.0;
-)"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2float
-     %two = OpConstant %float 2.0
-     %const = OpConstantComposite %v2float %float_1p5 %two
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorBoolNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2bool
-     %const = OpConstantNull %v2bool
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<bool> = vec2<bool>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorBoolUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2bool
-     %const = OpUndef %v2bool
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<bool> = vec2<bool>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorUintNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2uint
-     %const = OpConstantNull %v2uint
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorUintUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2uint
-     %const = OpUndef %v2uint
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorIntNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2int
-     %const = OpConstantNull %v2int
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorIntUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2int
-     %const = OpUndef %v2int
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorFloatNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2float
-     %const = OpConstantNull %v2float
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VectorFloatUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %v2float
-     %const = OpUndef %v2float
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, MatrixInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %m3v2float
-     %two = OpConstant %float 2.0
-     %three = OpConstant %float 3.0
-     %four = OpConstant %float 4.0
-     %v0 = OpConstantComposite %v2float %float_1p5 %two
-     %v1 = OpConstantComposite %v2float %two %three
-     %v2 = OpConstantComposite %v2float %three %four
-     %const = OpConstantComposite %m3v2float %v0 %v1 %v2
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>("
-                        "vec2<f32>(1.5, 2.0), "
-                        "vec2<f32>(2.0, 3.0), "
-                        "vec2<f32>(3.0, 4.0));"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, MatrixNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %m3v2float
-     %const = OpConstantNull %m3v2float
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, MatrixUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %m3v2float
-     %const = OpUndef %m3v2float
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str,
-              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ArrayInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %arr2uint
-     %two = OpConstant %uint 2
-     %const = OpConstantComposite %arr2uint %uint_1 %two
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr(
-          "var<private> x_200 : array<u32, 2u> = array<u32, 2u>(1u, 2u);"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ArrayNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %arr2uint
-     %const = OpConstantNull %arr2uint
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : array<u32, 2u> = array<u32, 2u>();"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ArrayUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr = OpTypePointer Private %arr2uint
-     %const = OpUndef %arr2uint
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : array<u32, 2u> = array<u32, 2u>();"));
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, StructInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
-                                 StructTypes() + R"(
-     %ptr = OpTypePointer Private %strct
-     %two = OpConstant %uint 2
-     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
-     %const = OpConstantComposite %strct %uint_1 %float_1p5 %arrconst
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, StructNullInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
-                                 StructTypes() + R"(
-     %ptr = OpTypePointer Private %strct
-     %const = OpConstantNull %strct
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, StructUndefInitializer) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
-                                 StructTypes() + R"(
-     %ptr = OpTypePointer Private %strct
-     %const = OpUndef %strct
-     %200 = OpVariable %ptr Private %const
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
-      << module_str;
-
-  // This example module emits ok, but is not valid SPIR-V in the first place.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       LocationDecoration_MissingOperandWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar Location
-)" + CommonTypes() + R"(
-     %ptr = OpTypePointer Input %uint
-     %myvar = OpVariable %ptr Input
-  )" + MainBody();
-  EXPECT_THAT(test::AssembleFailure(assembly),
-              Eq("10:4: Expected operand, found next instruction instead."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       LocationDecoration_TwoOperandsWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar Location 3 4
-)" + CommonTypes() + R"(
-     %ptr = OpTypePointer Input %uint
-     %myvar = OpVariable %ptr Input
-  )" + MainBody();
-  EXPECT_THAT(
-      test::AssembleFailure(assembly),
-      Eq("8:34: Expected <opcode> or <result-id> at the beginning of an "
-         "instruction, found '4'."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, DescriptorGroupDecoration_Valid) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonLayout() + R"(
-     OpDecorate %1 DescriptorSet 3
-     OpDecorate %1 Binding 9 ; Required to pass WGSL validation
-     OpDecorate %strct Block
-)" + CommonTypes() + StructTypes() +
-                                 R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %1 = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("@group(3) @binding(9) var<storage, read_write> x_1 : S;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       DescriptorGroupDecoration_MissingOperandWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + CommonLayout() + R"(
-     OpDecorate %1 DescriptorSet
-     OpDecorate %strct Block
-)" + CommonTypes() + StructTypes() +
-                        R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %1 = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody();
-  EXPECT_THAT(test::AssembleFailure(assembly),
-              Eq("13:5: Expected operand, found next instruction instead."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       DescriptorGroupDecoration_TwoOperandsWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar DescriptorSet 3 4
-     OpDecorate %strct Block
-)" + CommonTypes() + StructTypes() +
-                        R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %myvar = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody();
-  EXPECT_THAT(
-      test::AssembleFailure(assembly),
-      Eq("8:39: Expected <opcode> or <result-id> at the beginning of an "
-         "instruction, found '4'."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BindingDecoration_Valid) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %1 DescriptorSet 0 ; WGSL validation requires this already
-     OpDecorate %1 Binding 3
-     OpDecorate %strct Block
-)" + CommonLayout() + CommonTypes() +
-                                 StructTypes() +
-                                 R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %1 = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(
-      module_str,
-      HasSubstr("@group(0) @binding(3) var<storage, read_write> x_1 : S;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BindingDecoration_MissingOperandWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar Binding
-     OpDecorate %strct Block
-)" + CommonTypes() + StructTypes() +
-                        R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %myvar = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody();
-  EXPECT_THAT(test::AssembleFailure(assembly),
-              Eq("9:5: Expected operand, found next instruction instead."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BindingDecoration_TwoOperandsWontAssemble) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar Binding 3 4
-     OpDecorate %strct Block
-)" + CommonTypes() + StructTypes() +
-                        R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %myvar = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody();
-  EXPECT_THAT(
-      test::AssembleFailure(assembly),
-      Eq("8:33: Expected <opcode> or <result-id> at the beginning of an "
-         "instruction, found '4'."));
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       StructMember_NonReadableDecoration_Dropped) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %1 DescriptorSet 0
-     OpDecorate %1 Binding 0
-     OpDecorate %strct Block
-     OpMemberDecorate %strct 0 NonReadable
-)" + CommonLayout() + CommonTypes() +
-                                 StructTypes() + R"(
-     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
-     %1 = OpVariable %ptr_sb_strct StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(type Arr = @stride(4) array<u32, 2u>;
-
-struct S {
-  field0 : u32;
-  field1 : f32;
-  field2 : Arr;
-}
-
-@group(0) @binding(0) var<storage, read_write> x_1 : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ColMajorDecoration_Dropped) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar DescriptorSet 0
-     OpDecorate %myvar Binding 0
-     OpDecorate %s Block
-     OpMemberDecorate %s 0 ColMajor
-     OpMemberDecorate %s 0 Offset 0
-     OpMemberDecorate %s 0 MatrixStride 8
-     %float = OpTypeFloat 32
-     %v2float = OpTypeVector %float 2
-     %m3v2float = OpTypeMatrix %v2float 3
-
-     %s = OpTypeStruct %m3v2float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %myvar = OpVariable %ptr_sb_s StorageBuffer
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  field0 : mat3x2<f32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> myvar : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, MatrixStrideDecoration_Natural_Dropped) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar DescriptorSet 0
-     OpDecorate %myvar Binding 0
-     OpDecorate %s Block
-     OpMemberDecorate %s 0 MatrixStride 8
-     OpMemberDecorate %s 0 Offset 0
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v2float = OpTypeVector %float 2
-     %m3v2float = OpTypeMatrix %v2float 3
-
-     %s = OpTypeStruct %m3v2float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %myvar = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  field0 : mat3x2<f32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> myvar : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, MatrixStrideDecoration) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %myvar DescriptorSet 0
-     OpDecorate %myvar Binding 0
-     OpDecorate %s Block
-     OpMemberDecorate %s 0 MatrixStride 64
-     OpMemberDecorate %s 0 Offset 0
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v2float = OpTypeVector %float 2
-     %m3v2float = OpTypeMatrix %v2float 3
-
-     %s = OpTypeStruct %m3v2float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %myvar = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  @stride(64) @internal(disable_validation__ignore_stride)
-  field0 : mat3x2<f32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> myvar : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, RowMajorDecoration_IsError) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %myvar "myvar"
-     OpDecorate %s Block
-     OpMemberDecorate %s 0 RowMajor
-     OpMemberDecorate %s 0 Offset 0
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %v2float = OpTypeVector %float 2
-     %m3v2float = OpTypeMatrix %v2float 3
-
-     %s = OpTypeStruct %m3v2float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %myvar = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_THAT(
-      p->error(),
-      Eq(R"(WGSL does not support row-major matrices: can't translate member 0 of %3 = OpTypeStruct %8)"))
-      << p->error();
-}
-
-TEST_F(SpvModuleScopeVarParserTest, StorageBuffer_NonWritable_AllMembers) {
-  // Variable should have access(read)
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %s Block
-     OpDecorate %1 DescriptorSet 0
-     OpDecorate %1 Binding 0
-     OpMemberDecorate %s 0 NonWritable
-     OpMemberDecorate %s 1 NonWritable
-     OpMemberDecorate %s 0 Offset 0
-     OpMemberDecorate %s 1 Offset 4
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-
-     %s = OpTypeStruct %float %float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %1 = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  field0 : f32;
-  field1 : f32;
-}
-
-@group(0) @binding(0) var<storage, read> x_1 : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, StorageBuffer_NonWritable_NotAllMembers) {
-  // Variable should have access(read_write)
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %1 DescriptorSet 0
-     OpDecorate %1 Binding 0
-     OpDecorate %s Block
-     OpMemberDecorate %s 0 NonWritable
-     OpMemberDecorate %s 0 Offset 0
-     OpMemberDecorate %s 1 Offset 4
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-
-     %s = OpTypeStruct %float %float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %1 = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  field0 : f32;
-  field1 : f32;
-}
-
-@group(0) @binding(0) var<storage, read_write> x_1 : S;
-)")) << module_str;
-}
-
-TEST_F(
-    SpvModuleScopeVarParserTest,
-    StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember) {  // NOLINT
-  // Variable should have access(read_write)
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %s Block
-     OpDecorate %1 DescriptorSet 0
-     OpDecorate %1 Binding 0
-     OpMemberDecorate %s 0 NonWritable
-     OpMemberDecorate %s 0 NonWritable ; same member. Don't double-count it
-     OpMemberDecorate %s 0 Offset 0
-     OpMemberDecorate %s 1 Offset 4
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-
-     %s = OpTypeStruct %float %float
-     %ptr_sb_s = OpTypePointer StorageBuffer %s
-     %1 = OpVariable %ptr_sb_s StorageBuffer
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
-  field0 : f32;
-  field1 : f32;
-}
-
-@group(0) @binding(0) var<storage, read_write> x_1 : S;
-)")) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_Id_TooBig) {
-  // Override IDs must be between 0 and 65535
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %1 SpecId 65536
-     %bool = OpTypeBool
-     %1 = OpSpecConstantTrue %bool
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_FALSE(p->Parse());
-  EXPECT_EQ(p->error(),
-            "SpecId too large. WGSL override IDs must be between 0 and 65535: "
-            "ID %1 has SpecId 65536");
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       ScalarSpecConstant_DeclareConst_Id_MaxValid) {
-  // Override IDs must be between 0 and 65535
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpDecorate %1 SpecId 65535
-     %bool = OpTypeBool
-     %1 = OpSpecConstantTrue %bool
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  EXPECT_TRUE(p->Parse());
-  EXPECT_EQ(p->error(), "");
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_True) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     OpDecorate %c SpecId 12
-     %bool = OpTypeBool
-     %c = OpSpecConstantTrue %bool
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : bool = true;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_False) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     OpDecorate %c SpecId 12
-     %bool = OpTypeBool
-     %c = OpSpecConstantFalse %bool
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : bool = false;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_U32) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     OpDecorate %c SpecId 12
-     %uint = OpTypeInt 32 0
-     %c = OpSpecConstant %uint 42
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : u32 = 42u;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_I32) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     OpDecorate %c SpecId 12
-     %int = OpTypeInt 32 1
-     %c = OpSpecConstant %int 42
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : i32 = 42;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_F32) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     OpDecorate %c SpecId 12
-     %float = OpTypeFloat 32
-     %c = OpSpecConstant %float 2.5
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : f32 = 2.5;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       ScalarSpecConstant_DeclareConst_F32_WithoutSpecId) {
-  // When we don't have a spec ID, declare an undecorated module-scope constant.
-  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     %float = OpTypeFloat 32
-     %c = OpSpecConstant %float 2.5
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-  )" + MainBody()));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  EXPECT_THAT(module_str, HasSubstr("override myconst : f32 = 2.5;"))
-      << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_UsedInFunction) {
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpName %c "myconst"
-     %void = OpTypeVoid
-     %voidfn = OpTypeFunction %void
-     %float = OpTypeFloat 32
-     %c = OpSpecConstant %float 2.5
-     %floatfn = OpTypeFunction %float
-     %100 = OpFunction %float None %floatfn
-     %entry = OpLabel
-     %1 = OpFAdd %float %c %c
-     OpReturnValue %1
-     OpFunctionEnd
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-  auto fe = p->function_emitter(100);
-  EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_TRUE(p->error().empty());
-
-  Program program = p->program();
-  const auto got = test::ToString(program, fe.ast_body());
-
-  EXPECT_THAT(got, HasSubstr("return (myconst + myconst);")) << got;
-}
-
-// Returns the start of a shader for testing SampleId,
-// parameterized by store type of %int or %uint
-std::string SampleIdPreamble(std::string store_type) {
-  return R"(
-    OpCapability Shader
-    OpCapability SampleRateShading
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %main "main" %1
-    OpExecutionMode %main OriginUpperLeft
-    OpDecorate %1 BuiltIn SampleId
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %ptr_ty = OpTypePointer Input )" +
-         store_type + R"(
-    %1 = OpVariable %ptr_ty Input
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_Direct) {
-  const std::string assembly = SampleIdPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %int %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : i32;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_index) x_1_param : u32) {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_CopyObject) {
-  const std::string assembly = SampleIdPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected =
-      R"(Module{
-  Variable{
-    x_1
-    private
-    undefined
-    __i32
-  }
-  Function main_1 -> __void
-  ()
-  {
-    VariableDeclStatement{
-      VariableConst{
-        x_11
-        none
-        undefined
-        __ptr_private__i32
-        {
-          UnaryOp[not set]{
-            address-of
-            Identifier[not set]{x_1}
-          }
-        }
-      }
-    }
-    VariableDeclStatement{
-      VariableConst{
-        x_2
-        none
-        undefined
-        __i32
-        {
-          UnaryOp[not set]{
-            indirection
-            Identifier[not set]{x_14}
-          }
-        }
-      }
-    }
-    Return{}
-  }
-  Function main -> __void
-  StageDecoration{fragment}
-  (
-    VariableConst{
-      Decorations{
-        BuiltinDecoration{sample_index}
-      }
-      x_1_param
-      none
-      undefined
-      __u32
-    }
-  )
-  {
-    Assignment{
-      Identifier[not set]{x_1}
-      Bitcast[not set]<__i32>{
-        Identifier[not set]{x_1_param}
-      }
-    }
-    Call[not set]{
-      Identifier[not set]{main_1}
-      (
-      )
-    }
-  }
-}
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_AccessChain) {
-  const std::string assembly = SampleIdPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_index) x_1_param : u32) {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_FunctParam) {
-  const std::string assembly = SampleIdPreamble("%int") + R"(
-    %helper_ty = OpTypeFunction %int %ptr_ty
-    %helper = OpFunction %int None %helper_ty
-    %param = OpFunctionParameter %ptr_ty
-    %helper_entry = OpLabel
-    %3 = OpLoad %int %param
-    OpReturnValue %3
-    OpFunctionEnd
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %result = OpFunctionCall %int %helper %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-
-  // This example is invalid because you can't pass pointer-to-Input
-  // as a function parameter.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_FALSE(p->success());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Invalid storage class for pointer operand 1"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_Direct) {
-  const std::string assembly = SampleIdPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %uint %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_index) x_1_param : u32) {
-  x_1 = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_CopyObject) {
-  const std::string assembly = SampleIdPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-fn main_1() {
-  let x_11 : ptr<private, u32> = &(x_1);
-  let x_2 : u32 = *(x_11);
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_index) x_1_param : u32) {
-  x_1 = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_AccessChain) {
-  const std::string assembly = SampleIdPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_index) x_1_param : u32) {
-  x_1 = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_FunctParam) {
-  const std::string assembly = SampleIdPreamble("%uint") + R"(
-    %helper_ty = OpTypeFunction %uint %ptr_ty
-    %helper = OpFunction %uint None %helper_ty
-    %param = OpFunctionParameter %ptr_ty
-    %helper_entry = OpLabel
-    %3 = OpLoad %uint %param
-    OpReturnValue %3
-    OpFunctionEnd
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %result = OpFunctionCall %uint %helper %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  // This example is invalid because you can't pass pointer-to-Input
-  // as a function parameter.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Invalid storage class for pointer operand 1"));
-}
-
-// Returns the start of a shader for testing SampleMask
-// parameterized by store type.
-std::string SampleMaskPreamble(std::string store_type, uint32_t stride = 0u) {
-  return std::string(R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %main "main" %1
-    OpExecutionMode %main OriginUpperLeft
-    OpDecorate %1 BuiltIn SampleMask
-)") +
-         (stride > 0u ? R"(
-    OpDecorate %uarr1 ArrayStride 4
-    OpDecorate %uarr2 ArrayStride 4
-    OpDecorate %iarr1 ArrayStride 4
-    OpDecorate %iarr2 ArrayStride 4
-)"
-                      : "") +
-         R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %int_12 = OpConstant %int 12
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %uint_2 = OpConstant %uint 2
-    %uarr1 = OpTypeArray %uint %uint_1
-    %uarr2 = OpTypeArray %uint %uint_2
-    %iarr1 = OpTypeArray %int %uint_1
-    %iarr2 = OpTypeArray %int %uint_2
-    %iptr_in_ty = OpTypePointer Input %int
-    %uptr_in_ty = OpTypePointer Input %uint
-    %iptr_out_ty = OpTypePointer Output %int
-    %uptr_out_ty = OpTypePointer Output %uint
-    %in_ty = OpTypePointer Input )" +
-         store_type + R"(
-    %out_ty = OpTypePointer Output )" +
-         store_type + R"(
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_ArraySize2_Error) {
-  const std::string assembly = SampleMaskPreamble("%uarr2") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
-    %3 = OpLoad %int %2
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              HasSubstr("WGSL supports a sample mask of at most 32 bits. "
-                        "SampleMask must be an array of 1 element"))
-      << p->error() << assembly;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_Direct) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
-    %3 = OpLoad %uint %2
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  let x_3 : u32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_CopyObject) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
-    %3 = OpCopyObject %uptr_in_ty %2
-    %4 = OpLoad %uint %3
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  let x_4 : u32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_AccessChain) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
-    %3 = OpAccessChain %uptr_in_ty %2
-    %4 = OpLoad %uint %3
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  let x_4 : u32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_Direct) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
-    %3 = OpLoad %int %2
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  let x_3 : i32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_CopyObject) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
-    %3 = OpCopyObject %iptr_in_ty %2
-    %4 = OpLoad %int %3
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  let x_4 : i32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_AccessChain) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
-    %3 = OpAccessChain %iptr_in_ty %2
-    %4 = OpLoad %int %3
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  let x_4 : i32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_ArraySize2_Error) {
-  const std::string assembly = SampleMaskPreamble("%uarr2") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
-    OpStore %2 %uint_0
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_FALSE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->error(),
-              HasSubstr("WGSL supports a sample mask of at most 32 bits. "
-                        "SampleMask must be an array of 1 element"))
-      << p->error() << assembly;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_Direct) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
-    OpStore %2 %uint_0
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  x_1[0] = 0u;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0]);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_CopyObject) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
-    %3 = OpCopyObject %uptr_out_ty %2
-    OpStore %2 %uint_0
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  x_1[0] = 0u;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0]);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_AccessChain) {
-  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
-    %3 = OpAccessChain %uptr_out_ty %2
-    OpStore %2 %uint_0
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  x_1[0] = 0u;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0]);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_Direct) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
-    OpStore %2 %int_12
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  x_1[0] = 12;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(bitcast<u32>(x_1[0]));
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_CopyObject) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
-    %3 = OpCopyObject %iptr_out_ty %2
-    OpStore %2 %int_12
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  x_1[0] = 12;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(bitcast<u32>(x_1[0]));
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_AccessChain) {
-  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
-    %3 = OpAccessChain %iptr_out_ty %2
-    OpStore %2 %int_12
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  x_1[0] = 12;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(bitcast<u32>(x_1[0]));
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_WithStride) {
-  const std::string assembly = SampleMaskPreamble("%uarr1", 4u) + R"(
-    %1 = OpVariable %in_ty Input
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
-    %3 = OpLoad %uint %2
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(type Arr = @stride(4) array<u32, 1u>;
-
-type Arr_1 = @stride(4) array<u32, 2u>;
-
-type Arr_2 = @stride(4) array<i32, 1u>;
-
-type Arr_3 = @stride(4) array<i32, 2u>;
-
-var<private> x_1 : Arr;
-
-fn main_1() {
-  let x_3 : u32 = x_1[0];
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_WithStride) {
-  const std::string assembly = SampleMaskPreamble("%uarr1", 4u) + R"(
-    %1 = OpVariable %out_ty Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
-    OpStore %2 %uint_0
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(type Arr = @stride(4) array<u32, 1u>;
-
-type Arr_1 = @stride(4) array<u32, 2u>;
-
-type Arr_2 = @stride(4) array<i32, 1u>;
-
-type Arr_3 = @stride(4) array<i32, 2u>;
-
-var<private> x_1 : Arr;
-
-fn main_1() {
-  x_1[0] = 0u;
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0]);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-// Returns the start of a shader for testing VertexIndex,
-// parameterized by store type of %int or %uint
-std::string VertexIndexPreamble(std::string store_type) {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %position %1
-    OpDecorate %position BuiltIn Position
-    OpDecorate %1 BuiltIn VertexIndex
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %ptr_ty = OpTypePointer Input )" +
-         store_type + R"(
-    %1 = OpVariable %ptr_ty Input
-    %v4float = OpTypeVector %float 4
-    %posty = OpTypePointer Output %v4float
-    %position = OpVariable %posty Output
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_Direct) {
-  const std::string assembly = VertexIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %int %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_CopyObject) {
-  const std::string assembly = VertexIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_14 : ptr<private, i32> = &(x_1);
-  let x_2 : i32 = *(x_14);
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_AccessChain) {
-  const std::string assembly = VertexIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_Direct) {
-  const std::string assembly = VertexIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %uint %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_CopyObject) {
-  const std::string assembly = VertexIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_14 : ptr<private, u32> = &(x_1);
-  let x_2 : u32 = *(x_14);
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_AccessChain) {
-  const std::string assembly = VertexIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_FunctParam) {
-  const std::string assembly = VertexIndexPreamble("%uint") + R"(
-    %helper_ty = OpTypeFunction %uint %ptr_ty
-    %helper = OpFunction %uint None %helper_ty
-    %param = OpFunctionParameter %ptr_ty
-    %helper_entry = OpLabel
-    %3 = OpLoad %uint %param
-    OpReturnValue %3
-    OpFunctionEnd
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %result = OpFunctionCall %uint %helper %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-
-  // This example is invalid because you can't pass pointer-to-Input
-  // as a function parameter.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Invalid storage class for pointer operand 1"));
-}
-
-// Returns the start of a shader for testing InstanceIndex,
-// parameterized by store type of %int or %uint
-std::string InstanceIndexPreamble(std::string store_type) {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %position %1
-    OpName %position "position"
-    OpDecorate %position BuiltIn Position
-    OpDecorate %1 BuiltIn InstanceIndex
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %ptr_ty = OpTypePointer Input )" +
-         store_type + R"(
-    %1 = OpVariable %ptr_ty Input
-    %v4float = OpTypeVector %float 4
-    %posty = OpTypePointer Output %v4float
-    %position = OpVariable %posty Output
-)";
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_Direct) {
-  const std::string assembly = InstanceIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %int %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_CopyObject) {
-  const std::string assembly = InstanceIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_14 : ptr<private, i32> = &(x_1);
-  let x_2 : i32 = *(x_14);
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_AccessChain) {
-  const std::string assembly = InstanceIndexPreamble("%int") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %int %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_FunctParam) {
-  const std::string assembly = InstanceIndexPreamble("%int") + R"(
-    %helper_ty = OpTypeFunction %int %ptr_ty
-    %helper = OpFunction %int None %helper_ty
-    %param = OpFunctionParameter %ptr_ty
-    %helper_entry = OpLabel
-    %3 = OpLoad %int %param
-    OpReturnValue %3
-    OpFunctionEnd
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %result = OpFunctionCall %int %helper %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  // This example is invalid because you can't pass pointer-to-Input
-  // as a function parameter.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Invalid storage class for pointer operand 1"));
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_Direct) {
-  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad %uint %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_CopyObject) {
-  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpCopyObject %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_14 : ptr<private, u32> = &(x_1);
-  let x_2 : u32 = *(x_14);
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_AccessChain) {
-  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %copy_ptr = OpAccessChain %ptr_ty %1
-    %2 = OpLoad %uint %copy_ptr
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> position : vec4<f32>;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  position_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(position);
-}
-)";
-  EXPECT_EQ(module_str, expected);
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_FunctParam) {
-  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
-    %helper_ty = OpTypeFunction %uint %ptr_ty
-    %helper = OpFunction %uint None %helper_ty
-    %param = OpFunctionParameter %ptr_ty
-    %helper_entry = OpLabel
-    %3 = OpLoad %uint %param
-    OpReturnValue %3
-    OpFunctionEnd
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %result = OpFunctionCall %uint %helper %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  // This example is invalid because you can't pass pointer-to-Input
-  // as a function parameter.
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(p->error(),
-              HasSubstr("Invalid storage class for pointer operand 1"));
-}
-
-// Returns the start of a shader for testing LocalInvocationIndex,
-// parameterized by store type of %int or %uint
-std::string ComputeBuiltinInputPreamble(std::string builtin,
-                                        std::string store_type) {
-  return R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint GLCompute %main "main" %1
-    OpExecutionMode %main LocalSize 1 1 1
-    OpDecorate %1 BuiltIn )" +
-         builtin + R"(
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %uint = OpTypeInt 32 0
-    %int = OpTypeInt 32 1
-    %v3uint = OpTypeVector %uint 3
-    %v3int = OpTypeVector %int 3
-    %ptr_ty = OpTypePointer Input )" +
-         store_type + R"(
-    %1 = OpVariable %ptr_ty Input
-)";
-}
-
-struct ComputeBuiltinInputCase {
-  std::string spirv_builtin;
-  std::string spirv_store_type;
-  std::string wgsl_builtin;
-};
-inline std::ostream& operator<<(std::ostream& o, ComputeBuiltinInputCase c) {
-  return o << "ComputeBuiltinInputCase(" << c.spirv_builtin << " "
-           << c.spirv_store_type << " " << c.wgsl_builtin << ")";
-}
-
-std::string WgslType(std::string spirv_type) {
-  if (spirv_type == "%uint") {
-    return "u32";
-  }
-  if (spirv_type == "%int") {
-    return "i32";
-  }
-  if (spirv_type == "%v3uint") {
-    return "vec3<u32>";
-  }
-  if (spirv_type == "%v3int") {
-    return "vec3<i32>";
-  }
-  return "error";
-}
-
-std::string UnsignedWgslType(std::string wgsl_type) {
-  if (wgsl_type == "u32") {
-    return "u32";
-  }
-  if (wgsl_type == "i32") {
-    return "u32";
-  }
-  if (wgsl_type == "vec3<u32>") {
-    return "vec3<u32>";
-  }
-  if (wgsl_type == "vec3<i32>") {
-    return "vec3<u32>";
-  }
-  return "error";
-}
-
-std::string SignedWgslType(std::string wgsl_type) {
-  if (wgsl_type == "u32") {
-    return "i32";
-  }
-  if (wgsl_type == "i32") {
-    return "i32";
-  }
-  if (wgsl_type == "vec3<u32>") {
-    return "vec3<i32>";
-  }
-  if (wgsl_type == "vec3<i32>") {
-    return "vec3<i32>";
-  }
-  return "error";
-}
-
-using SpvModuleScopeVarParserTest_ComputeBuiltin =
-    SpvParserTestBase<::testing::TestWithParam<ComputeBuiltinInputCase>>;
-
-TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_Direct) {
-  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
-  const auto wgsl_builtin = GetParam().wgsl_builtin;
-  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
-  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
-  const std::string assembly =
-      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
-                                  GetParam().spirv_store_type) +
-      R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %2 = OpLoad )" +
-      GetParam().spirv_store_type + R"( %1
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  std::string expected = R"(var<private> x_1 : ${wgsl_type};
-
-fn main_1() {
-  let x_2 : ${wgsl_type} = x_1;
-  return;
-}
-
-@stage(compute) @workgroup_size(1, 1, 1)
-fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
-  x_1 = ${assignment_value};
-  main_1();
-}
-)";
-
-  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
-  expected =
-      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
-  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
-  expected =
-      utils::ReplaceAll(expected, "${assignment_value}",
-                        (wgsl_type == unsigned_wgsl_type)
-                            ? "x_1_param"
-                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
-
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_CopyObject) {
-  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
-  const auto wgsl_builtin = GetParam().wgsl_builtin;
-  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
-  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
-  const std::string assembly =
-      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
-                                  GetParam().spirv_store_type) +
-      R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %13 = OpCopyObject %ptr_ty %1
-    %2 = OpLoad )" +
-      GetParam().spirv_store_type + R"( %13
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  std::string expected = R"(var<private> x_1 : ${wgsl_type};
-
-fn main_1() {
-  let x_13 : ptr<private, ${wgsl_type}> = &(x_1);
-  let x_2 : ${wgsl_type} = *(x_13);
-  return;
-}
-
-@stage(compute) @workgroup_size(1, 1, 1)
-fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
-  x_1 = ${assignment_value};
-  main_1();
-}
-)";
-
-  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
-  expected =
-      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
-  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
-  expected =
-      utils::ReplaceAll(expected, "${assignment_value}",
-                        (wgsl_type == unsigned_wgsl_type)
-                            ? "x_1_param"
-                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
-
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_AccessChain) {
-  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
-  const auto wgsl_builtin = GetParam().wgsl_builtin;
-  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
-  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
-  const std::string assembly =
-      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
-                                  GetParam().spirv_store_type) +
-      R"(
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    %13 = OpAccessChain %ptr_ty %1
-    %2 = OpLoad )" +
-      GetParam().spirv_store_type + R"( %13
-    OpReturn
-    OpFunctionEnd
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto module_str = test::ToString(p->program());
-  std::string expected = R"(var<private> x_1 : ${wgsl_type};
-
-fn main_1() {
-  let x_2 : ${wgsl_type} = x_1;
-  return;
-}
-
-@stage(compute) @workgroup_size(1, 1, 1)
-fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
-  x_1 = ${assignment_value};
-  main_1();
-}
-)";
-
-  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
-  expected =
-      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
-  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
-  expected =
-      utils::ReplaceAll(expected, "${assignment_value}",
-                        (wgsl_type == unsigned_wgsl_type)
-                            ? "x_1_param"
-                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
-
-  EXPECT_EQ(module_str, expected) << module_str;
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Samples,
-    SpvModuleScopeVarParserTest_ComputeBuiltin,
-    ::testing::ValuesIn(std::vector<ComputeBuiltinInputCase>{
-        {"LocalInvocationIndex", "%uint", "local_invocation_index"},
-        {"LocalInvocationIndex", "%int", "local_invocation_index"},
-        {"LocalInvocationId", "%v3uint", "local_invocation_id"},
-        {"LocalInvocationId", "%v3int", "local_invocation_id"},
-        {"GlobalInvocationId", "%v3uint", "global_invocation_id"},
-        {"GlobalInvocationId", "%v3int", "global_invocation_id"},
-        {"WorkgroupId", "%v3uint", "workgroup_id"},
-        {"WorkgroupId", "%v3int", "workgroup_id"}}));
-
-// TODO(dneto): crbug.com/tint/752
-// NumWorkgroups support is blocked by crbug.com/tint/752
-// When the AST supports NumWorkgroups, add these cases:
-//        {"NumWorkgroups", "%uint", "num_workgroups"}
-//        {"NumWorkgroups", "%int", "num_workgroups"}
-
-TEST_F(SpvModuleScopeVarParserTest, RegisterInputOutputVars) {
-  const std::string assembly =
-      R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Fragment %1000 "w1000"
-    OpEntryPoint Fragment %1100 "w1100" %1
-    OpEntryPoint Fragment %1200 "w1200" %2 %15
-    ; duplication is tolerated prior to SPIR-V 1.4
-    OpEntryPoint Fragment %1300 "w1300" %1 %15 %2 %1
-    OpExecutionMode %1000 OriginUpperLeft
-    OpExecutionMode %1100 OriginUpperLeft
-    OpExecutionMode %1200 OriginUpperLeft
-    OpExecutionMode %1300 OriginUpperLeft
-
-    OpDecorate %1 Location 1
-    OpDecorate %2 Location 2
-    OpDecorate %5 Location 5
-    OpDecorate %11 Location 1
-    OpDecorate %12 Location 2
-    OpDecorate %15 Location 5
-
-)" + CommonTypes() +
-      R"(
-
-    %ptr_in_uint = OpTypePointer Input %uint
-    %ptr_out_uint = OpTypePointer Output %uint
-
-    %1 = OpVariable %ptr_in_uint Input
-    %2 = OpVariable %ptr_in_uint Input
-    %5 = OpVariable %ptr_in_uint Input
-    %11 = OpVariable %ptr_out_uint Output
-    %12 = OpVariable %ptr_out_uint Output
-    %15 = OpVariable %ptr_out_uint Output
-
-    %100 = OpFunction %void None %voidfn
-    %entry_100 = OpLabel
-    %load_100 = OpLoad %uint %1
-    OpReturn
-    OpFunctionEnd
-
-    %200 = OpFunction %void None %voidfn
-    %entry_200 = OpLabel
-    %load_200 = OpLoad %uint %2
-    OpStore %15 %load_200
-    OpStore %15 %load_200
-    OpReturn
-    OpFunctionEnd
-
-    %300 = OpFunction %void None %voidfn
-    %entry_300 = OpLabel
-    %dummy_300_1 = OpFunctionCall %void %100
-    %dummy_300_2 = OpFunctionCall %void %200
-    OpReturn
-    OpFunctionEnd
-
-    ; Call nothing
-    %1000 = OpFunction %void None %voidfn
-    %entry_1000 = OpLabel
-    OpReturn
-    OpFunctionEnd
-
-    ; Call %100
-    %1100 = OpFunction %void None %voidfn
-    %entry_1100 = OpLabel
-    %dummy_1100_1 = OpFunctionCall %void %100
-    OpReturn
-    OpFunctionEnd
-
-    ; Call %200
-    %1200 = OpFunction %void None %voidfn
-    %entry_1200 = OpLabel
-    %dummy_1200_1 = OpFunctionCall %void %200
-    OpReturn
-    OpFunctionEnd
-
-    ; Call %300
-    %1300 = OpFunction %void None %voidfn
-    %entry_1300 = OpLabel
-    %dummy_1300_1 = OpFunctionCall %void %300
-    OpReturn
-    OpFunctionEnd
-
- )";
-  auto p = parser(test::Assemble(assembly));
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto& info_1000 = p->GetEntryPointInfo(1000);
-  EXPECT_EQ(1u, info_1000.size());
-  EXPECT_TRUE(info_1000[0].inputs.empty());
-  EXPECT_TRUE(info_1000[0].outputs.empty());
-
-  const auto& info_1100 = p->GetEntryPointInfo(1100);
-  EXPECT_EQ(1u, info_1100.size());
-  EXPECT_THAT(info_1100[0].inputs, ElementsAre(1));
-  EXPECT_TRUE(info_1100[0].outputs.empty());
-
-  const auto& info_1200 = p->GetEntryPointInfo(1200);
-  EXPECT_EQ(1u, info_1200.size());
-  EXPECT_THAT(info_1200[0].inputs, ElementsAre(2));
-  EXPECT_THAT(info_1200[0].outputs, ElementsAre(15));
-
-  const auto& info_1300 = p->GetEntryPointInfo(1300);
-  EXPECT_EQ(1u, info_1300.size());
-  EXPECT_THAT(info_1300[0].inputs, ElementsAre(1, 2));
-  EXPECT_THAT(info_1300[0].outputs, ElementsAre(15));
-
-  // Validation incorrectly reports an overlap for the duplicated variable %1 on
-  // shader %1300
-  p->SkipDumpingPending(
-      "https://github.com/KhronosGroup/SPIRV-Tools/issues/4403");
-}
-
-TEST_F(SpvModuleScopeVarParserTest, InputVarsConvertedToPrivate) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr_in_uint = OpTypePointer Input %uint
-     %1 = OpVariable %ptr_in_uint Input
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = "var<private> x_1 : u32;";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, OutputVarsConvertedToPrivate) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr_out_uint = OpTypePointer Output %uint
-     %1 = OpVariable %ptr_out_uint Output
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = "var<private> x_1 : u32;";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       OutputVarsConvertedToPrivate_WithInitializer) {
-  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
-     %ptr_out_uint = OpTypePointer Output %uint
-     %1 = OpVariable %ptr_out_uint Output %uint_1
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = "var<private> x_1 : u32 = 1u;";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       Builtin_Output_Initializer_SameSignednessAsWGSL) {
-  // Only outputs can have initializers.
-  // WGSL sample_mask store type is u32.
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() + R"(
-     %arr_ty = OpTypeArray %uint %uint_1
-     %ptr_ty = OpTypePointer Output %arr_ty
-     %arr_init = OpConstantComposite %arr_ty %uint_2
-     %1 = OpVariable %ptr_ty Output %arr_init
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      "var<private> x_1 : array<u32, 1u> = array<u32, 1u>(2u);";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       Builtin_Output_Initializer_OppositeSignednessAsWGSL) {
-  // Only outputs can have initializers.
-  // WGSL sample_mask store type is u32.  Use i32 in SPIR-V
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() + R"(
-     %arr_ty = OpTypeArray %int %uint_1
-     %ptr_ty = OpTypePointer Output %arr_ty
-     %arr_init = OpConstantComposite %arr_ty %int_14
-     %1 = OpVariable %ptr_ty Output %arr_init
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      "var<private> x_1 : array<i32, 1u> = array<i32, 1u>(14);";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Builtin_Input_SameSignednessAsWGSL) {
-  // WGSL vertex_index store type is u32.
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpDecorate %1 BuiltIn VertexIndex
-)" + CommonTypes() + R"(
-     %ptr_ty = OpTypePointer Input %uint
-     %1 = OpVariable %ptr_ty Input
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = "var<private> x_1 : u32;";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Builtin_Input_OppositeSignednessAsWGSL) {
-  // WGSL vertex_index store type is u32.  Use i32 in SPIR-V.
-  const auto assembly = Preamble() + FragMain() + R"(
-     OpDecorate %1 BuiltIn VertexIndex
-)" + CommonTypes() + R"(
-     %ptr_ty = OpTypePointer Input %int
-     %1 = OpVariable %ptr_ty Input
-  )" + MainBody();
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = "var<private> x_1 : i32;";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, EntryPointWrapping_IOLocations) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1 %2 %3 %4
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 Location 0
-     OpDecorate %2 Location 0
-     OpDecorate %3 Location 30
-     OpDecorate %4 Location 6
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_uint = OpTypePointer Input %uint
-     %ptr_out_uint = OpTypePointer Output %uint
-     %1 = OpVariable %ptr_in_uint Input
-     %2 = OpVariable %ptr_out_uint Output
-     %3 = OpVariable %ptr_in_uint Input
-     %4 = OpVariable %ptr_out_uint Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : u32;
-
-var<private> x_2 : u32;
-
-var<private> x_3 : u32;
-
-var<private> x_4 : u32;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(0) @interpolate(flat)
-  x_2_1 : u32;
-  @location(6) @interpolate(flat)
-  x_4_1 : u32;
-}
-
-@stage(fragment)
-fn main(@location(0) @interpolate(flat) x_1_param : u32, @location(30) @interpolate(flat) x_3_param : u32) -> main_out {
-  x_1 = x_1_param;
-  x_3 = x_3_param;
-  main_1();
-  return main_out(x_2, x_4);
-}
-)";
-  EXPECT_THAT(got, HasSubstr(expected)) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_Input_SameSignedness) {
-  // instance_index is u32 in WGSL. Use uint in SPIR-V.
-  // No bitcasts are used for parameter formation or return value.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Vertex %main "main" %1 %position
-     OpDecorate %position BuiltIn Position
-     OpDecorate %1 BuiltIn InstanceIndex
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_uint = OpTypePointer Input %uint
-     %1 = OpVariable %ptr_in_uint Input
-     %posty = OpTypePointer Output %v4float
-     %position = OpVariable %posty Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpLoad %uint %1 ; load same signedness
-     ;;;; %3 = OpLoad %int %1 ; loading different signedness is invalid.
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : u32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : u32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = x_1_param;
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_Input_OppositeSignedness) {
-  // instance_index is u32 in WGSL. Use int in SPIR-V.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Vertex %main "main" %position %1
-     OpDecorate %position BuiltIn Position
-     OpDecorate %1 BuiltIn InstanceIndex
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_int = OpTypePointer Input %int
-     %1 = OpVariable %ptr_in_int Input
-     %posty = OpTypePointer Output %v4float
-     %position = OpVariable %posty Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     %2 = OpLoad %int %1 ; load same signedness
-     ;;; %3 = OpLoad %uint %1 ; loading different signedness is invalid
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : i32;
-
-var<private> x_4 : vec4<f32>;
-
-fn main_1() {
-  let x_2 : i32 = x_1;
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_4_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
-  x_1 = bitcast<i32>(x_1_param);
-  main_1();
-  return main_out(x_4);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-// SampleMask is an array in Vulkan SPIR-V, but a scalar in WGSL.
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned) {
-  // SampleMask is u32 in WGSL.
-  // Use unsigned array element in Vulkan.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() +
-                        R"(
-     %arr = OpTypeArray %uint %uint_1
-     %ptr_ty = OpTypePointer Input %arr
-     %1 = OpVariable %ptr_ty Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = x_1_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_SampleMask_In_Signed) {
-  // SampleMask is u32 in WGSL.
-  // Use signed array element in Vulkan.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() +
-                        R"(
-     %arr = OpTypeArray %int %uint_1
-     %ptr_ty = OpTypePointer Input %arr
-     %1 = OpVariable %ptr_ty Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@builtin(sample_mask) x_1_param : u32) {
-  x_1[0] = bitcast<i32>(x_1_param);
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer) {
-  // SampleMask is u32 in WGSL.
-  // Use unsigned array element in Vulkan.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() +
-                        R"(
-     %arr = OpTypeArray %uint %uint_1
-     %ptr_ty = OpTypePointer Output %arr
-     %zero = OpConstantNull %arr
-     %1 = OpVariable %ptr_ty Output %zero
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : array<u32, 1u> = array<u32, 1u>();
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0]);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer) {
-  // SampleMask is u32 in WGSL.
-  // Use signed array element in Vulkan.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 BuiltIn SampleMask
-)" + CommonTypes() +
-                        R"(
-     %arr = OpTypeArray %int %uint_1
-     %ptr_ty = OpTypePointer Output %arr
-     %zero = OpConstantNull %arr
-     %1 = OpVariable %ptr_ty Output %zero
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : array<i32, 1u> = array<i32, 1u>();
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(sample_mask)
-  x_1_1 : u32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(bitcast<u32>(x_1[0]));
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer) {
-  // FragDepth does not require conversion, because it's f32.
-  // The member of the return type is just the identifier corresponding
-  // to the module-scope private variable.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 BuiltIn FragDepth
-)" + CommonTypes() +
-                        R"(
-     %ptr_ty = OpTypePointer Output %float
-     %1 = OpVariable %ptr_ty Output %float_0
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : f32 = 0.0;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(frag_depth)
-  x_1_1 : f32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_BuiltIn_Position) {
-  // In Vulkan SPIR-V, Position is the first member of gl_PerVertex
-  const std::string assembly = PerVertexPreamble() + R"(
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> gl_Position : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  gl_Position : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(gl_Position);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       BuiltinPosition_BuiltIn_Position_Initializer) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1
-
-    OpDecorate %10 Block
-    OpMemberDecorate %10 0 BuiltIn Position
-    OpMemberDecorate %10 1 BuiltIn PointSize
-    OpMemberDecorate %10 2 BuiltIn ClipDistance
-    OpMemberDecorate %10 3 BuiltIn CullDistance
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %uint = OpTypeInt 32 0
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %arr = OpTypeArray %float %uint_1
-    %10 = OpTypeStruct %v4float %float %arr %arr
-    %11 = OpTypePointer Output %10
-
-    %float_1 = OpConstant %float 1
-    %float_2 = OpConstant %float 2
-    %float_3 = OpConstant %float 3
-    %float_4 = OpConstant %float 4
-    %float_5 = OpConstant %float 5
-    %float_6 = OpConstant %float 6
-    %float_7 = OpConstant %float 7
-
-    %init_pos = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
-    %init_clip = OpConstantComposite %arr %float_6
-    %init_cull = OpConstantComposite %arr %float_7
-    %init_per_vertex = OpConstantComposite %10 %init_pos %float_5 %init_clip %init_cull
-
-    %1 = OpVariable %11 Output %init_per_vertex
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> gl_Position : vec4<f32> = vec4<f32>(1.0, 2.0, 3.0, 4.0);
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  gl_Position : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(gl_Position);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Input_FlattenArray_OneLevel) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-    OpDecorate %1 Location 4
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %uint = OpTypeInt 32 0
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %uint_3 = OpConstant %uint 3
-    %arr = OpTypeArray %float %uint_3
-    %11 = OpTypePointer Input %arr
-
-    %1 = OpVariable %11 Input
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(4) x_1_param : f32, @location(5) x_1_param_1 : f32, @location(6) x_1_param_2 : f32) -> main_out {
-  x_1[0] = x_1_param;
-  x_1[1] = x_1_param_1;
-  x_1[2] = x_1_param_2;
-  main_1();
-  return main_out(x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Input_FlattenMatrix) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-    OpDecorate %1 Location 9
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %m2v4float = OpTypeMatrix %v4float 2
-    %uint = OpTypeInt 32 0
-
-    %11 = OpTypePointer Input %m2v4float
-
-    %1 = OpVariable %11 Input
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(9) x_1_param : vec4<f32>, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
-  x_1[0] = x_1_param;
-  x_1[1] = x_1_param_1;
-  main_1();
-  return main_out(x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Input_FlattenStruct_LocOnVariable) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-
-    OpName %strct "Communicators"
-    OpMemberName %strct 0 "alice"
-    OpMemberName %strct 1 "bob"
-
-    OpDecorate %1 Location 9
-    OpDecorate %2 BuiltIn Position
-
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %strct = OpTypeStruct %float %v4float
-
-    %11 = OpTypePointer Input %strct
-
-    %1 = OpVariable %11 Input
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(struct Communicators {
-  alice : f32;
-  bob : vec4<f32>;
-}
-
-var<private> x_1 : Communicators;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(9) x_1_param : f32, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
-  x_1.alice = x_1_param;
-  x_1.bob = x_1_param_1;
-  main_1();
-  return main_out(x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Input_FlattenNested) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-    OpDecorate %1 Location 7
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %m2v4float = OpTypeMatrix %v4float 2
-    %uint = OpTypeInt 32 0
-    %uint_2 = OpConstant %uint 2
-
-    %arr = OpTypeArray %m2v4float %uint_2
-
-    %11 = OpTypePointer Input %arr
-    %1 = OpVariable %11 Input
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<mat2x4<f32>, 2u>;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(7) x_1_param : vec4<f32>, @location(8) x_1_param_1 : vec4<f32>, @location(9) x_1_param_2 : vec4<f32>, @location(10) x_1_param_3 : vec4<f32>) -> main_out {
-  x_1[0][0] = x_1_param;
-  x_1[0][1] = x_1_param_1;
-  x_1[1][0] = x_1_param_2;
-  x_1[1][1] = x_1_param_3;
-  main_1();
-  return main_out(x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Output_FlattenArray_OneLevel) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-    OpDecorate %1 Location 4
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %uint = OpTypeInt 32 0
-    %uint_0 = OpConstant %uint 0
-    %uint_1 = OpConstant %uint 1
-    %uint_3 = OpConstant %uint 3
-    %arr = OpTypeArray %float %uint_3
-    %11 = OpTypePointer Output %arr
-
-    %1 = OpVariable %11 Output
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(4)
-  x_1_1 : f32;
-  @location(5)
-  x_1_2 : f32;
-  @location(6)
-  x_1_3 : f32;
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0], x_1[1], x_1[2], x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Output_FlattenMatrix) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-    OpDecorate %1 Location 9
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %m2v4float = OpTypeMatrix %v4float 2
-    %uint = OpTypeInt 32 0
-
-    %11 = OpTypePointer Output %m2v4float
-
-    %1 = OpVariable %11 Output
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(9)
-  x_1_1 : vec4<f32>;
-  @location(10)
-  x_1_2 : vec4<f32>;
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1[0], x_1[1], x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, Output_FlattenStruct_LocOnVariable) {
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2
-
-    OpName %strct "Communicators"
-    OpMemberName %strct 0 "alice"
-    OpMemberName %strct 1 "bob"
-
-    OpDecorate %1 Location 9
-    OpDecorate %2 BuiltIn Position
-
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %strct = OpTypeStruct %float %v4float
-
-    %11 = OpTypePointer Output %strct
-
-    %1 = OpVariable %11 Output
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(struct Communicators {
-  alice : f32;
-  bob : vec4<f32>;
-}
-
-var<private> x_1 : Communicators;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(9)
-  x_1_1 : f32;
-  @location(10)
-  x_1_2 : vec4<f32>;
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1.alice, x_1.bob, x_2);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest, FlattenStruct_LocOnMembers) {
-  // Block-decorated struct may have its members decorated with Location.
-  const std::string assembly = R"(
-    OpCapability Shader
-    OpMemoryModel Logical Simple
-    OpEntryPoint Vertex %main "main" %1 %2 %3
-
-    OpName %strct "Communicators"
-    OpMemberName %strct 0 "alice"
-    OpMemberName %strct 1 "bob"
-
-    OpMemberDecorate %strct 0 Location 9
-    OpMemberDecorate %strct 1 Location 11
-    OpDecorate %strct Block
-    OpDecorate %2 BuiltIn Position
-
-    %void = OpTypeVoid
-    %voidfn = OpTypeFunction %void
-    %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %strct = OpTypeStruct %float %v4float
-
-    %11 = OpTypePointer Input %strct
-    %13 = OpTypePointer Output %strct
-
-    %1 = OpVariable %11 Input
-    %3 = OpVariable %13 Output
-
-    %12 = OpTypePointer Output %v4float
-    %2 = OpVariable %12 Output
-
-    %main = OpFunction %void None %voidfn
-    %entry = OpLabel
-    OpReturn
-    OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
-  EXPECT_TRUE(p->error().empty());
-
-  const auto got = test::ToString(p->program());
-  const std::string expected = R"(struct Communicators {
-  alice : f32;
-  bob : vec4<f32>;
-}
-
-var<private> x_1 : Communicators;
-
-var<private> x_3 : Communicators;
-
-var<private> x_2 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_2_1 : vec4<f32>;
-  @location(9)
-  x_3_1 : f32;
-  @location(11)
-  x_3_2 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(9) x_1_param : f32, @location(11) x_1_param_1 : vec4<f32>) -> main_out {
-  x_1.alice = x_1_param;
-  x_1.bob = x_1_param_1;
-  main_1();
-  return main_out(x_2, x_3.alice, x_3.bob);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Flat_Vertex_In) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-     OpDecorate %1 Flat
-     OpDecorate %2 Flat
-     OpDecorate %3 Flat
-     OpDecorate %4 Flat
-     OpDecorate %5 Flat
-     OpDecorate %6 Flat
-     OpDecorate %10 BuiltIn Position
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_uint = OpTypePointer Input %uint
-     %ptr_in_v2uint = OpTypePointer Input %v2uint
-     %ptr_in_int = OpTypePointer Input %int
-     %ptr_in_v2int = OpTypePointer Input %v2int
-     %ptr_in_float = OpTypePointer Input %float
-     %ptr_in_v2float = OpTypePointer Input %v2float
-     %1 = OpVariable %ptr_in_uint Input
-     %2 = OpVariable %ptr_in_v2uint Input
-     %3 = OpVariable %ptr_in_int Input
-     %4 = OpVariable %ptr_in_v2int Input
-     %5 = OpVariable %ptr_in_float Input
-     %6 = OpVariable %ptr_in_v2float Input
-
-     %ptr_out_v4float = OpTypePointer Output %v4float
-     %10 = OpVariable %ptr_out_v4float Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : u32;
-
-var<private> x_2 : vec2<u32>;
-
-var<private> x_3 : i32;
-
-var<private> x_4 : vec2<i32>;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : vec2<f32>;
-
-var<private> x_10 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @builtin(position)
-  x_10_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) @interpolate(flat) x_5_param : f32, @location(6) @interpolate(flat) x_6_param : vec2<f32>) -> main_out {
-  x_1 = x_1_param;
-  x_2 = x_2_param;
-  x_3 = x_3_param;
-  x_4 = x_4_param;
-  x_5 = x_5_param;
-  x_6 = x_6_param;
-  main_1();
-  return main_out(x_10);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Flat_Vertex_Output) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-     OpDecorate %1 Flat
-     OpDecorate %2 Flat
-     OpDecorate %3 Flat
-     OpDecorate %4 Flat
-     OpDecorate %5 Flat
-     OpDecorate %6 Flat
-     OpDecorate %10 BuiltIn Position
-)" + CommonTypes() +
-                        R"(
-     %ptr_out_uint = OpTypePointer Output %uint
-     %ptr_out_v2uint = OpTypePointer Output %v2uint
-     %ptr_out_int = OpTypePointer Output %int
-     %ptr_out_v2int = OpTypePointer Output %v2int
-     %ptr_out_float = OpTypePointer Output %float
-     %ptr_out_v2float = OpTypePointer Output %v2float
-     %1 = OpVariable %ptr_out_uint Output
-     %2 = OpVariable %ptr_out_v2uint Output
-     %3 = OpVariable %ptr_out_int Output
-     %4 = OpVariable %ptr_out_v2int Output
-     %5 = OpVariable %ptr_out_float Output
-     %6 = OpVariable %ptr_out_v2float Output
-
-     %ptr_out_v4float = OpTypePointer Output %v4float
-     %10 = OpVariable %ptr_out_v4float Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : u32;
-
-var<private> x_2 : vec2<u32>;
-
-var<private> x_3 : i32;
-
-var<private> x_4 : vec2<i32>;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : vec2<f32>;
-
-var<private> x_10 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(1) @interpolate(flat)
-  x_1_1 : u32;
-  @location(2) @interpolate(flat)
-  x_2_1 : vec2<u32>;
-  @location(3) @interpolate(flat)
-  x_3_1 : i32;
-  @location(4) @interpolate(flat)
-  x_4_1 : vec2<i32>;
-  @location(5) @interpolate(flat)
-  x_5_1 : f32;
-  @location(6) @interpolate(flat)
-  x_6_1 : vec2<f32>;
-  @builtin(position)
-  x_10_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1, x_2, x_3, x_4, x_5, x_6, x_10);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1 %2
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 5
-     OpDecorate %1 Flat
-     OpDecorate %2 Flat
-)" + CommonTypes() +
-                        R"(
-     %arr = OpTypeArray %float %uint_2
-     %strct = OpTypeStruct %float %float
-     %ptr_in_arr = OpTypePointer Input %arr
-     %ptr_in_strct = OpTypePointer Input %strct
-     %1 = OpVariable %ptr_in_arr Input
-     %2 = OpVariable %ptr_in_strct Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(struct S {
-  field0 : f32;
-  field1 : f32;
-}
-
-var<private> x_1 : array<f32, 2u>;
-
-var<private> x_2 : S;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@location(1) @interpolate(flat) x_1_param : f32, @location(2) @interpolate(flat) x_1_param_1 : f32, @location(5) @interpolate(flat) x_2_param : f32, @location(6) @interpolate(flat) x_2_param_1 : f32) {
-  x_1[0] = x_1_param;
-  x_1[1] = x_1_param_1;
-  x_2.field0 = x_2_param;
-  x_2.field1 = x_2_param_1;
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Floating_Fragment_In) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-
-     ; %1 perspective center
-
-     OpDecorate %2 Centroid ; perspective centroid
-
-     OpDecorate %3 Sample ; perspective sample
-
-     OpDecorate %4 NoPerspective; linear center
-
-     OpDecorate %5 NoPerspective ; linear centroid
-     OpDecorate %5 Centroid
-
-     OpDecorate %6 NoPerspective ; linear sample
-     OpDecorate %6 Sample
-
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_float = OpTypePointer Input %float
-     %1 = OpVariable %ptr_in_float Input
-     %2 = OpVariable %ptr_in_float Input
-     %3 = OpVariable %ptr_in_float Input
-     %4 = OpVariable %ptr_in_float Input
-     %5 = OpVariable %ptr_in_float Input
-     %6 = OpVariable %ptr_in_float Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : f32;
-
-var<private> x_2 : f32;
-
-var<private> x_3 : f32;
-
-var<private> x_4 : f32;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : f32;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@location(1) x_1_param : f32, @location(2) @interpolate(perspective, centroid) x_2_param : f32, @location(3) @interpolate(perspective, sample) x_3_param : f32, @location(4) @interpolate(linear) x_4_param : f32, @location(5) @interpolate(linear, centroid) x_5_param : f32, @location(6) @interpolate(linear, sample) x_6_param : f32) {
-  x_1 = x_1_param;
-  x_2 = x_2_param;
-  x_3 = x_3_param;
-  x_4 = x_4_param;
-  x_5 = x_5_param;
-  x_6 = x_6_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 Location 1
-
-     ; member 0 perspective center
-
-     OpMemberDecorate %10 1 Centroid ; perspective centroid
-
-     OpMemberDecorate %10 2 Sample ; perspective sample
-
-     OpMemberDecorate %10 3 NoPerspective; linear center
-
-     OpMemberDecorate %10 4 NoPerspective ; linear centroid
-     OpMemberDecorate %10 4 Centroid
-
-     OpMemberDecorate %10 5 NoPerspective ; linear sample
-     OpMemberDecorate %10 5 Sample
-
-)" + CommonTypes() +
-                        R"(
-
-     %10 = OpTypeStruct %float %float %float %float %float %float
-     %ptr_in_strct = OpTypePointer Input %10
-     %1 = OpVariable %ptr_in_strct Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(struct S {
-  field0 : f32;
-  field1 : f32;
-  field2 : f32;
-  field3 : f32;
-  field4 : f32;
-  field5 : f32;
-}
-
-var<private> x_1 : S;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@location(1) x_1_param : f32, @location(2) @interpolate(perspective, centroid) x_1_param_1 : f32, @location(3) @interpolate(perspective, sample) x_1_param_2 : f32, @location(4) @interpolate(linear) x_1_param_3 : f32, @location(5) @interpolate(linear, centroid) x_1_param_4 : f32, @location(6) @interpolate(linear, sample) x_1_param_5 : f32) {
-  x_1.field0 = x_1_param;
-  x_1.field1 = x_1_param_1;
-  x_1.field2 = x_1_param_2;
-  x_1.field3 = x_1_param_3;
-  x_1.field4 = x_1_param_4;
-  x_1.field5 = x_1_param_5;
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Floating_Fragment_Out) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
-     OpExecutionMode %main OriginUpperLeft
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-
-     ; %1 perspective center
-
-     OpDecorate %2 Centroid ; perspective centroid
-
-     OpDecorate %3 Sample ; perspective sample
-
-     OpDecorate %4 NoPerspective; linear center
-
-     OpDecorate %5 NoPerspective ; linear centroid
-     OpDecorate %5 Centroid
-
-     OpDecorate %6 NoPerspective ; linear sample
-     OpDecorate %6 Sample
-
-)" + CommonTypes() +
-                        R"(
-     %ptr_out_float = OpTypePointer Output %float
-     %1 = OpVariable %ptr_out_float Output
-     %2 = OpVariable %ptr_out_float Output
-     %3 = OpVariable %ptr_out_float Output
-     %4 = OpVariable %ptr_out_float Output
-     %5 = OpVariable %ptr_out_float Output
-     %6 = OpVariable %ptr_out_float Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : f32;
-
-var<private> x_2 : f32;
-
-var<private> x_3 : f32;
-
-var<private> x_4 : f32;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : f32;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(1)
-  x_1_1 : f32;
-  @location(2) @interpolate(perspective, centroid)
-  x_2_1 : f32;
-  @location(3) @interpolate(perspective, sample)
-  x_3_1 : f32;
-  @location(4) @interpolate(linear)
-  x_4_1 : f32;
-  @location(5) @interpolate(linear, centroid)
-  x_5_1 : f32;
-  @location(6) @interpolate(linear, sample)
-  x_6_1 : f32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1, x_2, x_3, x_4, x_5, x_6);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out) {
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1
-     OpExecutionMode %main OriginUpperLeft
-
-     OpDecorate %1 Location 1
-
-     ; member 0 perspective center
-
-     OpMemberDecorate %10 1 Centroid ; perspective centroid
-
-     OpMemberDecorate %10 2 Sample ; perspective sample
-
-     OpMemberDecorate %10 3 NoPerspective; linear center
-
-     OpMemberDecorate %10 4 NoPerspective ; linear centroid
-     OpMemberDecorate %10 4 Centroid
-
-     OpMemberDecorate %10 5 NoPerspective ; linear sample
-     OpMemberDecorate %10 5 Sample
-
-)" + CommonTypes() +
-                        R"(
-
-     %10 = OpTypeStruct %float %float %float %float %float %float
-     %ptr_in_strct = OpTypePointer Output %10
-     %1 = OpVariable %ptr_in_strct Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(struct S {
-  field0 : f32;
-  field1 : f32;
-  field2 : f32;
-  field3 : f32;
-  field4 : f32;
-  field5 : f32;
-}
-
-var<private> x_1 : S;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(1)
-  x_1_1 : f32;
-  @location(2) @interpolate(perspective, centroid)
-  x_1_2 : f32;
-  @location(3) @interpolate(perspective, sample)
-  x_1_3 : f32;
-  @location(4) @interpolate(linear)
-  x_1_4 : f32;
-  @location(5) @interpolate(linear, centroid)
-  x_1_5 : f32;
-  @location(6) @interpolate(linear, sample)
-  x_1_6 : f32;
-}
-
-@stage(fragment)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1.field0, x_1.field1, x_1.field2, x_1.field3, x_1.field4, x_1.field5);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Default_Vertex_Output) {
-  // Integral types default to @interpolate(flat).
-  // Floating types default to @interpolate(perspective, center), which is the
-  // same as WGSL and therefore dropped.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-     OpDecorate %10 BuiltIn Position
-)" + CommonTypes() +
-                        R"(
-     %ptr_out_uint = OpTypePointer Output %uint
-     %ptr_out_v2uint = OpTypePointer Output %v2uint
-     %ptr_out_int = OpTypePointer Output %int
-     %ptr_out_v2int = OpTypePointer Output %v2int
-     %ptr_out_float = OpTypePointer Output %float
-     %ptr_out_v2float = OpTypePointer Output %v2float
-     %1 = OpVariable %ptr_out_uint Output
-     %2 = OpVariable %ptr_out_v2uint Output
-     %3 = OpVariable %ptr_out_int Output
-     %4 = OpVariable %ptr_out_v2int Output
-     %5 = OpVariable %ptr_out_float Output
-     %6 = OpVariable %ptr_out_v2float Output
-
-     %ptr_out_v4float = OpTypePointer Output %v4float
-     %10 = OpVariable %ptr_out_v4float Output
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : u32;
-
-var<private> x_2 : vec2<u32>;
-
-var<private> x_3 : i32;
-
-var<private> x_4 : vec2<i32>;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : vec2<f32>;
-
-var<private> x_10 : vec4<f32>;
-
-fn main_1() {
-  return;
-}
-
-struct main_out {
-  @location(1) @interpolate(flat)
-  x_1_1 : u32;
-  @location(2) @interpolate(flat)
-  x_2_1 : vec2<u32>;
-  @location(3) @interpolate(flat)
-  x_3_1 : i32;
-  @location(4) @interpolate(flat)
-  x_4_1 : vec2<i32>;
-  @location(5)
-  x_5_1 : f32;
-  @location(6)
-  x_6_1 : vec2<f32>;
-  @builtin(position)
-  x_10_1 : vec4<f32>;
-}
-
-@stage(vertex)
-fn main() -> main_out {
-  main_1();
-  return main_out(x_1, x_2, x_3, x_4, x_5, x_6, x_10);
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-TEST_F(SpvModuleScopeVarParserTest,
-       EntryPointWrapping_Interpolation_Default_Fragment_In) {
-  // Integral types default to @interpolate(flat).
-  // Floating types default to @interpolate(perspective, center), which is the
-  // same as WGSL and therefore dropped.
-  const auto assembly = CommonCapabilities() + R"(
-     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
-     OpDecorate %1 Location 1
-     OpDecorate %2 Location 2
-     OpDecorate %3 Location 3
-     OpDecorate %4 Location 4
-     OpDecorate %5 Location 5
-     OpDecorate %6 Location 6
-)" + CommonTypes() +
-                        R"(
-     %ptr_in_uint = OpTypePointer Input %uint
-     %ptr_in_v2uint = OpTypePointer Input %v2uint
-     %ptr_in_int = OpTypePointer Input %int
-     %ptr_in_v2int = OpTypePointer Input %v2int
-     %ptr_in_float = OpTypePointer Input %float
-     %ptr_in_v2float = OpTypePointer Input %v2float
-     %1 = OpVariable %ptr_in_uint Input
-     %2 = OpVariable %ptr_in_v2uint Input
-     %3 = OpVariable %ptr_in_int Input
-     %4 = OpVariable %ptr_in_v2int Input
-     %5 = OpVariable %ptr_in_float Input
-     %6 = OpVariable %ptr_in_v2float Input
-
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )";
-  auto p = parser(test::Assemble(assembly));
-
-  ASSERT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_TRUE(p->error().empty());
-  const auto got = test::ToString(p->program());
-  const std::string expected =
-      R"(var<private> x_1 : u32;
-
-var<private> x_2 : vec2<u32>;
-
-var<private> x_3 : i32;
-
-var<private> x_4 : vec2<i32>;
-
-var<private> x_5 : f32;
-
-var<private> x_6 : vec2<f32>;
-
-fn main_1() {
-  return;
-}
-
-@stage(fragment)
-fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) x_5_param : f32, @location(6) x_6_param : vec2<f32>) {
-  x_1 = x_1_param;
-  x_2 = x_2_param;
-  x_3 = x_3_param;
-  x_4 = x_4_param;
-  x_5 = x_5_param;
-  x_6 = x_6_param;
-  main_1();
-}
-)";
-  EXPECT_EQ(got, expected) << got;
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_named_types_test.cc b/src/reader/spirv/parser_impl_named_types_test.cc
deleted file mode 100644
index 8da9c73..0000000
--- a/src/reader/spirv/parser_impl_named_types_test.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-TEST_F(SpvParserTest, NamedTypes_AnonStruct) {
-  auto p = parser(test::Assemble(R"(
-    %uint = OpTypeInt 32 0
-    %s = OpTypeStruct %uint %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()), HasSubstr("struct S"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_NamedStruct) {
-  auto p = parser(test::Assemble(R"(
-    OpName %s "mystruct"
-    %uint = OpTypeInt 32 0
-    %s = OpTypeStruct %uint %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()), HasSubstr("struct mystruct"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_Dup_EmitBoth) {
-  auto p = parser(test::Assemble(R"(
-    %uint = OpTypeInt 32 0
-    %s = OpTypeStruct %uint %uint
-    %s2 = OpTypeStruct %uint %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_THAT(test::ToString(p->program()), HasSubstr(R"(struct S {
-  field0 : u32;
-  field1 : u32;
-}
-
-struct S_1 {
-  field0 : u32;
-  field1 : u32;
-})"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-// TODO(dneto): Should we make an alias for an un-decoratrd array with
-// an OpName?
-
-TEST_F(SpvParserTest, NamedTypes_AnonRTArrayWithDecoration) {
-  // Runtime arrays are always in SSBO, and those are always laid out.
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %arr ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %arr = OpTypeRuntimeArray %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()),
-              HasSubstr("RTArr = @stride(8) array<u32>;\n"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_AnonRTArray_Dup_EmitBoth) {
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %arr ArrayStride 8
-    OpDecorate %arr2 ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %arr = OpTypeRuntimeArray %uint
-    %arr2 = OpTypeRuntimeArray %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()),
-              HasSubstr(R"(type RTArr = @stride(8) array<u32>;
-
-type RTArr_1 = @stride(8) array<u32>;
-)"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_NamedRTArray) {
-  auto p = parser(test::Assemble(R"(
-    OpName %arr "myrtarr"
-    OpDecorate %arr ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %arr = OpTypeRuntimeArray %uint
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()),
-              HasSubstr("myrtarr = @stride(8) array<u32>;\n"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_NamedArray) {
-  auto p = parser(test::Assemble(R"(
-    OpName %arr "myarr"
-    OpDecorate %arr ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %uint_5 = OpConstant %uint 5
-    %arr = OpTypeArray %uint %uint_5
-    %arr2 = OpTypeArray %uint %uint_5
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()),
-              HasSubstr("myarr = @stride(8) array<u32, 5u>;"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserTest, NamedTypes_AnonArray_Dup_EmitBoth) {
-  auto p = parser(test::Assemble(R"(
-    OpDecorate %arr ArrayStride 8
-    OpDecorate %arr2 ArrayStride 8
-    %uint = OpTypeInt 32 0
-    %uint_5 = OpConstant %uint 5
-    %arr = OpTypeArray %uint %uint_5
-    %arr2 = OpTypeArray %uint %uint_5
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(test::ToString(p->program()),
-              HasSubstr(R"(type Arr = @stride(8) array<u32, 5u>;
-
-type Arr_1 = @stride(8) array<u32, 5u>;
-)"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-// TODO(dneto): Handle arrays sized by a spec constant.
-// Blocked by crbug.com/tint/32
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_test.cc b/src/reader/spirv/parser_impl_test.cc
deleted file mode 100644
index 49ccad3..0000000
--- a/src/reader/spirv/parser_impl_test.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::HasSubstr;
-
-TEST_F(SpvParserTest, Impl_Uint32VecEmpty) {
-  std::vector<uint32_t> data;
-  auto p = parser(data);
-  EXPECT_FALSE(p->Parse());
-  // TODO(dneto): What message?
-}
-
-TEST_F(SpvParserTest, Impl_InvalidModuleFails) {
-  auto invalid_spv = test::Assemble("%ty = OpTypeInt 3 0");
-  auto p = parser(invalid_spv);
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(
-      p->error(),
-      HasSubstr("TypeInt cannot appear before the memory model instruction"));
-  EXPECT_THAT(p->error(), HasSubstr("OpTypeInt 3 0"));
-}
-
-TEST_F(SpvParserTest, Impl_GenericVulkanShader_SimpleMemoryModel) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, Impl_GenericVulkanShader_GLSL450MemoryModel) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpMemoryModel Logical GLSL450
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, Impl_GenericVulkanShader_VulkanMemoryModel) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpCapability VulkanMemoryModelKHR
-  OpExtension "SPV_KHR_vulkan_memory_model"
-  OpMemoryModel Logical VulkanKHR
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-}
-
-TEST_F(SpvParserTest, Impl_OpenCLKernel_Fails) {
-  auto spv = test::Assemble(R"(
-  OpCapability Kernel
-  OpCapability Addresses
-  OpMemoryModel Physical32 OpenCL
-  OpEntryPoint Kernel %main "main"
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
-  %entry = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_FALSE(p->Parse());
-  EXPECT_THAT(p->error(), HasSubstr("Capability Kernel is not allowed"));
-}
-
-TEST_F(SpvParserTest, Impl_Source_NoOpLine) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %5 = OpTypeInt 32 0
-  %60 = OpConstantNull %5
-  %main = OpFunction %void None %voidfn
-  %1 = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-  // Use instruction counting.
-  auto s5 = p->GetSourceForResultIdForTest(5);
-  EXPECT_EQ(7u, s5.range.begin.line);
-  EXPECT_EQ(0u, s5.range.begin.column);
-  auto s60 = p->GetSourceForResultIdForTest(60);
-  EXPECT_EQ(8u, s60.range.begin.line);
-  EXPECT_EQ(0u, s60.range.begin.column);
-  auto s1 = p->GetSourceForResultIdForTest(1);
-  EXPECT_EQ(10u, s1.range.begin.line);
-  EXPECT_EQ(0u, s1.range.begin.column);
-}
-
-TEST_F(SpvParserTest, Impl_Source_WithOpLine_WithOpNoLine) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %15 = OpString "myfile"
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  OpLine %15 42 53
-  %5 = OpTypeInt 32 0
-  %60 = OpConstantNull %5
-  OpNoLine
-  %main = OpFunction %void None %voidfn
-  %1 = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-  // Use the information from the OpLine that is still in scope.
-  auto s5 = p->GetSourceForResultIdForTest(5);
-  EXPECT_EQ(42u, s5.range.begin.line);
-  EXPECT_EQ(53u, s5.range.begin.column);
-  auto s60 = p->GetSourceForResultIdForTest(60);
-  EXPECT_EQ(42u, s60.range.begin.line);
-  EXPECT_EQ(53u, s60.range.begin.column);
-  // After OpNoLine, revert back to instruction counting.
-  auto s1 = p->GetSourceForResultIdForTest(1);
-  EXPECT_EQ(14u, s1.range.begin.line);
-  EXPECT_EQ(0u, s1.range.begin.column);
-}
-
-TEST_F(SpvParserTest, Impl_Source_InvalidId) {
-  auto spv = test::Assemble(R"(
-  OpCapability Shader
-  OpMemoryModel Logical Simple
-  OpEntryPoint GLCompute %main "main"
-  OpExecutionMode %main LocalSize 1 1 1
-  %15 = OpString "myfile"
-  %void = OpTypeVoid
-  %voidfn = OpTypeFunction %void
-  %main = OpFunction %void None %voidfn
-  %1 = OpLabel
-  OpReturn
-  OpFunctionEnd
-)");
-  auto p = parser(spv);
-  EXPECT_TRUE(p->Parse());
-  EXPECT_TRUE(p->error().empty());
-  auto s99 = p->GetSourceForResultIdForTest(99);
-  EXPECT_EQ(0u, s99.range.begin.line);
-  EXPECT_EQ(0u, s99.range.begin.column);
-}
-
-TEST_F(SpvParserTest, Impl_IsValidIdentifier) {
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier(""));  // empty
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier("_"));
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier("__"));
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("_x"));
-  EXPECT_FALSE(
-      ParserImpl::IsValidIdentifier("9"));  // leading digit, but ok later
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier(" "));    // leading space
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier("a "));   // trailing space
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier("a 1"));  // space in the middle
-  EXPECT_FALSE(ParserImpl::IsValidIdentifier("."));    // weird character
-
-  // a simple identifier
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("A"));
-  // each upper case letter
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
-  // each lower case letter
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("abcdefghijklmnopqrstuvwxyz"));
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("a0123456789"));  // each digit
-  EXPECT_TRUE(ParserImpl::IsValidIdentifier("x_"));           // has underscore
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_test_helper.cc b/src/reader/spirv/parser_impl_test_helper.cc
deleted file mode 100644
index 7a1dd99..0000000
--- a/src/reader/spirv/parser_impl_test_helper.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2021 The Tint Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/writer/wgsl/generator_impl.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace test {
-
-// Default to not dumping the SPIR-V assembly.
-bool ParserImplWrapperForTest::dump_successfully_converted_spirv_ = false;
-
-ParserImplWrapperForTest::ParserImplWrapperForTest(
-    const std::vector<uint32_t>& input)
-    : impl_(input) {}
-
-ParserImplWrapperForTest::~ParserImplWrapperForTest() {
-  if (dump_successfully_converted_spirv_ && !skip_dumping_spirv_ &&
-      !impl_.spv_binary().empty() && impl_.success()) {
-    std::string disassembly = Disassemble(impl_.spv_binary());
-    std::cout << "BEGIN ConvertedOk:\n"
-              << disassembly << "\nEND ConvertedOk" << std::endl;
-  }
-}
-
-std::string ToString(const Program& program) {
-  writer::wgsl::GeneratorImpl writer(&program);
-  if (!writer.Generate()) {
-    return "WGSL writer error: " + writer.error();
-  }
-  return writer.result();
-}
-
-std::string ToString(const Program& program, const ast::StatementList& stmts) {
-  writer::wgsl::GeneratorImpl writer(&program);
-  for (const auto* stmt : stmts) {
-    if (!writer.EmitStatement(stmt)) {
-      return "WGSL writer error: " + writer.error();
-    }
-  }
-  return writer.result();
-}
-
-std::string ToString(const Program& program, const ast::Node* node) {
-  writer::wgsl::GeneratorImpl writer(&program);
-  if (auto* expr = node->As<ast::Expression>()) {
-    std::stringstream out;
-    if (!writer.EmitExpression(out, expr)) {
-      return "WGSL writer error: " + writer.error();
-    }
-    return out.str();
-  } else if (auto* stmt = node->As<ast::Statement>()) {
-    if (!writer.EmitStatement(stmt)) {
-      return "WGSL writer error: " + writer.error();
-    }
-  } else if (auto* ty = node->As<ast::Type>()) {
-    std::stringstream out;
-    if (!writer.EmitType(out, ty)) {
-      return "WGSL writer error: " + writer.error();
-    }
-    return out.str();
-  } else {
-    return "<unhandled AST node type " + std::string(node->TypeInfo().name) +
-           ">";
-  }
-  return writer.result();
-}
-
-}  // namespace test
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_impl_test_helper.h b/src/reader/spirv/parser_impl_test_helper.h
deleted file mode 100644
index d88333d..0000000
--- a/src/reader/spirv/parser_impl_test_helper.h
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
-#define SRC_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#if TINT_BUILD_SPV_READER
-#include "source/opt/ir_context.h"
-#endif
-
-#include "gtest/gtest.h"
-#include "src/demangler.h"
-#include "src/reader/spirv/fail_stream.h"
-#include "src/reader/spirv/function.h"
-#include "src/reader/spirv/namer.h"
-#include "src/reader/spirv/parser_impl.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-#include "src/reader/spirv/usage.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace test {
-
-/// A test class that wraps ParseImpl
-class ParserImplWrapperForTest {
- public:
-  /// Constructor
-  /// @param input the input data to parse
-  explicit ParserImplWrapperForTest(const std::vector<uint32_t>& input);
-  /// Dumps SPIR-V if the conversion succeeded, then destroys the wrapper.
-  ~ParserImplWrapperForTest();
-
-  /// Sets global state to force dumping of the assembly text of succesfully
-  /// SPIR-V.
-  static void DumpSuccessfullyConvertedSpirv() {
-    dump_successfully_converted_spirv_ = true;
-  }
-  /// Marks the test has having deliberately invalid SPIR-V
-  void DeliberatelyInvalidSpirv() { skip_dumping_spirv_ = true; }
-  /// Marks the test's SPIR-V as not being suitable for dumping, for a stated
-  /// reason.
-  void SkipDumpingPending(std::string) { skip_dumping_spirv_ = true; }
-
-  /// @returns a new function emitter for the given function ID.
-  /// Assumes ParserImpl::BuildInternalRepresentation has been run and
-  /// succeeded.
-  /// @param function_id the SPIR-V identifier of the function
-  FunctionEmitter function_emitter(uint32_t function_id) {
-    auto* spirv_function = impl_.ir_context()->GetFunction(function_id);
-    return FunctionEmitter(&impl_, *spirv_function);
-  }
-
-  /// Run the parser
-  /// @returns true if the parse was successful, false otherwise.
-  bool Parse() { return impl_.Parse(); }
-
-  /// @returns the program. The program builder in the parser will be reset
-  /// after this.
-  Program program() { return impl_.program(); }
-
-  /// @returns the namer object
-  Namer& namer() { return impl_.namer(); }
-
-  /// @returns a reference to the internal builder, without building the
-  /// program. To be used only for testing.
-  ProgramBuilder& builder() { return impl_.builder(); }
-
-  /// @returns the accumulated error string
-  const std::string error() { return impl_.error(); }
-
-  /// @return true if failure has not yet occurred
-  bool success() { return impl_.success(); }
-
-  /// Logs failure, ands return a failure stream to accumulate diagnostic
-  /// messages. By convention, a failure should only be logged along with
-  /// a non-empty string diagnostic.
-  /// @returns the failure stream
-  FailStream& Fail() { return impl_.Fail(); }
-
-  /// @returns a borrowed pointer to the internal representation of the module.
-  /// This is null until BuildInternalModule has been called.
-  spvtools::opt::IRContext* ir_context() { return impl_.ir_context(); }
-
-  /// Builds the internal representation of the SPIR-V module.
-  /// Assumes the module is somewhat well-formed.  Normally you
-  /// would want to validate the SPIR-V module before attempting
-  /// to build this internal representation. Also computes a topological
-  /// ordering of the functions.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if the parser is still successful.
-  bool BuildInternalModule() { return impl_.BuildInternalModule(); }
-
-  /// Builds an internal representation of the SPIR-V binary,
-  /// and parses the module, except functions, into a Tint AST module.
-  /// Diagnostics are emitted to the error stream.
-  /// @returns true if it was successful.
-  bool BuildAndParseInternalModuleExceptFunctions() {
-    return impl_.BuildAndParseInternalModuleExceptFunctions();
-  }
-
-  /// Builds an internal representation of the SPIR-V binary,
-  /// and parses it into a Tint AST module.  Diagnostics are emitted
-  /// to the error stream.
-  /// @returns true if it was successful.
-  bool BuildAndParseInternalModule() {
-    return impl_.BuildAndParseInternalModule();
-  }
-
-  /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
-  /// Also synthesizes struct field names.  Ensures uniqueness for names for
-  /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterUserAndStructMemberNames() {
-    return impl_.RegisterUserAndStructMemberNames();
-  }
-
-  /// Register Tint AST types for SPIR-V types, including type aliases as
-  /// needed.  This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterTypes() { return impl_.RegisterTypes(); }
-
-  /// Register sampler and texture usage for memory object declarations.
-  /// This must be called after we've registered line numbers for all
-  /// instructions. This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool RegisterHandleUsage() { return impl_.RegisterHandleUsage(); }
-
-  /// Emits module-scope variables.
-  /// This is a no-op if the parser has already failed.
-  /// @returns true if parser is still successful.
-  bool EmitModuleScopeVariables() { return impl_.EmitModuleScopeVariables(); }
-
-  /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450"
-  /// extended instruction set.
-  const std::unordered_set<uint32_t>& glsl_std_450_imports() const {
-    return impl_.glsl_std_450_imports();
-  }
-
-  /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup.
-  /// If the type is only used for builtins, then register that specially,
-  /// and return null.  If the type is a sampler, image, or sampled image, then
-  /// return the Void type, because those opaque types are handled in a
-  /// different way.
-  /// On failure, logs an error and returns null.  This should only be called
-  /// after the internal representation of the module has been built.
-  /// @param id the SPIR-V ID of a type.
-  /// @returns a Tint type, or nullptr
-  const Type* ConvertType(uint32_t id) { return impl_.ConvertType(id); }
-
-  /// Gets the list of decorations for a SPIR-V result ID.  Returns an empty
-  /// vector if the ID is not a result ID, or if no decorations target that ID.
-  /// The internal representation must have already been built.
-  /// @param id SPIR-V ID
-  /// @returns the list of decorations on the given ID
-  DecorationList GetDecorationsFor(uint32_t id) const {
-    return impl_.GetDecorationsFor(id);
-  }
-
-  /// Gets the list of decorations for the member of a struct.  Returns an empty
-  /// list if the `id` is not the ID of a struct, or if the member index is out
-  /// of range, or if the target member has no decorations.
-  /// The internal representation must have already been built.
-  /// @param id SPIR-V ID of a struct
-  /// @param member_index the member within the struct
-  /// @returns the list of decorations on the member
-  DecorationList GetDecorationsForMember(uint32_t id,
-                                         uint32_t member_index) const {
-    return impl_.GetDecorationsForMember(id, member_index);
-  }
-
-  /// Converts a SPIR-V struct member decoration into a number of AST
-  /// decorations. If the decoration is recognized but deliberately dropped,
-  /// then returns an empty list without a diagnostic. On failure, emits a
-  /// diagnostic and returns an empty list.
-  /// @param struct_type_id the ID of the struct type
-  /// @param member_index the index of the member
-  /// @param member_ty the type of the member
-  /// @param decoration an encoded SPIR-V Decoration
-  /// @returns the AST decorations
-  ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
-                                             uint32_t member_index,
-                                             const Type* member_ty,
-                                             const Decoration& decoration) {
-    return impl_.ConvertMemberDecoration(struct_type_id, member_index,
-                                         member_ty, decoration);
-  }
-
-  /// For a SPIR-V ID that might define a sampler, image, or sampled image
-  /// value, return the SPIR-V instruction that represents the memory object
-  /// declaration for the object.  If we encounter an OpSampledImage along the
-  /// way, follow the image operand when follow_image is true; otherwise follow
-  /// the sampler operand. Returns nullptr if we can't trace back to a memory
-  /// object declaration.  Emits an error and returns nullptr when the scan
-  /// fails due to a malformed module. This method can be used any time after
-  /// BuildInternalModule has been invoked.
-  /// @param id the SPIR-V ID of the sampler, image, or sampled image
-  /// @param follow_image indicates whether to follow the image operand of
-  /// OpSampledImage
-  /// @returns the memory object declaration for the handle, or nullptr
-  const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(
-      uint32_t id,
-      bool follow_image) {
-    return impl_.GetMemoryObjectDeclarationForHandle(id, follow_image);
-  }
-
-  /// @param entry_point the SPIR-V ID of an entry point.
-  /// @returns the entry point info for the given ID
-  const std::vector<EntryPointInfo>& GetEntryPointInfo(uint32_t entry_point) {
-    return impl_.GetEntryPointInfo(entry_point);
-  }
-
-  /// Returns the handle usage for a memory object declaration.
-  /// @param id SPIR-V ID of a sampler or image OpVariable or
-  /// OpFunctionParameter
-  /// @returns the handle usage, or an empty usage object.
-  Usage GetHandleUsage(uint32_t id) const { return impl_.GetHandleUsage(id); }
-
-  /// Returns the SPIR-V instruction with the given ID, or nullptr.
-  /// @param id the SPIR-V result ID
-  /// @returns the instruction, or nullptr on error
-  const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const {
-    return impl_.GetInstructionForTest(id);
-  }
-
-  /// @returns info about the gl_Position builtin variable.
-  const ParserImpl::BuiltInPositionInfo& GetBuiltInPositionInfo() {
-    return impl_.GetBuiltInPositionInfo();
-  }
-
-  /// Returns the source record for the SPIR-V instruction with the given
-  /// result ID.
-  /// @param id the SPIR-V result id.
-  /// @return the Source record, or a default one
-  Source GetSourceForResultIdForTest(uint32_t id) const {
-    return impl_.GetSourceForResultIdForTest(id);
-  }
-
- private:
-  ParserImpl impl_;
-  /// When true, indicates the input SPIR-V module should not be emitted.
-  /// It's either deliberately invalid, or not supported for some pending
-  /// reason.
-  bool skip_dumping_spirv_ = false;
-  static bool dump_successfully_converted_spirv_;
-};
-
-// Sets global state to force dumping of the assembly text of succesfully
-// SPIR-V.
-inline void DumpSuccessfullyConvertedSpirv() {
-  ParserImplWrapperForTest::DumpSuccessfullyConvertedSpirv();
-}
-
-/// Returns the WGSL printed string of a program.
-/// @param program the Program
-/// @returns the WGSL printed string the program.
-std::string ToString(const Program& program);
-
-/// Returns the WGSL printed string of a statement list.
-/// @param program the Program
-/// @param stmts the statement list
-/// @returns the WGSL printed string of a statement list.
-std::string ToString(const Program& program, const ast::StatementList& stmts);
-
-/// Returns the WGSL printed string of an AST node.
-/// @param program the Program
-/// @param node the AST node
-/// @returns the WGSL printed string of the AST node.
-std::string ToString(const Program& program, const ast::Node* node);
-
-}  // namespace test
-
-/// SPIR-V Parser test class
-template <typename T>
-class SpvParserTestBase : public T {
- public:
-  SpvParserTestBase() = default;
-  ~SpvParserTestBase() override = default;
-
-  /// Retrieves the parser from the helper
-  /// @param input the SPIR-V binary to parse
-  /// @returns a parser for the given binary
-  std::unique_ptr<test::ParserImplWrapperForTest> parser(
-      const std::vector<uint32_t>& input) {
-    auto parser = std::make_unique<test::ParserImplWrapperForTest>(input);
-
-    // Don't run the Resolver when building the program.
-    // We're not interested in type information with these tests.
-    parser->builder().SetResolveOnBuild(false);
-    return parser;
-  }
-};
-
-// Use this form when you don't need to template any further.
-using SpvParserTest = SpvParserTestBase<::testing::Test>;
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
diff --git a/src/reader/spirv/parser_impl_user_name_test.cc b/src/reader/spirv/parser_impl_user_name_test.cc
deleted file mode 100644
index 96ff4c9..0000000
--- a/src/reader/spirv/parser_impl_user_name_test.cc
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#include "src/reader/spirv/spirv_tools_helpers_test.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-using SpvParserUserNameTest = SpvParserTest;
-
-TEST_F(SpvParserUserNameTest, UserName_RespectOpName) {
-  auto p = parser(test::Assemble(R"(
-     OpName %1 "the_void_type"
-     %1 = OpTypeVoid
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetName(1), Eq("the_void_type"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_IgnoreEmptyName) {
-  auto p = parser(test::Assemble(R"(
-     OpName %1 ""
-     %1 = OpTypeVoid
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_FALSE(p->namer().HasName(1));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_DistinguishDuplicateSuggestion) {
-  auto p = parser(test::Assemble(R"(
-     OpName %1 "vanilla"
-     OpName %2 "vanilla"
-     %1 = OpTypeVoid
-     %2 = OpTypeInt 32 0
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetName(1), Eq("vanilla"));
-  EXPECT_THAT(p->namer().GetName(2), Eq("vanilla_1"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_RespectOpMemberName) {
-  auto p = parser(test::Assemble(R"(
-     OpMemberName %3 0 "strawberry"
-     OpMemberName %3 1 "vanilla"
-     OpMemberName %3 2 "chocolate"
-     %2 = OpTypeInt 32 0
-     %3 = OpTypeStruct %2 %2 %2
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("strawberry"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("vanilla"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("chocolate"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_IgnoreEmptyMemberName) {
-  auto p = parser(test::Assemble(R"(
-     OpMemberName %3 0 ""
-     %2 = OpTypeInt 32 0
-     %3 = OpTypeStruct %2
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_SynthesizeMemberNames) {
-  auto p = parser(test::Assemble(R"(
-     %2 = OpTypeInt 32 0
-     %3 = OpTypeStruct %2 %2 %2
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("field1"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("field2"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, UserName_MemberNamesMixUserAndSynthesized) {
-  auto p = parser(test::Assemble(R"(
-     OpMemberName %3 1 "vanilla"
-     %2 = OpTypeInt 32 0
-     %3 = OpTypeStruct %2 %2 %2
-  )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("vanilla"));
-  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("field2"));
-
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, EntryPointNames_AlwaysTakePrecedence) {
-  const std::string assembly = R"(
-   OpCapability Shader
-   OpMemoryModel Logical Simple
-   OpEntryPoint Vertex %100 "main"
-   OpEntryPoint Fragment %100 "main_1"
-   OpExecutionMode %100 OriginUpperLeft
-
-   ; attempt to grab the "main_1" that would be the derived name
-   ; for the second entry point.
-   OpName %1 "main_1"
-
-   %void = OpTypeVoid
-   %voidfn = OpTypeFunction %void
-   %uint = OpTypeInt 32 0
-   %uint_0 = OpConstant %uint 0
-
-   %100 = OpFunction %void None %voidfn
-   %100_entry = OpLabel
-   %1 = OpCopyObject %uint %uint_0
-   OpReturn
-   OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  // The first entry point grabs the best name, "main"
-  EXPECT_THAT(p->namer().Name(100), Eq("main"));
-  // The OpName on %1 is overriden because the second entry point
-  // has grabbed "main_1" first.
-  EXPECT_THAT(p->namer().Name(1), Eq("main_1_1"));
-
-  const auto& ep_info = p->GetEntryPointInfo(100);
-  ASSERT_EQ(2u, ep_info.size());
-  EXPECT_EQ(ep_info[0].name, "main");
-  EXPECT_EQ(ep_info[1].name, "main_1");
-
-  // This test checks two entry point with the same implementation function.
-  // But for the shader stages supported by WGSL, the SPIR-V rules require
-  // conflicting execution modes be applied to them.
-  // I still want to test the name disambiguation behaviour, but the cases
-  // are rejected by SPIR-V validation. This is true at least for the current
-  // WGSL feature set.
-  p->DeliberatelyInvalidSpirv();
-}
-
-TEST_F(SpvParserUserNameTest, EntryPointNames_DistinctFromInnerNames) {
-  const std::string assembly = R"(
-   OpCapability Shader
-   OpMemoryModel Logical Simple
-   OpEntryPoint Vertex %100 "main"
-   OpEntryPoint Fragment %100 "main_1"
-   OpExecutionMode %100 OriginUpperLeft
-
-   ; attempt to grab the "main_1" that would be the derived name
-   ; for the second entry point.
-   OpName %1 "main_1"
-
-   %void = OpTypeVoid
-   %voidfn = OpTypeFunction %void
-   %uint = OpTypeInt 32 0
-   %uint_0 = OpConstant %uint 0
-
-   %100 = OpFunction %void None %voidfn
-   %100_entry = OpLabel
-   %1 = OpCopyObject %uint %uint_0
-   OpReturn
-   OpFunctionEnd
-)";
-  auto p = parser(test::Assemble(assembly));
-
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
-  // The first entry point grabs the best name, "main"
-  EXPECT_THAT(p->namer().Name(100), Eq("main"));
-  EXPECT_THAT(p->namer().Name(1), Eq("main_1_1"));
-
-  const auto ep_info = p->GetEntryPointInfo(100);
-  ASSERT_EQ(2u, ep_info.size());
-  EXPECT_EQ(ep_info[0].name, "main");
-  EXPECT_EQ(ep_info[0].inner_name, "main_2");
-  // The second entry point retains its name...
-  EXPECT_EQ(ep_info[1].name, "main_1");
-  // ...but will use the same implementation function.
-  EXPECT_EQ(ep_info[1].inner_name, "main_2");
-
-  // This test checks two entry point with the same implementation function.
-  // But for the shader stages supported by WGSL, the SPIR-V rules require
-  // conflicting execution modes be applied to them.
-  // I still want to test the name disambiguation behaviour, but the cases
-  // are rejected by SPIR-V validation. This is true at least for the current
-  // WGSL feature set.
-  p->DeliberatelyInvalidSpirv();
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_test.cc b/src/reader/spirv/parser_test.cc
deleted file mode 100644
index cb7c49a..0000000
--- a/src/reader/spirv/parser_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/reader/spirv/parser.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ParserTest = testing::Test;
-
-TEST_F(ParserTest, DataEmpty) {
-  std::vector<uint32_t> data;
-  auto program = Parse(data);
-  auto errs = diag::Formatter().format(program.Diagnostics());
-  ASSERT_FALSE(program.IsValid()) << errs;
-  EXPECT_EQ(errs, "error: line:0: Invalid SPIR-V magic number.\n");
-}
-
-// TODO(dneto): uint32 vec, valid SPIR-V
-// TODO(dneto): uint32 vec, invalid SPIR-V
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_type.cc b/src/reader/spirv/parser_type.cc
deleted file mode 100644
index 846ad69..0000000
--- a/src/reader/spirv/parser_type.cc
+++ /dev/null
@@ -1,645 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or stateied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/spirv/parser_type.h"
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Type);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Void);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Bool);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::U32);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::F32);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::I32);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Pointer);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Reference);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Vector);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Matrix);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Array);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Sampler);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Texture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::DepthTexture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::DepthMultisampledTexture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::MultisampledTexture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::SampledTexture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::StorageTexture);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Named);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Alias);
-TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Struct);
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-namespace {
-struct PointerHasher {
-  size_t operator()(const Pointer& t) const {
-    return utils::Hash(t.type, t.storage_class);
-  }
-};
-
-struct ReferenceHasher {
-  size_t operator()(const Reference& t) const {
-    return utils::Hash(t.type, t.storage_class);
-  }
-};
-
-struct VectorHasher {
-  size_t operator()(const Vector& t) const {
-    return utils::Hash(t.type, t.size);
-  }
-};
-
-struct MatrixHasher {
-  size_t operator()(const Matrix& t) const {
-    return utils::Hash(t.type, t.columns, t.rows);
-  }
-};
-
-struct ArrayHasher {
-  size_t operator()(const Array& t) const {
-    return utils::Hash(t.type, t.size, t.stride);
-  }
-};
-
-struct MultisampledTextureHasher {
-  size_t operator()(const MultisampledTexture& t) const {
-    return utils::Hash(t.dims, t.type);
-  }
-};
-
-struct SampledTextureHasher {
-  size_t operator()(const SampledTexture& t) const {
-    return utils::Hash(t.dims, t.type);
-  }
-};
-
-struct StorageTextureHasher {
-  size_t operator()(const StorageTexture& t) const {
-    return utils::Hash(t.dims, t.format, t.access);
-  }
-};
-}  // namespace
-
-static bool operator==(const Pointer& a, const Pointer& b) {
-  return a.type == b.type && a.storage_class == b.storage_class;
-}
-
-static bool operator==(const Reference& a, const Reference& b) {
-  return a.type == b.type && a.storage_class == b.storage_class;
-}
-
-static bool operator==(const Vector& a, const Vector& b) {
-  return a.type == b.type && a.size == b.size;
-}
-
-static bool operator==(const Matrix& a, const Matrix& b) {
-  return a.type == b.type && a.columns == b.columns && a.rows == b.rows;
-}
-
-static bool operator==(const Array& a, const Array& b) {
-  return a.type == b.type && a.size == b.size && a.stride == b.stride;
-}
-
-static bool operator==(const MultisampledTexture& a,
-                       const MultisampledTexture& b) {
-  return a.dims == b.dims && a.type == b.type;
-}
-
-static bool operator==(const SampledTexture& a, const SampledTexture& b) {
-  return a.dims == b.dims && a.type == b.type;
-}
-
-static bool operator==(const StorageTexture& a, const StorageTexture& b) {
-  return a.dims == b.dims && a.format == b.format;
-}
-
-const ast::Type* Void::Build(ProgramBuilder& b) const {
-  return b.ty.void_();
-}
-
-const ast::Type* Bool::Build(ProgramBuilder& b) const {
-  return b.ty.bool_();
-}
-
-const ast::Type* U32::Build(ProgramBuilder& b) const {
-  return b.ty.u32();
-}
-
-const ast::Type* F32::Build(ProgramBuilder& b) const {
-  return b.ty.f32();
-}
-
-const ast::Type* I32::Build(ProgramBuilder& b) const {
-  return b.ty.i32();
-}
-
-Pointer::Pointer(const Type* t, ast::StorageClass s)
-    : type(t), storage_class(s) {}
-Pointer::Pointer(const Pointer&) = default;
-
-const ast::Type* Pointer::Build(ProgramBuilder& b) const {
-  return b.ty.pointer(type->Build(b), storage_class);
-}
-
-Reference::Reference(const Type* t, ast::StorageClass s)
-    : type(t), storage_class(s) {}
-Reference::Reference(const Reference&) = default;
-
-const ast::Type* Reference::Build(ProgramBuilder& b) const {
-  return type->Build(b);
-}
-
-Vector::Vector(const Type* t, uint32_t s) : type(t), size(s) {}
-Vector::Vector(const Vector&) = default;
-
-const ast::Type* Vector::Build(ProgramBuilder& b) const {
-  return b.ty.vec(type->Build(b), size);
-}
-
-Matrix::Matrix(const Type* t, uint32_t c, uint32_t r)
-    : type(t), columns(c), rows(r) {}
-Matrix::Matrix(const Matrix&) = default;
-
-const ast::Type* Matrix::Build(ProgramBuilder& b) const {
-  return b.ty.mat(type->Build(b), columns, rows);
-}
-
-Array::Array(const Type* t, uint32_t sz, uint32_t st)
-    : type(t), size(sz), stride(st) {}
-Array::Array(const Array&) = default;
-
-const ast::Type* Array::Build(ProgramBuilder& b) const {
-  if (size > 0) {
-    return b.ty.array(type->Build(b), size, stride);
-  } else {
-    return b.ty.array(type->Build(b), nullptr, stride);
-  }
-}
-
-Sampler::Sampler(ast::SamplerKind k) : kind(k) {}
-Sampler::Sampler(const Sampler&) = default;
-
-const ast::Type* Sampler::Build(ProgramBuilder& b) const {
-  return b.ty.sampler(kind);
-}
-
-Texture::Texture(ast::TextureDimension d) : dims(d) {}
-Texture::Texture(const Texture&) = default;
-
-DepthTexture::DepthTexture(ast::TextureDimension d) : Base(d) {}
-DepthTexture::DepthTexture(const DepthTexture&) = default;
-
-const ast::Type* DepthTexture::Build(ProgramBuilder& b) const {
-  return b.ty.depth_texture(dims);
-}
-
-DepthMultisampledTexture::DepthMultisampledTexture(ast::TextureDimension d)
-    : Base(d) {}
-DepthMultisampledTexture::DepthMultisampledTexture(
-    const DepthMultisampledTexture&) = default;
-
-const ast::Type* DepthMultisampledTexture::Build(ProgramBuilder& b) const {
-  return b.ty.depth_multisampled_texture(dims);
-}
-
-MultisampledTexture::MultisampledTexture(ast::TextureDimension d, const Type* t)
-    : Base(d), type(t) {}
-MultisampledTexture::MultisampledTexture(const MultisampledTexture&) = default;
-
-const ast::Type* MultisampledTexture::Build(ProgramBuilder& b) const {
-  return b.ty.multisampled_texture(dims, type->Build(b));
-}
-
-SampledTexture::SampledTexture(ast::TextureDimension d, const Type* t)
-    : Base(d), type(t) {}
-SampledTexture::SampledTexture(const SampledTexture&) = default;
-
-const ast::Type* SampledTexture::Build(ProgramBuilder& b) const {
-  return b.ty.sampled_texture(dims, type->Build(b));
-}
-
-StorageTexture::StorageTexture(ast::TextureDimension d,
-                               ast::TexelFormat f,
-                               ast::Access a)
-    : Base(d), format(f), access(a) {}
-StorageTexture::StorageTexture(const StorageTexture&) = default;
-
-const ast::Type* StorageTexture::Build(ProgramBuilder& b) const {
-  return b.ty.storage_texture(dims, format, access);
-}
-
-Named::Named(Symbol n) : name(n) {}
-Named::Named(const Named&) = default;
-Named::~Named() = default;
-
-Alias::Alias(Symbol n, const Type* ty) : Base(n), type(ty) {}
-Alias::Alias(const Alias&) = default;
-
-const ast::Type* Alias::Build(ProgramBuilder& b) const {
-  return b.ty.type_name(name);
-}
-
-Struct::Struct(Symbol n, TypeList m) : Base(n), members(std::move(m)) {}
-Struct::Struct(const Struct&) = default;
-Struct::~Struct() = default;
-
-const ast::Type* Struct::Build(ProgramBuilder& b) const {
-  return b.ty.type_name(name);
-}
-
-/// The PIMPL state of the Types object.
-struct TypeManager::State {
-  /// The allocator of types
-  BlockAllocator<Type> allocator_;
-  /// The lazily-created Void type
-  spirv::Void const* void_ = nullptr;
-  /// The lazily-created Bool type
-  spirv::Bool const* bool_ = nullptr;
-  /// The lazily-created U32 type
-  spirv::U32 const* u32_ = nullptr;
-  /// The lazily-created F32 type
-  spirv::F32 const* f32_ = nullptr;
-  /// The lazily-created I32 type
-  spirv::I32 const* i32_ = nullptr;
-  /// Map of Pointer to the returned Pointer type instance
-  std::unordered_map<spirv::Pointer, const spirv::Pointer*, PointerHasher>
-      pointers_;
-  /// Map of Reference to the returned Reference type instance
-  std::unordered_map<spirv::Reference, const spirv::Reference*, ReferenceHasher>
-      references_;
-  /// Map of Vector to the returned Vector type instance
-  std::unordered_map<spirv::Vector, const spirv::Vector*, VectorHasher>
-      vectors_;
-  /// Map of Matrix to the returned Matrix type instance
-  std::unordered_map<spirv::Matrix, const spirv::Matrix*, MatrixHasher>
-      matrices_;
-  /// Map of Array to the returned Array type instance
-  std::unordered_map<spirv::Array, const spirv::Array*, ArrayHasher> arrays_;
-  /// Map of type name to returned Alias instance
-  std::unordered_map<Symbol, const spirv::Alias*> aliases_;
-  /// Map of type name to returned Struct instance
-  std::unordered_map<Symbol, const spirv::Struct*> structs_;
-  /// Map of ast::SamplerKind to returned Sampler instance
-  std::unordered_map<ast::SamplerKind, const spirv::Sampler*> samplers_;
-  /// Map of ast::TextureDimension to returned DepthTexture instance
-  std::unordered_map<ast::TextureDimension, const spirv::DepthTexture*>
-      depth_textures_;
-  /// Map of ast::TextureDimension to returned DepthMultisampledTexture instance
-  std::unordered_map<ast::TextureDimension,
-                     const spirv::DepthMultisampledTexture*>
-      depth_multisampled_textures_;
-  /// Map of MultisampledTexture to the returned MultisampledTexture type
-  /// instance
-  std::unordered_map<spirv::MultisampledTexture,
-                     const spirv::MultisampledTexture*,
-                     MultisampledTextureHasher>
-      multisampled_textures_;
-  /// Map of SampledTexture to the returned SampledTexture type instance
-  std::unordered_map<spirv::SampledTexture,
-                     const spirv::SampledTexture*,
-                     SampledTextureHasher>
-      sampled_textures_;
-  /// Map of StorageTexture to the returned StorageTexture type instance
-  std::unordered_map<spirv::StorageTexture,
-                     const spirv::StorageTexture*,
-                     StorageTextureHasher>
-      storage_textures_;
-};
-
-const Type* Type::UnwrapPtr() const {
-  const Type* type = this;
-  while (auto* ptr = type->As<Pointer>()) {
-    type = ptr->type;
-  }
-  return type;
-}
-
-const Type* Type::UnwrapRef() const {
-  const Type* type = this;
-  while (auto* ptr = type->As<Reference>()) {
-    type = ptr->type;
-  }
-  return type;
-}
-
-const Type* Type::UnwrapAlias() const {
-  const Type* type = this;
-  while (auto* alias = type->As<Alias>()) {
-    type = alias->type;
-  }
-  return type;
-}
-
-const Type* Type::UnwrapAll() const {
-  auto* type = this;
-  while (true) {
-    if (auto* alias = type->As<Alias>()) {
-      type = alias->type;
-    } else if (auto* ptr = type->As<Pointer>()) {
-      type = ptr->type;
-    } else {
-      break;
-    }
-  }
-  return type;
-}
-
-bool Type::IsFloatScalar() const {
-  return Is<F32>();
-}
-
-bool Type::IsFloatScalarOrVector() const {
-  return IsFloatScalar() || IsFloatVector();
-}
-
-bool Type::IsFloatVector() const {
-  return Is([](const Vector* v) { return v->type->IsFloatScalar(); });
-}
-
-bool Type::IsIntegerScalar() const {
-  return IsAnyOf<U32, I32>();
-}
-
-bool Type::IsIntegerScalarOrVector() const {
-  return IsUnsignedScalarOrVector() || IsSignedScalarOrVector();
-}
-
-bool Type::IsScalar() const {
-  return IsAnyOf<F32, U32, I32, Bool>();
-}
-
-bool Type::IsSignedIntegerVector() const {
-  return Is([](const Vector* v) { return v->type->Is<I32>(); });
-}
-
-bool Type::IsSignedScalarOrVector() const {
-  return Is<I32>() || IsSignedIntegerVector();
-}
-
-bool Type::IsUnsignedIntegerVector() const {
-  return Is([](const Vector* v) { return v->type->Is<U32>(); });
-}
-
-bool Type::IsUnsignedScalarOrVector() const {
-  return Is<U32>() || IsUnsignedIntegerVector();
-}
-
-TypeManager::TypeManager() {
-  state = std::make_unique<State>();
-}
-
-TypeManager::~TypeManager() = default;
-
-const spirv::Void* TypeManager::Void() {
-  if (!state->void_) {
-    state->void_ = state->allocator_.Create<spirv::Void>();
-  }
-  return state->void_;
-}
-
-const spirv::Bool* TypeManager::Bool() {
-  if (!state->bool_) {
-    state->bool_ = state->allocator_.Create<spirv::Bool>();
-  }
-  return state->bool_;
-}
-
-const spirv::U32* TypeManager::U32() {
-  if (!state->u32_) {
-    state->u32_ = state->allocator_.Create<spirv::U32>();
-  }
-  return state->u32_;
-}
-
-const spirv::F32* TypeManager::F32() {
-  if (!state->f32_) {
-    state->f32_ = state->allocator_.Create<spirv::F32>();
-  }
-  return state->f32_;
-}
-
-const spirv::I32* TypeManager::I32() {
-  if (!state->i32_) {
-    state->i32_ = state->allocator_.Create<spirv::I32>();
-  }
-  return state->i32_;
-}
-
-const spirv::Pointer* TypeManager::Pointer(const Type* el,
-                                           ast::StorageClass sc) {
-  return utils::GetOrCreate(state->pointers_, spirv::Pointer(el, sc), [&] {
-    return state->allocator_.Create<spirv::Pointer>(el, sc);
-  });
-}
-
-const spirv::Reference* TypeManager::Reference(const Type* el,
-                                               ast::StorageClass sc) {
-  return utils::GetOrCreate(state->references_, spirv::Reference(el, sc), [&] {
-    return state->allocator_.Create<spirv::Reference>(el, sc);
-  });
-}
-
-const spirv::Vector* TypeManager::Vector(const Type* el, uint32_t size) {
-  return utils::GetOrCreate(state->vectors_, spirv::Vector(el, size), [&] {
-    return state->allocator_.Create<spirv::Vector>(el, size);
-  });
-}
-
-const spirv::Matrix* TypeManager::Matrix(const Type* el,
-                                         uint32_t columns,
-                                         uint32_t rows) {
-  return utils::GetOrCreate(
-      state->matrices_, spirv::Matrix(el, columns, rows), [&] {
-        return state->allocator_.Create<spirv::Matrix>(el, columns, rows);
-      });
-}
-
-const spirv::Array* TypeManager::Array(const Type* el,
-                                       uint32_t size,
-                                       uint32_t stride) {
-  return utils::GetOrCreate(
-      state->arrays_, spirv::Array(el, size, stride),
-      [&] { return state->allocator_.Create<spirv::Array>(el, size, stride); });
-}
-
-const spirv::Alias* TypeManager::Alias(Symbol name, const Type* ty) {
-  return utils::GetOrCreate(state->aliases_, name, [&] {
-    return state->allocator_.Create<spirv::Alias>(name, ty);
-  });
-}
-
-const spirv::Struct* TypeManager::Struct(Symbol name, TypeList members) {
-  return utils::GetOrCreate(state->structs_, name, [&] {
-    return state->allocator_.Create<spirv::Struct>(name, std::move(members));
-  });
-}
-
-const spirv::Sampler* TypeManager::Sampler(ast::SamplerKind kind) {
-  return utils::GetOrCreate(state->samplers_, kind, [&] {
-    return state->allocator_.Create<spirv::Sampler>(kind);
-  });
-}
-
-const spirv::DepthTexture* TypeManager::DepthTexture(
-    ast::TextureDimension dims) {
-  return utils::GetOrCreate(state->depth_textures_, dims, [&] {
-    return state->allocator_.Create<spirv::DepthTexture>(dims);
-  });
-}
-
-const spirv::DepthMultisampledTexture* TypeManager::DepthMultisampledTexture(
-    ast::TextureDimension dims) {
-  return utils::GetOrCreate(state->depth_multisampled_textures_, dims, [&] {
-    return state->allocator_.Create<spirv::DepthMultisampledTexture>(dims);
-  });
-}
-
-const spirv::MultisampledTexture* TypeManager::MultisampledTexture(
-    ast::TextureDimension dims,
-    const Type* ty) {
-  return utils::GetOrCreate(
-      state->multisampled_textures_, spirv::MultisampledTexture(dims, ty), [&] {
-        return state->allocator_.Create<spirv::MultisampledTexture>(dims, ty);
-      });
-}
-
-const spirv::SampledTexture* TypeManager::SampledTexture(
-    ast::TextureDimension dims,
-    const Type* ty) {
-  return utils::GetOrCreate(
-      state->sampled_textures_, spirv::SampledTexture(dims, ty), [&] {
-        return state->allocator_.Create<spirv::SampledTexture>(dims, ty);
-      });
-}
-
-const spirv::StorageTexture* TypeManager::StorageTexture(
-    ast::TextureDimension dims,
-    ast::TexelFormat fmt,
-    ast::Access access) {
-  return utils::GetOrCreate(
-      state->storage_textures_, spirv::StorageTexture(dims, fmt, access), [&] {
-        return state->allocator_.Create<spirv::StorageTexture>(dims, fmt,
-                                                               access);
-      });
-}
-
-// Debug String() methods for Type classes. Only enabled in debug builds.
-#ifndef NDEBUG
-std::string Void::String() const {
-  return "void";
-}
-
-std::string Bool::String() const {
-  return "bool";
-}
-
-std::string U32::String() const {
-  return "u32";
-}
-
-std::string F32::String() const {
-  return "f32";
-}
-
-std::string I32::String() const {
-  return "i32";
-}
-
-std::string Pointer::String() const {
-  std::stringstream ss;
-  ss << "ptr<" << std::string(ast::ToString(storage_class)) << ", "
-     << type->String() + ">";
-  return ss.str();
-}
-
-std::string Reference::String() const {
-  std::stringstream ss;
-  ss << "ref<" + std::string(ast::ToString(storage_class)) << ", "
-     << type->String() << ">";
-  return ss.str();
-}
-
-std::string Vector::String() const {
-  std::stringstream ss;
-  ss << "vec" << size << "<" << type->String() << ">";
-  return ss.str();
-}
-
-std::string Matrix::String() const {
-  std::stringstream ss;
-  ss << "mat" << columns << "x" << rows << "<" << type->String() << ">";
-  return ss.str();
-}
-
-std::string Array::String() const {
-  std::stringstream ss;
-  ss << "array<" << type->String() << ", " << size << ", " << stride << ">";
-  return ss.str();
-}
-
-std::string Sampler::String() const {
-  switch (kind) {
-    case ast::SamplerKind::kSampler:
-      return "sampler";
-    case ast::SamplerKind::kComparisonSampler:
-      return "sampler_comparison";
-  }
-  return "<unknown sampler>";
-}
-
-std::string DepthTexture::String() const {
-  std::stringstream ss;
-  ss << "depth_" << dims;
-  return ss.str();
-}
-
-std::string DepthMultisampledTexture::String() const {
-  std::stringstream ss;
-  ss << "depth_multisampled_" << dims;
-  return ss.str();
-}
-
-std::string MultisampledTexture::String() const {
-  std::stringstream ss;
-  ss << "texture_multisampled_" << dims << "<" << type << ">";
-  return ss.str();
-}
-
-std::string SampledTexture::String() const {
-  std::stringstream ss;
-  ss << "texture_" << dims << "<" << type << ">";
-  return ss.str();
-}
-
-std::string StorageTexture::String() const {
-  std::stringstream ss;
-  ss << "texture_storage_" << dims << "<" << format << ", " << access << ">";
-  return ss.str();
-}
-
-std::string Named::String() const {
-  return name.to_str();
-}
-#endif  // NDEBUG
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/parser_type.h b/src/reader/spirv/parser_type.h
deleted file mode 100644
index 9317dde..0000000
--- a/src/reader/spirv/parser_type.h
+++ /dev/null
@@ -1,610 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_READER_SPIRV_PARSER_TYPE_H_
-#define SRC_READER_SPIRV_PARSER_TYPE_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/ast/sampler.h"
-#include "src/ast/storage_class.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/texture.h"
-#include "src/block_allocator.h"
-#include "src/castable.h"
-
-// Forward declarations
-namespace tint {
-class ProgramBuilder;
-namespace ast {
-class Type;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// Type is the base class for all types
-class Type : public Castable<Type> {
- public:
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  virtual const ast::Type* Build(ProgramBuilder& b) const = 0;
-
-  /// @returns the inner most store type if this is a pointer, `this` otherwise
-  const Type* UnwrapPtr() const;
-
-  /// @returns the inner most store type if this is a reference, `this`
-  /// otherwise
-  const Type* UnwrapRef() const;
-
-  /// @returns the inner most aliased type if this is an alias, `this` otherwise
-  const Type* UnwrapAlias() const;
-
-  /// @returns the type with all aliasing, access control and pointers removed
-  const Type* UnwrapAll() const;
-
-  /// @returns true if this type is a float scalar
-  bool IsFloatScalar() const;
-  /// @returns true if this type is a float scalar or vector
-  bool IsFloatScalarOrVector() const;
-  /// @returns true if this type is a float vector
-  bool IsFloatVector() const;
-  /// @returns true if this type is an integer scalar
-  bool IsIntegerScalar() const;
-  /// @returns true if this type is an integer scalar or vector
-  bool IsIntegerScalarOrVector() const;
-  /// @returns true if this type is a scalar
-  bool IsScalar() const;
-  /// @returns true if this type is a signed integer vector
-  bool IsSignedIntegerVector() const;
-  /// @returns true if this type is a signed scalar or vector
-  bool IsSignedScalarOrVector() const;
-  /// @returns true if this type is an unsigned integer vector
-  bool IsUnsignedIntegerVector() const;
-  /// @returns true if this type is an unsigned scalar or vector
-  bool IsUnsignedScalarOrVector() const;
-
-#ifdef NDEBUG
-  /// @returns "<no-type-info>", for debug purposes only
-  std::string String() const { return "<no-type-info>"; }
-#else
-  /// @returns a string representation of the type, for debug purposes only
-  virtual std::string String() const = 0;
-#endif  // NDEBUG
-};
-
-using TypeList = std::vector<const Type*>;
-
-/// `void` type
-struct Void : public Castable<Void, Type> {
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `bool` type
-struct Bool : public Castable<Bool, Type> {
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `u32` type
-struct U32 : public Castable<U32, Type> {
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `f32` type
-struct F32 : public Castable<F32, Type> {
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `i32` type
-struct I32 : public Castable<I32, Type> {
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `ptr<SC, T>` type
-struct Pointer : public Castable<Pointer, Type> {
-  /// Constructor
-  /// @param ty the store type
-  /// @param sc the pointer storage class
-  Pointer(const Type* ty, ast::StorageClass sc);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Pointer(const Pointer& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the store type
-  Type const* const type;
-  /// the pointer storage class
-  ast::StorageClass const storage_class;
-};
-
-/// `ref<SC, T>` type
-/// Note this has no AST representation, but is used for type tracking in the
-/// reader.
-struct Reference : public Castable<Reference, Type> {
-  /// Constructor
-  /// @param ty the referenced type
-  /// @param sc the reference storage class
-  Reference(const Type* ty, ast::StorageClass sc);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Reference(const Reference& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the store type
-  Type const* const type;
-  /// the pointer storage class
-  ast::StorageClass const storage_class;
-};
-
-/// `vecN<T>` type
-struct Vector : public Castable<Vector, Type> {
-  /// Constructor
-  /// @param ty the element type
-  /// @param sz the number of elements in the vector
-  Vector(const Type* ty, uint32_t sz);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Vector(const Vector& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the element type
-  Type const* const type;
-  /// the number of elements in the vector
-  const uint32_t size;
-};
-
-/// `matNxM<T>` type
-struct Matrix : public Castable<Matrix, Type> {
-  /// Constructor
-  /// @param ty the matrix element type
-  /// @param c the number of columns in the matrix
-  /// @param r the number of rows in the matrix
-  Matrix(const Type* ty, uint32_t c, uint32_t r);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Matrix(const Matrix& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the matrix element type
-  Type const* const type;
-  /// the number of columns in the matrix
-  const uint32_t columns;
-  /// the number of rows in the matrix
-  const uint32_t rows;
-};
-
-/// `array<T, N>` type
-struct Array : public Castable<Array, Type> {
-  /// Constructor
-  /// @param el the element type
-  /// @param sz the number of elements in the array. 0 represents runtime-sized
-  /// array.
-  /// @param st the byte stride of the array. 0 means use implicit stride.
-  Array(const Type* el, uint32_t sz, uint32_t st);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Array(const Array& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the element type
-  Type const* const type;
-  /// the number of elements in the array. 0 represents runtime-sized array.
-  const uint32_t size;
-  /// the byte stride of the array
-  const uint32_t stride;
-};
-
-/// `sampler` type
-struct Sampler : public Castable<Sampler, Type> {
-  /// Constructor
-  /// @param k the sampler kind
-  explicit Sampler(ast::SamplerKind k);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Sampler(const Sampler& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the sampler kind
-  ast::SamplerKind const kind;
-};
-
-/// Base class for texture types
-struct Texture : public Castable<Texture, Type> {
-  /// Constructor
-  /// @param d the texture dimensions
-  explicit Texture(ast::TextureDimension d);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Texture(const Texture& other);
-
-  /// the texture dimensions
-  ast::TextureDimension const dims;
-};
-
-/// `texture_depth_D` type
-struct DepthTexture : public Castable<DepthTexture, Texture> {
-  /// Constructor
-  /// @param d the texture dimensions
-  explicit DepthTexture(ast::TextureDimension d);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  DepthTexture(const DepthTexture& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `texture_depth_multisampled_D` type
-struct DepthMultisampledTexture
-    : public Castable<DepthMultisampledTexture, Texture> {
-  /// Constructor
-  /// @param d the texture dimensions
-  explicit DepthMultisampledTexture(ast::TextureDimension d);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  DepthMultisampledTexture(const DepthMultisampledTexture& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-};
-
-/// `texture_multisampled_D<T>` type
-struct MultisampledTexture : public Castable<MultisampledTexture, Texture> {
-  /// Constructor
-  /// @param d the texture dimensions
-  /// @param t the multisampled texture type
-  MultisampledTexture(ast::TextureDimension d, const Type* t);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  MultisampledTexture(const MultisampledTexture& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the multisampled texture type
-  Type const* const type;
-};
-
-/// `texture_D<T>` type
-struct SampledTexture : public Castable<SampledTexture, Texture> {
-  /// Constructor
-  /// @param d the texture dimensions
-  /// @param t the sampled texture type
-  SampledTexture(ast::TextureDimension d, const Type* t);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  SampledTexture(const SampledTexture& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the sampled texture type
-  Type const* const type;
-};
-
-/// `texture_storage_D<F>` type
-struct StorageTexture : public Castable<StorageTexture, Texture> {
-  /// Constructor
-  /// @param d the texture dimensions
-  /// @param f the storage image format
-  /// @param a the access control
-  StorageTexture(ast::TextureDimension d, ast::TexelFormat f, ast::Access a);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  StorageTexture(const StorageTexture& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the storage image format
-  ast::TexelFormat const format;
-
-  /// the access control
-  ast::Access const access;
-};
-
-/// Base class for named types
-struct Named : public Castable<Named, Type> {
-  /// Constructor
-  /// @param n the type name
-  explicit Named(Symbol n);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Named(const Named& other);
-
-  /// Destructor
-  ~Named() override;
-
-#ifndef NDEBUG
-  /// @returns a string representation of the type, for debug purposes only
-  std::string String() const override;
-#endif  // NDEBUG
-
-  /// the type name
-  const Symbol name;
-};
-
-/// `type T = N` type
-struct Alias : public Castable<Alias, Named> {
-  /// Constructor
-  /// @param n the alias name
-  /// @param t the aliased type
-  Alias(Symbol n, const Type* t);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Alias(const Alias& other);
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-  /// the aliased type
-  Type const* const type;
-};
-
-/// `struct N { ... };` type
-struct Struct : public Castable<Struct, Named> {
-  /// Constructor
-  /// @param n the struct name
-  /// @param m the member types
-  Struct(Symbol n, TypeList m);
-
-  /// Copy constructor
-  /// @param other the other type to copy
-  Struct(const Struct& other);
-
-  /// Destructor
-  ~Struct() override;
-
-  /// @param b the ProgramBuilder used to construct the AST types
-  /// @returns the constructed ast::Type node for the given type
-  const ast::Type* Build(ProgramBuilder& b) const override;
-
-  /// the member types
-  const TypeList members;
-};
-
-/// A manager of types
-class TypeManager {
- public:
-  /// Constructor
-  TypeManager();
-
-  /// Destructor
-  ~TypeManager();
-
-  /// @return a Void type. Repeated calls will return the same pointer.
-  const spirv::Void* Void();
-  /// @return a Bool type. Repeated calls will return the same pointer.
-  const spirv::Bool* Bool();
-  /// @return a U32 type. Repeated calls will return the same pointer.
-  const spirv::U32* U32();
-  /// @return a F32 type. Repeated calls will return the same pointer.
-  const spirv::F32* F32();
-  /// @return a I32 type. Repeated calls will return the same pointer.
-  const spirv::I32* I32();
-  /// @param ty the store type
-  /// @param sc the pointer storage class
-  /// @return a Pointer type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Pointer* Pointer(const Type* ty, ast::StorageClass sc);
-  /// @param ty the referenced type
-  /// @param sc the reference storage class
-  /// @return a Reference type. Repeated calls with the same arguments will
-  /// return the same pointer.
-  const spirv::Reference* Reference(const Type* ty, ast::StorageClass sc);
-  /// @param ty the element type
-  /// @param sz the number of elements in the vector
-  /// @return a Vector type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Vector* Vector(const Type* ty, uint32_t sz);
-  /// @param ty the matrix element type
-  /// @param c the number of columns in the matrix
-  /// @param r the number of rows in the matrix
-  /// @return a Matrix type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Matrix* Matrix(const Type* ty, uint32_t c, uint32_t r);
-  /// @param el the element type
-  /// @param sz the number of elements in the array. 0 represents runtime-sized
-  /// array.
-  /// @param st the byte stride of the array
-  /// @return a Array type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Array* Array(const Type* el, uint32_t sz, uint32_t st);
-  /// @param n the alias name
-  /// @param t the aliased type
-  /// @return a Alias type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Alias* Alias(Symbol n, const Type* t);
-  /// @param n the struct name
-  /// @param m the member types
-  /// @return a Struct type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Struct* Struct(Symbol n, TypeList m);
-  /// @param k the sampler kind
-  /// @return a Sampler type. Repeated calls with the same arguments will return
-  /// the same pointer.
-  const spirv::Sampler* Sampler(ast::SamplerKind k);
-  /// @param d the texture dimensions
-  /// @return a DepthTexture type. Repeated calls with the same arguments will
-  /// return the same pointer.
-  const spirv::DepthTexture* DepthTexture(ast::TextureDimension d);
-  /// @param d the texture dimensions
-  /// @return a DepthMultisampledTexture type. Repeated calls with the same
-  /// arguments will return the same pointer.
-  const spirv::DepthMultisampledTexture* DepthMultisampledTexture(
-      ast::TextureDimension d);
-  /// @param d the texture dimensions
-  /// @param t the multisampled texture type
-  /// @return a MultisampledTexture type. Repeated calls with the same arguments
-  /// will return the same pointer.
-  const spirv::MultisampledTexture* MultisampledTexture(ast::TextureDimension d,
-                                                        const Type* t);
-  /// @param d the texture dimensions
-  /// @param t the sampled texture type
-  /// @return a SampledTexture type. Repeated calls with the same arguments will
-  /// return the same pointer.
-  const spirv::SampledTexture* SampledTexture(ast::TextureDimension d,
-                                              const Type* t);
-  /// @param d the texture dimensions
-  /// @param f the storage image format
-  /// @param a the access control
-  /// @return a StorageTexture type. Repeated calls with the same arguments will
-  /// return the same pointer.
-  const spirv::StorageTexture* StorageTexture(ast::TextureDimension d,
-                                              ast::TexelFormat f,
-                                              ast::Access a);
-
- private:
-  struct State;
-  std::unique_ptr<State> state;
-};
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_PARSER_TYPE_H_
diff --git a/src/reader/spirv/parser_type_test.cc b/src/reader/spirv/parser_type_test.cc
deleted file mode 100644
index dde9a01..0000000
--- a/src/reader/spirv/parser_type_test.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2020 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 "gtest/gtest.h"
-
-#include "src/reader/spirv/parser_type.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-TEST(SpvParserTypeTest, SameArgumentsGivesSamePointer) {
-  Symbol sym(Symbol(1, {}));
-
-  TypeManager ty;
-  EXPECT_EQ(ty.Void(), ty.Void());
-  EXPECT_EQ(ty.Bool(), ty.Bool());
-  EXPECT_EQ(ty.U32(), ty.U32());
-  EXPECT_EQ(ty.F32(), ty.F32());
-  EXPECT_EQ(ty.I32(), ty.I32());
-  EXPECT_EQ(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
-            ty.Pointer(ty.I32(), ast::StorageClass::kNone));
-  EXPECT_EQ(ty.Vector(ty.I32(), 3), ty.Vector(ty.I32(), 3));
-  EXPECT_EQ(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 3, 2));
-  EXPECT_EQ(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 3, 2));
-  EXPECT_EQ(ty.Alias(sym, ty.I32()), ty.Alias(sym, ty.I32()));
-  EXPECT_EQ(ty.Struct(sym, {ty.I32()}), ty.Struct(sym, {ty.I32()}));
-  EXPECT_EQ(ty.Sampler(ast::SamplerKind::kSampler),
-            ty.Sampler(ast::SamplerKind::kSampler));
-  EXPECT_EQ(ty.DepthTexture(ast::TextureDimension::k2d),
-            ty.DepthTexture(ast::TextureDimension::k2d));
-  EXPECT_EQ(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()));
-  EXPECT_EQ(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()));
-  EXPECT_EQ(ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
-            ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead));
-}
-
-TEST(SpvParserTypeTest, DifferentArgumentsGivesDifferentPointer) {
-  Symbol sym_a(Symbol(1, {}));
-  Symbol sym_b(Symbol(2, {}));
-
-  TypeManager ty;
-  EXPECT_NE(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
-            ty.Pointer(ty.U32(), ast::StorageClass::kNone));
-  EXPECT_NE(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
-            ty.Pointer(ty.I32(), ast::StorageClass::kInput));
-  EXPECT_NE(ty.Vector(ty.I32(), 3), ty.Vector(ty.U32(), 3));
-  EXPECT_NE(ty.Vector(ty.I32(), 3), ty.Vector(ty.I32(), 2));
-  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.U32(), 3, 2));
-  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 2, 2));
-  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 3, 3));
-  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.U32(), 3, 2));
-  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 2, 2));
-  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 3, 3));
-  EXPECT_NE(ty.Alias(sym_a, ty.I32()), ty.Alias(sym_b, ty.I32()));
-  EXPECT_NE(ty.Struct(sym_a, {ty.I32()}), ty.Struct(sym_b, {ty.I32()}));
-  EXPECT_NE(ty.Sampler(ast::SamplerKind::kSampler),
-            ty.Sampler(ast::SamplerKind::kComparisonSampler));
-  EXPECT_NE(ty.DepthTexture(ast::TextureDimension::k2d),
-            ty.DepthTexture(ast::TextureDimension::k1d));
-  EXPECT_NE(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.MultisampledTexture(ast::TextureDimension::k3d, ty.I32()));
-  EXPECT_NE(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.MultisampledTexture(ast::TextureDimension::k2d, ty.U32()));
-  EXPECT_NE(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.SampledTexture(ast::TextureDimension::k3d, ty.I32()));
-  EXPECT_NE(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
-            ty.SampledTexture(ast::TextureDimension::k2d, ty.U32()));
-  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
-            ty.StorageTexture(ast::TextureDimension::k3d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead));
-  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
-            ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Sint, ast::Access::kRead));
-  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
-            ty.StorageTexture(ast::TextureDimension::k2d,
-                              ast::TexelFormat::kR32Uint, ast::Access::kWrite));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/spirv_tools_helpers_test.cc b/src/reader/spirv/spirv_tools_helpers_test.cc
deleted file mode 100644
index a6b2268..0000000
--- a/src/reader/spirv/spirv_tools_helpers_test.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 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/reader/spirv/spirv_tools_helpers_test.h"
-
-#include "gtest/gtest.h"
-#include "spirv-tools/libspirv.hpp"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace test {
-
-std::vector<uint32_t> Assemble(const std::string& spirv_assembly) {
-  // TODO(dneto): Use ScopedTrace?
-
-  // (The target environment doesn't affect assembly.
-  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
-  std::stringstream errors;
-  std::vector<uint32_t> result;
-  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
-                                     const spv_position_t& position,
-                                     const char* message) {
-    errors << "assembly error:" << position.line << ":" << position.column
-           << ": " << message;
-  });
-
-  const auto success = tools.Assemble(
-      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  EXPECT_TRUE(success) << errors.str();
-
-  return result;
-}
-
-std::string AssembleFailure(const std::string& spirv_assembly) {
-  // TODO(dneto): Use ScopedTrace?
-
-  // (The target environment doesn't affect assembly.
-  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
-  std::stringstream errors;
-  std::vector<uint32_t> result;
-  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
-                                     const spv_position_t& position,
-                                     const char* message) {
-    errors << position.line << ":" << position.column << ": " << message;
-  });
-
-  const auto success = tools.Assemble(
-      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  EXPECT_FALSE(success);
-
-  return errors.str();
-}
-
-std::string Disassemble(const std::vector<uint32_t>& spirv_module) {
-  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
-  std::stringstream errors;
-  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
-                                     const spv_position_t& position,
-                                     const char* message) {
-    errors << "disassmbly error:" << position.line << ":" << position.column
-           << ": " << message;
-  });
-
-  std::string result;
-  const auto success = tools.Disassemble(
-      spirv_module, &result, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  EXPECT_TRUE(success) << errors.str();
-
-  return result;
-}
-
-}  // namespace test
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/spirv_tools_helpers_test.h b/src/reader/spirv/spirv_tools_helpers_test.h
deleted file mode 100644
index 050614f..0000000
--- a/src/reader/spirv/spirv_tools_helpers_test.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
-#define SRC_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
-
-#include <string>
-#include <vector>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace test {
-
-/// @param spirv_assembly SPIR-V assembly text
-/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
-/// are preserved.
-std::vector<uint32_t> Assemble(const std::string& spirv_assembly);
-
-/// Attempts to assemble given SPIR-V assembly text.  Expect it to fail.
-/// @param spirv_assembly the SPIR-V assembly
-/// @returns the failure message.
-std::string AssembleFailure(const std::string& spirv_assembly);
-
-/// @param spirv_module a SPIR-V binary module
-/// @returns the disassembled module
-std::string Disassemble(const std::vector<uint32_t>& spirv_module);
-
-}  // namespace test
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
diff --git a/src/reader/spirv/usage.cc b/src/reader/spirv/usage.cc
deleted file mode 100644
index 902d923..0000000
--- a/src/reader/spirv/usage.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2020 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/reader/spirv/usage.h"
-
-#include <sstream>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-Usage::Usage() {}
-Usage::Usage(const Usage& other) = default;
-Usage::~Usage() = default;
-
-std::ostream& Usage::operator<<(std::ostream& out) const {
-  out << "Usage(";
-  if (IsSampler()) {
-    out << "Sampler(";
-    if (is_comparison_sampler_) {
-      out << " comparison";
-    }
-    out << " )";
-  }
-  if (IsTexture()) {
-    out << "Texture(";
-    if (is_sampled_) {
-      out << " is_sampled";
-    }
-    if (is_multisampled_) {
-      out << " ms";
-    }
-    if (is_depth_) {
-      out << " depth";
-    }
-    if (is_storage_read_) {
-      out << " read";
-    }
-    if (is_storage_write_) {
-      out << " write";
-    }
-    out << " )";
-  }
-  out << ")";
-  return out;
-}
-
-bool Usage::IsValid() const {
-  // Check sampler state internal consistency.
-  if (is_comparison_sampler_ && !is_sampler_) {
-    return false;
-  }
-
-  // Check texture state.
-  // |is_texture_| is implied by any of the later texture-based properties.
-  if ((IsStorageTexture() || is_sampled_ || is_multisampled_ || is_depth_) &&
-      !is_texture_) {
-    return false;
-  }
-  if (is_texture_) {
-    // Multisampled implies sampled.
-    if (is_multisampled_) {
-      if (!is_sampled_) {
-        return false;
-      }
-    }
-    // Depth implies sampled.
-    if (is_depth_) {
-      if (!is_sampled_) {
-        return false;
-      }
-    }
-
-    // Sampled can't be storage.
-    if (is_sampled_) {
-      if (IsStorageTexture()) {
-        return false;
-      }
-    }
-
-    // Storage can't be sampled.
-    if (IsStorageTexture()) {
-      if (is_sampled_) {
-        return false;
-      }
-    }
-    // Storage texture can't also be a sampler.
-    if (IsStorageTexture()) {
-      if (is_sampler_) {
-        return false;
-      }
-    }
-
-    // Can't be both read and write.  This is a restriction in WebGPU.
-    if (is_storage_read_ && is_storage_write_) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool Usage::IsComplete() const {
-  if (!IsValid()) {
-    return false;
-  }
-  if (IsSampler()) {
-    return true;
-  }
-  if (IsTexture()) {
-    return is_sampled_ || IsStorageTexture();
-  }
-  return false;
-}
-
-bool Usage::operator==(const Usage& other) const {
-  return is_sampler_ == other.is_sampler_ &&
-         is_comparison_sampler_ == other.is_comparison_sampler_ &&
-         is_texture_ == other.is_texture_ && is_sampled_ == other.is_sampled_ &&
-         is_multisampled_ == other.is_multisampled_ &&
-         is_depth_ == other.is_depth_ &&
-         is_storage_read_ == other.is_storage_read_ &&
-         is_storage_write_ == other.is_storage_write_;
-}
-
-void Usage::Add(const Usage& other) {
-  is_sampler_ = is_sampler_ || other.is_sampler_;
-  is_comparison_sampler_ =
-      is_comparison_sampler_ || other.is_comparison_sampler_;
-  is_texture_ = is_texture_ || other.is_texture_;
-  is_sampled_ = is_sampled_ || other.is_sampled_;
-  is_multisampled_ = is_multisampled_ || other.is_multisampled_;
-  is_depth_ = is_depth_ || other.is_depth_;
-  is_storage_read_ = is_storage_read_ || other.is_storage_read_;
-  is_storage_write_ = is_storage_write_ || other.is_storage_write_;
-}
-
-void Usage::AddSampler() {
-  is_sampler_ = true;
-}
-
-void Usage::AddComparisonSampler() {
-  AddSampler();
-  is_comparison_sampler_ = true;
-}
-
-void Usage::AddTexture() {
-  is_texture_ = true;
-}
-
-void Usage::AddStorageReadTexture() {
-  AddTexture();
-  is_storage_read_ = true;
-}
-
-void Usage::AddStorageWriteTexture() {
-  AddTexture();
-  is_storage_write_ = true;
-}
-
-void Usage::AddSampledTexture() {
-  AddTexture();
-  is_sampled_ = true;
-}
-
-void Usage::AddMultisampledTexture() {
-  AddSampledTexture();
-  is_multisampled_ = true;
-}
-
-void Usage::AddDepthTexture() {
-  AddSampledTexture();
-  is_depth_ = true;
-}
-
-std::string Usage::to_str() const {
-  std::ostringstream ss;
-  ss << *this;
-  return ss.str();
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/spirv/usage.h b/src/reader/spirv/usage.h
deleted file mode 100644
index 92e6f1f..0000000
--- a/src/reader/spirv/usage.h
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2020 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_READER_SPIRV_USAGE_H_
-#define SRC_READER_SPIRV_USAGE_H_
-
-#include <string>
-
-namespace tint {
-namespace reader {
-namespace spirv {
-
-/// Records the properties of a sampler or texture based on how it's used
-/// by image instructions inside function bodies.
-///
-/// For example:
-///
-///   If %X is the "Image" parameter of an OpImageWrite instruction then
-///    - The memory object declaration underlying %X will gain
-///      AddStorageWriteTexture usage
-///
-///   If %Y is the "Sampled Image" parameter of an OpImageSampleDrefExplicitLod
-///   instruction, and %Y is composed from sampler %YSam and image %YIm, then:
-///    - The memory object declaration underlying %YSam will gain
-///      AddComparisonSampler usage
-///    - The memory object declaration unederlying %YIm will gain
-///      AddSampledTexture and AddDepthTexture usages
-class Usage {
- public:
-  /// Constructor
-  Usage();
-  /// Copy constructor
-  /// @param other the Usage to clone
-  Usage(const Usage& other);
-  /// Destructor
-  ~Usage();
-
-  /// @returns true if this usage is internally consistent
-  bool IsValid() const;
-  /// @returns true if the usage fully determines a WebGPU binding type.
-  bool IsComplete() const;
-
-  /// @returns true if this usage is a sampler usage.
-  bool IsSampler() const { return is_sampler_; }
-  /// @returns true if this usage is a comparison sampler usage.
-  bool IsComparisonSampler() const { return is_comparison_sampler_; }
-
-  /// @returns true if this usage is a texture usage.
-  bool IsTexture() const { return is_texture_; }
-  /// @returns true if this usage is a sampled texture usage.
-  bool IsSampledTexture() const { return is_sampled_; }
-  /// @returns true if this usage is a multisampled texture usage.
-  bool IsMultisampledTexture() const { return is_multisampled_; }
-  /// @returns true if this usage is a dpeth texture usage.
-  bool IsDepthTexture() const { return is_depth_; }
-  /// @returns true if this usage is a read-only storage texture
-  bool IsStorageReadTexture() const { return is_storage_read_; }
-  /// @returns true if this usage is a write-only storage texture
-  bool IsStorageWriteTexture() const { return is_storage_write_; }
-
-  /// @returns true if this is a storage texture.
-  bool IsStorageTexture() const {
-    return is_storage_read_ || is_storage_write_;
-  }
-
-  /// Emits this usage to the given stream
-  /// @param out the output stream.
-  /// @returns the modified stream.
-  std::ostream& operator<<(std::ostream& out) const;
-
-  /// Equality operator
-  /// @param other the RHS of the equality test.
-  /// @returns true if `other` is identical to `*this`
-  bool operator==(const Usage& other) const;
-
-  /// Adds the usages from another usage object.
-  /// @param other the other usage
-  void Add(const Usage& other);
-
-  /// Records usage as a sampler.
-  void AddSampler();
-  /// Records usage as a comparison sampler.
-  void AddComparisonSampler();
-
-  /// Records usage as a texture of some kind.
-  void AddTexture();
-  /// Records usage as a read-only storage texture.
-  void AddStorageReadTexture();
-  /// Records usage as a write-only storage texture.
-  void AddStorageWriteTexture();
-  /// Records usage as a sampled texture.
-  void AddSampledTexture();
-  /// Records usage as a multisampled texture.
-  void AddMultisampledTexture();
-  /// Records usage as a depth texture.
-  void AddDepthTexture();
-
-  /// @returns this usage object as a string.
-  std::string to_str() const;
-
- private:
-  // Sampler properties.
-  bool is_sampler_ = false;
-  // A comparison sampler is always a sampler:
-  //    |is_comparison_sampler_| implies |is_sampler_|
-  bool is_comparison_sampler_ = false;
-
-  // Texture properties.
-  // |is_texture_| is always implied by any of the others below.
-  bool is_texture_ = false;
-  bool is_sampled_ = false;
-  bool is_multisampled_ = false;  // This implies it's sampled as well.
-  bool is_depth_ = false;
-  bool is_storage_read_ = false;
-  bool is_storage_write_ = false;
-};
-
-inline std::ostream& operator<<(std::ostream& out, const Usage& u) {
-  return u.operator<<(out);
-}
-
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_SPIRV_USAGE_H_
diff --git a/src/reader/spirv/usage_test.cc b/src/reader/spirv/usage_test.cc
deleted file mode 100644
index 6ed12bc..0000000
--- a/src/reader/spirv/usage_test.cc
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2020 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 <algorithm>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "src/reader/spirv/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace spirv {
-namespace {
-
-using ::testing::Eq;
-
-TEST_F(SpvParserTest, Usage_Trivial_Properties) {
-  Usage u;
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_FALSE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_FALSE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-}
-
-TEST_F(SpvParserTest, Usage_Trivial_Output) {
-  std::ostringstream ss;
-  Usage u;
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage()"));
-}
-
-TEST_F(SpvParserTest, Usage_Equality_OneDifference) {
-  const int num_usages = 9;
-  std::vector<Usage> usages(num_usages);
-  usages[1].AddSampler();
-  usages[2].AddComparisonSampler();
-  usages[3].AddTexture();
-  usages[4].AddSampledTexture();
-  usages[5].AddMultisampledTexture();
-  usages[6].AddDepthTexture();
-  usages[7].AddStorageReadTexture();
-  usages[8].AddStorageWriteTexture();
-  for (int i = 0; i < num_usages; ++i) {
-    for (int j = 0; j < num_usages; ++j) {
-      const auto& lhs = usages[i];
-      const auto& rhs = usages[j];
-      if (i == j) {
-        EXPECT_TRUE(lhs == rhs);
-      } else {
-        EXPECT_FALSE(lhs == rhs);
-      }
-    }
-  }
-}
-
-TEST_F(SpvParserTest, Usage_Add) {
-  // Mix two nontrivial usages.
-  Usage a;
-  a.AddStorageReadTexture();
-
-  Usage b;
-  b.AddComparisonSampler();
-
-  a.Add(b);
-
-  EXPECT_FALSE(a.IsValid());
-  EXPECT_FALSE(a.IsComplete());
-  EXPECT_TRUE(a.IsSampler());
-  EXPECT_TRUE(a.IsComparisonSampler());
-  EXPECT_TRUE(a.IsTexture());
-  EXPECT_FALSE(a.IsSampledTexture());
-  EXPECT_FALSE(a.IsMultisampledTexture());
-  EXPECT_FALSE(a.IsDepthTexture());
-  EXPECT_TRUE(a.IsStorageReadTexture());
-  EXPECT_FALSE(a.IsStorageWriteTexture());
-
-  std::ostringstream ss;
-  ss << a;
-  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison )Texture( read ))"));
-}
-
-TEST_F(SpvParserTest, Usage_AddSampler) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddSampler();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_TRUE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_FALSE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( ))"));
-
-  // Check idempotency
-  auto copy(u);
-  u.AddSampler();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddComparisonSampler) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddComparisonSampler();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_TRUE(u.IsSampler());
-  EXPECT_TRUE(u.IsComparisonSampler());
-  EXPECT_FALSE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison ))"));
-
-  auto copy(u);
-  u.AddComparisonSampler();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_FALSE(u.IsComplete());  // Don't know if it's sampled or storage
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( ))"));
-
-  auto copy(u);
-  u.AddTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddSampledTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddSampledTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_TRUE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ))"));
-
-  auto copy(u);
-  u.AddSampledTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddMultisampledTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddMultisampledTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_TRUE(u.IsSampledTexture());
-  EXPECT_TRUE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ms ))"));
-
-  auto copy(u);
-  u.AddMultisampledTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddDepthTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddDepthTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_TRUE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_TRUE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled depth ))"));
-
-  auto copy(u);
-  u.AddDepthTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddStorageReadTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddStorageReadTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_TRUE(u.IsStorageReadTexture());
-  EXPECT_FALSE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( read ))"));
-
-  auto copy(u);
-  u.AddStorageReadTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-TEST_F(SpvParserTest, Usage_AddStorageWriteTexture) {
-  std::ostringstream ss;
-  Usage u;
-  u.AddStorageWriteTexture();
-
-  EXPECT_TRUE(u.IsValid());
-  EXPECT_TRUE(u.IsComplete());
-  EXPECT_FALSE(u.IsSampler());
-  EXPECT_FALSE(u.IsComparisonSampler());
-  EXPECT_TRUE(u.IsTexture());
-  EXPECT_FALSE(u.IsSampledTexture());
-  EXPECT_FALSE(u.IsMultisampledTexture());
-  EXPECT_FALSE(u.IsDepthTexture());
-  EXPECT_FALSE(u.IsStorageReadTexture());
-  EXPECT_TRUE(u.IsStorageWriteTexture());
-
-  ss << u;
-  EXPECT_THAT(ss.str(), Eq("Usage(Texture( write ))"));
-
-  auto copy(u);
-  u.AddStorageWriteTexture();
-  EXPECT_TRUE(u == copy);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
deleted file mode 100644
index 8b0f7ac..0000000
--- a/src/reader/wgsl/lexer.cc
+++ /dev/null
@@ -1,1104 +0,0 @@
-// Copyright 2020 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/reader/wgsl/lexer.h"
-
-#include <cctype>
-#include <cmath>
-#include <cstring>
-#include <limits>
-#include <utility>
-
-#include "src/debug.h"
-#include "src/text/unicode.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-bool is_whitespace(char c) {
-  return std::isspace(c);
-}
-
-uint32_t dec_value(char c) {
-  if (c >= '0' && c <= '9') {
-    return static_cast<uint32_t>(c - '0');
-  }
-  return 0;
-}
-
-uint32_t hex_value(char c) {
-  if (c >= '0' && c <= '9') {
-    return static_cast<uint32_t>(c - '0');
-  }
-  if (c >= 'a' && c <= 'f') {
-    return 0xA + static_cast<uint32_t>(c - 'a');
-  }
-  if (c >= 'A' && c <= 'F') {
-    return 0xA + static_cast<uint32_t>(c - 'A');
-  }
-  return 0;
-}
-
-}  // namespace
-
-Lexer::Lexer(const Source::File* file)
-    : file_(file),
-      len_(static_cast<uint32_t>(file->content.data.size())),
-      location_{1, 1} {}
-
-Lexer::~Lexer() = default;
-
-Token Lexer::next() {
-  if (auto t = skip_whitespace_and_comments(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_hex_float(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_hex_integer(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_float(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_integer(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_ident(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  if (auto t = try_punctuation(); !t.IsUninitialized()) {
-    return t;
-  }
-
-  return {Token::Type::kError, begin_source(),
-          (is_null() ? "null character found" : "invalid character found")};
-}
-
-Source Lexer::begin_source() const {
-  Source src{};
-  src.file = file_;
-  src.range.begin = location_;
-  src.range.end = location_;
-  return src;
-}
-
-void Lexer::end_source(Source& src) const {
-  src.range.end = location_;
-}
-
-bool Lexer::is_eof() const {
-  return pos_ >= len_;
-}
-
-bool Lexer::is_null() const {
-  return (pos_ < len_) && (file_->content.data[pos_] == 0);
-}
-
-bool Lexer::is_digit(char ch) const {
-  return std::isdigit(ch);
-}
-
-bool Lexer::is_hex(char ch) const {
-  return std::isxdigit(ch);
-}
-
-bool Lexer::matches(size_t pos, std::string_view substr) {
-  if (pos >= len_)
-    return false;
-  return file_->content.data_view.substr(pos, substr.size()) == substr;
-}
-
-Token Lexer::skip_whitespace_and_comments() {
-  for (;;) {
-    auto pos = pos_;
-    while (!is_eof() && is_whitespace(file_->content.data[pos_])) {
-      if (matches(pos_, "\n")) {
-        pos_++;
-        location_.line++;
-        location_.column = 1;
-        continue;
-      }
-
-      pos_++;
-      location_.column++;
-    }
-
-    auto t = skip_comment();
-    if (!t.IsUninitialized()) {
-      return t;
-    }
-
-    // If the cursor didn't advance we didn't remove any whitespace
-    // so we're done.
-    if (pos == pos_)
-      break;
-  }
-  if (is_eof()) {
-    return {Token::Type::kEOF, begin_source()};
-  }
-
-  return {};
-}
-
-Token Lexer::skip_comment() {
-  if (matches(pos_, "//")) {
-    // Line comment: ignore everything until the end of line
-    // or end of input.
-    while (!is_eof() && !matches(pos_, "\n")) {
-      if (is_null()) {
-        return {Token::Type::kError, begin_source(), "null character found"};
-      }
-      pos_++;
-      location_.column++;
-    }
-    return {};
-  }
-
-  if (matches(pos_, "/*")) {
-    // Block comment: ignore everything until the closing '*/' token.
-
-    // Record source location of the initial '/*'
-    auto source = begin_source();
-    source.range.end.column += 1;
-
-    pos_ += 2;
-    location_.column += 2;
-
-    int depth = 1;
-    while (!is_eof() && depth > 0) {
-      if (matches(pos_, "/*")) {
-        // Start of block comment: increase nesting depth.
-        pos_ += 2;
-        location_.column += 2;
-        depth++;
-      } else if (matches(pos_, "*/")) {
-        // End of block comment: decrease nesting depth.
-        pos_ += 2;
-        location_.column += 2;
-        depth--;
-      } else if (matches(pos_, "\n")) {
-        // Newline: skip and update source location.
-        pos_++;
-        location_.line++;
-        location_.column = 1;
-      } else if (is_null()) {
-        return {Token::Type::kError, begin_source(), "null character found"};
-      } else {
-        // Anything else: skip and update source location.
-        pos_++;
-        location_.column++;
-      }
-    }
-    if (depth > 0) {
-      return {Token::Type::kError, source, "unterminated block comment"};
-    }
-  }
-  return {};
-}
-
-Token Lexer::try_float() {
-  auto start = pos_;
-  auto end = pos_;
-
-  auto source = begin_source();
-  bool has_mantissa_digits = false;
-
-  if (matches(end, "-")) {
-    end++;
-  }
-  while (end < len_ && is_digit(file_->content.data[end])) {
-    has_mantissa_digits = true;
-    end++;
-  }
-
-  bool has_point = false;
-  if (end < len_ && matches(end, ".")) {
-    has_point = true;
-    end++;
-  }
-
-  while (end < len_ && is_digit(file_->content.data[end])) {
-    has_mantissa_digits = true;
-    end++;
-  }
-
-  if (!has_mantissa_digits) {
-    return {};
-  }
-
-  // Parse the exponent if one exists
-  bool has_exponent = false;
-  if (end < len_ && (matches(end, "e") || matches(end, "E"))) {
-    end++;
-    if (end < len_ && (matches(end, "+") || matches(end, "-"))) {
-      end++;
-    }
-
-    while (end < len_ && isdigit(file_->content.data[end])) {
-      has_exponent = true;
-      end++;
-    }
-
-    // If an 'e' or 'E' was present, then the number part must also be present.
-    if (!has_exponent) {
-      const auto str = file_->content.data.substr(start, end - start);
-      return {Token::Type::kError, source,
-              "incomplete exponent for floating point literal: " + str};
-    }
-  }
-
-  bool has_f_suffix = false;
-  if (end < len_ && matches(end, "f")) {
-    end++;
-    has_f_suffix = true;
-  }
-
-  if (!has_point && !has_exponent && !has_f_suffix) {
-    // If it only has digits then it's an integer.
-    return {};
-  }
-
-  // Save the error string, for use by diagnostics.
-  const auto str = file_->content.data.substr(start, end - start);
-
-  pos_ = end;
-  location_.column += (end - start);
-
-  end_source(source);
-
-  auto res = strtod(file_->content.data.c_str() + start, nullptr);
-  // This errors out if a non-zero magnitude is too small to represent in a
-  // float. It can't be represented faithfully in an f32.
-  const auto magnitude = std::fabs(res);
-  if (0.0 < magnitude &&
-      magnitude < static_cast<double>(std::numeric_limits<float>::min())) {
-    return {Token::Type::kError, source,
-            "f32 (" + str + ") magnitude too small, not representable"};
-  }
-  // This handles if the number is really large negative number
-  if (res < static_cast<double>(std::numeric_limits<float>::lowest())) {
-    return {Token::Type::kError, source,
-            "f32 (" + str + ") too large (negative)"};
-  }
-  if (res > static_cast<double>(std::numeric_limits<float>::max())) {
-    return {Token::Type::kError, source,
-            "f32 (" + str + ") too large (positive)"};
-  }
-
-  return {source, static_cast<float>(res)};
-}
-
-Token Lexer::try_hex_float() {
-  constexpr uint32_t kTotalBits = 32;
-  constexpr uint32_t kTotalMsb = kTotalBits - 1;
-  constexpr uint32_t kMantissaBits = 23;
-  constexpr uint32_t kMantissaMsb = kMantissaBits - 1;
-  constexpr uint32_t kMantissaShiftRight = kTotalBits - kMantissaBits;
-  constexpr int32_t kExponentBias = 127;
-  constexpr int32_t kExponentMax = 255;
-  constexpr uint32_t kExponentBits = 8;
-  constexpr uint32_t kExponentMask = (1 << kExponentBits) - 1;
-  constexpr uint32_t kExponentLeftShift = kMantissaBits;
-  constexpr uint32_t kSignBit = 31;
-
-  auto start = pos_;
-  auto end = pos_;
-
-  auto source = begin_source();
-
-  // clang-format off
-  // -?0[xX]([0-9a-fA-F]*.?[0-9a-fA-F]+ | [0-9a-fA-F]+.[0-9a-fA-F]*)(p|P)(+|-)?[0-9]+  // NOLINT
-  // clang-format on
-
-  // -?
-  int32_t sign_bit = 0;
-  if (matches(end, "-")) {
-    sign_bit = 1;
-    end++;
-  }
-  // 0[xX]
-  if (matches(end, "0x") || matches(end, "0X")) {
-    end += 2;
-  } else {
-    return {};
-  }
-
-  uint32_t mantissa = 0;
-  uint32_t exponent = 0;
-
-  // TODO(dneto): Values in the normal range for the format do not explicitly
-  // store the most significant bit.  The algorithm here works hard to eliminate
-  // that bit in the representation during parsing, and then it backtracks
-  // when it sees it may have to explicitly represent it, and backtracks again
-  // when it sees the number is sub-normal (i.e. the exponent underflows).
-  // I suspect the logic can be clarified by storing it during parsing, and
-  // then removing it later only when needed.
-
-  // `set_next_mantissa_bit_to` sets next `mantissa` bit starting from msb to
-  // lsb to value 1 if `set` is true, 0 otherwise. Returns true on success, i.e.
-  // when the bit can be accommodated in the available space.
-  uint32_t mantissa_next_bit = kTotalMsb;
-  auto set_next_mantissa_bit_to = [&](bool set, bool integer_part) -> bool {
-    // If adding bits for the integer part, we can overflow whether we set the
-    // bit or not. For the fractional part, we can only overflow when setting
-    // the bit.
-    const bool check_overflow = integer_part || set;
-    // Note: mantissa_next_bit actually decrements, so comparing it as
-    // larger than a positive number relies on wraparound.
-    if (check_overflow && (mantissa_next_bit > kTotalMsb)) {
-      return false;  // Overflowed mantissa
-    }
-    if (set) {
-      mantissa |= (1 << mantissa_next_bit);
-    }
-    --mantissa_next_bit;
-    return true;
-  };
-
-  // Collect integer range (if any)
-  auto integer_range = std::make_pair(end, end);
-  while (end < len_ && is_hex(file_->content.data[end])) {
-    integer_range.second = ++end;
-  }
-
-  // .?
-  bool hex_point = false;
-  if (matches(end, ".")) {
-    hex_point = true;
-    end++;
-  }
-
-  // Collect fractional range (if any)
-  auto fractional_range = std::make_pair(end, end);
-  while (end < len_ && is_hex(file_->content.data[end])) {
-    fractional_range.second = ++end;
-  }
-
-  // Must have at least an integer or fractional part
-  if ((integer_range.first == integer_range.second) &&
-      (fractional_range.first == fractional_range.second)) {
-    return {};
-  }
-
-  // Is the binary exponent present?  It's optional.
-  const bool has_exponent = (matches(end, "p") || matches(end, "P"));
-  if (has_exponent) {
-    end++;
-  }
-  if (!has_exponent && !hex_point) {
-    // It's not a hex float. At best it's a hex integer.
-    return {};
-  }
-
-  // At this point, we know for sure our token is a hex float value,
-  // or an invalid token.
-
-  // Parse integer part
-  // [0-9a-fA-F]*
-
-  bool has_zero_integer = true;
-  // The magnitude is zero if and only if seen_prior_one_bits is false.
-  bool seen_prior_one_bits = false;
-  for (auto i = integer_range.first; i < integer_range.second; ++i) {
-    const auto nibble = hex_value(file_->content.data[i]);
-    if (nibble != 0) {
-      has_zero_integer = false;
-    }
-
-    for (int32_t bit = 3; bit >= 0; --bit) {
-      auto v = 1 & (nibble >> bit);
-
-      // Skip leading 0s and the first 1
-      if (seen_prior_one_bits) {
-        if (!set_next_mantissa_bit_to(v != 0, true)) {
-          return {Token::Type::kError, source,
-                  "mantissa is too large for hex float"};
-        }
-        ++exponent;
-      } else {
-        if (v == 1) {
-          seen_prior_one_bits = true;
-        }
-      }
-    }
-  }
-
-  // Parse fractional part
-  // [0-9a-fA-F]*
-  for (auto i = fractional_range.first; i < fractional_range.second; ++i) {
-    auto nibble = hex_value(file_->content.data[i]);
-    for (int32_t bit = 3; bit >= 0; --bit) {
-      auto v = 1 & (nibble >> bit);
-
-      if (v == 1) {
-        seen_prior_one_bits = true;
-      }
-
-      // If integer part is 0, we only start writing bits to the
-      // mantissa once we have a non-zero fractional bit. While the fractional
-      // values are 0, we adjust the exponent to avoid overflowing `mantissa`.
-      if (!seen_prior_one_bits) {
-        --exponent;
-      } else {
-        if (!set_next_mantissa_bit_to(v != 0, false)) {
-          return {Token::Type::kError, source,
-                  "mantissa is too large for hex float"};
-        }
-      }
-    }
-  }
-
-  // Determine if the value of the mantissa is zero.
-  // Note: it's not enough to check mantissa == 0 as we drop the initial bit,
-  // whether it's in the integer part or the fractional part.
-  const bool is_zero = !seen_prior_one_bits;
-  TINT_ASSERT(Reader, !is_zero || mantissa == 0);
-
-  // Parse the optional exponent.
-  // ((p|P)(\+|-)?[0-9]+)?
-  uint32_t input_exponent = 0;  // Defaults to 0 if not present
-  int32_t exponent_sign = 1;
-  // If the 'p' part is present, the rest of the exponent must exist.
-  if (has_exponent) {
-    // Parse the rest of the exponent.
-    // (+|-)?
-    if (matches(end, "+")) {
-      end++;
-    } else if (matches(end, "-")) {
-      exponent_sign = -1;
-      end++;
-    }
-
-    // Parse exponent from input
-    // [0-9]+
-    // Allow overflow (in uint32_t) when the floating point value magnitude is
-    // zero.
-    bool has_exponent_digits = false;
-    while (end < len_ && isdigit(file_->content.data[end])) {
-      has_exponent_digits = true;
-      auto prev_exponent = input_exponent;
-      input_exponent =
-          (input_exponent * 10) + dec_value(file_->content.data[end]);
-      // Check if we've overflowed input_exponent. This only matters when
-      // the mantissa is non-zero.
-      if (!is_zero && (prev_exponent > input_exponent)) {
-        return {Token::Type::kError, source,
-                "exponent is too large for hex float"};
-      }
-      end++;
-    }
-
-    // Parse optional 'f' suffix.  For a hex float, it can only exist
-    // when the exponent is present. Otherwise it will look like
-    // one of the mantissa digits.
-    if (end < len_ && matches(end, "f")) {
-      end++;
-    }
-
-    if (!has_exponent_digits) {
-      return {Token::Type::kError, source,
-              "expected an exponent value for hex float"};
-    }
-  }
-
-  pos_ = end;
-  location_.column += (end - start);
-  end_source(source);
-
-  if (is_zero) {
-    // If value is zero, then ignore the exponent and produce a zero
-    exponent = 0;
-  } else {
-    // Ensure input exponent is not too large; i.e. that it won't overflow when
-    // adding the exponent bias.
-    const uint32_t kIntMax =
-        static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
-    const uint32_t kMaxInputExponent = kIntMax - kExponentBias;
-    if (input_exponent > kMaxInputExponent) {
-      return {Token::Type::kError, source,
-              "exponent is too large for hex float"};
-    }
-
-    // Compute exponent so far
-    exponent += static_cast<uint32_t>(static_cast<int32_t>(input_exponent) *
-                                      exponent_sign);
-
-    // Bias exponent if non-zero
-    // After this, if exponent is <= 0, our value is a denormal
-    exponent += kExponentBias;
-
-    // We know the number is not zero.  The MSB is 1 (by construction), and
-    // should be eliminated because it becomes the implicit 1 that isn't
-    // explicitly represented in the binary32 format.  We'll bring it back
-    // later if we find the exponent actually underflowed, i.e. the number
-    // is sub-normal.
-    if (has_zero_integer) {
-      mantissa <<= 1;
-      --exponent;
-    }
-  }
-
-  // We can now safely work with exponent as a signed quantity, as there's no
-  // chance to overflow
-  int32_t signed_exponent = static_cast<int32_t>(exponent);
-
-  // Shift mantissa to occupy the low 23 bits
-  mantissa >>= kMantissaShiftRight;
-
-  // If denormal, shift mantissa until our exponent is zero
-  if (!is_zero) {
-    // Denorm has exponent 0 and non-zero mantissa. We set the top bit here,
-    // then shift the mantissa to make exponent zero.
-    if (signed_exponent <= 0) {
-      mantissa >>= 1;
-      mantissa |= (1 << kMantissaMsb);
-    }
-
-    while (signed_exponent < 0) {
-      mantissa >>= 1;
-      ++signed_exponent;
-
-      // If underflow, clamp to zero
-      if (mantissa == 0) {
-        signed_exponent = 0;
-      }
-    }
-  }
-
-  if (signed_exponent > kExponentMax) {
-    // Overflow: set to infinity
-    signed_exponent = kExponentMax;
-    mantissa = 0;
-  } else if (signed_exponent == kExponentMax && mantissa != 0) {
-    // NaN: set to infinity
-    mantissa = 0;
-  }
-
-  // Combine sign, mantissa, and exponent
-  uint32_t result_u32 = sign_bit << kSignBit;
-  result_u32 |= mantissa;
-  result_u32 |= (static_cast<uint32_t>(signed_exponent) & kExponentMask)
-                << kExponentLeftShift;
-
-  // Reinterpret as float and return
-  float result;
-  std::memcpy(&result, &result_u32, sizeof(result));
-  return {source, static_cast<float>(result)};
-}
-
-Token Lexer::build_token_from_int_if_possible(Source source,
-                                              size_t start,
-                                              size_t end,
-                                              int32_t base) {
-  auto res = strtoll(file_->content.data.c_str() + start, nullptr, base);
-  if (matches(pos_, "u")) {
-    if (static_cast<uint64_t>(res) >
-        static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
-      return {Token::Type::kError, source,
-              "u32 (" + file_->content.data.substr(start, end - start) +
-                  ") too large"};
-    }
-    pos_ += 1;
-    location_.column += 1;
-    end_source(source);
-    return {source, static_cast<uint32_t>(res)};
-  }
-
-  if (res < static_cast<int64_t>(std::numeric_limits<int32_t>::min())) {
-    return {Token::Type::kError, source,
-            "i32 (" + file_->content.data.substr(start, end - start) +
-                ") too small"};
-  }
-  if (res > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) {
-    return {Token::Type::kError, source,
-            "i32 (" + file_->content.data.substr(start, end - start) +
-                ") too large"};
-  }
-  end_source(source);
-  return {source, static_cast<int32_t>(res)};
-}
-
-Token Lexer::try_hex_integer() {
-  constexpr size_t kMaxDigits = 8;  // Valid for both 32-bit integer types
-  auto start = pos_;
-  auto end = pos_;
-
-  auto source = begin_source();
-
-  if (matches(end, "-")) {
-    end++;
-  }
-
-  if (matches(end, "0x") || matches(end, "0X")) {
-    end += 2;
-  } else {
-    return {};
-  }
-
-  auto first = end;
-  while (!is_eof() && is_hex(file_->content.data[end])) {
-    end++;
-
-    auto digits = end - first;
-    if (digits > kMaxDigits) {
-      return {Token::Type::kError, source,
-              "integer literal (" +
-                  file_->content.data.substr(start, end - 1 - start) +
-                  "...) has too many digits"};
-    }
-  }
-  if (first == end) {
-    return {Token::Type::kError, source,
-            "integer or float hex literal has no significant digits"};
-  }
-
-  pos_ = end;
-  location_.column += (end - start);
-
-  return build_token_from_int_if_possible(source, start, end, 16);
-}
-
-Token Lexer::try_integer() {
-  constexpr size_t kMaxDigits = 10;  // Valid for both 32-bit integer types
-  auto start = pos_;
-  auto end = start;
-
-  auto source = begin_source();
-
-  if (matches(end, "-")) {
-    end++;
-  }
-
-  if (end >= len_ || !is_digit(file_->content.data[end])) {
-    return {};
-  }
-
-  auto first = end;
-  // If the first digit is a zero this must only be zero as leading zeros
-  // are not allowed.
-  auto next = first + 1;
-  if (next < len_) {
-    if (file_->content.data[first] == '0' &&
-        is_digit(file_->content.data[next])) {
-      return {Token::Type::kError, source,
-              "integer literal (" +
-                  file_->content.data.substr(start, end - 1 - start) +
-                  "...) has leading 0s"};
-    }
-  }
-
-  while (end < len_ && is_digit(file_->content.data[end])) {
-    auto digits = end - first;
-    if (digits > kMaxDigits) {
-      return {Token::Type::kError, source,
-              "integer literal (" +
-                  file_->content.data.substr(start, end - 1 - start) +
-                  "...) has too many digits"};
-    }
-
-    end++;
-  }
-
-  pos_ = end;
-  location_.column += (end - start);
-
-  return build_token_from_int_if_possible(source, start, end, 10);
-}
-
-Token Lexer::try_ident() {
-  auto source = begin_source();
-  auto start = pos_;
-
-  // This below assumes that the size of a single std::string element is 1 byte.
-  static_assert(sizeof(file_->content.data[0]) == sizeof(uint8_t),
-                "tint::reader::wgsl requires the size of a std::string element "
-                "to be a single byte");
-
-  // Must begin with an XID_Source unicode character, or underscore
-  {
-    auto* utf8 = reinterpret_cast<const uint8_t*>(&file_->content.data[pos_]);
-    auto [code_point, n] =
-        text::utf8::Decode(utf8, file_->content.data.size() - pos_);
-    if (code_point != text::CodePoint('_') && !code_point.IsXIDStart()) {
-      return {};
-    }
-    // Consume start codepoint
-    pos_ += n;
-    location_.column += n;
-  }
-
-  while (!is_eof()) {
-    // Must continue with an XID_Continue unicode character
-    auto* utf8 = reinterpret_cast<const uint8_t*>(&file_->content.data[pos_]);
-    auto [code_point, n] =
-        text::utf8::Decode(utf8, file_->content.data.size() - pos_);
-    if (!code_point.IsXIDContinue()) {
-      break;
-    }
-
-    // Consume continuing codepoint
-    pos_ += n;
-    location_.column += n;
-  }
-
-  if (file_->content.data[start] == '_') {
-    // Check for an underscore on its own (special token), or a
-    // double-underscore (not allowed).
-    if ((pos_ == start + 1) || (file_->content.data[start + 1] == '_')) {
-      location_.column -= (pos_ - start);
-      pos_ = start;
-      return {};
-    }
-  }
-
-  auto str = file_->content.data_view.substr(start, pos_ - start);
-  end_source(source);
-
-  auto t = check_keyword(source, str);
-  if (!t.IsUninitialized()) {
-    return t;
-  }
-
-  return {Token::Type::kIdentifier, source, str};
-}
-
-Token Lexer::try_punctuation() {
-  auto source = begin_source();
-  auto type = Token::Type::kUninitialized;
-
-  if (matches(pos_, "@")) {
-    type = Token::Type::kAttr;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "[[")) {
-    type = Token::Type::kAttrLeft;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "]]")) {
-    type = Token::Type::kAttrRight;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "(")) {
-    type = Token::Type::kParenLeft;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ")")) {
-    type = Token::Type::kParenRight;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "[")) {
-    type = Token::Type::kBracketLeft;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "]")) {
-    type = Token::Type::kBracketRight;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "{")) {
-    type = Token::Type::kBraceLeft;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "}")) {
-    type = Token::Type::kBraceRight;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "&&")) {
-    type = Token::Type::kAndAnd;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "&")) {
-    type = Token::Type::kAnd;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "/")) {
-    type = Token::Type::kForwardSlash;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "!=")) {
-    type = Token::Type::kNotEqual;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "!")) {
-    type = Token::Type::kBang;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ":")) {
-    type = Token::Type::kColon;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ",")) {
-    type = Token::Type::kComma;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "==")) {
-    type = Token::Type::kEqualEqual;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "=")) {
-    type = Token::Type::kEqual;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ">=")) {
-    type = Token::Type::kGreaterThanEqual;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, ">>")) {
-    type = Token::Type::kShiftRight;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, ">")) {
-    type = Token::Type::kGreaterThan;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "<=")) {
-    type = Token::Type::kLessThanEqual;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "<<")) {
-    type = Token::Type::kShiftLeft;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "<")) {
-    type = Token::Type::kLessThan;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "%")) {
-    type = Token::Type::kMod;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "->")) {
-    type = Token::Type::kArrow;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "--")) {
-    type = Token::Type::kMinusMinus;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "-")) {
-    type = Token::Type::kMinus;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ".")) {
-    type = Token::Type::kPeriod;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "++")) {
-    type = Token::Type::kPlusPlus;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "+")) {
-    type = Token::Type::kPlus;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "||")) {
-    type = Token::Type::kOrOr;
-    pos_ += 2;
-    location_.column += 2;
-  } else if (matches(pos_, "|")) {
-    type = Token::Type::kOr;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, ";")) {
-    type = Token::Type::kSemicolon;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "*")) {
-    type = Token::Type::kStar;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "~")) {
-    type = Token::Type::kTilde;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "_")) {
-    type = Token::Type::kUnderscore;
-    pos_ += 1;
-    location_.column += 1;
-  } else if (matches(pos_, "^")) {
-    type = Token::Type::kXor;
-    pos_ += 1;
-    location_.column += 1;
-  }
-
-  end_source(source);
-
-  return {type, source};
-}
-
-Token Lexer::check_keyword(const Source& source, std::string_view str) {
-  if (str == "array")
-    return {Token::Type::kArray, source, "array"};
-  if (str == "atomic")
-    return {Token::Type::kAtomic, source, "atomic"};
-  if (str == "bitcast")
-    return {Token::Type::kBitcast, source, "bitcast"};
-  if (str == "bool")
-    return {Token::Type::kBool, source, "bool"};
-  if (str == "break")
-    return {Token::Type::kBreak, source, "break"};
-  if (str == "case")
-    return {Token::Type::kCase, source, "case"};
-  if (str == "continue")
-    return {Token::Type::kContinue, source, "continue"};
-  if (str == "continuing")
-    return {Token::Type::kContinuing, source, "continuing"};
-  if (str == "discard")
-    return {Token::Type::kDiscard, source, "discard"};
-  if (str == "default")
-    return {Token::Type::kDefault, source, "default"};
-  if (str == "else")
-    return {Token::Type::kElse, source, "else"};
-  if (str == "elseif")
-    return {Token::Type::kElseIf, source, "elseif"};
-  if (str == "f32")
-    return {Token::Type::kF32, source, "f32"};
-  if (str == "fallthrough")
-    return {Token::Type::kFallthrough, source, "fallthrough"};
-  if (str == "false")
-    return {Token::Type::kFalse, source, "false"};
-  if (str == "fn")
-    return {Token::Type::kFn, source, "fn"};
-  if (str == "for")
-    return {Token::Type::kFor, source, "for"};
-  if (str == "function")
-    return {Token::Type::kFunction, source, "function"};
-  if (str == "i32")
-    return {Token::Type::kI32, source, "i32"};
-  if (str == "if")
-    return {Token::Type::kIf, source, "if"};
-  if (str == "import")
-    return {Token::Type::kImport, source, "import"};
-  if (str == "let")
-    return {Token::Type::kLet, source, "let"};
-  if (str == "loop")
-    return {Token::Type::kLoop, source, "loop"};
-  if (str == "mat2x2")
-    return {Token::Type::kMat2x2, source, "mat2x2"};
-  if (str == "mat2x3")
-    return {Token::Type::kMat2x3, source, "mat2x3"};
-  if (str == "mat2x4")
-    return {Token::Type::kMat2x4, source, "mat2x4"};
-  if (str == "mat3x2")
-    return {Token::Type::kMat3x2, source, "mat3x2"};
-  if (str == "mat3x3")
-    return {Token::Type::kMat3x3, source, "mat3x3"};
-  if (str == "mat3x4")
-    return {Token::Type::kMat3x4, source, "mat3x4"};
-  if (str == "mat4x2")
-    return {Token::Type::kMat4x2, source, "mat4x2"};
-  if (str == "mat4x3")
-    return {Token::Type::kMat4x3, source, "mat4x3"};
-  if (str == "mat4x4")
-    return {Token::Type::kMat4x4, source, "mat4x4"};
-  if (str == "override")
-    return {Token::Type::kOverride, source, "override"};
-  if (str == "private")
-    return {Token::Type::kPrivate, source, "private"};
-  if (str == "ptr")
-    return {Token::Type::kPtr, source, "ptr"};
-  if (str == "return")
-    return {Token::Type::kReturn, source, "return"};
-  if (str == "sampler")
-    return {Token::Type::kSampler, source, "sampler"};
-  if (str == "sampler_comparison")
-    return {Token::Type::kComparisonSampler, source, "sampler_comparison"};
-  if (str == "storage_buffer" || str == "storage")
-    return {Token::Type::kStorage, source, "storage"};
-  if (str == "struct")
-    return {Token::Type::kStruct, source, "struct"};
-  if (str == "switch")
-    return {Token::Type::kSwitch, source, "switch"};
-  if (str == "texture_1d")
-    return {Token::Type::kTextureSampled1d, source, "texture_1d"};
-  if (str == "texture_2d")
-    return {Token::Type::kTextureSampled2d, source, "texture_2d"};
-  if (str == "texture_2d_array")
-    return {Token::Type::kTextureSampled2dArray, source, "texture_2d_array"};
-  if (str == "texture_3d")
-    return {Token::Type::kTextureSampled3d, source, "texture_3d"};
-  if (str == "texture_cube")
-    return {Token::Type::kTextureSampledCube, source, "texture_cube"};
-  if (str == "texture_cube_array") {
-    return {Token::Type::kTextureSampledCubeArray, source,
-            "texture_cube_array"};
-  }
-  if (str == "texture_depth_2d")
-    return {Token::Type::kTextureDepth2d, source, "texture_depth_2d"};
-  if (str == "texture_depth_2d_array") {
-    return {Token::Type::kTextureDepth2dArray, source,
-            "texture_depth_2d_array"};
-  }
-  if (str == "texture_depth_cube")
-    return {Token::Type::kTextureDepthCube, source, "texture_depth_cube"};
-  if (str == "texture_depth_cube_array") {
-    return {Token::Type::kTextureDepthCubeArray, source,
-            "texture_depth_cube_array"};
-  }
-  if (str == "texture_depth_multisampled_2d") {
-    return {Token::Type::kTextureDepthMultisampled2d, source,
-            "texture_depth_multisampled_2d"};
-  }
-  if (str == "texture_external") {
-    return {Token::Type::kTextureExternal, source, "texture_external"};
-  }
-  if (str == "texture_multisampled_2d") {
-    return {Token::Type::kTextureMultisampled2d, source,
-            "texture_multisampled_2d"};
-  }
-  if (str == "texture_storage_1d") {
-    return {Token::Type::kTextureStorage1d, source, "texture_storage_1d"};
-  }
-  if (str == "texture_storage_2d") {
-    return {Token::Type::kTextureStorage2d, source, "texture_storage_2d"};
-  }
-  if (str == "texture_storage_2d_array") {
-    return {Token::Type::kTextureStorage2dArray, source,
-            "texture_storage_2d_array"};
-  }
-  if (str == "texture_storage_3d") {
-    return {Token::Type::kTextureStorage3d, source, "texture_storage_3d"};
-  }
-  if (str == "true")
-    return {Token::Type::kTrue, source, "true"};
-  if (str == "type")
-    return {Token::Type::kType, source, "type"};
-  if (str == "u32")
-    return {Token::Type::kU32, source, "u32"};
-  if (str == "uniform")
-    return {Token::Type::kUniform, source, "uniform"};
-  if (str == "var")
-    return {Token::Type::kVar, source, "var"};
-  if (str == "vec2")
-    return {Token::Type::kVec2, source, "vec2"};
-  if (str == "vec3")
-    return {Token::Type::kVec3, source, "vec3"};
-  if (str == "vec4")
-    return {Token::Type::kVec4, source, "vec4"};
-  if (str == "workgroup")
-    return {Token::Type::kWorkgroup, source, "workgroup"};
-  return {};
-}
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/lexer.h b/src/reader/wgsl/lexer.h
deleted file mode 100644
index 5bdb20f..0000000
--- a/src/reader/wgsl/lexer.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2020 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_READER_WGSL_LEXER_H_
-#define SRC_READER_WGSL_LEXER_H_
-
-#include <string>
-
-#include "src/reader/wgsl/token.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-/// Converts the input stream into a series of Tokens
-class Lexer {
- public:
-  /// Creates a new Lexer
-  /// @param file the source file
-  explicit Lexer(const Source::File* file);
-  ~Lexer();
-
-  /// Returns the next token in the input stream.
-  /// @return Token
-  Token next();
-
- private:
-  /// Advances past whitespace and comments, if present
-  /// at the current position.
-  /// @returns error token, EOF, or uninitialized
-  Token skip_whitespace_and_comments();
-  /// Advances past a comment at the current position, if one exists.
-  /// Returns an error if there was an unterminated block comment,
-  /// or a null character was present.
-  /// @returns uninitialized token on success, or error
-  Token skip_comment();
-
-  Token build_token_from_int_if_possible(Source source,
-                                         size_t start,
-                                         size_t end,
-                                         int32_t base);
-  Token check_keyword(const Source&, std::string_view);
-
-  /// The try_* methods have the following in common:
-  /// - They assume there is at least one character to be consumed,
-  ///   i.e. the input has not yet reached end of file.
-  /// - They return an initialized token when they match and consume
-  ///   a token of the specified kind.
-  /// - Some can return an error token.
-  /// - Otherwise they return an uninitialized token when they did not
-  ///   match a token of the specfied kind.
-  Token try_float();
-  Token try_hex_float();
-  Token try_hex_integer();
-  Token try_ident();
-  Token try_integer();
-  Token try_punctuation();
-
-  Source begin_source() const;
-  void end_source(Source&) const;
-
-  /// @returns true if the end of the input has been reached.
-  bool is_eof() const;
-  /// @returns true if there is another character on the input and
-  /// it is not null.
-  bool is_null() const;
-  /// @param ch a character
-  /// @returns true if 'ch' is a decimal digit
-  bool is_digit(char ch) const;
-  /// @param ch a character
-  /// @returns true if 'ch' is a hexadecimal digit
-  bool is_hex(char ch) const;
-  bool matches(size_t pos, std::string_view substr);
-
-  /// The source file content
-  Source::File const* const file_;
-  /// The length of the input
-  uint32_t len_ = 0;
-  /// The current position in utf-8 code units (bytes) within the input
-  uint32_t pos_ = 0;
-  /// The current location within the input
-  Source::Location location_;
-};
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_LEXER_H_
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
deleted file mode 100644
index 2e79906..0000000
--- a/src/reader/wgsl/lexer_test.cc
+++ /dev/null
@@ -1,847 +0,0 @@
-// Copyright 2020 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/reader/wgsl/lexer.h"
-
-#include <limits>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-using LexerTest = testing::Test;
-
-TEST_F(LexerTest, Empty) {
-  Source::File file("", "");
-  Lexer l(&file);
-  auto t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-
-TEST_F(LexerTest, Skips_Whitespace) {
-  Source::File file("", "\t\r\n\t    ident\t\n\t  \r ");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 2u);
-  EXPECT_EQ(t.source().range.begin.column, 6u);
-  EXPECT_EQ(t.source().range.end.line, 2u);
-  EXPECT_EQ(t.source().range.end.column, 11u);
-  EXPECT_EQ(t.to_str(), "ident");
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-
-TEST_F(LexerTest, Skips_Comments_Line) {
-  Source::File file("", R"(//starts with comment
-ident1 //ends with comment
-// blank line
- ident2)");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 2u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 2u);
-  EXPECT_EQ(t.source().range.end.column, 7u);
-  EXPECT_EQ(t.to_str(), "ident1");
-
-  t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 4u);
-  EXPECT_EQ(t.source().range.begin.column, 2u);
-  EXPECT_EQ(t.source().range.end.line, 4u);
-  EXPECT_EQ(t.source().range.end.column, 8u);
-  EXPECT_EQ(t.to_str(), "ident2");
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-
-TEST_F(LexerTest, Skips_Comments_Block) {
-  Source::File file("", R"(/* comment
-text */ident)");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 2u);
-  EXPECT_EQ(t.source().range.begin.column, 8u);
-  EXPECT_EQ(t.source().range.end.line, 2u);
-  EXPECT_EQ(t.source().range.end.column, 13u);
-  EXPECT_EQ(t.to_str(), "ident");
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-
-TEST_F(LexerTest, Skips_Comments_Block_Nested) {
-  Source::File file("", R"(/* comment
-text // nested line comments are ignored /* more text
-/////**/ */*/ident)");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 3u);
-  EXPECT_EQ(t.source().range.begin.column, 14u);
-  EXPECT_EQ(t.source().range.end.line, 3u);
-  EXPECT_EQ(t.source().range.end.column, 19u);
-  EXPECT_EQ(t.to_str(), "ident");
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-
-TEST_F(LexerTest, Skips_Comments_Block_Unterminated) {
-  // I had to break up the /* because otherwise the clang readability check
-  // errored out saying it could not find the end of a multi-line comment.
-  Source::File file("", R"(
-  /)"
-                        R"(*
-abcd)");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "unterminated block comment");
-  EXPECT_EQ(t.source().range.begin.line, 2u);
-  EXPECT_EQ(t.source().range.begin.column, 3u);
-  EXPECT_EQ(t.source().range.end.line, 2u);
-  EXPECT_EQ(t.source().range.end.column, 4u);
-}
-
-TEST_F(LexerTest, Null_InWhitespace_IsError) {
-  Source::File file("", std::string{' ', 0, ' '});
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsError());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 2u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 2u);
-  EXPECT_EQ(t.to_str(), "null character found");
-}
-
-TEST_F(LexerTest, Null_InLineComment_IsError) {
-  Source::File file("", std::string{'/', '/', ' ', 0, ' '});
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsError());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 4u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 4u);
-  EXPECT_EQ(t.to_str(), "null character found");
-}
-
-TEST_F(LexerTest, Null_InBlockComment_IsError) {
-  Source::File file("", std::string{'/', '*', ' ', 0, '*', '/'});
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsError());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 4u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 4u);
-  EXPECT_EQ(t.to_str(), "null character found");
-}
-
-TEST_F(LexerTest, Null_InIdentifier_IsError) {
-  // Try inserting a null in an identifier. Other valid token
-  // kinds will behave similarly, so use the identifier case
-  // as a representative.
-  Source::File file("", std::string{'a', 0, 'c'});
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.to_str(), "a");
-  t = l.next();
-  EXPECT_TRUE(t.IsError());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 2u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 2u);
-  EXPECT_EQ(t.to_str(), "null character found");
-}
-
-struct FloatData {
-  const char* input;
-  float result;
-};
-inline std::ostream& operator<<(std::ostream& out, FloatData data) {
-  out << std::string(data.input);
-  return out;
-}
-using FloatTest = testing::TestWithParam<FloatData>;
-TEST_P(FloatTest, Parse) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral));
-  EXPECT_EQ(t.to_f32(), params.result);
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-INSTANTIATE_TEST_SUITE_P(LexerTest,
-                         FloatTest,
-                         testing::Values(
-                             // No decimal, with 'f' suffix
-                             FloatData{"0f", 0.0f},
-                             FloatData{"1f", 1.0f},
-                             FloatData{"-0f", 0.0f},
-                             FloatData{"-1f", -1.0f},
-
-                             // Zero, with decimal.
-                             FloatData{"0.0", 0.0f},
-                             FloatData{"0.", 0.0f},
-                             FloatData{".0", 0.0f},
-                             FloatData{"-0.0", 0.0f},
-                             FloatData{"-0.", 0.0f},
-                             FloatData{"-.0", 0.0f},
-                             // Zero, with decimal and 'f' suffix
-                             FloatData{"0.0f", 0.0f},
-                             FloatData{"0.f", 0.0f},
-                             FloatData{".0f", 0.0f},
-                             FloatData{"-0.0f", 0.0f},
-                             FloatData{"-0.f", 0.0f},
-                             FloatData{"-.0", 0.0f},
-
-                             // Non-zero with decimal
-                             FloatData{"5.7", 5.7f},
-                             FloatData{"5.", 5.f},
-                             FloatData{".7", .7f},
-                             FloatData{"-5.7", -5.7f},
-                             FloatData{"-5.", -5.f},
-                             FloatData{"-.7", -.7f},
-                             // Non-zero with decimal and 'f' suffix
-                             FloatData{"5.7f", 5.7f},
-                             FloatData{"5.f", 5.f},
-                             FloatData{".7f", .7f},
-                             FloatData{"-5.7f", -5.7f},
-                             FloatData{"-5.f", -5.f},
-                             FloatData{"-.7f", -.7f},
-
-                             // No decimal, with exponent
-                             FloatData{"1e5", 1e5f},
-                             FloatData{"1E5", 1e5f},
-                             FloatData{"1e-5", 1e-5f},
-                             FloatData{"1E-5", 1e-5f},
-                             // No decimal, with exponent and 'f' suffix
-                             FloatData{"1e5f", 1e5f},
-                             FloatData{"1E5f", 1e5f},
-                             FloatData{"1e-5f", 1e-5f},
-                             FloatData{"1E-5f", 1e-5f},
-                             // With decimal and exponents
-                             FloatData{"0.2e+12", 0.2e12f},
-                             FloatData{"1.2e-5", 1.2e-5f},
-                             FloatData{"2.57e23", 2.57e23f},
-                             FloatData{"2.5e+0", 2.5f},
-                             FloatData{"2.5e-0", 2.5f},
-                             // With decimal and exponents and 'f' suffix
-                             FloatData{"0.2e+12f", 0.2e12f},
-                             FloatData{"1.2e-5f", 1.2e-5f},
-                             FloatData{"2.57e23f", 2.57e23f},
-                             FloatData{"2.5e+0f", 2.5f},
-                             FloatData{"2.5e-0f", 2.5f}));
-
-using FloatTest_Invalid = testing::TestWithParam<const char*>;
-TEST_P(FloatTest_Invalid, Handles) {
-  Source::File file("", GetParam());
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_FALSE(t.Is(Token::Type::kFloatLiteral));
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    FloatTest_Invalid,
-    testing::Values(".",
-                    "-.",
-                    // Need a mantissa digit
-                    ".e5",
-                    ".E5",
-                    // Need exponent digits
-                    ".e",
-                    ".e+",
-                    ".e-",
-                    ".E",
-                    ".e+",
-                    ".e-",
-                    // Overflow
-                    "2.5e+256",
-                    "-2.5e+127",
-                    // Magnitude smaller than smallest positive f32.
-                    "2.5e-300",
-                    "-2.5e-300",
-                    // Decimal exponent must immediately
-                    // follow the 'e'.
-                    "2.5e 12",
-                    "2.5e +12",
-                    "2.5e -12",
-                    "2.5e+ 123",
-                    "2.5e- 123",
-                    "2.5E 12",
-                    "2.5E +12",
-                    "2.5E -12",
-                    "2.5E+ 123",
-                    "2.5E- 123"));
-
-using AsciiIdentifierTest = testing::TestWithParam<const char*>;
-TEST_P(AsciiIdentifierTest, Parse) {
-  Source::File file("", GetParam());
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(GetParam()));
-  EXPECT_EQ(t.to_str(), GetParam());
-}
-INSTANTIATE_TEST_SUITE_P(LexerTest,
-                         AsciiIdentifierTest,
-                         testing::Values("a",
-                                         "test",
-                                         "test01",
-                                         "test_",
-                                         "_test",
-                                         "test_01",
-                                         "ALLCAPS",
-                                         "MiXeD_CaSe",
-                                         "abcdefghijklmnopqrstuvwxyz",
-                                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
-                                         "alldigits_0123456789"));
-
-struct UnicodeCase {
-  const char* utf8;
-  size_t code_units;
-};
-
-using UnicodeIdentifierTest = testing::TestWithParam<UnicodeCase>;
-TEST_P(UnicodeIdentifierTest, Parse) {
-  Source::File file("", GetParam().utf8);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + GetParam().code_units);
-  EXPECT_EQ(t.to_str(), GetParam().utf8);
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    UnicodeIdentifierTest,
-    testing::ValuesIn({
-        UnicodeCase{// "𝐢𝐝𝐞𝐧𝐭𝐢𝐟𝐢𝐞𝐫"
-                    "\xf0\x9d\x90\xa2\xf0\x9d\x90\x9d\xf0\x9d\x90\x9e\xf0\x9d"
-                    "\x90\xa7\xf0\x9d\x90\xad\xf0\x9d\x90\xa2\xf0\x9d\x90\x9f"
-                    "\xf0\x9d\x90\xa2\xf0\x9d\x90\x9e\xf0\x9d\x90\xab",
-                    40},
-        UnicodeCase{// "𝑖𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟"
-                    "\xf0\x9d\x91\x96\xf0\x9d\x91\x91\xf0\x9d\x91\x92\xf0\x9d"
-                    "\x91\x9b\xf0\x9d\x91\xa1\xf0\x9d\x91\x96\xf0\x9d\x91\x93"
-                    "\xf0\x9d\x91\x96\xf0\x9d\x91\x92\xf0\x9d\x91\x9f",
-                    40},
-        UnicodeCase{
-            // "identifier"
-            "\xef\xbd\x89\xef\xbd\x84\xef\xbd\x85\xef\xbd\x8e\xef\xbd\x94\xef"
-            "\xbd\x89\xef\xbd\x86\xef\xbd\x89\xef\xbd\x85\xef\xbd\x92",
-            30},
-        UnicodeCase{// "𝕚𝕕𝕖𝕟𝕥𝕚𝕗𝕚𝕖𝕣𝟙𝟚𝟛"
-                    "\xf0\x9d\x95\x9a\xf0\x9d\x95\x95\xf0\x9d\x95\x96\xf0\x9d"
-                    "\x95\x9f\xf0\x9d\x95\xa5\xf0\x9d\x95\x9a\xf0\x9d\x95\x97"
-                    "\xf0\x9d\x95\x9a\xf0\x9d\x95\x96\xf0\x9d\x95\xa3\xf0\x9d"
-                    "\x9f\x99\xf0\x9d\x9f\x9a\xf0\x9d\x9f\x9b",
-                    52},
-        UnicodeCase{
-            // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
-            "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
-            "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
-            "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33",
-            43},
-    }));
-
-TEST_F(LexerTest, IdentifierTest_SingleUnderscoreDoesNotMatch) {
-  Source::File file("", "_");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_FALSE(t.IsIdentifier());
-}
-
-TEST_F(LexerTest, IdentifierTest_DoesNotStartWithDoubleUnderscore) {
-  Source::File file("", "__test");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_FALSE(t.IsIdentifier());
-}
-
-TEST_F(LexerTest, IdentifierTest_DoesNotStartWithNumber) {
-  Source::File file("", "01test");
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_FALSE(t.IsIdentifier());
-}
-
-struct HexSignedIntData {
-  const char* input;
-  int32_t result;
-};
-inline std::ostream& operator<<(std::ostream& out, HexSignedIntData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-using IntegerTest_HexSigned = testing::TestWithParam<HexSignedIntData>;
-TEST_P(IntegerTest_HexSigned, Matches) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(Token::Type::kSintLiteral));
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-  EXPECT_EQ(t.to_i32(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    IntegerTest_HexSigned,
-    testing::Values(
-        HexSignedIntData{"0x0", 0},
-        HexSignedIntData{"0X0", 0},
-        HexSignedIntData{"0x42", 66},
-        HexSignedIntData{"0X42", 66},
-        HexSignedIntData{"-0x42", -66},
-        HexSignedIntData{"-0X42", -66},
-        HexSignedIntData{"0xeF1Abc9", 250719177},
-        HexSignedIntData{"0XeF1Abc9", 250719177},
-        HexSignedIntData{"-0x80000000", std::numeric_limits<int32_t>::min()},
-        HexSignedIntData{"-0X80000000", std::numeric_limits<int32_t>::min()},
-        HexSignedIntData{"0x7FFFFFFF", std::numeric_limits<int32_t>::max()},
-        HexSignedIntData{"0X7FFFFFFF", std::numeric_limits<int32_t>::max()}));
-
-TEST_F(LexerTest, HexPrefixOnly_IsError) {
-  // Could be the start of a hex integer or hex float, but is neither.
-  Source::File file("", "0x");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(),
-            "integer or float hex literal has no significant digits");
-}
-
-TEST_F(LexerTest, HexPrefixUpperCaseOnly_IsError) {
-  // Could be the start of a hex integer or hex float, but is neither.
-  Source::File file("", "0X");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(),
-            "integer or float hex literal has no significant digits");
-}
-
-TEST_F(LexerTest, NegativeHexPrefixOnly_IsError) {
-  // Could be the start of a hex integer or hex float, but is neither.
-  Source::File file("", "-0x");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(),
-            "integer or float hex literal has no significant digits");
-}
-
-TEST_F(LexerTest, NegativeHexPrefixUpperCaseOnly_IsError) {
-  // Could be the start of a hex integer or hex float, but is neither.
-  Source::File file("", "-0X");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(),
-            "integer or float hex literal has no significant digits");
-}
-
-TEST_F(LexerTest, IntegerTest_HexSignedTooLarge) {
-  Source::File file("", "0x80000000");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "i32 (0x80000000) too large");
-}
-
-TEST_F(LexerTest, IntegerTest_HexSignedTooSmall) {
-  Source::File file("", "-0x8000000F");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "i32 (-0x8000000F) too small");
-}
-
-TEST_F(LexerTest, IntegerTest_HexSignedTooManyDigits) {
-  {
-    Source::File file("", "-0x100000000000000000000000");
-    Lexer l(&file);
-
-    auto t = l.next();
-    ASSERT_TRUE(t.Is(Token::Type::kError));
-    EXPECT_EQ(t.to_str(),
-              "integer literal (-0x10000000...) has too many digits");
-  }
-  {
-    Source::File file("", "0x100000000000000");
-    Lexer l(&file);
-
-    auto t = l.next();
-    ASSERT_TRUE(t.Is(Token::Type::kError));
-    EXPECT_EQ(t.to_str(),
-              "integer literal (0x10000000...) has too many digits");
-  }
-}
-
-struct HexUnsignedIntData {
-  const char* input;
-  uint32_t result;
-};
-inline std::ostream& operator<<(std::ostream& out, HexUnsignedIntData data) {
-  out << std::string(data.input);
-  return out;
-}
-using IntegerTest_HexUnsigned = testing::TestWithParam<HexUnsignedIntData>;
-TEST_P(IntegerTest_HexUnsigned, Matches) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(Token::Type::kUintLiteral));
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-  EXPECT_EQ(t.to_u32(), params.result);
-
-  t = l.next();
-  EXPECT_TRUE(t.IsEof());
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    IntegerTest_HexUnsigned,
-    testing::Values(HexUnsignedIntData{"0x0u", 0},
-                    HexUnsignedIntData{"0x42u", 66},
-                    HexUnsignedIntData{"0xeF1Abc9u", 250719177},
-                    HexUnsignedIntData{"0x0u",
-                                       std::numeric_limits<uint32_t>::min()},
-                    HexUnsignedIntData{"0xFFFFFFFFu",
-                                       std::numeric_limits<uint32_t>::max()}));
-
-TEST_F(LexerTest, IntegerTest_HexUnsignedTooManyDigits) {
-  Source::File file("", "0x1000000000000000000000u");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "integer literal (0x10000000...) has too many digits");
-}
-
-struct UnsignedIntData {
-  const char* input;
-  uint32_t result;
-};
-inline std::ostream& operator<<(std::ostream& out, UnsignedIntData data) {
-  out << std::string(data.input);
-  return out;
-}
-using IntegerTest_Unsigned = testing::TestWithParam<UnsignedIntData>;
-TEST_P(IntegerTest_Unsigned, Matches) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(Token::Type::kUintLiteral));
-  EXPECT_EQ(t.to_u32(), params.result);
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-}
-INSTANTIATE_TEST_SUITE_P(LexerTest,
-                         IntegerTest_Unsigned,
-                         testing::Values(UnsignedIntData{"0u", 0u},
-                                         UnsignedIntData{"123u", 123u},
-                                         UnsignedIntData{"4294967295u",
-                                                         4294967295u}));
-
-TEST_F(LexerTest, IntegerTest_UnsignedTooManyDigits) {
-  Source::File file("", "10000000000000000000000u");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "integer literal (1000000000...) has too many digits");
-}
-
-struct SignedIntData {
-  const char* input;
-  int32_t result;
-};
-inline std::ostream& operator<<(std::ostream& out, SignedIntData data) {
-  out << std::string(data.input);
-  return out;
-}
-using IntegerTest_Signed = testing::TestWithParam<SignedIntData>;
-TEST_P(IntegerTest_Signed, Matches) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(Token::Type::kSintLiteral));
-  EXPECT_EQ(t.to_i32(), params.result);
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    IntegerTest_Signed,
-    testing::Values(SignedIntData{"0", 0},
-                    SignedIntData{"-2", -2},
-                    SignedIntData{"2", 2},
-                    SignedIntData{"123", 123},
-                    SignedIntData{"2147483647", 2147483647},
-                    SignedIntData{"-2147483648", -2147483648LL}));
-
-TEST_F(LexerTest, IntegerTest_SignedTooManyDigits) {
-  Source::File file("", "-10000000000000000");
-  Lexer l(&file);
-
-  auto t = l.next();
-  ASSERT_TRUE(t.Is(Token::Type::kError));
-  EXPECT_EQ(t.to_str(), "integer literal (-1000000000...) has too many digits");
-}
-
-using IntegerTest_Invalid = testing::TestWithParam<const char*>;
-TEST_P(IntegerTest_Invalid, Parses) {
-  Source::File file("", GetParam());
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_FALSE(t.Is(Token::Type::kSintLiteral));
-  EXPECT_FALSE(t.Is(Token::Type::kUintLiteral));
-}
-INSTANTIATE_TEST_SUITE_P(LexerTest,
-                         IntegerTest_Invalid,
-                         testing::Values("2147483648",
-                                         "4294967296u",
-                                         "01234",
-                                         "0000",
-                                         "-00",
-                                         "00u"));
-
-struct TokenData {
-  const char* input;
-  Token::Type type;
-};
-inline std::ostream& operator<<(std::ostream& out, TokenData data) {
-  out << std::string(data.input);
-  return out;
-}
-using PunctuationTest = testing::TestWithParam<TokenData>;
-TEST_P(PunctuationTest, Parses) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(params.type));
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-
-  t = l.next();
-  EXPECT_EQ(t.source().range.begin.column,
-            1 + std::string(params.input).size());
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    PunctuationTest,
-    testing::Values(TokenData{"&", Token::Type::kAnd},
-                    TokenData{"&&", Token::Type::kAndAnd},
-                    TokenData{"->", Token::Type::kArrow},
-                    TokenData{"@", Token::Type::kAttr},
-                    TokenData{"[[", Token::Type::kAttrLeft},
-                    TokenData{"]]", Token::Type::kAttrRight},
-                    TokenData{"/", Token::Type::kForwardSlash},
-                    TokenData{"!", Token::Type::kBang},
-                    TokenData{"[", Token::Type::kBracketLeft},
-                    TokenData{"]", Token::Type::kBracketRight},
-                    TokenData{"{", Token::Type::kBraceLeft},
-                    TokenData{"}", Token::Type::kBraceRight},
-                    TokenData{":", Token::Type::kColon},
-                    TokenData{",", Token::Type::kComma},
-                    TokenData{"=", Token::Type::kEqual},
-                    TokenData{"==", Token::Type::kEqualEqual},
-                    TokenData{">", Token::Type::kGreaterThan},
-                    TokenData{">=", Token::Type::kGreaterThanEqual},
-                    TokenData{">>", Token::Type::kShiftRight},
-                    TokenData{"<", Token::Type::kLessThan},
-                    TokenData{"<=", Token::Type::kLessThanEqual},
-                    TokenData{"<<", Token::Type::kShiftLeft},
-                    TokenData{"%", Token::Type::kMod},
-                    TokenData{"!=", Token::Type::kNotEqual},
-                    TokenData{"-", Token::Type::kMinus},
-                    TokenData{"--", Token::Type::kMinusMinus},
-                    TokenData{".", Token::Type::kPeriod},
-                    TokenData{"+", Token::Type::kPlus},
-                    TokenData{"++", Token::Type::kPlusPlus},
-                    TokenData{"|", Token::Type::kOr},
-                    TokenData{"||", Token::Type::kOrOr},
-                    TokenData{"(", Token::Type::kParenLeft},
-                    TokenData{")", Token::Type::kParenRight},
-                    TokenData{";", Token::Type::kSemicolon},
-                    TokenData{"*", Token::Type::kStar},
-                    TokenData{"~", Token::Type::kTilde},
-                    TokenData{"_", Token::Type::kUnderscore},
-                    TokenData{"^", Token::Type::kXor}));
-
-using KeywordTest = testing::TestWithParam<TokenData>;
-TEST_P(KeywordTest, Parses) {
-  auto params = GetParam();
-  Source::File file("", params.input);
-  Lexer l(&file);
-
-  auto t = l.next();
-  EXPECT_TRUE(t.Is(params.type)) << params.input;
-  EXPECT_EQ(t.source().range.begin.line, 1u);
-  EXPECT_EQ(t.source().range.begin.column, 1u);
-  EXPECT_EQ(t.source().range.end.line, 1u);
-  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
-
-  t = l.next();
-  EXPECT_EQ(t.source().range.begin.column,
-            1 + std::string(params.input).size());
-}
-INSTANTIATE_TEST_SUITE_P(
-    LexerTest,
-    KeywordTest,
-    testing::Values(
-        TokenData{"array", Token::Type::kArray},
-        TokenData{"bitcast", Token::Type::kBitcast},
-        TokenData{"bool", Token::Type::kBool},
-        TokenData{"break", Token::Type::kBreak},
-        TokenData{"case", Token::Type::kCase},
-        TokenData{"continue", Token::Type::kContinue},
-        TokenData{"continuing", Token::Type::kContinuing},
-        TokenData{"default", Token::Type::kDefault},
-        TokenData{"discard", Token::Type::kDiscard},
-        TokenData{"else", Token::Type::kElse},
-        TokenData{"elseif", Token::Type::kElseIf},
-        TokenData{"f32", Token::Type::kF32},
-        TokenData{"fallthrough", Token::Type::kFallthrough},
-        TokenData{"false", Token::Type::kFalse},
-        TokenData{"fn", Token::Type::kFn},
-        TokenData{"for", Token::Type::kFor},
-        TokenData{"function", Token::Type::kFunction},
-        TokenData{"i32", Token::Type::kI32},
-        TokenData{"if", Token::Type::kIf},
-        TokenData{"import", Token::Type::kImport},
-        TokenData{"let", Token::Type::kLet},
-        TokenData{"loop", Token::Type::kLoop},
-        TokenData{"mat2x2", Token::Type::kMat2x2},
-        TokenData{"mat2x3", Token::Type::kMat2x3},
-        TokenData{"mat2x4", Token::Type::kMat2x4},
-        TokenData{"mat3x2", Token::Type::kMat3x2},
-        TokenData{"mat3x3", Token::Type::kMat3x3},
-        TokenData{"mat3x4", Token::Type::kMat3x4},
-        TokenData{"mat4x2", Token::Type::kMat4x2},
-        TokenData{"mat4x3", Token::Type::kMat4x3},
-        TokenData{"mat4x4", Token::Type::kMat4x4},
-        TokenData{"override", Token::Type::kOverride},
-        TokenData{"private", Token::Type::kPrivate},
-        TokenData{"ptr", Token::Type::kPtr},
-        TokenData{"return", Token::Type::kReturn},
-        TokenData{"sampler", Token::Type::kSampler},
-        TokenData{"sampler_comparison", Token::Type::kComparisonSampler},
-        TokenData{"storage", Token::Type::kStorage},
-        TokenData{"storage_buffer", Token::Type::kStorage},
-        TokenData{"struct", Token::Type::kStruct},
-        TokenData{"switch", Token::Type::kSwitch},
-        TokenData{"texture_1d", Token::Type::kTextureSampled1d},
-        TokenData{"texture_2d", Token::Type::kTextureSampled2d},
-        TokenData{"texture_2d_array", Token::Type::kTextureSampled2dArray},
-        TokenData{"texture_3d", Token::Type::kTextureSampled3d},
-        TokenData{"texture_cube", Token::Type::kTextureSampledCube},
-        TokenData{"texture_cube_array", Token::Type::kTextureSampledCubeArray},
-        TokenData{"texture_depth_2d", Token::Type::kTextureDepth2d},
-        TokenData{"texture_depth_2d_array", Token::Type::kTextureDepth2dArray},
-        TokenData{"texture_depth_cube", Token::Type::kTextureDepthCube},
-        TokenData{"texture_depth_cube_array",
-                  Token::Type::kTextureDepthCubeArray},
-        TokenData{"texture_depth_multisampled_2d",
-                  Token::Type::kTextureDepthMultisampled2d},
-        TokenData{"texture_multisampled_2d",
-                  Token::Type::kTextureMultisampled2d},
-        TokenData{"texture_storage_1d", Token::Type::kTextureStorage1d},
-        TokenData{"texture_storage_2d", Token::Type::kTextureStorage2d},
-        TokenData{"texture_storage_2d_array",
-                  Token::Type::kTextureStorage2dArray},
-        TokenData{"texture_storage_3d", Token::Type::kTextureStorage3d},
-        TokenData{"true", Token::Type::kTrue},
-        TokenData{"type", Token::Type::kType},
-        TokenData{"u32", Token::Type::kU32},
-        TokenData{"uniform", Token::Type::kUniform},
-        TokenData{"var", Token::Type::kVar},
-        TokenData{"vec2", Token::Type::kVec2},
-        TokenData{"vec3", Token::Type::kVec3},
-        TokenData{"vec4", Token::Type::kVec4},
-        TokenData{"workgroup", Token::Type::kWorkgroup}));
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser.cc b/src/reader/wgsl/parser.cc
deleted file mode 100644
index fee6e1d..0000000
--- a/src/reader/wgsl/parser.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser.h"
-
-#include <utility>
-
-#include "src/reader/wgsl/parser_impl.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-Program Parse(Source::File const* file) {
-  ParserImpl parser(file);
-  parser.Parse();
-  return Program(std::move(parser.builder()));
-}
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser.h b/src/reader/wgsl/parser.h
deleted file mode 100644
index f913c11..0000000
--- a/src/reader/wgsl/parser.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 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_READER_WGSL_PARSER_H_
-#define SRC_READER_WGSL_PARSER_H_
-
-#include "src/program.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-/// Parses the WGSL source, returning the parsed program.
-/// If the source fails to parse then the returned
-/// `program.Diagnostics.contains_errors()` will be true, and the
-/// `program.Diagnostics()` will describe the error.
-/// @param file the source file
-/// @returns the parsed program
-Program Parse(Source::File const* file);
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_PARSER_H_
diff --git a/src/reader/wgsl/parser_bench.cc b/src/reader/wgsl/parser_bench.cc
deleted file mode 100644
index e47e38b..0000000
--- a/src/reader/wgsl/parser_bench.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 <string>
-
-#include "src/bench/benchmark.h"
-
-namespace tint::reader::wgsl {
-namespace {
-
-void ParseWGSL(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadInputFile(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& file = std::get<Source::File>(res);
-  for (auto _ : state) {
-    auto res = Parse(&file);
-    if (res.Diagnostics().contains_errors()) {
-      state.SkipWithError(res.Diagnostics().str().c_str());
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(ParseWGSL);
-
-}  // namespace
-}  // namespace tint::reader::wgsl
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
deleted file mode 100644
index 8eb761b..0000000
--- a/src/reader/wgsl/parser_impl.cc
+++ /dev/null
@@ -1,3364 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl.h"
-
-#include "src/ast/array.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/external_texture.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/invariant_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/type_name.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/vector.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/reader/wgsl/lexer.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-template <typename T>
-using Expect = ParserImpl::Expect<T>;
-
-template <typename T>
-using Maybe = ParserImpl::Maybe<T>;
-
-/// Controls the maximum number of times we'll call into the sync() and
-/// unary_expression() functions from themselves. This is to guard against stack
-/// overflow when there is an excessive number of blocks.
-constexpr uint32_t kMaxParseDepth = 128;
-
-/// The maximum number of tokens to look ahead to try and sync the
-/// parser on error.
-constexpr size_t const kMaxResynchronizeLookahead = 32;
-
-const char kVertexStage[] = "vertex";
-const char kFragmentStage[] = "fragment";
-const char kComputeStage[] = "compute";
-
-const char kReadAccess[] = "read";
-const char kWriteAccess[] = "write";
-const char kReadWriteAccess[] = "read_write";
-
-ast::Builtin ident_to_builtin(std::string_view str) {
-  if (str == "position") {
-    return ast::Builtin::kPosition;
-  }
-  if (str == "vertex_index") {
-    return ast::Builtin::kVertexIndex;
-  }
-  if (str == "instance_index") {
-    return ast::Builtin::kInstanceIndex;
-  }
-  if (str == "front_facing") {
-    return ast::Builtin::kFrontFacing;
-  }
-  if (str == "frag_depth") {
-    return ast::Builtin::kFragDepth;
-  }
-  if (str == "local_invocation_id") {
-    return ast::Builtin::kLocalInvocationId;
-  }
-  if (str == "local_invocation_idx" || str == "local_invocation_index") {
-    return ast::Builtin::kLocalInvocationIndex;
-  }
-  if (str == "global_invocation_id") {
-    return ast::Builtin::kGlobalInvocationId;
-  }
-  if (str == "workgroup_id") {
-    return ast::Builtin::kWorkgroupId;
-  }
-  if (str == "num_workgroups") {
-    return ast::Builtin::kNumWorkgroups;
-  }
-  if (str == "sample_index") {
-    return ast::Builtin::kSampleIndex;
-  }
-  if (str == "sample_mask") {
-    return ast::Builtin::kSampleMask;
-  }
-  return ast::Builtin::kNone;
-}
-
-const char kBindingAttribute[] = "binding";
-const char kBlockAttribute[] = "block";
-const char kBuiltinAttribute[] = "builtin";
-const char kGroupAttribute[] = "group";
-const char kIdAttribute[] = "id";
-const char kInterpolateAttribute[] = "interpolate";
-const char kInvariantAttribute[] = "invariant";
-const char kLocationAttribute[] = "location";
-const char kSizeAttribute[] = "size";
-const char kAlignAttribute[] = "align";
-const char kStageAttribute[] = "stage";
-const char kStrideAttribute[] = "stride";
-const char kWorkgroupSizeAttribute[] = "workgroup_size";
-
-bool is_attribute(Token t) {
-  return t == kAlignAttribute || t == kBindingAttribute ||
-         t == kBlockAttribute || t == kBuiltinAttribute ||
-         t == kGroupAttribute || t == kIdAttribute ||
-         t == kInterpolateAttribute || t == kLocationAttribute ||
-         t == kSizeAttribute || t == kStageAttribute || t == kStrideAttribute ||
-         t == kWorkgroupSizeAttribute;
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl.html#reserved-keywords
-bool is_reserved(Token t) {
-  return t == "asm" || t == "bf16" || t == "const" || t == "do" ||
-         t == "enum" || t == "f16" || t == "f64" || t == "handle" ||
-         t == "i8" || t == "i16" || t == "i64" || t == "mat" ||
-         t == "premerge" || t == "regardless" || t == "typedef" || t == "u8" ||
-         t == "u16" || t == "u64" || t == "unless" || t == "using" ||
-         t == "vec" || t == "void" || t == "while";
-}
-
-/// Enter-exit counters for block token types.
-/// Used by sync_to() to skip over closing block tokens that were opened during
-/// the forward scan.
-struct BlockCounters {
-  int attrs = 0;    // [[ ]]
-  int brace = 0;    // {   }
-  int bracket = 0;  // [   ]
-  int paren = 0;    // (   )
-
-  /// @return the current enter-exit depth for the given block token type. If
-  /// `t` is not a block token type, then 0 is always returned.
-  int consume(const Token& t) {
-    if (t.Is(Token::Type::kAttrLeft))  // [DEPRECATED]
-      return attrs++;
-    if (t.Is(Token::Type::kAttrRight))  // [DEPRECATED]
-      return attrs--;
-    if (t.Is(Token::Type::kBraceLeft))
-      return brace++;
-    if (t.Is(Token::Type::kBraceRight))
-      return brace--;
-    if (t.Is(Token::Type::kBracketLeft))
-      return bracket++;
-    if (t.Is(Token::Type::kBracketRight))
-      return bracket--;
-    if (t.Is(Token::Type::kParenLeft))
-      return paren++;
-    if (t.Is(Token::Type::kParenRight))
-      return paren--;
-    return 0;
-  }
-};
-}  // namespace
-
-/// RAII helper that combines a Source on construction with the last token's
-/// source when implicitly converted to `Source`.
-class ParserImpl::MultiTokenSource {
- public:
-  /// Constructor that starts with Source at the current peek position
-  /// @param parser the parser
-  explicit MultiTokenSource(ParserImpl* parser)
-      : MultiTokenSource(parser, parser->peek().source().Begin()) {}
-
-  /// Constructor that starts with the input `start` Source
-  /// @param parser the parser
-  /// @param start the start source of the range
-  MultiTokenSource(ParserImpl* parser, const Source& start)
-      : parser_(parser), start_(start) {}
-
-  /// Implicit conversion to Source that returns the combined source from start
-  /// to the current last token's source.
-  operator Source() const {
-    Source end = parser_->last_token().source().End();
-    if (end < start_) {
-      end = start_;
-    }
-    return Source::Combine(start_, end);
-  }
-
- private:
-  ParserImpl* parser_;
-  Source start_;
-};
-
-ParserImpl::TypedIdentifier::TypedIdentifier() = default;
-
-ParserImpl::TypedIdentifier::TypedIdentifier(const TypedIdentifier&) = default;
-
-ParserImpl::TypedIdentifier::TypedIdentifier(const ast::Type* type_in,
-                                             std::string name_in,
-                                             Source source_in)
-    : type(type_in), name(std::move(name_in)), source(std::move(source_in)) {}
-
-ParserImpl::TypedIdentifier::~TypedIdentifier() = default;
-
-ParserImpl::FunctionHeader::FunctionHeader() = default;
-
-ParserImpl::FunctionHeader::FunctionHeader(const FunctionHeader&) = default;
-
-ParserImpl::FunctionHeader::FunctionHeader(Source src,
-                                           std::string n,
-                                           ast::VariableList p,
-                                           const ast::Type* ret_ty,
-                                           ast::AttributeList ret_attrs)
-    : source(src),
-      name(n),
-      params(p),
-      return_type(ret_ty),
-      return_type_attributes(ret_attrs) {}
-
-ParserImpl::FunctionHeader::~FunctionHeader() = default;
-
-ParserImpl::FunctionHeader& ParserImpl::FunctionHeader::operator=(
-    const FunctionHeader& rhs) = default;
-
-ParserImpl::VarDeclInfo::VarDeclInfo() = default;
-
-ParserImpl::VarDeclInfo::VarDeclInfo(const VarDeclInfo&) = default;
-
-ParserImpl::VarDeclInfo::VarDeclInfo(Source source_in,
-                                     std::string name_in,
-                                     ast::StorageClass storage_class_in,
-                                     ast::Access access_in,
-                                     const ast::Type* type_in)
-    : source(std::move(source_in)),
-      name(std::move(name_in)),
-      storage_class(storage_class_in),
-      access(access_in),
-      type(type_in) {}
-
-ParserImpl::VarDeclInfo::~VarDeclInfo() = default;
-
-ParserImpl::ParserImpl(Source::File const* file)
-    : lexer_(std::make_unique<Lexer>(file)) {}
-
-ParserImpl::~ParserImpl() = default;
-
-ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
-                                                   std::string_view err,
-                                                   std::string_view use) {
-  std::stringstream msg;
-  msg << err;
-  if (!use.empty()) {
-    msg << " for " << use;
-  }
-  add_error(source, msg.str());
-  return Failure::kErrored;
-}
-
-ParserImpl::Failure::Errored ParserImpl::add_error(const Token& t,
-                                                   const std::string& err) {
-  add_error(t.source(), err);
-  return Failure::kErrored;
-}
-
-ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
-                                                   const std::string& err) {
-  if (silence_errors_ == 0) {
-    builder_.Diagnostics().add_error(diag::System::Reader, err, source);
-  }
-  return Failure::kErrored;
-}
-
-void ParserImpl::deprecated(const Source& source, const std::string& msg) {
-  builder_.Diagnostics().add_warning(
-      diag::System::Reader, "use of deprecated language feature: " + msg,
-      source);
-}
-
-Token ParserImpl::next() {
-  if (!token_queue_.empty()) {
-    auto t = token_queue_.front();
-    token_queue_.pop_front();
-    last_token_ = t;
-    return last_token_;
-  }
-  last_token_ = lexer_->next();
-  return last_token_;
-}
-
-Token ParserImpl::peek(size_t idx) {
-  while (token_queue_.size() < (idx + 1))
-    token_queue_.push_back(lexer_->next());
-  return token_queue_[idx];
-}
-
-bool ParserImpl::peek_is(Token::Type tok, size_t idx) {
-  return peek(idx).Is(tok);
-}
-
-Token ParserImpl::last_token() const {
-  return last_token_;
-}
-
-bool ParserImpl::Parse() {
-  translation_unit();
-  return !has_error();
-}
-
-// translation_unit
-//  : global_decl* EOF
-void ParserImpl::translation_unit() {
-  while (continue_parsing()) {
-    auto p = peek();
-    if (p.IsEof()) {
-      break;
-    }
-    expect_global_decl();
-    if (builder_.Diagnostics().error_count() >= max_errors_) {
-      add_error(Source{{}, p.source().file},
-                "stopping after " + std::to_string(max_errors_) + " errors");
-      break;
-    }
-  }
-}
-
-// global_decl
-//  : SEMICOLON
-//  | global_variable_decl SEMICLON
-//  | global_constant_decl SEMICOLON
-//  | type_alias SEMICOLON
-//  | struct_decl
-//  | function_decl
-Expect<bool> ParserImpl::expect_global_decl() {
-  if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF))
-    return true;
-
-  bool errored = false;
-
-  auto attrs = attribute_list();
-  if (attrs.errored)
-    errored = true;
-  if (!continue_parsing())
-    return Failure::kErrored;
-
-  auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> {
-    auto gv = global_variable_decl(attrs.value);
-    if (gv.errored)
-      return Failure::kErrored;
-    if (gv.matched) {
-      if (!expect("variable declaration", Token::Type::kSemicolon))
-        return Failure::kErrored;
-
-      builder_.AST().AddGlobalVariable(gv.value);
-      return true;
-    }
-
-    auto gc = global_constant_decl(attrs.value);
-    if (gc.errored)
-      return Failure::kErrored;
-
-    if (gc.matched) {
-      if (!expect("let declaration", Token::Type::kSemicolon))
-        return Failure::kErrored;
-
-      builder_.AST().AddGlobalVariable(gc.value);
-      return true;
-    }
-
-    auto ta = type_alias();
-    if (ta.errored)
-      return Failure::kErrored;
-
-    if (ta.matched) {
-      if (!expect("type alias", Token::Type::kSemicolon))
-        return Failure::kErrored;
-
-      builder_.AST().AddTypeDecl(ta.value);
-      return true;
-    }
-
-    auto str = struct_decl(attrs.value);
-    if (str.errored)
-      return Failure::kErrored;
-
-    if (str.matched) {
-      builder_.AST().AddTypeDecl(str.value);
-      return true;
-    }
-
-    return Failure::kNoMatch;
-  });
-
-  if (decl.errored) {
-    errored = true;
-  }
-  if (decl.matched) {
-    return expect_attributes_consumed(attrs.value);
-  }
-
-  auto func = function_decl(attrs.value);
-  if (func.errored) {
-    errored = true;
-  }
-  if (func.matched) {
-    builder_.AST().AddFunction(func.value);
-    return true;
-  }
-
-  if (errored) {
-    return Failure::kErrored;
-  }
-
-  // Invalid syntax found - try and determine the best error message
-
-  // We have attributes parsed, but nothing to consume them?
-  if (attrs.value.size() > 0) {
-    return add_error(next(), "expected declaration after attributes");
-  }
-
-  // We have a statement outside of a function?
-  auto t = peek();
-  auto stat = without_error([&] { return statement(); });
-  if (stat.matched) {
-    // Attempt to jump to the next '}' - the function might have just been
-    // missing an opening line.
-    sync_to(Token::Type::kBraceRight, true);
-    return add_error(t, "statement found outside of function body");
-  }
-  if (!stat.errored) {
-    // No match, no error - the parser might not have progressed.
-    // Ensure we always make _some_ forward progress.
-    next();
-  }
-
-  // The token might itself be an error.
-  if (t.IsError()) {
-    next();  // Consume it.
-    return add_error(t.source(), t.to_str());
-  }
-
-  // Exhausted all attempts to make sense of where we're at.
-  // Spew a generic error.
-
-  return add_error(t, "unexpected token");
-}
-
-// global_variable_decl
-//  : variable_attribute_list* variable_decl
-//  | variable_attribute_list* variable_decl EQUAL const_expr
-Maybe<const ast::Variable*> ParserImpl::global_variable_decl(
-    ast::AttributeList& attrs) {
-  auto decl = variable_decl();
-  if (decl.errored)
-    return Failure::kErrored;
-  if (!decl.matched)
-    return Failure::kNoMatch;
-
-  const ast::Expression* constructor = nullptr;
-  if (match(Token::Type::kEqual)) {
-    auto expr = expect_const_expr();
-    if (expr.errored)
-      return Failure::kErrored;
-    constructor = expr.value;
-  }
-
-  return create<ast::Variable>(
-      decl->source,                             // source
-      builder_.Symbols().Register(decl->name),  // symbol
-      decl->storage_class,                      // storage class
-      decl->access,                             // access control
-      decl->type,                               // type
-      false,                                    // is_const
-      false,                                    // is_overridable
-      constructor,                              // constructor
-      std::move(attrs));                        // attributes
-}
-
-// global_constant_decl :
-//  | LET (ident | variable_ident_decl) global_const_initializer
-//  | attribute* override (ident | variable_ident_decl) (equal expression)?
-// global_const_initializer
-//  : EQUAL const_expr
-Maybe<const ast::Variable*> ParserImpl::global_constant_decl(
-    ast::AttributeList& attrs) {
-  bool is_overridable = false;
-  const char* use = nullptr;
-  if (match(Token::Type::kLet)) {
-    use = "let declaration";
-  } else if (match(Token::Type::kOverride)) {
-    use = "override declaration";
-    is_overridable = true;
-  } else {
-    return Failure::kNoMatch;
-  }
-
-  auto decl = expect_variable_ident_decl(use, /* allow_inferred = */ true);
-  if (decl.errored)
-    return Failure::kErrored;
-
-  const ast::Expression* initializer = nullptr;
-  if (match(Token::Type::kEqual)) {
-    auto init = expect_const_expr();
-    if (init.errored) {
-      return Failure::kErrored;
-    }
-    initializer = std::move(init.value);
-  }
-
-  return create<ast::Variable>(
-      decl->source,                             // source
-      builder_.Symbols().Register(decl->name),  // symbol
-      ast::StorageClass::kNone,                 // storage class
-      ast::Access::kUndefined,                  // access control
-      decl->type,                               // type
-      true,                                     // is_const
-      is_overridable,                           // is_overridable
-      initializer,                              // constructor
-      std::move(attrs));                        // attributes
-}
-
-// variable_decl
-//   : VAR variable_qualifier? variable_ident_decl
-Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl(bool allow_inferred) {
-  Source source;
-  if (!match(Token::Type::kVar, &source))
-    return Failure::kNoMatch;
-
-  VariableQualifier vq;
-  auto explicit_vq = variable_qualifier();
-  if (explicit_vq.errored)
-    return Failure::kErrored;
-  if (explicit_vq.matched) {
-    vq = explicit_vq.value;
-  }
-
-  auto decl =
-      expect_variable_ident_decl("variable declaration", allow_inferred);
-  if (decl.errored)
-    return Failure::kErrored;
-
-  return VarDeclInfo{decl->source, decl->name, vq.storage_class, vq.access,
-                     decl->type};
-}
-
-// texture_sampler_types
-//  : sampler_type
-//  | depth_texture_type
-//  | sampled_texture_type LESS_THAN type_decl GREATER_THAN
-//  | multisampled_texture_type LESS_THAN type_decl GREATER_THAN
-//  | storage_texture_type LESS_THAN texel_format
-//                         COMMA access GREATER_THAN
-Maybe<const ast::Type*> ParserImpl::texture_sampler_types() {
-  auto type = sampler_type();
-  if (type.matched)
-    return type;
-
-  type = depth_texture_type();
-  if (type.matched)
-    return type;
-
-  type = external_texture_type();
-  if (type.matched)
-    return type.value;
-
-  auto source_range = make_source_range();
-
-  auto dim = sampled_texture_type();
-  if (dim.matched) {
-    const char* use = "sampled texture type";
-
-    auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
-    if (subtype.errored)
-      return Failure::kErrored;
-
-    return builder_.ty.sampled_texture(source_range, dim.value, subtype.value);
-  }
-
-  auto ms_dim = multisampled_texture_type();
-  if (ms_dim.matched) {
-    const char* use = "multisampled texture type";
-
-    auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
-    if (subtype.errored)
-      return Failure::kErrored;
-
-    return builder_.ty.multisampled_texture(source_range, ms_dim.value,
-                                            subtype.value);
-  }
-
-  auto storage = storage_texture_type();
-  if (storage.matched) {
-    const char* use = "storage texture type";
-    using StorageTextureInfo =
-        std::pair<tint::ast::TexelFormat, tint::ast::Access>;
-    auto params = expect_lt_gt_block(use, [&]() -> Expect<StorageTextureInfo> {
-      auto format = expect_texel_format(use);
-      if (format.errored) {
-        return Failure::kErrored;
-      }
-
-      if (!expect("access control", Token::Type::kComma)) {
-        return Failure::kErrored;
-      }
-
-      auto access = expect_access("access control");
-      if (access.errored) {
-        return Failure::kErrored;
-      }
-
-      return std::make_pair(format.value, access.value);
-    });
-
-    if (params.errored) {
-      return Failure::kErrored;
-    }
-
-    return builder_.ty.storage_texture(source_range, storage.value,
-                                       params->first, params->second);
-  }
-
-  return Failure::kNoMatch;
-}
-
-// sampler_type
-//  : SAMPLER
-//  | SAMPLER_COMPARISON
-Maybe<const ast::Type*> ParserImpl::sampler_type() {
-  Source source;
-  if (match(Token::Type::kSampler, &source))
-    return builder_.ty.sampler(source, ast::SamplerKind::kSampler);
-
-  if (match(Token::Type::kComparisonSampler, &source))
-    return builder_.ty.sampler(source, ast::SamplerKind::kComparisonSampler);
-
-  return Failure::kNoMatch;
-}
-
-// sampled_texture_type
-//  : TEXTURE_SAMPLED_1D
-//  | TEXTURE_SAMPLED_2D
-//  | TEXTURE_SAMPLED_2D_ARRAY
-//  | TEXTURE_SAMPLED_3D
-//  | TEXTURE_SAMPLED_CUBE
-//  | TEXTURE_SAMPLED_CUBE_ARRAY
-Maybe<const ast::TextureDimension> ParserImpl::sampled_texture_type() {
-  if (match(Token::Type::kTextureSampled1d))
-    return ast::TextureDimension::k1d;
-
-  if (match(Token::Type::kTextureSampled2d))
-    return ast::TextureDimension::k2d;
-
-  if (match(Token::Type::kTextureSampled2dArray))
-    return ast::TextureDimension::k2dArray;
-
-  if (match(Token::Type::kTextureSampled3d))
-    return ast::TextureDimension::k3d;
-
-  if (match(Token::Type::kTextureSampledCube))
-    return ast::TextureDimension::kCube;
-
-  if (match(Token::Type::kTextureSampledCubeArray))
-    return ast::TextureDimension::kCubeArray;
-
-  return Failure::kNoMatch;
-}
-
-// external_texture_type
-//  : TEXTURE_EXTERNAL
-Maybe<const ast::Type*> ParserImpl::external_texture_type() {
-  Source source;
-  if (match(Token::Type::kTextureExternal, &source)) {
-    return builder_.ty.external_texture(source);
-  }
-
-  return Failure::kNoMatch;
-}
-
-// multisampled_texture_type
-//  : TEXTURE_MULTISAMPLED_2D
-Maybe<const ast::TextureDimension> ParserImpl::multisampled_texture_type() {
-  if (match(Token::Type::kTextureMultisampled2d))
-    return ast::TextureDimension::k2d;
-
-  return Failure::kNoMatch;
-}
-
-// storage_texture_type
-//  : TEXTURE_STORAGE_1D
-//  | TEXTURE_STORAGE_2D
-//  | TEXTURE_STORAGE_2D_ARRAY
-//  | TEXTURE_STORAGE_3D
-Maybe<const ast::TextureDimension> ParserImpl::storage_texture_type() {
-  if (match(Token::Type::kTextureStorage1d))
-    return ast::TextureDimension::k1d;
-  if (match(Token::Type::kTextureStorage2d))
-    return ast::TextureDimension::k2d;
-  if (match(Token::Type::kTextureStorage2dArray))
-    return ast::TextureDimension::k2dArray;
-  if (match(Token::Type::kTextureStorage3d))
-    return ast::TextureDimension::k3d;
-
-  return Failure::kNoMatch;
-}
-
-// depth_texture_type
-//  : TEXTURE_DEPTH_2D
-//  | TEXTURE_DEPTH_2D_ARRAY
-//  | TEXTURE_DEPTH_CUBE
-//  | TEXTURE_DEPTH_CUBE_ARRAY
-//  | TEXTURE_DEPTH_MULTISAMPLED_2D
-Maybe<const ast::Type*> ParserImpl::depth_texture_type() {
-  Source source;
-  if (match(Token::Type::kTextureDepth2d, &source)) {
-    return builder_.ty.depth_texture(source, ast::TextureDimension::k2d);
-  }
-  if (match(Token::Type::kTextureDepth2dArray, &source)) {
-    return builder_.ty.depth_texture(source, ast::TextureDimension::k2dArray);
-  }
-  if (match(Token::Type::kTextureDepthCube, &source)) {
-    return builder_.ty.depth_texture(source, ast::TextureDimension::kCube);
-  }
-  if (match(Token::Type::kTextureDepthCubeArray, &source)) {
-    return builder_.ty.depth_texture(source, ast::TextureDimension::kCubeArray);
-  }
-  if (match(Token::Type::kTextureDepthMultisampled2d, &source)) {
-    return builder_.ty.depth_multisampled_texture(source,
-                                                  ast::TextureDimension::k2d);
-  }
-  return Failure::kNoMatch;
-}
-
-// texel_format
-//  : 'rgba8unorm'
-//  | 'rgba8snorm'
-//  | 'rgba8uint'
-//  | 'rgba8sint'
-//  | 'rgba16uint'
-//  | 'rgba16sint'
-//  | 'rgba16float'
-//  | 'r32uint'
-//  | 'r32sint'
-//  | 'r32float'
-//  | 'rg32uint'
-//  | 'rg32sint'
-//  | 'rg32float'
-//  | 'rgba32uint'
-//  | 'rgba32sint'
-//  | 'rgba32float'
-Expect<ast::TexelFormat> ParserImpl::expect_texel_format(std::string_view use) {
-  auto t = next();
-  if (t == "rgba8unorm") {
-    return ast::TexelFormat::kRgba8Unorm;
-  }
-  if (t == "rgba8snorm") {
-    return ast::TexelFormat::kRgba8Snorm;
-  }
-  if (t == "rgba8uint") {
-    return ast::TexelFormat::kRgba8Uint;
-  }
-  if (t == "rgba8sint") {
-    return ast::TexelFormat::kRgba8Sint;
-  }
-  if (t == "rgba16uint") {
-    return ast::TexelFormat::kRgba16Uint;
-  }
-  if (t == "rgba16sint") {
-    return ast::TexelFormat::kRgba16Sint;
-  }
-  if (t == "rgba16float") {
-    return ast::TexelFormat::kRgba16Float;
-  }
-  if (t == "r32uint") {
-    return ast::TexelFormat::kR32Uint;
-  }
-  if (t == "r32sint") {
-    return ast::TexelFormat::kR32Sint;
-  }
-  if (t == "r32float") {
-    return ast::TexelFormat::kR32Float;
-  }
-  if (t == "rg32uint") {
-    return ast::TexelFormat::kRg32Uint;
-  }
-  if (t == "rg32sint") {
-    return ast::TexelFormat::kRg32Sint;
-  }
-  if (t == "rg32float") {
-    return ast::TexelFormat::kRg32Float;
-  }
-  if (t == "rgba32uint") {
-    return ast::TexelFormat::kRgba32Uint;
-  }
-  if (t == "rgba32sint") {
-    return ast::TexelFormat::kRgba32Sint;
-  }
-  if (t == "rgba32float") {
-    return ast::TexelFormat::kRgba32Float;
-  }
-  return add_error(t.source(), "invalid format", use);
-}
-
-// variable_ident_decl
-//   : IDENT COLON variable_attribute_list* type_decl
-Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
-    std::string_view use,
-    bool allow_inferred) {
-  auto ident = expect_ident(use);
-  if (ident.errored)
-    return Failure::kErrored;
-
-  if (allow_inferred && !peek_is(Token::Type::kColon)) {
-    return TypedIdentifier{nullptr, ident.value, ident.source};
-  }
-
-  if (!expect(use, Token::Type::kColon))
-    return Failure::kErrored;
-
-  auto attrs = attribute_list();
-  if (attrs.errored)
-    return Failure::kErrored;
-
-  auto t = peek();
-  auto type = type_decl(attrs.value);
-  if (type.errored)
-    return Failure::kErrored;
-  if (!type.matched)
-    return add_error(t.source(), "invalid type", use);
-
-  if (!expect_attributes_consumed(attrs.value))
-    return Failure::kErrored;
-
-  return TypedIdentifier{type.value, ident.value, ident.source};
-}
-
-Expect<ast::Access> ParserImpl::expect_access(std::string_view use) {
-  auto ident = expect_ident(use);
-  if (ident.errored)
-    return Failure::kErrored;
-
-  if (ident.value == kReadAccess)
-    return {ast::Access::kRead, ident.source};
-  if (ident.value == kWriteAccess)
-    return {ast::Access::kWrite, ident.source};
-  if (ident.value == kReadWriteAccess)
-    return {ast::Access::kReadWrite, ident.source};
-
-  return add_error(ident.source, "invalid value for access control");
-}
-
-// variable_qualifier
-//   : LESS_THAN storage_class (COMMA access_mode)? GREATER_THAN
-Maybe<ParserImpl::VariableQualifier> ParserImpl::variable_qualifier() {
-  if (!peek_is(Token::Type::kLessThan)) {
-    return Failure::kNoMatch;
-  }
-
-  auto* use = "variable declaration";
-  auto vq = expect_lt_gt_block(use, [&]() -> Expect<VariableQualifier> {
-    auto source = make_source_range();
-    auto sc = expect_storage_class(use);
-    if (sc.errored) {
-      return Failure::kErrored;
-    }
-    if (match(Token::Type::kComma)) {
-      auto ac = expect_access(use);
-      if (ac.errored) {
-        return Failure::kErrored;
-      }
-      return VariableQualifier{sc.value, ac.value};
-    }
-    return Expect<VariableQualifier>{
-        VariableQualifier{sc.value, ast::Access::kUndefined}, source};
-  });
-
-  if (vq.errored) {
-    return Failure::kErrored;
-  }
-
-  return vq;
-}
-
-// type_alias
-//   : TYPE IDENT EQUAL type_decl
-Maybe<const ast::Alias*> ParserImpl::type_alias() {
-  if (!peek_is(Token::Type::kType))
-    return Failure::kNoMatch;
-
-  auto t = next();
-  const char* use = "type alias";
-
-  auto name = expect_ident(use);
-  if (name.errored)
-    return Failure::kErrored;
-
-  if (!expect(use, Token::Type::kEqual))
-    return Failure::kErrored;
-
-  auto type = type_decl();
-  if (type.errored)
-    return Failure::kErrored;
-  if (!type.matched)
-    return add_error(peek(), "invalid type alias");
-
-  return builder_.ty.alias(make_source_range_from(t.source()), name.value,
-                           type.value);
-}
-
-// type_decl
-//   : IDENTIFIER
-//   | BOOL
-//   | FLOAT32
-//   | INT32
-//   | UINT32
-//   | VEC2 LESS_THAN type_decl GREATER_THAN
-//   | VEC3 LESS_THAN type_decl GREATER_THAN
-//   | VEC4 LESS_THAN type_decl GREATER_THAN
-//   | PTR LESS_THAN storage_class, type_decl (COMMA access_mode)? GREATER_THAN
-//   | array_attribute_list* ARRAY LESS_THAN type_decl COMMA
-//          INT_LITERAL GREATER_THAN
-//   | array_attribute_list* ARRAY LESS_THAN type_decl
-//          GREATER_THAN
-//   | MAT2x2 LESS_THAN type_decl GREATER_THAN
-//   | MAT2x3 LESS_THAN type_decl GREATER_THAN
-//   | MAT2x4 LESS_THAN type_decl GREATER_THAN
-//   | MAT3x2 LESS_THAN type_decl GREATER_THAN
-//   | MAT3x3 LESS_THAN type_decl GREATER_THAN
-//   | MAT3x4 LESS_THAN type_decl GREATER_THAN
-//   | MAT4x2 LESS_THAN type_decl GREATER_THAN
-//   | MAT4x3 LESS_THAN type_decl GREATER_THAN
-//   | MAT4x4 LESS_THAN type_decl GREATER_THAN
-//   | texture_sampler_types
-Maybe<const ast::Type*> ParserImpl::type_decl() {
-  auto attrs = attribute_list();
-  if (attrs.errored)
-    return Failure::kErrored;
-
-  auto type = type_decl(attrs.value);
-  if (type.errored) {
-    return Failure::kErrored;
-  }
-  if (!expect_attributes_consumed(attrs.value)) {
-    return Failure::kErrored;
-  }
-  if (!type.matched) {
-    return Failure::kNoMatch;
-  }
-
-  return type;
-}
-
-Maybe<const ast::Type*> ParserImpl::type_decl(ast::AttributeList& attrs) {
-  auto t = peek();
-  Source source;
-  if (match(Token::Type::kIdentifier, &source)) {
-    return builder_.create<ast::TypeName>(
-        source, builder_.Symbols().Register(t.to_str()));
-  }
-
-  if (match(Token::Type::kBool, &source))
-    return builder_.ty.bool_(source);
-
-  if (match(Token::Type::kF32, &source))
-    return builder_.ty.f32(source);
-
-  if (match(Token::Type::kI32, &source))
-    return builder_.ty.i32(source);
-
-  if (match(Token::Type::kU32, &source))
-    return builder_.ty.u32(source);
-
-  if (t.IsVector()) {
-    next();  // Consume the peek
-    return expect_type_decl_vector(t);
-  }
-
-  if (match(Token::Type::kPtr)) {
-    return expect_type_decl_pointer(t);
-  }
-
-  if (match(Token::Type::kAtomic)) {
-    return expect_type_decl_atomic(t);
-  }
-
-  if (match(Token::Type::kArray, &source)) {
-    return expect_type_decl_array(t, std::move(attrs));
-  }
-
-  if (t.IsMatrix()) {
-    next();  // Consume the peek
-    return expect_type_decl_matrix(t);
-  }
-
-  auto texture_or_sampler = texture_sampler_types();
-  if (texture_or_sampler.errored)
-    return Failure::kErrored;
-  if (texture_or_sampler.matched)
-    return texture_or_sampler;
-
-  return Failure::kNoMatch;
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
-  auto type = type_decl();
-  if (type.errored)
-    return Failure::kErrored;
-  if (!type.matched)
-    return add_error(peek().source(), "invalid type", use);
-  return type.value;
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type_decl_pointer(Token t) {
-  const char* use = "ptr declaration";
-
-  auto storage_class = ast::StorageClass::kNone;
-  auto access = ast::Access::kUndefined;
-
-  auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
-    auto sc = expect_storage_class(use);
-    if (sc.errored) {
-      return Failure::kErrored;
-    }
-    storage_class = sc.value;
-
-    if (!expect(use, Token::Type::kComma)) {
-      return Failure::kErrored;
-    }
-
-    auto type = expect_type(use);
-    if (type.errored) {
-      return Failure::kErrored;
-    }
-
-    if (match(Token::Type::kComma)) {
-      auto ac = expect_access("access control");
-      if (ac.errored) {
-        return Failure::kErrored;
-      }
-      access = ac.value;
-    }
-
-    return type.value;
-  });
-
-  if (subtype.errored) {
-    return Failure::kErrored;
-  }
-
-  return builder_.ty.pointer(make_source_range_from(t.source()), subtype.value,
-                             storage_class, access);
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(Token t) {
-  const char* use = "atomic declaration";
-
-  auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
-  if (subtype.errored) {
-    return Failure::kErrored;
-  }
-
-  return builder_.ty.atomic(make_source_range_from(t.source()), subtype.value);
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(Token t) {
-  uint32_t count = 2;
-  if (t.Is(Token::Type::kVec3)) {
-    count = 3;
-  } else if (t.Is(Token::Type::kVec4)) {
-    count = 4;
-  }
-
-  const ast::Type* subtype = nullptr;
-  if (peek_is(Token::Type::kLessThan)) {
-    const char* use = "vector";
-    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
-    if (ty.errored) {
-      return Failure::kErrored;
-    }
-    subtype = ty.value;
-  }
-
-  return builder_.ty.vec(make_source_range_from(t.source()), subtype, count);
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type_decl_array(
-    Token t,
-    ast::AttributeList attrs) {
-  const char* use = "array declaration";
-
-  const ast::Expression* size = nullptr;
-
-  auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
-    auto type = expect_type(use);
-    if (type.errored)
-      return Failure::kErrored;
-
-    if (match(Token::Type::kComma)) {
-      auto expr = primary_expression();
-      if (expr.errored) {
-        return Failure::kErrored;
-      } else if (!expr.matched) {
-        return add_error(peek(), "expected array size expression");
-      }
-
-      size = std::move(expr.value);
-    }
-
-    return type.value;
-  });
-
-  if (subtype.errored) {
-    return Failure::kErrored;
-  }
-
-  return builder_.ty.array(make_source_range_from(t.source()), subtype.value,
-                           size, std::move(attrs));
-}
-
-Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(Token t) {
-  uint32_t rows = 2;
-  uint32_t columns = 2;
-  if (t.IsMat3xN()) {
-    columns = 3;
-  } else if (t.IsMat4xN()) {
-    columns = 4;
-  }
-  if (t.IsMatNx3()) {
-    rows = 3;
-  } else if (t.IsMatNx4()) {
-    rows = 4;
-  }
-
-  const ast::Type* subtype = nullptr;
-  if (peek_is(Token::Type::kLessThan)) {
-    const char* use = "matrix";
-    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
-    if (ty.errored) {
-      return Failure::kErrored;
-    }
-    subtype = ty.value;
-  }
-
-  return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns,
-                         rows);
-}
-
-// storage_class
-//  : INPUT
-//  | OUTPUT
-//  | UNIFORM
-//  | WORKGROUP
-//  | STORAGE
-//  | PRIVATE
-//  | FUNCTION
-Expect<ast::StorageClass> ParserImpl::expect_storage_class(
-    std::string_view use) {
-  auto source = peek().source();
-
-  if (match(Token::Type::kUniform))
-    return {ast::StorageClass::kUniform, source};
-
-  if (match(Token::Type::kWorkgroup))
-    return {ast::StorageClass::kWorkgroup, source};
-
-  if (match(Token::Type::kStorage))
-    return {ast::StorageClass::kStorage, source};
-
-  if (match(Token::Type::kPrivate))
-    return {ast::StorageClass::kPrivate, source};
-
-  if (match(Token::Type::kFunction))
-    return {ast::StorageClass::kFunction, source};
-
-  return add_error(source, "invalid storage class", use);
-}
-
-// struct_decl
-//   : struct_attribute_decl* STRUCT IDENT struct_body_decl
-Maybe<const ast::Struct*> ParserImpl::struct_decl(ast::AttributeList& attrs) {
-  auto t = peek();
-  auto source = t.source();
-
-  if (!match(Token::Type::kStruct))
-    return Failure::kNoMatch;
-
-  auto name = expect_ident("struct declaration");
-  if (name.errored)
-    return Failure::kErrored;
-
-  auto body = expect_struct_body_decl();
-  if (body.errored)
-    return Failure::kErrored;
-
-  auto sym = builder_.Symbols().Register(name.value);
-  return create<ast::Struct>(source, sym, std::move(body.value),
-                             std::move(attrs));
-}
-
-// struct_body_decl
-//   : BRACKET_LEFT struct_member* BRACKET_RIGHT
-Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
-  return expect_brace_block(
-      "struct declaration", [&]() -> Expect<ast::StructMemberList> {
-        bool errored = false;
-
-        ast::StructMemberList members;
-
-        while (continue_parsing() && !peek_is(Token::Type::kBraceRight) &&
-               !peek_is(Token::Type::kEOF)) {
-          auto member = sync(Token::Type::kSemicolon,
-                             [&]() -> Expect<ast::StructMember*> {
-                               auto attrs = attribute_list();
-                               if (attrs.errored) {
-                                 errored = true;
-                               }
-                               if (!synchronized_) {
-                                 return Failure::kErrored;
-                               }
-                               return expect_struct_member(attrs.value);
-                             });
-
-          if (member.errored) {
-            errored = true;
-          } else {
-            members.push_back(member.value);
-          }
-        }
-
-        if (errored)
-          return Failure::kErrored;
-
-        return members;
-      });
-}
-
-// struct_member
-//   : struct_member_attribute_decl+ variable_ident_decl SEMICOLON
-Expect<ast::StructMember*> ParserImpl::expect_struct_member(
-    ast::AttributeList& attrs) {
-  auto decl = expect_variable_ident_decl("struct member");
-  if (decl.errored)
-    return Failure::kErrored;
-
-  if (!expect("struct member", Token::Type::kSemicolon))
-    return Failure::kErrored;
-
-  return create<ast::StructMember>(decl->source,
-                                   builder_.Symbols().Register(decl->name),
-                                   decl->type, std::move(attrs));
-}
-
-// function_decl
-//   : function_header body_stmt
-Maybe<const ast::Function*> ParserImpl::function_decl(
-    ast::AttributeList& attrs) {
-  auto header = function_header();
-  if (header.errored) {
-    if (sync_to(Token::Type::kBraceLeft, /* consume: */ false)) {
-      // There were errors in the function header, but the parser has managed to
-      // resynchronize with the opening brace. As there's no outer
-      // synchronization token for function declarations, attempt to parse the
-      // function body. The AST isn't used as we've already errored, but this
-      // catches any errors inside the body, and can help keep the parser in
-      // sync.
-      expect_body_stmt();
-    }
-    return Failure::kErrored;
-  }
-  if (!header.matched)
-    return Failure::kNoMatch;
-
-  bool errored = false;
-
-  auto body = expect_body_stmt();
-  if (body.errored)
-    errored = true;
-
-  if (errored)
-    return Failure::kErrored;
-
-  return create<ast::Function>(
-      header->source, builder_.Symbols().Register(header->name), header->params,
-      header->return_type, body.value, attrs, header->return_type_attributes);
-}
-
-// function_header
-//   : FN IDENT PAREN_LEFT param_list PAREN_RIGHT return_type_decl_optional
-// return_type_decl_optional
-//   :
-//   | ARROW attribute_list* type_decl
-Maybe<ParserImpl::FunctionHeader> ParserImpl::function_header() {
-  Source source;
-  if (!match(Token::Type::kFn, &source)) {
-    return Failure::kNoMatch;
-  }
-
-  const char* use = "function declaration";
-  bool errored = false;
-
-  auto name = expect_ident(use);
-  if (name.errored) {
-    errored = true;
-    if (!sync_to(Token::Type::kParenLeft, /* consume: */ false)) {
-      return Failure::kErrored;
-    }
-  }
-
-  auto params = expect_paren_block(use, [&] { return expect_param_list(); });
-  if (params.errored) {
-    errored = true;
-    if (!synchronized_) {
-      return Failure::kErrored;
-    }
-  }
-
-  const ast::Type* return_type = nullptr;
-  ast::AttributeList return_attributes;
-
-  if (match(Token::Type::kArrow)) {
-    auto attrs = attribute_list();
-    if (attrs.errored) {
-      return Failure::kErrored;
-    }
-    return_attributes = attrs.value;
-
-    // Apply stride attributes to the type node instead of the function.
-    ast::AttributeList type_attributes;
-    auto itr =
-        std::find_if(return_attributes.begin(), return_attributes.end(),
-                     [](auto* attr) { return Is<ast::StrideAttribute>(attr); });
-    if (itr != return_attributes.end()) {
-      type_attributes.emplace_back(*itr);
-      return_attributes.erase(itr);
-    }
-
-    auto tok = peek();
-
-    auto type = type_decl(type_attributes);
-    if (type.errored) {
-      errored = true;
-    } else if (!type.matched) {
-      return add_error(peek(), "unable to determine function return type");
-    } else {
-      return_type = type.value;
-    }
-  } else {
-    return_type = builder_.ty.void_();
-  }
-
-  if (errored) {
-    return Failure::kErrored;
-  }
-
-  return FunctionHeader{source, name.value, std::move(params.value),
-                        return_type, std::move(return_attributes)};
-}
-
-// param_list
-//   :
-//   | (param COMMA)* param COMMA?
-Expect<ast::VariableList> ParserImpl::expect_param_list() {
-  ast::VariableList ret;
-  while (continue_parsing()) {
-    // Check for the end of the list.
-    auto t = peek();
-    if (!t.IsIdentifier() && !t.Is(Token::Type::kAttr) &&
-        !t.Is(Token::Type::kAttrLeft)) {
-      break;
-    }
-
-    auto param = expect_param();
-    if (param.errored)
-      return Failure::kErrored;
-    ret.push_back(param.value);
-
-    if (!match(Token::Type::kComma))
-      break;
-  }
-
-  return ret;
-}
-
-// param
-//   : attribute_list* variable_ident_decl
-Expect<ast::Variable*> ParserImpl::expect_param() {
-  auto attrs = attribute_list();
-
-  auto decl = expect_variable_ident_decl("parameter");
-  if (decl.errored)
-    return Failure::kErrored;
-
-  auto* var =
-      create<ast::Variable>(decl->source,                             // source
-                            builder_.Symbols().Register(decl->name),  // symbol
-                            ast::StorageClass::kNone,  // storage class
-                            ast::Access::kUndefined,   // access control
-                            decl->type,                // type
-                            true,                      // is_const
-                            false,                     // is_overridable
-                            nullptr,                   // constructor
-                            std::move(attrs.value));   // attributes
-  // Formal parameters are treated like a const declaration where the
-  // initializer value is provided by the call's argument.  The key point is
-  // that it's not updatable after initially set.  This is unlike C or GLSL
-  // which treat formal parameters like local variables that can be updated.
-
-  return var;
-}
-
-// pipeline_stage
-//   : VERTEX
-//   | FRAGMENT
-//   | COMPUTE
-Expect<ast::PipelineStage> ParserImpl::expect_pipeline_stage() {
-  auto t = peek();
-  if (t == kVertexStage) {
-    next();  // Consume the peek
-    return {ast::PipelineStage::kVertex, t.source()};
-  }
-  if (t == kFragmentStage) {
-    next();  // Consume the peek
-    return {ast::PipelineStage::kFragment, t.source()};
-  }
-  if (t == kComputeStage) {
-    next();  // Consume the peek
-    return {ast::PipelineStage::kCompute, t.source()};
-  }
-  return add_error(peek(), "invalid value for stage attribute");
-}
-
-Expect<ast::Builtin> ParserImpl::expect_builtin() {
-  auto ident = expect_ident("builtin");
-  if (ident.errored)
-    return Failure::kErrored;
-
-  ast::Builtin builtin = ident_to_builtin(ident.value);
-  if (builtin == ast::Builtin::kNone)
-    return add_error(ident.source, "invalid value for builtin attribute");
-
-  return {builtin, ident.source};
-}
-
-// body_stmt
-//   : BRACE_LEFT statements BRACE_RIGHT
-Expect<ast::BlockStatement*> ParserImpl::expect_body_stmt() {
-  return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> {
-    auto stmts = expect_statements();
-    if (stmts.errored)
-      return Failure::kErrored;
-    return create<ast::BlockStatement>(Source{}, stmts.value);
-  });
-}
-
-// paren_rhs_stmt
-//   : PAREN_LEFT logical_or_expression PAREN_RIGHT
-Expect<const ast::Expression*> ParserImpl::expect_paren_rhs_stmt() {
-  return expect_paren_block("", [&]() -> Expect<const ast::Expression*> {
-    auto expr = logical_or_expression();
-    if (expr.errored)
-      return Failure::kErrored;
-    if (!expr.matched)
-      return add_error(peek(), "unable to parse expression");
-
-    return expr.value;
-  });
-}
-
-// statements
-//   : statement*
-Expect<ast::StatementList> ParserImpl::expect_statements() {
-  bool errored = false;
-  ast::StatementList stmts;
-
-  while (continue_parsing()) {
-    auto stmt = statement();
-    if (stmt.errored) {
-      errored = true;
-    } else if (stmt.matched) {
-      stmts.emplace_back(stmt.value);
-    } else {
-      break;
-    }
-  }
-
-  if (errored)
-    return Failure::kErrored;
-
-  return stmts;
-}
-
-// statement
-//   : SEMICOLON
-//   | body_stmt?
-//   | if_stmt
-//   | switch_stmt
-//   | loop_stmt
-//   | for_stmt
-//   | non_block_statement
-//      : return_stmt SEMICOLON
-//      | func_call_stmt SEMICOLON
-//      | variable_stmt SEMICOLON
-//      | break_stmt SEMICOLON
-//      | continue_stmt SEMICOLON
-//      | DISCARD SEMICOLON
-//      | assignment_stmt SEMICOLON
-Maybe<const ast::Statement*> ParserImpl::statement() {
-  while (match(Token::Type::kSemicolon)) {
-    // Skip empty statements
-  }
-
-  // Non-block statments that error can resynchronize on semicolon.
-  auto stmt =
-      sync(Token::Type::kSemicolon, [&] { return non_block_statement(); });
-
-  if (stmt.errored)
-    return Failure::kErrored;
-  if (stmt.matched)
-    return stmt;
-
-  auto stmt_if = if_stmt();
-  if (stmt_if.errored)
-    return Failure::kErrored;
-  if (stmt_if.matched)
-    return stmt_if.value;
-
-  auto sw = switch_stmt();
-  if (sw.errored)
-    return Failure::kErrored;
-  if (sw.matched)
-    return sw.value;
-
-  auto loop = loop_stmt();
-  if (loop.errored)
-    return Failure::kErrored;
-  if (loop.matched)
-    return loop.value;
-
-  auto stmt_for = for_stmt();
-  if (stmt_for.errored)
-    return Failure::kErrored;
-  if (stmt_for.matched)
-    return stmt_for.value;
-
-  if (peek_is(Token::Type::kBraceLeft)) {
-    auto body = expect_body_stmt();
-    if (body.errored)
-      return Failure::kErrored;
-    return body.value;
-  }
-
-  return Failure::kNoMatch;
-}
-
-// statement (continued)
-//   : return_stmt SEMICOLON
-//   | func_call_stmt SEMICOLON
-//   | variable_stmt SEMICOLON
-//   | break_stmt SEMICOLON
-//   | continue_stmt SEMICOLON
-//   | DISCARD SEMICOLON
-//   | assignment_stmt SEMICOLON
-Maybe<const ast::Statement*> ParserImpl::non_block_statement() {
-  auto stmt = [&]() -> Maybe<const ast::Statement*> {
-    auto ret_stmt = return_stmt();
-    if (ret_stmt.errored)
-      return Failure::kErrored;
-    if (ret_stmt.matched)
-      return ret_stmt.value;
-
-    auto func = func_call_stmt();
-    if (func.errored)
-      return Failure::kErrored;
-    if (func.matched)
-      return func.value;
-
-    auto var = variable_stmt();
-    if (var.errored)
-      return Failure::kErrored;
-    if (var.matched)
-      return var.value;
-
-    auto b = break_stmt();
-    if (b.errored)
-      return Failure::kErrored;
-    if (b.matched)
-      return b.value;
-
-    auto cont = continue_stmt();
-    if (cont.errored)
-      return Failure::kErrored;
-    if (cont.matched)
-      return cont.value;
-
-    auto assign = assignment_stmt();
-    if (assign.errored)
-      return Failure::kErrored;
-    if (assign.matched)
-      return assign.value;
-
-    Source source;
-    if (match(Token::Type::kDiscard, &source))
-      return create<ast::DiscardStatement>(source);
-
-    return Failure::kNoMatch;
-  }();
-
-  if (stmt.matched && !expect(stmt->Name(), Token::Type::kSemicolon))
-    return Failure::kErrored;
-
-  return stmt;
-}
-
-// return_stmt
-//   : RETURN logical_or_expression?
-Maybe<const ast::ReturnStatement*> ParserImpl::return_stmt() {
-  Source source;
-  if (!match(Token::Type::kReturn, &source))
-    return Failure::kNoMatch;
-
-  if (peek_is(Token::Type::kSemicolon))
-    return create<ast::ReturnStatement>(source, nullptr);
-
-  auto expr = logical_or_expression();
-  if (expr.errored)
-    return Failure::kErrored;
-
-  // TODO(bclayton): Check matched?
-  return create<ast::ReturnStatement>(source, expr.value);
-}
-
-// variable_stmt
-//   : variable_decl
-//   | variable_decl EQUAL logical_or_expression
-//   | CONST variable_ident_decl EQUAL logical_or_expression
-Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
-  if (match(Token::Type::kLet)) {
-    auto decl = expect_variable_ident_decl("let declaration",
-                                           /*allow_inferred = */ true);
-    if (decl.errored)
-      return Failure::kErrored;
-
-    if (!expect("let declaration", Token::Type::kEqual))
-      return Failure::kErrored;
-
-    auto constructor = logical_or_expression();
-    if (constructor.errored)
-      return Failure::kErrored;
-    if (!constructor.matched)
-      return add_error(peek(), "missing constructor for let declaration");
-
-    auto* var = create<ast::Variable>(
-        decl->source,                             // source
-        builder_.Symbols().Register(decl->name),  // symbol
-        ast::StorageClass::kNone,                 // storage class
-        ast::Access::kUndefined,                  // access control
-        decl->type,                               // type
-        true,                                     // is_const
-        false,                                    // is_overridable
-        constructor.value,                        // constructor
-        ast::AttributeList{});                    // attributes
-
-    return create<ast::VariableDeclStatement>(decl->source, var);
-  }
-
-  auto decl = variable_decl(/*allow_inferred = */ true);
-  if (decl.errored)
-    return Failure::kErrored;
-  if (!decl.matched)
-    return Failure::kNoMatch;
-
-  const ast::Expression* constructor = nullptr;
-  if (match(Token::Type::kEqual)) {
-    auto constructor_expr = logical_or_expression();
-    if (constructor_expr.errored)
-      return Failure::kErrored;
-    if (!constructor_expr.matched)
-      return add_error(peek(), "missing constructor for variable declaration");
-
-    constructor = constructor_expr.value;
-  }
-
-  auto* var =
-      create<ast::Variable>(decl->source,                             // source
-                            builder_.Symbols().Register(decl->name),  // symbol
-                            decl->storage_class,    // storage class
-                            decl->access,           // access control
-                            decl->type,             // type
-                            false,                  // is_const
-                            false,                  // is_overridable
-                            constructor,            // constructor
-                            ast::AttributeList{});  // attributes
-
-  return create<ast::VariableDeclStatement>(var->source, var);
-}
-
-// if_stmt
-//   : IF paren_rhs_stmt body_stmt ( ELSE else_stmts ) ?
-Maybe<const ast::IfStatement*> ParserImpl::if_stmt() {
-  Source source;
-  if (!match(Token::Type::kIf, &source))
-    return Failure::kNoMatch;
-
-  auto condition = expect_paren_rhs_stmt();
-  if (condition.errored)
-    return Failure::kErrored;
-
-  auto body = expect_body_stmt();
-  if (body.errored)
-    return Failure::kErrored;
-
-  auto el = else_stmts();
-  if (el.errored) {
-    return Failure::kErrored;
-  }
-
-  return create<ast::IfStatement>(source, condition.value, body.value,
-                                  std::move(el.value));
-}
-
-// else_stmts
-//  : body_stmt
-//  | if_stmt
-Expect<ast::ElseStatementList> ParserImpl::else_stmts() {
-  ast::ElseStatementList stmts;
-  while (continue_parsing()) {
-    Source start;
-
-    bool else_if = false;
-    if (match(Token::Type::kElse, &start)) {
-      else_if = match(Token::Type::kIf);
-    } else if (match(Token::Type::kElseIf, &start)) {
-      deprecated(start, "'elseif' is now 'else if'");
-      else_if = true;
-    } else {
-      break;
-    }
-
-    const ast::Expression* cond = nullptr;
-    if (else_if) {
-      auto condition = expect_paren_rhs_stmt();
-      if (condition.errored) {
-        return Failure::kErrored;
-      }
-      cond = condition.value;
-    }
-
-    auto body = expect_body_stmt();
-    if (body.errored) {
-      return Failure::kErrored;
-    }
-
-    Source source = make_source_range_from(start);
-    stmts.emplace_back(create<ast::ElseStatement>(source, cond, body.value));
-  }
-
-  return stmts;
-}
-
-// switch_stmt
-//   : SWITCH paren_rhs_stmt BRACKET_LEFT switch_body+ BRACKET_RIGHT
-Maybe<const ast::SwitchStatement*> ParserImpl::switch_stmt() {
-  Source source;
-  if (!match(Token::Type::kSwitch, &source))
-    return Failure::kNoMatch;
-
-  auto condition = expect_paren_rhs_stmt();
-  if (condition.errored)
-    return Failure::kErrored;
-
-  auto body = expect_brace_block("switch statement",
-                                 [&]() -> Expect<ast::CaseStatementList> {
-                                   bool errored = false;
-                                   ast::CaseStatementList list;
-                                   while (continue_parsing()) {
-                                     auto stmt = switch_body();
-                                     if (stmt.errored) {
-                                       errored = true;
-                                       continue;
-                                     }
-                                     if (!stmt.matched)
-                                       break;
-                                     list.push_back(stmt.value);
-                                   }
-                                   if (errored)
-                                     return Failure::kErrored;
-                                   return list;
-                                 });
-
-  if (body.errored)
-    return Failure::kErrored;
-
-  return create<ast::SwitchStatement>(source, condition.value, body.value);
-}
-
-// switch_body
-//   : CASE case_selectors COLON BRACKET_LEFT case_body BRACKET_RIGHT
-//   | DEFAULT COLON BRACKET_LEFT case_body BRACKET_RIGHT
-Maybe<const ast::CaseStatement*> ParserImpl::switch_body() {
-  if (!peek_is(Token::Type::kCase) && !peek_is(Token::Type::kDefault))
-    return Failure::kNoMatch;
-
-  auto t = next();
-  auto source = t.source();
-
-  ast::CaseSelectorList selector_list;
-  if (t.Is(Token::Type::kCase)) {
-    auto selectors = expect_case_selectors();
-    if (selectors.errored)
-      return Failure::kErrored;
-
-    selector_list = std::move(selectors.value);
-  }
-
-  const char* use = "case statement";
-
-  if (!expect(use, Token::Type::kColon))
-    return Failure::kErrored;
-
-  auto body = expect_brace_block(use, [&] { return case_body(); });
-
-  if (body.errored)
-    return Failure::kErrored;
-  if (!body.matched)
-    return add_error(body.source, "expected case body");
-
-  return create<ast::CaseStatement>(source, selector_list, body.value);
-}
-
-// case_selectors
-//   : const_literal (COMMA const_literal)* COMMA?
-Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() {
-  ast::CaseSelectorList selectors;
-
-  while (continue_parsing()) {
-    auto cond = const_literal();
-    if (cond.errored) {
-      return Failure::kErrored;
-    } else if (!cond.matched) {
-      break;
-    } else if (!cond->Is<ast::IntLiteralExpression>()) {
-      return add_error(cond.value->source,
-                       "invalid case selector must be an integer value");
-    }
-
-    selectors.push_back(cond.value->As<ast::IntLiteralExpression>());
-
-    if (!match(Token::Type::kComma)) {
-      break;
-    }
-  }
-
-  if (selectors.empty())
-    return add_error(peek(), "unable to parse case selectors");
-
-  return selectors;
-}
-
-// case_body
-//   :
-//   | statement case_body
-//   | FALLTHROUGH SEMICOLON
-Maybe<const ast::BlockStatement*> ParserImpl::case_body() {
-  ast::StatementList stmts;
-  while (continue_parsing()) {
-    Source source;
-    if (match(Token::Type::kFallthrough, &source)) {
-      if (!expect("fallthrough statement", Token::Type::kSemicolon))
-        return Failure::kErrored;
-
-      stmts.emplace_back(create<ast::FallthroughStatement>(source));
-      break;
-    }
-
-    auto stmt = statement();
-    if (stmt.errored)
-      return Failure::kErrored;
-    if (!stmt.matched)
-      break;
-
-    stmts.emplace_back(stmt.value);
-  }
-
-  return create<ast::BlockStatement>(Source{}, stmts);
-}
-
-// loop_stmt
-//   : LOOP BRACKET_LEFT statements continuing_stmt? BRACKET_RIGHT
-Maybe<const ast::LoopStatement*> ParserImpl::loop_stmt() {
-  Source source;
-  if (!match(Token::Type::kLoop, &source))
-    return Failure::kNoMatch;
-
-  return expect_brace_block("loop", [&]() -> Maybe<const ast::LoopStatement*> {
-    auto stmts = expect_statements();
-    if (stmts.errored)
-      return Failure::kErrored;
-
-    auto continuing = continuing_stmt();
-    if (continuing.errored)
-      return Failure::kErrored;
-
-    auto* body = create<ast::BlockStatement>(source, stmts.value);
-    return create<ast::LoopStatement>(source, body, continuing.value);
-  });
-}
-
-ForHeader::ForHeader(const ast::Statement* init,
-                     const ast::Expression* cond,
-                     const ast::Statement* cont)
-    : initializer(init), condition(cond), continuing(cont) {}
-
-ForHeader::~ForHeader() = default;
-
-// (variable_stmt | assignment_stmt | func_call_stmt)?
-Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
-  auto call = func_call_stmt();
-  if (call.errored)
-    return Failure::kErrored;
-  if (call.matched)
-    return call.value;
-
-  auto var = variable_stmt();
-  if (var.errored)
-    return Failure::kErrored;
-  if (var.matched)
-    return var.value;
-
-  auto assign = assignment_stmt();
-  if (assign.errored)
-    return Failure::kErrored;
-  if (assign.matched)
-    return assign.value;
-
-  return Failure::kNoMatch;
-}
-
-// (assignment_stmt | func_call_stmt)?
-Maybe<const ast::Statement*> ParserImpl::for_header_continuing() {
-  auto call_stmt = func_call_stmt();
-  if (call_stmt.errored)
-    return Failure::kErrored;
-  if (call_stmt.matched)
-    return call_stmt.value;
-
-  auto assign = assignment_stmt();
-  if (assign.errored)
-    return Failure::kErrored;
-  if (assign.matched)
-    return assign.value;
-
-  return Failure::kNoMatch;
-}
-
-// for_header
-//   : (variable_stmt | assignment_stmt | func_call_stmt)?
-//   SEMICOLON
-//      logical_or_expression? SEMICOLON
-//      (assignment_stmt | func_call_stmt)?
-Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
-  auto initializer = for_header_initializer();
-  if (initializer.errored)
-    return Failure::kErrored;
-
-  if (!expect("initializer in for loop", Token::Type::kSemicolon))
-    return Failure::kErrored;
-
-  auto condition = logical_or_expression();
-  if (condition.errored)
-    return Failure::kErrored;
-
-  if (!expect("condition in for loop", Token::Type::kSemicolon))
-    return Failure::kErrored;
-
-  auto continuing = for_header_continuing();
-  if (continuing.errored)
-    return Failure::kErrored;
-
-  return std::make_unique<ForHeader>(initializer.value, condition.value,
-                                     continuing.value);
-}
-
-// for_statement
-//   : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
-Maybe<const ast::ForLoopStatement*> ParserImpl::for_stmt() {
-  Source source;
-  if (!match(Token::Type::kFor, &source))
-    return Failure::kNoMatch;
-
-  auto header =
-      expect_paren_block("for loop", [&] { return expect_for_header(); });
-  if (header.errored)
-    return Failure::kErrored;
-
-  auto stmts =
-      expect_brace_block("for loop", [&] { return expect_statements(); });
-  if (stmts.errored)
-    return Failure::kErrored;
-
-  return create<ast::ForLoopStatement>(
-      source, header->initializer, header->condition, header->continuing,
-      create<ast::BlockStatement>(stmts.value));
-}
-
-// func_call_stmt
-//    : IDENT argument_expression_list
-Maybe<const ast::CallStatement*> ParserImpl::func_call_stmt() {
-  auto t = peek();
-  auto t2 = peek(1);
-  if (!t.IsIdentifier() || !t2.Is(Token::Type::kParenLeft))
-    return Failure::kNoMatch;
-
-  next();  // Consume the first peek
-
-  auto source = t.source();
-  auto name = t.to_str();
-
-  auto params = expect_argument_expression_list("function call");
-  if (params.errored)
-    return Failure::kErrored;
-
-  return create<ast::CallStatement>(
-      source, create<ast::CallExpression>(
-                  source,
-                  create<ast::IdentifierExpression>(
-                      source, builder_.Symbols().Register(name)),
-                  std::move(params.value)));
-}
-
-// break_stmt
-//   : BREAK
-Maybe<const ast::BreakStatement*> ParserImpl::break_stmt() {
-  Source source;
-  if (!match(Token::Type::kBreak, &source))
-    return Failure::kNoMatch;
-
-  return create<ast::BreakStatement>(source);
-}
-
-// continue_stmt
-//   : CONTINUE
-Maybe<const ast::ContinueStatement*> ParserImpl::continue_stmt() {
-  Source source;
-  if (!match(Token::Type::kContinue, &source))
-    return Failure::kNoMatch;
-
-  return create<ast::ContinueStatement>(source);
-}
-
-// continuing_stmt
-//   : CONTINUING body_stmt
-Maybe<const ast::BlockStatement*> ParserImpl::continuing_stmt() {
-  if (!match(Token::Type::kContinuing))
-    return create<ast::BlockStatement>(Source{}, ast::StatementList{});
-
-  return expect_body_stmt();
-}
-
-// primary_expression
-//   : IDENT argument_expression_list?
-//   | type_decl argument_expression_list
-//   | const_literal
-//   | paren_rhs_stmt
-//   | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
-Maybe<const ast::Expression*> ParserImpl::primary_expression() {
-  auto t = peek();
-  auto source = t.source();
-
-  auto lit = const_literal();
-  if (lit.errored) {
-    return Failure::kErrored;
-  }
-  if (lit.matched) {
-    return lit.value;
-  }
-
-  if (t.Is(Token::Type::kParenLeft)) {
-    auto paren = expect_paren_rhs_stmt();
-    if (paren.errored) {
-      return Failure::kErrored;
-    }
-
-    return paren.value;
-  }
-
-  if (match(Token::Type::kBitcast)) {
-    const char* use = "bitcast expression";
-
-    auto type = expect_lt_gt_block(use, [&] { return expect_type(use); });
-    if (type.errored)
-      return Failure::kErrored;
-
-    auto params = expect_paren_rhs_stmt();
-    if (params.errored)
-      return Failure::kErrored;
-
-    return create<ast::BitcastExpression>(source, type.value, params.value);
-  }
-
-  if (t.IsIdentifier()) {
-    next();
-
-    auto* ident = create<ast::IdentifierExpression>(
-        t.source(), builder_.Symbols().Register(t.to_str()));
-
-    if (peek_is(Token::Type::kParenLeft)) {
-      auto params = expect_argument_expression_list("function call");
-      if (params.errored)
-        return Failure::kErrored;
-
-      return create<ast::CallExpression>(source, ident,
-                                         std::move(params.value));
-    }
-
-    return ident;
-  }
-
-  auto type = type_decl();
-  if (type.errored)
-    return Failure::kErrored;
-  if (type.matched) {
-    auto params = expect_argument_expression_list("type constructor");
-    if (params.errored)
-      return Failure::kErrored;
-
-    return builder_.Construct(source, type.value, std::move(params.value));
-  }
-
-  return Failure::kNoMatch;
-}
-
-// postfix_expression
-//   :
-//   | BRACE_LEFT logical_or_expression BRACE_RIGHT postfix_expr
-//   | PERIOD IDENTIFIER postfix_expr
-Maybe<const ast::Expression*> ParserImpl::postfix_expression(
-    const ast::Expression* prefix) {
-  Source source;
-
-  while (continue_parsing()) {
-    if (match(Token::Type::kPlusPlus, &source) ||
-        match(Token::Type::kMinusMinus, &source)) {
-      add_error(source,
-                "postfix increment and decrement operators are reserved for a "
-                "future WGSL version");
-      return Failure::kErrored;
-    }
-
-    if (match(Token::Type::kBracketLeft, &source)) {
-      auto res = sync(
-          Token::Type::kBracketRight, [&]() -> Maybe<const ast::Expression*> {
-            auto param = logical_or_expression();
-            if (param.errored)
-              return Failure::kErrored;
-            if (!param.matched) {
-              return add_error(peek(), "unable to parse expression inside []");
-            }
-
-            if (!expect("index accessor", Token::Type::kBracketRight)) {
-              return Failure::kErrored;
-            }
-
-            return create<ast::IndexAccessorExpression>(source, prefix,
-                                                        param.value);
-          });
-
-      if (res.errored) {
-        return res;
-      }
-      prefix = res.value;
-      continue;
-    }
-
-    if (match(Token::Type::kPeriod)) {
-      auto ident = expect_ident("member accessor");
-      if (ident.errored) {
-        return Failure::kErrored;
-      }
-
-      prefix = create<ast::MemberAccessorExpression>(
-          ident.source, prefix,
-          create<ast::IdentifierExpression>(
-              ident.source, builder_.Symbols().Register(ident.value)));
-      continue;
-    }
-
-    return prefix;
-  }
-
-  return Failure::kErrored;
-}
-
-// singular_expression
-//   : primary_expression postfix_expr
-Maybe<const ast::Expression*> ParserImpl::singular_expression() {
-  auto prefix = primary_expression();
-  if (prefix.errored)
-    return Failure::kErrored;
-  if (!prefix.matched)
-    return Failure::kNoMatch;
-
-  return postfix_expression(prefix.value);
-}
-
-// argument_expression_list
-//   : PAREN_LEFT ((logical_or_expression COMMA)* logical_or_expression COMMA?)?
-//   PAREN_RIGHT
-Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list(
-    std::string_view use) {
-  return expect_paren_block(use, [&]() -> Expect<ast::ExpressionList> {
-    ast::ExpressionList ret;
-    while (continue_parsing()) {
-      auto arg = logical_or_expression();
-      if (arg.errored) {
-        return Failure::kErrored;
-      } else if (!arg.matched) {
-        break;
-      }
-      ret.push_back(arg.value);
-
-      if (!match(Token::Type::kComma)) {
-        break;
-      }
-    }
-    return ret;
-  });
-}
-
-// unary_expression
-//   : singular_expression
-//   | MINUS unary_expression
-//   | BANG unary_expression
-//   | TILDE unary_expression
-//   | STAR unary_expression
-//   | AND unary_expression
-Maybe<const ast::Expression*> ParserImpl::unary_expression() {
-  auto t = peek();
-
-  if (match(Token::Type::kPlusPlus) || match(Token::Type::kMinusMinus)) {
-    add_error(t.source(),
-              "prefix increment and decrement operators are reserved for a "
-              "future WGSL version");
-    return Failure::kErrored;
-  }
-
-  ast::UnaryOp op;
-  if (match(Token::Type::kMinus)) {
-    op = ast::UnaryOp::kNegation;
-  } else if (match(Token::Type::kBang)) {
-    op = ast::UnaryOp::kNot;
-  } else if (match(Token::Type::kTilde)) {
-    op = ast::UnaryOp::kComplement;
-  } else if (match(Token::Type::kStar)) {
-    op = ast::UnaryOp::kIndirection;
-  } else if (match(Token::Type::kAnd)) {
-    op = ast::UnaryOp::kAddressOf;
-  } else {
-    return singular_expression();
-  }
-
-  if (parse_depth_ >= kMaxParseDepth) {
-    // We've hit a maximum parser recursive depth.
-    // We can't call into unary_expression() as we might stack overflow.
-    // Instead, report an error
-    add_error(peek(), "maximum parser recursive depth reached");
-    return Failure::kErrored;
-  }
-
-  ++parse_depth_;
-  auto expr = unary_expression();
-  --parse_depth_;
-
-  if (expr.errored) {
-    return Failure::kErrored;
-  }
-  if (!expr.matched) {
-    return add_error(peek(), "unable to parse right side of " +
-                                 std::string(t.to_name()) + " expression");
-  }
-
-  return create<ast::UnaryOpExpression>(t.source(), op, expr.value);
-}
-
-// multiplicative_expr
-//   :
-//   | STAR unary_expression multiplicative_expr
-//   | FORWARD_SLASH unary_expression multiplicative_expr
-//   | MODULO unary_expression multiplicative_expr
-Expect<const ast::Expression*> ParserImpl::expect_multiplicative_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    ast::BinaryOp op = ast::BinaryOp::kNone;
-    if (peek_is(Token::Type::kStar))
-      op = ast::BinaryOp::kMultiply;
-    else if (peek_is(Token::Type::kForwardSlash))
-      op = ast::BinaryOp::kDivide;
-    else if (peek_is(Token::Type::kMod))
-      op = ast::BinaryOp::kModulo;
-    else
-      return lhs;
-
-    auto t = next();
-    auto source = t.source();
-    auto name = t.to_name();
-
-    auto rhs = unary_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched) {
-      return add_error(peek(), "unable to parse right side of " +
-                                   std::string(name) + " expression");
-    }
-
-    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// multiplicative_expression
-//   : unary_expression multiplicative_expr
-Maybe<const ast::Expression*> ParserImpl::multiplicative_expression() {
-  auto lhs = unary_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_multiplicative_expr(lhs.value);
-}
-
-// additive_expr
-//   :
-//   | PLUS multiplicative_expression additive_expr
-//   | MINUS multiplicative_expression additive_expr
-Expect<const ast::Expression*> ParserImpl::expect_additive_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    ast::BinaryOp op = ast::BinaryOp::kNone;
-    if (peek_is(Token::Type::kPlus))
-      op = ast::BinaryOp::kAdd;
-    else if (peek_is(Token::Type::kMinus))
-      op = ast::BinaryOp::kSubtract;
-    else
-      return lhs;
-
-    auto t = next();
-    auto source = t.source();
-
-    auto rhs = multiplicative_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of + expression");
-
-    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// additive_expression
-//   : multiplicative_expression additive_expr
-Maybe<const ast::Expression*> ParserImpl::additive_expression() {
-  auto lhs = multiplicative_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_additive_expr(lhs.value);
-}
-
-// shift_expr
-//   :
-//   | SHIFT_LEFT additive_expression shift_expr
-//   | SHIFT_RIGHT additive_expression shift_expr
-Expect<const ast::Expression*> ParserImpl::expect_shift_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    auto* name = "";
-    ast::BinaryOp op = ast::BinaryOp::kNone;
-    if (peek_is(Token::Type::kShiftLeft)) {
-      op = ast::BinaryOp::kShiftLeft;
-      name = "<<";
-    } else if (peek_is(Token::Type::kShiftRight)) {
-      op = ast::BinaryOp::kShiftRight;
-      name = ">>";
-    } else {
-      return lhs;
-    }
-
-    auto t = next();
-    auto source = t.source();
-    auto rhs = additive_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched) {
-      return add_error(peek(), std::string("unable to parse right side of ") +
-                                   name + " expression");
-    }
-
-    return lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// shift_expression
-//   : additive_expression shift_expr
-Maybe<const ast::Expression*> ParserImpl::shift_expression() {
-  auto lhs = additive_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_shift_expr(lhs.value);
-}
-
-// relational_expr
-//   :
-//   | LESS_THAN shift_expression relational_expr
-//   | GREATER_THAN shift_expression relational_expr
-//   | LESS_THAN_EQUAL shift_expression relational_expr
-//   | GREATER_THAN_EQUAL shift_expression relational_expr
-Expect<const ast::Expression*> ParserImpl::expect_relational_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    ast::BinaryOp op = ast::BinaryOp::kNone;
-    if (peek_is(Token::Type::kLessThan))
-      op = ast::BinaryOp::kLessThan;
-    else if (peek_is(Token::Type::kGreaterThan))
-      op = ast::BinaryOp::kGreaterThan;
-    else if (peek_is(Token::Type::kLessThanEqual))
-      op = ast::BinaryOp::kLessThanEqual;
-    else if (peek_is(Token::Type::kGreaterThanEqual))
-      op = ast::BinaryOp::kGreaterThanEqual;
-    else
-      return lhs;
-
-    auto t = next();
-    auto source = t.source();
-    auto name = t.to_name();
-
-    auto rhs = shift_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched) {
-      return add_error(peek(), "unable to parse right side of " +
-                                   std::string(name) + " expression");
-    }
-
-    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// relational_expression
-//   : shift_expression relational_expr
-Maybe<const ast::Expression*> ParserImpl::relational_expression() {
-  auto lhs = shift_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_relational_expr(lhs.value);
-}
-
-// equality_expr
-//   :
-//   | EQUAL_EQUAL relational_expression equality_expr
-//   | NOT_EQUAL relational_expression equality_expr
-Expect<const ast::Expression*> ParserImpl::expect_equality_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    ast::BinaryOp op = ast::BinaryOp::kNone;
-    if (peek_is(Token::Type::kEqualEqual))
-      op = ast::BinaryOp::kEqual;
-    else if (peek_is(Token::Type::kNotEqual))
-      op = ast::BinaryOp::kNotEqual;
-    else
-      return lhs;
-
-    auto t = next();
-    auto source = t.source();
-    auto name = t.to_name();
-
-    auto rhs = relational_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched) {
-      return add_error(peek(), "unable to parse right side of " +
-                                   std::string(name) + " expression");
-    }
-
-    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// equality_expression
-//   : relational_expression equality_expr
-Maybe<const ast::Expression*> ParserImpl::equality_expression() {
-  auto lhs = relational_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_equality_expr(lhs.value);
-}
-
-// and_expr
-//   :
-//   | AND equality_expression and_expr
-Expect<const ast::Expression*> ParserImpl::expect_and_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    if (!peek_is(Token::Type::kAnd)) {
-      return lhs;
-    }
-
-    auto t = next();
-    auto source = t.source();
-
-    auto rhs = equality_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of & expression");
-
-    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kAnd, lhs,
-                                        rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// and_expression
-//   : equality_expression and_expr
-Maybe<const ast::Expression*> ParserImpl::and_expression() {
-  auto lhs = equality_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_and_expr(lhs.value);
-}
-
-// exclusive_or_expr
-//   :
-//   | XOR and_expression exclusive_or_expr
-Expect<const ast::Expression*> ParserImpl::expect_exclusive_or_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    Source source;
-    if (!match(Token::Type::kXor, &source))
-      return lhs;
-
-    auto rhs = and_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of ^ expression");
-
-    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kXor, lhs,
-                                        rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// exclusive_or_expression
-//   : and_expression exclusive_or_expr
-Maybe<const ast::Expression*> ParserImpl::exclusive_or_expression() {
-  auto lhs = and_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_exclusive_or_expr(lhs.value);
-}
-
-// inclusive_or_expr
-//   :
-//   | OR exclusive_or_expression inclusive_or_expr
-Expect<const ast::Expression*> ParserImpl::expect_inclusive_or_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    Source source;
-    if (!match(Token::Type::kOr))
-      return lhs;
-
-    auto rhs = exclusive_or_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of | expression");
-
-    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kOr, lhs,
-                                        rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// inclusive_or_expression
-//   : exclusive_or_expression inclusive_or_expr
-Maybe<const ast::Expression*> ParserImpl::inclusive_or_expression() {
-  auto lhs = exclusive_or_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_inclusive_or_expr(lhs.value);
-}
-
-// logical_and_expr
-//   :
-//   | AND_AND inclusive_or_expression logical_and_expr
-Expect<const ast::Expression*> ParserImpl::expect_logical_and_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    if (!peek_is(Token::Type::kAndAnd)) {
-      return lhs;
-    }
-
-    auto t = next();
-    auto source = t.source();
-
-    auto rhs = inclusive_or_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of && expression");
-
-    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalAnd, lhs,
-                                        rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// logical_and_expression
-//   : inclusive_or_expression logical_and_expr
-Maybe<const ast::Expression*> ParserImpl::logical_and_expression() {
-  auto lhs = inclusive_or_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_logical_and_expr(lhs.value);
-}
-
-// logical_or_expr
-//   :
-//   | OR_OR logical_and_expression logical_or_expr
-Expect<const ast::Expression*> ParserImpl::expect_logical_or_expr(
-    const ast::Expression* lhs) {
-  while (continue_parsing()) {
-    Source source;
-    if (!match(Token::Type::kOrOr))
-      return lhs;
-
-    auto rhs = logical_and_expression();
-    if (rhs.errored)
-      return Failure::kErrored;
-    if (!rhs.matched)
-      return add_error(peek(), "unable to parse right side of || expression");
-
-    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalOr, lhs,
-                                        rhs.value);
-  }
-  return Failure::kErrored;
-}
-
-// logical_or_expression
-//   : logical_and_expression logical_or_expr
-Maybe<const ast::Expression*> ParserImpl::logical_or_expression() {
-  auto lhs = logical_and_expression();
-  if (lhs.errored)
-    return Failure::kErrored;
-  if (!lhs.matched)
-    return Failure::kNoMatch;
-
-  return expect_logical_or_expr(lhs.value);
-}
-
-// assignment_stmt
-//   : (unary_expression | underscore) EQUAL logical_or_expression
-Maybe<const ast::AssignmentStatement*> ParserImpl::assignment_stmt() {
-  auto t = peek();
-  auto source = t.source();
-
-  // tint:295 - Test for `ident COLON` - this is invalid grammar, and without
-  // special casing will error as "missing = for assignment", which is less
-  // helpful than this error message:
-  if (peek_is(Token::Type::kIdentifier) && peek_is(Token::Type::kColon, 1)) {
-    return add_error(peek(0).source(),
-                     "expected 'var' for variable declaration");
-  }
-
-  auto lhs = unary_expression();
-  if (lhs.errored) {
-    return Failure::kErrored;
-  }
-  if (!lhs.matched) {
-    if (!match(Token::Type::kUnderscore, &source)) {
-      return Failure::kNoMatch;
-    }
-    lhs = create<ast::PhonyExpression>(source);
-  }
-
-  if (!expect("assignment", Token::Type::kEqual)) {
-    return Failure::kErrored;
-  }
-
-  auto rhs = logical_or_expression();
-  if (rhs.errored) {
-    return Failure::kErrored;
-  }
-  if (!rhs.matched) {
-    return add_error(peek(), "unable to parse right side of assignment");
-  }
-
-  return create<ast::AssignmentStatement>(source, lhs.value, rhs.value);
-}
-
-// const_literal
-//   : INT_LITERAL
-//   | UINT_LITERAL
-//   | FLOAT_LITERAL
-//   | TRUE
-//   | FALSE
-Maybe<const ast::LiteralExpression*> ParserImpl::const_literal() {
-  auto t = peek();
-  if (t.IsError()) {
-    return add_error(t.source(), t.to_str());
-  }
-  if (match(Token::Type::kTrue)) {
-    return create<ast::BoolLiteralExpression>(t.source(), true);
-  }
-  if (match(Token::Type::kFalse)) {
-    return create<ast::BoolLiteralExpression>(t.source(), false);
-  }
-  if (match(Token::Type::kSintLiteral)) {
-    return create<ast::SintLiteralExpression>(t.source(), t.to_i32());
-  }
-  if (match(Token::Type::kUintLiteral)) {
-    return create<ast::UintLiteralExpression>(t.source(), t.to_u32());
-  }
-  if (match(Token::Type::kFloatLiteral)) {
-    return create<ast::FloatLiteralExpression>(t.source(), t.to_f32());
-  }
-  return Failure::kNoMatch;
-}
-
-// const_expr
-//   : type_decl PAREN_LEFT ((const_expr COMMA)? const_expr COMMA?)? PAREN_RIGHT
-//   | const_literal
-Expect<const ast::Expression*> ParserImpl::expect_const_expr() {
-  auto t = peek();
-  auto source = t.source();
-  if (t.IsLiteral()) {
-    auto lit = const_literal();
-    if (lit.errored) {
-      return Failure::kErrored;
-    }
-    if (!lit.matched) {
-      return add_error(peek(), "unable to parse constant literal");
-    }
-    return lit.value;
-  }
-
-  if (peek_is(Token::Type::kParenLeft, 1) ||
-      peek_is(Token::Type::kLessThan, 1)) {
-    auto type = expect_type("const_expr");
-    if (type.errored) {
-      return Failure::kErrored;
-    }
-
-    auto params = expect_paren_block(
-        "type constructor", [&]() -> Expect<ast::ExpressionList> {
-          ast::ExpressionList list;
-          while (continue_parsing()) {
-            if (peek_is(Token::Type::kParenRight)) {
-              break;
-            }
-
-            auto arg = expect_const_expr();
-            if (arg.errored) {
-              return Failure::kErrored;
-            }
-            list.emplace_back(arg.value);
-
-            if (!match(Token::Type::kComma)) {
-              break;
-            }
-          }
-          return list;
-        });
-
-    if (params.errored)
-      return Failure::kErrored;
-
-    return builder_.Construct(source, type.value, params.value);
-  }
-  return add_error(peek(), "unable to parse const_expr");
-}
-
-Maybe<ast::AttributeList> ParserImpl::attribute_list() {
-  bool errored = false;
-  bool matched = false;
-  ast::AttributeList attrs;
-
-  while (continue_parsing()) {
-    if (match(Token::Type::kAttr)) {
-      if (auto attr = expect_attribute(); attr.errored) {
-        errored = true;
-      } else {
-        attrs.emplace_back(attr.value);
-      }
-    } else {  // [DEPRECATED] - old [[attribute]] style
-      auto list = attribute_bracketed_list(attrs);
-      if (list.errored) {
-        errored = true;
-      }
-      if (!list.matched) {
-        break;
-      }
-    }
-
-    matched = true;
-  }
-
-  if (errored)
-    return Failure::kErrored;
-
-  if (!matched)
-    return Failure::kNoMatch;
-
-  return attrs;
-}
-
-Maybe<bool> ParserImpl::attribute_bracketed_list(ast::AttributeList& attrs) {
-  const char* use = "attribute list";
-
-  Source source;
-  if (!match(Token::Type::kAttrLeft, &source)) {
-    return Failure::kNoMatch;
-  }
-
-  deprecated(source,
-             "[[attribute]] style attributes have been replaced with "
-             "@attribute style");
-
-  if (match(Token::Type::kAttrRight, &source))
-    return add_error(source, "empty attribute list");
-
-  return sync(Token::Type::kAttrRight, [&]() -> Expect<bool> {
-    bool errored = false;
-
-    while (continue_parsing()) {
-      auto attr = expect_attribute();
-      if (attr.errored) {
-        errored = true;
-      }
-      attrs.emplace_back(attr.value);
-
-      if (match(Token::Type::kComma)) {
-        continue;
-      }
-
-      if (is_attribute(peek())) {
-        // We have two attributes in a bracket without a separating comma.
-        // e.g. @location(1) group(2)
-        //                    ^^^ expected comma
-        expect(use, Token::Type::kComma);
-        return Failure::kErrored;
-      }
-
-      break;
-    }
-
-    if (errored) {
-      return Failure::kErrored;
-    }
-
-    if (!expect(use, Token::Type::kAttrRight)) {
-      return Failure::kErrored;
-    }
-
-    return true;
-  });
-}
-
-Expect<const ast::Attribute*> ParserImpl::expect_attribute() {
-  auto t = peek();
-  auto attr = attribute();
-  if (attr.errored)
-    return Failure::kErrored;
-  if (attr.matched)
-    return attr.value;
-  return add_error(t, "expected attribute");
-}
-
-Maybe<const ast::Attribute*> ParserImpl::attribute() {
-  using Result = Maybe<const ast::Attribute*>;
-  auto t = next();
-
-  if (!t.IsIdentifier()) {
-    return Failure::kNoMatch;
-  }
-
-  if (t == kLocationAttribute) {
-    const char* use = "location attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::LocationAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kBindingAttribute) {
-    const char* use = "binding attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::BindingAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kGroupAttribute) {
-    const char* use = "group attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::GroupAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kInterpolateAttribute) {
-    return expect_paren_block("interpolate attribute", [&]() -> Result {
-      ast::InterpolationType type;
-      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
-
-      auto type_tok = next();
-      if (type_tok == "perspective") {
-        type = ast::InterpolationType::kPerspective;
-      } else if (type_tok == "linear") {
-        type = ast::InterpolationType::kLinear;
-      } else if (type_tok == "flat") {
-        type = ast::InterpolationType::kFlat;
-      } else {
-        return add_error(type_tok, "invalid interpolation type");
-      }
-
-      if (match(Token::Type::kComma)) {
-        auto sampling_tok = next();
-        if (sampling_tok == "center") {
-          sampling = ast::InterpolationSampling::kCenter;
-        } else if (sampling_tok == "centroid") {
-          sampling = ast::InterpolationSampling::kCentroid;
-        } else if (sampling_tok == "sample") {
-          sampling = ast::InterpolationSampling::kSample;
-        } else {
-          return add_error(sampling_tok, "invalid interpolation sampling");
-        }
-      }
-
-      return create<ast::InterpolateAttribute>(t.source(), type, sampling);
-    });
-  }
-
-  if (t == kInvariantAttribute) {
-    return create<ast::InvariantAttribute>(t.source());
-  }
-
-  if (t == kBuiltinAttribute) {
-    return expect_paren_block("builtin attribute", [&]() -> Result {
-      auto builtin = expect_builtin();
-      if (builtin.errored)
-        return Failure::kErrored;
-
-      return create<ast::BuiltinAttribute>(t.source(), builtin.value);
-    });
-  }
-
-  if (t == kWorkgroupSizeAttribute) {
-    return expect_paren_block("workgroup_size attribute", [&]() -> Result {
-      const ast::Expression* x = nullptr;
-      const ast::Expression* y = nullptr;
-      const ast::Expression* z = nullptr;
-
-      auto expr = primary_expression();
-      if (expr.errored) {
-        return Failure::kErrored;
-      } else if (!expr.matched) {
-        return add_error(peek(), "expected workgroup_size x parameter");
-      }
-      x = std::move(expr.value);
-
-      if (match(Token::Type::kComma)) {
-        expr = primary_expression();
-        if (expr.errored) {
-          return Failure::kErrored;
-        } else if (!expr.matched) {
-          return add_error(peek(), "expected workgroup_size y parameter");
-        }
-        y = std::move(expr.value);
-
-        if (match(Token::Type::kComma)) {
-          expr = primary_expression();
-          if (expr.errored) {
-            return Failure::kErrored;
-          } else if (!expr.matched) {
-            return add_error(peek(), "expected workgroup_size z parameter");
-          }
-          z = std::move(expr.value);
-        }
-      }
-
-      return create<ast::WorkgroupAttribute>(t.source(), x, y, z);
-    });
-  }
-
-  if (t == kStageAttribute) {
-    return expect_paren_block("stage attribute", [&]() -> Result {
-      auto stage = expect_pipeline_stage();
-      if (stage.errored)
-        return Failure::kErrored;
-
-      return create<ast::StageAttribute>(t.source(), stage.value);
-    });
-  }
-
-  if (t == kBlockAttribute) {
-    deprecated(t.source(), "[[block]] attributes have been removed from WGSL");
-    return create<ast::StructBlockAttribute>(t.source());
-  }
-
-  if (t == kStrideAttribute) {
-    const char* use = "stride attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_nonzero_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-      deprecated(t.source(),
-                 "the @stride attribute is deprecated; use a larger type if "
-                 "necessary");
-      return create<ast::StrideAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kSizeAttribute) {
-    const char* use = "size attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::StructMemberSizeAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kAlignAttribute) {
-    const char* use = "align attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::StructMemberAlignAttribute>(t.source(), val.value);
-    });
-  }
-
-  if (t == kIdAttribute) {
-    const char* use = "id attribute";
-    return expect_paren_block(use, [&]() -> Result {
-      auto val = expect_positive_sint(use);
-      if (val.errored)
-        return Failure::kErrored;
-
-      return create<ast::IdAttribute>(t.source(), val.value);
-    });
-  }
-
-  return Failure::kNoMatch;
-}
-
-bool ParserImpl::expect_attributes_consumed(ast::AttributeList& in) {
-  if (in.empty()) {
-    return true;
-  }
-  add_error(in[0]->source, "unexpected attributes");
-  return false;
-}
-
-bool ParserImpl::match(Token::Type tok, Source* source /*= nullptr*/) {
-  auto t = peek();
-
-  if (source != nullptr)
-    *source = t.source();
-
-  if (t.Is(tok)) {
-    next();
-    return true;
-  }
-  return false;
-}
-
-bool ParserImpl::expect(std::string_view use, Token::Type tok) {
-  auto t = peek();
-  if (t.Is(tok)) {
-    next();
-    synchronized_ = true;
-    return true;
-  }
-
-  // Special case to split `>>` and `>=` tokens if we are looking for a `>`.
-  if (tok == Token::Type::kGreaterThan &&
-      (t.Is(Token::Type::kShiftRight) ||
-       t.Is(Token::Type::kGreaterThanEqual))) {
-    next();
-
-    // Push the second character to the token queue.
-    auto source = t.source();
-    source.range.begin.column++;
-    if (t.Is(Token::Type::kShiftRight)) {
-      token_queue_.push_front(Token(Token::Type::kGreaterThan, source));
-    } else if (t.Is(Token::Type::kGreaterThanEqual)) {
-      token_queue_.push_front(Token(Token::Type::kEqual, source));
-    }
-
-    synchronized_ = true;
-    return true;
-  }
-
-  // Handle the case when `]` is expected but the actual token is `]]`.
-  // For example, in `arr1[arr2[0]]`.
-  if (tok == Token::Type::kBracketRight && t.Is(Token::Type::kAttrRight)) {
-    next();
-    auto source = t.source();
-    source.range.begin.column++;
-    token_queue_.push_front({Token::Type::kBracketRight, source});
-    synchronized_ = true;
-    return true;
-  }
-
-  std::stringstream err;
-  err << "expected '" << Token::TypeToName(tok) << "'";
-  if (!use.empty()) {
-    err << " for " << use;
-  }
-  add_error(t, err.str());
-  synchronized_ = false;
-  return false;
-}
-
-Expect<int32_t> ParserImpl::expect_sint(std::string_view use) {
-  auto t = peek();
-  if (!t.Is(Token::Type::kSintLiteral))
-    return add_error(t.source(), "expected signed integer literal", use);
-
-  next();
-  return {t.to_i32(), t.source()};
-}
-
-Expect<uint32_t> ParserImpl::expect_positive_sint(std::string_view use) {
-  auto sint = expect_sint(use);
-  if (sint.errored)
-    return Failure::kErrored;
-
-  if (sint.value < 0)
-    return add_error(sint.source, std::string(use) + " must be positive");
-
-  return {static_cast<uint32_t>(sint.value), sint.source};
-}
-
-Expect<uint32_t> ParserImpl::expect_nonzero_positive_sint(
-    std::string_view use) {
-  auto sint = expect_sint(use);
-  if (sint.errored)
-    return Failure::kErrored;
-
-  if (sint.value <= 0)
-    return add_error(sint.source, std::string(use) + " must be greater than 0");
-
-  return {static_cast<uint32_t>(sint.value), sint.source};
-}
-
-Expect<std::string> ParserImpl::expect_ident(std::string_view use) {
-  auto t = peek();
-  if (t.IsIdentifier()) {
-    synchronized_ = true;
-    next();
-
-    if (is_reserved(t)) {
-      return add_error(t.source(),
-                       "'" + t.to_str() + "' is a reserved keyword");
-    }
-
-    return {t.to_str(), t.source()};
-  }
-  synchronized_ = false;
-  return add_error(t.source(), "expected identifier", use);
-}
-
-template <typename F, typename T>
-T ParserImpl::expect_block(Token::Type start,
-                           Token::Type end,
-                           std::string_view use,
-                           F&& body) {
-  if (!expect(use, start)) {
-    return Failure::kErrored;
-  }
-
-  return sync(end, [&]() -> T {
-    auto res = body();
-
-    if (res.errored)
-      return Failure::kErrored;
-
-    if (!expect(use, end))
-      return Failure::kErrored;
-
-    return res;
-  });
-}
-
-template <typename F, typename T>
-T ParserImpl::expect_paren_block(std::string_view use, F&& body) {
-  return expect_block(Token::Type::kParenLeft, Token::Type::kParenRight, use,
-                      std::forward<F>(body));
-}
-
-template <typename F, typename T>
-T ParserImpl::expect_brace_block(std::string_view use, F&& body) {
-  return expect_block(Token::Type::kBraceLeft, Token::Type::kBraceRight, use,
-                      std::forward<F>(body));
-}
-
-template <typename F, typename T>
-T ParserImpl::expect_lt_gt_block(std::string_view use, F&& body) {
-  return expect_block(Token::Type::kLessThan, Token::Type::kGreaterThan, use,
-                      std::forward<F>(body));
-}
-
-template <typename F, typename T>
-T ParserImpl::sync(Token::Type tok, F&& body) {
-  if (parse_depth_ >= kMaxParseDepth) {
-    // We've hit a maximum parser recursive depth.
-    // We can't call into body() as we might stack overflow.
-    // Instead, report an error...
-    add_error(peek(), "maximum parser recursive depth reached");
-    // ...and try to resynchronize. If we cannot resynchronize to `tok` then
-    // synchronized_ is set to false, and the parser knows that forward progress
-    // is not being made.
-    sync_to(tok, /* consume: */ true);
-    return Failure::kErrored;
-  }
-
-  sync_tokens_.push_back(tok);
-
-  ++parse_depth_;
-  auto result = body();
-  --parse_depth_;
-
-  if (sync_tokens_.back() != tok) {
-    TINT_ICE(Reader, builder_.Diagnostics()) << "sync_tokens is out of sync";
-  }
-  sync_tokens_.pop_back();
-
-  if (result.errored) {
-    sync_to(tok, /* consume: */ true);
-  }
-
-  return result;
-}
-
-bool ParserImpl::sync_to(Token::Type tok, bool consume) {
-  // Clear the synchronized state - gets set to true again on success.
-  synchronized_ = false;
-
-  BlockCounters counters;
-
-  for (size_t i = 0; i < kMaxResynchronizeLookahead; i++) {
-    auto t = peek(i);
-    if (counters.consume(t) > 0) {
-      continue;  // Nested block
-    }
-    if (!t.Is(tok) && !is_sync_token(t)) {
-      continue;  // Not a synchronization point
-    }
-
-    // Synchronization point found.
-
-    // Skip any tokens we don't understand, bringing us to just before the
-    // resync point.
-    while (i-- > 0) {
-      next();
-    }
-
-    // Is this synchronization token |tok|?
-    if (t.Is(tok)) {
-      if (consume) {
-        next();
-      }
-      synchronized_ = true;
-      return true;
-    }
-    break;
-  }
-
-  return false;
-}
-
-bool ParserImpl::is_sync_token(const Token& t) const {
-  for (auto r : sync_tokens_) {
-    if (t.Is(r)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-template <typename F, typename T>
-T ParserImpl::without_error(F&& body) {
-  silence_errors_++;
-  auto result = body();
-  silence_errors_--;
-  return result;
-}
-
-ParserImpl::MultiTokenSource ParserImpl::make_source_range() {
-  return MultiTokenSource(this);
-}
-
-ParserImpl::MultiTokenSource ParserImpl::make_source_range_from(
-    const Source& start) {
-  return MultiTokenSource(this, start);
-}
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
deleted file mode 100644
index 60c6d39..0000000
--- a/src/reader/wgsl/parser_impl.h
+++ /dev/null
@@ -1,891 +0,0 @@
-// Copyright 2020 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_READER_WGSL_PARSER_IMPL_H_
-#define SRC_READER_WGSL_PARSER_IMPL_H_
-
-#include <deque>
-#include <memory>
-#include <string>
-#include <string_view>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/program_builder.h"
-#include "src/reader/wgsl/parser_impl_detail.h"
-#include "src/reader/wgsl/token.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace ast {
-class AssignmentStatement;
-class BreakStatement;
-class CallStatement;
-class ContinueStatement;
-class IfStatement;
-class LoopStatement;
-class ReturnStatement;
-class SwitchStatement;
-class VariableDeclStatement;
-}  // namespace ast
-
-namespace reader {
-namespace wgsl {
-
-class Lexer;
-
-/// Struct holding information for a for loop
-struct ForHeader {
-  /// Constructor
-  /// @param init the initializer statement
-  /// @param cond the condition statement
-  /// @param cont the continuing statement
-  ForHeader(const ast::Statement* init,
-            const ast::Expression* cond,
-            const ast::Statement* cont);
-
-  ~ForHeader();
-
-  /// The for loop initializer
-  const ast::Statement* initializer = nullptr;
-  /// The for loop condition
-  const ast::Expression* condition = nullptr;
-  /// The for loop continuing statement
-  const ast::Statement* continuing = nullptr;
-};
-
-/// ParserImpl for WGSL source data
-class ParserImpl {
-  /// Failure holds enumerator values used for the constructing an Expect and
-  /// Match in an errored state.
-  struct Failure {
-    enum Errored { kErrored };
-    enum NoMatch { kNoMatch };
-  };
-
- public:
-  /// Expect is the return type of the parser methods that are expected to
-  /// return a parsed value of type T, unless there was an parse error.
-  /// In the case of a parse error the called method will have called
-  /// add_error() and #errored will be set to true.
-  template <typename T>
-  struct Expect {
-    /// An alias to the templated type T.
-    using type = T;
-
-    /// Don't allow an Expect to take a nullptr.
-    inline Expect(std::nullptr_t) = delete;  // NOLINT
-
-    /// Constructor for a successful parse.
-    /// @param val the result value of the parse
-    /// @param s the optional source of the value
-    template <typename U>
-    inline Expect(U&& val, const Source& s = {})  // NOLINT
-        : value(std::forward<U>(val)), source(s) {}
-
-    /// Constructor for parse error.
-    inline Expect(Failure::Errored) : errored(true) {}  // NOLINT
-
-    /// Copy constructor
-    inline Expect(const Expect&) = default;
-    /// Move constructor
-    inline Expect(Expect&&) = default;
-    /// Assignment operator
-    /// @return this Expect
-    inline Expect& operator=(const Expect&) = default;
-    /// Assignment move operator
-    /// @return this Expect
-    inline Expect& operator=(Expect&&) = default;
-
-    /// @return a pointer to the returned value. If T is a pointer or
-    /// std::unique_ptr, operator->() automatically dereferences so that the
-    /// return type will always be a pointer to a non-pointer type. #errored
-    /// must be false to call.
-    inline typename detail::OperatorArrow<T>::type operator->() {
-      TINT_ASSERT(Reader, !errored);
-      return detail::OperatorArrow<T>::ptr(value);
-    }
-
-    /// The expected value of a successful parse.
-    /// Zero-initialized when there was a parse error.
-    T value{};
-    /// Optional source of the value.
-    Source source;
-    /// True if there was a error parsing.
-    bool errored = false;
-  };
-
-  /// Maybe is the return type of the parser methods that attempts to match a
-  /// grammar and return a parsed value of type T, or may parse part of the
-  /// grammar and then hit a parse error.
-  /// In the case of a successful grammar match, the Maybe will have #matched
-  /// set to true.
-  /// In the case of a parse error the called method will have called
-  /// add_error() and the Maybe will have #errored set to true.
-  template <typename T>
-  struct Maybe {
-    inline Maybe(std::nullptr_t) = delete;  // NOLINT
-
-    /// Constructor for a successful parse.
-    /// @param val the result value of the parse
-    /// @param s the optional source of the value
-    template <typename U>
-    inline Maybe(U&& val, const Source& s = {})  // NOLINT
-        : value(std::forward<U>(val)), source(s), matched(true) {}
-
-    /// Constructor for parse error state.
-    inline Maybe(Failure::Errored) : errored(true) {}  // NOLINT
-
-    /// Constructor for the no-match state.
-    inline Maybe(Failure::NoMatch) {}  // NOLINT
-
-    /// Constructor from an Expect.
-    /// @param e the Expect to copy this Maybe from
-    template <typename U>
-    inline Maybe(const Expect<U>& e)  // NOLINT
-        : value(e.value),
-          source(e.value),
-          errored(e.errored),
-          matched(!e.errored) {}
-
-    /// Move from an Expect.
-    /// @param e the Expect to move this Maybe from
-    template <typename U>
-    inline Maybe(Expect<U>&& e)  // NOLINT
-        : value(std::move(e.value)),
-          source(std::move(e.source)),
-          errored(e.errored),
-          matched(!e.errored) {}
-
-    /// Copy constructor
-    inline Maybe(const Maybe&) = default;
-    /// Move constructor
-    inline Maybe(Maybe&&) = default;
-    /// Assignment operator
-    /// @return this Maybe
-    inline Maybe& operator=(const Maybe&) = default;
-    /// Assignment move operator
-    /// @return this Maybe
-    inline Maybe& operator=(Maybe&&) = default;
-
-    /// @return a pointer to the returned value. If T is a pointer or
-    /// std::unique_ptr, operator->() automatically dereferences so that the
-    /// return type will always be a pointer to a non-pointer type. #errored
-    /// must be false to call.
-    inline typename detail::OperatorArrow<T>::type operator->() {
-      TINT_ASSERT(Reader, !errored);
-      return detail::OperatorArrow<T>::ptr(value);
-    }
-
-    /// The value of a successful parse.
-    /// Zero-initialized when there was a parse error.
-    T value{};
-    /// Optional source of the value.
-    Source source;
-    /// True if there was a error parsing.
-    bool errored = false;
-    /// True if there was a error parsing.
-    bool matched = false;
-  };
-
-  /// TypedIdentifier holds a parsed identifier and type. Returned by
-  /// variable_ident_decl().
-  struct TypedIdentifier {
-    /// Constructor
-    TypedIdentifier();
-    /// Copy constructor
-    /// @param other the FunctionHeader to copy
-    TypedIdentifier(const TypedIdentifier& other);
-    /// Constructor
-    /// @param type_in parsed type
-    /// @param name_in parsed identifier
-    /// @param source_in source to the identifier
-    TypedIdentifier(const ast::Type* type_in,
-                    std::string name_in,
-                    Source source_in);
-    /// Destructor
-    ~TypedIdentifier();
-
-    /// Parsed type. May be nullptr for inferred types.
-    const ast::Type* type = nullptr;
-    /// Parsed identifier.
-    std::string name;
-    /// Source to the identifier.
-    Source source;
-  };
-
-  /// FunctionHeader contains the parsed information for a function header.
-  struct FunctionHeader {
-    /// Constructor
-    FunctionHeader();
-    /// Copy constructor
-    /// @param other the FunctionHeader to copy
-    FunctionHeader(const FunctionHeader& other);
-    /// Constructor
-    /// @param src parsed header source
-    /// @param n function name
-    /// @param p function parameters
-    /// @param ret_ty function return type
-    /// @param ret_attrs return type attributes
-    FunctionHeader(Source src,
-                   std::string n,
-                   ast::VariableList p,
-                   const ast::Type* ret_ty,
-                   ast::AttributeList ret_attrs);
-    /// Destructor
-    ~FunctionHeader();
-    /// Assignment operator
-    /// @param other the FunctionHeader to copy
-    /// @returns this FunctionHeader
-    FunctionHeader& operator=(const FunctionHeader& other);
-
-    /// Parsed header source
-    Source source;
-    /// Function name
-    std::string name;
-    /// Function parameters
-    ast::VariableList params;
-    /// Function return type
-    const ast::Type* return_type = nullptr;
-    /// Function return type attributes
-    ast::AttributeList return_type_attributes;
-  };
-
-  /// VarDeclInfo contains the parsed information for variable declaration.
-  struct VarDeclInfo {
-    /// Constructor
-    VarDeclInfo();
-    /// Copy constructor
-    /// @param other the VarDeclInfo to copy
-    VarDeclInfo(const VarDeclInfo& other);
-    /// Constructor
-    /// @param source_in variable declaration source
-    /// @param name_in variable name
-    /// @param storage_class_in variable storage class
-    /// @param access_in variable access control
-    /// @param type_in variable type
-    VarDeclInfo(Source source_in,
-                std::string name_in,
-                ast::StorageClass storage_class_in,
-                ast::Access access_in,
-                const ast::Type* type_in);
-    /// Destructor
-    ~VarDeclInfo();
-
-    /// Variable declaration source
-    Source source;
-    /// Variable name
-    std::string name;
-    /// Variable storage class
-    ast::StorageClass storage_class = ast::StorageClass::kNone;
-    /// Variable access control
-    ast::Access access = ast::Access::kUndefined;
-    /// Variable type
-    const ast::Type* type = nullptr;
-  };
-
-  /// VariableQualifier contains the parsed information for a variable qualifier
-  struct VariableQualifier {
-    /// The variable's storage class
-    ast::StorageClass storage_class = ast::StorageClass::kNone;
-    /// The variable's access control
-    ast::Access access = ast::Access::kUndefined;
-  };
-
-  /// Creates a new parser using the given file
-  /// @param file the input source file to parse
-  explicit ParserImpl(Source::File const* file);
-  ~ParserImpl();
-
-  /// Run the parser
-  /// @returns true if the parse was successful, false otherwise.
-  bool Parse();
-
-  /// set_max_diagnostics sets the maximum number of reported errors before
-  /// aborting parsing.
-  /// @param limit the new maximum number of errors
-  void set_max_errors(size_t limit) { max_errors_ = limit; }
-
-  /// @return the number of maximum number of reported errors before aborting
-  /// parsing.
-  size_t get_max_errors() const { return max_errors_; }
-
-  /// @returns true if an error was encountered.
-  bool has_error() const { return builder_.Diagnostics().contains_errors(); }
-
-  /// @returns the parser error string
-  std::string error() const {
-    diag::Formatter formatter{{false, false, false, false}};
-    return formatter.format(builder_.Diagnostics());
-  }
-
-  /// @returns the Program. The program builder in the parser will be reset
-  /// after this.
-  Program program() { return Program(std::move(builder_)); }
-
-  /// @returns the program builder.
-  ProgramBuilder& builder() { return builder_; }
-
-  /// @returns the next token
-  Token next();
-  /// Peeks ahead and returns the token at `idx` ahead of the current position
-  /// @param idx the index of the token to return
-  /// @returns the token `idx` positions ahead without advancing
-  Token peek(size_t idx = 0);
-  /// Peeks ahead and returns true if the token at `idx` ahead of the current
-  /// position is |tok|
-  /// @param idx the index of the token to return
-  /// @param tok the token to look for
-  /// @returns true if the token `idx` positions ahead is |tok|
-  bool peek_is(Token::Type tok, size_t idx = 0);
-  /// @returns the last token that was returned by `next()`
-  Token last_token() const;
-  /// Appends an error at `t` with the message `msg`
-  /// @param t the token to associate the error with
-  /// @param msg the error message
-  /// @return `Failure::Errored::kError` so that you can combine an add_error()
-  /// call and return on the same line.
-  Failure::Errored add_error(const Token& t, const std::string& msg);
-  /// Appends an error raised when parsing `use` at `t` with the message
-  /// `msg`
-  /// @param source the source to associate the error with
-  /// @param msg the error message
-  /// @param use a description of what was being parsed when the error was
-  /// raised.
-  /// @return `Failure::Errored::kError` so that you can combine an add_error()
-  /// call and return on the same line.
-  Failure::Errored add_error(const Source& source,
-                             std::string_view msg,
-                             std::string_view use);
-  /// Appends an error at `source` with the message `msg`
-  /// @param source the source to associate the error with
-  /// @param msg the error message
-  /// @return `Failure::Errored::kError` so that you can combine an add_error()
-  /// call and return on the same line.
-  Failure::Errored add_error(const Source& source, const std::string& msg);
-  /// Appends a deprecated-language-feature warning at `source` with the message
-  /// `msg`
-  /// @param source the source to associate the error with
-  /// @param msg the warning message
-  void deprecated(const Source& source, const std::string& msg);
-  /// Parses the `translation_unit` grammar element
-  void translation_unit();
-  /// Parses the `global_decl` grammar element, erroring on parse failure.
-  /// @return true on parse success, otherwise an error.
-  Expect<bool> expect_global_decl();
-  /// Parses a `global_variable_decl` grammar element with the initial
-  /// `variable_attribute_list*` provided as `attrs`
-  /// @returns the variable parsed or nullptr
-  /// @param attrs the list of attributes for the variable declaration.
-  Maybe<const ast::Variable*> global_variable_decl(ast::AttributeList& attrs);
-  /// Parses a `global_constant_decl` grammar element with the initial
-  /// `variable_attribute_list*` provided as `attrs`
-  /// @returns the const object or nullptr
-  /// @param attrs the list of attributes for the constant declaration.
-  Maybe<const ast::Variable*> global_constant_decl(ast::AttributeList& attrs);
-  /// Parses a `variable_decl` grammar element
-  /// @param allow_inferred if true, do not fail if variable decl does not
-  /// specify type
-  /// @returns the parsed variable declaration info
-  Maybe<VarDeclInfo> variable_decl(bool allow_inferred = false);
-  /// Parses a `variable_ident_decl` grammar element, erroring on parse
-  /// failure.
-  /// @param use a description of what was being parsed if an error was raised.
-  /// @param allow_inferred if true, do not fail if variable decl does not
-  /// specify type
-  /// @returns the identifier and type parsed or empty otherwise
-  Expect<TypedIdentifier> expect_variable_ident_decl(
-      std::string_view use,
-      bool allow_inferred = false);
-  /// Parses a `variable_qualifier` grammar element
-  /// @returns the variable qualifier information
-  Maybe<VariableQualifier> variable_qualifier();
-  /// Parses a `type_alias` grammar element
-  /// @returns the type alias or nullptr on error
-  Maybe<const ast::Alias*> type_alias();
-  /// Parses a `type_decl` grammar element
-  /// @returns the parsed Type or nullptr if none matched.
-  Maybe<const ast::Type*> type_decl();
-  /// Parses a `type_decl` grammar element with the given pre-parsed
-  /// attributes.
-  /// @param attrs the list of attributes for the type.
-  /// @returns the parsed Type or nullptr if none matched.
-  Maybe<const ast::Type*> type_decl(ast::AttributeList& attrs);
-  /// Parses a `storage_class` grammar element, erroring on parse failure.
-  /// @param use a description of what was being parsed if an error was raised.
-  /// @returns the storage class or StorageClass::kNone if none matched
-  Expect<ast::StorageClass> expect_storage_class(std::string_view use);
-  /// Parses a `struct_decl` grammar element with the initial
-  /// `struct_attribute_decl*` provided as `attrs`.
-  /// @returns the struct type or nullptr on error
-  /// @param attrs the list of attributes for the struct declaration.
-  Maybe<const ast::Struct*> struct_decl(ast::AttributeList& attrs);
-  /// Parses a `struct_body_decl` grammar element, erroring on parse failure.
-  /// @returns the struct members
-  Expect<ast::StructMemberList> expect_struct_body_decl();
-  /// Parses a `struct_member` grammar element with the initial
-  /// `struct_member_attribute_decl+` provided as `attrs`, erroring on parse
-  /// failure.
-  /// @param attrs the list of attributes for the struct member.
-  /// @returns the struct member or nullptr
-  Expect<ast::StructMember*> expect_struct_member(ast::AttributeList& attrs);
-  /// Parses a `function_decl` grammar element with the initial
-  /// `function_attribute_decl*` provided as `attrs`.
-  /// @param attrs the list of attributes for the function declaration.
-  /// @returns the parsed function, nullptr otherwise
-  Maybe<const ast::Function*> function_decl(ast::AttributeList& attrs);
-  /// Parses a `texture_sampler_types` grammar element
-  /// @returns the parsed Type or nullptr if none matched.
-  Maybe<const ast::Type*> texture_sampler_types();
-  /// Parses a `sampler_type` grammar element
-  /// @returns the parsed Type or nullptr if none matched.
-  Maybe<const ast::Type*> sampler_type();
-  /// Parses a `multisampled_texture_type` grammar element
-  /// @returns returns the multisample texture dimension or kNone if none
-  /// matched.
-  Maybe<const ast::TextureDimension> multisampled_texture_type();
-  /// Parses a `sampled_texture_type` grammar element
-  /// @returns returns the sample texture dimension or kNone if none matched.
-  Maybe<const ast::TextureDimension> sampled_texture_type();
-  /// Parses a `storage_texture_type` grammar element
-  /// @returns returns the storage texture dimension.
-  /// Returns kNone if none matched.
-  Maybe<const ast::TextureDimension> storage_texture_type();
-  /// Parses a `depth_texture_type` grammar element
-  /// @returns the parsed Type or nullptr if none matched.
-  Maybe<const ast::Type*> depth_texture_type();
-  /// Parses a 'texture_external_type' grammar element
-  /// @returns the parsed Type or nullptr if none matched
-  Maybe<const ast::Type*> external_texture_type();
-  /// Parses a `texel_format` grammar element
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns returns the texel format or kNone if none matched.
-  Expect<ast::TexelFormat> expect_texel_format(std::string_view use);
-  /// Parses a `function_header` grammar element
-  /// @returns the parsed function header
-  Maybe<FunctionHeader> function_header();
-  /// Parses a `param_list` grammar element, erroring on parse failure.
-  /// @returns the parsed variables
-  Expect<ast::VariableList> expect_param_list();
-  /// Parses a `param` grammar element, erroring on parse failure.
-  /// @returns the parsed variable
-  Expect<ast::Variable*> expect_param();
-  /// Parses a `pipeline_stage` grammar element, erroring if the next token does
-  /// not match a stage name.
-  /// @returns the pipeline stage.
-  Expect<ast::PipelineStage> expect_pipeline_stage();
-  /// Parses an access control identifier, erroring if the next token does not
-  /// match a valid access control.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the parsed access control.
-  Expect<ast::Access> expect_access(std::string_view use);
-  /// Parses a builtin identifier, erroring if the next token does not match a
-  /// valid builtin name.
-  /// @returns the parsed builtin.
-  Expect<ast::Builtin> expect_builtin();
-  /// Parses a `body_stmt` grammar element, erroring on parse failure.
-  /// @returns the parsed statements
-  Expect<ast::BlockStatement*> expect_body_stmt();
-  /// Parses a `paren_rhs_stmt` grammar element, erroring on parse failure.
-  /// @returns the parsed element or nullptr
-  Expect<const ast::Expression*> expect_paren_rhs_stmt();
-  /// Parses a `statements` grammar element
-  /// @returns the statements parsed
-  Expect<ast::StatementList> expect_statements();
-  /// Parses a `statement` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::Statement*> statement();
-  /// Parses a `break_stmt` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::BreakStatement*> break_stmt();
-  /// Parses a `return_stmt` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::ReturnStatement*> return_stmt();
-  /// Parses a `continue_stmt` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::ContinueStatement*> continue_stmt();
-  /// Parses a `variable_stmt` grammar element
-  /// @returns the parsed variable or nullptr
-  Maybe<const ast::VariableDeclStatement*> variable_stmt();
-  /// Parses a `if_stmt` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::IfStatement*> if_stmt();
-  /// Parses a list of `else_stmt` grammar elements
-  /// @returns the parsed statement or nullptr
-  Expect<ast::ElseStatementList> else_stmts();
-  /// Parses a `switch_stmt` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::SwitchStatement*> switch_stmt();
-  /// Parses a `switch_body` grammar element
-  /// @returns the parsed statement or nullptr
-  Maybe<const ast::CaseStatement*> switch_body();
-  /// Parses a `case_selectors` grammar element
-  /// @returns the list of literals
-  Expect<ast::CaseSelectorList> expect_case_selectors();
-  /// Parses a `case_body` grammar element
-  /// @returns the parsed statements
-  Maybe<const ast::BlockStatement*> case_body();
-  /// Parses a `func_call_stmt` grammar element
-  /// @returns the parsed function call or nullptr
-  Maybe<const ast::CallStatement*> func_call_stmt();
-  /// Parses a `loop_stmt` grammar element
-  /// @returns the parsed loop or nullptr
-  Maybe<const ast::LoopStatement*> loop_stmt();
-  /// Parses a `for_header` grammar element, erroring on parse failure.
-  /// @returns the parsed for header or nullptr
-  Expect<std::unique_ptr<ForHeader>> expect_for_header();
-  /// Parses a `for_stmt` grammar element
-  /// @returns the parsed for loop or nullptr
-  Maybe<const ast::ForLoopStatement*> for_stmt();
-  /// Parses a `continuing_stmt` grammar element
-  /// @returns the parsed statements
-  Maybe<const ast::BlockStatement*> continuing_stmt();
-  /// Parses a `const_literal` grammar element
-  /// @returns the const literal parsed or nullptr if none found
-  Maybe<const ast::LiteralExpression*> const_literal();
-  /// Parses a `const_expr` grammar element, erroring on parse failure.
-  /// @returns the parsed constructor expression or nullptr on error
-  Expect<const ast::Expression*> expect_const_expr();
-  /// Parses a `primary_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> primary_expression();
-  /// Parses a `argument_expression_list` grammar element, erroring on parse
-  /// failure.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the list of arguments
-  Expect<ast::ExpressionList> expect_argument_expression_list(
-      std::string_view use);
-  /// Parses the recursive portion of the postfix_expression
-  /// @param prefix the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> postfix_expression(
-      const ast::Expression* prefix);
-  /// Parses a `singular_expression` grammar elment
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> singular_expression();
-  /// Parses a `unary_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> unary_expression();
-  /// Parses the recursive part of the `multiplicative_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_multiplicative_expr(
-      const ast::Expression* lhs);
-  /// Parses the `multiplicative_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> multiplicative_expression();
-  /// Parses the recursive part of the `additive_expression`, erroring on parse
-  /// failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_additive_expr(
-      const ast::Expression* lhs);
-  /// Parses the `additive_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> additive_expression();
-  /// Parses the recursive part of the `shift_expression`, erroring on parse
-  /// failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_shift_expr(const ast::Expression* lhs);
-  /// Parses the `shift_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> shift_expression();
-  /// Parses the recursive part of the `relational_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_relational_expr(
-      const ast::Expression* lhs);
-  /// Parses the `relational_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> relational_expression();
-  /// Parses the recursive part of the `equality_expression`, erroring on parse
-  /// failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_equality_expr(
-      const ast::Expression* lhs);
-  /// Parses the `equality_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> equality_expression();
-  /// Parses the recursive part of the `and_expression`, erroring on parse
-  /// failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_and_expr(const ast::Expression* lhs);
-  /// Parses the `and_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> and_expression();
-  /// Parses the recursive part of the `exclusive_or_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_exclusive_or_expr(
-      const ast::Expression* lhs);
-  /// Parses the `exclusive_or_expression` grammar elememnt
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> exclusive_or_expression();
-  /// Parses the recursive part of the `inclusive_or_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_inclusive_or_expr(
-      const ast::Expression* lhs);
-  /// Parses the `inclusive_or_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> inclusive_or_expression();
-  /// Parses the recursive part of the `logical_and_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_logical_and_expr(
-      const ast::Expression* lhs);
-  /// Parses a `logical_and_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> logical_and_expression();
-  /// Parses the recursive part of the `logical_or_expression`, erroring on
-  /// parse failure.
-  /// @param lhs the left side of the expression
-  /// @returns the parsed expression or nullptr
-  Expect<const ast::Expression*> expect_logical_or_expr(
-      const ast::Expression* lhs);
-  /// Parses a `logical_or_expression` grammar element
-  /// @returns the parsed expression or nullptr
-  Maybe<const ast::Expression*> logical_or_expression();
-  /// Parses a `assignment_stmt` grammar element
-  /// @returns the parsed assignment or nullptr
-  Maybe<const ast::AssignmentStatement*> assignment_stmt();
-  /// Parses one or more attribute lists.
-  /// @return the parsed attribute list, or an empty list on error.
-  Maybe<ast::AttributeList> attribute_list();
-  /// Parses a list of attributes between `ATTR_LEFT` and `ATTR_RIGHT`
-  /// brackets.
-  /// @param attrs the list to append newly parsed attributes to.
-  /// @return true if any attributes were be parsed, otherwise false.
-  Maybe<bool> attribute_bracketed_list(ast::AttributeList& attrs);
-  /// Parses a single attribute of the following types:
-  /// * `struct_attribute`
-  /// * `struct_member_attribute`
-  /// * `array_attribute`
-  /// * `variable_attribute`
-  /// * `global_const_attribute`
-  /// * `function_attribute`
-  /// @return the parsed attribute, or nullptr.
-  Maybe<const ast::Attribute*> attribute();
-  /// Parses a single attribute, reporting an error if the next token does not
-  /// represent a attribute.
-  /// @see #attribute for the full list of attributes this method parses.
-  /// @return the parsed attribute, or nullptr on error.
-  Expect<const ast::Attribute*> expect_attribute();
-
- private:
-  /// ReturnType resolves to the return type for the function or lambda F.
-  template <typename F>
-  using ReturnType = typename std::invoke_result<F>::type;
-
-  /// ResultType resolves to `T` for a `RESULT` of type Expect<T>.
-  template <typename RESULT>
-  using ResultType = typename RESULT::type;
-
-  /// @returns true and consumes the next token if it equals `tok`
-  /// @param source if not nullptr, the next token's source is written to this
-  /// pointer, regardless of success or error
-  bool match(Token::Type tok, Source* source = nullptr);
-  /// Errors if the next token is not equal to `tok`
-  /// Consumes the next token on match.
-  /// expect() also updates #synchronized_, setting it to `true` if the next
-  /// token is equal to `tok`, otherwise `false`.
-  /// @param use a description of what was being parsed if an error was raised.
-  /// @param tok the token to test against
-  /// @returns true if the next token equals `tok`
-  bool expect(std::string_view use, Token::Type tok);
-  /// Parses a signed integer from the next token in the stream, erroring if the
-  /// next token is not a signed integer.
-  /// Consumes the next token on match.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the parsed integer.
-  Expect<int32_t> expect_sint(std::string_view use);
-  /// Parses a signed integer from the next token in the stream, erroring if
-  /// the next token is not a signed integer or is negative.
-  /// Consumes the next token if it is a signed integer (not necessarily
-  /// negative).
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the parsed integer.
-  Expect<uint32_t> expect_positive_sint(std::string_view use);
-  /// Parses a non-zero signed integer from the next token in the stream,
-  /// erroring if the next token is not a signed integer or is less than 1.
-  /// Consumes the next token if it is a signed integer (not necessarily
-  /// >= 1).
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the parsed integer.
-  Expect<uint32_t> expect_nonzero_positive_sint(std::string_view use);
-  /// Errors if the next token is not an identifier.
-  /// Consumes the next token on match.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @returns the parsed identifier.
-  Expect<std::string> expect_ident(std::string_view use);
-  /// Parses a lexical block starting with the token `start` and ending with
-  /// the token `end`. `body` is called to parse the lexical block body
-  /// between the `start` and `end` tokens. If the `start` or `end` tokens
-  /// are not matched then an error is generated and a zero-initialized `T` is
-  /// returned. If `body` raises an error while parsing then a zero-initialized
-  /// `T` is returned.
-  /// @param start the token that begins the lexical block
-  /// @param end the token that ends the lexical block
-  /// @param use a description of what was being parsed if an error was raised
-  /// @param body a function or lambda that is called to parse the lexical block
-  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
-  /// @return the value returned by `body` if no errors are raised, otherwise
-  /// an Expect with error state.
-  template <typename F, typename T = ReturnType<F>>
-  T expect_block(Token::Type start,
-                 Token::Type end,
-                 std::string_view use,
-                 F&& body);
-  /// A convenience function that calls expect_block() passing
-  /// `Token::Type::kParenLeft` and `Token::Type::kParenRight` for the `start`
-  /// and `end` arguments, respectively.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @param body a function or lambda that is called to parse the lexical block
-  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
-  /// @return the value returned by `body` if no errors are raised, otherwise
-  /// an Expect with error state.
-  template <typename F, typename T = ReturnType<F>>
-  T expect_paren_block(std::string_view use, F&& body);
-  /// A convenience function that calls `expect_block` passing
-  /// `Token::Type::kBraceLeft` and `Token::Type::kBraceRight` for the `start`
-  /// and `end` arguments, respectively.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @param body a function or lambda that is called to parse the lexical block
-  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
-  /// @return the value returned by `body` if no errors are raised, otherwise
-  /// an Expect with error state.
-  template <typename F, typename T = ReturnType<F>>
-  T expect_brace_block(std::string_view use, F&& body);
-  /// A convenience function that calls `expect_block` passing
-  /// `Token::Type::kLessThan` and `Token::Type::kGreaterThan` for the `start`
-  /// and `end` arguments, respectively.
-  /// @param use a description of what was being parsed if an error was raised
-  /// @param body a function or lambda that is called to parse the lexical block
-  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
-  /// @return the value returned by `body` if no errors are raised, otherwise
-  /// an Expect with error state.
-  template <typename F, typename T = ReturnType<F>>
-  T expect_lt_gt_block(std::string_view use, F&& body);
-
-  /// sync() calls the function `func`, and attempts to resynchronize the
-  /// parser to the next found resynchronization token if `func` fails. If the
-  /// next found resynchronization token is `tok`, then sync will also consume
-  /// `tok`.
-  ///
-  /// sync() will transiently add `tok` to the parser's stack of
-  /// synchronization tokens for the duration of the call to `func`. Once @p
-  /// func returns,
-  /// `tok` is removed from the stack of resynchronization tokens. sync calls
-  /// may be nested, and so the number of resynchronization tokens is equal to
-  /// the number of sync() calls in the current stack frame.
-  ///
-  /// sync() updates #synchronized_, setting it to `true` if the next
-  /// resynchronization token found was `tok`, otherwise `false`.
-  ///
-  /// @param tok the token to attempt to synchronize the parser to if `func`
-  /// fails.
-  /// @param func a function or lambda with the signature: `Expect<Result>()` or
-  /// `Maybe<Result>()`.
-  /// @return the value returned by `func`
-  template <typename F, typename T = ReturnType<F>>
-  T sync(Token::Type tok, F&& func);
-  /// sync_to() attempts to resynchronize the parser to the next found
-  /// resynchronization token or `tok` (whichever comes first).
-  ///
-  /// Synchronization tokens are transiently defined by calls to sync().
-  ///
-  /// sync_to() updates #synchronized_, setting it to `true` if a
-  /// resynchronization token was found and it was `tok`, otherwise `false`.
-  ///
-  /// @param tok the token to attempt to synchronize the parser to.
-  /// @param consume if true and the next found resynchronization token is
-  /// `tok` then sync_to() will also consume `tok`.
-  /// @return the state of #synchronized_.
-  /// @see sync().
-  bool sync_to(Token::Type tok, bool consume);
-  /// @return true if `t` is in the stack of resynchronization tokens.
-  /// @see sync().
-  bool is_sync_token(const Token& t) const;
-
-  /// @returns true if #synchronized_ is true and the number of reported errors
-  /// is less than #max_errors_.
-  bool continue_parsing() {
-    return synchronized_ && builder_.Diagnostics().error_count() < max_errors_;
-  }
-
-  /// without_error() calls the function `func` muting any grammatical errors
-  /// found while executing the function. This can be used as a best-effort to
-  /// produce a meaningful error message when the parser is out of sync.
-  /// @param func a function or lambda with the signature: `Expect<Result>()` or
-  /// `Maybe<Result>()`.
-  /// @return the value returned by `func`
-  template <typename F, typename T = ReturnType<F>>
-  T without_error(F&& func);
-
-  /// Reports an error if the attribute list `list` is not empty.
-  /// Used to ensure that all attributes are consumed.
-  bool expect_attributes_consumed(ast::AttributeList& list);
-
-  Expect<const ast::Type*> expect_type_decl_pointer(Token t);
-  Expect<const ast::Type*> expect_type_decl_atomic(Token t);
-  Expect<const ast::Type*> expect_type_decl_vector(Token t);
-  Expect<const ast::Type*> expect_type_decl_array(Token t,
-                                                  ast::AttributeList attrs);
-  Expect<const ast::Type*> expect_type_decl_matrix(Token t);
-
-  Expect<const ast::Type*> expect_type(std::string_view use);
-
-  Maybe<const ast::Statement*> non_block_statement();
-  Maybe<const ast::Statement*> for_header_initializer();
-  Maybe<const ast::Statement*> for_header_continuing();
-
-  class MultiTokenSource;
-  MultiTokenSource make_source_range();
-  MultiTokenSource make_source_range_from(const Source& start);
-
-  /// Creates a new `ast::Node` owned by the Module. When the Module is
-  /// destructed, the `ast::Node` will also be destructed.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  T* create(ARGS&&... args) {
-    return builder_.create<T>(std::forward<ARGS>(args)...);
-  }
-
-  std::unique_ptr<Lexer> lexer_;
-  std::deque<Token> token_queue_;
-  Token last_token_;
-  bool synchronized_ = true;
-  uint32_t parse_depth_ = 0;
-  std::vector<Token::Type> sync_tokens_;
-  int silence_errors_ = 0;
-  ProgramBuilder builder_;
-  size_t max_errors_ = 25;
-};
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_PARSER_IMPL_H_
diff --git a/src/reader/wgsl/parser_impl_additive_expression_test.cc b/src/reader/wgsl/parser_impl_additive_expression_test.cc
deleted file mode 100644
index e4eae74..0000000
--- a/src/reader/wgsl/parser_impl_additive_expression_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AdditiveExpression_Parses_Plus) {
-  auto p = parser("a + true");
-  auto e = p->additive_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, AdditiveExpression_Parses_Minus) {
-  auto p = parser("a - true");
-  auto e = p->additive_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, AdditiveExpression_InvalidLHS) {
-  auto p = parser("if (a) {} + true");
-  auto e = p->additive_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, AdditiveExpression_InvalidRHS) {
-  auto p = parser("true + if (a) {}");
-  auto e = p->additive_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of + expression");
-}
-
-TEST_F(ParserImplTest, AdditiveExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->additive_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_and_expression_test.cc b/src/reader/wgsl/parser_impl_and_expression_test.cc
deleted file mode 100644
index 823b5d8..0000000
--- a/src/reader/wgsl/parser_impl_and_expression_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AndExpression_Parses) {
-  auto p = parser("a & true");
-  auto e = p->and_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kAnd, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, AndExpression_InvalidLHS) {
-  auto p = parser("if (a) {} & true");
-  auto e = p->and_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, AndExpression_InvalidRHS) {
-  auto p = parser("true & if (a) {}");
-  auto e = p->and_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of & expression");
-}
-
-TEST_F(ParserImplTest, AndExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->and_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
deleted file mode 100644
index b16d67b..0000000
--- a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ArgumentExpressionList_Parses) {
-  auto p = parser("(a)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-
-  ASSERT_EQ(e.value.size(), 1u);
-  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_ParsesEmptyList) {
-  auto p = parser("()");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-
-  ASSERT_EQ(e.value.size(), 0u);
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_ParsesMultiple) {
-  auto p = parser("(a, -33, 1+2)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-
-  ASSERT_EQ(e.value.size(), 3u);
-  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
-  ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
-  ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_TrailingComma) {
-  auto p = parser("(a, 42,)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-
-  ASSERT_EQ(e.value.size(), 2u);
-  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
-  ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingLeftParen) {
-  auto p = parser("a)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:1: expected '(' for argument list");
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingRightParen) {
-  auto p = parser("(a");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:3: expected ')' for argument list");
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_0) {
-  auto p = parser("(,)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:2: expected ')' for argument list");
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_1) {
-  auto p = parser("(a, ,)");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:5: expected ')' for argument list");
-}
-
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesInvalidExpression) {
-  auto p = parser("(if(a) {})");
-  auto e = p->expect_argument_expression_list("argument list");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:2: expected ')' for argument list");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
deleted file mode 100644
index 8f8f165..0000000
--- a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AssignmentStmt_Parses_ToVariable) {
-  auto p = parser("a = 123");
-  auto e = p->assignment_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
-  ASSERT_NE(e->lhs, nullptr);
-  ASSERT_NE(e->rhs, nullptr);
-
-  ASSERT_TRUE(e->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = e->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_NE(e->rhs, nullptr);
-  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
-}
-
-TEST_F(ParserImplTest, AssignmentStmt_Parses_ToMember) {
-  auto p = parser("a.b.c[2].d = 123");
-  auto e = p->assignment_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
-  ASSERT_NE(e->lhs, nullptr);
-  ASSERT_NE(e->rhs, nullptr);
-
-  ASSERT_NE(e->rhs, nullptr);
-  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
-
-  ASSERT_TRUE(e->lhs->Is<ast::MemberAccessorExpression>());
-  auto* mem = e->lhs->As<ast::MemberAccessorExpression>();
-
-  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
-  auto* ident = mem->member->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
-
-  ASSERT_TRUE(mem->structure->Is<ast::IndexAccessorExpression>());
-  auto* idx = mem->structure->As<ast::IndexAccessorExpression>();
-
-  ASSERT_NE(idx->index, nullptr);
-  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
-
-  ASSERT_TRUE(idx->object->Is<ast::MemberAccessorExpression>());
-  mem = idx->object->As<ast::MemberAccessorExpression>();
-  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
-  ident = mem->member->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
-
-  ASSERT_TRUE(mem->structure->Is<ast::MemberAccessorExpression>());
-  mem = mem->structure->As<ast::MemberAccessorExpression>();
-
-  ASSERT_TRUE(mem->structure->Is<ast::IdentifierExpression>());
-  ident = mem->structure->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
-  ident = mem->member->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
-}
-
-TEST_F(ParserImplTest, AssignmentStmt_Parses_ToPhony) {
-  auto p = parser("_ = 123");
-  auto e = p->assignment_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
-  ASSERT_NE(e->lhs, nullptr);
-  ASSERT_NE(e->rhs, nullptr);
-
-  ASSERT_NE(e->rhs, nullptr);
-  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
-
-  ASSERT_TRUE(e->lhs->Is<ast::PhonyExpression>());
-}
-
-TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
-  auto p = parser("a.b.c[2].d 123");
-  auto e = p->assignment_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:12: expected '=' for assignment");
-}
-
-TEST_F(ParserImplTest, AssignmentStmt_InvalidLHS) {
-  auto p = parser("if (true) {} = 123");
-  auto e = p->assignment_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, AssignmentStmt_InvalidRHS) {
-  auto p = parser("a.b.c[2].d = if (true) {}");
-  auto e = p->assignment_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: unable to parse right side of assignment");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_body_stmt_test.cc b/src/reader/wgsl/parser_impl_body_stmt_test.cc
deleted file mode 100644
index ee3df0a..0000000
--- a/src/reader/wgsl/parser_impl_body_stmt_test.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, BodyStmt) {
-  auto p = parser(R"({
-  discard;
-  return 1 + b / 2;
-})");
-  auto e = p->expect_body_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_EQ(e->statements.size(), 2u);
-  EXPECT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
-  EXPECT_TRUE(e->statements[1]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, BodyStmt_Empty) {
-  auto p = parser("{}");
-  auto e = p->expect_body_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  EXPECT_EQ(e->statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, BodyStmt_InvalidStmt) {
-  auto p = parser("{fn main() {}}");
-  auto e = p->expect_body_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:2: expected '}'");
-}
-
-TEST_F(ParserImplTest, BodyStmt_MissingRightParen) {
-  auto p = parser("{return;");
-  auto e = p->expect_body_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:9: expected '}'");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_break_stmt_test.cc b/src/reader/wgsl/parser_impl_break_stmt_test.cc
deleted file mode 100644
index 9ba1361..0000000
--- a/src/reader/wgsl/parser_impl_break_stmt_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2020 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/ast/break_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, BreakStmt) {
-  auto p = parser("break");
-  auto e = p->break_stmt();
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::BreakStatement>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_bug_cases_test.cc b/src/reader/wgsl/parser_impl_bug_cases_test.cc
deleted file mode 100644
index 7d91001..0000000
--- a/src/reader/wgsl/parser_impl_bug_cases_test.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Bug_chromium_1180130) {
-  auto p = parser(
-      R"(a;{}}a;}};{{{;{}};{};{}}a;{}};{{{}};{{{;{}};{};{}}a;{}};{{{}}{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{{{;u[([[,a;{}}a;{}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{z{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}}i;{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{;u[[a,([}};{{{;{}})");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
deleted file mode 100644
index 83d31ee..0000000
--- a/src/reader/wgsl/parser_impl_call_stmt_test.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Statement_Call) {
-  auto p = parser("a();");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 1u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 2u);
-
-  ASSERT_TRUE(e->Is<ast::CallStatement>());
-  auto* c = e->As<ast::CallStatement>()->expr;
-
-  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
-
-  EXPECT_EQ(c->args.size(), 0u);
-}
-
-TEST_F(ParserImplTest, Statement_Call_WithParams) {
-  auto p = parser("a(1, b, 2 + 3 / b);");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-
-  ASSERT_TRUE(e->Is<ast::CallStatement>());
-  auto* c = e->As<ast::CallStatement>()->expr;
-
-  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
-
-  EXPECT_EQ(c->args.size(), 3u);
-  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
-  EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, Statement_Call_WithParams_TrailingComma) {
-  auto p = parser("a(1, b,);");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-
-  ASSERT_TRUE(e->Is<ast::CallStatement>());
-  auto* c = e->As<ast::CallStatement>()->expr;
-
-  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
-
-  EXPECT_EQ(c->args.size(), 2u);
-  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
-}
-
-TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) {
-  auto p = parser("a(");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
-}
-
-TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
-  auto p = parser("a()");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:4: expected ';' for function call");
-}
-
-TEST_F(ParserImplTest, Statement_Call_Bad_ArgList) {
-  auto p = parser("a(b c);");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:5: expected ')' for function call");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_case_body_test.cc b/src/reader/wgsl/parser_impl_case_body_test.cc
deleted file mode 100644
index 0237cdb..0000000
--- a/src/reader/wgsl/parser_impl_case_body_test.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, CaseBody_Empty) {
-  auto p = parser("");
-  auto e = p->case_body();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(e.errored);
-  EXPECT_TRUE(e.matched);
-  EXPECT_EQ(e->statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, CaseBody_Statements) {
-  auto p = parser(R"(
-  var a: i32;
-  a = 2;)");
-
-  auto e = p->case_body();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(e.errored);
-  EXPECT_TRUE(e.matched);
-  ASSERT_EQ(e->statements.size(), 2u);
-  EXPECT_TRUE(e->statements[0]->Is<ast::VariableDeclStatement>());
-  EXPECT_TRUE(e->statements[1]->Is<ast::AssignmentStatement>());
-}
-
-TEST_F(ParserImplTest, CaseBody_InvalidStatement) {
-  auto p = parser("a =");
-  auto e = p->case_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, CaseBody_Fallthrough) {
-  auto p = parser("fallthrough;");
-  auto e = p->case_body();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(e.errored);
-  EXPECT_TRUE(e.matched);
-  ASSERT_EQ(e->statements.size(), 1u);
-  EXPECT_TRUE(e->statements[0]->Is<ast::FallthroughStatement>());
-}
-
-TEST_F(ParserImplTest, CaseBody_Fallthrough_MissingSemicolon) {
-  auto p = parser("fallthrough");
-  auto e = p->case_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:12: expected ';' for fallthrough statement");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_const_expr_test.cc b/src/reader/wgsl/parser_impl_const_expr_test.cc
deleted file mode 100644
index 9d3dab8..0000000
--- a/src/reader/wgsl/parser_impl_const_expr_test.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl) {
-  auto p = parser("vec2<f32>(1., 2.)");
-  auto e = p->expect_const_expr();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-
-  auto* t = e->As<ast::CallExpression>();
-  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
-  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
-
-  ASSERT_EQ(t->args.size(), 2u);
-
-  ASSERT_TRUE(t->args[0]->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(t->args[0]->As<ast::FloatLiteralExpression>()->value, 1.);
-
-  ASSERT_TRUE(t->args[1]->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(t->args[1]->As<ast::FloatLiteralExpression>()->value, 2.);
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl_Empty) {
-  auto p = parser("vec2<f32>()");
-  auto e = p->expect_const_expr();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-
-  auto* t = e->As<ast::CallExpression>();
-  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
-  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
-
-  ASSERT_EQ(t->args.size(), 0u);
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl_TrailingComma) {
-  auto p = parser("vec2<f32>(1., 2.,)");
-  auto e = p->expect_const_expr();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-
-  auto* t = e->As<ast::CallExpression>();
-  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
-  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
-
-  ASSERT_EQ(t->args.size(), 2u);
-  ASSERT_TRUE(t->args[0]->Is<ast::LiteralExpression>());
-  ASSERT_TRUE(t->args[1]->Is<ast::LiteralExpression>());
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingRightParen) {
-  auto p = parser("vec2<f32>(1., 2.");
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:17: expected ')' for type constructor");
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingLeftParen) {
-  auto p = parser("vec2<f32> 1., 2.)");
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor");
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingComma) {
-  auto p = parser("vec2<f32>(1. 2.");
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:14: expected ')' for type constructor");
-}
-
-TEST_F(ParserImplTest, ConstExpr_InvalidExpr) {
-  auto p = parser("vec2<f32>(1., if(a) {})");
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:15: invalid type for const_expr");
-}
-
-TEST_F(ParserImplTest, ConstExpr_ConstLiteral) {
-  auto p = parser("true");
-  auto e = p->expect_const_expr();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e.value->Is<ast::BoolLiteralExpression>());
-  EXPECT_TRUE(e.value->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, ConstExpr_ConstLiteral_Invalid) {
-  auto p = parser("invalid");
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:1: unable to parse const_expr");
-}
-
-TEST_F(ParserImplTest, ConstExpr_TypeConstructor) {
-  auto p = parser("S(0)");
-
-  auto e = p->expect_const_expr();
-  ASSERT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  ASSERT_NE(e->As<ast::CallExpression>()->target.type, nullptr);
-  ASSERT_TRUE(e->As<ast::CallExpression>()->target.type->Is<ast::TypeName>());
-  EXPECT_EQ(
-      e->As<ast::CallExpression>()->target.type->As<ast::TypeName>()->name,
-      p->builder().Symbols().Get("S"));
-}
-
-TEST_F(ParserImplTest, ConstExpr_Recursion) {
-  std::stringstream out;
-  for (size_t i = 0; i < 200; i++) {
-    out << "f32(";
-  }
-  out << "1.0";
-  for (size_t i = 0; i < 200; i++) {
-    out << ")";
-  }
-  auto p = parser(out.str());
-  auto e = p->expect_const_expr();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:517: maximum parser recursive depth reached");
-}
-
-TEST_F(ParserImplTest, UnaryOp_Recursion) {
-  std::stringstream out;
-  for (size_t i = 0; i < 200; i++) {
-    out << "!";
-  }
-  out << "1.0";
-  auto p = parser(out.str());
-  auto e = p->unary_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:130: maximum parser recursive depth reached");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_const_literal_test.cc b/src/reader/wgsl/parser_impl_const_literal_test.cc
deleted file mode 100644
index e91051f..0000000
--- a/src/reader/wgsl/parser_impl_const_literal_test.cc
+++ /dev/null
@@ -1,525 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-#include <cmath>
-#include <cstring>
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-// Makes an IEEE 754 binary32 floating point number with
-// - 0 sign if sign is 0, 1 otherwise
-// - 'exponent_bits' is placed in the exponent space.
-//   So, the exponent bias must already be included.
-float MakeFloat(int sign, int biased_exponent, int mantissa) {
-  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
-  // The binary32 exponent is 8 bits, just below the sign.
-  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
-  // The mantissa is the bottom 23 bits.
-  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
-
-  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
-  float result = 0.0f;
-  static_assert(sizeof(result) == sizeof(bits),
-                "expected float and uint32_t to be the same size");
-  std::memcpy(&result, &bits, sizeof(bits));
-  return result;
-}
-
-TEST_F(ParserImplTest, ConstLiteral_Int) {
-  auto p = parser("-234");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, -234);
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_Uint) {
-  auto p = parser("234u");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::UintLiteralExpression>());
-  EXPECT_EQ(c->As<ast::UintLiteralExpression>()->value, 234u);
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_Float) {
-  auto p = parser("234.e12");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value, 234e12f);
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_IncompleteExponent) {
-  auto p = parser("1.0e+");
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_TRUE(c.errored);
-  EXPECT_EQ(p->error(),
-            "1:1: incomplete exponent for floating point literal: 1.0e+");
-  ASSERT_EQ(c.value, nullptr);
-}
-
-TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooSmallMagnitude) {
-  auto p = parser("1e-256");
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_TRUE(c.errored);
-  EXPECT_EQ(p->error(),
-            "1:1: f32 (1e-256) magnitude too small, not representable");
-  ASSERT_EQ(c.value, nullptr);
-}
-
-TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargeNegative) {
-  auto p = parser("-1.2e+256");
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_TRUE(c.errored);
-  EXPECT_EQ(p->error(), "1:1: f32 (-1.2e+256) too large (negative)");
-  ASSERT_EQ(c.value, nullptr);
-}
-
-TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargePositive) {
-  auto p = parser("1.2e+256");
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_TRUE(c.errored);
-  EXPECT_EQ(p->error(), "1:1: f32 (1.2e+256) too large (positive)");
-  ASSERT_EQ(c.value, nullptr);
-}
-
-// Returns true if the given non-Nan float numbers are equal.
-bool FloatEqual(float a, float b) {
-  // Avoid Clang complaining about equality test on float.
-  // -Wfloat-equal.
-  return (a <= b) && (a >= b);
-}
-
-struct FloatLiteralTestCase {
-  std::string input;
-  float expected;
-  bool operator==(const FloatLiteralTestCase& other) const {
-    return (input == other.input) && FloatEqual(expected, other.expected);
-  }
-};
-
-inline std::ostream& operator<<(std::ostream& out, FloatLiteralTestCase data) {
-  out << data.input;
-  return out;
-}
-
-class ParserImplFloatLiteralTest
-    : public ParserImplTestWithParam<FloatLiteralTestCase> {};
-TEST_P(ParserImplFloatLiteralTest, Parse) {
-  auto params = GetParam();
-  SCOPED_TRACE(params.input);
-  auto p = parser(params.input);
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value, params.expected);
-}
-
-using FloatLiteralTestCaseList = std::vector<FloatLiteralTestCase>;
-
-FloatLiteralTestCaseList DecimalFloatCases() {
-  return FloatLiteralTestCaseList{
-      {"0.0", 0.0f},                         // Zero
-      {"1.0", 1.0f},                         // One
-      {"-1.0", -1.0f},                       // MinusOne
-      {"1000000000.0", 1e9f},                // Billion
-      {"-0.0", std::copysign(0.0f, -5.0f)},  // NegativeZero
-      {"0.0", MakeFloat(0, 0, 0)},           // Zero
-      {"-0.0", MakeFloat(1, 0, 0)},          // NegativeZero
-      {"1.0", MakeFloat(0, 127, 0)},         // One
-      {"-1.0", MakeFloat(1, 127, 0)},        // NegativeOne
-  };
-}
-
-INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_Float,
-                         ParserImplFloatLiteralTest,
-                         testing::ValuesIn(DecimalFloatCases()));
-
-const float NegInf = MakeFloat(1, 255, 0);
-const float PosInf = MakeFloat(0, 255, 0);
-FloatLiteralTestCaseList HexFloatCases() {
-  return FloatLiteralTestCaseList{
-      // Regular numbers
-      {"0x0p+0", 0.f},
-      {"0x1p+0", 1.f},
-      {"0x1p+1", 2.f},
-      {"0x1.8p+1", 3.f},
-      {"0x1.99999ap-4", 0.1f},
-      {"0x1p-1", 0.5f},
-      {"0x1p-2", 0.25f},
-      {"0x1.8p-1", 0.75f},
-      {"-0x0p+0", -0.f},
-      {"-0x1p+0", -1.f},
-      {"-0x1p-1", -0.5f},
-      {"-0x1p-2", -0.25f},
-      {"-0x1.8p-1", -0.75f},
-
-      // Large numbers
-      {"0x1p+9", 512.f},
-      {"0x1p+10", 1024.f},
-      {"0x1.02p+10", 1024.f + 8.f},
-      {"-0x1p+9", -512.f},
-      {"-0x1p+10", -1024.f},
-      {"-0x1.02p+10", -1024.f - 8.f},
-
-      // Small numbers
-      {"0x1p-9", 1.0f / 512.f},
-      {"0x1p-10", 1.0f / 1024.f},
-      {"0x1.02p-3", 1.0f / 1024.f + 1.0f / 8.f},
-      {"-0x1p-9", 1.0f / -512.f},
-      {"-0x1p-10", 1.0f / -1024.f},
-      {"-0x1.02p-3", 1.0f / -1024.f - 1.0f / 8.f},
-
-      // Near lowest non-denorm
-      {"0x1p-124", std::ldexp(1.f * 8.f, -127)},
-      {"0x1p-125", std::ldexp(1.f * 4.f, -127)},
-      {"-0x1p-124", -std::ldexp(1.f * 8.f, -127)},
-      {"-0x1p-125", -std::ldexp(1.f * 4.f, -127)},
-
-      // Lowest non-denorm
-      {"0x1p-126", std::ldexp(1.f * 2.f, -127)},
-      {"-0x1p-126", -std::ldexp(1.f * 2.f, -127)},
-
-      // Denormalized values
-      {"0x1p-127", std::ldexp(1.f, -127)},
-      {"0x1p-128", std::ldexp(1.f / 2.f, -127)},
-      {"0x1p-129", std::ldexp(1.f / 4.f, -127)},
-      {"0x1p-130", std::ldexp(1.f / 8.f, -127)},
-      {"-0x1p-127", -std::ldexp(1.f, -127)},
-      {"-0x1p-128", -std::ldexp(1.f / 2.f, -127)},
-      {"-0x1p-129", -std::ldexp(1.f / 4.f, -127)},
-      {"-0x1p-130", -std::ldexp(1.f / 8.f, -127)},
-
-      {"0x1.8p-127", std::ldexp(1.f, -127) + (std::ldexp(1.f, -127) / 2.f)},
-      {"0x1.8p-128",
-       std::ldexp(1.f, -127) / 2.f + (std::ldexp(1.f, -127) / 4.f)},
-
-      {"0x1p-149", MakeFloat(0, 0, 1)},                 // +SmallestDenormal
-      {"0x1p-148", MakeFloat(0, 0, 2)},                 // +BiggerDenormal
-      {"0x1.fffffcp-127", MakeFloat(0, 0, 0x7fffff)},   // +LargestDenormal
-      {"-0x1p-149", MakeFloat(1, 0, 1)},                // -SmallestDenormal
-      {"-0x1p-148", MakeFloat(1, 0, 2)},                // -BiggerDenormal
-      {"-0x1.fffffcp-127", MakeFloat(1, 0, 0x7fffff)},  // -LargestDenormal
-
-      {"0x1.2bfaf8p-127", MakeFloat(0, 0, 0xcafebe)},   // +Subnormal
-      {"-0x1.2bfaf8p-127", MakeFloat(1, 0, 0xcafebe)},  // -Subnormal
-      {"0x1.55554p-130", MakeFloat(0, 0, 0xaaaaa)},     // +Subnormal
-      {"-0x1.55554p-130", MakeFloat(1, 0, 0xaaaaa)},    // -Subnormal
-
-      // Nan -> Infinity
-      {"0x1.8p+128", PosInf},
-      {"0x1.0002p+128", PosInf},
-      {"0x1.0018p+128", PosInf},
-      {"0x1.01ep+128", PosInf},
-      {"0x1.fffffep+128", PosInf},
-      {"-0x1.8p+128", NegInf},
-      {"-0x1.0002p+128", NegInf},
-      {"-0x1.0018p+128", NegInf},
-      {"-0x1.01ep+128", NegInf},
-      {"-0x1.fffffep+128", NegInf},
-
-      // Infinity
-      {"0x1p+128", PosInf},
-      {"-0x1p+128", NegInf},
-      {"0x32p+127", PosInf},
-      {"0x32p+500", PosInf},
-      {"-0x32p+127", NegInf},
-      {"-0x32p+500", NegInf},
-
-      // Overflow -> Infinity
-      {"0x1p+129", PosInf},
-      {"0x1.1p+128", PosInf},
-      {"-0x1p+129", NegInf},
-      {"-0x1.1p+128", NegInf},
-      {"0x1.0p2147483520", PosInf},  // INT_MAX - 127 (largest valid exponent)
-
-      // Underflow -> Zero
-      {"0x1p-500", 0.f},  // Exponent underflows
-      {"-0x1p-500", -0.f},
-      {"0x0.00000000001p-126", 0.f},  // Fraction causes underflow
-      {"-0x0.0000000001p-127", -0.f},
-      {"0x0.01p-142", 0.f},
-      {"-0x0.01p-142", -0.f},    // Fraction causes additional underflow
-      {"0x1.0p-2147483520", 0},  // -(INT_MAX - 127) (smallest valid exponent)
-
-      // Zero with non-zero exponent -> Zero
-      {"0x0p+0", 0.f},
-      {"0x0p+1", 0.f},
-      {"0x0p-1", 0.f},
-      {"0x0p+9999999999", 0.f},
-      {"0x0p-9999999999", 0.f},
-      // Same, but with very large positive exponents that would cause overflow
-      // if the mantissa were non-zero.
-      {"0x0p+4000000000", 0.f},    // 4 billion:
-      {"0x0p+40000000000", 0.f},   // 40 billion
-      {"-0x0p+40000000000", 0.f},  // As above 2, but negative mantissa
-      {"-0x0p+400000000000", 0.f},
-      {"0x0.00p+4000000000", 0.f},  // As above 4, but with fractional part
-      {"0x0.00p+40000000000", 0.f},
-      {"-0x0.00p+40000000000", 0.f},
-      {"-0x0.00p+400000000000", 0.f},
-      {"0x0p-4000000000", 0.f},  // As above 8, but with negative exponents
-      {"0x0p-40000000000", 0.f},
-      {"-0x0p-40000000000", 0.f},
-      {"-0x0p-400000000000", 0.f},
-      {"0x0.00p-4000000000", 0.f},
-      {"0x0.00p-40000000000", 0.f},
-      {"-0x0.00p-40000000000", 0.f},
-      {"-0x0.00p-400000000000", 0.f},
-
-      // Test parsing
-      {"0x0p0", 0.f},
-      {"0x0p-0", 0.f},
-      {"0x0p+000", 0.f},
-      {"0x00000000000000p+000000000000000", 0.f},
-      {"0x00000000000000p-000000000000000", 0.f},
-      {"0x00000000000001p+000000000000000", 1.f},
-      {"0x00000000000001p-000000000000000", 1.f},
-      {"0x0000000000000000000001.99999ap-000000000000000004", 0.1f},
-      {"0x2p+0", 2.f},
-      {"0xFFp+0", 255.f},
-      {"0x0.8p+0", 0.5f},
-      {"0x0.4p+0", 0.25f},
-      {"0x0.4p+1", 2 * 0.25f},
-      {"0x0.4p+2", 4 * 0.25f},
-      {"0x123Ep+1", 9340.f},
-      {"-0x123Ep+1", -9340.f},
-      {"0x1a2b3cP12", 7.024656e+09f},
-      {"-0x1a2b3cP12", -7.024656e+09f},
-
-      // Examples without a binary exponent part.
-      {"0x1.", 1.0f},
-      {"0x.8", 0.5f},
-      {"0x1.8", 1.5f},
-      {"-0x1.", -1.0f},
-      {"-0x.8", -0.5f},
-      {"-0x1.8", -1.5f},
-
-      // Examples with a binary exponent and a 'f' suffix.
-      {"0x1.p0f", 1.0f},
-      {"0x.8p2f", 2.0f},
-      {"0x1.8p-1f", 0.75f},
-      {"0x2p-2f", 0.5f},  // No binary point
-      {"-0x1.p0f", -1.0f},
-      {"-0x.8p2f", -2.0f},
-      {"-0x1.8p-1f", -0.75f},
-      {"-0x2p-2f", -0.5f},  // No binary point
-  };
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat,
-                         ParserImplFloatLiteralTest,
-                         testing::ValuesIn(HexFloatCases()));
-
-// Now test all the same hex float cases, but with 0X instead of 0x
-template <typename ARR>
-std::vector<FloatLiteralTestCase> UpperCase0X(const ARR& cases) {
-  std::vector<FloatLiteralTestCase> result;
-  result.reserve(cases.size());
-  for (const auto& c : cases) {
-    result.emplace_back(c);
-    auto& input = result.back().input;
-    const auto where = input.find("0x");
-    if (where != std::string::npos) {
-      input[where+1] = 'X';
-    }
-  }
-  return result;
-}
-
-using UpperCase0XTest = ::testing::Test;
-TEST_F(UpperCase0XTest, Samples) {
-  const auto cases = FloatLiteralTestCaseList{
-      {"absent", 0.0}, {"0x", 1.0},      {"0X", 2.0},      {"-0x", 3.0},
-      {"-0X", 4.0},    {"  0x1p1", 5.0}, {"  -0x1p", 6.0}, {" examine ", 7.0}};
-  const auto expected = FloatLiteralTestCaseList{
-      {"absent", 0.0}, {"0X", 1.0},      {"0X", 2.0},      {"-0X", 3.0},
-      {"-0X", 4.0},    {"  0X1p1", 5.0}, {"  -0X1p", 6.0}, {" examine ", 7.0}};
-
-  auto result = UpperCase0X(cases);
-  EXPECT_THAT(result, ::testing::ElementsAreArray(expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat_UpperCase0X,
-                         ParserImplFloatLiteralTest,
-                         testing::ValuesIn(UpperCase0X(HexFloatCases())));
-
-struct InvalidLiteralTestCase {
-  const char* input;
-  const char* error_msg;
-};
-class ParserImplInvalidLiteralTest
-    : public ParserImplTestWithParam<InvalidLiteralTestCase> {};
-TEST_P(ParserImplInvalidLiteralTest, Parse) {
-  auto params = GetParam();
-  SCOPED_TRACE(params.input);
-  auto p = parser(params.input);
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_TRUE(c.errored);
-  EXPECT_EQ(p->error(), params.error_msg);
-  ASSERT_EQ(c.value, nullptr);
-}
-
-InvalidLiteralTestCase invalid_hexfloat_mantissa_too_large_cases[] = {
-    {"0x1.ffffffff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1f.fffffff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1ff.ffffff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1fff.fffff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1ffff.ffff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1fffff.fff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1ffffff.ff8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1fffffff.f8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1ffffffff.8p0", "1:1: mantissa is too large for hex float"},
-    {"0x1ffffffff8.p0", "1:1: mantissa is too large for hex float"},
-};
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplInvalidLiteralTest_HexFloatMantissaTooLarge,
-    ParserImplInvalidLiteralTest,
-    testing::ValuesIn(invalid_hexfloat_mantissa_too_large_cases));
-
-InvalidLiteralTestCase invalid_hexfloat_exponent_too_large_cases[] = {
-    {"0x1p+2147483521", "1:1: exponent is too large for hex float"},
-    {"0x1p-2147483521", "1:1: exponent is too large for hex float"},
-    {"0x1p+4294967296", "1:1: exponent is too large for hex float"},
-    {"0x1p-4294967296", "1:1: exponent is too large for hex float"},
-};
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplInvalidLiteralTest_HexFloatExponentTooLarge,
-    ParserImplInvalidLiteralTest,
-    testing::ValuesIn(invalid_hexfloat_exponent_too_large_cases));
-
-InvalidLiteralTestCase invalid_hexfloat_exponent_missing_cases[] = {
-    // Lower case p
-    {"0x0p", "1:1: expected an exponent value for hex float"},
-    {"0x0p+", "1:1: expected an exponent value for hex float"},
-    {"0x0p-", "1:1: expected an exponent value for hex float"},
-    {"0x1.0p", "1:1: expected an exponent value for hex float"},
-    {"0x0.1p", "1:1: expected an exponent value for hex float"},
-    // Upper case p
-    {"0x0P", "1:1: expected an exponent value for hex float"},
-    {"0x0P+", "1:1: expected an exponent value for hex float"},
-    {"0x0P-", "1:1: expected an exponent value for hex float"},
-    {"0x1.0P", "1:1: expected an exponent value for hex float"},
-    {"0x0.1P", "1:1: expected an exponent value for hex float"},
-};
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplInvalidLiteralTest_HexFloatExponentMissing,
-    ParserImplInvalidLiteralTest,
-    testing::ValuesIn(invalid_hexfloat_exponent_missing_cases));
-
-TEST_F(ParserImplTest, ConstLiteral_FloatHighest) {
-  const auto highest = std::numeric_limits<float>::max();
-  const auto expected_highest = 340282346638528859811704183484516925440.0f;
-  if (highest < expected_highest || highest > expected_highest) {
-    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
-                    "this target";
-  }
-  auto p = parser("340282346638528859811704183484516925440.0");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value,
-                  std::numeric_limits<float>::max());
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 42u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_FloatLowest) {
-  // Some compilers complain if you test floating point numbers for equality.
-  // So say it via two inequalities.
-  const auto lowest = std::numeric_limits<float>::lowest();
-  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
-  if (lowest < expected_lowest || lowest > expected_lowest) {
-    GTEST_SKIP()
-        << "std::numeric_limits<float>::lowest() is not as expected for "
-           "this target";
-  }
-
-  auto p = parser("-340282346638528859811704183484516925440.0");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value,
-                  std::numeric_limits<float>::lowest());
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 43u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_True) {
-  auto p = parser("true");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::BoolLiteralExpression>());
-  EXPECT_TRUE(c->As<ast::BoolLiteralExpression>()->value);
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_False) {
-  auto p = parser("false");
-  auto c = p->const_literal();
-  EXPECT_TRUE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(c.value, nullptr);
-  ASSERT_TRUE(c->Is<ast::BoolLiteralExpression>());
-  EXPECT_FALSE(c->As<ast::BoolLiteralExpression>()->value);
-  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 6u}}));
-}
-
-TEST_F(ParserImplTest, ConstLiteral_NoMatch) {
-  auto p = parser("another-token");
-  auto c = p->const_literal();
-  EXPECT_FALSE(c.matched);
-  EXPECT_FALSE(c.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(c.value, nullptr);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_continue_stmt_test.cc b/src/reader/wgsl/parser_impl_continue_stmt_test.cc
deleted file mode 100644
index 8eba255..0000000
--- a/src/reader/wgsl/parser_impl_continue_stmt_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2020 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/ast/continue_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ContinueStmt) {
-  auto p = parser("continue");
-  auto e = p->continue_stmt();
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::ContinueStatement>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_continuing_stmt_test.cc b/src/reader/wgsl/parser_impl_continuing_stmt_test.cc
deleted file mode 100644
index 27714ff..0000000
--- a/src/reader/wgsl/parser_impl_continuing_stmt_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ContinuingStmt) {
-  auto p = parser("continuing { discard; }");
-  auto e = p->continuing_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->statements.size(), 1u);
-  ASSERT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
-}
-
-TEST_F(ParserImplTest, ContinuingStmt_InvalidBody) {
-  auto p = parser("continuing { discard }");
-  auto e = p->continuing_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:22: expected ';' for discard statement");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_depth_texture_type_test.cc b/src/reader/wgsl/parser_impl_depth_texture_type_test.cc
deleted file mode 100644
index da5e080..0000000
--- a/src/reader/wgsl/parser_impl_depth_texture_type_test.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-#include "src/sem/depth_texture_type.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, DepthTextureType_Invalid) {
-  auto p = parser("1234");
-  auto t = p->depth_texture_type();
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, DepthTextureType_2d) {
-  auto p = parser("texture_depth_2d");
-  auto t = p->depth_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, DepthTextureType_2dArray) {
-  auto p = parser("texture_depth_2d_array");
-  auto t = p->depth_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2dArray);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 23u}}));
-}
-
-TEST_F(ParserImplTest, DepthTextureType_Cube) {
-  auto p = parser("texture_depth_cube");
-  auto t = p->depth_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::kCube);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
-}
-
-TEST_F(ParserImplTest, DepthTextureType_CubeArray) {
-  auto p = parser("texture_depth_cube_array");
-  auto t = p->depth_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::kCubeArray);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
-}
-
-TEST_F(ParserImplTest, DepthTextureType_Multisampled2d) {
-  auto p = parser("texture_depth_multisampled_2d");
-  auto t = p->depth_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthMultisampledTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 30u}}));
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_detail.h b/src/reader/wgsl/parser_impl_detail.h
deleted file mode 100644
index 6d5d144..0000000
--- a/src/reader/wgsl/parser_impl_detail.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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_READER_WGSL_PARSER_IMPL_DETAIL_H_
-#define SRC_READER_WGSL_PARSER_IMPL_DETAIL_H_
-
-#include <memory>
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace detail {
-
-/// OperatorArrow is a traits helper for ParserImpl::Expect<T>::operator->() and
-/// ParserImpl::Maybe<T>::operator->() so that pointer types are automatically
-/// dereferenced. This simplifies usage by allowing
-///  `result.value->field`
-/// to be written as:
-///  `result->field`
-/// As well as reducing the amount of code, using the operator->() asserts that
-/// the Expect<T> or Maybe<T> is not in an error state before dereferencing.
-template <typename T>
-struct OperatorArrow {
-  /// type resolves to the return type for the operator->()
-  using type = T*;
-  /// @param val the value held by `ParserImpl::Expect<T>` or
-  /// `ParserImpl::Maybe<T>`.
-  /// @return a pointer to `val`
-  static inline T* ptr(T& val) { return &val; }
-};
-
-/// OperatorArrow template specialization for std::unique_ptr<>.
-template <typename T>
-struct OperatorArrow<std::unique_ptr<T>> {
-  /// type resolves to the return type for the operator->()
-  using type = T*;
-  /// @param val the value held by `ParserImpl::Expect<T>` or
-  /// `ParserImpl::Maybe<T>`.
-  /// @return the raw pointer held by `val`.
-  static inline T* ptr(std::unique_ptr<T>& val) { return val.get(); }
-};
-
-/// OperatorArrow template specialization for T*.
-template <typename T>
-struct OperatorArrow<T*> {
-  /// type resolves to the return type for the operator->()
-  using type = T*;
-  /// @param val the value held by `ParserImpl::Expect<T>` or
-  /// `ParserImpl::Maybe<T>`.
-  /// @return `val`.
-  static inline T* ptr(T* val) { return val; }
-};
-
-}  // namespace detail
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_PARSER_IMPL_DETAIL_H_
diff --git a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
deleted file mode 100644
index 11099d4..0000000
--- a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ElseStmts) {
-  auto p = parser("else if (a == 4) { a = b; c = d; }");
-  auto e = p->else_stmts();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e.value.size(), 1u);
-
-  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[0]->condition, nullptr);
-  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
-}
-
-TEST_F(ParserImplTest, ElseStmts_Multiple) {
-  auto p = parser("else if (a == 4) { a = b; c = d; } else if(c) { d = 2; }");
-  auto e = p->else_stmts();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e.value.size(), 2u);
-
-  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[0]->condition, nullptr);
-  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
-
-  ASSERT_TRUE(e.value[1]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[1]->condition, nullptr);
-  ASSERT_TRUE(e.value[1]->condition->Is<ast::IdentifierExpression>());
-  EXPECT_EQ(e.value[1]->body->statements.size(), 1u);
-}
-
-TEST_F(ParserImplTest, ElseStmts_InvalidBody) {
-  auto p = parser("else if (true) { fn main() {}}");
-  auto e = p->else_stmts();
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:18: expected '}'");
-}
-
-TEST_F(ParserImplTest, ElseStmts_MissingBody) {
-  auto p = parser("else if (true)");
-  auto e = p->else_stmts();
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:15: expected '{'");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// The tests below use the deprecated 'elseif' syntax
-////////////////////////////////////////////////////////////////////////////////
-
-TEST_F(ParserImplTest, DEPRECATED_ElseStmts) {
-  auto p = parser("elseif (a == 4) { a = b; c = d; }");
-  auto e = p->else_stmts();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if')");
-  ASSERT_EQ(e.value.size(), 1u);
-
-  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[0]->condition, nullptr);
-  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
-}
-
-TEST_F(ParserImplTest, DEPRECATED_ElseStmts_Multiple) {
-  auto p = parser("elseif (a == 4) { a = b; c = d; } elseif(c) { d = 2; }");
-  auto e = p->else_stmts();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
-1:35: use of deprecated language feature: 'elseif' is now 'else if')");
-  ASSERT_EQ(e.value.size(), 2u);
-
-  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[0]->condition, nullptr);
-  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
-
-  ASSERT_TRUE(e.value[1]->Is<ast::ElseStatement>());
-  ASSERT_NE(e.value[1]->condition, nullptr);
-  ASSERT_TRUE(e.value[1]->condition->Is<ast::IdentifierExpression>());
-  EXPECT_EQ(e.value[1]->body->statements.size(), 1u);
-}
-
-TEST_F(ParserImplTest, DEPRECATED_ElseStmts_InvalidBody) {
-  auto p = parser("elseif (true) { fn main() {}}");
-  auto e = p->else_stmts();
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
-1:17: expected '}')");
-}
-
-TEST_F(ParserImplTest, DEPRECATED_ElseStmts_MissingBody) {
-  auto p = parser("elseif (true)");
-  auto e = p->else_stmts();
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
-1:14: expected '{')");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_equality_expression_test.cc b/src/reader/wgsl/parser_impl_equality_expression_test.cc
deleted file mode 100644
index 2d6c2f0..0000000
--- a/src/reader/wgsl/parser_impl_equality_expression_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, EqualityExpression_Parses_Equal) {
-  auto p = parser("a == true");
-  auto e = p->equality_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kEqual, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, EqualityExpression_Parses_NotEqual) {
-  auto p = parser("a != true");
-  auto e = p->equality_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kNotEqual, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, EqualityExpression_InvalidLHS) {
-  auto p = parser("if (a) {} == true");
-  auto e = p->equality_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, EqualityExpression_InvalidRHS) {
-  auto p = parser("true == if (a) {}");
-  auto e = p->equality_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: unable to parse right side of == expression");
-}
-
-TEST_F(ParserImplTest, EqualityExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->equality_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
deleted file mode 100644
index 2a800ed..0000000
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ /dev/null
@@ -1,1695 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-const diag::Formatter::Style formatter_style{
-    /* print_file: */ true, /* print_severity: */ true,
-    /* print_line: */ true, /* print_newline_at_end: */ false};
-
-class ParserImplErrorTest : public ParserImplTest {};
-
-#define EXPECT(SOURCE, EXPECTED)                                               \
-  do {                                                                         \
-    std::string source = SOURCE;                                               \
-    std::string expected = EXPECTED;                                           \
-    auto p = parser(source);                                                   \
-    p->set_max_errors(5);                                                      \
-    EXPECT_EQ(false, p->Parse());                                              \
-    auto diagnostics = p->builder().Diagnostics();                             \
-    EXPECT_EQ(true, diagnostics.contains_errors());                            \
-    EXPECT_EQ(expected, diag::Formatter(formatter_style).format(diagnostics)); \
-  } while (false)
-
-TEST_F(ParserImplErrorTest, AdditiveInvalidExpr) {
-  EXPECT("fn f() { return 1.0 + <; }",
-         R"(test.wgsl:1:23 error: unable to parse right side of + expression
-fn f() { return 1.0 + <; }
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AndInvalidExpr) {
-  EXPECT("fn f() { return 1 & >; }",
-         R"(test.wgsl:1:21 error: unable to parse right side of & expression
-fn f() { return 1 & >; }
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AliasDeclInvalidAttribute) {
-  EXPECT("@invariant type e=u32;",
-         R"(test.wgsl:1:2 error: unexpected attributes
-@invariant type e=u32;
- ^^^^^^^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_AliasDeclInvalidAttribute) {
-  EXPECT(
-      "[[invariant]]type e=u32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[invariant]]type e=u32;
-^^
-
-test.wgsl:1:3 error: unexpected attributes
-[[invariant]]type e=u32;
-  ^^^^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, IndexExprInvalidExpr) {
-  EXPECT("fn f() { x = y[^]; }",
-         R"(test.wgsl:1:16 error: unable to parse expression inside []
-fn f() { x = y[^]; }
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, IndexExprMissingRBracket) {
-  EXPECT("fn f() { x = y[1; }",
-         R"(test.wgsl:1:17 error: expected ']' for index accessor
-fn f() { x = y[1; }
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment) {
-  EXPECT("fn f() { a; }", R"(test.wgsl:1:11 error: expected '=' for assignment
-fn f() { a; }
-          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment2) {
-  EXPECT("fn f() { a : i32; }",
-         R"(test.wgsl:1:10 error: expected 'var' for variable declaration
-fn f() { a : i32; }
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AssignmentStmtMissingSemicolon) {
-  EXPECT("fn f() { a = 1 }",
-         R"(test.wgsl:1:16 error: expected ';' for assignment statement
-fn f() { a = 1 }
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AssignmentStmtInvalidLHS_BuiltinFunctionName) {
-  EXPECT("normalize = 5;",
-         R"(test.wgsl:1:1 error: statement found outside of function body
-normalize = 5;
-^^^^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, AssignmentStmtInvalidRHS) {
-  EXPECT("fn f() { a = >; }",
-         R"(test.wgsl:1:14 error: unable to parse right side of assignment
-fn f() { a = >; }
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, BitcastExprMissingLessThan) {
-  EXPECT("fn f() { x = bitcast(y); }",
-         R"(test.wgsl:1:21 error: expected '<' for bitcast expression
-fn f() { x = bitcast(y); }
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, BitcastExprMissingGreaterThan) {
-  EXPECT("fn f() { x = bitcast<u32(y); }",
-         R"(test.wgsl:1:25 error: expected '>' for bitcast expression
-fn f() { x = bitcast<u32(y); }
-                        ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, BitcastExprMissingType) {
-  EXPECT("fn f() { x = bitcast<>(y); }",
-         R"(test.wgsl:1:22 error: invalid type for bitcast expression
-fn f() { x = bitcast<>(y); }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, BreakStmtMissingSemicolon) {
-  EXPECT("fn f() { loop { break } }",
-         R"(test.wgsl:1:23 error: expected ';' for break statement
-fn f() { loop { break } }
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, CallExprMissingRParen) {
-  EXPECT("fn f() { x = f(1.; }",
-         R"(test.wgsl:1:18 error: expected ')' for function call
-fn f() { x = f(1.; }
-                 ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, CallStmtMissingRParen) {
-  EXPECT("fn f() { f(1.; }",
-         R"(test.wgsl:1:14 error: expected ')' for function call
-fn f() { f(1.; }
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, CallStmtInvalidArgument0) {
-  EXPECT("fn f() { f(<); }",
-         R"(test.wgsl:1:12 error: expected ')' for function call
-fn f() { f(<); }
-           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, CallStmtInvalidArgument1) {
-  EXPECT("fn f() { f(1.0, <); }",
-         R"(test.wgsl:1:17 error: expected ')' for function call
-fn f() { f(1.0, <); }
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, CallStmtMissingSemicolon) {
-  EXPECT("fn f() { f() }",
-         R"(test.wgsl:1:14 error: expected ';' for function call
-fn f() { f() }
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ConstructorExprMissingLParen) {
-  EXPECT("fn f() { x = vec2<u32>1,2); }",
-         R"(test.wgsl:1:23 error: expected '(' for type constructor
-fn f() { x = vec2<u32>1,2); }
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ConstructorExprMissingRParen) {
-  EXPECT("fn f() { x = vec2<u32>(1,2; }",
-         R"(test.wgsl:1:27 error: expected ')' for type constructor
-fn f() { x = vec2<u32>(1,2; }
-                          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ConstVarStmtInvalid) {
-  EXPECT("fn f() { let >; }",
-         R"(test.wgsl:1:14 error: expected identifier for let declaration
-fn f() { let >; }
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ConstVarStmtMissingAssignment) {
-  EXPECT("fn f() { let a : i32; }",
-         R"(test.wgsl:1:21 error: expected '=' for let declaration
-fn f() { let a : i32; }
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ConstVarStmtMissingConstructor) {
-  EXPECT("fn f() { let a : i32 = >; }",
-         R"(test.wgsl:1:24 error: missing constructor for let declaration
-fn f() { let a : i32 = >; }
-                       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ContinueStmtMissingSemicolon) {
-  EXPECT("fn f() { loop { continue } }",
-         R"(test.wgsl:1:26 error: expected ';' for continue statement
-fn f() { loop { continue } }
-                         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, DiscardStmtMissingSemicolon) {
-  EXPECT("fn f() { discard }",
-         R"(test.wgsl:1:18 error: expected ';' for discard statement
-fn f() { discard }
-                 ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, EqualityInvalidExpr) {
-  EXPECT("fn f() { return 1 == >; }",
-         R"(test.wgsl:1:22 error: unable to parse right side of == expression
-fn f() { return 1 == >; }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopInitializerMissingSemicolon) {
-  EXPECT("fn f() { for (var i : i32 = 0 i < 8; i=i+1) {} }",
-         R"(test.wgsl:1:31 error: expected ';' for initializer in for loop
-fn f() { for (var i : i32 = 0 i < 8; i=i+1) {} }
-                              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopInitializerMissingVar) {
-  EXPECT("fn f() { for (i : i32 = 0; i < 8; i=i+1) {} }",
-         R"(test.wgsl:1:15 error: expected 'var' for variable declaration
-fn f() { for (i : i32 = 0; i < 8; i=i+1) {} }
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopConditionMissingSemicolon) {
-  EXPECT("fn f() { for (var i : i32 = 0; i < 8 i=i+1) {} }",
-         R"(test.wgsl:1:38 error: expected ';' for condition in for loop
-fn f() { for (var i : i32 = 0; i < 8 i=i+1) {} }
-                                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopMissingLParen) {
-  EXPECT("fn f() { for var i : i32 = 0; i < 8; i=i+1) {} }",
-         R"(test.wgsl:1:14 error: expected '(' for for loop
-fn f() { for var i : i32 = 0; i < 8; i=i+1) {} }
-             ^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopMissingRParen) {
-  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1 {} }",
-         R"(test.wgsl:1:45 error: expected ')' for for loop
-fn f() { for (var i : i32 = 0; i < 8; i=i+1 {} }
-                                            ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopMissingLBrace) {
-  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1) }",
-         R"(test.wgsl:1:46 error: expected '{' for for loop
-fn f() { for (var i : i32 = 0; i < 8; i=i+1) }
-                                             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ForLoopMissingRBrace) {
-  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1) {",
-         R"(test.wgsl:1:47 error: expected '}' for for loop
-fn f() { for (var i : i32 = 0; i < 8; i=i+1) {
-                                              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclStageMissingLParen) {
-  EXPECT("@stage vertex) fn f() {}",
-         R"(test.wgsl:1:8 error: expected '(' for stage attribute
-@stage vertex) fn f() {}
-       ^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclStageMissingRParen) {
-  EXPECT("@stage(vertex fn f() {}",
-         R"(test.wgsl:1:15 error: expected ')' for stage attribute
-@stage(vertex fn f() {}
-              ^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclStageMissingLParen) {
-  EXPECT(
-      "[[stage vertex]] fn f() {}",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[stage vertex]] fn f() {}
-^^
-
-test.wgsl:1:9 error: expected '(' for stage attribute
-[[stage vertex]] fn f() {}
-        ^^^^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclStageMissingRParen) {
-  EXPECT(
-      "[[stage(vertex]] fn f() {}",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[stage(vertex]] fn f() {}
-^^
-
-test.wgsl:1:15 error: expected ')' for stage attribute
-[[stage(vertex]] fn f() {}
-              ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclStageInvalid) {
-  EXPECT("@stage(x) fn f() {}",
-         R"(test.wgsl:1:8 error: invalid value for stage attribute
-@stage(x) fn f() {}
-       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclStageTypeInvalid) {
-  EXPECT("@shader(vertex) fn main() {}",
-         R"(test.wgsl:1:2 error: expected attribute
-@shader(vertex) fn main() {}
- ^^^^^^
-
-test.wgsl:1:8 error: unexpected token
-@shader(vertex) fn main() {}
-       ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclWorkgroupSizeMissingLParen) {
-  EXPECT(
-      "[[workgroup_size 1]] fn f() {}",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[workgroup_size 1]] fn f() {}
-^^
-
-test.wgsl:1:18 error: expected '(' for workgroup_size attribute
-[[workgroup_size 1]] fn f() {}
-                 ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclWorkgroupSizeMissingRParen) {
-  EXPECT(
-      "[[workgroup_size(1]] fn f() {}",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[workgroup_size(1]] fn f() {}
-^^
-
-test.wgsl:1:19 error: expected ')' for workgroup_size attribute
-[[workgroup_size(1]] fn f() {}
-                  ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeXInvalid) {
-  EXPECT("@workgroup_size() fn f() {}",
-         R"(test.wgsl:1:17 error: expected workgroup_size x parameter
-@workgroup_size() fn f() {}
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeYInvalid) {
-  EXPECT("@workgroup_size(1, ) fn f() {}",
-         R"(test.wgsl:1:20 error: expected workgroup_size y parameter
-@workgroup_size(1, ) fn f() {}
-                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeZInvalid) {
-  EXPECT("@workgroup_size(1, 2, ) fn f() {}",
-         R"(test.wgsl:1:23 error: expected workgroup_size z parameter
-@workgroup_size(1, 2, ) fn f() {}
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingIdentifier) {
-  EXPECT("fn () {}",
-         R"(test.wgsl:1:4 error: expected identifier for function declaration
-fn () {}
-   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingLParen) {
-  EXPECT("fn f) {}",
-         R"(test.wgsl:1:5 error: expected '(' for function declaration
-fn f) {}
-    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingRParen) {
-  EXPECT("fn f( {}",
-         R"(test.wgsl:1:7 error: expected ')' for function declaration
-fn f( {}
-      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingArrow) {
-  EXPECT("fn f() f32 {}", R"(test.wgsl:1:8 error: expected '{'
-fn f() f32 {}
-       ^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclInvalidReturnType) {
-  EXPECT("fn f() -> 1 {}",
-         R"(test.wgsl:1:11 error: unable to determine function return type
-fn f() -> 1 {}
-          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclParamMissingColon) {
-  EXPECT("fn f(x) {}", R"(test.wgsl:1:7 error: expected ':' for parameter
-fn f(x) {}
-      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclParamInvalidType) {
-  EXPECT("fn f(x : 1) {}", R"(test.wgsl:1:10 error: invalid type for parameter
-fn f(x : 1) {}
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclParamMissing) {
-  EXPECT("fn f(x : i32, ,) {}",
-         R"(test.wgsl:1:15 error: expected ')' for function declaration
-fn f(x : i32, ,) {}
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingLBrace) {
-  EXPECT("fn f() }", R"(test.wgsl:1:8 error: expected '{'
-fn f() }
-       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionDeclMissingRBrace) {
-  EXPECT("fn f() {", R"(test.wgsl:1:9 error: expected '}'
-fn f() {
-        ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionScopeUnusedDecl) {
-  EXPECT("fn f(a:i32)->i32{return a;@size(1)}",
-         R"(test.wgsl:1:28 error: unexpected attributes
-fn f(a:i32)->i32{return a;@size(1)}
-                           ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, FunctionMissingOpenLine) {
-  EXPECT(R"(let bar : vec2<f32> = vec2<f32>(1., 2.);
-  var a : f32 = bar[0];
-  return;
-})",
-         R"(test.wgsl:2:17 error: unable to parse const_expr
-  var a : f32 = bar[0];
-                ^^^
-
-test.wgsl:3:3 error: statement found outside of function body
-  return;
-  ^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) {
-  EXPECT("let ^ : i32 = 1;",
-         R"(test.wgsl:1:5 error: expected identifier for let declaration
-let ^ : i32 = 1;
-    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) {
-  EXPECT("let i : i32 = 1",
-         R"(test.wgsl:1:16 error: expected ';' for let declaration
-let i : i32 = 1
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstMissingLParen) {
-  EXPECT("let i : vec2<i32> = vec2<i32>;",
-         R"(test.wgsl:1:30 error: expected '(' for type constructor
-let i : vec2<i32> = vec2<i32>;
-                             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) {
-  EXPECT("let i : vec2<i32> = vec2<i32>(1., 2.;",
-         R"(test.wgsl:1:37 error: expected ')' for type constructor
-let i : vec2<i32> = vec2<i32>(1., 2.;
-                                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) {
-  EXPECT("let i : vec2<i32> = vec2<i32>(!);",
-         R"(test.wgsl:1:31 error: unable to parse const_expr
-let i : vec2<i32> = vec2<i32>(!);
-                              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) {
-  EXPECT("let i = 1 < 2;",
-         R"(test.wgsl:1:11 error: expected ';' for let declaration
-let i = 1 < 2;
-          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) {
-  EXPECT(
-      "let a = 1;\n"
-      "let b = a;",
-      R"(test.wgsl:2:9 error: unable to parse const_expr
-let b = a;
-        ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) {
-  uint32_t kMaxDepth = 128;
-
-  std::stringstream src;
-  std::stringstream mkr;
-  src << "let i : i32 = ";
-  mkr << "              ";
-  for (size_t i = 0; i < kMaxDepth + 8; i++) {
-    src << "f32(";
-    if (i < kMaxDepth) {
-      mkr << "    ";
-    } else if (i == kMaxDepth) {
-      mkr << "^^^";
-    }
-  }
-  src << "1.0";
-  for (size_t i = 0; i < 200; i++) {
-    src << ")";
-  }
-  src << ";";
-  std::stringstream err;
-  err << "test.wgsl:1:527 error: maximum parser recursive depth reached\n"
-      << src.str() << "\n"
-      << mkr.str() << "\n";
-  EXPECT(src.str().c_str(), err.str().c_str());
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingLParen) {
-  EXPECT("let i : vec2<i32> = vec2<i32> 1, 2);",
-         R"(test.wgsl:1:31 error: expected '(' for type constructor
-let i : vec2<i32> = vec2<i32> 1, 2);
-                              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) {
-  EXPECT("let i : vec2<i32> = vec2<i32>(1, 2;",
-         R"(test.wgsl:1:35 error: expected ')' for type constructor
-let i : vec2<i32> = vec2<i32>(1, 2;
-                                  ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclInvalidAttribute) {
-  EXPECT("@stage(vertex) x;",
-         R"(test.wgsl:1:16 error: expected declaration after attributes
-@stage(vertex) x;
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureMissingLessThan) {
-  EXPECT("var x : texture_1d;",
-         R"(test.wgsl:1:19 error: expected '<' for sampled texture type
-var x : texture_1d;
-                  ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureMissingGreaterThan) {
-  EXPECT("var x : texture_1d<f32;",
-         R"(test.wgsl:1:23 error: expected '>' for sampled texture type
-var x : texture_1d<f32;
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureInvalidSubtype) {
-  EXPECT("var x : texture_1d<1>;",
-         R"(test.wgsl:1:20 error: invalid type for sampled texture type
-var x : texture_1d<1>;
-                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureMissingLessThan) {
-  EXPECT("var x : texture_multisampled_2d;",
-         R"(test.wgsl:1:32 error: expected '<' for multisampled texture type
-var x : texture_multisampled_2d;
-                               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureMissingGreaterThan) {
-  EXPECT("var x : texture_multisampled_2d<f32;",
-         R"(test.wgsl:1:36 error: expected '>' for multisampled texture type
-var x : texture_multisampled_2d<f32;
-                                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureInvalidSubtype) {
-  EXPECT("var x : texture_multisampled_2d<1>;",
-         R"(test.wgsl:1:33 error: invalid type for multisampled texture type
-var x : texture_multisampled_2d<1>;
-                                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingLessThan) {
-  EXPECT("var x : texture_storage_2d;",
-         R"(test.wgsl:1:27 error: expected '<' for storage texture type
-var x : texture_storage_2d;
-                          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingGreaterThan) {
-  EXPECT("var x : texture_storage_2d<r32uint, read;",
-         R"(test.wgsl:1:41 error: expected '>' for storage texture type
-var x : texture_storage_2d<r32uint, read;
-                                        ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingSubtype) {
-  EXPECT("var x : texture_storage_2d<>;",
-         R"(test.wgsl:1:28 error: invalid format for storage texture type
-var x : texture_storage_2d<>;
-                           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingInvalidSubtype) {
-  EXPECT("var x : texture_storage_2d<1>;",
-         R"(test.wgsl:1:28 error: invalid format for storage texture type
-var x : texture_storage_2d<1>;
-                           ^
-)");
-}
-
-// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructAttrMissingStruct) {
-  EXPECT(
-      "[[block]];",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[block]];
-^^
-
-test.wgsl:1:3 warning: use of deprecated language feature: [[block]] attributes have been removed from WGSL
-[[block]];
-  ^^^^^
-
-test.wgsl:1:10 error: expected declaration after attributes
-[[block]];
-         ^
-)");
-}
-
-// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructAttrMissingEnd) {
-  EXPECT(
-      "[[block struct {};",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[block struct {};
-^^
-
-test.wgsl:1:3 warning: use of deprecated language feature: [[block]] attributes have been removed from WGSL
-[[block struct {};
-  ^^^^^
-
-test.wgsl:1:9 error: expected ']]' for attribute list
-[[block struct {};
-        ^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingIdentifier) {
-  EXPECT("struct {};",
-         R"(test.wgsl:1:8 error: expected identifier for struct declaration
-struct {};
-       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingLBrace) {
-  EXPECT("struct S };",
-         R"(test.wgsl:1:10 error: expected '{' for struct declaration
-struct S };
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingRBrace) {
-  EXPECT("struct S { i : i32;",
-         R"(test.wgsl:1:20 error: expected '}' for struct declaration
-struct S { i : i32;
-                   ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructMemberAttrEmpty) {
-  EXPECT(
-      "struct S { [[]] i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[]] i : i32; };
-           ^^
-
-test.wgsl:1:14 error: empty attribute list
-struct S { [[]] i : i32; };
-             ^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructMemberAttrMissingEnd) {
-  EXPECT(
-      "struct S { [[ i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[ i : i32; };
-           ^^
-
-test.wgsl:1:15 error: expected attribute
-struct S { [[ i : i32; };
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberInvalidIdentifier) {
-  EXPECT("struct S { 1 : i32; };",
-         R"(test.wgsl:1:12 error: expected identifier for struct member
-struct S { 1 : i32; };
-           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberMissingSemicolon) {
-  EXPECT("struct S { i : i32 };",
-         R"(test.wgsl:1:20 error: expected ';' for struct member
-struct S { i : i32 };
-                   ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest,
-       DEPRECATED_GlobalDeclStructMemberAlignMissingLParen) {
-  EXPECT(
-      "struct S { [[align 1)]] i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[align 1)]] i : i32; };
-           ^^
-
-test.wgsl:1:20 error: expected '(' for align attribute
-struct S { [[align 1)]] i : i32; };
-                   ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest,
-       DEPRECATED_GlobalDeclStructMemberAlignMissingRParen) {
-  EXPECT(
-      "struct S { [[align(1]] i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[align(1]] i : i32; };
-           ^^
-
-test.wgsl:1:21 error: expected ')' for align attribute
-struct S { [[align(1]] i : i32; };
-                    ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignInvaldValue) {
-  EXPECT(
-      "struct S { @align(x) i : i32; };",
-      R"(test.wgsl:1:19 error: expected signed integer literal for align attribute
-struct S { @align(x) i : i32; };
-                  ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignNegativeValue) {
-  EXPECT("struct S { @align(-2) i : i32; };",
-         R"(test.wgsl:1:19 error: align attribute must be positive
-struct S { @align(-2) i : i32; };
-                  ^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest,
-       DEPRECATED_GlobalDeclStructMemberSizeMissingLParen) {
-  EXPECT(
-      "struct S { [[size 1)]] i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[size 1)]] i : i32; };
-           ^^
-
-test.wgsl:1:19 error: expected '(' for size attribute
-struct S { [[size 1)]] i : i32; };
-                  ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest,
-       DEPRECATED_GlobalDeclStructMemberSizeMissingRParen) {
-  EXPECT(
-      "struct S { [[size(1]] i : i32; };",
-      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-struct S { [[size(1]] i : i32; };
-           ^^
-
-test.wgsl:1:20 error: expected ')' for size attribute
-struct S { [[size(1]] i : i32; };
-                   ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeInvaldValue) {
-  EXPECT(
-      "struct S { @size(x) i : i32; };",
-      R"(test.wgsl:1:18 error: expected signed integer literal for size attribute
-struct S { @size(x) i : i32; };
-                 ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeNegativeValue) {
-  EXPECT("struct S { @size(-2) i : i32; };",
-         R"(test.wgsl:1:18 error: size attribute must be positive
-struct S { @size(-2) i : i32; };
-                 ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingIdentifier) {
-  EXPECT("type 1 = f32;",
-         R"(test.wgsl:1:6 error: expected identifier for type alias
-type 1 = f32;
-     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasInvalidType) {
-  EXPECT("type meow = 1;", R"(test.wgsl:1:13 error: invalid type alias
-type meow = 1;
-            ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingAssignment) {
-  EXPECT("type meow f32", R"(test.wgsl:1:11 error: expected '=' for type alias
-type meow f32
-          ^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingSemicolon) {
-  EXPECT("type meow = f32", R"(test.wgsl:1:16 error: expected ';' for type alias
-type meow = f32
-               ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclTypeAttrInvalid) {
-  EXPECT(
-      "var x : [[]] i32;",
-      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-var x : [[]] i32;
-        ^^
-
-test.wgsl:1:11 error: empty attribute list
-var x : [[]] i32;
-          ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingLessThan) {
-  EXPECT("var i : array;",
-         R"(test.wgsl:1:14 error: expected '<' for array declaration
-var i : array;
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingGreaterThan) {
-  EXPECT("var i : array<u32, 3;",
-         R"(test.wgsl:1:21 error: expected '>' for array declaration
-var i : array<u32, 3;
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayAttrNotArray) {
-  EXPECT("var i : @location(1) i32;",
-         R"(test.wgsl:1:10 error: unexpected attributes
-var i : @location(1) i32;
-         ^^^^^^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayAttrMissingEnd) {
-  EXPECT(
-      "var i : [[location(1) array<i32>;",
-      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-var i : [[location(1) array<i32>;
-        ^^
-
-test.wgsl:1:23 error: expected ']]' for attribute list
-var i : [[location(1) array<i32>;
-                      ^^^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayStrideMissingLParen) {
-  EXPECT(
-      "var i : [[stride 1)]] array<i32>;",
-      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-var i : [[stride 1)]] array<i32>;
-        ^^
-
-test.wgsl:1:18 error: expected '(' for stride attribute
-var i : [[stride 1)]] array<i32>;
-                 ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayStrideMissingRParen) {
-  EXPECT(
-      "var i : [[location(1]] array<i32>;",
-      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-var i : [[location(1]] array<i32>;
-        ^^
-
-test.wgsl:1:21 error: expected ')' for location attribute
-var i : [[location(1]] array<i32>;
-                    ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayStrideInvalid) {
-  EXPECT(
-      "var i : @stride(x) array<i32>;",
-      R"(test.wgsl:1:17 error: expected signed integer literal for stride attribute
-var i : @stride(x) array<i32>;
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayStrideNegative) {
-  EXPECT("var i : @stride(-1) array<i32>;",
-         R"(test.wgsl:1:17 error: stride attribute must be greater than 0
-var i : @stride(-1) array<i32>;
-                ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingType) {
-  EXPECT("var i : array<1, 3>;",
-         R"(test.wgsl:1:15 error: invalid type for array declaration
-var i : array<1, 3>;
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingSize) {
-  EXPECT("var i : array<u32, >;",
-         R"(test.wgsl:1:20 error: expected array size expression
-var i : array<u32, >;
-                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayInvalidSize) {
-  EXPECT("var i : array<u32, !>;",
-         R"(test.wgsl:1:20 error: expected array size expression
-var i : array<u32, !>;
-                   ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListEmpty) {
-  EXPECT(
-      "[[]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[]] var i : i32;
-^^
-
-test.wgsl:1:3 error: empty attribute list
-[[]] var i : i32;
-  ^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListInvalid) {
-  EXPECT(
-      "[[location(1), meow]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[location(1), meow]] var i : i32;
-^^
-
-test.wgsl:1:16 error: expected attribute
-[[location(1), meow]] var i : i32;
-               ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrListMissingComma) {
-  EXPECT("@location(1) group(2) var i : i32;",
-         R"(test.wgsl:1:14 error: expected declaration after attributes
-@location(1) group(2) var i : i32;
-             ^^^^^
-
-test.wgsl:1:19 error: unexpected token
-@location(1) group(2) var i : i32;
-                  ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListMissingEnd) {
-  EXPECT(
-      "[[location(1) meow]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[location(1) meow]] var i : i32;
-^^
-
-test.wgsl:1:15 error: expected ']]' for attribute list
-[[location(1) meow]] var i : i32;
-              ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationMissingLParen) {
-  EXPECT("@location 1) var i : i32;",
-         R"(test.wgsl:1:11 error: expected '(' for location attribute
-@location 1) var i : i32;
-          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationMissingRParen) {
-  EXPECT("@location (1 var i : i32;",
-         R"(test.wgsl:1:14 error: expected ')' for location attribute
-@location (1 var i : i32;
-             ^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrLocationMissingLParen) {
-  EXPECT(
-      "[[location 1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[location 1]] var i : i32;
-^^
-
-test.wgsl:1:12 error: expected '(' for location attribute
-[[location 1]] var i : i32;
-           ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrLocationMissingRParen) {
-  EXPECT(
-      "[[location (1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[location (1]] var i : i32;
-^^
-
-test.wgsl:1:14 error: expected ')' for location attribute
-[[location (1]] var i : i32;
-             ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationInvalidValue) {
-  EXPECT(
-      "@location(x) var i : i32;",
-      R"(test.wgsl:1:11 error: expected signed integer literal for location attribute
-@location(x) var i : i32;
-          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinMissingLParen) {
-  EXPECT("@builtin position) var i : i32;",
-         R"(test.wgsl:1:10 error: expected '(' for builtin attribute
-@builtin position) var i : i32;
-         ^^^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinMissingRParen) {
-  EXPECT("@builtin(position var i : i32;",
-         R"(test.wgsl:1:19 error: expected ')' for builtin attribute
-@builtin(position var i : i32;
-                  ^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBuiltinMissingLParen) {
-  EXPECT(
-      "[[builtin position]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[builtin position]] var i : i32;
-^^
-
-test.wgsl:1:11 error: expected '(' for builtin attribute
-[[builtin position]] var i : i32;
-          ^^^^^^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBuiltinMissingRParen) {
-  EXPECT(
-      "[[builtin(position]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[builtin(position]] var i : i32;
-^^
-
-test.wgsl:1:19 error: expected ')' for builtin attribute
-[[builtin(position]] var i : i32;
-                  ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidIdentifer) {
-  EXPECT("@builtin(1) var i : i32;",
-         R"(test.wgsl:1:10 error: expected identifier for builtin
-@builtin(1) var i : i32;
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidValue) {
-  EXPECT("@builtin(x) var i : i32;",
-         R"(test.wgsl:1:10 error: invalid value for builtin attribute
-@builtin(x) var i : i32;
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingMissingLParen) {
-  EXPECT("@binding 1) var i : i32;",
-         R"(test.wgsl:1:10 error: expected '(' for binding attribute
-@binding 1) var i : i32;
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingMissingRParen) {
-  EXPECT("@binding(1 var i : i32;",
-         R"(test.wgsl:1:12 error: expected ')' for binding attribute
-@binding(1 var i : i32;
-           ^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBindingMissingLParen) {
-  EXPECT(
-      "[[binding 1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[binding 1]] var i : i32;
-^^
-
-test.wgsl:1:11 error: expected '(' for binding attribute
-[[binding 1]] var i : i32;
-          ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBindingMissingRParen) {
-  EXPECT(
-      "[[binding(1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[binding(1]] var i : i32;
-^^
-
-test.wgsl:1:12 error: expected ')' for binding attribute
-[[binding(1]] var i : i32;
-           ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingInvalidValue) {
-  EXPECT(
-      "@binding(x) var i : i32;",
-      R"(test.wgsl:1:10 error: expected signed integer literal for binding attribute
-@binding(x) var i : i32;
-         ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrGroupMissingLParen) {
-  EXPECT("@group 1) var i : i32;",
-         R"(test.wgsl:1:8 error: expected '(' for group attribute
-@group 1) var i : i32;
-       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrGroupMissingRParen) {
-  EXPECT("@group(1 var i : i32;",
-         R"(test.wgsl:1:10 error: expected ')' for group attribute
-@group(1 var i : i32;
-         ^^^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrGroupMissingLParen) {
-  EXPECT(
-      "[[group 1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[group 1]] var i : i32;
-^^
-
-test.wgsl:1:9 error: expected '(' for group attribute
-[[group 1]] var i : i32;
-        ^
-)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrGroupMissingRParen) {
-  EXPECT(
-      "[[group(1]] var i : i32;",
-      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-[[group(1]] var i : i32;
-^^
-
-test.wgsl:1:10 error: expected ')' for group attribute
-[[group(1]] var i : i32;
-         ^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingGroupValue) {
-  EXPECT(
-      "@group(x) var i : i32;",
-      R"(test.wgsl:1:8 error: expected signed integer literal for group attribute
-@group(x) var i : i32;
-       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarInvalidIdentifier) {
-  EXPECT("var ^ : mat4x4;",
-         R"(test.wgsl:1:5 error: expected identifier for variable declaration
-var ^ : mat4x4;
-    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingGreaterThan) {
-  EXPECT("var i : mat4x4<u32;", R"(test.wgsl:1:19 error: expected '>' for matrix
-var i : mat4x4<u32;
-                  ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingType) {
-  EXPECT("var i : mat4x4<1>;", R"(test.wgsl:1:16 error: invalid type for matrix
-var i : mat4x4<1>;
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarMissingSemicolon) {
-  EXPECT("var i : i32",
-         R"(test.wgsl:1:12 error: expected ';' for variable declaration
-var i : i32
-           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingLessThan) {
-  EXPECT("var i : ptr;",
-         R"(test.wgsl:1:12 error: expected '<' for ptr declaration
-var i : ptr;
-           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingGreaterThan) {
-  EXPECT("var i : ptr<private, u32;",
-         R"(test.wgsl:1:25 error: expected '>' for ptr declaration
-var i : ptr<private, u32;
-                        ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingComma) {
-  EXPECT("var i : ptr<private u32>;",
-         R"(test.wgsl:1:21 error: expected ',' for ptr declaration
-var i : ptr<private u32>;
-                    ^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingStorageClass) {
-  EXPECT("var i : ptr<meow, u32>;",
-         R"(test.wgsl:1:13 error: invalid storage class for ptr declaration
-var i : ptr<meow, u32>;
-            ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingType) {
-  EXPECT("var i : ptr<private, 1>;",
-         R"(test.wgsl:1:22 error: invalid type for ptr declaration
-var i : ptr<private, 1>;
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAtomicMissingLessThan) {
-  EXPECT("var i : atomic;",
-         R"(test.wgsl:1:15 error: expected '<' for atomic declaration
-var i : atomic;
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarAtomicMissingGreaterThan) {
-  EXPECT("var i : atomic<u32 x;",
-         R"(test.wgsl:1:20 error: expected '>' for atomic declaration
-var i : atomic<u32 x;
-                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarStorageDeclInvalidClass) {
-  EXPECT("var<fish> i : i32",
-         R"(test.wgsl:1:5 error: invalid storage class for variable declaration
-var<fish> i : i32
-    ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarStorageDeclMissingGThan) {
-  EXPECT("var<private i : i32",
-         R"(test.wgsl:1:13 error: expected '>' for variable declaration
-var<private i : i32
-            ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingGreaterThan) {
-  EXPECT("var i : vec3<u32;", R"(test.wgsl:1:17 error: expected '>' for vector
-var i : vec3<u32;
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingType) {
-  EXPECT("var i : vec3<1>;", R"(test.wgsl:1:14 error: invalid type for vector
-var i : vec3<1>;
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, IfStmtMissingLParen) {
-  EXPECT("fn f() { if true) {} }", R"(test.wgsl:1:13 error: expected '('
-fn f() { if true) {} }
-            ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, IfStmtMissingRParen) {
-  EXPECT("fn f() { if (true {} }", R"(test.wgsl:1:19 error: expected ')'
-fn f() { if (true {} }
-                  ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, IfStmtInvalidCond) {
-  EXPECT("fn f() { if (>) {} }",
-         R"(test.wgsl:1:14 error: unable to parse expression
-fn f() { if (>) {} }
-             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, LogicalAndInvalidExpr) {
-  EXPECT("fn f() { return 1 && >; }",
-         R"(test.wgsl:1:22 error: unable to parse right side of && expression
-fn f() { return 1 && >; }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, LogicalOrInvalidExpr) {
-  EXPECT("fn f() { return 1 || >; }",
-         R"(test.wgsl:1:22 error: unable to parse right side of || expression
-fn f() { return 1 || >; }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, LoopMissingLBrace) {
-  EXPECT("fn f() { loop }", R"(test.wgsl:1:15 error: expected '{' for loop
-fn f() { loop }
-              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, LoopMissingRBrace) {
-  EXPECT("fn f() { loop {", R"(test.wgsl:1:16 error: expected '}' for loop
-fn f() { loop {
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, MaxErrorsReached) {
-  EXPECT("x; x; x; x; x; x; x; x;", R"(test.wgsl:1:1 error: unexpected token
-x; x; x; x; x; x; x; x;
-^
-
-test.wgsl:1:4 error: unexpected token
-x; x; x; x; x; x; x; x;
-   ^
-
-test.wgsl:1:7 error: unexpected token
-x; x; x; x; x; x; x; x;
-      ^
-
-test.wgsl:1:10 error: unexpected token
-x; x; x; x; x; x; x; x;
-         ^
-
-test.wgsl:1:13 error: unexpected token
-x; x; x; x; x; x; x; x;
-            ^
-
-test.wgsl error: stopping after 5 errors)");
-}
-
-TEST_F(ParserImplErrorTest, MemberExprMissingIdentifier) {
-  EXPECT("fn f() { x = a.; }",
-         R"(test.wgsl:1:16 error: expected identifier for member accessor
-fn f() { x = a.; }
-               ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, MultiplicativeInvalidExpr) {
-  EXPECT("fn f() { return 1.0 * <; }",
-         R"(test.wgsl:1:23 error: unable to parse right side of * expression
-fn f() { return 1.0 * <; }
-                      ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, OrInvalidExpr) {
-  EXPECT("fn f() { return 1 | >; }",
-         R"(test.wgsl:1:21 error: unable to parse right side of | expression
-fn f() { return 1 | >; }
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, RelationalInvalidExpr) {
-  EXPECT("fn f() { return 1 < >; }",
-         R"(test.wgsl:1:21 error: unable to parse right side of < expression
-fn f() { return 1 < >; }
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ReturnStmtMissingSemicolon) {
-  EXPECT("fn f() { return }",
-         R"(test.wgsl:1:17 error: expected ';' for return statement
-fn f() { return }
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, ShiftInvalidExpr) {
-  EXPECT("fn f() { return 1 << >; }",
-         R"(test.wgsl:1:22 error: unable to parse right side of << expression
-fn f() { return 1 << >; }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtMissingLBrace) {
-  EXPECT("fn f() { switch(1) }",
-         R"(test.wgsl:1:20 error: expected '{' for switch statement
-fn f() { switch(1) }
-                   ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtMissingRBrace) {
-  EXPECT("fn f() { switch(1) {",
-         R"(test.wgsl:1:21 error: expected '}' for switch statement
-fn f() { switch(1) {
-                    ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtInvalidCase) {
-  EXPECT("fn f() { switch(1) { case ^: } }",
-         R"(test.wgsl:1:27 error: unable to parse case selectors
-fn f() { switch(1) { case ^: } }
-                          ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtInvalidCase2) {
-  EXPECT("fn f() { switch(1) { case false: } }",
-         R"(test.wgsl:1:27 error: invalid case selector must be an integer value
-fn f() { switch(1) { case false: } }
-                          ^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingColon) {
-  EXPECT("fn f() { switch(1) { case 1 {} } }",
-         R"(test.wgsl:1:29 error: expected ':' for case statement
-fn f() { switch(1) { case 1 {} } }
-                            ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingLBrace) {
-  EXPECT("fn f() { switch(1) { case 1: } }",
-         R"(test.wgsl:1:30 error: expected '{' for case statement
-fn f() { switch(1) { case 1: } }
-                             ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingRBrace) {
-  EXPECT("fn f() { switch(1) { case 1: {",
-         R"(test.wgsl:1:31 error: expected '}' for case statement
-fn f() { switch(1) { case 1: {
-                              ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, SwitchStmtCaseFallthroughMissingSemicolon) {
-  EXPECT("fn f() { switch(1) { case 1: { fallthrough } case 2: {} } }",
-         R"(test.wgsl:1:44 error: expected ';' for fallthrough statement
-fn f() { switch(1) { case 1: { fallthrough } case 2: {} } }
-                                           ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, VarStmtMissingSemicolon) {
-  EXPECT("fn f() { var a : u32 }",
-         R"(test.wgsl:1:22 error: expected ';' for variable declaration
-fn f() { var a : u32 }
-                     ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, VarStmtInvalidAssignment) {
-  EXPECT("fn f() { var a : u32 = >; }",
-         R"(test.wgsl:1:24 error: missing constructor for variable declaration
-fn f() { var a : u32 = >; }
-                       ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, UnaryInvalidExpr) {
-  EXPECT("fn f() { return !<; }",
-         R"(test.wgsl:1:18 error: unable to parse right side of ! expression
-fn f() { return !<; }
-                 ^
-)");
-}
-
-TEST_F(ParserImplErrorTest, UnexpectedToken) {
-  EXPECT("unexpected", R"(test.wgsl:1:1 error: unexpected token
-unexpected
-^^^^^^^^^^
-)");
-}
-
-TEST_F(ParserImplErrorTest, XorInvalidExpr) {
-  EXPECT("fn f() { return 1 ^ >; }",
-         R"(test.wgsl:1:21 error: unable to parse right side of ^ expression
-fn f() { return 1 ^ >; }
-                    ^
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_error_resync_test.cc b/src/reader/wgsl/parser_impl_error_resync_test.cc
deleted file mode 100644
index 86ee2a2..0000000
--- a/src/reader/wgsl/parser_impl_error_resync_test.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-const diag::Formatter::Style formatter_style{
-    /* print_file: */ true, /* print_severity: */ true,
-    /* print_line: */ true, /* print_newline_at_end: */ false};
-
-class ParserImplErrorResyncTest : public ParserImplTest {};
-
-#define EXPECT(SOURCE, EXPECTED)                                               \
-  do {                                                                         \
-    std::string source = SOURCE;                                               \
-    std::string expected = EXPECTED;                                           \
-    auto p = parser(source);                                                   \
-    EXPECT_EQ(false, p->Parse());                                              \
-    auto diagnostics = p->builder().Diagnostics();                             \
-    EXPECT_EQ(true, diagnostics.contains_errors());                            \
-    EXPECT_EQ(expected, diag::Formatter(formatter_style).format(diagnostics)); \
-  } while (false)
-
-TEST_F(ParserImplErrorResyncTest, BadFunctionDecls) {
-  EXPECT(R"(
-fn .() -> . {}
-fn x(.) {}
-@_ fn -> {}
-fn good() {}
-)",
-         R"(test.wgsl:2:4 error: expected identifier for function declaration
-fn .() -> . {}
-   ^
-
-test.wgsl:2:11 error: unable to determine function return type
-fn .() -> . {}
-          ^
-
-test.wgsl:3:6 error: expected ')' for function declaration
-fn x(.) {}
-     ^
-
-test.wgsl:4:2 error: expected attribute
-@_ fn -> {}
- ^
-
-test.wgsl:4:7 error: expected identifier for function declaration
-@_ fn -> {}
-      ^^
-)");
-}
-
-TEST_F(ParserImplErrorResyncTest, AssignmentStatement) {
-  EXPECT(R"(
-fn f() {
-  blah blah blah blah;
-  good = 1;
-  blah blah blah blah;
-  x = .;
-  good = 1;
-}
-)",
-         R"(test.wgsl:3:8 error: expected '=' for assignment
-  blah blah blah blah;
-       ^^^^
-
-test.wgsl:5:8 error: expected '=' for assignment
-  blah blah blah blah;
-       ^^^^
-
-test.wgsl:6:7 error: unable to parse right side of assignment
-  x = .;
-      ^
-)");
-}
-
-TEST_F(ParserImplErrorResyncTest, DiscardStatement) {
-  EXPECT(R"(
-fn f() {
-  discard blah blah blah;
-  a = 1;
-  discard blah blah blah;
-}
-)",
-         R"(test.wgsl:3:11 error: expected ';' for discard statement
-  discard blah blah blah;
-          ^^^^
-
-test.wgsl:5:11 error: expected ';' for discard statement
-  discard blah blah blah;
-          ^^^^
-)");
-}
-
-TEST_F(ParserImplErrorResyncTest, StructMembers) {
-  EXPECT(R"(
-struct S {
-    blah blah blah;
-    a : i32;
-    blah blah blah;
-    b : i32;
-    @- x : i32;
-    c : i32;
-}
-)",
-         R"(test.wgsl:3:10 error: expected ':' for struct member
-    blah blah blah;
-         ^^^^
-
-test.wgsl:5:10 error: expected ':' for struct member
-    blah blah blah;
-         ^^^^
-
-test.wgsl:7:6 error: expected attribute
-    @- x : i32;
-     ^
-)");
-}
-
-// Check that the forward scan in resynchronize() stop at nested sync points.
-// In this test the inner resynchronize() is looking for a terminating ';', and
-// the outer resynchronize() is looking for a terminating '}' for the function
-// scope.
-TEST_F(ParserImplErrorResyncTest, NestedSyncPoints) {
-  EXPECT(R"(
-fn f() {
-  x = 1;
-  discard
-}
-struct S { blah };
-)",
-         R"(test.wgsl:5:1 error: expected ';' for discard statement
-}
-^
-
-test.wgsl:6:17 error: expected ':' for struct member
-struct S { blah };
-                ^
-)");
-}
-
-TEST_F(ParserImplErrorResyncTest, BracketCounting) {
-  EXPECT(
-      R"(
-fn f(x(((())))) {
-  meow = {{{}}}
-}
-struct S { blah };
-)",
-      R"(test.wgsl:2:7 error: expected ':' for parameter
-fn f(x(((())))) {
-      ^
-
-test.wgsl:3:10 error: unable to parse right side of assignment
-  meow = {{{}}}
-         ^
-
-test.wgsl:5:17 error: expected ':' for struct member
-struct S { blah };
-                ^
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
deleted file mode 100644
index ad58ce6..0000000
--- a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ExclusiveOrExpression_Parses) {
-  auto p = parser("a ^ true");
-  auto e = p->exclusive_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kXor, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidLHS) {
-  auto p = parser("if (a) {} ^ true");
-  auto e = p->exclusive_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidRHS) {
-  auto p = parser("true ^ if (a) {}");
-  auto e = p->exclusive_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of ^ expression");
-}
-
-TEST_F(ParserImplTest, ExclusiveOrExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->exclusive_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_external_texture_type_test.cc b/src/reader/wgsl/parser_impl_external_texture_type_test.cc
deleted file mode 100644
index f09e083..0000000
--- a/src/reader/wgsl/parser_impl_external_texture_type_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ExternalTextureType_Invalid) {
-  auto p = parser("1234");
-  auto t = p->external_texture_type();
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ExternalTextureType) {
-  auto p = parser("texture_external");
-  auto t = p->external_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
deleted file mode 100644
index ba36f9d..0000000
--- a/src/reader/wgsl/parser_impl_for_stmt_test.cc
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-#include "src/ast/discard_statement.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-using ForStmtTest = ParserImplTest;
-
-// Test an empty for loop.
-TEST_F(ForStmtTest, Empty) {
-  auto p = parser("for (;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_EQ(fl->initializer, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop with non-empty body.
-TEST_F(ForStmtTest, Body) {
-  auto p = parser("for (;;) { discard; }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_EQ(fl->initializer, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  ASSERT_EQ(fl->body->statements.size(), 1u);
-  EXPECT_TRUE(fl->body->statements[0]->Is<ast::DiscardStatement>());
-}
-
-// Test a for loop declaring a variable in the initializer statement.
-TEST_F(ForStmtTest, InitializerStatementDecl) {
-  auto p = parser("for (var i: i32 ;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
-  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
-  EXPECT_FALSE(var->is_const);
-  EXPECT_EQ(var->constructor, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop declaring and initializing a variable in the initializer
-// statement.
-TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
-  auto p = parser("for (var i: i32 = 0 ;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
-  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
-  EXPECT_FALSE(var->is_const);
-  EXPECT_NE(var->constructor, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop declaring a const variable in the initializer statement.
-TEST_F(ForStmtTest, InitializerStatementConstDecl) {
-  auto p = parser("for (let i: i32 = 0 ;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
-  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
-  EXPECT_TRUE(var->is_const);
-  EXPECT_NE(var->constructor, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop assigning a variable in the initializer statement.
-TEST_F(ForStmtTest, InitializerStatementAssignment) {
-  auto p = parser("for (i = 0 ;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->initializer));
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop calling a function in the initializer statement.
-TEST_F(ForStmtTest, InitializerStatementFuncCall) {
-  auto p = parser("for (a(b,c) ;;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_TRUE(Is<ast::CallStatement>(fl->initializer));
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop with a break condition
-TEST_F(ForStmtTest, BreakCondition) {
-  auto p = parser("for (; 0 == 1;) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_EQ(fl->initializer, nullptr);
-  EXPECT_TRUE(Is<ast::BinaryExpression>(fl->condition));
-  EXPECT_EQ(fl->continuing, nullptr);
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop assigning a variable in the continuing statement.
-TEST_F(ForStmtTest, ContinuingAssignment) {
-  auto p = parser("for (;; x = 2) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_EQ(fl->initializer, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->continuing));
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-// Test a for loop calling a function in the continuing statement.
-TEST_F(ForStmtTest, ContinuingFuncCall) {
-  auto p = parser("for (;; a(b,c)) { }");
-  auto fl = p->for_stmt();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(fl.errored);
-  ASSERT_TRUE(fl.matched);
-  EXPECT_EQ(fl->initializer, nullptr);
-  EXPECT_EQ(fl->condition, nullptr);
-  EXPECT_TRUE(Is<ast::CallStatement>(fl->continuing));
-  EXPECT_TRUE(fl->body->Empty());
-}
-
-class ForStmtErrorTest : public ParserImplTest {
- public:
-  void TestForWithError(std::string for_str, std::string error_str) {
-    auto p_for = parser(for_str);
-    auto e_for = p_for->for_stmt();
-
-    EXPECT_FALSE(e_for.matched);
-    EXPECT_TRUE(e_for.errored);
-    EXPECT_TRUE(p_for->has_error());
-    ASSERT_EQ(e_for.value, nullptr);
-    EXPECT_EQ(p_for->error(), error_str);
-  }
-};
-
-// Test a for loop with missing left parenthesis is invalid.
-TEST_F(ForStmtErrorTest, MissingLeftParen) {
-  std::string for_str = "for { }";
-  std::string error_str = "1:5: expected '(' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with missing first semicolon is invalid.
-TEST_F(ForStmtErrorTest, MissingFirstSemicolon) {
-  std::string for_str = "for () {}";
-  std::string error_str = "1:6: expected ';' for initializer in for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with missing second semicolon is invalid.
-TEST_F(ForStmtErrorTest, MissingSecondSemicolon) {
-  std::string for_str = "for (;) {}";
-  std::string error_str = "1:7: expected ';' for condition in for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with missing right parenthesis is invalid.
-TEST_F(ForStmtErrorTest, MissingRightParen) {
-  std::string for_str = "for (;; {}";
-  std::string error_str = "1:9: expected ')' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with missing left brace is invalid.
-TEST_F(ForStmtErrorTest, MissingLeftBrace) {
-  std::string for_str = "for (;;)";
-  std::string error_str = "1:9: expected '{' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with missing right brace is invalid.
-TEST_F(ForStmtErrorTest, MissingRightBrace) {
-  std::string for_str = "for (;;) {";
-  std::string error_str = "1:11: expected '}' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with an invalid initializer statement.
-TEST_F(ForStmtErrorTest, InvalidInitializerAsConstDecl) {
-  std::string for_str = "for (let x: i32;;) { }";
-  std::string error_str = "1:16: expected '=' for let declaration";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with a initializer statement not matching
-// variable_stmt | assignment_stmt | func_call_stmt.
-TEST_F(ForStmtErrorTest, InvalidInitializerMatch) {
-  std::string for_str = "for (if (true) {} ;;) { }";
-  std::string error_str = "1:6: expected ';' for initializer in for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with an invalid break condition.
-TEST_F(ForStmtErrorTest, InvalidBreakConditionAsExpression) {
-  std::string for_str = "for (; (0 == 1; ) { }";
-  std::string error_str = "1:15: expected ')'";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with a break condition not matching
-// logical_or_expression.
-TEST_F(ForStmtErrorTest, InvalidBreakConditionMatch) {
-  std::string for_str = "for (; var i: i32 = 0;) { }";
-  std::string error_str = "1:8: expected ';' for condition in for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with an invalid continuing statement.
-TEST_F(ForStmtErrorTest, InvalidContinuingAsFuncCall) {
-  std::string for_str = "for (;; a(,) ) { }";
-  std::string error_str = "1:11: expected ')' for function call";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with a continuing statement not matching
-// assignment_stmt | func_call_stmt.
-TEST_F(ForStmtErrorTest, InvalidContinuingMatch) {
-  std::string for_str = "for (;; var i: i32 = 0) { }";
-  std::string error_str = "1:9: expected ')' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with an invalid body.
-TEST_F(ForStmtErrorTest, InvalidBody) {
-  std::string for_str = "for (;;) { let x: i32; }";
-  std::string error_str = "1:22: expected '=' for let declaration";
-
-  TestForWithError(for_str, error_str);
-}
-
-// Test a for loop with a body not matching statements
-TEST_F(ForStmtErrorTest, InvalidBodyMatch) {
-  std::string for_str = "for (;;) { fn main() {} }";
-  std::string error_str = "1:12: expected '}' for for loop";
-
-  TestForWithError(for_str, error_str);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_function_attribute_list_test.cc b/src/reader/wgsl/parser_impl_function_attribute_list_test.cc
deleted file mode 100644
index 36ce736..0000000
--- a/src/reader/wgsl/parser_impl_function_attribute_list_test.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2020 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/ast/workgroup_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AttributeList_Parses) {
-  auto p = parser("@workgroup_size(2) @stage(compute)");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 2u);
-
-  auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
-  auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
-  ASSERT_NE(attr_0, nullptr);
-  ASSERT_NE(attr_1, nullptr);
-
-  ASSERT_TRUE(attr_0->Is<ast::WorkgroupAttribute>());
-  const ast::Expression* x = attr_0->As<ast::WorkgroupAttribute>()->x;
-  ASSERT_NE(x, nullptr);
-  auto* x_literal = x->As<ast::LiteralExpression>();
-  ASSERT_NE(x_literal, nullptr);
-  ASSERT_TRUE(x_literal->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(x_literal->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(attr_1->Is<ast::StageAttribute>());
-  EXPECT_EQ(attr_1->As<ast::StageAttribute>()->stage,
-            ast::PipelineStage::kCompute);
-}
-
-TEST_F(ParserImplTest, AttributeList_Invalid) {
-  auto p = parser("@invalid");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(), "1:2: expected attribute");
-}
-
-TEST_F(ParserImplTest, AttributeList_ExtraComma) {
-  auto p = parser("[[workgroup_size(2), ]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:22: expected attribute)");
-}
-
-TEST_F(ParserImplTest, AttributeList_BadAttribute) {
-  auto p = parser("@stage()");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(p->error(), "1:8: invalid value for stage attribute");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_Empty) {
-  auto p = parser("[[]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: empty attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_Invalid) {
-  auto p = parser("[[invalid]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: expected attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_ExtraComma) {
-  auto p = parser("[[workgroup_size(2), ]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:22: expected attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingComma) {
-  auto p = parser("[[workgroup_size(2) workgroup_size(2)]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:21: expected ',' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_BadAttribute) {
-  auto p = parser("[[stage()]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:9: invalid value for stage attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingRightAttr) {
-  auto p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:45: expected ']]' for attribute list)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_function_attribute_test.cc b/src/reader/wgsl/parser_impl_function_attribute_test.cc
deleted file mode 100644
index 75d1173..0000000
--- a/src/reader/wgsl/parser_impl_function_attribute_test.cc
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Attribute_Workgroup) {
-  auto p = parser("workgroup_size(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  auto* func_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(func_attr, nullptr);
-  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
-
-  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  EXPECT_EQ(values[1], nullptr);
-  EXPECT_EQ(values[2], nullptr);
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_2Param) {
-  auto p = parser("workgroup_size(4, 5)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  auto* func_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(func_attr, nullptr) << p->error();
-  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
-
-  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
-
-  EXPECT_EQ(values[2], nullptr);
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_3Param) {
-  auto p = parser("workgroup_size(4, 5, 6)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  auto* func_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(func_attr, nullptr);
-  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
-
-  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
-
-  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_WithIdent) {
-  auto p = parser("workgroup_size(4, height)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  auto* func_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(func_attr, nullptr);
-  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
-
-  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_NE(values[1], nullptr);
-  auto* y_ident = values[1]->As<ast::IdentifierExpression>();
-  ASSERT_NE(y_ident, nullptr);
-  EXPECT_EQ(p->builder().Symbols().NameFor(y_ident->symbol), "height");
-
-  ASSERT_EQ(values[2], nullptr);
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_TooManyValues) {
-  auto p = parser("workgroup_size(1, 2, 3, 4)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_MissingLeftParam) {
-  auto p = parser("workgroup_size 4, 5, 6)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: expected '(' for workgroup_size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_MissingRightParam) {
-  auto p = parser("workgroup_size(4, 5, 6");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_MissingValues) {
-  auto p = parser("workgroup_size()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: expected workgroup_size x parameter");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_Missing_X_Value) {
-  auto p = parser("workgroup_size(, 2, 3)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: expected workgroup_size x parameter");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Y_Comma) {
-  auto p = parser("workgroup_size(1 2, 3)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:18: expected ')' for workgroup_size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Y_Value) {
-  auto p = parser("workgroup_size(1, , 3)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:19: expected workgroup_size y parameter");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Z_Comma) {
-  auto p = parser("workgroup_size(1, 2 3)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:21: expected ')' for workgroup_size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Z_Value) {
-  auto p = parser("workgroup_size(1, 2, )");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:22: expected workgroup_size z parameter");
-}
-
-TEST_F(ParserImplTest, Attribute_Stage) {
-  auto p = parser("stage(compute)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  auto* func_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(func_attr, nullptr);
-  ASSERT_TRUE(func_attr->Is<ast::StageAttribute>());
-  EXPECT_EQ(func_attr->As<ast::StageAttribute>()->stage,
-            ast::PipelineStage::kCompute);
-}
-
-TEST_F(ParserImplTest, Attribute_Stage_MissingValue) {
-  auto p = parser("stage()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: invalid value for stage attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Stage_MissingInvalid) {
-  auto p = parser("stage(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: invalid value for stage attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Stage_MissingLeftParen) {
-  auto p = parser("stage compute)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected '(' for stage attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Stage_MissingRightParen) {
-  auto p = parser("stage(compute");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: expected ')' for stage attribute");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
deleted file mode 100644
index 56f024e..0000000
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2020 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/ast/workgroup_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-#include "src/utils/string.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, FunctionDecl) {
-  auto p = parser("fn main(a : i32, b : f32) { return; }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-
-  ASSERT_EQ(f->params.size(), 2u);
-  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
-
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_Unicode) {
-  const std::string function_ident =  // "𝗳𝘂𝗻𝗰𝘁𝗶𝗼𝗻"
-      "\xf0\x9d\x97\xb3\xf0\x9d\x98\x82\xf0\x9d\x97\xbb\xf0\x9d\x97\xb0\xf0\x9d"
-      "\x98\x81\xf0\x9d\x97\xb6\xf0\x9d\x97\xbc\xf0\x9d\x97\xbb";
-
-  const std::string param_a_ident =  // "𝓹𝓪𝓻𝓪𝓶_𝓪"
-      "\xf0\x9d\x93\xb9\xf0\x9d\x93\xaa\xf0\x9d\x93\xbb\xf0\x9d\x93\xaa\xf0\x9d"
-      "\x93\xb6\x5f\xf0\x9d\x93\xaa";
-
-  const std::string param_b_ident =  // "𝕡𝕒𝕣𝕒𝕞_𝕓"
-      "\xf0\x9d\x95\xa1\xf0\x9d\x95\x92\xf0\x9d\x95\xa3\xf0\x9d\x95\x92\xf0\x9d"
-      "\x95\x9e\x5f\xf0\x9d\x95\x93";
-
-  std::string src = "fn $function($param_a : i32, $param_b : f32) { return; }";
-  src = utils::ReplaceAll(src, "$function", function_ident);
-  src = utils::ReplaceAll(src, "$param_a", param_a_ident);
-  src = utils::ReplaceAll(src, "$param_b", param_b_ident);
-
-  auto p = parser(src);
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get(function_ident));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-
-  ASSERT_EQ(f->params.size(), 2u);
-  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get(param_a_ident));
-  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get(param_b_ident));
-
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_AttributeList) {
-  auto p = parser("@workgroup_size(2, 3, 4) fn main() { return; }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  ASSERT_TRUE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-  ASSERT_EQ(f->params.size(), 0u);
-
-  auto& attributes = f->attributes;
-  ASSERT_EQ(attributes.size(), 1u);
-  ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
-
-  auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
-
-  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_AttributeList_MultipleEntries) {
-  auto p = parser(R"(
-@workgroup_size(2, 3, 4) @stage(compute)
-fn main() { return; })");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  ASSERT_TRUE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-  ASSERT_EQ(f->params.size(), 0u);
-
-  auto& attributes = f->attributes;
-  ASSERT_EQ(attributes.size(), 2u);
-
-  ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
-  auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
-
-  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_TRUE(attributes[1]->Is<ast::StageAttribute>());
-  EXPECT_EQ(attributes[1]->As<ast::StageAttribute>()->stage,
-            ast::PipelineStage::kCompute);
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_AttributeList_MultipleLists) {
-  auto p = parser(R"(
-@workgroup_size(2, 3, 4)
-@stage(compute)
-fn main() { return; })");
-  auto attributes = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attributes.errored);
-  ASSERT_TRUE(attributes.matched);
-  auto f = p->function_decl(attributes.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-  ASSERT_EQ(f->params.size(), 0u);
-
-  auto& attrs = f->attributes;
-  ASSERT_EQ(attrs.size(), 2u);
-
-  ASSERT_TRUE(attrs[0]->Is<ast::WorkgroupAttribute>());
-  auto values = attrs[0]->As<ast::WorkgroupAttribute>()->Values();
-
-  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
-  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
-
-  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
-
-  ASSERT_TRUE(attrs[1]->Is<ast::StageAttribute>());
-  EXPECT_EQ(attrs[1]->As<ast::StageAttribute>()->stage,
-            ast::PipelineStage::kCompute);
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_ReturnTypeAttributeList) {
-  auto p = parser("fn main() -> @location(1) f32 { return 1.0; }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_FALSE(f.errored);
-  EXPECT_TRUE(f.matched);
-  ASSERT_NE(f.value, nullptr);
-
-  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
-  ASSERT_NE(f->return_type, nullptr);
-  EXPECT_TRUE(f->return_type->Is<ast::F32>());
-  ASSERT_EQ(f->params.size(), 0u);
-
-  auto& attributes = f->attributes;
-  EXPECT_EQ(attributes.size(), 0u);
-
-  auto& ret_type_attributes = f->return_type_attributes;
-  ASSERT_EQ(ret_type_attributes.size(), 1u);
-  auto* loc = ret_type_attributes[0]->As<ast::LocationAttribute>();
-  ASSERT_TRUE(loc != nullptr);
-  EXPECT_EQ(loc->value, 1u);
-
-  auto* body = f->body;
-  ASSERT_EQ(body->statements.size(), 1u);
-  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, FunctionDecl_InvalidHeader) {
-  auto p = parser("fn main() -> { }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_TRUE(f.errored);
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(f.value, nullptr);
-  EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
-}
-
-TEST_F(ParserImplTest, FunctionDecl_InvalidBody) {
-  auto p = parser("fn main() { return }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_TRUE(f.errored);
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(f.value, nullptr);
-  EXPECT_EQ(p->error(), "1:20: expected ';' for return statement");
-}
-
-TEST_F(ParserImplTest, FunctionDecl_MissingLeftBrace) {
-  auto p = parser("fn main() return; }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto f = p->function_decl(attrs.value);
-  EXPECT_TRUE(f.errored);
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(f.value, nullptr);
-  EXPECT_EQ(p->error(), "1:11: expected '{'");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_function_header_test.cc b/src/reader/wgsl/parser_impl_function_header_test.cc
deleted file mode 100644
index 7e21cfc..0000000
--- a/src/reader/wgsl/parser_impl_function_header_test.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, FunctionHeader) {
-  auto p = parser("fn main(a : i32, b: f32)");
-  auto f = p->function_header();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(f.matched);
-  EXPECT_FALSE(f.errored);
-
-  EXPECT_EQ(f->name, "main");
-  ASSERT_EQ(f->params.size(), 2u);
-  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-}
-
-TEST_F(ParserImplTest, FunctionHeader_TrailingComma) {
-  auto p = parser("fn main(a :i32,)");
-  auto f = p->function_header();
-  EXPECT_TRUE(f.matched);
-  EXPECT_FALSE(f.errored);
-
-  EXPECT_EQ(f->name, "main");
-  ASSERT_EQ(f->params.size(), 1u);
-  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(f->return_type->Is<ast::Void>());
-}
-
-TEST_F(ParserImplTest, FunctionHeader_AttributeReturnType) {
-  auto p = parser("fn main() -> @location(1) f32");
-  auto f = p->function_header();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(f.matched);
-  EXPECT_FALSE(f.errored);
-
-  EXPECT_EQ(f->name, "main");
-  EXPECT_EQ(f->params.size(), 0u);
-  EXPECT_TRUE(f->return_type->Is<ast::F32>());
-  ASSERT_EQ(f->return_type_attributes.size(), 1u);
-  auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
-  ASSERT_TRUE(loc != nullptr);
-  EXPECT_EQ(loc->value, 1u);
-}
-
-TEST_F(ParserImplTest, FunctionHeader_InvariantReturnType) {
-  auto p = parser("fn main() -> [[invariant]] f32");
-  auto f = p->function_header();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(f.matched);
-  EXPECT_FALSE(f.errored);
-
-  EXPECT_EQ(f->name, "main");
-  EXPECT_EQ(f->params.size(), 0u);
-  EXPECT_TRUE(f->return_type->Is<ast::F32>());
-  ASSERT_EQ(f->return_type_attributes.size(), 1u);
-  EXPECT_TRUE(f->return_type_attributes[0]->Is<ast::InvariantAttribute>());
-}
-
-TEST_F(ParserImplTest, FunctionHeader_AttributeReturnType_WithArrayStride) {
-  auto p = parser("fn main() -> @location(1) @stride(16) array<f32, 4>");
-  auto f = p->function_header();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(f.matched);
-  EXPECT_FALSE(f.errored);
-
-  EXPECT_EQ(f->name, "main");
-  EXPECT_EQ(f->params.size(), 0u);
-  ASSERT_EQ(f->return_type_attributes.size(), 1u);
-  auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
-  ASSERT_TRUE(loc != nullptr);
-  EXPECT_EQ(loc->value, 1u);
-
-  auto* array_type = f->return_type->As<ast::Array>();
-  ASSERT_EQ(array_type->attributes.size(), 1u);
-  auto* stride = array_type->attributes[0]->As<ast::StrideAttribute>();
-  ASSERT_TRUE(stride != nullptr);
-  EXPECT_EQ(stride->stride, 16u);
-}
-
-TEST_F(ParserImplTest, FunctionHeader_MissingIdent) {
-  auto p = parser("fn ()");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
-}
-
-TEST_F(ParserImplTest, FunctionHeader_InvalidIdent) {
-  auto p = parser("fn 133main() -> i32");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
-}
-
-TEST_F(ParserImplTest, FunctionHeader_MissingParenLeft) {
-  auto p = parser("fn main) -> i32");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected '(' for function declaration");
-}
-
-TEST_F(ParserImplTest, FunctionHeader_InvalidParamList) {
-  auto p = parser("fn main(a :i32, ,) -> i32");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:17: expected ')' for function declaration");
-}
-
-TEST_F(ParserImplTest, FunctionHeader_MissingParenRight) {
-  auto p = parser("fn main( -> i32");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: expected ')' for function declaration");
-}
-
-TEST_F(ParserImplTest, FunctionHeader_MissingReturnType) {
-  auto p = parser("fn main() ->");
-  auto f = p->function_header();
-  EXPECT_FALSE(f.matched);
-  EXPECT_TRUE(f.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: unable to determine function return type");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
deleted file mode 100644
index 20bc9e9..0000000
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2020 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/ast/id_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, GlobalConstantDecl) {
-  auto p = parser("let a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(e->is_const);
-  EXPECT_FALSE(e->is_overridable);
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->type, nullptr);
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 5u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 6u);
-
-  ASSERT_NE(e->constructor, nullptr);
-  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
-}
-
-TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) {
-  auto p = parser("let a = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(e->is_const);
-  EXPECT_FALSE(e->is_overridable);
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_EQ(e->type, nullptr);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 5u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 6u);
-
-  ASSERT_NE(e->constructor, nullptr);
-  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
-}
-
-TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
-  auto p = parser("let a : f32 = if (a) {}");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:15: invalid type for const_expr");
-}
-
-TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) {
-  auto p = parser("let a : f32 =");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:14: unable to parse const_expr");
-}
-
-TEST_F(ParserImplTest, GlobalConstantDec_Override_WithId) {
-  auto p = parser("@id(7) override a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(e->is_const);
-  EXPECT_TRUE(e->is_overridable);
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->type, nullptr);
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 17u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 18u);
-
-  ASSERT_NE(e->constructor, nullptr);
-  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
-
-  auto* override_attr =
-      ast::GetAttribute<ast::IdAttribute>(e.value->attributes);
-  ASSERT_NE(override_attr, nullptr);
-  EXPECT_EQ(override_attr->value, 7u);
-}
-
-TEST_F(ParserImplTest, GlobalConstantDec_Override_WithoutId) {
-  auto p = parser("override a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(e->is_const);
-  EXPECT_TRUE(e->is_overridable);
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->type, nullptr);
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 10u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 11u);
-
-  ASSERT_NE(e->constructor, nullptr);
-  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
-
-  auto* id_attr = ast::GetAttribute<ast::IdAttribute>(e.value->attributes);
-  ASSERT_EQ(id_attr, nullptr);
-}
-
-TEST_F(ParserImplTest, GlobalConstantDec_Override_MissingId) {
-  auto p = parser("@id() override a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:5: expected signed integer literal for id attribute");
-}
-
-TEST_F(ParserImplTest, GlobalConstantDec_Override_InvalidId) {
-  auto p = parser("@id(-7) override a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto e = p->global_constant_decl(attrs.value);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:5: id attribute must be positive");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
deleted file mode 100644
index cd18874..0000000
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, GlobalDecl_Semicolon) {
-  auto p = parser(";");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalVariable) {
-  auto p = parser("var<private> a : vec2<i32> = vec2<i32>(1, 2);");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
-
-  auto* v = program.AST().GlobalVariables()[0];
-  EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Inferred_Invalid) {
-  auto p = parser("var<private> a = vec2<i32>(1, 2);");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: expected ':' for variable declaration");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_MissingSemicolon) {
-  auto p = parser("var<private> a : vec2<i32>");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:27: expected ';' for variable declaration");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) {
-  auto p = parser("let a : i32 = 2;");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
-
-  auto* v = program.AST().GlobalVariables()[0];
-  EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) {
-  auto p = parser("let a : vec2<i32> 1.0;");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:19: expected ';' for let declaration");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingSemicolon) {
-  auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:36: expected ';' for let declaration");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_TypeAlias) {
-  auto p = parser("type A = i32;");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
-  ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Alias>());
-  EXPECT_EQ(program.Symbols().NameFor(
-                program.AST().TypeDecls()[0]->As<ast::Alias>()->name),
-            "A");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_TypeAlias_StructIdent) {
-  auto p = parser(R"(struct A {
-  a : f32;
-}
-type B = A;)");
-  p->expect_global_decl();
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().TypeDecls().size(), 2u);
-  ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Struct>());
-  auto* str = program.AST().TypeDecls()[0]->As<ast::Struct>();
-  EXPECT_EQ(str->name, program.Symbols().Get("A"));
-
-  ASSERT_TRUE(program.AST().TypeDecls()[1]->Is<ast::Alias>());
-  auto* alias = program.AST().TypeDecls()[1]->As<ast::Alias>();
-  EXPECT_EQ(alias->name, program.Symbols().Get("B"));
-  auto* tn = alias->type->As<ast::TypeName>();
-  EXPECT_NE(tn, nullptr);
-  EXPECT_EQ(tn->name, str->name);
-}
-
-TEST_F(ParserImplTest, GlobalDecl_TypeAlias_MissingSemicolon) {
-  auto p = parser("type A = i32");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: expected ';' for type alias");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_Function) {
-  auto p = parser("fn main() { return; }");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().Functions().size(), 1u);
-  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol),
-            "main");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_Function_WithAttribute) {
-  auto p = parser("@workgroup_size(2) fn main() { return; }");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().Functions().size(), 1u);
-  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol),
-            "main");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_Function_Invalid) {
-  auto p = parser("fn main() -> { return; }");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
-}
-
-TEST_F(ParserImplTest, GlobalDecl_ParsesStruct) {
-  auto p = parser("struct A { b: i32; c: f32;}");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
-
-  auto* t = program.AST().TypeDecls()[0];
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<ast::Struct>());
-
-  auto* str = t->As<ast::Struct>();
-  EXPECT_EQ(str->name, program.Symbols().Get("A"));
-  EXPECT_EQ(str->members.size(), 2u);
-}
-
-TEST_F(ParserImplTest, GlobalDecl_Struct_WithStride) {
-  auto p = parser("struct A { data: @stride(4) array<f32>; }");
-
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
-
-  auto* t = program.AST().TypeDecls()[0];
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<ast::Struct>());
-
-  auto* str = t->As<ast::Struct>();
-  EXPECT_EQ(str->name, program.Symbols().Get("A"));
-  EXPECT_EQ(str->members.size(), 1u);
-
-  const auto* ty = str->members[0]->type;
-  ASSERT_TRUE(ty->Is<ast::Array>());
-  const auto* arr = ty->As<ast::Array>();
-
-  ASSERT_EQ(arr->attributes.size(), 1u);
-  auto* stride = arr->attributes[0];
-  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
-  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 4u);
-}
-
-// TODO(crbug.com/tint/1324): DEPRECATED: Remove when @block is removed.
-TEST_F(ParserImplTest, GlobalDecl_Struct_WithAttribute) {
-  auto p = parser("[[block]] struct A { data: f32; }");
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto program = p->program();
-  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
-
-  auto* t = program.AST().TypeDecls()[0];
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<ast::Struct>());
-
-  auto* str = t->As<ast::Struct>();
-  EXPECT_EQ(str->name, program.Symbols().Get("A"));
-  EXPECT_EQ(str->members.size(), 1u);
-}
-
-TEST_F(ParserImplTest, GlobalDecl_Struct_Invalid) {
-  auto p = parser("A {}");
-  p->expect_global_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:1: unexpected token");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
deleted file mode 100644
index 25f55d7..0000000
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, GlobalVariableDecl_WithoutConstructor) {
-  auto p = parser("var<private> a : f32");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_variable_decl(attrs.value);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kPrivate);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 14u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 15u);
-
-  ASSERT_EQ(e->constructor, nullptr);
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_WithConstructor) {
-  auto p = parser("var<private> a : f32 = 1.");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_variable_decl(attrs.value);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kPrivate);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 14u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 15u);
-
-  ASSERT_NE(e->constructor, nullptr);
-  ASSERT_TRUE(e->constructor->Is<ast::FloatLiteralExpression>());
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_WithAttribute) {
-  auto p = parser("@binding(2) @group(1) var<uniform> a : f32");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  auto e = p->global_variable_decl(attrs.value);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->type, nullptr);
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kUniform);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 36u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 37u);
-
-  ASSERT_EQ(e->constructor, nullptr);
-
-  auto& attributes = e->attributes;
-  ASSERT_EQ(attributes.size(), 2u);
-  ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
-  ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_WithAttribute_MulitpleGroups) {
-  auto p = parser("@binding(2) @group(1) var<uniform> a : f32");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-
-  auto e = p->global_variable_decl(attrs.value);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->type, nullptr);
-  EXPECT_TRUE(e->type->Is<ast::F32>());
-  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kUniform);
-
-  EXPECT_EQ(e->source.range.begin.line, 1u);
-  EXPECT_EQ(e->source.range.begin.column, 36u);
-  EXPECT_EQ(e->source.range.end.line, 1u);
-  EXPECT_EQ(e->source.range.end.column, 37u);
-
-  ASSERT_EQ(e->constructor, nullptr);
-
-  auto& attributes = e->attributes;
-  ASSERT_EQ(attributes.size(), 2u);
-  ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
-  ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_InvalidAttribute) {
-  auto p = parser("@binding() var<uniform> a : f32");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto e = p->global_variable_decl(attrs.value);
-  EXPECT_FALSE(e.errored);
-  EXPECT_TRUE(e.matched);
-  EXPECT_NE(e.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:10: expected signed integer literal for binding attribute");
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_InvalidConstExpr) {
-  auto p = parser("var<private> a : f32 = if (a) {}");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_variable_decl(attrs.value);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:24: invalid type for const_expr");
-}
-
-TEST_F(ParserImplTest, GlobalVariableDecl_InvalidVariableDecl) {
-  auto p = parser("var<invalid> a : f32;");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  auto e = p->global_variable_decl(attrs.value);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:5: invalid storage class for variable declaration");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_if_stmt_test.cc b/src/reader/wgsl/parser_impl_if_stmt_test.cc
deleted file mode 100644
index d7d86b2..0000000
--- a/src/reader/wgsl/parser_impl_if_stmt_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, IfStmt) {
-  auto p = parser("if (a == 4) { a = b; c = d; }");
-  auto e = p->if_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::IfStatement>());
-  ASSERT_NE(e->condition, nullptr);
-  ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e->body->statements.size(), 2u);
-  EXPECT_EQ(e->else_statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, IfStmt_WithElse) {
-  auto p =
-      parser("if (a == 4) { a = b; c = d; } else if(c) { d = 2; } else {}");
-  auto e = p->if_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::IfStatement>());
-  ASSERT_NE(e->condition, nullptr);
-  ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
-  EXPECT_EQ(e->body->statements.size(), 2u);
-
-  ASSERT_EQ(e->else_statements.size(), 2u);
-  ASSERT_NE(e->else_statements[0]->condition, nullptr);
-  ASSERT_TRUE(
-      e->else_statements[0]->condition->Is<ast::IdentifierExpression>());
-  EXPECT_EQ(e->else_statements[0]->body->statements.size(), 1u);
-
-  ASSERT_EQ(e->else_statements[1]->condition, nullptr);
-  EXPECT_EQ(e->else_statements[1]->body->statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, IfStmt_InvalidCondition) {
-  auto p = parser("if (a = 3) {}");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected ')'");
-}
-
-TEST_F(ParserImplTest, IfStmt_MissingCondition) {
-  auto p = parser("if {}");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:4: expected '('");
-}
-
-TEST_F(ParserImplTest, IfStmt_InvalidBody) {
-  auto p = parser("if (a) { fn main() {}}");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: expected '}'");
-}
-
-TEST_F(ParserImplTest, IfStmt_MissingBody) {
-  auto p = parser("if (a)");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected '{'");
-}
-
-TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
-  auto p = parser("if (a) {} else if (a) { fn main() -> a{}}");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:25: expected '}'");
-}
-
-TEST_F(ParserImplTest, IfStmt_InvalidElse) {
-  auto p = parser("if (a) {} else { fn main() -> a{}}");
-  auto e = p->if_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:18: expected '}'");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
deleted file mode 100644
index bc633ad..0000000
--- a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, InclusiveOrExpression_Parses) {
-  auto p = parser("a | true");
-  auto e = p->inclusive_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kOr, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, InclusiveOrExpression_InvalidLHS) {
-  auto p = parser("if (a) {} | true");
-  auto e = p->inclusive_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, InclusiveOrExpression_InvalidRHS) {
-  auto p = parser("true | if (a) {}");
-  auto e = p->inclusive_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of | expression");
-}
-
-TEST_F(ParserImplTest, InclusiveOrExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->inclusive_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
deleted file mode 100644
index 1322308..0000000
--- a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, LogicalAndExpression_Parses) {
-  auto p = parser("a && true");
-  auto e = p->logical_and_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kLogicalAnd, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, LogicalAndExpression_InvalidLHS) {
-  auto p = parser("if (a) {} && true");
-  auto e = p->logical_and_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, LogicalAndExpression_InvalidRHS) {
-  auto p = parser("true && if (a) {}");
-  auto e = p->logical_and_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: unable to parse right side of && expression");
-}
-
-TEST_F(ParserImplTest, LogicalAndExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->logical_and_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
deleted file mode 100644
index fe60f8b..0000000
--- a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, LogicalOrExpression_Parses) {
-  auto p = parser("a || true");
-  auto e = p->logical_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kLogicalOr, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, LogicalOrExpression_InvalidLHS) {
-  auto p = parser("if (a) {} || true");
-  auto e = p->logical_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, LogicalOrExpression_InvalidRHS) {
-  auto p = parser("true || if (a) {}");
-  auto e = p->logical_or_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: unable to parse right side of || expression");
-}
-
-TEST_F(ParserImplTest, LogicalOrExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->logical_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/reader/wgsl/parser_impl_loop_stmt_test.cc
deleted file mode 100644
index 4da4962..0000000
--- a/src/reader/wgsl/parser_impl_loop_stmt_test.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, LoopStmt_BodyNoContinuing) {
-  auto p = parser("loop { discard; }");
-  auto e = p->loop_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_EQ(e->body->statements.size(), 1u);
-  EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
-
-  EXPECT_EQ(e->continuing->statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, LoopStmt_BodyWithContinuing) {
-  auto p = parser("loop { discard; continuing { discard; }}");
-  auto e = p->loop_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_EQ(e->body->statements.size(), 1u);
-  EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
-
-  EXPECT_EQ(e->continuing->statements.size(), 1u);
-  EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
-}
-
-TEST_F(ParserImplTest, LoopStmt_NoBodyNoContinuing) {
-  auto p = parser("loop { }");
-  auto e = p->loop_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_EQ(e->body->statements.size(), 0u);
-  ASSERT_EQ(e->continuing->statements.size(), 0u);
-}
-
-TEST_F(ParserImplTest, LoopStmt_NoBodyWithContinuing) {
-  auto p = parser("loop { continuing { discard; }}");
-  auto e = p->loop_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_EQ(e->body->statements.size(), 0u);
-  ASSERT_EQ(e->continuing->statements.size(), 1u);
-  EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
-}
-
-TEST_F(ParserImplTest, LoopStmt_MissingBracketLeft) {
-  auto p = parser("loop discard; }");
-  auto e = p->loop_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
-}
-
-TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) {
-  auto p = parser("loop { discard; ");
-  auto e = p->loop_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:17: expected '}' for loop");
-}
-
-TEST_F(ParserImplTest, LoopStmt_InvalidStatements) {
-  auto p = parser("loop { discard }");
-  auto e = p->loop_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: expected ';' for discard statement");
-}
-
-TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) {
-  auto p = parser("loop { continuing { discard }}");
-  auto e = p->loop_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:29: expected ';' for discard statement");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
deleted file mode 100644
index 08a1ccf..0000000
--- a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply) {
-  auto p = parser("a * true");
-  auto e = p->multiplicative_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Divide) {
-  auto p = parser("a / true");
-  auto e = p->multiplicative_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kDivide, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Modulo) {
-  auto p = parser("a % true");
-  auto e = p->multiplicative_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kModulo, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, MultiplicativeExpression_InvalidLHS) {
-  auto p = parser("if (a) {} * true");
-  auto e = p->multiplicative_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, MultiplicativeExpression_InvalidRHS) {
-  auto p = parser("true * if (a) {}");
-  auto e = p->multiplicative_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of * expression");
-}
-
-TEST_F(ParserImplTest, MultiplicativeExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->multiplicative_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_param_list_test.cc b/src/reader/wgsl/parser_impl_param_list_test.cc
deleted file mode 100644
index 95f7f25..0000000
--- a/src/reader/wgsl/parser_impl_param_list_test.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ParamList_Single) {
-  auto p = parser("a : i32");
-
-  auto e = p->expect_param_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  EXPECT_EQ(e.value.size(), 1u);
-
-  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
-  EXPECT_TRUE(e.value[0]->is_const);
-
-  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.begin.column, 1u);
-  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.end.column, 2u);
-}
-
-TEST_F(ParserImplTest, ParamList_Multiple) {
-  auto p = parser("a : i32, b: f32, c: vec2<f32>");
-
-  auto e = p->expect_param_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  EXPECT_EQ(e.value.size(), 3u);
-
-  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
-  EXPECT_TRUE(e.value[0]->is_const);
-
-  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.begin.column, 1u);
-  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.end.column, 2u);
-
-  EXPECT_EQ(e.value[1]->symbol, p->builder().Symbols().Get("b"));
-  EXPECT_TRUE(e.value[1]->type->Is<ast::F32>());
-  EXPECT_TRUE(e.value[1]->is_const);
-
-  ASSERT_EQ(e.value[1]->source.range.begin.line, 1u);
-  ASSERT_EQ(e.value[1]->source.range.begin.column, 10u);
-  ASSERT_EQ(e.value[1]->source.range.end.line, 1u);
-  ASSERT_EQ(e.value[1]->source.range.end.column, 11u);
-
-  EXPECT_EQ(e.value[2]->symbol, p->builder().Symbols().Get("c"));
-  ASSERT_TRUE(e.value[2]->type->Is<ast::Vector>());
-  ASSERT_TRUE(e.value[2]->type->As<ast::Vector>()->type->Is<ast::F32>());
-  EXPECT_EQ(e.value[2]->type->As<ast::Vector>()->width, 2u);
-  EXPECT_TRUE(e.value[2]->is_const);
-
-  ASSERT_EQ(e.value[2]->source.range.begin.line, 1u);
-  ASSERT_EQ(e.value[2]->source.range.begin.column, 18u);
-  ASSERT_EQ(e.value[2]->source.range.end.line, 1u);
-  ASSERT_EQ(e.value[2]->source.range.end.column, 19u);
-}
-
-TEST_F(ParserImplTest, ParamList_Empty) {
-  auto p = parser("");
-  auto e = p->expect_param_list();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(e.errored);
-  EXPECT_EQ(e.value.size(), 0u);
-}
-
-TEST_F(ParserImplTest, ParamList_TrailingComma) {
-  auto p = parser("a : i32,");
-  auto e = p->expect_param_list();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(e.errored);
-  EXPECT_EQ(e.value.size(), 1u);
-}
-
-TEST_F(ParserImplTest, ParamList_Attributes) {
-  auto p =
-      parser("@builtin(position) coord : vec4<f32>, @location(1) loc1 : f32");
-
-  auto e = p->expect_param_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_EQ(e.value.size(), 2u);
-
-  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("coord"));
-  ASSERT_TRUE(e.value[0]->type->Is<ast::Vector>());
-  EXPECT_TRUE(e.value[0]->type->As<ast::Vector>()->type->Is<ast::F32>());
-  EXPECT_EQ(e.value[0]->type->As<ast::Vector>()->width, 4u);
-  EXPECT_TRUE(e.value[0]->is_const);
-  auto attrs_0 = e.value[0]->attributes;
-  ASSERT_EQ(attrs_0.size(), 1u);
-  EXPECT_TRUE(attrs_0[0]->Is<ast::BuiltinAttribute>());
-  EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin,
-            ast::Builtin::kPosition);
-
-  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.begin.column, 20u);
-  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
-  ASSERT_EQ(e.value[0]->source.range.end.column, 25u);
-
-  EXPECT_EQ(e.value[1]->symbol, p->builder().Symbols().Get("loc1"));
-  EXPECT_TRUE(e.value[1]->type->Is<ast::F32>());
-  EXPECT_TRUE(e.value[1]->is_const);
-  auto attrs_1 = e.value[1]->attributes;
-  ASSERT_EQ(attrs_1.size(), 1u);
-  EXPECT_TRUE(attrs_1[0]->Is<ast::LocationAttribute>());
-  EXPECT_EQ(attrs_1[0]->As<ast::LocationAttribute>()->value, 1u);
-
-  EXPECT_EQ(e.value[1]->source.range.begin.line, 1u);
-  EXPECT_EQ(e.value[1]->source.range.begin.column, 52u);
-  EXPECT_EQ(e.value[1]->source.range.end.line, 1u);
-  EXPECT_EQ(e.value[1]->source.range.end.column, 56u);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc b/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
deleted file mode 100644
index 0f59cad..0000000
--- a/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ParenRhsStmt) {
-  auto p = parser("(a + b)");
-  auto e = p->expect_paren_rhs_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, ParenRhsStmt_MissingLeftParen) {
-  auto p = parser("true)");
-  auto e = p->expect_paren_rhs_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:1: expected '('");
-}
-
-TEST_F(ParserImplTest, ParenRhsStmt_MissingRightParen) {
-  auto p = parser("(true");
-  auto e = p->expect_paren_rhs_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected ')'");
-}
-
-TEST_F(ParserImplTest, ParenRhsStmt_InvalidExpression) {
-  auto p = parser("(if (a() {})");
-  auto e = p->expect_paren_rhs_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
-}
-
-TEST_F(ParserImplTest, ParenRhsStmt_MissingExpression) {
-  auto p = parser("()");
-  auto e = p->expect_paren_rhs_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(e.errored);
-  ASSERT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_pipeline_stage_test.cc b/src/reader/wgsl/parser_impl_pipeline_stage_test.cc
deleted file mode 100644
index 39cf7f7..0000000
--- a/src/reader/wgsl/parser_impl_pipeline_stage_test.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-struct PipelineStageData {
-  std::string input;
-  ast::PipelineStage result;
-};
-inline std::ostream& operator<<(std::ostream& out, PipelineStageData data) {
-  return out << data.input;
-}
-
-class PipelineStageTest : public ParserImplTestWithParam<PipelineStageData> {};
-
-TEST_P(PipelineStageTest, Parses) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-
-  auto stage = p->expect_pipeline_stage();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(stage.errored);
-  EXPECT_EQ(stage.value, params.result);
-  EXPECT_EQ(stage.source.range.begin.line, 1u);
-  EXPECT_EQ(stage.source.range.begin.column, 1u);
-  EXPECT_EQ(stage.source.range.end.line, 1u);
-  EXPECT_EQ(stage.source.range.end.column, 1u + params.input.size());
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsEof());
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    PipelineStageTest,
-    testing::Values(
-        PipelineStageData{"vertex", ast::PipelineStage::kVertex},
-        PipelineStageData{"fragment", ast::PipelineStage::kFragment},
-        PipelineStageData{"compute", ast::PipelineStage::kCompute}));
-
-TEST_F(ParserImplTest, PipelineStage_NoMatch) {
-  auto p = parser("not-a-stage");
-  auto stage = p->expect_pipeline_stage();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(stage.errored);
-  ASSERT_EQ(p->error(), "1:1: invalid value for stage attribute");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc
deleted file mode 100644
index 868e39b..0000000
--- a/src/reader/wgsl/parser_impl_primary_expression_test.cc
+++ /dev/null
@@ -1,317 +0,0 @@
-// Copyright 2020 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/ast/bitcast_expression.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, PrimaryExpression_Ident) {
-  auto p = parser("a");
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-  auto* ident = e->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl) {
-  auto p = parser("vec4<i32>(1, 2, 3, 4))");
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* call = e->As<ast::CallExpression>();
-
-  EXPECT_NE(call->target.type, nullptr);
-
-  ASSERT_EQ(call->args.size(), 4u);
-  const auto& val = call->args;
-  ASSERT_TRUE(val[0]->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(val[0]->As<ast::SintLiteralExpression>()->value, 1);
-
-  ASSERT_TRUE(val[1]->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(val[1]->As<ast::SintLiteralExpression>()->value, 2);
-
-  ASSERT_TRUE(val[2]->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(val[2]->As<ast::SintLiteralExpression>()->value, 3);
-
-  ASSERT_TRUE(val[3]->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(val[3]->As<ast::SintLiteralExpression>()->value, 4);
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_ZeroConstructor) {
-  auto p = parser("vec4<i32>()");
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* call = e->As<ast::CallExpression>();
-
-  ASSERT_EQ(call->args.size(), 0u);
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidTypeDecl) {
-  auto p = parser("vec4<if>(2., 3., 4., 5.)");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: invalid type for vector");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingLeftParen) {
-  auto p = parser("vec4<f32> 2., 3., 4., 5.)");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingRightParen) {
-  auto p = parser("vec4<f32>(2., 3., 4., 5.");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:25: expected ')' for type constructor");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidValue) {
-  auto p = parser("i32(if(a) {})");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:5: expected ')' for type constructor");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_Empty) {
-  auto p = parser(R"(
-  struct S { a : i32; b : f32; }
-  S()
-  )");
-
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* call = e->As<ast::CallExpression>();
-
-  ASSERT_NE(call->target.name, nullptr);
-  EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
-
-  ASSERT_EQ(call->args.size(), 0u);
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_NotEmpty) {
-  auto p = parser(R"(
-  struct S { a : i32; b : f32; }
-  S(1u, 2.0)
-  )");
-
-  p->expect_global_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* call = e->As<ast::CallExpression>();
-
-  ASSERT_NE(call->target.name, nullptr);
-  EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
-
-  ASSERT_EQ(call->args.size(), 2u);
-
-  ASSERT_TRUE(call->args[0]->Is<ast::UintLiteralExpression>());
-  EXPECT_EQ(call->args[0]->As<ast::UintLiteralExpression>()->value, 1u);
-
-  ASSERT_TRUE(call->args[1]->Is<ast::FloatLiteralExpression>());
-  EXPECT_EQ(call->args[1]->As<ast::FloatLiteralExpression>()->value, 2.f);
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_ConstLiteral_True) {
-  auto p = parser("true");
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::BoolLiteralExpression>());
-  EXPECT_TRUE(e->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_ParenExpr) {
-  auto p = parser("(a == b)");
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingRightParen) {
-  auto p = parser("(a == b");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected ')'");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingExpr) {
-  auto p = parser("()");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_InvalidExpr) {
-  auto p = parser("(if (a) {})");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Cast) {
-  auto p = parser("f32(1)");
-
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* call = e->As<ast::CallExpression>();
-
-  ASSERT_TRUE(call->target.type->Is<ast::F32>());
-  ASSERT_EQ(call->args.size(), 1u);
-
-  ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast) {
-  auto p = parser("bitcast<f32>(1)");
-
-  auto e = p->primary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::BitcastExpression>());
-
-  auto* c = e->As<ast::BitcastExpression>();
-  ASSERT_TRUE(c->type->Is<ast::F32>());
-  ASSERT_TRUE(c->expr->Is<ast::IntLiteralExpression>());
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingGreaterThan) {
-  auto p = parser("bitcast<f32(1)");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:12: expected '>' for bitcast expression");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingType) {
-  auto p = parser("bitcast<>(1)");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: invalid type for bitcast expression");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingLeftParen) {
-  auto p = parser("bitcast<f32>1)");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: expected '('");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingRightParen) {
-  auto p = parser("bitcast<f32>(1");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:15: expected ')'");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingExpression) {
-  auto p = parser("bitcast<f32>()");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: unable to parse expression");
-}
-
-TEST_F(ParserImplTest, PrimaryExpression_bitcast_InvalidExpression) {
-  auto p = parser("bitcast<f32>(if (a) {})");
-  auto e = p->primary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: unable to parse expression");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_relational_expression_test.cc b/src/reader/wgsl/parser_impl_relational_expression_test.cc
deleted file mode 100644
index 1418b83..0000000
--- a/src/reader/wgsl/parser_impl_relational_expression_test.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, RelationalExpression_Parses_LessThan) {
-  auto p = parser("a < true");
-  auto e = p->relational_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kLessThan, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThan) {
-  auto p = parser("a > true");
-  auto e = p->relational_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kGreaterThan, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, RelationalExpression_Parses_LessThanEqual) {
-  auto p = parser("a <= true");
-  auto e = p->relational_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kLessThanEqual, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThanEqual) {
-  auto p = parser("a >= true");
-  auto e = p->relational_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, RelationalExpression_InvalidLHS) {
-  auto p = parser("if (a) {} < true");
-  auto e = p->relational_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, RelationalExpression_InvalidRHS) {
-  auto p = parser("true < if (a) {}");
-  auto e = p->relational_expression();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
-}
-
-TEST_F(ParserImplTest, RelationalExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->relational_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/reader/wgsl/parser_impl_reserved_keyword_test.cc
deleted file mode 100644
index 0b1ce39..0000000
--- a/src/reader/wgsl/parser_impl_reserved_keyword_test.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-using ParserImplReservedKeywordTest = ParserImplTestWithParam<std::string>;
-TEST_P(ParserImplReservedKeywordTest, Function) {
-  auto name = GetParam();
-  auto p = parser("fn " + name + "() {}");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:4: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, ModuleLet) {
-  auto name = GetParam();
-  auto p = parser("let " + name + " : i32 = 1;");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, ModuleVar) {
-  auto name = GetParam();
-  auto p = parser("var " + name + " : i32 = 1;");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, FunctionLet) {
-  auto name = GetParam();
-  auto p = parser("fn f() { let " + name + " : i32 = 1; }");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, FunctionVar) {
-  auto name = GetParam();
-  auto p = parser("fn f() { var " + name + " : i32 = 1; }");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, FunctionParam) {
-  auto name = GetParam();
-  auto p = parser("fn f(" + name + " : i32) {}");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, Struct) {
-  auto name = GetParam();
-  auto p = parser("struct " + name + " {};");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, StructMember) {
-  auto name = GetParam();
-  auto p = parser("struct S { " + name + " : i32; };");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:12: '" + name + "' is a reserved keyword");
-}
-TEST_P(ParserImplReservedKeywordTest, Alias) {
-  auto name = GetParam();
-  auto p = parser("type " + name + " = i32;");
-  EXPECT_FALSE(p->Parse());
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest,
-                         ParserImplReservedKeywordTest,
-                         testing::Values("asm",
-                                         "bf16",
-                                         "const",
-                                         "do",
-                                         "enum",
-                                         "f16",
-                                         "f64",
-                                         "handle",
-                                         "i8",
-                                         "i16",
-                                         "i64",
-                                         "mat",
-                                         "premerge",
-                                         "regardless",
-                                         "typedef",
-                                         "u8",
-                                         "u16",
-                                         "u64",
-                                         "unless",
-                                         "using",
-                                         "vec",
-                                         "void",
-                                         "while"));
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc b/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
deleted file mode 100644
index 884c083..0000000
--- a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, SampledTextureType_Invalid) {
-  auto p = parser("1234");
-  auto t = p->sampled_texture_type();
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_1d) {
-  auto p = parser("texture_1d");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k1d);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_2d) {
-  auto p = parser("texture_2d");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_2dArray) {
-  auto p = parser("texture_2d_array");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k2dArray);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_3d) {
-  auto p = parser("texture_3d");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k3d);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_Cube) {
-  auto p = parser("texture_cube");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::kCube);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_kCubeArray) {
-  auto p = parser("texture_cube_array");
-  auto t = p->sampled_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::kCubeArray);
-  EXPECT_FALSE(p->has_error());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_sampler_type_test.cc b/src/reader/wgsl/parser_impl_sampler_type_test.cc
deleted file mode 100644
index 32de50d..0000000
--- a/src/reader/wgsl/parser_impl_sampler_type_test.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, SamplerType_Invalid) {
-  auto p = parser("1234");
-  auto t = p->sampler_type();
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SamplerType_Sampler) {
-  auto p = parser("sampler");
-  auto t = p->sampler_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Sampler>());
-  EXPECT_FALSE(t->As<ast::Sampler>()->IsComparison());
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
-}
-
-TEST_F(ParserImplTest, SamplerType_ComparisonSampler) {
-  auto p = parser("sampler_comparison");
-  auto t = p->sampler_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Sampler>());
-  EXPECT_TRUE(t->As<ast::Sampler>()->IsComparison());
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_shift_expression_test.cc b/src/reader/wgsl/parser_impl_shift_expression_test.cc
deleted file mode 100644
index 98694c5..0000000
--- a/src/reader/wgsl/parser_impl_shift_expression_test.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftLeft) {
-  auto p = parser("a << true");
-  auto e = p->shift_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftRight) {
-  auto p = parser("a >> true");
-  auto e = p->shift_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
-  auto* rel = e->As<ast::BinaryExpression>();
-  EXPECT_EQ(ast::BinaryOp::kShiftRight, rel->op);
-
-  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
-  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
-  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
-}
-
-TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceLeft) {
-  auto p = parser("a < < true");
-  auto e = p->shift_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceRight) {
-  auto p = parser("a > > true");
-  auto e = p->shift_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, ShiftExpression_InvalidLHS) {
-  auto p = parser("if (a) {} << true");
-  auto e = p->shift_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e.value, nullptr);
-}
-
-TEST_F(ParserImplTest, ShiftExpression_InvalidRHS) {
-  auto p = parser("true << if (a) {}");
-  auto e = p->shift_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:9: unable to parse right side of << expression");
-}
-
-TEST_F(ParserImplTest, ShiftExpression_NoOr_ReturnsLHS) {
-  auto p = parser("a true");
-  auto e = p->shift_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_singular_expression_test.cc b/src/reader/wgsl/parser_impl_singular_expression_test.cc
deleted file mode 100644
index 4b53da5..0000000
--- a/src/reader/wgsl/parser_impl_singular_expression_test.cc
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, SingularExpression_Array_ConstantIndex) {
-  auto p = parser("a[1]");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
-  auto* idx = e->As<ast::IndexAccessorExpression>();
-
-  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
-  auto* ident = idx->object->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 1);
-}
-
-TEST_F(ParserImplTest, SingularExpression_Array_ExpressionIndex) {
-  auto p = parser("a[1 + b / 4]");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
-  auto* idx = e->As<ast::IndexAccessorExpression>();
-
-  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
-  auto* ident = idx->object->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(idx->index->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, SingularExpression_Array_MissingIndex) {
-  auto p = parser("a[]");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
-}
-
-TEST_F(ParserImplTest, SingularExpression_Array_MissingRightBrace) {
-  auto p = parser("a[1");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:4: expected ']' for index accessor");
-}
-
-TEST_F(ParserImplTest, SingularExpression_Array_InvalidIndex) {
-  auto p = parser("a[if(a() {})]");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
-}
-
-TEST_F(ParserImplTest, SingularExpression_Call_Empty) {
-  auto p = parser("a()");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* c = e->As<ast::CallExpression>();
-
-  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
-
-  EXPECT_EQ(c->args.size(), 0u);
-}
-
-TEST_F(ParserImplTest, SingularExpression_Call_WithArgs) {
-  auto p = parser("test(1, b, 2 + 3 / b)");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* c = e->As<ast::CallExpression>();
-
-  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("test"));
-
-  EXPECT_EQ(c->args.size(), 3u);
-  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
-  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
-  EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, SingularExpression_Call_TrailingComma) {
-  auto p = parser("a(b, )");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::CallExpression>());
-  auto* c = e->As<ast::CallExpression>();
-  EXPECT_EQ(c->args.size(), 1u);
-}
-
-TEST_F(ParserImplTest, SingularExpression_Call_InvalidArg) {
-  auto p = parser("a(if(a) {})");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
-}
-
-TEST_F(ParserImplTest, SingularExpression_Call_MissingRightParen) {
-  auto p = parser("a(");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
-}
-
-TEST_F(ParserImplTest, SingularExpression_MemberAccessor) {
-  auto p = parser("a.b");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::MemberAccessorExpression>());
-
-  auto* m = e->As<ast::MemberAccessorExpression>();
-  ASSERT_TRUE(m->structure->Is<ast::IdentifierExpression>());
-  EXPECT_EQ(m->structure->As<ast::IdentifierExpression>()->symbol,
-            p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(m->member->Is<ast::IdentifierExpression>());
-  EXPECT_EQ(m->member->As<ast::IdentifierExpression>()->symbol,
-            p->builder().Symbols().Get("b"));
-}
-
-TEST_F(ParserImplTest, SingularExpression_MemberAccesssor_InvalidIdent) {
-  auto p = parser("a.if");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
-}
-
-TEST_F(ParserImplTest, SingularExpression_MemberAccessor_MissingIdent) {
-  auto p = parser("a.");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
-}
-
-TEST_F(ParserImplTest, SingularExpression_NonMatch_returnLHS) {
-  auto p = parser("a b");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-}
-
-TEST_F(ParserImplTest, SingularExpression_Array_NestedIndexAccessor) {
-  auto p = parser("a[b[c]]");
-  auto e = p->singular_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  const auto* outer_accessor = e->As<ast::IndexAccessorExpression>();
-  ASSERT_TRUE(outer_accessor);
-
-  const auto* outer_object =
-      outer_accessor->object->As<ast::IdentifierExpression>();
-  ASSERT_TRUE(outer_object);
-  EXPECT_EQ(outer_object->symbol, p->builder().Symbols().Get("a"));
-
-  const auto* inner_accessor =
-      outer_accessor->index->As<ast::IndexAccessorExpression>();
-  ASSERT_TRUE(inner_accessor);
-
-  const auto* inner_object =
-      inner_accessor->object->As<ast::IdentifierExpression>();
-  ASSERT_TRUE(inner_object);
-  EXPECT_EQ(inner_object->symbol, p->builder().Symbols().Get("b"));
-
-  const auto* index_expr =
-      inner_accessor->index->As<ast::IdentifierExpression>();
-  ASSERT_TRUE(index_expr);
-  EXPECT_EQ(index_expr->symbol, p->builder().Symbols().Get("c"));
-}
-
-TEST_F(ParserImplTest, SingularExpression_PostfixPlusPlus) {
-  auto p = parser("a++");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:2: postfix increment and decrement operators are reserved for a "
-            "future WGSL version");
-}
-
-TEST_F(ParserImplTest, SingularExpression_PostfixMinusMinus) {
-  auto p = parser("a--");
-  auto e = p->singular_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:2: postfix increment and decrement operators are reserved for a "
-            "future WGSL version");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_statement_test.cc b/src/reader/wgsl/parser_impl_statement_test.cc
deleted file mode 100644
index 5a63a03..0000000
--- a/src/reader/wgsl/parser_impl_statement_test.cc
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2020 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/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Statement) {
-  auto p = parser("return;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Semicolon) {
-  auto p = parser(";");
-  p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-}
-
-TEST_F(ParserImplTest, Statement_Return_NoValue) {
-  auto p = parser("return;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
-  auto* ret = e->As<ast::ReturnStatement>();
-  ASSERT_EQ(ret->value, nullptr);
-}
-
-TEST_F(ParserImplTest, Statement_Return_Value) {
-  auto p = parser("return a + b * (.1 - .2);");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
-  auto* ret = e->As<ast::ReturnStatement>();
-  ASSERT_NE(ret->value, nullptr);
-  EXPECT_TRUE(ret->value->Is<ast::BinaryExpression>());
-}
-
-TEST_F(ParserImplTest, Statement_Return_MissingSemi) {
-  auto p = parser("return");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:7: expected ';' for return statement");
-}
-
-TEST_F(ParserImplTest, Statement_Return_Invalid) {
-  auto p = parser("return if(a) {};");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: expected ';' for return statement");
-}
-
-TEST_F(ParserImplTest, Statement_If) {
-  auto p = parser("if (a) {}");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::IfStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_If_Invalid) {
-  auto p = parser("if (a) { fn main() -> {}}");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:10: expected '}'");
-}
-
-TEST_F(ParserImplTest, Statement_Variable) {
-  auto p = parser("var a : i32 = 1;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Variable_Invalid) {
-  auto p = parser("var a : i32 =;");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:14: missing constructor for variable declaration");
-}
-
-TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) {
-  auto p = parser("var a : i32");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:12: expected ';' for variable declaration");
-}
-
-TEST_F(ParserImplTest, Statement_Switch) {
-  auto p = parser("switch (a) {}");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Switch_Invalid) {
-  auto p = parser("switch (a) { case: {}}");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:18: unable to parse case selectors");
-}
-
-TEST_F(ParserImplTest, Statement_Loop) {
-  auto p = parser("loop {}");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::LoopStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Loop_Invalid) {
-  auto p = parser("loop discard; }");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
-}
-
-TEST_F(ParserImplTest, Statement_Assignment) {
-  auto p = parser("a = b;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Assignment_Invalid) {
-  auto p = parser("a = if(b) {};");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:5: unable to parse right side of assignment");
-}
-
-TEST_F(ParserImplTest, Statement_Assignment_MissingSemicolon) {
-  auto p = parser("a = b");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected ';' for assignment statement");
-}
-
-TEST_F(ParserImplTest, Statement_Break) {
-  auto p = parser("break;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::BreakStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Break_MissingSemicolon) {
-  auto p = parser("break");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected ';' for break statement");
-}
-
-TEST_F(ParserImplTest, Statement_Continue) {
-  auto p = parser("continue;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::ContinueStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Continue_MissingSemicolon) {
-  auto p = parser("continue");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:9: expected ';' for continue statement");
-}
-
-TEST_F(ParserImplTest, Statement_Discard) {
-  auto p = parser("discard;");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::DiscardStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Discard_MissingSemicolon) {
-  auto p = parser("discard");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:8: expected ';' for discard statement");
-}
-
-TEST_F(ParserImplTest, Statement_Body) {
-  auto p = parser("{ var i: i32; }");
-  auto e = p->statement();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_TRUE(e->Is<ast::BlockStatement>());
-  EXPECT_TRUE(e->As<ast::BlockStatement>()
-                  ->statements[0]
-                  ->Is<ast::VariableDeclStatement>());
-}
-
-TEST_F(ParserImplTest, Statement_Body_Invalid) {
-  auto p = parser("{ fn main() -> {}}");
-  auto e = p->statement();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:3: expected '}'");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_statements_test.cc b/src/reader/wgsl/parser_impl_statements_test.cc
deleted file mode 100644
index 643d35f..0000000
--- a/src/reader/wgsl/parser_impl_statements_test.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 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/ast/discard_statement.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Statements) {
-  auto p = parser("discard; return;");
-  auto e = p->expect_statements();
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->size(), 2u);
-  EXPECT_TRUE(e.value[0]->Is<ast::DiscardStatement>());
-  EXPECT_TRUE(e.value[1]->Is<ast::ReturnStatement>());
-}
-
-TEST_F(ParserImplTest, Statements_Empty) {
-  auto p = parser("");
-  auto e = p->expect_statements();
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->size(), 0u);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_storage_class_test.cc b/src/reader/wgsl/parser_impl_storage_class_test.cc
deleted file mode 100644
index 63d844e..0000000
--- a/src/reader/wgsl/parser_impl_storage_class_test.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-struct StorageClassData {
-  const char* input;
-  ast::StorageClass result;
-};
-inline std::ostream& operator<<(std::ostream& out, StorageClassData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class StorageClassTest : public ParserImplTestWithParam<StorageClassData> {};
-
-TEST_P(StorageClassTest, Parses) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-
-  auto sc = p->expect_storage_class("test");
-  EXPECT_FALSE(sc.errored);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(sc.value, params.result);
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsEof());
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    StorageClassTest,
-    testing::Values(
-        StorageClassData{"uniform", ast::StorageClass::kUniform},
-        StorageClassData{"workgroup", ast::StorageClass::kWorkgroup},
-        StorageClassData{"storage", ast::StorageClass::kStorage},
-        StorageClassData{"storage_buffer", ast::StorageClass::kStorage},
-        StorageClassData{"private", ast::StorageClass::kPrivate},
-        StorageClassData{"function", ast::StorageClass::kFunction}));
-
-TEST_F(ParserImplTest, StorageClass_NoMatch) {
-  auto p = parser("not-a-storage-class");
-  auto sc = p->expect_storage_class("test");
-  EXPECT_EQ(sc.errored, true);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:1: invalid storage class for test");
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.to_str(), "not");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_storage_texture_type_test.cc b/src/reader/wgsl/parser_impl_storage_texture_type_test.cc
deleted file mode 100644
index 622a8df..0000000
--- a/src/reader/wgsl/parser_impl_storage_texture_type_test.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, StorageTextureType_Invalid) {
-  auto p = parser("abc");
-  auto t = p->storage_texture_type();
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, StorageTextureType_1d) {
-  auto p = parser("texture_storage_1d");
-  auto t = p->storage_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k1d);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, StorageTextureType_2d) {
-  auto p = parser("texture_storage_2d");
-  auto t = p->storage_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, StorageTextureType_2dArray) {
-  auto p = parser("texture_storage_2d_array");
-  auto t = p->storage_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k2dArray);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, StorageTextureType_3d) {
-  auto p = parser("texture_storage_3d");
-  auto t = p->storage_texture_type();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TextureDimension::k3d);
-  EXPECT_FALSE(p->has_error());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_attribute_decl_test.cc b/src/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
deleted file mode 100644
index 6f1651d..0000000
--- a/src/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2020 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/ast/invariant_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AttributeDecl_Parses) {
-  auto p = parser("@invariant");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 1u);
-  auto* invariant = attrs.value[0]->As<ast::Attribute>();
-  EXPECT_TRUE(invariant->Is<ast::InvariantAttribute>());
-}
-
-TEST_F(ParserImplTest, AttributeDecl_MissingParenLeft) {
-  auto p = parser("@location 1)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(), "1:11: expected '(' for location attribute");
-}
-
-TEST_F(ParserImplTest, AttributeDecl_MissingValue) {
-  auto p = parser("@location()");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(),
-            "1:11: expected signed integer literal for location attribute");
-}
-
-TEST_F(ParserImplTest, AttributeDecl_MissingParenRight) {
-  auto p = parser("@location(1");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(), "1:12: expected ')' for location attribute");
-}
-
-TEST_F(ParserImplTest, AttributeDecl_Invalidattribute) {
-  auto p = parser("@invalid");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_attributeDecl_Parses) {
-  auto p = parser("[[invariant]]");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 1u);
-  auto* invariant_attr = attrs.value[0]->As<ast::Attribute>();
-  EXPECT_TRUE(invariant_attr->Is<ast::InvariantAttribute>());
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_attributeDecl_MissingAttrRight) {
-  auto p = parser("[[invariant");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:12: expected ']]' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_attributeDecl_Invalidattribute) {
-  auto p = parser("[[invalid]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_attribute_test.cc b/src/reader/wgsl/parser_impl_struct_attribute_test.cc
deleted file mode 100644
index 026985f..0000000
--- a/src/reader/wgsl/parser_impl_struct_attribute_test.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-struct AttributeData {
-  const char* input;
-  bool is_block;
-};
-inline std::ostream& operator<<(std::ostream& out, AttributeData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class AttributeTest : public ParserImplTestWithParam<AttributeData> {};
-
-TEST_P(AttributeTest, Parses) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-
-  auto attr = p->attribute();
-  ASSERT_FALSE(p->has_error());
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* struct_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(struct_attr, nullptr);
-  EXPECT_EQ(struct_attr->Is<ast::StructBlockAttribute>(), params.is_block);
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         AttributeTest,
-                         testing::Values(AttributeData{"block", true}));
-
-TEST_F(ParserImplTest, Attribute_NoMatch) {
-  auto p = parser("not-a-stage");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_EQ(attr.value, nullptr);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
deleted file mode 100644
index 83c23fe..0000000
--- a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, StructBodyDecl_Parses) {
-  auto p = parser("{a : i32;}");
-
-  auto& builder = p->builder();
-
-  auto m = p->expect_struct_body_decl();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_EQ(m.value.size(), 1u);
-
-  const auto* mem = m.value[0];
-  EXPECT_EQ(mem->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(mem->type->Is<ast::I32>());
-  EXPECT_EQ(mem->attributes.size(), 0u);
-}
-
-TEST_F(ParserImplTest, StructBodyDecl_ParsesEmpty) {
-  auto p = parser("{}");
-  auto m = p->expect_struct_body_decl();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_EQ(m.value.size(), 0u);
-}
-
-TEST_F(ParserImplTest, StructBodyDecl_InvalidAlign) {
-  auto p = parser(R"(
-{
-  @align(nan) a : i32;
-})");
-  auto m = p->expect_struct_body_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(m.errored);
-  EXPECT_EQ(p->error(),
-            "3:10: expected signed integer literal for align attribute");
-}
-
-TEST_F(ParserImplTest, StructBodyDecl_InvalidSize) {
-  auto p = parser(R"(
-{
-  @size(nan) a : i32;
-})");
-  auto m = p->expect_struct_body_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(m.errored);
-  EXPECT_EQ(p->error(),
-            "3:9: expected signed integer literal for size attribute");
-}
-
-TEST_F(ParserImplTest, StructBodyDecl_MissingClosingBracket) {
-  auto p = parser("{a : i32;");
-  auto m = p->expect_struct_body_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(m.errored);
-  EXPECT_EQ(p->error(), "1:10: expected '}' for struct declaration");
-}
-
-TEST_F(ParserImplTest, StructBodyDecl_InvalidToken) {
-  auto p = parser(R"(
-{
-  a : i32;
-  1.23
-} )");
-  auto m = p->expect_struct_body_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(m.errored);
-  EXPECT_EQ(p->error(), "4:3: expected identifier for struct member");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
deleted file mode 100644
index 40d6cc7..0000000
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-#include "src/utils/string.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, StructDecl_Parses) {
-  auto p = parser(R"(
-struct S {
-  a : i32;
-  b : f32;
-})");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 0u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->name, p->builder().Symbols().Register("S"));
-  ASSERT_EQ(s->members.size(), 2u);
-  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
-  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
-}
-
-TEST_F(ParserImplTest, StructDecl_Unicode_Parses) {
-  const std::string struct_ident =  // "𝓼𝓽𝓻𝓾𝓬𝓽𝓾𝓻𝓮"
-      "\xf0\x9d\x93\xbc\xf0\x9d\x93\xbd\xf0\x9d\x93\xbb\xf0\x9d\x93\xbe\xf0\x9d"
-      "\x93\xac\xf0\x9d\x93\xbd\xf0\x9d\x93\xbe\xf0\x9d\x93\xbb\xf0\x9d\x93"
-      "\xae";
-  const std::string member_a_ident =  // "𝕞𝕖𝕞𝕓𝕖𝕣_𝕒"
-      "\xf0\x9d\x95\x9e\xf0\x9d\x95\x96\xf0\x9d\x95\x9e\xf0\x9d\x95\x93\xf0\x9d"
-      "\x95\x96\xf0\x9d\x95\xa3\x5f\xf0\x9d\x95\x92";
-  const std::string member_b_ident =  // "𝔪𝔢𝔪𝔟𝔢𝔯_𝔟"
-      "\xf0\x9d\x94\xaa\xf0\x9d\x94\xa2\xf0\x9d\x94\xaa\xf0\x9d\x94\x9f\xf0\x9d"
-      "\x94\xa2\xf0\x9d\x94\xaf\x5f\xf0\x9d\x94\x9f";
-
-  std::string src = R"(
-struct $struct {
-  $member_a : i32;
-  $member_b : f32;
-})";
-  src = utils::ReplaceAll(src, "$struct", struct_ident);
-  src = utils::ReplaceAll(src, "$member_a", member_a_ident);
-  src = utils::ReplaceAll(src, "$member_b", member_b_ident);
-
-  auto p = parser(src);
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 0u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->name, p->builder().Symbols().Register(struct_ident));
-  ASSERT_EQ(s->members.size(), 2u);
-  EXPECT_EQ(s->members[0]->symbol,
-            p->builder().Symbols().Register(member_a_ident));
-  EXPECT_EQ(s->members[1]->symbol,
-            p->builder().Symbols().Register(member_b_ident));
-}
-
-TEST_F(ParserImplTest, StructDecl_ParsesWithAttribute) {
-  auto p = parser(R"(
-[[block]] struct B {
-  a : f32;
-  b : f32;
-})");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 1u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->name, p->builder().Symbols().Register("B"));
-  ASSERT_EQ(s->members.size(), 2u);
-  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
-  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
-  ASSERT_EQ(s->attributes.size(), 1u);
-  EXPECT_TRUE(s->attributes[0]->Is<ast::StructBlockAttribute>());
-}
-
-TEST_F(ParserImplTest, StructDecl_ParsesWithMultipleAttribute) {
-  auto p = parser(R"(
-[[block]]
-[[block]] struct S {
-  a : f32;
-  b : f32;
-})");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 2u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->name, p->builder().Symbols().Register("S"));
-  ASSERT_EQ(s->members.size(), 2u);
-  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
-  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
-  ASSERT_EQ(s->attributes.size(), 2u);
-  EXPECT_TRUE(s->attributes[0]->Is<ast::StructBlockAttribute>());
-  EXPECT_TRUE(s->attributes[1]->Is<ast::StructBlockAttribute>());
-}
-
-TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
-  auto p = parser("struct S {}");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 0u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->members.size(), 0u);
-}
-
-TEST_F(ParserImplTest, StructDecl_MissingIdent) {
-  auto p = parser("struct {}");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 0u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_TRUE(s.errored);
-  EXPECT_FALSE(s.matched);
-  EXPECT_EQ(s.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected identifier for struct declaration");
-}
-
-TEST_F(ParserImplTest, StructDecl_MissingBracketLeft) {
-  auto p = parser("struct S }");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 0u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_TRUE(s.errored);
-  EXPECT_FALSE(s.matched);
-  EXPECT_EQ(s.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: expected '{' for struct declaration");
-}
-
-// TODO(crbug.com/tint/1324): DEPRECATED: Remove when @block is removed.
-TEST_F(ParserImplTest, StructDecl_InvalidAttributeDecl) {
-  auto p = parser("[[block struct S { a : i32; }");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(s.errored);
-  EXPECT_TRUE(s.matched);
-  EXPECT_NE(s.value, nullptr);
-
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: use of deprecated language feature: [[block]] attributes have been removed from WGSL
-1:9: expected ']]' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
-TEST_F(ParserImplTest, StructDecl_MissingStruct) {
-  auto p = parser("[[block]] S {}");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 1u);
-
-  auto s = p->struct_decl(attrs.value);
-  EXPECT_FALSE(s.errored);
-  EXPECT_FALSE(s.matched);
-  EXPECT_EQ(s.value, nullptr);
-
-  EXPECT_FALSE(p->has_error());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc b/src/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
deleted file mode 100644
index 0df720e..0000000
--- a/src/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AttributeDecl_EmptyStr) {
-  auto p = parser("");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 0u);
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeDecl_EmptyBlock) {
-  auto p = parser("[[]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 0u);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: empty attribute list)");
-}
-
-TEST_F(ParserImplTest, AttributeDecl_Single) {
-  auto p = parser("@size(4)");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 1u);
-  auto* attr = attrs.value[0]->As<ast::Attribute>();
-  ASSERT_NE(attr, nullptr);
-  EXPECT_TRUE(attr->Is<ast::StructMemberSizeAttribute>());
-}
-
-TEST_F(ParserImplTest, AttributeDecl_InvalidAttribute) {
-  auto p = parser("@size(nan)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error()) << p->error();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for size attribute");
-}
-
-TEST_F(ParserImplTest, AttributeDecl_MissingClose) {
-  auto p = parser("[[size(4)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error()) << p->error();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: expected ']]' for attribute list)");
-}
-
-TEST_F(ParserImplTest, StructMemberAttributeDecl_SizeMissingClose) {
-  auto p = parser("[[size(4)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error()) << p->error();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: expected ']]' for attribute list)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_member_attribute_test.cc b/src/reader/wgsl/parser_impl_struct_member_attribute_test.cc
deleted file mode 100644
index 997989a..0000000
--- a/src/reader/wgsl/parser_impl_struct_member_attribute_test.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Attribute_Size) {
-  auto p = parser("size(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  ASSERT_FALSE(p->has_error());
-
-  auto* member_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(member_attr, nullptr);
-  ASSERT_TRUE(member_attr->Is<ast::StructMemberSizeAttribute>());
-
-  auto* o = member_attr->As<ast::StructMemberSizeAttribute>();
-  EXPECT_EQ(o->size, 4u);
-}
-
-TEST_F(ParserImplTest, Attribute_Size_MissingLeftParen) {
-  auto p = parser("size 4)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: expected '(' for size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Size_MissingRightParen) {
-  auto p = parser("size(4");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected ')' for size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Size_MissingValue) {
-  auto p = parser("size()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:6: expected signed integer literal for size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Size_MissingInvalid) {
-  auto p = parser("size(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:6: expected signed integer literal for size attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Align) {
-  auto p = parser("align(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  ASSERT_FALSE(p->has_error());
-
-  auto* member_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(member_attr, nullptr);
-  ASSERT_TRUE(member_attr->Is<ast::StructMemberAlignAttribute>());
-
-  auto* o = member_attr->As<ast::StructMemberAlignAttribute>();
-  EXPECT_EQ(o->align, 4u);
-}
-
-TEST_F(ParserImplTest, Attribute_Align_MissingLeftParen) {
-  auto p = parser("align 4)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected '(' for align attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Align_MissingRightParen) {
-  auto p = parser("align(4");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected ')' for align attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Align_MissingValue) {
-  auto p = parser("align()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for align attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Align_MissingInvalid) {
-  auto p = parser("align(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for align attribute");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
deleted file mode 100644
index 3feec00..0000000
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, StructMember_Parses) {
-  auto p = parser("a : i32;");
-
-  auto& builder = p->builder();
-
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 0u);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(m->type->Is<ast::I32>());
-  EXPECT_EQ(m->attributes.size(), 0u);
-
-  EXPECT_EQ(m->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
-  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 5u}, {1u, 8u}}));
-}
-
-TEST_F(ParserImplTest, StructMember_ParsesWithAlignAttribute) {
-  auto p = parser("@align(2) a : i32;");
-
-  auto& builder = p->builder();
-
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 1u);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(m->type->Is<ast::I32>());
-  EXPECT_EQ(m->attributes.size(), 1u);
-  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberAlignAttribute>());
-  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberAlignAttribute>()->align, 2u);
-
-  EXPECT_EQ(m->source.range, (Source::Range{{1u, 11u}, {1u, 12u}}));
-  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 15u}, {1u, 18u}}));
-}
-
-TEST_F(ParserImplTest, StructMember_ParsesWithSizeAttribute) {
-  auto p = parser("@size(2) a : i32;");
-
-  auto& builder = p->builder();
-
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 1u);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(m->type->Is<ast::I32>());
-  EXPECT_EQ(m->attributes.size(), 1u);
-  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
-  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
-
-  EXPECT_EQ(m->source.range, (Source::Range{{1u, 10u}, {1u, 11u}}));
-  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, StructMember_ParsesWithAttribute) {
-  auto p = parser("@size(2) a : i32;");
-
-  auto& builder = p->builder();
-
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 1u);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(m->type->Is<ast::I32>());
-  EXPECT_EQ(m->attributes.size(), 1u);
-  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
-  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
-
-  EXPECT_EQ(m->source.range, (Source::Range{{1u, 10u}, {1u, 11u}}));
-  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, StructMember_ParsesWithMultipleattributes) {
-  auto p = parser(R"(@size(2)
-@align(4) a : i32;)");
-
-  auto& builder = p->builder();
-
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_TRUE(attrs.matched);
-  EXPECT_EQ(attrs.value.size(), 2u);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
-  EXPECT_TRUE(m->type->Is<ast::I32>());
-  EXPECT_EQ(m->attributes.size(), 2u);
-  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
-  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
-  EXPECT_TRUE(m->attributes[1]->Is<ast::StructMemberAlignAttribute>());
-  EXPECT_EQ(m->attributes[1]->As<ast::StructMemberAlignAttribute>()->align, 4u);
-
-  EXPECT_EQ(m->source.range, (Source::Range{{2u, 11u}, {2u, 12u}}));
-  EXPECT_EQ(m->type->source.range, (Source::Range{{2u, 15u}, {2u, 18u}}));
-}
-
-TEST_F(ParserImplTest, StructMember_InvalidAttribute) {
-  auto p = parser("@size(nan) a : i32;");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_FALSE(m.errored);
-  ASSERT_NE(m.value, nullptr);
-
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for size attribute");
-}
-
-TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
-  auto p = parser("a : i32");
-  auto attrs = p->attribute_list();
-  EXPECT_FALSE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-
-  auto m = p->expect_struct_member(attrs.value);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(m.errored);
-  ASSERT_EQ(m.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: expected ';' for struct member");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_switch_body_test.cc b/src/reader/wgsl/parser_impl_switch_body_test.cc
deleted file mode 100644
index f6fa138..0000000
--- a/src/reader/wgsl/parser_impl_switch_body_test.cc
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, SwitchBody_Case) {
-  auto p = parser("case 1: { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::CaseStatement>());
-  EXPECT_FALSE(e->IsDefault());
-  auto* stmt = e->As<ast::CaseStatement>();
-  ASSERT_EQ(stmt->selectors.size(), 1u);
-  EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
-  ASSERT_EQ(e->body->statements.size(), 1u);
-  EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_TrailingComma) {
-  auto p = parser("case 1, 2,: { }");
-  auto e = p->switch_body();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::CaseStatement>());
-  EXPECT_FALSE(e->IsDefault());
-  auto* stmt = e->As<ast::CaseStatement>();
-  ASSERT_EQ(stmt->selectors.size(), 2u);
-  EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
-  EXPECT_EQ(stmt->selectors[1]->ValueAsU32(), 2u);
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_InvalidConstLiteral) {
-  auto p = parser("case a == 4: { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: unable to parse case selectors");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_InvalidSelector_bool) {
-  auto p = parser("case true: { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: invalid case selector must be an integer value");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MissingConstLiteral) {
-  auto p = parser("case: { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:5: unable to parse case selectors");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MissingColon) {
-  auto p = parser("case 1 { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketLeft) {
-  auto p = parser("case 1: a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:9: expected '{' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketRight) {
-  auto p = parser("case 1: { a = 4; ");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:18: expected '}' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_InvalidCaseBody) {
-  auto p = parser("case 1: { fn main() {} }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:11: expected '}' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectors) {
-  auto p = parser("case 1, 2: { }");
-  auto e = p->switch_body();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::CaseStatement>());
-  EXPECT_FALSE(e->IsDefault());
-  ASSERT_EQ(e->body->statements.size(), 0u);
-  ASSERT_EQ(e->selectors.size(), 2u);
-  ASSERT_EQ(e->selectors[0]->ValueAsI32(), 1);
-  ASSERT_EQ(e->selectors[1]->ValueAsI32(), 2);
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingColon) {
-  auto p = parser("case 1, 2 { }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:11: expected ':' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingComma) {
-  auto p = parser("case 1 2: { }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsStartsWithComma) {
-  auto p = parser("case , 1, 2: { }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: unable to parse case selectors");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Default) {
-  auto p = parser("default: { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::CaseStatement>());
-  EXPECT_TRUE(e->IsDefault());
-  ASSERT_EQ(e->body->statements.size(), 1u);
-  EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
-}
-
-TEST_F(ParserImplTest, SwitchBody_Default_MissingColon) {
-  auto p = parser("default { a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:9: expected ':' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketLeft) {
-  auto p = parser("default: a = 4; }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:10: expected '{' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketRight) {
-  auto p = parser("default: { a = 4; ");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:19: expected '}' for case statement");
-}
-
-TEST_F(ParserImplTest, SwitchBody_Default_InvalidCaseBody) {
-  auto p = parser("default: { fn main() {} }");
-  auto e = p->switch_body();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(e.errored);
-  EXPECT_FALSE(e.matched);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_EQ(p->error(), "1:12: expected '}' for case statement");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/reader/wgsl/parser_impl_switch_stmt_test.cc
deleted file mode 100644
index 4963356..0000000
--- a/src/reader/wgsl/parser_impl_switch_stmt_test.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, SwitchStmt_WithoutDefault) {
-  auto p = parser(R"(switch(a) {
-  case 1: {}
-  case 2: {}
-})");
-  auto e = p->switch_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
-  ASSERT_EQ(e->body.size(), 2u);
-  EXPECT_FALSE(e->body[0]->IsDefault());
-  EXPECT_FALSE(e->body[1]->IsDefault());
-}
-
-TEST_F(ParserImplTest, SwitchStmt_Empty) {
-  auto p = parser("switch(a) { }");
-  auto e = p->switch_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
-  ASSERT_EQ(e->body.size(), 0u);
-}
-
-TEST_F(ParserImplTest, SwitchStmt_DefaultInMiddle) {
-  auto p = parser(R"(switch(a) {
-  case 1: {}
-  default: {}
-  case 2: {}
-})");
-  auto e = p->switch_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
-
-  ASSERT_EQ(e->body.size(), 3u);
-  ASSERT_FALSE(e->body[0]->IsDefault());
-  ASSERT_TRUE(e->body[1]->IsDefault());
-  ASSERT_FALSE(e->body[2]->IsDefault());
-}
-
-TEST_F(ParserImplTest, SwitchStmt_InvalidExpression) {
-  auto p = parser("switch(a=b) {}");
-  auto e = p->switch_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: expected ')'");
-}
-
-TEST_F(ParserImplTest, SwitchStmt_MissingExpression) {
-  auto p = parser("switch {}");
-  auto e = p->switch_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected '('");
-}
-
-TEST_F(ParserImplTest, SwitchStmt_MissingBracketLeft) {
-  auto p = parser("switch(a) }");
-  auto e = p->switch_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:11: expected '{' for switch statement");
-}
-
-TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
-  auto p = parser("switch(a) {");
-  auto e = p->switch_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:12: expected '}' for switch statement");
-}
-
-TEST_F(ParserImplTest, SwitchStmt_InvalidBody) {
-  auto p = parser(R"(switch(a) {
-  case: {}
-})");
-  auto e = p->switch_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "2:7: unable to parse case selectors");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_test.cc b/src/reader/wgsl/parser_impl_test.cc
deleted file mode 100644
index 09da3b5..0000000
--- a/src/reader/wgsl/parser_impl_test.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Empty) {
-  auto p = parser("");
-  ASSERT_TRUE(p->Parse()) << p->error();
-}
-
-TEST_F(ParserImplTest, Parses) {
-  auto p = parser(R"(
-@stage(fragment)
-fn main() -> @location(0) vec4<f32> {
-  return vec4<f32>(.4, .2, .3, 1);
-}
-)");
-  ASSERT_TRUE(p->Parse()) << p->error();
-
-  Program program = p->program();
-  ASSERT_EQ(1u, program.AST().Functions().size());
-}
-
-TEST_F(ParserImplTest, Parses_ExtraSemicolons) {
-  auto p = parser(R"(
-;
-struct S {
-  a : f32;
-};;
-;
-fn foo() -> S {
-  ;
-  return S();;;
-  ;
-};;
-;
-)");
-  ASSERT_TRUE(p->Parse()) << p->error();
-
-  Program program = p->program();
-  ASSERT_EQ(1u, program.AST().Functions().size());
-  ASSERT_EQ(1u, program.AST().TypeDecls().size());
-}
-
-TEST_F(ParserImplTest, HandlesError) {
-  auto p = parser(R"(
-fn main() ->  {  // missing return type
-  return;
-})");
-
-  ASSERT_FALSE(p->Parse());
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "2:15: unable to determine function return type");
-}
-
-TEST_F(ParserImplTest, HandlesUnexpectedToken) {
-  auto p = parser(R"(
-fn main() {
-}
-foobar
-)");
-
-  ASSERT_FALSE(p->Parse());
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "4:1: unexpected token");
-}
-
-TEST_F(ParserImplTest, HandlesBadToken_InMiddle) {
-  auto p = parser(R"(
-fn main() {
-  let f = 0x1p500000000000; // Exponent too big for hex float
-  return;
-})");
-
-  ASSERT_FALSE(p->Parse());
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "3:11: exponent is too large for hex float");
-}
-
-TEST_F(ParserImplTest, HandlesBadToken_AtModuleScope) {
-  auto p = parser(R"(
-fn main() {
-  return;
-}
-0x1p5000000000000
-)");
-
-  ASSERT_FALSE(p->Parse());
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "5:1: exponent is too large for hex float");
-}
-
-TEST_F(ParserImplTest, Comments_TerminatedBlockComment) {
-  auto p = parser(R"(
-/**
- * Here is my shader.
- *
- * /* I can nest /**/ comments. */
- * // I can nest line comments too.
- **/
-@stage(fragment) // This is the stage
-fn main(/*
-no
-parameters
-*/) -> @location(0) vec4<f32> {
-  return/*block_comments_delimit_tokens*/vec4<f32>(.4, .2, .3, 1);
-}/* block comments are OK at EOF...*/)");
-
-  ASSERT_TRUE(p->Parse()) << p->error();
-  ASSERT_EQ(1u, p->program().AST().Functions().size());
-}
-
-TEST_F(ParserImplTest, Comments_UnterminatedBlockComment) {
-  auto p = parser(R"(
-@stage(fragment)
-fn main() -> @location(0) vec4<f32> {
-  return vec4<f32>(.4, .2, .3, 1);
-} /* unterminated block comments are invalid ...)");
-
-  ASSERT_FALSE(p->Parse());
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "5:3: unterminated block comment") << p->error();
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_test_helper.cc b/src/reader/wgsl/parser_impl_test_helper.cc
deleted file mode 100644
index acc67ae..0000000
--- a/src/reader/wgsl/parser_impl_test_helper.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-ParserImplTest::ParserImplTest() = default;
-
-ParserImplTest::~ParserImplTest() = default;
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_test_helper.h b/src/reader/wgsl/parser_impl_test_helper.h
deleted file mode 100644
index 0b301cb..0000000
--- a/src/reader/wgsl/parser_impl_test_helper.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 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_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
-#define SRC_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "src/reader/wgsl/parser_impl.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-/// WGSL Parser test class
-class ParserImplTest : public testing::Test, public ProgramBuilder {
- public:
-  /// Constructor
-  ParserImplTest();
-  ~ParserImplTest() override;
-
-  /// Retrieves the parser from the helper
-  /// @param str the string to parse
-  /// @returns the parser implementation
-  std::unique_ptr<ParserImpl> parser(const std::string& str) {
-    auto file = std::make_unique<Source::File>("test.wgsl", str);
-    auto impl = std::make_unique<ParserImpl>(file.get());
-    files_.emplace_back(std::move(file));
-    return impl;
-  }
-
- private:
-  std::vector<std::unique_ptr<Source::File>> files_;
-};
-
-/// WGSL Parser test class with param
-template <typename T>
-class ParserImplTestWithParam : public testing::TestWithParam<T>,
-                                public ProgramBuilder {
- public:
-  /// Constructor
-  ParserImplTestWithParam() = default;
-  ~ParserImplTestWithParam() override = default;
-
-  /// Retrieves the parser from the helper
-  /// @param str the string to parse
-  /// @returns the parser implementation
-  std::unique_ptr<ParserImpl> parser(const std::string& str) {
-    auto file = std::make_unique<Source::File>("test.wgsl", str);
-    auto impl = std::make_unique<ParserImpl>(file.get());
-    files_.emplace_back(std::move(file));
-    return impl;
-  }
-
- private:
-  std::vector<std::unique_ptr<Source::File>> files_;
-};
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
diff --git a/src/reader/wgsl/parser_impl_texel_format_test.cc b/src/reader/wgsl/parser_impl_texel_format_test.cc
deleted file mode 100644
index 86955ae..0000000
--- a/src/reader/wgsl/parser_impl_texel_format_test.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, ImageStorageType_Invalid) {
-  auto p = parser("1234");
-  auto t = p->expect_texel_format("test");
-  EXPECT_TRUE(t.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:1: invalid format for test");
-}
-
-TEST_F(ParserImplTest, ImageStorageType_R32Uint) {
-  auto p = parser("r32uint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kR32Uint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_R32Sint) {
-  auto p = parser("r32sint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kR32Sint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_R32Float) {
-  auto p = parser("r32float");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kR32Float);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba8Unorm) {
-  auto p = parser("rgba8unorm");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Unorm);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba8Snorm) {
-  auto p = parser("rgba8snorm");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Snorm);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba8Uint) {
-  auto p = parser("rgba8uint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Uint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba8Sint) {
-  auto p = parser("rgba8sint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Sint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rg32Uint) {
-  auto p = parser("rg32uint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Uint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rg32Sint) {
-  auto p = parser("rg32sint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Sint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rg32Float) {
-  auto p = parser("rg32float");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Float);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba16Uint) {
-  auto p = parser("rgba16uint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Uint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba16Sint) {
-  auto p = parser("rgba16sint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Sint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba16Float) {
-  auto p = parser("rgba16float");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Float);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba32Uint) {
-  auto p = parser("rgba32uint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Uint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba32Sint) {
-  auto p = parser("rgba32sint");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Sint);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, ImageStorageType_Rgba32Float) {
-  auto p = parser("rgba32float");
-  auto t = p->expect_texel_format("test");
-  EXPECT_FALSE(t.errored);
-  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Float);
-  EXPECT_FALSE(p->has_error());
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc b/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
deleted file mode 100644
index 214bd72..0000000
--- a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
+++ /dev/null
@@ -1,267 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, TextureSamplerTypes_Invalid) {
-  auto p = parser("1234");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_FALSE(t.errored);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_Sampler) {
-  auto p = parser("sampler");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Sampler>());
-  ASSERT_FALSE(t->As<ast::Sampler>()->IsComparison());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SamplerComparison) {
-  auto p = parser("sampler_comparison");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Sampler>());
-  ASSERT_TRUE(t->As<ast::Sampler>()->IsComparison());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_DepthTexture) {
-  auto p = parser("texture_depth_2d");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::DepthTexture>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32) {
-  auto p = parser("texture_1d<f32>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::SampledTexture>());
-  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::F32>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k1d);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32) {
-  auto p = parser("texture_2d<i32>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::SampledTexture>());
-  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::I32>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
-  auto p = parser("texture_3d<u32>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::SampledTexture>());
-  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::U32>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k3d);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
-  auto p = parser("texture_1d<>");
-  auto t = p->texture_sampler_types();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:12: invalid type for sampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan) {
-  auto p = parser("texture_1d");
-  auto t = p->texture_sampler_types();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:11: expected '<' for sampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingGreaterThan) {
-  auto p = parser("texture_1d<u32");
-  auto t = p->texture_sampler_types();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:15: expected '>' for sampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
-  auto p = parser("texture_multisampled_2d<i32>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::MultisampledTexture>());
-  ASSERT_TRUE(t->As<ast::MultisampledTexture>()->type->Is<ast::I32>());
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 29u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
-  auto p = parser("texture_multisampled_2d<>");
-  auto t = p->texture_sampler_types();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:25: invalid type for multisampled texture type");
-}
-
-TEST_F(ParserImplTest,
-       TextureSamplerTypes_MultisampledTexture_MissingLessThan) {
-  auto p = parser("texture_multisampled_2d");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:24: expected '<' for multisampled texture type");
-}
-
-TEST_F(ParserImplTest,
-       TextureSamplerTypes_MultisampledTexture_MissingGreaterThan) {
-  auto p = parser("texture_multisampled_2d<u32");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:28: expected '>' for multisampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Readonly1dRg32Float) {
-  auto p = parser("texture_storage_1d<rg32float, read>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::StorageTexture>());
-  EXPECT_EQ(t->As<ast::StorageTexture>()->format, ast::TexelFormat::kRg32Float);
-  EXPECT_EQ(t->As<ast::StorageTexture>()->access, ast::Access::kRead);
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k1d);
-  EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 36u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Writeonly2dR32Uint) {
-  auto p = parser("texture_storage_2d<r32uint, write>");
-  auto t = p->texture_sampler_types();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-
-  ASSERT_TRUE(t->Is<ast::Texture>());
-  ASSERT_TRUE(t->Is<ast::StorageTexture>());
-  EXPECT_EQ(t->As<ast::StorageTexture>()->format, ast::TexelFormat::kR32Uint);
-  EXPECT_EQ(t->As<ast::StorageTexture>()->access, ast::Access::kWrite);
-  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
-  EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 35u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType) {
-  auto p = parser("texture_storage_1d<abc, read>");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:20: invalid format for storage texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) {
-  auto p = parser("texture_storage_1d<r32float, abc>");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:30: invalid value for access control");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType) {
-  auto p = parser("texture_storage_1d<>");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:20: invalid format for storage texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingLessThan) {
-  auto p = parser("texture_storage_1d");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:19: expected '<' for storage texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingGreaterThan) {
-  auto p = parser("texture_storage_1d<r32uint, read");
-  auto t = p->texture_sampler_types();
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(t.errored);
-  EXPECT_EQ(p->error(), "1:33: expected '>' for storage texture type");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
deleted file mode 100644
index 301db6f..0000000
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, TypeDecl_ParsesType) {
-  auto p = parser("type a = i32");
-
-  auto t = p->type_alias();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(t.errored);
-  EXPECT_TRUE(t.matched);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t->Is<ast::Alias>());
-  auto* alias = t->As<ast::Alias>();
-  ASSERT_TRUE(alias->type->Is<ast::I32>());
-
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 13u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Parses_Ident) {
-  auto p = parser("type a = B");
-
-  auto t = p->type_alias();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(t.errored);
-  EXPECT_TRUE(t.matched);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t.value->Is<ast::Alias>());
-  auto* alias = t.value->As<ast::Alias>();
-  EXPECT_EQ(p->builder().Symbols().NameFor(alias->name), "a");
-  EXPECT_TRUE(alias->type->Is<ast::TypeName>());
-  EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Unicode_Parses_Ident) {
-  const std::string ident =  // "𝓶𝔂_𝓽𝔂𝓹𝓮"
-      "\xf0\x9d\x93\xb6\xf0\x9d\x94\x82\x5f\xf0\x9d\x93\xbd\xf0\x9d\x94\x82\xf0"
-      "\x9d\x93\xb9\xf0\x9d\x93\xae";
-
-  auto p = parser("type " + ident + " = i32");
-
-  auto t = p->type_alias();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(t.errored);
-  EXPECT_TRUE(t.matched);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t.value->Is<ast::Alias>());
-  auto* alias = t.value->As<ast::Alias>();
-  EXPECT_EQ(p->builder().Symbols().NameFor(alias->name), ident);
-  EXPECT_TRUE(alias->type->Is<ast::I32>());
-  EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 37u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_MissingIdent) {
-  auto p = parser("type = i32");
-  auto t = p->type_alias();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
-}
-
-TEST_F(ParserImplTest, TypeDecl_InvalidIdent) {
-  auto p = parser("type 123 = i32");
-  auto t = p->type_alias();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
-}
-
-TEST_F(ParserImplTest, TypeDecl_MissingEqual) {
-  auto p = parser("type a i32");
-  auto t = p->type_alias();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_EQ(p->error(), "1:8: expected '=' for type alias");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
deleted file mode 100644
index 74db017..0000000
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ /dev/null
@@ -1,893 +0,0 @@
-// Copyright 2020 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/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/matrix.h"
-#include "src/ast/sampler.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-#include "src/sem/sampled_texture_type.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, TypeDecl_Invalid) {
-  auto p = parser("1234");
-  auto t = p->type_decl();
-  EXPECT_EQ(t.errored, false);
-  EXPECT_EQ(t.matched, false);
-  EXPECT_EQ(t.value, nullptr);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, TypeDecl_Identifier) {
-  auto p = parser("A");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  auto* type_name = t.value->As<ast::TypeName>();
-  ASSERT_NE(type_name, nullptr);
-  EXPECT_EQ(p->builder().Symbols().Get("A"), type_name->name);
-  EXPECT_EQ(type_name->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Bool) {
-  auto p = parser("bool");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_TRUE(t.value->Is<ast::Bool>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_F32) {
-  auto p = parser("f32");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_TRUE(t.value->Is<ast::F32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_I32) {
-  auto p = parser("i32");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_TRUE(t.value->Is<ast::I32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_U32) {
-  auto p = parser("u32");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_TRUE(t.value->Is<ast::U32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
-}
-
-struct VecData {
-  const char* input;
-  size_t count;
-  Source::Range range;
-};
-inline std::ostream& operator<<(std::ostream& out, VecData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class VecTest : public ParserImplTestWithParam<VecData> {};
-
-TEST_P(VecTest, Parse) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  EXPECT_TRUE(t.value->Is<ast::Vector>());
-  EXPECT_EQ(t.value->As<ast::Vector>()->width, params.count);
-  EXPECT_EQ(t.value->source.range, params.range);
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    VecTest,
-    testing::Values(VecData{"vec2<f32>", 2, {{1u, 1u}, {1u, 10u}}},
-                    VecData{"vec3<f32>", 3, {{1u, 1u}, {1u, 10u}}},
-                    VecData{"vec4<f32>", 4, {{1u, 1u}, {1u, 10u}}}));
-
-class VecMissingGreaterThanTest : public ParserImplTestWithParam<VecData> {};
-
-TEST_P(VecMissingGreaterThanTest, Handles_Missing_GreaterThan) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:9: expected '>' for vector");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         VecMissingGreaterThanTest,
-                         testing::Values(VecData{"vec2<f32", 2, {}},
-                                         VecData{"vec3<f32", 3, {}},
-                                         VecData{"vec4<f32", 4, {}}));
-
-class VecMissingType : public ParserImplTestWithParam<VecData> {};
-
-TEST_P(VecMissingType, Handles_Missing_Type) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:6: invalid type for vector");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         VecMissingType,
-                         testing::Values(VecData{"vec2<>", 2, {}},
-                                         VecData{"vec3<>", 3, {}},
-                                         VecData{"vec4<>", 4, {}}));
-
-TEST_F(ParserImplTest, TypeDecl_Ptr) {
-  auto p = parser("ptr<function, f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Pointer>());
-
-  auto* ptr = t.value->As<ast::Pointer>();
-  ASSERT_TRUE(ptr->type->Is<ast::F32>());
-  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_WithAccess) {
-  auto p = parser("ptr<function, f32, read>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Pointer>());
-
-  auto* ptr = t.value->As<ast::Pointer>();
-  ASSERT_TRUE(ptr->type->Is<ast::F32>());
-  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
-  ASSERT_EQ(ptr->access, ast::Access::kRead);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_ToVec) {
-  auto p = parser("ptr<function, vec2<f32>>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Pointer>());
-
-  auto* ptr = t.value->As<ast::Pointer>();
-  ASSERT_TRUE(ptr->type->Is<ast::Vector>());
-  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
-
-  auto* vec = ptr->type->As<ast::Vector>();
-  ASSERT_EQ(vec->width, 2u);
-  ASSERT_TRUE(vec->type->Is<ast::F32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingLessThan) {
-  auto p = parser("ptr private, f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:5: expected '<' for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterType) {
-  auto p = parser("ptr<function, f32");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:18: expected '>' for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterAccess) {
-  auto p = parser("ptr<function, f32, read");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:24: expected '>' for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterStorageClass) {
-  auto p = parser("ptr<function f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:14: expected ',' for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterAccess) {
-  auto p = parser("ptr<function, f32 read>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:19: expected '>' for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingStorageClass) {
-  auto p = parser("ptr<, f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingType) {
-  auto p = parser("ptr<function,>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:14: invalid type for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingAccess) {
-  auto p = parser("ptr<function, i32, >");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:20: expected identifier for access control");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_MissingParams) {
-  auto p = parser("ptr<>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
-  auto p = parser("ptr<unknown, f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Ptr_BadAccess) {
-  auto p = parser("ptr<function, i32, unknown>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:20: invalid value for access control");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Atomic) {
-  auto p = parser("atomic<f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Atomic>());
-
-  auto* atomic = t.value->As<ast::Atomic>();
-  ASSERT_TRUE(atomic->type->Is<ast::F32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 12u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Atomic_ToVec) {
-  auto p = parser("atomic<vec2<f32>>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Atomic>());
-
-  auto* atomic = t.value->As<ast::Atomic>();
-  ASSERT_TRUE(atomic->type->Is<ast::Vector>());
-
-  auto* vec = atomic->type->As<ast::Vector>();
-  ASSERT_EQ(vec->width, 2u);
-  ASSERT_TRUE(vec->type->Is<ast::F32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Atomic_MissingLessThan) {
-  auto p = parser("atomic f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:8: expected '<' for atomic declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Atomic_MissingGreaterThan) {
-  auto p = parser("atomic<f32");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: expected '>' for atomic declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Atomic_MissingType) {
-  auto p = parser("atomic<>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:8: invalid type for atomic declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_SintLiteralSize) {
-  auto p = parser("array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_FALSE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-  EXPECT_EQ(a->attributes.size(), 0u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
-
-  auto* size = a->count->As<ast::SintLiteralExpression>();
-  ASSERT_NE(size, nullptr);
-  EXPECT_EQ(size->ValueAsI32(), 5);
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_UintLiteralSize) {
-  auto p = parser("array<f32, 5u>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_FALSE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-  EXPECT_EQ(a->attributes.size(), 0u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
-
-  auto* size = a->count->As<ast::UintLiteralExpression>();
-  ASSERT_NE(size, nullptr);
-  EXPECT_EQ(size->ValueAsU32(), 5u);
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_ConstantSize) {
-  auto p = parser("array<f32, size>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_FALSE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-  EXPECT_EQ(a->attributes.size(), 0u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-
-  auto* count_expr = a->count->As<ast::IdentifierExpression>();
-  ASSERT_NE(count_expr, nullptr);
-  EXPECT_EQ(p->builder().Symbols().NameFor(count_expr->symbol), "size");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride) {
-  auto p = parser("@stride(16) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_FALSE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-
-  auto* size = a->count->As<ast::SintLiteralExpression>();
-  ASSERT_NE(size, nullptr);
-  EXPECT_EQ(size->ValueAsI32(), 5);
-
-  ASSERT_EQ(a->attributes.size(), 1u);
-  auto* stride = a->attributes[0];
-  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
-  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 16u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 13u}, {1u, 26u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Stride) {
-  auto p = parser("@stride(16) array<f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_TRUE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-
-  ASSERT_EQ(a->attributes.size(), 1u);
-  auto* stride = a->attributes[0];
-  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
-  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 16u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 13u}, {1u, 23u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MultipleAttributes_OneBlock) {
-  auto p = parser("@stride(16) @stride(32) array<f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_TRUE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-
-  auto& attrs = a->attributes;
-  ASSERT_EQ(attrs.size(), 2u);
-  EXPECT_TRUE(attrs[0]->Is<ast::StrideAttribute>());
-  EXPECT_EQ(attrs[0]->As<ast::StrideAttribute>()->stride, 16u);
-  EXPECT_TRUE(attrs[1]->Is<ast::StrideAttribute>());
-  EXPECT_EQ(attrs[1]->As<ast::StrideAttribute>()->stride, 32u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 25u}, {1u, 35u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MultipleAttributes_MultipleBlocks) {
-  auto p = parser("@stride(16) @stride(32) array<f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_TRUE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::F32>());
-
-  auto& attrs = a->attributes;
-  ASSERT_EQ(attrs.size(), 2u);
-  EXPECT_TRUE(attrs[0]->Is<ast::StrideAttribute>());
-  EXPECT_EQ(attrs[0]->As<ast::StrideAttribute>()->stride, 16u);
-  EXPECT_TRUE(attrs[1]->Is<ast::StrideAttribute>());
-  EXPECT_EQ(attrs[1]->As<ast::StrideAttribute>()->stride, 32u);
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 25u}, {1u, 35u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Attribute_MissingArray) {
-  auto p = parser("@stride(16) f32");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:2: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
-1:2: unexpected attributes)");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Attribute_UnknownAttribute) {
-  auto p = parser("@unknown(16) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), R"(1:2: expected attribute)");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingLeftParen) {
-  auto p = parser("@stride 4) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), R"(1:9: expected '(' for stride attribute)");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingRightParen) {
-  auto p = parser("@stride(4 array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:2: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
-1:11: expected ')' for stride attribute)");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingValue) {
-  auto p = parser("@stride() array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:9: expected signed integer literal for stride attribute");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue) {
-  auto p = parser("@stride(invalid) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:9: expected signed integer literal for stride attribute");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue_Negative) {
-  auto p = parser("@stride(-1) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: stride attribute must be greater than 0");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Attribute_MissingClosingAttr) {
-  auto p = parser("[[stride(16) array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
-1:14: expected ']]' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingLeftParen) {
-  auto p = parser("[[stride 4)]] array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: expected '(' for stride attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingRightParen) {
-  auto p = parser("[[stride(4]] array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
-1:11: expected ')' for stride attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingValue) {
-  auto p = parser("[[stride()]] array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: expected signed integer literal for stride attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_InvalidValue) {
-  auto p = parser("[[stride(invalid)]] array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: expected signed integer literal for stride attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_InvalidValue_Negative) {
-  auto p = parser("[[stride(-1)]] array<f32, 5>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:10: stride attribute must be greater than 0)");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
-  auto p = parser("array<u32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_TRUE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::U32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Vec) {
-  auto p = parser("array<vec4<u32>>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t.value->Is<ast::Array>());
-
-  auto* a = t.value->As<ast::Array>();
-  ASSERT_TRUE(a->IsRuntimeArray());
-  ASSERT_TRUE(a->type->Is<ast::Vector>());
-  EXPECT_EQ(a->type->As<ast::Vector>()->width, 4u);
-  EXPECT_TRUE(a->type->As<ast::Vector>()->type->Is<ast::U32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
-  auto p = parser("array<f32, !>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:12: expected array size expression");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MissingSize) {
-  auto p = parser("array<f32,>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: expected array size expression");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MissingLessThan) {
-  auto p = parser("array f32>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:7: expected '<' for array declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MissingGreaterThan) {
-  auto p = parser("array<f32");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:10: expected '>' for array declaration");
-}
-
-TEST_F(ParserImplTest, TypeDecl_Array_MissingComma) {
-  auto p = parser("array<f32 3>");
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: expected '>' for array declaration");
-}
-
-struct MatrixData {
-  const char* input;
-  size_t columns;
-  size_t rows;
-  Source::Range range;
-};
-inline std::ostream& operator<<(std::ostream& out, MatrixData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class MatrixTest : public ParserImplTestWithParam<MatrixData> {};
-
-TEST_P(MatrixTest, Parse) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_FALSE(p->has_error());
-  EXPECT_TRUE(t.value->Is<ast::Matrix>());
-  auto* mat = t.value->As<ast::Matrix>();
-  EXPECT_EQ(mat->rows, params.rows);
-  EXPECT_EQ(mat->columns, params.columns);
-  EXPECT_EQ(t.value->source.range, params.range);
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    MatrixTest,
-    testing::Values(MatrixData{"mat2x2<f32>", 2, 2, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat2x3<f32>", 2, 3, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat2x4<f32>", 2, 4, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat3x2<f32>", 3, 2, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat3x3<f32>", 3, 3, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat3x4<f32>", 3, 4, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat4x2<f32>", 4, 2, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat4x3<f32>", 4, 3, {{1u, 1u}, {1u, 12u}}},
-                    MatrixData{"mat4x4<f32>", 4, 4, {{1u, 1u}, {1u, 12u}}}));
-
-class MatrixMissingGreaterThanTest
-    : public ParserImplTestWithParam<MatrixData> {};
-
-TEST_P(MatrixMissingGreaterThanTest, Handles_Missing_GreaterThan) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: expected '>' for matrix");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         MatrixMissingGreaterThanTest,
-                         testing::Values(MatrixData{"mat2x2<f32", 2, 2, {}},
-                                         MatrixData{"mat2x3<f32", 2, 3, {}},
-                                         MatrixData{"mat2x4<f32", 2, 4, {}},
-                                         MatrixData{"mat3x2<f32", 3, 2, {}},
-                                         MatrixData{"mat3x3<f32", 3, 3, {}},
-                                         MatrixData{"mat3x4<f32", 3, 4, {}},
-                                         MatrixData{"mat4x2<f32", 4, 2, {}},
-                                         MatrixData{"mat4x3<f32", 4, 3, {}},
-                                         MatrixData{"mat4x4<f32", 4, 4, {}}));
-
-class MatrixMissingType : public ParserImplTestWithParam<MatrixData> {};
-
-TEST_P(MatrixMissingType, Handles_Missing_Type) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.errored);
-  EXPECT_FALSE(t.matched);
-  ASSERT_EQ(t.value, nullptr);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:8: invalid type for matrix");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         MatrixMissingType,
-                         testing::Values(MatrixData{"mat2x2<>", 2, 2, {}},
-                                         MatrixData{"mat2x3<>", 2, 3, {}},
-                                         MatrixData{"mat2x4<>", 2, 4, {}},
-                                         MatrixData{"mat3x2<>", 3, 2, {}},
-                                         MatrixData{"mat3x3<>", 3, 3, {}},
-                                         MatrixData{"mat3x4<>", 3, 4, {}},
-                                         MatrixData{"mat4x2<>", 4, 2, {}},
-                                         MatrixData{"mat4x3<>", 4, 3, {}},
-                                         MatrixData{"mat4x4<>", 4, 4, {}}));
-
-TEST_F(ParserImplTest, TypeDecl_Sampler) {
-  auto p = parser("sampler");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr) << p->error();
-  ASSERT_TRUE(t.value->Is<ast::Sampler>());
-  ASSERT_FALSE(t.value->As<ast::Sampler>()->IsComparison());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
-}
-
-TEST_F(ParserImplTest, TypeDecl_Texture) {
-  auto p = parser("texture_cube<f32>");
-
-  auto t = p->type_decl();
-  EXPECT_TRUE(t.matched);
-  EXPECT_FALSE(t.errored);
-  ASSERT_NE(t.value, nullptr);
-  ASSERT_TRUE(t.value->Is<ast::Texture>());
-  ASSERT_TRUE(t.value->Is<ast::SampledTexture>());
-  ASSERT_TRUE(t.value->As<ast::SampledTexture>()->type->Is<ast::F32>());
-  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_unary_expression_test.cc b/src/reader/wgsl/parser_impl_unary_expression_test.cc
deleted file mode 100644
index 8a3dd24..0000000
--- a/src/reader/wgsl/parser_impl_unary_expression_test.cc
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2020 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/ast/unary_op_expression.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, UnaryExpression_Postix) {
-  auto p = parser("a[2]");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-
-  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
-  auto* idx = e->As<ast::IndexAccessorExpression>();
-  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
-  auto* ident = idx->object->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-  ASSERT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Minus) {
-  auto p = parser("- 1");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  ASSERT_EQ(u->op, ast::UnaryOp::kNegation);
-
-  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
-}
-
-TEST_F(ParserImplTest, UnaryExpression_AddressOf) {
-  auto p = parser("&x");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  EXPECT_EQ(u->op, ast::UnaryOp::kAddressOf);
-  EXPECT_TRUE(u->expr->Is<ast::IdentifierExpression>());
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Dereference) {
-  auto p = parser("*x");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  EXPECT_EQ(u->op, ast::UnaryOp::kIndirection);
-  EXPECT_TRUE(u->expr->Is<ast::IdentifierExpression>());
-}
-
-TEST_F(ParserImplTest, UnaryExpression_AddressOf_Precedence) {
-  auto p = parser("&x.y");
-  auto e = p->logical_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  EXPECT_EQ(u->op, ast::UnaryOp::kAddressOf);
-  EXPECT_TRUE(u->expr->Is<ast::MemberAccessorExpression>());
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Dereference_Precedence) {
-  auto p = parser("*x.y");
-  auto e = p->logical_or_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  EXPECT_EQ(u->op, ast::UnaryOp::kIndirection);
-  EXPECT_TRUE(u->expr->Is<ast::MemberAccessorExpression>());
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Minus_InvalidRHS) {
-  auto p = parser("-if(a) {}");
-  auto e = p->unary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:2: unable to parse right side of - expression");
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Bang) {
-  auto p = parser("!1");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  ASSERT_EQ(u->op, ast::UnaryOp::kNot);
-
-  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Bang_InvalidRHS) {
-  auto p = parser("!if (a) {}");
-  auto e = p->unary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:2: unable to parse right side of ! expression");
-}
-
-TEST_F(ParserImplTest, UnaryExpression_Tilde) {
-  auto p = parser("~1");
-  auto e = p->unary_expression();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
-
-  auto* u = e->As<ast::UnaryOpExpression>();
-  ASSERT_EQ(u->op, ast::UnaryOp::kComplement);
-
-  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
-}
-
-TEST_F(ParserImplTest, UnaryExpression_PrefixPlusPlus) {
-  auto p = parser("++a");
-  auto e = p->unary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:1: prefix increment and decrement operators are reserved for a "
-            "future WGSL version");
-}
-
-TEST_F(ParserImplTest, UnaryExpression_PrefixMinusMinus) {
-  auto p = parser("--a");
-  auto e = p->unary_expression();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:1: prefix increment and decrement operators are reserved for a "
-            "future WGSL version");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_attribute_list_test.cc b/src/reader/wgsl/parser_impl_variable_attribute_list_test.cc
deleted file mode 100644
index 494502b..0000000
--- a/src/reader/wgsl/parser_impl_variable_attribute_list_test.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, AttributeList_Parses) {
-  auto p = parser(R"(@location(4) @builtin(position))");
-  auto attrs = p->attribute_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(attrs.errored);
-  ASSERT_TRUE(attrs.matched);
-  ASSERT_EQ(attrs.value.size(), 2u);
-
-  auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
-  auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
-  ASSERT_NE(attr_0, nullptr);
-  ASSERT_NE(attr_1, nullptr);
-
-  ASSERT_TRUE(attr_0->Is<ast::LocationAttribute>());
-  EXPECT_EQ(attr_0->As<ast::LocationAttribute>()->value, 4u);
-  ASSERT_TRUE(attr_1->Is<ast::BuiltinAttribute>());
-  EXPECT_EQ(attr_1->As<ast::BuiltinAttribute>()->builtin,
-            ast::Builtin::kPosition);
-}
-
-TEST_F(ParserImplTest, AttributeList_Invalid) {
-  auto p = parser(R"(@invalid)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(), R"(1:2: expected attribute)");
-}
-
-TEST_F(ParserImplTest, AttributeList_InvalidValue) {
-  auto p = parser("@builtin(invalid)");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(p->error(), "1:10: invalid value for builtin attribute");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_Empty) {
-  auto p = parser(R"([[]])");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: empty attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_Invalid) {
-  auto p = parser(R"([[invalid]])");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:3: expected attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_ExtraComma) {
-  auto p = parser(R"([[builtin(position), ]])");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:22: expected attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingComma) {
-  auto p = parser(R"([[binding(4) location(5)]])");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:14: expected ',' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_AttributeList_InvalidValue) {
-  auto p = parser("[[builtin(invalid)]]");
-  auto attrs = p->attribute_list();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(attrs.errored);
-  EXPECT_FALSE(attrs.matched);
-  EXPECT_TRUE(attrs.value.empty());
-  EXPECT_EQ(
-      p->error(),
-      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:11: invalid value for builtin attribute)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/reader/wgsl/parser_impl_variable_attribute_test.cc
deleted file mode 100644
index 3c0f4cc..0000000
--- a/src/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ /dev/null
@@ -1,417 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, Attribute_Location) {
-  auto p = parser("location(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::LocationAttribute>());
-
-  auto* loc = var_attr->As<ast::LocationAttribute>();
-  EXPECT_EQ(loc->value, 4u);
-}
-
-TEST_F(ParserImplTest, Attribute_Location_MissingLeftParen) {
-  auto p = parser("location 4)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: expected '(' for location attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Location_MissingRightParen) {
-  auto p = parser("location(4");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:11: expected ')' for location attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Location_MissingValue) {
-  auto p = parser("location()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:10: expected signed integer literal for location attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Location_MissingInvalid) {
-  auto p = parser("location(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:10: expected signed integer literal for location attribute");
-}
-
-struct BuiltinData {
-  const char* input;
-  ast::Builtin result;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class BuiltinTest : public ParserImplTestWithParam<BuiltinData> {};
-
-TEST_P(BuiltinTest, Attribute_Builtin) {
-  auto params = GetParam();
-  auto p = parser(std::string("builtin(") + params.input + ")");
-
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_TRUE(var_attr->Is<ast::BuiltinAttribute>());
-
-  auto* builtin = var_attr->As<ast::BuiltinAttribute>();
-  EXPECT_EQ(builtin->builtin, params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    BuiltinTest,
-    testing::Values(
-        BuiltinData{"position", ast::Builtin::kPosition},
-        BuiltinData{"vertex_index", ast::Builtin::kVertexIndex},
-        BuiltinData{"instance_index", ast::Builtin::kInstanceIndex},
-        BuiltinData{"front_facing", ast::Builtin::kFrontFacing},
-        BuiltinData{"frag_depth", ast::Builtin::kFragDepth},
-        BuiltinData{"local_invocation_id", ast::Builtin::kLocalInvocationId},
-        BuiltinData{"local_invocation_idx",
-                    ast::Builtin::kLocalInvocationIndex},
-        BuiltinData{"local_invocation_index",
-                    ast::Builtin::kLocalInvocationIndex},
-        BuiltinData{"global_invocation_id", ast::Builtin::kGlobalInvocationId},
-        BuiltinData{"workgroup_id", ast::Builtin::kWorkgroupId},
-        BuiltinData{"num_workgroups", ast::Builtin::kNumWorkgroups},
-        BuiltinData{"sample_index", ast::Builtin::kSampleIndex},
-        BuiltinData{"sample_mask", ast::Builtin::kSampleMask}));
-
-TEST_F(ParserImplTest, Attribute_Builtin_MissingLeftParen) {
-  auto p = parser("builtin position)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: expected '(' for builtin attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Builtin_MissingRightParen) {
-  auto p = parser("builtin(position");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:17: expected ')' for builtin attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Builtin_MissingValue) {
-  auto p = parser("builtin()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
-}
-
-TEST_F(ParserImplTest, Attribute_Builtin_InvalidValue) {
-  auto p = parser("builtin(other_thingy)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: invalid value for builtin attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Builtin_MissingInvalid) {
-  auto p = parser("builtin(3)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_Flat) {
-  auto p = parser("interpolate(flat)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
-
-  auto* interp = var_attr->As<ast::InterpolateAttribute>();
-  EXPECT_EQ(interp->type, ast::InterpolationType::kFlat);
-  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kNone);
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Center) {
-  auto p = parser("interpolate(perspective, center)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
-
-  auto* interp = var_attr->As<ast::InterpolateAttribute>();
-  EXPECT_EQ(interp->type, ast::InterpolationType::kPerspective);
-  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kCenter);
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Centroid) {
-  auto p = parser("interpolate(perspective, centroid)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
-
-  auto* interp = var_attr->As<ast::InterpolateAttribute>();
-  EXPECT_EQ(interp->type, ast::InterpolationType::kPerspective);
-  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kCentroid);
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_Linear_Sample) {
-  auto p = parser("interpolate(linear, sample)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
-
-  auto* interp = var_attr->As<ast::InterpolateAttribute>();
-  EXPECT_EQ(interp->type, ast::InterpolationType::kLinear);
-  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kSample);
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_MissingLeftParen) {
-  auto p = parser("interpolate flat)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: expected '(' for interpolate attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_MissingRightParen) {
-  auto p = parser("interpolate(flat");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:17: expected ')' for interpolate attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_MissingFirstValue) {
-  auto p = parser("interpolate()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_InvalidFirstValue) {
-  auto p = parser("interpolate(other_thingy)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_MissingSecondValue) {
-  auto p = parser("interpolate(perspective,)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:25: invalid interpolation sampling");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_InvalidSecondValue) {
-  auto p = parser("interpolate(perspective, nope)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:26: invalid interpolation sampling");
-}
-
-TEST_F(ParserImplTest, Attribute_Binding) {
-  auto p = parser("binding(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(var_attr->Is<ast::BindingAttribute>());
-
-  auto* binding = var_attr->As<ast::BindingAttribute>();
-  EXPECT_EQ(binding->value, 4u);
-}
-
-TEST_F(ParserImplTest, Attribute_Binding_MissingLeftParen) {
-  auto p = parser("binding 4)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: expected '(' for binding attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Binding_MissingRightParen) {
-  auto p = parser("binding(4");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: expected ')' for binding attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Binding_MissingValue) {
-  auto p = parser("binding()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:9: expected signed integer literal for binding attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Binding_MissingInvalid) {
-  auto p = parser("binding(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:9: expected signed integer literal for binding attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_group) {
-  auto p = parser("group(4)");
-  auto attr = p->attribute();
-  EXPECT_TRUE(attr.matched);
-  EXPECT_FALSE(attr.errored);
-  ASSERT_NE(attr.value, nullptr);
-  auto* var_attr = attr.value->As<ast::Attribute>();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(var_attr, nullptr);
-  ASSERT_TRUE(var_attr->Is<ast::GroupAttribute>());
-
-  auto* group = var_attr->As<ast::GroupAttribute>();
-  EXPECT_EQ(group->value, 4u);
-}
-
-TEST_F(ParserImplTest, Attribute_Group_MissingLeftParen) {
-  auto p = parser("group 2)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:7: expected '(' for group attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Group_MissingRightParen) {
-  auto p = parser("group(2");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: expected ')' for group attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Group_MissingValue) {
-  auto p = parser("group()");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for group attribute");
-}
-
-TEST_F(ParserImplTest, Attribute_Group_MissingInvalid) {
-  auto p = parser("group(nan)");
-  auto attr = p->attribute();
-  EXPECT_FALSE(attr.matched);
-  EXPECT_TRUE(attr.errored);
-  EXPECT_EQ(attr.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(),
-            "1:7: expected signed integer literal for group attribute");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_decl_test.cc b/src/reader/wgsl/parser_impl_variable_decl_test.cc
deleted file mode 100644
index 88a5873..0000000
--- a/src/reader/wgsl/parser_impl_variable_decl_test.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-TEST_F(ParserImplTest, VariableDecl_Parses) {
-  auto p = parser("var my_var : f32");
-  auto v = p->variable_decl();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_TRUE(v.matched);
-  EXPECT_FALSE(v.errored);
-  EXPECT_EQ(v->name, "my_var");
-  EXPECT_NE(v->type, nullptr);
-  EXPECT_TRUE(v->type->Is<ast::F32>());
-
-  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
-  EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
-}
-
-TEST_F(ParserImplTest, VariableDecl_Unicode_Parses) {
-  const std::string ident =  // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
-      "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
-      "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
-      "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33";
-
-  auto p = parser("var " + ident + " : f32");
-  auto v = p->variable_decl();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_TRUE(v.matched);
-  EXPECT_FALSE(v.errored);
-  EXPECT_EQ(v->name, ident);
-  EXPECT_NE(v->type, nullptr);
-  EXPECT_TRUE(v->type->Is<ast::F32>());
-
-  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 48u}}));
-  EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 51u}, {1u, 54u}}));
-}
-
-TEST_F(ParserImplTest, VariableDecl_Inferred_Parses) {
-  auto p = parser("var my_var = 1.0");
-  auto v = p->variable_decl(/*allow_inferred = */ true);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_TRUE(v.matched);
-  EXPECT_FALSE(v.errored);
-  EXPECT_EQ(v->name, "my_var");
-  EXPECT_EQ(v->type, nullptr);
-
-  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
-}
-
-TEST_F(ParserImplTest, VariableDecl_MissingVar) {
-  auto p = parser("my_var : f32");
-  auto v = p->variable_decl();
-  EXPECT_FALSE(v.matched);
-  EXPECT_FALSE(v.errored);
-  EXPECT_FALSE(p->has_error());
-
-  auto t = p->next();
-  ASSERT_TRUE(t.IsIdentifier());
-}
-
-TEST_F(ParserImplTest, VariableDecl_InvalidIdentDecl) {
-  auto p = parser("var my_var f32");
-  auto v = p->variable_decl();
-  EXPECT_FALSE(v.matched);
-  EXPECT_TRUE(v.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:12: expected ':' for variable declaration");
-}
-
-TEST_F(ParserImplTest, VariableDecl_WithStorageClass) {
-  auto p = parser("var<private> my_var : f32");
-  auto v = p->variable_decl();
-  EXPECT_TRUE(v.matched);
-  EXPECT_FALSE(v.errored);
-  EXPECT_FALSE(p->has_error());
-  EXPECT_EQ(v->name, "my_var");
-  EXPECT_TRUE(v->type->Is<ast::F32>());
-  EXPECT_EQ(v->storage_class, ast::StorageClass::kPrivate);
-
-  EXPECT_EQ(v->source.range.begin.line, 1u);
-  EXPECT_EQ(v->source.range.begin.column, 14u);
-  EXPECT_EQ(v->source.range.end.line, 1u);
-  EXPECT_EQ(v->source.range.end.column, 20u);
-}
-
-TEST_F(ParserImplTest, VariableDecl_InvalidStorageClass) {
-  auto p = parser("var<unknown> my_var : f32");
-  auto v = p->variable_decl();
-  EXPECT_FALSE(v.matched);
-  EXPECT_TRUE(v.errored);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:5: invalid storage class for variable declaration");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
deleted file mode 100644
index 94f5f23..0000000
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, VariableIdentDecl_Parses) {
-  auto p = parser("my_var : f32");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(decl.errored);
-  ASSERT_EQ(decl->name, "my_var");
-  ASSERT_NE(decl->type, nullptr);
-  ASSERT_TRUE(decl->type->Is<ast::F32>());
-
-  EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
-  EXPECT_EQ(decl->type->source.range, (Source::Range{{1u, 10u}, {1u, 13u}}));
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_Inferred_Parses) {
-  auto p = parser("my_var = 1.0");
-  auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_FALSE(decl.errored);
-  ASSERT_EQ(decl->name, "my_var");
-  ASSERT_EQ(decl->type, nullptr);
-
-  EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_MissingIdent) {
-  auto p = parser(": f32");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:1: expected identifier for test");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_MissingColon) {
-  auto p = parser("my_var f32");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:8: expected ':' for test");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_MissingType) {
-  auto p = parser("my_var :");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:9: invalid type for test");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_InvalidIdent) {
-  auto p = parser("123 : f32");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:1: expected identifier for test");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_NonAccessAttrFail) {
-  auto p = parser("my_var : @location(1) S");
-
-  auto* mem = Member("a", ty.i32(), ast::AttributeList{});
-  ast::StructMemberList members;
-  members.push_back(mem);
-
-  auto* block_attr = create<ast::StructBlockAttribute>();
-  ast::AttributeList attrs;
-  attrs.push_back(block_attr);
-
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:11: unexpected attributes");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_AttributeMissingRightParen) {
-  auto p = parser("my_var : @location(4 S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:22: expected ')' for location attribute");
-}
-
-TEST_F(ParserImplTest, VariableIdentDecl_AttributeMissingLeftParen) {
-  auto p = parser("my_var : @stride 4) S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(p->error(), "1:18: expected '(' for stride attribute");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest,
-       DEPRECATED_VariableIdentDecl_AttributeMissingRightBlock) {
-  auto p = parser("my_var : [[location(4) S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(
-      p->error(),
-      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:24: expected ']]' for attribute list)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest,
-       DEPRECATED_VariableIdentDecl_AttributeMissingRightParen) {
-  auto p = parser("my_var : [[location(4]] S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(
-      p->error(),
-      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:22: expected ')' for location attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_VariableIdentDecl_AttributeMissingLeftParen) {
-  auto p = parser("my_var : [[stride 4)]] S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(
-      p->error(),
-      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:19: expected '(' for stride attribute)");
-}
-
-// TODO(crbug.com/tint/1382): Remove
-TEST_F(ParserImplTest, DEPRECATED_VariableIdentDecl_AttributeEmpty) {
-  auto p = parser("my_var : [[]] S");
-  auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decl.errored);
-  ASSERT_EQ(
-      p->error(),
-      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
-1:12: empty attribute list)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_qualifier_test.cc b/src/reader/wgsl/parser_impl_variable_qualifier_test.cc
deleted file mode 100644
index db09bdc..0000000
--- a/src/reader/wgsl/parser_impl_variable_qualifier_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-struct VariableStorageData {
-  const char* input;
-  ast::StorageClass storage_class;
-  ast::Access access;
-};
-inline std::ostream& operator<<(std::ostream& out, VariableStorageData data) {
-  out << std::string(data.input);
-  return out;
-}
-
-class VariableQualifierTest
-    : public ParserImplTestWithParam<VariableStorageData> {};
-
-TEST_P(VariableQualifierTest, ParsesStorageClass) {
-  auto params = GetParam();
-  auto p = parser(std::string("<") + params.input + ">");
-
-  auto sc = p->variable_qualifier();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(sc.errored);
-  EXPECT_TRUE(sc.matched);
-  EXPECT_EQ(sc->storage_class, params.storage_class);
-  EXPECT_EQ(sc->access, params.access);
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsEof());
-}
-INSTANTIATE_TEST_SUITE_P(
-    ParserImplTest,
-    VariableQualifierTest,
-    testing::Values(
-        VariableStorageData{"uniform", ast::StorageClass::kUniform,
-                            ast::Access::kUndefined},
-        VariableStorageData{"workgroup", ast::StorageClass::kWorkgroup,
-                            ast::Access::kUndefined},
-        VariableStorageData{"storage", ast::StorageClass::kStorage,
-                            ast::Access::kUndefined},
-        VariableStorageData{"storage_buffer", ast::StorageClass::kStorage,
-                            ast::Access::kUndefined},
-        VariableStorageData{"private", ast::StorageClass::kPrivate,
-                            ast::Access::kUndefined},
-        VariableStorageData{"function", ast::StorageClass::kFunction,
-                            ast::Access::kUndefined},
-        VariableStorageData{"storage, read", ast::StorageClass::kStorage,
-                            ast::Access::kRead},
-        VariableStorageData{"storage, write", ast::StorageClass::kStorage,
-                            ast::Access::kWrite},
-        VariableStorageData{"storage, read_write", ast::StorageClass::kStorage,
-                            ast::Access::kReadWrite}));
-
-TEST_F(ParserImplTest, VariableQualifier_NoMatch) {
-  auto p = parser("<not-a-storage-class>");
-  auto sc = p->variable_qualifier();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(sc.errored);
-  EXPECT_FALSE(sc.matched);
-  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable declaration");
-}
-
-TEST_F(ParserImplTest, VariableQualifier_Empty) {
-  auto p = parser("<>");
-  auto sc = p->variable_qualifier();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(sc.errored);
-  EXPECT_FALSE(sc.matched);
-  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable declaration");
-}
-
-TEST_F(ParserImplTest, VariableQualifier_MissingLessThan) {
-  auto p = parser("private>");
-  auto sc = p->variable_qualifier();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(sc.errored);
-  EXPECT_FALSE(sc.matched);
-
-  auto t = p->next();
-  ASSERT_TRUE(t.Is(Token::Type::kPrivate));
-}
-
-TEST_F(ParserImplTest, VariableQualifier_MissingLessThan_AfterSC) {
-  auto p = parser("private, >");
-  auto sc = p->variable_qualifier();
-  EXPECT_FALSE(p->has_error());
-  EXPECT_FALSE(sc.errored);
-  EXPECT_FALSE(sc.matched);
-
-  auto t = p->next();
-  ASSERT_TRUE(t.Is(Token::Type::kPrivate));
-}
-
-TEST_F(ParserImplTest, VariableQualifier_MissingGreaterThan) {
-  auto p = parser("<private");
-  auto sc = p->variable_qualifier();
-  EXPECT_TRUE(p->has_error());
-  EXPECT_TRUE(sc.errored);
-  EXPECT_FALSE(sc.matched);
-  EXPECT_EQ(p->error(), "1:9: expected '>' for variable declaration");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
deleted file mode 100644
index 74f51f3..0000000
--- a/src/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl) {
-  auto p = parser("var a : i32;");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_EQ(e->source.range.begin.line, 1u);
-  ASSERT_EQ(e->source.range.begin.column, 5u);
-  ASSERT_EQ(e->source.range.end.line, 1u);
-  ASSERT_EQ(e->source.range.end.column, 6u);
-
-  EXPECT_EQ(e->variable->constructor, nullptr);
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_WithInit) {
-  auto p = parser("var a : i32 = 1;");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_EQ(e->source.range.begin.line, 1u);
-  ASSERT_EQ(e->source.range.begin.column, 5u);
-  ASSERT_EQ(e->source.range.end.line, 1u);
-  ASSERT_EQ(e->source.range.end.column, 6u);
-
-  ASSERT_NE(e->variable->constructor, nullptr);
-  EXPECT_TRUE(e->variable->constructor->Is<ast::LiteralExpression>());
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
-  auto p = parser("var a : i32 = if(a) {}");
-  auto e = p->variable_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:15: missing constructor for variable declaration");
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) {
-  auto p = parser("var a : array<i32> = array<i32>();");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_NE(e->variable->constructor, nullptr);
-  auto* call = e->variable->constructor->As<ast::CallExpression>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->target.name, nullptr);
-  EXPECT_NE(call->target.type, nullptr);
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit_NoSpace) {
-  auto p = parser("var a : array<i32>=array<i32>();");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_NE(e->variable->constructor, nullptr);
-  auto* call = e->variable->constructor->As<ast::CallExpression>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->target.name, nullptr);
-  EXPECT_NE(call->target.type, nullptr);
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit) {
-  auto p = parser("var a : vec2<i32> = vec2<i32>();");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_NE(e->variable->constructor, nullptr);
-  auto* call = e->variable->constructor->As<ast::CallExpression>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->target.name, nullptr);
-  EXPECT_NE(call->target.type, nullptr);
-}
-
-TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit_NoSpace) {
-  auto p = parser("var a : vec2<i32>=vec2<i32>();");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-  ASSERT_NE(e->variable, nullptr);
-  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
-
-  ASSERT_NE(e->variable->constructor, nullptr);
-  auto* call = e->variable->constructor->As<ast::CallExpression>();
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(call->target.name, nullptr);
-  EXPECT_NE(call->target.type, nullptr);
-}
-
-TEST_F(ParserImplTest, VariableStmt_Let) {
-  auto p = parser("let a : i32 = 1");
-  auto e = p->variable_stmt();
-  EXPECT_TRUE(e.matched);
-  EXPECT_FALSE(e.errored);
-  EXPECT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
-
-  ASSERT_EQ(e->source.range.begin.line, 1u);
-  ASSERT_EQ(e->source.range.begin.column, 5u);
-  ASSERT_EQ(e->source.range.end.line, 1u);
-  ASSERT_EQ(e->source.range.end.column, 6u);
-}
-
-TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
-  auto p = parser("let a : i32 1");
-  auto e = p->variable_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:13: expected '=' for let declaration");
-}
-
-TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) {
-  auto p = parser("let a : i32 =");
-  auto e = p->variable_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: missing constructor for let declaration");
-}
-
-TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
-  auto p = parser("let a : i32 = if (a) {}");
-  auto e = p->variable_stmt();
-  EXPECT_FALSE(e.matched);
-  EXPECT_TRUE(e.errored);
-  EXPECT_EQ(e.value, nullptr);
-  EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:15: missing constructor for let declaration");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/parser_test.cc b/src/reader/wgsl/parser_test.cc
deleted file mode 100644
index f6031dd..0000000
--- a/src/reader/wgsl/parser_test.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser.h"
-
-#include "gtest/gtest.h"
-
-#include "src/ast/module.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-using ParserTest = testing::Test;
-
-TEST_F(ParserTest, Empty) {
-  Source::File file("test.wgsl", "");
-  auto program = Parse(&file);
-  auto errs = diag::Formatter().format(program.Diagnostics());
-  ASSERT_TRUE(program.IsValid()) << errs;
-}
-
-TEST_F(ParserTest, Parses) {
-  Source::File file("test.wgsl", R"(
-@stage(fragment)
-fn main() -> @location(0) vec4<f32> {
-  return vec4<f32>(.4, .2, .3, 1.);
-}
-)");
-  auto program = Parse(&file);
-  auto errs = diag::Formatter().format(program.Diagnostics());
-  ASSERT_TRUE(program.IsValid()) << errs;
-
-  ASSERT_EQ(1u, program.AST().Functions().size());
-}
-
-TEST_F(ParserTest, HandlesError) {
-  Source::File file("test.wgsl", R"(
-fn main() ->  {  // missing return type
-  return;
-})");
-
-  auto program = Parse(&file);
-  auto errs = diag::Formatter().format(program.Diagnostics());
-  ASSERT_FALSE(program.IsValid()) << errs;
-  EXPECT_EQ(errs,
-            R"(test.wgsl:2:15 error: unable to determine function return type
-fn main() ->  {  // missing return type
-              ^
-
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
deleted file mode 100644
index c7531ba..0000000
--- a/src/reader/wgsl/token.cc
+++ /dev/null
@@ -1,328 +0,0 @@
-// Copyright 2020 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/reader/wgsl/token.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-// static
-std::string_view Token::TypeToName(Type type) {
-  switch (type) {
-    case Token::Type::kError:
-      return "kError";
-    case Token::Type::kEOF:
-      return "kEOF";
-    case Token::Type::kIdentifier:
-      return "kIdentifier";
-    case Token::Type::kFloatLiteral:
-      return "kFloatLiteral";
-    case Token::Type::kSintLiteral:
-      return "kSintLiteral";
-    case Token::Type::kUintLiteral:
-      return "kUintLiteral";
-    case Token::Type::kUninitialized:
-      return "kUninitialized";
-
-    case Token::Type::kAnd:
-      return "&";
-    case Token::Type::kAndAnd:
-      return "&&";
-    case Token::Type::kArrow:
-      return "->";
-    case Token::Type::kAttr:
-      return "@";
-    case Token::Type::kAttrLeft:
-      return "[[";
-    case Token::Type::kAttrRight:
-      return "]]";
-    case Token::Type::kForwardSlash:
-      return "/";
-    case Token::Type::kBang:
-      return "!";
-    case Token::Type::kBracketLeft:
-      return "[";
-    case Token::Type::kBracketRight:
-      return "]";
-    case Token::Type::kBraceLeft:
-      return "{";
-    case Token::Type::kBraceRight:
-      return "}";
-    case Token::Type::kColon:
-      return ":";
-    case Token::Type::kComma:
-      return ",";
-    case Token::Type::kEqual:
-      return "=";
-    case Token::Type::kEqualEqual:
-      return "==";
-    case Token::Type::kGreaterThan:
-      return ">";
-    case Token::Type::kGreaterThanEqual:
-      return ">=";
-    case Token::Type::kShiftRight:
-      return ">>";
-    case Token::Type::kLessThan:
-      return "<";
-    case Token::Type::kLessThanEqual:
-      return "<=";
-    case Token::Type::kShiftLeft:
-      return "<<";
-    case Token::Type::kMod:
-      return "%";
-    case Token::Type::kNotEqual:
-      return "!=";
-    case Token::Type::kMinus:
-      return "-";
-    case Token::Type::kMinusMinus:
-      return "--";
-    case Token::Type::kPeriod:
-      return ".";
-    case Token::Type::kPlus:
-      return "+";
-    case Token::Type::kPlusPlus:
-      return "++";
-    case Token::Type::kOr:
-      return "|";
-    case Token::Type::kOrOr:
-      return "||";
-    case Token::Type::kParenLeft:
-      return "(";
-    case Token::Type::kParenRight:
-      return ")";
-    case Token::Type::kSemicolon:
-      return ";";
-    case Token::Type::kStar:
-      return "*";
-    case Token::Type::kTilde:
-      return "~";
-    case Token::Type::kUnderscore:
-      return "_";
-    case Token::Type::kXor:
-      return "^";
-
-    case Token::Type::kArray:
-      return "array";
-    case Token::Type::kAtomic:
-      return "atomic";
-    case Token::Type::kBitcast:
-      return "bitcast";
-    case Token::Type::kBool:
-      return "bool";
-    case Token::Type::kBreak:
-      return "break";
-    case Token::Type::kCase:
-      return "case";
-    case Token::Type::kContinue:
-      return "continue";
-    case Token::Type::kContinuing:
-      return "continuing";
-    case Token::Type::kDiscard:
-      return "discard";
-    case Token::Type::kDefault:
-      return "default";
-    case Token::Type::kElse:
-      return "else";
-    case Token::Type::kElseIf:
-      return "elseif";
-    case Token::Type::kF32:
-      return "f32";
-    case Token::Type::kFallthrough:
-      return "fallthrough";
-    case Token::Type::kFalse:
-      return "false";
-    case Token::Type::kFn:
-      return "fn";
-    case Token::Type::kFor:
-      return "for";
-    case Token::Type::kFunction:
-      return "function";
-    case Token::Type::kI32:
-      return "i32";
-    case Token::Type::kIf:
-      return "if";
-    case Token::Type::kImport:
-      return "import";
-    case Token::Type::kLet:
-      return "let";
-    case Token::Type::kLoop:
-      return "loop";
-    case Token::Type::kMat2x2:
-      return "mat2x2";
-    case Token::Type::kMat2x3:
-      return "mat2x3";
-    case Token::Type::kMat2x4:
-      return "mat2x4";
-    case Token::Type::kMat3x2:
-      return "mat3x2";
-    case Token::Type::kMat3x3:
-      return "mat3x3";
-    case Token::Type::kMat3x4:
-      return "mat3x4";
-    case Token::Type::kMat4x2:
-      return "mat4x2";
-    case Token::Type::kMat4x3:
-      return "mat4x3";
-    case Token::Type::kMat4x4:
-      return "mat4x4";
-    case Token::Type::kOverride:
-      return "override";
-    case Token::Type::kPrivate:
-      return "private";
-    case Token::Type::kPtr:
-      return "ptr";
-    case Token::Type::kReturn:
-      return "return";
-    case Token::Type::kSampler:
-      return "sampler";
-    case Token::Type::kComparisonSampler:
-      return "sampler_comparison";
-    case Token::Type::kStorage:
-      return "storage";
-    case Token::Type::kStruct:
-      return "struct";
-    case Token::Type::kSwitch:
-      return "switch";
-    case Token::Type::kTextureDepth2d:
-      return "texture_depth_2d";
-    case Token::Type::kTextureDepth2dArray:
-      return "texture_depth_2d_array";
-    case Token::Type::kTextureDepthCube:
-      return "texture_depth_cube";
-    case Token::Type::kTextureDepthCubeArray:
-      return "texture_depth_cube_array";
-    case Token::Type::kTextureDepthMultisampled2d:
-      return "texture_depth_multisampled_2d";
-    case Token::Type::kTextureExternal:
-      return "texture_external";
-    case Token::Type::kTextureMultisampled2d:
-      return "texture_multisampled_2d";
-    case Token::Type::kTextureSampled1d:
-      return "texture_1d";
-    case Token::Type::kTextureSampled2d:
-      return "texture_2d";
-    case Token::Type::kTextureSampled2dArray:
-      return "texture_2d_array";
-    case Token::Type::kTextureSampled3d:
-      return "texture_3d";
-    case Token::Type::kTextureSampledCube:
-      return "texture_cube";
-    case Token::Type::kTextureSampledCubeArray:
-      return "texture_cube_array";
-    case Token::Type::kTextureStorage1d:
-      return "texture_storage_1d";
-    case Token::Type::kTextureStorage2d:
-      return "texture_storage_2d";
-    case Token::Type::kTextureStorage2dArray:
-      return "texture_storage_2d_array";
-    case Token::Type::kTextureStorage3d:
-      return "texture_storage_3d";
-    case Token::Type::kTrue:
-      return "true";
-    case Token::Type::kType:
-      return "type";
-    case Token::Type::kU32:
-      return "u32";
-    case Token::Type::kUniform:
-      return "uniform";
-    case Token::Type::kVar:
-      return "var";
-    case Token::Type::kVec2:
-      return "vec2";
-    case Token::Type::kVec3:
-      return "vec3";
-    case Token::Type::kVec4:
-      return "vec4";
-    case Token::Type::kWorkgroup:
-      return "workgroup";
-  }
-
-  return "<unknown>";
-}
-
-Token::Token() : type_(Type::kUninitialized) {}
-
-Token::Token(Type type, const Source& source, const std::string_view& view)
-    : type_(type), source_(source), value_(view) {}
-
-Token::Token(Type type, const Source& source, const std::string& str)
-    : type_(type), source_(source), value_(str) {}
-
-Token::Token(Type type, const Source& source, const char* str)
-    : type_(type), source_(source), value_(std::string_view(str)) {}
-
-Token::Token(const Source& source, uint32_t val)
-    : type_(Type::kUintLiteral), source_(source), value_(val) {}
-
-Token::Token(const Source& source, int32_t val)
-    : type_(Type::kSintLiteral), source_(source), value_(val) {}
-
-Token::Token(const Source& source, float val)
-    : type_(Type::kFloatLiteral), source_(source), value_(val) {}
-
-Token::Token(Type type, const Source& source) : type_(type), source_(source) {}
-
-Token::Token(Token&&) = default;
-
-Token::Token(const Token&) = default;
-
-Token::~Token() = default;
-
-Token& Token::operator=(const Token& rhs) = default;
-
-bool Token::operator==(std::string_view ident) {
-  if (type_ != Type::kIdentifier) {
-    return false;
-  }
-  if (auto* view = std::get_if<std::string_view>(&value_)) {
-    return *view == ident;
-  }
-  return std::get<std::string>(value_) == ident;
-}
-
-std::string Token::to_str() const {
-  switch (type_) {
-    case Type::kFloatLiteral:
-      return std::to_string(std::get<float>(value_));
-    case Type::kSintLiteral:
-      return std::to_string(std::get<int32_t>(value_));
-    case Type::kUintLiteral:
-      return std::to_string(std::get<uint32_t>(value_));
-    case Type::kIdentifier:
-    case Type::kError:
-      if (auto* view = std::get_if<std::string_view>(&value_)) {
-        return std::string(*view);
-      }
-      return std::get<std::string>(value_);
-    default:
-      return "";
-  }
-}
-
-float Token::to_f32() const {
-  return std::get<float>(value_);
-}
-
-uint32_t Token::to_u32() const {
-  return std::get<uint32_t>(value_);
-}
-
-int32_t Token::to_i32() const {
-  return std::get<int32_t>(value_);
-}
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
deleted file mode 100644
index 25f55f7..0000000
--- a/src/reader/wgsl/token.h
+++ /dev/null
@@ -1,412 +0,0 @@
-// Copyright 2020 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_READER_WGSL_TOKEN_H_
-#define SRC_READER_WGSL_TOKEN_H_
-
-#include <string>
-#include <string_view>
-#include <variant>  // NOLINT: cpplint doesn't recognise this
-
-#include "src/source.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-
-/// Stores tokens generated by the Lexer
-class Token {
- public:
-  /// The type of the parsed token
-  enum class Type {
-    /// Error result
-    kError = -2,
-    /// Uninitialized token
-    kUninitialized = 0,
-    /// End of input string reached
-    kEOF,
-
-    /// An identifier
-    kIdentifier,
-    /// A float value
-    kFloatLiteral,
-    /// An signed int value
-    kSintLiteral,
-    /// A unsigned int value
-    kUintLiteral,
-
-    /// A '&'
-    kAnd,
-    /// A '&&'
-    kAndAnd,
-    /// A '->'
-    kArrow,
-    /// A '@'
-    kAttr,
-    /// A '[[' - [DEPRECATED] now '@'
-    kAttrLeft,
-    /// A ']]' - [DEPRECATED] now '@'
-    kAttrRight,
-    /// A '/'
-    kForwardSlash,
-    /// A '!'
-    kBang,
-    /// A '['
-    kBracketLeft,
-    /// A ']'
-    kBracketRight,
-    /// A '{'
-    kBraceLeft,
-    /// A '}'
-    kBraceRight,
-    /// A ':'
-    kColon,
-    /// A ','
-    kComma,
-    /// A '='
-    kEqual,
-    /// A '=='
-    kEqualEqual,
-    /// A '>'
-    kGreaterThan,
-    /// A '>='
-    kGreaterThanEqual,
-    /// A '>>'
-    kShiftRight,
-    /// A '<'
-    kLessThan,
-    /// A '<='
-    kLessThanEqual,
-    /// A '<<'
-    kShiftLeft,
-    /// A '%'
-    kMod,
-    /// A '-'
-    kMinus,
-    /// A '--'
-    kMinusMinus,
-    /// A '!='
-    kNotEqual,
-    /// A '.'
-    kPeriod,
-    /// A '+'
-    kPlus,
-    /// A '++'
-    kPlusPlus,
-    /// A '|'
-    kOr,
-    /// A '||'
-    kOrOr,
-    /// A '('
-    kParenLeft,
-    /// A ')'
-    kParenRight,
-    /// A ';'
-    kSemicolon,
-    /// A '*'
-    kStar,
-    /// A '~'
-    kTilde,
-    /// A '_'
-    kUnderscore,
-    /// A '^'
-    kXor,
-
-    /// A 'array'
-    kArray,
-    /// A 'atomic'
-    kAtomic,
-    /// A 'bitcast'
-    kBitcast,
-    /// A 'bool'
-    kBool,
-    /// A 'break'
-    kBreak,
-    /// A 'case'
-    kCase,
-    /// A 'continue'
-    kContinue,
-    /// A 'continuing'
-    kContinuing,
-    /// A 'discard'
-    kDiscard,
-    /// A 'default'
-    kDefault,
-    /// A 'else'
-    kElse,
-    /// A 'elseif'
-    kElseIf,
-    /// A 'f32'
-    kF32,
-    /// A 'fallthrough'
-    kFallthrough,
-    /// A 'false'
-    kFalse,
-    /// A 'fn'
-    kFn,
-    // A 'for'
-    kFor,
-    /// A 'function'
-    kFunction,
-    /// A 'i32'
-    kI32,
-    /// A 'if'
-    kIf,
-    /// A 'import'
-    kImport,
-    /// A 'let'
-    kLet,
-    /// A 'loop'
-    kLoop,
-    /// A 'mat2x2'
-    kMat2x2,
-    /// A 'mat2x3'
-    kMat2x3,
-    /// A 'mat2x4'
-    kMat2x4,
-    /// A 'mat3x2'
-    kMat3x2,
-    /// A 'mat3x3'
-    kMat3x3,
-    /// A 'mat3x4'
-    kMat3x4,
-    /// A 'mat4x2'
-    kMat4x2,
-    /// A 'mat4x3'
-    kMat4x3,
-    /// A 'mat4x4'
-    kMat4x4,
-    /// A 'override'
-    kOverride,
-    /// A 'private'
-    kPrivate,
-    /// A 'ptr'
-    kPtr,
-    /// A 'return'
-    kReturn,
-    /// A 'sampler'
-    kSampler,
-    /// A 'sampler_comparison'
-    kComparisonSampler,
-    /// A 'storage'
-    kStorage,
-    /// A 'struct'
-    kStruct,
-    /// A 'switch'
-    kSwitch,
-    /// A 'texture_depth_2d'
-    kTextureDepth2d,
-    /// A 'texture_depth_2d_array'
-    kTextureDepth2dArray,
-    /// A 'texture_depth_cube'
-    kTextureDepthCube,
-    /// A 'texture_depth_cube_array'
-    kTextureDepthCubeArray,
-    /// A 'texture_depth_multisampled_2d'
-    kTextureDepthMultisampled2d,
-    /// A 'texture_external'
-    kTextureExternal,
-    /// A 'texture_multisampled_2d'
-    kTextureMultisampled2d,
-    /// A 'texture_1d'
-    kTextureSampled1d,
-    /// A 'texture_2d'
-    kTextureSampled2d,
-    /// A 'texture_2d_array'
-    kTextureSampled2dArray,
-    /// A 'texture_3d'
-    kTextureSampled3d,
-    /// A 'texture_cube'
-    kTextureSampledCube,
-    /// A 'texture_cube_array'
-    kTextureSampledCubeArray,
-    /// A 'texture_storage_1d'
-    kTextureStorage1d,
-    /// A 'texture_storage_2d'
-    kTextureStorage2d,
-    /// A 'texture_storage_2d_array'
-    kTextureStorage2dArray,
-    /// A 'texture_storage_3d'
-    kTextureStorage3d,
-    /// A 'true'
-    kTrue,
-    /// A 'type'
-    kType,
-    /// A 'u32'
-    kU32,
-    /// A 'uniform'
-    kUniform,
-    /// A 'var'
-    kVar,
-    /// A 'vec2'
-    kVec2,
-    /// A 'vec3'
-    kVec3,
-    /// A 'vec4'
-    kVec4,
-    /// A 'workgroup'
-    kWorkgroup,
-  };
-
-  /// Converts a token type to a name
-  /// @param type the type to convert
-  /// @returns the token type as as string
-  static std::string_view TypeToName(Type type);
-
-  /// Creates an uninitialized token
-  Token();
-  /// Create a Token
-  /// @param type the Token::Type of the token
-  /// @param source the source of the token
-  Token(Type type, const Source& source);
-
-  /// Create a string Token
-  /// @param type the Token::Type of the token
-  /// @param source the source of the token
-  /// @param view the source string view for the token
-  Token(Type type, const Source& source, const std::string_view& view);
-  /// Create a string Token
-  /// @param type the Token::Type of the token
-  /// @param source the source of the token
-  /// @param str the source string for the token
-  Token(Type type, const Source& source, const std::string& str);
-  /// Create a string Token
-  /// @param type the Token::Type of the token
-  /// @param source the source of the token
-  /// @param str the source string for the token
-  Token(Type type, const Source& source, const char* str);
-  /// Create a unsigned integer Token
-  /// @param source the source of the token
-  /// @param val the source unsigned for the token
-  Token(const Source& source, uint32_t val);
-  /// Create a signed integer Token
-  /// @param source the source of the token
-  /// @param val the source integer for the token
-  Token(const Source& source, int32_t val);
-  /// Create a float Token
-  /// @param source the source of the token
-  /// @param val the source float for the token
-  Token(const Source& source, float val);
-  /// Move constructor
-  Token(Token&&);
-  /// Copy constructor
-  Token(const Token&);
-  ~Token();
-
-  /// Assignment operator
-  /// @param b the token to copy
-  /// @return Token
-  Token& operator=(const Token& b);
-
-  /// Equality operator with an identifier
-  /// @param ident the identifier string
-  /// @return true if this token is an identifier and is equal to ident.
-  bool operator==(std::string_view ident);
-
-  /// Returns true if the token is of the given type
-  /// @param t the type to check against.
-  /// @returns true if the token is of type `t`
-  bool Is(Type t) const { return type_ == t; }
-
-  /// @returns true if the token is uninitialized
-  bool IsUninitialized() const { return type_ == Type::kUninitialized; }
-  /// @returns true if the token is EOF
-  bool IsEof() const { return type_ == Type::kEOF; }
-  /// @returns true if the token is Error
-  bool IsError() const { return type_ == Type::kError; }
-  /// @returns true if the token is an identifier
-  bool IsIdentifier() const { return type_ == Type::kIdentifier; }
-  /// @returns true if the token is a literal
-  bool IsLiteral() const {
-    return type_ == Type::kSintLiteral || type_ == Type::kFalse ||
-           type_ == Type::kUintLiteral || type_ == Type::kTrue ||
-           type_ == Type::kFloatLiteral;
-  }
-  /// @returns true if token is a 'matNxM'
-  bool IsMatrix() const {
-    return type_ == Type::kMat2x2 || type_ == Type::kMat2x3 ||
-           type_ == Type::kMat2x4 || type_ == Type::kMat3x2 ||
-           type_ == Type::kMat3x3 || type_ == Type::kMat3x4 ||
-           type_ == Type::kMat4x2 || type_ == Type::kMat4x3 ||
-           type_ == Type::kMat4x4;
-  }
-  /// @returns true if token is a 'mat3xM'
-  bool IsMat3xN() const {
-    return type_ == Type::kMat3x2 || type_ == Type::kMat3x3 ||
-           type_ == Type::kMat3x4;
-  }
-  /// @returns true if token is a 'mat4xM'
-  bool IsMat4xN() const {
-    return type_ == Type::kMat4x2 || type_ == Type::kMat4x3 ||
-           type_ == Type::kMat4x4;
-  }
-  /// @returns true if token is a 'matNx3'
-  bool IsMatNx3() const {
-    return type_ == Type::kMat2x3 || type_ == Type::kMat3x3 ||
-           type_ == Type::kMat4x3;
-  }
-  /// @returns true if token is a 'matNx4'
-  bool IsMatNx4() const {
-    return type_ == Type::kMat2x4 || type_ == Type::kMat3x4 ||
-           type_ == Type::kMat4x4;
-  }
-
-  /// @returns true if token is a 'vecN'
-  bool IsVector() const {
-    return type_ == Type::kVec2 || type_ == Type::kVec3 || type_ == Type::kVec4;
-  }
-
-  /// @returns the source information for this token
-  Source source() const { return source_; }
-
-  /// Returns the string value of the token
-  /// @return std::string
-  std::string to_str() const;
-  /// Returns the float value of the token. 0 is returned if the token does not
-  /// contain a float value.
-  /// @return float
-  float to_f32() const;
-  /// Returns the uint32 value of the token. 0 is returned if the token does not
-  /// contain a unsigned integer value.
-  /// @return uint32_t
-  uint32_t to_u32() const;
-  /// Returns the int32 value of the token. 0 is returned if the token does not
-  /// contain a signed integer value.
-  /// @return int32_t
-  int32_t to_i32() const;
-
-  /// @returns the token type as string
-  std::string_view to_name() const { return Token::TypeToName(type_); }
-
- private:
-  /// The Token::Type of the token
-  Type type_ = Type::kError;
-  /// The source where the token appeared
-  Source source_;
-  /// The value represented by the token
-  std::variant<int32_t, uint32_t, float, std::string, std::string_view> value_;
-};
-
-#ifndef NDEBUG
-inline std::ostream& operator<<(std::ostream& out, Token::Type type) {
-  out << Token::TypeToName(type);
-  return out;
-}
-#endif  // NDEBUG
-
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
-
-#endif  // SRC_READER_WGSL_TOKEN_H_
diff --git a/src/reader/wgsl/token_test.cc b/src/reader/wgsl/token_test.cc
deleted file mode 100644
index d67a547..0000000
--- a/src/reader/wgsl/token_test.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2020 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/reader/wgsl/token.h"
-
-#include <limits>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace reader {
-namespace wgsl {
-namespace {
-
-using TokenTest = testing::Test;
-
-TEST_F(TokenTest, ReturnsF32) {
-  Token t1(Source{}, -2.345f);
-  EXPECT_EQ(t1.to_f32(), -2.345f);
-
-  Token t2(Source{}, 2.345f);
-  EXPECT_EQ(t2.to_f32(), 2.345f);
-}
-
-TEST_F(TokenTest, ReturnsI32) {
-  Token t1(Source{}, -2345);
-  EXPECT_EQ(t1.to_i32(), -2345);
-
-  Token t2(Source{}, 2345);
-  EXPECT_EQ(t2.to_i32(), 2345);
-}
-
-TEST_F(TokenTest, HandlesMaxI32) {
-  Token t1(Source{}, std::numeric_limits<int32_t>::max());
-  EXPECT_EQ(t1.to_i32(), std::numeric_limits<int32_t>::max());
-}
-
-TEST_F(TokenTest, HandlesMinI32) {
-  Token t1(Source{}, std::numeric_limits<int32_t>::min());
-  EXPECT_EQ(t1.to_i32(), std::numeric_limits<int32_t>::min());
-}
-
-TEST_F(TokenTest, ReturnsU32) {
-  Token t2(Source{}, 2345u);
-  EXPECT_EQ(t2.to_u32(), 2345u);
-}
-
-TEST_F(TokenTest, ReturnsMaxU32) {
-  Token t1(Source{}, std::numeric_limits<uint32_t>::max());
-  EXPECT_EQ(t1.to_u32(), std::numeric_limits<uint32_t>::max());
-}
-
-TEST_F(TokenTest, Source) {
-  Source src;
-  src.range.begin = Source::Location{3, 9};
-  src.range.end = Source::Location{4, 3};
-
-  Token t(Token::Type::kUintLiteral, src);
-  EXPECT_EQ(t.source().range.begin.line, 3u);
-  EXPECT_EQ(t.source().range.begin.column, 9u);
-  EXPECT_EQ(t.source().range.end.line, 4u);
-  EXPECT_EQ(t.source().range.end.column, 3u);
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace reader
-}  // namespace tint
diff --git a/src/resolver/array_accessor_test.cc b/src/resolver/array_accessor_test.cc
deleted file mode 100644
index 746fd16..0000000
--- a/src/resolver/array_accessor_test.cc
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/reference_type.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverIndexAccessorTest = ResolverTest;
-
-TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_F32) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 1.0f));
-  WrapInFunction(acc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_Ref) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
-  auto* acc = IndexAccessor("my_var", idx);
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions_Dynamic_Ref) {
-  Global("my_var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
-  auto* idx = Var("idx", ty.u32(), Expr(3u));
-  auto* idy = Var("idy", ty.u32(), Expr(2u));
-  auto* acc = IndexAccessor(IndexAccessor("my_var", idx), idy);
-  WrapInFunction(Decl(idx), Decl(idy), acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic) {
-  GlobalConst("my_const", ty.mat2x3<f32>(), Construct(ty.mat2x3<f32>()));
-  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
-  auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "");
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_XDimension_Dynamic) {
-  GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
-  auto* idx = Var("idx", ty.u32(), Expr(3u));
-  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "");
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_BothDimension_Dynamic) {
-  GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
-  auto* idx = Var("idy", ty.u32(), Expr(2u));
-  auto* acc =
-      IndexAccessor(IndexAccessor("my_var", Expr(Source{{12, 34}}, idx)), 1);
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "");
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* acc = IndexAccessor("my_var", 2);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(acc)->As<sem::Reference>();
-  ASSERT_TRUE(ref->StoreType()->Is<sem::Vector>());
-  EXPECT_EQ(ref->StoreType()->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* acc = IndexAccessor(IndexAccessor("my_var", 2), 1);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(acc)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-}
-
-TEST_F(ResolverIndexAccessorTest, Vector_F32) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 2.0f));
-  WrapInFunction(acc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
-}
-
-TEST_F(ResolverIndexAccessorTest, Vector_Dynamic_Ref) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* idx = Var("idx", ty.i32(), Expr(2));
-  auto* acc = IndexAccessor("my_var", idx);
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverIndexAccessorTest, Vector_Dynamic) {
-  GlobalConst("my_var", ty.vec3<f32>(), Construct(ty.vec3<f32>()));
-  auto* idx = Var("idx", ty.i32(), Expr(2));
-  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
-  WrapInFunction(Decl(idx), acc);
-
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverIndexAccessorTest, Vector) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* acc = IndexAccessor("my_var", 2);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(acc)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-}
-
-TEST_F(ResolverIndexAccessorTest, Array) {
-  auto* idx = Expr(2);
-  Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
-
-  auto* acc = IndexAccessor("my_var", idx);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(acc)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-}
-
-TEST_F(ResolverIndexAccessorTest, Alias_Array) {
-  auto* aary = Alias("myarrty", ty.array<f32, 3>());
-
-  Global("my_var", ty.Of(aary), ast::StorageClass::kPrivate);
-
-  auto* acc = IndexAccessor("my_var", 2);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(acc)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-}
-
-TEST_F(ResolverIndexAccessorTest, Array_Constant) {
-  GlobalConst("my_var", ty.array<f32, 3>(), array<f32, 3>());
-
-  auto* acc = IndexAccessor("my_var", 2);
-  WrapInFunction(acc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(acc), nullptr);
-  EXPECT_TRUE(TypeOf(acc)->Is<sem::F32>()) << TypeOf(acc)->type_name();
-}
-
-TEST_F(ResolverIndexAccessorTest, Array_Dynamic_I32) {
-  // let a : array<f32, 3> = 0;
-  // var idx : i32 = 0;
-  // var f : f32 = a[idx];
-  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
-  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
-  auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(idx),
-           Decl(f),
-       },
-       ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "");
-}
-
-TEST_F(ResolverIndexAccessorTest, Array_Literal_F32) {
-  // let a : array<f32, 3>;
-  // var f : f32 = a[2.0f];
-  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
-  auto* f =
-      Var("a_2", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, 2.0f)));
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(f),
-       },
-       ast::AttributeList{});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
-}
-
-TEST_F(ResolverIndexAccessorTest, Array_Literal_I32) {
-  // let a : array<f32, 3>;
-  // var f : f32 = a[2];
-  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
-  auto* f = Var("a_2", ty.f32(), IndexAccessor("a", 2));
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(f),
-       },
-       ast::AttributeList{});
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncGoodParent) {
-  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
-  //     let idx: u32 = u32();
-  //     let x: f32 = (*p)[idx];
-  //     return x;
-  // }
-  auto* p =
-      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
-  auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
-  auto* star_p = Deref(p);
-  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, star_p, idx);
-  auto* x = Var("x", ty.f32(), accessor_expr);
-  Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncBadParent) {
-  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
-  //     let idx: u32 = u32();
-  //     let x: f32 = *p[idx];
-  //     return x;
-  // }
-  auto* p =
-      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
-  auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
-  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, p, idx);
-  auto* star_p = Deref(accessor_expr);
-  auto* x = Var("x", ty.f32(), star_p);
-  Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
-}
-
-TEST_F(ResolverIndexAccessorTest, Exr_Deref_BadParent) {
-  // var param: vec4<f32>
-  // let x: f32 = *(&param)[0];
-  auto* param = Var("param", ty.vec4<f32>());
-  auto* idx = Var("idx", ty.u32(), Construct(ty.u32()));
-  auto* addressOf_expr = AddressOf(param);
-  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, addressOf_expr, idx);
-  auto* star_p = Deref(accessor_expr);
-  auto* x = Var("x", ty.f32(), star_p);
-  WrapInFunction(param, idx, x);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
deleted file mode 100644
index d2020ba..0000000
--- a/src/resolver/assignment_validation_test.cc
+++ /dev/null
@@ -1,403 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverAssignmentValidationTest = ResolverTest;
-
-TEST_F(ResolverAssignmentValidationTest, ReadOnlyBuffer) {
-  // [[block]] struct S { m : i32 };
-  // @group(0) @binding(0)
-  // var<storage,read> a : S;
-  auto* s = Structure("S", {Member("m", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("a", "m"), 1));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: cannot store into a read-only type 'ref<storage, "
-            "i32, read>'");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignIncompatibleTypes) {
-  // {
-  //  var a : i32 = 2;
-  //  a = 2.3;
-  // }
-
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-
-  auto* assign = Assign(Source{{12, 34}}, "a", 2.3f);
-  WrapInFunction(var, assign);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignArraysWithDifferentSizeExpressions_Pass) {
-  // let len = 4u;
-  // {
-  //   var a : array<f32, 4>;
-  //   var b : array<f32, len>;
-  //   a = b;
-  // }
-
-  GlobalConst("len", nullptr, Expr(4u));
-
-  auto* a = Var("a", ty.array(ty.f32(), 4));
-  auto* b = Var("b", ty.array(ty.f32(), "len"));
-
-  auto* assign = Assign(Source{{12, 34}}, "a", "b");
-  WrapInFunction(a, b, assign);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignArraysWithDifferentSizeExpressions_Fail) {
-  // let len = 5u;
-  // {
-  //   var a : array<f32, 4>;
-  //   var b : array<f32, len>;
-  //   a = b;
-  // }
-
-  GlobalConst("len", nullptr, Expr(5u));
-
-  auto* a = Var("a", ty.array(ty.f32(), 4));
-  auto* b = Var("b", ty.array(ty.f32(), "len"));
-
-  auto* assign = Assign(Source{{12, 34}}, "a", "b");
-  WrapInFunction(a, b, assign);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot assign 'array<f32, 5>' to 'array<f32, 4>'");
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignCompatibleTypesInBlockStatement_Pass) {
-  // {
-  //  var a : i32 = 2;
-  //  a = 2
-  // }
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  WrapInFunction(var, Assign("a", 2));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignIncompatibleTypesInBlockStatement_Fail) {
-  // {
-  //  var a : i32 = 2;
-  //  a = 2.3;
-  // }
-
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2.3f));
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignIncompatibleTypesInNestedBlockStatement_Fail) {
-  // {
-  //  {
-  //   var a : i32 = 2;
-  //   a = 2.3;
-  //  }
-  // }
-
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  auto* inner_block = Block(Decl(var), Assign(Source{{12, 34}}, "a", 2.3f));
-  auto* outer_block = Block(inner_block);
-  WrapInFunction(outer_block);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignToScalar_Fail) {
-  // var my_var : i32 = 2;
-  // 1 = my_var;
-
-  auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  WrapInFunction(var, Assign(Expr(Source{{12, 34}}, 1), "my_var"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: cannot assign to value of type 'i32'");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
-  // var a : i32 = 2;
-  // a = 2
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignCompatibleTypesThroughAlias_Pass) {
-  // alias myint = i32;
-  // var a : myint = 2;
-  // a = 2
-  auto* myint = Alias("myint", ty.i32());
-  auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2));
-  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignCompatibleTypesInferRHSLoad_Pass) {
-  // var a : i32 = 2;
-  // var b : i32 = 3;
-  // a = b;
-  auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
-  WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, "a", "b"));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignThroughPointer_Pass) {
-  // var a : i32;
-  // let b : ptr<function,i32> = &a;
-  // *b = 2;
-  const auto func = ast::StorageClass::kFunction;
-  auto* var_a = Var("a", ty.i32(), func, Expr(2));
-  auto* var_b = Const("b", ty.pointer<int>(func), AddressOf(Expr("a")));
-  WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, Deref("b"), 2));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
-  // {
-  //  let a : i32 = 2;
-  //  a = 2
-  // }
-  auto* var = Const("a", ty.i32(), Expr(2));
-  WrapInFunction(var, Assign(Expr(Source{{12, 34}}, "a"), 2));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot assign to const\nnote: 'a' is declared here:");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) {
-  // var a : texture_storage_1d<rgba8unorm, write>;
-  // var b : texture_storage_1d<rgba8unorm, write>;
-  // a = b;
-
-  auto make_type = [&] {
-    return ty.storage_texture(ast::TextureDimension::k1d,
-                              ast::TexelFormat::kRgba8Unorm,
-                              ast::Access::kWrite);
-  };
-
-  Global("a", make_type(), ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-  Global("b", make_type(), ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(0),
-         });
-
-  WrapInFunction(Assign(Source{{56, 78}}, "a", "b"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: storage type of assignment must be constructible");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Atomic) {
-  // [[block]] struct S { a : atomic<i32>; };
-  // @group(0) @binding(0) var<storage, read_write> v : S;
-  // v.a = v.a;
-
-  auto* s = Structure("S", {Member("a", ty.atomic(ty.i32()))},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
-                        MemberAccessor("v", "a")));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: storage type of assignment must be constructible");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_RuntimeArray) {
-  // [[block]] struct S { a : array<f32>; };
-  // @group(0) @binding(0) var<storage, read_write> v : S;
-  // v.a = v.a;
-
-  auto* s = Structure("S", {Member("a", ty.array(ty.f32()))},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
-                        MemberAccessor("v", "a")));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: storage type of assignment must be constructible");
-}
-
-TEST_F(ResolverAssignmentValidationTest,
-       AssignToPhony_NonConstructibleStruct_Fail) {
-  // [[block]]
-  // struct S {
-  //   arr: array<i32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  // fn f() {
-  //   _ = s;
-  // }
-  auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
-  Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
-
-  WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot assign 'S' to '_'. "
-            "'_' can only be assigned a constructible, pointer, texture or "
-            "sampler type");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignToPhony_DynamicArray_Fail) {
-  // [[block]]
-  // struct S {
-  //   arr: array<i32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  // fn f() {
-  //   _ = s.arr;
-  // }
-  auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
-  Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
-
-  WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: cannot assign 'array<i32>' to '_'. "
-      "'_' can only be assigned a constructible, pointer, texture or sampler "
-      "type");
-}
-
-TEST_F(ResolverAssignmentValidationTest, AssignToPhony_Pass) {
-  // [[block]]
-  // struct S {
-  //   i:   i32;
-  //   arr: array<i32>;
-  // };
-  // [[block]]
-  // struct U {
-  //   i:   i32;
-  // };
-  // @group(0) @binding(0) var tex texture_2d;
-  // @group(0) @binding(1) var smp sampler;
-  // @group(0) @binding(2) var<uniform> u : U;
-  // @group(0) @binding(3) var<storage, read_write> s : S;
-  // var<workgroup> wg : array<f32, 10>
-  // fn f() {
-  //   _ = 1;
-  //   _ = 2u;
-  //   _ = 3.0;
-  //   _ = vec2<bool>();
-  //   _ = tex;
-  //   _ = smp;
-  //   _ = &s;
-  //   _ = s.i;
-  //   _ = &s.arr;
-  //   _ = u;
-  //   _ = u.i;
-  //   _ = wg;
-  //   _ = wg[3];
-  // }
-  auto* S = Structure("S",
-                      {
-                          Member("i", ty.i32()),
-                          Member("arr", ty.array<i32>()),
-                      },
-                      {StructBlock()});
-  auto* U = Structure("U", {Member("i", ty.i32())}, {StructBlock()});
-  Global("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(0, 0));
-  Global("smp", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 1));
-  Global("u", ty.Of(U), ast::StorageClass::kUniform, GroupAndBinding(0, 2));
-  Global("s", ty.Of(S), ast::StorageClass::kStorage, GroupAndBinding(0, 3));
-  Global("wg", ty.array<f32, 10>(), ast::StorageClass::kWorkgroup);
-
-  WrapInFunction(Assign(Phony(), 1),                                      //
-                 Assign(Phony(), 2),                                      //
-                 Assign(Phony(), 3),                                      //
-                 Assign(Phony(), vec2<bool>()),                           //
-                 Assign(Phony(), "tex"),                                  //
-                 Assign(Phony(), "smp"),                                  //
-                 Assign(Phony(), AddressOf("s")),                         //
-                 Assign(Phony(), MemberAccessor("s", "i")),               //
-                 Assign(Phony(), AddressOf(MemberAccessor("s", "arr"))),  //
-                 Assign(Phony(), "u"),                                    //
-                 Assign(Phony(), MemberAccessor("u", "i")),               //
-                 Assign(Phony(), "wg"),                                   //
-                 Assign(Phony(), IndexAccessor("wg", 3)));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/atomics_test.cc b/src/resolver/atomics_test.cc
deleted file mode 100644
index 61b85dd..0000000
--- a/src/resolver/atomics_test.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/reference_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverAtomicTest : public resolver::TestHelper,
-                            public testing::Test {};
-
-TEST_F(ResolverAtomicTest, GlobalWorkgroupI32) {
-  auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
-                   ast::StorageClass::kWorkgroup);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
-  auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
-  ASSERT_NE(atomic, nullptr);
-  EXPECT_TRUE(atomic->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverAtomicTest, GlobalWorkgroupU32) {
-  auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.u32()),
-                   ast::StorageClass::kWorkgroup);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
-  auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
-  ASSERT_NE(atomic, nullptr);
-  EXPECT_TRUE(atomic->Type()->Is<sem::U32>());
-}
-
-TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
-  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
-                      {create<ast::StructBlockAttribute>()});
-  auto* g = Global("g", ty.Of(s), ast::StorageClass::kStorage,
-                   ast::Access::kReadWrite,
-                   ast::AttributeList{
-                       create<ast::BindingAttribute>(0),
-                       create<ast::GroupAttribute>(0),
-                   });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
-  auto* str = TypeOf(g)->UnwrapRef()->As<sem::Struct>();
-  ASSERT_NE(str, nullptr);
-  ASSERT_EQ(str->Members().size(), 1u);
-  auto* atomic = str->Members()[0]->Type()->As<sem::Atomic>();
-  ASSERT_NE(atomic, nullptr);
-  ASSERT_TRUE(atomic->Type()->Is<sem::I32>());
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/atomics_validation_test.cc b/src/resolver/atomics_validation_test.cc
deleted file mode 100644
index c5a1597..0000000
--- a/src/resolver/atomics_validation_test.cc
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/reference_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverAtomicValidationTest : public resolver::TestHelper,
-                                      public testing::Test {};
-
-TEST_F(ResolverAtomicValidationTest, StorageClass_WorkGroup) {
-  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
-         ast::StorageClass::kWorkgroup);
-
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverAtomicValidationTest, StorageClass_Storage) {
-  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
-                      {StructBlock()});
-  Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         GroupAndBinding(0, 0));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidType) {
-  Global("a", ty.atomic(ty.f32(Source{{12, 34}})),
-         ast::StorageClass::kWorkgroup);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: atomic only supports i32 or u32 types");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Simple) {
-  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: atomic variables must have <storage> or <workgroup> "
-            "storage class");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Array) {
-  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: atomic variables must have <storage> or <workgroup> "
-            "storage class");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Struct) {
-  auto* s =
-      Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "note: atomic sub-type of 's' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_StructOfStruct) {
-  // struct Inner { m : atomic<i32>; };
-  // struct Outer { m : array<Inner, 4>; };
-  // var<private> g : Outer;
-
-  auto* Inner =
-      Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
-  auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
-  Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "note: atomic sub-type of 'Outer' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest,
-       InvalidStorageClass_StructOfStructOfArray) {
-  // struct Inner { m : array<atomic<i32>, 4>; };
-  // struct Outer { m : array<Inner, 4>; };
-  // var<private> g : Outer;
-
-  auto* Inner =
-      Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
-  auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
-  Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "12:34 note: atomic sub-type of 'Outer' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfArray) {
-  // type AtomicArray = array<atomic<i32>, 5>;
-  // var<private> v: array<s, 5>;
-
-  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
-                             ty.atomic(Source{{12, 34}}, ty.i32()));
-  Global(Source{{56, 78}}, "v", ty.Of(atomic_array),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStruct) {
-  // struct S{
-  //   m: atomic<u32>;
-  // };
-  // var<private> v: array<S, 5>;
-
-  auto* s = Structure("S", {Member("m", ty.atomic<u32>())});
-  Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "note: atomic sub-type of 'array<S, 5>' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStructOfArray) {
-  // type AtomicArray = array<atomic<i32>, 5>;
-  // struct S{
-  //   m: AtomicArray;
-  // };
-  // var<private> v: array<S, 5>;
-
-  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
-                             ty.atomic(Source{{12, 34}}, ty.i32()));
-  auto* s = Structure("S", {Member("m", ty.Of(atomic_array))});
-  Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "note: atomic sub-type of 'array<S, 5>' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Complex) {
-  // type AtomicArray = array<atomic<i32>, 5>;
-  // struct S6 { x: array<i32, 4>; };
-  // struct S5 { x: S6;
-  //             y: AtomicArray;
-  //             z: array<atomic<u32>, 8>; };
-  // struct S4 { x: S6;
-  //             y: S5;
-  //             z: array<atomic<i32>, 4>; };
-  // struct S3 { x: S4; };
-  // struct S2 { x: S3; };
-  // struct S1 { x: S2; };
-  // struct S0 { x: S1; };
-  // var<private> g : S0;
-
-  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
-                             ty.atomic(Source{{12, 34}}, ty.i32()));
-  auto* array_i32_4 = ty.array(ty.i32(), 4);
-  auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
-  auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
-
-  auto* s6 = Structure("S6", {Member("x", array_i32_4)});
-  auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
-                              Member("y", ty.Of(atomic_array)),   //
-                              Member("z", array_atomic_u32_8)});  //
-  auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
-                              Member("y", ty.Of(s5)),             //
-                              Member("z", array_atomic_i32_4)});  //
-  auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
-  auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
-  auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
-  auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
-  Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables must have <storage> or <workgroup> "
-            "storage class\n"
-            "note: atomic sub-type of 'S0' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
-  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
-                      {StructBlock()});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead, GroupAndBinding(0, 0));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "error: atomic variables in <storage> storage class must have read_write "
-      "access mode\n"
-      "note: atomic sub-type of 's' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
-  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
-                      {StructBlock()});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead, GroupAndBinding(0, 0));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "error: atomic variables in <storage> storage class must have read_write "
-      "access mode\n"
-      "note: atomic sub-type of 's' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStruct) {
-  // struct Inner { m : atomic<i32>; };
-  // struct Outer { m : array<Inner, 4>; };
-  // var<storage, read> g : Outer;
-
-  auto* Inner =
-      Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
-  auto* Outer =
-      Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
-  Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
-         ast::Access::kRead, GroupAndBinding(0, 0));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "error: atomic variables in <storage> storage class must have read_write "
-      "access mode\n"
-      "note: atomic sub-type of 'Outer' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStructOfArray) {
-  // struct Inner { m : array<atomic<i32>, 4>; };
-  // struct Outer { m : array<Inner, 4>; };
-  // var<storage, read> g : Outer;
-
-  auto* Inner =
-      Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
-  auto* Outer =
-      Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
-  Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
-         ast::Access::kRead, GroupAndBinding(0, 0));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables in <storage> storage class must have "
-            "read_write access mode\n"
-            "12:34 note: atomic sub-type of 'Outer' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Complex) {
-  // type AtomicArray = array<atomic<i32>, 5>;
-  // struct S6 { x: array<i32, 4>; };
-  // struct S5 { x: S6;
-  //             y: AtomicArray;
-  //             z: array<atomic<u32>, 8>; };
-  // struct S4 { x: S6;
-  //             y: S5;
-  //             z: array<atomic<i32>, 4>; };
-  // struct S3 { x: S4; };
-  // struct S2 { x: S3; };
-  // struct S1 { x: S2; };
-  // struct S0 { x: S1; };
-  // var<storage, read> g : S0;
-
-  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
-                             ty.atomic(Source{{12, 34}}, ty.i32()));
-  auto* array_i32_4 = ty.array(ty.i32(), 4);
-  auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
-  auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
-
-  auto* s6 = Structure("S6", {Member("x", array_i32_4)});
-  auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
-                              Member("y", ty.Of(atomic_array)),   //
-                              Member("z", array_atomic_u32_8)});  //
-  auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
-                              Member("y", ty.Of(s5)),             //
-                              Member("z", array_atomic_i32_4)});  //
-  auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
-  auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
-  auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
-  auto* s0 = Structure("S0", {Member("x", ty.Of(s1))}, {StructBlock()});
-  Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kStorage,
-         ast::Access::kRead, GroupAndBinding(0, 0));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: atomic variables in <storage> storage class must have "
-            "read_write access mode\n"
-            "note: atomic sub-type of 'S0' is declared here");
-}
-
-TEST_F(ResolverAtomicValidationTest, Local) {
-  WrapInFunction(Var("a", ty.atomic(Source{{12, 34}}, ty.i32())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function variable must have a constructible type");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/attribute_validation_test.cc b/src/resolver/attribute_validation_test.cc
deleted file mode 100644
index 4479e53..0000000
--- a/src/resolver/attribute_validation_test.cc
+++ /dev/null
@@ -1,1404 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <typename T, int ID = 0>
-using alias = builder::alias<T, ID>;
-template <typename T>
-using alias1 = builder::alias1<T>;
-template <typename T>
-using alias2 = builder::alias2<T>;
-template <typename T>
-using alias3 = builder::alias3<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-namespace AttributeTests {
-namespace {
-enum class AttributeKind {
-  kAlign,
-  kBinding,
-  kBuiltin,
-  kGroup,
-  kId,
-  kInterpolate,
-  kInvariant,
-  kLocation,
-  kOffset,
-  kSize,
-  kStage,
-  kStride,
-  kStructBlock,
-  kWorkgroup,
-
-  kBindingAndGroup,
-};
-
-static bool IsBindingAttribute(AttributeKind kind) {
-  switch (kind) {
-    case AttributeKind::kBinding:
-    case AttributeKind::kGroup:
-    case AttributeKind::kBindingAndGroup:
-      return true;
-    default:
-      return false;
-  }
-}
-
-struct TestParams {
-  AttributeKind kind;
-  bool should_pass;
-};
-struct TestWithParams : ResolverTestWithParam<TestParams> {};
-
-static ast::AttributeList createAttributes(const Source& source,
-                                           ProgramBuilder& builder,
-                                           AttributeKind kind) {
-  switch (kind) {
-    case AttributeKind::kAlign:
-      return {builder.create<ast::StructMemberAlignAttribute>(source, 4u)};
-    case AttributeKind::kBinding:
-      return {builder.create<ast::BindingAttribute>(source, 1u)};
-    case AttributeKind::kBuiltin:
-      return {builder.Builtin(source, ast::Builtin::kPosition)};
-    case AttributeKind::kGroup:
-      return {builder.create<ast::GroupAttribute>(source, 1u)};
-    case AttributeKind::kId:
-      return {builder.create<ast::IdAttribute>(source, 0u)};
-    case AttributeKind::kInterpolate:
-      return {builder.Interpolate(source, ast::InterpolationType::kLinear,
-                                  ast::InterpolationSampling::kCenter)};
-    case AttributeKind::kInvariant:
-      return {builder.Invariant(source)};
-    case AttributeKind::kLocation:
-      return {builder.Location(source, 1)};
-    case AttributeKind::kOffset:
-      return {builder.create<ast::StructMemberOffsetAttribute>(source, 4u)};
-    case AttributeKind::kSize:
-      return {builder.create<ast::StructMemberSizeAttribute>(source, 16u)};
-    case AttributeKind::kStage:
-      return {builder.Stage(source, ast::PipelineStage::kCompute)};
-    case AttributeKind::kStride:
-      return {builder.create<ast::StrideAttribute>(source, 4u)};
-    case AttributeKind::kStructBlock:
-      return {builder.create<ast::StructBlockAttribute>(source)};
-    case AttributeKind::kWorkgroup:
-      return {builder.create<ast::WorkgroupAttribute>(source, builder.Expr(1))};
-    case AttributeKind::kBindingAndGroup:
-      return {builder.create<ast::BindingAttribute>(source, 1u),
-              builder.create<ast::GroupAttribute>(source, 1u)};
-  }
-  return {};
-}
-
-namespace FunctionInputAndOutputTests {
-using FunctionParameterAttributeTest = TestWithParams;
-TEST_P(FunctionParameterAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  Func("main",
-       ast::VariableList{Param("a", ty.vec4<f32>(),
-                               createAttributes({}, *this, params.kind))},
-       ty.void_(), {});
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: attribute is not valid for non-entry point function "
-              "parameters");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    FunctionParameterAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using FunctionReturnTypeAttributeTest = TestWithParams;
-TEST_P(FunctionReturnTypeAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
-       {}, createAttributes({}, *this, params.kind));
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: attribute is not valid for non-entry point function "
-              "return types");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    FunctionReturnTypeAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-}  // namespace FunctionInputAndOutputTests
-
-namespace EntryPointInputAndOutputTests {
-using ComputeShaderParameterAttributeTest = TestWithParams;
-TEST_P(ComputeShaderParameterAttributeTest, IsValid) {
-  auto& params = GetParam();
-  auto* p = Param("a", ty.vec4<f32>(),
-                  createAttributes(Source{{12, 34}}, *this, params.kind));
-  Func("main", ast::VariableList{p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (params.kind == AttributeKind::kBuiltin) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: builtin(position) cannot be used in input of "
-                "compute pipeline stage");
-    } else if (params.kind == AttributeKind::kInterpolate ||
-               params.kind == AttributeKind::kLocation ||
-               params.kind == AttributeKind::kInvariant) {
-      EXPECT_EQ(
-          r()->error(),
-          "12:34 error: attribute is not valid for compute shader inputs");
-    } else {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for function parameters");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    ComputeShaderParameterAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using FragmentShaderParameterAttributeTest = TestWithParams;
-TEST_P(FragmentShaderParameterAttributeTest, IsValid) {
-  auto& params = GetParam();
-  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
-  if (params.kind != AttributeKind::kBuiltin &&
-      params.kind != AttributeKind::kLocation) {
-    attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
-  }
-  auto* p = Param("a", ty.vec4<f32>(), attrs);
-  Func("frag_main", {p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: attribute is not valid for function parameters");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    FragmentShaderParameterAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, true},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    // kInterpolate tested separately (requires [[location]])
-                    TestParams{AttributeKind::kInvariant, true},
-                    TestParams{AttributeKind::kLocation, true},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using VertexShaderParameterAttributeTest = TestWithParams;
-TEST_P(VertexShaderParameterAttributeTest, IsValid) {
-  auto& params = GetParam();
-  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
-  if (params.kind != AttributeKind::kLocation) {
-    attrs.push_back(Location(Source{{34, 56}}, 2));
-  }
-  auto* p = Param("a", ty.vec4<f32>(), attrs);
-  Func("vertex_main", ast::VariableList{p}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)},
-       {Builtin(ast::Builtin::kPosition)});
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (params.kind == AttributeKind::kBuiltin) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: builtin(position) cannot be used in input of "
-                "vertex pipeline stage");
-    } else if (params.kind == AttributeKind::kInvariant) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: invariant attribute must only be applied to a "
-                "position builtin");
-    } else {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for function parameters");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    VertexShaderParameterAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, true},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, true},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using ComputeShaderReturnTypeAttributeTest = TestWithParams;
-TEST_P(ComputeShaderReturnTypeAttributeTest, IsValid) {
-  auto& params = GetParam();
-  Func("main", ast::VariableList{}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>(), 1.f))},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)},
-       createAttributes(Source{{12, 34}}, *this, params.kind));
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (params.kind == AttributeKind::kBuiltin) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: builtin(position) cannot be used in output of "
-                "compute pipeline stage");
-    } else if (params.kind == AttributeKind::kInterpolate ||
-               params.kind == AttributeKind::kLocation ||
-               params.kind == AttributeKind::kInvariant) {
-      EXPECT_EQ(
-          r()->error(),
-          "12:34 error: attribute is not valid for compute shader output");
-    } else {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for entry point return "
-                "types");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    ComputeShaderReturnTypeAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using FragmentShaderReturnTypeAttributeTest = TestWithParams;
-TEST_P(FragmentShaderReturnTypeAttributeTest, IsValid) {
-  auto& params = GetParam();
-  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
-  attrs.push_back(Location(Source{{34, 56}}, 2));
-  Func("frag_main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kFragment)}, attrs);
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (params.kind == AttributeKind::kBuiltin) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: builtin(position) cannot be used in output of "
-                "fragment pipeline stage");
-    } else if (params.kind == AttributeKind::kInvariant) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: invariant attribute must only be applied to a "
-                "position builtin");
-    } else if (params.kind == AttributeKind::kLocation) {
-      EXPECT_EQ(r()->error(),
-                "34:56 error: duplicate location attribute\n"
-                "12:34 note: first attribute declared here");
-    } else {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for entry point return "
-                "types");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    FragmentShaderReturnTypeAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, true},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using VertexShaderReturnTypeAttributeTest = TestWithParams;
-TEST_P(VertexShaderReturnTypeAttributeTest, IsValid) {
-  auto& params = GetParam();
-  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
-  // a vertex shader must include the 'position' builtin in its return type
-  if (params.kind != AttributeKind::kBuiltin) {
-    attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
-  }
-  Func("vertex_main", ast::VariableList{}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)}, attrs);
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (params.kind == AttributeKind::kLocation) {
-      EXPECT_EQ(r()->error(),
-                "34:56 error: multiple entry point IO attributes\n"
-                "12:34 note: previously consumed location(1)");
-    } else {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for entry point return "
-                "types");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    VertexShaderReturnTypeAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, true},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    // kInterpolate tested separately (requires [[location]])
-                    TestParams{AttributeKind::kInvariant, true},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using EntryPointParameterAttributeTest = TestWithParams;
-TEST_F(EntryPointParameterAttributeTest, DuplicateAttribute) {
-  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
-       {Stage(ast::PipelineStage::kFragment)},
-       {
-           Location(Source{{12, 34}}, 2),
-           Location(Source{{56, 78}}, 3),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate location attribute
-12:34 note: first attribute declared here)");
-}
-
-TEST_F(EntryPointParameterAttributeTest, DuplicateInternalAttribute) {
-  auto* s = Param("s", ty.sampler(ast::SamplerKind::kSampler),
-                  ast::AttributeList{
-                      create<ast::BindingAttribute>(0),
-                      create<ast::GroupAttribute>(0),
-                      Disable(ast::DisabledValidation::kBindingPointCollision),
-                      Disable(ast::DisabledValidation::kEntryPointParameter),
-                  });
-  Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-using EntryPointReturnTypeAttributeTest = ResolverTest;
-TEST_F(EntryPointReturnTypeAttributeTest, DuplicateAttribute) {
-  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{
-           Location(Source{{12, 34}}, 2),
-           Location(Source{{56, 78}}, 3),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate location attribute
-12:34 note: first attribute declared here)");
-}
-
-TEST_F(EntryPointReturnTypeAttributeTest, DuplicateInternalAttribute) {
-  Func("f", {}, ty.i32(), {Return(1)}, {Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{
-           Disable(ast::DisabledValidation::kBindingPointCollision),
-           Disable(ast::DisabledValidation::kEntryPointParameter),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-}  // namespace EntryPointInputAndOutputTests
-
-namespace StructAndStructMemberTests {
-using StructAttributeTest = TestWithParams;
-TEST_P(StructAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  Structure("mystruct", {Member("a", ty.f32())},
-            createAttributes(Source{{12, 34}}, *this, params.kind));
-
-  WrapInFunction();
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: attribute is not valid for struct declarations");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    StructAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, true},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-TEST_F(StructAttributeTest, DuplicateAttribute) {
-  Structure("mystruct",
-            {
-                Member("a", ty.i32()),
-            },
-            {
-                create<ast::StructBlockAttribute>(Source{{12, 34}}),
-                create<ast::StructBlockAttribute>(Source{{56, 78}}),
-            });
-  WrapInFunction();
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate block attribute
-12:34 note: first attribute declared here)");
-}
-using StructMemberAttributeTest = TestWithParams;
-TEST_P(StructMemberAttributeTest, IsValid) {
-  auto& params = GetParam();
-  ast::StructMemberList members;
-  if (params.kind == AttributeKind::kBuiltin) {
-    members.push_back(
-        {Member("a", ty.vec4<f32>(),
-                createAttributes(Source{{12, 34}}, *this, params.kind))});
-  } else {
-    members.push_back(
-        {Member("a", ty.f32(),
-                createAttributes(Source{{12, 34}}, *this, params.kind))});
-  }
-  Structure("mystruct", members);
-  WrapInFunction();
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: attribute is not valid for structure members");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    StructMemberAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, true},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, true},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    // kInterpolate tested separately (requires [[location]])
-                    // kInvariant tested separately (requires position builtin)
-                    TestParams{AttributeKind::kLocation, true},
-                    TestParams{AttributeKind::kOffset, true},
-                    TestParams{AttributeKind::kSize, true},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-TEST_F(StructMemberAttributeTest, DuplicateAttribute) {
-  Structure(
-      "mystruct",
-      {
-          Member(
-              "a", ty.i32(),
-              {
-                  create<ast::StructMemberAlignAttribute>(Source{{12, 34}}, 4u),
-                  create<ast::StructMemberAlignAttribute>(Source{{56, 78}}, 8u),
-              }),
-      });
-  WrapInFunction();
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate align attribute
-12:34 note: first attribute declared here)");
-}
-TEST_F(StructMemberAttributeTest, InvariantAttributeWithPosition) {
-  Structure("mystruct", {
-                            Member("a", ty.vec4<f32>(),
-                                   {
-                                       Invariant(),
-                                       Builtin(ast::Builtin::kPosition),
-                                   }),
-                        });
-  WrapInFunction();
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-TEST_F(StructMemberAttributeTest, InvariantAttributeWithoutPosition) {
-  Structure("mystruct", {
-                            Member("a", ty.vec4<f32>(),
-                                   {
-                                       Invariant(Source{{12, 34}}),
-                                   }),
-                        });
-  WrapInFunction();
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: invariant attribute must only be applied to a "
-            "position builtin");
-}
-
-}  // namespace StructAndStructMemberTests
-
-using ArrayAttributeTest = TestWithParams;
-TEST_P(ArrayAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  auto* arr = ty.array(ty.f32(), nullptr,
-                       createAttributes(Source{{12, 34}}, *this, params.kind));
-  Structure("mystruct",
-            {
-                Member("a", arr),
-            },
-            {create<ast::StructBlockAttribute>()});
-
-  WrapInFunction();
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: attribute is not valid for array types");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    ArrayAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, true},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-using VariableAttributeTest = TestWithParams;
-TEST_P(VariableAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  if (IsBindingAttribute(params.kind)) {
-    Global("a", ty.sampler(ast::SamplerKind::kSampler),
-           ast::StorageClass::kNone, nullptr,
-           createAttributes(Source{{12, 34}}, *this, params.kind));
-  } else {
-    Global("a", ty.f32(), ast::StorageClass::kPrivate, nullptr,
-           createAttributes(Source{{12, 34}}, *this, params.kind));
-  }
-
-  WrapInFunction();
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    if (!IsBindingAttribute(params.kind)) {
-      EXPECT_EQ(r()->error(),
-                "12:34 error: attribute is not valid for variables");
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    VariableAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, false},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, true}));
-
-TEST_F(VariableAttributeTest, DuplicateAttribute) {
-  Global("a", ty.sampler(ast::SamplerKind::kSampler),
-         ast::AttributeList{
-             create<ast::BindingAttribute>(Source{{12, 34}}, 2),
-             create<ast::GroupAttribute>(2),
-             create<ast::BindingAttribute>(Source{{56, 78}}, 3),
-         });
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate binding attribute
-12:34 note: first attribute declared here)");
-}
-
-TEST_F(VariableAttributeTest, LocalVariable) {
-  auto* v = Var("a", ty.f32(),
-                ast::AttributeList{
-                    create<ast::BindingAttribute>(Source{{12, 34}}, 2),
-                });
-
-  WrapInFunction(v);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: attributes are not valid on local variables");
-}
-
-using ConstantAttributeTest = TestWithParams;
-TEST_P(ConstantAttributeTest, IsValid) {
-  auto& params = GetParam();
-
-  GlobalConst("a", ty.f32(), Expr(1.23f),
-              createAttributes(Source{{12, 34}}, *this, params.kind));
-
-  WrapInFunction();
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: attribute is not valid for constants");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    ConstantAttributeTest,
-    testing::Values(TestParams{AttributeKind::kAlign, false},
-                    TestParams{AttributeKind::kBinding, false},
-                    TestParams{AttributeKind::kBuiltin, false},
-                    TestParams{AttributeKind::kGroup, false},
-                    TestParams{AttributeKind::kId, true},
-                    TestParams{AttributeKind::kInterpolate, false},
-                    TestParams{AttributeKind::kInvariant, false},
-                    TestParams{AttributeKind::kLocation, false},
-                    TestParams{AttributeKind::kOffset, false},
-                    TestParams{AttributeKind::kSize, false},
-                    TestParams{AttributeKind::kStage, false},
-                    TestParams{AttributeKind::kStride, false},
-                    TestParams{AttributeKind::kStructBlock, false},
-                    TestParams{AttributeKind::kWorkgroup, false},
-                    TestParams{AttributeKind::kBindingAndGroup, false}));
-
-TEST_F(ConstantAttributeTest, DuplicateAttribute) {
-  GlobalConst("a", ty.f32(), Expr(1.23f),
-              ast::AttributeList{
-                  create<ast::IdAttribute>(Source{{12, 34}}, 0),
-                  create<ast::IdAttribute>(Source{{56, 78}}, 1),
-              });
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate id attribute
-12:34 note: first attribute declared here)");
-}
-
-}  // namespace
-}  // namespace AttributeTests
-
-namespace ArrayStrideTests {
-namespace {
-
-struct Params {
-  builder::ast_type_func_ptr create_el_type;
-  uint32_t stride;
-  bool should_pass;
-};
-
-template <typename T>
-constexpr Params ParamsFor(uint32_t stride, bool should_pass) {
-  return Params{DataType<T>::AST, stride, should_pass};
-}
-
-struct TestWithParams : ResolverTestWithParam<Params> {};
-
-using ArrayStrideTest = TestWithParams;
-TEST_P(ArrayStrideTest, All) {
-  auto& params = GetParam();
-  auto* el_ty = params.create_el_type(*this);
-
-  std::stringstream ss;
-  ss << "el_ty: " << FriendlyName(el_ty) << ", stride: " << params.stride
-     << ", should_pass: " << params.should_pass;
-  SCOPED_TRACE(ss.str());
-
-  auto* arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
-
-  Global("myarray", arr, ast::StorageClass::kPrivate);
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: arrays decorated with the stride attribute must "
-              "have a stride that is at least the size of the element type, "
-              "and be a multiple of the element type's alignment value.");
-  }
-}
-
-struct SizeAndAlignment {
-  uint32_t size;
-  uint32_t align;
-};
-constexpr SizeAndAlignment default_u32 = {4, 4};
-constexpr SizeAndAlignment default_i32 = {4, 4};
-constexpr SizeAndAlignment default_f32 = {4, 4};
-constexpr SizeAndAlignment default_vec2 = {8, 8};
-constexpr SizeAndAlignment default_vec3 = {12, 16};
-constexpr SizeAndAlignment default_vec4 = {16, 16};
-constexpr SizeAndAlignment default_mat2x2 = {16, 8};
-constexpr SizeAndAlignment default_mat3x3 = {48, 16};
-constexpr SizeAndAlignment default_mat4x4 = {64, 16};
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    ArrayStrideTest,
-    testing::Values(
-        // Succeed because stride >= element size (while being multiple of
-        // element alignment)
-        ParamsFor<u32>(default_u32.size, true),
-        ParamsFor<i32>(default_i32.size, true),
-        ParamsFor<f32>(default_f32.size, true),
-        ParamsFor<vec2<f32>>(default_vec2.size, true),
-        // vec3's default size is not a multiple of its alignment
-        // ParamsFor<vec3<f32>, default_vec3.size, true},
-        ParamsFor<vec4<f32>>(default_vec4.size, true),
-        ParamsFor<mat2x2<f32>>(default_mat2x2.size, true),
-        ParamsFor<mat3x3<f32>>(default_mat3x3.size, true),
-        ParamsFor<mat4x4<f32>>(default_mat4x4.size, true),
-
-        // Fail because stride is < element size
-        ParamsFor<u32>(default_u32.size - 1, false),
-        ParamsFor<i32>(default_i32.size - 1, false),
-        ParamsFor<f32>(default_f32.size - 1, false),
-        ParamsFor<vec2<f32>>(default_vec2.size - 1, false),
-        ParamsFor<vec3<f32>>(default_vec3.size - 1, false),
-        ParamsFor<vec4<f32>>(default_vec4.size - 1, false),
-        ParamsFor<mat2x2<f32>>(default_mat2x2.size - 1, false),
-        ParamsFor<mat3x3<f32>>(default_mat3x3.size - 1, false),
-        ParamsFor<mat4x4<f32>>(default_mat4x4.size - 1, false),
-
-        // Succeed because stride equals multiple of element alignment
-        ParamsFor<u32>(default_u32.align * 7, true),
-        ParamsFor<i32>(default_i32.align * 7, true),
-        ParamsFor<f32>(default_f32.align * 7, true),
-        ParamsFor<vec2<f32>>(default_vec2.align * 7, true),
-        ParamsFor<vec3<f32>>(default_vec3.align * 7, true),
-        ParamsFor<vec4<f32>>(default_vec4.align * 7, true),
-        ParamsFor<mat2x2<f32>>(default_mat2x2.align * 7, true),
-        ParamsFor<mat3x3<f32>>(default_mat3x3.align * 7, true),
-        ParamsFor<mat4x4<f32>>(default_mat4x4.align * 7, true),
-
-        // Fail because stride is not multiple of element alignment
-        ParamsFor<u32>((default_u32.align - 1) * 7, false),
-        ParamsFor<i32>((default_i32.align - 1) * 7, false),
-        ParamsFor<f32>((default_f32.align - 1) * 7, false),
-        ParamsFor<vec2<f32>>((default_vec2.align - 1) * 7, false),
-        ParamsFor<vec3<f32>>((default_vec3.align - 1) * 7, false),
-        ParamsFor<vec4<f32>>((default_vec4.align - 1) * 7, false),
-        ParamsFor<mat2x2<f32>>((default_mat2x2.align - 1) * 7, false),
-        ParamsFor<mat3x3<f32>>((default_mat3x3.align - 1) * 7, false),
-        ParamsFor<mat4x4<f32>>((default_mat4x4.align - 1) * 7, false)));
-
-TEST_F(ArrayStrideTest, DuplicateAttribute) {
-  auto* arr = ty.array(Source{{12, 34}}, ty.i32(), 4,
-                       {
-                           create<ast::StrideAttribute>(Source{{12, 34}}, 4),
-                           create<ast::StrideAttribute>(Source{{56, 78}}, 4),
-                       });
-
-  Global("myarray", arr, ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate stride attribute
-12:34 note: first attribute declared here)");
-}
-
-}  // namespace
-}  // namespace ArrayStrideTests
-
-namespace ResourceTests {
-namespace {
-
-using ResourceAttributeTest = ResolverTest;
-TEST_F(ResourceAttributeTest, UniformBufferMissingBinding) {
-  auto* s = Structure("S", {Member("x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kUniform);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, StorageBufferMissingBinding) {
-  auto* s = Structure("S", {Member("x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, TextureMissingBinding) {
-  Global(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d),
-         ast::StorageClass::kNone);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, SamplerMissingBinding) {
-  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-         ast::StorageClass::kNone);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
-  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::GroupAttribute>(1),
-         });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
-  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-         });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: resource variables require @group and @binding attributes)");
-}
-
-TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
-  Global(Source{{12, 34}}, "A",
-         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-  Global(Source{{56, 78}}, "B",
-         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("F", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
-                    Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
-           Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
-                    Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: entry point 'F' references multiple variables that use the same resource binding @group(2), @binding(1)
-12:34 note: first resource binding usage declared here)");
-}
-
-TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
-  Global(Source{{12, 34}}, "A",
-         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-  Global(Source{{56, 78}}, "B",
-         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         ast::StorageClass::kNone,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("F_A", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
-                    Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-  Func("F_B", {}, ty.void_(),
-       {
-           Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
-                    Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
-  Global(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: non-resource variables must not have @group or @binding attributes)");
-}
-
-}  // namespace
-}  // namespace ResourceTests
-
-namespace InvariantAttributeTests {
-namespace {
-using InvariantAttributeTests = ResolverTest;
-TEST_F(InvariantAttributeTests, InvariantWithPosition) {
-  auto* param = Param("p", ty.vec4<f32>(),
-                      {Invariant(Source{{12, 34}}),
-                       Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
-  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
-       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{
-           Location(0),
-       });
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(InvariantAttributeTests, InvariantWithoutPosition) {
-  auto* param =
-      Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
-  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
-       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{
-           Location(0),
-       });
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: invariant attribute must only be applied to a "
-            "position builtin");
-}
-}  // namespace
-}  // namespace InvariantAttributeTests
-
-namespace WorkgroupAttributeTests {
-namespace {
-
-using WorkgroupAttribute = ResolverTest;
-TEST_F(WorkgroupAttribute, ComputeShaderPass) {
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(WorkgroupAttribute, Missing) {
-  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: a compute shader must include 'workgroup_size' in its "
-      "attributes");
-}
-
-TEST_F(WorkgroupAttribute, NotAnEntryPoint) {
-  Func("main", {}, ty.void_(), {},
-       {create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: the workgroup_size attribute is only valid for "
-            "compute stages");
-}
-
-TEST_F(WorkgroupAttribute, NotAComputeShader) {
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: the workgroup_size attribute is only valid for "
-            "compute stages");
-}
-
-TEST_F(WorkgroupAttribute, DuplicateAttribute) {
-  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Source{{12, 34}}, 1, nullptr, nullptr),
-           WorkgroupSize(Source{{56, 78}}, 2, nullptr, nullptr),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate workgroup_size attribute
-12:34 note: first attribute declared here)");
-}
-
-}  // namespace
-}  // namespace WorkgroupAttributeTests
-
-namespace InterpolateTests {
-namespace {
-
-using InterpolateTest = ResolverTest;
-
-struct Params {
-  ast::InterpolationType type;
-  ast::InterpolationSampling sampling;
-  bool should_pass;
-};
-
-struct TestWithParams : ResolverTestWithParam<Params> {};
-
-using InterpolateParameterTest = TestWithParams;
-TEST_P(InterpolateParameterTest, All) {
-  auto& params = GetParam();
-
-  Func("main",
-       ast::VariableList{Param(
-           "a", ty.f32(),
-           {Location(0),
-            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
-       ty.void_(), {},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: flat interpolation attribute must not have a "
-              "sampling parameter");
-  }
-}
-
-TEST_P(InterpolateParameterTest, IntegerScalar) {
-  auto& params = GetParam();
-
-  Func("main",
-       ast::VariableList{Param(
-           "a", ty.i32(),
-           {Location(0),
-            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
-       ty.void_(), {},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  if (params.type != ast::InterpolationType::kFlat) {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: interpolation type must be 'flat' for integral "
-              "user-defined IO types");
-  } else if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: flat interpolation attribute must not have a "
-              "sampling parameter");
-  }
-}
-
-TEST_P(InterpolateParameterTest, IntegerVector) {
-  auto& params = GetParam();
-
-  Func("main",
-       ast::VariableList{Param(
-           "a", ty.vec4<u32>(),
-           {Location(0),
-            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
-       ty.void_(), {},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  if (params.type != ast::InterpolationType::kFlat) {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: interpolation type must be 'flat' for integral "
-              "user-defined IO types");
-  } else if (params.should_pass) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: flat interpolation attribute must not have a "
-              "sampling parameter");
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverAttributeValidationTest,
-    InterpolateParameterTest,
-    testing::Values(Params{ast::InterpolationType::kPerspective,
-                           ast::InterpolationSampling::kNone, true},
-                    Params{ast::InterpolationType::kPerspective,
-                           ast::InterpolationSampling::kCenter, true},
-                    Params{ast::InterpolationType::kPerspective,
-                           ast::InterpolationSampling::kCentroid, true},
-                    Params{ast::InterpolationType::kPerspective,
-                           ast::InterpolationSampling::kSample, true},
-                    Params{ast::InterpolationType::kLinear,
-                           ast::InterpolationSampling::kNone, true},
-                    Params{ast::InterpolationType::kLinear,
-                           ast::InterpolationSampling::kCenter, true},
-                    Params{ast::InterpolationType::kLinear,
-                           ast::InterpolationSampling::kCentroid, true},
-                    Params{ast::InterpolationType::kLinear,
-                           ast::InterpolationSampling::kSample, true},
-                    // flat interpolation must not have a sampling type
-                    Params{ast::InterpolationType::kFlat,
-                           ast::InterpolationSampling::kNone, true},
-                    Params{ast::InterpolationType::kFlat,
-                           ast::InterpolationSampling::kCenter, false},
-                    Params{ast::InterpolationType::kFlat,
-                           ast::InterpolationSampling::kCentroid, false},
-                    Params{ast::InterpolationType::kFlat,
-                           ast::InterpolationSampling::kSample, false}));
-
-TEST_F(InterpolateTest, FragmentInput_Integer_MissingFlatInterpolation) {
-  Func("main",
-       ast::VariableList{Param(Source{{12, 34}}, "a", ty.i32(), {Location(0)})},
-       ty.void_(), {},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: integral user-defined fragment inputs must have a flat interpolation attribute)");
-}
-
-TEST_F(InterpolateTest, VertexOutput_Integer_MissingFlatInterpolation) {
-  auto* s = Structure(
-      "S",
-      {
-          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-          Member(Source{{12, 34}}, "u", ty.u32(), {Location(0)}),
-      },
-      {});
-  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: integral user-defined vertex outputs must have a flat interpolation attribute
-note: while analysing entry point 'main')");
-}
-
-TEST_F(InterpolateTest, MissingLocationAttribute_Parameter) {
-  Func("main",
-       ast::VariableList{
-           Param("a", ty.vec4<f32>(),
-                 {Builtin(ast::Builtin::kPosition),
-                  Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
-                              ast::InterpolationSampling::kNone)})},
-       ty.void_(), {},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: interpolate attribute must only be used with @location)");
-}
-
-TEST_F(InterpolateTest, MissingLocationAttribute_ReturnType) {
-  Func("main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
-       {Builtin(ast::Builtin::kPosition),
-        Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
-                    ast::InterpolationSampling::kNone)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: interpolate attribute must only be used with @location)");
-}
-
-TEST_F(InterpolateTest, MissingLocationAttribute_Struct) {
-  Structure(
-      "S", {Member("a", ty.f32(),
-                   {Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
-                                ast::InterpolationSampling::kNone)})});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: interpolate attribute must only be used with @location)");
-}
-
-}  // namespace
-}  // namespace InterpolateTests
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/bitcast_validation_test.cc b/src/resolver/bitcast_validation_test.cc
deleted file mode 100644
index d4ce082..0000000
--- a/src/resolver/bitcast_validation_test.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/bitcast_expression.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct Type {
-  template <typename T>
-  static constexpr Type Create() {
-    return Type{builder::DataType<T>::AST, builder::DataType<T>::Sem,
-                builder::DataType<T>::Expr};
-  }
-
-  builder::ast_type_func_ptr ast;
-  builder::sem_type_func_ptr sem;
-  builder::ast_expr_func_ptr expr;
-};
-
-static constexpr Type kNumericScalars[] = {
-    Type::Create<builder::f32>(),
-    Type::Create<builder::i32>(),
-    Type::Create<builder::u32>(),
-};
-static constexpr Type kVec2NumericScalars[] = {
-    Type::Create<builder::vec2<builder::f32>>(),
-    Type::Create<builder::vec2<builder::i32>>(),
-    Type::Create<builder::vec2<builder::u32>>(),
-};
-static constexpr Type kVec3NumericScalars[] = {
-    Type::Create<builder::vec3<builder::f32>>(),
-    Type::Create<builder::vec3<builder::i32>>(),
-    Type::Create<builder::vec3<builder::u32>>(),
-};
-static constexpr Type kVec4NumericScalars[] = {
-    Type::Create<builder::vec4<builder::f32>>(),
-    Type::Create<builder::vec4<builder::i32>>(),
-    Type::Create<builder::vec4<builder::u32>>(),
-};
-static constexpr Type kInvalid[] = {
-    // A non-exhaustive selection of uncastable types
-    Type::Create<bool>(),
-    Type::Create<builder::vec2<bool>>(),
-    Type::Create<builder::vec3<bool>>(),
-    Type::Create<builder::vec4<bool>>(),
-    Type::Create<builder::array<2, builder::i32>>(),
-    Type::Create<builder::array<3, builder::u32>>(),
-    Type::Create<builder::array<4, builder::f32>>(),
-    Type::Create<builder::array<5, bool>>(),
-    Type::Create<builder::mat2x2<builder::f32>>(),
-    Type::Create<builder::mat3x3<builder::f32>>(),
-    Type::Create<builder::mat4x4<builder::f32>>(),
-    Type::Create<builder::ptr<builder::i32>>(),
-    Type::Create<builder::ptr<builder::array<2, builder::i32>>>(),
-    Type::Create<builder::ptr<builder::mat2x2<builder::f32>>>(),
-};
-
-using ResolverBitcastValidationTest =
-    ResolverTestWithParam<std::tuple<Type, Type>>;
-
-////////////////////////////////////////////////////////////////////////////////
-// Valid bitcasts
-////////////////////////////////////////////////////////////////////////////////
-using ResolverBitcastValidationTestPass = ResolverBitcastValidationTest;
-TEST_P(ResolverBitcastValidationTestPass, Test) {
-  auto src = std::get<0>(GetParam());
-  auto dst = std::get<1>(GetParam());
-
-  auto* cast = Bitcast(dst.ast(*this), src.expr(*this, 0));
-  WrapInFunction(cast);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(cast), dst.sem(*this));
-}
-INSTANTIATE_TEST_SUITE_P(Scalars,
-                         ResolverBitcastValidationTestPass,
-                         testing::Combine(testing::ValuesIn(kNumericScalars),
-                                          testing::ValuesIn(kNumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec2,
-    ResolverBitcastValidationTestPass,
-    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
-                     testing::ValuesIn(kVec2NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec3,
-    ResolverBitcastValidationTestPass,
-    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
-                     testing::ValuesIn(kVec3NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec4,
-    ResolverBitcastValidationTestPass,
-    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
-                     testing::ValuesIn(kVec4NumericScalars)));
-
-////////////////////////////////////////////////////////////////////////////////
-// Invalid source type for bitcasts
-////////////////////////////////////////////////////////////////////////////////
-using ResolverBitcastValidationTestInvalidSrcTy = ResolverBitcastValidationTest;
-TEST_P(ResolverBitcastValidationTestInvalidSrcTy, Test) {
-  auto src = std::get<0>(GetParam());
-  auto dst = std::get<1>(GetParam());
-
-  auto* cast = Bitcast(dst.ast(*this), Expr(Source{{12, 34}}, "src"));
-  WrapInFunction(Const("src", nullptr, src.expr(*this, 0)), cast);
-
-  auto expected = "12:34 error: '" + src.sem(*this)->FriendlyName(Symbols()) +
-                  "' cannot be bitcast";
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), expected);
-}
-INSTANTIATE_TEST_SUITE_P(Scalars,
-                         ResolverBitcastValidationTestInvalidSrcTy,
-                         testing::Combine(testing::ValuesIn(kInvalid),
-                                          testing::ValuesIn(kNumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec2,
-    ResolverBitcastValidationTestInvalidSrcTy,
-    testing::Combine(testing::ValuesIn(kInvalid),
-                     testing::ValuesIn(kVec2NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec3,
-    ResolverBitcastValidationTestInvalidSrcTy,
-    testing::Combine(testing::ValuesIn(kInvalid),
-                     testing::ValuesIn(kVec3NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec4,
-    ResolverBitcastValidationTestInvalidSrcTy,
-    testing::Combine(testing::ValuesIn(kInvalid),
-                     testing::ValuesIn(kVec4NumericScalars)));
-
-////////////////////////////////////////////////////////////////////////////////
-// Invalid target type for bitcasts
-////////////////////////////////////////////////////////////////////////////////
-using ResolverBitcastValidationTestInvalidDstTy = ResolverBitcastValidationTest;
-TEST_P(ResolverBitcastValidationTestInvalidDstTy, Test) {
-  auto src = std::get<0>(GetParam());
-  auto dst = std::get<1>(GetParam());
-
-  // Use an alias so we can put a Source on the bitcast type
-  Alias("T", dst.ast(*this));
-  WrapInFunction(
-      Bitcast(ty.type_name(Source{{12, 34}}, "T"), src.expr(*this, 0)));
-
-  auto expected = "12:34 error: cannot bitcast to '" +
-                  dst.sem(*this)->FriendlyName(Symbols()) + "'";
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), expected);
-}
-INSTANTIATE_TEST_SUITE_P(Scalars,
-                         ResolverBitcastValidationTestInvalidDstTy,
-                         testing::Combine(testing::ValuesIn(kNumericScalars),
-                                          testing::ValuesIn(kInvalid)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec2,
-    ResolverBitcastValidationTestInvalidDstTy,
-    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
-                     testing::ValuesIn(kInvalid)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec3,
-    ResolverBitcastValidationTestInvalidDstTy,
-    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
-                     testing::ValuesIn(kInvalid)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec4,
-    ResolverBitcastValidationTestInvalidDstTy,
-    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
-                     testing::ValuesIn(kInvalid)));
-
-////////////////////////////////////////////////////////////////////////////////
-// Incompatible bitcast, but both src and dst types are valid
-////////////////////////////////////////////////////////////////////////////////
-using ResolverBitcastValidationTestIncompatible = ResolverBitcastValidationTest;
-TEST_P(ResolverBitcastValidationTestIncompatible, Test) {
-  auto src = std::get<0>(GetParam());
-  auto dst = std::get<1>(GetParam());
-
-  WrapInFunction(Bitcast(Source{{12, 34}}, dst.ast(*this), src.expr(*this, 0)));
-
-  auto expected = "12:34 error: cannot bitcast from '" +
-                  src.sem(*this)->FriendlyName(Symbols()) + "' to '" +
-                  dst.sem(*this)->FriendlyName(Symbols()) + "'";
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), expected);
-}
-INSTANTIATE_TEST_SUITE_P(
-    ScalarToVec2,
-    ResolverBitcastValidationTestIncompatible,
-    testing::Combine(testing::ValuesIn(kNumericScalars),
-                     testing::ValuesIn(kVec2NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec2ToVec3,
-    ResolverBitcastValidationTestIncompatible,
-    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
-                     testing::ValuesIn(kVec3NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec3ToVec4,
-    ResolverBitcastValidationTestIncompatible,
-    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
-                     testing::ValuesIn(kVec4NumericScalars)));
-INSTANTIATE_TEST_SUITE_P(
-    Vec4ToScalar,
-    ResolverBitcastValidationTestIncompatible,
-    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
-                     testing::ValuesIn(kNumericScalars)));
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/builtin_test.cc b/src/resolver/builtin_test.cc
deleted file mode 100644
index 11fd892..0000000
--- a/src/resolver/builtin_test.cc
+++ /dev/null
@@ -1,2061 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-using ::testing::ElementsAre;
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using BuiltinType = sem::BuiltinType;
-
-using ResolverBuiltinTest = ResolverTest;
-
-using ResolverBuiltinDerivativeTest = ResolverTestWithParam<std::string>;
-TEST_P(ResolverBuiltinDerivativeTest, Scalar) {
-  auto name = GetParam();
-
-  Global("ident", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "ident");
-  Func("func", {}, ty.void_(), {Ignore(expr)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
-}
-
-TEST_P(ResolverBuiltinDerivativeTest, Vector) {
-  auto name = GetParam();
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "ident");
-  Func("func", {}, ty.void_(), {Ignore(expr)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_P(ResolverBuiltinDerivativeTest, MissingParam) {
-  auto name = GetParam();
-
-  auto* expr = Call(name);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> f32\n  " + name +
-                              "(vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinDerivativeTest,
-                         testing::Values("dpdx",
-                                         "dpdxCoarse",
-                                         "dpdxFine",
-                                         "dpdy",
-                                         "dpdyCoarse",
-                                         "dpdyFine",
-                                         "fwidth",
-                                         "fwidthCoarse",
-                                         "fwidthFine"));
-
-using ResolverBuiltinTest_BoolMethod = ResolverTestWithParam<std::string>;
-TEST_P(ResolverBuiltinTest_BoolMethod, Scalar) {
-  auto name = GetParam();
-
-  Global("my_var", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
-}
-TEST_P(ResolverBuiltinTest_BoolMethod, Vector) {
-  auto name = GetParam();
-
-  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinTest_BoolMethod,
-                         testing::Values("any", "all"));
-
-using ResolverBuiltinTest_FloatMethod = ResolverTestWithParam<std::string>;
-TEST_P(ResolverBuiltinTest_FloatMethod, Vector) {
-  auto name = GetParam();
-
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_FloatMethod, Scalar) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
-}
-
-TEST_P(ResolverBuiltinTest_FloatMethod, MissingParam) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> bool\n  " + name +
-                              "(vecN<f32>) -> vecN<bool>\n");
-}
-
-TEST_P(ResolverBuiltinTest_FloatMethod, TooManyParams) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call(name, "my_var", 1.23f);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "(f32, f32)\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> bool\n  " + name +
-                              "(vecN<f32>) -> vecN<bool>\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_FloatMethod,
-    testing::Values("isInf", "isNan", "isFinite", "isNormal"));
-
-enum class Texture { kF32, kI32, kU32 };
-inline std::ostream& operator<<(std::ostream& out, Texture data) {
-  if (data == Texture::kF32) {
-    out << "f32";
-  } else if (data == Texture::kI32) {
-    out << "i32";
-  } else {
-    out << "u32";
-  }
-  return out;
-}
-
-struct TextureTestParams {
-  ast::TextureDimension dim;
-  Texture type = Texture::kF32;
-  ast::TexelFormat format = ast::TexelFormat::kR32Float;
-};
-inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
-  out << data.dim << "_" << data.type;
-  return out;
-}
-
-class ResolverBuiltinTest_TextureOperation
-    : public ResolverTestWithParam<TextureTestParams> {
- public:
-  /// Gets an appropriate type for the coords parameter depending the the
-  /// dimensionality of the texture being sampled.
-  /// @param dim dimensionality of the texture being sampled
-  /// @param scalar the scalar type
-  /// @returns a pointer to a type appropriate for the coord param
-  const ast::Type* GetCoordsType(ast::TextureDimension dim,
-                                 const ast::Type* scalar) {
-    switch (dim) {
-      case ast::TextureDimension::k1d:
-        return scalar;
-      case ast::TextureDimension::k2d:
-      case ast::TextureDimension::k2dArray:
-        return ty.vec(scalar, 2);
-      case ast::TextureDimension::k3d:
-      case ast::TextureDimension::kCube:
-      case ast::TextureDimension::kCubeArray:
-        return ty.vec(scalar, 3);
-      default:
-        [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
-    }
-    return nullptr;
-  }
-
-  void add_call_param(std::string name,
-                      const ast::Type* type,
-                      ast::ExpressionList* call_params) {
-    if (type->IsAnyOf<ast::Texture, ast::Sampler>()) {
-      Global(name, type,
-             ast::AttributeList{
-                 create<ast::BindingAttribute>(0),
-                 create<ast::GroupAttribute>(0),
-             });
-
-    } else {
-      Global(name, type, ast::StorageClass::kPrivate);
-    }
-
-    call_params->push_back(Expr(name));
-  }
-  const ast::Type* subtype(Texture type) {
-    if (type == Texture::kF32) {
-      return ty.f32();
-    }
-    if (type == Texture::kI32) {
-      return ty.i32();
-    }
-    return ty.u32();
-  }
-};
-
-using ResolverBuiltinTest_SampledTextureOperation =
-    ResolverBuiltinTest_TextureOperation;
-TEST_P(ResolverBuiltinTest_SampledTextureOperation, TextureLoadSampled) {
-  auto dim = GetParam().dim;
-  auto type = GetParam().type;
-
-  auto* s = subtype(type);
-  auto* coords_type = GetCoordsType(dim, ty.i32());
-  auto* texture_type = ty.sampled_texture(dim, s);
-
-  ast::ExpressionList call_params;
-
-  add_call_param("texture", texture_type, &call_params);
-  add_call_param("coords", coords_type, &call_params);
-  if (dim == ast::TextureDimension::k2dArray) {
-    add_call_param("array_index", ty.i32(), &call_params);
-  }
-  add_call_param("level", ty.i32(), &call_params);
-
-  auto* expr = Call("textureLoad", call_params);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
-  if (type == Texture::kF32) {
-    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
-  } else if (type == Texture::kI32) {
-    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::I32>());
-  } else {
-    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::U32>());
-  }
-  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 4u);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_SampledTextureOperation,
-    testing::Values(TextureTestParams{ast::TextureDimension::k1d},
-                    TextureTestParams{ast::TextureDimension::k2d},
-                    TextureTestParams{ast::TextureDimension::k2dArray},
-                    TextureTestParams{ast::TextureDimension::k3d}));
-
-TEST_F(ResolverBuiltinTest, Dot_Vec2) {
-  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Dot_Vec3) {
-  Global("my_var", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::I32>());
-}
-
-TEST_F(ResolverBuiltinTest, Dot_Vec4) {
-  Global("my_var", ty.vec4<u32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::U32>());
-}
-
-TEST_F(ResolverBuiltinTest, Dot_Error_Scalar) {
-  auto* expr = Call("dot", 1.0f, 1.0f);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to dot(f32, f32)
-
-1 candidate function:
-  dot(vecN<T>, vecN<T>) -> T  where: T is f32, i32 or u32
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Select) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("select", "my_var", "my_var", "bool_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::Vector>());
-  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Select_Error_NoParams) {
-  auto* expr = Call("select");
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select()
-
-3 candidate functions:
-  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Select_Error_SelectorInt) {
-  auto* expr = Call("select", 1, 1, 1);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(i32, i32, i32)
-
-3 candidate functions:
-  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Select_Error_Matrix) {
-  auto* expr = Call(
-      "select", mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)),
-      mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)), Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool)
-
-3 candidate functions:
-  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Select_Error_MismatchTypes) {
-  auto* expr = Call("select", 1.0f, vec2<f32>(2.0f, 3.0f), Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(f32, vec2<f32>, bool)
-
-3 candidate functions:
-  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Select_Error_MismatchVectorSize) {
-  auto* expr = Call("select", vec2<f32>(1.0f, 2.0f),
-                    vec3<f32>(3.0f, 4.0f, 5.0f), Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool)
-
-3 candidate functions:
-  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
-)");
-}
-
-struct BuiltinData {
-  const char* name;
-  BuiltinType builtin;
-};
-
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.name;
-  return out;
-}
-
-using ResolverBuiltinTest_Barrier = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_Barrier, InferType) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(CallStmt(call));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::Void>());
-}
-
-TEST_P(ResolverBuiltinTest_Barrier, Error_TooManyParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f);
-  WrapInFunction(CallStmt(call));
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_Barrier,
-    testing::Values(BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
-                    BuiltinData{"workgroupBarrier",
-                                BuiltinType::kWorkgroupBarrier}));
-
-using ResolverBuiltinTest_DataPacking = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_DataPacking, InferType) {
-  auto param = GetParam();
-
-  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
-               param.builtin == BuiltinType::kPack4x8unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f))
-                     : Call(param.name, vec2<f32>(1.f, 2.f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
-}
-
-TEST_P(ResolverBuiltinTest_DataPacking, Error_IncorrectParamType) {
-  auto param = GetParam();
-
-  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
-               param.builtin == BuiltinType::kPack4x8unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<i32>(1, 2, 3, 4))
-                     : Call(param.name, vec2<i32>(1, 2));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-TEST_P(ResolverBuiltinTest_DataPacking, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-TEST_P(ResolverBuiltinTest_DataPacking, Error_TooManyParams) {
-  auto param = GetParam();
-
-  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
-               param.builtin == BuiltinType::kPack4x8unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f)
-                     : Call(param.name, vec2<f32>(1.f, 2.f), 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_DataPacking,
-    testing::Values(BuiltinData{"pack4x8snorm", BuiltinType::kPack4x8snorm},
-                    BuiltinData{"pack4x8unorm", BuiltinType::kPack4x8unorm},
-                    BuiltinData{"pack2x16snorm", BuiltinType::kPack2x16snorm},
-                    BuiltinData{"pack2x16unorm", BuiltinType::kPack2x16unorm},
-                    BuiltinData{"pack2x16float", BuiltinType::kPack2x16float}));
-
-using ResolverBuiltinTest_DataUnpacking = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_DataUnpacking, InferType) {
-  auto param = GetParam();
-
-  bool pack4 = param.builtin == BuiltinType::kUnpack4x8snorm ||
-               param.builtin == BuiltinType::kUnpack4x8unorm;
-
-  auto* call = Call(param.name, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  if (pack4) {
-    EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 4u);
-  } else {
-    EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 2u);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_DataUnpacking,
-    testing::Values(
-        BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4x8snorm},
-        BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4x8unorm},
-        BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2x16snorm},
-        BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2x16unorm},
-        BuiltinData{"unpack2x16float", BuiltinType::kUnpack2x16float}));
-
-using ResolverBuiltinTest_SingleParam = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_SingleParam, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32) -> f32\n  " +
-                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam, Error_TooManyParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 2, 3);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "(i32, i32, i32)\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32) -> f32\n  " +
-                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_SingleParam,
-    testing::Values(BuiltinData{"acos", BuiltinType::kAcos},
-                    BuiltinData{"asin", BuiltinType::kAsin},
-                    BuiltinData{"atan", BuiltinType::kAtan},
-                    BuiltinData{"ceil", BuiltinType::kCeil},
-                    BuiltinData{"cos", BuiltinType::kCos},
-                    BuiltinData{"cosh", BuiltinType::kCosh},
-                    BuiltinData{"exp", BuiltinType::kExp},
-                    BuiltinData{"exp2", BuiltinType::kExp2},
-                    BuiltinData{"floor", BuiltinType::kFloor},
-                    BuiltinData{"fract", BuiltinType::kFract},
-                    BuiltinData{"inverseSqrt", BuiltinType::kInverseSqrt},
-                    BuiltinData{"log", BuiltinType::kLog},
-                    BuiltinData{"log2", BuiltinType::kLog2},
-                    BuiltinData{"round", BuiltinType::kRound},
-                    BuiltinData{"sign", BuiltinType::kSign},
-                    BuiltinData{"sin", BuiltinType::kSin},
-                    BuiltinData{"sinh", BuiltinType::kSinh},
-                    BuiltinData{"sqrt", BuiltinType::kSqrt},
-                    BuiltinData{"tan", BuiltinType::kTan},
-                    BuiltinData{"tanh", BuiltinType::kTanh},
-                    BuiltinData{"trunc", BuiltinType::kTrunc}));
-
-using ResolverBuiltinDataTest = ResolverTest;
-
-TEST_F(ResolverBuiltinDataTest, ArrayLength_Vector) {
-  auto* ary = ty.array<i32>();
-  auto* str =
-      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
-  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  auto* call = Call("arrayLength", AddressOf(MemberAccessor("a", "x")));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
-}
-
-TEST_F(ResolverBuiltinDataTest, ArrayLength_Error_ArraySized) {
-  Global("arr", ty.array<int, 4>(), ast::StorageClass::kPrivate);
-  auto* call = Call("arrayLength", AddressOf("arr"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to arrayLength(ptr<private, array<i32, 4>, read_write>)
-
-1 candidate function:
-  arrayLength(ptr<storage, array<T>, A>) -> u32
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Normalize_Vector) {
-  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_F(ResolverBuiltinDataTest, Normalize_Error_NoParams) {
-  auto* call = Call("normalize");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
-
-1 candidate function:
-  normalize(vecN<f32>) -> vecN<f32>
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, FrexpScalar) {
-  auto* call = Call("frexp", 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  auto* ty = TypeOf(call)->As<sem::Struct>();
-  ASSERT_NE(ty, nullptr);
-  ASSERT_EQ(ty->Members().size(), 2u);
-
-  auto* sig = ty->Members()[0];
-  EXPECT_TRUE(sig->Type()->Is<sem::F32>());
-  EXPECT_EQ(sig->Offset(), 0u);
-  EXPECT_EQ(sig->Size(), 4u);
-  EXPECT_EQ(sig->Align(), 4u);
-  EXPECT_EQ(sig->Name(), Sym("sig"));
-
-  auto* exp = ty->Members()[1];
-  EXPECT_TRUE(exp->Type()->Is<sem::I32>());
-  EXPECT_EQ(exp->Offset(), 4u);
-  EXPECT_EQ(exp->Size(), 4u);
-  EXPECT_EQ(exp->Align(), 4u);
-  EXPECT_EQ(exp->Name(), Sym("exp"));
-
-  EXPECT_EQ(ty->Size(), 8u);
-  EXPECT_EQ(ty->SizeNoPadding(), 8u);
-}
-
-TEST_F(ResolverBuiltinDataTest, FrexpVector) {
-  auto* call = Call("frexp", vec3<f32>());
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  auto* ty = TypeOf(call)->As<sem::Struct>();
-  ASSERT_NE(ty, nullptr);
-  ASSERT_EQ(ty->Members().size(), 2u);
-
-  auto* sig = ty->Members()[0];
-  ASSERT_TRUE(sig->Type()->Is<sem::Vector>());
-  EXPECT_EQ(sig->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(sig->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sig->Offset(), 0u);
-  EXPECT_EQ(sig->Size(), 12u);
-  EXPECT_EQ(sig->Align(), 16u);
-  EXPECT_EQ(sig->Name(), Sym("sig"));
-
-  auto* exp = ty->Members()[1];
-  ASSERT_TRUE(exp->Type()->Is<sem::Vector>());
-  EXPECT_EQ(exp->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(exp->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(exp->Offset(), 16u);
-  EXPECT_EQ(exp->Size(), 12u);
-  EXPECT_EQ(exp->Align(), 16u);
-  EXPECT_EQ(exp->Name(), Sym("exp"));
-
-  EXPECT_EQ(ty->Size(), 32u);
-  EXPECT_EQ(ty->SizeNoPadding(), 28u);
-}
-
-TEST_F(ResolverBuiltinDataTest, Frexp_Error_FirstParamInt) {
-  Global("v", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", 1, AddressOf("v"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to frexp(i32, ptr<workgroup, i32, read_write>)
-
-2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Frexp_Error_SecondParamFloatPtr) {
-  Global("v", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", 1.0f, AddressOf("v"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to frexp(f32, ptr<workgroup, f32, read_write>)
-
-2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Frexp_Error_SecondParamNotAPointer) {
-  auto* call = Call("frexp", 1.0f, 1);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to frexp(f32, i32)
-
-2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Frexp_Error_VectorSizesDontMatch) {
-  Global("v", ty.vec4<i32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", vec2<f32>(1.0f, 2.0f), AddressOf("v"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to frexp(vec2<f32>, ptr<workgroup, vec4<i32>, read_write>)
-
-2 candidate functions:
-  frexp(vecN<f32>) -> __frexp_result_vecN
-  frexp(f32) -> __frexp_result
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, ModfScalar) {
-  auto* call = Call("modf", 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  auto* ty = TypeOf(call)->As<sem::Struct>();
-  ASSERT_NE(ty, nullptr);
-  ASSERT_EQ(ty->Members().size(), 2u);
-
-  auto* fract = ty->Members()[0];
-  EXPECT_TRUE(fract->Type()->Is<sem::F32>());
-  EXPECT_EQ(fract->Offset(), 0u);
-  EXPECT_EQ(fract->Size(), 4u);
-  EXPECT_EQ(fract->Align(), 4u);
-  EXPECT_EQ(fract->Name(), Sym("fract"));
-
-  auto* whole = ty->Members()[1];
-  EXPECT_TRUE(whole->Type()->Is<sem::F32>());
-  EXPECT_EQ(whole->Offset(), 4u);
-  EXPECT_EQ(whole->Size(), 4u);
-  EXPECT_EQ(whole->Align(), 4u);
-  EXPECT_EQ(whole->Name(), Sym("whole"));
-
-  EXPECT_EQ(ty->Size(), 8u);
-  EXPECT_EQ(ty->SizeNoPadding(), 8u);
-}
-
-TEST_F(ResolverBuiltinDataTest, ModfVector) {
-  auto* call = Call("modf", vec3<f32>());
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  auto* ty = TypeOf(call)->As<sem::Struct>();
-  ASSERT_NE(ty, nullptr);
-  ASSERT_EQ(ty->Members().size(), 2u);
-
-  auto* fract = ty->Members()[0];
-  ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
-  EXPECT_EQ(fract->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(fract->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(fract->Offset(), 0u);
-  EXPECT_EQ(fract->Size(), 12u);
-  EXPECT_EQ(fract->Align(), 16u);
-  EXPECT_EQ(fract->Name(), Sym("fract"));
-
-  auto* whole = ty->Members()[1];
-  ASSERT_TRUE(whole->Type()->Is<sem::Vector>());
-  EXPECT_EQ(whole->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(whole->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(whole->Offset(), 16u);
-  EXPECT_EQ(whole->Size(), 12u);
-  EXPECT_EQ(whole->Align(), 16u);
-  EXPECT_EQ(whole->Name(), Sym("whole"));
-
-  EXPECT_EQ(ty->Size(), 32u);
-  EXPECT_EQ(ty->SizeNoPadding(), 28u);
-}
-
-TEST_F(ResolverBuiltinDataTest, Modf_Error_FirstParamInt) {
-  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", 1, AddressOf("whole"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to modf(i32, ptr<workgroup, f32, read_write>)
-
-2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Modf_Error_SecondParamIntPtr) {
-  Global("whole", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", 1.0f, AddressOf("whole"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to modf(f32, ptr<workgroup, i32, read_write>)
-
-2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Modf_Error_SecondParamNotAPointer) {
-  auto* call = Call("modf", 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to modf(f32, f32)
-
-2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
-)");
-}
-
-TEST_F(ResolverBuiltinDataTest, Modf_Error_VectorSizesDontMatch) {
-  Global("whole", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", vec2<f32>(1.0f, 2.0f), AddressOf("whole"));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: no matching call to modf(vec2<f32>, ptr<workgroup, vec4<f32>, read_write>)
-
-2 candidate functions:
-  modf(vecN<f32>) -> __modf_result_vecN
-  modf(f32) -> __modf_result
-)");
-}
-
-using ResolverBuiltinTest_SingleParam_FloatOrInt =
-    ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Float_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Float_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Sint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, -1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Sint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Uint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Uint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinTest_SingleParam_FloatOrInt,
-                         testing::Values(BuiltinData{"abs",
-                                                     BuiltinType::kAbs}));
-
-TEST_F(ResolverBuiltinTest, Length_Scalar) {
-  auto* call = Call("length", 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_F(ResolverBuiltinTest, Length_FloatVector) {
-  auto* call = Call("length", vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-using ResolverBuiltinTest_TwoParam = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_TwoParam, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_TwoParam, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_TwoParam, Error_NoTooManyParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 2, 3);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "(i32, i32, i32)\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32, f32) -> f32\n  " +
-                std::string(param.name) +
-                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
-}
-
-TEST_P(ResolverBuiltinTest_TwoParam, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32, f32) -> f32\n  " +
-                std::string(param.name) +
-                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_TwoParam,
-    testing::Values(BuiltinData{"atan2", BuiltinType::kAtan2},
-                    BuiltinData{"pow", BuiltinType::kPow},
-                    BuiltinData{"step", BuiltinType::kStep}));
-
-TEST_F(ResolverBuiltinTest, Distance_Scalar) {
-  auto* call = Call("distance", 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_F(ResolverBuiltinTest, Distance_Vector) {
-  auto* call = Call("distance", vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Cross) {
-  auto* call =
-      Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_F(ResolverBuiltinTest, Cross_Error_NoArgs) {
-  auto* call = Call("cross");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to cross()
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Cross_Error_Scalar) {
-  auto* call = Call("cross", 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to cross(f32, f32)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Cross_Error_Vec3Int) {
-  auto* call = Call("cross", vec3<i32>(1, 2, 3), vec3<i32>(1, 2, 3));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec3<i32>, vec3<i32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Cross_Error_Vec4) {
-  auto* call = Call("cross", vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f),
-                    vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
-
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec4<f32>, vec4<f32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Cross_Error_TooManyParams) {
-  auto* call = Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f),
-                    vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
-
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec3<f32>, vec3<f32>, vec3<f32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-TEST_F(ResolverBuiltinTest, Normalize) {
-  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_F(ResolverBuiltinTest, Normalize_NoArgs) {
-  auto* call = Call("normalize");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
-
-1 candidate function:
-  normalize(vecN<f32>) -> vecN<f32>
-)");
-}
-
-using ResolverBuiltinTest_ThreeParam = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_ThreeParam, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-TEST_P(ResolverBuiltinTest_ThreeParam, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name) + "()"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_ThreeParam,
-    testing::Values(BuiltinData{"mix", BuiltinType::kMix},
-                    BuiltinData{"smoothStep", BuiltinType::kSmoothStep},
-                    BuiltinData{"fma", BuiltinType::kFma}));
-
-using ResolverBuiltinTest_ThreeParam_FloatOrInt =
-    ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Float_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Float_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Sint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 1, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Sint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3),
-                    vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Uint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1u, 1u, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Uint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u),
-                    vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T, T, T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>, vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 "
-                "or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinTest_ThreeParam_FloatOrInt,
-                         testing::Values(BuiltinData{"clamp",
-                                                     BuiltinType::kClamp}));
-
-using ResolverBuiltinTest_Int_SingleParam = ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_Int_SingleParam, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_integer_scalar());
-}
-
-TEST_P(ResolverBuiltinTest_Int_SingleParam, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_Int_SingleParam, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " +
-                              std::string(param.name) +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              std::string(param.name) +
-                              "(T) -> T  where: T is i32 or u32\n  " +
-                              std::string(param.name) +
-                              "(vecN<T>) -> vecN<T>  where: T is i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_Int_SingleParam,
-    testing::Values(BuiltinData{"countOneBits", BuiltinType::kCountOneBits},
-                    BuiltinData{"reverseBits", BuiltinType::kReverseBits}));
-
-using ResolverBuiltinTest_FloatOrInt_TwoParam =
-    ResolverTestWithParam<BuiltinData>;
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Signed) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Unsigned) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1u, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Float) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Signed) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Unsigned) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Float) {
-  auto param = GetParam();
-
-  auto* call =
-      Call(param.name, vec3<f32>(1.f, 1.f, 3.f), vec3<f32>(1.f, 1.f, 3.f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T, T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinTest_FloatOrInt_TwoParam,
-                         testing::Values(BuiltinData{"min", BuiltinType::kMin},
-                                         BuiltinData{"max",
-                                                     BuiltinType::kMax}));
-
-TEST_F(ResolverBuiltinTest, Determinant_2x2) {
-  Global("var", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Determinant_3x3) {
-  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Determinant_4x4) {
-  Global("var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinTest, Determinant_NotSquare) {
-  Global("var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to determinant(mat2x3<f32>)
-
-1 candidate function:
-  determinant(matNxN<f32>) -> f32
-)");
-}
-
-TEST_F(ResolverBuiltinTest, Determinant_NotMatrix) {
-  Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to determinant(f32)
-
-1 candidate function:
-  determinant(matNxN<f32>) -> f32
-)");
-}
-
-using ResolverBuiltinTest_Texture =
-    ResolverTestWithParam<ast::builtin::test::TextureOverloadCase>;
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverBuiltinTest_Texture,
-    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
-
-std::string to_str(const std::string& function,
-                   const sem::ParameterList& params) {
-  std::stringstream out;
-  out << function << "(";
-  bool first = true;
-  for (auto* param : params) {
-    if (!first) {
-      out << ", ";
-    }
-    out << sem::str(param->Usage());
-    first = false;
-  }
-  out << ")";
-  return out.str();
-}
-
-const char* expected_texture_overload(
-    ast::builtin::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-    case ValidTextureOverload::kDimensions2d:
-    case ValidTextureOverload::kDimensions2dArray:
-    case ValidTextureOverload::kDimensions3d:
-    case ValidTextureOverload::kDimensionsCube:
-    case ValidTextureOverload::kDimensionsCubeArray:
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-    case ValidTextureOverload::kDimensionsDepth2d:
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-    case ValidTextureOverload::kDimensionsDepthCube:
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return R"(textureDimensions(texture))";
-    case ValidTextureOverload::kGather2dF32:
-      return R"(textureGather(component, texture, sampler, coords))";
-    case ValidTextureOverload::kGather2dOffsetF32:
-      return R"(textureGather(component, texture, sampler, coords, offset))";
-    case ValidTextureOverload::kGather2dArrayF32:
-      return R"(textureGather(component, texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kGather2dArrayOffsetF32:
-      return R"(textureGather(component, texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kGatherCubeF32:
-      return R"(textureGather(component, texture, sampler, coords))";
-    case ValidTextureOverload::kGatherCubeArrayF32:
-      return R"(textureGather(component, texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kGatherDepth2dF32:
-      return R"(textureGather(texture, sampler, coords))";
-    case ValidTextureOverload::kGatherDepth2dOffsetF32:
-      return R"(textureGather(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kGatherDepth2dArrayF32:
-      return R"(textureGather(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
-      return R"(textureGather(texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kGatherDepthCubeF32:
-      return R"(textureGather(texture, sampler, coords))";
-    case ValidTextureOverload::kGatherDepthCubeArrayF32:
-      return R"(textureGather(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kGatherCompareDepth2dF32:
-      return R"(textureGatherCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
-      return R"(textureGatherCompare(texture, sampler, coords, depth_ref, offset))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
-      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
-      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref, offset))";
-    case ValidTextureOverload::kGatherCompareDepthCubeF32:
-      return R"(textureGatherCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
-      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kNumLayers2dArray:
-    case ValidTextureOverload::kNumLayersCubeArray:
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return R"(textureNumLayers(texture))";
-    case ValidTextureOverload::kNumLevels2d:
-    case ValidTextureOverload::kNumLevels2dArray:
-    case ValidTextureOverload::kNumLevels3d:
-    case ValidTextureOverload::kNumLevelsCube:
-    case ValidTextureOverload::kNumLevelsCubeArray:
-    case ValidTextureOverload::kNumLevelsDepth2d:
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-    case ValidTextureOverload::kNumLevelsDepthCube:
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return R"(textureNumLevels(texture))";
-    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-      return R"(textureNumSamples(texture))";
-    case ValidTextureOverload::kDimensions2dLevel:
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-    case ValidTextureOverload::kDimensions3dLevel:
-    case ValidTextureOverload::kDimensionsCubeLevel:
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return R"(textureDimensions(texture, level))";
-    case ValidTextureOverload::kSample1dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample2dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSample2dArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return R"(textureSample(texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kSample3dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSampleCubeF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return R"(textureSample(texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleBias2dF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias, offset))";
-    case ValidTextureOverload::kSampleBias3dF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kLoad1dLevelF32:
-    case ValidTextureOverload::kLoad1dLevelU32:
-    case ValidTextureOverload::kLoad1dLevelI32:
-    case ValidTextureOverload::kLoad2dLevelF32:
-    case ValidTextureOverload::kLoad2dLevelU32:
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kLoad3dLevelF32:
-    case ValidTextureOverload::kLoad3dLevelU32:
-    case ValidTextureOverload::kLoad3dLevelI32:
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return R"(textureLoad(texture, coords, sample_index))";
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return R"(textureStore(texture, coords, value))";
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return R"(textureStore(texture, coords, array_index, value))";
-  }
-  return "<unmatched texture overload>";
-}
-
-TEST_P(ResolverBuiltinTest_Texture, Call) {
-  auto param = GetParam();
-
-  param.BuildTextureVariable(this);
-  param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  auto* stmt = CallStmt(call);
-  Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  if (std::string(param.function) == "textureDimensions") {
-    switch (param.texture_dimension) {
-      default:
-        FAIL() << "invalid texture dimensions: " << param.texture_dimension;
-      case ast::TextureDimension::k1d:
-        EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-        break;
-      case ast::TextureDimension::k2d:
-      case ast::TextureDimension::k2dArray:
-      case ast::TextureDimension::kCube:
-      case ast::TextureDimension::kCubeArray: {
-        auto* vec = As<sem::Vector>(TypeOf(call));
-        ASSERT_NE(vec, nullptr);
-        EXPECT_EQ(vec->Width(), 2u);
-        EXPECT_TRUE(vec->type()->Is<sem::I32>());
-        break;
-      }
-      case ast::TextureDimension::k3d: {
-        auto* vec = As<sem::Vector>(TypeOf(call));
-        ASSERT_NE(vec, nullptr);
-        EXPECT_EQ(vec->Width(), 3u);
-        EXPECT_TRUE(vec->type()->Is<sem::I32>());
-        break;
-      }
-    }
-  } else if (std::string(param.function) == "textureNumLayers") {
-    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-  } else if (std::string(param.function) == "textureNumLevels") {
-    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-  } else if (std::string(param.function) == "textureNumSamples") {
-    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
-  } else if (std::string(param.function) == "textureStore") {
-    EXPECT_TRUE(TypeOf(call)->Is<sem::Void>());
-  } else if (std::string(param.function) == "textureGather") {
-    auto* vec = As<sem::Vector>(TypeOf(call));
-    ASSERT_NE(vec, nullptr);
-    EXPECT_EQ(vec->Width(), 4u);
-    switch (param.texture_data_type) {
-      case ast::builtin::test::TextureDataType::kF32:
-        EXPECT_TRUE(vec->type()->Is<sem::F32>());
-        break;
-      case ast::builtin::test::TextureDataType::kU32:
-        EXPECT_TRUE(vec->type()->Is<sem::U32>());
-        break;
-      case ast::builtin::test::TextureDataType::kI32:
-        EXPECT_TRUE(vec->type()->Is<sem::I32>());
-        break;
-    }
-  } else if (std::string(param.function) == "textureGatherCompare") {
-    auto* vec = As<sem::Vector>(TypeOf(call));
-    ASSERT_NE(vec, nullptr);
-    EXPECT_EQ(vec->Width(), 4u);
-    EXPECT_TRUE(vec->type()->Is<sem::F32>());
-  } else {
-    switch (param.texture_kind) {
-      case ast::builtin::test::TextureKind::kRegular:
-      case ast::builtin::test::TextureKind::kMultisampled:
-      case ast::builtin::test::TextureKind::kStorage: {
-        auto* vec = TypeOf(call)->As<sem::Vector>();
-        ASSERT_NE(vec, nullptr);
-        switch (param.texture_data_type) {
-          case ast::builtin::test::TextureDataType::kF32:
-            EXPECT_TRUE(vec->type()->Is<sem::F32>());
-            break;
-          case ast::builtin::test::TextureDataType::kU32:
-            EXPECT_TRUE(vec->type()->Is<sem::U32>());
-            break;
-          case ast::builtin::test::TextureDataType::kI32:
-            EXPECT_TRUE(vec->type()->Is<sem::I32>());
-            break;
-        }
-        break;
-      }
-      case ast::builtin::test::TextureKind::kDepth:
-      case ast::builtin::test::TextureKind::kDepthMultisampled: {
-        EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-        break;
-      }
-    }
-  }
-
-  auto* call_sem = Sem().Get(call);
-  ASSERT_NE(call_sem, nullptr);
-  auto* target = call_sem->Target();
-  ASSERT_NE(target, nullptr);
-
-  auto got = resolver::to_str(param.function, target->Parameters());
-  auto* expected = expected_texture_overload(param.overload);
-  EXPECT_EQ(got, expected);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/builtin_validation_test.cc b/src/resolver/builtin_validation_test.cc
deleted file mode 100644
index 70c776b..0000000
--- a/src/resolver/builtin_validation_test.cc
+++ /dev/null
@@ -1,402 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverBuiltinValidationTest = ResolverTest;
-
-TEST_F(ResolverBuiltinValidationTest,
-       FunctionTypeMustMatchReturnStatementType_void_fail) {
-  // fn func { return workgroupBarrier(); }
-  Func("func", {}, ty.void_(),
-       {
-           Return(Call(Source{Source::Location{12, 34}}, "workgroupBarrier")),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: builtin 'workgroupBarrier' does not return a value");
-}
-
-TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageDirect) {
-  // @stage(compute) @workgroup_size(1) fn func { return dpdx(1.0); }
-
-  auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
-                                           ast::ExpressionList{Expr(1.0f)});
-  Func(Source{{1, 2}}, "func", ast::VariableList{}, ty.void_(),
-       {CallStmt(dpdx)},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "3:4 error: built-in cannot be used by compute pipeline stage");
-}
-
-TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageIndirect) {
-  // fn f0 { return dpdx(1.0); }
-  // fn f1 { f0(); }
-  // fn f2 { f1(); }
-  // @stage(compute) @workgroup_size(1) fn main { return f2(); }
-
-  auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
-                                           ast::ExpressionList{Expr(1.0f)});
-  Func(Source{{1, 2}}, "f0", {}, ty.void_(), {CallStmt(dpdx)});
-
-  Func(Source{{3, 4}}, "f1", {}, ty.void_(), {CallStmt(Call("f0"))});
-
-  Func(Source{{5, 6}}, "f2", {}, ty.void_(), {CallStmt(Call("f1"))});
-
-  Func(Source{{7, 8}}, "main", {}, ty.void_(), {CallStmt(Call("f2"))},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(3:4 error: built-in cannot be used by compute pipeline stage
-1:2 note: called by function 'f0'
-3:4 note: called by function 'f1'
-5:6 note: called by function 'f2'
-7:8 note: called by entry point 'main')");
-}
-
-TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsFunction) {
-  Func(Source{{12, 34}}, "mix", {}, ty.i32(), {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a function)");
-}
-
-TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalLet) {
-  GlobalConst(Source{{12, 34}}, "mix", ty.i32(), Expr(1));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope let)");
-}
-
-TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalVar) {
-  Global(Source{{12, 34}}, "mix", ty.i32(), Expr(1),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope var)");
-}
-
-TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsAlias) {
-  Alias(Source{{12, 34}}, "mix", ty.i32());
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as an alias)");
-}
-
-TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsStruct) {
-  Structure(Source{{12, 34}}, "mix", {Member("m", ty.i32())});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a struct)");
-}
-
-namespace texture_constexpr_args {
-
-using TextureOverloadCase = ast::builtin::test::TextureOverloadCase;
-using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-using TextureKind = ast::builtin::test::TextureKind;
-using TextureDataType = ast::builtin::test::TextureDataType;
-using u32 = ProgramBuilder::u32;
-using i32 = ProgramBuilder::i32;
-using f32 = ProgramBuilder::f32;
-
-static std::vector<TextureOverloadCase> TextureCases(
-    std::unordered_set<ValidTextureOverload> overloads) {
-  std::vector<TextureOverloadCase> cases;
-  for (auto c : TextureOverloadCase::ValidCases()) {
-    if (overloads.count(c.overload)) {
-      cases.push_back(c);
-    }
-  }
-  return cases;
-}
-
-enum class Position {
-  kFirst,
-  kLast,
-};
-
-struct Parameter {
-  const char* const name;
-  const Position position;
-  int min;
-  int max;
-};
-
-class Constexpr {
- public:
-  enum class Kind {
-    kScalar,
-    kVec2,
-    kVec3,
-    kVec3_Scalar_Vec2,
-    kVec3_Vec2_Scalar,
-    kEmptyVec2,
-    kEmptyVec3,
-  };
-
-  Constexpr(int32_t invalid_idx,
-            Kind k,
-            int32_t x = 0,
-            int32_t y = 0,
-            int32_t z = 0)
-      : invalid_index(invalid_idx), kind(k), values{x, y, z} {}
-
-  const ast::Expression* operator()(Source src, ProgramBuilder& b) {
-    switch (kind) {
-      case Kind::kScalar:
-        return b.Expr(src, values[0]);
-      case Kind::kVec2:
-        return b.Construct(src, b.ty.vec2<i32>(), values[0], values[1]);
-      case Kind::kVec3:
-        return b.Construct(src, b.ty.vec3<i32>(), values[0], values[1],
-                           values[2]);
-      case Kind::kVec3_Scalar_Vec2:
-        return b.Construct(src, b.ty.vec3<i32>(), values[0],
-                           b.vec2<i32>(values[1], values[2]));
-      case Kind::kVec3_Vec2_Scalar:
-        return b.Construct(src, b.ty.vec3<i32>(),
-                           b.vec2<i32>(values[0], values[1]), values[2]);
-      case Kind::kEmptyVec2:
-        return b.Construct(src, b.ty.vec2<i32>());
-      case Kind::kEmptyVec3:
-        return b.Construct(src, b.ty.vec3<i32>());
-    }
-    return nullptr;
-  }
-
-  static const constexpr int32_t kValid = -1;
-  const int32_t invalid_index;  // Expected error value, or kValid
-  const Kind kind;
-  const std::array<int32_t, 3> values;
-};
-
-static std::ostream& operator<<(std::ostream& out, Parameter param) {
-  return out << param.name;
-}
-
-static std::ostream& operator<<(std::ostream& out, Constexpr expr) {
-  switch (expr.kind) {
-    case Constexpr::Kind::kScalar:
-      return out << expr.values[0];
-    case Constexpr::Kind::kVec2:
-      return out << "vec2(" << expr.values[0] << ", " << expr.values[1] << ")";
-    case Constexpr::Kind::kVec3:
-      return out << "vec3(" << expr.values[0] << ", " << expr.values[1] << ", "
-                 << expr.values[2] << ")";
-    case Constexpr::Kind::kVec3_Scalar_Vec2:
-      return out << "vec3(" << expr.values[0] << ", vec2(" << expr.values[1]
-                 << ", " << expr.values[2] << "))";
-    case Constexpr::Kind::kVec3_Vec2_Scalar:
-      return out << "vec3(vec2(" << expr.values[0] << ", " << expr.values[1]
-                 << "), " << expr.values[2] << ")";
-    case Constexpr::Kind::kEmptyVec2:
-      return out << "vec2()";
-    case Constexpr::Kind::kEmptyVec3:
-      return out << "vec3()";
-  }
-  return out;
-}
-
-using BuiltinTextureConstExprArgValidationTest = ResolverTestWithParam<
-    std::tuple<TextureOverloadCase, Parameter, Constexpr>>;
-
-TEST_P(BuiltinTextureConstExprArgValidationTest, Immediate) {
-  auto& p = GetParam();
-  auto overload = std::get<0>(p);
-  auto param = std::get<1>(p);
-  auto expr = std::get<2>(p);
-
-  overload.BuildTextureVariable(this);
-  overload.BuildSamplerVariable(this);
-
-  auto args = overload.args(this);
-  auto*& arg_to_replace =
-      (param.position == Position::kFirst) ? args.front() : args.back();
-
-  // BuildTextureVariable() uses a Literal for scalars, and a CallExpression for
-  // a vector constructor.
-  bool is_vector = arg_to_replace->Is<ast::CallExpression>();
-
-  // Make the expression to be replaced, reachable. This keeps the resolver
-  // happy.
-  WrapInFunction(arg_to_replace);
-
-  arg_to_replace = expr(Source{{12, 34}}, *this);
-
-  // Call the builtin with the constexpr argument replaced
-  Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  if (expr.invalid_index == Constexpr::kValid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    std::stringstream err;
-    if (is_vector) {
-      err << "12:34 error: each component of the " << param.name
-          << " argument must be at least " << param.min << " and at most "
-          << param.max << ". " << param.name << " component "
-          << expr.invalid_index << " is "
-          << std::to_string(expr.values[expr.invalid_index]);
-    } else {
-      err << "12:34 error: the " << param.name << " argument must be at least "
-          << param.min << " and at most " << param.max << ". " << param.name
-          << " is " << std::to_string(expr.values[expr.invalid_index]);
-    }
-    EXPECT_EQ(r()->error(), err.str());
-  }
-}
-
-TEST_P(BuiltinTextureConstExprArgValidationTest, GlobalConst) {
-  auto& p = GetParam();
-  auto overload = std::get<0>(p);
-  auto param = std::get<1>(p);
-  auto expr = std::get<2>(p);
-
-  // Build the global texture and sampler variables
-  overload.BuildTextureVariable(this);
-  overload.BuildSamplerVariable(this);
-
-  // Build the module-scope let 'G' with the offset value
-  GlobalConst("G", nullptr, expr({}, *this));
-
-  auto args = overload.args(this);
-  auto*& arg_to_replace =
-      (param.position == Position::kFirst) ? args.front() : args.back();
-
-  // Make the expression to be replaced, reachable. This keeps the resolver
-  // happy.
-  WrapInFunction(arg_to_replace);
-
-  arg_to_replace = Expr(Source{{12, 34}}, "G");
-
-  // Call the builtin with the constexpr argument replaced
-  Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  std::stringstream err;
-  err << "12:34 error: the " << param.name
-      << " argument must be a const_expression";
-  EXPECT_EQ(r()->error(), err.str());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Offset2D,
-    BuiltinTextureConstExprArgValidationTest,
-    testing::Combine(
-        testing::ValuesIn(TextureCases({
-            ValidTextureOverload::kSample2dOffsetF32,
-            ValidTextureOverload::kSample2dArrayOffsetF32,
-            ValidTextureOverload::kSampleDepth2dOffsetF32,
-            ValidTextureOverload::kSampleDepth2dArrayOffsetF32,
-            ValidTextureOverload::kSampleBias2dOffsetF32,
-            ValidTextureOverload::kSampleBias2dArrayOffsetF32,
-            ValidTextureOverload::kSampleLevel2dOffsetF32,
-            ValidTextureOverload::kSampleLevel2dArrayOffsetF32,
-            ValidTextureOverload::kSampleLevelDepth2dOffsetF32,
-            ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32,
-            ValidTextureOverload::kSampleGrad2dOffsetF32,
-            ValidTextureOverload::kSampleGrad2dArrayOffsetF32,
-            ValidTextureOverload::kSampleCompareDepth2dOffsetF32,
-            ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32,
-            ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32,
-            ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32,
-        })),
-        testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
-        testing::Values(
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec2},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, -1, 1},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, 7, -8},
-            Constexpr{0, Constexpr::Kind::kVec2, 8, 0},
-            Constexpr{1, Constexpr::Kind::kVec2, 0, 8},
-            Constexpr{0, Constexpr::Kind::kVec2, -9, 0},
-            Constexpr{1, Constexpr::Kind::kVec2, 0, -9},
-            Constexpr{0, Constexpr::Kind::kVec2, 8, 8},
-            Constexpr{0, Constexpr::Kind::kVec2, -9, -9})));
-
-INSTANTIATE_TEST_SUITE_P(
-    Offset3D,
-    BuiltinTextureConstExprArgValidationTest,
-    testing::Combine(
-        testing::ValuesIn(TextureCases({
-            ValidTextureOverload::kSample3dOffsetF32,
-            ValidTextureOverload::kSampleBias3dOffsetF32,
-            ValidTextureOverload::kSampleLevel3dOffsetF32,
-            ValidTextureOverload::kSampleGrad3dOffsetF32,
-        })),
-        testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
-        testing::Values(
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec3},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 0, 0, 0},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 7, -8, 7},
-            Constexpr{0, Constexpr::Kind::kVec3, 10, 0, 0},
-            Constexpr{1, Constexpr::Kind::kVec3, 0, 10, 0},
-            Constexpr{2, Constexpr::Kind::kVec3, 0, 0, 10},
-            Constexpr{0, Constexpr::Kind::kVec3, 10, 11, 12},
-            Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 0, 0},
-            Constexpr{1, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 10, 0},
-            Constexpr{2, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 0, 10},
-            Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 11, 12},
-            Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 0, 0},
-            Constexpr{1, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 10, 0},
-            Constexpr{2, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 0, 10},
-            Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 11, 12})));
-
-INSTANTIATE_TEST_SUITE_P(
-    Component,
-    BuiltinTextureConstExprArgValidationTest,
-    testing::Combine(
-        testing::ValuesIn(
-            TextureCases({ValidTextureOverload::kGather2dF32,
-                          ValidTextureOverload::kGather2dOffsetF32,
-                          ValidTextureOverload::kGather2dArrayF32,
-                          ValidTextureOverload::kGather2dArrayOffsetF32,
-                          ValidTextureOverload::kGatherCubeF32,
-                          ValidTextureOverload::kGatherCubeArrayF32})),
-        testing::Values(Parameter{"component", Position::kFirst, 0, 3}),
-        testing::Values(
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 0},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 1},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 2},
-            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 3},
-            Constexpr{0, Constexpr::Kind::kScalar, 4},
-            Constexpr{0, Constexpr::Kind::kScalar, 123},
-            Constexpr{0, Constexpr::Kind::kScalar, -1})));
-
-}  // namespace texture_constexpr_args
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/builtins_validation_test.cc b/src/resolver/builtins_validation_test.cc
deleted file mode 100644
index 1031fb7..0000000
--- a/src/resolver/builtins_validation_test.cc
+++ /dev/null
@@ -1,1292 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/call_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-template <typename T>
-using DataType = builder::DataType<T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-class ResolverBuiltinsValidationTest : public resolver::TestHelper,
-                                       public testing::Test {};
-namespace StageTest {
-struct Params {
-  builder::ast_type_func_ptr type;
-  ast::Builtin builtin;
-  ast::PipelineStage stage;
-  bool is_valid;
-};
-
-template <typename T>
-constexpr Params ParamsFor(ast::Builtin builtin,
-                           ast::PipelineStage stage,
-                           bool is_valid) {
-  return Params{DataType<T>::AST, builtin, stage, is_valid};
-}
-static constexpr Params cases[] = {
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
-                         ast::PipelineStage::kVertex,
-                         false),
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
-                         ast::PipelineStage::kFragment,
-                         true),
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
-                         ast::PipelineStage::kCompute,
-                         false),
-
-    ParamsFor<u32>(ast::Builtin::kVertexIndex,
-                   ast::PipelineStage::kVertex,
-                   true),
-    ParamsFor<u32>(ast::Builtin::kVertexIndex,
-                   ast::PipelineStage::kFragment,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kVertexIndex,
-                   ast::PipelineStage::kCompute,
-                   false),
-
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
-                   ast::PipelineStage::kVertex,
-                   true),
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
-                   ast::PipelineStage::kFragment,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
-                   ast::PipelineStage::kCompute,
-                   false),
-
-    ParamsFor<bool>(ast::Builtin::kFrontFacing,
-                    ast::PipelineStage::kVertex,
-                    false),
-    ParamsFor<bool>(ast::Builtin::kFrontFacing,
-                    ast::PipelineStage::kFragment,
-                    true),
-    ParamsFor<bool>(ast::Builtin::kFrontFacing,
-                    ast::PipelineStage::kCompute,
-                    false),
-
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
-                         ast::PipelineStage::kVertex,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
-                         ast::PipelineStage::kFragment,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
-                         ast::PipelineStage::kCompute,
-                         true),
-
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
-                   ast::PipelineStage::kVertex,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
-                   ast::PipelineStage::kFragment,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
-                   ast::PipelineStage::kCompute,
-                   true),
-
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
-                         ast::PipelineStage::kVertex,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
-                         ast::PipelineStage::kFragment,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
-                         ast::PipelineStage::kCompute,
-                         true),
-
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
-                         ast::PipelineStage::kVertex,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
-                         ast::PipelineStage::kFragment,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
-                         ast::PipelineStage::kCompute,
-                         true),
-
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
-                         ast::PipelineStage::kVertex,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
-                         ast::PipelineStage::kFragment,
-                         false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
-                         ast::PipelineStage::kCompute,
-                         true),
-
-    ParamsFor<u32>(ast::Builtin::kSampleIndex,
-                   ast::PipelineStage::kVertex,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kSampleIndex,
-                   ast::PipelineStage::kFragment,
-                   true),
-    ParamsFor<u32>(ast::Builtin::kSampleIndex,
-                   ast::PipelineStage::kCompute,
-                   false),
-
-    ParamsFor<u32>(ast::Builtin::kSampleMask,
-                   ast::PipelineStage::kVertex,
-                   false),
-    ParamsFor<u32>(ast::Builtin::kSampleMask,
-                   ast::PipelineStage::kFragment,
-                   true),
-    ParamsFor<u32>(ast::Builtin::kSampleMask,
-                   ast::PipelineStage::kCompute,
-                   false),
-};
-
-using ResolverBuiltinsStageTest = ResolverTestWithParam<Params>;
-TEST_P(ResolverBuiltinsStageTest, All_input) {
-  const Params& params = GetParam();
-
-  auto* p = Global("p", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  auto* input =
-      Param("input", params.type(*this),
-            ast::AttributeList{Builtin(Source{{12, 34}}, params.builtin)});
-  switch (params.stage) {
-    case ast::PipelineStage::kVertex:
-      Func("main", {input}, ty.vec4<f32>(), {Return(p)},
-           {Stage(ast::PipelineStage::kVertex)},
-           {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
-      break;
-    case ast::PipelineStage::kFragment:
-      Func("main", {input}, ty.void_(), {},
-           {Stage(ast::PipelineStage::kFragment)}, {});
-      break;
-    case ast::PipelineStage::kCompute:
-      Func("main", {input}, ty.void_(), {},
-           ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                              WorkgroupSize(1)});
-      break;
-    default:
-      break;
-  }
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    std::stringstream err;
-    err << "12:34 error: builtin(" << params.builtin << ")";
-    err << " cannot be used in input of " << params.stage << " pipeline stage";
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), err.str());
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         ResolverBuiltinsStageTest,
-                         testing::ValuesIn(cases));
-
-TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(frag_depth) fd: f32,
-  // ) -> @location(0) f32 { return 1.0; }
-  auto* fd = Param(
-      "fd", ty.f32(),
-      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
-  Func("fs_main", ast::VariableList{fd}, ty.f32(), {Return(1.0f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: builtin(frag_depth) cannot be used in input of "
-            "fragment pipeline stage");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
-  // struct MyInputs {
-  //   @builtin(frag_depth) ff: f32;
-  // };
-  // @stage(fragment)
-  // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* s = Structure(
-      "MyInputs", {Member("frag_depth", ty.f32(),
-                          ast::AttributeList{Builtin(
-                              Source{{12, 34}}, ast::Builtin::kFragDepth)})});
-
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: builtin(frag_depth) cannot be used in input of "
-            "fragment pipeline stage\n"
-            "note: while analysing entry point 'fragShader'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, StructBuiltinInsideEntryPoint_Ignored) {
-  // struct S {
-  //   @builtin(vertex_index) idx: u32;
-  // };
-  // @stage(fragment)
-  // fn fragShader() { var s : S; }
-
-  Structure("S",
-            {Member("idx", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)})});
-
-  Func("fragShader", {}, ty.void_(), {Decl(Var("s", ty.type_name("S")))},
-       {Stage(ast::PipelineStage::kFragment)});
-  EXPECT_TRUE(r()->Resolve());
-}
-
-}  // namespace StageTest
-
-TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_Struct_Fail) {
-  // struct MyInputs {
-  //   @builtin(kPosition) p: vec4<u32>;
-  // };
-  // @stage(fragment)
-  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* m = Member(
-      "position", ty.vec4<u32>(),
-      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
-  auto* s = Structure("MyInputs", {m});
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_ReturnType_Fail) {
-  // @stage(vertex)
-  // fn main() -> @builtin(position) f32 { return 1.0; }
-  Func("main", {}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kVertex)},
-       {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FragDepthNotF32_Struct_Fail) {
-  // struct MyInputs {
-  //   @builtin(kFragDepth) p: i32;
-  // };
-  // @stage(fragment)
-  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* m = Member(
-      "frag_depth", ty.i32(),
-      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
-  auto* s = Structure("MyInputs", {m});
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(frag_depth) must be 'f32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_Struct_Fail) {
-  // struct MyInputs {
-  //   @builtin(sample_mask) m: f32;
-  // };
-  // @stage(fragment)
-  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* s = Structure(
-      "MyInputs", {Member("m", ty.f32(),
-                          ast::AttributeList{Builtin(
-                              Source{{12, 34}}, ast::Builtin::kSampleMask)})});
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_ReturnType_Fail) {
-  // @stage(fragment)
-  // fn main() -> @builtin(sample_mask) i32 { return 1; }
-  Func("main", {}, ty.i32(), {Return(1)},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, SampleMaskIsNotU32_Fail) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(sample_mask) arg: bool
-  // ) -> @location(0) f32 { return 1.0; }
-  auto* arg = Param(
-      "arg", ty.bool_(),
-      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
-  Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Struct_Fail) {
-  // struct MyInputs {
-  //   @builtin(sample_index) m: f32;
-  // };
-  // @stage(fragment)
-  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* s = Structure(
-      "MyInputs", {Member("m", ty.f32(),
-                          ast::AttributeList{Builtin(
-                              Source{{12, 34}}, ast::Builtin::kSampleIndex)})});
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(sample_index) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Fail) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(sample_index) arg: bool
-  // ) -> @location(0) f32 { return 1.0; }
-  auto* arg = Param("arg", ty.bool_(),
-                    ast::AttributeList{
-                        Builtin(Source{{12, 34}}, ast::Builtin::kSampleIndex)});
-  Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(sample_index) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, PositionIsNotF32_Fail) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(kPosition) p: vec3<f32>,
-  // ) -> @location(0) f32 { return 1.0; }
-  auto* p = Param(
-      "p", ty.vec3<f32>(),
-      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
-  Func("fs_main", ast::VariableList{p}, ty.f32(), {Return(1.0f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FragDepthIsNotF32_Fail) {
-  // @stage(fragment)
-  // fn fs_main() -> @builtin(kFragDepth) f32 { var fd: i32; return fd; }
-  auto* fd = Var("fd", ty.i32());
-  Func("fs_main", {}, ty.i32(), {Decl(fd), Return(fd)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(frag_depth) must be 'f32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, VertexIndexIsNotU32_Fail) {
-  // @stage(vertex)
-  // fn main(
-  //   @builtin(kVertexIndex) vi : f32,
-  //   @builtin(kPosition) p :vec4<f32>
-  // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-  auto* p = Param("p", ty.vec4<f32>(),
-                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-  auto* vi = Param("vi", ty.f32(),
-                   ast::AttributeList{
-                       Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
-  Func("main", ast::VariableList{vi, p}, ty.vec4<f32>(), {Return(Expr("p"))},
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
-       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(vertex_index) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, InstanceIndexIsNotU32) {
-  // @stage(vertex)
-  // fn main(
-  //   @builtin(kInstanceIndex) ii : f32,
-  //   @builtin(kPosition) p :vec4<f32>
-  // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-  auto* p = Param("p", ty.vec4<f32>(),
-                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-  auto* ii = Param("ii", ty.f32(),
-                   ast::AttributeList{Builtin(Source{{12, 34}},
-                                              ast::Builtin::kInstanceIndex)});
-  Func("main", ast::VariableList{ii, p}, ty.vec4<f32>(), {Return(Expr("p"))},
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
-       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(instance_index) must be 'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltin_Pass) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(kPosition) p: vec4<f32>,
-  //   @builtin(front_facing) ff: bool,
-  //   @builtin(sample_index) si: u32,
-  //   @builtin(sample_mask) sm : u32
-  // ) -> @builtin(frag_depth) f32 { var fd: f32; return fd; }
-  auto* p = Param("p", ty.vec4<f32>(),
-                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-  auto* ff = Param("ff", ty.bool_(),
-                   ast::AttributeList{Builtin(ast::Builtin::kFrontFacing)});
-  auto* si = Param("si", ty.u32(),
-                   ast::AttributeList{Builtin(ast::Builtin::kSampleIndex)});
-  auto* sm = Param("sm", ty.u32(),
-                   ast::AttributeList{Builtin(ast::Builtin::kSampleMask)});
-  auto* var_fd = Var("fd", ty.f32());
-  Func("fs_main", ast::VariableList{p, ff, si, sm}, ty.f32(),
-       {Decl(var_fd), Return(var_fd)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{Builtin(ast::Builtin::kFragDepth)});
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, VertexBuiltin_Pass) {
-  // @stage(vertex)
-  // fn main(
-  //   @builtin(vertex_index) vi : u32,
-  //   @builtin(instance_index) ii : u32,
-  // ) -> @builtin(position) vec4<f32> { var p :vec4<f32>; return p; }
-  auto* vi = Param("vi", ty.u32(),
-                   ast::AttributeList{
-                       Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
-
-  auto* ii = Param("ii", ty.u32(),
-                   ast::AttributeList{Builtin(Source{{12, 34}},
-                                              ast::Builtin::kInstanceIndex)});
-  auto* p = Var("p", ty.vec4<f32>());
-  Func("main", ast::VariableList{vi, ii}, ty.vec4<f32>(),
-       {
-           Decl(p),
-           Return(p),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
-       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_Pass) {
-  // @stage(compute) @workgroup_size(1)
-  // fn main(
-  //   @builtin(local_invocationId) li_id: vec3<u32>,
-  //   @builtin(local_invocationIndex) li_index: u32,
-  //   @builtin(global_invocationId) gi: vec3<u32>,
-  //   @builtin(workgroup_id) wi: vec3<u32>,
-  //   @builtin(num_workgroups) nwgs: vec3<u32>,
-  // ) {}
-
-  auto* li_id =
-      Param("li_id", ty.vec3<u32>(),
-            ast::AttributeList{Builtin(ast::Builtin::kLocalInvocationId)});
-  auto* li_index =
-      Param("li_index", ty.u32(),
-            ast::AttributeList{Builtin(ast::Builtin::kLocalInvocationIndex)});
-  auto* gi =
-      Param("gi", ty.vec3<u32>(),
-            ast::AttributeList{Builtin(ast::Builtin::kGlobalInvocationId)});
-  auto* wi = Param("wi", ty.vec3<u32>(),
-                   ast::AttributeList{Builtin(ast::Builtin::kWorkgroupId)});
-  auto* nwgs = Param("nwgs", ty.vec3<u32>(),
-                     ast::AttributeList{Builtin(ast::Builtin::kNumWorkgroups)});
-
-  Func("main", ast::VariableList{li_id, li_index, gi, wi, nwgs}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_WorkGroupIdNotVec3U32) {
-  auto* wi = Param("wi", ty.f32(),
-                   ast::AttributeList{
-                       Builtin(Source{{12, 34}}, ast::Builtin::kWorkgroupId)});
-  Func("main", ast::VariableList{wi}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(workgroup_id) must be "
-            "'vec3<u32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_NumWorkgroupsNotVec3U32) {
-  auto* nwgs = Param("nwgs", ty.f32(),
-                     ast::AttributeList{Builtin(Source{{12, 34}},
-                                                ast::Builtin::kNumWorkgroups)});
-  Func("main", ast::VariableList{nwgs}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(num_workgroups) must be "
-            "'vec3<u32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest,
-       ComputeBuiltin_GlobalInvocationNotVec3U32) {
-  auto* gi = Param("gi", ty.vec3<i32>(),
-                   ast::AttributeList{Builtin(
-                       Source{{12, 34}}, ast::Builtin::kGlobalInvocationId)});
-  Func("main", ast::VariableList{gi}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(global_invocation_id) must be "
-            "'vec3<u32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest,
-       ComputeBuiltin_LocalInvocationIndexNotU32) {
-  auto* li_index =
-      Param("li_index", ty.vec3<u32>(),
-            ast::AttributeList{Builtin(Source{{12, 34}},
-                                       ast::Builtin::kLocalInvocationIndex)});
-  Func("main", ast::VariableList{li_index}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: store type of builtin(local_invocation_index) must be "
-      "'u32'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest,
-       ComputeBuiltin_LocalInvocationNotVec3U32) {
-  auto* li_id = Param("li_id", ty.vec2<u32>(),
-                      ast::AttributeList{Builtin(
-                          Source{{12, 34}}, ast::Builtin::kLocalInvocationId)});
-  Func("main", ast::VariableList{li_id}, ty.void_(), {},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(local_invocation_id) must be "
-            "'vec3<u32>'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltinStruct_Pass) {
-  // Struct MyInputs {
-  //   @builtin(kPosition) p: vec4<f32>;
-  //   @builtin(frag_depth) fd: f32;
-  //   @builtin(sample_index) si: u32;
-  //   @builtin(sample_mask) sm : u32;;
-  // };
-  // @stage(fragment)
-  // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* s = Structure(
-      "MyInputs",
-      {Member("position", ty.vec4<f32>(),
-              ast::AttributeList{Builtin(ast::Builtin::kPosition)}),
-       Member("front_facing", ty.bool_(),
-              ast::AttributeList{Builtin(ast::Builtin::kFrontFacing)}),
-       Member("sample_index", ty.u32(),
-              ast::AttributeList{Builtin(ast::Builtin::kSampleIndex)}),
-       Member("sample_mask", ty.u32(),
-              ast::AttributeList{Builtin(ast::Builtin::kSampleMask)})});
-  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsNotBool_Fail) {
-  // @stage(fragment)
-  // fn fs_main(
-  //   @builtin(front_facing) is_front: i32;
-  // ) -> @location(0) f32 { return 1.0; }
-
-  auto* is_front = Param("is_front", ty.i32(),
-                         ast::AttributeList{Builtin(
-                             Source{{12, 34}}, ast::Builtin::kFrontFacing)});
-  Func("fs_main", ast::VariableList{is_front}, ty.f32(), {Return(1.0f)},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(front_facing) must be 'bool'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) {
-  // struct MyInputs {
-  //   @builtin(front_facing) pos: f32;
-  // };
-  // @stage(fragment)
-  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
-
-  auto* s = Structure(
-      "MyInputs", {Member("pos", ty.f32(),
-                          ast::AttributeList{Builtin(
-                              Source{{12, 34}}, ast::Builtin::kFrontFacing)})});
-  Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of builtin(front_facing) must be 'bool'");
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) {
-  auto* builtin = Call("length", 1.0f);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec2) {
-  auto* builtin = Call("length", vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec3) {
-  auto* builtin = Call("length", vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec4) {
-  auto* builtin = Call("length", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Scalar) {
-  auto* builtin = Call("distance", 1.0f, 1.0f);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec2) {
-  auto* builtin =
-      Call("distance", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec3) {
-  auto* builtin = Call("distance", vec3<f32>(1.0f, 1.0f, 1.0f),
-                       vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec4) {
-  auto* builtin = Call("distance", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
-                       vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat2x2) {
-  auto* builtin = Call(
-      "determinant", mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat3x3) {
-  auto* builtin = Call("determinant", mat3x3<f32>(vec3<f32>(1.0f, 1.0f, 1.0f),
-                                                  vec3<f32>(1.0f, 1.0f, 1.0f),
-                                                  vec3<f32>(1.0f, 1.0f, 1.0f)));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat4x4) {
-  auto* builtin =
-      Call("determinant", mat4x4<f32>(vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
-                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
-                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
-                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f)));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Frexp_Scalar) {
-  auto* builtin = Call("frexp", 1.0f);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(members[1]->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec2) {
-  auto* builtin = Call("frexp", vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 2u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec3) {
-  auto* builtin = Call("frexp", vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec4) {
-  auto* builtin = Call("frexp", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Modf_Scalar) {
-  auto* builtin = Call("modf", 1.0f);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(members[1]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Modf_Vec2) {
-  auto* builtin = Call("modf", vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 2u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Modf_Vec3) {
-  auto* builtin = Call("modf", vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Modf_Vec4) {
-  auto* builtin = Call("modf", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
-  ASSERT_TRUE(res_ty != nullptr);
-  auto& members = res_ty->Members();
-  ASSERT_EQ(members.size(), 2u);
-  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
-  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
-  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Cross_Float_Vec3) {
-  auto* builtin =
-      Call("cross", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec2) {
-  auto* builtin = Call("dot", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec3) {
-  auto* builtin =
-      Call("dot", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec4) {
-  auto* builtin = Call("dot", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
-                       vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Float_Scalar) {
-  auto* builtin = Call("select", Expr(1.0f), Expr(1.0f), Expr(true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Integer_Scalar) {
-  auto* builtin = Call("select", Expr(1), Expr(1), Expr(true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Boolean_Scalar) {
-  auto* builtin = Call("select", Expr(true), Expr(true), Expr(true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Float_Vec2) {
-  auto* builtin = Call("select", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
-                       vec2<bool>(true, true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Integer_Vec2) {
-  auto* builtin =
-      Call("select", vec2<int>(1, 1), vec2<int>(1, 1), vec2<bool>(true, true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverBuiltinsValidationTest, Select_Boolean_Vec2) {
-  auto* builtin = Call("select", vec2<bool>(true, true), vec2<bool>(true, true),
-                       vec2<bool>(true, true));
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-template <typename T>
-class ResolverBuiltinsValidationTestWithParams
-    : public resolver::TestHelper,
-      public testing::TestWithParam<T> {};
-
-using FloatAllMatching =
-    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
-
-TEST_P(FloatAllMatching, Scalar) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(Expr(1.0f));
-  }
-  auto* builtin = Call(name, params);
-  Func("func", {}, ty.void_(), {CallStmt(builtin)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->Is<sem::F32>());
-}
-
-TEST_P(FloatAllMatching, Vec2) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec2<f32>(1.0f, 1.0f));
-  }
-  auto* builtin = Call(name, params);
-  Func("func", {}, ty.void_(), {CallStmt(builtin)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
-}
-
-TEST_P(FloatAllMatching, Vec3) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec3<f32>(1.0f, 1.0f, 1.0f));
-  }
-  auto* builtin = Call(name, params);
-  Func("func", {}, ty.void_(), {CallStmt(builtin)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
-}
-
-TEST_P(FloatAllMatching, Vec4) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  }
-  auto* builtin = Call(name, params);
-  Func("func", {}, ty.void_(), {CallStmt(builtin)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         FloatAllMatching,
-                         ::testing::Values(std::make_tuple("abs", 1),
-                                           std::make_tuple("acos", 1),
-                                           std::make_tuple("asin", 1),
-                                           std::make_tuple("atan", 1),
-                                           std::make_tuple("atan2", 2),
-                                           std::make_tuple("ceil", 1),
-                                           std::make_tuple("clamp", 3),
-                                           std::make_tuple("cos", 1),
-                                           std::make_tuple("cosh", 1),
-                                           std::make_tuple("dpdx", 1),
-                                           std::make_tuple("dpdxCoarse", 1),
-                                           std::make_tuple("dpdxFine", 1),
-                                           std::make_tuple("dpdy", 1),
-                                           std::make_tuple("dpdyCoarse", 1),
-                                           std::make_tuple("dpdyFine", 1),
-                                           std::make_tuple("exp", 1),
-                                           std::make_tuple("exp2", 1),
-                                           std::make_tuple("floor", 1),
-                                           std::make_tuple("fma", 3),
-                                           std::make_tuple("fract", 1),
-                                           std::make_tuple("fwidth", 1),
-                                           std::make_tuple("fwidthCoarse", 1),
-                                           std::make_tuple("fwidthFine", 1),
-                                           std::make_tuple("inverseSqrt", 1),
-                                           std::make_tuple("log", 1),
-                                           std::make_tuple("log2", 1),
-                                           std::make_tuple("max", 2),
-                                           std::make_tuple("min", 2),
-                                           std::make_tuple("mix", 3),
-                                           std::make_tuple("pow", 2),
-                                           std::make_tuple("round", 1),
-                                           std::make_tuple("sign", 1),
-                                           std::make_tuple("sin", 1),
-                                           std::make_tuple("sinh", 1),
-                                           std::make_tuple("smoothStep", 3),
-                                           std::make_tuple("sqrt", 1),
-                                           std::make_tuple("step", 2),
-                                           std::make_tuple("tan", 1),
-                                           std::make_tuple("tanh", 1),
-                                           std::make_tuple("trunc", 1)));
-
-using IntegerAllMatching =
-    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
-
-TEST_P(IntegerAllMatching, ScalarUnsigned) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(Construct<uint32_t>(1));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->Is<sem::U32>());
-}
-
-TEST_P(IntegerAllMatching, Vec2Unsigned) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec2<uint32_t>(1u, 1u));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
-}
-
-TEST_P(IntegerAllMatching, Vec3Unsigned) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec3<uint32_t>(1u, 1u, 1u));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
-}
-
-TEST_P(IntegerAllMatching, Vec4Unsigned) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec4<uint32_t>(1u, 1u, 1u, 1u));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
-}
-
-TEST_P(IntegerAllMatching, ScalarSigned) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(Construct<int32_t>(1));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->Is<sem::I32>());
-}
-
-TEST_P(IntegerAllMatching, Vec2Signed) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec2<int32_t>(1, 1));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
-}
-
-TEST_P(IntegerAllMatching, Vec3Signed) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec3<int32_t>(1, 1, 1));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
-}
-
-TEST_P(IntegerAllMatching, Vec4Signed) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec4<int32_t>(1, 1, 1, 1));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         IntegerAllMatching,
-                         ::testing::Values(std::make_tuple("abs", 1),
-                                           std::make_tuple("clamp", 3),
-                                           std::make_tuple("countOneBits", 1),
-                                           std::make_tuple("max", 2),
-                                           std::make_tuple("min", 2),
-                                           std::make_tuple("reverseBits", 1)));
-
-using BooleanVectorInput =
-    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
-
-TEST_P(BooleanVectorInput, Vec2) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec2<bool>(true, true));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(BooleanVectorInput, Vec3) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec3<bool>(true, true, true));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(BooleanVectorInput, Vec4) {
-  std::string name = std::get<0>(GetParam());
-  uint32_t num_params = std::get<1>(GetParam());
-
-  ast::ExpressionList params;
-  for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec4<bool>(true, true, true, true));
-  }
-  auto* builtin = Call(name, params);
-  WrapInFunction(builtin);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         BooleanVectorInput,
-                         ::testing::Values(std::make_tuple("all", 1),
-                                           std::make_tuple("any", 1)));
-
-using DataPacking4x8 = ResolverBuiltinsValidationTestWithParams<std::string>;
-
-TEST_P(DataPacking4x8, Float_Vec4) {
-  auto name = GetParam();
-  auto* builtin = Call(name, vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
-  WrapInFunction(builtin);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         DataPacking4x8,
-                         ::testing::Values("pack4x8snorm", "pack4x8unorm"));
-
-using DataPacking2x16 = ResolverBuiltinsValidationTestWithParams<std::string>;
-
-TEST_P(DataPacking2x16, Float_Vec2) {
-  auto name = GetParam();
-  auto* builtin = Call(name, vec2<f32>(1.0f, 1.0f));
-  WrapInFunction(builtin);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
-                         DataPacking2x16,
-                         ::testing::Values("pack2x16snorm",
-                                           "pack2x16unorm",
-                                           "pack2x16float"));
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/call_test.cc b/src/resolver/call_test.cc
deleted file mode 100644
index 9a3cc85..0000000
--- a/src/resolver/call_test.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <int N, typename T>
-using vec = builder::vec<N, T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <int N, int M, typename T>
-using mat = builder::mat<N, M, T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat2x3 = builder::mat2x3<T>;
-template <typename T>
-using mat3x2 = builder::mat3x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <typename T, int ID = 0>
-using alias = builder::alias<T, ID>;
-template <typename T>
-using alias1 = builder::alias1<T>;
-template <typename T>
-using alias2 = builder::alias2<T>;
-template <typename T>
-using alias3 = builder::alias3<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-using ResolverCallTest = ResolverTest;
-
-struct Params {
-  builder::ast_expr_func_ptr create_value;
-  builder::ast_type_func_ptr create_type;
-};
-
-template <typename T>
-constexpr Params ParamsFor() {
-  return Params{DataType<T>::Expr, DataType<T>::AST};
-}
-
-static constexpr Params all_param_types[] = {
-    ParamsFor<bool>(),         //
-    ParamsFor<u32>(),          //
-    ParamsFor<i32>(),          //
-    ParamsFor<f32>(),          //
-    ParamsFor<vec3<bool>>(),   //
-    ParamsFor<vec3<i32>>(),    //
-    ParamsFor<vec3<u32>>(),    //
-    ParamsFor<vec3<f32>>(),    //
-    ParamsFor<mat3x3<f32>>(),  //
-    ParamsFor<mat2x3<f32>>(),  //
-    ParamsFor<mat3x2<f32>>()   //
-};
-
-TEST_F(ResolverCallTest, Valid) {
-  ast::VariableList params;
-  ast::ExpressionList args;
-  for (auto& p : all_param_types) {
-    params.push_back(Param(Sym(), p.create_type(*this)));
-    args.push_back(p.create_value(*this, 0));
-  }
-
-  auto* func = Func("foo", std::move(params), ty.f32(), {Return(1.23f)});
-  auto* call_expr = Call("foo", std::move(args));
-  WrapInFunction(call_expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* call = Sem().Get(call_expr);
-  EXPECT_NE(call, nullptr);
-  EXPECT_EQ(call->Target(), Sem().Get(func));
-}
-
-TEST_F(ResolverCallTest, OutOfOrder) {
-  auto* call_expr = Call("b");
-  Func("a", {}, ty.void_(), {CallStmt(call_expr)});
-  auto* b = Func("b", {}, ty.void_(), {});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* call = Sem().Get(call_expr);
-  EXPECT_NE(call, nullptr);
-  EXPECT_EQ(call->Target(), Sem().Get(b));
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/call_validation_test.cc b/src/resolver/call_validation_test.cc
deleted file mode 100644
index 71cca02..0000000
--- a/src/resolver/call_validation_test.cc
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverCallValidationTest = ResolverTest;
-
-TEST_F(ResolverCallValidationTest, TooFewArgs) {
-  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
-       {Return()});
-  auto* call = Call(Source{{12, 34}}, "foo", 1);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: too few arguments in call to 'foo', expected 2, got 1");
-}
-
-TEST_F(ResolverCallValidationTest, TooManyArgs) {
-  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
-       {Return()});
-  auto* call = Call(Source{{12, 34}}, "foo", 1, 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: too many arguments in call to 'foo', expected 2, got 3");
-}
-
-TEST_F(ResolverCallValidationTest, MismatchedArgs) {
-  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
-       {Return()});
-  auto* call = Call("foo", Expr(Source{{12, 34}}, true), 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type mismatch for argument 1 in call to 'foo', "
-            "expected 'i32', got 'bool'");
-}
-
-TEST_F(ResolverCallValidationTest, UnusedRetval) {
-  // fn func() -> f32 { return 1.0; }
-  // fn main() {func(); return; }
-
-  Func("func", {}, ty.f32(), {Return(Expr(1.0f))}, {});
-
-  Func("main", {}, ty.void_(),
-       {
-           CallStmt(Source{{12, 34}}, Call("func")),
-           Return(),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverCallValidationTest, PointerArgument_VariableIdentExpr) {
-  // fn foo(p: ptr<function, i32>) {}
-  // fn main() {
-  //   var z: i32 = 1;
-  //   foo(&z);
-  // }
-  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-  Func("foo", {param}, ty.void_(), {});
-  Func("main", {}, ty.void_(),
-       {
-           Decl(Var("z", ty.i32(), Expr(1))),
-           CallStmt(Call("foo", AddressOf(Source{{12, 34}}, Expr("z")))),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverCallValidationTest, PointerArgument_ConstIdentExpr) {
-  // fn foo(p: ptr<function, i32>) {}
-  // fn main() {
-  //   let z: i32 = 1;
-  //   foo(&z);
-  // }
-  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-  Func("foo", {param}, ty.void_(), {});
-  Func("main", {}, ty.void_(),
-       {
-           Decl(Const("z", ty.i32(), Expr(1))),
-           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_NotIdentExprVar) {
-  // struct S { m: i32; };
-  // fn foo(p: ptr<function, i32>) {}
-  // fn main() {
-  //   var v: S;
-  //   foo(&v.m);
-  // }
-  auto* S = Structure("S", {Member("m", ty.i32())});
-  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-  Func("foo", {param}, ty.void_(), {});
-  Func("main", {}, ty.void_(),
-       {
-           Decl(Var("v", ty.Of(S))),
-           CallStmt(Call(
-               "foo", AddressOf(Source{{12, 34}}, MemberAccessor("v", "m")))),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected an address-of expression of a variable "
-            "identifier expression or a function parameter");
-}
-
-TEST_F(ResolverCallValidationTest, PointerArgument_AddressOfMemberAccessor) {
-  // struct S { m: i32; };
-  // fn foo(p: ptr<function, i32>) {}
-  // fn main() {
-  //   let v: S = S();
-  //   foo(&v.m);
-  // }
-  auto* S = Structure("S", {Member("m", ty.i32())});
-  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-  Func("foo", {param}, ty.void_(), {});
-  Func("main", {}, ty.void_(),
-       {
-           Decl(Const("v", ty.Of(S), Construct(ty.Of(S)))),
-           CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}},
-                                               MemberAccessor("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>) {
-  // foo(p);
-  // }
-  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
-       ty.void_(), {});
-  Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
-       ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParamWithMain) {
-  // fn foo(p: ptr<function, i32>) {}
-  // fn bar(p: ptr<function, i32>) {
-  // foo(p);
-  // }
-  // @stage(fragment)
-  // fn main() {
-  //   var v: i32;
-  //   bar(&v);
-  // }
-  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
-       ty.void_(), {});
-  Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
-       ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(Var("v", ty.i32(), Expr(1))),
-           CallStmt(Call("foo", AddressOf(Expr("v")))),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverCallValidationTest, LetPointer) {
-  // fn x(p : ptr<function, i32>) -> i32 {}
-  // @stage(fragment)
-  // fn main() {
-  //   var v: i32;
-  //   let p: ptr<function, i32> = &v;
-  //   var c: i32 = x(p);
-  // }
-  Func("x", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
-       ty.void_(), {});
-  auto* v = Var("v", ty.i32());
-  auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
-                  AddressOf(v));
-  auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
-                Call("x", Expr(Source{{12, 34}}, p)));
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(v),
-           Decl(p),
-           Decl(c),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected an address-of expression of a variable "
-            "identifier expression or a function parameter");
-}
-
-TEST_F(ResolverCallValidationTest, LetPointerPrivate) {
-  // let p: ptr<private, i32> = &v;
-  // fn foo(p : ptr<private, i32>) -> i32 {}
-  // var v: i32;
-  // @stage(fragment)
-  // fn main() {
-  //   var c: i32 = foo(p);
-  // }
-  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kPrivate))},
-       ty.void_(), {});
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
-                  AddressOf(v));
-  auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
-                Call("foo", Expr(Source{{12, 34}}, p)));
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(p),
-           Decl(c),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected an address-of expression of a variable "
-            "identifier expression or a function parameter");
-}
-
-TEST_F(ResolverCallValidationTest, CallVariable) {
-  // var v : i32;
-  // fn f() {
-  //   v();
-  // }
-  Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  Func("f", {}, ty.void_(), {CallStmt(Call(Source{{12, 34}}, "v"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(error: cannot call variable 'v'
-note: 'v' declared here)");
-}
-
-TEST_F(ResolverCallValidationTest, CallVariableShadowsFunction) {
-  // fn x() {}
-  // fn f() {
-  //   var x : i32;
-  //   x();
-  // }
-  Func("x", {}, ty.void_(), {});
-  Func("f", {}, ty.void_(),
-       {
-           Decl(Var(Source{{56, 78}}, "x", ty.i32())),
-           CallStmt(Call(Source{{12, 34}}, "x")),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(error: cannot call variable 'x'
-56:78 note: 'x' declared here)");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/compound_statement_test.cc b/src/resolver/compound_statement_test.cc
deleted file mode 100644
index 27e2126..0000000
--- a/src/resolver/compound_statement_test.cc
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/if_statement.h"
-#include "src/sem/loop_statement.h"
-#include "src/sem/switch_statement.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverCompoundStatementTest = ResolverTest;
-
-TEST_F(ResolverCompoundStatementTest, FunctionBlock) {
-  // fn F() {
-  //   var x : 32;
-  // }
-  auto* stmt = Decl(Var("x", ty.i32()));
-  auto* f = Func("F", {}, ty.void_(), {stmt});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* s = Sem().Get(stmt);
-  ASSERT_NE(s, nullptr);
-  ASSERT_NE(s->Block(), nullptr);
-  ASSERT_TRUE(s->Block()->Is<sem::FunctionBlockStatement>());
-  EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
-  EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
-  EXPECT_EQ(s->Function()->Declaration(), f);
-  EXPECT_EQ(s->Block()->Parent(), nullptr);
-}
-
-TEST_F(ResolverCompoundStatementTest, Block) {
-  // fn F() {
-  //   {
-  //     var x : 32;
-  //   }
-  // }
-  auto* stmt = Decl(Var("x", ty.i32()));
-  auto* block = Block(stmt);
-  auto* f = Func("F", {}, ty.void_(), {block});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  {
-    auto* s = Sem().Get(block);
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::BlockStatement>());
-    EXPECT_EQ(s, s->Block());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-  {
-    auto* s = Sem().Get(stmt);
-    ASSERT_NE(s, nullptr);
-    ASSERT_NE(s->Block(), nullptr);
-    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Block()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-    ASSERT_TRUE(s->Block()->Parent()->Is<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Function()->Declaration(), f);
-    EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
-  }
-}
-
-TEST_F(ResolverCompoundStatementTest, Loop) {
-  // fn F() {
-  //   loop {
-  //     break;
-  //     continuing {
-  //       stmt;
-  //     }
-  //   }
-  // }
-  auto* brk = Break();
-  auto* stmt = Ignore(1);
-  auto* loop = Loop(Block(brk), Block(stmt));
-  auto* f = Func("F", {}, ty.void_(), {loop});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  {
-    auto* s = Sem().Get(loop);
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::LoopStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* s = Sem().Get(brk);
-    ASSERT_NE(s, nullptr);
-    ASSERT_NE(s->Block(), nullptr);
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::LoopBlockStatement>());
-
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::LoopStatement>());
-    EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()));
-
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_TRUE(
-        Is<sem::FunctionBlockStatement>(s->Parent()->Parent()->Parent()));
-
-    EXPECT_EQ(s->Function()->Declaration(), f);
-
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), nullptr);
-  }
-  {
-    auto* s = Sem().Get(stmt);
-    ASSERT_NE(s, nullptr);
-    ASSERT_NE(s->Block(), nullptr);
-    EXPECT_EQ(s->Parent(), s->Block());
-
-    EXPECT_EQ(s->Parent(),
-              s->FindFirstParent<sem::LoopContinuingBlockStatement>());
-    EXPECT_TRUE(Is<sem::LoopContinuingBlockStatement>(s->Parent()));
-
-    EXPECT_EQ(s->Parent()->Parent(),
-              s->FindFirstParent<sem::LoopBlockStatement>());
-    EXPECT_TRUE(Is<sem::LoopBlockStatement>(s->Parent()->Parent()));
-
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::LoopStatement>());
-    EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()->Parent()));
-
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(
-        s->Parent()->Parent()->Parent()->Parent()));
-    EXPECT_EQ(s->Function()->Declaration(), f);
-
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent()->Parent(), nullptr);
-  }
-}
-
-TEST_F(ResolverCompoundStatementTest, ForLoop) {
-  // fn F() {
-  //   for (var i : u32; true; i = i + 1u) {
-  //     return;
-  //   }
-  // }
-  auto* init = Decl(Var("i", ty.u32()));
-  auto* cond = Expr(true);
-  auto* cont = Assign("i", Add("i", 1u));
-  auto* stmt = Return();
-  auto* body = Block(stmt);
-  auto* for_ = For(init, cond, cont, body);
-  auto* f = Func("F", {}, ty.void_(), {for_});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  {
-    auto* s = Sem().Get(for_);
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::ForLoopStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* s = Sem().Get(init);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
-    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
-    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
-  }
-  {  // Condition expression's statement is the for-loop itself
-    auto* e = Sem().Get(cond);
-    ASSERT_NE(e, nullptr);
-    auto* s = e->Stmt();
-    ASSERT_NE(s, nullptr);
-    ASSERT_TRUE(Is<sem::ForLoopStatement>(s));
-    ASSERT_NE(s->Parent(), nullptr);
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()));
-  }
-  {
-    auto* s = Sem().Get(cont);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
-    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
-    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
-  }
-  {
-    auto* s = Sem().Get(stmt);
-    ASSERT_NE(s, nullptr);
-    ASSERT_NE(s->Block(), nullptr);
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::LoopBlockStatement>());
-    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()->Parent()));
-    EXPECT_EQ(s->Block()->Parent(),
-              s->FindFirstParent<sem::ForLoopStatement>());
-    ASSERT_TRUE(
-        Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
-    EXPECT_EQ(s->Block()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Function()->Declaration(), f);
-    EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
-  }
-}
-
-TEST_F(ResolverCompoundStatementTest, If) {
-  // fn F() {
-  //   if (cond_a) {
-  //     stat_a;
-  //   } else if (cond_b) {
-  //     stat_b;
-  //   } else {
-  //     stat_c;
-  //   }
-  // }
-
-  auto* cond_a = Expr(true);
-  auto* stmt_a = Ignore(1);
-  auto* cond_b = Expr(true);
-  auto* stmt_b = Ignore(1);
-  auto* stmt_c = Ignore(1);
-  auto* if_stmt = If(cond_a, Block(stmt_a), Else(cond_b, Block(stmt_b)),
-                     Else(nullptr, Block(stmt_c)));
-  WrapInFunction(if_stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  {
-    auto* s = Sem().Get(if_stmt);
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::IfStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* e = Sem().Get(cond_a);
-    ASSERT_NE(e, nullptr);
-    auto* s = e->Stmt();
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::IfStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* s = Sem().Get(stmt_a);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::IfStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-  {
-    auto* e = Sem().Get(cond_b);
-    ASSERT_NE(e, nullptr);
-    auto* s = e->Stmt();
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::ElseStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::IfStatement>());
-    EXPECT_EQ(s->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent()->Parent(), s->Block());
-  }
-  {
-    auto* s = Sem().Get(stmt_b);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::IfStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-  {
-    auto* s = Sem().Get(stmt_c);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::IfStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-}
-
-TEST_F(ResolverCompoundStatementTest, Switch) {
-  // fn F() {
-  //   switch (expr) {
-  //     case 1: {
-  //        stmt_a;
-  //     }
-  //     case 2: {
-  //        stmt_b;
-  //     }
-  //     default: {
-  //        stmt_c;
-  //     }
-  //   }
-  // }
-
-  auto* expr = Expr(5);
-  auto* stmt_a = Ignore(1);
-  auto* stmt_b = Ignore(1);
-  auto* stmt_c = Ignore(1);
-  auto* swi = Switch(expr, Case(Expr(1), Block(stmt_a)),
-                     Case(Expr(2), Block(stmt_b)), DefaultCase(Block(stmt_c)));
-  WrapInFunction(swi);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  {
-    auto* s = Sem().Get(swi);
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::SwitchStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* e = Sem().Get(expr);
-    ASSERT_NE(e, nullptr);
-    auto* s = e->Stmt();
-    ASSERT_NE(s, nullptr);
-    EXPECT_TRUE(s->Is<sem::SwitchStatement>());
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-  }
-  {
-    auto* s = Sem().Get(stmt_a);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::SwitchStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-  {
-    auto* s = Sem().Get(stmt_b);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::SwitchStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-  {
-    auto* s = Sem().Get(stmt_c);
-    ASSERT_NE(s, nullptr);
-    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
-    EXPECT_EQ(s->Parent(), s->Block());
-    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::SwitchStatement>());
-    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
-              s->FindFirstParent<sem::FunctionBlockStatement>());
-  }
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc
deleted file mode 100644
index 40d5e5c..0000000
--- a/src/resolver/control_block_validation_test.cc
+++ /dev/null
@@ -1,364 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace {
-
-class ResolverControlBlockValidationTest : public resolver::TestHelper,
-                                           public testing::Test {};
-
-TEST_F(ResolverControlBlockValidationTest,
-       SwitchSelectorExpressionNoneIntegerType_Fail) {
-  // var a : f32 = 3.14;
-  // switch (a) {
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.f32(), Expr(3.14f));
-
-  auto* block = Block(Decl(var), Switch(Expr(Source{{12, 34}}, "a"),  //
-                                        DefaultCase()));
-
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: switch statement selector expression must be of a "
-            "scalar integer type");
-}
-
-TEST_F(ResolverControlBlockValidationTest, SwitchWithoutDefault_Fail) {
-  // var a : i32 = 2;
-  // switch (a) {
-  //   case 1: {}
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  auto* block = Block(Decl(var),                     //
-                      Switch(Source{{12, 34}}, "a",  //
-                             Case(Expr(1))));
-
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: switch statement must have a default clause");
-}
-
-TEST_F(ResolverControlBlockValidationTest, SwitchWithTwoDefault_Fail) {
-  // var a : i32 = 2;
-  // switch (a) {
-  //   default: {}
-  //   case 1: {}
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  auto* block = Block(Decl(var),             //
-                      Switch("a",            //
-                             DefaultCase(),  //
-                             Case(Expr(1)),  //
-                             DefaultCase(Source{{12, 34}})));
-
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: switch statement must have exactly one default clause");
-}
-
-TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
-  // loop {
-  //   if (false) { break; }
-  //   var z: i32;
-  //   continue;
-  //   z = 1;
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* cont = Continue();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(
-      Loop(Block(If(false, Block(Break())), decl_z, cont, assign_z)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       UnreachableCode_Loop_continue_InBlocks) {
-  // loop {
-  //   if (false) { break; }
-  //   var z: i32;
-  //   {{{continue;}}}
-  //   z = 1;
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* cont = Continue();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(Loop(Block(If(false, Block(Break())), decl_z,
-                            Block(Block(Block(cont))), assign_z)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
-  // for (;false;) {
-  //   var z: i32;
-  //   continue;
-  //   z = 1;
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* cont = Continue();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(For(nullptr, false, nullptr,  //
-                     Block(decl_z, cont, assign_z)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       UnreachableCode_ForLoop_continue_InBlocks) {
-  // for (;false;) {
-  //   var z: i32;
-  //   {{{continue;}}}
-  //   z = 1;
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* cont = Continue();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(For(nullptr, false, nullptr,
-                     Block(decl_z, Block(Block(Block(cont))), assign_z)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
-  // switch (1) {
-  //   case 1: {
-  //     var z: i32;
-  //     break;
-  //     z = 1;
-  //   default: {}
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* brk = Break();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(                                                //
-      Block(Switch(1,                                            //
-                   Case(Expr(1), Block(decl_z, brk, assign_z)),  //
-                   DefaultCase())));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(brk)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
-  // loop {
-  //   switch (1) {
-  //     case 1: { {{{break;}}} var a : u32 = 2;}
-  //     default: {}
-  //   }
-  //   break;
-  // }
-  auto* decl_z = Decl(Var("z", ty.i32()));
-  auto* brk = Break();
-  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
-  WrapInFunction(Loop(Block(
-      Switch(1,  //
-             Case(Expr(1), Block(decl_z, Block(Block(Block(brk))), assign_z)),
-             DefaultCase()),  //
-      Break())));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
-  EXPECT_TRUE(Sem().Get(brk)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       SwitchConditionTypeMustMatchSelectorType2_Fail) {
-  // var a : u32 = 2;
-  // switch (a) {
-  //   case 1: {}
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  auto* block = Block(Decl(var), Switch("a",                                 //
-                                        Case(Source{{12, 34}}, {Expr(1u)}),  //
-                                        DefaultCase()));
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: the case selector values must have the same type as "
-            "the selector expression.");
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       SwitchConditionTypeMustMatchSelectorType_Fail) {
-  // var a : u32 = 2;
-  // switch (a) {
-  //   case -1: {}
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.u32(), Expr(2u));
-
-  auto* block = Block(Decl(var),                                  //
-                      Switch("a",                                 //
-                             Case(Source{{12, 34}}, {Expr(-1)}),  //
-                             DefaultCase()));
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: the case selector values must have the same type as "
-            "the selector expression.");
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       NonUniqueCaseSelectorValueUint_Fail) {
-  // var a : u32 = 3;
-  // switch (a) {
-  //   case 0u: {}
-  //   case 2u, 3u, 2u: {}
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.u32(), Expr(3u));
-
-  auto* block = Block(Decl(var),   //
-                      Switch("a",  //
-                             Case(Expr(0u)),
-                             Case({
-                                 Expr(Source{{12, 34}}, 2u),
-                                 Expr(3u),
-                                 Expr(Source{{56, 78}}, 2u),
-                             }),
-                             DefaultCase()));
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: duplicate switch case '2'\n"
-            "12:34 note: previous case declared here");
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       NonUniqueCaseSelectorValueSint_Fail) {
-  // var a : i32 = 2;
-  // switch (a) {
-  //   case -10: {}
-  //   case 0,1,2,-10: {}
-  //   default: {}
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  auto* block = Block(Decl(var),   //
-                      Switch("a",  //
-                             Case(Expr(Source{{12, 34}}, -10)),
-                             Case({
-                                 Expr(0),
-                                 Expr(1),
-                                 Expr(2),
-                                 Expr(Source{{56, 78}}, -10),
-                             }),
-                             DefaultCase()));
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: duplicate switch case '-10'\n"
-            "12:34 note: previous case declared here");
-}
-
-TEST_F(ResolverControlBlockValidationTest,
-       LastClauseLastStatementIsFallthrough_Fail) {
-  // var a : i32 = 2;
-  // switch (a) {
-  //   default: { fallthrough; }
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-  auto* fallthrough = create<ast::FallthroughStatement>(Source{{12, 34}});
-  auto* block = Block(Decl(var),   //
-                      Switch("a",  //
-                             DefaultCase(Block(fallthrough))));
-  WrapInFunction(block);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: a fallthrough statement must not be used in the last "
-            "switch case");
-}
-
-TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
-  // var a : i32 = 2;
-  // switch (a) {
-  //   default: {}
-  //   case 5: {}
-  // }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  auto* block = Block(Decl(var),                             //
-                      Switch("a",                            //
-                             DefaultCase(Source{{12, 34}}),  //
-                             Case(Expr(5))));
-  WrapInFunction(block);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverControlBlockValidationTest, SwitchCaseAlias_Pass) {
-  // type MyInt = u32;
-  // var v: MyInt;
-  // switch(v){
-  //   default: {}
-  // }
-
-  auto* my_int = Alias("MyInt", ty.u32());
-  auto* var = Var("a", ty.Of(my_int), Expr(2u));
-  auto* block = Block(Decl(var),  //
-                      Switch("a", DefaultCase(Source{{12, 34}})));
-
-  WrapInFunction(block);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/resolver/dependency_graph.cc b/src/resolver/dependency_graph.cc
deleted file mode 100644
index bf82f2d..0000000
--- a/src/resolver/dependency_graph.cc
+++ /dev/null
@@ -1,736 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/dependency_graph.h"
-
-#include <string>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/traverse_expressions.h"
-#include "src/scope_stack.h"
-#include "src/sem/builtin.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/utils/unique_vector.h"
-
-#define TINT_DUMP_DEPENDENCY_GRAPH 0
-
-namespace tint {
-namespace resolver {
-namespace {
-
-// Forward declaration
-struct Global;
-
-/// Dependency describes how one global depends on another global
-struct DependencyInfo {
-  /// The source of the symbol that forms the dependency
-  Source source;
-  /// A string describing how the dependency is referenced. e.g. 'calls'
-  const char* action = nullptr;
-};
-
-/// DependencyEdge describes the two Globals used to define a dependency
-/// relationship.
-struct DependencyEdge {
-  /// The Global that depends on #to
-  const Global* from;
-  /// The Global that is depended on by #from
-  const Global* to;
-};
-
-/// DependencyEdgeCmp implements the contracts of std::equal_to<DependencyEdge>
-/// and std::hash<DependencyEdge>.
-struct DependencyEdgeCmp {
-  /// Equality operator
-  bool operator()(const DependencyEdge& lhs, const DependencyEdge& rhs) const {
-    return lhs.from == rhs.from && lhs.to == rhs.to;
-  }
-  /// Hashing operator
-  inline std::size_t operator()(const DependencyEdge& d) const {
-    return utils::Hash(d.from, d.to);
-  }
-};
-
-/// A map of DependencyEdge to DependencyInfo
-using DependencyEdges = std::unordered_map<DependencyEdge,
-                                           DependencyInfo,
-                                           DependencyEdgeCmp,
-                                           DependencyEdgeCmp>;
-
-/// Global describes a module-scope variable, type or function.
-struct Global {
-  explicit Global(const ast::Node* n) : node(n) {}
-
-  /// The declaration ast::Node
-  const ast::Node* node;
-  /// A list of dependencies that this global depends on
-  std::vector<Global*> deps;
-};
-
-/// A map of global name to Global
-using GlobalMap = std::unordered_map<Symbol, Global*>;
-
-/// Raises an ICE that a global ast::Node type was not handled by this system.
-void UnhandledNode(diag::List& diagnostics, const ast::Node* node) {
-  TINT_ICE(Resolver, diagnostics)
-      << "unhandled node type: " << node->TypeInfo().name;
-}
-
-/// Raises an error diagnostic with the given message and source.
-void AddError(diag::List& diagnostics,
-              const std::string& msg,
-              const Source& source) {
-  diagnostics.add_error(diag::System::Resolver, msg, source);
-}
-
-/// Raises a note diagnostic with the given message and source.
-void AddNote(diag::List& diagnostics,
-             const std::string& msg,
-             const Source& source) {
-  diagnostics.add_note(diag::System::Resolver, msg, source);
-}
-
-/// DependencyScanner is used to traverse a module to build the list of
-/// global-to-global dependencies.
-class DependencyScanner {
- public:
-  /// Constructor
-  /// @param syms the program symbol table
-  /// @param globals_by_name map of global symbol to Global pointer
-  /// @param diagnostics diagnostic messages, appended with any errors found
-  /// @param graph the dependency graph to populate with resolved symbols
-  /// @param edges the map of globals-to-global dependency edges, which will
-  /// be populated by calls to Scan()
-  DependencyScanner(const SymbolTable& syms,
-                    const GlobalMap& globals_by_name,
-                    diag::List& diagnostics,
-                    DependencyGraph& graph,
-                    DependencyEdges& edges)
-      : symbols_(syms),
-        globals_(globals_by_name),
-        diagnostics_(diagnostics),
-        graph_(graph),
-        dependency_edges_(edges) {
-    // Register all the globals at global-scope
-    for (auto it : globals_by_name) {
-      scope_stack_.Set(it.first, it.second->node);
-    }
-  }
-
-  /// Walks the global declarations, resolving symbols, and determining the
-  /// dependencies of each global.
-  void Scan(Global* global) {
-    TINT_SCOPED_ASSIGNMENT(current_global_, global);
-    Switch(
-        global->node,
-        [&](const ast::Struct* str) {
-          Declare(str->name, str);
-          for (auto* member : str->members) {
-            TraverseType(member->type);
-          }
-        },
-        [&](const ast::Alias* alias) {
-          Declare(alias->name, alias);
-          TraverseType(alias->type);
-        },
-        [&](const ast::Function* func) {
-          Declare(func->symbol, func);
-          TraverseAttributes(func->attributes);
-          TraverseFunction(func);
-        },
-        [&](const ast::Variable* var) {
-          Declare(var->symbol, var);
-          TraverseType(var->type);
-          if (var->constructor) {
-            TraverseExpression(var->constructor);
-          }
-        },
-        [&](Default) { UnhandledNode(diagnostics_, global->node); });
-  }
-
- private:
-  /// Traverses the function, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseFunction(const ast::Function* func) {
-    // Perform symbol resolution on all the parameter types before registering
-    // the parameters themselves. This allows the case of declaring a parameter
-    // with the same identifier as its type.
-    for (auto* param : func->params) {
-      TraverseType(param->type);
-    }
-    // Resolve the return type
-    TraverseType(func->return_type);
-
-    // Push the scope stack for the parameters and function body.
-    scope_stack_.Push();
-    TINT_DEFER(scope_stack_.Pop());
-
-    for (auto* param : func->params) {
-      if (auto* shadows = scope_stack_.Get(param->symbol)) {
-        graph_.shadows.emplace(param, shadows);
-      }
-      Declare(param->symbol, param);
-    }
-    if (func->body) {
-      TraverseStatements(func->body->statements);
-    }
-  }
-
-  /// Traverses the statements, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseStatements(const ast::StatementList& stmts) {
-    for (auto* s : stmts) {
-      TraverseStatement(s);
-    }
-  }
-
-  /// Traverses the statement, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseStatement(const ast::Statement* stmt) {
-    if (!stmt) {
-      return;
-    }
-    Switch(
-        stmt,  //
-        [&](const ast::AssignmentStatement* a) {
-          TraverseExpression(a->lhs);
-          TraverseExpression(a->rhs);
-        },
-        [&](const ast::BlockStatement* b) {
-          scope_stack_.Push();
-          TINT_DEFER(scope_stack_.Pop());
-          TraverseStatements(b->statements);
-        },
-        [&](const ast::CallStatement* r) {  //
-          TraverseExpression(r->expr);
-        },
-        [&](const ast::ForLoopStatement* l) {
-          scope_stack_.Push();
-          TINT_DEFER(scope_stack_.Pop());
-          TraverseStatement(l->initializer);
-          TraverseExpression(l->condition);
-          TraverseStatement(l->continuing);
-          TraverseStatement(l->body);
-        },
-        [&](const ast::LoopStatement* l) {
-          scope_stack_.Push();
-          TINT_DEFER(scope_stack_.Pop());
-          TraverseStatements(l->body->statements);
-          TraverseStatement(l->continuing);
-        },
-        [&](const ast::IfStatement* i) {
-          TraverseExpression(i->condition);
-          TraverseStatement(i->body);
-          for (auto* e : i->else_statements) {
-            TraverseExpression(e->condition);
-            TraverseStatement(e->body);
-          }
-        },
-        [&](const ast::ReturnStatement* r) {  //
-          TraverseExpression(r->value);
-        },
-        [&](const ast::SwitchStatement* s) {
-          TraverseExpression(s->condition);
-          for (auto* c : s->body) {
-            for (auto* sel : c->selectors) {
-              TraverseExpression(sel);
-            }
-            TraverseStatement(c->body);
-          }
-        },
-        [&](const ast::VariableDeclStatement* v) {
-          if (auto* shadows = scope_stack_.Get(v->variable->symbol)) {
-            graph_.shadows.emplace(v->variable, shadows);
-          }
-          TraverseType(v->variable->type);
-          TraverseExpression(v->variable->constructor);
-          Declare(v->variable->symbol, v->variable);
-        },
-        [&](Default) {
-          if (!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
-                             ast::DiscardStatement,
-                             ast::FallthroughStatement>()) {
-            UnhandledNode(diagnostics_, stmt);
-          }
-        });
-  }
-
-  /// Adds the symbol definition to the current scope, raising an error if two
-  /// symbols collide within the same scope.
-  void Declare(Symbol symbol, const ast::Node* node) {
-    auto* old = scope_stack_.Set(symbol, node);
-    if (old != nullptr && node != old) {
-      auto name = symbols_.NameFor(symbol);
-      AddError(diagnostics_, "redeclaration of '" + name + "'", node->source);
-      AddNote(diagnostics_, "'" + name + "' previously declared here",
-              old->source);
-    }
-  }
-
-  /// Traverses the expression, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseExpression(const ast::Expression* root) {
-    if (!root) {
-      return;
-    }
-    ast::TraverseExpressions(
-        root, diagnostics_, [&](const ast::Expression* expr) {
-          Switch(
-              expr,
-              [&](const ast::IdentifierExpression* ident) {
-                AddDependency(ident, ident->symbol, "identifier", "references");
-              },
-              [&](const ast::CallExpression* call) {
-                if (call->target.name) {
-                  AddDependency(call->target.name, call->target.name->symbol,
-                                "function", "calls");
-                }
-                if (call->target.type) {
-                  TraverseType(call->target.type);
-                }
-              },
-              [&](const ast::BitcastExpression* cast) {
-                TraverseType(cast->type);
-              });
-          return ast::TraverseAction::Descend;
-        });
-  }
-
-  /// Traverses the type node, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseType(const ast::Type* ty) {
-    if (!ty) {
-      return;
-    }
-    Switch(
-        ty,  //
-        [&](const ast::Array* arr) {
-          TraverseType(arr->type);  //
-          TraverseExpression(arr->count);
-        },
-        [&](const ast::Atomic* atomic) {  //
-          TraverseType(atomic->type);
-        },
-        [&](const ast::Matrix* mat) {  //
-          TraverseType(mat->type);
-        },
-        [&](const ast::Pointer* ptr) {  //
-          TraverseType(ptr->type);
-        },
-        [&](const ast::TypeName* tn) {  //
-          AddDependency(tn, tn->name, "type", "references");
-        },
-        [&](const ast::Vector* vec) {  //
-          TraverseType(vec->type);
-        },
-        [&](const ast::SampledTexture* tex) {  //
-          TraverseType(tex->type);
-        },
-        [&](const ast::MultisampledTexture* tex) {  //
-          TraverseType(tex->type);
-        },
-        [&](Default) {
-          if (!ty->IsAnyOf<ast::Void, ast::Bool, ast::I32, ast::U32, ast::F32,
-                           ast::DepthTexture, ast::DepthMultisampledTexture,
-                           ast::StorageTexture, ast::ExternalTexture,
-                           ast::Sampler>()) {
-            UnhandledNode(diagnostics_, ty);
-          }
-        });
-  }
-
-  /// Traverses the attribute list, performing symbol resolution and
-  /// determining global dependencies.
-  void TraverseAttributes(const ast::AttributeList& attrs) {
-    for (auto* attr : attrs) {
-      TraverseAttribute(attr);
-    }
-  }
-
-  /// Traverses the attribute, performing symbol resolution and determining
-  /// global dependencies.
-  void TraverseAttribute(const ast::Attribute* attr) {
-    if (auto* wg = attr->As<ast::WorkgroupAttribute>()) {
-      TraverseExpression(wg->x);
-      TraverseExpression(wg->y);
-      TraverseExpression(wg->z);
-      return;
-    }
-    if (attr->IsAnyOf<
-            ast::BindingAttribute, ast::BuiltinAttribute, ast::GroupAttribute,
-            ast::IdAttribute, ast::InternalAttribute, ast::InterpolateAttribute,
-            ast::InvariantAttribute, ast::LocationAttribute,
-            ast::StageAttribute, ast::StrideAttribute,
-            ast::StructBlockAttribute, ast::StructMemberAlignAttribute,
-            ast::StructMemberOffsetAttribute,
-            ast::StructMemberSizeAttribute>()) {
-      return;
-    }
-
-    UnhandledNode(diagnostics_, attr);
-  }
-
-  /// Adds the dependency from `from` to `to`, erroring if `to` cannot be
-  /// resolved.
-  void AddDependency(const ast::Node* from,
-                     Symbol to,
-                     const char* use,
-                     const char* action) {
-    auto* resolved = scope_stack_.Get(to);
-    if (!resolved) {
-      if (!IsBuiltin(to)) {
-        UnknownSymbol(to, from->source, use);
-        return;
-      }
-    }
-
-    if (auto* global = utils::Lookup(globals_, to);
-        global && global->node == resolved) {
-      if (dependency_edges_
-              .emplace(DependencyEdge{current_global_, global},
-                       DependencyInfo{from->source, action})
-              .second) {
-        current_global_->deps.emplace_back(global);
-      }
-    }
-
-    graph_.resolved_symbols.emplace(from, resolved);
-  }
-
-  /// @returns true if `name` is the name of a builtin function
-  bool IsBuiltin(Symbol name) const {
-    return sem::ParseBuiltinType(symbols_.NameFor(name)) !=
-           sem::BuiltinType::kNone;
-  }
-
-  /// Appends an error to the diagnostics that the given symbol cannot be
-  /// resolved.
-  void UnknownSymbol(Symbol name, Source source, const char* use) {
-    AddError(
-        diagnostics_,
-        "unknown " + std::string(use) + ": '" + symbols_.NameFor(name) + "'",
-        source);
-  }
-
-  using VariableMap = std::unordered_map<Symbol, const ast::Variable*>;
-  const SymbolTable& symbols_;
-  const GlobalMap& globals_;
-  diag::List& diagnostics_;
-  DependencyGraph& graph_;
-  DependencyEdges& dependency_edges_;
-
-  ScopeStack<const ast::Node*> scope_stack_;
-  Global* current_global_ = nullptr;
-};
-
-/// The global dependency analysis system
-struct DependencyAnalysis {
- public:
-  /// Constructor
-  DependencyAnalysis(const SymbolTable& symbols,
-                     diag::List& diagnostics,
-                     DependencyGraph& graph)
-      : symbols_(symbols), diagnostics_(diagnostics), graph_(graph) {}
-
-  /// Performs global dependency analysis on the module, emitting any errors to
-  /// #diagnostics.
-  /// @returns true if analysis found no errors, otherwise false.
-  bool Run(const ast::Module& module) {
-    // Collect all the named globals from the AST module
-    GatherGlobals(module);
-
-    // Traverse the named globals to build the dependency graph
-    DetermineDependencies();
-
-    // Sort the globals into dependency order
-    SortGlobals();
-
-    // Dump the dependency graph if TINT_DUMP_DEPENDENCY_GRAPH is non-zero
-    DumpDependencyGraph();
-
-    graph_.ordered_globals = std::move(sorted_);
-
-    return !diagnostics_.contains_errors();
-  }
-
- private:
-  /// @param node the ast::Node of the global declaration
-  /// @returns the symbol of the global declaration node
-  /// @note will raise an ICE if the node is not a type, function or variable
-  /// declaration
-  Symbol SymbolOf(const ast::Node* node) const {
-    return Switch(
-        node,  //
-        [&](const ast::TypeDecl* td) { return td->name; },
-        [&](const ast::Function* func) { return func->symbol; },
-        [&](const ast::Variable* var) { return var->symbol; },
-        [&](Default) {
-          UnhandledNode(diagnostics_, node);
-          return Symbol{};
-        });
-  }
-
-  /// @param node the ast::Node of the global declaration
-  /// @returns the name of the global declaration node
-  /// @note will raise an ICE if the node is not a type, function or variable
-  /// declaration
-  std::string NameOf(const ast::Node* node) const {
-    return symbols_.NameFor(SymbolOf(node));
-  }
-
-  /// @param node the ast::Node of the global declaration
-  /// @returns a string representation of the global declaration kind
-  /// @note will raise an ICE if the node is not a type, function or variable
-  /// declaration
-  std::string KindOf(const ast::Node* node) {
-    return Switch(
-        node,  //
-        [&](const ast::Struct*) { return "struct"; },
-        [&](const ast::Alias*) { return "alias"; },
-        [&](const ast::Function*) { return "function"; },
-        [&](const ast::Variable* var) { return var->is_const ? "let" : "var"; },
-        [&](Default) {
-          UnhandledNode(diagnostics_, node);
-          return "<error>";
-        });
-  }
-
-  /// Traverses `module`, collecting all the global declarations and populating
-  /// the #globals and #declaration_order fields.
-  void GatherGlobals(const ast::Module& module) {
-    for (auto* node : module.GlobalDeclarations()) {
-      auto* global = allocator_.Create(node);
-      globals_.emplace(SymbolOf(node), global);
-      declaration_order_.emplace_back(global);
-    }
-  }
-
-  /// Walks the global declarations, determining the dependencies of each global
-  /// and adding these to each global's Global::deps field.
-  void DetermineDependencies() {
-    DependencyScanner scanner(symbols_, globals_, diagnostics_, graph_,
-                              dependency_edges_);
-    for (auto* global : declaration_order_) {
-      scanner.Scan(global);
-    }
-  }
-
-  /// Performs a depth-first traversal of `root`'s dependencies, calling `enter`
-  /// as the function decends into each dependency and `exit` when bubbling back
-  /// up towards the root.
-  /// @param enter is a function with the signature: `bool(Global*)`. The
-  /// `enter` function returns true if TraverseDependencies() should traverse
-  /// the dependency, otherwise it will be skipped.
-  /// @param exit is a function with the signature: `void(Global*)`. The `exit`
-  /// function is only called if the corresponding `enter` call returned true.
-  template <typename ENTER, typename EXIT>
-  void TraverseDependencies(const Global* root, ENTER&& enter, EXIT&& exit) {
-    // Entry is a single entry in the traversal stack. Entry points to a
-    // dep_idx'th dependency of Entry::global.
-    struct Entry {
-      const Global* global;  // The parent global
-      size_t dep_idx;        // The dependency index in `global->deps`
-    };
-
-    if (!enter(root)) {
-      return;
-    }
-
-    std::vector<Entry> stack{Entry{root, 0}};
-    while (true) {
-      auto& entry = stack.back();
-      // Have we exhausted the dependencies of entry.global?
-      if (entry.dep_idx < entry.global->deps.size()) {
-        // No, there's more dependencies to traverse.
-        auto& dep = entry.global->deps[entry.dep_idx];
-        // Does the caller want to enter this dependency?
-        if (enter(dep)) {                  // Yes.
-          stack.push_back(Entry{dep, 0});  // Enter the dependency.
-        } else {
-          entry.dep_idx++;  // No. Skip this node.
-        }
-      } else {
-        // Yes. Time to back up.
-        // Exit this global, pop the stack, and if there's another parent node,
-        // increment its dependency index, and loop again.
-        exit(entry.global);
-        stack.pop_back();
-        if (stack.empty()) {
-          return;  // All done.
-        }
-        stack.back().dep_idx++;
-      }
-    }
-  }
-
-  /// SortGlobals sorts the globals into dependency order, erroring if cyclic
-  /// dependencies are found. The sorted dependencies are assigned to #sorted.
-  void SortGlobals() {
-    if (diagnostics_.contains_errors()) {
-      return;  // This code assumes there are no undeclared identifiers.
-    }
-
-    std::unordered_set<const Global*> visited;
-    for (auto* global : declaration_order_) {
-      utils::UniqueVector<const Global*> stack;
-      TraverseDependencies(
-          global,
-          [&](const Global* g) {  // Enter
-            if (!stack.add(g)) {
-              CyclicDependencyFound(g, stack);
-              return false;
-            }
-            if (sorted_.contains(g->node)) {
-              // Visited this global already.
-              // stack was pushed, but exit() will not be called when we return
-              // false, so pop here.
-              stack.pop_back();
-              return false;
-            }
-            return true;
-          },
-          [&](const Global* g) {  // Exit. Only called if Enter returned true.
-            sorted_.add(g->node);
-            stack.pop_back();
-          });
-
-      sorted_.add(global->node);
-
-      if (!stack.empty()) {
-        // Each stack.push() must have a corresponding stack.pop_back().
-        TINT_ICE(Resolver, diagnostics_)
-            << "stack not empty after returning from TraverseDependencies()";
-      }
-    }
-  }
-
-  /// DepInfoFor() looks up the global dependency information for the dependency
-  /// of global `from` depending on `to`.
-  /// @note will raise an ICE if the edge is not found.
-  DependencyInfo DepInfoFor(const Global* from, const Global* to) const {
-    auto it = dependency_edges_.find(DependencyEdge{from, to});
-    if (it != dependency_edges_.end()) {
-      return it->second;
-    }
-    TINT_ICE(Resolver, diagnostics_)
-        << "failed to find dependency info for edge: '" << NameOf(from->node)
-        << "' -> '" << NameOf(to->node) << "'";
-    return {};
-  }
-
-  /// CyclicDependencyFound() emits an error diagnostic for a cyclic dependency.
-  /// @param root is the global that starts the cyclic dependency, which must be
-  /// found in `stack`.
-  /// @param stack is the global dependency stack that contains a loop.
-  void CyclicDependencyFound(const Global* root,
-                             const std::vector<const Global*>& stack) {
-    std::stringstream msg;
-    msg << "cyclic dependency found: ";
-    constexpr size_t kLoopNotStarted = ~0u;
-    size_t loop_start = kLoopNotStarted;
-    for (size_t i = 0; i < stack.size(); i++) {
-      auto* e = stack[i];
-      if (loop_start == kLoopNotStarted && e == root) {
-        loop_start = i;
-      }
-      if (loop_start != kLoopNotStarted) {
-        msg << "'" << NameOf(e->node) << "' -> ";
-      }
-    }
-    msg << "'" << NameOf(root->node) << "'";
-    AddError(diagnostics_, msg.str(), root->node->source);
-    for (size_t i = loop_start; i < stack.size(); i++) {
-      auto* from = stack[i];
-      auto* to = (i + 1 < stack.size()) ? stack[i + 1] : stack[loop_start];
-      auto info = DepInfoFor(from, to);
-      AddNote(diagnostics_,
-              KindOf(from->node) + " '" + NameOf(from->node) + "' " +
-                  info.action + " " + KindOf(to->node) + " '" +
-                  NameOf(to->node) + "' here",
-              info.source);
-    }
-  }
-
-  void DumpDependencyGraph() {
-#if TINT_DUMP_DEPENDENCY_GRAPH == 0
-    if ((true)) {
-      return;
-    }
-#endif  // TINT_DUMP_DEPENDENCY_GRAPH
-    printf("=========================\n");
-    printf("------ declaration ------ \n");
-    for (auto* global : declaration_order_) {
-      printf("%s\n", NameOf(global->node).c_str());
-    }
-    printf("------ dependencies ------ \n");
-    for (auto* node : sorted_) {
-      auto symbol = SymbolOf(node);
-      auto* global = globals_.at(symbol);
-      printf("%s depends on:\n", symbols_.NameFor(symbol).c_str());
-      for (auto* dep : global->deps) {
-        printf("  %s\n", NameOf(dep->node).c_str());
-      }
-    }
-    printf("=========================\n");
-  }
-
-  /// Program symbols
-  const SymbolTable& symbols_;
-
-  /// Program diagnostics
-  diag::List& diagnostics_;
-
-  /// The resulting dependency graph
-  DependencyGraph& graph_;
-
-  /// Allocator of Globals
-  BlockAllocator<Global> allocator_;
-
-  /// Global map, keyed by name. Populated by GatherGlobals().
-  GlobalMap globals_;
-
-  /// Map of DependencyEdge to DependencyInfo. Populated by
-  /// DetermineDependencies().
-  DependencyEdges dependency_edges_;
-
-  /// Globals in declaration order. Populated by GatherGlobals().
-  std::vector<Global*> declaration_order_;
-
-  /// Globals in sorted dependency order. Populated by SortGlobals().
-  utils::UniqueVector<const ast::Node*> sorted_;
-};
-
-}  // namespace
-
-DependencyGraph::DependencyGraph() = default;
-DependencyGraph::DependencyGraph(DependencyGraph&&) = default;
-DependencyGraph::~DependencyGraph() = default;
-
-bool DependencyGraph::Build(const ast::Module& module,
-                            const SymbolTable& symbols,
-                            diag::List& diagnostics,
-                            DependencyGraph& output) {
-  DependencyAnalysis da{symbols, diagnostics, output};
-  return da.Run(module);
-}
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/dependency_graph.h b/src/resolver/dependency_graph.h
deleted file mode 100644
index 294786d..0000000
--- a/src/resolver/dependency_graph.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_RESOLVER_DEPENDENCY_GRAPH_H_
-#define SRC_RESOLVER_DEPENDENCY_GRAPH_H_
-
-#include <unordered_map>
-#include <vector>
-
-#include "src/ast/module.h"
-#include "src/diagnostic/diagnostic.h"
-
-namespace tint {
-namespace resolver {
-
-/// DependencyGraph holds information about module-scope declaration dependency
-/// analysis and symbol resolutions.
-struct DependencyGraph {
-  /// Constructor
-  DependencyGraph();
-  /// Move-constructor
-  DependencyGraph(DependencyGraph&&);
-  /// Destructor
-  ~DependencyGraph();
-
-  /// Build() performs symbol resolution and dependency analysis on `module`,
-  /// populating `output` with the resulting dependency graph.
-  /// @param module the AST module to analyse
-  /// @param symbols the symbol table
-  /// @param diagnostics the diagnostic list to populate with errors / warnings
-  /// @param output the resulting DependencyGraph
-  /// @returns true on success, false on error
-  static bool Build(const ast::Module& module,
-                    const SymbolTable& symbols,
-                    diag::List& diagnostics,
-                    DependencyGraph& output);
-
-  /// All globals in dependency-sorted order.
-  std::vector<const ast::Node*> ordered_globals;
-
-  /// Map of ast::IdentifierExpression or ast::TypeName to a type, function, or
-  /// variable that declares the symbol.
-  std::unordered_map<const ast::Node*, const ast::Node*> resolved_symbols;
-
-  /// Map of ast::Variable to a type, function, or variable that is shadowed by
-  /// the variable key. A declaration (X) shadows another (Y) if X and Y use
-  /// the same symbol, and X is declared in a sub-scope of the scope that
-  /// declares Y.
-  std::unordered_map<const ast::Variable*, const ast::Node*> shadows;
-};
-
-}  // namespace resolver
-}  // namespace tint
-
-#endif  // SRC_RESOLVER_DEPENDENCY_GRAPH_H_
diff --git a/src/resolver/dependency_graph_test.cc b/src/resolver/dependency_graph_test.cc
deleted file mode 100644
index 102d996..0000000
--- a/src/resolver/dependency_graph_test.cc
+++ /dev/null
@@ -1,1342 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string>
-#include <tuple>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "src/resolver/dependency_graph.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ::testing::ElementsAre;
-
-template <typename T>
-class ResolverDependencyGraphTestWithParam : public ResolverTestWithParam<T> {
- public:
-  DependencyGraph Build(std::string expected_error = "") {
-    DependencyGraph graph;
-    auto result = DependencyGraph::Build(this->AST(), this->Symbols(),
-                                         this->Diagnostics(), graph);
-    if (expected_error.empty()) {
-      EXPECT_TRUE(result) << this->Diagnostics().str();
-    } else {
-      EXPECT_FALSE(result);
-      EXPECT_EQ(expected_error, this->Diagnostics().str());
-    }
-    return graph;
-  }
-};
-
-using ResolverDependencyGraphTest =
-    ResolverDependencyGraphTestWithParam<::testing::Test>;
-
-////////////////////////////////////////////////////////////////////////////////
-// Parameterized test helpers
-////////////////////////////////////////////////////////////////////////////////
-
-/// SymbolDeclKind is used by parameterized tests to enumerate the different
-/// kinds of symbol declarations.
-enum class SymbolDeclKind {
-  GlobalVar,
-  GlobalLet,
-  Alias,
-  Struct,
-  Function,
-  Parameter,
-  LocalVar,
-  LocalLet,
-  NestedLocalVar,
-  NestedLocalLet,
-};
-
-static constexpr SymbolDeclKind kAllSymbolDeclKinds[] = {
-    SymbolDeclKind::GlobalVar,      SymbolDeclKind::GlobalLet,
-    SymbolDeclKind::Alias,          SymbolDeclKind::Struct,
-    SymbolDeclKind::Function,       SymbolDeclKind::Parameter,
-    SymbolDeclKind::LocalVar,       SymbolDeclKind::LocalLet,
-    SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet,
-};
-
-static constexpr SymbolDeclKind kTypeDeclKinds[] = {
-    SymbolDeclKind::Alias,
-    SymbolDeclKind::Struct,
-};
-
-static constexpr SymbolDeclKind kValueDeclKinds[] = {
-    SymbolDeclKind::GlobalVar,      SymbolDeclKind::GlobalLet,
-    SymbolDeclKind::Parameter,      SymbolDeclKind::LocalVar,
-    SymbolDeclKind::LocalLet,       SymbolDeclKind::NestedLocalVar,
-    SymbolDeclKind::NestedLocalLet,
-};
-
-static constexpr SymbolDeclKind kGlobalDeclKinds[] = {
-    SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalLet, SymbolDeclKind::Alias,
-    SymbolDeclKind::Struct,    SymbolDeclKind::Function,
-};
-
-static constexpr SymbolDeclKind kLocalDeclKinds[] = {
-    SymbolDeclKind::Parameter,      SymbolDeclKind::LocalVar,
-    SymbolDeclKind::LocalLet,       SymbolDeclKind::NestedLocalVar,
-    SymbolDeclKind::NestedLocalLet,
-};
-
-static constexpr SymbolDeclKind kGlobalValueDeclKinds[] = {
-    SymbolDeclKind::GlobalVar,
-    SymbolDeclKind::GlobalLet,
-};
-
-static constexpr SymbolDeclKind kFuncDeclKinds[] = {
-    SymbolDeclKind::Function,
-};
-
-/// SymbolUseKind is used by parameterized tests to enumerate the different
-/// kinds of symbol uses.
-enum class SymbolUseKind {
-  GlobalVarType,
-  GlobalVarArrayElemType,
-  GlobalVarArraySizeValue,
-  GlobalVarVectorElemType,
-  GlobalVarMatrixElemType,
-  GlobalVarSampledTexElemType,
-  GlobalVarMultisampledTexElemType,
-  GlobalVarValue,
-  GlobalLetType,
-  GlobalLetArrayElemType,
-  GlobalLetArraySizeValue,
-  GlobalLetVectorElemType,
-  GlobalLetMatrixElemType,
-  GlobalLetValue,
-  AliasType,
-  StructMemberType,
-  CallFunction,
-  ParameterType,
-  LocalVarType,
-  LocalVarArrayElemType,
-  LocalVarArraySizeValue,
-  LocalVarVectorElemType,
-  LocalVarMatrixElemType,
-  LocalVarValue,
-  LocalLetType,
-  LocalLetValue,
-  NestedLocalVarType,
-  NestedLocalVarValue,
-  NestedLocalLetType,
-  NestedLocalLetValue,
-  WorkgroupSizeValue,
-};
-
-static constexpr SymbolUseKind kTypeUseKinds[] = {
-    SymbolUseKind::GlobalVarType,
-    SymbolUseKind::GlobalVarArrayElemType,
-    SymbolUseKind::GlobalVarArraySizeValue,
-    SymbolUseKind::GlobalVarVectorElemType,
-    SymbolUseKind::GlobalVarMatrixElemType,
-    SymbolUseKind::GlobalVarSampledTexElemType,
-    SymbolUseKind::GlobalVarMultisampledTexElemType,
-    SymbolUseKind::GlobalLetType,
-    SymbolUseKind::GlobalLetArrayElemType,
-    SymbolUseKind::GlobalLetArraySizeValue,
-    SymbolUseKind::GlobalLetVectorElemType,
-    SymbolUseKind::GlobalLetMatrixElemType,
-    SymbolUseKind::AliasType,
-    SymbolUseKind::StructMemberType,
-    SymbolUseKind::ParameterType,
-    SymbolUseKind::LocalVarType,
-    SymbolUseKind::LocalVarArrayElemType,
-    SymbolUseKind::LocalVarArraySizeValue,
-    SymbolUseKind::LocalVarVectorElemType,
-    SymbolUseKind::LocalVarMatrixElemType,
-    SymbolUseKind::LocalLetType,
-    SymbolUseKind::NestedLocalVarType,
-    SymbolUseKind::NestedLocalLetType,
-};
-
-static constexpr SymbolUseKind kValueUseKinds[] = {
-    SymbolUseKind::GlobalVarValue,      SymbolUseKind::GlobalLetValue,
-    SymbolUseKind::LocalVarValue,       SymbolUseKind::LocalLetValue,
-    SymbolUseKind::NestedLocalVarValue, SymbolUseKind::NestedLocalLetValue,
-    SymbolUseKind::WorkgroupSizeValue,
-};
-
-static constexpr SymbolUseKind kFuncUseKinds[] = {
-    SymbolUseKind::CallFunction,
-};
-
-/// @returns the description of the symbol declaration kind.
-/// @note: This differs from the strings used in diagnostic messages.
-std::ostream& operator<<(std::ostream& out, SymbolDeclKind kind) {
-  switch (kind) {
-    case SymbolDeclKind::GlobalVar:
-      return out << "global var";
-    case SymbolDeclKind::GlobalLet:
-      return out << "global let";
-    case SymbolDeclKind::Alias:
-      return out << "alias";
-    case SymbolDeclKind::Struct:
-      return out << "struct";
-    case SymbolDeclKind::Function:
-      return out << "function";
-    case SymbolDeclKind::Parameter:
-      return out << "parameter";
-    case SymbolDeclKind::LocalVar:
-      return out << "local var";
-    case SymbolDeclKind::LocalLet:
-      return out << "local let";
-    case SymbolDeclKind::NestedLocalVar:
-      return out << "nested local var";
-    case SymbolDeclKind::NestedLocalLet:
-      return out << "nested local let";
-  }
-  return out << "<unknown>";
-}
-
-/// @returns the description of the symbol use kind.
-/// @note: This differs from the strings used in diagnostic messages.
-std::ostream& operator<<(std::ostream& out, SymbolUseKind kind) {
-  switch (kind) {
-    case SymbolUseKind::GlobalVarType:
-      return out << "global var type";
-    case SymbolUseKind::GlobalVarValue:
-      return out << "global var value";
-    case SymbolUseKind::GlobalVarArrayElemType:
-      return out << "global var array element type";
-    case SymbolUseKind::GlobalVarArraySizeValue:
-      return out << "global var array size value";
-    case SymbolUseKind::GlobalVarVectorElemType:
-      return out << "global var vector element type";
-    case SymbolUseKind::GlobalVarMatrixElemType:
-      return out << "global var matrix element type";
-    case SymbolUseKind::GlobalVarSampledTexElemType:
-      return out << "global var sampled_texture element type";
-    case SymbolUseKind::GlobalVarMultisampledTexElemType:
-      return out << "global var multisampled_texture element type";
-    case SymbolUseKind::GlobalLetType:
-      return out << "global let type";
-    case SymbolUseKind::GlobalLetValue:
-      return out << "global let value";
-    case SymbolUseKind::GlobalLetArrayElemType:
-      return out << "global let array element type";
-    case SymbolUseKind::GlobalLetArraySizeValue:
-      return out << "global let array size value";
-    case SymbolUseKind::GlobalLetVectorElemType:
-      return out << "global let vector element type";
-    case SymbolUseKind::GlobalLetMatrixElemType:
-      return out << "global let matrix element type";
-    case SymbolUseKind::AliasType:
-      return out << "alias type";
-    case SymbolUseKind::StructMemberType:
-      return out << "struct member type";
-    case SymbolUseKind::CallFunction:
-      return out << "call function";
-    case SymbolUseKind::ParameterType:
-      return out << "parameter type";
-    case SymbolUseKind::LocalVarType:
-      return out << "local var type";
-    case SymbolUseKind::LocalVarArrayElemType:
-      return out << "local var array element type";
-    case SymbolUseKind::LocalVarArraySizeValue:
-      return out << "local var array size value";
-    case SymbolUseKind::LocalVarVectorElemType:
-      return out << "local var vector element type";
-    case SymbolUseKind::LocalVarMatrixElemType:
-      return out << "local var matrix element type";
-    case SymbolUseKind::LocalVarValue:
-      return out << "local var value";
-    case SymbolUseKind::LocalLetType:
-      return out << "local let type";
-    case SymbolUseKind::LocalLetValue:
-      return out << "local let value";
-    case SymbolUseKind::NestedLocalVarType:
-      return out << "nested local var type";
-    case SymbolUseKind::NestedLocalVarValue:
-      return out << "nested local var value";
-    case SymbolUseKind::NestedLocalLetType:
-      return out << "nested local let type";
-    case SymbolUseKind::NestedLocalLetValue:
-      return out << "nested local let value";
-    case SymbolUseKind::WorkgroupSizeValue:
-      return out << "workgroup size value";
-  }
-  return out << "<unknown>";
-}
-
-/// @returns the the diagnostic message name used for the given use
-std::string DiagString(SymbolUseKind kind) {
-  switch (kind) {
-    case SymbolUseKind::GlobalVarType:
-    case SymbolUseKind::GlobalVarArrayElemType:
-    case SymbolUseKind::GlobalVarVectorElemType:
-    case SymbolUseKind::GlobalVarMatrixElemType:
-    case SymbolUseKind::GlobalVarSampledTexElemType:
-    case SymbolUseKind::GlobalVarMultisampledTexElemType:
-    case SymbolUseKind::GlobalLetType:
-    case SymbolUseKind::GlobalLetArrayElemType:
-    case SymbolUseKind::GlobalLetVectorElemType:
-    case SymbolUseKind::GlobalLetMatrixElemType:
-    case SymbolUseKind::AliasType:
-    case SymbolUseKind::StructMemberType:
-    case SymbolUseKind::ParameterType:
-    case SymbolUseKind::LocalVarType:
-    case SymbolUseKind::LocalVarArrayElemType:
-    case SymbolUseKind::LocalVarVectorElemType:
-    case SymbolUseKind::LocalVarMatrixElemType:
-    case SymbolUseKind::LocalLetType:
-    case SymbolUseKind::NestedLocalVarType:
-    case SymbolUseKind::NestedLocalLetType:
-      return "type";
-    case SymbolUseKind::GlobalVarValue:
-    case SymbolUseKind::GlobalVarArraySizeValue:
-    case SymbolUseKind::GlobalLetValue:
-    case SymbolUseKind::GlobalLetArraySizeValue:
-    case SymbolUseKind::LocalVarValue:
-    case SymbolUseKind::LocalVarArraySizeValue:
-    case SymbolUseKind::LocalLetValue:
-    case SymbolUseKind::NestedLocalVarValue:
-    case SymbolUseKind::NestedLocalLetValue:
-    case SymbolUseKind::WorkgroupSizeValue:
-      return "identifier";
-    case SymbolUseKind::CallFunction:
-      return "function";
-  }
-  return "<unknown>";
-}
-
-/// @returns the declaration scope depth for the symbol declaration kind.
-///          Globals are at depth 0, parameters and locals are at depth 1,
-///          nested locals are at depth 2.
-int ScopeDepth(SymbolDeclKind kind) {
-  switch (kind) {
-    case SymbolDeclKind::GlobalVar:
-    case SymbolDeclKind::GlobalLet:
-    case SymbolDeclKind::Alias:
-    case SymbolDeclKind::Struct:
-    case SymbolDeclKind::Function:
-      return 0;
-    case SymbolDeclKind::Parameter:
-    case SymbolDeclKind::LocalVar:
-    case SymbolDeclKind::LocalLet:
-      return 1;
-    case SymbolDeclKind::NestedLocalVar:
-    case SymbolDeclKind::NestedLocalLet:
-      return 2;
-  }
-  return -1;
-}
-
-/// @returns the use depth for the symbol use kind.
-///          Globals are at depth 0, parameters and locals are at depth 1,
-///          nested locals are at depth 2.
-int ScopeDepth(SymbolUseKind kind) {
-  switch (kind) {
-    case SymbolUseKind::GlobalVarType:
-    case SymbolUseKind::GlobalVarValue:
-    case SymbolUseKind::GlobalVarArrayElemType:
-    case SymbolUseKind::GlobalVarArraySizeValue:
-    case SymbolUseKind::GlobalVarVectorElemType:
-    case SymbolUseKind::GlobalVarMatrixElemType:
-    case SymbolUseKind::GlobalVarSampledTexElemType:
-    case SymbolUseKind::GlobalVarMultisampledTexElemType:
-    case SymbolUseKind::GlobalLetType:
-    case SymbolUseKind::GlobalLetValue:
-    case SymbolUseKind::GlobalLetArrayElemType:
-    case SymbolUseKind::GlobalLetArraySizeValue:
-    case SymbolUseKind::GlobalLetVectorElemType:
-    case SymbolUseKind::GlobalLetMatrixElemType:
-    case SymbolUseKind::AliasType:
-    case SymbolUseKind::StructMemberType:
-    case SymbolUseKind::WorkgroupSizeValue:
-      return 0;
-    case SymbolUseKind::CallFunction:
-    case SymbolUseKind::ParameterType:
-    case SymbolUseKind::LocalVarType:
-    case SymbolUseKind::LocalVarArrayElemType:
-    case SymbolUseKind::LocalVarArraySizeValue:
-    case SymbolUseKind::LocalVarVectorElemType:
-    case SymbolUseKind::LocalVarMatrixElemType:
-    case SymbolUseKind::LocalVarValue:
-    case SymbolUseKind::LocalLetType:
-    case SymbolUseKind::LocalLetValue:
-      return 1;
-    case SymbolUseKind::NestedLocalVarType:
-    case SymbolUseKind::NestedLocalVarValue:
-    case SymbolUseKind::NestedLocalLetType:
-    case SymbolUseKind::NestedLocalLetValue:
-      return 2;
-  }
-  return -1;
-}
-
-/// A helper for building programs that exercise symbol declaration tests.
-struct SymbolTestHelper {
-  /// The program builder
-  ProgramBuilder* const builder;
-  /// Parameters to a function that may need to be built
-  std::vector<const ast::Variable*> parameters;
-  /// Shallow function var / let declaration statements
-  std::vector<const ast::Statement*> statements;
-  /// Nested function local var / let declaration statements
-  std::vector<const ast::Statement*> nested_statements;
-  /// Function attributes
-  ast::AttributeList func_attrs;
-
-  /// Constructor
-  /// @param builder the program builder
-  explicit SymbolTestHelper(ProgramBuilder* builder);
-
-  /// Destructor.
-  ~SymbolTestHelper();
-
-  /// Declares a symbol with the given kind
-  /// @param kind the kind of symbol declaration
-  /// @param symbol the symbol to use for the declaration
-  /// @param source the source of the declaration
-  /// @returns the declaration node
-  const ast::Node* Add(SymbolDeclKind kind, Symbol symbol, Source source);
-
-  /// Declares a use of a symbol with the given kind
-  /// @param kind the kind of symbol use
-  /// @param symbol the declaration symbol to use
-  /// @param source the source of the use
-  /// @returns the use node
-  const ast::Node* Add(SymbolUseKind kind, Symbol symbol, Source source);
-
-  /// Builds a function, if any parameter or local declarations have been added
-  void Build();
-};
-
-SymbolTestHelper::SymbolTestHelper(ProgramBuilder* b) : builder(b) {}
-
-SymbolTestHelper::~SymbolTestHelper() {}
-
-const ast::Node* SymbolTestHelper::Add(SymbolDeclKind kind,
-                                       Symbol symbol,
-                                       Source source) {
-  auto& b = *builder;
-  switch (kind) {
-    case SymbolDeclKind::GlobalVar:
-      return b.Global(source, symbol, b.ty.i32(), ast::StorageClass::kPrivate);
-    case SymbolDeclKind::GlobalLet:
-      return b.GlobalConst(source, symbol, b.ty.i32(), b.Expr(1));
-    case SymbolDeclKind::Alias:
-      return b.Alias(source, symbol, b.ty.i32());
-    case SymbolDeclKind::Struct:
-      return b.Structure(source, symbol, {b.Member("m", b.ty.i32())});
-    case SymbolDeclKind::Function:
-      return b.Func(source, symbol, {}, b.ty.void_(), {});
-    case SymbolDeclKind::Parameter: {
-      auto* node = b.Param(source, symbol, b.ty.i32());
-      parameters.emplace_back(node);
-      return node;
-    }
-    case SymbolDeclKind::LocalVar: {
-      auto* node = b.Var(source, symbol, b.ty.i32());
-      statements.emplace_back(b.Decl(node));
-      return node;
-    }
-    case SymbolDeclKind::LocalLet: {
-      auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1));
-      statements.emplace_back(b.Decl(node));
-      return node;
-    }
-    case SymbolDeclKind::NestedLocalVar: {
-      auto* node = b.Var(source, symbol, b.ty.i32());
-      nested_statements.emplace_back(b.Decl(node));
-      return node;
-    }
-    case SymbolDeclKind::NestedLocalLet: {
-      auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1));
-      nested_statements.emplace_back(b.Decl(node));
-      return node;
-    }
-  }
-  return nullptr;
-}
-
-const ast::Node* SymbolTestHelper::Add(SymbolUseKind kind,
-                                       Symbol symbol,
-                                       Source source) {
-  auto& b = *builder;
-  switch (kind) {
-    case SymbolUseKind::GlobalVarType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(), node, ast::StorageClass::kPrivate);
-      return node;
-    }
-    case SymbolUseKind::GlobalVarArrayElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(), b.ty.array(node, 4), ast::StorageClass::kPrivate);
-      return node;
-    }
-    case SymbolUseKind::GlobalVarArraySizeValue: {
-      auto* node = b.Expr(source, symbol);
-      b.Global(b.Sym(), b.ty.array(b.ty.i32(), node),
-               ast::StorageClass::kPrivate);
-      return node;
-    }
-    case SymbolUseKind::GlobalVarVectorElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(), b.ty.vec3(node), ast::StorageClass::kPrivate);
-      return node;
-    }
-    case SymbolUseKind::GlobalVarMatrixElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(), b.ty.mat3x4(node), ast::StorageClass::kPrivate);
-      return node;
-    }
-    case SymbolUseKind::GlobalVarSampledTexElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(), b.ty.sampled_texture(ast::TextureDimension::k2d, node));
-      return node;
-    }
-    case SymbolUseKind::GlobalVarMultisampledTexElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Global(b.Sym(),
-               b.ty.multisampled_texture(ast::TextureDimension::k2d, node));
-      return node;
-    }
-    case SymbolUseKind::GlobalVarValue: {
-      auto* node = b.Expr(source, symbol);
-      b.Global(b.Sym(), b.ty.i32(), ast::StorageClass::kPrivate, node);
-      return node;
-    }
-    case SymbolUseKind::GlobalLetType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.GlobalConst(b.Sym(), node, b.Expr(1));
-      return node;
-    }
-    case SymbolUseKind::GlobalLetArrayElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.GlobalConst(b.Sym(), b.ty.array(node, 4), b.Expr(1));
-      return node;
-    }
-    case SymbolUseKind::GlobalLetArraySizeValue: {
-      auto* node = b.Expr(source, symbol);
-      b.GlobalConst(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1));
-      return node;
-    }
-    case SymbolUseKind::GlobalLetVectorElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1));
-      return node;
-    }
-    case SymbolUseKind::GlobalLetMatrixElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1));
-      return node;
-    }
-    case SymbolUseKind::GlobalLetValue: {
-      auto* node = b.Expr(source, symbol);
-      b.GlobalConst(b.Sym(), b.ty.i32(), node);
-      return node;
-    }
-    case SymbolUseKind::AliasType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Alias(b.Sym(), node);
-      return node;
-    }
-    case SymbolUseKind::StructMemberType: {
-      auto* node = b.ty.type_name(source, symbol);
-      b.Structure(b.Sym(), {b.Member("m", node)});
-      return node;
-    }
-    case SymbolUseKind::CallFunction: {
-      auto* node = b.Expr(source, symbol);
-      statements.emplace_back(b.CallStmt(b.Call(node)));
-      return node;
-    }
-    case SymbolUseKind::ParameterType: {
-      auto* node = b.ty.type_name(source, symbol);
-      parameters.emplace_back(b.Param(b.Sym(), node));
-      return node;
-    }
-    case SymbolUseKind::LocalVarType: {
-      auto* node = b.ty.type_name(source, symbol);
-      statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
-      return node;
-    }
-    case SymbolUseKind::LocalVarArrayElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      statements.emplace_back(
-          b.Decl(b.Var(b.Sym(), b.ty.array(node, 4), b.Expr(1))));
-      return node;
-    }
-    case SymbolUseKind::LocalVarArraySizeValue: {
-      auto* node = b.Expr(source, symbol);
-      statements.emplace_back(
-          b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1))));
-      return node;
-    }
-    case SymbolUseKind::LocalVarVectorElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.vec3(node))));
-      return node;
-    }
-    case SymbolUseKind::LocalVarMatrixElemType: {
-      auto* node = b.ty.type_name(source, symbol);
-      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node))));
-      return node;
-    }
-    case SymbolUseKind::LocalVarValue: {
-      auto* node = b.Expr(source, symbol);
-      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
-      return node;
-    }
-    case SymbolUseKind::LocalLetType: {
-      auto* node = b.ty.type_name(source, symbol);
-      statements.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1))));
-      return node;
-    }
-    case SymbolUseKind::LocalLetValue: {
-      auto* node = b.Expr(source, symbol);
-      statements.emplace_back(b.Decl(b.Const(b.Sym(), b.ty.i32(), node)));
-      return node;
-    }
-    case SymbolUseKind::NestedLocalVarType: {
-      auto* node = b.ty.type_name(source, symbol);
-      nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
-      return node;
-    }
-    case SymbolUseKind::NestedLocalVarValue: {
-      auto* node = b.Expr(source, symbol);
-      nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
-      return node;
-    }
-    case SymbolUseKind::NestedLocalLetType: {
-      auto* node = b.ty.type_name(source, symbol);
-      nested_statements.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1))));
-      return node;
-    }
-    case SymbolUseKind::NestedLocalLetValue: {
-      auto* node = b.Expr(source, symbol);
-      nested_statements.emplace_back(
-          b.Decl(b.Const(b.Sym(), b.ty.i32(), node)));
-      return node;
-    }
-    case SymbolUseKind::WorkgroupSizeValue: {
-      auto* node = b.Expr(source, symbol);
-      func_attrs.emplace_back(b.WorkgroupSize(1, node, 2));
-      return node;
-    }
-  }
-  return nullptr;
-}
-
-void SymbolTestHelper::Build() {
-  auto& b = *builder;
-  if (!nested_statements.empty()) {
-    statements.emplace_back(b.Block(nested_statements));
-    nested_statements.clear();
-  }
-  if (!parameters.empty() || !statements.empty() || !func_attrs.empty()) {
-    b.Func("func", parameters, b.ty.void_(), statements, func_attrs);
-    parameters.clear();
-    statements.clear();
-    func_attrs.clear();
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Used-before-declarated tests
-////////////////////////////////////////////////////////////////////////////////
-namespace used_before_decl_tests {
-
-using ResolverDependencyGraphUsedBeforeDeclTest = ResolverDependencyGraphTest;
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, FuncCall) {
-  // fn A() { B(); }
-  // fn B() {}
-
-  Func("A", {}, ty.void_(), {CallStmt(Call(Expr(Source{{12, 34}}, "B")))});
-  Func(Source{{56, 78}}, "B", {}, ty.void_(), {Return()});
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeConstructed) {
-  // fn F() {
-  //   { _ = T(); }
-  // }
-  // type T = i32;
-
-  Func("F", {}, ty.void_(),
-       {Block(Ignore(Construct(ty.type_name(Source{{12, 34}}, "T"))))});
-  Alias(Source{{56, 78}}, "T", ty.i32());
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByLocal) {
-  // fn F() {
-  //   { var v : T; }
-  // }
-  // type T = i32;
-
-  Func("F", {}, ty.void_(),
-       {Block(Decl(Var("v", ty.type_name(Source{{12, 34}}, "T"))))});
-  Alias(Source{{56, 78}}, "T", ty.i32());
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByParam) {
-  // fn F(p : T) {}
-  // type T = i32;
-
-  Func("F", {Param("p", ty.type_name(Source{{12, 34}}, "T"))}, ty.void_(), {});
-  Alias(Source{{56, 78}}, "T", ty.i32());
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedAsReturnType) {
-  // fn F() -> T {}
-  // type T = i32;
-
-  Func("F", {}, ty.type_name(Source{{12, 34}}, "T"), {});
-  Alias(Source{{56, 78}}, "T", ty.i32());
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeByStructMember) {
-  // struct S { m : T };
-  // type T = i32;
-
-  Structure("S", {Member("m", ty.type_name(Source{{12, 34}}, "T"))});
-  Alias(Source{{56, 78}}, "T", ty.i32());
-
-  Build();
-}
-
-TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, VarUsed) {
-  // fn F() {
-  //   { G = 3.14f; }
-  // }
-  // var G: f32 = 2.1;
-
-  Func("F", ast::VariableList{}, ty.void_(),
-       {Block(Assign(Expr(Source{{12, 34}}, "G"), 3.14f))});
-
-  Global(Source{{56, 78}}, "G", ty.f32(), ast::StorageClass::kPrivate,
-         Expr(2.1f));
-
-  Build();
-}
-
-}  // namespace used_before_decl_tests
-
-////////////////////////////////////////////////////////////////////////////////
-// Undeclared symbol tests
-////////////////////////////////////////////////////////////////////////////////
-namespace undeclared_tests {
-
-using ResolverDependencyGraphUndeclaredSymbolTest =
-    ResolverDependencyGraphTestWithParam<SymbolUseKind>;
-
-TEST_P(ResolverDependencyGraphUndeclaredSymbolTest, Test) {
-  const Symbol symbol = Sym("SYMBOL");
-  const auto use_kind = GetParam();
-
-  // Build a use of a non-existent symbol
-  SymbolTestHelper helper(this);
-  helper.Add(use_kind, symbol, Source{{56, 78}});
-  helper.Build();
-
-  Build("56:78 error: unknown " + DiagString(use_kind) + ": 'SYMBOL'");
-}
-
-INSTANTIATE_TEST_SUITE_P(Types,
-                         ResolverDependencyGraphUndeclaredSymbolTest,
-                         testing::ValuesIn(kTypeUseKinds));
-
-INSTANTIATE_TEST_SUITE_P(Values,
-                         ResolverDependencyGraphUndeclaredSymbolTest,
-                         testing::ValuesIn(kValueUseKinds));
-
-INSTANTIATE_TEST_SUITE_P(Functions,
-                         ResolverDependencyGraphUndeclaredSymbolTest,
-                         testing::ValuesIn(kFuncUseKinds));
-
-}  // namespace undeclared_tests
-
-////////////////////////////////////////////////////////////////////////////////
-// Self reference by decl
-////////////////////////////////////////////////////////////////////////////////
-namespace undeclared_tests {
-
-using ResolverDependencyGraphDeclSelfUse = ResolverDependencyGraphTest;
-
-TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalVar) {
-  const Symbol symbol = Sym("SYMBOL");
-  Global(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123));
-  Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL'
-12:34 note: var 'SYMBOL' references var 'SYMBOL' here)");
-}
-
-TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalLet) {
-  const Symbol symbol = Sym("SYMBOL");
-  GlobalConst(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123));
-  Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL'
-12:34 note: let 'SYMBOL' references let 'SYMBOL' here)");
-}
-
-TEST_F(ResolverDependencyGraphDeclSelfUse, LocalVar) {
-  const Symbol symbol = Sym("SYMBOL");
-  WrapInFunction(
-      Decl(Var(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123))));
-  Build("12:34 error: unknown identifier: 'SYMBOL'");
-}
-
-TEST_F(ResolverDependencyGraphDeclSelfUse, LocalLet) {
-  const Symbol symbol = Sym("SYMBOL");
-  WrapInFunction(
-      Decl(Const(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123))));
-  Build("12:34 error: unknown identifier: 'SYMBOL'");
-}
-
-}  // namespace undeclared_tests
-
-////////////////////////////////////////////////////////////////////////////////
-// Recursive dependency tests
-////////////////////////////////////////////////////////////////////////////////
-namespace recursive_tests {
-
-using ResolverDependencyGraphCyclicRefTest = ResolverDependencyGraphTest;
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, DirectCall) {
-  // fn main() { main(); }
-
-  Func(Source{{12, 34}}, "main", {}, ty.void_(),
-       {CallStmt(Call(Expr(Source{{56, 78}}, "main")))});
-
-  Build(R"(12:34 error: cyclic dependency found: 'main' -> 'main'
-56:78 note: function 'main' calls function 'main' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, IndirectCall) {
-  // 1: fn a() { b(); }
-  // 2: fn e() { }
-  // 3: fn d() { e(); b(); }
-  // 4: fn c() { d(); }
-  // 5: fn b() { c(); }
-
-  Func(Source{{1, 1}}, "a", {}, ty.void_(),
-       {CallStmt(Call(Expr(Source{{1, 10}}, "b")))});
-  Func(Source{{2, 1}}, "e", {}, ty.void_(), {});
-  Func(Source{{3, 1}}, "d", {}, ty.void_(),
-       {
-           CallStmt(Call(Expr(Source{{3, 10}}, "e"))),
-           CallStmt(Call(Expr(Source{{3, 10}}, "b"))),
-       });
-  Func(Source{{4, 1}}, "c", {}, ty.void_(),
-       {CallStmt(Call(Expr(Source{{4, 10}}, "d")))});
-  Func(Source{{5, 1}}, "b", {}, ty.void_(),
-       {CallStmt(Call(Expr(Source{{5, 10}}, "c")))});
-
-  Build(R"(5:1 error: cyclic dependency found: 'b' -> 'c' -> 'd' -> 'b'
-5:10 note: function 'b' calls function 'c' here
-4:10 note: function 'c' calls function 'd' here
-3:10 note: function 'd' calls function 'b' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Direct) {
-  // type T = T;
-
-  Alias(Source{{12, 34}}, "T", ty.type_name(Source{{56, 78}}, "T"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: cyclic dependency found: 'T' -> 'T'
-56:78 note: alias 'T' references alias 'T' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Indirect) {
-  // 1: type Y = Z;
-  // 2: type X = Y;
-  // 3: type Z = X;
-
-  Alias(Source{{1, 1}}, "Y", ty.type_name(Source{{1, 10}}, "Z"));
-  Alias(Source{{2, 1}}, "X", ty.type_name(Source{{2, 10}}, "Y"));
-  Alias(Source{{3, 1}}, "Z", ty.type_name(Source{{3, 10}}, "X"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
-1:10 note: alias 'Y' references alias 'Z' here
-3:10 note: alias 'Z' references alias 'X' here
-2:10 note: alias 'X' references alias 'Y' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Direct) {
-  // struct S {
-  //   a: S;
-  // };
-
-  Structure(Source{{12, 34}}, "S",
-            {Member("a", ty.type_name(Source{{56, 78}}, "S"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: cyclic dependency found: 'S' -> 'S'
-56:78 note: struct 'S' references struct 'S' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Indirect) {
-  // 1: struct Y { z: Z; };
-  // 2: struct X { y: Y; };
-  // 3: struct Z { x: X; };
-
-  Structure(Source{{1, 1}}, "Y",
-            {Member("z", ty.type_name(Source{{1, 10}}, "Z"))});
-  Structure(Source{{2, 1}}, "X",
-            {Member("y", ty.type_name(Source{{2, 10}}, "Y"))});
-  Structure(Source{{3, 1}}, "Z",
-            {Member("x", ty.type_name(Source{{3, 10}}, "X"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
-1:10 note: struct 'Y' references struct 'Z' here
-3:10 note: struct 'Z' references struct 'X' here
-2:10 note: struct 'X' references struct 'Y' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Direct) {
-  // var<private> V : i32 = V;
-
-  Global(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: cyclic dependency found: 'V' -> 'V'
-56:78 note: var 'V' references var 'V' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalLet_Direct) {
-  // let V : i32 = V;
-
-  GlobalConst(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: cyclic dependency found: 'V' -> 'V'
-56:78 note: let 'V' references let 'V' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Indirect) {
-  // 1: var<private> Y : i32 = Z;
-  // 2: var<private> X : i32 = Y;
-  // 3: var<private> Z : i32 = X;
-
-  Global(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z"));
-  Global(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y"));
-  Global(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
-1:10 note: var 'Y' references var 'Z' here
-3:10 note: var 'Z' references var 'X' here
-2:10 note: var 'X' references var 'Y' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalLet_Indirect) {
-  // 1: let Y : i32 = Z;
-  // 2: let X : i32 = Y;
-  // 3: let Z : i32 = X;
-
-  GlobalConst(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z"));
-  GlobalConst(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y"));
-  GlobalConst(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
-1:10 note: let 'Y' references let 'Z' here
-3:10 note: let 'Z' references let 'X' here
-2:10 note: let 'X' references let 'Y' here)");
-}
-
-TEST_F(ResolverDependencyGraphCyclicRefTest, Mixed_RecursiveDependencies) {
-  // 1: fn F() -> R { return Z; }
-  // 2: type A = S;
-  // 3: struct S { a : A };
-  // 4: var Z = L;
-  // 5: type R = A;
-  // 6: let L : S = Z;
-
-  Func(Source{{1, 1}}, "F", {}, ty.type_name(Source{{1, 5}}, "R"),
-       {Return(Expr(Source{{1, 10}}, "Z"))});
-  Alias(Source{{2, 1}}, "A", ty.type_name(Source{{2, 10}}, "S"));
-  Structure(Source{{3, 1}}, "S",
-            {Member("a", ty.type_name(Source{{3, 10}}, "A"))});
-  Global(Source{{4, 1}}, "Z", nullptr, Expr(Source{{4, 10}}, "L"));
-  Alias(Source{{5, 1}}, "R", ty.type_name(Source{{5, 10}}, "A"));
-  GlobalConst(Source{{6, 1}}, "L", ty.type_name(Source{{5, 5}}, "S"),
-              Expr(Source{{5, 10}}, "Z"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(2:1 error: cyclic dependency found: 'A' -> 'S' -> 'A'
-2:10 note: alias 'A' references struct 'S' here
-3:10 note: struct 'S' references alias 'A' here
-4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z'
-4:10 note: var 'Z' references let 'L' here
-5:10 note: let 'L' references var 'Z' here)");
-}
-
-}  // namespace recursive_tests
-
-////////////////////////////////////////////////////////////////////////////////
-// Symbol Redeclaration tests
-////////////////////////////////////////////////////////////////////////////////
-namespace redeclaration_tests {
-
-using ResolverDependencyGraphRedeclarationTest =
-    ResolverDependencyGraphTestWithParam<
-        std::tuple<SymbolDeclKind, SymbolDeclKind>>;
-
-TEST_P(ResolverDependencyGraphRedeclarationTest, Test) {
-  const auto symbol = Sym("SYMBOL");
-
-  auto a_kind = std::get<0>(GetParam());
-  auto b_kind = std::get<1>(GetParam());
-
-  auto a_source = Source{{12, 34}};
-  auto b_source = Source{{56, 78}};
-
-  if (a_kind != SymbolDeclKind::Parameter &&
-      b_kind == SymbolDeclKind::Parameter) {
-    std::swap(a_source, b_source);  // Parameters are declared before locals
-  }
-
-  SymbolTestHelper helper(this);
-  helper.Add(a_kind, symbol, a_source);
-  helper.Add(b_kind, symbol, b_source);
-  helper.Build();
-
-  bool error = ScopeDepth(a_kind) == ScopeDepth(b_kind);
-
-  Build(error ? R"(56:78 error: redeclaration of 'SYMBOL'
-12:34 note: 'SYMBOL' previously declared here)"
-              : "");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverDependencyGraphRedeclarationTest,
-    testing::Combine(testing::ValuesIn(kAllSymbolDeclKinds),
-                     testing::ValuesIn(kAllSymbolDeclKinds)));
-
-}  // namespace redeclaration_tests
-
-////////////////////////////////////////////////////////////////////////////////
-// Ordered global tests
-////////////////////////////////////////////////////////////////////////////////
-namespace ordered_globals {
-
-using ResolverDependencyGraphOrderedGlobalsTest =
-    ResolverDependencyGraphTestWithParam<
-        std::tuple<SymbolDeclKind, SymbolUseKind>>;
-
-TEST_P(ResolverDependencyGraphOrderedGlobalsTest, InOrder) {
-  const Symbol symbol = Sym("SYMBOL");
-  const auto decl_kind = std::get<0>(GetParam());
-  const auto use_kind = std::get<1>(GetParam());
-
-  // Declaration before use
-  SymbolTestHelper helper(this);
-  helper.Add(decl_kind, symbol, Source{{12, 34}});
-  helper.Add(use_kind, symbol, Source{{56, 78}});
-  helper.Build();
-
-  ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
-
-  auto* decl = AST().GlobalDeclarations()[0];
-  auto* use = AST().GlobalDeclarations()[1];
-  EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use));
-}
-
-TEST_P(ResolverDependencyGraphOrderedGlobalsTest, OutOfOrder) {
-  const Symbol symbol = Sym("SYMBOL");
-  const auto decl_kind = std::get<0>(GetParam());
-  const auto use_kind = std::get<1>(GetParam());
-
-  // Use before declaration
-  SymbolTestHelper helper(this);
-  helper.Add(use_kind, symbol, Source{{56, 78}});
-  helper.Build();  // If the use is in a function, then ensure this function is
-                   // built before the symbol declaration
-  helper.Add(decl_kind, symbol, Source{{12, 34}});
-  helper.Build();
-
-  ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
-
-  auto* use = AST().GlobalDeclarations()[0];
-  auto* decl = AST().GlobalDeclarations()[1];
-  EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use));
-}
-
-INSTANTIATE_TEST_SUITE_P(Types,
-                         ResolverDependencyGraphOrderedGlobalsTest,
-                         testing::Combine(testing::ValuesIn(kTypeDeclKinds),
-                                          testing::ValuesIn(kTypeUseKinds)));
-
-INSTANTIATE_TEST_SUITE_P(
-    Values,
-    ResolverDependencyGraphOrderedGlobalsTest,
-    testing::Combine(testing::ValuesIn(kGlobalValueDeclKinds),
-                     testing::ValuesIn(kValueUseKinds)));
-
-INSTANTIATE_TEST_SUITE_P(Functions,
-                         ResolverDependencyGraphOrderedGlobalsTest,
-                         testing::Combine(testing::ValuesIn(kFuncDeclKinds),
-                                          testing::ValuesIn(kFuncUseKinds)));
-}  // namespace ordered_globals
-
-////////////////////////////////////////////////////////////////////////////////
-// Resolved symbols tests
-////////////////////////////////////////////////////////////////////////////////
-namespace resolved_symbols {
-
-using ResolverDependencyGraphResolvedSymbolTest =
-    ResolverDependencyGraphTestWithParam<
-        std::tuple<SymbolDeclKind, SymbolUseKind>>;
-
-TEST_P(ResolverDependencyGraphResolvedSymbolTest, Test) {
-  const Symbol symbol = Sym("SYMBOL");
-  const auto decl_kind = std::get<0>(GetParam());
-  const auto use_kind = std::get<1>(GetParam());
-
-  // Build a symbol declaration and a use of that symbol
-  SymbolTestHelper helper(this);
-  auto* decl = helper.Add(decl_kind, symbol, Source{{12, 34}});
-  auto* use = helper.Add(use_kind, symbol, Source{{56, 78}});
-  helper.Build();
-
-  // If the declaration is visible to the use, then we expect the analysis to
-  // succeed.
-  bool expect_pass = ScopeDepth(decl_kind) <= ScopeDepth(use_kind);
-  auto graph =
-      Build(expect_pass ? "" : "56:78 error: unknown identifier: 'SYMBOL'");
-
-  if (expect_pass) {
-    // Check that the use resolves to the declaration
-    auto* resolved_symbol = graph.resolved_symbols[use];
-    EXPECT_EQ(resolved_symbol, decl)
-        << "resolved: "
-        << (resolved_symbol ? resolved_symbol->TypeInfo().name : "<null>")
-        << "\n"
-        << "decl:     " << decl->TypeInfo().name;
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(Types,
-                         ResolverDependencyGraphResolvedSymbolTest,
-                         testing::Combine(testing::ValuesIn(kTypeDeclKinds),
-                                          testing::ValuesIn(kTypeUseKinds)));
-
-INSTANTIATE_TEST_SUITE_P(Values,
-                         ResolverDependencyGraphResolvedSymbolTest,
-                         testing::Combine(testing::ValuesIn(kValueDeclKinds),
-                                          testing::ValuesIn(kValueUseKinds)));
-
-INSTANTIATE_TEST_SUITE_P(Functions,
-                         ResolverDependencyGraphResolvedSymbolTest,
-                         testing::Combine(testing::ValuesIn(kFuncDeclKinds),
-                                          testing::ValuesIn(kFuncUseKinds)));
-
-}  // namespace resolved_symbols
-
-////////////////////////////////////////////////////////////////////////////////
-// Shadowing tests
-////////////////////////////////////////////////////////////////////////////////
-namespace shadowing {
-
-using ResolverDependencyShadowTest = ResolverDependencyGraphTestWithParam<
-    std::tuple<SymbolDeclKind, SymbolDeclKind>>;
-
-TEST_P(ResolverDependencyShadowTest, Test) {
-  const Symbol symbol = Sym("SYMBOL");
-  const auto outer_kind = std::get<0>(GetParam());
-  const auto inner_kind = std::get<1>(GetParam());
-
-  // Build a symbol declaration and a use of that symbol
-  SymbolTestHelper helper(this);
-  auto* outer = helper.Add(outer_kind, symbol, Source{{12, 34}});
-  helper.Add(inner_kind, symbol, Source{{56, 78}});
-  auto* inner_var = helper.nested_statements.size()
-                        ? helper.nested_statements[0]
-                              ->As<ast::VariableDeclStatement>()
-                              ->variable
-                        : helper.statements.size()
-                              ? helper.statements[0]
-                                    ->As<ast::VariableDeclStatement>()
-                                    ->variable
-                              : helper.parameters[0];
-  helper.Build();
-
-  EXPECT_EQ(Build().shadows[inner_var], outer);
-}
-
-INSTANTIATE_TEST_SUITE_P(LocalShadowGlobal,
-                         ResolverDependencyShadowTest,
-                         testing::Combine(testing::ValuesIn(kGlobalDeclKinds),
-                                          testing::ValuesIn(kLocalDeclKinds)));
-
-INSTANTIATE_TEST_SUITE_P(
-    NestedLocalShadowLocal,
-    ResolverDependencyShadowTest,
-    testing::Combine(testing::Values(SymbolDeclKind::Parameter,
-                                     SymbolDeclKind::LocalVar,
-                                     SymbolDeclKind::LocalLet),
-                     testing::Values(SymbolDeclKind::NestedLocalVar,
-                                     SymbolDeclKind::NestedLocalLet)));
-
-}  // namespace shadowing
-
-////////////////////////////////////////////////////////////////////////////////
-// AST traversal tests
-////////////////////////////////////////////////////////////////////////////////
-namespace ast_traversal {
-
-using ResolverDependencyGraphTraversalTest = ResolverDependencyGraphTest;
-
-TEST_F(ResolverDependencyGraphTraversalTest, SymbolsReached) {
-  const auto value_sym = Sym("VALUE");
-  const auto type_sym = Sym("TYPE");
-  const auto func_sym = Sym("FUNC");
-
-  const auto* value_decl =
-      Global(value_sym, ty.i32(), ast::StorageClass::kPrivate);
-  const auto* type_decl = Alias(type_sym, ty.i32());
-  const auto* func_decl = Func(func_sym, {}, ty.void_(), {});
-
-  struct SymbolUse {
-    const ast::Node* decl = nullptr;
-    const ast::Node* use = nullptr;
-    std::string where = nullptr;
-  };
-
-  std::vector<SymbolUse> symbol_uses;
-
-  auto add_use = [&](const ast::Node* decl, auto* use, int line,
-                     const char* kind) {
-    symbol_uses.emplace_back(SymbolUse{
-        decl, use,
-        std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
-    return use;
-  };
-#define V add_use(value_decl, Expr(value_sym), __LINE__, "V()")
-#define T add_use(type_decl, ty.type_name(type_sym), __LINE__, "T()")
-#define F add_use(func_decl, Expr(func_sym), __LINE__, "F()")
-
-  Alias(Sym(), T);
-  Structure(Sym(), {Member(Sym(), T)});
-  Global(Sym(), T, V);
-  GlobalConst(Sym(), T, V);
-  Func(Sym(),              //
-       {Param(Sym(), T)},  //
-       T,                  // Return type
-       {
-           Decl(Var(Sym(), T, V)),                    //
-           Decl(Const(Sym(), T, V)),                  //
-           CallStmt(Call(F, V)),                      //
-           Block(                                     //
-               Assign(V, V)),                         //
-           If(V,                                      //
-              Block(Assign(V, V)),                    //
-              Else(V,                                 //
-                   Block(Assign(V, V)))),             //
-           Ignore(Bitcast(T, V)),                     //
-           For(Decl(Var(Sym(), T, V)),                //
-               Equal(V, V),                           //
-               Assign(V, V),                          //
-               Block(                                 //
-                   Assign(V, V))),                    //
-           Loop(Block(Assign(V, V)),                  //
-                Block(Assign(V, V))),                 //
-           Switch(V,                                  //
-                  Case(Expr(1),                       //
-                       Block(Assign(V, V))),          //
-                  Case(Expr(2),                       //
-                       Block(Fallthrough())),         //
-                  DefaultCase(Block(Assign(V, V)))),  //
-           Return(V),                                 //
-           Break(),                                   //
-           Discard(),                                 //
-       });                                            //
-  // Exercise type traversal
-  Global(Sym(), ty.atomic(T));
-  Global(Sym(), ty.bool_());
-  Global(Sym(), ty.i32());
-  Global(Sym(), ty.u32());
-  Global(Sym(), ty.f32());
-  Global(Sym(), ty.array(T, V, 4));
-  Global(Sym(), ty.vec3(T));
-  Global(Sym(), ty.mat3x2(T));
-  Global(Sym(), ty.pointer(T, ast::StorageClass::kPrivate));
-  Global(Sym(), ty.sampled_texture(ast::TextureDimension::k2d, T));
-  Global(Sym(), ty.depth_texture(ast::TextureDimension::k2d));
-  Global(Sym(), ty.depth_multisampled_texture(ast::TextureDimension::k2d));
-  Global(Sym(), ty.external_texture());
-  Global(Sym(), ty.multisampled_texture(ast::TextureDimension::k2d, T));
-  Global(Sym(), ty.storage_texture(ast::TextureDimension::k2d,
-                                   ast::TexelFormat::kR32Float,
-                                   ast::Access::kRead));  //
-  Global(Sym(), ty.sampler(ast::SamplerKind::kSampler));
-  Func(Sym(), {}, ty.void_(), {});
-#undef V
-#undef T
-#undef F
-
-  auto graph = Build();
-  for (auto use : symbol_uses) {
-    auto* resolved_symbol = graph.resolved_symbols[use.use];
-    EXPECT_EQ(resolved_symbol, use.decl) << use.where;
-  }
-}
-
-TEST_F(ResolverDependencyGraphTraversalTest, InferredType) {
-  // Check that the nullptr of the var / let type doesn't make things explode
-  Global("a", nullptr, Expr(1));
-  GlobalConst("b", nullptr, Expr(1));
-  WrapInFunction(Var("c", nullptr, Expr(1)),  //
-                 Const("d", nullptr, Expr(1)));
-  Build();
-}
-
-// Reproduces an unbalanced stack push / pop bug in
-// DependencyAnalysis::SortGlobals(), found by clusterfuzz.
-// See: crbug.com/chromium/1273451
-TEST_F(ResolverDependencyGraphTraversalTest, chromium_1273451) {
-  Structure("A", {Member("a", ty.i32())});
-  Structure("B", {Member("b", ty.i32())});
-  Func("f", {Param("a", ty.type_name("A"))}, ty.type_name("B"),
-       {
-           Return(Construct(ty.type_name("B"))),
-       });
-  Build();
-}
-
-}  // namespace ast_traversal
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
deleted file mode 100644
index 4d08e1f..0000000
--- a/src/resolver/entry_point_validation_test.cc
+++ /dev/null
@@ -1,804 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/builtin_attribute.h"
-#include "src/ast/location_attribute.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <typename T>
-using alias = builder::alias<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-class ResolverEntryPointValidationTest : public TestHelper,
-                                         public testing::Test {};
-
-TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Location) {
-  // @stage(fragment)
-  // fn main() -> @location(0) f32 { return 1.0; }
-  Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(1.0f)},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Builtin) {
-  // @stage(vertex)
-  // fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }
-  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)},
-       {Builtin(ast::Builtin::kPosition)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Missing) {
-  // @stage(vertex)
-  // fn main() -> f32 {
-  //   return 1.0;
-  // }
-  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: missing entry point IO attribute on return type");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Multiple) {
-  // @stage(vertex)
-  // fn main() -> @location(0) @builtin(position) vec4<f32> {
-  //   return vec4<f32>();
-  // }
-  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)},
-       {Location(Source{{13, 43}}, 0),
-        Builtin(Source{{14, 52}}, ast::Builtin::kPosition)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
-13:43 note: previously consumed location(0))");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_Valid) {
-  // struct Output {
-  //   @location(0) a : f32;
-  //   @builtin(frag_depth) b : f32;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output", {Member("a", ty.f32(), {Location(0)}),
-                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverEntryPointValidationTest,
-       ReturnType_Struct_MemberMultipleAttributes) {
-  // struct Output {
-  //   @location(0) @builtin(frag_depth) a : f32;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output",
-      {Member("a", ty.f32(),
-              {Location(Source{{13, 43}}, 0),
-               Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
-13:43 note: previously consumed location(0)
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest,
-       ReturnType_Struct_MemberMissingAttribute) {
-  // struct Output {
-  //   @location(0) a : f32;
-  //   b : f32;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
-                 Member(Source{{14, 52}}, "b", ty.f32(), {})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(14:52 error: missing entry point IO attribute
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
-  // struct Output {
-  //   @builtin(frag_depth) a : f32;
-  //   @builtin(frag_depth) b : f32;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
-                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
-  // @stage(fragment)
-  // fn main(@location(0) param : f32) {}
-  auto* param = Param("param", ty.f32(), {Location(0)});
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Missing) {
-  // @stage(fragment)
-  // fn main(param : f32) {}
-  auto* param = Param(Source{{13, 43}}, "param", ty.vec4<f32>());
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "13:43 error: missing entry point IO attribute on parameter");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
-  // @stage(fragment)
-  // fn main(@location(0) @builtin(sample_index) param : u32) {}
-  auto* param = Param("param", ty.u32(),
-                      {Location(Source{{13, 43}}, 0),
-                       Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)});
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
-13:43 note: previously consumed location(0))");
-}
-
-TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
-  // struct Input {
-  //   @location(0) a : f32;
-  //   @builtin(sample_index) b : u32;
-  // };
-  // @stage(fragment)
-  // fn main(param : Input) {}
-  auto* input = Structure(
-      "Input", {Member("a", ty.f32(), {Location(0)}),
-                Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverEntryPointValidationTest,
-       Parameter_Struct_MemberMultipleAttributes) {
-  // struct Input {
-  //   @location(0) @builtin(sample_index) a : u32;
-  // };
-  // @stage(fragment)
-  // fn main(param : Input) {}
-  auto* input = Structure(
-      "Input",
-      {Member("a", ty.u32(),
-              {Location(Source{{13, 43}}, 0),
-               Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
-13:43 note: previously consumed location(0)
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest,
-       Parameter_Struct_MemberMissingAttribute) {
-  // struct Input {
-  //   @location(0) a : f32;
-  //   b : f32;
-  // };
-  // @stage(fragment)
-  // fn main(param : Input) {}
-  auto* input = Structure(
-      "Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
-                Member(Source{{14, 52}}, "b", ty.f32(), {})});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
-  // @stage(fragment)
-  // fn main(@builtin(sample_index) param_a : u32,
-  //         @builtin(sample_index) param_b : u32) {}
-  auto* param_a =
-      Param("param_a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-  auto* param_b =
-      Param("param_b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: builtin(sample_index) attribute appears multiple times as "
-      "pipeline input");
-}
-
-TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
-  // struct InputA {
-  //   @builtin(sample_index) a : u32;
-  // };
-  // struct InputB {
-  //   @builtin(sample_index) a : u32;
-  // };
-  // @stage(fragment)
-  // fn main(param_a : InputA, param_b : InputB) {}
-  auto* input_a = Structure(
-      "InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
-  auto* input_b = Structure(
-      "InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
-  auto* param_a = Param("param_a", ty.Of(input_a));
-  auto* param_b = Param("param_b", ty.Of(input_b));
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
-  // @stage(vertex)
-  // fn main() {}
-  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: a vertex shader must include the 'position' builtin "
-            "in its return type");
-}
-
-namespace TypeValidationTests {
-struct Params {
-  builder::ast_type_func_ptr create_ast_type;
-  bool is_valid;
-};
-
-template <typename T>
-constexpr Params ParamsFor(bool is_valid) {
-  return Params{DataType<T>::AST, is_valid};
-}
-
-using TypeValidationTest = resolver::ResolverTestWithParam<Params>;
-
-static constexpr Params cases[] = {
-    ParamsFor<f32>(true),           //
-    ParamsFor<i32>(true),           //
-    ParamsFor<u32>(true),           //
-    ParamsFor<bool>(false),         //
-    ParamsFor<vec2<f32>>(true),     //
-    ParamsFor<vec3<f32>>(true),     //
-    ParamsFor<vec4<f32>>(true),     //
-    ParamsFor<mat2x2<f32>>(false),  //
-    ParamsFor<mat3x3<f32>>(false),  //
-    ParamsFor<mat4x4<f32>>(false),  //
-    ParamsFor<alias<f32>>(true),    //
-    ParamsFor<alias<i32>>(true),    //
-    ParamsFor<alias<u32>>(true),    //
-    ParamsFor<alias<bool>>(false),  //
-};
-
-TEST_P(TypeValidationTest, BareInputs) {
-  // @stage(fragment)
-  // fn main(@location(0) @interpolate(flat) a : *) {}
-  auto params = GetParam();
-  auto* a = Param("a", params.create_ast_type(*this), {Location(0), Flat()});
-  Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-  }
-}
-
-TEST_P(TypeValidationTest, StructInputs) {
-  // struct Input {
-  //   @location(0) @interpolate(flat) a : *;
-  // };
-  // @stage(fragment)
-  // fn main(a : Input) {}
-  auto params = GetParam();
-  auto* input = Structure("Input", {Member("a", params.create_ast_type(*this),
-                                           {Location(0), Flat()})});
-  auto* a = Param("a", ty.Of(input), {});
-  Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-  }
-}
-
-TEST_P(TypeValidationTest, BareOutputs) {
-  // @stage(fragment)
-  // fn main() -> @location(0) * {
-  //   return *();
-  // }
-  auto params = GetParam();
-  Func(Source{{12, 34}}, "main", {}, params.create_ast_type(*this),
-       {Return(Construct(params.create_ast_type(*this)))},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-  }
-}
-
-TEST_P(TypeValidationTest, StructOutputs) {
-  // struct Output {
-  //   @location(0) a : *;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto params = GetParam();
-  auto* output = Structure(
-      "Output", {Member("a", params.create_ast_type(*this), {Location(0)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverEntryPointValidationTest,
-                         TypeValidationTest,
-                         testing::ValuesIn(cases));
-
-}  // namespace TypeValidationTests
-
-namespace LocationAttributeTests {
-namespace {
-using LocationAttributeTests = ResolverTest;
-
-TEST_F(LocationAttributeTests, Pass) {
-  // @stage(fragment)
-  // fn frag_main(@location(0) @interpolate(flat) a: i32) {}
-
-  auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0), Flat()});
-  Func("frag_main", {p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(LocationAttributeTests, BadType_Input_bool) {
-  // @stage(fragment)
-  // fn frag_main(@location(0) a: bool) {}
-
-  auto* p =
-      Param(Source{{12, 34}}, "a", ty.bool_(), {Location(Source{{34, 56}}, 0)});
-  Func("frag_main", {p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot apply 'location' attribute to declaration of "
-            "type 'bool'\n"
-            "34:56 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadType_Output_Array) {
-  // @stage(fragment)
-  // fn frag_main()->@location(0) array<f32, 2> { return array<f32, 2>(); }
-
-  Func(Source{{12, 34}}, "frag_main", {}, ty.array<f32, 2>(),
-       {Return(Construct(ty.array<f32, 2>()))},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(Source{{34, 56}}, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot apply 'location' attribute to declaration of "
-            "type 'array<f32, 2>'\n"
-            "34:56 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadType_Input_Struct) {
-  // struct Input {
-  //   a : f32;
-  // };
-  // @stage(fragment)
-  // fn main(@location(0) param : Input) {}
-  auto* input = Structure("Input", {Member("a", ty.f32())});
-  auto* param = Param(Source{{12, 34}}, "param", ty.Of(input),
-                      {Location(Source{{13, 43}}, 0)});
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot apply 'location' attribute to declaration of "
-            "type 'Input'\n"
-            "13:43 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadType_Input_Struct_NestedStruct) {
-  // struct Inner {
-  //   @location(0) b : f32;
-  // };
-  // struct Input {
-  //   a : Inner;
-  // };
-  // @stage(fragment)
-  // fn main(param : Input) {}
-  auto* inner = Structure(
-      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* input =
-      Structure("Input", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "14:52 error: nested structures cannot be used for entry point IO\n"
-            "12:34 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationAttributeTests, BadType_Input_Struct_RuntimeArray) {
-  // [[block]]
-  // struct Input {
-  //   @location(0) a : array<f32>;
-  // };
-  // @stage(fragment)
-  // fn main(param : Input) {}
-  auto* input = Structure(
-      "Input",
-      {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
-      {create<ast::StructBlockAttribute>()});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "13:43 error: cannot apply 'location' attribute to declaration of "
-            "type 'array<f32>'\n"
-            "note: 'location' attribute must only be applied to declarations "
-            "of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadMemberType_Input) {
-  // [[block]]
-  // struct S { @location(0) m: array<i32>; };
-  // @stage(fragment)
-  // fn frag_main( a: S) {}
-
-  auto* m = Member(Source{{34, 56}}, "m", ty.array<i32>(),
-                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m}, ast::AttributeList{StructBlock()});
-  auto* p = Param("a", ty.Of(s));
-
-  Func("frag_main", {p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "34:56 error: cannot apply 'location' attribute to declaration of "
-            "type 'array<i32>'\n"
-            "12:34 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadMemberType_Output) {
-  // struct S { @location(0) m: atomic<i32>; };
-  // @stage(fragment)
-  // fn frag_main() -> S {}
-  auto* m = Member(Source{{34, 56}}, "m", ty.atomic<i32>(),
-                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m});
-
-  Func("frag_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-       {Stage(ast::PipelineStage::kFragment)}, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "34:56 error: cannot apply 'location' attribute to declaration of "
-            "type 'atomic<i32>'\n"
-            "12:34 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, BadMemberType_Unused) {
-  // struct S { @location(0) m: mat3x2<f32>; };
-
-  auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
-                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  Structure("S", {m});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "34:56 error: cannot apply 'location' attribute to declaration of "
-            "type 'mat3x2<f32>'\n"
-            "12:34 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, ReturnType_Struct_Valid) {
-  // struct Output {
-  //   @location(0) a : f32;
-  //   @builtin(frag_depth) b : f32;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output", {Member("a", ty.f32(), {Location(0)}),
-                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(LocationAttributeTests, ReturnType_Struct) {
-  // struct Output {
-  //   a : f32;
-  // };
-  // @stage(vertex)
-  // fn main() -> @location(0) Output {
-  //   return Output();
-  // }
-  auto* output = Structure("Output", {Member("a", ty.f32())});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
-       {Location(Source{{13, 43}}, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot apply 'location' attribute to declaration of "
-            "type 'Output'\n"
-            "13:43 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, ReturnType_Struct_NestedStruct) {
-  // struct Inner {
-  //   @location(0) b : f32;
-  // };
-  // struct Output {
-  //   a : Inner;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output { return Output(); }
-  auto* inner = Structure(
-      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* output =
-      Structure("Output", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "14:52 error: nested structures cannot be used for entry point IO\n"
-            "12:34 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationAttributeTests, ReturnType_Struct_RuntimeArray) {
-  // [[block]]
-  // struct Output {
-  //   @location(0) a : array<f32>;
-  // };
-  // @stage(fragment)
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure("Output",
-                           {Member(Source{{13, 43}}, "a", ty.array<float>(),
-                                   {Location(Source{{12, 34}}, 0)})},
-                           {create<ast::StructBlockAttribute>()});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "13:43 error: cannot apply 'location' attribute to declaration of "
-            "type 'array<f32>'\n"
-            "12:34 note: 'location' attribute must only be applied to "
-            "declarations of numeric scalar or numeric vector type");
-}
-
-TEST_F(LocationAttributeTests, ComputeShaderLocation_Input) {
-  Func("main", {}, ty.i32(), {Return(Expr(1))},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))},
-       ast::AttributeList{Location(Source{{12, 34}}, 1)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: attribute is not valid for compute shader output");
-}
-
-TEST_F(LocationAttributeTests, ComputeShaderLocation_Output) {
-  auto* input = Param("input", ty.i32(),
-                      ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  Func("main", {input}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: attribute is not valid for compute shader inputs");
-}
-
-TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Output) {
-  auto* m =
-      Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m});
-  Func(Source{{56, 78}}, "main", {}, ty.Of(s),
-       ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: attribute is not valid for compute shader output\n"
-            "56:78 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Input) {
-  auto* m =
-      Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m});
-  auto* input = Param("input", ty.Of(s));
-  Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: attribute is not valid for compute shader inputs\n"
-            "56:78 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationAttributeTests, Duplicate_input) {
-  // @stage(fragment)
-  // fn main(@location(1) param_a : f32,
-  //         @location(1) param_b : f32) {}
-  auto* param_a = Param("param_a", ty.f32(), {Location(1)});
-  auto* param_b = Param("param_b", ty.f32(), {Location(Source{{12, 34}}, 1)});
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: location(1) attribute appears multiple times");
-}
-
-TEST_F(LocationAttributeTests, Duplicate_struct) {
-  // struct InputA {
-  //   @location(1) a : f32;
-  // };
-  // struct InputB {
-  //   @location(1) a : f32;
-  // };
-  // @stage(fragment)
-  // fn main(param_a : InputA, param_b : InputB) {}
-  auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
-  auto* input_b = Structure(
-      "InputB", {Member("a", ty.f32(), {Location(Source{{34, 56}}, 1)})});
-  auto* param_a = Param("param_a", ty.Of(input_a));
-  auto* param_b = Param("param_b", ty.Of(input_b));
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "34:56 error: location(1) attribute appears multiple times\n"
-            "12:34 note: while analysing entry point 'main'");
-}
-
-}  // namespace
-}  // namespace LocationAttributeTests
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
deleted file mode 100644
index 7c46461..0000000
--- a/src/resolver/function_validation_test.cc
+++ /dev/null
@@ -1,830 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/discard_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace {
-
-class ResolverFunctionValidationTest : public resolver::TestHelper,
-                                       public testing::Test {};
-
-TEST_F(ResolverFunctionValidationTest, DuplicateParameterName) {
-  // fn func_a(common_name : f32) { }
-  // fn func_b(common_name : f32) { }
-  Func("func_a", {Param("common_name", ty.f32())}, ty.void_(), {});
-  Func("func_b", {Param("common_name", ty.f32())}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterMayShadowGlobal) {
-  // var<private> common_name : f32;
-  // fn func(common_name : f32) { }
-  Global("common_name", ty.f32(), ast::StorageClass::kPrivate);
-  Func("func", {Param("common_name", ty.f32())}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, LocalConflictsWithParameter) {
-  // fn func(common_name : f32) {
-  //   let common_name = 1;
-  // }
-  Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
-       {Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1)))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), R"(56:78 error: redeclaration of 'common_name'
-12:34 note: 'common_name' previously declared here)");
-}
-
-TEST_F(ResolverFunctionValidationTest, NestedLocalMayShadowParameter) {
-  // fn func(common_name : f32) {
-  //   {
-  //     let common_name = 1;
-  //   }
-  // }
-  Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
-       {Block(Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1))))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       VoidFunctionEndWithoutReturnStatement_Pass) {
-  // fn func { var a:i32 = 2; }
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-       });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, FunctionUsingSameVariableName_Pass) {
-  // fn func() -> i32 {
-  //   var func:i32 = 0;
-  //   return func;
-  // }
-
-  auto* var = Var("func", ty.i32(), Expr(0));
-  Func("func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           Decl(var),
-           Return(Source{{12, 34}}, Expr("func")),
-       },
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionNameSameAsFunctionScopeVariableName_Pass) {
-  // fn a() -> void { var b:i32 = 0; }
-  // fn b() -> i32 { return 2; }
-
-  auto* var = Var("b", ty.i32(), Expr(0));
-  Func("a", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-       },
-       ast::AttributeList{});
-
-  Func(Source{{12, 34}}, "b", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           Return(2),
-       },
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, UnreachableCode_return) {
-  // fn func() -> {
-  //  var a : i32;
-  //  return;
-  //  a = 2;
-  //}
-
-  auto* decl_a = Decl(Var("a", ty.i32()));
-  auto* ret = Return();
-  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
-
-  Func("func", ast::VariableList{}, ty.void_(), {decl_a, ret, assign_a});
-
-  ASSERT_TRUE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
-  EXPECT_TRUE(Sem().Get(ret)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
-}
-
-TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
-  // fn func() -> {
-  //  var a : i32;
-  //  {{{return;}}}
-  //  a = 2;
-  //}
-
-  auto* decl_a = Decl(Var("a", ty.i32()));
-  auto* ret = Return();
-  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
-
-  Func("func", ast::VariableList{}, ty.void_(),
-       {decl_a, Block(Block(Block(ret))), assign_a});
-
-  ASSERT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
-  EXPECT_TRUE(Sem().Get(ret)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
-}
-
-TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
-  // fn func() -> {
-  //  var a : i32;
-  //  discard;
-  //  a = 2;
-  //}
-
-  auto* decl_a = Decl(Var("a", ty.i32()));
-  auto* discard = Discard();
-  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
-
-  Func("func", ast::VariableList{}, ty.void_(), {decl_a, discard, assign_a});
-
-  ASSERT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
-  EXPECT_TRUE(Sem().Get(discard)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
-}
-
-TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
-  // fn func() -> {
-  //  var a : i32;
-  //  {{{discard;}}}
-  //  a = 2;
-  //}
-
-  auto* decl_a = Decl(Var("a", ty.i32()));
-  auto* discard = Discard();
-  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
-
-  Func("func", ast::VariableList{}, ty.void_(),
-       {decl_a, Block(Block(Block(discard))), assign_a});
-
-  ASSERT_TRUE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
-  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
-  EXPECT_TRUE(Sem().Get(discard)->IsReachable());
-  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
-}
-
-TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
-  // fn func() -> int { var a:i32 = 2; }
-
-  auto* var = Var("a", ty.i32(), Expr(2));
-
-  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           Decl(var),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
-  // fn func {}
-
-  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionEndWithoutReturnStatementEmptyBody_Fail) {
-  // fn func() -> int {}
-
-  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{}, ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementType_Pass) {
-  // fn func { return; }
-
-  Func("func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementType_fail) {
-  // fn func { return 2; }
-  Func("func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(Source{{12, 34}}, Expr(2)),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: return statement type must match its function return "
-            "type, returned 'i32', expected 'void'");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementType_void_fail) {
-  // fn v { return; }
-  // fn func { return v(); }
-  Func("v", {}, ty.void_(), {Return()});
-  Func("func", {}, ty.void_(),
-       {
-           Return(Call(Source{{12, 34}}, "v")),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: function 'v' does not return a value");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementTypeMissing_fail) {
-  // fn func() -> f32 { return; }
-  Func("func", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return(Source{{12, 34}}, nullptr),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: return statement type must match its function return "
-            "type, returned 'void', expected 'f32'");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementTypeF32_pass) {
-  // fn func() -> f32 { return 2.0; }
-  Func("func", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return(Source{{12, 34}}, Expr(2.f)),
-       },
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementTypeF32_fail) {
-  // fn func() -> f32 { return 2; }
-  Func("func", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return(Source{{12, 34}}, Expr(2)),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: return statement type must match its function return "
-            "type, returned 'i32', expected 'f32'");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementTypeF32Alias_pass) {
-  // type myf32 = f32;
-  // fn func() -> myf32 { return 2.0; }
-  auto* myf32 = Alias("myf32", ty.f32());
-  Func("func", ast::VariableList{}, ty.Of(myf32),
-       ast::StatementList{
-           Return(Source{{12, 34}}, Expr(2.f)),
-       },
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       FunctionTypeMustMatchReturnStatementTypeF32Alias_fail) {
-  // type myf32 = f32;
-  // fn func() -> myf32 { return 2; }
-  auto* myf32 = Alias("myf32", ty.f32());
-  Func("func", ast::VariableList{}, ty.Of(myf32),
-       ast::StatementList{
-           Return(Source{{12, 34}}, Expr(2u)),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: return statement type must match its function return "
-            "type, returned 'u32', expected 'f32'");
-}
-
-TEST_F(ResolverFunctionValidationTest, CannotCallEntryPoint) {
-  // @stage(compute) @workgroup_size(1) fn entrypoint() {}
-  // fn func() { return entrypoint(); }
-  Func("entrypoint", ast::VariableList{}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  Func("func", ast::VariableList{}, ty.void_(),
-       {
-           CallStmt(Call(Source{{12, 34}}, "entrypoint")),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-
-      R"(12:34 error: entry point functions cannot be the target of a function call)");
-}
-
-TEST_F(ResolverFunctionValidationTest, PipelineStage_MustBeUnique_Fail) {
-  // @stage(fragment)
-  // @stage(vertex)
-  // fn main() { return; }
-  Func(Source{{12, 34}}, "main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{
-           Stage(Source{{12, 34}}, ast::PipelineStage::kVertex),
-           Stage(Source{{56, 78}}, ast::PipelineStage::kFragment),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(56:78 error: duplicate stage attribute
-12:34 note: first attribute declared here)");
-}
-
-TEST_F(ResolverFunctionValidationTest, NoPipelineEntryPoints) {
-  Func("vtx_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, FunctionVarInitWithParam) {
-  // fn foo(bar : f32){
-  //   var baz : f32 = bar;
-  // }
-
-  auto* bar = Param("bar", ty.f32());
-  auto* baz = Var("baz", ty.f32(), Expr("bar"));
-
-  Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, FunctionConstInitWithParam) {
-  // fn foo(bar : f32){
-  //   let baz : f32 = bar;
-  // }
-
-  auto* bar = Param("bar", ty.f32());
-  auto* baz = Const("baz", ty.f32(), Expr("bar"));
-
-  Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
-       ast::AttributeList{});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, FunctionParamsConst) {
-  Func("foo", {Param(Sym("arg"), ty.i32())}, ty.void_(),
-       {Assign(Expr(Source{{12, 34}}, "arg"), Expr(1)), Return()});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot assign to function parameter\nnote: 'arg' is "
-            "declared here:");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_ConstU32) {
-  // let x = 4u;
-  // let x = 8u;
-  // @stage(compute) @workgroup_size(x, y, 16u)
-  // fn main() {}
-  auto* x = GlobalConst("x", ty.u32(), Expr(4u));
-  auto* y = GlobalConst("y", ty.u32(), Expr(8u));
-  auto* func = Func("main", {}, ty.void_(), {},
-                    {Stage(ast::PipelineStage::kCompute),
-                     WorkgroupSize(Expr("x"), Expr("y"), Expr(16u))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem_func = Sem().Get(func);
-  auto* sem_x = Sem().Get<sem::GlobalVariable>(x);
-  auto* sem_y = Sem().Get<sem::GlobalVariable>(y);
-
-  ASSERT_NE(sem_func, nullptr);
-  ASSERT_NE(sem_x, nullptr);
-  ASSERT_NE(sem_y, nullptr);
-
-  EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_x));
-  EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_y));
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_U32) {
-  // [[stage(compute), workgroup_size(1u, 2u, 3u)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Source{{12, 34}}, Expr(1u), Expr(2u), Expr(3u))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeU32) {
-  // [[stage(compute), workgroup_size(1u, 2u, 3)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(1u), Expr(2u), Expr(Source{{12, 34}}, 3))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size arguments must be of the same type, "
-            "either i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeI32) {
-  // [[stage(compute), workgroup_size(1, 2u, 3)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, 2u), Expr(3))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size arguments must be of the same type, "
-            "either i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch) {
-  // let x = 64u;
-  // [[stage(compute), workgroup_size(1, x)]
-  // fn main() {}
-  GlobalConst("x", ty.u32(), Expr(64u));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size arguments must be of the same type, "
-            "either i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch2) {
-  // let x = 64u;
-  // let y = 32;
-  // [[stage(compute), workgroup_size(x, y)]
-  // fn main() {}
-  GlobalConst("x", ty.u32(), Expr(64u));
-  GlobalConst("y", ty.i32(), Expr(32));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr("x"), Expr(Source{{12, 34}}, "y"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size arguments must be of the same type, "
-            "either i32 or u32");
-}
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Mismatch_ConstU32) {
-  // let x = 4u;
-  // let x = 8u;
-  // [[stage(compute), workgroup_size(x, y, 16]
-  // fn main() {}
-  GlobalConst("x", ty.u32(), Expr(4u));
-  GlobalConst("y", ty.u32(), Expr(8u));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr("x"), Expr("y"), Expr(Source{{12, 34}}, 16))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size arguments must be of the same type, "
-            "either i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_BadType) {
-  // [[stage(compute), workgroup_size(64.0)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, 64.f))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be either literal or "
-            "module-scope constant of type i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Negative) {
-  // [[stage(compute), workgroup_size(-2)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, -2))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be at least 1");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Zero) {
-  // [[stage(compute), workgroup_size(0)]
-  // fn main() {}
-
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, 0))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be at least 1");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_BadType) {
-  // let x = 64.0;
-  // [[stage(compute), workgroup_size(x)]
-  // fn main() {}
-  GlobalConst("x", ty.f32(), Expr(64.f));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be either literal or "
-            "module-scope constant of type i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Negative) {
-  // let x = -2;
-  // [[stage(compute), workgroup_size(x)]
-  // fn main() {}
-  GlobalConst("x", ty.i32(), Expr(-2));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be at least 1");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Zero) {
-  // let x = 0;
-  // [[stage(compute), workgroup_size(x)]
-  // fn main() {}
-  GlobalConst("x", ty.i32(), Expr(0));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be at least 1");
-}
-
-TEST_F(ResolverFunctionValidationTest,
-       WorkgroupSize_Const_NestedZeroValueConstructor) {
-  // let x = i32(i32(i32()));
-  // [[stage(compute), workgroup_size(x)]
-  // fn main() {}
-  GlobalConst("x", ty.i32(),
-              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32()))));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be at least 1");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_NonConst) {
-  // var<private> x = 0;
-  // [[stage(compute), workgroup_size(x)]
-  // fn main() {}
-  Global("x", ty.i32(), ast::StorageClass::kPrivate, Expr(64));
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be either literal or "
-            "module-scope constant of type i32 or u32");
-}
-
-TEST_F(ResolverFunctionValidationTest, WorkgroupSize_InvalidExpr) {
-  // [[stage(compute), workgroup_size(i32(1))]
-  // fn main() {}
-  Func("main", {}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        WorkgroupSize(Construct(Source{{12, 34}}, ty.i32(), 1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: workgroup_size argument must be either a literal or "
-            "a module-scope constant");
-}
-
-TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_NonPlain) {
-  auto* ret_type =
-      ty.pointer(Source{{12, 34}}, ty.i32(), ast::StorageClass::kFunction);
-  Func("f", {}, ret_type, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function return type must be a constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_AtomicInt) {
-  auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
-  Func("f", {}, ret_type, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function return type must be a constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_ArrayOfAtomic) {
-  auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10);
-  Func("f", {}, ret_type, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function return type must be a constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_StructOfAtomic) {
-  Structure("S", {Member("m", ty.atomic(ty.i32()))});
-  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
-  Func("f", {}, ret_type, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function return type must be a constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
-  auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
-  Func("f", {}, ret_type, {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function return type must be a constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterStoreType_NonAtomicFree) {
-  Structure("S", {Member("m", ty.atomic(ty.i32()))});
-  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
-  auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
-  Func("f", ast::VariableList{bar}, ty.void_(), {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: store type of function parameter must be a "
-            "constructible type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterSotreType_AtomicFree) {
-  Structure("S", {Member("m", ty.i32())});
-  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
-  auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
-  Func("f", ast::VariableList{bar}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, ParametersAtLimit) {
-  ast::VariableList params;
-  for (int i = 0; i < 255; i++) {
-    params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
-  }
-  Func(Source{{12, 34}}, "f", params, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverFunctionValidationTest, ParametersOverLimit) {
-  ast::VariableList params;
-  for (int i = 0; i < 256; i++) {
-    params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
-  }
-  Func(Source{{12, 34}}, "f", params, ty.void_(), {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: functions may declare at most 255 parameters");
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
-  // fn f(p : vec3) {}
-
-  Func(Source{{12, 34}}, "f",
-       {Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))},
-       ty.void_(), {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
-  // fn f(p : vec3) {}
-
-  Func(Source{{12, 34}}, "f",
-       {Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))},
-       ty.void_(), {});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
-}
-
-struct TestParams {
-  ast::StorageClass storage_class;
-  bool should_pass;
-};
-
-struct TestWithParams : resolver::ResolverTestWithParam<TestParams> {};
-
-using ResolverFunctionParameterValidationTest = TestWithParams;
-TEST_P(ResolverFunctionParameterValidationTest, StorageClass) {
-  auto& param = GetParam();
-  auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.storage_class);
-  auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
-  Func("f", ast::VariableList{arg}, ty.void_(), {});
-
-  if (param.should_pass) {
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    std::stringstream ss;
-    ss << param.storage_class;
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: function parameter of pointer type cannot be in '" +
-                  ss.str() + "' storage class");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverFunctionParameterValidationTest,
-    testing::Values(TestParams{ast::StorageClass::kNone, false},
-                    TestParams{ast::StorageClass::kInput, false},
-                    TestParams{ast::StorageClass::kOutput, false},
-                    TestParams{ast::StorageClass::kUniform, false},
-                    TestParams{ast::StorageClass::kWorkgroup, true},
-                    TestParams{ast::StorageClass::kUniformConstant, false},
-                    TestParams{ast::StorageClass::kStorage, false},
-                    TestParams{ast::StorageClass::kPrivate, true},
-                    TestParams{ast::StorageClass::kFunction, true}));
-
-}  // namespace
-}  // namespace tint
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
deleted file mode 100644
index 15a72bb..0000000
--- a/src/resolver/host_shareable_validation_test.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/struct.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverHostShareableValidationTest = ResolverTest;
-
-TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-12:34 note: while analysing structure member S.x
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'vec3<bool>' cannot be used in storage class 'storage' as it is non-host-shareable
-12:34 note: while analysing structure member S.x
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverHostShareableValidationTest, Aliases) {
-  auto* a1 = Alias("a1", ty.bool_());
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.Of(a1))},
-                      {create<ast::StructBlockAttribute>()});
-  auto* a2 = Alias("a2", ty.Of(s));
-  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-12:34 note: while analysing structure member S.x
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
-  auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
-  auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", ty.Of(i1))});
-  auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", ty.Of(i2))});
-
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(9:10 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-1:2 note: while analysing structure member I1.x
-3:4 note: while analysing structure member I2.y
-5:6 note: while analysing structure member I3.z
-7:8 note: while analysing structure member S.m
-9:10 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverHostShareableValidationTest, NoError) {
-  auto* i1 =
-      Structure("I1", {
-                          Member(Source{{1, 1}}, "x1", ty.f32()),
-                          Member(Source{{2, 1}}, "y1", ty.vec3<f32>()),
-                          Member(Source{{3, 1}}, "z1", ty.array<i32, 4>()),
-                      });
-  auto* a1 = Alias("a1", ty.Of(i1));
-  auto* i2 = Structure("I2", {
-                                 Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
-                                 Member(Source{{5, 1}}, "y2", ty.Of(i1)),
-                             });
-  auto* a2 = Alias("a2", ty.Of(i2));
-  auto* i3 = Structure("I3", {
-                                 Member(Source{{4, 1}}, "x3", ty.Of(a1)),
-                                 Member(Source{{5, 1}}, "y3", ty.Of(i2)),
-                                 Member(Source{{6, 1}}, "z3", ty.Of(a2)),
-                             });
-
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-  WrapInFunction();
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/inferred_type_test.cc b/src/resolver/inferred_type_test.cc
deleted file mode 100644
index f1f418c..0000000
--- a/src/resolver/inferred_type_test.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <typename T>
-using alias = builder::alias<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-struct ResolverInferredTypeTest : public resolver::TestHelper,
-                                  public testing::Test {};
-
-struct Params {
-  builder::ast_expr_func_ptr create_value;
-  builder::sem_type_func_ptr create_expected_type;
-};
-
-template <typename T>
-constexpr Params ParamsFor() {
-  return Params{DataType<T>::Expr, DataType<T>::Sem};
-}
-
-Params all_cases[] = {
-    ParamsFor<bool>(),                //
-    ParamsFor<u32>(),                 //
-    ParamsFor<i32>(),                 //
-    ParamsFor<f32>(),                 //
-    ParamsFor<vec3<bool>>(),          //
-    ParamsFor<vec3<i32>>(),           //
-    ParamsFor<vec3<u32>>(),           //
-    ParamsFor<vec3<f32>>(),           //
-    ParamsFor<mat3x3<f32>>(),         //
-    ParamsFor<alias<bool>>(),         //
-    ParamsFor<alias<u32>>(),          //
-    ParamsFor<alias<i32>>(),          //
-    ParamsFor<alias<f32>>(),          //
-    ParamsFor<alias<vec3<bool>>>(),   //
-    ParamsFor<alias<vec3<i32>>>(),    //
-    ParamsFor<alias<vec3<u32>>>(),    //
-    ParamsFor<alias<vec3<f32>>>(),    //
-    ParamsFor<alias<mat3x3<f32>>>(),  //
-};
-
-using ResolverInferredTypeParamTest = ResolverTestWithParam<Params>;
-
-TEST_P(ResolverInferredTypeParamTest, GlobalLet_Pass) {
-  auto& params = GetParam();
-
-  auto* expected_type = params.create_expected_type(*this);
-
-  // let a = <type constructor>;
-  auto* ctor_expr = params.create_value(*this, 0);
-  auto* var = GlobalConst("a", nullptr, ctor_expr);
-  WrapInFunction();
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(var), expected_type);
-}
-
-TEST_P(ResolverInferredTypeParamTest, GlobalVar_Fail) {
-  auto& params = GetParam();
-
-  // var a = <type constructor>;
-  auto* ctor_expr = params.create_value(*this, 0);
-  Global(Source{{12, 34}}, "a", nullptr, ast::StorageClass::kPrivate,
-         ctor_expr);
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: global var declaration must specify a type");
-}
-
-TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) {
-  auto& params = GetParam();
-
-  auto* expected_type = params.create_expected_type(*this);
-
-  // let a = <type constructor>;
-  auto* ctor_expr = params.create_value(*this, 0);
-  auto* var = Const("a", nullptr, ctor_expr);
-  WrapInFunction(var);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(var), expected_type);
-}
-
-TEST_P(ResolverInferredTypeParamTest, LocalVar_Pass) {
-  auto& params = GetParam();
-
-  auto* expected_type = params.create_expected_type(*this);
-
-  // var a = <type constructor>;
-  auto* ctor_expr = params.create_value(*this, 0);
-  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
-  WrapInFunction(var);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverInferredTypeParamTest,
-                         testing::ValuesIn(all_cases));
-
-TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
-  auto* type = ty.array(ty.u32(), 10);
-  auto* expected_type =
-      create<sem::Array>(create<sem::U32>(), 10, 4, 4 * 10, 4, 4);
-
-  auto* ctor_expr = Construct(type);
-  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
-  WrapInFunction(var);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
-}
-
-TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
-  auto* member = Member("x", ty.i32());
-  auto* str = Structure("S", {member}, {create<ast::StructBlockAttribute>()});
-
-  auto* expected_type = create<sem::Struct>(
-      str, str->name,
-      sem::StructMemberList{create<sem::StructMember>(
-          member, member->symbol, create<sem::I32>(), 0, 0, 0, 4)},
-      0, 4, 4);
-
-  auto* ctor_expr = Construct(ty.Of(str));
-
-  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
-  WrapInFunction(var);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/is_host_shareable_test.cc b/src/resolver/is_host_shareable_test.cc
deleted file mode 100644
index 804d8ee..0000000
--- a/src/resolver/is_host_shareable_test.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/atomic_type.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverIsHostShareable = ResolverTest;
-
-TEST_F(ResolverIsHostShareable, Void) {
-  EXPECT_FALSE(r()->IsHostShareable(create<sem::Void>()));
-}
-
-TEST_F(ResolverIsHostShareable, Bool) {
-  EXPECT_FALSE(r()->IsHostShareable(create<sem::Bool>()));
-}
-
-TEST_F(ResolverIsHostShareable, NumericScalar) {
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::I32>()));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::U32>()));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::F32>()));
-}
-
-TEST_F(ResolverIsHostShareable, NumericVector) {
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 4)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 4)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 4)));
-}
-
-TEST_F(ResolverIsHostShareable, BoolVector) {
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
-  EXPECT_FALSE(
-      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
-}
-
-TEST_F(ResolverIsHostShareable, Matrix) {
-  auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
-  auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
-  auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
-
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 4)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 4)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 2)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 3)));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 4)));
-}
-
-TEST_F(ResolverIsHostShareable, Pointer) {
-  auto* ptr = create<sem::Pointer>(
-      create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
-  EXPECT_FALSE(r()->IsHostShareable(ptr));
-}
-
-TEST_F(ResolverIsHostShareable, Atomic) {
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::I32>())));
-  EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::U32>())));
-}
-
-TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
-  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
-  EXPECT_TRUE(r()->IsHostShareable(arr));
-}
-
-TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
-  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
-  EXPECT_TRUE(r()->IsHostShareable(arr));
-}
-
-// Note: Structure tests covered in host_shareable_validation_test.cc
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
deleted file mode 100644
index f084d27..0000000
--- a/src/resolver/is_storeable_test.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/atomic_type.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverIsStorableTest = ResolverTest;
-
-TEST_F(ResolverIsStorableTest, Void) {
-  EXPECT_FALSE(r()->IsStorable(create<sem::Void>()));
-}
-
-TEST_F(ResolverIsStorableTest, Scalar) {
-  EXPECT_TRUE(r()->IsStorable(create<sem::Bool>()));
-  EXPECT_TRUE(r()->IsStorable(create<sem::I32>()));
-  EXPECT_TRUE(r()->IsStorable(create<sem::U32>()));
-  EXPECT_TRUE(r()->IsStorable(create<sem::F32>()));
-}
-
-TEST_F(ResolverIsStorableTest, Vector) {
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 4)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 4)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 4)));
-}
-
-TEST_F(ResolverIsStorableTest, Matrix) {
-  auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
-  auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
-  auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 4)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 4)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 2)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 3)));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 4)));
-}
-
-TEST_F(ResolverIsStorableTest, Pointer) {
-  auto* ptr = create<sem::Pointer>(
-      create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
-  EXPECT_FALSE(r()->IsStorable(ptr));
-}
-
-TEST_F(ResolverIsStorableTest, Atomic) {
-  EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::I32>())));
-  EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::U32>())));
-}
-
-TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
-  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
-  EXPECT_TRUE(r()->IsStorable(arr));
-}
-
-TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
-  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
-  EXPECT_TRUE(r()->IsStorable(arr));
-}
-
-TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
-  Structure("S", {
-                     Member("a", ty.i32()),
-                     Member("b", ty.f32()),
-                 });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
-  Structure("S", {
-                     Member("a", ty.i32()),
-                     Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
-}
-
-TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
-  auto* storable = Structure("Storable", {
-                                             Member("a", ty.i32()),
-                                             Member("b", ty.f32()),
-                                         });
-  Structure("S", {
-                     Member("a", ty.i32()),
-                     Member("b", ty.Of(storable)),
-                 });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
-  auto* non_storable =
-      Structure("nonstorable",
-                {
-                    Member("a", ty.i32()),
-                    Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
-                });
-  Structure("S", {
-                     Member("a", ty.i32()),
-                     Member("b", ty.Of(non_storable)),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/pipeline_overridable_constant_test.cc b/src/resolver/pipeline_overridable_constant_test.cc
deleted file mode 100644
index 64ca315..0000000
--- a/src/resolver/pipeline_overridable_constant_test.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-class ResolverPipelineOverridableConstantTest : public ResolverTest {
- protected:
-  /// Verify that the AST node `var` was resolved to an overridable constant
-  /// with an ID equal to `id`.
-  /// @param var the overridable constant AST node
-  /// @param id the expected constant ID
-  void ExpectConstantId(const ast::Variable* var, uint16_t id) {
-    auto* sem = Sem().Get<sem::GlobalVariable>(var);
-    ASSERT_NE(sem, nullptr);
-    EXPECT_EQ(sem->Declaration(), var);
-    EXPECT_TRUE(sem->IsOverridable());
-    EXPECT_EQ(sem->ConstantId(), id);
-    EXPECT_FALSE(sem->ConstantValue());
-  }
-};
-
-TEST_F(ResolverPipelineOverridableConstantTest, NonOverridable) {
-  auto* a = GlobalConst("a", ty.f32(), Expr(1.f));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem_a = Sem().Get<sem::GlobalVariable>(a);
-  ASSERT_NE(sem_a, nullptr);
-  EXPECT_EQ(sem_a->Declaration(), a);
-  EXPECT_FALSE(sem_a->IsOverridable());
-  EXPECT_TRUE(sem_a->ConstantValue());
-}
-
-TEST_F(ResolverPipelineOverridableConstantTest, WithId) {
-  auto* a = Override("a", ty.f32(), Expr(1.f), {Id(7u)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ExpectConstantId(a, 7u);
-}
-
-TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
-  auto* a = Override("a", ty.f32(), Expr(1.f));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ExpectConstantId(a, 0u);
-}
-
-TEST_F(ResolverPipelineOverridableConstantTest, WithAndWithoutIds) {
-  std::vector<ast::Variable*> variables;
-  auto* a = Override("a", ty.f32(), Expr(1.f));
-  auto* b = Override("b", ty.f32(), Expr(1.f));
-  auto* c = Override("c", ty.f32(), Expr(1.f), {Id(2u)});
-  auto* d = Override("d", ty.f32(), Expr(1.f), {Id(4u)});
-  auto* e = Override("e", ty.f32(), Expr(1.f));
-  auto* f = Override("f", ty.f32(), Expr(1.f), {Id(1u)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  // Verify that constant id allocation order is deterministic.
-  ExpectConstantId(a, 0u);
-  ExpectConstantId(b, 3u);
-  ExpectConstantId(c, 2u);
-  ExpectConstantId(d, 4u);
-  ExpectConstantId(e, 5u);
-  ExpectConstantId(f, 1u);
-}
-
-TEST_F(ResolverPipelineOverridableConstantTest, DuplicateIds) {
-  Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 7u)});
-  Override("b", ty.f32(), Expr(1.f), {Id(Source{{56, 78}}, 7u)});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(56:78 error: pipeline constant IDs must be unique
-12:34 note: a pipeline constant with an ID of 7 was previously declared here:)");
-}
-
-TEST_F(ResolverPipelineOverridableConstantTest, IdTooLarge) {
-  Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 65536u)});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: pipeline constant IDs must be between 0 and 65535");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/ptr_ref_test.cc b/src/resolver/ptr_ref_test.cc
deleted file mode 100644
index 694f07c..0000000
--- a/src/resolver/ptr_ref_test.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/reference_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverPtrRefTest : public resolver::TestHelper,
-                            public testing::Test {};
-
-TEST_F(ResolverPtrRefTest, AddressOf) {
-  // var v : i32;
-  // &v
-
-  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
-  auto* expr = AddressOf(v);
-
-  WrapInFunction(v, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Pointer>());
-  EXPECT_TRUE(TypeOf(expr)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(expr)->As<sem::Pointer>()->StorageClass(),
-            ast::StorageClass::kFunction);
-}
-
-TEST_F(ResolverPtrRefTest, AddressOfThenDeref) {
-  // var v : i32;
-  // *(&v)
-
-  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
-  auto* expr = Deref(AddressOf(v));
-
-  WrapInFunction(v, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
-  EXPECT_TRUE(TypeOf(expr)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
-}
-
-TEST_F(ResolverPtrRefTest, DefaultPtrStorageClass) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
-
-  auto* buf = Structure("S", {Member("m", ty.i32())},
-                        {create<ast::StructBlockAttribute>()});
-  auto* function = Var("f", ty.i32());
-  auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
-  auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(0),
-                             create<ast::GroupAttribute>(0),
-                         });
-  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(1),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  auto* function_ptr =
-      Const("f_ptr", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
-            AddressOf(function));
-  auto* private_ptr =
-      Const("p_ptr", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
-            AddressOf(private_));
-  auto* workgroup_ptr =
-      Const("w_ptr", ty.pointer(ty.i32(), ast::StorageClass::kWorkgroup),
-            AddressOf(workgroup));
-  auto* uniform_ptr =
-      Const("ub_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kUniform),
-            AddressOf(uniform));
-  auto* storage_ptr =
-      Const("sb_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kStorage),
-            AddressOf(storage));
-
-  WrapInFunction(function, function_ptr, private_ptr, workgroup_ptr,
-                 uniform_ptr, storage_ptr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(function_ptr)->Is<sem::Pointer>())
-      << "function_ptr is " << TypeOf(function_ptr)->TypeInfo().name;
-  ASSERT_TRUE(TypeOf(private_ptr)->Is<sem::Pointer>())
-      << "private_ptr is " << TypeOf(private_ptr)->TypeInfo().name;
-  ASSERT_TRUE(TypeOf(workgroup_ptr)->Is<sem::Pointer>())
-      << "workgroup_ptr is " << TypeOf(workgroup_ptr)->TypeInfo().name;
-  ASSERT_TRUE(TypeOf(uniform_ptr)->Is<sem::Pointer>())
-      << "uniform_ptr is " << TypeOf(uniform_ptr)->TypeInfo().name;
-  ASSERT_TRUE(TypeOf(storage_ptr)->Is<sem::Pointer>())
-      << "storage_ptr is " << TypeOf(storage_ptr)->TypeInfo().name;
-
-  EXPECT_EQ(TypeOf(function_ptr)->As<sem::Pointer>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(private_ptr)->As<sem::Pointer>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(workgroup_ptr)->As<sem::Pointer>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(uniform_ptr)->As<sem::Pointer>()->Access(),
-            ast::Access::kRead);
-  EXPECT_EQ(TypeOf(storage_ptr)->As<sem::Pointer>()->Access(),
-            ast::Access::kRead);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/ptr_ref_validation_test.cc b/src/resolver/ptr_ref_validation_test.cc
deleted file mode 100644
index 8856edb..0000000
--- a/src/resolver/ptr_ref_validation_test.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/reference_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverPtrRefValidationTest : public resolver::TestHelper,
-                                      public testing::Test {};
-
-TEST_F(ResolverPtrRefValidationTest, AddressOfLiteral) {
-  // &1
-
-  auto* expr = AddressOf(Expr(Source{{12, 34}}, 1));
-
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
-}
-
-TEST_F(ResolverPtrRefValidationTest, AddressOfLet) {
-  // let l : i32 = 1;
-  // &l
-  auto* l = Const("l", ty.i32(), Expr(1));
-  auto* expr = AddressOf(Expr(Source{{12, 34}}, "l"));
-
-  WrapInFunction(l, expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
-}
-
-TEST_F(ResolverPtrRefValidationTest, AddressOfHandle) {
-  // @group(0) @binding(0) var t: texture_3d<f32>;
-  // &t
-  Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
-         GroupAndBinding(0u, 0u));
-  auto* expr = AddressOf(Expr(Source{{12, 34}}, "t"));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot take the address of expression in handle "
-            "storage class");
-}
-
-TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_MemberAccessor) {
-  // var v : vec4<i32>;
-  // &v.y
-  auto* v = Var("v", ty.vec4<i32>());
-  auto* expr = AddressOf(MemberAccessor(Source{{12, 34}}, "v", "y"));
-
-  WrapInFunction(v, expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot take the address of a vector component");
-}
-
-TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_IndexAccessor) {
-  // var v : vec4<i32>;
-  // &v[2]
-  auto* v = Var("v", ty.vec4<i32>());
-  auto* expr = AddressOf(IndexAccessor(Source{{12, 34}}, "v", 2));
-
-  WrapInFunction(v, expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot take the address of a vector component");
-}
-
-TEST_F(ResolverPtrRefValidationTest, IndirectOfAddressOfHandle) {
-  // @group(0) @binding(0) var t: texture_3d<f32>;
-  // *&t
-  Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
-         GroupAndBinding(0u, 0u));
-  auto* expr = Deref(AddressOf(Expr(Source{{12, 34}}, "t")));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot take the address of expression in handle "
-            "storage class");
-}
-
-TEST_F(ResolverPtrRefValidationTest, DerefOfLiteral) {
-  // *1
-
-  auto* expr = Deref(Expr(Source{{12, 34}}, 1));
-
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot dereference expression of type 'i32'");
-}
-
-TEST_F(ResolverPtrRefValidationTest, DerefOfVar) {
-  // var v : i32 = 1;
-  // *1
-  auto* v = Var("v", ty.i32());
-  auto* expr = Deref(Expr(Source{{12, 34}}, "v"));
-
-  WrapInFunction(v, expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot dereference expression of type 'i32'");
-}
-
-TEST_F(ResolverPtrRefValidationTest, InferredPtrAccessMismatch) {
-  // struct Inner {
-  //    arr: array<i32, 4>;
-  // }
-  // [[block]] struct S {
-  //    inner: Inner;
-  // }
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  // fn f() {
-  //   let p : pointer<storage, i32> = &s.inner.arr[2];
-  // }
-  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
-                        {create<ast::StructBlockAttribute>()});
-  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::Access::kReadWrite,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(0),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  auto* expr =
-      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
-  auto* ptr =
-      Const(Source{{12, 34}}, "p", ty.pointer<i32>(ast::StorageClass::kStorage),
-            AddressOf(expr));
-
-  WrapInFunction(ptr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot initialize let of type "
-            "'ptr<storage, i32, read>' with value of type "
-            "'ptr<storage, i32, read_write>'");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
deleted file mode 100644
index f2f516a..0000000
--- a/src/resolver/resolver.cc
+++ /dev/null
@@ -1,2917 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include <algorithm>
-#include <cmath>
-#include <iomanip>
-#include <limits>
-#include <utility>
-
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/depth_texture.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/sampled_texture.h"
-#include "src/ast/sampler.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/traverse_expressions.h"
-#include "src/ast/type_name.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/vector.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/if_statement.h"
-#include "src/sem/loop_statement.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/pointer_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/switch_statement.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/variable.h"
-#include "src/utils/defer.h"
-#include "src/utils/math.h"
-#include "src/utils/reverse.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/utils/transform.h"
-
-namespace tint {
-namespace resolver {
-
-Resolver::Resolver(ProgramBuilder* builder)
-    : builder_(builder),
-      diagnostics_(builder->Diagnostics()),
-      builtin_table_(BuiltinTable::Create(*builder)) {}
-
-Resolver::~Resolver() = default;
-
-bool Resolver::Resolve() {
-  if (builder_->Diagnostics().contains_errors()) {
-    return false;
-  }
-
-  if (!DependencyGraph::Build(builder_->AST(), builder_->Symbols(),
-                              builder_->Diagnostics(), dependencies_)) {
-    return false;
-  }
-
-  // Create the semantic module
-  builder_->Sem().SetModule(
-      builder_->create<sem::Module>(dependencies_.ordered_globals));
-
-  bool result = ResolveInternal();
-
-  if (!result && !diagnostics_.contains_errors()) {
-    TINT_ICE(Resolver, diagnostics_)
-        << "resolving failed, but no error was raised";
-    return false;
-  }
-
-  return result;
-}
-
-bool Resolver::ResolveInternal() {
-  Mark(&builder_->AST());
-
-  // Process all module-scope declarations in dependency order.
-  for (auto* decl : dependencies_.ordered_globals) {
-    Mark(decl);
-    if (!Switch(
-            decl,                           //
-            [&](const ast::TypeDecl* td) {  //
-              return TypeDecl(td) != nullptr;
-            },
-            [&](const ast::Function* func) {
-              return Function(func) != nullptr;
-            },
-            [&](const ast::Variable* var) {
-              return GlobalVariable(var) != nullptr;
-            },
-            [&](Default) {
-              TINT_UNREACHABLE(Resolver, diagnostics_)
-                  << "unhandled global declaration: " << decl->TypeInfo().name;
-              return false;
-            })) {
-      return false;
-    }
-  }
-
-  AllocateOverridableConstantIds();
-
-  SetShadows();
-
-  if (!ValidatePipelineStages()) {
-    return false;
-  }
-
-  bool result = true;
-  for (auto* node : builder_->ASTNodes().Objects()) {
-    if (marked_.count(node) == 0) {
-      TINT_ICE(Resolver, diagnostics_) << "AST node '" << node->TypeInfo().name
-                                       << "' was not reached by the resolver\n"
-                                       << "At: " << node->source << "\n"
-                                       << "Pointer: " << node;
-      result = false;
-    }
-  }
-
-  return result;
-}
-
-sem::Type* Resolver::Type(const ast::Type* ty) {
-  Mark(ty);
-  auto* s = Switch(
-      ty,
-      [&](const ast::Void*) -> sem::Type* {
-        return builder_->create<sem::Void>();
-      },
-      [&](const ast::Bool*) -> sem::Type* {
-        return builder_->create<sem::Bool>();
-      },
-      [&](const ast::I32*) -> sem::Type* {
-        return builder_->create<sem::I32>();
-      },
-      [&](const ast::U32*) -> sem::Type* {
-        return builder_->create<sem::U32>();
-      },
-      [&](const ast::F32*) -> sem::Type* {
-        return builder_->create<sem::F32>();
-      },
-      [&](const ast::Vector* t) -> sem::Type* {
-        if (!t->type) {
-          AddError("missing vector element type", t->source.End());
-          return nullptr;
-        }
-        if (auto* el = Type(t->type)) {
-          if (auto* vector = builder_->create<sem::Vector>(el, t->width)) {
-            if (ValidateVector(vector, t->source)) {
-              return vector;
-            }
-          }
-        }
-        return nullptr;
-      },
-      [&](const ast::Matrix* t) -> sem::Type* {
-        if (!t->type) {
-          AddError("missing matrix element type", t->source.End());
-          return nullptr;
-        }
-        if (auto* el = Type(t->type)) {
-          if (auto* column_type = builder_->create<sem::Vector>(el, t->rows)) {
-            if (auto* matrix =
-                    builder_->create<sem::Matrix>(column_type, t->columns)) {
-              if (ValidateMatrix(matrix, t->source)) {
-                return matrix;
-              }
-            }
-          }
-        }
-        return nullptr;
-      },
-      [&](const ast::Array* t) -> sem::Type* { return Array(t); },
-      [&](const ast::Atomic* t) -> sem::Type* {
-        if (auto* el = Type(t->type)) {
-          auto* a = builder_->create<sem::Atomic>(el);
-          if (!ValidateAtomic(t, a)) {
-            return nullptr;
-          }
-          return a;
-        }
-        return nullptr;
-      },
-      [&](const ast::Pointer* t) -> sem::Type* {
-        if (auto* el = Type(t->type)) {
-          auto access = t->access;
-          if (access == ast::kUndefined) {
-            access = DefaultAccessForStorageClass(t->storage_class);
-          }
-          return builder_->create<sem::Pointer>(el, t->storage_class, access);
-        }
-        return nullptr;
-      },
-      [&](const ast::Sampler* t) -> sem::Type* {
-        return builder_->create<sem::Sampler>(t->kind);
-      },
-      [&](const ast::SampledTexture* t) -> sem::Type* {
-        if (auto* el = Type(t->type)) {
-          return builder_->create<sem::SampledTexture>(t->dim, el);
-        }
-        return nullptr;
-      },
-      [&](const ast::MultisampledTexture* t) -> sem::Type* {
-        if (auto* el = Type(t->type)) {
-          return builder_->create<sem::MultisampledTexture>(t->dim, el);
-        }
-        return nullptr;
-      },
-      [&](const ast::DepthTexture* t) -> sem::Type* {
-        return builder_->create<sem::DepthTexture>(t->dim);
-      },
-      [&](const ast::DepthMultisampledTexture* t) -> sem::Type* {
-        return builder_->create<sem::DepthMultisampledTexture>(t->dim);
-      },
-      [&](const ast::StorageTexture* t) -> sem::Type* {
-        if (auto* el = Type(t->type)) {
-          if (!ValidateStorageTexture(t)) {
-            return nullptr;
-          }
-          return builder_->create<sem::StorageTexture>(t->dim, t->format,
-                                                       t->access, el);
-        }
-        return nullptr;
-      },
-      [&](const ast::ExternalTexture*) -> sem::Type* {
-        return builder_->create<sem::ExternalTexture>();
-      },
-      [&](Default) -> sem::Type* {
-        auto* resolved = ResolvedSymbol(ty);
-        return Switch(
-            resolved,  //
-            [&](sem::Type* type) { return type; },
-            [&](sem::Variable* var) {
-              auto name =
-                  builder_->Symbols().NameFor(var->Declaration()->symbol);
-              AddError("cannot use variable '" + name + "' as type",
-                       ty->source);
-              AddNote("'" + name + "' declared here",
-                      var->Declaration()->source);
-              return nullptr;
-            },
-            [&](sem::Function* func) {
-              auto name =
-                  builder_->Symbols().NameFor(func->Declaration()->symbol);
-              AddError("cannot use function '" + name + "' as type",
-                       ty->source);
-              AddNote("'" + name + "' declared here",
-                      func->Declaration()->source);
-              return nullptr;
-            },
-            [&](Default) {
-              TINT_UNREACHABLE(Resolver, diagnostics_)
-                  << "Unhandled resolved type '"
-                  << (resolved ? resolved->TypeInfo().name : "<null>")
-                  << "' resolved from ast::Type '" << ty->TypeInfo().name
-                  << "'";
-              return nullptr;
-            });
-      });
-
-  if (s) {
-    builder_->Sem().Add(ty, s);
-  }
-  return s;
-}
-
-sem::Variable* Resolver::Variable(const ast::Variable* var,
-                                  VariableKind kind,
-                                  uint32_t index /* = 0 */) {
-  const sem::Type* storage_ty = nullptr;
-
-  // If the variable has a declared type, resolve it.
-  if (auto* ty = var->type) {
-    storage_ty = Type(ty);
-    if (!storage_ty) {
-      return nullptr;
-    }
-  }
-
-  const sem::Expression* rhs = nullptr;
-
-  // Does the variable have a constructor?
-  if (var->constructor) {
-    rhs = Expression(var->constructor);
-    if (!rhs) {
-      return nullptr;
-    }
-
-    // If the variable has no declared type, infer it from the RHS
-    if (!storage_ty) {
-      if (!var->is_const && kind == VariableKind::kGlobal) {
-        AddError("global var declaration must specify a type", var->source);
-        return nullptr;
-      }
-
-      storage_ty = rhs->Type()->UnwrapRef();  // Implicit load of RHS
-    }
-  } else if (var->is_const && !var->is_overridable &&
-             kind != VariableKind::kParameter) {
-    AddError("let declaration must have an initializer", var->source);
-    return nullptr;
-  } else if (!var->type) {
-    AddError(
-        (kind == VariableKind::kGlobal)
-            ? "module scope var declaration requires a type and initializer"
-            : "function scope var declaration requires a type or initializer",
-        var->source);
-    return nullptr;
-  }
-
-  if (!storage_ty) {
-    TINT_ICE(Resolver, diagnostics_)
-        << "failed to determine storage type for variable '" +
-               builder_->Symbols().NameFor(var->symbol) + "'\n"
-        << "Source: " << var->source;
-    return nullptr;
-  }
-
-  auto storage_class = var->declared_storage_class;
-  if (storage_class == ast::StorageClass::kNone && !var->is_const) {
-    // No declared storage class. Infer from usage / type.
-    if (kind == VariableKind::kLocal) {
-      storage_class = ast::StorageClass::kFunction;
-    } else if (storage_ty->UnwrapRef()->is_handle()) {
-      // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
-      // If the store type is a texture type or a sampler type, then the
-      // variable declaration must not have a storage class attribute. The
-      // storage class will always be handle.
-      storage_class = ast::StorageClass::kUniformConstant;
-    }
-  }
-
-  if (kind == VariableKind::kLocal && !var->is_const &&
-      storage_class != ast::StorageClass::kFunction &&
-      IsValidationEnabled(var->attributes,
-                          ast::DisabledValidation::kIgnoreStorageClass)) {
-    AddError("function variable has a non-function storage class", var->source);
-    return nullptr;
-  }
-
-  auto access = var->declared_access;
-  if (access == ast::Access::kUndefined) {
-    access = DefaultAccessForStorageClass(storage_class);
-  }
-
-  auto* var_ty = storage_ty;
-  if (!var->is_const) {
-    // Variable declaration. Unlike `let`, `var` has storage.
-    // Variables are always of a reference type to the declared storage type.
-    var_ty =
-        builder_->create<sem::Reference>(storage_ty, storage_class, access);
-  }
-
-  if (rhs && !ValidateVariableConstructorOrCast(var, storage_class, storage_ty,
-                                                rhs->Type())) {
-    return nullptr;
-  }
-
-  if (!ApplyStorageClassUsageToType(
-          storage_class, const_cast<sem::Type*>(var_ty), var->source)) {
-    AddNote(
-        std::string("while instantiating ") +
-            ((kind == VariableKind::kParameter) ? "parameter " : "variable ") +
-            builder_->Symbols().NameFor(var->symbol),
-        var->source);
-    return nullptr;
-  }
-
-  if (kind == VariableKind::kParameter) {
-    if (auto* ptr = var_ty->As<sem::Pointer>()) {
-      // For MSL, we push module-scope variables into the entry point as pointer
-      // parameters, so we also need to handle their store type.
-      if (!ApplyStorageClassUsageToType(
-              ptr->StorageClass(), const_cast<sem::Type*>(ptr->StoreType()),
-              var->source)) {
-        AddNote("while instantiating parameter " +
-                    builder_->Symbols().NameFor(var->symbol),
-                var->source);
-        return nullptr;
-      }
-    }
-  }
-
-  switch (kind) {
-    case VariableKind::kGlobal: {
-      sem::BindingPoint binding_point;
-      if (auto bp = var->BindingPoint()) {
-        binding_point = {bp.group->value, bp.binding->value};
-      }
-
-      bool has_const_val = rhs && var->is_const && !var->is_overridable;
-      auto* global = builder_->create<sem::GlobalVariable>(
-          var, var_ty, storage_class, access,
-          has_const_val ? rhs->ConstantValue() : sem::Constant{},
-          binding_point);
-
-      if (var->is_overridable) {
-        global->SetIsOverridable();
-        if (auto* id = ast::GetAttribute<ast::IdAttribute>(var->attributes)) {
-          global->SetConstantId(static_cast<uint16_t>(id->value));
-        }
-      }
-
-      global->SetConstructor(rhs);
-
-      builder_->Sem().Add(var, global);
-      return global;
-    }
-    case VariableKind::kLocal: {
-      auto* local = builder_->create<sem::LocalVariable>(
-          var, var_ty, storage_class, access, current_statement_,
-          (rhs && var->is_const) ? rhs->ConstantValue() : sem::Constant{});
-      builder_->Sem().Add(var, local);
-      local->SetConstructor(rhs);
-      return local;
-    }
-    case VariableKind::kParameter: {
-      auto* param = builder_->create<sem::Parameter>(var, index, var_ty,
-                                                     storage_class, access);
-      builder_->Sem().Add(var, param);
-      return param;
-    }
-  }
-
-  TINT_UNREACHABLE(Resolver, diagnostics_)
-      << "unhandled VariableKind " << static_cast<int>(kind);
-  return nullptr;
-}
-
-ast::Access Resolver::DefaultAccessForStorageClass(
-    ast::StorageClass storage_class) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
-  switch (storage_class) {
-    case ast::StorageClass::kStorage:
-    case ast::StorageClass::kUniform:
-    case ast::StorageClass::kUniformConstant:
-      return ast::Access::kRead;
-    default:
-      break;
-  }
-  return ast::Access::kReadWrite;
-}
-
-void Resolver::AllocateOverridableConstantIds() {
-  // The next pipeline constant ID to try to allocate.
-  uint16_t next_constant_id = 0;
-
-  // Allocate constant IDs in global declaration order, so that they are
-  // deterministic.
-  // TODO(crbug.com/tint/1192): If a transform changes the order or removes an
-  // unused constant, the allocation may change on the next Resolver pass.
-  for (auto* decl : builder_->AST().GlobalDeclarations()) {
-    auto* var = decl->As<ast::Variable>();
-    if (!var || !var->is_overridable) {
-      continue;
-    }
-
-    uint16_t constant_id;
-    if (auto* id_attr = ast::GetAttribute<ast::IdAttribute>(var->attributes)) {
-      constant_id = static_cast<uint16_t>(id_attr->value);
-    } else {
-      // No ID was specified, so allocate the next available ID.
-      constant_id = next_constant_id;
-      while (constant_ids_.count(constant_id)) {
-        if (constant_id == UINT16_MAX) {
-          TINT_ICE(Resolver, builder_->Diagnostics())
-              << "no more pipeline constant IDs available";
-          return;
-        }
-        constant_id++;
-      }
-      next_constant_id = constant_id + 1;
-    }
-
-    auto* sem = Sem<sem::GlobalVariable>(var);
-    const_cast<sem::GlobalVariable*>(sem)->SetConstantId(constant_id);
-  }
-}
-
-void Resolver::SetShadows() {
-  for (auto it : dependencies_.shadows) {
-    Switch(
-        Sem(it.first),  //
-        [&](sem::LocalVariable* local) { local->SetShadows(Sem(it.second)); },
-        [&](sem::Parameter* param) { param->SetShadows(Sem(it.second)); });
-  }
-}  // namespace resolver
-
-sem::GlobalVariable* Resolver::GlobalVariable(const ast::Variable* var) {
-  auto* sem = Variable(var, VariableKind::kGlobal);
-  if (!sem) {
-    return nullptr;
-  }
-
-  auto storage_class = sem->StorageClass();
-  if (!var->is_const && storage_class == ast::StorageClass::kNone) {
-    AddError("global variables must have a storage class", var->source);
-    return nullptr;
-  }
-  if (var->is_const && storage_class != ast::StorageClass::kNone) {
-    AddError("global constants shouldn't have a storage class", var->source);
-    return nullptr;
-  }
-
-  for (auto* attr : var->attributes) {
-    Mark(attr);
-
-    if (auto* id_attr = attr->As<ast::IdAttribute>()) {
-      // Track the constant IDs that are specified in the shader.
-      constant_ids_.emplace(id_attr->value, sem);
-    }
-  }
-
-  if (!ValidateNoDuplicateAttributes(var->attributes)) {
-    return nullptr;
-  }
-
-  if (!ValidateGlobalVariable(sem)) {
-    return nullptr;
-  }
-
-  // TODO(bclayton): Call this at the end of resolve on all uniform and storage
-  // referenced structs
-  if (!ValidateStorageClassLayout(sem)) {
-    return nullptr;
-  }
-
-  return sem->As<sem::GlobalVariable>();
-}
-
-sem::Function* Resolver::Function(const ast::Function* decl) {
-  uint32_t parameter_index = 0;
-  std::unordered_map<Symbol, Source> parameter_names;
-  std::vector<sem::Parameter*> parameters;
-
-  // Resolve all the parameters
-  for (auto* param : decl->params) {
-    Mark(param);
-
-    {  // Check the parameter name is unique for the function
-      auto emplaced = parameter_names.emplace(param->symbol, param->source);
-      if (!emplaced.second) {
-        auto name = builder_->Symbols().NameFor(param->symbol);
-        AddError("redefinition of parameter '" + name + "'", param->source);
-        AddNote("previous definition is here", emplaced.first->second);
-        return nullptr;
-      }
-    }
-
-    auto* var = As<sem::Parameter>(
-        Variable(param, VariableKind::kParameter, parameter_index++));
-    if (!var) {
-      return nullptr;
-    }
-
-    for (auto* attr : param->attributes) {
-      Mark(attr);
-    }
-    if (!ValidateNoDuplicateAttributes(param->attributes)) {
-      return nullptr;
-    }
-
-    parameters.emplace_back(var);
-
-    auto* var_ty = const_cast<sem::Type*>(var->Type());
-    if (auto* str = var_ty->As<sem::Struct>()) {
-      switch (decl->PipelineStage()) {
-        case ast::PipelineStage::kVertex:
-          str->AddUsage(sem::PipelineStageUsage::kVertexInput);
-          break;
-        case ast::PipelineStage::kFragment:
-          str->AddUsage(sem::PipelineStageUsage::kFragmentInput);
-          break;
-        case ast::PipelineStage::kCompute:
-          str->AddUsage(sem::PipelineStageUsage::kComputeInput);
-          break;
-        case ast::PipelineStage::kNone:
-          break;
-      }
-    }
-  }
-
-  // Resolve the return type
-  sem::Type* return_type = nullptr;
-  if (auto* ty = decl->return_type) {
-    return_type = Type(ty);
-    if (!return_type) {
-      return nullptr;
-    }
-  } else {
-    return_type = builder_->create<sem::Void>();
-  }
-
-  if (auto* str = return_type->As<sem::Struct>()) {
-    if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, str,
-                                      decl->source)) {
-      AddNote("while instantiating return type for " +
-                  builder_->Symbols().NameFor(decl->symbol),
-              decl->source);
-      return nullptr;
-    }
-
-    switch (decl->PipelineStage()) {
-      case ast::PipelineStage::kVertex:
-        str->AddUsage(sem::PipelineStageUsage::kVertexOutput);
-        break;
-      case ast::PipelineStage::kFragment:
-        str->AddUsage(sem::PipelineStageUsage::kFragmentOutput);
-        break;
-      case ast::PipelineStage::kCompute:
-        str->AddUsage(sem::PipelineStageUsage::kComputeOutput);
-        break;
-      case ast::PipelineStage::kNone:
-        break;
-    }
-  }
-
-  auto* func = builder_->create<sem::Function>(decl, return_type, parameters);
-  builder_->Sem().Add(decl, func);
-
-  TINT_SCOPED_ASSIGNMENT(current_function_, func);
-
-  if (!WorkgroupSize(decl)) {
-    return nullptr;
-  }
-
-  if (decl->IsEntryPoint()) {
-    entry_points_.emplace_back(func);
-  }
-
-  if (decl->body) {
-    Mark(decl->body);
-    if (current_compound_statement_) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "Resolver::Function() called with a current compound statement";
-      return nullptr;
-    }
-    auto* body = StatementScope(
-        decl->body, builder_->create<sem::FunctionBlockStatement>(func),
-        [&] { return Statements(decl->body->statements); });
-    if (!body) {
-      return nullptr;
-    }
-    func->Behaviors() = body->Behaviors();
-    if (func->Behaviors().Contains(sem::Behavior::kReturn)) {
-      // https://www.w3.org/TR/WGSL/#behaviors-rules
-      // We assign a behavior to each function: it is its body’s behavior
-      // (treating the body as a regular statement), with any "Return" replaced
-      // by "Next".
-      func->Behaviors().Remove(sem::Behavior::kReturn);
-      func->Behaviors().Add(sem::Behavior::kNext);
-    }
-  }
-
-  for (auto* attr : decl->attributes) {
-    Mark(attr);
-  }
-  if (!ValidateNoDuplicateAttributes(decl->attributes)) {
-    return nullptr;
-  }
-
-  for (auto* attr : decl->return_type_attributes) {
-    Mark(attr);
-  }
-  if (!ValidateNoDuplicateAttributes(decl->return_type_attributes)) {
-    return nullptr;
-  }
-
-  if (!ValidateFunction(func)) {
-    return nullptr;
-  }
-
-  // If this is an entry point, mark all transitively called functions as being
-  // used by this entry point.
-  if (decl->IsEntryPoint()) {
-    for (auto* f : func->TransitivelyCalledFunctions()) {
-      const_cast<sem::Function*>(f)->AddAncestorEntryPoint(func);
-    }
-  }
-
-  return func;
-}
-
-bool Resolver::WorkgroupSize(const ast::Function* func) {
-  // Set work-group size defaults.
-  sem::WorkgroupSize ws;
-  for (int i = 0; i < 3; i++) {
-    ws[i].value = 1;
-    ws[i].overridable_const = nullptr;
-  }
-
-  auto* attr = ast::GetAttribute<ast::WorkgroupAttribute>(func->attributes);
-  if (!attr) {
-    return true;
-  }
-
-  auto values = attr->Values();
-  auto any_i32 = false;
-  auto any_u32 = false;
-  for (int i = 0; i < 3; i++) {
-    // Each argument to this attribute can either be a literal, an
-    // identifier for a module-scope constants, or nullptr if not specified.
-
-    auto* expr = values[i];
-    if (!expr) {
-      // Not specified, just use the default.
-      continue;
-    }
-
-    auto* expr_sem = Expression(expr);
-    if (!expr_sem) {
-      return false;
-    }
-
-    constexpr const char* kErrBadType =
-        "workgroup_size argument must be either literal or module-scope "
-        "constant of type i32 or u32";
-    constexpr const char* kErrInconsistentType =
-        "workgroup_size arguments must be of the same type, either i32 "
-        "or u32";
-
-    auto* ty = TypeOf(expr);
-    bool is_i32 = ty->UnwrapRef()->Is<sem::I32>();
-    bool is_u32 = ty->UnwrapRef()->Is<sem::U32>();
-    if (!is_i32 && !is_u32) {
-      AddError(kErrBadType, expr->source);
-      return false;
-    }
-
-    any_i32 = any_i32 || is_i32;
-    any_u32 = any_u32 || is_u32;
-    if (any_i32 && any_u32) {
-      AddError(kErrInconsistentType, expr->source);
-      return false;
-    }
-
-    sem::Constant value;
-
-    if (auto* user = Sem(expr)->As<sem::VariableUser>()) {
-      // We have an variable of a module-scope constant.
-      auto* decl = user->Variable()->Declaration();
-      if (!decl->is_const) {
-        AddError(kErrBadType, expr->source);
-        return false;
-      }
-      // Capture the constant if it is pipeline-overridable.
-      if (decl->is_overridable) {
-        ws[i].overridable_const = decl;
-      }
-
-      if (decl->constructor) {
-        value = Sem(decl->constructor)->ConstantValue();
-      } else {
-        // No constructor means this value must be overriden by the user.
-        ws[i].value = 0;
-        continue;
-      }
-    } else if (expr->Is<ast::LiteralExpression>()) {
-      value = Sem(expr)->ConstantValue();
-    } else {
-      AddError(
-          "workgroup_size argument must be either a literal or a "
-          "module-scope constant",
-          values[i]->source);
-      return false;
-    }
-
-    if (!value) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "could not resolve constant workgroup_size constant value";
-      continue;
-    }
-    // Validate and set the default value for this dimension.
-    if (is_i32 ? value.Elements()[0].i32 < 1 : value.Elements()[0].u32 < 1) {
-      AddError("workgroup_size argument must be at least 1", values[i]->source);
-      return false;
-    }
-
-    ws[i].value = is_i32 ? static_cast<uint32_t>(value.Elements()[0].i32)
-                         : value.Elements()[0].u32;
-  }
-
-  current_function_->SetWorkgroupSize(std::move(ws));
-  return true;
-}
-
-bool Resolver::Statements(const ast::StatementList& stmts) {
-  sem::Behaviors behaviors{sem::Behavior::kNext};
-
-  bool reachable = true;
-  for (auto* stmt : stmts) {
-    Mark(stmt);
-    auto* sem = Statement(stmt);
-    if (!sem) {
-      return false;
-    }
-    // s1 s2:(B1∖{Next}) ∪ B2
-    sem->SetIsReachable(reachable);
-    if (reachable) {
-      behaviors = (behaviors - sem::Behavior::kNext) + sem->Behaviors();
-    }
-    reachable = reachable && sem->Behaviors().Contains(sem::Behavior::kNext);
-  }
-
-  current_statement_->Behaviors() = behaviors;
-
-  if (!ValidateStatements(stmts)) {
-    return false;
-  }
-
-  return true;
-}
-
-sem::Statement* Resolver::Statement(const ast::Statement* stmt) {
-  return Switch(
-      stmt,
-      // Compound statements. These create their own sem::CompoundStatement
-      // bindings.
-      [&](const ast::BlockStatement* b) -> sem::Statement* {
-        return BlockStatement(b);
-      },
-      [&](const ast::ForLoopStatement* l) -> sem::Statement* {
-        return ForLoopStatement(l);
-      },
-      [&](const ast::LoopStatement* l) -> sem::Statement* {
-        return LoopStatement(l);
-      },
-      [&](const ast::IfStatement* i) -> sem::Statement* {
-        return IfStatement(i);
-      },
-      [&](const ast::SwitchStatement* s) -> sem::Statement* {
-        return SwitchStatement(s);
-      },
-
-      // Non-Compound statements
-      [&](const ast::AssignmentStatement* a) -> sem::Statement* {
-        return AssignmentStatement(a);
-      },
-      [&](const ast::BreakStatement* b) -> sem::Statement* {
-        return BreakStatement(b);
-      },
-      [&](const ast::CallStatement* c) -> sem::Statement* {
-        return CallStatement(c);
-      },
-      [&](const ast::ContinueStatement* c) -> sem::Statement* {
-        return ContinueStatement(c);
-      },
-      [&](const ast::DiscardStatement* d) -> sem::Statement* {
-        return DiscardStatement(d);
-      },
-      [&](const ast::FallthroughStatement* f) -> sem::Statement* {
-        return FallthroughStatement(f);
-      },
-      [&](const ast::ReturnStatement* r) -> sem::Statement* {
-        return ReturnStatement(r);
-      },
-      [&](const ast::VariableDeclStatement* v) -> sem::Statement* {
-        return VariableDeclStatement(v);
-      },
-
-      // Error cases
-      [&](const ast::CaseStatement*) -> sem::Statement* {
-        AddError("case statement can only be used inside a switch statement",
-                 stmt->source);
-        return nullptr;
-      },
-      [&](const ast::ElseStatement*) -> sem::Statement* {
-        TINT_ICE(Resolver, diagnostics_)
-            << "Resolver::Statement() encountered an Else statement. Else "
-               "statements are embedded in If statements, so should never be "
-               "encountered as top-level statements";
-        return nullptr;
-      },
-      [&](Default) -> sem::Statement* {
-        AddError(
-            "unknown statement type: " + std::string(stmt->TypeInfo().name),
-            stmt->source);
-        return nullptr;
-      });
-}
-
-sem::CaseStatement* Resolver::CaseStatement(const ast::CaseStatement* stmt) {
-  auto* sem = builder_->create<sem::CaseStatement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    for (auto* sel : stmt->selectors) {
-      Mark(sel);
-    }
-    Mark(stmt->body);
-    auto* body = BlockStatement(stmt->body);
-    if (!body) {
-      return false;
-    }
-    sem->SetBlock(body);
-    sem->Behaviors() = body->Behaviors();
-    return true;
-  });
-}
-
-sem::IfStatement* Resolver::IfStatement(const ast::IfStatement* stmt) {
-  auto* sem = builder_->create<sem::IfStatement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    auto* cond = Expression(stmt->condition);
-    if (!cond) {
-      return false;
-    }
-    sem->SetCondition(cond);
-    sem->Behaviors() = cond->Behaviors();
-    sem->Behaviors().Remove(sem::Behavior::kNext);
-
-    Mark(stmt->body);
-    auto* body = builder_->create<sem::BlockStatement>(
-        stmt->body, current_compound_statement_, current_function_);
-    if (!StatementScope(stmt->body, body,
-                        [&] { return Statements(stmt->body->statements); })) {
-      return false;
-    }
-    sem->Behaviors().Add(body->Behaviors());
-
-    for (auto* else_stmt : stmt->else_statements) {
-      Mark(else_stmt);
-      auto* else_sem = ElseStatement(else_stmt);
-      if (!else_sem) {
-        return false;
-      }
-      sem->Behaviors().Add(else_sem->Behaviors());
-    }
-
-    if (stmt->else_statements.empty() ||
-        stmt->else_statements.back()->condition != nullptr) {
-      // https://www.w3.org/TR/WGSL/#behaviors-rules
-      // if statements without an else branch are treated as if they had an
-      // empty else branch (which adds Next to their behavior)
-      sem->Behaviors().Add(sem::Behavior::kNext);
-    }
-
-    return ValidateIfStatement(sem);
-  });
-}
-
-sem::ElseStatement* Resolver::ElseStatement(const ast::ElseStatement* stmt) {
-  auto* sem = builder_->create<sem::ElseStatement>(
-      stmt, current_compound_statement_->As<sem::IfStatement>(),
-      current_function_);
-  return StatementScope(stmt, sem, [&] {
-    if (auto* cond_expr = stmt->condition) {
-      auto* cond = Expression(cond_expr);
-      if (!cond) {
-        return false;
-      }
-      sem->SetCondition(cond);
-      // https://www.w3.org/TR/WGSL/#behaviors-rules
-      // if statements with else if branches are treated as if they were nested
-      // simple if/else statements
-      sem->Behaviors() = cond->Behaviors();
-    }
-    sem->Behaviors().Remove(sem::Behavior::kNext);
-
-    Mark(stmt->body);
-    auto* body = builder_->create<sem::BlockStatement>(
-        stmt->body, current_compound_statement_, current_function_);
-    if (!StatementScope(stmt->body, body,
-                        [&] { return Statements(stmt->body->statements); })) {
-      return false;
-    }
-    sem->Behaviors().Add(body->Behaviors());
-
-    return ValidateElseStatement(sem);
-  });
-}
-
-sem::BlockStatement* Resolver::BlockStatement(const ast::BlockStatement* stmt) {
-  auto* sem = builder_->create<sem::BlockStatement>(
-      stmt->As<ast::BlockStatement>(), current_compound_statement_,
-      current_function_);
-  return StatementScope(stmt, sem,
-                        [&] { return Statements(stmt->statements); });
-}
-
-sem::LoopStatement* Resolver::LoopStatement(const ast::LoopStatement* stmt) {
-  auto* sem = builder_->create<sem::LoopStatement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    Mark(stmt->body);
-
-    auto* body = builder_->create<sem::LoopBlockStatement>(
-        stmt->body, current_compound_statement_, current_function_);
-    return StatementScope(stmt->body, body, [&] {
-      if (!Statements(stmt->body->statements)) {
-        return false;
-      }
-      auto& behaviors = sem->Behaviors();
-      behaviors = body->Behaviors();
-
-      if (stmt->continuing) {
-        Mark(stmt->continuing);
-        if (!stmt->continuing->Empty()) {
-          auto* continuing = StatementScope(
-              stmt->continuing,
-              builder_->create<sem::LoopContinuingBlockStatement>(
-                  stmt->continuing, current_compound_statement_,
-                  current_function_),
-              [&] { return Statements(stmt->continuing->statements); });
-          if (!continuing) {
-            return false;
-          }
-          behaviors.Add(continuing->Behaviors());
-        }
-      }
-
-      if (behaviors.Contains(sem::Behavior::kBreak)) {  // Does the loop exit?
-        behaviors.Add(sem::Behavior::kNext);
-      } else {
-        behaviors.Remove(sem::Behavior::kNext);
-      }
-      behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kContinue);
-
-      return ValidateLoopStatement(sem);
-    });
-  });
-}
-
-sem::ForLoopStatement* Resolver::ForLoopStatement(
-    const ast::ForLoopStatement* stmt) {
-  auto* sem = builder_->create<sem::ForLoopStatement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    auto& behaviors = sem->Behaviors();
-    if (auto* initializer = stmt->initializer) {
-      Mark(initializer);
-      auto* init = Statement(initializer);
-      if (!init) {
-        return false;
-      }
-      behaviors.Add(init->Behaviors());
-    }
-
-    if (auto* cond_expr = stmt->condition) {
-      auto* cond = Expression(cond_expr);
-      if (!cond) {
-        return false;
-      }
-      sem->SetCondition(cond);
-      behaviors.Add(cond->Behaviors());
-    }
-
-    if (auto* continuing = stmt->continuing) {
-      Mark(continuing);
-      auto* cont = Statement(continuing);
-      if (!cont) {
-        return false;
-      }
-      behaviors.Add(cont->Behaviors());
-    }
-
-    Mark(stmt->body);
-
-    auto* body = builder_->create<sem::LoopBlockStatement>(
-        stmt->body, current_compound_statement_, current_function_);
-    if (!StatementScope(stmt->body, body,
-                        [&] { return Statements(stmt->body->statements); })) {
-      return false;
-    }
-
-    behaviors.Add(body->Behaviors());
-    if (stmt->condition ||
-        behaviors.Contains(sem::Behavior::kBreak)) {  // Does the loop exit?
-      behaviors.Add(sem::Behavior::kNext);
-    } else {
-      behaviors.Remove(sem::Behavior::kNext);
-    }
-    behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kContinue);
-
-    return ValidateForLoopStatement(sem);
-  });
-}
-
-sem::Expression* Resolver::Expression(const ast::Expression* root) {
-  std::vector<const ast::Expression*> sorted;
-  bool mark_failed = false;
-  if (!ast::TraverseExpressions<ast::TraverseOrder::RightToLeft>(
-          root, diagnostics_, [&](const ast::Expression* expr) {
-            if (!Mark(expr)) {
-              mark_failed = true;
-              return ast::TraverseAction::Stop;
-            }
-            sorted.emplace_back(expr);
-            return ast::TraverseAction::Descend;
-          })) {
-    return nullptr;
-  }
-
-  if (mark_failed) {
-    return nullptr;
-  }
-
-  for (auto* expr : utils::Reverse(sorted)) {
-    auto* sem_expr = Switch(
-        expr,
-        [&](const ast::IndexAccessorExpression* array) -> sem::Expression* {
-          return IndexAccessor(array);
-        },
-        [&](const ast::BinaryExpression* bin_op) -> sem::Expression* {
-          return Binary(bin_op);
-        },
-        [&](const ast::BitcastExpression* bitcast) -> sem::Expression* {
-          return Bitcast(bitcast);
-        },
-        [&](const ast::CallExpression* call) -> sem::Expression* {
-          return Call(call);
-        },
-        [&](const ast::IdentifierExpression* ident) -> sem::Expression* {
-          return Identifier(ident);
-        },
-        [&](const ast::LiteralExpression* literal) -> sem::Expression* {
-          return Literal(literal);
-        },
-        [&](const ast::MemberAccessorExpression* member) -> sem::Expression* {
-          return MemberAccessor(member);
-        },
-        [&](const ast::UnaryOpExpression* unary) -> sem::Expression* {
-          return UnaryOp(unary);
-        },
-        [&](const ast::PhonyExpression*) -> sem::Expression* {
-          return builder_->create<sem::Expression>(
-              expr, builder_->create<sem::Void>(), current_statement_,
-              sem::Constant{}, /* has_side_effects */ false);
-        },
-        [&](Default) {
-          TINT_ICE(Resolver, diagnostics_)
-              << "unhandled expression type: " << expr->TypeInfo().name;
-          return nullptr;
-        });
-    if (!sem_expr) {
-      return nullptr;
-    }
-
-    builder_->Sem().Add(expr, sem_expr);
-    if (expr == root) {
-      return sem_expr;
-    }
-  }
-
-  TINT_ICE(Resolver, diagnostics_) << "Expression() did not find root node";
-  return nullptr;
-}
-
-sem::Expression* Resolver::IndexAccessor(
-    const ast::IndexAccessorExpression* expr) {
-  auto* idx = Sem(expr->index);
-  auto* obj = Sem(expr->object);
-  auto* obj_raw_ty = obj->Type();
-  auto* obj_ty = obj_raw_ty->UnwrapRef();
-  auto* ty = Switch(
-      obj_ty,  //
-      [&](const sem::Array* arr) -> const sem::Type* {
-        return arr->ElemType();
-      },
-      [&](const sem::Vector* vec) -> const sem::Type* {  //
-        return vec->type();
-      },
-      [&](const sem::Matrix* mat) -> const sem::Type* {
-        return builder_->create<sem::Vector>(mat->type(), mat->rows());
-      },
-      [&](Default) -> const sem::Type* {
-        AddError("cannot index type '" + TypeNameOf(obj_ty) + "'",
-                 expr->source);
-        return nullptr;
-      });
-  if (ty == nullptr) {
-    return nullptr;
-  }
-
-  auto* idx_ty = idx->Type()->UnwrapRef();
-  if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
-    AddError("index must be of type 'i32' or 'u32', found: '" +
-                 TypeNameOf(idx_ty) + "'",
-             idx->Declaration()->source);
-    return nullptr;
-  }
-
-  // If we're extracting from a reference, we return a reference.
-  if (auto* ref = obj_raw_ty->As<sem::Reference>()) {
-    ty = builder_->create<sem::Reference>(ty, ref->StorageClass(),
-                                          ref->Access());
-  }
-
-  auto val = EvaluateConstantValue(expr, ty);
-  bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
-  auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
-                                                val, has_side_effects);
-  sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
-  return sem;
-}
-
-sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
-  auto* inner = Sem(expr->expr);
-  auto* ty = Type(expr->type);
-  if (!ty) {
-    return nullptr;
-  }
-
-  auto val = EvaluateConstantValue(expr, ty);
-  auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
-                                                val, inner->HasSideEffects());
-
-  sem->Behaviors() = inner->Behaviors();
-
-  if (!ValidateBitcast(expr, ty)) {
-    return nullptr;
-  }
-
-  return sem;
-}
-
-sem::Call* Resolver::Call(const ast::CallExpression* expr) {
-  std::vector<const sem::Expression*> args(expr->args.size());
-  std::vector<const sem::Type*> arg_tys(args.size());
-  sem::Behaviors arg_behaviors;
-
-  // The element type of all the arguments. Nullptr if argument types are
-  // different.
-  const sem::Type* arg_el_ty = nullptr;
-
-  for (size_t i = 0; i < expr->args.size(); i++) {
-    auto* arg = Sem(expr->args[i]);
-    if (!arg) {
-      return nullptr;
-    }
-    args[i] = arg;
-    arg_tys[i] = args[i]->Type();
-    arg_behaviors.Add(arg->Behaviors());
-
-    // Determine the common argument element type
-    auto* el_ty = arg_tys[i]->UnwrapRef();
-    if (auto* vec = el_ty->As<sem::Vector>()) {
-      el_ty = vec->type();
-    } else if (auto* mat = el_ty->As<sem::Matrix>()) {
-      el_ty = mat->type();
-    }
-    if (i == 0) {
-      arg_el_ty = el_ty;
-    } else if (arg_el_ty != el_ty) {
-      arg_el_ty = nullptr;
-    }
-  }
-
-  arg_behaviors.Remove(sem::Behavior::kNext);
-
-  auto type_ctor_or_conv = [&](const sem::Type* ty) -> sem::Call* {
-    // The call has resolved to a type constructor or cast.
-    if (args.size() == 1) {
-      auto* target = ty;
-      auto* source = args[0]->Type()->UnwrapRef();
-      if ((source != target) &&  //
-          ((source->is_scalar() && target->is_scalar()) ||
-           (source->Is<sem::Vector>() && target->Is<sem::Vector>()) ||
-           (source->Is<sem::Matrix>() && target->Is<sem::Matrix>()))) {
-        // Note: Matrix types currently cannot be converted (the element type
-        // must only be f32). We implement this for the day we support other
-        // matrix element types.
-        return TypeConversion(expr, ty, args[0], arg_tys[0]);
-      }
-    }
-    return TypeConstructor(expr, ty, std::move(args), std::move(arg_tys));
-  };
-
-  // Resolve the target of the CallExpression to determine whether this is a
-  // function call, cast or type constructor expression.
-  if (expr->target.type) {
-    const sem::Type* ty = nullptr;
-
-    auto err_cannot_infer_el_ty = [&](std::string name) {
-      AddError(
-          "cannot infer " + name +
-              " element type, as constructor arguments have different types",
-          expr->source);
-      for (size_t i = 0; i < args.size(); i++) {
-        auto* arg = args[i];
-        AddNote("argument " + std::to_string(i) + " has type " +
-                    arg->Type()->FriendlyName(builder_->Symbols()),
-                arg->Declaration()->source);
-      }
-    };
-
-    if (!expr->args.empty()) {
-      // vecN() without explicit element type?
-      // Try to infer element type from args
-      if (auto* vec = expr->target.type->As<ast::Vector>()) {
-        if (!vec->type) {
-          if (!arg_el_ty) {
-            err_cannot_infer_el_ty("vector");
-            return nullptr;
-          }
-
-          Mark(vec);
-          auto* v = builder_->create<sem::Vector>(
-              arg_el_ty, static_cast<uint32_t>(vec->width));
-          if (!ValidateVector(v, vec->source)) {
-            return nullptr;
-          }
-          builder_->Sem().Add(vec, v);
-          ty = v;
-        }
-      }
-
-      // matNxM() without explicit element type?
-      // Try to infer element type from args
-      if (auto* mat = expr->target.type->As<ast::Matrix>()) {
-        if (!mat->type) {
-          if (!arg_el_ty) {
-            err_cannot_infer_el_ty("matrix");
-            return nullptr;
-          }
-
-          Mark(mat);
-          auto* column_type =
-              builder_->create<sem::Vector>(arg_el_ty, mat->rows);
-          auto* m = builder_->create<sem::Matrix>(column_type, mat->columns);
-          if (!ValidateMatrix(m, mat->source)) {
-            return nullptr;
-          }
-          builder_->Sem().Add(mat, m);
-          ty = m;
-        }
-      }
-    }
-
-    if (ty == nullptr) {
-      ty = Type(expr->target.type);
-      if (!ty) {
-        return nullptr;
-      }
-    }
-
-    return type_ctor_or_conv(ty);
-  }
-
-  auto* ident = expr->target.name;
-  Mark(ident);
-
-  auto* resolved = ResolvedSymbol(ident);
-  return Switch(
-      resolved,  //
-      [&](sem::Type* type) { return type_ctor_or_conv(type); },
-      [&](sem::Function* func) {
-        return FunctionCall(expr, func, std::move(args), arg_behaviors);
-      },
-      [&](sem::Variable* var) {
-        auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
-        AddError("cannot call variable '" + name + "'", ident->source);
-        AddNote("'" + name + "' declared here", var->Declaration()->source);
-        return nullptr;
-      },
-      [&](Default) -> sem::Call* {
-        auto name = builder_->Symbols().NameFor(ident->symbol);
-        auto builtin_type = sem::ParseBuiltinType(name);
-        if (builtin_type != sem::BuiltinType::kNone) {
-          return BuiltinCall(expr, builtin_type, std::move(args),
-                             std::move(arg_tys));
-        }
-
-        TINT_ICE(Resolver, diagnostics_)
-            << expr->source << " unresolved CallExpression target:\n"
-            << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>")
-            << "\n"
-            << "name: " << builder_->Symbols().NameFor(ident->symbol);
-        return nullptr;
-      });
-}
-
-sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
-                                 sem::BuiltinType builtin_type,
-                                 const std::vector<const sem::Expression*> args,
-                                 const std::vector<const sem::Type*> arg_tys) {
-  auto* builtin =
-      builtin_table_->Lookup(builtin_type, std::move(arg_tys), expr->source);
-  if (!builtin) {
-    return nullptr;
-  }
-
-  if (builtin->IsDeprecated()) {
-    AddWarning("use of deprecated builtin", expr->source);
-  }
-
-  bool has_side_effects = builtin->HasSideEffects() ||
-                          std::any_of(args.begin(), args.end(), [](auto* e) {
-                            return e->HasSideEffects();
-                          });
-  auto* call = builder_->create<sem::Call>(expr, builtin, std::move(args),
-                                           current_statement_, sem::Constant{},
-                                           has_side_effects);
-
-  current_function_->AddDirectlyCalledBuiltin(builtin);
-
-  if (IsTextureBuiltin(builtin_type)) {
-    if (!ValidateTextureBuiltinFunction(call)) {
-      return nullptr;
-    }
-    // Collect a texture/sampler pair for this builtin.
-    const auto& signature = builtin->Signature();
-    int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
-    if (texture_index == -1) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "texture builtin without texture parameter";
-    }
-
-    auto* texture = args[texture_index]->As<sem::VariableUser>()->Variable();
-    if (!texture->Type()->UnwrapRef()->Is<sem::StorageTexture>()) {
-      int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
-      const sem::Variable* sampler =
-          sampler_index != -1
-              ? args[sampler_index]->As<sem::VariableUser>()->Variable()
-              : nullptr;
-      current_function_->AddTextureSamplerPair(texture, sampler);
-    }
-  }
-
-  if (!ValidateBuiltinCall(call)) {
-    return nullptr;
-  }
-
-  current_function_->AddDirectCall(call);
-
-  return call;
-}
-
-sem::Call* Resolver::FunctionCall(
-    const ast::CallExpression* expr,
-    sem::Function* target,
-    const std::vector<const sem::Expression*> args,
-    sem::Behaviors arg_behaviors) {
-  auto sym = expr->target.name->symbol;
-  auto name = builder_->Symbols().NameFor(sym);
-
-  // TODO(crbug.com/tint/1420): For now, assume all function calls have side
-  // effects.
-  bool has_side_effects = true;
-  auto* call = builder_->create<sem::Call>(expr, target, std::move(args),
-                                           current_statement_, sem::Constant{},
-                                           has_side_effects);
-
-  if (current_function_) {
-    // Note: Requires called functions to be resolved first.
-    // This is currently guaranteed as functions must be declared before
-    // use.
-    current_function_->AddTransitivelyCalledFunction(target);
-    current_function_->AddDirectCall(call);
-    for (auto* transitive_call : target->TransitivelyCalledFunctions()) {
-      current_function_->AddTransitivelyCalledFunction(transitive_call);
-    }
-
-    // We inherit any referenced variables from the callee.
-    for (auto* var : target->TransitivelyReferencedGlobals()) {
-      current_function_->AddTransitivelyReferencedGlobal(var);
-    }
-
-    // Map all texture/sampler pairs from the target function to the
-    // current function. These can only be global or parameter
-    // variables. Resolve any parameter variables to the corresponding
-    // argument passed to the current function. Leave global variables
-    // as-is. Then add the mapped pair to the current function's list of
-    // texture/sampler pairs.
-    for (sem::VariablePair pair : target->TextureSamplerPairs()) {
-      const sem::Variable* texture = pair.first;
-      const sem::Variable* sampler = pair.second;
-      if (auto* param = texture->As<sem::Parameter>()) {
-        texture = args[param->Index()]->As<sem::VariableUser>()->Variable();
-      }
-      if (sampler) {
-        if (auto* param = sampler->As<sem::Parameter>()) {
-          sampler = args[param->Index()]->As<sem::VariableUser>()->Variable();
-        }
-      }
-      current_function_->AddTextureSamplerPair(texture, sampler);
-    }
-  }
-
-  target->AddCallSite(call);
-
-  call->Behaviors() = arg_behaviors + target->Behaviors();
-
-  if (!ValidateFunctionCall(call)) {
-    return nullptr;
-  }
-
-  return call;
-}
-
-sem::Call* Resolver::TypeConversion(const ast::CallExpression* expr,
-                                    const sem::Type* target,
-                                    const sem::Expression* arg,
-                                    const sem::Type* source) {
-  // It is not valid to have a type-cast call expression inside a call
-  // statement.
-  if (IsCallStatement(expr)) {
-    AddError("type cast evaluated but not used", expr->source);
-    return nullptr;
-  }
-
-  auto* call_target = utils::GetOrCreate(
-      type_conversions_, TypeConversionSig{target, source},
-      [&]() -> sem::TypeConversion* {
-        // Now that the argument types have been determined, make sure that
-        // they obey the conversion rules laid out in
-        // https://gpuweb.github.io/gpuweb/wgsl/#conversion-expr.
-        bool ok = Switch(
-            target,
-            [&](const sem::Vector* vec_type) {
-              return ValidateVectorConstructorOrCast(expr, vec_type);
-            },
-            [&](const sem::Matrix* mat_type) {
-              // Note: Matrix types currently cannot be converted (the element
-              // type must only be f32). We implement this for the day we
-              // support other matrix element types.
-              return ValidateMatrixConstructorOrCast(expr, mat_type);
-            },
-            [&](const sem::Array* arr_type) {
-              return ValidateArrayConstructorOrCast(expr, arr_type);
-            },
-            [&](const sem::Struct* struct_type) {
-              return ValidateStructureConstructorOrCast(expr, struct_type);
-            },
-            [&](Default) {
-              if (target->is_scalar()) {
-                return ValidateScalarConstructorOrCast(expr, target);
-              }
-              AddError("type is not constructible", expr->source);
-              return false;
-            });
-        if (!ok) {
-          return nullptr;
-        }
-
-        auto* param = builder_->create<sem::Parameter>(
-            nullptr,                   // declaration
-            0,                         // index
-            source->UnwrapRef(),       // type
-            ast::StorageClass::kNone,  // storage_class
-            ast::Access::kUndefined);  // access
-        return builder_->create<sem::TypeConversion>(target, param);
-      });
-
-  if (!call_target) {
-    return nullptr;
-  }
-
-  auto val = EvaluateConstantValue(expr, target);
-  bool has_side_effects = arg->HasSideEffects();
-  return builder_->create<sem::Call>(expr, call_target,
-                                     std::vector<const sem::Expression*>{arg},
-                                     current_statement_, val, has_side_effects);
-}
-
-sem::Call* Resolver::TypeConstructor(
-    const ast::CallExpression* expr,
-    const sem::Type* ty,
-    const std::vector<const sem::Expression*> args,
-    const std::vector<const sem::Type*> arg_tys) {
-  // It is not valid to have a type-constructor call expression as a call
-  // statement.
-  if (IsCallStatement(expr)) {
-    AddError("type constructor evaluated but not used", expr->source);
-    return nullptr;
-  }
-
-  auto* call_target = utils::GetOrCreate(
-      type_ctors_, TypeConstructorSig{ty, arg_tys},
-      [&]() -> sem::TypeConstructor* {
-        // Now that the argument types have been determined, make sure that
-        // they obey the constructor type rules laid out in
-        // https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr.
-        bool ok = Switch(
-            ty,
-            [&](const sem::Vector* vec_type) {
-              return ValidateVectorConstructorOrCast(expr, vec_type);
-            },
-            [&](const sem::Matrix* mat_type) {
-              return ValidateMatrixConstructorOrCast(expr, mat_type);
-            },
-            [&](const sem::Array* arr_type) {
-              return ValidateArrayConstructorOrCast(expr, arr_type);
-            },
-            [&](const sem::Struct* struct_type) {
-              return ValidateStructureConstructorOrCast(expr, struct_type);
-            },
-            [&](Default) {
-              if (ty->is_scalar()) {
-                return ValidateScalarConstructorOrCast(expr, ty);
-              }
-              AddError("type is not constructible", expr->source);
-              return false;
-            });
-        if (!ok) {
-          return nullptr;
-        }
-
-        return builder_->create<sem::TypeConstructor>(
-            ty, utils::Transform(
-                    arg_tys,
-                    [&](const sem::Type* t, size_t i) -> const sem::Parameter* {
-                      return builder_->create<sem::Parameter>(
-                          nullptr,                   // declaration
-                          static_cast<uint32_t>(i),  // index
-                          t->UnwrapRef(),            // type
-                          ast::StorageClass::kNone,  // storage_class
-                          ast::Access::kUndefined);  // access
-                    }));
-      });
-
-  if (!call_target) {
-    return nullptr;
-  }
-
-  auto val = EvaluateConstantValue(expr, ty);
-  bool has_side_effects = std::any_of(
-      args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
-  return builder_->create<sem::Call>(expr, call_target, std::move(args),
-                                     current_statement_, val, has_side_effects);
-}
-
-sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
-  auto* ty = TypeOf(literal);
-  if (!ty) {
-    return nullptr;
-  }
-
-  auto val = EvaluateConstantValue(literal, ty);
-  return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
-                                           /* has_side_effects */ false);
-}
-
-sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
-  auto symbol = expr->symbol;
-  auto* resolved = ResolvedSymbol(expr);
-  if (auto* var = As<sem::Variable>(resolved)) {
-    auto* user =
-        builder_->create<sem::VariableUser>(expr, current_statement_, var);
-
-    if (current_statement_) {
-      // If identifier is part of a loop continuing block, make sure it
-      // doesn't refer to a variable that is bypassed by a continue statement
-      // in the loop's body block.
-      if (auto* continuing_block =
-              current_statement_
-                  ->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
-        auto* loop_block =
-            continuing_block->FindFirstParent<sem::LoopBlockStatement>();
-        if (loop_block->FirstContinue()) {
-          auto& decls = loop_block->Decls();
-          // If our identifier is in loop_block->decls, make sure its index is
-          // less than first_continue
-          auto iter =
-              std::find_if(decls.begin(), decls.end(),
-                           [&symbol](auto* v) { return v->symbol == symbol; });
-          if (iter != decls.end()) {
-            auto var_decl_index =
-                static_cast<size_t>(std::distance(decls.begin(), iter));
-            if (var_decl_index >= loop_block->NumDeclsAtFirstContinue()) {
-              AddError("continue statement bypasses declaration of '" +
-                           builder_->Symbols().NameFor(symbol) + "'",
-                       loop_block->FirstContinue()->source);
-              AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
-                          "' declared here",
-                      (*iter)->source);
-              AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
-                          "' referenced in continuing block here",
-                      expr->source);
-              return nullptr;
-            }
-          }
-        }
-      }
-    }
-
-    if (current_function_) {
-      if (auto* global = var->As<sem::GlobalVariable>()) {
-        current_function_->AddDirectlyReferencedGlobal(global);
-      }
-    }
-
-    var->AddUser(user);
-    return user;
-  }
-
-  if (Is<sem::Function>(resolved)) {
-    AddError("missing '(' for function call", expr->source.End());
-    return nullptr;
-  }
-
-  if (IsBuiltin(symbol)) {
-    AddError("missing '(' for builtin call", expr->source.End());
-    return nullptr;
-  }
-
-  if (resolved->Is<sem::Type>()) {
-    AddError("missing '(' for type constructor or cast", expr->source.End());
-    return nullptr;
-  }
-
-  TINT_ICE(Resolver, diagnostics_)
-      << expr->source << " unresolved identifier:\n"
-      << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>")
-      << "\n"
-      << "name: " << builder_->Symbols().NameFor(symbol);
-  return nullptr;
-}
-
-sem::Expression* Resolver::MemberAccessor(
-    const ast::MemberAccessorExpression* expr) {
-  auto* structure = TypeOf(expr->structure);
-  auto* storage_ty = structure->UnwrapRef();
-
-  const sem::Type* ret = nullptr;
-  std::vector<uint32_t> swizzle;
-
-  if (auto* str = storage_ty->As<sem::Struct>()) {
-    Mark(expr->member);
-    auto symbol = expr->member->symbol;
-
-    const sem::StructMember* member = nullptr;
-    for (auto* m : str->Members()) {
-      if (m->Name() == symbol) {
-        ret = m->Type();
-        member = m;
-        break;
-      }
-    }
-
-    if (ret == nullptr) {
-      AddError(
-          "struct member " + builder_->Symbols().NameFor(symbol) + " not found",
-          expr->source);
-      return nullptr;
-    }
-
-    // If we're extracting from a reference, we return a reference.
-    if (auto* ref = structure->As<sem::Reference>()) {
-      ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
-                                             ref->Access());
-    }
-
-    // Structure may be a side-effecting expression (e.g. function call).
-    auto* sem_structure = Sem(expr->structure);
-    bool has_side_effects = sem_structure && sem_structure->HasSideEffects();
-
-    return builder_->create<sem::StructMemberAccess>(
-        expr, ret, current_statement_, member, has_side_effects);
-  }
-
-  if (auto* vec = storage_ty->As<sem::Vector>()) {
-    Mark(expr->member);
-    std::string s = builder_->Symbols().NameFor(expr->member->symbol);
-    auto size = s.size();
-    swizzle.reserve(s.size());
-
-    for (auto c : s) {
-      switch (c) {
-        case 'x':
-        case 'r':
-          swizzle.emplace_back(0);
-          break;
-        case 'y':
-        case 'g':
-          swizzle.emplace_back(1);
-          break;
-        case 'z':
-        case 'b':
-          swizzle.emplace_back(2);
-          break;
-        case 'w':
-        case 'a':
-          swizzle.emplace_back(3);
-          break;
-        default:
-          AddError("invalid vector swizzle character",
-                   expr->member->source.Begin() + swizzle.size());
-          return nullptr;
-      }
-
-      if (swizzle.back() >= vec->Width()) {
-        AddError("invalid vector swizzle member", expr->member->source);
-        return nullptr;
-      }
-    }
-
-    if (size < 1 || size > 4) {
-      AddError("invalid vector swizzle size", expr->member->source);
-      return nullptr;
-    }
-
-    // All characters are valid, check if they're being mixed
-    auto is_rgba = [](char c) {
-      return c == 'r' || c == 'g' || c == 'b' || c == 'a';
-    };
-    auto is_xyzw = [](char c) {
-      return c == 'x' || c == 'y' || c == 'z' || c == 'w';
-    };
-    if (!std::all_of(s.begin(), s.end(), is_rgba) &&
-        !std::all_of(s.begin(), s.end(), is_xyzw)) {
-      AddError("invalid mixing of vector swizzle characters rgba with xyzw",
-               expr->member->source);
-      return nullptr;
-    }
-
-    if (size == 1) {
-      // A single element swizzle is just the type of the vector.
-      ret = vec->type();
-      // If we're extracting from a reference, we return a reference.
-      if (auto* ref = structure->As<sem::Reference>()) {
-        ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
-                                               ref->Access());
-      }
-    } else {
-      // The vector will have a number of components equal to the length of
-      // the swizzle.
-      ret = builder_->create<sem::Vector>(vec->type(),
-                                          static_cast<uint32_t>(size));
-    }
-    return builder_->create<sem::Swizzle>(expr, ret, current_statement_,
-                                          std::move(swizzle));
-  }
-
-  AddError(
-      "invalid member accessor expression. Expected vector or struct, got '" +
-          TypeNameOf(storage_ty) + "'",
-      expr->structure->source);
-  return nullptr;
-}
-
-sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
-  using Bool = sem::Bool;
-  using F32 = sem::F32;
-  using I32 = sem::I32;
-  using U32 = sem::U32;
-  using Matrix = sem::Matrix;
-  using Vector = sem::Vector;
-
-  auto* lhs = Sem(expr->lhs);
-  auto* rhs = Sem(expr->rhs);
-
-  auto* lhs_ty = lhs->Type()->UnwrapRef();
-  auto* rhs_ty = rhs->Type()->UnwrapRef();
-
-  auto* lhs_vec = lhs_ty->As<Vector>();
-  auto* lhs_vec_elem_type = lhs_vec ? lhs_vec->type() : nullptr;
-  auto* rhs_vec = rhs_ty->As<Vector>();
-  auto* rhs_vec_elem_type = rhs_vec ? rhs_vec->type() : nullptr;
-
-  const bool matching_vec_elem_types =
-      lhs_vec_elem_type && rhs_vec_elem_type &&
-      (lhs_vec_elem_type == rhs_vec_elem_type) &&
-      (lhs_vec->Width() == rhs_vec->Width());
-
-  const bool matching_types = matching_vec_elem_types || (lhs_ty == rhs_ty);
-
-  auto build = [&](const sem::Type* ty) {
-    auto val = EvaluateConstantValue(expr, ty);
-    bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
-    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
-                                                  val, has_side_effects);
-    sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
-    return sem;
-  };
-
-  // Binary logical expressions
-  if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
-    if (matching_types && lhs_ty->Is<Bool>()) {
-      return build(lhs_ty);
-    }
-  }
-  if (expr->IsOr() || expr->IsAnd()) {
-    if (matching_types && lhs_ty->Is<Bool>()) {
-      return build(lhs_ty);
-    }
-    if (matching_types && lhs_vec_elem_type && lhs_vec_elem_type->Is<Bool>()) {
-      return build(lhs_ty);
-    }
-  }
-
-  // Arithmetic expressions
-  if (expr->IsArithmetic()) {
-    // Binary arithmetic expressions over scalars
-    if (matching_types && lhs_ty->is_numeric_scalar()) {
-      return build(lhs_ty);
-    }
-
-    // Binary arithmetic expressions over vectors
-    if (matching_types && lhs_vec_elem_type &&
-        lhs_vec_elem_type->is_numeric_scalar()) {
-      return build(lhs_ty);
-    }
-
-    // Binary arithmetic expressions with mixed scalar and vector operands
-    if (lhs_vec_elem_type && (lhs_vec_elem_type == rhs_ty)) {
-      if (expr->IsModulo()) {
-        if (rhs_ty->is_integer_scalar()) {
-          return build(lhs_ty);
-        }
-      } else if (rhs_ty->is_numeric_scalar()) {
-        return build(lhs_ty);
-      }
-    }
-    if (rhs_vec_elem_type && (rhs_vec_elem_type == lhs_ty)) {
-      if (expr->IsModulo()) {
-        if (lhs_ty->is_integer_scalar()) {
-          return build(rhs_ty);
-        }
-      } else if (lhs_ty->is_numeric_scalar()) {
-        return build(rhs_ty);
-      }
-    }
-  }
-
-  // Matrix arithmetic
-  auto* lhs_mat = lhs_ty->As<Matrix>();
-  auto* lhs_mat_elem_type = lhs_mat ? lhs_mat->type() : nullptr;
-  auto* rhs_mat = rhs_ty->As<Matrix>();
-  auto* rhs_mat_elem_type = rhs_mat ? rhs_mat->type() : nullptr;
-  // Addition and subtraction of float matrices
-  if ((expr->IsAdd() || expr->IsSubtract()) && lhs_mat_elem_type &&
-      lhs_mat_elem_type->Is<F32>() && rhs_mat_elem_type &&
-      rhs_mat_elem_type->Is<F32>() &&
-      (lhs_mat->columns() == rhs_mat->columns()) &&
-      (lhs_mat->rows() == rhs_mat->rows())) {
-    return build(rhs_ty);
-  }
-  if (expr->IsMultiply()) {
-    // Multiplication of a matrix and a scalar
-    if (lhs_ty->Is<F32>() && rhs_mat_elem_type &&
-        rhs_mat_elem_type->Is<F32>()) {
-      return build(rhs_ty);
-    }
-    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
-        rhs_ty->Is<F32>()) {
-      return build(lhs_ty);
-    }
-
-    // Vector times matrix
-    if (lhs_vec_elem_type && lhs_vec_elem_type->Is<F32>() &&
-        rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
-        (lhs_vec->Width() == rhs_mat->rows())) {
-      return build(
-          builder_->create<sem::Vector>(lhs_vec->type(), rhs_mat->columns()));
-    }
-
-    // Matrix times vector
-    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
-        rhs_vec_elem_type && rhs_vec_elem_type->Is<F32>() &&
-        (lhs_mat->columns() == rhs_vec->Width())) {
-      return build(
-          builder_->create<sem::Vector>(rhs_vec->type(), lhs_mat->rows()));
-    }
-
-    // Matrix times matrix
-    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
-        rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
-        (lhs_mat->columns() == rhs_mat->rows())) {
-      return build(builder_->create<sem::Matrix>(
-          builder_->create<sem::Vector>(lhs_mat_elem_type, lhs_mat->rows()),
-          rhs_mat->columns()));
-    }
-  }
-
-  // Comparison expressions
-  if (expr->IsComparison()) {
-    if (matching_types) {
-      // Special case for bools: only == and !=
-      if (lhs_ty->Is<Bool>() && (expr->IsEqual() || expr->IsNotEqual())) {
-        return build(builder_->create<sem::Bool>());
-      }
-
-      // For the rest, we can compare i32, u32, and f32
-      if (lhs_ty->IsAnyOf<I32, U32, F32>()) {
-        return build(builder_->create<sem::Bool>());
-      }
-    }
-
-    // Same for vectors
-    if (matching_vec_elem_types) {
-      if (lhs_vec_elem_type->Is<Bool>() &&
-          (expr->IsEqual() || expr->IsNotEqual())) {
-        return build(builder_->create<sem::Vector>(
-            builder_->create<sem::Bool>(), lhs_vec->Width()));
-      }
-
-      if (lhs_vec_elem_type->is_numeric_scalar()) {
-        return build(builder_->create<sem::Vector>(
-            builder_->create<sem::Bool>(), lhs_vec->Width()));
-      }
-    }
-  }
-
-  // Binary bitwise operations
-  if (expr->IsBitwise()) {
-    if (matching_types && lhs_ty->is_integer_scalar_or_vector()) {
-      return build(lhs_ty);
-    }
-  }
-
-  // Bit shift expressions
-  if (expr->IsBitshift()) {
-    // Type validation rules are the same for left or right shift, despite
-    // differences in computation rules (i.e. right shift can be arithmetic or
-    // logical depending on lhs type).
-
-    if (lhs_ty->IsAnyOf<I32, U32>() && rhs_ty->Is<U32>()) {
-      return build(lhs_ty);
-    }
-
-    if (lhs_vec_elem_type && lhs_vec_elem_type->IsAnyOf<I32, U32>() &&
-        rhs_vec_elem_type && rhs_vec_elem_type->Is<U32>()) {
-      return build(lhs_ty);
-    }
-  }
-
-  AddError("Binary expression operand types are invalid for this operation: " +
-               TypeNameOf(lhs_ty) + " " + FriendlyName(expr->op) + " " +
-               TypeNameOf(rhs_ty),
-           expr->source);
-  return nullptr;
-}
-
-sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
-  auto* expr = Sem(unary->expr);
-  auto* expr_ty = expr->Type();
-  if (!expr_ty) {
-    return nullptr;
-  }
-
-  const sem::Type* ty = nullptr;
-
-  switch (unary->op) {
-    case ast::UnaryOp::kNot:
-      // Result type matches the deref'd inner type.
-      ty = expr_ty->UnwrapRef();
-      if (!ty->Is<sem::Bool>() && !ty->is_bool_vector()) {
-        AddError(
-            "cannot logical negate expression of type '" + TypeNameOf(expr_ty),
-            unary->expr->source);
-        return nullptr;
-      }
-      break;
-
-    case ast::UnaryOp::kComplement:
-      // Result type matches the deref'd inner type.
-      ty = expr_ty->UnwrapRef();
-      if (!ty->is_integer_scalar_or_vector()) {
-        AddError("cannot bitwise complement expression of type '" +
-                     TypeNameOf(expr_ty),
-                 unary->expr->source);
-        return nullptr;
-      }
-      break;
-
-    case ast::UnaryOp::kNegation:
-      // Result type matches the deref'd inner type.
-      ty = expr_ty->UnwrapRef();
-      if (!(ty->IsAnyOf<sem::F32, sem::I32>() ||
-            ty->is_signed_integer_vector() || ty->is_float_vector())) {
-        AddError("cannot negate expression of type '" + TypeNameOf(expr_ty),
-                 unary->expr->source);
-        return nullptr;
-      }
-      break;
-
-    case ast::UnaryOp::kAddressOf:
-      if (auto* ref = expr_ty->As<sem::Reference>()) {
-        if (ref->StoreType()->UnwrapRef()->is_handle()) {
-          AddError(
-              "cannot take the address of expression in handle storage class",
-              unary->expr->source);
-          return nullptr;
-        }
-
-        auto* array = unary->expr->As<ast::IndexAccessorExpression>();
-        auto* member = unary->expr->As<ast::MemberAccessorExpression>();
-        if ((array && TypeOf(array->object)->UnwrapRef()->Is<sem::Vector>()) ||
-            (member &&
-             TypeOf(member->structure)->UnwrapRef()->Is<sem::Vector>())) {
-          AddError("cannot take the address of a vector component",
-                   unary->expr->source);
-          return nullptr;
-        }
-
-        ty = builder_->create<sem::Pointer>(ref->StoreType(),
-                                            ref->StorageClass(), ref->Access());
-      } else {
-        AddError("cannot take the address of expression", unary->expr->source);
-        return nullptr;
-      }
-      break;
-
-    case ast::UnaryOp::kIndirection:
-      if (auto* ptr = expr_ty->As<sem::Pointer>()) {
-        ty = builder_->create<sem::Reference>(
-            ptr->StoreType(), ptr->StorageClass(), ptr->Access());
-      } else {
-        AddError("cannot dereference expression of type '" +
-                     TypeNameOf(expr_ty) + "'",
-                 unary->expr->source);
-        return nullptr;
-      }
-      break;
-  }
-
-  auto val = EvaluateConstantValue(unary, ty);
-  auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_,
-                                                val, expr->HasSideEffects());
-  sem->Behaviors() = expr->Behaviors();
-  return sem;
-}
-
-sem::Type* Resolver::TypeDecl(const ast::TypeDecl* named_type) {
-  sem::Type* result = nullptr;
-  if (auto* alias = named_type->As<ast::Alias>()) {
-    result = Alias(alias);
-  } else if (auto* str = named_type->As<ast::Struct>()) {
-    result = Structure(str);
-  } else {
-    TINT_UNREACHABLE(Resolver, diagnostics_) << "Unhandled TypeDecl";
-  }
-
-  if (!result) {
-    return nullptr;
-  }
-
-  builder_->Sem().Add(named_type, result);
-  return result;
-}
-
-sem::Type* Resolver::TypeOf(const ast::Expression* expr) {
-  auto* sem = Sem(expr);
-  return sem ? const_cast<sem::Type*>(sem->Type()) : nullptr;
-}
-
-std::string Resolver::TypeNameOf(const sem::Type* ty) {
-  return RawTypeNameOf(ty->UnwrapRef());
-}
-
-std::string Resolver::RawTypeNameOf(const sem::Type* ty) {
-  return ty->FriendlyName(builder_->Symbols());
-}
-
-sem::Type* Resolver::TypeOf(const ast::LiteralExpression* lit) {
-  return Switch(
-      lit,
-      [&](const ast::SintLiteralExpression*) -> sem::Type* {
-        return builder_->create<sem::I32>();
-      },
-      [&](const ast::UintLiteralExpression*) -> sem::Type* {
-        return builder_->create<sem::U32>();
-      },
-      [&](const ast::FloatLiteralExpression*) -> sem::Type* {
-        return builder_->create<sem::F32>();
-      },
-      [&](const ast::BoolLiteralExpression*) -> sem::Type* {
-        return builder_->create<sem::Bool>();
-      },
-      [&](Default) -> sem::Type* {
-        TINT_UNREACHABLE(Resolver, diagnostics_)
-            << "Unhandled literal type: " << lit->TypeInfo().name;
-        return nullptr;
-      });
-}
-
-sem::Array* Resolver::Array(const ast::Array* arr) {
-  auto source = arr->source;
-
-  auto* elem_type = Type(arr->type);
-  if (!elem_type) {
-    return nullptr;
-  }
-
-  if (!IsPlain(elem_type)) {  // Check must come before GetDefaultAlignAndSize()
-    AddError(TypeNameOf(elem_type) +
-                 " cannot be used as an element type of an array",
-             source);
-    return nullptr;
-  }
-
-  uint32_t el_align = elem_type->Align();
-  uint32_t el_size = elem_type->Size();
-
-  if (!ValidateNoDuplicateAttributes(arr->attributes)) {
-    return nullptr;
-  }
-
-  // Look for explicit stride via @stride(n) attribute
-  uint32_t explicit_stride = 0;
-  for (auto* attr : arr->attributes) {
-    Mark(attr);
-    if (auto* sd = attr->As<ast::StrideAttribute>()) {
-      explicit_stride = sd->stride;
-      if (!ValidateArrayStrideAttribute(sd, el_size, el_align, source)) {
-        return nullptr;
-      }
-      continue;
-    }
-
-    AddError("attribute is not valid for array types", attr->source);
-    return nullptr;
-  }
-
-  // Calculate implicit stride
-  uint64_t implicit_stride = utils::RoundUp<uint64_t>(el_align, el_size);
-
-  uint64_t stride = explicit_stride ? explicit_stride : implicit_stride;
-
-  // Evaluate the constant array size expression.
-  // sem::Array uses a size of 0 for a runtime-sized array.
-  uint32_t count = 0;
-  if (auto* count_expr = arr->count) {
-    auto* count_sem = Expression(count_expr);
-    if (!count_sem) {
-      return nullptr;
-    }
-
-    auto size_source = count_expr->source;
-
-    auto* ty = count_sem->Type()->UnwrapRef();
-    if (!ty->is_integer_scalar()) {
-      AddError("array size must be integer scalar", size_source);
-      return nullptr;
-    }
-
-    if (auto* ident = count_expr->As<ast::IdentifierExpression>()) {
-      // Make sure the identifier is a non-overridable module-scope constant.
-      auto* var = ResolvedSymbol<sem::GlobalVariable>(ident);
-      if (!var || !var->Declaration()->is_const) {
-        AddError("array size identifier must be a module-scope constant",
-                 size_source);
-        return nullptr;
-      }
-      if (var->IsOverridable()) {
-        AddError("array size expression must not be pipeline-overridable",
-                 size_source);
-        return nullptr;
-      }
-
-      count_expr = var->Declaration()->constructor;
-    } else if (!count_expr->Is<ast::LiteralExpression>()) {
-      AddError(
-          "array size expression must be either a literal or a module-scope "
-          "constant",
-          size_source);
-      return nullptr;
-    }
-
-    auto count_val = count_sem->ConstantValue();
-    if (!count_val) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "could not resolve array size expression";
-      return nullptr;
-    }
-
-    if (ty->is_signed_integer_scalar() ? count_val.Elements()[0].i32 < 1
-                                       : count_val.Elements()[0].u32 < 1u) {
-      AddError("array size must be at least 1", size_source);
-      return nullptr;
-    }
-
-    count = count_val.Elements()[0].u32;
-  }
-
-  auto size = std::max<uint64_t>(count, 1) * stride;
-  if (size > std::numeric_limits<uint32_t>::max()) {
-    std::stringstream msg;
-    msg << "array size in bytes must not exceed 0x" << std::hex
-        << std::numeric_limits<uint32_t>::max() << ", but is 0x" << std::hex
-        << size;
-    AddError(msg.str(), arr->source);
-    return nullptr;
-  }
-  if (stride > std::numeric_limits<uint32_t>::max() ||
-      implicit_stride > std::numeric_limits<uint32_t>::max()) {
-    TINT_ICE(Resolver, diagnostics_)
-        << "calculated array stride exceeds uint32";
-    return nullptr;
-  }
-  auto* out = builder_->create<sem::Array>(
-      elem_type, count, el_align, static_cast<uint32_t>(size),
-      static_cast<uint32_t>(stride), static_cast<uint32_t>(implicit_stride));
-
-  if (!ValidateArray(out, source)) {
-    return nullptr;
-  }
-
-  if (elem_type->Is<sem::Atomic>()) {
-    atomic_composite_info_.emplace(out, arr->type->source);
-  } else {
-    auto found = atomic_composite_info_.find(elem_type);
-    if (found != atomic_composite_info_.end()) {
-      atomic_composite_info_.emplace(out, found->second);
-    }
-  }
-
-  return out;
-}
-
-sem::Type* Resolver::Alias(const ast::Alias* alias) {
-  auto* ty = Type(alias->type);
-  if (!ty) {
-    return nullptr;
-  }
-  if (!ValidateAlias(alias)) {
-    return nullptr;
-  }
-  return ty;
-}
-
-sem::Struct* Resolver::Structure(const ast::Struct* str) {
-  if (!ValidateNoDuplicateAttributes(str->attributes)) {
-    return nullptr;
-  }
-  for (auto* attr : str->attributes) {
-    Mark(attr);
-  }
-
-  sem::StructMemberList sem_members;
-  sem_members.reserve(str->members.size());
-
-  // Calculate the effective size and alignment of each field, and the overall
-  // size of the structure.
-  // For size, use the size attribute if provided, otherwise use the default
-  // size for the type.
-  // For alignment, use the alignment attribute if provided, otherwise use the
-  // default alignment for the member type.
-  // Diagnostic errors are raised if a basic rule is violated.
-  // Validation of storage-class rules requires analysing the actual variable
-  // usage of the structure, and so is performed as part of the variable
-  // validation.
-  uint64_t struct_size = 0;
-  uint64_t struct_align = 1;
-  std::unordered_map<Symbol, const ast::StructMember*> member_map;
-
-  for (auto* member : str->members) {
-    Mark(member);
-    auto result = member_map.emplace(member->symbol, member);
-    if (!result.second) {
-      AddError("redefinition of '" +
-                   builder_->Symbols().NameFor(member->symbol) + "'",
-               member->source);
-      AddNote("previous definition is here", result.first->second->source);
-      return nullptr;
-    }
-
-    // Resolve member type
-    auto* type = Type(member->type);
-    if (!type) {
-      return nullptr;
-    }
-
-    // Validate member type
-    if (!IsPlain(type)) {
-      AddError(TypeNameOf(type) +
-                   " cannot be used as the type of a structure member",
-               member->source);
-      return nullptr;
-    }
-
-    uint64_t offset = struct_size;
-    uint64_t align = type->Align();
-    uint64_t size = type->Size();
-
-    if (!ValidateNoDuplicateAttributes(member->attributes)) {
-      return nullptr;
-    }
-
-    bool has_offset_attr = false;
-    bool has_align_attr = false;
-    bool has_size_attr = false;
-    for (auto* attr : member->attributes) {
-      Mark(attr);
-      if (auto* o = attr->As<ast::StructMemberOffsetAttribute>()) {
-        // Offset attributes are not part of the WGSL spec, but are emitted
-        // by the SPIR-V reader.
-        if (o->offset < struct_size) {
-          AddError("offsets must be in ascending order", o->source);
-          return nullptr;
-        }
-        offset = o->offset;
-        align = 1;
-        has_offset_attr = true;
-      } else if (auto* a = attr->As<ast::StructMemberAlignAttribute>()) {
-        if (a->align <= 0 || !utils::IsPowerOfTwo(a->align)) {
-          AddError("align value must be a positive, power-of-two integer",
-                   a->source);
-          return nullptr;
-        }
-        align = a->align;
-        has_align_attr = true;
-      } else if (auto* s = attr->As<ast::StructMemberSizeAttribute>()) {
-        if (s->size < size) {
-          AddError("size must be at least as big as the type's size (" +
-                       std::to_string(size) + ")",
-                   s->source);
-          return nullptr;
-        }
-        size = s->size;
-        has_size_attr = true;
-      }
-    }
-
-    if (has_offset_attr && (has_align_attr || has_size_attr)) {
-      AddError("offset attributes cannot be used with align or size attributes",
-               member->source);
-      return nullptr;
-    }
-
-    offset = utils::RoundUp(align, offset);
-    if (offset > std::numeric_limits<uint32_t>::max()) {
-      std::stringstream msg;
-      msg << "struct member has byte offset 0x" << std::hex << offset
-          << ", but must not exceed 0x" << std::hex
-          << std::numeric_limits<uint32_t>::max();
-      AddError(msg.str(), member->source);
-      return nullptr;
-    }
-
-    auto* sem_member = builder_->create<sem::StructMember>(
-        member, 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));
-    builder_->Sem().Add(member, sem_member);
-    sem_members.emplace_back(sem_member);
-
-    struct_size = offset + size;
-    struct_align = std::max(struct_align, align);
-  }
-
-  uint64_t size_no_padding = struct_size;
-  struct_size = utils::RoundUp(struct_align, struct_size);
-
-  if (struct_size > std::numeric_limits<uint32_t>::max()) {
-    std::stringstream msg;
-    msg << "struct size in bytes must not exceed 0x" << std::hex
-        << std::numeric_limits<uint32_t>::max() << ", but is 0x" << std::hex
-        << struct_size;
-    AddError(msg.str(), str->source);
-    return nullptr;
-  }
-  if (struct_align > std::numeric_limits<uint32_t>::max()) {
-    TINT_ICE(Resolver, diagnostics_)
-        << "calculated struct stride exceeds uint32";
-    return nullptr;
-  }
-
-  auto* out = builder_->create<sem::Struct>(
-      str, 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_.emplace(out,
-                                     sem_members[i]->Declaration()->source);
-      break;
-    } else {
-      auto found = atomic_composite_info_.find(mem_type);
-      if (found != atomic_composite_info_.end()) {
-        atomic_composite_info_.emplace(out, found->second);
-        break;
-      }
-    }
-  }
-
-  if (!ValidateStructure(out)) {
-    return nullptr;
-  }
-
-  return out;
-}
-
-sem::Statement* Resolver::ReturnStatement(const ast::ReturnStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    auto& behaviors = current_statement_->Behaviors();
-    behaviors = sem::Behavior::kReturn;
-
-    if (auto* value = stmt->value) {
-      auto* expr = Expression(value);
-      if (!expr) {
-        return false;
-      }
-      behaviors.Add(expr->Behaviors() - sem::Behavior::kNext);
-    }
-
-    // Validate after processing the return value expression so that its type
-    // is available for validation.
-    return ValidateReturn(stmt);
-  });
-}
-
-sem::SwitchStatement* Resolver::SwitchStatement(
-    const ast::SwitchStatement* stmt) {
-  auto* sem = builder_->create<sem::SwitchStatement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    auto& behaviors = sem->Behaviors();
-
-    auto* cond = Expression(stmt->condition);
-    if (!cond) {
-      return false;
-    }
-    behaviors = cond->Behaviors() - sem::Behavior::kNext;
-
-    for (auto* case_stmt : stmt->body) {
-      Mark(case_stmt);
-      auto* c = CaseStatement(case_stmt);
-      if (!c) {
-        return false;
-      }
-      behaviors.Add(c->Behaviors());
-    }
-
-    if (behaviors.Contains(sem::Behavior::kBreak)) {
-      behaviors.Add(sem::Behavior::kNext);
-    }
-    behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kFallthrough);
-
-    return ValidateSwitch(stmt);
-  });
-}
-
-sem::Statement* Resolver::VariableDeclStatement(
-    const ast::VariableDeclStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    Mark(stmt->variable);
-
-    auto* var = Variable(stmt->variable, VariableKind::kLocal);
-    if (!var) {
-      return false;
-    }
-
-    for (auto* attr : stmt->variable->attributes) {
-      Mark(attr);
-      if (!attr->Is<ast::InternalAttribute>()) {
-        AddError("attributes are not valid on local variables", attr->source);
-        return false;
-      }
-    }
-
-    if (current_block_) {  // Not all statements are inside a block
-      current_block_->AddDecl(stmt->variable);
-    }
-
-    if (auto* ctor = var->Constructor()) {
-      sem->Behaviors() = ctor->Behaviors();
-    }
-
-    return ValidateVariable(var);
-  });
-}
-
-sem::Statement* Resolver::AssignmentStatement(
-    const ast::AssignmentStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    auto* lhs = Expression(stmt->lhs);
-    if (!lhs) {
-      return false;
-    }
-
-    auto* rhs = Expression(stmt->rhs);
-    if (!rhs) {
-      return false;
-    }
-
-    auto& behaviors = sem->Behaviors();
-    behaviors = rhs->Behaviors();
-    if (!stmt->lhs->Is<ast::PhonyExpression>()) {
-      behaviors.Add(lhs->Behaviors());
-    }
-
-    return ValidateAssignment(stmt);
-  });
-}
-
-sem::Statement* Resolver::BreakStatement(const ast::BreakStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    sem->Behaviors() = sem::Behavior::kBreak;
-
-    return ValidateBreakStatement(sem);
-  });
-}
-
-sem::Statement* Resolver::CallStatement(const ast::CallStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    if (auto* expr = Expression(stmt->expr)) {
-      sem->Behaviors() = expr->Behaviors();
-      return true;
-    }
-    return false;
-  });
-}
-
-sem::Statement* Resolver::ContinueStatement(
-    const ast::ContinueStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    sem->Behaviors() = sem::Behavior::kContinue;
-
-    // Set if we've hit the first continue statement in our parent loop
-    if (auto* block = sem->FindFirstParent<sem::LoopBlockStatement>()) {
-      if (!block->FirstContinue()) {
-        const_cast<sem::LoopBlockStatement*>(block)->SetFirstContinue(
-            stmt, block->Decls().size());
-      }
-    }
-
-    return ValidateContinueStatement(sem);
-  });
-}
-
-sem::Statement* Resolver::DiscardStatement(const ast::DiscardStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    sem->Behaviors() = sem::Behavior::kDiscard;
-    current_function_->SetHasDiscard();
-
-    return ValidateDiscardStatement(sem);
-  });
-}
-
-sem::Statement* Resolver::FallthroughStatement(
-    const ast::FallthroughStatement* stmt) {
-  auto* sem = builder_->create<sem::Statement>(
-      stmt, current_compound_statement_, current_function_);
-  return StatementScope(stmt, sem, [&] {
-    sem->Behaviors() = sem::Behavior::kFallthrough;
-
-    return ValidateFallthroughStatement(sem);
-  });
-}
-
-bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
-                                            sem::Type* ty,
-                                            const Source& usage) {
-  ty = const_cast<sem::Type*>(ty->UnwrapRef());
-
-  if (auto* str = ty->As<sem::Struct>()) {
-    if (str->StorageClassUsage().count(sc)) {
-      return true;  // Already applied
-    }
-
-    str->AddUsage(sc);
-
-    for (auto* member : str->Members()) {
-      if (!ApplyStorageClassUsageToType(sc, member->Type(), usage)) {
-        std::stringstream err;
-        err << "while analysing structure member " << TypeNameOf(str) << "."
-            << builder_->Symbols().NameFor(member->Declaration()->symbol);
-        AddNote(err.str(), member->Declaration()->source);
-        return false;
-      }
-    }
-    return true;
-  }
-
-  if (auto* arr = ty->As<sem::Array>()) {
-    if (arr->IsRuntimeSized() && sc != ast::StorageClass::kStorage) {
-      AddError(
-          "runtime-sized arrays can only be used in the <storage> storage "
-          "class",
-          usage);
-      return false;
-    }
-
-    return ApplyStorageClassUsageToType(
-        sc, const_cast<sem::Type*>(arr->ElemType()), usage);
-  }
-
-  if (ast::IsHostShareable(sc) && !IsHostShareable(ty)) {
-    std::stringstream err;
-    err << "Type '" << TypeNameOf(ty) << "' cannot be used in storage class '"
-        << sc << "' as it is non-host-shareable";
-    AddError(err.str(), usage);
-    return false;
-  }
-
-  return true;
-}
-
-template <typename SEM, typename F>
-SEM* Resolver::StatementScope(const ast::Statement* ast,
-                              SEM* sem,
-                              F&& callback) {
-  builder_->Sem().Add(ast, sem);
-
-  auto* as_compound =
-      As<sem::CompoundStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
-  auto* as_block =
-      As<sem::BlockStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
-
-  TINT_SCOPED_ASSIGNMENT(current_statement_, sem);
-  TINT_SCOPED_ASSIGNMENT(
-      current_compound_statement_,
-      as_compound ? as_compound : current_compound_statement_);
-  TINT_SCOPED_ASSIGNMENT(current_block_, as_block ? as_block : current_block_);
-
-  if (!callback()) {
-    return nullptr;
-  }
-
-  return sem;
-}
-
-std::string Resolver::VectorPretty(uint32_t size,
-                                   const sem::Type* element_type) {
-  sem::Vector vec_type(element_type, size);
-  return vec_type.FriendlyName(builder_->Symbols());
-}
-
-bool Resolver::Mark(const ast::Node* node) {
-  if (node == nullptr) {
-    TINT_ICE(Resolver, diagnostics_) << "Resolver::Mark() called with nullptr";
-    return false;
-  }
-  if (marked_.emplace(node).second) {
-    return true;
-  }
-  TINT_ICE(Resolver, diagnostics_)
-      << "AST node '" << node->TypeInfo().name
-      << "' was encountered twice in the same AST of a Program\n"
-      << "At: " << node->source << "\n"
-      << "Pointer: " << node;
-  return false;
-}
-
-void Resolver::AddError(const std::string& msg, const Source& source) const {
-  diagnostics_.add_error(diag::System::Resolver, msg, source);
-}
-
-void Resolver::AddWarning(const std::string& msg, const Source& source) const {
-  diagnostics_.add_warning(diag::System::Resolver, msg, source);
-}
-
-void Resolver::AddNote(const std::string& msg, const Source& source) const {
-  diagnostics_.add_note(diag::System::Resolver, msg, source);
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section
-bool Resolver::IsPlain(const sem::Type* type) const {
-  return type->is_scalar() ||
-         type->IsAnyOf<sem::Atomic, sem::Vector, sem::Matrix, sem::Array,
-                       sem::Struct>();
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types
-bool Resolver::IsFixedFootprint(const sem::Type* type) const {
-  return Switch(
-      type,                                      //
-      [&](const sem::Vector*) { return true; },  //
-      [&](const sem::Matrix*) { return true; },  //
-      [&](const sem::Atomic*) { return true; },
-      [&](const sem::Array* arr) {
-        return !arr->IsRuntimeSized() && IsFixedFootprint(arr->ElemType());
-      },
-      [&](const sem::Struct* str) {
-        for (auto* member : str->Members()) {
-          if (!IsFixedFootprint(member->Type())) {
-            return false;
-          }
-        }
-        return true;
-      },
-      [&](Default) { return type->is_scalar(); });
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl.html#storable-types
-bool Resolver::IsStorable(const sem::Type* type) const {
-  return IsPlain(type) || type->IsAnyOf<sem::Texture, sem::Sampler>();
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable-types
-bool Resolver::IsHostShareable(const sem::Type* type) const {
-  if (type->IsAnyOf<sem::I32, sem::U32, sem::F32>()) {
-    return true;
-  }
-  return Switch(
-      type,  //
-      [&](const sem::Vector* vec) { return IsHostShareable(vec->type()); },
-      [&](const sem::Matrix* mat) { return IsHostShareable(mat->type()); },
-      [&](const sem::Array* arr) { return IsHostShareable(arr->ElemType()); },
-      [&](const sem::Struct* str) {
-        for (auto* member : str->Members()) {
-          if (!IsHostShareable(member->Type())) {
-            return false;
-          }
-        }
-        return true;
-      },
-      [&](const sem::Atomic* atomic) {
-        return IsHostShareable(atomic->Type());
-      });
-}
-
-bool Resolver::IsBuiltin(Symbol symbol) const {
-  std::string name = builder_->Symbols().NameFor(symbol);
-  return sem::ParseBuiltinType(name) != sem::BuiltinType::kNone;
-}
-
-bool Resolver::IsCallStatement(const ast::Expression* expr) const {
-  return current_statement_ &&
-         Is<ast::CallStatement>(current_statement_->Declaration(),
-                                [&](auto* stmt) { return stmt->expr == expr; });
-}
-
-const ast::Statement* Resolver::ClosestContinuing(bool stop_at_loop) const {
-  for (const auto* s = current_statement_; s != nullptr; s = s->Parent()) {
-    if (stop_at_loop && s->Is<sem::LoopStatement>()) {
-      break;
-    }
-    if (s->Is<sem::LoopContinuingBlockStatement>()) {
-      return s->Declaration();
-    }
-    if (auto* f = As<sem::ForLoopStatement>(s->Parent())) {
-      if (f->Declaration()->continuing == s->Declaration()) {
-        return s->Declaration();
-      }
-      if (stop_at_loop) {
-        break;
-      }
-    }
-  }
-  return nullptr;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Resolver::TypeConversionSig
-////////////////////////////////////////////////////////////////////////////////
-bool Resolver::TypeConversionSig::operator==(
-    const TypeConversionSig& rhs) const {
-  return target == rhs.target && source == rhs.source;
-}
-std::size_t Resolver::TypeConversionSig::Hasher::operator()(
-    const TypeConversionSig& sig) const {
-  return utils::Hash(sig.target, sig.source);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Resolver::TypeConstructorSig
-////////////////////////////////////////////////////////////////////////////////
-Resolver::TypeConstructorSig::TypeConstructorSig(
-    const sem::Type* ty,
-    const std::vector<const sem::Type*> params)
-    : type(ty), parameters(params) {}
-Resolver::TypeConstructorSig::TypeConstructorSig(const TypeConstructorSig&) =
-    default;
-Resolver::TypeConstructorSig::~TypeConstructorSig() = default;
-
-bool Resolver::TypeConstructorSig::operator==(
-    const TypeConstructorSig& rhs) const {
-  return type == rhs.type && parameters == rhs.parameters;
-}
-std::size_t Resolver::TypeConstructorSig::Hasher::operator()(
-    const TypeConstructorSig& sig) const {
-  return utils::Hash(sig.type, sig.parameters);
-}
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
deleted file mode 100644
index ed7ab3a..0000000
--- a/src/resolver/resolver.h
+++ /dev/null
@@ -1,545 +0,0 @@
-// Copyright 2020 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_RESOLVER_RESOLVER_H_
-#define SRC_RESOLVER_RESOLVER_H_
-
-#include <memory>
-#include <set>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/builtin_table.h"
-#include "src/program_builder.h"
-#include "src/resolver/dependency_graph.h"
-#include "src/scope_stack.h"
-#include "src/sem/binding_point.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/constant.h"
-#include "src/sem/function.h"
-#include "src/sem/struct.h"
-#include "src/utils/map.h"
-#include "src/utils/unique_vector.h"
-
-namespace tint {
-
-// Forward declarations
-namespace ast {
-class IndexAccessorExpression;
-class BinaryExpression;
-class BitcastExpression;
-class CallExpression;
-class CallStatement;
-class CaseStatement;
-class ForLoopStatement;
-class Function;
-class IdentifierExpression;
-class LoopStatement;
-class MemberAccessorExpression;
-class ReturnStatement;
-class SwitchStatement;
-class UnaryOpExpression;
-class Variable;
-}  // namespace ast
-namespace sem {
-class Array;
-class Atomic;
-class BlockStatement;
-class Builtin;
-class CaseStatement;
-class ElseStatement;
-class ForLoopStatement;
-class IfStatement;
-class LoopStatement;
-class Statement;
-class SwitchStatement;
-class TypeConstructor;
-}  // namespace sem
-
-namespace resolver {
-
-/// Resolves types for all items in the given tint program
-class Resolver {
- public:
-  /// Constructor
-  /// @param builder the program builder
-  explicit Resolver(ProgramBuilder* builder);
-
-  /// Destructor
-  ~Resolver();
-
-  /// @returns error messages from the resolver
-  std::string error() const { return diagnostics_.str(); }
-
-  /// @returns true if the resolver was successful
-  bool Resolve();
-
-  /// @param type the given type
-  /// @returns true if the given type is a plain type
-  bool IsPlain(const sem::Type* type) const;
-
-  /// @param type the given type
-  /// @returns true if the given type is a fixed-footprint type
-  bool IsFixedFootprint(const sem::Type* type) const;
-
-  /// @param type the given type
-  /// @returns true if the given type is storable
-  bool IsStorable(const sem::Type* type) const;
-
-  /// @param type the given type
-  /// @returns true if the given type is host-shareable
-  bool IsHostShareable(const sem::Type* type) const;
-
- private:
-  /// Describes the context in which a variable is declared
-  enum class VariableKind { kParameter, kLocal, kGlobal };
-
-  std::set<std::pair<const sem::Type*, ast::StorageClass>>
-      valid_type_storage_layouts_;
-
-  /// Structure holding semantic information about a block (i.e. scope), such as
-  /// parent block and variables declared in the block.
-  /// Used to validate variable scoping rules.
-  struct BlockInfo {
-    enum class Type { kGeneric, kLoop, kLoopContinuing, kSwitchCase };
-
-    BlockInfo(const ast::BlockStatement* block, Type type, BlockInfo* parent);
-    ~BlockInfo();
-
-    template <typename Pred>
-    BlockInfo* FindFirstParent(Pred&& pred) {
-      BlockInfo* curr = this;
-      while (curr && !pred(curr)) {
-        curr = curr->parent;
-      }
-      return curr;
-    }
-
-    BlockInfo* FindFirstParent(BlockInfo::Type ty) {
-      return FindFirstParent(
-          [ty](auto* block_info) { return block_info->type == ty; });
-    }
-
-    ast::BlockStatement const* const block;
-    const Type type;
-    BlockInfo* const parent;
-    std::vector<const ast::Variable*> decls;
-
-    // first_continue is set to the index of the first variable in decls
-    // declared after the first continue statement in a loop block, if any.
-    constexpr static size_t kNoContinue = size_t(~0);
-    size_t first_continue = kNoContinue;
-  };
-
-  // Structure holding information for a TypeDecl
-  struct TypeDeclInfo {
-    ast::TypeDecl const* const ast;
-    sem::Type* const sem;
-  };
-
-  /// Resolves the program, without creating final the semantic nodes.
-  /// @returns true on success, false on error
-  bool ResolveInternal();
-
-  bool ValidatePipelineStages();
-
-  /// Creates the nodes and adds them to the sem::Info mappings of the
-  /// ProgramBuilder.
-  void CreateSemanticNodes() const;
-
-  /// Retrieves information for the requested import.
-  /// @param src the source of the import
-  /// @param path the import path
-  /// @param name the method name to get information on
-  /// @param params the parameters to the method call
-  /// @param id out parameter for the external call ID. Must not be a nullptr.
-  /// @returns the return type of `name` in `path` or nullptr on error.
-  sem::Type* GetImportData(const Source& src,
-                           const std::string& path,
-                           const std::string& name,
-                           const ast::ExpressionList& params,
-                           uint32_t* id);
-
-  //////////////////////////////////////////////////////////////////////////////
-  // AST and Type traversal methods
-  //////////////////////////////////////////////////////////////////////////////
-
-  // Expression resolving methods
-  // Returns the semantic node pointer on success, nullptr on failure.
-  sem::Expression* IndexAccessor(const ast::IndexAccessorExpression*);
-  sem::Expression* Binary(const ast::BinaryExpression*);
-  sem::Expression* Bitcast(const ast::BitcastExpression*);
-  sem::Call* Call(const ast::CallExpression*);
-  sem::Expression* Expression(const ast::Expression*);
-  sem::Function* Function(const ast::Function*);
-  sem::Call* FunctionCall(const ast::CallExpression*,
-                          sem::Function* target,
-                          const std::vector<const sem::Expression*> args,
-                          sem::Behaviors arg_behaviors);
-  sem::Expression* Identifier(const ast::IdentifierExpression*);
-  sem::Call* BuiltinCall(const ast::CallExpression*,
-                         sem::BuiltinType,
-                         const std::vector<const sem::Expression*> args,
-                         const std::vector<const sem::Type*> arg_tys);
-  sem::Expression* Literal(const ast::LiteralExpression*);
-  sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
-  sem::Call* TypeConversion(const ast::CallExpression* expr,
-                            const sem::Type* ty,
-                            const sem::Expression* arg,
-                            const sem::Type* arg_ty);
-  sem::Call* TypeConstructor(const ast::CallExpression* expr,
-                             const sem::Type* ty,
-                             const std::vector<const sem::Expression*> args,
-                             const std::vector<const sem::Type*> arg_tys);
-  sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
-
-  // Statement resolving methods
-  // Each return true on success, false on failure.
-  sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
-  sem::BlockStatement* BlockStatement(const ast::BlockStatement*);
-  sem::Statement* BreakStatement(const ast::BreakStatement*);
-  sem::Statement* CallStatement(const ast::CallStatement*);
-  sem::CaseStatement* CaseStatement(const ast::CaseStatement*);
-  sem::Statement* ContinueStatement(const ast::ContinueStatement*);
-  sem::Statement* DiscardStatement(const ast::DiscardStatement*);
-  sem::ElseStatement* ElseStatement(const ast::ElseStatement*);
-  sem::Statement* FallthroughStatement(const ast::FallthroughStatement*);
-  sem::ForLoopStatement* ForLoopStatement(const ast::ForLoopStatement*);
-  sem::GlobalVariable* GlobalVariable(const ast::Variable*);
-  sem::Statement* Parameter(const ast::Variable*);
-  sem::IfStatement* IfStatement(const ast::IfStatement*);
-  sem::LoopStatement* LoopStatement(const ast::LoopStatement*);
-  sem::Statement* ReturnStatement(const ast::ReturnStatement*);
-  sem::Statement* Statement(const ast::Statement*);
-  sem::SwitchStatement* SwitchStatement(const ast::SwitchStatement* s);
-  sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
-  bool Statements(const ast::StatementList&);
-
-  // AST and Type validation methods
-  // Each return true on success, false on failure.
-  bool ValidateAlias(const ast::Alias*);
-  bool ValidateArray(const sem::Array* arr, const Source& source);
-  bool ValidateArrayStrideAttribute(const ast::StrideAttribute* attr,
-                                    uint32_t el_size,
-                                    uint32_t el_align,
-                                    const Source& source);
-  bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
-  bool ValidateAtomicVariable(const sem::Variable* var);
-  bool ValidateAssignment(const ast::AssignmentStatement* a);
-  bool ValidateBitcast(const ast::BitcastExpression* cast, const sem::Type* to);
-  bool ValidateBreakStatement(const sem::Statement* stmt);
-  bool ValidateBuiltinAttribute(const ast::BuiltinAttribute* attr,
-                                const sem::Type* storage_type,
-                                const bool is_input);
-  bool ValidateContinueStatement(const sem::Statement* stmt);
-  bool ValidateDiscardStatement(const sem::Statement* stmt);
-  bool ValidateElseStatement(const sem::ElseStatement* stmt);
-  bool ValidateEntryPoint(const sem::Function* func);
-  bool ValidateForLoopStatement(const sem::ForLoopStatement* stmt);
-  bool ValidateFallthroughStatement(const sem::Statement* stmt);
-  bool ValidateFunction(const sem::Function* func);
-  bool ValidateFunctionCall(const sem::Call* call);
-  bool ValidateGlobalVariable(const sem::Variable* var);
-  bool ValidateIfStatement(const sem::IfStatement* stmt);
-  bool ValidateInterpolateAttribute(const ast::InterpolateAttribute* attr,
-                                    const sem::Type* storage_type);
-  bool ValidateBuiltinCall(const sem::Call* call);
-  bool ValidateLocationAttribute(const ast::LocationAttribute* location,
-                                 const sem::Type* type,
-                                 std::unordered_set<uint32_t>& locations,
-                                 const Source& source,
-                                 const bool is_input = false);
-  bool ValidateLoopStatement(const sem::LoopStatement* stmt);
-  bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
-  bool ValidateFunctionParameter(const ast::Function* func,
-                                 const sem::Variable* var);
-  bool ValidateParameter(const ast::Function* func, const sem::Variable* var);
-  bool ValidateReturn(const ast::ReturnStatement* ret);
-  bool ValidateStatements(const ast::StatementList& stmts);
-  bool ValidateStorageTexture(const ast::StorageTexture* t);
-  bool ValidateStructure(const sem::Struct* str);
-  bool ValidateStructureConstructorOrCast(const ast::CallExpression* ctor,
-                                          const sem::Struct* struct_type);
-  bool ValidateSwitch(const ast::SwitchStatement* s);
-  bool ValidateVariable(const sem::Variable* var);
-  bool ValidateVariableConstructorOrCast(const ast::Variable* var,
-                                         ast::StorageClass storage_class,
-                                         const sem::Type* storage_type,
-                                         const sem::Type* rhs_type);
-  bool ValidateVector(const sem::Vector* ty, const Source& source);
-  bool ValidateVectorConstructorOrCast(const ast::CallExpression* ctor,
-                                       const sem::Vector* vec_type);
-  bool ValidateMatrixConstructorOrCast(const ast::CallExpression* ctor,
-                                       const sem::Matrix* matrix_type);
-  bool ValidateScalarConstructorOrCast(const ast::CallExpression* ctor,
-                                       const sem::Type* type);
-  bool ValidateArrayConstructorOrCast(const ast::CallExpression* ctor,
-                                      const sem::Array* arr_type);
-  bool ValidateTextureBuiltinFunction(const sem::Call* call);
-  bool ValidateNoDuplicateAttributes(const ast::AttributeList& attributes);
-  bool ValidateStorageClassLayout(const sem::Type* type,
-                                  ast::StorageClass sc,
-                                  Source source);
-  bool ValidateStorageClassLayout(const sem::Variable* var);
-
-  /// @returns true if the attribute list contains a
-  /// ast::DisableValidationAttribute with the validation mode equal to
-  /// `validation`
-  bool IsValidationDisabled(const ast::AttributeList& attributes,
-                            ast::DisabledValidation validation) const;
-
-  /// @returns true if the attribute list does not contains a
-  /// ast::DisableValidationAttribute with the validation mode equal to
-  /// `validation`
-  bool IsValidationEnabled(const ast::AttributeList& attributes,
-                           ast::DisabledValidation validation) const;
-
-  /// Resolves the WorkgroupSize for the given function, assigning it to
-  /// current_function_
-  bool WorkgroupSize(const ast::Function*);
-
-  /// @returns the sem::Type for the ast::Type `ty`, building it if it
-  /// hasn't been constructed already. If an error is raised, nullptr is
-  /// returned.
-  /// @param ty the ast::Type
-  sem::Type* Type(const ast::Type* ty);
-
-  /// @param named_type the named type to resolve
-  /// @returns the resolved semantic type
-  sem::Type* TypeDecl(const ast::TypeDecl* named_type);
-
-  /// Builds and returns the semantic information for the array `arr`.
-  /// This method does not mark the ast::Array node, nor attach the generated
-  /// semantic information to the AST node.
-  /// @returns the semantic Array information, or nullptr if an error is
-  /// raised.
-  /// @param arr the Array to get semantic information for
-  sem::Array* Array(const ast::Array* arr);
-
-  /// Builds and returns the semantic information for the alias `alias`.
-  /// This method does not mark the ast::Alias node, nor attach the generated
-  /// semantic information to the AST node.
-  /// @returns the aliased type, or nullptr if an error is raised.
-  sem::Type* Alias(const ast::Alias* alias);
-
-  /// Builds and returns the semantic information for the structure `str`.
-  /// This method does not mark the ast::Struct node, nor attach the generated
-  /// semantic information to the AST node.
-  /// @returns the semantic Struct information, or nullptr if an error is
-  /// raised.
-  sem::Struct* Structure(const ast::Struct* str);
-
-  /// @returns the semantic info for the variable `var`. If an error is
-  /// raised, nullptr is returned.
-  /// @note this method does not resolve the attributes as these are
-  /// context-dependent (global, local, parameter)
-  /// @param var the variable to create or return the `VariableInfo` for
-  /// @param kind what kind of variable we are declaring
-  /// @param index the index of the parameter, if this variable is a parameter
-  sem::Variable* Variable(const ast::Variable* var,
-                          VariableKind kind,
-                          uint32_t index = 0);
-
-  /// Records the storage class usage for the given type, and any transient
-  /// dependencies of the type. Validates that the type can be used for the
-  /// given storage class, erroring if it cannot.
-  /// @param sc the storage class to apply to the type and transitent types
-  /// @param ty the type to apply the storage class on
-  /// @param usage the Source of the root variable declaration that uses the
-  /// given type and storage class. Used for generating sensible error
-  /// messages.
-  /// @returns true on success, false on error
-  bool ApplyStorageClassUsageToType(ast::StorageClass sc,
-                                    sem::Type* ty,
-                                    const Source& usage);
-
-  /// @param storage_class the storage class
-  /// @returns the default access control for the given storage class
-  ast::Access DefaultAccessForStorageClass(ast::StorageClass storage_class);
-
-  /// Allocate constant IDs for pipeline-overridable constants.
-  void AllocateOverridableConstantIds();
-
-  /// Set the shadowing information on variable declarations.
-  /// @note this method must only be called after all semantic nodes are built.
-  void SetShadows();
-
-  /// @returns the resolved type of the ast::Expression `expr`
-  /// @param expr the expression
-  sem::Type* TypeOf(const ast::Expression* expr);
-
-  /// @returns the type name of the given semantic type, unwrapping
-  /// references.
-  std::string TypeNameOf(const sem::Type* ty);
-
-  /// @returns the type name of the given semantic type, without unwrapping
-  /// references.
-  std::string RawTypeNameOf(const sem::Type* ty);
-
-  /// @returns the semantic type of the AST literal `lit`
-  /// @param lit the literal
-  sem::Type* TypeOf(const ast::LiteralExpression* lit);
-
-  /// StatementScope() does the following:
-  /// * Creates the AST -> SEM mapping.
-  /// * Assigns `sem` to #current_statement_
-  /// * Assigns `sem` to #current_compound_statement_ if `sem` derives from
-  ///   sem::CompoundStatement.
-  /// * Assigns `sem` to #current_block_ if `sem` derives from
-  ///   sem::BlockStatement.
-  /// * Then calls `callback`.
-  /// * Before returning #current_statement_, #current_compound_statement_, and
-  ///   #current_block_ are restored to their original values.
-  /// @returns `sem` if `callback` returns true, otherwise `nullptr`.
-  template <typename SEM, typename F>
-  SEM* StatementScope(const ast::Statement* ast, SEM* sem, F&& callback);
-
-  /// Returns a human-readable string representation of the vector type name
-  /// with the given parameters.
-  /// @param size the vector dimension
-  /// @param element_type scalar vector sub-element type
-  /// @return pretty string representation
-  std::string VectorPretty(uint32_t size, const sem::Type* element_type);
-
-  /// Mark records that the given AST node has been visited, and asserts that
-  /// the given node has not already been seen. Diamonds in the AST are
-  /// illegal.
-  /// @param node the AST node.
-  /// @returns true on success, false on error
-  bool Mark(const ast::Node* node);
-
-  /// Adds the given error message to the diagnostics
-  void AddError(const std::string& msg, const Source& source) const;
-
-  /// Adds the given warning message to the diagnostics
-  void AddWarning(const std::string& msg, const Source& source) const;
-
-  /// Adds the given note message to the diagnostics
-  void AddNote(const std::string& msg, const Source& source) const;
-
-  //////////////////////////////////////////////////////////////////////////////
-  /// Constant value evaluation methods
-  //////////////////////////////////////////////////////////////////////////////
-  /// Cast `Value` to `target_type`
-  /// @return the casted value
-  sem::Constant ConstantCast(const sem::Constant& value,
-                             const sem::Type* target_elem_type);
-
-  sem::Constant EvaluateConstantValue(const ast::Expression* expr,
-                                      const sem::Type* type);
-  sem::Constant EvaluateConstantValue(const ast::LiteralExpression* literal,
-                                      const sem::Type* type);
-  sem::Constant EvaluateConstantValue(const ast::CallExpression* call,
-                                      const sem::Type* type);
-
-  /// Sem is a helper for obtaining the semantic node for the given AST node.
-  template <typename SEM = sem::Info::InferFromAST,
-            typename AST_OR_TYPE = CastableBase>
-  auto* Sem(const AST_OR_TYPE* ast) {
-    using T = sem::Info::GetResultType<SEM, AST_OR_TYPE>;
-    auto* sem = builder_->Sem().Get(ast);
-    if (!sem) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "AST node '" << ast->TypeInfo().name << "' had no semantic info\n"
-          << "At: " << ast->source << "\n"
-          << "Pointer: " << ast;
-    }
-    return const_cast<T*>(As<T>(sem));
-  }
-
-  /// @returns true if the symbol is the name of a builtin function.
-  bool IsBuiltin(Symbol) const;
-
-  /// @returns true if `expr` is the current CallStatement's CallExpression
-  bool IsCallStatement(const ast::Expression* expr) const;
-
-  /// Searches the current statement and up through parents of the current
-  /// statement looking for a loop or for-loop continuing statement.
-  /// @returns the closest continuing statement to the current statement that
-  /// (transitively) owns the current statement.
-  /// @param stop_at_loop if true then the function will return nullptr if a
-  /// loop or for-loop was found before the continuing.
-  const ast::Statement* ClosestContinuing(bool stop_at_loop) const;
-
-  /// @returns the resolved symbol (function, type or variable) for the given
-  /// ast::Identifier or ast::TypeName cast to the given semantic type.
-  template <typename SEM = sem::Node>
-  SEM* ResolvedSymbol(const ast::Node* node) {
-    auto* resolved = utils::Lookup(dependencies_.resolved_symbols, node);
-    return resolved ? const_cast<SEM*>(builder_->Sem().Get<SEM>(resolved))
-                    : nullptr;
-  }
-
-  struct TypeConversionSig {
-    const sem::Type* target;
-    const sem::Type* source;
-
-    bool operator==(const TypeConversionSig&) const;
-
-    /// Hasher provides a hash function for the TypeConversionSig
-    struct Hasher {
-      /// @param sig the TypeConversionSig to create a hash for
-      /// @return the hash value
-      std::size_t operator()(const TypeConversionSig& sig) const;
-    };
-  };
-
-  struct TypeConstructorSig {
-    const sem::Type* type;
-    const std::vector<const sem::Type*> parameters;
-
-    TypeConstructorSig(const sem::Type* ty,
-                       const std::vector<const sem::Type*> params);
-    TypeConstructorSig(const TypeConstructorSig&);
-    ~TypeConstructorSig();
-    bool operator==(const TypeConstructorSig&) const;
-
-    /// Hasher provides a hash function for the TypeConstructorSig
-    struct Hasher {
-      /// @param sig the TypeConstructorSig to create a hash for
-      /// @return the hash value
-      std::size_t operator()(const TypeConstructorSig& sig) const;
-    };
-  };
-
-  ProgramBuilder* const builder_;
-  diag::List& diagnostics_;
-  std::unique_ptr<BuiltinTable> const builtin_table_;
-  DependencyGraph dependencies_;
-  std::vector<sem::Function*> entry_points_;
-  std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
-  std::unordered_set<const ast::Node*> marked_;
-  std::unordered_map<uint32_t, const sem::Variable*> constant_ids_;
-  std::unordered_map<TypeConversionSig,
-                     sem::CallTarget*,
-                     TypeConversionSig::Hasher>
-      type_conversions_;
-  std::unordered_map<TypeConstructorSig,
-                     sem::CallTarget*,
-                     TypeConstructorSig::Hasher>
-      type_ctors_;
-
-  sem::Function* current_function_ = nullptr;
-  sem::Statement* current_statement_ = nullptr;
-  sem::CompoundStatement* current_compound_statement_ = nullptr;
-  sem::BlockStatement* current_block_ = nullptr;
-};
-
-}  // namespace resolver
-}  // namespace tint
-
-#endif  // SRC_RESOLVER_RESOLVER_H_
diff --git a/src/resolver/resolver_behavior_test.cc b/src/resolver/resolver_behavior_test.cc
deleted file mode 100644
index bcc2a17..0000000
--- a/src/resolver/resolver_behavior_test.cc
+++ /dev/null
@@ -1,659 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gtest/gtest.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/expression.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/if_statement.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-class ResolverBehaviorTest : public ResolverTest {
- protected:
-  void SetUp() override {
-    // Create a function called 'DiscardOrNext' which returns an i32, and has
-    // the behavior of {Discard, Return}, which when called, will have the
-    // behavior {Discard, Next}.
-    Func("DiscardOrNext", {}, ty.i32(),
-         {
-             If(true, Block(Discard())),
-             Return(1),
-         });
-  }
-};
-
-TEST_F(ResolverBehaviorTest, ExprBinaryOp_LHS) {
-  auto* stmt = Decl(Var("lhs", ty.i32(), Add(Call("DiscardOrNext"), 1)));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, ExprBinaryOp_RHS) {
-  auto* stmt = Decl(Var("lhs", ty.i32(), Add(1, Call("DiscardOrNext"))));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, ExprBitcastOp) {
-  auto* stmt = Decl(Var("lhs", ty.u32(), Bitcast<u32>(Call("DiscardOrNext"))));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, ExprIndex_Arr) {
-  Func("ArrayDiscardOrNext", {}, ty.array<i32, 4>(),
-       {
-           If(true, Block(Discard())),
-           Return(Construct(ty.array<i32, 4>())),
-       });
-
-  auto* stmt =
-      Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1)));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, ExprIndex_Idx) {
-  auto* stmt =
-      Decl(Var("lhs", ty.i32(), IndexAccessor("arr", Call("DiscardOrNext"))));
-  WrapInFunction(Decl(Var("arr", ty.array<i32, 4>())),  //
-                 stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, ExprUnaryOp) {
-  auto* stmt = Decl(Var("lhs", ty.i32(),
-                        create<ast::UnaryOpExpression>(
-                            ast::UnaryOp::kComplement, Call("DiscardOrNext"))));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtAssign) {
-  auto* stmt = Assign("lhs", "rhs");
-  WrapInFunction(Decl(Var("lhs", ty.i32())),  //
-                 Decl(Var("rhs", ty.i32())),  //
-                 stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtAssign_LHSDiscardOrNext) {
-  auto* stmt = Assign(IndexAccessor("lhs", Call("DiscardOrNext")), 1);
-  WrapInFunction(Decl(Var("lhs", ty.array<i32, 4>())),  //
-                 stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtAssign_RHSDiscardOrNext) {
-  auto* stmt = Assign("lhs", Call("DiscardOrNext"));
-  WrapInFunction(Decl(Var("lhs", ty.i32())),  //
-                 stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtBlockEmpty) {
-  auto* stmt = Block();
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtBlockSingleStmt) {
-  auto* stmt = Block(Discard());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtCallReturn) {
-  Func("f", {}, ty.void_(), {Return()});
-  auto* stmt = CallStmt(Call("f"));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtCallFuncDiscard) {
-  Func("f", {}, ty.void_(), {Discard()});
-  auto* stmt = CallStmt(Call("f"));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtCallFuncMayDiscard) {
-  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
-                   nullptr, Block(Break()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtBreak) {
-  auto* stmt = Break();
-  WrapInFunction(Loop(Block(stmt)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kBreak);
-}
-
-TEST_F(ResolverBehaviorTest, StmtContinue) {
-  auto* stmt = Continue();
-  WrapInFunction(Loop(Block(If(true, Block(Break())),  //
-                            stmt)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kContinue);
-}
-
-TEST_F(ResolverBehaviorTest, StmtDiscard) {
-  auto* stmt = Discard();
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_NoExit) {
-  auto* stmt = For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block());
-  WrapInFunction(stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopBreak) {
-  auto* stmt = For(nullptr, nullptr, nullptr, Block(Break()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopContinue_NoExit) {
-  auto* stmt =
-      For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block(Continue()));
-  WrapInFunction(stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopDiscard) {
-  auto* stmt = For(nullptr, nullptr, nullptr, Block(Discard()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopReturn) {
-  auto* stmt = For(nullptr, nullptr, nullptr, Block(Return()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopBreak_InitCallFuncMayDiscard) {
-  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
-                   nullptr, Block(Break()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_InitCallFuncMayDiscard) {
-  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
-                   nullptr, Block());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondTrue) {
-  auto* stmt = For(nullptr, true, nullptr, Block());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondCallFuncMayDiscard) {
-  auto* stmt = For(nullptr, Equal(Call("DiscardOrNext"), 1), nullptr, Block());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock) {
-  auto* stmt = If(true, Block());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard) {
-  auto* stmt = If(true, Block(Discard()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseDiscard) {
-  auto* stmt = If(true, Block(), Else(Block(Discard())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard_ElseDiscard) {
-  auto* stmt = If(true, Block(Discard()), Else(Block(Discard())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfCallFuncMayDiscard_ThenEmptyBlock) {
-  auto* stmt = If(Equal(Call("DiscardOrNext"), 1), Block());
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseCallFuncMayDiscard) {
-  auto* stmt = If(true, Block(),  //
-                  Else(Equal(Call("DiscardOrNext"), 1), Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtLetDecl) {
-  auto* stmt = Decl(Const("v", ty.i32(), Expr(1)));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtLetDecl_RHSDiscardOrNext) {
-  auto* stmt = Decl(Const("lhs", ty.i32(), Call("DiscardOrNext")));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopEmpty_NoExit) {
-  auto* stmt = Loop(Source{{12, 34}}, Block());
-  WrapInFunction(stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopBreak) {
-  auto* stmt = Loop(Block(Break()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopContinue_NoExit) {
-  auto* stmt = Loop(Source{{12, 34}}, Block(Continue()));
-  WrapInFunction(stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopDiscard) {
-  auto* stmt = Loop(Block(Discard()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopReturn) {
-  auto* stmt = Loop(Block(Return()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContEmpty_NoExit) {
-  auto* stmt = Loop(Source{{12, 34}}, Block(), Block());
-  WrapInFunction(stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
-}
-
-TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContIfTrueBreak) {
-  auto* stmt = Loop(Block(), Block(If(true, Block(Break()))));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtReturn) {
-  auto* stmt = Return();
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
-}
-
-TEST_F(ResolverBehaviorTest, StmtReturn_DiscardOrNext) {
-  auto* stmt = Return(Call("DiscardOrNext"));
-  Func("F", {}, ty.i32(), {stmt});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kReturn, sem::Behavior::kDiscard));
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondTrue_DefaultEmpty) {
-  auto* stmt = Switch(1, DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultEmpty) {
-  auto* stmt = Switch(1, DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultDiscard) {
-  auto* stmt = Switch(1, DefaultCase(Block(Discard())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultReturn) {
-  auto* stmt = Switch(1, DefaultCase(Block(Return())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultEmpty) {
-  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultDiscard) {
-  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Discard())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kDiscard));
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultReturn) {
-  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Return())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kReturn));
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultEmpty) {
-  auto* stmt = Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest,
-       StmtSwitch_CondLiteral_Case0Discard_DefaultDiscard) {
-  auto* stmt =
-      Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Discard())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
-}
-
-TEST_F(ResolverBehaviorTest,
-       StmtSwitch_CondLiteral_Case0Discard_DefaultReturn) {
-  auto* stmt =
-      Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Return())));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kReturn));
-}
-
-TEST_F(ResolverBehaviorTest,
-       StmtSwitch_CondLiteral_Case0Discard_Case1Return_DefaultEmpty) {
-  auto* stmt = Switch(1,                                //
-                      Case(Expr(0), Block(Discard())),  //
-                      Case(Expr(1), Block(Return())),   //
-                      DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext,
-                           sem::Behavior::kReturn));
-}
-
-TEST_F(ResolverBehaviorTest, StmtSwitch_CondCallFuncMayDiscard_DefaultEmpty) {
-  auto* stmt = Switch(Call("DiscardOrNext"), DefaultCase(Block()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-TEST_F(ResolverBehaviorTest, StmtVarDecl) {
-  auto* stmt = Decl(Var("v", ty.i32()));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
-}
-
-TEST_F(ResolverBehaviorTest, StmtVarDecl_RHSDiscardOrNext) {
-  auto* stmt = Decl(Var("lhs", ty.i32(), Call("DiscardOrNext")));
-  WrapInFunction(stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(stmt);
-  EXPECT_EQ(sem->Behaviors(),
-            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver_constants.cc b/src/resolver/resolver_constants.cc
deleted file mode 100644
index 0386239..0000000
--- a/src/resolver/resolver_constants.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "src/sem/constant.h"
-#include "src/sem/type_constructor.h"
-#include "src/utils/map.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using i32 = ProgramBuilder::i32;
-using u32 = ProgramBuilder::u32;
-using f32 = ProgramBuilder::f32;
-
-}  // namespace
-
-sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr,
-                                              const sem::Type* type) {
-  if (auto* e = expr->As<ast::LiteralExpression>()) {
-    return EvaluateConstantValue(e, type);
-  }
-  if (auto* e = expr->As<ast::CallExpression>()) {
-    return EvaluateConstantValue(e, type);
-  }
-  return {};
-}
-
-sem::Constant Resolver::EvaluateConstantValue(
-    const ast::LiteralExpression* literal,
-    const sem::Type* type) {
-  if (auto* lit = literal->As<ast::SintLiteralExpression>()) {
-    return {type, {lit->ValueAsI32()}};
-  }
-  if (auto* lit = literal->As<ast::UintLiteralExpression>()) {
-    return {type, {lit->ValueAsU32()}};
-  }
-  if (auto* lit = literal->As<ast::FloatLiteralExpression>()) {
-    return {type, {lit->value}};
-  }
-  if (auto* lit = literal->As<ast::BoolLiteralExpression>()) {
-    return {type, {lit->value}};
-  }
-  TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
-  return {};
-}
-
-sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
-                                              const sem::Type* type) {
-  auto* vec = type->As<sem::Vector>();
-
-  // For now, only fold scalars and vectors
-  if (!type->is_scalar() && !vec) {
-    return {};
-  }
-
-  auto* elem_type = vec ? vec->type() : type;
-  int result_size = vec ? static_cast<int>(vec->Width()) : 1;
-
-  // For zero value init, return 0s
-  if (call->args.empty()) {
-    if (elem_type->Is<sem::I32>()) {
-      return sem::Constant(type, sem::Constant::Scalars(result_size, 0));
-    }
-    if (elem_type->Is<sem::U32>()) {
-      return sem::Constant(type, sem::Constant::Scalars(result_size, 0u));
-    }
-    if (elem_type->Is<sem::F32>()) {
-      return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f));
-    }
-    if (elem_type->Is<sem::Bool>()) {
-      return sem::Constant(type, sem::Constant::Scalars(result_size, false));
-    }
-  }
-
-  // Build value for type_ctor from each child value by casting to
-  // type_ctor's type.
-  sem::Constant::Scalars elems;
-  for (auto* expr : call->args) {
-    auto* arg = builder_->Sem().Get(expr);
-    if (!arg || !arg->ConstantValue()) {
-      return {};
-    }
-    auto cast = ConstantCast(arg->ConstantValue(), elem_type);
-    elems.insert(elems.end(), cast.Elements().begin(), cast.Elements().end());
-  }
-
-  // Splat single-value initializers
-  if (elems.size() == 1) {
-    for (int i = 0; i < result_size - 1; ++i) {
-      elems.emplace_back(elems[0]);
-    }
-  }
-
-  return sem::Constant(type, std::move(elems));
-}
-
-sem::Constant Resolver::ConstantCast(const sem::Constant& value,
-                                     const sem::Type* target_elem_type) {
-  if (value.ElementType() == target_elem_type) {
-    return value;
-  }
-
-  sem::Constant::Scalars elems;
-  for (size_t i = 0; i < value.Elements().size(); ++i) {
-    if (target_elem_type->Is<sem::I32>()) {
-      elems.emplace_back(
-          value.WithScalarAt(i, [](auto&& s) { return static_cast<i32>(s); }));
-    } else if (target_elem_type->Is<sem::U32>()) {
-      elems.emplace_back(
-          value.WithScalarAt(i, [](auto&& s) { return static_cast<u32>(s); }));
-    } else if (target_elem_type->Is<sem::F32>()) {
-      elems.emplace_back(
-          value.WithScalarAt(i, [](auto&& s) { return static_cast<f32>(s); }));
-    } else if (target_elem_type->Is<sem::Bool>()) {
-      elems.emplace_back(
-          value.WithScalarAt(i, [](auto&& s) { return static_cast<bool>(s); }));
-    }
-  }
-
-  auto* target_type =
-      value.Type()->Is<sem::Vector>()
-          ? builder_->create<sem::Vector>(target_elem_type,
-                                          static_cast<uint32_t>(elems.size()))
-          : target_elem_type;
-
-  return sem::Constant(target_type, elems);
-}
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver_constants_test.cc b/src/resolver/resolver_constants_test.cc
deleted file mode 100644
index 512459d..0000000
--- a/src/resolver/resolver_constants_test.cc
+++ /dev/null
@@ -1,433 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gtest/gtest.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/expression.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using Scalar = sem::Constant::Scalar;
-
-using ResolverConstantsTest = ResolverTest;
-
-TEST_F(ResolverConstantsTest, Scalar_i32) {
-  auto* expr = Expr(99);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Type()->Is<sem::I32>());
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
-}
-
-TEST_F(ResolverConstantsTest, Scalar_u32) {
-  auto* expr = Expr(99u);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Type()->Is<sem::U32>());
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
-}
-
-TEST_F(ResolverConstantsTest, Scalar_f32) {
-  auto* expr = Expr(9.9f);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Type()->Is<sem::F32>());
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
-}
-
-TEST_F(ResolverConstantsTest, Scalar_bool) {
-  auto* expr = Expr(true);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Type()->Is<sem::Bool>());
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_ZeroInit_i32) {
-  auto* expr = vec3<i32>();
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 0);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 0);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 0);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_ZeroInit_u32) {
-  auto* expr = vec3<u32>();
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 0u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 0u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 0u);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) {
-  auto* expr = vec3<f32>();
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) {
-  auto* expr = vec3<bool>();
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, false);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, false);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Splat_i32) {
-  auto* expr = vec3<i32>(99);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 99);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 99);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Splat_u32) {
-  auto* expr = vec3<u32>(99u);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 99u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 99u);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Splat_f32) {
-  auto* expr = vec3<f32>(9.9f);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 9.9f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 9.9f);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Splat_bool) {
-  auto* expr = vec3<bool>(true);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, true);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_FullConstruct_i32) {
-  auto* expr = vec3<i32>(1, 2, 3);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_FullConstruct_u32) {
-  auto* expr = vec3<u32>(1u, 2u, 3u);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_FullConstruct_f32) {
-  auto* expr = vec3<f32>(1.f, 2.f, 3.f);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_FullConstruct_bool) {
-  auto* expr = vec3<bool>(true, false, true);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_MixConstruct_i32) {
-  auto* expr = vec3<i32>(1, vec2<i32>(2, 3));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_MixConstruct_u32) {
-  auto* expr = vec3<u32>(vec2<u32>(1u, 2u), 3u);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_MixConstruct_f32) {
-  auto* expr = vec3<f32>(1.f, vec2<f32>(2.f, 3.f));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
-  auto* expr = vec3<bool>(vec2<bool>(true, false), true);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_32) {
-  auto* expr = vec3<i32>(vec3<f32>(1.1f, 2.2f, 3.3f));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
-}
-
-TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
-  auto* expr = vec3<f32>(vec3<u32>(10u, 20u, 30u));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = Sem().Get(expr);
-  EXPECT_NE(sem, nullptr);
-  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
-  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
-  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
-  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 10.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 20.f);
-  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 30.f);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
deleted file mode 100644
index f4fd4e9..0000000
--- a/src/resolver/resolver_test.cc
+++ /dev/null
@@ -1,2189 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include <tuple>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest-spi.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/float_literal_expression.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-using ::testing::ElementsAre;
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace resolver {
-namespace {
-
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <int N, typename T>
-using vec = builder::vec<N, T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <int N, int M, typename T>
-using mat = builder::mat<N, M, T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat2x3 = builder::mat2x3<T>;
-template <typename T>
-using mat3x2 = builder::mat3x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <typename T, int ID = 0>
-using alias = builder::alias<T, ID>;
-template <typename T>
-using alias1 = builder::alias1<T>;
-template <typename T>
-using alias2 = builder::alias2<T>;
-template <typename T>
-using alias3 = builder::alias3<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-using Op = ast::BinaryOp;
-
-TEST_F(ResolverTest, Stmt_Assign) {
-  auto* v = Var("v", ty.f32());
-  auto* lhs = Expr("v");
-  auto* rhs = Expr(2.3f);
-
-  auto* assign = Assign(lhs, rhs);
-  WrapInFunction(v, assign);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-
-  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(lhs), assign);
-  EXPECT_EQ(StmtOf(rhs), assign);
-}
-
-TEST_F(ResolverTest, Stmt_Case) {
-  auto* v = Var("v", ty.f32());
-  auto* lhs = Expr("v");
-  auto* rhs = Expr(2.3f);
-
-  auto* assign = Assign(lhs, rhs);
-  auto* block = Block(assign);
-  ast::CaseSelectorList lit;
-  lit.push_back(create<ast::SintLiteralExpression>(3));
-  auto* cse = create<ast::CaseStatement>(lit, block);
-  auto* cond_var = Var("c", ty.i32());
-  auto* sw = Switch(cond_var, cse, DefaultCase());
-  WrapInFunction(v, cond_var, sw);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(lhs), assign);
-  EXPECT_EQ(StmtOf(rhs), assign);
-  EXPECT_EQ(BlockOf(assign), block);
-}
-
-TEST_F(ResolverTest, Stmt_Block) {
-  auto* v = Var("v", ty.f32());
-  auto* lhs = Expr("v");
-  auto* rhs = Expr(2.3f);
-
-  auto* assign = Assign(lhs, rhs);
-  auto* block = Block(assign);
-  WrapInFunction(v, block);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(lhs), assign);
-  EXPECT_EQ(StmtOf(rhs), assign);
-  EXPECT_EQ(BlockOf(lhs), block);
-  EXPECT_EQ(BlockOf(rhs), block);
-  EXPECT_EQ(BlockOf(assign), block);
-}
-
-TEST_F(ResolverTest, Stmt_If) {
-  auto* v = Var("v", ty.f32());
-  auto* else_lhs = Expr("v");
-  auto* else_rhs = Expr(2.3f);
-
-  auto* else_body = Block(Assign(else_lhs, else_rhs));
-
-  auto* else_cond = Expr(true);
-  auto* else_stmt = create<ast::ElseStatement>(else_cond, else_body);
-
-  auto* lhs = Expr("v");
-  auto* rhs = Expr(2.3f);
-
-  auto* assign = Assign(lhs, rhs);
-  auto* body = Block(assign);
-  auto* cond = Expr(true);
-  auto* stmt =
-      create<ast::IfStatement>(cond, body, ast::ElseStatementList{else_stmt});
-  WrapInFunction(v, stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(stmt->condition), nullptr);
-  ASSERT_NE(TypeOf(else_lhs), nullptr);
-  ASSERT_NE(TypeOf(else_rhs), nullptr);
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-  EXPECT_TRUE(TypeOf(stmt->condition)->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(else_lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(else_rhs)->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(lhs), assign);
-  EXPECT_EQ(StmtOf(rhs), assign);
-  EXPECT_EQ(StmtOf(cond), stmt);
-  EXPECT_EQ(StmtOf(else_cond), else_stmt);
-  EXPECT_EQ(BlockOf(lhs), body);
-  EXPECT_EQ(BlockOf(rhs), body);
-  EXPECT_EQ(BlockOf(else_lhs), else_body);
-  EXPECT_EQ(BlockOf(else_rhs), else_body);
-}
-
-TEST_F(ResolverTest, Stmt_Loop) {
-  auto* v = Var("v", ty.f32());
-  auto* body_lhs = Expr("v");
-  auto* body_rhs = Expr(2.3f);
-
-  auto* body = Block(Assign(body_lhs, body_rhs), Break());
-  auto* continuing_lhs = Expr("v");
-  auto* continuing_rhs = Expr(2.3f);
-
-  auto* continuing = Block(Assign(continuing_lhs, continuing_rhs));
-  auto* stmt = Loop(body, continuing);
-  WrapInFunction(v, stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(body_lhs), nullptr);
-  ASSERT_NE(TypeOf(body_rhs), nullptr);
-  ASSERT_NE(TypeOf(continuing_lhs), nullptr);
-  ASSERT_NE(TypeOf(continuing_rhs), nullptr);
-  EXPECT_TRUE(TypeOf(body_lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(body_rhs)->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(continuing_lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(continuing_rhs)->Is<sem::F32>());
-  EXPECT_EQ(BlockOf(body_lhs), body);
-  EXPECT_EQ(BlockOf(body_rhs), body);
-  EXPECT_EQ(BlockOf(continuing_lhs), continuing);
-  EXPECT_EQ(BlockOf(continuing_rhs), continuing);
-}
-
-TEST_F(ResolverTest, Stmt_Return) {
-  auto* cond = Expr(2);
-
-  auto* ret = Return(cond);
-  Func("test", {}, ty.i32(), {ret}, {});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(cond), nullptr);
-  EXPECT_TRUE(TypeOf(cond)->Is<sem::I32>());
-}
-
-TEST_F(ResolverTest, Stmt_Return_WithoutValue) {
-  auto* ret = Return();
-  WrapInFunction(ret);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_Switch) {
-  auto* v = Var("v", ty.f32());
-  auto* lhs = Expr("v");
-  auto* rhs = Expr(2.3f);
-  auto* case_block = Block(Assign(lhs, rhs));
-  auto* stmt = Switch(Expr(2), Case(Expr(3), case_block), DefaultCase());
-  WrapInFunction(v, stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(stmt->condition), nullptr);
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-
-  EXPECT_TRUE(TypeOf(stmt->condition)->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
-  EXPECT_EQ(BlockOf(lhs), case_block);
-  EXPECT_EQ(BlockOf(rhs), case_block);
-}
-
-TEST_F(ResolverTest, Stmt_Call) {
-  ast::VariableList params;
-  Func("my_func", params, ty.void_(), {Return()}, ast::AttributeList{});
-
-  auto* expr = Call("my_func");
-
-  auto* call = CallStmt(expr);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::Void>());
-  EXPECT_EQ(StmtOf(expr), call);
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl) {
-  auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  auto* init = var->constructor;
-
-  auto* decl = Decl(var);
-  WrapInFunction(decl);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(init), nullptr);
-  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl_Alias) {
-  auto* my_int = Alias("MyInt", ty.i32());
-  auto* var = Var("my_var", ty.Of(my_int), ast::StorageClass::kNone, Expr(2));
-  auto* init = var->constructor;
-
-  auto* decl = Decl(var);
-  WrapInFunction(decl);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(init), nullptr);
-  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScope) {
-  auto* init = Expr(2);
-  Global("my_var", ty.i32(), ast::StorageClass::kPrivate, init);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(init), nullptr);
-  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
-  EXPECT_EQ(StmtOf(init), nullptr);
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
-  // fn func_i32() {
-  //   {
-  //     var foo : i32 = 2;
-  //     var bar : i32 = foo;
-  //   }
-  //   var foo : f32 = 2.0;
-  //   var bar : f32 = foo;
-  // }
-
-  ast::VariableList params;
-
-  // Declare i32 "foo" inside a block
-  auto* foo_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  auto* foo_i32_init = foo_i32->constructor;
-  auto* foo_i32_decl = Decl(foo_i32);
-
-  // Reference "foo" inside the block
-  auto* bar_i32 = Var("bar", ty.i32(), ast::StorageClass::kNone, Expr("foo"));
-  auto* bar_i32_init = bar_i32->constructor;
-  auto* bar_i32_decl = Decl(bar_i32);
-
-  auto* inner = Block(foo_i32_decl, bar_i32_decl);
-
-  // Declare f32 "foo" at function scope
-  auto* foo_f32 = Var("foo", ty.f32(), ast::StorageClass::kNone, Expr(2.f));
-  auto* foo_f32_init = foo_f32->constructor;
-  auto* foo_f32_decl = Decl(foo_f32);
-
-  // Reference "foo" at function scope
-  auto* bar_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
-  auto* bar_f32_init = bar_f32->constructor;
-  auto* bar_f32_decl = Decl(bar_f32);
-
-  Func("func", params, ty.void_(), {inner, foo_f32_decl, bar_f32_decl},
-       ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(foo_i32_init), nullptr);
-  EXPECT_TRUE(TypeOf(foo_i32_init)->Is<sem::I32>());
-  ASSERT_NE(TypeOf(foo_f32_init), nullptr);
-  EXPECT_TRUE(TypeOf(foo_f32_init)->Is<sem::F32>());
-  ASSERT_NE(TypeOf(bar_i32_init), nullptr);
-  EXPECT_TRUE(TypeOf(bar_i32_init)->UnwrapRef()->Is<sem::I32>());
-  ASSERT_NE(TypeOf(bar_f32_init), nullptr);
-  EXPECT_TRUE(TypeOf(bar_f32_init)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(foo_i32_init), foo_i32_decl);
-  EXPECT_EQ(StmtOf(bar_i32_init), bar_i32_decl);
-  EXPECT_EQ(StmtOf(foo_f32_init), foo_f32_decl);
-  EXPECT_EQ(StmtOf(bar_f32_init), bar_f32_decl);
-  EXPECT_TRUE(CheckVarUsers(foo_i32, {bar_i32->constructor}));
-  EXPECT_TRUE(CheckVarUsers(foo_f32, {bar_f32->constructor}));
-  ASSERT_NE(VarOf(bar_i32->constructor), nullptr);
-  EXPECT_EQ(VarOf(bar_i32->constructor)->Declaration(), foo_i32);
-  ASSERT_NE(VarOf(bar_f32->constructor), nullptr);
-  EXPECT_EQ(VarOf(bar_f32->constructor)->Declaration(), foo_f32);
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
-  // fn func_i32() {
-  //   var foo : i32 = 2;
-  // }
-  // var foo : f32 = 2.0;
-  // fn func_f32() {
-  //   var bar : f32 = foo;
-  // }
-
-  ast::VariableList params;
-
-  // Declare i32 "foo" inside a function
-  auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
-  auto* fn_i32_init = fn_i32->constructor;
-  auto* fn_i32_decl = Decl(fn_i32);
-  Func("func_i32", params, ty.void_(), {fn_i32_decl}, ast::AttributeList{});
-
-  // Declare f32 "foo" at module scope
-  auto* mod_f32 = Var("foo", ty.f32(), ast::StorageClass::kPrivate, Expr(2.f));
-  auto* mod_init = mod_f32->constructor;
-  AST().AddGlobalVariable(mod_f32);
-
-  // Reference "foo" in another function
-  auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
-  auto* fn_f32_init = fn_f32->constructor;
-  auto* fn_f32_decl = Decl(fn_f32);
-  Func("func_f32", params, ty.void_(), {fn_f32_decl}, ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(mod_init), nullptr);
-  EXPECT_TRUE(TypeOf(mod_init)->Is<sem::F32>());
-  ASSERT_NE(TypeOf(fn_i32_init), nullptr);
-  EXPECT_TRUE(TypeOf(fn_i32_init)->Is<sem::I32>());
-  ASSERT_NE(TypeOf(fn_f32_init), nullptr);
-  EXPECT_TRUE(TypeOf(fn_f32_init)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(fn_i32_init), fn_i32_decl);
-  EXPECT_EQ(StmtOf(mod_init), nullptr);
-  EXPECT_EQ(StmtOf(fn_f32_init), fn_f32_decl);
-  EXPECT_TRUE(CheckVarUsers(fn_i32, {}));
-  EXPECT_TRUE(CheckVarUsers(mod_f32, {fn_f32->constructor}));
-  ASSERT_NE(VarOf(fn_f32->constructor), nullptr);
-  EXPECT_EQ(VarOf(fn_f32->constructor)->Declaration(), mod_f32);
-}
-
-TEST_F(ResolverTest, ArraySize_UnsignedLiteral) {
-  // var<private> a : array<f32, 10u>;
-  auto* a =
-      Global("a", ty.array(ty.f32(), Expr(10u)), ast::StorageClass::kPrivate);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(a), nullptr);
-  auto* ref = TypeOf(a)->As<sem::Reference>();
-  ASSERT_NE(ref, nullptr);
-  auto* ary = ref->StoreType()->As<sem::Array>();
-  EXPECT_EQ(ary->Count(), 10u);
-}
-
-TEST_F(ResolverTest, ArraySize_SignedLiteral) {
-  // var<private> a : array<f32, 10>;
-  auto* a =
-      Global("a", ty.array(ty.f32(), Expr(10)), ast::StorageClass::kPrivate);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(a), nullptr);
-  auto* ref = TypeOf(a)->As<sem::Reference>();
-  ASSERT_NE(ref, nullptr);
-  auto* ary = ref->StoreType()->As<sem::Array>();
-  EXPECT_EQ(ary->Count(), 10u);
-}
-
-TEST_F(ResolverTest, ArraySize_UnsignedConstant) {
-  // let size = 0u;
-  // var<private> a : array<f32, 10u>;
-  GlobalConst("size", nullptr, Expr(10u));
-  auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
-                   ast::StorageClass::kPrivate);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(a), nullptr);
-  auto* ref = TypeOf(a)->As<sem::Reference>();
-  ASSERT_NE(ref, nullptr);
-  auto* ary = ref->StoreType()->As<sem::Array>();
-  EXPECT_EQ(ary->Count(), 10u);
-}
-
-TEST_F(ResolverTest, ArraySize_SignedConstant) {
-  // let size = 0;
-  // var<private> a : array<f32, 10>;
-  GlobalConst("size", nullptr, Expr(10));
-  auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
-                   ast::StorageClass::kPrivate);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(a), nullptr);
-  auto* ref = TypeOf(a)->As<sem::Reference>();
-  ASSERT_NE(ref, nullptr);
-  auto* ary = ref->StoreType()->As<sem::Array>();
-  EXPECT_EQ(ary->Count(), 10u);
-}
-
-TEST_F(ResolverTest, Expr_Bitcast) {
-  Global("name", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr("name"));
-  WrapInFunction(bitcast);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(bitcast), nullptr);
-  EXPECT_TRUE(TypeOf(bitcast)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Call) {
-  ast::VariableList params;
-  Func("my_func", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
-  ast::VariableList params;
-  Func("func", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
-
-  auto* expr = Add(Call("func"), Call("func"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Call_WithParams) {
-  Func("my_func", {Param(Sym(), ty.f32())}, ty.f32(),
-       {
-           Return(1.2f),
-       });
-
-  auto* param = Expr(2.4f);
-
-  auto* call = Call("my_func", param);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(param), nullptr);
-  EXPECT_TRUE(TypeOf(param)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Call_Builtin) {
-  auto* call = Call("round", 2.4f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Cast) {
-  Global("name", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* cast = Construct(ty.f32(), "name");
-  WrapInFunction(cast);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(cast), nullptr);
-  EXPECT_TRUE(TypeOf(cast)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Constructor_Scalar) {
-  auto* s = Expr(1.0f);
-  WrapInFunction(s);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(s), nullptr);
-  EXPECT_TRUE(TypeOf(s)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Constructor_Type_Vec2) {
-  auto* tc = vec2<f32>(1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-}
-
-TEST_F(ResolverTest, Expr_Constructor_Type_Vec3) {
-  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-}
-
-TEST_F(ResolverTest, Expr_Constructor_Type_Vec4) {
-  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTest, Expr_Identifier_GlobalVariable) {
-  auto* my_var = Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* ident = Expr("my_var");
-  WrapInFunction(ident);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(ident), nullptr);
-  ASSERT_TRUE(TypeOf(ident)->Is<sem::Reference>());
-  EXPECT_TRUE(TypeOf(ident)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
-  ASSERT_NE(VarOf(ident), nullptr);
-  EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
-}
-
-TEST_F(ResolverTest, Expr_Identifier_GlobalConstant) {
-  auto* my_var = GlobalConst("my_var", ty.f32(), Construct(ty.f32()));
-
-  auto* ident = Expr("my_var");
-  WrapInFunction(ident);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(ident), nullptr);
-  EXPECT_TRUE(TypeOf(ident)->Is<sem::F32>());
-  EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
-  ASSERT_NE(VarOf(ident), nullptr);
-  EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
-}
-
-TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
-  auto* my_var_a = Expr("my_var");
-  auto* var = Const("my_var", ty.f32(), Construct(ty.f32()));
-  auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kNone, my_var_a));
-
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           decl,
-       },
-       ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(my_var_a), nullptr);
-  EXPECT_TRUE(TypeOf(my_var_a)->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(my_var_a), decl);
-  EXPECT_TRUE(CheckVarUsers(var, {my_var_a}));
-  ASSERT_NE(VarOf(my_var_a), nullptr);
-  EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
-}
-
-TEST_F(ResolverTest, IndexAccessor_Dynamic_Ref_F32) {
-  // var a : array<bool, 10> = 0;
-  // var idx : f32 = f32();
-  // var f : f32 = a[idx];
-  auto* a = Var("a", ty.array<bool, 10>(), array<bool, 10>());
-  auto* idx = Var("idx", ty.f32(), Construct(ty.f32()));
-  auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(idx),
-           Decl(f),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
-}
-
-TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {
-  auto* my_var_a = Expr("my_var");
-  auto* my_var_b = Expr("my_var");
-  auto* assign = Assign(my_var_a, my_var_b);
-
-  auto* var = Var("my_var", ty.f32());
-
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           assign,
-       },
-       ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(my_var_a), nullptr);
-  ASSERT_TRUE(TypeOf(my_var_a)->Is<sem::Reference>());
-  EXPECT_TRUE(TypeOf(my_var_a)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(my_var_a), assign);
-  ASSERT_NE(TypeOf(my_var_b), nullptr);
-  ASSERT_TRUE(TypeOf(my_var_b)->Is<sem::Reference>());
-  EXPECT_TRUE(TypeOf(my_var_b)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(my_var_b), assign);
-  EXPECT_TRUE(CheckVarUsers(var, {my_var_a, my_var_b}));
-  ASSERT_NE(VarOf(my_var_a), nullptr);
-  EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
-  ASSERT_NE(VarOf(my_var_b), nullptr);
-  EXPECT_EQ(VarOf(my_var_b)->Declaration(), var);
-}
-
-TEST_F(ResolverTest, Expr_Identifier_Function_Ptr) {
-  auto* v = Expr("v");
-  auto* p = Expr("p");
-  auto* v_decl = Decl(Var("v", ty.f32()));
-  auto* p_decl = Decl(
-      Const("p", ty.pointer<f32>(ast::StorageClass::kFunction), AddressOf(v)));
-  auto* assign = Assign(Deref(p), 1.23f);
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           v_decl,
-           p_decl,
-           assign,
-       },
-       ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(v), nullptr);
-  ASSERT_TRUE(TypeOf(v)->Is<sem::Reference>());
-  EXPECT_TRUE(TypeOf(v)->UnwrapRef()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(v), p_decl);
-  ASSERT_NE(TypeOf(p), nullptr);
-  ASSERT_TRUE(TypeOf(p)->Is<sem::Pointer>());
-  EXPECT_TRUE(TypeOf(p)->UnwrapPtr()->Is<sem::F32>());
-  EXPECT_EQ(StmtOf(p), assign);
-}
-
-TEST_F(ResolverTest, Expr_Call_Function) {
-  Func("my_func", ast::VariableList{}, ty.f32(), {Return(0.0f)},
-       ast::AttributeList{});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
-}
-
-TEST_F(ResolverTest, Expr_Identifier_Unknown) {
-  auto* a = Expr("a");
-  WrapInFunction(a);
-
-  EXPECT_FALSE(r()->Resolve());
-}
-
-TEST_F(ResolverTest, Function_Parameters) {
-  auto* param_a = Param("a", ty.f32());
-  auto* param_b = Param("b", ty.i32());
-  auto* param_c = Param("c", ty.u32());
-
-  auto* func = Func("my_func",
-                    ast::VariableList{
-                        param_a,
-                        param_b,
-                        param_c,
-                    },
-                    ty.void_(), {});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-  EXPECT_EQ(func_sem->Parameters().size(), 3u);
-  EXPECT_TRUE(func_sem->Parameters()[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(func_sem->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(func_sem->Parameters()[2]->Type()->Is<sem::U32>());
-  EXPECT_EQ(func_sem->Parameters()[0]->Declaration(), param_a);
-  EXPECT_EQ(func_sem->Parameters()[1]->Declaration(), param_b);
-  EXPECT_EQ(func_sem->Parameters()[2]->Declaration(), param_c);
-  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
-}
-
-TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
-  auto* s = Structure("S", {Member("m", ty.u32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
-                        ast::Access::kReadWrite,
-                        ast::AttributeList{
-                            create<ast::BindingAttribute>(0),
-                            create<ast::GroupAttribute>(0),
-                        });
-  auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    {
-                        Assign("wg_var", "wg_var"),
-                        Assign("sb_var", "sb_var"),
-                        Assign("priv_var", "priv_var"),
-                    });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-  EXPECT_EQ(func_sem->Parameters().size(), 0u);
-  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
-
-  const auto& vars = func_sem->TransitivelyReferencedGlobals();
-  ASSERT_EQ(vars.size(), 3u);
-  EXPECT_EQ(vars[0]->Declaration(), wg_var);
-  EXPECT_EQ(vars[1]->Declaration(), sb_var);
-  EXPECT_EQ(vars[2]->Declaration(), priv_var);
-}
-
-TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
-  auto* s = Structure("S", {Member("m", ty.u32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
-                        ast::Access::kReadWrite,
-                        ast::AttributeList{
-                            create<ast::BindingAttribute>(0),
-                            create<ast::GroupAttribute>(0),
-                        });
-  auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  Func("my_func", ast::VariableList{}, ty.f32(),
-       {Assign("wg_var", "wg_var"), Assign("sb_var", "sb_var"),
-        Assign("priv_var", "priv_var"), Return(0.0f)},
-       ast::AttributeList{});
-
-  auto* func2 = Func("func", ast::VariableList{}, ty.void_(),
-                     {
-                         WrapInStatement(Call("my_func")),
-                     },
-                     ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func2_sem = Sem().Get(func2);
-  ASSERT_NE(func2_sem, nullptr);
-  EXPECT_EQ(func2_sem->Parameters().size(), 0u);
-
-  const auto& vars = func2_sem->TransitivelyReferencedGlobals();
-  ASSERT_EQ(vars.size(), 3u);
-  EXPECT_EQ(vars[0]->Declaration(), wg_var);
-  EXPECT_EQ(vars[1]->Declaration(), sb_var);
-  EXPECT_EQ(vars[2]->Declaration(), priv_var);
-}
-
-TEST_F(ResolverTest, Function_NotRegisterFunctionVariable) {
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    {
-                        Decl(Var("var", ty.f32())),
-                        Assign("var", 1.f),
-                    });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
-  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
-}
-
-TEST_F(ResolverTest, Function_NotRegisterFunctionConstant) {
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    {
-                        Decl(Const("var", ty.f32(), Construct(ty.f32()))),
-                    });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
-  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
-}
-
-TEST_F(ResolverTest, Function_NotRegisterFunctionParams) {
-  auto* func = Func("my_func", {Const("var", ty.f32(), Construct(ty.f32()))},
-                    ty.void_(), {});
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
-  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
-}
-
-TEST_F(ResolverTest, Function_CallSites) {
-  auto* foo = Func("foo", ast::VariableList{}, ty.void_(), {});
-
-  auto* call_1 = Call("foo");
-  auto* call_2 = Call("foo");
-  auto* bar = Func("bar", ast::VariableList{}, ty.void_(),
-                   {
-                       CallStmt(call_1),
-                       CallStmt(call_2),
-                   });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* foo_sem = Sem().Get(foo);
-  ASSERT_NE(foo_sem, nullptr);
-  ASSERT_EQ(foo_sem->CallSites().size(), 2u);
-  EXPECT_EQ(foo_sem->CallSites()[0]->Declaration(), call_1);
-  EXPECT_EQ(foo_sem->CallSites()[1]->Declaration(), call_2);
-
-  auto* bar_sem = Sem().Get(bar);
-  ASSERT_NE(bar_sem, nullptr);
-  EXPECT_EQ(bar_sem->CallSites().size(), 0u);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_NotSet) {
-  // @stage(compute) @workgroup_size(1)
-  // fn main() {}
-  auto* func = Func("main", ast::VariableList{}, ty.void_(), {}, {});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 1u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 1u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 1u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_Literals) {
-  // @stage(compute) @workgroup_size(8, 2, 3)
-  // fn main() {}
-  auto* func =
-      Func("main", ast::VariableList{}, ty.void_(), {},
-           {Stage(ast::PipelineStage::kCompute), WorkgroupSize(8, 2, 3)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 2u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 3u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_Consts) {
-  // let width = 16;
-  // let height = 8;
-  // let depth = 2;
-  // @stage(compute) @workgroup_size(width, height, depth)
-  // fn main() {}
-  GlobalConst("width", ty.i32(), Expr(16));
-  GlobalConst("height", ty.i32(), Expr(8));
-  GlobalConst("depth", ty.i32(), Expr(2));
-  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
-                    {Stage(ast::PipelineStage::kCompute),
-                     WorkgroupSize("width", "height", "depth")});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 16u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 8u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 2u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_Consts_NestedInitializer) {
-  // let width = i32(i32(i32(8)));
-  // let height = i32(i32(i32(4)));
-  // @stage(compute) @workgroup_size(width, height)
-  // fn main() {}
-  GlobalConst("width", ty.i32(),
-              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 8))));
-  GlobalConst("height", ty.i32(),
-              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 4))));
-  auto* func = Func(
-      "main", ast::VariableList{}, ty.void_(), {},
-      {Stage(ast::PipelineStage::kCompute), WorkgroupSize("width", "height")});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 4u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 1u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_OverridableConsts) {
-  // @id(0) override width = 16;
-  // @id(1) override height = 8;
-  // @id(2) override depth = 2;
-  // @stage(compute) @workgroup_size(width, height, depth)
-  // fn main() {}
-  auto* width = Override("width", ty.i32(), Expr(16), {Id(0)});
-  auto* height = Override("height", ty.i32(), Expr(8), {Id(1)});
-  auto* depth = Override("depth", ty.i32(), Expr(2), {Id(2)});
-  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
-                    {Stage(ast::PipelineStage::kCompute),
-                     WorkgroupSize("width", "height", "depth")});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 16u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 8u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 2u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, width);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, depth);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_OverridableConsts_NoInit) {
-  // @id(0) override width : i32;
-  // @id(1) override height : i32;
-  // @id(2) override depth : i32;
-  // @stage(compute) @workgroup_size(width, height, depth)
-  // fn main() {}
-  auto* width = Override("width", ty.i32(), nullptr, {Id(0)});
-  auto* height = Override("height", ty.i32(), nullptr, {Id(1)});
-  auto* depth = Override("depth", ty.i32(), nullptr, {Id(2)});
-  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
-                    {Stage(ast::PipelineStage::kCompute),
-                     WorkgroupSize("width", "height", "depth")});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 0u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 0u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 0u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, width);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, depth);
-}
-
-TEST_F(ResolverTest, Function_WorkgroupSize_Mixed) {
-  // @id(1) override height = 2;
-  // let depth = 3;
-  // @stage(compute) @workgroup_size(8, height, depth)
-  // fn main() {}
-  auto* height = Override("height", ty.i32(), Expr(2), {Id(0)});
-  GlobalConst("depth", ty.i32(), Expr(3));
-  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
-                    {Stage(ast::PipelineStage::kCompute),
-                     WorkgroupSize(8, "height", "depth")});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_sem = Sem().Get(func);
-  ASSERT_NE(func_sem, nullptr);
-
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 2u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 3u);
-  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
-  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
-  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
-  auto* st = Structure("S", {Member("first_member", ty.i32()),
-                             Member("second_member", ty.f32())});
-  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
-
-  auto* mem = MemberAccessor("my_struct", "second_member");
-  WrapInFunction(mem);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(mem), nullptr);
-  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(mem)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-  auto* sma = Sem().Get(mem)->As<sem::StructMemberAccess>();
-  ASSERT_NE(sma, nullptr);
-  EXPECT_TRUE(sma->Member()->Type()->Is<sem::F32>());
-  EXPECT_EQ(sma->Member()->Index(), 1u);
-  EXPECT_EQ(sma->Member()->Declaration()->symbol,
-            Symbols().Get("second_member"));
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
-  auto* st = Structure("S", {Member("first_member", ty.i32()),
-                             Member("second_member", ty.f32())});
-  auto* alias = Alias("alias", ty.Of(st));
-  Global("my_struct", ty.Of(alias), ast::StorageClass::kPrivate);
-
-  auto* mem = MemberAccessor("my_struct", "second_member");
-  WrapInFunction(mem);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(mem), nullptr);
-  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(mem)->As<sem::Reference>();
-  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
-  auto* sma = Sem().Get(mem)->As<sem::StructMemberAccess>();
-  ASSERT_NE(sma, nullptr);
-  EXPECT_TRUE(sma->Member()->Type()->Is<sem::F32>());
-  EXPECT_EQ(sma->Member()->Index(), 1u);
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* mem = MemberAccessor("my_vec", "xzyw");
-  WrapInFunction(mem);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(mem), nullptr);
-  ASSERT_TRUE(TypeOf(mem)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(mem)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(mem)->As<sem::Vector>()->Width(), 4u);
-  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
-  EXPECT_THAT(Sem().Get(mem)->As<sem::Swizzle>()->Indices(),
-              ElementsAre(0, 2, 1, 3));
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_SingleElement) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* mem = MemberAccessor("my_vec", "b");
-  WrapInFunction(mem);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(mem), nullptr);
-  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
-
-  auto* ref = TypeOf(mem)->As<sem::Reference>();
-  ASSERT_TRUE(ref->StoreType()->Is<sem::F32>());
-  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
-  EXPECT_THAT(Sem().Get(mem)->As<sem::Swizzle>()->Indices(), ElementsAre(2));
-}
-
-TEST_F(ResolverTest, Expr_Accessor_MultiLevel) {
-  // struct b {
-  //   vec4<f32> foo
-  // }
-  // struct A {
-  //   array<b, 3> mem
-  // }
-  // var c : A
-  // c.mem[0].foo.yx
-  //   -> vec2<f32>
-  //
-  // fn f() {
-  //   c.mem[0].foo
-  // }
-  //
-
-  auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
-  auto* stA = Structure("A", {Member("mem", ty.array(ty.Of(stB), 3))});
-  Global("c", ty.Of(stA), ast::StorageClass::kPrivate);
-
-  auto* mem = MemberAccessor(
-      MemberAccessor(IndexAccessor(MemberAccessor("c", "mem"), 0), "foo"),
-      "yx");
-  WrapInFunction(mem);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(mem), nullptr);
-  ASSERT_TRUE(TypeOf(mem)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(mem)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(mem)->As<sem::Vector>()->Width(), 2u);
-  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
-  auto* st = Structure("S", {Member("first_member", ty.f32()),
-                             Member("second_member", ty.f32())});
-  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
-
-  auto* expr = Add(MemberAccessor("my_struct", "first_member"),
-                   MemberAccessor("my_struct", "second_member"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
-}
-
-namespace ExprBinaryTest {
-
-template <typename T, int ID>
-struct Aliased {
-  using type = alias<T, ID>;
-};
-
-template <int N, typename T, int ID>
-struct Aliased<vec<N, T>, ID> {
-  using type = vec<N, alias<T, ID>>;
-};
-
-template <int N, int M, typename T, int ID>
-struct Aliased<mat<N, M, T>, ID> {
-  using type = mat<N, M, alias<T, ID>>;
-};
-
-struct Params {
-  ast::BinaryOp op;
-  builder::ast_type_func_ptr create_lhs_type;
-  builder::ast_type_func_ptr create_rhs_type;
-  builder::ast_type_func_ptr create_lhs_alias_type;
-  builder::ast_type_func_ptr create_rhs_alias_type;
-  builder::sem_type_func_ptr create_result_type;
-};
-
-template <typename LHS, typename RHS, typename RES>
-constexpr Params ParamsFor(ast::BinaryOp op) {
-  return Params{op,
-                DataType<LHS>::AST,
-                DataType<RHS>::AST,
-                DataType<typename Aliased<LHS, 0>::type>::AST,
-                DataType<typename Aliased<RHS, 1>::type>::AST,
-                DataType<RES>::Sem};
-}
-
-static constexpr ast::BinaryOp all_ops[] = {
-    ast::BinaryOp::kAnd,
-    ast::BinaryOp::kOr,
-    ast::BinaryOp::kXor,
-    ast::BinaryOp::kLogicalAnd,
-    ast::BinaryOp::kLogicalOr,
-    ast::BinaryOp::kEqual,
-    ast::BinaryOp::kNotEqual,
-    ast::BinaryOp::kLessThan,
-    ast::BinaryOp::kGreaterThan,
-    ast::BinaryOp::kLessThanEqual,
-    ast::BinaryOp::kGreaterThanEqual,
-    ast::BinaryOp::kShiftLeft,
-    ast::BinaryOp::kShiftRight,
-    ast::BinaryOp::kAdd,
-    ast::BinaryOp::kSubtract,
-    ast::BinaryOp::kMultiply,
-    ast::BinaryOp::kDivide,
-    ast::BinaryOp::kModulo,
-};
-
-static constexpr builder::ast_type_func_ptr all_create_type_funcs[] = {
-    DataType<bool>::AST,         //
-    DataType<u32>::AST,          //
-    DataType<i32>::AST,          //
-    DataType<f32>::AST,          //
-    DataType<vec3<bool>>::AST,   //
-    DataType<vec3<i32>>::AST,    //
-    DataType<vec3<u32>>::AST,    //
-    DataType<vec3<f32>>::AST,    //
-    DataType<mat3x3<f32>>::AST,  //
-    DataType<mat2x3<f32>>::AST,  //
-    DataType<mat3x2<f32>>::AST   //
-};
-
-// A list of all valid test cases for 'lhs op rhs', except that for vecN and
-// matNxN, we only test N=3.
-static constexpr Params all_valid_cases[] = {
-    // Logical expressions
-    // https://gpuweb.github.io/gpuweb/wgsl.html#logical-expr
-
-    // Binary logical expressions
-    ParamsFor<bool, bool, bool>(Op::kLogicalAnd),
-    ParamsFor<bool, bool, bool>(Op::kLogicalOr),
-
-    ParamsFor<bool, bool, bool>(Op::kAnd),
-    ParamsFor<bool, bool, bool>(Op::kOr),
-    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kAnd),
-    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kOr),
-
-    // Arithmetic expressions
-    // https://gpuweb.github.io/gpuweb/wgsl.html#arithmetic-expr
-
-    // Binary arithmetic expressions over scalars
-    ParamsFor<i32, i32, i32>(Op::kAdd),
-    ParamsFor<i32, i32, i32>(Op::kSubtract),
-    ParamsFor<i32, i32, i32>(Op::kMultiply),
-    ParamsFor<i32, i32, i32>(Op::kDivide),
-    ParamsFor<i32, i32, i32>(Op::kModulo),
-
-    ParamsFor<u32, u32, u32>(Op::kAdd),
-    ParamsFor<u32, u32, u32>(Op::kSubtract),
-    ParamsFor<u32, u32, u32>(Op::kMultiply),
-    ParamsFor<u32, u32, u32>(Op::kDivide),
-    ParamsFor<u32, u32, u32>(Op::kModulo),
-
-    ParamsFor<f32, f32, f32>(Op::kAdd),
-    ParamsFor<f32, f32, f32>(Op::kSubtract),
-    ParamsFor<f32, f32, f32>(Op::kMultiply),
-    ParamsFor<f32, f32, f32>(Op::kDivide),
-    ParamsFor<f32, f32, f32>(Op::kModulo),
-
-    // Binary arithmetic expressions over vectors
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kAdd),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kSubtract),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kMultiply),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kDivide),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kModulo),
-
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kAdd),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kSubtract),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kMultiply),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kDivide),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kModulo),
-
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kAdd),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kSubtract),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kMultiply),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kDivide),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kModulo),
-
-    // Binary arithmetic expressions with mixed scalar and vector operands
-    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kAdd),
-    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kSubtract),
-    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kMultiply),
-    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kDivide),
-    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kModulo),
-
-    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kAdd),
-    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kSubtract),
-    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kMultiply),
-    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kDivide),
-    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kModulo),
-
-    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kAdd),
-    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kSubtract),
-    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kMultiply),
-    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kDivide),
-    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kModulo),
-
-    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kAdd),
-    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kSubtract),
-    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kMultiply),
-    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kDivide),
-    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kModulo),
-
-    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kAdd),
-    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kSubtract),
-    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kMultiply),
-    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kDivide),
-    // NOTE: no kModulo for vec3<f32>, f32
-    // ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kModulo),
-
-    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kAdd),
-    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kSubtract),
-    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kMultiply),
-    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kDivide),
-    // NOTE: no kModulo for f32, vec3<f32>
-    // ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kModulo),
-
-    // Matrix arithmetic
-    ParamsFor<mat2x3<f32>, f32, mat2x3<f32>>(Op::kMultiply),
-    ParamsFor<mat3x2<f32>, f32, mat3x2<f32>>(Op::kMultiply),
-    ParamsFor<mat3x3<f32>, f32, mat3x3<f32>>(Op::kMultiply),
-
-    ParamsFor<f32, mat2x3<f32>, mat2x3<f32>>(Op::kMultiply),
-    ParamsFor<f32, mat3x2<f32>, mat3x2<f32>>(Op::kMultiply),
-    ParamsFor<f32, mat3x3<f32>, mat3x3<f32>>(Op::kMultiply),
-
-    ParamsFor<vec3<f32>, mat2x3<f32>, vec2<f32>>(Op::kMultiply),
-    ParamsFor<vec2<f32>, mat3x2<f32>, vec3<f32>>(Op::kMultiply),
-    ParamsFor<vec3<f32>, mat3x3<f32>, vec3<f32>>(Op::kMultiply),
-
-    ParamsFor<mat3x2<f32>, vec3<f32>, vec2<f32>>(Op::kMultiply),
-    ParamsFor<mat2x3<f32>, vec2<f32>, vec3<f32>>(Op::kMultiply),
-    ParamsFor<mat3x3<f32>, vec3<f32>, vec3<f32>>(Op::kMultiply),
-
-    ParamsFor<mat2x3<f32>, mat3x2<f32>, mat3x3<f32>>(Op::kMultiply),
-    ParamsFor<mat3x2<f32>, mat2x3<f32>, mat2x2<f32>>(Op::kMultiply),
-    ParamsFor<mat3x2<f32>, mat3x3<f32>, mat3x2<f32>>(Op::kMultiply),
-    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kMultiply),
-    ParamsFor<mat3x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kMultiply),
-
-    ParamsFor<mat2x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kAdd),
-    ParamsFor<mat3x2<f32>, mat3x2<f32>, mat3x2<f32>>(Op::kAdd),
-    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kAdd),
-
-    ParamsFor<mat2x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kSubtract),
-    ParamsFor<mat3x2<f32>, mat3x2<f32>, mat3x2<f32>>(Op::kSubtract),
-    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kSubtract),
-
-    // Comparison expressions
-    // https://gpuweb.github.io/gpuweb/wgsl.html#comparison-expr
-
-    // Comparisons over scalars
-    ParamsFor<bool, bool, bool>(Op::kEqual),
-    ParamsFor<bool, bool, bool>(Op::kNotEqual),
-
-    ParamsFor<i32, i32, bool>(Op::kEqual),
-    ParamsFor<i32, i32, bool>(Op::kNotEqual),
-    ParamsFor<i32, i32, bool>(Op::kLessThan),
-    ParamsFor<i32, i32, bool>(Op::kLessThanEqual),
-    ParamsFor<i32, i32, bool>(Op::kGreaterThan),
-    ParamsFor<i32, i32, bool>(Op::kGreaterThanEqual),
-
-    ParamsFor<u32, u32, bool>(Op::kEqual),
-    ParamsFor<u32, u32, bool>(Op::kNotEqual),
-    ParamsFor<u32, u32, bool>(Op::kLessThan),
-    ParamsFor<u32, u32, bool>(Op::kLessThanEqual),
-    ParamsFor<u32, u32, bool>(Op::kGreaterThan),
-    ParamsFor<u32, u32, bool>(Op::kGreaterThanEqual),
-
-    ParamsFor<f32, f32, bool>(Op::kEqual),
-    ParamsFor<f32, f32, bool>(Op::kNotEqual),
-    ParamsFor<f32, f32, bool>(Op::kLessThan),
-    ParamsFor<f32, f32, bool>(Op::kLessThanEqual),
-    ParamsFor<f32, f32, bool>(Op::kGreaterThan),
-    ParamsFor<f32, f32, bool>(Op::kGreaterThanEqual),
-
-    // Comparisons over vectors
-    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kEqual),
-    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kNotEqual),
-
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kEqual),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kNotEqual),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kLessThan),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kLessThanEqual),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kGreaterThan),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kGreaterThanEqual),
-
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kEqual),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kNotEqual),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kLessThan),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kLessThanEqual),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kGreaterThan),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kGreaterThanEqual),
-
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kEqual),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kNotEqual),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kLessThan),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kLessThanEqual),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kGreaterThan),
-    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kGreaterThanEqual),
-
-    // Binary bitwise operations
-    ParamsFor<i32, i32, i32>(Op::kOr),
-    ParamsFor<i32, i32, i32>(Op::kAnd),
-    ParamsFor<i32, i32, i32>(Op::kXor),
-
-    ParamsFor<u32, u32, u32>(Op::kOr),
-    ParamsFor<u32, u32, u32>(Op::kAnd),
-    ParamsFor<u32, u32, u32>(Op::kXor),
-
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kOr),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kAnd),
-    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kXor),
-
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kOr),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kAnd),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kXor),
-
-    // Bit shift expressions
-    ParamsFor<i32, u32, i32>(Op::kShiftLeft),
-    ParamsFor<vec3<i32>, vec3<u32>, vec3<i32>>(Op::kShiftLeft),
-
-    ParamsFor<u32, u32, u32>(Op::kShiftLeft),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kShiftLeft),
-
-    ParamsFor<i32, u32, i32>(Op::kShiftRight),
-    ParamsFor<vec3<i32>, vec3<u32>, vec3<i32>>(Op::kShiftRight),
-
-    ParamsFor<u32, u32, u32>(Op::kShiftRight),
-    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kShiftRight),
-};
-
-using Expr_Binary_Test_Valid = ResolverTestWithParam<Params>;
-TEST_P(Expr_Binary_Test_Valid, All) {
-  auto& params = GetParam();
-
-  auto* lhs_type = params.create_lhs_type(*this);
-  auto* rhs_type = params.create_rhs_type(*this);
-  auto* result_type = params.create_result_type(*this);
-
-  std::stringstream ss;
-  ss << FriendlyName(lhs_type) << " " << params.op << " "
-     << FriendlyName(rhs_type);
-  SCOPED_TRACE(ss.str());
-
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
-
-  auto* expr =
-      create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr) == result_type);
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Expr_Binary_Test_Valid,
-                         testing::ValuesIn(all_valid_cases));
-
-enum class BinaryExprSide { Left, Right, Both };
-using Expr_Binary_Test_WithAlias_Valid =
-    ResolverTestWithParam<std::tuple<Params, BinaryExprSide>>;
-TEST_P(Expr_Binary_Test_WithAlias_Valid, All) {
-  const Params& params = std::get<0>(GetParam());
-  BinaryExprSide side = std::get<1>(GetParam());
-
-  auto* create_lhs_type =
-      (side == BinaryExprSide::Left || side == BinaryExprSide::Both)
-          ? params.create_lhs_alias_type
-          : params.create_lhs_type;
-  auto* create_rhs_type =
-      (side == BinaryExprSide::Right || side == BinaryExprSide::Both)
-          ? params.create_rhs_alias_type
-          : params.create_rhs_type;
-
-  auto* lhs_type = create_lhs_type(*this);
-  auto* rhs_type = create_rhs_type(*this);
-
-  std::stringstream ss;
-  ss << FriendlyName(lhs_type) << " " << params.op << " "
-     << FriendlyName(rhs_type);
-
-  ss << ", After aliasing: " << FriendlyName(lhs_type) << " " << params.op
-     << " " << FriendlyName(rhs_type);
-  SCOPED_TRACE(ss.str());
-
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
-
-  auto* expr =
-      create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(expr), nullptr);
-  // TODO(amaiorano): Bring this back once we have a way to get the canonical
-  // type
-  // auto* *result_type = params.create_result_type(*this);
-  // ASSERT_TRUE(TypeOf(expr) == result_type);
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Expr_Binary_Test_WithAlias_Valid,
-    testing::Combine(testing::ValuesIn(all_valid_cases),
-                     testing::Values(BinaryExprSide::Left,
-                                     BinaryExprSide::Right,
-                                     BinaryExprSide::Both)));
-
-// This test works by taking the cartesian product of all possible
-// (type * type * op), and processing only the triplets that are not found in
-// the `all_valid_cases` table.
-using Expr_Binary_Test_Invalid =
-    ResolverTestWithParam<std::tuple<builder::ast_type_func_ptr,
-                                     builder::ast_type_func_ptr,
-                                     ast::BinaryOp>>;
-TEST_P(Expr_Binary_Test_Invalid, All) {
-  const builder::ast_type_func_ptr& lhs_create_type_func =
-      std::get<0>(GetParam());
-  const builder::ast_type_func_ptr& rhs_create_type_func =
-      std::get<1>(GetParam());
-  const ast::BinaryOp op = std::get<2>(GetParam());
-
-  // Skip if valid case
-  // TODO(amaiorano): replace linear lookup with O(1) if too slow
-  for (auto& c : all_valid_cases) {
-    if (c.create_lhs_type == lhs_create_type_func &&
-        c.create_rhs_type == rhs_create_type_func && c.op == op) {
-      return;
-    }
-  }
-
-  auto* lhs_type = lhs_create_type_func(*this);
-  auto* rhs_type = rhs_create_type_func(*this);
-
-  std::stringstream ss;
-  ss << FriendlyName(lhs_type) << " " << op << " " << FriendlyName(rhs_type);
-  SCOPED_TRACE(ss.str());
-
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(Source{{12, 34}}, op, Expr("lhs"),
-                                             Expr("rhs"));
-  WrapInFunction(expr);
-
-  ASSERT_FALSE(r()->Resolve());
-  ASSERT_EQ(r()->error(),
-            "12:34 error: Binary expression operand types are invalid for "
-            "this operation: " +
-                FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
-                " " + FriendlyName(rhs_type));
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Expr_Binary_Test_Invalid,
-    testing::Combine(testing::ValuesIn(all_create_type_funcs),
-                     testing::ValuesIn(all_create_type_funcs),
-                     testing::ValuesIn(all_ops)));
-
-using Expr_Binary_Test_Invalid_VectorMatrixMultiply =
-    ResolverTestWithParam<std::tuple<bool, uint32_t, uint32_t, uint32_t>>;
-TEST_P(Expr_Binary_Test_Invalid_VectorMatrixMultiply, All) {
-  bool vec_by_mat = std::get<0>(GetParam());
-  uint32_t vec_size = std::get<1>(GetParam());
-  uint32_t mat_rows = std::get<2>(GetParam());
-  uint32_t mat_cols = std::get<3>(GetParam());
-
-  const ast::Type* lhs_type = nullptr;
-  const ast::Type* rhs_type = nullptr;
-  const sem::Type* result_type = nullptr;
-  bool is_valid_expr;
-
-  if (vec_by_mat) {
-    lhs_type = ty.vec<f32>(vec_size);
-    rhs_type = ty.mat<f32>(mat_cols, mat_rows);
-    result_type = create<sem::Vector>(create<sem::F32>(), mat_cols);
-    is_valid_expr = vec_size == mat_rows;
-  } else {
-    lhs_type = ty.mat<f32>(mat_cols, mat_rows);
-    rhs_type = ty.vec<f32>(vec_size);
-    result_type = create<sem::Vector>(create<sem::F32>(), mat_rows);
-    is_valid_expr = vec_size == mat_cols;
-  }
-
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
-
-  auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  if (is_valid_expr) {
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-    ASSERT_TRUE(TypeOf(expr) == result_type);
-  } else {
-    ASSERT_FALSE(r()->Resolve());
-    ASSERT_EQ(r()->error(),
-              "12:34 error: Binary expression operand types are invalid for "
-              "this operation: " +
-                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
-                  " " + FriendlyName(rhs_type));
-  }
-}
-auto all_dimension_values = testing::Values(2u, 3u, 4u);
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Expr_Binary_Test_Invalid_VectorMatrixMultiply,
-                         testing::Combine(testing::Values(true, false),
-                                          all_dimension_values,
-                                          all_dimension_values,
-                                          all_dimension_values));
-
-using Expr_Binary_Test_Invalid_MatrixMatrixMultiply =
-    ResolverTestWithParam<std::tuple<uint32_t, uint32_t, uint32_t, uint32_t>>;
-TEST_P(Expr_Binary_Test_Invalid_MatrixMatrixMultiply, All) {
-  uint32_t lhs_mat_rows = std::get<0>(GetParam());
-  uint32_t lhs_mat_cols = std::get<1>(GetParam());
-  uint32_t rhs_mat_rows = std::get<2>(GetParam());
-  uint32_t rhs_mat_cols = std::get<3>(GetParam());
-
-  auto* lhs_type = ty.mat<f32>(lhs_mat_cols, lhs_mat_rows);
-  auto* rhs_type = ty.mat<f32>(rhs_mat_cols, rhs_mat_rows);
-
-  auto* f32 = create<sem::F32>();
-  auto* col = create<sem::Vector>(f32, lhs_mat_rows);
-  auto* result_type = create<sem::Matrix>(col, rhs_mat_cols);
-
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
-
-  auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  bool is_valid_expr = lhs_mat_cols == rhs_mat_rows;
-  if (is_valid_expr) {
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-    ASSERT_TRUE(TypeOf(expr) == result_type);
-  } else {
-    ASSERT_FALSE(r()->Resolve());
-    ASSERT_EQ(r()->error(),
-              "12:34 error: Binary expression operand types are invalid for "
-              "this operation: " +
-                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
-                  " " + FriendlyName(rhs_type));
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Expr_Binary_Test_Invalid_MatrixMatrixMultiply,
-                         testing::Combine(all_dimension_values,
-                                          all_dimension_values,
-                                          all_dimension_values,
-                                          all_dimension_values));
-
-}  // namespace ExprBinaryTest
-
-using UnaryOpExpressionTest = ResolverTestWithParam<ast::UnaryOp>;
-TEST_P(UnaryOpExpressionTest, Expr_UnaryOp) {
-  auto op = GetParam();
-
-  if (op == ast::UnaryOp::kNot) {
-    Global("ident", ty.vec4<bool>(), ast::StorageClass::kPrivate);
-  } else if (op == ast::UnaryOp::kNegation || op == ast::UnaryOp::kComplement) {
-    Global("ident", ty.vec4<i32>(), ast::StorageClass::kPrivate);
-  } else {
-    Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  }
-  auto* der = create<ast::UnaryOpExpression>(op, Expr("ident"));
-  WrapInFunction(der);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(der), nullptr);
-  ASSERT_TRUE(TypeOf(der)->Is<sem::Vector>());
-  if (op == ast::UnaryOp::kNot) {
-    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  } else if (op == ast::UnaryOp::kNegation || op == ast::UnaryOp::kComplement) {
-    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::I32>());
-  } else {
-    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::F32>());
-  }
-  EXPECT_EQ(TypeOf(der)->As<sem::Vector>()->Width(), 4u);
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         UnaryOpExpressionTest,
-                         testing::Values(ast::UnaryOp::kComplement,
-                                         ast::UnaryOp::kNegation,
-                                         ast::UnaryOp::kNot));
-
-TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
-  auto* var = Var("var", ty.i32());
-
-  auto* stmt = Decl(var);
-  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kFunction);
-}
-
-TEST_F(ResolverTest, StorageClass_SetForSampler) {
-  auto* t = ty.sampler(ast::SamplerKind::kSampler);
-  auto* var = Global("var", t,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(0),
-                     });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get(var)->StorageClass(),
-            ast::StorageClass::kUniformConstant);
-}
-
-TEST_F(ResolverTest, StorageClass_SetForTexture) {
-  auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  auto* var = Global("var", t,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(0),
-                     });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get(var)->StorageClass(),
-            ast::StorageClass::kUniformConstant);
-}
-
-TEST_F(ResolverTest, StorageClass_DoesNotSetOnConst) {
-  auto* var = Const("var", ty.i32(), Construct(ty.i32()));
-  auto* stmt = Decl(var);
-  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kNone);
-}
-
-TEST_F(ResolverTest, Access_SetForStorageBuffer) {
-  // [[block]] struct S { x : i32 };
-  // var<storage> g : S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* var =
-      Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-             ast::AttributeList{
-                 create<ast::BindingAttribute>(0),
-                 create<ast::GroupAttribute>(0),
-             });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get(var)->Access(), ast::Access::kRead);
-}
-
-TEST_F(ResolverTest, BindingPoint_SetForResources) {
-  // @group(1) @binding(2) var s1 : sampler;
-  // @group(3) @binding(4) var s2 : sampler;
-  auto* s1 = Global(Sym(), ty.sampler(ast::SamplerKind::kSampler),
-                    ast::AttributeList{create<ast::GroupAttribute>(1),
-                                       create<ast::BindingAttribute>(2)});
-  auto* s2 = Global(Sym(), ty.sampler(ast::SamplerKind::kSampler),
-                    ast::AttributeList{create<ast::GroupAttribute>(3),
-                                       create<ast::BindingAttribute>(4)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s1)->BindingPoint(),
-            (sem::BindingPoint{1u, 2u}));
-  EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s2)->BindingPoint(),
-            (sem::BindingPoint{3u, 4u}));
-}
-
-TEST_F(ResolverTest, Function_EntryPoints_StageAttribute) {
-  // fn b() {}
-  // fn c() { b(); }
-  // fn a() { c(); }
-  // fn ep_1() { a(); b(); }
-  // fn ep_2() { c();}
-  //
-  // c -> {ep_1, ep_2}
-  // a -> {ep_1}
-  // b -> {ep_1, ep_2}
-  // ep_1 -> {}
-  // ep_2 -> {}
-
-  Global("first", ty.f32(), ast::StorageClass::kPrivate);
-  Global("second", ty.f32(), ast::StorageClass::kPrivate);
-  Global("call_a", ty.f32(), ast::StorageClass::kPrivate);
-  Global("call_b", ty.f32(), ast::StorageClass::kPrivate);
-  Global("call_c", ty.f32(), ast::StorageClass::kPrivate);
-
-  ast::VariableList params;
-  auto* func_b =
-      Func("b", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
-  auto* func_c =
-      Func("c", params, ty.f32(), {Assign("second", Call("b")), Return(0.0f)},
-           ast::AttributeList{});
-
-  auto* func_a =
-      Func("a", params, ty.f32(), {Assign("first", Call("c")), Return(0.0f)},
-           ast::AttributeList{});
-
-  auto* ep_1 = Func("ep_1", params, ty.void_(),
-                    {
-                        Assign("call_a", Call("a")),
-                        Assign("call_b", Call("b")),
-                    },
-                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                       WorkgroupSize(1)});
-
-  auto* ep_2 = Func("ep_2", params, ty.void_(),
-                    {
-                        Assign("call_c", Call("c")),
-                    },
-                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                       WorkgroupSize(1)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func_b_sem = Sem().Get(func_b);
-  auto* func_a_sem = Sem().Get(func_a);
-  auto* func_c_sem = Sem().Get(func_c);
-  auto* ep_1_sem = Sem().Get(ep_1);
-  auto* ep_2_sem = Sem().Get(ep_2);
-  ASSERT_NE(func_b_sem, nullptr);
-  ASSERT_NE(func_a_sem, nullptr);
-  ASSERT_NE(func_c_sem, nullptr);
-  ASSERT_NE(ep_1_sem, nullptr);
-  ASSERT_NE(ep_2_sem, nullptr);
-
-  EXPECT_EQ(func_b_sem->Parameters().size(), 0u);
-  EXPECT_EQ(func_a_sem->Parameters().size(), 0u);
-  EXPECT_EQ(func_c_sem->Parameters().size(), 0u);
-
-  const auto& b_eps = func_b_sem->AncestorEntryPoints();
-  ASSERT_EQ(2u, b_eps.size());
-  EXPECT_EQ(Symbols().Register("ep_1"), b_eps[0]->Declaration()->symbol);
-  EXPECT_EQ(Symbols().Register("ep_2"), b_eps[1]->Declaration()->symbol);
-
-  const auto& a_eps = func_a_sem->AncestorEntryPoints();
-  ASSERT_EQ(1u, a_eps.size());
-  EXPECT_EQ(Symbols().Register("ep_1"), a_eps[0]->Declaration()->symbol);
-
-  const auto& c_eps = func_c_sem->AncestorEntryPoints();
-  ASSERT_EQ(2u, c_eps.size());
-  EXPECT_EQ(Symbols().Register("ep_1"), c_eps[0]->Declaration()->symbol);
-  EXPECT_EQ(Symbols().Register("ep_2"), c_eps[1]->Declaration()->symbol);
-
-  EXPECT_TRUE(ep_1_sem->AncestorEntryPoints().empty());
-  EXPECT_TRUE(ep_2_sem->AncestorEntryPoints().empty());
-}
-
-// Check for linear-time traversal of functions reachable from entry points.
-// See: crbug.com/tint/245
-TEST_F(ResolverTest, Function_EntryPoints_LinearTime) {
-  // fn lNa() { }
-  // fn lNb() { }
-  // ...
-  // fn l2a() { l3a(); l3b(); }
-  // fn l2b() { l3a(); l3b(); }
-  // fn l1a() { l2a(); l2b(); }
-  // fn l1b() { l2a(); l2b(); }
-  // fn main() { l1a(); l1b(); }
-
-  static constexpr int levels = 64;
-
-  auto fn_a = [](int level) { return "l" + std::to_string(level + 1) + "a"; };
-  auto fn_b = [](int level) { return "l" + std::to_string(level + 1) + "b"; };
-
-  Func(fn_a(levels), {}, ty.void_(), {}, {});
-  Func(fn_b(levels), {}, ty.void_(), {}, {});
-
-  for (int i = levels - 1; i >= 0; i--) {
-    Func(fn_a(i), {}, ty.void_(),
-         {
-             CallStmt(Call(fn_a(i + 1))),
-             CallStmt(Call(fn_b(i + 1))),
-         },
-         {});
-    Func(fn_b(i), {}, ty.void_(),
-         {
-             CallStmt(Call(fn_a(i + 1))),
-             CallStmt(Call(fn_b(i + 1))),
-         },
-         {});
-  }
-
-  Func("main", {}, ty.void_(),
-       {
-           CallStmt(Call(fn_a(0))),
-           CallStmt(Call(fn_b(0))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Test for crbug.com/tint/728
-TEST_F(ResolverTest, ASTNodesAreReached) {
-  Structure("A", {Member("x", ty.array<f32, 4>(4))});
-  Structure("B", {Member("x", ty.array<f32, 4>(4))});
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, ASTNodeNotReached) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Expr("expr");
-        Resolver(&b).Resolve();
-      },
-      "internal compiler error: AST node 'tint::ast::IdentifierExpression' was "
-      "not reached by the resolver");
-}
-
-TEST_F(ResolverTest, ASTNodeReachedTwice) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        auto* expr = b.Expr(1);
-        b.Global("a", b.ty.i32(), ast::StorageClass::kPrivate, expr);
-        b.Global("b", b.ty.i32(), ast::StorageClass::kPrivate, expr);
-        Resolver(&b).Resolve();
-      },
-      "internal compiler error: AST node 'tint::ast::SintLiteralExpression' "
-      "was encountered twice in the same AST of a Program");
-}
-
-TEST_F(ResolverTest, UnaryOp_Not) {
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
-                                             Expr(Source{{12, 34}}, "ident"));
-  WrapInFunction(der);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot logical negate expression of type 'vec4<f32>");
-}
-
-TEST_F(ResolverTest, UnaryOp_Complement) {
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement,
-                                             Expr(Source{{12, 34}}, "ident"));
-  WrapInFunction(der);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: cannot bitwise complement expression of type 'vec4<f32>");
-}
-
-TEST_F(ResolverTest, UnaryOp_Negation) {
-  Global("ident", ty.u32(), ast::StorageClass::kPrivate);
-  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation,
-                                             Expr(Source{{12, 34}}, "ident"));
-  WrapInFunction(der);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: cannot negate expression of type 'u32");
-}
-
-TEST_F(ResolverTest, TextureSampler_TextureSample) {
-  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 1));
-  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
-
-  auto* call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
-  const ast::Function* f = Func("test_function", {}, ty.void_(), {call},
-                                {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  const sem::Function* sf = Sem().Get(f);
-  auto pairs = sf->TextureSamplerPairs();
-  ASSERT_EQ(pairs.size(), 1u);
-  EXPECT_TRUE(pairs[0].first != nullptr);
-  EXPECT_TRUE(pairs[0].second != nullptr);
-}
-
-TEST_F(ResolverTest, TextureSampler_TextureSampleInFunction) {
-  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 1));
-  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
-
-  auto* inner_call =
-      CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
-  const ast::Function* inner_func =
-      Func("inner_func", {}, ty.void_(), {inner_call});
-  auto* outer_call = CallStmt(Call("inner_func"));
-  const ast::Function* outer_func =
-      Func("outer_func", {}, ty.void_(), {outer_call},
-           {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto inner_pairs = Sem().Get(inner_func)->TextureSamplerPairs();
-  ASSERT_EQ(inner_pairs.size(), 1u);
-  EXPECT_TRUE(inner_pairs[0].first != nullptr);
-  EXPECT_TRUE(inner_pairs[0].second != nullptr);
-
-  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
-  ASSERT_EQ(outer_pairs.size(), 1u);
-  EXPECT_TRUE(outer_pairs[0].first != nullptr);
-  EXPECT_TRUE(outer_pairs[0].second != nullptr);
-}
-
-TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondSameVariables) {
-  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 1));
-  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
-
-  auto* inner_call_1 =
-      CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
-  const ast::Function* inner_func_1 =
-      Func("inner_func_1", {}, ty.void_(), {inner_call_1});
-  auto* inner_call_2 =
-      CallStmt(Call("textureSample", "t", "s", vec2<f32>(3.0f, 4.0f)));
-  const ast::Function* inner_func_2 =
-      Func("inner_func_2", {}, ty.void_(), {inner_call_2});
-  auto* outer_call_1 = CallStmt(Call("inner_func_1"));
-  auto* outer_call_2 = CallStmt(Call("inner_func_2"));
-  const ast::Function* outer_func =
-      Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
-           {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
-  ASSERT_EQ(inner_pairs_1.size(), 1u);
-  EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
-  EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
-
-  auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
-  ASSERT_EQ(inner_pairs_1.size(), 1u);
-  EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
-  EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
-
-  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
-  ASSERT_EQ(outer_pairs.size(), 1u);
-  EXPECT_TRUE(outer_pairs[0].first != nullptr);
-  EXPECT_TRUE(outer_pairs[0].second != nullptr);
-}
-
-TEST_F(ResolverTest,
-       TextureSampler_TextureSampleFunctionDiamondDifferentVariables) {
-  Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 1));
-  Global("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 2));
-  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 3));
-
-  auto* inner_call_1 =
-      CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1.0f, 2.0f)));
-  const ast::Function* inner_func_1 =
-      Func("inner_func_1", {}, ty.void_(), {inner_call_1});
-  auto* inner_call_2 =
-      CallStmt(Call("textureSample", "t2", "s", vec2<f32>(3.0f, 4.0f)));
-  const ast::Function* inner_func_2 =
-      Func("inner_func_2", {}, ty.void_(), {inner_call_2});
-  auto* outer_call_1 = CallStmt(Call("inner_func_1"));
-  auto* outer_call_2 = CallStmt(Call("inner_func_2"));
-  const ast::Function* outer_func =
-      Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
-           {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
-  ASSERT_EQ(inner_pairs_1.size(), 1u);
-  EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
-  EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
-
-  auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
-  ASSERT_EQ(inner_pairs_2.size(), 1u);
-  EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
-  EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
-
-  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
-  ASSERT_EQ(outer_pairs.size(), 2u);
-  EXPECT_TRUE(outer_pairs[0].first == inner_pairs_1[0].first);
-  EXPECT_TRUE(outer_pairs[0].second == inner_pairs_1[0].second);
-  EXPECT_TRUE(outer_pairs[1].first == inner_pairs_2[0].first);
-  EXPECT_TRUE(outer_pairs[1].second == inner_pairs_2[0].second);
-}
-
-TEST_F(ResolverTest, TextureSampler_TextureDimensions) {
-  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-         GroupAndBinding(1, 2));
-
-  auto* call = Call("textureDimensions", "t");
-  const ast::Function* f = WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  const sem::Function* sf = Sem().Get(f);
-  auto pairs = sf->TextureSamplerPairs();
-  ASSERT_EQ(pairs.size(), 1u);
-  EXPECT_TRUE(pairs[0].first != nullptr);
-  EXPECT_TRUE(pairs[0].second == nullptr);
-}
-
-TEST_F(ResolverTest, ModuleDependencyOrderedDeclarations) {
-  auto* f0 = Func("f0", {}, ty.void_(), {});
-  auto* v0 = Global("v0", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a0 = Alias("a0", ty.i32());
-  auto* s0 = Structure("s0", {Member("m", ty.i32())});
-  auto* f1 = Func("f1", {}, ty.void_(), {});
-  auto* v1 = Global("v1", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a1 = Alias("a1", ty.i32());
-  auto* s1 = Structure("s1", {Member("m", ty.i32())});
-  auto* f2 = Func("f2", {}, ty.void_(), {});
-  auto* v2 = Global("v2", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a2 = Alias("a2", ty.i32());
-  auto* s2 = Structure("s2", {Member("m", ty.i32())});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(Sem().Module(), nullptr);
-  EXPECT_THAT(Sem().Module()->DependencyOrderedDeclarations(),
-              ElementsAre(f0, v0, a0, s0, f1, v1, a1, s1, f2, v2, a2, s2));
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver_test_helper.cc b/src/resolver/resolver_test_helper.cc
deleted file mode 100644
index 17ffa5c..0000000
--- a/src/resolver/resolver_test_helper.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver_test_helper.h"
-
-#include <memory>
-
-namespace tint {
-namespace resolver {
-
-TestHelper::TestHelper() : resolver_(std::make_unique<Resolver>(this)) {}
-
-TestHelper::~TestHelper() = default;
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/resolver_test_helper.h b/src/resolver/resolver_test_helper.h
deleted file mode 100644
index bc50d93..0000000
--- a/src/resolver/resolver_test_helper.h
+++ /dev/null
@@ -1,489 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_RESOLVER_RESOLVER_TEST_HELPER_H_
-#define SRC_RESOLVER_RESOLVER_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-#include "src/resolver/resolver.h"
-#include "src/sem/expression.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-namespace tint {
-namespace resolver {
-
-/// Helper class for testing
-class TestHelper : public ProgramBuilder {
- public:
-  /// Constructor
-  TestHelper();
-
-  /// Destructor
-  ~TestHelper() override;
-
-  /// @return a pointer to the Resolver
-  Resolver* r() const { return resolver_.get(); }
-
-  /// Returns the statement that holds the given expression.
-  /// @param expr the ast::Expression
-  /// @return the ast::Statement of the ast::Expression, or nullptr if the
-  /// expression is not owned by a statement.
-  const ast::Statement* StmtOf(const ast::Expression* expr) {
-    auto* sem_stmt = Sem().Get(expr)->Stmt();
-    return sem_stmt ? sem_stmt->Declaration() : nullptr;
-  }
-
-  /// Returns the BlockStatement that holds the given statement.
-  /// @param stmt the ast::Statement
-  /// @return the ast::BlockStatement that holds the ast::Statement, or nullptr
-  /// if the statement is not owned by a BlockStatement.
-  const ast::BlockStatement* BlockOf(const ast::Statement* stmt) {
-    auto* sem_stmt = Sem().Get(stmt);
-    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
-  }
-
-  /// Returns the BlockStatement that holds the given expression.
-  /// @param expr the ast::Expression
-  /// @return the ast::Statement of the ast::Expression, or nullptr if the
-  /// expression is not indirectly owned by a BlockStatement.
-  const ast::BlockStatement* BlockOf(const ast::Expression* expr) {
-    auto* sem_stmt = Sem().Get(expr)->Stmt();
-    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
-  }
-
-  /// Returns the semantic variable for the given identifier expression.
-  /// @param expr the identifier expression
-  /// @return the resolved sem::Variable of the identifier, or nullptr if
-  /// the expression did not resolve to a variable.
-  const sem::Variable* VarOf(const ast::Expression* expr) {
-    auto* sem_ident = Sem().Get(expr);
-    auto* var_user = sem_ident ? sem_ident->As<sem::VariableUser>() : nullptr;
-    return var_user ? var_user->Variable() : nullptr;
-  }
-
-  /// Checks that all the users of the given variable are as expected
-  /// @param var the variable to check
-  /// @param expected_users the expected users of the variable
-  /// @return true if all users are as expected
-  bool CheckVarUsers(const ast::Variable* var,
-                     std::vector<const ast::Expression*>&& expected_users) {
-    auto& var_users = Sem().Get(var)->Users();
-    if (var_users.size() != expected_users.size()) {
-      return false;
-    }
-    for (size_t i = 0; i < var_users.size(); i++) {
-      if (var_users[i]->Declaration() != expected_users[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /// @param type a type
-  /// @returns the name for `type` that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const ast::Type* type) {
-    return type->FriendlyName(Symbols());
-  }
-
-  /// @param type a type
-  /// @returns the name for `type` that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const sem::Type* type) {
-    return type->FriendlyName(Symbols());
-  }
-
- private:
-  std::unique_ptr<Resolver> resolver_;
-};
-
-class ResolverTest : public TestHelper, public testing::Test {};
-
-template <typename T>
-class ResolverTestWithParam : public TestHelper,
-                              public testing::TestWithParam<T> {};
-
-namespace builder {
-
-using i32 = ProgramBuilder::i32;
-using u32 = ProgramBuilder::u32;
-using f32 = ProgramBuilder::f32;
-
-template <int N, typename T>
-struct vec {};
-
-template <typename T>
-using vec2 = vec<2, T>;
-
-template <typename T>
-using vec3 = vec<3, T>;
-
-template <typename T>
-using vec4 = vec<4, T>;
-
-template <int N, int M, typename T>
-struct mat {};
-
-template <typename T>
-using mat2x2 = mat<2, 2, T>;
-
-template <typename T>
-using mat2x3 = mat<2, 3, T>;
-
-template <typename T>
-using mat3x2 = mat<3, 2, T>;
-
-template <typename T>
-using mat3x3 = mat<3, 3, T>;
-
-template <typename T>
-using mat4x4 = mat<4, 4, T>;
-
-template <int N, typename T>
-struct array {};
-
-template <typename TO, int ID = 0>
-struct alias {};
-
-template <typename TO>
-using alias1 = alias<TO, 1>;
-
-template <typename TO>
-using alias2 = alias<TO, 2>;
-
-template <typename TO>
-using alias3 = alias<TO, 3>;
-
-template <typename TO>
-struct ptr {};
-
-using ast_type_func_ptr = const ast::Type* (*)(ProgramBuilder& b);
-using ast_expr_func_ptr = const ast::Expression* (*)(ProgramBuilder& b,
-                                                     int elem_value);
-using sem_type_func_ptr = const sem::Type* (*)(ProgramBuilder& b);
-
-template <typename T>
-struct DataType {};
-
-/// Helper for building bool types and expressions
-template <>
-struct DataType<bool> {
-  /// false as bool is not a composite type
-  static constexpr bool is_composite = false;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST bool type
-  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); }
-  /// @param b the ProgramBuilder
-  /// @return the semantic bool type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::Bool>();
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the b
-  /// @return a new AST expression of the bool type
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Expr(elem_value == 0);
-  }
-};
-
-/// Helper for building i32 types and expressions
-template <>
-struct DataType<i32> {
-  /// false as i32 is not a composite type
-  static constexpr bool is_composite = false;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST i32 type
-  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); }
-  /// @param b the ProgramBuilder
-  /// @return the semantic i32 type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::I32>();
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value i32 will be initialized with
-  /// @return a new AST i32 literal value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Expr(static_cast<i32>(elem_value));
-  }
-};
-
-/// Helper for building u32 types and expressions
-template <>
-struct DataType<u32> {
-  /// false as u32 is not a composite type
-  static constexpr bool is_composite = false;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST u32 type
-  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); }
-  /// @param b the ProgramBuilder
-  /// @return the semantic u32 type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::U32>();
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value u32 will be initialized with
-  /// @return a new AST u32 literal value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Expr(static_cast<u32>(elem_value));
-  }
-};
-
-/// Helper for building f32 types and expressions
-template <>
-struct DataType<f32> {
-  /// false as f32 is not a composite type
-  static constexpr bool is_composite = false;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST f32 type
-  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); }
-  /// @param b the ProgramBuilder
-  /// @return the semantic f32 type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::F32>();
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value f32 will be initialized with
-  /// @return a new AST f32 literal value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Expr(static_cast<f32>(elem_value));
-  }
-};
-
-/// Helper for building vector types and expressions
-template <int N, typename T>
-struct DataType<vec<N, T>> {
-  /// true as vectors are a composite type
-  static constexpr bool is_composite = true;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST vector type
-  static inline const ast::Type* AST(ProgramBuilder& b) {
-    return b.ty.vec(DataType<T>::AST(b), N);
-  }
-  /// @param b the ProgramBuilder
-  /// @return the semantic vector type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::Vector>(DataType<T>::Sem(b), N);
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element in the vector will be initialized
-  /// with
-  /// @return a new AST vector value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Construct(AST(b), ExprArgs(b, elem_value));
-  }
-
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element will be initialized with
-  /// @return the list of expressions that are used to construct the vector
-  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
-                                             int elem_value) {
-    ast::ExpressionList args;
-    for (int i = 0; i < N; i++) {
-      args.emplace_back(DataType<T>::Expr(b, elem_value));
-    }
-    return args;
-  }
-};
-
-/// Helper for building matrix types and expressions
-template <int N, int M, typename T>
-struct DataType<mat<N, M, T>> {
-  /// true as matrices are a composite type
-  static constexpr bool is_composite = true;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST matrix type
-  static inline const ast::Type* AST(ProgramBuilder& b) {
-    return b.ty.mat(DataType<T>::AST(b), N, M);
-  }
-  /// @param b the ProgramBuilder
-  /// @return the semantic matrix type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    auto* column_type = b.create<sem::Vector>(DataType<T>::Sem(b), M);
-    return b.create<sem::Matrix>(column_type, N);
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element in the matrix will be initialized
-  /// with
-  /// @return a new AST matrix value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Construct(AST(b), ExprArgs(b, elem_value));
-  }
-
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element will be initialized with
-  /// @return the list of expressions that are used to construct the matrix
-  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
-                                             int elem_value) {
-    ast::ExpressionList args;
-    for (int i = 0; i < N; i++) {
-      args.emplace_back(DataType<vec<M, T>>::Expr(b, elem_value));
-    }
-    return args;
-  }
-};
-
-/// Helper for building alias types and expressions
-template <typename T, int ID>
-struct DataType<alias<T, ID>> {
-  /// true if the aliased type is a composite type
-  static constexpr bool is_composite = DataType<T>::is_composite;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST alias type
-  static inline const ast::Type* AST(ProgramBuilder& b) {
-    auto name = b.Symbols().Register("alias_" + std::to_string(ID));
-    if (!b.AST().LookupType(name)) {
-      auto* type = DataType<T>::AST(b);
-      b.AST().AddTypeDecl(b.ty.alias(name, type));
-    }
-    return b.create<ast::TypeName>(name);
-  }
-  /// @param b the ProgramBuilder
-  /// @return the semantic aliased type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return DataType<T>::Sem(b);
-  }
-
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value nested elements will be initialized with
-  /// @return a new AST expression of the alias type
-  template <bool IS_COMPOSITE = is_composite>
-  static inline traits::EnableIf<!IS_COMPOSITE, const ast::Expression*> Expr(
-      ProgramBuilder& b,
-      int elem_value) {
-    // Cast
-    return b.Construct(AST(b), DataType<T>::Expr(b, elem_value));
-  }
-
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value nested elements will be initialized with
-  /// @return a new AST expression of the alias type
-  template <bool IS_COMPOSITE = is_composite>
-  static inline traits::EnableIf<IS_COMPOSITE, const ast::Expression*> Expr(
-      ProgramBuilder& b,
-      int elem_value) {
-    // Construct
-    return b.Construct(AST(b), DataType<T>::ExprArgs(b, elem_value));
-  }
-};
-
-/// Helper for building pointer types and expressions
-template <typename T>
-struct DataType<ptr<T>> {
-  /// true if the pointer type is a composite type
-  static constexpr bool is_composite = false;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST alias type
-  static inline const ast::Type* AST(ProgramBuilder& b) {
-    return b.create<ast::Pointer>(DataType<T>::AST(b),
-                                  ast::StorageClass::kPrivate,
-                                  ast::Access::kReadWrite);
-  }
-  /// @param b the ProgramBuilder
-  /// @return the semantic aliased type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    return b.create<sem::Pointer>(DataType<T>::Sem(b),
-                                  ast::StorageClass::kPrivate,
-                                  ast::Access::kReadWrite);
-  }
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST expression of the alias type
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int /*unused*/) {
-    auto sym = b.Symbols().New("global_for_ptr");
-    b.Global(sym, DataType<T>::AST(b), ast::StorageClass::kPrivate);
-    return b.AddressOf(sym);
-  }
-};
-
-/// Helper for building array types and expressions
-template <int N, typename T>
-struct DataType<array<N, T>> {
-  /// true as arrays are a composite type
-  static constexpr bool is_composite = true;
-
-  /// @param b the ProgramBuilder
-  /// @return a new AST array type
-  static inline const ast::Type* AST(ProgramBuilder& b) {
-    return b.ty.array(DataType<T>::AST(b), N);
-  }
-  /// @param b the ProgramBuilder
-  /// @return the semantic array type
-  static inline const sem::Type* Sem(ProgramBuilder& b) {
-    auto* el = DataType<T>::Sem(b);
-    return b.create<sem::Array>(
-        /* element */ el,
-        /* count */ N,
-        /* align */ el->Align(),
-        /* size */ el->Size(),
-        /* stride */ el->Align(),
-        /* implicit_stride */ el->Align());
-  }
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element in the array will be initialized
-  /// with
-  /// @return a new AST array value expression
-  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
-    return b.Construct(AST(b), ExprArgs(b, elem_value));
-  }
-
-  /// @param b the ProgramBuilder
-  /// @param elem_value the value each element will be initialized with
-  /// @return the list of expressions that are used to construct the array
-  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
-                                             int elem_value) {
-    ast::ExpressionList args;
-    for (int i = 0; i < N; i++) {
-      args.emplace_back(DataType<T>::Expr(b, elem_value));
-    }
-    return args;
-  }
-};
-
-/// Struct of all creation pointer types
-struct CreatePtrs {
-  /// ast node type create function
-  ast_type_func_ptr ast;
-  /// ast expression type create function
-  ast_expr_func_ptr expr;
-  /// sem type create function
-  sem_type_func_ptr sem;
-};
-
-/// Returns a CreatePtrs struct instance with all creation pointer types for
-/// type `T`
-template <typename T>
-constexpr CreatePtrs CreatePtrsFor() {
-  return {DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem};
-}
-
-}  // namespace builder
-
-}  // namespace resolver
-}  // namespace tint
-
-#endif  // SRC_RESOLVER_RESOLVER_TEST_HELPER_H_
diff --git a/src/resolver/resolver_validation.cc b/src/resolver/resolver_validation.cc
deleted file mode 100644
index a46c6fb..0000000
--- a/src/resolver/resolver_validation.cc
+++ /dev/null
@@ -1,2369 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include <algorithm>
-#include <limits>
-#include <utility>
-
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/depth_texture.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/matrix.h"
-#include "src/ast/pointer.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/sampled_texture.h"
-#include "src/ast/sampler.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/traverse_expressions.h"
-#include "src/ast/type_name.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/vector.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/if_statement.h"
-#include "src/sem/loop_statement.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/pointer_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/switch_statement.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/variable.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/utils/math.h"
-#include "src/utils/reverse.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/utils/transform.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-bool IsValidStorageTextureDimension(ast::TextureDimension dim) {
-  switch (dim) {
-    case ast::TextureDimension::k1d:
-    case ast::TextureDimension::k2d:
-    case ast::TextureDimension::k2dArray:
-    case ast::TextureDimension::k3d:
-      return true;
-    default:
-      return false;
-  }
-}
-
-bool IsValidStorageTextureTexelFormat(ast::TexelFormat format) {
-  switch (format) {
-    case ast::TexelFormat::kR32Uint:
-    case ast::TexelFormat::kR32Sint:
-    case ast::TexelFormat::kR32Float:
-    case ast::TexelFormat::kRg32Uint:
-    case ast::TexelFormat::kRg32Sint:
-    case ast::TexelFormat::kRg32Float:
-    case ast::TexelFormat::kRgba8Unorm:
-    case ast::TexelFormat::kRgba8Snorm:
-    case ast::TexelFormat::kRgba8Uint:
-    case ast::TexelFormat::kRgba8Sint:
-    case ast::TexelFormat::kRgba16Uint:
-    case ast::TexelFormat::kRgba16Sint:
-    case ast::TexelFormat::kRgba16Float:
-    case ast::TexelFormat::kRgba32Uint:
-    case ast::TexelFormat::kRgba32Sint:
-    case ast::TexelFormat::kRgba32Float:
-      return true;
-    default:
-      return false;
-  }
-}
-
-// Helper to stringify a pipeline IO attribute.
-std::string attr_to_str(const ast::Attribute* attr) {
-  std::stringstream str;
-  if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-    str << "builtin(" << builtin->builtin << ")";
-  } else if (auto* location = attr->As<ast::LocationAttribute>()) {
-    str << "location(" << location->value << ")";
-  }
-  return str.str();
-}
-
-template <typename CALLBACK>
-void TraverseCallChain(diag::List& diagnostics,
-                       const sem::Function* from,
-                       const sem::Function* to,
-                       CALLBACK&& callback) {
-  for (auto* f : from->TransitivelyCalledFunctions()) {
-    if (f == to) {
-      callback(f);
-      return;
-    }
-    if (f->TransitivelyCalledFunctions().contains(to)) {
-      TraverseCallChain(diagnostics, f, to, callback);
-      callback(f);
-      return;
-    }
-  }
-  TINT_ICE(Resolver, diagnostics)
-      << "TraverseCallChain() 'from' does not transitively call 'to'";
-}
-
-}  // namespace
-
-bool Resolver::ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
-  // T must be either u32 or i32.
-  if (!s->Type()->IsAnyOf<sem::U32, sem::I32>()) {
-    AddError("atomic only supports i32 or u32 types",
-             a->type ? a->type->source : a->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateStorageTexture(const ast::StorageTexture* t) {
-  switch (t->access) {
-    case ast::Access::kWrite:
-      break;
-    case ast::Access::kUndefined:
-      AddError("storage texture missing access control", t->source);
-      return false;
-    default:
-      AddError("storage textures currently only support 'write' access control",
-               t->source);
-      return false;
-  }
-
-  if (!IsValidStorageTextureDimension(t->dim)) {
-    AddError("cube dimensions for storage textures are not supported",
-             t->source);
-    return false;
-  }
-
-  if (!IsValidStorageTextureTexelFormat(t->format)) {
-    AddError(
-        "image format must be one of the texel formats specified for storage "
-        "textues in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats",
-        t->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateVariableConstructorOrCast(
-    const ast::Variable* var,
-    ast::StorageClass storage_class,
-    const sem::Type* storage_ty,
-    const sem::Type* rhs_ty) {
-  auto* value_type = rhs_ty->UnwrapRef();  // Implicit load of RHS
-
-  // Value type has to match storage type
-  if (storage_ty != value_type) {
-    std::string decl = var->is_const ? "let" : "var";
-    AddError("cannot initialize " + decl + " of type '" +
-                 TypeNameOf(storage_ty) + "' with value of type '" +
-                 TypeNameOf(rhs_ty) + "'",
-             var->source);
-    return false;
-  }
-
-  if (!var->is_const) {
-    switch (storage_class) {
-      case ast::StorageClass::kPrivate:
-      case ast::StorageClass::kFunction:
-        break;  // Allowed an initializer
-      default:
-        // https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
-        // Optionally has an initializer expression, if the variable is in the
-        // private or function storage classes.
-        AddError("var of storage class '" +
-                     std::string(ast::ToString(storage_class)) +
-                     "' cannot have an initializer. var initializers are only "
-                     "supported for the storage classes "
-                     "'private' and 'function'",
-                 var->source);
-        return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateStorageClassLayout(const sem::Type* store_ty,
-                                          ast::StorageClass sc,
-                                          Source source) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class-layout-constraints
-
-  auto is_uniform_struct_or_array = [sc](const sem::Type* ty) {
-    return sc == ast::StorageClass::kUniform &&
-           ty->IsAnyOf<sem::Array, sem::Struct>();
-  };
-
-  auto is_uniform_struct = [sc](const sem::Type* ty) {
-    return sc == ast::StorageClass::kUniform && ty->Is<sem::Struct>();
-  };
-
-  auto required_alignment_of = [&](const sem::Type* ty) {
-    uint32_t actual_align = ty->Align();
-    uint32_t required_align = actual_align;
-    if (is_uniform_struct_or_array(ty)) {
-      required_align = utils::RoundUp(16u, actual_align);
-    }
-    return required_align;
-  };
-
-  auto member_name_of = [this](const sem::StructMember* sm) {
-    return builder_->Symbols().NameFor(sm->Declaration()->symbol);
-  };
-
-  // Cache result of type + storage class pair.
-  if (!valid_type_storage_layouts_.emplace(store_ty, sc).second) {
-    return true;
-  }
-
-  if (!ast::IsHostShareable(sc)) {
-    return true;
-  }
-
-  if (auto* str = store_ty->As<sem::Struct>()) {
-    for (size_t i = 0; i < str->Members().size(); ++i) {
-      auto* const m = str->Members()[i];
-      uint32_t required_align = required_alignment_of(m->Type());
-
-      // Recurse into the member type.
-      if (!ValidateStorageClassLayout(m->Type(), sc,
-                                      m->Declaration()->type->source)) {
-        AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
-                str->Declaration()->source);
-        return false;
-      }
-
-      // Validate that member is at a valid byte offset
-      if (m->Offset() % required_align != 0) {
-        AddError("the offset of a struct member of type '" +
-                     m->Type()->UnwrapRef()->FriendlyName(builder_->Symbols()) +
-                     "' in storage class '" + ast::ToString(sc) +
-                     "' must be a multiple of " +
-                     std::to_string(required_align) + " 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);
-
-        AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
-                str->Declaration()->source);
-
-        if (auto* member_str = m->Type()->As<sem::Struct>()) {
-          AddNote("and layout of struct member:\n" +
-                      member_str->Layout(builder_->Symbols()),
-                  member_str->Declaration()->source);
-        }
-
-        return false;
-      }
-
-      // For uniform buffers, validate that the number of bytes between the
-      // previous member of type struct and the current is a multiple of 16
-      // bytes.
-      auto* const prev_member = (i == 0) ? nullptr : str->Members()[i - 1];
-      if (prev_member && is_uniform_struct(prev_member->Type())) {
-        const uint32_t prev_to_curr_offset =
-            m->Offset() - prev_member->Offset();
-        if (prev_to_curr_offset % 16 != 0) {
-          AddError(
-              "uniform storage requires that the number of bytes between the "
-              "start of the previous member of type struct and the current "
-              "member be a multiple of 16 bytes, but there are currently " +
-                  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);
-
-          AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
-                  str->Declaration()->source);
-
-          auto* prev_member_str = prev_member->Type()->As<sem::Struct>();
-          AddNote("and layout of previous member struct:\n" +
-                      prev_member_str->Layout(builder_->Symbols()),
-                  prev_member_str->Declaration()->source);
-          return false;
-        }
-      }
-    }
-  }
-
-  // For uniform buffer array members, validate that array elements are
-  // aligned to 16 bytes
-  if (auto* arr = store_ty->As<sem::Array>()) {
-    // Recurse into the element type.
-    // TODO(crbug.com/tint/1388): Ideally we'd pass the source for nested
-    // element type here, but we can't easily get that from the semantic node.
-    // We should consider recursing through the AST type nodes instead.
-    if (!ValidateStorageClassLayout(arr->ElemType(), sc, source)) {
-      return false;
-    }
-
-    if (sc == ast::StorageClass::kUniform) {
-      // We already validated that this array member is itself aligned to 16
-      // bytes above, so we only need to validate that stride is a multiple
-      // of 16 bytes.
-      if (arr->Stride() % 16 != 0) {
-        // Since WGSL has no stride attribute, try to provide a useful hint
-        // for how the shader author can resolve the issue.
-        std::string hint;
-        if (arr->ElemType()->is_scalar()) {
-          hint =
-              "Consider using a vector or struct as the element type "
-              "instead.";
-        } else if (auto* vec = arr->ElemType()->As<sem::Vector>();
-                   vec && vec->type()->Size() == 4) {
-          hint = "Consider using a vec4 instead.";
-        } else if (arr->ElemType()->Is<sem::Struct>()) {
-          hint =
-              "Consider using the @size attribute on the last struct "
-              "member.";
-        } else {
-          hint =
-              "Consider wrapping the element type in a struct and using "
-              "the "
-              "@size attribute.";
-        }
-        AddError(
-            "uniform storage requires that array elements be aligned to 16 "
-            "bytes, but array element alignment is currently " +
-                std::to_string(arr->Stride()) + ". " + hint,
-            source);
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateStorageClassLayout(const sem::Variable* var) {
-  if (auto* str = var->Type()->UnwrapRef()->As<sem::Struct>()) {
-    if (!ValidateStorageClassLayout(str, var->StorageClass(),
-                                    str->Declaration()->source)) {
-      AddNote("see declaration of variable", var->Declaration()->source);
-      return false;
-    }
-  } else {
-    Source source = var->Declaration()->source;
-    if (var->Declaration()->type) {
-      source = var->Declaration()->type->source;
-    }
-    if (!ValidateStorageClassLayout(var->Type()->UnwrapRef(),
-                                    var->StorageClass(), source)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateGlobalVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  if (!ValidateNoDuplicateAttributes(decl->attributes)) {
-    return false;
-  }
-
-  for (auto* attr : decl->attributes) {
-    if (decl->is_const) {
-      if (auto* id_attr = attr->As<ast::IdAttribute>()) {
-        uint32_t id = id_attr->value;
-        auto it = constant_ids_.find(id);
-        if (it != constant_ids_.end() && it->second != var) {
-          AddError("pipeline constant IDs must be unique", attr->source);
-          AddNote("a pipeline constant with an ID of " + std::to_string(id) +
-                      " was previously declared "
-                      "here:",
-                  ast::GetAttribute<ast::IdAttribute>(
-                      it->second->Declaration()->attributes)
-                      ->source);
-          return false;
-        }
-        if (id > 65535) {
-          AddError("pipeline constant IDs must be between 0 and 65535",
-                   attr->source);
-          return false;
-        }
-      } else {
-        AddError("attribute is not valid for constants", attr->source);
-        return false;
-      }
-    } else {
-      bool is_shader_io_attribute =
-          attr->IsAnyOf<ast::BuiltinAttribute, ast::InterpolateAttribute,
-                        ast::InvariantAttribute, ast::LocationAttribute>();
-      bool has_io_storage_class =
-          var->StorageClass() == ast::StorageClass::kInput ||
-          var->StorageClass() == ast::StorageClass::kOutput;
-      if (!(attr->IsAnyOf<ast::BindingAttribute, ast::GroupAttribute,
-                          ast::InternalAttribute>()) &&
-          (!is_shader_io_attribute || !has_io_storage_class)) {
-        AddError("attribute is not valid for variables", attr->source);
-        return false;
-      }
-    }
-  }
-
-  if (var->StorageClass() == ast::StorageClass::kFunction) {
-    AddError(
-        "variables declared at module scope must not be in the function "
-        "storage class",
-        decl->source);
-    return false;
-  }
-
-  auto binding_point = decl->BindingPoint();
-  switch (var->StorageClass()) {
-    case ast::StorageClass::kUniform:
-    case ast::StorageClass::kStorage:
-    case ast::StorageClass::kUniformConstant: {
-      // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
-      // Each resource variable must be declared with both group and binding
-      // attributes.
-      if (!binding_point) {
-        AddError(
-            "resource variables require @group and @binding "
-            "attributes",
-            decl->source);
-        return false;
-      }
-      break;
-    }
-    default:
-      if (binding_point.binding || binding_point.group) {
-        // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
-        // Must only be applied to a resource variable
-        AddError(
-            "non-resource variables must not have @group or @binding "
-            "attributes",
-            decl->source);
-        return false;
-      }
-  }
-
-  // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
-  // The access mode always has a default, and except for variables in the
-  // storage storage class, must not be written.
-  if (var->StorageClass() != ast::StorageClass::kStorage &&
-      decl->declared_access != ast::Access::kUndefined) {
-    AddError(
-        "only variables in <storage> storage class may declare an access mode",
-        decl->source);
-    return false;
-  }
-
-  if (!decl->is_const) {
-    if (!ValidateAtomicVariable(var)) {
-      return false;
-    }
-  }
-
-  return ValidateVariable(var);
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
-// Atomic types may only be instantiated by variables in the workgroup storage
-// class or by storage buffer variables with a read_write access mode.
-bool Resolver::ValidateAtomicVariable(const sem::Variable* var) {
-  auto sc = var->StorageClass();
-  auto* decl = var->Declaration();
-  auto access = var->Access();
-  auto* type = var->Type()->UnwrapRef();
-  auto source = decl->type ? decl->type->source : decl->source;
-
-  if (type->Is<sem::Atomic>()) {
-    if (sc != ast::StorageClass::kWorkgroup) {
-      AddError(
-          "atomic variables must have <storage> or <workgroup> storage class",
-          source);
-      return false;
-    }
-  } else if (type->IsAnyOf<sem::Struct, sem::Array>()) {
-    auto found = atomic_composite_info_.find(type);
-    if (found != atomic_composite_info_.end()) {
-      if (sc != ast::StorageClass::kStorage &&
-          sc != ast::StorageClass::kWorkgroup) {
-        AddError(
-            "atomic variables must have <storage> or <workgroup> storage class",
-            source);
-        AddNote(
-            "atomic sub-type of '" + TypeNameOf(type) + "' is declared here",
-            found->second);
-        return false;
-      } else if (sc == ast::StorageClass::kStorage &&
-                 access != ast::Access::kReadWrite) {
-        AddError(
-            "atomic variables in <storage> storage class must have read_write "
-            "access mode",
-            source);
-        AddNote(
-            "atomic sub-type of '" + TypeNameOf(type) + "' is declared here",
-            found->second);
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto* storage_ty = var->Type()->UnwrapRef();
-
-  if (var->Is<sem::GlobalVariable>()) {
-    auto name = builder_->Symbols().NameFor(decl->symbol);
-    if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
-      auto* kind = var->Declaration()->is_const ? "let" : "var";
-      AddError(
-          "'" + name +
-              "' is a builtin and cannot be redeclared as a module-scope " +
-              kind,
-          decl->source);
-      return false;
-    }
-  }
-
-  if (!decl->is_const && !IsStorable(storage_ty)) {
-    AddError(TypeNameOf(storage_ty) + " cannot be used as the type of a var",
-             decl->source);
-    return false;
-  }
-
-  if (decl->is_const && !var->Is<sem::Parameter>() &&
-      !(storage_ty->IsConstructible() || storage_ty->Is<sem::Pointer>())) {
-    AddError(TypeNameOf(storage_ty) + " cannot be used as the type of a let",
-             decl->source);
-    return false;
-  }
-
-  if (auto* r = storage_ty->As<sem::MultisampledTexture>()) {
-    if (r->dim() != ast::TextureDimension::k2d) {
-      AddError("only 2d multisampled textures are supported", decl->source);
-      return false;
-    }
-
-    if (!r->type()->UnwrapRef()->is_numeric_scalar()) {
-      AddError("texture_multisampled_2d<type>: type must be f32, i32 or u32",
-               decl->source);
-      return false;
-    }
-  }
-
-  if (var->Is<sem::LocalVariable>() && !decl->is_const &&
-      IsValidationEnabled(decl->attributes,
-                          ast::DisabledValidation::kIgnoreStorageClass)) {
-    if (!var->Type()->UnwrapRef()->IsConstructible()) {
-      AddError("function variable must have a constructible type",
-               decl->type ? decl->type->source : decl->source);
-      return false;
-    }
-  }
-
-  if (storage_ty->is_handle() &&
-      decl->declared_storage_class != ast::StorageClass::kNone) {
-    // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
-    // If the store type is a texture type or a sampler type, then the
-    // variable declaration must not have a storage class attribute. The
-    // storage class will always be handle.
-    AddError("variables of type '" + TypeNameOf(storage_ty) +
-                 "' must not have a storage class",
-             decl->source);
-    return false;
-  }
-
-  if (IsValidationEnabled(decl->attributes,
-                          ast::DisabledValidation::kIgnoreStorageClass) &&
-      (decl->declared_storage_class == ast::StorageClass::kInput ||
-       decl->declared_storage_class == ast::StorageClass::kOutput)) {
-    AddError("invalid use of input/output storage class", decl->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateFunctionParameter(const ast::Function* func,
-                                         const sem::Variable* var) {
-  if (!ValidateVariable(var)) {
-    return false;
-  }
-
-  auto* decl = var->Declaration();
-
-  for (auto* attr : decl->attributes) {
-    if (!func->IsEntryPoint() && !attr->Is<ast::InternalAttribute>()) {
-      AddError("attribute is not valid for non-entry point function parameters",
-               attr->source);
-      return false;
-    } else if (!attr->IsAnyOf<ast::BuiltinAttribute, ast::InvariantAttribute,
-                              ast::LocationAttribute, ast::InterpolateAttribute,
-                              ast::InternalAttribute>() &&
-               (IsValidationEnabled(
-                    decl->attributes,
-                    ast::DisabledValidation::kEntryPointParameter) &&
-                IsValidationEnabled(
-                    decl->attributes,
-                    ast::DisabledValidation::
-                        kIgnoreConstructibleFunctionParameter))) {
-      AddError("attribute is not valid for function parameters", attr->source);
-      return false;
-    }
-  }
-
-  if (auto* ref = var->Type()->As<sem::Pointer>()) {
-    auto sc = ref->StorageClass();
-    if (!(sc == ast::StorageClass::kFunction ||
-          sc == ast::StorageClass::kPrivate ||
-          sc == ast::StorageClass::kWorkgroup) &&
-        IsValidationEnabled(decl->attributes,
-                            ast::DisabledValidation::kIgnoreStorageClass)) {
-      std::stringstream ss;
-      ss << "function parameter of pointer type cannot be in '" << sc
-         << "' storage class";
-      AddError(ss.str(), decl->source);
-      return false;
-    }
-  }
-
-  if (IsPlain(var->Type())) {
-    if (!var->Type()->IsConstructible() &&
-        IsValidationEnabled(
-            decl->attributes,
-            ast::DisabledValidation::kIgnoreConstructibleFunctionParameter)) {
-      AddError("store type of function parameter must be a constructible type",
-               decl->source);
-      return false;
-    }
-  } else if (!var->Type()
-                  ->IsAnyOf<sem::Texture, sem::Sampler, sem::Pointer>()) {
-    AddError(
-        "store type of function parameter cannot be " + TypeNameOf(var->Type()),
-        decl->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateBuiltinAttribute(const ast::BuiltinAttribute* attr,
-                                        const sem::Type* storage_ty,
-                                        const bool is_input) {
-  auto* type = storage_ty->UnwrapRef();
-  const auto stage = current_function_
-                         ? current_function_->Declaration()->PipelineStage()
-                         : ast::PipelineStage::kNone;
-  std::stringstream stage_name;
-  stage_name << stage;
-  bool is_stage_mismatch = false;
-  bool is_output = !is_input;
-  switch (attr->builtin) {
-    case ast::Builtin::kPosition:
-      if (stage != ast::PipelineStage::kNone &&
-          !((is_input && stage == ast::PipelineStage::kFragment) ||
-            (is_output && stage == ast::PipelineStage::kVertex))) {
-        is_stage_mismatch = true;
-      }
-      if (!(type->is_float_vector() && type->As<sem::Vector>()->Width() == 4)) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'vec4<f32>'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kGlobalInvocationId:
-    case ast::Builtin::kLocalInvocationId:
-    case ast::Builtin::kNumWorkgroups:
-    case ast::Builtin::kWorkgroupId:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kCompute && is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!(type->is_unsigned_integer_vector() &&
-            type->As<sem::Vector>()->Width() == 3)) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'vec3<u32>'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kFragDepth:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kFragment && !is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::F32>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'f32'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kFrontFacing:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kFragment && is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::Bool>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'bool'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kLocalInvocationIndex:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kCompute && is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::U32>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kVertexIndex:
-    case ast::Builtin::kInstanceIndex:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kVertex && is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::U32>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kSampleMask:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kFragment)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::U32>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
-                 attr->source);
-        return false;
-      }
-      break;
-    case ast::Builtin::kSampleIndex:
-      if (stage != ast::PipelineStage::kNone &&
-          !(stage == ast::PipelineStage::kFragment && is_input)) {
-        is_stage_mismatch = true;
-      }
-      if (!type->Is<sem::U32>()) {
-        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
-                 attr->source);
-        return false;
-      }
-      break;
-    default:
-      break;
-  }
-
-  if (is_stage_mismatch) {
-    AddError(attr_to_str(attr) + " cannot be used in " +
-                 (is_input ? "input of " : "output of ") + stage_name.str() +
-                 " pipeline stage",
-             attr->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateInterpolateAttribute(
-    const ast::InterpolateAttribute* attr,
-    const sem::Type* storage_ty) {
-  auto* type = storage_ty->UnwrapRef();
-
-  if (type->is_integer_scalar_or_vector() &&
-      attr->type != ast::InterpolationType::kFlat) {
-    AddError(
-        "interpolation type must be 'flat' for integral user-defined IO types",
-        attr->source);
-    return false;
-  }
-
-  if (attr->type == ast::InterpolationType::kFlat &&
-      attr->sampling != ast::InterpolationSampling::kNone) {
-    AddError("flat interpolation attribute must not have a sampling parameter",
-             attr->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateFunction(const sem::Function* func) {
-  auto* decl = func->Declaration();
-
-  auto name = builder_->Symbols().NameFor(decl->symbol);
-  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
-    AddError(
-        "'" + name + "' is a builtin and cannot be redeclared as a function",
-        decl->source);
-    return false;
-  }
-
-  auto workgroup_attr_count = 0;
-  for (auto* attr : decl->attributes) {
-    if (attr->Is<ast::WorkgroupAttribute>()) {
-      workgroup_attr_count++;
-      if (decl->PipelineStage() != ast::PipelineStage::kCompute) {
-        AddError(
-            "the workgroup_size attribute is only valid for compute stages",
-            attr->source);
-        return false;
-      }
-    } else if (!attr->IsAnyOf<ast::StageAttribute, ast::InternalAttribute>()) {
-      AddError("attribute is not valid for functions", attr->source);
-      return false;
-    }
-  }
-
-  if (decl->params.size() > 255) {
-    AddError("functions may declare at most 255 parameters", decl->source);
-    return false;
-  }
-
-  for (size_t i = 0; i < decl->params.size(); i++) {
-    if (!ValidateFunctionParameter(decl, func->Parameters()[i])) {
-      return false;
-    }
-  }
-
-  if (!func->ReturnType()->Is<sem::Void>()) {
-    if (!func->ReturnType()->IsConstructible()) {
-      AddError("function return type must be a constructible type",
-               decl->return_type->source);
-      return false;
-    }
-
-    if (decl->body) {
-      sem::Behaviors behaviors{sem::Behavior::kNext};
-      if (auto* last = decl->body->Last()) {
-        behaviors = Sem(last)->Behaviors();
-      }
-      if (behaviors.Contains(sem::Behavior::kNext)) {
-        AddError("missing return at end of function", decl->source);
-        return false;
-      }
-    } else if (IsValidationEnabled(
-                   decl->attributes,
-                   ast::DisabledValidation::kFunctionHasNoBody)) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "Function " << builder_->Symbols().NameFor(decl->symbol)
-          << " has no body";
-    }
-
-    for (auto* attr : decl->return_type_attributes) {
-      if (!decl->IsEntryPoint()) {
-        AddError(
-            "attribute is not valid for non-entry point function return types",
-            attr->source);
-        return false;
-      }
-      if (!attr->IsAnyOf<ast::BuiltinAttribute, ast::InternalAttribute,
-                         ast::LocationAttribute, ast::InterpolateAttribute,
-                         ast::InvariantAttribute>() &&
-          (IsValidationEnabled(decl->attributes,
-                               ast::DisabledValidation::kEntryPointParameter) &&
-           IsValidationEnabled(decl->attributes,
-                               ast::DisabledValidation::
-                                   kIgnoreConstructibleFunctionParameter))) {
-        AddError("attribute is not valid for entry point return types",
-                 attr->source);
-        return false;
-      }
-    }
-  }
-
-  if (decl->IsEntryPoint()) {
-    if (!ValidateEntryPoint(func)) {
-      return false;
-    }
-  }
-
-  // https://www.w3.org/TR/WGSL/#behaviors-rules
-  // a function behavior is always one of {}, {Next}, {Discard}, or
-  // {Next, Discard}.
-  if (func->Behaviors() != sem::Behaviors{} &&  // NOLINT: bad warning
-      func->Behaviors() != sem::Behavior::kNext &&
-      func->Behaviors() != sem::Behavior::kDiscard &&
-      func->Behaviors() != sem::Behaviors{sem::Behavior::kNext,  //
-                                          sem::Behavior::kDiscard}) {
-    TINT_ICE(Resolver, diagnostics_)
-        << "function '" << name << "' behaviors are: " << func->Behaviors();
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateEntryPoint(const sem::Function* func) {
-  auto* decl = func->Declaration();
-
-  // Use a lambda to validate the entry point attributes for a type.
-  // Persistent state is used to track which builtins and locations have
-  // already been seen, in order to catch conflicts.
-  // TODO(jrprice): This state could be stored in sem::Function instead, and
-  // then passed to sem::Function since it would be useful there too.
-  std::unordered_set<ast::Builtin> builtins;
-  std::unordered_set<uint32_t> locations;
-  enum class ParamOrRetType {
-    kParameter,
-    kReturnType,
-  };
-
-  // Inner lambda that is applied to a type and all of its members.
-  auto validate_entry_point_attributes_inner = [&](const ast::AttributeList&
-                                                       attrs,
-                                                   const sem::Type* ty,
-                                                   Source source,
-                                                   ParamOrRetType param_or_ret,
-                                                   bool is_struct_member) {
-    // Scan attributes for pipeline IO attributes.
-    // Check for overlap with attributes that have been seen previously.
-    const ast::Attribute* pipeline_io_attribute = nullptr;
-    const ast::InterpolateAttribute* interpolate_attribute = nullptr;
-    const ast::InvariantAttribute* invariant_attribute = nullptr;
-    for (auto* attr : attrs) {
-      auto is_invalid_compute_shader_attribute = false;
-      if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-        if (pipeline_io_attribute) {
-          AddError("multiple entry point IO attributes", attr->source);
-          AddNote("previously consumed " + attr_to_str(pipeline_io_attribute),
-                  pipeline_io_attribute->source);
-          return false;
-        }
-        pipeline_io_attribute = attr;
-
-        if (builtins.count(builtin->builtin)) {
-          AddError(attr_to_str(builtin) +
-                       " attribute appears multiple times as pipeline " +
-                       (param_or_ret == ParamOrRetType::kParameter ? "input"
-                                                                   : "output"),
-                   decl->source);
-          return false;
-        }
-
-        if (!ValidateBuiltinAttribute(
-                builtin, ty,
-                /* is_input */ param_or_ret == ParamOrRetType::kParameter)) {
-          return false;
-        }
-        builtins.emplace(builtin->builtin);
-      } else if (auto* location = attr->As<ast::LocationAttribute>()) {
-        if (pipeline_io_attribute) {
-          AddError("multiple entry point IO attributes", attr->source);
-          AddNote("previously consumed " + attr_to_str(pipeline_io_attribute),
-                  pipeline_io_attribute->source);
-          return false;
-        }
-        pipeline_io_attribute = attr;
-
-        bool is_input = param_or_ret == ParamOrRetType::kParameter;
-        if (!ValidateLocationAttribute(location, ty, locations, source,
-                                       is_input)) {
-          return false;
-        }
-      } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-        if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
-          is_invalid_compute_shader_attribute = true;
-        } else if (!ValidateInterpolateAttribute(interpolate, ty)) {
-          return false;
-        }
-        interpolate_attribute = interpolate;
-      } else if (auto* invariant = attr->As<ast::InvariantAttribute>()) {
-        if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
-          is_invalid_compute_shader_attribute = true;
-        }
-        invariant_attribute = invariant;
-      }
-      if (is_invalid_compute_shader_attribute) {
-        std::string input_or_output =
-            param_or_ret == ParamOrRetType::kParameter ? "inputs" : "output";
-        AddError("attribute is not valid for compute shader " + input_or_output,
-                 attr->source);
-        return false;
-      }
-    }
-
-    if (IsValidationEnabled(attrs,
-                            ast::DisabledValidation::kEntryPointParameter)) {
-      if (is_struct_member && ty->Is<sem::Struct>()) {
-        AddError("nested structures cannot be used for entry point IO", source);
-        return false;
-      }
-
-      if (!ty->Is<sem::Struct>() && !pipeline_io_attribute) {
-        std::string err = "missing entry point IO attribute";
-        if (!is_struct_member) {
-          err +=
-              (param_or_ret == ParamOrRetType::kParameter ? " on parameter"
-                                                          : " on return type");
-        }
-        AddError(err, source);
-        return false;
-      }
-
-      if (pipeline_io_attribute &&
-          pipeline_io_attribute->Is<ast::LocationAttribute>()) {
-        if (ty->is_integer_scalar_or_vector() && !interpolate_attribute) {
-          if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
-              param_or_ret == ParamOrRetType::kReturnType) {
-            AddError(
-                "integral user-defined vertex outputs must have a flat "
-                "interpolation attribute",
-                source);
-            return false;
-          }
-          if (decl->PipelineStage() == ast::PipelineStage::kFragment &&
-              param_or_ret == ParamOrRetType::kParameter) {
-            AddError(
-                "integral user-defined fragment inputs must have a flat "
-                "interpolation attribute",
-                source);
-            return false;
-          }
-        }
-      }
-
-      if (interpolate_attribute) {
-        if (!pipeline_io_attribute ||
-            !pipeline_io_attribute->Is<ast::LocationAttribute>()) {
-          AddError("interpolate attribute must only be used with @location",
-                   interpolate_attribute->source);
-          return false;
-        }
-      }
-
-      if (invariant_attribute) {
-        bool has_position = false;
-        if (pipeline_io_attribute) {
-          if (auto* builtin =
-                  pipeline_io_attribute->As<ast::BuiltinAttribute>()) {
-            has_position = (builtin->builtin == ast::Builtin::kPosition);
-          }
-        }
-        if (!has_position) {
-          AddError(
-              "invariant attribute must only be applied to a position "
-              "builtin",
-              invariant_attribute->source);
-          return false;
-        }
-      }
-    }
-    return true;
-  };
-
-  // Outer lambda for validating the entry point attributes for a type.
-  auto validate_entry_point_attributes = [&](const ast::AttributeList& attrs,
-                                             const sem::Type* ty, Source source,
-                                             ParamOrRetType param_or_ret) {
-    if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
-                                               /*is_struct_member*/ false)) {
-      return false;
-    }
-
-    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,
-                /*is_struct_member*/ true)) {
-          AddNote("while analysing entry point '" +
-                      builder_->Symbols().NameFor(decl->symbol) + "'",
-                  decl->source);
-          return false;
-        }
-      }
-    }
-
-    return true;
-  };
-
-  for (auto* param : func->Parameters()) {
-    auto* param_decl = param->Declaration();
-    if (!validate_entry_point_attributes(param_decl->attributes, param->Type(),
-                                         param_decl->source,
-                                         ParamOrRetType::kParameter)) {
-      return false;
-    }
-  }
-
-  // Clear IO sets after parameter validation. Builtin and location attributes
-  // in return types should be validated independently from those used in
-  // parameters.
-  builtins.clear();
-  locations.clear();
-
-  if (!func->ReturnType()->Is<sem::Void>()) {
-    if (!validate_entry_point_attributes(decl->return_type_attributes,
-                                         func->ReturnType(), decl->source,
-                                         ParamOrRetType::kReturnType)) {
-      return false;
-    }
-  }
-
-  if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
-      builtins.count(ast::Builtin::kPosition) == 0) {
-    // Check module-scope variables, as the SPIR-V sanitizer generates these.
-    bool found = false;
-    for (auto* global : func->TransitivelyReferencedGlobals()) {
-      if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
-              global->Declaration()->attributes)) {
-        if (builtin->builtin == ast::Builtin::kPosition) {
-          found = true;
-          break;
-        }
-      }
-    }
-    if (!found) {
-      AddError(
-          "a vertex shader must include the 'position' builtin in its return "
-          "type",
-          decl->source);
-      return false;
-    }
-  }
-
-  if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
-    if (!ast::HasAttribute<ast::WorkgroupAttribute>(decl->attributes)) {
-      AddError(
-          "a compute shader must include 'workgroup_size' in its "
-          "attributes",
-          decl->source);
-      return false;
-    }
-  }
-
-  // Validate there are no resource variable binding collisions
-  std::unordered_map<sem::BindingPoint, const ast::Variable*> binding_points;
-  for (auto* var : func->TransitivelyReferencedGlobals()) {
-    auto* var_decl = var->Declaration();
-    if (!var_decl->BindingPoint()) {
-      continue;
-    }
-    auto bp = var->BindingPoint();
-    auto res = binding_points.emplace(bp, var_decl);
-    if (!res.second &&
-        IsValidationEnabled(decl->attributes,
-                            ast::DisabledValidation::kBindingPointCollision) &&
-        IsValidationEnabled(res.first->second->attributes,
-                            ast::DisabledValidation::kBindingPointCollision)) {
-      // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
-      // Bindings must not alias within a shader stage: two different
-      // variables in the resource interface of a given shader must not have
-      // the same group and binding values, when considered as a pair of
-      // values.
-      auto func_name = builder_->Symbols().NameFor(decl->symbol);
-      AddError("entry point '" + func_name +
-                   "' references multiple variables that use the "
-                   "same resource binding @group(" +
-                   std::to_string(bp.group) + "), @binding(" +
-                   std::to_string(bp.binding) + ")",
-               var_decl->source);
-      AddNote("first resource binding usage declared here",
-              res.first->second->source);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateStatements(const ast::StatementList& stmts) {
-  for (auto* stmt : stmts) {
-    if (!Sem(stmt)->IsReachable()) {
-      /// TODO(https://github.com/gpuweb/gpuweb/issues/2378): This may need to
-      /// become an error.
-      AddWarning("code is unreachable", stmt->source);
-      break;
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateBitcast(const ast::BitcastExpression* cast,
-                               const sem::Type* to) {
-  auto* from = TypeOf(cast->expr)->UnwrapRef();
-  if (!from->is_numeric_scalar_or_vector()) {
-    AddError("'" + TypeNameOf(from) + "' cannot be bitcast",
-             cast->expr->source);
-    return false;
-  }
-  if (!to->is_numeric_scalar_or_vector()) {
-    AddError("cannot bitcast to '" + TypeNameOf(to) + "'", cast->type->source);
-    return false;
-  }
-
-  auto width = [&](const sem::Type* ty) {
-    if (auto* vec = ty->As<sem::Vector>()) {
-      return vec->Width();
-    }
-    return 1u;
-  };
-
-  if (width(from) != width(to)) {
-    AddError("cannot bitcast from '" + TypeNameOf(from) + "' to '" +
-                 TypeNameOf(to) + "'",
-             cast->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateBreakStatement(const sem::Statement* stmt) {
-  if (!stmt->FindFirstParent<sem::LoopBlockStatement, sem::CaseStatement>()) {
-    AddError("break statement must be in a loop or switch case",
-             stmt->Declaration()->source);
-    return false;
-  }
-  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true)) {
-    auto fail = [&](const char* note_msg, const Source& note_src) {
-      constexpr const char* kErrorMsg =
-          "break statement in a continuing block must be the single statement "
-          "of an if statement's true or false block, and that if statement "
-          "must be the last statement of the continuing block";
-      AddError(kErrorMsg, stmt->Declaration()->source);
-      AddNote(note_msg, note_src);
-      return false;
-    };
-
-    if (auto* block = stmt->Parent()->As<sem::BlockStatement>()) {
-      auto* block_parent = block->Parent();
-      auto* if_stmt = block_parent->As<sem::IfStatement>();
-      auto* el_stmt = block_parent->As<sem::ElseStatement>();
-      if (el_stmt) {
-        if_stmt = el_stmt->Parent();
-      }
-      if (!if_stmt) {
-        return fail("break statement is not directly in if statement block",
-                    stmt->Declaration()->source);
-      }
-      if (block->Declaration()->statements.size() != 1) {
-        return fail("if statement block contains multiple statements",
-                    block->Declaration()->source);
-      }
-      for (auto* el : if_stmt->Declaration()->else_statements) {
-        if (el->condition) {
-          return fail("else has condition", el->condition->source);
-        }
-        bool el_contains_break = el_stmt && el == el_stmt->Declaration();
-        if (el_contains_break) {
-          if (auto* true_block = if_stmt->Declaration()->body;
-              !true_block->Empty()) {
-            return fail("non-empty true block", true_block->source);
-          }
-        } else {
-          if (!el->body->Empty()) {
-            return fail("non-empty false block", el->body->source);
-          }
-        }
-      }
-      if (if_stmt->Parent()->Declaration() != continuing) {
-        return fail(
-            "if statement containing break statement is not directly in "
-            "continuing block",
-            if_stmt->Declaration()->source);
-      }
-      if (auto* cont_block = continuing->As<ast::BlockStatement>()) {
-        if (if_stmt->Declaration() != cont_block->Last()) {
-          return fail(
-              "if statement containing break statement is not the last "
-              "statement of the continuing block",
-              if_stmt->Declaration()->source);
-        }
-      }
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateContinueStatement(const sem::Statement* stmt) {
-  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true)) {
-    AddError("continuing blocks must not contain a continue statement",
-             stmt->Declaration()->source);
-    if (continuing != stmt->Declaration() &&
-        continuing != stmt->Parent()->Declaration()) {
-      AddNote("see continuing block here", continuing->source);
-    }
-    return false;
-  }
-
-  if (!stmt->FindFirstParent<sem::LoopBlockStatement>()) {
-    AddError("continue statement must be in a loop",
-             stmt->Declaration()->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateDiscardStatement(const sem::Statement* stmt) {
-  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
-    AddError("continuing blocks must not contain a discard statement",
-             stmt->Declaration()->source);
-    if (continuing != stmt->Declaration() &&
-        continuing != stmt->Parent()->Declaration()) {
-      AddNote("see continuing block here", continuing->source);
-    }
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateFallthroughStatement(const sem::Statement* stmt) {
-  if (auto* block = As<sem::BlockStatement>(stmt->Parent())) {
-    if (auto* c = As<sem::CaseStatement>(block->Parent())) {
-      if (block->Declaration()->Last() == stmt->Declaration()) {
-        if (auto* s = As<sem::SwitchStatement>(c->Parent())) {
-          if (c->Declaration() != s->Declaration()->body.back()) {
-            return true;
-          }
-          AddError(
-              "a fallthrough statement must not be used in the last switch "
-              "case",
-              stmt->Declaration()->source);
-          return false;
-        }
-      }
-    }
-  }
-  AddError(
-      "fallthrough must only be used as the last statement of a case block",
-      stmt->Declaration()->source);
-  return false;
-}
-
-bool Resolver::ValidateElseStatement(const sem::ElseStatement* stmt) {
-  if (auto* cond = stmt->Condition()) {
-    auto* cond_ty = cond->Type()->UnwrapRef();
-    if (!cond_ty->Is<sem::Bool>()) {
-      AddError(
-          "else statement condition must be bool, got " + TypeNameOf(cond_ty),
-          stmt->Condition()->Declaration()->source);
-      return false;
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateLoopStatement(const sem::LoopStatement* stmt) {
-  if (stmt->Behaviors().Empty()) {
-    AddError("loop does not exit", stmt->Declaration()->source.Begin());
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateForLoopStatement(const sem::ForLoopStatement* stmt) {
-  if (stmt->Behaviors().Empty()) {
-    AddError("for-loop does not exit", stmt->Declaration()->source.Begin());
-    return false;
-  }
-  if (auto* cond = stmt->Condition()) {
-    auto* cond_ty = cond->Type()->UnwrapRef();
-    if (!cond_ty->Is<sem::Bool>()) {
-      AddError("for-loop condition must be bool, got " + TypeNameOf(cond_ty),
-               stmt->Condition()->Declaration()->source);
-      return false;
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateIfStatement(const sem::IfStatement* stmt) {
-  auto* cond_ty = stmt->Condition()->Type()->UnwrapRef();
-  if (!cond_ty->Is<sem::Bool>()) {
-    AddError("if statement condition must be bool, got " + TypeNameOf(cond_ty),
-             stmt->Condition()->Declaration()->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateBuiltinCall(const sem::Call* call) {
-  if (call->Type()->Is<sem::Void>()) {
-    bool is_call_statement = false;
-    if (auto* call_stmt = As<ast::CallStatement>(call->Stmt()->Declaration())) {
-      if (call_stmt->expr == call->Declaration()) {
-        is_call_statement = true;
-      }
-    }
-    if (!is_call_statement) {
-      // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
-      // If the called function does not return a value, a function call
-      // statement should be used instead.
-      auto* ident = call->Declaration()->target.name;
-      auto name = builder_->Symbols().NameFor(ident->symbol);
-      AddError("builtin '" + name + "' does not return a value",
-               call->Declaration()->source);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateTextureBuiltinFunction(const sem::Call* call) {
-  auto* builtin = call->Target()->As<sem::Builtin>();
-  if (!builtin) {
-    return false;
-  }
-
-  std::string func_name = builtin->str();
-  auto& signature = builtin->Signature();
-
-  auto check_arg_is_constexpr = [&](sem::ParameterUsage usage, int min,
-                                    int max) {
-    auto index = signature.IndexOf(usage);
-    if (index < 0) {
-      return true;
-    }
-    std::string name = sem::str(usage);
-    auto* arg = call->Arguments()[index];
-    if (auto values = arg->ConstantValue()) {
-      // Assert that the constant values are of the expected type.
-      if (!values.Type()->IsAnyOf<sem::I32, sem::Vector>() ||
-          !values.ElementType()->Is<sem::I32>()) {
-        TINT_ICE(Resolver, diagnostics_)
-            << "failed to resolve '" + func_name + "' " << name
-            << " parameter type";
-        return false;
-      }
-
-      // Currently const_expr is restricted to literals and type constructors.
-      // Check that that's all we have for the parameter.
-      bool is_const_expr = true;
-      ast::TraverseExpressions(
-          arg->Declaration(), diagnostics_, [&](const ast::Expression* e) {
-            if (e->IsAnyOf<ast::LiteralExpression, ast::CallExpression>()) {
-              return ast::TraverseAction::Descend;
-            }
-            is_const_expr = false;
-            return ast::TraverseAction::Stop;
-          });
-      if (is_const_expr) {
-        auto vector = builtin->Parameters()[index]->Type()->Is<sem::Vector>();
-        for (size_t i = 0; i < values.Elements().size(); i++) {
-          auto value = values.Elements()[i].i32;
-          if (value < min || value > max) {
-            if (vector) {
-              AddError("each component of the " + name +
-                           " argument must be at least " + std::to_string(min) +
-                           " and at most " + std::to_string(max) + ". " + name +
-                           " component " + std::to_string(i) + " is " +
-                           std::to_string(value),
-                       arg->Declaration()->source);
-            } else {
-              AddError("the " + name + " argument must be at least " +
-                           std::to_string(min) + " and at most " +
-                           std::to_string(max) + ". " + name + " is " +
-                           std::to_string(value),
-                       arg->Declaration()->source);
-            }
-            return false;
-          }
-        }
-        return true;
-      }
-    }
-    AddError("the " + name + " argument must be a const_expression",
-             arg->Declaration()->source);
-    return false;
-  };
-
-  return check_arg_is_constexpr(sem::ParameterUsage::kOffset, -8, 7) &&
-         check_arg_is_constexpr(sem::ParameterUsage::kComponent, 0, 3);
-}
-
-bool Resolver::ValidateFunctionCall(const sem::Call* call) {
-  auto* decl = call->Declaration();
-  auto* target = call->Target()->As<sem::Function>();
-  auto sym = decl->target.name->symbol;
-  auto name = builder_->Symbols().NameFor(sym);
-
-  if (target->Declaration()->IsEntryPoint()) {
-    // https://www.w3.org/TR/WGSL/#function-restriction
-    // An entry point must never be the target of a function call.
-    AddError("entry point functions cannot be the target of a function call",
-             decl->source);
-    return false;
-  }
-
-  if (decl->args.size() != target->Parameters().size()) {
-    bool more = decl->args.size() > target->Parameters().size();
-    AddError("too " + (more ? std::string("many") : std::string("few")) +
-                 " arguments in call to '" + name + "', expected " +
-                 std::to_string(target->Parameters().size()) + ", got " +
-                 std::to_string(call->Arguments().size()),
-             decl->source);
-    return false;
-  }
-
-  for (size_t i = 0; i < call->Arguments().size(); ++i) {
-    const sem::Variable* param = target->Parameters()[i];
-    const ast::Expression* arg_expr = decl->args[i];
-    auto* param_type = param->Type();
-    auto* arg_type = TypeOf(arg_expr)->UnwrapRef();
-
-    if (param_type != arg_type) {
-      AddError("type mismatch for argument " + std::to_string(i + 1) +
-                   " in call to '" + name + "', expected '" +
-                   TypeNameOf(param_type) + "', got '" + TypeNameOf(arg_type) +
-                   "'",
-               arg_expr->source);
-      return false;
-    }
-
-    if (param_type->Is<sem::Pointer>()) {
-      auto is_valid = false;
-      if (auto* ident_expr = arg_expr->As<ast::IdentifierExpression>()) {
-        auto* var = ResolvedSymbol<sem::Variable>(ident_expr);
-        if (!var) {
-          TINT_ICE(Resolver, diagnostics_) << "failed to resolve identifier";
-          return false;
-        }
-        if (var->Is<sem::Parameter>()) {
-          is_valid = true;
-        }
-      } else if (auto* unary = arg_expr->As<ast::UnaryOpExpression>()) {
-        if (unary->op == ast::UnaryOp::kAddressOf) {
-          if (auto* ident_unary =
-                  unary->expr->As<ast::IdentifierExpression>()) {
-            auto* var = ResolvedSymbol<sem::Variable>(ident_unary);
-            if (!var) {
-              TINT_ICE(Resolver, diagnostics_)
-                  << "failed to resolve identifier";
-              return false;
-            }
-            if (var->Declaration()->is_const) {
-              TINT_ICE(Resolver, diagnostics_)
-                  << "Resolver::FunctionCall() encountered an address-of "
-                     "expression of a constant identifier expression";
-              return false;
-            }
-            is_valid = true;
-          }
-        }
-      }
-
-      if (!is_valid &&
-          IsValidationEnabled(
-              param->Declaration()->attributes,
-              ast::DisabledValidation::kIgnoreInvalidPointerArgument)) {
-        AddError(
-            "expected an address-of expression of a variable identifier "
-            "expression or a function parameter",
-            arg_expr->source);
-        return false;
-      }
-    }
-  }
-
-  if (call->Type()->Is<sem::Void>()) {
-    bool is_call_statement = false;
-    if (auto* call_stmt = As<ast::CallStatement>(call->Stmt()->Declaration())) {
-      if (call_stmt->expr == call->Declaration()) {
-        is_call_statement = true;
-      }
-    }
-    if (!is_call_statement) {
-      // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
-      // If the called function does not return a value, a function call
-      // statement should be used instead.
-      AddError("function '" + name + "' does not return a value", decl->source);
-      return false;
-    }
-  }
-
-  if (call->Behaviors().Contains(sem::Behavior::kDiscard)) {
-    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
-      AddError(
-          "cannot call a function that may discard inside a continuing block",
-          call->Declaration()->source);
-      if (continuing != call->Stmt()->Declaration() &&
-          continuing != call->Stmt()->Parent()->Declaration()) {
-        AddNote("see continuing block here", continuing->source);
-      }
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateStructureConstructorOrCast(
-    const ast::CallExpression* ctor,
-    const sem::Struct* struct_type) {
-  if (!struct_type->IsConstructible()) {
-    AddError("struct constructor has non-constructible type", ctor->source);
-    return false;
-  }
-
-  if (ctor->args.size() > 0) {
-    if (ctor->args.size() != struct_type->Members().size()) {
-      std::string fm =
-          ctor->args.size() < struct_type->Members().size() ? "few" : "many";
-      AddError("struct constructor has too " + fm + " inputs: expected " +
-                   std::to_string(struct_type->Members().size()) + ", found " +
-                   std::to_string(ctor->args.size()),
-               ctor->source);
-      return false;
-    }
-    for (auto* member : struct_type->Members()) {
-      auto* value = ctor->args[member->Index()];
-      auto* value_ty = TypeOf(value);
-      if (member->Type() != value_ty->UnwrapRef()) {
-        AddError(
-            "type in struct constructor does not match struct member type: "
-            "expected '" +
-                TypeNameOf(member->Type()) + "', found '" +
-                TypeNameOf(value_ty) + "'",
-            value->source);
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateArrayConstructorOrCast(const ast::CallExpression* ctor,
-                                              const sem::Array* array_type) {
-  auto& values = ctor->args;
-  auto* elem_ty = array_type->ElemType();
-  for (auto* value : values) {
-    auto* value_ty = TypeOf(value)->UnwrapRef();
-    if (value_ty != elem_ty) {
-      AddError(
-          "type in array constructor does not match array type: "
-          "expected '" +
-              TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_ty) + "'",
-          value->source);
-      return false;
-    }
-  }
-
-  if (array_type->IsRuntimeSized()) {
-    AddError("cannot init a runtime-sized array", ctor->source);
-    return false;
-  } else if (!elem_ty->IsConstructible()) {
-    AddError("array constructor has non-constructible element type",
-             ctor->source);
-    return false;
-  } else if (!values.empty() && (values.size() != array_type->Count())) {
-    std::string fm = values.size() < array_type->Count() ? "few" : "many";
-    AddError("array constructor has too " + fm + " elements: expected " +
-                 std::to_string(array_type->Count()) + ", found " +
-                 std::to_string(values.size()),
-             ctor->source);
-    return false;
-  } else if (values.size() > array_type->Count()) {
-    AddError("array constructor has too many elements: expected " +
-                 std::to_string(array_type->Count()) + ", found " +
-                 std::to_string(values.size()),
-             ctor->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateVectorConstructorOrCast(const ast::CallExpression* ctor,
-                                               const sem::Vector* vec_type) {
-  auto& values = ctor->args;
-  auto* elem_ty = vec_type->type();
-  size_t value_cardinality_sum = 0;
-  for (auto* value : values) {
-    auto* value_ty = TypeOf(value)->UnwrapRef();
-    if (value_ty->is_scalar()) {
-      if (elem_ty != value_ty) {
-        AddError(
-            "type in vector constructor does not match vector type: "
-            "expected '" +
-                TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_ty) + "'",
-            value->source);
-        return false;
-      }
-
-      value_cardinality_sum++;
-    } else if (auto* value_vec = value_ty->As<sem::Vector>()) {
-      auto* value_elem_ty = value_vec->type();
-      // A mismatch of vector type parameter T is only an error if multiple
-      // arguments are present. A single argument constructor constitutes a
-      // type conversion expression.
-      if (elem_ty != value_elem_ty && values.size() > 1u) {
-        AddError(
-            "type in vector constructor does not match vector type: "
-            "expected '" +
-                TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_elem_ty) +
-                "'",
-            value->source);
-        return false;
-      }
-
-      value_cardinality_sum += value_vec->Width();
-    } else {
-      // A vector constructor can only accept vectors and scalars.
-      AddError("expected vector or scalar type in vector constructor; found: " +
-                   TypeNameOf(value_ty),
-               value->source);
-      return false;
-    }
-  }
-
-  // A correct vector constructor must either be a zero-value expression,
-  // a single-value initializer (splat) expression, or the number of components
-  // of all constructor arguments must add up to the vector cardinality.
-  if (value_cardinality_sum > 1 && value_cardinality_sum != vec_type->Width()) {
-    if (values.empty()) {
-      TINT_ICE(Resolver, diagnostics_)
-          << "constructor arguments expected to be non-empty!";
-    }
-    const Source& values_start = values[0]->source;
-    const Source& values_end = values[values.size() - 1]->source;
-    AddError("attempted to construct '" + TypeNameOf(vec_type) + "' with " +
-                 std::to_string(value_cardinality_sum) + " component(s)",
-             Source::Combine(values_start, values_end));
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateVector(const sem::Vector* ty, const Source& source) {
-  if (!ty->type()->is_scalar()) {
-    AddError("vector element type must be 'bool', 'f32', 'i32' or 'u32'",
-             source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateMatrix(const sem::Matrix* ty, const Source& source) {
-  if (!ty->is_float_matrix()) {
-    AddError("matrix element type must be 'f32'", source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateMatrixConstructorOrCast(const ast::CallExpression* ctor,
-                                               const sem::Matrix* matrix_ty) {
-  auto& values = ctor->args;
-  // Zero Value expression
-  if (values.empty()) {
-    return true;
-  }
-
-  if (!ValidateMatrix(matrix_ty, ctor->source)) {
-    return false;
-  }
-
-  std::vector<const sem::Type*> arg_tys;
-  arg_tys.reserve(values.size());
-  for (auto* value : values) {
-    arg_tys.emplace_back(TypeOf(value)->UnwrapRef());
-  }
-
-  auto* elem_type = matrix_ty->type();
-  auto num_elements = matrix_ty->columns() * matrix_ty->rows();
-
-  // Print a generic error for an invalid matrix constructor, showing the
-  // available overloads.
-  auto print_error = [&]() {
-    const Source& values_start = values[0]->source;
-    const Source& values_end = values[values.size() - 1]->source;
-    auto type_name = TypeNameOf(matrix_ty);
-    auto elem_type_name = TypeNameOf(elem_type);
-    std::stringstream ss;
-    ss << "no matching constructor " + type_name << "(";
-    for (size_t i = 0; i < values.size(); i++) {
-      if (i > 0) {
-        ss << ", ";
-      }
-      ss << arg_tys[i]->FriendlyName(builder_->Symbols());
-    }
-    ss << ")" << std::endl << std::endl;
-    ss << "3 candidates available:" << std::endl;
-    ss << "  " << type_name << "()" << std::endl;
-    ss << "  " << type_name << "(" << elem_type_name << ",...,"
-       << elem_type_name << ")"
-       << " // " << std::to_string(num_elements) << " arguments" << std::endl;
-    ss << "  " << type_name << "(";
-    for (uint32_t c = 0; c < matrix_ty->columns(); c++) {
-      if (c > 0) {
-        ss << ", ";
-      }
-      ss << VectorPretty(matrix_ty->rows(), elem_type);
-    }
-    ss << ")" << std::endl;
-    AddError(ss.str(), Source::Combine(values_start, values_end));
-  };
-
-  const sem::Type* expected_arg_type = nullptr;
-  if (num_elements == values.size()) {
-    // Column-major construction from scalar elements.
-    expected_arg_type = matrix_ty->type();
-  } else if (matrix_ty->columns() == values.size()) {
-    // Column-by-column construction from vectors.
-    expected_arg_type = matrix_ty->ColumnType();
-  } else {
-    print_error();
-    return false;
-  }
-
-  for (auto* arg_ty : arg_tys) {
-    if (arg_ty != expected_arg_type) {
-      print_error();
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateScalarConstructorOrCast(const ast::CallExpression* ctor,
-                                               const sem::Type* ty) {
-  if (ctor->args.size() == 0) {
-    return true;
-  }
-  if (ctor->args.size() > 1) {
-    AddError("expected zero or one value in constructor, got " +
-                 std::to_string(ctor->args.size()),
-             ctor->source);
-    return false;
-  }
-
-  // Validate constructor
-  auto* value = ctor->args[0];
-  auto* value_ty = TypeOf(value)->UnwrapRef();
-
-  using Bool = sem::Bool;
-  using I32 = sem::I32;
-  using U32 = sem::U32;
-  using F32 = sem::F32;
-
-  const bool is_valid = (ty->Is<Bool>() && value_ty->is_scalar()) ||
-                        (ty->Is<I32>() && value_ty->is_scalar()) ||
-                        (ty->Is<U32>() && value_ty->is_scalar()) ||
-                        (ty->Is<F32>() && value_ty->is_scalar());
-  if (!is_valid) {
-    AddError("cannot construct '" + TypeNameOf(ty) +
-                 "' with a value of type '" + TypeNameOf(value_ty) + "'",
-             ctor->source);
-
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidatePipelineStages() {
-  auto check_workgroup_storage = [&](const sem::Function* func,
-                                     const sem::Function* entry_point) {
-    auto stage = entry_point->Declaration()->PipelineStage();
-    if (stage != ast::PipelineStage::kCompute) {
-      for (auto* var : func->DirectlyReferencedGlobals()) {
-        if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
-          std::stringstream stage_name;
-          stage_name << stage;
-          for (auto* user : var->Users()) {
-            if (func == user->Stmt()->Function()) {
-              AddError("workgroup memory cannot be used by " +
-                           stage_name.str() + " pipeline stage",
-                       user->Declaration()->source);
-              break;
-            }
-          }
-          AddNote("variable is declared here", var->Declaration()->source);
-          if (func != entry_point) {
-            TraverseCallChain(diagnostics_, entry_point, func,
-                              [&](const sem::Function* f) {
-                                AddNote("called by function '" +
-                                            builder_->Symbols().NameFor(
-                                                f->Declaration()->symbol) +
-                                            "'",
-                                        f->Declaration()->source);
-                              });
-            AddNote("called by entry point '" +
-                        builder_->Symbols().NameFor(
-                            entry_point->Declaration()->symbol) +
-                        "'",
-                    entry_point->Declaration()->source);
-          }
-          return false;
-        }
-      }
-    }
-    return true;
-  };
-
-  for (auto* entry_point : entry_points_) {
-    if (!check_workgroup_storage(entry_point, entry_point)) {
-      return false;
-    }
-    for (auto* func : entry_point->TransitivelyCalledFunctions()) {
-      if (!check_workgroup_storage(func, entry_point)) {
-        return false;
-      }
-    }
-  }
-
-  auto check_builtin_calls = [&](const sem::Function* func,
-                                 const sem::Function* entry_point) {
-    auto stage = entry_point->Declaration()->PipelineStage();
-    for (auto* builtin : func->DirectlyCalledBuiltins()) {
-      if (!builtin->SupportedStages().Contains(stage)) {
-        auto* call = func->FindDirectCallTo(builtin);
-        std::stringstream err;
-        err << "built-in cannot be used by " << stage << " pipeline stage";
-        AddError(err.str(), call ? call->Declaration()->source
-                                 : func->Declaration()->source);
-        if (func != entry_point) {
-          TraverseCallChain(
-              diagnostics_, entry_point, func, [&](const sem::Function* f) {
-                AddNote(
-                    "called by function '" +
-                        builder_->Symbols().NameFor(f->Declaration()->symbol) +
-                        "'",
-                    f->Declaration()->source);
-              });
-          AddNote("called by entry point '" +
-                      builder_->Symbols().NameFor(
-                          entry_point->Declaration()->symbol) +
-                      "'",
-                  entry_point->Declaration()->source);
-        }
-        return false;
-      }
-    }
-    return true;
-  };
-
-  for (auto* entry_point : entry_points_) {
-    if (!check_builtin_calls(entry_point, entry_point)) {
-      return false;
-    }
-    for (auto* func : entry_point->TransitivelyCalledFunctions()) {
-      if (!check_builtin_calls(func, entry_point)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-bool Resolver::ValidateArray(const sem::Array* arr, const Source& source) {
-  auto* el_ty = arr->ElemType();
-
-  if (!IsFixedFootprint(el_ty)) {
-    AddError("an array element type cannot contain a runtime-sized array",
-             source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateArrayStrideAttribute(const ast::StrideAttribute* attr,
-                                            uint32_t el_size,
-                                            uint32_t el_align,
-                                            const Source& source) {
-  auto stride = attr->stride;
-  bool is_valid_stride =
-      (stride >= el_size) && (stride >= el_align) && (stride % el_align == 0);
-  if (!is_valid_stride) {
-    // https://gpuweb.github.io/gpuweb/wgsl/#array-layout-rules
-    // Arrays decorated with the stride attribute must have a stride that is
-    // at least the size of the element type, and be a multiple of the
-    // element type's alignment value.
-    AddError(
-        "arrays decorated with the stride attribute must have a stride "
-        "that is at least the size of the element type, and be a multiple "
-        "of the element type's alignment value.",
-        source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateAlias(const ast::Alias* alias) {
-  auto name = builder_->Symbols().NameFor(alias->name);
-  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
-    AddError("'" + name + "' is a builtin and cannot be redeclared as an alias",
-             alias->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateStructure(const sem::Struct* str) {
-  auto name = builder_->Symbols().NameFor(str->Declaration()->name);
-  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
-    AddError("'" + name + "' is a builtin and cannot be redeclared as a struct",
-             str->Declaration()->source);
-    return false;
-  }
-
-  if (str->Members().empty()) {
-    AddError("structures must have at least one member",
-             str->Declaration()->source);
-    return false;
-  }
-
-  std::unordered_set<uint32_t> locations;
-  for (auto* member : str->Members()) {
-    if (auto* r = member->Type()->As<sem::Array>()) {
-      if (r->IsRuntimeSized()) {
-        if (member != str->Members().back()) {
-          AddError(
-              "runtime arrays may only appear as the last member of a struct",
-              member->Declaration()->source);
-          return false;
-        }
-      }
-    } else if (!IsFixedFootprint(member->Type())) {
-      AddError(
-          "a struct that contains a runtime array cannot be nested inside "
-          "another struct",
-          member->Declaration()->source);
-      return false;
-    }
-
-    auto has_location = false;
-    auto has_position = false;
-    const ast::InvariantAttribute* invariant_attribute = nullptr;
-    const ast::InterpolateAttribute* interpolate_attribute = nullptr;
-    for (auto* attr : member->Declaration()->attributes) {
-      if (!attr->IsAnyOf<ast::BuiltinAttribute,             //
-                         ast::InternalAttribute,            //
-                         ast::InterpolateAttribute,         //
-                         ast::InvariantAttribute,           //
-                         ast::LocationAttribute,            //
-                         ast::StructMemberOffsetAttribute,  //
-                         ast::StructMemberSizeAttribute,    //
-                         ast::StructMemberAlignAttribute>()) {
-        if (attr->Is<ast::StrideAttribute>() &&
-            IsValidationDisabled(
-                member->Declaration()->attributes,
-                ast::DisabledValidation::kIgnoreStrideAttribute)) {
-          continue;
-        }
-        AddError("attribute is not valid for structure members", attr->source);
-        return false;
-      }
-
-      if (auto* invariant = attr->As<ast::InvariantAttribute>()) {
-        invariant_attribute = invariant;
-      } else if (auto* location = attr->As<ast::LocationAttribute>()) {
-        has_location = true;
-        if (!ValidateLocationAttribute(location, member->Type(), locations,
-                                       member->Declaration()->source)) {
-          return false;
-        }
-      } else if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-        if (!ValidateBuiltinAttribute(builtin, member->Type(),
-                                      /* is_input */ false)) {
-          return false;
-        }
-        if (builtin->builtin == ast::Builtin::kPosition) {
-          has_position = true;
-        }
-      } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-        interpolate_attribute = interpolate;
-        if (!ValidateInterpolateAttribute(interpolate, member->Type())) {
-          return false;
-        }
-      }
-    }
-
-    if (invariant_attribute && !has_position) {
-      AddError("invariant attribute must only be applied to a position builtin",
-               invariant_attribute->source);
-      return false;
-    }
-
-    if (interpolate_attribute && !has_location) {
-      AddError("interpolate attribute must only be used with @location",
-               interpolate_attribute->source);
-      return false;
-    }
-  }
-
-  for (auto* attr : str->Declaration()->attributes) {
-    if (!(attr->IsAnyOf<ast::StructBlockAttribute, ast::InternalAttribute>())) {
-      AddError("attribute is not valid for struct declarations", attr->source);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateLocationAttribute(
-    const ast::LocationAttribute* location,
-    const sem::Type* type,
-    std::unordered_set<uint32_t>& locations,
-    const Source& source,
-    const bool is_input) {
-  std::string inputs_or_output = is_input ? "inputs" : "output";
-  if (current_function_ && current_function_->Declaration()->PipelineStage() ==
-                               ast::PipelineStage::kCompute) {
-    AddError("attribute is not valid for compute shader " + inputs_or_output,
-             location->source);
-    return false;
-  }
-
-  if (!type->is_numeric_scalar_or_vector()) {
-    std::string invalid_type = TypeNameOf(type);
-    AddError("cannot apply 'location' attribute to declaration of type '" +
-                 invalid_type + "'",
-             source);
-    AddNote(
-        "'location' attribute must only be applied to declarations of "
-        "numeric scalar or numeric vector type",
-        location->source);
-    return false;
-  }
-
-  if (locations.count(location->value)) {
-    AddError(attr_to_str(location) + " attribute appears multiple times",
-             location->source);
-    return false;
-  }
-  locations.emplace(location->value);
-
-  return true;
-}
-
-bool Resolver::ValidateReturn(const ast::ReturnStatement* ret) {
-  auto* func_type = current_function_->ReturnType();
-
-  auto* ret_type = ret->value ? TypeOf(ret->value)->UnwrapRef()
-                              : builder_->create<sem::Void>();
-
-  if (func_type->UnwrapRef() != ret_type) {
-    AddError(
-        "return statement type must match its function "
-        "return type, returned '" +
-            TypeNameOf(ret_type) + "', expected '" + TypeNameOf(func_type) +
-            "'",
-        ret->source);
-    return false;
-  }
-
-  auto* sem = Sem(ret);
-  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
-    AddError("continuing blocks must not contain a return statement",
-             ret->source);
-    if (continuing != sem->Declaration() &&
-        continuing != sem->Parent()->Declaration()) {
-      AddNote("see continuing block here", continuing->source);
-    }
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
-  auto* cond_ty = TypeOf(s->condition)->UnwrapRef();
-  if (!cond_ty->is_integer_scalar()) {
-    AddError(
-        "switch statement selector expression must be of a "
-        "scalar integer type",
-        s->condition->source);
-    return false;
-  }
-
-  bool has_default = false;
-  std::unordered_map<uint32_t, Source> selectors;
-
-  for (auto* case_stmt : s->body) {
-    if (case_stmt->IsDefault()) {
-      if (has_default) {
-        // More than one default clause
-        AddError("switch statement must have exactly one default clause",
-                 case_stmt->source);
-        return false;
-      }
-      has_default = true;
-    }
-
-    for (auto* selector : case_stmt->selectors) {
-      if (cond_ty != TypeOf(selector)) {
-        AddError(
-            "the case selector values must have the same "
-            "type as the selector expression.",
-            case_stmt->source);
-        return false;
-      }
-
-      auto v = selector->ValueAsU32();
-      auto it = selectors.find(v);
-      if (it != selectors.end()) {
-        auto val = selector->Is<ast::IntLiteralExpression>()
-                       ? std::to_string(selector->ValueAsI32())
-                       : std::to_string(selector->ValueAsU32());
-        AddError("duplicate switch case '" + val + "'", selector->source);
-        AddNote("previous case declared here", it->second);
-        return false;
-      }
-      selectors.emplace(v, selector->source);
-    }
-  }
-
-  if (!has_default) {
-    // No default clause
-    AddError("switch statement must have a default clause", s->source);
-    return false;
-  }
-
-  return true;
-}
-
-bool Resolver::ValidateAssignment(const ast::AssignmentStatement* a) {
-  auto const* rhs_ty = TypeOf(a->rhs);
-
-  if (a->lhs->Is<ast::PhonyExpression>()) {
-    // https://www.w3.org/TR/WGSL/#phony-assignment-section
-    auto* ty = rhs_ty->UnwrapRef();
-    if (!ty->IsConstructible() &&
-        !ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler>()) {
-      AddError(
-          "cannot assign '" + TypeNameOf(rhs_ty) +
-              "' to '_'. '_' can only be assigned a constructible, pointer, "
-              "texture or sampler type",
-          a->rhs->source);
-      return false;
-    }
-    return true;  // RHS can be anything.
-  }
-
-  // https://gpuweb.github.io/gpuweb/wgsl/#assignment-statement
-  auto const* lhs_ty = TypeOf(a->lhs);
-
-  if (auto* var = ResolvedSymbol<sem::Variable>(a->lhs)) {
-    auto* decl = var->Declaration();
-    if (var->Is<sem::Parameter>()) {
-      AddError("cannot assign to function parameter", a->lhs->source);
-      AddNote("'" + builder_->Symbols().NameFor(decl->symbol) +
-                  "' is declared here:",
-              decl->source);
-      return false;
-    }
-    if (decl->is_const) {
-      AddError("cannot assign to const", a->lhs->source);
-      AddNote("'" + builder_->Symbols().NameFor(decl->symbol) +
-                  "' is declared here:",
-              decl->source);
-      return false;
-    }
-  }
-
-  auto* lhs_ref = lhs_ty->As<sem::Reference>();
-  if (!lhs_ref) {
-    // LHS is not a reference, so it has no storage.
-    AddError("cannot assign to value of type '" + TypeNameOf(lhs_ty) + "'",
-             a->lhs->source);
-    return false;
-  }
-
-  auto* storage_ty = lhs_ref->StoreType();
-  auto* value_type = rhs_ty->UnwrapRef();  // Implicit load of RHS
-
-  // Value type has to match storage type
-  if (storage_ty != value_type) {
-    AddError("cannot assign '" + TypeNameOf(rhs_ty) + "' to '" +
-                 TypeNameOf(lhs_ty) + "'",
-             a->source);
-    return false;
-  }
-  if (!storage_ty->IsConstructible()) {
-    AddError("storage type of assignment must be constructible", a->source);
-    return false;
-  }
-  if (lhs_ref->Access() == ast::Access::kRead) {
-    AddError(
-        "cannot store into a read-only type '" + RawTypeNameOf(lhs_ty) + "'",
-        a->source);
-    return false;
-  }
-  return true;
-}
-
-bool Resolver::ValidateNoDuplicateAttributes(
-    const ast::AttributeList& attributes) {
-  std::unordered_map<const TypeInfo*, Source> seen;
-  for (auto* d : attributes) {
-    auto res = seen.emplace(&d->TypeInfo(), d->source);
-    if (!res.second && !d->Is<ast::InternalAttribute>()) {
-      AddError("duplicate " + d->Name() + " attribute", d->source);
-      AddNote("first attribute declared here", res.first->second);
-      return false;
-    }
-  }
-  return true;
-}
-
-bool Resolver::IsValidationDisabled(const ast::AttributeList& attributes,
-                                    ast::DisabledValidation validation) const {
-  for (auto* attribute : attributes) {
-    if (auto* dv = attribute->As<ast::DisableValidationAttribute>()) {
-      if (dv->validation == validation) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-bool Resolver::IsValidationEnabled(const ast::AttributeList& attributes,
-                                   ast::DisabledValidation validation) const {
-  return !IsValidationDisabled(attributes, validation);
-}
-
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/side_effects_test.cc b/src/resolver/side_effects_test.cc
deleted file mode 100644
index fc20cc1..0000000
--- a/src/resolver/side_effects_test.cc
+++ /dev/null
@@ -1,371 +0,0 @@
-// 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/resolver/resolver.h"
-
-#include "gtest/gtest.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/expression.h"
-#include "src/sem/member_accessor_expression.h"
-
-namespace tint::resolver {
-namespace {
-
-struct SideEffectsTest : ResolverTest {
-  template <typename T>
-  void MakeSideEffectFunc(const char* name) {
-    auto global = Sym();
-    Global(global, ty.Of<T>(), ast::StorageClass::kPrivate);
-    auto local = Sym();
-    Func(name, {}, ty.Of<T>(),
-         {
-             Decl(Var(local, ty.Of<T>())),
-             Assign(global, local),
-             Return(global),
-         });
-  }
-
-  template <typename MAKE_TYPE_FUNC>
-  void MakeSideEffectFunc(const char* name, MAKE_TYPE_FUNC make_type) {
-    auto global = Sym();
-    Global(global, make_type(), ast::StorageClass::kPrivate);
-    auto local = Sym();
-    Func(name, {}, make_type(),
-         {
-             Decl(Var(local, make_type())),
-             Assign(global, local),
-             Return(global),
-         });
-  }
-};
-
-TEST_F(SideEffectsTest, Phony) {
-  auto* expr = Phony();
-  auto* body = Assign(expr, 1);
-  WrapInFunction(body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Literal) {
-  auto* expr = Expr(1);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, VariableUser) {
-  auto* var = Decl(Var("a", ty.i32()));
-  auto* expr = Expr("a");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::VariableUser>());
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_Builtin_NoSE) {
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call("dpdx", "a");
-  Func("f", {}, ty.void_(), {Ignore(expr)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_Builtin_NoSE_WithSEArg) {
-  MakeSideEffectFunc<f32>("se");
-  auto* expr = Call("dpdx", Call("se"));
-  Func("f", {}, ty.void_(), {Ignore(expr)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_Builtin_SE) {
-  Global("a", ty.atomic(ty.i32()), ast::StorageClass::kWorkgroup);
-  auto* expr = Call("atomicAdd", AddressOf("a"), 1);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_Function) {
-  Func("f", {}, ty.i32(), {Return(1)});
-  auto* expr = Call("f");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_TypeConversion_NoSE) {
-  auto* var = Decl(Var("a", ty.i32()));
-  auto* expr = Construct(ty.f32(), "a");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_TypeConversion_SE) {
-  MakeSideEffectFunc<i32>("se");
-  auto* expr = Construct(ty.f32(), Call("se"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_TypeConstructor_NoSE) {
-  auto* var = Decl(Var("a", ty.f32()));
-  auto* expr = Construct(ty.f32(), "a");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Call_TypeConstructor_SE) {
-  MakeSideEffectFunc<f32>("se");
-  auto* expr = Construct(ty.f32(), Call("se"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->Is<sem::Call>());
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, MemberAccessor_Struct_NoSE) {
-  auto* s = Structure("S", {Member("m", ty.i32())});
-  auto* var = Decl(Var("a", ty.Of(s)));
-  auto* expr = MemberAccessor("a", "m");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, MemberAccessor_Struct_SE) {
-  auto* s = Structure("S", {Member("m", ty.i32())});
-  MakeSideEffectFunc("se", [&] { return ty.Of(s); });
-  auto* expr = MemberAccessor(Call("se"), "m");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, MemberAccessor_Vector) {
-  auto* var = Decl(Var("a", ty.vec4<f32>()));
-  auto* expr = MemberAccessor("a", "x");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  EXPECT_TRUE(sem->Is<sem::MemberAccessorExpression>());
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, MemberAccessor_VectorSwizzle) {
-  auto* var = Decl(Var("a", ty.vec4<f32>()));
-  auto* expr = MemberAccessor("a", "xzyw");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  EXPECT_TRUE(sem->Is<sem::Swizzle>());
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Binary_NoSE) {
-  auto* a = Decl(Var("a", ty.i32()));
-  auto* b = Decl(Var("b", ty.i32()));
-  auto* expr = Add("a", "b");
-  WrapInFunction(a, b, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Binary_LeftSE) {
-  MakeSideEffectFunc<i32>("se");
-  auto* b = Decl(Var("b", ty.i32()));
-  auto* expr = Add(Call("se"), "b");
-  WrapInFunction(b, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Binary_RightSE) {
-  MakeSideEffectFunc<i32>("se");
-  auto* a = Decl(Var("a", ty.i32()));
-  auto* expr = Add("a", Call("se"));
-  WrapInFunction(a, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Binary_BothSE) {
-  MakeSideEffectFunc<i32>("se1");
-  MakeSideEffectFunc<i32>("se2");
-  auto* expr = Add(Call("se1"), Call("se2"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Unary_NoSE) {
-  auto* var = Decl(Var("a", ty.bool_()));
-  auto* expr = Not("a");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Unary_SE) {
-  MakeSideEffectFunc<bool>("se");
-  auto* expr = Not(Call("se"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, IndexAccessor_NoSE) {
-  auto* var = Decl(Var("a", ty.array<i32, 10>()));
-  auto* expr = IndexAccessor("a", 0);
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, IndexAccessor_ObjSE) {
-  MakeSideEffectFunc("se", [&] { return ty.array<i32, 10>(); });
-  auto* expr = IndexAccessor(Call("se"), 0);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, IndexAccessor_IndexSE) {
-  MakeSideEffectFunc<i32>("se");
-  auto* var = Decl(Var("a", ty.array<i32, 10>()));
-  auto* expr = IndexAccessor("a", Call("se"));
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, IndexAccessor_BothSE) {
-  MakeSideEffectFunc("se1", [&] { return ty.array<i32, 10>(); });
-  MakeSideEffectFunc<i32>("se2");
-  auto* expr = IndexAccessor(Call("se1"), Call("se2"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Bitcast_NoSE) {
-  auto* var = Decl(Var("a", ty.i32()));
-  auto* expr = Bitcast<f32>("a");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_FALSE(sem->HasSideEffects());
-}
-
-TEST_F(SideEffectsTest, Bitcast_SE) {
-  MakeSideEffectFunc<i32>("se");
-  auto* expr = Bitcast<f32>(Call("se"));
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  auto* sem = Sem().Get(expr);
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->HasSideEffects());
-}
-
-}  // namespace
-}  // namespace tint::resolver
diff --git a/src/resolver/storage_class_layout_validation_test.cc b/src/resolver/storage_class_layout_validation_test.cc
deleted file mode 100644
index 9355082..0000000
--- a/src/resolver/storage_class_layout_validation_test.cc
+++ /dev/null
@@ -1,573 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverStorageClassLayoutValidationTest = ResolverTest;
-
-// Detect unaligned member for storage buffers
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       StorageBuffer_UnalignedMember) {
-  // [[block]]
-  // struct S {
-  //     @size(5) a : f32;
-  //     @align(1) b : f32;
-  // };
-  // @group(0) @binding(0)
-  // var<storage> a : S;
-
-  Structure(Source{{12, 34}}, "S",
-            {Member("a", ty.f32(), {MemberSize(5)}),
-             Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(1)})},
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
-         GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: the offset of a struct member of type 'f32' in storage class 'storage' must be a multiple of 4 bytes, but 'b' is currently at offset 5. Consider setting @align(4) on this member
-12:34 note: see layout of struct:
-/*           align(4) size(12) */ struct S {
-/* offset(0) align(4) size( 5) */   a : f32;
-/* offset(5) align(1) size( 4) */   b : f32;
-/* offset(9) align(1) size( 3) */   // -- implicit struct size padding --;
-/*                             */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       StorageBuffer_UnalignedMember_SuggestedFix) {
-  // [[block]]
-  // struct S {
-  //     @size(5) a : f32;
-  //     @align(4) b : f32;
-  // };
-  // @group(0) @binding(0)
-  // var<storage> a : S;
-
-  Structure(Source{{12, 34}}, "S",
-            {Member("a", ty.f32(), {MemberSize(5)}),
-             Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(4)})},
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
-         GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Detect unaligned struct member for uniform buffers
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_UnalignedMember_Struct) {
-  // struct Inner {
-  //   scalar : i32;
-  // };
-  //
-  // [[block]]
-  // struct Outer {
-  //   scalar : f32;
-  //   inner : Inner;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
-
-  Structure(Source{{34, 56}}, "Outer",
-            {
-                Member("scalar", ty.f32()),
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: the offset of a struct member of type 'Inner' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
-34:56 note: see layout of struct:
-/*           align(4) size(8) */ struct Outer {
-/* offset(0) align(4) size(4) */   scalar : f32;
-/* offset(4) align(4) size(4) */   inner : Inner;
-/*                            */ };
-12:34 note: and layout of struct member:
-/*           align(4) size(4) */ struct Inner {
-/* offset(0) align(4) size(4) */   scalar : i32;
-/*                            */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_UnalignedMember_Struct_SuggestedFix) {
-  // struct Inner {
-  //   scalar : i32;
-  // };
-  //
-  // [[block]]
-  // struct Outer {
-  //   scalar : f32;
-  //   @align(16) inner : Inner;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
-
-  Structure(Source{{34, 56}}, "Outer",
-            {
-                Member("scalar", ty.f32()),
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner"),
-                       {MemberAlign(16)}),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Detect unaligned array member for uniform buffers
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_UnalignedMember_Array) {
-  // type Inner = @stride(16) array<f32, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   scalar : f32;
-  //   inner : Inner;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-  Alias("Inner", ty.array(ty.f32(), 10, 16));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("scalar", ty.f32()),
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: the offset of a struct member of type '@stride(16) array<f32, 10>' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
-12:34 note: see layout of struct:
-/*             align(4) size(164) */ struct Outer {
-/* offset(  0) align(4) size(  4) */   scalar : f32;
-/* offset(  4) align(4) size(160) */   inner : @stride(16) array<f32, 10>;
-/*                                */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_UnalignedMember_Array_SuggestedFix) {
-  // type Inner = @stride(16) array<f32, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   scalar : f32;
-  //   @align(16) inner : Inner;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-  Alias("Inner", ty.array(ty.f32(), 10, 16));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("scalar", ty.f32()),
-                Member(Source{{34, 56}}, "inner", ty.type_name("Inner"),
-                       {MemberAlign(16)}),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Detect uniform buffers with byte offset between 2 members that is not a
-// multiple of 16 bytes
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_MembersOffsetNotMultipleOf16) {
-  // struct Inner {
-  //   @align(1) @size(5) scalar : i32;
-  // };
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Structure(Source{{12, 34}}, "Inner",
-            {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
-
-  Structure(Source{{34, 56}}, "Outer",
-            {
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-                Member(Source{{78, 90}}, "scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 8 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
-34:56 note: see layout of struct:
-/*            align(4) size(12) */ struct Outer {
-/* offset( 0) align(1) size( 5) */   inner : Inner;
-/* offset( 5) align(1) size( 3) */   // -- implicit field alignment padding --;
-/* offset( 8) align(4) size( 4) */   scalar : i32;
-/*                              */ };
-12:34 note: and layout of previous member struct:
-/*           align(1) size(5) */ struct Inner {
-/* offset(0) align(1) size(5) */   scalar : i32;
-/*                            */ };
-22:24 note: see declaration of variable)");
-}
-
-// See https://crbug.com/tint/1344
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_MembersOffsetNotMultipleOf16_InnerMoreMembersThanOuter) {
-  // struct Inner {
-  //   a : i32;
-  //   b : i32;
-  //   c : i32;
-  //   @align(1) @size(5) scalar : i32;
-  // };
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Structure(Source{{12, 34}}, "Inner",
-            {
-                Member("a", ty.i32()),
-                Member("b", ty.i32()),
-                Member("c", ty.i32()),
-                Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)}),
-            });
-
-  Structure(Source{{34, 56}}, "Outer",
-            {
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-                Member(Source{{78, 90}}, "scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 20 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
-34:56 note: see layout of struct:
-/*            align(4) size(24) */ struct Outer {
-/* offset( 0) align(4) size(20) */   inner : Inner;
-/* offset(20) align(4) size( 4) */   scalar : i32;
-/*                              */ };
-12:34 note: and layout of previous member struct:
-/*            align(4) size(20) */ struct Inner {
-/* offset( 0) align(4) size( 4) */   a : i32;
-/* offset( 4) align(4) size( 4) */   b : i32;
-/* offset( 8) align(4) size( 4) */   c : i32;
-/* offset(12) align(1) size( 5) */   scalar : i32;
-/* offset(17) align(1) size( 3) */   // -- implicit struct size padding --;
-/*                              */ };
-22:24 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_MembersOffsetNotMultipleOf16_SuggestedFix) {
-  // struct Inner {
-  //   @align(1) @size(5) scalar : i32;
-  // };
-  //
-  // [[block]]
-  // struct Outer {
-  //   @align(16) inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Structure(Source{{12, 34}}, "Inner",
-            {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
-
-  Structure(Source{{34, 56}}, "Outer",
-            {
-                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-                Member(Source{{78, 90}}, "scalar", ty.i32(), {MemberAlign(16)}),
-            },
-            {StructBlock()});
-
-  Global(Source{{22, 34}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Make sure that this doesn't fail validation because vec3's align is 16, but
-// size is 12. 's' should be at offset 12, which is okay here.
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_Vec3MemberOffset_NoFail) {
-  // [[block]]
-  // struct ScalarPackedAtEndOfVec3 {
-  //     v : vec3<f32>;
-  //     s : f32;
-  // };
-  // @group(0) @binding(0)
-  // var<uniform> a : ScalarPackedAtEndOfVec3;
-
-  Structure("ScalarPackedAtEndOfVec3",
-            {
-                Member("v", ty.vec3(ty.f32())),
-                Member("s", ty.f32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-// Detect array stride must be a multiple of 16 bytes for uniform buffers
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_Scalar) {
-  // type Inner = array<f32, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Alias("Inner", ty.array(ty.f32(), 10));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
-                Member("scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
-12:34 note: see layout of struct:
-/*            align(4) size(44) */ struct Outer {
-/* offset( 0) align(4) size(40) */   inner : array<f32, 10>;
-/* offset(40) align(4) size( 4) */   scalar : i32;
-/*                              */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_Vector) {
-  // type Inner = array<vec2<f32>, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Alias("Inner", ty.array(ty.vec2<f32>(), 10));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
-                Member("scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using a vec4 instead.
-12:34 note: see layout of struct:
-/*            align(8) size(88) */ struct Outer {
-/* offset( 0) align(8) size(80) */   inner : array<vec2<f32>, 10>;
-/* offset(80) align(4) size( 4) */   scalar : i32;
-/* offset(84) align(1) size( 4) */   // -- implicit struct size padding --;
-/*                              */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_Struct) {
-  // struct ArrayElem {
-  //   a : f32;
-  //   b : i32;
-  // }
-  // type Inner = array<ArrayElem, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  auto* array_elem = Structure("ArrayElem", {
-                                                Member("a", ty.f32()),
-                                                Member("b", ty.i32()),
-                                            });
-  Alias("Inner", ty.array(ty.Of(array_elem), 10));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
-                Member("scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using the @size attribute on the last struct member.
-12:34 note: see layout of struct:
-/*            align(4) size(84) */ struct Outer {
-/* offset( 0) align(4) size(80) */   inner : array<ArrayElem, 10>;
-/* offset(80) align(4) size( 4) */   scalar : i32;
-/*                              */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_TopLevelArray) {
-  // @group(0) @binding(0)
-  // var<uniform> a : array<f32, 4>;
-  Global(Source{{78, 90}}, "a", ty.array(Source{{34, 56}}, ty.f32(), 4),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_NestedArray) {
-  // struct Outer {
-  //   inner : array<array<f32, 4>, 4>
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : array<Outer, 4>;
-
-  Structure(
-      Source{{12, 34}}, "Outer",
-      {
-          Member("inner", ty.array(Source{{34, 56}}, ty.array(ty.f32(), 4), 4)),
-      },
-      {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
-12:34 note: see layout of struct:
-/*            align(4) size(64) */ struct Outer {
-/* offset( 0) align(4) size(64) */   inner : array<array<f32, 4>, 4>;
-/*                              */ };
-78:90 note: see declaration of variable)");
-}
-
-TEST_F(ResolverStorageClassLayoutValidationTest,
-       UniformBuffer_InvalidArrayStride_SuggestedFix) {
-  // type Inner = @stride(16) array<f32, 10>;
-  //
-  // [[block]]
-  // struct Outer {
-  //   inner : Inner;
-  //   scalar : i32;
-  // };
-  //
-  // @group(0) @binding(0)
-  // var<uniform> a : Outer;
-
-  Alias("Inner", ty.array(ty.f32(), 10, 16));
-
-  Structure(Source{{12, 34}}, "Outer",
-            {
-                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
-                Member("scalar", ty.i32()),
-            },
-            {StructBlock()});
-
-  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
-         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
deleted file mode 100644
index 07e8f3d..0000000
--- a/src/resolver/storage_class_validation_test.cc
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/struct.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverStorageClassValidationTest = ResolverTest;
-
-TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
-  // var g : f32;
-  Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: global variables must have a storage class");
-}
-
-TEST_F(ResolverStorageClassValidationTest,
-       GlobalVariableFunctionStorageClass_Fail) {
-  // var<function> g : f32;
-  Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kFunction);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: variables declared at module scope must not be in "
-            "the function storage class");
-}
-
-TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) {
-  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-12:34 note: while instantiating variable v)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArrayInStruct) {
-  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
-  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-note: while analysing structure member S.m
-12:34 note: while instantiating variable v)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArray) {
-  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
-         ast::StorageClass::kWorkgroup);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-12:34 note: while instantiating variable v)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArrayInStruct) {
-  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
-  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kWorkgroup);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-note: while analysing structure member S.m
-12:34 note: while instantiating variable v)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
-  // var<storage> g : bool;
-  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
-  // var<storage> g : ptr<private, f32>;
-  Global(Source{{56, 78}}, "g",
-         ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
-         ast::StorageClass::kStorage,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'storage' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
-  // var<storage> g : i32;
-  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferVector) {
-  // var<storage> g : vec4<f32>;
-  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
-  // var<storage, read> g : array<S, 3>;
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(ty.Of(s), 3);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
-  // type a = bool;
-  // var<storage, read> g : a;
-  auto* a = Alias("a", ty.bool_());
-  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, NotStorage_AccessMode) {
-  // var<private, read> g : a;
-  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kPrivate,
-         ast::Access::kRead);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: only variables in <storage> storage class may declare an access mode)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
-  // [[block]] struct S { x : i32 };
-  // var<storage, read> g : S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
-  // [[block]] struct S { x : i32 };
-  // type a1 = S;
-  // var<storage, read> g : a1;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* a1 = Alias("a1", ty.Of(s));
-  auto* a2 = Alias("a2", ty.Of(a1));
-  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBuffer_Struct_Runtime) {
-  // [[block]] struct S { m:  array<f32>; };
-  // @group(0) @binding(0) var<uniform, > svar : S;
-
-  auto* s = Structure(Source{{12, 34}}, "S", {Member("m", ty.array<i32>())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
-note: while analysing structure member S.m
-56:78 note: while instantiating variable svar)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
-  // var<uniform> g : bool;
-  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
-  // var<uniform> g : ptr<private, f32>;
-  Global(Source{{56, 78}}, "g",
-         ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
-         ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'uniform' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
-  // var<uniform> g : i32;
-  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferVector) {
-  // var<uniform> g : vec4<f32>;
-  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
-  // struct S {
-  //   @size(16) f : f32;
-  // }
-  // var<uniform> g : array<S, 3>;
-  auto* s = Structure("S", {Member("a", ty.f32(), {MemberSize(16)})});
-  auto* a = ty.array(ty.Of(s), 3);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
-  // type a = bool;
-  // var<uniform> g : a;
-  auto* a = Alias("a", ty.bool_());
-  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
-  // [[block]] struct S { x : i32 };
-  // var<uniform> g :  S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Aliases) {
-  // [[block]] struct S { x : i32 };
-  // type a1 = S;
-  // var<uniform> g : a1;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* a1 = Alias("a1", ty.Of(s));
-  Global(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
deleted file mode 100644
index d3cbeed..0000000
--- a/src/resolver/struct_layout_test.cc
+++ /dev/null
@@ -1,410 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/struct.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverStructLayoutTest = ResolverTest;
-
-TEST_F(ResolverStructLayoutTest, Scalars) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.u32()),
-                               Member("c", ty.i32()),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 12u);
-  EXPECT_EQ(sem->SizeNoPadding(), 12u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 3u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 8u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, Alias) {
-  auto* alias_a = Alias("a", ty.f32());
-  auto* alias_b = Alias("b", ty.f32());
-
-  auto* s = Structure("S", {
-                               Member("a", ty.Of(alias_a)),
-                               Member("b", ty.Of(alias_b)),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 8u);
-  EXPECT_EQ(sem->SizeNoPadding(), 8u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 2u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
-  auto* s = Structure("S", {
-                               Member("a", ty.array<i32, 3>()),
-                               Member("b", ty.array<f32, 5>()),
-                               Member("c", ty.array<f32, 1>()),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 36u);
-  EXPECT_EQ(sem->SizeNoPadding(), 36u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 3u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 12u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 12u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 20u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
-  auto* s = Structure("S", {
-                               Member("a", ty.array<i32, 3>(/*stride*/ 8)),
-                               Member("b", ty.array<f32, 5>(/*stride*/ 16)),
-                               Member("c", ty.array<f32, 1>(/*stride*/ 32)),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 136u);
-  EXPECT_EQ(sem->SizeNoPadding(), 136u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 3u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 24u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 24u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 80u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 104u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
-}
-
-TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
-  auto* s = Structure("S",
-                      {
-                          Member("c", ty.array<f32>()),
-                      },
-                      ast::AttributeList{create<ast::StructBlockAttribute>()});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 4u);
-  EXPECT_EQ(sem->SizeNoPadding(), 4u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 1u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
-  auto* s = Structure("S",
-                      {
-                          Member("c", ty.array<f32>(/*stride*/ 32)),
-                      },
-                      ast::AttributeList{create<ast::StructBlockAttribute>()});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 32u);
-  EXPECT_EQ(sem->SizeNoPadding(), 32u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 1u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 32u);
-}
-
-TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
-  auto* inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
-  auto* outer = ty.array(inner, 12);              // size: 12 * 32
-  auto* s = Structure("S", {
-                               Member("c", outer),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 384u);
-  EXPECT_EQ(sem->SizeNoPadding(), 384u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 1u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 384u);
-}
-
-TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec2<i32>()),
-                                       Member("b", ty.vec3<i32>()),
-                                       Member("c", ty.vec4<i32>()),
-                                   });       // size: 48
-  auto* outer = ty.array(ty.Of(inner), 12);  // size: 12 * 48
-  auto* s = Structure("S", {
-                               Member("c", outer),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 576u);
-  EXPECT_EQ(sem->SizeNoPadding(), 576u);
-  EXPECT_EQ(sem->Align(), 16u);
-  ASSERT_EQ(sem->Members().size(), 1u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 576u);
-}
-
-TEST_F(ResolverStructLayoutTest, Vector) {
-  auto* s = Structure("S", {
-                               Member("a", ty.vec2<i32>()),
-                               Member("b", ty.vec3<i32>()),
-                               Member("c", ty.vec4<i32>()),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 48u);
-  EXPECT_EQ(sem->SizeNoPadding(), 48u);
-  EXPECT_EQ(sem->Align(), 16u);
-  ASSERT_EQ(sem->Members().size(), 3u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // vec2
-  EXPECT_EQ(sem->Members()[0]->Align(), 8u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 8u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // vec3
-  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 12u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);  // vec4
-  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 16u);
-}
-
-TEST_F(ResolverStructLayoutTest, Matrix) {
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<f32>()),
-                               Member("b", ty.mat2x3<f32>()),
-                               Member("c", ty.mat2x4<f32>()),
-                               Member("d", ty.mat3x2<f32>()),
-                               Member("e", ty.mat3x3<f32>()),
-                               Member("f", ty.mat3x4<f32>()),
-                               Member("g", ty.mat4x2<f32>()),
-                               Member("h", ty.mat4x3<f32>()),
-                               Member("i", ty.mat4x4<f32>()),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 368u);
-  EXPECT_EQ(sem->SizeNoPadding(), 368u);
-  EXPECT_EQ(sem->Align(), 16u);
-  ASSERT_EQ(sem->Members().size(), 9u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // mat2x2
-  EXPECT_EQ(sem->Members()[0]->Align(), 8u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 16u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // mat2x3
-  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 32u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 48u);  // mat2x4
-  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
-  EXPECT_EQ(sem->Members()[3]->Offset(), 80u);  // mat3x2
-  EXPECT_EQ(sem->Members()[3]->Align(), 8u);
-  EXPECT_EQ(sem->Members()[3]->Size(), 24u);
-  EXPECT_EQ(sem->Members()[4]->Offset(), 112u);  // mat3x3
-  EXPECT_EQ(sem->Members()[4]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[4]->Size(), 48u);
-  EXPECT_EQ(sem->Members()[5]->Offset(), 160u);  // mat3x4
-  EXPECT_EQ(sem->Members()[5]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[5]->Size(), 48u);
-  EXPECT_EQ(sem->Members()[6]->Offset(), 208u);  // mat4x2
-  EXPECT_EQ(sem->Members()[6]->Align(), 8u);
-  EXPECT_EQ(sem->Members()[6]->Size(), 32u);
-  EXPECT_EQ(sem->Members()[7]->Offset(), 240u);  // mat4x3
-  EXPECT_EQ(sem->Members()[7]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[7]->Size(), 64u);
-  EXPECT_EQ(sem->Members()[8]->Offset(), 304u);  // mat4x4
-  EXPECT_EQ(sem->Members()[8]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[8]->Size(), 64u);
-}
-
-TEST_F(ResolverStructLayoutTest, NestedStruct) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.mat3x3<f32>()),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.Of(inner)),
-                               Member("c", ty.i32()),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 80u);
-  EXPECT_EQ(sem->SizeNoPadding(), 68u);
-  EXPECT_EQ(sem->Align(), 16u);
-  ASSERT_EQ(sem->Members().size(), 3u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 48u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 64u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, SizeAttributes) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.f32(), {MemberSize(8)}),
-                                       Member("b", ty.f32(), {MemberSize(16)}),
-                                       Member("c", ty.f32(), {MemberSize(8)}),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.f32(), {MemberSize(4)}),
-                               Member("b", ty.u32(), {MemberSize(8)}),
-                               Member("c", ty.Of(inner)),
-                               Member("d", ty.i32(), {MemberSize(32)}),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 76u);
-  EXPECT_EQ(sem->SizeNoPadding(), 76u);
-  EXPECT_EQ(sem->Align(), 4u);
-  ASSERT_EQ(sem->Members().size(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 8u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 12u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
-  EXPECT_EQ(sem->Members()[3]->Offset(), 44u);
-  EXPECT_EQ(sem->Members()[3]->Align(), 4u);
-  EXPECT_EQ(sem->Members()[3]->Size(), 32u);
-}
-
-TEST_F(ResolverStructLayoutTest, AlignAttributes) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.f32(), {MemberAlign(8)}),
-                                       Member("b", ty.f32(), {MemberAlign(16)}),
-                                       Member("c", ty.f32(), {MemberAlign(4)}),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.f32(), {MemberAlign(4)}),
-                               Member("b", ty.u32(), {MemberAlign(8)}),
-                               Member("c", ty.Of(inner)),
-                               Member("d", ty.i32(), {MemberAlign(32)}),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 96u);
-  EXPECT_EQ(sem->SizeNoPadding(), 68u);
-  EXPECT_EQ(sem->Align(), 32u);
-  ASSERT_EQ(sem->Members().size(), 4u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  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(), 8u);
-  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
-  EXPECT_EQ(sem->Members()[2]->Offset(), 16u);
-  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
-  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
-  EXPECT_EQ(sem->Members()[3]->Offset(), 64u);
-  EXPECT_EQ(sem->Members()[3]->Align(), 32u);
-  EXPECT_EQ(sem->Members()[3]->Size(), 4u);
-}
-
-TEST_F(ResolverStructLayoutTest, StructWithLotsOfPadding) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberAlign(1024)}),
-                           });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_EQ(sem->Size(), 1024u);
-  EXPECT_EQ(sem->SizeNoPadding(), 4u);
-  EXPECT_EQ(sem->Align(), 1024u);
-  ASSERT_EQ(sem->Members().size(), 1u);
-  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
-  EXPECT_EQ(sem->Members()[0]->Align(), 1024u);
-  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/struct_pipeline_stage_use_test.cc b/src/resolver/struct_pipeline_stage_use_test.cc
deleted file mode 100644
index dbc5326..0000000
--- a/src/resolver/struct_pipeline_stage_use_test.cc
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/stage_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/struct.h"
-
-using ::testing::UnorderedElementsAre;
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverPipelineStageUseTest = ResolverTest;
-
-TEST_F(ResolverPipelineStageUseTest, UnusedStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->PipelineStageUses().empty());
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  Func("foo", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->PipelineStageUses().empty());
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  Func("foo", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))}, {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->PipelineStageUses().empty());
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(),
-       {Return(Construct(ty.vec4<f32>()))},
-       {Stage(ast::PipelineStage::kVertex)},
-       {Builtin(ast::Builtin::kPosition)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
-  auto* s = Structure(
-      "S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
-
-  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-
-  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
-  auto* s = Structure(
-      "S",
-      {Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
-
-  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kComputeInput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
-  auto* s = Structure(
-      "S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
-
-  Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput,
-                                   sem::PipelineStageUsage::kFragmentInput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = Alias("S_alias", ty.Of(s));
-
-  Func("main", {Param("param", ty.Of(s_alias))}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
-}
-
-TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = Alias("S_alias", ty.Of(s));
-
-  Func("main", {}, ty.Of(s_alias),
-       {Return(Construct(ty.Of(s_alias), Expr(0.f)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
deleted file mode 100644
index 2220ff9..0000000
--- a/src/resolver/struct_storage_class_use_test.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/struct.h"
-
-using ::testing::UnorderedElementsAre;
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverStorageClassUseTest = ResolverTest;
-
-TEST_F(ResolverStorageClassUseTest, UnreachableStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_TRUE(sem->StorageClassUsage().empty());
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-
-  Func("f", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kNone));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-
-  Func("f", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kNone));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kPrivate));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = Alias("A", ty.Of(s));
-  Global("g", ty.Of(a), ast::StorageClass::kPrivate);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kPrivate));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", ty.Of(s))});
-  Global("g", ty.Of(o), ast::StorageClass::kPrivate);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kPrivate));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(ty.Of(s), 3);
-  Global("g", a, ast::StorageClass::kPrivate);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kPrivate));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-
-  WrapInFunction(Var("g", ty.Of(s)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kFunction));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = Alias("A", ty.Of(s));
-  WrapInFunction(Var("g", ty.Of(a)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kFunction));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", ty.Of(s))});
-  WrapInFunction(Var("g", ty.Of(o)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kFunction));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(ty.Of(s), 3);
-  WrapInFunction(Var("g", a));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kFunction));
-}
-
-TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
-  auto* s = Structure("S", {Member("a", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("x", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-  Global("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(0),
-         });
-  WrapInFunction(Var("g", ty.Of(s)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* sem = TypeOf(s)->As<sem::Struct>();
-  ASSERT_NE(sem, nullptr);
-  EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kUniform,
-                                   ast::StorageClass::kStorage,
-                                   ast::StorageClass::kFunction));
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/type_constructor_validation_test.cc b/src/resolver/type_constructor_validation_test.cc
deleted file mode 100644
index 8394c2c..0000000
--- a/src/resolver/type_constructor_validation_test.cc
+++ /dev/null
@@ -1,2937 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ::testing::HasSubstr;
-
-// Helpers and typedefs
-using builder::alias;
-using builder::alias1;
-using builder::alias2;
-using builder::alias3;
-using builder::CreatePtrs;
-using builder::CreatePtrsFor;
-using builder::DataType;
-using builder::f32;
-using builder::i32;
-using builder::mat2x2;
-using builder::mat2x3;
-using builder::mat3x2;
-using builder::mat3x3;
-using builder::mat4x4;
-using builder::u32;
-using builder::vec2;
-using builder::vec3;
-using builder::vec4;
-
-class ResolverTypeConstructorValidationTest : public resolver::TestHelper,
-                                              public testing::Test {};
-
-namespace InferTypeTest {
-struct Params {
-  builder::ast_type_func_ptr create_rhs_ast_type;
-  builder::ast_expr_func_ptr create_rhs_ast_value;
-  builder::sem_type_func_ptr create_rhs_sem_type;
-};
-
-template <typename T>
-constexpr Params ParamsFor() {
-  return Params{DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem};
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferTypeTest_Simple) {
-  // var a = 1;
-  // var b = a;
-  auto* a = Var("a", nullptr, ast::StorageClass::kNone, Expr(1));
-  auto* b = Var("b", nullptr, ast::StorageClass::kNone, Expr("a"));
-  auto* a_ident = Expr("a");
-  auto* b_ident = Expr("b");
-
-  WrapInFunction(a, b, Assign(a_ident, "a"), Assign(b_ident, "b"));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_TRUE(TypeOf(a_ident)->Is<sem::Reference>());
-  EXPECT_TRUE(
-      TypeOf(a_ident)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(a_ident)->As<sem::Reference>()->StorageClass(),
-            ast::StorageClass::kFunction);
-  ASSERT_TRUE(TypeOf(b_ident)->Is<sem::Reference>());
-  EXPECT_TRUE(
-      TypeOf(b_ident)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(b_ident)->As<sem::Reference>()->StorageClass(),
-            ast::StorageClass::kFunction);
-}
-
-using InferTypeTest_FromConstructorExpression = ResolverTestWithParam<Params>;
-TEST_P(InferTypeTest_FromConstructorExpression, All) {
-  // e.g. for vec3<f32>
-  // {
-  //   var a = vec3<f32>(0.0, 0.0, 0.0)
-  // }
-  auto& params = GetParam();
-
-  auto* constructor_expr = params.create_rhs_ast_value(*this, 0);
-
-  auto* a = Var("a", nullptr, ast::StorageClass::kNone, constructor_expr);
-  // Self-assign 'a' to force the expression to be resolved so we can test its
-  // type below
-  auto* a_ident = Expr("a");
-  WrapInFunction(Decl(a), Assign(a_ident, "a"));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  auto* got = TypeOf(a_ident);
-  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
-                                          ast::StorageClass::kFunction,
-                                          ast::Access::kReadWrite);
-  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
-                           << "expected: " << FriendlyName(expected) << "\n";
-}
-
-static constexpr Params from_constructor_expression_cases[] = {
-    ParamsFor<bool>(),
-    ParamsFor<i32>(),
-    ParamsFor<u32>(),
-    ParamsFor<f32>(),
-    ParamsFor<vec3<i32>>(),
-    ParamsFor<vec3<u32>>(),
-    ParamsFor<vec3<f32>>(),
-    ParamsFor<mat3x3<f32>>(),
-    ParamsFor<alias<bool>>(),
-    ParamsFor<alias<i32>>(),
-    ParamsFor<alias<u32>>(),
-    ParamsFor<alias<f32>>(),
-    ParamsFor<alias<vec3<i32>>>(),
-    ParamsFor<alias<vec3<u32>>>(),
-    ParamsFor<alias<vec3<f32>>>(),
-    ParamsFor<alias<mat3x3<f32>>>(),
-};
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         InferTypeTest_FromConstructorExpression,
-                         testing::ValuesIn(from_constructor_expression_cases));
-
-using InferTypeTest_FromArithmeticExpression = ResolverTestWithParam<Params>;
-TEST_P(InferTypeTest_FromArithmeticExpression, All) {
-  // e.g. for vec3<f32>
-  // {
-  //   var a = vec3<f32>(2.0, 2.0, 2.0) * 3.0;
-  // }
-  auto& params = GetParam();
-
-  auto* arith_lhs_expr = params.create_rhs_ast_value(*this, 2);
-  auto* arith_rhs_expr = params.create_rhs_ast_value(*this, 3);
-  auto* constructor_expr = Mul(arith_lhs_expr, arith_rhs_expr);
-
-  auto* a = Var("a", nullptr, constructor_expr);
-  // Self-assign 'a' to force the expression to be resolved so we can test its
-  // type below
-  auto* a_ident = Expr("a");
-  WrapInFunction(Decl(a), Assign(a_ident, "a"));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  auto* got = TypeOf(a_ident);
-  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
-                                          ast::StorageClass::kFunction,
-                                          ast::Access::kReadWrite);
-  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
-                           << "expected: " << FriendlyName(expected) << "\n";
-}
-static constexpr Params from_arithmetic_expression_cases[] = {
-    ParamsFor<i32>(),       ParamsFor<u32>(),         ParamsFor<f32>(),
-    ParamsFor<vec3<f32>>(), ParamsFor<mat3x3<f32>>(),
-
-    // TODO(amaiorano): Uncomment once https://crbug.com/tint/680 is fixed
-    // ParamsFor<alias<ty_i32>>(),
-    // ParamsFor<alias<ty_u32>>(),
-    // ParamsFor<alias<ty_f32>>(),
-    // ParamsFor<alias<ty_vec3<f32>>>(),
-    // ParamsFor<alias<ty_mat3x3<f32>>>(),
-};
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         InferTypeTest_FromArithmeticExpression,
-                         testing::ValuesIn(from_arithmetic_expression_cases));
-
-using InferTypeTest_FromCallExpression = ResolverTestWithParam<Params>;
-TEST_P(InferTypeTest_FromCallExpression, All) {
-  // e.g. for vec3<f32>
-  //
-  // fn foo() -> vec3<f32> {
-  //   return vec3<f32>();
-  // }
-  //
-  // fn bar()
-  // {
-  //   var a = foo();
-  // }
-  auto& params = GetParam();
-
-  Func("foo", {}, params.create_rhs_ast_type(*this),
-       {Return(Construct(params.create_rhs_ast_type(*this)))}, {});
-
-  auto* a = Var("a", nullptr, Call("foo"));
-  // Self-assign 'a' to force the expression to be resolved so we can test its
-  // type below
-  auto* a_ident = Expr("a");
-  WrapInFunction(Decl(a), Assign(a_ident, "a"));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-  auto* got = TypeOf(a_ident);
-  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
-                                          ast::StorageClass::kFunction,
-                                          ast::Access::kReadWrite);
-  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
-                           << "expected: " << FriendlyName(expected) << "\n";
-}
-static constexpr Params from_call_expression_cases[] = {
-    ParamsFor<bool>(),
-    ParamsFor<i32>(),
-    ParamsFor<u32>(),
-    ParamsFor<f32>(),
-    ParamsFor<vec3<i32>>(),
-    ParamsFor<vec3<u32>>(),
-    ParamsFor<vec3<f32>>(),
-    ParamsFor<mat3x3<f32>>(),
-    ParamsFor<alias<bool>>(),
-    ParamsFor<alias<i32>>(),
-    ParamsFor<alias<u32>>(),
-    ParamsFor<alias<f32>>(),
-    ParamsFor<alias<vec3<i32>>>(),
-    ParamsFor<alias<vec3<u32>>>(),
-    ParamsFor<alias<vec3<f32>>>(),
-    ParamsFor<alias<mat3x3<f32>>>(),
-};
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         InferTypeTest_FromCallExpression,
-                         testing::ValuesIn(from_call_expression_cases));
-
-}  // namespace InferTypeTest
-
-namespace ConversionConstructTest {
-enum class Kind {
-  Construct,
-  Conversion,
-};
-
-struct Params {
-  Kind kind;
-  builder::ast_type_func_ptr lhs_type;
-  builder::ast_type_func_ptr rhs_type;
-  builder::ast_expr_func_ptr rhs_value_expr;
-};
-
-template <typename LhsType, typename RhsType>
-constexpr Params ParamsFor(Kind kind) {
-  return Params{kind, DataType<LhsType>::AST, DataType<RhsType>::AST,
-                DataType<RhsType>::Expr};
-}
-
-static constexpr Params valid_cases[] = {
-    // Direct init (non-conversions)
-    ParamsFor<bool, bool>(Kind::Construct),              //
-    ParamsFor<i32, i32>(Kind::Construct),                //
-    ParamsFor<u32, u32>(Kind::Construct),                //
-    ParamsFor<f32, f32>(Kind::Construct),                //
-    ParamsFor<vec3<bool>, vec3<bool>>(Kind::Construct),  //
-    ParamsFor<vec3<i32>, vec3<i32>>(Kind::Construct),    //
-    ParamsFor<vec3<u32>, vec3<u32>>(Kind::Construct),    //
-    ParamsFor<vec3<f32>, vec3<f32>>(Kind::Construct),    //
-
-    // Splat
-    ParamsFor<vec3<bool>, bool>(Kind::Construct),  //
-    ParamsFor<vec3<i32>, i32>(Kind::Construct),    //
-    ParamsFor<vec3<u32>, u32>(Kind::Construct),    //
-    ParamsFor<vec3<f32>, f32>(Kind::Construct),    //
-
-    // Conversion
-    ParamsFor<bool, u32>(Kind::Conversion),  //
-    ParamsFor<bool, i32>(Kind::Conversion),  //
-    ParamsFor<bool, f32>(Kind::Conversion),  //
-
-    ParamsFor<i32, bool>(Kind::Conversion),  //
-    ParamsFor<i32, u32>(Kind::Conversion),   //
-    ParamsFor<i32, f32>(Kind::Conversion),   //
-
-    ParamsFor<u32, bool>(Kind::Conversion),  //
-    ParamsFor<u32, i32>(Kind::Conversion),   //
-    ParamsFor<u32, f32>(Kind::Conversion),   //
-
-    ParamsFor<f32, bool>(Kind::Conversion),  //
-    ParamsFor<f32, u32>(Kind::Conversion),   //
-    ParamsFor<f32, i32>(Kind::Conversion),   //
-
-    ParamsFor<vec3<bool>, vec3<u32>>(Kind::Conversion),  //
-    ParamsFor<vec3<bool>, vec3<i32>>(Kind::Conversion),  //
-    ParamsFor<vec3<bool>, vec3<f32>>(Kind::Conversion),  //
-
-    ParamsFor<vec3<i32>, vec3<bool>>(Kind::Conversion),  //
-    ParamsFor<vec3<i32>, vec3<u32>>(Kind::Conversion),   //
-    ParamsFor<vec3<i32>, vec3<f32>>(Kind::Conversion),   //
-
-    ParamsFor<vec3<u32>, vec3<bool>>(Kind::Conversion),  //
-    ParamsFor<vec3<u32>, vec3<i32>>(Kind::Conversion),   //
-    ParamsFor<vec3<u32>, vec3<f32>>(Kind::Conversion),   //
-
-    ParamsFor<vec3<f32>, vec3<bool>>(Kind::Conversion),  //
-    ParamsFor<vec3<f32>, vec3<u32>>(Kind::Conversion),   //
-    ParamsFor<vec3<f32>, vec3<i32>>(Kind::Conversion),   //
-};
-
-using ConversionConstructorValidTest = ResolverTestWithParam<Params>;
-TEST_P(ConversionConstructorValidTest, All) {
-  auto& params = GetParam();
-
-  // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
-  auto* lhs_type1 = params.lhs_type(*this);
-  auto* lhs_type2 = params.lhs_type(*this);
-  auto* rhs_type = params.rhs_type(*this);
-  auto* rhs_value_expr = params.rhs_value_expr(*this, 0);
-
-  std::stringstream ss;
-  ss << FriendlyName(lhs_type1) << " = " << FriendlyName(lhs_type2) << "("
-     << FriendlyName(rhs_type) << "(<rhs value expr>))";
-  SCOPED_TRACE(ss.str());
-
-  auto* arg = Construct(rhs_type, rhs_value_expr);
-  auto* tc = Construct(lhs_type2, arg);
-  auto* a = Var("a", lhs_type1, ast::StorageClass::kNone, tc);
-
-  // Self-assign 'a' to force the expression to be resolved so we can test its
-  // type below
-  auto* a_ident = Expr("a");
-  WrapInFunction(Decl(a), Assign(a_ident, "a"));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  switch (params.kind) {
-    case Kind::Construct: {
-      auto* ctor = call->Target()->As<sem::TypeConstructor>();
-      ASSERT_NE(ctor, nullptr);
-      EXPECT_EQ(call->Type(), ctor->ReturnType());
-      ASSERT_EQ(ctor->Parameters().size(), 1u);
-      EXPECT_EQ(ctor->Parameters()[0]->Type(), TypeOf(arg));
-      break;
-    }
-    case Kind::Conversion: {
-      auto* conv = call->Target()->As<sem::TypeConversion>();
-      ASSERT_NE(conv, nullptr);
-      EXPECT_EQ(call->Type(), conv->ReturnType());
-      ASSERT_EQ(conv->Parameters().size(), 1u);
-      EXPECT_EQ(conv->Parameters()[0]->Type(), TypeOf(arg));
-      break;
-    }
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         ConversionConstructorValidTest,
-                         testing::ValuesIn(valid_cases));
-
-constexpr CreatePtrs all_types[] = {
-    CreatePtrsFor<bool>(),         //
-    CreatePtrsFor<u32>(),          //
-    CreatePtrsFor<i32>(),          //
-    CreatePtrsFor<f32>(),          //
-    CreatePtrsFor<vec3<bool>>(),   //
-    CreatePtrsFor<vec3<i32>>(),    //
-    CreatePtrsFor<vec3<u32>>(),    //
-    CreatePtrsFor<vec3<f32>>(),    //
-    CreatePtrsFor<mat3x3<i32>>(),  //
-    CreatePtrsFor<mat3x3<u32>>(),  //
-    CreatePtrsFor<mat3x3<f32>>(),  //
-    CreatePtrsFor<mat2x3<i32>>(),  //
-    CreatePtrsFor<mat2x3<u32>>(),  //
-    CreatePtrsFor<mat2x3<f32>>(),  //
-    CreatePtrsFor<mat3x2<i32>>(),  //
-    CreatePtrsFor<mat3x2<u32>>(),  //
-    CreatePtrsFor<mat3x2<f32>>()   //
-};
-
-using ConversionConstructorInvalidTest =
-    ResolverTestWithParam<std::tuple<CreatePtrs,  // lhs
-                                     CreatePtrs   // rhs
-                                     >>;
-TEST_P(ConversionConstructorInvalidTest, All) {
-  auto& params = GetParam();
-
-  auto& lhs_params = std::get<0>(params);
-  auto& rhs_params = std::get<1>(params);
-
-  // Skip test for valid cases
-  for (auto& v : valid_cases) {
-    if (v.lhs_type == lhs_params.ast && v.rhs_type == rhs_params.ast &&
-        v.rhs_value_expr == rhs_params.expr) {
-      return;
-    }
-  }
-  // Skip non-conversions
-  if (lhs_params.ast == rhs_params.ast) {
-    return;
-  }
-
-  // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
-  auto* lhs_type1 = lhs_params.ast(*this);
-  auto* lhs_type2 = lhs_params.ast(*this);
-  auto* rhs_type = rhs_params.ast(*this);
-  auto* rhs_value_expr = rhs_params.expr(*this, 0);
-
-  std::stringstream ss;
-  ss << FriendlyName(lhs_type1) << " = " << FriendlyName(lhs_type2) << "("
-     << FriendlyName(rhs_type) << "(<rhs value expr>))";
-  SCOPED_TRACE(ss.str());
-
-  auto* a = Var("a", lhs_type1, ast::StorageClass::kNone,
-                Construct(lhs_type2, Construct(rhs_type, rhs_value_expr)));
-
-  // Self-assign 'a' to force the expression to be resolved so we can test its
-  // type below
-  auto* a_ident = Expr("a");
-  WrapInFunction(Decl(a), Assign(a_ident, "a"));
-
-  ASSERT_FALSE(r()->Resolve());
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         ConversionConstructorInvalidTest,
-                         testing::Combine(testing::ValuesIn(all_types),
-                                          testing::ValuesIn(all_types)));
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       ConversionConstructorInvalid_TooManyInitializers) {
-  auto* a = Var("a", ty.f32(), ast::StorageClass::kNone,
-                Construct(Source{{12, 34}}, ty.f32(), Expr(1.0f), Expr(2.0f)));
-  WrapInFunction(a);
-
-  ASSERT_FALSE(r()->Resolve());
-  ASSERT_EQ(r()->error(),
-            "12:34 error: expected zero or one value in constructor, got 2");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       ConversionConstructorInvalid_InvalidInitializer) {
-  auto* a =
-      Var("a", ty.f32(), ast::StorageClass::kNone,
-          Construct(Source{{12, 34}}, ty.f32(), Construct(ty.array<f32, 4>())));
-  WrapInFunction(a);
-
-  ASSERT_FALSE(r()->Resolve());
-  ASSERT_EQ(r()->error(),
-            "12:34 error: cannot construct 'f32' with a value of type "
-            "'array<f32, 4>'");
-}
-
-}  // namespace ConversionConstructTest
-
-namespace ArrayConstructor {
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ZeroValue_Pass) {
-  // array<u32, 10>();
-  auto* tc = array<u32, 10>();
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  EXPECT_TRUE(call->Type()->Is<sem::Array>());
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 0u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_type_match) {
-  // array<u32, 3>(0u, 10u. 20u);
-  auto* tc = array<u32, 3>(Expr(0u), Expr(10u), Expr(20u));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  EXPECT_TRUE(call->Type()->Is<sem::Array>());
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_type_Mismatch_U32F32) {
-  // array<u32, 3>(0u, 1.0f, 20u);
-  auto* tc = array<u32, 3>(Expr(0u), Expr(Source{{12, 34}}, 1.0f), Expr(20u));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'u32', found 'f32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_F32I32) {
-  // array<f32, 1>(1);
-  auto* tc = array<f32, 1>(Expr(Source{{12, 34}}, 1));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'f32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_U32I32) {
-  // array<u32, 6>(1, 0u, 0u, 0u, 0u, 0u);
-  auto* tc = array<u32, 1>(Expr(Source{{12, 34}}, 1), Expr(0u), Expr(0u),
-                           Expr(0u), Expr(0u));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'u32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_Vec2) {
-  // array<i32, 3>(1, vec2<i32>());
-  auto* tc =
-      array<i32, 3>(Expr(1), Construct(Source{{12, 34}}, ty.vec2<i32>()));
-  WrapInFunction(tc);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'i32', found 'vec2<i32>'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32U32) {
-  // array<vec3<i32>, 2>(vec3<i32>(), vec3<u32>());
-  auto* e0 = vec3<i32>();
-  SetSource(Source::Location({12, 34}));
-  auto* e1 = vec3<u32>();
-  auto* t = Construct(ty.array(ty.vec3<i32>(), 2), e0, e1);
-  WrapInFunction(t);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'vec3<i32>', found 'vec3<u32>'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32Bool) {
-  // array<vec3<i32>, 2>(vec3<i32>(), vec3<bool>(true, true, false));
-  SetSource(Source::Location({12, 34}));
-  auto* e0 = vec3<bool>(true, true, false);
-  auto* e1 = vec3<i32>();
-  auto* t = Construct(ty.array(ty.vec3<i32>(), 2), e0, e1);
-  WrapInFunction(t);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'vec3<i32>', found 'vec3<bool>'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfArray_SubElemSizeMismatch) {
-  // array<array<i32, 2>, 2>(array<i32, 3>(), array<i32, 2>());
-  SetSource(Source::Location({12, 34}));
-  auto* e0 = array<i32, 3>();
-  auto* e1 = array<i32, 2>();
-  auto* t = Construct(ty.array(ty.array<i32, 2>(), 2), e0, e1);
-  WrapInFunction(t);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'array<i32, 2>', found 'array<i32, 3>'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfArray_SubElemTypeMismatch) {
-  // array<array<i32, 2>, 2>(array<i32, 2>(), array<u32, 2>());
-  auto* e0 = array<i32, 2>();
-  SetSource(Source::Location({12, 34}));
-  auto* e1 = array<u32, 2>();
-  auto* t = Construct(ty.array(ty.array<i32, 2>(), 2), e0, e1);
-  WrapInFunction(t);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in array constructor does not match array type: "
-            "expected 'array<i32, 2>', found 'array<u32, 2>'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_TooFewElements) {
-  // array<i32, 4>(1, 2, 3);
-  SetSource(Source::Location({12, 34}));
-  auto* tc = array<i32, 4>(Expr(1), Expr(2), Expr(3));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: array constructor has too few elements: expected 4, "
-            "found 3");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_TooManyElements) {
-  // array<i32, 4>(1, 2, 3, 4, 5);
-  SetSource(Source::Location({12, 34}));
-  auto* tc = array<i32, 4>(Expr(1), Expr(2), Expr(3), Expr(4), Expr(5));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: array constructor has too many "
-            "elements: expected 4, "
-            "found 5");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_Runtime) {
-  // array<i32>(1);
-  auto* tc = array(ty.i32(), nullptr, Expr(Source{{12, 34}}, 1));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_RuntimeZeroValue) {
-  // array<i32>();
-  auto* tc = array(ty.i32(), nullptr);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
-}
-
-}  // namespace ArrayConstructor
-
-namespace ScalarConstructor {
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_i32_Success) {
-  auto* expr = Construct<i32>(Expr(123));
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::I32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_u32_Success) {
-  auto* expr = Construct<u32>(Expr(123u));
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::U32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_f32_Success) {
-  auto* expr = Construct<f32>(Expr(1.23f));
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_f32_to_i32_Success) {
-  auto* expr = Construct<i32>(1.23f);
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::I32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConversion>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_i32_to_u32_Success) {
-  auto* expr = Construct<u32>(123);
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::U32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConversion>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_u32_to_f32_Success) {
-  auto* expr = Construct<f32>(123u);
-  WrapInFunction(expr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
-
-  auto* call = Sem().Get(expr);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConversion>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
-}
-
-}  // namespace ScalarConstructor
-
-namespace VectorConstructor {
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2F32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec2<f32>(Expr(Source{{12, 34}}, 1), 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'f32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2U32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec2<u32>(1u, Expr(Source{{12, 34}}, 1));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'u32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2I32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec2<i32>(Expr(Source{{12, 34}}, 1u), 1);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'i32', found 'u32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2Bool_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec2<bool>(true, Expr(Source{{12, 34}}, 1));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'bool', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_Vec3ArgumentCardinalityTooLarge) {
-  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_Vec4ArgumentCardinalityTooLarge) {
-  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec4<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_TooManyArgumentsScalar) {
-  auto* tc =
-      vec2<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
-                Expr(Source{{12, 46}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_TooManyArgumentsVector) {
-  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_TooManyArgumentsVectorAndScalar) {
-  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Expr(Source{{12, 40}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_InvalidArgumentType) {
-  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected vector or scalar type in vector "
-            "constructor; found: mat2x2<f32>");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Success_ZeroValue) {
-  auto* tc = vec2<f32>();
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 0u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2F32_Success_Scalar) {
-  auto* tc = vec2<f32>(1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2U32_Success_Scalar) {
-  auto* tc = vec2<u32>(1u, 1u);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2I32_Success_Scalar) {
-  auto* tc = vec2<i32>(1, 1);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2Bool_Success_Scalar) {
-  auto* tc = vec2<bool>(true, false);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Bool>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Success_Identity) {
-  auto* tc = vec2<f32>(vec2<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Success_Vec2TypeConversion) {
-  auto* tc = vec2<f32>(vec2<i32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConversion>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3F32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec3<f32>(1.0f, 1.0f, Expr(Source{{12, 34}}, 1));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'f32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3U32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec3<u32>(1u, Expr(Source{{12, 34}}, 1), 1u);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'u32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3I32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec3<i32>(1, Expr(Source{{12, 34}}, 1u), 1);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'i32', found 'u32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3Bool_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec3<bool>(true, Expr(Source{{12, 34}}, 1), false);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'bool', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_Vec4ArgumentCardinalityTooLarge) {
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec4<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooFewArgumentsScalar) {
-  auto* tc =
-      vec3<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooManyArgumentsScalar) {
-  auto* tc =
-      vec3<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
-                Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooFewArgumentsVec2) {
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2) {
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2AndScalar) {
-  auto* tc =
-      vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                Expr(Source{{12, 40}}, 1.0f), Expr(Source{{12, 46}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooManyArgumentsVec3) {
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
-                       Expr(Source{{12, 40}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_InvalidArgumentType) {
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected vector or scalar type in vector "
-            "constructor; found: mat2x2<f32>");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Success_ZeroValue) {
-  auto* tc = vec3<f32>();
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 0u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3F32_Success_Scalar) {
-  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3U32_Success_Scalar) {
-  auto* tc = vec3<u32>(1u, 1u, 1u);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3I32_Success_Scalar) {
-  auto* tc = vec3<i32>(1, 1, 1);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3Bool_Success_Scalar) {
-  auto* tc = vec3<bool>(true, false, true);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Bool>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::Bool>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Success_Vec2AndScalar) {
-  auto* tc = vec3<f32>(vec2<f32>(), 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Success_ScalarAndVec2) {
-  auto* tc = vec3<f32>(1.0f, vec2<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Vector>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Success_Identity) {
-  auto* tc = vec3<f32>(vec3<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Success_Vec3TypeConversion) {
-  auto* tc = vec3<f32>(vec3<i32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
-
-  auto* call = Sem().Get(tc);
-  ASSERT_NE(call, nullptr);
-  auto* ctor = call->Target()->As<sem::TypeConversion>();
-  ASSERT_NE(ctor, nullptr);
-  EXPECT_EQ(call->Type(), ctor->ReturnType());
-  ASSERT_EQ(ctor->Parameters().size(), 1u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4F32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec4<f32>(1.0f, 1.0f, Expr(Source{{12, 34}}, 1), 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'f32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4U32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec4<u32>(1u, 1u, Expr(Source{{12, 34}}, 1), 1u);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'u32', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4I32_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec4<i32>(1, 1, Expr(Source{{12, 34}}, 1u), 1);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'i32', found 'u32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4Bool_Error_ScalarArgumentTypeMismatch) {
-  auto* tc = vec4<bool>(true, false, Expr(Source{{12, 34}}, 1), true);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'bool', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooFewArgumentsScalar) {
-  auto* tc =
-      vec4<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
-                Expr(Source{{12, 46}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsScalar) {
-  auto* tc =
-      vec4<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
-                Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f),
-                Expr(Source{{12, 58}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooFewArgumentsVec2AndScalar) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Expr(Source{{12, 40}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndScalars) {
-  auto* tc = vec4<f32>(
-      Construct(Source{{12, 34}}, ty.vec2<f32>()), Expr(Source{{12, 40}}, 1.0f),
-      Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Scalar) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()),
-                       Expr(Source{{12, 46}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Vec2) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooFewArgumentsVec3) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndScalars) {
-  auto* tc =
-      vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
-                Expr(Source{{12, 40}}, 1.0f), Expr(Source{{12, 46}}, 1.0f));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec2) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndVec3) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec3<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec3) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
-                       Construct(Source{{12, 40}}, ty.vec3<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_InvalidArgumentType) {
-  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: expected vector or scalar type in vector "
-            "constructor; found: mat2x2<f32>");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_ZeroValue) {
-  auto* tc = vec4<f32>();
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4F32_Success_Scalar) {
-  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4U32_Success_Scalar) {
-  auto* tc = vec4<u32>(1u, 1u, 1u, 1u);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4I32_Success_Scalar) {
-  auto* tc = vec4<i32>(1, 1, 1, 1);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4Bool_Success_Scalar) {
-  auto* tc = vec4<bool>(true, false, true, false);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_Vec2ScalarScalar) {
-  auto* tc = vec4<f32>(vec2<f32>(), 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_ScalarVec2Scalar) {
-  auto* tc = vec4<f32>(1.0f, vec2<f32>(), 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_ScalarScalarVec2) {
-  auto* tc = vec4<f32>(1.0f, 1.0f, vec2<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_Vec2AndVec2) {
-  auto* tc = vec4<f32>(vec2<f32>(), vec2<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_Vec3AndScalar) {
-  auto* tc = vec4<f32>(vec3<f32>(), 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_ScalarAndVec3) {
-  auto* tc = vec4<f32>(1.0f, vec3<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_Identity) {
-  auto* tc = vec4<f32>(vec4<f32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Success_Vec4TypeConversion) {
-  auto* tc = vec4<f32>(vec4<i32>());
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_NestedVectorConstructors_InnerError) {
-  auto* tc = vec4<f32>(vec4<f32>(1.0f, 1.0f,
-                                 vec3<f32>(Expr(Source{{12, 34}}, 1.0f),
-                                           Expr(Source{{12, 34}}, 1.0f))),
-                       1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_NestedVectorConstructors_Success) {
-  auto* tc = vec4<f32>(vec3<f32>(vec2<f32>(1.0f, 1.0f), 1.0f), 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(tc), nullptr);
-  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_Alias_Argument_Error) {
-  auto* alias = Alias("UnsignedInt", ty.u32());
-  Global("uint_var", ty.Of(alias), ast::StorageClass::kPrivate);
-
-  auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'f32', found 'u32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_Alias_Argument_Success) {
-  auto* f32_alias = Alias("Float32", ty.f32());
-  auto* vec2_alias = Alias("VectorFloat2", ty.vec2<f32>());
-  Global("my_f32", ty.Of(f32_alias), ast::StorageClass::kPrivate);
-  Global("my_vec2", ty.Of(vec2_alias), ast::StorageClass::kPrivate);
-
-  auto* tc = vec3<f32>("my_vec2", "my_f32");
-  WrapInFunction(tc);
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ElementTypeAlias_Error) {
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  // vec2<Float32>(1.0f, 1u)
-  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
-  auto* tc =
-      Construct(Source{{12, 34}}, vec_type, 1.0f, Expr(Source{{12, 40}}, 1u));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:40 error: type in vector constructor does not match vector "
-            "type: expected 'f32', found 'u32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ElementTypeAlias_Success) {
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  // vec2<Float32>(1.0f, 1.0f)
-  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
-  auto* tc = Construct(Source{{12, 34}}, vec_type, 1.0f, 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ArgumentElementTypeAlias_Error) {
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  // vec3<u32>(vec<Float32>(), 1.0f)
-  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
-  auto* tc = vec3<u32>(Construct(Source{{12, 34}}, vec_type), 1.0f);
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type in vector constructor does not match vector "
-            "type: expected 'u32', found 'f32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ArgumentElementTypeAlias_Success) {
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  // vec3<f32>(vec<Float32>(), 1.0f)
-  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
-  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, vec_type), 1.0f);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromScalars) {
-  auto* vec2_bool =
-      Construct(create<ast::Vector>(nullptr, 2), Expr(true), Expr(false));
-  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), Expr(1), Expr(2));
-  auto* vec2_u32 =
-      Construct(create<ast::Vector>(nullptr, 2), Expr(1u), Expr(2u));
-  auto* vec2_f32 =
-      Construct(create<ast::Vector>(nullptr, 2), Expr(1.0f), Expr(2.0f));
-  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
-  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
-  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
-  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromVec2) {
-  auto* vec2_bool =
-      Construct(create<ast::Vector>(nullptr, 2), vec2<bool>(true, false));
-  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), vec2<i32>(1, 2));
-  auto* vec2_u32 =
-      Construct(create<ast::Vector>(nullptr, 2), vec2<u32>(1u, 2u));
-  auto* vec2_f32 =
-      Construct(create<ast::Vector>(nullptr, 2), vec2<f32>(1.0f, 2.0f));
-  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
-  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
-  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
-  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
-  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromScalars) {
-  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
-                              Expr(false), Expr(true));
-  auto* vec3_i32 =
-      Construct(create<ast::Vector>(nullptr, 3), Expr(1), Expr(2), Expr(3));
-  auto* vec3_u32 =
-      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), Expr(2u), Expr(3u));
-  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
-                             Expr(2.0f), Expr(3.0f));
-  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromVec3) {
-  auto* vec3_bool =
-      Construct(create<ast::Vector>(nullptr, 3), vec3<bool>(true, false, true));
-  auto* vec3_i32 =
-      Construct(create<ast::Vector>(nullptr, 3), vec3<i32>(1, 2, 3));
-  auto* vec3_u32 =
-      Construct(create<ast::Vector>(nullptr, 3), vec3<u32>(1u, 2u, 3u));
-  auto* vec3_f32 =
-      Construct(create<ast::Vector>(nullptr, 3), vec3<f32>(1.0f, 2.0f, 3.0f));
-  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       InferVec3ElementTypeFromScalarAndVec2) {
-  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
-                              vec2<bool>(false, true));
-  auto* vec3_i32 =
-      Construct(create<ast::Vector>(nullptr, 3), Expr(1), vec2<i32>(2, 3));
-  auto* vec3_u32 =
-      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), vec2<u32>(2u, 3u));
-  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
-                             vec2<f32>(2.0f, 3.0f));
-  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
-  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromScalars) {
-  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
-                              Expr(false), Expr(true), Expr(false));
-  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1), Expr(2),
-                             Expr(3), Expr(4));
-  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
-                             Expr(2u), Expr(3u), Expr(4u));
-  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
-                             Expr(2.0f), Expr(3.0f), Expr(4.0f));
-  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromVec4) {
-  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
-                              vec4<bool>(true, false, true, false));
-  auto* vec4_i32 =
-      Construct(create<ast::Vector>(nullptr, 4), vec4<i32>(1, 2, 3, 4));
-  auto* vec4_u32 =
-      Construct(create<ast::Vector>(nullptr, 4), vec4<u32>(1u, 2u, 3u, 4u));
-  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
-                             vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
-  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       InferVec4ElementTypeFromScalarAndVec3) {
-  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
-                              vec3<bool>(false, true, false));
-  auto* vec4_i32 =
-      Construct(create<ast::Vector>(nullptr, 4), Expr(1), vec3<i32>(2, 3, 4));
-  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
-                             vec3<u32>(2u, 3u, 4u));
-  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
-                             vec3<f32>(2.0f, 3.0f, 4.0f));
-  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       InferVec4ElementTypeFromVec2AndVec2) {
-  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
-                              vec2<bool>(true, false), vec2<bool>(true, false));
-  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), vec2<i32>(1, 2),
-                             vec2<i32>(3, 4));
-  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), vec2<u32>(1u, 2u),
-                             vec2<u32>(3u, 4u));
-  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
-                             vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f));
-  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
-  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
-  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
-  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
-  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVectorElementTypeWithoutArgs) {
-  WrapInFunction(Construct(create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec2ElementTypeFromScalarsMismatch) {
-  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 2),
-                           Expr(Source{{1, 2}}, 1),  //
-                           Expr(Source{{1, 3}}, 2u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type i32
-1:3 note: argument 1 has type u32)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec3ElementTypeFromScalarsMismatch) {
-  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
-                           Expr(Source{{1, 2}}, 1),   //
-                           Expr(Source{{1, 3}}, 2u),  //
-                           Expr(Source{{1, 4}}, 3)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type i32
-1:3 note: argument 1 has type u32
-1:4 note: argument 2 has type i32)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec3ElementTypeFromScalarAndVec2Mismatch) {
-  WrapInFunction(
-      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
-                Expr(Source{{1, 2}}, 1),  //
-                Construct(Source{{1, 3}}, ty.vec2<f32>(), 2.0f, 3.0f)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type i32
-1:3 note: argument 1 has type vec2<f32>)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec4ElementTypeFromScalarsMismatch) {
-  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
-                           Expr(Source{{1, 2}}, 1),     //
-                           Expr(Source{{1, 3}}, 2),     //
-                           Expr(Source{{1, 4}}, 3.0f),  //
-                           Expr(Source{{1, 5}}, 4)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type i32
-1:3 note: argument 1 has type i32
-1:4 note: argument 2 has type f32
-1:5 note: argument 3 has type i32)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec4ElementTypeFromScalarAndVec3Mismatch) {
-  WrapInFunction(
-      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
-                Expr(Source{{1, 2}}, 1),  //
-                Construct(Source{{1, 3}}, ty.vec3<u32>(), 2u, 3u, 4u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type i32
-1:3 note: argument 1 has type vec3<u32>)");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       CannotInferVec4ElementTypeFromVec2AndVec2Mismatch) {
-  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
-                           Construct(Source{{1, 2}}, ty.vec2<i32>(), 3, 4),  //
-                           Construct(Source{{1, 3}}, ty.vec2<u32>(), 3u, 4u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
-1:2 note: argument 0 has type vec2<i32>
-1:3 note: argument 1 has type vec2<u32>)");
-}
-
-}  // namespace VectorConstructor
-
-namespace MatrixConstructor {
-struct MatrixDimensions {
-  uint32_t rows;
-  uint32_t columns;
-};
-
-static std::string MatrixStr(const MatrixDimensions& dimensions) {
-  return "mat" + std::to_string(dimensions.columns) + "x" +
-         std::to_string(dimensions.rows) + "<f32>";
-}
-
-using MatrixConstructorTest = ResolverTestWithParam<MatrixDimensions>;
-
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooFewArguments) {
-  // matNxM<f32>(vecM<f32>(), ...); with N - 1 arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns - 1; i++) {
-    auto* vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<f32>";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooFewArguments) {
-  // matNxM<f32>(f32,...,f32); with N*M - 1 arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns * param.rows - 1; i++) {
-    args.push_back(Construct(Source{{12, i}}, ty.f32()));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "f32";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooManyArguments) {
-  // matNxM<f32>(vecM<f32>(), ...); with N + 1 arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns + 1; i++) {
-    auto* vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<f32>";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooManyArguments) {
-  // matNxM<f32>(f32,...,f32); with N*M + 1 arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns * param.rows + 1; i++) {
-    args.push_back(Construct(Source{{12, i}}, ty.f32()));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "f32";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest,
-       Expr_ColumnConstructor_Error_InvalidArgumentType) {
-  // matNxM<f32>(vec<u32>, vec<u32>, ...); N arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec<u32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<u32>";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest,
-       Expr_ElementConstructor_Error_InvalidArgumentType) {
-  // matNxM<f32>(u32, u32, ...); N*M arguments
-
-  const auto param = GetParam();
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    args.push_back(Expr(Source{{12, i}}, 1u));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "u32";
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest,
-       Expr_ColumnConstructor_Error_TooFewRowsInVectorArgument) {
-  // matNxM<f32>(vecM<f32>(),...,vecM-1<f32>());
-
-  const auto param = GetParam();
-
-  // Skip the test if parameters would have resulted in an invalid vec1 type.
-  if (param.rows == 2) {
-    return;
-  }
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns - 1; i++) {
-    auto* valid_vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, valid_vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<f32>";
-  }
-  const size_t kInvalidLoc = 2 * (param.columns - 1);
-  auto* invalid_vec_type = ty.vec<f32>(param.rows - 1);
-  args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
-  args_tys << ", vec" << (param.rows - 1) << "<f32>";
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest,
-       Expr_ColumnConstructor_Error_TooManyRowsInVectorArgument) {
-  // matNxM<f32>(vecM<f32>(),...,vecM+1<f32>());
-
-  const auto param = GetParam();
-
-  // Skip the test if parameters would have resulted in an invalid vec5 type.
-  if (param.rows == 4) {
-    return;
-  }
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns - 1; i++) {
-    auto* valid_vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, valid_vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<f32>";
-  }
-  const size_t kInvalidLoc = 2 * (param.columns - 1);
-  auto* invalid_vec_type = ty.vec<f32>(param.rows + 1);
-  args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
-  args_tys << ", vec" << (param.rows + 1) << "<f32>";
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_ZeroValue_Success) {
-  // matNxM<f32>();
-
-  const auto param = GetParam();
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{{12, 40}}, matrix_type);
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_WithColumns_Success) {
-  // matNxM<f32>(vecM<f32>(), ...); with N arguments
-
-  const auto param = GetParam();
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_WithElements_Success) {
-  // matNxM<f32>(f32,...,f32); with N*M arguments
-
-  const auto param = GetParam();
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns * param.rows; i++) {
-    args.push_back(Construct(Source{{12, i}}, ty.f32()));
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Error) {
-  // matNxM<Float32>(vecM<u32>(), ...); with N arguments
-
-  const auto param = GetParam();
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec(ty.u32(), param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<u32>";
-  }
-
-  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Success) {
-  // matNxM<Float32>(vecM<f32>(), ...); with N arguments
-
-  const auto param = GetParam();
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec<f32>(param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-  }
-
-  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
-  auto* alias = Alias("VectorUnsigned2", ty.vec2<u32>());
-  auto* tc =
-      mat2x2<f32>(Construct(Source{{12, 34}}, ty.Of(alias)), vec2<f32>());
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: no matching constructor mat2x2<f32>(vec2<u32>, vec2<f32>)
-
-3 candidates available:
-  mat2x2<f32>()
-  mat2x2<f32>(f32,...,f32) // 4 arguments
-  mat2x2<f32>(vec2<f32>, vec2<f32>)
-)");
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentTypeAlias_Success) {
-  const auto param = GetParam();
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* vec_type = ty.vec<f32>(param.rows);
-  auto* vec_alias = Alias("VectorFloat2", vec_type);
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    args.push_back(Construct(Source{{12, i}}, ty.Of(vec_alias)));
-  }
-
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentElementTypeAlias_Error) {
-  const auto param = GetParam();
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* f32_alias = Alias("UnsignedInt", ty.u32());
-
-  std::stringstream args_tys;
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-    if (i > 1) {
-      args_tys << ", ";
-    }
-    args_tys << "vec" << param.rows << "<u32>";
-  }
-
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
-                                      MatrixStr(param) + "(" + args_tys.str() +
-                                      ")\n\n3 candidates available:"));
-}
-
-TEST_P(MatrixConstructorTest,
-       Expr_Constructor_ArgumentElementTypeAlias_Success) {
-  const auto param = GetParam();
-  auto* f32_alias = Alias("Float32", ty.f32());
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
-    args.push_back(Construct(Source{{12, i}}, vec_type));
-  }
-
-  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, InferElementTypeFromVectors) {
-  const auto param = GetParam();
-
-  ast::ExpressionList args;
-  for (uint32_t i = 1; i <= param.columns; i++) {
-    args.push_back(Construct(ty.vec<f32>(param.rows)));
-  }
-
-  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
-  auto* tc = Construct(Source{}, matrix_type, std::move(args));
-  WrapInFunction(tc);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, InferElementTypeFromScalars) {
-  const auto param = GetParam();
-
-  ast::ExpressionList args;
-  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
-    args.push_back(Expr(static_cast<f32>(i)));
-  }
-
-  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
-  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_P(MatrixConstructorTest, CannotInferElementTypeFromVectors_Mismatch) {
-  const auto param = GetParam();
-
-  std::stringstream err;
-  err << "12:34 error: cannot infer matrix element type, as constructor "
-         "arguments have different types";
-
-  ast::ExpressionList args;
-  for (uint32_t i = 0; i < param.columns; i++) {
-    err << "\n";
-    auto src = Source{{1, 10 + i}};
-    if (i == 1) {
-      // Odd one out
-      args.push_back(Construct(src, ty.vec<i32>(param.rows)));
-      err << src << " note: argument " << i << " has type vec" << param.rows
-          << "<i32>";
-    } else {
-      args.push_back(Construct(src, ty.vec<f32>(param.rows)));
-      err << src << " note: argument " << i << " has type vec" << param.rows
-          << "<f32>";
-    }
-  }
-
-  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
-  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), err.str());
-}
-
-TEST_P(MatrixConstructorTest, CannotInferElementTypeFromScalars_Mismatch) {
-  const auto param = GetParam();
-
-  std::stringstream err;
-  err << "12:34 error: cannot infer matrix element type, as constructor "
-         "arguments have different types";
-  ast::ExpressionList args;
-  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
-    err << "\n";
-    auto src = Source{{1, 10 + i}};
-    if (i == 3) {
-      args.push_back(Expr(src, static_cast<i32>(i)));  // The odd one out
-      err << src << " note: argument " << i << " has type i32";
-    } else {
-      args.push_back(Expr(src, static_cast<f32>(i)));
-      err << src << " note: argument " << i << " has type f32";
-    }
-  }
-
-  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
-  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(), err.str());
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         MatrixConstructorTest,
-                         testing::Values(MatrixDimensions{2, 2},
-                                         MatrixDimensions{3, 2},
-                                         MatrixDimensions{4, 2},
-                                         MatrixDimensions{2, 3},
-                                         MatrixDimensions{3, 3},
-                                         MatrixDimensions{4, 3},
-                                         MatrixDimensions{2, 4},
-                                         MatrixDimensions{3, 4},
-                                         MatrixDimensions{4, 4}));
-}  // namespace MatrixConstructor
-
-namespace StructConstructor {
-using builder::CreatePtrs;
-using builder::CreatePtrsFor;
-using builder::f32;
-using builder::i32;
-using builder::mat2x2;
-using builder::mat3x3;
-using builder::mat4x4;
-using builder::u32;
-using builder::vec2;
-using builder::vec3;
-using builder::vec4;
-
-constexpr CreatePtrs all_types[] = {
-    CreatePtrsFor<bool>(),         //
-    CreatePtrsFor<u32>(),          //
-    CreatePtrsFor<i32>(),          //
-    CreatePtrsFor<f32>(),          //
-    CreatePtrsFor<vec4<bool>>(),   //
-    CreatePtrsFor<vec2<i32>>(),    //
-    CreatePtrsFor<vec3<u32>>(),    //
-    CreatePtrsFor<vec4<f32>>(),    //
-    CreatePtrsFor<mat2x2<f32>>(),  //
-    CreatePtrsFor<mat3x3<f32>>(),  //
-    CreatePtrsFor<mat4x4<f32>>()   //
-};
-
-auto number_of_members = testing::Values(2u, 32u, 64u);
-
-using StructConstructorInputsTest =
-    ResolverTestWithParam<std::tuple<CreatePtrs,  // struct member type
-                                     uint32_t>>;  // number of struct members
-TEST_P(StructConstructorInputsTest, TooFew) {
-  auto& param = GetParam();
-  auto& str_params = std::get<0>(param);
-  uint32_t N = std::get<1>(param);
-
-  ast::StructMemberList members;
-  ast::ExpressionList values;
-  for (uint32_t i = 0; i < N; i++) {
-    auto* struct_type = str_params.ast(*this);
-    members.push_back(Member("member_" + std::to_string(i), struct_type));
-    if (i < N - 1) {
-      auto* ctor_value_expr = str_params.expr(*this, 0);
-      values.push_back(ctor_value_expr);
-    }
-  }
-  auto* s = Structure("s", members);
-  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), values);
-  WrapInFunction(tc);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: struct constructor has too few inputs: expected " +
-                std::to_string(N) + ", found " + std::to_string(N - 1));
-}
-
-TEST_P(StructConstructorInputsTest, TooMany) {
-  auto& param = GetParam();
-  auto& str_params = std::get<0>(param);
-  uint32_t N = std::get<1>(param);
-
-  ast::StructMemberList members;
-  ast::ExpressionList values;
-  for (uint32_t i = 0; i < N + 1; i++) {
-    if (i < N) {
-      auto* struct_type = str_params.ast(*this);
-      members.push_back(Member("member_" + std::to_string(i), struct_type));
-    }
-    auto* ctor_value_expr = str_params.expr(*this, 0);
-    values.push_back(ctor_value_expr);
-  }
-  auto* s = Structure("s", members);
-  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), values);
-  WrapInFunction(tc);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: struct constructor has too many inputs: expected " +
-                std::to_string(N) + ", found " + std::to_string(N + 1));
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         StructConstructorInputsTest,
-                         testing::Combine(testing::ValuesIn(all_types),
-                                          number_of_members));
-using StructConstructorTypeTest =
-    ResolverTestWithParam<std::tuple<CreatePtrs,  // struct member type
-                                     CreatePtrs,  // constructor value type
-                                     uint32_t>>;  // number of struct members
-TEST_P(StructConstructorTypeTest, AllTypes) {
-  auto& param = GetParam();
-  auto& str_params = std::get<0>(param);
-  auto& ctor_params = std::get<1>(param);
-  uint32_t N = std::get<2>(param);
-
-  if (str_params.ast == ctor_params.ast) {
-    return;
-  }
-
-  ast::StructMemberList members;
-  ast::ExpressionList values;
-  // make the last value of the constructor to have a different type
-  uint32_t constructor_value_with_different_type = N - 1;
-  for (uint32_t i = 0; i < N; i++) {
-    auto* struct_type = str_params.ast(*this);
-    members.push_back(Member("member_" + std::to_string(i), struct_type));
-    auto* ctor_value_expr = (i == constructor_value_with_different_type)
-                                ? ctor_params.expr(*this, 0)
-                                : str_params.expr(*this, 0);
-    values.push_back(ctor_value_expr);
-  }
-  auto* s = Structure("s", members);
-  auto* tc = Construct(ty.Of(s), values);
-  WrapInFunction(tc);
-
-  std::string found = FriendlyName(ctor_params.ast(*this));
-  std::string expected = FriendlyName(str_params.ast(*this));
-  std::stringstream err;
-  err << "error: type in struct constructor does not match struct member ";
-  err << "type: expected '" << expected << "', found '" << found << "'";
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), err.str());
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
-                         StructConstructorTypeTest,
-                         testing::Combine(testing::ValuesIn(all_types),
-                                          testing::ValuesIn(all_types),
-                                          number_of_members));
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Nested) {
-  auto* inner_m = Member("m", ty.i32());
-  auto* inner_s = Structure("inner_s", {inner_m});
-
-  auto* m0 = Member("m0", ty.i32());
-  auto* m1 = Member("m1", ty.Of(inner_s));
-  auto* m2 = Member("m2", ty.i32());
-  auto* s = Structure("s", {m0, m1, m2});
-
-  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), 1, 1, 1);
-  WrapInFunction(tc);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: type in struct constructor does not match struct member "
-            "type: expected 'inner_s', found 'i32'");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct) {
-  auto* m = Member("m", ty.i32());
-  auto* s = Structure("MyInputs", {m});
-  auto* tc = Construct(Source{{12, 34}}, ty.Of(s));
-  WrapInFunction(tc);
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Empty) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str)));
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-}  // namespace StructConstructor
-
-TEST_F(ResolverTypeConstructorValidationTest, NonConstructibleType_Atomic) {
-  WrapInFunction(
-      Assign(Phony(), Construct(Source{{12, 34}}, ty.atomic(ty.i32()))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       NonConstructibleType_AtomicArray) {
-  WrapInFunction(Assign(
-      Phony(), Construct(Source{{12, 34}}, ty.array(ty.atomic(ty.i32()), 4))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: array constructor has non-constructible element type");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest,
-       NonConstructibleType_AtomicStructMember) {
-  auto* str = Structure("S", {Member("a", ty.atomic(ty.i32()))});
-  WrapInFunction(Assign(Phony(), Construct(Source{{12, 34}}, ty.Of(str))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: struct constructor has non-constructible type");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, NonConstructibleType_Sampler) {
-  WrapInFunction(Assign(
-      Phony(),
-      Construct(Source{{12, 34}}, ty.sampler(ast::SamplerKind::kSampler))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, TypeConstructorAsStatement) {
-  WrapInFunction(
-      CallStmt(Construct(Source{{12, 34}}, ty.vec2<f32>(), 1.f, 2.f)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: type constructor evaluated but not used");
-}
-
-TEST_F(ResolverTypeConstructorValidationTest, TypeConversionAsStatement) {
-  WrapInFunction(CallStmt(Construct(Source{{12, 34}}, ty.f32(), 1)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: type cast evaluated but not used");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
deleted file mode 100644
index 1c8f00b..0000000
--- a/src/resolver/type_validation_test.cc
+++ /dev/null
@@ -1,1163 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/id_attribute.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-// Helpers and typedefs
-template <typename T>
-using DataType = builder::DataType<T>;
-template <typename T>
-using vec2 = builder::vec2<T>;
-template <typename T>
-using vec3 = builder::vec3<T>;
-template <typename T>
-using vec4 = builder::vec4<T>;
-template <typename T>
-using mat2x2 = builder::mat2x2<T>;
-template <typename T>
-using mat3x3 = builder::mat3x3<T>;
-template <typename T>
-using mat4x4 = builder::mat4x4<T>;
-template <int N, typename T>
-using array = builder::array<N, T>;
-template <typename T>
-using alias = builder::alias<T>;
-template <typename T>
-using alias1 = builder::alias1<T>;
-template <typename T>
-using alias2 = builder::alias2<T>;
-template <typename T>
-using alias3 = builder::alias3<T>;
-using f32 = builder::f32;
-using i32 = builder::i32;
-using u32 = builder::u32;
-
-class ResolverTypeValidationTest : public resolver::TestHelper,
-                                   public testing::Test {};
-
-TEST_F(ResolverTypeValidationTest, VariableDeclNoConstructor_Pass) {
-  // {
-  // var a :i32;
-  // a = 2;
-  // }
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, nullptr);
-  auto* lhs = Expr("a");
-  auto* rhs = Expr(2);
-
-  auto* body =
-      Block(Decl(var), Assign(Source{Source::Location{12, 34}}, lhs, rhs));
-
-  WrapInFunction(body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(lhs), nullptr);
-  ASSERT_NE(TypeOf(rhs), nullptr);
-}
-
-TEST_F(ResolverTypeValidationTest, GlobalConstantNoConstructor_Pass) {
-  // @id(0) override a :i32;
-  Override(Source{{12, 34}}, "a", ty.i32(), nullptr, ast::AttributeList{Id(0)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, GlobalVariableWithStorageClass_Pass) {
-  // var<private> global_var: f32;
-  Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kPrivate);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
-  // const<private> global_var: f32;
-  AST().AddGlobalVariable(create<ast::Variable>(
-      Source{{12, 34}}, Symbols().Register("global_var"),
-      ast::StorageClass::kPrivate, ast::Access::kUndefined, ty.f32(), true,
-      false, Expr(1.23f), ast::AttributeList{}));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: global constants shouldn't have a storage class");
-}
-
-TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) {
-  // let global_var: f32;
-  GlobalConst(Source{{12, 34}}, "global_var", ty.f32(), Construct(ty.f32()));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, GlobalVariableUnique_Pass) {
-  // var global_var0 : f32 = 0.1;
-  // var global_var1 : i32 = 0;
-
-  Global("global_var0", ty.f32(), ast::StorageClass::kPrivate, Expr(0.1f));
-
-  Global(Source{{12, 34}}, "global_var1", ty.f32(), ast::StorageClass::kPrivate,
-         Expr(1.0f));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest,
-       GlobalVariableFunctionVariableNotUnique_Pass) {
-  // fn my_func() {
-  //   var a: f32 = 2.0;
-  // }
-  // var a: f32 = 2.1;
-
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-
-  Func("my_func", ast::VariableList{}, ty.void_(), {Decl(var)});
-
-  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScope_Pass) {
-  // {
-  // if (true) { var a : f32 = 2.0; }
-  // var a : f32 = 3.14;
-  // }
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-
-  auto* cond = Expr(true);
-  auto* body = Block(Decl(var));
-
-  auto* var_a_float = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
-
-  auto* outer_body =
-      Block(create<ast::IfStatement>(cond, body, ast::ElseStatementList{}),
-            Decl(Source{{12, 34}}, var_a_float));
-
-  WrapInFunction(outer_body);
-
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScopeBlock_Pass) {
-  // {
-  //  { var a : f32; }
-  //  var a : f32;
-  // }
-  auto* var_inner = Var("a", ty.f32(), ast::StorageClass::kNone);
-  auto* inner = Block(Decl(Source{{12, 34}}, var_inner));
-
-  auto* var_outer = Var("a", ty.f32(), ast::StorageClass::kNone);
-  auto* outer_body = Block(inner, Decl(var_outer));
-
-  WrapInFunction(outer_body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest,
-       RedeclaredIdentifierDifferentFunctions_Pass) {
-  // func0 { var a : f32 = 2.0; return; }
-  // func1 { var a : f32 = 3.0; return; }
-  auto* var0 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-
-  auto* var1 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1.0f));
-
-  Func("func0", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Source{{12, 34}}, var0),
-           Return(),
-       },
-       ast::AttributeList{});
-
-  Func("func1", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Source{{13, 34}}, var1),
-           Return(),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Pass) {
-  // var<private> a : array<f32, 4u>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4u)),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Pass) {
-  // var<private> a : array<f32, 4>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4)),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Pass) {
-  // let size = 4u;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(4u));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Pass) {
-  // let size = 4;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(4));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Zero) {
-  // var<private> a : array<f32, 0u>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0u)),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Zero) {
-  // var<private> a : array<f32, 0>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0)),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Negative) {
-  // var<private> a : array<f32, -10>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, -10)),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Zero) {
-  // let size = 0u;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(0u));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Zero) {
-  // let size = 0;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(0));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Negative) {
-  // let size = -10;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(-10));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_FloatLiteral) {
-  // var<private> a : array<f32, 10.0>;
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 10.f)),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_IVecLiteral) {
-  // var<private> a : array<f32, vec2<i32>(10, 10)>;
-  Global(
-      "a",
-      ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.vec2<i32>(), 10, 10)),
-      ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_FloatConstant) {
-  // let size = 10.0;
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Expr(10.f));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_IVecConstant) {
-  // let size = vec2<i32>(100, 100);
-  // var<private> a : array<f32, size>;
-  GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100, 100));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ImplicitStride) {
-  // var<private> a : array<f32, 0x40000000>;
-  Global("a", ty.array(Source{{12, 34}}, ty.f32(), 0x40000000),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: array size in bytes must not exceed 0xffffffff, but "
-            "is 0x100000000");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ExplicitStride) {
-  // var<private> a : @stride(8) array<f32, 0x20000000>;
-  Global("a", ty.array(Source{{12, 34}}, ty.f32(), 0x20000000, 8),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: array size in bytes must not exceed 0xffffffff, but "
-            "is 0x100000000");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_OverridableConstant) {
-  // override size = 10;
-  // var<private> a : array<f32, size>;
-  Override("size", nullptr, Expr(10));
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: array size expression must not be pipeline-overridable");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
-  // var<private> size : i32 = 10;
-  // var<private> a : array<f32, size>;
-  Global("size", ty.i32(), Expr(10), ast::StorageClass::kPrivate);
-  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: array size identifier must be a module-scope constant");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_FunctionConstant) {
-  // {
-  //   let size = 10;
-  //   var a : array<f32, size>;
-  // }
-  auto* size = Const("size", nullptr, Expr(10));
-  auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
-  WrapInFunction(Block(Decl(size), Decl(a)));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: array size identifier must be a module-scope constant");
-}
-
-TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
-  // var a : array<f32, i32(4)>;
-  auto* size = Const("size", nullptr, Expr(10));
-  auto* a =
-      Var("a", ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.i32(), 4)));
-  WrapInFunction(Block(Decl(size), Decl(a)));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: array size expression must be either a literal or a "
-            "module-scope constant");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) {
-  /// @stage(vertex)
-  // fn func() { var a : array<i32>; }
-
-  auto* var =
-      Var(Source{{12, 34}}, "a", ty.array<i32>(), ast::StorageClass::kNone);
-
-  Func("func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kVertex),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-12:34 note: while instantiating variable a)");
-}
-
-TEST_F(ResolverTypeValidationTest, Struct_Member_VectorNoType) {
-  // struct S {
-  //   a: vec3;
-  // };
-
-  Structure("S",
-            {Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverTypeValidationTest, Struct_Member_MatrixNoType) {
-  // struct S {
-  //   a: mat3x3;
-  // };
-  Structure(
-      "S", {Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
-}
-
-TEST_F(ResolverTypeValidationTest, Struct_TooBig) {
-  // struct Foo {
-  //   a: array<f32, 0x20000000>;
-  //   b: array<f32, 0x20000000>;
-  // };
-
-  Structure(Source{{12, 34}}, "Foo",
-            {
-                Member("a", ty.array<f32, 0x20000000>()),
-                Member("b", ty.array<f32, 0x20000000>()),
-            });
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: struct size in bytes must not exceed 0xffffffff, but "
-            "is 0x100000000");
-}
-
-TEST_F(ResolverTypeValidationTest, Struct_MemberOffset_TooBig) {
-  // struct Foo {
-  //   a: array<f32, 0x3fffffff>;
-  //   b: f32;
-  //   c: f32;
-  // };
-
-  Structure("Foo", {
-                       Member("a", ty.array<f32, 0x3fffffff>()),
-                       Member("b", ty.f32()),
-                       Member(Source{{12, 34}}, "c", ty.f32()),
-                   });
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: struct member has byte offset 0x100000000, but must "
-            "not exceed 0xffffffff");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLast_Pass) {
-  // [[block]]
-  // struct Foo {
-  //   vf: f32;
-  //   rt: array<f32>;
-  // };
-
-  Structure("Foo",
-            {
-                Member("vf", ty.f32()),
-                Member("rt", ty.array<f32>()),
-            },
-            {create<ast::StructBlockAttribute>()});
-
-  WrapInFunction();
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayInArray) {
-  // struct Foo {
-  //   rt : array<array<f32>, 4>;
-  // };
-
-  Structure("Foo",
-            {Member("rt", ty.array(Source{{12, 34}}, ty.array<f32>(), 4))});
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: an array element type cannot contain a runtime-sized "
-            "array");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInArray) {
-  // struct Foo {
-  //   rt : array<f32>;
-  // };
-  // var<private> a : array<Foo, 4>;
-
-  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
-  Global("v", ty.array(Source{{12, 34}}, ty.Of(foo), 4),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: an array element type cannot contain a runtime-sized "
-            "array");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInStruct) {
-  // struct Foo {
-  //   rt : array<f32>;
-  // };
-  // struct Outer {
-  //   inner : Foo;
-  // };
-
-  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
-  Structure("Outer", {Member(Source{{12, 34}}, "inner", ty.Of(foo))});
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: a struct that contains a runtime array cannot be "
-            "nested inside another struct");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
-  // [[block]]
-  // struct Foo {
-  //   rt: array<f32>;
-  //   vf: f32;
-  // };
-
-  Structure("Foo",
-            {
-                Member(Source{{12, 34}}, "rt", ty.array<f32>()),
-                Member("vf", ty.f32()),
-            },
-            {create<ast::StructBlockAttribute>()});
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime arrays may only appear as the last member of a struct)");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayAsGlobalVariable) {
-  Global(Source{{56, 78}}, "g", ty.array<i32>(), ast::StorageClass::kPrivate);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
-  auto* v = Var(Source{{56, 78}}, "g", ty.array<i32>());
-  WrapInFunction(v);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
-  // fn func(a : array<u32>) {}
-  // @stage(vertex) fn main() {}
-
-  auto* param = Param(Source{{12, 34}}, "a", ty.array<i32>());
-
-  Func("func", ast::VariableList{param}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{});
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kVertex),
-       });
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-12:34 note: while instantiating parameter a)");
-}
-
-TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsParameter_Fail) {
-  // fn func(a : ptr<workgroup, array<u32>>) {}
-
-  auto* param =
-      Param(Source{{12, 34}}, "a",
-            ty.pointer(ty.array<i32>(), ast::StorageClass::kWorkgroup));
-
-  Func("func", ast::VariableList{param}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-12:34 note: while instantiating parameter a)");
-}
-
-TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
-  // [[block]]
-  // type RTArr = array<u32>;
-  // struct s {
-  //  b: RTArr;
-  //  a: u32;
-  //}
-
-  auto* alias = Alias("RTArr", ty.array<u32>());
-  Structure("s",
-            {
-                Member(Source{{12, 34}}, "b", ty.Of(alias)),
-                Member("a", ty.u32()),
-            },
-            {create<ast::StructBlockAttribute>()});
-
-  WrapInFunction();
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: runtime arrays may only appear as the last member of "
-            "a struct");
-}
-
-TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsLast_Pass) {
-  // [[block]]
-  // type RTArr = array<u32>;
-  // struct s {
-  //  a: u32;
-  //  b: RTArr;
-  //}
-
-  auto* alias = Alias("RTArr", ty.array<u32>());
-  Structure("s",
-            {
-                Member("a", ty.u32()),
-                Member("b", ty.Of(alias)),
-            },
-            {create<ast::StructBlockAttribute>()});
-
-  WrapInFunction();
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTypeValidationTest, ArrayOfNonStorableType) {
-  auto* tex_ty = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
-  Global("arr", ty.array(Source{{12, 34}}, tex_ty, 4),
-         ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: texture_2d<f32> cannot be used as an element type of "
-            "an array");
-}
-
-TEST_F(ResolverTypeValidationTest, VariableAsType) {
-  // var<private> a : i32;
-  // var<private> b : a;
-  Global("a", ty.i32(), ast::StorageClass::kPrivate);
-  Global("b", ty.type_name("a"), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(error: cannot use variable 'a' as type
-note: 'a' declared here)");
-}
-
-TEST_F(ResolverTypeValidationTest, FunctionAsType) {
-  // fn f() {}
-  // var<private> v : f;
-  Func("f", {}, ty.void_(), {});
-  Global("v", ty.type_name("f"), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(error: cannot use function 'f' as type
-note: 'f' declared here)");
-}
-
-namespace GetCanonicalTests {
-struct Params {
-  builder::ast_type_func_ptr create_ast_type;
-  builder::sem_type_func_ptr create_sem_type;
-};
-
-template <typename T>
-constexpr Params ParamsFor() {
-  return Params{DataType<T>::AST, DataType<T>::Sem};
-}
-
-static constexpr Params cases[] = {
-    ParamsFor<bool>(),
-    ParamsFor<alias<bool>>(),
-    ParamsFor<alias1<alias<bool>>>(),
-
-    ParamsFor<vec3<f32>>(),
-    ParamsFor<alias<vec3<f32>>>(),
-    ParamsFor<alias1<alias<vec3<f32>>>>(),
-
-    ParamsFor<vec3<alias<f32>>>(),
-    ParamsFor<alias1<vec3<alias<f32>>>>(),
-    ParamsFor<alias2<alias1<vec3<alias<f32>>>>>(),
-    ParamsFor<alias3<alias2<vec3<alias1<alias<f32>>>>>>(),
-
-    ParamsFor<mat3x3<alias<f32>>>(),
-    ParamsFor<alias1<mat3x3<alias<f32>>>>(),
-    ParamsFor<alias2<alias1<mat3x3<alias<f32>>>>>(),
-    ParamsFor<alias3<alias2<mat3x3<alias1<alias<f32>>>>>>(),
-
-    ParamsFor<alias1<alias<bool>>>(),
-    ParamsFor<alias1<alias<vec3<f32>>>>(),
-    ParamsFor<alias1<alias<mat3x3<f32>>>>(),
-};
-
-using CanonicalTest = ResolverTestWithParam<Params>;
-TEST_P(CanonicalTest, All) {
-  auto& params = GetParam();
-
-  auto* type = params.create_ast_type(*this);
-
-  auto* var = Var("v", type);
-  auto* expr = Expr("v");
-  WrapInFunction(var, expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* got = TypeOf(expr)->UnwrapRef();
-  auto* expected = params.create_sem_type(*this);
-
-  EXPECT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
-                           << "expected: " << FriendlyName(expected) << "\n";
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         CanonicalTest,
-                         testing::ValuesIn(cases));
-
-}  // namespace GetCanonicalTests
-
-namespace MultisampledTextureTests {
-struct DimensionParams {
-  ast::TextureDimension dim;
-  bool is_valid;
-};
-
-static constexpr DimensionParams dimension_cases[] = {
-    DimensionParams{ast::TextureDimension::k1d, false},
-    DimensionParams{ast::TextureDimension::k2d, true},
-    DimensionParams{ast::TextureDimension::k2dArray, false},
-    DimensionParams{ast::TextureDimension::k3d, false},
-    DimensionParams{ast::TextureDimension::kCube, false},
-    DimensionParams{ast::TextureDimension::kCubeArray, false}};
-
-using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
-TEST_P(MultisampledTextureDimensionTest, All) {
-  auto& params = GetParam();
-  Global(Source{{12, 34}}, "a", ty.multisampled_texture(params.dim, ty.i32()),
-         ast::StorageClass::kNone, nullptr,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: only 2d multisampled textures are supported");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         MultisampledTextureDimensionTest,
-                         testing::ValuesIn(dimension_cases));
-
-struct TypeParams {
-  builder::ast_type_func_ptr type_func;
-  bool is_valid;
-};
-
-template <typename T>
-constexpr TypeParams TypeParamsFor(bool is_valid) {
-  return TypeParams{DataType<T>::AST, is_valid};
-}
-
-static constexpr TypeParams type_cases[] = {
-    TypeParamsFor<bool>(false),
-    TypeParamsFor<i32>(true),
-    TypeParamsFor<u32>(true),
-    TypeParamsFor<f32>(true),
-
-    TypeParamsFor<alias<bool>>(false),
-    TypeParamsFor<alias<i32>>(true),
-    TypeParamsFor<alias<u32>>(true),
-    TypeParamsFor<alias<f32>>(true),
-
-    TypeParamsFor<vec3<f32>>(false),
-    TypeParamsFor<mat3x3<f32>>(false),
-
-    TypeParamsFor<alias<vec3<f32>>>(false),
-    TypeParamsFor<alias<mat3x3<f32>>>(false),
-};
-
-using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
-TEST_P(MultisampledTextureTypeTest, All) {
-  auto& params = GetParam();
-  Global(Source{{12, 34}}, "a",
-         ty.multisampled_texture(ast::TextureDimension::k2d,
-                                 params.type_func(*this)),
-         ast::StorageClass::kNone, nullptr,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: texture_multisampled_2d<type>: type must be f32, "
-              "i32 or u32");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         MultisampledTextureTypeTest,
-                         testing::ValuesIn(type_cases));
-
-}  // namespace MultisampledTextureTests
-
-namespace StorageTextureTests {
-struct DimensionParams {
-  ast::TextureDimension dim;
-  bool is_valid;
-};
-
-static constexpr DimensionParams Dimension_cases[] = {
-    DimensionParams{ast::TextureDimension::k1d, true},
-    DimensionParams{ast::TextureDimension::k2d, true},
-    DimensionParams{ast::TextureDimension::k2dArray, true},
-    DimensionParams{ast::TextureDimension::k3d, true},
-    DimensionParams{ast::TextureDimension::kCube, false},
-    DimensionParams{ast::TextureDimension::kCubeArray, false}};
-
-using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
-TEST_P(StorageTextureDimensionTest, All) {
-  // @group(0) @binding(0)
-  // var a : texture_storage_*<ru32int, write>;
-  auto& params = GetParam();
-
-  auto* st =
-      ty.storage_texture(Source{{12, 34}}, params.dim,
-                         ast::TexelFormat::kR32Uint, ast::Access::kWrite);
-
-  Global("a", st, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: cube dimensions for storage textures are not "
-              "supported");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         StorageTextureDimensionTest,
-                         testing::ValuesIn(Dimension_cases));
-
-struct FormatParams {
-  ast::TexelFormat format;
-  bool is_valid;
-};
-
-static constexpr FormatParams format_cases[] = {
-    FormatParams{ast::TexelFormat::kR32Float, true},
-    FormatParams{ast::TexelFormat::kR32Sint, true},
-    FormatParams{ast::TexelFormat::kR32Uint, true},
-    FormatParams{ast::TexelFormat::kRg32Float, true},
-    FormatParams{ast::TexelFormat::kRg32Sint, true},
-    FormatParams{ast::TexelFormat::kRg32Uint, true},
-    FormatParams{ast::TexelFormat::kRgba16Float, true},
-    FormatParams{ast::TexelFormat::kRgba16Sint, true},
-    FormatParams{ast::TexelFormat::kRgba16Uint, true},
-    FormatParams{ast::TexelFormat::kRgba32Float, true},
-    FormatParams{ast::TexelFormat::kRgba32Sint, true},
-    FormatParams{ast::TexelFormat::kRgba32Uint, true},
-    FormatParams{ast::TexelFormat::kRgba8Sint, true},
-    FormatParams{ast::TexelFormat::kRgba8Snorm, true},
-    FormatParams{ast::TexelFormat::kRgba8Uint, true},
-    FormatParams{ast::TexelFormat::kRgba8Unorm, true}};
-
-using StorageTextureFormatTest = ResolverTestWithParam<FormatParams>;
-TEST_P(StorageTextureFormatTest, All) {
-  auto& params = GetParam();
-  // @group(0) @binding(0)
-  // var a : texture_storage_1d<*, write>;
-  // @group(0) @binding(1)
-  // var b : texture_storage_2d<*, write>;
-  // @group(0) @binding(2)
-  // var c : texture_storage_2d_array<*, write>;
-  // @group(0) @binding(3)
-  // var d : texture_storage_3d<*, write>;
-
-  auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
-                                  params.format, ast::Access::kWrite);
-  Global("a", st_a, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format,
-                                  ast::Access::kWrite);
-  Global("b", st_b, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 1)});
-
-  auto* st_c = ty.storage_texture(ast::TextureDimension::k2dArray,
-                                  params.format, ast::Access::kWrite);
-  Global("c", st_c, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 2)});
-
-  auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format,
-                                  ast::Access::kWrite);
-  Global("d", st_d, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 3)});
-
-  if (params.is_valid) {
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-  } else {
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: image format must be one of the texel formats "
-              "specified for storage textues in "
-              "https://gpuweb.github.io/gpuweb/wgsl/#texel-formats");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         StorageTextureFormatTest,
-                         testing::ValuesIn(format_cases));
-
-using StorageTextureAccessTest = ResolverTest;
-
-TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
-  // @group(0) @binding(0)
-  // var a : texture_storage_1d<ru32int>;
-
-  auto* st =
-      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
-                         ast::TexelFormat::kR32Uint, ast::Access::kUndefined);
-
-  Global("a", st, ast::StorageClass::kNone,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: storage texture missing access control");
-}
-
-TEST_F(StorageTextureAccessTest, RWAccess_Fail) {
-  // @group(0) @binding(0)
-  // var a : texture_storage_1d<ru32int, read_write>;
-
-  auto* st =
-      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
-                         ast::TexelFormat::kR32Uint, ast::Access::kReadWrite);
-
-  Global("a", st, ast::StorageClass::kNone, nullptr,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: storage textures currently only support 'write' "
-            "access control");
-}
-
-TEST_F(StorageTextureAccessTest, ReadOnlyAccess_Fail) {
-  // @group(0) @binding(0)
-  // var a : texture_storage_1d<ru32int, read>;
-
-  auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
-                                ast::TexelFormat::kR32Uint, ast::Access::kRead);
-
-  Global("a", st, ast::StorageClass::kNone, nullptr,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: storage textures currently only support 'write' "
-            "access control");
-}
-
-TEST_F(StorageTextureAccessTest, WriteOnlyAccess_Pass) {
-  // @group(0) @binding(0)
-  // var a : texture_storage_1d<ru32int, write>;
-
-  auto* st =
-      ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Uint,
-                         ast::Access::kWrite);
-
-  Global("a", st, ast::StorageClass::kNone, nullptr,
-         ast::AttributeList{GroupAndBinding(0, 0)});
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-}  // namespace StorageTextureTests
-
-namespace MatrixTests {
-struct Params {
-  uint32_t columns;
-  uint32_t rows;
-  builder::ast_type_func_ptr elem_ty;
-};
-
-template <typename T>
-constexpr Params ParamsFor(uint32_t columns, uint32_t rows) {
-  return Params{columns, rows, DataType<T>::AST};
-}
-
-using ValidMatrixTypes = ResolverTestWithParam<Params>;
-TEST_P(ValidMatrixTypes, Okay) {
-  // var a : matNxM<EL_TY>;
-  auto& params = GetParam();
-  Global("a", ty.mat(params.elem_ty(*this), params.columns, params.rows),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         ValidMatrixTypes,
-                         testing::Values(ParamsFor<f32>(2, 2),
-                                         ParamsFor<f32>(2, 3),
-                                         ParamsFor<f32>(2, 4),
-                                         ParamsFor<f32>(3, 2),
-                                         ParamsFor<f32>(3, 3),
-                                         ParamsFor<f32>(3, 4),
-                                         ParamsFor<f32>(4, 2),
-                                         ParamsFor<f32>(4, 3),
-                                         ParamsFor<f32>(4, 4),
-                                         ParamsFor<alias<f32>>(4, 2),
-                                         ParamsFor<alias<f32>>(4, 3),
-                                         ParamsFor<alias<f32>>(4, 4)));
-
-using InvalidMatrixElementTypes = ResolverTestWithParam<Params>;
-TEST_P(InvalidMatrixElementTypes, InvalidElementType) {
-  // var a : matNxM<EL_TY>;
-  auto& params = GetParam();
-  Global("a",
-         ty.mat(Source{{12, 34}}, params.elem_ty(*this), params.columns,
-                params.rows),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: matrix element type must be 'f32'");
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         InvalidMatrixElementTypes,
-                         testing::Values(ParamsFor<bool>(4, 2),
-                                         ParamsFor<i32>(4, 3),
-                                         ParamsFor<u32>(4, 4),
-                                         ParamsFor<vec2<f32>>(2, 2),
-                                         ParamsFor<vec3<i32>>(2, 3),
-                                         ParamsFor<vec4<u32>>(2, 4),
-                                         ParamsFor<mat2x2<f32>>(3, 2),
-                                         ParamsFor<mat3x3<f32>>(3, 3),
-                                         ParamsFor<mat4x4<f32>>(3, 4),
-                                         ParamsFor<array<2, f32>>(4, 2)));
-}  // namespace MatrixTests
-
-namespace VectorTests {
-struct Params {
-  uint32_t width;
-  builder::ast_type_func_ptr elem_ty;
-};
-
-template <typename T>
-constexpr Params ParamsFor(uint32_t width) {
-  return Params{width, DataType<T>::AST};
-}
-
-using ValidVectorTypes = ResolverTestWithParam<Params>;
-TEST_P(ValidVectorTypes, Okay) {
-  // var a : vecN<EL_TY>;
-  auto& params = GetParam();
-  Global("a", ty.vec(params.elem_ty(*this), params.width),
-         ast::StorageClass::kPrivate);
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         ValidVectorTypes,
-                         testing::Values(ParamsFor<bool>(2),
-                                         ParamsFor<f32>(2),
-                                         ParamsFor<i32>(2),
-                                         ParamsFor<u32>(2),
-                                         ParamsFor<bool>(3),
-                                         ParamsFor<f32>(3),
-                                         ParamsFor<i32>(3),
-                                         ParamsFor<u32>(3),
-                                         ParamsFor<bool>(4),
-                                         ParamsFor<f32>(4),
-                                         ParamsFor<i32>(4),
-                                         ParamsFor<u32>(4),
-                                         ParamsFor<alias<bool>>(4),
-                                         ParamsFor<alias<f32>>(4),
-                                         ParamsFor<alias<i32>>(4),
-                                         ParamsFor<alias<u32>>(4)));
-
-using InvalidVectorElementTypes = ResolverTestWithParam<Params>;
-TEST_P(InvalidVectorElementTypes, InvalidElementType) {
-  // var a : vecN<EL_TY>;
-  auto& params = GetParam();
-  Global("a", ty.vec(Source{{12, 34}}, params.elem_ty(*this), params.width),
-         ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: vector element type must be 'bool', 'f32', 'i32' "
-            "or 'u32'");
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
-                         InvalidVectorElementTypes,
-                         testing::Values(ParamsFor<vec2<f32>>(2),
-                                         ParamsFor<vec3<i32>>(2),
-                                         ParamsFor<vec4<u32>>(2),
-                                         ParamsFor<mat2x2<f32>>(2),
-                                         ParamsFor<mat3x3<f32>>(2),
-                                         ParamsFor<mat4x4<f32>>(2),
-                                         ParamsFor<array<2, f32>>(2)));
-}  // namespace VectorTests
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
deleted file mode 100644
index a7df8bf..0000000
--- a/src/resolver/validation_test.cc
+++ /dev/null
@@ -1,1320 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/resolver/resolver.h"
-
-#include "gmock/gmock.h"
-#include "gtest/gtest-spi.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-using ::testing::ElementsAre;
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace resolver {
-namespace {
-
-using ResolverValidationTest = ResolverTest;
-
-class FakeStmt : public Castable<FakeStmt, ast::Statement> {
- public:
-  FakeStmt(ProgramID pid, Source src) : Base(pid, src) {}
-  FakeStmt* Clone(CloneContext*) const override { return nullptr; }
-};
-
-class FakeExpr : public Castable<FakeExpr, ast::Expression> {
- public:
-  FakeExpr(ProgramID pid, Source src) : Base(pid, src) {}
-  FakeExpr* Clone(CloneContext*) const override { return nullptr; }
-};
-
-TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInVertexStage) {
-  Global(Source{{1, 2}}, "wg", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
-  Global("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
-
-  Func(Source{{9, 10}}, "f0", ast::VariableList{}, ty.vec4<f32>(),
-       {stmt, Return(Expr("dst"))},
-       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
-       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "3:4 error: workgroup memory cannot be used by vertex pipeline "
-            "stage\n1:2 note: variable is declared here");
-}
-
-TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInFragmentStage) {
-  // var<workgroup> wg : vec4<f32>;
-  // var<workgroup> dst : vec4<f32>;
-  // fn f2(){ dst = wg; }
-  // fn f1() { f2(); }
-  // @stage(fragment)
-  // fn f0() {
-  //  f1();
-  //}
-
-  Global(Source{{1, 2}}, "wg", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
-  Global("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
-
-  Func(Source{{5, 6}}, "f2", {}, ty.void_(), {stmt});
-  Func(Source{{7, 8}}, "f1", {}, ty.void_(), {CallStmt(Call("f2"))});
-  Func(Source{{9, 10}}, "f0", {}, ty.void_(), {CallStmt(Call("f1"))},
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:4 error: workgroup memory cannot be used by fragment pipeline stage
-1:2 note: variable is declared here
-5:6 note: called by function 'f2'
-7:8 note: called by function 'f1'
-9:10 note: called by entry point 'f0')");
-}
-
-TEST_F(ResolverValidationTest, UnhandledStmt) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.WrapInFunction(b.create<FakeStmt>());
-        Program(std::move(b));
-      },
-      "internal compiler error: unhandled node type: tint::resolver::FakeStmt");
-}
-
-TEST_F(ResolverValidationTest, Stmt_If_NonBool) {
-  // if (1.23f) {}
-
-  WrapInFunction(If(Expr(Source{{12, 34}}, 1.23f), Block()));
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: if statement condition must be bool, got f32");
-}
-
-TEST_F(ResolverValidationTest, Stmt_Else_NonBool) {
-  // else (1.23f) {}
-
-  WrapInFunction(
-      If(Expr(true), Block(), Else(Expr(Source{{12, 34}}, 1.23f), Block())));
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: else statement condition must be bool, got f32");
-}
-
-TEST_F(ResolverValidationTest, Expr_ErrUnknownExprType) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.WrapInFunction(b.create<FakeExpr>());
-        Resolver(&b).Resolve();
-      },
-      "internal compiler error: unhandled expression type: "
-      "tint::resolver::FakeExpr");
-}
-
-TEST_F(ResolverValidationTest, Expr_DontCall_Function) {
-  Func("func", {}, ty.void_(), {}, {});
-  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "func"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for function call");
-}
-
-TEST_F(ResolverValidationTest, Expr_DontCall_Builtin) {
-  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "round"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for builtin call");
-}
-
-TEST_F(ResolverValidationTest, Expr_DontCall_Type) {
-  Alias("T", ty.u32());
-  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "T"));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "3:8 error: missing '(' for type constructor or cast");
-}
-
-TEST_F(ResolverValidationTest, AssignmentStmt_InvalidLHS_BuiltinFunctionName) {
-  // normalize = 2;
-
-  auto* lhs = Expr(Source{{12, 34}}, "normalize");
-  auto* rhs = Expr(2);
-  auto* assign = Assign(lhs, rhs);
-  WrapInFunction(assign);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing '(' for builtin call");
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariable_Fail) {
-  // b = 2;
-
-  auto* lhs = Expr(Source{{12, 34}}, "b");
-  auto* rhs = Expr(2);
-  auto* assign = Assign(lhs, rhs);
-  WrapInFunction(assign);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'b'");
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
-  // {
-  //  b = 2;
-  // }
-
-  auto* lhs = Expr(Source{{12, 34}}, "b");
-  auto* rhs = Expr(2);
-
-  auto* body = Block(Assign(lhs, rhs));
-  WrapInFunction(body);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'b'");
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariableGlobalVariable_Pass) {
-  // var global_var: f32 = 2.1;
-  // fn my_func() {
-  //   global_var = 3.14;
-  //   return;
-  // }
-
-  Global("global_var", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
-
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Assign(Expr(Source{{12, 34}}, "global_var"), 3.14f),
-           Return(),
-       });
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariableInnerScope_Fail) {
-  // {
-  //   if (true) { var a : f32 = 2.0; }
-  //   a = 3.14;
-  // }
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-
-  auto* cond = Expr(true);
-  auto* body = Block(Decl(var));
-
-  SetSource(Source{{12, 34}});
-  auto* lhs = Expr(Source{{12, 34}}, "a");
-  auto* rhs = Expr(3.14f);
-
-  auto* outer_body =
-      Block(create<ast::IfStatement>(cond, body, ast::ElseStatementList{}),
-            Assign(lhs, rhs));
-
-  WrapInFunction(outer_body);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'a'");
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariableOuterScope_Pass) {
-  // {
-  //   var a : f32 = 2.0;
-  //   if (true) { a = 3.14; }
-  // }
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-
-  auto* lhs = Expr(Source{{12, 34}}, "a");
-  auto* rhs = Expr(3.14f);
-
-  auto* cond = Expr(true);
-  auto* body = Block(Assign(lhs, rhs));
-
-  auto* outer_body =
-      Block(Decl(var),
-            create<ast::IfStatement>(cond, body, ast::ElseStatementList{}));
-
-  WrapInFunction(outer_body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, UsingUndefinedVariableDifferentScope_Fail) {
-  // {
-  //  { var a : f32 = 2.0; }
-  //  { a = 3.14; }
-  // }
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
-  auto* first_body = Block(Decl(var));
-
-  auto* lhs = Expr(Source{{12, 34}}, "a");
-  auto* rhs = Expr(3.14f);
-  auto* second_body = Block(Assign(lhs, rhs));
-
-  auto* outer_body = Block(first_body, second_body);
-
-  WrapInFunction(outer_body);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'a'");
-}
-
-TEST_F(ResolverValidationTest, StorageClass_FunctionVariableWorkgroupClass) {
-  auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
-
-  auto* stmt = Decl(var);
-  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: function variable has a non-function storage class");
-}
-
-TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) {
-  auto* var = Var("s", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* stmt = Decl(var);
-  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: function variable has a non-function storage class");
-}
-
-TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
-  auto* t = ty.sampler(ast::SamplerKind::kSampler);
-  Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: variables of type 'sampler' must not have a storage class)");
-}
-
-TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) {
-  auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: variables of type 'texture_1d<f32>' must not have a storage class)");
-}
-
-TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* ident = Expr(Source{{{3, 3}, {3, 7}}}, "xyqz");
-
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:5 error: invalid vector swizzle character");
-}
-
-TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* ident = Expr(Source{{{3, 3}, {3, 7}}}, "rgyw");
-
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "3:3 error: invalid mixing of vector swizzle characters rgba with xyzw");
-}
-
-TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* ident = Expr(Source{{{3, 3}, {3, 8}}}, "zzzzz");
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle size");
-}
-
-TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadIndex) {
-  Global("my_vec", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* ident = Expr(Source{{3, 3}}, "z");
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle member");
-}
-
-TEST_F(ResolverValidationTest, Expr_MemberAccessor_BadParent) {
-  // var param: vec4<f32>
-  // let ret: f32 = *(&param).x;
-  auto* param = Var("param", ty.vec4<f32>());
-  auto* x = Expr(Source{{{3, 3}, {3, 8}}}, "x");
-
-  auto* addressOf_expr = AddressOf(Source{{12, 34}}, param);
-  auto* accessor_expr = MemberAccessor(addressOf_expr, x);
-  auto* star_p = Deref(accessor_expr);
-  auto* ret = Var("r", ty.f32(), star_p);
-  WrapInFunction(Decl(param), Decl(ret));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: invalid member accessor expression. Expected vector "
-            "or struct, got 'ptr<function, vec4<f32>, read_write>'");
-}
-
-TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncGoodParent) {
-  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
-  //     let x: f32 = (*p).z;
-  //     return x;
-  // }
-  auto* p =
-      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
-  auto* star_p = Deref(p);
-  auto* z = Expr(Source{{{3, 3}, {3, 8}}}, "z");
-  auto* accessor_expr = MemberAccessor(star_p, z);
-  auto* x = Var("x", ty.f32(), accessor_expr);
-  Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncBadParent) {
-  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
-  //     let x: f32 = *p.z;
-  //     return x;
-  // }
-  auto* p =
-      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
-  auto* z = Expr(Source{{{3, 3}, {3, 8}}}, "z");
-  auto* accessor_expr = MemberAccessor(p, z);
-  auto* star_p = Deref(accessor_expr);
-  auto* x = Var("x", ty.f32(), star_p);
-  Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "error: invalid member accessor expression. "
-      "Expected vector or struct, got 'ptr<function, vec4<f32>, read_write>'");
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
-  // loop  {
-  //     continue; // Bypasses z decl
-  //     var z : i32; // unreachable
-  //
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto error_loc = Source{{12, 34}};
-  auto* body =
-      Block(Continue(),
-            Decl(error_loc, Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Assign(Expr("z"), 2));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 warning: code is unreachable
-error: continue statement bypasses declaration of 'z'
-note: identifier 'z' declared here
-note: identifier 'z' referenced in continuing block here)");
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing_InBlocks) {
-  // loop  {
-  //     if (false) { break; }
-  //     var z : i32;
-  //     {{{continue;}}}
-  //     continue; // Ok
-  //
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto* body = Block(If(false, Block(Break())),  //
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
-                     Block(Block(Block(Continue()))));
-  auto* continuing = Block(Assign(Expr("z"), 2));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto cont_loc = Source{{12, 34}};
-  auto decl_loc = Source{{56, 78}};
-  auto ref_loc = Source{{90, 12}};
-  auto* body =
-      Block(If(Expr(true), Block(Continue(cont_loc))),
-            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Assign(Expr(ref_loc, "z"), 2));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: continue statement bypasses declaration of 'z'
-56:78 note: identifier 'z' declared here
-90:12 note: identifier 'z' referenced in continuing block here)");
-}
-
-TEST_F(
-    ResolverValidationTest,
-    Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         if (true) {
-  //             z = 2; // Must fail even if z is in a sub-scope
-  //         }
-  //     }
-  // }
-
-  auto cont_loc = Source{{12, 34}};
-  auto decl_loc = Source{{56, 78}};
-  auto ref_loc = Source{{90, 12}};
-  auto* body =
-      Block(If(Expr(true), Block(Continue(cont_loc))),
-            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
-
-  auto* continuing =
-      Block(If(Expr(true), Block(Assign(Expr(ref_loc, "z"), 2))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: continue statement bypasses declaration of 'z'
-56:78 note: identifier 'z' declared here
-90:12 note: identifier 'z' referenced in continuing block here)");
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageOutsideBlock) {
-  // loop  {
-  //     if (true) {
-  //         continue; // bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         // Must fail even if z is used in an expression that isn't
-  //         // directly contained inside a block.
-  //         if (z < 2) {
-  //         }
-  //     }
-  // }
-
-  auto cont_loc = Source{{12, 34}};
-  auto decl_loc = Source{{56, 78}};
-  auto ref_loc = Source{{90, 12}};
-  auto* body =
-      Block(If(Expr(true), Block(Continue(cont_loc))),
-            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
-  auto* compare = create<ast::BinaryExpression>(ast::BinaryOp::kLessThan,
-                                                Expr(ref_loc, "z"), Expr(2));
-  auto* continuing = Block(If(compare, Block()));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: continue statement bypasses declaration of 'z'
-56:78 note: identifier 'z' declared here
-90:12 note: identifier 'z' referenced in continuing block here)");
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingLoop) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         loop {
-  //             z = 2; // Must fail even if z is in a sub-scope
-  //         }
-  //     }
-  // }
-
-  auto cont_loc = Source{{12, 34}};
-  auto decl_loc = Source{{56, 78}};
-  auto ref_loc = Source{{90, 12}};
-  auto* body =
-      Block(If(Expr(true), Block(Continue(cont_loc))),
-            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
-
-  auto* continuing = Block(Loop(Block(Assign(Expr(ref_loc, "z"), 2))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: continue statement bypasses declaration of 'z'
-56:78 note: identifier 'z' declared here
-90:12 note: identifier 'z' referenced in continuing block here)");
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
-  // loop  {
-  //     loop {
-  //         if (true) { continue; } // OK: not part of the outer loop
-  //         break;
-  //     }
-  //     var z : i32;
-  //     break;
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(    //
-      If(true, Block(Continue())),  //
-      Break()));
-  auto* body = Block(inner_loop,                                          //
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
-                     Break());
-  auto* continuing = Block(Assign("z", 2));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
-  // loop  {
-  //     loop {
-  //         if (true) { continue; } // OK: not part of the outer loop
-  //         break;
-  //     }
-  //     var z : i32;
-  //     break;
-  //     continuing {
-  //         if (true) {
-  //             z = 2;
-  //         }
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
-                                Break()));
-  auto* body = Block(inner_loop,                                          //
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
-                     Break());
-  auto* continuing = Block(If(Expr(true), Block(Assign("z", 2))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
-  // loop  {
-  //     loop {
-  //         if (true) { continue; } // OK: not part of the outer loop
-  //         break;
-  //     }
-  //     var z : i32;
-  //     break;
-  //     continuing {
-  //         loop {
-  //             z = 2;
-  //             break;
-  //         }
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
-                                Break()));
-  auto* body = Block(inner_loop,                                          //
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
-                     Break());
-  auto* continuing = Block(Loop(Block(Assign("z", 2),  //
-                                      Break())));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
-  // loop  {
-  //     var z : i32;
-  //     if (true) { continue; }
-  //     break;
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto error_loc = Source{{12, 34}};
-  auto* body = Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
-                     If(true, Block(Continue())),  //
-                     Break());
-  auto* continuing = Block(Assign(Expr(error_loc, "z"), 2));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverTest, Stmt_Loop_ReturnInContinuing_Direct) {
-  // loop  {
-  //   continuing {
-  //     return;
-  //   }
-  // }
-
-  WrapInFunction(Loop(  // loop
-      Block(),          //   loop block
-      Block(            //   loop continuing block
-          Return(Source{{12, 34}}))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a return statement)");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_ReturnInContinuing_Indirect) {
-  // loop {
-  //   if (false) { break; }
-  //   continuing {
-  //     loop {
-  //       return;
-  //     }
-  //   }
-  // }
-
-  WrapInFunction(Loop(                   // outer loop
-      Block(If(false, Block(Break()))),  //   outer loop block
-      Block(Source{{56, 78}},            //   outer loop continuing block
-            Loop(                        //     inner loop
-                Block(                   //       inner loop block
-                    Return(Source{{12, 34}}))))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a return statement
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Direct) {
-  // loop  {
-  //   continuing {
-  //     discard;
-  //   }
-  // }
-
-  WrapInFunction(Loop(  // loop
-      Block(),          //   loop block
-      Block(            //   loop continuing block
-          Discard(Source{{12, 34}}))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a discard statement)");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect) {
-  // loop {
-  //   if (false) { break; }
-  //   continuing {
-  //     loop { discard; }
-  //   }
-  // }
-
-  WrapInFunction(Loop(                   // outer loop
-      Block(If(false, Block(Break()))),  //   outer loop block
-      Block(Source{{56, 78}},            //   outer loop continuing block
-            Loop(                        //     inner loop
-                Block(                   //       inner loop block
-                    Discard(Source{{12, 34}}))))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a discard statement
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect_ViaCall) {
-  // fn MayDiscard() { if (true) { discard; } }
-  // fn F() { MayDiscard(); }
-  // loop {
-  //   continuing {
-  //     loop { F(); }
-  //   }
-  // }
-
-  Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
-  Func("SomeFunc", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
-
-  WrapInFunction(Loop(         // outer loop
-      Block(),                 //   outer loop block
-      Block(Source{{56, 78}},  //   outer loop continuing block
-            Loop(              //     inner loop
-                Block(         //       inner loop block
-                    CallStmt(Call(Source{{12, 34}}, "SomeFunc")))))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: cannot call a function that may discard inside a continuing block
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Direct) {
-  // loop  {
-  //     continuing {
-  //         continue;
-  //     }
-  // }
-
-  WrapInFunction(Loop(         // loop
-      Block(),                 //   loop block
-      Block(Source{{56, 78}},  //   loop continuing block
-            Continue(Source{{12, 34}}))));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: continuing blocks must not contain a continue statement");
-}
-
-TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Indirect) {
-  // loop {
-  //   if (false) { break; }
-  //   continuing {
-  //     loop {
-  //       if (false) { break; }
-  //       continue;
-  //     }
-  //   }
-  // }
-
-  WrapInFunction(Loop(                        // outer loop
-      Block(                                  //   outer loop block
-          If(false, Block(Break()))),         //     if (false) { break; }
-      Block(                                  //   outer loop continuing block
-          Loop(                               //     inner loop
-              Block(                          //       inner loop block
-                  If(false, Block(Break())),  //          if (false) { break; }
-                  Continue(Source{{12, 34}}))))));  //    continue
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Direct) {
-  // for(;; return) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr, Return(Source{{12, 34}}),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a return statement)");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Indirect) {
-  // for(;; loop { return }) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr,
-                     Loop(Source{{56, 78}},                  //
-                          Block(Return(Source{{12, 34}}))),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a return statement
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Direct) {
-  // for(;; discard) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr, Discard(Source{{12, 34}}),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a discard statement)");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect) {
-  // for(;; loop { discard }) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr,
-                     Loop(Source{{56, 78}},                   //
-                          Block(Discard(Source{{12, 34}}))),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: continuing blocks must not contain a discard statement
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect_ViaCall) {
-  // fn MayDiscard() { if (true) { discard; } }
-  // fn F() { MayDiscard(); }
-  // for(;; loop { F() }) {
-  //   break;
-  // }
-
-  Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
-  Func("F", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
-
-  WrapInFunction(For(nullptr, nullptr,
-                     Loop(Source{{56, 78}},                               //
-                          Block(CallStmt(Call(Source{{12, 34}}, "F")))),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: cannot call a function that may discard inside a continuing block
-56:78 note: see continuing block here)");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Direct) {
-  // for(;; continue) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr, Continue(Source{{12, 34}}),  //
-                     Block(Break())));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: continuing blocks must not contain a continue statement");
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Indirect) {
-  // for(;; loop { if (false) { break; } continue }) {
-  //   break;
-  // }
-
-  WrapInFunction(For(nullptr, nullptr,
-                     Loop(                                    //
-                         Block(If(false, Block(Break())),     //
-                               Continue(Source{{12, 34}}))),  //
-                     Block(Break())));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_CondIsBoolRef) {
-  // var cond : bool = true;
-  // for (; cond; ) {
-  // }
-
-  auto* cond = Var("cond", ty.bool_(), Expr(true));
-  WrapInFunction(Decl(cond), For(nullptr, "cond", nullptr, Block()));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_ForLoop_CondIsNotBool) {
-  // for (; 1.0f; ) {
-  // }
-
-  WrapInFunction(For(nullptr, Expr(Source{{12, 34}}, 1.0f), nullptr, Block()));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: for-loop condition must be bool, got f32");
-}
-
-TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
-  WrapInFunction(Loop(Block(If(false, Block(Break())),  //
-                            Continue(Source{{12, 34}}))));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
-  WrapInFunction(Continue(Source{{12, 34}}));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: continue statement must be in a loop");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInLoop) {
-  WrapInFunction(Loop(Block(Break(Source{{12, 34}}))));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInSwitch) {
-  WrapInFunction(Loop(Block(Switch(Expr(1),               //
-                                   Case(Expr(1),          //
-                                        Block(Break())),  //
-                                   DefaultCase()),        //
-                            Break())));                   //
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
-  auto* cont = Block(                           // continuing {
-      If(true, Block(                           //   if(true) {
-                   Break(Source{{12, 34}}))));  //     break;
-                                                //   }
-                                                // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfElseInContinuing) {
-  auto* cont = Block(                      // continuing {
-      If(true, Block(),                    //   if(true) {
-         Else(Block(                       //   } else {
-             Break(Source{{12, 34}})))));  //     break;
-                                           //   }
-                                           // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInContinuing) {
-  auto* cont = Block(                   // continuing {
-      Block(Break(Source{{12, 34}})));  //   break;
-                                        // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "12:34 note: break statement is not directly in if statement block");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfInIfInContinuing) {
-  auto* cont = Block(                                      // continuing {
-      If(true, Block(                                      //   if(true) {
-                   If(Source{{56, 78}}, true,              //     if(true) {
-                      Block(Break(Source{{12, 34}}))))));  //       break;
-                                                           //     }
-                                                           //   }
-                                                           // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: if statement containing break statement is not directly in "
-      "continuing block");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueMultipleStmtsInContinuing) {
-  auto* cont = Block(                             // continuing {
-      If(true, Block(Source{{56, 78}},            //   if(true) {
-                     Assign(Phony(), 1),          //     _ = 1;
-                     Break(Source{{12, 34}}))));  //     break;
-                                                  //   }
-                                                  // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: if statement block contains multiple statements");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfElseMultipleStmtsInContinuing) {
-  auto* cont = Block(                             // continuing {
-      If(true, Block(),                           //   if(true) {
-         Else(Block(Source{{56, 78}},             //   } else {
-                    Assign(Phony(), 1),           //     _ = 1;
-                    Break(Source{{12, 34}})))));  //     break;
-                                                  //   }
-                                                  // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: if statement block contains multiple statements");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfElseIfInContinuing) {
-  auto* cont = Block(                             // continuing {
-      If(true, Block(),                           //   if(true) {
-         Else(Expr(Source{{56, 78}}, true),       //   } else if (true) {
-              Block(Break(Source{{12, 34}})))));  //     break;
-                                                  //   }
-                                                  // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: else has condition");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfNonEmptyElseInContinuing) {
-  auto* cont = Block(                        // continuing {
-      If(true,                               //   if(true) {
-         Block(Break(Source{{12, 34}})),     //     break;
-         Else(Block(Source{{56, 78}},        //   } else {
-                    Assign(Phony(), 1)))));  //     _ = 1;
-                                             //   }
-                                             // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: non-empty false block");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfElseNonEmptyTrueInContinuing) {
-  auto* cont = Block(                                  // continuing {
-      If(true,                                         //   if(true) {
-         Block(Source{{56, 78}}, Assign(Phony(), 1)),  //     _ = 1;
-         Else(Block(                                   //   } else {
-             Break(Source{{12, 34}})))));              //     break;
-                                                       //   }
-                                                       // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: non-empty true block");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakInIfInContinuingNotLast) {
-  auto* cont = Block(                      // continuing {
-      If(Source{{56, 78}}, true,           //   if(true) {
-         Block(Break(Source{{12, 34}}))),  //     break;
-                                           //   }
-      Assign(Phony(), 1));                 //   _ = 1;
-                                           // }
-  WrapInFunction(Loop(Block(), cont));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: break statement in a continuing block must be the single "
-      "statement of an if statement's true or false block, and that if "
-      "statement must be the last statement of the continuing block\n"
-      "56:78 note: if statement containing break statement is not the last "
-      "statement of the continuing block");
-}
-
-TEST_F(ResolverValidationTest, Stmt_BreakNotInLoopOrSwitch) {
-  WrapInFunction(Break(Source{{12, 34}}));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: break statement must be in a loop or switch case");
-}
-
-TEST_F(ResolverValidationTest, StructMemberDuplicateName) {
-  Structure("S", {Member(Source{{12, 34}}, "a", ty.i32()),
-                  Member(Source{{56, 78}}, "a", ty.i32())});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: redefinition of 'a'\n12:34 note: previous definition "
-            "is here");
-}
-TEST_F(ResolverValidationTest, StructMemberDuplicateNameDifferentTypes) {
-  Structure("S", {Member(Source{{12, 34}}, "a", ty.bool_()),
-                  Member(Source{{12, 34}}, "a", ty.vec3<f32>())});
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: redefinition of 'a'\n12:34 note: previous definition "
-            "is here");
-}
-TEST_F(ResolverValidationTest, StructMemberDuplicateNamePass) {
-  Structure("S", {Member("a", ty.i32()), Member("b", ty.f32())});
-  Structure("S1", {Member("a", ty.i32()), Member("b", ty.f32())});
-  EXPECT_TRUE(r()->Resolve());
-}
-
-TEST_F(ResolverValidationTest, NonPOTStructMemberAlignAttribute) {
-  Structure("S", {
-                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 3)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: align value must be a positive, power-of-two integer");
-}
-
-TEST_F(ResolverValidationTest, ZeroStructMemberAlignAttribute) {
-  Structure("S", {
-                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 0)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: align value must be a positive, power-of-two integer");
-}
-
-TEST_F(ResolverValidationTest, ZeroStructMemberSizeAttribute) {
-  Structure("S", {
-                     Member("a", ty.f32(), {MemberSize(Source{{12, 34}}, 0)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: size must be at least as big as the type's size (4)");
-}
-
-TEST_F(ResolverValidationTest, OffsetAndSizeAttribute) {
-  Structure("S", {
-                     Member(Source{{12, 34}}, "a", ty.f32(),
-                            {MemberOffset(0), MemberSize(4)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: offset attributes cannot be used with align or size "
-            "attributes");
-}
-
-TEST_F(ResolverValidationTest, OffsetAndAlignAttribute) {
-  Structure("S", {
-                     Member(Source{{12, 34}}, "a", ty.f32(),
-                            {MemberOffset(0), MemberAlign(4)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: offset attributes cannot be used with align or size "
-            "attributes");
-}
-
-TEST_F(ResolverValidationTest, OffsetAndAlignAndSizeAttribute) {
-  Structure("S", {
-                     Member(Source{{12, 34}}, "a", ty.f32(),
-                            {MemberOffset(0), MemberAlign(4), MemberSize(4)}),
-                 });
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: offset attributes cannot be used with align or size "
-            "attributes");
-}
-
-TEST_F(ResolverTest, Expr_Constructor_Cast_Pointer) {
-  auto* vf = Var("vf", ty.f32());
-  auto* c =
-      Construct(Source{{12, 34}}, ty.pointer<i32>(ast::StorageClass::kFunction),
-                ExprList(vf));
-  auto* ip = Const("ip", ty.pointer<i32>(ast::StorageClass::kFunction), c);
-  WrapInFunction(Decl(vf), Decl(ip));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
-
-TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeStmt);
-TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeExpr);
diff --git a/src/resolver/var_let_test.cc b/src/resolver/var_let_test.cc
deleted file mode 100644
index ac4f18c..0000000
--- a/src/resolver/var_let_test.cc
+++ /dev/null
@@ -1,701 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-#include "src/sem/reference_type.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverVarLetTest : public resolver::TestHelper,
-                            public testing::Test {};
-
-TEST_F(ResolverVarLetTest, VarDeclWithoutConstructor) {
-  // struct S { i : i32; }
-  // alias A = S;
-  // fn F(){
-  //   var i : i32;
-  //   var u : u32;
-  //   var f : f32;
-  //   var b : bool;
-  //   var s : S;
-  //   var a : A;
-  // }
-
-  auto* S = Structure("S", {Member("i", ty.i32())});
-  auto* A = Alias("A", ty.Of(S));
-
-  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
-  auto* u = Var("u", ty.u32(), ast::StorageClass::kNone);
-  auto* f = Var("f", ty.f32(), ast::StorageClass::kNone);
-  auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone);
-  auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
-  auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
-
-  Func("F", {}, ty.void_(),
-       {
-           Decl(i),
-           Decl(u),
-           Decl(f),
-           Decl(b),
-           Decl(s),
-           Decl(a),
-       });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  // `var` declarations are always of reference type
-  ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
-
-  EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
-  EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
-
-  EXPECT_EQ(Sem().Get(i)->Constructor(), nullptr);
-  EXPECT_EQ(Sem().Get(u)->Constructor(), nullptr);
-  EXPECT_EQ(Sem().Get(f)->Constructor(), nullptr);
-  EXPECT_EQ(Sem().Get(b)->Constructor(), nullptr);
-  EXPECT_EQ(Sem().Get(s)->Constructor(), nullptr);
-  EXPECT_EQ(Sem().Get(a)->Constructor(), nullptr);
-}
-
-TEST_F(ResolverVarLetTest, VarDeclWithConstructor) {
-  // struct S { i : i32; }
-  // alias A = S;
-  // fn F(){
-  //   var i : i32 = 1;
-  //   var u : u32 = 1u;
-  //   var f : f32 = 1.f;
-  //   var b : bool = true;
-  //   var s : S = S(1);
-  //   var a : A = A(1);
-  // }
-
-  auto* S = Structure("S", {Member("i", ty.i32())});
-  auto* A = Alias("A", ty.Of(S));
-
-  auto* i_c = Expr(1);
-  auto* u_c = Expr(1u);
-  auto* f_c = Expr(1.f);
-  auto* b_c = Expr(true);
-  auto* s_c = Construct(ty.Of(S), Expr(1));
-  auto* a_c = Construct(ty.Of(A), Expr(1));
-
-  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone, i_c);
-  auto* u = Var("u", ty.u32(), ast::StorageClass::kNone, u_c);
-  auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, f_c);
-  auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone, b_c);
-  auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone, s_c);
-  auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone, a_c);
-
-  Func("F", {}, ty.void_(),
-       {
-           Decl(i),
-           Decl(u),
-           Decl(f),
-           Decl(b),
-           Decl(s),
-           Decl(a),
-       });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  // `var` declarations are always of reference type
-  ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
-
-  EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
-  EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
-  EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
-  EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
-  EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
-  EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
-
-  EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
-  EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
-  EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
-  EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
-  EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
-  EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
-}
-
-TEST_F(ResolverVarLetTest, LetDecl) {
-  // struct S { i : i32; }
-  // fn F(){
-  //   var v : i32;
-  //   let i : i32 = 1;
-  //   let u : u32 = 1u;
-  //   let f : f32 = 1.;
-  //   let b : bool = true;
-  //   let s : S = S(1);
-  //   let a : A = A(1);
-  //   let p : pointer<function, i32> = &v;
-  // }
-
-  auto* S = Structure("S", {Member("i", ty.i32())});
-  auto* A = Alias("A", ty.Of(S));
-  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
-
-  auto* i_c = Expr(1);
-  auto* u_c = Expr(1u);
-  auto* f_c = Expr(1.f);
-  auto* b_c = Expr(true);
-  auto* s_c = Construct(ty.Of(S), Expr(1));
-  auto* a_c = Construct(ty.Of(A), Expr(1));
-  auto* p_c = AddressOf(v);
-
-  auto* i = Const("i", ty.i32(), i_c);
-  auto* u = Const("u", ty.u32(), u_c);
-  auto* f = Const("f", ty.f32(), f_c);
-  auto* b = Const("b", ty.bool_(), b_c);
-  auto* s = Const("s", ty.Of(S), s_c);
-  auto* a = Const("a", ty.Of(A), a_c);
-  auto* p = Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), p_c);
-
-  Func("F", {}, ty.void_(),
-       {
-           Decl(v),
-           Decl(i),
-           Decl(u),
-           Decl(f),
-           Decl(b),
-           Decl(s),
-           Decl(a),
-           Decl(p),
-       });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  // `let` declarations are always of the storage type
-  ASSERT_TRUE(TypeOf(i)->Is<sem::I32>());
-  ASSERT_TRUE(TypeOf(u)->Is<sem::U32>());
-  ASSERT_TRUE(TypeOf(f)->Is<sem::F32>());
-  ASSERT_TRUE(TypeOf(b)->Is<sem::Bool>());
-  ASSERT_TRUE(TypeOf(s)->Is<sem::Struct>());
-  ASSERT_TRUE(TypeOf(a)->Is<sem::Struct>());
-  ASSERT_TRUE(TypeOf(p)->Is<sem::Pointer>());
-  ASSERT_TRUE(TypeOf(p)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
-
-  EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
-  EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
-  EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
-  EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
-  EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
-  EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
-  EXPECT_EQ(Sem().Get(p)->Constructor()->Declaration(), p_c);
-}
-
-TEST_F(ResolverVarLetTest, DefaultVarStorageClass) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
-
-  auto* buf = Structure("S", {Member("m", ty.i32())},
-                        {create<ast::StructBlockAttribute>()});
-  auto* function = Var("f", ty.i32());
-  auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
-  auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(0),
-                             create<ast::GroupAttribute>(0),
-                         });
-  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(1),
-                             create<ast::GroupAttribute>(0),
-                         });
-  auto* handle = Global("h", ty.depth_texture(ast::TextureDimension::k2d),
-                        ast::AttributeList{
-                            create<ast::BindingAttribute>(2),
-                            create<ast::GroupAttribute>(0),
-                        });
-
-  WrapInFunction(function);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(function)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(private_)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(workgroup)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(uniform)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(handle)->Is<sem::Reference>());
-
-  EXPECT_EQ(TypeOf(function)->As<sem::Reference>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(private_)->As<sem::Reference>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(workgroup)->As<sem::Reference>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(uniform)->As<sem::Reference>()->Access(),
-            ast::Access::kRead);
-  EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
-            ast::Access::kRead);
-  EXPECT_EQ(TypeOf(handle)->As<sem::Reference>()->Access(), ast::Access::kRead);
-}
-
-TEST_F(ResolverVarLetTest, ExplicitVarStorageClass) {
-  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
-
-  auto* buf = Structure("S", {Member("m", ty.i32())},
-                        {create<ast::StructBlockAttribute>()});
-  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::Access::kReadWrite,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(1),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
-
-  EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
-            ast::Access::kReadWrite);
-}
-
-TEST_F(ResolverVarLetTest, LetInheritsAccessFromOriginatingVariable) {
-  // struct Inner {
-  //    arr: array<i32, 4>;
-  // }
-  // [[block]] struct S {
-  //    inner: Inner;
-  // }
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  // fn f() {
-  //   let p = &s.inner.arr[2];
-  // }
-  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
-                        {create<ast::StructBlockAttribute>()});
-  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::Access::kReadWrite,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(0),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  auto* expr =
-      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
-  auto* ptr = Const("p", nullptr, AddressOf(expr));
-
-  WrapInFunction(ptr);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
-  ASSERT_TRUE(TypeOf(ptr)->Is<sem::Pointer>());
-
-  EXPECT_EQ(TypeOf(expr)->As<sem::Reference>()->Access(),
-            ast::Access::kReadWrite);
-  EXPECT_EQ(TypeOf(ptr)->As<sem::Pointer>()->Access(), ast::Access::kReadWrite);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsAlias) {
-  // type a = i32;
-  //
-  // fn X() {
-  //   var a = false;
-  // }
-  //
-  // fn Y() {
-  //   let a = true;
-  // }
-
-  auto* t = Alias("a", ty.i32());
-  auto* v = Var("a", nullptr, Expr(false));
-  auto* l = Const("a", nullptr, Expr(false));
-  Func("X", {}, ty.void_(), {Decl(v)});
-  Func("Y", {}, ty.void_(), {Decl(l)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* type_t = Sem().Get(t);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), type_t);
-  EXPECT_EQ(local_l->Shadows(), type_t);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsStruct) {
-  // struct a {
-  //   m : i32;
-  // };
-  //
-  // fn X() {
-  //   var a = true;
-  // }
-  //
-  // fn Y() {
-  //   let a = false;
-  // }
-
-  auto* t = Structure("a", {Member("m", ty.i32())});
-  auto* v = Var("a", nullptr, Expr(false));
-  auto* l = Const("a", nullptr, Expr(false));
-  Func("X", {}, ty.void_(), {Decl(v)});
-  Func("Y", {}, ty.void_(), {Decl(l)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* type_t = Sem().Get(t);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), type_t);
-  EXPECT_EQ(local_l->Shadows(), type_t);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsFunction) {
-  // fn a() {
-  //   var a = true;
-  // }
-  //
-  // fn b() {
-  //   let b = false;
-  // }
-
-  auto* v = Var("a", nullptr, Expr(false));
-  auto* l = Const("b", nullptr, Expr(false));
-  auto* fa = Func("a", {}, ty.void_(), {Decl(v)});
-  auto* fb = Func("b", {}, ty.void_(), {Decl(l)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-  auto* func_a = Sem().Get(fa);
-  auto* func_b = Sem().Get(fb);
-
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-  ASSERT_NE(func_a, nullptr);
-  ASSERT_NE(func_b, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), func_a);
-  EXPECT_EQ(local_l->Shadows(), func_b);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsGlobalVar) {
-  // var<private> a : i32;
-  //
-  // fn X() {
-  //   var a = a;
-  // }
-  //
-  // fn Y() {
-  //   let a = a;
-  // }
-
-  auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-  auto* v = Var("a", nullptr, Expr("a"));
-  auto* l = Const("a", nullptr, Expr("a"));
-  Func("X", {}, ty.void_(), {Decl(v)});
-  Func("Y", {}, ty.void_(), {Decl(l)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* global = Sem().Get(g);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), global);
-  EXPECT_EQ(local_l->Shadows(), global);
-
-  auto* user_v =
-      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
-  auto* user_l =
-      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
-
-  ASSERT_NE(user_v, nullptr);
-  ASSERT_NE(user_l, nullptr);
-
-  EXPECT_EQ(user_v->Variable(), global);
-  EXPECT_EQ(user_l->Variable(), global);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsGlobalLet) {
-  // let a : i32 = 1;
-  //
-  // fn X() {
-  //   var a = (a == 123);
-  // }
-  //
-  // fn Y() {
-  //   let a = (a == 321);
-  // }
-
-  auto* g = GlobalConst("a", ty.i32(), Expr(1));
-  auto* v = Var("a", nullptr, Expr("a"));
-  auto* l = Const("a", nullptr, Expr("a"));
-  Func("X", {}, ty.void_(), {Decl(v)});
-  Func("Y", {}, ty.void_(), {Decl(l)});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* global = Sem().Get(g);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), global);
-  EXPECT_EQ(local_l->Shadows(), global);
-
-  auto* user_v =
-      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
-  auto* user_l =
-      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
-
-  ASSERT_NE(user_v, nullptr);
-  ASSERT_NE(user_l, nullptr);
-
-  EXPECT_EQ(user_v->Variable(), global);
-  EXPECT_EQ(user_l->Variable(), global);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsLocalVar) {
-  // fn X() {
-  //   var a : i32;
-  //   {
-  //     var a = a;
-  //   }
-  //   {
-  //     let a = a;
-  //   }
-  // }
-
-  auto* s = Var("a", ty.i32(), Expr(1));
-  auto* v = Var("a", nullptr, Expr("a"));
-  auto* l = Const("a", nullptr, Expr("a"));
-  Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* local_s = Sem().Get<sem::LocalVariable>(s);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_s, nullptr);
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), local_s);
-  EXPECT_EQ(local_l->Shadows(), local_s);
-
-  auto* user_v =
-      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
-  auto* user_l =
-      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
-
-  ASSERT_NE(user_v, nullptr);
-  ASSERT_NE(user_l, nullptr);
-
-  EXPECT_EQ(user_v->Variable(), local_s);
-  EXPECT_EQ(user_l->Variable(), local_s);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsLocalLet) {
-  // fn X() {
-  //   let a = 1;
-  //   {
-  //     var a = (a == 123);
-  //   }
-  //   {
-  //     let a = (a == 321);
-  //   }
-  // }
-
-  auto* s = Const("a", ty.i32(), Expr(1));
-  auto* v = Var("a", nullptr, Expr("a"));
-  auto* l = Const("a", nullptr, Expr("a"));
-  Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* local_s = Sem().Get<sem::LocalVariable>(s);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(local_s, nullptr);
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), local_s);
-  EXPECT_EQ(local_l->Shadows(), local_s);
-
-  auto* user_v =
-      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
-  auto* user_l =
-      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
-
-  ASSERT_NE(user_v, nullptr);
-  ASSERT_NE(user_l, nullptr);
-
-  EXPECT_EQ(user_v->Variable(), local_s);
-  EXPECT_EQ(user_l->Variable(), local_s);
-}
-
-TEST_F(ResolverVarLetTest, LocalShadowsParam) {
-  // fn F(a : i32) {
-  //   {
-  //     var a = a;
-  //   }
-  //   {
-  //     let a = a;
-  //   }
-  // }
-
-  auto* p = Param("a", ty.i32());
-  auto* v = Var("a", nullptr, Expr("a"));
-  auto* l = Const("a", nullptr, Expr("a"));
-  Func("X", {p}, ty.void_(), {Block(Decl(v)), Block(Decl(l))});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* param = Sem().Get<sem::Parameter>(p);
-  auto* local_v = Sem().Get<sem::LocalVariable>(v);
-  auto* local_l = Sem().Get<sem::LocalVariable>(l);
-
-  ASSERT_NE(param, nullptr);
-  ASSERT_NE(local_v, nullptr);
-  ASSERT_NE(local_l, nullptr);
-
-  EXPECT_EQ(local_v->Shadows(), param);
-  EXPECT_EQ(local_l->Shadows(), param);
-
-  auto* user_v =
-      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
-  auto* user_l =
-      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
-
-  ASSERT_NE(user_v, nullptr);
-  ASSERT_NE(user_l, nullptr);
-
-  EXPECT_EQ(user_v->Variable(), param);
-  EXPECT_EQ(user_l->Variable(), param);
-}
-
-TEST_F(ResolverVarLetTest, ParamShadowsFunction) {
-  // fn a(a : bool) {
-  // }
-
-  auto* p = Param("a", ty.bool_());
-  auto* f = Func("a", {p}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* func = Sem().Get(f);
-  auto* param = Sem().Get<sem::Parameter>(p);
-
-  ASSERT_NE(func, nullptr);
-  ASSERT_NE(param, nullptr);
-
-  EXPECT_EQ(param->Shadows(), func);
-}
-
-TEST_F(ResolverVarLetTest, ParamShadowsGlobalVar) {
-  // var<private> a : i32;
-  //
-  // fn F(a : bool) {
-  // }
-
-  auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-  auto* p = Param("a", ty.bool_());
-  Func("F", {p}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* global = Sem().Get(g);
-  auto* param = Sem().Get<sem::Parameter>(p);
-
-  ASSERT_NE(global, nullptr);
-  ASSERT_NE(param, nullptr);
-
-  EXPECT_EQ(param->Shadows(), global);
-}
-
-TEST_F(ResolverVarLetTest, ParamShadowsGlobalLet) {
-  // let a : i32 = 1;
-  //
-  // fn F(a : bool) {
-  // }
-
-  auto* g = GlobalConst("a", ty.i32(), Expr(1));
-  auto* p = Param("a", ty.bool_());
-  Func("F", {p}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* global = Sem().Get(g);
-  auto* param = Sem().Get<sem::Parameter>(p);
-
-  ASSERT_NE(global, nullptr);
-  ASSERT_NE(param, nullptr);
-
-  EXPECT_EQ(param->Shadows(), global);
-}
-
-TEST_F(ResolverVarLetTest, ParamShadowsAlias) {
-  // type a = i32;
-  //
-  // fn F(a : a) {
-  // }
-
-  auto* a = Alias("a", ty.i32());
-  auto* p = Param("a", ty.type_name("a"));
-  Func("F", {p}, ty.void_(), {});
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  auto* alias = Sem().Get(a);
-  auto* param = Sem().Get<sem::Parameter>(p);
-
-  ASSERT_NE(alias, nullptr);
-  ASSERT_NE(param, nullptr);
-
-  EXPECT_EQ(param->Shadows(), alias);
-  EXPECT_EQ(param->Type(), alias);
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/resolver/var_let_validation_test.cc b/src/resolver/var_let_validation_test.cc
deleted file mode 100644
index 21fa54c..0000000
--- a/src/resolver/var_let_validation_test.cc
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/resolver/resolver.h"
-#include "src/resolver/resolver_test_helper.h"
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace resolver {
-namespace {
-
-struct ResolverVarLetValidationTest : public resolver::TestHelper,
-                                      public testing::Test {};
-
-TEST_F(ResolverVarLetValidationTest, LetNoInitializer) {
-  // let a : i32;
-  WrapInFunction(Const(Source{{12, 34}}, "a", ty.i32(), nullptr));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: let declaration must have an initializer");
-}
-
-TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) {
-  // let a : i32;
-  GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: let declaration must have an initializer");
-}
-
-TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) {
-  // var a;
-  WrapInFunction(Var(Source{{12, 34}}, "a", nullptr));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function scope var declaration requires a type or "
-            "initializer");
-}
-
-TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) {
-  // var a;
-  Global(Source{{12, 34}}, "a", nullptr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: module scope var declaration requires a type and "
-            "initializer");
-}
-
-TEST_F(ResolverVarLetValidationTest, VarTypeNotStorable) {
-  // var i : i32;
-  // var p : pointer<function, i32> = &v;
-  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
-  auto* p =
-      Var(Source{{56, 78}}, "a", ty.pointer<i32>(ast::StorageClass::kFunction),
-          ast::StorageClass::kNone, AddressOf(Source{{12, 34}}, "i"));
-  WrapInFunction(i, p);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: ptr<function, i32, read_write> cannot be used as the "
-            "type of a var");
-}
-
-TEST_F(ResolverVarLetValidationTest, LetTypeNotConstructible) {
-  // @group(0) @binding(0) var t1 : texture_2d<f32>;
-  // let t2 : t1;
-  auto* t1 =
-      Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-             GroupAndBinding(0, 0));
-  auto* t2 = Const(Source{{56, 78}}, "t2", nullptr, Expr(t1));
-  WrapInFunction(t2);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: texture_2d<f32> cannot be used as the type of a let");
-}
-
-TEST_F(ResolverVarLetValidationTest, LetConstructorWrongType) {
-  // var v : i32 = 2u
-  WrapInFunction(Const(Source{{3, 3}}, "v", ty.i32(), Expr(2u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
-}
-
-TEST_F(ResolverVarLetValidationTest, VarConstructorWrongType) {
-  // var v : i32 = 2u
-  WrapInFunction(
-      Var(Source{{3, 3}}, "v", ty.i32(), ast::StorageClass::kNone, Expr(2u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
-}
-
-TEST_F(ResolverVarLetValidationTest, LetConstructorWrongTypeViaAlias) {
-  auto* a = Alias("I32", ty.i32());
-  WrapInFunction(Const(Source{{3, 3}}, "v", ty.Of(a), Expr(2u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
-}
-
-TEST_F(ResolverVarLetValidationTest, VarConstructorWrongTypeViaAlias) {
-  auto* a = Alias("I32", ty.i32());
-  WrapInFunction(
-      Var(Source{{3, 3}}, "v", ty.Of(a), ast::StorageClass::kNone, Expr(2u)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
-}
-
-TEST_F(ResolverVarLetValidationTest, LetOfPtrConstructedWithRef) {
-  // var a : f32;
-  // let b : ptr<function,f32> = a;
-  const auto priv = ast::StorageClass::kFunction;
-  auto* var_a = Var("a", ty.f32(), priv);
-  auto* var_b =
-      Const(Source{{12, 34}}, "b", ty.pointer<float>(priv), Expr("a"), {});
-  WrapInFunction(var_a, var_b);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: cannot initialize let of type 'ptr<function, f32, read_write>' with value of type 'f32')");
-}
-
-TEST_F(ResolverVarLetValidationTest, LocalLetRedeclared) {
-  // let l : f32 = 1.;
-  // let l : i32 = 0;
-  auto* l1 = Const("l", ty.f32(), Expr(1.f));
-  auto* l2 = Const(Source{{12, 34}}, "l", ty.i32(), Expr(0));
-  WrapInFunction(l1, l2);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: redeclaration of 'l'\nnote: 'l' previously declared here");
-}
-
-TEST_F(ResolverVarLetValidationTest, GlobalVarRedeclaredAsLocal) {
-  // var v : f32 = 2.1;
-  // fn my_func() {
-  //   var v : f32 = 2.0;
-  //   return 0;
-  // }
-
-  Global("v", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
-
-  WrapInFunction(Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
-                     Expr(2.0f)));
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverVarLetValidationTest, VarRedeclaredInInnerBlock) {
-  // {
-  //  var v : f32;
-  //  { var v : f32; }
-  // }
-  auto* var_outer = Var("v", ty.f32(), ast::StorageClass::kNone);
-  auto* var_inner =
-      Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone);
-  auto* inner = Block(Decl(var_inner));
-  auto* outer_body = Block(Decl(var_outer), inner);
-
-  WrapInFunction(outer_body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverVarLetValidationTest, VarRedeclaredInIfBlock) {
-  // {
-  //   var v : f32 = 3.14;
-  //   if (true) { var v : f32 = 2.0; }
-  // }
-  auto* var_a_float = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
-
-  auto* var = Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
-                  Expr(2.0f));
-
-  auto* cond = Expr(true);
-  auto* body = Block(Decl(var));
-
-  auto* outer_body =
-      Block(Decl(var_a_float),
-            create<ast::IfStatement>(cond, body, ast::ElseStatementList{}));
-
-  WrapInFunction(outer_body);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverVarLetValidationTest, InferredPtrStorageAccessMismatch) {
-  // struct Inner {
-  //    arr: array<i32, 4>;
-  // }
-  // [[block]] struct S {
-  //    inner: Inner;
-  // }
-  // @group(0) @binding(0) var<storage> s : S;
-  // fn f() {
-  //   let p : pointer<storage, i32, read_write> = &s.inner.arr[2];
-  // }
-  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
-                        {create<ast::StructBlockAttribute>()});
-  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(0),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  auto* expr =
-      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
-  auto* ptr = Const(
-      Source{{12, 34}}, "p",
-      ty.pointer<i32>(ast::StorageClass::kStorage, ast::Access::kReadWrite),
-      AddressOf(expr));
-
-  WrapInFunction(ptr);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: cannot initialize let of type "
-            "'ptr<storage, i32, read_write>' with value of type "
-            "'ptr<storage, i32, read>'");
-}
-
-TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Atomic) {
-  auto* v = Var("v", ty.atomic(Source{{12, 34}}, ty.i32()));
-  WrapInFunction(v);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function variable must have a constructible type");
-}
-
-TEST_F(ResolverVarLetValidationTest, NonConstructibleType_RuntimeArray) {
-  auto* s = Structure("S", {Member(Source{{56, 78}}, "m", ty.array(ty.i32()))},
-                      {StructBlock()});
-  auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
-  WrapInFunction(v);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
-56:78 note: while analysing structure member S.m
-12:34 note: while instantiating variable v)");
-}
-
-TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Struct_WithAtomic) {
-  auto* s = Structure("S", {Member("m", ty.atomic(ty.i32()))});
-  auto* v = Var("v", ty.Of(s));
-  WrapInFunction(v);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: function variable must have a constructible type");
-}
-
-TEST_F(ResolverVarLetValidationTest, NonConstructibleType_InferredType) {
-  // @group(0) @binding(0) var s : sampler;
-  // fn foo() {
-  //   var v = s;
-  // }
-  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 0));
-  auto* v = Var(Source{{12, 34}}, "v", nullptr, Expr("s"));
-  WrapInFunction(v);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: function variable must have a constructible type");
-}
-
-TEST_F(ResolverVarLetValidationTest, InvalidStorageClassForInitializer) {
-  // var<workgroup> v : f32 = 1.23;
-  Global(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kWorkgroup,
-         Expr(1.23f));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: var of storage class 'workgroup' cannot have "
-            "an initializer. var initializers are only supported for the "
-            "storage classes 'private' and 'function'");
-}
-
-TEST_F(ResolverVarLetValidationTest, VectorLetNoType) {
-  // let a : mat3x3 = mat3x3<f32>();
-  WrapInFunction(Const("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3),
-                       vec3<f32>()));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverVarLetValidationTest, VectorVarNoType) {
-  // var a : mat3x3;
-  WrapInFunction(Var("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverVarLetValidationTest, MatrixLetNoType) {
-  // let a : mat3x3 = mat3x3<f32>();
-  WrapInFunction(Const("a",
-                       create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3),
-                       mat3x3<f32>()));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
-}
-
-TEST_F(ResolverVarLetValidationTest, MatrixVarNoType) {
-  // var a : mat3x3;
-  WrapInFunction(
-      Var("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3)));
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
-}
-
-}  // namespace
-}  // namespace resolver
-}  // namespace tint
diff --git a/src/scope_stack.h b/src/scope_stack.h
deleted file mode 100644
index ec152e8..0000000
--- a/src/scope_stack.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 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_SCOPE_STACK_H_
-#define SRC_SCOPE_STACK_H_
-
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/symbol.h"
-
-namespace tint {
-
-/// Used to store a stack of scope information.
-/// The stack starts with a global scope which can not be popped.
-template <class T>
-class ScopeStack {
- public:
-  /// Constructor
-  ScopeStack() {
-    // Push global bucket
-    stack_.push_back({});
-  }
-  /// Copy Constructor
-  ScopeStack(const ScopeStack&) = default;
-  ~ScopeStack() = default;
-
-  /// Push a new scope on to the stack
-  void Push() { stack_.push_back({}); }
-
-  /// Pop the scope off the top of the stack
-  void Pop() {
-    if (stack_.size() > 1) {
-      stack_.pop_back();
-    }
-  }
-
-  /// Assigns the value into the top most scope of the stack.
-  /// @param symbol the symbol of the value
-  /// @param val the value
-  /// @returns the old value if there was an existing symbol at the top of the
-  /// stack, otherwise the zero initializer for type T.
-  T Set(const Symbol& symbol, T val) {
-    std::swap(val, stack_.back()[symbol]);
-    return val;
-  }
-
-  /// Retrieves a value from the stack
-  /// @param symbol the symbol to look for
-  /// @returns the value, or the zero initializer if the value was not found
-  T Get(const Symbol& symbol) const {
-    for (auto iter = stack_.rbegin(); iter != stack_.rend(); ++iter) {
-      auto& map = *iter;
-      auto val = map.find(symbol);
-      if (val != map.end()) {
-        return val->second;
-      }
-    }
-
-    return T{};
-  }
-
- private:
-  std::vector<std::unordered_map<Symbol, T>> stack_;
-};
-
-}  // namespace tint
-
-#endif  // SRC_SCOPE_STACK_H_
diff --git a/src/scope_stack_test.cc b/src/scope_stack_test.cc
deleted file mode 100644
index d33b16e..0000000
--- a/src/scope_stack_test.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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/scope_stack.h"
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace {
-
-class ScopeStackTest : public ProgramBuilder, public testing::Test {};
-
-TEST_F(ScopeStackTest, Get) {
-  ScopeStack<uint32_t> s;
-  Symbol a(1, ID());
-  Symbol b(3, ID());
-  s.Push();
-  s.Set(a, 5u);
-  s.Set(b, 10u);
-
-  EXPECT_EQ(s.Get(a), 5u);
-  EXPECT_EQ(s.Get(b), 10u);
-
-  s.Push();
-
-  s.Set(a, 15u);
-  EXPECT_EQ(s.Get(a), 15u);
-  EXPECT_EQ(s.Get(b), 10u);
-
-  s.Pop();
-  EXPECT_EQ(s.Get(a), 5u);
-  EXPECT_EQ(s.Get(b), 10u);
-}
-
-TEST_F(ScopeStackTest, Get_MissingSymbol) {
-  ScopeStack<uint32_t> s;
-  Symbol sym(1, ID());
-  EXPECT_EQ(s.Get(sym), 0u);
-}
-
-TEST_F(ScopeStackTest, Set) {
-  ScopeStack<uint32_t> s;
-  Symbol a(1, ID());
-  Symbol b(2, ID());
-
-  EXPECT_EQ(s.Set(a, 5u), 0u);
-  EXPECT_EQ(s.Get(a), 5u);
-
-  EXPECT_EQ(s.Set(b, 10u), 0u);
-  EXPECT_EQ(s.Get(b), 10u);
-
-  EXPECT_EQ(s.Set(a, 20u), 5u);
-  EXPECT_EQ(s.Get(a), 20u);
-
-  EXPECT_EQ(s.Set(b, 25u), 10u);
-  EXPECT_EQ(s.Get(b), 25u);
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/sem/array.cc b/src/sem/array.cc
deleted file mode 100644
index 9bb3604..0000000
--- a/src/sem/array.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/array.h"
-
-#include <string>
-
-#include "src/debug.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Array);
-
-namespace tint {
-namespace sem {
-
-Array::Array(const Type* element,
-             uint32_t count,
-             uint32_t align,
-             uint32_t size,
-             uint32_t stride,
-             uint32_t implicit_stride)
-    : element_(element),
-      count_(count),
-      align_(align),
-      size_(size),
-      stride_(stride),
-      implicit_stride_(implicit_stride),
-      constructible_(count > 0  // Runtime-sized arrays are not constructible
-                     && element->IsConstructible()) {
-  TINT_ASSERT(Semantic, element_);
-}
-
-bool Array::IsConstructible() const {
-  return constructible_;
-}
-
-std::string Array::type_name() const {
-  std::string type_name = "__array" + element_->type_name();
-  type_name += "_count_" + std::to_string(count_);
-  type_name += "_align_" + std::to_string(align_);
-  type_name += "_size_" + std::to_string(size_);
-  type_name += "_stride_" + std::to_string(stride_);
-  // Note: implicit_stride is not part of the type_name string as this is
-  // derived from the element type
-  return type_name;
-}
-
-std::string Array::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  if (!IsStrideImplicit()) {
-    out << "@stride(" << stride_ << ") ";
-  }
-  out << "array<" << element_->FriendlyName(symbols);
-  if (!IsRuntimeSized()) {
-    out << ", " << count_;
-  }
-  out << ">";
-  return out.str();
-}
-
-uint32_t Array::Align() const {
-  return align_;
-}
-
-uint32_t Array::Size() const {
-  return size_;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/array.h b/src/sem/array.h
deleted file mode 100644
index c22bd98..0000000
--- a/src/sem/array.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_ARRAY_H_
-#define SRC_SEM_ARRAY_H_
-
-#include <stdint.h>
-#include <string>
-
-#include "src/sem/node.h"
-#include "src/sem/type.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class Array;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Array holds the semantic information for Array nodes.
-class Array : public Castable<Array, Type> {
- public:
-  /// Constructor
-  /// @param element the array element type
-  /// @param count the number of elements in the array. 0 represents a
-  /// runtime-sized array.
-  /// @param align the byte alignment of the array
-  /// @param size the byte size of the array
-  /// @param stride the number of bytes from the start of one element of the
-  /// array to the start of the next element
-  /// @param implicit_stride the number of bytes from the start of one element
-  /// of the array to the start of the next element, if there was no `@stride`
-  /// attribute applied.
-  Array(Type const* element,
-        uint32_t count,
-        uint32_t align,
-        uint32_t size,
-        uint32_t stride,
-        uint32_t implicit_stride);
-
-  /// @return the array element type
-  Type const* ElemType() const { return element_; }
-
-  /// @returns the number of elements in the array. 0 represents a runtime-sized
-  /// array.
-  uint32_t Count() const { return count_; }
-
-  /// @returns the byte alignment of the array
-  /// @note this may differ from the alignment of a structure member of this
-  /// array type, if the member is annotated with the `@align(n)` attribute.
-  uint32_t Align() const override;
-
-  /// @returns the byte size of the array
-  /// @note this may differ from the size of a structure member of this array
-  /// type, if the member is annotated with the `@size(n)` attribute.
-  uint32_t Size() const override;
-
-  /// @returns the number of bytes from the start of one element of the
-  /// array to the start of the next element
-  uint32_t Stride() const { return stride_; }
-
-  /// @returns the number of bytes from the start of one element of the
-  /// array to the start of the next element, if there was no `@stride`
-  /// attribute applied
-  uint32_t ImplicitStride() const { return implicit_stride_; }
-
-  /// @returns true if the value returned by Stride() matches the element's
-  /// natural stride
-  bool IsStrideImplicit() const { return stride_ == implicit_stride_; }
-
-  /// @returns true if this array is runtime sized
-  bool IsRuntimeSized() const { return count_ == 0; }
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the name for the type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  Type const* const element_;
-  const uint32_t count_;
-  const uint32_t align_;
-  const uint32_t size_;
-  const uint32_t stride_;
-  const uint32_t implicit_stride_;
-  const bool constructible_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_ARRAY_H_
diff --git a/src/sem/atomic_type.cc b/src/sem/atomic_type.cc
deleted file mode 100644
index 59a7ddf..0000000
--- a/src/sem/atomic_type.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/atomic_type.h"
-
-#include "src/program_builder.h"
-#include "src/sem/reference_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Atomic);
-
-namespace tint {
-namespace sem {
-
-Atomic::Atomic(const sem::Type* subtype) : subtype_(subtype) {
-  TINT_ASSERT(AST, !subtype->Is<Reference>());
-}
-
-std::string Atomic::type_name() const {
-  std::ostringstream out;
-  out << "__atomic" << subtype_->type_name();
-  return out.str();
-}
-
-std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "atomic<" << subtype_->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-uint32_t Atomic::Size() const {
-  return subtype_->Size();
-}
-
-uint32_t Atomic::Align() const {
-  return subtype_->Align();
-}
-
-bool Atomic::IsConstructible() const {
-  return false;
-}
-
-Atomic::Atomic(Atomic&&) = default;
-
-Atomic::~Atomic() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/atomic_type.h b/src/sem/atomic_type.h
deleted file mode 100644
index 0fa74c6..0000000
--- a/src/sem/atomic_type.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_ATOMIC_TYPE_H_
-#define SRC_SEM_ATOMIC_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A atomic type.
-class Atomic : public Castable<Atomic, Type> {
- public:
-  /// Constructor
-  /// @param subtype the atomic type
-  explicit Atomic(const sem::Type* subtype);
-
-  /// Move constructor
-  Atomic(Atomic&&);
-  ~Atomic() override;
-
-  /// @returns the atomic type
-  const sem::Type* Type() const { return subtype_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns the size in bytes of the type.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type.
-  uint32_t Align() const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-typesd
-  bool IsConstructible() const override;
-
- private:
-  sem::Type const* const subtype_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_ATOMIC_TYPE_H_
diff --git a/src/sem/atomic_type_test.cc b/src/sem/atomic_type_test.cc
deleted file mode 100644
index 121f82d..0000000
--- a/src/sem/atomic_type_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/atomic_type.h"
-
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using AtomicTest = TestHelper;
-
-TEST_F(AtomicTest, Creation) {
-  auto* a = create<Atomic>(create<I32>());
-  EXPECT_TRUE(a->Type()->Is<sem::I32>());
-}
-
-TEST_F(AtomicTest, TypeName) {
-  auto* a = create<Atomic>(create<I32>());
-  EXPECT_EQ(a->type_name(), "__atomic__i32");
-}
-
-TEST_F(AtomicTest, FriendlyName) {
-  auto* a = create<Atomic>(create<I32>());
-  EXPECT_EQ(a->FriendlyName(Symbols()), "atomic<i32>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/behavior.cc b/src/sem/behavior.cc
deleted file mode 100644
index 2d6f15c..0000000
--- a/src/sem/behavior.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/behavior.h"
-
-namespace tint {
-namespace sem {
-
-std::ostream& operator<<(std::ostream& out, Behavior behavior) {
-  switch (behavior) {
-    case Behavior::kReturn:
-      return out << "Return";
-    case Behavior::kDiscard:
-      return out << "Discard";
-    case Behavior::kBreak:
-      return out << "Break";
-    case Behavior::kContinue:
-      return out << "Continue";
-    case Behavior::kFallthrough:
-      return out << "Fallthrough";
-    case Behavior::kNext:
-      return out << "Next";
-  }
-  return out << "<unknown>";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/behavior.h b/src/sem/behavior.h
deleted file mode 100644
index 1e318c1..0000000
--- a/src/sem/behavior.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_BEHAVIOR_H_
-#define SRC_SEM_BEHAVIOR_H_
-
-#include "src/utils/enum_set.h"
-
-namespace tint {
-namespace sem {
-
-/// Behavior enumerates the possible behaviors of an expression or statement.
-/// @see https://www.w3.org/TR/WGSL/#behaviors
-enum class Behavior {
-  kReturn,
-  kDiscard,
-  kBreak,
-  kContinue,
-  kFallthrough,
-  kNext,
-};
-
-/// Behaviors is a set of Behavior
-using Behaviors = utils::EnumSet<Behavior>;
-
-/// Writes the Behavior to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param behavior the Behavior to write
-/// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, Behavior behavior);
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_BEHAVIOR_H_
diff --git a/src/sem/binding_point.h b/src/sem/binding_point.h
deleted file mode 100644
index e245f8b..0000000
--- a/src/sem/binding_point.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_BINDING_POINT_H_
-#define SRC_SEM_BINDING_POINT_H_
-
-#include <stdint.h>
-
-#include <functional>
-
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace sem {
-
-/// BindingPoint holds a group and binding index.
-struct BindingPoint {
-  /// The `[[group]]` part of the binding point
-  uint32_t group = 0;
-  /// The `[[binding]]` part of the binding point
-  uint32_t binding = 0;
-
-  /// Equality operator
-  /// @param rhs the BindingPoint to compare against
-  /// @returns true if this BindingPoint is equal to `rhs`
-  inline bool operator==(const BindingPoint& rhs) const {
-    return group == rhs.group && binding == rhs.binding;
-  }
-
-  /// Inequality operator
-  /// @param rhs the BindingPoint to compare against
-  /// @returns true if this BindingPoint is not equal to `rhs`
-  inline bool operator!=(const BindingPoint& rhs) const {
-    return !(*this == rhs);
-  }
-};
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::BindingPoint so
-/// BindingPoints can be used as keys for std::unordered_map and
-/// std::unordered_set.
-template <>
-class hash<tint::sem::BindingPoint> {
- public:
-  /// @param binding_point the binding point to create a hash for
-  /// @return the hash value
-  inline std::size_t operator()(
-      const tint::sem::BindingPoint& binding_point) const {
-    return tint::utils::Hash(binding_point.group, binding_point.binding);
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_SEM_BINDING_POINT_H_
diff --git a/src/sem/block_statement.cc b/src/sem/block_statement.cc
deleted file mode 100644
index 89a0bf9..0000000
--- a/src/sem/block_statement.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/block_statement.h"
-
-#include "src/ast/block_statement.h"
-#include "src/ast/function.h"
-#include "src/sem/function.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::BlockStatement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::FunctionBlockStatement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopBlockStatement);
-
-namespace tint {
-namespace sem {
-
-BlockStatement::BlockStatement(const ast::BlockStatement* declaration,
-                               const CompoundStatement* parent,
-                               const sem::Function* function)
-    : Base(declaration, parent, function) {}
-
-BlockStatement::~BlockStatement() = default;
-
-const ast::BlockStatement* BlockStatement::Declaration() const {
-  return Base::Declaration()->As<ast::BlockStatement>();
-}
-
-void BlockStatement::AddDecl(const ast::Variable* var) {
-  decls_.push_back(var);
-}
-
-FunctionBlockStatement::FunctionBlockStatement(const sem::Function* function)
-    : Base(function->Declaration()->body, nullptr, function) {
-  TINT_ASSERT(Semantic, function);
-}
-
-FunctionBlockStatement::~FunctionBlockStatement() = default;
-
-LoopBlockStatement::LoopBlockStatement(const ast::BlockStatement* declaration,
-                                       const CompoundStatement* parent,
-                                       const sem::Function* function)
-    : Base(declaration, parent, function) {
-  TINT_ASSERT(Semantic, parent);
-  TINT_ASSERT(Semantic, function);
-}
-LoopBlockStatement::~LoopBlockStatement() = default;
-
-void LoopBlockStatement::SetFirstContinue(
-    const ast::ContinueStatement* first_continue,
-    size_t num_decls) {
-  first_continue_ = first_continue;
-  num_decls_at_first_continue_ = num_decls;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/block_statement.h b/src/sem/block_statement.h
deleted file mode 100644
index bc96e7d..0000000
--- a/src/sem/block_statement.h
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_BLOCK_STATEMENT_H_
-#define SRC_SEM_BLOCK_STATEMENT_H_
-
-#include <cstddef>
-#include <vector>
-
-#include "src/sem/statement.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class BlockStatement;
-class ContinueStatement;
-class Function;
-class Variable;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Holds semantic information about a block, such as parent block and variables
-/// declared in the block.
-class BlockStatement : public Castable<BlockStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this block statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  BlockStatement(const ast::BlockStatement* declaration,
-                 const CompoundStatement* parent,
-                 const sem::Function* function);
-
-  /// Destructor
-  ~BlockStatement() override;
-
-  /// @returns the AST block statement associated with this semantic block
-  /// statement
-  const ast::BlockStatement* Declaration() const;
-
-  /// @returns the declarations associated with this block
-  const std::vector<const ast::Variable*>& Decls() const { return decls_; }
-
-  /// Associates a declaration with this block.
-  /// @param var a variable declaration to be added to the block
-  void AddDecl(const ast::Variable* var);
-
- private:
-  std::vector<const ast::Variable*> decls_;
-};
-
-/// The root block statement for a function
-class FunctionBlockStatement
-    : public Castable<FunctionBlockStatement, BlockStatement> {
- public:
-  /// Constructor
-  /// @param function the owning function
-  explicit FunctionBlockStatement(const sem::Function* function);
-
-  /// Destructor
-  ~FunctionBlockStatement() override;
-};
-
-/// Holds semantic information about a loop body block or for-loop body block
-class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this block statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  LoopBlockStatement(const ast::BlockStatement* declaration,
-                     const CompoundStatement* parent,
-                     const sem::Function* function);
-
-  /// Destructor
-  ~LoopBlockStatement() override;
-
-  /// @returns the first continue statement in this loop block, or nullptr if
-  /// there are no continue statements in the block
-  const ast::ContinueStatement* FirstContinue() const {
-    return first_continue_;
-  }
-
-  /// @returns the number of variables declared before the first continue
-  /// statement
-  size_t NumDeclsAtFirstContinue() const {
-    return num_decls_at_first_continue_;
-  }
-
-  /// Allows the resolver to record the first continue statement in the block
-  /// and the number of variables declared prior to that statement.
-  /// @param first_continue the first continue statement in the block
-  /// @param num_decls the number of variable declarations before that continue
-  void SetFirstContinue(const ast::ContinueStatement* first_continue,
-                        size_t num_decls);
-
- private:
-  /// The first continue statement in this loop block.
-  const ast::ContinueStatement* first_continue_ = nullptr;
-
-  /// The number of variables declared before the first continue statement.
-  size_t num_decls_at_first_continue_ = 0;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_BLOCK_STATEMENT_H_
diff --git a/src/sem/bool_type.cc b/src/sem/bool_type.cc
deleted file mode 100644
index eeb3100..0000000
--- a/src/sem/bool_type.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/bool_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Bool);
-
-namespace tint {
-namespace sem {
-
-Bool::Bool() = default;
-
-Bool::Bool(Bool&&) = default;
-
-Bool::~Bool() = default;
-
-std::string Bool::type_name() const {
-  return "__bool";
-}
-
-std::string Bool::FriendlyName(const SymbolTable&) const {
-  return "bool";
-}
-
-bool Bool::IsConstructible() const {
-  return true;
-}
-
-uint32_t Bool::Size() const {
-  return 4;
-}
-
-uint32_t Bool::Align() const {
-  return 4;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/bool_type.h b/src/sem/bool_type.h
deleted file mode 100644
index c143989..0000000
--- a/src/sem/bool_type.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_BOOL_TYPE_H_
-#define SRC_SEM_BOOL_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-// X11 likes to #define Bool leading to confusing error messages.
-// If its defined, undefine it.
-#ifdef Bool
-#undef Bool
-#endif
-
-namespace tint {
-namespace sem {
-
-/// A boolean type
-class Bool : public Castable<Bool, Type> {
- public:
-  /// Constructor
-  Bool();
-  /// Move constructor
-  Bool(Bool&&);
-  ~Bool() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the size in bytes of the type.
-  /// @note: booleans are not host-sharable, but still may exist in workgroup
-  /// storage.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type.
-  /// @note: booleans are not host-sharable, but still may exist in workgroup
-  /// storage.
-  uint32_t Align() const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_BOOL_TYPE_H_
diff --git a/src/sem/bool_type_test.cc b/src/sem/bool_type_test.cc
deleted file mode 100644
index 4d08fda..0000000
--- a/src/sem/bool_type_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using BoolTest = TestHelper;
-
-TEST_F(BoolTest, TypeName) {
-  Bool b;
-  EXPECT_EQ(b.type_name(), "__bool");
-}
-
-TEST_F(BoolTest, FriendlyName) {
-  Bool b;
-  EXPECT_EQ(b.FriendlyName(Symbols()), "bool");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/builtin.cc b/src/sem/builtin.cc
deleted file mode 100644
index d96cf87..0000000
--- a/src/sem/builtin.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2020 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.
-
-// Doxygen seems to trip over this file for some unknown reason. Disable.
-//! @cond Doxygen_Suppress
-
-#include "src/sem/builtin.h"
-
-#include <vector>
-
-#include "src/utils/to_const_ptr_vec.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Builtin);
-
-namespace tint {
-namespace sem {
-
-const char* Builtin::str() const {
-  return sem::str(type_);
-}
-
-bool IsCoarseDerivativeBuiltin(BuiltinType i) {
-  return i == BuiltinType::kDpdxCoarse || i == BuiltinType::kDpdyCoarse ||
-         i == BuiltinType::kFwidthCoarse;
-}
-
-bool IsFineDerivativeBuiltin(BuiltinType i) {
-  return i == BuiltinType::kDpdxFine || i == BuiltinType::kDpdyFine ||
-         i == BuiltinType::kFwidthFine;
-}
-
-bool IsDerivativeBuiltin(BuiltinType i) {
-  return i == BuiltinType::kDpdx || i == BuiltinType::kDpdy ||
-         i == BuiltinType::kFwidth || IsCoarseDerivativeBuiltin(i) ||
-         IsFineDerivativeBuiltin(i);
-}
-
-bool IsFloatClassificationBuiltin(BuiltinType i) {
-  return i == BuiltinType::kIsFinite || i == BuiltinType::kIsInf ||
-         i == BuiltinType::kIsNan || i == BuiltinType::kIsNormal;
-}
-
-bool IsTextureBuiltin(BuiltinType i) {
-  return IsImageQueryBuiltin(i) || i == BuiltinType::kTextureLoad ||
-         i == BuiltinType::kTextureGather ||
-         i == BuiltinType::kTextureGatherCompare ||
-         i == BuiltinType::kTextureSample ||
-         i == BuiltinType::kTextureSampleLevel ||
-         i == BuiltinType::kTextureSampleBias ||
-         i == BuiltinType::kTextureSampleCompare ||
-         i == BuiltinType::kTextureSampleCompareLevel ||
-         i == BuiltinType::kTextureSampleGrad ||
-         i == BuiltinType::kTextureStore;
-}
-
-bool IsImageQueryBuiltin(BuiltinType i) {
-  return i == BuiltinType::kTextureDimensions ||
-         i == BuiltinType::kTextureNumLayers ||
-         i == BuiltinType::kTextureNumLevels ||
-         i == BuiltinType::kTextureNumSamples;
-}
-
-bool IsDataPackingBuiltin(BuiltinType i) {
-  return i == BuiltinType::kPack4x8snorm || i == BuiltinType::kPack4x8unorm ||
-         i == BuiltinType::kPack2x16snorm || i == BuiltinType::kPack2x16unorm ||
-         i == BuiltinType::kPack2x16float;
-}
-
-bool IsDataUnpackingBuiltin(BuiltinType i) {
-  return i == BuiltinType::kUnpack4x8snorm ||
-         i == BuiltinType::kUnpack4x8unorm ||
-         i == BuiltinType::kUnpack2x16snorm ||
-         i == BuiltinType::kUnpack2x16unorm ||
-         i == BuiltinType::kUnpack2x16float;
-}
-
-bool IsBarrierBuiltin(BuiltinType i) {
-  return i == BuiltinType::kWorkgroupBarrier ||
-         i == BuiltinType::kStorageBarrier;
-}
-
-bool IsAtomicBuiltin(BuiltinType i) {
-  return i == sem::BuiltinType::kAtomicLoad ||
-         i == sem::BuiltinType::kAtomicStore ||
-         i == sem::BuiltinType::kAtomicAdd ||
-         i == sem::BuiltinType::kAtomicSub ||
-         i == sem::BuiltinType::kAtomicMax ||
-         i == sem::BuiltinType::kAtomicMin ||
-         i == sem::BuiltinType::kAtomicAnd ||
-         i == sem::BuiltinType::kAtomicOr ||
-         i == sem::BuiltinType::kAtomicXor ||
-         i == sem::BuiltinType::kAtomicExchange ||
-         i == sem::BuiltinType::kAtomicCompareExchangeWeak;
-}
-
-Builtin::Builtin(BuiltinType type,
-                 const sem::Type* return_type,
-                 std::vector<Parameter*> parameters,
-                 PipelineStageSet supported_stages,
-                 bool is_deprecated)
-    : Base(return_type, utils::ToConstPtrVec(parameters)),
-      type_(type),
-      supported_stages_(supported_stages),
-      is_deprecated_(is_deprecated) {
-  for (auto* parameter : parameters) {
-    parameter->SetOwner(this);
-  }
-}
-
-Builtin::~Builtin() = default;
-
-bool Builtin::IsCoarseDerivative() const {
-  return IsCoarseDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsFineDerivative() const {
-  return IsFineDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsDerivative() const {
-  return IsDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsFloatClassification() const {
-  return IsFloatClassificationBuiltin(type_);
-}
-
-bool Builtin::IsTexture() const {
-  return IsTextureBuiltin(type_);
-}
-
-bool Builtin::IsImageQuery() const {
-  return IsImageQueryBuiltin(type_);
-}
-
-bool Builtin::IsDataPacking() const {
-  return IsDataPackingBuiltin(type_);
-}
-
-bool Builtin::IsDataUnpacking() const {
-  return IsDataUnpackingBuiltin(type_);
-}
-
-bool Builtin::IsBarrier() const {
-  return IsBarrierBuiltin(type_);
-}
-
-bool Builtin::IsAtomic() const {
-  return IsAtomicBuiltin(type_);
-}
-
-bool Builtin::HasSideEffects() const {
-  if (IsAtomic() && type_ != sem::BuiltinType::kAtomicLoad) {
-    return true;
-  }
-  if (type_ == sem::BuiltinType::kTextureStore) {
-    return true;
-  }
-  return false;
-}
-
-}  // namespace sem
-}  // namespace tint
-
-//! @endcond
diff --git a/src/sem/builtin.h b/src/sem/builtin.h
deleted file mode 100644
index be9ccaa..0000000
--- a/src/sem/builtin.h
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_BUILTIN_H_
-#define SRC_SEM_BUILTIN_H_
-
-#include <string>
-#include <vector>
-
-#include "src/sem/builtin_type.h"
-#include "src/sem/call_target.h"
-#include "src/sem/pipeline_stage_set.h"
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace sem {
-
-/// Determines if the given `i` is a coarse derivative
-/// @param i the builtin type
-/// @returns true if the given derivative is coarse.
-bool IsCoarseDerivativeBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a fine derivative
-/// @param i the builtin type
-/// @returns true if the given derivative is fine.
-bool IsFineDerivativeBuiltin(BuiltinType i);
-
-/// Determine if the given `i` is a derivative builtin
-/// @param i the builtin type
-/// @returns true if the given `i` is a derivative builtin
-bool IsDerivativeBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a float classification builtin
-/// @param i the builtin type
-/// @returns true if the given `i` is a float builtin
-bool IsFloatClassificationBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a texture operation builtin
-/// @param i the builtin type
-/// @returns true if the given `i` is a texture operation builtin
-bool IsTextureBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a image query builtin
-/// @param i the builtin type
-/// @returns true if the given `i` is a image query builtin
-bool IsImageQueryBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a data packing builtin
-/// @param i the builtin
-/// @returns true if the given `i` is a data packing builtin
-bool IsDataPackingBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a data unpacking builtin
-/// @param i the builtin
-/// @returns true if the given `i` is a data unpacking builtin
-bool IsDataUnpackingBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a barrier builtin
-/// @param i the builtin
-/// @returns true if the given `i` is a barrier builtin
-bool IsBarrierBuiltin(BuiltinType i);
-
-/// Determines if the given `i` is a atomic builtin
-/// @param i the builtin
-/// @returns true if the given `i` is a atomic builtin
-bool IsAtomicBuiltin(BuiltinType i);
-
-/// Builtin holds the semantic information for a builtin function.
-class Builtin : public Castable<Builtin, CallTarget> {
- public:
-  /// Constructor
-  /// @param type the builtin type
-  /// @param return_type the return type for the builtin call
-  /// @param parameters the parameters for the builtin overload
-  /// @param supported_stages the pipeline stages that this builtin can be
-  /// used in
-  /// @param is_deprecated true if the particular overload is considered
-  /// deprecated
-  Builtin(BuiltinType type,
-          const sem::Type* return_type,
-          std::vector<Parameter*> parameters,
-          PipelineStageSet supported_stages,
-          bool is_deprecated);
-
-  /// Destructor
-  ~Builtin() override;
-
-  /// @return the type of the builtin
-  BuiltinType Type() const { return type_; }
-
-  /// @return the pipeline stages that this builtin can be used in
-  PipelineStageSet SupportedStages() const { return supported_stages_; }
-
-  /// @return true if the builtin overload is considered deprecated
-  bool IsDeprecated() const { return is_deprecated_; }
-
-  /// @returns the name of the builtin function type. The spelling, including
-  /// case, matches the name in the WGSL spec.
-  const char* str() const;
-
-  /// @returns true if builtin is a coarse derivative builtin
-  bool IsCoarseDerivative() const;
-
-  /// @returns true if builtin is a fine a derivative builtin
-  bool IsFineDerivative() const;
-
-  /// @returns true if builtin is a derivative builtin
-  bool IsDerivative() const;
-
-  /// @returns true if builtin is a float builtin
-  bool IsFloatClassification() const;
-
-  /// @returns true if builtin is a texture operation builtin
-  bool IsTexture() const;
-
-  /// @returns true if builtin is a image query builtin
-  bool IsImageQuery() const;
-
-  /// @returns true if builtin is a data packing builtin
-  bool IsDataPacking() const;
-
-  /// @returns true if builtin is a data unpacking builtin
-  bool IsDataUnpacking() const;
-
-  /// @returns true if builtin is a barrier builtin
-  bool IsBarrier() const;
-
-  /// @returns true if builtin is a atomic builtin
-  bool IsAtomic() const;
-
-  /// @returns true if intrinsic may have side-effects (i.e. writes to at least
-  /// one of its inputs)
-  bool HasSideEffects() const;
-
- private:
-  const BuiltinType type_;
-  const PipelineStageSet supported_stages_;
-  const bool is_deprecated_;
-};
-
-/// Constant value used by the degrees() builtin
-static constexpr double kRadToDeg = 57.295779513082322865;
-
-/// Constant value used by the radians() builtin
-static constexpr double kDegToRad = 0.017453292519943295474;
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::Builtin
-template <>
-class hash<tint::sem::Builtin> {
- public:
-  /// @param i the Builtin to create a hash for
-  /// @return the hash value
-  inline std::size_t operator()(const tint::sem::Builtin& i) const {
-    return tint::utils::Hash(i.Type(), i.SupportedStages(), i.ReturnType(),
-                             i.Parameters(), i.IsDeprecated());
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_SEM_BUILTIN_H_
diff --git a/src/sem/builtin_test.cc b/src/sem/builtin_test.cc
deleted file mode 100644
index e646404..0000000
--- a/src/sem/builtin_test.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/builtin.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-struct BuiltinData {
-  const char* name;
-  BuiltinType builtin;
-};
-
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.name;
-  return out;
-}
-
-using BuiltinTypeTest = testing::TestWithParam<BuiltinData>;
-
-TEST_P(BuiltinTypeTest, Parse) {
-  auto param = GetParam();
-  EXPECT_EQ(ParseBuiltinType(param.name), param.builtin);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinTypeTest,
-    BuiltinTypeTest,
-    testing::Values(
-        BuiltinData{"abs", BuiltinType::kAbs},
-        BuiltinData{"acos", BuiltinType::kAcos},
-        BuiltinData{"all", BuiltinType::kAll},
-        BuiltinData{"any", BuiltinType::kAny},
-        BuiltinData{"arrayLength", BuiltinType::kArrayLength},
-        BuiltinData{"asin", BuiltinType::kAsin},
-        BuiltinData{"atan", BuiltinType::kAtan},
-        BuiltinData{"atan2", BuiltinType::kAtan2},
-        BuiltinData{"ceil", BuiltinType::kCeil},
-        BuiltinData{"clamp", BuiltinType::kClamp},
-        BuiltinData{"cos", BuiltinType::kCos},
-        BuiltinData{"cosh", BuiltinType::kCosh},
-        BuiltinData{"countOneBits", BuiltinType::kCountOneBits},
-        BuiltinData{"cross", BuiltinType::kCross},
-        BuiltinData{"determinant", BuiltinType::kDeterminant},
-        BuiltinData{"distance", BuiltinType::kDistance},
-        BuiltinData{"dot", BuiltinType::kDot},
-        BuiltinData{"dpdx", BuiltinType::kDpdx},
-        BuiltinData{"dpdxCoarse", BuiltinType::kDpdxCoarse},
-        BuiltinData{"dpdxFine", BuiltinType::kDpdxFine},
-        BuiltinData{"dpdy", BuiltinType::kDpdy},
-        BuiltinData{"dpdyCoarse", BuiltinType::kDpdyCoarse},
-        BuiltinData{"dpdyFine", BuiltinType::kDpdyFine},
-        BuiltinData{"exp", BuiltinType::kExp},
-        BuiltinData{"exp2", BuiltinType::kExp2},
-        BuiltinData{"faceForward", BuiltinType::kFaceForward},
-        BuiltinData{"floor", BuiltinType::kFloor},
-        BuiltinData{"fma", BuiltinType::kFma},
-        BuiltinData{"fract", BuiltinType::kFract},
-        BuiltinData{"frexp", BuiltinType::kFrexp},
-        BuiltinData{"fwidth", BuiltinType::kFwidth},
-        BuiltinData{"fwidthCoarse", BuiltinType::kFwidthCoarse},
-        BuiltinData{"fwidthFine", BuiltinType::kFwidthFine},
-        BuiltinData{"inverseSqrt", BuiltinType::kInverseSqrt},
-        BuiltinData{"isFinite", BuiltinType::kIsFinite},
-        BuiltinData{"isInf", BuiltinType::kIsInf},
-        BuiltinData{"isNan", BuiltinType::kIsNan},
-        BuiltinData{"isNormal", BuiltinType::kIsNormal},
-        BuiltinData{"ldexp", BuiltinType::kLdexp},
-        BuiltinData{"length", BuiltinType::kLength},
-        BuiltinData{"log", BuiltinType::kLog},
-        BuiltinData{"log2", BuiltinType::kLog2},
-        BuiltinData{"max", BuiltinType::kMax},
-        BuiltinData{"min", BuiltinType::kMin},
-        BuiltinData{"mix", BuiltinType::kMix},
-        BuiltinData{"modf", BuiltinType::kModf},
-        BuiltinData{"normalize", BuiltinType::kNormalize},
-        BuiltinData{"pow", BuiltinType::kPow},
-        BuiltinData{"reflect", BuiltinType::kReflect},
-        BuiltinData{"reverseBits", BuiltinType::kReverseBits},
-        BuiltinData{"round", BuiltinType::kRound},
-        BuiltinData{"select", BuiltinType::kSelect},
-        BuiltinData{"sign", BuiltinType::kSign},
-        BuiltinData{"sin", BuiltinType::kSin},
-        BuiltinData{"sinh", BuiltinType::kSinh},
-        BuiltinData{"smoothStep", BuiltinType::kSmoothStep},
-        BuiltinData{"sqrt", BuiltinType::kSqrt},
-        BuiltinData{"step", BuiltinType::kStep},
-        BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
-        BuiltinData{"tan", BuiltinType::kTan},
-        BuiltinData{"tanh", BuiltinType::kTanh},
-        BuiltinData{"textureDimensions", BuiltinType::kTextureDimensions},
-        BuiltinData{"textureLoad", BuiltinType::kTextureLoad},
-        BuiltinData{"textureNumLayers", BuiltinType::kTextureNumLayers},
-        BuiltinData{"textureNumLevels", BuiltinType::kTextureNumLevels},
-        BuiltinData{"textureNumSamples", BuiltinType::kTextureNumSamples},
-        BuiltinData{"textureSample", BuiltinType::kTextureSample},
-        BuiltinData{"textureSampleBias", BuiltinType::kTextureSampleBias},
-        BuiltinData{"textureSampleCompare", BuiltinType::kTextureSampleCompare},
-        BuiltinData{"textureSampleCompareLevel",
-                    BuiltinType::kTextureSampleCompareLevel},
-        BuiltinData{"textureSampleGrad", BuiltinType::kTextureSampleGrad},
-        BuiltinData{"textureSampleLevel", BuiltinType::kTextureSampleLevel},
-        BuiltinData{"trunc", BuiltinType::kTrunc},
-        BuiltinData{"unpack2x16float", BuiltinType::kUnpack2x16float},
-        BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2x16snorm},
-        BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2x16unorm},
-        BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4x8snorm},
-        BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4x8unorm},
-        BuiltinData{"workgroupBarrier", BuiltinType::kWorkgroupBarrier}));
-
-TEST_F(BuiltinTypeTest, ParseNoMatch) {
-  EXPECT_EQ(ParseBuiltinType("not_builtin"), BuiltinType::kNone);
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/builtin_type.cc b/src/sem/builtin_type.cc
deleted file mode 100644
index 3145f0a..0000000
--- a/src/sem/builtin_type.cc
+++ /dev/null
@@ -1,560 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/builtin-gen
-// using the template:
-//   src/sem/builtin_type.cc.tmpl
-// and the builtin defintion file:
-//   src/builtins.def
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/sem/builtin_type.h"
-
-#include <sstream>
-
-namespace tint {
-namespace sem {
-
-BuiltinType ParseBuiltinType(const std::string& name) {
-  if (name == "abs") {
-    return BuiltinType::kAbs;
-  }
-  if (name == "acos") {
-    return BuiltinType::kAcos;
-  }
-  if (name == "all") {
-    return BuiltinType::kAll;
-  }
-  if (name == "any") {
-    return BuiltinType::kAny;
-  }
-  if (name == "arrayLength") {
-    return BuiltinType::kArrayLength;
-  }
-  if (name == "asin") {
-    return BuiltinType::kAsin;
-  }
-  if (name == "atan") {
-    return BuiltinType::kAtan;
-  }
-  if (name == "atan2") {
-    return BuiltinType::kAtan2;
-  }
-  if (name == "ceil") {
-    return BuiltinType::kCeil;
-  }
-  if (name == "clamp") {
-    return BuiltinType::kClamp;
-  }
-  if (name == "cos") {
-    return BuiltinType::kCos;
-  }
-  if (name == "cosh") {
-    return BuiltinType::kCosh;
-  }
-  if (name == "countOneBits") {
-    return BuiltinType::kCountOneBits;
-  }
-  if (name == "cross") {
-    return BuiltinType::kCross;
-  }
-  if (name == "degrees") {
-    return BuiltinType::kDegrees;
-  }
-  if (name == "determinant") {
-    return BuiltinType::kDeterminant;
-  }
-  if (name == "distance") {
-    return BuiltinType::kDistance;
-  }
-  if (name == "dot") {
-    return BuiltinType::kDot;
-  }
-  if (name == "dpdx") {
-    return BuiltinType::kDpdx;
-  }
-  if (name == "dpdxCoarse") {
-    return BuiltinType::kDpdxCoarse;
-  }
-  if (name == "dpdxFine") {
-    return BuiltinType::kDpdxFine;
-  }
-  if (name == "dpdy") {
-    return BuiltinType::kDpdy;
-  }
-  if (name == "dpdyCoarse") {
-    return BuiltinType::kDpdyCoarse;
-  }
-  if (name == "dpdyFine") {
-    return BuiltinType::kDpdyFine;
-  }
-  if (name == "exp") {
-    return BuiltinType::kExp;
-  }
-  if (name == "exp2") {
-    return BuiltinType::kExp2;
-  }
-  if (name == "faceForward") {
-    return BuiltinType::kFaceForward;
-  }
-  if (name == "floor") {
-    return BuiltinType::kFloor;
-  }
-  if (name == "fma") {
-    return BuiltinType::kFma;
-  }
-  if (name == "fract") {
-    return BuiltinType::kFract;
-  }
-  if (name == "frexp") {
-    return BuiltinType::kFrexp;
-  }
-  if (name == "fwidth") {
-    return BuiltinType::kFwidth;
-  }
-  if (name == "fwidthCoarse") {
-    return BuiltinType::kFwidthCoarse;
-  }
-  if (name == "fwidthFine") {
-    return BuiltinType::kFwidthFine;
-  }
-  if (name == "inverseSqrt") {
-    return BuiltinType::kInverseSqrt;
-  }
-  if (name == "isFinite") {
-    return BuiltinType::kIsFinite;
-  }
-  if (name == "isInf") {
-    return BuiltinType::kIsInf;
-  }
-  if (name == "isNan") {
-    return BuiltinType::kIsNan;
-  }
-  if (name == "isNormal") {
-    return BuiltinType::kIsNormal;
-  }
-  if (name == "ldexp") {
-    return BuiltinType::kLdexp;
-  }
-  if (name == "length") {
-    return BuiltinType::kLength;
-  }
-  if (name == "log") {
-    return BuiltinType::kLog;
-  }
-  if (name == "log2") {
-    return BuiltinType::kLog2;
-  }
-  if (name == "max") {
-    return BuiltinType::kMax;
-  }
-  if (name == "min") {
-    return BuiltinType::kMin;
-  }
-  if (name == "mix") {
-    return BuiltinType::kMix;
-  }
-  if (name == "modf") {
-    return BuiltinType::kModf;
-  }
-  if (name == "normalize") {
-    return BuiltinType::kNormalize;
-  }
-  if (name == "pack2x16float") {
-    return BuiltinType::kPack2x16float;
-  }
-  if (name == "pack2x16snorm") {
-    return BuiltinType::kPack2x16snorm;
-  }
-  if (name == "pack2x16unorm") {
-    return BuiltinType::kPack2x16unorm;
-  }
-  if (name == "pack4x8snorm") {
-    return BuiltinType::kPack4x8snorm;
-  }
-  if (name == "pack4x8unorm") {
-    return BuiltinType::kPack4x8unorm;
-  }
-  if (name == "pow") {
-    return BuiltinType::kPow;
-  }
-  if (name == "radians") {
-    return BuiltinType::kRadians;
-  }
-  if (name == "reflect") {
-    return BuiltinType::kReflect;
-  }
-  if (name == "refract") {
-    return BuiltinType::kRefract;
-  }
-  if (name == "reverseBits") {
-    return BuiltinType::kReverseBits;
-  }
-  if (name == "round") {
-    return BuiltinType::kRound;
-  }
-  if (name == "select") {
-    return BuiltinType::kSelect;
-  }
-  if (name == "sign") {
-    return BuiltinType::kSign;
-  }
-  if (name == "sin") {
-    return BuiltinType::kSin;
-  }
-  if (name == "sinh") {
-    return BuiltinType::kSinh;
-  }
-  if (name == "smoothStep") {
-    return BuiltinType::kSmoothStep;
-  }
-  if (name == "sqrt") {
-    return BuiltinType::kSqrt;
-  }
-  if (name == "step") {
-    return BuiltinType::kStep;
-  }
-  if (name == "storageBarrier") {
-    return BuiltinType::kStorageBarrier;
-  }
-  if (name == "tan") {
-    return BuiltinType::kTan;
-  }
-  if (name == "tanh") {
-    return BuiltinType::kTanh;
-  }
-  if (name == "transpose") {
-    return BuiltinType::kTranspose;
-  }
-  if (name == "trunc") {
-    return BuiltinType::kTrunc;
-  }
-  if (name == "unpack2x16float") {
-    return BuiltinType::kUnpack2x16float;
-  }
-  if (name == "unpack2x16snorm") {
-    return BuiltinType::kUnpack2x16snorm;
-  }
-  if (name == "unpack2x16unorm") {
-    return BuiltinType::kUnpack2x16unorm;
-  }
-  if (name == "unpack4x8snorm") {
-    return BuiltinType::kUnpack4x8snorm;
-  }
-  if (name == "unpack4x8unorm") {
-    return BuiltinType::kUnpack4x8unorm;
-  }
-  if (name == "workgroupBarrier") {
-    return BuiltinType::kWorkgroupBarrier;
-  }
-  if (name == "textureDimensions") {
-    return BuiltinType::kTextureDimensions;
-  }
-  if (name == "textureGather") {
-    return BuiltinType::kTextureGather;
-  }
-  if (name == "textureGatherCompare") {
-    return BuiltinType::kTextureGatherCompare;
-  }
-  if (name == "textureNumLayers") {
-    return BuiltinType::kTextureNumLayers;
-  }
-  if (name == "textureNumLevels") {
-    return BuiltinType::kTextureNumLevels;
-  }
-  if (name == "textureNumSamples") {
-    return BuiltinType::kTextureNumSamples;
-  }
-  if (name == "textureSample") {
-    return BuiltinType::kTextureSample;
-  }
-  if (name == "textureSampleBias") {
-    return BuiltinType::kTextureSampleBias;
-  }
-  if (name == "textureSampleCompare") {
-    return BuiltinType::kTextureSampleCompare;
-  }
-  if (name == "textureSampleCompareLevel") {
-    return BuiltinType::kTextureSampleCompareLevel;
-  }
-  if (name == "textureSampleGrad") {
-    return BuiltinType::kTextureSampleGrad;
-  }
-  if (name == "textureSampleLevel") {
-    return BuiltinType::kTextureSampleLevel;
-  }
-  if (name == "textureStore") {
-    return BuiltinType::kTextureStore;
-  }
-  if (name == "textureLoad") {
-    return BuiltinType::kTextureLoad;
-  }
-  if (name == "atomicLoad") {
-    return BuiltinType::kAtomicLoad;
-  }
-  if (name == "atomicStore") {
-    return BuiltinType::kAtomicStore;
-  }
-  if (name == "atomicAdd") {
-    return BuiltinType::kAtomicAdd;
-  }
-  if (name == "atomicSub") {
-    return BuiltinType::kAtomicSub;
-  }
-  if (name == "atomicMax") {
-    return BuiltinType::kAtomicMax;
-  }
-  if (name == "atomicMin") {
-    return BuiltinType::kAtomicMin;
-  }
-  if (name == "atomicAnd") {
-    return BuiltinType::kAtomicAnd;
-  }
-  if (name == "atomicOr") {
-    return BuiltinType::kAtomicOr;
-  }
-  if (name == "atomicXor") {
-    return BuiltinType::kAtomicXor;
-  }
-  if (name == "atomicExchange") {
-    return BuiltinType::kAtomicExchange;
-  }
-  if (name == "atomicCompareExchangeWeak") {
-    return BuiltinType::kAtomicCompareExchangeWeak;
-  }
-  return BuiltinType::kNone;
-}
-
-const char* str(BuiltinType i) {
-  switch (i) {
-    case BuiltinType::kNone:
-      return "<none>";
-    case BuiltinType::kAbs:
-      return "abs";
-    case BuiltinType::kAcos:
-      return "acos";
-    case BuiltinType::kAll:
-      return "all";
-    case BuiltinType::kAny:
-      return "any";
-    case BuiltinType::kArrayLength:
-      return "arrayLength";
-    case BuiltinType::kAsin:
-      return "asin";
-    case BuiltinType::kAtan:
-      return "atan";
-    case BuiltinType::kAtan2:
-      return "atan2";
-    case BuiltinType::kCeil:
-      return "ceil";
-    case BuiltinType::kClamp:
-      return "clamp";
-    case BuiltinType::kCos:
-      return "cos";
-    case BuiltinType::kCosh:
-      return "cosh";
-    case BuiltinType::kCountOneBits:
-      return "countOneBits";
-    case BuiltinType::kCross:
-      return "cross";
-    case BuiltinType::kDegrees:
-      return "degrees";
-    case BuiltinType::kDeterminant:
-      return "determinant";
-    case BuiltinType::kDistance:
-      return "distance";
-    case BuiltinType::kDot:
-      return "dot";
-    case BuiltinType::kDpdx:
-      return "dpdx";
-    case BuiltinType::kDpdxCoarse:
-      return "dpdxCoarse";
-    case BuiltinType::kDpdxFine:
-      return "dpdxFine";
-    case BuiltinType::kDpdy:
-      return "dpdy";
-    case BuiltinType::kDpdyCoarse:
-      return "dpdyCoarse";
-    case BuiltinType::kDpdyFine:
-      return "dpdyFine";
-    case BuiltinType::kExp:
-      return "exp";
-    case BuiltinType::kExp2:
-      return "exp2";
-    case BuiltinType::kFaceForward:
-      return "faceForward";
-    case BuiltinType::kFloor:
-      return "floor";
-    case BuiltinType::kFma:
-      return "fma";
-    case BuiltinType::kFract:
-      return "fract";
-    case BuiltinType::kFrexp:
-      return "frexp";
-    case BuiltinType::kFwidth:
-      return "fwidth";
-    case BuiltinType::kFwidthCoarse:
-      return "fwidthCoarse";
-    case BuiltinType::kFwidthFine:
-      return "fwidthFine";
-    case BuiltinType::kInverseSqrt:
-      return "inverseSqrt";
-    case BuiltinType::kIsFinite:
-      return "isFinite";
-    case BuiltinType::kIsInf:
-      return "isInf";
-    case BuiltinType::kIsNan:
-      return "isNan";
-    case BuiltinType::kIsNormal:
-      return "isNormal";
-    case BuiltinType::kLdexp:
-      return "ldexp";
-    case BuiltinType::kLength:
-      return "length";
-    case BuiltinType::kLog:
-      return "log";
-    case BuiltinType::kLog2:
-      return "log2";
-    case BuiltinType::kMax:
-      return "max";
-    case BuiltinType::kMin:
-      return "min";
-    case BuiltinType::kMix:
-      return "mix";
-    case BuiltinType::kModf:
-      return "modf";
-    case BuiltinType::kNormalize:
-      return "normalize";
-    case BuiltinType::kPack2x16float:
-      return "pack2x16float";
-    case BuiltinType::kPack2x16snorm:
-      return "pack2x16snorm";
-    case BuiltinType::kPack2x16unorm:
-      return "pack2x16unorm";
-    case BuiltinType::kPack4x8snorm:
-      return "pack4x8snorm";
-    case BuiltinType::kPack4x8unorm:
-      return "pack4x8unorm";
-    case BuiltinType::kPow:
-      return "pow";
-    case BuiltinType::kRadians:
-      return "radians";
-    case BuiltinType::kReflect:
-      return "reflect";
-    case BuiltinType::kRefract:
-      return "refract";
-    case BuiltinType::kReverseBits:
-      return "reverseBits";
-    case BuiltinType::kRound:
-      return "round";
-    case BuiltinType::kSelect:
-      return "select";
-    case BuiltinType::kSign:
-      return "sign";
-    case BuiltinType::kSin:
-      return "sin";
-    case BuiltinType::kSinh:
-      return "sinh";
-    case BuiltinType::kSmoothStep:
-      return "smoothStep";
-    case BuiltinType::kSqrt:
-      return "sqrt";
-    case BuiltinType::kStep:
-      return "step";
-    case BuiltinType::kStorageBarrier:
-      return "storageBarrier";
-    case BuiltinType::kTan:
-      return "tan";
-    case BuiltinType::kTanh:
-      return "tanh";
-    case BuiltinType::kTranspose:
-      return "transpose";
-    case BuiltinType::kTrunc:
-      return "trunc";
-    case BuiltinType::kUnpack2x16float:
-      return "unpack2x16float";
-    case BuiltinType::kUnpack2x16snorm:
-      return "unpack2x16snorm";
-    case BuiltinType::kUnpack2x16unorm:
-      return "unpack2x16unorm";
-    case BuiltinType::kUnpack4x8snorm:
-      return "unpack4x8snorm";
-    case BuiltinType::kUnpack4x8unorm:
-      return "unpack4x8unorm";
-    case BuiltinType::kWorkgroupBarrier:
-      return "workgroupBarrier";
-    case BuiltinType::kTextureDimensions:
-      return "textureDimensions";
-    case BuiltinType::kTextureGather:
-      return "textureGather";
-    case BuiltinType::kTextureGatherCompare:
-      return "textureGatherCompare";
-    case BuiltinType::kTextureNumLayers:
-      return "textureNumLayers";
-    case BuiltinType::kTextureNumLevels:
-      return "textureNumLevels";
-    case BuiltinType::kTextureNumSamples:
-      return "textureNumSamples";
-    case BuiltinType::kTextureSample:
-      return "textureSample";
-    case BuiltinType::kTextureSampleBias:
-      return "textureSampleBias";
-    case BuiltinType::kTextureSampleCompare:
-      return "textureSampleCompare";
-    case BuiltinType::kTextureSampleCompareLevel:
-      return "textureSampleCompareLevel";
-    case BuiltinType::kTextureSampleGrad:
-      return "textureSampleGrad";
-    case BuiltinType::kTextureSampleLevel:
-      return "textureSampleLevel";
-    case BuiltinType::kTextureStore:
-      return "textureStore";
-    case BuiltinType::kTextureLoad:
-      return "textureLoad";
-    case BuiltinType::kAtomicLoad:
-      return "atomicLoad";
-    case BuiltinType::kAtomicStore:
-      return "atomicStore";
-    case BuiltinType::kAtomicAdd:
-      return "atomicAdd";
-    case BuiltinType::kAtomicSub:
-      return "atomicSub";
-    case BuiltinType::kAtomicMax:
-      return "atomicMax";
-    case BuiltinType::kAtomicMin:
-      return "atomicMin";
-    case BuiltinType::kAtomicAnd:
-      return "atomicAnd";
-    case BuiltinType::kAtomicOr:
-      return "atomicOr";
-    case BuiltinType::kAtomicXor:
-      return "atomicXor";
-    case BuiltinType::kAtomicExchange:
-      return "atomicExchange";
-    case BuiltinType::kAtomicCompareExchangeWeak:
-      return "atomicCompareExchangeWeak";
-  }
-  return "<unknown>";
-}
-
-std::ostream& operator<<(std::ostream& out, BuiltinType i) {
-  out << str(i);
-  return out;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/builtin_type.cc.tmpl b/src/sem/builtin_type.cc.tmpl
deleted file mode 100644
index aca86fa..0000000
--- a/src/sem/builtin_type.cc.tmpl
+++ /dev/null
@@ -1,45 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/builtin-gen to generate builtin_type.cc
-
-See:
-* tools/cmd/builtin-gen/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#include "src/sem/builtin_type.h"
-
-#include <sstream>
-
-namespace tint {
-namespace sem {
-
-BuiltinType ParseBuiltinType(const std::string& name) {
-{{- range .Sem.Functions  }}
-  if (name == "{{.Name}}") {
-    return BuiltinType::k{{Title .Name}};
-  }
-{{- end  }}
-  return BuiltinType::kNone;
-}
-
-const char* str(BuiltinType i) {
-  switch (i) {
-    case BuiltinType::kNone:
-      return "<none>";
-{{- range .Sem.Functions  }}
-    case BuiltinType::k{{Title .Name}}:
-      return "{{.Name}}";
-{{- end  }}
-  }
-  return "<unknown>";
-}
-
-std::ostream& operator<<(std::ostream& out, BuiltinType i) {
-  out << str(i);
-  return out;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/builtin_type.h b/src/sem/builtin_type.h
deleted file mode 100644
index 3055fb7..0000000
--- a/src/sem/builtin_type.h
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/builtin-gen
-// using the template:
-//   src/sem/builtin_type.h.tmpl
-// and the builtin defintion file:
-//   src/builtins.def
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#ifndef SRC_SEM_BUILTIN_TYPE_H_
-#define SRC_SEM_BUILTIN_TYPE_H_
-
-#include <sstream>
-#include <string>
-
-namespace tint {
-namespace sem {
-
-/// Enumerator of all builtin functions
-enum class BuiltinType {
-  kNone = -1,
-  kAbs,
-  kAcos,
-  kAll,
-  kAny,
-  kArrayLength,
-  kAsin,
-  kAtan,
-  kAtan2,
-  kCeil,
-  kClamp,
-  kCos,
-  kCosh,
-  kCountOneBits,
-  kCross,
-  kDegrees,
-  kDeterminant,
-  kDistance,
-  kDot,
-  kDpdx,
-  kDpdxCoarse,
-  kDpdxFine,
-  kDpdy,
-  kDpdyCoarse,
-  kDpdyFine,
-  kExp,
-  kExp2,
-  kFaceForward,
-  kFloor,
-  kFma,
-  kFract,
-  kFrexp,
-  kFwidth,
-  kFwidthCoarse,
-  kFwidthFine,
-  kInverseSqrt,
-  kIsFinite,
-  kIsInf,
-  kIsNan,
-  kIsNormal,
-  kLdexp,
-  kLength,
-  kLog,
-  kLog2,
-  kMax,
-  kMin,
-  kMix,
-  kModf,
-  kNormalize,
-  kPack2x16float,
-  kPack2x16snorm,
-  kPack2x16unorm,
-  kPack4x8snorm,
-  kPack4x8unorm,
-  kPow,
-  kRadians,
-  kReflect,
-  kRefract,
-  kReverseBits,
-  kRound,
-  kSelect,
-  kSign,
-  kSin,
-  kSinh,
-  kSmoothStep,
-  kSqrt,
-  kStep,
-  kStorageBarrier,
-  kTan,
-  kTanh,
-  kTranspose,
-  kTrunc,
-  kUnpack2x16float,
-  kUnpack2x16snorm,
-  kUnpack2x16unorm,
-  kUnpack4x8snorm,
-  kUnpack4x8unorm,
-  kWorkgroupBarrier,
-  kTextureDimensions,
-  kTextureGather,
-  kTextureGatherCompare,
-  kTextureNumLayers,
-  kTextureNumLevels,
-  kTextureNumSamples,
-  kTextureSample,
-  kTextureSampleBias,
-  kTextureSampleCompare,
-  kTextureSampleCompareLevel,
-  kTextureSampleGrad,
-  kTextureSampleLevel,
-  kTextureStore,
-  kTextureLoad,
-  kAtomicLoad,
-  kAtomicStore,
-  kAtomicAdd,
-  kAtomicSub,
-  kAtomicMax,
-  kAtomicMin,
-  kAtomicAnd,
-  kAtomicOr,
-  kAtomicXor,
-  kAtomicExchange,
-  kAtomicCompareExchangeWeak,
-};
-
-/// Matches the BuiltinType by name
-/// @param name the builtin name to parse
-/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
-/// match any builtin.
-BuiltinType ParseBuiltinType(const std::string& name);
-
-/// @returns the name of the builtin function type. The spelling, including
-/// case, matches the name in the WGSL spec.
-const char* str(BuiltinType i);
-
-/// Emits the name of the builtin function type. The spelling, including case,
-/// matches the name in the WGSL spec.
-std::ostream& operator<<(std::ostream& out, BuiltinType i);
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_BUILTIN_TYPE_H_
diff --git a/src/sem/builtin_type.h.tmpl b/src/sem/builtin_type.h.tmpl
deleted file mode 100644
index 5610be3..0000000
--- a/src/sem/builtin_type.h.tmpl
+++ /dev/null
@@ -1,45 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/builtin-gen to generate builtin_type.h
-
-See:
-* tools/cmd/builtin-gen/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#ifndef SRC_SEM_BUILTIN_TYPE_H_
-#define SRC_SEM_BUILTIN_TYPE_H_
-
-#include <sstream>
-#include <string>
-
-namespace tint {
-namespace sem {
-
-/// Enumerator of all builtin functions
-enum class BuiltinType {
-  kNone = -1,
-{{- range .Sem.Functions }}
-  k{{Title .Name}},
-{{- end }}
-};
-
-/// Matches the BuiltinType by name
-/// @param name the builtin name to parse
-/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
-/// match any builtin.
-BuiltinType ParseBuiltinType(const std::string& name);
-
-/// @returns the name of the builtin function type. The spelling, including
-/// case, matches the name in the WGSL spec.
-const char* str(BuiltinType i);
-
-/// Emits the name of the builtin function type. The spelling, including case,
-/// matches the name in the WGSL spec.
-std::ostream& operator<<(std::ostream& out, BuiltinType i);
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_BUILTIN_TYPE_H_
diff --git a/src/sem/call.cc b/src/sem/call.cc
deleted file mode 100644
index 63297a0..0000000
--- a/src/sem/call.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/call.h"
-
-#include <utility>
-#include <vector>
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Call);
-
-namespace tint {
-namespace sem {
-
-Call::Call(const ast::CallExpression* declaration,
-           const CallTarget* target,
-           std::vector<const sem::Expression*> arguments,
-           const Statement* statement,
-           Constant constant,
-           bool has_side_effects)
-    : Base(declaration,
-           target->ReturnType(),
-           statement,
-           std::move(constant),
-           has_side_effects),
-      target_(target),
-      arguments_(std::move(arguments)) {}
-
-Call::~Call() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/call.h b/src/sem/call.h
deleted file mode 100644
index 00c9467..0000000
--- a/src/sem/call.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_CALL_H_
-#define SRC_SEM_CALL_H_
-
-#include <vector>
-
-#include "src/sem/builtin.h"
-#include "src/sem/expression.h"
-
-namespace tint {
-namespace sem {
-
-/// Call is the base class for semantic nodes that hold semantic information for
-/// ast::CallExpression nodes.
-class Call : public Castable<Call, Expression> {
- public:
-  /// Constructor
-  /// @param declaration the AST node
-  /// @param target the call target
-  /// @param arguments the call arguments
-  /// @param statement the statement that owns this expression
-  /// @param constant the constant value of this expression
-  /// @param has_side_effects whether this expression may have side effects
-  Call(const ast::CallExpression* declaration,
-       const CallTarget* target,
-       std::vector<const sem::Expression*> arguments,
-       const Statement* statement,
-       Constant constant,
-       bool has_side_effects);
-
-  /// Destructor
-  ~Call() override;
-
-  /// @return the target of the call
-  const CallTarget* Target() const { return target_; }
-
-  /// @return the call arguments
-  const std::vector<const sem::Expression*>& Arguments() const {
-    return arguments_;
-  }
-
-  /// @returns the AST node
-  const ast::CallExpression* Declaration() const {
-    return static_cast<const ast::CallExpression*>(declaration_);
-  }
-
- private:
-  CallTarget const* const target_;
-  std::vector<const sem::Expression*> arguments_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_CALL_H_
diff --git a/src/sem/call_target.cc b/src/sem/call_target.cc
deleted file mode 100644
index b2eb89e..0000000
--- a/src/sem/call_target.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/call_target.h"
-
-#include "src/symbol_table.h"
-#include "src/utils/hash.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::CallTarget);
-
-namespace tint {
-namespace sem {
-
-CallTarget::CallTarget(const sem::Type* return_type,
-                       const ParameterList& parameters)
-    : signature_{return_type, parameters} {
-  TINT_ASSERT(Semantic, return_type);
-}
-
-CallTarget::CallTarget(const CallTarget&) = default;
-CallTarget::~CallTarget() = default;
-
-CallTargetSignature::CallTargetSignature(const sem::Type* ret_ty,
-                                         const ParameterList& params)
-    : return_type(ret_ty), parameters(params) {}
-CallTargetSignature::CallTargetSignature(const CallTargetSignature&) = default;
-CallTargetSignature::~CallTargetSignature() = default;
-
-int CallTargetSignature::IndexOf(ParameterUsage usage) const {
-  for (size_t i = 0; i < parameters.size(); i++) {
-    if (parameters[i]->Usage() == usage) {
-      return static_cast<int>(i);
-    }
-  }
-  return -1;
-}
-
-bool CallTargetSignature::operator==(const CallTargetSignature& other) const {
-  if (return_type != other.return_type ||
-      parameters.size() != other.parameters.size()) {
-    return false;
-  }
-  for (size_t i = 0; i < parameters.size(); i++) {
-    auto* a = parameters[i];
-    auto* b = other.parameters[i];
-    if (a->Type() != b->Type() || a->Usage() != b->Usage()) {
-      return false;
-    }
-  }
-  return true;
-}
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-std::size_t hash<tint::sem::CallTargetSignature>::operator()(
-    const tint::sem::CallTargetSignature& sig) const {
-  size_t hash = tint::utils::Hash(sig.parameters.size());
-  for (auto* p : sig.parameters) {
-    tint::utils::HashCombine(&hash, p->Type(), p->Usage());
-  }
-  return tint::utils::Hash(hash, sig.return_type);
-}
-
-}  // namespace std
diff --git a/src/sem/call_target.h b/src/sem/call_target.h
deleted file mode 100644
index ea8bdcb..0000000
--- a/src/sem/call_target.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_CALL_TARGET_H_
-#define SRC_SEM_CALL_TARGET_H_
-
-#include <vector>
-
-#include "src/sem/node.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/variable.h"
-#include "src/utils/hash.h"
-
-namespace tint {
-namespace sem {
-// Forward declarations
-class Type;
-
-/// CallTargetSignature holds the return type and parameters for a call target
-struct CallTargetSignature {
-  /// Constructor
-  /// @param ret_ty the call target return type
-  /// @param params the call target parameters
-  CallTargetSignature(const sem::Type* ret_ty, const ParameterList& params);
-
-  /// Copy constructor
-  CallTargetSignature(const CallTargetSignature&);
-
-  /// Destructor
-  ~CallTargetSignature();
-
-  /// The type of the call target return value
-  const sem::Type* const return_type = nullptr;
-  /// The parameters of the call target
-  const ParameterList parameters;
-
-  /// Equality operator
-  /// @param other the signature to compare this to
-  /// @returns true if this signature is equal to other
-  bool operator==(const CallTargetSignature& other) const;
-
-  /// @param usage the parameter usage to find
-  /// @returns the index of the parameter with the given usage, or -1 if no
-  /// parameter with the given usage exists.
-  int IndexOf(ParameterUsage usage) const;
-};
-
-/// CallTarget is the base for callable functions, builtins, type constructors
-/// and type casts.
-class CallTarget : public Castable<CallTarget, Node> {
- public:
-  /// Constructor
-  /// @param return_type the return type of the call target
-  /// @param parameters the parameters for the call target
-  CallTarget(const sem::Type* return_type, const ParameterList& parameters);
-
-  /// Copy constructor
-  CallTarget(const CallTarget&);
-
-  /// Destructor
-  ~CallTarget() override;
-
-  /// @return the return type of the call target
-  const sem::Type* ReturnType() const { return signature_.return_type; }
-
-  /// @return the parameters of the call target
-  const ParameterList& Parameters() const { return signature_.parameters; }
-
-  /// @return the signature of the call target
-  const CallTargetSignature& Signature() const { return signature_; }
-
- private:
-  CallTargetSignature signature_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::CallTargetSignature so
-/// CallTargetSignature can be used as keys for std::unordered_map and
-/// std::unordered_set.
-template <>
-class hash<tint::sem::CallTargetSignature> {
- public:
-  /// @param sig the CallTargetSignature to hash
-  /// @return the hash value
-  std::size_t operator()(const tint::sem::CallTargetSignature& sig) const;
-};
-
-}  // namespace std
-
-#endif  // SRC_SEM_CALL_TARGET_H_
diff --git a/src/sem/constant.cc b/src/sem/constant.cc
deleted file mode 100644
index df2e67e..0000000
--- a/src/sem/constant.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/constant.h"
-
-#include <functional>
-#include <utility>
-
-#include "src/debug.h"
-#include "src/program_builder.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-namespace {
-
-const Type* ElemType(const Type* ty, size_t num_elements) {
-  diag::List diag;
-  if (ty->is_scalar()) {
-    if (num_elements != 1) {
-      TINT_ICE(Semantic, diag)
-          << "sem::Constant() type <-> num_element mismatch. type: '"
-          << ty->type_name() << "' num_elements: " << num_elements;
-    }
-    return ty;
-  }
-  if (auto* vec = ty->As<Vector>()) {
-    if (num_elements != vec->Width()) {
-      TINT_ICE(Semantic, diag)
-          << "sem::Constant() type <-> num_element mismatch. type: '"
-          << ty->type_name() << "' num_elements: " << num_elements;
-    }
-    TINT_ASSERT(Semantic, vec->type()->is_scalar());
-    return vec->type();
-  }
-  TINT_UNREACHABLE(Semantic, diag) << "Unsupported sem::Constant type";
-  return nullptr;
-}
-
-}  // namespace
-
-Constant::Constant() {}
-
-Constant::Constant(const sem::Type* ty, Scalars els)
-    : type_(ty), elem_type_(ElemType(ty, els.size())), elems_(std::move(els)) {}
-
-Constant::Constant(const Constant&) = default;
-
-Constant::~Constant() = default;
-
-Constant& Constant::operator=(const Constant& rhs) = default;
-
-bool Constant::AnyZero() const {
-  for (size_t i = 0; i < Elements().size(); ++i) {
-    if (WithScalarAt(i, [&](auto&& s) {
-          // Use std::equal_to to work around -Wfloat-equal warnings
-          auto equals_to =
-              std::equal_to<std::remove_reference_t<decltype(s)>>{};
-
-          if (equals_to(s, 0)) {
-            return true;
-          }
-          return false;
-        })) {
-      return true;
-    }
-  }
-  return false;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/constant.h b/src/sem/constant.h
deleted file mode 100644
index ebd97a0..0000000
--- a/src/sem/constant.h
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_CONSTANT_H_
-#define SRC_SEM_CONSTANT_H_
-
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A Constant is compile-time known expression value, expressed as a flattened
-/// list of scalar values. Value may be of a scalar or vector type.
-class Constant {
-  using i32 = ProgramBuilder::i32;
-  using u32 = ProgramBuilder::u32;
-  using f32 = ProgramBuilder::f32;
-
- public:
-  /// Scalar holds a single constant scalar value, as a union of an i32, u32,
-  /// f32 or boolean.
-  union Scalar {
-    /// The scalar value as a i32
-    int32_t i32;
-    /// The scalar value as a u32
-    uint32_t u32;
-    /// The scalar value as a f32
-    float f32;
-    /// The scalar value as a bool
-    bool bool_;
-
-    /// Constructs the scalar with the i32 value `v`
-    /// @param v the value of the Scalar
-    Scalar(ProgramBuilder::i32 v) : i32(v) {}  // NOLINT
-
-    /// Constructs the scalar with the u32 value `v`
-    /// @param v the value of the Scalar
-    Scalar(ProgramBuilder::u32 v) : u32(v) {}  // NOLINT
-
-    /// Constructs the scalar with the f32 value `v`
-    /// @param v the value of the Scalar
-    Scalar(ProgramBuilder::f32 v) : f32(v) {}  // NOLINT
-
-    /// Constructs the scalar with the bool value `v`
-    /// @param v the value of the Scalar
-    Scalar(bool v) : bool_(v) {}  // NOLINT
-  };
-
-  /// Scalars is a list of scalar values
-  using Scalars = std::vector<Scalar>;
-
-  /// Constructs an invalid Constant
-  Constant();
-
-  /// Constructs a Constant of the given type and element values
-  /// @param ty the Constant type
-  /// @param els the Constant element values
-  Constant(const Type* ty, Scalars els);
-
-  /// Copy constructor
-  Constant(const Constant&);
-
-  /// Destructor
-  ~Constant();
-
-  /// Copy assignment
-  /// @param other the Constant to copy
-  /// @returns this Constant
-  Constant& operator=(const Constant& other);
-
-  /// @returns true if the Constant has been initialized
-  bool IsValid() const { return type_ != nullptr; }
-
-  /// @return true if the Constant has been initialized
-  operator bool() const { return IsValid(); }
-
-  /// @returns the type of the Constant
-  const sem::Type* Type() const { return type_; }
-
-  /// @returns the element type of the Constant
-  const sem::Type* ElementType() const { return elem_type_; }
-
-  /// @returns the constant's scalar elements
-  const Scalars& Elements() const { return elems_; }
-
-  /// @returns true if any scalar element is zero
-  bool AnyZero() const;
-
-  /// Calls `func(s)` with s being the current scalar value at `index`.
-  /// `func` is typically a lambda of the form '[](auto&& s)'.
-  /// @param index the index of the scalar value
-  /// @param func a function with signature `T(S)`
-  /// @return the value returned by func.
-  template <typename Func>
-  auto WithScalarAt(size_t index, Func&& func) const {
-    auto* elem_type = ElementType();
-    if (elem_type->Is<I32>()) {
-      return func(elems_[index].i32);
-    }
-    if (elem_type->Is<U32>()) {
-      return func(elems_[index].u32);
-    }
-    if (elem_type->Is<F32>()) {
-      return func(elems_[index].f32);
-    }
-    if (elem_type->Is<Bool>()) {
-      return func(elems_[index].bool_);
-    }
-    diag::List diags;
-    TINT_UNREACHABLE(Semantic, diags)
-        << "invalid scalar type " << type_->type_name();
-    return func(~0);
-  }
-
-  /// @param index the index of the scalar value
-  /// @return the value of the scalar `static_cast` to type T.
-  template <typename T>
-  T ElementAs(size_t index) const {
-    return WithScalarAt(index, [](auto val) { return static_cast<T>(val); });
-  }
-
- private:
-  const sem::Type* type_ = nullptr;
-  const sem::Type* elem_type_ = nullptr;
-  Scalars elems_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_CONSTANT_H_
diff --git a/src/sem/depth_multisampled_texture_type.cc b/src/sem/depth_multisampled_texture_type.cc
deleted file mode 100644
index 6f11f1a..0000000
--- a/src/sem/depth_multisampled_texture_type.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/depth_multisampled_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::DepthMultisampledTexture);
-
-namespace tint {
-namespace sem {
-namespace {
-
-bool IsValidDepthDimension(ast::TextureDimension dim) {
-  return dim == ast::TextureDimension::k2d;
-}
-
-}  // namespace
-
-DepthMultisampledTexture::DepthMultisampledTexture(ast::TextureDimension dim)
-    : Base(dim) {
-  TINT_ASSERT(Semantic, IsValidDepthDimension(dim));
-}
-
-DepthMultisampledTexture::DepthMultisampledTexture(DepthMultisampledTexture&&) =
-    default;
-
-DepthMultisampledTexture::~DepthMultisampledTexture() = default;
-
-std::string DepthMultisampledTexture::type_name() const {
-  std::ostringstream out;
-  out << "__depth_multisampled_texture_" << dim();
-  return out.str();
-}
-
-std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_depth_multisampled_" << dim();
-  return out.str();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/depth_multisampled_texture_type.h b/src/sem/depth_multisampled_texture_type.h
deleted file mode 100644
index f9570d2..0000000
--- a/src/sem/depth_multisampled_texture_type.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
-#define SRC_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-/// A multisampled depth texture type.
-class DepthMultisampledTexture
-    : public Castable<DepthMultisampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  explicit DepthMultisampledTexture(ast::TextureDimension dim);
-  /// Move constructor
-  DepthMultisampledTexture(DepthMultisampledTexture&&);
-  ~DepthMultisampledTexture() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
diff --git a/src/sem/depth_multisampled_texture_type_test.cc b/src/sem/depth_multisampled_texture_type_test.cc
deleted file mode 100644
index 7881a33..0000000
--- a/src/sem/depth_multisampled_texture_type_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/depth_multisampled_texture_type.h"
-
-#include "src/sem/test_helper.h"
-
-#include "src/sem/external_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using DepthMultisampledTextureTest = TestHelper;
-
-TEST_F(DepthMultisampledTextureTest, Dim) {
-  DepthMultisampledTexture d(ast::TextureDimension::k2d);
-  EXPECT_EQ(d.dim(), ast::TextureDimension::k2d);
-}
-
-TEST_F(DepthMultisampledTextureTest, TypeName) {
-  DepthMultisampledTexture d(ast::TextureDimension::k2d);
-  EXPECT_EQ(d.type_name(), "__depth_multisampled_texture_2d");
-}
-
-TEST_F(DepthMultisampledTextureTest, FriendlyName) {
-  DepthMultisampledTexture d(ast::TextureDimension::k2d);
-  EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_multisampled_2d");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/depth_texture_type.cc b/src/sem/depth_texture_type.cc
deleted file mode 100644
index f9cd3ec..0000000
--- a/src/sem/depth_texture_type.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/depth_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::DepthTexture);
-
-namespace tint {
-namespace sem {
-namespace {
-
-bool IsValidDepthDimension(ast::TextureDimension dim) {
-  return dim == ast::TextureDimension::k2d ||
-         dim == ast::TextureDimension::k2dArray ||
-         dim == ast::TextureDimension::kCube ||
-         dim == ast::TextureDimension::kCubeArray;
-}
-
-}  // namespace
-
-DepthTexture::DepthTexture(ast::TextureDimension dim) : Base(dim) {
-  TINT_ASSERT(Semantic, IsValidDepthDimension(dim));
-}
-
-DepthTexture::DepthTexture(DepthTexture&&) = default;
-
-DepthTexture::~DepthTexture() = default;
-
-std::string DepthTexture::type_name() const {
-  std::ostringstream out;
-  out << "__depth_texture_" << dim();
-  return out.str();
-}
-
-std::string DepthTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_depth_" << dim();
-  return out.str();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/depth_texture_type.h b/src/sem/depth_texture_type.h
deleted file mode 100644
index 1704ad9..0000000
--- a/src/sem/depth_texture_type.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_DEPTH_TEXTURE_TYPE_H_
-#define SRC_SEM_DEPTH_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-/// A depth texture type.
-class DepthTexture : public Castable<DepthTexture, Texture> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  explicit DepthTexture(ast::TextureDimension dim);
-  /// Move constructor
-  DepthTexture(DepthTexture&&);
-  ~DepthTexture() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_DEPTH_TEXTURE_TYPE_H_
diff --git a/src/sem/depth_texture_type_test.cc b/src/sem/depth_texture_type_test.cc
deleted file mode 100644
index c04507e..0000000
--- a/src/sem/depth_texture_type_test.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/depth_texture_type.h"
-
-#include "src/sem/test_helper.h"
-
-#include "src/sem/external_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using DepthTextureTest = TestHelper;
-
-TEST_F(DepthTextureTest, IsTexture) {
-  DepthTexture d(ast::TextureDimension::kCube);
-  Texture* ty = &d;
-  EXPECT_TRUE(ty->Is<DepthTexture>());
-  EXPECT_FALSE(ty->Is<ExternalTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(DepthTextureTest, Dim) {
-  DepthTexture d(ast::TextureDimension::kCube);
-  EXPECT_EQ(d.dim(), ast::TextureDimension::kCube);
-}
-
-TEST_F(DepthTextureTest, TypeName) {
-  DepthTexture d(ast::TextureDimension::kCube);
-  EXPECT_EQ(d.type_name(), "__depth_texture_cube");
-}
-
-TEST_F(DepthTextureTest, FriendlyName) {
-  DepthTexture d(ast::TextureDimension::kCube);
-  EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_cube");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/expression.cc b/src/sem/expression.cc
deleted file mode 100644
index 95a2e40..0000000
--- a/src/sem/expression.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/expression.h"
-
-#include <utility>
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
-
-namespace tint {
-namespace sem {
-
-Expression::Expression(const ast::Expression* declaration,
-                       const sem::Type* type,
-                       const Statement* statement,
-                       Constant constant,
-                       bool has_side_effects)
-    : declaration_(declaration),
-      type_(type),
-      statement_(statement),
-      constant_(std::move(constant)),
-      has_side_effects_(has_side_effects) {
-  TINT_ASSERT(Semantic, type_);
-}
-
-Expression::~Expression() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/expression.h b/src/sem/expression.h
deleted file mode 100644
index a3b0d38..0000000
--- a/src/sem/expression.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_EXPRESSION_H_
-#define SRC_SEM_EXPRESSION_H_
-
-#include "src/ast/expression.h"
-#include "src/sem/behavior.h"
-#include "src/sem/constant.h"
-#include "src/sem/node.h"
-
-namespace tint {
-namespace sem {
-// Forward declarations
-class Statement;
-class Type;
-
-/// Expression holds the semantic information for expression nodes.
-class Expression : public Castable<Expression, Node> {
- public:
-  /// Constructor
-  /// @param declaration the AST node
-  /// @param type the resolved type of the expression
-  /// @param statement the statement that owns this expression
-  /// @param constant the constant value of the expression. May be invalid
-  /// @param has_side_effects true if this expression may have side-effects
-  Expression(const ast::Expression* declaration,
-             const sem::Type* type,
-             const Statement* statement,
-             Constant constant,
-             bool has_side_effects);
-
-  /// Destructor
-  ~Expression() override;
-
-  /// @returns the AST node
-  const ast::Expression* Declaration() const { return declaration_; }
-
-  /// @return the resolved type of the expression
-  const sem::Type* Type() const { return type_; }
-
-  /// @return the statement that owns this expression
-  const Statement* Stmt() const { return statement_; }
-
-  /// @return the constant value of this expression
-  const Constant& ConstantValue() const { return constant_; }
-
-  /// @return the behaviors of this statement
-  const sem::Behaviors& Behaviors() const { return behaviors_; }
-
-  /// @return the behaviors of this statement
-  sem::Behaviors& Behaviors() { return behaviors_; }
-
-  /// @return true of this expression may have side effects
-  bool HasSideEffects() const { return has_side_effects_; }
-
- protected:
-  /// The AST expression node for this semantic expression
-  const ast::Expression* const declaration_;
-
- private:
-  const sem::Type* const type_;
-  const Statement* const statement_;
-  const Constant constant_;
-  sem::Behaviors behaviors_{sem::Behavior::kNext};
-  const bool has_side_effects_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_EXPRESSION_H_
diff --git a/src/sem/external_texture_type.cc b/src/sem/external_texture_type.cc
deleted file mode 100644
index 5152fc1..0000000
--- a/src/sem/external_texture_type.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/external_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ExternalTexture);
-
-namespace tint {
-namespace sem {
-
-ExternalTexture::ExternalTexture() : Base(ast::TextureDimension::k2d) {}
-
-ExternalTexture::ExternalTexture(ExternalTexture&&) = default;
-
-ExternalTexture::~ExternalTexture() = default;
-
-std::string ExternalTexture::type_name() const {
-  return "__external_texture";
-}
-
-std::string ExternalTexture::FriendlyName(const SymbolTable&) const {
-  return "texture_external";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/external_texture_type.h b/src/sem/external_texture_type.h
deleted file mode 100644
index 9b2fbcc..0000000
--- a/src/sem/external_texture_type.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_EXTERNAL_TEXTURE_TYPE_H_
-#define SRC_SEM_EXTERNAL_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-/// An external texture type
-class ExternalTexture : public Castable<ExternalTexture, Texture> {
- public:
-  /// Constructor
-  ExternalTexture();
-
-  /// Move constructor
-  ExternalTexture(ExternalTexture&&);
-  ~ExternalTexture() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_EXTERNAL_TEXTURE_TYPE_H_
diff --git a/src/sem/external_texture_type_test.cc b/src/sem/external_texture_type_test.cc
deleted file mode 100644
index ca15ea5..0000000
--- a/src/sem/external_texture_type_test.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/external_texture_type.h"
-
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using ExternalTextureTest = TestHelper;
-
-TEST_F(ExternalTextureTest, IsTexture) {
-  F32 f32;
-  ExternalTexture s;
-  Texture* ty = &s;
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_TRUE(ty->Is<ExternalTexture>());
-  EXPECT_FALSE(ty->Is<MultisampledTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(ExternalTextureTest, Dim) {
-  F32 f32;
-  ExternalTexture s;
-  EXPECT_EQ(s.dim(), ast::TextureDimension::k2d);
-}
-
-TEST_F(ExternalTextureTest, TypeName) {
-  F32 f32;
-  ExternalTexture s;
-  EXPECT_EQ(s.type_name(), "__external_texture");
-}
-
-TEST_F(ExternalTextureTest, FriendlyName) {
-  ExternalTexture s;
-  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_external");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/f32_type.cc b/src/sem/f32_type.cc
deleted file mode 100644
index 3935fe3a..0000000
--- a/src/sem/f32_type.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/f32_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::F32);
-
-namespace tint {
-namespace sem {
-
-F32::F32() = default;
-
-F32::F32(F32&&) = default;
-
-F32::~F32() = default;
-
-std::string F32::type_name() const {
-  return "__f32";
-}
-
-std::string F32::FriendlyName(const SymbolTable&) const {
-  return "f32";
-}
-
-bool F32::IsConstructible() const {
-  return true;
-}
-
-uint32_t F32::Size() const {
-  return 4;
-}
-
-uint32_t F32::Align() const {
-  return 4;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/f32_type.h b/src/sem/f32_type.h
deleted file mode 100644
index 16fe521..0000000
--- a/src/sem/f32_type.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_F32_TYPE_H_
-#define SRC_SEM_F32_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A float 32 type
-class F32 : public Castable<F32, Type> {
- public:
-  /// Constructor
-  F32();
-  /// Move constructor
-  F32(F32&&);
-  ~F32() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the size in bytes of the type.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type.
-  uint32_t Align() const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_F32_TYPE_H_
diff --git a/src/sem/f32_type_test.cc b/src/sem/f32_type_test.cc
deleted file mode 100644
index 88fe3db..0000000
--- a/src/sem/f32_type_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using F32Test = TestHelper;
-
-TEST_F(F32Test, TypeName) {
-  F32 f;
-  EXPECT_EQ(f.type_name(), "__f32");
-}
-
-TEST_F(F32Test, FriendlyName) {
-  F32 f;
-  EXPECT_EQ(f.FriendlyName(Symbols()), "f32");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/for_loop_statement.cc b/src/sem/for_loop_statement.cc
deleted file mode 100644
index 72a1226..0000000
--- a/src/sem/for_loop_statement.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/for_loop_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ForLoopStatement);
-
-namespace tint {
-namespace sem {
-
-ForLoopStatement::ForLoopStatement(const ast::ForLoopStatement* declaration,
-                                   const CompoundStatement* parent,
-                                   const sem::Function* function)
-    : Base(declaration, parent, function) {}
-
-ForLoopStatement::~ForLoopStatement() = default;
-
-const ast::ForLoopStatement* ForLoopStatement::Declaration() const {
-  return static_cast<const ast::ForLoopStatement*>(Base::Declaration());
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/for_loop_statement.h b/src/sem/for_loop_statement.h
deleted file mode 100644
index fad879a..0000000
--- a/src/sem/for_loop_statement.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_FOR_LOOP_STATEMENT_H_
-#define SRC_SEM_FOR_LOOP_STATEMENT_H_
-
-#include "src/sem/statement.h"
-
-namespace tint {
-namespace ast {
-class ForLoopStatement;
-}  // namespace ast
-namespace sem {
-class Expression;
-}  // namespace sem
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Holds semantic information about a for-loop statement
-class ForLoopStatement : public Castable<ForLoopStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this for-loop statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  ForLoopStatement(const ast::ForLoopStatement* declaration,
-                   const CompoundStatement* parent,
-                   const sem::Function* function);
-
-  /// Destructor
-  ~ForLoopStatement() override;
-
-  /// @returns the AST node
-  const ast::ForLoopStatement* Declaration() const;
-
-  /// @returns the for-loop condition expression
-  const Expression* Condition() const { return condition_; }
-
-  /// Sets the for-loop condition expression
-  /// @param condition the for-loop condition expression
-  void SetCondition(const Expression* condition) { condition_ = condition; }
-
- private:
-  const Expression* condition_ = nullptr;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_FOR_LOOP_STATEMENT_H_
diff --git a/src/sem/function.cc b/src/sem/function.cc
deleted file mode 100644
index cd2d1d9..0000000
--- a/src/sem/function.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/function.h"
-
-#include "src/ast/function.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/variable.h"
-#include "src/utils/to_const_ptr_vec.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Function);
-
-namespace tint {
-namespace sem {
-
-Function::Function(const ast::Function* declaration,
-                   Type* return_type,
-                   std::vector<Parameter*> parameters)
-    : Base(return_type, utils::ToConstPtrVec(parameters)),
-      declaration_(declaration),
-      workgroup_size_{WorkgroupDimension{1}, WorkgroupDimension{1},
-                      WorkgroupDimension{1}} {
-  for (auto* parameter : parameters) {
-    parameter->SetOwner(this);
-  }
-}  // namespace sem
-
-Function::~Function() = default;
-
-std::vector<std::pair<const Variable*, const ast::LocationAttribute*>>
-Function::TransitivelyReferencedLocationVariables() const {
-  std::vector<std::pair<const Variable*, const ast::LocationAttribute*>> ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    for (auto* attr : var->Declaration()->attributes) {
-      if (auto* location = attr->As<ast::LocationAttribute>()) {
-        ret.push_back({var, location});
-        break;
-      }
-    }
-  }
-  return ret;
-}
-
-Function::VariableBindings Function::TransitivelyReferencedUniformVariables()
-    const {
-  VariableBindings ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    if (var->StorageClass() != ast::StorageClass::kUniform) {
-      continue;
-    }
-
-    if (auto binding_point = var->Declaration()->BindingPoint()) {
-      ret.push_back({var, binding_point});
-    }
-  }
-  return ret;
-}
-
-Function::VariableBindings
-Function::TransitivelyReferencedStorageBufferVariables() const {
-  VariableBindings ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    if (var->StorageClass() != ast::StorageClass::kStorage) {
-      continue;
-    }
-
-    if (auto binding_point = var->Declaration()->BindingPoint()) {
-      ret.push_back({var, binding_point});
-    }
-  }
-  return ret;
-}
-
-std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>>
-Function::TransitivelyReferencedBuiltinVariables() const {
-  std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>> ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    for (auto* attr : var->Declaration()->attributes) {
-      if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-        ret.push_back({var, builtin});
-        break;
-      }
-    }
-  }
-  return ret;
-}
-
-Function::VariableBindings Function::TransitivelyReferencedSamplerVariables()
-    const {
-  return TransitivelyReferencedSamplerVariablesImpl(ast::SamplerKind::kSampler);
-}
-
-Function::VariableBindings
-Function::TransitivelyReferencedComparisonSamplerVariables() const {
-  return TransitivelyReferencedSamplerVariablesImpl(
-      ast::SamplerKind::kComparisonSampler);
-}
-
-Function::VariableBindings
-Function::TransitivelyReferencedSampledTextureVariables() const {
-  return TransitivelyReferencedSampledTextureVariablesImpl(false);
-}
-
-Function::VariableBindings
-Function::TransitivelyReferencedMultisampledTextureVariables() const {
-  return TransitivelyReferencedSampledTextureVariablesImpl(true);
-}
-
-Function::VariableBindings Function::TransitivelyReferencedVariablesOfType(
-    const tint::TypeInfo* type) const {
-  VariableBindings ret;
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    auto* unwrapped_type = var->Type()->UnwrapRef();
-    if (unwrapped_type->TypeInfo().Is(type)) {
-      if (auto binding_point = var->Declaration()->BindingPoint()) {
-        ret.push_back({var, binding_point});
-      }
-    }
-  }
-  return ret;
-}
-
-bool Function::HasAncestorEntryPoint(Symbol symbol) const {
-  for (const auto* point : ancestor_entry_points_) {
-    if (point->Declaration()->symbol == symbol) {
-      return true;
-    }
-  }
-  return false;
-}
-
-Function::VariableBindings Function::TransitivelyReferencedSamplerVariablesImpl(
-    ast::SamplerKind kind) const {
-  VariableBindings ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    auto* unwrapped_type = var->Type()->UnwrapRef();
-    auto* sampler = unwrapped_type->As<sem::Sampler>();
-    if (sampler == nullptr || sampler->kind() != kind) {
-      continue;
-    }
-
-    if (auto binding_point = var->Declaration()->BindingPoint()) {
-      ret.push_back({var, binding_point});
-    }
-  }
-  return ret;
-}
-
-Function::VariableBindings
-Function::TransitivelyReferencedSampledTextureVariablesImpl(
-    bool multisampled) const {
-  VariableBindings ret;
-
-  for (auto* var : TransitivelyReferencedGlobals()) {
-    auto* unwrapped_type = var->Type()->UnwrapRef();
-    auto* texture = unwrapped_type->As<sem::Texture>();
-    if (texture == nullptr) {
-      continue;
-    }
-
-    auto is_multisampled = texture->Is<sem::MultisampledTexture>();
-    auto is_sampled = texture->Is<sem::SampledTexture>();
-
-    if ((multisampled && !is_multisampled) || (!multisampled && !is_sampled)) {
-      continue;
-    }
-
-    if (auto binding_point = var->Declaration()->BindingPoint()) {
-      ret.push_back({var, binding_point});
-    }
-  }
-
-  return ret;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/function.h b/src/sem/function.h
deleted file mode 100644
index 62a591b..0000000
--- a/src/sem/function.h
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_FUNCTION_H_
-#define SRC_SEM_FUNCTION_H_
-
-#include <array>
-#include <utility>
-#include <vector>
-
-#include "src/ast/variable.h"
-#include "src/sem/call.h"
-#include "src/utils/unique_vector.h"
-
-namespace tint {
-
-// Forward declarations
-namespace ast {
-class BuiltinAttribute;
-class Function;
-class LocationAttribute;
-class ReturnStatement;
-}  // namespace ast
-
-namespace sem {
-
-class Builtin;
-class Variable;
-
-/// WorkgroupDimension describes the size of a single dimension of an entry
-/// point's workgroup size.
-struct WorkgroupDimension {
-  /// The size of this dimension.
-  uint32_t value;
-  /// A pipeline-overridable constant that overrides the size, or nullptr if
-  /// this dimension is not overridable.
-  const ast::Variable* overridable_const = nullptr;
-};
-
-/// WorkgroupSize is a three-dimensional array of WorkgroupDimensions.
-using WorkgroupSize = std::array<WorkgroupDimension, 3>;
-
-/// Function holds the semantic information for function nodes.
-class Function : public Castable<Function, CallTarget> {
- public:
-  /// A vector of [Variable*, ast::VariableBindingPoint] pairs
-  using VariableBindings =
-      std::vector<std::pair<const Variable*, ast::VariableBindingPoint>>;
-
-  /// Constructor
-  /// @param declaration the ast::Function
-  /// @param return_type the return type of the function
-  /// @param parameters the parameters to the function
-  Function(const ast::Function* declaration,
-           Type* return_type,
-           std::vector<Parameter*> parameters);
-
-  /// Destructor
-  ~Function() override;
-
-  /// @returns the ast::Function declaration
-  const ast::Function* Declaration() const { return declaration_; }
-
-  /// @returns the workgroup size {x, y, z} for the function.
-  const sem::WorkgroupSize& WorkgroupSize() const { return workgroup_size_; }
-
-  /// Sets the workgroup size {x, y, z} for the function.
-  /// @param workgroup_size the new workgroup size of the function
-  void SetWorkgroupSize(sem::WorkgroupSize workgroup_size) {
-    workgroup_size_ = std::move(workgroup_size);
-  }
-
-  /// @returns all directly referenced global variables
-  const utils::UniqueVector<const GlobalVariable*>& DirectlyReferencedGlobals()
-      const {
-    return directly_referenced_globals_;
-  }
-
-  /// Records that this function directly references the given global variable.
-  /// Note: Implicitly adds this global to the transtively-called globals.
-  /// @param global the module-scope variable
-  void AddDirectlyReferencedGlobal(const sem::GlobalVariable* global) {
-    directly_referenced_globals_.add(global);
-    transitively_referenced_globals_.add(global);
-  }
-
-  /// @returns all transitively referenced global variables
-  const utils::UniqueVector<const GlobalVariable*>&
-  TransitivelyReferencedGlobals() const {
-    return transitively_referenced_globals_;
-  }
-
-  /// Records that this function transitively references the given global
-  /// variable.
-  /// @param global the module-scoped variable
-  void AddTransitivelyReferencedGlobal(const sem::GlobalVariable* global) {
-    transitively_referenced_globals_.add(global);
-  }
-
-  /// @returns the list of functions that this function transitively calls.
-  const utils::UniqueVector<const Function*>& TransitivelyCalledFunctions()
-      const {
-    return transitively_called_functions_;
-  }
-
-  /// Records that this function transitively calls `function`.
-  /// @param function the function this function transitively calls
-  void AddTransitivelyCalledFunction(const Function* function) {
-    transitively_called_functions_.add(function);
-  }
-
-  /// @returns the list of builtins that this function directly calls.
-  const utils::UniqueVector<const Builtin*>& DirectlyCalledBuiltins() const {
-    return directly_called_builtins_;
-  }
-
-  /// Records that this function transitively calls `builtin`.
-  /// @param builtin the builtin this function directly calls
-  void AddDirectlyCalledBuiltin(const Builtin* builtin) {
-    directly_called_builtins_.add(builtin);
-  }
-
-  /// Adds the given texture/sampler pair to the list of unique pairs
-  /// that this function uses (directly or indirectly). These can only
-  /// be parameters to this function or global variables. Uniqueness is
-  /// ensured by texture_sampler_pairs_ being a UniqueVector.
-  /// @param texture the texture (must be non-null)
-  /// @param sampler the sampler (null indicates a texture-only reference)
-  void AddTextureSamplerPair(const sem::Variable* texture,
-                             const sem::Variable* sampler) {
-    texture_sampler_pairs_.add(VariablePair(texture, sampler));
-  }
-
-  /// @returns the list of texture/sampler pairs that this function uses
-  /// (directly or indirectly).
-  const std::vector<VariablePair>& TextureSamplerPairs() const {
-    return texture_sampler_pairs_;
-  }
-
-  /// @returns the list of direct calls to functions / builtins made by this
-  /// function
-  std::vector<const Call*> DirectCallStatements() const {
-    return direct_calls_;
-  }
-
-  /// Adds a record of the direct function / builtin calls made by this
-  /// function
-  /// @param call the call
-  void AddDirectCall(const Call* call) { direct_calls_.emplace_back(call); }
-
-  /// @param target the target of a call
-  /// @returns the Call to the given CallTarget, or nullptr the target was not
-  /// called by this function.
-  const Call* FindDirectCallTo(const CallTarget* target) const {
-    for (auto* call : direct_calls_) {
-      if (call->Target() == target) {
-        return call;
-      }
-    }
-    return nullptr;
-  }
-
-  /// @returns the list of callsites of this function
-  std::vector<const Call*> CallSites() const { return callsites_; }
-
-  /// Adds a record of a callsite to this function
-  /// @param call the callsite
-  void AddCallSite(const Call* call) { callsites_.emplace_back(call); }
-
-  /// @returns the ancestor entry points
-  const std::vector<const Function*>& AncestorEntryPoints() const {
-    return ancestor_entry_points_;
-  }
-
-  /// Adds a record that the given entry point transitively calls this function
-  /// @param entry_point the entry point that transtively calls this function
-  void AddAncestorEntryPoint(const sem::Function* entry_point) {
-    ancestor_entry_points_.emplace_back(entry_point);
-  }
-
-  /// Retrieves any referenced location variables
-  /// @returns the <variable, attribute> pair.
-  std::vector<std::pair<const Variable*, const ast::LocationAttribute*>>
-  TransitivelyReferencedLocationVariables() const;
-
-  /// Retrieves any referenced builtin variables
-  /// @returns the <variable, attribute> pair.
-  std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>>
-  TransitivelyReferencedBuiltinVariables() const;
-
-  /// Retrieves any referenced uniform variables. Note, the variables must be
-  /// decorated with both binding and group attributes.
-  /// @returns the referenced uniforms
-  VariableBindings TransitivelyReferencedUniformVariables() const;
-
-  /// Retrieves any referenced storagebuffer variables. Note, the variables
-  /// must be decorated with both binding and group attributes.
-  /// @returns the referenced storagebuffers
-  VariableBindings TransitivelyReferencedStorageBufferVariables() const;
-
-  /// Retrieves any referenced regular Sampler variables. Note, the
-  /// variables must be decorated with both binding and group attributes.
-  /// @returns the referenced storagebuffers
-  VariableBindings TransitivelyReferencedSamplerVariables() const;
-
-  /// Retrieves any referenced comparison Sampler variables. Note, the
-  /// variables must be decorated with both binding and group attributes.
-  /// @returns the referenced storagebuffers
-  VariableBindings TransitivelyReferencedComparisonSamplerVariables() const;
-
-  /// Retrieves any referenced sampled textures variables. Note, the
-  /// variables must be decorated with both binding and group attributes.
-  /// @returns the referenced sampled textures
-  VariableBindings TransitivelyReferencedSampledTextureVariables() const;
-
-  /// Retrieves any referenced multisampled textures variables. Note, the
-  /// variables must be decorated with both binding and group attributes.
-  /// @returns the referenced sampled textures
-  VariableBindings TransitivelyReferencedMultisampledTextureVariables() const;
-
-  /// Retrieves any referenced variables of the given type. Note, the variables
-  /// must be decorated with both binding and group attributes.
-  /// @param type the type of the variables to find
-  /// @returns the referenced variables
-  VariableBindings TransitivelyReferencedVariablesOfType(
-      const tint::TypeInfo* type) const;
-
-  /// Retrieves any referenced variables of the given type. Note, the variables
-  /// must be decorated with both binding and group attributes.
-  /// @returns the referenced variables
-  template <typename T>
-  VariableBindings TransitivelyReferencedVariablesOfType() const {
-    return TransitivelyReferencedVariablesOfType(&TypeInfo::Of<T>());
-  }
-
-  /// Checks if the given entry point is an ancestor
-  /// @param sym the entry point symbol
-  /// @returns true if `sym` is an ancestor entry point of this function
-  bool HasAncestorEntryPoint(Symbol sym) const;
-
-  /// Sets that this function has a discard statement
-  void SetHasDiscard() { has_discard_ = true; }
-
-  /// Returns true if this function has a discard statement
-  /// @returns true if this function has a discard statement
-  bool HasDiscard() const { return has_discard_; }
-
-  /// @return the behaviors of this function
-  const sem::Behaviors& Behaviors() const { return behaviors_; }
-
-  /// @return the behaviors of this function
-  sem::Behaviors& Behaviors() { return behaviors_; }
-
- private:
-  VariableBindings TransitivelyReferencedSamplerVariablesImpl(
-      ast::SamplerKind kind) const;
-  VariableBindings TransitivelyReferencedSampledTextureVariablesImpl(
-      bool multisampled) const;
-
-  const ast::Function* const declaration_;
-
-  sem::WorkgroupSize workgroup_size_;
-  utils::UniqueVector<const GlobalVariable*> directly_referenced_globals_;
-  utils::UniqueVector<const GlobalVariable*> transitively_referenced_globals_;
-  utils::UniqueVector<const Function*> transitively_called_functions_;
-  utils::UniqueVector<const Builtin*> directly_called_builtins_;
-  utils::UniqueVector<VariablePair> texture_sampler_pairs_;
-  std::vector<const Call*> direct_calls_;
-  std::vector<const Call*> callsites_;
-  std::vector<const Function*> ancestor_entry_points_;
-  bool has_discard_ = false;
-  sem::Behaviors behaviors_{sem::Behavior::kNext};
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_FUNCTION_H_
diff --git a/src/sem/i32_type.cc b/src/sem/i32_type.cc
deleted file mode 100644
index daacf27..0000000
--- a/src/sem/i32_type.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/i32_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::I32);
-
-namespace tint {
-namespace sem {
-
-I32::I32() = default;
-
-I32::I32(I32&&) = default;
-
-I32::~I32() = default;
-
-std::string I32::type_name() const {
-  return "__i32";
-}
-
-std::string I32::FriendlyName(const SymbolTable&) const {
-  return "i32";
-}
-
-bool I32::IsConstructible() const {
-  return true;
-}
-
-uint32_t I32::Size() const {
-  return 4;
-}
-
-uint32_t I32::Align() const {
-  return 4;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/i32_type.h b/src/sem/i32_type.h
deleted file mode 100644
index 6818559..0000000
--- a/src/sem/i32_type.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_I32_TYPE_H_
-#define SRC_SEM_I32_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A signed int 32 type.
-class I32 : public Castable<I32, Type> {
- public:
-  /// Constructor
-  I32();
-  /// Move constructor
-  I32(I32&&);
-  ~I32() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the size in bytes of the type.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type.
-  uint32_t Align() const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_I32_TYPE_H_
diff --git a/src/sem/i32_type_test.cc b/src/sem/i32_type_test.cc
deleted file mode 100644
index 02ae0b4..0000000
--- a/src/sem/i32_type_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using I32Test = TestHelper;
-
-TEST_F(I32Test, TypeName) {
-  I32 i;
-  EXPECT_EQ(i.type_name(), "__i32");
-}
-
-TEST_F(I32Test, FriendlyName) {
-  I32 i;
-  EXPECT_EQ(i.FriendlyName(Symbols()), "i32");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/if_statement.cc b/src/sem/if_statement.cc
deleted file mode 100644
index be01a6e..0000000
--- a/src/sem/if_statement.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/if_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::IfStatement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ElseStatement);
-
-namespace tint {
-namespace sem {
-
-IfStatement::IfStatement(const ast::IfStatement* declaration,
-                         const CompoundStatement* parent,
-                         const sem::Function* function)
-    : Base(declaration, parent, function) {}
-
-IfStatement::~IfStatement() = default;
-
-const ast::IfStatement* IfStatement::Declaration() const {
-  return static_cast<const ast::IfStatement*>(Base::Declaration());
-}
-
-ElseStatement::ElseStatement(const ast::ElseStatement* declaration,
-                             const IfStatement* parent,
-                             const sem::Function* function)
-    : Base(declaration, parent, function) {}
-
-ElseStatement::~ElseStatement() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/if_statement.h b/src/sem/if_statement.h
deleted file mode 100644
index 4b60d9f..0000000
--- a/src/sem/if_statement.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_IF_STATEMENT_H_
-#define SRC_SEM_IF_STATEMENT_H_
-
-#include "src/sem/statement.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class IfStatement;
-class ElseStatement;
-}  // namespace ast
-namespace sem {
-class Expression;
-}  // namespace sem
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Holds semantic information about an if statement
-class IfStatement : public Castable<IfStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this if statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  IfStatement(const ast::IfStatement* declaration,
-              const CompoundStatement* parent,
-              const sem::Function* function);
-
-  /// Destructor
-  ~IfStatement() override;
-
-  /// @returns the AST node
-  const ast::IfStatement* Declaration() const;
-
-  /// @returns the if-statement condition expression
-  const Expression* Condition() const { return condition_; }
-
-  /// Sets the if-statement condition expression
-  /// @param condition the if condition expression
-  void SetCondition(const Expression* condition) { condition_ = condition; }
-
- private:
-  const Expression* condition_ = nullptr;
-};
-
-/// Holds semantic information about an else statement
-class ElseStatement : public Castable<ElseStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this else statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  ElseStatement(const ast::ElseStatement* declaration,
-                const IfStatement* parent,
-                const sem::Function* function);
-
-  /// Destructor
-  ~ElseStatement() override;
-
-  /// @returns the else-statement condition expression
-  const Expression* Condition() const { return condition_; }
-
-  /// @return the statement that encloses this statement
-  const IfStatement* Parent() const {
-    return static_cast<const IfStatement*>(Statement::Parent());
-  }
-
-  /// Sets the else-statement condition expression
-  /// @param condition the else condition expression
-  void SetCondition(const Expression* condition) { condition_ = condition; }
-
- private:
-  const Expression* condition_ = nullptr;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_IF_STATEMENT_H_
diff --git a/src/sem/info.cc b/src/sem/info.cc
deleted file mode 100644
index e2bab98..0000000
--- a/src/sem/info.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/info.h"
-
-namespace tint {
-namespace sem {
-
-Info::Info() = default;
-
-Info::Info(Info&&) = default;
-
-Info::~Info() = default;
-
-Info& Info::operator=(Info&&) = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/info.h b/src/sem/info.h
deleted file mode 100644
index 232d252..0000000
--- a/src/sem/info.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_INFO_H_
-#define SRC_SEM_INFO_H_
-
-#include <type_traits>
-#include <unordered_map>
-
-#include "src/debug.h"
-#include "src/sem/node.h"
-#include "src/sem/type_mappings.h"
-
-namespace tint::sem {
-
-// Forward declarations
-class Module;
-
-/// Info holds all the resolved semantic information for a Program.
-class Info {
- public:
-  /// Placeholder type used by Get() to provide a default value for EXPLICIT_SEM
-  using InferFromAST = std::nullptr_t;
-
-  /// Resolves to the return type of the Get() method given the desired sementic
-  /// type and AST type.
-  template <typename SEM, typename AST_OR_TYPE>
-  using GetResultType =
-      std::conditional_t<std::is_same<SEM, InferFromAST>::value,
-                         SemanticNodeTypeFor<AST_OR_TYPE>,
-                         SEM>;
-
-  /// Constructor
-  Info();
-
-  /// Move constructor
-  Info(Info&&);
-
-  /// Destructor
-  ~Info();
-
-  /// Move assignment operator
-  /// @param rhs the Program to move
-  /// @return this Program
-  Info& operator=(Info&& rhs);
-
-  /// Get looks up the semantic information for the AST or type node `node`.
-  /// @param node the AST or type node
-  /// @returns a pointer to the semantic node if found, otherwise nullptr
-  template <typename SEM = InferFromAST,
-            typename AST_OR_TYPE = CastableBase,
-            typename RESULT = GetResultType<SEM, AST_OR_TYPE>>
-  const RESULT* Get(const AST_OR_TYPE* node) const {
-    auto it = map_.find(node);
-    if (it == map_.end()) {
-      return nullptr;
-    }
-    return As<RESULT>(it->second);
-  }
-
-  /// Add registers the semantic node `sem_node` for the AST or type node
-  /// `node`.
-  /// @param node the AST or type node
-  /// @param sem_node the semantic node
-  template <typename AST_OR_TYPE>
-  void Add(const AST_OR_TYPE* node,
-           const SemanticNodeTypeFor<AST_OR_TYPE>* sem_node) {
-    // Check there's no semantic info already existing for the node
-    TINT_ASSERT(Semantic, Get(node) == nullptr);
-    map_.emplace(node, sem_node);
-  }
-
-  /// Wrap returns a new Info created with the contents of `inner`.
-  /// The Info returned by Wrap is intended to temporarily extend the contents
-  /// of an existing immutable Info.
-  /// As the copied contents are owned by `inner`, `inner` must not be
-  /// destructed or assigned while using the returned Info.
-  /// @param inner the immutable Info to extend
-  /// @return the Info that wraps `inner`
-  static Info Wrap(const Info& inner) {
-    Info out;
-    out.map_ = inner.map_;
-    out.module_ = inner.module_;
-    return out;
-  }
-
-  /// Assigns the semantic module.
-  /// @param module the module to assign.
-  void SetModule(sem::Module* module) { module_ = module; }
-
-  /// @returns the semantic module.
-  const sem::Module* Module() const { return module_; }
-
- private:
-  // TODO(crbug.com/tint/724): Once finished, this map should be:
-  // std::unordered_map<const ast::Node*, const sem::Node*>
-  std::unordered_map<const CastableBase*, const CastableBase*> map_;
-  // The semantic module
-  sem::Module* module_ = nullptr;
-};
-
-}  // namespace tint::sem
-
-#endif  // SRC_SEM_INFO_H_
diff --git a/src/sem/loop_statement.cc b/src/sem/loop_statement.cc
deleted file mode 100644
index 6c43b93..0000000
--- a/src/sem/loop_statement.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/loop_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopStatement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement);
-
-namespace tint {
-namespace sem {
-
-LoopStatement::LoopStatement(const ast::LoopStatement* declaration,
-                             const CompoundStatement* parent,
-                             const sem::Function* function)
-    : Base(declaration, parent, function) {
-  TINT_ASSERT(Semantic, parent);
-  TINT_ASSERT(Semantic, function);
-}
-
-LoopStatement::~LoopStatement() = default;
-
-LoopContinuingBlockStatement::LoopContinuingBlockStatement(
-    const ast::BlockStatement* declaration,
-    const CompoundStatement* parent,
-    const sem::Function* function)
-    : Base(declaration, parent, function) {
-  TINT_ASSERT(Semantic, parent);
-  TINT_ASSERT(Semantic, function);
-}
-LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/loop_statement.h b/src/sem/loop_statement.h
deleted file mode 100644
index ad1f0a8..0000000
--- a/src/sem/loop_statement.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_LOOP_STATEMENT_H_
-#define SRC_SEM_LOOP_STATEMENT_H_
-
-#include "src/sem/block_statement.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class LoopStatement;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Holds semantic information about a loop statement
-class LoopStatement : public Castable<LoopStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this loop statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  LoopStatement(const ast::LoopStatement* declaration,
-                const CompoundStatement* parent,
-                const sem::Function* function);
-
-  /// Destructor
-  ~LoopStatement() override;
-};
-
-/// Holds semantic information about a loop continuing block
-class LoopContinuingBlockStatement
-    : public Castable<LoopContinuingBlockStatement, BlockStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this block statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  LoopContinuingBlockStatement(const ast::BlockStatement* declaration,
-                               const CompoundStatement* parent,
-                               const sem::Function* function);
-
-  /// Destructor
-  ~LoopContinuingBlockStatement() override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_LOOP_STATEMENT_H_
diff --git a/src/sem/matrix_type.cc b/src/sem/matrix_type.cc
deleted file mode 100644
index 14bd0ba..0000000
--- a/src/sem/matrix_type.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/matrix_type.h"
-
-#include "src/program_builder.h"
-#include "src/sem/vector_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Matrix);
-
-namespace tint {
-namespace sem {
-
-Matrix::Matrix(const Vector* column_type, uint32_t columns)
-    : subtype_(column_type->type()),
-      column_type_(column_type),
-      rows_(column_type->Width()),
-      columns_(columns) {
-  TINT_ASSERT(AST, rows_ > 1);
-  TINT_ASSERT(AST, rows_ < 5);
-  TINT_ASSERT(AST, columns_ > 1);
-  TINT_ASSERT(AST, columns_ < 5);
-}
-
-Matrix::Matrix(Matrix&&) = default;
-
-Matrix::~Matrix() = default;
-
-std::string Matrix::type_name() const {
-  return "__mat_" + std::to_string(rows_) + "_" + std::to_string(columns_) +
-         subtype_->type_name();
-}
-
-std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "mat" << columns_ << "x" << rows_ << "<"
-      << subtype_->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-bool Matrix::IsConstructible() const {
-  return true;
-}
-
-uint32_t Matrix::Size() const {
-  return column_type_->Align() * columns();
-}
-
-uint32_t Matrix::Align() const {
-  return column_type_->Align();
-}
-
-uint32_t Matrix::ColumnStride() const {
-  return column_type_->Align();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/matrix_type.h b/src/sem/matrix_type.h
deleted file mode 100644
index 896eb3b..0000000
--- a/src/sem/matrix_type.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_MATRIX_TYPE_H_
-#define SRC_SEM_MATRIX_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-// Forward declaration
-class Vector;
-
-/// A matrix type
-class Matrix : public Castable<Matrix, Type> {
- public:
-  /// Constructor
-  /// @param column_type the type of a column of the matrix
-  /// @param columns the number of columns in the matrix
-  Matrix(const Vector* column_type, uint32_t columns);
-  /// Move constructor
-  Matrix(Matrix&&);
-  ~Matrix() override;
-
-  /// @returns the type of the matrix
-  const Type* type() const { return subtype_; }
-  /// @returns the number of rows in the matrix
-  uint32_t rows() const { return rows_; }
-  /// @returns the number of columns in the matrix
-  uint32_t columns() const { return columns_; }
-
-  /// @returns the column-vector type of the matrix
-  const Vector* ColumnType() const { return column_type_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the size in bytes of the type. This may include tail padding.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type. This may include tail
-  /// padding.
-  uint32_t Align() const override;
-
-  /// @returns the number of bytes between columns of the matrix
-  uint32_t ColumnStride() const;
-
- private:
-  const Type* const subtype_;
-  const Vector* const column_type_;
-  const uint32_t rows_;
-  const uint32_t columns_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_MATRIX_TYPE_H_
diff --git a/src/sem/matrix_type_test.cc b/src/sem/matrix_type_test.cc
deleted file mode 100644
index 6a55b87..0000000
--- a/src/sem/matrix_type_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using MatrixTest = TestHelper;
-
-TEST_F(MatrixTest, Creation) {
-  I32 i32;
-  Vector c{&i32, 2};
-  Matrix m{&c, 4};
-  EXPECT_EQ(m.type(), &i32);
-  EXPECT_EQ(m.rows(), 2u);
-  EXPECT_EQ(m.columns(), 4u);
-}
-
-TEST_F(MatrixTest, TypeName) {
-  I32 i32;
-  Vector c{&i32, 2};
-  Matrix m{&c, 3};
-  EXPECT_EQ(m.type_name(), "__mat_2_3__i32");
-}
-
-TEST_F(MatrixTest, FriendlyName) {
-  I32 i32;
-  Vector c{&i32, 3};
-  Matrix m{&c, 2};
-  EXPECT_EQ(m.FriendlyName(Symbols()), "mat2x3<i32>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/member_accessor_expression.cc b/src/sem/member_accessor_expression.cc
deleted file mode 100644
index 7af2e7b..0000000
--- a/src/sem/member_accessor_expression.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/member_accessor_expression.h"
-#include "src/sem/member_accessor_expression.h"
-
-#include <utility>
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::MemberAccessorExpression);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMemberAccess);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Swizzle);
-
-namespace tint {
-namespace sem {
-
-MemberAccessorExpression::MemberAccessorExpression(
-    const ast::MemberAccessorExpression* declaration,
-    const sem::Type* type,
-    const Statement* statement,
-    bool has_side_effects)
-    : Base(declaration, type, statement, Constant{}, has_side_effects) {}
-
-MemberAccessorExpression::~MemberAccessorExpression() = default;
-
-StructMemberAccess::StructMemberAccess(
-    const ast::MemberAccessorExpression* declaration,
-    const sem::Type* type,
-    const Statement* statement,
-    const StructMember* member,
-    bool has_side_effects)
-    : Base(declaration, type, statement, has_side_effects), member_(member) {}
-
-StructMemberAccess::~StructMemberAccess() = default;
-
-Swizzle::Swizzle(const ast::MemberAccessorExpression* declaration,
-                 const sem::Type* type,
-                 const Statement* statement,
-                 std::vector<uint32_t> indices)
-    : Base(declaration, type, statement, /* has_side_effects */ false),
-      indices_(std::move(indices)) {}
-
-Swizzle::~Swizzle() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/member_accessor_expression.h b/src/sem/member_accessor_expression.h
deleted file mode 100644
index a123a88..0000000
--- a/src/sem/member_accessor_expression.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
-#define SRC_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
-
-#include <vector>
-
-#include "src/sem/expression.h"
-
-namespace tint {
-
-/// Forward declarations
-namespace ast {
-class MemberAccessorExpression;
-}  // namespace ast
-
-namespace sem {
-
-/// Forward declarations
-class Struct;
-class StructMember;
-
-/// MemberAccessorExpression holds the semantic information for a
-/// ast::MemberAccessorExpression node.
-class MemberAccessorExpression
-    : public Castable<MemberAccessorExpression, Expression> {
- public:
-  /// Constructor
-  /// @param declaration the AST node
-  /// @param type the resolved type of the expression
-  /// @param statement the statement that owns this expression
-  /// @param has_side_effects whether this expression may have side effects
-  MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
-                           const sem::Type* type,
-                           const Statement* statement,
-                           bool has_side_effects);
-
-  /// Destructor
-  ~MemberAccessorExpression() override;
-};
-
-/// StructMemberAccess holds the semantic information for a
-/// ast::MemberAccessorExpression node that represents an access to a structure
-/// member.
-class StructMemberAccess
-    : public Castable<StructMemberAccess, MemberAccessorExpression> {
- public:
-  /// Constructor
-  /// @param declaration the AST node
-  /// @param type the resolved type of the expression
-  /// @param statement the statement that owns this expression
-  /// @param member the structure member
-  /// @param has_side_effects whether this expression may have side effects
-  StructMemberAccess(const ast::MemberAccessorExpression* declaration,
-                     const sem::Type* type,
-                     const Statement* statement,
-                     const StructMember* member,
-                     bool has_side_effects);
-
-  /// Destructor
-  ~StructMemberAccess() override;
-
-  /// @returns the structure member
-  StructMember const* Member() const { return member_; }
-
- private:
-  StructMember const* const member_;
-};
-
-/// Swizzle holds the semantic information for a ast::MemberAccessorExpression
-/// node that represents a vector swizzle.
-class Swizzle : public Castable<Swizzle, MemberAccessorExpression> {
- public:
-  /// Constructor
-  /// @param declaration the AST node
-  /// @param type the resolved type of the expression
-  /// @param statement the statement that owns this expression
-  /// @param indices the swizzle indices
-  Swizzle(const ast::MemberAccessorExpression* declaration,
-          const sem::Type* type,
-          const Statement* statement,
-          std::vector<uint32_t> indices);
-
-  /// Destructor
-  ~Swizzle() override;
-
-  /// @return the swizzle indices, if this is a vector swizzle
-  const std::vector<uint32_t>& Indices() const { return indices_; }
-
- private:
-  std::vector<uint32_t> const indices_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
diff --git a/src/sem/module.cc b/src/sem/module.cc
deleted file mode 100644
index 48c348f..0000000
--- a/src/sem/module.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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/sem/module.h"
-
-#include <utility>
-#include <vector>
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Module);
-
-namespace tint::sem {
-
-Module::Module(std::vector<const ast::Node*> dep_ordered_decls)
-    : dep_ordered_decls_(std::move(dep_ordered_decls)) {}
-
-Module::~Module() = default;
-
-}  // namespace tint::sem
diff --git a/src/sem/module.h b/src/sem/module.h
deleted file mode 100644
index 07d7903..0000000
--- a/src/sem/module.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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_SEM_MODULE_H_
-#define SRC_SEM_MODULE_H_
-
-#include <vector>
-
-#include "src/sem/node.h"
-
-// Forward declarations
-namespace tint::ast {
-class Node;
-class Module;
-}  // namespace tint::ast
-
-namespace tint::sem {
-
-/// Module holds the top-level semantic types, functions and global variables
-/// used by a Program.
-class Module : public Castable<Module, Node> {
- public:
-  /// Constructor
-  /// @param dep_ordered_decls the dependency-ordered module-scope declarations
-  explicit Module(std::vector<const ast::Node*> dep_ordered_decls);
-
-  /// Destructor
-  ~Module() override;
-
-  /// @returns the dependency-ordered global declarations for the module
-  const std::vector<const ast::Node*>& DependencyOrderedDeclarations() const {
-    return dep_ordered_decls_;
-  }
-
- private:
-  const std::vector<const ast::Node*> dep_ordered_decls_;
-};
-
-}  // namespace tint::sem
-
-#endif  // SRC_SEM_MODULE_H_
diff --git a/src/sem/multisampled_texture_type.cc b/src/sem/multisampled_texture_type.cc
deleted file mode 100644
index ad9cc67..0000000
--- a/src/sem/multisampled_texture_type.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/multisampled_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::MultisampledTexture);
-
-namespace tint {
-namespace sem {
-
-MultisampledTexture::MultisampledTexture(ast::TextureDimension dim,
-                                         const Type* type)
-    : Base(dim), type_(type) {
-  TINT_ASSERT(Semantic, type_);
-}
-
-MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
-
-MultisampledTexture::~MultisampledTexture() = default;
-
-std::string MultisampledTexture::type_name() const {
-  std::ostringstream out;
-  out << "__multisampled_texture_" << dim() << type_->type_name();
-  return out.str();
-}
-
-std::string MultisampledTexture::FriendlyName(
-    const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "texture_multisampled_" << dim() << "<" << type_->FriendlyName(symbols)
-      << ">";
-  return out.str();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/multisampled_texture_type.h b/src/sem/multisampled_texture_type.h
deleted file mode 100644
index f67ee1a..0000000
--- a/src/sem/multisampled_texture_type.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_MULTISAMPLED_TEXTURE_TYPE_H_
-#define SRC_SEM_MULTISAMPLED_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-/// A multisampled texture type.
-class MultisampledTexture : public Castable<MultisampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  /// @param type the data type of the multisampled texture
-  MultisampledTexture(ast::TextureDimension dim, const Type* type);
-  /// Move constructor
-  MultisampledTexture(MultisampledTexture&&);
-  ~MultisampledTexture() override;
-
-  /// @returns the subtype of the sampled texture
-  const Type* type() const { return type_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  const Type* const type_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_MULTISAMPLED_TEXTURE_TYPE_H_
diff --git a/src/sem/multisampled_texture_type_test.cc b/src/sem/multisampled_texture_type_test.cc
deleted file mode 100644
index 97d0b38..0000000
--- a/src/sem/multisampled_texture_type_test.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/multisampled_texture_type.h"
-
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using MultisampledTextureTest = TestHelper;
-
-TEST_F(MultisampledTextureTest, IsTexture) {
-  F32 f32;
-  MultisampledTexture s(ast::TextureDimension::kCube, &f32);
-  Texture* ty = &s;
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_FALSE(ty->Is<ExternalTexture>());
-  EXPECT_TRUE(ty->Is<MultisampledTexture>());
-  EXPECT_FALSE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(MultisampledTextureTest, Dim) {
-  F32 f32;
-  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.dim(), ast::TextureDimension::k3d);
-}
-
-TEST_F(MultisampledTextureTest, Type) {
-  F32 f32;
-  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.type(), &f32);
-}
-
-TEST_F(MultisampledTextureTest, TypeName) {
-  F32 f32;
-  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.type_name(), "__multisampled_texture_3d__f32");
-}
-
-TEST_F(MultisampledTextureTest, FriendlyName) {
-  F32 f32;
-  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/node.cc b/src/sem/node.cc
deleted file mode 100644
index b60cffb..0000000
--- a/src/sem/node.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/node.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Node);
-
-namespace tint {
-namespace sem {
-
-Node::Node() = default;
-
-Node::Node(const Node&) = default;
-
-Node::~Node() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/node.h b/src/sem/node.h
deleted file mode 100644
index e1e5bd6..0000000
--- a/src/sem/node.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_NODE_H_
-#define SRC_SEM_NODE_H_
-
-#include "src/castable.h"
-
-namespace tint {
-namespace sem {
-
-/// Node is the base class for all semantic nodes
-class Node : public Castable<Node> {
- public:
-  /// Constructor
-  Node();
-
-  /// Copy constructor
-  Node(const Node&);
-
-  /// Destructor
-  ~Node() override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_NODE_H_
diff --git a/src/sem/parameter_usage.cc b/src/sem/parameter_usage.cc
deleted file mode 100644
index 6f0e071..0000000
--- a/src/sem/parameter_usage.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/builtin-gen
-// using the template:
-//   src/sem/parameter_usage.cc.tmpl
-// and the builtin defintion file:
-//   src/builtins.def
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/sem/parameter_usage.h"
-
-namespace tint {
-namespace sem {
-
-const char* str(ParameterUsage usage) {
-  switch (usage) {
-    case ParameterUsage::kNone:
-      return "none";
-    case ParameterUsage::kArrayIndex:
-      return "array_index";
-    case ParameterUsage::kBias:
-      return "bias";
-    case ParameterUsage::kComponent:
-      return "component";
-    case ParameterUsage::kCoords:
-      return "coords";
-    case ParameterUsage::kDdx:
-      return "ddx";
-    case ParameterUsage::kDdy:
-      return "ddy";
-    case ParameterUsage::kDepthRef:
-      return "depth_ref";
-    case ParameterUsage::kLevel:
-      return "level";
-    case ParameterUsage::kOffset:
-      return "offset";
-    case ParameterUsage::kSampleIndex:
-      return "sample_index";
-    case ParameterUsage::kSampler:
-      return "sampler";
-    case ParameterUsage::kTexture:
-      return "texture";
-    case ParameterUsage::kValue:
-      return "value";
-  }
-  return "<unknown>";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/parameter_usage.cc.tmpl b/src/sem/parameter_usage.cc.tmpl
deleted file mode 100644
index b8125e7..0000000
--- a/src/sem/parameter_usage.cc.tmpl
+++ /dev/null
@@ -1,29 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/builtin-gen to generate parameter_usage.cc
-
-See:
-* tools/cmd/builtin-gen/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#include "src/sem/parameter_usage.h"
-
-namespace tint {
-namespace sem {
-
-const char* str(ParameterUsage usage) {
-  switch (usage) {
-    case ParameterUsage::kNone:
-      return "none";
-{{- range .Sem.UniqueParameterNames  }}
-    case ParameterUsage::k{{PascalCase .}}:
-      return "{{.}}";
-{{- end  }}
-  }
-  return "<unknown>";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/parameter_usage.h b/src/sem/parameter_usage.h
deleted file mode 100644
index adcd41b..0000000
--- a/src/sem/parameter_usage.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/builtin-gen
-// using the template:
-//   src/sem/parameter_usage.h.tmpl
-// and the builtin defintion file:
-//   src/builtins.def
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#ifndef SRC_SEM_PARAMETER_USAGE_H_
-#define SRC_SEM_PARAMETER_USAGE_H_
-
-namespace tint {
-namespace sem {
-
-/// ParameterUsage is extra metadata for identifying a parameter based on its
-/// overload position
-enum class ParameterUsage {
-  kNone = -1,
-  kArrayIndex,
-  kBias,
-  kComponent,
-  kCoords,
-  kDdx,
-  kDdy,
-  kDepthRef,
-  kLevel,
-  kOffset,
-  kSampleIndex,
-  kSampler,
-  kTexture,
-  kValue,
-};
-
-/// @returns a string representation of the given parameter usage.
-const char* str(ParameterUsage usage);
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_PARAMETER_USAGE_H_
diff --git a/src/sem/parameter_usage.h.tmpl b/src/sem/parameter_usage.h.tmpl
deleted file mode 100644
index ef51d17..0000000
--- a/src/sem/parameter_usage.h.tmpl
+++ /dev/null
@@ -1,32 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/builtin-gen to generate parameter_usage.h
-
-See:
-* tools/cmd/builtin-gen/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#ifndef SRC_SEM_PARAMETER_USAGE_H_
-#define SRC_SEM_PARAMETER_USAGE_H_
-
-namespace tint {
-namespace sem {
-
-/// ParameterUsage is extra metadata for identifying a parameter based on its
-/// overload position
-enum class ParameterUsage {
-  kNone = -1,
-{{- range .Sem.UniqueParameterNames  }}
-  k{{PascalCase .}},
-{{- end  }}
-};
-
-/// @returns a string representation of the given parameter usage.
-const char* str(ParameterUsage usage);
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_PARAMETER_USAGE_H_
diff --git a/src/sem/pipeline_stage_set.h b/src/sem/pipeline_stage_set.h
deleted file mode 100644
index 304a2e1..0000000
--- a/src/sem/pipeline_stage_set.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_PIPELINE_STAGE_SET_H_
-#define SRC_SEM_PIPELINE_STAGE_SET_H_
-
-#include "src/ast/pipeline_stage.h"
-#include "src/utils/enum_set.h"
-
-namespace tint {
-namespace sem {
-
-using PipelineStageSet = utils::EnumSet<ast::PipelineStage>;
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_PIPELINE_STAGE_SET_H_
diff --git a/src/sem/pointer_type.cc b/src/sem/pointer_type.cc
deleted file mode 100644
index 1ab5d1a..0000000
--- a/src/sem/pointer_type.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/pointer_type.h"
-
-#include "src/program_builder.h"
-#include "src/sem/reference_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Pointer);
-
-namespace tint {
-namespace sem {
-
-Pointer::Pointer(const Type* subtype,
-                 ast::StorageClass storage_class,
-                 ast::Access access)
-    : subtype_(subtype), storage_class_(storage_class), access_(access) {
-  TINT_ASSERT(Semantic, !subtype->Is<Reference>());
-  TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
-}
-
-std::string Pointer::type_name() const {
-  std::ostringstream out;
-  out << "__ptr_" << storage_class_ << subtype_->type_name() << "__" << access_;
-  return out.str();
-}
-
-std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "ptr<";
-  if (storage_class_ != ast::StorageClass::kNone) {
-    out << storage_class_ << ", ";
-  }
-  out << subtype_->FriendlyName(symbols) << ", " << access_;
-  out << ">";
-  return out.str();
-}
-
-Pointer::Pointer(Pointer&&) = default;
-
-Pointer::~Pointer() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/pointer_type.h b/src/sem/pointer_type.h
deleted file mode 100644
index 73ac3a3..0000000
--- a/src/sem/pointer_type.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_POINTER_TYPE_H_
-#define SRC_SEM_POINTER_TYPE_H_
-
-#include <string>
-
-#include "src/ast/access.h"
-#include "src/ast/storage_class.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A pointer type.
-class Pointer : public Castable<Pointer, Type> {
- public:
-  /// Constructor
-  /// @param subtype the pointee type
-  /// @param storage_class the storage class of the pointer
-  /// @param access the resolved access control of the reference
-  Pointer(const Type* subtype,
-          ast::StorageClass storage_class,
-          ast::Access access);
-
-  /// Move constructor
-  Pointer(Pointer&&);
-  ~Pointer() override;
-
-  /// @returns the pointee type
-  const Type* StoreType() const { return subtype_; }
-
-  /// @returns the storage class of the pointer
-  ast::StorageClass StorageClass() const { return storage_class_; }
-
-  /// @returns the access control of the reference
-  ast::Access Access() const { return access_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  Type const* const subtype_;
-  ast::StorageClass const storage_class_;
-  ast::Access const access_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_POINTER_TYPE_H_
diff --git a/src/sem/pointer_type_test.cc b/src/sem/pointer_type_test.cc
deleted file mode 100644
index 950d282..0000000
--- a/src/sem/pointer_type_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using PointerTest = TestHelper;
-
-TEST_F(PointerTest, Creation) {
-  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kStorage,
-                            ast::Access::kReadWrite);
-  EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
-  EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
-  EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
-}
-
-TEST_F(PointerTest, TypeName) {
-  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
-                            ast::Access::kReadWrite);
-  EXPECT_EQ(r->type_name(), "__ptr_workgroup__i32__read_write");
-}
-
-TEST_F(PointerTest, FriendlyName) {
-  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kNone,
-                            ast::Access::kRead);
-  EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<i32, read>");
-}
-
-TEST_F(PointerTest, FriendlyNameWithStorageClass) {
-  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
-                            ast::Access::kRead);
-  EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<workgroup, i32, read>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/reference_type.cc b/src/sem/reference_type.cc
deleted file mode 100644
index 48bde77..0000000
--- a/src/sem/reference_type.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/reference_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Reference);
-
-namespace tint {
-namespace sem {
-
-Reference::Reference(const Type* subtype,
-                     ast::StorageClass storage_class,
-                     ast::Access access)
-    : subtype_(subtype), storage_class_(storage_class), access_(access) {
-  TINT_ASSERT(Semantic, !subtype->Is<Reference>());
-  TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
-}
-
-std::string Reference::type_name() const {
-  std::ostringstream out;
-  out << "__ref_" << storage_class_ << subtype_->type_name() << "__" << access_;
-  return out.str();
-}
-
-std::string Reference::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "ref<";
-  if (storage_class_ != ast::StorageClass::kNone) {
-    out << storage_class_ << ", ";
-  }
-  out << subtype_->FriendlyName(symbols) << ", " << access_;
-  out << ">";
-  return out.str();
-}
-
-Reference::Reference(Reference&&) = default;
-
-Reference::~Reference() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/reference_type.h b/src/sem/reference_type.h
deleted file mode 100644
index 72ebbdb..0000000
--- a/src/sem/reference_type.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_REFERENCE_TYPE_H_
-#define SRC_SEM_REFERENCE_TYPE_H_
-
-#include <string>
-
-#include "src/ast/access.h"
-#include "src/ast/storage_class.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A reference type.
-class Reference : public Castable<Reference, Type> {
- public:
-  /// Constructor
-  /// @param subtype the pointee type
-  /// @param storage_class the storage class of the reference
-  /// @param access the resolved access control of the reference
-  Reference(const Type* subtype,
-            ast::StorageClass storage_class,
-            ast::Access access);
-
-  /// Move constructor
-  Reference(Reference&&);
-  ~Reference() override;
-
-  /// @returns the pointee type
-  const Type* StoreType() const { return subtype_; }
-
-  /// @returns the storage class of the reference
-  ast::StorageClass StorageClass() const { return storage_class_; }
-
-  /// @returns the resolved access control of the reference.
-  ast::Access Access() const { return access_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  Type const* const subtype_;
-  ast::StorageClass const storage_class_;
-  ast::Access const access_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_REFERENCE_TYPE_H_
diff --git a/src/sem/reference_type_test.cc b/src/sem/reference_type_test.cc
deleted file mode 100644
index a397e3a..0000000
--- a/src/sem/reference_type_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/reference_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using ReferenceTest = TestHelper;
-
-TEST_F(ReferenceTest, Creation) {
-  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kStorage,
-                              ast::Access::kReadWrite);
-  EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
-  EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
-  EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
-}
-
-TEST_F(ReferenceTest, TypeName) {
-  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
-                              ast::Access::kReadWrite);
-  EXPECT_EQ(r->type_name(), "__ref_workgroup__i32__read_write");
-}
-
-TEST_F(ReferenceTest, FriendlyName) {
-  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kNone,
-                              ast::Access::kRead);
-  EXPECT_EQ(r->FriendlyName(Symbols()), "ref<i32, read>");
-}
-
-TEST_F(ReferenceTest, FriendlyNameWithStorageClass) {
-  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
-                              ast::Access::kRead);
-  EXPECT_EQ(r->FriendlyName(Symbols()), "ref<workgroup, i32, read>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sampled_texture_type.cc b/src/sem/sampled_texture_type.cc
deleted file mode 100644
index 21a30a4..0000000
--- a/src/sem/sampled_texture_type.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/sampled_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::SampledTexture);
-
-namespace tint {
-namespace sem {
-
-SampledTexture::SampledTexture(ast::TextureDimension dim, const Type* type)
-    : Base(dim), type_(type) {
-  TINT_ASSERT(Semantic, type_);
-}
-
-SampledTexture::SampledTexture(SampledTexture&&) = default;
-
-SampledTexture::~SampledTexture() = default;
-
-std::string SampledTexture::type_name() const {
-  std::ostringstream out;
-  out << "__sampled_texture_" << dim() << type_->type_name();
-  return out.str();
-}
-
-std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "texture_" << dim() << "<" << type_->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sampled_texture_type.h b/src/sem/sampled_texture_type.h
deleted file mode 100644
index 32c36b9..0000000
--- a/src/sem/sampled_texture_type.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_SAMPLED_TEXTURE_TYPE_H_
-#define SRC_SEM_SAMPLED_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-/// A sampled texture type.
-class SampledTexture : public Castable<SampledTexture, Texture> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  /// @param type the data type of the sampled texture
-  SampledTexture(ast::TextureDimension dim, const Type* type);
-  /// Move constructor
-  SampledTexture(SampledTexture&&);
-  ~SampledTexture() override;
-
-  /// @returns the subtype of the sampled texture
-  Type* type() const { return const_cast<Type*>(type_); }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  const Type* const type_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_SAMPLED_TEXTURE_TYPE_H_
diff --git a/src/sem/sampled_texture_type_test.cc b/src/sem/sampled_texture_type_test.cc
deleted file mode 100644
index a1ee7bc..0000000
--- a/src/sem/sampled_texture_type_test.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/sampled_texture_type.h"
-
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using SampledTextureTest = TestHelper;
-
-TEST_F(SampledTextureTest, IsTexture) {
-  F32 f32;
-  SampledTexture s(ast::TextureDimension::kCube, &f32);
-  Texture* ty = &s;
-  EXPECT_FALSE(ty->Is<DepthTexture>());
-  EXPECT_FALSE(ty->Is<ExternalTexture>());
-  EXPECT_TRUE(ty->Is<SampledTexture>());
-  EXPECT_FALSE(ty->Is<StorageTexture>());
-}
-
-TEST_F(SampledTextureTest, Dim) {
-  F32 f32;
-  SampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.dim(), ast::TextureDimension::k3d);
-}
-
-TEST_F(SampledTextureTest, Type) {
-  F32 f32;
-  SampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.type(), &f32);
-}
-
-TEST_F(SampledTextureTest, TypeName) {
-  F32 f32;
-  SampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.type_name(), "__sampled_texture_3d__f32");
-}
-
-TEST_F(SampledTextureTest, FriendlyName) {
-  F32 f32;
-  SampledTexture s(ast::TextureDimension::k3d, &f32);
-  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_3d<f32>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sampler_texture_pair.h b/src/sem/sampler_texture_pair.h
deleted file mode 100644
index 9f0135b..0000000
--- a/src/sem/sampler_texture_pair.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_SAMPLER_TEXTURE_PAIR_H_
-#define SRC_SEM_SAMPLER_TEXTURE_PAIR_H_
-
-#include <cstdint>
-#include <functional>
-
-#include "src/sem/binding_point.h"
-
-namespace tint {
-namespace sem {
-
-/// Mapping of a sampler to a texture it samples.
-struct SamplerTexturePair {
-  /// group & binding values for a sampler.
-  BindingPoint sampler_binding_point;
-  /// group & binding values for a texture samepled by the sampler.
-  BindingPoint texture_binding_point;
-
-  /// Equality operator
-  /// @param rhs the SamplerTexturePair to compare against
-  /// @returns true if this SamplerTexturePair is equal to `rhs`
-  inline bool operator==(const SamplerTexturePair& rhs) const {
-    return sampler_binding_point == rhs.sampler_binding_point &&
-           texture_binding_point == rhs.texture_binding_point;
-  }
-
-  /// Inequality operator
-  /// @param rhs the SamplerTexturePair to compare against
-  /// @returns true if this SamplerTexturePair is not equal to `rhs`
-  inline bool operator!=(const SamplerTexturePair& rhs) const {
-    return !(*this == rhs);
-  }
-};
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::SamplerTexturePair so
-/// SamplerTexturePairs be used as keys for std::unordered_map and
-/// std::unordered_set.
-template <>
-class hash<tint::sem::SamplerTexturePair> {
- public:
-  /// @param stp the texture pair to create a hash for
-  /// @return the hash value
-  inline std::size_t operator()(
-      const tint::sem::SamplerTexturePair& stp) const {
-    return tint::utils::Hash(stp.sampler_binding_point,
-                             stp.texture_binding_point);
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_SEM_SAMPLER_TEXTURE_PAIR_H_
diff --git a/src/sem/sampler_type.cc b/src/sem/sampler_type.cc
deleted file mode 100644
index 7af21e4..0000000
--- a/src/sem/sampler_type.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/sampler_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Sampler);
-
-namespace tint {
-namespace sem {
-
-Sampler::Sampler(ast::SamplerKind kind) : kind_(kind) {}
-
-Sampler::Sampler(Sampler&&) = default;
-
-Sampler::~Sampler() = default;
-
-std::string Sampler::type_name() const {
-  return std::string("__sampler_") +
-         (kind_ == ast::SamplerKind::kSampler ? "sampler" : "comparison");
-}
-
-std::string Sampler::FriendlyName(const SymbolTable&) const {
-  return kind_ == ast::SamplerKind::kSampler ? "sampler" : "sampler_comparison";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sampler_type.h b/src/sem/sampler_type.h
deleted file mode 100644
index 19e5a6b..0000000
--- a/src/sem/sampler_type.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_SAMPLER_TYPE_H_
-#define SRC_SEM_SAMPLER_TYPE_H_
-
-#include <string>
-
-#include "src/ast/sampler.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A sampler type.
-class Sampler : public Castable<Sampler, Type> {
- public:
-  /// Constructor
-  /// @param kind the kind of sampler
-  explicit Sampler(ast::SamplerKind kind);
-  /// Move constructor
-  Sampler(Sampler&&);
-  ~Sampler() override;
-
-  /// @returns the sampler type
-  ast::SamplerKind kind() const { return kind_; }
-
-  /// @returns true if this is a comparison sampler
-  bool IsComparison() const {
-    return kind_ == ast::SamplerKind::kComparisonSampler;
-  }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  ast::SamplerKind const kind_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_SAMPLER_TYPE_H_
diff --git a/src/sem/sampler_type_test.cc b/src/sem/sampler_type_test.cc
deleted file mode 100644
index 9f2d5fc..0000000
--- a/src/sem/sampler_type_test.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/sampler_type.h"
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using SamplerTest = TestHelper;
-
-TEST_F(SamplerTest, Creation) {
-  Sampler s{ast::SamplerKind::kSampler};
-  EXPECT_EQ(s.kind(), ast::SamplerKind::kSampler);
-}
-
-TEST_F(SamplerTest, Creation_ComparisonSampler) {
-  Sampler s{ast::SamplerKind::kComparisonSampler};
-  EXPECT_EQ(s.kind(), ast::SamplerKind::kComparisonSampler);
-  EXPECT_TRUE(s.IsComparison());
-}
-
-TEST_F(SamplerTest, TypeName_Sampler) {
-  Sampler s{ast::SamplerKind::kSampler};
-  EXPECT_EQ(s.type_name(), "__sampler_sampler");
-}
-
-TEST_F(SamplerTest, TypeName_Comparison) {
-  Sampler s{ast::SamplerKind::kComparisonSampler};
-  EXPECT_EQ(s.type_name(), "__sampler_comparison");
-}
-
-TEST_F(SamplerTest, FriendlyNameSampler) {
-  Sampler s{ast::SamplerKind::kSampler};
-  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler");
-}
-
-TEST_F(SamplerTest, FriendlyNameComparisonSampler) {
-  Sampler s{ast::SamplerKind::kComparisonSampler};
-  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler_comparison");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sem_array_test.cc b/src/sem/sem_array_test.cc
deleted file mode 100644
index a74c58f..0000000
--- a/src/sem/sem_array_test.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using ArrayTest = TestHelper;
-
-TEST_F(ArrayTest, CreateSizedArray) {
-  U32 u32;
-  auto* arr = create<Array>(&u32, 2, 4, 8, 32, 16);
-  EXPECT_EQ(arr->ElemType(), &u32);
-  EXPECT_EQ(arr->Count(), 2u);
-  EXPECT_EQ(arr->Align(), 4u);
-  EXPECT_EQ(arr->Size(), 8u);
-  EXPECT_EQ(arr->Stride(), 32u);
-  EXPECT_EQ(arr->ImplicitStride(), 16u);
-  EXPECT_FALSE(arr->IsStrideImplicit());
-  EXPECT_FALSE(arr->IsRuntimeSized());
-}
-
-TEST_F(ArrayTest, CreateRuntimeArray) {
-  U32 u32;
-  auto* arr = create<Array>(&u32, 0, 4, 8, 32, 32);
-  EXPECT_EQ(arr->ElemType(), &u32);
-  EXPECT_EQ(arr->Count(), 0u);
-  EXPECT_EQ(arr->Align(), 4u);
-  EXPECT_EQ(arr->Size(), 8u);
-  EXPECT_EQ(arr->Stride(), 32u);
-  EXPECT_EQ(arr->ImplicitStride(), 32u);
-  EXPECT_TRUE(arr->IsStrideImplicit());
-  EXPECT_TRUE(arr->IsRuntimeSized());
-}
-
-TEST_F(ArrayTest, TypeName) {
-  I32 i32;
-  auto* arr = create<Array>(&i32, 2, 0, 4, 4, 4);
-  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_0_size_4_stride_4");
-}
-
-TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
-  auto* arr = create<Array>(create<I32>(), 0, 0, 4, 4, 4);
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
-}
-
-TEST_F(ArrayTest, FriendlyNameStaticSized) {
-  auto* arr = create<Array>(create<I32>(), 5, 4, 20, 4, 4);
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
-}
-
-TEST_F(ArrayTest, FriendlyNameRuntimeSizedNonImplicitStride) {
-  auto* arr = create<Array>(create<I32>(), 0, 0, 4, 8, 4);
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32>");
-}
-
-TEST_F(ArrayTest, FriendlyNameStaticSizedNonImplicitStride) {
-  auto* arr = create<Array>(create<I32>(), 5, 4, 20, 8, 4);
-  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32, 5>");
-}
-
-TEST_F(ArrayTest, TypeName_RuntimeArray) {
-  I32 i32;
-  auto* arr = create<Array>(&i32, 2, 4, 8, 16, 16);
-  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_4_size_8_stride_16");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/sem_struct_test.cc b/src/sem/sem_struct_test.cc
deleted file mode 100644
index fed1363..0000000
--- a/src/sem/sem_struct_test.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/struct.h"
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using StructTest = TestHelper;
-
-TEST_F(StructTest, Creation) {
-  auto name = Sym("S");
-  auto* impl =
-      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
-  auto* ptr = impl;
-  auto* s =
-      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
-                          8 /* size */, 16 /* size_no_padding */);
-  EXPECT_EQ(s->Declaration(), ptr);
-  EXPECT_EQ(s->Align(), 4u);
-  EXPECT_EQ(s->Size(), 8u);
-  EXPECT_EQ(s->SizeNoPadding(), 16u);
-}
-
-TEST_F(StructTest, TypeName) {
-  auto name = Sym("my_struct");
-  auto* impl =
-      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
-  auto* s =
-      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
-                          4 /* size */, 4 /* size_no_padding */);
-  EXPECT_EQ(s->type_name(), "__struct_$1");
-}
-
-TEST_F(StructTest, FriendlyName) {
-  auto name = Sym("my_struct");
-  auto* impl =
-      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
-  auto* s =
-      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
-                          4 /* size */, 4 /* size_no_padding */);
-  EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
-}
-
-TEST_F(StructTest, Layout) {
-  auto* inner_st =  //
-      Structure("Inner", {
-                             Member("a", ty.i32()),
-                             Member("b", ty.u32()),
-                             Member("c", ty.f32()),
-                             Member("d", ty.vec3<f32>()),
-                             Member("e", ty.mat4x2<f32>()),
-                         });
-
-  auto* outer_st =
-      Structure("Outer", {
-                             Member("inner", ty.type_name("Inner")),
-                             Member("a", ty.i32()),
-                         });
-
-  auto p = Build();
-  ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
-
-  auto* sem_inner_st = p.Sem().Get(inner_st);
-  auto* sem_outer_st = p.Sem().Get(outer_st);
-
-  EXPECT_EQ(sem_inner_st->Layout(p.Symbols()),
-            R"(/*            align(16) size(64) */ struct Inner {
-/* offset( 0) align( 4) size( 4) */   a : i32;
-/* offset( 4) align( 4) size( 4) */   b : u32;
-/* offset( 8) align( 4) size( 4) */   c : f32;
-/* offset(12) align( 1) size( 4) */   // -- implicit field alignment padding --;
-/* offset(16) align(16) size(12) */   d : vec3<f32>;
-/* offset(28) align( 1) size( 4) */   // -- implicit field alignment padding --;
-/* offset(32) align( 8) size(32) */   e : mat4x2<f32>;
-/*                               */ };)");
-
-  EXPECT_EQ(sem_outer_st->Layout(p.Symbols()),
-            R"(/*            align(16) size(80) */ struct Outer {
-/* offset( 0) align(16) size(64) */   inner : Inner;
-/* offset(64) align( 4) size( 4) */   a : i32;
-/* offset(68) align( 1) size(12) */   // -- implicit struct size padding --;
-/*                               */ };)");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/statement.cc b/src/sem/statement.cc
deleted file mode 100644
index cc60668..0000000
--- a/src/sem/statement.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <algorithm>
-
-#include "src/ast/block_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/statement.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/statement.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::CompoundStatement);
-
-namespace tint {
-namespace sem {
-
-Statement::Statement(const ast::Statement* declaration,
-                     const CompoundStatement* parent,
-                     const sem::Function* function)
-    : declaration_(declaration), parent_(parent), function_(function) {}
-
-Statement::~Statement() = default;
-
-const BlockStatement* Statement::Block() const {
-  return FindFirstParent<BlockStatement>();
-}
-
-CompoundStatement::CompoundStatement(const ast::Statement* declaration,
-                                     const CompoundStatement* parent,
-                                     const sem::Function* function)
-    : Base(declaration, parent, function) {}
-
-CompoundStatement::~CompoundStatement() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/statement.h b/src/sem/statement.h
deleted file mode 100644
index 153ede1..0000000
--- a/src/sem/statement.h
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_STATEMENT_H_
-#define SRC_SEM_STATEMENT_H_
-
-#include "src/sem/behavior.h"
-#include "src/sem/node.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class Function;
-class Statement;
-}  // namespace ast
-namespace sem {
-class BlockStatement;
-}  // namespace sem
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Forward declaration
-class CompoundStatement;
-class Function;
-
-namespace detail {
-/// FindFirstParentReturn is a traits helper for determining the return type for
-/// the template member function Statement::FindFirstParent().
-/// For zero or multiple template arguments, FindFirstParentReturn::type
-/// resolves to CompoundStatement.
-template <typename... TYPES>
-struct FindFirstParentReturn {
-  /// The pointer type returned by Statement::FindFirstParent()
-  using type = CompoundStatement;
-};
-
-/// A specialization of FindFirstParentReturn for a single template argument.
-/// FindFirstParentReturn::type resolves to the single template argument.
-template <typename T>
-struct FindFirstParentReturn<T> {
-  /// The pointer type returned by Statement::FindFirstParent()
-  using type = T;
-};
-
-template <typename... TYPES>
-using FindFirstParentReturnType =
-    typename FindFirstParentReturn<TYPES...>::type;
-}  // namespace detail
-
-/// Statement holds the semantic information for a statement.
-class Statement : public Castable<Statement, Node> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  Statement(const ast::Statement* declaration,
-            const CompoundStatement* parent,
-            const sem::Function* function);
-
-  /// Destructor
-  ~Statement() override;
-
-  /// @return the AST node for this statement
-  const ast::Statement* Declaration() const { return declaration_; }
-
-  /// @return the statement that encloses this statement
-  const CompoundStatement* Parent() const { return parent_; }
-
-  /// @returns the closest enclosing parent that satisfies the given predicate,
-  /// which may be the statement itself, or nullptr if no match is found.
-  /// @param pred a predicate that the resulting block must satisfy
-  template <typename Pred>
-  const CompoundStatement* FindFirstParent(Pred&& pred) const;
-
-  /// @returns the closest enclosing parent that is of one of the types in
-  /// `TYPES`, which may be the statement itself, or nullptr if no match is
-  /// found. If `TYPES` is a single template argument, the return type is a
-  /// pointer to that template argument type, otherwise a CompoundStatement
-  /// pointer is returned.
-  template <typename... TYPES>
-  const detail::FindFirstParentReturnType<TYPES...>* FindFirstParent() const;
-
-  /// @return the closest enclosing block for this statement
-  const BlockStatement* Block() const;
-
-  /// @returns the function that owns this statement
-  const sem::Function* Function() const { return function_; }
-
-  /// @return the behaviors of this statement
-  const sem::Behaviors& Behaviors() const { return behaviors_; }
-
-  /// @return the behaviors of this statement
-  sem::Behaviors& Behaviors() { return behaviors_; }
-
-  /// @returns true if this statement is reachable by control flow according to
-  /// the behavior analysis
-  bool IsReachable() const { return is_reachable_; }
-
-  /// @param is_reachable whether this statement is reachable by control flow
-  /// according to the behavior analysis
-  void SetIsReachable(bool is_reachable) { is_reachable_ = is_reachable; }
-
- private:
-  const ast::Statement* const declaration_;
-  const CompoundStatement* const parent_;
-  const sem::Function* const function_;
-  sem::Behaviors behaviors_{sem::Behavior::kNext};
-  bool is_reachable_ = true;
-};
-
-/// CompoundStatement is the base class of statements that can hold other
-/// statements.
-class CompoundStatement : public Castable<Statement, Statement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this statement
-  /// @param statement the owning statement
-  /// @param function the owning function
-  CompoundStatement(const ast::Statement* declaration,
-                    const CompoundStatement* statement,
-                    const sem::Function* function);
-
-  /// Destructor
-  ~CompoundStatement() override;
-};
-
-template <typename Pred>
-const CompoundStatement* Statement::FindFirstParent(Pred&& pred) const {
-  if (auto* self = As<CompoundStatement>()) {
-    if (pred(self)) {
-      return self;
-    }
-  }
-  const auto* curr = parent_;
-  while (curr && !pred(curr)) {
-    curr = curr->Parent();
-  }
-  return curr;
-}
-
-template <typename... TYPES>
-const detail::FindFirstParentReturnType<TYPES...>* Statement::FindFirstParent()
-    const {
-  using ReturnType = detail::FindFirstParentReturnType<TYPES...>;
-  if (sizeof...(TYPES) == 1) {
-    if (auto* p = As<ReturnType>()) {
-      return p;
-    }
-    const auto* curr = parent_;
-    while (curr) {
-      if (auto* p = curr->As<ReturnType>()) {
-        return p;
-      }
-      curr = curr->Parent();
-    }
-  } else {
-    if (IsAnyOf<TYPES...>()) {
-      return As<ReturnType>();
-    }
-    const auto* curr = parent_;
-    while (curr) {
-      if (curr->IsAnyOf<TYPES...>()) {
-        return curr->As<ReturnType>();
-      }
-      curr = curr->Parent();
-    }
-  }
-  return nullptr;
-}
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_STATEMENT_H_
diff --git a/src/sem/storage_texture_type.cc b/src/sem/storage_texture_type.cc
deleted file mode 100644
index 6492afd..0000000
--- a/src/sem/storage_texture_type.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/storage_texture_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::StorageTexture);
-
-namespace tint {
-namespace sem {
-
-StorageTexture::StorageTexture(ast::TextureDimension dim,
-                               ast::TexelFormat format,
-                               ast::Access access,
-                               sem::Type* subtype)
-    : Base(dim), texel_format_(format), access_(access), subtype_(subtype) {}
-
-StorageTexture::StorageTexture(StorageTexture&&) = default;
-
-StorageTexture::~StorageTexture() = default;
-
-std::string StorageTexture::type_name() const {
-  std::ostringstream out;
-  out << "__storage_texture_" << dim() << "_" << texel_format_ << "_"
-      << access_;
-  return out.str();
-}
-
-std::string StorageTexture::FriendlyName(const SymbolTable&) const {
-  std::ostringstream out;
-  out << "texture_storage_" << dim() << "<" << texel_format_ << ", " << access_
-      << ">";
-  return out.str();
-}
-
-sem::Type* StorageTexture::SubtypeFor(ast::TexelFormat format,
-                                      sem::Manager& type_mgr) {
-  switch (format) {
-    case ast::TexelFormat::kR32Uint:
-    case ast::TexelFormat::kRgba8Uint:
-    case ast::TexelFormat::kRg32Uint:
-    case ast::TexelFormat::kRgba16Uint:
-    case ast::TexelFormat::kRgba32Uint: {
-      return type_mgr.Get<sem::U32>();
-    }
-
-    case ast::TexelFormat::kR32Sint:
-    case ast::TexelFormat::kRgba8Sint:
-    case ast::TexelFormat::kRg32Sint:
-    case ast::TexelFormat::kRgba16Sint:
-    case ast::TexelFormat::kRgba32Sint: {
-      return type_mgr.Get<sem::I32>();
-    }
-
-    case ast::TexelFormat::kRgba8Unorm:
-    case ast::TexelFormat::kRgba8Snorm:
-    case ast::TexelFormat::kR32Float:
-    case ast::TexelFormat::kRg32Float:
-    case ast::TexelFormat::kRgba16Float:
-    case ast::TexelFormat::kRgba32Float: {
-      return type_mgr.Get<sem::F32>();
-    }
-
-    case ast::TexelFormat::kNone:
-      break;
-  }
-
-  return nullptr;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/storage_texture_type.h b/src/sem/storage_texture_type.h
deleted file mode 100644
index 4881db8..0000000
--- a/src/sem/storage_texture_type.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_STORAGE_TEXTURE_TYPE_H_
-#define SRC_SEM_STORAGE_TEXTURE_TYPE_H_
-
-#include <string>
-
-#include "src/ast/access.h"
-#include "src/ast/storage_texture.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-
-class Manager;
-
-/// A storage texture type.
-class StorageTexture : public Castable<StorageTexture, Texture> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  /// @param format the texel format of the texture
-  /// @param access the access control type of the texture
-  /// @param subtype the storage subtype. Use SubtypeFor() to calculate this.
-  StorageTexture(ast::TextureDimension dim,
-                 ast::TexelFormat format,
-                 ast::Access access,
-                 sem::Type* subtype);
-
-  /// Move constructor
-  StorageTexture(StorageTexture&&);
-  ~StorageTexture() override;
-
-  /// @returns the storage subtype
-  Type* type() const { return subtype_; }
-
-  /// @returns the texel format
-  ast::TexelFormat texel_format() const { return texel_format_; }
-
-  /// @returns the access control
-  ast::Access access() const { return access_; }
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @param format the storage texture image format
-  /// @param type_mgr the sem::Manager used to build the returned type
-  /// @returns the storage texture subtype for the given TexelFormat
-  static sem::Type* SubtypeFor(ast::TexelFormat format, sem::Manager& type_mgr);
-
- private:
-  ast::TexelFormat const texel_format_;
-  ast::Access const access_;
-  Type* const subtype_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_STORAGE_TEXTURE_TYPE_H_
diff --git a/src/sem/storage_texture_type_test.cc b/src/sem/storage_texture_type_test.cc
deleted file mode 100644
index 45e75d6..0000000
--- a/src/sem/storage_texture_type_test.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2020 The Tint Authors->
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/storage_texture_type.h"
-
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/external_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using StorageTextureTest = TestHelper;
-
-TEST_F(StorageTextureTest, Dim) {
-  auto* subtype =
-      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
-  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Float,
-                                   ast::Access::kReadWrite, subtype);
-  EXPECT_EQ(s->dim(), ast::TextureDimension::k2dArray);
-}
-
-TEST_F(StorageTextureTest, Format) {
-  auto* subtype =
-      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
-  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Float,
-                                   ast::Access::kReadWrite, subtype);
-  EXPECT_EQ(s->texel_format(), ast::TexelFormat::kRgba32Float);
-}
-
-TEST_F(StorageTextureTest, TypeName) {
-  auto* subtype =
-      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
-  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Float,
-                                   ast::Access::kReadWrite, subtype);
-  EXPECT_EQ(s->type_name(),
-            "__storage_texture_2d_array_rgba32float_read_write");
-}
-
-TEST_F(StorageTextureTest, FriendlyName) {
-  auto* subtype =
-      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
-  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Float,
-                                   ast::Access::kReadWrite, subtype);
-  EXPECT_EQ(s->FriendlyName(Symbols()),
-            "texture_storage_2d_array<rgba32float, read_write>");
-}
-
-TEST_F(StorageTextureTest, F32) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
-  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Float,
-                                   ast::Access::kReadWrite, subtype);
-
-  auto program = Build();
-
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<F32>());
-}
-
-TEST_F(StorageTextureTest, U32) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRg32Uint, Types());
-  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRg32Uint,
-                                   ast::Access::kReadWrite, subtype);
-
-  auto program = Build();
-
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<U32>());
-}
-
-TEST_F(StorageTextureTest, I32) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Sint, Types());
-  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
-                                   ast::TexelFormat::kRgba32Sint,
-                                   ast::Access::kReadWrite, subtype);
-
-  auto program = Build();
-
-  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-  ASSERT_TRUE(s->Is<Texture>());
-  ASSERT_TRUE(s->Is<StorageTexture>());
-  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/struct.cc b/src/sem/struct.cc
deleted file mode 100644
index 796eb39..0000000
--- a/src/sem/struct.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/struct.h"
-
-#include <cmath>
-#include <iomanip>
-#include <string>
-#include <utility>
-
-#include "src/ast/struct_member.h"
-#include "src/symbol_table.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Struct);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMember);
-
-namespace tint {
-namespace sem {
-
-Struct::Struct(const ast::Struct* declaration,
-               Symbol name,
-               StructMemberList members,
-               uint32_t align,
-               uint32_t size,
-               uint32_t size_no_padding)
-    : declaration_(declaration),
-      name_(name),
-      members_(std::move(members)),
-      align_(align),
-      size_(size),
-      size_no_padding_(size_no_padding) {
-  constructible_ = true;
-  for (auto* member : members_) {
-    if (!member->Type()->IsConstructible()) {
-      constructible_ = false;
-      break;
-    }
-  }
-}
-
-Struct::~Struct() = default;
-
-const StructMember* Struct::FindMember(Symbol name) const {
-  for (auto* member : members_) {
-    if (member->Declaration()->symbol == name) {
-      return member;
-    }
-  }
-  return nullptr;
-}
-
-std::string Struct::type_name() const {
-  return "__struct_" + name_.to_str();
-}
-
-uint32_t Struct::Align() const {
-  return align_;
-}
-
-uint32_t Struct::Size() const {
-  return size_;
-}
-
-std::string Struct::FriendlyName(const SymbolTable& symbols) const {
-  return symbols.NameFor(name_);
-}
-
-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);
-  };
-
-  if (Members().empty()) {
-    return {};
-  }
-  const auto* const last_member = Members().back();
-  const uint32_t last_member_struct_padding_offset =
-      last_member->Offset() + last_member->Size();
-
-  // Compute max widths to align output
-  const auto offset_w =
-      static_cast<int>(::log10(last_member_struct_padding_offset)) + 1;
-  const auto size_w = static_cast<int>(::log10(Size())) + 1;
-  const auto align_w = static_cast<int>(::log10(Align())) + 1;
-
-  auto print_struct_begin_line = [&](size_t align, size_t size,
-                                     std::string struct_name) {
-    ss << "/*          " << std::setw(offset_w) << " "
-       << "align(" << std::setw(align_w) << align << ") size("
-       << std::setw(size_w) << size << ") */ struct " << struct_name << " {\n";
-  };
-
-  auto print_struct_end_line = [&]() {
-    ss << "/*                         "
-       << std::setw(offset_w + size_w + align_w) << " "
-       << "*/ };";
-  };
-
-  auto print_member_line = [&](size_t offset, size_t align, size_t size,
-                               std::string s) {
-    ss << "/* offset(" << std::setw(offset_w) << offset << ") align("
-       << std::setw(align_w) << align << ") size(" << std::setw(size_w) << size
-       << ") */   " << s << ";\n";
-  };
-
-  print_struct_begin_line(Align(), Size(), UnwrapRef()->FriendlyName(symbols));
-
-  for (size_t i = 0; i < Members().size(); ++i) {
-    auto* const m = Members()[i];
-
-    // Output field alignment padding, if any
-    auto* const prev_member = (i == 0) ? nullptr : Members()[i - 1];
-    if (prev_member) {
-      uint32_t padding =
-          m->Offset() - (prev_member->Offset() + prev_member->Size());
-      if (padding > 0) {
-        size_t padding_offset = m->Offset() - padding;
-        print_member_line(padding_offset, 1, padding,
-                          "// -- implicit field alignment padding --");
-      }
-    }
-
-    // Output member
-    std::string member_name = member_name_of(m);
-    print_member_line(
-        m->Offset(), m->Align(), m->Size(),
-        member_name + " : " + m->Type()->UnwrapRef()->FriendlyName(symbols));
-  }
-
-  // Output struct size padding, if any
-  uint32_t struct_padding = Size() - last_member_struct_padding_offset;
-  if (struct_padding > 0) {
-    print_member_line(last_member_struct_padding_offset, 1, struct_padding,
-                      "// -- implicit struct size padding --");
-  }
-
-  print_struct_end_line();
-
-  return ss.str();
-}
-
-bool Struct::IsConstructible() const {
-  return constructible_;
-}
-
-StructMember::StructMember(const ast::StructMember* declaration,
-                           Symbol name,
-                           sem::Type* type,
-                           uint32_t index,
-                           uint32_t offset,
-                           uint32_t align,
-                           uint32_t size)
-    : declaration_(declaration),
-      name_(name),
-      type_(type),
-      index_(index),
-      offset_(offset),
-      align_(align),
-      size_(size) {}
-
-StructMember::~StructMember() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/struct.h b/src/sem/struct.h
deleted file mode 100644
index 9548ae7..0000000
--- a/src/sem/struct.h
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_STRUCT_H_
-#define SRC_SEM_STRUCT_H_
-
-#include <stdint.h>
-
-#include <string>
-#include <unordered_set>
-#include <vector>
-
-#include "src/ast/storage_class.h"
-#include "src/ast/struct.h"
-#include "src/sem/node.h"
-#include "src/sem/type.h"
-#include "src/symbol.h"
-
-namespace tint {
-
-// Forward declarations
-namespace ast {
-class StructMember;
-}  // namespace ast
-
-namespace sem {
-
-// Forward declarations
-class StructMember;
-class Type;
-
-/// A vector of StructMember pointers.
-using StructMemberList = std::vector<const StructMember*>;
-
-/// Metadata to capture how a structure is used in a shader module.
-enum class PipelineStageUsage {
-  kVertexInput,
-  kVertexOutput,
-  kFragmentInput,
-  kFragmentOutput,
-  kComputeInput,
-  kComputeOutput,
-};
-
-/// Struct holds the semantic information for structures.
-class Struct : public Castable<Struct, Type> {
- public:
-  /// Constructor
-  /// @param declaration the AST structure declaration
-  /// @param name the name of the structure
-  /// @param members the structure members
-  /// @param align the byte alignment of the structure
-  /// @param size the byte size of the structure
-  /// @param size_no_padding size of the members without the end of structure
-  /// alignment padding
-  Struct(const ast::Struct* declaration,
-         Symbol name,
-         StructMemberList members,
-         uint32_t align,
-         uint32_t size,
-         uint32_t size_no_padding);
-
-  /// Destructor
-  ~Struct() override;
-
-  /// @returns the struct
-  const ast::Struct* Declaration() const { return declaration_; }
-
-  /// @returns the name of the structure
-  Symbol Name() const { return name_; }
-
-  /// @returns the members of the structure
-  const StructMemberList& Members() const { return members_; }
-
-  /// @param name the member name to look for
-  /// @returns the member with the given name, or nullptr if it was not found.
-  const StructMember* FindMember(Symbol name) const;
-
-  /// @returns the byte alignment of the structure
-  /// @note this may differ from the alignment of a structure member of this
-  /// structure type, if the member is annotated with the `@align(n)`
-  /// attribute.
-  uint32_t Align() const override;
-
-  /// @returns the byte size of the structure
-  /// @note this may differ from the size of a structure member of this
-  /// structure type, if the member is annotated with the `@size(n)`
-  /// attribute.
-  uint32_t Size() const override;
-
-  /// @returns the byte size of the members without the end of structure
-  /// alignment padding
-  uint32_t SizeNoPadding() const { return size_no_padding_; }
-
-  /// Adds the StorageClass usage to the structure.
-  /// @param usage the storage usage
-  void AddUsage(ast::StorageClass usage) {
-    storage_class_usage_.emplace(usage);
-  }
-
-  /// @returns the set of storage class uses of this structure
-  const std::unordered_set<ast::StorageClass>& StorageClassUsage() const {
-    return storage_class_usage_;
-  }
-
-  /// @param usage the ast::StorageClass usage type to query
-  /// @returns true iff this structure has been used as the given storage class
-  bool UsedAs(ast::StorageClass usage) const {
-    return storage_class_usage_.count(usage) > 0;
-  }
-
-  /// @returns true iff this structure has been used by storage class that's
-  /// host-shareable.
-  bool IsHostShareable() const {
-    for (auto sc : storage_class_usage_) {
-      if (ast::IsHostShareable(sc)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /// Adds the pipeline stage usage to the structure.
-  /// @param usage the storage usage
-  void AddUsage(PipelineStageUsage usage) {
-    pipeline_stage_uses_.emplace(usage);
-  }
-
-  /// @returns the set of entry point uses of this structure
-  const std::unordered_set<PipelineStageUsage>& PipelineStageUses() const {
-    return pipeline_stage_uses_;
-  }
-
-  /// @returns the name for the type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns a multiline string that describes the layout of this struct,
-  /// including size and alignment information.
-  std::string Layout(const tint::SymbolTable& symbols) const;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
- private:
-  uint64_t LargestMemberBaseAlignment(MemoryLayout mem_layout) const;
-
-  ast::Struct const* const declaration_;
-  const Symbol name_;
-  const StructMemberList members_;
-  const uint32_t align_;
-  const uint32_t size_;
-  const uint32_t size_no_padding_;
-  std::unordered_set<ast::StorageClass> storage_class_usage_;
-  std::unordered_set<PipelineStageUsage> pipeline_stage_uses_;
-  bool constructible_;
-};
-
-/// StructMember holds the semantic information for structure members.
-class StructMember : public Castable<StructMember, Node> {
- public:
-  /// Constructor
-  /// @param declaration the AST declaration node
-  /// @param name the name of the structure
-  /// @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
-  /// @param align the byte alignment of the member
-  /// @param size the byte size of the member
-  StructMember(const ast::StructMember* declaration,
-               Symbol name,
-               sem::Type* type,
-               uint32_t index,
-               uint32_t offset,
-               uint32_t align,
-               uint32_t size);
-
-  /// Destructor
-  ~StructMember() override;
-
-  /// @returns the AST declaration node
-  const ast::StructMember* Declaration() const { return declaration_; }
-
-  /// @returns the name of the structure
-  Symbol Name() const { return name_; }
-
-  /// @returns the type of the member
-  sem::Type* Type() const { return type_; }
-
-  /// @returns the member index
-  uint32_t Index() const { return index_; }
-
-  /// @returns byte offset from base of structure
-  uint32_t Offset() const { return offset_; }
-
-  /// @returns the alignment of the member in bytes
-  uint32_t Align() const { return align_; }
-
-  /// @returns byte size
-  uint32_t Size() const { return size_; }
-
- private:
-  const ast::StructMember* const declaration_;
-  const Symbol name_;
-  sem::Type* const type_;
-  const uint32_t index_;
-  const uint32_t offset_;
-  const uint32_t align_;
-  const uint32_t size_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_STRUCT_H_
diff --git a/src/sem/switch_statement.cc b/src/sem/switch_statement.cc
deleted file mode 100644
index 9a911a2..0000000
--- a/src/sem/switch_statement.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/switch_statement.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::CaseStatement);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchStatement);
-
-namespace tint {
-namespace sem {
-
-SwitchStatement::SwitchStatement(const ast::SwitchStatement* declaration,
-                                 const CompoundStatement* parent,
-                                 const sem::Function* function)
-    : Base(declaration, parent, function) {
-  TINT_ASSERT(Semantic, parent);
-  TINT_ASSERT(Semantic, function);
-}
-
-SwitchStatement::~SwitchStatement() = default;
-
-const ast::SwitchStatement* SwitchStatement::Declaration() const {
-  return static_cast<const ast::SwitchStatement*>(Base::Declaration());
-}
-
-CaseStatement::CaseStatement(const ast::CaseStatement* declaration,
-                             const CompoundStatement* parent,
-                             const sem::Function* function)
-    : Base(declaration, parent, function) {
-  TINT_ASSERT(Semantic, parent);
-  TINT_ASSERT(Semantic, function);
-}
-CaseStatement::~CaseStatement() = default;
-
-const ast::CaseStatement* CaseStatement::Declaration() const {
-  return static_cast<const ast::CaseStatement*>(Base::Declaration());
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/switch_statement.h b/src/sem/switch_statement.h
deleted file mode 100644
index 49da6e9..0000000
--- a/src/sem/switch_statement.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_SWITCH_STATEMENT_H_
-#define SRC_SEM_SWITCH_STATEMENT_H_
-
-#include "src/sem/block_statement.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class CaseStatement;
-class SwitchStatement;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace sem {
-
-/// Holds semantic information about an switch statement
-class SwitchStatement : public Castable<SwitchStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this switch statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  SwitchStatement(const ast::SwitchStatement* declaration,
-                  const CompoundStatement* parent,
-                  const sem::Function* function);
-
-  /// Destructor
-  ~SwitchStatement() override;
-
-  /// @return the AST node for this statement
-  const ast::SwitchStatement* Declaration() const;
-};
-
-/// Holds semantic information about a switch case statement
-class CaseStatement : public Castable<CaseStatement, CompoundStatement> {
- public:
-  /// Constructor
-  /// @param declaration the AST node for this case statement
-  /// @param parent the owning statement
-  /// @param function the owning function
-  CaseStatement(const ast::CaseStatement* declaration,
-                const CompoundStatement* parent,
-                const sem::Function* function);
-
-  /// Destructor
-  ~CaseStatement() override;
-
-  /// @return the AST node for this statement
-  const ast::CaseStatement* Declaration() const;
-
-  /// @param body the case body block statement
-  void SetBlock(const BlockStatement* body) { body_ = body; }
-
-  /// @returns the case body block statement
-  const BlockStatement* Body() const { return body_; }
-
- private:
-  const BlockStatement* body_ = nullptr;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_SWITCH_STATEMENT_H_
diff --git a/src/sem/test_helper.h b/src/sem/test_helper.h
deleted file mode 100644
index e57ba6c..0000000
--- a/src/sem/test_helper.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TEST_HELPER_H_
-#define SRC_SEM_TEST_HELPER_H_
-
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace sem {
-
-/// Helper class for testing
-template <typename BASE>
-class TestHelperBase : public BASE, public ProgramBuilder {
- public:
-  /// Builds and returns the program. Must only be called once per test
-  /// @return the built program
-  Program Build() {
-    diag::Formatter formatter;
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << formatter.format(Diagnostics());
-    }();
-    return Program(std::move(*this));
-  }
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TEST_HELPER_H_
diff --git a/src/sem/texture_type.cc b/src/sem/texture_type.cc
deleted file mode 100644
index d418d0a..0000000
--- a/src/sem/texture_type.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/texture_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Texture);
-
-namespace tint {
-namespace sem {
-
-Texture::Texture(ast::TextureDimension dim) : dim_(dim) {}
-
-Texture::Texture(Texture&&) = default;
-
-Texture::~Texture() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/texture_type.h b/src/sem/texture_type.h
deleted file mode 100644
index 5afab9b..0000000
--- a/src/sem/texture_type.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TEXTURE_TYPE_H_
-#define SRC_SEM_TEXTURE_TYPE_H_
-
-#include "src/ast/texture.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A texture type.
-class Texture : public Castable<Texture, Type> {
- public:
-  /// Constructor
-  /// @param dim the dimensionality of the texture
-  explicit Texture(ast::TextureDimension dim);
-  /// Move constructor
-  Texture(Texture&&);
-  ~Texture() override;
-
-  /// @returns the texture dimension
-  ast::TextureDimension dim() const { return dim_; }
-
- private:
-  ast::TextureDimension const dim_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TEXTURE_TYPE_H_
diff --git a/src/sem/texture_type_test.cc b/src/sem/texture_type_test.cc
deleted file mode 100644
index 28fabf7..0000000
--- a/src/sem/texture_type_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/texture_type.h"
-
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/test_helper.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using TextureTypeDimTest = TestParamHelper<ast::TextureDimension>;
-
-TEST_P(TextureTypeDimTest, DimMustMatch) {
-  // Check that the dim() query returns the right dimensionality.
-  F32 f32;
-  // TextureType is an abstract class, so use concrete class
-  // SampledTexture in its stead.
-  SampledTexture st(GetParam(), &f32);
-  EXPECT_EQ(st.dim(), GetParam());
-}
-
-INSTANTIATE_TEST_SUITE_P(Dimensions,
-                         TextureTypeDimTest,
-                         ::testing::Values(ast::TextureDimension::k1d,
-                                           ast::TextureDimension::k2d,
-                                           ast::TextureDimension::k2dArray,
-                                           ast::TextureDimension::k3d,
-                                           ast::TextureDimension::kCube,
-                                           ast::TextureDimension::kCubeArray));
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type.cc b/src/sem/type.cc
deleted file mode 100644
index 158fb73..0000000
--- a/src/sem/type.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/type.h"
-
-#include "src/sem/bool_type.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/matrix_type.h"
-#include "src/sem/pointer_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/texture_type.h"
-#include "src/sem/u32_type.h"
-#include "src/sem/vector_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Type);
-
-namespace tint {
-namespace sem {
-
-Type::Type() = default;
-
-Type::Type(Type&&) = default;
-
-Type::~Type() = default;
-
-const Type* Type::UnwrapPtr() const {
-  auto* type = this;
-  while (auto* ptr = type->As<sem::Pointer>()) {
-    type = ptr->StoreType();
-  }
-  return type;
-}
-
-const Type* Type::UnwrapRef() const {
-  auto* type = this;
-  if (auto* ref = type->As<sem::Reference>()) {
-    type = ref->StoreType();
-  }
-  return type;
-}
-
-uint32_t Type::Size() const {
-  return 0;
-}
-
-uint32_t Type::Align() const {
-  return 0;
-}
-
-bool Type::IsConstructible() const {
-  return false;
-}
-
-bool Type::is_scalar() const {
-  return IsAnyOf<F32, U32, I32, Bool>();
-}
-
-bool Type::is_numeric_scalar() const {
-  return IsAnyOf<F32, U32, I32>();
-}
-
-bool Type::is_float_scalar() const {
-  return Is<F32>();
-}
-
-bool Type::is_float_matrix() const {
-  return Is([](const Matrix* m) { return m->type()->is_float_scalar(); });
-}
-
-bool Type::is_float_vector() const {
-  return Is([](const Vector* v) { return v->type()->is_float_scalar(); });
-}
-
-bool Type::is_float_scalar_or_vector() const {
-  return is_float_scalar() || is_float_vector();
-}
-
-bool Type::is_float_scalar_or_vector_or_matrix() const {
-  return is_float_scalar() || is_float_vector() || is_float_matrix();
-}
-
-bool Type::is_integer_scalar() const {
-  return IsAnyOf<U32, I32>();
-}
-
-bool Type::is_signed_integer_scalar() const {
-  return Is<I32>();
-}
-
-bool Type::is_unsigned_integer_scalar() const {
-  return Is<U32>();
-}
-
-bool Type::is_signed_integer_vector() const {
-  return Is([](const Vector* v) { return v->type()->Is<I32>(); });
-}
-
-bool Type::is_unsigned_integer_vector() const {
-  return Is([](const Vector* v) { return v->type()->Is<U32>(); });
-}
-
-bool Type::is_unsigned_scalar_or_vector() const {
-  return Is<U32>() || is_unsigned_integer_vector();
-}
-
-bool Type::is_signed_scalar_or_vector() const {
-  return Is<I32>() || is_signed_integer_vector();
-}
-
-bool Type::is_integer_scalar_or_vector() const {
-  return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector();
-}
-
-bool Type::is_bool_vector() const {
-  return Is([](const Vector* v) { return v->type()->Is<Bool>(); });
-}
-
-bool Type::is_bool_scalar_or_vector() const {
-  return Is<Bool>() || is_bool_vector();
-}
-
-bool Type::is_numeric_vector() const {
-  return Is([](const Vector* v) { return v->type()->is_numeric_scalar(); });
-}
-
-bool Type::is_scalar_vector() const {
-  return Is([](const Vector* v) { return v->type()->is_scalar(); });
-}
-
-bool Type::is_numeric_scalar_or_vector() const {
-  return is_numeric_scalar() || is_numeric_vector();
-}
-
-bool Type::is_handle() const {
-  return IsAnyOf<Sampler, Texture>();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type.h b/src/sem/type.h
deleted file mode 100644
index d38b5ef..0000000
--- a/src/sem/type.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TYPE_H_
-#define SRC_SEM_TYPE_H_
-
-#include <string>
-
-#include "src/sem/node.h"
-
-namespace tint {
-
-// Forward declarations
-class ProgramBuilder;
-class SymbolTable;
-
-namespace sem {
-
-/// Supported memory layouts for calculating sizes
-enum class MemoryLayout { kUniformBuffer, kStorageBuffer };
-
-/// Base class for a type in the system
-class Type : public Castable<Type, Node> {
- public:
-  /// Move constructor
-  Type(Type&&);
-  ~Type() override;
-
-  /// @returns the name for this type. The type name is unique over all types.
-  virtual std::string type_name() const = 0;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
-
-  /// @returns the inner most pointee type if this is a pointer, `this`
-  /// otherwise
-  const Type* UnwrapPtr() const;
-
-  /// @returns the inner type if this is a reference, `this` otherwise
-  const Type* UnwrapRef() const;
-
-  /// @returns the size in bytes of the type. This may include tail padding.
-  /// @note opaque types will return a size of 0.
-  virtual uint32_t Size() const;
-
-  /// @returns the alignment in bytes of the type. This may include tail
-  /// padding.
-  /// @note opaque types will return a size of 0.
-  virtual uint32_t Align() const;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  virtual bool IsConstructible() const;
-
-  /// @returns true if this type is a scalar
-  bool is_scalar() const;
-  /// @returns true if this type is a numeric scalar
-  bool is_numeric_scalar() const;
-  /// @returns true if this type is a float scalar
-  bool is_float_scalar() const;
-  /// @returns true if this type is a float matrix
-  bool is_float_matrix() const;
-  /// @returns true if this type is a float vector
-  bool is_float_vector() const;
-  /// @returns true if this type is a float scalar or vector
-  bool is_float_scalar_or_vector() const;
-  /// @returns true if this type is a float scalar or vector or matrix
-  bool is_float_scalar_or_vector_or_matrix() const;
-  /// @returns true if this type is an integer scalar
-  bool is_integer_scalar() const;
-  /// @returns true if this type is a signed integer scalar
-  bool is_signed_integer_scalar() const;
-  /// @returns true if this type is an unsigned integer scalar
-  bool is_unsigned_integer_scalar() const;
-  /// @returns true if this type is a signed integer vector
-  bool is_signed_integer_vector() const;
-  /// @returns true if this type is an unsigned vector
-  bool is_unsigned_integer_vector() const;
-  /// @returns true if this type is an unsigned scalar or vector
-  bool is_unsigned_scalar_or_vector() const;
-  /// @returns true if this type is a signed scalar or vector
-  bool is_signed_scalar_or_vector() const;
-  /// @returns true if this type is an integer scalar or vector
-  bool is_integer_scalar_or_vector() const;
-  /// @returns true if this type is a boolean vector
-  bool is_bool_vector() const;
-  /// @returns true if this type is boolean scalar or vector
-  bool is_bool_scalar_or_vector() const;
-  /// @returns true if this type is a numeric vector
-  bool is_numeric_vector() const;
-  /// @returns true if this type is a vector of scalar type
-  bool is_scalar_vector() const;
-  /// @returns true if this type is a numeric scale or vector
-  bool is_numeric_scalar_or_vector() const;
-  /// @returns true if this type is a handle type
-  bool is_handle() const;
-
- protected:
-  Type();
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TYPE_H_
diff --git a/src/sem/type_constructor.cc b/src/sem/type_constructor.cc
deleted file mode 100644
index 7ff12c4..0000000
--- a/src/sem/type_constructor.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/type_constructor.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::TypeConstructor);
-
-namespace tint {
-namespace sem {
-
-TypeConstructor::TypeConstructor(const sem::Type* type,
-                                 const ParameterList& parameters)
-    : Base(type, parameters) {}
-
-TypeConstructor::~TypeConstructor() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type_constructor.h b/src/sem/type_constructor.h
deleted file mode 100644
index 7acc22f..0000000
--- a/src/sem/type_constructor.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TYPE_CONSTRUCTOR_H_
-#define SRC_SEM_TYPE_CONSTRUCTOR_H_
-
-#include "src/sem/call_target.h"
-
-namespace tint {
-namespace sem {
-
-/// TypeConstructor is the CallTarget for a type constructor.
-class TypeConstructor : public Castable<TypeConstructor, CallTarget> {
- public:
-  /// Constructor
-  /// @param type the type that's being constructed
-  /// @param parameters the type constructor parameters
-  TypeConstructor(const sem::Type* type, const ParameterList& parameters);
-
-  /// Destructor
-  ~TypeConstructor() override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TYPE_CONSTRUCTOR_H_
diff --git a/src/sem/type_conversion.cc b/src/sem/type_conversion.cc
deleted file mode 100644
index 4f9de30..0000000
--- a/src/sem/type_conversion.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/type_conversion.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::TypeConversion);
-
-namespace tint {
-namespace sem {
-
-TypeConversion::TypeConversion(const sem::Type* type,
-                               const sem::Parameter* parameter)
-    : Base(type, ParameterList{parameter}) {}
-
-TypeConversion::~TypeConversion() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type_conversion.h b/src/sem/type_conversion.h
deleted file mode 100644
index c39202a..0000000
--- a/src/sem/type_conversion.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TYPE_CONVERSION_H_
-#define SRC_SEM_TYPE_CONVERSION_H_
-
-#include "src/sem/call_target.h"
-
-namespace tint {
-namespace sem {
-
-/// TypeConversion is the CallTarget for a type conversion (cast).
-class TypeConversion : public Castable<TypeConversion, CallTarget> {
- public:
-  /// Constructor
-  /// @param type the target type of the cast
-  /// @param parameter the type cast parameter
-  TypeConversion(const sem::Type* type, const sem::Parameter* parameter);
-
-  /// Destructor
-  ~TypeConversion() override;
-
-  /// @returns the cast source type
-  const sem::Type* Source() const { return Parameters()[0]->Type(); }
-
-  /// @returns the cast target type
-  const sem::Type* Target() const { return ReturnType(); }
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TYPE_CONVERSION_H_
diff --git a/src/sem/type_manager.cc b/src/sem/type_manager.cc
deleted file mode 100644
index eaae591..0000000
--- a/src/sem/type_manager.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/type_manager.h"
-
-namespace tint {
-namespace sem {
-
-Manager::Manager() = default;
-Manager::Manager(Manager&&) = default;
-Manager& Manager::operator=(Manager&& rhs) = default;
-Manager::~Manager() = default;
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type_manager.h b/src/sem/type_manager.h
deleted file mode 100644
index 59d1041..0000000
--- a/src/sem/type_manager.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TYPE_MANAGER_H_
-#define SRC_SEM_TYPE_MANAGER_H_
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-
-#include "src/block_allocator.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// The type manager holds all the pointers to the known types.
-class Manager {
- public:
-  /// Iterator is the type returned by begin() and end()
-  using Iterator = BlockAllocator<sem::Type>::ConstIterator;
-
-  /// Constructor
-  Manager();
-
-  /// Move constructor
-  Manager(Manager&&);
-
-  /// Move assignment operator
-  /// @param rhs the Manager to move
-  /// @return this Manager
-  Manager& operator=(Manager&& rhs);
-
-  /// Destructor
-  ~Manager();
-
-  /// Get the given type `T` from the type manager
-  /// @param args the arguments to pass to the type constructor
-  /// @return the pointer to the registered type
-  template <typename T, typename... ARGS>
-  T* Get(ARGS&&... args) {
-    // Note: We do not use std::forward here, as we may need to use the
-    // arguments again for the call to Create<T>() below.
-    auto name = T(args...).type_name();
-    auto it = by_name_.find(name);
-    if (it != by_name_.end()) {
-      return static_cast<T*>(it->second);
-    }
-
-    auto* type = types_.Create<T>(std::forward<ARGS>(args)...);
-    by_name_.emplace(name, type);
-    return type;
-  }
-
-  /// Wrap returns a new Manager created with the types of `inner`.
-  /// The Manager returned by Wrap is intended to temporarily extend the types
-  /// of an existing immutable Manager.
-  /// As the copied types are owned by `inner`, `inner` must not be destructed
-  /// or assigned while using the returned Manager.
-  /// TODO(bclayton) - Evaluate whether there are safer alternatives to this
-  /// function. See crbug.com/tint/460.
-  /// @param inner the immutable Manager to extend
-  /// @return the Manager that wraps `inner`
-  static Manager Wrap(const Manager& inner) {
-    Manager out;
-    out.by_name_ = inner.by_name_;
-    return out;
-  }
-
-  /// Returns the type map
-  /// @returns the mapping from name string to type.
-  const std::unordered_map<std::string, sem::Type*>& types() const {
-    return by_name_;
-  }
-
-  /// @returns an iterator to the beginning of the types
-  Iterator begin() const { return types_.Objects().begin(); }
-  /// @returns an iterator to the end of the types
-  Iterator end() const { return types_.Objects().end(); }
-
- private:
-  std::unordered_map<std::string, sem::Type*> by_name_;
-  BlockAllocator<sem::Type> types_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TYPE_MANAGER_H_
diff --git a/src/sem/type_manager_test.cc b/src/sem/type_manager_test.cc
deleted file mode 100644
index dcd8f05..0000000
--- a/src/sem/type_manager_test.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/type_manager.h"
-
-#include "gtest/gtest.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/u32_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-template <typename T>
-size_t count(const T& range_loopable) {
-  size_t n = 0;
-  for (auto it : range_loopable) {
-    (void)it;
-    n++;
-  }
-  return n;
-}
-
-using TypeManagerTest = testing::Test;
-
-TEST_F(TypeManagerTest, GetUnregistered) {
-  Manager tm;
-  auto* t = tm.Get<I32>();
-  ASSERT_NE(t, nullptr);
-  EXPECT_TRUE(t->Is<I32>());
-}
-
-TEST_F(TypeManagerTest, GetSameTypeReturnsSamePtr) {
-  Manager tm;
-  auto* t = tm.Get<I32>();
-  ASSERT_NE(t, nullptr);
-  EXPECT_TRUE(t->Is<I32>());
-
-  auto* t2 = tm.Get<I32>();
-  EXPECT_EQ(t, t2);
-}
-
-TEST_F(TypeManagerTest, GetDifferentTypeReturnsDifferentPtr) {
-  Manager tm;
-  Type* t = tm.Get<I32>();
-  ASSERT_NE(t, nullptr);
-  EXPECT_TRUE(t->Is<I32>());
-
-  Type* t2 = tm.Get<U32>();
-  ASSERT_NE(t2, nullptr);
-  EXPECT_NE(t, t2);
-  EXPECT_TRUE(t2->Is<U32>());
-}
-
-TEST_F(TypeManagerTest, WrapDoesntAffectInner) {
-  Manager inner;
-  Manager outer = Manager::Wrap(inner);
-
-  inner.Get<I32>();
-
-  EXPECT_EQ(count(inner), 1u);
-  EXPECT_EQ(count(outer), 0u);
-
-  outer.Get<U32>();
-
-  EXPECT_EQ(count(inner), 1u);
-  EXPECT_EQ(count(outer), 1u);
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/type_mappings.h b/src/sem/type_mappings.h
deleted file mode 100644
index 5dbc059..0000000
--- a/src/sem/type_mappings.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_TYPE_MAPPINGS_H_
-#define SRC_SEM_TYPE_MAPPINGS_H_
-
-#include <type_traits>
-
-namespace tint {
-
-// Forward declarations
-namespace ast {
-class Array;
-class CallExpression;
-class Expression;
-class ElseStatement;
-class ForLoopStatement;
-class Function;
-class IfStatement;
-class MemberAccessorExpression;
-class Node;
-class Statement;
-class Struct;
-class StructMember;
-class Type;
-class TypeDecl;
-class Variable;
-}  // namespace ast
-
-namespace sem {
-// Forward declarations
-class Array;
-class Call;
-class Expression;
-class ElseStatement;
-class ForLoopStatement;
-class Function;
-class IfStatement;
-class MemberAccessorExpression;
-class Node;
-class Statement;
-class Struct;
-class StructMember;
-class Type;
-class Variable;
-
-/// TypeMappings is a struct that holds undefined `operator()` methods that's
-/// used by SemanticNodeTypeFor to map AST / type node types to their
-/// corresponding semantic node types. The standard operator overload resolving
-/// rules will be used to infer the return type based on the argument type.
-struct TypeMappings {
-  //! @cond Doxygen_Suppress
-  Array* operator()(ast::Array*);
-  Call* operator()(ast::CallExpression*);
-  Expression* operator()(ast::Expression*);
-  ElseStatement* operator()(ast::ElseStatement*);
-  ForLoopStatement* operator()(ast::ForLoopStatement*);
-  Function* operator()(ast::Function*);
-  IfStatement* operator()(ast::IfStatement*);
-  MemberAccessorExpression* operator()(ast::MemberAccessorExpression*);
-  Node* operator()(ast::Node*);
-  Statement* operator()(ast::Statement*);
-  Struct* operator()(ast::Struct*);
-  StructMember* operator()(ast::StructMember*);
-  Type* operator()(ast::Type*);
-  Type* operator()(ast::TypeDecl*);
-  Variable* operator()(ast::Variable*);
-  //! @endcond
-};
-
-/// SemanticNodeTypeFor resolves to the appropriate sem::Node type for the
-/// AST or type node `AST_OR_TYPE`.
-template <typename AST_OR_TYPE>
-using SemanticNodeTypeFor = typename std::remove_pointer<decltype(
-    TypeMappings()(std::declval<AST_OR_TYPE*>()))>::type;
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_TYPE_MAPPINGS_H_
diff --git a/src/sem/u32_type.cc b/src/sem/u32_type.cc
deleted file mode 100644
index 6383814..0000000
--- a/src/sem/u32_type.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/u32_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::U32);
-
-namespace tint {
-namespace sem {
-
-U32::U32() = default;
-
-U32::~U32() = default;
-
-U32::U32(U32&&) = default;
-
-std::string U32::type_name() const {
-  return "__u32";
-}
-
-std::string U32::FriendlyName(const SymbolTable&) const {
-  return "u32";
-}
-
-bool U32::IsConstructible() const {
-  return true;
-}
-
-uint32_t U32::Size() const {
-  return 4;
-}
-
-uint32_t U32::Align() const {
-  return 4;
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/u32_type.h b/src/sem/u32_type.h
deleted file mode 100644
index 8d4bb06..0000000
--- a/src/sem/u32_type.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_U32_TYPE_H_
-#define SRC_SEM_U32_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A unsigned int 32 type.
-class U32 : public Castable<U32, Type> {
- public:
-  /// Constructor
-  U32();
-  /// Move constructor
-  U32(U32&&);
-  ~U32() override;
-
-  /// @returns the name for th type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the size in bytes of the type.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type.
-  uint32_t Align() const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_U32_TYPE_H_
diff --git a/src/sem/u32_type_test.cc b/src/sem/u32_type_test.cc
deleted file mode 100644
index 17663dc..0000000
--- a/src/sem/u32_type_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using U32Test = TestHelper;
-
-TEST_F(U32Test, TypeName) {
-  U32 u;
-  EXPECT_EQ(u.type_name(), "__u32");
-}
-
-TEST_F(U32Test, FriendlyName) {
-  U32 u;
-  EXPECT_EQ(u.FriendlyName(Symbols()), "u32");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/variable.cc b/src/sem/variable.cc
deleted file mode 100644
index 96375f7..0000000
--- a/src/sem/variable.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/variable.h"
-
-#include <utility>
-
-#include "src/ast/identifier_expression.h"
-#include "src/ast/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Variable);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::GlobalVariable);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::LocalVariable);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Parameter);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::VariableUser);
-
-namespace tint {
-namespace sem {
-
-Variable::Variable(const ast::Variable* declaration,
-                   const sem::Type* type,
-                   ast::StorageClass storage_class,
-                   ast::Access access,
-                   Constant constant_value)
-    : declaration_(declaration),
-      type_(type),
-      storage_class_(storage_class),
-      access_(access),
-      constant_value_(constant_value) {}
-
-Variable::~Variable() = default;
-
-LocalVariable::LocalVariable(const ast::Variable* declaration,
-                             const sem::Type* type,
-                             ast::StorageClass storage_class,
-                             ast::Access access,
-                             const sem::Statement* statement,
-                             Constant constant_value)
-    : Base(declaration, type, storage_class, access, std::move(constant_value)),
-      statement_(statement) {}
-
-LocalVariable::~LocalVariable() = default;
-
-GlobalVariable::GlobalVariable(const ast::Variable* declaration,
-                               const sem::Type* type,
-                               ast::StorageClass storage_class,
-                               ast::Access access,
-                               Constant constant_value,
-                               sem::BindingPoint binding_point)
-    : Base(declaration, type, storage_class, access, std::move(constant_value)),
-      binding_point_(binding_point) {}
-
-GlobalVariable::~GlobalVariable() = default;
-
-Parameter::Parameter(const ast::Variable* declaration,
-                     uint32_t index,
-                     const sem::Type* type,
-                     ast::StorageClass storage_class,
-                     ast::Access access,
-                     const ParameterUsage usage /* = ParameterUsage::kNone */)
-    : Base(declaration, type, storage_class, access, Constant{}),
-      index_(index),
-      usage_(usage) {}
-
-Parameter::~Parameter() = default;
-
-VariableUser::VariableUser(const ast::IdentifierExpression* declaration,
-                           Statement* statement,
-                           sem::Variable* variable)
-    : Base(declaration,
-           variable->Type(),
-           statement,
-           variable->ConstantValue(),
-           /* has_side_effects */ false),
-      variable_(variable) {}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/variable.h b/src/sem/variable.h
deleted file mode 100644
index 0641183..0000000
--- a/src/sem/variable.h
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0(the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_VARIABLE_H_
-#define SRC_SEM_VARIABLE_H_
-
-#include <utility>
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/ast/storage_class.h"
-#include "src/sem/binding_point.h"
-#include "src/sem/expression.h"
-#include "src/sem/parameter_usage.h"
-
-namespace tint {
-
-// Forward declarations
-namespace ast {
-class IdentifierExpression;
-class Variable;
-}  // namespace ast
-
-namespace sem {
-
-// Forward declarations
-class CallTarget;
-class Type;
-class VariableUser;
-
-/// Variable is the base class for local variables, global variables and
-/// parameters.
-class Variable : public Castable<Variable, Node> {
- public:
-  /// Constructor
-  /// @param declaration the AST declaration node
-  /// @param type the variable type
-  /// @param storage_class the variable storage class
-  /// @param access the variable access control type
-  /// @param constant_value the constant value for the variable. May be invalid
-  Variable(const ast::Variable* declaration,
-           const sem::Type* type,
-           ast::StorageClass storage_class,
-           ast::Access access,
-           Constant constant_value);
-
-  /// Destructor
-  ~Variable() override;
-
-  /// @returns the AST declaration node
-  const ast::Variable* Declaration() const { return declaration_; }
-
-  /// @returns the canonical type for the variable
-  const sem::Type* Type() const { return type_; }
-
-  /// @returns the storage class for the variable
-  ast::StorageClass StorageClass() const { return storage_class_; }
-
-  /// @returns the access control for the variable
-  ast::Access Access() const { return access_; }
-
-  /// @return the constant value of this expression
-  const Constant& ConstantValue() const { return constant_value_; }
-
-  /// @returns the variable constructor expression, or nullptr if the variable
-  /// does not have one.
-  const Expression* Constructor() const { return constructor_; }
-
-  /// Sets the variable constructor expression.
-  /// @param constructor the constructor expression to assign to this variable.
-  void SetConstructor(const Expression* constructor) {
-    constructor_ = constructor;
-  }
-
-  /// @returns the expressions that use the variable
-  const std::vector<const VariableUser*>& Users() const { return users_; }
-
-  /// @param user the user to add
-  void AddUser(const VariableUser* user) { users_.emplace_back(user); }
-
- private:
-  const ast::Variable* const declaration_;
-  const sem::Type* const type_;
-  const ast::StorageClass storage_class_;
-  const ast::Access access_;
-  const Constant constant_value_;
-  const Expression* constructor_ = nullptr;
-  std::vector<const VariableUser*> users_;
-};
-
-/// LocalVariable is a function-scope variable
-class LocalVariable : public Castable<LocalVariable, Variable> {
- public:
-  /// Constructor
-  /// @param declaration the AST declaration node
-  /// @param type the variable type
-  /// @param storage_class the variable storage class
-  /// @param access the variable access control type
-  /// @param statement the statement that declared this local variable
-  /// @param constant_value the constant value for the variable. May be invalid
-  LocalVariable(const ast::Variable* declaration,
-                const sem::Type* type,
-                ast::StorageClass storage_class,
-                ast::Access access,
-                const sem::Statement* statement,
-                Constant constant_value);
-
-  /// Destructor
-  ~LocalVariable() override;
-
-  /// @returns the statement that declares this local variable
-  const sem::Statement* Statement() const { return statement_; }
-
-  /// @returns the Type, Function or Variable that this local variable shadows
-  const sem::Node* Shadows() const { return shadows_; }
-
-  /// Sets the Type, Function or Variable that this local variable shadows
-  /// @param shadows the Type, Function or Variable that this variable shadows
-  void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
-
- private:
-  const sem::Statement* const statement_;
-  const sem::Node* shadows_ = nullptr;
-};
-
-/// GlobalVariable is a module-scope variable
-class GlobalVariable : public Castable<GlobalVariable, Variable> {
- public:
-  /// Constructor
-  /// @param declaration the AST declaration node
-  /// @param type the variable type
-  /// @param storage_class the variable storage class
-  /// @param access the variable access control type
-  /// @param constant_value the constant value for the variable. May be invalid
-  /// @param binding_point the optional resource binding point of the variable
-  GlobalVariable(const ast::Variable* declaration,
-                 const sem::Type* type,
-                 ast::StorageClass storage_class,
-                 ast::Access access,
-                 Constant constant_value,
-                 sem::BindingPoint binding_point = {});
-
-  /// Destructor
-  ~GlobalVariable() override;
-
-  /// @returns the resource binding point for the variable
-  sem::BindingPoint BindingPoint() const { return binding_point_; }
-
-  /// @param id the constant identifier to assign to this variable
-  void SetConstantId(uint16_t id) {
-    constant_id_ = id;
-    is_overridable_ = true;
-  }
-
-  /// @returns the pipeline constant ID associated with the variable
-  uint16_t ConstantId() const { return constant_id_; }
-
-  /// @param is_overridable true if this is a pipeline overridable constant
-  void SetIsOverridable(bool is_overridable = true) {
-    is_overridable_ = is_overridable;
-  }
-
-  /// @returns true if this is pipeline overridable constant
-  bool IsOverridable() const { return is_overridable_; }
-
- private:
-  const sem::BindingPoint binding_point_;
-
-  bool is_overridable_ = false;
-  uint16_t constant_id_ = 0;
-};
-
-/// Parameter is a function parameter
-class Parameter : public Castable<Parameter, Variable> {
- public:
-  /// Constructor for function parameters
-  /// @param declaration the AST declaration node
-  /// @param index the index of the parmeter in the function
-  /// @param type the variable type
-  /// @param storage_class the variable storage class
-  /// @param access the variable access control type
-  /// @param usage the semantic usage for the parameter
-  Parameter(const ast::Variable* declaration,
-            uint32_t index,
-            const sem::Type* type,
-            ast::StorageClass storage_class,
-            ast::Access access,
-            const ParameterUsage usage = ParameterUsage::kNone);
-
-  /// Destructor
-  ~Parameter() override;
-
-  /// @return the index of the parmeter in the function
-  uint32_t Index() const { return index_; }
-
-  /// @returns the semantic usage for the parameter
-  ParameterUsage Usage() const { return usage_; }
-
-  /// @returns the CallTarget owner of this parameter
-  CallTarget const* Owner() const { return owner_; }
-
-  /// @param owner the CallTarget owner of this parameter
-  void SetOwner(CallTarget const* owner) { owner_ = owner; }
-
-  /// @returns the Type, Function or Variable that this local variable shadows
-  const sem::Node* Shadows() const { return shadows_; }
-
-  /// Sets the Type, Function or Variable that this local variable shadows
-  /// @param shadows the Type, Function or Variable that this variable shadows
-  void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
-
- private:
-  const uint32_t index_;
-  const ParameterUsage usage_;
-  CallTarget const* owner_ = nullptr;
-  const sem::Node* shadows_ = nullptr;
-};
-
-/// ParameterList is a list of Parameter
-using ParameterList = std::vector<const Parameter*>;
-
-/// VariableUser holds the semantic information for an identifier expression
-/// node that resolves to a variable.
-class VariableUser : public Castable<VariableUser, Expression> {
- public:
-  /// Constructor
-  /// @param declaration the AST identifier node
-  /// @param statement the statement that owns this expression
-  /// @param variable the semantic variable
-  VariableUser(const ast::IdentifierExpression* declaration,
-               Statement* statement,
-               sem::Variable* variable);
-
-  /// @returns the variable that this expression refers to
-  const sem::Variable* Variable() const { return variable_; }
-
- private:
-  const sem::Variable* const variable_;
-};
-
-/// A pair of sem::Variables. Can be hashed.
-typedef std::pair<const Variable*, const Variable*> VariablePair;
-
-}  // namespace sem
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for VariablePair
-template <>
-class hash<tint::sem::VariablePair> {
- public:
-  /// @param i the variable pair to create a hash for
-  /// @return the hash value
-  inline std::size_t operator()(const tint::sem::VariablePair& i) const {
-    return tint::utils::Hash(i.first, i.second);
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_SEM_VARIABLE_H_
diff --git a/src/sem/vector_type.cc b/src/sem/vector_type.cc
deleted file mode 100644
index 20faa13..0000000
--- a/src/sem/vector_type.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/vector_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Vector);
-
-namespace tint {
-namespace sem {
-
-Vector::Vector(Type const* subtype, uint32_t width)
-    : subtype_(subtype), width_(width) {
-  TINT_ASSERT(Semantic, width_ > 1);
-  TINT_ASSERT(Semantic, width_ < 5);
-}
-
-Vector::Vector(Vector&&) = default;
-
-Vector::~Vector() = default;
-
-std::string Vector::type_name() const {
-  return "__vec_" + std::to_string(width_) + subtype_->type_name();
-}
-
-std::string Vector::FriendlyName(const SymbolTable& symbols) const {
-  std::ostringstream out;
-  out << "vec" << width_ << "<" << subtype_->FriendlyName(symbols) << ">";
-  return out.str();
-}
-
-bool Vector::IsConstructible() const {
-  return true;
-}
-
-uint32_t Vector::Size() const {
-  return SizeOf(width_);
-}
-
-uint32_t Vector::Align() const {
-  return AlignOf(width_);
-}
-
-uint32_t Vector::SizeOf(uint32_t width) {
-  switch (width) {
-    case 2:
-      return 8;
-    case 3:
-      return 12;
-    case 4:
-      return 16;
-  }
-  return 0;  // Unreachable
-}
-
-uint32_t Vector::AlignOf(uint32_t width) {
-  switch (width) {
-    case 2:
-      return 8;
-    case 3:
-      return 16;
-    case 4:
-      return 16;
-  }
-  return 0;  // Unreachable
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/vector_type.h b/src/sem/vector_type.h
deleted file mode 100644
index 3f3b714..0000000
--- a/src/sem/vector_type.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_VECTOR_TYPE_H_
-#define SRC_SEM_VECTOR_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A vector type.
-class Vector : public Castable<Vector, Type> {
- public:
-  /// Constructor
-  /// @param subtype the vector element type
-  /// @param size the number of elements in the vector
-  Vector(Type const* subtype, uint32_t size);
-  /// Move constructor
-  Vector(Vector&&);
-  ~Vector() override;
-
-  /// @returns the type of the vector elements
-  const Type* type() const { return subtype_; }
-
-  /// @returns the name for th type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
-  /// @returns true if constructible as per
-  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
-  bool IsConstructible() const override;
-
-  /// @returns the number of elements in the vector
-  uint32_t Width() const { return width_; }
-
-  /// @returns the size in bytes of the type. This may include tail padding.
-  uint32_t Size() const override;
-
-  /// @returns the alignment in bytes of the type. This may include tail
-  /// padding.
-  uint32_t Align() const override;
-
-  /// @param width the width of the vector
-  /// @returns the size in bytes of a vector of the given width.
-  static uint32_t SizeOf(uint32_t width);
-
-  /// @param width the width of the vector
-  /// @returns the alignment in bytes of a vector of the given width.
-  static uint32_t AlignOf(uint32_t width);
-
- private:
-  Type const* const subtype_;
-  const uint32_t width_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_VECTOR_TYPE_H_
diff --git a/src/sem/vector_type_test.cc b/src/sem/vector_type_test.cc
deleted file mode 100644
index 6059972..0000000
--- a/src/sem/vector_type_test.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using VectorTest = TestHelper;
-
-TEST_F(VectorTest, Creation) {
-  I32 i32;
-  Vector v{&i32, 2};
-  EXPECT_EQ(v.type(), &i32);
-  EXPECT_EQ(v.Width(), 2u);
-}
-
-TEST_F(VectorTest, TypeName) {
-  auto* i32 = create<I32>();
-  auto* v = create<Vector>(i32, 3);
-  EXPECT_EQ(v->type_name(), "__vec_3__i32");
-}
-
-TEST_F(VectorTest, FriendlyName) {
-  auto* f32 = create<F32>();
-  auto* v = create<Vector>(f32, 3);
-  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/void_type.cc b/src/sem/void_type.cc
deleted file mode 100644
index 7792f92..0000000
--- a/src/sem/void_type.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/void_type.h"
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Void);
-
-namespace tint {
-namespace sem {
-
-Void::Void() = default;
-
-Void::Void(Void&&) = default;
-
-Void::~Void() = default;
-
-std::string Void::type_name() const {
-  return "__void";
-}
-
-std::string Void::FriendlyName(const SymbolTable&) const {
-  return "void";
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/void_type.h b/src/sem/void_type.h
deleted file mode 100644
index 02fc754..0000000
--- a/src/sem/void_type.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_VOID_TYPE_H_
-#define SRC_SEM_VOID_TYPE_H_
-
-#include <string>
-
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A void type
-class Void : public Castable<Void, Type> {
- public:
-  /// Constructor
-  Void();
-  /// Move constructor
-  Void(Void&&);
-  ~Void() override;
-
-  /// @returns the name for this type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_VOID_TYPE_H_
diff --git a/src/source.cc b/src/source.cc
deleted file mode 100644
index c2041ec..0000000
--- a/src/source.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2020 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/source.h"
-
-#include <algorithm>
-#include <sstream>
-#include <string_view>
-#include <utility>
-
-namespace tint {
-namespace {
-std::vector<std::string_view> SplitLines(std::string_view str) {
-  std::vector<std::string_view> lines;
-
-  size_t lineStart = 0;
-  for (size_t i = 0; i < str.size(); ++i) {
-    if (str[i] == '\n') {
-      lines.push_back(str.substr(lineStart, i - lineStart));
-      lineStart = i + 1;
-    }
-  }
-  if (lineStart < str.size()) {
-    lines.push_back(str.substr(lineStart));
-  }
-
-  return lines;
-}
-
-std::vector<std::string_view> CopyRelativeStringViews(
-    const std::vector<std::string_view>& src_list,
-    const std::string_view& src_view,
-    const std::string_view& dst_view) {
-  std::vector<std::string_view> out(src_list.size());
-  for (size_t i = 0; i < src_list.size(); i++) {
-    auto offset = static_cast<size_t>(&src_list[i].front() - &src_view.front());
-    auto count = src_list[i].length();
-    out[i] = dst_view.substr(offset, count);
-  }
-  return out;
-}
-
-}  // namespace
-
-Source::FileContent::FileContent(const std::string& body)
-    : data(body), data_view(data), lines(SplitLines(data_view)) {}
-
-Source::FileContent::FileContent(const FileContent& rhs)
-    : data(rhs.data),
-      data_view(data),
-      lines(CopyRelativeStringViews(rhs.lines, rhs.data_view, data_view)) {}
-
-Source::FileContent::~FileContent() = default;
-
-Source::File::~File() = default;
-
-std::ostream& operator<<(std::ostream& out, const Source& source) {
-  auto rng = source.range;
-
-  if (source.file) {
-    out << source.file->path << ":";
-  }
-  if (rng.begin.line) {
-    out << rng.begin.line << ":";
-    if (rng.begin.column) {
-      out << rng.begin.column;
-    }
-
-    if (source.file) {
-      out << std::endl << std::endl;
-
-      auto repeat = [&](char c, size_t n) {
-        while (n--) {
-          out << c;
-        }
-      };
-
-      for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
-        if (line < source.file->content.lines.size() + 1) {
-          auto len = source.file->content.lines[line - 1].size();
-
-          out << source.file->content.lines[line - 1];
-
-          out << std::endl;
-
-          if (line == rng.begin.line && line == rng.end.line) {
-            // Single line
-            repeat(' ', rng.begin.column - 1);
-            repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
-          } else if (line == rng.begin.line) {
-            // Start of multi-line
-            repeat(' ', rng.begin.column - 1);
-            repeat('^', len - (rng.begin.column - 1));
-          } else if (line == rng.end.line) {
-            // End of multi-line
-            repeat('^', rng.end.column - 1);
-          } else {
-            // Middle of multi-line
-            repeat('^', len);
-          }
-
-          out << std::endl;
-        }
-      }
-    }
-  }
-  return out;
-}
-
-}  // namespace tint
diff --git a/src/source.h b/src/source.h
deleted file mode 100644
index b40c994..0000000
--- a/src/source.h
+++ /dev/null
@@ -1,238 +0,0 @@
-
-// Copyright 2020 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_SOURCE_H_
-#define SRC_SOURCE_H_
-
-#include <iostream>
-#include <string>
-#include <string_view>
-#include <tuple>
-#include <vector>
-
-namespace tint {
-
-/// Source describes a range of characters within a source file.
-class Source {
- public:
-  /// FileContent describes the content of a source file encoded using utf-8.
-  class FileContent {
-   public:
-    /// Constructs the FileContent with the given file content.
-    /// @param data the file contents
-    explicit FileContent(const std::string& data);
-
-    /// Copy constructor
-    /// @param rhs the FileContent to copy
-    FileContent(const FileContent& rhs);
-
-    /// Destructor
-    ~FileContent();
-
-    /// The original un-split file content
-    const std::string data;
-    /// A string_view over #data
-    const std::string_view data_view;
-    /// #data split by lines
-    const std::vector<std::string_view> lines;
-  };
-
-  /// File describes a source file, including path and content.
-  class File {
-   public:
-    /// Constructs the File with the given file path and content.
-    /// @param p the path for this file
-    /// @param c the file contents
-    inline File(const std::string& p, const std::string& c)
-        : path(p), content(c) {}
-
-    /// Copy constructor
-    File(const File&) = default;
-
-    /// Move constructor
-    File(File&&) = default;
-
-    /// Destructor
-    ~File();
-
-    /// file path
-    const std::string path;
-    /// file content
-    const FileContent content;
-  };
-
-  /// Location holds a 1-based line and column index.
-  class Location {
-   public:
-    /// the 1-based line number. 0 represents no line information.
-    size_t line = 0;
-    /// the 1-based column number in utf8-code units (bytes).
-    /// 0 represents no column information.
-    size_t column = 0;
-
-    /// Returns true of `this` location is lexicographically less than `rhs`
-    /// @param rhs location to compare against
-    /// @returns true if `this` < `rhs`
-    inline bool operator<(const Source::Location& rhs) {
-      return std::tie(line, column) < std::tie(rhs.line, rhs.column);
-    }
-
-    /// Returns true of `this` location is equal to `rhs`
-    /// @param rhs location to compare against
-    /// @returns true if `this` == `rhs`
-    inline bool operator==(const Location& rhs) const {
-      return line == rhs.line && column == rhs.column;
-    }
-
-    /// Returns true of `this` location is not equal to `rhs`
-    /// @param rhs location to compare against
-    /// @returns true if `this` != `rhs`
-    inline bool operator!=(const Location& rhs) const {
-      return !(*this == rhs);
-    }
-  };
-
-  /// Range holds a Location interval described by [begin, end).
-  class Range {
-   public:
-    /// Constructs a zero initialized Range.
-    inline Range() = default;
-
-    /// Constructs a zero-length Range starting at `loc`
-    /// @param loc the start and end location for the range
-    inline constexpr explicit Range(const Location& loc)
-        : begin(loc), end(loc) {}
-
-    /// Constructs the Range beginning at `b` and ending at `e`
-    /// @param b the range start location
-    /// @param e the range end location
-    inline constexpr Range(const Location& b, const Location& e)
-        : begin(b), end(e) {}
-
-    /// Return a column-shifted Range
-    /// @param n the number of characters to shift by
-    /// @returns a Range with a #begin and #end column shifted by `n`
-    inline Range operator+(size_t n) const {
-      return Range{{begin.line, begin.column + n}, {end.line, end.column + n}};
-    }
-
-    /// Returns true of `this` range is not equal to `rhs`
-    /// @param rhs range to compare against
-    /// @returns true if `this` != `rhs`
-    inline bool operator==(const Range& rhs) const {
-      return begin == rhs.begin && end == rhs.end;
-    }
-
-    /// Returns true of `this` range is equal to `rhs`
-    /// @param rhs range to compare against
-    /// @returns true if `this` == `rhs`
-    inline bool operator!=(const Range& rhs) const { return !(*this == rhs); }
-
-    /// The location of the first character in the range.
-    Location begin;
-    /// The location of one-past the last character in the range.
-    Location end;
-  };
-
-  /// Constructs the Source with an zero initialized Range and null File.
-  inline Source() : range() {}
-
-  /// Constructs the Source with the Range `rng` and a null File
-  /// @param rng the source range
-  inline explicit Source(const Range& rng) : range(rng) {}
-
-  /// Constructs the Source with the Range `loc` and a null File
-  /// @param loc the start and end location for the source range
-  inline explicit Source(const Location& loc) : range(Range(loc)) {}
-
-  /// Constructs the Source with the Range `rng` and File `file`
-  /// @param rng the source range
-  /// @param f the source file
-  inline Source(const Range& rng, File const* f) : range(rng), file(f) {}
-
-  /// @returns a Source that points to the begin range of this Source.
-  inline Source Begin() const { return Source(Range{range.begin}, file); }
-
-  /// @returns a Source that points to the end range of this Source.
-  inline Source End() const { return Source(Range{range.end}, file); }
-
-  /// Return a column-shifted Source
-  /// @param n the number of characters to shift by
-  /// @returns a Source with the range's columns shifted by `n`
-  inline Source operator+(size_t n) const { return Source(range + n, file); }
-
-  /// Returns true of `this` Source is lexicographically less than `rhs`
-  /// @param rhs source to compare against
-  /// @returns true if `this` < `rhs`
-  inline bool operator<(const Source& rhs) {
-    if (file != rhs.file) {
-      return false;
-    }
-    return range.begin < rhs.range.begin;
-  }
-
-  /// Helper function that returns the range union of two source locations. The
-  /// `start` and `end` locations are assumed to refer to the same source file.
-  /// @param start the start source of the range
-  /// @param end the end source of the range
-  /// @returns the combined source
-  inline static Source Combine(const Source& start, const Source& end) {
-    return Source(Source::Range(start.range.begin, end.range.end), start.file);
-  }
-
-  /// range is the span of text this source refers to in #file
-  Range range;
-  /// file is the optional source content this source refers to
-  const File* file = nullptr;
-};
-
-/// Writes the Source::Location to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param loc the location to write
-/// @returns out so calls can be chained
-inline std::ostream& operator<<(std::ostream& out,
-                                const Source::Location& loc) {
-  out << loc.line << ":" << loc.column;
-  return out;
-}
-
-/// Writes the Source::Range to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param range the range to write
-/// @returns out so calls can be chained
-inline std::ostream& operator<<(std::ostream& out, const Source::Range& range) {
-  out << "[" << range.begin << ", " << range.end << "]";
-  return out;
-}
-
-/// Writes the Source to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param source the source to write
-/// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, const Source& source);
-
-/// Writes the Source::FileContent to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param content the file content to write
-/// @returns out so calls can be chained
-inline std::ostream& operator<<(std::ostream& out,
-                                const Source::FileContent& content) {
-  out << content.data;
-  return out;
-}
-
-}  // namespace tint
-
-#endif  // SRC_SOURCE_H_
diff --git a/src/source_test.cc b/src/source_test.cc
deleted file mode 100644
index 57ef4c4..0000000
--- a/src/source_test.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// 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/source.h"
-
-#include <memory>
-#include <utility>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-static constexpr const char* kSource = R"(line one
-line two
-line three)";
-
-using SourceFileContentTest = testing::Test;
-
-TEST_F(SourceFileContentTest, Ctor) {
-  Source::FileContent fc(kSource);
-  EXPECT_EQ(fc.data, kSource);
-  EXPECT_EQ(fc.data_view, kSource);
-  ASSERT_EQ(fc.lines.size(), 3u);
-  EXPECT_EQ(fc.lines[0], "line one");
-  EXPECT_EQ(fc.lines[1], "line two");
-  EXPECT_EQ(fc.lines[2], "line three");
-}
-
-TEST_F(SourceFileContentTest, CopyCtor) {
-  auto src = std::make_unique<Source::FileContent>(kSource);
-  Source::FileContent fc{*src};
-  src.reset();
-  EXPECT_EQ(fc.data, kSource);
-  EXPECT_EQ(fc.data_view, kSource);
-  ASSERT_EQ(fc.lines.size(), 3u);
-  EXPECT_EQ(fc.lines[0], "line one");
-  EXPECT_EQ(fc.lines[1], "line two");
-  EXPECT_EQ(fc.lines[2], "line three");
-}
-
-TEST_F(SourceFileContentTest, MoveCtor) {
-  auto src = std::make_unique<Source::FileContent>(kSource);
-  Source::FileContent fc{std::move(*src)};
-  src.reset();
-  EXPECT_EQ(fc.data, kSource);
-  EXPECT_EQ(fc.data_view, kSource);
-  ASSERT_EQ(fc.lines.size(), 3u);
-  EXPECT_EQ(fc.lines[0], "line one");
-  EXPECT_EQ(fc.lines[1], "line two");
-  EXPECT_EQ(fc.lines[2], "line three");
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/symbol.cc b/src/symbol.cc
deleted file mode 100644
index 19224ee..0000000
--- a/src/symbol.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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/symbol.h"
-
-#include <utility>
-
-namespace tint {
-
-Symbol::Symbol() = default;
-
-Symbol::Symbol(uint32_t val, tint::ProgramID program_id)
-    : val_(val), program_id_(program_id) {}
-
-#if TINT_SYMBOL_STORE_DEBUG_NAME
-Symbol::Symbol(uint32_t val, tint::ProgramID program_id, std::string debug_name)
-    : val_(val), program_id_(program_id), debug_name_(std::move(debug_name)) {}
-#endif
-
-Symbol::Symbol(const Symbol& o) = default;
-
-Symbol::Symbol(Symbol&& o) = default;
-
-Symbol::~Symbol() = default;
-
-Symbol& Symbol::operator=(const Symbol& o) = default;
-
-Symbol& Symbol::operator=(Symbol&& o) = default;
-
-bool Symbol::operator==(const Symbol& other) const {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_,
-                                         other.program_id_);
-  return val_ == other.val_;
-}
-
-bool Symbol::operator<(const Symbol& other) const {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_,
-                                         other.program_id_);
-  return val_ < other.val_;
-}
-
-std::string Symbol::to_str() const {
-  return "$" + std::to_string(val_);
-}
-
-}  // namespace tint
diff --git a/src/symbol.h b/src/symbol.h
deleted file mode 100644
index 51704ab..0000000
--- a/src/symbol.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2020 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_SYMBOL_H_
-#define SRC_SYMBOL_H_
-
-// If TINT_SYMBOL_STORE_DEBUG_NAME is 1, Symbol instances store a `debug_name_`
-// member initialized with the name of the identifier they represent. This
-// member is not exposed, but is useful for debugging purposes.
-#ifndef TINT_SYMBOL_STORE_DEBUG_NAME
-#define TINT_SYMBOL_STORE_DEBUG_NAME 0
-#endif
-
-#include <string>
-
-#include "src/program_id.h"
-
-namespace tint {
-
-/// A symbol representing a string in the system
-class Symbol {
- public:
-  /// Constructor
-  /// An invalid symbol
-  Symbol();
-  /// Constructor
-  /// @param val the symbol value
-  /// @param program_id the identifier of the program that owns this Symbol
-  Symbol(uint32_t val, tint::ProgramID program_id);
-#if TINT_SYMBOL_STORE_DEBUG_NAME
-  /// Constructor
-  /// @param val the symbol value
-  /// @param program_id the identifier of the program that owns this Symbol
-  /// @param debug_name name of symbols used only for debugging
-  Symbol(uint32_t val, tint::ProgramID program_id, std::string debug_name);
-#endif
-  /// Copy constructor
-  /// @param o the symbol to copy
-  Symbol(const Symbol& o);
-  /// Move constructor
-  /// @param o the symbol to move
-  Symbol(Symbol&& o);
-  /// Destructor
-  ~Symbol();
-
-  /// Copy assignment
-  /// @param o the other symbol
-  /// @returns the symbol after doing the copy
-  Symbol& operator=(const Symbol& o);
-  /// Move assignment
-  /// @param o the other symbol
-  /// @returns teh symbol after doing the move
-  Symbol& operator=(Symbol&& o);
-
-  /// Comparison operator
-  /// @param o the other symbol
-  /// @returns true if the symbols are the same
-  bool operator==(const Symbol& o) const;
-
-  /// Less-than operator
-  /// @param o the other symbol
-  /// @returns true if this symbol is ordered before symbol `o`
-  bool operator<(const Symbol& o) const;
-
-  /// @returns true if the symbol is valid
-  bool IsValid() const { return val_ != static_cast<uint32_t>(-1); }
-
-  /// @returns the value for the symbol
-  uint32_t value() const { return val_; }
-
-  /// Convert the symbol to a string
-  /// @return the string representation of the symbol
-  std::string to_str() const;
-
-  /// @returns the identifier of the Program that owns this symbol.
-  tint::ProgramID ProgramID() const { return program_id_; }
-
- private:
-  uint32_t val_ = static_cast<uint32_t>(-1);
-  tint::ProgramID program_id_;
-#if TINT_SYMBOL_STORE_DEBUG_NAME
-  std::string debug_name_;
-#endif
-};
-
-/// @param sym the Symbol
-/// @returns the ProgramID that owns the given Symbol
-inline ProgramID ProgramIDOf(Symbol sym) {
-  return sym.IsValid() ? sym.ProgramID() : ProgramID();
-}
-
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::Symbol so symbols can be used as
-/// keys for std::unordered_map and std::unordered_set.
-template <>
-class hash<tint::Symbol> {
- public:
-  /// @param sym the symbol to return
-  /// @return the Symbol internal value
-  inline std::size_t operator()(const tint::Symbol& sym) const {
-    return static_cast<std::size_t>(sym.value());
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_SYMBOL_H_
diff --git a/src/symbol_table.cc b/src/symbol_table.cc
deleted file mode 100644
index 07c7fa1..0000000
--- a/src/symbol_table.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 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/symbol_table.h"
-
-#include "src/debug.h"
-
-namespace tint {
-
-SymbolTable::SymbolTable(tint::ProgramID program_id)
-    : program_id_(program_id) {}
-
-SymbolTable::SymbolTable(const SymbolTable&) = default;
-
-SymbolTable::SymbolTable(SymbolTable&&) = default;
-
-SymbolTable::~SymbolTable() = default;
-
-SymbolTable& SymbolTable::operator=(const SymbolTable& other) = default;
-
-SymbolTable& SymbolTable::operator=(SymbolTable&&) = default;
-
-Symbol SymbolTable::Register(const std::string& name) {
-  TINT_ASSERT(Symbol, !name.empty());
-
-  auto it = name_to_symbol_.find(name);
-  if (it != name_to_symbol_.end())
-    return it->second;
-
-#if TINT_SYMBOL_STORE_DEBUG_NAME
-  Symbol sym(next_symbol_, program_id_, name);
-#else
-  Symbol sym(next_symbol_, program_id_);
-#endif
-  ++next_symbol_;
-
-  name_to_symbol_[name] = sym;
-  symbol_to_name_[sym] = name;
-
-  return sym;
-}
-
-Symbol SymbolTable::Get(const std::string& name) const {
-  auto it = name_to_symbol_.find(name);
-  return it != name_to_symbol_.end() ? it->second : Symbol();
-}
-
-std::string SymbolTable::NameFor(const Symbol symbol) const {
-  TINT_ASSERT_PROGRAM_IDS_EQUAL(Symbol, program_id_, symbol);
-  auto it = symbol_to_name_.find(symbol);
-  if (it == symbol_to_name_.end()) {
-    return symbol.to_str();
-  }
-
-  return it->second;
-}
-
-Symbol SymbolTable::New(std::string prefix /* = "" */) {
-  if (prefix.empty()) {
-    prefix = "tint_symbol";
-  }
-  auto it = name_to_symbol_.find(prefix);
-  if (it == name_to_symbol_.end()) {
-    return Register(prefix);
-  }
-  std::string name;
-  size_t i = 1;
-  do {
-    name = prefix + "_" + std::to_string(i++);
-  } while (name_to_symbol_.count(name));
-  return Register(name);
-}
-
-}  // namespace tint
diff --git a/src/symbol_table.h b/src/symbol_table.h
deleted file mode 100644
index 3967c7e..0000000
--- a/src/symbol_table.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2020 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_SYMBOL_TABLE_H_
-#define SRC_SYMBOL_TABLE_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/symbol.h"
-
-namespace tint {
-
-/// Holds mappings from symbols to their associated string names
-class SymbolTable {
- public:
-  /// Constructor
-  /// @param program_id the identifier of the program that owns this symbol
-  /// table
-  explicit SymbolTable(tint::ProgramID program_id);
-  /// Copy constructor
-  SymbolTable(const SymbolTable&);
-  /// Move Constructor
-  SymbolTable(SymbolTable&&);
-  /// Destructor
-  ~SymbolTable();
-
-  /// Copy assignment
-  /// @param other the symbol table to copy
-  /// @returns the new symbol table
-  SymbolTable& operator=(const SymbolTable& other);
-  /// Move assignment
-  /// @param other the symbol table to move
-  /// @returns the symbol table
-  SymbolTable& operator=(SymbolTable&& other);
-
-  /// Registers a name into the symbol table, returning the Symbol.
-  /// @param name the name to register
-  /// @returns the symbol representing the given name
-  Symbol Register(const std::string& name);
-
-  /// Returns the symbol for the given `name`
-  /// @param name the name to lookup
-  /// @returns the symbol for the name or symbol::kInvalid if not found.
-  Symbol Get(const std::string& name) const;
-
-  /// Returns the name for the given symbol
-  /// @param symbol the symbol to retrieve the name for
-  /// @returns the symbol name or "" if not found
-  std::string NameFor(const Symbol symbol) const;
-
-  /// Returns a new unique symbol with the given name, possibly suffixed with a
-  /// unique number.
-  /// @param name the symbol name
-  /// @returns a new, unnamed symbol with the given name. If the name is already
-  /// taken then this will be suffixed with an underscore and a unique numerical
-  /// value
-  Symbol New(std::string name = "");
-
-  /// Foreach calls the callback function `F` for each symbol in the table.
-  /// @param callback must be a function or function-like object with the
-  /// signature: `void(Symbol, const std::string&)`
-  template <typename F>
-  void Foreach(F&& callback) const {
-    for (auto it : symbol_to_name_) {
-      callback(it.first, it.second);
-    }
-  }
-
-  /// @returns the identifier of the Program that owns this symbol table.
-  tint::ProgramID ProgramID() const { return program_id_; }
-
- private:
-  // The value to be associated to the next registered symbol table entry.
-  uint32_t next_symbol_ = 1;
-
-  std::unordered_map<Symbol, std::string> symbol_to_name_;
-  std::unordered_map<std::string, Symbol> name_to_symbol_;
-  tint::ProgramID program_id_;
-};
-
-/// @param symbol_table the SymbolTable
-/// @returns the ProgramID that owns the given SymbolTable
-inline ProgramID ProgramIDOf(const SymbolTable& symbol_table) {
-  return symbol_table.ProgramID();
-}
-
-}  // namespace tint
-
-#endif  // SRC_SYMBOL_TABLE_H_
diff --git a/src/symbol_table_test.cc b/src/symbol_table_test.cc
deleted file mode 100644
index 4d5b07c..0000000
--- a/src/symbol_table_test.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 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/symbol_table.h"
-
-#include "gtest/gtest-spi.h"
-
-namespace tint {
-namespace {
-
-using SymbolTableTest = testing::Test;
-
-TEST_F(SymbolTableTest, GeneratesSymbolForName) {
-  auto program_id = ProgramID::New();
-  SymbolTable s{program_id};
-  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
-  EXPECT_EQ(Symbol(2, program_id), s.Register("another_name"));
-}
-
-TEST_F(SymbolTableTest, DeduplicatesNames) {
-  auto program_id = ProgramID::New();
-  SymbolTable s{program_id};
-  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
-  EXPECT_EQ(Symbol(2, program_id), s.Register("another_name"));
-  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
-}
-
-TEST_F(SymbolTableTest, ReturnsNameForSymbol) {
-  auto program_id = ProgramID::New();
-  SymbolTable s{program_id};
-  auto sym = s.Register("name");
-  EXPECT_EQ("name", s.NameFor(sym));
-}
-
-TEST_F(SymbolTableTest, ReturnsBlankForMissingSymbol) {
-  auto program_id = ProgramID::New();
-  SymbolTable s{program_id};
-  EXPECT_EQ("$2", s.NameFor(Symbol(2, program_id)));
-}
-
-TEST_F(SymbolTableTest, AssertsForBlankString) {
-  EXPECT_FATAL_FAILURE(
-      {
-        auto program_id = ProgramID::New();
-        SymbolTable s{program_id};
-        s.Register("");
-      },
-      "internal compiler error");
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/symbol_test.cc b/src/symbol_test.cc
deleted file mode 100644
index 3c13a92..0000000
--- a/src/symbol_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/symbol.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace {
-
-using SymbolTest = testing::Test;
-
-TEST_F(SymbolTest, ToStr) {
-  Symbol sym(1, ProgramID::New());
-  EXPECT_EQ("$1", sym.to_str());
-}
-
-TEST_F(SymbolTest, CopyAssign) {
-  Symbol sym1(1, ProgramID::New());
-  Symbol sym2;
-
-  EXPECT_FALSE(sym2.IsValid());
-  sym2 = sym1;
-  EXPECT_TRUE(sym2.IsValid());
-  EXPECT_EQ(sym2, sym1);
-}
-
-TEST_F(SymbolTest, Comparison) {
-  auto program_id = ProgramID::New();
-  Symbol sym1(1, program_id);
-  Symbol sym2(2, program_id);
-  Symbol sym3(1, program_id);
-
-  EXPECT_TRUE(sym1 == sym3);
-  EXPECT_FALSE(sym1 == sym2);
-  EXPECT_FALSE(sym3 == sym2);
-}
-
-}  // namespace
-}  // namespace tint
diff --git a/src/test_main.cc b/src/test_main.cc
deleted file mode 100644
index ba5971a..0000000
--- a/src/test_main.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/program.h"
-
-#if TINT_BUILD_SPV_READER
-#include "src/reader/spirv/parser_impl_test_helper.h"
-#endif
-
-#if TINT_BUILD_WGSL_WRITER
-#include "src/writer/wgsl/generator.h"
-#endif
-
-namespace {
-
-void TintInternalCompilerErrorReporter(const tint::diag::List& diagnostics) {
-  FAIL() << diagnostics.str();
-}
-
-struct Flags {
-  bool spirv_reader_dump_converted = false;
-
-  bool parse(int argc, char** argv) {
-    bool errored = false;
-    for (int i = 1; i < argc && !errored; i++) {
-      auto match = [&](std::string name) { return name == argv[i]; };
-
-      if (match("--dump-spirv")) {
-        spirv_reader_dump_converted = true;
-      } else {
-        std::cout << "Unknown flag '" << argv[i] << "'" << std::endl;
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
-}  // namespace
-
-// Entry point for tint unit tests
-int main(int argc, char** argv) {
-  testing::InitGoogleMock(&argc, argv);
-
-#if TINT_BUILD_WGSL_WRITER
-  tint::Program::printer = [](const tint::Program* program) {
-    auto result = tint::writer::wgsl::Generate(program, {});
-    if (!result.error.empty()) {
-      return "error: " + result.error;
-    }
-    return result.wgsl;
-  };
-#endif  //  TINT_BUILD_WGSL_WRITER
-
-  Flags flags;
-  if (!flags.parse(argc, argv)) {
-    return -1;
-  }
-
-#if TINT_BUILD_SPV_READER
-  if (flags.spirv_reader_dump_converted) {
-    tint::reader::spirv::test::DumpSuccessfullyConvertedSpirv();
-  }
-#endif  // TINT_BUILD_SPV_READER
-
-  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
-
-  auto res = RUN_ALL_TESTS();
-
-  return res;
-}
diff --git a/src/text/unicode.cc b/src/text/unicode.cc
deleted file mode 100644
index 826eb5b..0000000
--- a/src/text/unicode.cc
+++ /dev/null
@@ -1,506 +0,0 @@
-// 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/text/unicode.h"
-
-#include <algorithm>
-
-namespace tint::text {
-namespace {
-
-struct CodePointRange {
-  uint32_t first;  // First code point in the interval
-  uint32_t last;   // Last code point in the interval (inclusive)
-};
-
-inline bool operator<(CodePoint code_point, CodePointRange range) {
-  return code_point < range.first;
-}
-inline bool operator<(CodePointRange range, CodePoint code_point) {
-  return range.last < code_point;
-}
-
-// Interval ranges of all code points in the Unicode 14 XID_Start set
-// This array needs to be in ascending order.
-constexpr CodePointRange kXIDStartRanges[] = {
-    {0x00041, 0x0005a}, {0x00061, 0x0007a}, {0x000aa, 0x000aa},
-    {0x000b5, 0x000b5}, {0x000ba, 0x000ba}, {0x000c0, 0x000d6},
-    {0x000d8, 0x000f6}, {0x000f8, 0x002c1}, {0x002c6, 0x002d1},
-    {0x002e0, 0x002e4}, {0x002ec, 0x002ec}, {0x002ee, 0x002ee},
-    {0x00370, 0x00374}, {0x00376, 0x00377}, {0x0037b, 0x0037d},
-    {0x0037f, 0x0037f}, {0x00386, 0x00386}, {0x00388, 0x0038a},
-    {0x0038c, 0x0038c}, {0x0038e, 0x003a1}, {0x003a3, 0x003f5},
-    {0x003f7, 0x00481}, {0x0048a, 0x0052f}, {0x00531, 0x00556},
-    {0x00559, 0x00559}, {0x00560, 0x00588}, {0x005d0, 0x005ea},
-    {0x005ef, 0x005f2}, {0x00620, 0x0064a}, {0x0066e, 0x0066f},
-    {0x00671, 0x006d3}, {0x006d5, 0x006d5}, {0x006e5, 0x006e6},
-    {0x006ee, 0x006ef}, {0x006fa, 0x006fc}, {0x006ff, 0x006ff},
-    {0x00710, 0x00710}, {0x00712, 0x0072f}, {0x0074d, 0x007a5},
-    {0x007b1, 0x007b1}, {0x007ca, 0x007ea}, {0x007f4, 0x007f5},
-    {0x007fa, 0x007fa}, {0x00800, 0x00815}, {0x0081a, 0x0081a},
-    {0x00824, 0x00824}, {0x00828, 0x00828}, {0x00840, 0x00858},
-    {0x00860, 0x0086a}, {0x00870, 0x00887}, {0x00889, 0x0088e},
-    {0x008a0, 0x008c9}, {0x00904, 0x00939}, {0x0093d, 0x0093d},
-    {0x00950, 0x00950}, {0x00958, 0x00961}, {0x00971, 0x00980},
-    {0x00985, 0x0098c}, {0x0098f, 0x00990}, {0x00993, 0x009a8},
-    {0x009aa, 0x009b0}, {0x009b2, 0x009b2}, {0x009b6, 0x009b9},
-    {0x009bd, 0x009bd}, {0x009ce, 0x009ce}, {0x009dc, 0x009dd},
-    {0x009df, 0x009e1}, {0x009f0, 0x009f1}, {0x009fc, 0x009fc},
-    {0x00a05, 0x00a0a}, {0x00a0f, 0x00a10}, {0x00a13, 0x00a28},
-    {0x00a2a, 0x00a30}, {0x00a32, 0x00a33}, {0x00a35, 0x00a36},
-    {0x00a38, 0x00a39}, {0x00a59, 0x00a5c}, {0x00a5e, 0x00a5e},
-    {0x00a72, 0x00a74}, {0x00a85, 0x00a8d}, {0x00a8f, 0x00a91},
-    {0x00a93, 0x00aa8}, {0x00aaa, 0x00ab0}, {0x00ab2, 0x00ab3},
-    {0x00ab5, 0x00ab9}, {0x00abd, 0x00abd}, {0x00ad0, 0x00ad0},
-    {0x00ae0, 0x00ae1}, {0x00af9, 0x00af9}, {0x00b05, 0x00b0c},
-    {0x00b0f, 0x00b10}, {0x00b13, 0x00b28}, {0x00b2a, 0x00b30},
-    {0x00b32, 0x00b33}, {0x00b35, 0x00b39}, {0x00b3d, 0x00b3d},
-    {0x00b5c, 0x00b5d}, {0x00b5f, 0x00b61}, {0x00b71, 0x00b71},
-    {0x00b83, 0x00b83}, {0x00b85, 0x00b8a}, {0x00b8e, 0x00b90},
-    {0x00b92, 0x00b95}, {0x00b99, 0x00b9a}, {0x00b9c, 0x00b9c},
-    {0x00b9e, 0x00b9f}, {0x00ba3, 0x00ba4}, {0x00ba8, 0x00baa},
-    {0x00bae, 0x00bb9}, {0x00bd0, 0x00bd0}, {0x00c05, 0x00c0c},
-    {0x00c0e, 0x00c10}, {0x00c12, 0x00c28}, {0x00c2a, 0x00c39},
-    {0x00c3d, 0x00c3d}, {0x00c58, 0x00c5a}, {0x00c5d, 0x00c5d},
-    {0x00c60, 0x00c61}, {0x00c80, 0x00c80}, {0x00c85, 0x00c8c},
-    {0x00c8e, 0x00c90}, {0x00c92, 0x00ca8}, {0x00caa, 0x00cb3},
-    {0x00cb5, 0x00cb9}, {0x00cbd, 0x00cbd}, {0x00cdd, 0x00cde},
-    {0x00ce0, 0x00ce1}, {0x00cf1, 0x00cf2}, {0x00d04, 0x00d0c},
-    {0x00d0e, 0x00d10}, {0x00d12, 0x00d3a}, {0x00d3d, 0x00d3d},
-    {0x00d4e, 0x00d4e}, {0x00d54, 0x00d56}, {0x00d5f, 0x00d61},
-    {0x00d7a, 0x00d7f}, {0x00d85, 0x00d96}, {0x00d9a, 0x00db1},
-    {0x00db3, 0x00dbb}, {0x00dbd, 0x00dbd}, {0x00dc0, 0x00dc6},
-    {0x00e01, 0x00e30}, {0x00e32, 0x00e32}, {0x00e40, 0x00e46},
-    {0x00e81, 0x00e82}, {0x00e84, 0x00e84}, {0x00e86, 0x00e8a},
-    {0x00e8c, 0x00ea3}, {0x00ea5, 0x00ea5}, {0x00ea7, 0x00eb0},
-    {0x00eb2, 0x00eb2}, {0x00ebd, 0x00ebd}, {0x00ec0, 0x00ec4},
-    {0x00ec6, 0x00ec6}, {0x00edc, 0x00edf}, {0x00f00, 0x00f00},
-    {0x00f40, 0x00f47}, {0x00f49, 0x00f6c}, {0x00f88, 0x00f8c},
-    {0x01000, 0x0102a}, {0x0103f, 0x0103f}, {0x01050, 0x01055},
-    {0x0105a, 0x0105d}, {0x01061, 0x01061}, {0x01065, 0x01066},
-    {0x0106e, 0x01070}, {0x01075, 0x01081}, {0x0108e, 0x0108e},
-    {0x010a0, 0x010c5}, {0x010c7, 0x010c7}, {0x010cd, 0x010cd},
-    {0x010d0, 0x010fa}, {0x010fc, 0x01248}, {0x0124a, 0x0124d},
-    {0x01250, 0x01256}, {0x01258, 0x01258}, {0x0125a, 0x0125d},
-    {0x01260, 0x01288}, {0x0128a, 0x0128d}, {0x01290, 0x012b0},
-    {0x012b2, 0x012b5}, {0x012b8, 0x012be}, {0x012c0, 0x012c0},
-    {0x012c2, 0x012c5}, {0x012c8, 0x012d6}, {0x012d8, 0x01310},
-    {0x01312, 0x01315}, {0x01318, 0x0135a}, {0x01380, 0x0138f},
-    {0x013a0, 0x013f5}, {0x013f8, 0x013fd}, {0x01401, 0x0166c},
-    {0x0166f, 0x0167f}, {0x01681, 0x0169a}, {0x016a0, 0x016ea},
-    {0x016ee, 0x016f8}, {0x01700, 0x01711}, {0x0171f, 0x01731},
-    {0x01740, 0x01751}, {0x01760, 0x0176c}, {0x0176e, 0x01770},
-    {0x01780, 0x017b3}, {0x017d7, 0x017d7}, {0x017dc, 0x017dc},
-    {0x01820, 0x01878}, {0x01880, 0x018a8}, {0x018aa, 0x018aa},
-    {0x018b0, 0x018f5}, {0x01900, 0x0191e}, {0x01950, 0x0196d},
-    {0x01970, 0x01974}, {0x01980, 0x019ab}, {0x019b0, 0x019c9},
-    {0x01a00, 0x01a16}, {0x01a20, 0x01a54}, {0x01aa7, 0x01aa7},
-    {0x01b05, 0x01b33}, {0x01b45, 0x01b4c}, {0x01b83, 0x01ba0},
-    {0x01bae, 0x01baf}, {0x01bba, 0x01be5}, {0x01c00, 0x01c23},
-    {0x01c4d, 0x01c4f}, {0x01c5a, 0x01c7d}, {0x01c80, 0x01c88},
-    {0x01c90, 0x01cba}, {0x01cbd, 0x01cbf}, {0x01ce9, 0x01cec},
-    {0x01cee, 0x01cf3}, {0x01cf5, 0x01cf6}, {0x01cfa, 0x01cfa},
-    {0x01d00, 0x01dbf}, {0x01e00, 0x01f15}, {0x01f18, 0x01f1d},
-    {0x01f20, 0x01f45}, {0x01f48, 0x01f4d}, {0x01f50, 0x01f57},
-    {0x01f59, 0x01f59}, {0x01f5b, 0x01f5b}, {0x01f5d, 0x01f5d},
-    {0x01f5f, 0x01f7d}, {0x01f80, 0x01fb4}, {0x01fb6, 0x01fbc},
-    {0x01fbe, 0x01fbe}, {0x01fc2, 0x01fc4}, {0x01fc6, 0x01fcc},
-    {0x01fd0, 0x01fd3}, {0x01fd6, 0x01fdb}, {0x01fe0, 0x01fec},
-    {0x01ff2, 0x01ff4}, {0x01ff6, 0x01ffc}, {0x02071, 0x02071},
-    {0x0207f, 0x0207f}, {0x02090, 0x0209c}, {0x02102, 0x02102},
-    {0x02107, 0x02107}, {0x0210a, 0x02113}, {0x02115, 0x02115},
-    {0x02118, 0x0211d}, {0x02124, 0x02124}, {0x02126, 0x02126},
-    {0x02128, 0x02128}, {0x0212a, 0x02139}, {0x0213c, 0x0213f},
-    {0x02145, 0x02149}, {0x0214e, 0x0214e}, {0x02160, 0x02188},
-    {0x02c00, 0x02ce4}, {0x02ceb, 0x02cee}, {0x02cf2, 0x02cf3},
-    {0x02d00, 0x02d25}, {0x02d27, 0x02d27}, {0x02d2d, 0x02d2d},
-    {0x02d30, 0x02d67}, {0x02d6f, 0x02d6f}, {0x02d80, 0x02d96},
-    {0x02da0, 0x02da6}, {0x02da8, 0x02dae}, {0x02db0, 0x02db6},
-    {0x02db8, 0x02dbe}, {0x02dc0, 0x02dc6}, {0x02dc8, 0x02dce},
-    {0x02dd0, 0x02dd6}, {0x02dd8, 0x02dde}, {0x03005, 0x03007},
-    {0x03021, 0x03029}, {0x03031, 0x03035}, {0x03038, 0x0303c},
-    {0x03041, 0x03096}, {0x0309d, 0x0309f}, {0x030a1, 0x030fa},
-    {0x030fc, 0x030ff}, {0x03105, 0x0312f}, {0x03131, 0x0318e},
-    {0x031a0, 0x031bf}, {0x031f0, 0x031ff}, {0x03400, 0x04dbf},
-    {0x04e00, 0x0a48c}, {0x0a4d0, 0x0a4fd}, {0x0a500, 0x0a60c},
-    {0x0a610, 0x0a61f}, {0x0a62a, 0x0a62b}, {0x0a640, 0x0a66e},
-    {0x0a67f, 0x0a69d}, {0x0a6a0, 0x0a6ef}, {0x0a717, 0x0a71f},
-    {0x0a722, 0x0a788}, {0x0a78b, 0x0a7ca}, {0x0a7d0, 0x0a7d1},
-    {0x0a7d3, 0x0a7d3}, {0x0a7d5, 0x0a7d9}, {0x0a7f2, 0x0a801},
-    {0x0a803, 0x0a805}, {0x0a807, 0x0a80a}, {0x0a80c, 0x0a822},
-    {0x0a840, 0x0a873}, {0x0a882, 0x0a8b3}, {0x0a8f2, 0x0a8f7},
-    {0x0a8fb, 0x0a8fb}, {0x0a8fd, 0x0a8fe}, {0x0a90a, 0x0a925},
-    {0x0a930, 0x0a946}, {0x0a960, 0x0a97c}, {0x0a984, 0x0a9b2},
-    {0x0a9cf, 0x0a9cf}, {0x0a9e0, 0x0a9e4}, {0x0a9e6, 0x0a9ef},
-    {0x0a9fa, 0x0a9fe}, {0x0aa00, 0x0aa28}, {0x0aa40, 0x0aa42},
-    {0x0aa44, 0x0aa4b}, {0x0aa60, 0x0aa76}, {0x0aa7a, 0x0aa7a},
-    {0x0aa7e, 0x0aaaf}, {0x0aab1, 0x0aab1}, {0x0aab5, 0x0aab6},
-    {0x0aab9, 0x0aabd}, {0x0aac0, 0x0aac0}, {0x0aac2, 0x0aac2},
-    {0x0aadb, 0x0aadd}, {0x0aae0, 0x0aaea}, {0x0aaf2, 0x0aaf4},
-    {0x0ab01, 0x0ab06}, {0x0ab09, 0x0ab0e}, {0x0ab11, 0x0ab16},
-    {0x0ab20, 0x0ab26}, {0x0ab28, 0x0ab2e}, {0x0ab30, 0x0ab5a},
-    {0x0ab5c, 0x0ab69}, {0x0ab70, 0x0abe2}, {0x0ac00, 0x0d7a3},
-    {0x0d7b0, 0x0d7c6}, {0x0d7cb, 0x0d7fb}, {0x0f900, 0x0fa6d},
-    {0x0fa70, 0x0fad9}, {0x0fb00, 0x0fb06}, {0x0fb13, 0x0fb17},
-    {0x0fb1d, 0x0fb1d}, {0x0fb1f, 0x0fb28}, {0x0fb2a, 0x0fb36},
-    {0x0fb38, 0x0fb3c}, {0x0fb3e, 0x0fb3e}, {0x0fb40, 0x0fb41},
-    {0x0fb43, 0x0fb44}, {0x0fb46, 0x0fbb1}, {0x0fbd3, 0x0fc5d},
-    {0x0fc64, 0x0fd3d}, {0x0fd50, 0x0fd8f}, {0x0fd92, 0x0fdc7},
-    {0x0fdf0, 0x0fdf9}, {0x0fe71, 0x0fe71}, {0x0fe73, 0x0fe73},
-    {0x0fe77, 0x0fe77}, {0x0fe79, 0x0fe79}, {0x0fe7b, 0x0fe7b},
-    {0x0fe7d, 0x0fe7d}, {0x0fe7f, 0x0fefc}, {0x0ff21, 0x0ff3a},
-    {0x0ff41, 0x0ff5a}, {0x0ff66, 0x0ff9d}, {0x0ffa0, 0x0ffbe},
-    {0x0ffc2, 0x0ffc7}, {0x0ffca, 0x0ffcf}, {0x0ffd2, 0x0ffd7},
-    {0x0ffda, 0x0ffdc}, {0x10000, 0x1000b}, {0x1000d, 0x10026},
-    {0x10028, 0x1003a}, {0x1003c, 0x1003d}, {0x1003f, 0x1004d},
-    {0x10050, 0x1005d}, {0x10080, 0x100fa}, {0x10140, 0x10174},
-    {0x10280, 0x1029c}, {0x102a0, 0x102d0}, {0x10300, 0x1031f},
-    {0x1032d, 0x1034a}, {0x10350, 0x10375}, {0x10380, 0x1039d},
-    {0x103a0, 0x103c3}, {0x103c8, 0x103cf}, {0x103d1, 0x103d5},
-    {0x10400, 0x1049d}, {0x104b0, 0x104d3}, {0x104d8, 0x104fb},
-    {0x10500, 0x10527}, {0x10530, 0x10563}, {0x10570, 0x1057a},
-    {0x1057c, 0x1058a}, {0x1058c, 0x10592}, {0x10594, 0x10595},
-    {0x10597, 0x105a1}, {0x105a3, 0x105b1}, {0x105b3, 0x105b9},
-    {0x105bb, 0x105bc}, {0x10600, 0x10736}, {0x10740, 0x10755},
-    {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107b0},
-    {0x107b2, 0x107ba}, {0x10800, 0x10805}, {0x10808, 0x10808},
-    {0x1080a, 0x10835}, {0x10837, 0x10838}, {0x1083c, 0x1083c},
-    {0x1083f, 0x10855}, {0x10860, 0x10876}, {0x10880, 0x1089e},
-    {0x108e0, 0x108f2}, {0x108f4, 0x108f5}, {0x10900, 0x10915},
-    {0x10920, 0x10939}, {0x10980, 0x109b7}, {0x109be, 0x109bf},
-    {0x10a00, 0x10a00}, {0x10a10, 0x10a13}, {0x10a15, 0x10a17},
-    {0x10a19, 0x10a35}, {0x10a60, 0x10a7c}, {0x10a80, 0x10a9c},
-    {0x10ac0, 0x10ac7}, {0x10ac9, 0x10ae4}, {0x10b00, 0x10b35},
-    {0x10b40, 0x10b55}, {0x10b60, 0x10b72}, {0x10b80, 0x10b91},
-    {0x10c00, 0x10c48}, {0x10c80, 0x10cb2}, {0x10cc0, 0x10cf2},
-    {0x10d00, 0x10d23}, {0x10e80, 0x10ea9}, {0x10eb0, 0x10eb1},
-    {0x10f00, 0x10f1c}, {0x10f27, 0x10f27}, {0x10f30, 0x10f45},
-    {0x10f70, 0x10f81}, {0x10fb0, 0x10fc4}, {0x10fe0, 0x10ff6},
-    {0x11003, 0x11037}, {0x11071, 0x11072}, {0x11075, 0x11075},
-    {0x11083, 0x110af}, {0x110d0, 0x110e8}, {0x11103, 0x11126},
-    {0x11144, 0x11144}, {0x11147, 0x11147}, {0x11150, 0x11172},
-    {0x11176, 0x11176}, {0x11183, 0x111b2}, {0x111c1, 0x111c4},
-    {0x111da, 0x111da}, {0x111dc, 0x111dc}, {0x11200, 0x11211},
-    {0x11213, 0x1122b}, {0x11280, 0x11286}, {0x11288, 0x11288},
-    {0x1128a, 0x1128d}, {0x1128f, 0x1129d}, {0x1129f, 0x112a8},
-    {0x112b0, 0x112de}, {0x11305, 0x1130c}, {0x1130f, 0x11310},
-    {0x11313, 0x11328}, {0x1132a, 0x11330}, {0x11332, 0x11333},
-    {0x11335, 0x11339}, {0x1133d, 0x1133d}, {0x11350, 0x11350},
-    {0x1135d, 0x11361}, {0x11400, 0x11434}, {0x11447, 0x1144a},
-    {0x1145f, 0x11461}, {0x11480, 0x114af}, {0x114c4, 0x114c5},
-    {0x114c7, 0x114c7}, {0x11580, 0x115ae}, {0x115d8, 0x115db},
-    {0x11600, 0x1162f}, {0x11644, 0x11644}, {0x11680, 0x116aa},
-    {0x116b8, 0x116b8}, {0x11700, 0x1171a}, {0x11740, 0x11746},
-    {0x11800, 0x1182b}, {0x118a0, 0x118df}, {0x118ff, 0x11906},
-    {0x11909, 0x11909}, {0x1190c, 0x11913}, {0x11915, 0x11916},
-    {0x11918, 0x1192f}, {0x1193f, 0x1193f}, {0x11941, 0x11941},
-    {0x119a0, 0x119a7}, {0x119aa, 0x119d0}, {0x119e1, 0x119e1},
-    {0x119e3, 0x119e3}, {0x11a00, 0x11a00}, {0x11a0b, 0x11a32},
-    {0x11a3a, 0x11a3a}, {0x11a50, 0x11a50}, {0x11a5c, 0x11a89},
-    {0x11a9d, 0x11a9d}, {0x11ab0, 0x11af8}, {0x11c00, 0x11c08},
-    {0x11c0a, 0x11c2e}, {0x11c40, 0x11c40}, {0x11c72, 0x11c8f},
-    {0x11d00, 0x11d06}, {0x11d08, 0x11d09}, {0x11d0b, 0x11d30},
-    {0x11d46, 0x11d46}, {0x11d60, 0x11d65}, {0x11d67, 0x11d68},
-    {0x11d6a, 0x11d89}, {0x11d98, 0x11d98}, {0x11ee0, 0x11ef2},
-    {0x11fb0, 0x11fb0}, {0x12000, 0x12399}, {0x12400, 0x1246e},
-    {0x12480, 0x12543}, {0x12f90, 0x12ff0}, {0x13000, 0x1342e},
-    {0x14400, 0x14646}, {0x16800, 0x16a38}, {0x16a40, 0x16a5e},
-    {0x16a70, 0x16abe}, {0x16ad0, 0x16aed}, {0x16b00, 0x16b2f},
-    {0x16b40, 0x16b43}, {0x16b63, 0x16b77}, {0x16b7d, 0x16b8f},
-    {0x16e40, 0x16e7f}, {0x16f00, 0x16f4a}, {0x16f50, 0x16f50},
-    {0x16f93, 0x16f9f}, {0x16fe0, 0x16fe1}, {0x16fe3, 0x16fe3},
-    {0x17000, 0x187f7}, {0x18800, 0x18cd5}, {0x18d00, 0x18d08},
-    {0x1aff0, 0x1aff3}, {0x1aff5, 0x1affb}, {0x1affd, 0x1affe},
-    {0x1b000, 0x1b122}, {0x1b150, 0x1b152}, {0x1b164, 0x1b167},
-    {0x1b170, 0x1b2fb}, {0x1bc00, 0x1bc6a}, {0x1bc70, 0x1bc7c},
-    {0x1bc80, 0x1bc88}, {0x1bc90, 0x1bc99}, {0x1d400, 0x1d454},
-    {0x1d456, 0x1d49c}, {0x1d49e, 0x1d49f}, {0x1d4a2, 0x1d4a2},
-    {0x1d4a5, 0x1d4a6}, {0x1d4a9, 0x1d4ac}, {0x1d4ae, 0x1d4b9},
-    {0x1d4bb, 0x1d4bb}, {0x1d4bd, 0x1d4c3}, {0x1d4c5, 0x1d505},
-    {0x1d507, 0x1d50a}, {0x1d50d, 0x1d514}, {0x1d516, 0x1d51c},
-    {0x1d51e, 0x1d539}, {0x1d53b, 0x1d53e}, {0x1d540, 0x1d544},
-    {0x1d546, 0x1d546}, {0x1d54a, 0x1d550}, {0x1d552, 0x1d6a5},
-    {0x1d6a8, 0x1d6c0}, {0x1d6c2, 0x1d6da}, {0x1d6dc, 0x1d6fa},
-    {0x1d6fc, 0x1d714}, {0x1d716, 0x1d734}, {0x1d736, 0x1d74e},
-    {0x1d750, 0x1d76e}, {0x1d770, 0x1d788}, {0x1d78a, 0x1d7a8},
-    {0x1d7aa, 0x1d7c2}, {0x1d7c4, 0x1d7cb}, {0x1df00, 0x1df1e},
-    {0x1e100, 0x1e12c}, {0x1e137, 0x1e13d}, {0x1e14e, 0x1e14e},
-    {0x1e290, 0x1e2ad}, {0x1e2c0, 0x1e2eb}, {0x1e7e0, 0x1e7e6},
-    {0x1e7e8, 0x1e7eb}, {0x1e7ed, 0x1e7ee}, {0x1e7f0, 0x1e7fe},
-    {0x1e800, 0x1e8c4}, {0x1e900, 0x1e943}, {0x1e94b, 0x1e94b},
-    {0x1ee00, 0x1ee03}, {0x1ee05, 0x1ee1f}, {0x1ee21, 0x1ee22},
-    {0x1ee24, 0x1ee24}, {0x1ee27, 0x1ee27}, {0x1ee29, 0x1ee32},
-    {0x1ee34, 0x1ee37}, {0x1ee39, 0x1ee39}, {0x1ee3b, 0x1ee3b},
-    {0x1ee42, 0x1ee42}, {0x1ee47, 0x1ee47}, {0x1ee49, 0x1ee49},
-    {0x1ee4b, 0x1ee4b}, {0x1ee4d, 0x1ee4f}, {0x1ee51, 0x1ee52},
-    {0x1ee54, 0x1ee54}, {0x1ee57, 0x1ee57}, {0x1ee59, 0x1ee59},
-    {0x1ee5b, 0x1ee5b}, {0x1ee5d, 0x1ee5d}, {0x1ee5f, 0x1ee5f},
-    {0x1ee61, 0x1ee62}, {0x1ee64, 0x1ee64}, {0x1ee67, 0x1ee6a},
-    {0x1ee6c, 0x1ee72}, {0x1ee74, 0x1ee77}, {0x1ee79, 0x1ee7c},
-    {0x1ee7e, 0x1ee7e}, {0x1ee80, 0x1ee89}, {0x1ee8b, 0x1ee9b},
-    {0x1eea1, 0x1eea3}, {0x1eea5, 0x1eea9}, {0x1eeab, 0x1eebb},
-    {0x20000, 0x2a6df}, {0x2a700, 0x2b738}, {0x2b740, 0x2b81d},
-    {0x2b820, 0x2cea1}, {0x2ceb0, 0x2ebe0}, {0x2f800, 0x2fa1d},
-    {0x30000, 0x3134a},
-};
-
-// Number of ranges in kXIDStartRanges
-constexpr size_t kNumXIDStartRanges =
-    sizeof(kXIDStartRanges) / sizeof(kXIDStartRanges[0]);
-
-// The additional code point interval ranges for the Unicode 14 XID_Continue
-// set. This extends the values in kXIDStartRanges.
-// This array needs to be in ascending order.
-constexpr CodePointRange kXIDContinueRanges[] = {
-    {0x00030, 0x00039}, {0x0005f, 0x0005f}, {0x000b7, 0x000b7},
-    {0x00300, 0x0036f}, {0x00387, 0x00387}, {0x00483, 0x00487},
-    {0x00591, 0x005bd}, {0x005bf, 0x005bf}, {0x005c1, 0x005c2},
-    {0x005c4, 0x005c5}, {0x005c7, 0x005c7}, {0x00610, 0x0061a},
-    {0x0064b, 0x00669}, {0x00670, 0x00670}, {0x006d6, 0x006dc},
-    {0x006df, 0x006e4}, {0x006e7, 0x006e8}, {0x006ea, 0x006ed},
-    {0x006f0, 0x006f9}, {0x00711, 0x00711}, {0x00730, 0x0074a},
-    {0x007a6, 0x007b0}, {0x007c0, 0x007c9}, {0x007eb, 0x007f3},
-    {0x007fd, 0x007fd}, {0x00816, 0x00819}, {0x0081b, 0x00823},
-    {0x00825, 0x00827}, {0x00829, 0x0082d}, {0x00859, 0x0085b},
-    {0x00898, 0x0089f}, {0x008ca, 0x008e1}, {0x008e3, 0x00903},
-    {0x0093a, 0x0093c}, {0x0093e, 0x0094f}, {0x00951, 0x00957},
-    {0x00962, 0x00963}, {0x00966, 0x0096f}, {0x00981, 0x00983},
-    {0x009bc, 0x009bc}, {0x009be, 0x009c4}, {0x009c7, 0x009c8},
-    {0x009cb, 0x009cd}, {0x009d7, 0x009d7}, {0x009e2, 0x009e3},
-    {0x009e6, 0x009ef}, {0x009fe, 0x009fe}, {0x00a01, 0x00a03},
-    {0x00a3c, 0x00a3c}, {0x00a3e, 0x00a42}, {0x00a47, 0x00a48},
-    {0x00a4b, 0x00a4d}, {0x00a51, 0x00a51}, {0x00a66, 0x00a71},
-    {0x00a75, 0x00a75}, {0x00a81, 0x00a83}, {0x00abc, 0x00abc},
-    {0x00abe, 0x00ac5}, {0x00ac7, 0x00ac9}, {0x00acb, 0x00acd},
-    {0x00ae2, 0x00ae3}, {0x00ae6, 0x00aef}, {0x00afa, 0x00aff},
-    {0x00b01, 0x00b03}, {0x00b3c, 0x00b3c}, {0x00b3e, 0x00b44},
-    {0x00b47, 0x00b48}, {0x00b4b, 0x00b4d}, {0x00b55, 0x00b57},
-    {0x00b62, 0x00b63}, {0x00b66, 0x00b6f}, {0x00b82, 0x00b82},
-    {0x00bbe, 0x00bc2}, {0x00bc6, 0x00bc8}, {0x00bca, 0x00bcd},
-    {0x00bd7, 0x00bd7}, {0x00be6, 0x00bef}, {0x00c00, 0x00c04},
-    {0x00c3c, 0x00c3c}, {0x00c3e, 0x00c44}, {0x00c46, 0x00c48},
-    {0x00c4a, 0x00c4d}, {0x00c55, 0x00c56}, {0x00c62, 0x00c63},
-    {0x00c66, 0x00c6f}, {0x00c81, 0x00c83}, {0x00cbc, 0x00cbc},
-    {0x00cbe, 0x00cc4}, {0x00cc6, 0x00cc8}, {0x00cca, 0x00ccd},
-    {0x00cd5, 0x00cd6}, {0x00ce2, 0x00ce3}, {0x00ce6, 0x00cef},
-    {0x00d00, 0x00d03}, {0x00d3b, 0x00d3c}, {0x00d3e, 0x00d44},
-    {0x00d46, 0x00d48}, {0x00d4a, 0x00d4d}, {0x00d57, 0x00d57},
-    {0x00d62, 0x00d63}, {0x00d66, 0x00d6f}, {0x00d81, 0x00d83},
-    {0x00dca, 0x00dca}, {0x00dcf, 0x00dd4}, {0x00dd6, 0x00dd6},
-    {0x00dd8, 0x00ddf}, {0x00de6, 0x00def}, {0x00df2, 0x00df3},
-    {0x00e31, 0x00e31}, {0x00e33, 0x00e3a}, {0x00e47, 0x00e4e},
-    {0x00e50, 0x00e59}, {0x00eb1, 0x00eb1}, {0x00eb3, 0x00ebc},
-    {0x00ec8, 0x00ecd}, {0x00ed0, 0x00ed9}, {0x00f18, 0x00f19},
-    {0x00f20, 0x00f29}, {0x00f35, 0x00f35}, {0x00f37, 0x00f37},
-    {0x00f39, 0x00f39}, {0x00f3e, 0x00f3f}, {0x00f71, 0x00f84},
-    {0x00f86, 0x00f87}, {0x00f8d, 0x00f97}, {0x00f99, 0x00fbc},
-    {0x00fc6, 0x00fc6}, {0x0102b, 0x0103e}, {0x01040, 0x01049},
-    {0x01056, 0x01059}, {0x0105e, 0x01060}, {0x01062, 0x01064},
-    {0x01067, 0x0106d}, {0x01071, 0x01074}, {0x01082, 0x0108d},
-    {0x0108f, 0x0109d}, {0x0135d, 0x0135f}, {0x01369, 0x01371},
-    {0x01712, 0x01715}, {0x01732, 0x01734}, {0x01752, 0x01753},
-    {0x01772, 0x01773}, {0x017b4, 0x017d3}, {0x017dd, 0x017dd},
-    {0x017e0, 0x017e9}, {0x0180b, 0x0180d}, {0x0180f, 0x01819},
-    {0x018a9, 0x018a9}, {0x01920, 0x0192b}, {0x01930, 0x0193b},
-    {0x01946, 0x0194f}, {0x019d0, 0x019da}, {0x01a17, 0x01a1b},
-    {0x01a55, 0x01a5e}, {0x01a60, 0x01a7c}, {0x01a7f, 0x01a89},
-    {0x01a90, 0x01a99}, {0x01ab0, 0x01abd}, {0x01abf, 0x01ace},
-    {0x01b00, 0x01b04}, {0x01b34, 0x01b44}, {0x01b50, 0x01b59},
-    {0x01b6b, 0x01b73}, {0x01b80, 0x01b82}, {0x01ba1, 0x01bad},
-    {0x01bb0, 0x01bb9}, {0x01be6, 0x01bf3}, {0x01c24, 0x01c37},
-    {0x01c40, 0x01c49}, {0x01c50, 0x01c59}, {0x01cd0, 0x01cd2},
-    {0x01cd4, 0x01ce8}, {0x01ced, 0x01ced}, {0x01cf4, 0x01cf4},
-    {0x01cf7, 0x01cf9}, {0x01dc0, 0x01dff}, {0x0203f, 0x02040},
-    {0x02054, 0x02054}, {0x020d0, 0x020dc}, {0x020e1, 0x020e1},
-    {0x020e5, 0x020f0}, {0x02cef, 0x02cf1}, {0x02d7f, 0x02d7f},
-    {0x02de0, 0x02dff}, {0x0302a, 0x0302f}, {0x03099, 0x0309a},
-    {0x0a620, 0x0a629}, {0x0a66f, 0x0a66f}, {0x0a674, 0x0a67d},
-    {0x0a69e, 0x0a69f}, {0x0a6f0, 0x0a6f1}, {0x0a802, 0x0a802},
-    {0x0a806, 0x0a806}, {0x0a80b, 0x0a80b}, {0x0a823, 0x0a827},
-    {0x0a82c, 0x0a82c}, {0x0a880, 0x0a881}, {0x0a8b4, 0x0a8c5},
-    {0x0a8d0, 0x0a8d9}, {0x0a8e0, 0x0a8f1}, {0x0a8ff, 0x0a909},
-    {0x0a926, 0x0a92d}, {0x0a947, 0x0a953}, {0x0a980, 0x0a983},
-    {0x0a9b3, 0x0a9c0}, {0x0a9d0, 0x0a9d9}, {0x0a9e5, 0x0a9e5},
-    {0x0a9f0, 0x0a9f9}, {0x0aa29, 0x0aa36}, {0x0aa43, 0x0aa43},
-    {0x0aa4c, 0x0aa4d}, {0x0aa50, 0x0aa59}, {0x0aa7b, 0x0aa7d},
-    {0x0aab0, 0x0aab0}, {0x0aab2, 0x0aab4}, {0x0aab7, 0x0aab8},
-    {0x0aabe, 0x0aabf}, {0x0aac1, 0x0aac1}, {0x0aaeb, 0x0aaef},
-    {0x0aaf5, 0x0aaf6}, {0x0abe3, 0x0abea}, {0x0abec, 0x0abed},
-    {0x0abf0, 0x0abf9}, {0x0fb1e, 0x0fb1e}, {0x0fe00, 0x0fe0f},
-    {0x0fe20, 0x0fe2f}, {0x0fe33, 0x0fe34}, {0x0fe4d, 0x0fe4f},
-    {0x0ff10, 0x0ff19}, {0x0ff3f, 0x0ff3f}, {0x0ff9e, 0x0ff9f},
-    {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, {0x10376, 0x1037a},
-    {0x104a0, 0x104a9}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
-    {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
-    {0x10ae5, 0x10ae6}, {0x10d24, 0x10d27}, {0x10d30, 0x10d39},
-    {0x10eab, 0x10eac}, {0x10f46, 0x10f50}, {0x10f82, 0x10f85},
-    {0x11000, 0x11002}, {0x11038, 0x11046}, {0x11066, 0x11070},
-    {0x11073, 0x11074}, {0x1107f, 0x11082}, {0x110b0, 0x110ba},
-    {0x110c2, 0x110c2}, {0x110f0, 0x110f9}, {0x11100, 0x11102},
-    {0x11127, 0x11134}, {0x11136, 0x1113f}, {0x11145, 0x11146},
-    {0x11173, 0x11173}, {0x11180, 0x11182}, {0x111b3, 0x111c0},
-    {0x111c9, 0x111cc}, {0x111ce, 0x111d9}, {0x1122c, 0x11237},
-    {0x1123e, 0x1123e}, {0x112df, 0x112ea}, {0x112f0, 0x112f9},
-    {0x11300, 0x11303}, {0x1133b, 0x1133c}, {0x1133e, 0x11344},
-    {0x11347, 0x11348}, {0x1134b, 0x1134d}, {0x11357, 0x11357},
-    {0x11362, 0x11363}, {0x11366, 0x1136c}, {0x11370, 0x11374},
-    {0x11435, 0x11446}, {0x11450, 0x11459}, {0x1145e, 0x1145e},
-    {0x114b0, 0x114c3}, {0x114d0, 0x114d9}, {0x115af, 0x115b5},
-    {0x115b8, 0x115c0}, {0x115dc, 0x115dd}, {0x11630, 0x11640},
-    {0x11650, 0x11659}, {0x116ab, 0x116b7}, {0x116c0, 0x116c9},
-    {0x1171d, 0x1172b}, {0x11730, 0x11739}, {0x1182c, 0x1183a},
-    {0x118e0, 0x118e9}, {0x11930, 0x11935}, {0x11937, 0x11938},
-    {0x1193b, 0x1193e}, {0x11940, 0x11940}, {0x11942, 0x11943},
-    {0x11950, 0x11959}, {0x119d1, 0x119d7}, {0x119da, 0x119e0},
-    {0x119e4, 0x119e4}, {0x11a01, 0x11a0a}, {0x11a33, 0x11a39},
-    {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a5b},
-    {0x11a8a, 0x11a99}, {0x11c2f, 0x11c36}, {0x11c38, 0x11c3f},
-    {0x11c50, 0x11c59}, {0x11c92, 0x11ca7}, {0x11ca9, 0x11cb6},
-    {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, {0x11d3c, 0x11d3d},
-    {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, {0x11d50, 0x11d59},
-    {0x11d8a, 0x11d8e}, {0x11d90, 0x11d91}, {0x11d93, 0x11d97},
-    {0x11da0, 0x11da9}, {0x11ef3, 0x11ef6}, {0x16a60, 0x16a69},
-    {0x16ac0, 0x16ac9}, {0x16af0, 0x16af4}, {0x16b30, 0x16b36},
-    {0x16b50, 0x16b59}, {0x16f4f, 0x16f4f}, {0x16f51, 0x16f87},
-    {0x16f8f, 0x16f92}, {0x16fe4, 0x16fe4}, {0x16ff0, 0x16ff1},
-    {0x1bc9d, 0x1bc9e}, {0x1cf00, 0x1cf2d}, {0x1cf30, 0x1cf46},
-    {0x1d165, 0x1d169}, {0x1d16d, 0x1d172}, {0x1d17b, 0x1d182},
-    {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244},
-    {0x1d7ce, 0x1d7ff}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c},
-    {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
-    {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018},
-    {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a},
-    {0x1e130, 0x1e136}, {0x1e140, 0x1e149}, {0x1e2ae, 0x1e2ae},
-    {0x1e2ec, 0x1e2f9}, {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a},
-    {0x1e950, 0x1e959}, {0x1fbf0, 0x1fbf9}, {0xe0100, 0xe01ef},
-};
-
-// Number of ranges in kXIDContinueRanges
-constexpr size_t kNumXIDContinueRanges =
-    sizeof(kXIDContinueRanges) / sizeof(kXIDContinueRanges[0]);
-
-}  // namespace
-
-bool CodePoint::IsXIDStart() const {
-  return std::binary_search(kXIDStartRanges,
-                            kXIDStartRanges + kNumXIDStartRanges, *this);
-}
-
-bool CodePoint::IsXIDContinue() const {
-  return IsXIDStart() ||
-         std::binary_search(kXIDContinueRanges,
-                            kXIDContinueRanges + kNumXIDContinueRanges, *this);
-}
-
-std::ostream& operator<<(std::ostream& out, CodePoint code_point) {
-  if (code_point < 0x7f) {
-    // See https://en.cppreference.com/w/cpp/language/escape
-    switch (code_point) {
-      case '\a':
-        return out << R"('\a')";
-      case '\b':
-        return out << R"('\b')";
-      case '\f':
-        return out << R"('\f')";
-      case '\n':
-        return out << R"('\n')";
-      case '\r':
-        return out << R"('\r')";
-      case '\t':
-        return out << R"('\t')";
-      case '\v':
-        return out << R"('\v')";
-    }
-    return out << "'" << static_cast<char>(code_point) << "'";
-  }
-  return out << "'U+" << std::hex << code_point.value << "'";
-}
-
-namespace utf8 {
-
-std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len) {
-  if (len < 1) {
-    return {};
-  }
-
-  // Lookup table for the first byte of a UTF-8 sequence.
-  // 0 indicates an invalid length.
-  // Note that bit encodings that can fit in a smaller number of bytes are
-  // invalid (e.g. 0xc0). Code points that exceed the unicode maximum of
-  // 0x10FFFF are also invalid (0xf5+).
-  // See: https://en.wikipedia.org/wiki/UTF-8#Encoding and
-  //      https://datatracker.ietf.org/doc/html/rfc3629#section-3
-  static constexpr uint8_t kSequenceLength[256] = {
-      //         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
-      /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x10 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x20 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-      /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-      /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-      /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-      /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-      /* 0xc0 */ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-      /* 0xd0 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-      /* 0xe0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-      /* 0xf0 */ 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-  };
-
-  uint8_t n = kSequenceLength[ptr[0]];
-  if (n > len) {
-    return {};
-  }
-
-  CodePoint c;
-
-  switch (n) {
-    // Note: n=0 (invalid) is correctly handled without a case.
-    case 1:
-      c = CodePoint{ptr[0]};
-      break;
-    case 2:
-      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00011111) << 6) |
-                    (static_cast<uint32_t>(ptr[1] & 0b00111111))};
-      break;
-    case 3:
-      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00001111) << 12) |
-                    (static_cast<uint32_t>(ptr[1] & 0b00111111) << 6) |
-                    (static_cast<uint32_t>(ptr[2] & 0b00111111))};
-      break;
-    case 4:
-      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00000111) << 18) |
-                    (static_cast<uint32_t>(ptr[1] & 0b00111111) << 12) |
-                    (static_cast<uint32_t>(ptr[2] & 0b00111111) << 6) |
-                    (static_cast<uint32_t>(ptr[3] & 0b00111111))};
-      break;
-  }
-  return {c, n};
-}
-
-bool IsASCII(std::string_view str) {
-  for (auto c : str) {
-    if (c & 0x80) {
-      return false;
-    }
-  }
-  return true;
-}
-
-}  // namespace utf8
-
-}  // namespace tint::text
diff --git a/src/text/unicode.h b/src/text/unicode.h
deleted file mode 100644
index 3c74221..0000000
--- a/src/text/unicode.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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_TEXT_UNICODE_H_
-#define SRC_TEXT_UNICODE_H_
-
-#include <cstddef>
-#include <cstdint>
-#include <ostream>
-#include <utility>
-
-namespace tint::text {
-
-/// CodePoint is a unicode code point.
-struct CodePoint {
-  /// Constructor
-  inline CodePoint() = default;
-
-  /// Constructor
-  /// @param v the code point value
-  inline explicit CodePoint(uint32_t v) : value(v) {}
-
-  /// @returns the code point value
-  inline operator uint32_t() const { return value; }
-
-  /// Assignment operator
-  /// @param v the new value for the code point
-  /// @returns this CodePoint
-  inline CodePoint& operator=(uint32_t v) {
-    value = v;
-    return *this;
-  }
-
-  /// @returns true if this CodePoint is in the XID_Start set.
-  /// @see https://unicode.org/reports/tr31/
-  bool IsXIDStart() const;
-
-  /// @returns true if this CodePoint is in the XID_Continue set.
-  /// @see https://unicode.org/reports/tr31/
-  bool IsXIDContinue() const;
-
-  /// The code point value
-  uint32_t value = 0;
-};
-
-/// Writes the CodePoint to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param codepoint the CodePoint to write
-/// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, CodePoint codepoint);
-
-namespace utf8 {
-
-/// Decodes the first code point in the utf8 string.
-/// @param ptr the pointer to the first byte of the utf8 sequence
-/// @param len the maximum number of bytes to read
-/// @returns a pair of CodePoint and width in code units (bytes).
-///          If the next code point cannot be decoded then returns [0,0].
-std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len);
-
-/// @returns true if all the utf-8 code points in the string are ASCII
-/// (code-points 0x00..0x7f).
-bool IsASCII(std::string_view);
-
-}  // namespace utf8
-
-}  // namespace tint::text
-
-#endif  // SRC_TEXT_UNICODE_H_
diff --git a/src/text/unicode_test.cc b/src/text/unicode_test.cc
deleted file mode 100644
index 75212ba..0000000
--- a/src/text/unicode_test.cc
+++ /dev/null
@@ -1,485 +0,0 @@
-// 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/text/unicode.h"
-
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-
-/// Helper for constructing a CodePoint
-#define C(x) CodePoint(x)
-
-namespace tint::text {
-
-////////////////////////////////////////////////////////////////////////////////
-// CodePoint character set tests
-////////////////////////////////////////////////////////////////////////////////
-namespace {
-
-struct CodePointCase {
-  CodePoint code_point;
-  bool is_xid_start;
-  bool is_xid_continue;
-};
-
-std::ostream& operator<<(std::ostream& out, CodePointCase c) {
-  return out << c.code_point;
-}
-
-class CodePointTest : public testing::TestWithParam<CodePointCase> {};
-
-TEST_P(CodePointTest, CharacterSets) {
-  auto param = GetParam();
-  EXPECT_EQ(param.code_point.IsXIDStart(), param.is_xid_start);
-  EXPECT_EQ(param.code_point.IsXIDContinue(), param.is_xid_continue);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    CodePointTest,
-    CodePointTest,
-    ::testing::ValuesIn({
-        CodePointCase{C(' '), /* start */ false, /* continue */ false},
-        CodePointCase{C('\t'), /* start */ false, /* continue */ false},
-        CodePointCase{C('\n'), /* start */ false, /* continue */ false},
-        CodePointCase{C('\r'), /* start */ false, /* continue */ false},
-        CodePointCase{C('!'), /* start */ false, /* continue */ false},
-        CodePointCase{C('"'), /* start */ false, /* continue */ false},
-        CodePointCase{C('#'), /* start */ false, /* continue */ false},
-        CodePointCase{C('$'), /* start */ false, /* continue */ false},
-        CodePointCase{C('%'), /* start */ false, /* continue */ false},
-        CodePointCase{C('&'), /* start */ false, /* continue */ false},
-        CodePointCase{C('\\'), /* start */ false, /* continue */ false},
-        CodePointCase{C('/'), /* start */ false, /* continue */ false},
-        CodePointCase{C('('), /* start */ false, /* continue */ false},
-        CodePointCase{C(')'), /* start */ false, /* continue */ false},
-        CodePointCase{C('*'), /* start */ false, /* continue */ false},
-        CodePointCase{C(','), /* start */ false, /* continue */ false},
-        CodePointCase{C('-'), /* start */ false, /* continue */ false},
-        CodePointCase{C('/'), /* start */ false, /* continue */ false},
-        CodePointCase{C('`'), /* start */ false, /* continue */ false},
-        CodePointCase{C('@'), /* start */ false, /* continue */ false},
-        CodePointCase{C('^'), /* start */ false, /* continue */ false},
-        CodePointCase{C('['), /* start */ false, /* continue */ false},
-        CodePointCase{C(']'), /* start */ false, /* continue */ false},
-        CodePointCase{C('|'), /* start */ false, /* continue */ false},
-        CodePointCase{C('('), /* start */ false, /* continue */ false},
-        CodePointCase{C(','), /* start */ false, /* continue */ false},
-        CodePointCase{C('}'), /* start */ false, /* continue */ false},
-        CodePointCase{C('a'), /* start */ true, /* continue */ true},
-        CodePointCase{C('b'), /* start */ true, /* continue */ true},
-        CodePointCase{C('c'), /* start */ true, /* continue */ true},
-        CodePointCase{C('x'), /* start */ true, /* continue */ true},
-        CodePointCase{C('y'), /* start */ true, /* continue */ true},
-        CodePointCase{C('z'), /* start */ true, /* continue */ true},
-        CodePointCase{C('A'), /* start */ true, /* continue */ true},
-        CodePointCase{C('B'), /* start */ true, /* continue */ true},
-        CodePointCase{C('C'), /* start */ true, /* continue */ true},
-        CodePointCase{C('X'), /* start */ true, /* continue */ true},
-        CodePointCase{C('Y'), /* start */ true, /* continue */ true},
-        CodePointCase{C('Z'), /* start */ true, /* continue */ true},
-        CodePointCase{C('_'), /* start */ false, /* continue */ true},
-        CodePointCase{C('0'), /* start */ false, /* continue */ true},
-        CodePointCase{C('1'), /* start */ false, /* continue */ true},
-        CodePointCase{C('2'), /* start */ false, /* continue */ true},
-        CodePointCase{C('8'), /* start */ false, /* continue */ true},
-        CodePointCase{C('9'), /* start */ false, /* continue */ true},
-        CodePointCase{C('0'), /* start */ false, /* continue */ true},
-
-        // First in XID_Start
-        CodePointCase{C(0x00041), /* start */ true, /* continue */ true},
-        // Last in XID_Start
-        CodePointCase{C(0x3134a), /* start */ true, /* continue */ true},
-
-        // Random selection from XID_Start, using the interval's first
-        CodePointCase{C(0x002ee), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x005ef), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x009f0), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00d3d), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00d54), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00e86), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00edc), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x01c00), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x01c80), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x02071), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x02dd0), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x0a4d0), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x0aac0), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x0ab5c), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x0ffda), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x11313), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x1ee49), /* start */ true, /* continue */ true},
-
-        // Random selection from XID_Start, using the interval's last
-        CodePointCase{C(0x00710), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00b83), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00b9a), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x00ec4), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x01081), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x012be), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x02107), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x03029), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x03035), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x0aadd), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x10805), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x11075), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x1d4a2), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x1e7fe), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x1ee27), /* start */ true, /* continue */ true},
-        CodePointCase{C(0x2b738), /* start */ true, /* continue */ true},
-
-        // Random selection from XID_Continue, using the interval's first
-        CodePointCase{C(0x16ac0), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00dca), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x16f4f), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0fe00), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00ec8), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x009be), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x11d47), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x11d50), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0a926), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0aac1), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00f18), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x11145), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x017dd), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0aaeb), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x11173), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00a51), /* start */ false, /* continue */ true},
-
-        // Random selection from XID_Continue, using the interval's last
-        CodePointCase{C(0x00f84), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x10a3a), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x1e018), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0a827), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x01abd), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x009d7), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00b6f), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0096f), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x11146), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x10eac), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00f39), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x1e136), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00def), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0fe34), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x009c8), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00fbc), /* start */ false, /* continue */ true},
-
-        // Random code points that are one less than an interval of XID_Start
-        CodePointCase{C(0x003f6), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x005ee), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x009ef), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00d3c), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x00d53), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00e85), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00edb), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x01bff), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x02070), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x02dcf), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x0a4cf), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x0aabf), /* start */ false, /* continue */ true},
-        CodePointCase{C(0x0ab5b), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x0ffd9), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x11312), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x1ee48), /* start */ false, /* continue */ false},
-
-        // Random code points that are one more than an interval of XID_Continue
-        CodePointCase{C(0x00060), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00a4e), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00a84), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00cce), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00eda), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x00f85), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x01b74), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x01c38), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x0fe30), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x11174), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x112eb), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x115de), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x1172c), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x11a3f), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x11c37), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x11d92), /* start */ false, /* continue */ false},
-        CodePointCase{C(0x1e2af), /* start */ false, /* continue */ false},
-    }));
-
-}  // namespace
-
-////////////////////////////////////////////////////////////////////////////////
-// DecodeUTF8 valid tests
-////////////////////////////////////////////////////////////////////////////////
-namespace {
-
-struct CodePointAndWidth {
-  CodePoint code_point;
-  size_t width;
-};
-
-bool operator==(const CodePointAndWidth& a, const CodePointAndWidth& b) {
-  return a.code_point == b.code_point && a.width == b.width;
-}
-
-std::ostream& operator<<(std::ostream& out, CodePointAndWidth cpw) {
-  return out << "code_point: " << cpw.code_point << ", width: " << cpw.width;
-}
-
-struct DecodeUTF8Case {
-  std::string string;
-  std::vector<CodePointAndWidth> expected;
-};
-
-std::ostream& operator<<(std::ostream& out, DecodeUTF8Case c) {
-  return out << "'" << c.string << "'";
-}
-
-class DecodeUTF8Test : public testing::TestWithParam<DecodeUTF8Case> {};
-
-TEST_P(DecodeUTF8Test, Valid) {
-  auto param = GetParam();
-
-  const uint8_t* data = reinterpret_cast<const uint8_t*>(param.string.data());
-  const size_t len = param.string.size();
-
-  std::vector<CodePointAndWidth> got;
-  size_t offset = 0;
-  while (offset < len) {
-    auto [code_point, width] = utf8::Decode(data + offset, len - offset);
-    if (width == 0) {
-      FAIL() << "Decode() failed at byte offset " << offset;
-    }
-    offset += width;
-    got.emplace_back(CodePointAndWidth{code_point, width});
-  }
-
-  EXPECT_THAT(got, ::testing::ElementsAreArray(param.expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AsciiLetters,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({
-        DecodeUTF8Case{"a", {{C('a'), 1}}},
-        DecodeUTF8Case{"abc", {{C('a'), 1}, {C('b'), 1}, {C('c'), 1}}},
-        DecodeUTF8Case{"def", {{C('d'), 1}, {C('e'), 1}, {C('f'), 1}}},
-        DecodeUTF8Case{"gh", {{C('g'), 1}, {C('h'), 1}}},
-        DecodeUTF8Case{"ij", {{C('i'), 1}, {C('j'), 1}}},
-        DecodeUTF8Case{"klm", {{C('k'), 1}, {C('l'), 1}, {C('m'), 1}}},
-        DecodeUTF8Case{"nop", {{C('n'), 1}, {C('o'), 1}, {C('p'), 1}}},
-        DecodeUTF8Case{"qr", {{C('q'), 1}, {C('r'), 1}}},
-        DecodeUTF8Case{"stu", {{C('s'), 1}, {C('t'), 1}, {C('u'), 1}}},
-        DecodeUTF8Case{"vw", {{C('v'), 1}, {C('w'), 1}}},
-        DecodeUTF8Case{"xyz", {{C('x'), 1}, {C('y'), 1}, {C('z'), 1}}},
-        DecodeUTF8Case{"A", {{C('A'), 1}}},
-        DecodeUTF8Case{"ABC", {{C('A'), 1}, {C('B'), 1}, {C('C'), 1}}},
-        DecodeUTF8Case{"DEF", {{C('D'), 1}, {C('E'), 1}, {C('F'), 1}}},
-        DecodeUTF8Case{"GH", {{C('G'), 1}, {C('H'), 1}}},
-        DecodeUTF8Case{"IJ", {{C('I'), 1}, {C('J'), 1}}},
-        DecodeUTF8Case{"KLM", {{C('K'), 1}, {C('L'), 1}, {C('M'), 1}}},
-        DecodeUTF8Case{"NOP", {{C('N'), 1}, {C('O'), 1}, {C('P'), 1}}},
-        DecodeUTF8Case{"QR", {{C('Q'), 1}, {C('R'), 1}}},
-        DecodeUTF8Case{"STU", {{C('S'), 1}, {C('T'), 1}, {C('U'), 1}}},
-        DecodeUTF8Case{"VW", {{C('V'), 1}, {C('W'), 1}}},
-        DecodeUTF8Case{"XYZ", {{C('X'), 1}, {C('Y'), 1}, {C('Z'), 1}}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    AsciiNumbers,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({
-        DecodeUTF8Case{"012", {{C('0'), 1}, {C('1'), 1}, {C('2'), 1}}},
-        DecodeUTF8Case{"345", {{C('3'), 1}, {C('4'), 1}, {C('5'), 1}}},
-        DecodeUTF8Case{"678", {{C('6'), 1}, {C('7'), 1}, {C('8'), 1}}},
-        DecodeUTF8Case{"9", {{C('9'), 1}}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    AsciiSymbols,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({
-        DecodeUTF8Case{"!\"#", {{C('!'), 1}, {C('"'), 1}, {C('#'), 1}}},
-        DecodeUTF8Case{"$%&", {{C('$'), 1}, {C('%'), 1}, {C('&'), 1}}},
-        DecodeUTF8Case{"'()", {{C('\''), 1}, {C('('), 1}, {C(')'), 1}}},
-        DecodeUTF8Case{"*,-", {{C('*'), 1}, {C(','), 1}, {C('-'), 1}}},
-        DecodeUTF8Case{"/`@", {{C('/'), 1}, {C('`'), 1}, {C('@'), 1}}},
-        DecodeUTF8Case{"^\\[", {{C('^'), 1}, {C('\\'), 1}, {C('['), 1}}},
-        DecodeUTF8Case{"]_|", {{C(']'), 1}, {C('_'), 1}, {C('|'), 1}}},
-        DecodeUTF8Case{"{}", {{C('{'), 1}, {C('}'), 1}}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    AsciiSpecial,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({
-        DecodeUTF8Case{"", {}},
-        DecodeUTF8Case{" \t\n", {{C(' '), 1}, {C('\t'), 1}, {C('\n'), 1}}},
-        DecodeUTF8Case{"\a\b\f", {{C('\a'), 1}, {C('\b'), 1}, {C('\f'), 1}}},
-        DecodeUTF8Case{"\n\r\t", {{C('\n'), 1}, {C('\r'), 1}, {C('\t'), 1}}},
-        DecodeUTF8Case{"\v", {{C('\v'), 1}}},
-    }));
-
-INSTANTIATE_TEST_SUITE_P(
-    Hindi,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({DecodeUTF8Case{
-        // नमस्ते दुनिया
-        "\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5"
-        "\x87\x20\xe0\xa4\xa6\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xaf"
-        "\xe0\xa4\xbe",
-        {
-            {C(0x0928), 3},  // न
-            {C(0x092e), 3},  // म
-            {C(0x0938), 3},  // स
-            {C(0x094d), 3},  // ्
-            {C(0x0924), 3},  // त
-            {C(0x0947), 3},  // े
-            {C(' '), 1},
-            {C(0x0926), 3},  // द
-            {C(0x0941), 3},  // ु
-            {C(0x0928), 3},  // न
-            {C(0x093f), 3},  // ि
-            {C(0x092f), 3},  // य
-            {C(0x093e), 3},  // ा
-        },
-    }}));
-
-INSTANTIATE_TEST_SUITE_P(Mandarin,
-                         DecodeUTF8Test,
-                         ::testing::ValuesIn({DecodeUTF8Case{
-                             // 你好世界
-                             "\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c",
-                             {
-                                 {C(0x4f60), 3},  // 你
-                                 {C(0x597d), 3},  // 好
-                                 {C(0x4e16), 3},  // 世
-                                 {C(0x754c), 3},  // 界
-                             },
-                         }}));
-
-INSTANTIATE_TEST_SUITE_P(Japanese,
-                         DecodeUTF8Test,
-                         ::testing::ValuesIn({DecodeUTF8Case{
-                             // こんにちは世界
-                             "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1"
-                             "\xe3\x81\xaf\xe4\xb8\x96\xe7\x95\x8c",
-                             {
-                                 {C(0x3053), 3},  // こ
-                                 {C(0x3093), 3},  // ん
-                                 {C(0x306B), 3},  // に
-                                 {C(0x3061), 3},  // ち
-                                 {C(0x306F), 3},  // は
-                                 {C(0x4E16), 3},  // 世
-                                 {C(0x754C), 3},  // 界
-                             },
-                         }}));
-
-INSTANTIATE_TEST_SUITE_P(Korean,
-                         DecodeUTF8Test,
-                         ::testing::ValuesIn({DecodeUTF8Case{
-                             // 안녕하세요 세계
-                             "\xec\x95\x88\xeb\x85\x95\xed\x95\x98\xec\x84\xb8"
-                             "\xec\x9a\x94\x20\xec\x84\xb8\xea\xb3\x84",
-                             {
-                                 {C(0xc548), 3},  // 안
-                                 {C(0xb155), 3},  // 녕
-                                 {C(0xd558), 3},  // 하
-                                 {C(0xc138), 3},  // 세
-                                 {C(0xc694), 3},  // 요
-                                 {C(' '), 1},     //
-                                 {C(0xc138), 3},  // 세
-                                 {C(0xacc4), 3},  // 계
-                             },
-                         }}));
-
-INSTANTIATE_TEST_SUITE_P(Emoji,
-                         DecodeUTF8Test,
-                         ::testing::ValuesIn({DecodeUTF8Case{
-                             // 👋🌎
-                             "\xf0\x9f\x91\x8b\xf0\x9f\x8c\x8e",
-                             {
-                                 {C(0x1f44b), 4},  // 👋
-                                 {C(0x1f30e), 4},  // 🌎
-                             },
-                         }}));
-
-INSTANTIATE_TEST_SUITE_P(
-    Random,
-    DecodeUTF8Test,
-    ::testing::ValuesIn({DecodeUTF8Case{
-        // Øⓑꚫ쁹Ǵ𐌒岾🥍ⴵ㍨又ᮗ
-        "\xc3\x98\xe2\x93\x91\xea\x9a\xab\xec\x81\xb9\xc7\xb4\xf0\x90\x8c\x92"
-        "\xe5\xb2\xbe\xf0\x9f\xa5\x8d\xe2\xb4\xb5\xe3\x8d\xa8\xe5\x8f\x88\xe1"
-        "\xae\x97",
-        {
-            {C(0x000d8), 2},  // Ø
-            {C(0x024d1), 3},  // ⓑ
-            {C(0x0a6ab), 3},  // ꚫ
-            {C(0x0c079), 3},  // 쁹
-            {C(0x001f4), 2},  // Ǵ
-            {C(0x10312), 4},  // 𐌒
-            {C(0x05cbe), 3},  // 岾
-            {C(0x1f94d), 4},  // 🥍
-            {C(0x02d35), 3},  // ⴵ
-            {C(0x03368), 3},  // ㍨
-            {C(0x053c8), 3},  // 又
-            {C(0x01b97), 3},  // ᮗ
-        },
-    }}));
-
-}  // namespace
-
-////////////////////////////////////////////////////////////////////////////////
-// DecodeUTF8 invalid tests
-////////////////////////////////////////////////////////////////////////////////
-namespace {
-class DecodeUTF8InvalidTest : public testing::TestWithParam<const char*> {};
-
-TEST_P(DecodeUTF8InvalidTest, Invalid) {
-  auto* param = GetParam();
-
-  const uint8_t* data = reinterpret_cast<const uint8_t*>(param);
-  const size_t len = std::string(param).size();
-
-  auto [code_point, width] = utf8::Decode(data, len);
-  EXPECT_EQ(code_point, CodePoint(0));
-  EXPECT_EQ(width, 0u);
-}
-
-INSTANTIATE_TEST_SUITE_P(Invalid,
-                         DecodeUTF8InvalidTest,
-                         ::testing::ValuesIn({
-                             "\x80\x80\x80\x80",  // 10000000
-                             "\x81\x80\x80\x80",  // 10000001
-                             "\x8f\x80\x80\x80",  // 10001111
-                             "\x90\x80\x80\x80",  // 10010000
-                             "\x91\x80\x80\x80",  // 10010001
-                             "\x9f\x80\x80\x80",  // 10011111
-                             "\xa0\x80\x80\x80",  // 10100000
-                             "\xa1\x80\x80\x80",  // 10100001
-                             "\xaf\x80\x80\x80",  // 10101111
-                             "\xb0\x80\x80\x80",  // 10110000
-                             "\xb1\x80\x80\x80",  // 10110001
-                             "\xbf\x80\x80\x80",  // 10111111
-                             "\xc0\x80\x80\x80",  // 11000000
-                             "\xc1\x80\x80\x80",  // 11000001
-                             "\xf5\x80\x80\x80",  // 11110101
-                             "\xf6\x80\x80\x80",  // 11110110
-                             "\xf7\x80\x80\x80",  // 11110111
-                             "\xf8\x80\x80\x80",  // 11111000
-                             "\xfe\x80\x80\x80",  // 11111110
-                             "\xff\x80\x80\x80",  // 11111111
-                         }));
-
-}  // namespace
-
-}  // namespace tint::text
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
new file mode 100644
index 0000000..94bcb00
--- /dev/null
+++ b/src/tint/BUILD.gn
@@ -0,0 +1,788 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("../../tint_overrides_with_defaults.gni")
+
+###############################################################################
+# Common - Configs, etc. shared across targets
+###############################################################################
+
+config("tint_common_config") {
+  include_dirs = [
+    "${target_gen_dir}",
+    "${tint_root_dir}/",
+    "${tint_spirv_headers_dir}/include",
+    "${tint_spirv_tools_dir}/",
+    "${tint_spirv_tools_dir}/include",
+  ]
+}
+
+config("tint_public_config") {
+  defines = []
+  if (tint_build_spv_reader) {
+    defines += [ "TINT_BUILD_SPV_READER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_SPV_READER=0" ]
+  }
+
+  if (tint_build_spv_writer) {
+    defines += [ "TINT_BUILD_SPV_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_SPV_WRITER=0" ]
+  }
+
+  if (tint_build_wgsl_reader) {
+    defines += [ "TINT_BUILD_WGSL_READER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_WGSL_READER=0" ]
+  }
+
+  if (tint_build_wgsl_writer) {
+    defines += [ "TINT_BUILD_WGSL_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_WGSL_WRITER=0" ]
+  }
+
+  if (tint_build_msl_writer) {
+    defines += [ "TINT_BUILD_MSL_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_MSL_WRITER=0" ]
+  }
+
+  if (tint_build_hlsl_writer) {
+    defines += [ "TINT_BUILD_HLSL_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_HLSL_WRITER=0" ]
+  }
+
+  if (tint_build_glsl_writer) {
+    defines += [ "TINT_BUILD_GLSL_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_GLSL_WRITER=0" ]
+  }
+
+  include_dirs = [
+    "${tint_root_dir}/",
+    "${tint_root_dir}/include/",
+    "${tint_spirv_headers_dir}/include",
+  ]
+}
+
+config("tint_config") {
+  include_dirs = []
+  if (tint_build_spv_reader || tint_build_spv_writer) {
+    include_dirs += [ "${tint_spirv_tools_dir}/include/" ]
+  }
+}
+
+###############################################################################
+# Helper library for IO operations
+# Only to be used by tests and sample executable
+###############################################################################
+source_set("tint_utils_io") {
+  sources = [
+    "utils/io/command.h",
+    "utils/io/tmpfile.h",
+  ]
+
+  if (is_linux || is_mac) {
+    sources += [ "utils/io/command_posix.cc" ]
+    sources += [ "utils/io/tmpfile_posix.cc" ]
+  } else if (is_win) {
+    sources += [ "utils/io/command_windows.cc" ]
+    sources += [ "utils/io/tmpfile_windows.cc" ]
+  } else {
+    sources += [ "utils/io/command_other.cc" ]
+    sources += [ "utils/io/tmpfile_other.cc" ]
+  }
+
+  public_deps = [ ":libtint_core_all_src" ]
+}
+
+###############################################################################
+# Helper library for validating generated shaders
+# As this depends on tint_utils_io, this is only to be used by tests and sample
+# executable
+###############################################################################
+source_set("tint_val") {
+  sources = [
+    "val/hlsl.cc",
+    "val/msl.cc",
+    "val/val.h",
+  ]
+  public_deps = [ ":tint_utils_io" ]
+}
+
+###############################################################################
+# Library - Tint core and optional modules of libtint
+###############################################################################
+# libtint source sets are divided into a non-optional core in :libtint_core_src
+# and optional :libtint_*_src subsets, because ninja does not like having
+# multiple source files with the same name, like function.cc, in the same
+# source set
+# target.
+#
+# Targets that want to use tint as a library should depend on ":libtint" and
+# use the build flags to control what is included, instead of trying to specify
+# the subsets that they want.
+
+template("libtint_source_set") {
+  source_set(target_name) {
+    forward_variables_from(invoker, "*", [ "configs" ])
+
+    if (!defined(invoker.deps)) {
+      deps = []
+    }
+    deps += [
+      "${tint_spirv_headers_dir}:spv_headers",
+      "${tint_spirv_tools_dir}:spvtools_core_enums_unified1",
+      "${tint_spirv_tools_dir}:spvtools_core_tables_unified1",
+      "${tint_spirv_tools_dir}:spvtools_headers",
+      "${tint_spirv_tools_dir}:spvtools_language_header_cldebuginfo100",
+      "${tint_spirv_tools_dir}:spvtools_language_header_debuginfo",
+      "${tint_spirv_tools_dir}:spvtools_language_header_vkdebuginfo100",
+    ]
+
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+    configs += [ ":tint_common_config" ]
+    if (build_with_chromium) {
+      configs -= [ "//build/config/compiler:chromium_code" ]
+      configs += [ "//build/config/compiler:no_chromium_code" ]
+    }
+
+    if (!defined(invoker.public_configs)) {
+      public_configs = []
+    }
+    public_configs += [ ":tint_public_config" ]
+  }
+}
+
+libtint_source_set("libtint_core_all_src") {
+  sources = [
+    "ast/access.cc",
+    "ast/access.h",
+    "ast/alias.cc",
+    "ast/alias.h",
+    "ast/array.cc",
+    "ast/array.h",
+    "ast/assignment_statement.cc",
+    "ast/assignment_statement.h",
+    "ast/ast_type.cc",  # TODO(bclayton) - rename to type.cc
+    "ast/atomic.cc",
+    "ast/atomic.h",
+    "ast/attribute.cc",
+    "ast/attribute.h",
+    "ast/binary_expression.cc",
+    "ast/binary_expression.h",
+    "ast/binding_attribute.cc",
+    "ast/binding_attribute.h",
+    "ast/bitcast_expression.cc",
+    "ast/bitcast_expression.h",
+    "ast/block_statement.cc",
+    "ast/block_statement.h",
+    "ast/bool.cc",
+    "ast/bool.h",
+    "ast/bool_literal_expression.cc",
+    "ast/bool_literal_expression.h",
+    "ast/break_statement.cc",
+    "ast/break_statement.h",
+    "ast/builtin.cc",
+    "ast/builtin.h",
+    "ast/builtin_attribute.cc",
+    "ast/builtin_attribute.h",
+    "ast/call_expression.cc",
+    "ast/call_expression.h",
+    "ast/call_statement.cc",
+    "ast/call_statement.h",
+    "ast/case_statement.cc",
+    "ast/case_statement.h",
+    "ast/continue_statement.cc",
+    "ast/continue_statement.h",
+    "ast/depth_multisampled_texture.cc",
+    "ast/depth_multisampled_texture.h",
+    "ast/depth_texture.cc",
+    "ast/depth_texture.h",
+    "ast/disable_validation_attribute.cc",
+    "ast/disable_validation_attribute.h",
+    "ast/discard_statement.cc",
+    "ast/discard_statement.h",
+    "ast/else_statement.cc",
+    "ast/else_statement.h",
+    "ast/expression.cc",
+    "ast/expression.h",
+    "ast/external_texture.cc",
+    "ast/external_texture.h",
+    "ast/f32.cc",
+    "ast/f32.h",
+    "ast/fallthrough_statement.cc",
+    "ast/fallthrough_statement.h",
+    "ast/float_literal_expression.cc",
+    "ast/float_literal_expression.h",
+    "ast/for_loop_statement.cc",
+    "ast/for_loop_statement.h",
+    "ast/function.cc",
+    "ast/function.h",
+    "ast/group_attribute.cc",
+    "ast/group_attribute.h",
+    "ast/i32.cc",
+    "ast/i32.h",
+    "ast/id_attribute.cc",
+    "ast/id_attribute.h",
+    "ast/identifier_expression.cc",
+    "ast/identifier_expression.h",
+    "ast/if_statement.cc",
+    "ast/if_statement.h",
+    "ast/index_accessor_expression.cc",
+    "ast/index_accessor_expression.h",
+    "ast/int_literal_expression.cc",
+    "ast/int_literal_expression.h",
+    "ast/internal_attribute.cc",
+    "ast/internal_attribute.h",
+    "ast/interpolate_attribute.cc",
+    "ast/interpolate_attribute.h",
+    "ast/invariant_attribute.cc",
+    "ast/invariant_attribute.h",
+    "ast/literal_expression.cc",
+    "ast/literal_expression.h",
+    "ast/location_attribute.cc",
+    "ast/location_attribute.h",
+    "ast/loop_statement.cc",
+    "ast/loop_statement.h",
+    "ast/matrix.cc",
+    "ast/matrix.h",
+    "ast/member_accessor_expression.cc",
+    "ast/member_accessor_expression.h",
+    "ast/module.cc",
+    "ast/module.h",
+    "ast/multisampled_texture.cc",
+    "ast/multisampled_texture.h",
+    "ast/node.cc",
+    "ast/node.h",
+    "ast/phony_expression.cc",
+    "ast/phony_expression.h",
+    "ast/pipeline_stage.cc",
+    "ast/pipeline_stage.h",
+    "ast/pointer.cc",
+    "ast/pointer.h",
+    "ast/return_statement.cc",
+    "ast/return_statement.h",
+    "ast/sampled_texture.cc",
+    "ast/sampled_texture.h",
+    "ast/sampler.cc",
+    "ast/sampler.h",
+    "ast/sint_literal_expression.cc",
+    "ast/sint_literal_expression.h",
+    "ast/stage_attribute.cc",
+    "ast/stage_attribute.h",
+    "ast/statement.cc",
+    "ast/statement.h",
+    "ast/storage_class.cc",
+    "ast/storage_class.h",
+    "ast/storage_texture.cc",
+    "ast/storage_texture.h",
+    "ast/stride_attribute.cc",
+    "ast/stride_attribute.h",
+    "ast/struct.cc",
+    "ast/struct.h",
+    "ast/struct_block_attribute.cc",
+    "ast/struct_block_attribute.h",
+    "ast/struct_member.cc",
+    "ast/struct_member.h",
+    "ast/struct_member_align_attribute.cc",
+    "ast/struct_member_align_attribute.h",
+    "ast/struct_member_offset_attribute.cc",
+    "ast/struct_member_offset_attribute.h",
+    "ast/struct_member_size_attribute.cc",
+    "ast/struct_member_size_attribute.h",
+    "ast/switch_statement.cc",
+    "ast/switch_statement.h",
+    "ast/texture.cc",
+    "ast/texture.h",
+    "ast/traverse_expressions.h",
+    "ast/type.h",
+    "ast/type_decl.cc",
+    "ast/type_decl.h",
+    "ast/type_name.cc",
+    "ast/type_name.h",
+    "ast/u32.cc",
+    "ast/u32.h",
+    "ast/uint_literal_expression.cc",
+    "ast/uint_literal_expression.h",
+    "ast/unary_op.cc",
+    "ast/unary_op.h",
+    "ast/unary_op_expression.cc",
+    "ast/unary_op_expression.h",
+    "ast/variable.cc",
+    "ast/variable.h",
+    "ast/variable_decl_statement.cc",
+    "ast/variable_decl_statement.h",
+    "ast/vector.cc",
+    "ast/vector.h",
+    "ast/void.cc",
+    "ast/void.h",
+    "ast/workgroup_attribute.cc",
+    "ast/workgroup_attribute.h",
+    "block_allocator.h",
+    "builtin_table.cc",
+    "builtin_table.h",
+    "builtin_table.inl",
+    "castable.cc",
+    "castable.h",
+    "clone_context.cc",
+    "clone_context.h",
+    "debug.cc",
+    "debug.h",
+    "demangler.cc",
+    "demangler.h",
+    "diagnostic/diagnostic.cc",
+    "diagnostic/diagnostic.h",
+    "diagnostic/formatter.cc",
+    "diagnostic/formatter.h",
+    "diagnostic/printer.cc",
+    "diagnostic/printer.h",
+    "inspector/entry_point.cc",
+    "inspector/entry_point.h",
+    "inspector/inspector.cc",
+    "inspector/inspector.h",
+    "inspector/resource_binding.cc",
+    "inspector/resource_binding.h",
+    "inspector/scalar.cc",
+    "inspector/scalar.h",
+    "program.cc",
+    "program.h",
+    "program_builder.cc",
+    "program_builder.h",
+    "program_id.cc",
+    "program_id.h",
+    "reader/reader.cc",
+    "reader/reader.h",
+    "resolver/dependency_graph.cc",
+    "resolver/dependency_graph.h",
+    "resolver/resolver.cc",
+    "resolver/resolver.h",
+    "resolver/resolver_constants.cc",
+    "resolver/resolver_validation.cc",
+    "scope_stack.h",
+    "sem/array.h",
+    "sem/atomic_type.h",
+    "sem/behavior.h",
+    "sem/binding_point.h",
+    "sem/bool_type.h",
+    "sem/builtin.h",
+    "sem/builtin_type.h",
+    "sem/call.h",
+    "sem/call_target.h",
+    "sem/constant.h",
+    "sem/depth_multisampled_texture_type.h",
+    "sem/depth_texture_type.h",
+    "sem/expression.h",
+    "sem/external_texture_type.h",
+    "sem/f32_type.h",
+    "sem/for_loop_statement.h",
+    "sem/i32_type.h",
+    "sem/if_statement.h",
+    "sem/info.h",
+    "sem/loop_statement.h",
+    "sem/matrix_type.h",
+    "sem/module.h",
+    "sem/multisampled_texture_type.h",
+    "sem/node.h",
+    "sem/parameter_usage.h",
+    "sem/pipeline_stage_set.h",
+    "sem/pointer_type.h",
+    "sem/reference_type.h",
+    "sem/sampled_texture_type.h",
+    "sem/sampler_texture_pair.h",
+    "sem/sampler_type.h",
+    "sem/storage_texture_type.h",
+    "sem/switch_statement.h",
+    "sem/texture_type.h",
+    "sem/type.h",
+    "sem/type_constructor.h",
+    "sem/type_conversion.h",
+    "sem/type_manager.h",
+    "sem/type_mappings.h",
+    "sem/u32_type.h",
+    "sem/vector_type.h",
+    "sem/void_type.h",
+    "source.cc",
+    "source.h",
+    "symbol.cc",
+    "symbol.h",
+    "symbol_table.cc",
+    "symbol_table.h",
+    "text/unicode.cc",
+    "text/unicode.h",
+    "traits.h",
+    "transform/add_empty_entry_point.cc",
+    "transform/add_empty_entry_point.h",
+    "transform/add_spirv_block_attribute.cc",
+    "transform/add_spirv_block_attribute.h",
+    "transform/array_length_from_uniform.cc",
+    "transform/array_length_from_uniform.h",
+    "transform/binding_remapper.cc",
+    "transform/binding_remapper.h",
+    "transform/calculate_array_length.cc",
+    "transform/calculate_array_length.h",
+    "transform/canonicalize_entry_point_io.cc",
+    "transform/canonicalize_entry_point_io.h",
+    "transform/combine_samplers.cc",
+    "transform/combine_samplers.h",
+    "transform/decompose_memory_access.cc",
+    "transform/decompose_memory_access.h",
+    "transform/decompose_strided_array.cc",
+    "transform/decompose_strided_array.h",
+    "transform/decompose_strided_matrix.cc",
+    "transform/decompose_strided_matrix.h",
+    "transform/external_texture_transform.cc",
+    "transform/external_texture_transform.h",
+    "transform/first_index_offset.cc",
+    "transform/first_index_offset.h",
+    "transform/fold_constants.cc",
+    "transform/fold_constants.h",
+    "transform/fold_trivial_single_use_lets.cc",
+    "transform/fold_trivial_single_use_lets.h",
+    "transform/for_loop_to_loop.cc",
+    "transform/for_loop_to_loop.h",
+    "transform/localize_struct_array_assignment.cc",
+    "transform/localize_struct_array_assignment.h",
+    "transform/loop_to_for_loop.cc",
+    "transform/loop_to_for_loop.h",
+    "transform/manager.cc",
+    "transform/manager.h",
+    "transform/module_scope_var_to_entry_point_param.cc",
+    "transform/module_scope_var_to_entry_point_param.h",
+    "transform/multiplanar_external_texture.cc",
+    "transform/multiplanar_external_texture.h",
+    "transform/num_workgroups_from_uniform.cc",
+    "transform/num_workgroups_from_uniform.h",
+    "transform/pad_array_elements.cc",
+    "transform/pad_array_elements.h",
+    "transform/promote_initializers_to_const_var.cc",
+    "transform/promote_initializers_to_const_var.h",
+    "transform/remove_phonies.cc",
+    "transform/remove_phonies.h",
+    "transform/remove_unreachable_statements.cc",
+    "transform/remove_unreachable_statements.h",
+    "transform/renamer.cc",
+    "transform/renamer.h",
+    "transform/robustness.cc",
+    "transform/robustness.h",
+    "transform/simplify_pointers.cc",
+    "transform/simplify_pointers.h",
+    "transform/single_entry_point.cc",
+    "transform/single_entry_point.h",
+    "transform/transform.cc",
+    "transform/transform.h",
+    "transform/unshadow.cc",
+    "transform/unshadow.h",
+    "transform/utils/hoist_to_decl_before.cc",
+    "transform/utils/hoist_to_decl_before.h",
+    "transform/var_for_dynamic_index.cc",
+    "transform/var_for_dynamic_index.h",
+    "transform/vectorize_scalar_matrix_constructors.cc",
+    "transform/vectorize_scalar_matrix_constructors.h",
+    "transform/vertex_pulling.cc",
+    "transform/vertex_pulling.h",
+    "transform/wrap_arrays_in_structs.cc",
+    "transform/wrap_arrays_in_structs.h",
+    "transform/zero_init_workgroup_memory.cc",
+    "transform/zero_init_workgroup_memory.h",
+    "utils/crc32.h",
+    "utils/debugger.cc",
+    "utils/debugger.h",
+    "utils/enum_set.h",
+    "utils/hash.h",
+    "utils/map.h",
+    "utils/math.h",
+    "utils/scoped_assignment.h",
+    "utils/string.h",
+    "utils/unique_vector.h",
+    "writer/append_vector.cc",
+    "writer/append_vector.h",
+    "writer/array_length_from_uniform_options.cc",
+    "writer/array_length_from_uniform_options.h",
+    "writer/float_to_string.cc",
+    "writer/float_to_string.h",
+    "writer/text.cc",
+    "writer/text.h",
+    "writer/text_generator.cc",
+    "writer/text_generator.h",
+    "writer/writer.cc",
+    "writer/writer.h",
+  ]
+
+  if (is_linux) {
+    sources += [ "diagnostic/printer_linux.cc" ]
+  } else if (is_win) {
+    sources += [ "diagnostic/printer_windows.cc" ]
+  } else {
+    sources += [ "diagnostic/printer_other.cc" ]
+  }
+}
+
+libtint_source_set("libtint_sem_src") {
+  sources = [
+    "sem/array.cc",
+    "sem/array.h",
+    "sem/atomic_type.cc",
+    "sem/atomic_type.h",
+    "sem/behavior.cc",
+    "sem/behavior.h",
+    "sem/binding_point.h",
+    "sem/block_statement.cc",
+    "sem/bool_type.cc",
+    "sem/bool_type.h",
+    "sem/builtin.cc",
+    "sem/builtin.h",
+    "sem/builtin_type.cc",
+    "sem/builtin_type.h",
+    "sem/call.cc",
+    "sem/call.h",
+    "sem/call_target.cc",
+    "sem/call_target.h",
+    "sem/constant.cc",
+    "sem/constant.h",
+    "sem/depth_multisampled_texture_type.cc",
+    "sem/depth_multisampled_texture_type.h",
+    "sem/depth_texture_type.cc",
+    "sem/depth_texture_type.h",
+    "sem/expression.cc",
+    "sem/expression.h",
+    "sem/external_texture_type.cc",
+    "sem/external_texture_type.h",
+    "sem/f32_type.cc",
+    "sem/f32_type.h",
+    "sem/for_loop_statement.cc",
+    "sem/for_loop_statement.h",
+    "sem/function.cc",
+    "sem/i32_type.cc",
+    "sem/i32_type.h",
+    "sem/if_statement.cc",
+    "sem/if_statement.h",
+    "sem/info.cc",
+    "sem/info.h",
+    "sem/loop_statement.cc",
+    "sem/loop_statement.h",
+    "sem/matrix_type.cc",
+    "sem/matrix_type.h",
+    "sem/member_accessor_expression.cc",
+    "sem/module.cc",
+    "sem/module.h",
+    "sem/multisampled_texture_type.cc",
+    "sem/multisampled_texture_type.h",
+    "sem/node.cc",
+    "sem/node.h",
+    "sem/parameter_usage.cc",
+    "sem/parameter_usage.h",
+    "sem/pipeline_stage_set.h",
+    "sem/pointer_type.cc",
+    "sem/pointer_type.h",
+    "sem/reference_type.cc",
+    "sem/reference_type.h",
+    "sem/sampled_texture_type.cc",
+    "sem/sampled_texture_type.h",
+    "sem/sampler_type.cc",
+    "sem/sampler_type.h",
+    "sem/statement.cc",
+    "sem/storage_texture_type.cc",
+    "sem/storage_texture_type.h",
+    "sem/struct.cc",
+    "sem/switch_statement.cc",
+    "sem/switch_statement.h",
+    "sem/texture_type.cc",
+    "sem/texture_type.h",
+    "sem/type.cc",
+    "sem/type.h",
+    "sem/type_constructor.cc",
+    "sem/type_constructor.h",
+    "sem/type_conversion.cc",
+    "sem/type_conversion.h",
+    "sem/type_manager.cc",
+    "sem/type_manager.h",
+    "sem/type_mappings.h",
+    "sem/u32_type.cc",
+    "sem/u32_type.h",
+    "sem/variable.cc",
+    "sem/vector_type.cc",
+    "sem/vector_type.h",
+    "sem/void_type.cc",
+    "sem/void_type.h",
+  ]
+
+  public_deps = [ ":libtint_core_all_src" ]
+}
+
+libtint_source_set("libtint_core_src") {
+  public_deps = [
+    ":libtint_core_all_src",
+    ":libtint_sem_src",
+  ]
+}
+
+libtint_source_set("libtint_spv_reader_src") {
+  sources = [
+    "reader/spirv/construct.cc",
+    "reader/spirv/construct.h",
+    "reader/spirv/entry_point_info.cc",
+    "reader/spirv/entry_point_info.h",
+    "reader/spirv/enum_converter.cc",
+    "reader/spirv/enum_converter.h",
+    "reader/spirv/fail_stream.h",
+    "reader/spirv/function.cc",
+    "reader/spirv/function.h",
+    "reader/spirv/namer.cc",
+    "reader/spirv/namer.h",
+    "reader/spirv/parser.cc",
+    "reader/spirv/parser.h",
+    "reader/spirv/parser_impl.cc",
+    "reader/spirv/parser_impl.h",
+    "reader/spirv/parser_type.cc",
+    "reader/spirv/parser_type.h",
+    "reader/spirv/usage.cc",
+    "reader/spirv/usage.h",
+  ]
+
+  public_deps = [
+    ":libtint_core_src",
+    "${tint_spirv_tools_dir}/:spvtools_opt",
+  ]
+
+  public_configs = [ "${tint_spirv_tools_dir}/:spvtools_internal_config" ]
+}
+
+libtint_source_set("libtint_spv_writer_src") {
+  sources = [
+    "writer/spirv/binary_writer.cc",
+    "writer/spirv/binary_writer.h",
+    "writer/spirv/builder.cc",
+    "writer/spirv/builder.h",
+    "writer/spirv/function.cc",
+    "writer/spirv/function.h",
+    "writer/spirv/generator.cc",
+    "writer/spirv/generator.h",
+    "writer/spirv/instruction.cc",
+    "writer/spirv/instruction.h",
+    "writer/spirv/operand.cc",
+    "writer/spirv/operand.h",
+    "writer/spirv/scalar_constant.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+libtint_source_set("libtint_wgsl_reader_src") {
+  sources = [
+    "reader/wgsl/lexer.cc",
+    "reader/wgsl/lexer.h",
+    "reader/wgsl/parser.cc",
+    "reader/wgsl/parser.h",
+    "reader/wgsl/parser_impl.cc",
+    "reader/wgsl/parser_impl.h",
+    "reader/wgsl/parser_impl_detail.h",
+    "reader/wgsl/token.cc",
+    "reader/wgsl/token.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+libtint_source_set("libtint_wgsl_writer_src") {
+  sources = [
+    "writer/wgsl/generator.cc",
+    "writer/wgsl/generator.h",
+    "writer/wgsl/generator_impl.cc",
+    "writer/wgsl/generator_impl.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+libtint_source_set("libtint_msl_writer_src") {
+  sources = [
+    "writer/msl/generator.cc",
+    "writer/msl/generator.h",
+    "writer/msl/generator_impl.cc",
+    "writer/msl/generator_impl.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+libtint_source_set("libtint_hlsl_writer_src") {
+  sources = [
+    "writer/hlsl/generator.cc",
+    "writer/hlsl/generator.h",
+    "writer/hlsl/generator_impl.cc",
+    "writer/hlsl/generator_impl.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+libtint_source_set("libtint_glsl_writer_src") {
+  sources = [
+    "transform/glsl.cc",
+    "transform/glsl.h",
+    "writer/glsl/generator.cc",
+    "writer/glsl/generator.h",
+    "writer/glsl/generator_impl.cc",
+    "writer/glsl/generator_impl.h",
+  ]
+
+  public_deps = [ ":libtint_core_src" ]
+}
+
+source_set("libtint") {
+  public_deps = [ ":libtint_core_src" ]
+
+  if (tint_build_spv_reader) {
+    public_deps += [ ":libtint_spv_reader_src" ]
+  }
+
+  if (tint_build_spv_writer) {
+    public_deps += [ ":libtint_spv_writer_src" ]
+  }
+
+  if (tint_build_wgsl_reader) {
+    public_deps += [ ":libtint_wgsl_reader_src" ]
+  }
+
+  if (tint_build_wgsl_writer) {
+    public_deps += [ ":libtint_wgsl_writer_src" ]
+  }
+
+  if (tint_build_msl_writer) {
+    public_deps += [ ":libtint_msl_writer_src" ]
+  }
+
+  if (tint_build_hlsl_writer) {
+    public_deps += [ ":libtint_hlsl_writer_src" ]
+  }
+
+  if (tint_build_glsl_writer) {
+    public_deps += [ ":libtint_glsl_writer_src" ]
+  }
+
+  configs += [ ":tint_common_config" ]
+  public_configs = [ ":tint_public_config" ]
+
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+}
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
new file mode 100644
index 0000000..e8f794d
--- /dev/null
+++ b/src/tint/CMakeLists.txt
@@ -0,0 +1,1221 @@
+# Copyright 2020 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.
+
+function(tint_spvtools_compile_options TARGET)
+  # We'll use the optimizer for its nice SPIR-V in-memory representation
+  target_link_libraries(${TARGET} SPIRV-Tools-opt SPIRV-Tools)
+
+  # We'll be cheating: using internal interfaces to the SPIRV-Tools
+  # optimizer.
+  target_include_directories(${TARGET} PRIVATE
+    ${spirv-tools_SOURCE_DIR}
+    ${spirv-tools_BINARY_DIR}
+  )
+
+  if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
+    # The SPIRV-Tools code is conditioned against C++ and an older version of Clang.
+    # Suppress warnings triggered in our current compilation environment.
+    # TODO(dneto): Fix the issues upstream.
+    target_compile_options(${TARGET} PRIVATE
+      -Wno-newline-eof
+      -Wno-sign-conversion
+      -Wno-old-style-cast
+      -Wno-weak-vtables
+    )
+  endif()
+endfunction()
+
+## Tint diagnostic utilities. Used by libtint and tint_utils_io.
+add_library(tint_diagnostic_utils
+  debug.cc
+  debug.h
+  source.h
+  source.cc
+  diagnostic/diagnostic.cc
+  diagnostic/diagnostic.h
+  diagnostic/formatter.cc
+  diagnostic/formatter.h
+  diagnostic/printer.cc
+  diagnostic/printer.h
+  utils/debugger.cc
+  utils/debugger.h
+)
+tint_default_compile_options(tint_diagnostic_utils)
+
+if (TINT_ENABLE_BREAK_IN_DEBUGGER)
+  set_source_files_properties(utils/debugger.cc
+    PROPERTIES COMPILE_DEFINITIONS "TINT_ENABLE_BREAK_IN_DEBUGGER=1" )
+endif()
+
+set(TINT_LIB_SRCS
+  ../../include/tint/tint.h
+  ast/access.cc
+  ast/access.h
+  ast/attribute.cc
+  ast/attribute.h
+  ast/alias.cc
+  ast/alias.h
+  ast/index_accessor_expression.cc
+  ast/index_accessor_expression.h
+  ast/array.cc
+  ast/array.h
+  ast/assignment_statement.cc
+  ast/assignment_statement.h
+  ast/atomic.cc
+  ast/atomic.h
+  ast/binary_expression.cc
+  ast/binary_expression.h
+  ast/binding_attribute.cc
+  ast/binding_attribute.h
+  ast/bitcast_expression.cc
+  ast/bitcast_expression.h
+  ast/block_statement.cc
+  ast/block_statement.h
+  ast/bool_literal_expression.cc
+  ast/bool_literal_expression.h
+  ast/bool.cc
+  ast/bool.h
+  ast/break_statement.cc
+  ast/break_statement.h
+  ast/builtin_attribute.cc
+  ast/builtin_attribute.h
+  ast/builtin.cc
+  ast/builtin.h
+  ast/call_expression.cc
+  ast/call_expression.h
+  ast/call_statement.cc
+  ast/call_statement.h
+  ast/case_statement.cc
+  ast/case_statement.h
+  ast/continue_statement.cc
+  ast/continue_statement.h
+  ast/depth_multisampled_texture.cc
+  ast/depth_multisampled_texture.h
+  ast/disable_validation_attribute.cc
+  ast/disable_validation_attribute.h
+  ast/depth_texture.cc
+  ast/depth_texture.h
+  ast/discard_statement.cc
+  ast/discard_statement.h
+  ast/else_statement.cc
+  ast/else_statement.h
+  ast/expression.cc
+  ast/expression.h
+  ast/external_texture.cc
+  ast/external_texture.h
+  ast/f32.cc
+  ast/f32.h
+  ast/fallthrough_statement.cc
+  ast/fallthrough_statement.h
+  ast/float_literal_expression.cc
+  ast/float_literal_expression.h
+  ast/for_loop_statement.cc
+  ast/for_loop_statement.h
+  ast/function.cc
+  ast/function.h
+  ast/group_attribute.cc
+  ast/group_attribute.h
+  ast/i32.cc
+  ast/i32.h
+  ast/id_attribute.cc
+  ast/id_attribute.h
+  ast/identifier_expression.cc
+  ast/identifier_expression.h
+  ast/if_statement.cc
+  ast/if_statement.h
+  ast/int_literal_expression.cc
+  ast/int_literal_expression.h
+  ast/internal_attribute.cc
+  ast/internal_attribute.h
+  ast/interpolate_attribute.cc
+  ast/interpolate_attribute.h
+  ast/invariant_attribute.cc
+  ast/invariant_attribute.h
+  ast/literal_expression.cc
+  ast/literal_expression.h
+  ast/location_attribute.cc
+  ast/location_attribute.h
+  ast/loop_statement.cc
+  ast/loop_statement.h
+  ast/matrix.cc
+  ast/matrix.h
+  ast/member_accessor_expression.cc
+  ast/member_accessor_expression.h
+  ast/module.cc
+  ast/module.h
+  ast/multisampled_texture.cc
+  ast/multisampled_texture.h
+  ast/node.cc
+  ast/node.h
+  ast/phony_expression.cc
+  ast/phony_expression.h
+  ast/pipeline_stage.cc
+  ast/pipeline_stage.h
+  ast/pointer.cc
+  ast/pointer.h
+  ast/return_statement.cc
+  ast/return_statement.h
+  ast/sampled_texture.cc
+  ast/sampled_texture.h
+  ast/sampler.cc
+  ast/sampler.h
+  ast/sint_literal_expression.cc
+  ast/sint_literal_expression.h
+  ast/stage_attribute.cc
+  ast/stage_attribute.h
+  ast/statement.cc
+  ast/statement.h
+  ast/storage_class.cc
+  ast/storage_class.h
+  ast/storage_texture.cc
+  ast/storage_texture.h
+  ast/stride_attribute.cc
+  ast/stride_attribute.h
+  ast/struct_block_attribute.cc
+  ast/struct_block_attribute.h
+  ast/struct_member_align_attribute.cc
+  ast/struct_member_align_attribute.h
+  ast/struct_member_offset_attribute.cc
+  ast/struct_member_offset_attribute.h
+  ast/struct_member_size_attribute.cc
+  ast/struct_member_size_attribute.h
+  ast/struct_member.cc
+  ast/struct_member.h
+  ast/struct.cc
+  ast/struct.h
+  ast/switch_statement.cc
+  ast/switch_statement.h
+  ast/texture.cc
+  ast/texture.h
+  ast/traverse_expressions.h
+  ast/type_name.cc
+  ast/type_name.h
+  ast/ast_type.cc  # TODO(bclayton) - rename to type.cc
+  ast/type.h
+  ast/type_decl.cc
+  ast/type_decl.h
+  ast/type_name.cc
+  ast/type_name.h
+  ast/u32.cc
+  ast/u32.h
+  ast/uint_literal_expression.cc
+  ast/uint_literal_expression.h
+  ast/unary_op_expression.cc
+  ast/unary_op_expression.h
+  ast/unary_op.cc
+  ast/unary_op.h
+  ast/variable_decl_statement.cc
+  ast/variable_decl_statement.h
+  ast/variable.cc
+  ast/variable.h
+  ast/vector.cc
+  ast/vector.h
+  ast/void.cc
+  ast/void.h
+  ast/workgroup_attribute.cc
+  ast/workgroup_attribute.h
+  block_allocator.h
+  builtin_table.cc
+  builtin_table.h
+  builtin_table.inl
+  castable.cc
+  castable.h
+  clone_context.cc
+  clone_context.h
+  demangler.cc
+  demangler.h
+  inspector/entry_point.cc
+  inspector/entry_point.h
+  inspector/inspector.cc
+  inspector/inspector.h
+  inspector/resource_binding.cc
+  inspector/resource_binding.h
+  inspector/scalar.cc
+  inspector/scalar.h
+  program_builder.cc
+  program_builder.h
+  program_id.cc
+  program_id.h
+  program.cc
+  program.h
+  reader/reader.cc
+  reader/reader.h
+  resolver/dependency_graph.cc
+  resolver/dependency_graph.h
+  resolver/resolver.cc
+  resolver/resolver_constants.cc
+  resolver/resolver_validation.cc
+  resolver/resolver.h
+  scope_stack.h
+  sem/array.cc
+  sem/array.h
+  sem/atomic_type.cc
+  sem/atomic_type.h
+  sem/behavior.cc
+  sem/behavior.h
+  sem/binding_point.h
+  sem/block_statement.cc
+  sem/block_statement.h
+  sem/builtin_type.cc
+  sem/builtin_type.h
+  sem/builtin.cc
+  sem/builtin.h
+  sem/call_target.cc
+  sem/call_target.h
+  sem/call.cc
+  sem/call.h
+  sem/constant.cc
+  sem/constant.h
+  sem/depth_multisampled_texture_type.cc
+  sem/depth_multisampled_texture_type.h
+  sem/expression.cc
+  sem/expression.h
+  sem/function.cc
+  sem/info.cc
+  sem/info.h
+  sem/member_accessor_expression.cc
+  sem/parameter_usage.cc
+  sem/parameter_usage.h
+  sem/pipeline_stage_set.h
+  sem/node.cc
+  sem/node.h
+  sem/module.cc
+  sem/module.h
+  sem/sampler_texture_pair.h
+  sem/statement.cc
+  sem/struct.cc
+  sem/type_mappings.h
+  sem/variable.cc
+  symbol_table.cc
+  symbol_table.h
+  symbol.cc
+  symbol.h
+  text/unicode.cc
+  text/unicode.h
+  traits.h
+  transform/add_empty_entry_point.cc
+  transform/add_empty_entry_point.h
+  transform/add_spirv_block_attribute.cc
+  transform/add_spirv_block_attribute.h
+  transform/array_length_from_uniform.cc
+  transform/array_length_from_uniform.h
+  transform/binding_remapper.cc
+  transform/binding_remapper.h
+  transform/calculate_array_length.cc
+  transform/calculate_array_length.h
+  transform/combine_samplers.cc
+  transform/combine_samplers.h
+  transform/canonicalize_entry_point_io.cc
+  transform/canonicalize_entry_point_io.h
+  transform/decompose_memory_access.cc
+  transform/decompose_memory_access.h
+  transform/decompose_strided_array.cc
+  transform/decompose_strided_array.h
+  transform/decompose_strided_matrix.cc
+  transform/decompose_strided_matrix.h
+  transform/external_texture_transform.cc
+  transform/external_texture_transform.h
+  transform/first_index_offset.cc
+  transform/first_index_offset.h
+  transform/fold_constants.cc
+  transform/fold_constants.h
+  transform/fold_trivial_single_use_lets.cc
+  transform/fold_trivial_single_use_lets.h
+  transform/localize_struct_array_assignment.cc
+  transform/localize_struct_array_assignment.h
+  transform/for_loop_to_loop.cc
+  transform/for_loop_to_loop.h
+  transform/glsl.cc
+  transform/glsl.h
+  transform/loop_to_for_loop.cc
+  transform/loop_to_for_loop.h
+  transform/manager.cc
+  transform/manager.h
+  transform/module_scope_var_to_entry_point_param.cc
+  transform/module_scope_var_to_entry_point_param.h
+  transform/multiplanar_external_texture.cc
+  transform/multiplanar_external_texture.h
+  transform/num_workgroups_from_uniform.cc
+  transform/num_workgroups_from_uniform.h
+  transform/pad_array_elements.cc
+  transform/pad_array_elements.h
+  transform/promote_initializers_to_const_var.cc
+  transform/promote_initializers_to_const_var.h
+  transform/remove_phonies.cc
+  transform/remove_phonies.h
+  transform/remove_unreachable_statements.cc
+  transform/remove_unreachable_statements.h
+  transform/renamer.cc
+  transform/renamer.h
+  transform/robustness.cc
+  transform/robustness.h
+  transform/simplify_pointers.cc
+  transform/simplify_pointers.h
+  transform/single_entry_point.cc
+  transform/single_entry_point.h
+  transform/transform.cc
+  transform/transform.h
+  transform/unshadow.cc
+  transform/unshadow.h
+  transform/vectorize_scalar_matrix_constructors.cc
+  transform/vectorize_scalar_matrix_constructors.h
+  transform/var_for_dynamic_index.cc
+  transform/var_for_dynamic_index.h
+  transform/vertex_pulling.cc
+  transform/vertex_pulling.h
+  transform/wrap_arrays_in_structs.cc
+  transform/wrap_arrays_in_structs.h
+  transform/zero_init_workgroup_memory.cc
+  transform/zero_init_workgroup_memory.h
+  transform/utils/hoist_to_decl_before.cc
+  transform/utils/hoist_to_decl_before.h
+  sem/bool_type.cc
+  sem/bool_type.h
+  sem/depth_texture_type.cc
+  sem/depth_texture_type.h
+  sem/external_texture_type.cc
+  sem/external_texture_type.h
+  sem/f32_type.cc
+  sem/f32_type.h
+  sem/for_loop_statement.cc
+  sem/for_loop_statement.h
+  sem/i32_type.cc
+  sem/i32_type.h
+  sem/if_statement.cc
+  sem/if_statement.h
+  sem/loop_statement.cc
+  sem/loop_statement.h
+  sem/matrix_type.cc
+  sem/matrix_type.h
+  sem/multisampled_texture_type.cc
+  sem/multisampled_texture_type.h
+  sem/pointer_type.cc
+  sem/pointer_type.h
+  sem/reference_type.cc
+  sem/reference_type.h
+  sem/sampled_texture_type.cc
+  sem/sampled_texture_type.h
+  sem/sampler_type.cc
+  sem/sampler_type.h
+  sem/storage_texture_type.cc
+  sem/storage_texture_type.h
+  sem/switch_statement.cc
+  sem/switch_statement.h
+  sem/texture_type.cc
+  sem/texture_type.h
+  sem/type_constructor.cc
+  sem/type_constructor.h
+  sem/type_conversion.cc
+  sem/type_conversion.h
+  sem/type.cc
+  sem/type.h
+  sem/type_manager.cc
+  sem/type_manager.h
+  sem/u32_type.cc
+  sem/u32_type.h
+  sem/vector_type.cc
+  sem/vector_type.h
+  sem/void_type.cc
+  sem/void_type.h
+  utils/crc32.h
+  utils/enum_set.h
+  utils/hash.h
+  utils/map.h
+  utils/math.h
+  utils/scoped_assignment.h
+  utils/string.h
+  utils/unique_vector.h
+  writer/append_vector.cc
+  writer/append_vector.h
+  writer/array_length_from_uniform_options.cc
+  writer/array_length_from_uniform_options.h
+  writer/float_to_string.cc
+  writer/float_to_string.h
+  writer/text_generator.cc
+  writer/text_generator.h
+  writer/text.cc
+  writer/text.h
+  writer/writer.cc
+  writer/writer.h
+)
+
+if(UNIX)
+  list(APPEND TINT_LIB_SRCS diagnostic/printer_linux.cc)
+elseif(WIN32)
+  list(APPEND TINT_LIB_SRCS diagnostic/printer_windows.cc)
+else()
+  list(APPEND TINT_LIB_SRCS diagnostic/printer_other.cc)
+endif()
+
+if(${TINT_BUILD_SPV_READER})
+  list(APPEND TINT_LIB_SRCS
+    reader/spirv/construct.h
+    reader/spirv/construct.cc
+    reader/spirv/entry_point_info.h
+    reader/spirv/entry_point_info.cc
+    reader/spirv/enum_converter.h
+    reader/spirv/enum_converter.cc
+    reader/spirv/fail_stream.h
+    reader/spirv/function.cc
+    reader/spirv/function.h
+    reader/spirv/namer.cc
+    reader/spirv/namer.h
+    reader/spirv/parser_type.cc
+    reader/spirv/parser_type.h
+    reader/spirv/parser.cc
+    reader/spirv/parser.h
+    reader/spirv/parser_impl.cc
+    reader/spirv/parser_impl.h
+    reader/spirv/usage.cc
+    reader/spirv/usage.h
+  )
+endif()
+
+if(${TINT_BUILD_WGSL_READER})
+  list(APPEND TINT_LIB_SRCS
+    reader/wgsl/lexer.cc
+    reader/wgsl/lexer.h
+    reader/wgsl/parser.cc
+    reader/wgsl/parser.h
+    reader/wgsl/parser_impl.cc
+    reader/wgsl/parser_impl.h
+    reader/wgsl/parser_impl_detail.h
+    reader/wgsl/token.cc
+    reader/wgsl/token.h
+  )
+endif()
+
+if(${TINT_BUILD_SPV_WRITER})
+  list(APPEND TINT_LIB_SRCS
+    writer/spirv/binary_writer.cc
+    writer/spirv/binary_writer.h
+    writer/spirv/builder.cc
+    writer/spirv/builder.h
+    writer/spirv/function.cc
+    writer/spirv/function.h
+    writer/spirv/generator.cc
+    writer/spirv/generator.h
+    writer/spirv/instruction.cc
+    writer/spirv/instruction.h
+    writer/spirv/operand.cc
+    writer/spirv/operand.h
+    writer/spirv/scalar_constant.h
+  )
+endif()
+
+if(${TINT_BUILD_WGSL_WRITER})
+  list(APPEND TINT_LIB_SRCS
+    writer/wgsl/generator.cc
+    writer/wgsl/generator.h
+    writer/wgsl/generator_impl.cc
+    writer/wgsl/generator_impl.h
+  )
+endif()
+
+if(${TINT_BUILD_MSL_WRITER})
+  list(APPEND TINT_LIB_SRCS
+    writer/msl/generator.cc
+    writer/msl/generator.h
+    writer/msl/generator_impl.cc
+    writer/msl/generator_impl.h
+  )
+endif()
+
+if(${TINT_BUILD_GLSL_WRITER})
+  list(APPEND TINT_LIB_SRCS
+    writer/glsl/generator.cc
+    writer/glsl/generator.h
+    writer/glsl/generator_impl.cc
+    writer/glsl/generator_impl.h
+    writer/glsl/version.h
+  )
+endif()
+
+if(${TINT_BUILD_HLSL_WRITER})
+  list(APPEND TINT_LIB_SRCS
+    writer/hlsl/generator.cc
+    writer/hlsl/generator.h
+    writer/hlsl/generator_impl.cc
+    writer/hlsl/generator_impl.h
+  )
+endif()
+
+if(MSVC)
+  list(APPEND TINT_LIB_SRCS
+    tint.natvis
+  )
+endif()
+
+## Tint IO utilities. Used by tint_val.
+add_library(tint_utils_io
+  utils/io/command_${TINT_OS_CC_SUFFIX}.cc
+  utils/io/command.h
+  utils/io/tmpfile_${TINT_OS_CC_SUFFIX}.cc
+  utils/io/tmpfile.h
+)
+tint_default_compile_options(tint_utils_io)
+target_link_libraries(tint_utils_io tint_diagnostic_utils)
+
+## Tint validation utilities. Used by tests and the tint executable.
+add_library(tint_val
+  val/hlsl.cc
+  val/msl.cc
+  val/val.h
+)
+
+# If we're building on mac / ios and we have CoreGraphics, then we can use the
+# metal API to validate our shaders. This is roughly 4x faster than invoking
+# the metal shader compiler executable.
+if(APPLE)
+  find_library(LIB_CORE_GRAPHICS CoreGraphics)
+  if(LIB_CORE_GRAPHICS)
+    target_sources(tint_val PRIVATE "val/msl_metal.mm")
+    target_compile_definitions(tint_val PUBLIC "-DTINT_ENABLE_MSL_VALIDATION_USING_METAL_API=1")
+    target_compile_options(tint_val PRIVATE "-fmodules" "-fcxx-modules")
+    target_link_options(tint_val PUBLIC "-framework" "CoreGraphics")
+  endif()
+endif()
+
+tint_default_compile_options(tint_val)
+target_link_libraries(tint_val tint_utils_io)
+
+## Tint library
+add_library(libtint ${TINT_LIB_SRCS})
+tint_default_compile_options(libtint)
+target_link_libraries(libtint tint_diagnostic_utils)
+if (${COMPILER_IS_LIKE_GNU})
+  target_compile_options(libtint PRIVATE -fvisibility=hidden)
+endif()
+if (${TINT_SYMBOL_STORE_DEBUG_NAME})
+    target_compile_definitions(libtint PUBLIC "TINT_SYMBOL_STORE_DEBUG_NAME=1")
+endif()
+set_target_properties(libtint PROPERTIES OUTPUT_NAME "tint")
+
+if (${TINT_BUILD_FUZZERS})
+  # Tint library with fuzzer instrumentation
+  add_library(libtint-fuzz ${TINT_LIB_SRCS})
+  tint_default_compile_options(libtint-fuzz)
+  target_link_libraries(libtint-fuzz tint_diagnostic_utils)
+  if (${COMPILER_IS_LIKE_GNU})
+    target_compile_options(libtint-fuzz PRIVATE -fvisibility=hidden)
+  endif()
+
+  if (NOT ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "")
+    # This is set when the fuzzers are being built by OSS-Fuzz. In this case the
+    # variable provides the necessary linker flags, and OSS-Fuzz will take care
+    # of passing suitable compiler flags.
+    target_link_options(libtint-fuzz PUBLIC ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS})
+  else()
+    # When the fuzzers are being built outside of OSS-Fuzz, specific libFuzzer
+    # arguments to enable fuzzing are used.
+    target_compile_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp)
+    target_link_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp)
+  endif()
+endif()
+
+if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
+  tint_spvtools_compile_options(libtint)
+  if (${TINT_BUILD_FUZZERS})
+    tint_spvtools_compile_options(libtint-fuzz)
+  endif()
+endif()
+
+################################################################################
+# Tests
+################################################################################
+if(TINT_BUILD_TESTS)
+  set(TINT_TEST_SRCS
+    ast/alias_test.cc
+    ast/array_test.cc
+    ast/assignment_statement_test.cc
+    ast/atomic_test.cc
+    ast/binary_expression_test.cc
+    ast/binding_attribute_test.cc
+    ast/bitcast_expression_test.cc
+    ast/block_statement_test.cc
+    ast/bool_literal_expression_test.cc
+    ast/bool_test.cc
+    ast/break_statement_test.cc
+    ast/builtin_attribute_test.cc
+    ast/builtin_texture_helper_test.cc
+    ast/builtin_texture_helper_test.h
+    ast/call_expression_test.cc
+    ast/call_statement_test.cc
+    ast/case_statement_test.cc
+    ast/continue_statement_test.cc
+    ast/depth_multisampled_texture_test.cc
+    ast/depth_texture_test.cc
+    ast/discard_statement_test.cc
+    ast/else_statement_test.cc
+    ast/external_texture_test.cc
+    ast/f32_test.cc
+    ast/fallthrough_statement_test.cc
+    ast/float_literal_expression_test.cc
+    ast/for_loop_statement_test.cc
+    ast/function_test.cc
+    ast/group_attribute_test.cc
+    ast/i32_test.cc
+    ast/id_attribute_test.cc
+    ast/identifier_expression_test.cc
+    ast/if_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
+    ast/matrix_test.cc
+    ast/member_accessor_expression_test.cc
+    ast/module_clone_test.cc
+    ast/module_test.cc
+    ast/multisampled_texture_test.cc
+    ast/phony_expression_test.cc
+    ast/pointer_test.cc
+    ast/return_statement_test.cc
+    ast/sampled_texture_test.cc
+    ast/sampler_test.cc
+    ast/sint_literal_expression_test.cc
+    ast/stage_attribute_test.cc
+    ast/storage_texture_test.cc
+    ast/stride_attribute_test.cc
+    ast/struct_member_align_attribute_test.cc
+    ast/struct_member_offset_attribute_test.cc
+    ast/struct_member_size_attribute_test.cc
+    ast/struct_member_test.cc
+    ast/struct_test.cc
+    ast/switch_statement_test.cc
+    ast/test_helper.h
+    ast/texture_test.cc
+    ast/traverse_expressions_test.cc
+    ast/u32_test.cc
+    ast/uint_literal_expression_test.cc
+    ast/unary_op_expression_test.cc
+    ast/variable_decl_statement_test.cc
+    ast/variable_test.cc
+    ast/vector_test.cc
+    ast/workgroup_attribute_test.cc
+    block_allocator_test.cc
+    builtin_table_test.cc
+    castable_test.cc
+    clone_context_test.cc
+    debug_test.cc
+    demangler_test.cc
+    diagnostic/diagnostic_test.cc
+    diagnostic/formatter_test.cc
+    diagnostic/printer_test.cc
+    program_test.cc
+    resolver/array_accessor_test.cc
+    resolver/assignment_validation_test.cc
+    resolver/atomics_test.cc
+    resolver/atomics_validation_test.cc
+    resolver/bitcast_validation_test.cc
+    resolver/builtins_validation_test.cc
+    resolver/builtin_test.cc
+    resolver/builtin_validation_test.cc
+    resolver/call_test.cc
+    resolver/call_validation_test.cc
+    resolver/compound_statement_test.cc
+    resolver/control_block_validation_test.cc
+    resolver/attribute_validation_test.cc
+    resolver/dependency_graph_test.cc
+    resolver/entry_point_validation_test.cc
+    resolver/function_validation_test.cc
+    resolver/host_shareable_validation_test.cc
+    resolver/inferred_type_test.cc
+    resolver/is_host_shareable_test.cc
+    resolver/is_storeable_test.cc
+    resolver/pipeline_overridable_constant_test.cc
+    resolver/ptr_ref_test.cc
+    resolver/ptr_ref_validation_test.cc
+    resolver/resolver_behavior_test.cc
+    resolver/resolver_constants_test.cc
+    resolver/resolver_test_helper.cc
+    resolver/resolver_test_helper.h
+    resolver/resolver_test.cc
+    resolver/side_effects_test.cc
+    resolver/storage_class_layout_validation_test.cc
+    resolver/storage_class_validation_test.cc
+    resolver/struct_layout_test.cc
+    resolver/struct_pipeline_stage_use_test.cc
+    resolver/struct_storage_class_use_test.cc
+    resolver/type_constructor_validation_test.cc
+    resolver/type_validation_test.cc
+    resolver/validation_test.cc
+    resolver/var_let_test.cc
+    resolver/var_let_validation_test.cc
+    scope_stack_test.cc
+    sem/atomic_type_test.cc
+    sem/bool_type_test.cc
+    sem/builtin_test.cc
+    sem/depth_multisampled_texture_type_test.cc
+    sem/depth_texture_type_test.cc
+    sem/external_texture_type_test.cc
+    sem/f32_type_test.cc
+    sem/i32_type_test.cc
+    sem/matrix_type_test.cc
+    sem/multisampled_texture_type_test.cc
+    sem/pointer_type_test.cc
+    sem/reference_type_test.cc
+    sem/sampled_texture_type_test.cc
+    sem/sampler_type_test.cc
+    sem/sem_array_test.cc
+    sem/sem_struct_test.cc
+    sem/storage_texture_type_test.cc
+    sem/texture_type_test.cc
+    sem/type_manager_test.cc
+    sem/u32_type_test.cc
+    sem/vector_type_test.cc
+    source_test.cc
+    symbol_table_test.cc
+    symbol_test.cc
+    test_main.cc
+    text/unicode_test.cc
+    traits_test.cc
+    transform/transform_test.cc
+    utils/crc32_test.cc
+    utils/defer_test.cc
+    utils/enum_set_test.cc
+    utils/hash_test.cc
+    utils/io/command_test.cc
+    utils/io/tmpfile_test.cc
+    utils/map_test.cc
+    utils/math_test.cc
+    utils/reverse_test.cc
+    utils/scoped_assignment_test.cc
+    utils/string_test.cc
+    utils/transform_test.cc
+    utils/unique_vector_test.cc
+    writer/append_vector_test.cc
+    writer/float_to_string_test.cc
+    writer/text_generator_test.cc
+  )
+
+  # Inspector tests depend on WGSL reader
+  if(${TINT_BUILD_WGSL_READER})
+    list(APPEND TINT_TEST_SRCS
+      inspector/inspector_test.cc
+      inspector/test_inspector_builder.cc
+      inspector/test_inspector_builder.h
+      inspector/test_inspector_runner.cc
+      inspector/test_inspector_runner.h
+    )
+  endif()
+
+  if(${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_WGSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      reader/spirv/enum_converter_test.cc
+      reader/spirv/fail_stream_test.cc
+      reader/spirv/function_arithmetic_test.cc
+      reader/spirv/function_bit_test.cc
+      reader/spirv/function_cfg_test.cc
+      reader/spirv/function_call_test.cc
+      reader/spirv/function_composite_test.cc
+      reader/spirv/function_conversion_test.cc
+      reader/spirv/function_decl_test.cc
+      reader/spirv/function_glsl_std_450_test.cc
+      reader/spirv/function_logical_test.cc
+      reader/spirv/function_memory_test.cc
+      reader/spirv/function_misc_test.cc
+      reader/spirv/function_var_test.cc
+      reader/spirv/namer_test.cc
+      reader/spirv/parser_impl_barrier_test.cc
+      reader/spirv/parser_impl_convert_member_decoration_test.cc
+      reader/spirv/parser_impl_convert_type_test.cc
+      reader/spirv/parser_impl_function_decl_test.cc
+      reader/spirv/parser_impl_get_decorations_test.cc
+      reader/spirv/parser_impl_handle_test.cc
+      reader/spirv/parser_impl_import_test.cc
+      reader/spirv/parser_impl_module_var_test.cc
+      reader/spirv/parser_impl_named_types_test.cc
+      reader/spirv/parser_impl_test_helper.cc
+      reader/spirv/parser_impl_test_helper.h
+      reader/spirv/parser_impl_test.cc
+      reader/spirv/parser_impl_user_name_test.cc
+      reader/spirv/parser_type_test.cc
+      reader/spirv/parser_test.cc
+      reader/spirv/spirv_tools_helpers_test.cc
+      reader/spirv/spirv_tools_helpers_test.h
+      reader/spirv/usage_test.cc
+    )
+  endif()
+
+  if(${TINT_BUILD_WGSL_READER})
+    list(APPEND TINT_TEST_SRCS
+      reader/wgsl/lexer_test.cc
+      reader/wgsl/parser_test.cc
+      reader/wgsl/parser_impl_additive_expression_test.cc
+      reader/wgsl/parser_impl_and_expression_test.cc
+      reader/wgsl/parser_impl_argument_expression_list_test.cc
+      reader/wgsl/parser_impl_assignment_stmt_test.cc
+      reader/wgsl/parser_impl_body_stmt_test.cc
+      reader/wgsl/parser_impl_break_stmt_test.cc
+      reader/wgsl/parser_impl_bug_cases_test.cc
+      reader/wgsl/parser_impl_call_stmt_test.cc
+      reader/wgsl/parser_impl_case_body_test.cc
+      reader/wgsl/parser_impl_const_expr_test.cc
+      reader/wgsl/parser_impl_const_literal_test.cc
+      reader/wgsl/parser_impl_continue_stmt_test.cc
+      reader/wgsl/parser_impl_continuing_stmt_test.cc
+      reader/wgsl/parser_impl_depth_texture_type_test.cc
+      reader/wgsl/parser_impl_external_texture_type_test.cc
+      reader/wgsl/parser_impl_elseif_stmt_test.cc
+      reader/wgsl/parser_impl_equality_expression_test.cc
+      reader/wgsl/parser_impl_error_msg_test.cc
+      reader/wgsl/parser_impl_error_resync_test.cc
+      reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+      reader/wgsl/parser_impl_for_stmt_test.cc
+      reader/wgsl/parser_impl_function_decl_test.cc
+      reader/wgsl/parser_impl_function_attribute_list_test.cc
+      reader/wgsl/parser_impl_function_attribute_test.cc
+      reader/wgsl/parser_impl_function_header_test.cc
+      reader/wgsl/parser_impl_global_constant_decl_test.cc
+      reader/wgsl/parser_impl_global_decl_test.cc
+      reader/wgsl/parser_impl_global_variable_decl_test.cc
+      reader/wgsl/parser_impl_if_stmt_test.cc
+      reader/wgsl/parser_impl_inclusive_or_expression_test.cc
+      reader/wgsl/parser_impl_logical_and_expression_test.cc
+      reader/wgsl/parser_impl_logical_or_expression_test.cc
+      reader/wgsl/parser_impl_loop_stmt_test.cc
+      reader/wgsl/parser_impl_multiplicative_expression_test.cc
+      reader/wgsl/parser_impl_param_list_test.cc
+      reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
+      reader/wgsl/parser_impl_pipeline_stage_test.cc
+      reader/wgsl/parser_impl_primary_expression_test.cc
+      reader/wgsl/parser_impl_relational_expression_test.cc
+      reader/wgsl/parser_impl_reserved_keyword_test.cc
+      reader/wgsl/parser_impl_sampled_texture_type_test.cc
+      reader/wgsl/parser_impl_sampler_type_test.cc
+      reader/wgsl/parser_impl_shift_expression_test.cc
+      reader/wgsl/parser_impl_singular_expression_test.cc
+      reader/wgsl/parser_impl_statement_test.cc
+      reader/wgsl/parser_impl_statements_test.cc
+      reader/wgsl/parser_impl_storage_class_test.cc
+      reader/wgsl/parser_impl_storage_texture_type_test.cc
+      reader/wgsl/parser_impl_struct_body_decl_test.cc
+      reader/wgsl/parser_impl_struct_decl_test.cc
+      reader/wgsl/parser_impl_struct_attribute_decl_test.cc
+      reader/wgsl/parser_impl_struct_attribute_test.cc
+      reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
+      reader/wgsl/parser_impl_struct_member_attribute_test.cc
+      reader/wgsl/parser_impl_struct_member_test.cc
+      reader/wgsl/parser_impl_switch_body_test.cc
+      reader/wgsl/parser_impl_switch_stmt_test.cc
+      reader/wgsl/parser_impl_test.cc
+      reader/wgsl/parser_impl_test_helper.cc
+      reader/wgsl/parser_impl_test_helper.h
+      reader/wgsl/parser_impl_texel_format_test.cc
+      reader/wgsl/parser_impl_texture_sampler_types_test.cc
+      reader/wgsl/parser_impl_type_alias_test.cc
+      reader/wgsl/parser_impl_type_decl_test.cc
+      reader/wgsl/parser_impl_unary_expression_test.cc
+      reader/wgsl/parser_impl_variable_decl_test.cc
+      reader/wgsl/parser_impl_variable_attribute_list_test.cc
+      reader/wgsl/parser_impl_variable_attribute_test.cc
+      reader/wgsl/parser_impl_variable_ident_decl_test.cc
+      reader/wgsl/parser_impl_variable_stmt_test.cc
+      reader/wgsl/parser_impl_variable_qualifier_test.cc
+      reader/wgsl/token_test.cc
+    )
+  endif()
+
+  if(${TINT_BUILD_SPV_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      writer/spirv/binary_writer_test.cc
+      writer/spirv/builder_accessor_expression_test.cc
+      writer/spirv/builder_assign_test.cc
+      writer/spirv/builder_binary_expression_test.cc
+      writer/spirv/builder_bitcast_expression_test.cc
+      writer/spirv/builder_block_test.cc
+      writer/spirv/builder_builtin_test.cc
+      writer/spirv/builder_builtin_texture_test.cc
+      writer/spirv/builder_call_test.cc
+      writer/spirv/builder_constructor_expression_test.cc
+      writer/spirv/builder_discard_test.cc
+      writer/spirv/builder_entry_point_test.cc
+      writer/spirv/builder_format_conversion_test.cc
+      writer/spirv/builder_function_attribute_test.cc
+      writer/spirv/builder_function_test.cc
+      writer/spirv/builder_function_variable_test.cc
+      writer/spirv/builder_global_variable_test.cc
+      writer/spirv/builder_ident_expression_test.cc
+      writer/spirv/builder_if_test.cc
+      writer/spirv/builder_literal_test.cc
+      writer/spirv/builder_loop_test.cc
+      writer/spirv/builder_return_test.cc
+      writer/spirv/builder_switch_test.cc
+      writer/spirv/builder_test.cc
+      writer/spirv/builder_type_test.cc
+      writer/spirv/builder_unary_op_expression_test.cc
+      writer/spirv/instruction_test.cc
+      writer/spirv/operand_test.cc
+      writer/spirv/scalar_constant_test.cc
+      writer/spirv/spv_dump.cc
+      writer/spirv/spv_dump.h
+      writer/spirv/test_helper.h
+    )
+  endif()
+
+  if(${TINT_BUILD_WGSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      writer/wgsl/generator_impl_test.cc
+      writer/wgsl/generator_impl_alias_type_test.cc
+      writer/wgsl/generator_impl_array_accessor_test.cc
+      writer/wgsl/generator_impl_assign_test.cc
+      writer/wgsl/generator_impl_binary_test.cc
+      writer/wgsl/generator_impl_bitcast_test.cc
+      writer/wgsl/generator_impl_block_test.cc
+      writer/wgsl/generator_impl_break_test.cc
+      writer/wgsl/generator_impl_call_test.cc
+      writer/wgsl/generator_impl_case_test.cc
+      writer/wgsl/generator_impl_cast_test.cc
+      writer/wgsl/generator_impl_constructor_test.cc
+      writer/wgsl/generator_impl_continue_test.cc
+      writer/wgsl/generator_impl_discard_test.cc
+      writer/wgsl/generator_impl_fallthrough_test.cc
+      writer/wgsl/generator_impl_function_test.cc
+      writer/wgsl/generator_impl_global_decl_test.cc
+      writer/wgsl/generator_impl_identifier_test.cc
+      writer/wgsl/generator_impl_if_test.cc
+      writer/wgsl/generator_impl_loop_test.cc
+      writer/wgsl/generator_impl_literal_test.cc
+      writer/wgsl/generator_impl_member_accessor_test.cc
+      writer/wgsl/generator_impl_return_test.cc
+      writer/wgsl/generator_impl_switch_test.cc
+      writer/wgsl/generator_impl_type_test.cc
+      writer/wgsl/generator_impl_unary_op_test.cc
+      writer/wgsl/generator_impl_variable_decl_statement_test.cc
+      writer/wgsl/generator_impl_variable_test.cc
+      writer/wgsl/test_helper.h
+    )
+  endif()
+
+  if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      transform/add_empty_entry_point_test.cc
+      transform/add_spirv_block_attribute_test.cc
+      transform/array_length_from_uniform_test.cc
+      transform/binding_remapper_test.cc
+      transform/calculate_array_length_test.cc
+      transform/canonicalize_entry_point_io_test.cc
+      transform/combine_samplers_test.cc
+      transform/decompose_memory_access_test.cc
+      transform/decompose_strided_array_test.cc
+      transform/decompose_strided_matrix_test.cc
+      transform/external_texture_transform_test.cc
+      transform/first_index_offset_test.cc
+      transform/fold_constants_test.cc
+      transform/fold_trivial_single_use_lets_test.cc
+      transform/for_loop_to_loop_test.cc
+      transform/localize_struct_array_assignment_test.cc
+      transform/loop_to_for_loop_test.cc
+      transform/module_scope_var_to_entry_point_param_test.cc
+      transform/multiplanar_external_texture_test.cc
+      transform/num_workgroups_from_uniform_test.cc
+      transform/pad_array_elements_test.cc
+      transform/promote_initializers_to_const_var_test.cc
+      transform/remove_phonies_test.cc
+      transform/remove_unreachable_statements_test.cc
+      transform/renamer_test.cc
+      transform/robustness_test.cc
+      transform/simplify_pointers_test.cc
+      transform/single_entry_point_test.cc
+      transform/test_helper.h
+      transform/unshadow_test.cc
+      transform/var_for_dynamic_index_test.cc
+      transform/vectorize_scalar_matrix_constructors_test.cc
+      transform/vertex_pulling_test.cc
+      transform/wrap_arrays_in_structs_test.cc
+      transform/zero_init_workgroup_memory_test.cc
+      transform/utils/hoist_to_decl_before_test.cc
+    )
+  endif()
+
+  if(${TINT_BUILD_MSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      writer/msl/generator_impl_array_accessor_test.cc
+      writer/msl/generator_impl_assign_test.cc
+      writer/msl/generator_impl_binary_test.cc
+      writer/msl/generator_impl_bitcast_test.cc
+      writer/msl/generator_impl_block_test.cc
+      writer/msl/generator_impl_break_test.cc
+      writer/msl/generator_impl_builtin_test.cc
+      writer/msl/generator_impl_builtin_texture_test.cc
+      writer/msl/generator_impl_call_test.cc
+      writer/msl/generator_impl_case_test.cc
+      writer/msl/generator_impl_cast_test.cc
+      writer/msl/generator_impl_constructor_test.cc
+      writer/msl/generator_impl_continue_test.cc
+      writer/msl/generator_impl_discard_test.cc
+      writer/msl/generator_impl_function_test.cc
+      writer/msl/generator_impl_identifier_test.cc
+      writer/msl/generator_impl_if_test.cc
+      writer/msl/generator_impl_import_test.cc
+      writer/msl/generator_impl_loop_test.cc
+      writer/msl/generator_impl_member_accessor_test.cc
+      writer/msl/generator_impl_module_constant_test.cc
+      writer/msl/generator_impl_return_test.cc
+      writer/msl/generator_impl_sanitizer_test.cc
+      writer/msl/generator_impl_switch_test.cc
+      writer/msl/generator_impl_test.cc
+      writer/msl/generator_impl_type_test.cc
+      writer/msl/generator_impl_unary_op_test.cc
+      writer/msl/generator_impl_variable_decl_statement_test.cc
+      writer/msl/test_helper.h
+    )
+  endif()
+
+  if (${TINT_BUILD_GLSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      writer/glsl/generator_impl_array_accessor_test.cc
+      writer/glsl/generator_impl_assign_test.cc
+      writer/glsl/generator_impl_binary_test.cc
+      writer/glsl/generator_impl_bitcast_test.cc
+      writer/glsl/generator_impl_block_test.cc
+      writer/glsl/generator_impl_break_test.cc
+      writer/glsl/generator_impl_builtin_test.cc
+      writer/glsl/generator_impl_builtin_texture_test.cc
+      writer/glsl/generator_impl_call_test.cc
+      writer/glsl/generator_impl_case_test.cc
+      writer/glsl/generator_impl_cast_test.cc
+      writer/glsl/generator_impl_constructor_test.cc
+      writer/glsl/generator_impl_continue_test.cc
+      writer/glsl/generator_impl_discard_test.cc
+      writer/glsl/generator_impl_function_test.cc
+      writer/glsl/generator_impl_identifier_test.cc
+      writer/glsl/generator_impl_if_test.cc
+      writer/glsl/generator_impl_import_test.cc
+      writer/glsl/generator_impl_loop_test.cc
+      writer/glsl/generator_impl_member_accessor_test.cc
+      writer/glsl/generator_impl_module_constant_test.cc
+      writer/glsl/generator_impl_return_test.cc
+      writer/glsl/generator_impl_sanitizer_test.cc
+      writer/glsl/generator_impl_storage_buffer_test.cc
+      writer/glsl/generator_impl_switch_test.cc
+      writer/glsl/generator_impl_test.cc
+      writer/glsl/generator_impl_type_test.cc
+      writer/glsl/generator_impl_unary_op_test.cc
+      writer/glsl/generator_impl_uniform_buffer_test.cc
+      writer/glsl/generator_impl_variable_decl_statement_test.cc
+      writer/glsl/generator_impl_workgroup_var_test.cc
+      writer/glsl/test_helper.h
+    )
+  endif()
+
+  if (${TINT_BUILD_HLSL_WRITER})
+    list(APPEND TINT_TEST_SRCS
+      writer/hlsl/generator_impl_array_accessor_test.cc
+      writer/hlsl/generator_impl_assign_test.cc
+      writer/hlsl/generator_impl_binary_test.cc
+      writer/hlsl/generator_impl_bitcast_test.cc
+      writer/hlsl/generator_impl_block_test.cc
+      writer/hlsl/generator_impl_break_test.cc
+      writer/hlsl/generator_impl_builtin_test.cc
+      writer/hlsl/generator_impl_builtin_texture_test.cc
+      writer/hlsl/generator_impl_call_test.cc
+      writer/hlsl/generator_impl_case_test.cc
+      writer/hlsl/generator_impl_cast_test.cc
+      writer/hlsl/generator_impl_constructor_test.cc
+      writer/hlsl/generator_impl_continue_test.cc
+      writer/hlsl/generator_impl_discard_test.cc
+      writer/hlsl/generator_impl_function_test.cc
+      writer/hlsl/generator_impl_identifier_test.cc
+      writer/hlsl/generator_impl_if_test.cc
+      writer/hlsl/generator_impl_import_test.cc
+      writer/hlsl/generator_impl_loop_test.cc
+      writer/hlsl/generator_impl_member_accessor_test.cc
+      writer/hlsl/generator_impl_module_constant_test.cc
+      writer/hlsl/generator_impl_return_test.cc
+      writer/hlsl/generator_impl_sanitizer_test.cc
+      writer/hlsl/generator_impl_switch_test.cc
+      writer/hlsl/generator_impl_test.cc
+      writer/hlsl/generator_impl_type_test.cc
+      writer/hlsl/generator_impl_unary_op_test.cc
+      writer/hlsl/generator_impl_variable_decl_statement_test.cc
+      writer/hlsl/generator_impl_workgroup_var_test.cc
+      writer/hlsl/test_helper.h
+    )
+  endif()
+
+  if (${TINT_BUILD_FUZZERS})
+    list(APPEND TINT_TEST_SRCS
+      fuzzers/mersenne_twister_engine.cc
+      fuzzers/mersenne_twister_engine.h
+      fuzzers/random_generator.cc
+      fuzzers/random_generator.h
+      fuzzers/random_generator_engine.cc
+      fuzzers/random_generator_engine.h
+      fuzzers/random_generator_test.cc
+    )
+  endif()
+
+  add_executable(tint_unittests ${TINT_TEST_SRCS})
+  set_target_properties(${target} PROPERTIES FOLDER "Tests")
+
+  if(NOT MSVC)
+    target_compile_options(tint_unittests PRIVATE
+      -Wno-global-constructors
+      -Wno-weak-vtables
+    )
+  endif()
+
+  ## Test executable
+  target_include_directories(
+      tint_unittests PRIVATE ${gmock_SOURCE_DIR}/include)
+  target_link_libraries(tint_unittests libtint gmock tint_utils_io)
+  tint_default_compile_options(tint_unittests)
+
+  if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
+    tint_spvtools_compile_options(tint_unittests)
+  endif()
+
+  add_test(NAME tint_unittests COMMAND tint_unittests)
+endif(TINT_BUILD_TESTS)
+
+################################################################################
+# Benchmarks
+################################################################################
+if(TINT_BUILD_BENCHMARKS)
+  if(NOT TINT_BUILD_WGSL_READER)
+    message(FATAL_ERROR "TINT_BUILD_BENCHMARKS requires TINT_BUILD_WGSL_READER")
+  endif()
+
+  set(TINT_BENCHMARK_SRC
+    "castable_bench.cc"
+    "bench/benchmark.cc"
+    "reader/wgsl/parser_bench.cc"
+  )
+
+  if (${TINT_BUILD_GLSL_WRITER})
+    list(APPEND TINT_BENCHMARK_SRC writer/glsl/generator_bench.cc)
+  endif()
+  if (${TINT_BUILD_HLSL_WRITER})
+    list(APPEND TINT_BENCHMARK_SRC writer/hlsl/generator_bench.cc)
+  endif()
+  if (${TINT_BUILD_MSL_WRITER})
+    list(APPEND TINT_BENCHMARK_SRC writer/msl/generator_bench.cc)
+  endif()
+  if (${TINT_BUILD_SPV_WRITER})
+    list(APPEND TINT_BENCHMARK_SRC writer/spirv/generator_bench.cc)
+  endif()
+  if (${TINT_BUILD_WGSL_WRITER})
+    list(APPEND TINT_BENCHMARK_SRC writer/wgsl/generator_bench.cc)
+  endif()
+
+  add_executable(tint-benchmark ${TINT_BENCHMARK_SRC})
+  set_target_properties(${target} PROPERTIES FOLDER "Benchmarks")
+
+  tint_core_compile_options(tint-benchmark)
+
+  target_link_libraries(tint-benchmark PRIVATE benchmark::benchmark libtint)
+endif(TINT_BUILD_BENCHMARKS)
diff --git a/src/tint/ast/access.cc b/src/tint/ast/access.cc
new file mode 100644
index 0000000..cb5f864
--- /dev/null
+++ b/src/tint/ast/access.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/access.h"
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, Access access) {
+  switch (access) {
+    case ast::Access::kUndefined: {
+      out << "undefined";
+      break;
+    }
+    case ast::Access::kRead: {
+      out << "read";
+      break;
+    }
+    case ast::Access::kReadWrite: {
+      out << "read_write";
+      break;
+    }
+    case ast::Access::kWrite: {
+      out << "write";
+      break;
+    }
+  }
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/access.h b/src/tint/ast/access.h
new file mode 100644
index 0000000..67ad714
--- /dev/null
+++ b/src/tint/ast/access.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ACCESS_H_
+#define SRC_TINT_AST_ACCESS_H_
+
+#include <ostream>
+#include <string>
+
+namespace tint {
+namespace ast {
+
+/// The access control settings
+enum Access {
+  /// Not declared in the source
+  kUndefined = 0,
+  /// Read only
+  kRead,
+  /// Write only
+  kWrite,
+  /// Read write
+  kReadWrite,
+  // Last valid access mode
+  kLastValid = kReadWrite,
+};
+
+/// @param out the std::ostream to write to
+/// @param access the Access
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, Access access);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ACCESS_H_
diff --git a/src/tint/ast/alias.cc b/src/tint/ast/alias.cc
new file mode 100644
index 0000000..d852667
--- /dev/null
+++ b/src/tint/ast/alias.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/alias.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Alias);
+
+namespace tint {
+namespace ast {
+
+Alias::Alias(ProgramID pid,
+             const Source& src,
+             const Symbol& n,
+             const Type* subtype)
+    : Base(pid, src, n), type(subtype) {
+  TINT_ASSERT(AST, type);
+}
+
+Alias::Alias(Alias&&) = default;
+
+Alias::~Alias() = default;
+
+const Alias* Alias::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto sym = ctx->Clone(name);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<Alias>(src, sym, ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/alias.h b/src/tint/ast/alias.h
new file mode 100644
index 0000000..b308fa5
--- /dev/null
+++ b/src/tint/ast/alias.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ALIAS_H_
+#define SRC_TINT_AST_ALIAS_H_
+
+#include <string>
+
+#include "src/tint/ast/type_decl.h"
+
+namespace tint {
+namespace ast {
+
+/// A type alias type. Holds a name and pointer to another type.
+class Alias : public Castable<Alias, TypeDecl> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param name the symbol for the alias
+  /// @param subtype the alias'd type
+  Alias(ProgramID pid,
+        const Source& src,
+        const Symbol& name,
+        const Type* subtype);
+  /// Move constructor
+  Alias(Alias&&);
+  /// Destructor
+  ~Alias() override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Alias* Clone(CloneContext* ctx) const override;
+
+  /// the alias type
+  const Type* const type;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ALIAS_H_
diff --git a/src/tint/ast/alias_test.cc b/src/tint/ast/alias_test.cc
new file mode 100644
index 0000000..db82082
--- /dev/null
+++ b/src/tint/ast/alias_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/struct.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstAliasTest = TestHelper;
+
+TEST_F(AstAliasTest, Create) {
+  auto* u32 = create<U32>();
+  auto* a = Alias("a_type", u32);
+  EXPECT_EQ(a->name, Symbol(1, ID()));
+  EXPECT_EQ(a->type, u32);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc
new file mode 100644
index 0000000..99b8d56
--- /dev/null
+++ b/src/tint/ast/array.cc
@@ -0,0 +1,78 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/array.h"
+
+#include <cmath>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Array);
+
+namespace tint {
+namespace ast {
+
+namespace {
+// Returns the string representation of an array size expression.
+std::string SizeExprToString(const Expression* size,
+                             const SymbolTable& symbols) {
+  if (auto* ident = size->As<IdentifierExpression>()) {
+    return symbols.NameFor(ident->symbol);
+  }
+  if (auto* literal = size->As<IntLiteralExpression>()) {
+    return std::to_string(literal->ValueAsU32());
+  }
+  // This will never be exposed to the user as the Resolver will reject this
+  // expression for array size.
+  return "<invalid>";
+}
+}  // namespace
+
+Array::Array(ProgramID pid,
+             const Source& src,
+             const Type* subtype,
+             const Expression* cnt,
+             AttributeList attrs)
+    : Base(pid, src), type(subtype), count(cnt), attributes(attrs) {}
+
+Array::Array(Array&&) = default;
+
+Array::~Array() = default;
+
+std::string Array::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  for (auto* attr : attributes) {
+    if (auto* stride = attr->As<ast::StrideAttribute>()) {
+      out << "@stride(" << stride->stride << ") ";
+    }
+  }
+  out << "array<" << type->FriendlyName(symbols);
+  if (!IsRuntimeArray()) {
+    out << ", " << SizeExprToString(count, symbols);
+  }
+  out << ">";
+  return out.str();
+}
+
+const Array* Array::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  auto* cnt = ctx->Clone(count);
+  auto attrs = ctx->Clone(attributes);
+  return ctx->dst->create<Array>(src, ty, cnt, attrs);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/array.h b/src/tint/ast/array.h
new file mode 100644
index 0000000..b4429f3
--- /dev/null
+++ b/src/tint/ast/array.h
@@ -0,0 +1,75 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ARRAY_H_
+#define SRC_TINT_AST_ARRAY_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declarations.
+class Expression;
+
+/// An array type. If size is zero then it is a runtime array.
+class Array : public Castable<Array, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param subtype the type of the array elements
+  /// @param count the number of elements in the array. nullptr represents a
+  /// runtime-sized array.
+  /// @param attributes the array attributes
+  Array(ProgramID pid,
+        const Source& src,
+        const Type* subtype,
+        const Expression* count,
+        AttributeList attributes);
+  /// Move constructor
+  Array(Array&&);
+  ~Array() override;
+
+  /// @returns true if this is a runtime array.
+  /// i.e. the size is determined at runtime
+  bool IsRuntimeArray() const { return count == nullptr; }
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Array* Clone(CloneContext* ctx) const override;
+
+  /// the array element type
+  const Type* const type;
+
+  /// the array size in elements, or nullptr for a runtime array
+  const Expression* const count;
+
+  /// the array attributes
+  const AttributeList attributes;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ARRAY_H_
diff --git a/src/tint/ast/array_test.cc b/src/tint/ast/array_test.cc
new file mode 100644
index 0000000..ff97734
--- /dev/null
+++ b/src/tint/ast/array_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/array.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstArrayTest = TestHelper;
+
+TEST_F(AstArrayTest, CreateSizedArray) {
+  auto* u32 = create<U32>();
+  auto* count = Expr(3);
+  auto* arr = create<Array>(u32, count, AttributeList{});
+  EXPECT_EQ(arr->type, u32);
+  EXPECT_EQ(arr->count, count);
+  EXPECT_TRUE(arr->Is<Array>());
+  EXPECT_FALSE(arr->IsRuntimeArray());
+}
+
+TEST_F(AstArrayTest, CreateRuntimeArray) {
+  auto* u32 = create<U32>();
+  auto* arr = create<Array>(u32, nullptr, AttributeList{});
+  EXPECT_EQ(arr->type, u32);
+  EXPECT_EQ(arr->count, nullptr);
+  EXPECT_TRUE(arr->Is<Array>());
+  EXPECT_TRUE(arr->IsRuntimeArray());
+}
+
+TEST_F(AstArrayTest, FriendlyName_RuntimeSized) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, nullptr, AttributeList{});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
+}
+
+TEST_F(AstArrayTest, FriendlyName_LiteralSized) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, Expr(5), AttributeList{});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
+}
+
+TEST_F(AstArrayTest, FriendlyName_ConstantSized) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, Expr("size"), AttributeList{});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, size>");
+}
+
+TEST_F(AstArrayTest, FriendlyName_WithStride) {
+  auto* i32 = create<I32>();
+  auto* arr =
+      create<Array>(i32, Expr(5), AttributeList{create<StrideAttribute>(32)});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/assignment_statement.cc b/src/tint/ast/assignment_statement.cc
new file mode 100644
index 0000000..a2340b7
--- /dev/null
+++ b/src/tint/ast/assignment_statement.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/assignment_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::AssignmentStatement);
+
+namespace tint {
+namespace ast {
+
+AssignmentStatement::AssignmentStatement(ProgramID pid,
+                                         const Source& src,
+                                         const Expression* l,
+                                         const Expression* r)
+    : Base(pid, src), lhs(l), rhs(r) {
+  TINT_ASSERT(AST, lhs);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
+  TINT_ASSERT(AST, rhs);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
+}
+
+AssignmentStatement::AssignmentStatement(AssignmentStatement&&) = default;
+
+AssignmentStatement::~AssignmentStatement() = default;
+
+const AssignmentStatement* AssignmentStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* l = ctx->Clone(lhs);
+  auto* r = ctx->Clone(rhs);
+  return ctx->dst->create<AssignmentStatement>(src, l, r);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/assignment_statement.h b/src/tint/ast/assignment_statement.h
new file mode 100644
index 0000000..0b9c06b
--- /dev/null
+++ b/src/tint/ast/assignment_statement.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_
+#define SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// An assignment statement
+class AssignmentStatement : public Castable<AssignmentStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the assignment statement source
+  /// @param lhs the left side of the expression
+  /// @param rhs the right side of the expression
+  AssignmentStatement(ProgramID program_id,
+                      const Source& source,
+                      const Expression* lhs,
+                      const Expression* rhs);
+  /// Move constructor
+  AssignmentStatement(AssignmentStatement&&);
+  ~AssignmentStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const AssignmentStatement* Clone(CloneContext* ctx) const override;
+
+  /// left side expression
+  const Expression* const lhs;
+
+  /// right side expression
+  const Expression* const rhs;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_
diff --git a/src/tint/ast/assignment_statement_test.cc b/src/tint/ast/assignment_statement_test.cc
new file mode 100644
index 0000000..4a41a7c
--- /dev/null
+++ b/src/tint/ast/assignment_statement_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/assignment_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AssignmentStatementTest = TestHelper;
+
+TEST_F(AssignmentStatementTest, Creation) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* stmt = create<AssignmentStatement>(lhs, rhs);
+  EXPECT_EQ(stmt->lhs, lhs);
+  EXPECT_EQ(stmt->rhs, rhs);
+}
+
+TEST_F(AssignmentStatementTest, CreationWithSource) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* stmt =
+      create<AssignmentStatement>(Source{Source::Location{20, 2}}, lhs, rhs);
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(AssignmentStatementTest, IsAssign) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* stmt = create<AssignmentStatement>(lhs, rhs);
+  EXPECT_TRUE(stmt->Is<AssignmentStatement>());
+}
+
+TEST_F(AssignmentStatementTest, Assert_Null_LHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<AssignmentStatement>(nullptr, b.Expr(1));
+      },
+      "internal compiler error");
+}
+
+TEST_F(AssignmentStatementTest, Assert_Null_RHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<AssignmentStatement>(b.Expr(1), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_LHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<AssignmentStatement>(b2.Expr("lhs"), b1.Expr("rhs"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_RHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<AssignmentStatement>(b1.Expr("lhs"), b2.Expr("rhs"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/ast_type.cc b/src/tint/ast/ast_type.cc
new file mode 100644
index 0000000..cb01679
--- /dev/null
+++ b/src/tint/ast/ast_type.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/type.h"
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/symbol_table.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Type);
+
+namespace tint {
+namespace ast {
+
+Type::Type(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Type::Type(Type&&) = default;
+
+Type::~Type() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/atomic.cc b/src/tint/ast/atomic.cc
new file mode 100644
index 0000000..addefee
--- /dev/null
+++ b/src/tint/ast/atomic.cc
@@ -0,0 +1,45 @@
+// 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/ast/atomic.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Atomic);
+
+namespace tint {
+namespace ast {
+
+Atomic::Atomic(ProgramID pid, const Source& src, const Type* const subtype)
+    : Base(pid, src), type(subtype) {}
+
+std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "atomic<" << type->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+Atomic::Atomic(Atomic&&) = default;
+
+Atomic::~Atomic() = default;
+
+const Atomic* Atomic::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<Atomic>(src, ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/atomic.h b/src/tint/ast/atomic.h
new file mode 100644
index 0000000..cdf3374
--- /dev/null
+++ b/src/tint/ast/atomic.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ATOMIC_H_
+#define SRC_TINT_AST_ATOMIC_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// An atomic type.
+class Atomic : public Castable<Atomic, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param subtype the pointee type
+  Atomic(ProgramID pid, const Source& src, const Type* const subtype);
+  /// Move constructor
+  Atomic(Atomic&&);
+  ~Atomic() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Atomic* Clone(CloneContext* ctx) const override;
+
+  /// the pointee type
+  const Type* const type;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ATOMIC_H_
diff --git a/src/tint/ast/atomic_test.cc b/src/tint/ast/atomic_test.cc
new file mode 100644
index 0000000..636654b
--- /dev/null
+++ b/src/tint/ast/atomic_test.cc
@@ -0,0 +1,40 @@
+// 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/ast/atomic.h"
+
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstAtomicTest = TestHelper;
+
+TEST_F(AstAtomicTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* p = create<Atomic>(i32);
+  EXPECT_EQ(p->type, i32);
+}
+
+TEST_F(AstAtomicTest, FriendlyName) {
+  auto* i32 = create<I32>();
+  auto* p = create<Atomic>(i32);
+  EXPECT_EQ(p->FriendlyName(Symbols()), "atomic<i32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/attribute.cc b/src/tint/ast/attribute.cc
new file mode 100644
index 0000000..87ad3f1
--- /dev/null
+++ b/src/tint/ast/attribute.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/attribute.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Attribute);
+
+namespace tint {
+namespace ast {
+
+Attribute::~Attribute() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/attribute.h b/src/tint/ast/attribute.h
new file mode 100644
index 0000000..d336727
--- /dev/null
+++ b/src/tint/ast/attribute.h
@@ -0,0 +1,71 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ATTRIBUTE_H_
+#define SRC_TINT_AST_ATTRIBUTE_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/node.h"
+
+namespace tint {
+namespace ast {
+
+/// The base class for all attributes
+class Attribute : public Castable<Attribute, Node> {
+ public:
+  ~Attribute() override;
+
+  /// @returns the WGSL name for the attribute
+  virtual std::string Name() const = 0;
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Attribute(ProgramID pid, const Source& src) : Base(pid, src) {}
+};
+
+/// A list of attributes
+using AttributeList = std::vector<const Attribute*>;
+
+/// @param attributes the list of attributes to search
+/// @returns true if `attributes` includes a attribute of type `T`
+template <typename T>
+bool HasAttribute(const AttributeList& attributes) {
+  for (auto* attr : attributes) {
+    if (attr->Is<T>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/// @param attributes the list of attributes to search
+/// @returns a pointer to `T` from `attributes` if found, otherwise nullptr.
+template <typename T>
+const T* GetAttribute(const AttributeList& attributes) {
+  for (auto* attr : attributes) {
+    if (attr->Is<T>()) {
+      return attr->As<T>();
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ATTRIBUTE_H_
diff --git a/src/tint/ast/binary_expression.cc b/src/tint/ast/binary_expression.cc
new file mode 100644
index 0000000..6b2be73
--- /dev/null
+++ b/src/tint/ast/binary_expression.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/binary_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BinaryExpression);
+
+namespace tint {
+namespace ast {
+
+BinaryExpression::BinaryExpression(ProgramID pid,
+                                   const Source& src,
+                                   BinaryOp o,
+                                   const Expression* l,
+                                   const Expression* r)
+    : Base(pid, src), op(o), lhs(l), rhs(r) {
+  TINT_ASSERT(AST, lhs);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
+  TINT_ASSERT(AST, rhs);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
+  TINT_ASSERT(AST, op != BinaryOp::kNone);
+}
+
+BinaryExpression::BinaryExpression(BinaryExpression&&) = default;
+
+BinaryExpression::~BinaryExpression() = default;
+
+const BinaryExpression* BinaryExpression::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* l = ctx->Clone(lhs);
+  auto* r = ctx->Clone(rhs);
+  return ctx->dst->create<BinaryExpression>(src, op, l, r);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/binary_expression.h b/src/tint/ast/binary_expression.h
new file mode 100644
index 0000000..73b2909
--- /dev/null
+++ b/src/tint/ast/binary_expression.h
@@ -0,0 +1,228 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BINARY_EXPRESSION_H_
+#define SRC_TINT_AST_BINARY_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// The operator type
+enum class BinaryOp {
+  kNone = 0,
+  kAnd,  // &
+  kOr,   // |
+  kXor,
+  kLogicalAnd,  // &&
+  kLogicalOr,   // ||
+  kEqual,
+  kNotEqual,
+  kLessThan,
+  kGreaterThan,
+  kLessThanEqual,
+  kGreaterThanEqual,
+  kShiftLeft,
+  kShiftRight,
+  kAdd,
+  kSubtract,
+  kMultiply,
+  kDivide,
+  kModulo,
+};
+
+/// An binary expression
+class BinaryExpression : public Castable<BinaryExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the binary expression source
+  /// @param op the operation type
+  /// @param lhs the left side of the expression
+  /// @param rhs the right side of the expression
+  BinaryExpression(ProgramID program_id,
+                   const Source& source,
+                   BinaryOp op,
+                   const Expression* lhs,
+                   const Expression* rhs);
+  /// Move constructor
+  BinaryExpression(BinaryExpression&&);
+  ~BinaryExpression() override;
+
+  /// @returns true if the op is and
+  bool IsAnd() const { return op == BinaryOp::kAnd; }
+  /// @returns true if the op is or
+  bool IsOr() const { return op == BinaryOp::kOr; }
+  /// @returns true if the op is xor
+  bool IsXor() const { return op == BinaryOp::kXor; }
+  /// @returns true if the op is logical and
+  bool IsLogicalAnd() const { return op == BinaryOp::kLogicalAnd; }
+  /// @returns true if the op is logical or
+  bool IsLogicalOr() const { return op == BinaryOp::kLogicalOr; }
+  /// @returns true if the op is equal
+  bool IsEqual() const { return op == BinaryOp::kEqual; }
+  /// @returns true if the op is not equal
+  bool IsNotEqual() const { return op == BinaryOp::kNotEqual; }
+  /// @returns true if the op is less than
+  bool IsLessThan() const { return op == BinaryOp::kLessThan; }
+  /// @returns true if the op is greater than
+  bool IsGreaterThan() const { return op == BinaryOp::kGreaterThan; }
+  /// @returns true if the op is less than equal
+  bool IsLessThanEqual() const { return op == BinaryOp::kLessThanEqual; }
+  /// @returns true if the op is greater than equal
+  bool IsGreaterThanEqual() const { return op == BinaryOp::kGreaterThanEqual; }
+  /// @returns true if the op is shift left
+  bool IsShiftLeft() const { return op == BinaryOp::kShiftLeft; }
+  /// @returns true if the op is shift right
+  bool IsShiftRight() const { return op == BinaryOp::kShiftRight; }
+  /// @returns true if the op is add
+  bool IsAdd() const { return op == BinaryOp::kAdd; }
+  /// @returns true if the op is subtract
+  bool IsSubtract() const { return op == BinaryOp::kSubtract; }
+  /// @returns true if the op is multiply
+  bool IsMultiply() const { return op == BinaryOp::kMultiply; }
+  /// @returns true if the op is divide
+  bool IsDivide() const { return op == BinaryOp::kDivide; }
+  /// @returns true if the op is modulo
+  bool IsModulo() const { return op == BinaryOp::kModulo; }
+  /// @returns true if the op is an arithmetic operation
+  bool IsArithmetic() const;
+  /// @returns true if the op is a comparison operation
+  bool IsComparison() const;
+  /// @returns true if the op is a bitwise operation
+  bool IsBitwise() const;
+  /// @returns true if the op is a bit shift operation
+  bool IsBitshift() const;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BinaryExpression* Clone(CloneContext* ctx) const override;
+
+  /// the binary op type
+  const BinaryOp op;
+  /// the left side expression
+  const Expression* const lhs;
+  /// the right side expression
+  const Expression* const rhs;
+};
+
+inline bool BinaryExpression::IsArithmetic() const {
+  switch (op) {
+    case ast::BinaryOp::kAdd:
+    case ast::BinaryOp::kSubtract:
+    case ast::BinaryOp::kMultiply:
+    case ast::BinaryOp::kDivide:
+    case ast::BinaryOp::kModulo:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline bool BinaryExpression::IsComparison() const {
+  switch (op) {
+    case ast::BinaryOp::kEqual:
+    case ast::BinaryOp::kNotEqual:
+    case ast::BinaryOp::kLessThan:
+    case ast::BinaryOp::kLessThanEqual:
+    case ast::BinaryOp::kGreaterThan:
+    case ast::BinaryOp::kGreaterThanEqual:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline bool BinaryExpression::IsBitwise() const {
+  switch (op) {
+    case ast::BinaryOp::kAnd:
+    case ast::BinaryOp::kOr:
+    case ast::BinaryOp::kXor:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline bool BinaryExpression::IsBitshift() const {
+  switch (op) {
+    case ast::BinaryOp::kShiftLeft:
+    case ast::BinaryOp::kShiftRight:
+      return true;
+    default:
+      return false;
+  }
+}
+
+/// @returns the human readable name of the given BinaryOp
+/// @param op the BinaryOp
+constexpr const char* FriendlyName(BinaryOp op) {
+  switch (op) {
+    case BinaryOp::kNone:
+      return "none";
+    case BinaryOp::kAnd:
+      return "and";
+    case BinaryOp::kOr:
+      return "or";
+    case BinaryOp::kXor:
+      return "xor";
+    case BinaryOp::kLogicalAnd:
+      return "logical_and";
+    case BinaryOp::kLogicalOr:
+      return "logical_or";
+    case BinaryOp::kEqual:
+      return "equal";
+    case BinaryOp::kNotEqual:
+      return "not_equal";
+    case BinaryOp::kLessThan:
+      return "less_than";
+    case BinaryOp::kGreaterThan:
+      return "greater_than";
+    case BinaryOp::kLessThanEqual:
+      return "less_than_equal";
+    case BinaryOp::kGreaterThanEqual:
+      return "greater_than_equal";
+    case BinaryOp::kShiftLeft:
+      return "shift_left";
+    case BinaryOp::kShiftRight:
+      return "shift_right";
+    case BinaryOp::kAdd:
+      return "add";
+    case BinaryOp::kSubtract:
+      return "subtract";
+    case BinaryOp::kMultiply:
+      return "multiply";
+    case BinaryOp::kDivide:
+      return "divide";
+    case BinaryOp::kModulo:
+      return "modulo";
+  }
+  return "INVALID";
+}
+
+/// @param out the std::ostream to write to
+/// @param op the BinaryOp
+/// @return the std::ostream so calls can be chained
+inline std::ostream& operator<<(std::ostream& out, BinaryOp op) {
+  out << FriendlyName(op);
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BINARY_EXPRESSION_H_
diff --git a/src/tint/ast/binary_expression_test.cc b/src/tint/ast/binary_expression_test.cc
new file mode 100644
index 0000000..20b8f8f
--- /dev/null
+++ b/src/tint/ast/binary_expression_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BinaryExpressionTest = TestHelper;
+
+TEST_F(BinaryExpressionTest, Creation) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs);
+  EXPECT_EQ(r->lhs, lhs);
+  EXPECT_EQ(r->rhs, rhs);
+  EXPECT_EQ(r->op, BinaryOp::kEqual);
+}
+
+TEST_F(BinaryExpressionTest, Creation_WithSource) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* r = create<BinaryExpression>(Source{Source::Location{20, 2}},
+                                     BinaryOp::kEqual, lhs, rhs);
+  auto src = r->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(BinaryExpressionTest, IsBinary) {
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs);
+  EXPECT_TRUE(r->Is<BinaryExpression>());
+}
+
+TEST_F(BinaryExpressionTest, Assert_Null_LHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<BinaryExpression>(BinaryOp::kEqual, nullptr, b.Expr("rhs"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(BinaryExpressionTest, Assert_Null_RHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<BinaryExpression>(BinaryOp::kEqual, b.Expr("lhs"), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_LHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<BinaryExpression>(BinaryOp::kEqual, b2.Expr("lhs"),
+                                    b1.Expr("rhs"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_RHS) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<BinaryExpression>(BinaryOp::kEqual, b1.Expr("lhs"),
+                                    b2.Expr("rhs"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/binding_attribute.cc b/src/tint/ast/binding_attribute.cc
new file mode 100644
index 0000000..bc1a74a
--- /dev/null
+++ b/src/tint/ast/binding_attribute.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/binding_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BindingAttribute);
+
+namespace tint {
+namespace ast {
+
+BindingAttribute::BindingAttribute(ProgramID pid,
+                                   const Source& src,
+                                   uint32_t val)
+    : Base(pid, src), value(val) {}
+
+BindingAttribute::~BindingAttribute() = default;
+
+std::string BindingAttribute::Name() const {
+  return "binding";
+}
+
+const BindingAttribute* BindingAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<BindingAttribute>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/binding_attribute.h b/src/tint/ast/binding_attribute.h
new file mode 100644
index 0000000..5e669ec
--- /dev/null
+++ b/src/tint/ast/binding_attribute.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BINDING_ATTRIBUTE_H_
+#define SRC_TINT_AST_BINDING_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A binding attribute
+class BindingAttribute : public Castable<BindingAttribute, Attribute> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the binding value
+  BindingAttribute(ProgramID pid, const Source& src, uint32_t value);
+  ~BindingAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BindingAttribute* Clone(CloneContext* ctx) const override;
+
+  /// the binding value
+  const uint32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BINDING_ATTRIBUTE_H_
diff --git a/src/tint/ast/binding_attribute_test.cc b/src/tint/ast/binding_attribute_test.cc
new file mode 100644
index 0000000..c4c7e39
--- /dev/null
+++ b/src/tint/ast/binding_attribute_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BindingAttributeTest = TestHelper;
+
+TEST_F(BindingAttributeTest, Creation) {
+  auto* d = create<BindingAttribute>(2);
+  EXPECT_EQ(2u, d->value);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bitcast_expression.cc b/src/tint/ast/bitcast_expression.cc
new file mode 100644
index 0000000..8de3afc
--- /dev/null
+++ b/src/tint/ast/bitcast_expression.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bitcast_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BitcastExpression);
+
+namespace tint {
+namespace ast {
+
+BitcastExpression::BitcastExpression(ProgramID pid,
+                                     const Source& src,
+                                     const Type* t,
+                                     const Expression* e)
+    : Base(pid, src), type(t), expr(e) {
+  TINT_ASSERT(AST, type);
+  TINT_ASSERT(AST, expr);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
+}
+
+BitcastExpression::BitcastExpression(BitcastExpression&&) = default;
+BitcastExpression::~BitcastExpression() = default;
+
+const BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* t = ctx->Clone(type);
+  auto* e = ctx->Clone(expr);
+  return ctx->dst->create<BitcastExpression>(src, t, e);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bitcast_expression.h b/src/tint/ast/bitcast_expression.h
new file mode 100644
index 0000000..db98d87
--- /dev/null
+++ b/src/tint/ast/bitcast_expression.h
@@ -0,0 +1,57 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BITCAST_EXPRESSION_H_
+#define SRC_TINT_AST_BITCAST_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declaration
+class Type;
+
+/// A bitcast expression
+class BitcastExpression : public Castable<BitcastExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the bitcast expression source
+  /// @param type the type
+  /// @param expr the expr
+  BitcastExpression(ProgramID program_id,
+                    const Source& source,
+                    const Type* type,
+                    const Expression* expr);
+  /// Move constructor
+  BitcastExpression(BitcastExpression&&);
+  ~BitcastExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BitcastExpression* Clone(CloneContext* ctx) const override;
+
+  /// the target cast type
+  const Type* const type;
+  /// the expression
+  const Expression* const expr;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BITCAST_EXPRESSION_H_
diff --git a/src/tint/ast/bitcast_expression_test.cc b/src/tint/ast/bitcast_expression_test.cc
new file mode 100644
index 0000000..5803003
--- /dev/null
+++ b/src/tint/ast/bitcast_expression_test.cc
@@ -0,0 +1,81 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bitcast_expression.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BitcastExpressionTest = TestHelper;
+
+TEST_F(BitcastExpressionTest, Create) {
+  auto* expr = Expr("expr");
+
+  auto* exp = create<BitcastExpression>(ty.f32(), expr);
+  EXPECT_TRUE(exp->type->Is<ast::F32>());
+  ASSERT_EQ(exp->expr, expr);
+}
+
+TEST_F(BitcastExpressionTest, CreateWithSource) {
+  auto* expr = Expr("expr");
+
+  auto* exp = create<BitcastExpression>(Source{Source::Location{20, 2}},
+                                        ty.f32(), expr);
+  auto src = exp->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(BitcastExpressionTest, IsBitcast) {
+  auto* expr = Expr("expr");
+
+  auto* exp = create<BitcastExpression>(ty.f32(), expr);
+  EXPECT_TRUE(exp->Is<BitcastExpression>());
+}
+
+TEST_F(BitcastExpressionTest, Assert_Null_Type) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<BitcastExpression>(nullptr, b.Expr("idx"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(BitcastExpressionTest, Assert_Null_Expr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<BitcastExpression>(b.ty.f32(), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(BitcastExpressionTest, Assert_DifferentProgramID_Expr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<BitcastExpression>(b1.ty.f32(), b2.Expr("idx"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/block_statement.cc b/src/tint/ast/block_statement.cc
new file mode 100644
index 0000000..5a7dcba
--- /dev/null
+++ b/src/tint/ast/block_statement.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/block_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BlockStatement);
+
+namespace tint {
+namespace ast {
+
+BlockStatement::BlockStatement(ProgramID pid,
+                               const Source& src,
+                               const StatementList& stmts)
+    : Base(pid, src), statements(std::move(stmts)) {
+  for (auto* stmt : statements) {
+    TINT_ASSERT(AST, stmt);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id);
+  }
+}
+
+BlockStatement::BlockStatement(BlockStatement&&) = default;
+
+BlockStatement::~BlockStatement() = default;
+
+const BlockStatement* BlockStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto stmts = ctx->Clone(statements);
+  return ctx->dst->create<BlockStatement>(src, stmts);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/block_statement.h b/src/tint/ast/block_statement.h
new file mode 100644
index 0000000..fe0eeb5
--- /dev/null
+++ b/src/tint/ast/block_statement.h
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BLOCK_STATEMENT_H_
+#define SRC_TINT_AST_BLOCK_STATEMENT_H_
+
+#include <utility>
+
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// A block statement
+class BlockStatement : public Castable<BlockStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the block statement source
+  /// @param statements the statements
+  BlockStatement(ProgramID program_id,
+                 const Source& source,
+                 const StatementList& statements);
+  /// Move constructor
+  BlockStatement(BlockStatement&&);
+  ~BlockStatement() override;
+
+  /// @returns true if the block has no statements
+  bool Empty() const { return statements.empty(); }
+
+  /// @returns the last statement in the block or nullptr if block empty
+  const Statement* Last() const {
+    return statements.empty() ? nullptr : statements.back();
+  }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BlockStatement* Clone(CloneContext* ctx) const override;
+
+  /// the statement list
+  const StatementList statements;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BLOCK_STATEMENT_H_
diff --git a/src/tint/ast/block_statement_test.cc b/src/tint/ast/block_statement_test.cc
new file mode 100644
index 0000000..1cc8f38
--- /dev/null
+++ b/src/tint/ast/block_statement_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BlockStatementTest = TestHelper;
+
+TEST_F(BlockStatementTest, Creation) {
+  auto* d = create<DiscardStatement>();
+  auto* ptr = d;
+
+  auto* b = create<BlockStatement>(StatementList{d});
+
+  ASSERT_EQ(b->statements.size(), 1u);
+  EXPECT_EQ(b->statements[0], ptr);
+}
+
+TEST_F(BlockStatementTest, Creation_WithSource) {
+  auto* b = create<BlockStatement>(Source{Source::Location{20, 2}},
+                                   ast::StatementList{});
+  auto src = b->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(BlockStatementTest, IsBlock) {
+  auto* b = create<BlockStatement>(ast::StatementList{});
+  EXPECT_TRUE(b->Is<BlockStatement>());
+}
+
+TEST_F(BlockStatementTest, Assert_Null_Statement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<BlockStatement>(ast::StatementList{nullptr});
+      },
+      "internal compiler error");
+}
+
+TEST_F(BlockStatementTest, Assert_DifferentProgramID_Statement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<BlockStatement>(
+            ast::StatementList{b2.create<DiscardStatement>()});
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bool.cc b/src/tint/ast/bool.cc
new file mode 100644
index 0000000..79596a3
--- /dev/null
+++ b/src/tint/ast/bool.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bool.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Bool);
+
+namespace tint {
+namespace ast {
+
+Bool::Bool(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Bool::Bool(Bool&&) = default;
+
+Bool::~Bool() = default;
+
+std::string Bool::FriendlyName(const SymbolTable&) const {
+  return "bool";
+}
+
+const Bool* Bool::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<Bool>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bool.h b/src/tint/ast/bool.h
new file mode 100644
index 0000000..77ec064
--- /dev/null
+++ b/src/tint/ast/bool.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BOOL_H_
+#define SRC_TINT_AST_BOOL_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+// X11 likes to #define Bool leading to confusing error messages.
+// If its defined, undefine it.
+#ifdef Bool
+#undef Bool
+#endif
+
+namespace tint {
+namespace ast {
+
+/// A boolean type
+class Bool : public Castable<Bool, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Bool(ProgramID pid, const Source& src);
+  /// Move constructor
+  Bool(Bool&&);
+  ~Bool() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Bool* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BOOL_H_
diff --git a/src/tint/ast/bool_literal_expression.cc b/src/tint/ast/bool_literal_expression.cc
new file mode 100644
index 0000000..5c961b4
--- /dev/null
+++ b/src/tint/ast/bool_literal_expression.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bool_literal_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BoolLiteralExpression);
+
+namespace tint {
+namespace ast {
+
+BoolLiteralExpression::BoolLiteralExpression(ProgramID pid,
+                                             const Source& src,
+                                             bool val)
+    : Base(pid, src), value(val) {}
+
+BoolLiteralExpression::~BoolLiteralExpression() = default;
+
+const BoolLiteralExpression* BoolLiteralExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<BoolLiteralExpression>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bool_literal_expression.h b/src/tint/ast/bool_literal_expression.h
new file mode 100644
index 0000000..819c892
--- /dev/null
+++ b/src/tint/ast/bool_literal_expression.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_
+
+#include <string>
+
+#include "src/tint/ast/literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A boolean literal
+class BoolLiteralExpression
+    : public Castable<BoolLiteralExpression, LiteralExpression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the bool literals value
+  BoolLiteralExpression(ProgramID pid, const Source& src, bool value);
+  ~BoolLiteralExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BoolLiteralExpression* Clone(CloneContext* ctx) const override;
+
+  /// The boolean literal value
+  const bool value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/bool_literal_expression_test.cc b/src/tint/ast/bool_literal_expression_test.cc
new file mode 100644
index 0000000..78cd632
--- /dev/null
+++ b/src/tint/ast/bool_literal_expression_test.cc
@@ -0,0 +1,37 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BoolLiteralExpressionTest = TestHelper;
+
+TEST_F(BoolLiteralExpressionTest, True) {
+  auto* b = create<BoolLiteralExpression>(true);
+  ASSERT_TRUE(b->Is<BoolLiteralExpression>());
+  ASSERT_TRUE(b->value);
+}
+
+TEST_F(BoolLiteralExpressionTest, False) {
+  auto* b = create<BoolLiteralExpression>(false);
+  ASSERT_TRUE(b->Is<BoolLiteralExpression>());
+  ASSERT_FALSE(b->value);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/bool_test.cc b/src/tint/ast/bool_test.cc
new file mode 100644
index 0000000..3defbd4
--- /dev/null
+++ b/src/tint/ast/bool_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bool.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstBoolTest = TestHelper;
+
+TEST_F(AstBoolTest, FriendlyName) {
+  auto* b = create<Bool>();
+  EXPECT_EQ(b->FriendlyName(Symbols()), "bool");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/break_statement.cc b/src/tint/ast/break_statement.cc
new file mode 100644
index 0000000..0c78c73
--- /dev/null
+++ b/src/tint/ast/break_statement.cc
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BreakStatement);
+
+namespace tint {
+namespace ast {
+
+BreakStatement::BreakStatement(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+BreakStatement::BreakStatement(BreakStatement&&) = default;
+
+BreakStatement::~BreakStatement() = default;
+
+const BreakStatement* BreakStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<BreakStatement>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/break_statement.h b/src/tint/ast/break_statement.h
new file mode 100644
index 0000000..c329f18
--- /dev/null
+++ b/src/tint/ast/break_statement.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BREAK_STATEMENT_H_
+#define SRC_TINT_AST_BREAK_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// An break statement
+class BreakStatement : public Castable<BreakStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  BreakStatement(ProgramID pid, const Source& src);
+  /// Move constructor
+  BreakStatement(BreakStatement&&);
+  ~BreakStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BreakStatement* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BREAK_STATEMENT_H_
diff --git a/src/tint/ast/break_statement_test.cc b/src/tint/ast/break_statement_test.cc
new file mode 100644
index 0000000..ce419ae
--- /dev/null
+++ b/src/tint/ast/break_statement_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_statement.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BreakStatementTest = TestHelper;
+
+TEST_F(BreakStatementTest, Creation_WithSource) {
+  auto* stmt = create<BreakStatement>(Source{Source::Location{20, 2}});
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(BreakStatementTest, IsBreak) {
+  auto* stmt = create<BreakStatement>();
+  EXPECT_TRUE(stmt->Is<BreakStatement>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/builtin.cc b/src/tint/ast/builtin.cc
new file mode 100644
index 0000000..48744e0
--- /dev/null
+++ b/src/tint/ast/builtin.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/builtin.h"
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, Builtin builtin) {
+  switch (builtin) {
+    case Builtin::kNone: {
+      out << "none";
+      break;
+    }
+    case Builtin::kPosition: {
+      out << "position";
+      break;
+    }
+    case Builtin::kVertexIndex: {
+      out << "vertex_index";
+      break;
+    }
+    case Builtin::kInstanceIndex: {
+      out << "instance_index";
+      break;
+    }
+    case Builtin::kFrontFacing: {
+      out << "front_facing";
+      break;
+    }
+    case Builtin::kFragDepth: {
+      out << "frag_depth";
+      break;
+    }
+    case Builtin::kLocalInvocationId: {
+      out << "local_invocation_id";
+      break;
+    }
+    case Builtin::kLocalInvocationIndex: {
+      out << "local_invocation_index";
+      break;
+    }
+    case Builtin::kGlobalInvocationId: {
+      out << "global_invocation_id";
+      break;
+    }
+    case Builtin::kWorkgroupId: {
+      out << "workgroup_id";
+      break;
+    }
+    case Builtin::kNumWorkgroups: {
+      out << "num_workgroups";
+      break;
+    }
+    case Builtin::kSampleIndex: {
+      out << "sample_index";
+      break;
+    }
+    case Builtin::kSampleMask: {
+      out << "sample_mask";
+      break;
+    }
+    case Builtin::kPointSize: {
+      out << "pointsize";
+    }
+  }
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/builtin.h b/src/tint/ast/builtin.h
new file mode 100644
index 0000000..913eba2
--- /dev/null
+++ b/src/tint/ast/builtin.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BUILTIN_H_
+#define SRC_TINT_AST_BUILTIN_H_
+
+#include <ostream>
+
+namespace tint {
+namespace ast {
+
+/// The builtin identifiers
+enum class Builtin {
+  kNone = -1,
+  kPosition,
+  kVertexIndex,
+  kInstanceIndex,
+  kFrontFacing,
+  kFragDepth,
+  kLocalInvocationId,
+  kLocalInvocationIndex,
+  kGlobalInvocationId,
+  kWorkgroupId,
+  kNumWorkgroups,
+  kSampleIndex,
+  kSampleMask,
+
+  // Below are not currently WGSL builtins, but are included in this enum as
+  // they are used by certain backends.
+  kPointSize,
+};
+
+/// @param out the std::ostream to write to
+/// @param builtin the Builtin
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, Builtin builtin);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BUILTIN_H_
diff --git a/src/tint/ast/builtin_attribute.cc b/src/tint/ast/builtin_attribute.cc
new file mode 100644
index 0000000..0591b91
--- /dev/null
+++ b/src/tint/ast/builtin_attribute.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/builtin_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BuiltinAttribute);
+
+namespace tint {
+namespace ast {
+
+BuiltinAttribute::BuiltinAttribute(ProgramID pid, const Source& src, Builtin b)
+    : Base(pid, src), builtin(b) {}
+
+BuiltinAttribute::~BuiltinAttribute() = default;
+
+std::string BuiltinAttribute::Name() const {
+  return "builtin";
+}
+
+const BuiltinAttribute* BuiltinAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<BuiltinAttribute>(src, builtin);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/builtin_attribute.h b/src/tint/ast/builtin_attribute.h
new file mode 100644
index 0000000..e4f3f36
--- /dev/null
+++ b/src/tint/ast/builtin_attribute.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_
+#define SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/builtin.h"
+
+namespace tint {
+namespace ast {
+
+/// A builtin attribute
+class BuiltinAttribute : public Castable<BuiltinAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param builtin the builtin value
+  BuiltinAttribute(ProgramID pid, const Source& src, Builtin builtin);
+  ~BuiltinAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const BuiltinAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The builtin value
+  const Builtin builtin;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_
diff --git a/src/tint/ast/builtin_attribute_test.cc b/src/tint/ast/builtin_attribute_test.cc
new file mode 100644
index 0000000..e5a91ea
--- /dev/null
+++ b/src/tint/ast/builtin_attribute_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied->
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using BuiltinAttributeTest = TestHelper;
+
+TEST_F(BuiltinAttributeTest, Creation) {
+  auto* d = create<BuiltinAttribute>(Builtin::kFragDepth);
+  EXPECT_EQ(Builtin::kFragDepth, d->builtin);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc
new file mode 100644
index 0000000..2f80558
--- /dev/null
+++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -0,0 +1,2286 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/builtin_texture_helper_test.h"
+
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+
+namespace tint {
+namespace ast {
+namespace builtin {
+namespace test {
+
+using u32 = ProgramBuilder::u32;
+using i32 = ProgramBuilder::i32;
+using f32 = ProgramBuilder::f32;
+
+TextureOverloadCase::TextureOverloadCase(
+    ValidTextureOverload o,
+    const char* desc,
+    TextureKind tk,
+    ast::SamplerKind sk,
+    ast::TextureDimension dims,
+    TextureDataType datatype,
+    const char* f,
+    std::function<ExpressionList(ProgramBuilder*)> a)
+    : overload(o),
+      description(desc),
+      texture_kind(tk),
+      sampler_kind(sk),
+      texture_dimension(dims),
+      texture_data_type(datatype),
+      function(f),
+      args(std::move(a)) {}
+TextureOverloadCase::TextureOverloadCase(
+    ValidTextureOverload o,
+    const char* desc,
+    TextureKind tk,
+    ast::TextureDimension dims,
+    TextureDataType datatype,
+    const char* f,
+    std::function<ExpressionList(ProgramBuilder*)> a)
+    : overload(o),
+      description(desc),
+      texture_kind(tk),
+      texture_dimension(dims),
+      texture_data_type(datatype),
+      function(f),
+      args(std::move(a)) {}
+TextureOverloadCase::TextureOverloadCase(
+    ValidTextureOverload o,
+    const char* d,
+    Access acc,
+    ast::TexelFormat fmt,
+    ast::TextureDimension dims,
+    TextureDataType datatype,
+    const char* f,
+    std::function<ExpressionList(ProgramBuilder*)> a)
+    : overload(o),
+      description(d),
+      texture_kind(TextureKind::kStorage),
+      access(acc),
+      texel_format(fmt),
+      texture_dimension(dims),
+      texture_data_type(datatype),
+      function(f),
+      args(std::move(a)) {}
+TextureOverloadCase::TextureOverloadCase(const TextureOverloadCase&) = default;
+TextureOverloadCase::~TextureOverloadCase() = default;
+
+std::ostream& operator<<(std::ostream& out, const TextureKind& kind) {
+  switch (kind) {
+    case TextureKind::kRegular:
+      out << "regular";
+      break;
+    case TextureKind::kDepth:
+      out << "depth";
+      break;
+    case TextureKind::kDepthMultisampled:
+      out << "depth-multisampled";
+      break;
+    case TextureKind::kMultisampled:
+      out << "multisampled";
+      break;
+    case TextureKind::kStorage:
+      out << "storage";
+      break;
+  }
+  return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const TextureDataType& ty) {
+  switch (ty) {
+    case TextureDataType::kF32:
+      out << "f32";
+      break;
+    case TextureDataType::kU32:
+      out << "u32";
+      break;
+    case TextureDataType::kI32:
+      out << "i32";
+      break;
+  }
+  return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data) {
+  out << "TextureOverloadCase " << static_cast<int>(data.overload) << "\n";
+  out << data.description << "\n";
+  out << "texture_kind:      " << data.texture_kind << "\n";
+  out << "sampler_kind:      ";
+  if (data.texture_kind != TextureKind::kStorage) {
+    out << data.sampler_kind;
+  } else {
+    out << "<unused>";
+  }
+  out << "\n";
+  out << "access:            " << data.access << "\n";
+  out << "texel_format:      " << data.texel_format << "\n";
+  out << "texture_dimension: " << data.texture_dimension << "\n";
+  out << "texture_data_type: " << data.texture_data_type << "\n";
+  return out;
+}
+
+const ast::Type* TextureOverloadCase::BuildResultVectorComponentType(
+    ProgramBuilder* b) const {
+  switch (texture_data_type) {
+    case ast::builtin::test::TextureDataType::kF32:
+      return b->ty.f32();
+    case ast::builtin::test::TextureDataType::kU32:
+      return b->ty.u32();
+    case ast::builtin::test::TextureDataType::kI32:
+      return b->ty.i32();
+  }
+
+  TINT_UNREACHABLE(AST, b->Diagnostics());
+  return {};
+}
+
+const ast::Variable* TextureOverloadCase::BuildTextureVariable(
+    ProgramBuilder* b) const {
+  AttributeList attrs = {
+      b->create<ast::GroupAttribute>(0),
+      b->create<ast::BindingAttribute>(0),
+  };
+  switch (texture_kind) {
+    case ast::builtin::test::TextureKind::kRegular:
+      return b->Global("texture",
+                       b->ty.sampled_texture(texture_dimension,
+                                             BuildResultVectorComponentType(b)),
+                       attrs);
+
+    case ast::builtin::test::TextureKind::kDepth:
+      return b->Global("texture", b->ty.depth_texture(texture_dimension),
+                       attrs);
+
+    case ast::builtin::test::TextureKind::kDepthMultisampled:
+      return b->Global("texture",
+                       b->ty.depth_multisampled_texture(texture_dimension),
+                       attrs);
+
+    case ast::builtin::test::TextureKind::kMultisampled:
+      return b->Global(
+          "texture",
+          b->ty.multisampled_texture(texture_dimension,
+                                     BuildResultVectorComponentType(b)),
+          attrs);
+
+    case ast::builtin::test::TextureKind::kStorage: {
+      auto* st = b->ty.storage_texture(texture_dimension, texel_format, access);
+      return b->Global("texture", st, attrs);
+    }
+  }
+
+  TINT_UNREACHABLE(AST, b->Diagnostics());
+  return nullptr;
+}
+
+const ast::Variable* TextureOverloadCase::BuildSamplerVariable(
+    ProgramBuilder* b) const {
+  AttributeList attrs = {
+      b->create<ast::GroupAttribute>(0),
+      b->create<ast::BindingAttribute>(1),
+  };
+  return b->Global("sampler", b->ty.sampler(sampler_kind), attrs);
+}
+
+std::vector<TextureOverloadCase> TextureOverloadCase::ValidCases() {
+  return {
+      {
+          ValidTextureOverload::kDimensions1d,
+          "textureDimensions(t : texture_1d<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k1d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensions2d,
+          "textureDimensions(t : texture_2d<f32>) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensions2dLevel,
+          "textureDimensions(t     : texture_2d<f32>,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensions2dArray,
+          "textureDimensions(t : texture_2d_array<f32>) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensions2dArrayLevel,
+          "textureDimensions(t     : texture_2d_array<f32>,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensions3d,
+          "textureDimensions(t : texture_3d<f32>) -> vec3<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensions3dLevel,
+          "textureDimensions(t     : texture_3d<f32>,\n"
+          "                  level : i32) -> vec3<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsCube,
+          "textureDimensions(t : texture_cube<f32>) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsCubeLevel,
+          "textureDimensions(t     : texture_cube<f32>,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsCubeArray,
+          "textureDimensions(t : texture_cube_array<f32>) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsCubeArrayLevel,
+          "textureDimensions(t     : texture_cube_array<f32>,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsMultisampled2d,
+          "textureDimensions(t : texture_multisampled_2d<f32>)-> vec2<i32>",
+          TextureKind::kMultisampled,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepth2d,
+          "textureDimensions(t : texture_depth_2d) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepth2dLevel,
+          "textureDimensions(t     : texture_depth_2d,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepth2dArray,
+          "textureDimensions(t : texture_depth_2d_array) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepth2dArrayLevel,
+          "textureDimensions(t     : texture_depth_2d_array,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepthCube,
+          "textureDimensions(t : texture_depth_cube) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepthCubeLevel,
+          "textureDimensions(t     : texture_depth_cube,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepthCubeArray,
+          "textureDimensions(t : texture_depth_cube_array) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepthCubeArrayLevel,
+          "textureDimensions(t     : texture_depth_cube_array,\n"
+          "                  level : i32) -> vec2<i32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
+      },
+      {
+          ValidTextureOverload::kDimensionsDepthMultisampled2d,
+          "textureDimensions(t : texture_depth_multisampled_2d) -> vec2<i32>",
+          TextureKind::kDepthMultisampled,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsStorageWO1d,
+          "textureDimensions(t : texture_storage_1d<rgba32float>) -> i32",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k1d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsStorageWO2d,
+          "textureDimensions(t : texture_storage_2d<rgba32float>) -> "
+          "vec2<i32>",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsStorageWO2dArray,
+          "textureDimensions(t : texture_storage_2d_array<rgba32float>) -> "
+          "vec2<i32>",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kDimensionsStorageWO3d,
+          "textureDimensions(t : texture_storage_3d<rgba32float>) -> "
+          "vec3<i32>",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureDimensions",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+
+      {
+          ValidTextureOverload::kGather2dF32,
+          "textureGather(component : i32,\n"
+          "              t         : texture_2d<T>,\n"
+          "              s         : sampler,\n"
+          "              coords    : vec2<f32>) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                        // component
+                               "texture",                // t
+                               "sampler",                // s
+                               b->vec2<f32>(1.f, 2.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kGather2dOffsetF32,
+          "textureGather(component : i32,\n"
+          "              t         : texture_2d<T>,\n"
+          "              s         : sampler,\n"
+          "              coords    : vec2<f32>,\n"
+          "              offset    : vec2<i32>) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                       // component
+                               "texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               b->vec2<i32>(3, 4));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGather2dArrayF32,
+          "textureGather(component   : i32,\n"
+          "              t           : texture_2d_array<T>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                       // component
+                               "texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3);                      // array index
+          },
+      },
+      {
+          ValidTextureOverload::kGather2dArrayOffsetF32,
+          "textureGather(component   : i32,\n"
+          "              t           : texture_2d_array<T>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32,\n"
+          "              offset      : vec2<i32>) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                       // component
+                               "texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCubeF32,
+          "textureGather(component : i32,\n"
+          "              t         : texture_cube<T>,\n"
+          "              s         : sampler,\n"
+          "              coords    : vec3<f32>) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                             // component
+                               "texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCubeArrayF32,
+          "textureGather(component   : i32,\n"
+          "              t           : texture_cube_array<T>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec3<f32>,\n"
+          "              array_index : i32) -> vec4<T>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList(0,                            // component
+                               "texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4);                           // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepth2dF32,
+          "textureGather(t      : texture_depth_2d,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                // t
+                               "sampler",                // s
+                               b->vec2<f32>(1.f, 2.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepth2dOffsetF32,
+          "textureGather(t      : texture_depth_2d,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>,\n"
+          "              offset : vec2<i32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               b->vec2<i32>(3, 4));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepth2dArrayF32,
+          "textureGather(t           : texture_depth_2d_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3);                      // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepth2dArrayOffsetF32,
+          "textureGather(t           : texture_depth_2d_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32,\n"
+          "              offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepthCubeF32,
+          "textureGather(t      : texture_depth_cube,\n"
+          "              s      : sampler,\n"
+          "              coords : vec3<f32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kGatherDepthCubeArrayF32,
+          "textureGather(t           : texture_depth_cube_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec3<f32>,\n"
+          "              array_index : i32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureGather",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4);                           // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepth2dF32,
+          "textureGatherCompare(t         : texture_depth_2d,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec2<f32>,\n"
+          "                     depth_ref : f32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f);                    // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepth2dOffsetF32,
+          "textureGatherCompare(t         : texture_depth_2d,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec2<f32>,\n"
+          "                     depth_ref : f32,\n"
+          "                     offset    : vec2<i32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f,                     // depth_ref
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepth2dArrayF32,
+          "textureGatherCompare(t           : texture_depth_2d_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec2<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4.f);                    // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32,
+          "textureGatherCompare(t           : texture_depth_2d_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec2<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32,\n"
+          "                     offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4.f,                     // depth_ref
+                               b->vec2<i32>(5, 6));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepthCubeF32,
+          "textureGatherCompare(t         : texture_depth_cube,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec3<f32>,\n"
+          "                     depth_ref : f32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kGatherCompareDepthCubeArrayF32,
+          "textureGatherCompare(t           : texture_depth_cube_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec3<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32) -> vec4<f32>",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureGatherCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4,                            // array_index
+                               5.f);                         // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kNumLayers2dArray,
+          "textureNumLayers(t : texture_2d_array<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureNumLayers",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLayersCubeArray,
+          "textureNumLayers(t : texture_cube_array<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureNumLayers",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLayersDepth2dArray,
+          "textureNumLayers(t : texture_depth_2d_array) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureNumLayers",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLayersDepthCubeArray,
+          "textureNumLayers(t : texture_depth_cube_array) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureNumLayers",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLayersStorageWO2dArray,
+          "textureNumLayers(t : texture_storage_2d_array<rgba32float>) -> i32",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureNumLayers",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevels2d,
+          "textureNumLevels(t : texture_2d<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevels2dArray,
+          "textureNumLevels(t : texture_2d_array<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevels3d,
+          "textureNumLevels(t : texture_3d<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsCube,
+          "textureNumLevels(t : texture_cube<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsCubeArray,
+          "textureNumLevels(t : texture_cube_array<f32>) -> i32",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsDepth2d,
+          "textureNumLevels(t : texture_depth_2d) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsDepth2dArray,
+          "textureNumLevels(t : texture_depth_2d_array) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsDepthCube,
+          "textureNumLevels(t : texture_depth_cube) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumLevelsDepthCubeArray,
+          "textureNumLevels(t : texture_depth_cube_array) -> i32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureNumLevels",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kNumSamplesMultisampled2d,
+          "textureNumSamples(t : texture_multisampled_2d<f32>) -> i32",
+          TextureKind::kMultisampled,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureNumSamples",
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
+      },
+      {
+          ValidTextureOverload::kSample1dF32,
+          "textureSample(t      : texture_1d<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k1d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",  // t
+                               "sampler",  // s
+                               1.0f);      // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSample2dF32,
+          "textureSample(t      : texture_2d<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                // t
+                               "sampler",                // s
+                               b->vec2<f32>(1.f, 2.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSample2dOffsetF32,
+          "textureSample(t      : texture_2d<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>\n"
+          "              offset : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               b->vec2<i32>(3, 4));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSample2dArrayF32,
+          "textureSample(t           : texture_2d_array<f32>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3);                      // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kSample2dArrayOffsetF32,
+          "textureSample(t           : texture_2d_array<f32>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32\n"
+          "              offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSample3dF32,
+          "textureSample(t      : texture_3d<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : vec3<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSample3dOffsetF32,
+          "textureSample(t      : texture_3d<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : vec3<f32>\n"
+          "              offset : vec3<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               b->vec3<i32>(4, 5, 6));       // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCubeF32,
+          "textureSample(t      : texture_cube<f32>,\n"
+          "              s      : sampler,\n"
+          "              coords : vec3<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCubeArrayF32,
+          "textureSample(t           : texture_cube_array<f32>,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec3<f32>,\n"
+          "              array_index : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4);                           // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepth2dF32,
+          "textureSample(t      : texture_depth_2d,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                // t
+                               "sampler",                // s
+                               b->vec2<f32>(1.f, 2.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepth2dOffsetF32,
+          "textureSample(t      : texture_depth_2d,\n"
+          "              s      : sampler,\n"
+          "              coords : vec2<f32>\n"
+          "              offset : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               b->vec2<i32>(3, 4));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepth2dArrayF32,
+          "textureSample(t           : texture_depth_2d_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3);                      // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepth2dArrayOffsetF32,
+          "textureSample(t           : texture_depth_2d_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec2<f32>,\n"
+          "              array_index : i32\n"
+          "              offset      : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepthCubeF32,
+          "textureSample(t      : texture_depth_cube,\n"
+          "              s      : sampler,\n"
+          "              coords : vec3<f32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f));  // coords
+          },
+      },
+      {
+          ValidTextureOverload::kSampleDepthCubeArrayF32,
+          "textureSample(t           : texture_depth_cube_array,\n"
+          "              s           : sampler,\n"
+          "              coords      : vec3<f32>,\n"
+          "              array_index : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSample",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4);                           // array_index
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias2dF32,
+          "textureSampleBias(t      : texture_2d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec2<f32>,\n"
+          "                  bias   : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f);                    // bias
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias2dOffsetF32,
+          "textureSampleBias(t      : texture_2d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec2<f32>,\n"
+          "                  bias   : f32,\n"
+          "                  offset : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f,                     // bias
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias2dArrayF32,
+          "textureSampleBias(t           : texture_2d_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec2<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  bias        : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               4,                       // array_index
+                               3.f);                    // bias
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias2dArrayOffsetF32,
+          "textureSampleBias(t           : texture_2d_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec2<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  bias        : f32,\n"
+          "                  offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4.f,                     // bias
+                               b->vec2<i32>(5, 6));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias3dF32,
+          "textureSampleBias(t      : texture_3d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  bias   : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // bias
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBias3dOffsetF32,
+          "textureSampleBias(t      : texture_3d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  bias   : f32,\n"
+          "                  offset : vec3<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f,                          // bias
+                               b->vec3<i32>(5, 6, 7));       // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBiasCubeF32,
+          "textureSampleBias(t      : texture_cube<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  bias   : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // bias
+          },
+      },
+      {
+          ValidTextureOverload::kSampleBiasCubeArrayF32,
+          "textureSampleBias(t           : texture_cube_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec3<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  bias        : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSampleBias",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               3,                            // array_index
+                               4.f);                         // bias
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel2dF32,
+          "textureSampleLevel(t      : texture_2d<f32>,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec2<f32>,\n"
+          "                   level  : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f);                    // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel2dOffsetF32,
+          "textureSampleLevel(t      : texture_2d<f32>,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec2<f32>,\n"
+          "                   level  : f32,\n"
+          "                   offset : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f,                     // level
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel2dArrayF32,
+          "textureSampleLevel(t           : texture_2d_array<f32>,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec2<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4.f);                    // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel2dArrayOffsetF32,
+          "textureSampleLevel(t           : texture_2d_array<f32>,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec2<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : f32,\n"
+          "                   offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4.f,                     // level
+                               b->vec2<i32>(5, 6));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel3dF32,
+          "textureSampleLevel(t      : texture_3d<f32>,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec3<f32>,\n"
+          "                   level  : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevel3dOffsetF32,
+          "textureSampleLevel(t      : texture_3d<f32>,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec3<f32>,\n"
+          "                   level  : f32,\n"
+          "                   offset : vec3<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f,                          // level
+                               b->vec3<i32>(5, 6, 7));       // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelCubeF32,
+          "textureSampleLevel(t      : texture_cube<f32>,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec3<f32>,\n"
+          "                   level  : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelCubeArrayF32,
+          "textureSampleLevel(t           : texture_cube_array<f32>,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec3<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : f32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4,                            // array_index
+                               5.f);                         // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepth2dF32,
+          "textureSampleLevel(t      : texture_depth_2d,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec2<f32>,\n"
+          "                   level  : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3);                      // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepth2dOffsetF32,
+          "textureSampleLevel(t      : texture_depth_2d,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec2<f32>,\n"
+          "                   level  : i32,\n"
+          "                   offset : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // level
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepth2dArrayF32,
+          "textureSampleLevel(t           : texture_depth_2d_array,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec2<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4);                      // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32,
+          "textureSampleLevel(t           : texture_depth_2d_array,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec2<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : i32,\n"
+          "                   offset      : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               4,                       // level
+                               b->vec2<i32>(5, 6));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepthCubeF32,
+          "textureSampleLevel(t      : texture_depth_cube,\n"
+          "                   s      : sampler,\n"
+          "                   coords : vec3<f32>,\n"
+          "                   level  : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4);                           // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleLevelDepthCubeArrayF32,
+          "textureSampleLevel(t           : texture_depth_cube_array,\n"
+          "                   s           : sampler,\n"
+          "                   coords      : vec3<f32>,\n"
+          "                   array_index : i32,\n"
+          "                   level       : i32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSampleLevel",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4,                            // array_index
+                               5);                           // level
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad2dF32,
+          "textureSampleGrad(t      : texture_2d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec2<f32>\n"
+          "                  ddx    : vec2<f32>,\n"
+          "                  ddy    : vec2<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                  // t
+                               "sampler",                  // s
+                               b->vec2<f32>(1.0f, 2.0f),   // coords
+                               b->vec2<f32>(3.0f, 4.0f),   // ddx
+                               b->vec2<f32>(5.0f, 6.0f));  // ddy
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad2dOffsetF32,
+          "textureSampleGrad(t      : texture_2d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec2<f32>,\n"
+          "                  ddx    : vec2<f32>,\n"
+          "                  ddy    : vec2<f32>,\n"
+          "                  offset : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               b->vec2<f32>(3.f, 4.f),  // ddx
+                               b->vec2<f32>(5.f, 6.f),  // ddy
+                               b->vec2<i32>(7, 7));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad2dArrayF32,
+          "textureSampleGrad(t           : texture_2d_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec2<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  ddx         : vec2<f32>,\n"
+          "                  ddy         : vec2<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                // t
+                               "sampler",                // s
+                               b->vec2<f32>(1.f, 2.f),   // coords
+                               3,                        // array_index
+                               b->vec2<f32>(4.f, 5.f),   // ddx
+                               b->vec2<f32>(6.f, 7.f));  // ddy
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad2dArrayOffsetF32,
+          "textureSampleGrad(t           : texture_2d_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec2<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  ddx         : vec2<f32>,\n"
+          "                  ddy         : vec2<f32>,\n"
+          "                  offset      : vec2<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3,                       // array_index
+                               b->vec2<f32>(4.f, 5.f),  // ddx
+                               b->vec2<f32>(6.f, 7.f),  // ddy
+                               b->vec2<i32>(6, 7));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad3dF32,
+          "textureSampleGrad(t      : texture_3d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  ddx    : vec3<f32>,\n"
+          "                  ddy    : vec3<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),   // coords
+                               b->vec3<f32>(4.f, 5.f, 6.f),   // ddx
+                               b->vec3<f32>(7.f, 8.f, 9.f));  // ddy
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGrad3dOffsetF32,
+          "textureSampleGrad(t      : texture_3d<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  ddx    : vec3<f32>,\n"
+          "                  ddy    : vec3<f32>,\n"
+          "                  offset : vec3<i32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               b->vec3<f32>(4.f, 5.f, 6.f),  // ddx
+                               b->vec3<f32>(7.f, 8.f, 9.f),  // ddy
+                               b->vec3<i32>(0, 1, 2));       // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGradCubeF32,
+          "textureSampleGrad(t      : texture_cube<f32>,\n"
+          "                  s      : sampler,\n"
+          "                  coords : vec3<f32>,\n"
+          "                  ddx    : vec3<f32>,\n"
+          "                  ddy    : vec3<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                     // t
+                               "sampler",                     // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),   // coords
+                               b->vec3<f32>(4.f, 5.f, 6.f),   // ddx
+                               b->vec3<f32>(7.f, 8.f, 9.f));  // ddy
+          },
+      },
+      {
+          ValidTextureOverload::kSampleGradCubeArrayF32,
+          "textureSampleGrad(t           : texture_cube_array<f32>,\n"
+          "                  s           : sampler,\n"
+          "                  coords      : vec3<f32>,\n"
+          "                  array_index : i32,\n"
+          "                  ddx         : vec3<f32>,\n"
+          "                  ddy         : vec3<f32>) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::SamplerKind::kSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSampleGrad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                      // t
+                               "sampler",                      // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),    // coords
+                               4,                              // array_index
+                               b->vec3<f32>(5.f, 6.f, 7.f),    // ddx
+                               b->vec3<f32>(8.f, 9.f, 10.f));  // ddy
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepth2dF32,
+          "textureSampleCompare(t         : texture_depth_2d,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec2<f32>,\n"
+          "                     depth_ref : f32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f);                    // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepth2dOffsetF32,
+          "textureSampleCompare(t         : texture_depth_2d,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec2<f32>,\n"
+          "                     depth_ref : f32,\n"
+          "                     offset    : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               3.f,                     // depth_ref
+                               b->vec2<i32>(4, 5));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepth2dArrayF32,
+          "textureSampleCompare(t           : texture_depth_2d_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec2<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               4,                       // array_index
+                               3.f);                    // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32,
+          "textureSampleCompare(t           : texture_depth_2d_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec2<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32,\n"
+          "                     offset      : vec2<i32>) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",               // t
+                               "sampler",               // s
+                               b->vec2<f32>(1.f, 2.f),  // coords
+                               4,                       // array_index
+                               3.f,                     // depth_ref
+                               b->vec2<i32>(5, 6));     // offset
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepthCubeF32,
+          "textureSampleCompare(t         : texture_depth_cube,\n"
+          "                     s         : sampler_comparison,\n"
+          "                     coords    : vec3<f32>,\n"
+          "                     depth_ref : f32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::kCube,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4.f);                         // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kSampleCompareDepthCubeArrayF32,
+          "textureSampleCompare(t           : texture_depth_cube_array,\n"
+          "                     s           : sampler_comparison,\n"
+          "                     coords      : vec3<f32>,\n"
+          "                     array_index : i32,\n"
+          "                     depth_ref   : f32) -> f32",
+          TextureKind::kDepth,
+          ast::SamplerKind::kComparisonSampler,
+          ast::TextureDimension::kCubeArray,
+          TextureDataType::kF32,
+          "textureSampleCompare",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                    // t
+                               "sampler",                    // s
+                               b->vec3<f32>(1.f, 2.f, 3.f),  // coords
+                               4,                            // array_index
+                               5.f);                         // depth_ref
+          },
+      },
+      {
+          ValidTextureOverload::kLoad1dLevelF32,
+          "textureLoad(t      : texture_1d<f32>,\n"
+          "            coords : i32,\n"
+          "            level  : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k1d,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",  // t
+                               1,          // coords
+                               3);         // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad1dLevelU32,
+          "textureLoad(t      : texture_1d<u32>,\n"
+          "            coords : i32,\n"
+          "            level  : i32) -> vec4<u32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k1d,
+          TextureDataType::kU32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",  // t
+                               1,          // coords
+                               3);         // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad1dLevelI32,
+          "textureLoad(t      : texture_1d<i32>,\n"
+          "            coords : i32,\n"
+          "            level  : i32) -> vec4<i32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k1d,
+          TextureDataType::kI32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",  // t
+                               1,          // coords
+                               3);         // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dLevelF32,
+          "textureLoad(t      : texture_2d<f32>,\n"
+          "            coords : vec2<i32>,\n"
+          "            level  : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dLevelU32,
+          "textureLoad(t      : texture_2d<u32>,\n"
+          "            coords : vec2<i32>,\n"
+          "            level  : i32) -> vec4<u32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2d,
+          TextureDataType::kU32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dLevelI32,
+          "textureLoad(t      : texture_2d<i32>,\n"
+          "            coords : vec2<i32>,\n"
+          "            level  : i32) -> vec4<i32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2d,
+          TextureDataType::kI32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dArrayLevelF32,
+          "textureLoad(t           : texture_2d_array<f32>,\n"
+          "            coords      : vec2<i32>,\n"
+          "            array_index : i32,\n"
+          "            level       : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3,                   // array_index
+                               4);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dArrayLevelU32,
+          "textureLoad(t           : texture_2d_array<u32>,\n"
+          "            coords      : vec2<i32>,\n"
+          "            array_index : i32,\n"
+          "            level       : i32) -> vec4<u32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kU32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3,                   // array_index
+                               4);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad2dArrayLevelI32,
+          "textureLoad(t           : texture_2d_array<i32>,\n"
+          "            coords      : vec2<i32>,\n"
+          "            array_index : i32,\n"
+          "            level       : i32) -> vec4<i32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kI32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3,                   // array_index
+                               4);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad3dLevelF32,
+          "textureLoad(t      : texture_3d<f32>,\n"
+          "            coords : vec3<i32>,\n"
+          "            level  : i32) -> vec4<f32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",              // t
+                               b->vec3<i32>(1, 2, 3),  // coords
+                               4);                     // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad3dLevelU32,
+          "textureLoad(t      : texture_3d<u32>,\n"
+          "            coords : vec3<i32>,\n"
+          "            level  : i32) -> vec4<u32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k3d,
+          TextureDataType::kU32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",              // t
+                               b->vec3<i32>(1, 2, 3),  // coords
+                               4);                     // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoad3dLevelI32,
+          "textureLoad(t      : texture_3d<i32>,\n"
+          "            coords : vec3<i32>,\n"
+          "            level  : i32) -> vec4<i32>",
+          TextureKind::kRegular,
+          ast::TextureDimension::k3d,
+          TextureDataType::kI32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",              // t
+                               b->vec3<i32>(1, 2, 3),  // coords
+                               4);                     // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoadMultisampled2dF32,
+          "textureLoad(t            : texture_multisampled_2d<f32>,\n"
+          "            coords       : vec2<i32>,\n"
+          "            sample_index : i32) -> vec4<f32>",
+          TextureKind::kMultisampled,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // sample_index
+          },
+      },
+      {
+          ValidTextureOverload::kLoadMultisampled2dU32,
+          "textureLoad(t            : texture_multisampled_2d<u32>,\n"
+          "            coords       : vec2<i32>,\n"
+          "            sample_index : i32) -> vec4<u32>",
+          TextureKind::kMultisampled,
+          ast::TextureDimension::k2d,
+          TextureDataType::kU32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // sample_index
+          },
+      },
+      {
+          ValidTextureOverload::kLoadMultisampled2dI32,
+          "textureLoad(t            : texture_multisampled_2d<i32>,\n"
+          "            coords       : vec2<i32>,\n"
+          "            sample_index : i32) -> vec4<i32>",
+          TextureKind::kMultisampled,
+          ast::TextureDimension::k2d,
+          TextureDataType::kI32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // sample_index
+          },
+      },
+      {
+          ValidTextureOverload::kLoadDepth2dLevelF32,
+          "textureLoad(t      : texture_depth_2d,\n"
+          "            coords : vec2<i32>,\n"
+          "            level  : i32) -> f32",
+          TextureKind::kDepth,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kLoadDepth2dArrayLevelF32,
+          "textureLoad(t           : texture_depth_2d_array,\n"
+          "            coords      : vec2<i32>,\n"
+          "            array_index : i32,\n"
+          "            level       : i32) -> f32",
+          TextureKind::kDepth,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureLoad",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3,                   // array_index
+                               4);                  // level
+          },
+      },
+      {
+          ValidTextureOverload::kStoreWO1dRgba32float,
+          "textureStore(t      : texture_storage_1d<rgba32float>,\n"
+          "             coords : i32,\n"
+          "             value  : vec4<T>)",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k1d,
+          TextureDataType::kF32,
+          "textureStore",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                          // t
+                               1,                                  // coords
+                               b->vec4<f32>(2.f, 3.f, 4.f, 5.f));  // value
+          },
+      },
+      {
+          ValidTextureOverload::kStoreWO2dRgba32float,
+          "textureStore(t      : texture_storage_2d<rgba32float>,\n"
+          "             coords : vec2<i32>,\n"
+          "             value  : vec4<T>)",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k2d,
+          TextureDataType::kF32,
+          "textureStore",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                          // t
+                               b->vec2<i32>(1, 2),                 // coords
+                               b->vec4<f32>(3.f, 4.f, 5.f, 6.f));  // value
+          },
+      },
+      {
+          ValidTextureOverload::kStoreWO2dArrayRgba32float,
+          "textureStore(t           : texture_storage_2d_array<rgba32float>,\n"
+          "             coords      : vec2<i32>,\n"
+          "             array_index : i32,\n"
+          "             value       : vec4<T>)",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k2dArray,
+          TextureDataType::kF32,
+          "textureStore",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",           // t
+                               b->vec2<i32>(1, 2),  // coords
+                               3,                   // array_index
+                               b->vec4<f32>(4.f, 5.f, 6.f, 7.f));  // value
+          },
+      },
+      {
+          ValidTextureOverload::kStoreWO3dRgba32float,
+          "textureStore(t      : texture_storage_3d<rgba32float>,\n"
+          "             coords : vec3<i32>,\n"
+          "             value  : vec4<T>)",
+          ast::Access::kWrite,
+          ast::TexelFormat::kRgba32Float,
+          ast::TextureDimension::k3d,
+          TextureDataType::kF32,
+          "textureStore",
+          [](ProgramBuilder* b) {
+            return b->ExprList("texture",                          // t
+                               b->vec3<i32>(1, 2, 3),              // coords
+                               b->vec4<f32>(4.f, 5.f, 6.f, 7.f));  // value
+          },
+      },
+  };
+}
+
+bool ReturnsVoid(ValidTextureOverload texture_overload) {
+  switch (texture_overload) {
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace test
+}  // namespace builtin
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h
new file mode 100644
index 0000000..751f51b
--- /dev/null
+++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -0,0 +1,269 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
+#define SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
+
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace ast {
+namespace builtin {
+namespace test {
+
+enum class TextureKind {
+  kRegular,
+  kDepth,
+  kDepthMultisampled,
+  kMultisampled,
+  kStorage
+};
+enum class TextureDataType { kF32, kU32, kI32 };
+
+std::ostream& operator<<(std::ostream& out, const TextureKind& kind);
+std::ostream& operator<<(std::ostream& out, const TextureDataType& ty);
+
+/// Non-exhaustive list of valid texture overloads
+enum class ValidTextureOverload {
+  kDimensions1d,
+  kDimensions2d,
+  kDimensions2dLevel,
+  kDimensions2dArray,
+  kDimensions2dArrayLevel,
+  kDimensions3d,
+  kDimensions3dLevel,
+  kDimensionsCube,
+  kDimensionsCubeLevel,
+  kDimensionsCubeArray,
+  kDimensionsCubeArrayLevel,
+  kDimensionsMultisampled2d,
+  kDimensionsDepth2d,
+  kDimensionsDepth2dLevel,
+  kDimensionsDepth2dArray,
+  kDimensionsDepth2dArrayLevel,
+  kDimensionsDepthCube,
+  kDimensionsDepthCubeLevel,
+  kDimensionsDepthCubeArray,
+  kDimensionsDepthCubeArrayLevel,
+  kDimensionsDepthMultisampled2d,
+  kDimensionsStorageWO1d,
+  kDimensionsStorageWO2d,
+  kDimensionsStorageWO2dArray,
+  kDimensionsStorageWO3d,
+  kGather2dF32,
+  kGather2dOffsetF32,
+  kGather2dArrayF32,
+  kGather2dArrayOffsetF32,
+  kGatherCubeF32,
+  kGatherCubeArrayF32,
+  kGatherDepth2dF32,
+  kGatherDepth2dOffsetF32,
+  kGatherDepth2dArrayF32,
+  kGatherDepth2dArrayOffsetF32,
+  kGatherDepthCubeF32,
+  kGatherDepthCubeArrayF32,
+  kGatherCompareDepth2dF32,
+  kGatherCompareDepth2dOffsetF32,
+  kGatherCompareDepth2dArrayF32,
+  kGatherCompareDepth2dArrayOffsetF32,
+  kGatherCompareDepthCubeF32,
+  kGatherCompareDepthCubeArrayF32,
+  kNumLayers2dArray,
+  kNumLayersCubeArray,
+  kNumLayersDepth2dArray,
+  kNumLayersDepthCubeArray,
+  kNumLayersStorageWO2dArray,
+  kNumLevels2d,
+  kNumLevels2dArray,
+  kNumLevels3d,
+  kNumLevelsCube,
+  kNumLevelsCubeArray,
+  kNumLevelsDepth2d,
+  kNumLevelsDepth2dArray,
+  kNumLevelsDepthCube,
+  kNumLevelsDepthCubeArray,
+  kNumSamplesMultisampled2d,
+  kNumSamplesDepthMultisampled2d,
+  kSample1dF32,
+  kSample2dF32,
+  kSample2dOffsetF32,
+  kSample2dArrayF32,
+  kSample2dArrayOffsetF32,
+  kSample3dF32,
+  kSample3dOffsetF32,
+  kSampleCubeF32,
+  kSampleCubeArrayF32,
+  kSampleDepth2dF32,
+  kSampleDepth2dOffsetF32,
+  kSampleDepth2dArrayF32,
+  kSampleDepth2dArrayOffsetF32,
+  kSampleDepthCubeF32,
+  kSampleDepthCubeArrayF32,
+  kSampleBias2dF32,
+  kSampleBias2dOffsetF32,
+  kSampleBias2dArrayF32,
+  kSampleBias2dArrayOffsetF32,
+  kSampleBias3dF32,
+  kSampleBias3dOffsetF32,
+  kSampleBiasCubeF32,
+  kSampleBiasCubeArrayF32,
+  kSampleLevel2dF32,
+  kSampleLevel2dOffsetF32,
+  kSampleLevel2dArrayF32,
+  kSampleLevel2dArrayOffsetF32,
+  kSampleLevel3dF32,
+  kSampleLevel3dOffsetF32,
+  kSampleLevelCubeF32,
+  kSampleLevelCubeArrayF32,
+  kSampleLevelDepth2dF32,
+  kSampleLevelDepth2dOffsetF32,
+  kSampleLevelDepth2dArrayF32,
+  kSampleLevelDepth2dArrayOffsetF32,
+  kSampleLevelDepthCubeF32,
+  kSampleLevelDepthCubeArrayF32,
+  kSampleGrad2dF32,
+  kSampleGrad2dOffsetF32,
+  kSampleGrad2dArrayF32,
+  kSampleGrad2dArrayOffsetF32,
+  kSampleGrad3dF32,
+  kSampleGrad3dOffsetF32,
+  kSampleGradCubeF32,
+  kSampleGradCubeArrayF32,
+  kSampleCompareDepth2dF32,
+  kSampleCompareDepth2dOffsetF32,
+  kSampleCompareDepth2dArrayF32,
+  kSampleCompareDepth2dArrayOffsetF32,
+  kSampleCompareDepthCubeF32,
+  kSampleCompareDepthCubeArrayF32,
+  kSampleCompareLevelDepth2dF32,
+  kSampleCompareLevelDepth2dOffsetF32,
+  kSampleCompareLevelDepth2dArrayF32,
+  kSampleCompareLevelDepth2dArrayOffsetF32,
+  kSampleCompareLevelDepthCubeF32,
+  kSampleCompareLevelDepthCubeArrayF32,
+  kLoad1dLevelF32,
+  kLoad1dLevelU32,
+  kLoad1dLevelI32,
+  kLoad2dLevelF32,
+  kLoad2dLevelU32,
+  kLoad2dLevelI32,
+  kLoad2dArrayLevelF32,
+  kLoad2dArrayLevelU32,
+  kLoad2dArrayLevelI32,
+  kLoad3dLevelF32,
+  kLoad3dLevelU32,
+  kLoad3dLevelI32,
+  kLoadMultisampled2dF32,
+  kLoadMultisampled2dU32,
+  kLoadMultisampled2dI32,
+  kLoadDepth2dLevelF32,
+  kLoadDepth2dArrayLevelF32,
+  kLoadDepthMultisampled2dF32,
+  kStoreWO1dRgba32float,       // Not permutated for all texel formats
+  kStoreWO2dRgba32float,       // Not permutated for all texel formats
+  kStoreWO2dArrayRgba32float,  // Not permutated for all texel formats
+  kStoreWO3dRgba32float,       // Not permutated for all texel formats
+};
+
+/// @param texture_overload the ValidTextureOverload
+/// @returns true if the ValidTextureOverload builtin returns no value.
+bool ReturnsVoid(ValidTextureOverload texture_overload);
+
+/// Describes a texture builtin overload
+struct TextureOverloadCase {
+  /// Constructor for textureSample...() functions
+  TextureOverloadCase(ValidTextureOverload,
+                      const char*,
+                      TextureKind,
+                      ast::SamplerKind,
+                      ast::TextureDimension,
+                      TextureDataType,
+                      const char*,
+                      std::function<ExpressionList(ProgramBuilder*)>);
+  /// Constructor for textureLoad() functions with non-storage textures
+  TextureOverloadCase(ValidTextureOverload,
+                      const char*,
+                      TextureKind,
+                      ast::TextureDimension,
+                      TextureDataType,
+                      const char*,
+                      std::function<ExpressionList(ProgramBuilder*)>);
+  /// Constructor for textureLoad() with storage textures
+  TextureOverloadCase(ValidTextureOverload,
+                      const char*,
+                      Access,
+                      ast::TexelFormat,
+                      ast::TextureDimension,
+                      TextureDataType,
+                      const char*,
+                      std::function<ExpressionList(ProgramBuilder*)>);
+  /// Copy constructor
+  TextureOverloadCase(const TextureOverloadCase&);
+  /// Destructor
+  ~TextureOverloadCase();
+
+  /// @return a vector containing a large number (non-exhaustive) of valid
+  /// texture overloads.
+  static std::vector<TextureOverloadCase> ValidCases();
+
+  /// @param builder the AST builder used for the test
+  /// @returns the vector component type of the texture function return value
+  const ast::Type* BuildResultVectorComponentType(
+      ProgramBuilder* builder) const;
+  /// @param builder the AST builder used for the test
+  /// @returns a variable holding the test texture, automatically registered as
+  /// a global variable.
+  const ast::Variable* BuildTextureVariable(ProgramBuilder* builder) const;
+  /// @param builder the AST builder used for the test
+  /// @returns a Variable holding the test sampler, automatically registered as
+  /// a global variable.
+  const ast::Variable* BuildSamplerVariable(ProgramBuilder* builder) const;
+
+  /// The enumerator for this overload
+  const ValidTextureOverload overload;
+  /// A human readable description of the overload
+  const char* const description;
+  /// The texture kind for the texture parameter
+  const TextureKind texture_kind;
+  /// The sampler kind for the sampler parameter
+  /// Used only when texture_kind is not kStorage
+  ast::SamplerKind const sampler_kind = ast::SamplerKind::kSampler;
+  /// The access control for the storage texture
+  /// Used only when texture_kind is kStorage
+  Access const access = Access::kReadWrite;
+  /// The image format for the storage texture
+  /// Used only when texture_kind is kStorage
+  ast::TexelFormat const texel_format = ast::TexelFormat::kNone;
+  /// The dimensions of the texture parameter
+  ast::TextureDimension const texture_dimension;
+  /// The data type of the texture parameter
+  const TextureDataType texture_data_type;
+  /// Name of the function. e.g. `textureSample`, `textureSampleGrad`, etc
+  const char* const function;
+  /// A function that builds the AST arguments for the overload
+  std::function<ExpressionList(ProgramBuilder*)> const args;
+};
+
+std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data);
+
+}  // namespace test
+}  // namespace builtin
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
diff --git a/src/tint/ast/call_expression.cc b/src/tint/ast/call_expression.cc
new file mode 100644
index 0000000..7abf4d7
--- /dev/null
+++ b/src/tint/ast/call_expression.cc
@@ -0,0 +1,78 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::CallExpression);
+
+namespace tint {
+namespace ast {
+
+namespace {
+CallExpression::Target ToTarget(const IdentifierExpression* name) {
+  CallExpression::Target target;
+  target.name = name;
+  return target;
+}
+CallExpression::Target ToTarget(const Type* type) {
+  CallExpression::Target target;
+  target.type = type;
+  return target;
+}
+}  // namespace
+
+CallExpression::CallExpression(ProgramID pid,
+                               const Source& src,
+                               const IdentifierExpression* name,
+                               ExpressionList a)
+    : Base(pid, src), target(ToTarget(name)), args(a) {
+  TINT_ASSERT(AST, name);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
+  for (auto* arg : args) {
+    TINT_ASSERT(AST, arg);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
+  }
+}
+
+CallExpression::CallExpression(ProgramID pid,
+                               const Source& src,
+                               const Type* type,
+                               ExpressionList a)
+    : Base(pid, src), target(ToTarget(type)), args(a) {
+  TINT_ASSERT(AST, type);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
+  for (auto* arg : args) {
+    TINT_ASSERT(AST, arg);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
+  }
+}
+
+CallExpression::CallExpression(CallExpression&&) = default;
+
+CallExpression::~CallExpression() = default;
+
+const CallExpression* CallExpression::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto p = ctx->Clone(args);
+  return target.name
+             ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), p)
+             : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type),
+                                                p);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/call_expression.h b/src/tint/ast/call_expression.h
new file mode 100644
index 0000000..21b1c4c
--- /dev/null
+++ b/src/tint/ast/call_expression.h
@@ -0,0 +1,84 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_CALL_EXPRESSION_H_
+#define SRC_TINT_AST_CALL_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declarations.
+class Type;
+class IdentifierExpression;
+
+/// A call expression - represents either a:
+/// * sem::Function
+/// * sem::Builtin
+/// * sem::TypeConstructor
+/// * sem::TypeConversion
+class CallExpression : public Castable<CallExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the call expression source
+  /// @param name the function or type name
+  /// @param args the arguments
+  CallExpression(ProgramID program_id,
+                 const Source& source,
+                 const IdentifierExpression* name,
+                 ExpressionList args);
+
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the call expression source
+  /// @param type the type
+  /// @param args the arguments
+  CallExpression(ProgramID program_id,
+                 const Source& source,
+                 const Type* type,
+                 ExpressionList args);
+
+  /// Move constructor
+  CallExpression(CallExpression&&);
+  ~CallExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const CallExpression* Clone(CloneContext* ctx) const override;
+
+  /// Target is either an identifier, or a Type.
+  /// One of these must be nullptr and the other a non-nullptr.
+  struct Target {
+    /// name is a function or builtin to call, or type name to construct or
+    /// cast-to
+    const IdentifierExpression* name = nullptr;
+    /// type to construct or cast-to
+    const Type* type = nullptr;
+  };
+
+  /// The target function
+  const Target target;
+
+  /// The arguments
+  const ExpressionList args;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_CALL_EXPRESSION_H_
diff --git a/src/tint/ast/call_expression_test.cc b/src/tint/ast/call_expression_test.cc
new file mode 100644
index 0000000..a150af6
--- /dev/null
+++ b/src/tint/ast/call_expression_test.cc
@@ -0,0 +1,149 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using CallExpressionTest = TestHelper;
+
+TEST_F(CallExpressionTest, CreationIdentifier) {
+  auto* func = Expr("func");
+  ExpressionList params;
+  params.push_back(Expr("param1"));
+  params.push_back(Expr("param2"));
+
+  auto* stmt = create<CallExpression>(func, params);
+  EXPECT_EQ(stmt->target.name, func);
+  EXPECT_EQ(stmt->target.type, nullptr);
+
+  const auto& vec = stmt->args;
+  ASSERT_EQ(vec.size(), 2u);
+  EXPECT_EQ(vec[0], params[0]);
+  EXPECT_EQ(vec[1], params[1]);
+}
+
+TEST_F(CallExpressionTest, CreationIdentifier_WithSource) {
+  auto* func = Expr("func");
+  auto* stmt = create<CallExpression>(Source{{20, 2}}, func, ExpressionList{});
+  EXPECT_EQ(stmt->target.name, func);
+  EXPECT_EQ(stmt->target.type, nullptr);
+
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(CallExpressionTest, CreationType) {
+  auto* type = ty.f32();
+  ExpressionList params;
+  params.push_back(Expr("param1"));
+  params.push_back(Expr("param2"));
+
+  auto* stmt = create<CallExpression>(type, params);
+  EXPECT_EQ(stmt->target.name, nullptr);
+  EXPECT_EQ(stmt->target.type, type);
+
+  const auto& vec = stmt->args;
+  ASSERT_EQ(vec.size(), 2u);
+  EXPECT_EQ(vec[0], params[0]);
+  EXPECT_EQ(vec[1], params[1]);
+}
+
+TEST_F(CallExpressionTest, CreationType_WithSource) {
+  auto* type = ty.f32();
+  auto* stmt = create<CallExpression>(Source{{20, 2}}, type, ExpressionList{});
+  EXPECT_EQ(stmt->target.name, nullptr);
+  EXPECT_EQ(stmt->target.type, type);
+
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(CallExpressionTest, IsCall) {
+  auto* func = Expr("func");
+  auto* stmt = create<CallExpression>(func, ExpressionList{});
+  EXPECT_TRUE(stmt->Is<CallExpression>());
+}
+
+TEST_F(CallExpressionTest, Assert_Null_Identifier) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<CallExpression>(static_cast<IdentifierExpression*>(nullptr),
+                                 ExpressionList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallExpressionTest, Assert_Null_Type) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<CallExpression>(static_cast<Type*>(nullptr), ExpressionList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallExpressionTest, Assert_Null_Param) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        ExpressionList params;
+        params.push_back(b.Expr("param1"));
+        params.push_back(nullptr);
+        params.push_back(b.Expr("param2"));
+        b.create<CallExpression>(b.Expr("func"), params);
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallExpressionTest, Assert_DifferentProgramID_Identifier) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CallExpression>(b2.Expr("func"), ExpressionList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallExpressionTest, Assert_DifferentProgramID_Type) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CallExpression>(b2.ty.f32(), ExpressionList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallExpressionTest, Assert_DifferentProgramID_Param) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CallExpression>(b1.Expr("func"),
+                                  ExpressionList{b2.Expr("param1")});
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/call_statement.cc b/src/tint/ast/call_statement.cc
new file mode 100644
index 0000000..be2e97c
--- /dev/null
+++ b/src/tint/ast/call_statement.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::CallStatement);
+
+namespace tint {
+namespace ast {
+
+CallStatement::CallStatement(ProgramID pid,
+                             const Source& src,
+                             const CallExpression* call)
+    : Base(pid, src), expr(call) {
+  TINT_ASSERT(AST, expr);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
+}
+
+CallStatement::CallStatement(CallStatement&&) = default;
+
+CallStatement::~CallStatement() = default;
+
+const CallStatement* CallStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* call = ctx->Clone(expr);
+  return ctx->dst->create<CallStatement>(src, call);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/call_statement.h b/src/tint/ast/call_statement.h
new file mode 100644
index 0000000..6445819
--- /dev/null
+++ b/src/tint/ast/call_statement.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_CALL_STATEMENT_H_
+#define SRC_TINT_AST_CALL_STATEMENT_H_
+
+#include "src/tint/ast/call_expression.h"
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// A call expression
+class CallStatement : public Castable<CallStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node for the statement
+  /// @param call the function
+  CallStatement(ProgramID pid, const Source& src, const CallExpression* call);
+  /// Move constructor
+  CallStatement(CallStatement&&);
+  ~CallStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const CallStatement* Clone(CloneContext* ctx) const override;
+
+  /// The call expression
+  const CallExpression* const expr;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_CALL_STATEMENT_H_
diff --git a/src/tint/ast/call_statement_test.cc b/src/tint/ast/call_statement_test.cc
new file mode 100644
index 0000000..1267a6d
--- /dev/null
+++ b/src/tint/ast/call_statement_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using CallStatementTest = TestHelper;
+
+TEST_F(CallStatementTest, Creation) {
+  auto* expr = create<CallExpression>(Expr("func"), ExpressionList{});
+
+  auto* c = create<CallStatement>(expr);
+  EXPECT_EQ(c->expr, expr);
+}
+
+TEST_F(CallStatementTest, IsCall) {
+  auto* c = create<CallStatement>(Call("f"));
+  EXPECT_TRUE(c->Is<CallStatement>());
+}
+
+TEST_F(CallStatementTest, Assert_Null_Call) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<CallStatement>(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(CallStatementTest, Assert_DifferentProgramID_Call) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CallStatement>(
+            b2.create<CallExpression>(b2.Expr("func"), ExpressionList{}));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/case_statement.cc b/src/tint/ast/case_statement.cc
new file mode 100644
index 0000000..98a2277
--- /dev/null
+++ b/src/tint/ast/case_statement.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/case_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::CaseStatement);
+
+namespace tint {
+namespace ast {
+
+CaseStatement::CaseStatement(ProgramID pid,
+                             const Source& src,
+                             CaseSelectorList s,
+                             const BlockStatement* b)
+    : Base(pid, src), selectors(s), body(b) {
+  TINT_ASSERT(AST, body);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+  for (auto* selector : selectors) {
+    TINT_ASSERT(AST, selector);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, selector, program_id);
+  }
+}
+
+CaseStatement::CaseStatement(CaseStatement&&) = default;
+
+CaseStatement::~CaseStatement() = default;
+
+const CaseStatement* CaseStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto sel = ctx->Clone(selectors);
+  auto* b = ctx->Clone(body);
+  return ctx->dst->create<CaseStatement>(src, sel, b);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/case_statement.h b/src/tint/ast/case_statement.h
new file mode 100644
index 0000000..d557728
--- /dev/null
+++ b/src/tint/ast/case_statement.h
@@ -0,0 +1,67 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_CASE_STATEMENT_H_
+#define SRC_TINT_AST_CASE_STATEMENT_H_
+
+#include <vector>
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/int_literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A list of case literals
+using CaseSelectorList = std::vector<const IntLiteralExpression*>;
+
+/// A case statement
+class CaseStatement : public Castable<CaseStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param selectors the case selectors
+  /// @param body the case body
+  CaseStatement(ProgramID pid,
+                const Source& src,
+                CaseSelectorList selectors,
+                const BlockStatement* body);
+  /// Move constructor
+  CaseStatement(CaseStatement&&);
+  ~CaseStatement() override;
+
+  /// @returns true if this is a default statement
+  bool IsDefault() const { return selectors.empty(); }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const CaseStatement* Clone(CloneContext* ctx) const override;
+
+  /// The case selectors, empty if none set
+  const CaseSelectorList selectors;
+
+  /// The case body
+  const BlockStatement* const body;
+};
+
+/// A list of case statements
+using CaseStatementList = std::vector<const CaseStatement*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_CASE_STATEMENT_H_
diff --git a/src/tint/ast/case_statement_test.cc b/src/tint/ast/case_statement_test.cc
new file mode 100644
index 0000000..b716f9f
--- /dev/null
+++ b/src/tint/ast/case_statement_test.cc
@@ -0,0 +1,137 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/case_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using CaseStatementTest = TestHelper;
+
+TEST_F(CaseStatementTest, Creation_i32) {
+  CaseSelectorList b;
+  auto* selector = create<SintLiteralExpression>(2);
+  b.push_back(selector);
+
+  auto* discard = create<DiscardStatement>();
+  auto* body = create<BlockStatement>(StatementList{discard});
+
+  auto* c = create<CaseStatement>(b, body);
+  ASSERT_EQ(c->selectors.size(), 1u);
+  EXPECT_EQ(c->selectors[0], selector);
+  ASSERT_EQ(c->body->statements.size(), 1u);
+  EXPECT_EQ(c->body->statements[0], discard);
+}
+
+TEST_F(CaseStatementTest, Creation_u32) {
+  CaseSelectorList b;
+  auto* selector = create<UintLiteralExpression>(2u);
+  b.push_back(selector);
+
+  auto* discard = create<DiscardStatement>();
+  auto* body = create<BlockStatement>(StatementList{discard});
+
+  auto* c = create<CaseStatement>(b, body);
+  ASSERT_EQ(c->selectors.size(), 1u);
+  EXPECT_EQ(c->selectors[0], selector);
+  ASSERT_EQ(c->body->statements.size(), 1u);
+  EXPECT_EQ(c->body->statements[0], discard);
+}
+
+TEST_F(CaseStatementTest, Creation_WithSource) {
+  CaseSelectorList b;
+  b.push_back(create<SintLiteralExpression>(2));
+
+  auto* body = create<BlockStatement>(StatementList{
+      create<DiscardStatement>(),
+  });
+  auto* c = create<CaseStatement>(Source{Source::Location{20, 2}}, b, body);
+  auto src = c->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(CaseStatementTest, IsDefault_WithoutSelectors) {
+  auto* body = create<BlockStatement>(StatementList{
+      create<DiscardStatement>(),
+  });
+  auto* c = create<CaseStatement>(CaseSelectorList{}, body);
+  EXPECT_TRUE(c->IsDefault());
+}
+
+TEST_F(CaseStatementTest, IsDefault_WithSelectors) {
+  CaseSelectorList b;
+  b.push_back(create<SintLiteralExpression>(2));
+
+  auto* c = create<CaseStatement>(b, create<BlockStatement>(StatementList{}));
+  EXPECT_FALSE(c->IsDefault());
+}
+
+TEST_F(CaseStatementTest, IsCase) {
+  auto* c = create<CaseStatement>(CaseSelectorList{},
+                                  create<BlockStatement>(StatementList{}));
+  EXPECT_TRUE(c->Is<CaseStatement>());
+}
+
+TEST_F(CaseStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<CaseStatement>(CaseSelectorList{}, nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(CaseStatementTest, Assert_Null_Selector) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<CaseStatement>(CaseSelectorList{nullptr},
+                                b.create<BlockStatement>(StatementList{}));
+      },
+      "internal compiler error");
+}
+
+TEST_F(CaseStatementTest, Assert_DifferentProgramID_Call) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CaseStatement>(CaseSelectorList{},
+                                 b2.create<BlockStatement>(StatementList{}));
+      },
+      "internal compiler error");
+}
+
+TEST_F(CaseStatementTest, Assert_DifferentProgramID_Selector) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<CaseStatement>(
+            CaseSelectorList{b2.create<SintLiteralExpression>(2)},
+            b1.create<BlockStatement>(StatementList{}));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/continue_statement.cc b/src/tint/ast/continue_statement.cc
new file mode 100644
index 0000000..764c912
--- /dev/null
+++ b/src/tint/ast/continue_statement.cc
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/continue_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ContinueStatement);
+
+namespace tint {
+namespace ast {
+
+ContinueStatement::ContinueStatement(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+ContinueStatement::ContinueStatement(ContinueStatement&&) = default;
+
+ContinueStatement::~ContinueStatement() = default;
+
+const ContinueStatement* ContinueStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<ContinueStatement>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/continue_statement.h b/src/tint/ast/continue_statement.h
new file mode 100644
index 0000000..707ae5b
--- /dev/null
+++ b/src/tint/ast/continue_statement.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_CONTINUE_STATEMENT_H_
+#define SRC_TINT_AST_CONTINUE_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// An continue statement
+class ContinueStatement : public Castable<ContinueStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  ContinueStatement(ProgramID pid, const Source& src);
+  /// Move constructor
+  ContinueStatement(ContinueStatement&&);
+  ~ContinueStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const ContinueStatement* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_CONTINUE_STATEMENT_H_
diff --git a/src/tint/ast/continue_statement_test.cc b/src/tint/ast/continue_statement_test.cc
new file mode 100644
index 0000000..8f78852
--- /dev/null
+++ b/src/tint/ast/continue_statement_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/continue_statement.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ContinueStatementTest = TestHelper;
+
+TEST_F(ContinueStatementTest, Creation_WithSource) {
+  auto* stmt = create<ContinueStatement>(Source{Source::Location{20, 2}});
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(ContinueStatementTest, IsContinue) {
+  auto* stmt = create<ContinueStatement>();
+  EXPECT_TRUE(stmt->Is<ContinueStatement>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/depth_multisampled_texture.cc b/src/tint/ast/depth_multisampled_texture.cc
new file mode 100644
index 0000000..bda191d
--- /dev/null
+++ b/src/tint/ast/depth_multisampled_texture.cc
@@ -0,0 +1,56 @@
+// 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/ast/depth_multisampled_texture.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthMultisampledTexture);
+
+namespace tint {
+namespace ast {
+namespace {
+
+bool IsValidDepthDimension(TextureDimension dim) {
+  return dim == TextureDimension::k2d;
+}
+
+}  // namespace
+
+DepthMultisampledTexture::DepthMultisampledTexture(ProgramID pid,
+                                                   const Source& src,
+                                                   TextureDimension d)
+    : Base(pid, src, d) {
+  TINT_ASSERT(AST, IsValidDepthDimension(dim));
+}
+
+DepthMultisampledTexture::DepthMultisampledTexture(DepthMultisampledTexture&&) =
+    default;
+
+DepthMultisampledTexture::~DepthMultisampledTexture() = default;
+
+std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_depth_multisampled_" << dim;
+  return out.str();
+}
+
+const DepthMultisampledTexture* DepthMultisampledTexture::Clone(
+    CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<DepthMultisampledTexture>(src, dim);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/depth_multisampled_texture.h b/src/tint/ast/depth_multisampled_texture.h
new file mode 100644
index 0000000..a22b52d
--- /dev/null
+++ b/src/tint/ast/depth_multisampled_texture.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
+#define SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A multisampled depth texture type.
+class DepthMultisampledTexture
+    : public Castable<DepthMultisampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  DepthMultisampledTexture(ProgramID pid,
+                           const Source& src,
+                           TextureDimension dim);
+  /// Move constructor
+  DepthMultisampledTexture(DepthMultisampledTexture&&);
+  ~DepthMultisampledTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const DepthMultisampledTexture* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/depth_multisampled_texture_test.cc b/src/tint/ast/depth_multisampled_texture_test.cc
new file mode 100644
index 0000000..540a667
--- /dev/null
+++ b/src/tint/ast/depth_multisampled_texture_test.cc
@@ -0,0 +1,37 @@
+// 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/ast/depth_multisampled_texture.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstDepthMultisampledTextureTest = TestHelper;
+
+TEST_F(AstDepthMultisampledTextureTest, Dim) {
+  auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d);
+  EXPECT_EQ(d->dim, TextureDimension::k2d);
+}
+
+TEST_F(AstDepthMultisampledTextureTest, FriendlyName) {
+  auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d);
+  EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_multisampled_2d");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/depth_texture.cc b/src/tint/ast/depth_texture.cc
new file mode 100644
index 0000000..5abfa76
--- /dev/null
+++ b/src/tint/ast/depth_texture.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/depth_texture.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthTexture);
+
+namespace tint {
+namespace ast {
+namespace {
+
+bool IsValidDepthDimension(TextureDimension dim) {
+  return dim == TextureDimension::k2d || dim == TextureDimension::k2dArray ||
+         dim == TextureDimension::kCube || dim == TextureDimension::kCubeArray;
+}
+
+}  // namespace
+
+DepthTexture::DepthTexture(ProgramID pid, const Source& src, TextureDimension d)
+    : Base(pid, src, d) {
+  TINT_ASSERT(AST, IsValidDepthDimension(dim));
+}
+
+DepthTexture::DepthTexture(DepthTexture&&) = default;
+
+DepthTexture::~DepthTexture() = default;
+
+std::string DepthTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_depth_" << dim;
+  return out.str();
+}
+
+const DepthTexture* DepthTexture::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<DepthTexture>(src, dim);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/depth_texture.h b/src/tint/ast/depth_texture.h
new file mode 100644
index 0000000..8efc5ed
--- /dev/null
+++ b/src/tint/ast/depth_texture.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_DEPTH_TEXTURE_H_
+#define SRC_TINT_AST_DEPTH_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A depth texture type.
+class DepthTexture : public Castable<DepthTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  DepthTexture(ProgramID pid, const Source& src, TextureDimension dim);
+  /// Move constructor
+  DepthTexture(DepthTexture&&);
+  ~DepthTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const DepthTexture* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_DEPTH_TEXTURE_H_
diff --git a/src/tint/ast/depth_texture_test.cc b/src/tint/ast/depth_texture_test.cc
new file mode 100644
index 0000000..2c4de5e
--- /dev/null
+++ b/src/tint/ast/depth_texture_test.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/depth_texture.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstDepthTextureTest = TestHelper;
+
+TEST_F(AstDepthTextureTest, IsTexture) {
+  Texture* ty = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_TRUE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstDepthTextureTest, Dim) {
+  auto* d = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_EQ(d->dim, TextureDimension::kCube);
+}
+
+TEST_F(AstDepthTextureTest, FriendlyName) {
+  auto* d = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_cube");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/disable_validation_attribute.cc b/src/tint/ast/disable_validation_attribute.cc
new file mode 100644
index 0000000..e474b8e
--- /dev/null
+++ b/src/tint/ast/disable_validation_attribute.cc
@@ -0,0 +1,57 @@
+// 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/ast/disable_validation_attribute.h"
+#include "src/tint/clone_context.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::DisableValidationAttribute);
+
+namespace tint {
+namespace ast {
+
+DisableValidationAttribute::DisableValidationAttribute(ProgramID pid,
+                                                       DisabledValidation val)
+    : Base(pid), validation(val) {}
+
+DisableValidationAttribute::~DisableValidationAttribute() = default;
+
+std::string DisableValidationAttribute::InternalName() const {
+  switch (validation) {
+    case DisabledValidation::kFunctionHasNoBody:
+      return "disable_validation__function_has_no_body";
+    case DisabledValidation::kBindingPointCollision:
+      return "disable_validation__binding_point_collision";
+    case DisabledValidation::kIgnoreStorageClass:
+      return "disable_validation__ignore_storage_class";
+    case DisabledValidation::kEntryPointParameter:
+      return "disable_validation__entry_point_parameter";
+    case DisabledValidation::kIgnoreConstructibleFunctionParameter:
+      return "disable_validation__ignore_constructible_function_parameter";
+    case DisabledValidation::kIgnoreStrideAttribute:
+      return "disable_validation__ignore_stride";
+    case DisabledValidation::kIgnoreInvalidPointerArgument:
+      return "disable_validation__ignore_invalid_pointer_argument";
+  }
+  return "<invalid>";
+}
+
+const DisableValidationAttribute* DisableValidationAttribute::Clone(
+    CloneContext* ctx) const {
+  return ctx->dst->ASTNodes().Create<DisableValidationAttribute>(ctx->dst->ID(),
+                                                                 validation);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/disable_validation_attribute.h b/src/tint/ast/disable_validation_attribute.h
new file mode 100644
index 0000000..ef75c62
--- /dev/null
+++ b/src/tint/ast/disable_validation_attribute.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
+#define SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/internal_attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// Enumerator of validation features that can be disabled with a
+/// DisableValidationAttribute attribute.
+enum class DisabledValidation {
+  /// When applied to a function, the validator will not complain there is no
+  /// body to a function.
+  kFunctionHasNoBody,
+  /// When applied to a module-scoped variable, the validator will not complain
+  /// if two resource variables have the same binding points.
+  kBindingPointCollision,
+  /// When applied to a variable, the validator will not complain about the
+  /// declared storage class.
+  kIgnoreStorageClass,
+  /// When applied to an entry-point function parameter, the validator will not
+  /// check for entry IO attributes.
+  kEntryPointParameter,
+  /// When applied to a function parameter, the validator will not
+  /// check if parameter type is constructible
+  kIgnoreConstructibleFunctionParameter,
+  /// When applied to a member attribute, a stride attribute may be applied to
+  /// non-array types.
+  kIgnoreStrideAttribute,
+  /// When applied to a pointer function parameter, the validator will not
+  /// require a function call argument passed for that parameter to have a
+  /// certain form.
+  kIgnoreInvalidPointerArgument,
+};
+
+/// An internal attribute used to tell the validator to ignore specific
+/// violations. Typically generated by transforms that need to produce ASTs that
+/// would otherwise cause validation errors.
+class DisableValidationAttribute
+    : public Castable<DisableValidationAttribute, InternalAttribute> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param validation the validation to disable
+  explicit DisableValidationAttribute(ProgramID program_id,
+                                      DisabledValidation validation);
+
+  /// Destructor
+  ~DisableValidationAttribute() override;
+
+  /// @return a short description of the internal attribute which will be
+  /// displayed in WGSL as `@internal(<name>)` (but is not parsable).
+  std::string InternalName() const override;
+
+  /// Performs a deep clone of this object using the CloneContext `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned object
+  const DisableValidationAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The validation that this attribute disables
+  const DisabledValidation validation;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
diff --git a/src/tint/ast/discard_statement.cc b/src/tint/ast/discard_statement.cc
new file mode 100644
index 0000000..a8f9cf6
--- /dev/null
+++ b/src/tint/ast/discard_statement.cc
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::DiscardStatement);
+
+namespace tint {
+namespace ast {
+
+DiscardStatement::DiscardStatement(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+DiscardStatement::DiscardStatement(DiscardStatement&&) = default;
+
+DiscardStatement::~DiscardStatement() = default;
+
+const DiscardStatement* DiscardStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<DiscardStatement>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/discard_statement.h b/src/tint/ast/discard_statement.h
new file mode 100644
index 0000000..a884594
--- /dev/null
+++ b/src/tint/ast/discard_statement.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_DISCARD_STATEMENT_H_
+#define SRC_TINT_AST_DISCARD_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// A discard statement
+class DiscardStatement : public Castable<DiscardStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  DiscardStatement(ProgramID pid, const Source& src);
+  /// Move constructor
+  DiscardStatement(DiscardStatement&&);
+  ~DiscardStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const DiscardStatement* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_DISCARD_STATEMENT_H_
diff --git a/src/tint/ast/discard_statement_test.cc b/src/tint/ast/discard_statement_test.cc
new file mode 100644
index 0000000..08a0cc3
--- /dev/null
+++ b/src/tint/ast/discard_statement_test.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using DiscardStatementTest = TestHelper;
+
+TEST_F(DiscardStatementTest, Creation) {
+  auto* stmt = create<DiscardStatement>();
+  EXPECT_EQ(stmt->source.range.begin.line, 0u);
+  EXPECT_EQ(stmt->source.range.begin.column, 0u);
+  EXPECT_EQ(stmt->source.range.end.line, 0u);
+  EXPECT_EQ(stmt->source.range.end.column, 0u);
+}
+
+TEST_F(DiscardStatementTest, Creation_WithSource) {
+  auto* stmt = create<DiscardStatement>(
+      Source{Source::Range{Source::Location{20, 2}, Source::Location{20, 5}}});
+  EXPECT_EQ(stmt->source.range.begin.line, 20u);
+  EXPECT_EQ(stmt->source.range.begin.column, 2u);
+  EXPECT_EQ(stmt->source.range.end.line, 20u);
+  EXPECT_EQ(stmt->source.range.end.column, 5u);
+}
+
+TEST_F(DiscardStatementTest, IsDiscard) {
+  auto* stmt = create<DiscardStatement>();
+  EXPECT_TRUE(stmt->Is<DiscardStatement>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/else_statement.cc b/src/tint/ast/else_statement.cc
new file mode 100644
index 0000000..62908b5
--- /dev/null
+++ b/src/tint/ast/else_statement.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/else_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ElseStatement);
+
+namespace tint {
+namespace ast {
+
+ElseStatement::ElseStatement(ProgramID pid,
+                             const Source& src,
+                             const Expression* cond,
+                             const BlockStatement* b)
+    : Base(pid, src), condition(cond), body(b) {
+  TINT_ASSERT(AST, body);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
+}
+
+ElseStatement::ElseStatement(ElseStatement&&) = default;
+
+ElseStatement::~ElseStatement() = default;
+
+const ElseStatement* ElseStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* cond = ctx->Clone(condition);
+  auto* b = ctx->Clone(body);
+  return ctx->dst->create<ElseStatement>(src, cond, b);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/else_statement.h b/src/tint/ast/else_statement.h
new file mode 100644
index 0000000..979b3b9
--- /dev/null
+++ b/src/tint/ast/else_statement.h
@@ -0,0 +1,61 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_ELSE_STATEMENT_H_
+#define SRC_TINT_AST_ELSE_STATEMENT_H_
+
+#include <vector>
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// An else statement
+class ElseStatement : public Castable<ElseStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param condition the else condition
+  /// @param body the else body
+  ElseStatement(ProgramID pid,
+                const Source& src,
+                const Expression* condition,
+                const BlockStatement* body);
+  /// Move constructor
+  ElseStatement(ElseStatement&&);
+  ~ElseStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const ElseStatement* Clone(CloneContext* ctx) const override;
+
+  /// The else condition or nullptr if none set
+  const Expression* const condition;
+
+  /// The else body
+  const BlockStatement* const body;
+};
+
+/// A list of else statements
+using ElseStatementList = std::vector<const ElseStatement*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ELSE_STATEMENT_H_
diff --git a/src/tint/ast/else_statement_test.cc b/src/tint/ast/else_statement_test.cc
new file mode 100644
index 0000000..27fe9ec
--- /dev/null
+++ b/src/tint/ast/else_statement_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ElseStatementTest = TestHelper;
+
+TEST_F(ElseStatementTest, Creation) {
+  auto* cond = Expr(true);
+  auto* body = create<BlockStatement>(StatementList{
+      create<DiscardStatement>(),
+  });
+  auto* discard = body->statements[0];
+
+  auto* e = create<ElseStatement>(cond, body);
+  EXPECT_EQ(e->condition, cond);
+  ASSERT_EQ(e->body->statements.size(), 1u);
+  EXPECT_EQ(e->body->statements[0], discard);
+}
+
+TEST_F(ElseStatementTest, Creation_WithSource) {
+  auto* e = create<ElseStatement>(Source{Source::Location{20, 2}}, Expr(true),
+                                  Block());
+  auto src = e->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(ElseStatementTest, IsElse) {
+  auto* e = create<ElseStatement>(nullptr, Block());
+  EXPECT_TRUE(e->Is<ElseStatement>());
+}
+
+TEST_F(ElseStatementTest, HasCondition) {
+  auto* cond = Expr(true);
+  auto* e = create<ElseStatement>(cond, Block());
+  EXPECT_TRUE(e->condition);
+}
+
+TEST_F(ElseStatementTest, HasContition_NullCondition) {
+  auto* e = create<ElseStatement>(nullptr, Block());
+  EXPECT_FALSE(e->condition);
+}
+
+TEST_F(ElseStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<ElseStatement>(b.Expr(true), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ElseStatementTest, Assert_DifferentProgramID_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<ElseStatement>(b2.Expr(true), b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ElseStatementTest, Assert_DifferentProgramID_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<ElseStatement>(b1.Expr(true), b2.Block());
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/expression.cc b/src/tint/ast/expression.cc
new file mode 100644
index 0000000..a432326
--- /dev/null
+++ b/src/tint/ast/expression.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/expression.h"
+
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/info.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Expression);
+
+namespace tint {
+namespace ast {
+
+Expression::Expression(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Expression::Expression(Expression&&) = default;
+
+Expression::~Expression() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/expression.h b/src/tint/ast/expression.h
new file mode 100644
index 0000000..15bdacc
--- /dev/null
+++ b/src/tint/ast/expression.h
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_EXPRESSION_H_
+#define SRC_TINT_AST_EXPRESSION_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/node.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace ast {
+
+/// Base expression class
+class Expression : public Castable<Expression, Node> {
+ public:
+  ~Expression() override;
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Expression(ProgramID pid, const Source& src);
+  /// Move constructor
+  Expression(Expression&&);
+};
+
+/// A list of expressions
+using ExpressionList = std::vector<const Expression*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_EXPRESSION_H_
diff --git a/src/tint/ast/external_texture.cc b/src/tint/ast/external_texture.cc
new file mode 100644
index 0000000..a5a703d
--- /dev/null
+++ b/src/tint/ast/external_texture.cc
@@ -0,0 +1,41 @@
+// 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/ast/external_texture.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ExternalTexture);
+
+namespace tint {
+namespace ast {
+
+// ExternalTexture::ExternalTexture() : Base(ast::TextureDimension::k2d) {}
+ExternalTexture::ExternalTexture(ProgramID pid, const Source& src)
+    : Base(pid, src, ast::TextureDimension::k2d) {}
+
+ExternalTexture::ExternalTexture(ExternalTexture&&) = default;
+
+ExternalTexture::~ExternalTexture() = default;
+
+std::string ExternalTexture::FriendlyName(const SymbolTable&) const {
+  return "texture_external";
+}
+
+const ExternalTexture* ExternalTexture::Clone(CloneContext* ctx) const {
+  return ctx->dst->create<ExternalTexture>();
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/external_texture.h b/src/tint/ast/external_texture.h
new file mode 100644
index 0000000..6fc638d
--- /dev/null
+++ b/src/tint/ast/external_texture.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_EXTERNAL_TEXTURE_H_
+#define SRC_TINT_AST_EXTERNAL_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// An external texture type
+class ExternalTexture : public Castable<ExternalTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  ExternalTexture(ProgramID pid, const Source& src);
+
+  /// Move constructor
+  ExternalTexture(ExternalTexture&&);
+  ~ExternalTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const ExternalTexture* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_EXTERNAL_TEXTURE_H_
diff --git a/src/tint/ast/external_texture_test.cc b/src/tint/ast/external_texture_test.cc
new file mode 100644
index 0000000..af25ac2
--- /dev/null
+++ b/src/tint/ast/external_texture_test.cc
@@ -0,0 +1,46 @@
+// 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/ast/external_texture.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstExternalTextureTest = TestHelper;
+
+TEST_F(AstExternalTextureTest, IsTexture) {
+  Texture* ty = create<ExternalTexture>();
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<ExternalTexture>());
+  EXPECT_FALSE(ty->Is<MultisampledTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstExternalTextureTest, Dim) {
+  auto* ty = create<ExternalTexture>();
+  EXPECT_EQ(ty->dim, ast::TextureDimension::k2d);
+}
+
+TEST_F(AstExternalTextureTest, FriendlyName) {
+  auto* ty = create<ExternalTexture>();
+  EXPECT_EQ(ty->FriendlyName(Symbols()), "texture_external");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/f32.cc b/src/tint/ast/f32.cc
new file mode 100644
index 0000000..319822c
--- /dev/null
+++ b/src/tint/ast/f32.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/f32.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::F32);
+
+namespace tint {
+namespace ast {
+
+F32::F32(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+F32::F32(F32&&) = default;
+
+F32::~F32() = default;
+
+std::string F32::FriendlyName(const SymbolTable&) const {
+  return "f32";
+}
+
+const F32* F32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<F32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/f32.h b/src/tint/ast/f32.h
new file mode 100644
index 0000000..92c7f51
--- /dev/null
+++ b/src/tint/ast/f32.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_F32_H_
+#define SRC_TINT_AST_F32_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A float 32 type
+class F32 : public Castable<F32, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  F32(ProgramID pid, const Source& src);
+  /// Move constructor
+  F32(F32&&);
+  ~F32() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const F32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_F32_H_
diff --git a/src/tint/ast/f32_test.cc b/src/tint/ast/f32_test.cc
new file mode 100644
index 0000000..ec6d62e
--- /dev/null
+++ b/src/tint/ast/f32_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/f32.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstF32Test = TestHelper;
+
+TEST_F(AstF32Test, FriendlyName) {
+  auto* f = create<F32>();
+  EXPECT_EQ(f->FriendlyName(Symbols()), "f32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/fallthrough_statement.cc b/src/tint/ast/fallthrough_statement.cc
new file mode 100644
index 0000000..463d2ad
--- /dev/null
+++ b/src/tint/ast/fallthrough_statement.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::FallthroughStatement);
+
+namespace tint {
+namespace ast {
+
+FallthroughStatement::FallthroughStatement(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+FallthroughStatement::FallthroughStatement(FallthroughStatement&&) = default;
+
+FallthroughStatement::~FallthroughStatement() = default;
+
+const FallthroughStatement* FallthroughStatement::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<FallthroughStatement>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/fallthrough_statement.h b/src/tint/ast/fallthrough_statement.h
new file mode 100644
index 0000000..d9501dd
--- /dev/null
+++ b/src/tint/ast/fallthrough_statement.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
+#define SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// An fallthrough statement
+class FallthroughStatement : public Castable<FallthroughStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  FallthroughStatement(ProgramID pid, const Source& src);
+  /// Move constructor
+  FallthroughStatement(FallthroughStatement&&);
+  ~FallthroughStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const FallthroughStatement* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
diff --git a/src/tint/ast/fallthrough_statement_test.cc b/src/tint/ast/fallthrough_statement_test.cc
new file mode 100644
index 0000000..5adda3d
--- /dev/null
+++ b/src/tint/ast/fallthrough_statement_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using FallthroughStatementTest = TestHelper;
+
+TEST_F(FallthroughStatementTest, Creation) {
+  auto* stmt = create<FallthroughStatement>();
+  EXPECT_EQ(stmt->source.range.begin.line, 0u);
+  EXPECT_EQ(stmt->source.range.begin.column, 0u);
+  EXPECT_EQ(stmt->source.range.end.line, 0u);
+  EXPECT_EQ(stmt->source.range.end.column, 0u);
+}
+
+TEST_F(FallthroughStatementTest, Creation_WithSource) {
+  auto* stmt = create<FallthroughStatement>(Source{Source::Location{20, 2}});
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(FallthroughStatementTest, IsFallthrough) {
+  auto* stmt = create<FallthroughStatement>();
+  EXPECT_TRUE(stmt->Is<FallthroughStatement>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/float_literal_expression.cc b/src/tint/ast/float_literal_expression.cc
new file mode 100644
index 0000000..b505f67
--- /dev/null
+++ b/src/tint/ast/float_literal_expression.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/float_literal_expression.h"
+
+#include <limits>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::FloatLiteralExpression);
+
+namespace tint {
+namespace ast {
+
+FloatLiteralExpression::FloatLiteralExpression(ProgramID pid,
+                                               const Source& src,
+                                               float val)
+    : Base(pid, src), value(val) {}
+
+FloatLiteralExpression::~FloatLiteralExpression() = default;
+
+const FloatLiteralExpression* FloatLiteralExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<FloatLiteralExpression>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/float_literal_expression.h b/src/tint/ast/float_literal_expression.h
new file mode 100644
index 0000000..e367389
--- /dev/null
+++ b/src/tint/ast/float_literal_expression.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_
+
+#include <string>
+
+#include "src/tint/ast/literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A float literal
+class FloatLiteralExpression
+    : public Castable<FloatLiteralExpression, LiteralExpression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the float literals value
+  FloatLiteralExpression(ProgramID pid, const Source& src, float value);
+  ~FloatLiteralExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const FloatLiteralExpression* Clone(CloneContext* ctx) const override;
+
+  /// The float literal value
+  const float value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/float_literal_expression_test.cc b/src/tint/ast/float_literal_expression_test.cc
new file mode 100644
index 0000000..7a91da9
--- /dev/null
+++ b/src/tint/ast/float_literal_expression_test.cc
@@ -0,0 +1,31 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using FloatLiteralExpressionTest = TestHelper;
+
+TEST_F(FloatLiteralExpressionTest, Value) {
+  auto* f = create<FloatLiteralExpression>(47.2f);
+  ASSERT_TRUE(f->Is<FloatLiteralExpression>());
+  EXPECT_EQ(f->value, 47.2f);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/for_loop_statement.cc b/src/tint/ast/for_loop_statement.cc
new file mode 100644
index 0000000..7bb9389
--- /dev/null
+++ b/src/tint/ast/for_loop_statement.cc
@@ -0,0 +1,59 @@
+// 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/ast/for_loop_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ForLoopStatement);
+
+namespace tint {
+namespace ast {
+
+ForLoopStatement::ForLoopStatement(ProgramID pid,
+                                   const Source& src,
+                                   const Statement* init,
+                                   const Expression* cond,
+                                   const Statement* cont,
+                                   const BlockStatement* b)
+    : Base(pid, src),
+      initializer(init),
+      condition(cond),
+      continuing(cont),
+      body(b) {
+  TINT_ASSERT(AST, body);
+
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+}
+
+ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default;
+
+ForLoopStatement::~ForLoopStatement() = default;
+
+const ForLoopStatement* ForLoopStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+
+  auto* init = ctx->Clone(initializer);
+  auto* cond = ctx->Clone(condition);
+  auto* cont = ctx->Clone(continuing);
+  auto* b = ctx->Clone(body);
+  return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/for_loop_statement.h b/src/tint/ast/for_loop_statement.h
new file mode 100644
index 0000000..8d15d6c
--- /dev/null
+++ b/src/tint/ast/for_loop_statement.h
@@ -0,0 +1,67 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_FOR_LOOP_STATEMENT_H_
+#define SRC_TINT_AST_FOR_LOOP_STATEMENT_H_
+
+#include "src/tint/ast/block_statement.h"
+
+namespace tint {
+namespace ast {
+
+class Expression;
+
+/// A for loop statement
+class ForLoopStatement : public Castable<ForLoopStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the for loop statement source
+  /// @param initializer the optional loop initializer statement
+  /// @param condition the optional loop condition expression
+  /// @param continuing the optional continuing statement
+  /// @param body the loop body
+  ForLoopStatement(ProgramID program_id,
+                   Source const& source,
+                   const Statement* initializer,
+                   const Expression* condition,
+                   const Statement* continuing,
+                   const BlockStatement* body);
+  /// Move constructor
+  ForLoopStatement(ForLoopStatement&&);
+  ~ForLoopStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const ForLoopStatement* Clone(CloneContext* ctx) const override;
+
+  /// The initializer statement
+  const Statement* const initializer;
+
+  /// The condition expression
+  const Expression* const condition;
+
+  /// The continuing statement
+  const Statement* const continuing;
+
+  /// The loop body block
+  const BlockStatement* const body;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_FOR_LOOP_STATEMENT_H_
diff --git a/src/tint/ast/for_loop_statement_test.cc b/src/tint/ast/for_loop_statement_test.cc
new file mode 100644
index 0000000..3e5e585
--- /dev/null
+++ b/src/tint/ast/for_loop_statement_test.cc
@@ -0,0 +1,104 @@
+// 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ForLoopStatementTest = TestHelper;
+
+TEST_F(ForLoopStatementTest, Creation) {
+  auto* init = Decl(Var("i", ty.u32()));
+  auto* cond =
+      create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5u));
+  auto* cont = Assign("i", Add("i", 1));
+  auto* body = Block(Return());
+  auto* l = For(init, cond, cont, body);
+
+  EXPECT_EQ(l->initializer, init);
+  EXPECT_EQ(l->condition, cond);
+  EXPECT_EQ(l->continuing, cont);
+  EXPECT_EQ(l->body, body);
+}
+
+TEST_F(ForLoopStatementTest, Creation_WithSource) {
+  auto* body = Block(Return());
+  auto* l = For(Source{{20u, 2u}}, nullptr, nullptr, nullptr, body);
+  auto src = l->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(ForLoopStatementTest, Creation_Null_InitCondCont) {
+  auto* body = Block(Return());
+  auto* l = For(nullptr, nullptr, nullptr, body);
+  EXPECT_EQ(l->body, body);
+}
+
+TEST_F(ForLoopStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.For(nullptr, nullptr, nullptr, nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Initializer) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(b2.Block(), nullptr, nullptr, b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, b2.Expr(true), nullptr, b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Continuing) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, nullptr, b2.Block(), b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, nullptr, nullptr, b2.Block());
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/function.cc b/src/tint/ast/function.cc
new file mode 100644
index 0000000..cba8091
--- /dev/null
+++ b/src/tint/ast/function.cc
@@ -0,0 +1,108 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/function.h"
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Function);
+
+namespace tint {
+namespace ast {
+
+Function::Function(ProgramID pid,
+                   const Source& src,
+                   Symbol sym,
+                   VariableList parameters,
+                   const Type* return_ty,
+                   const BlockStatement* b,
+                   AttributeList attrs,
+                   AttributeList return_type_attrs)
+    : Base(pid, src),
+      symbol(sym),
+      params(std::move(parameters)),
+      return_type(return_ty),
+      body(b),
+      attributes(std::move(attrs)),
+      return_type_attributes(std::move(return_type_attrs)) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+  for (auto* param : params) {
+    TINT_ASSERT(AST, param && param->is_const);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, param, program_id);
+  }
+  TINT_ASSERT(AST, symbol.IsValid());
+  TINT_ASSERT(AST, return_type);
+  for (auto* attr : attributes) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+  }
+  for (auto* attr : return_type_attributes) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+  }
+}
+
+Function::Function(Function&&) = default;
+
+Function::~Function() = default;
+
+PipelineStage Function::PipelineStage() const {
+  if (auto* stage = GetAttribute<StageAttribute>(attributes)) {
+    return stage->stage;
+  }
+  return PipelineStage::kNone;
+}
+
+const Function* Function::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto sym = ctx->Clone(symbol);
+  auto p = ctx->Clone(params);
+  auto* ret = ctx->Clone(return_type);
+  auto* b = ctx->Clone(body);
+  auto attrs = ctx->Clone(attributes);
+  auto ret_attrs = ctx->Clone(return_type_attributes);
+  return ctx->dst->create<Function>(src, sym, p, ret, b, attrs, ret_attrs);
+}
+
+const Function* FunctionList::Find(Symbol sym) const {
+  for (auto* func : *this) {
+    if (func->symbol == sym) {
+      return func;
+    }
+  }
+  return nullptr;
+}
+
+const Function* FunctionList::Find(Symbol sym, PipelineStage stage) const {
+  for (auto* func : *this) {
+    if (func->symbol == sym && func->PipelineStage() == stage) {
+      return func;
+    }
+  }
+  return nullptr;
+}
+
+bool FunctionList::HasStage(ast::PipelineStage stage) const {
+  for (auto* func : *this) {
+    if (func->PipelineStage() == stage) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/function.h b/src/tint/ast/function.h
new file mode 100644
index 0000000..fd48e3f
--- /dev/null
+++ b/src/tint/ast/function.h
@@ -0,0 +1,118 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_FUNCTION_H_
+#define SRC_TINT_AST_FUNCTION_H_
+
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/binding_attribute.h"
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/builtin_attribute.h"
+#include "src/tint/ast/group_attribute.h"
+#include "src/tint/ast/location_attribute.h"
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/ast/variable.h"
+
+namespace tint {
+namespace ast {
+
+/// A Function statement.
+class Function : public Castable<Function, Node> {
+ public:
+  /// Create a function
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the variable source
+  /// @param symbol the function symbol
+  /// @param params the function parameters
+  /// @param return_type the return type
+  /// @param body the function body
+  /// @param attributes the function attributes
+  /// @param return_type_attributes the return type attributes
+  Function(ProgramID program_id,
+           const Source& source,
+           Symbol symbol,
+           VariableList params,
+           const Type* return_type,
+           const BlockStatement* body,
+           AttributeList attributes,
+           AttributeList return_type_attributes);
+  /// Move constructor
+  Function(Function&&);
+
+  ~Function() override;
+
+  /// @returns the functions pipeline stage or None if not set
+  ast::PipelineStage PipelineStage() const;
+
+  /// @returns true if this function is an entry point
+  bool IsEntryPoint() const { return PipelineStage() != PipelineStage::kNone; }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const Function* Clone(CloneContext* ctx) const override;
+
+  /// The function symbol
+  const Symbol symbol;
+
+  /// The function params
+  const VariableList params;
+
+  /// The function return type
+  const Type* const return_type;
+
+  /// The function body
+  const BlockStatement* const body;
+
+  /// The attributes attached to this function
+  const AttributeList attributes;
+
+  /// The attributes attached to the function return type.
+  const AttributeList return_type_attributes;
+};
+
+/// A list of functions
+class FunctionList : public std::vector<const Function*> {
+ public:
+  /// Appends f to the end of the list
+  /// @param f the function to append to this list
+  void Add(const Function* f) { this->emplace_back(f); }
+
+  /// Returns the function with the given name
+  /// @param sym the function symbol to search for
+  /// @returns the associated function or nullptr if none exists
+  const Function* Find(Symbol sym) const;
+
+  /// Returns the function with the given name
+  /// @param sym the function symbol to search for
+  /// @param stage the pipeline stage
+  /// @returns the associated function or nullptr if none exists
+  const Function* Find(Symbol sym, PipelineStage stage) const;
+
+  /// @param stage the pipeline stage
+  /// @returns true if the Builder contains an entrypoint function with
+  /// the given stage
+  bool HasStage(PipelineStage stage) const;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_FUNCTION_H_
diff --git a/src/tint/ast/function_test.cc b/src/tint/ast/function_test.cc
new file mode 100644
index 0000000..d4077bd
--- /dev/null
+++ b/src/tint/ast/function_test.cc
@@ -0,0 +1,196 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/workgroup_attribute.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using FunctionTest = TestHelper;
+
+TEST_F(FunctionTest, Creation) {
+  VariableList params;
+  params.push_back(Param("var", ty.i32()));
+  auto* var = params[0];
+
+  auto* f = Func("func", params, ty.void_(), StatementList{}, AttributeList{});
+  EXPECT_EQ(f->symbol, Symbols().Get("func"));
+  ASSERT_EQ(f->params.size(), 1u);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+  EXPECT_EQ(f->params[0], var);
+}
+
+TEST_F(FunctionTest, Creation_WithSource) {
+  VariableList params;
+  params.push_back(Param("var", ty.i32()));
+
+  auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_(),
+                 StatementList{}, AttributeList{});
+  auto src = f->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(FunctionTest, Assert_InvalidName) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Func("", VariableList{}, b.ty.void_(), StatementList{},
+               AttributeList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_Null_ReturnType) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Func("f", VariableList{}, nullptr, StatementList{}, AttributeList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_Null_Param) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        VariableList params;
+        params.push_back(b.Param("var", b.ty.i32()));
+        params.push_back(nullptr);
+
+        b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_DifferentProgramID_Symbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Func(b2.Sym("func"), VariableList{}, b1.ty.void_(), StatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_DifferentProgramID_Param) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Func("func", VariableList{b2.Param("var", b2.ty.i32())},
+                b1.ty.void_(), StatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_DifferentProgramID_Attr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{},
+                AttributeList{
+                    b2.WorkgroupSize(2, 4, 6),
+                });
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_DifferentProgramID_ReturnAttr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{},
+                AttributeList{},
+                AttributeList{
+                    b2.WorkgroupSize(2, 4, 6),
+                });
+      },
+      "internal compiler error");
+}
+
+TEST_F(FunctionTest, Assert_NonConstParam) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        VariableList params;
+        params.push_back(b.Var("var", b.ty.i32(), ast::StorageClass::kNone));
+
+        b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{});
+      },
+      "internal compiler error");
+}
+
+using FunctionListTest = TestHelper;
+
+TEST_F(FunctionListTest, FindSymbol) {
+  auto* func = Func("main", VariableList{}, ty.f32(), StatementList{},
+                    ast::AttributeList{});
+  FunctionList list;
+  list.Add(func);
+  EXPECT_EQ(func, list.Find(Symbols().Register("main")));
+}
+
+TEST_F(FunctionListTest, FindSymbolMissing) {
+  FunctionList list;
+  EXPECT_EQ(nullptr, list.Find(Symbols().Register("Missing")));
+}
+
+TEST_F(FunctionListTest, FindSymbolStage) {
+  auto* fs = Func("main", VariableList{}, ty.f32(), StatementList{},
+                  ast::AttributeList{
+                      Stage(PipelineStage::kFragment),
+                  });
+  auto* vs = Func("main", VariableList{}, ty.f32(), StatementList{},
+                  ast::AttributeList{
+                      Stage(PipelineStage::kVertex),
+                  });
+  FunctionList list;
+  list.Add(fs);
+  list.Add(vs);
+  EXPECT_EQ(fs,
+            list.Find(Symbols().Register("main"), PipelineStage::kFragment));
+  EXPECT_EQ(vs, list.Find(Symbols().Register("main"), PipelineStage::kVertex));
+}
+
+TEST_F(FunctionListTest, FindSymbolStageMissing) {
+  FunctionList list;
+  list.Add(Func("main", VariableList{}, ty.f32(), StatementList{},
+                ast::AttributeList{
+                    Stage(PipelineStage::kFragment),
+                }));
+  EXPECT_EQ(nullptr,
+            list.Find(Symbols().Register("main"), PipelineStage::kVertex));
+}
+
+TEST_F(FunctionListTest, HasStage) {
+  FunctionList list;
+  list.Add(Func("main", VariableList{}, ty.f32(), StatementList{},
+                ast::AttributeList{
+                    Stage(PipelineStage::kFragment),
+                }));
+  EXPECT_TRUE(list.HasStage(PipelineStage::kFragment));
+  EXPECT_FALSE(list.HasStage(PipelineStage::kVertex));
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/group_attribute.cc b/src/tint/ast/group_attribute.cc
new file mode 100644
index 0000000..58714e8
--- /dev/null
+++ b/src/tint/ast/group_attribute.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/group_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::GroupAttribute);
+
+namespace tint {
+namespace ast {
+
+GroupAttribute::GroupAttribute(ProgramID pid, const Source& src, uint32_t val)
+    : Base(pid, src), value(val) {}
+
+GroupAttribute::~GroupAttribute() = default;
+
+std::string GroupAttribute::Name() const {
+  return "group";
+}
+
+const GroupAttribute* GroupAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<GroupAttribute>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/group_attribute.h b/src/tint/ast/group_attribute.h
new file mode 100644
index 0000000..31d5007
--- /dev/null
+++ b/src/tint/ast/group_attribute.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_GROUP_ATTRIBUTE_H_
+#define SRC_TINT_AST_GROUP_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A group attribute
+class GroupAttribute : public Castable<GroupAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the group value
+  GroupAttribute(ProgramID pid, const Source& src, uint32_t value);
+  ~GroupAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const GroupAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The group value
+  const uint32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_GROUP_ATTRIBUTE_H_
diff --git a/src/tint/ast/group_attribute_test.cc b/src/tint/ast/group_attribute_test.cc
new file mode 100644
index 0000000..fd7c18b
--- /dev/null
+++ b/src/tint/ast/group_attribute_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using GroupAttributeTest = TestHelper;
+
+TEST_F(GroupAttributeTest, Creation) {
+  auto* d = create<GroupAttribute>(2);
+  EXPECT_EQ(2u, d->value);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/i32.cc b/src/tint/ast/i32.cc
new file mode 100644
index 0000000..83e4524
--- /dev/null
+++ b/src/tint/ast/i32.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/i32.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::I32);
+
+namespace tint {
+namespace ast {
+
+I32::I32(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+I32::I32(I32&&) = default;
+
+I32::~I32() = default;
+
+std::string I32::FriendlyName(const SymbolTable&) const {
+  return "i32";
+}
+
+const I32* I32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<I32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/i32.h b/src/tint/ast/i32.h
new file mode 100644
index 0000000..22c7e1b
--- /dev/null
+++ b/src/tint/ast/i32.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_I32_H_
+#define SRC_TINT_AST_I32_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A signed int 32 type.
+class I32 : public Castable<I32, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  I32(ProgramID pid, const Source& src);
+  /// Move constructor
+  I32(I32&&);
+  ~I32() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const I32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_I32_H_
diff --git a/src/tint/ast/i32_test.cc b/src/tint/ast/i32_test.cc
new file mode 100644
index 0000000..7e1c265
--- /dev/null
+++ b/src/tint/ast/i32_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/i32.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstI32Test = TestHelper;
+
+TEST_F(AstI32Test, FriendlyName) {
+  auto* i = create<I32>();
+  EXPECT_EQ(i->FriendlyName(Symbols()), "i32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/id_attribute.cc b/src/tint/ast/id_attribute.cc
new file mode 100644
index 0000000..b94b450
--- /dev/null
+++ b/src/tint/ast/id_attribute.cc
@@ -0,0 +1,42 @@
+// 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/ast/id_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IdAttribute);
+
+namespace tint {
+namespace ast {
+
+IdAttribute::IdAttribute(ProgramID pid, const Source& src, uint32_t val)
+    : Base(pid, src), value(val) {}
+
+IdAttribute::~IdAttribute() = default;
+
+std::string IdAttribute::Name() const {
+  return "id";
+}
+
+const IdAttribute* IdAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<IdAttribute>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/id_attribute.h b/src/tint/ast/id_attribute.h
new file mode 100644
index 0000000..d4ede06
--- /dev/null
+++ b/src/tint/ast/id_attribute.h
@@ -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.
+
+#ifndef SRC_TINT_AST_ID_ATTRIBUTE_H_
+#define SRC_TINT_AST_ID_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// An id attribute for pipeline-overridable constants
+class IdAttribute : public Castable<IdAttribute, Attribute> {
+ public:
+  /// Create an id attribute.
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param val the numeric id value
+  IdAttribute(ProgramID pid, const Source& src, uint32_t val);
+  ~IdAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const IdAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The id value
+  const uint32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_ID_ATTRIBUTE_H_
diff --git a/src/tint/ast/id_attribute_test.cc b/src/tint/ast/id_attribute_test.cc
new file mode 100644
index 0000000..c3c372b
--- /dev/null
+++ b/src/tint/ast/id_attribute_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/id_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IdAttributeTest = TestHelper;
+
+TEST_F(IdAttributeTest, Creation) {
+  auto* d = create<IdAttribute>(12);
+  EXPECT_EQ(12u, d->value);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/identifier_expression.cc b/src/tint/ast/identifier_expression.cc
new file mode 100644
index 0000000..1f73a45
--- /dev/null
+++ b/src/tint/ast/identifier_expression.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/identifier_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IdentifierExpression);
+
+namespace tint {
+namespace ast {
+
+IdentifierExpression::IdentifierExpression(ProgramID pid,
+                                           const Source& src,
+                                           Symbol sym)
+    : Base(pid, src), symbol(sym) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
+  TINT_ASSERT(AST, symbol.IsValid());
+}
+
+IdentifierExpression::IdentifierExpression(IdentifierExpression&&) = default;
+
+IdentifierExpression::~IdentifierExpression() = default;
+
+const IdentifierExpression* IdentifierExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto sym = ctx->Clone(symbol);
+  return ctx->dst->create<IdentifierExpression>(src, sym);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/identifier_expression.h b/src/tint/ast/identifier_expression.h
new file mode 100644
index 0000000..58d0140
--- /dev/null
+++ b/src/tint/ast/identifier_expression.h
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_
+#define SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// An identifier expression
+class IdentifierExpression : public Castable<IdentifierExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param sym the symbol for the identifier
+  IdentifierExpression(ProgramID pid, const Source& src, Symbol sym);
+  /// Move constructor
+  IdentifierExpression(IdentifierExpression&&);
+  ~IdentifierExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const IdentifierExpression* Clone(CloneContext* ctx) const override;
+
+  /// The symbol for the identifier
+  const Symbol symbol;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_
diff --git a/src/tint/ast/identifier_expression_test.cc b/src/tint/ast/identifier_expression_test.cc
new file mode 100644
index 0000000..b8c3a2a
--- /dev/null
+++ b/src/tint/ast/identifier_expression_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IdentifierExpressionTest = TestHelper;
+
+TEST_F(IdentifierExpressionTest, Creation) {
+  auto* i = Expr("ident");
+  EXPECT_EQ(i->symbol, Symbol(1, ID()));
+}
+
+TEST_F(IdentifierExpressionTest, Creation_WithSource) {
+  auto* i = Expr(Source{Source::Location{20, 2}}, "ident");
+  EXPECT_EQ(i->symbol, Symbol(1, ID()));
+
+  auto src = i->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(IdentifierExpressionTest, IsIdentifier) {
+  auto* i = Expr("ident");
+  EXPECT_TRUE(i->Is<IdentifierExpression>());
+}
+
+TEST_F(IdentifierExpressionTest, Assert_InvalidSymbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Expr("");
+      },
+      "internal compiler error");
+}
+
+TEST_F(IdentifierExpressionTest, Assert_DifferentProgramID_Symbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Expr(b2.Sym("b2"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/if_statement.cc b/src/tint/ast/if_statement.cc
new file mode 100644
index 0000000..276d5d2
--- /dev/null
+++ b/src/tint/ast/if_statement.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/if_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IfStatement);
+
+namespace tint {
+namespace ast {
+
+IfStatement::IfStatement(ProgramID pid,
+                         const Source& src,
+                         const Expression* cond,
+                         const BlockStatement* b,
+                         ElseStatementList else_stmts)
+    : Base(pid, src),
+      condition(cond),
+      body(b),
+      else_statements(std::move(else_stmts)) {
+  TINT_ASSERT(AST, condition);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
+  TINT_ASSERT(AST, body);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+  for (auto* el : else_statements) {
+    TINT_ASSERT(AST, el);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, el, program_id);
+  }
+}
+
+IfStatement::IfStatement(IfStatement&&) = default;
+
+IfStatement::~IfStatement() = default;
+
+const IfStatement* IfStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* cond = ctx->Clone(condition);
+  auto* b = ctx->Clone(body);
+  auto el = ctx->Clone(else_statements);
+  return ctx->dst->create<IfStatement>(src, cond, b, el);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/if_statement.h b/src/tint/ast/if_statement.h
new file mode 100644
index 0000000..3afe172
--- /dev/null
+++ b/src/tint/ast/if_statement.h
@@ -0,0 +1,62 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_IF_STATEMENT_H_
+#define SRC_TINT_AST_IF_STATEMENT_H_
+
+#include <utility>
+
+#include "src/tint/ast/else_statement.h"
+
+namespace tint {
+namespace ast {
+
+/// An if statement
+class IfStatement : public Castable<IfStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param condition the if condition
+  /// @param body the if body
+  /// @param else_stmts the else statements
+  IfStatement(ProgramID pid,
+              const Source& src,
+              const Expression* condition,
+              const BlockStatement* body,
+              ElseStatementList else_stmts);
+  /// Move constructor
+  IfStatement(IfStatement&&);
+  ~IfStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const IfStatement* Clone(CloneContext* ctx) const override;
+
+  /// The if condition or nullptr if none set
+  const Expression* const condition;
+
+  /// The if body
+  const BlockStatement* const body;
+
+  /// The else statements
+  const ElseStatementList else_statements;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_IF_STATEMENT_H_
diff --git a/src/tint/ast/if_statement_test.cc b/src/tint/ast/if_statement_test.cc
new file mode 100644
index 0000000..6090eca
--- /dev/null
+++ b/src/tint/ast/if_statement_test.cc
@@ -0,0 +1,106 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/if_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IfStatementTest = TestHelper;
+
+TEST_F(IfStatementTest, Creation) {
+  auto* cond = Expr("cond");
+  auto* stmt = create<IfStatement>(Source{Source::Location{20, 2}}, cond,
+                                   Block(create<DiscardStatement>()),
+                                   ElseStatementList{});
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(IfStatementTest, IsIf) {
+  auto* stmt = create<IfStatement>(Expr(true), Block(), ElseStatementList{});
+  EXPECT_TRUE(stmt->Is<IfStatement>());
+}
+
+TEST_F(IfStatementTest, Assert_Null_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<IfStatement>(nullptr, b.Block(), ElseStatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(IfStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<IfStatement>(b.Expr(true), nullptr, ElseStatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(IfStatementTest, Assert_Null_ElseStatement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        auto* body = b.create<BlockStatement>(StatementList{});
+        b.create<IfStatement>(b.Expr(true), body, ElseStatementList{nullptr});
+      },
+      "internal compiler error");
+}
+
+TEST_F(IfStatementTest, Assert_DifferentProgramID_Cond) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<IfStatement>(b2.Expr(true), b1.Block(), ElseStatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(IfStatementTest, Assert_DifferentProgramID_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<IfStatement>(b1.Expr(true), b2.Block(), ElseStatementList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(IfStatementTest, Assert_DifferentProgramID_ElseStatement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<IfStatement>(
+            b1.Expr(true), b1.Block(),
+            ElseStatementList{
+                b2.create<ElseStatement>(b2.Expr("ident"), b2.Block()),
+            });
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/index_accessor_expression.cc b/src/tint/ast/index_accessor_expression.cc
new file mode 100644
index 0000000..afd73fd
--- /dev/null
+++ b/src/tint/ast/index_accessor_expression.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/index_accessor_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IndexAccessorExpression);
+
+namespace tint {
+namespace ast {
+
+IndexAccessorExpression::IndexAccessorExpression(ProgramID pid,
+                                                 const Source& src,
+                                                 const Expression* obj,
+                                                 const Expression* idx)
+    : Base(pid, src), object(obj), index(idx) {
+  TINT_ASSERT(AST, object);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, object, program_id);
+  TINT_ASSERT(AST, idx);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, idx, program_id);
+}
+
+IndexAccessorExpression::IndexAccessorExpression(IndexAccessorExpression&&) =
+    default;
+
+IndexAccessorExpression::~IndexAccessorExpression() = default;
+
+const IndexAccessorExpression* IndexAccessorExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* obj = ctx->Clone(object);
+  auto* idx = ctx->Clone(index);
+  return ctx->dst->create<IndexAccessorExpression>(src, obj, idx);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/index_accessor_expression.h b/src/tint/ast/index_accessor_expression.h
new file mode 100644
index 0000000..41dbcec
--- /dev/null
+++ b/src/tint/ast/index_accessor_expression.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_
+#define SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// An index accessor expression
+class IndexAccessorExpression
+    : public Castable<IndexAccessorExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the index accessor source
+  /// @param obj the object
+  /// @param idx the index expression
+  IndexAccessorExpression(ProgramID program_id,
+                          const Source& source,
+                          const Expression* obj,
+                          const Expression* idx);
+  /// Move constructor
+  IndexAccessorExpression(IndexAccessorExpression&&);
+  ~IndexAccessorExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const IndexAccessorExpression* Clone(CloneContext* ctx) const override;
+
+  /// the array, vector or matrix
+  const Expression* const object;
+
+  /// the index expression
+  const Expression* const index;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_
diff --git a/src/tint/ast/index_accessor_expression_test.cc b/src/tint/ast/index_accessor_expression_test.cc
new file mode 100644
index 0000000..8b91239
--- /dev/null
+++ b/src/tint/ast/index_accessor_expression_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IndexAccessorExpressionTest = TestHelper;
+
+TEST_F(IndexAccessorExpressionTest, Create) {
+  auto* obj = Expr("obj");
+  auto* idx = Expr("idx");
+
+  auto* exp = IndexAccessor(obj, idx);
+  ASSERT_EQ(exp->object, obj);
+  ASSERT_EQ(exp->index, idx);
+}
+
+TEST_F(IndexAccessorExpressionTest, CreateWithSource) {
+  auto* obj = Expr("obj");
+  auto* idx = Expr("idx");
+
+  auto* exp = IndexAccessor(Source{{20, 2}}, obj, idx);
+  auto src = exp->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(IndexAccessorExpressionTest, IsIndexAccessor) {
+  auto* obj = Expr("obj");
+  auto* idx = Expr("idx");
+
+  auto* exp = IndexAccessor(obj, idx);
+  EXPECT_TRUE(exp->Is<IndexAccessorExpression>());
+}
+
+TEST_F(IndexAccessorExpressionTest, Assert_Null_Array) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.IndexAccessor(nullptr, b.Expr("idx"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(IndexAccessorExpressionTest, Assert_Null_Index) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.IndexAccessor(b.Expr("arr"), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Array) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.IndexAccessor(b2.Expr("arr"), b1.Expr("idx"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Index) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.IndexAccessor(b1.Expr("arr"), b2.Expr("idx"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/int_literal_expression.cc b/src/tint/ast/int_literal_expression.cc
new file mode 100644
index 0000000..05c9f2d
--- /dev/null
+++ b/src/tint/ast/int_literal_expression.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/int_literal_expression.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IntLiteralExpression);
+
+namespace tint {
+namespace ast {
+
+IntLiteralExpression::IntLiteralExpression(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+IntLiteralExpression::~IntLiteralExpression() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/int_literal_expression.h b/src/tint/ast/int_literal_expression.h
new file mode 100644
index 0000000..9da44bb
--- /dev/null
+++ b/src/tint/ast/int_literal_expression.h
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_
+
+#include "src/tint/ast/literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// An integer literal. This could be either signed or unsigned.
+class IntLiteralExpression
+    : public Castable<IntLiteralExpression, LiteralExpression> {
+ public:
+  ~IntLiteralExpression() override;
+
+  /// @returns the literal value as a u32
+  virtual uint32_t ValueAsU32() const = 0;
+
+  /// @returns the literal value as an i32
+  int32_t ValueAsI32() const { return static_cast<int32_t>(ValueAsU32()); }
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  IntLiteralExpression(ProgramID pid, const Source& src);
+};  // namespace ast
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/int_literal_expression_test.cc b/src/tint/ast/int_literal_expression_test.cc
new file mode 100644
index 0000000..e64d456
--- /dev/null
+++ b/src/tint/ast/int_literal_expression_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IntLiteralExpressionTest = TestHelper;
+
+TEST_F(IntLiteralExpressionTest, Sint_IsInt) {
+  auto* i = create<SintLiteralExpression>(47);
+  ASSERT_TRUE(i->Is<IntLiteralExpression>());
+}
+
+TEST_F(IntLiteralExpressionTest, Uint_IsInt) {
+  auto* i = create<UintLiteralExpression>(42);
+  EXPECT_TRUE(i->Is<IntLiteralExpression>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/internal_attribute.cc b/src/tint/ast/internal_attribute.cc
new file mode 100644
index 0000000..7f8c84b
--- /dev/null
+++ b/src/tint/ast/internal_attribute.cc
@@ -0,0 +1,31 @@
+// 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/ast/internal_attribute.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::InternalAttribute);
+
+namespace tint {
+namespace ast {
+
+InternalAttribute::InternalAttribute(ProgramID pid) : Base(pid, Source{}) {}
+
+InternalAttribute::~InternalAttribute() = default;
+
+std::string InternalAttribute::Name() const {
+  return "internal";
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/internal_attribute.h b/src/tint/ast/internal_attribute.h
new file mode 100644
index 0000000..6a9fd4a
--- /dev/null
+++ b/src/tint/ast/internal_attribute.h
@@ -0,0 +1,48 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_
+#define SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// An attribute used to indicate that a function is tint-internal.
+/// These attributes are not produced by generators, but instead are usually
+/// created by transforms for consumption by a particular backend.
+class InternalAttribute : public Castable<InternalAttribute, Attribute> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  explicit InternalAttribute(ProgramID program_id);
+
+  /// Destructor
+  ~InternalAttribute() override;
+
+  /// @return a short description of the internal attribute which will be
+  /// displayed in WGSL as `@internal(<name>)` (but is not parsable).
+  virtual std::string InternalName() const = 0;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_
diff --git a/src/tint/ast/interpolate_attribute.cc b/src/tint/ast/interpolate_attribute.cc
new file mode 100644
index 0000000..1cf62c6
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute.cc
@@ -0,0 +1,86 @@
+// 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/ast/interpolate_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::InterpolateAttribute);
+
+namespace tint {
+namespace ast {
+
+InterpolateAttribute::InterpolateAttribute(ProgramID pid,
+                                           const Source& src,
+                                           InterpolationType ty,
+                                           InterpolationSampling smpl)
+    : Base(pid, src), type(ty), sampling(smpl) {}
+
+InterpolateAttribute::~InterpolateAttribute() = default;
+
+std::string InterpolateAttribute::Name() const {
+  return "interpolate";
+}
+
+const InterpolateAttribute* InterpolateAttribute::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<InterpolateAttribute>(src, type, sampling);
+}
+
+std::ostream& operator<<(std::ostream& out, InterpolationType type) {
+  switch (type) {
+    case InterpolationType::kPerspective: {
+      out << "perspective";
+      break;
+    }
+    case InterpolationType::kLinear: {
+      out << "linear";
+      break;
+    }
+    case InterpolationType::kFlat: {
+      out << "flat";
+      break;
+    }
+  }
+  return out;
+}
+
+std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling) {
+  switch (sampling) {
+    case InterpolationSampling::kNone: {
+      out << "none";
+      break;
+    }
+    case InterpolationSampling::kCenter: {
+      out << "center";
+      break;
+    }
+    case InterpolationSampling::kCentroid: {
+      out << "centroid";
+      break;
+    }
+    case InterpolationSampling::kSample: {
+      out << "sample";
+      break;
+    }
+  }
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/interpolate_attribute.h b/src/tint/ast/interpolate_attribute.h
new file mode 100644
index 0000000..d198ee7
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute.h
@@ -0,0 +1,75 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_
+#define SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_
+
+#include <ostream>
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// The interpolation type.
+enum class InterpolationType { kPerspective, kLinear, kFlat };
+
+/// The interpolation sampling.
+enum class InterpolationSampling { kNone = -1, kCenter, kCentroid, kSample };
+
+/// An interpolate attribute
+class InterpolateAttribute : public Castable<InterpolateAttribute, Attribute> {
+ public:
+  /// Create an interpolate attribute.
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  InterpolateAttribute(ProgramID pid,
+                       const Source& src,
+                       InterpolationType type,
+                       InterpolationSampling sampling);
+  ~InterpolateAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const InterpolateAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The interpolation type
+  const InterpolationType type;
+
+  /// The interpolation sampling
+  const InterpolationSampling sampling;
+};
+
+/// @param out the std::ostream to write to
+/// @param type the interpolation type
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, InterpolationType type);
+
+/// @param out the std::ostream to write to
+/// @param sampling the interpolation sampling
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_
diff --git a/src/tint/ast/interpolate_attribute_test.cc b/src/tint/ast/interpolate_attribute_test.cc
new file mode 100644
index 0000000..e8417b2
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute_test.cc
@@ -0,0 +1,34 @@
+// 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/ast/interpolate_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace 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
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/invariant_attribute.cc b/src/tint/ast/invariant_attribute.cc
new file mode 100644
index 0000000..f893b8a
--- /dev/null
+++ b/src/tint/ast/invariant_attribute.cc
@@ -0,0 +1,40 @@
+// 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/ast/invariant_attribute.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::InvariantAttribute);
+
+namespace tint {
+namespace ast {
+
+InvariantAttribute::InvariantAttribute(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+InvariantAttribute::~InvariantAttribute() = default;
+
+std::string InvariantAttribute::Name() const {
+  return "invariant";
+}
+
+const InvariantAttribute* InvariantAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<InvariantAttribute>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/invariant_attribute.h b/src/tint/ast/invariant_attribute.h
new file mode 100644
index 0000000..c427147
--- /dev/null
+++ b/src/tint/ast/invariant_attribute.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_
+#define SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// The invariant attribute
+class InvariantAttribute : public Castable<InvariantAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  InvariantAttribute(ProgramID pid, const Source& src);
+  ~InvariantAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const InvariantAttribute* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_
diff --git a/src/tint/ast/invariant_attribute_test.cc b/src/tint/ast/invariant_attribute_test.cc
new file mode 100644
index 0000000..7b09ed6
--- /dev/null
+++ b/src/tint/ast/invariant_attribute_test.cc
@@ -0,0 +1,27 @@
+// 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/ast/invariant_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using InvariantAttributeTest = TestHelper;
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/literal_expression.cc b/src/tint/ast/literal_expression.cc
new file mode 100644
index 0000000..25aa908
--- /dev/null
+++ b/src/tint/ast/literal_expression.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/literal_expression.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::LiteralExpression);
+
+namespace tint {
+namespace ast {
+
+LiteralExpression::LiteralExpression(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+LiteralExpression::~LiteralExpression() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/literal_expression.h b/src/tint/ast/literal_expression.h
new file mode 100644
index 0000000..0b1c708
--- /dev/null
+++ b/src/tint/ast/literal_expression.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_LITERAL_EXPRESSION_H_
+
+#include <string>
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// Base class for a literal value expressions
+class LiteralExpression : public Castable<LiteralExpression, Expression> {
+ public:
+  ~LiteralExpression() override;
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the input source
+  LiteralExpression(ProgramID pid, const Source& src);
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/location_attribute.cc b/src/tint/ast/location_attribute.cc
new file mode 100644
index 0000000..9f3a53d
--- /dev/null
+++ b/src/tint/ast/location_attribute.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/location_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::LocationAttribute);
+
+namespace tint {
+namespace ast {
+
+LocationAttribute::LocationAttribute(ProgramID pid,
+                                     const Source& src,
+                                     uint32_t val)
+    : Base(pid, src), value(val) {}
+
+LocationAttribute::~LocationAttribute() = default;
+
+std::string LocationAttribute::Name() const {
+  return "location";
+}
+
+const LocationAttribute* LocationAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<LocationAttribute>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/location_attribute.h b/src/tint/ast/location_attribute.h
new file mode 100644
index 0000000..74a01cf
--- /dev/null
+++ b/src/tint/ast/location_attribute.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_LOCATION_ATTRIBUTE_H_
+#define SRC_TINT_AST_LOCATION_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A location attribute
+class LocationAttribute : public Castable<LocationAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the location value
+  LocationAttribute(ProgramID pid, const Source& src, uint32_t value);
+  ~LocationAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const LocationAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The location value
+  const uint32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_LOCATION_ATTRIBUTE_H_
diff --git a/src/tint/ast/location_attribute_test.cc b/src/tint/ast/location_attribute_test.cc
new file mode 100644
index 0000000..e826a1c
--- /dev/null
+++ b/src/tint/ast/location_attribute_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using LocationAttributeTest = TestHelper;
+
+TEST_F(LocationAttributeTest, Creation) {
+  auto* d = create<LocationAttribute>(2);
+  EXPECT_EQ(2u, d->value);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/loop_statement.cc b/src/tint/ast/loop_statement.cc
new file mode 100644
index 0000000..4e490fd
--- /dev/null
+++ b/src/tint/ast/loop_statement.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/loop_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::LoopStatement);
+
+namespace tint {
+namespace ast {
+
+LoopStatement::LoopStatement(ProgramID pid,
+                             const Source& src,
+                             const BlockStatement* b,
+                             const BlockStatement* cont)
+    : Base(pid, src), body(b), continuing(cont) {
+  TINT_ASSERT(AST, body);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
+}
+
+LoopStatement::LoopStatement(LoopStatement&&) = default;
+
+LoopStatement::~LoopStatement() = default;
+
+const LoopStatement* LoopStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* b = ctx->Clone(body);
+  auto* cont = ctx->Clone(continuing);
+  return ctx->dst->create<LoopStatement>(src, b, cont);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/loop_statement.h b/src/tint/ast/loop_statement.h
new file mode 100644
index 0000000..13484ed
--- /dev/null
+++ b/src/tint/ast/loop_statement.h
@@ -0,0 +1,55 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_LOOP_STATEMENT_H_
+#define SRC_TINT_AST_LOOP_STATEMENT_H_
+
+#include "src/tint/ast/block_statement.h"
+
+namespace tint {
+namespace ast {
+
+/// A loop statement
+class LoopStatement : public Castable<LoopStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the loop statement source
+  /// @param body the body statements
+  /// @param continuing the continuing statements
+  LoopStatement(ProgramID program_id,
+                const Source& source,
+                const BlockStatement* body,
+                const BlockStatement* continuing);
+  /// Move constructor
+  LoopStatement(LoopStatement&&);
+  ~LoopStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const LoopStatement* Clone(CloneContext* ctx) const override;
+
+  /// The loop body
+  const BlockStatement* const body;
+
+  /// The continuing statements
+  const BlockStatement* const continuing;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_LOOP_STATEMENT_H_
diff --git a/src/tint/ast/loop_statement_test.cc b/src/tint/ast/loop_statement_test.cc
new file mode 100644
index 0000000..17cf7e9
--- /dev/null
+++ b/src/tint/ast/loop_statement_test.cc
@@ -0,0 +1,105 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/loop_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using LoopStatementTest = TestHelper;
+
+TEST_F(LoopStatementTest, Creation) {
+  auto* body = Block(create<DiscardStatement>());
+  auto* b = body->Last();
+
+  auto* continuing = Block(create<DiscardStatement>());
+
+  auto* l = create<LoopStatement>(body, continuing);
+  ASSERT_EQ(l->body->statements.size(), 1u);
+  EXPECT_EQ(l->body->statements[0], b);
+  ASSERT_EQ(l->continuing->statements.size(), 1u);
+  EXPECT_EQ(l->continuing->statements[0], continuing->Last());
+}
+
+TEST_F(LoopStatementTest, Creation_WithSource) {
+  auto* body = Block(create<DiscardStatement>());
+
+  auto* continuing = Block(create<DiscardStatement>());
+
+  auto* l =
+      create<LoopStatement>(Source{Source::Location{20, 2}}, body, continuing);
+  auto src = l->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(LoopStatementTest, IsLoop) {
+  auto* l = create<LoopStatement>(Block(), Block());
+  EXPECT_TRUE(l->Is<LoopStatement>());
+}
+
+TEST_F(LoopStatementTest, HasContinuing_WithoutContinuing) {
+  auto* body = Block(create<DiscardStatement>());
+
+  auto* l = create<LoopStatement>(body, nullptr);
+  EXPECT_FALSE(l->continuing);
+}
+
+TEST_F(LoopStatementTest, HasContinuing_WithContinuing) {
+  auto* body = Block(create<DiscardStatement>());
+
+  auto* continuing = Block(create<DiscardStatement>());
+
+  auto* l = create<LoopStatement>(body, continuing);
+  EXPECT_TRUE(l->continuing);
+}
+
+TEST_F(LoopStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<LoopStatement>(nullptr, nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(LoopStatementTest, Assert_DifferentProgramID_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<LoopStatement>(b2.Block(), b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(LoopStatementTest, Assert_DifferentProgramID_Continuing) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<LoopStatement>(b1.Block(), b2.Block());
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/matrix.cc b/src/tint/ast/matrix.cc
new file mode 100644
index 0000000..ce65483
--- /dev/null
+++ b/src/tint/ast/matrix.cc
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/matrix.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Matrix);
+
+namespace tint {
+namespace ast {
+
+Matrix::Matrix(ProgramID pid,
+               const Source& src,
+               const Type* subtype,
+               uint32_t r,
+               uint32_t c)
+    : Base(pid, src), type(subtype), rows(r), columns(c) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
+  TINT_ASSERT(AST, rows > 1);
+  TINT_ASSERT(AST, rows < 5);
+  TINT_ASSERT(AST, columns > 1);
+  TINT_ASSERT(AST, columns < 5);
+}
+
+Matrix::Matrix(Matrix&&) = default;
+
+Matrix::~Matrix() = default;
+
+std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "mat" << columns << "x" << rows << "<" << type->FriendlyName(symbols)
+      << ">";
+  return out.str();
+}
+
+const Matrix* Matrix::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<Matrix>(src, ty, rows, columns);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/matrix.h b/src/tint/ast/matrix.h
new file mode 100644
index 0000000..6f96d4b
--- /dev/null
+++ b/src/tint/ast/matrix.h
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_MATRIX_H_
+#define SRC_TINT_AST_MATRIX_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A matrix type
+class Matrix : public Castable<Matrix, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param subtype the declared type of the matrix components. May be null for
+  ///        matrix constructors, where the element type will be inferred from
+  ///        the constructor arguments
+  /// @param rows the number of rows in the matrix
+  /// @param columns the number of columns in the matrix
+  Matrix(ProgramID pid,
+         const Source& src,
+         const Type* subtype,
+         uint32_t rows,
+         uint32_t columns);
+  /// Move constructor
+  Matrix(Matrix&&);
+  ~Matrix() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Matrix* Clone(CloneContext* ctx) const override;
+
+  /// The declared type of the matrix components. May be null for matrix
+  /// constructors, where the element type will be inferred from the constructor
+  /// arguments
+  const Type* const type;
+
+  /// The number of rows in the matrix
+  const uint32_t rows;
+
+  /// The number of columns in the matrix
+  const uint32_t columns;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_MATRIX_H_
diff --git a/src/tint/ast/matrix_test.cc b/src/tint/ast/matrix_test.cc
new file mode 100644
index 0000000..9bb4b8c
--- /dev/null
+++ b/src/tint/ast/matrix_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/struct.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstMatrixTest = TestHelper;
+
+TEST_F(AstMatrixTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* m = create<Matrix>(i32, 2, 4);
+  EXPECT_EQ(m->type, i32);
+  EXPECT_EQ(m->rows, 2u);
+  EXPECT_EQ(m->columns, 4u);
+}
+
+TEST_F(AstMatrixTest, FriendlyName) {
+  auto* i32 = create<I32>();
+  auto* m = create<Matrix>(i32, 3, 2);
+  EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3<i32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/member_accessor_expression.cc b/src/tint/ast/member_accessor_expression.cc
new file mode 100644
index 0000000..1b5a724
--- /dev/null
+++ b/src/tint/ast/member_accessor_expression.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/member_accessor_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::MemberAccessorExpression);
+
+namespace tint {
+namespace ast {
+
+MemberAccessorExpression::MemberAccessorExpression(
+    ProgramID pid,
+    const Source& src,
+    const Expression* str,
+    const IdentifierExpression* mem)
+    : Base(pid, src), structure(str), member(mem) {
+  TINT_ASSERT(AST, structure);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, structure, program_id);
+  TINT_ASSERT(AST, member);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, member, program_id);
+}
+
+MemberAccessorExpression::MemberAccessorExpression(MemberAccessorExpression&&) =
+    default;
+
+MemberAccessorExpression::~MemberAccessorExpression() = default;
+
+const MemberAccessorExpression* MemberAccessorExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* str = ctx->Clone(structure);
+  auto* mem = ctx->Clone(member);
+  return ctx->dst->create<MemberAccessorExpression>(src, str, mem);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/member_accessor_expression.h b/src/tint/ast/member_accessor_expression.h
new file mode 100644
index 0000000..9aa3e3a
--- /dev/null
+++ b/src/tint/ast/member_accessor_expression.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_
+#define SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_
+
+#include "src/tint/ast/identifier_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A member accessor expression
+class MemberAccessorExpression
+    : public Castable<MemberAccessorExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the member accessor expression source
+  /// @param structure the structure
+  /// @param member the member
+  MemberAccessorExpression(ProgramID program_id,
+                           const Source& source,
+                           const Expression* structure,
+                           const IdentifierExpression* member);
+  /// Move constructor
+  MemberAccessorExpression(MemberAccessorExpression&&);
+  ~MemberAccessorExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const MemberAccessorExpression* Clone(CloneContext* ctx) const override;
+
+  /// The structure
+  const Expression* const structure;
+
+  /// The member expression
+  const IdentifierExpression* const member;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_
diff --git a/src/tint/ast/member_accessor_expression_test.cc b/src/tint/ast/member_accessor_expression_test.cc
new file mode 100644
index 0000000..12c4e1f
--- /dev/null
+++ b/src/tint/ast/member_accessor_expression_test.cc
@@ -0,0 +1,89 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using MemberAccessorExpressionTest = TestHelper;
+
+TEST_F(MemberAccessorExpressionTest, Creation) {
+  auto* str = Expr("structure");
+  auto* mem = Expr("member");
+
+  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  EXPECT_EQ(stmt->structure, str);
+  EXPECT_EQ(stmt->member, mem);
+}
+
+TEST_F(MemberAccessorExpressionTest, Creation_WithSource) {
+  auto* stmt = create<MemberAccessorExpression>(
+      Source{Source::Location{20, 2}}, Expr("structure"), Expr("member"));
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(MemberAccessorExpressionTest, IsMemberAccessor) {
+  auto* stmt =
+      create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
+  EXPECT_TRUE(stmt->Is<MemberAccessorExpression>());
+}
+
+TEST_F(MemberAccessorExpressionTest, Assert_Null_Struct) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<MemberAccessorExpression>(nullptr, b.Expr("member"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(MemberAccessorExpressionTest, Assert_Null_Member) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<MemberAccessorExpression>(b.Expr("struct"), nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Struct) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<MemberAccessorExpression>(b2.Expr("structure"),
+                                            b1.Expr("member"));
+      },
+      "internal compiler error");
+}
+
+TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Member) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<MemberAccessorExpression>(b1.Expr("structure"),
+                                            b2.Expr("member"));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/module.cc b/src/tint/ast/module.cc
new file mode 100644
index 0000000..49634f8
--- /dev/null
+++ b/src/tint/ast/module.cc
@@ -0,0 +1,127 @@
+// 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/ast/module.h"
+
+#include <utility>
+
+#include "src/tint/ast/type_decl.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Module);
+
+namespace tint {
+namespace ast {
+
+Module::Module(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Module::Module(ProgramID pid,
+               const Source& src,
+               std::vector<const ast::Node*> global_decls)
+    : Base(pid, src), global_declarations_(std::move(global_decls)) {
+  for (auto* decl : global_declarations_) {
+    if (decl == nullptr) {
+      continue;
+    }
+    diag::List diags;
+    BinGlobalDeclaration(decl, diags);
+  }
+}
+
+Module::~Module() = default;
+
+const ast::TypeDecl* Module::LookupType(Symbol name) const {
+  for (auto* ty : TypeDecls()) {
+    if (ty->name == name) {
+      return ty;
+    }
+  }
+  return nullptr;
+}
+
+void Module::AddGlobalDeclaration(const tint::ast::Node* decl) {
+  diag::List diags;
+  BinGlobalDeclaration(decl, diags);
+  global_declarations_.emplace_back(decl);
+}
+
+void Module::BinGlobalDeclaration(const tint::ast::Node* decl,
+                                  diag::List& diags) {
+  Switch(
+      decl,  //
+      [&](const ast::TypeDecl* type) {
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
+        type_decls_.push_back(type);
+      },
+      [&](const Function* func) {
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
+        functions_.push_back(func);
+      },
+      [&](const Variable* var) {
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
+        global_variables_.push_back(var);
+      },
+      [&](Default) {
+        TINT_ICE(AST, diags) << "Unknown global declaration type";
+      });
+}
+
+void Module::AddGlobalVariable(const ast::Variable* var) {
+  TINT_ASSERT(AST, var);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
+  global_variables_.push_back(var);
+  global_declarations_.push_back(var);
+}
+
+void Module::AddTypeDecl(const ast::TypeDecl* type) {
+  TINT_ASSERT(AST, type);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
+  type_decls_.push_back(type);
+  global_declarations_.push_back(type);
+}
+
+void Module::AddFunction(const ast::Function* func) {
+  TINT_ASSERT(AST, func);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
+  functions_.push_back(func);
+  global_declarations_.push_back(func);
+}
+
+const Module* Module::Clone(CloneContext* ctx) const {
+  auto* out = ctx->dst->create<Module>();
+  out->Copy(ctx, this);
+  return out;
+}
+
+void Module::Copy(CloneContext* ctx, const Module* src) {
+  ctx->Clone(global_declarations_, src->global_declarations_);
+
+  // During the clone, declarations may have been placed into the module.
+  // Clear everything out, as we're about to re-bin the declarations.
+  type_decls_.clear();
+  functions_.clear();
+  global_variables_.clear();
+
+  for (auto* decl : global_declarations_) {
+    if (!decl) {
+      TINT_ICE(AST, ctx->dst->Diagnostics())
+          << "src global declaration was nullptr";
+      continue;
+    }
+    BinGlobalDeclaration(decl, ctx->dst->Diagnostics());
+  }
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/module.h b/src/tint/ast/module.h
new file mode 100644
index 0000000..f7b3faf
--- /dev/null
+++ b/src/tint/ast/module.h
@@ -0,0 +1,125 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_MODULE_H_
+#define SRC_TINT_AST_MODULE_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/function.h"
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+class TypeDecl;
+
+/// Module holds the top-level AST types, functions and global variables used by
+/// a Program.
+class Module : public Castable<Module, Node> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Module(ProgramID pid, const Source& src);
+
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param global_decls the list of global types, functions, and variables, in
+  /// the order they were declared in the source program
+  Module(ProgramID pid,
+         const Source& src,
+         std::vector<const Node*> global_decls);
+
+  /// Destructor
+  ~Module() override;
+
+  /// @returns the declaration-ordered global declarations for the module
+  const std::vector<const Node*>& GlobalDeclarations() const {
+    return global_declarations_;
+  }
+
+  /// Add a global variable to the Builder
+  /// @param var the variable to add
+  void AddGlobalVariable(const Variable* var);
+
+  /// @returns true if the module has the global declaration `decl`
+  /// @param decl the declaration to check
+  bool HasGlobalDeclaration(Node* decl) const {
+    for (auto* d : global_declarations_) {
+      if (d == decl) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Adds a global declaration to the Builder.
+  /// @param decl the declaration to add
+  void AddGlobalDeclaration(const tint::ast::Node* decl);
+
+  /// @returns the global variables for the module
+  const VariableList& GlobalVariables() const { return global_variables_; }
+
+  /// @returns the global variables for the module
+  VariableList& GlobalVariables() { return global_variables_; }
+
+  /// Adds a type declaration to the Builder.
+  /// @param decl the type declaration to add
+  void AddTypeDecl(const TypeDecl* decl);
+
+  /// @returns the TypeDecl registered as a TypeDecl()
+  /// @param name the name of the type to search for
+  const TypeDecl* LookupType(Symbol name) const;
+
+  /// @returns the declared types in the module
+  const std::vector<const TypeDecl*>& TypeDecls() const { return type_decls_; }
+
+  /// Add a function to the Builder
+  /// @param func the function to add
+  void AddFunction(const Function* func);
+
+  /// @returns the functions declared in the module
+  const FunctionList& Functions() const { return functions_; }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const Module* Clone(CloneContext* ctx) const override;
+
+  /// Copy copies the content of the Module src into this module.
+  /// @param ctx the clone context
+  /// @param src the module to copy into this module
+  void Copy(CloneContext* ctx, const Module* src);
+
+ private:
+  /// Adds `decl` to either:
+  /// * #global_declarations_
+  /// * #type_decls_
+  /// * #functions_
+  void BinGlobalDeclaration(const tint::ast::Node* decl, diag::List& diags);
+
+  std::vector<const Node*> global_declarations_;
+  std::vector<const TypeDecl*> type_decls_;
+  FunctionList functions_;
+  VariableList global_variables_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_MODULE_H_
diff --git a/src/tint/ast/module_clone_test.cc b/src/tint/ast/module_clone_test.cc
new file mode 100644
index 0000000..3879b1a
--- /dev/null
+++ b/src/tint/ast/module_clone_test.cc
@@ -0,0 +1,182 @@
+// Copyright 2020 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 <unordered_set>
+
+#include "gtest/gtest.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+TEST(ModuleCloneTest, Clone) {
+#if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER
+  // Shader that exercises the bulk of the AST nodes and types.
+  // See also fuzzers/tint_ast_clone_fuzzer.cc for further coverage of cloning.
+  Source::File file("test.wgsl", R"([[block]]
+struct S0 {
+  @size(4)
+  m0 : u32;
+  m1 : array<u32>;
+};
+
+[[block]] struct S1 {
+  @size(4)
+  m0 : u32;
+  m1 : array<u32, 6>;
+};
+
+let c0 : i32 = 10;
+let c1 : bool = true;
+
+type t0 = @stride(16) array<vec4<f32>>;
+type t1 = array<vec4<f32>>;
+
+var<private> g0 : u32 = 20u;
+var<private> g1 : f32 = 123.0;
+@group(0) @binding(0) var g2 : texture_2d<f32>;
+@group(1) @binding(0) var g3 : texture_depth_2d;
+@group(2) @binding(0) var g4 : texture_storage_2d<rg32float, write>;
+@group(3) @binding(0) var g5 : texture_depth_cube_array;
+@group(4) @binding(0) var g6 : texture_external;
+
+var<private> g7 : vec3<f32>;
+@group(0) @binding(1) var<storage, write> g8 : S0;
+@group(1) @binding(1) var<storage, read> g9 : S0;
+@group(2) @binding(1) var<storage, read_write> g10 : S0;
+
+fn f0(p0 : bool) -> f32 {
+  if (p0) {
+    return 1.0;
+  }
+  return 0.0;
+}
+
+fn f1(p0 : f32, p1 : i32) -> f32 {
+  var l0 : i32 = 3;
+  var l1 : f32 = 8.0;
+  var l2 : u32 = bitcast<u32>(4);
+  var l3 : vec2<u32> = vec2<u32>(u32(l0), u32(l1));
+  var l4 : S1;
+  var l5 : u32 = l4.m1[5];
+  let l6 : ptr<private, u32> = &g0;
+  loop {
+    l0 = (p1 + 2);
+    if (((l0 % 4) == 0)) {
+      break;
+    }
+
+    continuing {
+      if (1 == 2) {
+        l0 = l0 - 1;
+      } else {
+        l0 = l0 - 2;
+      }
+    }
+  }
+  switch(l2) {
+    case 0u: {
+      break;
+    }
+    case 1u: {
+      return f0(true);
+    }
+    default: {
+      discard;
+    }
+  }
+  return 1.0;
+}
+
+@stage(fragment)
+fn main() {
+  f1(1.0, 2);
+}
+
+let declaration_order_check_0 : i32 = 1;
+
+type declaration_order_check_1 = f32;
+
+fn declaration_order_check_2() {}
+
+type declaration_order_check_3 = f32;
+
+let declaration_order_check_4 : i32 = 1;
+
+)");
+
+  // Parse the wgsl, create the src program
+  auto src = reader::wgsl::Parse(&file);
+
+  ASSERT_TRUE(src.IsValid()) << diag::Formatter().format(src.Diagnostics());
+
+  // Clone the src program to dst
+  Program dst(src.Clone());
+
+  ASSERT_TRUE(dst.IsValid()) << diag::Formatter().format(dst.Diagnostics());
+
+  // Expect the printed strings to match
+  EXPECT_EQ(Program::printer(&src), Program::printer(&dst));
+
+  // Check that none of the AST nodes or type pointers in dst are found in src
+  std::unordered_set<const ast::Node*> src_nodes;
+  for (auto* src_node : src.ASTNodes().Objects()) {
+    src_nodes.emplace(src_node);
+  }
+  std::unordered_set<const sem::Type*> src_types;
+  for (auto* src_type : src.Types()) {
+    src_types.emplace(src_type);
+  }
+  for (auto* dst_node : dst.ASTNodes().Objects()) {
+    ASSERT_EQ(src_nodes.count(dst_node), 0u);
+  }
+  for (auto* dst_type : dst.Types()) {
+    ASSERT_EQ(src_types.count(dst_type), 0u);
+  }
+
+  // Regenerate the wgsl for the src program. We use this instead of the
+  // original source so that reformatting doesn't impact the final wgsl
+  // comparison.
+  writer::wgsl::Options options;
+  std::string src_wgsl;
+  {
+    auto result = writer::wgsl::Generate(&src, options);
+    ASSERT_TRUE(result.success) << result.error;
+    src_wgsl = result.wgsl;
+
+    // Move the src program to a temporary that'll be dropped, so that the src
+    // program is released before we attempt to print the dst program. This
+    // guarantee that all the source program nodes and types are destructed and
+    // freed. ASAN should error if there's any remaining references in dst when
+    // we try to reconstruct the WGSL.
+    auto tmp = std::move(src);
+  }
+
+  // Print the dst module, check it matches the original source
+  auto result = writer::wgsl::Generate(&dst, options);
+  ASSERT_TRUE(result.success);
+  auto dst_wgsl = result.wgsl;
+  ASSERT_EQ(src_wgsl, dst_wgsl);
+
+#else  // #if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER
+  GTEST_SKIP() << "ModuleCloneTest requires TINT_BUILD_WGSL_READER and "
+                  "TINT_BUILD_WGSL_WRITER to be enabled";
+#endif
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/module_test.cc b/src/tint/ast/module_test.cc
new file mode 100644
index 0000000..21b48dc
--- /dev/null
+++ b/src/tint/ast/module_test.cc
@@ -0,0 +1,143 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/clone_context.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ModuleTest = TestHelper;
+
+TEST_F(ModuleTest, Creation) {
+  EXPECT_EQ(Program(std::move(*this)).AST().Functions().size(), 0u);
+}
+
+TEST_F(ModuleTest, LookupFunction) {
+  auto* func = Func("main", VariableList{}, ty.f32(), StatementList{},
+                    ast::AttributeList{});
+
+  Program program(std::move(*this));
+  EXPECT_EQ(func,
+            program.AST().Functions().Find(program.Symbols().Get("main")));
+}
+
+TEST_F(ModuleTest, LookupFunctionMissing) {
+  Program program(std::move(*this));
+  EXPECT_EQ(nullptr,
+            program.AST().Functions().Find(program.Symbols().Get("Missing")));
+}
+
+TEST_F(ModuleTest, Assert_Null_GlobalVariable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder builder;
+        builder.AST().AddGlobalVariable(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ModuleTest, Assert_Null_TypeDecl) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder builder;
+        builder.AST().AddTypeDecl(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ModuleTest, Assert_DifferentProgramID_Function) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.AST().AddFunction(b2.create<ast::Function>(
+            b2.Symbols().Register("func"), VariableList{}, b2.ty.f32(),
+            b2.Block(), AttributeList{}, AttributeList{}));
+      },
+      "internal compiler error");
+}
+
+TEST_F(ModuleTest, Assert_DifferentProgramID_GlobalVariable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.AST().AddGlobalVariable(
+            b2.Var("var", b2.ty.i32(), ast::StorageClass::kPrivate));
+      },
+      "internal compiler error");
+}
+
+TEST_F(ModuleTest, Assert_Null_Function) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder builder;
+        builder.AST().AddFunction(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ModuleTest, CloneOrder) {
+  // Create a program with a function, alias decl and var decl.
+  Program p = [] {
+    ProgramBuilder b;
+    b.Func("F", {}, b.ty.void_(), {});
+    b.Alias("A", b.ty.u32());
+    b.Global("V", b.ty.i32(), ast::StorageClass::kPrivate);
+    return Program(std::move(b));
+  }();
+
+  // Clone the program, using ReplaceAll() to create new module-scope
+  // declarations. We want to test that these are added just before the
+  // declaration that triggered the ReplaceAll().
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &p);
+  ctx.ReplaceAll([&](const ast::Function*) -> const ast::Function* {
+    ctx.dst->Alias("inserted_before_F", cloned.ty.u32());
+    return nullptr;
+  });
+  ctx.ReplaceAll([&](const ast::Alias*) -> const ast::Alias* {
+    ctx.dst->Alias("inserted_before_A", cloned.ty.u32());
+    return nullptr;
+  });
+  ctx.ReplaceAll([&](const ast::Variable*) -> const ast::Variable* {
+    ctx.dst->Alias("inserted_before_V", cloned.ty.u32());
+    return nullptr;
+  });
+  ctx.Clone();
+
+  auto& decls = cloned.AST().GlobalDeclarations();
+  ASSERT_EQ(decls.size(), 6u);
+  EXPECT_TRUE(decls[1]->Is<ast::Function>());
+  EXPECT_TRUE(decls[3]->Is<ast::Alias>());
+  EXPECT_TRUE(decls[5]->Is<ast::Variable>());
+
+  ASSERT_TRUE(decls[0]->Is<ast::Alias>());
+  ASSERT_TRUE(decls[2]->Is<ast::Alias>());
+  ASSERT_TRUE(decls[4]->Is<ast::Alias>());
+
+  ASSERT_EQ(cloned.Symbols().NameFor(decls[0]->As<ast::Alias>()->name),
+            "inserted_before_F");
+  ASSERT_EQ(cloned.Symbols().NameFor(decls[2]->As<ast::Alias>()->name),
+            "inserted_before_A");
+  ASSERT_EQ(cloned.Symbols().NameFor(decls[4]->As<ast::Alias>()->name),
+            "inserted_before_V");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/multisampled_texture.cc b/src/tint/ast/multisampled_texture.cc
new file mode 100644
index 0000000..f216a3c
--- /dev/null
+++ b/src/tint/ast/multisampled_texture.cc
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/multisampled_texture.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::MultisampledTexture);
+
+namespace tint {
+namespace ast {
+
+MultisampledTexture::MultisampledTexture(ProgramID pid,
+                                         const Source& src,
+                                         TextureDimension d,
+                                         const Type* ty)
+    : Base(pid, src, d), type(ty) {
+  TINT_ASSERT(AST, type);
+}
+
+MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
+
+MultisampledTexture::~MultisampledTexture() = default;
+
+std::string MultisampledTexture::FriendlyName(
+    const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_multisampled_" << dim << "<" << type->FriendlyName(symbols)
+      << ">";
+  return out.str();
+}
+
+const MultisampledTexture* MultisampledTexture::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<MultisampledTexture>(src, dim, ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/multisampled_texture.h b/src/tint/ast/multisampled_texture.h
new file mode 100644
index 0000000..ea91011
--- /dev/null
+++ b/src/tint/ast/multisampled_texture.h
@@ -0,0 +1,58 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
+#define SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A multisampled texture type.
+class MultisampledTexture : public Castable<MultisampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the multisampled texture
+  MultisampledTexture(ProgramID pid,
+                      const Source& src,
+                      TextureDimension dim,
+                      const Type* type);
+  /// Move constructor
+  MultisampledTexture(MultisampledTexture&&);
+  ~MultisampledTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const MultisampledTexture* Clone(CloneContext* ctx) const override;
+
+  /// The subtype of the multisampled texture
+  const Type* const type;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/multisampled_texture_test.cc b/src/tint/ast/multisampled_texture_test.cc
new file mode 100644
index 0000000..ba7ffb5
--- /dev/null
+++ b/src/tint/ast/multisampled_texture_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/multisampled_texture.h"
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/depth_texture.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampled_texture.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/struct.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstMultisampledTextureTest = TestHelper;
+
+TEST_F(AstMultisampledTextureTest, IsTexture) {
+  auto* f32 = create<F32>();
+  Texture* ty = create<MultisampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<MultisampledTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstMultisampledTextureTest, Dim) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->dim, TextureDimension::k3d);
+}
+
+TEST_F(AstMultisampledTextureTest, Type) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type, f32);
+}
+
+TEST_F(AstMultisampledTextureTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/node.cc b/src/tint/ast/node.cc
new file mode 100644
index 0000000..b90f0ef
--- /dev/null
+++ b/src/tint/ast/node.cc
@@ -0,0 +1,29 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/node.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Node);
+
+namespace tint {
+namespace ast {
+
+Node::Node(ProgramID pid, const Source& src) : program_id(pid), source(src) {}
+
+Node::Node(Node&&) = default;
+
+Node::~Node() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/node.h b/src/tint/ast/node.h
new file mode 100644
index 0000000..01b017b
--- /dev/null
+++ b/src/tint/ast/node.h
@@ -0,0 +1,68 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_NODE_H_
+#define SRC_TINT_AST_NODE_H_
+
+#include <string>
+
+#include "src/tint/clone_context.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+namespace sem {
+class Type;
+}
+namespace sem {
+class Info;
+}
+
+namespace ast {
+
+/// AST base class node
+class Node : public Castable<Node, Cloneable> {
+ public:
+  ~Node() override;
+
+  /// The identifier of the program that owns this node
+  const ProgramID program_id;
+
+  /// The node source data
+  const Source source;
+
+ protected:
+  /// Create a new node
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the input source for the node
+  Node(ProgramID pid, const Source& src);
+  /// Move constructor
+  Node(Node&&);
+
+ private:
+  Node(const Node&) = delete;
+};
+
+}  // namespace ast
+
+/// @param node a pointer to an AST node
+/// @returns the ProgramID of the given AST node.
+inline ProgramID ProgramIDOf(const ast::Node* node) {
+  return node ? node->program_id : ProgramID();
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_NODE_H_
diff --git a/src/tint/ast/phony_expression.cc b/src/tint/ast/phony_expression.cc
new file mode 100644
index 0000000..c05fb1d
--- /dev/null
+++ b/src/tint/ast/phony_expression.cc
@@ -0,0 +1,38 @@
+// 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/ast/phony_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::PhonyExpression);
+
+namespace tint {
+namespace ast {
+
+PhonyExpression::PhonyExpression(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+PhonyExpression::PhonyExpression(PhonyExpression&&) = default;
+
+PhonyExpression::~PhonyExpression() = default;
+
+const PhonyExpression* PhonyExpression::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<PhonyExpression>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/phony_expression.h b/src/tint/ast/phony_expression.h
new file mode 100644
index 0000000..28f31ca
--- /dev/null
+++ b/src/tint/ast/phony_expression.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_PHONY_EXPRESSION_H_
+#define SRC_TINT_AST_PHONY_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// Represents the `_` of a phony assignment `_ = <expr>`
+/// @see https://www.w3.org/TR/WGSL/#phony-assignment-section
+class PhonyExpression : public Castable<PhonyExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  PhonyExpression(ProgramID pid, const Source& src);
+  /// Move constructor
+  PhonyExpression(PhonyExpression&&);
+  ~PhonyExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const PhonyExpression* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_PHONY_EXPRESSION_H_
diff --git a/src/tint/ast/phony_expression_test.cc b/src/tint/ast/phony_expression_test.cc
new file mode 100644
index 0000000..568a2d0
--- /dev/null
+++ b/src/tint/ast/phony_expression_test.cc
@@ -0,0 +1,42 @@
+// 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/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using IdentifierExpressionTest = TestHelper;
+
+TEST_F(IdentifierExpressionTest, Creation) {
+  EXPECT_NE(Phony(), nullptr);
+}
+
+TEST_F(IdentifierExpressionTest, Creation_WithSource) {
+  auto* p = Phony(Source{{20, 2}});
+
+  auto src = p->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(IdentifierExpressionTest, IsPhony) {
+  auto* p = Phony();
+  EXPECT_TRUE(p->Is<PhonyExpression>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/pipeline_stage.cc b/src/tint/ast/pipeline_stage.cc
new file mode 100644
index 0000000..9f5203d
--- /dev/null
+++ b/src/tint/ast/pipeline_stage.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/pipeline_stage.h"
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, PipelineStage stage) {
+  switch (stage) {
+    case PipelineStage::kNone: {
+      out << "none";
+      break;
+    }
+    case PipelineStage::kVertex: {
+      out << "vertex";
+      break;
+    }
+    case PipelineStage::kFragment: {
+      out << "fragment";
+      break;
+    }
+    case PipelineStage::kCompute: {
+      out << "compute";
+      break;
+    }
+  }
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/pipeline_stage.h b/src/tint/ast/pipeline_stage.h
new file mode 100644
index 0000000..37e5030
--- /dev/null
+++ b/src/tint/ast/pipeline_stage.h
@@ -0,0 +1,34 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_PIPELINE_STAGE_H_
+#define SRC_TINT_AST_PIPELINE_STAGE_H_
+
+#include <ostream>
+
+namespace tint {
+namespace ast {
+
+/// The pipeline stage
+enum class PipelineStage { kNone = -1, kVertex, kFragment, kCompute };
+
+/// @param out the std::ostream to write to
+/// @param stage the PipelineStage
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, PipelineStage stage);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_PIPELINE_STAGE_H_
diff --git a/src/tint/ast/pointer.cc b/src/tint/ast/pointer.cc
new file mode 100644
index 0000000..1db6fc0
--- /dev/null
+++ b/src/tint/ast/pointer.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/pointer.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Pointer);
+
+namespace tint {
+namespace ast {
+
+Pointer::Pointer(ProgramID pid,
+                 const Source& src,
+                 const Type* const subtype,
+                 ast::StorageClass sc,
+                 ast::Access ac)
+    : Base(pid, src), type(subtype), storage_class(sc), access(ac) {}
+
+std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "ptr<";
+  if (storage_class != ast::StorageClass::kNone) {
+    out << storage_class << ", ";
+  }
+  out << type->FriendlyName(symbols);
+  if (access != ast::Access::kUndefined) {
+    out << ", " << access;
+  }
+  out << ">";
+  return out.str();
+}
+
+Pointer::Pointer(Pointer&&) = default;
+
+Pointer::~Pointer() = default;
+
+const Pointer* Pointer::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<Pointer>(src, ty, storage_class, access);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/pointer.h b/src/tint/ast/pointer.h
new file mode 100644
index 0000000..38e2ef9
--- /dev/null
+++ b/src/tint/ast/pointer.h
@@ -0,0 +1,68 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_POINTER_H_
+#define SRC_TINT_AST_POINTER_H_
+
+#include <string>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A pointer type.
+class Pointer : public Castable<Pointer, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param subtype the pointee type
+  /// @param storage_class the storage class of the pointer
+  /// @param access the access control of the pointer
+  Pointer(ProgramID pid,
+          const Source& src,
+          const Type* const subtype,
+          ast::StorageClass storage_class,
+          ast::Access access);
+  /// Move constructor
+  Pointer(Pointer&&);
+  ~Pointer() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Pointer* Clone(CloneContext* ctx) const override;
+
+  /// The pointee type
+  const Type* const type;
+
+  /// The storage class of the pointer
+  ast::StorageClass const storage_class;
+
+  /// The access control of the pointer
+  ast::Access const access;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_POINTER_H_
diff --git a/src/tint/ast/pointer_test.cc b/src/tint/ast/pointer_test.cc
new file mode 100644
index 0000000..ec5520c
--- /dev/null
+++ b/src/tint/ast/pointer_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/pointer.h"
+
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstPointerTest = TestHelper;
+
+TEST_F(AstPointerTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* p = create<Pointer>(i32, ast::StorageClass::kStorage, Access::kRead);
+  EXPECT_EQ(p->type, i32);
+  EXPECT_EQ(p->storage_class, ast::StorageClass::kStorage);
+  EXPECT_EQ(p->access, Access::kRead);
+}
+
+TEST_F(AstPointerTest, FriendlyName) {
+  auto* i32 = create<I32>();
+  auto* p =
+      create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kUndefined);
+  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>");
+}
+
+TEST_F(AstPointerTest, FriendlyNameWithAccess) {
+  auto* i32 = create<I32>();
+  auto* p =
+      create<Pointer>(i32, ast::StorageClass::kStorage, Access::kReadWrite);
+  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<storage, i32, read_write>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/return_statement.cc b/src/tint/ast/return_statement.cc
new file mode 100644
index 0000000..61b6d45
--- /dev/null
+++ b/src/tint/ast/return_statement.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/return_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ReturnStatement);
+
+namespace tint {
+namespace ast {
+
+ReturnStatement::ReturnStatement(ProgramID pid, const Source& src)
+    : Base(pid, src), value(nullptr) {}
+
+ReturnStatement::ReturnStatement(ProgramID pid,
+                                 const Source& src,
+                                 const Expression* val)
+    : Base(pid, src), value(val) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, value, program_id);
+}
+
+ReturnStatement::ReturnStatement(ReturnStatement&&) = default;
+
+ReturnStatement::~ReturnStatement() = default;
+
+const ReturnStatement* ReturnStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ret = ctx->Clone(value);
+  return ctx->dst->create<ReturnStatement>(src, ret);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/return_statement.h b/src/tint/ast/return_statement.h
new file mode 100644
index 0000000..f6be148
--- /dev/null
+++ b/src/tint/ast/return_statement.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_RETURN_STATEMENT_H_
+#define SRC_TINT_AST_RETURN_STATEMENT_H_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/statement.h"
+
+namespace tint {
+namespace ast {
+
+/// A return statement
+class ReturnStatement : public Castable<ReturnStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  ReturnStatement(ProgramID pid, const Source& src);
+
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the return value
+  ReturnStatement(ProgramID pid, const Source& src, const Expression* value);
+  /// Move constructor
+  ReturnStatement(ReturnStatement&&);
+  ~ReturnStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const ReturnStatement* Clone(CloneContext* ctx) const override;
+
+  /// The value returned. May be null.
+  const Expression* const value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_RETURN_STATEMENT_H_
diff --git a/src/tint/ast/return_statement_test.cc b/src/tint/ast/return_statement_test.cc
new file mode 100644
index 0000000..93c75a7
--- /dev/null
+++ b/src/tint/ast/return_statement_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/return_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ReturnStatementTest = TestHelper;
+
+TEST_F(ReturnStatementTest, Creation) {
+  auto* expr = Expr("expr");
+
+  auto* r = create<ReturnStatement>(expr);
+  EXPECT_EQ(r->value, expr);
+}
+
+TEST_F(ReturnStatementTest, Creation_WithSource) {
+  auto* r = create<ReturnStatement>(Source{Source::Location{20, 2}});
+  auto src = r->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(ReturnStatementTest, IsReturn) {
+  auto* r = create<ReturnStatement>();
+  EXPECT_TRUE(r->Is<ReturnStatement>());
+}
+
+TEST_F(ReturnStatementTest, WithoutValue) {
+  auto* r = create<ReturnStatement>();
+  EXPECT_EQ(r->value, nullptr);
+}
+
+TEST_F(ReturnStatementTest, WithValue) {
+  auto* expr = Expr("expr");
+  auto* r = create<ReturnStatement>(expr);
+  EXPECT_NE(r->value, nullptr);
+}
+
+TEST_F(ReturnStatementTest, Assert_DifferentProgramID_Expr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<ReturnStatement>(b2.Expr(true));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sampled_texture.cc b/src/tint/ast/sampled_texture.cc
new file mode 100644
index 0000000..7937ad5
--- /dev/null
+++ b/src/tint/ast/sampled_texture.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/sampled_texture.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::SampledTexture);
+
+namespace tint {
+namespace ast {
+
+SampledTexture::SampledTexture(ProgramID pid,
+                               const Source& src,
+                               TextureDimension d,
+                               const Type* ty)
+    : Base(pid, src, d), type(ty) {
+  TINT_ASSERT(AST, type);
+}
+
+SampledTexture::SampledTexture(SampledTexture&&) = default;
+
+SampledTexture::~SampledTexture() = default;
+
+std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_" << dim << "<" << type->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+const SampledTexture* SampledTexture::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<SampledTexture>(src, dim, ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sampled_texture.h b/src/tint/ast/sampled_texture.h
new file mode 100644
index 0000000..af2cab3
--- /dev/null
+++ b/src/tint/ast/sampled_texture.h
@@ -0,0 +1,58 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_SAMPLED_TEXTURE_H_
+#define SRC_TINT_AST_SAMPLED_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A sampled texture type.
+class SampledTexture : public Castable<SampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the sampled texture
+  SampledTexture(ProgramID pid,
+                 const Source& src,
+                 TextureDimension dim,
+                 const Type* type);
+  /// Move constructor
+  SampledTexture(SampledTexture&&);
+  ~SampledTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const SampledTexture* Clone(CloneContext* ctx) const override;
+
+  /// The subtype of the sampled texture
+  const Type* const type;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_SAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/sampled_texture_test.cc b/src/tint/ast/sampled_texture_test.cc
new file mode 100644
index 0000000..033751d
--- /dev/null
+++ b/src/tint/ast/sampled_texture_test.cc
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/sampled_texture.h"
+
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstSampledTextureTest = TestHelper;
+
+TEST_F(AstSampledTextureTest, IsTexture) {
+  auto* f32 = create<F32>();
+  Texture* ty = create<SampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstSampledTextureTest, Dim) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->dim, TextureDimension::k3d);
+}
+
+TEST_F(AstSampledTextureTest, Type) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type, f32);
+}
+
+TEST_F(AstSampledTextureTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_3d<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sampler.cc b/src/tint/ast/sampler.cc
new file mode 100644
index 0000000..2db571d
--- /dev/null
+++ b/src/tint/ast/sampler.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/sampler.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Sampler);
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, SamplerKind kind) {
+  switch (kind) {
+    case SamplerKind::kSampler:
+      out << "sampler";
+      break;
+    case SamplerKind::kComparisonSampler:
+      out << "comparison_sampler";
+      break;
+  }
+  return out;
+}
+
+Sampler::Sampler(ProgramID pid, const Source& src, SamplerKind k)
+    : Base(pid, src), kind(k) {}
+
+Sampler::Sampler(Sampler&&) = default;
+
+Sampler::~Sampler() = default;
+
+std::string Sampler::FriendlyName(const SymbolTable&) const {
+  return kind == SamplerKind::kSampler ? "sampler" : "sampler_comparison";
+}
+
+const Sampler* Sampler::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<Sampler>(src, kind);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sampler.h b/src/tint/ast/sampler.h
new file mode 100644
index 0000000..7552e22
--- /dev/null
+++ b/src/tint/ast/sampler.h
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_SAMPLER_H_
+#define SRC_TINT_AST_SAMPLER_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The different kinds of samplers
+enum class SamplerKind {
+  /// A regular sampler
+  kSampler,
+  /// A comparison sampler
+  kComparisonSampler
+};
+
+/// @param out the std::ostream to write to
+/// @param kind the SamplerKind
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, SamplerKind kind);
+
+/// A sampler type.
+class Sampler : public Castable<Sampler, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param kind the kind of sampler
+  Sampler(ProgramID pid, const Source& src, SamplerKind kind);
+  /// Move constructor
+  Sampler(Sampler&&);
+  ~Sampler() override;
+
+  /// @returns true if this is a comparison sampler
+  bool IsComparison() const { return kind == SamplerKind::kComparisonSampler; }
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Sampler* Clone(CloneContext* ctx) const override;
+
+  /// The sampler type
+  const SamplerKind kind;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_SAMPLER_H_
diff --git a/src/tint/ast/sampler_test.cc b/src/tint/ast/sampler_test.cc
new file mode 100644
index 0000000..2ae7d42
--- /dev/null
+++ b/src/tint/ast/sampler_test.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/sampler.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstSamplerTest = TestHelper;
+
+TEST_F(AstSamplerTest, Creation) {
+  auto* s = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_EQ(s->kind, SamplerKind::kSampler);
+}
+
+TEST_F(AstSamplerTest, Creation_ComparisonSampler) {
+  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
+  EXPECT_EQ(s->kind, SamplerKind::kComparisonSampler);
+  EXPECT_TRUE(s->IsComparison());
+}
+
+TEST_F(AstSamplerTest, FriendlyNameSampler) {
+  auto* s = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler");
+}
+
+TEST_F(AstSamplerTest, FriendlyNameComparisonSampler) {
+  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler_comparison");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sint_literal_expression.cc b/src/tint/ast/sint_literal_expression.cc
new file mode 100644
index 0000000..fc0a4b3
--- /dev/null
+++ b/src/tint/ast/sint_literal_expression.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/sint_literal_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::SintLiteralExpression);
+
+namespace tint {
+namespace ast {
+
+SintLiteralExpression::SintLiteralExpression(ProgramID pid,
+                                             const Source& src,
+                                             int32_t val)
+    : Base(pid, src), value(val) {}
+
+SintLiteralExpression::~SintLiteralExpression() = default;
+
+uint32_t SintLiteralExpression::ValueAsU32() const {
+  return static_cast<uint32_t>(value);
+}
+
+const SintLiteralExpression* SintLiteralExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<SintLiteralExpression>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/sint_literal_expression.h b/src/tint/ast/sint_literal_expression.h
new file mode 100644
index 0000000..bc0f60b
--- /dev/null
+++ b/src/tint/ast/sint_literal_expression.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
+
+#include <string>
+
+#include "src/tint/ast/int_literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A signed int literal
+class SintLiteralExpression
+    : public Castable<SintLiteralExpression, IntLiteralExpression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the signed int literals value
+  SintLiteralExpression(ProgramID pid, const Source& src, int32_t value);
+  ~SintLiteralExpression() override;
+
+  /// @returns the literal value as a u32
+  uint32_t ValueAsU32() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const SintLiteralExpression* Clone(CloneContext* ctx) const override;
+
+  /// The int literal value
+  const int32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/sint_literal_expression_test.cc b/src/tint/ast/sint_literal_expression_test.cc
new file mode 100644
index 0000000..f19fff9
--- /dev/null
+++ b/src/tint/ast/sint_literal_expression_test.cc
@@ -0,0 +1,31 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using SintLiteralExpressionTest = TestHelper;
+
+TEST_F(SintLiteralExpressionTest, Value) {
+  auto* i = create<SintLiteralExpression>(47);
+  ASSERT_TRUE(i->Is<SintLiteralExpression>());
+  EXPECT_EQ(i->value, 47);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/stage_attribute.cc b/src/tint/ast/stage_attribute.cc
new file mode 100644
index 0000000..92cc802
--- /dev/null
+++ b/src/tint/ast/stage_attribute.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StageAttribute);
+
+namespace tint {
+namespace ast {
+
+StageAttribute::StageAttribute(ProgramID pid,
+                               const Source& src,
+                               PipelineStage s)
+    : Base(pid, src), stage(s) {}
+
+StageAttribute::~StageAttribute() = default;
+
+std::string StageAttribute::Name() const {
+  return "stage";
+}
+
+const StageAttribute* StageAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StageAttribute>(src, stage);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/stage_attribute.h b/src/tint/ast/stage_attribute.h
new file mode 100644
index 0000000..b523538
--- /dev/null
+++ b/src/tint/ast/stage_attribute.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STAGE_ATTRIBUTE_H_
+#define SRC_TINT_AST_STAGE_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/pipeline_stage.h"
+
+namespace tint {
+namespace ast {
+
+/// A workgroup attribute
+class StageAttribute : public Castable<StageAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param stage the pipeline stage
+  /// @param source the source of this attribute
+  StageAttribute(ProgramID program_id,
+                 const Source& source,
+                 PipelineStage stage);
+  ~StageAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StageAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The pipeline stage
+  const PipelineStage stage;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STAGE_ATTRIBUTE_H_
diff --git a/src/tint/ast/stage_attribute_test.cc b/src/tint/ast/stage_attribute_test.cc
new file mode 100644
index 0000000..e1cc93a
--- /dev/null
+++ b/src/tint/ast/stage_attribute_test.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/workgroup_attribute.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StageAttributeTest = TestHelper;
+
+TEST_F(StageAttributeTest, Creation_1param) {
+  auto* d = create<StageAttribute>(PipelineStage::kFragment);
+  EXPECT_EQ(d->stage, PipelineStage::kFragment);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/statement.cc b/src/tint/ast/statement.cc
new file mode 100644
index 0000000..de02bbd
--- /dev/null
+++ b/src/tint/ast/statement.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/statement.h"
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/variable_decl_statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Statement);
+
+namespace tint {
+namespace ast {
+
+Statement::Statement(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Statement::Statement(Statement&&) = default;
+
+Statement::~Statement() = default;
+
+const char* Statement::Name() const {
+  if (Is<AssignmentStatement>()) {
+    return "assignment statement";
+  }
+  if (Is<BlockStatement>()) {
+    return "block statement";
+  }
+  if (Is<BreakStatement>()) {
+    return "break statement";
+  }
+  if (Is<CaseStatement>()) {
+    return "case statement";
+  }
+  if (Is<CallStatement>()) {
+    return "function call";
+  }
+  if (Is<ContinueStatement>()) {
+    return "continue statement";
+  }
+  if (Is<DiscardStatement>()) {
+    return "discard statement";
+  }
+  if (Is<ElseStatement>()) {
+    return "else statement";
+  }
+  if (Is<FallthroughStatement>()) {
+    return "fallthrough statement";
+  }
+  if (Is<IfStatement>()) {
+    return "if statement";
+  }
+  if (Is<LoopStatement>()) {
+    return "loop statement";
+  }
+  if (Is<ReturnStatement>()) {
+    return "return statement";
+  }
+  if (Is<SwitchStatement>()) {
+    return "switch statement";
+  }
+  if (Is<VariableDeclStatement>()) {
+    return "variable declaration";
+  }
+  return "statement";
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/statement.h b/src/tint/ast/statement.h
new file mode 100644
index 0000000..e6c9a1c
--- /dev/null
+++ b/src/tint/ast/statement.h
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STATEMENT_H_
+#define SRC_TINT_AST_STATEMENT_H_
+
+#include <vector>
+
+#include "src/tint/ast/node.h"
+
+namespace tint {
+namespace ast {
+
+/// Base statement class
+class Statement : public Castable<Statement, Node> {
+ public:
+  ~Statement() override;
+
+  /// @returns the human readable name for the statement type.
+  const char* Name() const;
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of the expression
+  Statement(ProgramID pid, const Source& src);
+  /// Move constructor
+  Statement(Statement&&);
+};
+
+/// A list of statements
+using StatementList = std::vector<const Statement*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STATEMENT_H_
diff --git a/src/tint/ast/storage_class.cc b/src/tint/ast/storage_class.cc
new file mode 100644
index 0000000..b760647
--- /dev/null
+++ b/src/tint/ast/storage_class.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/storage_class.h"
+
+namespace tint {
+namespace ast {
+
+const char* ToString(StorageClass sc) {
+  switch (sc) {
+    case StorageClass::kInvalid:
+      return "invalid";
+    case StorageClass::kNone:
+      return "none";
+    case StorageClass::kInput:
+      return "in";
+    case StorageClass::kOutput:
+      return "out";
+    case StorageClass::kUniform:
+      return "uniform";
+    case StorageClass::kWorkgroup:
+      return "workgroup";
+    case StorageClass::kUniformConstant:
+      return "uniform_constant";
+    case StorageClass::kStorage:
+      return "storage";
+    case StorageClass::kPrivate:
+      return "private";
+    case StorageClass::kFunction:
+      return "function";
+  }
+  return "<unknown>";
+}
+std::ostream& operator<<(std::ostream& out, StorageClass sc) {
+  out << ToString(sc);
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/storage_class.h b/src/tint/ast/storage_class.h
new file mode 100644
index 0000000..bc22468
--- /dev/null
+++ b/src/tint/ast/storage_class.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STORAGE_CLASS_H_
+#define SRC_TINT_AST_STORAGE_CLASS_H_
+
+#include <ostream>
+
+namespace tint {
+namespace ast {
+
+/// Storage class of a given pointer.
+enum class StorageClass {
+  kInvalid = -1,
+  kNone,
+  kInput,
+  kOutput,
+  kUniform,
+  kWorkgroup,
+  kUniformConstant,
+  kStorage,
+  kPrivate,
+  kFunction
+};
+
+/// @returns true if the StorageClass is host-shareable
+/// @param sc the StorageClass
+/// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable
+inline bool IsHostShareable(StorageClass sc) {
+  return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
+}
+
+/// @param sc the StorageClass
+/// @return the name of the given storage class
+const char* ToString(StorageClass sc);
+
+/// @param out the std::ostream to write to
+/// @param sc the StorageClass
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, StorageClass sc);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STORAGE_CLASS_H_
diff --git a/src/tint/ast/storage_texture.cc b/src/tint/ast/storage_texture.cc
new file mode 100644
index 0000000..9f818ec
--- /dev/null
+++ b/src/tint/ast/storage_texture.cc
@@ -0,0 +1,146 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/storage_texture.h"
+
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StorageTexture);
+
+namespace tint {
+namespace ast {
+
+// Note, these names match the names in the WGSL spec. This behaviour is used
+// in the WGSL writer to emit the texture format names.
+std::ostream& operator<<(std::ostream& out, TexelFormat format) {
+  switch (format) {
+    case TexelFormat::kNone:
+      out << "none";
+      break;
+    case TexelFormat::kR32Uint:
+      out << "r32uint";
+      break;
+    case TexelFormat::kR32Sint:
+      out << "r32sint";
+      break;
+    case TexelFormat::kR32Float:
+      out << "r32float";
+      break;
+    case TexelFormat::kRgba8Unorm:
+      out << "rgba8unorm";
+      break;
+    case TexelFormat::kRgba8Snorm:
+      out << "rgba8snorm";
+      break;
+    case TexelFormat::kRgba8Uint:
+      out << "rgba8uint";
+      break;
+    case TexelFormat::kRgba8Sint:
+      out << "rgba8sint";
+      break;
+    case TexelFormat::kRg32Uint:
+      out << "rg32uint";
+      break;
+    case TexelFormat::kRg32Sint:
+      out << "rg32sint";
+      break;
+    case TexelFormat::kRg32Float:
+      out << "rg32float";
+      break;
+    case TexelFormat::kRgba16Uint:
+      out << "rgba16uint";
+      break;
+    case TexelFormat::kRgba16Sint:
+      out << "rgba16sint";
+      break;
+    case TexelFormat::kRgba16Float:
+      out << "rgba16float";
+      break;
+    case TexelFormat::kRgba32Uint:
+      out << "rgba32uint";
+      break;
+    case TexelFormat::kRgba32Sint:
+      out << "rgba32sint";
+      break;
+    case TexelFormat::kRgba32Float:
+      out << "rgba32float";
+      break;
+  }
+  return out;
+}
+
+StorageTexture::StorageTexture(ProgramID pid,
+                               const Source& src,
+                               TextureDimension d,
+                               TexelFormat fmt,
+                               const Type* subtype,
+                               Access ac)
+    : Base(pid, src, d), format(fmt), type(subtype), access(ac) {}
+
+StorageTexture::StorageTexture(StorageTexture&&) = default;
+
+StorageTexture::~StorageTexture() = default;
+
+std::string StorageTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_storage_" << dim << "<" << format << ", " << access << ">";
+  return out.str();
+}
+
+const StorageTexture* StorageTexture::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<StorageTexture>(src, dim, format, ty, access);
+}
+
+Type* StorageTexture::SubtypeFor(TexelFormat format, ProgramBuilder& builder) {
+  switch (format) {
+    case TexelFormat::kR32Uint:
+    case TexelFormat::kRgba8Uint:
+    case TexelFormat::kRg32Uint:
+    case TexelFormat::kRgba16Uint:
+    case TexelFormat::kRgba32Uint: {
+      return builder.create<U32>();
+    }
+
+    case TexelFormat::kR32Sint:
+    case TexelFormat::kRgba8Sint:
+    case TexelFormat::kRg32Sint:
+    case TexelFormat::kRgba16Sint:
+    case TexelFormat::kRgba32Sint: {
+      return builder.create<I32>();
+    }
+
+    case TexelFormat::kRgba8Unorm:
+    case TexelFormat::kRgba8Snorm:
+    case TexelFormat::kR32Float:
+    case TexelFormat::kRg32Float:
+    case TexelFormat::kRgba16Float:
+    case TexelFormat::kRgba32Float: {
+      return builder.create<F32>();
+    }
+
+    case TexelFormat::kNone:
+      break;
+  }
+
+  return nullptr;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/storage_texture.h b/src/tint/ast/storage_texture.h
new file mode 100644
index 0000000..e1f74d9
--- /dev/null
+++ b/src/tint/ast/storage_texture.h
@@ -0,0 +1,101 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STORAGE_TEXTURE_H_
+#define SRC_TINT_AST_STORAGE_TEXTURE_H_
+
+#include <string>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// The texel format in the storage texture
+enum class TexelFormat {
+  kNone = -1,
+  kRgba8Unorm,
+  kRgba8Snorm,
+  kRgba8Uint,
+  kRgba8Sint,
+  kRgba16Uint,
+  kRgba16Sint,
+  kRgba16Float,
+  kR32Uint,
+  kR32Sint,
+  kR32Float,
+  kRg32Uint,
+  kRg32Sint,
+  kRg32Float,
+  kRgba32Uint,
+  kRgba32Sint,
+  kRgba32Float,
+};
+
+/// @param out the std::ostream to write to
+/// @param format the TexelFormat
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, TexelFormat format);
+
+/// A storage texture type.
+class StorageTexture : public Castable<StorageTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  /// @param format the image format of the texture
+  /// @param subtype the storage subtype. Use SubtypeFor() to calculate this.
+  /// @param access_control the access control for the texture.
+  StorageTexture(ProgramID pid,
+                 const Source& src,
+                 TextureDimension dim,
+                 TexelFormat format,
+                 const Type* subtype,
+                 Access access_control);
+
+  /// Move constructor
+  StorageTexture(StorageTexture&&);
+  ~StorageTexture() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const StorageTexture* Clone(CloneContext* ctx) const override;
+
+  /// @param format the storage texture image format
+  /// @param builder the ProgramBuilder used to build the returned type
+  /// @returns the storage texture subtype for the given TexelFormat
+  static Type* SubtypeFor(TexelFormat format, ProgramBuilder& builder);
+
+  /// The image format
+  const TexelFormat format;
+
+  /// The storage subtype
+  const Type* const type;
+
+  /// The access control
+  const Access access;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STORAGE_TEXTURE_H_
diff --git a/src/tint/ast/storage_texture_test.cc b/src/tint/ast/storage_texture_test.cc
new file mode 100644
index 0000000..5186b7e
--- /dev/null
+++ b/src/tint/ast/storage_texture_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2020 The Tint Authors->
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/storage_texture.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstStorageTextureTest = TestHelper;
+
+TEST_F(AstStorageTextureTest, IsTexture) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
+  Texture* ty =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Float, subtype, Access::kRead);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_TRUE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstStorageTextureTest, Dim) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
+  auto* s =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Float, subtype, Access::kRead);
+  EXPECT_EQ(s->dim, TextureDimension::k2dArray);
+}
+
+TEST_F(AstStorageTextureTest, Format) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
+  auto* s =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Float, subtype, Access::kRead);
+  EXPECT_EQ(s->format, TexelFormat::kRgba32Float);
+}
+
+TEST_F(AstStorageTextureTest, FriendlyName) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
+  auto* s =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Float, subtype, Access::kRead);
+  EXPECT_EQ(s->FriendlyName(Symbols()),
+            "texture_storage_2d_array<rgba32float, read>");
+}
+
+TEST_F(AstStorageTextureTest, F32) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this);
+  Type* s =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Float, subtype, Access::kRead);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<F32>());
+}
+
+TEST_F(AstStorageTextureTest, U32) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRg32Uint, *this);
+  Type* s =
+      create<StorageTexture>(TextureDimension::k2dArray, TexelFormat::kRg32Uint,
+                             subtype, Access::kRead);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<U32>());
+}
+
+TEST_F(AstStorageTextureTest, I32) {
+  auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Sint, *this);
+  Type* s =
+      create<StorageTexture>(TextureDimension::k2dArray,
+                             TexelFormat::kRgba32Sint, subtype, Access::kRead);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type->Is<I32>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/stride_attribute.cc b/src/tint/ast/stride_attribute.cc
new file mode 100644
index 0000000..1c763ac
--- /dev/null
+++ b/src/tint/ast/stride_attribute.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stride_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StrideAttribute);
+
+namespace tint {
+namespace ast {
+
+StrideAttribute::StrideAttribute(ProgramID pid, const Source& src, uint32_t s)
+    : Base(pid, src), stride(s) {}
+
+StrideAttribute::~StrideAttribute() = default;
+
+std::string StrideAttribute::Name() const {
+  return "stride";
+}
+
+const StrideAttribute* StrideAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StrideAttribute>(src, stride);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/stride_attribute.h b/src/tint/ast/stride_attribute.h
new file mode 100644
index 0000000..f2067cb
--- /dev/null
+++ b/src/tint/ast/stride_attribute.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRIDE_ATTRIBUTE_H_
+#define SRC_TINT_AST_STRIDE_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A stride attribute
+class StrideAttribute : public Castable<StrideAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param stride the stride value
+  StrideAttribute(ProgramID pid, const Source& src, uint32_t stride);
+  ~StrideAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StrideAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The stride value
+  const uint32_t stride;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRIDE_ATTRIBUTE_H_
diff --git a/src/tint/ast/stride_attribute_test.cc b/src/tint/ast/stride_attribute_test.cc
new file mode 100644
index 0000000..f8549ec
--- /dev/null
+++ b/src/tint/ast/stride_attribute_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StrideAttributeTest = TestHelper;
+
+TEST_F(StrideAttributeTest, Creation) {
+  auto* d = create<StrideAttribute>(2);
+  EXPECT_EQ(2u, d->stride);
+}
+
+TEST_F(StrideAttributeTest, Source) {
+  auto* d = create<StrideAttribute>(
+      Source{Source::Range{Source::Location{1, 2}, Source::Location{3, 4}}}, 2);
+  EXPECT_EQ(d->source.range.begin.line, 1u);
+  EXPECT_EQ(d->source.range.begin.column, 2u);
+  EXPECT_EQ(d->source.range.end.line, 3u);
+  EXPECT_EQ(d->source.range.end.column, 4u);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct.cc b/src/tint/ast/struct.cc
new file mode 100644
index 0000000..b3f5c3a
--- /dev/null
+++ b/src/tint/ast/struct.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct.h"
+
+#include <string>
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Struct);
+
+namespace tint {
+namespace ast {
+
+Struct::Struct(ProgramID pid,
+               const Source& src,
+               Symbol n,
+               StructMemberList m,
+               AttributeList attrs)
+    : Base(pid, src, n), members(std::move(m)), attributes(std::move(attrs)) {
+  for (auto* mem : members) {
+    TINT_ASSERT(AST, mem);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, mem, program_id);
+  }
+  for (auto* attr : attributes) {
+    TINT_ASSERT(AST, attr);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+  }
+}
+
+Struct::Struct(Struct&&) = default;
+
+Struct::~Struct() = default;
+
+const Struct* Struct::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto n = ctx->Clone(name);
+  auto mem = ctx->Clone(members);
+  auto attrs = ctx->Clone(attributes);
+  return ctx->dst->create<Struct>(src, n, mem, attrs);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct.h b/src/tint/ast/struct.h
new file mode 100644
index 0000000..95bc376
--- /dev/null
+++ b/src/tint/ast/struct.h
@@ -0,0 +1,63 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_H_
+#define SRC_TINT_AST_STRUCT_H_
+
+#include <string>
+#include <utility>
+
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/struct_member.h"
+#include "src/tint/ast/type_decl.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct statement.
+class Struct : public Castable<Struct, TypeDecl> {
+ public:
+  /// Create a new struct statement
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node for the import statement
+  /// @param name The name of the structure
+  /// @param members The struct members
+  /// @param attributes The struct attributes
+  Struct(ProgramID pid,
+         const Source& src,
+         Symbol name,
+         StructMemberList members,
+         AttributeList attributes);
+  /// Move constructor
+  Struct(Struct&&);
+
+  ~Struct() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const Struct* Clone(CloneContext* ctx) const override;
+
+  /// The members
+  const StructMemberList members;
+
+  /// The struct attributes
+  const AttributeList attributes;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_H_
diff --git a/src/tint/ast/struct_block_attribute.cc b/src/tint/ast/struct_block_attribute.cc
new file mode 100644
index 0000000..77ed34e
--- /dev/null
+++ b/src/tint/ast/struct_block_attribute.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructBlockAttribute);
+
+namespace tint {
+namespace ast {
+
+StructBlockAttribute::StructBlockAttribute(ProgramID pid, const Source& src)
+    : Base(pid, src) {}
+
+StructBlockAttribute::~StructBlockAttribute() = default;
+
+std::string StructBlockAttribute::Name() const {
+  return "block";
+}
+
+const StructBlockAttribute* StructBlockAttribute::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StructBlockAttribute>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_block_attribute.h b/src/tint/ast/struct_block_attribute.h
new file mode 100644
index 0000000..df929f8
--- /dev/null
+++ b/src/tint/ast/struct_block_attribute.h
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_BLOCK_ATTRIBUTE_H_
+#define SRC_TINT_AST_STRUCT_BLOCK_ATTRIBUTE_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// The struct block attribute
+class StructBlockAttribute : public Castable<StructBlockAttribute, Attribute> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  StructBlockAttribute(ProgramID pid, const Source& src);
+  ~StructBlockAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StructBlockAttribute* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_BLOCK_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member.cc b/src/tint/ast/struct_member.cc
new file mode 100644
index 0000000..2afb10d
--- /dev/null
+++ b/src/tint/ast/struct_member.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_member.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMember);
+
+namespace tint {
+namespace ast {
+
+StructMember::StructMember(ProgramID pid,
+                           const Source& src,
+                           const Symbol& sym,
+                           const ast::Type* ty,
+                           AttributeList attrs)
+    : Base(pid, src), symbol(sym), type(ty), attributes(std::move(attrs)) {
+  TINT_ASSERT(AST, type);
+  TINT_ASSERT(AST, symbol.IsValid());
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
+  for (auto* attr : attributes) {
+    TINT_ASSERT(AST, attr);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+  }
+}
+
+StructMember::StructMember(StructMember&&) = default;
+
+StructMember::~StructMember() = default;
+
+const StructMember* StructMember::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto sym = ctx->Clone(symbol);
+  auto* ty = ctx->Clone(type);
+  auto attrs = ctx->Clone(attributes);
+  return ctx->dst->create<StructMember>(src, sym, ty, attrs);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member.h b/src/tint/ast/struct_member.h
new file mode 100644
index 0000000..cf2e47b
--- /dev/null
+++ b/src/tint/ast/struct_member.h
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_MEMBER_H_
+#define SRC_TINT_AST_STRUCT_MEMBER_H_
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declaration
+class Type;
+
+/// A struct member statement.
+class StructMember : public Castable<StructMember, Node> {
+ public:
+  /// Create a new struct member statement
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node for the struct member statement
+  /// @param sym The struct member symbol
+  /// @param type The struct member type
+  /// @param attributes The struct member attributes
+  StructMember(ProgramID pid,
+               const Source& src,
+               const Symbol& sym,
+               const ast::Type* type,
+               AttributeList attributes);
+  /// Move constructor
+  StructMember(StructMember&&);
+
+  ~StructMember() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StructMember* Clone(CloneContext* ctx) const override;
+
+  /// The symbol
+  const Symbol symbol;
+
+  /// The type
+  const ast::Type* const type;
+
+  /// The attributes
+  const AttributeList attributes;
+};
+
+/// A list of struct members
+using StructMemberList = std::vector<const StructMember*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_MEMBER_H_
diff --git a/src/tint/ast/struct_member_align_attribute.cc b/src/tint/ast/struct_member_align_attribute.cc
new file mode 100644
index 0000000..7790800
--- /dev/null
+++ b/src/tint/ast/struct_member_align_attribute.cc
@@ -0,0 +1,46 @@
+// 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/ast/struct_member_align_attribute.h"
+
+#include <string>
+
+#include "src/tint/clone_context.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberAlignAttribute);
+
+namespace tint {
+namespace ast {
+
+StructMemberAlignAttribute::StructMemberAlignAttribute(ProgramID pid,
+                                                       const Source& src,
+                                                       uint32_t a)
+    : Base(pid, src), align(a) {}
+
+StructMemberAlignAttribute::~StructMemberAlignAttribute() = default;
+
+std::string StructMemberAlignAttribute::Name() const {
+  return "align";
+}
+
+const StructMemberAlignAttribute* StructMemberAlignAttribute::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StructMemberAlignAttribute>(src, align);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_align_attribute.h b/src/tint/ast/struct_member_align_attribute.h
new file mode 100644
index 0000000..b90c71f
--- /dev/null
+++ b/src/tint/ast/struct_member_align_attribute.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
+#define SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
+
+#include <stddef.h>
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct member align attribute
+class StructMemberAlignAttribute
+    : public Castable<StructMemberAlignAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param align the align value
+  StructMemberAlignAttribute(ProgramID pid, const Source& src, uint32_t align);
+  ~StructMemberAlignAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StructMemberAlignAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The align value
+  const uint32_t align;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_align_attribute_test.cc b/src/tint/ast/struct_member_align_attribute_test.cc
new file mode 100644
index 0000000..9dcddd4
--- /dev/null
+++ b/src/tint/ast/struct_member_align_attribute_test.cc
@@ -0,0 +1,32 @@
+// 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/ast/struct_member_align_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberAlignAttributeTest = TestHelper;
+
+TEST_F(StructMemberAlignAttributeTest, Creation) {
+  auto* d = create<StructMemberAlignAttribute>(2);
+  EXPECT_EQ(2u, d->align);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_offset_attribute.cc b/src/tint/ast/struct_member_offset_attribute.cc
new file mode 100644
index 0000000..a854f8c
--- /dev/null
+++ b/src/tint/ast/struct_member_offset_attribute.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_member_offset_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberOffsetAttribute);
+
+namespace tint {
+namespace ast {
+
+StructMemberOffsetAttribute::StructMemberOffsetAttribute(ProgramID pid,
+                                                         const Source& src,
+                                                         uint32_t o)
+    : Base(pid, src), offset(o) {}
+
+StructMemberOffsetAttribute::~StructMemberOffsetAttribute() = default;
+
+std::string StructMemberOffsetAttribute::Name() const {
+  return "offset";
+}
+
+const StructMemberOffsetAttribute* StructMemberOffsetAttribute::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StructMemberOffsetAttribute>(src, offset);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_offset_attribute.h b/src/tint/ast/struct_member_offset_attribute.h
new file mode 100644
index 0000000..f599a7e
--- /dev/null
+++ b/src/tint/ast/struct_member_offset_attribute.h
@@ -0,0 +1,63 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
+#define SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct member offset attribute
+/// @note The WGSL spec removed the `@offset(n)` attribute for `@size(n)`
+/// and `@align(n)` in https://github.com/gpuweb/gpuweb/pull/1447. However
+/// this attribute is kept because the SPIR-V reader has to deal with absolute
+/// offsets, and transforming these to size / align is complex and can be done
+/// in a number of ways. The Resolver is responsible for consuming the size and
+/// align attributes and transforming these into absolute offsets. It is
+/// trivial for the Resolver to handle `@offset(n)` or `@size(n)` /
+/// `@align(n)` attributes, so this is what we do, keeping all the layout
+/// logic in one place.
+class StructMemberOffsetAttribute
+    : public Castable<StructMemberOffsetAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param offset the offset value
+  StructMemberOffsetAttribute(ProgramID pid,
+                              const Source& src,
+                              uint32_t offset);
+  ~StructMemberOffsetAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StructMemberOffsetAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The offset value
+  const uint32_t offset;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_offset_attribute_test.cc b/src/tint/ast/struct_member_offset_attribute_test.cc
new file mode 100644
index 0000000..022821a
--- /dev/null
+++ b/src/tint/ast/struct_member_offset_attribute_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberOffsetAttributeTest = TestHelper;
+
+TEST_F(StructMemberOffsetAttributeTest, Creation) {
+  auto* d = create<StructMemberOffsetAttribute>(2);
+  EXPECT_EQ(2u, d->offset);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_size_attribute.cc b/src/tint/ast/struct_member_size_attribute.cc
new file mode 100644
index 0000000..d76820a
--- /dev/null
+++ b/src/tint/ast/struct_member_size_attribute.cc
@@ -0,0 +1,46 @@
+// 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/ast/struct_member_size_attribute.h"
+
+#include <string>
+
+#include "src/tint/clone_context.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberSizeAttribute);
+
+namespace tint {
+namespace ast {
+
+StructMemberSizeAttribute::StructMemberSizeAttribute(ProgramID pid,
+                                                     const Source& src,
+                                                     uint32_t sz)
+    : Base(pid, src), size(sz) {}
+
+StructMemberSizeAttribute::~StructMemberSizeAttribute() = default;
+
+std::string StructMemberSizeAttribute::Name() const {
+  return "size";
+}
+
+const StructMemberSizeAttribute* StructMemberSizeAttribute::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<StructMemberSizeAttribute>(src, size);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_size_attribute.h b/src/tint/ast/struct_member_size_attribute.h
new file mode 100644
index 0000000..a6397ab
--- /dev/null
+++ b/src/tint/ast/struct_member_size_attribute.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
+#define SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
+
+#include <stddef.h>
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct member size attribute
+class StructMemberSizeAttribute
+    : public Castable<StructMemberSizeAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param size the size value
+  StructMemberSizeAttribute(ProgramID pid, const Source& src, uint32_t size);
+  ~StructMemberSizeAttribute() override;
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const StructMemberSizeAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The size value
+  const uint32_t size;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_size_attribute_test.cc b/src/tint/ast/struct_member_size_attribute_test.cc
new file mode 100644
index 0000000..346535d
--- /dev/null
+++ b/src/tint/ast/struct_member_size_attribute_test.cc
@@ -0,0 +1,32 @@
+// 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/ast/struct_member_size_attribute.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberSizeAttributeTest = TestHelper;
+
+TEST_F(StructMemberSizeAttributeTest, Creation) {
+  auto* d = create<StructMemberSizeAttribute>(2);
+  EXPECT_EQ(2u, d->size);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_member_test.cc b/src/tint/ast/struct_member_test.cc
new file mode 100644
index 0000000..c675a47
--- /dev/null
+++ b/src/tint/ast/struct_member_test.cc
@@ -0,0 +1,98 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberTest = TestHelper;
+
+TEST_F(StructMemberTest, Creation) {
+  auto* st = Member("a", ty.i32(), {MemberSize(4)});
+  EXPECT_EQ(st->symbol, Symbol(1, ID()));
+  EXPECT_TRUE(st->type->Is<ast::I32>());
+  EXPECT_EQ(st->attributes.size(), 1u);
+  EXPECT_TRUE(st->attributes[0]->Is<StructMemberSizeAttribute>());
+  EXPECT_EQ(st->source.range.begin.line, 0u);
+  EXPECT_EQ(st->source.range.begin.column, 0u);
+  EXPECT_EQ(st->source.range.end.line, 0u);
+  EXPECT_EQ(st->source.range.end.column, 0u);
+}
+
+TEST_F(StructMemberTest, CreationWithSource) {
+  auto* st = Member(
+      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
+      "a", ty.i32());
+  EXPECT_EQ(st->symbol, Symbol(1, ID()));
+  EXPECT_TRUE(st->type->Is<ast::I32>());
+  EXPECT_EQ(st->attributes.size(), 0u);
+  EXPECT_EQ(st->source.range.begin.line, 27u);
+  EXPECT_EQ(st->source.range.begin.column, 4u);
+  EXPECT_EQ(st->source.range.end.line, 27u);
+  EXPECT_EQ(st->source.range.end.column, 8u);
+}
+
+TEST_F(StructMemberTest, Assert_Empty_Symbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Member("", b.ty.i32());
+      },
+      "internal compiler error");
+}
+
+TEST_F(StructMemberTest, Assert_Null_Type) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Member("a", nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(StructMemberTest, Assert_Null_Attribute) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Member("a", b.ty.i32(), {b.MemberSize(4), nullptr});
+      },
+      "internal compiler error");
+}
+
+TEST_F(StructMemberTest, Assert_DifferentProgramID_Symbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Member(b2.Sym("a"), b1.ty.i32(), {b1.MemberSize(4)});
+      },
+      "internal compiler error");
+}
+
+TEST_F(StructMemberTest, Assert_DifferentProgramID_Attribute) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Member("a", b1.ty.i32(), {b2.MemberSize(4)});
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/struct_test.cc b/src/tint/ast/struct_test.cc
new file mode 100644
index 0000000..c26d8cc
--- /dev/null
+++ b/src/tint/ast/struct_test.cc
@@ -0,0 +1,131 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct.h"
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstStructTest = TestHelper;
+
+TEST_F(AstStructTest, Creation) {
+  auto name = Sym("s");
+  auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())},
+                           AttributeList{});
+  EXPECT_EQ(s->name, name);
+  EXPECT_EQ(s->members.size(), 1u);
+  EXPECT_TRUE(s->attributes.empty());
+  EXPECT_EQ(s->source.range.begin.line, 0u);
+  EXPECT_EQ(s->source.range.begin.column, 0u);
+  EXPECT_EQ(s->source.range.end.line, 0u);
+  EXPECT_EQ(s->source.range.end.column, 0u);
+}
+
+TEST_F(AstStructTest, Creation_WithAttributes) {
+  auto name = Sym("s");
+  AttributeList attrs;
+  attrs.push_back(create<StructBlockAttribute>());
+
+  auto* s =
+      create<Struct>(name, StructMemberList{Member("a", ty.i32())}, attrs);
+  EXPECT_EQ(s->name, name);
+  EXPECT_EQ(s->members.size(), 1u);
+  ASSERT_EQ(s->attributes.size(), 1u);
+  EXPECT_TRUE(s->attributes[0]->Is<StructBlockAttribute>());
+  EXPECT_EQ(s->source.range.begin.line, 0u);
+  EXPECT_EQ(s->source.range.begin.column, 0u);
+  EXPECT_EQ(s->source.range.end.line, 0u);
+  EXPECT_EQ(s->source.range.end.column, 0u);
+}
+
+TEST_F(AstStructTest, CreationWithSourceAndAttributes) {
+  auto name = Sym("s");
+  auto* s = create<Struct>(
+      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
+      name, StructMemberList{Member("a", ty.i32())},
+      AttributeList{create<StructBlockAttribute>()});
+  EXPECT_EQ(s->name, name);
+  EXPECT_EQ(s->members.size(), 1u);
+  ASSERT_EQ(s->attributes.size(), 1u);
+  EXPECT_TRUE(s->attributes[0]->Is<StructBlockAttribute>());
+  EXPECT_EQ(s->source.range.begin.line, 27u);
+  EXPECT_EQ(s->source.range.begin.column, 4u);
+  EXPECT_EQ(s->source.range.end.line, 27u);
+  EXPECT_EQ(s->source.range.end.column, 8u);
+}
+
+TEST_F(AstStructTest, Assert_Null_StructMember) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<Struct>(b.Sym("S"),
+                         StructMemberList{b.Member("a", b.ty.i32()), nullptr},
+                         AttributeList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(AstStructTest, Assert_Null_Attribute) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<Struct>(b.Sym("S"),
+                         StructMemberList{b.Member("a", b.ty.i32())},
+                         AttributeList{nullptr});
+      },
+      "internal compiler error");
+}
+
+TEST_F(AstStructTest, Assert_DifferentProgramID_StructMember) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<Struct>(b1.Sym("S"),
+                          StructMemberList{b2.Member("a", b2.ty.i32())},
+                          AttributeList{});
+      },
+      "internal compiler error");
+}
+
+TEST_F(AstStructTest, Assert_DifferentProgramID_Attribute) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<Struct>(b1.Sym("S"),
+                          StructMemberList{b1.Member("a", b1.ty.i32())},
+                          AttributeList{b2.create<StructBlockAttribute>()});
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/switch_statement.cc b/src/tint/ast/switch_statement.cc
new file mode 100644
index 0000000..02cbb61
--- /dev/null
+++ b/src/tint/ast/switch_statement.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/switch_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::SwitchStatement);
+
+namespace tint {
+namespace ast {
+
+SwitchStatement::SwitchStatement(ProgramID pid,
+                                 const Source& src,
+                                 const Expression* cond,
+                                 CaseStatementList b)
+    : Base(pid, src), condition(cond), body(b) {
+  TINT_ASSERT(AST, condition);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
+  for (auto* stmt : body) {
+    TINT_ASSERT(AST, stmt);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id);
+  }
+}
+
+SwitchStatement::SwitchStatement(SwitchStatement&&) = default;
+
+SwitchStatement::~SwitchStatement() = default;
+
+const SwitchStatement* SwitchStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* cond = ctx->Clone(condition);
+  auto b = ctx->Clone(body);
+  return ctx->dst->create<SwitchStatement>(src, cond, b);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h
new file mode 100644
index 0000000..8fa2fb5
--- /dev/null
+++ b/src/tint/ast/switch_statement.h
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_SWITCH_STATEMENT_H_
+#define SRC_TINT_AST_SWITCH_STATEMENT_H_
+
+#include "src/tint/ast/case_statement.h"
+#include "src/tint/ast/expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A switch statement
+class SwitchStatement : public Castable<SwitchStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param condition the switch condition
+  /// @param body the switch body
+  SwitchStatement(ProgramID pid,
+                  const Source& src,
+                  const Expression* condition,
+                  CaseStatementList body);
+  /// Move constructor
+  SwitchStatement(SwitchStatement&&);
+  ~SwitchStatement() override;
+
+  /// @returns true if this is a default statement
+  bool IsDefault() const { return condition == nullptr; }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const SwitchStatement* Clone(CloneContext* ctx) const override;
+
+  /// The switch condition or nullptr if none set
+  const Expression* const condition;
+
+  /// The Switch body
+  const CaseStatementList body;
+  SwitchStatement(const SwitchStatement&) = delete;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_SWITCH_STATEMENT_H_
diff --git a/src/tint/ast/switch_statement_test.cc b/src/tint/ast/switch_statement_test.cc
new file mode 100644
index 0000000..ecbf68c
--- /dev/null
+++ b/src/tint/ast/switch_statement_test.cc
@@ -0,0 +1,118 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/switch_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using SwitchStatementTest = TestHelper;
+
+TEST_F(SwitchStatementTest, Creation) {
+  CaseSelectorList lit;
+  lit.push_back(create<SintLiteralExpression>(1));
+
+  auto* ident = Expr("ident");
+  CaseStatementList body;
+  auto* case_stmt = create<CaseStatement>(lit, Block());
+  body.push_back(case_stmt);
+
+  auto* stmt = create<SwitchStatement>(ident, body);
+  EXPECT_EQ(stmt->condition, ident);
+  ASSERT_EQ(stmt->body.size(), 1u);
+  EXPECT_EQ(stmt->body[0], case_stmt);
+}
+
+TEST_F(SwitchStatementTest, Creation_WithSource) {
+  auto* ident = Expr("ident");
+
+  auto* stmt = create<SwitchStatement>(Source{Source::Location{20, 2}}, ident,
+                                       CaseStatementList());
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(SwitchStatementTest, IsSwitch) {
+  CaseSelectorList lit;
+  lit.push_back(create<SintLiteralExpression>(2));
+
+  auto* ident = Expr("ident");
+  CaseStatementList body;
+  body.push_back(create<CaseStatement>(lit, Block()));
+
+  auto* stmt = create<SwitchStatement>(ident, body);
+  EXPECT_TRUE(stmt->Is<SwitchStatement>());
+}
+
+TEST_F(SwitchStatementTest, Assert_Null_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        CaseStatementList cases;
+        cases.push_back(
+            b.create<CaseStatement>(CaseSelectorList{b.Expr(1)}, b.Block()));
+        b.create<SwitchStatement>(nullptr, cases);
+      },
+      "internal compiler error");
+}
+
+TEST_F(SwitchStatementTest, Assert_Null_CaseStatement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr});
+      },
+      "internal compiler error");
+}
+
+TEST_F(SwitchStatementTest, Assert_DifferentProgramID_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<SwitchStatement>(b2.Expr(true), CaseStatementList{
+                                                      b1.create<CaseStatement>(
+                                                          CaseSelectorList{
+                                                              b1.Expr(1),
+                                                          },
+                                                          b1.Block()),
+                                                  });
+      },
+      "internal compiler error");
+}
+
+TEST_F(SwitchStatementTest, Assert_DifferentProgramID_CaseStatement) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<SwitchStatement>(b1.Expr(true), CaseStatementList{
+                                                      b2.create<CaseStatement>(
+                                                          CaseSelectorList{
+                                                              b2.Expr(1),
+                                                          },
+                                                          b2.Block()),
+                                                  });
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/test_helper.h b/src/tint/ast/test_helper.h
new file mode 100644
index 0000000..2c3be98
--- /dev/null
+++ b/src/tint/ast/test_helper.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TEST_HELPER_H_
+#define SRC_TINT_AST_TEST_HELPER_H_
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace ast {
+
+/// Helper base class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {};
+
+/// Helper class for testing that derives from testing::Test.
+using TestHelper = TestHelperBase<testing::Test>;
+
+/// Helper class for testing that derives from `T`.
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TEST_HELPER_H_
diff --git a/src/tint/ast/texture.cc b/src/tint/ast/texture.cc
new file mode 100644
index 0000000..38d16e4
--- /dev/null
+++ b/src/tint/ast/texture.cc
@@ -0,0 +1,89 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/texture.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Texture);
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::kNone:
+      out << "None";
+      break;
+    case TextureDimension::k1d:
+      out << "1d";
+      break;
+    case TextureDimension::k2d:
+      out << "2d";
+      break;
+    case TextureDimension::k2dArray:
+      out << "2d_array";
+      break;
+    case TextureDimension::k3d:
+      out << "3d";
+      break;
+    case TextureDimension::kCube:
+      out << "cube";
+      break;
+    case TextureDimension::kCubeArray:
+      out << "cube_array";
+      break;
+  }
+  return out;
+}
+
+bool IsTextureArray(TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::k2dArray:
+    case TextureDimension::kCubeArray:
+      return true;
+    case TextureDimension::k2d:
+    case TextureDimension::kNone:
+    case TextureDimension::k1d:
+    case TextureDimension::k3d:
+    case TextureDimension::kCube:
+      return false;
+  }
+  return false;
+}
+
+int NumCoordinateAxes(TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::kNone:
+      return 0;
+    case TextureDimension::k1d:
+      return 1;
+    case TextureDimension::k2d:
+    case TextureDimension::k2dArray:
+      return 2;
+    case TextureDimension::k3d:
+    case TextureDimension::kCube:
+    case TextureDimension::kCubeArray:
+      return 3;
+  }
+  return 0;
+}
+
+Texture::Texture(ProgramID pid, const Source& src, TextureDimension d)
+    : Base(pid, src), dim(d) {}
+
+Texture::Texture(Texture&&) = default;
+
+Texture::~Texture() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/texture.h b/src/tint/ast/texture.h
new file mode 100644
index 0000000..41e893a
--- /dev/null
+++ b/src/tint/ast/texture.h
@@ -0,0 +1,83 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TEXTURE_H_
+#define SRC_TINT_AST_TEXTURE_H_
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The dimensionality of the texture
+enum class TextureDimension {
+  /// Invalid texture
+  kNone = -1,
+  /// 1 dimensional texture
+  k1d,
+  /// 2 dimensional texture
+  k2d,
+  /// 2 dimensional array texture
+  k2dArray,
+  /// 3 dimensional texture
+  k3d,
+  /// cube texture
+  kCube,
+  /// cube array texture
+  kCubeArray,
+};
+
+/// @param out the std::ostream to write to
+/// @param dim the TextureDimension
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, TextureDimension dim);
+
+/// @param dim the TextureDimension to query
+/// @return true if the given TextureDimension is an array texture
+bool IsTextureArray(TextureDimension dim);
+
+/// Returns the number of axes in the coordinate used for accessing
+/// the texture, where an access is one of: sampling, fetching, load,
+/// or store.
+///  None -> 0
+///  1D -> 1
+///  2D, 2DArray -> 2
+///  3D, Cube, CubeArray -> 3
+/// Note: To sample a cube texture, the coordinate has 3 dimensions,
+/// but textureDimensions on a cube or cube array returns a 2-element
+/// size, representing the (x,y) size of each cube face, in texels.
+/// @param dim the TextureDimension to query
+/// @return number of dimensions in a coordinate for the dimensionality
+int NumCoordinateAxes(TextureDimension dim);
+
+/// A texture type.
+class Texture : public Castable<Texture, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param dim the dimensionality of the texture
+  Texture(ProgramID pid, const Source& src, TextureDimension dim);
+  /// Move constructor
+  Texture(Texture&&);
+  ~Texture() override;
+
+  /// The texture dimension
+  const TextureDimension dim;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TEXTURE_H_
diff --git a/src/tint/ast/texture_test.cc b/src/tint/ast/texture_test.cc
new file mode 100644
index 0000000..017d56e
--- /dev/null
+++ b/src/tint/ast/texture_test.cc
@@ -0,0 +1,58 @@
+// 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/ast/texture.h"
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/struct.h"
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstTextureTypeTest = TestHelper;
+
+TEST_F(AstTextureTypeTest, IsTextureArray) {
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::kNone));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k1d));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k2d));
+  EXPECT_EQ(true, IsTextureArray(TextureDimension::k2dArray));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k3d));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::kCube));
+  EXPECT_EQ(true, IsTextureArray(TextureDimension::kCubeArray));
+}
+
+TEST_F(AstTextureTypeTest, NumCoordinateAxes) {
+  EXPECT_EQ(0, NumCoordinateAxes(TextureDimension::kNone));
+  EXPECT_EQ(1, NumCoordinateAxes(TextureDimension::k1d));
+  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2d));
+  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2dArray));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::k3d));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCube));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCubeArray));
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/traverse_expressions.h b/src/tint/ast/traverse_expressions.h
new file mode 100644
index 0000000..084a201
--- /dev/null
+++ b/src/tint/ast/traverse_expressions.h
@@ -0,0 +1,154 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_
+#define SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_
+
+#include <vector>
+
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/call_expression.h"
+#include "src/tint/ast/index_accessor_expression.h"
+#include "src/tint/ast/literal_expression.h"
+#include "src/tint/ast/member_accessor_expression.h"
+#include "src/tint/ast/phony_expression.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/utils/reverse.h"
+
+namespace tint {
+namespace ast {
+
+/// The action to perform after calling the TraverseExpressions() callback
+/// function.
+enum class TraverseAction {
+  /// Stop traversal immediately.
+  Stop,
+  /// Descend into this expression.
+  Descend,
+  /// Do not descend into this expression.
+  Skip,
+};
+
+/// The order TraverseExpressions() will traverse expressions
+enum class TraverseOrder {
+  /// Expressions will be traversed from left to right
+  LeftToRight,
+  /// Expressions will be traversed from right to left
+  RightToLeft,
+};
+
+/// TraverseExpressions performs a depth-first traversal of the expression nodes
+/// from `root`, calling `callback` for each of the visited expressions that
+/// match the predicate parameter type, in pre-ordering (root first).
+/// @param root the root expression node
+/// @param diags the diagnostics used for error messages
+/// @param callback the callback function. Must be of the signature:
+///        `TraverseAction(const T*)` where T is an ast::Expression type.
+/// @return true on success, false on error
+template <TraverseOrder ORDER = TraverseOrder::LeftToRight, typename CALLBACK>
+bool TraverseExpressions(const ast::Expression* root,
+                         diag::List& diags,
+                         CALLBACK&& callback) {
+  using EXPR_TYPE = std::remove_pointer_t<traits::ParameterType<CALLBACK, 0>>;
+  std::vector<const ast::Expression*> to_visit{root};
+
+  auto push_pair = [&](const ast::Expression* left,
+                       const ast::Expression* right) {
+    if (ORDER == TraverseOrder::LeftToRight) {
+      to_visit.push_back(right);
+      to_visit.push_back(left);
+    } else {
+      to_visit.push_back(left);
+      to_visit.push_back(right);
+    }
+  };
+  auto push_list = [&](const std::vector<const ast::Expression*>& exprs) {
+    if (ORDER == TraverseOrder::LeftToRight) {
+      for (auto* expr : utils::Reverse(exprs)) {
+        to_visit.push_back(expr);
+      }
+    } else {
+      for (auto* expr : exprs) {
+        to_visit.push_back(expr);
+      }
+    }
+  };
+
+  while (!to_visit.empty()) {
+    auto* expr = to_visit.back();
+    to_visit.pop_back();
+
+    if (auto* filtered = expr->As<EXPR_TYPE>()) {
+      switch (callback(filtered)) {
+        case TraverseAction::Stop:
+          return true;
+        case TraverseAction::Skip:
+          continue;
+        case TraverseAction::Descend:
+          break;
+      }
+    }
+
+    bool ok = Switch(
+        expr,
+        [&](const IndexAccessorExpression* idx) {
+          push_pair(idx->object, idx->index);
+          return true;
+        },
+        [&](const BinaryExpression* bin_op) {
+          push_pair(bin_op->lhs, bin_op->rhs);
+          return true;
+        },
+        [&](const BitcastExpression* bitcast) {
+          to_visit.push_back(bitcast->expr);
+          return true;
+        },
+        [&](const CallExpression* call) {
+          // TODO(crbug.com/tint/1257): Resolver breaks if we actually include
+          // the function name in the traversal. to_visit.push_back(call->func);
+          push_list(call->args);
+          return true;
+        },
+        [&](const MemberAccessorExpression* member) {
+          // TODO(crbug.com/tint/1257): Resolver breaks if we actually include
+          // the member name in the traversal. push_pair(member->structure,
+          // member->member);
+          to_visit.push_back(member->structure);
+          return true;
+        },
+        [&](const UnaryOpExpression* unary) {
+          to_visit.push_back(unary->expr);
+          return true;
+        },
+        [&](Default) {
+          if (expr->IsAnyOf<LiteralExpression, IdentifierExpression,
+                            PhonyExpression>()) {
+            return true;  // Leaf expression
+          }
+          TINT_ICE(AST, diags)
+              << "unhandled expression type: " << expr->TypeInfo().name;
+          return false;
+        });
+    if (!ok) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_
diff --git a/src/tint/ast/traverse_expressions_test.cc b/src/tint/ast/traverse_expressions_test.cc
new file mode 100644
index 0000000..ae839ba
--- /dev/null
+++ b/src/tint/ast/traverse_expressions_test.cc
@@ -0,0 +1,237 @@
+// 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/ast/traverse_expressions.h"
+#include "gmock/gmock.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ::testing::ElementsAre;
+
+using TraverseExpressionsTest = TestHelper;
+
+TEST_F(TraverseExpressionsTest, DescendIndexAccessor) {
+  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
+  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
+                                           IndexAccessor(e[2], e[3])};
+  auto* root = IndexAccessor(i[0], i[1]);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3]));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0]));
+  }
+}
+
+TEST_F(TraverseExpressionsTest, DescendBinaryExpression) {
+  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
+  std::vector<const ast::Expression*> i = {Add(e[0], e[1]), Sub(e[2], e[3])};
+  auto* root = Mul(i[0], i[1]);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3]));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0]));
+  }
+}
+
+TEST_F(TraverseExpressionsTest, DescendBitcastExpression) {
+  auto* e = Expr(1);
+  auto* b0 = Bitcast<i32>(e);
+  auto* b1 = Bitcast<i32>(b0);
+  auto* b2 = Bitcast<i32>(b1);
+  auto* root = Bitcast<i32>(b2);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, b2, b1, b0, e));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, b2, b1, b0, e));
+  }
+}
+
+TEST_F(TraverseExpressionsTest, DescendCallExpression) {
+  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
+  std::vector<const ast::Expression*> c = {Call("a", e[0], e[1]),
+                                           Call("b", e[2], e[3])};
+  auto* root = Call("c", c[0], c[1]);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, c[0], e[0], e[1], c[1], e[2], e[3]));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, c[1], e[3], e[2], c[0], e[1], e[0]));
+  }
+}
+
+// TODO(crbug.com/tint/1257): Test ignores member accessor 'member' field.
+// Replace with the test below when fixed.
+TEST_F(TraverseExpressionsTest, DescendMemberIndexExpression) {
+  auto* e = Expr(1);
+  auto* m = MemberAccessor(e, Expr("a"));
+  auto* root = MemberAccessor(m, Expr("b"));
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, m, e));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, m, e));
+  }
+}
+
+// TODO(crbug.com/tint/1257): The correct test for DescendMemberIndexExpression.
+TEST_F(TraverseExpressionsTest, DISABLED_DescendMemberIndexExpression) {
+  auto* e = Expr(1);
+  std::vector<const ast::IdentifierExpression*> i = {Expr("a"), Expr("b")};
+  auto* m = MemberAccessor(e, i[0]);
+  auto* root = MemberAccessor(m, i[1]);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, m, e, i[0], i[1]));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, i[1], m, i[0], e));
+  }
+}
+
+TEST_F(TraverseExpressionsTest, DescendUnaryExpression) {
+  auto* e = Expr(1);
+  auto* u0 = AddressOf(e);
+  auto* u1 = Deref(u0);
+  auto* u2 = AddressOf(u1);
+  auto* root = Deref(u2);
+  {
+    std::vector<const ast::Expression*> l2r;
+    TraverseExpressions<TraverseOrder::LeftToRight>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          l2r.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(l2r, ElementsAre(root, u2, u1, u0, e));
+  }
+  {
+    std::vector<const ast::Expression*> r2l;
+    TraverseExpressions<TraverseOrder::RightToLeft>(
+        root, Diagnostics(), [&](const ast::Expression* expr) {
+          r2l.push_back(expr);
+          return ast::TraverseAction::Descend;
+        });
+    EXPECT_THAT(r2l, ElementsAre(root, u2, u1, u0, e));
+  }
+}
+
+TEST_F(TraverseExpressionsTest, Skip) {
+  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
+  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
+                                           IndexAccessor(e[2], e[3])};
+  auto* root = IndexAccessor(i[0], i[1]);
+  std::vector<const ast::Expression*> order;
+  TraverseExpressions<TraverseOrder::LeftToRight>(
+      root, Diagnostics(), [&](const ast::Expression* expr) {
+        order.push_back(expr);
+        return expr == i[0] ? ast::TraverseAction::Skip
+                            : ast::TraverseAction::Descend;
+      });
+  EXPECT_THAT(order, ElementsAre(root, i[0], i[1], e[2], e[3]));
+}
+
+TEST_F(TraverseExpressionsTest, Stop) {
+  std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)};
+  std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]),
+                                           IndexAccessor(e[2], e[3])};
+  auto* root = IndexAccessor(i[0], i[1]);
+  std::vector<const ast::Expression*> order;
+  TraverseExpressions<TraverseOrder::LeftToRight>(
+      root, Diagnostics(), [&](const ast::Expression* expr) {
+        order.push_back(expr);
+        return expr == i[0] ? ast::TraverseAction::Stop
+                            : ast::TraverseAction::Descend;
+      });
+  EXPECT_THAT(order, ElementsAre(root, i[0]));
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/type.h b/src/tint/ast/type.h
new file mode 100644
index 0000000..8154988
--- /dev/null
+++ b/src/tint/ast/type.h
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TYPE_H_
+#define SRC_TINT_AST_TYPE_H_
+
+#include <string>
+
+#include "src/tint/ast/node.h"
+#include "src/tint/clone_context.h"
+
+namespace tint {
+
+// Forward declarations
+class ProgramBuilder;
+class SymbolTable;
+
+namespace ast {
+
+/// Base class for a type in the system
+class Type : public Castable<Type, Node> {
+ public:
+  /// Move constructor
+  Type(Type&&);
+  ~Type() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
+
+ protected:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Type(ProgramID pid, const Source& src);
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TYPE_H_
diff --git a/src/tint/ast/type_decl.cc b/src/tint/ast/type_decl.cc
new file mode 100644
index 0000000..6d0b301
--- /dev/null
+++ b/src/tint/ast/type_decl.cc
@@ -0,0 +1,34 @@
+// 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/ast/type_decl.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeDecl);
+
+namespace tint {
+namespace ast {
+
+TypeDecl::TypeDecl(ProgramID pid, const Source& src, Symbol n)
+    : Base(pid, src), name(n) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
+}
+
+TypeDecl::TypeDecl(TypeDecl&&) = default;
+
+TypeDecl::~TypeDecl() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/type_decl.h b/src/tint/ast/type_decl.h
new file mode 100644
index 0000000..0e290cd
--- /dev/null
+++ b/src/tint/ast/type_decl.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TYPE_DECL_H_
+#define SRC_TINT_AST_TYPE_DECL_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The base class for type declarations.
+class TypeDecl : public Castable<TypeDecl, Node> {
+ public:
+  /// Create a new struct statement
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node for the import statement
+  /// @param name The name of the structure
+  TypeDecl(ProgramID pid, const Source& src, Symbol name);
+  /// Move constructor
+  TypeDecl(TypeDecl&&);
+
+  ~TypeDecl() override;
+
+  /// The name of the type declaration
+  const Symbol name;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TYPE_DECL_H_
diff --git a/src/tint/ast/type_name.cc b/src/tint/ast/type_name.cc
new file mode 100644
index 0000000..1f58b9e
--- /dev/null
+++ b/src/tint/ast/type_name.cc
@@ -0,0 +1,42 @@
+// 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/ast/type_name.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeName);
+
+namespace tint {
+namespace ast {
+
+TypeName::TypeName(ProgramID pid, const Source& src, Symbol n)
+    : Base(pid, src), name(n) {}
+
+TypeName::~TypeName() = default;
+
+TypeName::TypeName(TypeName&&) = default;
+
+std::string TypeName::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(name);
+}
+
+const TypeName* TypeName::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  auto n = ctx->Clone(name);
+  return ctx->dst->create<TypeName>(src, n);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/type_name.h b/src/tint/ast/type_name.h
new file mode 100644
index 0000000..c8e85da
--- /dev/null
+++ b/src/tint/ast/type_name.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_TYPE_NAME_H_
+#define SRC_TINT_AST_TYPE_NAME_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A named type (i.e. struct or alias)
+class TypeName : public Castable<TypeName, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param name the type name
+  TypeName(ProgramID pid, const Source& src, Symbol name);
+  /// Move constructor
+  TypeName(TypeName&&);
+  /// Destructor
+  ~TypeName() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const TypeName* Clone(CloneContext* ctx) const override;
+
+  /// The type name
+  Symbol name;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_TYPE_NAME_H_
diff --git a/src/tint/ast/u32.cc b/src/tint/ast/u32.cc
new file mode 100644
index 0000000..892289a
--- /dev/null
+++ b/src/tint/ast/u32.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/u32.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::U32);
+
+namespace tint {
+namespace ast {
+
+U32::U32(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+U32::~U32() = default;
+
+U32::U32(U32&&) = default;
+
+std::string U32::FriendlyName(const SymbolTable&) const {
+  return "u32";
+}
+
+const U32* U32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<U32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/u32.h b/src/tint/ast/u32.h
new file mode 100644
index 0000000..156fbe2
--- /dev/null
+++ b/src/tint/ast/u32.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_U32_H_
+#define SRC_TINT_AST_U32_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A unsigned int 32 type.
+class U32 : public Castable<U32, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  U32(ProgramID pid, const Source& src);
+  /// Move constructor
+  U32(U32&&);
+  ~U32() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const U32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_U32_H_
diff --git a/src/tint/ast/u32_test.cc b/src/tint/ast/u32_test.cc
new file mode 100644
index 0000000..9e8e5a6
--- /dev/null
+++ b/src/tint/ast/u32_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/u32.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstU32Test = TestHelper;
+
+TEST_F(AstU32Test, FriendlyName) {
+  auto* u = create<U32>();
+  EXPECT_EQ(u->FriendlyName(Symbols()), "u32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/uint_literal_expression.cc b/src/tint/ast/uint_literal_expression.cc
new file mode 100644
index 0000000..d53e3eb
--- /dev/null
+++ b/src/tint/ast/uint_literal_expression.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/uint_literal_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::UintLiteralExpression);
+
+namespace tint {
+namespace ast {
+
+UintLiteralExpression::UintLiteralExpression(ProgramID pid,
+                                             const Source& src,
+                                             uint32_t val)
+    : Base(pid, src), value(val) {}
+
+UintLiteralExpression::~UintLiteralExpression() = default;
+
+uint32_t UintLiteralExpression::ValueAsU32() const {
+  return value;
+}
+
+const UintLiteralExpression* UintLiteralExpression::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<UintLiteralExpression>(src, value);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/uint_literal_expression.h b/src/tint/ast/uint_literal_expression.h
new file mode 100644
index 0000000..edc79e1
--- /dev/null
+++ b/src/tint/ast/uint_literal_expression.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
+#define SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
+
+#include <string>
+
+#include "src/tint/ast/int_literal_expression.h"
+
+namespace tint {
+namespace ast {
+
+/// A uint literal
+class UintLiteralExpression
+    : public Castable<UintLiteralExpression, IntLiteralExpression> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param value the uint literals value
+  UintLiteralExpression(ProgramID pid, const Source& src, uint32_t value);
+  ~UintLiteralExpression() override;
+
+  /// @returns the literal value as a u32
+  uint32_t ValueAsU32() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const UintLiteralExpression* Clone(CloneContext* ctx) const override;
+
+  /// The int literal value
+  const uint32_t value;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/uint_literal_expression_test.cc b/src/tint/ast/uint_literal_expression_test.cc
new file mode 100644
index 0000000..1732dde
--- /dev/null
+++ b/src/tint/ast/uint_literal_expression_test.cc
@@ -0,0 +1,31 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using UintLiteralExpressionTest = TestHelper;
+
+TEST_F(UintLiteralExpressionTest, Value) {
+  auto* u = create<UintLiteralExpression>(47);
+  ASSERT_TRUE(u->Is<UintLiteralExpression>());
+  EXPECT_EQ(u->value, 47u);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/unary_op.cc b/src/tint/ast/unary_op.cc
new file mode 100644
index 0000000..4b363f5
--- /dev/null
+++ b/src/tint/ast/unary_op.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/unary_op.h"
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, UnaryOp mod) {
+  switch (mod) {
+    case UnaryOp::kAddressOf: {
+      out << "address-of";
+      break;
+    }
+    case UnaryOp::kComplement: {
+      out << "complement";
+      break;
+    }
+    case UnaryOp::kIndirection: {
+      out << "indirection";
+      break;
+    }
+    case UnaryOp::kNegation: {
+      out << "negation";
+      break;
+    }
+    case UnaryOp::kNot: {
+      out << "not";
+      break;
+    }
+  }
+  return out;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/unary_op.h b/src/tint/ast/unary_op.h
new file mode 100644
index 0000000..33fdbbf
--- /dev/null
+++ b/src/tint/ast/unary_op.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_UNARY_OP_H_
+#define SRC_TINT_AST_UNARY_OP_H_
+
+#include <ostream>
+
+namespace tint {
+namespace ast {
+
+/// The unary op
+enum class UnaryOp {
+  kAddressOf,    // &EXPR
+  kComplement,   // ~EXPR
+  kIndirection,  // *EXPR
+  kNegation,     // -EXPR
+  kNot,          // !EXPR
+};
+
+/// @param out the std::ostream to write to
+/// @param mod the UnaryOp
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, UnaryOp mod);
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_UNARY_OP_H_
diff --git a/src/tint/ast/unary_op_expression.cc b/src/tint/ast/unary_op_expression.cc
new file mode 100644
index 0000000..032fa02
--- /dev/null
+++ b/src/tint/ast/unary_op_expression.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/unary_op_expression.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::UnaryOpExpression);
+
+namespace tint {
+namespace ast {
+
+UnaryOpExpression::UnaryOpExpression(ProgramID pid,
+                                     const Source& src,
+                                     UnaryOp o,
+                                     const Expression* e)
+    : Base(pid, src), op(o), expr(e) {
+  TINT_ASSERT(AST, expr);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
+}
+
+UnaryOpExpression::UnaryOpExpression(UnaryOpExpression&&) = default;
+
+UnaryOpExpression::~UnaryOpExpression() = default;
+
+const UnaryOpExpression* UnaryOpExpression::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* e = ctx->Clone(expr);
+  return ctx->dst->create<UnaryOpExpression>(src, op, e);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/unary_op_expression.h b/src/tint/ast/unary_op_expression.h
new file mode 100644
index 0000000..62bc881
--- /dev/null
+++ b/src/tint/ast/unary_op_expression.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_UNARY_OP_EXPRESSION_H_
+#define SRC_TINT_AST_UNARY_OP_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/unary_op.h"
+
+namespace tint {
+namespace ast {
+
+/// A unary op expression
+class UnaryOpExpression : public Castable<UnaryOpExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the unary op expression source
+  /// @param op the op
+  /// @param expr the expr
+  UnaryOpExpression(ProgramID program_id,
+                    const Source& source,
+                    UnaryOp op,
+                    const Expression* expr);
+  /// Move constructor
+  UnaryOpExpression(UnaryOpExpression&&);
+  ~UnaryOpExpression() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const UnaryOpExpression* Clone(CloneContext* ctx) const override;
+
+  /// The op
+  const UnaryOp op;
+
+  /// The expression
+  const Expression* const expr;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_UNARY_OP_EXPRESSION_H_
diff --git a/src/tint/ast/unary_op_expression_test.cc b/src/tint/ast/unary_op_expression_test.cc
new file mode 100644
index 0000000..3f1b1cf
--- /dev/null
+++ b/src/tint/ast/unary_op_expression_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/unary_op_expression.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using UnaryOpExpressionTest = TestHelper;
+
+TEST_F(UnaryOpExpressionTest, Creation) {
+  auto* ident = Expr("ident");
+
+  auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident);
+  EXPECT_EQ(u->op, UnaryOp::kNot);
+  EXPECT_EQ(u->expr, ident);
+}
+
+TEST_F(UnaryOpExpressionTest, Creation_WithSource) {
+  auto* ident = Expr("ident");
+  auto* u = create<UnaryOpExpression>(Source{Source::Location{20, 2}},
+                                      UnaryOp::kNot, ident);
+  auto src = u->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(UnaryOpExpressionTest, IsUnaryOp) {
+  auto* ident = Expr("ident");
+  auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident);
+  EXPECT_TRUE(u->Is<UnaryOpExpression>());
+}
+
+TEST_F(UnaryOpExpressionTest, Assert_Null_Expression) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<UnaryOpExpression>(UnaryOp::kNot, nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(UnaryOpExpressionTest, Assert_DifferentProgramID_Expression) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<UnaryOpExpression>(UnaryOp::kNot, b2.Expr(true));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc
new file mode 100644
index 0000000..62d8dd9
--- /dev/null
+++ b/src/tint/ast/variable.cc
@@ -0,0 +1,79 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Variable);
+
+namespace tint {
+namespace ast {
+
+Variable::Variable(ProgramID pid,
+                   const Source& src,
+                   const Symbol& sym,
+                   StorageClass dsc,
+                   Access da,
+                   const ast::Type* ty,
+                   bool constant,
+                   bool overridable,
+                   const Expression* ctor,
+                   AttributeList attrs)
+    : Base(pid, src),
+      symbol(sym),
+      type(ty),
+      is_const(constant),
+      is_overridable(overridable),
+      constructor(ctor),
+      attributes(std::move(attrs)),
+      declared_storage_class(dsc),
+      declared_access(da) {
+  TINT_ASSERT(AST, symbol.IsValid());
+  TINT_ASSERT(AST, is_overridable ? is_const : true);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, constructor, program_id);
+}
+
+Variable::Variable(Variable&&) = default;
+
+Variable::~Variable() = default;
+
+VariableBindingPoint Variable::BindingPoint() const {
+  const GroupAttribute* group = nullptr;
+  const BindingAttribute* binding = nullptr;
+  for (auto* attr : attributes) {
+    if (auto* g = attr->As<GroupAttribute>()) {
+      group = g;
+    } else if (auto* b = attr->As<BindingAttribute>()) {
+      binding = b;
+    }
+  }
+  return VariableBindingPoint{group, binding};
+}
+
+const Variable* Variable::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  auto sym = ctx->Clone(symbol);
+  auto* ty = ctx->Clone(type);
+  auto* ctor = ctx->Clone(constructor);
+  auto attrs = ctx->Clone(attributes);
+  return ctx->dst->create<Variable>(src, sym, declared_storage_class,
+                                    declared_access, ty, is_const,
+                                    is_overridable, ctor, attrs);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h
new file mode 100644
index 0000000..0afe376
--- /dev/null
+++ b/src/tint/ast/variable.h
@@ -0,0 +1,186 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_VARIABLE_H_
+#define SRC_TINT_AST_VARIABLE_H_
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/attribute.h"
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/storage_class.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declarations
+class BindingAttribute;
+class GroupAttribute;
+class LocationAttribute;
+class Type;
+
+/// VariableBindingPoint holds a group and binding attribute.
+struct VariableBindingPoint {
+  /// The `@group` part of the binding point
+  const GroupAttribute* group = nullptr;
+  /// The `@binding` part of the binding point
+  const BindingAttribute* binding = nullptr;
+
+  /// @returns true if the BindingPoint has a valid group and binding
+  /// attribute.
+  inline operator bool() const { return group && binding; }
+};
+
+/// A Variable statement.
+///
+/// An instance of this class represents one of four constructs in WGSL: "var"
+/// declaration, "let" declaration, "override" declaration, or formal parameter
+/// to a function.
+///
+/// 1. A "var" declaration is a name for typed storage.  Examples:
+///
+///       // Declared outside a function, i.e. at module scope, requires
+///       // a storage class.
+///       var<workgroup> width : i32;     // no initializer
+///       var<private> height : i32 = 3;  // with initializer
+///
+///       // A variable declared inside a function doesn't take a storage class,
+///       // and maps to SPIR-V Function storage.
+///       var computed_depth : i32;
+///       var area : i32 = compute_area(width, height);
+///
+/// 2. A "let" declaration is a name for a typed value.  Examples:
+///
+///       let twice_depth : i32 = width + width;  // Must have initializer
+///
+/// 3. An "override" declaration is a name for a pipeline-overridable constant.
+/// Examples:
+///
+///       override radius : i32 = 2;       // Can be overridden by name.
+///       @id(5) override width : i32 = 2; // Can be overridden by ID.
+///       override scale : f32;            // No default - must be overridden.
+///
+/// 4. A formal parameter to a function is a name for a typed value to
+///    be passed into a function.  Example:
+///
+///       fn twice(a: i32) -> i32 {  // "a:i32" is the formal parameter
+///         return a + a;
+///       }
+///
+/// From the WGSL draft, about "var"::
+///
+///   A variable is a named reference to storage that can contain a value of a
+///   particular type.
+///
+///   Two types are associated with a variable: its store type (the type of
+///   value that may be placed in the referenced storage) and its reference
+///   type (the type of the variable itself).  If a variable has store type T
+///   and storage class S, then its reference type is pointer-to-T-in-S.
+///
+/// This class uses the term "type" to refer to:
+///     the value type of a "let",
+///     the value type of an "override",
+///     the value type of the formal parameter,
+///     or the store type of the "var".
+//
+/// Setting is_const:
+///   - "var" gets false
+///   - "let" gets true
+///   - "override" gets true
+///   - formal parameter gets true
+///
+/// Setting is_overrideable:
+///   - "var" gets false
+///   - "let" gets false
+///   - "override" gets true
+///   - formal parameter gets false
+///
+/// Setting storage class:
+///   - "var" is StorageClass::kNone when using the
+///     defaulting syntax for a "var" declared inside a function.
+///   - "let" is always StorageClass::kNone.
+///   - formal parameter is always StorageClass::kNone.
+class Variable : public Castable<Variable, Node> {
+ public:
+  /// Create a variable
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the variable source
+  /// @param sym the variable symbol
+  /// @param declared_storage_class the declared storage class
+  /// @param declared_access the declared access control
+  /// @param type the declared variable type
+  /// @param is_const true if the variable is const
+  /// @param is_overridable true if the variable is pipeline-overridable
+  /// @param constructor the constructor expression
+  /// @param attributes the variable attributes
+  Variable(ProgramID program_id,
+           const Source& source,
+           const Symbol& sym,
+           StorageClass declared_storage_class,
+           Access declared_access,
+           const ast::Type* type,
+           bool is_const,
+           bool is_overridable,
+           const Expression* constructor,
+           AttributeList attributes);
+  /// Move constructor
+  Variable(Variable&&);
+
+  ~Variable() override;
+
+  /// @returns the binding point information for the variable
+  VariableBindingPoint BindingPoint() const;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const Variable* Clone(CloneContext* ctx) const override;
+
+  /// The variable symbol
+  const Symbol symbol;
+
+  /// The declared variable type. This is null if the type is inferred, e.g.:
+  ///   let f = 1.0;
+  ///   var i = 1;
+  const ast::Type* const type;
+
+  /// True if this is a constant, false otherwise
+  const bool is_const;
+
+  /// True if this is a pipeline-overridable constant, false otherwise
+  const bool is_overridable;
+
+  /// The constructor expression or nullptr if none set
+  const Expression* const constructor;
+
+  /// The attributes attached to this variable
+  const AttributeList attributes;
+
+  /// The declared storage class
+  const StorageClass declared_storage_class;
+
+  /// The declared access control
+  const Access declared_access;
+};
+
+/// A list of variables
+using VariableList = std::vector<const Variable*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_VARIABLE_H_
diff --git a/src/tint/ast/variable_decl_statement.cc b/src/tint/ast/variable_decl_statement.cc
new file mode 100644
index 0000000..6adf183
--- /dev/null
+++ b/src/tint/ast/variable_decl_statement.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable_decl_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::VariableDeclStatement);
+
+namespace tint {
+namespace ast {
+
+VariableDeclStatement::VariableDeclStatement(ProgramID pid,
+                                             const Source& src,
+                                             const Variable* var)
+    : Base(pid, src), variable(var) {
+  TINT_ASSERT(AST, variable);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, variable, program_id);
+}
+
+VariableDeclStatement::VariableDeclStatement(VariableDeclStatement&&) = default;
+
+VariableDeclStatement::~VariableDeclStatement() = default;
+
+const VariableDeclStatement* VariableDeclStatement::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* var = ctx->Clone(variable);
+  return ctx->dst->create<VariableDeclStatement>(src, var);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/variable_decl_statement.h b/src/tint/ast/variable_decl_statement.h
new file mode 100644
index 0000000..3419d37
--- /dev/null
+++ b/src/tint/ast/variable_decl_statement.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_
+#define SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+#include "src/tint/ast/variable.h"
+
+namespace tint {
+namespace ast {
+
+/// A variable declaration statement
+class VariableDeclStatement
+    : public Castable<VariableDeclStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the variable statement source
+  /// @param variable the variable
+  VariableDeclStatement(ProgramID program_id,
+                        const Source& source,
+                        const Variable* variable);
+  /// Move constructor
+  VariableDeclStatement(VariableDeclStatement&&);
+  ~VariableDeclStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const VariableDeclStatement* Clone(CloneContext* ctx) const override;
+
+  /// The variable
+  const Variable* const variable;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_
diff --git a/src/tint/ast/variable_decl_statement_test.cc b/src/tint/ast/variable_decl_statement_test.cc
new file mode 100644
index 0000000..9881c66
--- /dev/null
+++ b/src/tint/ast/variable_decl_statement_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable_decl_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using VariableDeclStatementTest = TestHelper;
+
+TEST_F(VariableDeclStatementTest, Creation) {
+  auto* var = Var("a", ty.f32(), StorageClass::kNone);
+
+  auto* stmt = create<VariableDeclStatement>(var);
+  EXPECT_EQ(stmt->variable, var);
+}
+
+TEST_F(VariableDeclStatementTest, Creation_WithSource) {
+  auto* var = Var("a", ty.f32(), StorageClass::kNone);
+
+  auto* stmt =
+      create<VariableDeclStatement>(Source{Source::Location{20, 2}}, var);
+  auto src = stmt->source;
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(VariableDeclStatementTest, IsVariableDecl) {
+  auto* var = Var("a", ty.f32(), StorageClass::kNone);
+
+  auto* stmt = create<VariableDeclStatement>(var);
+  EXPECT_TRUE(stmt->Is<VariableDeclStatement>());
+}
+
+TEST_F(VariableDeclStatementTest, Assert_Null_Variable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.create<VariableDeclStatement>(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(VariableDeclStatementTest, Assert_DifferentProgramID_Variable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<VariableDeclStatement>(
+            b2.Var("a", b2.ty.f32(), StorageClass::kNone));
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
new file mode 100644
index 0000000..b053c0f
--- /dev/null
+++ b/src/tint/ast/variable_test.cc
@@ -0,0 +1,156 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using VariableTest = TestHelper;
+
+TEST_F(VariableTest, Creation) {
+  auto* v = Var("my_var", ty.i32(), StorageClass::kFunction);
+
+  EXPECT_EQ(v->symbol, Symbol(1, ID()));
+  EXPECT_EQ(v->declared_storage_class, StorageClass::kFunction);
+  EXPECT_TRUE(v->type->Is<ast::I32>());
+  EXPECT_EQ(v->source.range.begin.line, 0u);
+  EXPECT_EQ(v->source.range.begin.column, 0u);
+  EXPECT_EQ(v->source.range.end.line, 0u);
+  EXPECT_EQ(v->source.range.end.column, 0u);
+}
+
+TEST_F(VariableTest, CreationWithSource) {
+  auto* v = Var(
+      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 5}}},
+      "i", ty.f32(), StorageClass::kPrivate, nullptr, AttributeList{});
+
+  EXPECT_EQ(v->symbol, Symbol(1, ID()));
+  EXPECT_EQ(v->declared_storage_class, StorageClass::kPrivate);
+  EXPECT_TRUE(v->type->Is<ast::F32>());
+  EXPECT_EQ(v->source.range.begin.line, 27u);
+  EXPECT_EQ(v->source.range.begin.column, 4u);
+  EXPECT_EQ(v->source.range.end.line, 27u);
+  EXPECT_EQ(v->source.range.end.column, 5u);
+}
+
+TEST_F(VariableTest, CreationEmpty) {
+  auto* v = Var(
+      Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 7}}},
+      "a_var", ty.i32(), StorageClass::kWorkgroup, nullptr, AttributeList{});
+
+  EXPECT_EQ(v->symbol, Symbol(1, ID()));
+  EXPECT_EQ(v->declared_storage_class, StorageClass::kWorkgroup);
+  EXPECT_TRUE(v->type->Is<ast::I32>());
+  EXPECT_EQ(v->source.range.begin.line, 27u);
+  EXPECT_EQ(v->source.range.begin.column, 4u);
+  EXPECT_EQ(v->source.range.end.line, 27u);
+  EXPECT_EQ(v->source.range.end.column, 7u);
+}
+
+TEST_F(VariableTest, Assert_MissingSymbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Var("", b.ty.i32(), StorageClass::kNone);
+      },
+      "internal compiler error");
+}
+
+TEST_F(VariableTest, Assert_DifferentProgramID_Symbol) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Var(b2.Sym("x"), b1.ty.f32(), StorageClass::kNone);
+      },
+      "internal compiler error");
+}
+
+TEST_F(VariableTest, Assert_DifferentProgramID_Constructor) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.Var("x", b1.ty.f32(), StorageClass::kNone, b2.Expr(1.2f));
+      },
+      "internal compiler error");
+}
+
+TEST_F(VariableTest, WithAttributes) {
+  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
+                  AttributeList{
+                      create<LocationAttribute>(1),
+                      create<BuiltinAttribute>(Builtin::kPosition),
+                      create<IdAttribute>(1200),
+                  });
+
+  auto& attributes = var->attributes;
+  EXPECT_TRUE(ast::HasAttribute<ast::LocationAttribute>(attributes));
+  EXPECT_TRUE(ast::HasAttribute<ast::BuiltinAttribute>(attributes));
+  EXPECT_TRUE(ast::HasAttribute<ast::IdAttribute>(attributes));
+
+  auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes);
+  ASSERT_NE(nullptr, location);
+  EXPECT_EQ(1u, location->value);
+}
+
+TEST_F(VariableTest, BindingPoint) {
+  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
+                  AttributeList{
+                      create<BindingAttribute>(2),
+                      create<GroupAttribute>(1),
+                  });
+  EXPECT_TRUE(var->BindingPoint());
+  ASSERT_NE(var->BindingPoint().binding, nullptr);
+  ASSERT_NE(var->BindingPoint().group, nullptr);
+  EXPECT_EQ(var->BindingPoint().binding->value, 2u);
+  EXPECT_EQ(var->BindingPoint().group->value, 1u);
+}
+
+TEST_F(VariableTest, BindingPointAttributes) {
+  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
+                  AttributeList{});
+  EXPECT_FALSE(var->BindingPoint());
+  EXPECT_EQ(var->BindingPoint().group, nullptr);
+  EXPECT_EQ(var->BindingPoint().binding, nullptr);
+}
+
+TEST_F(VariableTest, BindingPointMissingGroupAttribute) {
+  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
+                  AttributeList{
+                      create<BindingAttribute>(2),
+                  });
+  EXPECT_FALSE(var->BindingPoint());
+  ASSERT_NE(var->BindingPoint().binding, nullptr);
+  EXPECT_EQ(var->BindingPoint().binding->value, 2u);
+  EXPECT_EQ(var->BindingPoint().group, nullptr);
+}
+
+TEST_F(VariableTest, BindingPointMissingBindingAttribute) {
+  auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
+                  AttributeList{create<GroupAttribute>(1)});
+  EXPECT_FALSE(var->BindingPoint());
+  ASSERT_NE(var->BindingPoint().group, nullptr);
+  EXPECT_EQ(var->BindingPoint().group->value, 1u);
+  EXPECT_EQ(var->BindingPoint().binding, nullptr);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/vector.cc b/src/tint/ast/vector.cc
new file mode 100644
index 0000000..d63a61b
--- /dev/null
+++ b/src/tint/ast/vector.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/vector.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Vector);
+
+namespace tint {
+namespace ast {
+
+Vector::Vector(ProgramID pid,
+               Source const& src,
+               const Type* subtype,
+               uint32_t w)
+    : Base(pid, src), type(subtype), width(w) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
+  TINT_ASSERT(AST, width > 1);
+  TINT_ASSERT(AST, width < 5);
+}
+
+Vector::Vector(Vector&&) = default;
+
+Vector::~Vector() = default;
+
+std::string Vector::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "vec" << width;
+  if (type) {
+    out << "<" << type->FriendlyName(symbols) << ">";
+  }
+  return out.str();
+}
+
+const Vector* Vector::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* ty = ctx->Clone(type);
+  return ctx->dst->create<Vector>(src, ty, width);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/vector.h b/src/tint/ast/vector.h
new file mode 100644
index 0000000..4087817
--- /dev/null
+++ b/src/tint/ast/vector.h
@@ -0,0 +1,62 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_VECTOR_H_
+#define SRC_TINT_AST_VECTOR_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A vector type.
+class Vector : public Castable<Vector, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param subtype the declared type of the vector components. May be null
+  ///        for vector constructors, where the element type will be inferred
+  ///        from the constructor arguments
+  /// @param width the number of elements in the vector
+  Vector(ProgramID pid, Source const& src, const Type* subtype, uint32_t width);
+  /// Move constructor
+  Vector(Vector&&);
+  ~Vector() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Vector* Clone(CloneContext* ctx) const override;
+
+  /// The declared type of the vector components. May be null for vector
+  /// constructors, where the element type will be inferred from the constructor
+  /// arguments
+  const Type* const type;
+
+  /// The number of elements in the vector
+  const uint32_t width;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_VECTOR_H_
diff --git a/src/tint/ast/vector_test.cc b/src/tint/ast/vector_test.cc
new file mode 100644
index 0000000..a029e73
--- /dev/null
+++ b/src/tint/ast/vector_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/vector.h"
+
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstVectorTest = TestHelper;
+
+TEST_F(AstVectorTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* v = create<Vector>(i32, 2);
+  EXPECT_EQ(v->type, i32);
+  EXPECT_EQ(v->width, 2u);
+}
+
+TEST_F(AstVectorTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* v = create<Vector>(f32, 3);
+  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/void.cc b/src/tint/ast/void.cc
new file mode 100644
index 0000000..1abb83c
--- /dev/null
+++ b/src/tint/ast/void.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/void.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Void);
+
+namespace tint {
+namespace ast {
+
+Void::Void(ProgramID pid, const Source& src) : Base(pid, src) {}
+
+Void::Void(Void&&) = default;
+
+Void::~Void() = default;
+
+std::string Void::FriendlyName(const SymbolTable&) const {
+  return "void";
+}
+
+const Void* Void::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source);
+  return ctx->dst->create<Void>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/void.h b/src/tint/ast/void.h
new file mode 100644
index 0000000..69d1571
--- /dev/null
+++ b/src/tint/ast/void.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_VOID_H_
+#define SRC_TINT_AST_VOID_H_
+
+#include <string>
+
+#include "src/tint/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A void type
+class Void : public Castable<Void, Type> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  Void(ProgramID pid, const Source& src);
+  /// Move constructor
+  Void(Void&&);
+  ~Void() override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  const Void* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_VOID_H_
diff --git a/src/tint/ast/workgroup_attribute.cc b/src/tint/ast/workgroup_attribute.cc
new file mode 100644
index 0000000..7ff6954
--- /dev/null
+++ b/src/tint/ast/workgroup_attribute.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/workgroup_attribute.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::WorkgroupAttribute);
+
+namespace tint {
+namespace ast {
+
+WorkgroupAttribute::WorkgroupAttribute(ProgramID pid,
+                                       const Source& src,
+                                       const ast::Expression* x_,
+                                       const ast::Expression* y_,
+                                       const ast::Expression* z_)
+    : Base(pid, src), x(x_), y(y_), z(z_) {}
+
+WorkgroupAttribute::~WorkgroupAttribute() = default;
+
+std::string WorkgroupAttribute::Name() const {
+  return "workgroup_size";
+}
+
+const WorkgroupAttribute* WorkgroupAttribute::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* x_ = ctx->Clone(x);
+  auto* y_ = ctx->Clone(y);
+  auto* z_ = ctx->Clone(z);
+  return ctx->dst->create<WorkgroupAttribute>(src, x_, y_, z_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/ast/workgroup_attribute.h b/src/tint/ast/workgroup_attribute.h
new file mode 100644
index 0000000..815420a
--- /dev/null
+++ b/src/tint/ast/workgroup_attribute.h
@@ -0,0 +1,69 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_
+#define SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_
+
+#include <array>
+#include <string>
+
+#include "src/tint/ast/attribute.h"
+
+namespace tint {
+namespace ast {
+
+// Forward declaration
+class Expression;
+
+/// A workgroup attribute
+class WorkgroupAttribute : public Castable<WorkgroupAttribute, Attribute> {
+ public:
+  /// constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param x the workgroup x dimension expression
+  /// @param y the optional workgroup y dimension expression
+  /// @param z the optional workgroup z dimension expression
+  WorkgroupAttribute(ProgramID pid,
+                     const Source& src,
+                     const ast::Expression* x,
+                     const ast::Expression* y = nullptr,
+                     const ast::Expression* z = nullptr);
+
+  ~WorkgroupAttribute() override;
+
+  /// @returns the workgroup dimensions
+  std::array<const ast::Expression*, 3> Values() const { return {x, y, z}; }
+
+  /// @returns the WGSL name for the attribute
+  std::string Name() const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const WorkgroupAttribute* Clone(CloneContext* ctx) const override;
+
+  /// The workgroup x dimension.
+  const ast::Expression* const x;
+  /// The optional workgroup y dimension. May be null.
+  const ast::Expression* const y = nullptr;
+  /// The optional workgroup z dimension. May be null.
+  const ast::Expression* const z = nullptr;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_
diff --git a/src/tint/ast/workgroup_attribute_test.cc b/src/tint/ast/workgroup_attribute_test.cc
new file mode 100644
index 0000000..ecf7186
--- /dev/null
+++ b/src/tint/ast/workgroup_attribute_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/workgroup_attribute.h"
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using WorkgroupAttributeTest = TestHelper;
+
+TEST_F(WorkgroupAttributeTest, Creation_1param) {
+  auto* d = WorkgroupSize(2);
+  auto values = d->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  EXPECT_EQ(values[1], nullptr);
+  EXPECT_EQ(values[2], nullptr);
+}
+TEST_F(WorkgroupAttributeTest, Creation_2param) {
+  auto* d = WorkgroupSize(2, 4);
+  auto values = d->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  EXPECT_EQ(values[2], nullptr);
+}
+
+TEST_F(WorkgroupAttributeTest, Creation_3param) {
+  auto* d = WorkgroupSize(2, 4, 6);
+  auto values = d->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
+}
+
+TEST_F(WorkgroupAttributeTest, Creation_WithIdentifier) {
+  auto* d = WorkgroupSize(2, 4, "depth");
+  auto values = d->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  auto* z_ident = As<ast::IdentifierExpression>(values[2]);
+  ASSERT_TRUE(z_ident);
+  EXPECT_EQ(Symbols().NameFor(z_ident->symbol), "depth");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/tint/bench/benchmark.cc b/src/tint/bench/benchmark.cc
new file mode 100644
index 0000000..4754823
--- /dev/null
+++ b/src/tint/bench/benchmark.cc
@@ -0,0 +1,123 @@
+// 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/bench/benchmark.h"
+
+#include <filesystem>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+namespace tint::bench {
+namespace {
+
+std::filesystem::path kInputFileDir;
+
+/// Copies the content from the file named `input_file` to `buffer`,
+/// assuming each element in the file is of type `T`.  If any error occurs,
+/// writes error messages to the standard error stream and returns false.
+/// Assumes the size of a `T` object is divisible by its required alignment.
+/// @returns true if we successfully read the file.
+template <typename T>
+std::variant<std::vector<T>, Error> ReadFile(const std::string& input_file) {
+  FILE* file = nullptr;
+#if defined(_MSC_VER)
+  fopen_s(&file, input_file.c_str(), "rb");
+#else
+  file = fopen(input_file.c_str(), "rb");
+#endif
+  if (!file) {
+    return Error{"Failed to open " + input_file};
+  }
+
+  fseek(file, 0, SEEK_END);
+  const auto file_size = static_cast<size_t>(ftell(file));
+  if (0 != (file_size % sizeof(T))) {
+    std::stringstream err;
+    err << "File " << input_file
+        << " does not contain an integral number of objects: " << file_size
+        << " bytes in the file, require " << sizeof(T) << " bytes per object";
+    fclose(file);
+    return Error{err.str()};
+  }
+  fseek(file, 0, SEEK_SET);
+
+  std::vector<T> buffer;
+  buffer.resize(file_size / sizeof(T));
+
+  size_t bytes_read = fread(buffer.data(), 1, file_size, file);
+  fclose(file);
+  if (bytes_read != file_size) {
+    return Error{"Failed to read " + input_file};
+  }
+
+  return buffer;
+}
+
+bool FindBenchmarkInputDir() {
+  // Attempt to find the benchmark input files by searching up from the current
+  // working directory.
+  auto path = std::filesystem::current_path();
+  while (std::filesystem::is_directory(path)) {
+    auto test = path / "test" / "benchmark";
+    if (std::filesystem::is_directory(test)) {
+      kInputFileDir = test;
+      return true;
+    }
+    auto parent = path.parent_path();
+    if (path == parent) {
+      break;
+    }
+    path = parent;
+  }
+  return false;
+}
+
+}  // namespace
+
+std::variant<tint::Source::File, Error> LoadInputFile(std::string name) {
+  auto path = (kInputFileDir / name).string();
+  auto data = ReadFile<uint8_t>(path);
+  if (auto* buf = std::get_if<std::vector<uint8_t>>(&data)) {
+    return tint::Source::File(path, std::string(buf->begin(), buf->end()));
+  }
+  return std::get<Error>(data);
+}
+
+std::variant<ProgramAndFile, Error> LoadProgram(std::string name) {
+  auto res = bench::LoadInputFile(name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    return *err;
+  }
+  auto& file = std::get<Source::File>(res);
+  auto program = reader::wgsl::Parse(&file);
+  if (program.Diagnostics().contains_errors()) {
+    return Error{program.Diagnostics().str()};
+  }
+  return ProgramAndFile{std::move(program), std::move(file)};
+}
+
+}  // namespace tint::bench
+
+int main(int argc, char** argv) {
+  benchmark::Initialize(&argc, argv);
+  if (benchmark::ReportUnrecognizedArguments(argc, argv)) {
+    return 1;
+  }
+  if (!tint::bench::FindBenchmarkInputDir()) {
+    std::cerr << "failed to locate benchmark input files" << std::endl;
+    return 1;
+  }
+  benchmark::RunSpecifiedBenchmarks();
+}
diff --git a/src/tint/bench/benchmark.h b/src/tint/bench/benchmark.h
new file mode 100644
index 0000000..96a935d
--- /dev/null
+++ b/src/tint/bench/benchmark.h
@@ -0,0 +1,76 @@
+// 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_BENCH_BENCHMARK_H_
+#define SRC_TINT_BENCH_BENCHMARK_H_
+
+#include <memory>
+#include <string>
+#include <variant>  // NOLINT: Found C system header after C++ system header.
+
+#include "benchmark/benchmark.h"
+#include "src/tint/utils/concat.h"
+#include "tint/tint.h"
+
+namespace tint::bench {
+
+/// Error indicates an operation did not complete successfully.
+struct Error {
+  /// The error message.
+  std::string msg;
+};
+
+/// ProgramAndFile holds a Program and a Source::File.
+struct ProgramAndFile {
+  /// The tint program parsed from file.
+  Program program;
+  /// The source file
+  Source::File file;
+};
+
+/// LoadInputFile attempts to load a benchmark input file with the given file
+/// name.
+/// @param name the file name
+/// @returns either the loaded Source::File or an Error
+std::variant<Source::File, Error> LoadInputFile(std::string name);
+
+/// LoadInputFile attempts to load a benchmark input program with the given file
+/// name.
+/// @param name the file name
+/// @returns either the loaded Program or an Error
+std::variant<ProgramAndFile, Error> LoadProgram(std::string name);
+
+/// Declares a benchmark with the given function and WGSL file name
+#define TINT_BENCHMARK_WGSL_PROGRAM(FUNC, WGSL_NAME) \
+  BENCHMARK_CAPTURE(FUNC, WGSL_NAME, WGSL_NAME);
+
+/// Declares a set of benchmarks for the given function using a list of WGSL
+/// files in `<tint>/test/benchmark`.
+#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC)                                 \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "animometer.wgsl");                    \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "bloom-vertical-blur.wgsl");           \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "cluster-lights.wgsl");                \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "empty.wgsl");                         \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "metaball-isosurface.wgsl");           \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl");                     \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "shadow-fragment.wgsl");               \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-compute.wgsl");                \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-fragment.wgsl");               \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-vertex.wgsl");                 \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-fragment.wgsl"); \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-vertex.wgsl");
+
+}  // namespace tint::bench
+
+#endif  // SRC_TINT_BENCH_BENCHMARK_H_
diff --git a/src/tint/block_allocator.h b/src/tint/block_allocator.h
new file mode 100644
index 0000000..c6bf377
--- /dev/null
+++ b/src/tint/block_allocator.h
@@ -0,0 +1,294 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_BLOCK_ALLOCATOR_H_
+#define SRC_TINT_BLOCK_ALLOCATOR_H_
+
+#include <array>
+#include <utility>
+
+#include "src/tint/utils/math.h"
+
+namespace tint {
+
+/// A container and allocator of objects of (or deriving from) the template
+/// type `T`. Objects are allocated by calling Create(), and are owned by the
+/// BlockAllocator. When the BlockAllocator is destructed, all constructed
+/// objects are automatically destructed and freed.
+///
+/// Objects held by the BlockAllocator can be iterated over using a View.
+template <typename T,
+          size_t BLOCK_SIZE = 64 * 1024,
+          size_t BLOCK_ALIGNMENT = 16>
+class BlockAllocator {
+  /// Pointers is a chunk of T* pointers, forming a linked list.
+  /// The list of Pointers are used to maintain the list of allocated objects.
+  /// Pointers are allocated out of the block memory.
+  struct Pointers {
+    static constexpr size_t kMax = 32;
+    std::array<T*, kMax> ptrs;
+    Pointers* next;
+  };
+
+  /// Block is linked list of memory blocks.
+  /// Blocks are allocated out of heap memory.
+  ///
+  /// Note: We're not using std::aligned_storage here as this warns / errors
+  /// on MSVC.
+  struct alignas(BLOCK_ALIGNMENT) Block {
+    uint8_t data[BLOCK_SIZE];
+    Block* next;
+  };
+
+  // Forward declaration
+  template <bool IS_CONST>
+  class TView;
+
+  /// An iterator for the objects owned by the BlockAllocator.
+  template <bool IS_CONST>
+  class TIterator {
+    using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
+
+   public:
+    /// Equality operator
+    /// @param other the iterator to compare this iterator to
+    /// @returns true if this iterator is equal to other
+    bool operator==(const TIterator& other) const {
+      return ptrs == other.ptrs && idx == other.idx;
+    }
+
+    /// Inequality operator
+    /// @param other the iterator to compare this iterator to
+    /// @returns true if this iterator is not equal to other
+    bool operator!=(const TIterator& other) const { return !(*this == other); }
+
+    /// Advances the iterator
+    /// @returns this iterator
+    TIterator& operator++() {
+      if (ptrs != nullptr) {
+        ++idx;
+        if (idx == Pointers::kMax) {
+          idx = 0;
+          ptrs = ptrs->next;
+        }
+      }
+      return *this;
+    }
+
+    /// @returns the pointer to the object at the current iterator position
+    PointerTy operator*() const { return ptrs ? ptrs->ptrs[idx] : nullptr; }
+
+   private:
+    friend TView<IS_CONST>;  // Keep internal iterator impl private.
+    explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
+
+    const Pointers* ptrs;
+    size_t idx;
+  };
+
+  /// View provides begin() and end() methods for looping over the objects
+  /// owned by the BlockAllocator.
+  template <bool IS_CONST>
+  class TView {
+   public:
+    /// @returns an iterator to the beginning of the view
+    TIterator<IS_CONST> begin() const {
+      return TIterator<IS_CONST>{allocator_->pointers_.root, 0};
+    }
+
+    /// @returns an iterator to the end of the view
+    TIterator<IS_CONST> end() const {
+      return allocator_->pointers_.current_index >= Pointers::kMax
+                 ? TIterator<IS_CONST>(nullptr, 0)
+                 : TIterator<IS_CONST>(allocator_->pointers_.current,
+                                       allocator_->pointers_.current_index);
+    }
+
+   private:
+    friend BlockAllocator;  // For BlockAllocator::operator View()
+    explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
+    BlockAllocator const* const allocator_;
+  };
+
+ public:
+  /// An iterator type over the objects of the BlockAllocator
+  using Iterator = TIterator<false>;
+
+  /// An immutable iterator type over the objects of the BlockAllocator
+  using ConstIterator = TIterator<true>;
+
+  /// View provides begin() and end() methods for looping over the objects
+  /// owned by the BlockAllocator.
+  using View = TView<false>;
+
+  /// ConstView provides begin() and end() methods for looping over the objects
+  /// owned by the BlockAllocator.
+  using ConstView = TView<true>;
+
+  /// Constructor
+  BlockAllocator() = default;
+
+  /// Move constructor
+  /// @param rhs the BlockAllocator to move
+  BlockAllocator(BlockAllocator&& rhs) {
+    std::swap(block_, rhs.block_);
+    std::swap(pointers_, rhs.pointers_);
+  }
+
+  /// Move assignment operator
+  /// @param rhs the BlockAllocator to move
+  /// @return this BlockAllocator
+  BlockAllocator& operator=(BlockAllocator&& rhs) {
+    if (this != &rhs) {
+      Reset();
+      std::swap(block_, rhs.block_);
+      std::swap(pointers_, rhs.pointers_);
+    }
+    return *this;
+  }
+
+  /// Destructor
+  ~BlockAllocator() { Reset(); }
+
+  /// @return a View of all objects owned by this BlockAllocator
+  View Objects() { return View(this); }
+
+  /// @return a ConstView of all objects owned by this BlockAllocator
+  ConstView Objects() const { return ConstView(this); }
+
+  /// Creates a new `TYPE` owned by the BlockAllocator.
+  /// When the BlockAllocator is destructed the object will be destructed and
+  /// freed.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the pointer to the constructed object
+  template <typename TYPE = T, typename... ARGS>
+  TYPE* Create(ARGS&&... args) {
+    static_assert(
+        std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
+        "TYPE does not derive from T");
+    static_assert(
+        std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
+        "TYPE requires a virtual destructor when calling Create() for a type "
+        "that is not T");
+
+    auto* ptr = Allocate<TYPE>();
+    new (ptr) TYPE(std::forward<ARGS>(args)...);
+    AddObjectPointer(ptr);
+
+    return ptr;
+  }
+
+  /// Frees all allocations from the allocator.
+  void Reset() {
+    for (auto ptr : Objects()) {
+      ptr->~T();
+    }
+    auto* block = block_.root;
+    while (block != nullptr) {
+      auto* next = block->next;
+      delete block;
+      block = next;
+    }
+    block_ = {};
+    pointers_ = {};
+  }
+
+ private:
+  BlockAllocator(const BlockAllocator&) = delete;
+  BlockAllocator& operator=(const BlockAllocator&) = delete;
+
+  /// Allocates an instance of TYPE from the current block, or from a newly
+  /// allocated block if the current block is full.
+  template <typename TYPE>
+  TYPE* Allocate() {
+    static_assert(sizeof(TYPE) <= BLOCK_SIZE,
+                  "Cannot construct TYPE with size greater than BLOCK_SIZE");
+    static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT,
+                  "alignof(TYPE) is greater than ALIGNMENT");
+
+    block_.current_offset =
+        utils::RoundUp(alignof(TYPE), block_.current_offset);
+    if (block_.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
+      // Allocate a new block from the heap
+      auto* prev_block = block_.current;
+      block_.current = new Block;
+      if (!block_.current) {
+        return nullptr;  // out of memory
+      }
+      block_.current->next = nullptr;
+      block_.current_offset = 0;
+      if (prev_block) {
+        prev_block->next = block_.current;
+      } else {
+        block_.root = block_.current;
+      }
+    }
+
+    auto* base = &block_.current->data[0];
+    auto* ptr = reinterpret_cast<TYPE*>(base + block_.current_offset);
+    block_.current_offset += sizeof(TYPE);
+    return ptr;
+  }
+
+  /// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
+  /// Once added, `ptr` will be tracked for destruction when the BlockAllocator
+  /// is destructed.
+  void AddObjectPointer(T* ptr) {
+    if (pointers_.current_index >= Pointers::kMax) {
+      auto* prev_pointers = pointers_.current;
+      pointers_.current = Allocate<Pointers>();
+      if (!pointers_.current) {
+        return;  // out of memory
+      }
+      pointers_.current->next = nullptr;
+      pointers_.current_index = 0;
+
+      if (prev_pointers) {
+        prev_pointers->next = pointers_.current;
+      } else {
+        pointers_.root = pointers_.current;
+      }
+    }
+
+    pointers_.current->ptrs[pointers_.current_index++] = ptr;
+  }
+
+  struct {
+    /// The root block of the block linked list
+    Block* root = nullptr;
+    /// The current (end) block of the blocked linked list.
+    /// New allocations come from this block
+    Block* current = nullptr;
+    /// The byte offset in #current for the next allocation.
+    /// Initialized with BLOCK_SIZE so that the first allocation triggers a
+    /// block allocation.
+    size_t current_offset = BLOCK_SIZE;
+  } block_;
+
+  struct {
+    /// The root Pointers structure of the pointers linked list
+    Pointers* root = nullptr;
+    /// The current (end) Pointers structure of the pointers linked list.
+    /// AddObjectPointer() adds to this structure.
+    Pointers* current = nullptr;
+    /// The array index in #current for the next append.
+    /// Initialized with Pointers::kMax so that the first append triggers a
+    /// allocation of the Pointers structure.
+    size_t current_index = Pointers::kMax;
+  } pointers_;
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_BLOCK_ALLOCATOR_H_
diff --git a/src/tint/block_allocator_test.cc b/src/tint/block_allocator_test.cc
new file mode 100644
index 0000000..8b3ded0
--- /dev/null
+++ b/src/tint/block_allocator_test.cc
@@ -0,0 +1,147 @@
+// 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/block_allocator.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct LifetimeCounter {
+  explicit LifetimeCounter(size_t* count) : count_(count) { (*count)++; }
+  ~LifetimeCounter() { (*count_)--; }
+
+  size_t* const count_;
+};
+
+using BlockAllocatorTest = testing::Test;
+
+TEST_F(BlockAllocatorTest, Empty) {
+  using Allocator = BlockAllocator<int>;
+
+  Allocator allocator;
+
+  for (int* i : allocator.Objects()) {
+    (void)i;
+    if ((true)) {  // Workaround for "error: loop will run at most once"
+      FAIL() << "BlockAllocator should be empty";
+    }
+  }
+  for (const int* i : static_cast<const Allocator&>(allocator).Objects()) {
+    (void)i;
+    if ((true)) {  // Workaround for "error: loop will run at most once"
+      FAIL() << "BlockAllocator should be empty";
+    }
+  }
+}
+
+TEST_F(BlockAllocatorTest, ObjectLifetime) {
+  using Allocator = BlockAllocator<LifetimeCounter>;
+
+  size_t count = 0;
+  {
+    Allocator allocator;
+    EXPECT_EQ(count, 0u);
+    allocator.Create(&count);
+    EXPECT_EQ(count, 1u);
+    allocator.Create(&count);
+    EXPECT_EQ(count, 2u);
+    allocator.Create(&count);
+    EXPECT_EQ(count, 3u);
+  }
+  EXPECT_EQ(count, 0u);
+}
+
+TEST_F(BlockAllocatorTest, MoveConstruct) {
+  using Allocator = BlockAllocator<LifetimeCounter>;
+
+  for (size_t n :
+       {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
+    size_t count = 0;
+    {
+      Allocator allocator_a;
+      for (size_t i = 0; i < n; i++) {
+        allocator_a.Create(&count);
+      }
+      EXPECT_EQ(count, n);
+
+      Allocator allocator_b{std::move(allocator_a)};
+      EXPECT_EQ(count, n);
+    }
+
+    EXPECT_EQ(count, 0u);
+  }
+}
+
+TEST_F(BlockAllocatorTest, MoveAssign) {
+  using Allocator = BlockAllocator<LifetimeCounter>;
+
+  for (size_t n :
+       {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
+    size_t count_a = 0;
+    size_t count_b = 0;
+
+    {
+      Allocator allocator_a;
+      for (size_t i = 0; i < n; i++) {
+        allocator_a.Create(&count_a);
+      }
+      EXPECT_EQ(count_a, n);
+
+      Allocator allocator_b;
+      for (size_t i = 0; i < n; i++) {
+        allocator_b.Create(&count_b);
+      }
+      EXPECT_EQ(count_b, n);
+
+      allocator_b = std::move(allocator_a);
+      EXPECT_EQ(count_a, n);
+      EXPECT_EQ(count_b, 0u);
+    }
+
+    EXPECT_EQ(count_a, 0u);
+    EXPECT_EQ(count_b, 0u);
+  }
+}
+
+TEST_F(BlockAllocatorTest, ObjectOrder) {
+  using Allocator = BlockAllocator<int>;
+
+  Allocator allocator;
+  constexpr int N = 10000;
+  for (int i = 0; i < N; i++) {
+    allocator.Create(i);
+  }
+
+  {
+    int i = 0;
+    for (int* p : allocator.Objects()) {
+      EXPECT_EQ(*p, i);
+      i++;
+    }
+    EXPECT_EQ(i, N);
+  }
+  {
+    int i = 0;
+    for (const int* p : static_cast<const Allocator&>(allocator).Objects()) {
+      EXPECT_EQ(*p, i);
+      i++;
+    }
+    EXPECT_EQ(i, N);
+  }
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/builtin_table.cc b/src/tint/builtin_table.cc
new file mode 100644
index 0000000..4d11c9d
--- /dev/null
+++ b/src/tint/builtin_table.cc
@@ -0,0 +1,1166 @@
+// 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/builtin_table.h"
+
+#include <algorithm>
+#include <limits>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/pipeline_stage_set.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+namespace tint {
+namespace {
+
+// Forward declarations
+struct OverloadInfo;
+class Matchers;
+class NumberMatcher;
+class TypeMatcher;
+
+/// A special type that matches all TypeMatchers
+class Any : public Castable<Any, sem::Type> {
+ public:
+  Any() = default;
+  ~Any() override = default;
+  std::string type_name() const override { return "<any>"; }
+  std::string FriendlyName(const SymbolTable&) const override {
+    return "<any>";
+  }
+};
+
+/// Number is an 32 bit unsigned integer, which can be in one of three states:
+/// * Invalid - Number has not been assigned a value
+/// * Valid   - a fixed integer value
+/// * Any     - matches any other non-invalid number
+struct Number {
+  static const Number any;
+  static const Number invalid;
+
+  /// Constructed as a valid number with the value v
+  explicit Number(uint32_t v) : value_(v), state_(kValid) {}
+
+  /// @returns the value of the number
+  inline uint32_t Value() const { return value_; }
+
+  /// @returns the true if the number is valid
+  inline bool IsValid() const { return state_ == kValid; }
+
+  /// @returns the true if the number is any
+  inline bool IsAny() const { return state_ == kAny; }
+
+  /// Assignment operator.
+  /// The number becomes valid, with the value n
+  inline Number& operator=(uint32_t n) {
+    value_ = n;
+    state_ = kValid;
+    return *this;
+  }
+
+ private:
+  enum State {
+    kInvalid,
+    kValid,
+    kAny,
+  };
+
+  constexpr explicit Number(State state) : state_(state) {}
+
+  uint32_t value_ = 0;
+  State state_ = kInvalid;
+};
+
+const Number Number::any{Number::kAny};
+const Number Number::invalid{Number::kInvalid};
+
+/// ClosedState holds the state of the open / closed numbers and types.
+/// Used by the MatchState.
+class ClosedState {
+ public:
+  explicit ClosedState(ProgramBuilder& b) : builder(b) {}
+
+  /// If the type with index `idx` is open, then it is closed with type `ty` and
+  /// Type() returns true. If the type is closed, then `Type()` returns true iff
+  /// it is equal to `ty`.
+  bool Type(uint32_t idx, const sem::Type* ty) {
+    auto res = types_.emplace(idx, ty);
+    return res.second || res.first->second == ty;
+  }
+
+  /// If the number with index `idx` is open, then it is closed with number
+  /// `number` and Num() returns true. If the number is closed, then `Num()`
+  /// returns true iff it is equal to `ty`.
+  bool Num(uint32_t idx, Number number) {
+    auto res = numbers_.emplace(idx, number.Value());
+    return res.second || res.first->second == number.Value();
+  }
+
+  /// Type returns the closed type with index `idx`.
+  /// An ICE is raised if the type is not closed.
+  const sem::Type* Type(uint32_t idx) const {
+    auto it = types_.find(idx);
+    if (it == types_.end()) {
+      TINT_ICE(Resolver, builder.Diagnostics())
+          << "type with index " << idx << " is not closed";
+      return nullptr;
+    }
+    TINT_ASSERT(Resolver, it != types_.end());
+    return it->second;
+  }
+
+  /// Type returns the number type with index `idx`.
+  /// An ICE is raised if the number is not closed.
+  Number Num(uint32_t idx) const {
+    auto it = numbers_.find(idx);
+    if (it == numbers_.end()) {
+      TINT_ICE(Resolver, builder.Diagnostics())
+          << "number with index " << idx << " is not closed";
+      return Number::invalid;
+    }
+    return Number(it->second);
+  }
+
+ private:
+  ProgramBuilder& builder;
+  std::unordered_map<uint32_t, const sem::Type*> types_;
+  std::unordered_map<uint32_t, uint32_t> numbers_;
+};
+
+/// Index type used for matcher indices
+using MatcherIndex = uint8_t;
+
+/// Index value used for open types / numbers that do not have a constraint
+constexpr MatcherIndex kNoMatcher = std::numeric_limits<MatcherIndex>::max();
+
+/// MatchState holds the state used to match an overload.
+class MatchState {
+ public:
+  MatchState(ProgramBuilder& b,
+             ClosedState& c,
+             const Matchers& m,
+             const OverloadInfo& o,
+             MatcherIndex const* matcher_indices)
+      : builder(b),
+        closed(c),
+        matchers(m),
+        overload(o),
+        matcher_indices_(matcher_indices) {}
+
+  /// The program builder
+  ProgramBuilder& builder;
+  /// The open / closed types and numbers
+  ClosedState& closed;
+  /// The type and number matchers
+  Matchers const& matchers;
+  /// The current overload being evaluated
+  OverloadInfo const& overload;
+
+  /// Type uses the next TypeMatcher from the matcher indices to match the type
+  /// `ty`. If the type matches, the canonical expected type is returned. If the
+  /// type `ty` does not match, then nullptr is returned.
+  /// @note: The matcher indices are progressed on calling.
+  const sem::Type* Type(const sem::Type* ty);
+
+  /// Num uses the next NumMatcher from the matcher indices to match the number
+  /// `num`. If the number matches, the canonical expected number is returned.
+  /// If the number `num` does not match, then an invalid number is returned.
+  /// @note: The matcher indices are progressed on calling.
+  Number Num(Number num);
+
+  /// @returns a string representation of the next TypeMatcher from the matcher
+  /// indices.
+  /// @note: The matcher indices are progressed on calling.
+  std::string TypeName();
+
+  /// @returns a string representation of the next NumberMatcher from the
+  /// matcher indices.
+  /// @note: The matcher indices are progressed on calling.
+  std::string NumName();
+
+ private:
+  MatcherIndex const* matcher_indices_ = nullptr;
+};
+
+/// A TypeMatcher is the interface used to match an type used as part of an
+/// overload's parameter or return type.
+class TypeMatcher {
+ public:
+  /// Destructor
+  virtual ~TypeMatcher() = default;
+
+  /// Checks whether the given type matches the matcher rules, and returns the
+  /// expected, canonicalized type on success.
+  /// Match may close open types and numbers in state.
+  /// @param type the type to match
+  /// @returns the canonicalized type on match, otherwise nullptr
+  virtual const sem::Type* Match(MatchState& state,
+                                 const sem::Type* type) const = 0;
+
+  /// @return a string representation of the matcher. Used for printing error
+  /// messages when no overload is found.
+  virtual std::string String(MatchState& state) const = 0;
+};
+
+/// A NumberMatcher is the interface used to match a number or enumerator used
+/// as part of an overload's parameter or return type.
+class NumberMatcher {
+ public:
+  /// Destructor
+  virtual ~NumberMatcher() = default;
+
+  /// Checks whether the given number matches the matcher rules.
+  /// Match may close open numbers in state.
+  /// @param number the number to match
+  /// @returns true if the argument type is as expected.
+  virtual Number Match(MatchState& state, Number number) const = 0;
+
+  /// @return a string representation of the matcher. Used for printing error
+  /// messages when no overload is found.
+  virtual std::string String(MatchState& state) const = 0;
+};
+
+/// OpenTypeMatcher is a Matcher for an open type.
+/// The OpenTypeMatcher will match against any type (so long as it is consistent
+/// across all uses in the overload)
+class OpenTypeMatcher : public TypeMatcher {
+ public:
+  /// Constructor
+  explicit OpenTypeMatcher(uint32_t index) : index_(index) {}
+
+  const sem::Type* Match(MatchState& state,
+                         const sem::Type* type) const override {
+    if (type->Is<Any>()) {
+      return state.closed.Type(index_);
+    }
+    return state.closed.Type(index_, type) ? type : nullptr;
+  }
+
+  std::string String(MatchState& state) const override;
+
+ private:
+  uint32_t index_;
+};
+
+/// OpenNumberMatcher is a Matcher for an open number.
+/// The OpenNumberMatcher will match against any number (so long as it is
+/// consistent for the overload)
+class OpenNumberMatcher : public NumberMatcher {
+ public:
+  explicit OpenNumberMatcher(uint32_t index) : index_(index) {}
+
+  Number Match(MatchState& state, Number number) const override {
+    if (number.IsAny()) {
+      return state.closed.Num(index_);
+    }
+    return state.closed.Num(index_, number) ? number : Number::invalid;
+  }
+
+  std::string String(MatchState& state) const override;
+
+ private:
+  uint32_t index_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Binding functions for use in the generated builtin_table.inl
+// TODO(bclayton): See if we can move more of this hand-rolled code to the
+// template
+////////////////////////////////////////////////////////////////////////////////
+using TexelFormat = ast::TexelFormat;
+using Access = ast::Access;
+using StorageClass = ast::StorageClass;
+using ParameterUsage = sem::ParameterUsage;
+using PipelineStageSet = sem::PipelineStageSet;
+using PipelineStage = ast::PipelineStage;
+
+bool match_bool(const sem::Type* ty) {
+  return ty->IsAnyOf<Any, sem::Bool>();
+}
+
+const sem::Bool* build_bool(MatchState& state) {
+  return state.builder.create<sem::Bool>();
+}
+
+bool match_f32(const sem::Type* ty) {
+  return ty->IsAnyOf<Any, sem::F32>();
+}
+
+const sem::I32* build_i32(MatchState& state) {
+  return state.builder.create<sem::I32>();
+}
+
+bool match_i32(const sem::Type* ty) {
+  return ty->IsAnyOf<Any, sem::I32>();
+}
+
+const sem::U32* build_u32(MatchState& state) {
+  return state.builder.create<sem::U32>();
+}
+
+bool match_u32(const sem::Type* ty) {
+  return ty->IsAnyOf<Any, sem::U32>();
+}
+
+const sem::F32* build_f32(MatchState& state) {
+  return state.builder.create<sem::F32>();
+}
+
+bool match_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    N = Number::any;
+    T = ty;
+    return true;
+  }
+
+  if (auto* v = ty->As<sem::Vector>()) {
+    N = v->Width();
+    T = v->type();
+    return true;
+  }
+  return false;
+}
+
+const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
+  return state.builder.create<sem::Vector>(el, N.Value());
+}
+
+template <int N>
+bool match_vec(const sem::Type* ty, const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    T = ty;
+    return true;
+  }
+
+  if (auto* v = ty->As<sem::Vector>()) {
+    if (v->Width() == N) {
+      T = v->type();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool match_vec2(const sem::Type* ty, const sem::Type*& T) {
+  return match_vec<2>(ty, T);
+}
+
+const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) {
+  return build_vec(state, Number(2), T);
+}
+
+bool match_vec3(const sem::Type* ty, const sem::Type*& T) {
+  return match_vec<3>(ty, T);
+}
+
+const sem::Vector* build_vec3(MatchState& state, const sem::Type* T) {
+  return build_vec(state, Number(3), T);
+}
+
+bool match_vec4(const sem::Type* ty, const sem::Type*& T) {
+  return match_vec<4>(ty, T);
+}
+
+const sem::Vector* build_vec4(MatchState& state, const sem::Type* T) {
+  return build_vec(state, Number(4), T);
+}
+
+bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    M = Number::any;
+    N = Number::any;
+    T = ty;
+    return true;
+  }
+  if (auto* m = ty->As<sem::Matrix>()) {
+    M = m->columns();
+    N = m->ColumnType()->Width();
+    T = m->type();
+    return true;
+  }
+  return false;
+}
+
+const sem::Matrix* build_mat(MatchState& state,
+                             Number N,
+                             Number M,
+                             const sem::Type* T) {
+  auto* column_type = state.builder.create<sem::Vector>(T, M.Value());
+  return state.builder.create<sem::Matrix>(column_type, N.Value());
+}
+
+bool match_array(const sem::Type* ty, const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    T = ty;
+    return true;
+  }
+
+  if (auto* a = ty->As<sem::Array>()) {
+    if (a->Count() == 0) {
+      T = a->ElemType();
+      return true;
+    }
+  }
+  return false;
+}
+
+const sem::Array* build_array(MatchState& state, const sem::Type* el) {
+  return state.builder.create<sem::Array>(el,
+                                          /* count */ 0,
+                                          /* align */ 0,
+                                          /* size */ 0,
+                                          /* stride */ 0,
+                                          /* stride_implicit */ 0);
+}
+
+bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
+  if (ty->Is<Any>()) {
+    S = Number::any;
+    T = ty;
+    A = Number::any;
+    return true;
+  }
+
+  if (auto* p = ty->As<sem::Pointer>()) {
+    S = Number(static_cast<uint32_t>(p->StorageClass()));
+    T = p->StoreType();
+    A = Number(static_cast<uint32_t>(p->Access()));
+    return true;
+  }
+  return false;
+}
+
+const sem::Pointer* build_ptr(MatchState& state,
+                              Number S,
+                              const sem::Type* T,
+                              Number& A) {
+  return state.builder.create<sem::Pointer>(
+      T, static_cast<ast::StorageClass>(S.Value()),
+      static_cast<ast::Access>(A.Value()));
+}
+
+bool match_atomic(const sem::Type* ty, const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    T = ty;
+    return true;
+  }
+
+  if (auto* a = ty->As<sem::Atomic>()) {
+    T = a->Type();
+    return true;
+  }
+  return false;
+}
+
+const sem::Atomic* build_atomic(MatchState& state, const sem::Type* T) {
+  return state.builder.create<sem::Atomic>(T);
+}
+
+bool match_sampler(const sem::Type* ty) {
+  if (ty->Is<Any>()) {
+    return true;
+  }
+  return ty->Is([](const sem::Sampler* s) {
+    return s->kind() == ast::SamplerKind::kSampler;
+  });
+}
+
+const sem::Sampler* build_sampler(MatchState& state) {
+  return state.builder.create<sem::Sampler>(ast::SamplerKind::kSampler);
+}
+
+bool match_sampler_comparison(const sem::Type* ty) {
+  if (ty->Is<Any>()) {
+    return true;
+  }
+  return ty->Is([](const sem::Sampler* s) {
+    return s->kind() == ast::SamplerKind::kComparisonSampler;
+  });
+}
+
+const sem::Sampler* build_sampler_comparison(MatchState& state) {
+  return state.builder.create<sem::Sampler>(
+      ast::SamplerKind::kComparisonSampler);
+}
+
+bool match_texture(const sem::Type* ty,
+                   ast::TextureDimension dim,
+                   const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    T = ty;
+    return true;
+  }
+  if (auto* v = ty->As<sem::SampledTexture>()) {
+    if (v->dim() == dim) {
+      T = v->type();
+      return true;
+    }
+  }
+  return false;
+}
+
+#define JOIN(a, b) a##b
+
+#define DECLARE_SAMPLED_TEXTURE(suffix, dim)                  \
+  bool JOIN(match_texture_, suffix)(const sem::Type* ty,      \
+                                    const sem::Type*& T) {    \
+    return match_texture(ty, dim, T);                         \
+  }                                                           \
+  const sem::SampledTexture* JOIN(build_texture_, suffix)(    \
+      MatchState & state, const sem::Type* T) {               \
+    return state.builder.create<sem::SampledTexture>(dim, T); \
+  }
+
+DECLARE_SAMPLED_TEXTURE(1d, ast::TextureDimension::k1d)
+DECLARE_SAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
+DECLARE_SAMPLED_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
+DECLARE_SAMPLED_TEXTURE(3d, ast::TextureDimension::k3d)
+DECLARE_SAMPLED_TEXTURE(cube, ast::TextureDimension::kCube)
+DECLARE_SAMPLED_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
+#undef DECLARE_SAMPLED_TEXTURE
+
+bool match_texture_multisampled(const sem::Type* ty,
+                                ast::TextureDimension dim,
+                                const sem::Type*& T) {
+  if (ty->Is<Any>()) {
+    T = ty;
+    return true;
+  }
+  if (auto* v = ty->As<sem::MultisampledTexture>()) {
+    if (v->dim() == dim) {
+      T = v->type();
+      return true;
+    }
+  }
+  return false;
+}
+
+#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim)                            \
+  bool JOIN(match_texture_multisampled_, suffix)(const sem::Type* ty,        \
+                                                 const sem::Type*& T) {      \
+    return match_texture_multisampled(ty, dim, T);                           \
+  }                                                                          \
+  const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \
+      MatchState & state, const sem::Type* T) {                              \
+    return state.builder.create<sem::MultisampledTexture>(dim, T);           \
+  }
+
+DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
+#undef DECLARE_MULTISAMPLED_TEXTURE
+
+bool match_texture_depth(const sem::Type* ty, ast::TextureDimension dim) {
+  if (ty->Is<Any>()) {
+    return true;
+  }
+  return ty->Is([&](const sem::DepthTexture* t) { return t->dim() == dim; });
+}
+
+#define DECLARE_DEPTH_TEXTURE(suffix, dim)                       \
+  bool JOIN(match_texture_depth_, suffix)(const sem::Type* ty) { \
+    return match_texture_depth(ty, dim);                         \
+  }                                                              \
+  const sem::DepthTexture* JOIN(build_texture_depth_,            \
+                                suffix)(MatchState & state) {    \
+    return state.builder.create<sem::DepthTexture>(dim);         \
+  }
+
+DECLARE_DEPTH_TEXTURE(2d, ast::TextureDimension::k2d)
+DECLARE_DEPTH_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
+DECLARE_DEPTH_TEXTURE(cube, ast::TextureDimension::kCube)
+DECLARE_DEPTH_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
+#undef DECLARE_DEPTH_TEXTURE
+
+bool match_texture_depth_multisampled_2d(const sem::Type* ty) {
+  if (ty->Is<Any>()) {
+    return true;
+  }
+  return ty->Is([&](const sem::DepthMultisampledTexture* t) {
+    return t->dim() == ast::TextureDimension::k2d;
+  });
+}
+
+sem::DepthMultisampledTexture* build_texture_depth_multisampled_2d(
+    MatchState& state) {
+  return state.builder.create<sem::DepthMultisampledTexture>(
+      ast::TextureDimension::k2d);
+}
+
+bool match_texture_storage(const sem::Type* ty,
+                           ast::TextureDimension dim,
+                           Number& F,
+                           Number& A) {
+  if (ty->Is<Any>()) {
+    F = Number::any;
+    A = Number::any;
+    return true;
+  }
+  if (auto* v = ty->As<sem::StorageTexture>()) {
+    if (v->dim() == dim) {
+      F = Number(static_cast<uint32_t>(v->texel_format()));
+      A = Number(static_cast<uint32_t>(v->access()));
+      return true;
+    }
+  }
+  return false;
+}
+
+#define DECLARE_STORAGE_TEXTURE(suffix, dim)                                  \
+  bool JOIN(match_texture_storage_, suffix)(const sem::Type* ty, Number& F,   \
+                                            Number& A) {                      \
+    return match_texture_storage(ty, dim, F, A);                              \
+  }                                                                           \
+  const sem::StorageTexture* JOIN(build_texture_storage_, suffix)(            \
+      MatchState & state, Number F, Number A) {                               \
+    auto format = static_cast<TexelFormat>(F.Value());                        \
+    auto access = static_cast<Access>(A.Value());                             \
+    auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types()); \
+    return state.builder.create<sem::StorageTexture>(dim, format, access, T); \
+  }
+
+DECLARE_STORAGE_TEXTURE(1d, ast::TextureDimension::k1d)
+DECLARE_STORAGE_TEXTURE(2d, ast::TextureDimension::k2d)
+DECLARE_STORAGE_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
+DECLARE_STORAGE_TEXTURE(3d, ast::TextureDimension::k3d)
+#undef DECLARE_STORAGE_TEXTURE
+
+bool match_texture_external(const sem::Type* ty) {
+  return ty->IsAnyOf<Any, sem::ExternalTexture>();
+}
+
+const sem::ExternalTexture* build_texture_external(MatchState& state) {
+  return state.builder.create<sem::ExternalTexture>();
+}
+
+// Builtin types starting with a _ prefix cannot be declared in WGSL, so they
+// can only be used as return types. Because of this, they must only match Any,
+// which is used as the return type matcher.
+bool match_modf_result(const sem::Type* ty) {
+  return ty->Is<Any>();
+}
+bool match_modf_result_vec(const sem::Type* ty, Number& N) {
+  if (!ty->Is<Any>()) {
+    return false;
+  }
+  N = Number::any;
+  return true;
+}
+bool match_frexp_result(const sem::Type* ty) {
+  return ty->Is<Any>();
+}
+bool match_frexp_result_vec(const sem::Type* ty, Number& N) {
+  if (!ty->Is<Any>()) {
+    return false;
+  }
+  N = Number::any;
+  return true;
+}
+
+struct NameAndType {
+  std::string name;
+  sem::Type* type;
+};
+const sem::Struct* build_struct(
+    MatchState& state,
+    std::string name,
+    std::initializer_list<NameAndType> member_names_and_types) {
+  uint32_t offset = 0;
+  uint32_t max_align = 0;
+  sem::StructMemberList members;
+  for (auto& m : member_names_and_types) {
+    uint32_t align = m.type->Align();
+    uint32_t size = m.type->Size();
+    offset = utils::RoundUp(align, offset);
+    max_align = std::max(max_align, align);
+    members.emplace_back(state.builder.create<sem::StructMember>(
+        /* declaration */ nullptr,
+        /* name */ state.builder.Sym(m.name),
+        /* type */ m.type,
+        /* index */ static_cast<uint32_t>(members.size()),
+        /* offset */ offset,
+        /* align */ align,
+        /* size */ size));
+    offset += size;
+  }
+  uint32_t size_without_padding = offset;
+  uint32_t size_with_padding = utils::RoundUp(max_align, offset);
+  return state.builder.create<sem::Struct>(
+      /* declaration */ nullptr,
+      /* name */ state.builder.Sym(name),
+      /* members */ members,
+      /* align */ max_align,
+      /* size */ size_with_padding,
+      /* size_no_padding */ size_without_padding);
+}
+
+const sem::Struct* build_modf_result(MatchState& state) {
+  auto* f32 = state.builder.create<sem::F32>();
+  return build_struct(state, "__modf_result", {{"fract", f32}, {"whole", f32}});
+}
+const sem::Struct* build_modf_result_vec(MatchState& state, Number& n) {
+  auto* vec_f32 = state.builder.create<sem::Vector>(
+      state.builder.create<sem::F32>(), n.Value());
+  return build_struct(state, "__modf_result_vec" + std::to_string(n.Value()),
+                      {{"fract", vec_f32}, {"whole", vec_f32}});
+}
+const sem::Struct* build_frexp_result(MatchState& state) {
+  auto* f32 = state.builder.create<sem::F32>();
+  auto* i32 = state.builder.create<sem::I32>();
+  return build_struct(state, "__frexp_result", {{"sig", f32}, {"exp", i32}});
+}
+const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n) {
+  auto* vec_f32 = state.builder.create<sem::Vector>(
+      state.builder.create<sem::F32>(), n.Value());
+  auto* vec_i32 = state.builder.create<sem::Vector>(
+      state.builder.create<sem::I32>(), n.Value());
+  return build_struct(state, "__frexp_result_vec" + std::to_string(n.Value()),
+                      {{"sig", vec_f32}, {"exp", vec_i32}});
+}
+
+/// ParameterInfo describes a parameter
+struct ParameterInfo {
+  /// The parameter usage (parameter name in definition file)
+  const ParameterUsage usage;
+
+  /// Pointer to a list of indices that are used to match the parameter type.
+  /// The matcher indices index on Matchers::type and / or Matchers::number.
+  /// These indices are consumed by the matchers themselves.
+  /// The first index is always a TypeMatcher.
+  MatcherIndex const* const matcher_indices;
+};
+
+/// OpenTypeInfo describes an open type
+struct OpenTypeInfo {
+  /// Name of the open type (e.g. 'T')
+  const char* name;
+  /// Optional type matcher constraint.
+  /// Either an index in Matchers::type, or kNoMatcher
+  const MatcherIndex matcher_index;
+};
+
+/// OpenNumberInfo describes an open number
+struct OpenNumberInfo {
+  /// Name of the open number (e.g. 'N')
+  const char* name;
+  /// Optional number matcher constraint.
+  /// Either an index in Matchers::number, or kNoMatcher
+  const MatcherIndex matcher_index;
+};
+
+/// OverloadInfo describes a single function overload
+struct OverloadInfo {
+  /// Total number of parameters for the overload
+  const uint8_t num_parameters;
+  /// Total number of open types for the overload
+  const uint8_t num_open_types;
+  /// Total number of open numbers for the overload
+  const uint8_t num_open_numbers;
+  /// Pointer to the first open type
+  OpenTypeInfo const* const open_types;
+  /// Pointer to the first open number
+  OpenNumberInfo const* const open_numbers;
+  /// Pointer to the first parameter
+  ParameterInfo const* const parameters;
+  /// Pointer to a list of matcher indices that index on Matchers::type and
+  /// Matchers::number, used to build the return type. If the function has no
+  /// return type then this is null
+  MatcherIndex const* const return_matcher_indices;
+  /// The pipeline stages that this overload can be used in
+  PipelineStageSet supported_stages;
+  /// True if the overload is marked as deprecated
+  bool is_deprecated;
+};
+
+/// BuiltinInfo describes a builtin function
+struct BuiltinInfo {
+  /// Number of overloads of the builtin function
+  const uint8_t num_overloads;
+  /// Pointer to the start of the overloads for the function
+  OverloadInfo const* const overloads;
+};
+
+#include "builtin_table.inl"
+
+/// BuiltinPrototype describes a fully matched builtin function, which is
+/// used as a lookup for building unique sem::Builtin instances.
+struct BuiltinPrototype {
+  /// Parameter describes a single parameter
+  struct Parameter {
+    /// Parameter type
+    const sem::Type* const type;
+    /// Parameter usage
+    ParameterUsage const usage = ParameterUsage::kNone;
+  };
+
+  /// Hasher provides a hash function for the BuiltinPrototype
+  struct Hasher {
+    /// @param i the BuiltinPrototype to create a hash for
+    /// @return the hash value
+    inline std::size_t operator()(const BuiltinPrototype& i) const {
+      size_t hash = utils::Hash(i.parameters.size());
+      for (auto& p : i.parameters) {
+        utils::HashCombine(&hash, p.type, p.usage);
+      }
+      return utils::Hash(hash, i.type, i.return_type, i.supported_stages,
+                         i.is_deprecated);
+    }
+  };
+
+  sem::BuiltinType type = sem::BuiltinType::kNone;
+  std::vector<Parameter> parameters;
+  sem::Type const* return_type = nullptr;
+  PipelineStageSet supported_stages;
+  bool is_deprecated = false;
+};
+
+/// Equality operator for BuiltinPrototype
+bool operator==(const BuiltinPrototype& a, const BuiltinPrototype& b) {
+  if (a.type != b.type || a.supported_stages != b.supported_stages ||
+      a.return_type != b.return_type || a.is_deprecated != b.is_deprecated ||
+      a.parameters.size() != b.parameters.size()) {
+    return false;
+  }
+  for (size_t i = 0; i < a.parameters.size(); i++) {
+    auto& pa = a.parameters[i];
+    auto& pb = b.parameters[i];
+    if (pa.type != pb.type || pa.usage != pb.usage) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/// Impl is the private implementation of the BuiltinTable interface.
+class Impl : public BuiltinTable {
+ public:
+  explicit Impl(ProgramBuilder& builder);
+
+  const sem::Builtin* Lookup(sem::BuiltinType builtin_type,
+                             const std::vector<const sem::Type*>& args,
+                             const Source& source) override;
+
+ private:
+  const sem::Builtin* Match(sem::BuiltinType builtin_type,
+                            const OverloadInfo& overload,
+                            const std::vector<const sem::Type*>& args,
+                            int& match_score);
+
+  MatchState Match(ClosedState& closed,
+                   const OverloadInfo& overload,
+                   MatcherIndex const* matcher_indices) const;
+
+  void PrintOverload(std::ostream& ss,
+                     const OverloadInfo& overload,
+                     sem::BuiltinType builtin_type) const;
+
+  ProgramBuilder& builder;
+  Matchers matchers;
+  std::unordered_map<BuiltinPrototype, sem::Builtin*, BuiltinPrototype::Hasher>
+      builtins;
+};
+
+/// @return a string representing a call to a builtin with the given argument
+/// types.
+std::string CallSignature(ProgramBuilder& builder,
+                          sem::BuiltinType builtin_type,
+                          const std::vector<const sem::Type*>& args) {
+  std::stringstream ss;
+  ss << sem::str(builtin_type) << "(";
+  {
+    bool first = true;
+    for (auto* arg : args) {
+      if (!first) {
+        ss << ", ";
+      }
+      first = false;
+      ss << arg->UnwrapRef()->FriendlyName(builder.Symbols());
+    }
+  }
+  ss << ")";
+
+  return ss.str();
+}
+
+std::string OpenTypeMatcher::String(MatchState& state) const {
+  return state.overload.open_types[index_].name;
+}
+
+std::string OpenNumberMatcher::String(MatchState& state) const {
+  return state.overload.open_numbers[index_].name;
+}
+
+Impl::Impl(ProgramBuilder& b) : builder(b) {}
+
+const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
+                                 const std::vector<const sem::Type*>& args,
+                                 const Source& source) {
+  // Candidate holds information about a mismatched overload that could be what
+  // the user intended to call.
+  struct Candidate {
+    const OverloadInfo* overload;
+    int score;
+  };
+
+  // The list of failed matches that had promise.
+  std::vector<Candidate> candidates;
+
+  auto& builtin = kBuiltins[static_cast<uint32_t>(builtin_type)];
+  for (uint32_t o = 0; o < builtin.num_overloads; o++) {
+    int match_score = 1000;
+    auto& overload = builtin.overloads[o];
+    if (auto* match = Match(builtin_type, overload, args, match_score)) {
+      return match;
+    }
+    if (match_score > 0) {
+      candidates.emplace_back(Candidate{&overload, match_score});
+    }
+  }
+
+  // Sort the candidates with the most promising first
+  std::stable_sort(
+      candidates.begin(), candidates.end(),
+      [](const Candidate& a, const Candidate& b) { return a.score > b.score; });
+
+  // Generate an error message
+  std::stringstream ss;
+  ss << "no matching call to " << CallSignature(builder, builtin_type, args)
+     << std::endl;
+  if (!candidates.empty()) {
+    ss << std::endl;
+    ss << candidates.size() << " candidate function"
+       << (candidates.size() > 1 ? "s:" : ":") << std::endl;
+    for (auto& candidate : candidates) {
+      ss << "  ";
+      PrintOverload(ss, *candidate.overload, builtin_type);
+      ss << std::endl;
+    }
+  }
+  builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
+  return nullptr;
+}
+
+const sem::Builtin* Impl::Match(sem::BuiltinType builtin_type,
+                                const OverloadInfo& overload,
+                                const std::vector<const sem::Type*>& args,
+                                int& match_score) {
+  // Score wait for argument <-> parameter count matches / mismatches
+  constexpr int kScorePerParamArgMismatch = -1;
+  constexpr int kScorePerMatchedParam = 2;
+  constexpr int kScorePerMatchedOpenType = 1;
+  constexpr int kScorePerMatchedOpenNumber = 1;
+
+  auto num_parameters = overload.num_parameters;
+  auto num_arguments = static_cast<decltype(num_parameters)>(args.size());
+
+  bool overload_matched = true;
+
+  if (num_parameters != num_arguments) {
+    match_score +=
+        kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
+                                     std::min(num_parameters, num_arguments));
+    overload_matched = false;
+  }
+
+  ClosedState closed(builder);
+
+  std::vector<BuiltinPrototype::Parameter> parameters;
+
+  auto num_params = std::min(num_parameters, num_arguments);
+  for (uint32_t p = 0; p < num_params; p++) {
+    auto& parameter = overload.parameters[p];
+    auto* indices = parameter.matcher_indices;
+    auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef());
+    if (type) {
+      parameters.emplace_back(
+          BuiltinPrototype::Parameter{type, parameter.usage});
+      match_score += kScorePerMatchedParam;
+    } else {
+      overload_matched = false;
+    }
+  }
+
+  if (overload_matched) {
+    // Check all constrained open types matched
+    for (uint32_t ot = 0; ot < overload.num_open_types; ot++) {
+      auto& open_type = overload.open_types[ot];
+      if (open_type.matcher_index != kNoMatcher) {
+        auto* index = &open_type.matcher_index;
+        if (Match(closed, overload, index).Type(closed.Type(ot))) {
+          match_score += kScorePerMatchedOpenType;
+        } else {
+          overload_matched = false;
+        }
+      }
+    }
+  }
+
+  if (overload_matched) {
+    // Check all constrained open numbers matched
+    for (uint32_t on = 0; on < overload.num_open_numbers; on++) {
+      auto& open_number = overload.open_numbers[on];
+      if (open_number.matcher_index != kNoMatcher) {
+        auto* index = &open_number.matcher_index;
+        if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) {
+          match_score += kScorePerMatchedOpenNumber;
+        } else {
+          overload_matched = false;
+        }
+      }
+    }
+  }
+
+  if (!overload_matched) {
+    return nullptr;
+  }
+
+  // Build the return type
+  const sem::Type* return_type = nullptr;
+  if (auto* indices = overload.return_matcher_indices) {
+    Any any;
+    return_type = Match(closed, overload, indices).Type(&any);
+    if (!return_type) {
+      std::stringstream ss;
+      PrintOverload(ss, overload, builtin_type);
+      TINT_ICE(Resolver, builder.Diagnostics())
+          << "MatchState.Match() returned null for " << ss.str();
+      return nullptr;
+    }
+  } else {
+    return_type = builder.create<sem::Void>();
+  }
+
+  BuiltinPrototype builtin;
+  builtin.type = builtin_type;
+  builtin.return_type = return_type;
+  builtin.parameters = std::move(parameters);
+  builtin.supported_stages = overload.supported_stages;
+  builtin.is_deprecated = overload.is_deprecated;
+
+  // De-duplicate builtins that are identical.
+  return utils::GetOrCreate(builtins, builtin, [&] {
+    std::vector<sem::Parameter*> params;
+    params.reserve(builtin.parameters.size());
+    for (auto& p : builtin.parameters) {
+      params.emplace_back(builder.create<sem::Parameter>(
+          nullptr, static_cast<uint32_t>(params.size()), p.type,
+          ast::StorageClass::kNone, ast::Access::kUndefined, p.usage));
+    }
+    return builder.create<sem::Builtin>(
+        builtin.type, builtin.return_type, std::move(params),
+        builtin.supported_stages, builtin.is_deprecated);
+  });
+}
+
+MatchState Impl::Match(ClosedState& closed,
+                       const OverloadInfo& overload,
+                       MatcherIndex const* matcher_indices) const {
+  return MatchState(builder, closed, matchers, overload, matcher_indices);
+}
+
+void Impl::PrintOverload(std::ostream& ss,
+                         const OverloadInfo& overload,
+                         sem::BuiltinType builtin_type) const {
+  ClosedState closed(builder);
+
+  ss << builtin_type << "(";
+  for (uint32_t p = 0; p < overload.num_parameters; p++) {
+    auto& parameter = overload.parameters[p];
+    if (p > 0) {
+      ss << ", ";
+    }
+    if (parameter.usage != ParameterUsage::kNone) {
+      ss << sem::str(parameter.usage) << ": ";
+    }
+    auto* indices = parameter.matcher_indices;
+    ss << Match(closed, overload, indices).TypeName();
+  }
+  ss << ")";
+  if (overload.return_matcher_indices) {
+    ss << " -> ";
+    auto* indices = overload.return_matcher_indices;
+    ss << Match(closed, overload, indices).TypeName();
+  }
+
+  bool first = true;
+  auto separator = [&] {
+    ss << (first ? "  where: " : ", ");
+    first = false;
+  };
+  for (uint32_t i = 0; i < overload.num_open_types; i++) {
+    auto& open_type = overload.open_types[i];
+    if (open_type.matcher_index != kNoMatcher) {
+      separator();
+      ss << open_type.name;
+      auto* index = &open_type.matcher_index;
+      ss << " is " << Match(closed, overload, index).TypeName();
+    }
+  }
+  for (uint32_t i = 0; i < overload.num_open_numbers; i++) {
+    auto& open_number = overload.open_numbers[i];
+    if (open_number.matcher_index != kNoMatcher) {
+      separator();
+      ss << open_number.name;
+      auto* index = &open_number.matcher_index;
+      ss << " is " << Match(closed, overload, index).NumName();
+    }
+  }
+}
+
+const sem::Type* MatchState::Type(const sem::Type* ty) {
+  MatcherIndex matcher_index = *matcher_indices_++;
+  auto* matcher = matchers.type[matcher_index];
+  return matcher->Match(*this, ty);
+}
+
+Number MatchState::Num(Number number) {
+  MatcherIndex matcher_index = *matcher_indices_++;
+  auto* matcher = matchers.number[matcher_index];
+  return matcher->Match(*this, number);
+}
+
+std::string MatchState::TypeName() {
+  MatcherIndex matcher_index = *matcher_indices_++;
+  auto* matcher = matchers.type[matcher_index];
+  return matcher->String(*this);
+}
+
+std::string MatchState::NumName() {
+  MatcherIndex matcher_index = *matcher_indices_++;
+  auto* matcher = matchers.number[matcher_index];
+  return matcher->String(*this);
+}
+
+}  // namespace
+
+std::unique_ptr<BuiltinTable> BuiltinTable::Create(ProgramBuilder& builder) {
+  return std::make_unique<Impl>(builder);
+}
+
+BuiltinTable::~BuiltinTable() = default;
+
+/// TypeInfo for the Any type declared in the anonymous namespace above
+TINT_INSTANTIATE_TYPEINFO(Any);
+
+}  // namespace tint
diff --git a/src/tint/builtin_table.h b/src/tint/builtin_table.h
new file mode 100644
index 0000000..246eed2
--- /dev/null
+++ b/src/tint/builtin_table.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_BUILTIN_TABLE_H_
+#define SRC_TINT_BUILTIN_TABLE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "src/tint/sem/builtin.h"
+
+namespace tint {
+
+// Forward declarations
+class ProgramBuilder;
+
+/// BuiltinTable is a lookup table of all the WGSL builtin functions
+class BuiltinTable {
+ public:
+  /// @param builder the program builder
+  /// @return a pointer to a newly created BuiltinTable
+  static std::unique_ptr<BuiltinTable> Create(ProgramBuilder& builder);
+
+  /// Destructor
+  virtual ~BuiltinTable();
+
+  /// Lookup looks for the builtin overload with the given signature, raising
+  /// an error diagnostic if the builtin was not found.
+  /// @param type the builtin type
+  /// @param args the argument types passed to the builtin function
+  /// @param source the source of the builtin call
+  /// @return the semantic builtin if found, otherwise nullptr
+  virtual const sem::Builtin* Lookup(sem::BuiltinType type,
+                                     const std::vector<const sem::Type*>& args,
+                                     const Source& source) = 0;
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_BUILTIN_TABLE_H_
diff --git a/src/builtin_table.inl b/src/tint/builtin_table.inl
similarity index 100%
rename from src/builtin_table.inl
rename to src/tint/builtin_table.inl
diff --git a/src/builtin_table.inl.tmpl b/src/tint/builtin_table.inl.tmpl
similarity index 100%
rename from src/builtin_table.inl.tmpl
rename to src/tint/builtin_table.inl.tmpl
diff --git a/src/tint/builtin_table_test.cc b/src/tint/builtin_table_test.cc
new file mode 100644
index 0000000..1734c7d
--- /dev/null
+++ b/src/tint/builtin_table_test.cc
@@ -0,0 +1,601 @@
+// 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/builtin_table.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace {
+
+using ::testing::HasSubstr;
+
+using BuiltinType = sem::BuiltinType;
+using Parameter = sem::Parameter;
+using ParameterUsage = sem::ParameterUsage;
+
+class BuiltinTableTest : public testing::Test, public ProgramBuilder {
+ public:
+  std::unique_ptr<BuiltinTable> table = BuiltinTable::Create(*this);
+};
+
+TEST_F(BuiltinTableTest, MatchF32) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kCos, {f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kCos);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
+}
+
+TEST_F(BuiltinTableTest, MismatchF32) {
+  auto* i32 = create<sem::I32>();
+  auto* result = table->Lookup(BuiltinType::kCos, {i32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchU32) {
+  auto* f32 = create<sem::F32>();
+  auto* u32 = create<sem::U32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {u32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kUnpack2x16float);
+  EXPECT_EQ(result->ReturnType(), vec2_f32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
+}
+
+TEST_F(BuiltinTableTest, MismatchU32) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchI32) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), vec4_f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
+}
+
+TEST_F(BuiltinTableTest, MismatchI32) {
+  auto* f32 = create<sem::F32>();
+  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
+  auto* result = table->Lookup(BuiltinType::kTextureLoad, {tex, f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchIU32AsI32) {
+  auto* i32 = create<sem::I32>();
+  auto* result = table->Lookup(BuiltinType::kCountOneBits, {i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits);
+  EXPECT_EQ(result->ReturnType(), i32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), i32);
+}
+
+TEST_F(BuiltinTableTest, MatchIU32AsU32) {
+  auto* u32 = create<sem::U32>();
+  auto* result = table->Lookup(BuiltinType::kCountOneBits, {u32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits);
+  EXPECT_EQ(result->ReturnType(), u32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
+}
+
+TEST_F(BuiltinTableTest, MismatchIU32) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kCountOneBits, {f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchFIU32AsI32) {
+  auto* i32 = create<sem::I32>();
+  auto* result = table->Lookup(BuiltinType::kClamp, {i32, i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
+  EXPECT_EQ(result->ReturnType(), i32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+}
+
+TEST_F(BuiltinTableTest, MatchFIU32AsU32) {
+  auto* u32 = create<sem::U32>();
+  auto* result = table->Lookup(BuiltinType::kClamp, {u32, u32, u32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
+  EXPECT_EQ(result->ReturnType(), u32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), u32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), u32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), u32);
+}
+
+TEST_F(BuiltinTableTest, MatchFIU32AsF32) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), f32);
+}
+
+TEST_F(BuiltinTableTest, MismatchFIU32) {
+  auto* bool_ = create<sem::Bool>();
+  auto* result =
+      table->Lookup(BuiltinType::kClamp, {bool_, bool_, bool_}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchBool) {
+  auto* f32 = create<sem::F32>();
+  auto* bool_ = create<sem::Bool>();
+  auto* result =
+      table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kSelect);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), bool_);
+}
+
+TEST_F(BuiltinTableTest, MismatchBool) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kSelect, {f32, f32, f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchPointer) {
+  auto* i32 = create<sem::I32>();
+  auto* atomicI32 = create<sem::Atomic>(i32);
+  auto* ptr = create<sem::Pointer>(atomicI32, ast::StorageClass::kWorkgroup,
+                                   ast::Access::kReadWrite);
+  auto* result = table->Lookup(BuiltinType::kAtomicLoad, {ptr}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kAtomicLoad);
+  EXPECT_EQ(result->ReturnType(), i32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), ptr);
+}
+
+TEST_F(BuiltinTableTest, MismatchPointer) {
+  auto* i32 = create<sem::I32>();
+  auto* atomicI32 = create<sem::Atomic>(i32);
+  auto* result = table->Lookup(BuiltinType::kAtomicLoad, {atomicI32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchArray) {
+  auto* arr = create<sem::Array>(create<sem::U32>(), 0, 4, 4, 4, 4);
+  auto* arr_ptr = create<sem::Pointer>(arr, ast::StorageClass::kStorage,
+                                       ast::Access::kReadWrite);
+  auto* result = table->Lookup(BuiltinType::kArrayLength, {arr_ptr}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kArrayLength);
+  EXPECT_TRUE(result->ReturnType()->Is<sem::U32>());
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  auto* param_type = result->Parameters()[0]->Type();
+  ASSERT_TRUE(param_type->Is<sem::Pointer>());
+  EXPECT_TRUE(param_type->As<sem::Pointer>()->StoreType()->Is<sem::Array>());
+}
+
+TEST_F(BuiltinTableTest, MismatchArray) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kArrayLength, {f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchSampler) {
+  auto* f32 = create<sem::F32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+  auto* result = table->Lookup(BuiltinType::kTextureSample,
+                               {tex, sampler, vec2_f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureSample);
+  EXPECT_EQ(result->ReturnType(), vec4_f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), sampler);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kSampler);
+  EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kCoords);
+}
+
+TEST_F(BuiltinTableTest, MismatchSampler) {
+  auto* f32 = create<sem::F32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
+  auto* result = table->Lookup(BuiltinType::kTextureSample,
+                               {tex, f32, vec2_f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchSampledTexture) {
+  auto* i32 = create<sem::I32>();
+  auto* f32 = create<sem::F32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), vec4_f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
+}
+
+TEST_F(BuiltinTableTest, MatchMultisampledTexture) {
+  auto* i32 = create<sem::I32>();
+  auto* f32 = create<sem::F32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* tex = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), vec4_f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex);
+}
+
+TEST_F(BuiltinTableTest, MatchDepthTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel);
+}
+
+TEST_F(BuiltinTableTest, MatchDepthMultisampledTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* tex = create<sem::DepthMultisampledTexture>(ast::TextureDimension::k2d);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), i32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex);
+}
+
+TEST_F(BuiltinTableTest, MatchExternalTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* tex = create<sem::ExternalTexture>();
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad);
+  EXPECT_EQ(result->ReturnType(), vec4_f32);
+  ASSERT_EQ(result->Parameters().size(), 2u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+}
+
+TEST_F(BuiltinTableTest, MatchWOStorageTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* vec4_f32 = create<sem::Vector>(f32, 4);
+  auto* subtype =
+      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kR32Float, Types());
+  auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d,
+                                          ast::TexelFormat::kR32Float,
+                                          ast::Access::kWrite, subtype);
+
+  auto* result = table->Lookup(BuiltinType::kTextureStore,
+                               {tex, vec2_i32, vec4_f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kTextureStore);
+  EXPECT_TRUE(result->ReturnType()->Is<sem::Void>());
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), tex);
+  EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32);
+  EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords);
+  EXPECT_EQ(result->Parameters()[2]->Type(), vec4_f32);
+  EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kValue);
+}
+
+TEST_F(BuiltinTableTest, MismatchTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+  auto* vec2_i32 = create<sem::Vector>(i32, 2);
+  auto* result =
+      table->Lookup(BuiltinType::kTextureLoad, {f32, vec2_i32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, ImplicitLoadOnReference) {
+  auto* f32 = create<sem::F32>();
+  auto* result =
+      table->Lookup(BuiltinType::kCos,
+                    {create<sem::Reference>(f32, ast::StorageClass::kFunction,
+                                            ast::Access::kReadWrite)},
+                    Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kCos);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
+}
+
+TEST_F(BuiltinTableTest, MatchOpenType) {
+  auto* f32 = create<sem::F32>();
+  auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
+  EXPECT_EQ(result->ReturnType(), f32);
+  EXPECT_EQ(result->Parameters()[0]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), f32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), f32);
+}
+
+TEST_F(BuiltinTableTest, MismatchOpenType) {
+  auto* f32 = create<sem::F32>();
+  auto* u32 = create<sem::U32>();
+  auto* result = table->Lookup(BuiltinType::kClamp, {f32, u32, f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchOpenSizeVector) {
+  auto* f32 = create<sem::F32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* result = table->Lookup(BuiltinType::kClamp,
+                               {vec2_f32, vec2_f32, vec2_f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kClamp);
+  EXPECT_EQ(result->ReturnType(), vec2_f32);
+  ASSERT_EQ(result->Parameters().size(), 3u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), vec2_f32);
+  EXPECT_EQ(result->Parameters()[1]->Type(), vec2_f32);
+  EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32);
+}
+
+TEST_F(BuiltinTableTest, MismatchOpenSizeVector) {
+  auto* f32 = create<sem::F32>();
+  auto* u32 = create<sem::U32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* result =
+      table->Lookup(BuiltinType::kClamp, {vec2_f32, u32, vec2_f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, MatchOpenSizeMatrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3_f32 = create<sem::Vector>(f32, 3);
+  auto* mat3_f32 = create<sem::Matrix>(vec3_f32, 3);
+  auto* result = table->Lookup(BuiltinType::kDeterminant, {mat3_f32}, Source{});
+  ASSERT_NE(result, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+  EXPECT_EQ(result->Type(), BuiltinType::kDeterminant);
+  EXPECT_EQ(result->ReturnType(), f32);
+  ASSERT_EQ(result->Parameters().size(), 1u);
+  EXPECT_EQ(result->Parameters()[0]->Type(), mat3_f32);
+}
+
+TEST_F(BuiltinTableTest, MismatchOpenSizeMatrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec2_f32 = create<sem::Vector>(f32, 2);
+  auto* mat3x2_f32 = create<sem::Matrix>(vec2_f32, 3);
+  auto* result =
+      table->Lookup(BuiltinType::kDeterminant, {mat3x2_f32}, Source{});
+  ASSERT_EQ(result, nullptr);
+  ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
+}
+
+TEST_F(BuiltinTableTest, OverloadOrderByNumberOfParameters) {
+  // None of the arguments match, so expect the overloads with 2 parameters to
+  // come first
+  auto* bool_ = create<sem::Bool>();
+  table->Lookup(BuiltinType::kTextureDimensions, {bool_, bool_}, Source{});
+  ASSERT_EQ(Diagnostics().str(),
+            R"(error: no matching call to textureDimensions(bool, bool)
+
+27 candidate functions:
+  textureDimensions(texture: texture_1d<T>, level: i32) -> i32  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_1d<T>) -> i32  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_3d<T>) -> vec3<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_depth_2d) -> vec2<i32>
+  textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube_array) -> vec2<i32>
+  textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32>
+  textureDimensions(texture: texture_storage_1d<F, A>) -> i32  where: A is write
+  textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>  where: A is write
+  textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>  where: A is write
+  textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>  where: A is write
+  textureDimensions(texture: texture_external) -> vec2<i32>
+)");
+}
+
+TEST_F(BuiltinTableTest, OverloadOrderByMatchingParameter) {
+  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
+  auto* bool_ = create<sem::Bool>();
+  table->Lookup(BuiltinType::kTextureDimensions, {tex, bool_}, Source{});
+  ASSERT_EQ(
+      Diagnostics().str(),
+      R"(error: no matching call to textureDimensions(texture_depth_2d, bool)
+
+27 candidate functions:
+  textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_2d) -> vec2<i32>
+  textureDimensions(texture: texture_1d<T>, level: i32) -> i32  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32>
+  textureDimensions(texture: texture_1d<T>) -> i32  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_3d<T>) -> vec3<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_cube_array<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>  where: T is f32, i32 or u32
+  textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube) -> vec2<i32>
+  textureDimensions(texture: texture_depth_cube_array) -> vec2<i32>
+  textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32>
+  textureDimensions(texture: texture_storage_1d<F, A>) -> i32  where: A is write
+  textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>  where: A is write
+  textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>  where: A is write
+  textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>  where: A is write
+  textureDimensions(texture: texture_external) -> vec2<i32>
+)");
+}
+
+TEST_F(BuiltinTableTest, SameOverloadReturnsSameBuiltinPointer) {
+  auto* f32 = create<sem::F32>();
+  auto* vec2_f32 = create<sem::Vector>(create<sem::F32>(), 2);
+  auto* bool_ = create<sem::Bool>();
+  auto* a = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
+  ASSERT_NE(a, nullptr) << Diagnostics().str();
+
+  auto* b = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{});
+  ASSERT_NE(b, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+
+  auto* c = table->Lookup(BuiltinType::kSelect, {vec2_f32, vec2_f32, bool_},
+                          Source{});
+  ASSERT_NE(c, nullptr) << Diagnostics().str();
+  ASSERT_EQ(Diagnostics().str(), "");
+
+  EXPECT_EQ(a, b);
+  EXPECT_NE(a, c);
+  EXPECT_NE(b, c);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/builtins.def b/src/tint/builtins.def
similarity index 100%
rename from src/builtins.def
rename to src/tint/builtins.def
diff --git a/src/tint/castable.cc b/src/tint/castable.cc
new file mode 100644
index 0000000..cff430e
--- /dev/null
+++ b/src/tint/castable.cc
@@ -0,0 +1,29 @@
+// Copyright 2020 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/castable.h"
+
+namespace tint {
+
+/// The unique TypeInfo for the CastableBase type
+/// @return doxygen-thinks-this-static-field-is-a-function :(
+template <>
+const TypeInfo detail::TypeInfoOf<CastableBase>::info{
+    nullptr,
+    "CastableBase",
+    tint::TypeInfo::HashCodeOf<CastableBase>(),
+    tint::TypeInfo::FullHashCodeOf<CastableBase>(),
+};
+
+}  // namespace tint
diff --git a/src/tint/castable.h b/src/tint/castable.h
new file mode 100644
index 0000000..51cad00
--- /dev/null
+++ b/src/tint/castable.h
@@ -0,0 +1,736 @@
+// Copyright 2020 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_CASTABLE_H_
+#define SRC_TINT_CASTABLE_H_
+
+#include <stdint.h>
+#include <functional>
+#include <tuple>
+#include <utility>
+
+#include "src/tint/traits.h"
+#include "src/tint/utils/crc32.h"
+
+#if defined(__clang__)
+/// Temporarily disable certain warnings when using Castable API
+#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS()                               \
+  _Pragma("clang diagnostic push")                                     /**/ \
+      _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") /**/ \
+      static_assert(true, "require extra semicolon")
+
+/// Restore disabled warnings
+#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \
+  _Pragma("clang diagnostic pop") /**/       \
+      static_assert(true, "require extra semicolon")
+#else
+#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() \
+  static_assert(true, "require extra semicolon")
+#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \
+  static_assert(true, "require extra semicolon")
+#endif
+
+TINT_CASTABLE_PUSH_DISABLE_WARNINGS();
+
+namespace tint {
+
+// Forward declaration
+class CastableBase;
+
+/// Ignore is used as a special type used for skipping over types for trait
+/// helper functions.
+class Ignore {};
+
+namespace detail {
+template <typename T>
+struct TypeInfoOf;
+
+}  // namespace detail
+
+/// True if all template types that are not Ignore derive from CastableBase
+template <typename... TYPES>
+static constexpr bool IsCastable =
+    ((traits::IsTypeOrDerived<TYPES, CastableBase> ||
+      std::is_same_v<TYPES, Ignore>)&&...) &&
+    !(std::is_same_v<TYPES, Ignore> && ...);
+
+/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
+#define TINT_INSTANTIATE_TYPEINFO(CLASS)                      \
+  TINT_CASTABLE_PUSH_DISABLE_WARNINGS();                      \
+  template <>                                                 \
+  const tint::TypeInfo tint::detail::TypeInfoOf<CLASS>::info{ \
+      &tint::detail::TypeInfoOf<CLASS::TrueBase>::info,       \
+      #CLASS,                                                 \
+      tint::TypeInfo::HashCodeOf<CLASS>(),                    \
+      tint::TypeInfo::FullHashCodeOf<CLASS>(),                \
+  };                                                          \
+  TINT_CASTABLE_POP_DISABLE_WARNINGS()
+
+/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and
+/// As().
+enum CastFlags {
+  /// Disables the static_assert() inside Is(), that compile-time-verifies that
+  /// the cast is possible. This flag may be useful for highly-generic template
+  /// code that needs to compile for template permutations that generate
+  /// impossible casts.
+  kDontErrorOnImpossibleCast = 1,
+};
+
+/// TypeInfo holds type information for a Castable type.
+struct TypeInfo {
+  /// The type of a hash code
+  using HashCode = uint64_t;
+
+  /// The base class of this type
+  const TypeInfo* base;
+  /// The type name
+  const char* name;
+  /// The type hash code
+  const HashCode hashcode;
+  /// The type hash code bitwise-or'd with all ancestor's hashcodes.
+  const HashCode full_hashcode;
+
+  /// @param type the test type info
+  /// @returns true if the class with this TypeInfo is of, or derives from the
+  /// class with the given TypeInfo.
+  inline bool Is(const tint::TypeInfo* type) const {
+    // Optimization: Check whether the all the bits of the type's hashcode can
+    // be found in the full_hashcode. If a single bit is missing, then we
+    // can quickly tell that that this TypeInfo does not derive from `type`.
+    if ((full_hashcode & type->hashcode) != type->hashcode) {
+      return false;
+    }
+
+    // Walk the base types, starting with this TypeInfo, to see if any of the
+    // pointers match `type`.
+    for (auto* ti = this; ti != nullptr; ti = ti->base) {
+      if (ti == type) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// @returns true if `type` derives from the class `TO`
+  /// @param type the object type to test from, which must be, or derive from
+  /// type `FROM`.
+  /// @see CastFlags
+  template <typename TO, typename FROM, int FLAGS = 0>
+  static inline bool Is(const tint::TypeInfo* type) {
+    constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
+    constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
+    constexpr const bool nocast = std::is_same<FROM, TO>::value;
+    constexpr const bool assert_is_castable =
+        (FLAGS & kDontErrorOnImpossibleCast) == 0;
+
+    static_assert(upcast || downcast || nocast || !assert_is_castable,
+                  "impossible cast");
+
+    if (upcast || nocast) {
+      return true;
+    }
+
+    return type->Is(&Of<std::remove_cv_t<TO>>());
+  }
+
+  /// @returns the static TypeInfo for the type T
+  template <typename T>
+  static const TypeInfo& Of() {
+    return detail::TypeInfoOf<std::remove_cv_t<T>>::info;
+  }
+
+  /// @returns a compile-time hashcode for the type `T`.
+  /// @note the returned hashcode will have at most 2 bits set, as the hashes
+  /// are expected to be used in bloom-filters which will quickly saturate when
+  /// multiple hashcodes are bitwise-or'd together.
+  template <typename T>
+  static constexpr HashCode HashCodeOf() {
+    static_assert(IsCastable<T>, "T is not Castable");
+    static_assert(
+        std::is_same_v<T, std::remove_cv_t<T>>,
+        "Strip const / volatile decorations before calling HashCodeOf");
+    /// Use the compiler's "pretty" function name, which includes the template
+    /// type, to obtain a unique hash value.
+#ifdef _MSC_VER
+    constexpr uint32_t crc = utils::CRC32(__FUNCSIG__);
+#else
+    constexpr uint32_t crc = utils::CRC32(__PRETTY_FUNCTION__);
+#endif
+    constexpr uint32_t bit_a = (crc & 63);
+    constexpr uint32_t bit_b = ((crc >> 6) & 63);
+    return (static_cast<HashCode>(1) << bit_a) |
+           (static_cast<HashCode>(1) << bit_b);
+  }
+
+  /// @returns the hashcode of the given type, bitwise-or'd with the hashcodes
+  /// of all base classes.
+  template <typename T>
+  static constexpr HashCode FullHashCodeOf() {
+    if constexpr (std::is_same_v<T, CastableBase>) {
+      return HashCodeOf<CastableBase>();
+    } else {
+      return HashCodeOf<T>() | FullHashCodeOf<typename T::TrueBase>();
+    }
+  }
+
+  /// @returns the bitwise-or'd hashcodes of all the types of the tuple `TUPLE`.
+  /// @see HashCodeOf
+  template <typename TUPLE>
+  static constexpr HashCode CombinedHashCodeOfTuple() {
+    constexpr auto kCount = std::tuple_size_v<TUPLE>;
+    if constexpr (kCount == 0) {
+      return 0;
+    } else if constexpr (kCount == 1) {
+      return HashCodeOf<std::remove_cv_t<std::tuple_element_t<0, TUPLE>>>();
+    } else {
+      constexpr auto kMid = kCount / 2;
+      return CombinedHashCodeOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() |
+             CombinedHashCodeOfTuple<
+                 traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+    }
+  }
+
+  /// @returns the bitwise-or'd hashcodes of all the template parameter types.
+  /// @see HashCodeOf
+  template <typename... TYPES>
+  static constexpr HashCode CombinedHashCodeOf() {
+    return CombinedHashCodeOfTuple<std::tuple<TYPES...>>();
+  }
+
+  /// @returns true if this TypeInfo is of, or derives from any of the types in
+  /// `TUPLE`.
+  template <typename TUPLE>
+  inline bool IsAnyOfTuple() const {
+    constexpr auto kCount = std::tuple_size_v<TUPLE>;
+    if constexpr (kCount == 0) {
+      return false;
+    } else if constexpr (kCount == 1) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>());
+    } else if constexpr (kCount == 2) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<1, TUPLE>>());
+    } else if constexpr (kCount == 3) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<1, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<2, TUPLE>>());
+    } else {
+      // Optimization: Compare the object's hashcode to the bitwise-or of all
+      // the tested type's hashcodes. If there's no intersection of bits in
+      // the two masks, then we can guarantee that the type is not in `TO`.
+      if (full_hashcode & TypeInfo::CombinedHashCodeOfTuple<TUPLE>()) {
+        // Possibly one of the types in `TUPLE`.
+        // Split the search in two, and scan each block.
+        static constexpr auto kMid = kCount / 2;
+        return IsAnyOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() ||
+               IsAnyOfTuple<traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+      }
+      return false;
+    }
+  }
+
+  /// @returns true if this TypeInfo is of, or derives from any of the types in
+  /// `TYPES`.
+  template <typename... TYPES>
+  inline bool IsAnyOf() const {
+    return IsAnyOfTuple<std::tuple<TYPES...>>();
+  }
+};
+
+namespace detail {
+
+/// TypeInfoOf contains a single TypeInfo field for the type T.
+/// TINT_INSTANTIATE_TYPEINFO() must be defined in a .cpp file for each type
+/// `T`.
+template <typename T>
+struct TypeInfoOf {
+  /// The unique TypeInfo for the type T.
+  static const TypeInfo info;
+};
+
+/// A placeholder structure used for template parameters that need a default
+/// type, but can always be automatically inferred.
+struct Infer;
+
+}  // namespace detail
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from the
+/// class `TO`
+/// @param obj the object to test from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
+inline bool Is(FROM* obj) {
+  if (obj == nullptr) {
+    return false;
+  }
+  return TypeInfo::Is<TO, FROM, FLAGS>(&obj->TypeInfo());
+}
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from the
+/// type `TYPE`, and pred(const TYPE*) returns true
+/// @param obj the object to test from
+/// @param pred predicate function with signature `bool(const TYPE*)` called iff
+/// object is of, or derives from the class `TYPE`.
+/// @see CastFlags
+template <typename TYPE,
+          int FLAGS = 0,
+          typename OBJ = detail::Infer,
+          typename Pred = detail::Infer>
+inline bool Is(OBJ* obj, Pred&& pred) {
+  return Is<TYPE, FLAGS, OBJ>(obj) &&
+         pred(static_cast<std::add_const_t<TYPE>*>(obj));
+}
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from any of
+/// the types in `TYPES`.OBJ
+/// @param obj the object to query.
+template <typename... TYPES, typename OBJ>
+inline bool IsAnyOf(OBJ* obj) {
+  if (!obj) {
+    return false;
+  }
+  return obj->TypeInfo().template IsAnyOf<TYPES...>();
+}
+
+/// @returns obj dynamically cast to the type `TO` or `nullptr` if
+/// this object does not derive from `TO`.
+/// @param obj the object to cast from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
+inline TO* As(FROM* obj) {
+  auto* as_castable = static_cast<CastableBase*>(obj);
+  return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr;
+}
+
+/// @returns obj dynamically cast to the type `TO` or `nullptr` if
+/// this object does not derive from `TO`.
+/// @param obj the object to cast from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
+inline const TO* As(const FROM* obj) {
+  auto* as_castable = static_cast<const CastableBase*>(obj);
+  return Is<TO, FLAGS>(obj) ? static_cast<const TO*>(as_castable) : nullptr;
+}
+
+/// CastableBase is the base class for all Castable objects.
+/// It is not encouraged to directly derive from CastableBase without using the
+/// Castable helper template.
+/// @see Castable
+class CastableBase {
+ public:
+  /// Copy constructor
+  CastableBase(const CastableBase&) = default;
+
+  /// Destructor
+  virtual ~CastableBase() = default;
+
+  /// Copy assignment
+  /// @param other the CastableBase to copy
+  /// @returns the new CastableBase
+  CastableBase& operator=(const CastableBase& other) = default;
+
+  /// @returns the TypeInfo of the object
+  virtual const tint::TypeInfo& TypeInfo() const = 0;
+
+  /// @returns true if this object is of, or derives from the class `TO`
+  template <typename TO>
+  inline bool Is() const {
+    return tint::Is<TO>(this);
+  }
+
+  /// @returns true if this object is of, or derives from the class `TO` and
+  /// pred(const TO*) returns true
+  /// @param pred predicate function with signature `bool(const TO*)` called iff
+  /// object is of, or derives from the class `TO`.
+  template <typename TO, int FLAGS = 0, typename Pred = detail::Infer>
+  inline bool Is(Pred&& pred) const {
+    return tint::Is<TO, FLAGS>(this, std::forward<Pred>(pred));
+  }
+
+  /// @returns true if this object is of, or derives from any of the `TO`
+  /// classes.
+  template <typename... TO>
+  inline bool IsAnyOf() const {
+    return tint::IsAnyOf<TO...>(this);
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
+  inline TO* As() {
+    return tint::As<TO, FLAGS>(this);
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
+  inline const TO* As() const {
+    return tint::As<const TO, FLAGS>(this);
+  }
+
+ protected:
+  CastableBase() = default;
+};
+
+/// Castable is a helper to derive `CLASS` from `BASE`, automatically
+/// implementing the Is() and As() methods, along with a #Base type alias.
+///
+/// Example usage:
+///
+/// ```
+/// class Animal : public Castable<Animal> {};
+///
+/// class Sheep : public Castable<Sheep, Animal> {};
+///
+/// Sheep* cast_to_sheep(Animal* animal) {
+///    // You can query whether a Castable is of the given type with Is<T>():
+///    printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no");
+///
+///    // You can always just try the cast with As<T>().
+///    // If the object is not of the correct type, As<T>() will return nullptr:
+///    return animal->As<Sheep>();
+/// }
+/// ```
+template <typename CLASS, typename BASE = CastableBase>
+class Castable : public BASE {
+ public:
+  // Inherit the `BASE` class constructors.
+  using BASE::BASE;
+
+  /// A type alias for `CLASS` to easily access the `BASE` class members.
+  /// Base actually aliases to the Castable instead of `BASE` so that you can
+  /// use Base in the `CLASS` constructor.
+  using Base = Castable;
+
+  /// A type alias for `BASE`.
+  using TrueBase = BASE;
+
+  /// @returns the TypeInfo of the object
+  const tint::TypeInfo& TypeInfo() const override {
+    return TypeInfo::Of<CLASS>();
+  }
+
+  /// @returns true if this object is of, or derives from the class `TO`
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
+  inline bool Is() const {
+    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this));
+  }
+
+  /// @returns true if this object is of, or derives from the class `TO` and
+  /// pred(const TO*) returns true
+  /// @param pred predicate function with signature `bool(const TO*)` called iff
+  /// object is of, or derives from the class `TO`.
+  template <int FLAGS = 0, typename Pred = detail::Infer>
+  inline bool Is(Pred&& pred) const {
+    using TO =
+        typename std::remove_pointer<traits::ParameterType<Pred, 0>>::type;
+    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
+                               std::forward<Pred>(pred));
+  }
+
+  /// @returns true if this object is of, or derives from any of the `TO`
+  /// classes.
+  template <typename... TO>
+  inline bool IsAnyOf() const {
+    return tint::IsAnyOf<TO...>(static_cast<const CLASS*>(this));
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
+  inline TO* As() {
+    return tint::As<TO, FLAGS>(this);
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
+  inline const TO* As() const {
+    return tint::As<const TO, FLAGS>(this);
+  }
+};
+
+namespace detail {
+/// <code>typename CastableCommonBaseImpl<TYPES>::type</code> resolves to the
+/// common base class for all of TYPES.
+template <typename... TYPES>
+struct CastableCommonBaseImpl {};
+
+/// Alias to typename CastableCommonBaseImpl<TYPES>::type
+template <typename... TYPES>
+using CastableCommonBase =
+    typename detail::CastableCommonBaseImpl<TYPES...>::type;
+
+/// CastableCommonBaseImpl template specialization for a single type
+template <typename T>
+struct CastableCommonBaseImpl<T> {
+  /// Common base class of a single type is itself
+  using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> CastableBase specialization
+template <typename A>
+struct CastableCommonBaseImpl<A, CastableBase> {
+  /// Common base class for A and CastableBase is CastableBase
+  using type = CastableBase;
+};
+
+/// CastableCommonBaseImpl T <-> Ignore specialization
+template <typename T>
+struct CastableCommonBaseImpl<T, Ignore> {
+  /// Resolves to T as the other type is ignored
+  using type = T;
+};
+
+/// CastableCommonBaseImpl Ignore <-> T specialization
+template <typename T>
+struct CastableCommonBaseImpl<Ignore, T> {
+  /// Resolves to T as the other type is ignored
+  using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> B specialization
+template <typename A, typename B>
+struct CastableCommonBaseImpl<A, B> {
+  /// The common base class for A, B and OTHERS
+  using type = std::conditional_t<traits::IsTypeOrDerived<A, B>,
+                                  B,  // A derives from B
+                                  CastableCommonBase<A, typename B::TrueBase>>;
+};
+
+/// CastableCommonBaseImpl 3+ types specialization
+template <typename A, typename B, typename... OTHERS>
+struct CastableCommonBaseImpl<A, B, OTHERS...> {
+  /// The common base class for A, B and OTHERS
+  using type = CastableCommonBase<CastableCommonBase<A, B>, OTHERS...>;
+};
+
+}  // namespace detail
+
+/// Resolves to the common most derived type that each of the types in `TYPES`
+/// derives from.
+template <typename... TYPES>
+using CastableCommonBase = detail::CastableCommonBase<TYPES...>;
+
+/// Default can be used as the default case for a Switch(), when all previous
+/// cases failed to match.
+///
+/// Example:
+/// ```
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     [&](Default) { /* If not TypeA or TypeB */ });
+/// ```
+struct Default {};
+
+namespace detail {
+
+/// Evaluates to the Switch case type being matched by the switch case function
+/// `FN`.
+/// @note does not handle the Default case
+/// @see Switch().
+template <typename FN>
+using SwitchCaseType = std::remove_pointer_t<
+    traits::ParameterType<std::remove_reference_t<FN>, 0>>;
+
+/// Evaluates to true if the function `FN` has the signature of a Default case
+/// in a Switch().
+/// @see Switch().
+template <typename FN>
+inline constexpr bool IsDefaultCase =
+    std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>,
+                   Default>;
+
+/// Searches the list of Switch cases for a Default case, returning the index of
+/// the Default case. If the a Default case is not found in the tuple, then -1
+/// is returned.
+template <typename TUPLE, std::size_t START_IDX = 0>
+constexpr int IndexOfDefaultCase() {
+  if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
+    return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
+               ? static_cast<int>(START_IDX)
+               : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
+  } else {
+    return -1;
+  }
+}
+
+/// The implementation of Switch() for non-Default cases.
+/// Switch splits the cases into two a low and high block of cases, and quickly
+/// rules out blocks that cannot match by comparing the TypeInfo::HashCode of
+/// the object and the cases in the block. If a block of cases may match the
+/// given object's type, then that block is split into two, and the process
+/// recurses. When NonDefaultCases() is called with a single case, then As<>
+/// will be used to dynamically cast to the case type and if the cast succeeds,
+/// then the case handler is called.
+/// @returns true if a case handler was found, otherwise false.
+template <typename T, typename RETURN_TYPE, typename... CASES>
+inline bool NonDefaultCases(T* object,
+                            const TypeInfo* type,
+                            RETURN_TYPE* result,
+                            std::tuple<CASES...>&& cases) {
+  using Cases = std::tuple<CASES...>;
+
+  (void)result;  // Not always used, avoid warning.
+
+  static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
+  static constexpr size_t kNumCases = sizeof...(CASES);
+
+  if constexpr (kNumCases == 0) {
+    // No cases. Nothing to do.
+    return false;
+  } else if constexpr (kNumCases == 1) {  // NOLINT: cpplint doesn't understand
+                                          // `else if constexpr`
+    // Single case.
+    using CaseFunc = std::tuple_element_t<0, Cases>;
+    static_assert(!IsDefaultCase<CaseFunc>,
+                  "NonDefaultCases called with a Default case");
+    // Attempt to dynamically cast the object to the handler type. If that
+    // succeeds, call the case handler with the cast object.
+    using CaseType = SwitchCaseType<CaseFunc>;
+    if (type->Is(&TypeInfo::Of<CaseType>())) {
+      auto* ptr = static_cast<CaseType*>(object);
+      if constexpr (kHasReturnType) {
+        *result = std::get<0>(cases)(ptr);
+      } else {
+        std::get<0>(cases)(ptr);
+      }
+      return true;
+    }
+    return false;
+  } else {
+    // Multiple cases.
+    // Check the hashcode bits to see if there's any possibility of a case
+    // matching in these cases. If there isn't, we can skip all these cases.
+    if (type->full_hashcode &
+        TypeInfo::CombinedHashCodeOf<SwitchCaseType<CASES>...>()) {
+      // There's a possibility. We need to scan further.
+      // Split the cases into two, and recurse.
+      constexpr size_t kMid = kNumCases / 2;
+      return NonDefaultCases(object, type, result,
+                             traits::Slice<0, kMid>(cases)) ||
+             NonDefaultCases(object, type, result,
+                             traits::Slice<kMid, kNumCases - kMid>(cases));
+    } else {
+      return false;
+    }
+  }
+}
+
+/// The implementation of Switch() for all cases.
+/// @see NonDefaultCases
+template <typename T, typename RETURN_TYPE, typename... CASES>
+inline void SwitchCases(T* object,
+                        RETURN_TYPE* result,
+                        std::tuple<CASES...>&& cases) {
+  using Cases = std::tuple<CASES...>;
+  static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<Cases>();
+  static_assert(kDefaultIndex == -1 || std::tuple_size_v<Cases> - 1,
+                "Default case must be last in Switch()");
+  static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
+  static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
+
+  if (object) {
+    auto* type = &object->TypeInfo();
+    if constexpr (kHasDefaultCase) {
+      // Evaluate non-default cases.
+      if (!detail::NonDefaultCases<T>(object, type, result,
+                                      traits::Slice<0, kDefaultIndex>(cases))) {
+        // Nothing matched. Evaluate default case.
+        if constexpr (kHasReturnType) {
+          *result = std::get<kDefaultIndex>(cases)({});
+        } else {
+          std::get<kDefaultIndex>(cases)({});
+        }
+      }
+    } else {
+      detail::NonDefaultCases<T>(object, type, result, std::move(cases));
+    }
+  } else {
+    // Object is nullptr, so no cases can match
+    if constexpr (kHasDefaultCase) {
+      // Evaluate default case.
+      if constexpr (kHasReturnType) {
+        *result = std::get<kDefaultIndex>(cases)({});
+      } else {
+        std::get<kDefaultIndex>(cases)({});
+      }
+    }
+  }
+}
+
+}  // namespace detail
+
+/// Switch is used to dispatch one of the provided callback case handler
+/// functions based on the type of `object` and the parameter type of the case
+/// handlers. Switch will sequentially check the type of `object` against each
+/// of the switch case handler functions, and will invoke the first case handler
+/// function which has a parameter type that matches the object type. When a
+/// case handler is matched, it will be called with the single argument of
+/// `object` cast to the case handler's parameter type. Switch will invoke at
+/// most one case handler. Each of the case functions must have the signature
+/// `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
+/// is the return type, consistent across all case handlers.
+///
+/// An optional default case function with the signature `R(Default)` can be
+/// used as the last case. This default case will be called if all previous
+/// cases failed to match.
+///
+/// If `object` is nullptr and a default case is provided, then the default case
+/// will be called. If `object` is nullptr and no default case is provided, then
+/// no cases will be called.
+///
+/// Example:
+/// ```
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ });
+///
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     [&](Default) { /* Called if object is not TypeA or TypeB */ });
+/// ```
+///
+/// @param object the object who's type is used to
+/// @param cases the switch cases
+/// @return the value returned by the called case. If no cases matched, then the
+/// zero value for the consistent case type.
+template <typename T, typename... CASES>
+inline auto Switch(T* object, CASES&&... cases) {
+  using Cases = std::tuple<CASES...>;
+  using ReturnType = traits::ReturnType<std::tuple_element_t<0, Cases>>;
+  static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
+
+  if constexpr (kHasReturnType) {
+    ReturnType res = {};
+    detail::SwitchCases(object, &res,
+                        std::forward_as_tuple(std::forward<CASES>(cases)...));
+    return res;
+  } else {
+    detail::SwitchCases<T, void>(
+        object, nullptr, std::forward_as_tuple(std::forward<CASES>(cases)...));
+  }
+}
+
+}  // namespace tint
+
+TINT_CASTABLE_POP_DISABLE_WARNINGS();
+
+#endif  // SRC_TINT_CASTABLE_H_
diff --git a/src/castable_bench.cc b/src/tint/castable_bench.cc
similarity index 100%
rename from src/castable_bench.cc
rename to src/tint/castable_bench.cc
diff --git a/src/tint/castable_test.cc b/src/tint/castable_test.cc
new file mode 100644
index 0000000..0e01172
--- /dev/null
+++ b/src/tint/castable_test.cc
@@ -0,0 +1,471 @@
+// Copyright 2020 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/castable.h"
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct Animal : public tint::Castable<Animal> {};
+struct Amphibian : public tint::Castable<Amphibian, Animal> {};
+struct Mammal : public tint::Castable<Mammal, Animal> {};
+struct Reptile : public tint::Castable<Reptile, Animal> {};
+struct Frog : public tint::Castable<Frog, Amphibian> {};
+struct Bear : public tint::Castable<Bear, Mammal> {};
+struct Lizard : public tint::Castable<Lizard, Reptile> {};
+struct Gecko : public tint::Castable<Gecko, Lizard> {};
+struct Iguana : public tint::Castable<Iguana, Lizard> {};
+
+TEST(CastableBase, Is) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE(frog->Is<Animal>());
+  ASSERT_TRUE(bear->Is<Animal>());
+  ASSERT_TRUE(gecko->Is<Animal>());
+
+  ASSERT_TRUE(frog->Is<Amphibian>());
+  ASSERT_FALSE(bear->Is<Amphibian>());
+  ASSERT_FALSE(gecko->Is<Amphibian>());
+
+  ASSERT_FALSE(frog->Is<Mammal>());
+  ASSERT_TRUE(bear->Is<Mammal>());
+  ASSERT_FALSE(gecko->Is<Mammal>());
+
+  ASSERT_FALSE(frog->Is<Reptile>());
+  ASSERT_FALSE(bear->Is<Reptile>());
+  ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(CastableBase, Is_kDontErrorOnImpossibleCast) {
+  // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without
+  // going via CastableBase.
+  auto frog = std::make_unique<Frog>();
+  auto bear = std::make_unique<Bear>();
+  auto gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>()));
+}
+
+TEST(CastableBase, IsWithPredicate) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+
+  frog->Is<Animal>([&frog](const Animal* a) {
+    EXPECT_EQ(a, frog.get());
+    return true;
+  });
+
+  ASSERT_TRUE((frog->Is<Animal>([](const Animal*) { return true; })));
+  ASSERT_FALSE((frog->Is<Animal>([](const Animal*) { return false; })));
+
+  // Predicate not called if cast is invalid
+  auto expect_not_called = [] { FAIL() << "Should not be called"; };
+  ASSERT_FALSE((frog->Is<Bear>([&](const Animal*) {
+    expect_not_called();
+    return true;
+  })));
+}
+
+TEST(CastableBase, IsAnyOf) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE((frog->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+  ASSERT_TRUE((frog->IsAnyOf<Mammal, Amphibian>()));
+  ASSERT_TRUE((frog->IsAnyOf<Amphibian, Reptile>()));
+  ASSERT_FALSE((frog->IsAnyOf<Mammal, Reptile>()));
+
+  ASSERT_TRUE((bear->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+  ASSERT_TRUE((bear->IsAnyOf<Mammal, Amphibian>()));
+  ASSERT_TRUE((bear->IsAnyOf<Mammal, Reptile>()));
+  ASSERT_FALSE((bear->IsAnyOf<Amphibian, Reptile>()));
+
+  ASSERT_TRUE((gecko->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+  ASSERT_TRUE((gecko->IsAnyOf<Mammal, Reptile>()));
+  ASSERT_TRUE((gecko->IsAnyOf<Amphibian, Reptile>()));
+  ASSERT_FALSE((gecko->IsAnyOf<Mammal, Amphibian>()));
+}
+
+TEST(CastableBase, As) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+  ASSERT_EQ(frog->As<Mammal>(), nullptr);
+  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+  ASSERT_EQ(frog->As<Reptile>(), nullptr);
+  ASSERT_EQ(bear->As<Reptile>(), nullptr);
+  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(CastableBase, As_kDontErrorOnImpossibleCast) {
+  // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without
+  // going via CastableBase.
+  auto frog = std::make_unique<Frog>();
+  auto bear = std::make_unique<Bear>();
+  auto gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(frog.get()));
+  ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(bear.get()));
+  ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()),
+            static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+
+  ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()),
+            static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+
+  ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()),
+            static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(Castable, Is) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE(frog->Is<Animal>());
+  ASSERT_TRUE(bear->Is<Animal>());
+  ASSERT_TRUE(gecko->Is<Animal>());
+
+  ASSERT_TRUE(frog->Is<Amphibian>());
+  ASSERT_FALSE(bear->Is<Amphibian>());
+  ASSERT_FALSE(gecko->Is<Amphibian>());
+
+  ASSERT_FALSE(frog->Is<Mammal>());
+  ASSERT_TRUE(bear->Is<Mammal>());
+  ASSERT_FALSE(gecko->Is<Mammal>());
+
+  ASSERT_FALSE(frog->Is<Reptile>());
+  ASSERT_FALSE(bear->Is<Reptile>());
+  ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(Castable, IsWithPredicate) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+
+  frog->Is([&frog](const Animal* a) {
+    EXPECT_EQ(a, frog.get());
+    return true;
+  });
+
+  ASSERT_TRUE((frog->Is([](const Animal*) { return true; })));
+  ASSERT_FALSE((frog->Is([](const Animal*) { return false; })));
+
+  // Predicate not called if cast is invalid
+  auto expect_not_called = [] { FAIL() << "Should not be called"; };
+  ASSERT_FALSE((frog->Is([&](const Bear*) {
+    expect_not_called();
+    return true;
+  })));
+}
+
+TEST(Castable, As) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+  ASSERT_EQ(frog->As<Mammal>(), nullptr);
+  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+  ASSERT_EQ(frog->As<Reptile>(), nullptr);
+  ASSERT_EQ(bear->As<Reptile>(), nullptr);
+  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(Castable, SwitchNoDefault) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+  {
+    bool frog_matched_amphibian = false;
+    Switch(
+        frog.get(),  //
+        [&](Reptile*) { FAIL() << "frog is not reptile"; },
+        [&](Mammal*) { FAIL() << "frog is not mammal"; },
+        [&](Amphibian* amphibian) {
+          EXPECT_EQ(amphibian, frog.get());
+          frog_matched_amphibian = true;
+        });
+    EXPECT_TRUE(frog_matched_amphibian);
+  }
+  {
+    bool bear_matched_mammal = false;
+    Switch(
+        bear.get(),  //
+        [&](Reptile*) { FAIL() << "bear is not reptile"; },
+        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+        [&](Mammal* mammal) {
+          EXPECT_EQ(mammal, bear.get());
+          bear_matched_mammal = true;
+        });
+    EXPECT_TRUE(bear_matched_mammal);
+  }
+  {
+    bool gecko_matched_reptile = false;
+    Switch(
+        gecko.get(),  //
+        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+        [&](Reptile* reptile) {
+          EXPECT_EQ(reptile, gecko.get());
+          gecko_matched_reptile = true;
+        });
+    EXPECT_TRUE(gecko_matched_reptile);
+  }
+}
+
+TEST(Castable, SwitchWithUnusedDefault) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+  {
+    bool frog_matched_amphibian = false;
+    Switch(
+        frog.get(),  //
+        [&](Reptile*) { FAIL() << "frog is not reptile"; },
+        [&](Mammal*) { FAIL() << "frog is not mammal"; },
+        [&](Amphibian* amphibian) {
+          EXPECT_EQ(amphibian, frog.get());
+          frog_matched_amphibian = true;
+        },
+        [&](Default) { FAIL() << "default should not have been selected"; });
+    EXPECT_TRUE(frog_matched_amphibian);
+  }
+  {
+    bool bear_matched_mammal = false;
+    Switch(
+        bear.get(),  //
+        [&](Reptile*) { FAIL() << "bear is not reptile"; },
+        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+        [&](Mammal* mammal) {
+          EXPECT_EQ(mammal, bear.get());
+          bear_matched_mammal = true;
+        },
+        [&](Default) { FAIL() << "default should not have been selected"; });
+    EXPECT_TRUE(bear_matched_mammal);
+  }
+  {
+    bool gecko_matched_reptile = false;
+    Switch(
+        gecko.get(),  //
+        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+        [&](Reptile* reptile) {
+          EXPECT_EQ(reptile, gecko.get());
+          gecko_matched_reptile = true;
+        },
+        [&](Default) { FAIL() << "default should not have been selected"; });
+    EXPECT_TRUE(gecko_matched_reptile);
+  }
+}
+
+TEST(Castable, SwitchDefault) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+  {
+    bool frog_matched_default = false;
+    Switch(
+        frog.get(),  //
+        [&](Reptile*) { FAIL() << "frog is not reptile"; },
+        [&](Mammal*) { FAIL() << "frog is not mammal"; },
+        [&](Default) { frog_matched_default = true; });
+    EXPECT_TRUE(frog_matched_default);
+  }
+  {
+    bool bear_matched_default = false;
+    Switch(
+        bear.get(),  //
+        [&](Reptile*) { FAIL() << "bear is not reptile"; },
+        [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+        [&](Default) { bear_matched_default = true; });
+    EXPECT_TRUE(bear_matched_default);
+  }
+  {
+    bool gecko_matched_default = false;
+    Switch(
+        gecko.get(),  //
+        [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+        [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+        [&](Default) { gecko_matched_default = true; });
+    EXPECT_TRUE(gecko_matched_default);
+  }
+}
+
+TEST(Castable, SwitchMatchFirst) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  {
+    bool frog_matched_animal = false;
+    Switch(
+        frog.get(),
+        [&](Animal* animal) {
+          EXPECT_EQ(animal, frog.get());
+          frog_matched_animal = true;
+        },
+        [&](Amphibian*) { FAIL() << "animal should have been matched first"; });
+    EXPECT_TRUE(frog_matched_animal);
+  }
+  {
+    bool frog_matched_amphibian = false;
+    Switch(
+        frog.get(),
+        [&](Amphibian* amphibain) {
+          EXPECT_EQ(amphibain, frog.get());
+          frog_matched_amphibian = true;
+        },
+        [&](Animal*) { FAIL() << "amphibian should have been matched first"; });
+    EXPECT_TRUE(frog_matched_amphibian);
+  }
+}
+
+TEST(Castable, SwitchNull) {
+  Animal* null = nullptr;
+  Switch(
+      null,  //
+      [&](Amphibian*) { FAIL() << "should not be called"; },
+      [&](Animal*) { FAIL() << "should not be called"; });
+}
+
+TEST(Castable, SwitchNullNoDefault) {
+  Animal* null = nullptr;
+  bool default_called = false;
+  Switch(
+      null,  //
+      [&](Amphibian*) { FAIL() << "should not be called"; },
+      [&](Animal*) { FAIL() << "should not be called"; },
+      [&](Default) { default_called = true; });
+  EXPECT_TRUE(default_called);
+}
+
+// IsCastable static tests
+static_assert(IsCastable<CastableBase>);
+static_assert(IsCastable<Animal>);
+static_assert(IsCastable<Ignore, Frog, Bear>);
+static_assert(IsCastable<Mammal, Ignore, Amphibian, Gecko>);
+static_assert(!IsCastable<Mammal, int, Amphibian, Ignore, Gecko>);
+static_assert(!IsCastable<bool>);
+static_assert(!IsCastable<int, float>);
+static_assert(!IsCastable<Ignore>);
+
+// CastableCommonBase static tests
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal, Animal>>);
+static_assert(
+    std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal, Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile, Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog, Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko, Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana, Iguana>>);
+
+static_assert(
+    std::is_same_v<CastableBase, CastableCommonBase<CastableBase, Animal>>);
+static_assert(
+    std::is_same_v<CastableBase, CastableCommonBase<Animal, CastableBase>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Frog>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Frog, Amphibian>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Reptile, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Reptile>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog, Iguana>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Lizard, Gecko, Iguana>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana, Lizard>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Gecko, Lizard, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Gecko, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Iguana, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Frog, Iguana>>);
+
+static_assert(
+    std::is_same_v<CastableBase,
+                   CastableCommonBase<Bear, Frog, Iguana, CastableBase>>);
+
+}  // namespace
+
+TINT_INSTANTIATE_TYPEINFO(Animal);
+TINT_INSTANTIATE_TYPEINFO(Amphibian);
+TINT_INSTANTIATE_TYPEINFO(Mammal);
+TINT_INSTANTIATE_TYPEINFO(Reptile);
+TINT_INSTANTIATE_TYPEINFO(Frog);
+TINT_INSTANTIATE_TYPEINFO(Bear);
+TINT_INSTANTIATE_TYPEINFO(Lizard);
+TINT_INSTANTIATE_TYPEINFO(Gecko);
+
+}  // namespace tint
diff --git a/src/tint/clone_context.cc b/src/tint/clone_context.cc
new file mode 100644
index 0000000..afdf488
--- /dev/null
+++ b/src/tint/clone_context.cc
@@ -0,0 +1,116 @@
+// Copyright 2020 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/clone_context.h"
+
+#include <string>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::Cloneable);
+
+namespace tint {
+
+CloneContext::ListTransforms::ListTransforms() = default;
+CloneContext::ListTransforms::~ListTransforms() = default;
+
+CloneContext::CloneContext(ProgramBuilder* to,
+                           Program const* from,
+                           bool auto_clone_symbols)
+    : dst(to), src(from) {
+  if (auto_clone_symbols) {
+    // Almost all transforms will want to clone all symbols before doing any
+    // work, to avoid any newly created symbols clashing with existing symbols
+    // in the source program and causing them to be renamed.
+    from->Symbols().Foreach([&](Symbol s, const std::string&) { Clone(s); });
+  }
+}
+
+CloneContext::CloneContext(ProgramBuilder* builder)
+    : CloneContext(builder, nullptr, false) {}
+
+CloneContext::~CloneContext() = default;
+
+Symbol CloneContext::Clone(Symbol s) {
+  if (!src) {
+    return s;  // In-place clone
+  }
+  return utils::GetOrCreate(cloned_symbols_, s, [&]() -> Symbol {
+    if (symbol_transform_) {
+      return symbol_transform_(s);
+    }
+    return dst->Symbols().New(src->Symbols().NameFor(s));
+  });
+}
+
+void CloneContext::Clone() {
+  dst->AST().Copy(this, &src->AST());
+}
+
+ast::FunctionList CloneContext::Clone(const ast::FunctionList& v) {
+  ast::FunctionList out;
+  out.reserve(v.size());
+  for (const ast::Function* el : v) {
+    out.Add(Clone(el));
+  }
+  return out;
+}
+
+const tint::Cloneable* CloneContext::CloneCloneable(const Cloneable* object) {
+  // If the input is nullptr, there's nothing to clone - just return nullptr.
+  if (object == nullptr) {
+    return nullptr;
+  }
+
+  // Was Replace() called for this object?
+  auto it = replacements_.find(object);
+  if (it != replacements_.end()) {
+    return it->second();
+  }
+
+  // Attempt to clone using the registered replacer functions.
+  auto& typeinfo = object->TypeInfo();
+  for (auto& transform : transforms_) {
+    if (typeinfo.Is(transform.typeinfo)) {
+      if (auto* transformed = transform.function(object)) {
+        return transformed;
+      }
+      break;
+    }
+  }
+
+  // No transform for this type, or the transform returned nullptr.
+  // Clone with T::Clone().
+  return object->Clone(this);
+}
+
+void CloneContext::CheckedCastFailure(const Cloneable* got,
+                                      const TypeInfo& expected) {
+  TINT_ICE(Clone, Diagnostics())
+      << "Cloned object was not of the expected type\n"
+      << "got:      " << got->TypeInfo().name << "\n"
+      << "expected: " << expected.name;
+}
+
+diag::List& CloneContext::Diagnostics() const {
+  return dst->Diagnostics();
+}
+
+CloneContext::CloneableTransform::CloneableTransform() = default;
+CloneContext::CloneableTransform::CloneableTransform(
+    const CloneableTransform&) = default;
+CloneContext::CloneableTransform::~CloneableTransform() = default;
+
+}  // namespace tint
diff --git a/src/tint/clone_context.h b/src/tint/clone_context.h
new file mode 100644
index 0000000..8c02c83
--- /dev/null
+++ b/src/tint/clone_context.h
@@ -0,0 +1,584 @@
+// Copyright 2020 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_CLONE_CONTEXT_H_
+#define SRC_TINT_CLONE_CONTEXT_H_
+
+#include <algorithm>
+#include <functional>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/castable.h"
+#include "src/tint/debug.h"
+#include "src/tint/program_id.h"
+#include "src/tint/symbol.h"
+#include "src/tint/traits.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+class Program;
+class ProgramBuilder;
+namespace ast {
+class FunctionList;
+class Node;
+}  // namespace ast
+
+ProgramID ProgramIDOf(const Program*);
+ProgramID ProgramIDOf(const ProgramBuilder*);
+
+/// Cloneable is the base class for all objects that can be cloned
+class Cloneable : public Castable<Cloneable> {
+ public:
+  /// Performs a deep clone of this object using the CloneContext `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned object
+  virtual const Cloneable* Clone(CloneContext* ctx) const = 0;
+};
+
+/// @returns an invalid ProgramID
+inline ProgramID ProgramIDOf(const Cloneable*) {
+  return ProgramID();
+}
+
+/// CloneContext holds the state used while cloning AST nodes.
+class CloneContext {
+  /// ParamTypeIsPtrOf<F, T> is true iff the first parameter of
+  /// F is a pointer of (or derives from) type T.
+  template <typename F, typename T>
+  static constexpr bool ParamTypeIsPtrOf = traits::IsTypeOrDerived<
+      typename std::remove_pointer<traits::ParameterType<F, 0>>::type,
+      T>;
+
+ public:
+  /// SymbolTransform is a function that takes a symbol and returns a new
+  /// symbol.
+  using SymbolTransform = std::function<Symbol(Symbol)>;
+
+  /// Constructor for cloning objects from `from` into `to`.
+  /// @param to the target ProgramBuilder to clone into
+  /// @param from the source Program to clone from
+  /// @param auto_clone_symbols clone all symbols in `from` before returning
+  CloneContext(ProgramBuilder* to,
+               Program const* from,
+               bool auto_clone_symbols = true);
+
+  /// Constructor for cloning objects from and to the ProgramBuilder `builder`.
+  /// @param builder the ProgramBuilder
+  explicit CloneContext(ProgramBuilder* builder);
+
+  /// Destructor
+  ~CloneContext();
+
+  /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
+  /// not null. If `a` is null, then Clone() returns null.
+  ///
+  /// Clone() may use a function registered with ReplaceAll() to create a
+  /// transformed version of the object. See ReplaceAll() for more information.
+  ///
+  /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
+  /// the Node or sem::Type `a` must be owned by the Program #src.
+  ///
+  /// @param object the type deriving from Cloneable to clone
+  /// @return the cloned node
+  template <typename T>
+  const T* Clone(const T* object) {
+    if (src) {
+      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
+    }
+    if (auto* cloned = CloneCloneable(object)) {
+      auto* out = CheckedCast<T>(cloned);
+      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out);
+      return out;
+    }
+    return nullptr;
+  }
+
+  /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
+  /// not null. If `a` is null, then Clone() returns null.
+  ///
+  /// Unlike Clone(), this method does not invoke or use any transformations
+  /// registered by ReplaceAll().
+  ///
+  /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
+  /// the Node or sem::Type `a` must be owned by the Program #src.
+  ///
+  /// @param a the type deriving from Cloneable to clone
+  /// @return the cloned node
+  template <typename T>
+  const T* CloneWithoutTransform(const T* a) {
+    // If the input is nullptr, there's nothing to clone - just return nullptr.
+    if (a == nullptr) {
+      return nullptr;
+    }
+    if (src) {
+      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, a);
+    }
+    auto* c = a->Clone(this);
+    return CheckedCast<T>(c);
+  }
+
+  /// Clones the Source `s` into #dst
+  /// TODO(bclayton) - Currently this 'clone' is a shallow copy. If/when
+  /// `Source.File`s are owned by the Program this should make a copy of the
+  /// file.
+  /// @param s the `Source` to clone
+  /// @return the cloned source
+  Source Clone(const Source& s) const { return s; }
+
+  /// Clones the Symbol `s` into #dst
+  ///
+  /// The Symbol `s` must be owned by the Program #src.
+  ///
+  /// @param s the Symbol to clone
+  /// @return the cloned source
+  Symbol Clone(Symbol s);
+
+  /// Clones each of the elements of the vector `v` into the ProgramBuilder
+  /// #dst.
+  ///
+  /// All the elements of the vector `v` must be owned by the Program #src.
+  ///
+  /// @param v the vector to clone
+  /// @return the cloned vector
+  template <typename T>
+  std::vector<T> Clone(const std::vector<T>& v) {
+    std::vector<T> out;
+    out.reserve(v.size());
+    for (auto& el : v) {
+      out.emplace_back(Clone(el));
+    }
+    return out;
+  }
+
+  /// Clones each of the elements of the vector `v` using the ProgramBuilder
+  /// #dst, inserting any additional elements into the list that were registered
+  /// with calls to InsertBefore().
+  ///
+  /// All the elements of the vector `v` must be owned by the Program #src.
+  ///
+  /// @param v the vector to clone
+  /// @return the cloned vector
+  template <typename T>
+  std::vector<T*> Clone(const std::vector<T*>& v) {
+    std::vector<T*> out;
+    Clone(out, v);
+    return out;
+  }
+
+  /// Clones each of the elements of the vector `from` into the vector `to`,
+  /// inserting any additional elements into the list that were registered with
+  /// calls to InsertBefore().
+  ///
+  /// All the elements of the vector `from` must be owned by the Program #src.
+  ///
+  /// @param from the vector to clone
+  /// @param to the cloned result
+  template <typename T>
+  void Clone(std::vector<T*>& to, const std::vector<T*>& from) {
+    to.reserve(from.size());
+
+    auto list_transform_it = list_transforms_.find(&from);
+    if (list_transform_it != list_transforms_.end()) {
+      const auto& transforms = list_transform_it->second;
+      for (auto* o : transforms.insert_front_) {
+        to.emplace_back(CheckedCast<T>(o));
+      }
+      for (auto& el : from) {
+        auto insert_before_it = transforms.insert_before_.find(el);
+        if (insert_before_it != transforms.insert_before_.end()) {
+          for (auto insert : insert_before_it->second) {
+            to.emplace_back(CheckedCast<T>(insert));
+          }
+        }
+        if (transforms.remove_.count(el) == 0) {
+          to.emplace_back(Clone(el));
+        }
+        auto insert_after_it = transforms.insert_after_.find(el);
+        if (insert_after_it != transforms.insert_after_.end()) {
+          for (auto insert : insert_after_it->second) {
+            to.emplace_back(CheckedCast<T>(insert));
+          }
+        }
+      }
+      for (auto* o : transforms.insert_back_) {
+        to.emplace_back(CheckedCast<T>(o));
+      }
+    } else {
+      for (auto& el : from) {
+        to.emplace_back(Clone(el));
+
+        // Clone(el) may have inserted after
+        list_transform_it = list_transforms_.find(&from);
+        if (list_transform_it != list_transforms_.end()) {
+          const auto& transforms = list_transform_it->second;
+
+          auto insert_after_it = transforms.insert_after_.find(el);
+          if (insert_after_it != transforms.insert_after_.end()) {
+            for (auto insert : insert_after_it->second) {
+              to.emplace_back(CheckedCast<T>(insert));
+            }
+          }
+        }
+      }
+
+      // Clone(el)s may have inserted back
+      list_transform_it = list_transforms_.find(&from);
+      if (list_transform_it != list_transforms_.end()) {
+        const auto& transforms = list_transform_it->second;
+
+        for (auto* o : transforms.insert_back_) {
+          to.emplace_back(CheckedCast<T>(o));
+        }
+      }
+    }
+  }
+
+  /// Clones each of the elements of the vector `v` into the ProgramBuilder
+  /// #dst.
+  ///
+  /// All the elements of the vector `v` must be owned by the Program #src.
+  ///
+  /// @param v the vector to clone
+  /// @return the cloned vector
+  ast::FunctionList Clone(const ast::FunctionList& v);
+
+  /// ReplaceAll() registers `replacer` to be called whenever the Clone() method
+  /// is called with a Cloneable type that matches (or derives from) the type of
+  /// the single parameter of `replacer`.
+  /// The returned Cloneable of `replacer` will be used as the replacement for
+  /// all references to the object that's being cloned. This returned Cloneable
+  /// must be owned by the Program #dst.
+  ///
+  /// `replacer` must be function-like with the signature: `T* (T*)`
+  ///  where `T` is a type deriving from Cloneable.
+  ///
+  /// If `replacer` returns a nullptr then Clone() will call `T::Clone()` to
+  /// clone the object.
+  ///
+  /// Example:
+  ///
+  /// ```
+  ///   // Replace all ast::UintLiteralExpressions with the number 42
+  ///   CloneCtx ctx(&out, in);
+  ///   ctx.ReplaceAll([&] (ast::UintLiteralExpression* l) {
+  ///       return ctx->dst->create<ast::UintLiteralExpression>(
+  ///           ctx->Clone(l->source),
+  ///           ctx->Clone(l->type),
+  ///           42);
+  ///     });
+  ///   ctx.Clone();
+  /// ```
+  ///
+  /// @warning a single handler can only be registered for any given type.
+  /// Attempting to register two handlers for the same type will result in an
+  /// ICE.
+  /// @warning The replacement object must be of the correct type for all
+  /// references of the original object. A type mismatch will result in an
+  /// assertion in debug builds, and undefined behavior in release builds.
+  /// @param replacer a function or function-like object with the signature
+  ///        `T* (T*)`, where `T` derives from Cloneable
+  /// @returns this CloneContext so calls can be chained
+  template <typename F>
+  traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>, CloneContext>& ReplaceAll(
+      F&& replacer) {
+    using TPtr = traits::ParameterType<F, 0>;
+    using T = typename std::remove_pointer<TPtr>::type;
+    for (auto& transform : transforms_) {
+      if (transform.typeinfo->Is(&TypeInfo::Of<T>()) ||
+          TypeInfo::Of<T>().Is(transform.typeinfo)) {
+        TINT_ICE(Clone, Diagnostics())
+            << "ReplaceAll() called with a handler for type "
+            << TypeInfo::Of<T>().name
+            << " that is already handled by a handler for type "
+            << transform.typeinfo->name;
+        return *this;
+      }
+    }
+    CloneableTransform transform;
+    transform.typeinfo = &TypeInfo::Of<T>();
+    transform.function = [=](const Cloneable* in) {
+      return replacer(in->As<T>());
+    };
+    transforms_.emplace_back(std::move(transform));
+    return *this;
+  }
+
+  /// ReplaceAll() registers `replacer` to be called whenever the Clone() method
+  /// is called with a Symbol.
+  /// The returned symbol of `replacer` will be used as the replacement for
+  /// all references to the symbol that's being cloned. This returned Symbol
+  /// must be owned by the Program #dst.
+  /// @param replacer a function the signature `Symbol(Symbol)`.
+  /// @warning a SymbolTransform can only be registered once. Attempting to
+  /// register a SymbolTransform more than once will result in an ICE.
+  /// @returns this CloneContext so calls can be chained
+  CloneContext& ReplaceAll(const SymbolTransform& replacer) {
+    if (symbol_transform_) {
+      TINT_ICE(Clone, Diagnostics())
+          << "ReplaceAll(const SymbolTransform&) called "
+             "multiple times on the same CloneContext";
+      return *this;
+    }
+    symbol_transform_ = replacer;
+    return *this;
+  }
+
+  /// Replace replaces all occurrences of `what` in #src with the pointer `with`
+  /// in #dst when calling Clone().
+  /// [DEPRECATED]: This function cannot handle nested replacements. Use the
+  /// overload of Replace() that take a function for the `WITH` argument.
+  /// @param what a pointer to the object in #src that will be replaced with
+  /// `with`
+  /// @param with a pointer to the replacement object owned by #dst that will be
+  /// used as a replacement for `what`
+  /// @warning The replacement object must be of the correct type for all
+  /// references of the original object. A type mismatch will result in an
+  /// assertion in debug builds, and undefined behavior in release builds.
+  /// @returns this CloneContext so calls can be chained
+  template <typename WHAT,
+            typename WITH,
+            typename = traits::EnableIfIsType<WITH, Cloneable>>
+  CloneContext& Replace(const WHAT* what, const WITH* with) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, with);
+    replacements_[what] = [with]() -> const Cloneable* { return with; };
+    return *this;
+  }
+
+  /// Replace replaces all occurrences of `what` in #src with the result of the
+  /// function `with` in #dst when calling Clone(). `with` will be called each
+  /// time `what` is cloned by this context. If `what` is not cloned, then
+  /// `with` may never be called.
+  /// @param what a pointer to the object in #src that will be replaced with
+  /// `with`
+  /// @param with a function that takes no arguments and returns a pointer to
+  /// the replacement object owned by #dst. The returned pointer will be used as
+  /// a replacement for `what`.
+  /// @warning The replacement object must be of the correct type for all
+  /// references of the original object. A type mismatch will result in an
+  /// assertion in debug builds, and undefined behavior in release builds.
+  /// @returns this CloneContext so calls can be chained
+  template <typename WHAT, typename WITH, typename = std::result_of_t<WITH()>>
+  CloneContext& Replace(const WHAT* what, WITH&& with) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what);
+    replacements_[what] = with;
+    return *this;
+  }
+
+  /// Removes `object` from the cloned copy of `vector`.
+  /// @param vector the vector in #src
+  /// @param object a pointer to the object in #src that will be omitted from
+  /// the cloned vector.
+  /// @returns this CloneContext so calls can be chained
+  template <typename T, typename OBJECT>
+  CloneContext& Remove(const std::vector<T>& vector, OBJECT* object) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
+    if (std::find(vector.begin(), vector.end(), object) == vector.end()) {
+      TINT_ICE(Clone, Diagnostics())
+          << "CloneContext::Remove() vector does not contain object";
+      return *this;
+    }
+
+    list_transforms_[&vector].remove_.emplace(object);
+    return *this;
+  }
+
+  /// Inserts `object` before any other objects of `vector`, when it is cloned.
+  /// @param vector the vector in #src
+  /// @param object a pointer to the object in #dst that will be inserted at the
+  /// front of the vector
+  /// @returns this CloneContext so calls can be chained
+  template <typename T, typename OBJECT>
+  CloneContext& InsertFront(const std::vector<T>& vector, OBJECT* object) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
+    auto& transforms = list_transforms_[&vector];
+    auto& list = transforms.insert_front_;
+    list.emplace_back(object);
+    return *this;
+  }
+
+  /// Inserts `object` after any other objects of `vector`, when it is cloned.
+  /// @param vector the vector in #src
+  /// @param object a pointer to the object in #dst that will be inserted at the
+  /// end of the vector
+  /// @returns this CloneContext so calls can be chained
+  template <typename T, typename OBJECT>
+  CloneContext& InsertBack(const std::vector<T>& vector, OBJECT* object) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
+    auto& transforms = list_transforms_[&vector];
+    auto& list = transforms.insert_back_;
+    list.emplace_back(object);
+    return *this;
+  }
+
+  /// Inserts `object` before `before` whenever `vector` is cloned.
+  /// @param vector the vector in #src
+  /// @param before a pointer to the object in #src
+  /// @param object a pointer to the object in #dst that will be inserted before
+  /// any occurrence of the clone of `before`
+  /// @returns this CloneContext so calls can be chained
+  template <typename T, typename BEFORE, typename OBJECT>
+  CloneContext& InsertBefore(const std::vector<T>& vector,
+                             const BEFORE* before,
+                             const OBJECT* object) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, before);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
+    if (std::find(vector.begin(), vector.end(), before) == vector.end()) {
+      TINT_ICE(Clone, Diagnostics())
+          << "CloneContext::InsertBefore() vector does not contain before";
+      return *this;
+    }
+
+    auto& transforms = list_transforms_[&vector];
+    auto& list = transforms.insert_before_[before];
+    list.emplace_back(object);
+    return *this;
+  }
+
+  /// Inserts `object` after `after` whenever `vector` is cloned.
+  /// @param vector the vector in #src
+  /// @param after a pointer to the object in #src
+  /// @param object a pointer to the object in #dst that will be inserted after
+  /// any occurrence of the clone of `after`
+  /// @returns this CloneContext so calls can be chained
+  template <typename T, typename AFTER, typename OBJECT>
+  CloneContext& InsertAfter(const std::vector<T>& vector,
+                            const AFTER* after,
+                            const OBJECT* object) {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, after);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
+    if (std::find(vector.begin(), vector.end(), after) == vector.end()) {
+      TINT_ICE(Clone, Diagnostics())
+          << "CloneContext::InsertAfter() vector does not contain after";
+      return *this;
+    }
+
+    auto& transforms = list_transforms_[&vector];
+    auto& list = transforms.insert_after_[after];
+    list.emplace_back(object);
+    return *this;
+  }
+
+  /// Clone performs the clone of the Program's AST nodes, types and symbols
+  /// from #src to #dst. Semantic nodes are not cloned, as these will be rebuilt
+  /// when the ProgramBuilder #dst builds its Program.
+  void Clone();
+
+  /// The target ProgramBuilder to clone into.
+  ProgramBuilder* const dst;
+
+  /// The source Program to clone from.
+  Program const* const src;
+
+ private:
+  struct CloneableTransform {
+    /// Constructor
+    CloneableTransform();
+    /// Copy constructor
+    /// @param other the CloneableTransform to copy
+    CloneableTransform(const CloneableTransform& other);
+    /// Destructor
+    ~CloneableTransform();
+
+    // TypeInfo of the Cloneable that the transform operates on
+    const TypeInfo* typeinfo;
+    std::function<const Cloneable*(const Cloneable*)> function;
+  };
+
+  CloneContext(const CloneContext&) = delete;
+  CloneContext& operator=(const CloneContext&) = delete;
+
+  /// Cast `obj` from type `FROM` to type `TO`, returning the cast object.
+  /// Reports an internal compiler error if the cast failed.
+  template <typename TO, typename FROM>
+  const TO* CheckedCast(const FROM* obj) {
+    if (obj == nullptr) {
+      return nullptr;
+    }
+    if (const TO* cast = obj->template As<TO>()) {
+      return cast;
+    }
+    CheckedCastFailure(obj, TypeInfo::Of<TO>());
+    return nullptr;
+  }
+
+  /// Clones a Cloneable object, using any replacements or transforms that have
+  /// been configured.
+  const Cloneable* CloneCloneable(const Cloneable* object);
+
+  /// Adds an error diagnostic to Diagnostics() that the cloned object was not
+  /// of the expected type.
+  void CheckedCastFailure(const Cloneable* got, const TypeInfo& expected);
+
+  /// @returns the diagnostic list of #dst
+  diag::List& Diagnostics() const;
+
+  /// A vector of const Cloneable*
+  using CloneableList = std::vector<const Cloneable*>;
+
+  /// Transformations to be applied to a list (vector)
+  struct ListTransforms {
+    /// Constructor
+    ListTransforms();
+    /// Destructor
+    ~ListTransforms();
+
+    /// A map of object in #src to omit when cloned into #dst.
+    std::unordered_set<const Cloneable*> remove_;
+
+    /// A list of objects in #dst to insert before any others when the vector is
+    /// cloned.
+    CloneableList insert_front_;
+
+    /// A list of objects in #dst to insert befor after any others when the
+    /// vector is cloned.
+    CloneableList insert_back_;
+
+    /// A map of object in #src to the list of cloned objects in #dst.
+    /// Clone(const std::vector<T*>& v) will use this to insert the map-value
+    /// list into the target vector before cloning and inserting the map-key.
+    std::unordered_map<const Cloneable*, CloneableList> insert_before_;
+
+    /// A map of object in #src to the list of cloned objects in #dst.
+    /// Clone(const std::vector<T*>& v) will use this to insert the map-value
+    /// list into the target vector after cloning and inserting the map-key.
+    std::unordered_map<const Cloneable*, CloneableList> insert_after_;
+  };
+
+  /// A map of object in #src to functions that create their replacement in
+  /// #dst
+  std::unordered_map<const Cloneable*, std::function<const Cloneable*()>>
+      replacements_;
+
+  /// A map of symbol in #src to their cloned equivalent in #dst
+  std::unordered_map<Symbol, Symbol> cloned_symbols_;
+
+  /// Cloneable transform functions registered with ReplaceAll()
+  std::vector<CloneableTransform> transforms_;
+
+  /// Map of std::vector pointer to transforms for that list
+  std::unordered_map<const void*, ListTransforms> list_transforms_;
+
+  /// Symbol transform registered with ReplaceAll()
+  SymbolTransform symbol_transform_;
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_CLONE_CONTEXT_H_
diff --git a/src/tint/clone_context_test.cc b/src/tint/clone_context_test.cc
new file mode 100644
index 0000000..5339d64
--- /dev/null
+++ b/src/tint/clone_context_test.cc
@@ -0,0 +1,950 @@
+// Copyright 2020 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 <unordered_set>
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace {
+
+struct Allocator {
+  template <typename T, typename... ARGS>
+  T* Create(ARGS&&... args) {
+    return alloc.Create<T>(this, std::forward<ARGS>(args)...);
+  }
+
+ private:
+  BlockAllocator<Cloneable> alloc;
+};
+
+struct Node : public Castable<Node, Cloneable> {
+  Node(Allocator* alloc,
+       Symbol n,
+       const Node* node_a = nullptr,
+       const Node* node_b = nullptr,
+       const Node* node_c = nullptr)
+      : allocator(alloc), name(n), a(node_a), b(node_b), c(node_c) {}
+  Allocator* const allocator;
+  Symbol name;
+  const Node* a = nullptr;
+  const Node* b = nullptr;
+  const Node* c = nullptr;
+  std::vector<const Node*> vec;
+
+  Node* Clone(CloneContext* ctx) const override {
+    auto* out = allocator->Create<Node>(ctx->Clone(name));
+    out->a = ctx->Clone(a);
+    out->b = ctx->Clone(b);
+    out->c = ctx->Clone(c);
+    out->vec = ctx->Clone(vec);
+    return out;
+  }
+};
+
+struct Replaceable : public Castable<Replaceable, Node> {
+  Replaceable(Allocator* alloc,
+              Symbol n,
+              const Node* node_a = nullptr,
+              const Node* node_b = nullptr,
+              const Node* node_c = nullptr)
+      : Base(alloc, n, node_a, node_b, node_c) {}
+};
+
+struct Replacement : public Castable<Replacement, Replaceable> {
+  Replacement(Allocator* alloc, Symbol n) : Base(alloc, n) {}
+};
+
+struct NotANode : public Castable<NotANode, Cloneable> {
+  explicit NotANode(Allocator* alloc) : allocator(alloc) {}
+
+  Allocator* const allocator;
+  NotANode* Clone(CloneContext*) const override {
+    return allocator->Create<NotANode>();
+  }
+};
+
+struct ProgramNode : public Castable<ProgramNode, Cloneable> {
+  ProgramNode(Allocator* alloc, ProgramID id, ProgramID cloned_id)
+      : allocator(alloc), program_id(id), cloned_program_id(cloned_id) {}
+
+  Allocator* const allocator;
+  const ProgramID program_id;
+  const ProgramID cloned_program_id;
+
+  ProgramNode* Clone(CloneContext*) const override {
+    return allocator->Create<ProgramNode>(cloned_program_id, cloned_program_id);
+  }
+};
+
+ProgramID ProgramIDOf(const ProgramNode* node) {
+  return node->program_id;
+}
+
+using CloneContextNodeTest = ::testing::Test;
+
+TEST_F(CloneContextNodeTest, Clone) {
+  Allocator alloc;
+
+  ProgramBuilder builder;
+  Node* original_root;
+  {
+    auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b"));
+    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
+    auto* b_a = a;  // Aliased
+    auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b"));
+    auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b);
+    auto* c = b;  // Aliased
+    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
+  }
+  Program original(std::move(builder));
+
+  //                          root
+  //        ╭──────────────────┼──────────────────╮
+  //       (a)                (b)                (c)
+  //        N  <──────┐        N  <───────────────┘
+  //   ╭────┼────╮    │   ╭────┼────╮
+  //  (a)  (b)  (c)   │  (a)  (b)  (c)
+  //        N         └───┘    N
+  //
+  // N: Node
+
+  ProgramBuilder cloned;
+  auto* cloned_root = CloneContext(&cloned, &original).Clone(original_root);
+
+  EXPECT_NE(cloned_root->a, nullptr);
+  EXPECT_EQ(cloned_root->a->a, nullptr);
+  EXPECT_NE(cloned_root->a->b, nullptr);
+  EXPECT_EQ(cloned_root->a->c, nullptr);
+  EXPECT_NE(cloned_root->b, nullptr);
+  EXPECT_NE(cloned_root->b->a, nullptr);
+  EXPECT_NE(cloned_root->b->b, nullptr);
+  EXPECT_EQ(cloned_root->b->c, nullptr);
+  EXPECT_NE(cloned_root->c, nullptr);
+
+  EXPECT_NE(cloned_root->a, original_root->a);
+  EXPECT_NE(cloned_root->a->b, original_root->a->b);
+  EXPECT_NE(cloned_root->b, original_root->b);
+  EXPECT_NE(cloned_root->b->a, original_root->b->a);
+  EXPECT_NE(cloned_root->b->b, original_root->b->b);
+  EXPECT_NE(cloned_root->c, original_root->c);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("a->b"));
+  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("b->b"));
+
+  EXPECT_NE(cloned_root->b->a, cloned_root->a);  // De-aliased
+  EXPECT_NE(cloned_root->c, cloned_root->b);     // De-aliased
+
+  EXPECT_EQ(cloned_root->b->a->name, cloned_root->a->name);
+  EXPECT_EQ(cloned_root->c->name, cloned_root->b->name);
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Cloneable) {
+  Allocator alloc;
+
+  ProgramBuilder builder;
+  Node* original_root;
+  {
+    auto* a_b = alloc.Create<Replaceable>(builder.Symbols().New("a->b"));
+    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
+    auto* b_a = a;  // Aliased
+    auto* b =
+        alloc.Create<Replaceable>(builder.Symbols().New("b"), b_a, nullptr);
+    auto* c = b;  // Aliased
+    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
+  }
+  Program original(std::move(builder));
+
+  //                          root
+  //        ╭──────────────────┼──────────────────╮
+  //       (a)                (b)                (c)
+  //        N  <──────┐        R  <───────────────┘
+  //   ╭────┼────╮    │   ╭────┼────╮
+  //  (a)  (b)  (c)   │  (a)  (b)  (c)
+  //        R         └───┘
+  //
+  // N: Node
+  // R: Replaceable
+
+  ProgramBuilder cloned;
+
+  CloneContext ctx(&cloned, &original);
+  ctx.ReplaceAll([&](const Replaceable* in) {
+    auto out_name = cloned.Symbols().Register(
+        "replacement:" + original.Symbols().NameFor(in->name));
+    auto b_name = cloned.Symbols().Register(
+        "replacement-child:" + original.Symbols().NameFor(in->name));
+    auto* out = alloc.Create<Replacement>(out_name);
+    out->b = alloc.Create<Node>(b_name);
+    out->c = ctx.Clone(in->a);
+    return out;
+  });
+  auto* cloned_root = ctx.Clone(original_root);
+
+  //                         root
+  //        ╭─────────────────┼──────────────────╮
+  //       (a)               (b)                (c)
+  //        N  <──────┐       R  <───────────────┘
+  //   ╭────┼────╮    │  ╭────┼────╮
+  //  (a)  (b)  (c)   │ (a)  (b)  (c)
+  //        R         │       N    |
+  //   ╭────┼────╮    └────────────┘
+  //  (a)  (b)  (c)
+  //        N
+  //
+  // N: Node
+  // R: Replacement
+
+  EXPECT_NE(cloned_root->a, nullptr);
+  EXPECT_EQ(cloned_root->a->a, nullptr);
+  EXPECT_NE(cloned_root->a->b, nullptr);     // Replaced
+  EXPECT_EQ(cloned_root->a->b->a, nullptr);  // From replacement
+  EXPECT_NE(cloned_root->a->b->b, nullptr);  // From replacement
+  EXPECT_EQ(cloned_root->a->b->c, nullptr);  // From replacement
+  EXPECT_EQ(cloned_root->a->c, nullptr);
+  EXPECT_NE(cloned_root->b, nullptr);
+  EXPECT_EQ(cloned_root->b->a, nullptr);  // From replacement
+  EXPECT_NE(cloned_root->b->b, nullptr);  // From replacement
+  EXPECT_NE(cloned_root->b->c, nullptr);  // From replacement
+  EXPECT_NE(cloned_root->c, nullptr);
+
+  EXPECT_NE(cloned_root->a, original_root->a);
+  EXPECT_NE(cloned_root->a->b, original_root->a->b);
+  EXPECT_NE(cloned_root->b, original_root->b);
+  EXPECT_NE(cloned_root->b->a, original_root->b->a);
+  EXPECT_NE(cloned_root->c, original_root->c);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("replacement:a->b"));
+  EXPECT_EQ(cloned_root->a->b->b->name,
+            cloned.Symbols().Get("replacement-child:a->b"));
+  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement:b"));
+  EXPECT_EQ(cloned_root->b->b->name,
+            cloned.Symbols().Get("replacement-child:b"));
+
+  EXPECT_NE(cloned_root->b->c, cloned_root->a);  // De-aliased
+  EXPECT_NE(cloned_root->c, cloned_root->b);     // De-aliased
+
+  EXPECT_EQ(cloned_root->b->c->name, cloned_root->a->name);
+  EXPECT_EQ(cloned_root->c->name, cloned_root->b->name);
+
+  EXPECT_FALSE(Is<Replacement>(cloned_root->a));
+  EXPECT_TRUE(Is<Replacement>(cloned_root->a->b));
+  EXPECT_FALSE(Is<Replacement>(cloned_root->a->b->b));
+  EXPECT_TRUE(Is<Replacement>(cloned_root->b));
+  EXPECT_FALSE(Is<Replacement>(cloned_root->b->b));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Symbols) {
+  Allocator alloc;
+
+  ProgramBuilder builder;
+  Node* original_root;
+  {
+    auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b"));
+    auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b);
+    auto* b_a = a;  // Aliased
+    auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b"));
+    auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b);
+    auto* c = b;  // Aliased
+    original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c);
+  }
+  Program original(std::move(builder));
+
+  //                          root
+  //        ╭──────────────────┼──────────────────╮
+  //       (a)                (b)                (c)
+  //        N  <──────┐        N  <───────────────┘
+  //   ╭────┼────╮    │   ╭────┼────╮
+  //  (a)  (b)  (c)   │  (a)  (b)  (c)
+  //        N         └───┘    N
+  //
+  // N: Node
+
+  ProgramBuilder cloned;
+  auto* cloned_root = CloneContext(&cloned, &original, false)
+                          .ReplaceAll([&](Symbol sym) {
+                            auto in = original.Symbols().NameFor(sym);
+                            auto out = "transformed<" + in + ">";
+                            return cloned.Symbols().New(out);
+                          })
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("transformed<root>"));
+  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("transformed<a>"));
+  EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("transformed<a->b>"));
+  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("transformed<b>"));
+  EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("transformed<b->b>"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithoutTransform) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_node = a.Create<Node>(builder.Symbols().New("root"));
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original);
+  ctx.ReplaceAll([&](const Node*) {
+    return a.Create<Replacement>(builder.Symbols().New("<unexpected-node>"));
+  });
+
+  auto* cloned_node = ctx.CloneWithoutTransform(original_node);
+  EXPECT_NE(cloned_node, original_node);
+  EXPECT_EQ(cloned_node->name, cloned.Symbols().Get("root"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplacePointer) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().New("root"));
+  original_root->a = a.Create<Node>(builder.Symbols().New("a"));
+  original_root->b = a.Create<Node>(builder.Symbols().New("b"));
+  original_root->c = a.Create<Node>(builder.Symbols().New("c"));
+  Program original(std::move(builder));
+
+  //                          root
+  //        ╭──────────────────┼──────────────────╮
+  //       (a)                (b)                (c)
+  //                        Replaced
+
+  ProgramBuilder cloned;
+  auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .Replace(original_root->b, replacement)
+                          .Clone(original_root);
+
+  EXPECT_NE(cloned_root->a, replacement);
+  EXPECT_EQ(cloned_root->b, replacement);
+  EXPECT_NE(cloned_root->c, replacement);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement"));
+  EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceFunction) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().New("root"));
+  original_root->a = a.Create<Node>(builder.Symbols().New("a"));
+  original_root->b = a.Create<Node>(builder.Symbols().New("b"));
+  original_root->c = a.Create<Node>(builder.Symbols().New("c"));
+  Program original(std::move(builder));
+
+  //                          root
+  //        ╭──────────────────┼──────────────────╮
+  //       (a)                (b)                (c)
+  //                        Replaced
+
+  ProgramBuilder cloned;
+  auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement"));
+
+  auto* cloned_root =
+      CloneContext(&cloned, &original)
+          .Replace(original_root->b, [=] { return replacement; })
+          .Clone(original_root);
+
+  EXPECT_NE(cloned_root->a, replacement);
+  EXPECT_EQ(cloned_root->b, replacement);
+  EXPECT_NE(cloned_root->c, replacement);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement"));
+  EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithRemove) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .Remove(original_root->vec, original_root->vec[1])
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 2u);
+
+  EXPECT_NE(cloned_root->vec[0], cloned_root->a);
+  EXPECT_NE(cloned_root->vec[1], cloned_root->c);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertFront) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertFront(original_root->vec, insertion)
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_NE(cloned_root->vec[0], cloned_root->a);
+  EXPECT_NE(cloned_root->vec[1], cloned_root->b);
+  EXPECT_NE(cloned_root->vec[2], cloned_root->c);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertFront_Empty) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {};
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertFront(original_root->vec, insertion)
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 1u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertBack) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertBack(original_root->vec, insertion)
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertBack_Empty) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {};
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertBack(original_root->vec, insertion)
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 1u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertFrontAndBack_Empty) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {};
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion_front =
+      a.Create<Node>(cloned.Symbols().New("insertion_front"));
+  auto* insertion_back = a.Create<Node>(cloned.Symbols().New("insertion_back"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertBack(original_root->vec, insertion_back)
+                          .InsertFront(original_root->vec, insertion_front)
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 2u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion_front"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion_back"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertBefore) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root =
+      CloneContext(&cloned, &original)
+          .InsertBefore(original_root->vec, original_root->vec[1], insertion)
+          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertAfter) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+
+  auto* cloned_root =
+      CloneContext(&cloned, &original)
+          .InsertAfter(original_root->vec, original_root->vec[1], insertion)
+          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertAfterInVectorNodeClone) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Replaceable>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original);
+  ctx.ReplaceAll([&](const Replaceable* r) {
+    auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+    ctx.InsertAfter(original_root->vec, r, insertion);
+    return nullptr;
+  });
+
+  auto* cloned_root = ctx.Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertBackInVectorNodeClone) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Replaceable>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original);
+  ctx.ReplaceAll([&](const Replaceable* /*r*/) {
+    auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion"));
+    ctx.InsertBack(original_root->vec, insertion);
+    return nullptr;
+  });
+
+  auto* cloned_root = ctx.Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion"));
+}
+
+TEST_F(CloneContextNodeTest, CloneWithInsertBeforeAndAfterRemoved) {
+  Allocator a;
+
+  ProgramBuilder builder;
+  auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
+  original_root->vec = {
+      a.Create<Node>(builder.Symbols().Register("a")),
+      a.Create<Node>(builder.Symbols().Register("b")),
+      a.Create<Node>(builder.Symbols().Register("c")),
+  };
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  auto* insertion_before =
+      a.Create<Node>(cloned.Symbols().New("insertion_before"));
+  auto* insertion_after =
+      a.Create<Node>(cloned.Symbols().New("insertion_after"));
+
+  auto* cloned_root = CloneContext(&cloned, &original)
+                          .InsertBefore(original_root->vec,
+                                        original_root->vec[1], insertion_before)
+                          .InsertAfter(original_root->vec,
+                                       original_root->vec[1], insertion_after)
+                          .Remove(original_root->vec, original_root->vec[1])
+                          .Clone(original_root);
+
+  EXPECT_EQ(cloned_root->vec.size(), 4u);
+
+  EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
+  EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
+  EXPECT_EQ(cloned_root->vec[1]->name,
+            cloned.Symbols().Get("insertion_before"));
+  EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion_after"));
+  EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c"));
+}
+
+TEST_F(CloneContextNodeTest, CloneIntoSameBuilder) {
+  ProgramBuilder builder;
+  CloneContext ctx(&builder);
+  Allocator allocator;
+  auto* original = allocator.Create<Node>(builder.Symbols().New());
+  auto* cloned_a = ctx.Clone(original);
+  auto* cloned_b = ctx.Clone(original);
+  EXPECT_NE(original, cloned_a);
+  EXPECT_NE(original, cloned_b);
+
+  EXPECT_NE(cloned_a, cloned_b);
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceAll_SameTypeTwice) {
+  std::string node_name = TypeInfo::Of<Node>().name;
+
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder cloned;
+        Program original;
+        CloneContext ctx(&cloned, &original);
+        ctx.ReplaceAll([](const Node*) { return nullptr; });
+        ctx.ReplaceAll([](const Node*) { return nullptr; });
+      },
+      "internal compiler error: ReplaceAll() called with a handler for type " +
+          node_name + " that is already handled by a handler for type " +
+          node_name);
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceAll_BaseThenDerived) {
+  std::string node_name = TypeInfo::Of<Node>().name;
+  std::string replaceable_name = TypeInfo::Of<Replaceable>().name;
+
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder cloned;
+        Program original;
+        CloneContext ctx(&cloned, &original);
+        ctx.ReplaceAll([](const Node*) { return nullptr; });
+        ctx.ReplaceAll([](const Replaceable*) { return nullptr; });
+      },
+      "internal compiler error: ReplaceAll() called with a handler for type " +
+          replaceable_name + " that is already handled by a handler for type " +
+          node_name);
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceAll_DerivedThenBase) {
+  std::string node_name = TypeInfo::Of<Node>().name;
+  std::string replaceable_name = TypeInfo::Of<Replaceable>().name;
+
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder cloned;
+        Program original;
+        CloneContext ctx(&cloned, &original);
+        ctx.ReplaceAll([](const Replaceable*) { return nullptr; });
+        ctx.ReplaceAll([](const Node*) { return nullptr; });
+      },
+      "internal compiler error: ReplaceAll() called with a handler for type " +
+          node_name + " that is already handled by a handler for type " +
+          replaceable_name);
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplacePointer_WithNotANode) {
+  EXPECT_FATAL_FAILURE(
+      {
+        Allocator allocator;
+        ProgramBuilder builder;
+        auto* original_root =
+            allocator.Create<Node>(builder.Symbols().New("root"));
+        original_root->a = allocator.Create<Node>(builder.Symbols().New("a"));
+        original_root->b = allocator.Create<Node>(builder.Symbols().New("b"));
+        original_root->c = allocator.Create<Node>(builder.Symbols().New("c"));
+        Program original(std::move(builder));
+
+        //                          root
+        //        ╭──────────────────┼──────────────────╮
+        //       (a)                (b)                (c)
+        //                        Replaced
+
+        ProgramBuilder cloned;
+        auto* replacement = allocator.Create<NotANode>();
+
+        CloneContext ctx(&cloned, &original);
+        ctx.Replace(original_root->b, replacement);
+
+        ctx.Clone(original_root);
+      },
+      "internal compiler error");
+}
+
+TEST_F(CloneContextNodeTest, CloneWithReplaceFunction_WithNotANode) {
+  EXPECT_FATAL_FAILURE(
+      {
+        Allocator allocator;
+        ProgramBuilder builder;
+        auto* original_root =
+            allocator.Create<Node>(builder.Symbols().New("root"));
+        original_root->a = allocator.Create<Node>(builder.Symbols().New("a"));
+        original_root->b = allocator.Create<Node>(builder.Symbols().New("b"));
+        original_root->c = allocator.Create<Node>(builder.Symbols().New("c"));
+        Program original(std::move(builder));
+
+        //                          root
+        //        ╭──────────────────┼──────────────────╮
+        //       (a)                (b)                (c)
+        //                        Replaced
+
+        ProgramBuilder cloned;
+        auto* replacement = allocator.Create<NotANode>();
+
+        CloneContext ctx(&cloned, &original);
+        ctx.Replace(original_root->b, [=] { return replacement; });
+
+        ctx.Clone(original_root);
+      },
+      "internal compiler error");
+}
+
+using CloneContextTest = ::testing::Test;
+
+TEST_F(CloneContextTest, CloneWithReplaceAll_SymbolsTwice) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder cloned;
+        Program original;
+        CloneContext ctx(&cloned, &original);
+        ctx.ReplaceAll([](const Symbol s) { return s; });
+        ctx.ReplaceAll([](const Symbol s) { return s; });
+      },
+      "internal compiler error: ReplaceAll(const SymbolTransform&) called "
+      "multiple times on the same CloneContext");
+}
+
+TEST_F(CloneContextTest, CloneNewUnnamedSymbols) {
+  ProgramBuilder builder;
+  Symbol old_a = builder.Symbols().New();
+  Symbol old_b = builder.Symbols().New();
+  Symbol old_c = builder.Symbols().New();
+  EXPECT_EQ(builder.Symbols().NameFor(old_a), "tint_symbol");
+  EXPECT_EQ(builder.Symbols().NameFor(old_b), "tint_symbol_1");
+  EXPECT_EQ(builder.Symbols().NameFor(old_c), "tint_symbol_2");
+
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original, false);
+  Symbol new_x = cloned.Symbols().New();
+  Symbol new_a = ctx.Clone(old_a);
+  Symbol new_y = cloned.Symbols().New();
+  Symbol new_b = ctx.Clone(old_b);
+  Symbol new_z = cloned.Symbols().New();
+  Symbol new_c = ctx.Clone(old_c);
+
+  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "tint_symbol");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "tint_symbol_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "tint_symbol_2");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "tint_symbol_1_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "tint_symbol_3");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "tint_symbol_2_1");
+}
+
+TEST_F(CloneContextTest, CloneNewSymbols) {
+  ProgramBuilder builder;
+  Symbol old_a = builder.Symbols().New("a");
+  Symbol old_b = builder.Symbols().New("b");
+  Symbol old_c = builder.Symbols().New("c");
+  EXPECT_EQ(builder.Symbols().NameFor(old_a), "a");
+  EXPECT_EQ(builder.Symbols().NameFor(old_b), "b");
+  EXPECT_EQ(builder.Symbols().NameFor(old_c), "c");
+
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original, false);
+  Symbol new_x = cloned.Symbols().New("a");
+  Symbol new_a = ctx.Clone(old_a);
+  Symbol new_y = cloned.Symbols().New("b");
+  Symbol new_b = ctx.Clone(old_b);
+  Symbol new_z = cloned.Symbols().New("c");
+  Symbol new_c = ctx.Clone(old_c);
+
+  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c_1");
+}
+
+TEST_F(CloneContextTest, CloneNewSymbols_AfterCloneSymbols) {
+  ProgramBuilder builder;
+  Symbol old_a = builder.Symbols().New("a");
+  Symbol old_b = builder.Symbols().New("b");
+  Symbol old_c = builder.Symbols().New("c");
+  EXPECT_EQ(builder.Symbols().NameFor(old_a), "a");
+  EXPECT_EQ(builder.Symbols().NameFor(old_b), "b");
+  EXPECT_EQ(builder.Symbols().NameFor(old_c), "c");
+
+  Program original(std::move(builder));
+
+  ProgramBuilder cloned;
+  CloneContext ctx(&cloned, &original);
+  Symbol new_x = cloned.Symbols().New("a");
+  Symbol new_a = ctx.Clone(old_a);
+  Symbol new_y = cloned.Symbols().New("b");
+  Symbol new_b = ctx.Clone(old_b);
+  Symbol new_z = cloned.Symbols().New("c");
+  Symbol new_c = ctx.Clone(old_c);
+
+  EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c_1");
+  EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c");
+}
+
+TEST_F(CloneContextTest, ProgramIDs) {
+  ProgramBuilder dst;
+  Program src(ProgramBuilder{});
+  CloneContext ctx(&dst, &src);
+  Allocator allocator;
+  auto* cloned = ctx.Clone(allocator.Create<ProgramNode>(src.ID(), dst.ID()));
+  EXPECT_EQ(cloned->program_id, dst.ID());
+}
+
+TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedBySrc) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder dst;
+        Program src(ProgramBuilder{});
+        CloneContext ctx(&dst, &src);
+        Allocator allocator;
+        ctx.Clone(allocator.Create<ProgramNode>(ProgramID::New(), dst.ID()));
+      },
+      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object))");
+}
+
+TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedByDst) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder dst;
+        Program src(ProgramBuilder{});
+        CloneContext ctx(&dst, &src);
+        Allocator allocator;
+        ctx.Clone(allocator.Create<ProgramNode>(src.ID(), ProgramID::New()));
+      },
+      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out))");
+}
+
+}  // namespace
+
+TINT_INSTANTIATE_TYPEINFO(Node);
+TINT_INSTANTIATE_TYPEINFO(Replaceable);
+TINT_INSTANTIATE_TYPEINFO(Replacement);
+TINT_INSTANTIATE_TYPEINFO(NotANode);
+TINT_INSTANTIATE_TYPEINFO(ProgramNode);
+
+}  // namespace tint
diff --git a/src/tint/cmd/BUILD.gn b/src/tint/cmd/BUILD.gn
new file mode 100644
index 0000000..c9c2f20
--- /dev/null
+++ b/src/tint/cmd/BUILD.gn
@@ -0,0 +1,44 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("../../../tint_overrides_with_defaults.gni")
+
+executable("tint") {
+  sources = [ "main.cc" ]
+  deps = [
+    "${tint_root_dir}/src/tint:libtint",
+    "${tint_root_dir}/src/tint:tint_val",
+    "${tint_spirv_tools_dir}/:spvtools",
+    "${tint_spirv_tools_dir}/:spvtools_opt",
+    "${tint_spirv_tools_dir}/:spvtools_val",
+  ]
+
+  if (tint_build_glsl_writer) {
+    deps += [
+      "${tint_root_dir}/third_party/glslang:glslang_default_resource_limits_sources",
+      "${tint_root_dir}/third_party/glslang:glslang_lib_sources",
+    ]
+  }
+
+  configs += [
+    "${tint_root_dir}/src/tint:tint_common_config",
+    "${tint_root_dir}/src/tint:tint_config",
+  ]
+
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+}
diff --git a/samples/CMakeLists.txt b/src/tint/cmd/CMakeLists.txt
similarity index 100%
rename from samples/CMakeLists.txt
rename to src/tint/cmd/CMakeLists.txt
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
new file mode 100644
index 0000000..ff3587c
--- /dev/null
+++ b/src/tint/cmd/main.cc
@@ -0,0 +1,1216 @@
+// Copyright 2020 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 <cstdio>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#if TINT_BUILD_GLSL_WRITER
+#include "StandAlone/ResourceLimits.h"
+#include "glslang/Public/ShaderLang.h"
+#endif
+
+#if TINT_BUILD_SPV_READER
+#include "spirv-tools/libspirv.hpp"
+#endif  // TINT_BUILD_SPV_READER
+
+#include "src/tint/utils/io/command.h"
+#include "src/tint/utils/string.h"
+#include "src/tint/val/val.h"
+#include "tint/tint.h"
+
+namespace {
+
+[[noreturn]] void TintInternalCompilerErrorReporter(
+    const tint::diag::List& diagnostics) {
+  auto printer = tint::diag::Printer::create(stderr, true);
+  tint::diag::Formatter{}.format(diagnostics, printer.get());
+  tint::diag::Style bold_red{tint::diag::Color::kRed, true};
+  constexpr const char* please_file_bug = R"(
+********************************************************************
+*  The tint shader compiler has encountered an unexpected error.   *
+*                                                                  *
+*  Please help us fix this issue by submitting a bug report at     *
+*  crbug.com/tint with the source program that triggered the bug.  *
+********************************************************************
+)";
+  printer->write(please_file_bug, bold_red);
+  exit(1);
+}
+
+enum class Format {
+  kNone = -1,
+  kSpirv,
+  kSpvAsm,
+  kWgsl,
+  kMsl,
+  kHlsl,
+  kGlsl,
+};
+
+struct Options {
+  bool show_help = false;
+
+  std::string input_filename;
+  std::string output_file = "-";  // Default to stdout
+
+  bool parse_only = false;
+  bool disable_workgroup_init = false;
+  bool validate = false;
+  bool demangle = false;
+  bool dump_inspector_bindings = false;
+
+  Format format = Format::kNone;
+
+  bool emit_single_entry_point = false;
+  std::string ep_name;
+
+  std::vector<std::string> transforms;
+
+  bool use_fxc = false;
+  std::string dxc_path;
+  std::string xcrun_path;
+};
+
+const char kUsage[] = R"(Usage: tint [options] <input-file>
+
+ options:
+  --format <spirv|spvasm|wgsl|msl|hlsl>  -- Output format.
+                               If not provided, will be inferred from output
+                               filename extension:
+                                   .spvasm -> spvasm
+                                   .spv    -> spirv
+                                   .wgsl   -> wgsl
+                                   .metal  -> msl
+                                   .hlsl   -> hlsl
+                               If none matches, then default to SPIR-V assembly.
+  -ep <name>                -- Output single entry point
+  --output-file <name>      -- Output file name.  Use "-" for standard output
+  -o <name>                 -- Output file name.  Use "-" for standard output
+  --transform <name list>   -- Runs transforms, name list is comma separated
+                               Available transforms:
+${transforms}
+  --parse-only              -- Stop after parsing the input
+  --disable-workgroup-init  -- Disable workgroup memory zero initialization.
+  --demangle                -- Preserve original source names. Demangle them.
+                               Affects AST dumping, and text-based output languages.
+  --dump-inspector-bindings -- Dump reflection data about bindins to stdout.
+  -h                        -- This help text
+  --validate                -- Validates the generated shader
+  --fxc                     -- Ask to validate HLSL output using FXC instead of DXC.
+                               When specified, automatically enables --validate
+  --dxc                     -- Path to DXC executable, used to validate HLSL output.
+                               When specified, automatically enables --validate
+  --xcrun                   -- Path to xcrun executable, used to validate MSL output.
+                               When specified, automatically enables --validate)";
+
+Format parse_format(const std::string& fmt) {
+  (void)fmt;
+
+#if TINT_BUILD_SPV_WRITER
+  if (fmt == "spirv")
+    return Format::kSpirv;
+  if (fmt == "spvasm")
+    return Format::kSpvAsm;
+#endif  // TINT_BUILD_SPV_WRITER
+
+#if TINT_BUILD_WGSL_WRITER
+  if (fmt == "wgsl")
+    return Format::kWgsl;
+#endif  // TINT_BUILD_WGSL_WRITER
+
+#if TINT_BUILD_MSL_WRITER
+  if (fmt == "msl")
+    return Format::kMsl;
+#endif  // TINT_BUILD_MSL_WRITER
+
+#if TINT_BUILD_HLSL_WRITER
+  if (fmt == "hlsl")
+    return Format::kHlsl;
+#endif  // TINT_BUILD_HLSL_WRITER
+
+#if TINT_BUILD_GLSL_WRITER
+  if (fmt == "glsl")
+    return Format::kGlsl;
+#endif  // TINT_BUILD_GLSL_WRITER
+
+  return Format::kNone;
+}
+
+#if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || \
+    TINT_BUILD_MSL_WRITER || TINT_BUILD_HLSL_WRITER
+/// @param input input string
+/// @param suffix potential suffix string
+/// @returns true if input ends with the given suffix.
+bool ends_with(const std::string& input, const std::string& suffix) {
+  const auto input_len = input.size();
+  const auto suffix_len = suffix.size();
+  // Avoid integer overflow.
+  return (input_len >= suffix_len) &&
+         (input_len - suffix_len == input.rfind(suffix));
+}
+#endif
+
+/// @param filename the filename to inspect
+/// @returns the inferred format for the filename suffix
+Format infer_format(const std::string& filename) {
+  (void)filename;
+
+#if TINT_BUILD_SPV_WRITER
+  if (ends_with(filename, ".spv")) {
+    return Format::kSpirv;
+  }
+  if (ends_with(filename, ".spvasm")) {
+    return Format::kSpvAsm;
+  }
+#endif  // TINT_BUILD_SPV_WRITER
+
+#if TINT_BUILD_WGSL_WRITER
+  if (ends_with(filename, ".wgsl")) {
+    return Format::kWgsl;
+  }
+#endif  // TINT_BUILD_WGSL_WRITER
+
+#if TINT_BUILD_MSL_WRITER
+  if (ends_with(filename, ".metal")) {
+    return Format::kMsl;
+  }
+#endif  // TINT_BUILD_MSL_WRITER
+
+#if TINT_BUILD_HLSL_WRITER
+  if (ends_with(filename, ".hlsl")) {
+    return Format::kHlsl;
+  }
+#endif  // TINT_BUILD_HLSL_WRITER
+
+  return Format::kNone;
+}
+
+std::vector<std::string> split_transform_names(std::string list) {
+  std::vector<std::string> res;
+
+  std::stringstream str(list);
+  while (str.good()) {
+    std::string substr;
+    getline(str, substr, ',');
+    res.push_back(substr);
+  }
+  return res;
+}
+
+std::string TextureDimensionToString(
+    tint::inspector::ResourceBinding::TextureDimension dim) {
+  switch (dim) {
+    case tint::inspector::ResourceBinding::TextureDimension::kNone:
+      return "None";
+    case tint::inspector::ResourceBinding::TextureDimension::k1d:
+      return "1d";
+    case tint::inspector::ResourceBinding::TextureDimension::k2d:
+      return "2d";
+    case tint::inspector::ResourceBinding::TextureDimension::k2dArray:
+      return "2dArray";
+    case tint::inspector::ResourceBinding::TextureDimension::k3d:
+      return "3d";
+    case tint::inspector::ResourceBinding::TextureDimension::kCube:
+      return "Cube";
+    case tint::inspector::ResourceBinding::TextureDimension::kCubeArray:
+      return "CubeArray";
+  }
+
+  return "Unknown";
+}
+
+std::string SampledKindToString(
+    tint::inspector::ResourceBinding::SampledKind kind) {
+  switch (kind) {
+    case tint::inspector::ResourceBinding::SampledKind::kFloat:
+      return "Float";
+    case tint::inspector::ResourceBinding::SampledKind::kUInt:
+      return "UInt";
+    case tint::inspector::ResourceBinding::SampledKind::kSInt:
+      return "SInt";
+    case tint::inspector::ResourceBinding::SampledKind::kUnknown:
+      break;
+  }
+
+  return "Unknown";
+}
+
+std::string TexelFormatToString(
+    tint::inspector::ResourceBinding::TexelFormat format) {
+  switch (format) {
+    case tint::inspector::ResourceBinding::TexelFormat::kR32Uint:
+      return "R32Uint";
+    case tint::inspector::ResourceBinding::TexelFormat::kR32Sint:
+      return "R32Sint";
+    case tint::inspector::ResourceBinding::TexelFormat::kR32Float:
+      return "R32Float";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm:
+      return "Rgba8Unorm";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm:
+      return "Rgba8Snorm";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint:
+      return "Rgba8Uint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint:
+      return "Rgba8Sint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint:
+      return "Rg32Uint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint:
+      return "Rg32Sint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRg32Float:
+      return "Rg32Float";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint:
+      return "Rgba16Uint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint:
+      return "Rgba16Sint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float:
+      return "Rgba16Float";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint:
+      return "Rgba32Uint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint:
+      return "Rgba32Sint";
+    case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float:
+      return "Rgba32Float";
+    case tint::inspector::ResourceBinding::TexelFormat::kNone:
+      return "None";
+  }
+  return "Unknown";
+}
+
+std::string ResourceTypeToString(
+    tint::inspector::ResourceBinding::ResourceType type) {
+  switch (type) {
+    case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
+      return "UniformBuffer";
+    case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
+      return "StorageBuffer";
+    case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer:
+      return "ReadOnlyStorageBuffer";
+    case tint::inspector::ResourceBinding::ResourceType::kSampler:
+      return "Sampler";
+    case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
+      return "ComparisonSampler";
+    case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
+      return "SampledTexture";
+    case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture:
+      return "MultisampledTexture";
+    case tint::inspector::ResourceBinding::ResourceType::
+        kWriteOnlyStorageTexture:
+      return "WriteOnlyStorageTexture";
+    case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
+      return "DepthTexture";
+    case tint::inspector::ResourceBinding::ResourceType::
+        kDepthMultisampledTexture:
+      return "DepthMultisampledTexture";
+    case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
+      return "ExternalTexture";
+  }
+
+  return "Unknown";
+}
+
+bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
+  for (size_t i = 1; i < args.size(); ++i) {
+    const std::string& arg = args[i];
+    if (arg == "--format") {
+      ++i;
+      if (i >= args.size()) {
+        std::cerr << "Missing value for --format argument." << std::endl;
+        return false;
+      }
+      opts->format = parse_format(args[i]);
+
+      if (opts->format == Format::kNone) {
+        std::cerr << "Unknown output format: " << args[i] << std::endl;
+        return false;
+      }
+    } else if (arg == "-ep") {
+      if (i + 1 >= args.size()) {
+        std::cerr << "Missing value for -ep" << std::endl;
+        return false;
+      }
+      i++;
+      opts->ep_name = args[i];
+      opts->emit_single_entry_point = true;
+
+    } else if (arg == "-o" || arg == "--output-name") {
+      ++i;
+      if (i >= args.size()) {
+        std::cerr << "Missing value for " << arg << std::endl;
+        return false;
+      }
+      opts->output_file = args[i];
+
+    } else if (arg == "-h" || arg == "--help") {
+      opts->show_help = true;
+    } else if (arg == "--transform") {
+      ++i;
+      if (i >= args.size()) {
+        std::cerr << "Missing value for " << arg << std::endl;
+        return false;
+      }
+      opts->transforms = split_transform_names(args[i]);
+    } else if (arg == "--parse-only") {
+      opts->parse_only = true;
+    } else if (arg == "--disable-workgroup-init") {
+      opts->disable_workgroup_init = true;
+    } else if (arg == "--demangle") {
+      opts->demangle = true;
+    } else if (arg == "--dump-inspector-bindings") {
+      opts->dump_inspector_bindings = true;
+    } else if (arg == "--validate") {
+      opts->validate = true;
+    } else if (arg == "--fxc") {
+      opts->validate = true;
+      opts->use_fxc = true;
+    } else if (arg == "--dxc") {
+      ++i;
+      if (i >= args.size()) {
+        std::cerr << "Missing value for " << arg << std::endl;
+        return false;
+      }
+      opts->dxc_path = args[i];
+      opts->validate = true;
+    } else if (arg == "--xcrun") {
+      ++i;
+      if (i >= args.size()) {
+        std::cerr << "Missing value for " << arg << std::endl;
+        return false;
+      }
+      opts->xcrun_path = args[i];
+      opts->validate = true;
+    } else if (!arg.empty()) {
+      if (arg[0] == '-') {
+        std::cerr << "Unrecognized option: " << arg << std::endl;
+        return false;
+      }
+      if (!opts->input_filename.empty()) {
+        std::cerr << "More than one input file specified: '"
+                  << opts->input_filename << "' and '" << arg << "'"
+                  << std::endl;
+        return false;
+      }
+      opts->input_filename = arg;
+    }
+  }
+  return true;
+}
+
+/// Copies the content from the file named `input_file` to `buffer`,
+/// assuming each element in the file is of type `T`.  If any error occurs,
+/// writes error messages to the standard error stream and returns false.
+/// Assumes the size of a `T` object is divisible by its required alignment.
+/// @returns true if we successfully read the file.
+template <typename T>
+bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
+  if (!buffer) {
+    std::cerr << "The buffer pointer was null" << std::endl;
+    return false;
+  }
+
+  FILE* file = nullptr;
+#if defined(_MSC_VER)
+  fopen_s(&file, input_file.c_str(), "rb");
+#else
+  file = fopen(input_file.c_str(), "rb");
+#endif
+  if (!file) {
+    std::cerr << "Failed to open " << input_file << std::endl;
+    return false;
+  }
+
+  fseek(file, 0, SEEK_END);
+  const auto file_size = static_cast<size_t>(ftell(file));
+  if (0 != (file_size % sizeof(T))) {
+    std::cerr << "File " << input_file
+              << " does not contain an integral number of objects: "
+              << file_size << " bytes in the file, require " << sizeof(T)
+              << " bytes per object" << std::endl;
+    fclose(file);
+    return false;
+  }
+  fseek(file, 0, SEEK_SET);
+
+  buffer->clear();
+  buffer->resize(file_size / sizeof(T));
+
+  size_t bytes_read = fread(buffer->data(), 1, file_size, file);
+  fclose(file);
+  if (bytes_read != file_size) {
+    std::cerr << "Failed to read " << input_file << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+/// Writes the given `buffer` into the file named as `output_file` using the
+/// given `mode`.  If `output_file` is empty or "-", writes to standard
+/// output. If any error occurs, returns false and outputs error message to
+/// standard error. The ContainerT type must have data() and size() methods,
+/// like `std::string` and `std::vector` do.
+/// @returns true on success
+template <typename ContainerT>
+bool WriteFile(const std::string& output_file,
+               const std::string mode,
+               const ContainerT& buffer) {
+  const bool use_stdout = output_file.empty() || output_file == "-";
+  FILE* file = stdout;
+
+  if (!use_stdout) {
+#if defined(_MSC_VER)
+    fopen_s(&file, output_file.c_str(), mode.c_str());
+#else
+    file = fopen(output_file.c_str(), mode.c_str());
+#endif
+    if (!file) {
+      std::cerr << "Could not open file " << output_file << " for writing"
+                << std::endl;
+      return false;
+    }
+  }
+
+  size_t written =
+      fwrite(buffer.data(), sizeof(typename ContainerT::value_type),
+             buffer.size(), file);
+  if (buffer.size() != written) {
+    if (use_stdout) {
+      std::cerr << "Could not write all output to standard output" << std::endl;
+    } else {
+      std::cerr << "Could not write to file " << output_file << std::endl;
+      fclose(file);
+    }
+    return false;
+  }
+  if (!use_stdout) {
+    fclose(file);
+  }
+
+  return true;
+}
+
+#if TINT_BUILD_SPV_WRITER
+std::string Disassemble(const std::vector<uint32_t>& data) {
+  std::string spv_errors;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
+
+  auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
+                                    const spv_position_t& position,
+                                    const char* message) {
+    switch (level) {
+      case SPV_MSG_FATAL:
+      case SPV_MSG_INTERNAL_ERROR:
+      case SPV_MSG_ERROR:
+        spv_errors += "error: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_WARNING:
+        spv_errors += "warning: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_INFO:
+        spv_errors += "info: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_DEBUG:
+        break;
+    }
+  };
+
+  spvtools::SpirvTools tools(target_env);
+  tools.SetMessageConsumer(msg_consumer);
+
+  std::string result;
+  if (!tools.Disassemble(data, &result,
+                         SPV_BINARY_TO_TEXT_OPTION_INDENT |
+                             SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) {
+    std::cerr << spv_errors << std::endl;
+  }
+  return result;
+}
+#endif  // TINT_BUILD_SPV_WRITER
+
+/// PrintWGSL writes the WGSL of the program to the provided ostream, if the
+/// WGSL writer is enabled, otherwise it does nothing.
+/// @param out the output stream to write the WGSL to
+/// @param program the program
+void PrintWGSL(std::ostream& out, const tint::Program& program) {
+#if TINT_BUILD_WGSL_WRITER
+  tint::writer::wgsl::Options options;
+  auto result = tint::writer::wgsl::Generate(&program, options);
+  out << std::endl << result.wgsl << std::endl;
+#else
+  (void)out;
+  (void)program;
+#endif
+}
+
+/// Generate SPIR-V code for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateSpirv(const tint::Program* program, const Options& options) {
+#if TINT_BUILD_SPV_WRITER
+  // TODO(jrprice): Provide a way for the user to set non-default options.
+  tint::writer::spirv::Options gen_options;
+  gen_options.disable_workgroup_init = options.disable_workgroup_init;
+  auto result = tint::writer::spirv::Generate(program, gen_options);
+  if (!result.success) {
+    PrintWGSL(std::cerr, *program);
+    std::cerr << "Failed to generate: " << result.error << std::endl;
+    return false;
+  }
+
+  if (options.format == Format::kSpvAsm) {
+    if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) {
+      return false;
+    }
+  } else {
+    if (!WriteFile(options.output_file, "wb", result.spirv)) {
+      return false;
+    }
+  }
+
+  if (options.validate) {
+    // Use Vulkan 1.1, since this is what Tint, internally, uses.
+    spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
+    tools.SetMessageConsumer([](spv_message_level_t, const char*,
+                                const spv_position_t& pos, const char* msg) {
+      std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
+                << std::endl;
+    });
+    if (!tools.Validate(result.spirv.data(), result.spirv.size(),
+                        spvtools::ValidatorOptions())) {
+      return false;
+    }
+  }
+
+  return true;
+#else
+  (void)program;
+  (void)options;
+  std::cerr << "SPIR-V writer not enabled in tint build" << std::endl;
+  return false;
+#endif  // TINT_BUILD_SPV_WRITER
+}
+
+/// Generate WGSL code for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateWgsl(const tint::Program* program, const Options& options) {
+#if TINT_BUILD_WGSL_WRITER
+  // TODO(jrprice): Provide a way for the user to set non-default options.
+  tint::writer::wgsl::Options gen_options;
+  auto result = tint::writer::wgsl::Generate(program, gen_options);
+  if (!result.success) {
+    std::cerr << "Failed to generate: " << result.error << std::endl;
+    return false;
+  }
+
+  if (!WriteFile(options.output_file, "w", result.wgsl)) {
+    return false;
+  }
+
+  if (options.validate) {
+    // Attempt to re-parse the output program with Tint's WGSL reader.
+    auto source = std::make_unique<tint::Source::File>(options.input_filename,
+                                                       result.wgsl);
+    auto reparsed_program = tint::reader::wgsl::Parse(source.get());
+    if (!reparsed_program.IsValid()) {
+      auto diag_printer = tint::diag::Printer::create(stderr, true);
+      tint::diag::Formatter diag_formatter;
+      diag_formatter.format(reparsed_program.Diagnostics(), diag_printer.get());
+      return false;
+    }
+  }
+
+  return true;
+#else
+  (void)program;
+  (void)options;
+  std::cerr << "WGSL writer not enabled in tint build" << std::endl;
+  return false;
+#endif  // TINT_BUILD_WGSL_WRITER
+}
+
+/// Generate MSL code for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateMsl(const tint::Program* program, const Options& options) {
+#if TINT_BUILD_MSL_WRITER
+  const tint::Program* input_program = program;
+
+  // Remap resource numbers to a flat namespace.
+  // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points.
+  using BindingPoint = tint::transform::BindingPoint;
+  tint::transform::BindingRemapper::BindingPoints binding_points;
+  uint32_t next_buffer_idx = 0;
+  uint32_t next_sampler_idx = 0;
+  uint32_t next_texture_idx = 0;
+
+  tint::inspector::Inspector inspector(program);
+  auto entry_points = inspector.GetEntryPoints();
+  for (auto& entry_point : entry_points) {
+    auto bindings = inspector.GetResourceBindings(entry_point.name);
+    for (auto& binding : bindings) {
+      BindingPoint src = {binding.bind_group, binding.binding};
+      if (binding_points.count(src)) {
+        continue;
+      }
+      switch (binding.resource_type) {
+        case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
+        case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
+        case tint::inspector::ResourceBinding::ResourceType::
+            kReadOnlyStorageBuffer:
+          binding_points.emplace(src, BindingPoint{0, next_buffer_idx++});
+          break;
+        case tint::inspector::ResourceBinding::ResourceType::kSampler:
+        case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
+          binding_points.emplace(src, BindingPoint{0, next_sampler_idx++});
+          break;
+        case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
+        case tint::inspector::ResourceBinding::ResourceType::
+            kMultisampledTexture:
+        case tint::inspector::ResourceBinding::ResourceType::
+            kWriteOnlyStorageTexture:
+        case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
+        case tint::inspector::ResourceBinding::ResourceType::
+            kDepthMultisampledTexture:
+        case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
+          binding_points.emplace(src, BindingPoint{0, next_texture_idx++});
+          break;
+      }
+    }
+  }
+
+  // Run the binding remapper transform.
+  tint::transform::Output transform_output;
+  if (!binding_points.empty()) {
+    tint::transform::Manager manager;
+    tint::transform::DataMap inputs;
+    inputs.Add<tint::transform::BindingRemapper::Remappings>(
+        std::move(binding_points),
+        tint::transform::BindingRemapper::AccessControls{},
+        /* mayCollide */ true);
+    manager.Add<tint::transform::BindingRemapper>();
+    transform_output = manager.Run(program, inputs);
+    input_program = &transform_output.program;
+  }
+
+  // TODO(jrprice): Provide a way for the user to set non-default options.
+  tint::writer::msl::Options gen_options;
+  gen_options.disable_workgroup_init = options.disable_workgroup_init;
+  auto result = tint::writer::msl::Generate(input_program, gen_options);
+  if (!result.success) {
+    PrintWGSL(std::cerr, *program);
+    std::cerr << "Failed to generate: " << result.error << std::endl;
+    return false;
+  }
+
+  if (!WriteFile(options.output_file, "w", result.msl)) {
+    return false;
+  }
+
+  if (options.validate) {
+    tint::val::Result res;
+#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
+    res = tint::val::MslUsingMetalAPI(result.msl);
+#else
+#ifdef _WIN32
+    const char* default_xcrun_exe = "metal.exe";
+#else
+    const char* default_xcrun_exe = "xcrun";
+#endif
+    auto xcrun = tint::utils::Command::LookPath(
+        options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path);
+    if (xcrun.Found()) {
+      res = tint::val::Msl(xcrun.Path(), result.msl);
+    } else {
+      res.output = "xcrun executable not found. Cannot validate.";
+      res.failed = true;
+    }
+#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
+    if (res.failed) {
+      std::cerr << res.output << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+#else
+  (void)program;
+  (void)options;
+  std::cerr << "MSL writer not enabled in tint build" << std::endl;
+  return false;
+#endif  // TINT_BUILD_MSL_WRITER
+}
+
+/// Generate HLSL code for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateHlsl(const tint::Program* program, const Options& options) {
+#if TINT_BUILD_HLSL_WRITER
+  // TODO(jrprice): Provide a way for the user to set non-default options.
+  tint::writer::hlsl::Options gen_options;
+  gen_options.disable_workgroup_init = options.disable_workgroup_init;
+  auto result = tint::writer::hlsl::Generate(program, gen_options);
+  if (!result.success) {
+    PrintWGSL(std::cerr, *program);
+    std::cerr << "Failed to generate: " << result.error << std::endl;
+    return false;
+  }
+
+  if (!WriteFile(options.output_file, "w", result.hlsl)) {
+    return false;
+  }
+
+  if (options.validate) {
+    tint::val::Result res;
+    if (options.use_fxc) {
+#ifdef _WIN32
+      res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points);
+#else
+      res.failed = true;
+      res.output = "FXC can only be used on Windows. Sorry :X";
+#endif  // _WIN32
+    } else {
+      auto dxc = tint::utils::Command::LookPath(
+          options.dxc_path.empty() ? "dxc" : options.dxc_path);
+      if (dxc.Found()) {
+        res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl,
+                                      result.entry_points);
+      } else {
+        res.failed = true;
+        res.output = "DXC executable not found. Cannot validate";
+      }
+    }
+    if (res.failed) {
+      std::cerr << res.output << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+#else
+  (void)program;
+  (void)options;
+  std::cerr << "HLSL writer not enabled in tint build" << std::endl;
+  return false;
+#endif  // TINT_BUILD_HLSL_WRITER
+}
+
+#if TINT_BUILD_GLSL_WRITER
+EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) {
+  switch (stage) {
+    case tint::ast::PipelineStage::kFragment:
+      return EShLangFragment;
+    case tint::ast::PipelineStage::kVertex:
+      return EShLangVertex;
+    case tint::ast::PipelineStage::kCompute:
+      return EShLangCompute;
+    default:
+      TINT_ASSERT(AST, false);
+      return EShLangVertex;
+  }
+}
+#endif
+
+/// Generate GLSL code for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateGlsl(const tint::Program* program, const Options& options) {
+#if TINT_BUILD_GLSL_WRITER
+  if (options.validate) {
+    glslang::InitializeProcess();
+  }
+
+  auto generate = [&](const tint::Program* prg,
+                      const std::string entry_point_name) -> bool {
+    tint::writer::glsl::Options gen_options;
+    auto result =
+        tint::writer::glsl::Generate(prg, gen_options, entry_point_name);
+    if (!result.success) {
+      PrintWGSL(std::cerr, *prg);
+      std::cerr << "Failed to generate: " << result.error << std::endl;
+      return false;
+    }
+
+    if (!WriteFile(options.output_file, "w", result.glsl)) {
+      return false;
+    }
+
+    if (options.validate) {
+      for (auto entry_pt : result.entry_points) {
+        EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second);
+        glslang::TShader shader(lang);
+        const char* strings[1] = {result.glsl.c_str()};
+        int lengths[1] = {static_cast<int>(result.glsl.length())};
+        shader.setStringsWithLengths(strings, lengths, 1);
+        shader.setEntryPoint("main");
+        bool glslang_result =
+            shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile,
+                         false, false, EShMsgDefault);
+        if (!glslang_result) {
+          std::cerr << "Error parsing GLSL shader:\n"
+                    << shader.getInfoLog() << "\n"
+                    << shader.getInfoDebugLog() << "\n";
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+  tint::inspector::Inspector inspector(program);
+
+  if (inspector.GetEntryPoints().empty()) {
+    // Pass empty string here so that the GLSL generator will generate
+    // code for all functions, reachable or not.
+    return generate(program, "");
+  }
+
+  bool success = true;
+  for (auto& entry_point : inspector.GetEntryPoints()) {
+    success &= generate(program, entry_point.name);
+  }
+  return success;
+#else
+  (void)program;
+  (void)options;
+  std::cerr << "GLSL writer not enabled in tint build" << std::endl;
+  return false;
+#endif  // TINT_BUILD_GLSL_WRITER
+}
+
+}  // namespace
+
+int main(int argc, const char** argv) {
+  std::vector<std::string> args(argv, argv + argc);
+  Options options;
+
+  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+
+#if TINT_BUILD_WGSL_WRITER
+  tint::Program::printer = [](const tint::Program* program) {
+    auto result = tint::writer::wgsl::Generate(program, {});
+    if (!result.error.empty()) {
+      return "error: " + result.error;
+    }
+    return result.wgsl;
+  };
+#endif  // TINT_BUILD_WGSL_WRITER
+
+  if (!ParseArgs(args, &options)) {
+    std::cerr << "Failed to parse arguments." << std::endl;
+    return 1;
+  }
+
+  struct TransformFactory {
+    const char* name;
+    std::function<void(tint::transform::Manager& manager,
+                       tint::transform::DataMap& inputs)>
+        make;
+  };
+  std::vector<TransformFactory> transforms = {
+      {"first_index_offset",
+       [](tint::transform::Manager& m, tint::transform::DataMap& i) {
+         i.Add<tint::transform::FirstIndexOffset::BindingPoint>(0, 0);
+         m.Add<tint::transform::FirstIndexOffset>();
+       }},
+      {"fold_trivial_single_use_lets",
+       [](tint::transform::Manager& m, tint::transform::DataMap&) {
+         m.Add<tint::transform::FoldTrivialSingleUseLets>();
+       }},
+      {"renamer",
+       [](tint::transform::Manager& m, tint::transform::DataMap&) {
+         m.Add<tint::transform::Renamer>();
+       }},
+      {"robustness",
+       [](tint::transform::Manager& m, tint::transform::DataMap&) {
+         m.Add<tint::transform::Robustness>();
+       }},
+  };
+  auto transform_names = [&] {
+    std::stringstream names;
+    for (auto& t : transforms) {
+      names << "   " << t.name << std::endl;
+    }
+    return names.str();
+  };
+
+  if (options.show_help) {
+    std::string usage =
+        tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names());
+    std::cout << usage << std::endl;
+    return 0;
+  }
+
+  // Implement output format defaults.
+  if (options.format == Format::kNone) {
+    // Try inferring from filename.
+    options.format = infer_format(options.output_file);
+  }
+  if (options.format == Format::kNone) {
+    // Ultimately, default to SPIR-V assembly. That's nice for interactive use.
+    options.format = Format::kSpvAsm;
+  }
+
+  auto diag_printer = tint::diag::Printer::create(stderr, true);
+  tint::diag::Formatter diag_formatter;
+
+  std::unique_ptr<tint::Program> program;
+  std::unique_ptr<tint::Source::File> source_file;
+
+  enum class InputFormat {
+    kUnknown,
+    kWgsl,
+    kSpirvBin,
+    kSpirvAsm,
+  };
+  auto input_format = InputFormat::kUnknown;
+
+  if (options.input_filename.size() > 5 &&
+      options.input_filename.substr(options.input_filename.size() - 5) ==
+          ".wgsl") {
+    input_format = InputFormat::kWgsl;
+  } else if (options.input_filename.size() > 4 &&
+             options.input_filename.substr(options.input_filename.size() - 4) ==
+                 ".spv") {
+    input_format = InputFormat::kSpirvBin;
+  } else if (options.input_filename.size() > 7 &&
+             options.input_filename.substr(options.input_filename.size() - 7) ==
+                 ".spvasm") {
+    input_format = InputFormat::kSpirvAsm;
+  }
+
+  switch (input_format) {
+    case InputFormat::kUnknown: {
+      std::cerr << "Unknown input format" << std::endl;
+      return 1;
+    }
+    case InputFormat::kWgsl: {
+#if TINT_BUILD_WGSL_READER
+      std::vector<uint8_t> data;
+      if (!ReadFile<uint8_t>(options.input_filename, &data)) {
+        return 1;
+      }
+      source_file = std::make_unique<tint::Source::File>(
+          options.input_filename, std::string(data.begin(), data.end()));
+      program = std::make_unique<tint::Program>(
+          tint::reader::wgsl::Parse(source_file.get()));
+      break;
+#else
+      std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
+      return 1;
+#endif  // TINT_BUILD_WGSL_READER
+    }
+    case InputFormat::kSpirvBin: {
+#if TINT_BUILD_SPV_READER
+      std::vector<uint32_t> data;
+      if (!ReadFile<uint32_t>(options.input_filename, &data)) {
+        return 1;
+      }
+      program =
+          std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
+      break;
+#else
+      std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
+      return 1;
+#endif  // TINT_BUILD_SPV_READER
+    }
+    case InputFormat::kSpirvAsm: {
+#if TINT_BUILD_SPV_READER
+      std::vector<char> text;
+      if (!ReadFile<char>(options.input_filename, &text)) {
+        return 1;
+      }
+      // Use Vulkan 1.1, since this is what Tint, internally, is expecting.
+      spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
+      tools.SetMessageConsumer([](spv_message_level_t, const char*,
+                                  const spv_position_t& pos, const char* msg) {
+        std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
+                  << std::endl;
+      });
+      std::vector<uint32_t> data;
+      if (!tools.Assemble(text.data(), text.size(), &data,
+                          SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
+        return 1;
+      }
+      program =
+          std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
+      break;
+#else
+      std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
+      return 1;
+#endif  // TINT_BUILD_SPV_READER
+    }
+  }
+
+  if (!program) {
+    std::cerr << "Failed to parse input file: " << options.input_filename
+              << std::endl;
+    return 1;
+  }
+  if (program->Diagnostics().count() > 0) {
+    if (!program->IsValid() && input_format != InputFormat::kWgsl) {
+      // Invalid program from a non-wgsl source. Print the WGSL, to help
+      // understand the diagnostics.
+      PrintWGSL(std::cout, *program);
+    }
+    diag_formatter.format(program->Diagnostics(), diag_printer.get());
+  }
+
+  if (!program->IsValid()) {
+    return 1;
+  }
+  if (options.parse_only) {
+    return 1;
+  }
+
+  tint::transform::Manager transform_manager;
+  tint::transform::DataMap transform_inputs;
+  for (const auto& name : options.transforms) {
+    // TODO(dsinclair): The vertex pulling transform requires setup code to
+    // be run that needs user input. Should we find a way to support that here
+    // maybe through a provided file?
+
+    bool found = false;
+    for (auto& t : transforms) {
+      if (t.name == name) {
+        t.make(transform_manager, transform_inputs);
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      std::cerr << "Unknown transform: " << name << std::endl;
+      std::cerr << "Available transforms: " << std::endl << transform_names();
+      return 1;
+    }
+  }
+
+  if (options.emit_single_entry_point) {
+    transform_manager.append(
+        std::make_unique<tint::transform::SingleEntryPoint>());
+    transform_inputs.Add<tint::transform::SingleEntryPoint::Config>(
+        options.ep_name);
+  }
+
+  switch (options.format) {
+    case Format::kMsl: {
+#if TINT_BUILD_MSL_WRITER
+      transform_inputs.Add<tint::transform::Renamer::Config>(
+          tint::transform::Renamer::Target::kMslKeywords,
+          /* preserve_unicode */ false);
+      transform_manager.Add<tint::transform::Renamer>();
+#endif  // TINT_BUILD_MSL_WRITER
+      break;
+    }
+#if TINT_BUILD_GLSL_WRITER
+    case Format::kGlsl: {
+      break;
+    }
+#endif  // TINT_BUILD_GLSL_WRITER
+    case Format::kHlsl: {
+#if TINT_BUILD_HLSL_WRITER
+      transform_inputs.Add<tint::transform::Renamer::Config>(
+          tint::transform::Renamer::Target::kHlslKeywords,
+          /* preserve_unicode */ false);
+      transform_manager.Add<tint::transform::Renamer>();
+#endif  // TINT_BUILD_HLSL_WRITER
+      break;
+    }
+    default:
+      break;
+  }
+
+  auto out = transform_manager.Run(program.get(), std::move(transform_inputs));
+  if (!out.program.IsValid()) {
+    PrintWGSL(std::cerr, out.program);
+    diag_formatter.format(out.program.Diagnostics(), diag_printer.get());
+    return 1;
+  }
+
+  *program = std::move(out.program);
+
+  if (options.dump_inspector_bindings) {
+    std::cout << std::string(80, '-') << std::endl;
+    tint::inspector::Inspector inspector(program.get());
+    auto entry_points = inspector.GetEntryPoints();
+    if (!inspector.error().empty()) {
+      std::cerr << "Failed to get entry points from Inspector: "
+                << inspector.error() << std::endl;
+      return 1;
+    }
+
+    for (auto& entry_point : entry_points) {
+      auto bindings = inspector.GetResourceBindings(entry_point.name);
+      if (!inspector.error().empty()) {
+        std::cerr << "Failed to get bindings from Inspector: "
+                  << inspector.error() << std::endl;
+        return 1;
+      }
+      std::cout << "Entry Point = " << entry_point.name << std::endl;
+      for (auto& binding : bindings) {
+        std::cout << "\t[" << binding.bind_group << "][" << binding.binding
+                  << "]:" << std::endl;
+        std::cout << "\t\t resource_type = "
+                  << ResourceTypeToString(binding.resource_type) << std::endl;
+        std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim)
+                  << std::endl;
+        std::cout << "\t\t sampled_kind = "
+                  << SampledKindToString(binding.sampled_kind) << std::endl;
+        std::cout << "\t\t image_format = "
+                  << TexelFormatToString(binding.image_format) << std::endl;
+      }
+    }
+    std::cout << std::string(80, '-') << std::endl;
+  }
+
+  bool success = false;
+  switch (options.format) {
+    case Format::kSpirv:
+    case Format::kSpvAsm:
+      success = GenerateSpirv(program.get(), options);
+      break;
+    case Format::kWgsl:
+      success = GenerateWgsl(program.get(), options);
+      break;
+    case Format::kMsl:
+      success = GenerateMsl(program.get(), options);
+      break;
+    case Format::kHlsl:
+      success = GenerateHlsl(program.get(), options);
+      break;
+    case Format::kGlsl:
+      success = GenerateGlsl(program.get(), options);
+      break;
+    default:
+      std::cerr << "Unknown output format specified" << std::endl;
+      return 1;
+  }
+  if (!success) {
+    return 1;
+  }
+
+  return 0;
+}
diff --git a/src/tint/debug.cc b/src/tint/debug.cc
new file mode 100644
index 0000000..c51cf3f
--- /dev/null
+++ b/src/tint/debug.cc
@@ -0,0 +1,50 @@
+// 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/debug.h"
+
+#include <memory>
+
+#include "src/tint/utils/debugger.h"
+
+namespace tint {
+namespace {
+
+InternalCompilerErrorReporter* ice_reporter = nullptr;
+
+}  // namespace
+
+void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter) {
+  ice_reporter = reporter;
+}
+
+InternalCompilerError::InternalCompilerError(const char* file,
+                                             size_t line,
+                                             diag::System system,
+                                             diag::List& diagnostics)
+    : file_(file), line_(line), system_(system), diagnostics_(diagnostics) {}
+
+InternalCompilerError::~InternalCompilerError() {
+  auto file = std::make_shared<Source::File>(file_, "");
+  Source source{Source::Range{{line_}}, file.get()};
+  diagnostics_.add_ice(system_, msg_.str(), source, std::move(file));
+
+  if (ice_reporter) {
+    ice_reporter(diagnostics_);
+  }
+
+  debugger::Break();
+}
+
+}  // namespace tint
diff --git a/src/tint/debug.h b/src/tint/debug.h
new file mode 100644
index 0000000..90e6b66
--- /dev/null
+++ b/src/tint/debug.h
@@ -0,0 +1,123 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_DEBUG_H_
+#define SRC_TINT_DEBUG_H_
+
+#include <utility>
+
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/diagnostic/formatter.h"
+#include "src/tint/diagnostic/printer.h"
+
+namespace tint {
+
+/// Function type used for registering an internal compiler error reporter
+using InternalCompilerErrorReporter = void(const diag::List&);
+
+/// Sets the global error reporter to be called in case of internal compiler
+/// errors.
+/// @param reporter the error reporter
+void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter);
+
+/// InternalCompilerError is a helper for reporting internal compiler errors.
+/// Construct the InternalCompilerError with the source location of the ICE
+/// fault and append any error details with the `<<` operator.
+/// When the InternalCompilerError is destructed, the concatenated error message
+/// is appended to the diagnostics list with the severity of
+/// tint::diag::Severity::InternalCompilerError, and if a
+/// InternalCompilerErrorReporter is set, then it is called with the diagnostic
+/// list.
+class InternalCompilerError {
+ public:
+  /// Constructor
+  /// @param file the file containing the ICE
+  /// @param line the line containing the ICE
+  /// @param system the Tint system that has raised the ICE
+  /// @param diagnostics the list of diagnostics to append the ICE message to
+  InternalCompilerError(const char* file,
+                        size_t line,
+                        diag::System system,
+                        diag::List& diagnostics);
+
+  /// Destructor.
+  /// Adds the internal compiler error message to the diagnostics list, and then
+  /// calls the InternalCompilerErrorReporter if one is set.
+  ~InternalCompilerError();
+
+  /// Appends `arg` to the ICE message.
+  /// @param arg the argument to append to the ICE message
+  /// @returns this object so calls can be chained
+  template <typename T>
+  InternalCompilerError& operator<<(T&& arg) {
+    msg_ << std::forward<T>(arg);
+    return *this;
+  }
+
+ private:
+  char const* const file_;
+  const size_t line_;
+  diag::System system_;
+  diag::List& diagnostics_;
+  std::stringstream msg_;
+};
+
+}  // namespace tint
+
+/// TINT_ICE() is a macro for appending an internal compiler error message
+/// to the diagnostics list `diagnostics`, and calling the
+/// InternalCompilerErrorReporter with the full diagnostic list if a reporter is
+/// set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_ICE(system, diagnostics)             \
+  tint::InternalCompilerError(__FILE__, __LINE__, \
+                              ::tint::diag::System::system, diagnostics)
+
+/// TINT_UNREACHABLE() is a macro for appending a "TINT_UNREACHABLE"
+/// internal compiler error message to the diagnostics list `diagnostics`, and
+/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
+/// reporter is set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_UNREACHABLE(system, diagnostics) \
+  TINT_ICE(system, diagnostics) << "TINT_UNREACHABLE "
+
+/// TINT_UNIMPLEMENTED() is a macro for appending a "TINT_UNIMPLEMENTED"
+/// internal compiler error message to the diagnostics list `diagnostics`, and
+/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
+/// reporter is set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_UNIMPLEMENTED(system, diagnostics) \
+  TINT_ICE(system, diagnostics) << "TINT_UNIMPLEMENTED "
+
+/// TINT_ASSERT() is a macro for checking the expression is true, triggering a
+/// TINT_ICE if it is not.
+/// The ICE message contains the callsite's file and line.
+/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT() does not
+/// append a message to an existing tint::diag::List. As such, TINT_ASSERT()
+/// may silently fail in builds where SetInternalCompilerErrorReporter() is not
+/// called. Only use in places where there's no sensible place to put proper
+/// error handling.
+#define TINT_ASSERT(system, condition)                   \
+  do {                                                   \
+    if (!(condition)) {                                  \
+      tint::diag::List diagnostics;                      \
+      TINT_ICE(system, diagnostics)                      \
+          << "TINT_ASSERT(" #system ", " #condition ")"; \
+    }                                                    \
+  } while (false)
+
+#endif  // SRC_TINT_DEBUG_H_
diff --git a/src/tint/debug_test.cc b/src/tint/debug_test.cc
new file mode 100644
index 0000000..257b312
--- /dev/null
+++ b/src/tint/debug_test.cc
@@ -0,0 +1,41 @@
+// 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/debug.h"
+
+#include "gtest/gtest-spi.h"
+
+namespace tint {
+namespace {
+
+TEST(DebugTest, Unreachable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        diag::List diagnostics;
+        TINT_UNREACHABLE(Test, diagnostics);
+      },
+      "internal compiler error");
+}
+
+TEST(DebugTest, AssertTrue) {
+  TINT_ASSERT(Test, true);
+}
+
+TEST(DebugTest, AssertFalse) {
+  EXPECT_FATAL_FAILURE({ TINT_ASSERT(Test, false); },
+                       "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/demangler.cc b/src/tint/demangler.cc
new file mode 100644
index 0000000..cf5e4d6
--- /dev/null
+++ b/src/tint/demangler.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 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/demangler.h"
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace {
+
+constexpr char kSymbol[] = "$";
+constexpr size_t kSymbolLen = sizeof(kSymbol) - 1;
+
+}  // namespace
+
+Demangler::Demangler() = default;
+
+Demangler::~Demangler() = default;
+
+std::string Demangler::Demangle(const SymbolTable& symbols,
+                                const std::string& str) const {
+  std::stringstream out;
+
+  size_t pos = 0;
+  for (;;) {
+    auto idx = str.find(kSymbol, pos);
+    if (idx == std::string::npos) {
+      out << str.substr(pos);
+      break;
+    }
+
+    out << str.substr(pos, idx - pos);
+
+    auto start_idx = idx + kSymbolLen;
+    auto end_idx = start_idx;
+    while (str[end_idx] >= '0' && str[end_idx] <= '9') {
+      end_idx++;
+    }
+    auto len = end_idx - start_idx;
+
+    auto id = str.substr(start_idx, len);
+    Symbol sym(std::stoi(id), symbols.ProgramID());
+    out << symbols.NameFor(sym);
+
+    pos = end_idx;
+  }
+
+  return out.str();
+}
+
+}  // namespace tint
diff --git a/src/tint/demangler.h b/src/tint/demangler.h
new file mode 100644
index 0000000..8c0c964
--- /dev/null
+++ b/src/tint/demangler.h
@@ -0,0 +1,42 @@
+// Copyright 2020 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_DEMANGLER_H_
+#define SRC_TINT_DEMANGLER_H_
+
+#include <string>
+
+namespace tint {
+
+class SymbolTable;
+
+/// Helper to demangle strings and replace symbols with original names
+class Demangler {
+ public:
+  /// Constructor
+  Demangler();
+  /// Destructor
+  ~Demangler();
+
+  /// Transforms given string and replaces any symbols with original names
+  /// @param symbols the symbol table
+  /// @param str the string to replace
+  /// @returns the string with any symbol replacements performed.
+  std::string Demangle(const SymbolTable& symbols,
+                       const std::string& str) const;
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_DEMANGLER_H_
diff --git a/src/tint/demangler_test.cc b/src/tint/demangler_test.cc
new file mode 100644
index 0000000..f2c7658
--- /dev/null
+++ b/src/tint/demangler_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/demangler.h"
+#include "src/tint/symbol_table.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+using DemanglerTest = testing::Test;
+
+TEST_F(DemanglerTest, NoSymbols) {
+  SymbolTable t{ProgramID::New()};
+  t.Register("sym1");
+
+  Demangler d;
+  EXPECT_EQ("test str", d.Demangle(t, "test str"));
+}
+
+TEST_F(DemanglerTest, Symbol) {
+  SymbolTable t{ProgramID::New()};
+  t.Register("sym1");
+
+  Demangler d;
+  EXPECT_EQ("test sym1 str", d.Demangle(t, "test $1 str"));
+}
+
+TEST_F(DemanglerTest, MultipleSymbols) {
+  SymbolTable t{ProgramID::New()};
+  t.Register("sym1");
+  t.Register("sym2");
+
+  Demangler d;
+  EXPECT_EQ("test sym1 sym2 sym1 str", d.Demangle(t, "test $1 $2 $1 str"));
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/diagnostic/diagnostic.cc b/src/tint/diagnostic/diagnostic.cc
new file mode 100644
index 0000000..f7b36a4
--- /dev/null
+++ b/src/tint/diagnostic/diagnostic.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 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/diagnostic/diagnostic.h"
+
+#include <unordered_map>
+
+#include "src/tint/diagnostic/formatter.h"
+
+namespace tint {
+namespace diag {
+
+Diagnostic::Diagnostic() = default;
+Diagnostic::Diagnostic(const Diagnostic&) = default;
+Diagnostic::~Diagnostic() = default;
+Diagnostic& Diagnostic::operator=(const Diagnostic&) = default;
+
+List::List() = default;
+List::List(std::initializer_list<Diagnostic> list) : entries_(list) {}
+List::List(const List& rhs) = default;
+
+List::List(List&& rhs) = default;
+
+List::~List() = default;
+
+List& List::operator=(const List& rhs) = default;
+
+List& List::operator=(List&& rhs) = default;
+
+std::string List::str() const {
+  diag::Formatter::Style style;
+  style.print_newline_at_end = false;
+  return Formatter{style}.format(*this);
+}
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h
new file mode 100644
index 0000000..28b0c66
--- /dev/null
+++ b/src/tint/diagnostic/diagnostic.h
@@ -0,0 +1,252 @@
+// Copyright 2020 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_DIAGNOSTIC_DIAGNOSTIC_H_
+#define SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/source.h"
+
+namespace tint {
+namespace diag {
+
+/// Severity is an enumerator of diagnostic severities.
+enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal };
+
+/// @return true iff `a` is more than, or of equal severity to `b`
+inline bool operator>=(Severity a, Severity b) {
+  return static_cast<int>(a) >= static_cast<int>(b);
+}
+
+/// System is an enumerator of Tint systems that can be the originator of a
+/// diagnostic message.
+enum class System {
+  AST,
+  Clone,
+  Inspector,
+  Program,
+  ProgramBuilder,
+  Reader,
+  Resolver,
+  Semantic,
+  Symbol,
+  Test,
+  Transform,
+  Utils,
+  Writer,
+};
+
+/// Diagnostic holds all the information for a single compiler diagnostic
+/// message.
+class Diagnostic {
+ public:
+  /// Constructor
+  Diagnostic();
+  /// Copy constructor
+  Diagnostic(const Diagnostic&);
+  /// Destructor
+  ~Diagnostic();
+
+  /// Copy assignment operator
+  /// @return this diagnostic
+  Diagnostic& operator=(const Diagnostic&);
+
+  /// severity is the severity of the diagnostic message.
+  Severity severity = Severity::Error;
+  /// source is the location of the diagnostic.
+  Source source;
+  /// message is the text associated with the diagnostic.
+  std::string message;
+  /// system is the Tint system that raised the diagnostic.
+  System system;
+  /// code is the error code, for example a validation error might have the code
+  /// `"v-0001"`.
+  const char* code = nullptr;
+  /// A shared pointer to a Source::File. Only used if the diagnostic Source
+  /// points to a file that was created specifically for this diagnostic
+  /// (usually an ICE).
+  std::shared_ptr<Source::File> owned_file = nullptr;
+};
+
+/// List is a container of Diagnostic messages.
+class List {
+ public:
+  /// iterator is the type used for range based iteration.
+  using iterator = std::vector<Diagnostic>::const_iterator;
+
+  /// Constructs the list with no elements.
+  List();
+
+  /// Copy constructor. Copies the diagnostics from `list` into this list.
+  /// @param list the list of diagnostics to copy into this list.
+  List(std::initializer_list<Diagnostic> list);
+
+  /// Copy constructor. Copies the diagnostics from `list` into this list.
+  /// @param list the list of diagnostics to copy into this list.
+  List(const List& list);
+
+  /// Move constructor. Moves the diagnostics from `list` into this list.
+  /// @param list the list of diagnostics to move into this list.
+  List(List&& list);
+
+  /// Destructor
+  ~List();
+
+  /// Assignment operator. Copies the diagnostics from `list` into this list.
+  /// @param list the list to copy into this list.
+  /// @return this list.
+  List& operator=(const List& list);
+
+  /// Assignment move operator. Moves the diagnostics from `list` into this
+  /// list.
+  /// @param list the list to move into this list.
+  /// @return this list.
+  List& operator=(List&& list);
+
+  /// adds a diagnostic to the end of this list.
+  /// @param diag the diagnostic to append to this list.
+  void add(Diagnostic&& diag) {
+    if (diag.severity >= Severity::Error) {
+      error_count_++;
+    }
+    entries_.emplace_back(std::move(diag));
+  }
+
+  /// adds a list of diagnostics to the end of this list.
+  /// @param list the diagnostic to append to this list.
+  void add(const List& list) {
+    for (auto diag : list) {
+      add(std::move(diag));
+    }
+  }
+
+  /// adds the note message with the given Source to the end of this list.
+  /// @param system the system raising the note message
+  /// @param note_msg the note message
+  /// @param source the source of the note diagnostic
+  void add_note(System system,
+                const std::string& note_msg,
+                const Source& source) {
+    diag::Diagnostic note{};
+    note.severity = diag::Severity::Note;
+    note.system = system;
+    note.source = source;
+    note.message = note_msg;
+    add(std::move(note));
+  }
+
+  /// adds the warning message with the given Source to the end of this list.
+  /// @param system the system raising the warning message
+  /// @param warning_msg the warning message
+  /// @param source the source of the warning diagnostic
+  void add_warning(System system,
+                   const std::string& warning_msg,
+                   const Source& source) {
+    diag::Diagnostic warning{};
+    warning.severity = diag::Severity::Warning;
+    warning.system = system;
+    warning.source = source;
+    warning.message = warning_msg;
+    add(std::move(warning));
+  }
+
+  /// adds the error message without a source to the end of this list.
+  /// @param system the system raising the error message
+  /// @param err_msg the error message
+  void add_error(System system, std::string err_msg) {
+    diag::Diagnostic error{};
+    error.severity = diag::Severity::Error;
+    error.system = system;
+    error.message = std::move(err_msg);
+    add(std::move(error));
+  }
+
+  /// adds the error message with the given Source to the end of this list.
+  /// @param system the system raising the error message
+  /// @param err_msg the error message
+  /// @param source the source of the error diagnostic
+  void add_error(System system, std::string err_msg, const Source& source) {
+    diag::Diagnostic error{};
+    error.severity = diag::Severity::Error;
+    error.system = system;
+    error.source = source;
+    error.message = std::move(err_msg);
+    add(std::move(error));
+  }
+
+  /// adds the error message with the given code and Source to the end of this
+  /// list.
+  /// @param system the system raising the error message
+  /// @param code the error code
+  /// @param err_msg the error message
+  /// @param source the source of the error diagnostic
+  void add_error(System system,
+                 const char* code,
+                 std::string err_msg,
+                 const Source& source) {
+    diag::Diagnostic error{};
+    error.code = code;
+    error.severity = diag::Severity::Error;
+    error.system = system;
+    error.source = source;
+    error.message = std::move(err_msg);
+    add(std::move(error));
+  }
+
+  /// adds an internal compiler error message to the end of this list.
+  /// @param system the system raising the error message
+  /// @param err_msg the error message
+  /// @param source the source of the internal compiler error
+  /// @param file the Source::File owned by this diagnostic
+  void add_ice(System system,
+               const std::string& err_msg,
+               const Source& source,
+               std::shared_ptr<Source::File> file) {
+    diag::Diagnostic ice{};
+    ice.severity = diag::Severity::InternalCompilerError;
+    ice.system = system;
+    ice.source = source;
+    ice.message = err_msg;
+    ice.owned_file = std::move(file);
+    add(std::move(ice));
+  }
+
+  /// @returns true iff the diagnostic list contains errors diagnostics (or of
+  /// higher severity).
+  bool contains_errors() const { return error_count_ > 0; }
+  /// @returns the number of error diagnostics (or of higher severity).
+  size_t error_count() const { return error_count_; }
+  /// @returns the number of entries in the list.
+  size_t count() const { return entries_.size(); }
+  /// @returns the first diagnostic in the list.
+  iterator begin() const { return entries_.begin(); }
+  /// @returns the last diagnostic in the list.
+  iterator end() const { return entries_.end(); }
+
+  /// @returns a formatted string of all the diagnostics in this list.
+  std::string str() const;
+
+ private:
+  std::vector<Diagnostic> entries_;
+  size_t error_count_ = 0;
+};
+
+}  // namespace diag
+}  // namespace tint
+
+#endif  // SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
diff --git a/src/tint/diagnostic/diagnostic_test.cc b/src/tint/diagnostic/diagnostic_test.cc
new file mode 100644
index 0000000..940971b
--- /dev/null
+++ b/src/tint/diagnostic/diagnostic_test.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 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/diagnostic/formatter.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/diagnostic/diagnostic.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+TEST(DiagListTest, OwnedFilesShared) {
+  auto file = std::make_shared<Source::File>("path", "content");
+
+  diag::List list_a, list_b;
+  {
+    diag::Diagnostic diag{};
+    diag.source = Source{Source::Range{{0, 0}}, file.get()};
+    list_a.add(std::move(diag));
+  }
+
+  list_b = list_a;
+
+  ASSERT_EQ(list_b.count(), list_a.count());
+  EXPECT_EQ(list_b.begin()->source.file, file.get());
+}
+
+}  // namespace
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/formatter.cc b/src/tint/diagnostic/formatter.cc
new file mode 100644
index 0000000..1eabe91
--- /dev/null
+++ b/src/tint/diagnostic/formatter.cc
@@ -0,0 +1,271 @@
+// Copyright 2020 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/diagnostic/formatter.h"
+
+#include <algorithm>
+#include <iterator>
+#include <vector>
+
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/diagnostic/printer.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+const char* to_str(Severity severity) {
+  switch (severity) {
+    case Severity::Note:
+      return "note";
+    case Severity::Warning:
+      return "warning";
+    case Severity::Error:
+      return "error";
+    case Severity::InternalCompilerError:
+      return "internal compiler error";
+    case Severity::Fatal:
+      return "fatal";
+  }
+  return "";
+}
+
+std::string to_str(const Source::Location& location) {
+  std::stringstream ss;
+  if (location.line > 0) {
+    ss << location.line;
+    if (location.column > 0) {
+      ss << ":" << location.column;
+    }
+  }
+  return ss.str();
+}
+
+}  // namespace
+
+/// State holds the internal formatter state for a format() call.
+struct Formatter::State {
+  /// Constructs a State associated with the given printer.
+  /// @param p the printer to write formatted messages to.
+  explicit State(Printer* p) : printer(p) {}
+  ~State() { flush(); }
+
+  /// set_style() sets the current style to new_style, flushing any pending
+  /// messages to the printer if the style changed.
+  /// @param new_style the new style to apply for future written messages.
+  void set_style(const diag::Style& new_style) {
+    if (style.color != new_style.color || style.bold != new_style.bold) {
+      flush();
+      style = new_style;
+    }
+  }
+
+  /// flush writes any pending messages to the printer, clearing the buffer.
+  void flush() {
+    auto str = stream.str();
+    if (str.length() > 0) {
+      printer->write(str, style);
+      std::stringstream reset;
+      stream.swap(reset);
+    }
+  }
+
+  /// operator<< queues msg to be written to the printer.
+  /// @param msg the value or string to write to the printer
+  /// @returns this State so that calls can be chained
+  template <typename T>
+  State& operator<<(const T& msg) {
+    stream << msg;
+    return *this;
+  }
+
+  /// newline queues a newline to be written to the printer.
+  void newline() { stream << std::endl; }
+
+  /// repeat queues the character c to be written to the printer n times.
+  /// @param c the character to print `n` times
+  /// @param n the number of times to print character `c`
+  void repeat(char c, size_t n) {
+    std::fill_n(std::ostream_iterator<char>(stream), n, c);
+  }
+
+ private:
+  Printer* printer;
+  diag::Style style;
+  std::stringstream stream;
+};
+
+Formatter::Formatter() {}
+Formatter::Formatter(const Style& style) : style_(style) {}
+
+void Formatter::format(const List& list, Printer* printer) const {
+  State state{printer};
+
+  bool first = true;
+  for (auto diag : list) {
+    state.set_style({});
+    if (!first) {
+      state.newline();
+    }
+    format(diag, state);
+    first = false;
+  }
+
+  if (style_.print_newline_at_end) {
+    state.newline();
+  }
+}
+
+void Formatter::format(const Diagnostic& diag, State& state) const {
+  auto const& src = diag.source;
+  auto const& rng = src.range;
+  bool has_code = diag.code != nullptr && diag.code[0] != '\0';
+
+  state.set_style({Color::kDefault, true});
+
+  struct TextAndColor {
+    std::string text;
+    Color color;
+    bool bold = false;
+  };
+  std::vector<TextAndColor> prefix;
+  prefix.reserve(6);
+
+  if (style_.print_file && src.file != nullptr) {
+    if (rng.begin.line > 0) {
+      prefix.emplace_back(TextAndColor{src.file->path + ":" + to_str(rng.begin),
+                                       Color::kDefault});
+    } else {
+      prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault});
+    }
+  } else if (rng.begin.line > 0) {
+    prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
+  }
+
+  Color severity_color = Color::kDefault;
+  switch (diag.severity) {
+    case Severity::Note:
+      break;
+    case Severity::Warning:
+      severity_color = Color::kYellow;
+      break;
+    case Severity::Error:
+      severity_color = Color::kRed;
+      break;
+    case Severity::Fatal:
+    case Severity::InternalCompilerError:
+      severity_color = Color::kMagenta;
+      break;
+  }
+  if (style_.print_severity) {
+    prefix.emplace_back(
+        TextAndColor{to_str(diag.severity), severity_color, true});
+  }
+  if (has_code) {
+    prefix.emplace_back(TextAndColor{diag.code, severity_color});
+  }
+
+  for (size_t i = 0; i < prefix.size(); i++) {
+    if (i > 0) {
+      state << " ";
+    }
+    state.set_style({prefix[i].color, prefix[i].bold});
+    state << prefix[i].text;
+  }
+
+  state.set_style({Color::kDefault, true});
+  if (!prefix.empty()) {
+    state << ": ";
+  }
+  state << diag.message;
+
+  if (style_.print_line && src.file && rng.begin.line > 0) {
+    state.newline();
+    state.set_style({Color::kDefault, false});
+
+    for (size_t line_num = rng.begin.line;
+         (line_num <= rng.end.line) &&
+         (line_num <= src.file->content.lines.size());
+         line_num++) {
+      auto& line = src.file->content.lines[line_num - 1];
+      auto line_len = line.size();
+
+      bool is_ascii = true;
+      for (auto c : line) {
+        if (c == '\t') {
+          state.repeat(' ', style_.tab_width);
+        } else {
+          state << c;
+        }
+        if (c & 0x80) {
+          is_ascii = false;
+        }
+      }
+
+      state.newline();
+
+      // If the line contains non-ascii characters, then we cannot assume that
+      // a single utf8 code unit represents a single glyph, so don't attempt to
+      // draw squiggles.
+      if (!is_ascii) {
+        continue;
+      }
+
+      state.set_style({Color::kCyan, false});
+
+      // Count the number of glyphs in the line span.
+      // start and end use 1-based indexing.
+      auto num_glyphs = [&](size_t start, size_t end) {
+        size_t count = 0;
+        start = (start > 0) ? (start - 1) : 0;
+        end = (end > 0) ? (end - 1) : 0;
+        for (size_t i = start; (i < end) && (i < line_len); i++) {
+          count += (line[i] == '\t') ? style_.tab_width : 1;
+        }
+        return count;
+      };
+
+      if (line_num == rng.begin.line && line_num == rng.end.line) {
+        // Single line
+        state.repeat(' ', num_glyphs(1, rng.begin.column));
+        state.repeat('^', std::max<size_t>(
+                              num_glyphs(rng.begin.column, rng.end.column), 1));
+      } else if (line_num == rng.begin.line) {
+        // Start of multi-line
+        state.repeat(' ', num_glyphs(1, rng.begin.column));
+        state.repeat('^', num_glyphs(rng.begin.column, line_len + 1));
+      } else if (line_num == rng.end.line) {
+        // End of multi-line
+        state.repeat('^', num_glyphs(1, rng.end.column));
+      } else {
+        // Middle of multi-line
+        state.repeat('^', num_glyphs(1, line_len + 1));
+      }
+      state.newline();
+    }
+
+    state.set_style({});
+  }
+}
+
+std::string Formatter::format(const List& list) const {
+  StringPrinter printer;
+  format(list, &printer);
+  return printer.str();
+}
+
+Formatter::~Formatter() = default;
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/formatter.h b/src/tint/diagnostic/formatter.h
new file mode 100644
index 0000000..209bacb
--- /dev/null
+++ b/src/tint/diagnostic/formatter.h
@@ -0,0 +1,72 @@
+// Copyright 2020 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_DIAGNOSTIC_FORMATTER_H_
+#define SRC_TINT_DIAGNOSTIC_FORMATTER_H_
+
+#include <string>
+
+namespace tint {
+namespace diag {
+
+class Diagnostic;
+class List;
+class Printer;
+
+/// Formatter are used to print a list of diagnostics messages.
+class Formatter {
+ public:
+  /// Style controls the formatter's output style.
+  struct Style {
+    /// include the file path for each diagnostic
+    bool print_file = true;
+    /// include the severity for each diagnostic
+    bool print_severity = true;
+    /// include the source line(s) for the diagnostic
+    bool print_line = true;
+    /// print a newline at the end of a diagnostic list
+    bool print_newline_at_end = true;
+    /// width of a tab character
+    size_t tab_width = 2u;
+  };
+
+  /// Constructor for the formatter using a default style.
+  Formatter();
+
+  /// Constructor for the formatter using the custom style.
+  /// @param style the style used for the formatter.
+  explicit Formatter(const Style& style);
+
+  ~Formatter();
+
+  /// @param list the list of diagnostic messages to format
+  /// @param printer the printer used to display the formatted diagnostics
+  void format(const List& list, Printer* printer) const;
+
+  /// @return the list of diagnostics `list` formatted to a string.
+  /// @param list the list of diagnostic messages to format
+  std::string format(const List& list) const;
+
+ private:
+  struct State;
+
+  void format(const Diagnostic& diag, State& state) const;
+
+  const Style style_;
+};
+
+}  // namespace diag
+}  // namespace tint
+
+#endif  // SRC_TINT_DIAGNOSTIC_FORMATTER_H_
diff --git a/src/tint/diagnostic/formatter_test.cc b/src/tint/diagnostic/formatter_test.cc
new file mode 100644
index 0000000..cee4140
--- /dev/null
+++ b/src/tint/diagnostic/formatter_test.cc
@@ -0,0 +1,310 @@
+// Copyright 2020 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/diagnostic/formatter.h"
+
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/diagnostic/diagnostic.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+Diagnostic Diag(Severity severity,
+                Source source,
+                std::string message,
+                System system,
+                const char* code = nullptr) {
+  Diagnostic d;
+  d.severity = severity;
+  d.source = source;
+  d.message = std::move(message);
+  d.system = system;
+  d.code = code;
+  return d;
+}
+
+constexpr const char* ascii_content =  // Note: words are tab-delimited
+    R"(the	cat	says	meow
+the	dog	says	woof
+the	snake	says	quack
+the	snail	says	???
+)";
+
+constexpr const char* utf8_content =  // Note: words are tab-delimited
+    "the	\xf0\x9f\x90\xb1	says	meow\n"   // NOLINT: tabs
+    "the	\xf0\x9f\x90\x95	says	woof\n"   // NOLINT: tabs
+    "the	\xf0\x9f\x90\x8d	says	quack\n"  // NOLINT: tabs
+    "the	\xf0\x9f\x90\x8c	says	???\n";   // NOLINT: tabs
+
+class DiagFormatterTest : public testing::Test {
+ public:
+  Source::File ascii_file{"file.name", ascii_content};
+  Source::File utf8_file{"file.name", utf8_content};
+  Diagnostic ascii_diag_note =
+      Diag(Severity::Note,
+           Source{Source::Range{Source::Location{1, 14}}, &ascii_file},
+           "purr",
+           System::Test);
+  Diagnostic ascii_diag_warn =
+      Diag(Severity::Warning,
+           Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file},
+           "grrr",
+           System::Test);
+  Diagnostic ascii_diag_err =
+      Diag(Severity::Error,
+           Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file},
+           "hiss",
+           System::Test,
+           "abc123");
+  Diagnostic ascii_diag_ice =
+      Diag(Severity::InternalCompilerError,
+           Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
+           "unreachable",
+           System::Test);
+  Diagnostic ascii_diag_fatal =
+      Diag(Severity::Fatal,
+           Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
+           "nothing",
+           System::Test);
+
+  Diagnostic utf8_diag_note =
+      Diag(Severity::Note,
+           Source{Source::Range{Source::Location{1, 15}}, &utf8_file},
+           "purr",
+           System::Test);
+  Diagnostic utf8_diag_warn =
+      Diag(Severity::Warning,
+           Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file},
+           "grrr",
+           System::Test);
+  Diagnostic utf8_diag_err =
+      Diag(Severity::Error,
+           Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file},
+           "hiss",
+           System::Test,
+           "abc123");
+  Diagnostic utf8_diag_ice =
+      Diag(Severity::InternalCompilerError,
+           Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
+           "unreachable",
+           System::Test);
+  Diagnostic utf8_diag_fatal =
+      Diag(Severity::Fatal,
+           Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
+           "nothing",
+           System::Test);
+};
+
+TEST_F(DiagFormatterTest, Simple) {
+  Formatter fmt{{false, false, false, false}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(1:14: purr
+2:14: grrr
+3:16 abc123: hiss)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, SimpleNewlineAtEnd) {
+  Formatter fmt{{false, false, false, true}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(1:14: purr
+2:14: grrr
+3:16 abc123: hiss
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, SimpleNoSource) {
+  Formatter fmt{{false, false, false, false}};
+  auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test);
+  auto got = fmt.format(List{diag});
+  auto* expect = "no source!";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithFile) {
+  Formatter fmt{{true, false, false, false}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(file.name:1:14: purr
+file.name:2:14: grrr
+file.name:3:16 abc123: hiss)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithSeverity) {
+  Formatter fmt{{false, true, false, false}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(1:14 note: purr
+2:14 warning: grrr
+3:16 error abc123: hiss)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithLine) {
+  Formatter fmt{{false, false, true, false}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(1:14: purr
+the  cat  says  meow
+                ^
+
+2:14: grrr
+the  dog  says  woof
+                ^^^^
+
+3:16 abc123: hiss
+the  snake  says  quack
+                  ^^^^^
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, UnicodeWithLine) {
+  Formatter fmt{{false, false, true, false}};
+  auto got = fmt.format(List{utf8_diag_note, utf8_diag_warn, utf8_diag_err});
+  auto* expect =
+      "1:15: purr\n"
+      "the  \xf0\x9f\x90\xb1  says  meow\n"
+      "\n"
+      "2:15: grrr\n"
+      "the  \xf0\x9f\x90\x95  says  woof\n"
+      "\n"
+      "3:15 abc123: hiss\n"
+      "the  \xf0\x9f\x90\x8d  says  quack\n";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
+  Formatter fmt{{true, true, true, false}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(file.name:1:14 note: purr
+the  cat  says  meow
+                ^
+
+file.name:2:14 warning: grrr
+the  dog  says  woof
+                ^^^^
+
+file.name:3:16 error abc123: hiss
+the  snake  says  quack
+                  ^^^^^
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithMultiLine) {
+  auto multiline = Diag(Severity::Warning,
+                        Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
+                        "multiline", System::Test);
+  Formatter fmt{{false, false, true, false}};
+  auto got = fmt.format(List{multiline});
+  auto* expect = R"(2:9: multiline
+the  dog  says  woof
+          ^^^^^^^^^^
+the  snake  says  quack
+^^^^^^^^^^^^^^^^^^^^^^^
+the  snail  says  ???
+^^^^^^^^^^^^^^^^
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, UnicodeWithMultiLine) {
+  auto multiline = Diag(Severity::Warning,
+                        Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file},
+                        "multiline", System::Test);
+  Formatter fmt{{false, false, true, false}};
+  auto got = fmt.format(List{multiline});
+  auto* expect =
+      "2:9: multiline\n"
+      "the  \xf0\x9f\x90\x95  says  woof\n"
+      "the  \xf0\x9f\x90\x8d  says  quack\n"
+      "the  \xf0\x9f\x90\x8c  says  ???\n";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithFileSeverityLineTab4) {
+  Formatter fmt{{true, true, true, false, 4u}};
+  auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+  auto* expect = R"(file.name:1:14 note: purr
+the    cat    says    meow
+                      ^
+
+file.name:2:14 warning: grrr
+the    dog    says    woof
+                      ^^^^
+
+file.name:3:16 error abc123: hiss
+the    snake    says    quack
+                        ^^^^^
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) {
+  auto multiline = Diag(Severity::Warning,
+                        Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
+                        "multiline", System::Test);
+  Formatter fmt{{false, false, true, false, 4u}};
+  auto got = fmt.format(List{multiline});
+  auto* expect = R"(2:9: multiline
+the    dog    says    woof
+              ^^^^^^^^^^^^
+the    snake    says    quack
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+the    snail    says    ???
+^^^^^^^^^^^^^^^^^^^^
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, ICE) {
+  Formatter fmt{{}};
+  auto got = fmt.format(List{ascii_diag_ice});
+  auto* expect = R"(file.name:4:16 internal compiler error: unreachable
+the  snail  says  ???
+                  ^^^
+
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, Fatal) {
+  Formatter fmt{{}};
+  auto got = fmt.format(List{ascii_diag_fatal});
+  auto* expect = R"(file.name:4:16 fatal: nothing
+the  snail  says  ???
+                  ^^^
+
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, RangeOOB) {
+  Formatter fmt{{true, true, true, true}};
+  diag::List list;
+  list.add_error(System::Test, "oob",
+                 Source{{{10, 20}, {30, 20}}, &ascii_file});
+  auto got = fmt.format(list);
+  auto* expect = R"(file.name:10:20 error: oob
+
+)";
+  ASSERT_EQ(expect, got);
+}
+
+}  // namespace
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/printer.cc b/src/tint/diagnostic/printer.cc
new file mode 100644
index 0000000..54fd5f7
--- /dev/null
+++ b/src/tint/diagnostic/printer.cc
@@ -0,0 +1,34 @@
+// Copyright 2020 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/diagnostic/printer.h"
+
+namespace tint {
+namespace diag {
+
+Printer::~Printer() = default;
+
+StringPrinter::StringPrinter() = default;
+StringPrinter::~StringPrinter() = default;
+
+std::string StringPrinter::str() const {
+  return stream.str();
+}
+
+void StringPrinter::write(const std::string& str, const Style&) {
+  stream << str;
+}
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/printer.h b/src/tint/diagnostic/printer.h
new file mode 100644
index 0000000..570223b
--- /dev/null
+++ b/src/tint/diagnostic/printer.h
@@ -0,0 +1,83 @@
+// Copyright 2020 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_DIAGNOSTIC_PRINTER_H_
+#define SRC_TINT_DIAGNOSTIC_PRINTER_H_
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+namespace tint {
+namespace diag {
+
+class List;
+
+/// Color is an enumerator of colors used by Style.
+enum class Color {
+  kDefault,
+  kBlack,
+  kRed,
+  kGreen,
+  kYellow,
+  kBlue,
+  kMagenta,
+  kCyan,
+  kWhite,
+};
+
+/// Style describes how a diagnostic message should be printed.
+struct Style {
+  /// The foreground text color
+  Color color = Color::kDefault;
+  /// If true the text will be displayed with a strong weight
+  bool bold = false;
+};
+
+/// Printers are used to print formatted diagnostic messages to a terminal.
+class Printer {
+ public:
+  /// @returns a diagnostic Printer
+  /// @param out the file to print to.
+  /// @param use_colors if true, the printer will use colors if `out` is a
+  /// terminal and supports them.
+  static std::unique_ptr<Printer> create(FILE* out, bool use_colors);
+
+  virtual ~Printer();
+
+  /// writes the string str to the printer with the given style.
+  /// @param str the string to write to the printer
+  /// @param style the style used to print `str`
+  virtual void write(const std::string& str, const Style& style) = 0;
+};
+
+/// StringPrinter is an implementation of Printer that writes to a std::string.
+class StringPrinter : public Printer {
+ public:
+  StringPrinter();
+  ~StringPrinter() override;
+
+  /// @returns the printed string.
+  std::string str() const;
+
+  void write(const std::string& str, const Style&) override;
+
+ private:
+  std::stringstream stream;
+};
+
+}  // namespace diag
+}  // namespace tint
+
+#endif  // SRC_TINT_DIAGNOSTIC_PRINTER_H_
diff --git a/src/tint/diagnostic/printer_linux.cc b/src/tint/diagnostic/printer_linux.cc
new file mode 100644
index 0000000..fc40cf3
--- /dev/null
+++ b/src/tint/diagnostic/printer_linux.cc
@@ -0,0 +1,100 @@
+// Copyright 2020 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 <unistd.h>
+
+#include <cstring>
+
+#include "src/tint/diagnostic/printer.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+bool supports_colors(FILE* f) {
+  if (!isatty(fileno(f))) {
+    return false;
+  }
+
+  const char* cterm = getenv("TERM");
+  if (cterm == nullptr) {
+    return false;
+  }
+
+  std::string term = getenv("TERM");
+  if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" &&
+      term != "rxvt-unicode" && term != "screen-256color" && term != "screen" &&
+      term != "tmux-256color" && term != "tmux" && term != "xterm-256color" &&
+      term != "xterm-color" && term != "xterm") {
+    return false;
+  }
+
+  return true;
+}
+
+class PrinterLinux : public Printer {
+ public:
+  PrinterLinux(FILE* f, bool colors)
+      : file(f), use_colors(colors && supports_colors(f)) {}
+
+  void write(const std::string& str, const Style& style) override {
+    write_color(style.color, style.bold);
+    fwrite(str.data(), 1, str.size(), file);
+    write_color(Color::kDefault, false);
+  }
+
+ private:
+  constexpr const char* color_code(Color color, bool bold) {
+    switch (color) {
+      case Color::kDefault:
+        return bold ? "\u001b[1m" : "\u001b[0m";
+      case Color::kBlack:
+        return bold ? "\u001b[30;1m" : "\u001b[30m";
+      case Color::kRed:
+        return bold ? "\u001b[31;1m" : "\u001b[31m";
+      case Color::kGreen:
+        return bold ? "\u001b[32;1m" : "\u001b[32m";
+      case Color::kYellow:
+        return bold ? "\u001b[33;1m" : "\u001b[33m";
+      case Color::kBlue:
+        return bold ? "\u001b[34;1m" : "\u001b[34m";
+      case Color::kMagenta:
+        return bold ? "\u001b[35;1m" : "\u001b[35m";
+      case Color::kCyan:
+        return bold ? "\u001b[36;1m" : "\u001b[36m";
+      case Color::kWhite:
+        return bold ? "\u001b[37;1m" : "\u001b[37m";
+    }
+    return "";  // unreachable
+  }
+
+  void write_color(Color color, bool bold) {
+    if (use_colors) {
+      auto* code = color_code(color, bold);
+      fwrite(code, 1, strlen(code), file);
+    }
+  }
+
+  FILE* const file;
+  const bool use_colors;
+};
+
+}  // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
+  return std::make_unique<PrinterLinux>(out, use_colors);
+}
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/printer_other.cc b/src/tint/diagnostic/printer_other.cc
new file mode 100644
index 0000000..21498be
--- /dev/null
+++ b/src/tint/diagnostic/printer_other.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 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 <cstring>
+
+#include "src/tint/diagnostic/printer.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+class PrinterOther : public Printer {
+ public:
+  explicit PrinterOther(FILE* f) : file(f) {}
+
+  void write(const std::string& str, const Style&) override {
+    fwrite(str.data(), 1, str.size(), file);
+  }
+
+ private:
+  FILE* file;
+};
+
+}  // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool) {
+  return std::make_unique<PrinterOther>(out);
+}
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/printer_test.cc b/src/tint/diagnostic/printer_test.cc
new file mode 100644
index 0000000..f0cf374
--- /dev/null
+++ b/src/tint/diagnostic/printer_test.cc
@@ -0,0 +1,98 @@
+// Copyright 2020 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/diagnostic/printer.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace diag {
+namespace {
+
+// Actually verifying that the expected colors are printed is exceptionally
+// difficult as:
+// a) The color emission varies by OS.
+// b) The logic checks to see if the printer is writing to a terminal, making
+//    mocking hard.
+// c) Actually probing what gets written to a FILE* is notoriously tricky.
+//
+// The least we can do is to exersice the code - which is what we do here.
+// The test will print each of the colors, and can be examined with human
+// eyeballs.
+// This can be enabled or disabled with ENABLE_PRINTER_TESTS
+#define ENABLE_PRINTER_TESTS 0
+#if ENABLE_PRINTER_TESTS
+
+using PrinterTest = testing::Test;
+
+TEST_F(PrinterTest, WithColors) {
+  auto printer = Printer::create(stdout, true);
+  printer->write("Default", Style{Color::kDefault, false});
+  printer->write("Black", Style{Color::kBlack, false});
+  printer->write("Red", Style{Color::kRed, false});
+  printer->write("Green", Style{Color::kGreen, false});
+  printer->write("Yellow", Style{Color::kYellow, false});
+  printer->write("Blue", Style{Color::kBlue, false});
+  printer->write("Magenta", Style{Color::kMagenta, false});
+  printer->write("Cyan", Style{Color::kCyan, false});
+  printer->write("White", Style{Color::kWhite, false});
+  printf("\n");
+}
+
+TEST_F(PrinterTest, BoldWithColors) {
+  auto printer = Printer::create(stdout, true);
+  printer->write("Default", Style{Color::kDefault, true});
+  printer->write("Black", Style{Color::kBlack, true});
+  printer->write("Red", Style{Color::kRed, true});
+  printer->write("Green", Style{Color::kGreen, true});
+  printer->write("Yellow", Style{Color::kYellow, true});
+  printer->write("Blue", Style{Color::kBlue, true});
+  printer->write("Magenta", Style{Color::kMagenta, true});
+  printer->write("Cyan", Style{Color::kCyan, true});
+  printer->write("White", Style{Color::kWhite, true});
+  printf("\n");
+}
+
+TEST_F(PrinterTest, WithoutColors) {
+  auto printer = Printer::create(stdout, false);
+  printer->write("Default", Style{Color::kDefault, false});
+  printer->write("Black", Style{Color::kBlack, false});
+  printer->write("Red", Style{Color::kRed, false});
+  printer->write("Green", Style{Color::kGreen, false});
+  printer->write("Yellow", Style{Color::kYellow, false});
+  printer->write("Blue", Style{Color::kBlue, false});
+  printer->write("Magenta", Style{Color::kMagenta, false});
+  printer->write("Cyan", Style{Color::kCyan, false});
+  printer->write("White", Style{Color::kWhite, false});
+  printf("\n");
+}
+
+TEST_F(PrinterTest, BoldWithoutColors) {
+  auto printer = Printer::create(stdout, false);
+  printer->write("Default", Style{Color::kDefault, true});
+  printer->write("Black", Style{Color::kBlack, true});
+  printer->write("Red", Style{Color::kRed, true});
+  printer->write("Green", Style{Color::kGreen, true});
+  printer->write("Yellow", Style{Color::kYellow, true});
+  printer->write("Blue", Style{Color::kBlue, true});
+  printer->write("Magenta", Style{Color::kMagenta, true});
+  printer->write("Cyan", Style{Color::kCyan, true});
+  printer->write("White", Style{Color::kWhite, true});
+  printf("\n");
+}
+
+#endif  // ENABLE_PRINTER_TESTS
+}  // namespace
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/diagnostic/printer_windows.cc b/src/tint/diagnostic/printer_windows.cc
new file mode 100644
index 0000000..9dcb43c
--- /dev/null
+++ b/src/tint/diagnostic/printer_windows.cc
@@ -0,0 +1,113 @@
+// Copyright 2020 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 <cstring>
+
+#include "src/tint/diagnostic/printer.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+
+namespace tint {
+namespace diag {
+namespace {
+
+struct ConsoleInfo {
+  HANDLE handle = INVALID_HANDLE_VALUE;
+  WORD default_attributes = 0;
+  operator bool() const { return handle != INVALID_HANDLE_VALUE; }
+};
+
+ConsoleInfo console_info(FILE* file) {
+  if (file == nullptr) {
+    return {};
+  }
+
+  ConsoleInfo console{};
+  if (file == stdout) {
+    console.handle = GetStdHandle(STD_OUTPUT_HANDLE);
+  } else if (file == stderr) {
+    console.handle = GetStdHandle(STD_ERROR_HANDLE);
+  } else {
+    return {};
+  }
+
+  CONSOLE_SCREEN_BUFFER_INFO info{};
+  if (GetConsoleScreenBufferInfo(console.handle, &info) == 0) {
+    return {};
+  }
+
+  console.default_attributes = info.wAttributes;
+  return console;
+}
+
+class PrinterWindows : public Printer {
+ public:
+  PrinterWindows(FILE* f, bool use_colors)
+      : file(f), console(console_info(use_colors ? f : nullptr)) {}
+
+  void write(const std::string& str, const Style& style) override {
+    write_color(style.color, style.bold);
+    fwrite(str.data(), 1, str.size(), file);
+    write_color(Color::kDefault, false);
+  }
+
+ private:
+  WORD attributes(Color color, bool bold) {
+    switch (color) {
+      case Color::kDefault:
+        return console.default_attributes;
+      case Color::kBlack:
+        return 0;
+      case Color::kRed:
+        return FOREGROUND_RED | (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kGreen:
+        return FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kYellow:
+        return FOREGROUND_RED | FOREGROUND_GREEN |
+               (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kBlue:
+        return FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kMagenta:
+        return FOREGROUND_RED | FOREGROUND_BLUE |
+               (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kCyan:
+        return FOREGROUND_GREEN | FOREGROUND_BLUE |
+               (bold ? FOREGROUND_INTENSITY : 0);
+      case Color::kWhite:
+        return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
+               (bold ? FOREGROUND_INTENSITY : 0);
+    }
+    return 0;  // unreachable
+  }
+
+  void write_color(Color color, bool bold) {
+    if (console) {
+      SetConsoleTextAttribute(console.handle, attributes(color, bold));
+      fflush(file);
+    }
+  }
+
+  FILE* const file;
+  const ConsoleInfo console;
+};
+
+}  // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
+  return std::make_unique<PrinterWindows>(out, use_colors);
+}
+
+}  // namespace diag
+}  // namespace tint
diff --git a/src/tint/fuzzers/BUILD.gn b/src/tint/fuzzers/BUILD.gn
new file mode 100644
index 0000000..fd29315
--- /dev/null
+++ b/src/tint/fuzzers/BUILD.gn
@@ -0,0 +1,334 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("../../../tint_overrides_with_defaults.gni")
+
+# Fuzzers - Libfuzzer based fuzzing targets for Chromium
+# To run the fuzzers outside of Chromium, use the CMake based builds.
+
+if (build_with_chromium) {
+  import("//testing/libfuzzer/fuzzer_test.gni")
+
+  fuzzer_corpus_wgsl_dir = "${target_gen_dir}/fuzzer_corpus_wgsl"
+  action("tint_generate_wgsl_corpus") {
+    script = "generate_wgsl_corpus.py"
+    sources = [ "generate_wgsl_corpus.py" ]
+    args = [
+      rebase_path("${tint_root_dir}/test", root_build_dir),
+      rebase_path(fuzzer_corpus_wgsl_dir, root_build_dir),
+    ]
+    outputs = [ fuzzer_corpus_wgsl_dir ]
+  }
+
+  tint_fuzzer_common_libfuzzer_options = [
+    "only_ascii=1",
+    "max_len=10000",
+  ]
+
+  tint_ast_fuzzer_common_libfuzzer_options =
+      tint_fuzzer_common_libfuzzer_options + [
+        "cross_over=0",
+        "mutate_depth=1",
+        "tint_enable_all_mutations=false",
+        "tint_mutation_batch_size=5",
+      ]
+
+  tint_regex_fuzzer_common_libfuzzer_options =
+      tint_fuzzer_common_libfuzzer_options + [
+        "cross_over=0",
+        "mutate_depth=1",
+      ]
+
+  # fuzzer_test doesn't have configs members, so need to define them in an empty
+  # source_set.
+
+  source_set("tint_fuzzer_common_src") {
+    public_configs = [
+      "${tint_root_dir}/src/tint:tint_config",
+      "${tint_root_dir}/src/tint:tint_common_config",
+    ]
+
+    public_deps = [
+      "${tint_root_dir}/src/tint:libtint",
+      "${tint_spirv_tools_dir}/:spvtools_val",
+    ]
+
+    sources = [
+      "data_builder.h",
+      "mersenne_twister_engine.cc",
+      "mersenne_twister_engine.h",
+      "random_generator.cc",
+      "random_generator.h",
+      "random_generator_engine.cc",
+      "random_generator_engine.h",
+      "shuffle_transform.cc",
+      "shuffle_transform.h",
+      "tint_common_fuzzer.cc",
+      "tint_common_fuzzer.h",
+      "tint_reader_writer_fuzzer.h",
+      "transform_builder.h",
+    ]
+  }
+
+  source_set("tint_fuzzer_common_with_init_src") {
+    public_deps = [ ":tint_fuzzer_common_src" ]
+
+    sources = [
+      "cli.cc",
+      "cli.h",
+      "fuzzer_init.cc",
+      "fuzzer_init.h",
+    ]
+  }
+
+  if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
+    fuzzer_test("tint_ast_clone_fuzzer") {
+      sources = [ "tint_ast_clone_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_ast_wgsl_writer_fuzzer") {
+      sources = [ "tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc" ]
+      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
+      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_regex_wgsl_writer_fuzzer") {
+      sources = [ "tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc" ]
+      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
+      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_wgsl_reader_wgsl_writer_fuzzer") {
+      sources = [ "tint_wgsl_reader_wgsl_writer_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+  }
+
+  if (tint_build_wgsl_reader && tint_build_spv_writer) {
+    fuzzer_test("tint_all_transforms_fuzzer") {
+      sources = [ "tint_all_transforms_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_ast_spv_writer_fuzzer") {
+      sources = [ "tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc" ]
+      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
+      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_binding_remapper_fuzzer") {
+      sources = [ "tint_binding_remapper_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_first_index_offset_fuzzer") {
+      sources = [ "tint_first_index_offset_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_regex_spv_writer_fuzzer") {
+      sources = [ "tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc" ]
+      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
+      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_renamer_fuzzer") {
+      sources = [ "tint_renamer_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_robustness_fuzzer") {
+      sources = [ "tint_robustness_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_single_entry_point_fuzzer") {
+      sources = [ "tint_single_entry_point_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_vertex_pulling_fuzzer") {
+      sources = [ "tint_vertex_pulling_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") {
+      sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+  }
+
+  if (tint_build_wgsl_reader && tint_build_hlsl_writer) {
+    fuzzer_test("tint_ast_hlsl_writer_fuzzer") {
+      sources = [ "tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc" ]
+      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
+      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_regex_hlsl_writer_fuzzer") {
+      sources = [ "tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc" ]
+      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
+      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_wgsl_reader_hlsl_writer_fuzzer") {
+      sources = [ "tint_wgsl_reader_hlsl_writer_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+  }
+
+  if (tint_build_wgsl_reader && tint_build_msl_writer) {
+    fuzzer_test("tint_ast_msl_writer_fuzzer") {
+      sources = [ "tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc" ]
+      deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ]
+      libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_regex_msl_writer_fuzzer") {
+      sources = [ "tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc" ]
+      deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ]
+      libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
+    fuzzer_test("tint_wgsl_reader_msl_writer_fuzzer") {
+      sources = [ "tint_wgsl_reader_msl_writer_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+  }
+
+  if (tint_build_wgsl_reader && tint_build_hlsl_writer &&
+      tint_build_msl_writer && tint_build_spv_writer &&
+      tint_build_wgsl_writer) {
+    executable("tint_black_box_fuzz_target") {
+      sources = [ "tint_black_box_fuzz_target.cc" ]
+      deps = [ ":tint_fuzzer_common_src" ]
+    }
+  }
+
+  group("fuzzers") {
+    testonly = true
+    deps = []
+
+    if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
+      deps += [
+        ":tint_ast_clone_fuzzer",
+        ":tint_ast_wgsl_writer_fuzzer",
+        ":tint_regex_wgsl_writer_fuzzer",
+        ":tint_wgsl_reader_wgsl_writer_fuzzer",
+      ]
+    }
+    if (tint_build_wgsl_reader && tint_build_spv_writer) {
+      deps += [
+        ":tint_all_transforms_fuzzer",
+        ":tint_ast_spv_writer_fuzzer",
+        ":tint_binding_remapper_fuzzer",
+        ":tint_first_index_offset_fuzzer",
+        ":tint_regex_spv_writer_fuzzer",
+        ":tint_renamer_fuzzer",
+        ":tint_robustness_fuzzer",
+        ":tint_single_entry_point_fuzzer",
+        ":tint_vertex_pulling_fuzzer",
+        ":tint_wgsl_reader_spv_writer_fuzzer",
+      ]
+    }
+    if (tint_build_wgsl_reader && tint_build_hlsl_writer) {
+      deps += [
+        ":tint_ast_hlsl_writer_fuzzer",
+        ":tint_regex_hlsl_writer_fuzzer",
+        ":tint_wgsl_reader_hlsl_writer_fuzzer",
+      ]
+    }
+    if (tint_build_wgsl_reader && tint_build_msl_writer) {
+      deps += [
+        ":tint_ast_msl_writer_fuzzer",
+        ":tint_regex_msl_writer_fuzzer",
+        ":tint_wgsl_reader_msl_writer_fuzzer",
+      ]
+    }
+    if (tint_build_wgsl_reader && tint_build_hlsl_writer &&
+        tint_build_msl_writer && tint_build_spv_writer &&
+        tint_build_wgsl_writer) {
+      deps += [ ":tint_black_box_fuzz_target" ]
+    }
+  }
+} else {
+  group("fuzzers") {
+  }
+}
diff --git a/fuzzers/CMakeLists.txt b/src/tint/fuzzers/CMakeLists.txt
similarity index 100%
rename from fuzzers/CMakeLists.txt
rename to src/tint/fuzzers/CMakeLists.txt
diff --git a/src/tint/fuzzers/cli.cc b/src/tint/fuzzers/cli.cc
new file mode 100644
index 0000000..2a0b814
--- /dev/null
+++ b/src/tint/fuzzers/cli.cc
@@ -0,0 +1,116 @@
+// 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/fuzzers/cli.h"
+
+#include <cstring>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace tint {
+namespace fuzzers {
+namespace {
+
+const char* const kHelpMessage = R"(
+This is a fuzzer for the Tint compiler that works by mutating the AST.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+  -tint_dump_input=
+                       If `true`, the fuzzer will dump input data to a file with
+                       name tint_input_<hash>.spv/wgsl, where the hash is the hash
+                       of the input data.
+
+  -tint_help
+                       Show this message. Note that there is also a -help=1
+                       parameter that will display libfuzzer's help message.
+
+  -tint_enforce_validity=
+                       If `true`, the fuzzer will enforce that Tint does not
+                       generate invalid shaders. Currently `false` by default
+                       since options provided by the fuzzer are not guaranteed
+                       to be correct.
+                       See https://bugs.chromium.org/p/tint/issues/detail?id=1356
+)";
+
+[[noreturn]] void InvalidParam(const std::string& param) {
+  std::cout << "Invalid value for " << param << std::endl;
+  std::cout << kHelpMessage << std::endl;
+  exit(1);
+}
+
+bool ParseBool(const std::string& value, bool* out) {
+  if (value.compare("true") == 0) {
+    *out = true;
+  } else if (value.compare("false") == 0) {
+    *out = false;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+CliParams ParseCliParams(int* argc, char** argv) {
+  CliParams cli_params;
+  auto help = false;
+
+  for (int i = *argc - 1; i > 0; --i) {
+    std::string param(argv[i]);
+    auto recognized_parameter = true;
+
+    if (std::string::npos != param.find("-tint_dump_input=")) {
+      if (!ParseBool(param.substr(std::string("-tint_dump_input=").length()),
+                     &cli_params.dump_input)) {
+        InvalidParam(param);
+      }
+    } else if (std::string::npos != param.find("-tint_help")) {
+      help = true;
+    } else if (std::string::npos != param.find("-tint_enforce_validity=")) {
+      if (!ParseBool(
+              param.substr(std::string("-tint_enforce_validity=").length()),
+              &cli_params.enforce_validity)) {
+        InvalidParam(param);
+      }
+    } else {
+      recognized_parameter = false;
+    }
+
+    if (recognized_parameter) {
+      // Remove the recognized parameter from the list of all parameters by
+      // swapping it with the last one. This will suppress warnings in the
+      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
+      // that all user-defined parameters start with two dashes. However, we are
+      // forced to use a single one to make the fuzzer compatible with the
+      // ClusterFuzz.
+      std::swap(argv[i], argv[*argc - 1]);
+      *argc -= 1;
+    }
+  }
+
+  if (help) {
+    std::cout << kHelpMessage << std::endl;
+    exit(0);
+  }
+
+  return cli_params;
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/cli.h b/src/tint/fuzzers/cli.h
new file mode 100644
index 0000000..02ca3db
--- /dev/null
+++ b/src/tint/fuzzers/cli.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_CLI_H_
+#define SRC_TINT_FUZZERS_CLI_H_
+
+#include <cstdint>
+
+namespace tint {
+namespace fuzzers {
+
+/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
+/// help message
+struct CliParams {
+  /// Log contents of input shader
+  bool dump_input = false;
+  /// Throw error if shader becomes invalid during run
+  bool enforce_validity = false;
+};
+
+/// @brief Parses CLI parameters.
+///
+/// This function will exit the process with non-zero return code if some
+/// parameters are invalid. This function will remove recognized parameters from
+/// `argv` and adjust `argc` accordingly.
+///
+/// @param argc - the total number of parameters.
+/// @param argv - array of all CLI parameters.
+/// @return parsed parameters.
+CliParams ParseCliParams(int* argc, char** argv);
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_CLI_H_
diff --git a/src/tint/fuzzers/data_builder.h b/src/tint/fuzzers/data_builder.h
new file mode 100644
index 0000000..31a1a98
--- /dev/null
+++ b/src/tint/fuzzers/data_builder.h
@@ -0,0 +1,245 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_DATA_BUILDER_H_
+#define SRC_TINT_FUZZERS_DATA_BUILDER_H_
+
+#include <cassert>
+#include <functional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/writer/hlsl/generator.h"
+#include "src/tint/writer/msl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Builder for generic pseudo-random data
+class DataBuilder {
+ public:
+  /// @brief Initializes the internal engine using a seed value
+  /// @param seed - seed value passed to engine
+  explicit DataBuilder(uint64_t seed) : generator_(seed) {}
+
+  /// @brief Initializes the internal engine using seed data
+  /// @param data - data fuzzer to calculate seed from
+  /// @param size - size of data buffer
+  explicit DataBuilder(const uint8_t* data, size_t size)
+      : generator_(RandomGenerator::CalculateSeed(data, size)) {
+    assert(data != nullptr && "|data| must be !nullptr");
+  }
+
+  /// Destructor
+  ~DataBuilder() = default;
+
+  /// Move Constructor
+  DataBuilder(DataBuilder&&) = default;
+
+  /// Generate pseudo-random data of a specific type
+  /// @tparam T - type of data to produce
+  /// @returns pseudo-random data of type T
+  template <typename T>
+  T build() {
+    return BuildImpl<T>::impl(this);
+  }
+
+  /// Generate pseudo-random data of a specific type in a vector
+  /// @tparam T - data type held vector
+  /// @returns pseudo-random data of type std::vector<T>
+  template <typename T>
+  std::vector<T> vector() {
+    auto count = build<uint8_t>();
+    std::vector<T> out(count);
+    for (uint8_t i = 0; i < count; i++) {
+      out[i] = build<T>();
+    }
+    return out;
+  }
+
+  /// Generate complex pseudo-random data of a specific type in a vector
+  /// @tparam T - data type held vector
+  /// @tparam Callback - callback that takes in a DataBuilder* and returns a T
+  /// @param generate - callback for generating each instance of T
+  /// @returns pseudo-random data of type std::vector<T>
+  template <typename T, typename Callback>
+  std::vector<T> vector(Callback generate) {
+    auto count = build<uint8_t>();
+    std::vector<T> out(count);
+    for (size_t i = 0; i < count; i++) {
+      out[i] = generate(this);
+    }
+    return out;
+  }
+
+  /// Generate an pseudo-random entry to a enum class.
+  /// Assumes enum is tightly packed starting at 0.
+  /// @tparam T - type of enum class
+  /// @param count - number of entries in enum class
+  /// @returns a random enum class entry
+  template <typename T>
+  T enum_class(uint32_t count) {
+    return static_cast<T>(generator_.Get4Bytes() % count);
+  }
+
+ private:
+  RandomGenerator generator_;
+
+  // Disallow copy & assign
+  DataBuilder(const DataBuilder&) = delete;
+  DataBuilder& operator=(const DataBuilder&) = delete;
+
+  /// Get N bytes of pseudo-random data
+  /// @param out - pointer to location to save data
+  /// @param n - number of bytes to get
+  void build(void* out, size_t n) {
+    assert(out != nullptr && "|out| cannot be nullptr");
+    assert(n > 0 && "|n| must be > 0");
+
+    generator_.GetNBytes(reinterpret_cast<uint8_t*>(out), n);
+  }
+
+  /// Generate pseudo-random data of a specific type into an output var
+  /// @tparam T - type of data to produce
+  /// @param out - output var to generate into
+  template <typename T>
+  void build(T& out) {
+    out = build<T>();
+  }
+
+  /// Implementation of ::build<T>()
+  /// @tparam T - type of data to produce
+  template <typename T>
+  struct BuildImpl {
+    /// Generate a pseudo-random variable of type T
+    /// @param b - data builder to use
+    /// @returns a variable of type T filled with pseudo-random data
+    static T impl(DataBuilder* b) {
+      T out{};
+      b->build(&out, sizeof(T));
+      return out;
+    }
+  };
+
+  /// Specialization for std::string
+  template <>
+  struct BuildImpl<std::string> {
+    /// Generate a pseudo-random string
+    /// @param b - data builder to use
+    /// @returns a string filled with pseudo-random data
+    static std::string impl(DataBuilder* b) {
+      auto count = b->build<uint8_t>();
+      if (count == 0) {
+        return "";
+      }
+      std::vector<uint8_t> source(count);
+      b->build(source.data(), count);
+      return {source.begin(), source.end()};
+    }
+  };
+
+  /// Specialization for bool
+  template <>
+  struct BuildImpl<bool> {
+    /// Generate a pseudo-random bool
+    /// @param b - data builder to use
+    /// @returns a boolean with even odds of being true or false
+    static bool impl(DataBuilder* b) { return b->generator_.GetBool(); }
+  };
+
+  /// Specialization for writer::msl::Options
+  template <>
+  struct BuildImpl<writer::msl::Options> {
+    /// Generate a pseudo-random writer::msl::Options struct
+    /// @param b - data builder to use
+    /// @returns writer::msl::Options filled with pseudo-random data
+    static writer::msl::Options impl(DataBuilder* b) {
+      writer::msl::Options out{};
+      b->build(out.buffer_size_ubo_index);
+      b->build(out.fixed_sample_mask);
+      b->build(out.emit_vertex_point_size);
+      b->build(out.disable_workgroup_init);
+      b->build(out.array_length_from_uniform);
+      return out;
+    }
+  };
+
+  /// Specialization for writer::hlsl::Options
+  template <>
+  struct BuildImpl<writer::hlsl::Options> {
+    /// Generate a pseudo-random writer::hlsl::Options struct
+    /// @param b - data builder to use
+    /// @returns writer::hlsl::Options filled with pseudo-random data
+    static writer::hlsl::Options impl(DataBuilder* b) {
+      writer::hlsl::Options out{};
+      b->build(out.root_constant_binding_point);
+      b->build(out.disable_workgroup_init);
+      b->build(out.array_length_from_uniform);
+      return out;
+    }
+  };
+
+  /// Specialization for writer::spirv::Options
+  template <>
+  struct BuildImpl<writer::spirv::Options> {
+    /// Generate a pseudo-random writer::spirv::Options struct
+    /// @param b - data builder to use
+    /// @returns writer::spirv::Options filled with pseudo-random data
+    static writer::spirv::Options impl(DataBuilder* b) {
+      writer::spirv::Options out{};
+      b->build(out.emit_vertex_point_size);
+      b->build(out.disable_workgroup_init);
+      return out;
+    }
+  };
+
+  /// Specialization for writer::ArrayLengthFromUniformOptions
+  template <>
+  struct BuildImpl<writer::ArrayLengthFromUniformOptions> {
+    /// Generate a pseudo-random writer::ArrayLengthFromUniformOptions struct
+    /// @param b - data builder to use
+    /// @returns writer::ArrayLengthFromUniformOptions filled with pseudo-random
+    /// data
+    static writer::ArrayLengthFromUniformOptions impl(DataBuilder* b) {
+      writer::ArrayLengthFromUniformOptions out{};
+      b->build(out.ubo_binding);
+      b->build(out.bindpoint_to_size_index);
+      return out;
+    }
+  };
+
+  /// Specialization for std::unordered_map<K, V>
+  template <typename K, typename V>
+  struct BuildImpl<std::unordered_map<K, V>> {
+    /// Generate a pseudo-random std::unordered_map<K, V>
+    /// @param b - data builder to use
+    /// @returns std::unordered_map<K, V> filled with
+    /// pseudo-random data
+    static std::unordered_map<K, V> impl(DataBuilder* b) {
+      std::unordered_map<K, V> out;
+      uint8_t count = b->build<uint8_t>();
+      for (uint8_t i = 0; i < count; ++i) {
+        out.emplace(b->build<K>(), b->build<V>());
+      }
+      return out;
+    }
+  };
+};
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_DATA_BUILDER_H_
diff --git a/fuzzers/dictionary.txt b/src/tint/fuzzers/dictionary.txt
similarity index 100%
rename from fuzzers/dictionary.txt
rename to src/tint/fuzzers/dictionary.txt
diff --git a/src/tint/fuzzers/fuzzer_init.cc b/src/tint/fuzzers/fuzzer_init.cc
new file mode 100644
index 0000000..ac9a4cf
--- /dev/null
+++ b/src/tint/fuzzers/fuzzer_init.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/cli.h"
+
+namespace tint {
+namespace fuzzers {
+
+namespace {
+CliParams cli_params;
+}
+
+const CliParams& GetCliParams() {
+  return cli_params;
+}
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  cli_params = ParseCliParams(argc, *argv);
+  return 0;
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/fuzzer_init.h b/src/tint/fuzzers/fuzzer_init.h
new file mode 100644
index 0000000..b81a850
--- /dev/null
+++ b/src/tint/fuzzers/fuzzer_init.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_FUZZER_INIT_H_
+#define SRC_TINT_FUZZERS_FUZZER_INIT_H_
+
+#include "src/tint/fuzzers/cli.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Returns the common CliParams parsed and populated by LLVMFuzzerInitialize()
+const CliParams& GetCliParams();
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_FUZZER_INIT_H_
diff --git a/src/tint/fuzzers/generate_spirv_corpus.py b/src/tint/fuzzers/generate_spirv_corpus.py
new file mode 100644
index 0000000..c608901
--- /dev/null
+++ b/src/tint/fuzzers/generate_spirv_corpus.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+
+# 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.
+
+# Collect all .spvasm files under a given directory, assemble them using
+# spirv-as, and emit the assembled binaries to a given corpus directory,
+# flattening their file names by replacing path separators with underscores.
+# If the output directory already exists, it will be deleted and re-created.
+# Files ending with ".expected.spvasm" are skipped.
+#
+# The intended use of this script is to generate a corpus of SPIR-V
+# binaries for fuzzing.
+#
+# Usage:
+#    generate_spirv_corpus.py <input_dir> <corpus_dir> <path to spirv-as>
+
+import os
+import pathlib
+import shutil
+import subprocess
+import sys
+
+
+def list_spvasm_files(root_search_dir):
+    for root, folders, files in os.walk(root_search_dir):
+        for filename in folders + files:
+            if pathlib.Path(filename).suffix == ".spvasm":
+                yield os.path.join(root, filename)
+
+
+def main():
+    if len(sys.argv) != 4:
+        print("Usage: " + sys.argv[0] +
+              " <input dir> <output dir> <spirv-as path>")
+        return 1
+    input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep))
+    corpus_dir: str = os.path.abspath(sys.argv[2])
+    spirv_as_path: str = os.path.abspath(sys.argv[3])
+    if os.path.exists(corpus_dir):
+        shutil.rmtree(corpus_dir)
+    os.makedirs(corpus_dir)
+
+    # It might be that some of the attempts to convert SPIR-V assembly shaders
+    # into SPIR-V binaries go wrong. It is sensible to tolerate a small number
+    # of such errors, to avoid fuzzer preparation failing due to bugs in
+    # spirv-as. But it is important to know when a large number of failures
+    # occur, in case something is more deeply wrong.
+    num_errors = 0
+    max_tolerated_errors = 10
+    logged_errors = ""
+
+    for in_file in list_spvasm_files(input_dir):
+        if in_file.endswith(".expected.spvasm"):
+            continue
+        out_file = os.path.splitext(
+            corpus_dir + os.sep +
+            in_file[len(input_dir) + 1:].replace(os.sep, '_'))[0] + ".spv"
+        cmd = [
+            spirv_as_path, "--target-env", "spv1.3", in_file, "-o", out_file
+        ]
+        proc = subprocess.Popen(cmd,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        stdout, stderr = proc.communicate()
+        if proc.returncode != 0:
+            num_errors += 1
+            logged_errors += "Error running " + " ".join(
+                cmd) + ": " + stdout.decode('utf-8') + stderr.decode('utf-8')
+
+    if num_errors > max_tolerated_errors:
+        print("Too many (" + str(num_errors) +
+              ") errors occured while generating the SPIR-V corpus.")
+        print(logged_errors)
+        return 1
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/src/tint/fuzzers/generate_wgsl_corpus.py b/src/tint/fuzzers/generate_wgsl_corpus.py
new file mode 100644
index 0000000..65c564f
--- /dev/null
+++ b/src/tint/fuzzers/generate_wgsl_corpus.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+# 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.
+
+# Collect all .wgsl files under a given directory and copy them to a given
+# corpus directory, flattening their file names by replacing path
+# separators with underscores. If the output directory already exists, it
+# will be deleted and re-created. Files ending with ".expected.spvasm" are
+# skipped.
+#
+# The intended use of this script is to generate a corpus of WGSL shaders
+# for fuzzing.
+#
+# Usage:
+#    generate_wgsl_corpus.py <input_dir> <corpus_dir>
+
+import os
+import pathlib
+import shutil
+import sys
+
+
+def list_wgsl_files(root_search_dir):
+    for root, folders, files in os.walk(root_search_dir):
+        for filename in folders + files:
+            if pathlib.Path(filename).suffix == '.wgsl':
+                yield os.path.join(root, filename)
+
+
+def main():
+    if len(sys.argv) != 3:
+        print("Usage: " + sys.argv[0] + " <input dir> <output dir>")
+        return 1
+    input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep))
+    corpus_dir: str = os.path.abspath(sys.argv[2])
+    if os.path.exists(corpus_dir):
+        shutil.rmtree(corpus_dir)
+    os.makedirs(corpus_dir)
+    for in_file in list_wgsl_files(input_dir):
+        if in_file.endswith(".expected.wgsl"):
+            continue
+        out_file = in_file[len(input_dir) + 1:].replace(os.sep, '_')
+        shutil.copy(in_file, corpus_dir + os.sep + out_file)
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/src/tint/fuzzers/mersenne_twister_engine.cc b/src/tint/fuzzers/mersenne_twister_engine.cc
new file mode 100644
index 0000000..5acba2b
--- /dev/null
+++ b/src/tint/fuzzers/mersenne_twister_engine.cc
@@ -0,0 +1,59 @@
+// 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/fuzzers/mersenne_twister_engine.h"
+
+#include <algorithm>
+#include <cassert>
+
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace fuzzers {
+
+namespace {
+
+/// Generate integer from uniform distribution
+/// @tparam I - integer type
+/// @param engine - random number engine to use
+/// @param lower - Lower bound of integer generated
+/// @param upper - Upper bound of integer generated
+/// @returns i, where lower <= i < upper
+template <typename I>
+I RandomInteger(std::mt19937_64* engine, I lower, I upper) {
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return std::uniform_int_distribution<I>(lower, upper - 1)(*engine);
+}
+
+}  // namespace
+
+MersenneTwisterEngine::MersenneTwisterEngine(uint64_t seed) : engine_(seed) {}
+
+uint32_t MersenneTwisterEngine::RandomUInt32(uint32_t lower, uint32_t upper) {
+  return RandomInteger(&engine_, lower, upper);
+}
+
+uint64_t MersenneTwisterEngine::RandomUInt64(uint64_t lower, uint64_t upper) {
+  return RandomInteger(&engine_, lower, upper);
+}
+
+void MersenneTwisterEngine::RandomNBytes(uint8_t* dest, size_t n) {
+  assert(dest && "|dest| must not be nullptr");
+  std::generate(
+      dest, dest + n,
+      std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_));
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/mersenne_twister_engine.h b/src/tint/fuzzers/mersenne_twister_engine.h
new file mode 100644
index 0000000..c482953
--- /dev/null
+++ b/src/tint/fuzzers/mersenne_twister_engine.h
@@ -0,0 +1,61 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_
+#define SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_
+
+#include <random>
+
+#include "src/tint/fuzzers/random_generator_engine.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Standard MT based random number generation
+class MersenneTwisterEngine : public RandomGeneratorEngine {
+ public:
+  /// @brief Initializes using provided seed
+  /// @param seed - seed value to use
+  explicit MersenneTwisterEngine(uint64_t seed);
+  ~MersenneTwisterEngine() override = default;
+
+  /// Generate random uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint32_t RandomUInt32(uint32_t lower, uint32_t upper) override;
+
+  /// Get random uint64_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint64_t RandomUInt64(uint64_t lower, uint64_t upper) override;
+
+  /// Get N bytes of pseudo-random data
+  /// @param dest - memory location to store data
+  /// @param n - number of bytes of data to generate
+  void RandomNBytes(uint8_t* dest, size_t n) override;
+
+ private:
+  // Disallow copy & assign
+  MersenneTwisterEngine(const MersenneTwisterEngine&) = delete;
+  MersenneTwisterEngine& operator=(const MersenneTwisterEngine&) = delete;
+
+  std::mt19937_64 engine_;
+};  // class MersenneTwisterEngine
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_
diff --git a/src/tint/fuzzers/random_generator.cc b/src/tint/fuzzers/random_generator.cc
new file mode 100644
index 0000000..6b3c98a
--- /dev/null
+++ b/src/tint/fuzzers/random_generator.cc
@@ -0,0 +1,124 @@
+// 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/fuzzers/random_generator.h"
+
+#include <algorithm>
+#include <cassert>
+#include <utility>
+
+#include "src/tint/fuzzers/mersenne_twister_engine.h"
+#include "src/tint/fuzzers/random_generator_engine.h"
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace fuzzers {
+
+namespace {
+
+/// Calculate the hash for the contents of a c-style data buffer
+/// This is intentionally not implemented as a generic override of HashCombine
+/// in "src/tint/utils/hash.h", because it conflicts with the vardiac override
+/// for the case where a pointer and an integer are being hashed.
+/// @param data - pointer to buffer to be hashed
+/// @param size - number of elements in buffer
+/// @returns hash of the data in the buffer
+size_t HashBuffer(const uint8_t* data, const size_t size) {
+  size_t hash = 102931;
+  utils::HashCombine(&hash, size);
+  for (size_t i = 0; i < size; i++) {
+    utils::HashCombine(&hash, data[i]);
+  }
+  return hash;
+}
+
+}  // namespace
+
+RandomGenerator::RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine)
+    : engine_(std::move(engine)) {}
+
+RandomGenerator::RandomGenerator(uint64_t seed)
+    : RandomGenerator(std::make_unique<MersenneTwisterEngine>(seed)) {}
+
+uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) {
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return engine_->RandomUInt32(lower, upper);
+}
+
+uint32_t RandomGenerator::GetUInt32(uint32_t bound) {
+  assert(bound > 0 && "|bound| must be greater than 0");
+  return engine_->RandomUInt32(0u, bound);
+}
+
+uint64_t RandomGenerator::GetUInt64(uint64_t lower, uint64_t upper) {
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return engine_->RandomUInt64(lower, upper);
+}
+
+uint64_t RandomGenerator::GetUInt64(uint64_t bound) {
+  assert(bound > 0 && "|bound| must be greater than 0");
+  return engine_->RandomUInt64(static_cast<uint64_t>(0), bound);
+}
+
+uint8_t RandomGenerator::GetByte() {
+  uint8_t result;
+  engine_->RandomNBytes(&result, 1);
+  return result;
+}
+
+uint32_t RandomGenerator::Get4Bytes() {
+  uint32_t result;
+  engine_->RandomNBytes(reinterpret_cast<uint8_t*>(&result), 4);
+  return result;
+}
+
+void RandomGenerator::GetNBytes(uint8_t* dest, size_t n) {
+  assert(dest && "|dest| must not be nullptr");
+  engine_->RandomNBytes(dest, n);
+}
+
+bool RandomGenerator::GetBool() {
+  return engine_->RandomUInt32(0u, 2u);
+}
+
+bool RandomGenerator::GetWeightedBool(uint32_t percentage) {
+  static const uint32_t kMaxPercentage = 100;
+  assert(percentage <= kMaxPercentage &&
+         "|percentage| needs to be within [0, 100]");
+  return engine_->RandomUInt32(0u, kMaxPercentage) < percentage;
+}
+
+uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) {
+  assert(data != nullptr && "|data| must be !nullptr");
+
+  // Number of bytes we want to skip at the start of data for the hash.
+  // Fewer bytes may be skipped when `size` is small.
+  // Has lower precedence than kHashDesiredMinBytes.
+  static const int64_t kHashDesiredLeadingSkipBytes = 5;
+  // Minimum number of bytes we want to use in the hash.
+  // Used for short buffers.
+  static const int64_t kHashDesiredMinBytes = 4;
+  // Maximum number of bytes we want to use in the hash.
+  static const int64_t kHashDesiredMaxBytes = 32;
+  auto size_i64 = static_cast<int64_t>(size);
+  auto hash_begin_i64 =
+      std::min(kHashDesiredLeadingSkipBytes,
+               std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0));
+  auto hash_end_i64 = std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64);
+  auto hash_begin = static_cast<size_t>(hash_begin_i64);
+  auto hash_size = static_cast<size_t>(hash_end_i64) - hash_begin;
+  return HashBuffer(data + hash_begin, hash_size);
+}
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/random_generator.h b/src/tint/fuzzers/random_generator.h
new file mode 100644
index 0000000..bb9a46f
--- /dev/null
+++ b/src/tint/fuzzers/random_generator.h
@@ -0,0 +1,118 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_
+#define SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_
+
+#include <memory>
+#include <random>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator_engine.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Pseudo random generator utility class for fuzzing
+class RandomGenerator {
+ public:
+  /// @brief Initializes using provided engine
+  /// @param engine - engine implementation to use
+  explicit RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine);
+
+  /// @brief Creates a MersenneTwisterEngine and initializes using that
+  /// @param seed - seed value to use for engine
+  explicit RandomGenerator(uint64_t seed);
+
+  /// Destructor
+  ~RandomGenerator() = default;
+
+  /// Move Constructor
+  RandomGenerator(RandomGenerator&&) = default;
+
+  /// Get uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint32_t GetUInt32(uint32_t lower, uint32_t upper);
+
+  /// Get uint32_t value from uniform distribution.
+  /// @param bound - Upper bound of integer generated
+  /// @returns i, where 0 <= i < bound
+  uint32_t GetUInt32(uint32_t bound);
+
+  /// Get uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint64_t GetUInt64(uint64_t lower, uint64_t upper);
+
+  /// Get uint64_t value from uniform distribution.
+  /// @param bound - Upper bound of integer generated
+  /// @returns i, where 0 <= i < bound
+  uint64_t GetUInt64(uint64_t bound);
+
+  /// Get 1 byte of pseudo-random data
+  /// Should be more efficient then calling GetNBytes(1);
+  /// @returns 1-byte of random data
+  uint8_t GetByte();
+
+  /// Get 4 bytes of pseudo-random data
+  /// Should be more efficient then calling GetNBytes(4);
+  /// @returns 4-bytes of random data
+  uint32_t Get4Bytes();
+
+  /// Get N bytes of pseudo-random data
+  /// @param dest - memory location to store data
+  /// @param n - number of bytes of data to get
+  void GetNBytes(uint8_t* dest, size_t n);
+
+  /// Get random bool with even odds
+  /// @returns true 50% of the time and false %50 of time.
+  bool GetBool();
+
+  /// Get random bool with weighted odds
+  /// @param percentage - likelihood of true being returned
+  /// @returns true |percentage|% of the time, and false (100 - |percentage|)%
+  /// of the time.
+  bool GetWeightedBool(uint32_t percentage);
+
+  /// Returns a randomly-chosen element from vector v.
+  /// @param v - the vector from which the random element will be selected.
+  /// @return a random element of vector v.
+  template <typename T>
+  inline T GetRandomElement(const std::vector<T>& v) {
+    return v[GetUInt64(0, v.size())];
+  }
+
+  /// Calculate a seed value based on a blob of data.
+  /// Currently hashes bytes near the front of the buffer, after skipping N
+  /// bytes.
+  /// @param data - pointer to data to base calculation off of, must be !nullptr
+  /// @param size - number of elements in |data|, must be > 0
+  /// @returns calculated seed value
+  static uint64_t CalculateSeed(const uint8_t* data, size_t size);
+
+ private:
+  // Disallow copy & assign
+  RandomGenerator(const RandomGenerator&) = delete;
+  RandomGenerator& operator=(const RandomGenerator&) = delete;
+
+  std::unique_ptr<RandomGeneratorEngine> engine_;
+};  // class RandomGenerator
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_
diff --git a/src/tint/fuzzers/random_generator_engine.cc b/src/tint/fuzzers/random_generator_engine.cc
new file mode 100644
index 0000000..2e861e3
--- /dev/null
+++ b/src/tint/fuzzers/random_generator_engine.cc
@@ -0,0 +1,26 @@
+// 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/fuzzers/random_generator_engine.h"
+
+namespace tint {
+namespace fuzzers {
+
+// Not in header to avoid weak vtable warnings from clang
+RandomGeneratorEngine::RandomGeneratorEngine() = default;
+RandomGeneratorEngine::~RandomGeneratorEngine() = default;
+RandomGeneratorEngine::RandomGeneratorEngine(RandomGeneratorEngine&&) = default;
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/random_generator_engine.h b/src/tint/fuzzers/random_generator_engine.h
new file mode 100644
index 0000000..eb9e716
--- /dev/null
+++ b/src/tint/fuzzers/random_generator_engine.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_
+#define SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_
+
+#include <memory>
+#include <random>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+
+/// Wrapper interface around STL random number engine
+class RandomGeneratorEngine {
+ public:
+  /// Constructor
+  RandomGeneratorEngine();
+
+  /// Destructor
+  virtual ~RandomGeneratorEngine();
+
+  /// Move Constructor
+  RandomGeneratorEngine(RandomGeneratorEngine&&);
+
+  /// Generates a random uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  virtual uint32_t RandomUInt32(uint32_t lower, uint32_t upper) = 0;
+
+  /// Generates a random uint64_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  virtual uint64_t RandomUInt64(uint64_t lower, uint64_t upper) = 0;
+
+  /// Generates N bytes of pseudo-random data
+  /// @param dest - memory location to store data
+  /// @param n - number of bytes of data to generate
+  virtual void RandomNBytes(uint8_t* dest, size_t n) = 0;
+
+ private:
+  // Disallow copy & assign
+  RandomGeneratorEngine(const RandomGeneratorEngine&) = delete;
+  RandomGeneratorEngine& operator=(const RandomGeneratorEngine&) = delete;
+};  // class RandomGeneratorEngine
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_
diff --git a/src/tint/fuzzers/random_generator_test.cc b/src/tint/fuzzers/random_generator_test.cc
new file mode 100644
index 0000000..182e7ab
--- /dev/null
+++ b/src/tint/fuzzers/random_generator_test.cc
@@ -0,0 +1,202 @@
+// 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/fuzzers/random_generator.h"
+
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/fuzzers/mersenne_twister_engine.h"
+
+namespace tint {
+namespace fuzzers {
+namespace {
+
+/// Implementation of RandomGeneratorEngine that just returns a stream of
+/// monotonically increasing numbers.
+class MonotonicEngine : public RandomGeneratorEngine {
+ public:
+  uint32_t RandomUInt32(uint32_t, uint32_t) override { return next_++; }
+
+  uint64_t RandomUInt64(uint64_t, uint64_t) override { return next_++; }
+
+  void RandomNBytes(uint8_t*, size_t) override {
+    assert(false && "MonotonicDelegate does not implement RandomNBytes");
+  }
+
+ private:
+  uint32_t next_ = 0;
+};
+
+class RandomGeneratorTest : public testing::Test {
+ public:
+  void SetUp() override { rng_ = std::make_unique<RandomGenerator>(0); }
+
+  void TearDown() override {}
+
+ protected:
+  std::unique_ptr<RandomGenerator> rng_;
+};
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetUInt32ReversedBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(10, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32EmptyBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(5, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32ZeroBoundCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(0u), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetUInt32SingularReturnsOneValue) {
+  {
+    uint32_t result = rng_->GetUInt32(5u, 6u);
+    ASSERT_EQ(5u, result);
+  }
+  {
+    uint32_t result = rng_->GetUInt32(1u);
+    ASSERT_EQ(0u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32StaysInBounds) {
+  {
+    uint32_t result = rng_->GetUInt32(5u, 10u);
+    ASSERT_LE(5u, result);
+    ASSERT_GT(10u, result);
+  }
+  {
+    uint32_t result = rng_->GetUInt32(10u);
+    ASSERT_LE(0u, result);
+    ASSERT_GT(10u, result);
+  }
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetUInt64ReversedBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(10, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64EmptyBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(5, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64ZeroBoundCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(0u), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetUInt64SingularReturnsOneValue) {
+  {
+    uint64_t result = rng_->GetUInt64(5u, 6u);
+    ASSERT_EQ(5u, result);
+  }
+  {
+    uint64_t result = rng_->GetUInt64(1u);
+    ASSERT_EQ(0u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64StaysInBounds) {
+  {
+    uint64_t result = rng_->GetUInt64(5u, 10u);
+    ASSERT_LE(5u, result);
+    ASSERT_GT(10u, result);
+  }
+  {
+    uint64_t result = rng_->GetUInt64(10u);
+    ASSERT_LE(0u, result);
+    ASSERT_GT(10u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetByte) {
+  rng_->GetByte();
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetNBytesNullDataBufferCrashes) {
+  EXPECT_DEATH(rng_->GetNBytes(nullptr, 5), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetNBytes) {
+  std::vector<uint8_t> data;
+  for (uint32_t i = 25; i < 1000u; i = i + 25) {
+    data.resize(i);
+    rng_->GetNBytes(data.data(), data.size());
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetBool) {
+  rng_->GetBool();
+}
+
+TEST_F(RandomGeneratorTest, GetWeightedBoolZeroAlwaysFalse) {
+  ASSERT_FALSE(rng_->GetWeightedBool(0));
+}
+
+TEST_F(RandomGeneratorTest, GetWeightedBoolHundredAlwaysTrue) {
+  ASSERT_TRUE(rng_->GetWeightedBool(100));
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetWeightedBoolAboveHundredCrashes) {
+  EXPECT_DEATH(rng_->GetWeightedBool(101), ".*");
+  EXPECT_DEATH(rng_->GetWeightedBool(500), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetWeightedBool) {
+  for (uint32_t i = 0; i <= 100; i++) {
+    rng_ =
+        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
+    for (uint32_t j = 0; j <= 100; j++) {
+      if (j < i) {
+        ASSERT_TRUE(rng_->GetWeightedBool(i));
+      } else {
+        ASSERT_FALSE(rng_->GetWeightedBool(i));
+      }
+    }
+  }
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetRandomElementEmptyVectorCrashes) {
+  std::vector<uint8_t> v;
+  EXPECT_DEATH(rng_->GetRandomElement(v), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetRandomElement) {
+  std::vector<uint32_t> v;
+  for (uint32_t i = 25; i < 100u; i = i + 25) {
+    rng_ =
+        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
+    v.resize(i);
+    std::iota(v.begin(), v.end(), 0);
+    for (uint32_t j = 0; j < i; j++) {
+      EXPECT_EQ(j, rng_->GetRandomElement(v));
+    }
+  }
+}
+
+}  // namespace
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/shuffle_transform.cc b/src/tint/fuzzers/shuffle_transform.cc
new file mode 100644
index 0000000..4061104
--- /dev/null
+++ b/src/tint/fuzzers/shuffle_transform.cc
@@ -0,0 +1,38 @@
+// 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/fuzzers/shuffle_transform.h"
+
+#include <random>
+
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+ShuffleTransform::ShuffleTransform(size_t seed) : seed_(seed) {}
+
+void ShuffleTransform::Run(CloneContext& ctx,
+                           const tint::transform::DataMap&,
+                           tint::transform::DataMap&) const {
+  auto decls = ctx.src->AST().GlobalDeclarations();
+  auto rng = std::mt19937_64{seed_};
+  std::shuffle(std::begin(decls), std::end(decls), rng);
+  for (auto* decl : decls) {
+    ctx.dst->AST().AddGlobalDeclaration(ctx.Clone(decl));
+  }
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/shuffle_transform.h b/src/tint/fuzzers/shuffle_transform.h
new file mode 100644
index 0000000..4674d35
--- /dev/null
+++ b/src/tint/fuzzers/shuffle_transform.h
@@ -0,0 +1,42 @@
+// 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_FUZZERS_SHUFFLE_TRANSFORM_H_
+#define SRC_TINT_FUZZERS_SHUFFLE_TRANSFORM_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// ShuffleTransform reorders the module scope declarations into a random order
+class ShuffleTransform : public tint::transform::Transform {
+ public:
+  /// Constructor
+  /// @param seed the random seed to use for the shuffling
+  explicit ShuffleTransform(size_t seed);
+
+ protected:
+  void Run(CloneContext& ctx,
+           const tint::transform::DataMap&,
+           tint::transform::DataMap&) const override;
+
+ private:
+  size_t seed_;
+};
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_SHUFFLE_TRANSFORM_H_
diff --git a/src/tint/fuzzers/tint_all_transforms_fuzzer.cc b/src/tint/fuzzers/tint_all_transforms_fuzzer.cc
new file mode 100644
index 0000000..356a53c
--- /dev/null
+++ b/src/tint/fuzzers/tint_all_transforms_fuzzer.cc
@@ -0,0 +1,85 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  {
+    TransformBuilder tb(data, size);
+    tb.AddTransform<ShuffleTransform>();
+    tb.AddPlatformIndependentPasses();
+
+    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+    fuzzer.SetDumpInput(GetCliParams().dump_input);
+    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+    fuzzer.Run(data, size);
+  }
+
+#if TINT_BUILD_HLSL_WRITER
+  {
+    TransformBuilder tb(data, size);
+    tb.AddTransform<ShuffleTransform>();
+    tb.AddPlatformIndependentPasses();
+
+    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kHLSL);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+    fuzzer.SetDumpInput(GetCliParams().dump_input);
+    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+    fuzzer.Run(data, size);
+  }
+#endif  // TINT_BUILD_HLSL_WRITER
+
+#if TINT_BUILD_MSL_WRITER
+  {
+    TransformBuilder tb(data, size);
+    tb.AddTransform<ShuffleTransform>();
+    tb.AddPlatformIndependentPasses();
+
+    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kMSL);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+    fuzzer.SetDumpInput(GetCliParams().dump_input);
+    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+    fuzzer.Run(data, size);
+  }
+#endif  // TINT_BUILD_MSL_WRITER
+#if TINT_BUILD_SPV_WRITER
+  {
+    TransformBuilder tb(data, size);
+    tb.AddTransform<ShuffleTransform>();
+    tb.AddPlatformIndependentPasses();
+
+    fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+    fuzzer.SetDumpInput(GetCliParams().dump_input);
+    fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+    fuzzer.Run(data, size);
+  }
+#endif  // TINT_BUILD_SPV_WRITER
+
+  return 0;
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
new file mode 100644
index 0000000..5382f24
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
@@ -0,0 +1,116 @@
+// Copyright 2020 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 <iostream>
+#include <string>
+#include <unordered_set>
+
+#include "src/tint/reader/wgsl/parser_impl.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+#define ASSERT_EQ(A, B)                                  \
+  do {                                                   \
+    decltype(A) assert_a = (A);                          \
+    decltype(B) assert_b = (B);                          \
+    if (assert_a != assert_b) {                          \
+      std::cerr << "ASSERT_EQ(" #A ", " #B ") failed:\n" \
+                << #A << " was: " << assert_a << "\n"    \
+                << #B << " was: " << assert_b << "\n";   \
+      __builtin_trap();                                  \
+    }                                                    \
+  } while (false)
+
+#define ASSERT_TRUE(A)                                 \
+  do {                                                 \
+    decltype(A) assert_a = (A);                        \
+    if (!assert_a) {                                   \
+      std::cerr << "ASSERT_TRUE(" #A ") failed:\n"     \
+                << #A << " was: " << assert_a << "\n"; \
+      __builtin_trap();                                \
+    }                                                  \
+  } while (false)
+
+[[noreturn]] void TintInternalCompilerErrorReporter(
+    const tint::diag::List& diagnostics) {
+  auto printer = tint::diag::Printer::create(stderr, true);
+  tint::diag::Formatter{}.format(diagnostics, printer.get());
+  __builtin_trap();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  std::string str(reinterpret_cast<const char*>(data), size);
+
+  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+
+  tint::Source::File file("test.wgsl", str);
+
+  // Parse the wgsl, create the src program
+  tint::reader::wgsl::ParserImpl parser(&file);
+  parser.set_max_errors(1);
+  if (!parser.Parse()) {
+    return 0;
+  }
+  auto src = parser.program();
+  if (!src.IsValid()) {
+    return 0;
+  }
+
+  // Clone the src program to dst
+  tint::Program dst(src.Clone());
+
+  // Expect the printed strings to match
+  ASSERT_EQ(tint::Program::printer(&src), tint::Program::printer(&dst));
+
+  // Check that none of the AST nodes or type pointers in dst are found in src
+  std::unordered_set<const tint::ast::Node*> src_nodes;
+  for (auto* src_node : src.ASTNodes().Objects()) {
+    src_nodes.emplace(src_node);
+  }
+  std::unordered_set<const tint::sem::Type*> src_types;
+  for (auto* src_type : src.Types()) {
+    src_types.emplace(src_type);
+  }
+  for (auto* dst_node : dst.ASTNodes().Objects()) {
+    ASSERT_EQ(src_nodes.count(dst_node), 0u);
+  }
+  for (auto* dst_type : dst.Types()) {
+    ASSERT_EQ(src_types.count(dst_type), 0u);
+  }
+
+  // Regenerate the wgsl for the src program. We use this instead of the
+  // original source so that reformatting doesn't impact the final wgsl
+  // comparison.
+  std::string src_wgsl;
+  tint::writer::wgsl::Options wgsl_options;
+  {
+    auto result = tint::writer::wgsl::Generate(&src, wgsl_options);
+    ASSERT_TRUE(result.success);
+    src_wgsl = result.wgsl;
+
+    // Move the src program to a temporary that'll be dropped, so that the src
+    // program is released before we attempt to print the dst program. This
+    // guarantee that all the source program nodes and types are destructed and
+    // freed. ASAN should error if there's any remaining references in dst when
+    // we try to reconstruct the WGSL.
+    auto tmp = std::move(src);
+  }
+
+  // Print the dst program, check it matches the original source
+  auto result = tint::writer::wgsl::Generate(&dst, wgsl_options);
+  ASSERT_TRUE(result.success);
+  auto dst_wgsl = result.wgsl;
+  ASSERT_EQ(src_wgsl, dst_wgsl);
+
+  return 0;
+}
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
new file mode 100644
index 0000000..e9a7f28
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -0,0 +1,64 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("../../../../tint_overrides_with_defaults.gni")
+
+if (build_with_chromium) {
+  import("//third_party/protobuf/proto_library.gni")
+
+  proto_library("tint_ast_fuzzer_proto") {
+    sources = [ "protobufs/tint_ast_fuzzer.proto" ]
+    generate_python = false
+    use_protobuf_full = true
+  }
+
+  source_set("tint_ast_fuzzer") {
+    public_configs = [
+      "${tint_root_dir}/src/tint:tint_config",
+      "${tint_root_dir}/src/tint:tint_common_config",
+    ]
+
+    include_dirs = [ "${target_gen_dir}/../.." ]
+
+    deps = [
+      ":tint_ast_fuzzer_proto",
+      "${tint_root_dir}/src/tint/fuzzers:tint_fuzzer_common_src",
+      "//third_party/protobuf:protobuf_full",
+    ]
+
+    sources = [
+      "cli.cc",
+      "cli.h",
+      "fuzzer.cc",
+      "mutation.cc",
+      "mutation.h",
+      "mutation_finder.cc",
+      "mutation_finder.h",
+      "mutation_finders/replace_identifiers.cc",
+      "mutation_finders/replace_identifiers.h",
+      "mutations/replace_identifier.cc",
+      "mutations/replace_identifier.h",
+      "mutator.cc",
+      "mutator.h",
+      "node_id_map.cc",
+      "node_id_map.h",
+      "override_cli_params.h",
+      "probability_context.cc",
+      "probability_context.h",
+      "protobufs/tint_ast_fuzzer.h",
+      "util.h",
+    ]
+  }
+}
diff --git a/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
similarity index 100%
rename from fuzzers/tint_ast_fuzzer/CMakeLists.txt
rename to src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/cli.cc b/src/tint/fuzzers/tint_ast_fuzzer/cli.cc
new file mode 100644
index 0000000..34a7d92
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/cli.cc
@@ -0,0 +1,167 @@
+// 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/fuzzers/tint_ast_fuzzer/cli.h"
+
+#include <cstring>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+const char* const kHelpMessage = R"(
+This is a fuzzer for the Tint compiler that works by mutating the AST.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+  -tint_enable_all_mutations=
+                       If `false`, the fuzzer will only apply mutations from a
+                       randomly selected subset of mutation types. Otherwise,
+                       all mutation types will be considered. This must be one
+                       of `true` or `false` (without `). By default it's `false`.
+
+  -tint_fuzzing_target=
+                       Specifies the shading language to target during fuzzing.
+                       This must be one or a combination of `wgsl`, `spv`, `hlsl`,
+                       `msl` (without `) separated by commas. By default it's
+                       `wgsl,msl,hlsl,spv`.
+
+  -tint_help
+                       Show this message. Note that there is also a -help=1
+                       parameter that will display libfuzzer's help message.
+
+  -tint_mutation_batch_size=
+                       The number of mutations to apply in a single libfuzzer
+                       mutation session. This must be a numeric value that fits
+                       in type `uint32_t`. By default it's 5.
+)";
+
+bool HasPrefix(const char* str, const char* prefix) {
+  return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+[[noreturn]] void InvalidParam(const char* param) {
+  std::cout << "Invalid value for " << param << std::endl;
+  std::cout << kHelpMessage << std::endl;
+  exit(1);
+}
+
+bool ParseBool(const char* value, bool* out) {
+  if (!strcmp(value, "true")) {
+    *out = true;
+  } else if (!strcmp(value, "false")) {
+    *out = false;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool ParseUint32(const char* value, uint32_t* out) {
+  auto parsed = strtoul(value, nullptr, 10);
+  if (parsed > std::numeric_limits<uint32_t>::max()) {
+    return false;
+  }
+  *out = static_cast<uint32_t>(parsed);
+  return true;
+}
+
+bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
+  if (!strcmp(value, "wgsl")) {
+    *out = FuzzingTarget::kWgsl;
+  } else if (!strcmp(value, "spv")) {
+    *out = FuzzingTarget::kSpv;
+  } else if (!strcmp(value, "msl")) {
+    *out = FuzzingTarget::kMsl;
+  } else if (!strcmp(value, "hlsl")) {
+    *out = FuzzingTarget::kHlsl;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+CliParams ParseCliParams(int* argc, char** argv) {
+  CliParams cli_params;
+  auto help = false;
+
+  for (int i = *argc - 1; i > 0; --i) {
+    auto param = argv[i];
+    auto recognized_parameter = true;
+
+    if (HasPrefix(param, "-tint_enable_all_mutations=")) {
+      if (!ParseBool(param + sizeof("-tint_enable_all_mutations=") - 1,
+                     &cli_params.enable_all_mutations)) {
+        InvalidParam(param);
+      }
+    } else if (HasPrefix(param, "-tint_mutation_batch_size=")) {
+      if (!ParseUint32(param + sizeof("-tint_mutation_batch_size=") - 1,
+                       &cli_params.mutation_batch_size)) {
+        InvalidParam(param);
+      }
+    } else if (HasPrefix(param, "-tint_fuzzing_target=")) {
+      auto result = FuzzingTarget::kNone;
+
+      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
+      for (std::string value; std::getline(ss, value, ',');) {
+        auto tmp = FuzzingTarget::kNone;
+        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
+          InvalidParam(param);
+        }
+        result = result | tmp;
+      }
+
+      if (result == FuzzingTarget::kNone) {
+        InvalidParam(param);
+      }
+
+      cli_params.fuzzing_target = result;
+    } else if (!strcmp(param, "-tint_help")) {
+      help = true;
+    } else {
+      recognized_parameter = false;
+    }
+
+    if (recognized_parameter) {
+      // Remove the recognized parameter from the list of all parameters by
+      // swapping it with the last one. This will suppress warnings in the
+      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
+      // that all user-defined parameters start with two dashes. However, we are
+      // forced to use a single one to make the fuzzer compatible with the
+      // ClusterFuzz.
+      std::swap(argv[i], argv[*argc - 1]);
+      *argc -= 1;
+    }
+  }
+
+  if (help) {
+    std::cout << kHelpMessage << std::endl;
+    exit(0);
+  }
+
+  return cli_params;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/cli.h b/src/tint/fuzzers/tint_ast_fuzzer/cli.h
new file mode 100644
index 0000000..ed1bfaa
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/cli.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_
+
+#include <cstdint>
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// The backend this fuzzer will test.
+enum class FuzzingTarget {
+  kNone = 0,
+  kHlsl = 1 << 0,
+  kMsl = 1 << 1,
+  kSpv = 1 << 2,
+  kWgsl = 1 << 3,
+  kAll = kHlsl | kMsl | kSpv | kWgsl
+};
+
+inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
+/// help message
+struct CliParams {
+  /// Whether to use all mutation finders or only a randomly selected subset of
+  /// them.
+  bool enable_all_mutations = false;
+
+  /// The maximum number of mutations applied during a single mutation session
+  /// (i.e. a call to `ast_fuzzer::Mutate` function).
+  uint32_t mutation_batch_size = 5;
+
+  /// Compiler backends we want to fuzz.
+  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
+};
+
+/// @brief Parses CLI parameters.
+///
+/// This function will exit the process with non-zero return code if some
+/// parameters are invalid. This function will remove recognized parameters from
+/// `argv` and adjust `argc` accordingly.
+///
+/// @param argc - the total number of parameters.
+/// @param argv - array of all CLI parameters.
+/// @return parsed parameters.
+CliParams ParseCliParams(int* argc, char** argv);
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
new file mode 100644
index 0000000..5570e06
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
@@ -0,0 +1,134 @@
+// 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 <cstddef>
+#include <cstdint>
+
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+CliParams cli_params{};
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
+  // is invalid.
+  cli_params = ParseCliParams(argc, *argv);
+  // For some fuzz targets it is desirable to force the values of certain CLI
+  // parameters after parsing.
+  OverrideCliParams(cli_params);
+  return 0;
+}
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
+                                          size_t size,
+                                          size_t max_size,
+                                          unsigned seed) {
+  Source::File file("test.wgsl", {reinterpret_cast<char*>(data), size});
+  auto program = reader::wgsl::Parse(&file);
+  if (!program.IsValid()) {
+    std::cout << "Trying to mutate an invalid program:" << std::endl
+              << program.Diagnostics().str() << std::endl;
+    return 0;
+  }
+
+  // Run the mutator.
+  RandomGenerator generator(seed);
+  ProbabilityContext probability_context(&generator);
+  program = Mutate(std::move(program), &probability_context,
+                   cli_params.enable_all_mutations,
+                   cli_params.mutation_batch_size, nullptr);
+
+  if (!program.IsValid()) {
+    std::cout << "Mutator produced invalid WGSL:" << std::endl
+              << "  seed: " << seed << std::endl
+              << program.Diagnostics().str() << std::endl;
+    return 0;
+  }
+
+  auto result = writer::wgsl::Generate(&program, writer::wgsl::Options());
+  if (!result.success) {
+    std::cout << "Can't generate WGSL for a valid tint::Program:" << std::endl
+              << result.error << std::endl;
+    return 0;
+  }
+
+  if (result.wgsl.size() > max_size) {
+    return 0;
+  }
+
+  // No need to worry about the \0 here. The reason is that if \0 is included by
+  // developer by mistake, it will be considered a part of the string and will
+  // cause all sorts of strange bugs. Thus, unless `data` below is used as a raw
+  // C string, the \0 symbol should be ignored.
+  std::memcpy(  // NOLINT - clang-tidy warns about lack of null termination.
+      data, result.wgsl.data(), result.wgsl.size());
+  return result.wgsl.size();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size == 0) {
+    return 0;
+  }
+
+  struct Target {
+    FuzzingTarget fuzzing_target;
+    OutputFormat output_format;
+    const char* name;
+  };
+
+  Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"},
+                      {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"},
+                      {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"},
+                      {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}};
+
+  for (auto target : targets) {
+    if ((target.fuzzing_target & cli_params.fuzzing_target) !=
+        target.fuzzing_target) {
+      continue;
+    }
+
+    TransformBuilder tb(data, size);
+    tb.AddTransform<tint::transform::Robustness>();
+
+    CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+
+    fuzzer.Run(data, size);
+    if (fuzzer.HasErrors()) {
+      std::cout << "Fuzzing " << target.name << " produced an error"
+                << std::endl;
+      auto printer = tint::diag::Printer::create(stderr, true);
+      tint::diag::Formatter{}.format(fuzzer.Diagnostics(), printer.get());
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
new file mode 100644
index 0000000..63e00b3
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
@@ -0,0 +1,42 @@
+// 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/fuzzers/tint_ast_fuzzer/mutation.h"
+
+#include <cassert>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+Mutation::~Mutation() = default;
+
+std::unique_ptr<Mutation> Mutation::FromMessage(
+    const protobufs::Mutation& message) {
+  switch (message.mutation_case()) {
+    case protobufs::Mutation::kReplaceIdentifier:
+      return std::make_unique<MutationReplaceIdentifier>(
+          message.replace_identifier());
+    case protobufs::Mutation::MUTATION_NOT_SET:
+      assert(false && "Mutation is not set");
+      break;
+  }
+  return nullptr;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation.h
new file mode 100644
index 0000000..cc1afdd
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.h
@@ -0,0 +1,86 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_
+
+#include <memory>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
+
+#include "src/tint/clone_context.h"
+#include "src/tint/program.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// The base class for all the mutations in the fuzzer. Children must override
+/// three methods:
+/// - `IsApplicable` - checks whether it is possible to apply the mutation
+///   in a manner that will lead to a valid program.
+/// - `Apply` - applies the mutation.
+/// - `ToMessage` - converts the mutation data into a protobuf message.
+class Mutation {
+ public:
+  /// Virtual destructor.
+  virtual ~Mutation();
+
+  /// @brief Determines whether this mutation is applicable to the `program`.
+  ///
+  /// @param program - the program this mutation will be applied to. The program
+  ///     must be valid.
+  /// @param node_id_map - the map from `tint::ast::` nodes to their ids.
+  /// @return `true` if `Apply` method can be called without breaking the
+  ///     semantics of the `program`.
+  /// @return `false` otherwise.
+  virtual bool IsApplicable(const tint::Program& program,
+                            const NodeIdMap& node_id_map) const = 0;
+
+  /// @brief Applies this mutation to the `clone_context`.
+  ///
+  /// Precondition: `IsApplicable` must return `true` when invoked on the same
+  /// `node_id_map` and `clone_context->src` instance of `tint::Program`. A new
+  /// `tint::Program` that arises in `clone_context` must be valid.
+  ///
+  /// @param node_id_map - the map from `tint::ast::` nodes to their ids.
+  /// @param clone_context - the context that will clone the program with some
+  ///     changes introduced by this mutation.
+  /// @param new_node_id_map - this map will store ids for the mutated and
+  ///     cloned program. This argument cannot be a `nullptr` nor can it point
+  ///     to the same object as `node_id_map`.
+  virtual void Apply(const NodeIdMap& node_id_map,
+                     tint::CloneContext* clone_context,
+                     NodeIdMap* new_node_id_map) const = 0;
+
+  /// @return a protobuf message for this mutation.
+  virtual protobufs::Mutation ToMessage() const = 0;
+
+  /// @brief Converts a protobuf message into the mutation instance.
+  ///
+  /// @param message - a protobuf message.
+  /// @return the instance of this class.
+  static std::unique_ptr<Mutation> FromMessage(
+      const protobufs::Mutation& message);
+};
+
+using MutationList = std::vector<std::unique_ptr<Mutation>>;
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc
new file mode 100644
index 0000000..7344320
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc
@@ -0,0 +1,25 @@
+// 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/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationFinder::~MutationFinder() = default;
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h
new file mode 100644
index 0000000..d13a440
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h
@@ -0,0 +1,77 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
+
+#include <memory>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// Instances of this class traverse the `tint::Program`, looking for
+/// opportunities to apply mutations and return them to the caller.
+///
+/// Ideally, the behaviour of this class (precisely, its `FindMutations` method)
+/// should not be probabilistic. This is useful when mutation finders are used
+/// for test case reduction, because it enables the test case reducer to
+/// systematically explore all available mutations. There may be some
+/// exceptions, however. For example, if a huge number of mutations is returned,
+/// it would make sense to apply only a probabilistically selected subset of
+/// them.
+class MutationFinder {
+ public:
+  /// Virtual destructor.
+  virtual ~MutationFinder();
+
+  /// @brief Traverses the `program`, looking for opportunities to apply
+  /// mutations.
+  ///
+  /// @param program - the program being fuzzed.
+  /// @param node_id_map - a map from `tint::ast::` nodes in the `program` to
+  ///     their unique ids.
+  /// @param probability_context - determines various probabilistic stuff in the
+  ///     mutator. This should ideally be used as less as possible.
+  /// @return all the found mutations.
+  virtual MutationList FindMutations(
+      const tint::Program& program,
+      NodeIdMap* node_id_map,
+      ProbabilityContext* probability_context) const = 0;
+
+  /// @brief Compute a probability of applying a single mutation, returned by
+  /// this class.
+  ///
+  /// @param probability_context - contains information about various
+  ///     non-deterministic stuff in the fuzzer.
+  /// @return a number in the range [0; 100] which is a chance of applying a
+  ///     mutation.
+  virtual uint32_t GetChanceOfApplyingMutation(
+      ProbabilityContext* probability_context) const = 0;
+};
+
+using MutationFinderList = std::vector<std::unique_ptr<MutationFinder>>;
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
new file mode 100644
index 0000000..59e0fa2
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
@@ -0,0 +1,79 @@
+// 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/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
+
+#include <memory>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationList MutationFinderReplaceIdentifiers::FindMutations(
+    const tint::Program& program,
+    NodeIdMap* node_id_map,
+    ProbabilityContext* probability_context) const {
+  MutationList result;
+
+  // Go through each variable in the AST and for each user of that variable, try
+  // to replace it with some other variable usage.
+
+  for (const auto* node : program.SemNodes().Objects()) {
+    const auto* sem_variable = tint::As<sem::Variable>(node);
+    if (!sem_variable) {
+      continue;
+    }
+
+    // Iterate over all users of `sem_variable`.
+    for (const auto* user : sem_variable->Users()) {
+      // Get all variables that can be used to replace the `user` of
+      // `sem_variable`.
+      auto candidate_variables = util::GetAllVarsInScope(
+          program, user->Stmt(), [user](const sem::Variable* var) {
+            return var != user->Variable() && var->Type() == user->Type();
+          });
+
+      if (candidate_variables.empty()) {
+        // No suitable replacements have been found.
+        continue;
+      }
+
+      const auto* replacement =
+          candidate_variables[probability_context->GetRandomIndex(
+              candidate_variables)];
+
+      result.push_back(std::make_unique<MutationReplaceIdentifier>(
+          node_id_map->GetId(user->Declaration()),
+          node_id_map->GetId(replacement->Declaration())));
+    }
+  }
+
+  return result;
+}
+
+uint32_t MutationFinderReplaceIdentifiers::GetChanceOfApplyingMutation(
+    ProbabilityContext* probability_context) const {
+  return probability_context->GetChanceOfReplacingIdentifiers();
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h
new file mode 100644
index 0000000..2d8d70e
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h
@@ -0,0 +1,42 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// Looks for opportunities to apply `MutationReplaceIdentifier`.
+///
+/// Concretely, for each variable in the module, tries to replace its users with
+/// the uses of some other variables.
+class MutationFinderReplaceIdentifiers : public MutationFinder {
+ public:
+  MutationList FindMutations(
+      const tint::Program& program,
+      NodeIdMap* node_id_map,
+      ProbabilityContext* probability_context) const override;
+  uint32_t GetChanceOfApplyingMutation(
+      ProbabilityContext* probability_context) const override;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc
new file mode 100644
index 0000000..7f09dcc
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc
@@ -0,0 +1,106 @@
+// 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/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+
+#include <utility>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationReplaceIdentifier::MutationReplaceIdentifier(
+    protobufs::MutationReplaceIdentifier message)
+    : message_(std::move(message)) {}
+
+MutationReplaceIdentifier::MutationReplaceIdentifier(uint32_t use_id,
+                                                     uint32_t replacement_id) {
+  message_.set_use_id(use_id);
+  message_.set_replacement_id(replacement_id);
+}
+
+bool MutationReplaceIdentifier::IsApplicable(
+    const tint::Program& program,
+    const NodeIdMap& node_id_map) const {
+  const auto* use_ast_node = tint::As<ast::IdentifierExpression>(
+      node_id_map.GetNode(message_.use_id()));
+  if (!use_ast_node) {
+    // Either the `use_id` is invalid or the node is not an
+    // `IdentifierExpression`.
+    return false;
+  }
+
+  const auto* use_sem_node =
+      tint::As<sem::VariableUser>(program.Sem().Get(use_ast_node));
+  if (!use_sem_node) {
+    // Either the semantic information is not present for a `use_node` or that
+    // node is not a variable user.
+    return false;
+  }
+
+  const auto* replacement_ast_node =
+      tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
+  if (!replacement_ast_node) {
+    // Either the `replacement_id` is invalid or is not an id of a variable.
+    return false;
+  }
+
+  const auto* replacement_sem_node = program.Sem().Get(replacement_ast_node);
+  if (!replacement_sem_node) {
+    return false;
+  }
+
+  if (replacement_sem_node == use_sem_node->Variable()) {
+    return false;
+  }
+
+  auto in_scope =
+      util::GetAllVarsInScope(program, use_sem_node->Stmt(),
+                              [replacement_sem_node](const sem::Variable* var) {
+                                return var == replacement_sem_node;
+                              });
+  if (in_scope.empty()) {
+    // The replacement variable is not in scope.
+    return false;
+  }
+
+  return use_sem_node->Type() == replacement_sem_node->Type();
+}
+
+void MutationReplaceIdentifier::Apply(const NodeIdMap& node_id_map,
+                                      tint::CloneContext* clone_context,
+                                      NodeIdMap* new_node_id_map) const {
+  const auto* use_node = node_id_map.GetNode(message_.use_id());
+  const auto* replacement_var =
+      tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
+
+  auto* cloned_replacement =
+      clone_context->dst->Expr(clone_context->Clone(use_node->source),
+                               clone_context->Clone(replacement_var->symbol));
+  clone_context->Replace(use_node, cloned_replacement);
+  new_node_id_map->Add(cloned_replacement, message_.use_id());
+}
+
+protobufs::Mutation MutationReplaceIdentifier::ToMessage() const {
+  protobufs::Mutation mutation;
+  *mutation.mutable_replace_identifier() = message_;
+  return mutation;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h
new file mode 100644
index 0000000..553b8b4
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h
@@ -0,0 +1,77 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// @see MutationReplaceIdentifier::Apply
+class MutationReplaceIdentifier : public Mutation {
+ public:
+  /// @brief Constructs an instance of this mutation from a protobuf message.
+  /// @param message - protobuf message
+  explicit MutationReplaceIdentifier(
+      protobufs::MutationReplaceIdentifier message);
+
+  /// @brief Constructor.
+  /// @param use_id - the id of a variable user.
+  /// @param replacement_id - the id of a variable to replace the `use_id`.
+  MutationReplaceIdentifier(uint32_t use_id, uint32_t replacement_id);
+
+  /// @copybrief Mutation::IsApplicable
+  ///
+  /// The mutation is applicable iff:
+  /// - `use_id` is a valid id of an `ast::IdentifierExpression`, that
+  ///   references a variable.
+  /// - `replacement_id` is a valid id of an `ast::Variable`.
+  /// - The identifier expression doesn't reference the variable of a
+  ///   `replacement_id`.
+  /// - The variable with `replacement_id` is in scope of an identifier
+  ///   expression with `use_id`.
+  /// - The identifier expression and the variable have the same type.
+  ///
+  /// @copydetails Mutation::IsApplicable
+  bool IsApplicable(const tint::Program& program,
+                    const NodeIdMap& node_id_map) const override;
+
+  /// @copybrief Mutation::Apply
+  ///
+  /// Replaces the use of an identifier expression with `use_id` with a newly
+  /// created identifier expression, that references a variable with
+  /// `replacement_id`. The newly created identifier expression will have the
+  /// same id as the old one (i.e. `use_id`).
+  ///
+  /// @copydetails Mutation::Apply
+  void Apply(const NodeIdMap& node_id_map,
+             tint::CloneContext* clone_context,
+             NodeIdMap* new_node_id_map) const override;
+
+  protobufs::Mutation ToMessage() const override;
+
+ private:
+  protobufs::MutationReplaceIdentifier message_;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
new file mode 100644
index 0000000..6ffc40a
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
@@ -0,0 +1,670 @@
+// 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 <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+TEST(ReplaceIdentifierTest, NotApplicable_Simple) {
+  std::string content = R"(
+    fn main() {
+      let a = 5;
+      let c = 6;
+      let b = a + 5;
+
+      let d = vec2<i32>(1, 2);
+      let e = d.x;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements;
+
+  const auto* a_var =
+      main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable;
+  ASSERT_NE(a_var, nullptr);
+
+  const auto* b_var =
+      main_fn_stmts[2]->As<ast::VariableDeclStatement>()->variable;
+  ASSERT_NE(b_var, nullptr);
+
+  const auto* e_var =
+      main_fn_stmts[4]->As<ast::VariableDeclStatement>()->variable;
+  ASSERT_NE(e_var, nullptr);
+
+  auto a_var_id = node_id_map.GetId(a_var);
+  ASSERT_NE(a_var_id, 0);
+
+  auto b_var_id = node_id_map.GetId(b_var);
+  ASSERT_NE(b_var_id, 0);
+
+  const auto* sum_expr = b_var->constructor->As<ast::BinaryExpression>();
+  ASSERT_NE(sum_expr, nullptr);
+
+  auto a_ident_id = node_id_map.GetId(sum_expr->lhs);
+  ASSERT_NE(a_ident_id, 0);
+
+  auto sum_expr_id = node_id_map.GetId(sum_expr);
+  ASSERT_NE(sum_expr_id, 0);
+
+  auto e_var_id = node_id_map.GetId(e_var);
+  ASSERT_NE(e_var_id, 0);
+
+  auto vec_member_access_id = node_id_map.GetId(
+      e_var->constructor->As<ast::MemberAccessorExpression>()->member);
+  ASSERT_NE(vec_member_access_id, 0);
+
+  // use_id is invalid.
+  EXPECT_FALSE(MutationReplaceIdentifier(0, a_var_id)
+                   .IsApplicable(program, node_id_map));
+
+  // use_id is not an identifier expression.
+  EXPECT_FALSE(MutationReplaceIdentifier(sum_expr_id, a_var_id)
+                   .IsApplicable(program, node_id_map));
+
+  // use_id is an identifier but not a variable user.
+  EXPECT_FALSE(MutationReplaceIdentifier(vec_member_access_id, a_var_id)
+                   .IsApplicable(program, node_id_map));
+
+  // replacement_id is invalid.
+  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, 0)
+                   .IsApplicable(program, node_id_map));
+
+  // replacement_id is not a variable.
+  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, sum_expr_id)
+                   .IsApplicable(program, node_id_map));
+
+  // Can't replace a variable with itself.
+  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, a_var_id)
+                   .IsApplicable(program, node_id_map));
+
+  // Replacement is not in scope.
+  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, b_var_id)
+                   .IsApplicable(program, node_id_map));
+  EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, e_var_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, GlobalVarNotInScope) {
+  // Can't use the global variable if it's not in scope.
+  std::string shader = R"(
+var<private> a: i32;
+
+fn f() {
+  a = 3;
+}
+
+var<private> b: i32;
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
+  ASSERT_NE(replacement_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable1) {
+  // Can't replace `a` with `b` since the store type is wrong (the same storage
+  // class though).
+  std::string shader = R"(
+var<private> a: i32;
+var<private> b: u32;
+fn f() {
+  *&a = 4;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable2) {
+  // Can't replace `a` with `b` since the store type is wrong (the storage
+  // class is different though).
+  std::string shader = R"(
+var<private> a: i32;
+fn f() {
+  var b: u32;
+  *&a = 4;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST()
+                                              .Functions()[0]
+                                              ->body->statements[0]
+                                              ->As<ast::VariableDeclStatement>()
+                                              ->variable);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[1]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable3) {
+  // Can't replace `a` with `b` since the latter is not a reference (the store
+  // type is the same, though).
+  std::string shader = R"(
+var<private> a: i32;
+fn f() {
+  let b = 45;
+  *&a = 4;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST()
+                                              .Functions()[0]
+                                              ->body->statements[0]
+                                              ->As<ast::VariableDeclStatement>()
+                                              ->variable);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[1]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable4) {
+  // Can't replace `a` with `b` since the latter is not a reference (the store
+  // type is the same, though).
+  std::string shader = R"(
+var<private> a: i32;
+fn f(b: i32) {
+  *&a = 4;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id =
+      node_id_map.GetId(program.AST().Functions()[0]->params[0]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable5) {
+  // Can't replace `a` with `b` since the latter has a wrong access mode
+  // (`read` for uniform storage class).
+  std::string shader = R"(
+struct S {
+  a: i32;
+};
+
+var<private> a: S;
+@group(1) @binding(1) var<uniform> b: S;
+fn f() {
+  *&a = S(4);
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable6) {
+  // Can't replace `ptr_b` with `a` since the latter is not a pointer.
+  std::string shader = R"(
+struct S {
+  a: i32;
+};
+
+var<private> a: S;
+@group(1) @binding(1) var<uniform> b: S;
+fn f() {
+  let ptr_b = &b;
+  *&a = *ptr_b;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[1]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->rhs->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable8) {
+  // Can't replace `ptr_b` with `c` since the latter has a wrong access mode and
+  // storage class.
+  std::string shader = R"(
+struct S {
+  a: i32;
+};
+
+var<private> a: S;
+@group(1) @binding(1) var<uniform> b: S;
+@group(1) @binding(2) var<storage, write> c: S;
+fn f() {
+  let ptr_b = &b;
+  *&a = *ptr_b;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[2]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[1]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->rhs->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable9) {
+  // Can't replace `b` with `e` since the latter is not a reference.
+  std::string shader = R"(
+struct S {
+  a: i32;
+};
+
+var<private> a: S;
+let e = 3;
+@group(1) @binding(1) var<uniform> b: S;
+fn f() {
+  *&a = *&b;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->rhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable10) {
+  // Can't replace `b` with `e` since the latter has a wrong access mode.
+  std::string shader = R"(
+struct S {
+  a: i32;
+};
+
+var<private> a: S;
+@group(0) @binding(0) var<storage, write> e: S;
+@group(1) @binding(1) var<uniform> b: S;
+fn f() {
+  *&a = *&b;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
+  ASSERT_NE(replacement_id, 0);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[0]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->rhs->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, Applicable1) {
+  // Can replace `a` with `b` (same storage class).
+  std::string shader = R"(
+fn f() {
+  var b : vec2<u32>;
+  var a = vec2<u32>(34u, 45u);
+  (*&a)[1] = 3u;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[2]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::IndexAccessorExpression>()
+                                      ->object->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id = node_id_map.GetId(program.AST()
+                                              .Functions()[0]
+                                              ->body->statements[0]
+                                              ->As<ast::VariableDeclStatement>()
+                                              ->variable);
+  ASSERT_NE(replacement_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map,
+      &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn f() {
+  var b : vec2<u32>;
+  var a = vec2<u32>(34u, 45u);
+  (*(&(b)))[1] = 3u;
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(ReplaceIdentifierTest, Applicable2) {
+  // Can replace `ptr_a` with `b` - the function parameter.
+  std::string shader = R"(
+fn f(b: ptr<function, vec2<u32>>) {
+  var a = vec2<u32>(34u, 45u);
+  let ptr_a = &a;
+  (*ptr_a)[1] = 3u;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[2]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::IndexAccessorExpression>()
+                                      ->object->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id =
+      node_id_map.GetId(program.AST().Functions()[0]->params[0]);
+  ASSERT_NE(replacement_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map,
+      &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn f(b : ptr<function, vec2<u32>>) {
+  var a = vec2<u32>(34u, 45u);
+  let ptr_a = &(a);
+  (*(b))[1] = 3u;
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable12) {
+  // Can't replace `a` with `b` (both are references with different storage
+  // class).
+  std::string shader = R"(
+var<private> b : vec2<u32>;
+fn f() {
+  var a = vec2<u32>(34u, 45u);
+  (*&a)[1] = 3u;
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(program.AST()
+                                      .Functions()[0]
+                                      ->body->statements[1]
+                                      ->As<ast::AssignmentStatement>()
+                                      ->lhs->As<ast::IndexAccessorExpression>()
+                                      ->object->As<ast::UnaryOpExpression>()
+                                      ->expr->As<ast::UnaryOpExpression>()
+                                      ->expr);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
+  ASSERT_NE(replacement_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable13) {
+  // Can't replace `a` with `b` (both are references with different storage
+  // class).
+  std::string shader = R"(
+var<private> b : vec2<u32>;
+fn f() {
+  var a = vec2<u32>(34u, 45u);
+  let c = (*&a)[1];
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(
+      program.AST()
+          .Functions()[0]
+          ->body->statements[1]
+          ->As<ast::VariableDeclStatement>()
+          ->variable->constructor->As<ast::IndexAccessorExpression>()
+          ->object->As<ast::UnaryOpExpression>()
+          ->expr->As<ast::UnaryOpExpression>()
+          ->expr);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]);
+  ASSERT_NE(replacement_id, 0);
+
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+TEST(ReplaceIdentifierTest, NotApplicable14) {
+  // Can't replace `ptr_a` with `ptr_b` (both are pointers with different
+  // storage class).
+  std::string shader = R"(
+var<private> b: vec2<u32>;
+fn f() {
+  var a = vec2<u32>(34u, 45u);
+  let ptr_a = &a;
+  let ptr_b = &b;
+  let c = (*ptr_a)[1];
+}
+)";
+  Source::File file("test.wgsl", shader);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  auto use_id = node_id_map.GetId(
+      program.AST()
+          .Functions()[0]
+          ->body->statements[3]
+          ->As<ast::VariableDeclStatement>()
+          ->variable->constructor->As<ast::IndexAccessorExpression>()
+          ->object->As<ast::UnaryOpExpression>()
+          ->expr);
+  ASSERT_NE(use_id, 0);
+
+  auto replacement_id = node_id_map.GetId(program.AST()
+                                              .Functions()[0]
+                                              ->body->statements[2]
+                                              ->As<ast::VariableDeclStatement>()
+                                              ->variable);
+  ASSERT_NE(replacement_id, 0);
+  ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
+                   .IsApplicable(program, node_id_map));
+}
+
+}  // namespace
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
new file mode 100644
index 0000000..26ce653
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -0,0 +1,184 @@
+// 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/fuzzers/tint_ast_fuzzer/mutator.h"
+
+#include <cassert>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+template <typename T, typename... Args>
+void MaybeAddFinder(bool enable_all_mutations,
+                    ProbabilityContext* probability_context,
+                    MutationFinderList* finders,
+                    Args&&... args) {
+  if (enable_all_mutations || probability_context->RandomBool()) {
+    finders->push_back(std::make_unique<T>(std::forward<Args>(args)...));
+  }
+}
+
+MutationFinderList CreateMutationFinders(
+    ProbabilityContext* probability_context,
+    bool enable_all_mutations) {
+  MutationFinderList result;
+  do {
+    MaybeAddFinder<MutationFinderReplaceIdentifiers>(
+        enable_all_mutations, probability_context, &result);
+  } while (result.empty());
+  return result;
+}
+
+}  // namespace
+
+bool MaybeApplyMutation(const tint::Program& program,
+                        const Mutation& mutation,
+                        const NodeIdMap& node_id_map,
+                        tint::Program* out_program,
+                        NodeIdMap* out_node_id_map,
+                        protobufs::MutationSequence* mutation_sequence) {
+  assert(out_program && "`out_program` may not be a nullptr");
+  assert(out_node_id_map && "`out_node_id_map` may not be a nullptr");
+
+  if (!mutation.IsApplicable(program, node_id_map)) {
+    return false;
+  }
+
+  // The mutated `program` will be copied into the `mutated` program builder.
+  tint::ProgramBuilder mutated;
+  tint::CloneContext clone_context(&mutated, &program);
+  NodeIdMap new_node_id_map;
+  clone_context.ReplaceAll(
+      [&node_id_map, &new_node_id_map, &clone_context](const ast::Node* node) {
+        // Make sure all `tint::ast::` nodes' ids are preserved.
+        auto* cloned = tint::As<ast::Node>(node->Clone(&clone_context));
+        new_node_id_map.Add(cloned, node_id_map.GetId(node));
+        return cloned;
+      });
+
+  mutation.Apply(node_id_map, &clone_context, &new_node_id_map);
+  if (mutation_sequence) {
+    *mutation_sequence->add_mutation() = mutation.ToMessage();
+  }
+
+  clone_context.Clone();
+  *out_program = tint::Program(std::move(mutated));
+  *out_node_id_map = std::move(new_node_id_map);
+  return true;
+}
+
+tint::Program Replay(tint::Program program,
+                     const protobufs::MutationSequence& mutation_sequence) {
+  assert(program.IsValid() && "Initial program is invalid");
+
+  NodeIdMap node_id_map(program);
+  for (const auto& mutation_message : mutation_sequence.mutation()) {
+    auto mutation = Mutation::FromMessage(mutation_message);
+    auto status = MaybeApplyMutation(program, *mutation, node_id_map, &program,
+                                     &node_id_map, nullptr);
+    (void)status;  // `status` will be unused in release mode.
+    assert(status && "`mutation` is inapplicable - it's most likely a bug");
+    if (!program.IsValid()) {
+      // `mutation` has a bug.
+      break;
+    }
+  }
+
+  return program;
+}
+
+tint::Program Mutate(tint::Program program,
+                     ProbabilityContext* probability_context,
+                     bool enable_all_mutations,
+                     uint32_t max_applied_mutations,
+                     protobufs::MutationSequence* mutation_sequence) {
+  assert(max_applied_mutations != 0 &&
+         "Maximum number of mutations is invalid");
+  assert(program.IsValid() && "Initial program is invalid");
+
+  // The number of allowed failed attempts to apply mutations. If this number is
+  // exceeded, the mutator is considered stuck and the mutation session is
+  // stopped.
+  const uint32_t kMaxFailureToApply = 10;
+
+  auto finders =
+      CreateMutationFinders(probability_context, enable_all_mutations);
+  NodeIdMap node_id_map(program);
+
+  // Total number of applied mutations during this call to `Mutate`.
+  uint32_t applied_mutations = 0;
+
+  // The number of consecutively failed attempts to apply mutations.
+  uint32_t failure_to_apply = 0;
+
+  // Apply mutations as long as the `program` is valid, the limit on the number
+  // of mutations is not reached and the mutator is not stuck (i.e. unable to
+  // apply any mutations for some time).
+  while (program.IsValid() && applied_mutations < max_applied_mutations &&
+         failure_to_apply < kMaxFailureToApply) {
+    // Get all applicable mutations from some mutation finder.
+    const auto& mutation_finder =
+        finders[probability_context->GetRandomIndex(finders)];
+    auto mutations = mutation_finder->FindMutations(program, &node_id_map,
+                                                    probability_context);
+
+    const auto old_applied_mutations = applied_mutations;
+    for (const auto& mutation : mutations) {
+      if (!probability_context->ChoosePercentage(
+              mutation_finder->GetChanceOfApplyingMutation(
+                  probability_context))) {
+        // Skip this `mutation` probabilistically.
+        continue;
+      }
+
+      if (!MaybeApplyMutation(program, *mutation, node_id_map, &program,
+                              &node_id_map, mutation_sequence)) {
+        // This `mutation` is inapplicable. This may happen if some of the
+        // earlier mutations cancelled this one.
+        continue;
+      }
+
+      applied_mutations++;
+      if (!program.IsValid()) {
+        // This `mutation` has a bug.
+        return program;
+      }
+    }
+
+    if (old_applied_mutations == applied_mutations) {
+      // No mutation was applied. Increase the counter to prevent an infinite
+      // loop.
+      failure_to_apply++;
+    } else {
+      failure_to_apply = 0;
+    }
+  }
+
+  return program;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
new file mode 100644
index 0000000..d700b70
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
@@ -0,0 +1,101 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+/// @file
+
+/// @brief Tries to apply a `mutation` to the `program`.
+///
+/// If the `mutation` is inapplicable, this function will return `false` and
+/// `out_program`, `out_node_id_map` and `mutation_sequence` won't be modified.
+///
+/// The `mutation` is required to produce a valid program when the
+/// `Mutation::Apply` method is called. This guarantees that this function
+/// returns a valid program as well.
+///
+/// @param program - the initial program (must be valid).
+/// @param mutation - the mutation that will be applied.
+/// @param node_id_map - a map from `tint::ast::` nodes in the `program` to
+///     their unique ids.
+/// @param out_program - the resulting mutated program will be written through
+///     this pointer. It may *not* be a `nullptr`. It _may_ point to `program`,
+///     so that a program can be updated in place.
+/// @param out_node_id_map - will contain new ids for the AST nodes in the
+///     mutated program. It may *not* be a `nullptr`. It _may_ point to
+///     `node_id_map`, so that a map can be updated in place.
+/// @param mutation_sequence - the message about this mutation will be recorded
+///     here. It may be a `nullptr`, in which case it's ignored.
+/// @return `true` if the `mutation` was applied.
+/// @return `false` if the `mutation` is inapplicable.
+bool MaybeApplyMutation(const tint::Program& program,
+                        const Mutation& mutation,
+                        const NodeIdMap& node_id_map,
+                        tint::Program* out_program,
+                        NodeIdMap* out_node_id_map,
+                        protobufs::MutationSequence* mutation_sequence);
+
+/// @brief Applies mutations from `mutations_sequence` to the `program`.
+///
+/// All mutations in `mutation_sequence` must be applicable. Additionally, all
+/// mutations must produce a valid program when the `Mutation::Apply` method is
+/// called. This guarantees that this function returns a valid program as well.
+///
+/// @param program - the initial program - must be valid.
+/// @param mutation_sequence - a sequence of mutations.
+/// @return the mutated program.
+tint::Program Replay(tint::Program program,
+                     const protobufs::MutationSequence& mutation_sequence);
+
+/// @brief Applies up to `max_applied_mutations` mutations to the `program`.
+///
+/// All applied mutations must produce valid programs. This guarantees that the
+/// returned program is valid as well. The returned program may be identical to
+/// the initial `program` if no mutation was applied.
+///
+/// @param program - initial program - must be valid.
+/// @param probability_context - contains information about various
+///     probabilistic behaviour of the fuzzer.
+/// @param enable_all_mutations - if `false`, only mutations from a
+///     probabilistically selected set of mutation types are applied. If `true`,
+///     all mutation types are considered.
+/// @param max_applied_mutations - the maximum number of applied mutations. This
+///     may not be 0.
+/// @param mutation_sequence - applied mutations will be recorded into this
+///     protobuf message. This argument may be `nullptr`, in which case it's
+///     ignored.
+/// @return the mutated program.
+tint::Program Mutate(tint::Program program,
+                     ProbabilityContext* probability_context,
+                     bool enable_all_mutations,
+                     uint32_t max_applied_mutations,
+                     protobufs::MutationSequence* mutation_sequence);
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
new file mode 100644
index 0000000..543a57b
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
@@ -0,0 +1,65 @@
+// 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/fuzzers/tint_ast_fuzzer/node_id_map.h"
+
+#include <cassert>
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+NodeIdMap::NodeIdMap() = default;
+
+NodeIdMap::NodeIdMap(const Program& program) : NodeIdMap() {
+  for (const auto* node : program.ASTNodes().Objects()) {
+    Add(node, TakeFreshId());
+  }
+}
+
+NodeIdMap::IdType NodeIdMap::GetId(const ast::Node* node) const {
+  auto it = node_to_id_.find(node);
+  return it == node_to_id_.end() ? 0 : it->second;
+}
+
+const ast::Node* NodeIdMap::GetNode(IdType id) const {
+  auto it = id_to_node_.find(id);
+  return it == id_to_node_.end() ? nullptr : it->second;
+}
+
+void NodeIdMap::Add(const ast::Node* node, IdType id) {
+  assert(!node_to_id_.count(node) && "The node already exists in the map");
+  assert(IdIsFreshAndValid(id) && "Id already exists in the map or Id is zero");
+  assert(node && "`node` can't be a nullptr");
+
+  node_to_id_[node] = id;
+  id_to_node_[id] = node;
+
+  if (id >= fresh_id_) {
+    fresh_id_ = id + 1;
+  }
+}
+
+bool NodeIdMap::IdIsFreshAndValid(IdType id) {
+  return id && !id_to_node_.count(id);
+}
+
+NodeIdMap::IdType NodeIdMap::TakeFreshId() {
+  assert(fresh_id_ != 0 && "`NodeIdMap` id has overflowed");
+  return fresh_id_++;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
new file mode 100644
index 0000000..18dd96a
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
+
+#include <unordered_map>
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// Contains a one-to-one mapping between the nodes in the AST of the program
+/// and their ids.
+///
+/// The motivation for having this mapping is:
+/// - To be able to uniquely identify a node in the AST. This will be used
+///   to record transformations in the protobuf messages.
+/// - When the AST is being modified, only the mapping for the modified nodes
+///   must be affected. That is, if some node is unchanged, it must have the
+///   same id defined in this class.
+///
+/// This class achieves these goals partially. Concretely, the only way to
+/// change the AST is by cloning it since all instances of `tint::ast::` classes
+/// are immutable. This will invalidate all the pointers to the AST nodes which
+/// are used in this class. To overcome this, a new instance of this class is
+/// created with all the cloned nodes and the old instance is discarded.
+class NodeIdMap {
+ public:
+  /// Type of the id used by this map.
+  using IdType = uint32_t;
+
+  /// Creates an empty map.
+  NodeIdMap();
+
+  /// @brief Initializes this instance with all the nodes in the `program`.
+  /// @param program - must be valid.
+  explicit NodeIdMap(const Program& program);
+
+  /// @brief Returns a node for the given `id`.
+  /// @param id - any value is accepted.
+  /// @return a pointer to some node if `id` exists in this map.
+  /// @return `nullptr` otherwise.
+  const ast::Node* GetNode(IdType id) const;
+
+  /// @brief Returns an id of the given `node`.
+  /// @param node - can be a `nullptr`.
+  /// @return not equal to 0 if `node` exists in this map.
+  /// @return 0 otherwise.
+  IdType GetId(const ast::Node* node) const;
+
+  /// @brief Adds a mapping from `node` to `id` to this map.
+  /// @param node - may not be a `nullptr` and can't be present in this map.
+  /// @param id - may not be 0 and can't be present in this map.
+  void Add(const ast::Node* node, IdType id);
+
+  /// @brief Returns whether the id is fresh by checking if it exists in
+  /// the id map and the id is not 0.
+  /// @param id - an id that is used to check in the map.
+  /// @return true the given id is fresh and valid (non-zero).
+  /// @return false otherwise.
+  bool IdIsFreshAndValid(IdType id);
+
+  /// @brief Returns an id that is guaranteed to be unoccupied in this map.
+  ///
+  /// This will effectively increase the counter. This means that two
+  /// consecutive calls to this method will return different ids.
+  ///
+  /// @return an unoccupied id.
+  IdType TakeFreshId();
+
+ private:
+  IdType fresh_id_ = 1;
+
+  std::unordered_map<const ast::Node*, IdType> node_to_id_;
+  std::unordered_map<IdType, const ast::Node*> id_to_node_;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h
new file mode 100644
index 0000000..31b2946
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// @brief Allows CLI parameters to be overridden.
+///
+/// This function allows fuzz targets to override particular CLI parameters,
+/// for example forcing a particular back-end to be targeted.
+///
+/// @param cli_params - the parsed CLI parameters to be updated.
+void OverrideCliParams(CliParams& cli_params);
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
new file mode 100644
index 0000000..515078c
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
@@ -0,0 +1,44 @@
+// 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/fuzzers/tint_ast_fuzzer/probability_context.h"
+
+#include <cassert>
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
+
+}  // namespace
+
+ProbabilityContext::ProbabilityContext(RandomGenerator* generator)
+    : generator_(generator),
+      chance_of_replacing_identifiers_(
+          RandomFromRange(kChanceOfReplacingIdentifiers)) {
+  assert(generator != nullptr && "generator must not be nullptr");
+}
+
+uint32_t ProbabilityContext::RandomFromRange(
+    std::pair<uint32_t, uint32_t> range) {
+  assert(range.first <= range.second && "Range must be non-decreasing");
+  return generator_->GetUInt32(
+      range.first, range.second + 1);  // + 1 need since range is inclusive.
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
new file mode 100644
index 0000000..0d6393a
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
@@ -0,0 +1,77 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// This class is intended to be used by the `MutationFinder`s to introduce some
+/// variance to the mutation process.
+class ProbabilityContext {
+ public:
+  /// Initializes this instance with a random number generator.
+  /// @param generator - must not be a `nullptr`. Must remain in scope as long
+  /// as this
+  ///     instance exists.
+  explicit ProbabilityContext(RandomGenerator* generator);
+
+  /// Get random bool with even odds
+  /// @returns true 50% of the time and false %50 of time.
+  bool RandomBool() { return generator_->GetBool(); }
+
+  /// Get random bool with weighted odds
+  /// @param percentage - likelihood of true being returned
+  /// @returns true |percentage|% of the time, and false (100 - |percentage|)%
+  /// of the time.
+  bool ChoosePercentage(uint32_t percentage) {
+    return generator_->GetWeightedBool(percentage);
+  }
+
+  /// Returns a random value in the range `[0; arr.size())`.
+  /// @tparam T - type of the elements in the vector.
+  /// @param arr - may not be empty.
+  /// @return the random index in the `arr`.
+  template <typename T>
+  size_t GetRandomIndex(const std::vector<T>& arr) {
+    return static_cast<size_t>(generator_->GetUInt64(arr.size()));
+  }
+
+  /// @return the probability of replacing some identifier with some other one.
+  uint32_t GetChanceOfReplacingIdentifiers() const {
+    return chance_of_replacing_identifiers_;
+  }
+
+ private:
+  /// @param range - a pair of integers `a` and `b` s.t. `a <= b`.
+  /// @return an random number in the range `[a; b]`.
+  uint32_t RandomFromRange(std::pair<uint32_t, uint32_t> range);
+
+  RandomGenerator* generator_;
+
+  uint32_t chance_of_replacing_identifiers_;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h
new file mode 100644
index 0000000..54586b1
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h
@@ -0,0 +1,35 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
+
+// Compilation of the protobuf library and its autogenerated code can produce
+// warnings. Ignore them since we can't control them.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+#pragma clang diagnostic ignored "-Wextra-semi-stmt"
+#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
+#pragma clang diagnostic ignored "-Wreserved-identifier"
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.pb.h"
+
+#pragma clang diagnostic pop
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
diff --git a/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
similarity index 100%
rename from fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
rename to src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc
new file mode 100644
index 0000000..77b00c4
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc
@@ -0,0 +1,28 @@
+// 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/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+void OverrideCliParams(CliParams& /*unused*/) {
+  // Leave the CLI parameters unchanged.
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc
new file mode 100644
index 0000000..a1d886e
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc
new file mode 100644
index 0000000..8354ac3
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kMsl;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc
new file mode 100644
index 0000000..266ef6a
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kSpv;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc
new file mode 100644
index 0000000..ede740f
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/util.h b/src/tint/fuzzers/tint_ast_fuzzer/util.h
new file mode 100644
index 0000000..019fdec
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/util.h
@@ -0,0 +1,109 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_
+
+#include <vector>
+
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/castable.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace util {
+/// @file
+
+/// @brief Returns all in-scope variables (including formal function parameters)
+/// related to statement `curr_stmt`.
+///
+/// These variables are additionally filtered by applying a predicate `pred`.
+///
+/// @tparam Pred - a predicate that accepts a `const sem::Variable*` and returns
+///     `bool`.
+/// @param program - the program to look for variables in.
+/// @param curr_stmt - the current statement. Everything below it is not in
+///     scope.
+/// @param pred - a predicate (e.g. a function pointer, functor, lambda etc) of
+///     type `Pred`.
+/// @return a vector of all variables that can be accessed from `curr_stmt`.
+template <typename Pred>
+std::vector<const sem::Variable*> GetAllVarsInScope(
+    const tint::Program& program,
+    const sem::Statement* curr_stmt,
+    Pred&& pred) {
+  std::vector<const sem::Variable*> result;
+
+  // Walk up the hierarchy of blocks in which `curr_stmt` is contained.
+  for (const auto* block = curr_stmt->Block(); block;
+       block = tint::As<sem::BlockStatement>(block->Parent())) {
+    for (const auto* stmt : block->Declaration()->statements) {
+      if (stmt == curr_stmt->Declaration()) {
+        // `curr_stmt` was found. This is only possible if `block is the
+        // enclosing block of `curr_stmt` since the AST nodes are not shared.
+        // Because of all this, skip the iteration of the inner loop since
+        // the rest of the instructions in the `block` are not visible from the
+        // `curr_stmt`.
+        break;
+      }
+
+      if (const auto* var_node = tint::As<ast::VariableDeclStatement>(stmt)) {
+        const auto* sem_var = program.Sem().Get(var_node->variable);
+        if (pred(sem_var)) {
+          result.push_back(sem_var);
+        }
+      }
+    }
+  }
+
+  // Process function parameters.
+  for (const auto* param : curr_stmt->Function()->Parameters()) {
+    if (pred(param)) {
+      result.push_back(param);
+    }
+  }
+
+  // Global variables do not belong to any ast::BlockStatement.
+  for (const auto* global_decl : program.AST().GlobalDeclarations()) {
+    if (global_decl == curr_stmt->Function()->Declaration()) {
+      // The same situation as in the previous loop. The current function has
+      // been reached. If there are any variables declared below, they won't be
+      // visible in this function. Thus, exit the loop.
+      break;
+    }
+
+    if (const auto* global_var = tint::As<ast::Variable>(global_decl)) {
+      const auto* sem_node = program.Sem().Get(global_var);
+      if (pred(sem_node)) {
+        result.push_back(sem_node);
+      }
+    }
+  }
+
+  return result;
+}
+
+}  // namespace util
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_
diff --git a/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc b/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc
new file mode 100644
index 0000000..f736cca
--- /dev/null
+++ b/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<transform::BindingRemapper>();
+
+  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_black_box_fuzz_target.cc b/src/tint/fuzzers/tint_black_box_fuzz_target.cc
new file mode 100644
index 0000000..426b58a
--- /dev/null
+++ b/src/tint/fuzzers/tint_black_box_fuzz_target.cc
@@ -0,0 +1,157 @@
+// 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 <cassert>
+#include <cstdio>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+
+namespace {
+
+/// Controls the target language in which code will be generated.
+enum class TargetLanguage {
+  kHlsl,
+  kMsl,
+  kSpv,
+  kWgsl,
+  kTargetLanguageMax,
+};
+
+/// Copies the content from the file named `input_file` to `buffer`,
+/// assuming each element in the file is of type `T`.  If any error occurs,
+/// writes error messages to the standard error stream and returns false.
+/// Assumes the size of a `T` object is divisible by its required alignment.
+/// @returns true if we successfully read the file.
+template <typename T>
+bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
+  if (!buffer) {
+    std::cerr << "The buffer pointer was null" << std::endl;
+    return false;
+  }
+
+  FILE* file = nullptr;
+#if defined(_MSC_VER)
+  fopen_s(&file, input_file.c_str(), "rb");
+#else
+  file = fopen(input_file.c_str(), "rb");
+#endif
+  if (!file) {
+    std::cerr << "Failed to open " << input_file << std::endl;
+    return false;
+  }
+
+  fseek(file, 0, SEEK_END);
+  const auto file_size = static_cast<size_t>(ftell(file));
+  if (0 != (file_size % sizeof(T))) {
+    std::cerr << "File " << input_file
+              << " does not contain an integral number of objects: "
+              << file_size << " bytes in the file, require " << sizeof(T)
+              << " bytes per object" << std::endl;
+    fclose(file);
+    return false;
+  }
+  fseek(file, 0, SEEK_SET);
+
+  buffer->clear();
+  buffer->resize(file_size / sizeof(T));
+
+  size_t bytes_read = fread(buffer->data(), 1, file_size, file);
+  fclose(file);
+  if (bytes_read != file_size) {
+    std::cerr << "Failed to read " << input_file << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+int main(int argc, const char** argv) {
+  if (argc < 2 || argc > 3) {
+    std::cerr << "Usage: " << argv[0] << " <input file> [hlsl|msl|spv|wgsl]"
+              << std::endl;
+    return 1;
+  }
+
+  std::string input_filename(argv[1]);
+
+  std::vector<uint8_t> data;
+  if (!ReadFile<uint8_t>(input_filename, &data)) {
+    return 1;
+  }
+
+  if (data.empty()) {
+    return 0;
+  }
+
+  tint::fuzzers::DataBuilder builder(data.data(), data.size());
+
+  TargetLanguage target_language;
+
+  if (argc == 3) {
+    std::string target_language_string = argv[2];
+    if (target_language_string == "hlsl") {
+      target_language = TargetLanguage::kHlsl;
+    } else if (target_language_string == "msl") {
+      target_language = TargetLanguage::kMsl;
+    } else if (target_language_string == "spv") {
+      target_language = TargetLanguage::kSpv;
+    } else {
+      assert(target_language_string == "wgsl" && "Unknown target language.");
+      target_language = TargetLanguage::kWgsl;
+    }
+  } else {
+    target_language = builder.enum_class<TargetLanguage>(
+        static_cast<uint32_t>(TargetLanguage::kTargetLanguageMax));
+  }
+
+  switch (target_language) {
+    case TargetLanguage::kHlsl: {
+      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
+                                         tint::fuzzers::OutputFormat::kHLSL);
+      return fuzzer.Run(data.data(), data.size());
+    }
+    case TargetLanguage::kMsl: {
+      tint::writer::msl::Options options;
+      GenerateMslOptions(&builder, &options);
+      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
+                                         tint::fuzzers::OutputFormat::kMSL);
+      fuzzer.SetOptionsMsl(options);
+      return fuzzer.Run(data.data(), data.size());
+    }
+    case TargetLanguage::kSpv: {
+      tint::writer::spirv::Options options;
+      GenerateSpirvOptions(&builder, &options);
+      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
+                                         tint::fuzzers::OutputFormat::kSpv);
+      fuzzer.SetOptionsSpirv(options);
+      return fuzzer.Run(data.data(), data.size());
+    }
+    case TargetLanguage::kWgsl: {
+      tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL,
+                                         tint::fuzzers::OutputFormat::kWGSL);
+      return fuzzer.Run(data.data(), data.size());
+    }
+    default:
+      std::cerr << "Aborting due to unknown target language; fuzzer must be "
+                   "misconfigured."
+                << std::endl;
+      abort();
+  }
+}
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
new file mode 100644
index 0000000..80f5797
--- /dev/null
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -0,0 +1,353 @@
+// 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/fuzzers/tint_common_fuzzer.h"
+
+#include <cassert>
+#include <cstring>
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#if TINT_BUILD_SPV_READER
+#include "spirv-tools/libspirv.hpp"
+#endif  // TINT_BUILD_SPV_READER
+
+#include "src/tint/ast/module.h"
+#include "src/tint/diagnostic/formatter.h"
+#include "src/tint/program.h"
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace fuzzers {
+
+namespace {
+
+// A macro is used to avoid FATAL_ERROR creating its own stack frame. This leads
+// to better de-duplication of bug reports, because ClusterFuzz only uses the
+// top few stack frames for de-duplication, and a FATAL_ERROR stack frame
+// provides no useful information.
+#define FATAL_ERROR(diags, msg_string)                        \
+  do {                                                        \
+    std::string msg = msg_string;                             \
+    auto printer = tint::diag::Printer::create(stderr, true); \
+    if (!msg.empty()) {                                       \
+      printer->write(msg + "\n", {diag::Color::kRed, true});  \
+    }                                                         \
+    tint::diag::Formatter().format(diags, printer.get());     \
+    __builtin_trap();                                         \
+  } while (false)
+
+[[noreturn]] void TintInternalCompilerErrorReporter(
+    const tint::diag::List& diagnostics) {
+  FATAL_ERROR(diagnostics, "");
+}
+
+// Wrapping in a macro, so it can be a one-liner in the code, but not
+// introduce another level in the stack trace. This will help with de-duping
+// ClusterFuzz issues.
+#define CHECK_INSPECTOR(program, inspector)                    \
+  do {                                                         \
+    if ((inspector).has_error()) {                             \
+      if (!enforce_validity) {                                 \
+        return;                                                \
+      }                                                        \
+      FATAL_ERROR((program)->Diagnostics(),                    \
+                  "Inspector failed: " + (inspector).error()); \
+    }                                                          \
+  } while (false)
+
+// Wrapping in a macro to make code more readable and help with issue de-duping.
+#define VALIDITY_ERROR(diags, msg_string) \
+  do {                                    \
+    if (!enforce_validity) {              \
+      return 0;                           \
+    }                                     \
+    FATAL_ERROR(diags, msg_string);       \
+  } while (false)
+
+bool SPIRVToolsValidationCheck(const tint::Program& program,
+                               const std::vector<uint32_t>& spirv) {
+  spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
+  const tint::diag::List& diags = program.Diagnostics();
+  tools.SetMessageConsumer([diags](spv_message_level_t, const char*,
+                                   const spv_position_t& pos, const char* msg) {
+    std::stringstream out;
+    out << "Unexpected spirv-val error:\n"
+        << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
+        << std::endl;
+
+    auto printer = tint::diag::Printer::create(stderr, true);
+    printer->write(out.str(), {diag::Color::kYellow, false});
+    tint::diag::Formatter().format(diags, printer.get());
+  });
+
+  return tools.Validate(spirv.data(), spirv.size(),
+                        spvtools::ValidatorOptions());
+}
+
+}  // namespace
+
+void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options) {
+  *options = b->build<writer::spirv::Options>();
+}
+
+void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options) {
+  *options = b->build<writer::wgsl::Options>();
+}
+
+void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options) {
+  *options = b->build<writer::hlsl::Options>();
+}
+
+void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options) {
+  *options = b->build<writer::msl::Options>();
+}
+
+CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output)
+    : input_(input), output_(output) {}
+
+CommonFuzzer::~CommonFuzzer() = default;
+
+int CommonFuzzer::Run(const uint8_t* data, size_t size) {
+  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+
+#if TINT_BUILD_WGSL_WRITER
+  tint::Program::printer = [](const tint::Program* program) {
+    auto result = tint::writer::wgsl::Generate(program, {});
+    if (!result.error.empty()) {
+      return "error: " + result.error;
+    }
+    return result.wgsl;
+  };
+#endif  // TINT_BUILD_WGSL_WRITER
+
+  Program program;
+
+#if TINT_BUILD_SPV_READER
+  std::vector<uint32_t> spirv_input(size / sizeof(uint32_t));
+
+#endif  // TINT_BUILD_SPV_READER
+
+#if TINT_BUILD_WGSL_READER || TINT_BUILD_SPV_READER
+  auto dump_input_data = [&](auto& content, const char* extension) {
+    size_t hash = utils::Hash(content);
+    auto filename = "fuzzer_input_" + std::to_string(hash) + extension;  //
+    std::ofstream fout(filename, std::ios::binary);
+    fout.write(reinterpret_cast<const char*>(data),
+               static_cast<std::streamsize>(size));
+    std::cout << "Dumped input data to " << filename << std::endl;
+  };
+#endif
+
+  switch (input_) {
+#if TINT_BUILD_WGSL_READER
+    case InputFormat::kWGSL: {
+      // Clear any existing diagnostics, as these will hold pointers to file_,
+      // which we are about to release.
+      diagnostics_ = {};
+      std::string str(reinterpret_cast<const char*>(data), size);
+      file_ = std::make_unique<Source::File>("test.wgsl", str);
+      if (dump_input_) {
+        dump_input_data(str, ".wgsl");
+      }
+      program = reader::wgsl::Parse(file_.get());
+      break;
+    }
+#endif  // TINT_BUILD_WGSL_READER
+#if TINT_BUILD_SPV_READER
+    case InputFormat::kSpv: {
+      // `spirv_input` has been initialized with the capacity to store `size /
+      // sizeof(uint32_t)` uint32_t values. If `size` is not a multiple of
+      // sizeof(uint32_t) then not all of `data` can be copied into
+      // `spirv_input`, and any trailing bytes are discarded.
+      std::memcpy(spirv_input.data(), data,
+                  spirv_input.size() * sizeof(uint32_t));
+      if (spirv_input.empty()) {
+        return 0;
+      }
+      if (dump_input_) {
+        dump_input_data(spirv_input, ".spv");
+      }
+      program = reader::spirv::Parse(spirv_input);
+      break;
+    }
+#endif  // TINT_BUILD_SPV_READER
+  }
+
+  if (!program.IsValid()) {
+    diagnostics_ = program.Diagnostics();
+    return 0;
+  }
+
+#if TINT_BUILD_SPV_READER
+  if (input_ == InputFormat::kSpv &&
+      !SPIRVToolsValidationCheck(program, spirv_input)) {
+    FATAL_ERROR(
+        program.Diagnostics(),
+        "Fuzzing detected invalid input spirv not being caught by Tint");
+  }
+#endif  // TINT_BUILD_SPV_READER
+
+  RunInspector(&program);
+  diagnostics_ = program.Diagnostics();
+
+  if (transform_manager_) {
+    auto out = transform_manager_->Run(&program, *transform_inputs_);
+    if (!out.program.IsValid()) {
+      // Transforms can produce error messages for bad input.
+      // Catch ICEs and errors from non transform systems.
+      for (const auto& diag : out.program.Diagnostics()) {
+        if (diag.severity > diag::Severity::Error ||
+            diag.system != diag::System::Transform) {
+          VALIDITY_ERROR(program.Diagnostics(),
+                         "Fuzzing detected valid input program being "
+                         "transformed into an invalid output program");
+        }
+      }
+    }
+
+    program = std::move(out.program);
+    RunInspector(&program);
+  }
+
+  switch (output_) {
+    case OutputFormat::kWGSL: {
+#if TINT_BUILD_WGSL_WRITER
+      auto result = writer::wgsl::Generate(&program, options_wgsl_);
+      generated_wgsl_ = std::move(result.wgsl);
+      if (!result.success) {
+        VALIDITY_ERROR(
+            program.Diagnostics(),
+            "WGSL writer errored on validated input:\n" + result.error);
+      }
+#endif  // TINT_BUILD_WGSL_WRITER
+      break;
+    }
+    case OutputFormat::kSpv: {
+#if TINT_BUILD_SPV_WRITER
+      auto result = writer::spirv::Generate(&program, options_spirv_);
+      generated_spirv_ = std::move(result.spirv);
+      if (!result.success) {
+        VALIDITY_ERROR(
+            program.Diagnostics(),
+            "SPIR-V writer errored on validated input:\n" + result.error);
+      }
+
+      if (!SPIRVToolsValidationCheck(program, generated_spirv_)) {
+        VALIDITY_ERROR(program.Diagnostics(),
+                       "Fuzzing detected invalid spirv being emitted by Tint");
+      }
+
+#endif  // TINT_BUILD_SPV_WRITER
+      break;
+    }
+    case OutputFormat::kHLSL: {
+#if TINT_BUILD_HLSL_WRITER
+      auto result = writer::hlsl::Generate(&program, options_hlsl_);
+      generated_hlsl_ = std::move(result.hlsl);
+      if (!result.success) {
+        VALIDITY_ERROR(
+            program.Diagnostics(),
+            "HLSL writer errored on validated input:\n" + result.error);
+      }
+#endif  // TINT_BUILD_HLSL_WRITER
+      break;
+    }
+    case OutputFormat::kMSL: {
+#if TINT_BUILD_MSL_WRITER
+      auto result = writer::msl::Generate(&program, options_msl_);
+      generated_msl_ = std::move(result.msl);
+      if (!result.success) {
+        VALIDITY_ERROR(
+            program.Diagnostics(),
+            "MSL writer errored on validated input:\n" + result.error);
+      }
+#endif  // TINT_BUILD_MSL_WRITER
+      break;
+    }
+  }
+
+  return 0;
+}
+
+void CommonFuzzer::RunInspector(Program* program) {
+  inspector::Inspector inspector(program);
+  diagnostics_ = program->Diagnostics();
+
+  auto entry_points = inspector.GetEntryPoints();
+  CHECK_INSPECTOR(program, inspector);
+
+  auto constant_ids = inspector.GetConstantIDs();
+  CHECK_INSPECTOR(program, inspector);
+
+  auto constant_name_to_id = inspector.GetConstantNameToIdMap();
+  CHECK_INSPECTOR(program, inspector);
+
+  for (auto& ep : entry_points) {
+    inspector.GetRemappedNameForEntryPoint(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetStorageSize(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetUniformBufferResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetStorageBufferResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetSamplerResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetComparisonSamplerResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetSampledTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetMultisampledTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetWriteOnlyStorageTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetDepthTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetDepthMultisampledTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetExternalTextureResourceBindings(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetSamplerTextureUses(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+
+    inspector.GetWorkgroupStorageSize(ep.name);
+    CHECK_INSPECTOR(program, inspector);
+  }
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_common_fuzzer.h b/src/tint/fuzzers/tint_common_fuzzer.h
new file mode 100644
index 0000000..50bfb61
--- /dev/null
+++ b/src/tint/fuzzers/tint_common_fuzzer.h
@@ -0,0 +1,162 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_
+#define SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "include/tint/tint.h"
+
+#include "src/tint/fuzzers/data_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+// TODO(crbug.com/tint/1356): Add using shader reflection to generate options
+//                            that are potentially valid for Generate*Options
+//                            functions.
+/// Generates random set of options for SPIRV generation
+void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options);
+
+/// Generates random set of options for WGSL generation
+void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options);
+
+/// Generates random set of options for HLSL generation
+void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options);
+
+/// Generates random set of options for MSL generation
+void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options);
+
+/// Shader language the fuzzer is reading
+enum class InputFormat { kWGSL, kSpv };
+
+/// Shader language the fuzzer is emitting
+enum class OutputFormat { kWGSL, kSpv, kHLSL, kMSL };
+
+/// Generic runner for reading and emitting shaders using Tint, used by most
+/// fuzzers to share common code.
+class CommonFuzzer {
+ public:
+  /// Constructor
+  /// @param input shader language being read
+  /// @param output shader language being emitted
+  CommonFuzzer(InputFormat input, OutputFormat output);
+
+  /// Destructor
+  ~CommonFuzzer();
+
+  /// @param tm manager for transforms to run
+  /// @param inputs data for transforms to run
+  void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) {
+    assert((!tm || inputs) && "DataMap must be !nullptr if Manager !nullptr");
+    transform_manager_ = tm;
+    transform_inputs_ = inputs;
+  }
+
+  /// @param enabled if the input shader for run should be outputted to the log
+  void SetDumpInput(bool enabled) { dump_input_ = enabled; }
+
+  /// @param enabled if the shader being valid after parsing is being enforced.
+  /// If false, invalidation of the shader will cause an early exit, but not
+  /// throw an error.
+  /// If true invalidation will throw an error that is caught by libFuzzer and
+  /// will generate a crash report.
+  void SetEnforceValidity(bool enabled) { enforce_validity = enabled; }
+
+  /// Convert given shader from input to output format.
+  /// Will also apply provided transforms and run the inspector over the result.
+  /// @param data buffer of data that will interpreted as a byte array or string
+  ///             depending on the shader input format.
+  /// @param size number of elements in buffer
+  /// @returns 0, this is what libFuzzer expects
+  int Run(const uint8_t* data, size_t size);
+
+  /// @returns diagnostic messages generated while Run() is executed.
+  const tint::diag::List& Diagnostics() const { return diagnostics_; }
+
+  /// @returns if there are any errors in the diagnostic messages
+  bool HasErrors() const { return diagnostics_.contains_errors(); }
+
+  /// @returns generated SPIR-V binary, if SPIR-V was emitted.
+  const std::vector<uint32_t>& GetGeneratedSpirv() const {
+    return generated_spirv_;
+  }
+
+  /// @returns generated WGSL string, if WGSL was emitted.
+  const std::string& GetGeneratedWgsl() const { return generated_wgsl_; }
+
+  /// @returns generated HLSL string, if HLSL was emitted.
+  const std::string& GetGeneratedHlsl() const { return generated_hlsl_; }
+
+  /// @returns generated MSL string, if HLSL was emitted.
+  const std::string& GetGeneratedMsl() const { return generated_msl_; }
+
+  /// @param options SPIR-V emission options
+  void SetOptionsSpirv(const writer::spirv::Options& options) {
+    options_spirv_ = options;
+  }
+
+  /// @param options WGSL emission options
+  void SetOptionsWgsl(const writer::wgsl::Options& options) {
+    options_wgsl_ = options;
+  }
+
+  /// @param options HLSL emission options
+  void SetOptionsHlsl(const writer::hlsl::Options& options) {
+    options_hlsl_ = options;
+  }
+
+  /// @param options MSL emission options
+  void SetOptionsMsl(const writer::msl::Options& options) {
+    options_msl_ = options;
+  }
+
+ private:
+  InputFormat input_;
+  OutputFormat output_;
+  transform::Manager* transform_manager_ = nullptr;
+  transform::DataMap* transform_inputs_ = nullptr;
+  bool dump_input_ = false;
+  tint::diag::List diagnostics_;
+  bool enforce_validity = false;
+
+  std::vector<uint32_t> generated_spirv_;
+  std::string generated_wgsl_;
+  std::string generated_hlsl_;
+  std::string generated_msl_;
+
+  writer::spirv::Options options_spirv_;
+  writer::wgsl::Options options_wgsl_;
+  writer::hlsl::Options options_hlsl_;
+  writer::msl::Options options_msl_;
+
+#if TINT_BUILD_WGSL_READER
+  /// The source file needs to live at least as long as #diagnostics_
+  std::unique_ptr<Source::File> file_;
+#endif  // TINT_BUILD_WGSL_READER
+
+  /// Runs a series of reflection operations to exercise the Inspector API.
+  void RunInspector(Program* program);
+};
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_
diff --git a/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc b/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc
new file mode 100644
index 0000000..f8d437c
--- /dev/null
+++ b/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<transform::FirstIndexOffset>();
+
+  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_reader_writer_fuzzer.h b/src/tint/fuzzers/tint_reader_writer_fuzzer.h
new file mode 100644
index 0000000..e4a4e37
--- /dev/null
+++ b/src/tint/fuzzers/tint_reader_writer_fuzzer.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_
+#define SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_
+
+#include <memory>
+
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Wrapper around the common fuzzing class for tint_*_reader_*_writter fuzzers
+class ReaderWriterFuzzer : public CommonFuzzer {
+ public:
+  /// Constructor
+  /// Pass through to the CommonFuzzer constructor
+  /// @param input shader language being read
+  /// @param output shader language being emitted
+  ReaderWriterFuzzer(InputFormat input, OutputFormat output)
+      : CommonFuzzer(input, output) {}
+
+  /// Destructor
+  ~ReaderWriterFuzzer() {}
+
+  /// Pass through to the CommonFuzzer setter, but records if it has been
+  /// invoked.
+  /// @param tm manager for transforms to run
+  /// @param inputs data for transforms to run
+  void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) {
+    tm_set_ = true;
+    CommonFuzzer::SetTransformManager(tm, inputs);
+  }
+
+  /// Pass through to the CommonFuzzer implementation, but will setup a
+  /// robustness transform, if no other transforms have been set.
+  /// @param data buffer of data that will interpreted as a byte array or string
+  ///             depending on the shader input format.
+  /// @param size number of elements in buffer
+  /// @returns 0, this is what libFuzzer expects
+  int Run(const uint8_t* data, size_t size) {
+    if (!tm_set_) {
+      tb_ = std::make_unique<TransformBuilder>(data, size);
+      tb_->AddTransform<tint::transform::Robustness>();
+      SetTransformManager(tb_->manager(), tb_->data_map());
+    }
+
+    return CommonFuzzer::Run(data, size);
+  }
+
+ private:
+  bool tm_set_ = false;
+  std::unique_ptr<TransformBuilder> tb_;
+};
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn
new file mode 100644
index 0000000..e8d647d
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn
@@ -0,0 +1,36 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("../../../../tint_overrides_with_defaults.gni")
+
+if (build_with_chromium) {
+  source_set("tint_regex_fuzzer") {
+    public_configs = [
+      "${tint_root_dir}/src/tint:tint_config",
+      "${tint_root_dir}/src/tint:tint_common_config",
+    ]
+
+    deps = [ "${tint_root_dir}/src/tint/fuzzers:tint_fuzzer_common_src" ]
+
+    sources = [
+      "cli.cc",
+      "cli.h",
+      "fuzzer.cc",
+      "override_cli_params.h",
+      "wgsl_mutator.cc",
+      "wgsl_mutator.h",
+    ]
+  }
+}
diff --git a/fuzzers/tint_regex_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_regex_fuzzer/CMakeLists.txt
similarity index 100%
rename from fuzzers/tint_regex_fuzzer/CMakeLists.txt
rename to src/tint/fuzzers/tint_regex_fuzzer/CMakeLists.txt
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg b/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg
new file mode 100644
index 0000000..96988ad
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/c++11
\ No newline at end of file
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/cli.cc b/src/tint/fuzzers/tint_regex_fuzzer/cli.cc
new file mode 100644
index 0000000..1fd3e10
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/cli.cc
@@ -0,0 +1,126 @@
+// 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/fuzzers/tint_regex_fuzzer/cli.h"
+
+#include <cstring>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+const char* const kHelpMessage = R"(
+This is a fuzzer for the Tint compiler that works by mutating a WGSL shader.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+  -tint_fuzzing_target=
+                       Specifies the shading language to target during fuzzing.
+                       This must be one or a combination of `wgsl`, `spv`, `hlsl`,
+                       `msl` (without `) separated by commas. By default it's
+                       `wgsl,msl,hlsl,spv`.
+
+  -tint_help
+                       Show this message. Note that there is also a -help=1
+                       parameter that will display libfuzzer's help message.
+)";
+
+bool HasPrefix(const char* str, const char* prefix) {
+  return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+[[noreturn]] void InvalidParam(const char* param) {
+  std::cout << "Invalid value for " << param << std::endl;
+  std::cout << kHelpMessage << std::endl;
+  exit(1);
+}
+
+bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
+  if (!strcmp(value, "wgsl")) {
+    *out = FuzzingTarget::kWgsl;
+  } else if (!strcmp(value, "spv")) {
+    *out = FuzzingTarget::kSpv;
+  } else if (!strcmp(value, "msl")) {
+    *out = FuzzingTarget::kMsl;
+  } else if (!strcmp(value, "hlsl")) {
+    *out = FuzzingTarget::kHlsl;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+CliParams ParseCliParams(int* argc, char** argv) {
+  CliParams cli_params;
+  auto help = false;
+
+  for (int i = *argc - 1; i > 0; --i) {
+    auto param = argv[i];
+    auto recognized_parameter = true;
+
+    if (HasPrefix(param, "-tint_fuzzing_target=")) {
+      auto result = FuzzingTarget::kNone;
+
+      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
+      for (std::string value; std::getline(ss, value, ',');) {
+        auto tmp = FuzzingTarget::kNone;
+        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
+          InvalidParam(param);
+        }
+        result = result | tmp;
+      }
+
+      if (result == FuzzingTarget::kNone) {
+        InvalidParam(param);
+      }
+
+      cli_params.fuzzing_target = result;
+    } else if (!strcmp(param, "-tint_help")) {
+      help = true;
+    } else {
+      recognized_parameter = false;
+    }
+
+    if (recognized_parameter) {
+      // Remove the recognized parameter from the list of all parameters by
+      // swapping it with the last one. This will suppress warnings in the
+      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
+      // that all user-defined parameters start with two dashes. However, we are
+      // forced to use a single one to make the fuzzer compatible with the
+      // ClusterFuzz.
+      std::swap(argv[i], argv[*argc - 1]);
+      *argc -= 1;
+    }
+  }
+
+  if (help) {
+    std::cout << kHelpMessage << std::endl;
+    exit(0);
+  }
+
+  return cli_params;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/cli.h b/src/tint/fuzzers/tint_regex_fuzzer/cli.h
new file mode 100644
index 0000000..55048c5
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/cli.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_
+#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_
+
+#include <cstdint>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+/// The backend this fuzzer will test.
+enum class FuzzingTarget {
+  kNone = 0,
+  kHlsl = 1 << 0,
+  kMsl = 1 << 1,
+  kSpv = 1 << 2,
+  kWgsl = 1 << 3,
+  kAll = kHlsl | kMsl | kSpv | kWgsl
+};
+
+inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
+/// help message
+struct CliParams {
+  /// Compiler backends we want to fuzz.
+  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
+};
+
+/// @brief Parses CLI parameters.
+///
+/// This function will exit the process with non-zero return code if some
+/// parameters are invalid. This function will remove recognized parameters from
+/// `argv` and adjust `argc` accordingly.
+///
+/// @param argc - the total number of parameters.
+/// @param argv - array of all CLI parameters.
+/// @return parsed parameters.
+CliParams ParseCliParams(int* argc, char** argv);
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
new file mode 100644
index 0000000..bc8dc78
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
@@ -0,0 +1,156 @@
+// 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 <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+#include "src/tint/fuzzers/transform_builder.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+CliParams cli_params{};
+
+enum class MutationKind {
+  kSwapIntervals,
+  kDeleteInterval,
+  kDuplicateInterval,
+  kReplaceIdentifier,
+  kReplaceLiteral,
+  kInsertReturnStatement,
+  kNumMutationKinds
+};
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
+  // is invalid.
+  cli_params = ParseCliParams(argc, *argv);
+  // For some fuzz targets it is desirable to force the values of certain CLI
+  // parameters after parsing.
+  OverrideCliParams(cli_params);
+  return 0;
+}
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
+                                          size_t size,
+                                          size_t max_size,
+                                          unsigned seed) {
+  std::string wgsl_code(data, data + size);
+  const std::vector<std::string> delimiters{";"};
+  RandomGenerator generator(seed);
+
+  std::string delimiter =
+      delimiters[generator.GetUInt32(static_cast<uint32_t>(delimiters.size()))];
+
+  MutationKind mutation_kind = static_cast<MutationKind>(generator.GetUInt32(
+      static_cast<uint32_t>(MutationKind::kNumMutationKinds)));
+
+  switch (mutation_kind) {
+    case MutationKind::kSwapIntervals:
+      if (!SwapRandomIntervals(delimiter, wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    case MutationKind::kDeleteInterval:
+      if (!DeleteRandomInterval(delimiter, wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    case MutationKind::kDuplicateInterval:
+      if (!DuplicateRandomInterval(delimiter, wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    case MutationKind::kReplaceIdentifier:
+      if (!ReplaceRandomIdentifier(wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    case MutationKind::kReplaceLiteral:
+      if (!ReplaceRandomIntLiteral(wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    case MutationKind::kInsertReturnStatement:
+      if (!InsertReturnStatement(wgsl_code, generator)) {
+        return 0;
+      }
+      break;
+
+    default:
+      assert(false && "Unreachable");
+      return 0;
+  }
+
+  if (wgsl_code.size() > max_size) {
+    return 0;
+  }
+
+  memcpy(data, wgsl_code.c_str(), wgsl_code.size());
+  return wgsl_code.size();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size == 0) {
+    return 0;
+  }
+
+  struct Target {
+    FuzzingTarget fuzzing_target;
+    OutputFormat output_format;
+    const char* name;
+  };
+
+  Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"},
+                      {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"},
+                      {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"},
+                      {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}};
+
+  for (auto target : targets) {
+    if ((target.fuzzing_target & cli_params.fuzzing_target) !=
+        target.fuzzing_target) {
+      continue;
+    }
+
+    TransformBuilder tb(data, size);
+    tb.AddTransform<tint::transform::Robustness>();
+
+    CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format);
+    fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+
+    fuzzer.Run(data, size);
+  }
+
+  return 0;
+}
+
+}  // namespace
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h
new file mode 100644
index 0000000..445f524
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
+#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+/// @brief Allows CLI parameters to be overridden.
+///
+/// This function allows fuzz targets to override particular CLI parameters,
+/// for example forcing a particular back-end to be targeted.
+///
+/// @param cli_params - the parsed CLI parameters to be updated.
+void OverrideCliParams(CliParams& cli_params);
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc b/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
new file mode 100644
index 0000000..d58942d
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
@@ -0,0 +1,521 @@
+// 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 <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+// Swaps two non-consecutive regions in the edge
+TEST(SwapRegionsTest, SwapIntervalsEdgeNonConsecutive) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;";
+  std::string all_regions = R1 + R2 + R3;
+
+  // this call should swap R1 with R3.
+  SwapIntervals(0, R1.length(), R1.length() + R2.length(), R3.length(),
+                all_regions);
+
+  ASSERT_EQ(R3 + R2 + R1, all_regions);
+}
+
+// Swaps two non-consecutive regions not in the edge
+TEST(SwapRegionsTest, SwapIntervalsNonConsecutiveNonEdge) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // this call should swap R2 with R4.
+  SwapIntervals(R1.length(), R2.length(),
+                R1.length() + R2.length() + R3.length(), R4.length(),
+                all_regions);
+
+  ASSERT_EQ(R1 + R4 + R3 + R2 + R5, all_regions);
+}
+
+// Swaps two consecutive regions not in the edge (sorrounded by other
+// regions)
+TEST(SwapRegionsTest, SwapIntervalsConsecutiveEdge) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4;
+
+  // this call should swap R2 with R3.
+  SwapIntervals(R1.length(), R2.length(), R1.length() + R2.length(),
+                R3.length(), all_regions);
+
+  ASSERT_EQ(R1 + R3 + R2 + R4, all_regions);
+}
+
+// Swaps two consecutive regions not in the edge (not sorrounded by other
+// regions)
+TEST(SwapRegionsTest, SwapIntervalsConsecutiveNonEdge) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // this call should swap R4 with R5.
+  SwapIntervals(R1.length() + R2.length() + R3.length(), R4.length(),
+                R1.length() + R2.length() + R3.length() + R4.length(),
+                R5.length(), all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R5 + R4, all_regions);
+}
+
+// Deletes the first region.
+TEST(DeleteRegionTest, DeleteFirstRegion) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should delete R1.
+  DeleteInterval(0, R1.length(), all_regions);
+
+  ASSERT_EQ(";" + R2 + R3 + R4 + R5, all_regions);
+}
+
+// Deletes the last region.
+TEST(DeleteRegionTest, DeleteLastRegion) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should delete R5.
+  DeleteInterval(R1.length() + R2.length() + R3.length() + R4.length(),
+                 R5.length(), all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R4 + ";", all_regions);
+}
+
+// Deletes the middle region.
+TEST(DeleteRegionTest, DeleteMiddleRegion) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should delete R3.
+  DeleteInterval(R1.length() + R2.length(), R3.length(), all_regions);
+
+  ASSERT_EQ(R1 + R2 + ";" + R4 + R5, all_regions);
+}
+
+TEST(InsertRegionTest, InsertRegionTest1) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should insert R2 after R4.
+  DuplicateInterval(R1.length(), R2.length(),
+                    R1.length() + R2.length() + R3.length() + R4.length() - 1,
+                    all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R4 + R2.substr(1, R2.size() - 1) + R5, all_regions);
+}
+
+TEST(InsertRegionTest, InsertRegionTest2) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should insert R3 after R1.
+  DuplicateInterval(R1.length() + R2.length(), R3.length(), R1.length() - 1,
+                    all_regions);
+
+  ASSERT_EQ(R1 + R3.substr(1, R3.length() - 1) + R2 + R3 + R4 + R5,
+            all_regions);
+}
+
+TEST(InsertRegionTest, InsertRegionTest3) {
+  std::string R1 = ";region1;", R2 = ";regionregion2;",
+              R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;",
+              R5 = ";regionregionregionregionregion5;";
+
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // This call should insert R2 after R5.
+  DuplicateInterval(R1.length(), R2.length(), all_regions.length() - 1,
+                    all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R4 + R5 + R2.substr(1, R2.length() - 1),
+            all_regions);
+}
+
+TEST(ReplaceIdentifierTest, ReplaceIdentifierTest1) {
+  std::string R1 = "|region1|", R2 = "; region2;",
+              R3 = "---------region3---------", R4 = "++region4++",
+              R5 = "***region5***";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // Replaces R3 with R1.
+  ReplaceRegion(0, R1.length(), R1.length() + R2.length(), R3.length(),
+                all_regions);
+
+  ASSERT_EQ(R1 + R2 + R1 + R4 + R5, all_regions);
+}
+
+TEST(ReplaceIdentifierTest, ReplaceIdentifierTest2) {
+  std::string R1 = "|region1|", R2 = "; region2;",
+              R3 = "---------region3---------", R4 = "++region4++",
+              R5 = "***region5***";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // Replaces R5 with R3.
+  ReplaceRegion(R1.length() + R2.length(), R3.length(),
+                R1.length() + R2.length() + R3.length() + R4.length(),
+                R5.length(), all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R4 + R3, all_regions);
+}
+
+TEST(GetIdentifierTest, GetIdentifierTest1) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+      }
+      @stage(vertex)
+      fn vertex_main() -> @builtin(position) vec4<f32> {
+         clamp_0acf8f();"
+         return vec4<f32>();
+      }
+      @stage(fragment)
+      fn fragment_main() {
+        clamp_0acf8f();
+      }
+      @stage(compute) @workgroup_size(1)
+      fn compute_main() {"
+        var<private> foo: f32 = 0.0;
+        clamp_0acf8f();
+      })";
+
+  std::vector<std::pair<size_t, size_t>> identifiers_pos =
+      GetIdentifiers(wgsl_code);
+
+  std::vector<std::pair<size_t, size_t>> ground_truth = {
+      std::make_pair(3, 12),   std::make_pair(28, 3),  std::make_pair(37, 4),
+      std::make_pair(49, 5),   std::make_pair(60, 3),  std::make_pair(68, 4),
+      std::make_pair(81, 4),   std::make_pair(110, 5), std::make_pair(130, 2),
+      std::make_pair(140, 4),  std::make_pair(151, 7), std::make_pair(169, 4),
+      std::make_pair(190, 12), std::make_pair(216, 6), std::make_pair(228, 3),
+      std::make_pair(251, 5),  std::make_pair(273, 2), std::make_pair(285, 4),
+      std::make_pair(302, 12), std::make_pair(333, 5), std::make_pair(349, 14),
+      std::make_pair(373, 2),  std::make_pair(384, 4), std::make_pair(402, 3),
+      std::make_pair(415, 3),  std::make_pair(420, 3), std::make_pair(439, 12)};
+
+  ASSERT_EQ(ground_truth, identifiers_pos);
+}
+
+TEST(TestGetLiteralsValues, TestGetLiteralsValues1) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+      }
+      @stage(vertex)
+      fn vertex_main() -> @builtin(position) vec4<f32> {
+        clamp_0acf8f();
+        var foo_1: i32 = 3;
+        return vec4<f32>();
+      }
+      @stage(fragment)
+      fn fragment_main() {
+        clamp_0acf8f();
+      }
+      @stage(compute) @workgroup_size(1)
+      fn compute_main() {
+        var<private> foo: f32 = 0.0;
+        var foo_2: i32 = 10;
+        clamp_0acf8f();
+      }
+      foo_1 = 5 + 7;
+      var foo_3 : i32 = -20;)";
+
+  std::vector<std::pair<size_t, size_t>> literals_pos =
+      GetIntLiterals(wgsl_code);
+
+  std::vector<std::string> ground_truth = {"3", "10", "5", "7", "-20"};
+
+  std::vector<std::string> result;
+
+  for (auto pos : literals_pos) {
+    result.push_back(wgsl_code.substr(pos.first, pos.second));
+  }
+
+  ASSERT_EQ(ground_truth, result);
+}
+
+TEST(InsertReturnTest, FindClosingBrace) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+        if(false){
+
+        } else{
+          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+          }
+        }
+        @stage(vertex)
+        fn vertex_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f();
+          var foo_1: i32 = 3;
+          return vec4<f32>();
+        }
+        @stage(fragment)
+        fn fragment_main() {
+          clamp_0acf8f();
+        }
+        @stage(compute) @workgroup_size(1)
+        fn compute_main() {
+          var<private> foo: f32 = 0.0;
+          var foo_2: i32 = 10;
+          clamp_0acf8f();
+        }
+        foo_1 = 5 + 7;
+        var foo_3 : i32 = -20;
+      )";
+  size_t opening_bracket_pos = 18;
+  size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code);
+
+  // The -1 is needed since the function body starts after the left bracket.
+  std::string function_body = wgsl_code.substr(
+      opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1);
+  std::string expected =
+      R"(
+        if(false){
+
+        } else{
+          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+          }
+        )";
+  ASSERT_EQ(expected, function_body);
+}
+
+TEST(InsertReturnTest, FindClosingBraceFailing) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+      // This comment } causes the test to fail.
+      "if(false){
+
+      } else{
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+        }
+      }
+      @stage(vertex)
+      fn vertex_main() -> @builtin(position) vec4<f32> {
+        clamp_0acf8f();
+        var foo_1: i32 = 3;
+        return vec4<f32>();
+      }
+      @stage(fragment)
+      fn fragment_main() {
+        clamp_0acf8f();
+      }
+      @stage(compute) @workgroup_size(1)
+      fn compute_main() {
+        var<private> foo: f32 = 0.0;
+        var foo_2: i32 = 10;
+        clamp_0acf8f();
+      }
+      foo_1 = 5 + 7;
+      var foo_3 : i32 = -20;)";
+  size_t opening_bracket_pos = 18;
+  size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code);
+
+  // The -1 is needed since the function body starts after the left bracket.
+  std::string function_body = wgsl_code.substr(
+      opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1);
+  std::string expected =
+      R"(// This comment } causes the test to fail.
+      "if(false){
+
+      } else{
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+        })";
+  ASSERT_NE(expected, function_body);
+}
+
+TEST(TestInsertReturn, TestInsertReturn1) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+      }
+      @stage(vertex)
+      fn vertex_main() -> @builtin(position) vec4<f32> {
+        clamp_0acf8f();
+        var foo_1: i32 = 3;
+        return vec4<f32>();
+      }
+      @stage(fragment)
+      fn fragment_main() {
+        clamp_0acf8f();
+      }
+      @stage(compute) @workgroup_size(1)
+      fn compute_main() {
+        var<private> foo: f32 = 0.0;
+        var foo_2: i32 = 10;
+        clamp_0acf8f();
+      }
+      foo_1 = 5 + 7;
+      var foo_3 : i32 = -20;)";
+
+  std::vector<size_t> semicolon_pos;
+  for (size_t pos = wgsl_code.find(";", 0); pos != std::string::npos;
+       pos = wgsl_code.find(";", pos + 1)) {
+    semicolon_pos.push_back(pos);
+  }
+
+  // should insert a return true statement after the first semicolon of the
+  // first function the the WGSL-like string above.
+  wgsl_code.insert(semicolon_pos[0] + 1, "return true;");
+
+  std::string expected_wgsl_code =
+      R"(fn clamp_0acf8f() {
+        var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());return true;
+      }
+      @stage(vertex)
+      fn vertex_main() -> @builtin(position) vec4<f32> {
+        clamp_0acf8f();
+        var foo_1: i32 = 3;
+        return vec4<f32>();
+      }
+      @stage(fragment)
+      fn fragment_main() {
+        clamp_0acf8f();
+      }
+      @stage(compute) @workgroup_size(1)
+      fn compute_main() {
+        var<private> foo: f32 = 0.0;
+        var foo_2: i32 = 10;
+        clamp_0acf8f();
+      }
+      foo_1 = 5 + 7;
+      var foo_3 : i32 = -20;)";
+
+  ASSERT_EQ(expected_wgsl_code, wgsl_code);
+}
+
+TEST(TestInsertReturn, TestFunctionPositions) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());
+        }
+        @stage(vertex)
+        fn vertex_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f();
+          var foo_1: i32 = 3;
+          return vec4<f32>();
+        }
+        @stage(fragment)
+        fn fragment_main() {
+          clamp_0acf8f();
+        }
+        @stage(compute) @workgroup_size(1)
+        fn compute_main() {
+          var<private> foo: f32 = 0.0;
+          var foo_2: i32 = 10;
+          clamp_0acf8f();
+        }
+        fn vert_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f();
+          var foo_1: i32 = 3;
+          return vec4<f32>();
+        }
+        foo_1 = 5 + 7;
+        var foo_3 : i32 = -20;)";
+
+  std::vector<size_t> function_positions = GetFunctionBodyPositions(wgsl_code);
+  std::vector<size_t> expected_positions = {187, 607};
+  ASSERT_EQ(expected_positions, function_positions);
+}
+
+TEST(TestInsertReturn, TestMissingSemicolon) {
+  std::string wgsl_code =
+      R"(fn clamp_0acf8f() {
+          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>())
+        }
+        @stage(vertex)
+        fn vertex_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f()
+          var foo_1: i32 = 3
+          return vec4<f32>()
+        }
+        @stage(fragment)
+        fn fragment_main() {
+          clamp_0acf8f();
+        }
+        @stage(compute) @workgroup_size(1)
+        fn compute_main() {
+          var<private> foo: f32 = 0.0;
+          var foo_2: i32 = 10;
+          clamp_0acf8f();
+        }
+        fn vert_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f()
+          var foo_1: i32 = 3
+          return vec4<f32>()
+        }
+        foo_1 = 5 + 7;
+        var foo_3 : i32 = -20;)";
+
+  RandomGenerator generator(0);
+  InsertReturnStatement(wgsl_code, generator);
+
+  // No semicolons found in the function's body, so wgsl_code
+  // should remain unchanged.
+  std::string expected_wgsl_code =
+      R"(fn clamp_0acf8f() {
+          var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>())
+        }
+        @stage(vertex)
+        fn vertex_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f()
+          var foo_1: i32 = 3
+          return vec4<f32>()
+        }
+        @stage(fragment)
+        fn fragment_main() {
+          clamp_0acf8f();
+        }
+        @stage(compute) @workgroup_size(1)
+        fn compute_main() {
+          var<private> foo: f32 = 0.0;
+          var foo_2: i32 = 10;
+          clamp_0acf8f();
+        }
+        fn vert_main() -> @builtin(position) vec4<f32> {
+          clamp_0acf8f()
+          var foo_1: i32 = 3
+          return vec4<f32>()
+        }
+        foo_1 = 5 + 7;
+        var foo_3 : i32 = -20;)";
+  ASSERT_EQ(expected_wgsl_code, wgsl_code);
+}
+
+}  // namespace
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc
new file mode 100644
index 0000000..045ecfd
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc
@@ -0,0 +1,28 @@
+// 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/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+void OverrideCliParams(CliParams& /*unused*/) {
+  // Leave the CLI parameters unchanged.
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc
new file mode 100644
index 0000000..e89dd97
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc
new file mode 100644
index 0000000..23afc86
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kMsl;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc
new file mode 100644
index 0000000..18a1a3e
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kSpv;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc
new file mode 100644
index 0000000..8fbd395
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+void OverrideCliParams(CliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
new file mode 100644
index 0000000..b8d9160
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
@@ -0,0 +1,358 @@
+// 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/fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+
+#include <cassert>
+#include <cstring>
+#include <map>
+#include <regex>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
+                                         const std::string& wgsl_code) {
+  std::vector<size_t> result;
+  for (size_t pos = wgsl_code.find(delimiter, 0); pos != std::string::npos;
+       pos = wgsl_code.find(delimiter, pos + 1)) {
+    result.push_back(pos);
+  }
+
+  return result;
+}
+
+std::vector<std::pair<size_t, size_t>> GetIdentifiers(
+    const std::string& wgsl_code) {
+  std::vector<std::pair<size_t, size_t>> result;
+
+  // This regular expression works by looking for a character that
+  // is not part of an identifier followed by a WGSL identifier, followed
+  // by a character which cannot be part of a WGSL identifer. The regex
+  // for the WGSL identifier is obtained from:
+  // https://www.w3.org/TR/WGSL/#identifiers.
+  std::regex wgsl_identifier_regex(
+      "[^a-zA-Z]([a-zA-Z][0-9a-zA-Z_]*)[^0-9a-zA-Z_]");
+
+  std::smatch match;
+
+  std::string::const_iterator search_start(wgsl_code.cbegin());
+  std::string prefix;
+
+  while (regex_search(search_start, wgsl_code.cend(), match,
+                      wgsl_identifier_regex) == true) {
+    prefix += match.prefix();
+    result.push_back(std::make_pair(prefix.size() + 1, match.str(1).size()));
+    prefix += match.str(0);
+    search_start = match.suffix().first;
+  }
+  return result;
+}
+
+std::vector<std::pair<size_t, size_t>> GetIntLiterals(const std::string& s) {
+  std::vector<std::pair<size_t, size_t>> result;
+
+  // Looks for integer literals in decimal or hexadecimal form.
+  // Regex obtained here: https://www.w3.org/TR/WGSL/#literals
+  std::regex int_literal_regex("-?0x[0-9a-fA-F]+ | 0 | -?[1-9][0-9]*");
+  std::regex uint_literal_regex("0x[0-9a-fA-F]+u | 0u | [1-9][0-9]*u");
+  std::smatch match;
+
+  std::string::const_iterator search_start(s.cbegin());
+  std::string prefix = "";
+
+  while (regex_search(search_start, s.cend(), match, int_literal_regex) ||
+         regex_search(search_start, s.cend(), match, uint_literal_regex)) {
+    prefix += match.prefix();
+    result.push_back(
+        std::make_pair(prefix.size() + 1, match.str(0).size() - 1));
+    prefix += match.str(0);
+    search_start = match.suffix().first;
+  }
+  return result;
+}
+
+size_t FindClosingBrace(size_t opening_bracket_pos,
+                        const std::string& wgsl_code) {
+  size_t open_bracket_count = 1;
+  size_t pos = opening_bracket_pos + 1;
+  while (open_bracket_count >= 1 && pos < wgsl_code.size()) {
+    if (wgsl_code[pos] == '{') {
+      ++open_bracket_count;
+    } else if (wgsl_code[pos] == '}') {
+      --open_bracket_count;
+    }
+    ++pos;
+  }
+  return (pos == wgsl_code.size() && open_bracket_count >= 1) ? 0 : pos - 1;
+}
+
+std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code) {
+  // Finds all the functions with a non-void return value.
+  std::regex function_regex("fn.*?->.*?\\{");
+  std::smatch match;
+  std::vector<size_t> result;
+
+  auto search_start(wgsl_code.cbegin());
+  std::string prefix = "";
+
+  while (std::regex_search(search_start, wgsl_code.cend(), match,
+                           function_regex)) {
+    result.push_back(
+        static_cast<size_t>(match.suffix().first - wgsl_code.cbegin() - 1L));
+    search_start = match.suffix().first;
+  }
+  return result;
+}
+
+bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator) {
+  std::vector<size_t> function_body_positions =
+      GetFunctionBodyPositions(wgsl_code);
+
+  // No function was found in wgsl_code.
+  if (function_body_positions.empty()) {
+    return false;
+  }
+
+  // Pick a random function's opening bracket, find the corresponding closing
+  // bracket, and find a semi-colon within the function body.
+  size_t left_bracket_pos = generator.GetRandomElement(function_body_positions);
+
+  size_t right_bracket_pos = FindClosingBrace(left_bracket_pos, wgsl_code);
+
+  if (right_bracket_pos == 0) {
+    return false;
+  }
+
+  std::vector<size_t> semicolon_positions;
+  for (size_t pos = wgsl_code.find(";", left_bracket_pos + 1);
+       pos < right_bracket_pos; pos = wgsl_code.find(";", pos + 1)) {
+    semicolon_positions.push_back(pos);
+  }
+
+  if (semicolon_positions.empty()) {
+    return false;
+  }
+
+  size_t semicolon_position = generator.GetRandomElement(semicolon_positions);
+
+  // Get all identifiers and integer literals to use as potential return values.
+  std::vector<std::pair<size_t, size_t>> identifiers =
+      GetIdentifiers(wgsl_code);
+  auto return_values = identifiers;
+  std::vector<std::pair<size_t, size_t>> int_literals =
+      GetIntLiterals(wgsl_code);
+  return_values.insert(return_values.end(), int_literals.begin(),
+                       int_literals.end());
+  std::pair<size_t, size_t> return_value =
+      generator.GetRandomElement(return_values);
+  std::string return_statement =
+      "return " + wgsl_code.substr(return_value.first, return_value.second) +
+      ";";
+
+  // Insert the return statement immediately after the semicolon.
+  wgsl_code.insert(semicolon_position + 1, return_statement);
+  return true;
+}
+
+void SwapIntervals(size_t idx1,
+                   size_t reg1_len,
+                   size_t idx2,
+                   size_t reg2_len,
+                   std::string& wgsl_code) {
+  std::string region_1 = wgsl_code.substr(idx1 + 1, reg1_len - 1);
+
+  std::string region_2 = wgsl_code.substr(idx2 + 1, reg2_len - 1);
+
+  // The second transformation is done first as it doesn't affect idx2.
+  wgsl_code.replace(idx2 + 1, region_2.size(), region_1);
+
+  wgsl_code.replace(idx1 + 1, region_1.size(), region_2);
+}
+
+void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code) {
+  wgsl_code.erase(idx1 + 1, reg_len - 1);
+}
+
+void DuplicateInterval(size_t idx1,
+                       size_t reg1_len,
+                       size_t idx2,
+                       std::string& wgsl_code) {
+  std::string region = wgsl_code.substr(idx1 + 1, reg1_len - 1);
+  wgsl_code.insert(idx2 + 1, region);
+}
+
+void ReplaceRegion(size_t idx1,
+                   size_t id1_len,
+                   size_t idx2,
+                   size_t id2_len,
+                   std::string& wgsl_code) {
+  std::string region_1 = wgsl_code.substr(idx1, id1_len);
+  std::string region_2 = wgsl_code.substr(idx2, id2_len);
+  wgsl_code.replace(idx2, region_2.size(), region_1);
+}
+
+void ReplaceInterval(size_t start_index,
+                     size_t length,
+                     std::string replacement_text,
+                     std::string& wgsl_code) {
+  std::string region_1 = wgsl_code.substr(start_index, length);
+  wgsl_code.replace(start_index, length, replacement_text);
+}
+
+bool SwapRandomIntervals(const std::string& delimiter,
+                         std::string& wgsl_code,
+                         RandomGenerator& generator) {
+  std::vector<size_t> delimiter_positions =
+      FindDelimiterIndices(delimiter, wgsl_code);
+
+  // Need to have at least 3 indices.
+  if (delimiter_positions.size() < 3) {
+    return false;
+  }
+
+  // Choose indices:
+  //   interval_1_start < interval_1_end <= interval_2_start < interval_2_end
+  uint32_t interval_1_start = generator.GetUInt32(
+      static_cast<uint32_t>(delimiter_positions.size()) - 2u);
+  uint32_t interval_1_end = generator.GetUInt32(
+      interval_1_start + 1u,
+      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
+  uint32_t interval_2_start = generator.GetUInt32(
+      interval_1_end, static_cast<uint32_t>(delimiter_positions.size()) - 1u);
+  uint32_t interval_2_end = generator.GetUInt32(
+      interval_2_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
+
+  SwapIntervals(delimiter_positions[interval_1_start],
+                delimiter_positions[interval_1_end] -
+                    delimiter_positions[interval_1_start],
+                delimiter_positions[interval_2_start],
+                delimiter_positions[interval_2_end] -
+                    delimiter_positions[interval_2_start],
+                wgsl_code);
+
+  return true;
+}
+
+bool DeleteRandomInterval(const std::string& delimiter,
+                          std::string& wgsl_code,
+                          RandomGenerator& generator) {
+  std::vector<size_t> delimiter_positions =
+      FindDelimiterIndices(delimiter, wgsl_code);
+
+  // Need to have at least 2 indices.
+  if (delimiter_positions.size() < 2) {
+    return false;
+  }
+
+  uint32_t interval_start = generator.GetUInt32(
+      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
+  uint32_t interval_end = generator.GetUInt32(
+      interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
+
+  DeleteInterval(
+      delimiter_positions[interval_start],
+      delimiter_positions[interval_end] - delimiter_positions[interval_start],
+      wgsl_code);
+
+  return true;
+}
+
+bool DuplicateRandomInterval(const std::string& delimiter,
+                             std::string& wgsl_code,
+                             RandomGenerator& generator) {
+  std::vector<size_t> delimiter_positions =
+      FindDelimiterIndices(delimiter, wgsl_code);
+
+  // Need to have at least 2 indices
+  if (delimiter_positions.size() < 2) {
+    return false;
+  }
+
+  uint32_t interval_start = generator.GetUInt32(
+      static_cast<uint32_t>(delimiter_positions.size()) - 1u);
+  uint32_t interval_end = generator.GetUInt32(
+      interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size()));
+  uint32_t duplication_point =
+      generator.GetUInt32(static_cast<uint32_t>(delimiter_positions.size()));
+
+  DuplicateInterval(
+      delimiter_positions[interval_start],
+      delimiter_positions[interval_end] - delimiter_positions[interval_start],
+      delimiter_positions[duplication_point], wgsl_code);
+
+  return true;
+}
+
+bool ReplaceRandomIdentifier(std::string& wgsl_code,
+                             RandomGenerator& generator) {
+  std::vector<std::pair<size_t, size_t>> identifiers =
+      GetIdentifiers(wgsl_code);
+
+  // Need at least 2 identifiers
+  if (identifiers.size() < 2) {
+    return false;
+  }
+
+  uint32_t id1_index =
+      generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
+  uint32_t id2_index =
+      generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
+
+  // The two identifiers must be different
+  while (id1_index == id2_index) {
+    id2_index = generator.GetUInt32(static_cast<uint32_t>(identifiers.size()));
+  }
+
+  ReplaceRegion(identifiers[id1_index].first, identifiers[id1_index].second,
+                identifiers[id2_index].first, identifiers[id2_index].second,
+                wgsl_code);
+
+  return true;
+}
+
+bool ReplaceRandomIntLiteral(std::string& wgsl_code,
+                             RandomGenerator& generator) {
+  std::vector<std::pair<size_t, size_t>> literals = GetIntLiterals(wgsl_code);
+
+  // Need at least one integer literal
+  if (literals.size() < 1) {
+    return false;
+  }
+
+  uint32_t literal_index =
+      generator.GetUInt32(static_cast<uint32_t>(literals.size()));
+
+  // INT_MAX = 2147483647, INT_MIN = -2147483648
+  std::vector<std::string> boundary_values = {
+      "2147483647", "-2147483648", "1", "-1", "0", "4294967295"};
+
+  uint32_t boundary_index =
+      generator.GetUInt32(static_cast<uint32_t>(boundary_values.size()));
+
+  ReplaceInterval(literals[literal_index].first, literals[literal_index].second,
+                  boundary_values[boundary_index], wgsl_code);
+
+  return true;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
new file mode 100644
index 0000000..47e6adb
--- /dev/null
+++ b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
@@ -0,0 +1,186 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+/// A function that given a delimiter, returns a vector that contains
+/// all the positions of the delimiter in the WGSL code.
+/// @param delimiter - the delimiter of the enclosed region.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @return a vector with the positions of the delimiter in the WGSL code.
+std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
+                                         const std::string& wgsl_code);
+
+/// A function that finds all the identifiers in a WGSL-like string.
+/// @param wgsl_code - the WGSL-like string where the identifiers will be found.
+/// @return a vector with the positions and the length of all the
+/// identifiers in wgsl_code.
+std::vector<std::pair<size_t, size_t>> GetIdentifiers(
+    const std::string& wgsl_code);
+
+/// A function that returns returns the starting position
+/// and the length of all the integer literals in a WGSL-like string.
+/// @param wgsl_code - the WGSL-like string where the int literals
+/// will be found.
+/// @return a vector with the starting positions and the length
+/// of all the integer literals.
+std::vector<std::pair<size_t, size_t>> GetIntLiterals(
+    const std::string& wgsl_code);
+
+/// Finds a possible closing brace corresponding to the opening
+/// brace at position opening_bracket_pos.
+/// @param opening_bracket_pos - the position of the opening brace.
+/// @param wgsl_code - the WGSL-like string where the closing brace.
+/// @return the position of the closing bracket or 0 if there is no closing
+/// brace.
+size_t FindClosingBrace(size_t opening_bracket_pos,
+                        const std::string& wgsl_code);
+
+/// Returns the starting_position of the bodies of the functions
+/// that follow the regular expression: fn.*?->.*?\\{, which searches for the
+/// keyword fn followed by the function name, its return type and opening brace.
+/// @param wgsl_code - the WGSL-like string where the functions will be
+/// searched.
+/// @return a vector with the starting position of the function bodies in
+/// wgsl_code.
+std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code);
+
+/// Given 4 indices, idx1, idx2, idx3 and idx4 it swaps the regions
+/// in the interval (idx1, idx2] with the region in the interval (idx3, idx4]
+/// in wgsl_text.
+/// @param idx1 - starting index of the first region.
+/// @param reg1_len - length of the first region.
+/// @param idx2 - starting index of the second region.
+/// @param reg2_len - length of the second region.
+/// @param wgsl_code - the string where the swap will occur.
+void SwapIntervals(size_t idx1,
+                   size_t reg1_len,
+                   size_t idx2,
+                   size_t reg2_len,
+                   std::string& wgsl_code);
+
+/// Given index idx1 it delets the region of length interval_len
+/// starting at index idx1;
+/// @param idx1 - starting index of the first region.
+/// @param reg_len - terminating index of the second region.
+/// @param wgsl_code - the string where the swap will occur.
+void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code);
+
+/// Given 2 indices, idx1, idx2, it inserts the region of length
+/// reg1_len starting at idx1 after idx2.
+/// @param idx1 - starting index of region.
+/// @param reg1_len - length of the region.
+/// @param idx2 - the position where the region will be inserted.
+/// @param wgsl_code - the string where the swap will occur.
+void DuplicateInterval(size_t idx1,
+                       size_t reg1_len,
+                       size_t idx2,
+                       std::string& wgsl_code);
+
+/// Replaces a region of a WGSL-like string of length id2_len starting
+/// at position idx2 with a region of length id1_len starting at
+/// position idx1.
+/// @param idx1 - starting position of the first region.
+/// @param id1_len - length of the first region.
+/// @param idx2 - starting position of the second region.
+/// @param id2_len - length of the second region.
+/// @param wgsl_code - the string where the replacement will occur.
+void ReplaceRegion(size_t idx1,
+                   size_t id1_len,
+                   size_t idx2,
+                   size_t id2_len,
+                   std::string& wgsl_code);
+
+/// Replaces an interval of length `length` starting at start_index
+/// with the `replacement_text`.
+/// @param start_index - starting position of the interval to be replaced.
+/// @param length - length of the interval to be replaced.
+/// @param replacement_text - the interval that will be used as a replacement.
+/// @param wgsl_code - the WGSL-like string where the replacement will occur.
+void ReplaceInterval(size_t start_index,
+                     size_t length,
+                     std::string replacement_text,
+                     std::string& wgsl_code);
+
+/// A function that, given WGSL-like string and a delimiter,
+/// generates another WGSL-like string by picking two random regions
+/// enclosed by the delimiter and swapping them.
+/// @param delimiter - the delimiter that will be used to find enclosed regions.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @param generator - the random number generator.
+/// @return true if a swap happened or false otherwise.
+bool SwapRandomIntervals(const std::string& delimiter,
+                         std::string& wgsl_code,
+                         RandomGenerator& generator);
+
+/// A function that, given a WGSL-like string and a delimiter,
+/// generates another WGSL-like string by deleting a random
+/// region enclosed by the delimiter.
+/// @param delimiter - the delimiter that will be used to find enclosed regions.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @param generator - the random number generator.
+/// @return true if a deletion happened or false otherwise.
+bool DeleteRandomInterval(const std::string& delimiter,
+                          std::string& wgsl_code,
+                          RandomGenerator& generator);
+
+/// A function that, given a WGSL-like string and a delimiter,
+/// generates another WGSL-like string by duplicating a random
+/// region enclosed by the delimiter.
+/// @param delimiter - the delimiter that will be used to find enclosed regions.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @param generator - the random number generator.
+/// @return true if a duplication happened or false otherwise.
+bool DuplicateRandomInterval(const std::string& delimiter,
+                             std::string& wgsl_code,
+                             RandomGenerator& generator);
+
+/// Replaces a randomly-chosen identifier in wgsl_code.
+/// @param wgsl_code - WGSL-like string where the replacement will occur.
+/// @param generator - the random number generator.
+/// @return true if a replacement happened or false otherwise.
+bool ReplaceRandomIdentifier(std::string& wgsl_code,
+                             RandomGenerator& generator);
+
+/// Replaces the value of a randomly-chosen integer with one of
+/// the values in the set {INT_MAX, INT_MIN, 0, -1}.
+/// @param wgsl_code - WGSL-like string where the replacement will occur.
+/// @param generator - the random number generator.
+/// @return true if a replacement happened or false otherwise.
+bool ReplaceRandomIntLiteral(std::string& wgsl_code,
+                             RandomGenerator& generator);
+
+/// Inserts a return statement in a randomly chosen function of a
+/// WGSL-like string. The return value is a randomly-chosen identifier
+/// or literal in the string.
+/// @param wgsl_code - WGSL-like string that will be mutated.
+/// @param generator - the random number generator.
+/// @return true if the mutation was succesful or false otherwise.
+bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator);
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_renamer_fuzzer.cc b/src/tint/fuzzers/tint_renamer_fuzzer.cc
new file mode 100644
index 0000000..02e539c
--- /dev/null
+++ b/src/tint/fuzzers/tint_renamer_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<transform::Renamer>();
+
+  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_robustness_fuzzer.cc b/src/tint/fuzzers/tint_robustness_fuzzer.cc
new file mode 100644
index 0000000..22f0ad1
--- /dev/null
+++ b/src/tint/fuzzers/tint_robustness_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<tint::transform::Robustness>();
+
+  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc b/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc
new file mode 100644
index 0000000..84146d8
--- /dev/null
+++ b/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<transform::SingleEntryPoint>();
+
+  fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
similarity index 100%
rename from fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
rename to src/tint/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc
new file mode 100644
index 0000000..7659fcc
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc
@@ -0,0 +1,484 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+
+#include <fstream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "source/opt/build_module.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace {
+
+const char* const kMutatorParameters = R"(
+Mutators' parameters:
+
+  -tint_donors=
+                       A path to the text file with a list of paths to the
+                       SPIR-V donor files. Check out the doc for the spirv-fuzz
+                       to learn more about donor binaries. Donors are not used
+                       by default.
+
+  -tint_enable_all_fuzzer_passes=
+                       Whether to use all fuzzer passes or a randomly selected subset
+                       of them. This must be one of `true` or `false` (without `).
+                       By default it's `false`.
+
+  -tint_enable_all_reduce_passes=
+                       Whether to use all reduction passes or a randomly selected subset
+                       of them. This must be one of `true` or `false` (without `).
+                       By default it's `false`.
+
+  -tint_opt_batch_size=
+                       The maximum number of spirv-opt optimizations that
+                       will be applied in a single mutation session (i.e.
+                       a call to LLVMFuzzerCustomMutator). This must fit in
+                       uint32_t. By default it's 6.
+
+  -tint_reduction_batch_size=
+                       The maximum number of spirv-reduce reductions that
+                       will be applied in a single mutation session (i.e.
+                       a call to LLVMFuzzerCustomMutator). This must fit in
+                       uint32_t. By default it's 3.
+
+  -tint_repeated_pass_strategy=
+                       The strategy that will be used to recommend the next fuzzer
+                       pass. This must be one of `simple`, `looped` or `random`
+                       (without `). By default it's `simple`. Check out the doc for
+                       spirv-fuzz to learn more.
+
+  -tint_transformation_batch_size=
+                       The maximum number of spirv-fuzz transformations
+                       that will be applied during a single mutation
+                       session (i.e. a call to LLVMFuzzerCustomMutator).
+                       This must fit in uint32_t. By default it's 3.
+
+  -tint_validate_after_each_fuzzer_pass=
+                       Whether to validate SPIR-V binary after each fuzzer pass.
+                       This must be one of `true` or `false` (without `).
+                       By default it's `true`. Switch this to `false` if you experience
+                       bad performance.
+
+  -tint_validate_after_each_opt_pass=
+                       Whether to validate SPIR-V binary after each optimization pass.
+                       This must be one of `true` or `false` (without `).
+                       By default it's `true`. Switch this to `false` if you experience
+                       bad performance.
+
+  -tint_validate_after_each_reduce_pass=
+                       Whether to validate SPIR-V binary after each reduction pass.
+                       This must be one of `true` or `false` (without `).
+                       By default it's `true`. Switch this to `false` if you experience
+                       bad performance.
+)";
+
+const char* const kFuzzerHelpMessage = R"(
+This fuzzer uses SPIR-V binaries to fuzz the Tint compiler. It uses SPIRV-Tools
+to mutate those binaries. The fuzzer works on a corpus of SPIR-V shaders.
+For each shader from the corpus it uses one of `spirv-fuzz`, `spirv-reduce` or
+`spirv-opt` to mutate it and then runs the shader through the Tint compiler in
+two steps:
+- Converts the mutated shader to WGSL.
+- Converts WGSL to some target language specified in the CLI arguments.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+Fuzzer parameters:
+
+  -tint_error_dir
+                       The directory that will be used to output invalid SPIR-V
+                       binaries to. This is especially useful during debugging
+                       mutators. The directory must have the following subdirectories:
+                       - spv/ - will be used to output errors, produced during
+                         the conversion from the SPIR-V to WGSL.
+                       - wgsl/ - will be used to output errors, produced during
+                         the conversion from the WGSL to `--fuzzing_target`.
+                       - mutator/ - will be used to output errors, produced by
+                         the mutators.
+                       By default invalid files are not printed out.
+
+  -tint_fuzzing_target
+                       The type of backend to target during fuzzing. This must
+                       be one or a combination of `wgsl`, `spv`, `msl` or `hlsl`
+                       (without `) separated by commas. By default it's
+                       `wgsl,spv,msl,hlsl`.
+
+  -tint_help
+                       Show this message. Note that there is also a -help=1
+                       parameter that will display libfuzzer's help message.
+
+  -tint_mutator_cache_size=
+                       The maximum size of the cache that stores
+                       mutation sessions. This must fit in uint32_t.
+                       By default it's 20.
+
+  -tint_mutator_type=
+                       Determines types of the mutators to run. This must be one or
+                       a combination of `fuzz`, `opt`, `reduce` (without `) separated by
+                       comma. If a combination is specified, each element in the
+                       combination will have an equal chance of mutating a SPIR-V
+                       binary during a mutation session (i.e. if no mutator exists
+                       for that binary in the mutator cache). By default, the
+                       parameter's value is `fuzz,opt,reduce`.
+)";
+
+const char* const kMutatorDebuggerHelpMessage = R"(
+This tool is used to debug *mutators*. It uses CLI arguments similar to the
+ones used by the fuzzer. To debug some mutator you just need to specify the
+mutator type, the seed and the path to the SPIR-V binary that triggered the
+error. This tool will run the mutator on the binary until the error is
+produced or the mutator returns `kLimitReached`.
+
+Note that this is different from debugging the fuzzer by specifying input
+files to test. The difference is that the latter will not execute any
+mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
+tool is useful when one of the SPIRV-Tools mutators crashes or produces an
+invalid binary in LLVMFuzzerCustomMutator.
+
+Debugger parameters:
+
+  --help
+                       Show this message.
+
+  --mutator_type=
+                       Determines the type of the mutator to debug. This must be
+                       one of `fuzz`, `reduce` or `opt` (without `). This parameter
+                       is REQUIRED.
+
+  --original_binary=
+                       The path to the SPIR-V binary that the faulty mutator was
+                       initialized with. This will be dumped on errors by the fuzzer
+                       if `--error_dir` is specified. This parameter is REQUIRED.
+
+  --seed=
+                       The seed for the random number generator that was used to
+                       initialize the mutator. This value is usually printed to
+                       the console when the mutator produces an invalid binary.
+                       It is also dumped into the log file if `--error_dir` is
+                       specified. This must fit in uint32_t. This parameter is
+                       REQUIRED.
+)";
+
+void PrintHelpMessage(const char* help_message) {
+  std::cout << help_message << std::endl << kMutatorParameters << std::endl;
+}
+
+[[noreturn]] void InvalidParameter(const char* help_message,
+                                   const char* param) {
+  std::cout << "Invalid value for " << param << std::endl;
+  PrintHelpMessage(help_message);
+  exit(1);
+}
+
+bool ParseUint32(const char* param, uint32_t* out) {
+  uint64_t value = static_cast<uint64_t>(strtoul(param, nullptr, 10));
+  if (value > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
+    return false;
+  }
+  *out = static_cast<uint32_t>(value);
+  return true;
+}
+
+std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> ParseDonors(
+    const char* file_name) {
+  std::ifstream fin(file_name);
+  if (!fin) {
+    std::cout << "Can't open donors list file: " << file_name << std::endl;
+    exit(1);
+  }
+
+  std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> result;
+  for (std::string donor_file_name; fin >> donor_file_name;) {
+    if (!std::ifstream(donor_file_name)) {
+      std::cout << "Can't open donor file: " << donor_file_name << std::endl;
+      exit(1);
+    }
+
+    result.emplace_back([donor_file_name] {
+      std::vector<uint32_t> binary;
+      if (!util::ReadBinary(donor_file_name, &binary)) {
+        std::cout << "Failed to read donor from: " << donor_file_name
+                  << std::endl;
+        exit(1);
+      }
+      return spvtools::BuildModule(
+          kDefaultTargetEnv, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+          binary.data(), binary.size());
+    });
+  }
+
+  return result;
+}
+
+bool ParseRepeatedPassStrategy(const char* param,
+                               spvtools::fuzz::RepeatedPassStrategy* out) {
+  if (!strcmp(param, "simple")) {
+    *out = spvtools::fuzz::RepeatedPassStrategy::kSimple;
+  } else if (!strcmp(param, "looped")) {
+    *out = spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations;
+  } else if (!strcmp(param, "random")) {
+    *out = spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool ParseBool(const char* param, bool* out) {
+  if (!strcmp(param, "true")) {
+    *out = true;
+  } else if (!strcmp(param, "false")) {
+    *out = false;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool ParseMutatorType(const char* param, MutatorType* out) {
+  if (!strcmp(param, "fuzz")) {
+    *out = MutatorType::kFuzz;
+  } else if (!strcmp(param, "opt")) {
+    *out = MutatorType::kOpt;
+  } else if (!strcmp(param, "reduce")) {
+    *out = MutatorType::kReduce;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool ParseFuzzingTarget(const char* param, FuzzingTarget* out) {
+  if (!strcmp(param, "wgsl")) {
+    *out = FuzzingTarget::kWgsl;
+  } else if (!strcmp(param, "spv")) {
+    *out = FuzzingTarget::kSpv;
+  } else if (!strcmp(param, "msl")) {
+    *out = FuzzingTarget::kMsl;
+  } else if (!strcmp(param, "hlsl")) {
+    *out = FuzzingTarget::kHlsl;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool HasPrefix(const char* str, const char* prefix) {
+  return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+bool ParseMutatorCliParam(const char* param,
+                          const char* help_message,
+                          MutatorCliParams* out) {
+  if (HasPrefix(param, "-tint_transformation_batch_size=")) {
+    if (!ParseUint32(param + sizeof("-tint_transformation_batch_size=") - 1,
+                     &out->transformation_batch_size)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_reduction_batch_size=")) {
+    if (!ParseUint32(param + sizeof("-tint_reduction_batch_size=") - 1,
+                     &out->reduction_batch_size)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_opt_batch_size=")) {
+    if (!ParseUint32(param + sizeof("-tint_opt_batch_size=") - 1,
+                     &out->opt_batch_size)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_donors=")) {
+    out->donors = ParseDonors(param + sizeof("-tint_donors=") - 1);
+  } else if (HasPrefix(param, "-tint_repeated_pass_strategy=")) {
+    if (!ParseRepeatedPassStrategy(
+            param + sizeof("-tint_repeated_pass_strategy=") - 1,
+            &out->repeated_pass_strategy)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_enable_all_fuzzer_passes=")) {
+    if (!ParseBool(param + sizeof("-tint_enable_all_fuzzer_passes=") - 1,
+                   &out->enable_all_fuzzer_passes)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_enable_all_reduce_passes=")) {
+    if (!ParseBool(param + sizeof("-tint_enable_all_reduce_passes=") - 1,
+                   &out->enable_all_reduce_passes)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_validate_after_each_opt_pass=")) {
+    if (!ParseBool(param + sizeof("-tint_validate_after_each_opt_pass=") - 1,
+                   &out->validate_after_each_opt_pass)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_validate_after_each_fuzzer_pass=")) {
+    if (!ParseBool(param + sizeof("-tint_validate_after_each_fuzzer_pass=") - 1,
+                   &out->validate_after_each_fuzzer_pass)) {
+      InvalidParameter(help_message, param);
+    }
+  } else if (HasPrefix(param, "-tint_validate_after_each_reduce_pass=")) {
+    if (!ParseBool(param + sizeof("-tint_validate_after_each_reduce_pass=") - 1,
+                   &out->validate_after_each_reduce_pass)) {
+      InvalidParameter(help_message, param);
+    }
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv) {
+  FuzzerCliParams cli_params;
+  const auto* help_message = kFuzzerHelpMessage;
+  auto help = false;
+
+  for (int i = *argc - 1; i > 0; --i) {
+    auto param = argv[i];
+    auto recognized_param = true;
+
+    if (HasPrefix(param, "-tint_mutator_cache_size=")) {
+      if (!ParseUint32(param + sizeof("-tint_mutator_cache_size=") - 1,
+                       &cli_params.mutator_cache_size)) {
+        InvalidParameter(help_message, param);
+      }
+    } else if (HasPrefix(param, "-tint_mutator_type=")) {
+      auto result = MutatorType::kNone;
+
+      std::stringstream ss(param + sizeof("-tint_mutator_type=") - 1);
+      for (std::string value; std::getline(ss, value, ',');) {
+        auto out = MutatorType::kNone;
+        if (!ParseMutatorType(value.c_str(), &out)) {
+          InvalidParameter(help_message, param);
+        }
+        result = result | out;
+      }
+
+      if (result == MutatorType::kNone) {
+        InvalidParameter(help_message, param);
+      }
+
+      cli_params.mutator_type = result;
+    } else if (HasPrefix(param, "-tint_fuzzing_target=")) {
+      auto result = FuzzingTarget::kNone;
+
+      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
+      for (std::string value; std::getline(ss, value, ',');) {
+        auto tmp = FuzzingTarget::kNone;
+        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
+          InvalidParameter(help_message, param);
+        }
+        result = result | tmp;
+      }
+
+      if (result == FuzzingTarget::kNone) {
+        InvalidParameter(help_message, param);
+      }
+
+      cli_params.fuzzing_target = result;
+    } else if (HasPrefix(param, "-tint_error_dir=")) {
+      cli_params.error_dir = param + sizeof("-tint_error_dir=") - 1;
+    } else if (!strcmp(param, "-tint_help")) {
+      help = true;
+    } else {
+      recognized_param =
+          ParseMutatorCliParam(param, help_message, &cli_params.mutator_params);
+    }
+
+    if (recognized_param) {
+      // Remove the recognized parameter from the list of all parameters by
+      // swapping it with the last one. This will suppress warnings in the
+      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
+      // that all user-defined parameters start with two dashes. However, we are
+      // forced to use a single one to make the fuzzer compatible with the
+      // ClusterFuzz.
+      std::swap(argv[i], argv[*argc - 1]);
+      *argc -= 1;
+    }
+  }
+
+  if (help) {
+    PrintHelpMessage(help_message);
+    exit(0);
+  }
+
+  return cli_params;
+}
+
+MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(
+    int argc,
+    const char* const* argv) {
+  MutatorDebuggerCliParams cli_params;
+  bool seed_param_present = false;
+  bool original_binary_param_present = false;
+  bool mutator_type_param_present = false;
+  const auto* help_message = kMutatorDebuggerHelpMessage;
+  auto help = false;
+
+  for (int i = 0; i < argc; ++i) {
+    auto param = argv[i];
+    ParseMutatorCliParam(param, help_message, &cli_params.mutator_params);
+
+    if (HasPrefix(param, "--mutator_type=")) {
+      if (!ParseMutatorType(param + sizeof("--mutator_type=") - 1,
+                            &cli_params.mutator_type)) {
+        InvalidParameter(help_message, param);
+      }
+      mutator_type_param_present = true;
+    } else if (HasPrefix(param, "--original_binary=")) {
+      if (!util::ReadBinary(param + sizeof("--original_binary=") - 1,
+                            &cli_params.original_binary)) {
+        InvalidParameter(help_message, param);
+      }
+      original_binary_param_present = true;
+    } else if (HasPrefix(param, "--seed=")) {
+      if (!ParseUint32(param + sizeof("--seed=") - 1, &cli_params.seed)) {
+        InvalidParameter(help_message, param);
+      }
+      seed_param_present = true;
+    } else if (!strcmp(param, "--help")) {
+      help = true;
+    }
+  }
+
+  if (help) {
+    PrintHelpMessage(help_message);
+    exit(0);
+  }
+
+  std::pair<bool, const char*> required_params[] = {
+      {seed_param_present, "--seed"},
+      {original_binary_param_present, "--original_binary"},
+      {mutator_type_param_present, "--mutator_type"}};
+
+  for (auto required_param : required_params) {
+    if (!required_param.first) {
+      std::cout << required_param.second << " is missing" << std::endl;
+      exit(1);
+    }
+  }
+
+  return cli_params;
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h
new file mode 100644
index 0000000..6e6d60b
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h
@@ -0,0 +1,167 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
+
+#include <string>
+#include <vector>
+
+#include "source/fuzz/fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Default SPIR-V environment that will be used during fuzzing.
+const auto kDefaultTargetEnv = SPV_ENV_VULKAN_1_1;
+
+/// The type of the mutator to run.
+enum class MutatorType {
+  kNone = 0,
+  kFuzz = 1 << 0,
+  kReduce = 1 << 1,
+  kOpt = 1 << 2,
+  kAll = kFuzz | kReduce | kOpt
+};
+
+inline MutatorType operator|(MutatorType a, MutatorType b) {
+  return static_cast<MutatorType>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline MutatorType operator&(MutatorType a, MutatorType b) {
+  return static_cast<MutatorType>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// Shading language to target during fuzzing.
+enum class FuzzingTarget {
+  kNone = 0,
+  kHlsl = 1 << 0,
+  kMsl = 1 << 1,
+  kSpv = 1 << 2,
+  kWgsl = 1 << 3,
+  kAll = kHlsl | kMsl | kSpv | kWgsl
+};
+
+inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// These parameters are accepted by various mutators and thus they are accepted
+/// by both the fuzzer and the mutator debugger.
+struct MutatorCliParams {
+  /// SPIR-V target environment for fuzzing.
+  spv_target_env target_env = kDefaultTargetEnv;
+
+  /// The number of spirv-fuzz transformations to apply at a time.
+  uint32_t transformation_batch_size = 3;
+
+  /// The number of spirv-reduce reductions to apply at a time.
+  uint32_t reduction_batch_size = 3;
+
+  /// The number of spirv-opt optimizations to apply at a time.
+  uint32_t opt_batch_size = 6;
+
+  /// The vector of donors to use in spirv-fuzz (see the doc for spirv-fuzz to
+  /// learn more).
+  std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donors = {};
+
+  /// The strategy to use during fuzzing in spirv-fuzz (see the doc for
+  /// spirv-fuzz to learn more).
+  spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy =
+      spvtools::fuzz::RepeatedPassStrategy::kSimple;
+
+  /// Whether to use all fuzzer passes or a randomly selected subset of them.
+  bool enable_all_fuzzer_passes = false;
+
+  /// Whether to use all reduction passes or a randomly selected subset of them.
+  bool enable_all_reduce_passes = false;
+
+  /// Whether to validate the SPIR-V binary after each optimization pass.
+  bool validate_after_each_opt_pass = true;
+
+  /// Whether to validate the SPIR-V binary after each fuzzer pass.
+  bool validate_after_each_fuzzer_pass = true;
+
+  /// Whether to validate the SPIR-V binary after each reduction pass.
+  bool validate_after_each_reduce_pass = true;
+};
+
+/// Parameters specific to the fuzzer. Type `-tint_help` in the CLI to learn
+/// more.
+struct FuzzerCliParams {
+  /// The size of the cache that records ongoing mutation sessions.
+  uint32_t mutator_cache_size = 20;
+
+  /// The type of the mutator to run.
+  MutatorType mutator_type = MutatorType::kAll;
+
+  /// Tint backend to fuzz.
+  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
+
+  /// The path to the directory, that will be used to output buggy shaders.
+  std::string error_dir = "";
+
+  /// Parameters for various mutators.
+  MutatorCliParams mutator_params;
+};
+
+/// Parameters specific to the mutator debugger. Type `--help` in the CLI to
+/// learn more.
+struct MutatorDebuggerCliParams {
+  /// The type of the mutator to debug.
+  MutatorType mutator_type = MutatorType::kNone;
+
+  /// The seed that was used to initialize the mutator.
+  uint32_t seed = 0;
+
+  /// The binary that triggered a bug in the mutator.
+  std::vector<uint32_t> original_binary;
+
+  /// Parameters for various mutators.
+  MutatorCliParams mutator_params;
+};
+
+/// Parses CLI parameters for the fuzzer. This function exits with an error code
+/// and a message is printed to the console if some parameter has invalid
+/// format. You can pass `-tint_help` to check out all available parameters.
+/// This function will remove recognized parameters from the `argv` and adjust
+/// the `argc` accordingly.
+///
+/// @param argc - the number of parameters (identical to the `argc` in `main`
+///     function).
+/// @param argv - array of C strings of parameters.
+/// @return the parsed parameters.
+FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv);
+
+/// Parses CLI parameters for the mutator debugger. This function exits with an
+/// error code and a message is printed to the console if some parameter has
+/// invalid format. You can pass `--help` to check out all available parameters.
+///
+/// @param argc - the number of parameters (identical to the `argc` in `main`
+///     function).
+/// @param argv - array of C strings of parameters.
+/// @return the parsed parameters.
+MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(int argc,
+                                                       const char* const* argv);
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
new file mode 100644
index 0000000..d17d743
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
@@ -0,0 +1,263 @@
+// 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 <cassert>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "spirv-tools/libspirv.hpp"
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace {
+
+struct Context {
+  FuzzerCliParams params;
+  std::unique_ptr<MutatorCache> mutator_cache;
+};
+
+Context* context = nullptr;
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  auto params = ParseFuzzerCliParams(argc, *argv);
+  auto mutator_cache =
+      params.mutator_cache_size
+          ? std::make_unique<MutatorCache>(params.mutator_cache_size)
+          : nullptr;
+  context = new Context{std::move(params), std::move(mutator_cache)};
+  OverrideCliParams(context->params);
+  return 0;
+}
+
+std::unique_ptr<Mutator> CreateMutator(const std::vector<uint32_t>& binary,
+                                       unsigned seed) {
+  std::vector<MutatorType> types;
+  types.reserve(3);
+
+  // Determine which mutator we will be using for `binary` at random.
+  auto cli_mutator_type = context->params.mutator_type;
+  if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) {
+    types.push_back(MutatorType::kFuzz);
+  }
+  if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) {
+    types.push_back(MutatorType::kReduce);
+  }
+  if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) {
+    types.push_back(MutatorType::kOpt);
+  }
+
+  assert(!types.empty() && "At least one mutator type must be specified");
+  RandomGenerator generator(seed);
+  auto mutator_type =
+      types[generator.GetUInt32(static_cast<uint32_t>(types.size()))];
+
+  const auto& mutator_params = context->params.mutator_params;
+  switch (mutator_type) {
+    case MutatorType::kFuzz:
+      return std::make_unique<SpirvFuzzMutator>(
+          mutator_params.target_env, binary, seed, mutator_params.donors,
+          mutator_params.enable_all_fuzzer_passes,
+          mutator_params.repeated_pass_strategy,
+          mutator_params.validate_after_each_fuzzer_pass,
+          mutator_params.transformation_batch_size);
+    case MutatorType::kReduce:
+      return std::make_unique<SpirvReduceMutator>(
+          mutator_params.target_env, binary, seed,
+          mutator_params.reduction_batch_size,
+          mutator_params.enable_all_reduce_passes,
+          mutator_params.validate_after_each_reduce_pass);
+    case MutatorType::kOpt:
+      return std::make_unique<SpirvOptMutator>(
+          mutator_params.target_env, seed, binary,
+          mutator_params.validate_after_each_opt_pass,
+          mutator_params.opt_batch_size);
+    default:
+      assert(false && "All mutator types must be handled above");
+      return nullptr;
+  }
+}
+
+void CLIMessageConsumer(spv_message_level_t level,
+                        const char*,
+                        const spv_position_t& position,
+                        const char* message) {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+}
+
+bool IsValid(const std::vector<uint32_t>& binary) {
+  spvtools::SpirvTools tools(context->params.mutator_params.target_env);
+  tools.SetMessageConsumer(CLIMessageConsumer);
+  return tools.IsValid() && tools.Validate(binary.data(), binary.size(),
+                                           spvtools::ValidatorOptions());
+}
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
+                                          size_t size,
+                                          size_t max_size,
+                                          unsigned seed) {
+  if ((size % sizeof(uint32_t)) != 0) {
+    // A valid SPIR-V binary's size must be a multiple of the size of a 32-bit
+    // word, and the SPIR-V Tools fuzzer is only designed to work with valid
+    // binaries.
+    return 0;
+  }
+
+  std::vector<uint32_t> binary(size / sizeof(uint32_t));
+  std::memcpy(binary.data(), data, size);
+
+  MutatorCache placeholder_cache(1);
+  auto* mutator_cache = context->mutator_cache.get();
+  if (!mutator_cache) {
+    // Use a placeholder cache if the user has decided not to use a real cache.
+    // The placeholder cache will be destroyed when we return from this function
+    // but it will save us from writing all the `if (mutator_cache)` below.
+    mutator_cache = &placeholder_cache;
+  }
+
+  if (!mutator_cache->Get(binary)) {
+    // This is an unknown binary, so its validity must be checked before
+    // proceeding.
+    if (!IsValid(binary)) {
+      return 0;
+    }
+    // Assign a mutator to the binary if it doesn't have one yet.
+    mutator_cache->Put(binary, CreateMutator(binary, seed));
+  }
+
+  auto* mutator = mutator_cache->Get(binary);
+  assert(mutator && "Mutator must be present in the cache");
+
+  auto result = mutator->Mutate();
+
+  if (result.GetStatus() == Mutator::Status::kInvalid) {
+    // The binary is invalid - log the error and remove the mutator from the
+    // cache.
+    util::LogMutatorError(*mutator, context->params.error_dir);
+    mutator_cache->Remove(binary);
+    return 0;
+  }
+
+  if (!result.IsChanged()) {
+    // The mutator didn't change the binary this time. This could be due to the
+    // fact that we've reached the number of mutations we can apply (e.g. the
+    // number of transformations in spirv-fuzz) or the mutator was just unlucky.
+    // Either way, there is no harm in destroying mutator and maybe trying again
+    // later (i.e. if libfuzzer decides to do so).
+    mutator_cache->Remove(binary);
+    return 0;
+  }
+
+  // At this point the binary is valid and was changed by the mutator.
+
+  auto mutated = mutator->GetBinary();
+  auto mutated_bytes_size = mutated.size() * sizeof(uint32_t);
+  if (mutated_bytes_size > max_size) {
+    // The binary is too big. It's unlikely that we'll reduce its size by
+    // applying the mutator one more time.
+    mutator_cache->Remove(binary);
+    return 0;
+  }
+
+  if (result.GetStatus() == Mutator::Status::kComplete) {
+    // Reassign the mutator to the mutated binary in the cache so that we can
+    // access later.
+    mutator_cache->Put(mutated, mutator_cache->Remove(binary));
+  } else {
+    // If the binary is valid and was changed but is not `kComplete`, then the
+    // mutator has reached some limit on the number of mutations.
+    mutator_cache->Remove(binary);
+  }
+
+  std::memcpy(data, mutated.data(), mutated_bytes_size);
+  return mutated_bytes_size;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size == 0) {
+    return 0;
+  }
+
+  if ((size % sizeof(uint32_t)) != 0) {
+    // The SPIR-V Tools fuzzer has been designed to work with valid
+    // SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit
+    // word.
+    return 0;
+  }
+
+  CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL);
+  spv_to_wgsl.Run(data, size);
+  if (spv_to_wgsl.HasErrors()) {
+    auto error = spv_to_wgsl.Diagnostics().str();
+    util::LogSpvError(error, data, size,
+                      context ? context->params.error_dir : "");
+    return 0;
+  }
+
+  const auto& wgsl = spv_to_wgsl.GetGeneratedWgsl();
+
+  std::pair<FuzzingTarget, OutputFormat> targets[] = {
+      {FuzzingTarget::kHlsl, OutputFormat::kHLSL},
+      {FuzzingTarget::kMsl, OutputFormat::kMSL},
+      {FuzzingTarget::kSpv, OutputFormat::kSpv},
+      {FuzzingTarget::kWgsl, OutputFormat::kWGSL}};
+
+  for (auto target : targets) {
+    if ((target.first & context->params.fuzzing_target) != target.first) {
+      continue;
+    }
+
+    CommonFuzzer fuzzer(InputFormat::kWGSL, target.second);
+    fuzzer.Run(reinterpret_cast<const uint8_t*>(wgsl.data()), wgsl.size());
+    if (fuzzer.HasErrors()) {
+      auto error = spv_to_wgsl.Diagnostics().str();
+      util::LogWgslError(error, data, size, wgsl, target.second,
+                         context->params.error_dir);
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
new file mode 100644
index 0000000..05dcc41
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
@@ -0,0 +1,34 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+// We need to define constructor here so that vtable is produced in this
+// translation unit (see -Wweak-vtables clang flag).
+Mutator::~Mutator() = default;
+
+Mutator::Result::Result(Status status, bool is_changed)
+    : status_(status), is_changed_(is_changed) {
+  assert((is_changed || status == Status::kStuck ||
+          status == Status::kLimitReached) &&
+         "Returning invalid result state");
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h
new file mode 100644
index 0000000..9ff8fd9
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h
@@ -0,0 +1,108 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
+
+#include <cassert>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// This is an interface that is used to define custom mutators based on the
+/// SPIR-V tools.
+class Mutator {
+ public:
+  /// The status of the mutation.
+  enum class Status {
+    /// Binary is valid, the limit is not reached - can mutate further.
+    kComplete,
+
+    /// The binary is valid, the limit of mutations has been reached -
+    /// can't mutate further.
+    kLimitReached,
+
+    /// The binary is valid, the limit is not reached but the mutator has spent
+    /// too much time without mutating anything - better to restart to make sure
+    /// we can make any progress.
+    kStuck,
+
+    /// The binary is invalid - this is likely a bug in the mutator - must
+    /// abort.
+    kInvalid
+  };
+
+  /// Represents the result of the mutation. The following states are possible:
+  /// - if `IsChanged() == false`, then `GetStatus()` can be either
+  ///   `kLimitReached` or `kStuck`.
+  /// - otherwise, any value of `Status` is possible.
+  class Result {
+   public:
+    /// Constructor.
+    /// @param status - the status of the mutation.
+    /// @param is_changed - whether the module was changed during mutation.
+    Result(Status status, bool is_changed);
+
+    /// @return the status of the mutation.
+    Status GetStatus() const { return status_; }
+
+    /// @return whether the module was changed during mutation.
+    bool IsChanged() const { return is_changed_; }
+
+   private:
+    Status status_;
+    bool is_changed_;
+  };
+
+  /// Virtual destructor.
+  virtual ~Mutator();
+
+  /// Causes the mutator to apply a mutation. This method can be called
+  /// multiple times as long as the previous call didn't return
+  /// `Status::kInvalid`.
+  ///
+  /// @return the status of the mutation (e.g. success, error etc) and whether
+  ///     the binary was changed during mutation.
+  virtual Result Mutate() = 0;
+
+  /// Returns the mutated binary. The returned binary is guaranteed to be valid
+  /// iff the previous call to the `Mutate` method returned didn't return
+  /// `Status::kInvalid`.
+  ///
+  /// @return the mutated SPIR-V binary. It might be identical to the original
+  ///     binary if `Result::IsChanged` returns `false`.
+  virtual std::vector<uint32_t> GetBinary() const = 0;
+
+  /// Returns errors, produced by the mutator.
+  ///
+  /// @param path - the directory to which the errors are printed to. No files
+  ///     are created if the `path` is nullptr.
+  /// @param count - the number of the error. Files for this error will be
+  ///     prefixed with `count`.
+  virtual void LogErrors(const std::string* path, uint32_t count) const = 0;
+
+  /// @return errors encountered during the mutation. The returned string is
+  ///     if there were no errors during mutation.
+  virtual std::string GetErrors() const = 0;
+};
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
new file mode 100644
index 0000000..4ce1ad2
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
@@ -0,0 +1,78 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+MutatorCache::MutatorCache(size_t max_size)
+    : map_(), entries_(), max_size_(max_size) {
+  assert(max_size && "`max_size` may not be 0");
+}
+
+MutatorCache::Value::pointer MutatorCache::Get(const Key& key) {
+  auto it = map_.find(key);
+  if (it == map_.end()) {
+    return nullptr;
+  }
+  UpdateUsage(it);
+  return entries_.front().second.get();
+}
+
+void MutatorCache::Put(const Key& key, Value value) {
+  assert(value && "Mutator cache can't have nullptr unique_ptr");
+  auto it = map_.find(key);
+  if (it != map_.end()) {
+    it->second->second = std::move(value);
+    UpdateUsage(it);
+  } else {
+    if (map_.size() == max_size_) {
+      Remove(*entries_.back().first);
+    }
+
+    entries_.emplace_front(nullptr, std::move(value));
+    auto pair = map_.emplace(key, entries_.begin());
+    assert(pair.second && "The key must be unique");
+    entries_.front().first = &pair.first->first;
+  }
+}
+
+MutatorCache::Value MutatorCache::Remove(const Key& key) {
+  auto it = map_.find(key);
+  if (it == map_.end()) {
+    return nullptr;
+  }
+  auto result = std::move(it->second->second);
+  entries_.erase(it->second);
+  map_.erase(it);
+  return result;
+}
+
+size_t MutatorCache::KeyHash::operator()(
+    const std::vector<uint32_t>& vec) const {
+  return std::hash<std::u32string>()({vec.begin(), vec.end()});
+}
+
+void MutatorCache::UpdateUsage(Map::iterator it) {
+  auto entry = std::move(*it->second);
+  entries_.erase(it->second);
+  entries_.push_front(std::move(entry));
+  it->second = entries_.begin();
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
new file mode 100644
index 0000000..7318b5c
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
@@ -0,0 +1,99 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
+
+#include <cassert>
+#include <list>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Implementation of a fixed size LRU cache. That is, when the number of
+/// elements reaches a certain threshold, the element that wasn't used for the
+/// longest period of time is removed from the cache when a new element is
+/// inserted. All operations have amortized constant time complexity.
+class MutatorCache {
+ public:
+  /// SPIR-V binary that is being mutated.
+  using Key = std::vector<uint32_t>;
+
+  /// Mutator that is used to mutate the `Key`.
+  using Value = std::unique_ptr<Mutator>;
+
+  /// Constructor.
+  /// @param max_size - the maximum number of elements the cache can store. May
+  ///     not be equal to 0.
+  explicit MutatorCache(size_t max_size);
+
+  /// Retrieves a pointer to a value, associated with a given `key`.
+  ///
+  /// If the key is present in the cache, its usage is updated and the
+  /// (non-null) pointer to the value is returned. Otherwise, `nullptr` is
+  /// returned.
+  ///
+  /// @param key - may not exist in this cache.
+  /// @return non-`nullptr` pointer to a value if `key` exists in the cache.
+  /// @return `nullptr` if `key` doesn't exist in this cache.
+  Value::pointer Get(const Key& key);
+
+  /// Inserts a `key`-`value` pair into the cache.
+  ///
+  /// If the `key` is already present, the `value` replaces the old value and
+  /// the usage of `key` is updated. If the `key` is not present, then:
+  /// - if the number of elements in the cache is equal to `max_size`, the
+  ///   key-value pair, where the usage of the key wasn't updated for the
+  ///   longest period of time, is removed from the cache.
+  /// - a new `key`-`value` pair is inserted into the cache.
+  ///
+  /// @param key - a key.
+  /// @param value - may not be a `nullptr`.
+  void Put(const Key& key, Value value);
+
+  /// Removes `key` and an associated value from the cache.
+  ///
+  /// @param key - a key.
+  /// @return a non-`nullptr` pointer to the removed value, associated with
+  ///     `key`.
+  /// @return `nullptr` if `key` is not present in the cache.
+  Value Remove(const Key& key);
+
+ private:
+  struct KeyHash {
+    size_t operator()(const std::vector<uint32_t>& vec) const;
+  };
+
+  using Entry = std::pair<const Key*, Value>;
+  using Map = std::unordered_map<Key, std::list<Entry>::iterator, KeyHash>;
+
+  void UpdateUsage(Map::iterator it);
+
+  Map map_;
+  std::list<Entry> entries_;
+  const size_t max_size_;
+};
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
new file mode 100644
index 0000000..aaa85fa
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
@@ -0,0 +1,84 @@
+// 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 <memory>
+#include <string>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+/// This tool is used to debug *mutators*. It uses CLI arguments similar to the
+/// ones used by the fuzzer. To debug some mutator you just need to specify the
+/// mutator type, the seed and the path to the SPIR-V binary that triggered the
+/// error. This tool will run the mutator on the binary until the error is
+/// produced or the mutator returns `kLimitReached`.
+///
+/// Note that this is different from debugging the fuzzer by specifying input
+/// files to test. The difference is that the latter will not execute any
+/// mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
+/// tool is useful when one of the spirv-tools mutators crashes or produces an
+/// invalid binary in LLVMFuzzerCustomMutator.
+int main(int argc, const char** argv) {
+  auto params =
+      tint::fuzzers::spvtools_fuzzer::ParseMutatorDebuggerCliParams(argc, argv);
+
+  std::unique_ptr<tint::fuzzers::spvtools_fuzzer::Mutator> mutator;
+  const auto& mutator_params = params.mutator_params;
+  switch (params.mutator_type) {
+    case tint::fuzzers::spvtools_fuzzer::MutatorType::kFuzz:
+      mutator =
+          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvFuzzMutator>(
+              mutator_params.target_env, params.original_binary, params.seed,
+              mutator_params.donors, mutator_params.enable_all_fuzzer_passes,
+              mutator_params.repeated_pass_strategy,
+              mutator_params.validate_after_each_fuzzer_pass,
+              mutator_params.transformation_batch_size);
+      break;
+    case tint::fuzzers::spvtools_fuzzer::MutatorType::kReduce:
+      mutator =
+          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvReduceMutator>(
+              mutator_params.target_env, params.original_binary, params.seed,
+              mutator_params.reduction_batch_size,
+              mutator_params.enable_all_reduce_passes,
+              mutator_params.validate_after_each_reduce_pass);
+      break;
+    case tint::fuzzers::spvtools_fuzzer::MutatorType::kOpt:
+      mutator =
+          std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvOptMutator>(
+              mutator_params.target_env, params.seed, params.original_binary,
+              mutator_params.validate_after_each_opt_pass,
+              mutator_params.opt_batch_size);
+      break;
+    default:
+      assert(false && "All mutator types must've been handled");
+      return 1;
+  }
+
+  while (true) {
+    auto result = mutator->Mutate();
+    if (result.GetStatus() ==
+        tint::fuzzers::spvtools_fuzzer::Mutator::Status::kInvalid) {
+      std::cerr << mutator->GetErrors() << std::endl;
+      return 0;
+    }
+    if (result.GetStatus() ==
+        tint::fuzzers::spvtools_fuzzer::Mutator::Status::kLimitReached) {
+      break;
+    }
+  }
+}
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h
new file mode 100644
index 0000000..2aa2086
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// @brief Allows CLI parameters to be overridden.
+///
+/// This function allows fuzz targets to override particular CLI parameters,
+/// for example forcing a particular back-end to be targeted.
+///
+/// @param cli_params - the parsed CLI parameters to be updated.
+void OverrideCliParams(FuzzerCliParams& cli_params);
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
new file mode 100644
index 0000000..05fd2a3
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
@@ -0,0 +1,127 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+
+#include <fstream>
+#include <utility>
+
+#include "source/opt/build_module.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvFuzzMutator::SpirvFuzzMutator(
+    spv_target_env target_env,
+    std::vector<uint32_t> binary,
+    unsigned seed,
+    const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
+    bool enable_all_passes,
+    spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
+    bool validate_after_each_pass,
+    uint32_t transformation_batch_size)
+    : transformation_batch_size_(transformation_batch_size),
+      errors_(std::make_unique<std::stringstream>()),
+      fuzzer_(nullptr),
+      validator_options_(),
+      original_binary_(std::move(binary)),
+      seed_(seed) {
+  auto ir_context = spvtools::BuildModule(
+      target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+      original_binary_.data(), original_binary_.size());
+  assert(ir_context && "|binary| is invalid");
+
+  auto transformation_context =
+      std::make_unique<spvtools::fuzz::TransformationContext>(
+          std::make_unique<spvtools::fuzz::FactManager>(ir_context.get()),
+          validator_options_);
+
+  auto fuzzer_context = std::make_unique<spvtools::fuzz::FuzzerContext>(
+      std::make_unique<spvtools::fuzz::PseudoRandomGenerator>(seed),
+      spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false);
+  fuzzer_ = std::make_unique<spvtools::fuzz::Fuzzer>(
+      std::move(ir_context), std::move(transformation_context),
+      std::move(fuzzer_context), util::GetBufferMessageConsumer(errors_.get()),
+      donors, enable_all_passes, repeated_pass_strategy,
+      validate_after_each_pass, validator_options_);
+}
+
+Mutator::Result SpirvFuzzMutator::Mutate() {
+  // The assertion will fail in |fuzzer_->Run| if the previous fuzzing led to
+  // invalid module.
+  auto result = fuzzer_->Run(transformation_batch_size_);
+  switch (result.status) {
+    case spvtools::fuzz::Fuzzer::Status::kComplete:
+      return {Mutator::Status::kComplete, result.is_changed};
+    case spvtools::fuzz::Fuzzer::Status::kModuleTooBig:
+    case spvtools::fuzz::Fuzzer::Status::kTransformationLimitReached:
+      return {Mutator::Status::kLimitReached, result.is_changed};
+    case spvtools::fuzz::Fuzzer::Status::kFuzzerStuck:
+      return {Mutator::Status::kStuck, result.is_changed};
+    case spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule:
+      return {Mutator::Status::kInvalid, result.is_changed};
+  }
+}
+
+std::vector<uint32_t> SpirvFuzzMutator::GetBinary() const {
+  std::vector<uint32_t> result;
+  fuzzer_->GetIRContext()->module()->ToBinary(&result, true);
+  return result;
+}
+
+std::string SpirvFuzzMutator::GetErrors() const {
+  return errors_->str();
+}
+
+void SpirvFuzzMutator::LogErrors(const std::string* path,
+                                 uint32_t count) const {
+  auto message = GetErrors();
+  std::cout << count << " | SpirvFuzzMutator (seed: " << seed_ << ")"
+            << std::endl;
+  std::cout << message << std::endl;
+
+  if (path) {
+    auto prefix = *path + std::to_string(count);
+
+    // Write errors to file.
+    std::ofstream(prefix + ".fuzzer.log") << "seed: " << seed_ << std::endl
+                                          << message << std::endl;
+
+    // Write the invalid SPIR-V binary.
+    util::WriteBinary(prefix + ".fuzzer.invalid.spv", GetBinary());
+
+    // Write the original SPIR-V binary.
+    util::WriteBinary(prefix + ".fuzzer.original.spv", original_binary_);
+
+    // Write transformations.
+    google::protobuf::util::JsonOptions options;
+    options.add_whitespace = true;
+    std::string json;
+    google::protobuf::util::MessageToJsonString(
+        fuzzer_->GetTransformationSequence(), &json, options);
+    std::ofstream(prefix + ".fuzzer.transformations.json") << json << std::endl;
+
+    std::ofstream binary_transformations(
+        prefix + ".fuzzer.transformations.binary",
+        std::ios::binary | std::ios::out);
+    fuzzer_->GetTransformationSequence().SerializeToOstream(
+        &binary_transformations);
+  }
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
new file mode 100644
index 0000000..073662e
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
@@ -0,0 +1,95 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/pseudo_random_generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// The mutator that uses spirv-fuzz to mutate SPIR-V.
+///
+/// The initial `binary` must be valid according to `target_env`. All other
+/// parameters (except for the `seed` which just initializes the RNG) are from
+/// the `spvtools::fuzz::Fuzzer` class.
+class SpirvFuzzMutator : public Mutator {
+ public:
+  /// Constructor.
+  /// @param target_env - the target environment for the `binary`.
+  /// @param binary - the SPIR-V binary. Must be valid.
+  /// @param seed - seed for the RNG.
+  /// @param donors - vector of donor suppliers.
+  /// @param enable_all_passes - whether to use all fuzzer passes.
+  /// @param repeated_pass_strategy - the strategy to use when selecting the
+  ///     next fuzzer pass.
+  /// @param validate_after_each_pass - whether to validate the binary after
+  ///     each fuzzer pass.
+  /// @param transformation_batch_size - the maximum number of transformations
+  ///     that will be applied during a single call to `Mutate`. It it's equal
+  ///     to 0 then we apply as much transformations as we can until the
+  ///     threshold in the spvtools::fuzz::Fuzzer is reached (see the doc for
+  ///     that class for more info).
+  SpirvFuzzMutator(
+      spv_target_env target_env,
+      std::vector<uint32_t> binary,
+      uint32_t seed,
+      const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
+      bool enable_all_passes,
+      spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
+      bool validate_after_each_pass,
+      uint32_t transformation_batch_size);
+
+  Result Mutate() override;
+  std::vector<uint32_t> GetBinary() const override;
+  void LogErrors(const std::string* path, uint32_t count) const override;
+  std::string GetErrors() const override;
+
+ private:
+  // The number of transformations that will be applied during a single call to
+  // the `Mutate` method. Is this only a lower bound since transformations are
+  // applied in batches by fuzzer passes (see docs for the
+  // `spvtools::fuzz::Fuzzer` for more info).
+  const uint32_t transformation_batch_size_;
+
+  // The errors produced by the `spvtools::fuzz::Fuzzer`.
+  std::unique_ptr<std::stringstream> errors_;
+  std::unique_ptr<spvtools::fuzz::Fuzzer> fuzzer_;
+  spvtools::ValidatorOptions validator_options_;
+
+  // The following fields are useful for debugging.
+
+  // The binary that the mutator is constructed with.
+  const std::vector<uint32_t> original_binary_;
+
+  // The seed that the mutator is constructed with.
+  const uint32_t seed_;
+};
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
new file mode 100644
index 0000000..4e17ad0
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
@@ -0,0 +1,159 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+
+#include <fstream>
+#include <iostream>
+#include <unordered_set>
+#include <utility>
+
+#include "spirv-tools/optimizer.hpp"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvOptMutator::SpirvOptMutator(spv_target_env target_env,
+                                 uint32_t seed,
+                                 std::vector<uint32_t> binary,
+                                 bool validate_after_each_opt,
+                                 uint32_t opt_batch_size)
+    : num_executions_(0),
+      is_valid_(true),
+      target_env_(target_env),
+      original_binary_(std::move(binary)),
+      seed_(seed),
+      opt_passes_({"--combine-access-chains",
+                   "--loop-unroll",
+                   "--merge-blocks",
+                   "--cfg-cleanup",
+                   "--eliminate-dead-functions",
+                   "--merge-return",
+                   "--wrap-opkill",
+                   "--eliminate-dead-code-aggressive",
+                   "--if-conversion",
+                   "--eliminate-local-single-store",
+                   "--eliminate-local-single-block",
+                   "--eliminate-dead-branches",
+                   "--scalar-replacement=0",
+                   "--eliminate-dead-inserts",
+                   "--eliminate-dead-members",
+                   "--simplify-instructions",
+                   "--private-to-local",
+                   "--ssa-rewrite",
+                   "--ccp",
+                   "--reduce-load-size",
+                   "--vector-dce",
+                   "--scalar-replacement=100",
+                   "--inline-entry-points-exhaustive",
+                   "--redundancy-elimination",
+                   "--convert-local-access-chains",
+                   "--copy-propagate-arrays",
+                   "--fix-storage-class"}),
+      optimized_binary_(),
+      validate_after_each_opt_(validate_after_each_opt),
+      opt_batch_size_(opt_batch_size),
+      generator_(seed) {
+  assert(spvtools::SpirvTools(target_env).Validate(original_binary_) &&
+         "Initial binary is invalid");
+  assert(!opt_passes_.empty() && "Must be at least one pass");
+}
+
+SpirvOptMutator::Result SpirvOptMutator::Mutate() {
+  assert(is_valid_ && "The optimizer is not longer valid");
+
+  const uint32_t kMaxNumExecutions = 100;
+  const uint32_t kMaxNumStuck = 10;
+
+  if (num_executions_ == kMaxNumExecutions) {
+    // We've applied this mutator many times already. Indicate to the user that
+    // it might be better to try a different mutator.
+    return {Status::kLimitReached, false};
+  }
+
+  num_executions_++;
+
+  // Get the input binary. If this is the first time we run this mutator, use
+  // the `original_binary_`. Otherwise, one of the following will be true:
+  // - the `optimized_binary_` is not empty.
+  // - the previous call to the `Mutate` method returned `kStuck`.
+  auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_;
+  optimized_binary_.clear();
+
+  assert(!binary.empty() && "Can't run the optimizer on an empty binary");
+
+  // Number of times spirv-opt wasn't able to produce any new result.
+  uint32_t num_stuck = 0;
+  do {
+    // Randomly select `opt_batch_size` optimization passes. If `opt_batch_size`
+    // is equal to 0, we will use the number of passes equal to the number of
+    // all available passes.
+    auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size();
+    std::vector<std::string> passes;
+
+    while (passes.size() < num_of_passes) {
+      auto idx =
+          generator_.GetUInt32(static_cast<uint32_t>(opt_passes_.size()));
+      passes.push_back(opt_passes_[idx]);
+    }
+
+    // Run the `binary` into the `optimized_binary_`.
+    spvtools::Optimizer optimizer(target_env_);
+    optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_));
+    optimizer.SetValidateAfterAll(validate_after_each_opt_);
+    optimizer.RegisterPassesFromFlags(passes);
+    if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) {
+      is_valid_ = false;
+      return {Status::kInvalid, true};
+    }
+  } while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck);
+
+  return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete,
+          !optimized_binary_.empty()};
+}
+
+std::vector<uint32_t> SpirvOptMutator::GetBinary() const {
+  return optimized_binary_;
+}
+
+std::string SpirvOptMutator::GetErrors() const {
+  return errors_.str();
+}
+
+void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const {
+  auto message = GetErrors();
+  std::cout << count << " | SpirvOptMutator (seed: " << seed_ << ")"
+            << std::endl;
+  std::cout << message << std::endl;
+
+  if (path) {
+    auto prefix = *path + std::to_string(count);
+
+    // Write errors to file.
+    std::ofstream(prefix + ".opt.log") << "seed: " << seed_ << std::endl
+                                       << message << std::endl;
+
+    // Write the invalid SPIR-V binary.
+    util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_);
+
+    // Write the original SPIR-V binary.
+    util::WriteBinary(prefix + ".opt.original.spv", original_binary_);
+  }
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
new file mode 100644
index 0000000..6c209cd
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "spirv-tools/libspirv.h"
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Mutates the SPIR-V module using the spirv-opt tool.
+///
+/// The initial `binary` must be valid according to `target_env`. On each call
+/// to the `Mutate` method the mutator selects `opt_batch_size` random
+/// optimization passes (with substitutions) and applies them to the binary.
+class SpirvOptMutator : public Mutator {
+ public:
+  /// Constructor.
+  /// @param target_env - target environment for the `binary`.
+  /// @param seed - seed for the RNG.
+  /// @param binary - SPIR-V binary. Must be valid.
+  /// @param validate_after_each_opt - whether to validate the binary after each
+  ///     optimization pass.
+  /// @param opt_batch_size - the maximum number of optimization passes that
+  ///     will be applied in a single call to `Mutate`. If it's equal to 0 then
+  ///     all available optimization passes are applied.
+  SpirvOptMutator(spv_target_env target_env,
+                  uint32_t seed,
+                  std::vector<uint32_t> binary,
+                  bool validate_after_each_opt,
+                  uint32_t opt_batch_size);
+
+  Result Mutate() override;
+  std::vector<uint32_t> GetBinary() const override;
+  void LogErrors(const std::string* path, uint32_t count) const override;
+  std::string GetErrors() const override;
+
+ private:
+  // Number of times this mutator was executed.
+  uint32_t num_executions_;
+
+  // Whether the last execution left it in a valid state.
+  bool is_valid_;
+
+  // Target environment for the SPIR-V binary.
+  const spv_target_env target_env_;
+
+  // The original SPIR-V binary. Useful for debugging.
+  const std::vector<uint32_t> original_binary_;
+
+  // The seed for the RNG. Useful for debugging.
+  const uint32_t seed_;
+
+  // All the optimization passes available.
+  const std::vector<std::string> opt_passes_;
+
+  // The result of the optimization.
+  std::vector<uint32_t> optimized_binary_;
+
+  // Whether we need to validate the binary after each optimization pass.
+  const bool validate_after_each_opt_;
+
+  // The number of optimization passes to apply at once.
+  const uint32_t opt_batch_size_;
+
+  // All the errors produced by the optimizer.
+  std::stringstream errors_;
+
+  // The random number generator initialized with `seed_`.
+  RandomGenerator generator_;
+};
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
new file mode 100644
index 0000000..93d2e8b
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
@@ -0,0 +1,190 @@
+// 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/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+
+#include <fstream>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/build_module.h"
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env,
+                                       std::vector<uint32_t> binary,
+                                       uint32_t seed,
+                                       uint32_t reductions_batch_size,
+                                       bool enable_all_reductions,
+                                       bool validate_after_each_reduction)
+    : ir_context_(nullptr),
+      finders_(),
+      generator_(seed),
+      errors_(),
+      is_valid_(true),
+      reductions_batch_size_(reductions_batch_size),
+      total_applied_reductions_(0),
+      enable_all_reductions_(enable_all_reductions),
+      validate_after_each_reduction_(validate_after_each_reduction),
+      original_binary_(std::move(binary)),
+      seed_(seed) {
+  ir_context_ = spvtools::BuildModule(
+      target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+      original_binary_.data(), original_binary_.size());
+  assert(ir_context_ && "|binary| is invalid");
+
+  do {
+    MaybeAddFinder<
+        spvtools::reduce::
+            ConditionalBranchToSimpleConditionalBranchOpportunityFinder>();
+    MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::OperandToConstReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::OperandToUndefReductionOpportunityFinder>();
+    MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::RemoveFunctionReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::RemoveSelectionReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>(
+        true);
+    MaybeAddFinder<
+        spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>();
+    MaybeAddFinder<
+        spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>();
+    MaybeAddFinder<spvtools::reduce::
+                       StructuredLoopToSelectionReductionOpportunityFinder>();
+  } while (finders_.empty());
+}
+
+Mutator::Result SpirvReduceMutator::Mutate() {
+  assert(is_valid_ && "Can't mutate invalid module");
+
+  // The upper limit on the number of applied reduction passes.
+  const uint32_t kMaxAppliedReductions = 500;
+  const auto old_applied_reductions = total_applied_reductions_;
+
+  // The upper limit on the number of failed attempts to apply reductions (i.e.
+  // when no reduction was returned by the reduction finder).
+  const uint32_t kMaxConsecutiveFailures = 10;
+  uint32_t num_consecutive_failures = 0;
+
+  // Iterate while we haven't exceeded the limit on the total number of applied
+  // reductions, the limit on the number of reductions applied at once and limit
+  // on the number of consecutive failed attempts.
+  while (total_applied_reductions_ < kMaxAppliedReductions &&
+         (reductions_batch_size_ == 0 ||
+          total_applied_reductions_ - old_applied_reductions <
+              reductions_batch_size_) &&
+         num_consecutive_failures < kMaxConsecutiveFailures) {
+    // Select an opportunity finder and get some reduction opportunities from
+    // it.
+    auto finder = GetRandomElement(&finders_);
+    auto reduction_opportunities =
+        finder->GetAvailableOpportunities(ir_context_.get(), 0);
+
+    if (reduction_opportunities.empty()) {
+      // There is nothing to reduce. We increase the counter to make sure we
+      // don't stuck in this situation.
+      num_consecutive_failures++;
+    } else {
+      // Apply a random reduction opportunity. The latter should be applicable.
+      auto opportunity = GetRandomElement(&reduction_opportunities);
+      assert(opportunity->PreconditionHolds() && "Preconditions should hold");
+      total_applied_reductions_++;
+      num_consecutive_failures = 0;
+      if (!ApplyReduction(opportunity)) {
+        // The module became invalid as a result of the applied reduction.
+        is_valid_ = false;
+        return {Mutator::Status::kInvalid,
+                total_applied_reductions_ != old_applied_reductions};
+      }
+    }
+  }
+
+  auto is_changed = total_applied_reductions_ != old_applied_reductions;
+  if (total_applied_reductions_ == kMaxAppliedReductions) {
+    return {Mutator::Status::kLimitReached, is_changed};
+  }
+
+  if (num_consecutive_failures == kMaxConsecutiveFailures) {
+    return {Mutator::Status::kStuck, is_changed};
+  }
+
+  assert(is_changed && "This is the only way left to break the loop");
+  return {Mutator::Status::kComplete, is_changed};
+}
+
+bool SpirvReduceMutator::ApplyReduction(
+    spvtools::reduce::ReductionOpportunity* reduction_opportunity) {
+  reduction_opportunity->TryToApply();
+  return !validate_after_each_reduction_ ||
+         spvtools::fuzz::fuzzerutil::IsValidAndWellFormed(
+             ir_context_.get(), spvtools::ValidatorOptions(),
+             util::GetBufferMessageConsumer(&errors_));
+}
+
+std::vector<uint32_t> SpirvReduceMutator::GetBinary() const {
+  std::vector<uint32_t> result;
+  ir_context_->module()->ToBinary(&result, true);
+  return result;
+}
+
+std::string SpirvReduceMutator::GetErrors() const {
+  return errors_.str();
+}
+
+void SpirvReduceMutator::LogErrors(const std::string* path,
+                                   uint32_t count) const {
+  auto message = GetErrors();
+  std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")"
+            << std::endl;
+  std::cout << message << std::endl;
+
+  if (path) {
+    auto prefix = *path + std::to_string(count);
+
+    // Write errors to file.
+    std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl
+                                           << message << std::endl;
+
+    // Write the invalid SPIR-V binary.
+    util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary());
+
+    // Write the original SPIR-V binary.
+    util::WriteBinary(prefix + ".reducer.original.spv", original_binary_);
+  }
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
new file mode 100644
index 0000000..9699b01
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
@@ -0,0 +1,132 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/fuzzers/random_generator.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Mutates SPIR-V binary by running spirv-reduce tool.
+///
+/// The initial `binary` must be valid according to `target_env`. Applies at
+/// most `reductions_batch_size` reductions at a time. This parameter is ignored
+/// if its value is 0. Uses a random subset of reduction opportunity finders by
+/// default. This can be overridden with the `enable_all_reductions` parameter.
+class SpirvReduceMutator : public Mutator {
+ public:
+  /// Constructor.
+  /// @param target_env - the target environment for the `binary`.
+  /// @param binary - SPIR-V binary. Must be valid.
+  /// @param seed - the seed for the RNG.
+  /// @param reductions_batch_size - the number of reduction passes that will be
+  ///     applied during a single call to `Mutate`. If it's equal to 0 then we
+  ///     apply the passes until we reach the threshold for the total number of
+  ///     applied passes.
+  /// @param enable_all_reductions - whether to use all reduction passes or only
+  ///     a randomly selected subset of them.
+  /// @param validate_after_each_reduction - whether to validate after each
+  ///     applied reduction.
+  SpirvReduceMutator(spv_target_env target_env,
+                     std::vector<uint32_t> binary,
+                     uint32_t seed,
+                     uint32_t reductions_batch_size,
+                     bool enable_all_reductions,
+                     bool validate_after_each_reduction);
+
+  Result Mutate() override;
+  std::vector<uint32_t> GetBinary() const override;
+  void LogErrors(const std::string* path, uint32_t count) const override;
+  std::string GetErrors() const override;
+
+ private:
+  template <typename T, typename... Args>
+  void MaybeAddFinder(Args&&... args) {
+    if (enable_all_reductions_ || generator_.GetBool()) {
+      finders_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
+    }
+  }
+
+  template <typename T>
+  T* GetRandomElement(std::vector<T>* arr) {
+    assert(!arr->empty() && "Can't get random element from an empty vector");
+    auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size()));
+    return &(*arr)[index];
+  }
+
+  template <typename T>
+  T* GetRandomElement(std::vector<std::unique_ptr<T>>* arr) {
+    assert(!arr->empty() && "Can't get random element from an empty vector");
+    auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size()));
+    return (*arr)[index].get();
+  }
+
+  bool ApplyReduction(
+      spvtools::reduce::ReductionOpportunity* reduction_opportunity);
+
+  // The SPIR-V binary that is being reduced.
+  std::unique_ptr<spvtools::opt::IRContext> ir_context_;
+
+  // The selected subset of reduction opportunity finders.
+  std::vector<std::unique_ptr<spvtools::reduce::ReductionOpportunityFinder>>
+      finders_;
+
+  // Random number generator initialized with `seed_`.
+  RandomGenerator generator_;
+
+  // All the errors produced by the reducer.
+  std::stringstream errors_;
+
+  // Whether the last call to the `Mutate` method produced the valid binary.
+  bool is_valid_;
+
+  // The number of reductions to apply on a single call to `Mutate`.
+  const uint32_t reductions_batch_size_;
+
+  // The total number of applied reductions.
+  uint32_t total_applied_reductions_;
+
+  // Whether we want to use all the reduction opportunity finders and not just a
+  // subset of them.
+  const bool enable_all_reductions_;
+
+  // Whether we want to validate all the binary after each reduction.
+  const bool validate_after_each_reduction_;
+
+  // The original binary that was used to initialize this mutator.
+  // Useful for debugging.
+  const std::vector<uint32_t> original_binary_;
+
+  // The seed that was used to initialize the random number generator.
+  // Useful for debugging.
+  const uint32_t seed_;
+};
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc
new file mode 100644
index 0000000..8c14547
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc
@@ -0,0 +1,30 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+void OverrideCliParams(FuzzerCliParams& /*unused*/) {
+  // Leave the CLI parameters unchanged.
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc
new file mode 100644
index 0000000..08111d9
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+void OverrideCliParams(FuzzerCliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kHlsl;
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc
new file mode 100644
index 0000000..86a493e
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+void OverrideCliParams(FuzzerCliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kMsl;
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc
new file mode 100644
index 0000000..2cbfef7
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+void OverrideCliParams(FuzzerCliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kSpv;
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc
new file mode 100644
index 0000000..4538973
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <cassert>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+void OverrideCliParams(FuzzerCliParams& cli_params) {
+  assert(cli_params.fuzzing_target == FuzzingTarget::kAll &&
+         "The fuzzing target should not have been set by a CLI parameter: it "
+         "should have its default value.");
+  cli_params.fuzzing_target = FuzzingTarget::kWgsl;
+}
+
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc
new file mode 100644
index 0000000..a20a1f4
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc
@@ -0,0 +1,157 @@
+// 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 <fstream>
+#include <iostream>
+
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace util {
+namespace {
+
+bool WriteBinary(const std::string& path, const uint8_t* data, size_t size) {
+  std::ofstream spv(path, std::ios::binary);
+  return spv && spv.write(reinterpret_cast<const char*>(data),
+                          static_cast<std::streamsize>(size));
+}
+
+void LogError(uint32_t index,
+              const std::string& type,
+              const std::string& message,
+              const std::string* path,
+              const uint8_t* data,
+              size_t size,
+              const std::string* wgsl) {
+  std::cout << index << " | " << type << ": " << message << std::endl;
+
+  if (path) {
+    auto prefix = *path + std::to_string(index);
+    std::ofstream(prefix + ".log") << message << std::endl;
+
+    WriteBinary(prefix + ".spv", data, size);
+
+    if (wgsl) {
+      std::ofstream(prefix + ".wgsl") << *wgsl << std::endl;
+    }
+  }
+}
+
+}  // namespace
+
+spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer) {
+  return [buffer](spv_message_level_t level, const char*,
+                  const spv_position_t& position, const char* message) {
+    std::string status;
+    switch (level) {
+      case SPV_MSG_FATAL:
+      case SPV_MSG_INTERNAL_ERROR:
+      case SPV_MSG_ERROR:
+        status = "ERROR";
+        break;
+      case SPV_MSG_WARNING:
+      case SPV_MSG_INFO:
+      case SPV_MSG_DEBUG:
+        status = "INFO";
+        break;
+    }
+    *buffer << status << " " << position.line << ":" << position.column << ":"
+            << position.index << ": " << message << std::endl;
+  };
+}
+
+void LogMutatorError(const Mutator& mutator, const std::string& error_dir) {
+  static uint32_t mutator_count = 0;
+  auto error_path = error_dir.empty() ? error_dir : error_dir + "/mutator/";
+  mutator.LogErrors(error_dir.empty() ? nullptr : &error_path, mutator_count++);
+}
+
+void LogWgslError(const std::string& message,
+                  const uint8_t* data,
+                  size_t size,
+                  const std::string& wgsl,
+                  OutputFormat output_format,
+                  const std::string& error_dir) {
+  static uint32_t wgsl_count = 0;
+  std::string error_type;
+  switch (output_format) {
+    case OutputFormat::kSpv:
+      error_type = "WGSL -> SPV";
+      break;
+    case OutputFormat::kMSL:
+      error_type = "WGSL -> MSL";
+      break;
+    case OutputFormat::kHLSL:
+      error_type = "WGSL -> HLSL";
+      break;
+    case OutputFormat::kWGSL:
+      error_type = "WGSL -> WGSL";
+      break;
+  }
+  auto error_path = error_dir.empty() ? error_dir : error_dir + "/wgsl/";
+  LogError(wgsl_count++, error_type, message,
+           error_dir.empty() ? nullptr : &error_path, data, size, &wgsl);
+}
+
+void LogSpvError(const std::string& message,
+                 const uint8_t* data,
+                 size_t size,
+                 const std::string& error_dir) {
+  static uint32_t spv_count = 0;
+  auto error_path = error_dir.empty() ? error_dir : error_dir + "/spv/";
+  LogError(spv_count++, "SPV -> WGSL", message,
+           error_dir.empty() ? nullptr : &error_path, data, size, nullptr);
+}
+
+bool ReadBinary(const std::string& path, std::vector<uint32_t>* out) {
+  if (!out) {
+    return false;
+  }
+
+  std::ifstream file(path, std::ios::binary | std::ios::ate);
+  if (!file) {
+    return false;
+  }
+
+  size_t size = static_cast<size_t>(file.tellg());
+  if (!file) {
+    return false;
+  }
+
+  file.seekg(0);
+  if (!file) {
+    return false;
+  }
+
+  std::vector<char> binary(size);
+  if (!file.read(binary.data(), size)) {
+    return false;
+  }
+
+  out->resize(binary.size() / sizeof(uint32_t));
+  std::memcpy(out->data(), binary.data(), binary.size());
+  return true;
+}
+
+bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary) {
+  return WriteBinary(path, reinterpret_cast<const uint8_t*>(binary.data()),
+                     binary.size() * sizeof(uint32_t));
+}
+
+}  // namespace util
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h
new file mode 100644
index 0000000..301e3ba
--- /dev/null
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
+#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "spirv-tools/libspirv.hpp"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace util {
+
+/// @param buffer will be used to output errors by the returned message
+///     consumer. Must remain in scope as long as the returned consumer is in
+///     scope.
+/// @return the message consumer that will print errors to the `buffer`.
+spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer);
+
+/// Output errors from the SPV -> WGSL conversion.
+///
+/// @param message - the error message.
+/// @param data - invalid SPIR-V binary.
+/// @param size - the size of `data`.
+/// @param error_dir - the directory, to which the binary will be printed to.
+///     If it's empty, the invalid binary and supplemental files will not be
+///     printed. Otherwise, it must have a `spv/` subdirectory.
+void LogSpvError(const std::string& message,
+                 const uint8_t* data,
+                 size_t size,
+                 const std::string& error_dir);
+
+/// Output errors from the WGSL -> `output_format` conversion.
+///
+/// @param message - the error message.
+/// @param data - the SPIR-V binary that generated the WGSL binary.
+/// @param size - the size of `data`.
+/// @param wgsl - the invalid WGSL binary.
+/// @param output_format - the format which we attempted to convert `wgsl` to.
+/// @param error_dir - the directory, to which the binary will be printed out.
+///     If it's empty, the invalid binary and supplemental files will not be
+///     printed. Otherwise, it must have a `wgsl/` subdirectory.
+void LogWgslError(const std::string& message,
+                  const uint8_t* data,
+                  size_t size,
+                  const std::string& wgsl,
+                  OutputFormat output_format,
+                  const std::string& error_dir);
+
+/// Output errors produced by the mutator.
+///
+/// @param mutator - the mutator with invalid state.
+/// @param error_dir - the directory, to which invalid files will be printed to.
+///     If it's empty, the invalid binary and supplemental files will not be
+///     printed. Otherwise, it must have a `mutator/` subdirectory.
+void LogMutatorError(const Mutator& mutator, const std::string& error_dir);
+
+/// Reads SPIR-V binary from `path` into `out`. Returns `true` if successful and
+/// `false` otherwise (in this case, `out` is unchanged).
+///
+/// @param path - the path to the SPIR-V binary.
+/// @param out - may be a `nullptr`. In this case, `false` is returned.
+/// @return `true` if successful and `false` otherwise.
+bool ReadBinary(const std::string& path, std::vector<uint32_t>* out);
+
+/// Writes `binary` into `path`.
+///
+/// @param path - the path to write `binary` to.
+/// @param binary - SPIR-V binary.
+/// @return whether the operation was successful.
+bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary);
+
+}  // namespace util
+}  // namespace spvtools_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
diff --git a/src/tint/fuzzers/tint_spv_reader_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_fuzzer.cc
new file mode 100644
index 0000000..0daab5d
--- /dev/null
+++ b/src/tint/fuzzers/tint_spv_reader_fuzzer.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 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 <vector>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kSpv, OutputFormat::kNone);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc
new file mode 100644
index 0000000..5f2f2c7
--- /dev/null
+++ b/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <vector>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
+                                           OutputFormat::kHLSL);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc
new file mode 100644
index 0000000..0b1446b
--- /dev/null
+++ b/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc
@@ -0,0 +1,37 @@
+// 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 <vector>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  DataBuilder db(data, size);
+  writer::msl::Options options;
+  GenerateMslOptions(&db, &options);
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
+                                           OutputFormat::kMSL);
+  fuzzer.SetOptionsMsl(options);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc
new file mode 100644
index 0000000..b3cc6b0
--- /dev/null
+++ b/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc
@@ -0,0 +1,37 @@
+// 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 <vector>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  DataBuilder db(data, size);
+  writer::spirv::Options options;
+  GenerateSpirvOptions(&db, &options);
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
+                                           OutputFormat::kSpv);
+  fuzzer.SetOptionsSpirv(options);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc
new file mode 100644
index 0000000..eff349d
--- /dev/null
+++ b/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <vector>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv,
+                                           OutputFormat::kWGSL);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc b/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc
new file mode 100644
index 0000000..e1ad99e
--- /dev/null
+++ b/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc
@@ -0,0 +1,35 @@
+// 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/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+#include "src/tint/fuzzers/transform_builder.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TransformBuilder tb(data, size);
+  tb.AddTransform<transform::VertexPulling>();
+
+  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL);
+  fuzzer.SetTransformManager(tb.manager(), tb.data_map());
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc
new file mode 100644
index 0000000..d1b1f27
--- /dev/null
+++ b/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 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 <string>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_common_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kNone);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc
new file mode 100644
index 0000000..d5a15a7
--- /dev/null
+++ b/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <string>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
+                                           OutputFormat::kHLSL);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc
new file mode 100644
index 0000000..58fdf8b
--- /dev/null
+++ b/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc
@@ -0,0 +1,37 @@
+// 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 <string>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  DataBuilder db(data, size);
+  writer::msl::Options options;
+  GenerateMslOptions(&db, &options);
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
+                                           OutputFormat::kMSL);
+  fuzzer.SetOptionsMsl(options);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc
new file mode 100644
index 0000000..f4932dd
--- /dev/null
+++ b/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc
@@ -0,0 +1,37 @@
+// 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 <string>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  DataBuilder db(data, size);
+  writer::spirv::Options options;
+  GenerateSpirvOptions(&db, &options);
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
+                                           OutputFormat::kSpv);
+  fuzzer.SetOptionsSpirv(options);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc
new file mode 100644
index 0000000..8cb7eaa
--- /dev/null
+++ b/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@
+// 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 <string>
+
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL,
+                                           OutputFormat::kWGSL);
+  fuzzer.SetDumpInput(GetCliParams().dump_input);
+  fuzzer.SetEnforceValidity(GetCliParams().enforce_validity);
+
+  return fuzzer.Run(data, size);
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/transform_builder.h b/src/tint/fuzzers/transform_builder.h
new file mode 100644
index 0000000..7c5073d
--- /dev/null
+++ b/src/tint/fuzzers/transform_builder.h
@@ -0,0 +1,228 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_
+#define SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_
+
+#include <string>
+#include <vector>
+
+#include "include/tint/tint.h"
+
+#include "src/tint/fuzzers/data_builder.h"
+#include "src/tint/fuzzers/shuffle_transform.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Fuzzer utility class to build inputs for transforms and setup the transform
+/// manager.
+class TransformBuilder {
+ public:
+  /// @brief Initializes the internal builder using a seed value
+  /// @param seed - seed value passed to engine
+  explicit TransformBuilder(uint64_t seed) : builder_(seed) {}
+
+  /// @brief Initializes the internal builder using seed data
+  /// @param data - data fuzzer to calculate seed from
+  /// @param size - size of data buffer
+  explicit TransformBuilder(const uint8_t* data, size_t size)
+      : builder_(data, size) {
+    assert(data != nullptr && "|data| must be !nullptr");
+  }
+
+  ~TransformBuilder() = default;
+
+  /// @returns manager for transforms
+  transform::Manager* manager() { return &manager_; }
+
+  /// @returns data for transforms
+  transform::DataMap* data_map() { return &data_map_; }
+
+  /// Adds a transform and needed data to |manager_| and |data_map_|.
+  /// @tparam T - A class that inherits from transform::Transform and has an
+  ///             explicit specialization in AddTransformImpl.
+  template <typename T>
+  void AddTransform() {
+    static_assert(std::is_base_of<transform::Transform, T>::value,
+                  "T is not a transform::Transform");
+    AddTransformImpl<T>::impl(this);
+  }
+
+  /// Helper that invokes Add*Transform for all of the platform independent
+  /// passes.
+  void AddPlatformIndependentPasses() {
+    AddTransform<transform::Robustness>();
+    AddTransform<transform::FirstIndexOffset>();
+    AddTransform<transform::BindingRemapper>();
+    AddTransform<transform::Renamer>();
+    AddTransform<transform::SingleEntryPoint>();
+    AddTransform<transform::VertexPulling>();
+  }
+
+ private:
+  DataBuilder builder_;
+  transform::Manager manager_;
+  transform::DataMap data_map_;
+
+  DataBuilder* builder() { return &builder_; }
+
+  /// Implementation of AddTransform, specialized for each transform that is
+  /// implemented. Default implementation intentionally deleted to cause compile
+  /// error if unimplemented type passed in.
+  /// @tparam T - A fuzzer transform
+  template <typename T>
+  struct AddTransformImpl;
+
+  /// Implementation of AddTransform for ShuffleTransform
+  template <>
+  struct AddTransformImpl<ShuffleTransform> {
+    /// Add instance of ShuffleTransform to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      tb->manager()->Add<ShuffleTransform>(tb->builder_.build<size_t>());
+    }
+  };
+
+  /// Implementation of AddTransform for transform::Robustness
+  template <>
+  struct AddTransformImpl<transform::Robustness> {
+    /// Add instance of transform::Robustness to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      tb->manager()->Add<transform::Robustness>();
+    }
+  };
+
+  /// Implementation of AddTransform for transform::FirstIndexOffset
+  template <>
+  struct AddTransformImpl<transform::FirstIndexOffset> {
+    /// Add instance of transform::FirstIndexOffset to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      struct Config {
+        uint32_t group;
+        uint32_t binding;
+      };
+
+      Config config = tb->builder()->build<Config>();
+
+      tb->data_map()->Add<tint::transform::FirstIndexOffset::BindingPoint>(
+          config.binding, config.group);
+      tb->manager()->Add<transform::FirstIndexOffset>();
+    }
+  };
+
+  /// Implementation of AddTransform for transform::BindingRemapper
+  template <>
+  struct AddTransformImpl<transform::BindingRemapper> {
+    /// Add instance of transform::BindingRemapper to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      struct Config {
+        uint8_t old_group;
+        uint8_t old_binding;
+        uint8_t new_group;
+        uint8_t new_binding;
+        ast::Access new_access;
+      };
+
+      std::vector<Config> configs = tb->builder()->vector<Config>();
+      transform::BindingRemapper::BindingPoints binding_points;
+      transform::BindingRemapper::AccessControls accesses;
+      for (const auto& config : configs) {
+        binding_points[{config.old_binding, config.old_group}] = {
+            config.new_binding, config.new_group};
+        accesses[{config.old_binding, config.old_group}] = config.new_access;
+      }
+
+      tb->data_map()->Add<transform::BindingRemapper::Remappings>(
+          binding_points, accesses, tb->builder()->build<bool>());
+      tb->manager()->Add<transform::BindingRemapper>();
+    }
+  };
+
+  /// Implementation of AddTransform for transform::Renamer
+  template <>
+  struct AddTransformImpl<transform::Renamer> {
+    /// Add instance of transform::Renamer to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      tb->manager()->Add<transform::Renamer>();
+    }
+  };
+
+  /// Implementation of AddTransform for transform::SingleEntryPoint
+  template <>
+  struct AddTransformImpl<transform::SingleEntryPoint> {
+    /// Add instance of transform::SingleEntryPoint to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      auto input = tb->builder()->build<std::string>();
+      transform::SingleEntryPoint::Config cfg(input);
+
+      tb->data_map()->Add<transform::SingleEntryPoint::Config>(cfg);
+      tb->manager()->Add<transform::SingleEntryPoint>();
+    }
+  };  // struct AddTransformImpl<transform::SingleEntryPoint>
+
+  /// Implementation of AddTransform for transform::VertexPulling
+  template <>
+  struct AddTransformImpl<transform::VertexPulling> {
+    /// Add instance of transform::VertexPulling to TransformBuilder
+    /// @param tb - TransformBuilder to add transform to
+    static void impl(TransformBuilder* tb) {
+      transform::VertexPulling::Config cfg;
+      cfg.entry_point_name = tb->builder()->build<std::string>();
+      cfg.vertex_state =
+          tb->builder()->vector<transform::VertexBufferLayoutDescriptor>(
+              GenerateVertexBufferLayoutDescriptor);
+      cfg.pulling_group = tb->builder()->build<uint32_t>();
+
+      tb->data_map()->Add<transform::VertexPulling::Config>(cfg);
+      tb->manager()->Add<transform::VertexPulling>();
+    }
+
+   private:
+    /// Generate an instance of transform::VertexAttributeDescriptor
+    /// @param b - DataBuilder to use
+    static transform::VertexAttributeDescriptor
+    GenerateVertexAttributeDescriptor(DataBuilder* b) {
+      transform::VertexAttributeDescriptor desc{};
+      desc.format = b->enum_class<transform::VertexFormat>(
+          static_cast<uint8_t>(transform::VertexFormat::kLastEntry) + 1);
+      desc.offset = b->build<uint32_t>();
+      desc.shader_location = b->build<uint32_t>();
+      return desc;
+    }
+
+    /// Generate an instance of VertexBufferLayoutDescriptor
+    /// @param b - DataBuilder to use
+    static transform::VertexBufferLayoutDescriptor
+    GenerateVertexBufferLayoutDescriptor(DataBuilder* b) {
+      transform::VertexBufferLayoutDescriptor desc;
+      desc.array_stride = b->build<uint32_t>();
+      desc.step_mode = b->enum_class<transform::VertexStepMode>(
+          static_cast<uint8_t>(transform::VertexStepMode::kLastEntry) + 1);
+      desc.attributes = b->vector<transform::VertexAttributeDescriptor>(
+          GenerateVertexAttributeDescriptor);
+      return desc;
+    }
+  };
+};  // class TransformBuilder
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_
diff --git a/src/tint/inspector/entry_point.cc b/src/tint/inspector/entry_point.cc
new file mode 100644
index 0000000..9b0b46f
--- /dev/null
+++ b/src/tint/inspector/entry_point.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 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/inspector/entry_point.h"
+
+namespace tint {
+namespace inspector {
+
+StageVariable::StageVariable() = default;
+StageVariable::StageVariable(const StageVariable& other)
+    : name(other.name),
+      has_location_attribute(other.has_location_attribute),
+      location_attribute(other.location_attribute),
+      has_location_decoration(has_location_attribute),
+      location_decoration(location_attribute),
+      component_type(other.component_type),
+      composition_type(other.composition_type),
+      interpolation_type(other.interpolation_type),
+      interpolation_sampling(other.interpolation_sampling) {}
+
+StageVariable::~StageVariable() = default;
+
+EntryPoint::EntryPoint() = default;
+EntryPoint::EntryPoint(EntryPoint&) = default;
+EntryPoint::EntryPoint(EntryPoint&&) = default;
+EntryPoint::~EntryPoint() = default;
+
+InterpolationType ASTToInspectorInterpolationType(
+    ast::InterpolationType ast_type) {
+  switch (ast_type) {
+    case ast::InterpolationType::kPerspective:
+      return InterpolationType::kPerspective;
+    case ast::InterpolationType::kLinear:
+      return InterpolationType::kLinear;
+    case ast::InterpolationType::kFlat:
+      return InterpolationType::kFlat;
+  }
+
+  return InterpolationType::kUnknown;
+}
+
+InterpolationSampling ASTToInspectorInterpolationSampling(
+    ast::InterpolationSampling sampling) {
+  switch (sampling) {
+    case ast::InterpolationSampling::kNone:
+      return InterpolationSampling::kNone;
+    case ast::InterpolationSampling::kCenter:
+      return InterpolationSampling::kCenter;
+    case ast::InterpolationSampling::kCentroid:
+      return InterpolationSampling::kCentroid;
+    case ast::InterpolationSampling::kSample:
+      return InterpolationSampling::kSample;
+  }
+
+  return InterpolationSampling::kUnknown;
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/entry_point.h b/src/tint/inspector/entry_point.h
new file mode 100644
index 0000000..46b87dd
--- /dev/null
+++ b/src/tint/inspector/entry_point.h
@@ -0,0 +1,187 @@
+// Copyright 2020 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_INSPECTOR_ENTRY_POINT_H_
+#define SRC_TINT_INSPECTOR_ENTRY_POINT_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/pipeline_stage.h"
+
+namespace tint {
+namespace inspector {
+
+/// Base component type of a stage variable.
+enum class ComponentType {
+  kUnknown = -1,
+  kFloat,
+  kUInt,
+  kSInt,
+};
+
+/// Composition of components of a stage variable.
+enum class CompositionType {
+  kUnknown = -1,
+  kScalar,
+  kVec2,
+  kVec3,
+  kVec4,
+};
+
+/// Type of interpolation of a stage variable.
+enum class InterpolationType { kUnknown = -1, kPerspective, kLinear, kFlat };
+
+/// Type of interpolation sampling of a stage variable.
+enum class InterpolationSampling {
+  kUnknown = -1,
+  kNone,
+  kCenter,
+  kCentroid,
+  kSample
+};
+
+/// Reflection data about an entry point input or output.
+struct StageVariable {
+  /// Constructor
+  StageVariable();
+  /// Copy constructor
+  /// @param other the StageVariable to copy
+  StageVariable(const StageVariable& other);
+  /// Destructor
+  ~StageVariable();
+
+  /// Name of the variable in the shader.
+  std::string name;
+  /// Is location attribute present
+  bool has_location_attribute = false;
+  /// Value of the location attribute, only valid if #has_location_attribute is
+  /// true.
+  uint32_t location_attribute;
+  /// Is Location attribute present
+  /// [DEPRECATED]: Use #has_location_attribute
+  bool& has_location_decoration = has_location_attribute;
+  /// Value of Location Decoration, only valid if #has_location_decoration is
+  /// true.
+  /// [DEPRECATED]: Use #location_attribute
+  uint32_t& location_decoration = location_attribute;
+  /// Scalar type that the variable is composed of.
+  ComponentType component_type = ComponentType::kUnknown;
+  /// How the scalars are composed for the variable.
+  CompositionType composition_type = CompositionType::kUnknown;
+  /// Interpolation type of the variable.
+  InterpolationType interpolation_type = InterpolationType::kUnknown;
+  /// Interpolation sampling of the variable.
+  InterpolationSampling interpolation_sampling =
+      InterpolationSampling::kUnknown;
+};
+
+/// Convert from internal ast::InterpolationType to public ::InterpolationType.
+/// @param ast_type internal value to convert from
+/// @returns the publicly visible equivalent
+InterpolationType ASTToInspectorInterpolationType(
+    ast::InterpolationType ast_type);
+
+/// Convert from internal ast::InterpolationSampling to public
+/// ::InterpolationSampling
+/// @param sampling internal value to convert from
+/// @returns the publicly visible equivalent
+InterpolationSampling ASTToInspectorInterpolationSampling(
+    ast::InterpolationSampling sampling);
+
+/// Reflection data about a pipeline overridable constant referenced by an entry
+/// point
+struct OverridableConstant {
+  /// Name of the constant
+  std::string name;
+
+  /// ID of the constant
+  uint16_t numeric_id;
+
+  /// Type of the scalar
+  enum class Type {
+    kBool,
+    kFloat32,
+    kUint32,
+    kInt32,
+  };
+
+  /// Type of the scalar
+  Type type;
+
+  /// Does this pipeline overridable constant have an initializer?
+  bool is_initialized = false;
+
+  /// Does this pipeline overridable constant have a numeric ID specified
+  /// explicitly?
+  bool is_numeric_id_specified = false;
+};
+
+/// Reflection data for an entry point in the shader.
+struct EntryPoint {
+  /// Constructors
+  EntryPoint();
+  /// Copy Constructor
+  EntryPoint(EntryPoint&);
+  /// Move Constructor
+  EntryPoint(EntryPoint&&);
+  ~EntryPoint();
+
+  /// The entry point name
+  std::string name;
+  /// Remapped entry point name in the backend
+  std::string remapped_name;
+  /// The entry point stage
+  ast::PipelineStage stage = ast::PipelineStage::kNone;
+  /// The workgroup x size
+  uint32_t workgroup_size_x = 0;
+  /// The workgroup y size
+  uint32_t workgroup_size_y = 0;
+  /// The workgroup z size
+  uint32_t workgroup_size_z = 0;
+  /// List of the input variable accessed via this entry point.
+  std::vector<StageVariable> input_variables;
+  /// List of the output variable accessed via this entry point.
+  std::vector<StageVariable> output_variables;
+  /// List of the pipeline overridable constants accessed via this entry point.
+  std::vector<OverridableConstant> overridable_constants;
+  /// Does the entry point use the sample_mask builtin as an input builtin
+  /// variable.
+  bool input_sample_mask_used = false;
+  /// Does the entry point use the sample_mask builtin as an output builtin
+  /// variable.
+  bool output_sample_mask_used = false;
+  /// Does the entry point use the position builtin as an input builtin
+  /// variable.
+  bool input_position_used = false;
+  /// Does the entry point use the front_facing builtin
+  bool front_facing_used = false;
+  /// Does the entry point use the sample_index builtin
+  bool sample_index_used = false;
+  /// Does the entry point use the num_workgroups builtin
+  bool num_workgroups_used = false;
+
+  /// @returns the size of the workgroup in {x,y,z} format
+  std::tuple<uint32_t, uint32_t, uint32_t> workgroup_size() {
+    return std::tuple<uint32_t, uint32_t, uint32_t>(
+        workgroup_size_x, workgroup_size_y, workgroup_size_z);
+  }
+};
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_ENTRY_POINT_H_
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
new file mode 100644
index 0000000..ed49371
--- /dev/null
+++ b/src/tint/inspector/inspector.cc
@@ -0,0 +1,969 @@
+// Copyright 2020 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/inspector/inspector.h"
+
+#include <limits>
+#include <utility>
+
+#include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/call_expression.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/location_attribute.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/sint_literal_expression.h"
+#include "src/tint/ast/uint_literal_expression.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/matrix_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/u32_type.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/sem/vector_type.h"
+#include "src/tint/sem/void_type.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/unique_vector.h"
+
+namespace tint {
+namespace inspector {
+
+namespace {
+
+void AppendResourceBindings(std::vector<ResourceBinding>* dest,
+                            const std::vector<ResourceBinding>& orig) {
+  TINT_ASSERT(Inspector, dest);
+  if (!dest) {
+    return;
+  }
+
+  dest->reserve(dest->size() + orig.size());
+  dest->insert(dest->end(), orig.begin(), orig.end());
+}
+
+std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(
+    const sem::Type* type) {
+  if (type->is_float_scalar()) {
+    return {ComponentType::kFloat, CompositionType::kScalar};
+  } else if (type->is_float_vector()) {
+    auto* vec = type->As<sem::Vector>();
+    if (vec->Width() == 2) {
+      return {ComponentType::kFloat, CompositionType::kVec2};
+    } else if (vec->Width() == 3) {
+      return {ComponentType::kFloat, CompositionType::kVec3};
+    } else if (vec->Width() == 4) {
+      return {ComponentType::kFloat, CompositionType::kVec4};
+    }
+  } else if (type->is_unsigned_integer_scalar()) {
+    return {ComponentType::kUInt, CompositionType::kScalar};
+  } else if (type->is_unsigned_integer_vector()) {
+    auto* vec = type->As<sem::Vector>();
+    if (vec->Width() == 2) {
+      return {ComponentType::kUInt, CompositionType::kVec2};
+    } else if (vec->Width() == 3) {
+      return {ComponentType::kUInt, CompositionType::kVec3};
+    } else if (vec->Width() == 4) {
+      return {ComponentType::kUInt, CompositionType::kVec4};
+    }
+  } else if (type->is_signed_integer_scalar()) {
+    return {ComponentType::kSInt, CompositionType::kScalar};
+  } else if (type->is_signed_integer_vector()) {
+    auto* vec = type->As<sem::Vector>();
+    if (vec->Width() == 2) {
+      return {ComponentType::kSInt, CompositionType::kVec2};
+    } else if (vec->Width() == 3) {
+      return {ComponentType::kSInt, CompositionType::kVec3};
+    } else if (vec->Width() == 4) {
+      return {ComponentType::kSInt, CompositionType::kVec4};
+    }
+  }
+  return {ComponentType::kUnknown, CompositionType::kUnknown};
+}
+
+std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
+    const sem::Type* type,
+    const ast::AttributeList& attributes) {
+  auto* interpolation_attribute =
+      ast::GetAttribute<ast::InterpolateAttribute>(attributes);
+  if (type->is_integer_scalar_or_vector()) {
+    return {InterpolationType::kFlat, InterpolationSampling::kNone};
+  }
+
+  if (!interpolation_attribute) {
+    return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
+  }
+
+  auto interpolation_type = interpolation_attribute->type;
+  auto sampling = interpolation_attribute->sampling;
+  if (interpolation_type != ast::InterpolationType::kFlat &&
+      sampling == ast::InterpolationSampling::kNone) {
+    sampling = ast::InterpolationSampling::kCenter;
+  }
+  return {ASTToInspectorInterpolationType(interpolation_type),
+          ASTToInspectorInterpolationSampling(sampling)};
+}
+
+}  // namespace
+
+Inspector::Inspector(const Program* program) : program_(program) {}
+
+Inspector::~Inspector() = default;
+
+std::vector<EntryPoint> Inspector::GetEntryPoints() {
+  std::vector<EntryPoint> result;
+
+  for (auto* func : program_->AST().Functions()) {
+    if (!func->IsEntryPoint()) {
+      continue;
+    }
+
+    auto* sem = program_->Sem().Get(func);
+
+    EntryPoint entry_point;
+    entry_point.name = program_->Symbols().NameFor(func->symbol);
+    entry_point.remapped_name = program_->Symbols().NameFor(func->symbol);
+    entry_point.stage = func->PipelineStage();
+
+    auto wgsize = sem->WorkgroupSize();
+    entry_point.workgroup_size_x = wgsize[0].value;
+    entry_point.workgroup_size_y = wgsize[1].value;
+    entry_point.workgroup_size_z = wgsize[2].value;
+    if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
+        wgsize[2].overridable_const) {
+      // TODO(crbug.com/tint/713): Handle overridable constants.
+      TINT_ASSERT(Inspector, false);
+    }
+
+    for (auto* param : sem->Parameters()) {
+      AddEntryPointInOutVariables(
+          program_->Symbols().NameFor(param->Declaration()->symbol),
+          param->Type(), param->Declaration()->attributes,
+          entry_point.input_variables);
+
+      entry_point.input_position_used |=
+          ContainsBuiltin(ast::Builtin::kPosition, param->Type(),
+                          param->Declaration()->attributes);
+      entry_point.front_facing_used |=
+          ContainsBuiltin(ast::Builtin::kFrontFacing, param->Type(),
+                          param->Declaration()->attributes);
+      entry_point.sample_index_used |=
+          ContainsBuiltin(ast::Builtin::kSampleIndex, param->Type(),
+                          param->Declaration()->attributes);
+      entry_point.input_sample_mask_used |=
+          ContainsBuiltin(ast::Builtin::kSampleMask, param->Type(),
+                          param->Declaration()->attributes);
+      entry_point.num_workgroups_used |=
+          ContainsBuiltin(ast::Builtin::kNumWorkgroups, param->Type(),
+                          param->Declaration()->attributes);
+    }
+
+    if (!sem->ReturnType()->Is<sem::Void>()) {
+      AddEntryPointInOutVariables("<retval>", sem->ReturnType(),
+                                  func->return_type_attributes,
+                                  entry_point.output_variables);
+
+      entry_point.output_sample_mask_used =
+          ContainsBuiltin(ast::Builtin::kSampleMask, sem->ReturnType(),
+                          func->return_type_attributes);
+    }
+
+    for (auto* var : sem->TransitivelyReferencedGlobals()) {
+      auto* decl = var->Declaration();
+
+      auto name = program_->Symbols().NameFor(decl->symbol);
+
+      auto* global = var->As<sem::GlobalVariable>();
+      if (global && global->IsOverridable()) {
+        OverridableConstant overridable_constant;
+        overridable_constant.name = name;
+        overridable_constant.numeric_id = global->ConstantId();
+        auto* type = var->Type();
+        TINT_ASSERT(Inspector, type->is_scalar());
+        if (type->is_bool_scalar_or_vector()) {
+          overridable_constant.type = OverridableConstant::Type::kBool;
+        } else if (type->is_float_scalar()) {
+          overridable_constant.type = OverridableConstant::Type::kFloat32;
+        } else if (type->is_signed_integer_scalar()) {
+          overridable_constant.type = OverridableConstant::Type::kInt32;
+        } else if (type->is_unsigned_integer_scalar()) {
+          overridable_constant.type = OverridableConstant::Type::kUint32;
+        } else {
+          TINT_UNREACHABLE(Inspector, diagnostics_);
+        }
+
+        overridable_constant.is_initialized =
+            global->Declaration()->constructor;
+        overridable_constant.is_numeric_id_specified =
+            ast::HasAttribute<ast::IdAttribute>(
+                global->Declaration()->attributes);
+
+        entry_point.overridable_constants.push_back(overridable_constant);
+      }
+    }
+
+    result.push_back(std::move(entry_point));
+  }
+
+  return result;
+}
+
+std::string Inspector::GetRemappedNameForEntryPoint(
+    const std::string& entry_point) {
+  // TODO(rharrison): Reenable once all of the backends are using the renamed
+  //                  entry points.
+
+  //  auto* func = FindEntryPointByName(entry_point);
+  //  if (!func) {
+  //    return {};
+  //  }
+  //  return func->name();
+  return entry_point;
+}
+
+std::map<uint32_t, Scalar> Inspector::GetConstantIDs() {
+  std::map<uint32_t, Scalar> result;
+  for (auto* var : program_->AST().GlobalVariables()) {
+    auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
+    if (!global || !global->IsOverridable()) {
+      continue;
+    }
+
+    // If there are conflicting defintions for a constant id, that is invalid
+    // WGSL, so the resolver should catch it. Thus here the inspector just
+    // assumes all definitions of the constant id are the same, so only needs
+    // to find the first reference to constant id.
+    uint32_t constant_id = global->ConstantId();
+    if (result.find(constant_id) != result.end()) {
+      continue;
+    }
+
+    if (!var->constructor) {
+      result[constant_id] = Scalar();
+      continue;
+    }
+
+    auto* literal = var->constructor->As<ast::LiteralExpression>();
+    if (!literal) {
+      // This is invalid WGSL, but handling gracefully.
+      result[constant_id] = Scalar();
+      continue;
+    }
+
+    if (auto* l = literal->As<ast::BoolLiteralExpression>()) {
+      result[constant_id] = Scalar(l->value);
+      continue;
+    }
+
+    if (auto* l = literal->As<ast::UintLiteralExpression>()) {
+      result[constant_id] = Scalar(l->value);
+      continue;
+    }
+
+    if (auto* l = literal->As<ast::SintLiteralExpression>()) {
+      result[constant_id] = Scalar(l->value);
+      continue;
+    }
+
+    if (auto* l = literal->As<ast::FloatLiteralExpression>()) {
+      result[constant_id] = Scalar(l->value);
+      continue;
+    }
+
+    result[constant_id] = Scalar();
+  }
+
+  return result;
+}
+
+std::map<std::string, uint32_t> Inspector::GetConstantNameToIdMap() {
+  std::map<std::string, uint32_t> result;
+  for (auto* var : program_->AST().GlobalVariables()) {
+    auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
+    if (global && global->IsOverridable()) {
+      auto name = program_->Symbols().NameFor(var->symbol);
+      result[name] = global->ConstantId();
+    }
+  }
+  return result;
+}
+
+uint32_t Inspector::GetStorageSize(const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return 0;
+  }
+
+  size_t size = 0;
+  auto* func_sem = program_->Sem().Get(func);
+  for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
+    size += ruv.first->Type()->UnwrapRef()->Size();
+  }
+  for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
+    size += rsv.first->Type()->UnwrapRef()->Size();
+  }
+
+  if (static_cast<uint64_t>(size) >
+      static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
+    return std::numeric_limits<uint32_t>::max();
+  }
+  return static_cast<uint32_t>(size);
+}
+
+std::vector<ResourceBinding> Inspector::GetResourceBindings(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+  for (auto fn : {
+           &Inspector::GetUniformBufferResourceBindings,
+           &Inspector::GetStorageBufferResourceBindings,
+           &Inspector::GetReadOnlyStorageBufferResourceBindings,
+           &Inspector::GetSamplerResourceBindings,
+           &Inspector::GetComparisonSamplerResourceBindings,
+           &Inspector::GetSampledTextureResourceBindings,
+           &Inspector::GetMultisampledTextureResourceBindings,
+           &Inspector::GetWriteOnlyStorageTextureResourceBindings,
+           &Inspector::GetDepthTextureResourceBindings,
+           &Inspector::GetDepthMultisampledTextureResourceBindings,
+           &Inspector::GetExternalTextureResourceBindings,
+       }) {
+    AppendResourceBindings(&result, (this->*fn)(entry_point));
+  }
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetUniformBufferResourceBindings(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+
+  auto* func_sem = program_->Sem().Get(func);
+  for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
+    auto* var = ruv.first;
+    auto binding_info = ruv.second;
+
+    auto* unwrapped_type = var->Type()->UnwrapRef();
+
+    ResourceBinding entry;
+    entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+    entry.size = unwrapped_type->Size();
+    entry.size_no_padding = entry.size;
+    if (auto* str = unwrapped_type->As<sem::Struct>()) {
+      entry.size_no_padding = str->SizeNoPadding();
+    } else {
+      entry.size_no_padding = entry.size;
+    }
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings(
+    const std::string& entry_point) {
+  return GetStorageBufferResourceBindingsImpl(entry_point, false);
+}
+
+std::vector<ResourceBinding>
+Inspector::GetReadOnlyStorageBufferResourceBindings(
+    const std::string& entry_point) {
+  return GetStorageBufferResourceBindingsImpl(entry_point, true);
+}
+
+std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+
+  auto* func_sem = program_->Sem().Get(func);
+  for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) {
+    auto binding_info = rs.second;
+
+    ResourceBinding entry;
+    entry.resource_type = ResourceBinding::ResourceType::kSampler;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+
+  auto* func_sem = program_->Sem().Get(func);
+  for (auto& rcs :
+       func_sem->TransitivelyReferencedComparisonSamplerVariables()) {
+    auto binding_info = rcs.second;
+
+    ResourceBinding entry;
+    entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetSampledTextureResourceBindingsImpl(entry_point, false);
+}
+
+std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetSampledTextureResourceBindingsImpl(entry_point, true);
+}
+
+std::vector<ResourceBinding>
+Inspector::GetWriteOnlyStorageTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetStorageTextureResourceBindingsImpl(entry_point);
+}
+
+std::vector<ResourceBinding> Inspector::GetTextureResourceBindings(
+    const std::string& entry_point,
+    const tint::TypeInfo* texture_type,
+    ResourceBinding::ResourceType resource_type) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+  auto* func_sem = program_->Sem().Get(func);
+  for (auto& ref :
+       func_sem->TransitivelyReferencedVariablesOfType(texture_type)) {
+    auto* var = ref.first;
+    auto binding_info = ref.second;
+
+    ResourceBinding entry;
+    entry.resource_type = resource_type;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+
+    auto* tex = var->Type()->UnwrapRef()->As<sem::Texture>();
+    entry.dim =
+        TypeTextureDimensionToResourceBindingTextureDimension(tex->dim());
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetTextureResourceBindings(
+      entry_point, &TypeInfo::Of<sem::DepthTexture>(),
+      ResourceBinding::ResourceType::kDepthTexture);
+}
+
+std::vector<ResourceBinding>
+Inspector::GetDepthMultisampledTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetTextureResourceBindings(
+      entry_point, &TypeInfo::Of<sem::DepthMultisampledTexture>(),
+      ResourceBinding::ResourceType::kDepthMultisampledTexture);
+}
+
+std::vector<ResourceBinding> Inspector::GetExternalTextureResourceBindings(
+    const std::string& entry_point) {
+  return GetTextureResourceBindings(
+      entry_point, &TypeInfo::Of<sem::ExternalTexture>(),
+      ResourceBinding::ResourceType::kExternalTexture);
+}
+
+std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  GenerateSamplerTargets();
+
+  auto it = sampler_targets_->find(entry_point);
+  if (it == sampler_targets_->end()) {
+    return {};
+  }
+  return it->second;
+}
+
+std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses(
+    const std::string& entry_point,
+    const sem::BindingPoint& placeholder) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+  auto* func_sem = program_->Sem().Get(func);
+
+  std::vector<sem::SamplerTexturePair> new_pairs;
+  for (auto pair : func_sem->TextureSamplerPairs()) {
+    auto* texture = pair.first->As<sem::GlobalVariable>();
+    auto* sampler =
+        pair.second ? pair.second->As<sem::GlobalVariable>() : nullptr;
+    SamplerTexturePair new_pair;
+    new_pair.sampler_binding_point =
+        sampler ? sampler->BindingPoint() : placeholder;
+    new_pair.texture_binding_point = texture->BindingPoint();
+    new_pairs.push_back(new_pair);
+  }
+  return new_pairs;
+}
+
+uint32_t Inspector::GetWorkgroupStorageSize(const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return 0;
+  }
+
+  uint32_t total_size = 0;
+  auto* func_sem = program_->Sem().Get(func);
+  for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) {
+    if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
+      auto* ty = var->Type()->UnwrapRef();
+      uint32_t align = ty->Align();
+      uint32_t size = ty->Size();
+
+      // This essentially matches std430 layout rules from GLSL, which are in
+      // turn specified as an upper bound for Vulkan layout sizing. Since D3D
+      // and Metal are even less specific, we assume Vulkan behavior as a
+      // good-enough approximation everywhere.
+      total_size += utils::RoundUp(align, size);
+    }
+  }
+
+  return total_size;
+}
+
+const ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
+  auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name));
+  if (!func) {
+    diagnostics_.add_error(diag::System::Inspector, name + " was not found!");
+    return nullptr;
+  }
+
+  if (!func->IsEntryPoint()) {
+    diagnostics_.add_error(diag::System::Inspector,
+                           name + " is not an entry point!");
+    return nullptr;
+  }
+
+  return func;
+}
+
+void Inspector::AddEntryPointInOutVariables(
+    std::string name,
+    const sem::Type* type,
+    const ast::AttributeList& attributes,
+    std::vector<StageVariable>& variables) const {
+  // Skip builtins.
+  if (ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
+    return;
+  }
+
+  auto* unwrapped_type = type->UnwrapRef();
+
+  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, variables);
+    }
+    return;
+  }
+
+  // Base case: add the variable.
+
+  StageVariable stage_variable;
+  stage_variable.name = name;
+  std::tie(stage_variable.component_type, stage_variable.composition_type) =
+      CalculateComponentAndComposition(type);
+
+  auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes);
+  TINT_ASSERT(Inspector, location != nullptr);
+  stage_variable.has_location_attribute = true;
+  stage_variable.location_attribute = location->value;
+
+  std::tie(stage_variable.interpolation_type,
+           stage_variable.interpolation_sampling) =
+      CalculateInterpolationData(type, attributes);
+
+  variables.push_back(stage_variable);
+}
+
+bool Inspector::ContainsBuiltin(ast::Builtin builtin,
+                                const sem::Type* type,
+                                const ast::AttributeList& attributes) const {
+  auto* unwrapped_type = type->UnwrapRef();
+
+  if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
+    // Recurse into members.
+    for (auto* member : struct_ty->Members()) {
+      if (ContainsBuiltin(builtin, member->Type(),
+                          member->Declaration()->attributes)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Base case: check for builtin
+  auto* builtin_declaration =
+      ast::GetAttribute<ast::BuiltinAttribute>(attributes);
+  if (!builtin_declaration || builtin_declaration->builtin != builtin) {
+    return false;
+  }
+
+  return true;
+}
+
+std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl(
+    const std::string& entry_point,
+    bool read_only) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  auto* func_sem = program_->Sem().Get(func);
+  std::vector<ResourceBinding> result;
+  for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
+    auto* var = rsv.first;
+    auto binding_info = rsv.second;
+
+    if (read_only != (var->Access() == ast::Access::kRead)) {
+      continue;
+    }
+
+    auto* unwrapped_type = var->Type()->UnwrapRef();
+
+    ResourceBinding entry;
+    entry.resource_type =
+        read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer
+                  : ResourceBinding::ResourceType::kStorageBuffer;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+    entry.size = unwrapped_type->Size();
+    if (auto* str = unwrapped_type->As<sem::Struct>()) {
+      entry.size_no_padding = str->SizeNoPadding();
+    } else {
+      entry.size_no_padding = entry.size;
+    }
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl(
+    const std::string& entry_point,
+    bool multisampled_only) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+  auto* func_sem = program_->Sem().Get(func);
+  auto referenced_variables =
+      multisampled_only
+          ? func_sem->TransitivelyReferencedMultisampledTextureVariables()
+          : func_sem->TransitivelyReferencedSampledTextureVariables();
+  for (auto& ref : referenced_variables) {
+    auto* var = ref.first;
+    auto binding_info = ref.second;
+
+    ResourceBinding entry;
+    entry.resource_type =
+        multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture
+                          : ResourceBinding::ResourceType::kSampledTexture;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+
+    auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>();
+    entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
+        texture_type->dim());
+
+    const sem::Type* base_type = nullptr;
+    if (multisampled_only) {
+      base_type = texture_type->As<sem::MultisampledTexture>()->type();
+    } else {
+      base_type = texture_type->As<sem::SampledTexture>()->type();
+    }
+    entry.sampled_kind = BaseTypeToSampledKind(base_type);
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  auto* func_sem = program_->Sem().Get(func);
+  std::vector<ResourceBinding> result;
+  for (auto& ref :
+       func_sem->TransitivelyReferencedVariablesOfType<sem::StorageTexture>()) {
+    auto* var = ref.first;
+    auto binding_info = ref.second;
+
+    auto* texture_type = var->Type()->UnwrapRef()->As<sem::StorageTexture>();
+
+    ResourceBinding entry;
+    entry.resource_type =
+        ResourceBinding::ResourceType::kWriteOnlyStorageTexture;
+    entry.bind_group = binding_info.group->value;
+    entry.binding = binding_info.binding->value;
+
+    entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
+        texture_type->dim());
+
+    auto* base_type = texture_type->type();
+    entry.sampled_kind = BaseTypeToSampledKind(base_type);
+    entry.image_format = TypeTexelFormatToResourceBindingTexelFormat(
+        texture_type->texel_format());
+
+    result.push_back(entry);
+  }
+
+  return result;
+}
+
+void Inspector::GenerateSamplerTargets() {
+  // Do not re-generate, since |program_| should not change during the lifetime
+  // of the inspector.
+  if (sampler_targets_ != nullptr) {
+    return;
+  }
+
+  sampler_targets_ = std::make_unique<std::unordered_map<
+      std::string, utils::UniqueVector<sem::SamplerTexturePair>>>();
+
+  auto& sem = program_->Sem();
+
+  for (auto* node : program_->ASTNodes().Objects()) {
+    auto* c = node->As<ast::CallExpression>();
+    if (!c) {
+      continue;
+    }
+
+    auto* call = sem.Get(c);
+    if (!call) {
+      continue;
+    }
+
+    auto* i = call->Target()->As<sem::Builtin>();
+    if (!i) {
+      continue;
+    }
+
+    const auto& signature = i->Signature();
+    int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
+    if (sampler_index == -1) {
+      continue;
+    }
+
+    int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
+    if (texture_index == -1) {
+      continue;
+    }
+
+    auto* call_func = call->Stmt()->Function();
+    std::vector<const sem::Function*> entry_points;
+    if (call_func->Declaration()->IsEntryPoint()) {
+      entry_points = {call_func};
+    } else {
+      entry_points = call_func->AncestorEntryPoints();
+    }
+
+    if (entry_points.empty()) {
+      continue;
+    }
+
+    auto* t = c->args[texture_index];
+    auto* s = c->args[sampler_index];
+
+    GetOriginatingResources(
+        std::array<const ast::Expression*, 2>{t, s},
+        [&](std::array<const sem::GlobalVariable*, 2> globals) {
+          auto* texture = globals[0];
+          sem::BindingPoint texture_binding_point = {
+              texture->Declaration()->BindingPoint().group->value,
+              texture->Declaration()->BindingPoint().binding->value};
+
+          auto* sampler = globals[1];
+          sem::BindingPoint sampler_binding_point = {
+              sampler->Declaration()->BindingPoint().group->value,
+              sampler->Declaration()->BindingPoint().binding->value};
+
+          for (auto* entry_point : entry_points) {
+            const auto& ep_name =
+                program_->Symbols().NameFor(entry_point->Declaration()->symbol);
+            (*sampler_targets_)[ep_name].add(
+                {sampler_binding_point, texture_binding_point});
+          }
+        });
+  }
+}
+
+template <size_t N, typename F>
+void Inspector::GetOriginatingResources(
+    std::array<const ast::Expression*, N> exprs,
+    F&& callback) {
+  if (!program_->IsValid()) {
+    TINT_ICE(Inspector, diagnostics_)
+        << "attempting to get originating resources in invalid program";
+    return;
+  }
+
+  auto& sem = program_->Sem();
+
+  std::array<const sem::GlobalVariable*, N> globals{};
+  std::array<const sem::Parameter*, N> parameters{};
+  utils::UniqueVector<const ast::CallExpression*> callsites;
+
+  for (size_t i = 0; i < N; i++) {
+    auto*& expr = exprs[i];
+    // Resolve each of the expressions
+    while (true) {
+      if (auto* user = sem.Get<sem::VariableUser>(expr)) {
+        auto* var = user->Variable();
+
+        if (auto* global = tint::As<sem::GlobalVariable>(var)) {
+          // Found the global resource declaration.
+          globals[i] = global;
+          break;  // Done with this expression.
+        }
+
+        if (auto* local = tint::As<sem::LocalVariable>(var)) {
+          // Chase the variable
+          expr = local->Declaration()->constructor;
+          if (!expr) {
+            TINT_ICE(Inspector, diagnostics_)
+                << "resource variable had no initializer";
+            return;
+          }
+          continue;  // Continue chasing the expression in this function
+        }
+
+        if (auto* param = tint::As<sem::Parameter>(var)) {
+          // Gather each of the callers of this function
+          auto* func = tint::As<sem::Function>(param->Owner());
+          if (func->CallSites().empty()) {
+            // One or more of the expressions is a parameter, but this function
+            // is not called. Ignore.
+            return;
+          }
+          for (auto* call : func->CallSites()) {
+            callsites.add(call->Declaration());
+          }
+          // Need to evaluate each function call with the group of
+          // expressions, so move on to the next expression.
+          parameters[i] = param;
+          break;
+        }
+
+        TINT_ICE(Inspector, diagnostics_)
+            << "unexpected variable type " << var->TypeInfo().name;
+      }
+
+      if (auto* unary = tint::As<ast::UnaryOpExpression>(expr)) {
+        switch (unary->op) {
+          case ast::UnaryOp::kAddressOf:
+          case ast::UnaryOp::kIndirection:
+            // `*` and `&` are the only valid unary ops for a resource type,
+            // and must be balanced in order for the program to have passed
+            // validation. Just skip past these.
+            expr = unary->expr;
+            continue;
+          default: {
+            TINT_ICE(Inspector, diagnostics_)
+                << "unexpected unary op on resource: " << unary->op;
+            return;
+          }
+        }
+      }
+
+      TINT_ICE(Inspector, diagnostics_)
+          << "cannot resolve originating resource with expression type "
+          << expr->TypeInfo().name;
+      return;
+    }
+  }
+
+  if (callsites.size()) {
+    for (auto* call_expr : callsites) {
+      // Make a copy of the expressions for this callsite
+      std::array<const ast::Expression*, N> call_exprs = exprs;
+      // Patch all the parameter expressions with their argument
+      for (size_t i = 0; i < N; i++) {
+        if (auto* param = parameters[i]) {
+          call_exprs[i] = call_expr->args[param->Index()];
+        }
+      }
+      // Now call GetOriginatingResources() with from the callsite
+      GetOriginatingResources(call_exprs, callback);
+    }
+  } else {
+    // All the expressions resolved to globals
+    callback(globals);
+  }
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h
new file mode 100644
index 0000000..1fa2d00
--- /dev/null
+++ b/src/tint/inspector/inspector.h
@@ -0,0 +1,239 @@
+// Copyright 2020 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_INSPECTOR_INSPECTOR_H_
+#define SRC_TINT_INSPECTOR_INSPECTOR_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#include "src/tint/inspector/entry_point.h"
+#include "src/tint/inspector/resource_binding.h"
+#include "src/tint/inspector/scalar.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/sampler_texture_pair.h"
+#include "src/tint/utils/unique_vector.h"
+
+namespace tint {
+namespace inspector {
+
+/// A temporary alias to sem::SamplerTexturePair. [DEPRECATED]
+using SamplerTexturePair = sem::SamplerTexturePair;
+
+/// Extracts information from a program
+class Inspector {
+ public:
+  /// Constructor
+  /// @param program Shader program to extract information from.
+  explicit Inspector(const Program* program);
+
+  /// Destructor
+  ~Inspector();
+
+  /// @returns error messages from the Inspector
+  std::string error() { return diagnostics_.str(); }
+  /// @returns true if an error was encountered
+  bool has_error() const { return diagnostics_.contains_errors(); }
+
+  /// @returns vector of entry point information
+  std::vector<EntryPoint> GetEntryPoints();
+
+  /// @param entry_point name of the entry point to get the remapped version of
+  /// @returns the remapped name of the entry point, or the empty string if it
+  ///          isn't a known entry point.
+  std::string GetRemappedNameForEntryPoint(const std::string& entry_point);
+
+  /// @returns map of const_id to initial value
+  std::map<uint32_t, Scalar> GetConstantIDs();
+
+  /// @returns map of module-constant name to pipeline constant ID
+  std::map<std::string, uint32_t> GetConstantNameToIdMap();
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns the total size of shared storage required by an entry point,
+  ///          including all uniform storage buffers.
+  uint32_t GetStorageSize(const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the resource bindings.
+  std::vector<ResourceBinding> GetResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for uniform buffers.
+  std::vector<ResourceBinding> GetUniformBufferResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for storage buffers.
+  std::vector<ResourceBinding> GetStorageBufferResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for read-only storage buffers.
+  std::vector<ResourceBinding> GetReadOnlyStorageBufferResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for regular samplers.
+  std::vector<ResourceBinding> GetSamplerResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for comparison samplers.
+  std::vector<ResourceBinding> GetComparisonSamplerResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for sampled textures.
+  std::vector<ResourceBinding> GetSampledTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for multisampled textures.
+  std::vector<ResourceBinding> GetMultisampledTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for write-only storage textures.
+  std::vector<ResourceBinding> GetWriteOnlyStorageTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for depth textures.
+  std::vector<ResourceBinding> GetDepthTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for depth textures.
+  std::vector<ResourceBinding> GetDepthMultisampledTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for external textures.
+  std::vector<ResourceBinding> GetExternalTextureResourceBindings(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the sampler/texture sampling pairs that are used
+  /// by that entry point.
+  std::vector<sem::SamplerTexturePair> GetSamplerTextureUses(
+      const std::string& entry_point);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @param placeholder the sampler binding point to use for texture-only
+  /// access (e.g., textureLoad)
+  /// @returns vector of all of the sampler/texture sampling pairs that are used
+  /// by that entry point.
+  std::vector<sem::SamplerTexturePair> GetSamplerTextureUses(
+      const std::string& entry_point,
+      const sem::BindingPoint& placeholder);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns the total size in bytes of all Workgroup storage-class storage
+  /// referenced transitively by the entry point.
+  uint32_t GetWorkgroupStorageSize(const std::string& entry_point);
+
+ private:
+  const Program* program_;
+  diag::List diagnostics_;
+  std::unique_ptr<
+      std::unordered_map<std::string,
+                         utils::UniqueVector<sem::SamplerTexturePair>>>
+      sampler_targets_;
+
+  /// @param name name of the entry point to find
+  /// @returns a pointer to the entry point if it exists, otherwise returns
+  ///          nullptr and sets the error string.
+  const ast::Function* FindEntryPointByName(const std::string& name);
+
+  /// Recursively add entry point IO variables.
+  /// If `type` is a struct, recurse into members, appending the member name.
+  /// Otherwise, add the variable unless it is a builtin.
+  /// @param name the name of the variable being added
+  /// @param type the type of the variable
+  /// @param attributes the variable attributes
+  /// @param variables the list to add the variables to
+  void AddEntryPointInOutVariables(std::string name,
+                                   const sem::Type* type,
+                                   const ast::AttributeList& attributes,
+                                   std::vector<StageVariable>& variables) const;
+
+  /// Recursively determine if the type contains builtin.
+  /// If `type` is a struct, recurse into members to check for the attribute.
+  /// Otherwise, check `attributes` for the attribute.
+  bool ContainsBuiltin(ast::Builtin builtin,
+                       const sem::Type* type,
+                       const ast::AttributeList& attributes) const;
+
+  /// Gathers all the texture resource bindings of the given type for the given
+  /// entry point.
+  /// @param entry_point name of the entry point to get information about.
+  /// @param texture_type the type of the textures to gather.
+  /// @param resource_type the ResourceBinding::ResourceType for the given
+  /// texture type.
+  /// @returns vector of all of the bindings for depth textures.
+  std::vector<ResourceBinding> GetTextureResourceBindings(
+      const std::string& entry_point,
+      const tint::TypeInfo* texture_type,
+      ResourceBinding::ResourceType resource_type);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @param read_only if true get only read-only bindings, if false get
+  ///                  write-only bindings.
+  /// @returns vector of all of the bindings for the requested storage buffers.
+  std::vector<ResourceBinding> GetStorageBufferResourceBindingsImpl(
+      const std::string& entry_point,
+      bool read_only);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @param multisampled_only only get multisampled textures if true, otherwise
+  ///                          only get sampled textures.
+  /// @returns vector of all of the bindings for the request storage buffers.
+  std::vector<ResourceBinding> GetSampledTextureResourceBindingsImpl(
+      const std::string& entry_point,
+      bool multisampled_only);
+
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for the requested storage textures.
+  std::vector<ResourceBinding> GetStorageTextureResourceBindingsImpl(
+      const std::string& entry_point);
+
+  /// Constructs |sampler_targets_| if it hasn't already been instantiated.
+  void GenerateSamplerTargets();
+
+  /// For a N-uple of expressions, resolve to the appropriate global resources
+  /// and call 'cb'.
+  /// 'cb' may be called multiple times.
+  /// Assumes that not being able to resolve the resources is an error, so will
+  /// invoke TINT_ICE when that occurs.
+  /// @tparam N number of expressions in the n-uple
+  /// @tparam F type of the callback provided.
+  /// @param exprs N-uple of expressions to resolve.
+  /// @param cb is a callback function with the signature:
+  /// `void(std::array<const sem::GlobalVariable*, N>)`, which is invoked
+  /// whenever a set of expressions are resolved to globals.
+  template <size_t N, typename F>
+  void GetOriginatingResources(std::array<const ast::Expression*, N> exprs,
+                               F&& cb);
+};
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_INSPECTOR_H_
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
new file mode 100644
index 0000000..36d54b9
--- /dev/null
+++ b/src/tint/inspector/inspector_test.cc
@@ -0,0 +1,3106 @@
+// Copyright 2020 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 "gtest/gtest.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/inspector/test_inspector_builder.h"
+#include "src/tint/inspector/test_inspector_runner.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/variable.h"
+#include "tint/tint.h"
+
+namespace tint {
+namespace inspector {
+namespace {
+
+// All the tests that descend from InspectorBuilder are expected to define their
+// test state via building up the AST through InspectorBuilder and then generate
+// the program with ::Build.
+// The returned Inspector from ::Build can then be used to test expecations.
+//
+// All the tests that descend from InspectorRunner are expected to define their
+// test state via a WGSL shader, which will be parsed to generate a Program and
+// Inspector in ::Initialize.
+// The returned Inspector from ::Initialize can then be used to test
+// expecations.
+
+class InspectorGetEntryPointTest : public InspectorBuilder,
+                                   public testing::Test {};
+
+typedef std::tuple<inspector::ComponentType, inspector::CompositionType>
+    InspectorGetEntryPointComponentAndCompositionTestParams;
+class InspectorGetEntryPointComponentAndCompositionTest
+    : public InspectorBuilder,
+      public testing::TestWithParam<
+          InspectorGetEntryPointComponentAndCompositionTestParams> {};
+struct InspectorGetEntryPointInterpolateTestParams {
+  ast::InterpolationType in_type;
+  ast::InterpolationSampling in_sampling;
+  inspector::InterpolationType out_type;
+  inspector::InterpolationSampling out_sampling;
+};
+class InspectorGetEntryPointInterpolateTest
+    : public InspectorBuilder,
+      public testing::TestWithParam<
+          InspectorGetEntryPointInterpolateTestParams> {};
+class InspectorGetRemappedNameForEntryPointTest : public InspectorBuilder,
+                                                  public testing::Test {};
+class InspectorGetConstantIDsTest : public InspectorBuilder,
+                                    public testing::Test {};
+class InspectorGetConstantNameToIdMapTest : public InspectorBuilder,
+                                            public testing::Test {};
+class InspectorGetStorageSizeTest : public InspectorBuilder,
+                                    public testing::Test {};
+class InspectorGetResourceBindingsTest : public InspectorBuilder,
+                                         public testing::Test {};
+class InspectorGetUniformBufferResourceBindingsTest : public InspectorBuilder,
+                                                      public testing::Test {};
+class InspectorGetStorageBufferResourceBindingsTest : public InspectorBuilder,
+                                                      public testing::Test {};
+class InspectorGetReadOnlyStorageBufferResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+class InspectorGetSamplerResourceBindingsTest : public InspectorBuilder,
+                                                public testing::Test {};
+class InspectorGetComparisonSamplerResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+class InspectorGetSampledTextureResourceBindingsTest : public InspectorBuilder,
+                                                       public testing::Test {};
+class InspectorGetSampledArrayTextureResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+struct GetSampledTextureTestParams {
+  ast::TextureDimension type_dim;
+  inspector::ResourceBinding::TextureDimension inspector_dim;
+  inspector::ResourceBinding::SampledKind sampled_kind;
+};
+class InspectorGetSampledTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetSampledTextureTestParams> {};
+class InspectorGetSampledArrayTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetSampledTextureTestParams> {};
+class InspectorGetMultisampledTextureResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+class InspectorGetMultisampledArrayTextureResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+typedef GetSampledTextureTestParams GetMultisampledTextureTestParams;
+class InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetMultisampledTextureTestParams> {};
+class InspectorGetMultisampledTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetMultisampledTextureTestParams> {};
+class InspectorGetStorageTextureResourceBindingsTest : public InspectorBuilder,
+                                                       public testing::Test {};
+struct GetDepthTextureTestParams {
+  ast::TextureDimension type_dim;
+  inspector::ResourceBinding::TextureDimension inspector_dim;
+};
+class InspectorGetDepthTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetDepthTextureTestParams> {};
+
+class InspectorGetDepthMultisampledTextureResourceBindingsTest
+    : public InspectorBuilder,
+      public testing::Test {};
+
+typedef std::tuple<ast::TextureDimension, ResourceBinding::TextureDimension>
+    DimensionParams;
+typedef std::tuple<ast::TexelFormat,
+                   ResourceBinding::TexelFormat,
+                   ResourceBinding::SampledKind>
+    TexelFormatParams;
+typedef std::tuple<DimensionParams, TexelFormatParams>
+    GetStorageTextureTestParams;
+class InspectorGetStorageTextureResourceBindingsTestWithParam
+    : public InspectorBuilder,
+      public testing::TestWithParam<GetStorageTextureTestParams> {};
+
+class InspectorGetExternalTextureResourceBindingsTest : public InspectorBuilder,
+                                                        public testing::Test {};
+
+class InspectorGetSamplerTextureUsesTest : public InspectorRunner,
+                                           public testing::Test {};
+
+class InspectorGetWorkgroupStorageSizeTest : public InspectorBuilder,
+                                             public testing::Test {};
+
+// This is a catch all for shaders that have demonstrated regressions/crashes in
+// the wild.
+class InspectorRegressionTest : public InspectorRunner, public testing::Test {};
+
+TEST_F(InspectorGetEntryPointTest, NoFunctions) {
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, NoEntryPoints) {
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kFragment),
+                               });
+
+  // TODO(dsinclair): Update to run the namer transform when available.
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ("foo", result[0].name);
+  EXPECT_EQ("foo", result[0].remapped_name);
+  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
+}
+
+TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kFragment),
+                               });
+
+  MakeEmptyBodyFunction("bar",
+                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                           WorkgroupSize(1)});
+
+  // TODO(dsinclair): Update to run the namer transform when available.
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("foo", result[0].name);
+  EXPECT_EQ("foo", result[0].remapped_name);
+  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
+  EXPECT_EQ("bar", result[1].name);
+  EXPECT_EQ("bar", result[1].remapped_name);
+  EXPECT_EQ(ast::PipelineStage::kCompute, result[1].stage);
+}
+
+TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) {
+  MakeEmptyBodyFunction("func", {});
+
+  MakeCallerBodyFunction("foo", {"func"},
+                         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                            WorkgroupSize(1)});
+
+  MakeCallerBodyFunction("bar", {"func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  // TODO(dsinclair): Update to run the namer transform when available.
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  EXPECT_FALSE(inspector.has_error());
+
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("foo", result[0].name);
+  EXPECT_EQ("foo", result[0].remapped_name);
+  EXPECT_EQ(ast::PipelineStage::kCompute, result[0].stage);
+  EXPECT_EQ("bar", result[1].name);
+  EXPECT_EQ("bar", result[1].remapped_name);
+  EXPECT_EQ(ast::PipelineStage::kFragment, result[1].stage);
+}
+
+TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) {
+  MakeEmptyBodyFunction("foo",
+                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                           WorkgroupSize(8, 2, 1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+  uint32_t x, y, z;
+  std::tie(x, y, z) = result[0].workgroup_size();
+  EXPECT_EQ(8u, x);
+  EXPECT_EQ(2u, y);
+  EXPECT_EQ(1u, z);
+}
+
+TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) {
+  MakeEmptyBodyFunction(
+      "foo", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(8, 2, 1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+  uint32_t x, y, z;
+  std::tie(x, y, z) = result[0].workgroup_size();
+  EXPECT_EQ(8u, x);
+  EXPECT_EQ(2u, y);
+  EXPECT_EQ(1u, z);
+}
+
+TEST_F(InspectorGetEntryPointTest, NoInOutVariables) {
+  MakeEmptyBodyFunction("func", {});
+
+  MakeCallerBodyFunction("foo", {"func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].input_variables.size());
+  EXPECT_EQ(0u, result[0].output_variables.size());
+}
+
+TEST_P(InspectorGetEntryPointComponentAndCompositionTest, Test) {
+  ComponentType component;
+  CompositionType composition;
+  std::tie(component, composition) = GetParam();
+  std::function<const ast::Type*()> tint_type =
+      GetTypeFunction(component, composition);
+
+  auto* in_var = Param("in_var", tint_type(), {Location(0u), Flat()});
+  Func("foo", {in_var}, tint_type(), {Return("in_var")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  ASSERT_EQ(1u, result[0].input_variables.size());
+  EXPECT_EQ("in_var", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(component, result[0].input_variables[0].component_type);
+
+  ASSERT_EQ(1u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(component, result[0].output_variables[0].component_type);
+}
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetEntryPointTest,
+    InspectorGetEntryPointComponentAndCompositionTest,
+    testing::Combine(testing::Values(ComponentType::kFloat,
+                                     ComponentType::kSInt,
+                                     ComponentType::kUInt),
+                     testing::Values(CompositionType::kScalar,
+                                     CompositionType::kVec2,
+                                     CompositionType::kVec3,
+                                     CompositionType::kVec4)));
+
+TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) {
+  auto* in_var0 = Param("in_var0", ty.u32(), {Location(0u), Flat()});
+  auto* in_var1 = Param("in_var1", ty.u32(), {Location(1u), Flat()});
+  auto* in_var4 = Param("in_var4", ty.u32(), {Location(4u), Flat()});
+  Func("foo", {in_var0, in_var1, in_var4}, ty.u32(), {Return("in_var0")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  ASSERT_EQ(3u, result[0].input_variables.size());
+  EXPECT_EQ("in_var0", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(InterpolationType::kFlat,
+            result[0].input_variables[0].interpolation_type);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+  EXPECT_EQ("in_var1", result[0].input_variables[1].name);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+  EXPECT_EQ(InterpolationType::kFlat,
+            result[0].input_variables[1].interpolation_type);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+  EXPECT_EQ("in_var4", result[0].input_variables[2].name);
+  EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
+  EXPECT_EQ(4u, result[0].input_variables[2].location_attribute);
+  EXPECT_EQ(InterpolationType::kFlat,
+            result[0].input_variables[2].interpolation_type);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
+
+  ASSERT_EQ(1u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+}
+
+TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
+  auto* in_var_foo = Param("in_var_foo", ty.u32(), {Location(0u), Flat()});
+  Func("foo", {in_var_foo}, ty.u32(), {Return("in_var_foo")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
+
+  auto* in_var_bar = Param("in_var_bar", ty.u32(), {Location(0u), Flat()});
+  Func("bar", {in_var_bar}, ty.u32(), {Return("in_var_bar")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(1u)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(2u, result.size());
+
+  ASSERT_EQ(1u, result[0].input_variables.size());
+  EXPECT_EQ("in_var_foo", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(InterpolationType::kFlat,
+            result[0].input_variables[0].interpolation_type);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+
+  ASSERT_EQ(1u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+
+  ASSERT_EQ(1u, result[1].input_variables.size());
+  EXPECT_EQ("in_var_bar", result[1].input_variables[0].name);
+  EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
+  EXPECT_EQ(InterpolationType::kFlat,
+            result[1].input_variables[0].interpolation_type);
+  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
+
+  ASSERT_EQ(1u, result[1].output_variables.size());
+  EXPECT_EQ("<retval>", result[1].output_variables[0].name);
+  EXPECT_TRUE(result[1].output_variables[0].has_location_attribute);
+  EXPECT_EQ(1u, result[1].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[1].output_variables[0].component_type);
+}
+
+TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
+  auto* in_var0 =
+      Param("in_var0", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+  auto* in_var1 = Param("in_var1", ty.f32(), {Location(0u)});
+  Func("foo", {in_var0, in_var1}, ty.f32(), {Return("in_var1")},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kFragDepth)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  ASSERT_EQ(1u, result[0].input_variables.size());
+  EXPECT_EQ("in_var1", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[0].component_type);
+
+  ASSERT_EQ(0u, result[0].output_variables.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, InOutStruct) {
+  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
+  Func("foo", {Param("param", ty.Of(interface))}, ty.Of(interface),
+       {Return("param")}, {Stage(ast::PipelineStage::kFragment)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  ASSERT_EQ(2u, result[0].input_variables.size());
+  EXPECT_EQ("param.a", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+  EXPECT_EQ("param.b", result[0].input_variables[1].name);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+
+  ASSERT_EQ(2u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
+  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+}
+
+TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
+  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
+  Func("foo", {}, ty.Of(interface), {Return(Construct(ty.Of(interface)))},
+       {Stage(ast::PipelineStage::kFragment)});
+  Func("bar", {Param("param", ty.Of(interface))}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(2u, result.size());
+
+  ASSERT_EQ(0u, result[0].input_variables.size());
+
+  ASSERT_EQ(2u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
+  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+
+  ASSERT_EQ(2u, result[1].input_variables.size());
+  EXPECT_EQ("param.a", result[1].input_variables[0].name);
+  EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
+  EXPECT_EQ("param.b", result[1].input_variables[1].name);
+  EXPECT_TRUE(result[1].input_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[1].input_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[1].component_type);
+
+  ASSERT_EQ(0u, result[1].output_variables.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) {
+  auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
+  auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
+  Func("foo",
+       {Param("param_a", ty.Of(struct_a)), Param("param_b", ty.Of(struct_b)),
+        Param("param_c", ty.f32(), {Location(3u)}),
+        Param("param_d", ty.f32(), {Location(4u)})},
+       ty.Of(struct_a), {Return("param_a")},
+       {Stage(ast::PipelineStage::kFragment)});
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  ASSERT_EQ(5u, result[0].input_variables.size());
+  EXPECT_EQ("param_a.a", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+  EXPECT_EQ("param_a.b", result[0].input_variables[1].name);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+  EXPECT_EQ("param_b.a", result[0].input_variables[2].name);
+  EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
+  EXPECT_EQ(2u, result[0].input_variables[2].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
+  EXPECT_EQ("param_c", result[0].input_variables[3].name);
+  EXPECT_TRUE(result[0].input_variables[3].has_location_attribute);
+  EXPECT_EQ(3u, result[0].input_variables[3].location_attribute);
+  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[3].component_type);
+  EXPECT_EQ("param_d", result[0].input_variables[4].name);
+  EXPECT_TRUE(result[0].input_variables[4].has_location_attribute);
+  EXPECT_EQ(4u, result[0].input_variables[4].location_attribute);
+  EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[4].component_type);
+
+  ASSERT_EQ(2u, result[0].output_variables.size());
+  EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
+  EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+  EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
+  EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
+  EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+  EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantUnreferenced) {
+  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
+  MakeEmptyBodyFunction(
+      "ep_func", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].overridable_constants.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByEntryPoint) {
+  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
+  MakePlainGlobalReferenceBodyFunction(
+      "ep_func", "foo", ty.f32(),
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByCallee) {
+  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
+  MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
+  MakeCallerBodyFunction(
+      "ep_func", {"callee_func"},
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantSomeReferenced) {
+  AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr);
+  AddOverridableConstantWithID("bar", 2, ty.f32(), nullptr);
+  MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
+  MakeCallerBodyFunction(
+      "ep_func", {"callee_func"},
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
+  EXPECT_EQ(1, result[0].overridable_constants[0].numeric_id);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantTypes) {
+  AddOverridableConstantWithoutID("bool_var", ty.bool_(), nullptr);
+  AddOverridableConstantWithoutID("float_var", ty.f32(), nullptr);
+  AddOverridableConstantWithoutID("u32_var", ty.u32(), nullptr);
+  AddOverridableConstantWithoutID("i32_var", ty.i32(), nullptr);
+
+  MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), {});
+  MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), {});
+  MakePlainGlobalReferenceBodyFunction("u32_func", "u32_var", ty.u32(), {});
+  MakePlainGlobalReferenceBodyFunction("i32_func", "i32_var", ty.i32(), {});
+
+  MakeCallerBodyFunction(
+      "ep_func", {"bool_func", "float_func", "u32_func", "i32_func"},
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(4u, result[0].overridable_constants.size());
+  EXPECT_EQ("bool_var", result[0].overridable_constants[0].name);
+  EXPECT_EQ(inspector::OverridableConstant::Type::kBool,
+            result[0].overridable_constants[0].type);
+  EXPECT_EQ("float_var", result[0].overridable_constants[1].name);
+  EXPECT_EQ(inspector::OverridableConstant::Type::kFloat32,
+            result[0].overridable_constants[1].type);
+  EXPECT_EQ("u32_var", result[0].overridable_constants[2].name);
+  EXPECT_EQ(inspector::OverridableConstant::Type::kUint32,
+            result[0].overridable_constants[2].type);
+  EXPECT_EQ("i32_var", result[0].overridable_constants[3].name);
+  EXPECT_EQ(inspector::OverridableConstant::Type::kInt32,
+            result[0].overridable_constants[3].type);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantInitialized) {
+  AddOverridableConstantWithoutID("foo", ty.f32(), Expr(0.0f));
+  MakePlainGlobalReferenceBodyFunction(
+      "ep_func", "foo", ty.f32(),
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
+  EXPECT_TRUE(result[0].overridable_constants[0].is_initialized);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantUninitialized) {
+  AddOverridableConstantWithoutID("foo", ty.f32(), nullptr);
+  MakePlainGlobalReferenceBodyFunction(
+      "ep_func", "foo", ty.f32(),
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo", result[0].overridable_constants[0].name);
+
+  EXPECT_FALSE(result[0].overridable_constants[0].is_initialized);
+}
+
+TEST_F(InspectorGetEntryPointTest, OverridableConstantNumericIDSpecified) {
+  AddOverridableConstantWithoutID("foo_no_id", ty.f32(), nullptr);
+  AddOverridableConstantWithID("foo_id", 1234, ty.f32(), nullptr);
+
+  MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), {});
+  MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), {});
+
+  MakeCallerBodyFunction(
+      "ep_func", {"no_id_func", "id_func"},
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(2u, result[0].overridable_constants.size());
+  EXPECT_EQ("foo_no_id", result[0].overridable_constants[0].name);
+  EXPECT_EQ("foo_id", result[0].overridable_constants[1].name);
+  EXPECT_EQ(1234, result[0].overridable_constants[1].numeric_id);
+
+  EXPECT_FALSE(result[0].overridable_constants[0].is_numeric_id_specified);
+  EXPECT_TRUE(result[0].overridable_constants[1].is_numeric_id_specified);
+}
+
+TEST_F(InspectorGetEntryPointTest, NonOverridableConstantSkipped) {
+  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         {Stage(ast::PipelineStage::kFragment)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].overridable_constants.size());
+}
+
+TEST_F(InspectorGetEntryPointTest, BuiltinNotReferenced) {
+  MakeEmptyBodyFunction("ep_func", {Stage(ast::PipelineStage::kFragment)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_FALSE(result[0].input_sample_mask_used);
+  EXPECT_FALSE(result[0].output_sample_mask_used);
+  EXPECT_FALSE(result[0].input_position_used);
+  EXPECT_FALSE(result[0].front_facing_used);
+  EXPECT_FALSE(result[0].sample_index_used);
+  EXPECT_FALSE(result[0].num_workgroups_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].input_sample_mask_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(
+      Member("inner_position", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].input_sample_mask_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
+  Func("ep_func", {in_var}, ty.u32(), {Return("in_var")},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kSampleMask)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].output_sample_mask_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(Member("inner_sample_mask", ty.u32(),
+                           {Builtin(ast::Builtin::kSampleMask)}));
+  Structure("out_struct", members, {});
+
+  Func("ep_func", {}, ty.type_name("out_struct"),
+       {Decl(Var("out_var", ty.type_name("out_struct"))), Return("out_var")},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].output_sample_mask_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, InputPositionSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].input_position_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, InputPositionStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(Member("inner_position", ty.vec4<f32>(),
+                           {Builtin(ast::Builtin::kPosition)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].input_position_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)});
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].front_facing_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(Member("inner_position", ty.bool_(),
+                           {Builtin(ast::Builtin::kFrontFacing)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].front_facing_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].sample_index_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(Member("inner_position", ty.u32(),
+                           {Builtin(ast::Builtin::kSampleIndex)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].sample_index_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) {
+  auto* in_var =
+      Param("in_var", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)});
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].num_workgroups_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) {
+  ast::StructMemberList members;
+  members.push_back(Member("inner_position", ty.vec3<u32>(),
+                           {Builtin(ast::Builtin::kNumWorkgroups)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(result[0].num_workgroups_used);
+}
+
+TEST_F(InspectorGetEntryPointTest, ImplicitInterpolate) {
+  ast::StructMemberList members;
+  members.push_back(Member("struct_inner", ty.f32(), {Location(0)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].input_variables.size());
+  EXPECT_EQ(InterpolationType::kPerspective,
+            result[0].input_variables[0].interpolation_type);
+  EXPECT_EQ(InterpolationSampling::kCenter,
+            result[0].input_variables[0].interpolation_sampling);
+}
+
+TEST_P(InspectorGetEntryPointInterpolateTest, Test) {
+  auto& params = GetParam();
+  ast::StructMemberList members;
+  members.push_back(
+      Member("struct_inner", ty.f32(),
+             {Interpolate(params.in_type, params.in_sampling), Location(0)}));
+  Structure("in_struct", members, {});
+  auto* in_var = Param("in_var", ty.type_name("in_struct"), {});
+
+  Func("ep_func", {in_var}, ty.void_(), {Return()},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+
+  ASSERT_EQ(1u, result.size());
+  ASSERT_EQ(1u, result[0].input_variables.size());
+  EXPECT_EQ(params.out_type, result[0].input_variables[0].interpolation_type);
+  EXPECT_EQ(params.out_sampling,
+            result[0].input_variables[0].interpolation_sampling);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetEntryPointTest,
+    InspectorGetEntryPointInterpolateTest,
+    testing::Values(
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kPerspective,
+            ast::InterpolationSampling::kCenter,
+            InterpolationType::kPerspective, InterpolationSampling::kCenter},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kPerspective,
+            ast::InterpolationSampling::kCentroid,
+            InterpolationType::kPerspective, InterpolationSampling::kCentroid},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kPerspective,
+            ast::InterpolationSampling::kSample,
+            InterpolationType::kPerspective, InterpolationSampling::kSample},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kPerspective,
+            ast::InterpolationSampling::kNone, InterpolationType::kPerspective,
+            InterpolationSampling::kCenter},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kLinear,
+            ast::InterpolationSampling::kCenter, InterpolationType::kLinear,
+            InterpolationSampling::kCenter},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kLinear,
+            ast::InterpolationSampling::kCentroid, InterpolationType::kLinear,
+            InterpolationSampling::kCentroid},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kLinear,
+            ast::InterpolationSampling::kSample, InterpolationType::kLinear,
+            InterpolationSampling::kSample},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kLinear, ast::InterpolationSampling::kNone,
+            InterpolationType::kLinear, InterpolationSampling::kCenter},
+        InspectorGetEntryPointInterpolateTestParams{
+            ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone,
+            InterpolationType::kFlat, InterpolationSampling::kNone}));
+
+// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
+// through
+TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoFunctions) {
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_TRUE(inspector.has_error());
+
+  EXPECT_EQ("", result);
+}
+
+// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
+// through
+TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoEntryPoints) {
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_TRUE(inspector.has_error());
+
+  EXPECT_EQ("", result);
+}
+
+// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
+// through
+TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_OneEntryPoint) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kVertex),
+                               });
+
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ("foo", result);
+}
+
+// TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
+// through
+TEST_F(InspectorGetRemappedNameForEntryPointTest,
+       DISABLED_MultipleEntryPoints) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kVertex),
+                               });
+
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
+
+  MakeEmptyBodyFunction("bar",
+                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                           WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  {
+    auto result = inspector.GetRemappedNameForEntryPoint("foo");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
+    EXPECT_EQ("foo", result);
+  }
+  {
+    auto result = inspector.GetRemappedNameForEntryPoint("bar");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
+    EXPECT_EQ("bar", result);
+  }
+}
+
+TEST_F(InspectorGetConstantIDsTest, Bool) {
+  AddOverridableConstantWithID("foo", 1, ty.bool_(), nullptr);
+  AddOverridableConstantWithID("bar", 20, ty.bool_(), Expr(true));
+  AddOverridableConstantWithID("baz", 300, ty.bool_(), Expr(false));
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
+  ASSERT_EQ(3u, result.size());
+
+  ASSERT_TRUE(result.find(1) != result.end());
+  EXPECT_TRUE(result[1].IsNull());
+
+  ASSERT_TRUE(result.find(20) != result.end());
+  EXPECT_TRUE(result[20].IsBool());
+  EXPECT_TRUE(result[20].AsBool());
+
+  ASSERT_TRUE(result.find(300) != result.end());
+  EXPECT_TRUE(result[300].IsBool());
+  EXPECT_FALSE(result[300].AsBool());
+}
+
+TEST_F(InspectorGetConstantIDsTest, U32) {
+  AddOverridableConstantWithID("foo", 1, ty.u32(), nullptr);
+  AddOverridableConstantWithID("bar", 20, ty.u32(), Expr(42u));
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
+  ASSERT_EQ(2u, result.size());
+
+  ASSERT_TRUE(result.find(1) != result.end());
+  EXPECT_TRUE(result[1].IsNull());
+
+  ASSERT_TRUE(result.find(20) != result.end());
+  EXPECT_TRUE(result[20].IsU32());
+  EXPECT_EQ(42u, result[20].AsU32());
+}
+
+TEST_F(InspectorGetConstantIDsTest, I32) {
+  AddOverridableConstantWithID("foo", 1, ty.i32(), nullptr);
+  AddOverridableConstantWithID("bar", 20, ty.i32(), Expr(-42));
+  AddOverridableConstantWithID("baz", 300, ty.i32(), Expr(42));
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
+  ASSERT_EQ(3u, result.size());
+
+  ASSERT_TRUE(result.find(1) != result.end());
+  EXPECT_TRUE(result[1].IsNull());
+
+  ASSERT_TRUE(result.find(20) != result.end());
+  EXPECT_TRUE(result[20].IsI32());
+  EXPECT_EQ(-42, result[20].AsI32());
+
+  ASSERT_TRUE(result.find(300) != result.end());
+  EXPECT_TRUE(result[300].IsI32());
+  EXPECT_EQ(42, result[300].AsI32());
+}
+
+TEST_F(InspectorGetConstantIDsTest, Float) {
+  AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr);
+  AddOverridableConstantWithID("bar", 20, ty.f32(), Expr(0.0f));
+  AddOverridableConstantWithID("baz", 300, ty.f32(), Expr(-10.0f));
+  AddOverridableConstantWithID("x", 4000, ty.f32(), Expr(15.0f));
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
+  ASSERT_EQ(4u, result.size());
+
+  ASSERT_TRUE(result.find(1) != result.end());
+  EXPECT_TRUE(result[1].IsNull());
+
+  ASSERT_TRUE(result.find(20) != result.end());
+  EXPECT_TRUE(result[20].IsFloat());
+  EXPECT_FLOAT_EQ(0.0, result[20].AsFloat());
+
+  ASSERT_TRUE(result.find(300) != result.end());
+  EXPECT_TRUE(result[300].IsFloat());
+  EXPECT_FLOAT_EQ(-10.0, result[300].AsFloat());
+
+  ASSERT_TRUE(result.find(4000) != result.end());
+  EXPECT_TRUE(result[4000].IsFloat());
+  EXPECT_FLOAT_EQ(15.0, result[4000].AsFloat());
+}
+
+TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) {
+  AddOverridableConstantWithID("v1", 1, ty.f32(), nullptr);
+  AddOverridableConstantWithID("v20", 20, ty.f32(), nullptr);
+  AddOverridableConstantWithID("v300", 300, ty.f32(), nullptr);
+  auto* a = AddOverridableConstantWithoutID("a", ty.f32(), nullptr);
+  auto* b = AddOverridableConstantWithoutID("b", ty.f32(), nullptr);
+  auto* c = AddOverridableConstantWithoutID("c", ty.f32(), nullptr);
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantNameToIdMap();
+  ASSERT_EQ(6u, result.size());
+
+  ASSERT_TRUE(result.count("v1"));
+  EXPECT_EQ(result["v1"], 1u);
+
+  ASSERT_TRUE(result.count("v20"));
+  EXPECT_EQ(result["v20"], 20u);
+
+  ASSERT_TRUE(result.count("v300"));
+  EXPECT_EQ(result["v300"], 300u);
+
+  ASSERT_TRUE(result.count("a"));
+  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(a));
+  EXPECT_EQ(result["a"],
+            program_->Sem().Get<sem::GlobalVariable>(a)->ConstantId());
+
+  ASSERT_TRUE(result.count("b"));
+  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(b));
+  EXPECT_EQ(result["b"],
+            program_->Sem().Get<sem::GlobalVariable>(b)->ConstantId());
+
+  ASSERT_TRUE(result.count("c"));
+  ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(c));
+  EXPECT_EQ(result["c"],
+            program_->Sem().Get<sem::GlobalVariable>(c)->ConstantId());
+}
+
+TEST_F(InspectorGetStorageSizeTest, Empty) {
+  MakeEmptyBodyFunction("ep_func",
+                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                           WorkgroupSize(1)});
+  Inspector& inspector = Build();
+  EXPECT_EQ(0u, inspector.GetStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetStorageSizeTest, Simple_NonStruct) {
+  AddUniformBuffer("ub_var", ty.i32(), 0, 0);
+  AddStorageBuffer("sb_var", ty.i32(), ast::Access::kReadWrite, 1, 0);
+  AddStorageBuffer("rosb_var", ty.i32(), ast::Access::kRead, 1, 1);
+  Func("ep_func", {}, ty.void_(),
+       {
+           Decl(Const("ub", nullptr, Expr("ub_var"))),
+           Decl(Const("sb", nullptr, Expr("sb_var"))),
+           Decl(Const("rosb", nullptr, Expr("rosb_var"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  EXPECT_EQ(12u, inspector.GetStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetStorageSizeTest, Simple_Struct) {
+  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32(), ty.i32()});
+  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
+  MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
+
+  auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
+  AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
+  MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
+
+  auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
+  AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
+  MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
+                                          {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func", "sb_func", "rosb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kCompute),
+                             WorkgroupSize(1),
+                         });
+
+  Inspector& inspector = Build();
+
+  EXPECT_EQ(16u, inspector.GetStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetStorageSizeTest, NonStructVec3) {
+  AddUniformBuffer("ub_var", ty.vec3<f32>(), 0, 0);
+  Func("ep_func", {}, ty.void_(),
+       {
+           Decl(Const("ub", nullptr, Expr("ub_var"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  EXPECT_EQ(12u, inspector.GetStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetStorageSizeTest, StructVec3) {
+  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.vec3<f32>()});
+  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
+  Func("ep_func", {}, ty.void_(),
+       {
+           Decl(Const("ub", nullptr, Expr("ub_var"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Inspector& inspector = Build();
+
+  EXPECT_EQ(16u, inspector.GetStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetResourceBindingsTest, Empty) {
+  MakeCallerBodyFunction("ep_func", {},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetResourceBindingsTest, Simple) {
+  auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()});
+  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
+  MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
+
+  auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
+  AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
+  MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
+
+  auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
+  AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
+  MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
+                                          {{0, ty.i32()}});
+
+  auto* s_texture_type =
+      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  AddResource("s_texture", s_texture_type, 2, 0);
+  AddSampler("s_var", 3, 0);
+  AddGlobalVariable("s_coords", ty.f32());
+  MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords",
+                                   ty.f32(), {});
+
+  auto* cs_depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
+  AddResource("cs_texture", cs_depth_texture_type, 3, 1);
+  AddComparisonSampler("cs_var", 3, 2);
+  AddGlobalVariable("cs_coords", ty.vec2<f32>());
+  AddGlobalVariable("cs_depth", ty.f32());
+  MakeComparisonSamplerReferenceBodyFunction(
+      "cs_func", "cs_texture", "cs_var", "cs_coords", "cs_depth", ty.f32(), {});
+
+  auto* depth_ms_texture_type =
+      ty.depth_multisampled_texture(ast::TextureDimension::k2d);
+  AddResource("depth_ms_texture", depth_ms_texture_type, 3, 3);
+  Func("depth_ms_func", {}, ty.void_(), {Ignore("depth_ms_texture")});
+
+  auto* st_type = MakeStorageTextureTypes(ast::TextureDimension::k2d,
+                                          ast::TexelFormat::kR32Uint);
+  AddStorageTexture("st_var", st_type, 4, 0);
+  MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), {});
+
+  MakeCallerBodyFunction("ep_func",
+                         {"ub_func", "sb_func", "rosb_func", "s_func",
+                          "cs_func", "depth_ms_func", "st_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(9u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[1].resource_type);
+  EXPECT_EQ(1u, result[1].bind_group);
+  EXPECT_EQ(0u, result[1].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[2].resource_type);
+  EXPECT_EQ(1u, result[2].bind_group);
+  EXPECT_EQ(1u, result[2].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[3].resource_type);
+  EXPECT_EQ(3u, result[3].bind_group);
+  EXPECT_EQ(0u, result[3].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
+            result[4].resource_type);
+  EXPECT_EQ(3u, result[4].bind_group);
+  EXPECT_EQ(2u, result[4].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
+            result[5].resource_type);
+  EXPECT_EQ(2u, result[5].bind_group);
+  EXPECT_EQ(0u, result[5].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture,
+            result[6].resource_type);
+  EXPECT_EQ(4u, result[6].bind_group);
+  EXPECT_EQ(0u, result[6].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture,
+            result[7].resource_type);
+  EXPECT_EQ(3u, result[7].bind_group);
+  EXPECT_EQ(1u, result[7].binding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture,
+            result[8].resource_type);
+  EXPECT_EQ(3u, result[8].bind_group);
+  EXPECT_EQ(3u, result[8].binding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingEntryPoint) {
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_TRUE(inspector.has_error());
+  std::string error = inspector.error();
+  EXPECT_TRUE(error.find("not found") != std::string::npos);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
+  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ub_func");
+  std::string error = inspector.error();
+  EXPECT_TRUE(error.find("not an entry point") != std::string::npos);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_NonStruct) {
+  AddUniformBuffer("foo_ub", ty.i32(), 0, 0);
+  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.i32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(4u, result[0].size);
+  EXPECT_EQ(4u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_Struct) {
+  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(4u, result[0].size);
+  EXPECT_EQ(4u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
+  auto* foo_struct_type =
+      MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()});
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+
+  MakeStructVariableReferenceBodyFunction(
+      "ub_func", "foo_ub", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) {
+  auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.vec3<f32>()});
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                          {{0, ty.vec3<f32>()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(16u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonStructVec3) {
+  AddUniformBuffer("foo_ub", ty.vec3<f32>(), 0, 0);
+  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
+  auto* ub_struct_type =
+      MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()});
+  AddUniformBuffer("ub_foo", ty.Of(ub_struct_type), 0, 0);
+  AddUniformBuffer("ub_bar", ty.Of(ub_struct_type), 0, 1);
+  AddUniformBuffer("ub_baz", ty.Of(ub_struct_type), 2, 0);
+
+  auto AddReferenceFunc = [this](const std::string& func_name,
+                                 const std::string& var_name) {
+    MakeStructVariableReferenceBodyFunction(
+        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+  };
+  AddReferenceFunc("ub_foo_func", "ub_foo");
+  AddReferenceFunc("ub_bar_func", "ub_bar");
+  AddReferenceFunc("ub_baz_func", "ub_baz");
+
+  auto FuncCall = [&](const std::string& callee) {
+    return create<ast::CallStatement>(Call(callee));
+  };
+
+  Func("ep_func", ast::VariableList(), ty.void_(),
+       ast::StatementList{FuncCall("ub_foo_func"), FuncCall("ub_bar_func"),
+                          FuncCall("ub_baz_func"), Return()},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(3u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[1].resource_type);
+  EXPECT_EQ(0u, result[1].bind_group);
+  EXPECT_EQ(1u, result[1].binding);
+  EXPECT_EQ(12u, result[1].size);
+  EXPECT_EQ(12u, result[1].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[2].resource_type);
+  EXPECT_EQ(2u, result[2].bind_group);
+  EXPECT_EQ(0u, result[2].binding);
+  EXPECT_EQ(12u, result[2].size);
+  EXPECT_EQ(12u, result[2].size_no_padding);
+}
+
+TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) {
+  // Manually create uniform buffer to make sure it had a valid layout (array
+  // with elem stride of 16, and that is 16-byte aligned within the struct)
+  auto* foo_struct_type = Structure(
+      "foo_type",
+      {Member("0i32", ty.i32()),
+       Member("b", ty.array(ty.u32(), 4, /*stride*/ 16), {MemberAlign(16)})},
+      {create<ast::StructBlockAttribute>()});
+
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(80u, result[0].size);
+  EXPECT_EQ(80u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_NonStruct) {
+  AddStorageBuffer("foo_sb", ty.i32(), ast::Access::kReadWrite, 0, 0);
+  MakePlainGlobalReferenceBodyFunction("sb_func", "foo_sb", ty.i32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(4u, result[0].size);
+  EXPECT_EQ(4u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_Struct) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(4u, result[0].size);
+  EXPECT_EQ(4u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+                                                                ty.i32(),
+                                                                ty.u32(),
+                                                                ty.f32(),
+                                                            });
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction(
+      "sb_func", "foo_sb", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
+  auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
+                                                              ty.i32(),
+                                                              ty.u32(),
+                                                              ty.f32(),
+                                                          });
+  AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kReadWrite, 0, 0);
+  AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kReadWrite, 0, 1);
+  AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kReadWrite, 2, 0);
+
+  auto AddReferenceFunc = [this](const std::string& func_name,
+                                 const std::string& var_name) {
+    MakeStructVariableReferenceBodyFunction(
+        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+  };
+  AddReferenceFunc("sb_foo_func", "sb_foo");
+  AddReferenceFunc("sb_bar_func", "sb_bar");
+  AddReferenceFunc("sb_baz_func", "sb_baz");
+
+  auto FuncCall = [&](const std::string& callee) {
+    return create<ast::CallStatement>(Call(callee));
+  };
+
+  Func("ep_func", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           FuncCall("sb_foo_func"),
+           FuncCall("sb_bar_func"),
+           FuncCall("sb_baz_func"),
+           Return(),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(3u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[1].resource_type);
+  EXPECT_EQ(0u, result[1].bind_group);
+  EXPECT_EQ(1u, result[1].binding);
+  EXPECT_EQ(12u, result[1].size);
+  EXPECT_EQ(12u, result[1].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[2].resource_type);
+  EXPECT_EQ(2u, result[2].bind_group);
+  EXPECT_EQ(0u, result[2].binding);
+  EXPECT_EQ(12u, result[2].size);
+  EXPECT_EQ(12u, result[2].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
+  auto foo_struct_type =
+      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(20u, result[0].size);
+  EXPECT_EQ(20u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+                                                                ty.i32(),
+                                                                ty.array<u32>(),
+                                                            });
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(8u, result[0].size);
+  EXPECT_EQ(8u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                          {{0, ty.vec3<f32>()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(16u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, NonStructVec3) {
+  AddStorageBuffer("foo_ub", ty.vec3<f32>(), ast::Access::kReadWrite, 0, 0);
+  MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
+
+  MakeCallerBodyFunction("ep_func", {"ub_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(4u, result[0].size);
+  EXPECT_EQ(4u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
+       MultipleStorageBuffers) {
+  auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
+                                                              ty.i32(),
+                                                              ty.u32(),
+                                                              ty.f32(),
+                                                          });
+  AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kRead, 0, 0);
+  AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kRead, 0, 1);
+  AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kRead, 2, 0);
+
+  auto AddReferenceFunc = [this](const std::string& func_name,
+                                 const std::string& var_name) {
+    MakeStructVariableReferenceBodyFunction(
+        func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+  };
+  AddReferenceFunc("sb_foo_func", "sb_foo");
+  AddReferenceFunc("sb_bar_func", "sb_bar");
+  AddReferenceFunc("sb_baz_func", "sb_baz");
+
+  auto FuncCall = [&](const std::string& callee) {
+    return create<ast::CallStatement>(Call(callee));
+  };
+
+  Func("ep_func", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           FuncCall("sb_foo_func"),
+           FuncCall("sb_bar_func"),
+           FuncCall("sb_baz_func"),
+           Return(),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(3u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(12u, result[0].size);
+  EXPECT_EQ(12u, result[0].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[1].resource_type);
+  EXPECT_EQ(0u, result[1].bind_group);
+  EXPECT_EQ(1u, result[1].binding);
+  EXPECT_EQ(12u, result[1].size);
+  EXPECT_EQ(12u, result[1].size_no_padding);
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[2].resource_type);
+  EXPECT_EQ(2u, result[2].bind_group);
+  EXPECT_EQ(0u, result[2].binding);
+  EXPECT_EQ(12u, result[2].size);
+  EXPECT_EQ(12u, result[2].size_no_padding);
+}
+
+TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
+  auto foo_struct_type =
+      MakeStorageBufferTypes("foo_type", {
+                                             ty.i32(),
+                                             ty.array<u32, 4>(),
+                                         });
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(20u, result[0].size);
+  EXPECT_EQ(20u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
+       ContainingRuntimeArray) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+                                                                ty.i32(),
+                                                                ty.array<u32>(),
+                                                            });
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(8u, result[0].size);
+  EXPECT_EQ(8u, result[0].size_no_padding);
+}
+
+TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
+
+  MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+
+  MakeCallerBodyFunction("ep_func", {"sb_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) {
+  auto* sampled_texture_type =
+      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.f32());
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords", ty.f32(),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
+  MakeEmptyBodyFunction("ep_func", ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) {
+  auto* sampled_texture_type =
+      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.f32());
+
+  MakeSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler",
+                                   "foo_coords", ty.f32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"foo_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) {
+  auto* sampled_texture_type =
+      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.f32());
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords", ty.f32(),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("foo");
+  ASSERT_TRUE(inspector.has_error()) << inspector.error();
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) {
+  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
+  AddResource("foo_texture", depth_texture_type, 0, 0);
+  AddComparisonSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.vec2<f32>());
+  AddGlobalVariable("foo_depth", ty.f32());
+
+  MakeComparisonSamplerReferenceBodyFunction(
+      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
+      ast::AttributeList{
+          Stage(ast::PipelineStage::kFragment),
+      });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) {
+  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
+  AddResource("foo_texture", depth_texture_type, 0, 0);
+  AddComparisonSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.vec2<f32>());
+  AddGlobalVariable("foo_depth", ty.f32());
+
+  MakeComparisonSamplerReferenceBodyFunction(
+      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
+      ast::AttributeList{
+          Stage(ast::PipelineStage::kFragment),
+      });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
+            result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) {
+  MakeEmptyBodyFunction("ep_func", ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) {
+  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
+  AddResource("foo_texture", depth_texture_type, 0, 0);
+  AddComparisonSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.vec2<f32>());
+  AddGlobalVariable("foo_depth", ty.f32());
+
+  MakeComparisonSamplerReferenceBodyFunction("foo_func", "foo_texture",
+                                             "foo_sampler", "foo_coords",
+                                             "foo_depth", ty.f32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"foo_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kFragment),
+                         });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler,
+            result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) {
+  auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
+  AddResource("foo_texture", depth_texture_type, 0, 0);
+  AddComparisonSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.vec2<f32>());
+  AddGlobalVariable("foo_depth", ty.f32());
+
+  MakeComparisonSamplerReferenceBodyFunction(
+      "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
+      ast::AttributeList{
+          Stage(ast::PipelineStage::kFragment),
+      });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("foo");
+  ASSERT_TRUE(inspector.has_error()) << inspector.error();
+}
+
+TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) {
+  auto* sampled_texture_type =
+      ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddGlobalVariable("foo_coords", ty.f32());
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords", ty.f32(),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kFragment),
+                               });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) {
+  auto* sampled_texture_type = ty.sampled_texture(
+      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  AddGlobalVariable("foo_coords", coord_type);
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords",
+                                   GetBaseType(GetParam().sampled_kind),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
+            result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
+  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
+
+  // Prove that sampled and multi-sampled bindings are accounted
+  // for separately.
+  auto multisampled_result =
+      inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_TRUE(multisampled_result.empty());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetSampledTextureResourceBindingsTest,
+    InspectorGetSampledTextureResourceBindingsTestWithParam,
+    testing::Values(
+        GetSampledTextureTestParams{
+            ast::TextureDimension::k1d,
+            inspector::ResourceBinding::TextureDimension::k1d,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetSampledTextureTestParams{
+            ast::TextureDimension::k2d,
+            inspector::ResourceBinding::TextureDimension::k2d,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetSampledTextureTestParams{
+            ast::TextureDimension::k3d,
+            inspector::ResourceBinding::TextureDimension::k3d,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetSampledTextureTestParams{
+            ast::TextureDimension::kCube,
+            inspector::ResourceBinding::TextureDimension::kCube,
+            inspector::ResourceBinding::SampledKind::kFloat}));
+
+TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam,
+       textureSample) {
+  auto* sampled_texture_type = ty.sampled_texture(
+      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
+  AddResource("foo_texture", sampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  AddGlobalVariable("foo_coords", coord_type);
+  AddGlobalVariable("foo_array_index", ty.i32());
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords", "foo_array_index",
+                                   GetBaseType(GetParam().sampled_kind),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
+  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetSampledArrayTextureResourceBindingsTest,
+    InspectorGetSampledArrayTextureResourceBindingsTestWithParam,
+    testing::Values(
+        GetSampledTextureTestParams{
+            ast::TextureDimension::k2dArray,
+            inspector::ResourceBinding::TextureDimension::k2dArray,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetSampledTextureTestParams{
+            ast::TextureDimension::kCubeArray,
+            inspector::ResourceBinding::TextureDimension::kCubeArray,
+            inspector::ResourceBinding::SampledKind::kFloat}));
+
+TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam,
+       textureLoad) {
+  auto* multisampled_texture_type = ty.multisampled_texture(
+      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
+  AddResource("foo_texture", multisampled_texture_type, 0, 0);
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
+  AddGlobalVariable("foo_coords", coord_type);
+  AddGlobalVariable("foo_sample_index", ty.i32());
+
+  Func("ep", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("textureLoad", "foo_texture", "foo_coords",
+                         "foo_sample_index")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
+  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
+
+  // Prove that sampled and multi-sampled bindings are accounted
+  // for separately.
+  auto single_sampled_result =
+      inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_TRUE(single_sampled_result.empty());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetMultisampledTextureResourceBindingsTest,
+    InspectorGetMultisampledTextureResourceBindingsTestWithParam,
+    testing::Values(
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2d,
+            inspector::ResourceBinding::TextureDimension::k2d,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2d,
+            inspector::ResourceBinding::TextureDimension::k2d,
+            inspector::ResourceBinding::SampledKind::kSInt},
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2d,
+            inspector::ResourceBinding::TextureDimension::k2d,
+            inspector::ResourceBinding::SampledKind::kUInt}));
+
+TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) {
+  MakeEmptyBodyFunction("foo", ast::AttributeList{
+                                   Stage(ast::PipelineStage::kFragment),
+                               });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST_P(InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam,
+       DISABLED_textureSample) {
+  auto* multisampled_texture_type = ty.multisampled_texture(
+      GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
+  AddResource("foo_texture", multisampled_texture_type, 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  AddGlobalVariable("foo_coords", coord_type);
+  AddGlobalVariable("foo_array_index", ty.i32());
+
+  MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
+                                   "foo_coords", "foo_array_index",
+                                   GetBaseType(GetParam().sampled_kind),
+                                   ast::AttributeList{
+                                       Stage(ast::PipelineStage::kFragment),
+                                   });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
+  EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetMultisampledArrayTextureResourceBindingsTest,
+    InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam,
+    testing::Values(
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2dArray,
+            inspector::ResourceBinding::TextureDimension::k2dArray,
+            inspector::ResourceBinding::SampledKind::kFloat},
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2dArray,
+            inspector::ResourceBinding::TextureDimension::k2dArray,
+            inspector::ResourceBinding::SampledKind::kSInt},
+        GetMultisampledTextureTestParams{
+            ast::TextureDimension::k2dArray,
+            inspector::ResourceBinding::TextureDimension::k2dArray,
+            inspector::ResourceBinding::SampledKind::kUInt}));
+
+TEST_F(InspectorGetStorageTextureResourceBindingsTest, Empty) {
+  MakeEmptyBodyFunction("ep", ast::AttributeList{
+                                  Stage(ast::PipelineStage::kFragment),
+                              });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST_P(InspectorGetStorageTextureResourceBindingsTestWithParam, Simple) {
+  DimensionParams dim_params;
+  TexelFormatParams format_params;
+  std::tie(dim_params, format_params) = GetParam();
+
+  ast::TextureDimension dim;
+  ResourceBinding::TextureDimension expected_dim;
+  std::tie(dim, expected_dim) = dim_params;
+
+  ast::TexelFormat format;
+  ResourceBinding::TexelFormat expected_format;
+  ResourceBinding::SampledKind expected_kind;
+  std::tie(format, expected_format, expected_kind) = format_params;
+
+  auto* st_type = MakeStorageTextureTypes(dim, format);
+  AddStorageTexture("st_var", st_type, 0, 0);
+
+  const ast::Type* dim_type = nullptr;
+  switch (dim) {
+    case ast::TextureDimension::k1d:
+      dim_type = ty.i32();
+      break;
+    case ast::TextureDimension::k2d:
+    case ast::TextureDimension::k2dArray:
+      dim_type = ty.vec2<i32>();
+      break;
+    case ast::TextureDimension::k3d:
+      dim_type = ty.vec3<i32>();
+      break;
+    default:
+      break;
+  }
+
+  ASSERT_FALSE(dim_type == nullptr);
+
+  MakeStorageTextureBodyFunction(
+      "ep", "st_var", dim_type,
+      ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture,
+            result[0].resource_type);
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(expected_dim, result[0].dim);
+  EXPECT_EQ(expected_format, result[0].image_format);
+  EXPECT_EQ(expected_kind, result[0].sampled_kind);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetStorageTextureResourceBindingsTest,
+    InspectorGetStorageTextureResourceBindingsTestWithParam,
+    testing::Combine(
+        testing::Values(
+            std::make_tuple(ast::TextureDimension::k1d,
+                            ResourceBinding::TextureDimension::k1d),
+            std::make_tuple(ast::TextureDimension::k2d,
+                            ResourceBinding::TextureDimension::k2d),
+            std::make_tuple(ast::TextureDimension::k2dArray,
+                            ResourceBinding::TextureDimension::k2dArray),
+            std::make_tuple(ast::TextureDimension::k3d,
+                            ResourceBinding::TextureDimension::k3d)),
+        testing::Values(
+            std::make_tuple(ast::TexelFormat::kR32Float,
+                            ResourceBinding::TexelFormat::kR32Float,
+                            ResourceBinding::SampledKind::kFloat),
+            std::make_tuple(ast::TexelFormat::kR32Sint,
+                            ResourceBinding::TexelFormat::kR32Sint,
+                            ResourceBinding::SampledKind::kSInt),
+            std::make_tuple(ast::TexelFormat::kR32Uint,
+                            ResourceBinding::TexelFormat::kR32Uint,
+                            ResourceBinding::SampledKind::kUInt),
+            std::make_tuple(ast::TexelFormat::kRg32Float,
+                            ResourceBinding::TexelFormat::kRg32Float,
+                            ResourceBinding::SampledKind::kFloat),
+            std::make_tuple(ast::TexelFormat::kRg32Sint,
+                            ResourceBinding::TexelFormat::kRg32Sint,
+                            ResourceBinding::SampledKind::kSInt),
+            std::make_tuple(ast::TexelFormat::kRg32Uint,
+                            ResourceBinding::TexelFormat::kRg32Uint,
+                            ResourceBinding::SampledKind::kUInt),
+            std::make_tuple(ast::TexelFormat::kRgba16Float,
+                            ResourceBinding::TexelFormat::kRgba16Float,
+                            ResourceBinding::SampledKind::kFloat),
+            std::make_tuple(ast::TexelFormat::kRgba16Sint,
+                            ResourceBinding::TexelFormat::kRgba16Sint,
+                            ResourceBinding::SampledKind::kSInt),
+            std::make_tuple(ast::TexelFormat::kRgba16Uint,
+                            ResourceBinding::TexelFormat::kRgba16Uint,
+                            ResourceBinding::SampledKind::kUInt),
+            std::make_tuple(ast::TexelFormat::kRgba32Float,
+                            ResourceBinding::TexelFormat::kRgba32Float,
+                            ResourceBinding::SampledKind::kFloat),
+            std::make_tuple(ast::TexelFormat::kRgba32Sint,
+                            ResourceBinding::TexelFormat::kRgba32Sint,
+                            ResourceBinding::SampledKind::kSInt),
+            std::make_tuple(ast::TexelFormat::kRgba32Uint,
+                            ResourceBinding::TexelFormat::kRgba32Uint,
+                            ResourceBinding::SampledKind::kUInt),
+            std::make_tuple(ast::TexelFormat::kRgba8Sint,
+                            ResourceBinding::TexelFormat::kRgba8Sint,
+                            ResourceBinding::SampledKind::kSInt),
+            std::make_tuple(ast::TexelFormat::kRgba8Snorm,
+                            ResourceBinding::TexelFormat::kRgba8Snorm,
+                            ResourceBinding::SampledKind::kFloat),
+            std::make_tuple(ast::TexelFormat::kRgba8Uint,
+                            ResourceBinding::TexelFormat::kRgba8Uint,
+                            ResourceBinding::SampledKind::kUInt),
+            std::make_tuple(ast::TexelFormat::kRgba8Unorm,
+                            ResourceBinding::TexelFormat::kRgba8Unorm,
+                            ResourceBinding::SampledKind::kFloat))));
+
+TEST_P(InspectorGetDepthTextureResourceBindingsTestWithParam,
+       textureDimensions) {
+  auto* depth_texture_type = ty.depth_texture(GetParam().type_dim);
+  AddResource("dt", depth_texture_type, 0, 0);
+
+  Func("ep", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("textureDimensions", "dt")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetDepthTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture,
+            result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InspectorGetDepthTextureResourceBindingsTest,
+    InspectorGetDepthTextureResourceBindingsTestWithParam,
+    testing::Values(
+        GetDepthTextureTestParams{
+            ast::TextureDimension::k2d,
+            inspector::ResourceBinding::TextureDimension::k2d},
+        GetDepthTextureTestParams{
+            ast::TextureDimension::k2dArray,
+            inspector::ResourceBinding::TextureDimension::k2dArray},
+        GetDepthTextureTestParams{
+            ast::TextureDimension::kCube,
+            inspector::ResourceBinding::TextureDimension::kCube},
+        GetDepthTextureTestParams{
+            ast::TextureDimension::kCubeArray,
+            inspector::ResourceBinding::TextureDimension::kCubeArray}));
+
+TEST_F(InspectorGetDepthMultisampledTextureResourceBindingsTest,
+       textureDimensions) {
+  auto* depth_ms_texture_type =
+      ty.depth_multisampled_texture(ast::TextureDimension::k2d);
+  AddResource("tex", depth_ms_texture_type, 0, 0);
+
+  Func("ep", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("textureDimensions", "tex")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetDepthMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture,
+            result[0].resource_type);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+  EXPECT_EQ(ResourceBinding::TextureDimension::k2d, result[0].dim);
+}
+
+TEST_F(InspectorGetExternalTextureResourceBindingsTest, Simple) {
+  auto* external_texture_type = ty.external_texture();
+  AddResource("et", external_texture_type, 0, 0);
+
+  Func("ep", ast::VariableList(), ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("textureDimensions", "et")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetExternalTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+  EXPECT_EQ(ResourceBinding::ResourceType::kExternalTexture,
+            result[0].resource_type);
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(0u, result[0].binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, None) {
+  std::string shader = R"(
+@stage(fragment)
+fn main() {
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, Simple) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+  EXPECT_EQ(0u, result[0].texture_binding_point.group);
+  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, UnknownEntryPoint) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("foo");
+  ASSERT_TRUE(inspector.has_error()) << inspector.error();
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, MultipleCalls) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result_0 = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  auto result_1 = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  EXPECT_EQ(result_0, result_1);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, BothIndirect) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, uv);
+}
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return doSample(myTexture, mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+  EXPECT_EQ(0u, result[0].texture_binding_point.group);
+  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, SamplerIndirect) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+fn doSample(s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(myTexture, s, uv);
+}
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return doSample(mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+  EXPECT_EQ(0u, result[0].texture_binding_point.group);
+  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, TextureIndirect) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+fn doSample(t: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(t, mySampler, uv);
+}
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return doSample(myTexture, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+  EXPECT_EQ(0u, result[0].texture_binding_point.group);
+  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, NeitherIndirect) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+fn doSample(uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(myTexture, mySampler, uv);
+}
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return doSample(fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+  ASSERT_EQ(1u, result.size());
+
+  EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+  EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+  EXPECT_EQ(0u, result[0].texture_binding_point.group);
+  EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+}
+
+TEST_F(InspectorGetSamplerTextureUsesTest, Complex) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+
+fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, uv);
+}
+
+fn X(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return doSample(t, s, uv);
+}
+
+fn Y(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return doSample(t, s, uv);
+}
+
+fn Z(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return X(t, s, uv) + Y(t, s, uv);
+}
+
+@stage(fragment)
+fn via_call(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return Z(myTexture, mySampler, fragUV) * fragPosition;
+}
+
+@stage(fragment)
+fn via_ptr(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
+}
+
+@stage(fragment)
+fn direct(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+
+  {
+    auto result = inspector.GetSamplerTextureUses("via_call");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+    ASSERT_EQ(1u, result.size());
+
+    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+    EXPECT_EQ(0u, result[0].texture_binding_point.group);
+    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+  }
+
+  {
+    auto result = inspector.GetSamplerTextureUses("via_ptr");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+    ASSERT_EQ(1u, result.size());
+
+    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+    EXPECT_EQ(0u, result[0].texture_binding_point.group);
+    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+  }
+
+  {
+    auto result = inspector.GetSamplerTextureUses("direct");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
+
+    ASSERT_EQ(1u, result.size());
+
+    EXPECT_EQ(0u, result[0].sampler_binding_point.group);
+    EXPECT_EQ(1u, result[0].sampler_binding_point.binding);
+    EXPECT_EQ(0u, result[0].texture_binding_point.group);
+    EXPECT_EQ(2u, result[0].texture_binding_point.binding);
+  }
+}
+
+TEST_F(InspectorGetWorkgroupStorageSizeTest, Empty) {
+  MakeEmptyBodyFunction("ep_func",
+                        ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                           WorkgroupSize(1)});
+  Inspector& inspector = Build();
+  EXPECT_EQ(0u, inspector.GetWorkgroupStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetWorkgroupStorageSizeTest, Simple) {
+  AddWorkgroupStorage("wg_f32", ty.f32());
+  MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"f32_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kCompute),
+                             WorkgroupSize(1),
+                         });
+
+  Inspector& inspector = Build();
+  EXPECT_EQ(4u, inspector.GetWorkgroupStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetWorkgroupStorageSizeTest, CompoundTypes) {
+  // This struct should occupy 68 bytes. 4 from the i32 field, and another 64
+  // from the 4-element array with 16-byte stride.
+  auto* wg_struct_type = MakeStructType(
+      "WgStruct", {ty.i32(), ty.array(ty.i32(), 4, /*stride=*/16)},
+      /*is_block=*/false);
+  AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
+  MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
+                                          {{0, ty.i32()}});
+
+  // Plus another 4 bytes from this other workgroup-class f32.
+  AddWorkgroupStorage("wg_f32", ty.f32());
+  MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
+
+  MakeCallerBodyFunction("ep_func", {"wg_struct_func", "f32_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kCompute),
+                             WorkgroupSize(1),
+                         });
+
+  Inspector& inspector = Build();
+  EXPECT_EQ(72u, inspector.GetWorkgroupStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetWorkgroupStorageSizeTest, AlignmentPadding) {
+  // vec3<f32> has an alignment of 16 but a size of 12. We leverage this to test
+  // that our padded size calculation for workgroup storage is accurate.
+  AddWorkgroupStorage("wg_vec3", ty.vec3<f32>());
+  MakePlainGlobalReferenceBodyFunction("wg_func", "wg_vec3", ty.vec3<f32>(),
+                                       {});
+
+  MakeCallerBodyFunction("ep_func", {"wg_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kCompute),
+                             WorkgroupSize(1),
+                         });
+
+  Inspector& inspector = Build();
+  EXPECT_EQ(16u, inspector.GetWorkgroupStorageSize("ep_func"));
+}
+
+TEST_F(InspectorGetWorkgroupStorageSizeTest, StructAlignment) {
+  // Per WGSL spec, a struct's size is the offset its last member plus the size
+  // of its last member, rounded up to the alignment of its largest member. So
+  // here the struct is expected to occupy 1024 bytes of workgroup storage.
+  const auto* wg_struct_type = MakeStructTypeFromMembers(
+      "WgStruct",
+      {MakeStructMember(0, ty.f32(),
+                        {create<ast::StructMemberAlignAttribute>(1024)})},
+      /*is_block=*/false);
+
+  AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
+  MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
+                                          {{0, ty.f32()}});
+
+  MakeCallerBodyFunction("ep_func", {"wg_struct_func"},
+                         ast::AttributeList{
+                             Stage(ast::PipelineStage::kCompute),
+                             WorkgroupSize(1),
+                         });
+
+  Inspector& inspector = Build();
+  EXPECT_EQ(1024u, inspector.GetWorkgroupStorageSize("ep_func"));
+}
+
+// Crash was occuring in ::GenerateSamplerTargets, when
+// ::GetSamplerTextureUses was called.
+TEST_F(InspectorRegressionTest, tint967) {
+  std::string shader = R"(
+@group(0) @binding(1) var mySampler: sampler;
+@group(0) @binding(2) var myTexture: texture_2d<f32>;
+
+fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, uv);
+}
+
+@stage(fragment)
+fn main(@location(0) fragUV: vec2<f32>,
+        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
+  return doSample(myTexture, mySampler, fragUV) * fragPosition;
+})";
+
+  Inspector& inspector = Initialize(shader);
+  auto result = inspector.GetSamplerTextureUses("main");
+}
+
+}  // namespace
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/resource_binding.cc b/src/tint/inspector/resource_binding.cc
new file mode 100644
index 0000000..a4a0793
--- /dev/null
+++ b/src/tint/inspector/resource_binding.cc
@@ -0,0 +1,116 @@
+// 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/inspector/resource_binding.h"
+
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/matrix_type.h"
+#include "src/tint/sem/type.h"
+#include "src/tint/sem/u32_type.h"
+#include "src/tint/sem/vector_type.h"
+
+namespace tint {
+namespace inspector {
+
+ResourceBinding::TextureDimension
+TypeTextureDimensionToResourceBindingTextureDimension(
+    const ast::TextureDimension& type_dim) {
+  switch (type_dim) {
+    case ast::TextureDimension::k1d:
+      return ResourceBinding::TextureDimension::k1d;
+    case ast::TextureDimension::k2d:
+      return ResourceBinding::TextureDimension::k2d;
+    case ast::TextureDimension::k2dArray:
+      return ResourceBinding::TextureDimension::k2dArray;
+    case ast::TextureDimension::k3d:
+      return ResourceBinding::TextureDimension::k3d;
+    case ast::TextureDimension::kCube:
+      return ResourceBinding::TextureDimension::kCube;
+    case ast::TextureDimension::kCubeArray:
+      return ResourceBinding::TextureDimension::kCubeArray;
+    case ast::TextureDimension::kNone:
+      return ResourceBinding::TextureDimension::kNone;
+  }
+  return ResourceBinding::TextureDimension::kNone;
+}
+
+ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type) {
+  if (!base_type) {
+    return ResourceBinding::SampledKind::kUnknown;
+  }
+
+  if (auto* at = base_type->As<sem::Array>()) {
+    base_type = at->ElemType();
+  } else if (auto* mt = base_type->As<sem::Matrix>()) {
+    base_type = mt->type();
+  } else if (auto* vt = base_type->As<sem::Vector>()) {
+    base_type = vt->type();
+  }
+
+  if (base_type->Is<sem::F32>()) {
+    return ResourceBinding::SampledKind::kFloat;
+  } else if (base_type->Is<sem::U32>()) {
+    return ResourceBinding::SampledKind::kUInt;
+  } else if (base_type->Is<sem::I32>()) {
+    return ResourceBinding::SampledKind::kSInt;
+  } else {
+    return ResourceBinding::SampledKind::kUnknown;
+  }
+}
+
+ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat(
+    const ast::TexelFormat& image_format) {
+  switch (image_format) {
+    case ast::TexelFormat::kR32Uint:
+      return ResourceBinding::TexelFormat::kR32Uint;
+    case ast::TexelFormat::kR32Sint:
+      return ResourceBinding::TexelFormat::kR32Sint;
+    case ast::TexelFormat::kR32Float:
+      return ResourceBinding::TexelFormat::kR32Float;
+    case ast::TexelFormat::kRgba8Unorm:
+      return ResourceBinding::TexelFormat::kRgba8Unorm;
+    case ast::TexelFormat::kRgba8Snorm:
+      return ResourceBinding::TexelFormat::kRgba8Snorm;
+    case ast::TexelFormat::kRgba8Uint:
+      return ResourceBinding::TexelFormat::kRgba8Uint;
+    case ast::TexelFormat::kRgba8Sint:
+      return ResourceBinding::TexelFormat::kRgba8Sint;
+    case ast::TexelFormat::kRg32Uint:
+      return ResourceBinding::TexelFormat::kRg32Uint;
+    case ast::TexelFormat::kRg32Sint:
+      return ResourceBinding::TexelFormat::kRg32Sint;
+    case ast::TexelFormat::kRg32Float:
+      return ResourceBinding::TexelFormat::kRg32Float;
+    case ast::TexelFormat::kRgba16Uint:
+      return ResourceBinding::TexelFormat::kRgba16Uint;
+    case ast::TexelFormat::kRgba16Sint:
+      return ResourceBinding::TexelFormat::kRgba16Sint;
+    case ast::TexelFormat::kRgba16Float:
+      return ResourceBinding::TexelFormat::kRgba16Float;
+    case ast::TexelFormat::kRgba32Uint:
+      return ResourceBinding::TexelFormat::kRgba32Uint;
+    case ast::TexelFormat::kRgba32Sint:
+      return ResourceBinding::TexelFormat::kRgba32Sint;
+    case ast::TexelFormat::kRgba32Float:
+      return ResourceBinding::TexelFormat::kRgba32Float;
+    case ast::TexelFormat::kNone:
+      return ResourceBinding::TexelFormat::kNone;
+  }
+  return ResourceBinding::TexelFormat::kNone;
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/resource_binding.h b/src/tint/inspector/resource_binding.h
new file mode 100644
index 0000000..f2c74d7
--- /dev/null
+++ b/src/tint/inspector/resource_binding.h
@@ -0,0 +1,129 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_
+#define SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_
+
+#include <cstdint>
+
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/texture.h"
+
+namespace tint {
+namespace inspector {
+
+/// Container for information about how a resource is bound
+struct ResourceBinding {
+  /// The dimensionality of a texture
+  enum class TextureDimension {
+    /// Invalid texture
+    kNone = -1,
+    /// 1 dimensional texture
+    k1d,
+    /// 2 dimensional texture
+    k2d,
+    /// 2 dimensional array texture
+    k2dArray,
+    /// 3 dimensional texture
+    k3d,
+    /// cube texture
+    kCube,
+    /// cube array texture
+    kCubeArray,
+  };
+
+  /// Component type of the texture's data. Same as the Sampled Type parameter
+  /// in SPIR-V OpTypeImage.
+  enum class SampledKind { kUnknown = -1, kFloat, kUInt, kSInt };
+
+  /// Enumerator of texel image formats
+  enum class TexelFormat {
+    kNone = -1,
+
+    kRgba8Unorm,
+    kRgba8Snorm,
+    kRgba8Uint,
+    kRgba8Sint,
+    kRgba16Uint,
+    kRgba16Sint,
+    kRgba16Float,
+    kR32Uint,
+    kR32Sint,
+    kR32Float,
+    kRg32Uint,
+    kRg32Sint,
+    kRg32Float,
+    kRgba32Uint,
+    kRgba32Sint,
+    kRgba32Float,
+  };
+
+  /// kXXX maps to entries returned by GetXXXResourceBindings call.
+  enum class ResourceType {
+    kUniformBuffer,
+    kStorageBuffer,
+    kReadOnlyStorageBuffer,
+    kSampler,
+    kComparisonSampler,
+    kSampledTexture,
+    kMultisampledTexture,
+    kWriteOnlyStorageTexture,
+    kDepthTexture,
+    kDepthMultisampledTexture,
+    kExternalTexture
+  };
+
+  /// Type of resource that is bound.
+  ResourceType resource_type;
+  /// Bind group the binding belongs
+  uint32_t bind_group;
+  /// Identifier to identify this binding within the bind group
+  uint32_t binding;
+  /// Size for this binding, in bytes, if defined.
+  uint64_t size;
+  /// Size for this binding without trailing structure padding, in bytes, if
+  /// defined.
+  uint64_t size_no_padding;
+  /// Dimensionality of this binding, if defined.
+  TextureDimension dim;
+  /// Kind of data being sampled, if defined.
+  SampledKind sampled_kind;
+  /// Format of data, if defined.
+  TexelFormat image_format;
+};
+
+/// Convert from internal ast::TextureDimension to public
+/// ResourceBinding::TextureDimension
+/// @param type_dim internal value to convert from
+/// @returns the publicly visible equivalent
+ResourceBinding::TextureDimension
+TypeTextureDimensionToResourceBindingTextureDimension(
+    const ast::TextureDimension& type_dim);
+
+/// Infer ResourceBinding::SampledKind for a given sem::Type
+/// @param base_type internal type to infer from
+/// @returns the publicly visible equivalent
+ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type);
+
+/// Convert from internal ast::TexelFormat to public
+/// ResourceBinding::TexelFormat
+/// @param image_format internal value to convert from
+/// @returns the publicly visible equivalent
+ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat(
+    const ast::TexelFormat& image_format);
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_
diff --git a/src/tint/inspector/scalar.cc b/src/tint/inspector/scalar.cc
new file mode 100644
index 0000000..fa276f3
--- /dev/null
+++ b/src/tint/inspector/scalar.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 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/inspector/scalar.h"
+
+namespace tint {
+namespace inspector {
+
+Scalar::Scalar() : type_(kNull) {}
+
+Scalar::Scalar(bool val) : type_(kBool) {
+  value_.b = val;
+}
+
+Scalar::Scalar(uint32_t val) : type_(kU32) {
+  value_.u = val;
+}
+
+Scalar::Scalar(int32_t val) : type_(kI32) {
+  value_.i = val;
+}
+
+Scalar::Scalar(float val) : type_(kFloat) {
+  value_.f = val;
+}
+
+bool Scalar::IsNull() const {
+  return type_ == kNull;
+}
+
+bool Scalar::IsBool() const {
+  return type_ == kBool;
+}
+
+bool Scalar::IsU32() const {
+  return type_ == kU32;
+}
+
+bool Scalar::IsI32() const {
+  return type_ == kI32;
+}
+
+bool Scalar::IsFloat() const {
+  return type_ == kFloat;
+}
+
+bool Scalar::AsBool() const {
+  return value_.b;
+}
+
+uint32_t Scalar::AsU32() const {
+  return value_.u;
+}
+
+int32_t Scalar::AsI32() const {
+  return value_.i;
+}
+
+float Scalar::AsFloat() const {
+  return value_.f;
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/scalar.h b/src/tint/inspector/scalar.h
new file mode 100644
index 0000000..d4d61a0
--- /dev/null
+++ b/src/tint/inspector/scalar.h
@@ -0,0 +1,80 @@
+// Copyright 2020 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_INSPECTOR_SCALAR_H_
+#define SRC_TINT_INSPECTOR_SCALAR_H_
+
+#include <cstdint>
+
+namespace tint {
+namespace inspector {
+
+/// Contains a literal scalar value
+class Scalar {
+ public:
+  /// Null Constructor
+  Scalar();
+  /// @param val literal scalar value to contain
+  explicit Scalar(bool val);
+  /// @param val literal scalar value to contain
+  explicit Scalar(uint32_t val);
+  /// @param val literal scalar value to contain
+  explicit Scalar(int32_t val);
+  /// @param val literal scalar value to contain
+  explicit Scalar(float val);
+
+  /// @returns true if this is a null
+  bool IsNull() const;
+  /// @returns true if this is a bool
+  bool IsBool() const;
+  /// @returns true if this is a unsigned integer.
+  bool IsU32() const;
+  /// @returns true if this is a signed integer.
+  bool IsI32() const;
+  /// @returns true if this is a float.
+  bool IsFloat() const;
+
+  /// @returns scalar value if bool, otherwise undefined behaviour.
+  bool AsBool() const;
+  /// @returns scalar value if unsigned integer, otherwise undefined behaviour.
+  uint32_t AsU32() const;
+  /// @returns scalar value if signed integer, otherwise undefined behaviour.
+  int32_t AsI32() const;
+  /// @returns scalar value if float, otherwise undefined behaviour.
+  float AsFloat() const;
+
+ private:
+  typedef enum {
+    kNull,
+    kBool,
+    kU32,
+    kI32,
+    kFloat,
+  } Type;
+
+  typedef union {
+    bool b;
+    uint32_t u;
+    int32_t i;
+    float f;
+  } Value;
+
+  Type type_;
+  Value value_;
+};
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_SCALAR_H_
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
new file mode 100644
index 0000000..301d328
--- /dev/null
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -0,0 +1,405 @@
+// 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/inspector/test_inspector_builder.h"
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace inspector {
+
+InspectorBuilder::InspectorBuilder() = default;
+InspectorBuilder::~InspectorBuilder() = default;
+
+void InspectorBuilder::MakeEmptyBodyFunction(std::string name,
+                                             ast::AttributeList attributes) {
+  Func(name, ast::VariableList(), ty.void_(), ast::StatementList{Return()},
+       attributes);
+}
+
+void InspectorBuilder::MakeCallerBodyFunction(std::string caller,
+                                              std::vector<std::string> callees,
+                                              ast::AttributeList attributes) {
+  ast::StatementList body;
+  body.reserve(callees.size() + 1);
+  for (auto callee : callees) {
+    body.push_back(CallStmt(Call(callee)));
+  }
+  body.push_back(Return());
+
+  Func(caller, ast::VariableList(), ty.void_(), body, attributes);
+}
+
+const ast::Struct* InspectorBuilder::MakeInOutStruct(
+    std::string name,
+    std::vector<std::tuple<std::string, uint32_t>> inout_vars) {
+  ast::StructMemberList members;
+  for (auto var : inout_vars) {
+    std::string member_name;
+    uint32_t location;
+    std::tie(member_name, location) = var;
+    members.push_back(
+        Member(member_name, ty.u32(), {Location(location), Flat()}));
+  }
+  return Structure(name, members);
+}
+
+const ast::Function* InspectorBuilder::MakePlainGlobalReferenceBodyFunction(
+    std::string func,
+    std::string var,
+    const ast::Type* type,
+    ast::AttributeList attributes) {
+  ast::StatementList stmts;
+  stmts.emplace_back(Decl(Var("local_" + var, type)));
+  stmts.emplace_back(Assign("local_" + var, var));
+  stmts.emplace_back(Return());
+
+  return Func(func, ast::VariableList(), ty.void_(), stmts, attributes);
+}
+
+bool InspectorBuilder::ContainsName(const std::vector<StageVariable>& vec,
+                                    const std::string& name) {
+  for (auto& s : vec) {
+    if (s.name == name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::string InspectorBuilder::StructMemberName(size_t idx,
+                                               const ast::Type* type) {
+  return std::to_string(idx) + type->FriendlyName(Symbols());
+}
+
+const ast::Struct* InspectorBuilder::MakeStructType(
+    const std::string& name,
+    std::vector<const ast::Type*> member_types,
+    bool is_block) {
+  ast::StructMemberList members;
+  for (auto* type : member_types) {
+    members.push_back(MakeStructMember(members.size(), type, {}));
+  }
+  return MakeStructTypeFromMembers(name, std::move(members), is_block);
+}
+
+const ast::Struct* InspectorBuilder::MakeStructTypeFromMembers(
+    const std::string& name,
+    ast::StructMemberList members,
+    bool is_block) {
+  ast::AttributeList attrs;
+  if (is_block) {
+    attrs.push_back(create<ast::StructBlockAttribute>());
+  }
+  return Structure(name, std::move(members), attrs);
+}
+
+const ast::StructMember* InspectorBuilder::MakeStructMember(
+    size_t index,
+    const ast::Type* type,
+    ast::AttributeList attributes) {
+  return Member(StructMemberName(index, type), type, std::move(attributes));
+}
+
+const ast::Struct* InspectorBuilder::MakeUniformBufferType(
+    const std::string& name,
+    std::vector<const ast::Type*> member_types) {
+  return MakeStructType(name, member_types, true);
+}
+
+std::function<const ast::TypeName*()> InspectorBuilder::MakeStorageBufferTypes(
+    const std::string& name,
+    std::vector<const ast::Type*> member_types) {
+  MakeStructType(name, member_types, true);
+  return [this, name] { return ty.type_name(name); };
+}
+
+void InspectorBuilder::AddUniformBuffer(const std::string& name,
+                                        const ast::Type* type,
+                                        uint32_t group,
+                                        uint32_t binding) {
+  Global(name, type, ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+void InspectorBuilder::AddWorkgroupStorage(const std::string& name,
+                                           const ast::Type* type) {
+  Global(name, type, ast::StorageClass::kWorkgroup);
+}
+
+void InspectorBuilder::AddStorageBuffer(const std::string& name,
+                                        const ast::Type* type,
+                                        ast::Access access,
+                                        uint32_t group,
+                                        uint32_t binding) {
+  Global(name, type, ast::StorageClass::kStorage, access,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
+    std::string func_name,
+    std::string struct_name,
+    std::vector<std::tuple<size_t, const ast::Type*>> members) {
+  ast::StatementList stmts;
+  for (auto member : members) {
+    size_t member_idx;
+    const ast::Type* member_type;
+    std::tie(member_idx, member_type) = member;
+    std::string member_name = StructMemberName(member_idx, member_type);
+
+    stmts.emplace_back(Decl(Var("local" + member_name, member_type)));
+  }
+
+  for (auto member : members) {
+    size_t member_idx;
+    const ast::Type* member_type;
+    std::tie(member_idx, member_type) = member;
+    std::string member_name = StructMemberName(member_idx, member_type);
+
+    stmts.emplace_back(Assign("local" + member_name,
+                              MemberAccessor(struct_name, member_name)));
+  }
+
+  stmts.emplace_back(Return());
+
+  Func(func_name, ast::VariableList(), ty.void_(), stmts, ast::AttributeList{});
+}
+
+void InspectorBuilder::AddSampler(const std::string& name,
+                                  uint32_t group,
+                                  uint32_t binding) {
+  Global(name, sampler_type(),
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+void InspectorBuilder::AddComparisonSampler(const std::string& name,
+                                            uint32_t group,
+                                            uint32_t binding) {
+  Global(name, comparison_sampler_type(),
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+void InspectorBuilder::AddResource(const std::string& name,
+                                   const ast::Type* type,
+                                   uint32_t group,
+                                   uint32_t binding) {
+  Global(name, type,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+void InspectorBuilder::AddGlobalVariable(const std::string& name,
+                                         const ast::Type* type) {
+  Global(name, type, ast::StorageClass::kPrivate);
+}
+
+const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction(
+    const std::string& func_name,
+    const std::string& texture_name,
+    const std::string& sampler_name,
+    const std::string& coords_name,
+    const ast::Type* base_type,
+    ast::AttributeList attributes) {
+  std::string result_name = "sampler_result";
+
+  ast::StatementList stmts;
+  stmts.emplace_back(Decl(Var(result_name, ty.vec(base_type, 4))));
+
+  stmts.emplace_back(Assign(result_name, Call("textureSample", texture_name,
+                                              sampler_name, coords_name)));
+  stmts.emplace_back(Return());
+
+  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
+}
+
+const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction(
+    const std::string& func_name,
+    const std::string& texture_name,
+    const std::string& sampler_name,
+    const std::string& coords_name,
+    const std::string& array_index,
+    const ast::Type* base_type,
+    ast::AttributeList attributes) {
+  std::string result_name = "sampler_result";
+
+  ast::StatementList stmts;
+
+  stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4))));
+
+  stmts.emplace_back(
+      Assign("sampler_result", Call("textureSample", texture_name, sampler_name,
+                                    coords_name, array_index)));
+  stmts.emplace_back(Return());
+
+  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
+}
+
+const ast::Function*
+InspectorBuilder::MakeComparisonSamplerReferenceBodyFunction(
+    const std::string& func_name,
+    const std::string& texture_name,
+    const std::string& sampler_name,
+    const std::string& coords_name,
+    const std::string& depth_name,
+    const ast::Type* base_type,
+    ast::AttributeList attributes) {
+  std::string result_name = "sampler_result";
+
+  ast::StatementList stmts;
+
+  stmts.emplace_back(Decl(Var("sampler_result", base_type)));
+  stmts.emplace_back(
+      Assign("sampler_result", Call("textureSampleCompare", texture_name,
+                                    sampler_name, coords_name, depth_name)));
+  stmts.emplace_back(Return());
+
+  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
+}
+
+const ast::Type* InspectorBuilder::GetBaseType(
+    ResourceBinding::SampledKind sampled_kind) {
+  switch (sampled_kind) {
+    case ResourceBinding::SampledKind::kFloat:
+      return ty.f32();
+    case ResourceBinding::SampledKind::kSInt:
+      return ty.i32();
+    case ResourceBinding::SampledKind::kUInt:
+      return ty.u32();
+    default:
+      return nullptr;
+  }
+}
+
+const ast::Type* InspectorBuilder::GetCoordsType(ast::TextureDimension dim,
+                                                 const ast::Type* scalar) {
+  switch (dim) {
+    case ast::TextureDimension::k1d:
+      return scalar;
+    case ast::TextureDimension::k2d:
+    case ast::TextureDimension::k2dArray:
+      return create<ast::Vector>(scalar, 2);
+    case ast::TextureDimension::k3d:
+    case ast::TextureDimension::kCube:
+    case ast::TextureDimension::kCubeArray:
+      return create<ast::Vector>(scalar, 3);
+    default:
+      [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
+  }
+  return nullptr;
+}
+
+const ast::Type* InspectorBuilder::MakeStorageTextureTypes(
+    ast::TextureDimension dim,
+    ast::TexelFormat format) {
+  return ty.storage_texture(dim, format, ast::Access::kWrite);
+}
+
+void InspectorBuilder::AddStorageTexture(const std::string& name,
+                                         const ast::Type* type,
+                                         uint32_t group,
+                                         uint32_t binding) {
+  Global(name, type,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(binding),
+             create<ast::GroupAttribute>(group),
+         });
+}
+
+const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction(
+    const std::string& func_name,
+    const std::string& st_name,
+    const ast::Type* dim_type,
+    ast::AttributeList attributes) {
+  ast::StatementList stmts;
+
+  stmts.emplace_back(Decl(Var("dim", dim_type)));
+  stmts.emplace_back(Assign("dim", Call("textureDimensions", st_name)));
+  stmts.emplace_back(Return());
+
+  return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes);
+}
+
+std::function<const ast::Type*()> InspectorBuilder::GetTypeFunction(
+    ComponentType component,
+    CompositionType composition) {
+  std::function<const ast::Type*()> func;
+  switch (component) {
+    case ComponentType::kFloat:
+      func = [this]() -> const ast::Type* { return ty.f32(); };
+      break;
+    case ComponentType::kSInt:
+      func = [this]() -> const ast::Type* { return ty.i32(); };
+      break;
+    case ComponentType::kUInt:
+      func = [this]() -> const ast::Type* { return ty.u32(); };
+      break;
+    case ComponentType::kUnknown:
+      return []() -> const ast::Type* { return nullptr; };
+  }
+
+  uint32_t n;
+  switch (composition) {
+    case CompositionType::kScalar:
+      return func;
+    case CompositionType::kVec2:
+      n = 2;
+      break;
+    case CompositionType::kVec3:
+      n = 3;
+      break;
+    case CompositionType::kVec4:
+      n = 4;
+      break;
+    default:
+      return []() -> ast::Type* { return nullptr; };
+  }
+
+  return [this, func, n]() -> const ast::Type* { return ty.vec(func(), n); };
+}
+
+Inspector& InspectorBuilder::Build() {
+  if (inspector_) {
+    return *inspector_;
+  }
+  program_ = std::make_unique<Program>(std::move(*this));
+  [&]() {
+    ASSERT_TRUE(program_->IsValid())
+        << diag::Formatter().format(program_->Diagnostics());
+  }();
+  inspector_ = std::make_unique<Inspector>(program_.get());
+  return *inspector_;
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/test_inspector_builder.h b/src/tint/inspector/test_inspector_builder.h
new file mode 100644
index 0000000..4101505
--- /dev/null
+++ b/src/tint/inspector/test_inspector_builder.h
@@ -0,0 +1,391 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
+#define SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/variable.h"
+#include "tint/tint.h"
+
+namespace tint {
+namespace inspector {
+
+/// Utility class for building programs in inspector tests
+class InspectorBuilder : public ProgramBuilder {
+ public:
+  InspectorBuilder();
+  ~InspectorBuilder() override;
+
+  /// Generates an empty function
+  /// @param name name of the function created
+  /// @param attributes the function attributes
+  void MakeEmptyBodyFunction(std::string name, ast::AttributeList attributes);
+
+  /// Generates a function that calls other functions
+  /// @param caller name of the function created
+  /// @param callees names of the functions to be called
+  /// @param attributes the function attributes
+  void MakeCallerBodyFunction(std::string caller,
+                              std::vector<std::string> callees,
+                              ast::AttributeList attributes);
+
+  /// Generates a struct that contains user-defined IO members
+  /// @param name the name of the generated struct
+  /// @param inout_vars tuples of {name, loc} that will be the struct members
+  /// @returns a structure object
+  const ast::Struct* MakeInOutStruct(
+      std::string name,
+      std::vector<std::tuple<std::string, uint32_t>> inout_vars);
+
+  // TODO(crbug.com/tint/697): Remove this.
+  /// Add In/Out variables to the global variables
+  /// @param inout_vars tuples of {in, out} that will be added as entries to the
+  ///                   global variables
+  void AddInOutVariables(
+      std::vector<std::tuple<std::string, std::string>> inout_vars);
+
+  // TODO(crbug.com/tint/697): Remove this.
+  /// Generates a function that references in/out variables
+  /// @param name name of the function created
+  /// @param inout_vars tuples of {in, out} that will be converted into out = in
+  ///                   calls in the function body
+  /// @param attributes the function attributes
+  void MakeInOutVariableBodyFunction(
+      std::string name,
+      std::vector<std::tuple<std::string, std::string>> inout_vars,
+      ast::AttributeList attributes);
+
+  // TODO(crbug.com/tint/697): Remove this.
+  /// Generates a function that references in/out variables and calls another
+  /// function.
+  /// @param caller name of the function created
+  /// @param callee name of the function to be called
+  /// @param inout_vars tuples of {in, out} that will be converted into out = in
+  ///                   calls in the function body
+  /// @param attributes the function attributes
+  /// @returns a function object
+  const ast::Function* MakeInOutVariableCallerBodyFunction(
+      std::string caller,
+      std::string callee,
+      std::vector<std::tuple<std::string, std::string>> inout_vars,
+      ast::AttributeList attributes);
+
+  /// Add a pipeline constant to the global variables, with a specific ID.
+  /// @param name name of the variable to add
+  /// @param id id number for the constant id
+  /// @param type type of the variable
+  /// @param constructor val to initialize the constant with, if NULL no
+  ///             constructor will be added.
+  /// @returns the constant that was created
+  const ast::Variable* AddOverridableConstantWithID(
+      std::string name,
+      uint32_t id,
+      const ast::Type* type,
+      const ast::Expression* constructor) {
+    return Override(name, type, constructor, {Id(id)});
+  }
+
+  /// Add a pipeline constant to the global variables, without a specific ID.
+  /// @param name name of the variable to add
+  /// @param type type of the variable
+  /// @param constructor val to initialize the constant with, if NULL no
+  ///             constructor will be added.
+  /// @returns the constant that was created
+  const ast::Variable* AddOverridableConstantWithoutID(
+      std::string name,
+      const ast::Type* type,
+      const ast::Expression* constructor) {
+    return Override(name, type, constructor);
+  }
+
+  /// Generates a function that references module-scoped, plain-typed constant
+  /// or variable.
+  /// @param func name of the function created
+  /// @param var name of the constant to be reference
+  /// @param type type of the const being referenced
+  /// @param attributes the function attributes
+  /// @returns a function object
+  const ast::Function* MakePlainGlobalReferenceBodyFunction(
+      std::string func,
+      std::string var,
+      const ast::Type* type,
+      ast::AttributeList attributes);
+
+  /// @param vec Vector of StageVariable to be searched
+  /// @param name Name to be searching for
+  /// @returns true if name is in vec, otherwise false
+  bool ContainsName(const std::vector<StageVariable>& vec,
+                    const std::string& name);
+
+  /// Builds a string for accessing a member in a generated struct
+  /// @param idx index of member
+  /// @param type type of member
+  /// @returns a string for the member
+  std::string StructMemberName(size_t idx, const ast::Type* type);
+
+  /// Generates a struct type
+  /// @param name name for the type
+  /// @param member_types a vector of member types
+  /// @param is_block whether or not to decorate as a Block
+  /// @returns a struct type
+  const ast::Struct* MakeStructType(const std::string& name,
+                                    std::vector<const ast::Type*> member_types,
+                                    bool is_block);
+
+  /// Generates a struct type from a list of member nodes.
+  /// @param name name for the struct type
+  /// @param members a vector of members
+  /// @param is_block whether or not to decorate as a Block
+  /// @returns a struct type
+  const ast::Struct* MakeStructTypeFromMembers(const std::string& name,
+                                               ast::StructMemberList members,
+                                               bool is_block);
+
+  /// Generates a struct member with a specified index and type.
+  /// @param index index of the field within the struct
+  /// @param type the type of the member field
+  /// @param attributes a list of attributes to apply to the member field
+  /// @returns a struct member
+  const ast::StructMember* MakeStructMember(size_t index,
+                                            const ast::Type* type,
+                                            ast::AttributeList attributes);
+
+  /// Generates types appropriate for using in an uniform buffer
+  /// @param name name for the type
+  /// @param member_types a vector of member types
+  /// @returns a struct type that has the layout for an uniform buffer.
+  const ast::Struct* MakeUniformBufferType(
+      const std::string& name,
+      std::vector<const ast::Type*> member_types);
+
+  /// Generates types appropriate for using in a storage buffer
+  /// @param name name for the type
+  /// @param member_types a vector of member types
+  /// @returns a function that returns the created structure.
+  std::function<const ast::TypeName*()> MakeStorageBufferTypes(
+      const std::string& name,
+      std::vector<const ast::Type*> member_types);
+
+  /// Adds an uniform buffer variable to the program
+  /// @param name the name of the variable
+  /// @param type the type to use
+  /// @param group the binding/group/ to use for the uniform buffer
+  /// @param binding the binding number to use for the uniform buffer
+  void AddUniformBuffer(const std::string& name,
+                        const ast::Type* type,
+                        uint32_t group,
+                        uint32_t binding);
+
+  /// Adds a workgroup storage variable to the program
+  /// @param name the name of the variable
+  /// @param type the type of the variable
+  void AddWorkgroupStorage(const std::string& name, const ast::Type* type);
+
+  /// Adds a storage buffer variable to the program
+  /// @param name the name of the variable
+  /// @param type the type to use
+  /// @param access the storage buffer access control
+  /// @param group the binding/group to use for the storage buffer
+  /// @param binding the binding number to use for the storage buffer
+  void AddStorageBuffer(const std::string& name,
+                        const ast::Type* type,
+                        ast::Access access,
+                        uint32_t group,
+                        uint32_t binding);
+
+  /// Generates a function that references a specific struct variable
+  /// @param func_name name of the function created
+  /// @param struct_name name of the struct variabler to be accessed
+  /// @param members list of members to access, by index and type
+  void MakeStructVariableReferenceBodyFunction(
+      std::string func_name,
+      std::string struct_name,
+      std::vector<std::tuple<size_t, const ast::Type*>> members);
+
+  /// Adds a regular sampler variable to the program
+  /// @param name the name of the variable
+  /// @param group the binding/group to use for the storage buffer
+  /// @param binding the binding number to use for the storage buffer
+  void AddSampler(const std::string& name, uint32_t group, uint32_t binding);
+
+  /// Adds a comparison sampler variable to the program
+  /// @param name the name of the variable
+  /// @param group the binding/group to use for the storage buffer
+  /// @param binding the binding number to use for the storage buffer
+  void AddComparisonSampler(const std::string& name,
+                            uint32_t group,
+                            uint32_t binding);
+
+  /// Adds a sampler or texture variable to the program
+  /// @param name the name of the variable
+  /// @param type the type to use
+  /// @param group the binding/group to use for the resource
+  /// @param binding the binding number to use for the resource
+  void AddResource(const std::string& name,
+                   const ast::Type* type,
+                   uint32_t group,
+                   uint32_t binding);
+
+  /// Add a module scope private variable to the progames
+  /// @param name the name of the variable
+  /// @param type the type to use
+  void AddGlobalVariable(const std::string& name, const ast::Type* type);
+
+  /// Generates a function that references a specific sampler variable
+  /// @param func_name name of the function created
+  /// @param texture_name name of the texture to be sampled
+  /// @param sampler_name name of the sampler to use
+  /// @param coords_name name of the coords variable to use
+  /// @param base_type sampler base type
+  /// @param attributes the function attributes
+  /// @returns a function that references all of the values specified
+  const ast::Function* MakeSamplerReferenceBodyFunction(
+      const std::string& func_name,
+      const std::string& texture_name,
+      const std::string& sampler_name,
+      const std::string& coords_name,
+      const ast::Type* base_type,
+      ast::AttributeList attributes);
+
+  /// Generates a function that references a specific sampler variable
+  /// @param func_name name of the function created
+  /// @param texture_name name of the texture to be sampled
+  /// @param sampler_name name of the sampler to use
+  /// @param coords_name name of the coords variable to use
+  /// @param array_index name of the array index variable to use
+  /// @param base_type sampler base type
+  /// @param attributes the function attributes
+  /// @returns a function that references all of the values specified
+  const ast::Function* MakeSamplerReferenceBodyFunction(
+      const std::string& func_name,
+      const std::string& texture_name,
+      const std::string& sampler_name,
+      const std::string& coords_name,
+      const std::string& array_index,
+      const ast::Type* base_type,
+      ast::AttributeList attributes);
+
+  /// Generates a function that references a specific comparison sampler
+  /// variable.
+  /// @param func_name name of the function created
+  /// @param texture_name name of the depth texture to  use
+  /// @param sampler_name name of the sampler to use
+  /// @param coords_name name of the coords variable to use
+  /// @param depth_name name of the depth reference to use
+  /// @param base_type sampler base type
+  /// @param attributes the function attributes
+  /// @returns a function that references all of the values specified
+  const ast::Function* MakeComparisonSamplerReferenceBodyFunction(
+      const std::string& func_name,
+      const std::string& texture_name,
+      const std::string& sampler_name,
+      const std::string& coords_name,
+      const std::string& depth_name,
+      const ast::Type* base_type,
+      ast::AttributeList attributes);
+
+  /// Gets an appropriate type for the data in a given texture type.
+  /// @param sampled_kind type of in the texture
+  /// @returns a pointer to a type appropriate for the coord param
+  const ast::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind);
+
+  /// Gets an appropriate type for the coords parameter depending the the
+  /// dimensionality of the texture being sampled.
+  /// @param dim dimensionality of the texture being sampled
+  /// @param scalar the scalar type
+  /// @returns a pointer to a type appropriate for the coord param
+  const ast::Type* GetCoordsType(ast::TextureDimension dim,
+                                 const ast::Type* scalar);
+
+  /// Generates appropriate types for a Read-Only StorageTexture
+  /// @param dim the texture dimension of the storage texture
+  /// @param format the texel format of the storage texture
+  /// @returns the storage texture type
+  const ast::Type* MakeStorageTextureTypes(ast::TextureDimension dim,
+                                           ast::TexelFormat format);
+
+  /// Adds a storage texture variable to the program
+  /// @param name the name of the variable
+  /// @param type the type to use
+  /// @param group the binding/group to use for the sampled texture
+  /// @param binding the binding57 number to use for the sampled texture
+  void AddStorageTexture(const std::string& name,
+                         const ast::Type* type,
+                         uint32_t group,
+                         uint32_t binding);
+
+  /// Generates a function that references a storage texture variable.
+  /// @param func_name name of the function created
+  /// @param st_name name of the storage texture to use
+  /// @param dim_type type expected by textureDimensons to return
+  /// @param attributes the function attributes
+  /// @returns a function that references all of the values specified
+  const ast::Function* MakeStorageTextureBodyFunction(
+      const std::string& func_name,
+      const std::string& st_name,
+      const ast::Type* dim_type,
+      ast::AttributeList attributes);
+
+  /// Get a generator function that returns a type appropriate for a stage
+  /// variable with the given combination of component and composition type.
+  /// @param component component type of the stage variable
+  /// @param composition composition type of the stage variable
+  /// @returns a generator function for the stage variable's type.
+  std::function<const ast::Type*()> GetTypeFunction(
+      ComponentType component,
+      CompositionType composition);
+
+  /// Build the Program given all of the previous methods called and return an
+  /// Inspector for it.
+  /// Should only be called once per test.
+  /// @returns a reference to the Inspector for the built Program.
+  Inspector& Build();
+
+  /// @returns the type for a SamplerKind::kSampler
+  const ast::Sampler* sampler_type() {
+    return ty.sampler(ast::SamplerKind::kSampler);
+  }
+
+  /// @returns the type for a SamplerKind::kComparison
+  const ast::Sampler* comparison_sampler_type() {
+    return ty.sampler(ast::SamplerKind::kComparisonSampler);
+  }
+
+ protected:
+  /// Program built by this builder.
+  std::unique_ptr<Program> program_;
+  /// Inspector for |program_|
+  std::unique_ptr<Inspector> inspector_;
+};
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
diff --git a/src/tint/inspector/test_inspector_runner.cc b/src/tint/inspector/test_inspector_runner.cc
new file mode 100644
index 0000000..5b937eb
--- /dev/null
+++ b/src/tint/inspector/test_inspector_runner.cc
@@ -0,0 +1,39 @@
+// 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/inspector/test_inspector_runner.h"
+
+namespace tint {
+namespace inspector {
+
+InspectorRunner::InspectorRunner() = default;
+InspectorRunner::~InspectorRunner() = default;
+
+Inspector& InspectorRunner::Initialize(std::string shader) {
+  if (inspector_) {
+    return *inspector_;
+  }
+
+  file_ = std::make_unique<Source::File>("test", shader);
+  program_ = std::make_unique<Program>(reader::wgsl::Parse(file_.get()));
+  [&]() {
+    ASSERT_TRUE(program_->IsValid())
+        << diag::Formatter().format(program_->Diagnostics());
+  }();
+  inspector_ = std::make_unique<Inspector>(program_.get());
+  return *inspector_;
+}
+
+}  // namespace inspector
+}  // namespace tint
diff --git a/src/tint/inspector/test_inspector_runner.h b/src/tint/inspector/test_inspector_runner.h
new file mode 100644
index 0000000..0d435d1
--- /dev/null
+++ b/src/tint/inspector/test_inspector_runner.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
+#define SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "tint/tint.h"
+
+namespace tint {
+namespace inspector {
+
+/// Utility class for running shaders in inspector tests
+class InspectorRunner {
+ public:
+  InspectorRunner();
+  virtual ~InspectorRunner();
+
+  /// Create a Program with Inspector from the provided WGSL shader.
+  /// Should only be called once per test.
+  /// @param shader a WGSL shader
+  /// @returns a reference to the Inspector for the built Program.
+  Inspector& Initialize(std::string shader);
+
+ protected:
+  /// File created from input shader and used to create Program.
+  std::unique_ptr<Source::File> file_;
+  /// Program created by this runner.
+  std::unique_ptr<Program> program_;
+  /// Inspector for |program_|
+  std::unique_ptr<Inspector> inspector_;
+};
+
+}  // namespace inspector
+}  // namespace tint
+
+#endif  // SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
diff --git a/src/tint/program.cc b/src/tint/program.cc
new file mode 100644
index 0000000..a6a6ab7
--- /dev/null
+++ b/src/tint/program.cc
@@ -0,0 +1,131 @@
+// 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/program.h"
+
+#include <utility>
+
+#include "src/tint/demangler.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/sem/expression.h"
+
+namespace tint {
+namespace {
+
+std::string DefaultPrinter(const Program*) {
+  return "<no program printer assigned>";
+}
+
+}  // namespace
+
+Program::Printer Program::printer = DefaultPrinter;
+
+Program::Program() = default;
+
+Program::Program(Program&& program)
+    : id_(std::move(program.id_)),
+      types_(std::move(program.types_)),
+      ast_nodes_(std::move(program.ast_nodes_)),
+      sem_nodes_(std::move(program.sem_nodes_)),
+      ast_(std::move(program.ast_)),
+      sem_(std::move(program.sem_)),
+      symbols_(std::move(program.symbols_)),
+      diagnostics_(std::move(program.diagnostics_)),
+      is_valid_(program.is_valid_) {
+  program.AssertNotMoved();
+  program.moved_ = true;
+}
+
+Program::Program(ProgramBuilder&& builder) {
+  id_ = builder.ID();
+
+  is_valid_ = builder.IsValid();
+  if (builder.ResolveOnBuild() && builder.IsValid()) {
+    resolver::Resolver resolver(&builder);
+    if (!resolver.Resolve()) {
+      is_valid_ = false;
+    }
+  }
+
+  // The above must be called *before* the calls to std::move() below
+  types_ = std::move(builder.Types());
+  ast_nodes_ = std::move(builder.ASTNodes());
+  sem_nodes_ = std::move(builder.SemNodes());
+  ast_ = &builder.AST();  // ast::Module is actually a heap allocation.
+  sem_ = std::move(builder.Sem());
+  symbols_ = std::move(builder.Symbols());
+  diagnostics_.add(std::move(builder.Diagnostics()));
+  builder.MarkAsMoved();
+
+  if (!is_valid_ && !diagnostics_.contains_errors()) {
+    // If the builder claims to be invalid, then we really should have an error
+    // message generated. If we find a situation where the program is not valid
+    // and there are no errors reported, add one here.
+    diagnostics_.add_error(diag::System::Program, "invalid program generated");
+  }
+}
+
+Program::~Program() = default;
+
+Program& Program::operator=(Program&& program) {
+  program.AssertNotMoved();
+  program.moved_ = true;
+  moved_ = false;
+  id_ = std::move(program.id_);
+  types_ = std::move(program.types_);
+  ast_nodes_ = std::move(program.ast_nodes_);
+  sem_nodes_ = std::move(program.sem_nodes_);
+  ast_ = std::move(program.ast_);
+  sem_ = std::move(program.sem_);
+  symbols_ = std::move(program.symbols_);
+  diagnostics_ = std::move(program.diagnostics_);
+  is_valid_ = program.is_valid_;
+  return *this;
+}
+
+Program Program::Clone() const {
+  AssertNotMoved();
+  return Program(CloneAsBuilder());
+}
+
+ProgramBuilder Program::CloneAsBuilder() const {
+  AssertNotMoved();
+  ProgramBuilder out;
+  CloneContext(&out, this).Clone();
+  return out;
+}
+
+bool Program::IsValid() const {
+  AssertNotMoved();
+  return is_valid_;
+}
+
+const sem::Type* Program::TypeOf(const ast::Expression* expr) const {
+  auto* sem = Sem().Get(expr);
+  return sem ? sem->Type() : nullptr;
+}
+
+const sem::Type* Program::TypeOf(const ast::Type* type) const {
+  return Sem().Get(type);
+}
+
+const sem::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
+  return Sem().Get(type_decl);
+}
+
+void Program::AssertNotMoved() const {
+  TINT_ASSERT(Program, !moved_);
+}
+
+}  // namespace tint
diff --git a/src/tint/program.h b/src/tint/program.h
new file mode 100644
index 0000000..9531ca4
--- /dev/null
+++ b/src/tint/program.h
@@ -0,0 +1,180 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_PROGRAM_H_
+#define SRC_TINT_PROGRAM_H_
+
+#include <string>
+#include <unordered_set>
+
+#include "src/tint/ast/function.h"
+#include "src/tint/program_id.h"
+#include "src/tint/sem/info.h"
+#include "src/tint/sem/type_manager.h"
+#include "src/tint/symbol_table.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace ast {
+
+class Module;
+
+}  // namespace ast
+
+/// Program holds the AST, Type information and SymbolTable for a tint program.
+class Program {
+ public:
+  /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node>
+  using ASTNodeAllocator = BlockAllocator<ast::Node>;
+
+  /// SemNodeAllocator is an alias to BlockAllocator<sem::Node>
+  using SemNodeAllocator = BlockAllocator<sem::Node>;
+
+  /// Constructor
+  Program();
+
+  /// Move constructor
+  /// @param rhs the Program to move
+  Program(Program&& rhs);
+
+  /// Move constructor from builder
+  /// @param builder the builder used to construct the program
+  explicit Program(ProgramBuilder&& builder);
+
+  /// Destructor
+  ~Program();
+
+  /// Move assignment operator
+  /// @param rhs the Program to move
+  /// @return this Program
+  Program& operator=(Program&& rhs);
+
+  /// @returns the unique identifier for this program
+  ProgramID ID() const { return id_; }
+
+  /// @returns a reference to the program's types
+  const sem::Manager& Types() const {
+    AssertNotMoved();
+    return types_;
+  }
+
+  /// @returns a reference to the program's AST nodes storage
+  const ASTNodeAllocator& ASTNodes() const {
+    AssertNotMoved();
+    return ast_nodes_;
+  }
+
+  /// @returns a reference to the program's semantic nodes storage
+  const SemNodeAllocator& SemNodes() const {
+    AssertNotMoved();
+    return sem_nodes_;
+  }
+
+  /// @returns a reference to the program's AST root Module
+  const ast::Module& AST() const {
+    AssertNotMoved();
+    return *ast_;
+  }
+
+  /// @returns a reference to the program's semantic info
+  const sem::Info& Sem() const {
+    AssertNotMoved();
+    return sem_;
+  }
+
+  /// @returns a reference to the program's SymbolTable
+  const SymbolTable& Symbols() const {
+    AssertNotMoved();
+    return symbols_;
+  }
+
+  /// @returns a reference to the program's diagnostics
+  const diag::List& Diagnostics() const {
+    AssertNotMoved();
+    return diagnostics_;
+  }
+
+  /// Performs a deep clone of this program.
+  /// The returned Program will contain no pointers to objects owned by this
+  /// Program, and so after calling, this Program can be safely destructed.
+  /// @return a new Program copied from this Program
+  Program Clone() const;
+
+  /// Performs a deep clone of this Program's AST nodes, types and symbols into
+  /// a new ProgramBuilder. Semantic nodes are not cloned, as these will be
+  /// rebuilt when the ProgramBuilder builds its Program.
+  /// The returned ProgramBuilder will contain no pointers to objects owned by
+  /// this Program, and so after calling, this Program can be safely destructed.
+  /// @return a new ProgramBuilder copied from this Program
+  ProgramBuilder CloneAsBuilder() const;
+
+  /// @returns true if the program has no error diagnostics and is not missing
+  /// information
+  bool IsValid() const;
+
+  /// Helper for returning the resolved semantic type of the expression `expr`.
+  /// @param expr the AST expression
+  /// @return the resolved semantic type for the expression, or nullptr if the
+  /// expression has no resolved type.
+  const sem::Type* TypeOf(const ast::Expression* expr) const;
+
+  /// Helper for returning the resolved semantic type of the AST type `type`.
+  /// @param type the AST type
+  /// @return the resolved semantic type for the type, or nullptr if the type
+  /// has no resolved type.
+  const sem::Type* TypeOf(const ast::Type* type) const;
+
+  /// Helper for returning the resolved semantic type of the AST type
+  /// declaration `type_decl`.
+  /// @param type_decl the AST type declaration
+  /// @return the resolved semantic type for the type declaration, or nullptr if
+  /// the type declaration has no resolved type.
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
+
+  /// A function that can be used to print a program
+  using Printer = std::string (*)(const Program*);
+
+  /// The Program printer used for testing and debugging.
+  static Printer printer;
+
+ private:
+  Program(const Program&) = delete;
+
+  /// Asserts that the program has not been moved.
+  void AssertNotMoved() const;
+
+  ProgramID id_;
+  sem::Manager types_;
+  ASTNodeAllocator ast_nodes_;
+  SemNodeAllocator sem_nodes_;
+  ast::Module* ast_ = nullptr;
+  sem::Info sem_;
+  SymbolTable symbols_{id_};
+  diag::List diagnostics_;
+  bool is_valid_ = false;  // Not valid until it is built
+  bool moved_ = false;
+};
+
+/// @param program the Program
+/// @returns the ProgramID of the Program
+inline ProgramID ProgramIDOf(const Program* program) {
+  return program->ID();
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_PROGRAM_H_
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
new file mode 100644
index 0000000..c2f58ec
--- /dev/null
+++ b/src/tint/program_builder.cc
@@ -0,0 +1,138 @@
+// 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/program_builder.h"
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/debug.h"
+#include "src/tint/demangler.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+
+ProgramBuilder::VarOptionals::~VarOptionals() = default;
+
+ProgramBuilder::ProgramBuilder()
+    : id_(ProgramID::New()),
+      ast_(ast_nodes_.Create<ast::Module>(id_, Source{})) {}
+
+ProgramBuilder::ProgramBuilder(ProgramBuilder&& rhs)
+    : id_(std::move(rhs.id_)),
+      types_(std::move(rhs.types_)),
+      ast_nodes_(std::move(rhs.ast_nodes_)),
+      sem_nodes_(std::move(rhs.sem_nodes_)),
+      ast_(rhs.ast_),
+      sem_(std::move(rhs.sem_)),
+      symbols_(std::move(rhs.symbols_)),
+      diagnostics_(std::move(rhs.diagnostics_)) {
+  rhs.MarkAsMoved();
+}
+
+ProgramBuilder::~ProgramBuilder() = default;
+
+ProgramBuilder& ProgramBuilder::operator=(ProgramBuilder&& rhs) {
+  rhs.MarkAsMoved();
+  AssertNotMoved();
+  id_ = std::move(rhs.id_);
+  types_ = std::move(rhs.types_);
+  ast_nodes_ = std::move(rhs.ast_nodes_);
+  sem_nodes_ = std::move(rhs.sem_nodes_);
+  ast_ = rhs.ast_;
+  sem_ = std::move(rhs.sem_);
+  symbols_ = std::move(rhs.symbols_);
+  diagnostics_ = std::move(rhs.diagnostics_);
+
+  return *this;
+}
+
+ProgramBuilder ProgramBuilder::Wrap(const Program* program) {
+  ProgramBuilder builder;
+  builder.id_ = program->ID();
+  builder.types_ = sem::Manager::Wrap(program->Types());
+  builder.ast_ = builder.create<ast::Module>(
+      program->AST().source, program->AST().GlobalDeclarations());
+  builder.sem_ = sem::Info::Wrap(program->Sem());
+  builder.symbols_ = program->Symbols();
+  builder.diagnostics_ = program->Diagnostics();
+  return builder;
+}
+
+bool ProgramBuilder::IsValid() const {
+  return !diagnostics_.contains_errors();
+}
+
+void ProgramBuilder::MarkAsMoved() {
+  AssertNotMoved();
+  moved_ = true;
+}
+
+void ProgramBuilder::AssertNotMoved() const {
+  if (moved_) {
+    TINT_ICE(ProgramBuilder, const_cast<ProgramBuilder*>(this)->diagnostics_)
+        << "Attempting to use ProgramBuilder after it has been moved";
+  }
+}
+
+const sem::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
+  auto* sem = Sem().Get(expr);
+  return sem ? sem->Type() : nullptr;
+}
+
+const sem::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const {
+  auto* sem = Sem().Get(var);
+  return sem ? sem->Type() : nullptr;
+}
+
+const sem::Type* ProgramBuilder::TypeOf(const ast::Type* type) const {
+  return Sem().Get(type);
+}
+
+const sem::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
+  return Sem().Get(type_decl);
+}
+
+const ast::TypeName* ProgramBuilder::TypesBuilder::Of(
+    const ast::TypeDecl* decl) const {
+  return type_name(decl->name);
+}
+
+ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
+
+const ast::Statement* ProgramBuilder::WrapInStatement(
+    const ast::Expression* expr) {
+  // Create a temporary variable of inferred type from expr.
+  return Decl(Const(symbols_.New(), nullptr, expr));
+}
+
+const ast::VariableDeclStatement* ProgramBuilder::WrapInStatement(
+    const ast::Variable* v) {
+  return create<ast::VariableDeclStatement>(v);
+}
+
+const ast::Statement* ProgramBuilder::WrapInStatement(
+    const ast::Statement* stmt) {
+  return stmt;
+}
+
+const ast::Function* ProgramBuilder::WrapInFunction(
+    const ast::StatementList stmts) {
+  return Func("test_function", {}, ty.void_(), std::move(stmts),
+              {create<ast::StageAttribute>(ast::PipelineStage::kCompute),
+               WorkgroupSize(1, 1, 1)});
+}
+
+}  // namespace tint
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
new file mode 100644
index 0000000..270c293
--- /dev/null
+++ b/src/tint/program_builder.h
@@ -0,0 +1,2666 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_PROGRAM_BUILDER_H_
+#define SRC_TINT_PROGRAM_BUILDER_H_
+
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/atomic.h"
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/binding_attribute.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_expression.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/case_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/depth_multisampled_texture.h"
+#include "src/tint/ast/depth_texture.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/external_texture.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/index_accessor_expression.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/invariant_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/member_accessor_expression.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/multisampled_texture.h"
+#include "src/tint/ast/phony_expression.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/sampled_texture.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/sint_literal_expression.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/stride_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/struct_member_align_attribute.h"
+#include "src/tint/ast/struct_member_offset_attribute.h"
+#include "src/tint/ast/struct_member_size_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/uint_literal_expression.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/ast/void.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/program.h"
+#include "src/tint/program_id.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/bool_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/matrix_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/pointer_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/u32_type.h"
+#include "src/tint/sem/vector_type.h"
+#include "src/tint/sem/void_type.h"
+
+#ifdef INCLUDE_TINT_TINT_H_
+#error "internal tint header being #included from tint.h"
+#endif
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class VariableDeclStatement;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+class CloneContext;
+
+/// ProgramBuilder is a mutable builder for a Program.
+/// To construct a Program, populate the builder and then `std::move` it to a
+/// Program.
+class ProgramBuilder {
+  /// A helper used to disable overloads if the first type in `TYPES` is a
+  /// Source. Used to avoid ambiguities in overloads that take a Source as the
+  /// first parameter and those that perfectly-forward the first argument.
+  template <typename... TYPES>
+  using DisableIfSource = traits::EnableIfIsNotType<
+      traits::Decay<traits::NthTypeOf<0, TYPES..., void>>,
+      Source>;
+
+  /// VarOptionals is a helper for accepting a number of optional, extra
+  /// arguments for Var() and Global().
+  struct VarOptionals {
+    template <typename... ARGS>
+    explicit VarOptionals(ARGS&&... args) {
+      Apply(std::forward<ARGS>(args)...);
+    }
+    ~VarOptionals();
+
+    ast::StorageClass storage = ast::StorageClass::kNone;
+    ast::Access access = ast::Access::kUndefined;
+    const ast::Expression* constructor = nullptr;
+    ast::AttributeList attributes = {};
+
+   private:
+    void Set(ast::StorageClass sc) { storage = sc; }
+    void Set(ast::Access ac) { access = ac; }
+    void Set(const ast::Expression* c) { constructor = c; }
+    void Set(const ast::AttributeList& l) { attributes = l; }
+
+    template <typename FIRST, typename... ARGS>
+    void Apply(FIRST&& first, ARGS&&... args) {
+      Set(std::forward<FIRST>(first));
+      Apply(std::forward<ARGS>(args)...);
+    }
+    void Apply() {}
+  };
+
+ public:
+  /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node>
+  using ASTNodeAllocator = BlockAllocator<ast::Node>;
+
+  /// SemNodeAllocator is an alias to BlockAllocator<sem::Node>
+  using SemNodeAllocator = BlockAllocator<sem::Node>;
+
+  /// `i32` is a type alias to `int`.
+  /// Useful for passing to template methods such as `vec2<i32>()` to imitate
+  /// WGSL syntax.
+  /// Note: this is intentionally not aliased to uint32_t as we want integer
+  /// literals passed to the builder to match WGSL's integer literal types.
+  using i32 = decltype(1);
+  /// `u32` is a type alias to `unsigned int`.
+  /// Useful for passing to template methods such as `vec2<u32>()` to imitate
+  /// WGSL syntax.
+  /// Note: this is intentionally not aliased to uint32_t as we want integer
+  /// literals passed to the builder to match WGSL's integer literal types.
+  using u32 = decltype(1u);
+  /// `f32` is a type alias to `float`
+  /// Useful for passing to template methods such as `vec2<f32>()` to imitate
+  /// WGSL syntax.
+  using f32 = float;
+
+  /// Constructor
+  ProgramBuilder();
+
+  /// Move constructor
+  /// @param rhs the builder to move
+  ProgramBuilder(ProgramBuilder&& rhs);
+
+  /// Destructor
+  virtual ~ProgramBuilder();
+
+  /// Move assignment operator
+  /// @param rhs the builder to move
+  /// @return this builder
+  ProgramBuilder& operator=(ProgramBuilder&& rhs);
+
+  /// Wrap returns a new ProgramBuilder wrapping the Program `program` without
+  /// making a deep clone of the Program contents.
+  /// ProgramBuilder returned by Wrap() is intended to temporarily extend an
+  /// existing immutable program.
+  /// As the returned ProgramBuilder wraps `program`, `program` must not be
+  /// destructed or assigned while using the returned ProgramBuilder.
+  /// TODO(bclayton) - Evaluate whether there are safer alternatives to this
+  /// function. See crbug.com/tint/460.
+  /// @param program the immutable Program to wrap
+  /// @return the ProgramBuilder that wraps `program`
+  static ProgramBuilder Wrap(const Program* program);
+
+  /// @returns the unique identifier for this program
+  ProgramID ID() const { return id_; }
+
+  /// @returns a reference to the program's types
+  sem::Manager& Types() {
+    AssertNotMoved();
+    return types_;
+  }
+
+  /// @returns a reference to the program's types
+  const sem::Manager& Types() const {
+    AssertNotMoved();
+    return types_;
+  }
+
+  /// @returns a reference to the program's AST nodes storage
+  ASTNodeAllocator& ASTNodes() {
+    AssertNotMoved();
+    return ast_nodes_;
+  }
+
+  /// @returns a reference to the program's AST nodes storage
+  const ASTNodeAllocator& ASTNodes() const {
+    AssertNotMoved();
+    return ast_nodes_;
+  }
+
+  /// @returns a reference to the program's semantic nodes storage
+  SemNodeAllocator& SemNodes() {
+    AssertNotMoved();
+    return sem_nodes_;
+  }
+
+  /// @returns a reference to the program's semantic nodes storage
+  const SemNodeAllocator& SemNodes() const {
+    AssertNotMoved();
+    return sem_nodes_;
+  }
+
+  /// @returns a reference to the program's AST root Module
+  ast::Module& AST() {
+    AssertNotMoved();
+    return *ast_;
+  }
+
+  /// @returns a reference to the program's AST root Module
+  const ast::Module& AST() const {
+    AssertNotMoved();
+    return *ast_;
+  }
+
+  /// @returns a reference to the program's semantic info
+  sem::Info& Sem() {
+    AssertNotMoved();
+    return sem_;
+  }
+
+  /// @returns a reference to the program's semantic info
+  const sem::Info& Sem() const {
+    AssertNotMoved();
+    return sem_;
+  }
+
+  /// @returns a reference to the program's SymbolTable
+  SymbolTable& Symbols() {
+    AssertNotMoved();
+    return symbols_;
+  }
+
+  /// @returns a reference to the program's SymbolTable
+  const SymbolTable& Symbols() const {
+    AssertNotMoved();
+    return symbols_;
+  }
+
+  /// @returns a reference to the program's diagnostics
+  diag::List& Diagnostics() {
+    AssertNotMoved();
+    return diagnostics_;
+  }
+
+  /// @returns a reference to the program's diagnostics
+  const diag::List& Diagnostics() const {
+    AssertNotMoved();
+    return diagnostics_;
+  }
+
+  /// Controls whether the Resolver will be run on the program when it is built.
+  /// @param enable the new flag value (defaults to true)
+  void SetResolveOnBuild(bool enable) { resolve_on_build_ = enable; }
+
+  /// @return true if the Resolver will be run on the program when it is
+  /// built.
+  bool ResolveOnBuild() const { return resolve_on_build_; }
+
+  /// @returns true if the program has no error diagnostics and is not missing
+  /// information
+  bool IsValid() const;
+
+  /// Creates a new ast::Node owned by the ProgramBuilder. When the
+  /// ProgramBuilder is destructed, the ast::Node will also be destructed.
+  /// @param source the Source of the node
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  traits::EnableIfIsType<T, ast::Node>* create(const Source& source,
+                                               ARGS&&... args) {
+    AssertNotMoved();
+    return ast_nodes_.Create<T>(id_, source, std::forward<ARGS>(args)...);
+  }
+
+  /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
+  /// Source as set by the last call to SetSource() as the only argument to the
+  /// constructor.
+  /// When the ProgramBuilder is destructed, the ast::Node will also be
+  /// destructed.
+  /// @returns the node pointer
+  template <typename T>
+  traits::EnableIfIsType<T, ast::Node>* create() {
+    AssertNotMoved();
+    return ast_nodes_.Create<T>(id_, source_);
+  }
+
+  /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
+  /// Source as set by the last call to SetSource() as the first argument to the
+  /// constructor.
+  /// When the ProgramBuilder is destructed, the ast::Node will also be
+  /// destructed.
+  /// @param arg0 the first arguments to pass to the type constructor
+  /// @param args the remaining arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename ARG0, typename... ARGS>
+  traits::EnableIf</* T is ast::Node and ARG0 is not Source */
+                   traits::IsTypeOrDerived<T, ast::Node> &&
+                       !traits::IsTypeOrDerived<ARG0, Source>,
+                   T>*
+  create(ARG0&& arg0, ARGS&&... args) {
+    AssertNotMoved();
+    return ast_nodes_.Create<T>(id_, source_, std::forward<ARG0>(arg0),
+                                std::forward<ARGS>(args)...);
+  }
+
+  /// Creates a new sem::Node owned by the ProgramBuilder.
+  /// When the ProgramBuilder is destructed, the sem::Node will also be
+  /// destructed.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> &&
+                       !traits::IsTypeOrDerived<T, sem::Type>,
+                   T>*
+  create(ARGS&&... args) {
+    AssertNotMoved();
+    return sem_nodes_.Create<T>(std::forward<ARGS>(args)...);
+  }
+
+  /// Creates a new sem::Type owned by the ProgramBuilder.
+  /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
+  /// 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.
+  /// @warning Use this method to acquire a type only if all of its type
+  /// information is provided in the constructor arguments `args`.<br>
+  /// If the type requires additional configuration after construction that
+  /// affect its fundamental type, build the type with `std::make_unique`, make
+  /// any necessary alterations and then call unique_type() instead.
+  /// @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)...);
+  }
+
+  /// Marks this builder as moved, preventing any further use of the builder.
+  void MarkAsMoved();
+
+  //////////////////////////////////////////////////////////////////////////////
+  // TypesBuilder
+  //////////////////////////////////////////////////////////////////////////////
+
+  /// TypesBuilder holds basic `tint` types and methods for constructing
+  /// complex types.
+  class TypesBuilder {
+   public:
+    /// Constructor
+    /// @param builder the program builder
+    explicit TypesBuilder(ProgramBuilder* builder);
+
+    /// @return the tint AST type for the C type `T`.
+    template <typename T>
+    const ast::Type* Of() const {
+      return CToAST<T>::get(this);
+    }
+
+    /// @returns a boolean type
+    const ast::Bool* bool_() const { return builder->create<ast::Bool>(); }
+
+    /// @param source the Source of the node
+    /// @returns a boolean type
+    const ast::Bool* bool_(const Source& source) const {
+      return builder->create<ast::Bool>(source);
+    }
+
+    /// @returns a f32 type
+    const ast::F32* f32() const { return builder->create<ast::F32>(); }
+
+    /// @param source the Source of the node
+    /// @returns a f32 type
+    const ast::F32* f32(const Source& source) const {
+      return builder->create<ast::F32>(source);
+    }
+
+    /// @returns a i32 type
+    const ast::I32* i32() const { return builder->create<ast::I32>(); }
+
+    /// @param source the Source of the node
+    /// @returns a i32 type
+    const ast::I32* i32(const Source& source) const {
+      return builder->create<ast::I32>(source);
+    }
+
+    /// @returns a u32 type
+    const ast::U32* u32() const { return builder->create<ast::U32>(); }
+
+    /// @param source the Source of the node
+    /// @returns a u32 type
+    const ast::U32* u32(const Source& source) const {
+      return builder->create<ast::U32>(source);
+    }
+
+    /// @returns a void type
+    const ast::Void* void_() const { return builder->create<ast::Void>(); }
+
+    /// @param source the Source of the node
+    /// @returns a void type
+    const ast::Void* void_(const Source& source) const {
+      return builder->create<ast::Void>(source);
+    }
+
+    /// @param type vector subtype
+    /// @param n vector width in elements
+    /// @return the tint AST type for a `n`-element vector of `type`.
+    const ast::Vector* vec(const ast::Type* type, uint32_t n) const {
+      return builder->create<ast::Vector>(type, n);
+    }
+
+    /// @param source the Source of the node
+    /// @param type vector subtype
+    /// @param n vector width in elements
+    /// @return the tint AST type for a `n`-element vector of `type`.
+    const ast::Vector* vec(const Source& source,
+                           const ast::Type* type,
+                           uint32_t n) const {
+      return builder->create<ast::Vector>(source, type, n);
+    }
+
+    /// @param type vector subtype
+    /// @return the tint AST type for a 2-element vector of `type`.
+    const ast::Vector* vec2(const ast::Type* type) const {
+      return vec(type, 2u);
+    }
+
+    /// @param type vector subtype
+    /// @return the tint AST type for a 3-element vector of `type`.
+    const ast::Vector* vec3(const ast::Type* type) const {
+      return vec(type, 3u);
+    }
+
+    /// @param type vector subtype
+    /// @return the tint AST type for a 4-element vector of `type`.
+    const ast::Vector* vec4(const ast::Type* type) const {
+      return vec(type, 4u);
+    }
+
+    /// @param n vector width in elements
+    /// @return the tint AST type for a `n`-element vector of `type`.
+    template <typename T>
+    const ast::Vector* vec(uint32_t n) const {
+      return vec(Of<T>(), n);
+    }
+
+    /// @return the tint AST type for a 2-element vector of the C type `T`.
+    template <typename T>
+    const ast::Vector* vec2() const {
+      return vec2(Of<T>());
+    }
+
+    /// @return the tint AST type for a 3-element vector of the C type `T`.
+    template <typename T>
+    const ast::Vector* vec3() const {
+      return vec3(Of<T>());
+    }
+
+    /// @return the tint AST type for a 4-element vector of the C type `T`.
+    template <typename T>
+    const ast::Vector* vec4() const {
+      return vec4(Of<T>());
+    }
+
+    /// @param type matrix subtype
+    /// @param columns number of columns for the matrix
+    /// @param rows number of rows for the matrix
+    /// @return the tint AST type for a matrix of `type`
+    const ast::Matrix* mat(const ast::Type* type,
+                           uint32_t columns,
+                           uint32_t rows) const {
+      return builder->create<ast::Matrix>(type, rows, columns);
+    }
+
+    /// @param source the Source of the node
+    /// @param type matrix subtype
+    /// @param columns number of columns for the matrix
+    /// @param rows number of rows for the matrix
+    /// @return the tint AST type for a matrix of `type`
+    const ast::Matrix* mat(const Source& source,
+                           const ast::Type* type,
+                           uint32_t columns,
+                           uint32_t rows) const {
+      return builder->create<ast::Matrix>(source, type, rows, columns);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 2x3 matrix of `type`.
+    const ast::Matrix* mat2x2(const ast::Type* type) const {
+      return mat(type, 2u, 2u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 2x3 matrix of `type`.
+    const ast::Matrix* mat2x3(const ast::Type* type) const {
+      return mat(type, 2u, 3u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 2x4 matrix of `type`.
+    const ast::Matrix* mat2x4(const ast::Type* type) const {
+      return mat(type, 2u, 4u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 3x2 matrix of `type`.
+    const ast::Matrix* mat3x2(const ast::Type* type) const {
+      return mat(type, 3u, 2u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 3x3 matrix of `type`.
+    const ast::Matrix* mat3x3(const ast::Type* type) const {
+      return mat(type, 3u, 3u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 3x4 matrix of `type`.
+    const ast::Matrix* mat3x4(const ast::Type* type) const {
+      return mat(type, 3u, 4u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 4x2 matrix of `type`.
+    const ast::Matrix* mat4x2(const ast::Type* type) const {
+      return mat(type, 4u, 2u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 4x3 matrix of `type`.
+    const ast::Matrix* mat4x3(const ast::Type* type) const {
+      return mat(type, 4u, 3u);
+    }
+
+    /// @param type matrix subtype
+    /// @return the tint AST type for a 4x4 matrix of `type`.
+    const ast::Matrix* mat4x4(const ast::Type* type) const {
+      return mat(type, 4u, 4u);
+    }
+
+    /// @param columns number of columns for the matrix
+    /// @param rows number of rows for the matrix
+    /// @return the tint AST type for a matrix of `type`
+    template <typename T>
+    const ast::Matrix* mat(uint32_t columns, uint32_t rows) const {
+      return mat(Of<T>(), columns, rows);
+    }
+
+    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat2x2() const {
+      return mat2x2(Of<T>());
+    }
+
+    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat2x3() const {
+      return mat2x3(Of<T>());
+    }
+
+    /// @return the tint AST type for a 2x4 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat2x4() const {
+      return mat2x4(Of<T>());
+    }
+
+    /// @return the tint AST type for a 3x2 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat3x2() const {
+      return mat3x2(Of<T>());
+    }
+
+    /// @return the tint AST type for a 3x3 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat3x3() const {
+      return mat3x3(Of<T>());
+    }
+
+    /// @return the tint AST type for a 3x4 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat3x4() const {
+      return mat3x4(Of<T>());
+    }
+
+    /// @return the tint AST type for a 4x2 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat4x2() const {
+      return mat4x2(Of<T>());
+    }
+
+    /// @return the tint AST type for a 4x3 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat4x3() const {
+      return mat4x3(Of<T>());
+    }
+
+    /// @return the tint AST type for a 4x4 matrix of the C type `T`.
+    template <typename T>
+    const ast::Matrix* mat4x4() const {
+      return mat4x4(Of<T>());
+    }
+
+    /// @param subtype the array element type
+    /// @param n the array size. nullptr represents a runtime-array
+    /// @param attrs the optional attributes for the array
+    /// @return the tint AST type for a array of size `n` of type `T`
+    template <typename EXPR = ast::Expression*>
+    const ast::Array* array(const ast::Type* subtype,
+                            EXPR&& n = nullptr,
+                            ast::AttributeList attrs = {}) const {
+      return builder->create<ast::Array>(
+          subtype, builder->Expr(std::forward<EXPR>(n)), attrs);
+    }
+
+    /// @param source the Source of the node
+    /// @param subtype the array element type
+    /// @param n the array size. nullptr represents a runtime-array
+    /// @param attrs the optional attributes for the array
+    /// @return the tint AST type for a array of size `n` of type `T`
+    template <typename EXPR = ast::Expression*>
+    const ast::Array* array(const Source& source,
+                            const ast::Type* subtype,
+                            EXPR&& n = nullptr,
+                            ast::AttributeList attrs = {}) const {
+      return builder->create<ast::Array>(
+          source, subtype, builder->Expr(std::forward<EXPR>(n)), attrs);
+    }
+
+    /// @param subtype the array element type
+    /// @param n the array size. nullptr represents a runtime-array
+    /// @param stride the array stride. 0 represents implicit stride
+    /// @return the tint AST type for a array of size `n` of type `T`
+    template <typename EXPR>
+    const ast::Array* array(const ast::Type* subtype,
+                            EXPR&& n,
+                            uint32_t stride) const {
+      ast::AttributeList attrs;
+      if (stride) {
+        attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
+      }
+      return array(subtype, std::forward<EXPR>(n), std::move(attrs));
+    }
+
+    /// @param source the Source of the node
+    /// @param subtype the array element type
+    /// @param n the array size. nullptr represents a runtime-array
+    /// @param stride the array stride. 0 represents implicit stride
+    /// @return the tint AST type for a array of size `n` of type `T`
+    template <typename EXPR>
+    const ast::Array* array(const Source& source,
+                            const ast::Type* subtype,
+                            EXPR&& n,
+                            uint32_t stride) const {
+      ast::AttributeList attrs;
+      if (stride) {
+        attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
+      }
+      return array(source, subtype, std::forward<EXPR>(n), std::move(attrs));
+    }
+
+    /// @return the tint AST type for a runtime-sized array of type `T`
+    template <typename T>
+    const ast::Array* array() const {
+      return array(Of<T>(), nullptr);
+    }
+
+    /// @return the tint AST type for an array of size `N` of type `T`
+    template <typename T, int N>
+    const ast::Array* array() const {
+      return array(Of<T>(), builder->Expr(N));
+    }
+
+    /// @param stride the array stride
+    /// @return the tint AST type for a runtime-sized array of type `T`
+    template <typename T>
+    const ast::Array* array(uint32_t stride) const {
+      return array(Of<T>(), nullptr, stride);
+    }
+
+    /// @param stride the array stride
+    /// @return the tint AST type for an array of size `N` of type `T`
+    template <typename T, int N>
+    const ast::Array* array(uint32_t stride) const {
+      return array(Of<T>(), builder->Expr(N), stride);
+    }
+
+    /// Creates a type name
+    /// @param name the name
+    /// @returns the type name
+    template <typename NAME>
+    const ast::TypeName* type_name(NAME&& name) const {
+      return builder->create<ast::TypeName>(
+          builder->Sym(std::forward<NAME>(name)));
+    }
+
+    /// Creates a type name
+    /// @param source the Source of the node
+    /// @param name the name
+    /// @returns the type name
+    template <typename NAME>
+    const ast::TypeName* type_name(const Source& source, NAME&& name) const {
+      return builder->create<ast::TypeName>(
+          source, builder->Sym(std::forward<NAME>(name)));
+    }
+
+    /// Creates an alias type
+    /// @param name the alias name
+    /// @param type the alias type
+    /// @returns the alias pointer
+    template <typename NAME>
+    const ast::Alias* alias(NAME&& name, const ast::Type* type) const {
+      auto sym = builder->Sym(std::forward<NAME>(name));
+      return builder->create<ast::Alias>(sym, type);
+    }
+
+    /// Creates an alias type
+    /// @param source the Source of the node
+    /// @param name the alias name
+    /// @param type the alias type
+    /// @returns the alias pointer
+    template <typename NAME>
+    const ast::Alias* alias(const Source& source,
+                            NAME&& name,
+                            const ast::Type* type) const {
+      auto sym = builder->Sym(std::forward<NAME>(name));
+      return builder->create<ast::Alias>(source, sym, type);
+    }
+
+    /// @param type the type of the pointer
+    /// @param storage_class the storage class of the pointer
+    /// @param access the optional access control of the pointer
+    /// @return the pointer to `type` with the given ast::StorageClass
+    const ast::Pointer* pointer(
+        const ast::Type* type,
+        ast::StorageClass storage_class,
+        ast::Access access = ast::Access::kUndefined) const {
+      return builder->create<ast::Pointer>(type, storage_class, access);
+    }
+
+    /// @param source the Source of the node
+    /// @param type the type of the pointer
+    /// @param storage_class the storage class of the pointer
+    /// @param access the optional access control of the pointer
+    /// @return the pointer to `type` with the given ast::StorageClass
+    const ast::Pointer* pointer(
+        const Source& source,
+        const ast::Type* type,
+        ast::StorageClass storage_class,
+        ast::Access access = ast::Access::kUndefined) const {
+      return builder->create<ast::Pointer>(source, type, storage_class, access);
+    }
+
+    /// @param storage_class the storage class of the pointer
+    /// @param access the optional access control of the pointer
+    /// @return the pointer to type `T` with the given ast::StorageClass.
+    template <typename T>
+    const ast::Pointer* pointer(
+        ast::StorageClass storage_class,
+        ast::Access access = ast::Access::kUndefined) const {
+      return pointer(Of<T>(), storage_class, access);
+    }
+
+    /// @param source the Source of the node
+    /// @param type the type of the atomic
+    /// @return the atomic to `type`
+    const ast::Atomic* atomic(const Source& source,
+                              const ast::Type* type) const {
+      return builder->create<ast::Atomic>(source, type);
+    }
+
+    /// @param type the type of the atomic
+    /// @return the atomic to `type`
+    const ast::Atomic* atomic(const ast::Type* type) const {
+      return builder->create<ast::Atomic>(type);
+    }
+
+    /// @return the atomic to type `T`
+    template <typename T>
+    const ast::Atomic* atomic() const {
+      return atomic(Of<T>());
+    }
+
+    /// @param kind the kind of sampler
+    /// @returns the sampler
+    const ast::Sampler* sampler(ast::SamplerKind kind) const {
+      return builder->create<ast::Sampler>(kind);
+    }
+
+    /// @param source the Source of the node
+    /// @param kind the kind of sampler
+    /// @returns the sampler
+    const ast::Sampler* sampler(const Source& source,
+                                ast::SamplerKind kind) const {
+      return builder->create<ast::Sampler>(source, kind);
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @returns the depth texture
+    const ast::DepthTexture* depth_texture(ast::TextureDimension dims) const {
+      return builder->create<ast::DepthTexture>(dims);
+    }
+
+    /// @param source the Source of the node
+    /// @param dims the dimensionality of the texture
+    /// @returns the depth texture
+    const ast::DepthTexture* depth_texture(const Source& source,
+                                           ast::TextureDimension dims) const {
+      return builder->create<ast::DepthTexture>(source, dims);
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @returns the multisampled depth texture
+    const ast::DepthMultisampledTexture* depth_multisampled_texture(
+        ast::TextureDimension dims) const {
+      return builder->create<ast::DepthMultisampledTexture>(dims);
+    }
+
+    /// @param source the Source of the node
+    /// @param dims the dimensionality of the texture
+    /// @returns the multisampled depth texture
+    const ast::DepthMultisampledTexture* depth_multisampled_texture(
+        const Source& source,
+        ast::TextureDimension dims) const {
+      return builder->create<ast::DepthMultisampledTexture>(source, dims);
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the sampled texture
+    const ast::SampledTexture* sampled_texture(ast::TextureDimension dims,
+                                               const ast::Type* subtype) const {
+      return builder->create<ast::SampledTexture>(dims, subtype);
+    }
+
+    /// @param source the Source of the node
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the sampled texture
+    const ast::SampledTexture* sampled_texture(const Source& source,
+                                               ast::TextureDimension dims,
+                                               const ast::Type* subtype) const {
+      return builder->create<ast::SampledTexture>(source, dims, subtype);
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the multisampled texture
+    const ast::MultisampledTexture* multisampled_texture(
+        ast::TextureDimension dims,
+        const ast::Type* subtype) const {
+      return builder->create<ast::MultisampledTexture>(dims, subtype);
+    }
+
+    /// @param source the Source of the node
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the multisampled texture
+    const ast::MultisampledTexture* multisampled_texture(
+        const Source& source,
+        ast::TextureDimension dims,
+        const ast::Type* subtype) const {
+      return builder->create<ast::MultisampledTexture>(source, dims, subtype);
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param format the texel format of the texture
+    /// @param access the access control of the texture
+    /// @returns the storage texture
+    const ast::StorageTexture* storage_texture(ast::TextureDimension dims,
+                                               ast::TexelFormat format,
+                                               ast::Access access) const {
+      auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
+      return builder->create<ast::StorageTexture>(dims, format, subtype,
+                                                  access);
+    }
+
+    /// @param source the Source of the node
+    /// @param dims the dimensionality of the texture
+    /// @param format the texel format of the texture
+    /// @param access the access control of the texture
+    /// @returns the storage texture
+    const ast::StorageTexture* storage_texture(const Source& source,
+                                               ast::TextureDimension dims,
+                                               ast::TexelFormat format,
+                                               ast::Access access) const {
+      auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
+      return builder->create<ast::StorageTexture>(source, dims, format, subtype,
+                                                  access);
+    }
+
+    /// @returns the external texture
+    const ast::ExternalTexture* external_texture() const {
+      return builder->create<ast::ExternalTexture>();
+    }
+
+    /// @param source the Source of the node
+    /// @returns the external texture
+    const ast::ExternalTexture* external_texture(const Source& source) const {
+      return builder->create<ast::ExternalTexture>(source);
+    }
+
+    /// Constructs a TypeName for the type declaration.
+    /// @param type the type
+    /// @return either type or a pointer to a new ast::TypeName
+    const ast::TypeName* Of(const ast::TypeDecl* type) const;
+
+    /// The ProgramBuilder
+    ProgramBuilder* const builder;
+
+   private:
+    /// CToAST<T> is specialized for various `T` types and each specialization
+    /// contains a single static `get()` method for obtaining the corresponding
+    /// AST type for the C type `T`.
+    /// `get()` has the signature:
+    ///    `static const ast::Type* get(Types* t)`
+    template <typename T>
+    struct CToAST {};
+  };
+
+  //////////////////////////////////////////////////////////////////////////////
+  // AST helper methods
+  //////////////////////////////////////////////////////////////////////////////
+
+  /// @return a new unnamed symbol
+  Symbol Sym() { return Symbols().New(); }
+
+  /// @param name the symbol string
+  /// @return a Symbol with the given name
+  Symbol Sym(const std::string& name) { return Symbols().Register(name); }
+
+  /// @param sym the symbol
+  /// @return `sym`
+  Symbol Sym(Symbol sym) { return sym; }
+
+  /// @param expr the expression
+  /// @return expr
+  template <typename T>
+  traits::EnableIfIsType<T, ast::Expression>* Expr(T* expr) {
+    return expr;
+  }
+
+  /// Passthrough for nullptr
+  /// @return nullptr
+  const ast::IdentifierExpression* Expr(std::nullptr_t) { return nullptr; }
+
+  /// @param source the source information
+  /// @param symbol the identifier symbol
+  /// @return an ast::IdentifierExpression with the given symbol
+  const ast::IdentifierExpression* Expr(const Source& source, Symbol symbol) {
+    return create<ast::IdentifierExpression>(source, symbol);
+  }
+
+  /// @param symbol the identifier symbol
+  /// @return an ast::IdentifierExpression with the given symbol
+  const ast::IdentifierExpression* Expr(Symbol symbol) {
+    return create<ast::IdentifierExpression>(symbol);
+  }
+
+  /// @param source the source information
+  /// @param variable the AST variable
+  /// @return an ast::IdentifierExpression with the variable's symbol
+  const ast::IdentifierExpression* Expr(const Source& source,
+                                        const ast::Variable* variable) {
+    return create<ast::IdentifierExpression>(source, variable->symbol);
+  }
+
+  /// @param variable the AST variable
+  /// @return an ast::IdentifierExpression with the variable's symbol
+  const ast::IdentifierExpression* Expr(const ast::Variable* variable) {
+    return create<ast::IdentifierExpression>(variable->symbol);
+  }
+
+  /// @param source the source information
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  const ast::IdentifierExpression* Expr(const Source& source,
+                                        const char* name) {
+    return create<ast::IdentifierExpression>(source, Symbols().Register(name));
+  }
+
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  const ast::IdentifierExpression* Expr(const char* name) {
+    return create<ast::IdentifierExpression>(Symbols().Register(name));
+  }
+
+  /// @param source the source information
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  const ast::IdentifierExpression* Expr(const Source& source,
+                                        const std::string& name) {
+    return create<ast::IdentifierExpression>(source, Symbols().Register(name));
+  }
+
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  const ast::IdentifierExpression* Expr(const std::string& name) {
+    return create<ast::IdentifierExpression>(Symbols().Register(name));
+  }
+
+  /// @param source the source information
+  /// @param value the boolean value
+  /// @return a Scalar constructor for the given value
+  const ast::BoolLiteralExpression* Expr(const Source& source, bool value) {
+    return create<ast::BoolLiteralExpression>(source, value);
+  }
+
+  /// @param value the boolean value
+  /// @return a Scalar constructor for the given value
+  const ast::BoolLiteralExpression* Expr(bool value) {
+    return create<ast::BoolLiteralExpression>(value);
+  }
+
+  /// @param source the source information
+  /// @param value the float value
+  /// @return a Scalar constructor for the given value
+  const ast::FloatLiteralExpression* Expr(const Source& source, f32 value) {
+    return create<ast::FloatLiteralExpression>(source, value);
+  }
+
+  /// @param value the float value
+  /// @return a Scalar constructor for the given value
+  const ast::FloatLiteralExpression* Expr(f32 value) {
+    return create<ast::FloatLiteralExpression>(value);
+  }
+
+  /// @param source the source information
+  /// @param value the integer value
+  /// @return a Scalar constructor for the given value
+  const ast::SintLiteralExpression* Expr(const Source& source, i32 value) {
+    return create<ast::SintLiteralExpression>(source, value);
+  }
+
+  /// @param value the integer value
+  /// @return a Scalar constructor for the given value
+  const ast::SintLiteralExpression* Expr(i32 value) {
+    return create<ast::SintLiteralExpression>(value);
+  }
+
+  /// @param source the source information
+  /// @param value the unsigned int value
+  /// @return a Scalar constructor for the given value
+  const ast::UintLiteralExpression* Expr(const Source& source, u32 value) {
+    return create<ast::UintLiteralExpression>(source, value);
+  }
+
+  /// @param value the unsigned int value
+  /// @return a Scalar constructor for the given value
+  const ast::UintLiteralExpression* Expr(u32 value) {
+    return create<ast::UintLiteralExpression>(value);
+  }
+
+  /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to
+  /// `list`.
+  /// @param list the list to append too
+  /// @param arg the arg to create
+  template <typename ARG>
+  void Append(ast::ExpressionList& list, ARG&& arg) {
+    list.emplace_back(Expr(std::forward<ARG>(arg)));
+  }
+
+  /// Converts `arg0` and `args` to `ast::Expression`s using `Expr()`,
+  /// then appends them to `list`.
+  /// @param list the list to append too
+  /// @param arg0 the first argument
+  /// @param args the rest of the arguments
+  template <typename ARG0, typename... ARGS>
+  void Append(ast::ExpressionList& list, ARG0&& arg0, ARGS&&... args) {
+    Append(list, std::forward<ARG0>(arg0));
+    Append(list, std::forward<ARGS>(args)...);
+  }
+
+  /// @return an empty list of expressions
+  ast::ExpressionList ExprList() { return {}; }
+
+  /// @param args the list of expressions
+  /// @return the list of expressions converted to `ast::Expression`s using
+  /// `Expr()`,
+  template <typename... ARGS>
+  ast::ExpressionList ExprList(ARGS&&... args) {
+    ast::ExpressionList list;
+    list.reserve(sizeof...(args));
+    Append(list, std::forward<ARGS>(args)...);
+    return list;
+  }
+
+  /// @param list the list of expressions
+  /// @return `list`
+  ast::ExpressionList ExprList(ast::ExpressionList list) { return list; }
+
+  /// @param args the arguments for the type constructor
+  /// @return an `ast::CallExpression` of type `ty`, with the values
+  /// of `args` converted to `ast::Expression`s using `Expr()`
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* Construct(ARGS&&... args) {
+    return Construct(ty.Of<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param type the type to construct
+  /// @param args the arguments for the constructor
+  /// @return an `ast::CallExpression` of `type` constructed with the
+  /// values `args`.
+  template <typename... ARGS>
+  const ast::CallExpression* Construct(const ast::Type* type, ARGS&&... args) {
+    return Construct(source_, type, std::forward<ARGS>(args)...);
+  }
+
+  /// @param source the source information
+  /// @param type the type to construct
+  /// @param args the arguments for the constructor
+  /// @return an `ast::CallExpression` of `type` constructed with the
+  /// values `args`.
+  template <typename... ARGS>
+  const ast::CallExpression* Construct(const Source& source,
+                                       const ast::Type* type,
+                                       ARGS&&... args) {
+    return create<ast::CallExpression>(source, type,
+                                       ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param expr the expression for the bitcast
+  /// @return an `ast::BitcastExpression` of type `ty`, with the values of
+  /// `expr` converted to `ast::Expression`s using `Expr()`
+  template <typename T, typename EXPR>
+  const ast::BitcastExpression* Bitcast(EXPR&& expr) {
+    return Bitcast(ty.Of<T>(), std::forward<EXPR>(expr));
+  }
+
+  /// @param type the type to cast to
+  /// @param expr the expression for the bitcast
+  /// @return an `ast::BitcastExpression` of `type` constructed with the values
+  /// `expr`.
+  template <typename EXPR>
+  const ast::BitcastExpression* Bitcast(const ast::Type* type, EXPR&& expr) {
+    return create<ast::BitcastExpression>(type, Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param source the source information
+  /// @param type the type to cast to
+  /// @param expr the expression for the bitcast
+  /// @return an `ast::BitcastExpression` of `type` constructed with the values
+  /// `expr`.
+  template <typename EXPR>
+  const ast::BitcastExpression* Bitcast(const Source& source,
+                                        const ast::Type* type,
+                                        EXPR&& expr) {
+    return create<ast::BitcastExpression>(source, type,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @param type the vector type
+  /// @param size the vector size
+  /// @return an `ast::CallExpression` of a `size`-element vector of
+  /// type `type`, constructed with the values `args`.
+  template <typename... ARGS>
+  const ast::CallExpression* vec(const ast::Type* type,
+                                 uint32_t size,
+                                 ARGS&&... args) {
+    return Construct(ty.vec(type, size), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::CallExpression` of a 2-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* vec2(ARGS&&... args) {
+    return Construct(ty.vec2<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::CallExpression` of a 3-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* vec3(ARGS&&... args) {
+    return Construct(ty.vec3<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::CallExpression` of a 4-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* vec4(ARGS&&... args) {
+    return Construct(ty.vec4<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 2x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat2x2(ARGS&&... args) {
+    return Construct(ty.mat2x2<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 2x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat2x3(ARGS&&... args) {
+    return Construct(ty.mat2x3<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 2x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat2x4(ARGS&&... args) {
+    return Construct(ty.mat2x4<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 3x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat3x2(ARGS&&... args) {
+    return Construct(ty.mat3x2<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 3x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat3x3(ARGS&&... args) {
+    return Construct(ty.mat3x3<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 3x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat3x4(ARGS&&... args) {
+    return Construct(ty.mat3x4<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 4x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat4x2(ARGS&&... args) {
+    return Construct(ty.mat4x2<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 4x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat4x3(ARGS&&... args) {
+    return Construct(ty.mat4x3<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::CallExpression` of a 4x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  const ast::CallExpression* mat4x4(ARGS&&... args) {
+    return Construct(ty.mat4x4<T>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param args the arguments for the array constructor
+  /// @return an `ast::CallExpression` of an array with element type
+  /// `T` and size `N`, constructed with the values `args`.
+  template <typename T, int N, typename... ARGS>
+  const ast::CallExpression* array(ARGS&&... args) {
+    return Construct(ty.array<T, N>(), std::forward<ARGS>(args)...);
+  }
+
+  /// @param subtype the array element type
+  /// @param n the array size. nullptr represents a runtime-array.
+  /// @param args the arguments for the array constructor
+  /// @return an `ast::CallExpression` of an array with element type
+  /// `subtype`, constructed with the values `args`.
+  template <typename EXPR, typename... ARGS>
+  const ast::CallExpression* array(const ast::Type* subtype,
+                                   EXPR&& n,
+                                   ARGS&&... args) {
+    return Construct(ty.array(subtype, std::forward<EXPR>(n)),
+                     std::forward<ARGS>(args)...);
+  }
+
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param optional the optional variable settings.
+  /// Can be any of the following, in any order:
+  ///   * ast::StorageClass   - specifies the variable storage class
+  ///   * ast::Access         - specifies the variable's access control
+  ///   * ast::Expression*    - specifies the variable's initializer expression
+  ///   * ast::AttributeList - specifies the variable's attributes
+  /// Note that repeated arguments of the same type will use the last argument's
+  /// value.
+  /// @returns a `ast::Variable` with the given name, type and additional
+  /// options
+  template <typename NAME, typename... OPTIONAL>
+  const ast::Variable* Var(NAME&& name,
+                           const ast::Type* type,
+                           OPTIONAL&&... optional) {
+    VarOptionals opts(std::forward<OPTIONAL>(optional)...);
+    return create<ast::Variable>(Sym(std::forward<NAME>(name)), opts.storage,
+                                 opts.access, type, false /* is_const */,
+                                 false /* is_overridable */, opts.constructor,
+                                 std::move(opts.attributes));
+  }
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param optional the optional variable settings.
+  /// Can be any of the following, in any order:
+  ///   * ast::StorageClass   - specifies the variable storage class
+  ///   * ast::Access         - specifies the variable's access control
+  ///   * ast::Expression*    - specifies the variable's initializer expression
+  ///   * ast::AttributeList - specifies the variable's attributes
+  /// Note that repeated arguments of the same type will use the last argument's
+  /// value.
+  /// @returns a `ast::Variable` with the given name, storage and type
+  template <typename NAME, typename... OPTIONAL>
+  const ast::Variable* Var(const Source& source,
+                           NAME&& name,
+                           const ast::Type* type,
+                           OPTIONAL&&... optional) {
+    VarOptionals opts(std::forward<OPTIONAL>(optional)...);
+    return create<ast::Variable>(
+        source, Sym(std::forward<NAME>(name)), opts.storage, opts.access, type,
+        false /* is_const */, false /* is_overridable */, opts.constructor,
+        std::move(opts.attributes));
+  }
+
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns a constant `ast::Variable` with the given name and type
+  template <typename NAME>
+  const ast::Variable* Const(NAME&& name,
+                             const ast::Type* type,
+                             const ast::Expression* constructor,
+                             ast::AttributeList attributes = {}) {
+    return create<ast::Variable>(
+        Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        false /* is_overridable */, constructor, attributes);
+  }
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns a constant `ast::Variable` with the given name and type
+  template <typename NAME>
+  const ast::Variable* Const(const Source& source,
+                             NAME&& name,
+                             const ast::Type* type,
+                             const ast::Expression* constructor,
+                             ast::AttributeList attributes = {}) {
+    return create<ast::Variable>(
+        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        false /* is_overridable */, constructor, attributes);
+  }
+
+  /// @param name the parameter name
+  /// @param type the parameter type
+  /// @param attributes optional parameter attributes
+  /// @returns a constant `ast::Variable` with the given name and type
+  template <typename NAME>
+  const ast::Variable* Param(NAME&& name,
+                             const ast::Type* type,
+                             ast::AttributeList attributes = {}) {
+    return create<ast::Variable>(
+        Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        false /* is_overridable */, nullptr, attributes);
+  }
+
+  /// @param source the parameter source
+  /// @param name the parameter name
+  /// @param type the parameter type
+  /// @param attributes optional parameter attributes
+  /// @returns a constant `ast::Variable` with the given name and type
+  template <typename NAME>
+  const ast::Variable* Param(const Source& source,
+                             NAME&& name,
+                             const ast::Type* type,
+                             ast::AttributeList attributes = {}) {
+    return create<ast::Variable>(
+        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        false /* is_overridable */, nullptr, attributes);
+  }
+
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param optional the optional variable settings.
+  /// Can be any of the following, in any order:
+  ///   * ast::StorageClass   - specifies the variable storage class
+  ///   * ast::Access         - specifies the variable's access control
+  ///   * ast::Expression*    - specifies the variable's initializer expression
+  ///   * ast::AttributeList - specifies the variable's attributes
+  /// Note that repeated arguments of the same type will use the last argument's
+  /// value.
+  /// @returns a new `ast::Variable`, which is automatically registered as a
+  /// global variable with the ast::Module.
+  template <typename NAME,
+            typename... OPTIONAL,
+            typename = DisableIfSource<NAME>>
+  const ast::Variable* Global(NAME&& name,
+                              const ast::Type* type,
+                              OPTIONAL&&... optional) {
+    auto* var = Var(std::forward<NAME>(name), type,
+                    std::forward<OPTIONAL>(optional)...);
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param optional the optional variable settings.
+  /// Can be any of the following, in any order:
+  ///   * ast::StorageClass   - specifies the variable storage class
+  ///   * ast::Access         - specifies the variable's access control
+  ///   * ast::Expression*    - specifies the variable's initializer expression
+  ///   * ast::AttributeList - specifies the variable's attributes
+  /// Note that repeated arguments of the same type will use the last argument's
+  /// value.
+  /// @returns a new `ast::Variable`, which is automatically registered as a
+  /// global variable with the ast::Module.
+  template <typename NAME, typename... OPTIONAL>
+  const ast::Variable* Global(const Source& source,
+                              NAME&& name,
+                              const ast::Type* type,
+                              OPTIONAL&&... optional) {
+    auto* var = Var(source, std::forward<NAME>(name), type,
+                    std::forward<OPTIONAL>(optional)...);
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns a const `ast::Variable` constructed by calling Var() with the
+  /// arguments of `args`, which is automatically registered as a global
+  /// variable with the ast::Module.
+  template <typename NAME>
+  const ast::Variable* GlobalConst(NAME&& name,
+                                   const ast::Type* type,
+                                   const ast::Expression* constructor,
+                                   ast::AttributeList attributes = {}) {
+    auto* var = Const(std::forward<NAME>(name), type, constructor,
+                      std::move(attributes));
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns a const `ast::Variable` constructed by calling Var() with the
+  /// arguments of `args`, which is automatically registered as a global
+  /// variable with the ast::Module.
+  template <typename NAME>
+  const ast::Variable* GlobalConst(const Source& source,
+                                   NAME&& name,
+                                   const ast::Type* type,
+                                   const ast::Expression* constructor,
+                                   ast::AttributeList attributes = {}) {
+    auto* var = Const(source, std::forward<NAME>(name), type, constructor,
+                      std::move(attributes));
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor optional constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns an overridable const `ast::Variable` which is automatically
+  /// registered as a global variable with the ast::Module.
+  template <typename NAME>
+  const ast::Variable* Override(NAME&& name,
+                                const ast::Type* type,
+                                const ast::Expression* constructor,
+                                ast::AttributeList attributes = {}) {
+    auto* var = create<ast::Variable>(
+        source_, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        true /* is_overridable */, constructor, std::move(attributes));
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param attributes optional variable attributes
+  /// @returns a const `ast::Variable` constructed by calling Var() with the
+  /// arguments of `args`, which is automatically registered as a global
+  /// variable with the ast::Module.
+  template <typename NAME>
+  const ast::Variable* Override(const Source& source,
+                                NAME&& name,
+                                const ast::Type* type,
+                                const ast::Expression* constructor,
+                                ast::AttributeList attributes = {}) {
+    auto* var = create<ast::Variable>(
+        source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
+        ast::Access::kUndefined, type, true /* is_const */,
+        true /* is_overridable */, constructor, std::move(attributes));
+    AST().AddGlobalVariable(var);
+    return var;
+  }
+
+  /// @param source the source information
+  /// @param expr the expression to take the address of
+  /// @return an ast::UnaryOpExpression that takes the address of `expr`
+  template <typename EXPR>
+  const ast::UnaryOpExpression* AddressOf(const Source& source, EXPR&& expr) {
+    return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kAddressOf,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param expr the expression to take the address of
+  /// @return an ast::UnaryOpExpression that takes the address of `expr`
+  template <typename EXPR>
+  const ast::UnaryOpExpression* AddressOf(EXPR&& expr) {
+    return create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param source the source information
+  /// @param expr the expression to perform an indirection on
+  /// @return an ast::UnaryOpExpression that dereferences the pointer `expr`
+  template <typename EXPR>
+  const ast::UnaryOpExpression* Deref(const Source& source, EXPR&& expr) {
+    return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kIndirection,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param expr the expression to perform an indirection on
+  /// @return an ast::UnaryOpExpression that dereferences the pointer `expr`
+  template <typename EXPR>
+  const ast::UnaryOpExpression* Deref(EXPR&& expr) {
+    return create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param expr the expression to perform a unary not on
+  /// @return an ast::UnaryOpExpression that is the unary not of the input
+  /// expression
+  template <typename EXPR>
+  const ast::UnaryOpExpression* Not(EXPR&& expr) {
+    return create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
+                                          Expr(std::forward<EXPR>(expr)));
+  }
+
+  /// @param source the source information
+  /// @param func the function name
+  /// @param args the function call arguments
+  /// @returns a `ast::CallExpression` to the function `func`, with the
+  /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
+  template <typename NAME, typename... ARGS>
+  const ast::CallExpression* Call(const Source& source,
+                                  NAME&& func,
+                                  ARGS&&... args) {
+    return create<ast::CallExpression>(source, Expr(func),
+                                       ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param func the function name
+  /// @param args the function call arguments
+  /// @returns a `ast::CallExpression` to the function `func`, with the
+  /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
+  template <typename NAME, typename... ARGS, typename = DisableIfSource<NAME>>
+  const ast::CallExpression* Call(NAME&& func, ARGS&&... args) {
+    return create<ast::CallExpression>(Expr(func),
+                                       ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param source the source information
+  /// @param call the call expression to wrap in a call statement
+  /// @returns a `ast::CallStatement` for the given call expression
+  const ast::CallStatement* CallStmt(const Source& source,
+                                     const ast::CallExpression* call) {
+    return create<ast::CallStatement>(source, call);
+  }
+
+  /// @param call the call expression to wrap in a call statement
+  /// @returns a `ast::CallStatement` for the given call expression
+  const ast::CallStatement* CallStmt(const ast::CallExpression* call) {
+    return create<ast::CallStatement>(call);
+  }
+
+  /// @param source the source information
+  /// @returns a `ast::PhonyExpression`
+  const ast::PhonyExpression* Phony(const Source& source) {
+    return create<ast::PhonyExpression>(source);
+  }
+
+  /// @returns a `ast::PhonyExpression`
+  const ast::PhonyExpression* Phony() { return create<ast::PhonyExpression>(); }
+
+  /// @param expr the expression to ignore
+  /// @returns a `ast::AssignmentStatement` that assigns 'expr' to the phony
+  /// (underscore) variable.
+  template <typename EXPR>
+  const ast::AssignmentStatement* Ignore(EXPR&& expr) {
+    return create<ast::AssignmentStatement>(Phony(), Expr(expr));
+  }
+
+  /// @param lhs the left hand argument to the addition operation
+  /// @param rhs the right hand argument to the addition operation
+  /// @returns a `ast::BinaryExpression` summing the arguments `lhs` and `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Add(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kAdd,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the and operation
+  /// @param rhs the right hand argument to the and operation
+  /// @returns a `ast::BinaryExpression` bitwise anding `lhs` and `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* And(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kAnd,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the or operation
+  /// @param rhs the right hand argument to the or operation
+  /// @returns a `ast::BinaryExpression` bitwise or-ing `lhs` and `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Or(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kOr,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the subtraction operation
+  /// @param rhs the right hand argument to the subtraction operation
+  /// @returns a `ast::BinaryExpression` subtracting `rhs` from `lhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Sub(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kSubtract,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the multiplication operation
+  /// @param rhs the right hand argument to the multiplication operation
+  /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Mul(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param source the source information
+  /// @param lhs the left hand argument to the multiplication operation
+  /// @param rhs the right hand argument to the multiplication operation
+  /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Mul(const Source& source, LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(source, ast::BinaryOp::kMultiply,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the division operation
+  /// @param rhs the right hand argument to the division operation
+  /// @returns a `ast::BinaryExpression` dividing `lhs` by `rhs`
+  template <typename LHS, typename RHS>
+  const ast::Expression* Div(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kDivide,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the modulo operation
+  /// @param rhs the right hand argument to the modulo operation
+  /// @returns a `ast::BinaryExpression` applying modulo of `lhs` by `rhs`
+  template <typename LHS, typename RHS>
+  const ast::Expression* Mod(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kModulo,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the bit shift right operation
+  /// @param rhs the right hand argument to the bit shift right operation
+  /// @returns a `ast::BinaryExpression` bit shifting right `lhs` by `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Shr(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kShiftRight,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the bit shift left operation
+  /// @param rhs the right hand argument to the bit shift left operation
+  /// @returns a `ast::BinaryExpression` bit shifting left `lhs` by `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Shl(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kShiftLeft,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the equal expression
+  /// @param rhs the right hand argument to the equal expression
+  /// @returns a `ast::BinaryExpression` comparing `lhs` equal to `rhs`
+  template <typename LHS, typename RHS>
+  const ast::BinaryExpression* Equal(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kEqual,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param source the source information
+  /// @param obj the object for the index accessor expression
+  /// @param idx the index argument for the index accessor expression
+  /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx`
+  template <typename OBJ, typename IDX>
+  const ast::IndexAccessorExpression* IndexAccessor(const Source& source,
+                                                    OBJ&& obj,
+                                                    IDX&& idx) {
+    return create<ast::IndexAccessorExpression>(
+        source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx)));
+  }
+
+  /// @param obj the object for the index accessor expression
+  /// @param idx the index argument for the index accessor expression
+  /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx`
+  template <typename OBJ, typename IDX>
+  const ast::IndexAccessorExpression* IndexAccessor(OBJ&& obj, IDX&& idx) {
+    return create<ast::IndexAccessorExpression>(Expr(std::forward<OBJ>(obj)),
+                                                Expr(std::forward<IDX>(idx)));
+  }
+
+  /// @param source the source information
+  /// @param obj the object for the member accessor expression
+  /// @param idx the index argument for the member accessor expression
+  /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx`
+  template <typename OBJ, typename IDX>
+  const ast::MemberAccessorExpression* MemberAccessor(const Source& source,
+                                                      OBJ&& obj,
+                                                      IDX&& idx) {
+    return create<ast::MemberAccessorExpression>(
+        source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx)));
+  }
+
+  /// @param obj the object for the member accessor expression
+  /// @param idx the index argument for the member accessor expression
+  /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx`
+  template <typename OBJ, typename IDX>
+  const ast::MemberAccessorExpression* MemberAccessor(OBJ&& obj, IDX&& idx) {
+    return create<ast::MemberAccessorExpression>(Expr(std::forward<OBJ>(obj)),
+                                                 Expr(std::forward<IDX>(idx)));
+  }
+
+  /// Creates a ast::StructMemberOffsetAttribute
+  /// @param val the offset value
+  /// @returns the offset attribute pointer
+  const ast::StructMemberOffsetAttribute* MemberOffset(uint32_t val) {
+    return create<ast::StructMemberOffsetAttribute>(source_, val);
+  }
+
+  /// Creates a ast::StructMemberSizeAttribute
+  /// @param source the source information
+  /// @param val the size value
+  /// @returns the size attribute pointer
+  const ast::StructMemberSizeAttribute* MemberSize(const Source& source,
+                                                   uint32_t val) {
+    return create<ast::StructMemberSizeAttribute>(source, val);
+  }
+
+  /// Creates a ast::StructMemberSizeAttribute
+  /// @param val the size value
+  /// @returns the size attribute pointer
+  const ast::StructMemberSizeAttribute* MemberSize(uint32_t val) {
+    return create<ast::StructMemberSizeAttribute>(source_, val);
+  }
+
+  /// Creates a ast::StructMemberAlignAttribute
+  /// @param source the source information
+  /// @param val the align value
+  /// @returns the align attribute pointer
+  const ast::StructMemberAlignAttribute* MemberAlign(const Source& source,
+                                                     uint32_t val) {
+    return create<ast::StructMemberAlignAttribute>(source, val);
+  }
+
+  /// Creates a ast::StructMemberAlignAttribute
+  /// @param val the align value
+  /// @returns the align attribute pointer
+  const ast::StructMemberAlignAttribute* MemberAlign(uint32_t val) {
+    return create<ast::StructMemberAlignAttribute>(source_, val);
+  }
+
+  /// Creates a ast::StructBlockAttribute
+  /// @returns the struct block attribute pointer
+  const ast::StructBlockAttribute* StructBlock() {
+    return create<ast::StructBlockAttribute>();
+  }
+
+  /// Creates the ast::GroupAttribute
+  /// @param value group attribute index
+  /// @returns the group attribute pointer
+  const ast::GroupAttribute* Group(uint32_t value) {
+    return create<ast::GroupAttribute>(value);
+  }
+
+  /// Creates the ast::BindingAttribute
+  /// @param value the binding index
+  /// @returns the binding deocration pointer
+  const ast::BindingAttribute* Binding(uint32_t value) {
+    return create<ast::BindingAttribute>(value);
+  }
+
+  /// Convenience function to create both a ast::GroupAttribute and
+  /// ast::BindingAttribute
+  /// @param group the group index
+  /// @param binding the binding index
+  /// @returns a attribute list with both the group and binding attributes
+  ast::AttributeList GroupAndBinding(uint32_t group, uint32_t binding) {
+    return {Group(group), Binding(binding)};
+  }
+
+  /// Creates an ast::Function and registers it with the ast::Module.
+  /// @param source the source information
+  /// @param name the function name
+  /// @param params the function parameters
+  /// @param type the function return type
+  /// @param body the function body
+  /// @param attributes the optional function attributes
+  /// @param return_type_attributes the optional function return type
+  /// attributes
+  /// @returns the function pointer
+  template <typename NAME>
+  const ast::Function* Func(const Source& source,
+                            NAME&& name,
+                            ast::VariableList params,
+                            const ast::Type* type,
+                            ast::StatementList body,
+                            ast::AttributeList attributes = {},
+                            ast::AttributeList return_type_attributes = {}) {
+    auto* func = create<ast::Function>(
+        source, Sym(std::forward<NAME>(name)), params, type,
+        create<ast::BlockStatement>(body), attributes, return_type_attributes);
+    AST().AddFunction(func);
+    return func;
+  }
+
+  /// Creates an ast::Function and registers it with the ast::Module.
+  /// @param name the function name
+  /// @param params the function parameters
+  /// @param type the function return type
+  /// @param body the function body
+  /// @param attributes the optional function attributes
+  /// @param return_type_attributes the optional function return type
+  /// attributes
+  /// @returns the function pointer
+  template <typename NAME>
+  const ast::Function* Func(NAME&& name,
+                            ast::VariableList params,
+                            const ast::Type* type,
+                            ast::StatementList body,
+                            ast::AttributeList attributes = {},
+                            ast::AttributeList return_type_attributes = {}) {
+    auto* func = create<ast::Function>(Sym(std::forward<NAME>(name)), params,
+                                       type, create<ast::BlockStatement>(body),
+                                       attributes, return_type_attributes);
+    AST().AddFunction(func);
+    return func;
+  }
+
+  /// Creates an ast::BreakStatement
+  /// @param source the source information
+  /// @returns the break statement pointer
+  const ast::BreakStatement* Break(const Source& source) {
+    return create<ast::BreakStatement>(source);
+  }
+
+  /// Creates an ast::BreakStatement
+  /// @returns the break statement pointer
+  const ast::BreakStatement* Break() { return create<ast::BreakStatement>(); }
+
+  /// Creates an ast::ContinueStatement
+  /// @param source the source information
+  /// @returns the continue statement pointer
+  const ast::ContinueStatement* Continue(const Source& source) {
+    return create<ast::ContinueStatement>(source);
+  }
+
+  /// Creates an ast::ContinueStatement
+  /// @returns the continue statement pointer
+  const ast::ContinueStatement* Continue() {
+    return create<ast::ContinueStatement>();
+  }
+
+  /// Creates an ast::ReturnStatement with no return value
+  /// @param source the source information
+  /// @returns the return statement pointer
+  const ast::ReturnStatement* Return(const Source& source) {
+    return create<ast::ReturnStatement>(source);
+  }
+
+  /// Creates an ast::ReturnStatement with no return value
+  /// @returns the return statement pointer
+  const ast::ReturnStatement* Return() {
+    return create<ast::ReturnStatement>();
+  }
+
+  /// Creates an ast::ReturnStatement with the given return value
+  /// @param source the source information
+  /// @param val the return value
+  /// @returns the return statement pointer
+  template <typename EXPR>
+  const ast::ReturnStatement* Return(const Source& source, EXPR&& val) {
+    return create<ast::ReturnStatement>(source, Expr(std::forward<EXPR>(val)));
+  }
+
+  /// Creates an ast::ReturnStatement with the given return value
+  /// @param val the return value
+  /// @returns the return statement pointer
+  template <typename EXPR, typename = DisableIfSource<EXPR>>
+  const ast::ReturnStatement* Return(EXPR&& val) {
+    return create<ast::ReturnStatement>(Expr(std::forward<EXPR>(val)));
+  }
+
+  /// Creates an ast::DiscardStatement
+  /// @param source the source information
+  /// @returns the discard statement pointer
+  const ast::DiscardStatement* Discard(const Source& source) {
+    return create<ast::DiscardStatement>(source);
+  }
+
+  /// Creates an ast::DiscardStatement
+  /// @returns the discard statement pointer
+  const ast::DiscardStatement* Discard() {
+    return create<ast::DiscardStatement>();
+  }
+
+  /// Creates a ast::Alias registering it with the AST().TypeDecls().
+  /// @param source the source information
+  /// @param name the alias name
+  /// @param type the alias target type
+  /// @returns the alias type
+  template <typename NAME>
+  const ast::Alias* Alias(const Source& source,
+                          NAME&& name,
+                          const ast::Type* type) {
+    auto* out = ty.alias(source, std::forward<NAME>(name), type);
+    AST().AddTypeDecl(out);
+    return out;
+  }
+
+  /// Creates a ast::Alias registering it with the AST().TypeDecls().
+  /// @param name the alias name
+  /// @param type the alias target type
+  /// @returns the alias type
+  template <typename NAME>
+  const ast::Alias* Alias(NAME&& name, const ast::Type* type) {
+    auto* out = ty.alias(std::forward<NAME>(name), type);
+    AST().AddTypeDecl(out);
+    return out;
+  }
+
+  /// Creates a ast::Struct registering it with the AST().TypeDecls().
+  /// @param source the source information
+  /// @param name the struct name
+  /// @param members the struct members
+  /// @param attributes the optional struct attributes
+  /// @returns the struct type
+  template <typename NAME>
+  const ast::Struct* Structure(const Source& source,
+                               NAME&& name,
+                               ast::StructMemberList members,
+                               ast::AttributeList attributes = {}) {
+    auto sym = Sym(std::forward<NAME>(name));
+    auto* type = create<ast::Struct>(source, sym, std::move(members),
+                                     std::move(attributes));
+    AST().AddTypeDecl(type);
+    return type;
+  }
+
+  /// Creates a ast::Struct registering it with the AST().TypeDecls().
+  /// @param name the struct name
+  /// @param members the struct members
+  /// @param attributes the optional struct attributes
+  /// @returns the struct type
+  template <typename NAME>
+  const ast::Struct* Structure(NAME&& name,
+                               ast::StructMemberList members,
+                               ast::AttributeList attributes = {}) {
+    auto sym = Sym(std::forward<NAME>(name));
+    auto* type =
+        create<ast::Struct>(sym, std::move(members), std::move(attributes));
+    AST().AddTypeDecl(type);
+    return type;
+  }
+
+  /// Creates a ast::StructMember
+  /// @param source the source information
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @param attributes the optional struct member attributes
+  /// @returns the struct member pointer
+  template <typename NAME>
+  const ast::StructMember* Member(const Source& source,
+                                  NAME&& name,
+                                  const ast::Type* type,
+                                  ast::AttributeList attributes = {}) {
+    return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)),
+                                     type, std::move(attributes));
+  }
+
+  /// Creates a ast::StructMember
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @param attributes the optional struct member attributes
+  /// @returns the struct member pointer
+  template <typename NAME>
+  const ast::StructMember* Member(NAME&& name,
+                                  const ast::Type* type,
+                                  ast::AttributeList attributes = {}) {
+    return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)),
+                                     type, std::move(attributes));
+  }
+
+  /// Creates a ast::StructMember with the given byte offset
+  /// @param offset the offset to use in the StructMemberOffsetattribute
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @returns the struct member pointer
+  template <typename NAME>
+  const ast::StructMember* Member(uint32_t offset,
+                                  NAME&& name,
+                                  const ast::Type* type) {
+    return create<ast::StructMember>(
+        source_, Sym(std::forward<NAME>(name)), type,
+        ast::AttributeList{
+            create<ast::StructMemberOffsetAttribute>(offset),
+        });
+  }
+
+  /// Creates a ast::BlockStatement with input statements
+  /// @param source the source information for the block
+  /// @param statements statements of block
+  /// @returns the block statement pointer
+  template <typename... Statements>
+  const ast::BlockStatement* Block(const Source& source,
+                                   Statements&&... statements) {
+    return create<ast::BlockStatement>(
+        source, ast::StatementList{std::forward<Statements>(statements)...});
+  }
+
+  /// Creates a ast::BlockStatement with input statements
+  /// @param statements statements of block
+  /// @returns the block statement pointer
+  template <typename... STATEMENTS, typename = DisableIfSource<STATEMENTS...>>
+  const ast::BlockStatement* Block(STATEMENTS&&... statements) {
+    return create<ast::BlockStatement>(
+        ast::StatementList{std::forward<STATEMENTS>(statements)...});
+  }
+
+  /// Creates a ast::ElseStatement with input condition and body
+  /// @param condition the else condition expression
+  /// @param body the else body
+  /// @returns the else statement pointer
+  template <typename CONDITION>
+  const ast::ElseStatement* Else(CONDITION&& condition,
+                                 const ast::BlockStatement* body) {
+    return create<ast::ElseStatement>(Expr(std::forward<CONDITION>(condition)),
+                                      body);
+  }
+
+  /// Creates a ast::ElseStatement with no condition and body
+  /// @param body the else body
+  /// @returns the else statement pointer
+  const ast::ElseStatement* Else(const ast::BlockStatement* body) {
+    return create<ast::ElseStatement>(nullptr, body);
+  }
+
+  /// Creates a ast::IfStatement with input condition, body, and optional
+  /// variadic else statements
+  /// @param source the source information for the if statement
+  /// @param condition the if statement condition expression
+  /// @param body the if statement body
+  /// @param elseStatements optional variadic else statements
+  /// @returns the if statement pointer
+  template <typename CONDITION, typename... ELSE_STATEMENTS>
+  const ast::IfStatement* If(const Source& source,
+                             CONDITION&& condition,
+                             const ast::BlockStatement* body,
+                             ELSE_STATEMENTS&&... elseStatements) {
+    return create<ast::IfStatement>(
+        source, Expr(std::forward<CONDITION>(condition)), body,
+        ast::ElseStatementList{
+            std::forward<ELSE_STATEMENTS>(elseStatements)...});
+  }
+
+  /// Creates a ast::IfStatement with input condition, body, and optional
+  /// variadic else statements
+  /// @param condition the if statement condition expression
+  /// @param body the if statement body
+  /// @param elseStatements optional variadic else statements
+  /// @returns the if statement pointer
+  template <typename CONDITION, typename... ELSE_STATEMENTS>
+  const ast::IfStatement* If(CONDITION&& condition,
+                             const ast::BlockStatement* body,
+                             ELSE_STATEMENTS&&... elseStatements) {
+    return create<ast::IfStatement>(
+        Expr(std::forward<CONDITION>(condition)), body,
+        ast::ElseStatementList{
+            std::forward<ELSE_STATEMENTS>(elseStatements)...});
+  }
+
+  /// Creates a ast::AssignmentStatement with input lhs and rhs expressions
+  /// @param source the source information
+  /// @param lhs the left hand side expression initializer
+  /// @param rhs the right hand side expression initializer
+  /// @returns the assignment statement pointer
+  template <typename LhsExpressionInit, typename RhsExpressionInit>
+  const ast::AssignmentStatement* Assign(const Source& source,
+                                         LhsExpressionInit&& lhs,
+                                         RhsExpressionInit&& rhs) {
+    return create<ast::AssignmentStatement>(
+        source, Expr(std::forward<LhsExpressionInit>(lhs)),
+        Expr(std::forward<RhsExpressionInit>(rhs)));
+  }
+
+  /// Creates a ast::AssignmentStatement with input lhs and rhs expressions
+  /// @param lhs the left hand side expression initializer
+  /// @param rhs the right hand side expression initializer
+  /// @returns the assignment statement pointer
+  template <typename LhsExpressionInit, typename RhsExpressionInit>
+  const ast::AssignmentStatement* Assign(LhsExpressionInit&& lhs,
+                                         RhsExpressionInit&& rhs) {
+    return create<ast::AssignmentStatement>(
+        Expr(std::forward<LhsExpressionInit>(lhs)),
+        Expr(std::forward<RhsExpressionInit>(rhs)));
+  }
+
+  /// Creates a ast::LoopStatement with input body and optional continuing
+  /// @param source the source information
+  /// @param body the loop body
+  /// @param continuing the optional continuing block
+  /// @returns the loop statement pointer
+  const ast::LoopStatement* Loop(
+      const Source& source,
+      const ast::BlockStatement* body,
+      const ast::BlockStatement* continuing = nullptr) {
+    return create<ast::LoopStatement>(source, body, continuing);
+  }
+
+  /// Creates a ast::LoopStatement with input body and optional continuing
+  /// @param body the loop body
+  /// @param continuing the optional continuing block
+  /// @returns the loop statement pointer
+  const ast::LoopStatement* Loop(
+      const ast::BlockStatement* body,
+      const ast::BlockStatement* continuing = nullptr) {
+    return create<ast::LoopStatement>(body, continuing);
+  }
+
+  /// Creates a ast::ForLoopStatement with input body and optional initializer,
+  /// condition and continuing.
+  /// @param source the source information
+  /// @param init the optional loop initializer
+  /// @param cond the optional loop condition
+  /// @param cont the optional loop continuing
+  /// @param body the loop body
+  /// @returns the for loop statement pointer
+  template <typename COND>
+  const ast::ForLoopStatement* For(const Source& source,
+                                   const ast::Statement* init,
+                                   COND&& cond,
+                                   const ast::Statement* cont,
+                                   const ast::BlockStatement* body) {
+    return create<ast::ForLoopStatement>(
+        source, init, Expr(std::forward<COND>(cond)), cont, body);
+  }
+
+  /// Creates a ast::ForLoopStatement with input body and optional initializer,
+  /// condition and continuing.
+  /// @param init the optional loop initializer
+  /// @param cond the optional loop condition
+  /// @param cont the optional loop continuing
+  /// @param body the loop body
+  /// @returns the for loop statement pointer
+  template <typename COND>
+  const ast::ForLoopStatement* For(const ast::Statement* init,
+                                   COND&& cond,
+                                   const ast::Statement* cont,
+                                   const ast::BlockStatement* body) {
+    return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)),
+                                         cont, body);
+  }
+
+  /// Creates a ast::VariableDeclStatement for the input variable
+  /// @param source the source information
+  /// @param var the variable to wrap in a decl statement
+  /// @returns the variable decl statement pointer
+  const ast::VariableDeclStatement* Decl(const Source& source,
+                                         const ast::Variable* var) {
+    return create<ast::VariableDeclStatement>(source, var);
+  }
+
+  /// Creates a ast::VariableDeclStatement for the input variable
+  /// @param var the variable to wrap in a decl statement
+  /// @returns the variable decl statement pointer
+  const ast::VariableDeclStatement* Decl(const ast::Variable* var) {
+    return create<ast::VariableDeclStatement>(var);
+  }
+
+  /// Creates a ast::SwitchStatement with input expression and cases
+  /// @param source the source information
+  /// @param condition the condition expression initializer
+  /// @param cases case statements
+  /// @returns the switch statement pointer
+  template <typename ExpressionInit, typename... Cases>
+  const ast::SwitchStatement* Switch(const Source& source,
+                                     ExpressionInit&& condition,
+                                     Cases&&... cases) {
+    return create<ast::SwitchStatement>(
+        source, Expr(std::forward<ExpressionInit>(condition)),
+        ast::CaseStatementList{std::forward<Cases>(cases)...});
+  }
+
+  /// Creates a ast::SwitchStatement with input expression and cases
+  /// @param condition the condition expression initializer
+  /// @param cases case statements
+  /// @returns the switch statement pointer
+  template <typename ExpressionInit,
+            typename... Cases,
+            typename = DisableIfSource<ExpressionInit>>
+  const ast::SwitchStatement* Switch(ExpressionInit&& condition,
+                                     Cases&&... cases) {
+    return create<ast::SwitchStatement>(
+        Expr(std::forward<ExpressionInit>(condition)),
+        ast::CaseStatementList{std::forward<Cases>(cases)...});
+  }
+
+  /// Creates a ast::CaseStatement with input list of selectors, and body
+  /// @param source the source information
+  /// @param selectors list of selectors
+  /// @param body the case body
+  /// @returns the case statement pointer
+  const ast::CaseStatement* Case(const Source& source,
+                                 ast::CaseSelectorList selectors,
+                                 const ast::BlockStatement* body = nullptr) {
+    return create<ast::CaseStatement>(source, std::move(selectors),
+                                      body ? body : Block());
+  }
+
+  /// Creates a ast::CaseStatement with input list of selectors, and body
+  /// @param selectors list of selectors
+  /// @param body the case body
+  /// @returns the case statement pointer
+  const ast::CaseStatement* Case(ast::CaseSelectorList selectors,
+                                 const ast::BlockStatement* body = nullptr) {
+    return create<ast::CaseStatement>(std::move(selectors),
+                                      body ? body : Block());
+  }
+
+  /// Convenient overload that takes a single selector
+  /// @param selector a single case selector
+  /// @param body the case body
+  /// @returns the case statement pointer
+  const ast::CaseStatement* Case(const ast::IntLiteralExpression* selector,
+                                 const ast::BlockStatement* body = nullptr) {
+    return Case(ast::CaseSelectorList{selector}, body);
+  }
+
+  /// Convenience function that creates a 'default' ast::CaseStatement
+  /// @param source the source information
+  /// @param body the case body
+  /// @returns the case statement pointer
+  const ast::CaseStatement* DefaultCase(
+      const Source& source,
+      const ast::BlockStatement* body = nullptr) {
+    return Case(source, ast::CaseSelectorList{}, body);
+  }
+
+  /// Convenience function that creates a 'default' ast::CaseStatement
+  /// @param body the case body
+  /// @returns the case statement pointer
+  const ast::CaseStatement* DefaultCase(
+      const ast::BlockStatement* body = nullptr) {
+    return Case(ast::CaseSelectorList{}, body);
+  }
+
+  /// Creates an ast::FallthroughStatement
+  /// @param source the source information
+  /// @returns the fallthrough statement pointer
+  const ast::FallthroughStatement* Fallthrough(const Source& source) {
+    return create<ast::FallthroughStatement>(source);
+  }
+
+  /// Creates an ast::FallthroughStatement
+  /// @returns the fallthrough statement pointer
+  const ast::FallthroughStatement* Fallthrough() {
+    return create<ast::FallthroughStatement>();
+  }
+
+  /// Creates an ast::BuiltinAttribute
+  /// @param source the source information
+  /// @param builtin the builtin value
+  /// @returns the builtin attribute pointer
+  const ast::BuiltinAttribute* Builtin(const Source& source,
+                                       ast::Builtin builtin) {
+    return create<ast::BuiltinAttribute>(source, builtin);
+  }
+
+  /// Creates an ast::BuiltinAttribute
+  /// @param builtin the builtin value
+  /// @returns the builtin attribute pointer
+  const ast::BuiltinAttribute* Builtin(ast::Builtin builtin) {
+    return create<ast::BuiltinAttribute>(source_, builtin);
+  }
+
+  /// Creates an ast::InterpolateAttribute
+  /// @param source the source information
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  /// @returns the interpolate attribute pointer
+  const ast::InterpolateAttribute* Interpolate(
+      const Source& source,
+      ast::InterpolationType type,
+      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) {
+    return create<ast::InterpolateAttribute>(source, type, sampling);
+  }
+
+  /// Creates an ast::InterpolateAttribute
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  /// @returns the interpolate attribute pointer
+  const ast::InterpolateAttribute* Interpolate(
+      ast::InterpolationType type,
+      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) {
+    return create<ast::InterpolateAttribute>(source_, type, sampling);
+  }
+
+  /// Creates an ast::InterpolateAttribute using flat interpolation
+  /// @param source the source information
+  /// @returns the interpolate attribute pointer
+  const ast::InterpolateAttribute* Flat(const Source& source) {
+    return Interpolate(source, ast::InterpolationType::kFlat);
+  }
+
+  /// Creates an ast::InterpolateAttribute using flat interpolation
+  /// @returns the interpolate attribute pointer
+  const ast::InterpolateAttribute* Flat() {
+    return Interpolate(ast::InterpolationType::kFlat);
+  }
+
+  /// Creates an ast::InvariantAttribute
+  /// @param source the source information
+  /// @returns the invariant attribute pointer
+  const ast::InvariantAttribute* Invariant(const Source& source) {
+    return create<ast::InvariantAttribute>(source);
+  }
+
+  /// Creates an ast::InvariantAttribute
+  /// @returns the invariant attribute pointer
+  const ast::InvariantAttribute* Invariant() {
+    return create<ast::InvariantAttribute>(source_);
+  }
+
+  /// Creates an ast::LocationAttribute
+  /// @param source the source information
+  /// @param location the location value
+  /// @returns the location attribute pointer
+  const ast::LocationAttribute* Location(const Source& source,
+                                         uint32_t location) {
+    return create<ast::LocationAttribute>(source, location);
+  }
+
+  /// Creates an ast::LocationAttribute
+  /// @param location the location value
+  /// @returns the location attribute pointer
+  const ast::LocationAttribute* Location(uint32_t location) {
+    return create<ast::LocationAttribute>(source_, location);
+  }
+
+  /// Creates an ast::IdAttribute
+  /// @param source the source information
+  /// @param id the id value
+  /// @returns the override attribute pointer
+  const ast::IdAttribute* Id(const Source& source, uint32_t id) {
+    return create<ast::IdAttribute>(source, id);
+  }
+
+  /// Creates an ast::IdAttribute with a constant ID
+  /// @param id the optional id value
+  /// @returns the override attribute pointer
+  const ast::IdAttribute* Id(uint32_t id) { return Id(source_, id); }
+
+  /// Creates an ast::StageAttribute
+  /// @param source the source information
+  /// @param stage the pipeline stage
+  /// @returns the stage attribute pointer
+  const ast::StageAttribute* Stage(const Source& source,
+                                   ast::PipelineStage stage) {
+    return create<ast::StageAttribute>(source, stage);
+  }
+
+  /// Creates an ast::StageAttribute
+  /// @param stage the pipeline stage
+  /// @returns the stage attribute pointer
+  const ast::StageAttribute* Stage(ast::PipelineStage stage) {
+    return create<ast::StageAttribute>(source_, stage);
+  }
+
+  /// Creates an ast::WorkgroupAttribute
+  /// @param x the x dimension expression
+  /// @returns the workgroup attribute pointer
+  template <typename EXPR_X>
+  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x) {
+    return WorkgroupSize(std::forward<EXPR_X>(x), nullptr, nullptr);
+  }
+
+  /// Creates an ast::WorkgroupAttribute
+  /// @param x the x dimension expression
+  /// @param y the y dimension expression
+  /// @returns the workgroup attribute pointer
+  template <typename EXPR_X, typename EXPR_Y>
+  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x, EXPR_Y&& y) {
+    return WorkgroupSize(std::forward<EXPR_X>(x), std::forward<EXPR_Y>(y),
+                         nullptr);
+  }
+
+  /// Creates an ast::WorkgroupAttribute
+  /// @param source the source information
+  /// @param x the x dimension expression
+  /// @param y the y dimension expression
+  /// @param z the z dimension expression
+  /// @returns the workgroup attribute pointer
+  template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z>
+  const ast::WorkgroupAttribute* WorkgroupSize(const Source& source,
+                                               EXPR_X&& x,
+                                               EXPR_Y&& y,
+                                               EXPR_Z&& z) {
+    return create<ast::WorkgroupAttribute>(
+        source, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)),
+        Expr(std::forward<EXPR_Z>(z)));
+  }
+
+  /// Creates an ast::WorkgroupAttribute
+  /// @param x the x dimension expression
+  /// @param y the y dimension expression
+  /// @param z the z dimension expression
+  /// @returns the workgroup attribute pointer
+  template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z>
+  const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x,
+                                               EXPR_Y&& y,
+                                               EXPR_Z&& z) {
+    return create<ast::WorkgroupAttribute>(
+        source_, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)),
+        Expr(std::forward<EXPR_Z>(z)));
+  }
+
+  /// Creates an ast::DisableValidationAttribute
+  /// @param validation the validation to disable
+  /// @returns the disable validation attribute pointer
+  const ast::DisableValidationAttribute* Disable(
+      ast::DisabledValidation validation) {
+    return ASTNodes().Create<ast::DisableValidationAttribute>(ID(), validation);
+  }
+
+  /// Sets the current builder source to `src`
+  /// @param src the Source used for future create() calls
+  void SetSource(const Source& src) {
+    AssertNotMoved();
+    source_ = src;
+  }
+
+  /// Sets the current builder source to `loc`
+  /// @param loc the Source used for future create() calls
+  void SetSource(const Source::Location& loc) {
+    AssertNotMoved();
+    source_ = Source(loc);
+  }
+
+  /// Helper for returning the resolved semantic type of the expression `expr`.
+  /// @note As the Resolver is run when the Program is built, this will only be
+  /// useful for the Resolver itself and tests that use their own Resolver.
+  /// @param expr the AST expression
+  /// @return the resolved semantic type for the expression, or nullptr if the
+  /// expression has no resolved type.
+  const sem::Type* TypeOf(const ast::Expression* expr) const;
+
+  /// Helper for returning the resolved semantic type of the variable `var`.
+  /// @note As the Resolver is run when the Program is built, this will only be
+  /// useful for the Resolver itself and tests that use their own Resolver.
+  /// @param var the AST variable
+  /// @return the resolved semantic type for the variable, or nullptr if the
+  /// variable has no resolved type.
+  const sem::Type* TypeOf(const ast::Variable* var) const;
+
+  /// Helper for returning the resolved semantic type of the AST type `type`.
+  /// @note As the Resolver is run when the Program is built, this will only be
+  /// useful for the Resolver itself and tests that use their own Resolver.
+  /// @param type the AST type
+  /// @return the resolved semantic type for the type, or nullptr if the type
+  /// has no resolved type.
+  const sem::Type* TypeOf(const ast::Type* type) const;
+
+  /// Helper for returning the resolved semantic type of the AST type
+  /// declaration `type_decl`.
+  /// @note As the Resolver is run when the Program is built, this will only be
+  /// useful for the Resolver itself and tests that use their own Resolver.
+  /// @param type_decl the AST type declaration
+  /// @return the resolved semantic type for the type declaration, or nullptr if
+  /// the type declaration has no resolved type.
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
+
+  /// Wraps the ast::Expression in a statement. This is used by tests that
+  /// construct a partial AST and require the Resolver to reach these
+  /// nodes.
+  /// @param expr the ast::Expression to be wrapped by an ast::Statement
+  /// @return the ast::Statement that wraps the ast::Expression
+  const ast::Statement* WrapInStatement(const ast::Expression* expr);
+  /// Wraps the ast::Variable in a ast::VariableDeclStatement. This is used by
+  /// tests that construct a partial AST and require the Resolver to reach
+  /// these nodes.
+  /// @param v the ast::Variable to be wrapped by an ast::VariableDeclStatement
+  /// @return the ast::VariableDeclStatement that wraps the ast::Variable
+  const ast::VariableDeclStatement* WrapInStatement(const ast::Variable* v);
+  /// Returns the statement argument. Used as a passthrough-overload by
+  /// WrapInFunction().
+  /// @param stmt the ast::Statement
+  /// @return `stmt`
+  const ast::Statement* WrapInStatement(const ast::Statement* stmt);
+  /// Wraps the list of arguments in a simple function so that each is reachable
+  /// by the Resolver.
+  /// @param args a mix of ast::Expression, ast::Statement, ast::Variables.
+  /// @returns the function
+  template <typename... ARGS>
+  const ast::Function* WrapInFunction(ARGS&&... args) {
+    ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...};
+    return WrapInFunction(std::move(stmts));
+  }
+  /// @param stmts a list of ast::Statement that will be wrapped by a function,
+  /// so that each statement is reachable by the Resolver.
+  /// @returns the function
+  const ast::Function* WrapInFunction(ast::StatementList stmts);
+
+  /// The builder types
+  TypesBuilder const ty{this};
+
+ protected:
+  /// Asserts that the builder has not been moved.
+  void AssertNotMoved() const;
+
+ private:
+  ProgramID id_;
+  sem::Manager types_;
+  ASTNodeAllocator ast_nodes_;
+  SemNodeAllocator sem_nodes_;
+  ast::Module* ast_;
+  sem::Info sem_;
+  SymbolTable symbols_{id_};
+  diag::List diagnostics_;
+
+  /// The source to use when creating AST nodes without providing a Source as
+  /// the first argument.
+  Source source_;
+
+  /// Set by SetResolveOnBuild(). If set, the Resolver will be run on the
+  /// program when built.
+  bool resolve_on_build_ = true;
+
+  /// Set by MarkAsMoved(). Once set, no methods may be called on this builder.
+  bool moved_ = false;
+};
+
+//! @cond Doxygen_Suppress
+// Various template specializations for ProgramBuilder::TypesBuilder::CToAST.
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::i32> {
+  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->i32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::u32> {
+  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->u32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::f32> {
+  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->f32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<bool> {
+  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->bool_();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<void> {
+  static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->void_();
+  }
+};
+//! @endcond
+
+/// @param builder the ProgramBuilder
+/// @returns the ProgramID of the ProgramBuilder
+inline ProgramID ProgramIDOf(const ProgramBuilder* builder) {
+  return builder->ID();
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_PROGRAM_BUILDER_H_
diff --git a/src/tint/program_builder_test.cc b/src/tint/program_builder_test.cc
new file mode 100644
index 0000000..f18aa10
--- /dev/null
+++ b/src/tint/program_builder_test.cc
@@ -0,0 +1,72 @@
+// 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/program_builder.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+using ProgramBuilderTest = testing::Test;
+
+TEST_F(ProgramBuilderTest, IDsAreUnique) {
+  Program program_a(ProgramBuilder{});
+  Program program_b(ProgramBuilder{});
+  Program program_c(ProgramBuilder{});
+  EXPECT_NE(program_a.ID(), program_b.ID());
+  EXPECT_NE(program_b.ID(), program_c.ID());
+  EXPECT_NE(program_c.ID(), program_a.ID());
+}
+
+TEST_F(ProgramBuilderTest, WrapDoesntAffectInner) {
+  Program inner([] {
+    ProgramBuilder builder;
+    auto* ty = builder.ty.f32();
+    builder.Func("a", {}, ty, {}, {});
+    return builder;
+  }());
+
+  ASSERT_EQ(inner.AST().Functions().size(), 1u);
+  ASSERT_TRUE(inner.Symbols().Get("a").IsValid());
+  ASSERT_FALSE(inner.Symbols().Get("b").IsValid());
+
+  ProgramBuilder outer = ProgramBuilder::Wrap(&inner);
+
+  ASSERT_EQ(inner.AST().Functions().size(), 1u);
+  ASSERT_EQ(outer.AST().Functions().size(), 1u);
+  EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
+  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
+  EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
+  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
+  EXPECT_TRUE(outer.Symbols().Get("a").IsValid());
+  EXPECT_FALSE(inner.Symbols().Get("b").IsValid());
+  EXPECT_FALSE(outer.Symbols().Get("b").IsValid());
+
+  auto* ty = outer.ty.f32();
+  outer.Func("b", {}, ty, {}, {});
+
+  ASSERT_EQ(inner.AST().Functions().size(), 1u);
+  ASSERT_EQ(outer.AST().Functions().size(), 2u);
+  EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
+  EXPECT_EQ(outer.AST().Functions()[1]->symbol, outer.Symbols().Get("b"));
+  EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
+  EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
+  EXPECT_TRUE(outer.Symbols().Get("a").IsValid());
+  EXPECT_FALSE(inner.Symbols().Get("b").IsValid());
+  EXPECT_TRUE(outer.Symbols().Get("b").IsValid());
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/program_id.cc b/src/tint/program_id.cc
new file mode 100644
index 0000000..5350de7
--- /dev/null
+++ b/src/tint/program_id.cc
@@ -0,0 +1,58 @@
+// 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/program_id.h"
+
+#include <atomic>
+
+namespace tint {
+
+namespace {
+
+std::atomic<uint32_t> next_program_id{1};
+
+}  // namespace
+
+ProgramID::ProgramID() = default;
+
+ProgramID::ProgramID(uint32_t id) : val(id) {}
+
+ProgramID ProgramID::New() {
+  return ProgramID(next_program_id++);
+}
+
+namespace detail {
+
+/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and
+/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs
+/// `a` and `b` are equal.
+void AssertProgramIDsEqual(ProgramID a,
+                           ProgramID b,
+                           bool if_valid,
+                           diag::System system,
+                           const char* msg,
+                           const char* file,
+                           size_t line) {
+  if (a == b) {
+    return;  // matched
+  }
+  if (if_valid && (!a || !b)) {
+    return;  //  a or b were not valid
+  }
+  diag::List diagnostics;
+  tint::InternalCompilerError(file, line, system, diagnostics) << msg;
+}
+
+}  // namespace detail
+}  // namespace tint
diff --git a/src/tint/program_id.h b/src/tint/program_id.h
new file mode 100644
index 0000000..09e232f
--- /dev/null
+++ b/src/tint/program_id.h
@@ -0,0 +1,126 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_PROGRAM_ID_H_
+#define SRC_TINT_PROGRAM_ID_H_
+
+#include <stdint.h>
+#include <iostream>
+#include <utility>
+
+#include "src/tint/debug.h"
+
+namespace tint {
+
+/// If 1 then checks are enabled that AST nodes are not leaked from one program
+/// to another.
+/// TODO(bclayton): We'll want to disable this in production builds. For now we
+/// always check.
+#define TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS 1
+
+/// A ProgramID is a unique identifier of a Program.
+/// ProgramID can be used to ensure that objects referenced by the Program are
+/// owned exclusively by that Program and have accidentally not leaked from
+/// another Program.
+class ProgramID {
+ public:
+  /// Constructor
+  ProgramID();
+
+  /// @returns a new. globally unique ProgramID
+  static ProgramID New();
+
+  /// Equality operator
+  /// @param rhs the other ProgramID
+  /// @returns true if the ProgramIDs are equal
+  bool operator==(const ProgramID& rhs) const { return val == rhs.val; }
+
+  /// Inequality operator
+  /// @param rhs the other ProgramID
+  /// @returns true if the ProgramIDs are not equal
+  bool operator!=(const ProgramID& rhs) const { return val != rhs.val; }
+
+  /// @returns the numerical identifier value
+  uint32_t Value() const { return val; }
+
+  /// @returns true if this ProgramID is valid
+  operator bool() const { return val != 0; }
+
+ private:
+  explicit ProgramID(uint32_t);
+
+  uint32_t val = 0;
+};
+
+/// A simple pass-through function for ProgramID. Intended to be overloaded for
+/// other types.
+/// @param id a ProgramID
+/// @returns id. Simple pass-through function
+inline ProgramID ProgramIDOf(ProgramID id) {
+  return id;
+}
+
+/// Writes the ProgramID to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param id the program identifier to write
+/// @returns out so calls can be chained
+inline std::ostream& operator<<(std::ostream& out, ProgramID id) {
+  out << "Program<" << id.Value() << ">";
+  return out;
+}
+
+namespace detail {
+
+/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and
+/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs
+/// `a` and `b` are equal.
+void AssertProgramIDsEqual(ProgramID a,
+                           ProgramID b,
+                           bool if_valid,
+                           diag::System system,
+                           const char* msg,
+                           const char* file,
+                           size_t line);
+
+}  // namespace detail
+
+/// TINT_ASSERT_PROGRAM_IDS_EQUAL(SYSTEM, A, B) is a macro that asserts that the
+/// program identifiers for A and B are equal.
+///
+/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(SYSTEM, A, B) is a macro that asserts
+/// that the program identifiers for A and B are equal, if both A and B have
+/// valid program identifiers.
+#if TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS
+#define TINT_ASSERT_PROGRAM_IDS_EQUAL(system, a, b)                          \
+  detail::AssertProgramIDsEqual(                                             \
+      ProgramIDOf(a), ProgramIDOf(b), false, tint::diag::System::system,     \
+      "TINT_ASSERT_PROGRAM_IDS_EQUAL(" #system "," #a ", " #b ")", __FILE__, \
+      __LINE__)
+#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(system, a, b)                 \
+  detail::AssertProgramIDsEqual(                                             \
+      ProgramIDOf(a), ProgramIDOf(b), true, tint::diag::System::system,      \
+      "TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(" #system ", " #a ", " #b ")", \
+      __FILE__, __LINE__)
+#else
+#define TINT_ASSERT_PROGRAM_IDS_EQUAL(a, b) \
+  do {                                      \
+  } while (false)
+#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(a, b) \
+  do {                                               \
+  } while (false)
+#endif
+
+}  // namespace tint
+
+#endif  // SRC_TINT_PROGRAM_ID_H_
diff --git a/src/tint/program_test.cc b/src/tint/program_test.cc
new file mode 100644
index 0000000..a161ecb
--- /dev/null
+++ b/src/tint/program_test.cc
@@ -0,0 +1,110 @@
+// Copyright 2020 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 "gtest/gtest-spi.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint {
+namespace {
+
+using ProgramTest = ast::TestHelper;
+
+TEST_F(ProgramTest, Unbuilt) {
+  Program program;
+  EXPECT_FALSE(program.IsValid());
+}
+
+TEST_F(ProgramTest, Creation) {
+  Program program(std::move(*this));
+  EXPECT_EQ(program.AST().Functions().size(), 0u);
+}
+
+TEST_F(ProgramTest, EmptyIsValid) {
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
+}
+
+TEST_F(ProgramTest, IDsAreUnique) {
+  Program program_a(ProgramBuilder{});
+  Program program_b(ProgramBuilder{});
+  Program program_c(ProgramBuilder{});
+  EXPECT_NE(program_a.ID(), program_b.ID());
+  EXPECT_NE(program_b.ID(), program_c.ID());
+  EXPECT_NE(program_c.ID(), program_a.ID());
+}
+
+TEST_F(ProgramTest, Assert_GlobalVariable) {
+  Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
+}
+
+TEST_F(ProgramTest, Assert_NullGlobalVariable) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.AST().AddGlobalVariable(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ProgramTest, Assert_NullTypeDecl) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.AST().AddTypeDecl(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ProgramTest, Assert_Null_Function) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.AST().AddFunction(nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ProgramTest, DiagnosticsMove) {
+  Diagnostics().add_error(diag::System::Program, "an error message");
+
+  Program program_a(std::move(*this));
+  EXPECT_FALSE(program_a.IsValid());
+  EXPECT_EQ(program_a.Diagnostics().count(), 1u);
+  EXPECT_EQ(program_a.Diagnostics().error_count(), 1u);
+  EXPECT_EQ(program_a.Diagnostics().begin()->message, "an error message");
+
+  Program program_b(std::move(program_a));
+  EXPECT_FALSE(program_b.IsValid());
+  EXPECT_EQ(program_b.Diagnostics().count(), 1u);
+  EXPECT_EQ(program_b.Diagnostics().error_count(), 1u);
+  EXPECT_EQ(program_b.Diagnostics().begin()->message, "an error message");
+}
+
+TEST_F(ProgramTest, ReuseMovedFromVariable) {
+  Program a(std::move(*this));
+  EXPECT_TRUE(a.IsValid());
+
+  Program b = std::move(a);
+  EXPECT_TRUE(b.IsValid());
+
+  a = std::move(b);
+  EXPECT_TRUE(a.IsValid());
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/reader/reader.cc b/src/tint/reader/reader.cc
new file mode 100644
index 0000000..2937ff8
--- /dev/null
+++ b/src/tint/reader/reader.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 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/reader/reader.h"
+
+namespace tint {
+namespace reader {
+
+Reader::Reader() = default;
+
+Reader::~Reader() = default;
+
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/reader.h b/src/tint/reader/reader.h
new file mode 100644
index 0000000..6c9c52a
--- /dev/null
+++ b/src/tint/reader/reader.h
@@ -0,0 +1,65 @@
+// Copyright 2020 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_READER_READER_H_
+#define SRC_TINT_READER_READER_H_
+
+#include <string>
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace reader {
+
+/// Base class for input readers
+class Reader {
+ public:
+  virtual ~Reader();
+
+  /// Parses the input data
+  /// @returns true if the parse was successful
+  virtual bool Parse() = 0;
+
+  /// @returns true if an error was encountered.
+  bool has_error() const { return diags_.contains_errors(); }
+
+  /// @returns the parser error string
+  std::string error() const {
+    diag::Formatter formatter{{false, false, false, false}};
+    return formatter.format(diags_);
+  }
+
+  /// @returns the full list of diagnostic messages.
+  const diag::List& diagnostics() const { return diags_; }
+
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
+  virtual Program program() = 0;
+
+ protected:
+  /// Constructor
+  Reader();
+
+  /// Sets the diagnostic messages
+  /// @param diags the list of diagnostic messages
+  void set_diagnostics(const diag::List& diags) { diags_ = diags; }
+
+  /// All diagnostic messages from the reader.
+  diag::List diags_;
+};
+
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_READER_H_
diff --git a/src/reader/spirv/README.md b/src/tint/reader/spirv/README.md
similarity index 100%
rename from src/reader/spirv/README.md
rename to src/tint/reader/spirv/README.md
diff --git a/src/tint/reader/spirv/construct.cc b/src/tint/reader/spirv/construct.cc
new file mode 100644
index 0000000..23808e1
--- /dev/null
+++ b/src/tint/reader/spirv/construct.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 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/reader/spirv/construct.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+Construct::Construct(const Construct* the_parent,
+                     int the_depth,
+                     Kind the_kind,
+                     uint32_t the_begin_id,
+                     uint32_t the_end_id,
+                     uint32_t the_begin_pos,
+                     uint32_t the_end_pos,
+                     uint32_t the_scope_end_pos)
+    : parent(the_parent),
+      enclosing_loop(
+          // Compute the enclosing loop construct. Doing this in the
+          // constructor member list lets us make the member const.
+          // Compare parent depth because loop and continue are siblings and
+          // it's incidental which will appear on the stack first.
+          the_kind == kLoop
+              ? this
+              : ((parent && parent->depth < the_depth) ? parent->enclosing_loop
+                                                       : nullptr)),
+      enclosing_continue(
+          // Compute the enclosing continue construct. Doing this in the
+          // constructor member list lets us make the member const.
+          // Compare parent depth because loop and continue are siblings and
+          // it's incidental which will appear on the stack first.
+          the_kind == kContinue ? this
+                                : ((parent && parent->depth < the_depth)
+                                       ? parent->enclosing_continue
+                                       : nullptr)),
+      enclosing_loop_or_continue_or_switch(
+          // Compute the enclosing loop or continue or switch construct.
+          // Doing this in the constructor member list lets us make the
+          // member const.
+          // Compare parent depth because loop and continue are siblings and
+          // it's incidental which will appear on the stack first.
+          (the_kind == kLoop || the_kind == kContinue ||
+           the_kind == kSwitchSelection)
+              ? this
+              : ((parent && parent->depth < the_depth)
+                     ? parent->enclosing_loop_or_continue_or_switch
+                     : nullptr)),
+      depth(the_depth),
+      kind(the_kind),
+      begin_id(the_begin_id),
+      end_id(the_end_id),
+      begin_pos(the_begin_pos),
+      end_pos(the_end_pos),
+      scope_end_pos(the_scope_end_pos) {}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/construct.h b/src/tint/reader/spirv/construct.h
new file mode 100644
index 0000000..898b682
--- /dev/null
+++ b/src/tint/reader/spirv/construct.h
@@ -0,0 +1,278 @@
+// Copyright 2020 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_READER_SPIRV_CONSTRUCT_H_
+#define SRC_TINT_READER_SPIRV_CONSTRUCT_H_
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// A structured control flow construct, consisting of a set of basic blocks.
+/// A construct is a span of blocks in the computed block order,
+/// and will appear contiguously in the WGSL source.
+///
+/// SPIR-V (2.11 Structured Control Flow) defines:
+///   - loop construct
+///   - continue construct
+///   - selection construct
+/// We also define a "function construct" consisting of all the basic blocks in
+/// the function.
+///
+/// The first block in a construct (by computed block order) is called a
+/// "header". For the constructs defined by SPIR-V, the header block is the
+/// basic block containing the merge instruction.  The header for the function
+/// construct is the entry block of the function.
+///
+/// Given two constructs A and B, we say "A encloses B" if B is a subset of A,
+/// i.e. if every basic block in B is also in A.  Note that a construct encloses
+/// itself.
+///
+/// In a valid SPIR-V module, constructs will nest, meaning given
+/// constructs A and B, either A encloses B, or B encloses A, or
+/// or they are disjoint (have no basic blocks in commont).
+///
+/// A loop in a high level language translates into either:
+//
+///  - a single-block loop, where the loop header branches back to itself.
+///     In this case this single-block loop consists only of the *continue
+///     construct*.  There is no "loop construct" for this case.
+//
+///  - a multi-block loop, where the loop back-edge is different from the loop
+///     header.
+///     This case has both a non-empty loop construct containing at least the
+///     loop header, and a non-empty continue construct, containing at least the
+///     back-edge block.
+///
+/// We care about two kinds of selection constructs:
+///
+///  - if-selection: where the header block ends in OpBranchConditional
+///
+///  - switch-selection: where the header block ends in OpSwitch
+///
+struct Construct {
+  /// Enumeration for the kinds of structured constructs.
+  enum Kind {
+    /// The whole function.
+    kFunction,
+    /// A SPIR-V selection construct, header basic block ending in
+    /// OpBrancConditional.
+    kIfSelection,
+    /// A SPIR-V selection construct, header basic block ending in OpSwitch.
+    kSwitchSelection,
+    /// A SPIR-V loop construct.
+    kLoop,
+    /// A SPIR-V continue construct.
+    kContinue,
+  };
+
+  /// Constructor
+  /// @param the_parent parent construct
+  /// @param the_depth construct nesting depth
+  /// @param the_kind construct kind
+  /// @param the_begin_id block id of the first block in the construct
+  /// @param the_end_id block id of the first block after the construct, or 0
+  /// @param the_begin_pos block order position of the_begin_id
+  /// @param the_end_pos block order position of the_end_id or a too-large value
+  /// @param the_scope_end_pos block position of the first block past the end of
+  /// the WGSL scope
+  Construct(const Construct* the_parent,
+            int the_depth,
+            Kind the_kind,
+            uint32_t the_begin_id,
+            uint32_t the_end_id,
+            uint32_t the_begin_pos,
+            uint32_t the_end_pos,
+            uint32_t the_scope_end_pos);
+
+  /// @param pos a block position
+  /// @returns true if the given block position is inside this construct.
+  bool ContainsPos(uint32_t pos) const {
+    return begin_pos <= pos && pos < end_pos;
+  }
+  /// Returns true if the given block position is inside the WGSL scope
+  /// corresponding to this construct. A loop construct's WGSL scope encloses
+  /// the associated continue construct. Otherwise the WGSL scope extent is the
+  /// same as the block extent.
+  /// @param pos a block position
+  /// @returns true if the given block position is inside the WGSL scope.
+  bool ScopeContainsPos(uint32_t pos) const {
+    return begin_pos <= pos && pos < scope_end_pos;
+  }
+
+  /// The nearest enclosing construct other than itself, or nullptr if
+  /// this construct represents the entire function.
+  const Construct* const parent = nullptr;
+  /// The nearest enclosing loop construct, if one exists.  Points to `this`
+  /// when this is a loop construct.
+  const Construct* const enclosing_loop = nullptr;
+  /// The nearest enclosing continue construct, if one exists.  Points to
+  /// `this` when this is a contnue construct.
+  const Construct* const enclosing_continue = nullptr;
+  /// The nearest enclosing loop construct or continue construct or
+  /// switch-selection construct, if one exists. The signficance is
+  /// that a high level language "break" will branch to the merge block
+  /// of such an enclosing construct. Points to `this` when this is
+  /// a loop construct, a continue construct, or a switch-selection construct.
+  const Construct* const enclosing_loop_or_continue_or_switch = nullptr;
+
+  /// Control flow nesting depth. The entry block is at nesting depth 0.
+  const int depth = 0;
+  /// The construct kind
+  const Kind kind = kFunction;
+  /// The id of the first block in this structure.
+  const uint32_t begin_id = 0;
+  /// 0 for kFunction, or the id of the block immediately after this construct
+  /// in the computed block order.
+  const uint32_t end_id = 0;
+  /// The position of block #begin_id in the computed block order.
+  const uint32_t begin_pos = 0;
+  /// The position of block #end_id in the block order, or the number of
+  /// block order elements if #end_id is 0.
+  const uint32_t end_pos = 0;
+  /// The position of the first block after the WGSL scope corresponding to
+  /// this construct.
+  const uint32_t scope_end_pos = 0;
+};
+
+using ConstructList = std::vector<std::unique_ptr<Construct>>;
+
+/// Converts a construct kind to a string.
+/// @param kind the construct kind to convert
+/// @returns the string representation
+inline std::string ToString(Construct::Kind kind) {
+  switch (kind) {
+    case Construct::kFunction:
+      return "Function";
+    case Construct::kIfSelection:
+      return "IfSelection";
+    case Construct::kSwitchSelection:
+      return "SwitchSelection";
+    case Construct::kLoop:
+      return "Loop";
+    case Construct::kContinue:
+      return "Continue";
+  }
+  return "NONE";
+}
+
+/// Converts a construct into a short summary string.
+/// @param c the construct, which can be null
+/// @returns a short summary string
+inline std::string ToStringBrief(const Construct* c) {
+  if (c) {
+    std::stringstream ss;
+    ss << ToString(c->kind) << "@" << c->begin_id;
+    return ss.str();
+  }
+  return "null";
+}
+
+/// Emits a construct to a stream.
+/// @param o the stream
+/// @param c the structured construct
+/// @returns the stream
+inline std::ostream& operator<<(std::ostream& o, const Construct& c) {
+  o << "Construct{ " << ToString(c.kind) << " [" << c.begin_pos << ","
+    << c.end_pos << ")"
+    << " begin_id:" << c.begin_id << " end_id:" << c.end_id
+    << " depth:" << c.depth;
+
+  o << " parent:" << ToStringBrief(c.parent);
+
+  if (c.scope_end_pos != c.end_pos) {
+    o << " scope:[" << c.begin_pos << "," << c.scope_end_pos << ")";
+  }
+
+  if (c.enclosing_loop) {
+    o << " in-l:" << ToStringBrief(c.enclosing_loop);
+  }
+
+  if (c.enclosing_continue) {
+    o << " in-c:" << ToStringBrief(c.enclosing_continue);
+  }
+
+  if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) &&
+      (c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) {
+    o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch);
+  }
+
+  o << " }";
+  return o;
+}
+
+/// Emits a construct to a stream.
+/// @param o the stream
+/// @param c the structured construct
+/// @returns the stream
+inline std::ostream& operator<<(std::ostream& o,
+                                const std::unique_ptr<Construct>& c) {
+  return o << *(c.get());
+}
+
+/// Converts a construct to a string.
+/// @param c the construct
+/// @returns the string representation
+inline std::string ToString(const Construct& c) {
+  std::stringstream ss;
+  ss << c;
+  return ss.str();
+}
+
+/// Converts a construct to a string.
+/// @param c the construct
+/// @returns the string representation
+inline std::string ToString(const Construct* c) {
+  return c ? ToString(*c) : ToStringBrief(c);
+}
+
+/// Converts a unique pointer to a construct to a string.
+/// @param c the construct
+/// @returns the string representation
+inline std::string ToString(const std::unique_ptr<Construct>& c) {
+  return ToString(*(c.get()));
+}
+
+/// Emits a construct list to a stream.
+/// @param o the stream
+/// @param cl the construct list
+/// @returns the stream
+inline std::ostream& operator<<(std::ostream& o, const ConstructList& cl) {
+  o << "ConstructList{\n";
+  for (const auto& c : cl) {
+    o << "  " << c << "\n";
+  }
+  o << "}";
+  return o;
+}
+
+/// Converts a construct list to a string.
+/// @param cl the construct list
+/// @returns the string representation
+inline std::string ToString(const ConstructList& cl) {
+  std::stringstream ss;
+  ss << cl;
+  return ss.str();
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_CONSTRUCT_H_
diff --git a/src/tint/reader/spirv/entry_point_info.cc b/src/tint/reader/spirv/entry_point_info.cc
new file mode 100644
index 0000000..5258606
--- /dev/null
+++ b/src/tint/reader/spirv/entry_point_info.cc
@@ -0,0 +1,44 @@
+// 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/reader/spirv/entry_point_info.h"
+
+#include <utility>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+EntryPointInfo::EntryPointInfo(std::string the_name,
+                               ast::PipelineStage the_stage,
+                               bool the_owns_inner_implementation,
+                               std::string the_inner_name,
+                               std::vector<uint32_t>&& the_inputs,
+                               std::vector<uint32_t>&& the_outputs,
+                               GridSize the_wg_size)
+    : name(the_name),
+      stage(the_stage),
+      owns_inner_implementation(the_owns_inner_implementation),
+      inner_name(std::move(the_inner_name)),
+      inputs(std::move(the_inputs)),
+      outputs(std::move(the_outputs)),
+      workgroup_size(the_wg_size) {}
+
+EntryPointInfo::EntryPointInfo(const EntryPointInfo&) = default;
+
+EntryPointInfo::~EntryPointInfo() = default;
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/entry_point_info.h b/src/tint/reader/spirv/entry_point_info.h
new file mode 100644
index 0000000..e65b79d
--- /dev/null
+++ b/src/tint/reader/spirv/entry_point_info.h
@@ -0,0 +1,95 @@
+// Copyright 2020 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_READER_SPIRV_ENTRY_POINT_INFO_H_
+#define SRC_TINT_READER_SPIRV_ENTRY_POINT_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/pipeline_stage.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// The size of an integer-coordinate grid, in the x, y, and z dimensions.
+struct GridSize {
+  /// x value
+  uint32_t x = 0;
+  /// y value
+  uint32_t y = 0;
+  /// z value
+  uint32_t z = 0;
+};
+
+/// Entry point information for a function
+struct EntryPointInfo {
+  /// Constructor.
+  /// @param the_name the name of the entry point
+  /// @param the_stage the pipeline stage
+  /// @param the_owns_inner_implementation if true, this entry point is
+  /// responsible for generating the inner implementation function.
+  /// @param the_inner_name the name of the inner implementation function of the
+  /// entry point
+  /// @param the_inputs list of IDs for Input variables used by the shader
+  /// @param the_outputs list of IDs for Output variables used by the shader
+  /// @param the_wg_size the workgroup_size, for a compute shader
+  EntryPointInfo(std::string the_name,
+                 ast::PipelineStage the_stage,
+                 bool the_owns_inner_implementation,
+                 std::string the_inner_name,
+                 std::vector<uint32_t>&& the_inputs,
+                 std::vector<uint32_t>&& the_outputs,
+                 GridSize the_wg_size);
+  /// Copy constructor
+  /// @param other the other entry point info to be built from
+  EntryPointInfo(const EntryPointInfo& other);
+  /// Destructor
+  ~EntryPointInfo();
+
+  /// The entry point name.
+  /// In the WGSL output, this function will have pipeline inputs and outputs
+  /// as parameters. This function will store them into Private variables,
+  /// and then call the "inner" function, named by the next memeber.
+  /// Then outputs are copied from the private variables to the return value.
+  std::string name;
+  /// The entry point stage
+  ast::PipelineStage stage = ast::PipelineStage::kNone;
+
+  /// True when this entry point is responsible for generating the
+  /// inner implementation function.  False when this is the second entry
+  /// point encountered for the same function in SPIR-V. It's unusual, but
+  /// possible for the same function to be the implementation for multiple
+  /// entry points.
+  bool owns_inner_implementation;
+  /// The name of the inner implementation function of the entry point.
+  std::string inner_name;
+  /// IDs of pipeline input variables, sorted and without duplicates.
+  std::vector<uint32_t> inputs;
+  /// IDs of pipeline output variables, sorted and without duplicates.
+  std::vector<uint32_t> outputs;
+
+  /// If this is a compute shader, this is the workgroup size in the x, y,
+  /// and z dimensions set via LocalSize, or via the composite value
+  /// decorated as the WorkgroupSize BuiltIn.  The WorkgroupSize builtin
+  /// takes priority.
+  GridSize workgroup_size;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_ENTRY_POINT_INFO_H_
diff --git a/src/tint/reader/spirv/enum_converter.cc b/src/tint/reader/spirv/enum_converter.cc
new file mode 100644
index 0000000..a197bac
--- /dev/null
+++ b/src/tint/reader/spirv/enum_converter.cc
@@ -0,0 +1,182 @@
+// Copyright 2020 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/reader/spirv/enum_converter.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+EnumConverter::EnumConverter(const FailStream& fs) : fail_stream_(fs) {}
+
+EnumConverter::~EnumConverter() = default;
+
+ast::PipelineStage EnumConverter::ToPipelineStage(SpvExecutionModel model) {
+  switch (model) {
+    case SpvExecutionModelVertex:
+      return ast::PipelineStage::kVertex;
+    case SpvExecutionModelFragment:
+      return ast::PipelineStage::kFragment;
+    case SpvExecutionModelGLCompute:
+      return ast::PipelineStage::kCompute;
+    default:
+      break;
+  }
+
+  Fail() << "unknown SPIR-V execution model: " << uint32_t(model);
+  return ast::PipelineStage::kNone;
+}
+
+ast::StorageClass EnumConverter::ToStorageClass(const SpvStorageClass sc) {
+  switch (sc) {
+    case SpvStorageClassInput:
+      return ast::StorageClass::kInput;
+    case SpvStorageClassOutput:
+      return ast::StorageClass::kOutput;
+    case SpvStorageClassUniform:
+      return ast::StorageClass::kUniform;
+    case SpvStorageClassWorkgroup:
+      return ast::StorageClass::kWorkgroup;
+    case SpvStorageClassUniformConstant:
+      return ast::StorageClass::kNone;
+    case SpvStorageClassStorageBuffer:
+      return ast::StorageClass::kStorage;
+    case SpvStorageClassPrivate:
+      return ast::StorageClass::kPrivate;
+    case SpvStorageClassFunction:
+      return ast::StorageClass::kFunction;
+    default:
+      break;
+  }
+
+  Fail() << "unknown SPIR-V storage class: " << uint32_t(sc);
+  return ast::StorageClass::kInvalid;
+}
+
+ast::Builtin EnumConverter::ToBuiltin(SpvBuiltIn b) {
+  switch (b) {
+    case SpvBuiltInPosition:
+      return ast::Builtin::kPosition;
+    case SpvBuiltInVertexIndex:
+      return ast::Builtin::kVertexIndex;
+    case SpvBuiltInInstanceIndex:
+      return ast::Builtin::kInstanceIndex;
+    case SpvBuiltInFrontFacing:
+      return ast::Builtin::kFrontFacing;
+    case SpvBuiltInFragCoord:
+      return ast::Builtin::kPosition;
+    case SpvBuiltInFragDepth:
+      return ast::Builtin::kFragDepth;
+    case SpvBuiltInLocalInvocationId:
+      return ast::Builtin::kLocalInvocationId;
+    case SpvBuiltInLocalInvocationIndex:
+      return ast::Builtin::kLocalInvocationIndex;
+    case SpvBuiltInGlobalInvocationId:
+      return ast::Builtin::kGlobalInvocationId;
+    case SpvBuiltInWorkgroupId:
+      return ast::Builtin::kWorkgroupId;
+    case SpvBuiltInSampleId:
+      return ast::Builtin::kSampleIndex;
+    case SpvBuiltInSampleMask:
+      return ast::Builtin::kSampleMask;
+    default:
+      break;
+  }
+
+  Fail() << "unknown SPIR-V builtin: " << uint32_t(b);
+  return ast::Builtin::kNone;
+}
+
+ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) {
+  if (arrayed) {
+    switch (dim) {
+      case SpvDim2D:
+        return ast::TextureDimension::k2dArray;
+      case SpvDimCube:
+        return ast::TextureDimension::kCubeArray;
+      default:
+        break;
+    }
+    Fail() << "arrayed dimension must be 2D or Cube. Got " << int(dim);
+    return ast::TextureDimension::kNone;
+  }
+  // Assume non-arrayed
+  switch (dim) {
+    case SpvDim1D:
+      return ast::TextureDimension::k1d;
+    case SpvDim2D:
+      return ast::TextureDimension::k2d;
+    case SpvDim3D:
+      return ast::TextureDimension::k3d;
+    case SpvDimCube:
+      return ast::TextureDimension::kCube;
+    default:
+      break;
+  }
+  Fail() << "invalid dimension: " << int(dim);
+  return ast::TextureDimension::kNone;
+}
+
+ast::TexelFormat EnumConverter::ToTexelFormat(SpvImageFormat fmt) {
+  switch (fmt) {
+    case SpvImageFormatUnknown:
+      return ast::TexelFormat::kNone;
+
+    // 8 bit channels
+    case SpvImageFormatRgba8:
+      return ast::TexelFormat::kRgba8Unorm;
+    case SpvImageFormatRgba8Snorm:
+      return ast::TexelFormat::kRgba8Snorm;
+    case SpvImageFormatRgba8ui:
+      return ast::TexelFormat::kRgba8Uint;
+    case SpvImageFormatRgba8i:
+      return ast::TexelFormat::kRgba8Sint;
+
+    // 16 bit channels
+    case SpvImageFormatRgba16ui:
+      return ast::TexelFormat::kRgba16Uint;
+    case SpvImageFormatRgba16i:
+      return ast::TexelFormat::kRgba16Sint;
+    case SpvImageFormatRgba16f:
+      return ast::TexelFormat::kRgba16Float;
+
+    // 32 bit channels
+    case SpvImageFormatR32ui:
+      return ast::TexelFormat::kR32Uint;
+    case SpvImageFormatR32i:
+      return ast::TexelFormat::kR32Sint;
+    case SpvImageFormatR32f:
+      return ast::TexelFormat::kR32Float;
+    case SpvImageFormatRg32ui:
+      return ast::TexelFormat::kRg32Uint;
+    case SpvImageFormatRg32i:
+      return ast::TexelFormat::kRg32Sint;
+    case SpvImageFormatRg32f:
+      return ast::TexelFormat::kRg32Float;
+    case SpvImageFormatRgba32ui:
+      return ast::TexelFormat::kRgba32Uint;
+    case SpvImageFormatRgba32i:
+      return ast::TexelFormat::kRgba32Sint;
+    case SpvImageFormatRgba32f:
+      return ast::TexelFormat::kRgba32Float;
+    default:
+      break;
+  }
+  Fail() << "invalid image format: " << int(fmt);
+  return ast::TexelFormat::kNone;
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/enum_converter.h b/src/tint/reader/spirv/enum_converter.h
new file mode 100644
index 0000000..d2caac3
--- /dev/null
+++ b/src/tint/reader/spirv/enum_converter.h
@@ -0,0 +1,81 @@
+// Copyright 2020 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_READER_SPIRV_ENUM_CONVERTER_H_
+#define SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_
+
+#include "spirv/unified1/spirv.h"
+#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/reader/spirv/fail_stream.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// A converter from SPIR-V enums to Tint AST enums.
+class EnumConverter {
+ public:
+  /// Creates a new enum converter.
+  /// @param fail_stream the error reporting stream.
+  explicit EnumConverter(const FailStream& fail_stream);
+  /// Destructor
+  ~EnumConverter();
+
+  /// Converts a SPIR-V execution model to a Tint pipeline stage.
+  /// On failure, logs an error and returns kNone
+  /// @param model the SPIR-V entry point execution model
+  /// @returns a Tint AST pipeline stage
+  ast::PipelineStage ToPipelineStage(SpvExecutionModel model);
+
+  /// Converts a SPIR-V storage class to a Tint storage class.
+  /// On failure, logs an error and returns kNone
+  /// @param sc the SPIR-V storage class
+  /// @returns a Tint AST storage class
+  ast::StorageClass ToStorageClass(const SpvStorageClass sc);
+
+  /// Converts a SPIR-V Builtin value a Tint Builtin.
+  /// On failure, logs an error and returns kNone
+  /// @param b the SPIR-V builtin
+  /// @returns a Tint AST builtin
+  ast::Builtin ToBuiltin(SpvBuiltIn b);
+
+  /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension.
+  /// On failure, logs an error and returns kNone
+  /// @param dim the SPIR-V Dim value
+  /// @param arrayed true if the texture is arrayed
+  /// @returns a Tint AST texture dimension
+  ast::TextureDimension ToDim(SpvDim dim, bool arrayed);
+
+  /// Converts a SPIR-V Image Format to a TexelFormat
+  /// On failure, logs an error and returns kNone
+  /// @param fmt the SPIR-V format
+  /// @returns a Tint AST format
+  ast::TexelFormat ToTexelFormat(SpvImageFormat fmt);
+
+ private:
+  /// Registers a failure and returns a stream for log diagnostics.
+  /// @returns a failure stream
+  FailStream Fail() { return fail_stream_.Fail(); }
+
+  FailStream fail_stream_;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_
diff --git a/src/tint/reader/spirv/enum_converter_test.cc b/src/tint/reader/spirv/enum_converter_test.cc
new file mode 100644
index 0000000..eec14f3
--- /dev/null
+++ b/src/tint/reader/spirv/enum_converter_test.cc
@@ -0,0 +1,429 @@
+// Copyright 2020 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/reader/spirv/enum_converter.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+// Pipeline stage
+
+struct PipelineStageCase {
+  SpvExecutionModel model;
+  bool expect_success;
+  ast::PipelineStage expected;
+};
+inline std::ostream& operator<<(std::ostream& out, PipelineStageCase psc) {
+  out << "PipelineStageCase{ SpvExecutionModel:" << int(psc.model)
+      << " expect_success?:" << int(psc.expect_success)
+      << " expected:" << int(psc.expected) << "}";
+  return out;
+}
+
+class SpvPipelineStageTest : public testing::TestWithParam<PipelineStageCase> {
+ public:
+  SpvPipelineStageTest()
+      : success_(true),
+        fail_stream_(&success_, &errors_),
+        converter_(fail_stream_) {}
+
+  std::string error() const { return errors_.str(); }
+
+ protected:
+  bool success_ = true;
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  EnumConverter converter_;
+};
+
+TEST_P(SpvPipelineStageTest, Samples) {
+  const auto params = GetParam();
+
+  const auto result = converter_.ToPipelineStage(params.model);
+  EXPECT_EQ(success_, params.expect_success);
+  if (params.expect_success) {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_TRUE(error().empty());
+  } else {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_THAT(error(),
+                ::testing::StartsWith("unknown SPIR-V execution model:"));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood,
+    SpvPipelineStageTest,
+    testing::Values(PipelineStageCase{SpvExecutionModelVertex, true,
+                                      ast::PipelineStage::kVertex},
+                    PipelineStageCase{SpvExecutionModelFragment, true,
+                                      ast::PipelineStage::kFragment},
+                    PipelineStageCase{SpvExecutionModelGLCompute, true,
+                                      ast::PipelineStage::kCompute}));
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterBad,
+    SpvPipelineStageTest,
+    testing::Values(PipelineStageCase{static_cast<SpvExecutionModel>(9999),
+                                      false, ast::PipelineStage::kNone},
+                    PipelineStageCase{SpvExecutionModelTessellationControl,
+                                      false, ast::PipelineStage::kNone}));
+
+// Storage class
+
+struct StorageClassCase {
+  SpvStorageClass sc;
+  bool expect_success;
+  ast::StorageClass expected;
+};
+inline std::ostream& operator<<(std::ostream& out, StorageClassCase scc) {
+  out << "StorageClassCase{ SpvStorageClass:" << int(scc.sc)
+      << " expect_success?:" << int(scc.expect_success)
+      << " expected:" << int(scc.expected) << "}";
+  return out;
+}
+
+class SpvStorageClassTest : public testing::TestWithParam<StorageClassCase> {
+ public:
+  SpvStorageClassTest()
+      : success_(true),
+        fail_stream_(&success_, &errors_),
+        converter_(fail_stream_) {}
+
+  std::string error() const { return errors_.str(); }
+
+ protected:
+  bool success_ = true;
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  EnumConverter converter_;
+};
+
+TEST_P(SpvStorageClassTest, Samples) {
+  const auto params = GetParam();
+
+  const auto result = converter_.ToStorageClass(params.sc);
+  EXPECT_EQ(success_, params.expect_success);
+  if (params.expect_success) {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_TRUE(error().empty());
+  } else {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_THAT(error(),
+                ::testing::StartsWith("unknown SPIR-V storage class: "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood,
+    SpvStorageClassTest,
+    testing::Values(StorageClassCase{SpvStorageClassInput, true,
+                                     ast::StorageClass::kInput},
+                    StorageClassCase{SpvStorageClassOutput, true,
+                                     ast::StorageClass::kOutput},
+                    StorageClassCase{SpvStorageClassUniform, true,
+                                     ast::StorageClass::kUniform},
+                    StorageClassCase{SpvStorageClassWorkgroup, true,
+                                     ast::StorageClass::kWorkgroup},
+                    StorageClassCase{SpvStorageClassUniformConstant, true,
+                                     ast::StorageClass::kNone},
+                    StorageClassCase{SpvStorageClassStorageBuffer, true,
+                                     ast::StorageClass::kStorage},
+                    StorageClassCase{SpvStorageClassPrivate, true,
+                                     ast::StorageClass::kPrivate},
+                    StorageClassCase{SpvStorageClassFunction, true,
+                                     ast::StorageClass::kFunction}));
+
+INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
+                         SpvStorageClassTest,
+                         testing::Values(StorageClassCase{
+                             static_cast<SpvStorageClass>(9999), false,
+                             ast::StorageClass::kInvalid}));
+
+// Builtin
+
+struct BuiltinCase {
+  SpvBuiltIn builtin;
+  bool expect_success;
+  ast::Builtin expected;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinCase bc) {
+  out << "BuiltinCase{ SpvBuiltIn:" << int(bc.builtin)
+      << " expect_success?:" << int(bc.expect_success)
+      << " expected:" << int(bc.expected) << "}";
+  return out;
+}
+
+class SpvBuiltinTest : public testing::TestWithParam<BuiltinCase> {
+ public:
+  SpvBuiltinTest()
+      : success_(true),
+        fail_stream_(&success_, &errors_),
+        converter_(fail_stream_) {}
+
+  std::string error() const { return errors_.str(); }
+
+ protected:
+  bool success_ = true;
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  EnumConverter converter_;
+};
+
+TEST_P(SpvBuiltinTest, Samples) {
+  const auto params = GetParam();
+
+  const auto result = converter_.ToBuiltin(params.builtin);
+  EXPECT_EQ(success_, params.expect_success);
+  if (params.expect_success) {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_TRUE(error().empty());
+  } else {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_THAT(error(), ::testing::StartsWith("unknown SPIR-V builtin: "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood_Input,
+    SpvBuiltinTest,
+    testing::Values(
+        BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
+        BuiltinCase{SpvBuiltInInstanceIndex, true,
+                    ast::Builtin::kInstanceIndex},
+        BuiltinCase{SpvBuiltInFrontFacing, true, ast::Builtin::kFrontFacing},
+        BuiltinCase{SpvBuiltInFragCoord, true, ast::Builtin::kPosition},
+        BuiltinCase{SpvBuiltInLocalInvocationId, true,
+                    ast::Builtin::kLocalInvocationId},
+        BuiltinCase{SpvBuiltInLocalInvocationIndex, true,
+                    ast::Builtin::kLocalInvocationIndex},
+        BuiltinCase{SpvBuiltInGlobalInvocationId, true,
+                    ast::Builtin::kGlobalInvocationId},
+        BuiltinCase{SpvBuiltInWorkgroupId, true, ast::Builtin::kWorkgroupId},
+        BuiltinCase{SpvBuiltInSampleId, true, ast::Builtin::kSampleIndex},
+        BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood_Output,
+    SpvBuiltinTest,
+    testing::Values(
+        BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
+        BuiltinCase{SpvBuiltInFragDepth, true, ast::Builtin::kFragDepth},
+        BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterBad,
+    SpvBuiltinTest,
+    testing::Values(
+        BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
+        BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
+        BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::Builtin::kNone}));
+
+// Dim
+
+struct DimCase {
+  SpvDim dim;
+  bool arrayed;
+  bool expect_success;
+  ast::TextureDimension expected;
+};
+inline std::ostream& operator<<(std::ostream& out, DimCase dc) {
+  out << "DimCase{ SpvDim:" << int(dc.dim) << " arrayed?:" << int(dc.arrayed)
+      << " expect_success?:" << int(dc.expect_success)
+      << " expected:" << int(dc.expected) << "}";
+  return out;
+}
+
+class SpvDimTest : public testing::TestWithParam<DimCase> {
+ public:
+  SpvDimTest()
+      : success_(true),
+        fail_stream_(&success_, &errors_),
+        converter_(fail_stream_) {}
+
+  std::string error() const { return errors_.str(); }
+
+ protected:
+  bool success_ = true;
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  EnumConverter converter_;
+};
+
+TEST_P(SpvDimTest, Samples) {
+  const auto params = GetParam();
+
+  const auto result = converter_.ToDim(params.dim, params.arrayed);
+  EXPECT_EQ(success_, params.expect_success);
+  if (params.expect_success) {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_TRUE(error().empty());
+  } else {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_THAT(error(), ::testing::HasSubstr("dimension"));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood,
+    SpvDimTest,
+    testing::Values(
+        // Non-arrayed
+        DimCase{SpvDim1D, false, true, ast::TextureDimension::k1d},
+        DimCase{SpvDim2D, false, true, ast::TextureDimension::k2d},
+        DimCase{SpvDim3D, false, true, ast::TextureDimension::k3d},
+        DimCase{SpvDimCube, false, true, ast::TextureDimension::kCube},
+        // Arrayed
+        DimCase{SpvDim2D, true, true, ast::TextureDimension::k2dArray},
+        DimCase{SpvDimCube, true, true, ast::TextureDimension::kCubeArray}));
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterBad,
+    SpvDimTest,
+    testing::Values(
+        // Invalid SPIR-V dimensionality.
+        DimCase{SpvDimMax, false, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimMax, true, false, ast::TextureDimension::kNone},
+        // Vulkan non-arrayed dimensionalities not supported by WGSL.
+        DimCase{SpvDimRect, false, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimBuffer, false, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimSubpassData, false, false, ast::TextureDimension::kNone},
+        // Arrayed dimensionalities not supported by WGSL
+        DimCase{SpvDim3D, true, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimRect, true, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimBuffer, true, false, ast::TextureDimension::kNone},
+        DimCase{SpvDimSubpassData, true, false, ast::TextureDimension::kNone}));
+
+// TexelFormat
+
+struct TexelFormatCase {
+  SpvImageFormat format;
+  bool expect_success;
+  ast::TexelFormat expected;
+};
+inline std::ostream& operator<<(std::ostream& out, TexelFormatCase ifc) {
+  out << "TexelFormatCase{ SpvImageFormat:" << int(ifc.format)
+      << " expect_success?:" << int(ifc.expect_success)
+      << " expected:" << int(ifc.expected) << "}";
+  return out;
+}
+
+class SpvImageFormatTest : public testing::TestWithParam<TexelFormatCase> {
+ public:
+  SpvImageFormatTest()
+      : success_(true),
+        fail_stream_(&success_, &errors_),
+        converter_(fail_stream_) {}
+
+  std::string error() const { return errors_.str(); }
+
+ protected:
+  bool success_ = true;
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  EnumConverter converter_;
+};
+
+TEST_P(SpvImageFormatTest, Samples) {
+  const auto params = GetParam();
+
+  const auto result = converter_.ToTexelFormat(params.format);
+  EXPECT_EQ(success_, params.expect_success) << params;
+  if (params.expect_success) {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_TRUE(error().empty());
+  } else {
+    EXPECT_EQ(result, params.expected);
+    EXPECT_THAT(error(), ::testing::StartsWith("invalid image format: "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterGood,
+    SpvImageFormatTest,
+    testing::Values(
+        // Unknown.  This is used for sampled images.
+        TexelFormatCase{SpvImageFormatUnknown, true, ast::TexelFormat::kNone},
+        // 8 bit channels
+        TexelFormatCase{SpvImageFormatRgba8, true,
+                        ast::TexelFormat::kRgba8Unorm},
+        TexelFormatCase{SpvImageFormatRgba8Snorm, true,
+                        ast::TexelFormat::kRgba8Snorm},
+        TexelFormatCase{SpvImageFormatRgba8ui, true,
+                        ast::TexelFormat::kRgba8Uint},
+        TexelFormatCase{SpvImageFormatRgba8i, true,
+                        ast::TexelFormat::kRgba8Sint},
+        // 16 bit channels
+        TexelFormatCase{SpvImageFormatRgba16ui, true,
+                        ast::TexelFormat::kRgba16Uint},
+        TexelFormatCase{SpvImageFormatRgba16i, true,
+                        ast::TexelFormat::kRgba16Sint},
+        TexelFormatCase{SpvImageFormatRgba16f, true,
+                        ast::TexelFormat::kRgba16Float},
+        // 32 bit channels
+        // ... 1 channel
+        TexelFormatCase{SpvImageFormatR32ui, true, ast::TexelFormat::kR32Uint},
+        TexelFormatCase{SpvImageFormatR32i, true, ast::TexelFormat::kR32Sint},
+        TexelFormatCase{SpvImageFormatR32f, true, ast::TexelFormat::kR32Float},
+        // ... 2 channels
+        TexelFormatCase{SpvImageFormatRg32ui, true,
+                        ast::TexelFormat::kRg32Uint},
+        TexelFormatCase{SpvImageFormatRg32i, true, ast::TexelFormat::kRg32Sint},
+        TexelFormatCase{SpvImageFormatRg32f, true,
+                        ast::TexelFormat::kRg32Float},
+        // ... 4 channels
+        TexelFormatCase{SpvImageFormatRgba32ui, true,
+                        ast::TexelFormat::kRgba32Uint},
+        TexelFormatCase{SpvImageFormatRgba32i, true,
+                        ast::TexelFormat::kRgba32Sint},
+        TexelFormatCase{SpvImageFormatRgba32f, true,
+                        ast::TexelFormat::kRgba32Float}));
+
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterBad,
+    SpvImageFormatTest,
+    testing::Values(
+        // Scanning in order from the SPIR-V spec.
+        TexelFormatCase{SpvImageFormatRg16f, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatR11fG11fB10f, false,
+                        ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatR16f, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRgb10A2, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg16, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg8, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatR16, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatR8, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRgba16Snorm, false,
+                        ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg16Snorm, false,
+                        ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg8Snorm, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg16i, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg8i, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatR8i, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRgb10a2ui, false,
+                        ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg16ui, false, ast::TexelFormat::kNone},
+        TexelFormatCase{SpvImageFormatRg8ui, false, ast::TexelFormat::kNone}));
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/fail_stream.h b/src/tint/reader/spirv/fail_stream.h
new file mode 100644
index 0000000..a7530ca
--- /dev/null
+++ b/src/tint/reader/spirv/fail_stream.h
@@ -0,0 +1,74 @@
+// Copyright 2020 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_READER_SPIRV_FAIL_STREAM_H_
+#define SRC_TINT_READER_SPIRV_FAIL_STREAM_H_
+
+#include <ostream>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// A FailStream object accumulates values onto a given std::ostream,
+/// and can be used to record failure by writing the false value
+/// to given a pointer-to-bool.
+class FailStream {
+ public:
+  /// Creates a new fail stream
+  /// @param status_ptr where we will write false to indicate failure. Assumed
+  /// to be a valid pointer to bool.
+  /// @param out output stream where a message should be written to explain
+  /// the failure
+  FailStream(bool* status_ptr, std::ostream* out)
+      : status_ptr_(status_ptr), out_(out) {}
+  /// Copy constructor
+  /// @param other the fail stream to clone
+  FailStream(const FailStream& other) = default;
+
+  /// Converts to a boolean status. A true result indicates success,
+  /// and a false result indicates failure.
+  /// @returns the status
+  operator bool() const { return *status_ptr_; }
+  /// Returns the current status value.  This can be more readable
+  /// the conversion operator.
+  /// @returns the status
+  bool status() const { return *status_ptr_; }
+
+  /// Records failure.
+  /// @returns a FailStream
+  FailStream& Fail() {
+    *status_ptr_ = false;
+    return *this;
+  }
+
+  /// Appends the given value to the message output stream.
+  /// @param val the value to write to the output stream.
+  /// @returns this object
+  template <typename T>
+  FailStream& operator<<(const T& val) {
+    *out_ << val;
+    return *this;
+  }
+
+ private:
+  bool* status_ptr_;
+  std::ostream* out_;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_FAIL_STREAM_H_
diff --git a/src/tint/reader/spirv/fail_stream_test.cc b/src/tint/reader/spirv/fail_stream_test.cc
new file mode 100644
index 0000000..4c6e9bf
--- /dev/null
+++ b/src/tint/reader/spirv/fail_stream_test.cc
@@ -0,0 +1,73 @@
+// Copyright 2020 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/reader/spirv/fail_stream.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+using FailStreamTest = ::testing::Test;
+
+TEST_F(FailStreamTest, ConversionToBoolIsSameAsStatusMethod) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+
+  EXPECT_TRUE(fs.status());
+  EXPECT_TRUE(bool(fs));  // NOLINT
+  flag = false;
+  EXPECT_FALSE(fs.status());
+  EXPECT_FALSE(bool(fs));  // NOLINT
+  flag = true;
+  EXPECT_TRUE(fs.status());
+  EXPECT_TRUE(bool(fs));  // NOLINT
+}
+
+TEST_F(FailStreamTest, FailMethodChangesStatusToFalse) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+  EXPECT_TRUE(flag);
+  EXPECT_TRUE(bool(fs));  // NOLINT
+  fs.Fail();
+  EXPECT_FALSE(flag);
+  EXPECT_FALSE(bool(fs));  // NOLINT
+}
+
+TEST_F(FailStreamTest, FailMethodReturnsSelf) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+  FailStream& result = fs.Fail();
+  EXPECT_THAT(&result, Eq(&fs));
+}
+
+TEST_F(FailStreamTest, ShiftOperatorAccumulatesValues) {
+  bool flag = true;
+  std::stringstream ss;
+  FailStream fs(&flag, &ss);
+
+  ss << "prefix ";
+  fs << "cat " << 42;
+
+  EXPECT_THAT(ss.str(), Eq("prefix cat 42"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
new file mode 100644
index 0000000..f827001
--- /dev/null
+++ b/src/tint/reader/spirv/function.cc
@@ -0,0 +1,6148 @@
+// Copyright 2020 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/reader/spirv/function.h"
+
+#include <algorithm>
+#include <array>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/builtin_attribute.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/sem/builtin_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+
+// Terms:
+//    CFG: the control flow graph of the function, where basic blocks are the
+//    nodes, and branches form the directed arcs.  The function entry block is
+//    the root of the CFG.
+//
+//    Suppose H is a header block (i.e. has an OpSelectionMerge or OpLoopMerge).
+//    Then:
+//    - Let M(H) be the merge block named by the merge instruction in H.
+//    - If H is a loop header, i.e. has an OpLoopMerge instruction, then let
+//      CT(H) be the continue target block named by the OpLoopMerge
+//      instruction.
+//    - If H is a selection construct whose header ends in
+//      OpBranchConditional with true target %then and false target %else,
+//      then  TT(H) = %then and FT(H) = %else
+//
+// Determining output block order:
+//    The "structured post-order traversal" of the CFG is a post-order traversal
+//    of the basic blocks in the CFG, where:
+//      We visit the entry node of the function first.
+//      When visiting a header block:
+//        We next visit its merge block
+//        Then if it's a loop header, we next visit the continue target,
+//      Then we visit the block's successors (whether it's a header or not)
+//        If the block ends in an OpBranchConditional, we visit the false target
+//        before the true target.
+//
+//    The "reverse structured post-order traversal" of the CFG is the reverse
+//    of the structured post-order traversal.
+//    This is the order of basic blocks as they should be emitted to the WGSL
+//    function. It is the order computed by ComputeBlockOrder, and stored in
+//    the |FunctionEmiter::block_order_|.
+//    Blocks not in this ordering are ignored by the rest of the algorithm.
+//
+//    Note:
+//     - A block D in the function might not appear in this order because
+//       no block in the order branches to D.
+//     - An unreachable block D might still be in the order because some header
+//       block in the order names D as its continue target, or merge block,
+//       or D is reachable from one of those otherwise-unreachable continue
+//       targets or merge blocks.
+//
+// Terms:
+//    Let Pos(B) be the index position of a block B in the computed block order.
+//
+// CFG intervals and valid nesting:
+//
+//    A correctly structured CFG satisfies nesting rules that we can check by
+//    comparing positions of related blocks.
+//
+//    If header block H is in the block order, then the following holds:
+//
+//      Pos(H) < Pos(M(H))
+//
+//      If CT(H) exists, then:
+//
+//         Pos(H) <= Pos(CT(H))
+//         Pos(CT(H)) < Pos(M)
+//
+//    This gives us the fundamental ordering of blocks in relation to a
+//    structured construct:
+//      The blocks before H in the block order, are not in the construct
+//      The blocks at M(H) or later in the block order, are not in the construct
+//      The blocks in a selection headed at H are in positions [ Pos(H),
+//      Pos(M(H)) ) The blocks in a loop construct headed at H are in positions
+//      [ Pos(H), Pos(CT(H)) ) The blocks in the continue construct for loop
+//      headed at H are in
+//        positions [ Pos(CT(H)), Pos(M(H)) )
+//
+//      Schematically, for a selection construct headed by H, the blocks are in
+//      order from left to right:
+//
+//                 ...a-b-c H d-e-f M(H) n-o-p...
+//
+//           where ...a-b-c: blocks before the selection construct
+//           where H and d-e-f: blocks in the selection construct
+//           where M(H) and n-o-p...: blocks after the selection construct
+//
+//      Schematically, for a loop construct headed by H that is its own
+//      continue construct, the blocks in order from left to right:
+//
+//                 ...a-b-c H=CT(H) d-e-f M(H) n-o-p...
+//
+//           where ...a-b-c: blocks before the loop
+//           where H is the continue construct; CT(H)=H, and the loop construct
+//           is *empty*
+//           where d-e-f... are other blocks in the continue construct
+//           where M(H) and n-o-p...: blocks after the continue construct
+//
+//      Schematically, for a multi-block loop construct headed by H, there are
+//      blocks in order from left to right:
+//
+//                 ...a-b-c H d-e-f CT(H) j-k-l M(H) n-o-p...
+//
+//           where ...a-b-c: blocks before the loop
+//           where H and d-e-f: blocks in the loop construct
+//           where CT(H) and j-k-l: blocks in the continue construct
+//           where M(H) and n-o-p...: blocks after the loop and continue
+//           constructs
+//
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+namespace {
+
+constexpr uint32_t kMaxVectorLen = 4;
+
+// Gets the AST unary opcode for the given SPIR-V opcode, if any
+// @param opcode SPIR-V opcode
+// @param ast_unary_op return parameter
+// @returns true if it was a unary operation
+bool GetUnaryOp(SpvOp opcode, ast::UnaryOp* ast_unary_op) {
+  switch (opcode) {
+    case SpvOpSNegate:
+    case SpvOpFNegate:
+      *ast_unary_op = ast::UnaryOp::kNegation;
+      return true;
+    case SpvOpLogicalNot:
+      *ast_unary_op = ast::UnaryOp::kNot;
+      return true;
+    case SpvOpNot:
+      *ast_unary_op = ast::UnaryOp::kComplement;
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+/// Converts a SPIR-V opcode for a WGSL builtin function, if there is a
+/// direct translation. Returns nullptr otherwise.
+/// @returns the WGSL builtin function name for the given opcode, or nullptr.
+const char* GetUnaryBuiltInFunctionName(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpAny:
+      return "any";
+    case SpvOpAll:
+      return "all";
+    case SpvOpIsNan:
+      return "isNan";
+    case SpvOpIsInf:
+      return "isInf";
+    case SpvOpTranspose:
+      return "transpose";
+    default:
+      break;
+  }
+  return nullptr;
+}
+
+// Converts a SPIR-V opcode to its corresponding AST binary opcode, if any
+// @param opcode SPIR-V opcode
+// @returns the AST binary op for the given opcode, or kNone
+ast::BinaryOp ConvertBinaryOp(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+      return ast::BinaryOp::kAdd;
+    case SpvOpISub:
+    case SpvOpFSub:
+      return ast::BinaryOp::kSubtract;
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpVectorTimesScalar:
+    case SpvOpMatrixTimesScalar:
+    case SpvOpVectorTimesMatrix:
+    case SpvOpMatrixTimesVector:
+    case SpvOpMatrixTimesMatrix:
+      return ast::BinaryOp::kMultiply;
+    case SpvOpUDiv:
+    case SpvOpSDiv:
+    case SpvOpFDiv:
+      return ast::BinaryOp::kDivide;
+    case SpvOpUMod:
+    case SpvOpSMod:
+    case SpvOpFRem:
+      return ast::BinaryOp::kModulo;
+    case SpvOpLogicalEqual:
+    case SpvOpIEqual:
+    case SpvOpFOrdEqual:
+      return ast::BinaryOp::kEqual;
+    case SpvOpLogicalNotEqual:
+    case SpvOpINotEqual:
+    case SpvOpFOrdNotEqual:
+      return ast::BinaryOp::kNotEqual;
+    case SpvOpBitwiseAnd:
+      return ast::BinaryOp::kAnd;
+    case SpvOpBitwiseOr:
+      return ast::BinaryOp::kOr;
+    case SpvOpBitwiseXor:
+      return ast::BinaryOp::kXor;
+    case SpvOpLogicalAnd:
+      return ast::BinaryOp::kAnd;
+    case SpvOpLogicalOr:
+      return ast::BinaryOp::kOr;
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpFOrdGreaterThan:
+      return ast::BinaryOp::kGreaterThan;
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+      return ast::BinaryOp::kGreaterThanEqual;
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpFOrdLessThan:
+      return ast::BinaryOp::kLessThan;
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpFOrdLessThanEqual:
+      return ast::BinaryOp::kLessThanEqual;
+    default:
+      break;
+  }
+  // It's not clear what OpSMod should map to.
+  // https://bugs.chromium.org/p/tint/issues/detail?id=52
+  return ast::BinaryOp::kNone;
+}
+
+// If the given SPIR-V opcode is a floating point unordered comparison,
+// then returns the binary float comparison for which it is the negation.
+// Othewrise returns BinaryOp::kNone.
+// @param opcode SPIR-V opcode
+// @returns operation corresponding to negated version of the SPIR-V opcode
+ast::BinaryOp NegatedFloatCompare(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpFUnordEqual:
+      return ast::BinaryOp::kNotEqual;
+    case SpvOpFUnordNotEqual:
+      return ast::BinaryOp::kEqual;
+    case SpvOpFUnordLessThan:
+      return ast::BinaryOp::kGreaterThanEqual;
+    case SpvOpFUnordLessThanEqual:
+      return ast::BinaryOp::kGreaterThan;
+    case SpvOpFUnordGreaterThan:
+      return ast::BinaryOp::kLessThanEqual;
+    case SpvOpFUnordGreaterThanEqual:
+      return ast::BinaryOp::kLessThan;
+    default:
+      break;
+  }
+  return ast::BinaryOp::kNone;
+}
+
+// Returns the WGSL standard library function for the given
+// GLSL.std.450 extended instruction operation code.  Unknown
+// and invalid opcodes map to the empty string.
+// @returns the WGSL standard function name, or an empty string.
+std::string GetGlslStd450FuncName(uint32_t ext_opcode) {
+  switch (ext_opcode) {
+    case GLSLstd450FAbs:
+    case GLSLstd450SAbs:
+      return "abs";
+    case GLSLstd450Acos:
+      return "acos";
+    case GLSLstd450Asin:
+      return "asin";
+    case GLSLstd450Atan:
+      return "atan";
+    case GLSLstd450Atan2:
+      return "atan2";
+    case GLSLstd450Ceil:
+      return "ceil";
+    case GLSLstd450UClamp:
+    case GLSLstd450SClamp:
+    case GLSLstd450NClamp:
+    case GLSLstd450FClamp:  // FClamp is less prescriptive about NaN operands
+      return "clamp";
+    case GLSLstd450Cos:
+      return "cos";
+    case GLSLstd450Cosh:
+      return "cosh";
+    case GLSLstd450Cross:
+      return "cross";
+    case GLSLstd450Degrees:
+      return "degrees";
+    case GLSLstd450Distance:
+      return "distance";
+    case GLSLstd450Exp:
+      return "exp";
+    case GLSLstd450Exp2:
+      return "exp2";
+    case GLSLstd450FaceForward:
+      return "faceForward";
+    case GLSLstd450Floor:
+      return "floor";
+    case GLSLstd450Fma:
+      return "fma";
+    case GLSLstd450Fract:
+      return "fract";
+    case GLSLstd450InverseSqrt:
+      return "inverseSqrt";
+    case GLSLstd450Ldexp:
+      return "ldexp";
+    case GLSLstd450Length:
+      return "length";
+    case GLSLstd450Log:
+      return "log";
+    case GLSLstd450Log2:
+      return "log2";
+    case GLSLstd450NMax:
+    case GLSLstd450FMax:  // FMax is less prescriptive about NaN operands
+    case GLSLstd450UMax:
+    case GLSLstd450SMax:
+      return "max";
+    case GLSLstd450NMin:
+    case GLSLstd450FMin:  // FMin is less prescriptive about NaN operands
+    case GLSLstd450UMin:
+    case GLSLstd450SMin:
+      return "min";
+    case GLSLstd450FMix:
+      return "mix";
+    case GLSLstd450Normalize:
+      return "normalize";
+    case GLSLstd450PackSnorm4x8:
+      return "pack4x8snorm";
+    case GLSLstd450PackUnorm4x8:
+      return "pack4x8unorm";
+    case GLSLstd450PackSnorm2x16:
+      return "pack2x16snorm";
+    case GLSLstd450PackUnorm2x16:
+      return "pack2x16unorm";
+    case GLSLstd450PackHalf2x16:
+      return "pack2x16float";
+    case GLSLstd450Pow:
+      return "pow";
+    case GLSLstd450FSign:
+      return "sign";
+    case GLSLstd450Radians:
+      return "radians";
+    case GLSLstd450Reflect:
+      return "reflect";
+    case GLSLstd450Refract:
+      return "refract";
+    case GLSLstd450Round:
+    case GLSLstd450RoundEven:
+      return "round";
+    case GLSLstd450Sin:
+      return "sin";
+    case GLSLstd450Sinh:
+      return "sinh";
+    case GLSLstd450SmoothStep:
+      return "smoothStep";
+    case GLSLstd450Sqrt:
+      return "sqrt";
+    case GLSLstd450Step:
+      return "step";
+    case GLSLstd450Tan:
+      return "tan";
+    case GLSLstd450Tanh:
+      return "tanh";
+    case GLSLstd450Trunc:
+      return "trunc";
+    case GLSLstd450UnpackSnorm4x8:
+      return "unpack4x8snorm";
+    case GLSLstd450UnpackUnorm4x8:
+      return "unpack4x8unorm";
+    case GLSLstd450UnpackSnorm2x16:
+      return "unpack2x16snorm";
+    case GLSLstd450UnpackUnorm2x16:
+      return "unpack2x16unorm";
+    case GLSLstd450UnpackHalf2x16:
+      return "unpack2x16float";
+
+    default:
+      // TODO(dneto) - The following are not implemented.
+      // They are grouped semantically, as in GLSL.std.450.h.
+
+    case GLSLstd450SSign:
+
+    case GLSLstd450Asinh:
+    case GLSLstd450Acosh:
+    case GLSLstd450Atanh:
+
+    case GLSLstd450Determinant:
+    case GLSLstd450MatrixInverse:
+
+    case GLSLstd450Modf:
+    case GLSLstd450ModfStruct:
+    case GLSLstd450IMix:
+
+    case GLSLstd450Frexp:
+    case GLSLstd450FrexpStruct:
+
+    case GLSLstd450PackDouble2x32:
+    case GLSLstd450UnpackDouble2x32:
+
+    case GLSLstd450FindILsb:
+    case GLSLstd450FindSMsb:
+    case GLSLstd450FindUMsb:
+
+    case GLSLstd450InterpolateAtCentroid:
+    case GLSLstd450InterpolateAtSample:
+    case GLSLstd450InterpolateAtOffset:
+      break;
+  }
+  return "";
+}
+
+// Returns the WGSL standard library function builtin for the
+// given instruction, or sem::BuiltinType::kNone
+sem::BuiltinType GetBuiltin(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpBitCount:
+      return sem::BuiltinType::kCountOneBits;
+    case SpvOpBitReverse:
+      return sem::BuiltinType::kReverseBits;
+    case SpvOpDot:
+      return sem::BuiltinType::kDot;
+    case SpvOpDPdx:
+      return sem::BuiltinType::kDpdx;
+    case SpvOpDPdy:
+      return sem::BuiltinType::kDpdy;
+    case SpvOpFwidth:
+      return sem::BuiltinType::kFwidth;
+    case SpvOpDPdxFine:
+      return sem::BuiltinType::kDpdxFine;
+    case SpvOpDPdyFine:
+      return sem::BuiltinType::kDpdyFine;
+    case SpvOpFwidthFine:
+      return sem::BuiltinType::kFwidthFine;
+    case SpvOpDPdxCoarse:
+      return sem::BuiltinType::kDpdxCoarse;
+    case SpvOpDPdyCoarse:
+      return sem::BuiltinType::kDpdyCoarse;
+    case SpvOpFwidthCoarse:
+      return sem::BuiltinType::kFwidthCoarse;
+    default:
+      break;
+  }
+  return sem::BuiltinType::kNone;
+}
+
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image access instruction
+// whose first input operand is an OpSampledImage value.
+bool IsSampledImageAccess(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImageQueryLod:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image sampling, gather,
+// or gather-compare operation.
+bool IsImageSamplingOrGatherOrDrefGather(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+      // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image access instruction
+// whose first input operand is an OpImage value.
+bool IsRawImageAccess(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageRead:
+    case SpvOpImageWrite:
+    case SpvOpImageFetch:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image query instruction
+bool IsImageQuery(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageQuerySize:
+    case SpvOpImageQuerySizeLod:
+    case SpvOpImageQueryLevels:
+    case SpvOpImageQuerySamples:
+    case SpvOpImageQueryLod:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// @returns the merge block ID for the given basic block, or 0 if there is none.
+uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) {
+  // Get the OpSelectionMerge or OpLoopMerge instruction, if any.
+  auto* inst = bb.GetMergeInst();
+  return inst == nullptr ? 0 : inst->GetSingleWordInOperand(0);
+}
+
+// @returns the continue target ID for the given basic block, or 0 if there
+// is none.
+uint32_t ContinueTargetFor(const spvtools::opt::BasicBlock& bb) {
+  // Get the OpLoopMerge instruction, if any.
+  auto* inst = bb.GetLoopMergeInst();
+  return inst == nullptr ? 0 : inst->GetSingleWordInOperand(1);
+}
+
+// A structured traverser produces the reverse structured post-order of the
+// CFG of a function.  The blocks traversed are the transitive closure (minimum
+// fixed point) of:
+//  - the entry block
+//  - a block reached by a branch from another block in the set
+//  - a block mentioned as a merge block or continue target for a block in the
+//  set
+class StructuredTraverser {
+ public:
+  explicit StructuredTraverser(const spvtools::opt::Function& function)
+      : function_(function) {
+    for (auto& block : function_) {
+      id_to_block_[block.id()] = &block;
+    }
+  }
+
+  // Returns the reverse postorder traversal of the CFG, where:
+  //  - a merge block always follows its associated constructs
+  //  - a continue target always follows the associated loop construct, if any
+  // @returns the IDs of blocks in reverse structured post order
+  std::vector<uint32_t> ReverseStructuredPostOrder() {
+    visit_order_.clear();
+    visited_.clear();
+    VisitBackward(function_.entry()->id());
+
+    std::vector<uint32_t> order(visit_order_.rbegin(), visit_order_.rend());
+    return order;
+  }
+
+ private:
+  // Executes a depth first search of the CFG, where right after we visit a
+  // header, we will visit its merge block, then its continue target (if any).
+  // Also records the post order ordering.
+  void VisitBackward(uint32_t id) {
+    if (id == 0)
+      return;
+    if (visited_.count(id))
+      return;
+    visited_.insert(id);
+
+    const spvtools::opt::BasicBlock* bb =
+        id_to_block_[id];  // non-null for valid modules
+    VisitBackward(MergeFor(*bb));
+    VisitBackward(ContinueTargetFor(*bb));
+
+    // Visit successors. We will naturally skip the continue target and merge
+    // blocks.
+    auto* terminator = bb->terminator();
+    auto opcode = terminator->opcode();
+    if (opcode == SpvOpBranchConditional) {
+      // Visit the false branch, then the true branch, to make them come
+      // out in the natural order for an "if".
+      VisitBackward(terminator->GetSingleWordInOperand(2));
+      VisitBackward(terminator->GetSingleWordInOperand(1));
+    } else if (opcode == SpvOpBranch) {
+      VisitBackward(terminator->GetSingleWordInOperand(0));
+    } else if (opcode == SpvOpSwitch) {
+      // TODO(dneto): Consider visiting the labels in literal-value order.
+      std::vector<uint32_t> successors;
+      bb->ForEachSuccessorLabel([&successors](const uint32_t succ_id) {
+        successors.push_back(succ_id);
+      });
+      for (auto succ_id : successors) {
+        VisitBackward(succ_id);
+      }
+    }
+
+    visit_order_.push_back(id);
+  }
+
+  const spvtools::opt::Function& function_;
+  std::unordered_map<uint32_t, const spvtools::opt::BasicBlock*> id_to_block_;
+  std::vector<uint32_t> visit_order_;
+  std::unordered_set<uint32_t> visited_;
+};
+
+/// A StatementBuilder for ast::SwitchStatement
+/// @see StatementBuilder
+struct SwitchStatementBuilder
+    : public Castable<SwitchStatementBuilder, StatementBuilder> {
+  /// Constructor
+  /// @param cond the switch statement condition
+  explicit SwitchStatementBuilder(const ast::Expression* cond)
+      : condition(cond) {}
+
+  /// @param builder the program builder
+  /// @returns the built ast::SwitchStatement
+  const ast::SwitchStatement* Build(ProgramBuilder* builder) const override {
+    // We've listed cases in reverse order in the switch statement.
+    // Reorder them to match the presentation order in WGSL.
+    auto reversed_cases = cases;
+    std::reverse(reversed_cases.begin(), reversed_cases.end());
+
+    return builder->create<ast::SwitchStatement>(Source{}, condition,
+                                                 reversed_cases);
+  }
+
+  /// Switch statement condition
+  const ast::Expression* const condition;
+  /// Switch statement cases
+  ast::CaseStatementList cases;
+};
+
+/// A StatementBuilder for ast::IfStatement
+/// @see StatementBuilder
+struct IfStatementBuilder
+    : public Castable<IfStatementBuilder, StatementBuilder> {
+  /// Constructor
+  /// @param c the if-statement condition
+  explicit IfStatementBuilder(const ast::Expression* c) : cond(c) {}
+
+  /// @param builder the program builder
+  /// @returns the built ast::IfStatement
+  const ast::IfStatement* Build(ProgramBuilder* builder) const override {
+    return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmts);
+  }
+
+  /// If-statement condition
+  const ast::Expression* const cond;
+  /// If-statement block body
+  const ast::BlockStatement* body = nullptr;
+  /// Optional if-statement else statements
+  ast::ElseStatementList else_stmts;
+};
+
+/// A StatementBuilder for ast::LoopStatement
+/// @see StatementBuilder
+struct LoopStatementBuilder
+    : public Castable<LoopStatementBuilder, StatementBuilder> {
+  /// @param builder the program builder
+  /// @returns the built ast::LoopStatement
+  ast::LoopStatement* Build(ProgramBuilder* builder) const override {
+    return builder->create<ast::LoopStatement>(Source{}, body, continuing);
+  }
+
+  /// Loop-statement block body
+  const ast::BlockStatement* body = nullptr;
+  /// Loop-statement continuing body
+  /// @note the mutable keyword here is required as all non-StatementBuilders
+  /// `ast::Node`s are immutable and are referenced with `const` pointers.
+  /// StatementBuilders however exist to provide mutable state while the
+  /// FunctionEmitter is building the function. All StatementBuilders are
+  /// replaced with immutable AST nodes when Finalize() is called.
+  mutable const ast::BlockStatement* continuing = nullptr;
+};
+
+/// @param decos a list of parsed decorations
+/// @returns true if the decorations include a SampleMask builtin
+bool HasBuiltinSampleMask(const ast::AttributeList& decos) {
+  if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(decos)) {
+    return builtin->builtin == ast::Builtin::kSampleMask;
+  }
+  return false;
+}
+
+}  // namespace
+
+BlockInfo::BlockInfo(const spvtools::opt::BasicBlock& bb)
+    : basic_block(&bb), id(bb.id()) {}
+
+BlockInfo::~BlockInfo() = default;
+
+DefInfo::DefInfo(const spvtools::opt::Instruction& def_inst,
+                 uint32_t the_block_pos,
+                 size_t the_index)
+    : inst(def_inst), block_pos(the_block_pos), index(the_index) {}
+
+DefInfo::~DefInfo() = default;
+
+ast::Node* StatementBuilder::Clone(CloneContext*) const {
+  return nullptr;
+}
+
+FunctionEmitter::FunctionEmitter(ParserImpl* pi,
+                                 const spvtools::opt::Function& function,
+                                 const EntryPointInfo* ep_info)
+    : parser_impl_(*pi),
+      ty_(pi->type_manager()),
+      builder_(pi->builder()),
+      ir_context_(*(pi->ir_context())),
+      def_use_mgr_(ir_context_.get_def_use_mgr()),
+      constant_mgr_(ir_context_.get_constant_mgr()),
+      type_mgr_(ir_context_.get_type_mgr()),
+      fail_stream_(pi->fail_stream()),
+      namer_(pi->namer()),
+      function_(function),
+      sample_mask_in_id(0u),
+      sample_mask_out_id(0u),
+      ep_info_(ep_info) {
+  PushNewStatementBlock(nullptr, 0, nullptr);
+}
+
+FunctionEmitter::FunctionEmitter(ParserImpl* pi,
+                                 const spvtools::opt::Function& function)
+    : FunctionEmitter(pi, function, nullptr) {}
+
+FunctionEmitter::FunctionEmitter(FunctionEmitter&& other)
+    : parser_impl_(other.parser_impl_),
+      ty_(other.ty_),
+      builder_(other.builder_),
+      ir_context_(other.ir_context_),
+      def_use_mgr_(ir_context_.get_def_use_mgr()),
+      constant_mgr_(ir_context_.get_constant_mgr()),
+      type_mgr_(ir_context_.get_type_mgr()),
+      fail_stream_(other.fail_stream_),
+      namer_(other.namer_),
+      function_(other.function_),
+      sample_mask_in_id(other.sample_mask_out_id),
+      sample_mask_out_id(other.sample_mask_in_id),
+      ep_info_(other.ep_info_) {
+  other.statements_stack_.clear();
+  PushNewStatementBlock(nullptr, 0, nullptr);
+}
+
+FunctionEmitter::~FunctionEmitter() = default;
+
+FunctionEmitter::StatementBlock::StatementBlock(
+    const Construct* construct,
+    uint32_t end_id,
+    FunctionEmitter::CompletionAction completion_action)
+    : construct_(construct),
+      end_id_(end_id),
+      completion_action_(completion_action) {}
+
+FunctionEmitter::StatementBlock::StatementBlock(StatementBlock&& other) =
+    default;
+
+FunctionEmitter::StatementBlock::~StatementBlock() = default;
+
+void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) {
+  TINT_ASSERT(Reader, !finalized_ /* Finalize() must only be called once */);
+
+  for (size_t i = 0; i < statements_.size(); i++) {
+    if (auto* sb = statements_[i]->As<StatementBuilder>()) {
+      statements_[i] = sb->Build(pb);
+    }
+  }
+
+  if (completion_action_ != nullptr) {
+    completion_action_(statements_);
+  }
+
+  finalized_ = true;
+}
+
+void FunctionEmitter::StatementBlock::Add(const ast::Statement* statement) {
+  TINT_ASSERT(Reader,
+              !finalized_ /* Add() must not be called after Finalize() */);
+  statements_.emplace_back(statement);
+}
+
+void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
+                                            uint32_t end_id,
+                                            CompletionAction action) {
+  statements_stack_.emplace_back(StatementBlock{construct, end_id, action});
+}
+
+void FunctionEmitter::PushGuard(const std::string& guard_name,
+                                uint32_t end_id) {
+  TINT_ASSERT(Reader, !statements_stack_.empty());
+  TINT_ASSERT(Reader, !guard_name.empty());
+  // Guard control flow by the guard variable.  Introduce a new
+  // if-selection with a then-clause ending at the same block
+  // as the statement block at the top of the stack.
+  const auto& top = statements_stack_.back();
+
+  auto* cond = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(guard_name));
+  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
+
+  PushNewStatementBlock(
+      top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
+        builder->body = create<ast::BlockStatement>(Source{}, stmts);
+      });
+}
+
+void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
+  TINT_ASSERT(Reader, !statements_stack_.empty());
+  const auto& top = statements_stack_.back();
+
+  auto* cond = MakeTrue(Source{});
+  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
+
+  PushNewStatementBlock(
+      top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
+        builder->body = create<ast::BlockStatement>(Source{}, stmts);
+      });
+}
+
+const ast::StatementList FunctionEmitter::ast_body() {
+  TINT_ASSERT(Reader, !statements_stack_.empty());
+  auto& entry = statements_stack_[0];
+  entry.Finalize(&builder_);
+  return entry.GetStatements();
+}
+
+const ast::Statement* FunctionEmitter::AddStatement(
+    const ast::Statement* statement) {
+  TINT_ASSERT(Reader, !statements_stack_.empty());
+  if (statement != nullptr) {
+    statements_stack_.back().Add(statement);
+  }
+  return statement;
+}
+
+const ast::Statement* FunctionEmitter::LastStatement() {
+  TINT_ASSERT(Reader, !statements_stack_.empty());
+  auto& statement_list = statements_stack_.back().GetStatements();
+  TINT_ASSERT(Reader, !statement_list.empty());
+  return statement_list.back();
+}
+
+bool FunctionEmitter::Emit() {
+  if (failed()) {
+    return false;
+  }
+  // We only care about functions with bodies.
+  if (function_.cbegin() == function_.cend()) {
+    return true;
+  }
+
+  // The function declaration, corresponding to how it's written in SPIR-V,
+  // and without regard to whether it's an entry point.
+  FunctionDeclaration decl;
+  if (!ParseFunctionDeclaration(&decl)) {
+    return false;
+  }
+
+  bool make_body_function = true;
+  if (ep_info_) {
+    TINT_ASSERT(Reader, !ep_info_->inner_name.empty());
+    if (ep_info_->owns_inner_implementation) {
+      // This is an entry point, and we want to emit it as a wrapper around
+      // an implementation function.
+      decl.name = ep_info_->inner_name;
+    } else {
+      // This is a second entry point that shares an inner implementation
+      // function.
+      make_body_function = false;
+    }
+  }
+
+  if (make_body_function) {
+    auto* body = MakeFunctionBody();
+    if (!body) {
+      return false;
+    }
+
+    builder_.AST().AddFunction(create<ast::Function>(
+        decl.source, builder_.Symbols().Register(decl.name),
+        std::move(decl.params), decl.return_type->Build(builder_), body,
+        std::move(decl.attributes), ast::AttributeList{}));
+  }
+
+  if (ep_info_ && !ep_info_->inner_name.empty()) {
+    return EmitEntryPointAsWrapper();
+  }
+
+  return success();
+}
+
+const ast::BlockStatement* FunctionEmitter::MakeFunctionBody() {
+  TINT_ASSERT(Reader, statements_stack_.size() == 1);
+
+  if (!EmitBody()) {
+    return nullptr;
+  }
+
+  // Set the body of the AST function node.
+  if (statements_stack_.size() != 1) {
+    Fail() << "internal error: statement-list stack should have 1 "
+              "element but has "
+           << statements_stack_.size();
+    return nullptr;
+  }
+
+  statements_stack_[0].Finalize(&builder_);
+  auto& statements = statements_stack_[0].GetStatements();
+  auto* body = create<ast::BlockStatement>(Source{}, statements);
+
+  // Maintain the invariant by repopulating the one and only element.
+  statements_stack_.clear();
+  PushNewStatementBlock(constructs_[0].get(), 0, nullptr);
+
+  return body;
+}
+
+bool FunctionEmitter::EmitPipelineInput(std::string var_name,
+                                        const Type* var_type,
+                                        ast::AttributeList* attrs,
+                                        std::vector<int> index_prefix,
+                                        const Type* tip_type,
+                                        const Type* forced_param_type,
+                                        ast::VariableList* params,
+                                        ast::StatementList* statements) {
+  // TODO(dneto): Handle structs where the locations are annotated on members.
+  tip_type = tip_type->UnwrapAlias();
+  if (auto* ref_type = tip_type->As<Reference>()) {
+    tip_type = ref_type->type;
+  }
+
+  // Recursively flatten matrices, arrays, and structures.
+  return Switch(
+      tip_type,
+      [&](const Matrix* matrix_type) -> bool {
+        index_prefix.push_back(0);
+        const auto num_columns = static_cast<int>(matrix_type->columns);
+        const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
+        for (int col = 0; col < num_columns; col++) {
+          index_prefix.back() = col;
+          if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix,
+                                 vec_ty, forced_param_type, params,
+                                 statements)) {
+            return false;
+          }
+        }
+        return success();
+      },
+      [&](const Array* array_type) -> bool {
+        if (array_type->size == 0) {
+          return Fail() << "runtime-size array not allowed on pipeline IO";
+        }
+        index_prefix.push_back(0);
+        const Type* elem_ty = array_type->type;
+        for (int i = 0; i < static_cast<int>(array_type->size); i++) {
+          index_prefix.back() = i;
+          if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix,
+                                 elem_ty, forced_param_type, params,
+                                 statements)) {
+            return false;
+          }
+        }
+        return success();
+      },
+      [&](const Struct* struct_type) -> bool {
+        const auto& members = struct_type->members;
+        index_prefix.push_back(0);
+        for (int i = 0; i < static_cast<int>(members.size()); ++i) {
+          index_prefix.back() = i;
+          ast::AttributeList member_attrs(*attrs);
+          if (!parser_impl_.ConvertPipelineDecorations(
+                  struct_type,
+                  parser_impl_.GetMemberPipelineDecorations(*struct_type, i),
+                  &member_attrs)) {
+            return false;
+          }
+          if (!EmitPipelineInput(var_name, var_type, &member_attrs,
+                                 index_prefix, members[i], forced_param_type,
+                                 params, statements)) {
+            return false;
+          }
+          // Copy the location as updated by nested expansion of the member.
+          parser_impl_.SetLocation(attrs, GetLocation(member_attrs));
+        }
+        return success();
+      },
+      [&](Default) {
+        const bool is_builtin =
+            ast::HasAttribute<ast::BuiltinAttribute>(*attrs);
+
+        const Type* param_type = is_builtin ? forced_param_type : tip_type;
+
+        const auto param_name = namer_.MakeDerivedName(var_name + "_param");
+        // Create the parameter.
+        // TODO(dneto): Note: If the parameter has non-location decorations,
+        // then those decoration AST nodes will be reused between multiple
+        // elements of a matrix, array, or structure.  Normally that's
+        // disallowed but currently the SPIR-V reader will make duplicates when
+        // the entire AST is cloned at the top level of the SPIR-V reader flow.
+        // Consider rewriting this to avoid this node-sharing.
+        params->push_back(
+            builder_.Param(param_name, param_type->Build(builder_), *attrs));
+
+        // Add a body statement to copy the parameter to the corresponding
+        // private variable.
+        const ast::Expression* param_value = builder_.Expr(param_name);
+        const ast::Expression* store_dest = builder_.Expr(var_name);
+
+        // Index into the LHS as needed.
+        auto* current_type =
+            var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias();
+        for (auto index : index_prefix) {
+          Switch(
+              current_type,
+              [&](const Matrix* matrix_type) {
+                store_dest =
+                    builder_.IndexAccessor(store_dest, builder_.Expr(index));
+                current_type = ty_.Vector(matrix_type->type, matrix_type->rows);
+              },
+              [&](const Array* array_type) {
+                store_dest =
+                    builder_.IndexAccessor(store_dest, builder_.Expr(index));
+                current_type = array_type->type->UnwrapAlias();
+              },
+              [&](const Struct* struct_type) {
+                store_dest = builder_.MemberAccessor(
+                    store_dest, builder_.Expr(parser_impl_.GetMemberName(
+                                    *struct_type, index)));
+                current_type = struct_type->members[index];
+              });
+        }
+
+        if (is_builtin && (tip_type != forced_param_type)) {
+          // The parameter will have the WGSL type, but we need bitcast to
+          // the variable store type.
+          param_value = create<ast::BitcastExpression>(
+              tip_type->Build(builder_), param_value);
+        }
+
+        statements->push_back(builder_.Assign(store_dest, param_value));
+
+        // Increment the location attribute, in case more parameters will
+        // follow.
+        IncrementLocation(attrs);
+
+        return success();
+      });
+}
+
+void FunctionEmitter::IncrementLocation(ast::AttributeList* attributes) {
+  for (auto*& attr : *attributes) {
+    if (auto* loc_attr = attr->As<ast::LocationAttribute>()) {
+      // Replace this location attribute with a new one with one higher index.
+      // The old one doesn't leak because it's kept in the builder's AST node
+      // list.
+      attr = builder_.Location(loc_attr->source, loc_attr->value + 1);
+    }
+  }
+}
+
+const ast::Attribute* FunctionEmitter::GetLocation(
+    const ast::AttributeList& attributes) {
+  for (auto* const& attr : attributes) {
+    if (attr->Is<ast::LocationAttribute>()) {
+      return attr;
+    }
+  }
+  return nullptr;
+}
+
+bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
+                                         const Type* var_type,
+                                         ast::AttributeList* decos,
+                                         std::vector<int> index_prefix,
+                                         const Type* tip_type,
+                                         const Type* forced_member_type,
+                                         ast::StructMemberList* return_members,
+                                         ast::ExpressionList* return_exprs) {
+  tip_type = tip_type->UnwrapAlias();
+  if (auto* ref_type = tip_type->As<Reference>()) {
+    tip_type = ref_type->type;
+  }
+
+  // Recursively flatten matrices, arrays, and structures.
+  return Switch(
+      tip_type,
+      [&](const Matrix* matrix_type) -> bool {
+        index_prefix.push_back(0);
+        const auto num_columns = static_cast<int>(matrix_type->columns);
+        const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
+        for (int col = 0; col < num_columns; col++) {
+          index_prefix.back() = col;
+          if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix,
+                                  vec_ty, forced_member_type, return_members,
+                                  return_exprs)) {
+            return false;
+          }
+        }
+        return success();
+      },
+      [&](const Array* array_type) -> bool {
+        if (array_type->size == 0) {
+          return Fail() << "runtime-size array not allowed on pipeline IO";
+        }
+        index_prefix.push_back(0);
+        const Type* elem_ty = array_type->type;
+        for (int i = 0; i < static_cast<int>(array_type->size); i++) {
+          index_prefix.back() = i;
+          if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix,
+                                  elem_ty, forced_member_type, return_members,
+                                  return_exprs)) {
+            return false;
+          }
+        }
+        return success();
+      },
+      [&](const Struct* struct_type) -> bool {
+        const auto& members = struct_type->members;
+        index_prefix.push_back(0);
+        for (int i = 0; i < static_cast<int>(members.size()); ++i) {
+          index_prefix.back() = i;
+          ast::AttributeList member_attrs(*decos);
+          if (!parser_impl_.ConvertPipelineDecorations(
+                  struct_type,
+                  parser_impl_.GetMemberPipelineDecorations(*struct_type, i),
+                  &member_attrs)) {
+            return false;
+          }
+          if (!EmitPipelineOutput(var_name, var_type, &member_attrs,
+                                  index_prefix, members[i], forced_member_type,
+                                  return_members, return_exprs)) {
+            return false;
+          }
+          // Copy the location as updated by nested expansion of the member.
+          parser_impl_.SetLocation(decos, GetLocation(member_attrs));
+        }
+        return success();
+      },
+      [&](Default) {
+        const bool is_builtin =
+            ast::HasAttribute<ast::BuiltinAttribute>(*decos);
+
+        const Type* member_type = is_builtin ? forced_member_type : tip_type;
+        // Derive the member name directly from the variable name.  They can't
+        // collide.
+        const auto member_name = namer_.MakeDerivedName(var_name);
+        // Create the member.
+        // TODO(dneto): Note: If the parameter has non-location decorations,
+        // then those decoration AST nodes  will be reused between multiple
+        // elements of a matrix, array, or structure.  Normally that's
+        // disallowed but currently the SPIR-V reader will make duplicates when
+        // the entire AST is cloned at the top level of the SPIR-V reader flow.
+        // Consider rewriting this to avoid this node-sharing.
+        return_members->push_back(
+            builder_.Member(member_name, member_type->Build(builder_), *decos));
+
+        // Create an expression to evaluate the part of the variable indexed by
+        // the index_prefix.
+        const ast::Expression* load_source = builder_.Expr(var_name);
+
+        // Index into the variable as needed to pick out the flattened member.
+        auto* current_type =
+            var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias();
+        for (auto index : index_prefix) {
+          Switch(
+              current_type,
+              [&](const Matrix* matrix_type) {
+                load_source =
+                    builder_.IndexAccessor(load_source, builder_.Expr(index));
+                current_type = ty_.Vector(matrix_type->type, matrix_type->rows);
+              },
+              [&](const Array* array_type) {
+                load_source =
+                    builder_.IndexAccessor(load_source, builder_.Expr(index));
+                current_type = array_type->type->UnwrapAlias();
+              },
+              [&](const Struct* struct_type) {
+                load_source = builder_.MemberAccessor(
+                    load_source, builder_.Expr(parser_impl_.GetMemberName(
+                                     *struct_type, index)));
+                current_type = struct_type->members[index];
+              });
+        }
+
+        if (is_builtin && (tip_type != forced_member_type)) {
+          // The member will have the WGSL type, but we need bitcast to
+          // the variable store type.
+          load_source = create<ast::BitcastExpression>(
+              forced_member_type->Build(builder_), load_source);
+        }
+        return_exprs->push_back(load_source);
+
+        // Increment the location attribute, in case more parameters will
+        // follow.
+        IncrementLocation(decos);
+
+        return success();
+      });
+}
+
+bool FunctionEmitter::EmitEntryPointAsWrapper() {
+  Source source;
+
+  // The statements in the body.
+  ast::StatementList stmts;
+
+  FunctionDeclaration decl;
+  decl.source = source;
+  decl.name = ep_info_->name;
+  const ast::Type* return_type = nullptr;  // Populated below.
+
+  // Pipeline inputs become parameters to the wrapper function, and
+  // their values are saved into the corresponding private variables that
+  // have already been created.
+  for (uint32_t var_id : ep_info_->inputs) {
+    const auto* var = def_use_mgr_->GetDef(var_id);
+    TINT_ASSERT(Reader, var != nullptr);
+    TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
+    auto* store_type = GetVariableStoreType(*var);
+    auto* forced_param_type = store_type;
+    ast::AttributeList param_decos;
+    if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_param_type,
+                                                    &param_decos, true)) {
+      // This occurs, and is not an error, for the PointSize builtin.
+      if (!success()) {
+        // But exit early if an error was logged.
+        return false;
+      }
+      continue;
+    }
+
+    // We don't have to handle initializers because in Vulkan SPIR-V, Input
+    // variables must not have them.
+
+    const auto var_name = namer_.GetName(var_id);
+
+    bool ok = true;
+    if (HasBuiltinSampleMask(param_decos)) {
+      // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a scalar.
+      // Use the first element only.
+      auto* sample_mask_array_type =
+          store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
+      TINT_ASSERT(Reader, sample_mask_array_type);
+      ok = EmitPipelineInput(var_name, store_type, &param_decos, {0},
+                             sample_mask_array_type->type, forced_param_type,
+                             &(decl.params), &stmts);
+    } else {
+      // The normal path.
+      ok = EmitPipelineInput(var_name, store_type, &param_decos, {}, store_type,
+                             forced_param_type, &(decl.params), &stmts);
+    }
+    if (!ok) {
+      return false;
+    }
+  }
+
+  // Call the inner function.  It has no parameters.
+  stmts.push_back(create<ast::CallStatement>(
+      source,
+      create<ast::CallExpression>(
+          source,
+          create<ast::IdentifierExpression>(
+              source, builder_.Symbols().Register(ep_info_->inner_name)),
+          ast::ExpressionList{})));
+
+  // Pipeline outputs are mapped to the return value.
+  if (ep_info_->outputs.empty()) {
+    // There is nothing to return.
+    return_type = ty_.Void()->Build(builder_);
+  } else {
+    // Pipeline outputs are converted to a structure that is written
+    // to just before returning.
+
+    const auto return_struct_name =
+        namer_.MakeDerivedName(ep_info_->name + "_out");
+    const auto return_struct_sym =
+        builder_.Symbols().Register(return_struct_name);
+
+    // Define the structure.
+    std::vector<const ast::StructMember*> return_members;
+    ast::ExpressionList return_exprs;
+
+    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+
+    for (uint32_t var_id : ep_info_->outputs) {
+      if (var_id == builtin_position_info.per_vertex_var_id) {
+        // The SPIR-V gl_PerVertex variable has already been remapped to
+        // a gl_Position variable.  Substitute the type.
+        const Type* param_type = ty_.Vector(ty_.F32(), 4);
+        ast::AttributeList out_decos{
+            create<ast::BuiltinAttribute>(source, ast::Builtin::kPosition)};
+
+        const auto var_name = namer_.GetName(var_id);
+        return_members.push_back(
+            builder_.Member(var_name, param_type->Build(builder_), out_decos));
+        return_exprs.push_back(builder_.Expr(var_name));
+
+      } else {
+        const auto* var = def_use_mgr_->GetDef(var_id);
+        TINT_ASSERT(Reader, var != nullptr);
+        TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
+        const Type* store_type = GetVariableStoreType(*var);
+        const Type* forced_member_type = store_type;
+        ast::AttributeList out_decos;
+        if (!parser_impl_.ConvertDecorationsForVariable(
+                var_id, &forced_member_type, &out_decos, true)) {
+          // This occurs, and is not an error, for the PointSize builtin.
+          if (!success()) {
+            // But exit early if an error was logged.
+            return false;
+          }
+          continue;
+        }
+
+        const auto var_name = namer_.GetName(var_id);
+        bool ok = true;
+        if (HasBuiltinSampleMask(out_decos)) {
+          // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a
+          // scalar. Use the first element only.
+          auto* sample_mask_array_type =
+              store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
+          TINT_ASSERT(Reader, sample_mask_array_type);
+          ok = EmitPipelineOutput(var_name, store_type, &out_decos, {0},
+                                  sample_mask_array_type->type,
+                                  forced_member_type, &return_members,
+                                  &return_exprs);
+        } else {
+          // The normal path.
+          ok = EmitPipelineOutput(var_name, store_type, &out_decos, {},
+                                  store_type, forced_member_type,
+                                  &return_members, &return_exprs);
+        }
+        if (!ok) {
+          return false;
+        }
+      }
+    }
+
+    if (return_members.empty()) {
+      // This can occur if only the PointSize member is accessed, because we
+      // never emit it.
+      return_type = ty_.Void()->Build(builder_);
+    } else {
+      // Create and register the result type.
+      auto* str = create<ast::Struct>(Source{}, return_struct_sym,
+                                      return_members, ast::AttributeList{});
+      parser_impl_.AddTypeDecl(return_struct_sym, str);
+      return_type = builder_.ty.Of(str);
+
+      // Add the return-value statement.
+      stmts.push_back(create<ast::ReturnStatement>(
+          source,
+          builder_.Construct(source, return_type, std::move(return_exprs))));
+    }
+  }
+
+  auto* body = create<ast::BlockStatement>(source, stmts);
+  ast::AttributeList fn_attrs;
+  fn_attrs.emplace_back(create<ast::StageAttribute>(source, ep_info_->stage));
+
+  if (ep_info_->stage == ast::PipelineStage::kCompute) {
+    auto& size = ep_info_->workgroup_size;
+    if (size.x != 0 && size.y != 0 && size.z != 0) {
+      const ast::Expression* x = builder_.Expr(static_cast<int>(size.x));
+      const ast::Expression* y =
+          size.y ? builder_.Expr(static_cast<int>(size.y)) : nullptr;
+      const ast::Expression* z =
+          size.z ? builder_.Expr(static_cast<int>(size.z)) : nullptr;
+      fn_attrs.emplace_back(create<ast::WorkgroupAttribute>(Source{}, x, y, z));
+    }
+  }
+
+  builder_.AST().AddFunction(
+      create<ast::Function>(source, builder_.Symbols().Register(ep_info_->name),
+                            std::move(decl.params), return_type, body,
+                            std::move(fn_attrs), ast::AttributeList{}));
+
+  return true;
+}
+
+bool FunctionEmitter::ParseFunctionDeclaration(FunctionDeclaration* decl) {
+  if (failed()) {
+    return false;
+  }
+
+  const std::string name = namer_.Name(function_.result_id());
+
+  // Surprisingly, the "type id" on an OpFunction is the result type of the
+  // function, not the type of the function.  This is the one exceptional case
+  // in SPIR-V where the type ID is not the type of the result ID.
+  auto* ret_ty = parser_impl_.ConvertType(function_.type_id());
+  if (failed()) {
+    return false;
+  }
+  if (ret_ty == nullptr) {
+    return Fail()
+           << "internal error: unregistered return type for function with ID "
+           << function_.result_id();
+  }
+
+  ast::VariableList ast_params;
+  function_.ForEachParam(
+      [this, &ast_params](const spvtools::opt::Instruction* param) {
+        auto* type = parser_impl_.ConvertType(param->type_id());
+        if (type != nullptr) {
+          auto* ast_param = parser_impl_.MakeVariable(
+              param->result_id(), ast::StorageClass::kNone, type, true, false,
+              nullptr, ast::AttributeList{});
+          // Parameters are treated as const declarations.
+          ast_params.emplace_back(ast_param);
+          // The value is accessible by name.
+          identifier_types_.emplace(param->result_id(), type);
+        } else {
+          // We've already logged an error and emitted a diagnostic. Do nothing
+          // here.
+        }
+      });
+  if (failed()) {
+    return false;
+  }
+  decl->name = name;
+  decl->params = std::move(ast_params);
+  decl->return_type = ret_ty;
+  decl->attributes.clear();
+
+  return success();
+}
+
+const Type* FunctionEmitter::GetVariableStoreType(
+    const spvtools::opt::Instruction& var_decl_inst) {
+  const auto type_id = var_decl_inst.type_id();
+  // Normally we use the SPIRV-Tools optimizer to manage types.
+  // But when two struct types have the same member types and decorations,
+  // but differ only in member names, the two struct types will be
+  // represented by a single common internal struct type.
+  // So avoid the optimizer's representation and instead follow the
+  // SPIR-V instructions themselves.
+  const auto* ptr_ty = def_use_mgr_->GetDef(type_id);
+  const auto store_ty_id = ptr_ty->GetSingleWordInOperand(1);
+  const auto* result = parser_impl_.ConvertType(store_ty_id);
+  return result;
+}
+
+bool FunctionEmitter::EmitBody() {
+  RegisterBasicBlocks();
+
+  if (!TerminatorsAreValid()) {
+    return false;
+  }
+  if (!RegisterMerges()) {
+    return false;
+  }
+
+  ComputeBlockOrderAndPositions();
+  if (!VerifyHeaderContinueMergeOrder()) {
+    return false;
+  }
+  if (!LabelControlFlowConstructs()) {
+    return false;
+  }
+  if (!FindSwitchCaseHeaders()) {
+    return false;
+  }
+  if (!ClassifyCFGEdges()) {
+    return false;
+  }
+  if (!FindIfSelectionInternalHeaders()) {
+    return false;
+  }
+
+  if (!RegisterSpecialBuiltInVariables()) {
+    return false;
+  }
+  if (!RegisterLocallyDefinedValues()) {
+    return false;
+  }
+  FindValuesNeedingNamedOrHoistedDefinition();
+
+  if (!EmitFunctionVariables()) {
+    return false;
+  }
+  if (!EmitFunctionBodyStatements()) {
+    return false;
+  }
+  return success();
+}
+
+void FunctionEmitter::RegisterBasicBlocks() {
+  for (auto& block : function_) {
+    block_info_[block.id()] = std::make_unique<BlockInfo>(block);
+  }
+}
+
+bool FunctionEmitter::TerminatorsAreValid() {
+  if (failed()) {
+    return false;
+  }
+
+  const auto entry_id = function_.begin()->id();
+  for (const auto& block : function_) {
+    if (!block.terminator()) {
+      return Fail() << "Block " << block.id() << " has no terminator";
+    }
+  }
+  for (const auto& block : function_) {
+    block.WhileEachSuccessorLabel(
+        [this, &block, entry_id](const uint32_t succ_id) -> bool {
+          if (succ_id == entry_id) {
+            return Fail() << "Block " << block.id()
+                          << " branches to function entry block " << entry_id;
+          }
+          if (!GetBlockInfo(succ_id)) {
+            return Fail() << "Block " << block.id() << " in function "
+                          << function_.DefInst().result_id() << " branches to "
+                          << succ_id << " which is not a block in the function";
+          }
+          return true;
+        });
+  }
+  return success();
+}
+
+bool FunctionEmitter::RegisterMerges() {
+  if (failed()) {
+    return false;
+  }
+
+  const auto entry_id = function_.begin()->id();
+  for (const auto& block : function_) {
+    const auto block_id = block.id();
+    auto* block_info = GetBlockInfo(block_id);
+    if (!block_info) {
+      return Fail() << "internal error: block " << block_id
+                    << " missing; blocks should already "
+                       "have been registered";
+    }
+
+    if (const auto* inst = block.GetMergeInst()) {
+      auto terminator_opcode = block.terminator()->opcode();
+      switch (inst->opcode()) {
+        case SpvOpSelectionMerge:
+          if ((terminator_opcode != SpvOpBranchConditional) &&
+              (terminator_opcode != SpvOpSwitch)) {
+            return Fail() << "Selection header " << block_id
+                          << " does not end in an OpBranchConditional or "
+                             "OpSwitch instruction";
+          }
+          break;
+        case SpvOpLoopMerge:
+          if ((terminator_opcode != SpvOpBranchConditional) &&
+              (terminator_opcode != SpvOpBranch)) {
+            return Fail() << "Loop header " << block_id
+                          << " does not end in an OpBranch or "
+                             "OpBranchConditional instruction";
+          }
+          break;
+        default:
+          break;
+      }
+
+      const uint32_t header = block.id();
+      auto* header_info = block_info;
+      const uint32_t merge = inst->GetSingleWordInOperand(0);
+      auto* merge_info = GetBlockInfo(merge);
+      if (!merge_info) {
+        return Fail() << "Structured header block " << header
+                      << " declares invalid merge block " << merge;
+      }
+      if (merge == header) {
+        return Fail() << "Structured header block " << header
+                      << " cannot be its own merge block";
+      }
+      if (merge_info->header_for_merge) {
+        return Fail() << "Block " << merge
+                      << " declared as merge block for more than one header: "
+                      << merge_info->header_for_merge << ", " << header;
+      }
+      merge_info->header_for_merge = header;
+      header_info->merge_for_header = merge;
+
+      if (inst->opcode() == SpvOpLoopMerge) {
+        if (header == entry_id) {
+          return Fail() << "Function entry block " << entry_id
+                        << " cannot be a loop header";
+        }
+        const uint32_t ct = inst->GetSingleWordInOperand(1);
+        auto* ct_info = GetBlockInfo(ct);
+        if (!ct_info) {
+          return Fail() << "Structured header " << header
+                        << " declares invalid continue target " << ct;
+        }
+        if (ct == merge) {
+          return Fail() << "Invalid structured header block " << header
+                        << ": declares block " << ct
+                        << " as both its merge block and continue target";
+        }
+        if (ct_info->header_for_continue) {
+          return Fail()
+                 << "Block " << ct
+                 << " declared as continue target for more than one header: "
+                 << ct_info->header_for_continue << ", " << header;
+        }
+        ct_info->header_for_continue = header;
+        header_info->continue_for_header = ct;
+      }
+    }
+
+    // Check single-block loop cases.
+    bool is_single_block_loop = false;
+    block_info->basic_block->ForEachSuccessorLabel(
+        [&is_single_block_loop, block_id](const uint32_t succ) {
+          if (block_id == succ)
+            is_single_block_loop = true;
+        });
+    const auto ct = block_info->continue_for_header;
+    block_info->is_continue_entire_loop = ct == block_id;
+    if (is_single_block_loop && !block_info->is_continue_entire_loop) {
+      return Fail() << "Block " << block_id
+                    << " branches to itself but is not its own continue target";
+    }
+    // It's valid for a the header of a multi-block loop header to declare
+    // itself as its own continue target.
+  }
+  return success();
+}
+
+void FunctionEmitter::ComputeBlockOrderAndPositions() {
+  block_order_ = StructuredTraverser(function_).ReverseStructuredPostOrder();
+
+  for (uint32_t i = 0; i < block_order_.size(); ++i) {
+    GetBlockInfo(block_order_[i])->pos = i;
+  }
+  // The invalid block position is not the position of any block that is in the
+  // order.
+  assert(block_order_.size() <= kInvalidBlockPos);
+}
+
+bool FunctionEmitter::VerifyHeaderContinueMergeOrder() {
+  // Verify interval rules for a structured header block:
+  //
+  //    If the CFG satisfies structured control flow rules, then:
+  //    If header H is reachable, then the following "interval rules" hold,
+  //    where M(H) is H's merge block, and CT(H) is H's continue target:
+  //
+  //      Pos(H) < Pos(M(H))
+  //
+  //      If CT(H) exists, then:
+  //         Pos(H) <= Pos(CT(H))
+  //         Pos(CT(H)) < Pos(M)
+  //
+  for (auto block_id : block_order_) {
+    const auto* block_info = GetBlockInfo(block_id);
+    const auto merge = block_info->merge_for_header;
+    if (merge == 0) {
+      continue;
+    }
+    // This is a header.
+    const auto header = block_id;
+    const auto* header_info = block_info;
+    const auto header_pos = header_info->pos;
+    const auto merge_pos = GetBlockInfo(merge)->pos;
+
+    // Pos(H) < Pos(M(H))
+    // Note: When recording merges we made sure H != M(H)
+    if (merge_pos <= header_pos) {
+      return Fail() << "Header " << header
+                    << " does not strictly dominate its merge block " << merge;
+      // TODO(dneto): Report a path from the entry block to the merge block
+      // without going through the header block.
+    }
+
+    const auto ct = block_info->continue_for_header;
+    if (ct == 0) {
+      continue;
+    }
+    // Furthermore, this is a loop header.
+    const auto* ct_info = GetBlockInfo(ct);
+    const auto ct_pos = ct_info->pos;
+    // Pos(H) <= Pos(CT(H))
+    if (ct_pos < header_pos) {
+      Fail() << "Loop header " << header
+             << " does not dominate its continue target " << ct;
+    }
+    // Pos(CT(H)) < Pos(M(H))
+    // Note: When recording merges we made sure CT(H) != M(H)
+    if (merge_pos <= ct_pos) {
+      return Fail() << "Merge block " << merge << " for loop headed at block "
+                    << header
+                    << " appears at or before the loop's continue "
+                       "construct headed by "
+                       "block "
+                    << ct;
+    }
+  }
+  return success();
+}
+
+bool FunctionEmitter::LabelControlFlowConstructs() {
+  // Label each block in the block order with its nearest enclosing structured
+  // control flow construct. Populates the |construct| member of BlockInfo.
+
+  //  Keep a stack of enclosing structured control flow constructs.  Start
+  //  with the synthetic construct representing the entire function.
+  //
+  //  Scan from left to right in the block order, and check conditions
+  //  on each block in the following order:
+  //
+  //        a. When you reach a merge block, the top of the stack should
+  //           be the associated header. Pop it off.
+  //        b. When you reach a header, push it on the stack.
+  //        c. When you reach a continue target, push it on the stack.
+  //           (A block can be both a header and a continue target.)
+  //        c. When you reach a block with an edge branching backward (in the
+  //           structured order) to block T:
+  //            T should be a loop header, and the top of the stack should be a
+  //            continue target associated with T.
+  //            This is the end of the continue construct. Pop the continue
+  //            target off the stack.
+  //
+  //       Note: A loop header can declare itself as its own continue target.
+  //
+  //       Note: For a single-block loop, that block is a header, its own
+  //       continue target, and its own backedge block.
+  //
+  //       Note: We pop the merge off first because a merge block that marks
+  //       the end of one construct can be a single-block loop.  So that block
+  //       is a merge, a header, a continue target, and a backedge block.
+  //       But we want to finish processing of the merge before dealing with
+  //       the loop.
+  //
+  //      In the same scan, mark each basic block with the nearest enclosing
+  //      header: the most recent header for which we haven't reached its merge
+  //      block. Also mark the the most recent continue target for which we
+  //      haven't reached the backedge block.
+
+  TINT_ASSERT(Reader, block_order_.size() > 0);
+  constructs_.clear();
+  const auto entry_id = block_order_[0];
+
+  // The stack of enclosing constructs.
+  std::vector<Construct*> enclosing;
+
+  // Creates a control flow construct and pushes it onto the stack.
+  // Its parent is the top of the stack, or nullptr if the stack is empty.
+  // Returns the newly created construct.
+  auto push_construct = [this, &enclosing](size_t depth, Construct::Kind k,
+                                           uint32_t begin_id,
+                                           uint32_t end_id) -> Construct* {
+    const auto begin_pos = GetBlockInfo(begin_id)->pos;
+    const auto end_pos =
+        end_id == 0 ? uint32_t(block_order_.size()) : GetBlockInfo(end_id)->pos;
+    const auto* parent = enclosing.empty() ? nullptr : enclosing.back();
+    auto scope_end_pos = end_pos;
+    // A loop construct is added right after its associated continue construct.
+    // In that case, adjust the parent up.
+    if (k == Construct::kLoop) {
+      TINT_ASSERT(Reader, parent);
+      TINT_ASSERT(Reader, parent->kind == Construct::kContinue);
+      scope_end_pos = parent->end_pos;
+      parent = parent->parent;
+    }
+    constructs_.push_back(std::make_unique<Construct>(
+        parent, static_cast<int>(depth), k, begin_id, end_id, begin_pos,
+        end_pos, scope_end_pos));
+    Construct* result = constructs_.back().get();
+    enclosing.push_back(result);
+    return result;
+  };
+
+  // Make a synthetic kFunction construct to enclose all blocks in the function.
+  push_construct(0, Construct::kFunction, entry_id, 0);
+  // The entry block can be a selection construct, so be sure to process
+  // it anyway.
+
+  for (uint32_t i = 0; i < block_order_.size(); ++i) {
+    const auto block_id = block_order_[i];
+    TINT_ASSERT(Reader, block_id > 0);
+    auto* block_info = GetBlockInfo(block_id);
+    TINT_ASSERT(Reader, block_info);
+
+    if (enclosing.empty()) {
+      return Fail() << "internal error: too many merge blocks before block "
+                    << block_id;
+    }
+    const Construct* top = enclosing.back();
+
+    while (block_id == top->end_id) {
+      // We've reached a predeclared end of the construct.  Pop it off the
+      // stack.
+      enclosing.pop_back();
+      if (enclosing.empty()) {
+        return Fail() << "internal error: too many merge blocks before block "
+                      << block_id;
+      }
+      top = enclosing.back();
+    }
+
+    const auto merge = block_info->merge_for_header;
+    if (merge != 0) {
+      // The current block is a header.
+      const auto header = block_id;
+      const auto* header_info = block_info;
+      const auto depth = 1 + top->depth;
+      const auto ct = header_info->continue_for_header;
+      if (ct != 0) {
+        // The current block is a loop header.
+        // We should see the continue construct after the loop construct, so
+        // push the loop construct last.
+
+        // From the interval rule, the continue construct consists of blocks
+        // in the block order, starting at the continue target, until just
+        // before the merge block.
+        top = push_construct(depth, Construct::kContinue, ct, merge);
+        // A loop header that is its own continue target will have an
+        // empty loop construct. Only create a loop construct when
+        // the continue target is *not* the same as the loop header.
+        if (header != ct) {
+          // From the interval rule, the loop construct consists of blocks
+          // in the block order, starting at the header, until just
+          // before the continue target.
+          top = push_construct(depth, Construct::kLoop, header, ct);
+
+          // If the loop header branches to two different blocks inside the loop
+          // construct, then the loop body should be modeled as an if-selection
+          // construct
+          std::vector<uint32_t> targets;
+          header_info->basic_block->ForEachSuccessorLabel(
+              [&targets](const uint32_t target) { targets.push_back(target); });
+          if ((targets.size() == 2u) && targets[0] != targets[1]) {
+            const auto target0_pos = GetBlockInfo(targets[0])->pos;
+            const auto target1_pos = GetBlockInfo(targets[1])->pos;
+            if (top->ContainsPos(target0_pos) &&
+                top->ContainsPos(target1_pos)) {
+              // Insert a synthetic if-selection
+              top = push_construct(depth + 1, Construct::kIfSelection, header,
+                                   ct);
+            }
+          }
+        }
+      } else {
+        // From the interval rule, the selection construct consists of blocks
+        // in the block order, starting at the header, until just before the
+        // merge block.
+        const auto branch_opcode =
+            header_info->basic_block->terminator()->opcode();
+        const auto kind = (branch_opcode == SpvOpBranchConditional)
+                              ? Construct::kIfSelection
+                              : Construct::kSwitchSelection;
+        top = push_construct(depth, kind, header, merge);
+      }
+    }
+
+    TINT_ASSERT(Reader, top);
+    block_info->construct = top;
+  }
+
+  // At the end of the block list, we should only have the kFunction construct
+  // left.
+  if (enclosing.size() != 1) {
+    return Fail() << "internal error: unbalanced structured constructs when "
+                     "labeling structured constructs: ended with "
+                  << enclosing.size() - 1 << " unterminated constructs";
+  }
+  const auto* top = enclosing[0];
+  if (top->kind != Construct::kFunction || top->depth != 0) {
+    return Fail() << "internal error: outermost construct is not a function?!";
+  }
+
+  return success();
+}
+
+bool FunctionEmitter::FindSwitchCaseHeaders() {
+  if (failed()) {
+    return false;
+  }
+  for (auto& construct : constructs_) {
+    if (construct->kind != Construct::kSwitchSelection) {
+      continue;
+    }
+    const auto* branch =
+        GetBlockInfo(construct->begin_id)->basic_block->terminator();
+
+    // Mark the default block
+    const auto default_id = branch->GetSingleWordInOperand(1);
+    auto* default_block = GetBlockInfo(default_id);
+    // A default target can't be a backedge.
+    if (construct->begin_pos >= default_block->pos) {
+      // An OpSwitch must dominate its cases.  Also, it can't be a self-loop
+      // as that would be a backedge, and backedges can only target a loop,
+      // and loops use an OpLoopMerge instruction, which can't precede an
+      // OpSwitch.
+      return Fail() << "Switch branch from block " << construct->begin_id
+                    << " to default target block " << default_id
+                    << " can't be a back-edge";
+    }
+    // A default target can be the merge block, but can't go past it.
+    if (construct->end_pos < default_block->pos) {
+      return Fail() << "Switch branch from block " << construct->begin_id
+                    << " to default block " << default_id
+                    << " escapes the selection construct";
+    }
+    if (default_block->default_head_for) {
+      // An OpSwitch must dominate its cases, including the default target.
+      return Fail() << "Block " << default_id
+                    << " is declared as the default target for two OpSwitch "
+                       "instructions, at blocks "
+                    << default_block->default_head_for->begin_id << " and "
+                    << construct->begin_id;
+    }
+    if ((default_block->header_for_merge != 0) &&
+        (default_block->header_for_merge != construct->begin_id)) {
+      // The switch instruction for this default block is an alternate path to
+      // the merge block, and hence the merge block is not dominated by its own
+      // (different) header.
+      return Fail() << "Block " << default_block->id
+                    << " is the default block for switch-selection header "
+                    << construct->begin_id << " and also the merge block for "
+                    << default_block->header_for_merge
+                    << " (violates dominance rule)";
+    }
+
+    default_block->default_head_for = construct.get();
+    default_block->default_is_merge = default_block->pos == construct->end_pos;
+
+    // Map a case target to the list of values selecting that case.
+    std::unordered_map<uint32_t, std::vector<uint64_t>> block_to_values;
+    std::vector<uint32_t> case_targets;
+    std::unordered_set<uint64_t> case_values;
+
+    // Process case targets.
+    for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
+      const auto value = branch->GetInOperand(iarg).AsLiteralUint64();
+      const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
+
+      if (case_values.count(value)) {
+        return Fail() << "Duplicate case value " << value
+                      << " in OpSwitch in block " << construct->begin_id;
+      }
+      case_values.insert(value);
+      if (block_to_values.count(case_target_id) == 0) {
+        case_targets.push_back(case_target_id);
+      }
+      block_to_values[case_target_id].push_back(value);
+    }
+
+    for (uint32_t case_target_id : case_targets) {
+      auto* case_block = GetBlockInfo(case_target_id);
+
+      case_block->case_values = std::make_unique<std::vector<uint64_t>>(
+          std::move(block_to_values[case_target_id]));
+
+      // A case target can't be a back-edge.
+      if (construct->begin_pos >= case_block->pos) {
+        // An OpSwitch must dominate its cases.  Also, it can't be a self-loop
+        // as that would be a backedge, and backedges can only target a loop,
+        // and loops use an OpLoopMerge instruction, which can't preceded an
+        // OpSwitch.
+        return Fail() << "Switch branch from block " << construct->begin_id
+                      << " to case target block " << case_target_id
+                      << " can't be a back-edge";
+      }
+      // A case target can be the merge block, but can't go past it.
+      if (construct->end_pos < case_block->pos) {
+        return Fail() << "Switch branch from block " << construct->begin_id
+                      << " to case target block " << case_target_id
+                      << " escapes the selection construct";
+      }
+      if (case_block->header_for_merge != 0 &&
+          case_block->header_for_merge != construct->begin_id) {
+        // The switch instruction for this case block is an alternate path to
+        // the merge block, and hence the merge block is not dominated by its
+        // own (different) header.
+        return Fail() << "Block " << case_block->id
+                      << " is a case block for switch-selection header "
+                      << construct->begin_id << " and also the merge block for "
+                      << case_block->header_for_merge
+                      << " (violates dominance rule)";
+      }
+
+      // Mark the target as a case target.
+      if (case_block->case_head_for) {
+        // An OpSwitch must dominate its cases.
+        return Fail()
+               << "Block " << case_target_id
+               << " is declared as the switch case target for two OpSwitch "
+                  "instructions, at blocks "
+               << case_block->case_head_for->begin_id << " and "
+               << construct->begin_id;
+      }
+      case_block->case_head_for = construct.get();
+    }
+  }
+  return success();
+}
+
+BlockInfo* FunctionEmitter::HeaderIfBreakable(const Construct* c) {
+  if (c == nullptr) {
+    return nullptr;
+  }
+  switch (c->kind) {
+    case Construct::kLoop:
+    case Construct::kSwitchSelection:
+      return GetBlockInfo(c->begin_id);
+    case Construct::kContinue: {
+      const auto* continue_target = GetBlockInfo(c->begin_id);
+      return GetBlockInfo(continue_target->header_for_continue);
+    }
+    default:
+      break;
+  }
+  return nullptr;
+}
+
+const Construct* FunctionEmitter::SiblingLoopConstruct(
+    const Construct* c) const {
+  if (c == nullptr || c->kind != Construct::kContinue) {
+    return nullptr;
+  }
+  const uint32_t continue_target_id = c->begin_id;
+  const auto* continue_target = GetBlockInfo(continue_target_id);
+  const uint32_t header_id = continue_target->header_for_continue;
+  if (continue_target_id == header_id) {
+    // The continue target is the whole loop.
+    return nullptr;
+  }
+  const auto* candidate = GetBlockInfo(header_id)->construct;
+  // Walk up the construct tree until we hit the loop.  In future
+  // we might handle the corner case where the same block is both a
+  // loop header and a selection header. For example, where the
+  // loop header block has a conditional branch going to distinct
+  // targets inside the loop body.
+  while (candidate && candidate->kind != Construct::kLoop) {
+    candidate = candidate->parent;
+  }
+  return candidate;
+}
+
+bool FunctionEmitter::ClassifyCFGEdges() {
+  if (failed()) {
+    return false;
+  }
+
+  // Checks validity of CFG edges leaving each basic block.  This implicitly
+  // checks dominance rules for headers and continue constructs.
+  //
+  // For each branch encountered, classify each edge (S,T) as:
+  //    - a back-edge
+  //    - a structured exit (specific ways of branching to enclosing construct)
+  //    - a normal (forward) edge, either natural control flow or a case
+  //    fallthrough
+  //
+  // If more than one block is targeted by a normal edge, then S must be a
+  // structured header.
+  //
+  // Term: NEC(B) is the nearest enclosing construct for B.
+  //
+  // If edge (S,T) is a normal edge, and NEC(S) != NEC(T), then
+  //    T is the header block of its NEC(T), and
+  //    NEC(S) is the parent of NEC(T).
+
+  for (const auto src : block_order_) {
+    TINT_ASSERT(Reader, src > 0);
+    auto* src_info = GetBlockInfo(src);
+    TINT_ASSERT(Reader, src_info);
+    const auto src_pos = src_info->pos;
+    const auto& src_construct = *(src_info->construct);
+
+    // Compute the ordered list of unique successors.
+    std::vector<uint32_t> successors;
+    {
+      std::unordered_set<uint32_t> visited;
+      src_info->basic_block->ForEachSuccessorLabel(
+          [&successors, &visited](const uint32_t succ) {
+            if (visited.count(succ) == 0) {
+              successors.push_back(succ);
+              visited.insert(succ);
+            }
+          });
+    }
+
+    // There should only be one backedge per backedge block.
+    uint32_t num_backedges = 0;
+
+    // Track destinations for normal forward edges, either kForward
+    // or kCaseFallThrough. These count toward the need
+    // to have a merge instruction.  We also track kIfBreak edges
+    // because when used with normal forward edges, we'll need
+    // to generate a flow guard variable.
+    std::vector<uint32_t> normal_forward_edges;
+    std::vector<uint32_t> if_break_edges;
+
+    if (successors.empty() && src_construct.enclosing_continue) {
+      // Kill and return are not allowed in a continue construct.
+      return Fail() << "Invalid function exit at block " << src
+                    << " from continue construct starting at "
+                    << src_construct.enclosing_continue->begin_id;
+    }
+
+    for (const auto dest : successors) {
+      const auto* dest_info = GetBlockInfo(dest);
+      // We've already checked terminators are valid.
+      TINT_ASSERT(Reader, dest_info);
+      const auto dest_pos = dest_info->pos;
+
+      // Insert the edge kind entry and keep a handle to update
+      // its classification.
+      EdgeKind& edge_kind = src_info->succ_edge[dest];
+
+      if (src_pos >= dest_pos) {
+        // This is a backedge.
+        edge_kind = EdgeKind::kBack;
+        num_backedges++;
+        const auto* continue_construct = src_construct.enclosing_continue;
+        if (!continue_construct) {
+          return Fail() << "Invalid backedge (" << src << "->" << dest
+                        << "): " << src << " is not in a continue construct";
+        }
+        if (src_pos != continue_construct->end_pos - 1) {
+          return Fail() << "Invalid exit (" << src << "->" << dest
+                        << ") from continue construct: " << src
+                        << " is not the last block in the continue construct "
+                           "starting at "
+                        << src_construct.begin_id
+                        << " (violates post-dominance rule)";
+        }
+        const auto* ct_info = GetBlockInfo(continue_construct->begin_id);
+        TINT_ASSERT(Reader, ct_info);
+        if (ct_info->header_for_continue != dest) {
+          return Fail()
+                 << "Invalid backedge (" << src << "->" << dest
+                 << "): does not branch to the corresponding loop header, "
+                    "expected "
+                 << ct_info->header_for_continue;
+        }
+      } else {
+        // This is a forward edge.
+        // For now, classify it that way, but we might update it.
+        edge_kind = EdgeKind::kForward;
+
+        // Exit from a continue construct can only be from the last block.
+        const auto* continue_construct = src_construct.enclosing_continue;
+        if (continue_construct != nullptr) {
+          if (continue_construct->ContainsPos(src_pos) &&
+              !continue_construct->ContainsPos(dest_pos) &&
+              (src_pos != continue_construct->end_pos - 1)) {
+            return Fail() << "Invalid exit (" << src << "->" << dest
+                          << ") from continue construct: " << src
+                          << " is not the last block in the continue construct "
+                             "starting at "
+                          << continue_construct->begin_id
+                          << " (violates post-dominance rule)";
+          }
+        }
+
+        // Check valid structured exit cases.
+
+        if (edge_kind == EdgeKind::kForward) {
+          // Check for a 'break' from a loop or from a switch.
+          const auto* breakable_header = HeaderIfBreakable(
+              src_construct.enclosing_loop_or_continue_or_switch);
+          if (breakable_header != nullptr) {
+            if (dest == breakable_header->merge_for_header) {
+              // It's a break.
+              edge_kind = (breakable_header->construct->kind ==
+                           Construct::kSwitchSelection)
+                              ? EdgeKind::kSwitchBreak
+                              : EdgeKind::kLoopBreak;
+            }
+          }
+        }
+
+        if (edge_kind == EdgeKind::kForward) {
+          // Check for a 'continue' from within a loop.
+          const auto* loop_header =
+              HeaderIfBreakable(src_construct.enclosing_loop);
+          if (loop_header != nullptr) {
+            if (dest == loop_header->continue_for_header) {
+              // It's a continue.
+              edge_kind = EdgeKind::kLoopContinue;
+            }
+          }
+        }
+
+        if (edge_kind == EdgeKind::kForward) {
+          const auto& header_info = *GetBlockInfo(src_construct.begin_id);
+          if (dest == header_info.merge_for_header) {
+            // Branch to construct's merge block.  The loop break and
+            // switch break cases have already been covered.
+            edge_kind = EdgeKind::kIfBreak;
+          }
+        }
+
+        // A forward edge into a case construct that comes from something
+        // other than the OpSwitch is actually a fallthrough.
+        if (edge_kind == EdgeKind::kForward) {
+          const auto* switch_construct =
+              (dest_info->case_head_for ? dest_info->case_head_for
+                                        : dest_info->default_head_for);
+          if (switch_construct != nullptr) {
+            if (src != switch_construct->begin_id) {
+              edge_kind = EdgeKind::kCaseFallThrough;
+            }
+          }
+        }
+
+        // The edge-kind has been finalized.
+
+        if ((edge_kind == EdgeKind::kForward) ||
+            (edge_kind == EdgeKind::kCaseFallThrough)) {
+          normal_forward_edges.push_back(dest);
+        }
+        if (edge_kind == EdgeKind::kIfBreak) {
+          if_break_edges.push_back(dest);
+        }
+
+        if ((edge_kind == EdgeKind::kForward) ||
+            (edge_kind == EdgeKind::kCaseFallThrough)) {
+          // Check for an invalid forward exit out of this construct.
+          if (dest_info->pos > src_construct.end_pos) {
+            // In most cases we're bypassing the merge block for the source
+            // construct.
+            auto end_block = src_construct.end_id;
+            const char* end_block_desc = "merge block";
+            if (src_construct.kind == Construct::kLoop) {
+              // For a loop construct, we have two valid places to go: the
+              // continue target or the merge for the loop header, which is
+              // further down.
+              const auto loop_merge =
+                  GetBlockInfo(src_construct.begin_id)->merge_for_header;
+              if (dest_info->pos >= GetBlockInfo(loop_merge)->pos) {
+                // We're bypassing the loop's merge block.
+                end_block = loop_merge;
+              } else {
+                // We're bypassing the loop's continue target, and going into
+                // the middle of the continue construct.
+                end_block_desc = "continue target";
+              }
+            }
+            return Fail()
+                   << "Branch from block " << src << " to block " << dest
+                   << " is an invalid exit from construct starting at block "
+                   << src_construct.begin_id << "; branch bypasses "
+                   << end_block_desc << " " << end_block;
+          }
+
+          // Check dominance.
+
+          //      Look for edges that violate the dominance condition: a branch
+          //      from X to Y where:
+          //        If Y is in a nearest enclosing continue construct headed by
+          //        CT:
+          //          Y is not CT, and
+          //          In the structured order, X appears before CT order or
+          //          after CT's backedge block.
+          //        Otherwise, if Y is in a nearest enclosing construct
+          //        headed by H:
+          //          Y is not H, and
+          //          In the structured order, X appears before H or after H's
+          //          merge block.
+
+          const auto& dest_construct = *(dest_info->construct);
+          if (dest != dest_construct.begin_id &&
+              !dest_construct.ContainsPos(src_pos)) {
+            return Fail() << "Branch from " << src << " to " << dest
+                          << " bypasses "
+                          << (dest_construct.kind == Construct::kContinue
+                                  ? "continue target "
+                                  : "header ")
+                          << dest_construct.begin_id
+                          << " (dominance rule violated)";
+          }
+        }
+      }  // end forward edge
+    }    // end successor
+
+    if (num_backedges > 1) {
+      return Fail() << "Block " << src
+                    << " has too many backedges: " << num_backedges;
+    }
+    if ((normal_forward_edges.size() > 1) &&
+        (src_info->merge_for_header == 0)) {
+      return Fail() << "Control flow diverges at block " << src << " (to "
+                    << normal_forward_edges[0] << ", "
+                    << normal_forward_edges[1]
+                    << ") but it is not a structured header (it has no merge "
+                       "instruction)";
+    }
+    if ((normal_forward_edges.size() + if_break_edges.size() > 1) &&
+        (src_info->merge_for_header == 0)) {
+      // There is a branch to the merge of an if-selection combined
+      // with an other normal forward branch.  Control within the
+      // if-selection needs to be gated by a flow predicate.
+      for (auto if_break_dest : if_break_edges) {
+        auto* head_info =
+            GetBlockInfo(GetBlockInfo(if_break_dest)->header_for_merge);
+        // Generate a guard name, but only once.
+        if (head_info->flow_guard_name.empty()) {
+          const std::string guard = "guard" + std::to_string(head_info->id);
+          head_info->flow_guard_name = namer_.MakeDerivedName(guard);
+        }
+      }
+    }
+  }
+
+  return success();
+}
+
+bool FunctionEmitter::FindIfSelectionInternalHeaders() {
+  if (failed()) {
+    return false;
+  }
+  for (auto& construct : constructs_) {
+    if (construct->kind != Construct::kIfSelection) {
+      continue;
+    }
+    auto* if_header_info = GetBlockInfo(construct->begin_id);
+    const auto* branch = if_header_info->basic_block->terminator();
+    const auto true_head = branch->GetSingleWordInOperand(1);
+    const auto false_head = branch->GetSingleWordInOperand(2);
+
+    auto* true_head_info = GetBlockInfo(true_head);
+    auto* false_head_info = GetBlockInfo(false_head);
+    const auto true_head_pos = true_head_info->pos;
+    const auto false_head_pos = false_head_info->pos;
+
+    const bool contains_true = construct->ContainsPos(true_head_pos);
+    const bool contains_false = construct->ContainsPos(false_head_pos);
+
+    // The cases for each edge are:
+    //  - kBack: invalid because it's an invalid exit from the selection
+    //  - kSwitchBreak ; record this for later special processing
+    //  - kLoopBreak ; record this for later special processing
+    //  - kLoopContinue ; record this for later special processing
+    //  - kIfBreak; normal case, may require a guard variable.
+    //  - kFallThrough; invalid exit from the selection
+    //  - kForward; normal case
+
+    if_header_info->true_kind = if_header_info->succ_edge[true_head];
+    if_header_info->false_kind = if_header_info->succ_edge[false_head];
+    if (contains_true) {
+      if_header_info->true_head = true_head;
+    }
+    if (contains_false) {
+      if_header_info->false_head = false_head;
+    }
+
+    if (contains_true && (true_head_info->header_for_merge != 0) &&
+        (true_head_info->header_for_merge != construct->begin_id)) {
+      // The OpBranchConditional instruction for the true head block is an
+      // alternate path to the merge block of a construct nested inside the
+      // selection, and hence the merge block is not dominated by its own
+      // (different) header.
+      return Fail() << "Block " << true_head
+                    << " is the true branch for if-selection header "
+                    << construct->begin_id
+                    << " and also the merge block for header block "
+                    << true_head_info->header_for_merge
+                    << " (violates dominance rule)";
+    }
+    if (contains_false && (false_head_info->header_for_merge != 0) &&
+        (false_head_info->header_for_merge != construct->begin_id)) {
+      // The OpBranchConditional instruction for the false head block is an
+      // alternate path to the merge block of a construct nested inside the
+      // selection, and hence the merge block is not dominated by its own
+      // (different) header.
+      return Fail() << "Block " << false_head
+                    << " is the false branch for if-selection header "
+                    << construct->begin_id
+                    << " and also the merge block for header block "
+                    << false_head_info->header_for_merge
+                    << " (violates dominance rule)";
+    }
+
+    if (contains_true && contains_false && (true_head_pos != false_head_pos)) {
+      // This construct has both a "then" clause and an "else" clause.
+      //
+      // We have this structure:
+      //
+      //   Option 1:
+      //
+      //     * condbranch
+      //        * true-head (start of then-clause)
+      //        ...
+      //        * end-then-clause
+      //        * false-head (start of else-clause)
+      //        ...
+      //        * end-false-clause
+      //        * premerge-head
+      //        ...
+      //     * selection merge
+      //
+      //   Option 2:
+      //
+      //     * condbranch
+      //        * true-head (start of then-clause)
+      //        ...
+      //        * end-then-clause
+      //        * false-head (start of else-clause) and also premerge-head
+      //        ...
+      //        * end-false-clause
+      //     * selection merge
+      //
+      //   Option 3:
+      //
+      //     * condbranch
+      //        * false-head (start of else-clause)
+      //        ...
+      //        * end-else-clause
+      //        * true-head (start of then-clause) and also premerge-head
+      //        ...
+      //        * end-then-clause
+      //     * selection merge
+      //
+      // The premerge-head exists if there is a kForward branch from the end
+      // of the first clause to a block within the surrounding selection.
+      // The first clause might be a then-clause or an else-clause.
+      const auto second_head = std::max(true_head_pos, false_head_pos);
+      const auto end_first_clause_pos = second_head - 1;
+      TINT_ASSERT(Reader, end_first_clause_pos < block_order_.size());
+      const auto end_first_clause = block_order_[end_first_clause_pos];
+      uint32_t premerge_id = 0;
+      uint32_t if_break_id = 0;
+      for (auto& then_succ_iter : GetBlockInfo(end_first_clause)->succ_edge) {
+        const uint32_t dest_id = then_succ_iter.first;
+        const auto edge_kind = then_succ_iter.second;
+        switch (edge_kind) {
+          case EdgeKind::kIfBreak:
+            if_break_id = dest_id;
+            break;
+          case EdgeKind::kForward: {
+            if (construct->ContainsPos(GetBlockInfo(dest_id)->pos)) {
+              // It's a premerge.
+              if (premerge_id != 0) {
+                // TODO(dneto): I think this is impossible to trigger at this
+                // point in the flow. It would require a merge instruction to
+                // get past the check of "at-most-one-forward-edge".
+                return Fail()
+                       << "invalid structure: then-clause headed by block "
+                       << true_head << " ending at block " << end_first_clause
+                       << " has two forward edges to within selection"
+                       << " going to " << premerge_id << " and " << dest_id;
+              }
+              premerge_id = dest_id;
+              auto* dest_block_info = GetBlockInfo(dest_id);
+              if_header_info->premerge_head = dest_id;
+              if (dest_block_info->header_for_merge != 0) {
+                // Premerge has two edges coming into it, from the then-clause
+                // and the else-clause. It's also, by construction, not the
+                // merge block of the if-selection.  So it must not be a merge
+                // block itself. The OpBranchConditional instruction for the
+                // false head block is an alternate path to the merge block, and
+                // hence the merge block is not dominated by its own (different)
+                // header.
+                return Fail()
+                       << "Block " << premerge_id << " is the merge block for "
+                       << dest_block_info->header_for_merge
+                       << " but has alternate paths reaching it, starting from"
+                       << " blocks " << true_head << " and " << false_head
+                       << " which are the true and false branches for the"
+                       << " if-selection header block " << construct->begin_id
+                       << " (violates dominance rule)";
+              }
+            }
+            break;
+          }
+          default:
+            break;
+        }
+      }
+      if (if_break_id != 0 && premerge_id != 0) {
+        return Fail() << "Block " << end_first_clause
+                      << " in if-selection headed at block "
+                      << construct->begin_id
+                      << " branches to both the merge block " << if_break_id
+                      << " and also to block " << premerge_id
+                      << " later in the selection";
+      }
+    }
+  }
+  return success();
+}
+
+bool FunctionEmitter::EmitFunctionVariables() {
+  if (failed()) {
+    return false;
+  }
+  for (auto& inst : *function_.entry()) {
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    auto* var_store_type = GetVariableStoreType(inst);
+    if (failed()) {
+      return false;
+    }
+    const ast::Expression* constructor = nullptr;
+    if (inst.NumInOperands() > 1) {
+      // SPIR-V initializers are always constants.
+      // (OpenCL also allows the ID of an OpVariable, but we don't handle that
+      // here.)
+      constructor =
+          parser_impl_.MakeConstantExpression(inst.GetSingleWordInOperand(1))
+              .expr;
+      if (!constructor) {
+        return false;
+      }
+    }
+    auto* var = parser_impl_.MakeVariable(
+        inst.result_id(), ast::StorageClass::kNone, var_store_type, false,
+        false, constructor, ast::AttributeList{});
+    auto* var_decl_stmt = create<ast::VariableDeclStatement>(Source{}, var);
+    AddStatement(var_decl_stmt);
+    auto* var_type = ty_.Reference(var_store_type, ast::StorageClass::kNone);
+    identifier_types_.emplace(inst.result_id(), var_type);
+  }
+  return success();
+}
+
+TypedExpression FunctionEmitter::AddressOfIfNeeded(
+    TypedExpression expr,
+    const spvtools::opt::Instruction* inst) {
+  if (inst && expr) {
+    if (auto* spirv_type = type_mgr_->GetType(inst->type_id())) {
+      if (expr.type->Is<Reference>() && spirv_type->AsPointer()) {
+        return AddressOf(expr);
+      }
+    }
+  }
+  return expr;
+}
+
+TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
+  if (failed()) {
+    return {};
+  }
+  switch (GetSkipReason(id)) {
+    case SkipReason::kDontSkip:
+      break;
+    case SkipReason::kOpaqueObject:
+      Fail() << "internal error: unhandled use of opaque object with ID: "
+             << id;
+      return {};
+    case SkipReason::kSinkPointerIntoUse: {
+      // Replace the pointer with its source reference expression.
+      auto source_expr = GetDefInfo(id)->sink_pointer_source_expr;
+      TINT_ASSERT(Reader, source_expr.type->Is<Reference>());
+      return source_expr;
+    }
+    case SkipReason::kPointSizeBuiltinValue: {
+      return {ty_.F32(), create<ast::FloatLiteralExpression>(Source{}, 1.0f)};
+    }
+    case SkipReason::kPointSizeBuiltinPointer:
+      Fail() << "unhandled use of a pointer to the PointSize builtin, with ID: "
+             << id;
+      return {};
+    case SkipReason::kSampleMaskInBuiltinPointer:
+      Fail()
+          << "unhandled use of a pointer to the SampleMask builtin, with ID: "
+          << id;
+      return {};
+    case SkipReason::kSampleMaskOutBuiltinPointer: {
+      // The result type is always u32.
+      auto name = namer_.Name(sample_mask_out_id);
+      return TypedExpression{ty_.U32(),
+                             create<ast::IdentifierExpression>(
+                                 Source{}, builder_.Symbols().Register(name))};
+    }
+  }
+  auto type_it = identifier_types_.find(id);
+  if (type_it != identifier_types_.end()) {
+    auto name = namer_.Name(id);
+    auto* type = type_it->second;
+    return TypedExpression{type,
+                           create<ast::IdentifierExpression>(
+                               Source{}, builder_.Symbols().Register(name))};
+  }
+  if (parser_impl_.IsScalarSpecConstant(id)) {
+    auto name = namer_.Name(id);
+    return TypedExpression{
+        parser_impl_.ConvertType(def_use_mgr_->GetDef(id)->type_id()),
+        create<ast::IdentifierExpression>(Source{},
+                                          builder_.Symbols().Register(name))};
+  }
+  if (singly_used_values_.count(id)) {
+    auto expr = std::move(singly_used_values_[id]);
+    singly_used_values_.erase(id);
+    return expr;
+  }
+  const auto* spirv_constant = constant_mgr_->FindDeclaredConstant(id);
+  if (spirv_constant) {
+    return parser_impl_.MakeConstantExpression(id);
+  }
+  const auto* inst = def_use_mgr_->GetDef(id);
+  if (inst == nullptr) {
+    Fail() << "ID " << id << " does not have a defining SPIR-V instruction";
+    return {};
+  }
+  switch (inst->opcode()) {
+    case SpvOpVariable: {
+      // This occurs for module-scope variables.
+      auto name = namer_.Name(inst->result_id());
+      return TypedExpression{
+          parser_impl_.ConvertType(inst->type_id(), PtrAs::Ref),
+          create<ast::IdentifierExpression>(Source{},
+                                            builder_.Symbols().Register(name))};
+    }
+    case SpvOpUndef:
+      // Substitute a null value for undef.
+      // This case occurs when OpUndef appears at module scope, as if it were
+      // a constant.
+      return parser_impl_.MakeNullExpression(
+          parser_impl_.ConvertType(inst->type_id()));
+
+    default:
+      break;
+  }
+  if (const spvtools::opt::BasicBlock* const bb =
+          ir_context_.get_instr_block(id)) {
+    if (auto* block = GetBlockInfo(bb->id())) {
+      if (block->pos == kInvalidBlockPos) {
+        // The value came from a block not in the block order.
+        // Substitute a null value.
+        return parser_impl_.MakeNullExpression(
+            parser_impl_.ConvertType(inst->type_id()));
+      }
+    }
+  }
+  Fail() << "unhandled expression for ID " << id << "\n" << inst->PrettyPrint();
+  return {};
+}
+
+bool FunctionEmitter::EmitFunctionBodyStatements() {
+  // Dump the basic blocks in order, grouped by construct.
+
+  // We maintain a stack of StatementBlock objects, where new statements
+  // are always written to the topmost entry of the stack. By this point in
+  // processing, we have already recorded the interesting control flow
+  // boundaries in the BlockInfo and associated Construct objects. As we
+  // enter a new statement grouping, we push onto the stack, and also schedule
+  // the statement block's completion and removal at a future block's ID.
+
+  // Upon entry, the statement stack has one entry representing the whole
+  // function.
+  TINT_ASSERT(Reader, !constructs_.empty());
+  Construct* function_construct = constructs_[0].get();
+  TINT_ASSERT(Reader, function_construct != nullptr);
+  TINT_ASSERT(Reader, function_construct->kind == Construct::kFunction);
+  // Make the first entry valid by filling in the construct field, which
+  // had not been computed at the time the entry was first created.
+  // TODO(dneto): refactor how the first construct is created vs.
+  // this statements stack entry is populated.
+  TINT_ASSERT(Reader, statements_stack_.size() == 1);
+  statements_stack_[0].SetConstruct(function_construct);
+
+  for (auto block_id : block_order()) {
+    if (!EmitBasicBlock(*GetBlockInfo(block_id))) {
+      return false;
+    }
+  }
+  return success();
+}
+
+bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
+  // Close off previous constructs.
+  while (!statements_stack_.empty() &&
+         (statements_stack_.back().GetEndId() == block_info.id)) {
+    statements_stack_.back().Finalize(&builder_);
+    statements_stack_.pop_back();
+  }
+  if (statements_stack_.empty()) {
+    return Fail() << "internal error: statements stack empty at block "
+                  << block_info.id;
+  }
+
+  // Enter new constructs.
+
+  std::vector<const Construct*> entering_constructs;  // inner most comes first
+  {
+    auto* here = block_info.construct;
+    auto* const top_construct = statements_stack_.back().GetConstruct();
+    while (here != top_construct) {
+      // Only enter a construct at its header block.
+      if (here->begin_id == block_info.id) {
+        entering_constructs.push_back(here);
+      }
+      here = here->parent;
+    }
+  }
+  // What constructs can we have entered?
+  // - It can't be kFunction, because there is only one of those, and it was
+  //   already on the stack at the outermost level.
+  // - We have at most one of kSwitchSelection, or kLoop because each of those
+  //   is headed by a block with a merge instruction (OpLoopMerge for kLoop,
+  //   and OpSelectionMerge for kSwitchSelection).
+  // - When there is a kIfSelection, it can't contain another construct,
+  //   because both would have to have their own distinct merge instructions
+  //   and distinct terminators.
+  // - A kContinue can contain a kContinue
+  //   This is possible in Vulkan SPIR-V, but Tint disallows this by the rule
+  //   that a block can be continue target for at most one header block. See
+  //   test DISABLED_BlockIsContinueForMoreThanOneHeader. If we generalize this,
+  //   then by a dominance argument, the inner loop continue target can only be
+  //   a single-block loop.
+  // TODO(dneto): Handle this case.
+  // - If a kLoop is on the outside, its terminator is either:
+  //   - an OpBranch, in which case there is no other construct.
+  //   - an OpBranchConditional, in which case there is either an kIfSelection
+  //     (when both branch targets are different and are inside the loop),
+  //     or no other construct (because the branch targets are the same,
+  //     or one of them is a break or continue).
+  // - All that's left is a kContinue on the outside, and one of
+  //   kIfSelection, kSwitchSelection, kLoop on the inside.
+  //
+  //   The kContinue can be the parent of the other.  For example, a selection
+  //   starting at the first block of a continue construct.
+  //
+  //   The kContinue can't be the child of the other because either:
+  //     - The other can't be kLoop because:
+  //        - If the kLoop is for a different loop then the kContinue, then
+  //          the kContinue must be its own loop header, and so the same
+  //          block is two different loops. That's a contradiction.
+  //        - If the kLoop is for a the same loop, then this is a contradiction
+  //          because a kContinue and its kLoop have disjoint block sets.
+  //     - The other construct can't be a selection because:
+  //       - The kContinue construct is the entire loop, i.e. the continue
+  //         target is its own loop header block.  But then the continue target
+  //         has an OpLoopMerge instruction, which contradicts this block being
+  //         a selection header.
+  //       - The kContinue is in a multi-block loop that is has a non-empty
+  //         kLoop; and the selection contains the kContinue block but not the
+  //         loop block. That breaks dominance rules. That is, the continue
+  //         target is dominated by that loop header, and so gets found by the
+  //         block traversal on the outside before the selection is found. The
+  //         selection is inside the outer loop.
+  //
+  // So we fall into one of the following cases:
+  //  - We are entering 0 or 1 constructs, or
+  //  - We are entering 2 constructs, with the outer one being a kContinue or
+  //    kLoop, the inner one is not a continue.
+  if (entering_constructs.size() > 2) {
+    return Fail() << "internal error: bad construct nesting found";
+  }
+  if (entering_constructs.size() == 2) {
+    auto inner_kind = entering_constructs[0]->kind;
+    auto outer_kind = entering_constructs[1]->kind;
+    if (outer_kind != Construct::kContinue && outer_kind != Construct::kLoop) {
+      return Fail()
+             << "internal error: bad construct nesting. Only a Continue "
+                "or a Loop construct can be outer construct on same block.  "
+                "Got outer kind "
+             << int(outer_kind) << " inner kind " << int(inner_kind);
+    }
+    if (inner_kind == Construct::kContinue) {
+      return Fail() << "internal error: unsupported construct nesting: "
+                       "Continue around Continue";
+    }
+    if (inner_kind != Construct::kIfSelection &&
+        inner_kind != Construct::kSwitchSelection &&
+        inner_kind != Construct::kLoop) {
+      return Fail() << "internal error: bad construct nesting. Continue around "
+                       "something other than if, switch, or loop";
+    }
+  }
+
+  // Enter constructs from outermost to innermost.
+  // kLoop and kContinue push a new statement-block onto the stack before
+  // emitting statements in the block.
+  // kIfSelection and kSwitchSelection emit statements in the block and then
+  // emit push a new statement-block. Only emit the statements in the block
+  // once.
+
+  // Have we emitted the statements for this block?
+  bool emitted = false;
+
+  // When entering an if-selection or switch-selection, we will emit the WGSL
+  // construct to cause the divergent branching.  But otherwise, we will
+  // emit a "normal" block terminator, which occurs at the end of this method.
+  bool has_normal_terminator = true;
+
+  for (auto iter = entering_constructs.rbegin();
+       iter != entering_constructs.rend(); ++iter) {
+    const Construct* construct = *iter;
+
+    switch (construct->kind) {
+      case Construct::kFunction:
+        return Fail() << "internal error: nested function construct";
+
+      case Construct::kLoop:
+        if (!EmitLoopStart(construct)) {
+          return false;
+        }
+        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+          return false;
+        }
+        break;
+
+      case Construct::kContinue:
+        if (block_info.is_continue_entire_loop) {
+          if (!EmitLoopStart(construct)) {
+            return false;
+          }
+          if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+            return false;
+          }
+        } else {
+          if (!EmitContinuingStart(construct)) {
+            return false;
+          }
+        }
+        break;
+
+      case Construct::kIfSelection:
+        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+          return false;
+        }
+        if (!EmitIfStart(block_info)) {
+          return false;
+        }
+        has_normal_terminator = false;
+        break;
+
+      case Construct::kSwitchSelection:
+        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+          return false;
+        }
+        if (!EmitSwitchStart(block_info)) {
+          return false;
+        }
+        has_normal_terminator = false;
+        break;
+    }
+  }
+
+  // If we aren't starting or transitioning, then emit the normal
+  // statements now.
+  if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+    return false;
+  }
+
+  if (has_normal_terminator) {
+    if (!EmitNormalTerminator(block_info)) {
+      return false;
+    }
+  }
+  return success();
+}
+
+bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
+  // The block is the if-header block.  So its construct is the if construct.
+  auto* construct = block_info.construct;
+  TINT_ASSERT(Reader, construct->kind == Construct::kIfSelection);
+  TINT_ASSERT(Reader, construct->begin_id == block_info.id);
+
+  const uint32_t true_head = block_info.true_head;
+  const uint32_t false_head = block_info.false_head;
+  const uint32_t premerge_head = block_info.premerge_head;
+
+  const std::string guard_name = block_info.flow_guard_name;
+  if (!guard_name.empty()) {
+    // Declare the guard variable just before the "if", initialized to true.
+    auto* guard_var =
+        builder_.Var(guard_name, builder_.ty.bool_(), MakeTrue(Source{}));
+    auto* guard_decl = create<ast::VariableDeclStatement>(Source{}, guard_var);
+    AddStatement(guard_decl);
+  }
+
+  const auto condition_id =
+      block_info.basic_block->terminator()->GetSingleWordInOperand(0);
+  auto* cond = MakeExpression(condition_id).expr;
+  if (!cond) {
+    return false;
+  }
+  // Generate the code for the condition.
+  auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
+
+  // Compute the block IDs that should end the then-clause and the else-clause.
+
+  // We need to know where the *emitted* selection should end, i.e. the intended
+  // merge block id.  That should be the current premerge block, if it exists,
+  // or otherwise the declared merge block.
+  //
+  // This is another way to think about it:
+  //   If there is a premerge, then there are three cases:
+  //    - premerge_head is different from the true_head and false_head:
+  //      - Premerge comes last. In effect, move the selection merge up
+  //        to where the premerge begins.
+  //    - premerge_head is the same as the false_head
+  //      - This is really an if-then without an else clause.
+  //        Move the merge up to where the premerge is.
+  //    - premerge_head is the same as the true_head
+  //      - This is really an if-else without an then clause.
+  //        Emit it as:   if (cond) {} else {....}
+  //        Move the merge up to where the premerge is.
+  const uint32_t intended_merge =
+      premerge_head ? premerge_head : construct->end_id;
+
+  // then-clause:
+  //   If true_head exists:
+  //     spans from true head to the earlier of the false head (if it exists)
+  //     or the selection merge.
+  //   Otherwise:
+  //     ends at from the false head (if it exists), otherwise the selection
+  //     end.
+  const uint32_t then_end = false_head ? false_head : intended_merge;
+
+  // else-clause:
+  //   ends at the premerge head (if it exists) or at the selection end.
+  const uint32_t else_end = premerge_head ? premerge_head : intended_merge;
+
+  const bool true_is_break = (block_info.true_kind == EdgeKind::kSwitchBreak) ||
+                             (block_info.true_kind == EdgeKind::kLoopBreak);
+  const bool false_is_break =
+      (block_info.false_kind == EdgeKind::kSwitchBreak) ||
+      (block_info.false_kind == EdgeKind::kLoopBreak);
+  const bool true_is_continue = block_info.true_kind == EdgeKind::kLoopContinue;
+  const bool false_is_continue =
+      block_info.false_kind == EdgeKind::kLoopContinue;
+
+  // Push statement blocks for the then-clause and the else-clause.
+  // But make sure we do it in the right order.
+  auto push_else = [this, builder, else_end, construct, false_is_break,
+                    false_is_continue]() {
+    // Push the else clause onto the stack first.
+    PushNewStatementBlock(
+        construct, else_end, [=](const ast::StatementList& stmts) {
+          // Only set the else-clause if there are statements to fill it.
+          if (!stmts.empty()) {
+            // The "else" consists of the statement list from the top of
+            // statements stack, without an elseif condition.
+            auto* else_body = create<ast::BlockStatement>(Source{}, stmts);
+            builder->else_stmts.emplace_back(
+                create<ast::ElseStatement>(Source{}, nullptr, else_body));
+          }
+        });
+    if (false_is_break) {
+      AddStatement(create<ast::BreakStatement>(Source{}));
+    }
+    if (false_is_continue) {
+      AddStatement(create<ast::ContinueStatement>(Source{}));
+    }
+  };
+
+  if (!true_is_break && !true_is_continue &&
+      (GetBlockInfo(else_end)->pos < GetBlockInfo(then_end)->pos)) {
+    // Process the else-clause first.  The then-clause will be empty so avoid
+    // pushing onto the stack at all.
+    push_else();
+  } else {
+    // Blocks for the then-clause appear before blocks for the else-clause.
+    // So push the else-clause handling onto the stack first. The else-clause
+    // might be empty, but this works anyway.
+
+    // Handle the premerge, if it exists.
+    if (premerge_head) {
+      // The top of the stack is the statement block that is the parent of the
+      // if-statement. Adding statements now will place them after that 'if'.
+      if (guard_name.empty()) {
+        // We won't have a flow guard for the premerge.
+        // Insert a trivial if(true) { ... } around the blocks from the
+        // premerge head until the end of the if-selection.  This is needed
+        // to ensure uniform reconvergence occurs at the end of the if-selection
+        // just like in the original SPIR-V.
+        PushTrueGuard(construct->end_id);
+      } else {
+        // Add a flow guard around the blocks in the premerge area.
+        PushGuard(guard_name, construct->end_id);
+      }
+    }
+
+    push_else();
+    if (true_head && false_head && !guard_name.empty()) {
+      // There are non-trivial then and else clauses.
+      // We have to guard the start of the else.
+      PushGuard(guard_name, else_end);
+    }
+
+    // Push the then clause onto the stack.
+    PushNewStatementBlock(
+        construct, then_end, [=](const ast::StatementList& stmts) {
+          builder->body = create<ast::BlockStatement>(Source{}, stmts);
+        });
+    if (true_is_break) {
+      AddStatement(create<ast::BreakStatement>(Source{}));
+    }
+    if (true_is_continue) {
+      AddStatement(create<ast::ContinueStatement>(Source{}));
+    }
+  }
+
+  return success();
+}
+
+bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
+  // The block is the if-header block.  So its construct is the if construct.
+  auto* construct = block_info.construct;
+  TINT_ASSERT(Reader, construct->kind == Construct::kSwitchSelection);
+  TINT_ASSERT(Reader, construct->begin_id == block_info.id);
+  const auto* branch = block_info.basic_block->terminator();
+
+  const auto selector_id = branch->GetSingleWordInOperand(0);
+  // Generate the code for the selector.
+  auto selector = MakeExpression(selector_id);
+  if (!selector) {
+    return false;
+  }
+  // First, push the statement block for the entire switch.
+  auto* swch = AddStatementBuilder<SwitchStatementBuilder>(selector.expr);
+
+  // Grab a pointer to the case list.  It will get buried in the statement block
+  // stack.
+  PushNewStatementBlock(construct, construct->end_id, nullptr);
+
+  // We will push statement-blocks onto the stack to gather the statements in
+  // the default clause and cases clauses. Determine the list of blocks
+  // that start each clause.
+  std::vector<const BlockInfo*> clause_heads;
+
+  // Collect the case clauses, even if they are just the merge block.
+  // First the default clause.
+  const auto default_id = branch->GetSingleWordInOperand(1);
+  const auto* default_info = GetBlockInfo(default_id);
+  clause_heads.push_back(default_info);
+  // Now the case clauses.
+  for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
+    const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
+    clause_heads.push_back(GetBlockInfo(case_target_id));
+  }
+
+  std::stable_sort(clause_heads.begin(), clause_heads.end(),
+                   [](const BlockInfo* lhs, const BlockInfo* rhs) {
+                     return lhs->pos < rhs->pos;
+                   });
+  // Remove duplicates
+  {
+    // Use read index r, and write index w.
+    // Invariant: w <= r;
+    size_t w = 0;
+    for (size_t r = 0; r < clause_heads.size(); ++r) {
+      if (clause_heads[r] != clause_heads[w]) {
+        ++w;  // Advance the write cursor.
+      }
+      clause_heads[w] = clause_heads[r];
+    }
+    // We know it's not empty because it always has at least a default clause.
+    TINT_ASSERT(Reader, !clause_heads.empty());
+    clause_heads.resize(w + 1);
+  }
+
+  // Push them on in reverse order.
+  const auto last_clause_index = clause_heads.size() - 1;
+  for (size_t i = last_clause_index;; --i) {
+    // Create a list of integer literals for the selector values leading to
+    // this case clause.
+    ast::CaseSelectorList selectors;
+    const auto* values_ptr = clause_heads[i]->case_values.get();
+    const bool has_selectors = (values_ptr && !values_ptr->empty());
+    if (has_selectors) {
+      std::vector<uint64_t> values(values_ptr->begin(), values_ptr->end());
+      std::stable_sort(values.begin(), values.end());
+      for (auto value : values) {
+        // The rest of this module can handle up to 64 bit switch values.
+        // The Tint AST handles 32-bit values.
+        const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
+        if (selector.type->IsUnsignedScalarOrVector()) {
+          selectors.emplace_back(
+              create<ast::UintLiteralExpression>(Source{}, value32));
+        } else {
+          selectors.emplace_back(
+              create<ast::SintLiteralExpression>(Source{}, value32));
+        }
+      }
+    }
+
+    // Where does this clause end?
+    const auto end_id = (i + 1 < clause_heads.size()) ? clause_heads[i + 1]->id
+                                                      : construct->end_id;
+
+    // Reserve the case clause slot in swch->cases, push the new statement block
+    // for the case, and fill the case clause once the block is generated.
+    auto case_idx = swch->cases.size();
+    swch->cases.emplace_back(nullptr);
+    PushNewStatementBlock(
+        construct, end_id, [=](const ast::StatementList& stmts) {
+          auto* body = create<ast::BlockStatement>(Source{}, stmts);
+          swch->cases[case_idx] =
+              create<ast::CaseStatement>(Source{}, selectors, body);
+        });
+
+    if ((default_info == clause_heads[i]) && has_selectors &&
+        construct->ContainsPos(default_info->pos)) {
+      // Generate a default clause with a just fallthrough.
+      auto* stmts = create<ast::BlockStatement>(
+          Source{}, ast::StatementList{
+                        create<ast::FallthroughStatement>(Source{}),
+                    });
+      auto* case_stmt =
+          create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{}, stmts);
+      swch->cases.emplace_back(case_stmt);
+    }
+
+    if (i == 0) {
+      break;
+    }
+  }
+
+  return success();
+}
+
+bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
+  auto* builder = AddStatementBuilder<LoopStatementBuilder>();
+  PushNewStatementBlock(
+      construct, construct->end_id, [=](const ast::StatementList& stmts) {
+        builder->body = create<ast::BlockStatement>(Source{}, stmts);
+      });
+  return success();
+}
+
+bool FunctionEmitter::EmitContinuingStart(const Construct* construct) {
+  // A continue construct has the same depth as its associated loop
+  // construct. Start a continue construct.
+  auto* loop_candidate = LastStatement();
+  auto* loop = loop_candidate->As<LoopStatementBuilder>();
+  if (loop == nullptr) {
+    return Fail() << "internal error: starting continue construct, "
+                     "expected loop on top of stack";
+  }
+  PushNewStatementBlock(
+      construct, construct->end_id, [=](const ast::StatementList& stmts) {
+        loop->continuing = create<ast::BlockStatement>(Source{}, stmts);
+      });
+
+  return success();
+}
+
+bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
+  const auto& terminator = *(block_info.basic_block->terminator());
+  switch (terminator.opcode()) {
+    case SpvOpReturn:
+      AddStatement(create<ast::ReturnStatement>(Source{}));
+      return true;
+    case SpvOpReturnValue: {
+      auto value = MakeExpression(terminator.GetSingleWordInOperand(0));
+      if (!value) {
+        return false;
+      }
+      AddStatement(create<ast::ReturnStatement>(Source{}, value.expr));
+    }
+      return true;
+    case SpvOpKill:
+      // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
+      // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
+      AddStatement(create<ast::DiscardStatement>(Source{}));
+      return true;
+    case SpvOpUnreachable:
+      // Translate as if it's a return. This avoids the problem where WGSL
+      // requires a return statement at the end of the function body.
+      {
+        const auto* result_type = type_mgr_->GetType(function_.type_id());
+        if (result_type->AsVoid() != nullptr) {
+          AddStatement(create<ast::ReturnStatement>(Source{}));
+        } else {
+          auto* ast_type = parser_impl_.ConvertType(function_.type_id());
+          AddStatement(create<ast::ReturnStatement>(
+              Source{}, parser_impl_.MakeNullValue(ast_type)));
+        }
+      }
+      return true;
+    case SpvOpBranch: {
+      const auto dest_id = terminator.GetSingleWordInOperand(0);
+      AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
+      return true;
+    }
+    case SpvOpBranchConditional: {
+      // If both destinations are the same, then do the same as we would
+      // for an unconditional branch (OpBranch).
+      const auto true_dest = terminator.GetSingleWordInOperand(1);
+      const auto false_dest = terminator.GetSingleWordInOperand(2);
+      if (true_dest == false_dest) {
+        // This is like an unconditional branch.
+        AddStatement(MakeBranch(block_info, *GetBlockInfo(true_dest)));
+        return true;
+      }
+
+      const EdgeKind true_kind = block_info.succ_edge.find(true_dest)->second;
+      const EdgeKind false_kind = block_info.succ_edge.find(false_dest)->second;
+      auto* const true_info = GetBlockInfo(true_dest);
+      auto* const false_info = GetBlockInfo(false_dest);
+      auto* cond = MakeExpression(terminator.GetSingleWordInOperand(0)).expr;
+      if (!cond) {
+        return false;
+      }
+
+      // We have two distinct destinations. But we only get here if this
+      // is a normal terminator; in particular the source block is *not* the
+      // start of an if-selection or a switch-selection.  So at most one branch
+      // is a kForward, kCaseFallThrough, or kIfBreak.
+
+      // The fallthrough case is special because WGSL requires the fallthrough
+      // statement to be last in the case clause.
+      if (true_kind == EdgeKind::kCaseFallThrough) {
+        return EmitConditionalCaseFallThrough(block_info, cond, false_kind,
+                                              *false_info, true);
+      } else if (false_kind == EdgeKind::kCaseFallThrough) {
+        return EmitConditionalCaseFallThrough(block_info, cond, true_kind,
+                                              *true_info, false);
+      }
+
+      // At this point, at most one edge is kForward or kIfBreak.
+
+      // Emit an 'if' statement to express the *other* branch as a conditional
+      // break or continue.  Either or both of these could be nullptr.
+      // (A nullptr is generated for kIfBreak, kForward, or kBack.)
+      // Also if one of the branches is an if-break out of an if-selection
+      // requiring a flow guard, then get that flow guard name too.  It will
+      // come from at most one of these two branches.
+      std::string flow_guard;
+      auto* true_branch =
+          MakeBranchDetailed(block_info, *true_info, false, &flow_guard);
+      auto* false_branch =
+          MakeBranchDetailed(block_info, *false_info, false, &flow_guard);
+
+      AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
+      if (!flow_guard.empty()) {
+        PushGuard(flow_guard, statements_stack_.back().GetEndId());
+      }
+      return true;
+    }
+    case SpvOpSwitch:
+      // An OpSelectionMerge must precede an OpSwitch.  That is clarified
+      // in the resolution to Khronos-internal SPIR-V issue 115.
+      // A new enough version of the SPIR-V validator checks this case.
+      // But issue an error in this case, as a defensive measure.
+      return Fail() << "invalid structured control flow: found an OpSwitch "
+                       "that is not preceded by an "
+                       "OpSelectionMerge: "
+                    << terminator.PrettyPrint();
+    default:
+      break;
+  }
+  return success();
+}
+
+const ast::Statement* FunctionEmitter::MakeBranchDetailed(
+    const BlockInfo& src_info,
+    const BlockInfo& dest_info,
+    bool forced,
+    std::string* flow_guard_name_ptr) const {
+  auto kind = src_info.succ_edge.find(dest_info.id)->second;
+  switch (kind) {
+    case EdgeKind::kBack:
+      // Nothing to do. The loop backedge is implicit.
+      break;
+    case EdgeKind::kSwitchBreak: {
+      if (forced) {
+        return create<ast::BreakStatement>(Source{});
+      }
+      // Unless forced, don't bother with a break at the end of a case/default
+      // clause.
+      const auto header = dest_info.header_for_merge;
+      TINT_ASSERT(Reader, header != 0);
+      const auto* exiting_construct = GetBlockInfo(header)->construct;
+      TINT_ASSERT(Reader,
+                  exiting_construct->kind == Construct::kSwitchSelection);
+      const auto candidate_next_case_pos = src_info.pos + 1;
+      // Leaving the last block from the last case?
+      if (candidate_next_case_pos == dest_info.pos) {
+        // No break needed.
+        return nullptr;
+      }
+      // Leaving the last block from not-the-last-case?
+      if (exiting_construct->ContainsPos(candidate_next_case_pos)) {
+        const auto* candidate_next_case =
+            GetBlockInfo(block_order_[candidate_next_case_pos]);
+        if (candidate_next_case->case_head_for == exiting_construct ||
+            candidate_next_case->default_head_for == exiting_construct) {
+          // No break needed.
+          return nullptr;
+        }
+      }
+      // We need a break.
+      return create<ast::BreakStatement>(Source{});
+    }
+    case EdgeKind::kLoopBreak:
+      return create<ast::BreakStatement>(Source{});
+    case EdgeKind::kLoopContinue:
+      // An unconditional continue to the next block is redundant and ugly.
+      // Skip it in that case.
+      if (dest_info.pos == 1 + src_info.pos) {
+        break;
+      }
+      // Otherwise, emit a regular continue statement.
+      return create<ast::ContinueStatement>(Source{});
+    case EdgeKind::kIfBreak: {
+      const auto& flow_guard =
+          GetBlockInfo(dest_info.header_for_merge)->flow_guard_name;
+      if (!flow_guard.empty()) {
+        if (flow_guard_name_ptr != nullptr) {
+          *flow_guard_name_ptr = flow_guard;
+        }
+        // Signal an exit from the branch.
+        return create<ast::AssignmentStatement>(
+            Source{},
+            create<ast::IdentifierExpression>(
+                Source{}, builder_.Symbols().Register(flow_guard)),
+            MakeFalse(Source{}));
+      }
+
+      // For an unconditional branch, the break out to an if-selection
+      // merge block is implicit.
+      break;
+    }
+    case EdgeKind::kCaseFallThrough:
+      return create<ast::FallthroughStatement>(Source{});
+    case EdgeKind::kForward:
+      // Unconditional forward branch is implicit.
+      break;
+  }
+  return nullptr;
+}
+
+const ast::Statement* FunctionEmitter::MakeSimpleIf(
+    const ast::Expression* condition,
+    const ast::Statement* then_stmt,
+    const ast::Statement* else_stmt) const {
+  if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
+    return nullptr;
+  }
+  ast::ElseStatementList else_stmts;
+  if (else_stmt != nullptr) {
+    ast::StatementList stmts{else_stmt};
+    else_stmts.emplace_back(create<ast::ElseStatement>(
+        Source{}, nullptr, create<ast::BlockStatement>(Source{}, stmts)));
+  }
+  ast::StatementList if_stmts;
+  if (then_stmt != nullptr) {
+    if_stmts.emplace_back(then_stmt);
+  }
+  auto* if_block = create<ast::BlockStatement>(Source{}, if_stmts);
+  auto* if_stmt =
+      create<ast::IfStatement>(Source{}, condition, if_block, else_stmts);
+
+  return if_stmt;
+}
+
+bool FunctionEmitter::EmitConditionalCaseFallThrough(
+    const BlockInfo& src_info,
+    const ast::Expression* cond,
+    EdgeKind other_edge_kind,
+    const BlockInfo& other_dest,
+    bool fall_through_is_true_branch) {
+  // In WGSL, the fallthrough statement must come last in the case clause.
+  // So we'll emit an if statement for the other branch, and then emit
+  // the fallthrough.
+
+  // We have two distinct destinations. But we only get here if this
+  // is a normal terminator; in particular the source block is *not* the
+  // start of an if-selection.  So at most one branch is a kForward or
+  // kCaseFallThrough.
+  if (other_edge_kind == EdgeKind::kForward) {
+    return Fail()
+           << "internal error: normal terminator OpBranchConditional has "
+              "both forward and fallthrough edges";
+  }
+  if (other_edge_kind == EdgeKind::kIfBreak) {
+    return Fail()
+           << "internal error: normal terminator OpBranchConditional has "
+              "both IfBreak and fallthrough edges.  Violates nesting rule";
+  }
+  if (other_edge_kind == EdgeKind::kBack) {
+    return Fail()
+           << "internal error: normal terminator OpBranchConditional has "
+              "both backedge and fallthrough edges.  Violates nesting rule";
+  }
+  auto* other_branch = MakeForcedBranch(src_info, other_dest);
+  if (other_branch == nullptr) {
+    return Fail() << "internal error: expected a branch for edge-kind "
+                  << int(other_edge_kind);
+  }
+  if (fall_through_is_true_branch) {
+    AddStatement(MakeSimpleIf(cond, nullptr, other_branch));
+  } else {
+    AddStatement(MakeSimpleIf(cond, other_branch, nullptr));
+  }
+  AddStatement(create<ast::FallthroughStatement>(Source{}));
+
+  return success();
+}
+
+bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
+                                                 bool* already_emitted) {
+  if (*already_emitted) {
+    // Only emit this part of the basic block once.
+    return true;
+  }
+  // Returns the given list of local definition IDs, sorted by their index.
+  auto sorted_by_index = [this](const std::vector<uint32_t>& ids) {
+    auto sorted = ids;
+    std::stable_sort(sorted.begin(), sorted.end(),
+                     [this](const uint32_t lhs, const uint32_t rhs) {
+                       return GetDefInfo(lhs)->index < GetDefInfo(rhs)->index;
+                     });
+    return sorted;
+  };
+
+  // Emit declarations of hoisted variables, in index order.
+  for (auto id : sorted_by_index(block_info.hoisted_ids)) {
+    const auto* def_inst = def_use_mgr_->GetDef(id);
+    TINT_ASSERT(Reader, def_inst);
+    auto* storage_type =
+        RemapStorageClass(parser_impl_.ConvertType(def_inst->type_id()), id);
+    AddStatement(create<ast::VariableDeclStatement>(
+        Source{}, parser_impl_.MakeVariable(id, ast::StorageClass::kNone,
+                                            storage_type, false, false, nullptr,
+                                            ast::AttributeList{})));
+    auto* type = ty_.Reference(storage_type, ast::StorageClass::kNone);
+    identifier_types_.emplace(id, type);
+  }
+  // Emit declarations of phi state variables, in index order.
+  for (auto id : sorted_by_index(block_info.phis_needing_state_vars)) {
+    const auto* def_inst = def_use_mgr_->GetDef(id);
+    TINT_ASSERT(Reader, def_inst);
+    const auto phi_var_name = GetDefInfo(id)->phi_var;
+    TINT_ASSERT(Reader, !phi_var_name.empty());
+    auto* var = builder_.Var(
+        phi_var_name,
+        parser_impl_.ConvertType(def_inst->type_id())->Build(builder_));
+    AddStatement(create<ast::VariableDeclStatement>(Source{}, var));
+  }
+
+  // Emit regular statements.
+  const spvtools::opt::BasicBlock& bb = *(block_info.basic_block);
+  const auto* terminator = bb.terminator();
+  const auto* merge = bb.GetMergeInst();  // Might be nullptr
+  for (auto& inst : bb) {
+    if (&inst == terminator || &inst == merge || inst.opcode() == SpvOpLabel ||
+        inst.opcode() == SpvOpVariable) {
+      continue;
+    }
+    if (!EmitStatement(inst)) {
+      return false;
+    }
+  }
+
+  // Emit assignments to carry values to phi nodes in potential destinations.
+  // Do it in index order.
+  if (!block_info.phi_assignments.empty()) {
+    auto sorted = block_info.phi_assignments;
+    std::stable_sort(sorted.begin(), sorted.end(),
+                     [this](const BlockInfo::PhiAssignment& lhs,
+                            const BlockInfo::PhiAssignment& rhs) {
+                       return GetDefInfo(lhs.phi_id)->index <
+                              GetDefInfo(rhs.phi_id)->index;
+                     });
+    for (auto assignment : block_info.phi_assignments) {
+      const auto var_name = GetDefInfo(assignment.phi_id)->phi_var;
+      auto expr = MakeExpression(assignment.value);
+      if (!expr) {
+        return false;
+      }
+      AddStatement(create<ast::AssignmentStatement>(
+          Source{},
+          create<ast::IdentifierExpression>(
+              Source{}, builder_.Symbols().Register(var_name)),
+          expr.expr));
+    }
+  }
+
+  *already_emitted = true;
+  return true;
+}
+
+bool FunctionEmitter::EmitConstDefinition(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression expr) {
+  if (!expr) {
+    return false;
+  }
+
+  // Do not generate pointers that we want to sink.
+  if (GetDefInfo(inst.result_id())->skip == SkipReason::kSinkPointerIntoUse) {
+    return true;
+  }
+
+  expr = AddressOfIfNeeded(expr, &inst);
+  auto* ast_const = parser_impl_.MakeVariable(
+      inst.result_id(), ast::StorageClass::kNone, expr.type, true, false,
+      expr.expr, ast::AttributeList{});
+  if (!ast_const) {
+    return false;
+  }
+  AddStatement(create<ast::VariableDeclStatement>(Source{}, ast_const));
+  identifier_types_.emplace(inst.result_id(), expr.type);
+  return success();
+}
+
+bool FunctionEmitter::EmitConstDefOrWriteToHoistedVar(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression expr) {
+  return WriteIfHoistedVar(inst, expr) || EmitConstDefinition(inst, expr);
+}
+
+bool FunctionEmitter::WriteIfHoistedVar(const spvtools::opt::Instruction& inst,
+                                        TypedExpression expr) {
+  const auto result_id = inst.result_id();
+  const auto* def_info = GetDefInfo(result_id);
+  if (def_info && def_info->requires_hoisted_def) {
+    auto name = namer_.Name(result_id);
+    // Emit an assignment of the expression to the hoisted variable.
+    AddStatement(create<ast::AssignmentStatement>(
+        Source{},
+        create<ast::IdentifierExpression>(Source{},
+                                          builder_.Symbols().Register(name)),
+        expr.expr));
+    return true;
+  }
+  return false;
+}
+
+bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
+  if (failed()) {
+    return false;
+  }
+  const auto result_id = inst.result_id();
+  const auto type_id = inst.type_id();
+
+  if (type_id != 0) {
+    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+    if (type_id == builtin_position_info.struct_type_id) {
+      return Fail() << "operations producing a per-vertex structure are not "
+                       "supported: "
+                    << inst.PrettyPrint();
+    }
+    if (type_id == builtin_position_info.pointer_type_id) {
+      return Fail() << "operations producing a pointer to a per-vertex "
+                       "structure are not "
+                       "supported: "
+                    << inst.PrettyPrint();
+    }
+  }
+
+  // Handle combinatorial instructions.
+  const auto* def_info = GetDefInfo(result_id);
+  if (def_info) {
+    TypedExpression combinatorial_expr;
+    if (def_info->skip == SkipReason::kDontSkip) {
+      combinatorial_expr = MaybeEmitCombinatorialValue(inst);
+      if (!success()) {
+        return false;
+      }
+    }
+    // An access chain or OpCopyObject can generate a skip.
+    if (def_info->skip != SkipReason::kDontSkip) {
+      return true;
+    }
+
+    if (combinatorial_expr.expr != nullptr) {
+      if (def_info->requires_hoisted_def ||
+          def_info->requires_named_const_def || def_info->num_uses != 1) {
+        // Generate a const definition or an assignment to a hoisted definition
+        // now and later use the const or variable name at the uses of this
+        // value.
+        return EmitConstDefOrWriteToHoistedVar(inst, combinatorial_expr);
+      }
+      // It is harmless to defer emitting the expression until it's used.
+      // Any supporting statements have already been emitted.
+      singly_used_values_.insert(std::make_pair(result_id, combinatorial_expr));
+      return success();
+    }
+  }
+  if (failed()) {
+    return false;
+  }
+
+  if (IsImageQuery(inst.opcode())) {
+    return EmitImageQuery(inst);
+  }
+
+  if (IsSampledImageAccess(inst.opcode()) || IsRawImageAccess(inst.opcode())) {
+    return EmitImageAccess(inst);
+  }
+
+  switch (inst.opcode()) {
+    case SpvOpNop:
+      return true;
+
+    case SpvOpStore: {
+      auto ptr_id = inst.GetSingleWordInOperand(0);
+      const auto value_id = inst.GetSingleWordInOperand(1);
+
+      const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id();
+      const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+      if (ptr_type_id == builtin_position_info.pointer_type_id) {
+        return Fail()
+               << "storing to the whole per-vertex structure is not supported: "
+               << inst.PrettyPrint();
+      }
+
+      TypedExpression rhs = MakeExpression(value_id);
+      if (!rhs) {
+        return false;
+      }
+
+      TypedExpression lhs;
+
+      // Handle exceptional cases
+      switch (GetSkipReason(ptr_id)) {
+        case SkipReason::kPointSizeBuiltinPointer:
+          if (IsFloatOne(value_id)) {
+            // Don't store to PointSize
+            return true;
+          }
+          return Fail() << "cannot store a value other than constant 1.0 to "
+                           "PointSize builtin: "
+                        << inst.PrettyPrint();
+
+        case SkipReason::kSampleMaskOutBuiltinPointer:
+          lhs = MakeExpression(sample_mask_out_id);
+          if (lhs.type->Is<Pointer>()) {
+            // LHS of an assignment must be a reference type.
+            // Convert the LHS to a reference by dereferencing it.
+            lhs = Dereference(lhs);
+          }
+          // The private variable is an array whose element type is already of
+          // the same type as the value being stored into it.  Form the
+          // reference into the first element.
+          lhs.expr = create<ast::IndexAccessorExpression>(
+              Source{}, lhs.expr, parser_impl_.MakeNullValue(ty_.I32()));
+          if (auto* ref = lhs.type->As<Reference>()) {
+            lhs.type = ref->type;
+          }
+          if (auto* arr = lhs.type->As<Array>()) {
+            lhs.type = arr->type;
+          }
+          TINT_ASSERT(Reader, lhs.type);
+          break;
+        default:
+          break;
+      }
+
+      // Handle an ordinary store as an assignment.
+      if (!lhs) {
+        lhs = MakeExpression(ptr_id);
+      }
+      if (!lhs) {
+        return false;
+      }
+
+      if (lhs.type->Is<Pointer>()) {
+        // LHS of an assignment must be a reference type.
+        // Convert the LHS to a reference by dereferencing it.
+        lhs = Dereference(lhs);
+      }
+
+      AddStatement(
+          create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr));
+      return success();
+    }
+
+    case SpvOpLoad: {
+      // Memory accesses must be issued in SPIR-V program order.
+      // So represent a load by a new const definition.
+      const auto ptr_id = inst.GetSingleWordInOperand(0);
+      const auto skip_reason = GetSkipReason(ptr_id);
+
+      switch (skip_reason) {
+        case SkipReason::kPointSizeBuiltinPointer:
+          GetDefInfo(inst.result_id())->skip =
+              SkipReason::kPointSizeBuiltinValue;
+          return true;
+        case SkipReason::kSampleMaskInBuiltinPointer: {
+          auto name = namer_.Name(sample_mask_in_id);
+          const ast::Expression* id_expr = create<ast::IdentifierExpression>(
+              Source{}, builder_.Symbols().Register(name));
+          // SampleMask is an array in Vulkan SPIR-V. Always access the first
+          // element.
+          id_expr = create<ast::IndexAccessorExpression>(
+              Source{}, id_expr, parser_impl_.MakeNullValue(ty_.I32()));
+
+          auto* loaded_type = parser_impl_.ConvertType(inst.type_id());
+
+          if (!loaded_type->IsIntegerScalar()) {
+            return Fail() << "loading the whole SampleMask input array is not "
+                             "supported: "
+                          << inst.PrettyPrint();
+          }
+
+          auto expr = TypedExpression{loaded_type, id_expr};
+          return EmitConstDefinition(inst, expr);
+        }
+        default:
+          break;
+      }
+      auto expr = MakeExpression(ptr_id);
+      if (!expr) {
+        return false;
+      }
+
+      // The load result type is the storage type of its operand.
+      if (expr.type->Is<Pointer>()) {
+        expr = Dereference(expr);
+      } else if (auto* ref = expr.type->As<Reference>()) {
+        expr.type = ref->type;
+      } else {
+        Fail() << "OpLoad expression is not a pointer or reference";
+        return false;
+      }
+
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
+
+    case SpvOpCopyMemory: {
+      // Generate an assignment.
+      auto lhs = MakeOperand(inst, 0);
+      auto rhs = MakeOperand(inst, 1);
+      // Ignore any potential memory operands. Currently they are all for
+      // concepts not in WGSL:
+      //   Volatile
+      //   Aligned
+      //   Nontemporal
+      //   MakePointerAvailable ; Vulkan memory model
+      //   MakePointerVisible   ; Vulkan memory model
+      //   NonPrivatePointer    ; Vulkan memory model
+
+      if (!success()) {
+        return false;
+      }
+
+      // LHS and RHS pointers must be reference types in WGSL.
+      if (lhs.type->Is<Pointer>()) {
+        lhs = Dereference(lhs);
+      }
+      if (rhs.type->Is<Pointer>()) {
+        rhs = Dereference(rhs);
+      }
+
+      AddStatement(
+          create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr));
+      return success();
+    }
+
+    case SpvOpCopyObject: {
+      // Arguably, OpCopyObject is purely combinatorial. On the other hand,
+      // it exists to make a new name for something. So we choose to make
+      // a new named constant definition.
+      auto value_id = inst.GetSingleWordInOperand(0);
+      const auto skip = GetSkipReason(value_id);
+      if (skip != SkipReason::kDontSkip) {
+        GetDefInfo(inst.result_id())->skip = skip;
+        GetDefInfo(inst.result_id())->sink_pointer_source_expr =
+            GetDefInfo(value_id)->sink_pointer_source_expr;
+        return true;
+      }
+      auto expr = AddressOfIfNeeded(MakeExpression(value_id), &inst);
+      if (!expr) {
+        return false;
+      }
+      expr.type = RemapStorageClass(expr.type, result_id);
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
+
+    case SpvOpPhi: {
+      // Emit a read from the associated state variable.
+      TypedExpression expr{
+          parser_impl_.ConvertType(inst.type_id()),
+          create<ast::IdentifierExpression>(
+              Source{}, builder_.Symbols().Register(def_info->phi_var))};
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
+
+    case SpvOpOuterProduct:
+      // Synthesize an outer product expression in its own statement.
+      return EmitConstDefOrWriteToHoistedVar(inst, MakeOuterProduct(inst));
+
+    case SpvOpVectorInsertDynamic:
+      // Synthesize a vector insertion in its own statements.
+      return MakeVectorInsertDynamic(inst);
+
+    case SpvOpCompositeInsert:
+      // Synthesize a composite insertion in its own statements.
+      return MakeCompositeInsert(inst);
+
+    case SpvOpFunctionCall:
+      return EmitFunctionCall(inst);
+
+    case SpvOpControlBarrier:
+      return EmitControlBarrier(inst);
+
+    case SpvOpExtInst:
+      if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
+        return true;
+      }
+      break;
+
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+      return Fail() << "extended arithmetic is not finalized for WGSL: "
+                       "https://github.com/gpuweb/gpuweb/issues/1565: "
+                    << inst.PrettyPrint();
+
+    default:
+      break;
+  }
+  return Fail() << "unhandled instruction with opcode " << inst.opcode() << ": "
+                << inst.PrettyPrint();
+}
+
+TypedExpression FunctionEmitter::MakeOperand(
+    const spvtools::opt::Instruction& inst,
+    uint32_t operand_index) {
+  auto expr = MakeExpression(inst.GetSingleWordInOperand(operand_index));
+  if (!expr) {
+    return {};
+  }
+  return parser_impl_.RectifyOperandSignedness(inst, std::move(expr));
+}
+
+TypedExpression FunctionEmitter::InferFunctionStorageClass(
+    TypedExpression expr) {
+  TypedExpression result(expr);
+  if (const auto* ref = expr.type->UnwrapAlias()->As<Reference>()) {
+    if (ref->storage_class == ast::StorageClass::kNone) {
+      expr.type = ty_.Reference(ref->type, ast::StorageClass::kFunction);
+    }
+  } else if (const auto* ptr = expr.type->UnwrapAlias()->As<Pointer>()) {
+    if (ptr->storage_class == ast::StorageClass::kNone) {
+      expr.type = ty_.Pointer(ptr->type, ast::StorageClass::kFunction);
+    }
+  }
+  return expr;
+}
+
+TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
+    const spvtools::opt::Instruction& inst) {
+  if (inst.result_id() == 0) {
+    return {};
+  }
+
+  const auto opcode = inst.opcode();
+
+  const Type* ast_type = nullptr;
+  if (inst.type_id()) {
+    ast_type = parser_impl_.ConvertType(inst.type_id());
+    if (!ast_type) {
+      Fail() << "couldn't convert result type for: " << inst.PrettyPrint();
+      return {};
+    }
+  }
+
+  auto binary_op = ConvertBinaryOp(opcode);
+  if (binary_op != ast::BinaryOp::kNone) {
+    auto arg0 = MakeOperand(inst, 0);
+    auto arg1 = parser_impl_.RectifySecondOperandSignedness(
+        inst, arg0.type, MakeOperand(inst, 1));
+    if (!arg0 || !arg1) {
+      return {};
+    }
+    auto* binary_expr = create<ast::BinaryExpression>(Source{}, binary_op,
+                                                      arg0.expr, arg1.expr);
+    TypedExpression result{ast_type, binary_expr};
+    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
+  }
+
+  auto unary_op = ast::UnaryOp::kNegation;
+  if (GetUnaryOp(opcode, &unary_op)) {
+    auto arg0 = MakeOperand(inst, 0);
+    auto* unary_expr =
+        create<ast::UnaryOpExpression>(Source{}, unary_op, arg0.expr);
+    TypedExpression result{ast_type, unary_expr};
+    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
+  }
+
+  const char* unary_builtin_name = GetUnaryBuiltInFunctionName(opcode);
+  if (unary_builtin_name != nullptr) {
+    ast::ExpressionList params;
+    params.emplace_back(MakeOperand(inst, 0).expr);
+    return {ast_type,
+            create<ast::CallExpression>(
+                Source{},
+                create<ast::IdentifierExpression>(
+                    Source{}, builder_.Symbols().Register(unary_builtin_name)),
+                std::move(params))};
+  }
+
+  const auto builtin = GetBuiltin(opcode);
+  if (builtin != sem::BuiltinType::kNone) {
+    return MakeBuiltinCall(inst);
+  }
+
+  if (opcode == SpvOpFMod) {
+    return MakeFMod(inst);
+  }
+
+  if (opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) {
+    return MakeAccessChain(inst);
+  }
+
+  if (opcode == SpvOpBitcast) {
+    return {ast_type,
+            create<ast::BitcastExpression>(Source{}, ast_type->Build(builder_),
+                                           MakeOperand(inst, 0).expr)};
+  }
+
+  if (opcode == SpvOpShiftLeftLogical || opcode == SpvOpShiftRightLogical ||
+      opcode == SpvOpShiftRightArithmetic) {
+    auto arg0 = MakeOperand(inst, 0);
+    // The second operand must be unsigned. It's ok to wrap the shift amount
+    // since the shift is modulo the bit width of the first operand.
+    auto arg1 = parser_impl_.AsUnsigned(MakeOperand(inst, 1));
+
+    switch (opcode) {
+      case SpvOpShiftLeftLogical:
+        binary_op = ast::BinaryOp::kShiftLeft;
+        break;
+      case SpvOpShiftRightLogical:
+        arg0 = parser_impl_.AsUnsigned(arg0);
+        binary_op = ast::BinaryOp::kShiftRight;
+        break;
+      case SpvOpShiftRightArithmetic:
+        arg0 = parser_impl_.AsSigned(arg0);
+        binary_op = ast::BinaryOp::kShiftRight;
+        break;
+      default:
+        break;
+    }
+    TypedExpression result{
+        ast_type, create<ast::BinaryExpression>(Source{}, binary_op, arg0.expr,
+                                                arg1.expr)};
+    return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
+  }
+
+  auto negated_op = NegatedFloatCompare(opcode);
+  if (negated_op != ast::BinaryOp::kNone) {
+    auto arg0 = MakeOperand(inst, 0);
+    auto arg1 = MakeOperand(inst, 1);
+    auto* binary_expr = create<ast::BinaryExpression>(Source{}, negated_op,
+                                                      arg0.expr, arg1.expr);
+    auto* negated_expr = create<ast::UnaryOpExpression>(
+        Source{}, ast::UnaryOp::kNot, binary_expr);
+    return {ast_type, negated_expr};
+  }
+
+  if (opcode == SpvOpExtInst) {
+    if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
+      // Ignore it but don't error out.
+      return {};
+    }
+    if (!parser_impl_.IsGlslExtendedInstruction(inst)) {
+      Fail() << "unhandled extended instruction import with ID "
+             << inst.GetSingleWordInOperand(0);
+      return {};
+    }
+    return EmitGlslStd450ExtInst(inst);
+  }
+
+  if (opcode == SpvOpCompositeConstruct) {
+    ast::ExpressionList operands;
+    for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
+      operands.emplace_back(MakeOperand(inst, iarg).expr);
+    }
+    return {ast_type, builder_.Construct(Source{}, ast_type->Build(builder_),
+                                         std::move(operands))};
+  }
+
+  if (opcode == SpvOpCompositeExtract) {
+    return MakeCompositeExtract(inst);
+  }
+
+  if (opcode == SpvOpVectorShuffle) {
+    return MakeVectorShuffle(inst);
+  }
+
+  if (opcode == SpvOpVectorExtractDynamic) {
+    return {ast_type, create<ast::IndexAccessorExpression>(
+                          Source{}, MakeOperand(inst, 0).expr,
+                          MakeOperand(inst, 1).expr)};
+  }
+
+  if (opcode == SpvOpConvertSToF || opcode == SpvOpConvertUToF ||
+      opcode == SpvOpConvertFToS || opcode == SpvOpConvertFToU) {
+    return MakeNumericConversion(inst);
+  }
+
+  if (opcode == SpvOpUndef) {
+    // Replace undef with the null value.
+    return parser_impl_.MakeNullExpression(ast_type);
+  }
+
+  if (opcode == SpvOpSelect) {
+    return MakeSimpleSelect(inst);
+  }
+
+  if (opcode == SpvOpArrayLength) {
+    return MakeArrayLength(inst);
+  }
+
+  // builtin readonly function
+  // glsl.std.450 readonly function
+
+  // Instructions:
+  //    OpSatConvertSToU // Only in Kernel (OpenCL), not in WebGPU
+  //    OpSatConvertUToS // Only in Kernel (OpenCL), not in WebGPU
+  //    OpUConvert // Only needed when multiple widths supported
+  //    OpSConvert // Only needed when multiple widths supported
+  //    OpFConvert // Only needed when multiple widths supported
+  //    OpConvertPtrToU // Not in WebGPU
+  //    OpConvertUToPtr // Not in WebGPU
+  //    OpPtrCastToGeneric // Not in Vulkan
+  //    OpGenericCastToPtr // Not in Vulkan
+  //    OpGenericCastToPtrExplicit // Not in Vulkan
+
+  return {};
+}
+
+TypedExpression FunctionEmitter::EmitGlslStd450ExtInst(
+    const spvtools::opt::Instruction& inst) {
+  const auto ext_opcode = inst.GetSingleWordInOperand(1);
+
+  if (ext_opcode == GLSLstd450Ldexp) {
+    // WGSL requires the second argument to be signed.
+    // Use a type constructor to convert it, which is the same as a bitcast.
+    // If the value would go from very large positive to negative, then the
+    // original result would have been infinity.  And since WGSL
+    // implementations may assume that infinities are not present, then we
+    // don't have to worry about that case.
+    auto e1 = MakeOperand(inst, 2);
+    auto e2 = ToSignedIfUnsigned(MakeOperand(inst, 3));
+
+    return {e1.type, builder_.Call(Source{}, "ldexp",
+                                   ast::ExpressionList{e1.expr, e2.expr})};
+  }
+
+  auto* result_type = parser_impl_.ConvertType(inst.type_id());
+
+  if (result_type->IsScalar()) {
+    // Some GLSLstd450 builtins have scalar forms not supported by WGSL.
+    // Emulate them.
+    switch (ext_opcode) {
+      case GLSLstd450Normalize:
+        // WGSL does not have scalar form of the normalize builtin.
+        // The answer would be 1 anyway, so return that directly.
+        return {ty_.F32(), builder_.Expr(1.0f)};
+
+      case GLSLstd450FaceForward: {
+        // If dot(Nref, Incident) < 0, the result is Normal, otherwise -Normal.
+        // Also: select(-normal,normal, Incident*Nref < 0)
+        // (The dot product of scalars is their product.)
+        // Use a multiply instead of comparing floating point signs. It should
+        // be among the fastest operations on a GPU.
+        auto normal = MakeOperand(inst, 2);
+        auto incident = MakeOperand(inst, 3);
+        auto nref = MakeOperand(inst, 4);
+        TINT_ASSERT(Reader, normal.type->Is<F32>());
+        TINT_ASSERT(Reader, incident.type->Is<F32>());
+        TINT_ASSERT(Reader, nref.type->Is<F32>());
+        return {ty_.F32(),
+                builder_.Call(
+                    Source{}, "select",
+                    ast::ExpressionList{
+                        create<ast::UnaryOpExpression>(
+                            Source{}, ast::UnaryOp::kNegation, normal.expr),
+                        normal.expr,
+                        create<ast::BinaryExpression>(
+                            Source{}, ast::BinaryOp::kLessThan,
+                            builder_.Mul({}, incident.expr, nref.expr),
+                            builder_.Expr(0.0f))})};
+      }
+
+      case GLSLstd450Reflect: {
+        // Compute  Incident - 2 * Normal * Normal * Incident
+        auto incident = MakeOperand(inst, 2);
+        auto normal = MakeOperand(inst, 3);
+        TINT_ASSERT(Reader, incident.type->Is<F32>());
+        TINT_ASSERT(Reader, normal.type->Is<F32>());
+        return {
+            ty_.F32(),
+            builder_.Sub(
+                incident.expr,
+                builder_.Mul(2.0f, builder_.Mul(normal.expr,
+                                                builder_.Mul(normal.expr,
+                                                             incident.expr))))};
+      }
+
+      case GLSLstd450Refract: {
+        // It's a complicated expression. Compute it in two dimensions, but
+        // with a 0-valued y component in both the incident and normal vectors,
+        // then take the x component of that result.
+        auto incident = MakeOperand(inst, 2);
+        auto normal = MakeOperand(inst, 3);
+        auto eta = MakeOperand(inst, 4);
+        TINT_ASSERT(Reader, incident.type->Is<F32>());
+        TINT_ASSERT(Reader, normal.type->Is<F32>());
+        TINT_ASSERT(Reader, eta.type->Is<F32>());
+        if (!success()) {
+          return {};
+        }
+        const Type* f32 = eta.type;
+        return {f32,
+                builder_.MemberAccessor(
+                    builder_.Call(
+                        Source{}, "refract",
+                        ast::ExpressionList{
+                            builder_.vec2<float>(incident.expr, 0.0f),
+                            builder_.vec2<float>(normal.expr, 0.0f), eta.expr}),
+                    "x")};
+      }
+      default:
+        break;
+    }
+  }
+
+  const auto name = GetGlslStd450FuncName(ext_opcode);
+  if (name.empty()) {
+    Fail() << "unhandled GLSL.std.450 instruction " << ext_opcode;
+    return {};
+  }
+
+  auto* func = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(name));
+  ast::ExpressionList operands;
+  const Type* first_operand_type = nullptr;
+  // All parameters to GLSL.std.450 extended instructions are IDs.
+  for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) {
+    TypedExpression operand = MakeOperand(inst, iarg);
+    if (first_operand_type == nullptr) {
+      first_operand_type = operand.type;
+    }
+    operands.emplace_back(operand.expr);
+  }
+  auto* call = create<ast::CallExpression>(Source{}, func, std::move(operands));
+  TypedExpression call_expr{result_type, call};
+  return parser_impl_.RectifyForcedResultType(call_expr, inst,
+                                              first_operand_type);
+}
+
+ast::IdentifierExpression* FunctionEmitter::Swizzle(uint32_t i) {
+  if (i >= kMaxVectorLen) {
+    Fail() << "vector component index is larger than " << kMaxVectorLen - 1
+           << ": " << i;
+    return nullptr;
+  }
+  const char* names[] = {"x", "y", "z", "w"};
+  return create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(names[i & 3]));
+}
+
+ast::IdentifierExpression* FunctionEmitter::PrefixSwizzle(uint32_t n) {
+  switch (n) {
+    case 1:
+      return create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register("x"));
+    case 2:
+      return create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register("xy"));
+    case 3:
+      return create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register("xyz"));
+    default:
+      break;
+  }
+  Fail() << "invalid swizzle prefix count: " << n;
+  return nullptr;
+}
+
+TypedExpression FunctionEmitter::MakeFMod(
+    const spvtools::opt::Instruction& inst) {
+  auto x = MakeOperand(inst, 0);
+  auto y = MakeOperand(inst, 1);
+  if (!x || !y) {
+    return {};
+  }
+  // Emulated with: x - y * floor(x / y)
+  auto* div = builder_.Div(x.expr, y.expr);
+  auto* floor = builder_.Call("floor", div);
+  auto* y_floor = builder_.Mul(y.expr, floor);
+  auto* res = builder_.Sub(x.expr, y_floor);
+  return {x.type, res};
+}
+
+TypedExpression FunctionEmitter::MakeAccessChain(
+    const spvtools::opt::Instruction& inst) {
+  if (inst.NumInOperands() < 1) {
+    // Binary parsing will fail on this anyway.
+    Fail() << "invalid access chain: has no input operands";
+    return {};
+  }
+
+  const auto base_id = inst.GetSingleWordInOperand(0);
+  const auto base_skip = GetSkipReason(base_id);
+  if (base_skip != SkipReason::kDontSkip) {
+    // This can occur for AccessChain with no indices.
+    GetDefInfo(inst.result_id())->skip = base_skip;
+    GetDefInfo(inst.result_id())->sink_pointer_source_expr =
+        GetDefInfo(base_id)->sink_pointer_source_expr;
+    return {};
+  }
+
+  auto ptr_ty_id = def_use_mgr_->GetDef(base_id)->type_id();
+  uint32_t first_index = 1;
+  const auto num_in_operands = inst.NumInOperands();
+
+  bool sink_pointer = false;
+  TypedExpression current_expr;
+
+  // If the variable was originally gl_PerVertex, then in the AST we
+  // have instead emitted a gl_Position variable.
+  // If computing the pointer to the Position builtin, then emit the
+  // pointer to the generated gl_Position variable.
+  // If computing the pointer to the PointSize builtin, then mark the
+  // result as skippable due to being the point-size pointer.
+  // If computing the pointer to the ClipDistance or CullDistance builtins,
+  // then error out.
+  {
+    const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+    if (base_id == builtin_position_info.per_vertex_var_id) {
+      // We only support the Position member.
+      const auto* member_index_inst =
+          def_use_mgr_->GetDef(inst.GetSingleWordInOperand(first_index));
+      if (member_index_inst == nullptr) {
+        Fail()
+            << "first index of access chain does not reference an instruction: "
+            << inst.PrettyPrint();
+        return {};
+      }
+      const auto* member_index_const =
+          constant_mgr_->GetConstantFromInst(member_index_inst);
+      if (member_index_const == nullptr) {
+        Fail() << "first index of access chain into per-vertex structure is "
+                  "not a constant: "
+               << inst.PrettyPrint();
+        return {};
+      }
+      const auto* member_index_const_int = member_index_const->AsIntConstant();
+      if (member_index_const_int == nullptr) {
+        Fail() << "first index of access chain into per-vertex structure is "
+                  "not a constant integer: "
+               << inst.PrettyPrint();
+        return {};
+      }
+      const auto member_index_value =
+          member_index_const_int->GetZeroExtendedValue();
+      if (member_index_value != builtin_position_info.position_member_index) {
+        if (member_index_value ==
+            builtin_position_info.pointsize_member_index) {
+          if (auto* def_info = GetDefInfo(inst.result_id())) {
+            def_info->skip = SkipReason::kPointSizeBuiltinPointer;
+            return {};
+          }
+        } else {
+          // TODO(dneto): Handle ClipDistance and CullDistance
+          Fail() << "accessing per-vertex member " << member_index_value
+                 << " is not supported. Only Position is supported, and "
+                    "PointSize is ignored";
+          return {};
+        }
+      }
+
+      // Skip past the member index that gets us to Position.
+      first_index = first_index + 1;
+      // Replace the gl_PerVertex reference with the gl_Position reference
+      ptr_ty_id = builtin_position_info.position_member_pointer_type_id;
+
+      auto name = namer_.Name(base_id);
+      current_expr.expr = create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register(name));
+      current_expr.type = parser_impl_.ConvertType(ptr_ty_id, PtrAs::Ref);
+    }
+  }
+
+  // A SPIR-V access chain is a single instruction with multiple indices
+  // walking down into composites.  The Tint AST represents this as
+  // ever-deeper nested indexing expressions. Start off with an expression
+  // for the base, and then bury that inside nested indexing expressions.
+  if (!current_expr) {
+    current_expr = InferFunctionStorageClass(MakeOperand(inst, 0));
+    if (current_expr.type->Is<Pointer>()) {
+      current_expr = Dereference(current_expr);
+    }
+  }
+  const auto constants = constant_mgr_->GetOperandConstants(&inst);
+
+  const auto* ptr_type_inst = def_use_mgr_->GetDef(ptr_ty_id);
+  if (!ptr_type_inst || (ptr_type_inst->opcode() != SpvOpTypePointer)) {
+    Fail() << "Access chain %" << inst.result_id()
+           << " base pointer is not of pointer type";
+    return {};
+  }
+  SpvStorageClass storage_class =
+      static_cast<SpvStorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
+  uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+
+  // Build up a nested expression for the access chain by walking down the type
+  // hierarchy, maintaining |pointee_type_id| as the SPIR-V ID of the type of
+  // the object pointed to after processing the previous indices.
+  for (uint32_t index = first_index; index < num_in_operands; ++index) {
+    const auto* index_const =
+        constants[index] ? constants[index]->AsIntConstant() : nullptr;
+    const int64_t index_const_val =
+        index_const ? index_const->GetSignExtendedValue() : 0;
+    const ast::Expression* next_expr = nullptr;
+
+    const auto* pointee_type_inst = def_use_mgr_->GetDef(pointee_type_id);
+    if (!pointee_type_inst) {
+      Fail() << "pointee type %" << pointee_type_id
+             << " is invalid after following " << (index - first_index)
+             << " indices: " << inst.PrettyPrint();
+      return {};
+    }
+    switch (pointee_type_inst->opcode()) {
+      case SpvOpTypeVector:
+        if (index_const) {
+          // Try generating a MemberAccessor expression
+          const auto num_elems = pointee_type_inst->GetSingleWordInOperand(1);
+          if (index_const_val < 0 || num_elems <= index_const_val) {
+            Fail() << "Access chain %" << inst.result_id() << " index %"
+                   << inst.GetSingleWordInOperand(index) << " value "
+                   << index_const_val << " is out of bounds for vector of "
+                   << num_elems << " elements";
+            return {};
+          }
+          if (uint64_t(index_const_val) >= kMaxVectorLen) {
+            Fail() << "internal error: swizzle index " << index_const_val
+                   << " is too big. Max handled index is " << kMaxVectorLen - 1;
+          }
+          next_expr = create<ast::MemberAccessorExpression>(
+              Source{}, current_expr.expr, Swizzle(uint32_t(index_const_val)));
+        } else {
+          // Non-constant index. Use array syntax
+          next_expr = create<ast::IndexAccessorExpression>(
+              Source{}, current_expr.expr, MakeOperand(inst, index).expr);
+        }
+        // All vector components are the same type.
+        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
+        // Sink pointers to vector components.
+        sink_pointer = true;
+        break;
+      case SpvOpTypeMatrix:
+        // Use array syntax.
+        next_expr = create<ast::IndexAccessorExpression>(
+            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
+        // All matrix components are the same type.
+        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeArray:
+        next_expr = create<ast::IndexAccessorExpression>(
+            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
+        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeRuntimeArray:
+        next_expr = create<ast::IndexAccessorExpression>(
+            Source{}, current_expr.expr, MakeOperand(inst, index).expr);
+        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeStruct: {
+        if (!index_const) {
+          Fail() << "Access chain %" << inst.result_id() << " index %"
+                 << inst.GetSingleWordInOperand(index)
+                 << " is a non-constant index into a structure %"
+                 << pointee_type_id;
+          return {};
+        }
+        const auto num_members = pointee_type_inst->NumInOperands();
+        if ((index_const_val < 0) || num_members <= uint64_t(index_const_val)) {
+          Fail() << "Access chain %" << inst.result_id() << " index value "
+                 << index_const_val << " is out of bounds for structure %"
+                 << pointee_type_id << " having " << num_members << " members";
+          return {};
+        }
+        auto name =
+            namer_.GetMemberName(pointee_type_id, uint32_t(index_const_val));
+        auto* member_access = create<ast::IdentifierExpression>(
+            Source{}, builder_.Symbols().Register(name));
+
+        next_expr = create<ast::MemberAccessorExpression>(
+            Source{}, current_expr.expr, member_access);
+        pointee_type_id = pointee_type_inst->GetSingleWordInOperand(
+            static_cast<uint32_t>(index_const_val));
+        break;
+      }
+      default:
+        Fail() << "Access chain with unknown or invalid pointee type %"
+               << pointee_type_id << ": " << pointee_type_inst->PrettyPrint();
+        return {};
+    }
+    const auto pointer_type_id =
+        type_mgr_->FindPointerToType(pointee_type_id, storage_class);
+    auto* type = parser_impl_.ConvertType(pointer_type_id, PtrAs::Ref);
+    TINT_ASSERT(Reader, type && type->Is<Reference>());
+    current_expr = TypedExpression{type, next_expr};
+  }
+
+  if (sink_pointer) {
+    // Capture the reference so that we can sink it into the point of use.
+    GetDefInfo(inst.result_id())->skip = SkipReason::kSinkPointerIntoUse;
+    GetDefInfo(inst.result_id())->sink_pointer_source_expr = current_expr;
+  }
+
+  return current_expr;
+}
+
+TypedExpression FunctionEmitter::MakeCompositeExtract(
+    const spvtools::opt::Instruction& inst) {
+  // This is structurally similar to creating an access chain, but
+  // the SPIR-V instruction has literal indices instead of IDs for indices.
+
+  auto composite_index = 0;
+  auto first_index_position = 1;
+  TypedExpression current_expr(MakeOperand(inst, composite_index));
+  if (!current_expr) {
+    return {};
+  }
+
+  const auto composite_id = inst.GetSingleWordInOperand(composite_index);
+  auto current_type_id = def_use_mgr_->GetDef(composite_id)->type_id();
+
+  return MakeCompositeValueDecomposition(inst, current_expr, current_type_id,
+                                         first_index_position);
+}
+
+TypedExpression FunctionEmitter::MakeCompositeValueDecomposition(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression composite,
+    uint32_t composite_type_id,
+    int index_start) {
+  // This is structurally similar to creating an access chain, but
+  // the SPIR-V instruction has literal indices instead of IDs for indices.
+
+  // A SPIR-V composite extract is a single instruction with multiple
+  // literal indices walking down into composites.
+  // A SPIR-V composite insert is similar but also tells you what component
+  // to inject. This function is responsible for the the walking-into part
+  // of composite-insert.
+  //
+  // The Tint AST represents this as ever-deeper nested indexing expressions.
+  // Start off with an expression for the composite, and then bury that inside
+  // nested indexing expressions.
+
+  auto current_expr = composite;
+  auto current_type_id = composite_type_id;
+
+  auto make_index = [this](uint32_t literal) {
+    return create<ast::UintLiteralExpression>(Source{}, literal);
+  };
+
+  // Build up a nested expression for the decomposition by walking down the type
+  // hierarchy, maintaining |current_type_id| as the SPIR-V ID of the type of
+  // the object pointed to after processing the previous indices.
+  const auto num_in_operands = inst.NumInOperands();
+  for (uint32_t index = index_start; index < num_in_operands; ++index) {
+    const uint32_t index_val = inst.GetSingleWordInOperand(index);
+
+    const auto* current_type_inst = def_use_mgr_->GetDef(current_type_id);
+    if (!current_type_inst) {
+      Fail() << "composite type %" << current_type_id
+             << " is invalid after following " << (index - index_start)
+             << " indices: " << inst.PrettyPrint();
+      return {};
+    }
+    const char* operation_name = nullptr;
+    switch (inst.opcode()) {
+      case SpvOpCompositeExtract:
+        operation_name = "OpCompositeExtract";
+        break;
+      case SpvOpCompositeInsert:
+        operation_name = "OpCompositeInsert";
+        break;
+      default:
+        Fail() << "internal error: unhandled " << inst.PrettyPrint();
+        return {};
+    }
+    const ast::Expression* next_expr = nullptr;
+    switch (current_type_inst->opcode()) {
+      case SpvOpTypeVector: {
+        // Try generating a MemberAccessor expression. That result in something
+        // like  "foo.z", which is more idiomatic than "foo[2]".
+        const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
+        if (num_elems <= index_val) {
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for vector of " << num_elems
+                 << " elements";
+          return {};
+        }
+        if (index_val >= kMaxVectorLen) {
+          Fail() << "internal error: swizzle index " << index_val
+                 << " is too big. Max handled index is " << kMaxVectorLen - 1;
+          return {};
+        }
+        next_expr = create<ast::MemberAccessorExpression>(
+            Source{}, current_expr.expr, Swizzle(index_val));
+        // All vector components are the same type.
+        current_type_id = current_type_inst->GetSingleWordInOperand(0);
+        break;
+      }
+      case SpvOpTypeMatrix: {
+        // Check bounds
+        const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
+        if (num_elems <= index_val) {
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for matrix of " << num_elems
+                 << " elements";
+          return {};
+        }
+        if (index_val >= kMaxVectorLen) {
+          Fail() << "internal error: swizzle index " << index_val
+                 << " is too big. Max handled index is " << kMaxVectorLen - 1;
+        }
+        // Use array syntax.
+        next_expr = create<ast::IndexAccessorExpression>(
+            Source{}, current_expr.expr, make_index(index_val));
+        // All matrix components are the same type.
+        current_type_id = current_type_inst->GetSingleWordInOperand(0);
+        break;
+      }
+      case SpvOpTypeArray:
+        // The array size could be a spec constant, and so it's not always
+        // statically checkable.  Instead, rely on a runtime index clamp
+        // or runtime check to keep this safe.
+        next_expr = create<ast::IndexAccessorExpression>(
+            Source{}, current_expr.expr, make_index(index_val));
+        current_type_id = current_type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeRuntimeArray:
+        Fail() << "can't do " << operation_name
+               << " on a runtime array: " << inst.PrettyPrint();
+        return {};
+      case SpvOpTypeStruct: {
+        const auto num_members = current_type_inst->NumInOperands();
+        if (num_members <= index_val) {
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for structure %" << current_type_id
+                 << " having " << num_members << " members";
+          return {};
+        }
+        auto name = namer_.GetMemberName(current_type_id, uint32_t(index_val));
+        auto* member_access = create<ast::IdentifierExpression>(
+            Source{}, builder_.Symbols().Register(name));
+
+        next_expr = create<ast::MemberAccessorExpression>(
+            Source{}, current_expr.expr, member_access);
+        current_type_id = current_type_inst->GetSingleWordInOperand(index_val);
+        break;
+      }
+      default:
+        Fail() << operation_name << " with bad type %" << current_type_id
+               << ": " << current_type_inst->PrettyPrint();
+        return {};
+    }
+    current_expr =
+        TypedExpression{parser_impl_.ConvertType(current_type_id), next_expr};
+  }
+  return current_expr;
+}
+
+const ast::Expression* FunctionEmitter::MakeTrue(const Source& source) const {
+  return create<ast::BoolLiteralExpression>(source, true);
+}
+
+const ast::Expression* FunctionEmitter::MakeFalse(const Source& source) const {
+  return create<ast::BoolLiteralExpression>(source, false);
+}
+
+TypedExpression FunctionEmitter::MakeVectorShuffle(
+    const spvtools::opt::Instruction& inst) {
+  const auto vec0_id = inst.GetSingleWordInOperand(0);
+  const auto vec1_id = inst.GetSingleWordInOperand(1);
+  const spvtools::opt::Instruction& vec0 = *(def_use_mgr_->GetDef(vec0_id));
+  const spvtools::opt::Instruction& vec1 = *(def_use_mgr_->GetDef(vec1_id));
+  const auto vec0_len =
+      type_mgr_->GetType(vec0.type_id())->AsVector()->element_count();
+  const auto vec1_len =
+      type_mgr_->GetType(vec1.type_id())->AsVector()->element_count();
+
+  // Idiomatic vector accessors.
+
+  // Generate an ast::TypeConstructor expression.
+  // Assume the literal indices are valid, and there is a valid number of them.
+  auto source = GetSourceForInst(inst);
+  const Vector* result_type =
+      As<Vector>(parser_impl_.ConvertType(inst.type_id()));
+  ast::ExpressionList values;
+  for (uint32_t i = 2; i < inst.NumInOperands(); ++i) {
+    const auto index = inst.GetSingleWordInOperand(i);
+    if (index < vec0_len) {
+      auto expr = MakeExpression(vec0_id);
+      if (!expr) {
+        return {};
+      }
+      values.emplace_back(create<ast::MemberAccessorExpression>(
+          source, expr.expr, Swizzle(index)));
+    } else if (index < vec0_len + vec1_len) {
+      const auto sub_index = index - vec0_len;
+      TINT_ASSERT(Reader, sub_index < kMaxVectorLen);
+      auto expr = MakeExpression(vec1_id);
+      if (!expr) {
+        return {};
+      }
+      values.emplace_back(create<ast::MemberAccessorExpression>(
+          source, expr.expr, Swizzle(sub_index)));
+    } else if (index == 0xFFFFFFFF) {
+      // By rule, this maps to OpUndef.  Instead, make it zero.
+      values.emplace_back(parser_impl_.MakeNullValue(result_type->type));
+    } else {
+      Fail() << "invalid vectorshuffle ID %" << inst.result_id()
+             << ": index too large: " << index;
+      return {};
+    }
+  }
+  return {result_type,
+          builder_.Construct(source, result_type->Build(builder_), values)};
+}
+
+bool FunctionEmitter::RegisterSpecialBuiltInVariables() {
+  size_t index = def_info_.size();
+  for (auto& special_var : parser_impl_.special_builtins()) {
+    const auto id = special_var.first;
+    const auto builtin = special_var.second;
+    const auto* var = def_use_mgr_->GetDef(id);
+    def_info_[id] = std::make_unique<DefInfo>(*var, 0, index);
+    ++index;
+    auto& def = def_info_[id];
+    switch (builtin) {
+      case SpvBuiltInPointSize:
+        def->skip = SkipReason::kPointSizeBuiltinPointer;
+        break;
+      case SpvBuiltInSampleMask: {
+        // Distinguish between input and output variable.
+        const auto storage_class =
+            static_cast<SpvStorageClass>(var->GetSingleWordInOperand(0));
+        if (storage_class == SpvStorageClassInput) {
+          sample_mask_in_id = id;
+          def->skip = SkipReason::kSampleMaskInBuiltinPointer;
+        } else {
+          sample_mask_out_id = id;
+          def->skip = SkipReason::kSampleMaskOutBuiltinPointer;
+        }
+        break;
+      }
+      case SpvBuiltInSampleId:
+      case SpvBuiltInInstanceIndex:
+      case SpvBuiltInVertexIndex:
+      case SpvBuiltInLocalInvocationIndex:
+      case SpvBuiltInLocalInvocationId:
+      case SpvBuiltInGlobalInvocationId:
+      case SpvBuiltInWorkgroupId:
+      case SpvBuiltInNumWorkgroups:
+        break;
+      default:
+        return Fail() << "unrecognized special builtin: " << int(builtin);
+    }
+  }
+  return true;
+}
+
+bool FunctionEmitter::RegisterLocallyDefinedValues() {
+  // Create a DefInfo for each value definition in this function.
+  size_t index = def_info_.size();
+  for (auto block_id : block_order_) {
+    const auto* block_info = GetBlockInfo(block_id);
+    const auto block_pos = block_info->pos;
+    for (const auto& inst : *(block_info->basic_block)) {
+      const auto result_id = inst.result_id();
+      if ((result_id == 0) || inst.opcode() == SpvOpLabel) {
+        continue;
+      }
+      def_info_[result_id] = std::make_unique<DefInfo>(inst, block_pos, index);
+      ++index;
+      auto& info = def_info_[result_id];
+
+      // Determine storage class for pointer values. Do this in order because
+      // we might rely on the storage class for a previously-visited definition.
+      // Logical pointers can't be transmitted through OpPhi, so remaining
+      // pointer definitions are SSA values, and their definitions must be
+      // visited before their uses.
+      const auto* type = type_mgr_->GetType(inst.type_id());
+      if (type) {
+        if (type->AsPointer()) {
+          if (auto* ast_type = parser_impl_.ConvertType(inst.type_id())) {
+            if (auto* ptr = ast_type->As<Pointer>()) {
+              info->storage_class = ptr->storage_class;
+            }
+          }
+          switch (inst.opcode()) {
+            case SpvOpUndef:
+              return Fail()
+                     << "undef pointer is not valid: " << inst.PrettyPrint();
+            case SpvOpVariable:
+              // Keep the default decision based on the result type.
+              break;
+            case SpvOpAccessChain:
+            case SpvOpInBoundsAccessChain:
+            case SpvOpCopyObject:
+              // Inherit from the first operand. We need this so we can pick up
+              // a remapped storage buffer.
+              info->storage_class = GetStorageClassForPointerValue(
+                  inst.GetSingleWordInOperand(0));
+              break;
+            default:
+              return Fail()
+                     << "pointer defined in function from unknown opcode: "
+                     << inst.PrettyPrint();
+          }
+        }
+        auto* unwrapped = type;
+        while (auto* ptr = unwrapped->AsPointer()) {
+          unwrapped = ptr->pointee_type();
+        }
+        if (unwrapped->AsSampler() || unwrapped->AsImage() ||
+            unwrapped->AsSampledImage()) {
+          // Defer code generation until the instruction that actually acts on
+          // the image.
+          info->skip = SkipReason::kOpaqueObject;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+ast::StorageClass FunctionEmitter::GetStorageClassForPointerValue(uint32_t id) {
+  auto where = def_info_.find(id);
+  if (where != def_info_.end()) {
+    auto candidate = where->second.get()->storage_class;
+    if (candidate != ast::StorageClass::kInvalid) {
+      return candidate;
+    }
+  }
+  const auto type_id = def_use_mgr_->GetDef(id)->type_id();
+  if (type_id) {
+    auto* ast_type = parser_impl_.ConvertType(type_id);
+    if (auto* ptr = As<Pointer>(ast_type)) {
+      return ptr->storage_class;
+    }
+  }
+  return ast::StorageClass::kInvalid;
+}
+
+const Type* FunctionEmitter::RemapStorageClass(const Type* type,
+                                               uint32_t result_id) {
+  if (auto* ast_ptr_type = As<Pointer>(type)) {
+    // Remap an old-style storage buffer pointer to a new-style storage
+    // buffer pointer.
+    const auto sc = GetStorageClassForPointerValue(result_id);
+    if (ast_ptr_type->storage_class != sc) {
+      return ty_.Pointer(ast_ptr_type->type, sc);
+    }
+  }
+  return type;
+}
+
+void FunctionEmitter::FindValuesNeedingNamedOrHoistedDefinition() {
+  // Mark vector operands of OpVectorShuffle as needing a named definition,
+  // but only if they are defined in this function as well.
+  auto require_named_const_def = [&](const spvtools::opt::Instruction& inst,
+                                     int in_operand_index) {
+    const auto id = inst.GetSingleWordInOperand(in_operand_index);
+    auto* const operand_def = GetDefInfo(id);
+    if (operand_def) {
+      operand_def->requires_named_const_def = true;
+    }
+  };
+  for (auto& id_def_info_pair : def_info_) {
+    const auto& inst = id_def_info_pair.second->inst;
+    const auto opcode = inst.opcode();
+    if ((opcode == SpvOpVectorShuffle) || (opcode == SpvOpOuterProduct)) {
+      // We might access the vector operands multiple times. Make sure they
+      // are evaluated only once.
+      require_named_const_def(inst, 0);
+      require_named_const_def(inst, 1);
+    }
+    if (parser_impl_.IsGlslExtendedInstruction(inst)) {
+      // Some emulations of GLSLstd450 instructions evaluate certain operands
+      // multiple times. Ensure their expressions are evaluated only once.
+      switch (inst.GetSingleWordInOperand(1)) {
+        case GLSLstd450FaceForward:
+          // The "normal" operand expression is used twice in code generation.
+          require_named_const_def(inst, 2);
+          break;
+        case GLSLstd450Reflect:
+          require_named_const_def(inst, 2);  // Incident
+          require_named_const_def(inst, 3);  // Normal
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  // Scan uses of locally defined IDs, in function block order.
+  for (auto block_id : block_order_) {
+    const auto* block_info = GetBlockInfo(block_id);
+    const auto block_pos = block_info->pos;
+    for (const auto& inst : *(block_info->basic_block)) {
+      // Update bookkeeping for locally-defined IDs used by this instruction.
+      inst.ForEachInId([this, block_pos, block_info](const uint32_t* id_ptr) {
+        auto* def_info = GetDefInfo(*id_ptr);
+        if (def_info) {
+          // Update usage count.
+          def_info->num_uses++;
+          // Update usage span.
+          def_info->last_use_pos = std::max(def_info->last_use_pos, block_pos);
+
+          // Determine whether this ID is defined in a different construct
+          // from this use.
+          const auto defining_block = block_order_[def_info->block_pos];
+          const auto* def_in_construct =
+              GetBlockInfo(defining_block)->construct;
+          if (def_in_construct != block_info->construct) {
+            def_info->used_in_another_construct = true;
+          }
+        }
+      });
+
+      if (inst.opcode() == SpvOpPhi) {
+        // Declare a name for the variable used to carry values to a phi.
+        const auto phi_id = inst.result_id();
+        auto* phi_def_info = GetDefInfo(phi_id);
+        phi_def_info->phi_var =
+            namer_.MakeDerivedName(namer_.Name(phi_id) + "_phi");
+        // Track all the places where we need to mention the variable,
+        // so we can place its declaration.  First, record the location of
+        // the read from the variable.
+        uint32_t first_pos = block_pos;
+        uint32_t last_pos = block_pos;
+        // Record the assignments that will propagate values from predecessor
+        // blocks.
+        for (uint32_t i = 0; i + 1 < inst.NumInOperands(); i += 2) {
+          const uint32_t value_id = inst.GetSingleWordInOperand(i);
+          const uint32_t pred_block_id = inst.GetSingleWordInOperand(i + 1);
+          auto* pred_block_info = GetBlockInfo(pred_block_id);
+          // The predecessor might not be in the block order at all, so we
+          // need this guard.
+          if (IsInBlockOrder(pred_block_info)) {
+            // Record the assignment that needs to occur at the end
+            // of the predecessor block.
+            pred_block_info->phi_assignments.push_back({phi_id, value_id});
+            first_pos = std::min(first_pos, pred_block_info->pos);
+            last_pos = std::max(last_pos, pred_block_info->pos);
+          }
+        }
+
+        // Schedule the declaration of the state variable.
+        const auto* enclosing_construct =
+            GetEnclosingScope(first_pos, last_pos);
+        GetBlockInfo(enclosing_construct->begin_id)
+            ->phis_needing_state_vars.push_back(phi_id);
+      }
+    }
+  }
+
+  // For an ID defined in this function, determine if its evaluation and
+  // potential declaration needs special handling:
+  // - Compensate for the fact that dominance does not map directly to scope.
+  //   A definition could dominate its use, but a named definition in WGSL
+  //   at the location of the definition could go out of scope by the time
+  //   you reach the use.  In that case, we hoist the definition to a basic
+  //   block at the smallest scope enclosing both the definition and all
+  //   its uses.
+  // - If value is used in a different construct than its definition, then it
+  //   needs a named constant definition.  Otherwise we might sink an
+  //   expensive computation into control flow, and hence change performance.
+  for (auto& id_def_info_pair : def_info_) {
+    const auto def_id = id_def_info_pair.first;
+    auto* def_info = id_def_info_pair.second.get();
+    if (def_info->num_uses == 0) {
+      // There is no need to adjust the location of the declaration.
+      continue;
+    }
+    // The first use must be the at the SSA definition, because block order
+    // respects dominance.
+    const auto first_pos = def_info->block_pos;
+    const auto last_use_pos = def_info->last_use_pos;
+
+    const auto* def_in_construct =
+        GetBlockInfo(block_order_[first_pos])->construct;
+    // A definition in the first block of an kIfSelection or kSwitchSelection
+    // occurs before the branch, and so that definition should count as
+    // having been defined at the scope of the parent construct.
+    if (first_pos == def_in_construct->begin_pos) {
+      if ((def_in_construct->kind == Construct::kIfSelection) ||
+          (def_in_construct->kind == Construct::kSwitchSelection)) {
+        def_in_construct = def_in_construct->parent;
+      }
+    }
+
+    bool should_hoist = false;
+    if (!def_in_construct->ContainsPos(last_use_pos)) {
+      // To satisfy scoping, we have to hoist the definition out to an enclosing
+      // construct.
+      should_hoist = true;
+    } else {
+      // Avoid moving combinatorial values across constructs.  This is a
+      // simple heuristic to avoid changing the cost of an operation
+      // by moving it into or out of a loop, for example.
+      if ((def_info->storage_class == ast::StorageClass::kInvalid) &&
+          def_info->used_in_another_construct) {
+        should_hoist = true;
+      }
+    }
+
+    if (should_hoist) {
+      const auto* enclosing_construct =
+          GetEnclosingScope(first_pos, last_use_pos);
+      if (enclosing_construct == def_in_construct) {
+        // We can use a plain 'const' definition.
+        def_info->requires_named_const_def = true;
+      } else {
+        // We need to make a hoisted variable definition.
+        // TODO(dneto): Handle non-storable types, particularly pointers.
+        def_info->requires_hoisted_def = true;
+        auto* hoist_to_block = GetBlockInfo(enclosing_construct->begin_id);
+        hoist_to_block->hoisted_ids.push_back(def_id);
+      }
+    }
+  }
+}
+
+const Construct* FunctionEmitter::GetEnclosingScope(uint32_t first_pos,
+                                                    uint32_t last_pos) const {
+  const auto* enclosing_construct =
+      GetBlockInfo(block_order_[first_pos])->construct;
+  TINT_ASSERT(Reader, enclosing_construct != nullptr);
+  // Constructs are strictly nesting, so follow parent pointers
+  while (enclosing_construct &&
+         !enclosing_construct->ScopeContainsPos(last_pos)) {
+    // The scope of a continue construct is enclosed in its associated loop
+    // construct, but they are siblings in our construct tree.
+    const auto* sibling_loop = SiblingLoopConstruct(enclosing_construct);
+    // Go to the sibling loop if it exists, otherwise walk up to the parent.
+    enclosing_construct =
+        sibling_loop ? sibling_loop : enclosing_construct->parent;
+  }
+  // At worst, we go all the way out to the function construct.
+  TINT_ASSERT(Reader, enclosing_construct != nullptr);
+  return enclosing_construct;
+}
+
+TypedExpression FunctionEmitter::MakeNumericConversion(
+    const spvtools::opt::Instruction& inst) {
+  const auto opcode = inst.opcode();
+  auto* requested_type = parser_impl_.ConvertType(inst.type_id());
+  auto arg_expr = MakeOperand(inst, 0);
+  if (!arg_expr) {
+    return {};
+  }
+  arg_expr.type = arg_expr.type->UnwrapRef();
+
+  const Type* expr_type = nullptr;
+  if ((opcode == SpvOpConvertSToF) || (opcode == SpvOpConvertUToF)) {
+    if (arg_expr.type->IsIntegerScalarOrVector()) {
+      expr_type = requested_type;
+    } else {
+      Fail() << "operand for conversion to floating point must be integral "
+                "scalar or vector: "
+             << inst.PrettyPrint();
+    }
+  } else if (inst.opcode() == SpvOpConvertFToU) {
+    if (arg_expr.type->IsFloatScalarOrVector()) {
+      expr_type = parser_impl_.GetUnsignedIntMatchingShape(arg_expr.type);
+    } else {
+      Fail() << "operand for conversion to unsigned integer must be floating "
+                "point scalar or vector: "
+             << inst.PrettyPrint();
+    }
+  } else if (inst.opcode() == SpvOpConvertFToS) {
+    if (arg_expr.type->IsFloatScalarOrVector()) {
+      expr_type = parser_impl_.GetSignedIntMatchingShape(arg_expr.type);
+    } else {
+      Fail() << "operand for conversion to signed integer must be floating "
+                "point scalar or vector: "
+             << inst.PrettyPrint();
+    }
+  }
+  if (expr_type == nullptr) {
+    // The diagnostic has already been emitted.
+    return {};
+  }
+
+  ast::ExpressionList params;
+  params.push_back(arg_expr.expr);
+  TypedExpression result{
+      expr_type,
+      builder_.Construct(GetSourceForInst(inst), expr_type->Build(builder_),
+                         std::move(params))};
+
+  if (requested_type == expr_type) {
+    return result;
+  }
+  return {requested_type, create<ast::BitcastExpression>(
+                              GetSourceForInst(inst),
+                              requested_type->Build(builder_), result.expr)};
+}
+
+bool FunctionEmitter::EmitFunctionCall(const spvtools::opt::Instruction& inst) {
+  // We ignore function attributes such as Inline, DontInline, Pure, Const.
+  auto name = namer_.Name(inst.GetSingleWordInOperand(0));
+  auto* function = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(name));
+
+  ast::ExpressionList args;
+  for (uint32_t iarg = 1; iarg < inst.NumInOperands(); ++iarg) {
+    auto expr = MakeOperand(inst, iarg);
+    if (!expr) {
+      return false;
+    }
+    // Functions cannot use references as parameters, so we need to pass by
+    // pointer if the operand is of pointer type.
+    expr = AddressOfIfNeeded(
+        expr, def_use_mgr_->GetDef(inst.GetSingleWordInOperand(iarg)));
+    args.emplace_back(expr.expr);
+  }
+  if (failed()) {
+    return false;
+  }
+  auto* call_expr =
+      create<ast::CallExpression>(Source{}, function, std::move(args));
+  auto* result_type = parser_impl_.ConvertType(inst.type_id());
+  if (!result_type) {
+    return Fail() << "internal error: no mapped type result of call: "
+                  << inst.PrettyPrint();
+  }
+
+  if (result_type->Is<Void>()) {
+    return nullptr !=
+           AddStatement(create<ast::CallStatement>(Source{}, call_expr));
+  }
+
+  return EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr});
+}
+
+bool FunctionEmitter::EmitControlBarrier(
+    const spvtools::opt::Instruction& inst) {
+  uint32_t operands[3];
+  for (int i = 0; i < 3; i++) {
+    auto id = inst.GetSingleWordInOperand(i);
+    if (auto* constant = constant_mgr_->FindDeclaredConstant(id)) {
+      operands[i] = constant->GetU32();
+    } else {
+      return Fail() << "invalid or missing operands for control barrier";
+    }
+  }
+
+  uint32_t execution = operands[0];
+  uint32_t memory = operands[1];
+  uint32_t semantics = operands[2];
+
+  if (execution != SpvScopeWorkgroup) {
+    return Fail() << "unsupported control barrier execution scope: "
+                  << "expected Workgroup (2), got: " << execution;
+  }
+  if (semantics & SpvMemorySemanticsAcquireReleaseMask) {
+    semantics &= ~SpvMemorySemanticsAcquireReleaseMask;
+  } else {
+    return Fail() << "control barrier semantics requires acquire and release";
+  }
+  if (semantics & SpvMemorySemanticsWorkgroupMemoryMask) {
+    if (memory != SpvScopeWorkgroup) {
+      return Fail() << "workgroupBarrier requires workgroup memory scope";
+    }
+    AddStatement(create<ast::CallStatement>(builder_.Call("workgroupBarrier")));
+    semantics &= ~SpvMemorySemanticsWorkgroupMemoryMask;
+  }
+  if (semantics & SpvMemorySemanticsUniformMemoryMask) {
+    if (memory != SpvScopeDevice) {
+      return Fail() << "storageBarrier requires device memory scope";
+    }
+    AddStatement(create<ast::CallStatement>(builder_.Call("storageBarrier")));
+    semantics &= ~SpvMemorySemanticsUniformMemoryMask;
+  }
+  if (semantics) {
+    return Fail() << "unsupported control barrier semantics: " << semantics;
+  }
+  return true;
+}
+
+TypedExpression FunctionEmitter::MakeBuiltinCall(
+    const spvtools::opt::Instruction& inst) {
+  const auto builtin = GetBuiltin(inst.opcode());
+  auto* name = sem::str(builtin);
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(name));
+
+  ast::ExpressionList params;
+  const Type* first_operand_type = nullptr;
+  for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
+    TypedExpression operand = MakeOperand(inst, iarg);
+    if (first_operand_type == nullptr) {
+      first_operand_type = operand.type;
+    }
+    params.emplace_back(operand.expr);
+  }
+  auto* call_expr =
+      create<ast::CallExpression>(Source{}, ident, std::move(params));
+  auto* result_type = parser_impl_.ConvertType(inst.type_id());
+  if (!result_type) {
+    Fail() << "internal error: no mapped type result of call: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  TypedExpression call{result_type, call_expr};
+  return parser_impl_.RectifyForcedResultType(call, inst, first_operand_type);
+}
+
+TypedExpression FunctionEmitter::MakeSimpleSelect(
+    const spvtools::opt::Instruction& inst) {
+  auto condition = MakeOperand(inst, 0);
+  auto true_value = MakeOperand(inst, 1);
+  auto false_value = MakeOperand(inst, 2);
+
+  // SPIR-V validation requires:
+  // - the condition to be bool or bool vector, so we don't check it here.
+  // - true_value false_value, and result type to match.
+  // - you can't select over pointers or pointer vectors, unless you also have
+  //   a VariablePointers* capability, which is not allowed in by WebGPU.
+  auto* op_ty = true_value.type;
+  if (op_ty->Is<Vector>() || op_ty->IsFloatScalar() ||
+      op_ty->IsIntegerScalar() || op_ty->Is<Bool>()) {
+    ast::ExpressionList params;
+    params.push_back(false_value.expr);
+    params.push_back(true_value.expr);
+    // The condition goes last.
+    params.push_back(condition.expr);
+    return {op_ty, create<ast::CallExpression>(
+                       Source{},
+                       create<ast::IdentifierExpression>(
+                           Source{}, builder_.Symbols().Register("select")),
+                       std::move(params))};
+  }
+  return {};
+}
+
+Source FunctionEmitter::GetSourceForInst(
+    const spvtools::opt::Instruction& inst) const {
+  return parser_impl_.GetSourceForInst(&inst);
+}
+
+const spvtools::opt::Instruction* FunctionEmitter::GetImage(
+    const spvtools::opt::Instruction& inst) {
+  if (inst.NumInOperands() == 0) {
+    Fail() << "not an image access instruction: " << inst.PrettyPrint();
+    return nullptr;
+  }
+  // The image or sampled image operand is always the first operand.
+  const auto image_or_sampled_image_operand_id = inst.GetSingleWordInOperand(0);
+  const auto* image = parser_impl_.GetMemoryObjectDeclarationForHandle(
+      image_or_sampled_image_operand_id, true);
+  if (!image) {
+    Fail() << "internal error: couldn't find image for " << inst.PrettyPrint();
+    return nullptr;
+  }
+  return image;
+}
+
+const Texture* FunctionEmitter::GetImageType(
+    const spvtools::opt::Instruction& image) {
+  const Pointer* ptr_type = parser_impl_.GetTypeForHandleVar(image);
+  if (!parser_impl_.success()) {
+    Fail();
+    return {};
+  }
+  if (!ptr_type) {
+    Fail() << "invalid texture type for " << image.PrettyPrint();
+    return {};
+  }
+  auto* result = ptr_type->type->UnwrapAll()->As<Texture>();
+  if (!result) {
+    Fail() << "invalid texture type for " << image.PrettyPrint();
+    return {};
+  }
+  return result;
+}
+
+const ast::Expression* FunctionEmitter::GetImageExpression(
+    const spvtools::opt::Instruction& inst) {
+  auto* image = GetImage(inst);
+  if (!image) {
+    return nullptr;
+  }
+  auto name = namer_.Name(image->result_id());
+  return create<ast::IdentifierExpression>(GetSourceForInst(inst),
+                                           builder_.Symbols().Register(name));
+}
+
+const ast::Expression* FunctionEmitter::GetSamplerExpression(
+    const spvtools::opt::Instruction& inst) {
+  // The sampled image operand is always the first operand.
+  const auto image_or_sampled_image_operand_id = inst.GetSingleWordInOperand(0);
+  const auto* image = parser_impl_.GetMemoryObjectDeclarationForHandle(
+      image_or_sampled_image_operand_id, false);
+  if (!image) {
+    Fail() << "internal error: couldn't find sampler for "
+           << inst.PrettyPrint();
+    return nullptr;
+  }
+  auto name = namer_.Name(image->result_id());
+  return create<ast::IdentifierExpression>(GetSourceForInst(inst),
+                                           builder_.Symbols().Register(name));
+}
+
+bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) {
+  ast::ExpressionList args;
+  const auto opcode = inst.opcode();
+
+  // Form the texture operand.
+  const spvtools::opt::Instruction* image = GetImage(inst);
+  if (!image) {
+    return false;
+  }
+  args.push_back(GetImageExpression(inst));
+
+  // Form the sampler operand, if needed.
+  if (IsSampledImageAccess(opcode)) {
+    // Form the sampler operand.
+    if (auto* sampler = GetSamplerExpression(inst)) {
+      args.push_back(sampler);
+    } else {
+      return false;
+    }
+  }
+
+  // Find the texture type.
+  const Pointer* texture_ptr_type = parser_impl_.GetTypeForHandleVar(*image);
+  if (!texture_ptr_type) {
+    return Fail();
+  }
+  const Texture* texture_type =
+      texture_ptr_type->type->UnwrapAll()->As<Texture>();
+
+  if (!texture_type) {
+    return Fail();
+  }
+
+  // This is the SPIR-V operand index.  We're done with the first operand.
+  uint32_t arg_index = 1;
+
+  // Push the coordinates operands.
+  auto coords = MakeCoordinateOperandsForImageAccess(inst);
+  if (coords.empty()) {
+    return false;
+  }
+  args.insert(args.end(), coords.begin(), coords.end());
+  // Skip the coordinates operand.
+  arg_index++;
+
+  const auto num_args = inst.NumInOperands();
+
+  // Consumes the depth-reference argument, pushing it onto the end of
+  // the parameter list. Issues a diagnostic and returns false on error.
+  auto consume_dref = [&]() -> bool {
+    if (arg_index < num_args) {
+      args.push_back(MakeOperand(inst, arg_index).expr);
+      arg_index++;
+    } else {
+      return Fail()
+             << "image depth-compare instruction is missing a Dref operand: "
+             << inst.PrettyPrint();
+    }
+    return true;
+  };
+
+  std::string builtin_name;
+  bool use_level_of_detail_suffix = true;
+  bool is_dref_sample = false;
+  bool is_gather_or_dref_gather = false;
+  bool is_non_dref_sample = false;
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+      is_non_dref_sample = true;
+      builtin_name = "textureSample";
+      break;
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+      is_dref_sample = true;
+      builtin_name = "textureSampleCompare";
+      if (!consume_dref()) {
+        return false;
+      }
+      break;
+    case SpvOpImageGather:
+      is_gather_or_dref_gather = true;
+      builtin_name = "textureGather";
+      if (!texture_type->Is<DepthTexture>()) {
+        // The explicit component is the *first* argument in WGSL.
+        args.insert(args.begin(), ToI32(MakeOperand(inst, arg_index)).expr);
+      }
+      // Skip over the component operand, even for depth textures.
+      arg_index++;
+      break;
+    case SpvOpImageDrefGather:
+      is_gather_or_dref_gather = true;
+      builtin_name = "textureGatherCompare";
+      if (!consume_dref()) {
+        return false;
+      }
+      break;
+    case SpvOpImageFetch:
+    case SpvOpImageRead:
+      // Read a single texel from a sampled or storage image.
+      builtin_name = "textureLoad";
+      use_level_of_detail_suffix = false;
+      break;
+    case SpvOpImageWrite:
+      builtin_name = "textureStore";
+      use_level_of_detail_suffix = false;
+      if (arg_index < num_args) {
+        auto texel = MakeOperand(inst, arg_index);
+        auto* converted_texel =
+            ConvertTexelForStorage(inst, texel, texture_type);
+        if (!converted_texel) {
+          return false;
+        }
+
+        args.push_back(converted_texel);
+        arg_index++;
+      } else {
+        return Fail() << "image write is missing a Texel operand: "
+                      << inst.PrettyPrint();
+      }
+      break;
+    default:
+      return Fail() << "internal error: unrecognized image access: "
+                    << inst.PrettyPrint();
+  }
+
+  // Loop over the image operands, looking for extra operands to the builtin.
+  // Except we uroll the loop.
+  uint32_t image_operands_mask = 0;
+  if (arg_index < num_args) {
+    image_operands_mask = inst.GetSingleWordInOperand(arg_index);
+    arg_index++;
+  }
+  if (arg_index < num_args &&
+      (image_operands_mask & SpvImageOperandsBiasMask)) {
+    if (is_dref_sample) {
+      return Fail() << "WGSL does not support depth-reference sampling with "
+                       "level-of-detail bias: "
+                    << inst.PrettyPrint();
+    }
+    if (is_gather_or_dref_gather) {
+      return Fail() << "WGSL does not support image gather with "
+                       "level-of-detail bias: "
+                    << inst.PrettyPrint();
+    }
+    builtin_name += "Bias";
+    args.push_back(MakeOperand(inst, arg_index).expr);
+    image_operands_mask ^= SpvImageOperandsBiasMask;
+    arg_index++;
+  }
+  if (arg_index < num_args && (image_operands_mask & SpvImageOperandsLodMask)) {
+    if (use_level_of_detail_suffix) {
+      builtin_name += "Level";
+    }
+    if (is_dref_sample || is_gather_or_dref_gather) {
+      // Metal only supports Lod = 0 for comparison sampling without
+      // derivatives.
+      // Vulkan SPIR-V does not allow Lod with OpImageGather or
+      // OpImageDrefGather.
+      if (!IsFloatZero(inst.GetSingleWordInOperand(arg_index))) {
+        return Fail() << "WGSL comparison sampling without derivatives "
+                         "requires level-of-detail 0.0"
+                      << inst.PrettyPrint();
+      }
+      // Don't generate the Lod argument.
+    } else {
+      // Generate the Lod argument.
+      TypedExpression lod = MakeOperand(inst, arg_index);
+      // When sampling from a depth texture, the Lod operand must be an I32.
+      if (texture_type->Is<DepthTexture>()) {
+        // Convert it to a signed integer type.
+        lod = ToI32(lod);
+      }
+      args.push_back(lod.expr);
+    }
+
+    image_operands_mask ^= SpvImageOperandsLodMask;
+    arg_index++;
+  } else if ((opcode == SpvOpImageFetch || opcode == SpvOpImageRead) &&
+             !texture_type
+                  ->IsAnyOf<DepthMultisampledTexture, MultisampledTexture>()) {
+    // textureLoad requires an explicit level-of-detail parameter for
+    // non-multisampled texture types.
+    args.push_back(parser_impl_.MakeNullValue(ty_.I32()));
+  }
+  if (arg_index + 1 < num_args &&
+      (image_operands_mask & SpvImageOperandsGradMask)) {
+    if (is_dref_sample) {
+      return Fail() << "WGSL does not support depth-reference sampling with "
+                       "explicit gradient: "
+                    << inst.PrettyPrint();
+    }
+    if (is_gather_or_dref_gather) {
+      return Fail() << "WGSL does not support image gather with "
+                       "explicit gradient: "
+                    << inst.PrettyPrint();
+    }
+    builtin_name += "Grad";
+    args.push_back(MakeOperand(inst, arg_index).expr);
+    args.push_back(MakeOperand(inst, arg_index + 1).expr);
+    image_operands_mask ^= SpvImageOperandsGradMask;
+    arg_index += 2;
+  }
+  if (arg_index < num_args &&
+      (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
+    if (!IsImageSamplingOrGatherOrDrefGather(opcode)) {
+      return Fail() << "ConstOffset is only permitted for sampling, gather, or "
+                       "depth-reference gather operations: "
+                    << inst.PrettyPrint();
+    }
+    switch (texture_type->dims) {
+      case ast::TextureDimension::k2d:
+      case ast::TextureDimension::k2dArray:
+      case ast::TextureDimension::k3d:
+        break;
+      default:
+        return Fail() << "ConstOffset is only permitted for 2D, 2D Arrayed, "
+                         "and 3D textures: "
+                      << inst.PrettyPrint();
+    }
+
+    args.push_back(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
+    image_operands_mask ^= SpvImageOperandsConstOffsetMask;
+    arg_index++;
+  }
+  if (arg_index < num_args &&
+      (image_operands_mask & SpvImageOperandsSampleMask)) {
+    // TODO(dneto): only permitted with ImageFetch
+    args.push_back(ToI32(MakeOperand(inst, arg_index)).expr);
+    image_operands_mask ^= SpvImageOperandsSampleMask;
+    arg_index++;
+  }
+  if (image_operands_mask) {
+    return Fail() << "unsupported image operands (" << image_operands_mask
+                  << "): " << inst.PrettyPrint();
+  }
+
+  // If any of the arguments are nullptr, then we've failed.
+  if (std::any_of(args.begin(), args.end(),
+                  [](auto* expr) { return expr == nullptr; })) {
+    return false;
+  }
+
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(builtin_name));
+  auto* call_expr =
+      create<ast::CallExpression>(Source{}, ident, std::move(args));
+
+  if (inst.type_id() != 0) {
+    // It returns a value.
+    const ast::Expression* value = call_expr;
+
+    // The result type, derived from the SPIR-V instruction.
+    auto* result_type = parser_impl_.ConvertType(inst.type_id());
+    auto* result_component_type = result_type;
+    if (auto* result_vector_type = As<Vector>(result_type)) {
+      result_component_type = result_vector_type->type;
+    }
+
+    // For depth textures, the arity might mot match WGSL:
+    //  Operation           SPIR-V                     WGSL
+    //   normal sampling     vec4  ImplicitLod          f32
+    //   normal sampling     vec4  ExplicitLod          f32
+    //   compare sample      f32   DrefImplicitLod      f32
+    //   compare sample      f32   DrefExplicitLod      f32
+    //   texel load          vec4  ImageFetch           f32
+    //   normal gather       vec4  ImageGather          vec4
+    //   dref gather         vec4  ImageDrefGather      vec4
+    // Construct a 4-element vector with the result from the builtin in the
+    // first component.
+    if (texture_type->IsAnyOf<DepthTexture, DepthMultisampledTexture>()) {
+      if (is_non_dref_sample || (opcode == SpvOpImageFetch)) {
+        value = builder_.Construct(
+            Source{},
+            result_type->Build(builder_),  // a vec4
+            ast::ExpressionList{
+                value, parser_impl_.MakeNullValue(result_component_type),
+                parser_impl_.MakeNullValue(result_component_type),
+                parser_impl_.MakeNullValue(result_component_type)});
+      }
+    }
+
+    // If necessary, convert the result to the signedness of the instruction
+    // result type. Compare the SPIR-V image's sampled component type with the
+    // component of the result type of the SPIR-V instruction.
+    auto* spirv_image_type =
+        parser_impl_.GetSpirvTypeForHandleMemoryObjectDeclaration(*image);
+    if (!spirv_image_type || (spirv_image_type->opcode() != SpvOpTypeImage)) {
+      return Fail() << "invalid image type for image memory object declaration "
+                    << image->PrettyPrint();
+    }
+    auto* expected_component_type =
+        parser_impl_.ConvertType(spirv_image_type->GetSingleWordInOperand(0));
+    if (expected_component_type != result_component_type) {
+      // This occurs if one is signed integer and the other is unsigned integer,
+      // or vice versa. Perform a bitcast.
+      value = create<ast::BitcastExpression>(
+          Source{}, result_type->Build(builder_), call_expr);
+    }
+    if (!expected_component_type->Is<F32>() && IsSampledImageAccess(opcode)) {
+      // WGSL permits sampled image access only on float textures.
+      // Reject this case in the SPIR-V reader, at least until SPIR-V validation
+      // catches up with this rule and can reject it earlier in the workflow.
+      return Fail() << "sampled image must have float component type";
+    }
+
+    EmitConstDefOrWriteToHoistedVar(inst, {result_type, value});
+  } else {
+    // It's an image write. No value is returned, so make a statement out
+    // of the call.
+    AddStatement(create<ast::CallStatement>(Source{}, call_expr));
+  }
+  return success();
+}
+
+bool FunctionEmitter::EmitImageQuery(const spvtools::opt::Instruction& inst) {
+  // TODO(dneto): Reject cases that are valid in Vulkan but invalid in WGSL.
+  const spvtools::opt::Instruction* image = GetImage(inst);
+  if (!image) {
+    return false;
+  }
+  auto* texture_type = GetImageType(*image);
+  if (!texture_type) {
+    return false;
+  }
+
+  const auto opcode = inst.opcode();
+  switch (opcode) {
+    case SpvOpImageQuerySize:
+    case SpvOpImageQuerySizeLod: {
+      ast::ExpressionList exprs;
+      // Invoke textureDimensions.
+      // If the texture is arrayed, combine with the result from
+      // textureNumLayers.
+      auto* dims_ident = create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register("textureDimensions"));
+      ast::ExpressionList dims_args{GetImageExpression(inst)};
+      if (opcode == SpvOpImageQuerySizeLod) {
+        dims_args.push_back(ToI32(MakeOperand(inst, 1)).expr);
+      }
+      const ast::Expression* dims_call =
+          create<ast::CallExpression>(Source{}, dims_ident, dims_args);
+      auto dims = texture_type->dims;
+      if ((dims == ast::TextureDimension::kCube) ||
+          (dims == ast::TextureDimension::kCubeArray)) {
+        // textureDimension returns a 3-element vector but SPIR-V expects 2.
+        dims_call = create<ast::MemberAccessorExpression>(Source{}, dims_call,
+                                                          PrefixSwizzle(2));
+      }
+      exprs.push_back(dims_call);
+      if (ast::IsTextureArray(dims)) {
+        auto* layers_ident = create<ast::IdentifierExpression>(
+            Source{}, builder_.Symbols().Register("textureNumLayers"));
+        exprs.push_back(create<ast::CallExpression>(
+            Source{}, layers_ident,
+            ast::ExpressionList{GetImageExpression(inst)}));
+      }
+      auto* result_type = parser_impl_.ConvertType(inst.type_id());
+      TypedExpression expr = {
+          result_type,
+          builder_.Construct(Source{}, result_type->Build(builder_), exprs)};
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
+    case SpvOpImageQueryLod:
+      return Fail() << "WGSL does not support querying the level of detail of "
+                       "an image: "
+                    << inst.PrettyPrint();
+    case SpvOpImageQueryLevels:
+    case SpvOpImageQuerySamples: {
+      const auto* name = (opcode == SpvOpImageQueryLevels)
+                             ? "textureNumLevels"
+                             : "textureNumSamples";
+      auto* levels_ident = create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register(name));
+      const ast::Expression* ast_expr = create<ast::CallExpression>(
+          Source{}, levels_ident,
+          ast::ExpressionList{GetImageExpression(inst)});
+      auto* result_type = parser_impl_.ConvertType(inst.type_id());
+      // The SPIR-V result type must be integer scalar. The WGSL bulitin
+      // returns i32. If they aren't the same then convert the result.
+      if (!result_type->Is<I32>()) {
+        ast_expr = builder_.Construct(Source{}, result_type->Build(builder_),
+                                      ast::ExpressionList{ast_expr});
+      }
+      TypedExpression expr{result_type, ast_expr};
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
+    default:
+      break;
+  }
+  return Fail() << "unhandled image query: " << inst.PrettyPrint();
+}
+
+ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess(
+    const spvtools::opt::Instruction& inst) {
+  if (!parser_impl_.success()) {
+    Fail();
+    return {};
+  }
+  const spvtools::opt::Instruction* image = GetImage(inst);
+  if (!image) {
+    return {};
+  }
+  if (inst.NumInOperands() < 1) {
+    Fail() << "image access is missing a coordinate parameter: "
+           << inst.PrettyPrint();
+    return {};
+  }
+
+  // In SPIR-V for Shader, coordinates are:
+  //  - floating point for sampling, dref sampling, gather, dref gather
+  //  - integral for fetch, read, write
+  // In WGSL:
+  //  - floating point for sampling, dref sampling, gather, dref gather
+  //  - signed integral for textureLoad, textureStore
+  //
+  // The only conversions we have to do for WGSL are:
+  //  - When the coordinates are unsigned integral, convert them to signed.
+  //  - Array index is always i32
+
+  // The coordinates parameter is always in position 1.
+  TypedExpression raw_coords(MakeOperand(inst, 1));
+  if (!raw_coords) {
+    return {};
+  }
+  const Texture* texture_type = GetImageType(*image);
+  if (!texture_type) {
+    return {};
+  }
+  ast::TextureDimension dim = texture_type->dims;
+  // Number of regular coordinates.
+  uint32_t num_axes = ast::NumCoordinateAxes(dim);
+  bool is_arrayed = ast::IsTextureArray(dim);
+  if ((num_axes == 0) || (num_axes > 3)) {
+    Fail() << "unsupported image dimensionality for "
+           << texture_type->TypeInfo().name << " prompted by "
+           << inst.PrettyPrint();
+  }
+  bool is_proj = false;
+  switch (inst.opcode()) {
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+      is_proj = true;
+      break;
+    default:
+      break;
+  }
+
+  const auto num_coords_required =
+      num_axes + (is_arrayed ? 1 : 0) + (is_proj ? 1 : 0);
+  uint32_t num_coords_supplied = 0;
+  auto* component_type = raw_coords.type;
+  if (component_type->IsFloatScalar() || component_type->IsIntegerScalar()) {
+    num_coords_supplied = 1;
+  } else if (auto* vec_type = As<Vector>(raw_coords.type)) {
+    component_type = vec_type->type;
+    num_coords_supplied = vec_type->size;
+  }
+  if (num_coords_supplied == 0) {
+    Fail() << "bad or unsupported coordinate type for image access: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  if (num_coords_required > num_coords_supplied) {
+    Fail() << "image access required " << num_coords_required
+           << " coordinate components, but only " << num_coords_supplied
+           << " provided, in: " << inst.PrettyPrint();
+    return {};
+  }
+
+  ast::ExpressionList result;
+
+  // Generates the expression for the WGSL coordinates, when it is a prefix
+  // swizzle with num_axes.  If the result would be unsigned, also converts
+  // it to a signed value of the same shape (scalar or vector).
+  // Use a lambda to make it easy to only generate the expressions when we
+  // will actually use them.
+  auto prefix_swizzle_expr = [this, num_axes, component_type, is_proj,
+                              raw_coords]() -> const ast::Expression* {
+    auto* swizzle_type =
+        (num_axes == 1) ? component_type : ty_.Vector(component_type, num_axes);
+    auto* swizzle = create<ast::MemberAccessorExpression>(
+        Source{}, raw_coords.expr, PrefixSwizzle(num_axes));
+    if (is_proj) {
+      auto* q = create<ast::MemberAccessorExpression>(Source{}, raw_coords.expr,
+                                                      Swizzle(num_axes));
+      auto* proj_div = builder_.Div(swizzle, q);
+      return ToSignedIfUnsigned({swizzle_type, proj_div}).expr;
+    } else {
+      return ToSignedIfUnsigned({swizzle_type, swizzle}).expr;
+    }
+  };
+
+  if (is_arrayed) {
+    // The source must be a vector. It has at least one coordinate component
+    // and it must have an array component.  Use a vector swizzle to get the
+    // first `num_axes` components.
+    result.push_back(prefix_swizzle_expr());
+
+    // Now get the array index.
+    const ast::Expression* array_index =
+        builder_.MemberAccessor(raw_coords.expr, Swizzle(num_axes));
+    if (component_type->IsFloatScalar()) {
+      // When converting from a float array layer to integer, Vulkan requires
+      // round-to-nearest, with preference for round-to-nearest-even.
+      // But i32(f32) in WGSL has unspecified rounding mode, so we have to
+      // explicitly specify the rounding.
+      array_index = builder_.Call("round", array_index);
+    }
+    // Convert it to a signed integer type, if needed.
+    result.push_back(ToI32({component_type, array_index}).expr);
+  } else {
+    if (num_coords_supplied == num_coords_required && !is_proj) {
+      // Pass the value through, with possible unsigned->signed conversion.
+      result.push_back(ToSignedIfUnsigned(raw_coords).expr);
+    } else {
+      // There are more coordinates supplied than needed. So the source type
+      // is a vector. Use a vector swizzle to get the first `num_axes`
+      // components.
+      result.push_back(prefix_swizzle_expr());
+    }
+  }
+  return result;
+}
+
+const ast::Expression* FunctionEmitter::ConvertTexelForStorage(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression texel,
+    const Texture* texture_type) {
+  auto* storage_texture_type = As<StorageTexture>(texture_type);
+  auto* src_type = texel.type;
+  if (!storage_texture_type) {
+    Fail() << "writing to other than storage texture: " << inst.PrettyPrint();
+    return nullptr;
+  }
+  const auto format = storage_texture_type->format;
+  auto* dest_type = parser_impl_.GetTexelTypeForFormat(format);
+  if (!dest_type) {
+    Fail();
+    return nullptr;
+  }
+
+  // The texel type is always a 4-element vector.
+  const uint32_t dest_count = 4u;
+  TINT_ASSERT(Reader, dest_type->Is<Vector>() &&
+                          dest_type->As<Vector>()->size == dest_count);
+  TINT_ASSERT(Reader, dest_type->IsFloatVector() ||
+                          dest_type->IsUnsignedIntegerVector() ||
+                          dest_type->IsSignedIntegerVector());
+
+  if (src_type == dest_type) {
+    return texel.expr;
+  }
+
+  // Component type must match floatness, or integral signedness.
+  if ((src_type->IsFloatScalarOrVector() != dest_type->IsFloatVector()) ||
+      (src_type->IsUnsignedIntegerVector() !=
+       dest_type->IsUnsignedIntegerVector()) ||
+      (src_type->IsSignedIntegerVector() !=
+       dest_type->IsSignedIntegerVector())) {
+    Fail() << "invalid texel type for storage texture write: component must be "
+              "float, signed integer, or unsigned integer "
+              "to match the texture channel type: "
+           << inst.PrettyPrint();
+    return nullptr;
+  }
+
+  const auto required_count = parser_impl_.GetChannelCountForFormat(format);
+  TINT_ASSERT(Reader, 0 < required_count && required_count <= 4);
+
+  const uint32_t src_count =
+      src_type->IsScalar() ? 1 : src_type->As<Vector>()->size;
+  if (src_count < required_count) {
+    Fail() << "texel has too few components for storage texture: " << src_count
+           << " provided but " << required_count
+           << " required, in: " << inst.PrettyPrint();
+    return nullptr;
+  }
+
+  // It's valid for required_count < src_count. The extra components will
+  // be written out but the textureStore will ignore them.
+
+  if (src_count < dest_count) {
+    // Expand the texel to a 4 element vector.
+    auto* component_type =
+        texel.type->IsScalar() ? texel.type : texel.type->As<Vector>()->type;
+    texel.type = ty_.Vector(component_type, dest_count);
+    ast::ExpressionList exprs;
+    exprs.push_back(texel.expr);
+    for (auto i = src_count; i < dest_count; i++) {
+      exprs.push_back(parser_impl_.MakeNullExpression(component_type).expr);
+    }
+    texel.expr = builder_.Construct(Source{}, texel.type->Build(builder_),
+                                    std::move(exprs));
+  }
+
+  return texel.expr;
+}
+
+TypedExpression FunctionEmitter::ToI32(TypedExpression value) {
+  if (!value || value.type->Is<I32>()) {
+    return value;
+  }
+  return {ty_.I32(), builder_.Construct(Source{}, builder_.ty.i32(),
+                                        ast::ExpressionList{value.expr})};
+}
+
+TypedExpression FunctionEmitter::ToSignedIfUnsigned(TypedExpression value) {
+  if (!value || !value.type->IsUnsignedScalarOrVector()) {
+    return value;
+  }
+  if (auto* vec_type = value.type->As<Vector>()) {
+    auto* new_type = ty_.Vector(ty_.I32(), vec_type->size);
+    return {new_type, builder_.Construct(new_type->Build(builder_),
+                                         ast::ExpressionList{value.expr})};
+  }
+  return ToI32(value);
+}
+
+TypedExpression FunctionEmitter::MakeArrayLength(
+    const spvtools::opt::Instruction& inst) {
+  if (inst.NumInOperands() != 2) {
+    // Binary parsing will fail on this anyway.
+    Fail() << "invalid array length: requires 2 operands: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  const auto struct_ptr_id = inst.GetSingleWordInOperand(0);
+  const auto field_index = inst.GetSingleWordInOperand(1);
+  const auto struct_ptr_type_id =
+      def_use_mgr_->GetDef(struct_ptr_id)->type_id();
+  // Trace through the pointer type to get to the struct type.
+  const auto struct_type_id =
+      def_use_mgr_->GetDef(struct_ptr_type_id)->GetSingleWordInOperand(1);
+  const auto field_name = namer_.GetMemberName(struct_type_id, field_index);
+  if (field_name.empty()) {
+    Fail() << "struct index out of bounds for array length: "
+           << inst.PrettyPrint();
+    return {};
+  }
+
+  auto member_expr = MakeExpression(struct_ptr_id);
+  if (!member_expr) {
+    return {};
+  }
+  if (member_expr.type->Is<Pointer>()) {
+    member_expr = Dereference(member_expr);
+  }
+  auto* member_ident = create<ast::IdentifierExpression>(
+      Source{}, builder_.Symbols().Register(field_name));
+  auto* member_access = create<ast::MemberAccessorExpression>(
+      Source{}, member_expr.expr, member_ident);
+
+  // Generate the builtin function call.
+  auto* call_expr =
+      builder_.Call(Source{}, "arrayLength", builder_.AddressOf(member_access));
+
+  return {parser_impl_.ConvertType(inst.type_id()), call_expr};
+}
+
+TypedExpression FunctionEmitter::MakeOuterProduct(
+    const spvtools::opt::Instruction& inst) {
+  // Synthesize the result.
+  auto col = MakeOperand(inst, 0);
+  auto row = MakeOperand(inst, 1);
+  auto* col_ty = As<Vector>(col.type);
+  auto* row_ty = As<Vector>(row.type);
+  auto* result_ty = As<Matrix>(parser_impl_.ConvertType(inst.type_id()));
+  if (!col_ty || !col_ty || !result_ty || result_ty->type != col_ty->type ||
+      result_ty->type != row_ty->type || result_ty->columns != row_ty->size ||
+      result_ty->rows != col_ty->size) {
+    Fail() << "invalid outer product instruction: bad types "
+           << inst.PrettyPrint();
+    return {};
+  }
+
+  // Example:
+  //    c : vec3 column vector
+  //    r : vec2 row vector
+  //    OuterProduct c r : mat2x3 (2 columns, 3 rows)
+  //    Result:
+  //      | c.x * r.x   c.x * r.y |
+  //      | c.y * r.x   c.y * r.y |
+  //      | c.z * r.x   c.z * r.y |
+
+  ast::ExpressionList result_columns;
+  for (uint32_t icol = 0; icol < result_ty->columns; icol++) {
+    ast::ExpressionList result_row;
+    auto* row_factor = create<ast::MemberAccessorExpression>(Source{}, row.expr,
+                                                             Swizzle(icol));
+    for (uint32_t irow = 0; irow < result_ty->rows; irow++) {
+      auto* column_factor = create<ast::MemberAccessorExpression>(
+          Source{}, col.expr, Swizzle(irow));
+      auto* elem = create<ast::BinaryExpression>(
+          Source{}, ast::BinaryOp::kMultiply, row_factor, column_factor);
+      result_row.push_back(elem);
+    }
+    result_columns.push_back(
+        builder_.Construct(Source{}, col_ty->Build(builder_), result_row));
+  }
+  return {result_ty, builder_.Construct(Source{}, result_ty->Build(builder_),
+                                        result_columns)};
+}
+
+bool FunctionEmitter::MakeVectorInsertDynamic(
+    const spvtools::opt::Instruction& inst) {
+  // For
+  //    %result = OpVectorInsertDynamic %type %src_vector %component %index
+  // there are two cases.
+  //
+  // Case 1:
+  //   The %src_vector value has already been hoisted into a variable.
+  //   In this case, assign %src_vector to that variable, then write the
+  //   component into the right spot:
+  //
+  //    hoisted = src_vector;
+  //    hoisted[index] = component;
+  //
+  // Case 2:
+  //   The %src_vector value is not hoisted. In this case, make a temporary
+  //   variable with the %src_vector contents, then write the component,
+  //   and then make a let-declaration that reads the value out:
+  //
+  //    var temp : type = src_vector;
+  //    temp[index] = component;
+  //    let result : type = temp;
+  //
+  //   Then use result everywhere the original SPIR-V id is used.  Using a const
+  //   like this avoids constantly reloading the value many times.
+
+  auto* type = parser_impl_.ConvertType(inst.type_id());
+  auto src_vector = MakeOperand(inst, 0);
+  auto component = MakeOperand(inst, 1);
+  auto index = MakeOperand(inst, 2);
+
+  std::string var_name;
+  auto original_value_name = namer_.Name(inst.result_id());
+  const bool hoisted = WriteIfHoistedVar(inst, src_vector);
+  if (hoisted) {
+    // The variable was already declared in an earlier block.
+    var_name = original_value_name;
+    // Assign the source vector value to it.
+    builder_.Assign({}, builder_.Expr(var_name), src_vector.expr);
+  } else {
+    // Synthesize the temporary variable.
+    // It doesn't correspond to a SPIR-V ID, so we don't use the ordinary
+    // API in parser_impl_.
+    var_name = namer_.MakeDerivedName(original_value_name);
+
+    auto* temp_var = builder_.Var(var_name, type->Build(builder_),
+                                  ast::StorageClass::kNone, src_vector.expr);
+
+    AddStatement(builder_.Decl({}, temp_var));
+  }
+
+  auto* lhs = create<ast::IndexAccessorExpression>(
+      Source{}, builder_.Expr(var_name), index.expr);
+  if (!lhs) {
+    return false;
+  }
+
+  AddStatement(builder_.Assign(lhs, component.expr));
+
+  if (hoisted) {
+    // The hoisted variable itself stands for this result ID.
+    return success();
+  }
+  // Create a new let-declaration that is initialized by the contents
+  // of the temporary variable.
+  return EmitConstDefinition(inst, {type, builder_.Expr(var_name)});
+}
+
+bool FunctionEmitter::MakeCompositeInsert(
+    const spvtools::opt::Instruction& inst) {
+  // For
+  //    %result = OpCompositeInsert %type %object %composite 1 2 3 ...
+  // there are two cases.
+  //
+  // Case 1:
+  //   The %composite value has already been hoisted into a variable.
+  //   In this case, assign %composite to that variable, then write the
+  //   component into the right spot:
+  //
+  //    hoisted = composite;
+  //    hoisted[index].x = object;
+  //
+  // Case 2:
+  //   The %composite value is not hoisted. In this case, make a temporary
+  //   variable with the %composite contents, then write the component,
+  //   and then make a let-declaration that reads the value out:
+  //
+  //    var temp : type = composite;
+  //    temp[index].x = object;
+  //    let result : type = temp;
+  //
+  //   Then use result everywhere the original SPIR-V id is used.  Using a const
+  //   like this avoids constantly reloading the value many times.
+  //
+  //   This technique is a combination of:
+  //   - making a temporary variable and constant declaration, like what we do
+  //     for VectorInsertDynamic, and
+  //   - building up an access-chain like access like for CompositeExtract, but
+  //     on the left-hand side of the assignment.
+
+  auto* type = parser_impl_.ConvertType(inst.type_id());
+  auto component = MakeOperand(inst, 0);
+  auto src_composite = MakeOperand(inst, 1);
+
+  std::string var_name;
+  auto original_value_name = namer_.Name(inst.result_id());
+  const bool hoisted = WriteIfHoistedVar(inst, src_composite);
+  if (hoisted) {
+    // The variable was already declared in an earlier block.
+    var_name = original_value_name;
+    // Assign the source composite value to it.
+    builder_.Assign({}, builder_.Expr(var_name), src_composite.expr);
+  } else {
+    // Synthesize a temporary variable.
+    // It doesn't correspond to a SPIR-V ID, so we don't use the ordinary
+    // API in parser_impl_.
+    var_name = namer_.MakeDerivedName(original_value_name);
+    auto* temp_var = builder_.Var(var_name, type->Build(builder_),
+                                  ast::StorageClass::kNone, src_composite.expr);
+    AddStatement(builder_.Decl({}, temp_var));
+  }
+
+  TypedExpression seed_expr{type, builder_.Expr(var_name)};
+
+  // The left-hand side of the assignment *looks* like a decomposition.
+  TypedExpression lhs =
+      MakeCompositeValueDecomposition(inst, seed_expr, inst.type_id(), 2);
+  if (!lhs) {
+    return false;
+  }
+
+  AddStatement(builder_.Assign(lhs.expr, component.expr));
+
+  if (hoisted) {
+    // The hoisted variable itself stands for this result ID.
+    return success();
+  }
+  // Create a new let-declaration that is initialized by the contents
+  // of the temporary variable.
+  return EmitConstDefinition(inst, {type, builder_.Expr(var_name)});
+}
+
+TypedExpression FunctionEmitter::AddressOf(TypedExpression expr) {
+  auto* ref = expr.type->As<Reference>();
+  if (!ref) {
+    Fail() << "AddressOf() called on non-reference type";
+    return {};
+  }
+  return {
+      ty_.Pointer(ref->type, ref->storage_class),
+      create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kAddressOf,
+                                     expr.expr),
+  };
+}
+
+TypedExpression FunctionEmitter::Dereference(TypedExpression expr) {
+  auto* ptr = expr.type->As<Pointer>();
+  if (!ptr) {
+    Fail() << "Dereference() called on non-pointer type";
+    return {};
+  }
+  return {
+      ptr->type,
+      create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kIndirection,
+                                     expr.expr),
+  };
+}
+
+bool FunctionEmitter::IsFloatZero(uint32_t value_id) {
+  if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) {
+    if (const auto* float_const = c->AsFloatConstant()) {
+      return 0.0f == float_const->GetFloatValue();
+    }
+    if (c->AsNullConstant()) {
+      // Valid SPIR-V requires it to be a float value anyway.
+      return true;
+    }
+  }
+  return false;
+}
+
+bool FunctionEmitter::IsFloatOne(uint32_t value_id) {
+  if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) {
+    if (const auto* float_const = c->AsFloatConstant()) {
+      return 1.0f == float_const->GetFloatValue();
+    }
+  }
+  return false;
+}
+
+FunctionEmitter::FunctionDeclaration::FunctionDeclaration() = default;
+FunctionEmitter::FunctionDeclaration::~FunctionDeclaration() = default;
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::StatementBuilder);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::SwitchStatementBuilder);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::IfStatementBuilder);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::LoopStatementBuilder);
diff --git a/src/tint/reader/spirv/function.h b/src/tint/reader/spirv/function.h
new file mode 100644
index 0000000..46b36ca
--- /dev/null
+++ b/src/tint/reader/spirv/function.h
@@ -0,0 +1,1304 @@
+// Copyright 2020 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_READER_SPIRV_FUNCTION_H_
+#define SRC_TINT_READER_SPIRV_FUNCTION_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/spirv/construct.h"
+#include "src/tint/reader/spirv/parser_impl.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// Kinds of CFG edges.
+//
+// The edge kinds are used in many ways.
+//
+// For example, consider the edges leaving a basic block and going to distinct
+// targets. If the total number of kForward + kIfBreak + kCaseFallThrough edges
+// is more than 1, then the block must be a structured header, i.e. it needs
+// a merge instruction to declare the control flow divergence and associated
+// reconvergence point.  Those those edge kinds count toward divergence
+// because SPIR-v is designed to easily map back to structured control flow
+// in GLSL (and C).  In GLSL and C, those forward-flow edges don't have a
+// special statement to express them.  The other forward edges: kSwitchBreak,
+// kLoopBreak, and kLoopContinue directly map to 'break', 'break', and
+// 'continue', respectively.
+enum class EdgeKind {
+  // A back-edge: An edge from a node to one of its ancestors in a depth-first
+  // search from the entry block.
+  kBack,
+  // An edge from a node to the merge block of the nearest enclosing switch,
+  // where there is no intervening loop.
+  kSwitchBreak,
+  // An edge from a node to the merge block of the nearest enclosing loop, where
+  // there is no intervening switch.
+  // The source block is a "break block" as defined by SPIR-V.
+  kLoopBreak,
+  // An edge from a node in a loop body to the associated continue target, where
+  // there are no other intervening loops or switches.
+  // The source block is a "continue block" as defined by SPIR-V.
+  kLoopContinue,
+  // An edge from a node to the merge block of the nearest enclosing structured
+  // construct, but which is neither a kSwitchBreak or a kLoopBreak.
+  // This can only occur for an "if" selection, i.e. where the selection
+  // header ends in OpBranchConditional.
+  kIfBreak,
+  // An edge from one switch case to the next sibling switch case.
+  kCaseFallThrough,
+  // None of the above.
+  kForward
+};
+
+enum : uint32_t { kInvalidBlockPos = ~(0u) };
+
+/// Bookkeeping info for a basic block.
+struct BlockInfo {
+  /// Constructor
+  /// @param bb internal representation of the basic block
+  explicit BlockInfo(const spvtools::opt::BasicBlock& bb);
+  ~BlockInfo();
+
+  /// The internal representation of the basic block.
+  const spvtools::opt::BasicBlock* basic_block;
+
+  /// The ID of the OpLabel instruction that starts this block.
+  uint32_t id = 0;
+
+  /// The position of this block in the reverse structured post-order.
+  /// If the block is not in that order, then this remains the invalid value.
+  uint32_t pos = kInvalidBlockPos;
+
+  /// If this block is a header, then this is the ID of the merge block.
+  uint32_t merge_for_header = 0;
+  /// If this block is a loop header, then this is the ID of the continue
+  /// target.
+  uint32_t continue_for_header = 0;
+  /// If this block is a merge, then this is the ID of the header.
+  uint32_t header_for_merge = 0;
+  /// If this block is a continue target, then this is the ID of the loop
+  /// header.
+  uint32_t header_for_continue = 0;
+  /// Is this block a continue target which is its own loop header block?
+  /// In this case the continue construct is the entire loop.  The associated
+  /// "loop construct" is empty, and not represented.
+  bool is_continue_entire_loop = false;
+
+  /// The immediately enclosing structured construct. If this block is not
+  /// in the block order at all, then this is still nullptr.
+  const Construct* construct = nullptr;
+
+  /// Maps the ID of a successor block (in the CFG) to its edge classification.
+  std::unordered_map<uint32_t, EdgeKind> succ_edge;
+
+  /// The following fields record relationships among blocks in a selection
+  /// construct for an OpSwitch instruction.
+
+  /// If not null, then the pointed-at construct is a selection for an OpSwitch,
+  /// and this block is a case target for it.  We say this block "heads" the
+  /// case construct.
+  const Construct* case_head_for = nullptr;
+  /// If not null, then the pointed-at construct is a selection for an OpSwitch,
+  /// and this block is the default target for it.  We say this block "heads"
+  /// the default case construct.
+  const Construct* default_head_for = nullptr;
+  /// Is this a default target for a switch, and is it also the merge for its
+  /// switch?
+  bool default_is_merge = false;
+  /// The list of switch values that cause a branch to this block.
+  std::unique_ptr<std::vector<uint64_t>> case_values;
+
+  /// The following fields record relationships among blocks in a selection
+  /// construct for an OpBranchConditional instruction.
+
+  /// When this block is an if-selection header, this is the edge kind
+  /// for the true branch.
+  EdgeKind true_kind = EdgeKind::kForward;
+  /// When this block is an if-selection header, this is the edge kind
+  /// for the false branch.
+  EdgeKind false_kind = EdgeKind::kForward;
+  /// If not 0, then this block is an if-selection header, and `true_head` is
+  /// the target id of the true branch on the OpBranchConditional, and that
+  /// target is inside the if-selection.
+  uint32_t true_head = 0;
+  /// If not 0, then this block is an if-selection header, and `false_head`
+  /// is the target id of the false branch on the OpBranchConditional, and
+  /// that target is inside the if-selection.
+  uint32_t false_head = 0;
+  /// If not 0, then this block is an if-selection header, and when following
+  /// the flow via the true and false branches, control first reconverges at
+  /// the block with ID `premerge_head`, and `premerge_head` is still inside
+  /// the if-selection.
+  uint32_t premerge_head = 0;
+  /// If non-empty, then this block is an if-selection header, and control flow
+  /// in the body must be guarded by a boolean flow variable with this name.
+  /// This occurs when a block in this selection has both an if-break edge, and
+  /// also a different normal forward edge but without a merge instruction.
+  std::string flow_guard_name = "";
+
+  /// The result IDs that this block is responsible for declaring as a
+  /// hoisted variable.
+  /// @see DefInfo#requires_hoisted_def
+  std::vector<uint32_t> hoisted_ids;
+
+  /// A PhiAssignment represents the assignment of a value to the state
+  /// variable associated with an OpPhi in a successor block.
+  struct PhiAssignment {
+    /// The ID of an OpPhi receiving a value from this basic block.
+    uint32_t phi_id;
+    /// The the value carried to the given OpPhi.
+    uint32_t value;
+  };
+  /// If this basic block branches to a visited basic block containing phis,
+  /// then this is the list of writes to the variables associated those phis.
+  std::vector<PhiAssignment> phi_assignments;
+  /// The IDs of OpPhi instructions which require their associated state
+  /// variable to be declared in this basic block.
+  std::vector<uint32_t> phis_needing_state_vars;
+};
+
+inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
+  o << "BlockInfo{"
+    << " id: " << bi.id << " pos: " << bi.pos
+    << " merge_for_header: " << bi.merge_for_header
+    << " continue_for_header: " << bi.continue_for_header
+    << " header_for_merge: " << bi.header_for_merge
+    << " is_continue_entire_loop: " << int(bi.is_continue_entire_loop) << "}";
+  return o;
+}
+
+/// Reasons for avoiding generating an intermediate value.
+enum class SkipReason {
+  /// `kDontSkip`: The value should be generated. Used for most values.
+  kDontSkip,
+
+  /// For remaining cases, the value is not generated.
+
+  /// `kOpaqueObject`: used for any intermediate value which is an sampler,
+  /// image,
+  /// or sampled image, or any pointer to such object. Code is generated
+  /// for those objects only when emitting the image instructions that access
+  /// the image (read, write, sample, gather, fetch, or query). For example,
+  /// when encountering an OpImageSampleExplicitLod, a call to the
+  /// textureSampleLevel builtin function will be emitted, and the call will
+  /// directly reference the underlying texture and sampler (variable or
+  /// function parameter).
+  kOpaqueObject,
+
+  /// `kSinkPointerIntoUse`: used to avoid emitting certain pointer expressions,
+  /// by instead generating their reference expression directly at the point of
+  /// use. For example, we apply this to OpAccessChain when indexing into a
+  /// vector, to avoid generating address-of vector component expressions.
+  kSinkPointerIntoUse,
+
+  /// `kPointSizeBuiltinPointer`: the value is a pointer to the Position builtin
+  /// variable.  Don't generate its address.  Avoid generating stores to this
+  /// pointer.
+  kPointSizeBuiltinPointer,
+  /// `kPointSizeBuiltinValue`: the value is the value loaded from the
+  /// PointSize builtin. Use 1.0f instead, because that's the only value
+  /// supported by WebGPU.
+  kPointSizeBuiltinValue,
+
+  /// `kSampleMaskInBuiltinPointer`: the value is a pointer to the SampleMaskIn
+  /// builtin input variable.  Don't generate its address.
+  kSampleMaskInBuiltinPointer,
+
+  /// `kSampleMaskOutBuiltinPointer`: the value is a pointer to the SampleMask
+  /// builtin output variable.
+  kSampleMaskOutBuiltinPointer,
+};
+
+/// Bookkeeping info for a SPIR-V ID defined in the function, or some
+/// module-scope variables. This will be valid for result IDs that are:
+/// - defined in the function and:
+///    - instructions that are not OpLabel, and not OpFunctionParameter
+///    - are defined in a basic block visited in the block-order for the
+///    function.
+/// - certain module-scope builtin variables.
+struct DefInfo {
+  /// Constructor.
+  /// @param def_inst the SPIR-V instruction defining the ID
+  /// @param block_pos the position of the basic block where the ID is defined.
+  /// @param index an ordering index for this local definition
+  DefInfo(const spvtools::opt::Instruction& def_inst,
+          uint32_t block_pos,
+          size_t index);
+  /// Destructor.
+  ~DefInfo();
+
+  /// The SPIR-V instruction that defines the ID.
+  const spvtools::opt::Instruction& inst;
+  /// The position of the first block in which this ID is visible, in function
+  /// block order.  For IDs defined outside of the function, it is 0.
+  /// For IDs defined in the function, it is the position of the block
+  /// containing the definition of the ID.
+  /// See method `FunctionEmitter::ComputeBlockOrderAndPositions`
+  const uint32_t block_pos = 0;
+
+  /// An index for uniquely and deterministically ordering all DefInfo records
+  /// in a function.
+  const size_t index = 0;
+
+  /// The number of uses of this ID.
+  uint32_t num_uses = 0;
+
+  /// The block position of the last use of this ID, or 0 if it is not used
+  /// at all.  The "last" ordering is determined by the function block order.
+  uint32_t last_use_pos = 0;
+
+  /// Is this value used in a construct other than the one in which it was
+  /// defined?
+  bool used_in_another_construct = false;
+
+  /// True if this ID requires a WGSL 'const' definition, due to context. It
+  /// might get one anyway (so this is *not* an if-and-only-if condition).
+  bool requires_named_const_def = false;
+
+  /// True if this ID must map to a WGSL variable declaration before the
+  /// corresponding position of the ID definition in SPIR-V.  This compensates
+  /// for the difference between dominance and scoping. An SSA definition can
+  /// dominate all its uses, but the construct where it is defined does not
+  /// enclose all the uses, and so if it were declared as a WGSL constant
+  /// definition at the point of its SPIR-V definition, then the WGSL name
+  /// would go out of scope too early. Fix that by creating a variable at the
+  /// top of the smallest construct that encloses both the definition and all
+  /// its uses. Then the original SPIR-V definition maps to a WGSL assignment
+  /// to that variable, and each SPIR-V use becomes a WGSL read from the
+  /// variable.
+  /// TODO(dneto): This works for constants of storable type, but not, for
+  /// example, pointers. crbug.com/tint/98
+  bool requires_hoisted_def = false;
+
+  /// If the definition is an OpPhi, then `phi_var` is the name of the
+  /// variable that stores the value carried from parent basic blocks into
+  /// the basic block containing the OpPhi. Otherwise this is the empty string.
+  std::string phi_var;
+
+  /// The storage class to use for this value, if it is of pointer type.
+  /// This is required to carry a storage class override from a storage
+  /// buffer expressed in the old style (with Uniform storage class)
+  /// that needs to be remapped to StorageBuffer storage class.
+  /// This is kInvalid for non-pointers.
+  ast::StorageClass storage_class = ast::StorageClass::kInvalid;
+
+  /// The expression to use when sinking pointers into their use.
+  /// When encountering a use of this instruction, we will emit this expression
+  /// instead.
+  TypedExpression sink_pointer_source_expr = {};
+
+  /// The reason, if any, that this value should be ignored.
+  /// Normally no values are ignored.  This field can be updated while
+  /// generating code because sometimes we only discover necessary facts
+  /// in the middle of generating code.
+  SkipReason skip = SkipReason::kDontSkip;
+};
+
+inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) {
+  o << "DefInfo{"
+    << " inst.result_id: " << di.inst.result_id()
+    << " block_pos: " << di.block_pos << " num_uses: " << di.num_uses
+    << " last_use_pos: " << di.last_use_pos << " requires_named_const_def: "
+    << (di.requires_named_const_def ? "true" : "false")
+    << " requires_hoisted_def: " << (di.requires_hoisted_def ? "true" : "false")
+    << " phi_var: '" << di.phi_var << "'";
+  if (di.storage_class != ast::StorageClass::kNone) {
+    o << " sc:" << int(di.storage_class);
+  }
+  switch (di.skip) {
+    case SkipReason::kDontSkip:
+      break;
+    case SkipReason::kOpaqueObject:
+      o << " skip:opaque";
+      break;
+    case SkipReason::kSinkPointerIntoUse:
+      o << " skip:sink_pointer";
+      break;
+    case SkipReason::kPointSizeBuiltinPointer:
+      o << " skip:pointsize_pointer";
+      break;
+    case SkipReason::kPointSizeBuiltinValue:
+      o << " skip:pointsize_value";
+      break;
+    case SkipReason::kSampleMaskInBuiltinPointer:
+      o << " skip:samplemaskin_pointer";
+      break;
+    case SkipReason::kSampleMaskOutBuiltinPointer:
+      o << " skip:samplemaskout_pointer";
+      break;
+  }
+  o << "}";
+  return o;
+}
+
+/// A placeholder Statement that exists for the duration of building a
+/// StatementBlock. Once the StatementBlock is built, Build() will be called to
+/// construct the final AST node, which will be used in the place of this
+/// StatementBuilder.
+/// StatementBuilders are used to simplify construction of AST nodes that will
+/// become immutable. The builders may hold mutable state while the
+/// StatementBlock is being constructed, which becomes an immutable node on
+/// StatementBlock::Finalize().
+class StatementBuilder : public Castable<StatementBuilder, ast::Statement> {
+ public:
+  /// Constructor
+  StatementBuilder() : Base(ProgramID(), Source{}) {}
+
+  /// @param builder the program builder
+  /// @returns the build AST node
+  virtual const ast::Statement* Build(ProgramBuilder* builder) const = 0;
+
+ private:
+  Node* Clone(CloneContext*) const override;
+};
+
+/// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
+class FunctionEmitter {
+ public:
+  /// Creates a FunctionEmitter, and prepares to write to the AST module
+  /// in `pi`
+  /// @param pi a ParserImpl which has already executed BuildInternalModule
+  /// @param function the function to emit
+  FunctionEmitter(ParserImpl* pi, const spvtools::opt::Function& function);
+  /// Creates a FunctionEmitter, and prepares to write to the AST module
+  /// in `pi`
+  /// @param pi a ParserImpl which has already executed BuildInternalModule
+  /// @param function the function to emit
+  /// @param ep_info entry point information for this function, or nullptr
+  FunctionEmitter(ParserImpl* pi,
+                  const spvtools::opt::Function& function,
+                  const EntryPointInfo* ep_info);
+  /// Move constructor. Only valid when the other object was newly created.
+  /// @param other the emitter to clone
+  FunctionEmitter(FunctionEmitter&& other);
+  /// Destructor
+  ~FunctionEmitter();
+
+  /// Emits the function to AST module.
+  /// @return whether emission succeeded
+  bool Emit();
+
+  /// @returns true if emission has not yet failed.
+  bool success() const { return fail_stream_.status(); }
+  /// @returns true if emission has failed.
+  bool failed() const { return !success(); }
+
+  /// Finalizes any StatementBuilders returns the body of the function.
+  /// Must only be called once, and to be used only for testing.
+  /// @returns the body of the function.
+  const ast::StatementList ast_body();
+
+  /// Records failure.
+  /// @returns a FailStream on which to emit diagnostics.
+  FailStream& Fail() { return fail_stream_.Fail(); }
+
+  /// @returns the parser implementation
+  ParserImpl* parser() { return &parser_impl_; }
+
+  /// Emits the entry point as a wrapper around its implementation function.
+  /// Pipeline inputs become formal parameters, and pipeline outputs become
+  /// return values.
+  /// @returns false if emission failed.
+  bool EmitEntryPointAsWrapper();
+
+  /// Creates one or more entry point input parameters corresponding to a
+  /// part of an input variable.  The part of the input variable is specfied
+  /// by the `index_prefix`, which successively indexes into the variable.
+  /// Also generates the assignment statements that copy the input parameter
+  /// to the corresponding part of the variable.  Assumes the variable
+  /// has already been created in the Private storage class.
+  /// @param var_name The name of the variable
+  /// @param var_type The store type of the variable
+  /// @param decos The variable's decorations
+  /// @param index_prefix Indices stepping into the variable, indicating
+  /// what part of the variable to populate.
+  /// @param tip_type The type of the component inside variable, after indexing
+  /// with the indices in `index_prefix`.
+  /// @param forced_param_type The type forced by WGSL, if the variable is a
+  /// builtin, otherwise the same as var_type.
+  /// @param params The parameter list where the new parameter is appended.
+  /// @param statements The statement list where the assignment is appended.
+  /// @returns false if emission failed
+  bool EmitPipelineInput(std::string var_name,
+                         const Type* var_type,
+                         ast::AttributeList* decos,
+                         std::vector<int> index_prefix,
+                         const Type* tip_type,
+                         const Type* forced_param_type,
+                         ast::VariableList* params,
+                         ast::StatementList* statements);
+
+  /// Creates one or more struct members from an output variable, and the
+  /// expressions that compute the value they contribute to the entry point
+  /// return value.  The part of the output variable is specfied
+  /// by the `index_prefix`, which successively indexes into the variable.
+  /// Assumes the variable has already been created in the Private storage
+  /// class.
+  /// @param var_name The name of the variable
+  /// @param var_type The store type of the variable
+  /// @param decos The variable's decorations
+  /// @param index_prefix Indices stepping into the variable, indicating
+  /// what part of the variable to populate.
+  /// @param tip_type The type of the component inside variable, after indexing
+  /// with the indices in `index_prefix`.
+  /// @param forced_member_type The type forced by WGSL, if the variable is a
+  /// builtin, otherwise the same as var_type.
+  /// @param return_members The struct member list where the new member is
+  /// added.
+  /// @param return_exprs The expression list where the return expression is
+  /// added.
+  /// @returns false if emission failed
+  bool EmitPipelineOutput(std::string var_name,
+                          const Type* var_type,
+                          ast::AttributeList* decos,
+                          std::vector<int> index_prefix,
+                          const Type* tip_type,
+                          const Type* forced_member_type,
+                          ast::StructMemberList* return_members,
+                          ast::ExpressionList* return_exprs);
+
+  /// Updates the attribute list, replacing an existing Location attribute
+  /// with another having one higher location value. Does nothing if no
+  /// location attribute exists.
+  /// Assumes the list contains at most one Location attribute.
+  /// @param attributes the attribute list to modify
+  void IncrementLocation(ast::AttributeList* attributes);
+
+  /// Returns the Location attribute, if it exists.
+  /// @param attributes the list of attributes to search
+  /// @returns the Location attribute, or nullptr if it doesn't exist
+  const ast::Attribute* GetLocation(const ast::AttributeList& attributes);
+
+  /// Create an ast::BlockStatement representing the body of the function.
+  /// This creates the statement stack, which is non-empty for the lifetime
+  /// of the function.
+  /// @returns the body of the function, or null on error
+  const ast::BlockStatement* MakeFunctionBody();
+
+  /// Emits the function body, populating the bottom entry of the statements
+  /// stack.
+  /// @returns false if emission failed.
+  bool EmitBody();
+
+  /// Records a mapping from block ID to a BlockInfo struct.
+  /// Populates `block_info_`
+  void RegisterBasicBlocks();
+
+  /// Verifies that terminators only branch to labels in the current function.
+  /// Assumes basic blocks have been registered.
+  /// @returns true if terminators are valid
+  bool TerminatorsAreValid();
+
+  /// Populates merge-header cross-links and BlockInfo#is_continue_entire_loop.
+  /// Also verifies that merge instructions go to blocks in the same function.
+  /// Assumes basic blocks have been registered, and terminators are valid.
+  /// @returns false if registration fails
+  bool RegisterMerges();
+
+  /// Determines the output order for the basic blocks in the function.
+  /// Populates `block_order_` and BlockInfo#pos.
+  /// Assumes basic blocks have been registered.
+  void ComputeBlockOrderAndPositions();
+
+  /// @returns the reverse structured post order of the basic blocks in
+  /// the function.
+  const std::vector<uint32_t>& block_order() const { return block_order_; }
+
+  /// Verifies that the orderings among a structured header, continue target,
+  /// and merge block are valid. Assumes block order has been computed, and
+  /// merges are valid and recorded.
+  /// @returns false if invalid nesting was detected
+  bool VerifyHeaderContinueMergeOrder();
+
+  /// Labels each basic block with its nearest enclosing structured construct.
+  /// Populates BlockInfo#construct and the `constructs_` list.
+  /// Assumes terminators are valid and merges have been registered, block
+  /// order has been computed, and each block is labeled with its position.
+  /// Checks nesting of structured control flow constructs.
+  /// @returns false if bad nesting has been detected
+  bool LabelControlFlowConstructs();
+
+  /// @returns the structured constructs
+  const ConstructList& constructs() const { return constructs_; }
+
+  /// Marks blocks targets of a switch, either as the head of a case or
+  /// as the default target.
+  /// @returns false on failure
+  bool FindSwitchCaseHeaders();
+
+  /// Classifies the successor CFG edges for the ordered basic blocks.
+  /// Also checks validity of each edge (populates BlockInfo#succ_edge).
+  /// Implicitly checks dominance rules for headers and continue constructs.
+  /// Assumes each block has been labeled with its control flow construct.
+  /// @returns false on failure
+  bool ClassifyCFGEdges();
+
+  /// Marks the blocks within a selection construct that are the first blocks
+  /// in the "then" clause, the "else" clause, and the "premerge" clause.
+  /// The head of the premerge clause is the block, if it exists, at which
+  /// control flow reconverges from the "then" and "else" clauses, but before
+  /// before the merge block for that selection.   The existence of a premerge
+  /// should be an exceptional case, but is allowed by the structured control
+  /// flow rules.
+  /// @returns false if bad nesting has been detected.
+  bool FindIfSelectionInternalHeaders();
+
+  /// Creates a DefInfo record for each module-scope builtin variable
+  /// that should be handled specially.  Either it's ignored, or its store
+  /// type is converted on load.
+  /// Populates the `def_info_` mapping for such IDs.
+  /// @returns false on failure
+  bool RegisterSpecialBuiltInVariables();
+
+  /// Creates a DefInfo record for each locally defined SPIR-V ID.
+  /// Populates the `def_info_` mapping with basic results for such IDs.
+  /// @returns false on failure
+  bool RegisterLocallyDefinedValues();
+
+  /// Returns the Tint storage class for the given SPIR-V ID that is a
+  /// pointer value.
+  /// @param id a SPIR-V ID for a pointer value
+  /// @returns the storage class
+  ast::StorageClass GetStorageClassForPointerValue(uint32_t id);
+
+  /// Remaps the storage class for the type of a locally-defined value,
+  /// if necessary. If it's not a pointer type, or if its storage class
+  /// already matches, then the result is a copy of the `type` argument.
+  /// @param type the AST type
+  /// @param result_id the SPIR-V ID for the locally defined value
+  /// @returns an possibly updated type
+  const Type* RemapStorageClass(const Type* type, uint32_t result_id);
+
+  /// Marks locally defined values when they should get a 'const'
+  /// definition in WGSL, or a 'var' definition at an outer scope.
+  /// This occurs in several cases:
+  ///  - When a SPIR-V instruction might use the dynamically computed value
+  ///    only once, but the WGSL code might reference it multiple times.
+  ///    For example, this occurs for the vector operands of OpVectorShuffle.
+  ///    In this case the definition's DefInfo#requires_named_const_def property
+  ///    is set to true.
+  ///  - When a definition and at least one of its uses are not in the
+  ///    same structured construct.
+  ///    In this case the definition's DefInfo#requires_named_const_def property
+  ///    is set to true.
+  ///  - When a definition is in a construct that does not enclose all the
+  ///    uses.  In this case the definition's DefInfo#requires_hoisted_def
+  ///    property is set to true.
+  /// Updates the `def_info_` mapping.
+  void FindValuesNeedingNamedOrHoistedDefinition();
+
+  /// Emits declarations of function variables.
+  /// @returns false if emission failed.
+  bool EmitFunctionVariables();
+
+  /// Emits statements in the body.
+  /// @returns false if emission failed.
+  bool EmitFunctionBodyStatements();
+
+  /// Emits a basic block.
+  /// @param block_info the block to emit
+  /// @returns false if emission failed.
+  bool EmitBasicBlock(const BlockInfo& block_info);
+
+  /// Emits an IfStatement, including its condition expression, and sets
+  /// up the statement stack to accumulate subsequent basic blocks into
+  /// the "then" and "else" clauses.
+  /// @param block_info the if-selection header block
+  /// @returns false if emission failed.
+  bool EmitIfStart(const BlockInfo& block_info);
+
+  /// Emits a SwitchStatement, including its condition expression, and sets
+  /// up the statement stack to accumulate subsequent basic blocks into
+  /// the default clause and case clauses.
+  /// @param block_info the switch-selection header block
+  /// @returns false if emission failed.
+  bool EmitSwitchStart(const BlockInfo& block_info);
+
+  /// Emits a LoopStatement, and pushes a new StatementBlock to accumulate
+  /// the remaining instructions in the current block and subsequent blocks
+  /// in the loop.
+  /// @param construct the loop construct
+  /// @returns false if emission failed.
+  bool EmitLoopStart(const Construct* construct);
+
+  /// Emits a ContinuingStatement, and pushes a new StatementBlock to accumulate
+  /// the remaining instructions in the current block and subsequent blocks
+  /// in the continue construct.
+  /// @param construct the continue construct
+  /// @returns false if emission failed.
+  bool EmitContinuingStart(const Construct* construct);
+
+  /// Emits the non-control-flow parts of a basic block, but only once.
+  /// The `already_emitted` parameter indicates whether the code has already
+  /// been emitted, and is used to signal that this invocation actually emitted
+  /// it.
+  /// @param block_info the block to emit
+  /// @param already_emitted the block to emit
+  /// @returns false if the code had not yet been emitted, but emission failed
+  bool EmitStatementsInBasicBlock(const BlockInfo& block_info,
+                                  bool* already_emitted);
+
+  /// Emits code for terminators, but that aren't part of entering or
+  /// resolving structured control flow. That is, if the basic block
+  /// terminator calls for it, emit the fallthrough, break, continue, return,
+  /// or kill commands.
+  /// @param block_info the block with the terminator to emit (if any)
+  /// @returns false if emission failed
+  bool EmitNormalTerminator(const BlockInfo& block_info);
+
+  /// Returns a new statement to represent the given branch representing a
+  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
+  /// WGSL statement is required, the statement will be nullptr. This method
+  /// tries to avoid emitting a 'break' statement when that would be redundant
+  /// in WGSL due to implicit breaking out of a switch.
+  /// @param src_info the source block
+  /// @param dest_info the destination block
+  /// @returns the new statement, or a null statement
+  const ast::Statement* MakeBranch(const BlockInfo& src_info,
+                                   const BlockInfo& dest_info) const {
+    return MakeBranchDetailed(src_info, dest_info, false, nullptr);
+  }
+
+  /// Returns a new statement to represent the given branch representing a
+  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
+  /// WGSL statement is required, the statement will be nullptr.
+  /// @param src_info the source block
+  /// @param dest_info the destination block
+  /// @returns the new statement, or a null statement
+  const ast::Statement* MakeForcedBranch(const BlockInfo& src_info,
+                                         const BlockInfo& dest_info) const {
+    return MakeBranchDetailed(src_info, dest_info, true, nullptr);
+  }
+
+  /// Returns a new statement to represent the given branch representing a
+  /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
+  /// WGSL statement is required, the statement will be nullptr. When `forced`
+  /// is false, this method tries to avoid emitting a 'break' statement when
+  /// that would be redundant in WGSL due to implicit breaking out of a switch.
+  /// When `forced` is true, the method won't try to avoid emitting that break.
+  /// If the control flow edge is an if-break for an if-selection with a
+  /// control flow guard, then return that guard name via `flow_guard_name_ptr`
+  /// when that parameter is not null.
+  /// @param src_info the source block
+  /// @param dest_info the destination block
+  /// @param forced if true, always emit the branch (if it exists in WGSL)
+  /// @param flow_guard_name_ptr return parameter for control flow guard name
+  /// @returns the new statement, or a null statement
+  const ast::Statement* MakeBranchDetailed(
+      const BlockInfo& src_info,
+      const BlockInfo& dest_info,
+      bool forced,
+      std::string* flow_guard_name_ptr) const;
+
+  /// Returns a new if statement with the given statements as the then-clause
+  /// and the else-clause.  Either or both clauses might be nullptr. If both
+  /// are nullptr, then don't make a new statement and instead return nullptr.
+  /// @param condition the branching condition
+  /// @param then_stmt the statement for the then clause of the if, or nullptr
+  /// @param else_stmt the statement for the else clause of the if, or nullptr
+  /// @returns the new statement, or nullptr
+  const ast::Statement* MakeSimpleIf(const ast::Expression* condition,
+                                     const ast::Statement* then_stmt,
+                                     const ast::Statement* else_stmt) const;
+
+  /// Emits the statements for an normal-terminator OpBranchConditional
+  /// where one branch is a case fall through (the true branch if and only
+  /// if `fall_through_is_true_branch` is true), and the other branch is
+  /// goes to a different destination, named by `other_dest`.
+  /// @param src_info the basic block from which we're branching
+  /// @param cond the branching condition
+  /// @param other_edge_kind the edge kind from the source block to the other
+  /// destination
+  /// @param other_dest the other branching destination
+  /// @param fall_through_is_true_branch true when the fall-through is the true
+  /// branch
+  /// @returns the false if emission fails
+  bool EmitConditionalCaseFallThrough(const BlockInfo& src_info,
+                                      const ast::Expression* cond,
+                                      EdgeKind other_edge_kind,
+                                      const BlockInfo& other_dest,
+                                      bool fall_through_is_true_branch);
+
+  /// Emits a normal instruction: not a terminator, label, or variable
+  /// declaration.
+  /// @param inst the instruction
+  /// @returns false if emission failed.
+  bool EmitStatement(const spvtools::opt::Instruction& inst);
+
+  /// Emits a const definition for the typed value in `ast_expr`, and
+  /// records it as the translation for the result ID from `inst`.
+  /// @param inst the SPIR-V instruction defining the value
+  /// @param ast_expr the already-computed AST expression for the value
+  /// @returns false if emission failed.
+  bool EmitConstDefinition(const spvtools::opt::Instruction& inst,
+                           TypedExpression ast_expr);
+
+  /// Emits a write of the typed value in `ast_expr` to a hoisted variable
+  /// for the given SPIR-V ID, if that ID has a hoisted declaration. Otherwise,
+  /// emits a const definition instead.
+  /// @param inst the SPIR-V instruction defining the value
+  /// @param ast_expr the already-computed AST expression for the value
+  /// @returns false if emission failed.
+  bool EmitConstDefOrWriteToHoistedVar(const spvtools::opt::Instruction& inst,
+                                       TypedExpression ast_expr);
+
+  /// If the result ID of the given instruction is hoisted, then emits
+  /// a statement to write the expression to the hoisted variable, and
+  /// returns true.  Otherwise return false.
+  /// @param inst the SPIR-V instruction defining a value.
+  /// @param ast_expr the expression to assign.
+  /// @returns true if the instruction has an associated hoisted variable.
+  bool WriteIfHoistedVar(const spvtools::opt::Instruction& inst,
+                         TypedExpression ast_expr);
+
+  /// Makes an expression from a SPIR-V ID.
+  /// if the SPIR-V result type is a pointer.
+  /// @param id the SPIR-V ID of the value
+  /// @returns an AST expression for the instruction, or an invalid
+  /// TypedExpression on error.
+  TypedExpression MakeExpression(uint32_t id);
+
+  /// Creates an expression and supporting statements for a combinatorial
+  /// instruction, or returns null.  A SPIR-V instruction is combinatorial
+  /// if it has no side effects and its result depends only on its operands,
+  /// and not on accessing external state like memory or the state of other
+  /// invocations.  Statements are only created if required to provide values
+  /// to the expression. Supporting statements are not required to be
+  /// combinatorial.
+  /// @param inst a SPIR-V instruction representing an exrpression
+  /// @returns an AST expression for the instruction, or nullptr.
+  TypedExpression MaybeEmitCombinatorialValue(
+      const spvtools::opt::Instruction& inst);
+
+  /// Creates an expression and supporting statements for the a GLSL.std.450
+  /// extended instruction.
+  /// @param inst a SPIR-V OpExtInst instruction from GLSL.std.450
+  /// @returns an AST expression for the instruction, or nullptr.
+  TypedExpression EmitGlslStd450ExtInst(const spvtools::opt::Instruction& inst);
+
+  /// Creates an expression for OpCompositeExtract
+  /// @param inst an OpCompositeExtract instruction.
+  /// @returns an AST expression for the instruction, or nullptr.
+  TypedExpression MakeCompositeExtract(const spvtools::opt::Instruction& inst);
+
+  /// Creates an expression for indexing into a composite value.  The literal
+  /// indices that step into the value start at instruction input operand
+  /// `start_index` and run to the end of the instruction.
+  /// @param inst the original instruction
+  /// @param composite the typed expression for the composite
+  /// @param composite_type_id the SPIR-V type ID for the composite
+  /// @param index_start the index of the first operand in `inst` that is an
+  /// index into the composite type
+  /// @returns an AST expression for the decomposed composite, or {} on error
+  TypedExpression MakeCompositeValueDecomposition(
+      const spvtools::opt::Instruction& inst,
+      TypedExpression composite,
+      uint32_t composite_type_id,
+      int index_start);
+
+  /// Creates an expression for OpVectorShuffle
+  /// @param inst an OpVectorShuffle instruction.
+  /// @returns an AST expression for the instruction, or nullptr.
+  TypedExpression MakeVectorShuffle(const spvtools::opt::Instruction& inst);
+
+  /// Creates an expression for a numeric conversion.
+  /// @param inst a numeric conversion instruction
+  /// @returns an AST expression for the instruction, or nullptr.
+  TypedExpression MakeNumericConversion(const spvtools::opt::Instruction& inst);
+
+  /// Gets the block info for a block ID, if any exists
+  /// @param id the SPIR-V ID of the OpLabel instruction starting the block
+  /// @returns the block info for the given ID, if it exists, or nullptr
+  BlockInfo* GetBlockInfo(uint32_t id) const {
+    auto where = block_info_.find(id);
+    if (where == block_info_.end()) {
+      return nullptr;
+    }
+    return where->second.get();
+  }
+
+  /// Is the block, represented by info, in the structured block order?
+  /// @param info the block
+  /// @returns true if the block is in the structured block order.
+  bool IsInBlockOrder(const BlockInfo* info) const {
+    return info && info->pos != kInvalidBlockPos;
+  }
+
+  /// Gets the local definition info for a result ID.
+  /// @param id the SPIR-V ID of local definition.
+  /// @returns the definition info for the given ID, if it exists, or nullptr
+  DefInfo* GetDefInfo(uint32_t id) const {
+    auto where = def_info_.find(id);
+    if (where == def_info_.end()) {
+      return nullptr;
+    }
+    return where->second.get();
+  }
+  /// Returns the skip reason for a result ID.
+  /// @param id SPIR-V result ID
+  /// @returns the skip reason for the given ID, or SkipReason::kDontSkip
+  SkipReason GetSkipReason(uint32_t id) const {
+    if (auto* def_info = GetDefInfo(id)) {
+      return def_info->skip;
+    }
+    return SkipReason::kDontSkip;
+  }
+
+  /// Returns the most deeply nested structured construct which encloses the
+  /// WGSL scopes of names declared in both block positions. Each position must
+  /// be a valid index into the function block order array.
+  /// @param first_pos the first block position
+  /// @param last_pos the last block position
+  /// @returns the smallest construct containing both positions
+  const Construct* GetEnclosingScope(uint32_t first_pos,
+                                     uint32_t last_pos) const;
+
+  /// Finds loop construct associated with a continue construct, if it exists.
+  /// Returns nullptr if:
+  ///  - the given construct is not a continue construct
+  ///  - the continue construct does not have an associated loop construct
+  ///    (the continue target is also the loop header block)
+  /// @param c the continue construct
+  /// @returns the associated loop construct, or nullptr
+  const Construct* SiblingLoopConstruct(const Construct* c) const;
+
+  /// Returns an identifier expression for the swizzle name of the given
+  /// index into a vector.  Emits an error and returns nullptr if the
+  /// index is out of range, i.e. 4 or higher.
+  /// @param i index of the subcomponent
+  /// @returns the identifier expression for the `i`'th component
+  ast::IdentifierExpression* Swizzle(uint32_t i);
+
+  /// Returns an identifier expression for the swizzle name of the first
+  /// `n` elements of a vector.  Emits an error and returns nullptr if `n`
+  /// is out of range, i.e. 4 or higher.
+  /// @param n the number of components in the swizzle
+  /// @returns the swizzle identifier for the first n elements of a vector
+  ast::IdentifierExpression* PrefixSwizzle(uint32_t n);
+
+  /// Converts SPIR-V image coordinates from an image access instruction
+  /// (e.g. OpImageSampledImplicitLod) into an expression list consisting of
+  /// the texture coordinates, and an integral array index if the texture is
+  /// arrayed. The texture coordinate is a scalar for 1D textures, a vector of
+  /// 2 elements for a 2D texture, and a vector of 3 elements for a 3D or
+  /// Cube texture. Excess components are ignored, e.g. if the SPIR-V
+  /// coordinate is a 4-element vector but the image is a 2D non-arrayed
+  /// texture then the 3rd and 4th components are ignored.
+  /// On failure, issues an error and returns an empty expression list.
+  /// @param image_access the image access instruction
+  /// @returns an ExpressionList of the coordinate and array index (if any)
+  ast::ExpressionList MakeCoordinateOperandsForImageAccess(
+      const spvtools::opt::Instruction& image_access);
+
+  /// Returns the given value as an I32.  If it's already an I32 then this
+  /// return the given value.  Otherwise, wrap the value in a TypeConstructor
+  /// expression.
+  /// @param value the value to pass through or convert
+  /// @returns the value as an I32 value.
+  TypedExpression ToI32(TypedExpression value);
+
+  /// Returns the given value as a signed integer type of the same shape
+  /// if the value is unsigned scalar or vector, by wrapping the value
+  /// with a TypeConstructor expression.  Returns the value itself if the
+  /// value otherwise.
+  /// @param value the value to pass through or convert
+  /// @returns the value itself, or converted to signed integral
+  TypedExpression ToSignedIfUnsigned(TypedExpression value);
+
+  /// @param value_id the value identifier to check
+  /// @returns true if the given SPIR-V id represents a constant float 0.
+  bool IsFloatZero(uint32_t value_id);
+  /// @param value_id the value identifier to check
+  /// @returns true if the given SPIR-V id represents a constant float 1.
+  bool IsFloatOne(uint32_t value_id);
+
+ private:
+  /// FunctionDeclaration contains the parsed information for a function header.
+  struct FunctionDeclaration {
+    /// Constructor
+    FunctionDeclaration();
+    /// Destructor
+    ~FunctionDeclaration();
+
+    /// Parsed header source
+    Source source;
+    /// Function name
+    std::string name;
+    /// Function parameters
+    ast::VariableList params;
+    /// Function return type
+    const Type* return_type;
+    /// Function attributes
+    ast::AttributeList attributes;
+  };
+
+  /// Parse the function declaration, which comprises the name, parameters, and
+  /// return type, populating `decl`.
+  /// @param decl the FunctionDeclaration to populate
+  /// @returns true if emission has not yet failed.
+  bool ParseFunctionDeclaration(FunctionDeclaration* decl);
+
+  /// @returns the store type for the OpVariable instruction, or
+  /// null on failure.
+  const Type* GetVariableStoreType(
+      const spvtools::opt::Instruction& var_decl_inst);
+
+  /// Returns an expression for an instruction operand. Signedness conversion is
+  /// performed to match the result type of the SPIR-V instruction.
+  /// @param inst the SPIR-V instruction
+  /// @param operand_index the index of the operand, counting 0 as the first
+  /// input operand
+  /// @returns a new expression node
+  TypedExpression MakeOperand(const spvtools::opt::Instruction& inst,
+                              uint32_t operand_index);
+
+  /// Copies a typed expression to the result, but when the type is a pointer
+  /// or reference type, ensures the storage class is not defaulted.  That is,
+  /// it changes a storage class of "none" to "function".
+  /// @param expr a typed expression
+  /// @results a copy of the expression, with possibly updated type
+  TypedExpression InferFunctionStorageClass(TypedExpression expr);
+
+  /// Returns an expression for a SPIR-V OpFMod instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeFMod(const spvtools::opt::Instruction& inst);
+
+  /// Returns an expression for a SPIR-V OpAccessChain or OpInBoundsAccessChain
+  /// instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeAccessChain(const spvtools::opt::Instruction& inst);
+
+  /// Emits a function call.  On failure, emits a diagnostic and returns false.
+  /// @param inst the SPIR-V function call instruction
+  /// @returns false if emission failed
+  bool EmitFunctionCall(const spvtools::opt::Instruction& inst);
+
+  /// Emits a control barrier builtin.  On failure, emits a diagnostic and
+  /// returns false.
+  /// @param inst the SPIR-V control barrier instruction
+  /// @returns false if emission failed
+  bool EmitControlBarrier(const spvtools::opt::Instruction& inst);
+
+  /// Returns an expression for a SPIR-V instruction that maps to a WGSL
+  /// builtin function call.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeBuiltinCall(const spvtools::opt::Instruction& inst);
+
+  /// Returns an expression for a SPIR-V OpArrayLength instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeArrayLength(const spvtools::opt::Instruction& inst);
+
+  /// Generates an expression for a SPIR-V OpOuterProduct instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeOuterProduct(const spvtools::opt::Instruction& inst);
+
+  /// Generates statements for a SPIR-V OpVectorInsertDynamic instruction.
+  /// Registers a const declaration for the result.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst);
+
+  /// Generates statements for a SPIR-V OpComposite instruction.
+  /// Registers a const declaration for the result.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool MakeCompositeInsert(const spvtools::opt::Instruction& inst);
+
+  /// Get the SPIR-V instruction for the image memory object declaration for
+  /// the image operand to the given instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns a SPIR-V OpVariable or OpFunctionParameter instruction, or null
+  /// on error
+  const spvtools::opt::Instruction* GetImage(
+      const spvtools::opt::Instruction& inst);
+
+  /// Get the AST texture the SPIR-V image memory object declaration.
+  /// @param inst the SPIR-V memory object declaration for the image.
+  /// @returns a texture type, or null on error
+  const Texture* GetImageType(const spvtools::opt::Instruction& inst);
+
+  /// Get the expression for the image operand from the first operand to the
+  /// given instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an identifier expression, or null on error
+  const ast::Expression* GetImageExpression(
+      const spvtools::opt::Instruction& inst);
+
+  /// Get the expression for the sampler operand from the first operand to the
+  /// given instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an identifier expression, or null on error
+  const ast::Expression* GetSamplerExpression(
+      const spvtools::opt::Instruction& inst);
+
+  /// Emits a texture builtin function call for a SPIR-V instruction that
+  /// accesses an image or sampled image.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool EmitImageAccess(const spvtools::opt::Instruction& inst);
+
+  /// Emits statements to implement a SPIR-V image query.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool EmitImageQuery(const spvtools::opt::Instruction& inst);
+
+  /// Converts the given texel to match the type required for the storage
+  /// texture with the given type. In WGSL the texel value is always provided
+  /// as a 4-element vector, but the component type is determined by the
+  /// texel channel type. See "Texel Formats for Storage Textures" in the WGSL
+  /// spec. Returns an expression, or emits an error and returns nullptr.
+  /// @param inst the image access instruction (used for diagnostics)
+  /// @param texel the texel
+  /// @param texture_type the type of the storage texture
+  /// @returns the texel, after necessary conversion.
+  const ast::Expression* ConvertTexelForStorage(
+      const spvtools::opt::Instruction& inst,
+      TypedExpression texel,
+      const Texture* texture_type);
+
+  /// Returns an expression for an OpSelect, if its operands are scalars
+  /// or vectors. These translate directly to WGSL select.  Otherwise, return
+  /// an expression with a null owned expression
+  /// @param inst the SPIR-V OpSelect instruction
+  /// @returns a typed expression, or one with a null owned expression
+  TypedExpression MakeSimpleSelect(const spvtools::opt::Instruction& inst);
+
+  /// Finds the header block for a structured construct that we can "break"
+  /// out from, from deeply nested control flow, if such a block exists.
+  /// If the construct is:
+  ///  - a switch selection: return the selection header (ending in OpSwitch)
+  ///  - a loop construct: return the loop header block
+  ///  - a continue construct: return the loop header block
+  /// Otherwise, return nullptr.
+  /// @param c a structured construct, or nullptr
+  /// @returns the block info for the structured header we can "break" from,
+  /// or nullptr
+  BlockInfo* HeaderIfBreakable(const Construct* c);
+
+  /// Appends a new statement to the top of the statement stack.
+  /// Does nothing if the statement is null.
+  /// @param statement the new statement
+  /// @returns a pointer to the statement.
+  const ast::Statement* AddStatement(const ast::Statement* statement);
+
+  /// AddStatementBuilder() constructs and adds the StatementBuilder of type
+  /// `T` to the top of the statement stack.
+  /// @param args the arguments forwarded to the T constructor
+  /// @return the built StatementBuilder
+  template <typename T, typename... ARGS>
+  T* AddStatementBuilder(ARGS&&... args) {
+    TINT_ASSERT(Reader, !statements_stack_.empty());
+    return statements_stack_.back().AddStatementBuilder<T>(
+        std::forward<ARGS>(args)...);
+  }
+
+  /// Returns the source record for the given instruction.
+  /// @param inst the SPIR-V instruction
+  /// @return the Source record, or a default one
+  Source GetSourceForInst(const spvtools::opt::Instruction& inst) const;
+
+  /// @returns the last statetment in the top of the statement stack.
+  const ast::Statement* LastStatement();
+
+  using CompletionAction = std::function<void(const ast::StatementList&)>;
+
+  // A StatementBlock represents a braced-list of statements while it is being
+  // constructed.
+  class StatementBlock {
+   public:
+    StatementBlock(const Construct* construct,
+                   uint32_t end_id,
+                   CompletionAction completion_action);
+    StatementBlock(StatementBlock&&);
+    ~StatementBlock();
+
+    StatementBlock(const StatementBlock&) = delete;
+    StatementBlock& operator=(const StatementBlock&) = delete;
+
+    /// Replaces any StatementBuilders with the built result, and calls the
+    /// completion callback (if set). Must only be called once, after all
+    /// statements have been added with Add().
+    /// @param builder the program builder
+    void Finalize(ProgramBuilder* builder);
+
+    /// Add() adds `statement` to the block.
+    /// Add() must not be called after calling Finalize().
+    void Add(const ast::Statement* statement);
+
+    /// AddStatementBuilder() constructs and adds the StatementBuilder of type
+    /// `T` to the block.
+    /// Add() must not be called after calling Finalize().
+    /// @param args the arguments forwarded to the T constructor
+    /// @return the built StatementBuilder
+    template <typename T, typename... ARGS>
+    T* AddStatementBuilder(ARGS&&... args) {
+      auto builder = std::make_unique<T>(std::forward<ARGS>(args)...);
+      auto* ptr = builder.get();
+      Add(ptr);
+      builders_.emplace_back(std::move(builder));
+      return ptr;
+    }
+
+    /// @param construct the construct which this construct constributes to
+    void SetConstruct(const Construct* construct) { construct_ = construct; }
+
+    /// @return the construct to which this construct constributes
+    const Construct* GetConstruct() const { return construct_; }
+
+    /// @return the ID of the block at which the completion action should be
+    /// triggered and this statement block discarded. This is often the `end_id`
+    /// of `construct` itself.
+    uint32_t GetEndId() const { return end_id_; }
+
+    /// @return the list of statements being built, if this construct is not a
+    /// switch.
+    const ast::StatementList& GetStatements() const { return statements_; }
+
+   private:
+    /// The construct to which this construct constributes.
+    const Construct* construct_;
+    /// The ID of the block at which the completion action should be triggered
+    /// and this statement block discarded. This is often the `end_id` of
+    /// `construct` itself.
+    const uint32_t end_id_;
+    /// The completion action finishes processing this statement block.
+    FunctionEmitter::CompletionAction const completion_action_;
+    /// The list of statements being built, if this construct is not a switch.
+    ast::StatementList statements_;
+
+    /// Owned statement builders
+    std::vector<std::unique_ptr<StatementBuilder>> builders_;
+    /// True if Finalize() has been called.
+    bool finalized_ = false;
+  };
+
+  /// Pushes an empty statement block onto the statements stack.
+  /// @param action the completion action for this block
+  void PushNewStatementBlock(const Construct* construct,
+                             uint32_t end_id,
+                             CompletionAction action);
+
+  /// Emits an if-statement whose condition is the given flow guard
+  /// variable, and pushes onto the statement stack the corresponding
+  /// statement block ending (and not including) the given block.
+  /// @param flow_guard name of the flow guard variable
+  /// @param end_id first block after the if construct.
+  void PushGuard(const std::string& flow_guard, uint32_t end_id);
+
+  /// Emits an if-statement with 'true' condition, and pushes onto the
+  /// statement stack the corresponding statement block ending (and not
+  /// including) the given block.
+  /// @param end_id first block after the if construct.
+  void PushTrueGuard(uint32_t end_id);
+
+  /// @returns a boolean true expression.
+  const ast::Expression* MakeTrue(const Source&) const;
+
+  /// @returns a boolean false expression.
+  const ast::Expression* MakeFalse(const Source&) const;
+
+  /// @param expr the expression to take the address of
+  /// @returns a TypedExpression that is the address-of `expr` (`&expr`)
+  /// @note `expr` must be a reference type
+  TypedExpression AddressOf(TypedExpression expr);
+
+  /// Returns AddressOf(expr) if expr is has reference type and
+  /// the instruction has a pointer result type.  Otherwise returns expr.
+  /// @param expr the expression to take the address of
+  /// @returns a TypedExpression that is the address-of `expr` (`&expr`)
+  /// @note `expr` must be a reference type
+  TypedExpression AddressOfIfNeeded(TypedExpression expr,
+                                    const spvtools::opt::Instruction* inst);
+
+  /// @param expr the expression to dereference
+  /// @returns a TypedExpression that is the dereference-of `expr` (`*expr`)
+  /// @note `expr` must be a pointer type
+  TypedExpression Dereference(TypedExpression expr);
+
+  /// Creates a new `ast::Node` owned by the ProgramBuilder.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  T* create(ARGS&&... args) const {
+    return builder_.create<T>(std::forward<ARGS>(args)...);
+  }
+
+  using StatementsStack = std::vector<StatementBlock>;
+  using PtrAs = ParserImpl::PtrAs;
+
+  ParserImpl& parser_impl_;
+  TypeManager& ty_;
+  ProgramBuilder& builder_;
+  spvtools::opt::IRContext& ir_context_;
+  spvtools::opt::analysis::DefUseManager* def_use_mgr_;
+  spvtools::opt::analysis::ConstantManager* constant_mgr_;
+  spvtools::opt::analysis::TypeManager* type_mgr_;
+  FailStream& fail_stream_;
+  Namer& namer_;
+  const spvtools::opt::Function& function_;
+
+  // The SPIR-V ID for the SampleMask input variable.
+  uint32_t sample_mask_in_id;
+  // The SPIR-V ID for the SampleMask output variable.
+  uint32_t sample_mask_out_id;
+
+  // A stack of statement lists. Each list is contained in a construct in
+  // the next deeper element of stack. The 0th entry represents the statements
+  // for the entire function.  This stack is never empty.
+  // The `construct` member for the 0th element is only valid during the
+  // lifetime of the EmitFunctionBodyStatements method.
+  StatementsStack statements_stack_;
+
+  // The map of IDs that have already had an identifier name generated for it,
+  // to their Type.
+  std::unordered_map<uint32_t, const Type*> identifier_types_;
+  // Mapping from SPIR-V ID that is used at most once, to its AST expression.
+  std::unordered_map<uint32_t, TypedExpression> singly_used_values_;
+
+  // The IDs of basic blocks, in reverse structured post-order (RSPO).
+  // This is the output order for the basic blocks.
+  std::vector<uint32_t> block_order_;
+
+  // Mapping from block ID to its bookkeeping info.
+  std::unordered_map<uint32_t, std::unique_ptr<BlockInfo>> block_info_;
+
+  // Mapping from a locally-defined result ID to its bookkeeping info.
+  std::unordered_map<uint32_t, std::unique_ptr<DefInfo>> def_info_;
+
+  // Structured constructs, where enclosing constructs precede their children.
+  ConstructList constructs_;
+
+  // Information about entry point, if this function is referenced by one
+  const EntryPointInfo* ep_info_ = nullptr;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_FUNCTION_H_
diff --git a/src/tint/reader/spirv/function_arithmetic_test.cc b/src/tint/reader/spirv/function_arithmetic_test.cc
new file mode 100644
index 0000000..8b0ed5d
--- /dev/null
+++ b/src/tint/reader/spirv/function_arithmetic_test.cc
@@ -0,0 +1,1085 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint Fragment %100 "main"
+  OpExecutionMode %100 OriginUpperLeft
+
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %uint_10 = OpConstant %uint 10
+  %uint_20 = OpConstant %uint 20
+  %int_30 = OpConstant %int 30
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+  %float_70 = OpConstant %float 70
+
+  %ptr_uint = OpTypePointer Function %uint
+  %ptr_int = OpTypePointer Function %int
+  %ptr_float = OpTypePointer Function %float
+
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+  %v3float = OpTypeVector %float 3
+
+  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
+  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
+  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
+  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+  %v3float_50_60_70 = OpConstantComposite %v3float %float_50 %float_60 %float_70
+  %v3float_60_70_50 = OpConstantComposite %v3float %float_60 %float_70 %float_50
+
+  %m2v2float = OpTypeMatrix %v2float 2
+  %m2v3float = OpTypeMatrix %v3float 2
+  %m3v2float = OpTypeMatrix %v2float 3
+  %m2v2float_a = OpConstantComposite %m2v2float %v2float_50_60 %v2float_60_50
+  %m2v2float_b = OpConstantComposite %m2v2float %v2float_60_50 %v2float_50_60
+  %m3v2float_a = OpConstantComposite %m3v2float %v2float_50_60 %v2float_60_50 %v2float_50_60
+  %m2v3float_a = OpConstantComposite %m2v3float %v3float_50_60_70 %v3float_60_70_50
+)";
+}
+
+// Returns the AST dump for a given SPIR-V assembly constant.
+std::string AstFor(std::string assembly) {
+  if (assembly == "v2uint_10_20") {
+    return "vec2<u32>(10u, 20u)";
+  }
+  if (assembly == "v2uint_20_10") {
+    return "vec2<u32>(20u, 10u)";
+  }
+  if (assembly == "v2int_30_40") {
+    return "vec2<i32>(30, 40)";
+  }
+  if (assembly == "v2int_40_30") {
+    return "vec2<i32>(40, 30)";
+  }
+  if (assembly == "cast_int_v2uint_10_20") {
+    return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
+  }
+  if (assembly == "cast_uint_v2int_40_30") {
+    return "bitcast<vec2<u32>>(vec2<i32>(40, 30))";
+  }
+  if (assembly == "v2float_50_60") {
+    return "vec2<f32>(50.0, 60.0)";
+  }
+  if (assembly == "v2float_60_50") {
+    return "vec2<f32>(60.0, 50.0)";
+  }
+  return "bad case";
+}
+
+using SpvUnaryArithTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvUnaryArithTest, SNegate_Int_Int) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %int %int_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : i32 = -(30);"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_Int_Uint) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %int %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : i32 = -(bitcast<i32>(10u));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_Uint_Int) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %uint %int_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>(-(30));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_Uint_Uint) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %uint %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>(-(bitcast<i32>(10u)));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_SignedVec_SignedVec) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %v2int %v2int_30_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<i32> = -(vec2<i32>(30, 40));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_SignedVec_UnsignedVec) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %v2int %v2uint_10_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          "let x_1 : vec2<i32> = -(bitcast<vec2<i32>>(vec2<u32>(10u, 20u)));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_SignedVec) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %v2uint %v2int_30_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(vec2<i32>(30, 40)));"));
+}
+
+TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_UnsignedVec) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSNegate %v2uint %v2uint_10_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(bitcast<vec2<i32>>(vec2<u32>(10u, 20u))));)"));
+}
+
+TEST_F(SpvUnaryArithTest, FNegate_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFNegate %float %float_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = -(50.0);"));
+}
+
+TEST_F(SpvUnaryArithTest, FNegate_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFNegate %v2float %v2float_50_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<f32> = -(vec2<f32>(50.0, 60.0));"));
+}
+
+struct BinaryData {
+  const std::string res_type;
+  const std::string lhs;
+  const std::string op;
+  const std::string rhs;
+  const std::string ast_type;
+  const std::string ast_lhs;
+  const std::string ast_op;
+  const std::string ast_rhs;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
+      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
+      << data.ast_op << "," << data.ast_rhs << "}";
+  return out;
+}
+
+using SpvBinaryArithTest =
+    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
+using SpvBinaryArithTestBasic = SpvParserTestBase<::testing::Test>;
+
+TEST_P(SpvBinaryArithTest, EmitExpression) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = )" + GetParam().op +
+                        " %" + GetParam().res_type + " %" + GetParam().lhs +
+                        " %" + GetParam().rhs + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  std::ostringstream ss;
+  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
+     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
+}
+
+// Use this when the result might have extra bitcasts on the outside.
+struct BinaryDataGeneral {
+  const std::string res_type;
+  const std::string lhs;
+  const std::string op;
+  const std::string rhs;
+  const std::string wgsl_type;
+  const std::string expected;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryDataGeneral data) {
+  out << "BinaryDataGeneral{" << data.res_type << "," << data.lhs << ","
+      << data.op << "," << data.rhs << "," << data.wgsl_type << ","
+      << data.expected << "}";
+  return out;
+}
+
+using SpvBinaryArithGeneralTest =
+    SpvParserTestBase<::testing::TestWithParam<BinaryDataGeneral>>;
+
+TEST_P(SpvBinaryArithGeneralTest, EmitExpression) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = )" + GetParam().op +
+                        " %" + GetParam().res_type + " %" + GetParam().lhs +
+                        " %" + GetParam().rhs + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  std::ostringstream ss;
+  ss << "let x_1 : " << GetParam().wgsl_type << " = " << GetParam().expected
+     << ";";
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_IAdd,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpIAdd", "uint_20", "u32", "10u", "+",
+                   "20u"},  // Both int
+        BinaryData{"int", "int_30", "OpIAdd", "int_40", "i32", "30", "+",
+                   "40"},  // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpIAdd", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "+",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpIAdd", "v2int_40_30", "vec2<i32>",
+                   AstFor("v2int_30_40"), "+", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_IAdd_MixedSignedness,
+    SpvBinaryArithGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpIAdd", "uint_10", "u32",
+                          "bitcast<u32>((30 + bitcast<i32>(10u)))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpIAdd", "uint_10", "i32",
+                          "(30 + bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpIAdd", "int_30", "u32",
+                          "(10u + bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpIAdd", "uint_10", "i32",
+                          "bitcast<i32>((20u + 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpIAdd", "v2uint_10_20", "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) + bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpIAdd", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) + bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FAdd,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Scalar float
+        BinaryData{"float", "float_50", "OpFAdd", "float_60", "f32", "50.0",
+                   "+", "60.0"},  // Vector float
+        BinaryData{"v2float", "v2float_50_60", "OpFAdd", "v2float_60_50",
+                   "vec2<f32>", AstFor("v2float_50_60"), "+",
+                   AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ISub,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpISub", "uint_20", "u32", "10u", "-",
+                   "20u"},  // Both int
+        BinaryData{"int", "int_30", "OpISub", "int_40", "i32", "30", "-",
+                   "40"},  // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpISub", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "-",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpISub", "v2int_40_30", "vec2<i32>",
+                   AstFor("v2int_30_40"), "-", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ISub_MixedSignedness,
+    SpvBinaryArithGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpISub", "uint_10", "u32",
+                          R"(bitcast<u32>((30 - bitcast<i32>(10u))))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpISub", "uint_10", "i32",
+                          "(30 - bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpISub", "int_30", "u32",
+                          "(10u - bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpISub", "uint_10", "i32",
+                          "bitcast<i32>((20u - 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpISub", "v2uint_10_20", "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) - bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpISub", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) - bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FSub,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Scalar float
+        BinaryData{"float", "float_50", "OpFSub", "float_60", "f32", "50.0",
+                   "-", "60.0"},  // Vector float
+        BinaryData{"v2float", "v2float_50_60", "OpFSub", "v2float_60_50",
+                   "vec2<f32>", AstFor("v2float_50_60"), "-",
+                   AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_IMul,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpIMul", "uint_20", "u32", "10u", "*",
+                   "20u"},  // Both int
+        BinaryData{"int", "int_30", "OpIMul", "int_40", "i32", "30", "*",
+                   "40"},  // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpIMul", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "*",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpIMul", "v2int_40_30", "vec2<i32>",
+                   AstFor("v2int_30_40"), "*", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_IMul_MixedSignedness,
+    SpvBinaryArithGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpIMul", "uint_10", "u32",
+                          "bitcast<u32>((30 * bitcast<i32>(10u)))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpIMul", "uint_10", "i32",
+                          "(30 * bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpIMul", "int_30", "u32",
+                          "(10u * bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpIMul", "uint_10", "i32",
+                          "bitcast<i32>((20u * 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpIMul", "v2uint_10_20", "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) * bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpIMul", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) * bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FMul,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Scalar float
+        BinaryData{"float", "float_50", "OpFMul", "float_60", "f32", "50.0",
+                   "*", "60.0"},  // Vector float
+        BinaryData{"v2float", "v2float_50_60", "OpFMul", "v2float_60_50",
+                   "vec2<f32>", AstFor("v2float_50_60"), "*",
+                   AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_UDiv,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpUDiv", "uint_20", "u32", "10u", "/",
+                   "20u"},  // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpUDiv", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "/",
+                   AstFor("v2uint_20_10")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SDiv,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both int
+        BinaryData{"int", "int_30", "OpSDiv", "int_40", "i32", "30", "/",
+                   "40"},  // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2int_40_30", "vec2<i32>",
+                   AstFor("v2int_30_40"), "/", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SDiv_MixedSignednessOperands,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Mixed, returning int, second arg uint
+        BinaryData{"int", "int_30", "OpSDiv", "uint_10", "i32", "30", "/",
+                   "bitcast<i32>(10u)"},
+        // Mixed, returning int, first arg uint
+        BinaryData{"int", "uint_10", "OpSDiv", "int_30", "i32",
+                   "bitcast<i32>(10u)", "/",
+                   "30"},  // Mixed, returning v2int, first arg v2uint
+        BinaryData{"v2int", "v2uint_10_20", "OpSDiv", "v2int_30_40",
+                   "vec2<i32>", AstFor("cast_int_v2uint_10_20"), "/",
+                   AstFor("v2int_30_40")},
+        // Mixed, returning v2int, second arg v2uint
+        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2uint_10_20",
+                   "vec2<i32>", AstFor("v2int_30_40"), "/",
+                   AstFor("cast_int_v2uint_10_20")}));
+
+TEST_F(SpvBinaryArithTestBasic, SDiv_Scalar_UnsignedResult) {
+  // The WGSL signed division operator expects both operands to be signed
+  // and the result is signed as well.
+  // In this test SPIR-V demands an unsigned result, so we have to
+  // wrap the result with an as-cast.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSDiv %uint %int_30 %int_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>((30 / 40));"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, SDiv_Vector_UnsignedResult) {
+  // The WGSL signed division operator expects both operands to be signed
+  // and the result is signed as well.
+  // In this test SPIR-V demands an unsigned result, so we have to
+  // wrap the result with an as-cast.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSDiv %v2uint %v2int_30_40 %v2int_40_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30, 40) / vec2<i32>(40, 30)));)"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FDiv,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Scalar float
+        BinaryData{"float", "float_50", "OpFDiv", "float_60", "f32", "50.0",
+                   "/", "60.0"},  // Vector float
+        BinaryData{"v2float", "v2float_50_60", "OpFDiv", "v2float_60_50",
+                   "vec2<f32>", AstFor("v2float_50_60"), "/",
+                   AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_UMod,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpUMod", "uint_20", "u32", "10u", "%",
+                   "20u"},  // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpUMod", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "%",
+                   AstFor("v2uint_20_10")}));
+
+// Currently WGSL is missing a mapping for OpSRem
+// https://github.com/gpuweb/gpuweb/issues/702
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SMod,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Both int
+        BinaryData{"int", "int_30", "OpSMod", "int_40", "i32", "30", "%",
+                   "40"},  // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2int_40_30", "vec2<i32>",
+                   AstFor("v2int_30_40"), "%", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SMod_MixedSignednessOperands,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Mixed, returning int, second arg uint
+        BinaryData{"int", "int_30", "OpSMod", "uint_10", "i32", "30", "%",
+                   "bitcast<i32>(10u)"},
+        // Mixed, returning int, first arg uint
+        BinaryData{"int", "uint_10", "OpSMod", "int_30", "i32",
+                   "bitcast<i32>(10u)", "%",
+                   "30"},  // Mixed, returning v2int, first arg v2uint
+        BinaryData{"v2int", "v2uint_10_20", "OpSMod", "v2int_30_40",
+                   "vec2<i32>", AstFor("cast_int_v2uint_10_20"), "%",
+                   AstFor("v2int_30_40")},
+        // Mixed, returning v2int, second arg v2uint
+        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2uint_10_20",
+                   "vec2<i32>", AstFor("v2int_30_40"), "%",
+                   AstFor("cast_int_v2uint_10_20")}));
+
+TEST_F(SpvBinaryArithTestBasic, SMod_Scalar_UnsignedResult) {
+  // The WGSL signed modulus operator expects both operands to be signed
+  // and the result is signed as well.
+  // In this test SPIR-V demands an unsigned result, so we have to
+  // wrap the result with an as-cast.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSMod %uint %int_30 %int_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>((30 % 40));"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, SMod_Vector_UnsignedResult) {
+  // The WGSL signed modulus operator expects both operands to be signed
+  // and the result is signed as well.
+  // In this test SPIR-V demands an unsigned result, so we have to
+  // wrap the result with an as-cast.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSMod %v2uint %v2int_30_40 %v2int_40_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30, 40) % vec2<i32>(40, 30)));)"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FRem,
+    SpvBinaryArithTest,
+    ::testing::Values(
+        // Scalar float
+        BinaryData{"float", "float_50", "OpFRem", "float_60", "f32", "50.0",
+                   "%", "60.0"},  // Vector float
+        BinaryData{"v2float", "v2float_50_60", "OpFRem", "v2float_60_50",
+                   "vec2<f32>", AstFor("v2float_50_60"), "%",
+                   AstFor("v2float_60_50")}));
+
+TEST_F(SpvBinaryArithTestBasic, FMod_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFMod %float %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : f32 = (50.0 - (60.0 * floor((50.0 / 60.0))));"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, FMod_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFMod %v2float %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          R"(let x_1 : vec2<f32> = (vec2<f32>(50.0, 60.0) - (vec2<f32>(60.0, 50.0) * floor((vec2<f32>(50.0, 60.0) / vec2<f32>(60.0, 50.0)))));)"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, VectorTimesScalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2float %v2float_50_60
+     %2 = OpCopyObject %float %float_50
+     %10 = OpVectorTimesScalar %v2float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, MatrixTimesScalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v2float %m2v2float_a
+     %2 = OpCopyObject %float %float_50
+     %10 = OpMatrixTimesScalar %m2v2float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, VectorTimesMatrix) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v2float %m2v2float_a
+     %2 = OpCopyObject %v2float %v2float_50_60
+     %10 = OpMatrixTimesVector %v2float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, MatrixTimesVector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v2float %m2v2float_a
+     %2 = OpCopyObject %v2float %v2float_50_60
+     %10 = OpMatrixTimesVector %v2float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, MatrixTimesMatrix) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v2float %m2v2float_a
+     %2 = OpCopyObject %m2v2float %m2v2float_b
+     %10 = OpMatrixTimesMatrix %m2v2float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, Dot) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2float %v2float_50_60
+     %2 = OpCopyObject %v2float %v2float_60_50
+     %3 = OpDot %float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_3 : f32 = dot(x_1, x_2);"));
+}
+
+TEST_F(SpvBinaryArithTestBasic, OuterProduct) {
+  // OpOuterProduct is expanded to basic operations.
+  // The operands, even if used once, are given their own const definitions.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFAdd %v3float %v3float_50_60_70 %v3float_50_60_70 ; column vector
+     %2 = OpFAdd %v2float %v2float_60_50 %v2float_50_60 ; row vector
+     %3 = OpOuterProduct %m2v3float %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      got,
+      HasSubstr(
+          "let x_3 : mat2x3<f32> = mat2x3<f32>("
+          "vec3<f32>((x_2.x * x_1.x), (x_2.x * x_1.y), (x_2.x * x_1.z)), "
+          "vec3<f32>((x_2.y * x_1.x), (x_2.y * x_1.y), (x_2.y * x_1.z)));"))
+      << got;
+}
+
+struct BuiltinData {
+  const std::string spirv;
+  const std::string wgsl;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << "OpData{" << data.spirv << "," << data.wgsl << "}";
+  return out;
+}
+struct ArgAndTypeData {
+  const std::string spirv_type;
+  const std::string spirv_arg;
+  const std::string ast_type;
+};
+inline std::ostream& operator<<(std::ostream& out, ArgAndTypeData data) {
+  out << "ArgAndTypeData{" << data.spirv_type << "," << data.spirv_arg << ","
+      << data.ast_type << "}";
+  return out;
+}
+
+using SpvBinaryDerivativeTest = SpvParserTestBase<
+    ::testing::TestWithParam<std::tuple<BuiltinData, ArgAndTypeData>>>;
+
+TEST_P(SpvBinaryDerivativeTest, Derivatives) {
+  auto& builtin = std::get<0>(GetParam());
+  auto& arg = std::get<1>(GetParam());
+
+  const auto assembly = R"(
+     OpCapability DerivativeControl
+)" + Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %)" +
+                        arg.spirv_type + " %" + arg.spirv_arg + R"(
+     %2 = )" + builtin.spirv +
+                        " %" + arg.spirv_type + R"( %1
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_2 : " + arg.ast_type + " = " + builtin.wgsl + "(x_1);"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvBinaryDerivativeTest,
+    SpvBinaryDerivativeTest,
+    testing::Combine(
+        ::testing::Values(BuiltinData{"OpDPdx", "dpdx"},
+                          BuiltinData{"OpDPdy", "dpdy"},
+                          BuiltinData{"OpFwidth", "fwidth"},
+                          BuiltinData{"OpDPdxFine", "dpdxFine"},
+                          BuiltinData{"OpDPdyFine", "dpdyFine"},
+                          BuiltinData{"OpFwidthFine", "fwidthFine"},
+                          BuiltinData{"OpDPdxCoarse", "dpdxCoarse"},
+                          BuiltinData{"OpDPdyCoarse", "dpdyCoarse"},
+                          BuiltinData{"OpFwidthCoarse", "fwidthCoarse"}),
+        ::testing::Values(
+            ArgAndTypeData{"float", "float_50", "f32"},
+            ArgAndTypeData{"v2float", "v2float_50_60", "vec2<f32>"},
+            ArgAndTypeData{"v3float", "v3float_50_60_70", "vec3<f32>"})));
+
+TEST_F(SpvUnaryArithTest, Transpose_2x2) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v2float %m2v2float_a
+     %2 = OpTranspose %m2v2float %1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  const auto* expected = "let x_2 : mat2x2<f32> = transpose(x_1);";
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvUnaryArithTest, Transpose_2x3) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m2v3float %m2v3float_a
+     %2 = OpTranspose %m3v2float %1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  // Note, in the AST dump mat_2_3 means 2 rows and 3 columns.
+  // So the column vectors have 2 elements.
+  // That is,   %m3v2float is __mat_2_3f32.
+  const auto* expected = "let x_2 : mat3x2<f32> = transpose(x_1);";
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvUnaryArithTest, Transpose_3x2) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %m3v2float %m3v2float_a
+     %2 = OpTranspose %m2v3float %1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  const auto* expected = "let x_2 : mat2x3<f32> = transpose(x_1);";
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+// TODO(dneto): OpSRem. Missing from WGSL
+// https://github.com/gpuweb/gpuweb/issues/702
+
+// TODO(dneto): OpFRem. Missing from WGSL
+// https://github.com/gpuweb/gpuweb/issues/702
+
+// TODO(dneto): OpIAddCarry
+// TODO(dneto): OpISubBorrow
+// TODO(dneto): OpUMulExtended
+// TODO(dneto): OpSMulExtended
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_bit_test.cc b/src/tint/reader/spirv/function_bit_test.cc
new file mode 100644
index 0000000..2ac6531
--- /dev/null
+++ b/src/tint/reader/spirv/function_bit_test.cc
@@ -0,0 +1,911 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string CommonTypes() {
+  return R"(
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %uint_10 = OpConstant %uint 10
+  %uint_20 = OpConstant %uint 20
+  %int_30 = OpConstant %int 30
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+
+  %ptr_uint = OpTypePointer Function %uint
+  %ptr_int = OpTypePointer Function %int
+  %ptr_float = OpTypePointer Function %float
+
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+
+  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
+  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
+  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
+  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+)";
+}
+
+std::string SimplePreamble() {
+  return R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint Fragment %100 "main"
+  OpExecutionMode %100 OriginUpperLeft
+)" + CommonTypes();
+}
+
+// Returns the AST dump for a given SPIR-V assembly constant.
+std::string AstFor(std::string assembly) {
+  if (assembly == "v2uint_10_20") {
+    return "vec2<u32>(10u, 20u)";
+  }
+  if (assembly == "v2uint_20_10") {
+    return "vec2<u32>(20u, 10u)";
+  }
+  if (assembly == "v2int_30_40") {
+    return "vec2<i32>(30, 40)";
+  }
+  if (assembly == "v2int_40_30") {
+    return "vec2<i32>(40, 30)";
+  }
+  if (assembly == "cast_int_v2uint_10_20") {
+    return "bitcast<vec2<i32>(vec2<u32>(10u, 20u))";
+  }
+  if (assembly == "v2float_50_60") {
+    return "vec2<f32>(50.0, 60.0))";
+  }
+  if (assembly == "v2float_60_50") {
+    return "vec2<f32>(60.0, 50.0))";
+  }
+  return "bad case";
+}
+
+using SpvUnaryBitTest = SpvParserTestBase<::testing::Test>;
+
+struct BinaryData {
+  const std::string res_type;
+  const std::string lhs;
+  const std::string op;
+  const std::string rhs;
+  const std::string ast_type;
+  const std::string ast_lhs;
+  const std::string ast_op;
+  const std::string ast_rhs;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
+      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
+      << data.ast_op << "," << data.ast_rhs << "}";
+  return out;
+}
+
+using SpvBinaryBitTest =
+    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
+using SpvBinaryBitTestBasic = SpvParserTestBase<::testing::Test>;
+
+TEST_P(SpvBinaryBitTest, EmitExpression) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = )" + GetParam().op +
+                        " %" + GetParam().res_type + " %" + GetParam().lhs +
+                        " %" + GetParam().rhs + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  std::ostringstream ss;
+  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
+     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(ss.str()))
+      << assembly;
+}
+
+// Use this when the result might have extra bitcasts on the outside.
+struct BinaryDataGeneral {
+  const std::string res_type;
+  const std::string lhs;
+  const std::string op;
+  const std::string rhs;
+  const std::string wgsl_type;
+  const std::string expected;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryDataGeneral data) {
+  out << "BinaryDataGeneral{" << data.res_type << "," << data.lhs << ","
+      << data.op << "," << data.rhs << "," << data.wgsl_type << ","
+      << data.expected << "}";
+  return out;
+}
+
+using SpvBinaryBitGeneralTest =
+    SpvParserTestBase<::testing::TestWithParam<BinaryDataGeneral>>;
+
+TEST_P(SpvBinaryBitGeneralTest, EmitExpression) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = )" + GetParam().op +
+                        " %" + GetParam().res_type + " %" + GetParam().lhs +
+                        " %" + GetParam().rhs + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
+  std::ostringstream ss;
+  ss << "let x_1 : " << GetParam().wgsl_type << " = " << GetParam().expected
+     << ";\nreturn;\n";
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(ss.str())) << "got:\n" << got << assembly;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftLeftLogical_Arg2Unsigned,
+    SpvBinaryBitTest,
+    ::testing::Values(
+        // uint uint -> uint
+        BinaryData{"uint", "uint_10", "OpShiftLeftLogical", "uint_20", "u32",
+                   "10u", "<<", "20u"},
+        // int, uint -> int
+        BinaryData{"int", "int_30", "OpShiftLeftLogical", "uint_20", "i32",
+                   "30", "<<", "20u"},
+        // v2uint v2uint -> v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpShiftLeftLogical",
+                   "v2uint_20_10", "vec2<u32>", AstFor("v2uint_10_20"), "<<",
+                   AstFor("v2uint_20_10")},
+        // v2int, v2uint -> v2int
+        BinaryData{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
+                   "vec2<i32>", AstFor("v2int_30_40"), "<<",
+                   AstFor("v2uint_20_10")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // WGSL requires second operand to be unsigned, so insert bitcasts
+    SpvParserTest_ShiftLeftLogical_Arg2Signed,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // int, int -> int
+        BinaryDataGeneral{"int", "int_30", "OpShiftLeftLogical", "int_40",
+                          "i32", "(30 << bitcast<u32>(40))"},
+        // uint, int -> uint
+        BinaryDataGeneral{"uint", "uint_10", "OpShiftLeftLogical", "int_40",
+                          "u32", "(10u << bitcast<u32>(40))"},
+        // v2uint, v2int -> v2uint
+        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftLeftLogical",
+                          "v2uint_20_10", "vec2<u32>",
+                          "(vec2<u32>(10u, 20u) << vec2<u32>(20u, 10u))"},
+        // v2int, v2int -> v2int
+        BinaryDataGeneral{
+            "v2int", "v2int_30_40", "OpShiftLeftLogical", "v2int_40_30",
+            "vec2<i32>",
+            "(vec2<i32>(30, 40) << bitcast<vec2<u32>>(vec2<i32>(40, 30)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftLeftLogical_BitcastResult,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // int, int -> uint
+        BinaryDataGeneral{"uint", "int_30", "OpShiftLeftLogical", "uint_10",
+                          "u32", "bitcast<u32>((30 << 10u))"},
+        // v2uint, v2int -> v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
+            "vec2<u32>",
+            "bitcast<vec2<u32>>((vec2<i32>(30, 40) << vec2<u32>(20u, 10u)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightLogical_Arg2Unsigned,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // uint, uint -> uint
+        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightLogical", "uint_20",
+                          "u32", "(10u >> 20u)"},
+        // int, uint -> int
+        BinaryDataGeneral{"int", "int_30", "OpShiftRightLogical", "uint_20",
+                          "i32", "bitcast<i32>((bitcast<u32>(30) >> 20u))"},
+        // v2uint, v2uint -> v2uint
+        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical",
+                          "v2uint_20_10", "vec2<u32>",
+                          "(vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))"},
+        // v2int, v2uint -> v2int
+        BinaryDataGeneral{
+            "v2int", "v2int_30_40", "OpShiftRightLogical", "v2uint_10_20",
+            "vec2<i32>",
+            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(30, 40)) >> vec2<u32>(10u, 20u))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightLogical_Arg2Signed,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // uint, int -> uint
+        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightLogical", "int_30",
+                          "u32", "(10u >> bitcast<u32>(30))"},
+        // int, int -> int
+        BinaryDataGeneral{
+            "int", "int_30", "OpShiftRightLogical", "int_40", "i32",
+            "bitcast<i32>((bitcast<u32>(30) >> bitcast<u32>(40)))"},
+        // v2uint, v2int -> v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2int_30_40",
+            "vec2<u32>",
+            "(vec2<u32>(10u, 20u) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))"},
+        // v2int, v2int -> v2int
+        BinaryDataGeneral{
+            "v2int", "v2int_40_30", "OpShiftRightLogical", "v2int_30_40",
+            "vec2<i32>",
+            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(40, 30)) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightLogical_BitcastResult,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // uint, uint -> int
+        BinaryDataGeneral{"int", "uint_20", "OpShiftRightLogical", "uint_10",
+                          "i32", "bitcast<i32>((20u >> 10u))"},
+        // v2uint, v2uint -> v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10",
+            "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightArithmetic_Arg2Unsigned,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // uint, uint -> uint
+        BinaryDataGeneral{"uint", "uint_10", "OpShiftRightArithmetic",
+                          "uint_20", "u32",
+                          "bitcast<u32>((bitcast<i32>(10u) >> 20u))"},
+        // int, uint -> int
+        BinaryDataGeneral{"int", "int_30", "OpShiftRightArithmetic", "uint_10",
+                          "i32", "(30 >> 10u)"},
+        // v2uint, v2uint -> v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2uint_20_10",
+            "vec2<u32>",
+            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> vec2<u32>(20u, 10u))))"},
+        // v2int, v2uint -> v2int
+        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic",
+                          "v2uint_20_10", "vec2<i32>",
+                          "(vec2<i32>(40, 30) >> vec2<u32>(20u, 10u))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightArithmetic_Arg2Signed,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // uint, int -> uint
+        BinaryDataGeneral{
+            "uint", "uint_10", "OpShiftRightArithmetic", "int_30", "u32",
+            "bitcast<u32>((bitcast<i32>(10u) >> bitcast<u32>(30)))"},
+        // int, int -> int
+        BinaryDataGeneral{"int", "int_30", "OpShiftRightArithmetic", "int_40",
+                          "i32", "(30 >> bitcast<u32>(40))"},
+        // v2uint, v2int -> v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2int_30_40",
+            "vec2<u32>",
+            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))))"},
+        // v2int, v2int -> v2int
+        BinaryDataGeneral{
+            "v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2int_30_40",
+            "vec2<i32>",
+            "(vec2<i32>(40, 30) >> bitcast<vec2<u32>>(vec2<i32>(30, 40)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ShiftRightArithmetic_BitcastResult,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // int, uint -> uint
+        BinaryDataGeneral{"uint", "int_30", "OpShiftRightArithmetic", "uint_10",
+                          "u32", "bitcast<u32>((30 >> 10u))"},
+        // v2int, v2uint -> v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpShiftRightArithmetic", "v2uint_20_10",
+            "vec2<u32>",
+            "bitcast<vec2<u32>>((vec2<i32>(30, 40) >> vec2<u32>(20u, 10u)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseAnd,
+    SpvBinaryBitTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpBitwiseAnd", "uint_20", "u32", "10u",
+                   "&", "20u"},
+        // Both int
+        BinaryData{"int", "int_30", "OpBitwiseAnd", "int_40", "i32", "30", "&",
+                   "40"},
+        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
+        // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseAnd", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "&",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseAnd", "v2int_40_30",
+                   "vec2<i32>", AstFor("v2int_30_40"), "&",
+                   AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseAnd_MixedSignedness,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpBitwiseAnd", "uint_10", "u32",
+                          "bitcast<u32>((30 & bitcast<i32>(10u)))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpBitwiseAnd", "uint_10", "i32",
+                          "(30 & bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseAnd", "int_30", "u32",
+                          "(10u & bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpBitwiseAnd", "uint_10", "i32",
+                          "bitcast<i32>((20u & 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpBitwiseAnd", "v2uint_10_20",
+            "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) & bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpBitwiseAnd", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) & bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseOr,
+    SpvBinaryBitTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpBitwiseOr", "uint_20", "u32", "10u",
+                   "|", "20u"},
+        // Both int
+        BinaryData{"int", "int_30", "OpBitwiseOr", "int_40", "i32", "30", "|",
+                   "40"},
+        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
+        // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseOr", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "|",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseOr", "v2int_40_30",
+                   "vec2<i32>", AstFor("v2int_30_40"), "|",
+                   AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseOr_MixedSignedness,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpBitwiseOr", "uint_10", "u32",
+                          "bitcast<u32>((30 | bitcast<i32>(10u)))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpBitwiseOr", "uint_10", "i32",
+                          "(30 | bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseOr", "int_30", "u32",
+                          "(10u | bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpBitwiseOr", "uint_10", "i32",
+                          "bitcast<i32>((20u | 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpBitwiseOr", "v2uint_10_20", "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) | bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpBitwiseOr", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) | bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseXor,
+    SpvBinaryBitTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"uint", "uint_10", "OpBitwiseXor", "uint_20", "u32", "10u",
+                   "^", "20u"},
+        // Both int
+        BinaryData{"int", "int_30", "OpBitwiseXor", "int_40", "i32", "30", "^",
+                   "40"},
+        // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
+        // Both v2uint
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseXor", "v2uint_20_10",
+                   "vec2<u32>", AstFor("v2uint_10_20"), "^",
+                   AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseXor", "v2int_40_30",
+                   "vec2<i32>", AstFor("v2int_30_40"), "^",
+                   AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_BitwiseXor_MixedSignedness,
+    SpvBinaryBitGeneralTest,
+    ::testing::Values(
+        // Mixed, uint <- int uint
+        BinaryDataGeneral{"uint", "int_30", "OpBitwiseXor", "uint_10", "u32",
+                          "bitcast<u32>((30 ^ bitcast<i32>(10u)))"},
+        // Mixed, int <- int uint
+        BinaryDataGeneral{"int", "int_30", "OpBitwiseXor", "uint_10", "i32",
+                          "(30 ^ bitcast<i32>(10u))"},
+        // Mixed, uint <- uint int
+        BinaryDataGeneral{"uint", "uint_10", "OpBitwiseXor", "int_30", "u32",
+                          "(10u ^ bitcast<u32>(30))"},
+        // Mixed, int <- uint uint
+        BinaryDataGeneral{"int", "uint_20", "OpBitwiseXor", "uint_10", "i32",
+                          "bitcast<i32>((20u ^ 10u))"},
+        // Mixed, returning v2uint
+        BinaryDataGeneral{
+            "v2uint", "v2int_30_40", "OpBitwiseXor", "v2uint_10_20",
+            "vec2<u32>",
+            R"(bitcast<vec2<u32>>((vec2<i32>(30, 40) ^ bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        // Mixed, returning v2int
+        BinaryDataGeneral{
+            "v2int", "v2uint_10_20", "OpBitwiseXor", "v2int_40_30", "vec2<i32>",
+            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) ^ bitcast<vec2<u32>>(vec2<i32>(40, 30)))))"}));
+
+TEST_F(SpvUnaryBitTest, Not_Int_Int) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %int %int_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = ~(30);"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_Int_Uint) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %int %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = bitcast<i32>(~(10u));"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_Uint_Int) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %uint %int_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = bitcast<u32>(~(30));"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_Uint_Uint) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %uint %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = ~(10u);"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_SignedVec_SignedVec) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %v2int %v2int_30_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = ~(vec2<i32>(30, 40));"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %v2int %v2uint_10_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          "let x_1 : vec2<i32> = bitcast<vec2<i32>>(~(vec2<u32>(10u, 20u)));"));
+}
+
+TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %v2uint %v2int_30_40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(~(vec2<i32>(30, 40)));"));
+}
+TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
+  const auto assembly = SimplePreamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpNot %v2uint %v2uint_10_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = ~(vec2<u32>(10u, 20u));"));
+}
+
+std::string BitTestPreamble() {
+  return R"(
+  OpCapability Shader
+  %glsl = OpExtInstImport "GLSL.std.450"
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint GLCompute %100 "main"
+  OpExecutionMode %100 LocalSize 1 1 1
+
+  OpName %u1 "u1"
+  OpName %i1 "i1"
+  OpName %v2u1 "v2u1"
+  OpName %v2i1 "v2i1"
+
+)" + CommonTypes() +
+         R"(
+
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  %u1 = OpCopyObject %uint %uint_10
+  %i1 = OpCopyObject %int %int_30
+  %v2u1 = OpCopyObject %v2uint %v2uint_10_20
+  %v2i1 = OpCopyObject %v2int %v2int_30_40
+)";
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_Uint_Uint) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %uint %u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = countOneBits(u1);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_Uint_Int) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %uint %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : u32 = bitcast<u32>(countOneBits(i1));"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_Int_Uint) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %int %u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : i32 = bitcast<i32>(countOneBits(u1));"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_Int_Int) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %int %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = countOneBits(i1);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_UintVector_UintVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %v2uint %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = countOneBits(v2u1);"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_UintVector_IntVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %v2uint %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          "let x_1 : vec2<u32> = bitcast<vec2<u32>>(countOneBits(v2i1));"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_IntVector_UintVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %v2int %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          "let x_1 : vec2<i32> = bitcast<vec2<i32>>(countOneBits(v2u1));"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitCount_IntVector_IntVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitCount %v2int %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = countOneBits(v2i1);"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_Uint_Uint) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %uint %u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = reverseBits(u1);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_Uint_Int) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %uint %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_Int_Uint) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %int %u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_Int_Int) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %int %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = reverseBits(i1);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_UintVector_UintVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %v2uint %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = reverseBits(v2u1);"))
+      << body;
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_UintVector_IntVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %v2uint %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_IntVector_UintVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %v2int %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
+}
+
+TEST_F(SpvUnaryBitTest, BitReverse_IntVector_IntVector) {
+  const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitReverse %v2int %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = reverseBits(v2i1);"))
+      << body;
+}
+
+// TODO(dneto): OpBitFieldInsert
+// TODO(dneto): OpBitFieldSExtract
+// TODO(dneto): OpBitFieldUExtract
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_call_test.cc b/src/tint/reader/spirv/function_call_test.cc
new file mode 100644
index 0000000..bfc4eab
--- /dev/null
+++ b/src/tint/reader/spirv/function_call_test.cc
@@ -0,0 +1,202 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "x_100"
+     OpExecutionMode %100 OriginUpperLeft
+)";
+}
+
+TEST_F(SpvParserTest, EmitStatement_VoidCallNoParams) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %50 = OpFunction %void None %voidfn
+     %entry_50 = OpLabel
+     OpReturn
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFunctionCall %void %50
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  const auto got = test::ToString(p->program());
+  const char* expect = R"(fn x_50() {
+  return;
+}
+
+fn x_100_1() {
+  x_50();
+  return;
+}
+
+@stage(fragment)
+fn x_100() {
+  x_100_1();
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParams) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %uintfn = OpTypeFunction %uint
+     %val = OpConstant %uint 42
+
+     %50 = OpFunction %uint None %uintfn
+     %entry_50 = OpLabel
+     OpReturnValue %val
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFunctionCall %uint %50
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  ast::StatementList f100;
+  {
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    f100 = fe.ast_body();
+  }
+  ast::StatementList f50;
+  {
+    auto fe = p->function_emitter(50);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    f50 = fe.ast_body();
+  }
+  auto program = p->program();
+  EXPECT_THAT(test::ToString(program, f100),
+              HasSubstr("let x_1 : u32 = x_50();\nreturn;"));
+  EXPECT_THAT(test::ToString(program, f50), HasSubstr("return 42u;"));
+}
+
+TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParamsUsedTwice) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %uintfn = OpTypeFunction %uint
+     %val = OpConstant %uint 42
+     %ptr_uint = OpTypePointer Function %uint
+
+     %50 = OpFunction %uint None %uintfn
+     %entry_50 = OpLabel
+     OpReturnValue %val
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpVariable %ptr_uint Function
+     %1 = OpFunctionCall %uint %50
+     OpStore %10 %1
+     OpStore %10 %1
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  ast::StatementList f100;
+  {
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    f100 = fe.ast_body();
+  }
+  ast::StatementList f50;
+  {
+    auto fe = p->function_emitter(50);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    f50 = fe.ast_body();
+  }
+  auto program = p->program();
+  EXPECT_EQ(test::ToString(program, f100), R"(var x_10 : u32;
+let x_1 : u32 = x_50();
+x_10 = x_1;
+x_10 = x_1;
+return;
+)");
+  EXPECT_THAT(test::ToString(program, f50), HasSubstr("return 42u;"));
+}
+
+TEST_F(SpvParserTest, EmitStatement_CallWithParams) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %uintfn_uint_uint = OpTypeFunction %uint %uint %uint
+     %val = OpConstant %uint 42
+     %val2 = OpConstant %uint 84
+
+     %50 = OpFunction %uint None %uintfn_uint_uint
+     %51 = OpFunctionParameter %uint
+     %52 = OpFunctionParameter %uint
+     %entry_50 = OpLabel
+     %sum = OpIAdd %uint %51 %52
+     OpReturnValue %sum
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFunctionCall %uint %50 %val %val2
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto program_ast_str = test::ToString(p->program());
+  const std::string expected = R"(fn x_50(x_51 : u32, x_52 : u32) -> u32 {
+  return (x_51 + x_52);
+}
+
+fn x_100_1() {
+  let x_1 : u32 = x_50(42u, 84u);
+  return;
+}
+
+@stage(fragment)
+fn x_100() {
+  x_100_1();
+}
+)";
+  EXPECT_EQ(program_ast_str, expected);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_cfg_test.cc b/src/tint/reader/spirv/function_cfg_test.cc
new file mode 100644
index 0000000..ce36521
--- /dev/null
+++ b/src/tint/reader/spirv/function_cfg_test.cc
@@ -0,0 +1,13229 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+// Make a local name so it's easier to triage errors.
+using SpvParserCFGTest = SpvParserTest;
+
+std::string Dump(const std::vector<uint32_t>& v) {
+  std::ostringstream o;
+  o << "{";
+  for (auto a : v) {
+    o << a << " ";
+  }
+  o << "}";
+  return o.str();
+}
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::UnorderedElementsAre;
+
+std::string CommonTypes() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %100 "main"
+    OpExecutionMode %100 OriginUpperLeft
+
+    OpName %var "var"
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %cond = OpConstantNull %bool
+    %cond2 = OpConstantTrue %bool
+    %cond3 = OpConstantFalse %bool
+
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %selector = OpConstant %uint 42
+    %signed_selector = OpConstant %int 42
+
+    %uintfn = OpTypeFunction %uint
+
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+    %uint_3 = OpConstant %uint 3
+    %uint_4 = OpConstant %uint 4
+    %uint_5 = OpConstant %uint 5
+    %uint_6 = OpConstant %uint 6
+    %uint_7 = OpConstant %uint 7
+    %uint_8 = OpConstant %uint 8
+    %uint_10 = OpConstant %uint 10
+    %uint_20 = OpConstant %uint 20
+    %uint_30 = OpConstant %uint 30
+    %uint_40 = OpConstant %uint 40
+    %uint_50 = OpConstant %uint 50
+    %uint_90 = OpConstant %uint 90
+    %uint_99 = OpConstant %uint 99
+
+    %ptr_Private_uint = OpTypePointer Private %uint
+    %var = OpVariable %ptr_Private_uint Private
+
+    %999 = OpConstant %uint 999
+  )";
+}
+
+/// Runs the necessary flow until and including labeling control
+/// flow constructs.
+/// @returns the result of labeling control flow constructs.
+bool FlowLabelControlFlowConstructs(FunctionEmitter* fe) {
+  fe->RegisterBasicBlocks();
+  EXPECT_TRUE(fe->RegisterMerges()) << fe->parser()->error();
+  fe->ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe->VerifyHeaderContinueMergeOrder()) << fe->parser()->error();
+  return fe->LabelControlFlowConstructs();
+}
+
+/// Runs the necessary flow until and including finding switch case
+/// headers.
+/// @returns the result of finding switch case headers.
+bool FlowFindSwitchCaseHeaders(FunctionEmitter* fe) {
+  EXPECT_TRUE(FlowLabelControlFlowConstructs(fe)) << fe->parser()->error();
+  return fe->FindSwitchCaseHeaders();
+}
+
+/// Runs the necessary flow until and including classify CFG edges,
+/// @returns the result of classify CFG edges.
+bool FlowClassifyCFGEdges(FunctionEmitter* fe) {
+  EXPECT_TRUE(FlowFindSwitchCaseHeaders(fe)) << fe->parser()->error();
+  return fe->ClassifyCFGEdges();
+}
+
+/// Runs the necessary flow until and including finding if-selection
+/// internal headers.
+/// @returns the result of classify CFG edges.
+bool FlowFindIfSelectionInternalHeaders(FunctionEmitter* fe) {
+  EXPECT_TRUE(FlowClassifyCFGEdges(fe)) << fe->parser()->error();
+  return fe->FindIfSelectionInternalHeaders();
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_SingleBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %42 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Sequence) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %20 = OpLabel
+     OpBranch %30
+
+     %30 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_If) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %40
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Switch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20 30 %30
+
+     %20 = OpLabel
+     OpBranch %30 ; fall through
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Loop_SingleBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Loop_Simple) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %20 ; back edge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Kill) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpKill
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_Unreachable) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpUnreachable
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.TerminatorsAreValid());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_MissingTerminator) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+
+     OpFunctionEnd
+  )"));
+  // The SPIRV-Tools internal representation rejects this case earlier.
+  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowLoopToEntryBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpBranch %10 ; not allowed
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.TerminatorsAreValid());
+  EXPECT_THAT(p->error(), Eq("Block 20 branches to function entry block 10"));
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowNonBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %999 ; definitely wrong
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.TerminatorsAreValid());
+  EXPECT_THAT(p->error(),
+              Eq("Block 10 in function 100 branches to 999 which is "
+                 "not a block in the function"));
+}
+
+TEST_F(SpvParserCFGTest, TerminatorsAreValid_DisallowBlockInDifferentFunction) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %210
+
+     OpFunctionEnd
+
+
+     %200 = OpFunction %void None %voidfn
+
+     %210 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.TerminatorsAreValid());
+  EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 210 which "
+                             "is not a block in the function"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_NoMerges) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  const auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->merge_for_header, 0u);
+  EXPECT_EQ(bi->continue_for_header, 0u);
+  EXPECT_EQ(bi->header_for_merge, 0u);
+  EXPECT_EQ(bi->header_for_continue, 0u);
+  EXPECT_FALSE(bi->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_GoodSelectionMerge_BranchConditional) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Header points to the merge
+  const auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->merge_for_header, 99u);
+  EXPECT_EQ(bi10->continue_for_header, 0u);
+  EXPECT_EQ(bi10->header_for_merge, 0u);
+  EXPECT_EQ(bi10->header_for_continue, 0u);
+  EXPECT_FALSE(bi10->is_continue_entire_loop);
+
+  // Middle block is neither header nor merge
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 0u);
+  EXPECT_EQ(bi20->continue_for_header, 0u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 0u);
+  EXPECT_FALSE(bi20->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 10u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_GoodSelectionMerge_Switch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Header points to the merge
+  const auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->merge_for_header, 99u);
+  EXPECT_EQ(bi10->continue_for_header, 0u);
+  EXPECT_EQ(bi10->header_for_merge, 0u);
+  EXPECT_EQ(bi10->header_for_continue, 0u);
+  EXPECT_FALSE(bi10->is_continue_entire_loop);
+
+  // Middle block is neither header nor merge
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 0u);
+  EXPECT_EQ(bi20->continue_for_header, 0u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 0u);
+  EXPECT_FALSE(bi20->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 10u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_GoodLoopMerge_SingleBlockLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Entry block is not special
+  const auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->merge_for_header, 0u);
+  EXPECT_EQ(bi10->continue_for_header, 0u);
+  EXPECT_EQ(bi10->header_for_merge, 0u);
+  EXPECT_EQ(bi10->header_for_continue, 0u);
+  EXPECT_FALSE(bi10->is_continue_entire_loop);
+
+  // Single block loop is its own continue, and marked as single block loop.
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 99u);
+  EXPECT_EQ(bi20->continue_for_header, 20u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 20u);
+  EXPECT_TRUE(bi20->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 20u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest,
+       RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Loop header points to continue (itself) and merge
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 99u);
+  EXPECT_EQ(bi20->continue_for_header, 20u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 20u);
+  EXPECT_TRUE(bi20->is_continue_entire_loop);
+
+  // Backedge block, but is not a declared header, merge, or continue
+  const auto* bi40 = fe.GetBlockInfo(40);
+  ASSERT_NE(bi40, nullptr);
+  EXPECT_EQ(bi40->merge_for_header, 0u);
+  EXPECT_EQ(bi40->continue_for_header, 0u);
+  EXPECT_EQ(bi40->header_for_merge, 0u);
+  EXPECT_EQ(bi40->header_for_continue, 0u);
+  EXPECT_FALSE(bi40->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 20u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest,
+       RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranchConditional %cond %40 %99
+
+     %40 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Loop header points to continue and merge
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 99u);
+  EXPECT_EQ(bi20->continue_for_header, 40u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 0u);
+  EXPECT_FALSE(bi20->is_continue_entire_loop);
+
+  // Continue block points to header
+  const auto* bi40 = fe.GetBlockInfo(40);
+  ASSERT_NE(bi40, nullptr);
+  EXPECT_EQ(bi40->merge_for_header, 0u);
+  EXPECT_EQ(bi40->continue_for_header, 0u);
+  EXPECT_EQ(bi40->header_for_merge, 0u);
+  EXPECT_EQ(bi40->header_for_continue, 20u);
+  EXPECT_FALSE(bi40->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 20u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) {  // NOLINT
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_TRUE(fe.RegisterMerges());
+
+  // Loop header points to continue and merge
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->merge_for_header, 99u);
+  EXPECT_EQ(bi20->continue_for_header, 40u);
+  EXPECT_EQ(bi20->header_for_merge, 0u);
+  EXPECT_EQ(bi20->header_for_continue, 0u);
+  EXPECT_FALSE(bi20->is_continue_entire_loop);
+
+  // Continue block points to header
+  const auto* bi40 = fe.GetBlockInfo(40);
+  ASSERT_NE(bi40, nullptr);
+  EXPECT_EQ(bi40->merge_for_header, 0u);
+  EXPECT_EQ(bi40->continue_for_header, 0u);
+  EXPECT_EQ(bi40->header_for_merge, 0u);
+  EXPECT_EQ(bi40->header_for_continue, 20u);
+  EXPECT_FALSE(bi40->is_continue_entire_loop);
+
+  // Merge block points to the header
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->merge_for_header, 0u);
+  EXPECT_EQ(bi99->continue_for_header, 0u);
+  EXPECT_EQ(bi99->header_for_merge, 20u);
+  EXPECT_EQ(bi99->header_for_continue, 0u);
+  EXPECT_FALSE(bi99->is_continue_entire_loop);
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_SelectionMerge_BadTerminator) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(), Eq("Selection header 10 does not end in an "
+                             "OpBranchConditional or OpSwitch instruction"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_LoopMerge_BadTerminator) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpSwitch %selector %99 30 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(), Eq("Loop header 20 does not end in an OpBranch or "
+                             "OpBranchConditional instruction"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_BadMergeBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %void None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(),
+              Eq("Structured header block 10 declares invalid merge block 2"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_HeaderIsItsOwnMerge) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %10 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(),
+              Eq("Structured header block 10 cannot be its own merge block"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_MergeReused) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %20 %49
+
+     %20 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %49 None  ; can't reuse merge block
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 49 declared as merge block for more than one header: 10, 50"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_EntryBlockIsLoopHeader) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %10 %99
+
+     %30 = OpLabel
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(),
+              Eq("Function entry block 10 cannot be a loop header"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_BadContinueTarget) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %999 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(),
+              Eq("Structured header 20 declares invalid continue target 999"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_MergeSameAsContinue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %50 %50 None
+     OpBranchConditional %cond %20 %99
+
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(),
+              Eq("Invalid structured header block 20: declares block 50 as "
+                 "both its merge block and continue target"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_ContinueReused) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond %30 %49
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %20
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel
+     OpBranch %50
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(), Eq("Block 40 declared as continue target for more "
+                             "than one header: 20, 50"));
+}
+
+TEST_F(SpvParserCFGTest, RegisterMerges_SingleBlockLoop_NotItsOwnContinue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %20 %99
+
+     %30 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 20 branches to itself but is not its own continue target"));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_OneBlock) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %42 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(42));
+
+  const auto* bi = fe.GetBlockInfo(42);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->pos, 0u);
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_IgnoreStaticalyUnreachable) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %15 = OpLabel ; statically dead
+     OpReturn
+
+     %20 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_KillIsDeadEnd) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %15 = OpLabel ; statically dead
+     OpReturn
+
+     %20 = OpLabel
+     OpKill        ; Kill doesn't lead anywhere
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_UnreachableIsDeadEnd) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %15 = OpLabel ; statically dead
+     OpReturn
+
+     %20 = OpLabel
+     OpUnreachable ; Unreachable doesn't lead anywhere
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_ReorderSequence) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %30 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %30 ; backtrack, but does dominate %30
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99));
+
+  const auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->pos, 0u);
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->pos, 1u);
+  const auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->pos, 2u);
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->pos, 3u);
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_DupConditionalBranch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectConditionalBranchOrder) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %30 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; dominated by %20, so follow %20
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_TrueOnlyBranch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_FalseOnlyBranch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_SwitchOrderNaturallyReversed) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %99 = OpLabel
+     OpReturn
+
+     %30 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 99));
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20 30 %30
+
+     %80 = OpLabel ; the default case
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     %30 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Switch_DefaultSameAsACase) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20 30 %30 40 %40
+
+     %99 = OpLabel
+     OpReturn
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 40, 20, 30, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectSwitchCaseFallthrough) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     ; SPIR-V validation requires a fallthrough destination to immediately
+     ; follow the source. So %20 -> %40, %30 -> %50
+     OpSwitch %selector %99 20 %20 40 %40 30 %30 50 %50
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %50 ; fallthrough
+
+     %20 = OpLabel
+     OpBranch %40 ; fallthrough
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 20, 40, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20 30 %30 40 %40
+
+     %80 = OpLabel ; the default case
+     OpBranch %30 ; fallthrough to another case
+
+     %99 = OpLabel
+     OpReturn
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %20 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 40, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20 30 %30
+
+     %20 = OpLabel
+     OpBranch %80 ; fallthrough to default
+
+     %80 = OpLabel ; the default case
+     OpBranch %30 ; fallthrough to 30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; dominated by %30, so follow %30
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_SwitchCasesFallthrough_OppositeDirections) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %30 ; forward
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     ; SPIR-V doesn't actually allow a fall-through that goes backward in the
+     ; module. But the block ordering algorithm tolerates it.
+     %50 = OpLabel
+     OpBranch %40 ; backward
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 50, 40, 20, 30, 99))
+      << assembly;
+
+  // We're deliberately testing a case that SPIR-V doesn't allow.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     ; SPIR-V validation requires a fallthrough destination to immediately
+     ; follow the source. So %20 -> %40
+     OpSwitch %selector %99 20 %20 40 %40 30 %30 50 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpBranch %40
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %40 = OpLabel
+     OpBranch %60
+
+     %50 = OpLabel
+     OpBranch %70
+
+     %60 = OpLabel
+     OpBranch %99
+
+     %70 = OpLabel
+     OpBranch %99
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 70, 20, 40, 60, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_If_Contains_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %30 %40
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %49
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %50 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond %60 %70
+
+     %79 = OpLabel
+     OpBranch %99
+
+     %60 = OpLabel
+     OpBranch %79
+
+     %70 = OpLabel
+     OpBranch %79
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_If_In_SwitchCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %50 20 %20 50 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %30 %40
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %49
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %50 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond %60 %70
+
+     %79 = OpLabel
+     OpBranch %99
+
+     %60 = OpLabel
+     OpBranch %79
+
+     %70 = OpLabel
+     OpBranch %79
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %50 20 %20 50 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %30 %40
+
+     %49 = OpLabel
+     OpBranchConditional %cond %99 %50 ; fallthrough
+
+     %30 = OpLabel
+     OpBranch %49
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %50 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond %60 %70
+
+     %79 = OpLabel
+     OpBranch %99
+
+     %60 = OpLabel
+     OpBranch %79
+
+     %70 = OpLabel
+     OpBranch %79
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Nest_IfBreak_In_SwitchCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %50 20 %20 50 %50
+
+     %99 = OpLabel
+     OpReturn
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %99 %40 ; break-if
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond %60 %99 ; break-unless
+
+     %60 = OpLabel
+     OpBranch %79
+
+     %79 = OpLabel ; dominated by 60, so must follow 60
+     OpBranch %99
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 49, 50, 60, 79, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_Simple) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     ; The entry block can't be the target of a branch
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_Infinite) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     ; The entry block can't be the target of a branch
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_SingleBlock_DupInfinite) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     ; The entry block can't be the target of a branch
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_HeaderHasBreakIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99 ; like While
+
+     %30 = OpLabel ; trivial body
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_HeaderHasBreakUnless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %99 %30 ; has break-unless
+
+     %30 = OpLabel ; trivial body
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreak) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99 ; break
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreakIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %99 %40 ; break-if
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasBreakUnless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %40 %99 ; break-unless
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %40 %45 ; nested if
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %45 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 45, 49, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If_Break) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %40 %49 ; nested if
+
+     %40 = OpLabel
+     OpBranch %99   ; break from nested if
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasContinueIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %50 %40 ; continue-if
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_BodyHasContinueUnless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %40 %50 ; continue-unless
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_If_Continue) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %40 %49 ; nested if
+
+     %40 = OpLabel
+     OpBranch %50   ; continue from nested if
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpSwitch %selector %49 40 %40 45 %45 ; fully nested switch
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %45 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
+      << assembly;
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch_CaseBreaks) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpSwitch %selector %49 40 %40 45 %45
+
+     %40 = OpLabel
+     ; This case breaks out of the loop. This is not possible in C
+     ; because "break" will escape the switch only.
+     OpBranch %99
+
+     %45 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
+      << assembly;
+
+  // Fails SPIR-V validation:
+  // Branch from block 40 to block 99 is an invalid exit from construct starting
+  // at block 30; branch bypasses merge block 49
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Body_Switch_CaseContinues) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %49 None
+     OpSwitch %selector %49 40 %40 45 %45
+
+     %40 = OpLabel
+     OpBranch %50   ; continue bypasses switch merge
+
+     %45 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
+      << assembly;
+}
+
+// TODO(crbug.com/tint/1406): Re-enable with the typo fix (preceeded->preceded)
+// once that typo fix is rolled in Tint's SPIRV-Tools.
+TEST_F(SpvParserCFGTest,
+       DISABLED_ComputeBlockOrder_Loop_BodyHasSwitchContinueBreak) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     ; OpSwitch must be preceded by a selection merge
+     OpSwitch %selector %99 50 %50 ; default is break, 50 is continue
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(),
+              HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge"));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_Sequence) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %60
+
+     %60 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_ContainsIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond2 %60 %70
+
+     %89 = OpLabel
+     OpBranch %20 ; backedge
+
+     %60 = OpLabel
+     OpBranch %89
+
+     %70 = OpLabel
+     OpBranch %89
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 70, 89, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_HasBreakIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranchConditional %cond2 %99 %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Continue_HasBreakUnless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranchConditional %cond2 %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
+}
+
+// TODO(crbug.com/tint/1406): Re-enable with the typo fix (preceeded->preceded)
+// once that typo fix is rolled in Tint's SPIRV-Tools.
+TEST_F(SpvParserCFGTest, DISABLED_ComputeBlockOrder_Loop_Continue_SwitchBreak) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     ; Updated SPIR-V rule:
+     ; OpSwitch must be preceded by a selection.
+     OpSwitch %selector %20 99 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(),
+              HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge"));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranch %37
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     OpBranch %30 ; backedge
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerBreak) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranchConditional %cond3 %49 %37 ; break to inner merge
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     OpBranch %30 ; backedge
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinue) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranchConditional %cond3 %37 %49 ; continue to inner continue target
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     OpBranch %30 ; backedge
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinueBreaks) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranch %37
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     OpBranchConditional %cond3 %30 %49 ; backedge and inner break
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+}
+
+TEST_F(SpvParserCFGTest, ComputeBlockOrder_Loop_Loop_InnerContinueContinues) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranch %37
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     OpBranchConditional %cond3 %30 %50 ; backedge and continue to outer
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+
+  p->DeliberatelyInvalidSpirv();
+  // SPIR-V validation fails:
+  //    block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not
+  //    via a structured exit"
+}
+
+TEST_F(SpvParserCFGTest,
+       ComputeBlockOrder_Loop_Loop_SwitchBackedgeBreakContinue) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpLoopMerge %49 %40 None
+     OpBranchConditional %cond2 %35 %49
+
+     %35 = OpLabel
+     OpBranch %37
+
+     %37 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner loop's continue
+     ; This switch does triple duty:
+     ; default -> backedge
+     ; 49 -> loop break
+     ; 49 -> inner loop break
+     ; 50 -> outer loop continue
+     OpSwitch %selector %30 49 %49 50 %50
+
+     %49 = OpLabel ; inner loop's merge
+     OpBranch %50
+
+     %50 = OpLabel ; outer loop's continue
+     OpBranch %20 ; outer loop's backege
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+
+  EXPECT_THAT(fe.block_order(),
+              ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
+
+  p->DeliberatelyInvalidSpirv();
+  // SPIR-V validation fails:
+  //    block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not
+  //    via a structured exit"
+}
+
+TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_Selection_Good) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+}
+
+TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()) << p->error();
+}
+
+TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+}
+
+TEST_F(SpvParserCFGTest,
+       VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %20 None ; this is backward
+     OpBranchConditional %cond2 %60 %99
+
+     %60 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_THAT(p->error(),
+              Eq("Header 50 does not strictly dominate its merge block 20"))
+      << *fe.GetBlockInfo(50) << std::endl
+      << *fe.GetBlockInfo(20) << std::endl
+      << Dump(fe.block_order());
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) {  // NOLINT
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpLoopMerge %99 %20 None ; this is backward
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %50
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_THAT(p->error(),
+              Eq("Loop header 50 does not dominate its continue target 20"))
+      << *fe.GetBlockInfo(50) << std::endl
+      << *fe.GetBlockInfo(20) << std::endl
+      << Dump(fe.block_order());
+}
+
+TEST_F(SpvParserCFGTest,
+       VerifyHeaderContinueMergeOrder_MergeInsideContinueTarget) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpLoopMerge %60 %70 None
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel
+     OpBranch %50
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_THAT(p->error(),
+              Eq("Merge block 60 for loop headed at block 50 appears at or "
+                 "before the loop's continue construct headed by block 70"))
+      << Dump(fe.block_order());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  EXPECT_EQ(fe.constructs().size(), 1u);
+  auto& c = fe.constructs().front();
+  EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,1) begin_id:10 end_id:0 "
+                              "depth:0 parent:null }"));
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %5
+
+     %5 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  EXPECT_EQ(fe.constructs().size(), 1u);
+  auto& c = fe.constructs().front();
+  EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,2) begin_id:10 end_id:0 "
+                              "depth:0 parent:null }"));
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
+  EXPECT_EQ(fe.GetBlockInfo(5)->construct, c.get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 2u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpBranch %200
+
+     %200 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 2u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,6) begin_id:5 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [1,4) begin_id:10 end_id:99 depth:1 parent:Function@5 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(5)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(200)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_SwitchSelection) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %40 20 %20 30 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 2u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_SingleBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 2u);
+  // A single-block loop consists *only* of a continue target with one block in
+  // it.
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,3) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [1,2) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue) {
+  // In this case, we have a continue construct and a non-empty loop construct.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [3,5) begin_id:40 end_id:99 depth:1 parent:Function@10 in-c:Continue@40 }
+  Construct{ Loop [1,3) begin_id:20 end_id:40 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue) {
+  // In this case, we have only a continue construct and no loop construct.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [1,5) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %50
+
+     ; %50 is the merge block for the selection starting at 10,
+     ; and its own continue target.
+     %50 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %50 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 3u);
+  // A single-block loop consists *only* of a continue target with one block in
+  // it.
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
+  Construct{ Continue [2,3) begin_id:50 end_id:99 depth:1 parent:Function@10 in-c:Continue@50 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest,
+       LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %50
+
+     ; %50 is the merge block for the selection starting at 10,
+     ; and a loop block header but not its own continue target.
+     %50 = OpLabel
+     OpLoopMerge %99 %60 None
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %50
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
+  Construct{ Continue [3,4) begin_id:60 end_id:99 depth:1 parent:Function@10 in-c:Continue@60 }
+  Construct{ Loop [2,3) begin_id:50 end_id:60 depth:1 parent:Function@10 scope:[2,4) in-l:Loop@50 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %40 ;; true only
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; merge for first inner "if"
+     OpBranch %49
+
+     %49 = OpLabel ; an extra padding block
+     OpBranch %99
+
+     %50 = OpLabel
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %89 %60 ;; false only
+
+     %60 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,9) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,8) begin_id:10 end_id:99 depth:1 parent:Function@10 }
+  Construct{ IfSelection [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 }
+  Construct{ IfSelection [5,7) begin_id:50 end_id:89 depth:2 parent:IfSelection@10 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Switch_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel ; if-then nested in case 20
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %30 %49
+
+     %30 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel ; unles-then nested in case 50
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %89 %60
+
+     %60 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  // The ordering among siblings depends on the computed block order.
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ SwitchSelection [0,7) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
+  Construct{ IfSelection [1,3) begin_id:50 end_id:89 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
+  Construct{ IfSelection [4,6) begin_id:20 end_id:49 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_Switch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %89 20 %30
+
+     %30 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 3u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 }
+  Construct{ SwitchSelection [1,3) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c-l-s:SwitchSelection@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Loop_Loop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %89 %50 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel ; single block loop
+     OpLoopMerge %40 %30 None
+     OpBranchConditional %cond2 %30 %40
+
+     %40 = OpLabel ; padding block
+     OpBranch %50
+
+     %50 = OpLabel ; outer continue target
+     OpBranch %60
+
+     %60 = OpLabel
+     OpBranch %20
+
+     %89 = OpLabel ; outer merge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 }
+  Construct{ Loop [1,4) begin_id:20 end_id:50 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
+  Construct{ Continue [2,3) begin_id:30 end_id:40 depth:2 parent:Loop@20 in-l:Loop@20 in-c:Continue@30 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_Loop_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; If, nested in the loop construct
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %40 %49
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel ; merge for inner if
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 }
+  Construct{ Loop [1,5) begin_id:20 end_id:80 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
+  Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Loop@20 in-l:Loop@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(80)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_LoopContinue_If) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; If, nested at the top of the continue construct head
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %40 %49
+
+     %40 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel ; merge for inner if, backedge
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 }
+  Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
+  Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Continue@30 in-c:Continue@30 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_SingleBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpLoopMerge %89 %20 None
+     OpBranchConditional %cond %20 %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 3u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
+  Construct{ Continue [1,2) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel ; start loop body
+     OpLoopMerge %89 %40 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel ; body block
+     OpBranch %40
+
+     %40 = OpLabel ; continue target
+     OpBranch %50
+
+     %50 = OpLabel ; backedge block
+     OpBranch %20
+
+     %89 = OpLabel ; merge for the loop
+     OpBranch %99
+
+     %99 = OpLabel ; merge for the if
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  fe.RegisterMerges();
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 }
+  Construct{ Continue [3,5) begin_id:40 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@40 }
+  Construct{ Loop [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 scope:[1,5) in-l:Loop@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
+  EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, LabelControlFlowConstructs_LoopInterallyDiverge) {
+  // In this case, insert a synthetic if-selection with the same blocks
+  // as the loop construct.
+  // crbug.com/tint/524
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranchConditional %cond %30 %40 ; divergence to distinct targets in the body
+
+       %30 = OpLabel
+       OpBranch %90
+
+       %40 = OpLabel
+       OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
+  const auto& constructs = fe.constructs();
+  EXPECT_EQ(constructs.size(), 4u);
+  ASSERT_THAT(ToString(constructs), Eq(R"(ConstructList{
+  Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
+  Construct{ Continue [4,5) begin_id:90 end_id:99 depth:1 parent:Function@10 in-c:Continue@90 }
+  Construct{ Loop [1,4) begin_id:20 end_id:90 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
+  Construct{ IfSelection [1,4) begin_id:20 end_id:90 depth:2 parent:Loop@20 in-l:Loop@20 }
+})")) << constructs;
+  // The block records the nearest enclosing construct.
+  EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
+  EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
+  EXPECT_EQ(fe.GetBlockInfo(90)->construct, constructs[1].get());
+  EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsLongRangeBackedge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %10 30 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to default target "
+                             "block 10 can't be a back-edge"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsSelfLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %20 30 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  // Self-loop that isn't its own continue target is already rejected with a
+  // different message.
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 20 branches to itself but is not its own continue target"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultCantEscapeSwitch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %50 None
+     OpSwitch %selector %99 30 %30 ; default goes past the merge
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; merge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to default block 99 "
+                             "escapes the selection construct"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultForTwoSwitches_AsMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %89 20 %20
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %89 60 %60
+
+     %60 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(),
+              Eq("Block 89 is the default block for switch-selection header 10 "
+                 "and also the merge block for 50 (violates dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindSwitchCaseHeaders_DefaultForTwoSwitches_AsCaseClause) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %80 60 %60
+
+     %60 = OpLabel
+     OpBranch %89 ; fallthrough
+
+     %80 = OpLabel ; default for both switches
+     OpBranch %89
+
+     %89 = OpLabel ; inner selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer selection mege
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Block 80 is declared as the default target for "
+                             "two OpSwitch instructions, at blocks 10 and 50"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsLongRangeBackedge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 10 %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target "
+                             "block 10 can't be a back-edge"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsSelfLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  // The error is caught earlier
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 20 branches to itself but is not its own continue target"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseCanBeSwitchMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  // TODO(crbug.com/tint/774) Re-enable after codegen bug fixed.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseCantEscapeSwitch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; force %99 to be very late in block order
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %89 20 %99
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target block "
+                             "99 escapes the selection construct"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseForMoreThanOneSwitch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %89 50 %50
+
+     %50 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is declared as the switch case target for two "
+                 "OpSwitch instructions, at blocks 10 and 20"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsMergeForAnotherConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %49 None
+     OpSwitch %selector %49 20 %20
+
+     %20 = OpLabel
+     OpBranch %49
+
+     %49 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %20 None ; points back to the case.
+     OpBranchConditional %cond %60 %99
+
+     %60 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to case target block "
+                             "20 escapes the selection construct"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_NoSwitch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->case_head_for, nullptr);
+  EXPECT_EQ(bi10->default_head_for, nullptr);
+  EXPECT_FALSE(bi10->default_is_merge);
+  EXPECT_EQ(bi10->case_values.get(), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->case_head_for, nullptr);
+  ASSERT_NE(bi99->default_head_for, nullptr);
+  EXPECT_EQ(bi99->default_head_for->begin_id, 10u);
+  EXPECT_TRUE(bi99->default_is_merge);
+  EXPECT_EQ(bi99->case_values.get(), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsNotMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->case_head_for, nullptr);
+  ASSERT_NE(bi30->default_head_for, nullptr);
+  EXPECT_EQ(bi30->default_head_for->begin_id, 10u);
+  EXPECT_FALSE(bi30->default_is_merge);
+  EXPECT_EQ(bi30->case_values.get(), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsNotDefault) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 200 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->case_head_for, nullptr);
+  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
+  EXPECT_EQ(bi20->default_head_for, nullptr);
+  EXPECT_FALSE(bi20->default_is_merge);
+  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsDefault) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %20 200 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->case_head_for, nullptr);
+  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
+  EXPECT_EQ(bi20->default_head_for, bi20->case_head_for);
+  EXPECT_FALSE(bi20->default_is_merge);
+  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_ManyCasesWithSameValue_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 200 %20 200 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+
+  EXPECT_THAT(p->error(),
+              Eq("Duplicate case value 200 in OpSwitch in block 10"));
+}
+
+TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_ManyValuesWithSameCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 200 %20 300 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_TRUE(fe.FindSwitchCaseHeaders());
+
+  const auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->case_head_for, nullptr);
+  EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
+  EXPECT_EQ(bi20->default_head_for, nullptr);
+  EXPECT_FALSE(bi20->default_is_merge);
+  EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200, 300));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BranchEscapesIfConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %30 %50
+
+     %30 = OpLabel
+     OpBranch %80   ; bad exit to %80
+
+     %50 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel  ; bad target
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
+  // Some further processing
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 80 is an invalid exit from construct "
+         "starting at block 20; branch bypasses merge block 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_ReturnInContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; body
+     OpBranch %50
+
+     %50 = OpLabel
+     OpReturn
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
+  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
+                             "construct starting at 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_KillInContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; body
+     OpBranch %50
+
+     %50 = OpLabel
+     OpKill
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
+                             "construct starting at 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_UnreachableInContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; body
+     OpBranch %50
+
+     %50 = OpLabel
+     OpUnreachable
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
+                             "construct starting at 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_NotInContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; body
+     OpBranch %20  ; bad backedge
+
+     %50 = OpLabel ; continue target
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_BackEdge_NotInLastBlockOfContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; body
+     OpBranch %50
+
+     %50 = OpLabel ; continue target
+     OpBranchConditional %cond %20 %60 ; bad branch to %20
+
+     %60 = OpLabel ; end of continue construct
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (50->20) from continue construct: 50 is not the "
+                 "last block in the continue construct starting at 50 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_ToWrongHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpLoopMerge %89 %50 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel ; loop body
+     OpBranch %50
+
+     %50 = OpLabel ; continue target
+     OpBranch %10
+
+     %89 = OpLabel ; inner merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(), Eq("Invalid backedge (50->10): does not branch to "
+                             "the corresponding loop header, expected 20"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_SingleBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi20->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; continue target
+     OpBranch %20  ; good back edge
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi40 = fe.GetBlockInfo(40);
+  ASSERT_NE(bi40, nullptr);
+  EXPECT_EQ(bi40->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) {  // NOLINT
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; continue target
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %20  ; good back edge
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi50 = fe.GetBlockInfo(50);
+  ASSERT_NE(bi50, nullptr);
+  EXPECT_EQ(bi50->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) {  // NOLINT
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None ; continue target
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranchConditional %cond %20 %99 ; good back edge
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
+
+  auto* bi50 = fe.GetBlockInfo(50);
+  ASSERT_NE(bi50, nullptr);
+  EXPECT_EQ(bi50->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_PrematureExitFromContinueConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; continue construct
+     OpBranchConditional %cond2 %99 %50 ; invalid early exit
+
+     %50 = OpLabel
+     OpBranch %20  ; back edge
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (40->99) from continue construct: 40 is not the "
+                 "last block in the continue construct starting at 40 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel    ; single block loop
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel    ; single block loop
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; Single block continue construct
+     OpBranchConditional %cond2 %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromIfHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromIfThenElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  // Then clause
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+
+  // Else clause
+  auto* bi50 = fe.GetBlockInfo(50);
+  ASSERT_NE(bi50, nullptr);
+  EXPECT_EQ(bi50->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi50->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_BypassesMerge_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel ; merge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 20 to block 99 is an invalid exit from "
+         "construct starting at block 10; branch bypasses merge block 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_EscapeSwitchCase_IsError) {
+  // Code generation assumes that you can't have kCaseFallThrough and kIfBreak
+  // from the same OpBranchConditional.
+  // This checks one direction of that, where the IfBreak is shown it can't
+  // escape a switch case.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; Set up if-break to %99
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 30 %30 40 %40
+
+     %30 = OpLabel ; first case
+        ; branch to %99 would be an if-break, but it bypasess the switch merge
+        ; Also has case fall-through
+     OpBranchConditional %cond2 %99 %40
+
+     %40 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch-selection's merge
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %99 ; directly to merge
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond %30 %80
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %80 = OpLabel ; inner merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond %30 %80
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %99 %80 ; break-if
+
+     %80 = OpLabel ; inner merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_BypassesMerge_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %50 None
+     OpSwitch %selector %50 20 %20
+
+     %20 = OpLabel
+     OpBranch %99 ; invalid exit
+
+     %50 = OpLabel ; switch merge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 20 to block 99 is an invalid exit from "
+         "construct starting at block 10; branch bypasses merge block 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_SwitchBreak_FromNestedLoop_IsError) {
+  // It's an error because the break can only go as far as the loop.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpLoopMerge %80 %70 None
+     OpBranchConditional %cond %30 %80
+
+     %30 = OpLabel ; in loop construct
+     OpBranch %99 ; break
+
+     %70 = OpLabel
+     OpBranch %20
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_SwitchBreak_FromNestedSwitch_IsError) {
+  // It's an error because the break can only go as far as inner switch
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None
+     OpSwitch %selector %80 30 %30
+
+     %30 = OpLabel
+     OpBranch %99 ; break
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromLoopBody) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %50 %99 ; break-unless
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromContinueConstructTail) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; continue target
+     OpBranch %60
+
+     %60 = OpLabel ; continue construct tail
+     OpBranchConditional %cond2 %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(60);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99  ; unconditional break
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpBranch %99 ; deeply nested break
+
+     %50 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %20  ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpBranchConditional %cond3 %99 %50 ; break-if
+
+     %50 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %20  ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromContinueConstructNestedFlow_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %40 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; continue construct
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond2 %50 %79
+
+     %50 = OpLabel
+     OpBranchConditional %cond3 %99 %79 ; attempt to break to 99 should fail
+
+     %79 = OpLabel
+     OpBranch %80  ; inner merge
+
+     %80 = OpLabel
+     OpBranch %20  ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (50->99) from continue construct: 50 is not the "
+                 "last block in the continue construct starting at 40 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopBypassesMerge_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %50 %40 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %99 ; bad exit
+
+     %40 = OpLabel ; continue construct
+     OpBranch %20
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 50"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopBreak_FromContinueBypassesMerge_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %50 %40 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; continue construct
+     OpBranch %45
+
+     %45 = OpLabel
+     OpBranchConditional %cond2 %20 %99 ; branch to %99 is bad exit
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 45 to block 99 is an invalid exit from "
+         "construct starting at block 40; branch bypasses merge block 50"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopContinue_LoopBodyToContinue) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %80 ; a forward edge
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_LoopContinue_FromNestedIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond2 %40 %79
+
+     %40 = OpLabel
+     OpBranch %80 ; continue
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpBranchConditional %cond2 %40 %79
+
+     %40 = OpLabel
+     OpBranchConditional %cond2 %80 %79 ; continue-if
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40
+
+     %40 = OpLabel
+     OpBranch %80
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseDirect_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %80 ; continue here
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_TRUE(fe.RegisterMerges());
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to case target block "
+                             "80 escapes the selection construct"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultDirect_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %80 40 %79 ; continue here
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_TRUE(fe.RegisterMerges());
+  EXPECT_TRUE(fe.LabelControlFlowConstructs());
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to default block 80 "
+                             "escapes the selection construct"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %40 79 %79
+
+     %40 = OpLabel
+     OpBranchConditional %cond2 %80 %79
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %40 79 %79
+
+     %40 = OpLabel
+     OpBranch %80
+
+     %79 = OpLabel ; inner merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(40);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError) {
+  // Inner loop header tries to do continue to outer loop continue target.
+  // This is disallowed by the rule:
+  //    "a continue block is valid only for the innermost loop it is nested
+  //    inside of"
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; inner loop.
+     OpStore %var %uint_1
+     OpLoopMerge %59 %50 None
+     OpBranchConditional %cond %59 %80  ; break and outer continue
+
+     %50 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %30 ; inner backedge
+
+     %59 = OpLabel ; inner merge
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; outer continue
+     OpStore %var %uint_4
+     OpBranch %20 ; outer backedge
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 80 is an invalid exit from construct "
+         "starting at block 30; branch bypasses merge block 59"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Fallthrough_CaseTailToCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 40 %40
+
+     %20 = OpLabel ; case 20
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %40 ; fallthrough
+
+     %40 = OpLabel ; case 40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(40), 1u);
+  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %40 20 %20
+
+     %20 = OpLabel ; case 20
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %40 ; fallthrough
+
+     %40 = OpLabel ; case 40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(40), 1u);
+  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Fallthrough_DefaultToCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %20 40 %40
+
+     %20 = OpLabel ; default
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %40 ; fallthrough
+
+     %40 = OpLabel ; case 40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(30);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(40), 1u);
+  EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_IfBreak_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kIfBreak.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; Set up if-break to %99
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 30 %30 40 %40
+
+     %30 = OpLabel ; first case
+        ; branch to %99 would be an if-break, but it bypasess the switch merge
+        ; Also has case fall-through
+     OpBranchConditional %cond2 %99 %40
+
+     %40 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch-selection's merge
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kForward.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 30 %30
+
+     ; Try to make branch to 35 a kForward branch
+     %20 = OpLabel ; first case
+     OpBranchConditional %cond2 %25 %30
+
+     %25 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel ; second case
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 20 (to 25, 30) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnOutside_IsError) {  // NOLINT
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the outside. The backedge coming from a case
+  // clause means the switch is inside the continue construct, and the nesting
+  // of the switch's merge means the backedge is coming from a block that is not
+  // at the end of the continue construct.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranch %30
+
+     %30 = OpLabel  ; continue target and
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 40 %40 50 %50
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel ; first case
+     OpBranchConditional %cond2 %20 %50
+
+     %50 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch merge
+     OpBranch %20  ; also backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (40->20) from continue construct: 40 is not the "
+                 "last block in the continue construct starting at 30 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    FindSwitchCaseSelectionHeaders_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsMerge_IsError) {  // NOLINT
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The merge block is also the
+  // fallthrough target.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel ; first case, and loop header
+     OpLoopMerge %50 %40 None
+     OpBranch %40
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel
+     OpBranchConditional %cond2 %20 %50
+
+     %50 = OpLabel ; second case.  also the loop merge ; header must dominate its merge block !
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 99));
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is a case block for switch-selection header 10 and "
+                 "also the merge block for 20 (violates dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsNotMerge_IsError) {  // NOLINT
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The merge block is not the merge
+  // target But the block order gets messed up because of the weird
+  // connectivity.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel ; first case, and loop header
+     OpLoopMerge %45 %40 None  ; move the merge to an unreachable block
+     OpBranch %40
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel
+     OpBranchConditional %cond2 %20 %50
+
+     %45 = OpLabel ; merge for the loop
+     OpUnreachable
+
+     %50 = OpLabel ; second case. target of fallthrough
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
+                             "(dominance rule violated)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_NestedMerge_IsError) {  // NOLINT
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The fallthrough is an invalid exit
+  // from the loop. However, the block order gets all messed up because going
+  // from 40 to 50 ends up pulling in 99
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+       %20 = OpLabel ; first case, and loop header
+       OpLoopMerge %49 %40 None
+       OpBranch %40
+
+       ; try to make a back edge with a fallthrough
+       %40 = OpLabel
+       OpBranchConditional %cond2 %20 %50
+
+       %49 = OpLabel ; loop merge
+       OpBranch %99
+
+     %50 = OpLabel ; second case
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 49, 99));
+  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
+                             "(dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_TrueBranch) {
+  // This is an unusual one, and is an error. Structurally it looks like this:
+  //   switch (val) {
+  //   case 0: {
+  //        if (cond) {
+  //          fallthrough;
+  //        }
+  //        something = 1;
+  //      }
+  //   case 1: { }
+  //   }
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %30 %49
+
+     %30 = OpLabel
+     OpBranch %50 ; attempt to fallthrough
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_FalseBranch) {
+  // Like previous test, but taking the false branch.
+
+  // This is an unusual one, and is an error. Structurally it looks like this:
+  //   switch (val) {
+  //   case 0: {
+  //        if (cond) {
+  //          fallthrough;
+  //        }
+  //        something = 1;
+  //      }
+  //   case 1: { }
+  //   }
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond %49 %30 ;; this is the difference
+
+     %30 = OpLabel
+     OpBranch %50 ; attempt to fallthrough
+
+     %49 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_IfToThen) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_IfToElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(30), 1u);
+  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_SwitchToCase) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(30), 1u);
+  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Forward_LoopHeadToBody) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(30), 1u);
+  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_DomViolation_BeforeIfToSelectionInterior) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
+
+     %20 = OpLabel
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %50 %89
+
+     %50 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_DomViolation_BeforeSwitchToSelectionInterior) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
+
+     %20 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %89 50 %50
+
+     %50 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_DomViolation_BeforeLoopToLoopBodyInterior) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50 ;%50 is a bad branch
+
+     %20 = OpLabel
+     OpLoopMerge %89 %80 None
+     OpBranchConditional %cond %50 %89
+
+     %50 = OpLabel
+     OpBranch %89
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              // Weird error, but still we caught it.
+              // Preferred: Eq("Branch from 10 to 50 bypasses header 20
+              // (dominance rule violated)"))
+              Eq("Branch from 10 to 50 bypasses continue target 80 (dominance "
+                 "rule violated)"))
+      << Dump(fe.block_order());
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_DomViolation_BeforeContinueToContinueInterior) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %60
+
+     %50 = OpLabel ; continue target
+     OpBranch %60
+
+     %60 = OpLabel
+     OpBranch %20
+
+     %89 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 60 is an invalid exit from "
+         "construct starting at block 20; branch bypasses continue target 50"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_DomViolation_AfterContinueToContinueInterior) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %80 %50 None
+     OpBranchConditional %cond %30 %80
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpBranch %60
+
+     %60 = OpLabel
+     OpBranch %20
+
+     %80 = OpLabel
+     OpBranch %60 ; bad branch
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 50 to block 60 is an invalid exit from "
+         "construct starting at block 50; branch bypasses merge block 80"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {  // NOLINT
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; case and merge block. Error
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is a case block for switch-selection header 10 and "
+                 "also the merge block for 20 (violates dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    ClassifyCFGEdges_DomViolation_SwitchDefault_CantBeMergeForOtherConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %50 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; default-case and merge block. Error
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is the default block for switch-selection header 10 "
+                 "and also the merge block for 20 (violates dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_TooManyBackedges) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranchConditional %cond2 %20 %50
+
+     %50 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_NeededMerge_BranchConditional) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %20 = OpLabel
+     OpBranchConditional %cond %30 %40
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 20 (to 30, 40) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_NeededMerge_Switch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 10 (to 99, 20) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody) {
+  // In this case the branch-conditional in the loop header is really also a
+  // selection header.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %50 ; what to make of this?
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %50 = OpLabel
+     OpBranch %99
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(30), 1u);
+  EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
+  EXPECT_EQ(bi->succ_edge.count(50), 1u);
+  EXPECT_EQ(bi->succ_edge[50], EdgeKind::kForward);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Pathological_Forward_Premerge) {
+  // Two arms of an if-selection converge early, before the merge block
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; this is an early merge!
+     OpBranch %60
+
+     %60 = OpLabel ; still early merge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(50), 1u);
+  EXPECT_EQ(bi20->succ_edge[50], EdgeKind::kForward);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->succ_edge.count(50), 1u);
+  EXPECT_EQ(bi30->succ_edge[50], EdgeKind::kForward);
+
+  auto* bi50 = fe.GetBlockInfo(50);
+  ASSERT_NE(bi50, nullptr);
+  EXPECT_EQ(bi50->succ_edge.count(60), 1u);
+  EXPECT_EQ(bi50->succ_edge[60], EdgeKind::kForward);
+
+  auto* bi60 = fe.GetBlockInfo(60);
+  ASSERT_NE(bi60, nullptr);
+  EXPECT_EQ(bi60->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi60->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_Pathological_Forward_Regardless) {
+  // Both arms of an OpBranchConditional go to the same target.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %20 ; same target!
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi10->succ_edge[20], EdgeKind::kForward);
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_NoIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->true_head, 0u);
+  EXPECT_EQ(bi->false_head, 0u);
+  EXPECT_EQ(bi->premerge_head, 0u);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_ThenElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 20u);
+  EXPECT_EQ(bi10->false_head, 30u);
+  EXPECT_EQ(bi10->premerge_head, 0u);
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->true_head, 0u);
+  EXPECT_EQ(bi20->false_head, 0u);
+  EXPECT_EQ(bi20->premerge_head, 0u);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head, 0u);
+  EXPECT_EQ(bi30->false_head, 0u);
+  EXPECT_EQ(bi30->premerge_head, 0u);
+
+  auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->true_head, 0u);
+  EXPECT_EQ(bi99->false_head, 0u);
+  EXPECT_EQ(bi99->premerge_head, 0u);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_IfOnly) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 30u);
+  EXPECT_EQ(bi10->false_head, 0u);
+  EXPECT_EQ(bi10->premerge_head, 0u);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head, 0u);
+  EXPECT_EQ(bi30->false_head, 0u);
+  EXPECT_EQ(bi30->premerge_head, 0u);
+
+  auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->true_head, 0u);
+  EXPECT_EQ(bi99->false_head, 0u);
+  EXPECT_EQ(bi99->premerge_head, 0u);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_ElseOnly) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 0u);
+  EXPECT_EQ(bi10->false_head, 30u);
+  EXPECT_EQ(bi10->premerge_head, 0u);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head, 0u);
+  EXPECT_EQ(bi30->false_head, 0u);
+  EXPECT_EQ(bi30->premerge_head, 0u);
+
+  auto* bi99 = fe.GetBlockInfo(99);
+  ASSERT_NE(bi99, nullptr);
+  EXPECT_EQ(bi99->true_head, 0u);
+  EXPECT_EQ(bi99->false_head, 0u);
+  EXPECT_EQ(bi99->premerge_head, 0u);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_Regardless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %20 ; same target
+
+     %20 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 20u);
+  EXPECT_EQ(bi10->false_head, 20u);
+  EXPECT_EQ(bi10->premerge_head, 0u);
+}
+
+TEST_F(SpvParserCFGTest, FindIfSelectionInternalHeaders_Premerge_Simple) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %80
+
+     %30 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 20u);
+  EXPECT_EQ(bi10->false_head, 30u);
+  EXPECT_EQ(bi10->premerge_head, 80u);
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 20u);
+  EXPECT_EQ(bi10->false_head, 30u);
+  EXPECT_EQ(bi10->premerge_head, 30u);
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %80 ; branches to premerge
+
+     %30 = OpLabel ; else
+     OpBranch %20  ; branches to then
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
+
+  auto* bi10 = fe.GetBlockInfo(10);
+  ASSERT_NE(bi10, nullptr);
+  EXPECT_EQ(bi10->true_head, 20u);
+  EXPECT_EQ(bi10->false_head, 30u);
+  EXPECT_EQ(bi10->premerge_head, 20u);
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_Premerge_MultiCandidate_IsError) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     ; Try to force several branches down into "else" territory,
+     ; but we error out earlier in the flow due to lack of merge
+     ; instruction.
+     OpBranchConditional %cond2  %70 %80
+
+     %30 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel ; candidate premerge
+     OpBranch %80
+
+     %80 = OpLabel ; canddiate premerge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  // Error out sooner in the flow
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 20 (to 70, 80) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
+  // SPIR-V allows this unusual configuration.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
+  // SPIR-V allows this unusual configuration.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel ; else clause
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi30->succ_edge[80], EdgeKind::kForward);
+  EXPECT_EQ(bi30->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
+
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel ; then
+     OpBranchConditional %cond2 %99 %80 ; break with forward to premerge
+
+     %30 = OpLabel ; else
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+
+  EXPECT_THAT(p->error(), Eq(""));
+
+  // TODO(crbug.com/tint/775): The SPIR-V reader errors out on this case.
+  // Remove this when it's fixed.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {  // NOLINT - line length
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %40 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner merge, and true-head for outer if-selection
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 40 is the true branch for if-selection header 10 and also the "
+         "merge block for header block 20 (violates dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {  // NOLINT - line length
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %40
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %40
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner merge, and true-head for outer if-selection
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 40 is the false branch for if-selection header 10 and also the "
+         "merge block for header block 20 (violates dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBePremerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel ; outer if-header
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %70
+
+     %50 = OpLabel ; inner if-header
+     OpSelectionMerge %70 None
+     OpBranchConditional %cond %60 %70
+
+     %60 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel ; inner merge, and premerge for outer if-selection
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Block 70 is the merge block for 50 but has alternate paths "
+                 "reaching it, starting from blocks 20 and 50 which are the "
+                 "true and false branches for the if-selection header block 10 "
+                 "(violates dominance rule)"));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %99 %30 ; true branch breaking is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %90 %30 ; true branch continue is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %uint_20 %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %99 %30 ; true branch switch break is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; if-selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %99 ; false branch breaking is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %90 ; false branch continue is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %uint_20 %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %99 ; false branch switch break is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; if-selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq(""));
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
+  // Exercises the hard case where we a single OpBranchConditional has both
+  // IfBreak and Forward edges, within the true-branch clause.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond2 %99 %30 ; kIfBreak with kForward
+
+     %30 = OpLabel ; still in then clause
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_4
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+var guard10 : bool = true;
+if (false) {
+  var_1 = 2u;
+  if (true) {
+    guard10 = false;
+  }
+  if (guard10) {
+    var_1 = 3u;
+    guard10 = false;
+  }
+} else {
+  if (guard10) {
+    var_1 = 4u;
+    guard10 = false;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_IfBreak_FromElse_ForwardWithinElse) {
+  // Exercises the hard case where we a single OpBranchConditional has both
+  // IfBreak and Forward edges, within the false-branch clause.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %99
+
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_3
+     OpBranchConditional %cond2 %99 %80 ; kIfBreak with kForward
+
+     %80 = OpLabel ; still in then clause
+     OpStore %var %uint_4
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+var guard10 : bool = true;
+if (false) {
+  var_1 = 2u;
+  guard10 = false;
+} else {
+  if (guard10) {
+    var_1 = 3u;
+    if (true) {
+      guard10 = false;
+    }
+    if (guard10) {
+      var_1 = 4u;
+      guard10 = false;
+    }
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
+  // This is a combination of the previous two, but also adding a premerge.
+  // We have IfBreak and Forward edges from the same OpBranchConditional, and
+  // this occurs in the true-branch clause, the false-branch clause, and within
+  // the premerge clause.  Flow guards have to be sprinkled in lots of places.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel ; then
+     OpStore %var %uint_2
+     OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
+
+     %21 = OpLabel ; still in then clause
+     OpStore %var %uint_3
+     OpBranch %80 ; to premerge
+
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_4
+     OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
+
+     %51 = OpLabel ; still in else clause
+     OpStore %var %uint_5
+     OpBranch %80 ; to premerge
+
+     %80 = OpLabel ; premerge
+     OpStore %var %uint_6
+     OpBranchConditional %cond3 %81 %99
+
+     %81 = OpLabel ; premerge
+     OpStore %var %uint_7
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_8
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+var guard10 : bool = true;
+if (false) {
+  var_1 = 2u;
+  if (true) {
+  } else {
+    guard10 = false;
+  }
+  if (guard10) {
+    var_1 = 3u;
+  }
+} else {
+  if (guard10) {
+    var_1 = 4u;
+    if (true) {
+      guard10 = false;
+    }
+    if (guard10) {
+      var_1 = 5u;
+    }
+  }
+}
+if (guard10) {
+  var_1 = 6u;
+  if (false) {
+  } else {
+    guard10 = false;
+  }
+  if (guard10) {
+    var_1 = 7u;
+    guard10 = false;
+  }
+}
+var_1 = 8u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, BlockIsContinueForMoreThanOneHeader) {
+  // This is disallowed by the rule:
+  //    "a continue block is valid only for the innermost loop it is nested
+  //    inside of"
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel ; outer loop
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %50 %99
+
+     %50 = OpLabel ; continue target, but also single-block loop
+     OpLoopMerge %80 %50 None
+     OpBranchConditional %cond2 %50 %80
+
+     %80 = OpLabel
+     OpBranch %20 ; backedge for outer loop
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  EXPECT_FALSE(fe.RegisterMerges());
+  EXPECT_THAT(p->error(), Eq("Block 50 declared as continue target for more "
+                             "than one header: 20, 50"));
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Empty) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Then_NoElse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+  var_1 = 1u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_NoThen_Else) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+} else {
+  var_1 = 1u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Then_Else) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %40
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %40 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %99
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+  var_1 = 1u;
+} else {
+  var_1 = 2u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Then_Else_Premerge) {
+  // TODO(dneto): This should get an extra if(true) around
+  // the premerge code.
+  // See https://bugs.chromium.org/p/tint/issues/detail?id=82
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %40
+
+     %80 = OpLabel ; premerge
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80
+
+     %40 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %80
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+  var_1 = 1u;
+} else {
+  var_1 = 2u;
+}
+if (true) {
+  var_1 = 3u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Then_Premerge) {
+  // The premerge *is* the else.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %80
+
+     %80 = OpLabel ; premerge
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+  var_1 = 1u;
+}
+if (true) {
+  var_1 = 3u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Else_Premerge) {
+  // The premerge *is* the then-clause.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %80 %30
+
+     %80 = OpLabel ; premerge
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+} else {
+  var_1 = 1u;
+}
+if (true) {
+  var_1 = 3u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_If_Nest_If) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %40
+
+     %30 = OpLabel ;; inner if #1
+     OpStore %var %uint_1
+     OpSelectionMerge %39 None
+     OpBranchConditional %cond2 %33 %39
+
+     %33 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %39
+
+     %39 = OpLabel ;; inner merge
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %40 = OpLabel ;; inner if #2
+     OpStore %var %uint_4
+     OpSelectionMerge %49 None
+     OpBranchConditional %cond2 %49 %43
+
+     %43 = OpLabel
+     OpStore %var %uint_5
+     OpBranch %49
+
+     %49 = OpLabel ;; 2nd inner merge
+     OpStore %var %uint_6
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+  var_1 = 1u;
+  if (true) {
+    var_1 = 2u;
+  }
+  var_1 = 3u;
+} else {
+  var_1 = 4u;
+  if (true) {
+  } else {
+    var_1 = 5u;
+  }
+  var_1 = 6u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_FalseBackedge) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (false) {
+    break;
+  }
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_BothBackedge) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_SingleBlockContinue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+  }
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_MultiBlockContinue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %60
+
+     %60 = OpLabel
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+    var_1 = 4u;
+  }
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_Unconditional_Body_ContinueNestIf) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel ; continue target; also if-header
+     OpStore %var %uint_3
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond2 %60 %80
+
+     %60 = OpLabel
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+    if (true) {
+      var_1 = 4u;
+    }
+    var_1 = 5u;
+  }
+}
+var_1 = 999u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_MultiBlockContinueIsEntireLoop) {
+  // Test case where both branches exit. e.g both go to merge.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel ; its own continue target
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %uint_3
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (false) {
+    break;
+  }
+}
+var_1 = 3u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_Never) {
+  // Test case where both branches exit. e.g both go to merge.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_2
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_3
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  break;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+var_1 = 3u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_HeaderBreakAndContinue) {
+  // Header block branches to merge, and to an outer continue.
+  // This is disallowed by the rule:
+  //    "a continue block is valid only for the innermost loop it is nested
+  //    inside of"
+  // See test ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_TrueToBody_FalseBreaks) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_4
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+  }
+}
+var_1 = 4u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_FalseToBody_TrueBreaks) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_4
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+  }
+}
+var_1 = 4u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_NestedIfContinue) {
+  // By construction, it has to come from nested code.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80 ; continue edge
+
+     %50 = OpLabel ; inner selection merge
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+    var_1 = 1u;
+    continue;
+  }
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyAlwaysBreaks) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99 ; break is here
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  break;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue) {
+  // The else-branch has a continue but it's skipped because it's from a
+  // block that immediately precedes the continue construct.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+    break;
+  }
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse) {
+  // The else-branch has a continue but it's skipped because it's from a
+  // block that immediately precedes the continue construct.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %80 %99
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %70
+
+     %70 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+    break;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %70 %99
+
+     %70 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_NoCases) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+// First do no special control flow: no fallthroughs, breaks, continues.
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_OneCase) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_TwoCases) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 30u: {
+    var_1 = 30u;
+  }
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsMerge_CasesWithDup) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30 40 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 30u: {
+    var_1 = 30u;
+  }
+  case 20u, 40u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsCase_NoDupCases) {
+  // The default block is not the merge block. But not the same as a case
+  // either.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20 40 %40
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel ; the named default block
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 40u: {
+    var_1 = 40u;
+  }
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+    var_1 = 30u;
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_DefaultIsCase_WithDupCase) {
+  // The default block is not the merge block and is the same as a case.
+  // We emit the default case separately, but just before the labeled
+  // case, and with a fallthrough.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %30 20 %20 30 %30 40 %40
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel ; the named default block, also a case
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 40u: {
+    var_1 = 40u;
+  }
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+    fallthrough;
+  }
+  case 30u: {
+    var_1 = 30u;
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_Case_SintValue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     ; SPIR-V assembler doesn't support negative literals in switch
+     OpSwitch %signed_selector %99 20 %20 2000000000 %30 !4000000000 %40
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42) {
+  case -294967296: {
+    var_1 = 40u;
+  }
+  case 2000000000: {
+    var_1 = 30u;
+  }
+  case 20: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Switch_Case_UintValue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 2000000000 %30 50 %40
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 50u: {
+    var_1 = 40u;
+  }
+  case 2000000000u: {
+    var_1 = 30u;
+  }
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Return_TopLevel) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Return_InsideIf) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpReturn
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+  return;
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Return_InsideLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %30
+
+     %30 = OpLabel
+     OpReturn
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  return;
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_TopLevel) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %200 = OpFunction %uint None %uintfn
+
+     %210 = OpLabel
+     OpReturnValue %uint_2
+
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %11 = OpFunctionCall %uint %200
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(return 2u;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_InsideIf) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %200 = OpFunction %uint None %uintfn
+
+     %210 = OpLabel
+     OpSelectionMerge %299 None
+     OpBranchConditional %cond %220 %299
+
+     %220 = OpLabel
+     OpReturnValue %uint_2
+
+     %299 = OpLabel
+     OpReturnValue %uint_3
+
+     OpFunctionEnd
+
+
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %11 = OpFunctionCall %uint %200
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+  return 2u;
+}
+return 3u;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_ReturnValue_Loop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %200 = OpFunction %uint None %uintfn
+
+     %210 = OpLabel
+     OpBranch %220
+
+     %220 = OpLabel
+     OpLoopMerge %299 %280 None
+     OpBranchConditional %cond %230 %230
+
+     %230 = OpLabel
+     OpReturnValue %uint_2
+
+     %280 = OpLabel
+     OpBranch %220
+
+     %299 = OpLabel
+     OpReturnValue %uint_3
+
+     OpFunctionEnd
+
+
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %11 = OpFunctionCall %uint %200
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  return 2u;
+}
+return 3u;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Kill_TopLevel) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpKill
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(discard;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Kill_InsideIf) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpKill
+
+     %99 = OpLabel
+     OpKill
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+  discard;
+}
+discard;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Kill_InsideLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %30
+
+     %30 = OpLabel
+     OpKill
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpKill
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  discard;
+}
+discard;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Unreachable_TopLevel) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpUnreachable
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InsideIf) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpUnreachable
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+  return;
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InsideLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %30
+
+     %30 = OpLabel
+     OpUnreachable
+
+     %80 = OpLabel
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  return;
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Unreachable_InNonVoidFunction) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %200 = OpFunction %uint None %uintfn
+
+     %210 = OpLabel
+     OpUnreachable
+
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %11 = OpFunctionCall %uint %200
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(return 0u;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_BackEdge_MultiBlockLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20 ; here is one
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+
+  continuing {
+    var_1 = 1u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_BackEdge_SingleBlockLoop) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranch %20 ; backedge in single block loop
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_SwitchBreak_LastInCase) {
+  // When the break is last in its case, we omit it because it's implicit in
+  // WGSL.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %99 ; branch to merge. Last in case
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_SwitchBreak_NotLastInCase) {
+  // When the break is not last in its case, we must emit a 'break'
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranch %99 ; branch to merge. Not last in case
+
+     %50 = OpLabel ; inner merge
+     OpStore %var %uint_50
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+      var_1 = 40u;
+      break;
+    }
+    var_1 = 50u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99 ; break is here
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+  break;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructConditional) {
+  // This case is invalid because the backedge block doesn't post-dominate the
+  // continue target.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranch %30
+
+     %30 = OpLabel ; continue target; also an if-header
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond %40 %80
+
+     %40 = OpLabel
+     OpBranch %99 ; break, inside a nested if.
+
+     %80 = OpLabel
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (40->99) from continue construct: 40 is not the "
+                 "last block in the continue construct starting at 30 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) {  // NOLINT - line length
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_1
+     OpBranch %99  ; should be a backedge
+     ; This is invalid as there must be a backedge to the loop header.
+     ; The SPIR-V allows this and understands how to emit it, even if it's not
+     ; permitted by the SPIR-V validator.
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+
+  p->DeliberatelyInvalidSpirv();
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+
+  continuing {
+    var_1 = 1u;
+    break;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) {  // NOLINT - line length
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_1
+     OpBranchConditional %cond %20 %99  ; backedge, and exit
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+
+  continuing {
+    var_1 = 1u;
+    if (false) {
+    } else {
+      break;
+    }
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_LastInLoopConstruct) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80 ; continue edge from last block before continue target
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_2
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var_1 = 1u;
+
+  continuing {
+    var_1 = 2u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_BeforeLast) {
+  // By construction, it has to come from nested code.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80 ; continue edge
+
+     %50 = OpLabel ; inner selection merge
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+    var_1 = 1u;
+    continue;
+  }
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 3u;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopContinue_FromSwitch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_3
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_4
+     OpBranch %80 ; continue edge
+
+     %79 = OpLabel ; switch merge
+     OpStore %var %uint_5
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_6
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+loop {
+  var_1 = 2u;
+  var_1 = 3u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 4u;
+      continue;
+    }
+    default: {
+    }
+  }
+  var_1 = 5u;
+
+  continuing {
+    var_1 = 6u;
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_IfBreak_FromThen) {
+  // When unconditional, the if-break must be last in the then clause.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+  var_1 = 1u;
+}
+var_1 = 2u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_IfBreak_FromElse) {
+  // When unconditional, the if-break must be last in the else clause.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (false) {
+} else {
+  var_1 = 1u;
+}
+var_1 = 2u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_Fallthrough) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranch %30 ; uncondtional fallthrough
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    fallthrough;
+  }
+  case 30u: {
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_Forward) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99 ; forward
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+var_1 = 2u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+// Test matrix for normal OpBranchConditional:
+//
+//    kBack with:
+//      kBack : TESTED dup general case
+//      kSwitchBreak: invalid (invalid escape, or invalid backedge)
+//      kLoopBreak: TESTED in single- and multi block loop configurations
+//      kLoopContinue: invalid
+//                      If single block loop, then the continue is backward
+//                      If continue is forward, then it's a continue from a
+//                      continue which is also invalid.
+//      kIfBreak: invalid: loop and if must have distinct merge blocks
+//      kCaseFallThrough: invalid: loop header must dominate its merge
+//      kForward: impossible; would be a loop break
+//
+//    kSwitchBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: dup general case
+//      kLoopBreak: invalid; only one kind of break allowed
+//      kLoopContinue: TESTED
+//      kIfBreak: invalid: switch and if must have distinct merge blocks
+//      kCaseFallThrough: TESTED
+//      kForward: TESTED
+//
+//    kLoopBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: dup general case
+//      kLoopContinue: TESTED
+//      kIfBreak: invalid: switch and if must have distinct merge blocks
+//      kCaseFallThrough: not possible, because switch break conflicts with loop
+//      break kForward: TESTED
+//
+//    kLoopContinue with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: dup general case
+//      kIfBreak: TESTED
+//      kCaseFallThrough: TESTED
+//      kForward: TESTED
+//
+//    kIfBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: dup general case
+//      kCaseFallThrough: invalid; violates nesting or unique merges
+//      kForward: invalid: needs a merge instruction
+//
+//    kCaseFallThrough with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: symmetry
+//      kCaseFallThrough: dup general case
+//      kForward: invalid (tested)
+//
+//    kForward with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: symmetry
+//      kCaseFallThrough: symmetry
+//      kForward: dup general case
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Back_SingleBlock_Back) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %20
+
+     %99 = OpLabel ; dead
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (false) {
+    break;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (false) {
+  } else {
+    break;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+
+  continuing {
+    if (false) {
+      break;
+    }
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+
+  continuing {
+    if (false) {
+    } else {
+      break;
+    }
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase) {
+  // When the break is last in its case, we omit it because it's implicit in
+  // WGSL.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond2 %99 %99 ; branch to merge. Last in case
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase) {
+  // When the break is not last in its case, we must emit a 'break'
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranchConditional %cond2 %99 %99 ; branch to merge. Not last in case
+
+     %50 = OpLabel ; inner merge
+     OpStore %var %uint_50
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+      var_1 = 40u;
+      break;
+    }
+    var_1 = 50u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_3
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranchConditional %cond %80 %79 ; break; continue on true
+
+     %79 = OpLabel
+     OpStore %var %uint_6
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_7
+     OpBranch %20
+
+     %99 = OpLabel ; loop merge
+     OpStore %var %uint_8
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+loop {
+  var_1 = 2u;
+  var_1 = 3u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 40u;
+      if (false) {
+        continue;
+      }
+    }
+    default: {
+    }
+  }
+  var_1 = 6u;
+
+  continuing {
+    var_1 = 7u;
+  }
+}
+var_1 = 8u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_3
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranchConditional %cond %79 %80 ; break; continue on false
+
+     %79 = OpLabel
+     OpStore %var %uint_6
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_7
+     OpBranch %20
+
+     %99 = OpLabel ; loop merge
+     OpStore %var %uint_8
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+loop {
+  var_1 = 2u;
+  var_1 = 3u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 40u;
+      if (false) {
+      } else {
+        continue;
+      }
+    }
+    default: {
+    }
+  }
+  var_1 = 6u;
+
+  continuing {
+    var_1 = 7u;
+  }
+}
+var_1 = 8u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond %30 %99 ; break; forward on true
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpStore %var %uint_8
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+    } else {
+      break;
+    }
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 8u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond %99 %30 ; break; forward on false
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpStore %var %uint_8
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+      break;
+    }
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 8u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond %30 %99; fallthrough on true
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+    } else {
+      break;
+    }
+    fallthrough;
+  }
+  case 30u: {
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond %99 %30; fallthrough on false
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    if (false) {
+      break;
+    }
+    fallthrough;
+  }
+  case 30u: {
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  break;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  break;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Continue_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %25
+
+     ; Need this extra selection to make another block between
+     ; %30 and the continue target, so we actually induce a Continue
+     ; statement to exist.
+     %25 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; continue on true
+     OpBranchConditional %cond %80 %99
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (true) {
+    var_1 = 2u;
+    if (false) {
+      continue;
+    } else {
+      break;
+    }
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_LoopBreak_Continue_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %25
+
+     ; Need this extra selection to make another block between
+     ; %30 and the continue target, so we actually induce a Continue
+     ; statement to exist.
+     %25 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; continue on false
+     OpBranchConditional %cond %99 %80
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  if (true) {
+    var_1 = 2u;
+    if (false) {
+      break;
+    } else {
+      continue;
+    }
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_LoopBreak_Fallthrough_IsError) {
+  // It's an error because switch break conflicts with loop break.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40 50 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     ; error: branch to 99 bypasses switch's merge
+     OpBranchConditional %cond %99 %50 ; loop break; fall through
+
+     %50 = OpLabel
+     OpStore %var %uint_50
+     OpBranch %79
+
+     %79 = OpLabel ; switch merge
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 40 to block 99 is an invalid exit from construct "
+         "starting at block 30; branch bypasses merge block 79"));
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; forward on true
+     OpBranchConditional %cond %40 %99
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (false) {
+  } else {
+    break;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopBreak_Forward_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; forward on false
+     OpBranchConditional %cond %99 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (false) {
+    break;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Continue_FromHeader) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %80 %80 ; to continue
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %80 %80 ; to continue
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional) {
+  // Create an intervening block so we actually require a "continue" statement
+  // instead of just an adjacent fallthrough to the continue target.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranchConditional %cond3 %80 %80 ; to continue
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (true) {
+    var_1 = 3u;
+    continue;
+  }
+  var_1 = 4u;
+
+  continuing {
+    var_1 = 5u;
+  }
+}
+var_1 = 6u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserCFGTest,
+    EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) {  // NOLINT
+  // Like the previous tests, but with an empty continuing clause.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranchConditional %cond3 %80 %80 ; to continue
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     ; no statements here.
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (true) {
+    var_1 = 3u;
+    continue;
+  }
+  var_1 = 4u;
+}
+var_1 = 6u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_LoopContinue_FromSwitch) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_2
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_3
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_4
+     OpBranchConditional %cond2 %80 %80; dup continue edge
+
+     %79 = OpLabel ; switch merge
+     OpStore %var %uint_5
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_6
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+loop {
+  var_1 = 2u;
+  var_1 = 3u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 4u;
+      continue;
+    }
+    default: {
+    }
+  }
+  var_1 = 5u;
+
+  continuing {
+    var_1 = 6u;
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+ ; true to if's merge;  false to continue
+     OpBranchConditional %cond3 %50 %80
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (true) {
+    var_1 = 3u;
+    if (false) {
+    } else {
+      continue;
+    }
+  }
+  var_1 = 4u;
+
+  continuing {
+    var_1 = 5u;
+  }
+}
+var_1 = 6u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_IfBreak_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+ ; false to if's merge;  true to continue
+     OpBranchConditional %cond3 %80 %50
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (true) {
+    var_1 = 3u;
+    if (false) {
+      continue;
+    }
+  }
+  var_1 = 4u;
+
+  continuing {
+    var_1 = 5u;
+  }
+}
+var_1 = 6u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40 50 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranchConditional %cond %50 %80 ; loop continue; fall through on true
+
+     %50 = OpLabel
+     OpStore %var %uint_50
+     OpBranch %79
+
+     %79 = OpLabel ; switch merge
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 40u;
+      if (false) {
+      } else {
+        continue;
+      }
+      fallthrough;
+    }
+    case 50u: {
+      var_1 = 50u;
+    }
+    default: {
+    }
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %79 None
+     OpSwitch %selector %79 40 %40 50 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_40
+     OpBranchConditional %cond %80 %50 ; loop continue; fall through on false
+
+     %50 = OpLabel
+     OpStore %var %uint_50
+     OpBranch %79
+
+     %79 = OpLabel ; switch merge
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  switch(42u) {
+    case 40u: {
+      var_1 = 40u;
+      if (false) {
+        continue;
+      }
+      fallthrough;
+    }
+    case 50u: {
+      var_1 = 50u;
+    }
+    default: {
+    }
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; continue; forward on true
+     OpBranchConditional %cond %40 %80
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (false) {
+  } else {
+    continue;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Continue_Forward_OnFalse) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; continue; forward on true
+     OpBranchConditional %cond %80 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+loop {
+  var_1 = 1u;
+  var_1 = 2u;
+  if (false) {
+    continue;
+  }
+  var_1 = 3u;
+
+  continuing {
+    var_1 = 4u;
+  }
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_IfBreak_IfBreak_Same) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %99
+
+     %20 = OpLabel ; dead
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 0u;
+if (false) {
+}
+var_1 = 5u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_IfBreak_IfBreak_DifferentIsError) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %89 %99 ; invalid divergence
+
+     %89 = OpLabel ; inner if-merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer if-merge
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from construct "
+         "starting at block 20; branch bypasses merge block 89"));
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 30 %30
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpBranchConditional %cond %30 %30 ; fallthrough fallthrough
+
+     %30 = OpLabel
+     OpStore %var %uint_30
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+switch(42u) {
+  case 20u: {
+    var_1 = 20u;
+    fallthrough;
+  }
+  case 30u: {
+    var_1 = 30u;
+  }
+  default: {
+  }
+}
+var_1 = 7u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Fallthrough_NotLastInCase_IsError) {
+  // See also
+  // ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError.
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 40 %40
+
+     %20 = OpLabel ; case 30
+     OpSelectionMerge %39 None
+     OpBranchConditional %cond %40 %30 ; fallthrough and forward
+
+     %30 = OpLabel
+     OpBranch %39
+
+     %39 = OpLabel
+     OpBranch %99
+
+     %40 = OpLabel  ; case 40
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  // The weird forward branch pulls in 40 as part of the selection rather than
+  // as a case.
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 30, 39, 99));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from 10 to 40 bypasses header 20 (dominance rule violated)"));
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_BranchConditional_Forward_Forward_Same) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %99; forward
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 1u;
+var_1 = 2u;
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpReturn
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 10 (to 20, 99) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(SpvParserCFGTest, Switch_NotAsSelectionHeader_Simple) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSwitch %uint_0 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("invalid structured control flow: found an OpSwitch that "
+                "is not preceded by an OpSelectionMerge:"));
+}
+
+TEST_F(SpvParserCFGTest,
+       Switch_NotAsSelectionHeader_NonDefaultBranchesAreContinue) {
+  // Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpBranch %loop
+ %loop = OpLabel
+ OpLoopMerge %merge %cont None
+ OpBranchConditional %cond %merge %b1
+
+ ; Here an OpSwitch is used with only one "unseen-so-far" target
+ ; so it doesn't need an OpSelectionMerge.
+ ; The %cont target can be implemented via "continue". So we can
+ ; generate:
+ ;    if ((selector != 1) && (selector != 3)) { continue; }
+ %b1 = OpLabel
+ OpSwitch %selector %b2 0 %b2 1 %cont 2 %b2 3 %cont
+
+ %b2 = OpLabel ; the one unseen target
+ OpBranch %cont
+ %cont = OpLabel
+ OpBranchConditional %cond2 %merge %loop
+ %merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+   )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("invalid structured control flow: found an OpSwitch that "
+                "is not preceded by an OpSelectionMerge:"));
+}
+
+TEST_F(SpvParserCFGTest, Switch_NotAsSelectionHeader_DefaultBranchIsContinue) {
+  // Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
+  auto p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpBranch %loop
+ %loop = OpLabel
+ OpLoopMerge %merge %cont None
+ OpBranchConditional %cond %merge %b1
+
+ ; Here an OpSwitch is used with only one "unseen-so-far" target
+ ; so it doesn't need an OpSelectionMerge.
+ ; The %cont target can be implemented via "continue". So we can
+ ; generate:
+ ;    if (!(selector == 0 || selector == 2)) {continue;}
+ %b1 = OpLabel
+ OpSwitch %selector %cont 0 %b2 1 %cont 2 %b2
+
+ %b2 = OpLabel ; the one unseen target
+ OpBranch %cont
+ %cont = OpLabel
+ OpBranchConditional %cond2 %merge %loop
+ %merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+   )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("invalid structured control flow: found an OpSwitch that "
+                "is not preceded by an OpSelectionMerge:"));
+}
+
+TEST_F(SpvParserCFGTest, SiblingLoopConstruct_Null) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %10 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_EQ(fe.SiblingLoopConstruct(nullptr), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, SiblingLoopConstruct_NotAContinue) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
+  const Construct* c = fe.GetBlockInfo(10)->construct;
+  EXPECT_NE(c, nullptr);
+  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, SiblingLoopConstruct_SingleBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
+  const Construct* c = fe.GetBlockInfo(20)->construct;
+  EXPECT_EQ(c->kind, Construct::kContinue);
+  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %20 None ; continue target is also loop header
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
+  const Construct* c = fe.GetBlockInfo(20)->construct;
+  EXPECT_EQ(c->kind, Construct::kContinue);
+  EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
+}
+
+TEST_F(SpvParserCFGTest, SiblingLoopConstruct_HasSiblingLoop) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; continue target
+     OpBranch %20
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
+  const Construct* c = fe.GetBlockInfo(30)->construct;
+  EXPECT_EQ(c->kind, Construct::kContinue);
+  EXPECT_THAT(ToString(fe.SiblingLoopConstruct(c)),
+              Eq("Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 "
+                 "parent:Function@10 scope:[1,3) in-l:Loop@20 }"));
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_IfSelection_TrueBranch_LoopBreak) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %99 %30 ; true branch breaking is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+    break;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_TrueBranch_LoopContinue) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %90 %30 ; true branch continue is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+    continue;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_TrueBranch_SwitchBreak) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %uint_20 %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %99 %30 ; true branch switch break is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; if-selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(switch(20u) {
+  case 20u: {
+    if (false) {
+      break;
+    }
+  }
+  default: {
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_LoopBreak) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %99 ; false branch breaking is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+  } else {
+    break;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_LoopContinue) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     %10 = OpLabel
+     OpLoopMerge %99 %90 None
+     OpBranch %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %90 ; false branch continue is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; selection merge
+     OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpBranch %10 ; backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  if (false) {
+  } else {
+    continue;
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got) << p->error();
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_FalseBranch_SwitchBreak) {
+  // crbug.com/tint/243
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %uint_20 %99 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %99 ; false branch switch break is ok
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; if-selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(switch(20u) {
+  case 20u: {
+    if (false) {
+    } else {
+      break;
+    }
+  }
+  default: {
+  }
+}
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_LoopInternallyDiverge_Simple) {
+  // crbug.com/tint/524
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %10 = OpLabel
+     OpStore %var %uint_10
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_20
+     OpLoopMerge %99 %90 None
+     OpBranchConditional %cond %30 %40 ; divergence
+
+       %30 = OpLabel
+       OpStore %var %uint_30
+       OpBranch %90
+
+       %40 = OpLabel
+       OpStore %var %uint_40
+       OpBranch %90
+
+     %90 = OpLabel ; continue target
+     OpStore %var %uint_90
+     OpBranch %20
+
+     %99 = OpLabel ; loop merge
+     OpStore %var %uint_99
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var_1 = 10u;
+loop {
+  var_1 = 20u;
+  if (false) {
+    var_1 = 30u;
+    continue;
+  } else {
+    var_1 = 40u;
+  }
+
+  continuing {
+    var_1 = 90u;
+  }
+}
+var_1 = 99u;
+return;
+)";
+  ASSERT_EQ(expect, got) << got;
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_ContinueFromSingleBlockLoopToOuterLoop_IsError) {
+  // crbug.com/tint/793
+  // This is invalid SPIR-V but the validator was only recently upgraded
+  // to catch it.
+  auto assembly = CommonTypes() + R"(
+  %100 = OpFunction %void None %voidfn
+  %5 = OpLabel
+  OpBranch %10
+
+  %10 = OpLabel ; outer loop header
+  OpLoopMerge %99 %89 None
+  OpBranchConditional %cond %99 %20
+
+  %20 = OpLabel ; inner loop single block loop
+  OpLoopMerge %79 %20 None
+
+  ; true -> continue to outer loop
+  ; false -> self-loop
+  ; The true branch is invalid because a "continue block", i.e. the block
+  ; containing the branch to the continue target, "is valid only for the
+  ; innermost loop it is nested inside of".
+  ; So it can't branch to the continue target of an outer loop.
+  OpBranchConditional %cond %89 %20
+
+  %79 = OpLabel ; merge for outer loop
+  OpUnreachable
+
+  %89 = OpLabel
+  OpBranch %10 ; backedge for outer loop
+
+  %99 = OpLabel ; merge for outer
+  OpReturn
+
+  OpFunctionEnd
+
+)";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(),
+              HasSubstr("block <ID> 20[%20] exits the continue headed by <ID> "
+                        "20[%20], but not via a structured exit"))
+      << p->error();
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_composite_test.cc b/src/tint/reader/spirv/function_composite_test.cc
new file mode 100644
index 0000000..e11b0f0
--- /dev/null
+++ b/src/tint/reader/spirv/function_composite_test.cc
@@ -0,0 +1,1075 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+std::string Caps() {
+  return R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %100 "main"
+  OpExecutionMode %100 LocalSize 1 1 1
+)";
+}
+
+std::string CommonTypes() {
+  return R"(
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %uint_10 = OpConstant %uint 10
+  %uint_20 = OpConstant %uint 20
+  %uint_1 = OpConstant %uint 1
+  %uint_3 = OpConstant %uint 3
+  %uint_4 = OpConstant %uint 4
+  %uint_5 = OpConstant %uint 5
+  %int_1 = OpConstant %int 1
+  %int_30 = OpConstant %int 30
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+  %float_70 = OpConstant %float 70
+
+  %v2uint = OpTypeVector %uint 2
+  %v3uint = OpTypeVector %uint 3
+  %v4uint = OpTypeVector %uint 4
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+
+  %m3v2float = OpTypeMatrix %v2float 3
+  %m3v2float_0 = OpConstantNull %m3v2float
+
+  %s_v2f_u_i = OpTypeStruct %v2float %uint %int
+  %a_u_5 = OpTypeArray %uint %uint_5
+
+  %v2uint_3_4 = OpConstantComposite %v2uint %uint_3 %uint_4
+  %v2uint_4_3 = OpConstantComposite %v2uint %uint_4 %uint_3
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+  %v2float_70_70 = OpConstantComposite %v2float %float_70 %float_70
+)";
+}
+
+std::string Preamble() {
+  return Caps() + CommonTypes();
+}
+
+using SpvParserTest_Composite_Construct = SpvParserTest;
+
+TEST_F(SpvParserTest_Composite_Construct, Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeConstruct %v2uint %uint_10 %uint_20
+     %2 = OpCompositeConstruct %v2int %int_30 %int_40
+     %3 = OpCompositeConstruct %v2float %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_1 : vec2<u32> = vec2<u32>(10u, 20u);
+let x_2 : vec2<i32> = vec2<i32>(30, 40);
+let x_3 : vec2<f32> = vec2<f32>(50.0, 60.0);
+)"));
+}
+
+TEST_F(SpvParserTest_Composite_Construct, Matrix) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeConstruct %m3v2float %v2float_50_60 %v2float_60_50 %v2float_70_70
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : mat3x2<f32> = mat3x2<f32>("
+                        "vec2<f32>(50.0, 60.0), "
+                        "vec2<f32>(60.0, 50.0), "
+                        "vec2<f32>(70.0, 70.0));"));
+}
+
+TEST_F(SpvParserTest_Composite_Construct, Array) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeConstruct %a_u_5 %uint_10 %uint_20 %uint_3 %uint_4 %uint_5
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          "let x_1 : array<u32, 5u> = array<u32, 5u>(10u, 20u, 3u, 4u, 5u);"));
+}
+
+TEST_F(SpvParserTest_Composite_Construct, Struct) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeConstruct %s_v2f_u_i %v2float_50_60 %uint_5 %int_30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : S = S(vec2<f32>(50.0, 60.0), 5u, 30);"));
+}
+
+TEST_F(SpvParserTest_Composite_Construct,
+       ConstantComposite_Struct_NoDeduplication) {
+  const auto assembly = Preamble() + R"(
+     %200 = OpTypeStruct %uint
+     %300 = OpTypeStruct %uint ; isomorphic structures
+
+     %201 = OpConstantComposite %200 %uint_10
+     %301 = OpConstantComposite %300 %uint_10  ; isomorphic constants
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpCopyObject %200 %201
+     %3 = OpCopyObject %300 %301
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const auto expected = std::string(
+      R"(let x_2 : S_1 = S_1(10u);
+let x_3 : S_2 = S_2(10u);
+return;
+)");
+  EXPECT_EQ(got, expected) << got;
+}
+
+using SpvParserTest_CompositeExtract = SpvParserTest;
+
+TEST_F(SpvParserTest_CompositeExtract, Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeExtract %float %v2float_50_60 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = vec2<f32>(50.0, 60.0).y;"));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeExtract %float %v2float_50_60 900
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_EQ(p->error(),
+            "OpCompositeExtract %1 index value 900 is out of bounds for vector "
+            "of 2 elements");
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Matrix) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeExtract %v2float %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : vec2<f32> = x_1[2u];"));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Matrix_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeExtract %v2float %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_EQ(p->error(),
+            "OpCompositeExtract %2 index value 3 is out of bounds for matrix "
+            "of 3 elements");
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeExtract %float %1 2 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : f32 = x_1[2u].y;"));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Array) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %a_u_5
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %a_u_5 %var
+     %2 = OpCompositeExtract %uint %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : u32 = x_1[3u];"));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, RuntimeArray_IsError) {
+  const auto assembly = Preamble() + R"(
+     %rtarr = OpTypeRuntimeArray %uint
+     %ptr = OpTypePointer Function %rtarr
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %rtarr %var
+     %2 = OpCompositeExtract %uint %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              HasSubstr("can't do OpCompositeExtract on a runtime array: "));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Struct) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeExtract %int %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : i32 = x_1.field2;"));
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Struct_DifferOnlyInMemberName) {
+  const std::string assembly = R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+
+     OpMemberName %s0 0 "algo"
+     OpMemberName %s1 0 "rithm"
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %uint = OpTypeInt 32 0
+
+     %s0 = OpTypeStruct %uint
+     %s1 = OpTypeStruct %uint
+     %ptr0 = OpTypePointer Function %s0
+     %ptr1 = OpTypePointer Function %s1
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var0 = OpVariable %ptr0 Function
+     %var1 = OpVariable %ptr1 Function
+     %1 = OpLoad %s0 %var0
+     %2 = OpCompositeExtract %uint %1 0
+     %3 = OpLoad %s1 %var1
+     %4 = OpCompositeExtract %uint %3 0
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto got = fe.ast_body();
+  auto program = p->program();
+  EXPECT_THAT(test::ToString(program, got),
+              HasSubstr("let x_2 : u32 = x_1.algo;"))
+      << test::ToString(program, got);
+  EXPECT_THAT(test::ToString(program, got),
+              HasSubstr("let x_4 : u32 = x_3.rithm;"))
+      << test::ToString(program, got);
+  p->SkipDumpingPending("crbug.com/tint/863");
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Struct_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeExtract %int %1 40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_EQ(p->error(),
+            "OpCompositeExtract %2 index value 40 is out of bounds for "
+            "structure %27 having 3 members");
+}
+
+TEST_F(SpvParserTest_CompositeExtract, Struct_Array_Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %a_mat = OpTypeArray %m3v2float %uint_3
+     %s = OpTypeStruct %uint %a_mat
+     %ptr = OpTypePointer Function %s
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s %var
+     %2 = OpCompositeExtract %float %1 1 2 0 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : f32 = x_1.field1[2u][0u].y;"));
+}
+
+using SpvParserTest_CompositeInsert = SpvParserTest;
+
+TEST_F(SpvParserTest_CompositeInsert, Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  const auto* expected =
+      R"(var x_1_1 : vec2<f32> = vec2<f32>(50.0, 60.0);
+x_1_1.y = 70.0;
+let x_1 : vec2<f32> = x_1_1;
+return;
+)";
+  EXPECT_EQ(got, expected);
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Vector_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 900
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_EQ(p->error(),
+            "OpCompositeInsert %1 index value 900 is out of bounds for vector "
+            "of 2 elements");
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
+x_2_1[2u] = vec2<f32>(50.0, 60.0);
+let x_2 : mat3x2<f32> = x_2_1;
+)")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_EQ(p->error(),
+            "OpCompositeInsert %2 index value 3 is out of bounds for matrix of "
+            "3 elements");
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
+x_2_1[2u] = vec2<f32>(50.0, 60.0);
+let x_2 : mat3x2<f32> = x_2_1;
+return;
+)")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Array) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %a_u_5
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %a_u_5 %var
+     %2 = OpCompositeInsert %a_u_5 %uint_20 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : array<u32, 5u> = x_1;
+x_2_1[3u] = 20u;
+let x_2 : array<u32, 5u> = x_2_1;
+)")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, RuntimeArray_IsError) {
+  const auto assembly = Preamble() + R"(
+     %rtarr = OpTypeRuntimeArray %uint
+     %ptr = OpTypePointer Function %rtarr
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %rtarr %var
+     %2 = OpCompositeInsert %rtarr %uint_20 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              HasSubstr("can't do OpCompositeInsert on a runtime array: "));
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeInsert %s_v2f_u_i %int_30 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(var x_36 : S;
+let x_1 : S = x_36;
+var x_2_1 : S = x_1;
+x_2_1.field2 = 30;
+let x_2 : S = x_2_1;
+)")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_DifferOnlyInMemberName) {
+  const std::string assembly = R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+
+     OpName %var0 "var0"
+     OpName %var1 "var1"
+     OpMemberName %s0 0 "algo"
+     OpMemberName %s1 0 "rithm"
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %uint = OpTypeInt 32 0
+     %uint_10 = OpConstant %uint 10
+     %uint_11 = OpConstant %uint 11
+
+     %s0 = OpTypeStruct %uint
+     %s1 = OpTypeStruct %uint
+     %ptr0 = OpTypePointer Function %s0
+     %ptr1 = OpTypePointer Function %s1
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var0 = OpVariable %ptr0 Function
+     %var1 = OpVariable %ptr1 Function
+     %1 = OpLoad %s0 %var0
+     %2 = OpCompositeInsert %s0 %uint_10 %1 0
+     %3 = OpLoad %s1 %var1
+     %4 = OpCompositeInsert %s1 %uint_11 %3 0
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const std::string expected = R"(var var0 : S;
+var var1 : S_1;
+let x_1 : S = var0;
+var x_2_1 : S = x_1;
+x_2_1.algo = 10u;
+let x_2 : S = x_2_1;
+let x_3 : S_1 = var1;
+var x_4_1 : S_1 = x_3;
+x_4_1.rithm = 11u;
+let x_4 : S_1 = x_4_1;
+return;
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeInsert %s_v2f_u_i %uint_10 %1 40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_EQ(p->error(),
+            "OpCompositeInsert %2 index value 40 is out of bounds for "
+            "structure %27 having 3 members");
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_Array_Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %a_mat = OpTypeArray %m3v2float %uint_3
+     %s = OpTypeStruct %uint %a_mat
+     %ptr = OpTypePointer Function %s
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s %var
+     %2 = OpCompositeInsert %s %float_70 %1 1 2 0 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(var x_38 : S_1;
+let x_1 : S_1 = x_38;
+var x_2_1 : S_1 = x_1;
+x_2_1.field1[2u][0u].y = 70.0;
+let x_2 : S_1 = x_2_1;
+)")) << body_str;
+}
+
+using SpvParserTest_CopyObject = SpvParserTest;
+
+TEST_F(SpvParserTest_CopyObject, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %uint %uint_3
+     %2 = OpCopyObject %uint %1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_1 : u32 = 3u;
+let x_2 : u32 = x_1;
+)"));
+}
+
+TEST_F(SpvParserTest_CopyObject, Pointer) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %uint
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpVariable %ptr Function
+     %1 = OpCopyObject %ptr %10
+     %2 = OpCopyObject %ptr %1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_1 : ptr<function, u32> = &(x_10);
+let x_2 : ptr<function, u32> = x_1;
+)"));
+}
+
+using SpvParserTest_VectorShuffle = SpvParserTest;
+
+TEST_F(SpvParserTest_VectorShuffle, FunctionScopeOperands_UseBoth) {
+  // Note that variables are generated for the vector operands.
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_3_4
+     %2 = OpIAdd %v2uint %v2uint_4_3 %v2uint_3_4
+     %10 = OpVectorShuffle %v4uint %1 %2 3 2 1 0
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          "let x_10 : vec4<u32> = vec4<u32>(x_2.y, x_2.x, x_1.y, x_1.x);"));
+}
+
+TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_UseBoth) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpVectorShuffle %v4uint %v2uint_3_4 %v2uint_4_3 3 2 1 0
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec4<u32> = vec4<u32>("
+                        "vec2<u32>(4u, 3u).y, "
+                        "vec2<u32>(4u, 3u).x, "
+                        "vec2<u32>(3u, 4u).y, "
+                        "vec2<u32>(3u, 4u).x);"));
+}
+
+TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_AllOnesMapToNull) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_4_3
+     %10 = OpVectorShuffle %v2uint %1 %1 0xFFFFFFFF 1
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec2<u32> = vec2<u32>(0u, x_1.y);"));
+}
+
+TEST_F(SpvParserTest_VectorShuffle,
+       FunctionScopeOperands_MixedInputOperandSizes) {
+  // Note that variables are generated for the vector operands.
+  const auto assembly = Preamble() + R"(
+     %v3uint_3_4_5 = OpConstantComposite %v3uint %uint_3 %uint_4 %uint_5
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_3_4
+     %3 = OpCopyObject %v3uint %v3uint_3_4_5
+     %10 = OpVectorShuffle %v2uint %1 %3 1 4
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_10 : vec2<u32> = vec2<u32>(x_1.y, x_3.z);"));
+}
+
+TEST_F(SpvParserTest_VectorShuffle, IndexTooBig_IsError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpVectorShuffle %v4uint %v2uint_3_4 %v2uint_4_3 9 2 1 0
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              Eq("invalid vectorshuffle ID %10: index too large: 9"));
+}
+
+using SpvParserTest_VectorExtractDynamic = SpvParserTest;
+
+TEST_F(SpvParserTest_VectorExtractDynamic, SignedIndex) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_3_4
+     %2 = OpCopyObject %int %int_1
+     %10 = OpVectorExtractDynamic %uint %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr("let x_10 : u32 = x_1[x_2];")) << got;
+}
+
+TEST_F(SpvParserTest_VectorExtractDynamic, UnsignedIndex) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_3_4
+     %2 = OpCopyObject %uint %uint_1
+     %10 = OpVectorExtractDynamic %uint %1 %2
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr("let x_10 : u32 = x_1[x_2];")) << got;
+}
+
+using SpvParserTest_VectorInsertDynamic = SpvParserTest;
+
+TEST_F(SpvParserTest_VectorInsertDynamic, Sample) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCopyObject %v2uint %v2uint_3_4
+     %2 = OpCopyObject %uint %uint_3
+     %3 = OpCopyObject %int %int_1
+     %10 = OpVectorInsertDynamic %v2uint %1 %2 %3
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(R"(var x_10_1 : vec2<u32> = x_1;
+x_10_1[x_3] = x_2;
+let x_10 : vec2<u32> = x_10_1;
+)")) << got
+     << assembly;
+}
+
+TEST_F(SpvParserTest, DISABLED_WorkgroupSize_Overridable) {
+  // TODO(dneto): Support specializable workgroup size. crbug.com/tint/504
+  const auto* assembly = R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %100 "main"
+  OpDecorate %1 BuiltIn WorkgroupSize
+  OpDecorate %uint_2 SpecId 0
+  OpDecorate %uint_4 SpecId 1
+  OpDecorate %uint_8 SpecId 2
+
+  %uint = OpTypeInt 32 0
+  %uint_2 = OpSpecConstant %uint 2
+  %uint_4 = OpSpecConstant %uint 4
+  %uint_8 = OpSpecConstant %uint 8
+  %v3uint = OpTypeVector %uint 3
+  %1 = OpSpecConstantComposite %v3uint %uint_2 %uint_4 %uint_8
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpCopyObject %v3uint %1
+     %11 = OpCopyObject %uint %uint_2
+     %12 = OpCopyObject %uint %uint_4
+     %13 = OpCopyObject %uint %uint_8
+     OpReturn
+     OpFunctionEnd
+)";
+
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.Emit()) << p->error();
+  const auto got = test::ToString(p->program());
+  EXPECT_THAT(got, HasSubstr(R"(
+  VariableConst{
+    Decorations{
+      OverrideDecoration{0}
+    }
+    x_2
+    none
+    __u32
+    {
+      ScalarConstructor[not set]{2}
+    }
+  }
+  VariableConst{
+    Decorations{
+      OverrideDecoration{1}
+    }
+    x_3
+    none
+    __u32
+    {
+      ScalarConstructor[not set]{4}
+    }
+  }
+  VariableConst{
+    Decorations{
+      OverrideDecoration{2}
+    }
+    x_4
+    none
+    __u32
+    {
+      ScalarConstructor[not set]{8}
+    }
+  }
+)")) << got;
+  EXPECT_THAT(got, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_10
+        none
+        __vec_3__u32
+        {
+          TypeConstructor[not set]{
+            __vec_3__u32
+            ScalarConstructor[not set]{2}
+            ScalarConstructor[not set]{4}
+            ScalarConstructor[not set]{8}
+          }
+        }
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_11
+        none
+        __u32
+        {
+          Identifier[not set]{x_2}
+        }
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_12
+        none
+        __u32
+        {
+          Identifier[not set]{x_3}
+        }
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_13
+        none
+        __u32
+        {
+          Identifier[not set]{x_4}
+        }
+      }
+    })"))
+      << got << assembly;
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_conversion_test.cc b/src/tint/reader/spirv/function_conversion_test.cc
new file mode 100644
index 0000000..1dffd35
--- /dev/null
+++ b/src/tint/reader/spirv/function_conversion_test.cc
@@ -0,0 +1,655 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint Fragment %100 "main"
+  OpExecutionMode %100 OriginUpperLeft
+
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %bool = OpTypeBool
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %true = OpConstantTrue %bool
+  %false = OpConstantFalse %bool
+  %v2bool = OpTypeVector %bool 2
+  %v2bool_t_f = OpConstantComposite %v2bool %true %false
+
+  %uint_10 = OpConstant %uint 10
+  %uint_20 = OpConstant %uint 20
+  %int_30 = OpConstant %int 30
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+
+  %ptr_uint = OpTypePointer Function %uint
+  %ptr_int = OpTypePointer Function %int
+  %ptr_float = OpTypePointer Function %float
+
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+
+  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
+  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
+  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
+  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+)";
+}
+
+using SpvUnaryConversionTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvUnaryConversionTest, Bitcast_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpBitcast %uint %float_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>(50.0);"));
+}
+
+TEST_F(SpvUnaryConversionTest, Bitcast_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpBitcast %v2float %v2uint_10_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr(
+          "let x_1 : vec2<f32> = bitcast<vec2<f32>>(vec2<u32>(10u, 20u));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_BadArg) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertSToF %float %void
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_BadArg) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertUToF %float %void
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_BadArg) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToS %float %void
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_BadArg) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToU %float %void
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("unhandled expression for ID 2\n%2 = OpTypeVoid"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertSToF %float %false
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("operand for conversion to floating point must be "
+                        "integral scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertSToF %v2float %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to floating point must be integral "
+                "scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %int %int_30
+     %1 = OpConvertSToF %float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = f32(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %uint %uint_10
+     %1 = OpConvertSToF %float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = f32(bitcast<i32>(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2int %v2int_30_40
+     %1 = OpConvertSToF %v2float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2uint %v2uint_10_20
+     %1 = OpConvertSToF %v2float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<i32>>(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertUToF %float %false
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("operand for conversion to floating point must be "
+                        "integral scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertUToF %v2float %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to floating point must be integral "
+                "scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %int %int_30
+     %1 = OpConvertUToF %float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = f32(bitcast<u32>(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %uint %uint_10
+     %1 = OpConvertUToF %float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = f32(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2int %v2int_30_40
+     %1 = OpConvertUToF %v2float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<u32>>(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2uint %v2uint_10_20
+     %1 = OpConvertUToF %v2float %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToS %int %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to signed integer must be floating "
+                "point scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToS %v2float %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to signed integer must be floating "
+                "point scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %float %float_50
+     %1 = OpConvertFToS %int %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : i32 = i32(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %float %float_50
+     %1 = OpConvertFToS %uint %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = bitcast<u32>(i32(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToSigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2float %v2float_50_60
+     %1 = OpConvertFToS %v2int %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<i32> = vec2<i32>(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2float %v2float_50_60
+     %1 = OpConvertFToS %v2uint %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(vec2<i32>(x_30));"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToU %int %uint_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to unsigned integer must be floating "
+                "point scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_BadArgType) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpConvertFToU %v2float %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operand for conversion to unsigned integer must be floating "
+                "point scalar or vector"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToSigned_IsError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %float %float_50
+     %1 = OpConvertFToU %int %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(), HasSubstr("Expected unsigned int scalar or vector "
+                                    "type as Result Type: ConvertFToU"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %float %float_50
+     %1 = OpConvertFToU %uint %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = u32(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToSigned_IsError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2float %v2float_50_60
+     %1 = OpConvertFToU %v2int %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(), HasSubstr("Expected unsigned int scalar or vector "
+                                    "type as Result Type: ConvertFToU"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToUnsigned) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %30 = OpCopyObject %v2float %v2float_50_60
+     %1 = OpConvertFToU %v2uint %30
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<u32> = vec2<u32>(x_30);"));
+}
+
+TEST_F(SpvUnaryConversionTest, ConvertFToU_HoistedValue) {
+  // From crbug.com/tint/804
+  const auto assembly = Preamble() + R"(
+
+%100 = OpFunction %void None %voidfn
+%10 = OpLabel
+OpBranch %30
+
+%30 = OpLabel
+OpLoopMerge %90 %80 None
+OpBranchConditional %true %90 %40
+
+%40 = OpLabel
+OpSelectionMerge %50 None
+OpBranchConditional %true %45 %50
+
+%45 = OpLabel
+; This value is hoisted
+%600 = OpCopyObject %float %float_50
+OpBranch %50
+
+%50 = OpLabel
+OpBranch %90
+
+%80 = OpLabel ; unreachable continue target
+%82 = OpConvertFToU %uint %600
+OpBranch %30 ; backedge
+
+%90 = OpLabel
+OpReturn
+OpFunctionEnd
+
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_82 : u32 = u32(x_600);"));
+}
+
+// TODO(dneto): OpSConvert // only if multiple widths
+// TODO(dneto): OpUConvert // only if multiple widths
+// TODO(dneto): OpFConvert // only if multiple widths
+// TODO(dneto): OpQuantizeToF16 // only if f16 supported
+// TODO(dneto): OpSatConvertSToU // Kernel (OpenCL), not in WebGPU
+// TODO(dneto): OpSatConvertUToS // Kernel (OpenCL), not in WebGPU
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_decl_test.cc b/src/tint/reader/spirv/function_decl_test.cc
new file mode 100644
index 0000000..e30930d
--- /dev/null
+++ b/src/tint/reader/spirv/function_decl_test.cc
@@ -0,0 +1,156 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %100 "x_100"
+    OpExecutionMode %100 OriginUpperLeft
+  )";
+}
+
+/// @returns a SPIR-V assembly segment which assigns debug names
+/// to particular IDs.
+std::string Names(std::vector<std::string> ids) {
+  std::ostringstream outs;
+  for (auto& id : ids) {
+    outs << "    OpName %" << id << " \"" << id << "\"\n";
+  }
+  return outs.str();
+}
+
+std::string CommonTypes() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %float_0 = OpConstant %float 0.0
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %100 = OpFunction %void None %voidfn
+    %entry_100 = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
+TEST_F(SpvParserTest, Emit_VoidFunctionWithoutParams) {
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.Emit());
+  auto got = test::ToString(p->program());
+  std::string expect = R"(fn x_100() {
+  return;
+}
+)";
+  EXPECT_EQ(got, expect);
+}
+
+TEST_F(SpvParserTest, Emit_NonVoidResultType) {
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
+     %fn_ret_float = OpTypeFunction %float
+     %200 = OpFunction %float None %fn_ret_float
+     %entry = OpLabel
+     OpReturnValue %float_0
+     OpFunctionEnd
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.Emit());
+
+  auto got = test::ToString(p->program());
+  std::string expect = R"(fn x_200() -> f32 {
+  return 0.0;
+}
+)";
+  EXPECT_THAT(got, HasSubstr(expect));
+}
+
+TEST_F(SpvParserTest, Emit_MixedParamTypes) {
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"a", "b", "c"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
+
+     %200 = OpFunction %void None %fn_mixed_params
+     %a = OpFunctionParameter %uint
+     %b = OpFunctionParameter %float
+     %c = OpFunctionParameter %int
+     %mixed_entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.Emit());
+
+  auto got = test::ToString(p->program());
+  std::string expect = R"(fn x_200(a : u32, b : f32, c : i32) {
+  return;
+}
+)";
+  EXPECT_THAT(got, HasSubstr(expect));
+}
+
+TEST_F(SpvParserTest, Emit_GenerateParamNames) {
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
+
+     %200 = OpFunction %void None %fn_mixed_params
+     %14 = OpFunctionParameter %uint
+     %15 = OpFunctionParameter %float
+     %16 = OpFunctionParameter %int
+     %mixed_entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(200);
+  EXPECT_TRUE(fe.Emit());
+
+  auto got = test::ToString(p->program());
+  std::string expect = R"(fn x_200(x_14 : u32, x_15 : f32, x_16 : i32) {
+  return;
+}
+)";
+  EXPECT_THAT(got, HasSubstr(expect));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_glsl_std_450_test.cc b/src/tint/reader/spirv/function_glsl_std_450_test.cc
new file mode 100644
index 0000000..ce16f13
--- /dev/null
+++ b/src/tint/reader/spirv/function_glsl_std_450_test.cc
@@ -0,0 +1,1177 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+  OpCapability Shader
+  %glsl = OpExtInstImport "GLSL.std.450"
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint GLCompute %100 "main"
+  OpExecutionMode %100 LocalSize 1 1 1
+
+  OpName %u1 "u1"
+  OpName %u2 "u2"
+  OpName %u3 "u3"
+  OpName %i1 "i1"
+  OpName %i2 "i2"
+  OpName %i3 "i3"
+  OpName %f1 "f1"
+  OpName %f2 "f2"
+  OpName %f3 "f3"
+  OpName %v2u1 "v2u1"
+  OpName %v2u2 "v2u2"
+  OpName %v2u3 "v2u3"
+  OpName %v2i1 "v2i1"
+  OpName %v2i2 "v2i2"
+  OpName %v2i3 "v2i3"
+  OpName %v2f1 "v2f1"
+  OpName %v2f2 "v2f2"
+  OpName %v2f3 "v2f3"
+  OpName %v3f1 "v3f1"
+  OpName %v3f2 "v3f2"
+  OpName %v4f1 "v4f1"
+  OpName %v4f2 "v4f2"
+
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %uint_10 = OpConstant %uint 10
+  %uint_15 = OpConstant %uint 15
+  %uint_20 = OpConstant %uint 20
+  %int_30 = OpConstant %int 30
+  %int_35 = OpConstant %int 35
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+  %float_70 = OpConstant %float 70
+
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+  %v3float = OpTypeVector %float 3
+  %v4float = OpTypeVector %float 4
+
+  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
+  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
+  %v2uint_15_15 = OpConstantComposite %v2uint %uint_15 %uint_15
+  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
+  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
+  %v2int_35_35 = OpConstantComposite %v2int %int_35 %int_35
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+  %v2float_70_70 = OpConstantComposite %v2float %float_70 %float_70
+
+  %v3float_50_60_70 = OpConstantComposite %v3float %float_50 %float_60 %float_70
+  %v3float_60_70_50 = OpConstantComposite %v3float %float_60 %float_70 %float_50
+
+  %v4float_50_50_50_50 = OpConstantComposite %v4float %float_50 %float_50 %float_50 %float_50
+
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  %u1 = OpCopyObject %uint %uint_10
+  %u2 = OpCopyObject %uint %uint_15
+  %u3 = OpCopyObject %uint %uint_20
+
+  %i1 = OpCopyObject %int %int_30
+  %i2 = OpCopyObject %int %int_35
+  %i3 = OpCopyObject %int %int_40
+
+  %f1 = OpCopyObject %float %float_50
+  %f2 = OpCopyObject %float %float_60
+  %f3 = OpCopyObject %float %float_70
+
+  %v2u1 = OpCopyObject %v2uint %v2uint_10_20
+  %v2u2 = OpCopyObject %v2uint %v2uint_20_10
+  %v2u3 = OpCopyObject %v2uint %v2uint_15_15
+
+  %v2i1 = OpCopyObject %v2int %v2int_30_40
+  %v2i2 = OpCopyObject %v2int %v2int_40_30
+  %v2i3 = OpCopyObject %v2int %v2int_35_35
+
+  %v2f1 = OpCopyObject %v2float %v2float_50_60
+  %v2f2 = OpCopyObject %v2float %v2float_60_50
+  %v2f3 = OpCopyObject %v2float %v2float_70_70
+
+  %v3f1 = OpCopyObject %v3float %v3float_50_60_70
+  %v3f2 = OpCopyObject %v3float %v3float_60_70_50
+
+  %v4f1 = OpCopyObject %v4float %v4float_50_50_50_50
+  %v4f2 = OpCopyObject %v4float %v4f1
+)";
+}
+
+struct GlslStd450Case {
+  std::string opcode;
+  std::string wgsl_func;
+};
+inline std::ostream& operator<<(std::ostream& out, GlslStd450Case c) {
+  out << "GlslStd450Case(" << c.opcode << " " << c.wgsl_func << ")";
+  return out;
+}
+
+// Nomenclature:
+// Float = scalar float
+// Floating = scalar float or vector-of-float
+// Float3 = 3-element vector of float
+// Int = scalar signed int
+// Inting = scalar int or vector-of-int
+// Uint = scalar unsigned int
+// Uinting = scalar unsigned or vector-of-unsigned
+
+using SpvParserTest_GlslStd450_Float_Floating =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Float_FloatingFloating =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Floating_Floating =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Floating_FloatingFloating =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Floating_FloatingInting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Float3_Float3Float3 =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+
+using SpvParserTest_GlslStd450_Inting_Inting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Inting_IntingInting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Inting_IntingIntingInting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Uinting_UintingUinting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+using SpvParserTest_GlslStd450_Uinting_UintingUintingUinting =
+    SpvParserTestBase<::testing::TestWithParam<GlslStd450Case>>;
+
+TEST_P(SpvParserTest_GlslStd450_Float_Floating, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Float_Floating, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %v2f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(v2f1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1 %f2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, f2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %v2f1 %v2f2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func +
+                              "(v2f1, v2f2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl )" +
+                        GetParam().opcode + R"( %v2f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
+                              "(v2f1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1 %f2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, f2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl )" +
+                        GetParam().opcode + R"( %v2f1 %v2f2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
+                              "(v2f1, v2f2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1 %f2 %f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func +
+                              "(f1, f2, f3);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2f1 %v2f2 %v2f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
+                              "(v2f1, v2f2, v2f3);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl )" +
+                        GetParam().opcode + R"( %f1 %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body, HasSubstr("let x_1 : f32 = " + GetParam().wgsl_func + "(f1, i1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2f1 %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func +
+                              "(v2f1, v2i1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Float3_Float3Float3, Samples) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v3float %glsl )" +
+                        GetParam().opcode +
+                        R"( %v3f1 %v3f2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = " + GetParam().wgsl_func +
+                              "(v3f1, v3f2);"))
+      << body;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Float_Floating,
+                         ::testing::Values(GlslStd450Case{"Length", "length"}));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Float_FloatingFloating,
+                         ::testing::Values(GlslStd450Case{"Distance",
+                                                          "distance"}));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Floating_Floating,
+                         ::testing::ValuesIn(std::vector<GlslStd450Case>{
+                             {"Acos", "acos"},                //
+                             {"Asin", "asin"},                //
+                             {"Atan", "atan"},                //
+                             {"Ceil", "ceil"},                //
+                             {"Cos", "cos"},                  //
+                             {"Cosh", "cosh"},                //
+                             {"Degrees", "degrees"},          //
+                             {"Exp", "exp"},                  //
+                             {"Exp2", "exp2"},                //
+                             {"FAbs", "abs"},                 //
+                             {"FSign", "sign"},               //
+                             {"Floor", "floor"},              //
+                             {"Fract", "fract"},              //
+                             {"InverseSqrt", "inverseSqrt"},  //
+                             {"Log", "log"},                  //
+                             {"Log2", "log2"},                //
+                             {"Radians", "radians"},          //
+                             {"Round", "round"},              //
+                             {"RoundEven", "round"},          //
+                             {"Sin", "sin"},                  //
+                             {"Sinh", "sinh"},                //
+                             {"Sqrt", "sqrt"},                //
+                             {"Tan", "tan"},                  //
+                             {"Tanh", "tanh"},                //
+                             {"Trunc", "trunc"},              //
+                         }));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Floating_FloatingFloating,
+                         ::testing::ValuesIn(std::vector<GlslStd450Case>{
+                             {"Atan2", "atan2"},
+                             {"NMax", "max"},
+                             {"NMin", "min"},
+                             {"FMax", "max"},  // WGSL max promises more for NaN
+                             {"FMin", "min"},  // WGSL min promises more for NaN
+                             {"Pow", "pow"},
+                             {"Step", "step"},
+                         }));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Floating_FloatingInting,
+                         ::testing::Values(GlslStd450Case{"Ldexp", "ldexp"}));
+// For ldexp with unsigned second argument, see below.
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Float3_Float3Float3,
+                         ::testing::Values(GlslStd450Case{"Cross", "cross"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    Samples,
+    SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating,
+    ::testing::ValuesIn(std::vector<GlslStd450Case>{
+        {"NClamp", "clamp"},
+        {"FClamp", "clamp"},  // WGSL FClamp promises more for NaN
+        {"Fma", "fma"},
+        {"FMix", "mix"},
+        {"SmoothStep", "smoothStep"}}));
+
+TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl )" +
+                        GetParam().opcode +
+                        R"( %i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body,
+              HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func + "(i1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2int %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2i1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
+                              "(v2i1);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl )" +
+                        GetParam().opcode +
+                        R"( %i1 %i2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body, HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func + "(i1, i2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2int %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2i1 %v2i2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
+                              "(v2i1, v2i2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl )" +
+                        GetParam().opcode +
+                        R"( %i1 %i2 %i3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : i32 = " + GetParam().wgsl_func +
+                              "(i1, i2, i3);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2int %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2i1 %v2i2 %v2i3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
+                              "(v2i1, v2i2, v2i3);"))
+      << body;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Inting_Inting,
+                         ::testing::Values(GlslStd450Case{"SAbs", "abs"}));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Inting_IntingInting,
+                         ::testing::Values(GlslStd450Case{"SMax", "max"},
+                                           GlslStd450Case{"SMin", "min"}));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Inting_IntingIntingInting,
+                         ::testing::Values(GlslStd450Case{"SClamp", "clamp"}));
+
+TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl )" +
+                        GetParam().opcode + R"( %u1 %u2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body, HasSubstr("let x_1 : u32 = " + GetParam().wgsl_func + "(u1, u2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2uint %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2u1 %v2u2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func +
+                              "(v2u1, v2u2);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl )" +
+                        GetParam().opcode + R"( %u1 %u2 %u3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = " + GetParam().wgsl_func +
+                              "(u1, u2, u3);"))
+      << body;
+}
+
+TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2uint %glsl )" +
+                        GetParam().opcode +
+                        R"( %v2u1 %v2u2 %v2u3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func +
+                              "(v2u1, v2u2, v2u3);"))
+      << body;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Uinting_UintingUinting,
+                         ::testing::Values(GlslStd450Case{"UMax", "max"},
+                                           GlslStd450Case{"UMin", "min"}));
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_Uinting_UintingUintingUinting,
+                         ::testing::Values(GlslStd450Case{"UClamp", "clamp"}));
+
+// Test Normalize.  WGSL does not have a scalar form of the normalize builtin.
+// So we have to test it separately, as it does not fit the patterns tested
+// above.
+
+TEST_F(SpvParserTest, Normalize_Scalar) {
+  // Scalar normalize always results in 1.0
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl Normalize %f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : f32 = 1.0;")) << body;
+}
+
+TEST_F(SpvParserTest, Normalize_Vector2) {
+  // Scalar normalize always results in 1.0
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl Normalize %v2f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = normalize(v2f1);"))
+      << body;
+}
+
+TEST_F(SpvParserTest, Normalize_Vector3) {
+  // Scalar normalize always results in 1.0
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v3float %glsl Normalize %v3f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = normalize(v3f1);"))
+      << body;
+}
+
+TEST_F(SpvParserTest, Normalize_Vector4) {
+  // Scalar normalize always results in 1.0
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v4float %glsl Normalize %v4f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : vec4<f32> = normalize(v4f1);"))
+      << body;
+}
+
+// Check that we convert signedness of operands and result type.
+// This is needed for each of the integer-based extended instructions.
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_SAbs) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl SAbs %u1
+     %2 = OpExtInst %v2uint %glsl SAbs %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(R"(let x_1 : u32 = bitcast<u32>(abs(bitcast<i32>(u1)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(abs(bitcast<vec2<i32>>(v2u1)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_SMax) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl SMax %u1 %u2
+     %2 = OpExtInst %v2uint %glsl SMax %v2u1 %v2u2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : u32 = bitcast<u32>(max(bitcast<i32>(u1), bitcast<i32>(u2)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(max(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_SMin) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl SMin %u1 %u2
+     %2 = OpExtInst %v2uint %glsl SMin %v2u1 %v2u2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : u32 = bitcast<u32>(min(bitcast<i32>(u1), bitcast<i32>(u2)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(min(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_SClamp) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %uint %glsl SClamp %u1 %i2 %u3
+     %2 = OpExtInst %v2uint %glsl SClamp %v2u1 %v2i2 %v2u3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : u32 = bitcast<u32>(clamp(bitcast<i32>(u1), i2, bitcast<i32>(u3)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(clamp(bitcast<vec2<i32>>(v2u1), v2i2, bitcast<vec2<i32>>(v2u3)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_UMax) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl UMax %i1 %i2
+     %2 = OpExtInst %v2int %glsl UMax %v2i1 %v2i2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : i32 = bitcast<i32>(max(bitcast<u32>(i1), bitcast<u32>(i2)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(max(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_UMin) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl UMin %i1 %i2
+     %2 = OpExtInst %v2int %glsl UMin %v2i1 %v2i2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : i32 = bitcast<i32>(min(bitcast<u32>(i1), bitcast<u32>(i2)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(min(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
+      << body;
+}
+
+TEST_F(SpvParserTest, RectifyOperandsAndResult_UClamp) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %int %glsl UClamp %i1 %u2 %i3
+     %2 = OpExtInst %v2int %glsl UClamp %v2i1 %v2u2 %v2i3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_1 : i32 = bitcast<i32>(clamp(bitcast<u32>(i1), u2, bitcast<u32>(i3)));)"))
+      << body;
+  EXPECT_THAT(
+      body,
+      HasSubstr(
+          R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(clamp(bitcast<vec2<u32>>(v2i1), v2u2, bitcast<vec2<u32>>(v2i3)));)"))
+      << body;
+}
+
+struct DataPackingCase {
+  std::string opcode;
+  std::string wgsl_func;
+  uint32_t vec_size;
+};
+
+inline std::ostream& operator<<(std::ostream& out, DataPackingCase c) {
+  out << "DataPacking(" << c.opcode << ")";
+  return out;
+}
+
+using SpvParserTest_GlslStd450_DataPacking =
+    SpvParserTestBase<::testing::TestWithParam<DataPackingCase>>;
+
+TEST_P(SpvParserTest_GlslStd450_DataPacking, Valid) {
+  auto param = GetParam();
+  const auto assembly = Preamble() + R"(
+  %1 = OpExtInst %uint %glsl )" +
+                        param.opcode +
+                        (param.vec_size == 2 ? " %v2f1" : " %v4f1") + R"(
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : u32 = " + param.wgsl_func + "(v" +
+                              std::to_string(param.vec_size) + "f1);"))
+      << body;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_DataPacking,
+                         ::testing::ValuesIn(std::vector<DataPackingCase>{
+                             {"PackSnorm4x8", "pack4x8snorm", 4},
+                             {"PackUnorm4x8", "pack4x8unorm", 4},
+                             {"PackSnorm2x16", "pack2x16snorm", 2},
+                             {"PackUnorm2x16", "pack2x16unorm", 2},
+                             {"PackHalf2x16", "pack2x16float", 2}}));
+
+using SpvParserTest_GlslStd450_DataUnpacking =
+    SpvParserTestBase<::testing::TestWithParam<DataPackingCase>>;
+
+TEST_P(SpvParserTest_GlslStd450_DataUnpacking, Valid) {
+  auto param = GetParam();
+  const auto assembly = Preamble() + R"(
+  %1 = OpExtInst )" + (param.vec_size == 2 ? "%v2float" : "%v4float") +
+                        std::string(" %glsl ") + param.opcode + R"( %u1
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body, HasSubstr("let x_1 : " +
+                              std::string(param.vec_size == 2 ? "vec2<f32>"
+                                                              : "vec4<f32>") +
+
+                              +" = " + param.wgsl_func + "(u1);"))
+      << body;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samples,
+                         SpvParserTest_GlslStd450_DataUnpacking,
+                         ::testing::ValuesIn(std::vector<DataPackingCase>{
+                             {"UnpackSnorm4x8", "unpack4x8snorm", 4},
+                             {"UnpackUnorm4x8", "unpack4x8unorm", 4},
+                             {"UnpackSnorm2x16", "unpack2x16snorm", 2},
+                             {"UnpackUnorm2x16", "unpack2x16unorm", 2},
+                             {"UnpackHalf2x16", "unpack2x16float", 2}}));
+
+TEST_F(SpvParserTest, GlslStd450_Refract_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl Refract %f1 %f2 %f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected =
+      R"(let x_1 : f32 = refract(vec2<f32>(f1, 0.0), vec2<f32>(f2, 0.0), f3).x;)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_Refract_Vector) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl Refract %v2f1 %v2f2 %f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected = R"(let x_1 : vec2<f32> = refract(v2f1, v2f2, f3);)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_FaceForward_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %99 = OpFAdd %float %f1 %f1 ; normal operand has only one use
+     %1 = OpExtInst %float %glsl FaceForward %99 %f2 %f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  // The %99 sum only has one use.  Ensure it is evaluated only once by
+  // making a let-declaration for it, since it is the normal operand to
+  // the builtin function, and code generation uses it twice.
+  const auto* expected =
+      R"(let x_1 : f32 = select(-(x_99), x_99, ((f2 * f3) < 0.0));)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_FaceForward_Vector) {
+  const auto assembly = Preamble() + R"(
+     %99 = OpFAdd %v2float %v2f1 %v2f1
+     %1 = OpExtInst %v2float %glsl FaceForward %v2f1 %v2f2 %v2f3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected =
+      R"(let x_1 : vec2<f32> = faceForward(v2f1, v2f2, v2f3);)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_Reflect_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %98 = OpFAdd %float %f1 %f1 ; has only one use
+     %99 = OpFAdd %float %f2 %f2 ; has only one use
+     %1 = OpExtInst %float %glsl Reflect %98 %99
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  // The %99 sum only has one use.  Ensure it is evaluated only once by
+  // making a let-declaration for it, since it is the normal operand to
+  // the builtin function, and code generation uses it twice.
+  const auto* expected =
+      R"(let x_1 : f32 = (x_98 - (2.0 * (x_99 * (x_99 * x_98))));)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_Reflect_Vector) {
+  const auto assembly = Preamble() + R"(
+     %98 = OpFAdd %v2float %v2f1 %v2f1
+     %99 = OpFAdd %v2float %v2f2 %v2f2
+     %1 = OpExtInst %v2float %glsl Reflect %98 %99
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected = R"(
+let x_98 : vec2<f32> = (v2f1 + v2f1);
+let x_99 : vec2<f32> = (v2f2 + v2f2);
+let x_1 : vec2<f32> = reflect(x_98, x_99);
+)";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+// For ldexp with signed second argument, see above.
+TEST_F(SpvParserTest, GlslStd450_Ldexp_Scalar_Float_Uint) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl Ldexp %f1 %u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected = "let x_1 : f32 = ldexp(f1, i32(u1));";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+TEST_F(SpvParserTest, GlslStd450_Ldexp_Vector_Floatvec_Uintvec) {
+  const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %v2float %glsl Ldexp %v2f1 %v2u1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body = test::ToString(p->program(), ast_body);
+  const auto* expected = "let x_1 : vec2<f32> = ldexp(v2f1, vec2<i32>(v2u1));";
+
+  EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_logical_test.cc b/src/tint/reader/spirv/function_logical_test.cc
new file mode 100644
index 0000000..23dc5c4
--- /dev/null
+++ b/src/tint/reader/spirv/function_logical_test.cc
@@ -0,0 +1,992 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint Fragment %100 "main"
+  OpExecutionMode %100 OriginUpperLeft
+
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %bool = OpTypeBool
+  %true = OpConstantTrue %bool
+  %false = OpConstantFalse %bool
+
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %uint_10 = OpConstant %uint 10
+  %uint_20 = OpConstant %uint 20
+  %int_30 = OpConstant %int 30
+  %int_40 = OpConstant %int 40
+  %float_50 = OpConstant %float 50
+  %float_60 = OpConstant %float 60
+
+  %ptr_uint = OpTypePointer Function %uint
+  %ptr_int = OpTypePointer Function %int
+  %ptr_float = OpTypePointer Function %float
+
+  %v2bool = OpTypeVector %bool 2
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+
+  %v2bool_t_f = OpConstantComposite %v2bool %true %false
+  %v2bool_f_t = OpConstantComposite %v2bool %false %true
+  %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
+  %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
+  %v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
+  %v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
+  %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60
+  %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50
+)";
+}
+
+// Returns the AST dump for a given SPIR-V assembly constant.
+std::string AstFor(std::string assembly) {
+  if (assembly == "v2bool_t_f") {
+    return "vec2<bool>(true, false)";
+  }
+  if (assembly == "v2bool_f_t") {
+    return "vec2<bool>(false, true)";
+  }
+  if (assembly == "v2uint_10_20") {
+    return "vec2<u32>(10u, 20u)";
+  }
+  if (assembly == "cast_uint_10") {
+    return "bitcast<i32>(10u)";
+  }
+  if (assembly == "cast_uint_20") {
+    return "bitcast<i32>(20u)";
+  }
+  if (assembly == "cast_v2uint_10_20") {
+    return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
+  }
+  if (assembly == "v2uint_20_10") {
+    return "vec2<u32>(20u, 10u)";
+  }
+  if (assembly == "cast_v2uint_20_10") {
+    return "bitcast<vec2<i32>>(vec2<u32>(20u, 10u))";
+  }
+  if (assembly == "cast_int_30") {
+    return "bitcast<u32>(30)";
+  }
+  if (assembly == "cast_int_40") {
+    return "bitcast<u32>(40)";
+  }
+  if (assembly == "v2int_30_40") {
+    return "vec2<i32>(30, 40)";
+  }
+  if (assembly == "cast_v2int_30_40") {
+    return "bitcast<vec2<u32>>(vec2<i32>(30, 40))";
+  }
+  if (assembly == "v2int_40_30") {
+    return "vec2<i32>(40, 30)";
+  }
+  if (assembly == "cast_v2int_40_30") {
+    return "bitcast<vec2<u32>>(vec2<i32>(40, 30))";
+  }
+  if (assembly == "v2float_50_60") {
+    return "vec2<f32>(50.0, 60.0)";
+  }
+  if (assembly == "v2float_60_50") {
+    return "vec2<f32>(60.0, 50.0)";
+  }
+  return "bad case";
+}
+
+using SpvUnaryLogicalTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvUnaryLogicalTest, LogicalNot_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpLogicalNot %bool %true
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !(true);"));
+}
+
+TEST_F(SpvUnaryLogicalTest, LogicalNot_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpLogicalNot %v2bool %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<bool> = !(vec2<bool>(true, false));"));
+}
+
+struct BinaryData {
+  const std::string res_type;
+  const std::string lhs;
+  const std::string op;
+  const std::string rhs;
+  const std::string ast_type;
+  const std::string ast_lhs;
+  const std::string ast_op;
+  const std::string ast_rhs;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
+      << "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
+      << data.ast_op << "," << data.ast_rhs << "}";
+  return out;
+}
+
+using SpvBinaryLogicalTest =
+    SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
+
+TEST_P(SpvBinaryLogicalTest, EmitExpression) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = )" + GetParam().op +
+                        " %" + GetParam().res_type + " %" + GetParam().lhs +
+                        " %" + GetParam().rhs + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << "\n"
+      << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  std::ostringstream ss;
+  ss << "let x_1 : " << GetParam().ast_type << " = (" << GetParam().ast_lhs
+     << " " << GetParam().ast_op << " " << GetParam().ast_rhs << ");";
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(ss.str()))
+      << assembly;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_IEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // uint uint
+        BinaryData{"bool", "uint_10", "OpIEqual", "uint_20", "bool", "10u",
+                   "==", "20u"},
+        // int int
+        BinaryData{"bool", "int_30", "OpIEqual", "int_40", "bool", "30",
+                   "==", "40"},
+        // uint int
+        BinaryData{"bool", "uint_10", "OpIEqual", "int_40", "bool", "10u",
+                   "==", "bitcast<u32>(40)"},
+        // int uint
+        BinaryData{"bool", "int_40", "OpIEqual", "uint_10", "bool", "40",
+                   "==", "bitcast<i32>(10u)"},
+        // v2uint v2uint
+        BinaryData{"v2bool", "v2uint_10_20", "OpIEqual", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2uint_10_20"),
+                   "==", AstFor("v2uint_20_10")},
+        // v2int v2int
+        BinaryData{"v2bool", "v2int_30_40", "OpIEqual", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2int_30_40"),
+                   "==", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdEqual", "float_60",
+                                 "bool", "50.0", "==", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdEqual",
+                                 "v2float_60_50", "vec2<bool>",
+                                 AstFor("v2float_50_60"),
+                                 "==", AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_INotEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both uint
+        BinaryData{"bool", "uint_10", "OpINotEqual", "uint_20", "bool", "10u",
+                   "!=", "20u"},
+        // Both int
+        BinaryData{"bool", "int_30", "OpINotEqual", "int_40", "bool", "30",
+                   "!=", "40"},
+        // uint int
+        BinaryData{"bool", "uint_10", "OpINotEqual", "int_40", "bool", "10u",
+                   "!=", "bitcast<u32>(40)"},
+        // int uint
+        BinaryData{"bool", "int_40", "OpINotEqual", "uint_10", "bool", "40",
+                   "!=", "bitcast<i32>(10u)"},
+        // Both v2uint
+        BinaryData{"v2bool", "v2uint_10_20", "OpINotEqual", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2uint_10_20"),
+                   "!=", AstFor("v2uint_20_10")},
+        // Both v2int
+        BinaryData{"v2bool", "v2int_30_40", "OpINotEqual", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2int_30_40"),
+                   "!=", AstFor("v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdNotEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdNotEqual",
+                                 "float_60", "bool", "50.0", "!=", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdNotEqual",
+                                 "v2float_60_50", "vec2<bool>",
+                                 AstFor("v2float_50_60"),
+                                 "!=", AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdLessThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThan",
+                                 "float_60", "bool", "50.0", "<", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdLessThan",
+                                 "v2float_60_50", "vec2<bool>",
+                                 AstFor("v2float_50_60"), "<",
+                                 AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdLessThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThanEqual",
+                                 "float_60", "bool", "50.0", "<=", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60",
+                                 "OpFOrdLessThanEqual", "v2float_60_50",
+                                 "vec2<bool>", AstFor("v2float_50_60"),
+                                 "<=", AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdGreaterThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThan",
+                                 "float_60", "bool", "50.0", ">", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60", "OpFOrdGreaterThan",
+                                 "v2float_60_50", "vec2<bool>",
+                                 AstFor("v2float_50_60"), ">",
+                                 AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_FOrdGreaterThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThanEqual",
+                                 "float_60", "bool", "50.0", ">=", "60.0"},
+                      BinaryData{"v2bool", "v2float_50_60",
+                                 "OpFOrdGreaterThanEqual", "v2float_60_50",
+                                 "vec2<bool>", AstFor("v2float_50_60"),
+                                 ">=", AstFor("v2float_60_50")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_LogicalAnd,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "true", "OpLogicalAnd", "false",
+                                 "bool", "true", "&", "false"},
+                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalAnd",
+                                 "v2bool_f_t", "vec2<bool>",
+                                 AstFor("v2bool_t_f"), "&",
+                                 AstFor("v2bool_f_t")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_LogicalOr,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "true", "OpLogicalOr", "false", "bool",
+                                 "true", "|", "false"},
+                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalOr",
+                                 "v2bool_f_t", "vec2<bool>",
+                                 AstFor("v2bool_t_f"), "|",
+                                 AstFor("v2bool_f_t")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_LogicalEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "true", "OpLogicalEqual", "false",
+                                 "bool", "true", "==", "false"},
+                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalEqual",
+                                 "v2bool_f_t", "vec2<bool>",
+                                 AstFor("v2bool_t_f"),
+                                 "==", AstFor("v2bool_f_t")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_LogicalNotEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(BinaryData{"bool", "true", "OpLogicalNotEqual", "false",
+                                 "bool", "true", "!=", "false"},
+                      BinaryData{"v2bool", "v2bool_t_f", "OpLogicalNotEqual",
+                                 "v2bool_f_t", "vec2<bool>",
+                                 AstFor("v2bool_t_f"),
+                                 "!=", AstFor("v2bool_f_t")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_UGreaterThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both unsigned
+        BinaryData{"bool", "uint_10", "OpUGreaterThan", "uint_20", "bool",
+                   "10u", ">", "20u"},
+        // First arg signed
+        BinaryData{"bool", "int_30", "OpUGreaterThan", "uint_20", "bool",
+                   AstFor("cast_int_30"), ">", "20u"},
+        // Second arg signed
+        BinaryData{"bool", "uint_10", "OpUGreaterThan", "int_40", "bool", "10u",
+                   ">", AstFor("cast_int_40")},
+        // Vector, both unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2uint_10_20"), ">",
+                   AstFor("v2uint_20_10")},
+        // First arg signed
+        BinaryData{"v2bool", "v2int_30_40", "OpUGreaterThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("cast_v2int_30_40"), ">",
+                   AstFor("v2uint_20_10")},
+        // Second arg signed
+        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2uint_10_20"), ">",
+                   AstFor("cast_v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_UGreaterThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both unsigned
+        BinaryData{"bool", "uint_10", "OpUGreaterThanEqual", "uint_20", "bool",
+                   "10u", ">=", "20u"},
+        // First arg signed
+        BinaryData{"bool", "int_30", "OpUGreaterThanEqual", "uint_20", "bool",
+                   AstFor("cast_int_30"), ">=", "20u"},
+        // Second arg signed
+        BinaryData{"bool", "uint_10", "OpUGreaterThanEqual", "int_40", "bool",
+                   "10u", ">=", AstFor("cast_int_40")},
+        // Vector, both unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThanEqual",
+                   "v2uint_20_10", "vec2<bool>", AstFor("v2uint_10_20"),
+                   ">=", AstFor("v2uint_20_10")},
+        // First arg signed
+        BinaryData{"v2bool", "v2int_30_40", "OpUGreaterThanEqual",
+                   "v2uint_20_10", "vec2<bool>", AstFor("cast_v2int_30_40"),
+                   ">=", AstFor("v2uint_20_10")},
+        // Second arg signed
+        BinaryData{"v2bool", "v2uint_10_20", "OpUGreaterThanEqual",
+                   "v2int_40_30", "vec2<bool>", AstFor("v2uint_10_20"),
+                   ">=", AstFor("cast_v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ULessThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both unsigned
+        BinaryData{"bool", "uint_10", "OpULessThan", "uint_20", "bool", "10u",
+                   "<", "20u"},
+        // First arg signed
+        BinaryData{"bool", "int_30", "OpULessThan", "uint_20", "bool",
+                   AstFor("cast_int_30"), "<", "20u"},
+        // Second arg signed
+        BinaryData{"bool", "uint_10", "OpULessThan", "int_40", "bool", "10u",
+                   "<", AstFor("cast_int_40")},
+        // Vector, both unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpULessThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2uint_10_20"), "<",
+                   AstFor("v2uint_20_10")},
+        // First arg signed
+        BinaryData{"v2bool", "v2int_30_40", "OpULessThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("cast_v2int_30_40"), "<",
+                   AstFor("v2uint_20_10")},
+        // Second arg signed
+        BinaryData{"v2bool", "v2uint_10_20", "OpULessThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2uint_10_20"), "<",
+                   AstFor("cast_v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_ULessThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both unsigned
+        BinaryData{"bool", "uint_10", "OpULessThanEqual", "uint_20", "bool",
+                   "10u", "<=", "20u"},
+        // First arg signed
+        BinaryData{"bool", "int_30", "OpULessThanEqual", "uint_20", "bool",
+                   AstFor("cast_int_30"), "<=", "20u"},
+        // Second arg signed
+        BinaryData{"bool", "uint_10", "OpULessThanEqual", "int_40", "bool",
+                   "10u", "<=", AstFor("cast_int_40")},
+        // Vector, both unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpULessThanEqual", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2uint_10_20"),
+                   "<=", AstFor("v2uint_20_10")},
+        // First arg signed
+        BinaryData{"v2bool", "v2int_30_40", "OpULessThanEqual", "v2uint_20_10",
+                   "vec2<bool>", AstFor("cast_v2int_30_40"),
+                   "<=", AstFor("v2uint_20_10")},
+        // Second arg signed
+        BinaryData{"v2bool", "v2uint_10_20", "OpULessThanEqual", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2uint_10_20"),
+                   "<=", AstFor("cast_v2int_40_30")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SGreaterThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both signed
+        BinaryData{"bool", "int_30", "OpSGreaterThan", "int_40", "bool", "30",
+                   ">", "40"},
+        // First arg unsigned
+        BinaryData{"bool", "uint_10", "OpSGreaterThan", "int_40", "bool",
+                   AstFor("cast_uint_10"), ">", "40"},
+        // Second arg unsigned
+        BinaryData{"bool", "int_30", "OpSGreaterThan", "uint_20", "bool", "30",
+                   ">", AstFor("cast_uint_20")},
+        // Vector, both signed
+        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2int_30_40"), ">",
+                   AstFor("v2int_40_30")},
+        // First arg unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpSGreaterThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("cast_v2uint_10_20"), ">",
+                   AstFor("v2int_40_30")},
+        // Second arg unsigned
+        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2int_30_40"), ">",
+                   AstFor("cast_v2uint_20_10")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SGreaterThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both signed
+        BinaryData{"bool", "int_30", "OpSGreaterThanEqual", "int_40", "bool",
+                   "30", ">=", "40"},
+        // First arg unsigned
+        BinaryData{"bool", "uint_10", "OpSGreaterThanEqual", "int_40", "bool",
+                   AstFor("cast_uint_10"), ">=", "40"},
+        // Second arg unsigned
+        BinaryData{"bool", "int_30", "OpSGreaterThanEqual", "uint_20", "bool",
+                   "30", ">=", AstFor("cast_uint_20")},
+        // Vector, both signed
+        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThanEqual",
+                   "v2int_40_30", "vec2<bool>", AstFor("v2int_30_40"),
+                   ">=", AstFor("v2int_40_30")},
+        // First arg unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpSGreaterThanEqual",
+                   "v2int_40_30", "vec2<bool>", AstFor("cast_v2uint_10_20"),
+                   ">=", AstFor("v2int_40_30")},
+        // Second arg unsigned
+        BinaryData{"v2bool", "v2int_30_40", "OpSGreaterThanEqual",
+                   "v2uint_20_10", "vec2<bool>", AstFor("v2int_30_40"),
+                   ">=", AstFor("cast_v2uint_20_10")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SLessThan,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both signed
+        BinaryData{"bool", "int_30", "OpSLessThan", "int_40", "bool", "30", "<",
+                   "40"},
+        // First arg unsigned
+        BinaryData{"bool", "uint_10", "OpSLessThan", "int_40", "bool",
+                   AstFor("cast_uint_10"), "<", "40"},
+        // Second arg unsigned
+        BinaryData{"bool", "int_30", "OpSLessThan", "uint_20", "bool", "30",
+                   "<", AstFor("cast_uint_20")},
+        // Vector, both signed
+        BinaryData{"v2bool", "v2int_30_40", "OpSLessThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2int_30_40"), "<",
+                   AstFor("v2int_40_30")},
+        // First arg unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpSLessThan", "v2int_40_30",
+                   "vec2<bool>", AstFor("cast_v2uint_10_20"), "<",
+                   AstFor("v2int_40_30")},
+        // Second arg unsigned
+        BinaryData{"v2bool", "v2int_30_40", "OpSLessThan", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2int_30_40"), "<",
+                   AstFor("cast_v2uint_20_10")}));
+
+INSTANTIATE_TEST_SUITE_P(
+    SpvParserTest_SLessThanEqual,
+    SpvBinaryLogicalTest,
+    ::testing::Values(
+        // Both signed
+        BinaryData{"bool", "int_30", "OpSLessThanEqual", "int_40", "bool", "30",
+                   "<=", "40"},
+        // First arg unsigned
+        BinaryData{"bool", "uint_10", "OpSLessThanEqual", "int_40", "bool",
+                   AstFor("cast_uint_10"), "<=", "40"},
+        // Second arg unsigned
+        BinaryData{"bool", "int_30", "OpSLessThanEqual", "uint_20", "bool",
+                   "30", "<=", AstFor("cast_uint_20")},
+        // Vector, both signed
+        BinaryData{"v2bool", "v2int_30_40", "OpSLessThanEqual", "v2int_40_30",
+                   "vec2<bool>", AstFor("v2int_30_40"),
+                   "<=", AstFor("v2int_40_30")},
+        // First arg unsigned
+        BinaryData{"v2bool", "v2uint_10_20", "OpSLessThanEqual", "v2int_40_30",
+                   "vec2<bool>", AstFor("cast_v2uint_10_20"),
+                   "<=", AstFor("v2int_40_30")},
+        // Second arg unsigned
+        BinaryData{"v2bool", "v2int_30_40", "OpSLessThanEqual", "v2uint_20_10",
+                   "vec2<bool>", AstFor("v2int_30_40"),
+                   "<=", AstFor("cast_v2uint_20_10")}));
+
+using SpvFUnordTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvFUnordTest, FUnordEqual_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 != 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordEqual %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = "
+                "!((vec2<f32>(50.0, 60.0) != vec2<f32>(60.0, 50.0)));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordNotEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 == 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordNotEqual %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = "
+                "!((vec2<f32>(50.0, 60.0) == vec2<f32>(60.0, 50.0)));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThan %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 >= 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThan %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = "
+                "!((vec2<f32>(50.0, 60.0) >= vec2<f32>(60.0, 50.0)));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThanEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 > 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThanEqual %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<bool> = "
+                        "!((vec2<f32>(50.0, 60.0) > vec2<f32>(60.0, 50.0)));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThan %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 <= 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThan %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = "
+                "!((vec2<f32>(50.0, 60.0) <= vec2<f32>(60.0, 50.0)));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThanEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = !((50.0 < 60.0));"));
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThanEqual %v2bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<bool> = !(("
+                        "vec2<f32>(50.0, 60.0) < vec2<f32>(60.0, 50.0)"
+                        "));"));
+}
+
+using SpvLogicalTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvLogicalTest, Select_BoolCond_BoolParams) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSelect %bool %true %true %false
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = select(false, true, true);"));
+}
+
+TEST_F(SpvLogicalTest, Select_BoolCond_IntScalarParams) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSelect %uint %true %uint_10 %uint_20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : u32 = select(20u, 10u, true);"));
+}
+
+TEST_F(SpvLogicalTest, Select_BoolCond_FloatScalarParams) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSelect %float %true %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : f32 = select(60.0, 50.0, true);"));
+}
+
+TEST_F(SpvLogicalTest, Select_BoolCond_VectorParams) {
+  // Prior to SPIR-V 1.4, the condition must be a vector of bools
+  // when the value operands are vectors.
+  // "Before version 1.4, results are only computed per component."
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSelect %v2uint %true %v2uint_10_20 %v2uint_20_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<u32> = select("
+                        "vec2<u32>(20u, 10u), "
+                        "vec2<u32>(10u, 20u), "
+                        "true);"));
+
+  // Fails validation prior to SPIR-V 1.4: If the value operands are vectors,
+  // then the condition must be a vector.
+  // "Expected vector sizes of Result Type and the condition to be equal:
+  // Select"
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvLogicalTest, Select_VecBoolCond_VectorParams) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpSelect %v2uint %v2bool_t_f %v2uint_10_20 %v2uint_20_10
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : vec2<u32> = select("
+                        "vec2<u32>(20u, 10u), "
+                        "vec2<u32>(10u, 20u), "
+                        "vec2<bool>(true, false));"));
+}
+
+TEST_F(SpvLogicalTest, Any) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpAny %bool %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = any(vec2<bool>(true, false));"));
+}
+
+TEST_F(SpvLogicalTest, All) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpAll %bool %v2bool_t_f
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = all(vec2<bool>(true, false));"));
+}
+
+TEST_F(SpvLogicalTest, IsNan_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpIsNan %bool %float_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = isNan(50.0);"));
+}
+
+TEST_F(SpvLogicalTest, IsNan_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpIsNan %v2bool %v2float_50_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = isNan(vec2<f32>(50.0, 60.0));"));
+}
+
+TEST_F(SpvLogicalTest, IsInf_Scalar) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpIsInf %bool %float_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_1 : bool = isInf(50.0);"));
+}
+
+TEST_F(SpvLogicalTest, IsInf_Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpIsInf %v2bool %v2float_50_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("let x_1 : vec2<bool> = isInf(vec2<f32>(50.0, 60.0));"));
+}
+
+// TODO(dneto): Kernel-guarded instructions.
+// TODO(dneto): OpSelect over more general types, as in SPIR-V 1.4
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_memory_test.cc b/src/tint/reader/spirv/function_memory_test.cc
new file mode 100644
index 0000000..605e851
--- /dev/null
+++ b/src/tint/reader/spirv/function_memory_test.cc
@@ -0,0 +1,1314 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+using SpvParserMemoryTest = SpvParserTest;
+
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %100 "main"
+    OpExecutionMode %100 OriginUpperLeft
+)";
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_StoreBoolConst) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeBool
+     %true = OpConstantTrue %ty
+     %false = OpConstantFalse %ty
+     %null = OpConstantNull %ty
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     OpStore %1 %true
+     OpStore %1 %false
+     OpStore %1 %null
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = true;
+x_1 = false;
+x_1 = false;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_StoreUintConst) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %val = OpConstant %ty 42
+     %null = OpConstantNull %ty
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     OpStore %1 %val
+     OpStore %1 %null
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42u;
+x_1 = 0u;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_StoreIntConst) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 1
+     %val = OpConstant %ty 42
+     %null = OpConstantNull %ty
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     OpStore %1 %val
+     OpStore %1 %null
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42;
+x_1 = 0;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_StoreFloatConst) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeFloat 32
+     %val = OpConstant %ty 42
+     %null = OpConstantNull %ty
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     OpStore %1 %val
+     OpStore %1 %null
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42.0;
+x_1 = 0.0;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_LoadBool) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeBool
+     %true = OpConstantTrue %ty
+     %false = OpConstantFalse %ty
+     %null = OpConstantNull %ty
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function %true
+     %2 = OpLoad %ty %1
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_2 : bool = x_1;"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_LoadScalar) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %ty_42 = OpConstant %ty 42
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function %ty_42
+     %2 = OpLoad %ty %1
+     %3 = OpLoad %ty %1
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_2 : u32 = x_1;
+let x_3 : u32 = x_1;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_UseLoadedScalarTwice) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %ty_42 = OpConstant %ty 42
+     %ptr_ty = OpTypePointer Function %ty
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function %ty_42
+     %2 = OpLoad %ty %1
+     OpStore %1 %2
+     OpStore %1 %2
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_2 : u32 = x_1;
+x_1 = x_2;
+x_1 = x_2;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_StoreToModuleScopeVar) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %val = OpConstant %ty 42
+     %ptr_ty = OpTypePointer Private %ty
+     %1 = OpVariable %ptr_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpStore %1 %val
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("x_1 = 42u;"));
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_CopyMemory_Scalar_Function_To_Private) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %val = OpConstant %ty 42
+     %ptr_fn_ty = OpTypePointer Function %ty
+     %ptr_priv_ty = OpTypePointer Private %ty
+     %2 = OpVariable %ptr_priv_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_fn_ty Function
+     OpCopyMemory %2 %1
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const auto* expected = "x_2 = x_1;";
+  EXPECT_THAT(got, HasSubstr(expected));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_NoOperands) {
+  auto err = test::AssembleFailure(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %ty = OpTypeInt 32 0
+     %val = OpConstant %ty 42
+     %ptr_ty = OpTypePointer Private %ty
+     %1 = OpVariable %ptr_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %2 = OpAccessChain %ptr_ty  ; Needs a base operand
+     OpStore %1 %val
+     OpReturn
+  )");
+  EXPECT_THAT(err,
+              Eq("16:5: Expected operand, found next instruction instead."));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_BaseIsNotPointer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %10 = OpTypeInt 32 0
+     %val = OpConstant %10 42
+     %ptr_ty = OpTypePointer Private %10
+     %20 = OpVariable %10 Private ; bad pointer type
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpAccessChain %ptr_ty %20
+     OpStore %1 %val
+     OpReturn
+  )"));
+  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_THAT(p->error(), Eq("variable with ID 20 has non-pointer type 10"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorSwizzle) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_42 = OpConstant %uint 42
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_2
+     OpStore %2 %uint_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar.z = 42u;"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorConstOutOfBounds) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %42 = OpConstant %uint 42
+     %uint_99 = OpConstant %uint 99
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %42
+     OpStore %2 %uint_99
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(), Eq("Access chain %2 index %42 value 42 is out of "
+                             "bounds for vector of 4 elements"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorNonConstIndex) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpName %13 "a_dynamic_index"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_42 = OpConstant %uint 42
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %10 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %11 = OpLoad %store_ty %10
+     %12 = OpCompositeExtract %uint %11 2
+     %13 = OpCopyObject %uint %12
+     %2 = OpAccessChain %elem_ty %1 %13
+     OpStore %2 %uint_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar[a_dynamic_index] = 42u;"));
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_VectorComponent_MultiUse) {
+  // WGSL does not support pointer-to-vector-component, so test that we sink
+  // these pointers into the point of use.
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_42 = OpConstant %uint 42
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %ptr = OpAccessChain %elem_ty %1 %uint_2
+     %load = OpLoad %uint %ptr
+     %result = OpIAdd %uint %load %uint_2
+     OpStore %ptr %result
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto wgsl = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
+  EXPECT_THAT(wgsl, HasSubstr(" = myvar.z;"));
+  EXPECT_THAT(wgsl, HasSubstr("myvar.z = "));
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_VectorComponent_MultiUse_NonConstIndex) {
+  // WGSL does not support pointer-to-vector-component, so test that we sink
+  // these pointers into the point of use.
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_42 = OpConstant %uint 42
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %2 = OpVariable %elem_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %idx = OpLoad %uint %2
+     %ptr = OpAccessChain %elem_ty %1 %idx
+     %load = OpLoad %uint %ptr
+     %result = OpIAdd %uint %load %uint_2
+     OpStore %ptr %result
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto wgsl = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
+  EXPECT_THAT(wgsl, HasSubstr(" = myvar[x_12];"));
+  EXPECT_THAT(wgsl, HasSubstr("myvar[x_12] = "));
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_VectorComponent_SinkThroughChain) {
+  // Test that we can sink a pointer-to-vector-component through a chain of
+  // instructions that propagate it.
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+     %store_ty = OpTypeVector %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_42 = OpConstant %uint 42
+     %elem_ty = OpTypePointer Private %uint
+     %var_ty = OpTypePointer Private %store_ty
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %ptr = OpAccessChain %elem_ty %1 %uint_2
+     %ptr2 = OpCopyObject %elem_ty %ptr
+     %ptr3 = OpInBoundsAccessChain %elem_ty %ptr2
+     %ptr4 = OpAccessChain %elem_ty %ptr3
+     %load = OpLoad %uint %ptr3
+     %result = OpIAdd %uint %load %uint_2
+     OpStore %ptr4 %result
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  auto wgsl = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(wgsl, Not(HasSubstr("&")));
+  EXPECT_THAT(wgsl, HasSubstr(" = myvar.z;"));
+  EXPECT_THAT(wgsl, HasSubstr("myvar.z = "));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Matrix) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v4float = OpTypeVector %float 4
+     %m3v4float = OpTypeMatrix %v4float 3
+     %elem_ty = OpTypePointer Private %v4float
+     %var_ty = OpTypePointer Private %m3v4float
+     %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %float_42 = OpConstant %float 42
+     %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_2
+     OpStore %2 %v4float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Array) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v4float = OpTypeVector %float 4
+     %m3v4float = OpTypeMatrix %v4float 3
+     %elem_ty = OpTypePointer Private %v4float
+     %var_ty = OpTypePointer Private %m3v4float
+     %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %float_42 = OpConstant %float 42
+     %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_2
+     OpStore %2 %v4float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpMemberName %strct 1 "age"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %float_42 = OpConstant %float 42
+     %strct = OpTypeStruct %float %float
+     %elem_ty = OpTypePointer Private %float
+     %var_ty = OpTypePointer Private %strct
+     %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_1
+     OpStore %2 %float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar.age = 42.0;"));
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_Struct_DifferOnlyMemberName) {
+  // The spirv-opt internal representation will map both structs to the
+  // same canonicalized type, because it doesn't care about member names.
+  // But we care about member names when producing a member-access expression.
+  // crbug.com/tint/213
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpName %10 "myvar2"
+     OpMemberName %strct 1 "age"
+     OpMemberName %strct2 1 "ancientness"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %float_42 = OpConstant %float 42
+     %float_420 = OpConstant %float 420
+     %strct = OpTypeStruct %float %float
+     %strct2 = OpTypeStruct %float %float
+     %elem_ty = OpTypePointer Private %float
+     %var_ty = OpTypePointer Private %strct
+     %var2_ty = OpTypePointer Private %strct2
+     %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+
+     %1 = OpVariable %var_ty Private
+     %10 = OpVariable %var2_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_1
+     OpStore %2 %float_42
+     %20 = OpAccessChain %elem_ty %10 %uint_1
+     OpStore %20 %float_420
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(myvar.age = 42.0;
+myvar2.ancientness = 420.0;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_StructNonConstIndex) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpMemberName %55 1 "age"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %float_42 = OpConstant %float 42
+     %55 = OpTypeStruct %float %float
+     %elem_ty = OpTypePointer Private %float
+     %var_ty = OpTypePointer Private %55
+     %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+     %uint_ptr = OpTypePointer Private %uint
+     %uintvar = OpVariable %uint_ptr Private
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %10 = OpLoad %uint %uintvar
+     %2 = OpAccessChain %elem_ty %1 %10
+     OpStore %2 %float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(), Eq("Access chain %2 index %10 is a non-constant "
+                             "index into a structure %55"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_StructConstOutOfBounds) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpMemberName %55 1 "age"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %float_42 = OpConstant %float 42
+     %55 = OpTypeStruct %float %float
+     %elem_ty = OpTypePointer Private %float
+     %var_ty = OpTypePointer Private %55
+     %uint = OpTypeInt 32 0
+     %uint_99 = OpConstant %uint 99
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_99
+     OpStore %2 %float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(), Eq("Access chain %2 index value 99 is out of bounds "
+                             "for structure %55 having 2 members"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct_RuntimeArray) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     OpMemberName %strct 1 "age"
+
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
+     OpDecorate %strct BufferBlock
+     OpMemberDecorate %strct 0 Offset 0
+     OpMemberDecorate %strct 1 Offset 4
+     OpDecorate %rtarr ArrayStride 4
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %float_42 = OpConstant %float 42
+     %rtarr = OpTypeRuntimeArray %float
+     %strct = OpTypeStruct %float %rtarr
+     %elem_ty = OpTypePointer Uniform %float
+     %var_ty = OpTypePointer Uniform %strct
+     %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+
+     %1 = OpVariable %var_ty Uniform
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_1 %uint_2
+     OpStore %2 %float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar.age[2u] = 42.0;"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Compound_Matrix_Vector) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v4float = OpTypeVector %float 4
+     %m3v4float = OpTypeMatrix %v4float 3
+     %elem_ty = OpTypePointer Private %float
+     %var_ty = OpTypePointer Private %m3v4float
+     %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+     %float_42 = OpConstant %float 42
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_2 %uint_3
+     OpStore %2 %float_42
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody());
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar[2u].w = 42.0;"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_InvalidPointeeType) {
+  const std::string assembly = Preamble() + R"(
+     OpName %1 "myvar"
+     %55 = OpTypeVoid
+     %voidfn = OpTypeFunction %55
+     %float = OpTypeFloat 32
+     %60 = OpTypePointer Private %55
+     %var_ty = OpTypePointer Private %60
+     %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+
+     %1 = OpVariable %var_ty Private
+     %100 = OpFunction %55 None %voidfn
+     %entry = OpLabel
+     %2 = OpAccessChain %60 %1 %uint_2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Access chain with unknown or invalid pointee type "
+                        "%60: %60 = OpTypePointer Private %55"));
+}
+
+TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_DereferenceBase) {
+  // The base operand to OpAccessChain may have to be dereferenced first.
+  // crbug.com/tint/737
+  const std::string assembly = Preamble() + R"(
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %uint = OpTypeInt 32 0
+     %v2uint = OpTypeVector %uint 2
+     %elem_ty = OpTypePointer Private %uint
+     %vec_ty = OpTypePointer Private %v2uint
+
+     %ptrfn = OpTypeFunction %void %vec_ty
+
+     %uint_0 = OpConstant %uint 0
+
+     ; The shortest way to make a pointer example is as a function parameter.
+     %200 = OpFunction %void None %ptrfn
+     %1 = OpFunctionParameter %vec_ty
+     %entry = OpLabel
+     %2 = OpAccessChain %elem_ty %1 %uint_0
+     %3 = OpLoad %uint %2
+     OpReturn
+     OpFunctionEnd
+
+     %100 = OpFunction %void None %voidfn
+     %main_entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(fn x_200(x_1 : ptr<private, vec2<u32>>) {
+  let x_3 : u32 = (*(x_1)).x;
+  return;
+}
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main() {
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_InferFunctionStorageClass) {
+  // An access chain can have no indices. When the base is a Function variable,
+  // the reference type has no explicit storage class in the AST representation.
+  // But the pointer type for the let declaration must have an explicit
+  // 'function' storage class. From crbug.com/tint/807
+  const std::string assembly = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%uint = OpTypeInt 32 0
+%ptr_ty = OpTypePointer Function %uint
+
+  %void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     %2 = OpAccessChain %ptr_ty %1
+          OpReturn
+          OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly;
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(fn main_1() {
+  var x_1 : u32;
+  let x_2 : ptr<function, u32> = &(x_1);
+  return;
+}
+
+@stage(fragment)
+fn main() {
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+std::string OldStorageBufferPreamble() {
+  return Preamble() + R"(
+     OpName %myvar "myvar"
+
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
+
+     OpDecorate %struct BufferBlock
+     OpMemberDecorate %struct 0 Offset 0
+     OpMemberDecorate %struct 1 Offset 4
+     OpDecorate %arr ArrayStride 4
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+
+     %arr = OpTypeRuntimeArray %uint
+     %struct = OpTypeStruct %uint %arr
+     %ptr_struct = OpTypePointer Uniform %struct
+     %ptr_uint = OpTypePointer Uniform %uint
+
+     %myvar = OpVariable %ptr_struct Uniform
+  )";
+}
+
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_TypesAndVarDeclarations) {
+  // Enusure we get the right module-scope declaration.  This tests translation
+  // of the structure type, arrays of the structure, pointers to them, and
+  // OpVariable of these.
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  ; The preamble declared %100 to be an entry point, so supply it.
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(type RTArr = @stride(4) array<u32>;
+
+struct S {
+  field0 : u32;
+  field1 : RTArr;
+}
+
+@group(0) @binding(0) var<storage, read_write> myvar : S;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded) {
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  ; the scalar element
+  %1 = OpAccessChain %ptr_uint %myvar %uint_0
+  OpStore %1 %uint_0
+
+  ; element in the runtime array
+  %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+  OpStore %2 %uint_0
+
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(R"(myvar.field0 = 0u;
+myvar.field1[1u] = 0u;
+)"));
+}
+
+TEST_F(SpvParserMemoryTest,
+       RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain) {
+  // Like the previous test, but using OpInBoundsAccessChain.
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  ; the scalar element
+  %1 = OpInBoundsAccessChain %ptr_uint %myvar %uint_0
+  OpStore %1 %uint_0
+
+  ; element in the runtime array
+  %2 = OpInBoundsAccessChain %ptr_uint %myvar %uint_1 %uint_1
+  OpStore %2 %uint_0
+
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(R"(myvar.field0 = 0u;
+myvar.field1[1u] = 0u;
+)")) << got
+     << p->error();
+}
+
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_Cascaded) {
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  %ptr_rtarr = OpTypePointer Uniform %arr
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  ; get the runtime array
+  %1 = OpAccessChain %ptr_rtarr %myvar %uint_1
+  ; now an element in it
+  %2 = OpAccessChain %ptr_uint %1 %uint_1
+  OpStore %2 %uint_0
+
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("myvar.field1[1u] = 0u;"))
+      << p->error();
+}
+
+TEST_F(SpvParserMemoryTest,
+       RemapStorageBuffer_ThroughCopyObject_WithoutHoisting) {
+  // Generates a const declaration directly.
+  // We have to do a bunch of storage class tracking for locally
+  // defined values in order to get the right pointer-to-storage-buffer
+  // value type for the const declration.
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+
+  %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+  %2 = OpCopyObject %ptr_uint %1
+  OpStore %2 %uint_0
+
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_2 : ptr<storage, u32> = &(myvar.field1[1u]);
+*(x_2) = 0u;
+)")) << p->error();
+
+  p->SkipDumpingPending(
+      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
+}
+
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughCopyObject_WithHoisting) {
+  // TODO(dneto): Hoisting non-storable values (pointers) is not yet supported.
+  // It's debatable whether this test should run at all.
+  // crbug.com/tint/98
+
+  // Like the previous test, but the declaration for the copy-object
+  // has its declaration hoisted.
+  const auto assembly = OldStorageBufferPreamble() + R"(
+  %bool = OpTypeBool
+  %cond = OpConstantTrue %bool
+
+  %100 = OpFunction %void None %voidfn
+
+  %entry = OpLabel
+  OpSelectionMerge %99 None
+  OpBranchConditional %cond %20 %30
+
+  %20 = OpLabel
+  %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+  ; this definintion dominates the use in %99
+  %2 = OpCopyObject %ptr_uint %1
+  OpBranch %99
+
+  %30 = OpLabel
+  OpReturn
+
+  %99 = OpLabel
+  OpStore %2 %uint_0
+  OpReturn
+
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_EQ(test::ToString(p->program(), ast_body),
+            R"(var x_2 : ptr<storage, u32>;
+if (true) {
+  x_2 = &(myvar.field1[1u]);
+} else {
+  return;
+}
+x_2 = 0u;
+return;
+)") << p->error();
+  p->SkipDumpingPending("crbug.com/tint/98");
+}
+
+TEST_F(SpvParserMemoryTest, DISABLED_RemapStorageBuffer_ThroughFunctionCall) {
+  // WGSL does not support pointer-to-storage-buffer as function parameter
+}
+TEST_F(SpvParserMemoryTest,
+       DISABLED_RemapStorageBuffer_ThroughFunctionParameter) {
+  // WGSL does not support pointer-to-storage-buffer as function parameter
+}
+
+std::string RuntimeArrayPreamble() {
+  return R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+
+     OpName %myvar "myvar"
+     OpMemberName %struct 0 "first"
+     OpMemberName %struct 1 "rtarr"
+
+     OpDecorate %struct Block
+     OpMemberDecorate %struct 0 Offset 0
+     OpMemberDecorate %struct 1 Offset 4
+     OpDecorate %arr ArrayStride 4
+
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+
+     %arr = OpTypeRuntimeArray %uint
+     %struct = OpTypeStruct %uint %arr
+     %ptr_struct = OpTypePointer StorageBuffer %struct
+     %ptr_uint = OpTypePointer StorageBuffer %uint
+
+     %myvar = OpVariable %ptr_struct StorageBuffer
+  )";
+}
+
+TEST_F(SpvParserMemoryTest, ArrayLength_FromVar) {
+  const auto assembly = RuntimeArrayPreamble() + R"(
+
+  %100 = OpFunction %void None %voidfn
+
+  %entry = OpLabel
+  %1 = OpArrayLength %uint %myvar 1
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str,
+              HasSubstr("let x_1 : u32 = arrayLength(&(myvar.rtarr));"))
+      << body_str;
+}
+
+TEST_F(SpvParserMemoryTest, ArrayLength_FromCopyObject) {
+  const auto assembly = RuntimeArrayPreamble() + R"(
+
+  %100 = OpFunction %void None %voidfn
+
+  %entry = OpLabel
+  %2 = OpCopyObject %ptr_struct %myvar
+  %1 = OpArrayLength %uint %2 1
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str, HasSubstr(R"(let x_2 : ptr<storage, S> = &(myvar);
+let x_1 : u32 = arrayLength(&((*(x_2)).rtarr));
+)")) << body_str;
+
+  p->SkipDumpingPending(
+      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
+}
+
+TEST_F(SpvParserMemoryTest, ArrayLength_FromAccessChain) {
+  const auto assembly = RuntimeArrayPreamble() + R"(
+
+  %100 = OpFunction %void None %voidfn
+
+  %entry = OpLabel
+  %2 = OpAccessChain %ptr_struct %myvar ; no indices
+  %1 = OpArrayLength %uint %2 1
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto body_str = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(body_str,
+              HasSubstr("let x_1 : u32 = arrayLength(&(myvar.rtarr));"))
+      << body_str;
+}
+
+std::string InvalidPointerPreamble() {
+  return R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%uint = OpTypeInt 32 0
+%ptr_ty = OpTypePointer Function %uint
+
+  %void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+)";
+}
+
+TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_ModuleScope_IsError) {
+  const std::string assembly = InvalidPointerPreamble() + R"(
+ %ptr = OpUndef %ptr_ty
+
+  %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+     %1 = OpCopyObject %ptr_ty %ptr
+     %2 = OpAccessChain %ptr_ty %ptr
+     %3 = OpInBoundsAccessChain %ptr_ty %ptr
+; now show the invalid pointer propagates
+     %10 = OpCopyObject %ptr_ty %1
+     %20 = OpAccessChain %ptr_ty %2
+     %30 = OpInBoundsAccessChain %ptr_ty %3
+          OpReturn
+          OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_EQ(p->error(), "undef pointer is not valid: %9 = OpUndef %6");
+}
+
+TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_FunctionScope_IsError) {
+  const std::string assembly = InvalidPointerPreamble() + R"(
+
+  %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+   %ptr = OpUndef %ptr_ty
+          OpReturn
+          OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_EQ(p->error(), "undef pointer is not valid: %7 = OpUndef %3");
+}
+
+TEST_F(SpvParserMemoryTest, InvalidPointer_ConstantNull_IsError) {
+  // OpConstantNull on logical pointer requires variable-pointers, which
+  // is not (yet) supported by WGSL features.
+  const std::string assembly = InvalidPointerPreamble() + R"(
+ %ptr = OpConstantNull %ptr_ty
+
+  %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+     %1 = OpCopyObject %ptr_ty %ptr
+     %2 = OpAccessChain %ptr_ty %ptr
+     %3 = OpInBoundsAccessChain %ptr_ty %ptr
+; now show the invalid pointer propagates
+     %10 = OpCopyObject %ptr_ty %1
+     %20 = OpAccessChain %ptr_ty %2
+     %30 = OpInBoundsAccessChain %ptr_ty %3
+          OpReturn
+          OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_EQ(p->error(), "null pointer is not valid: %9 = OpConstantNull %6");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_misc_test.cc b/src/tint/reader/spirv/function_misc_test.cc
new file mode 100644
index 0000000..582ff8d
--- /dev/null
+++ b/src/tint/reader/spirv/function_misc_test.cc
@@ -0,0 +1,348 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+std::string Preamble() {
+  return R"(
+   OpCapability Shader
+   OpMemoryModel Logical Simple
+   OpEntryPoint Fragment %100 "main"
+   OpExecutionMode %100 OriginUpperLeft
+)";
+}
+
+std::string CommonTypes() {
+  return R"(
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+
+  %bool = OpTypeBool
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+
+  %v2bool = OpTypeVector %bool 2
+  %v2uint = OpTypeVector %uint 2
+  %v2int = OpTypeVector %int 2
+  %v2float = OpTypeVector %float 2
+)";
+}
+
+using SpvParserTestMiscInstruction = SpvParserTest;
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_BeforeFunction_Scalar) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %1 = OpUndef %bool
+     %2 = OpUndef %uint
+     %3 = OpUndef %int
+     %4 = OpUndef %float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %11 = OpCopyObject %bool %1
+     %12 = OpCopyObject %uint %2
+     %13 = OpCopyObject %int %3
+     %14 = OpCopyObject %float %4
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_11 : bool = false;
+let x_12 : u32 = 0u;
+let x_13 : i32 = 0;
+let x_14 : f32 = 0.0;
+)"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_BeforeFunction_Vector) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %4 = OpUndef %v2bool
+     %1 = OpUndef %v2uint
+     %2 = OpUndef %v2int
+     %3 = OpUndef %v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %14 = OpCopyObject %v2bool %4
+     %11 = OpCopyObject %v2uint %1
+     %12 = OpCopyObject %v2int %2
+     %13 = OpCopyObject %v2float %3
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_14 : vec2<bool> = vec2<bool>();
+let x_11 : vec2<u32> = vec2<u32>();
+let x_12 : vec2<i32> = vec2<i32>();
+let x_13 : vec2<f32> = vec2<f32>();
+)"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Scalar) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpUndef %bool
+     %2 = OpUndef %uint
+     %3 = OpUndef %int
+     %4 = OpUndef %float
+
+     %11 = OpCopyObject %bool %1
+     %12 = OpCopyObject %uint %2
+     %13 = OpCopyObject %int %3
+     %14 = OpCopyObject %float %4
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_11 : bool = false;
+let x_12 : u32 = 0u;
+let x_13 : i32 = 0;
+let x_14 : f32 = 0.0;
+)"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Vector) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpUndef %v2uint
+     %2 = OpUndef %v2int
+     %3 = OpUndef %v2float
+
+     %11 = OpCopyObject %v2uint %1
+     %12 = OpCopyObject %v2int %2
+     %13 = OpCopyObject %v2float %3
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(let x_11 : vec2<u32> = vec2<u32>();
+let x_12 : vec2<i32> = vec2<i32>();
+let x_13 : vec2<f32> = vec2<f32>();
+)"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Matrix) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %mat = OpTypeMatrix %v2float 2
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpUndef %mat
+
+     %11 = OpCopyObject %mat %1
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_11 : mat2x2<f32> = mat2x2<f32>();"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Array) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %uint_2 = OpConstant %uint 2
+     %arr = OpTypeArray %uint %uint_2
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpUndef %arr
+
+     %11 = OpCopyObject %arr %1
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_11 : array<u32, 2u> = array<u32, 2u>();"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Struct) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %strct = OpTypeStruct %bool %uint %int %float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpUndef %strct
+
+     %11 = OpCopyObject %strct %1
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("let x_11 : S = S(false, 0u, 0, 0.0);"));
+}
+
+TEST_F(SpvParserTestMiscInstruction, OpNop) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpNop
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  EXPECT_EQ(test::ToString(p->program(), ast_body), "return;\n");
+}
+
+// Test swizzle generation.
+
+struct SwizzleCase {
+  uint32_t index;
+  std::string expected_expr;
+  std::string expected_error;
+};
+using SpvParserSwizzleTest =
+    SpvParserTestBase<::testing::TestWithParam<SwizzleCase>>;
+
+TEST_P(SpvParserSwizzleTest, Sample) {
+  // We need a function so we can get a FunctionEmitter.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+
+  auto* result = fe.Swizzle(GetParam().index);
+  if (GetParam().expected_error.empty()) {
+    Program program(p->program());
+    EXPECT_TRUE(fe.success());
+    ASSERT_NE(result, nullptr);
+    auto got = test::ToString(program, result);
+    EXPECT_EQ(got, GetParam().expected_expr);
+  } else {
+    EXPECT_EQ(result, nullptr);
+    EXPECT_FALSE(fe.success());
+    EXPECT_EQ(p->error(), GetParam().expected_error);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidIndex,
+    SpvParserSwizzleTest,
+    ::testing::ValuesIn(std::vector<SwizzleCase>{
+        {0, "x", ""},
+        {1, "y", ""},
+        {2, "z", ""},
+        {3, "w", ""},
+        {4, "", "vector component index is larger than 3: 4"},
+        {99999, "", "vector component index is larger than 3: 99999"}}));
+
+TEST_F(SpvParserTest, ValueFromBlockNotInBlockOrder) {
+  // crbug.com/tint/804
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %float_42 = OpConstant %float 42.0
+     %cond = OpUndef %bool
+
+     %100 = OpFunction %void None %voidfn
+     %10 = OpLabel
+     OpBranch %30
+
+     ; unreachable
+     %20 = OpLabel
+     %499 = OpFAdd %float %float_42 %float_42
+     %500 = OpFAdd %float %499 %float_42
+     OpBranch %25
+
+     %25 = OpLabel
+     OpBranch %80
+
+
+     %30 = OpLabel
+     OpLoopMerge %90 %80 None
+     OpBranchConditional %cond %90 %40
+
+     %40 = OpLabel
+     OpBranch %90
+
+     %80 = OpLabel ; unreachable continue target
+                ; but "dominated" by %20 and %25
+     %81 = OpFMul %float %500 %float_42 ; %500 is defined in %20
+     OpBranch %30 ; backedge
+
+     %90 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr("let x_81 : f32 = (0.0 * 42.0);"));
+}
+
+// TODO(dneto): OpSizeof : requires Kernel (OpenCL)
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/function_var_test.cc b/src/tint/reader/spirv/function_var_test.cc
new file mode 100644
index 0000000..ddec083
--- /dev/null
+++ b/src/tint/reader/spirv/function_var_test.cc
@@ -0,0 +1,1675 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+/// @returns a SPIR-V assembly segment which assigns debug names
+/// to particular IDs.
+std::string Names(std::vector<std::string> ids) {
+  std::ostringstream outs;
+  for (auto& id : ids) {
+    outs << "    OpName %" << id << " \"" << id << "\"\n";
+  }
+  return outs.str();
+}
+
+std::string CommonTypes() {
+  return
+      R"(
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+
+    %ptr_bool = OpTypePointer Function %bool
+    %ptr_float = OpTypePointer Function %float
+    %ptr_uint = OpTypePointer Function %uint
+    %ptr_int = OpTypePointer Function %int
+
+    %true = OpConstantTrue %bool
+    %false = OpConstantFalse %bool
+    %float_0 = OpConstant %float 0.0
+    %float_1p5 = OpConstant %float 1.5
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %int_m1 = OpConstant %int -1
+    %int_0 = OpConstant %int 0
+    %int_1 = OpConstant %int 1
+    %int_3 = OpConstant %int 3
+    %uint_2 = OpConstant %uint 2
+    %uint_3 = OpConstant %uint 3
+    %uint_4 = OpConstant %uint 4
+    %uint_5 = OpConstant %uint 5
+
+    %v2int = OpTypeVector %int 2
+    %v2float = OpTypeVector %float 2
+    %m3v2float = OpTypeMatrix %v2float 3
+
+    %v2int_null = OpConstantNull %v2int
+
+    %arr2uint = OpTypeArray %uint %uint_2
+    %strct = OpTypeStruct %uint %float %arr2uint
+  )";
+}
+
+// Returns the SPIR-V assembly for capabilities, the memory model,
+// a vertex shader entry point declaration, and name declarations
+// for specified IDs.
+std::string Caps(std::vector<std::string> ids = {}) {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %100 "main"
+    OpExecutionMode %100 OriginUpperLeft
+)" + Names(ids);
+}
+
+// Returns the SPIR-V assembly for a vertex shader, optionally
+// with OpName decorations for certain SPIR-V IDs
+std::string PreambleNames(std::vector<std::string> ids) {
+  return Caps(ids) + CommonTypes();
+}
+
+std::string Preamble() {
+  return PreambleNames({});
+}
+
+using SpvParserFunctionVarTest = SpvParserTest;
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_AnonymousVars) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpVariable %ptr_uint Function
+     %2 = OpVariable %ptr_uint Function
+     %3 = OpVariable %ptr_uint Function
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(var x_1 : u32;
+var x_2 : u32;
+var x_3 : u32;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_NamedVars) {
+  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c"}) + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %a = OpVariable %ptr_uint Function
+     %b = OpVariable %ptr_uint Function
+     %c = OpVariable %ptr_uint Function
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(var a : u32;
+var b : u32;
+var c : u32;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MixedTypes) {
+  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c"}) + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %a = OpVariable %ptr_uint Function
+     %b = OpVariable %ptr_int Function
+     %c = OpVariable %ptr_float Function
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(var a : u32;
+var b : i32;
+var c : f32;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ScalarInitializers) {
+  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c", "d", "e"}) + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %a = OpVariable %ptr_bool Function %true
+     %b = OpVariable %ptr_bool Function %false
+     %c = OpVariable %ptr_int Function %int_m1
+     %d = OpVariable %ptr_uint Function %uint_1
+     %e = OpVariable %ptr_float Function %float_1p5
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(var a : bool = true;
+var b : bool = false;
+var c : i32 = -1;
+var d : u32 = 1u;
+var e : f32 = 1.5;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ScalarNullInitializers) {
+  auto p = parser(test::Assemble(PreambleNames({"a", "b", "c", "d"}) + R"(
+     %null_bool = OpConstantNull %bool
+     %null_int = OpConstantNull %int
+     %null_uint = OpConstantNull %uint
+     %null_float = OpConstantNull %float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %a = OpVariable %ptr_bool Function %null_bool
+     %b = OpVariable %ptr_int Function %null_int
+     %c = OpVariable %ptr_uint Function %null_uint
+     %d = OpVariable %ptr_float Function %null_float
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr(R"(var a : bool = false;
+var b : i32 = 0;
+var c : u32 = 0u;
+var d : f32 = 0.0;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_VectorInitializer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %v2float
+     %two = OpConstant %float 2.0
+     %const = OpConstantComposite %v2float %float_1p5 %two
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MatrixInitializer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+     %two = OpConstant %float 2.0
+     %three = OpConstant %float 3.0
+     %four = OpConstant %float 4.0
+     %v0 = OpConstantComposite %v2float %float_1p5 %two
+     %v1 = OpConstantComposite %v2float %two %three
+     %v2 = OpConstantComposite %v2float %three %four
+     %const = OpConstantComposite %m3v2float %v0 %v1 %v2
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : mat3x2<f32> = mat3x2<f32>("
+                        "vec2<f32>(1.5, 2.0), "
+                        "vec2<f32>(2.0, 3.0), "
+                        "vec2<f32>(3.0, 4.0));"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %arr2uint
+     %two = OpConstant %uint 2
+     %const = OpConstantComposite %arr2uint %uint_1 %two
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(
+      test::ToString(p->program(), ast_body),
+      HasSubstr("var x_200 : array<u32, 2u> = array<u32, 2u>(1u, 2u);"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer_Alias) {
+  auto p = parser(test::Assemble(R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpDecorate %arr2uint ArrayStride 16
+)" + CommonTypes() + R"(
+     %ptr = OpTypePointer Function %arr2uint
+     %two = OpConstant %uint 2
+     %const = OpConstantComposite %arr2uint %uint_1 %two
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  const char* expect = "var x_200 : Arr = Arr(1u, 2u);\n";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer_Null) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %arr2uint
+     %two = OpConstant %uint 2
+     %const = OpConstantNull %arr2uint
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : array<u32, 2u> = array<u32, 2u>();"));
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitFunctionVariables_ArrayInitializer_Alias_Null) {
+  auto p = parser(test::Assemble(R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpDecorate %arr2uint ArrayStride 16
+)" + CommonTypes() + R"(
+     %ptr = OpTypePointer Function %arr2uint
+     %two = OpConstant %uint 2
+     %const = OpConstantNull %arr2uint
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : Arr = @stride(16) array<u32, 2u>();"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_StructInitializer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %strct
+     %two = OpConstant %uint 2
+     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
+     %const = OpConstantComposite %strct %uint_1 %float_1p5 %arrconst
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"));
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_StructInitializer_Null) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+     %ptr = OpTypePointer Function %strct
+     %two = OpConstant %uint 2
+     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
+     %const = OpConstantNull %strct
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %200 = OpVariable %ptr Function %const
+     OpReturn
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  EXPECT_THAT(test::ToString(p->program(), ast_body),
+              HasSubstr("var x_200 : S = S(0u, 0.0, array<u32, 2u>());"));
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitFunctionVariables_Decorate_RelaxedPrecision) {
+  // RelaxedPrecisionis dropped
+  const auto assembly = Caps({"myvar"}) + R"(
+     OpDecorate %myvar RelaxedPrecision
+
+     %float = OpTypeFloat 32
+     %ptr = OpTypePointer Function %float
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %myvar = OpVariable %ptr Function
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_EQ(got, "var myvar : f32;\n") << got;
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitFunctionVariables_MemberDecorate_RelaxedPrecision) {
+  // RelaxedPrecisionis dropped
+  const auto assembly = Caps({"myvar", "strct"}) + R"(
+     OpMemberDecorate %strct 0 RelaxedPrecision
+
+     %float = OpTypeFloat 32
+     %strct = OpTypeStruct %float
+     %ptr = OpTypePointer Function %strct
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %myvar = OpVariable %ptr Function
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error() << std::endl;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_EQ(got, "var myvar : strct;\n") << got;
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitFunctionVariables_StructDifferOnlyInMemberName) {
+  auto p = parser(test::Assemble(R"(
+      OpCapability Shader
+      OpMemoryModel Logical Simple
+      OpEntryPoint Fragment %100 "main"
+      OpExecutionMode %100 OriginUpperLeft
+      OpName %_struct_5 "S"
+      OpName %_struct_6 "S"
+      OpMemberName %_struct_5 0 "algo"
+      OpMemberName %_struct_6 0 "rithm"
+
+      %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %uint = OpTypeInt 32 0
+
+      %_struct_5 = OpTypeStruct %uint
+      %_struct_6 = OpTypeStruct %uint
+      %_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+      %_ptr_Function__struct_6 = OpTypePointer Function %_struct_6
+      %100 = OpFunction %void None %voidfn
+      %39 = OpLabel
+      %40 = OpVariable %_ptr_Function__struct_5 Function
+      %41 = OpVariable %_ptr_Function__struct_6 Function
+      OpReturn
+      OpFunctionEnd)"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitFunctionVariables());
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_THAT(got, HasSubstr(R"(var x_40 : S;
+var x_41 : S_1;
+)"));
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct) {
+  auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %25 = OpVariable %ptr_uint Function
+     %2 = OpIAdd %uint %uint_1 %uint_1
+     OpStore %25 %uint_1 ; Do initial store to mark source location
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %25 %2 ; defer emission of the addition until here.
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect =
+      R"(var x_25 : u32;
+x_25 = 1u;
+x_25 = (1u + 1u);
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_CombinatorialValue_Immediate_UsedTwice) {
+  auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %25 = OpVariable %ptr_uint Function
+     %2 = OpIAdd %uint %uint_1 %uint_1
+     OpStore %25 %uint_1 ; Do initial store to mark source location
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %25 %2
+     OpStore %25 %2
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var x_25 : u32;
+let x_2 : u32 = (1u + 1u);
+x_25 = 1u;
+x_25 = x_2;
+x_25 = x_2;
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct) {
+  // Translation should not sink expensive operations into or out of control
+  // flow. As a simple heuristic, don't move *any* combinatorial operation
+  // across any control flow.
+  auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     %25 = OpVariable %ptr_uint Function
+     %2 = OpIAdd %uint %uint_1 %uint_1
+     OpStore %25 %uint_1 ; Do initial store to mark source location
+     OpBranch %20
+
+     %20 = OpLabel  ; Introduce a new construct
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %25 %2  ; store combinatorial value %2, inside the loop
+     OpBranch %20
+
+     %99 = OpLabel ; merge block
+     OpStore %25 %uint_2
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var x_25 : u32;
+let x_2 : u32 = (1u + 1u);
+x_25 = 1u;
+loop {
+
+  continuing {
+    x_25 = x_2;
+  }
+}
+x_25 = 2u;
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserFunctionVarTest,
+    EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses) {
+  // Compensate for the difference between dominance and scoping.
+  // Exercise hoisting of the constant definition to before its natural
+  // location.
+  //
+  // The definition of %2 should be hoisted
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %3 = OpLabel
+     OpStore %1 %uint_0
+     OpBranch %5
+
+     %5 = OpLabel
+     OpStore %1 %uint_1
+     OpLoopMerge  %99 %80 None
+     OpBranchConditional %false %99 %20
+
+     %20 = OpLabel
+     OpStore %1 %uint_3
+     OpSelectionMerge %50 None
+     OpBranchConditional %true %30 %40
+
+     %30 = OpLabel
+     ; This combinatorial definition in nested control flow dominates
+     ; the use in the merge block in %50
+     %2 = OpIAdd %uint %uint_1 %uint_1
+     OpBranch %50
+
+     %40 = OpLabel
+     OpReturn
+
+     %50 = OpLabel ; merge block for if-selection
+     OpStore %1 %2
+     OpBranch %80
+
+     %80 = OpLabel ; merge block
+     OpStore %1 %uint_4
+     OpBranchConditional %false %99 %5 ; loop backedge
+
+     %99 = OpLabel
+     OpStore %1 %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(x_1 = 0u;
+loop {
+  var x_2 : u32;
+  x_1 = 1u;
+  if (false) {
+    break;
+  }
+  x_1 = 3u;
+  if (true) {
+    x_2 = (1u + 1u);
+  } else {
+    return;
+  }
+  x_1 = x_2;
+
+  continuing {
+    x_1 = 4u;
+    if (false) {
+      break;
+    }
+  }
+}
+x_1 = 5u;
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserFunctionVarTest,
+    EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction) {
+  // This is a hoisting case, where the definition is in the first block
+  // of an if selection construct. In this case the definition should count
+  // as being in the parent (enclosing) construct.
+  //
+  // The definition of %1 is in an IfSelection construct and also the enclosing
+  // Function construct, both of which start at block %10. For the purpose of
+  // determining the construct containing %10, go to the parent construct of
+  // the IfSelection.
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %200 = OpVariable %pty Private
+     %cond = OpConstantTrue %bool
+
+     %100 = OpFunction %void None %voidfn
+
+     ; in IfSelection construct, nested in Function construct
+     %10 = OpLabel
+     %1 = OpCopyObject %uint %uint_1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel  ; in IfSelection construct
+     OpBranch %99
+
+     %99 = OpLabel
+     %3 = OpCopyObject %uint %1; in Function construct
+     OpStore %200 %3
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  // We don't hoist x_1 into its own mutable variable. It is emitted as
+  // a const definition.
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(let x_1 : u32 = 1u;
+if (true) {
+}
+let x_3 : u32 = x_1;
+x_200 = x_3;
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf) {
+  // This is like the previous case, but the IfSelection is nested inside
+  // another IfSelection.
+  // This tests that the hoisting algorithm goes to only one parent of
+  // the definining if-selection block, and doesn't jump all the way out
+  // to the Function construct that encloses everything.
+  //
+  // We should not hoist %1 because its definition should count as being
+  // in the outer IfSelection, not the inner IfSelection.
+  auto assembly = Preamble() + R"(
+
+     %pty = OpTypePointer Private %uint
+     %200 = OpVariable %pty Private
+     %cond = OpConstantTrue %bool
+
+     %100 = OpFunction %void None %voidfn
+
+     ; outer IfSelection
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     ; inner IfSelection
+     %20 = OpLabel
+     %1 = OpCopyObject %uint %uint_1
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel ; last block of inner IfSelection
+     OpBranch %89
+
+     ; in outer IfSelection
+     %89 = OpLabel
+     %3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
+     OpStore %200 %3
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (true) {
+  let x_1 : u32 = 1u;
+  if (true) {
+  }
+  let x_3 : u32 = x_1;
+  x_200 = x_3;
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(
+    SpvParserFunctionVarTest,
+    EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf) {
+  // This is like the previous case, but the definition is in a SwitchSelection
+  // inside another IfSelection.
+  // Tests that definitions in the first block of a switch count as being
+  // in the parent of the switch construct.
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %200 = OpVariable %pty Private
+     %cond = OpConstantTrue %bool
+
+     %100 = OpFunction %void None %voidfn
+
+     ; outer IfSelection
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     ; inner SwitchSelection
+     %20 = OpLabel
+     %1 = OpCopyObject %uint %uint_1
+     OpSelectionMerge %89 None
+     OpSwitch %uint_1 %89 0 %30
+
+     %30 = OpLabel ; last block of inner SwitchSelection
+     OpBranch %89
+
+     ; in outer IfSelection
+     %89 = OpLabel
+     %3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
+     OpStore %200 %3
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(if (true) {
+  let x_1 : u32 = 1u;
+  switch(1u) {
+    case 0u: {
+    }
+    default: {
+    }
+  }
+  let x_3 : u32 = x_1;
+  x_200 = x_3;
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf) {
+  // In this test, both the defintion and the use are in the first block
+  // of an IfSelection.  No hoisting occurs because hoisting is triggered
+  // on whether the defining construct contains the last use, rather than
+  // whether the two constructs are the same.
+  //
+  // This example has two SSA IDs which are tempting to hoist but should not:
+  //   %1 is defined and used in the first block of an IfSelection.
+  //       Do not hoist it.
+  auto assembly = Preamble() + R"(
+     %cond = OpConstantTrue %bool
+
+     %100 = OpFunction %void None %voidfn
+
+     ; in IfSelection construct, nested in Function construct
+     %10 = OpLabel
+     %1 = OpCopyObject %uint %uint_1
+     %2 = OpCopyObject %uint %1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel  ; in IfSelection construct
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  // We don't hoist x_1 into its own mutable variable. It is emitted as
+  // a const definition.
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(let x_1 : u32 = 1u;
+let x_2 : u32 = x_1;
+if (true) {
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_SingleBlockLoopIndex) {
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %7 = OpVariable %boolpty Private
+     %8 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     ; Use an outer loop to show we put the new variable in the
+     ; smallest enclosing scope.
+     %10 = OpLabel
+     %101 = OpLoad %bool %7
+     %102 = OpLoad %bool %8
+     OpLoopMerge %99 %89 None
+     OpBranchConditional %101 %99 %20
+
+     %20 = OpLabel
+     %2 = OpPhi %uint %uint_0 %10 %4 %20  ; gets computed value
+     %3 = OpPhi %uint %uint_1 %10 %3 %20  ; gets itself
+     %4 = OpIAdd %uint %2 %uint_1
+     OpLoopMerge %79 %20 None
+     OpBranchConditional %102 %79 %20
+
+     %79 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var x_2_phi : u32;
+  var x_3_phi : u32;
+  let x_101 : bool = x_7;
+  let x_102 : bool = x_8;
+  x_2_phi = 0u;
+  x_3_phi = 1u;
+  if (x_101) {
+    break;
+  }
+  loop {
+    let x_2 : u32 = x_2_phi;
+    let x_3 : u32 = x_3_phi;
+    x_2_phi = (x_2 + 1u);
+    x_3_phi = x_3;
+    if (x_102) {
+      break;
+    }
+  }
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_MultiBlockLoopIndex) {
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %7 = OpVariable %boolpty Private
+     %8 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     OpBranch %10
+
+     ; Use an outer loop to show we put the new variable in the
+     ; smallest enclosing scope.
+     %10 = OpLabel
+     %101 = OpLoad %bool %7
+     %102 = OpLoad %bool %8
+     OpLoopMerge %99 %89 None
+     OpBranchConditional %101 %99 %20
+
+     %20 = OpLabel
+     %2 = OpPhi %uint %uint_0 %10 %4 %30  ; gets computed value
+     %3 = OpPhi %uint %uint_1 %10 %3 %30  ; gets itself
+     OpLoopMerge %79 %30 None
+     OpBranchConditional %102 %79 %30
+
+     %30 = OpLabel
+     %4 = OpIAdd %uint %2 %uint_1
+     OpBranch %20
+
+     %79 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel ; continue target for outer loop
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(loop {
+  var x_2_phi : u32;
+  var x_3_phi : u32;
+  let x_101 : bool = x_7;
+  let x_102 : bool = x_8;
+  x_2_phi = 0u;
+  x_3_phi = 1u;
+  if (x_101) {
+    break;
+  }
+  loop {
+    var x_4 : u32;
+    let x_2 : u32 = x_2_phi;
+    let x_3 : u32 = x_3_phi;
+    if (x_102) {
+      break;
+    }
+
+    continuing {
+      x_4 = (x_2 + 1u);
+      x_2_phi = x_4;
+      x_3_phi = x_3;
+    }
+  }
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_Phi_ValueFromLoopBodyAndContinuing) {
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %17 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %9 = OpLabel
+     %101 = OpLoad %bool %17
+     OpBranch %10
+
+     ; Use an outer loop to show we put the new variable in the
+     ; smallest enclosing scope.
+     %10 = OpLabel
+     OpLoopMerge %99 %89 None
+     OpBranch %20
+
+     %20 = OpLabel
+     %2 = OpPhi %uint %uint_0 %10 %4 %30  ; gets computed value
+     %5 = OpPhi %uint %uint_1 %10 %7 %30
+     %4 = OpIAdd %uint %2 %uint_1 ; define %4
+     %6 = OpIAdd %uint %4 %uint_1 ; use %4
+     OpLoopMerge %79 %30 None
+     OpBranchConditional %101 %79 %30
+
+     %30 = OpLabel
+     %7 = OpIAdd %uint %4 %6 ; use %4 again
+     OpBranch %20
+
+     %79 = OpLabel
+     OpBranch %89
+
+     %89 = OpLabel
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
+      << assembly << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(let x_101 : bool = x_17;
+loop {
+  var x_2_phi : u32;
+  var x_5_phi : u32;
+  x_2_phi = 0u;
+  x_5_phi = 1u;
+  loop {
+    var x_7 : u32;
+    let x_2 : u32 = x_2_phi;
+    let x_5 : u32 = x_5_phi;
+    let x_4 : u32 = (x_2 + 1u);
+    let x_6 : u32 = (x_4 + 1u);
+    if (x_101) {
+      break;
+    }
+
+    continuing {
+      x_7 = (x_4 + x_6);
+      x_2_phi = x_4;
+      x_5_phi = x_7;
+    }
+  }
+}
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_FromElseAndThen) {
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %7 = OpVariable %boolpty Private
+     %8 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     %101 = OpLoad %bool %7
+     %102 = OpLoad %bool %8
+     OpBranch %10
+
+     ; Use an outer loop to show we put the new variable in the
+     ; smallest enclosing scope.
+     %10 = OpLabel
+     OpLoopMerge %99 %89 None
+     OpBranchConditional %101 %99 %20
+
+     %20 = OpLabel ; if seleciton
+     OpSelectionMerge %79 None
+     OpBranchConditional %102 %30 %40
+
+     %30 = OpLabel
+     OpBranch %89
+
+     %40 = OpLabel
+     OpBranch %89
+
+     %79 = OpLabel ; disconnected selection merge node
+     OpBranch %89
+
+     %89 = OpLabel
+     %2 = OpPhi %uint %uint_0 %30 %uint_1 %40 %uint_0 %79
+     OpStore %1 %2
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(let x_101 : bool = x_7;
+let x_102 : bool = x_8;
+loop {
+  var x_2_phi : u32;
+  if (x_101) {
+    break;
+  }
+  if (x_102) {
+    x_2_phi = 0u;
+    continue;
+  } else {
+    x_2_phi = 1u;
+    continue;
+  }
+  x_2_phi = 0u;
+
+  continuing {
+    let x_2 : u32 = x_2_phi;
+    x_1 = x_2;
+  }
+}
+return;
+)";
+  EXPECT_EQ(expect, got) << got;
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Phi_FromHeaderAndThen) {
+  auto assembly = Preamble() + R"(
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %7 = OpVariable %boolpty Private
+     %8 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %5 = OpLabel
+     %101 = OpLoad %bool %7
+     %102 = OpLoad %bool %8
+     OpBranch %10
+
+     ; Use an outer loop to show we put the new variable in the
+     ; smallest enclosing scope.
+     %10 = OpLabel
+     OpLoopMerge %99 %89 None
+     OpBranchConditional %101 %99 %20
+
+     %20 = OpLabel ; if seleciton
+     OpSelectionMerge %79 None
+     OpBranchConditional %102 %30 %89
+
+     %30 = OpLabel
+     OpBranch %89
+
+     %79 = OpLabel ; disconnected selection merge node
+     OpUnreachable
+
+     %89 = OpLabel
+     %2 = OpPhi %uint %uint_0 %20 %uint_1 %30
+     OpStore %1 %2
+     OpBranch %10
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(let x_101 : bool = x_7;
+let x_102 : bool = x_8;
+loop {
+  var x_2_phi : u32;
+  if (x_101) {
+    break;
+  }
+  x_2_phi = 0u;
+  if (x_102) {
+    x_2_phi = 1u;
+    continue;
+  } else {
+    continue;
+  }
+  return;
+
+  continuing {
+    let x_2 : u32 = x_2_phi;
+    x_1 = x_2;
+  }
+}
+return;
+)";
+  EXPECT_EQ(expect, got) << got;
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase) {
+  // This is the essence of the bug report from crbug.com/tint/495
+  auto assembly = Preamble() + R"(
+     %cond = OpConstantTrue %bool
+     %pty = OpTypePointer Private %uint
+     %1 = OpVariable %pty Private
+     %boolpty = OpTypePointer Private %bool
+     %7 = OpVariable %boolpty Private
+     %8 = OpVariable %boolpty Private
+
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %uint_1 %20 0 %20 1 %30
+
+       %20 = OpLabel ; case 0
+       OpBranch %30 ;; fall through
+
+       %30 = OpLabel ; case 1
+       OpSelectionMerge %50 None
+       OpBranchConditional %true %40 %45
+
+         %40 = OpLabel
+         OpBranch %50
+
+         %45 = OpLabel
+         OpBranch %99 ; break
+
+       %50 = OpLabel ; end the case
+       OpBranch %99
+
+     %99 = OpLabel
+     ; predecessors are all dominated by case construct head at %30
+     %phi = OpPhi %uint %uint_0 %45 %uint_1 %50
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var x_41_phi : u32;
+switch(1u) {
+  default: {
+    fallthrough;
+  }
+  case 0u: {
+    fallthrough;
+  }
+  case 1u: {
+    if (true) {
+    } else {
+      x_41_phi = 0u;
+      break;
+    }
+    x_41_phi = 1u;
+  }
+}
+let x_41 : u32 = x_41_phi;
+return;
+)";
+  EXPECT_EQ(expect, got) << got << assembly;
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_UseInPhiCountsAsUse) {
+  // From crbug.com/215
+  // If the only use of a combinatorially computed ID is as the value
+  // in an OpPhi, then we still have to emit it.  The algorithm fix
+  // is to always count uses in Phis.
+  // This is the reduced case from the bug report.
+  //
+  // The only use of %12 is in the phi.
+  // The only use of %11 is in %12.
+  // Both definintions need to be emitted to the output.
+  auto assembly = Preamble() + R"(
+        %100 = OpFunction %void None %voidfn
+
+         %10 = OpLabel
+         %11 = OpLogicalAnd %bool %true %true
+         %12 = OpLogicalNot %bool %11  ;
+               OpSelectionMerge %99 None
+               OpBranchConditional %true %20 %99
+
+         %20 = OpLabel
+               OpBranch %99
+
+         %99 = OpLabel
+        %101 = OpPhi %bool %11 %10 %12 %20
+               OpReturn
+
+               OpFunctionEnd
+
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var x_101_phi : bool;
+let x_11 : bool = (true & true);
+let x_12 : bool = !(x_11);
+x_101_phi = x_11;
+if (true) {
+  x_101_phi = x_12;
+}
+let x_101 : bool = x_101_phi;
+return;
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(SpvParserFunctionVarTest,
+       EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored) {
+  // From crbug.com/tint/804
+  const auto assembly = Preamble() + R"(
+     %float_42 = OpConstant %float 42.0
+     %cond = OpUndef %bool
+
+     %100 = OpFunction %void None %voidfn
+     %10 = OpLabel
+     OpBranch %30
+
+     ; unreachable
+     %20 = OpLabel
+     %499 = OpFAdd %float %float_42 %float_42
+     %500 = OpFAdd %float %499 %float_42
+     OpBranch %25
+
+     %25 = OpLabel
+     OpBranch %80
+
+
+     %30 = OpLabel
+     OpLoopMerge %90 %80 None
+     OpBranchConditional %cond %90 %40
+
+     %40 = OpLabel
+     OpBranch %90
+
+     %80 = OpLabel ; unreachable continue target
+                ; but "dominated" by %20 and %25
+     %81 = OpPhi %float %500 %25
+     OpBranch %30 ; backedge
+
+     %90 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  const auto* expected = R"(loop {
+  if (false) {
+    break;
+  }
+  break;
+
+  continuing {
+    var x_81_phi_1 : f32;
+    let x_81 : f32 = x_81_phi_1;
+  }
+}
+return;
+)";
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_EQ(got, expected);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_CompositeInsert) {
+  // From crbug.com/tint/804
+  const auto assembly = Preamble() + R"(
+    %100 = OpFunction %void None %voidfn
+
+    %10 = OpLabel
+    OpSelectionMerge %50 None
+    OpBranchConditional %true %20 %30
+
+      %20 = OpLabel
+      %200 = OpCompositeInsert %v2int %int_0 %v2int_null 0
+      OpBranch %50
+
+      %30 = OpLabel
+      OpReturn
+
+    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
+    %201 = OpCopyObject %v2int %200
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  const auto* expected = R"(var x_200 : vec2<i32>;
+if (true) {
+  x_200 = vec2<i32>();
+  x_200.x = 0;
+} else {
+  return;
+}
+let x_201 : vec2<i32> = x_200;
+return;
+)";
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  EXPECT_EQ(got, expected);
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_VectorInsertDynamic) {
+  // Spawned from crbug.com/tint/804
+  const auto assembly = Preamble() + R"(
+    %100 = OpFunction %void None %voidfn
+
+    %10 = OpLabel
+    OpSelectionMerge %50 None
+    OpBranchConditional %true %20 %30
+
+      %20 = OpLabel
+      %200 = OpVectorInsertDynamic %v2int %v2int_null %int_3 %int_1
+      OpBranch %50
+
+      %30 = OpLabel
+      OpReturn
+
+    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
+    %201 = OpCopyObject %v2int %200
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const auto* expected = R"(var x_200 : vec2<i32>;
+if (true) {
+  x_200 = vec2<i32>();
+  x_200[1] = 3;
+} else {
+  return;
+}
+let x_201 : vec2<i32> = x_200;
+return;
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvParserFunctionVarTest, EmitStatement_Hoist_UsedAsNonPtrArg) {
+  // Spawned from crbug.com/tint/804
+  const auto assembly = Preamble() + R"(
+    %fn_int = OpTypeFunction %void %int
+
+    %500 = OpFunction %void None %fn_int
+    %501 = OpFunctionParameter %int
+    %502 = OpLabel
+    OpReturn
+    OpFunctionEnd
+
+    %100 = OpFunction %void None %voidfn
+
+    %10 = OpLabel
+    OpSelectionMerge %50 None
+    OpBranchConditional %true %20 %30
+
+      %20 = OpLabel
+      %200 = OpCopyObject %int %int_1
+      OpBranch %50
+
+      %30 = OpLabel
+      OpReturn
+
+    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
+    %201 = OpFunctionCall %void %500 %200
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const auto* expected = R"(var x_200 : i32;
+if (true) {
+  x_200 = 1;
+} else {
+  return;
+}
+x_500(x_200);
+return;
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvParserFunctionVarTest, DISABLED_EmitStatement_Hoist_UsedAsPtrArg) {
+  // Spawned from crbug.com/tint/804
+  // Blocked by crbug.com/tint/98: hoisting pointer types
+  const auto assembly = Preamble() + R"(
+
+    %fn_int = OpTypeFunction %void %ptr_int
+
+    %500 = OpFunction %void None %fn_int
+    %501 = OpFunctionParameter %ptr_int
+    %502 = OpLabel
+    OpReturn
+    OpFunctionEnd
+
+    %100 = OpFunction %void None %voidfn
+
+    %10 = OpLabel
+    %199 = OpVariable %ptr_int Function
+    OpSelectionMerge %50 None
+    OpBranchConditional %true %20 %30
+
+      %20 = OpLabel
+      %200 = OpCopyObject %ptr_int %199
+      OpBranch %50
+
+      %30 = OpLabel
+      OpReturn
+
+    %50 = OpLabel   ; dominated by %20, but %200 needs to be hoisted
+    %201 = OpFunctionCall %void %500 %200
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  const auto* expected = R"(xxxxxxxxxxxxxxxxxxxxx)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/namer.cc b/src/tint/reader/spirv/namer.cc
new file mode 100644
index 0000000..ab19bb4
--- /dev/null
+++ b/src/tint/reader/spirv/namer.cc
@@ -0,0 +1,239 @@
+// Copyright 2020 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/reader/spirv/namer.h"
+
+#include <algorithm>
+#include <sstream>
+#include <unordered_set>
+
+#include "src/tint/debug.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+namespace {
+
+const char* kWGSLReservedWords[] = {
+    // Please keep this list sorted
+    "array",      "as",          "asm",
+    "bf16",       "binding",     "block",
+    "bool",       "break",       "builtin",
+    "case",       "cast",        "compute",
+    "const",      "continue",    "default",
+    "discard",    "do",          "else",
+    "elseif",     "entry_point", "enum",
+    "f16",        "f32",         "fallthrough",
+    "false",      "fn",          "for",
+    "fragment",   "i16",         "i32",
+    "i64",        "i8",          "if",
+    "image",      "import",      "in",
+    "let",        "location",    "loop",
+    "mat2x2",     "mat2x3",      "mat2x4",
+    "mat3x2",     "mat3x3",      "mat3x4",
+    "mat4x2",     "mat4x3",      "mat4x4",
+    "offset",     "out",         "override",
+    "premerge",   "private",     "ptr",
+    "regardless", "return",      "set",
+    "storage",    "struct",      "switch",
+    "true",       "type",        "typedef",
+    "u16",        "u32",         "u64",
+    "u8",         "uniform",     "uniform_constant",
+    "unless",     "using",       "var",
+    "vec2",       "vec3",        "vec4",
+    "vertex",     "void",        "while",
+    "workgroup",
+};
+
+}  // namespace
+
+Namer::Namer(const FailStream& fail_stream) : fail_stream_(fail_stream) {
+  for (const auto* reserved : kWGSLReservedWords) {
+    name_to_id_[std::string(reserved)] = 0;
+  }
+}
+
+Namer::~Namer() = default;
+
+std::string Namer::Sanitize(const std::string& suggested_name) {
+  if (suggested_name.empty()) {
+    return "empty";
+  }
+  // Otherwise, replace invalid characters by '_'.
+  std::string result;
+  std::string invalid_as_first_char = "_0123456789";
+  std::string valid =
+      "abcdefghijklmnopqrstuvwxyz"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      "_0123456789";
+  // If the first character is invalid for starting a WGSL identifier, then
+  // prefix the result with "x".
+  if ((std::string::npos != invalid_as_first_char.find(suggested_name[0])) ||
+      (std::string::npos == valid.find(suggested_name[0]))) {
+    result = "x";
+  }
+  std::transform(suggested_name.begin(), suggested_name.end(),
+                 std::back_inserter(result), [&valid](const char c) {
+                   return (std::string::npos == valid.find(c)) ? '_' : c;
+                 });
+  return result;
+}
+
+std::string Namer::GetMemberName(uint32_t struct_id,
+                                 uint32_t member_index) const {
+  std::string result;
+  auto where = struct_member_names_.find(struct_id);
+  if (where != struct_member_names_.end()) {
+    auto& member_names = where->second;
+    if (member_index < member_names.size()) {
+      result = member_names[member_index];
+    }
+  }
+  return result;
+}
+
+std::string Namer::FindUnusedDerivedName(const std::string& base_name) {
+  // Ensure uniqueness among names.
+  std::string derived_name;
+  uint32_t& i = next_unusued_derived_name_id_[base_name];
+  while (i != 0xffffffff) {
+    std::stringstream new_name_stream;
+    new_name_stream << base_name;
+    if (i > 0) {
+      new_name_stream << "_" << i;
+    }
+    derived_name = new_name_stream.str();
+    if (!IsRegistered(derived_name)) {
+      return derived_name;
+    }
+    i++;
+  }
+  TINT_ASSERT(Reader, false /* FindUnusedDerivedName() overflowed u32 */);
+  return "<u32 overflow>";
+}
+
+std::string Namer::MakeDerivedName(const std::string& base_name) {
+  auto result = FindUnusedDerivedName(base_name);
+  const bool registered = RegisterWithoutId(result);
+  TINT_ASSERT(Reader, registered);
+  return result;
+}
+
+bool Namer::Register(uint32_t id, const std::string& name) {
+  if (HasName(id)) {
+    return Fail() << "internal error: ID " << id
+                  << " already has registered name: " << id_to_name_[id];
+  }
+  if (!RegisterWithoutId(name)) {
+    return false;
+  }
+  id_to_name_[id] = name;
+  name_to_id_[name] = id;
+  return true;
+}
+
+bool Namer::RegisterWithoutId(const std::string& name) {
+  if (IsRegistered(name)) {
+    return Fail() << "internal error: name already registered: " << name;
+  }
+  name_to_id_[name] = 0;
+  return true;
+}
+
+bool Namer::SuggestSanitizedName(uint32_t id,
+                                 const std::string& suggested_name) {
+  if (HasName(id)) {
+    return false;
+  }
+
+  return Register(id, FindUnusedDerivedName(Sanitize(suggested_name)));
+}
+
+bool Namer::SuggestSanitizedMemberName(uint32_t struct_id,
+                                       uint32_t member_index,
+                                       const std::string& suggested_name) {
+  // Creates an empty vector the first time we visit this struct.
+  auto& name_vector = struct_member_names_[struct_id];
+  // Resizing will set new entries to the empty string.
+  name_vector.resize(std::max(name_vector.size(), size_t(member_index + 1)));
+  auto& entry = name_vector[member_index];
+  if (entry.empty()) {
+    entry = Sanitize(suggested_name);
+    return true;
+  }
+  return false;
+}
+
+void Namer::ResolveMemberNamesForStruct(uint32_t struct_id,
+                                        uint32_t num_members) {
+  auto& name_vector = struct_member_names_[struct_id];
+  // Resizing will set new entries to the empty string.
+  // It would have been an error if the client had registered a name for
+  // an out-of-bounds member index, so toss those away.
+  name_vector.resize(num_members);
+
+  std::unordered_set<std::string> used_names;
+
+  // Returns a name, based on the suggestion, which does not equal
+  // any name in the used_names set.
+  auto disambiguate_name =
+      [&used_names](const std::string& suggestion) -> std::string {
+    if (used_names.find(suggestion) == used_names.end()) {
+      // There is no collision.
+      return suggestion;
+    }
+
+    uint32_t i = 1;
+    std::string new_name;
+    do {
+      std::stringstream new_name_stream;
+      new_name_stream << suggestion << "_" << i;
+      new_name = new_name_stream.str();
+      ++i;
+    } while (used_names.find(new_name) != used_names.end());
+    return new_name;
+  };
+
+  // First ensure uniqueness among names for which we have already taken
+  // suggestions.
+  for (auto& name : name_vector) {
+    if (!name.empty()) {
+      // This modifies the names in-place, i.e. update the name_vector
+      // entries.
+      name = disambiguate_name(name);
+      used_names.insert(name);
+    }
+  }
+
+  // Now ensure uniqueness among the rest.  Doing this in a second pass
+  // allows us to preserve suggestions as much as possible.  Otherwise
+  // a generated name such as 'field1' might collide with a user-suggested
+  // name of 'field1' attached to a later member.
+  uint32_t index = 0;
+  for (auto& name : name_vector) {
+    if (name.empty()) {
+      std::stringstream suggestion;
+      suggestion << "field" << index;
+      // Again, modify the name-vector in-place.
+      name = disambiguate_name(suggestion.str());
+      used_names.insert(name);
+    }
+    index++;
+  }
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/namer.h b/src/tint/reader/spirv/namer.h
new file mode 100644
index 0000000..5491152
--- /dev/null
+++ b/src/tint/reader/spirv/namer.h
@@ -0,0 +1,165 @@
+// Copyright 2020 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_READER_SPIRV_NAMER_H_
+#define SRC_TINT_READER_SPIRV_NAMER_H_
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "src/tint/reader/spirv/fail_stream.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// A Namer maps SPIR-V IDs to strings.
+///
+/// Sanitization:
+/// Some names are user-suggested, but "sanitized" in the sense that an
+/// unusual character (e.g. invalid for use in WGSL identifiers) is remapped
+/// to a safer character such as an underscore.  Also, sanitized names
+/// never start with an underscore.
+class Namer {
+ public:
+  /// Creates a new namer
+  /// @param fail_stream the error reporting stream
+  explicit Namer(const FailStream& fail_stream);
+  /// Destructor
+  ~Namer();
+
+  /// Sanitizes the given string, to replace unusual characters with
+  /// obviously-valid idenfier characters. An empy string yields "empty".
+  /// A sanitized name never starts with an underscore.
+  /// @param suggested_name input string
+  /// @returns sanitized name, suitable for use as an identifier
+  static std::string Sanitize(const std::string& suggested_name);
+
+  /// Registers a failure.
+  /// @returns a fail stream to accumulate diagnostics.
+  FailStream& Fail() { return fail_stream_.Fail(); }
+
+  /// @param id the SPIR-V ID
+  /// @returns true if we the given ID already has a registered name.
+  bool HasName(uint32_t id) {
+    return id_to_name_.find(id) != id_to_name_.end();
+  }
+
+  /// @param name a string
+  /// @returns true if the string has been registered as a name.
+  bool IsRegistered(const std::string& name) const {
+    return name_to_id_.find(name) != name_to_id_.end();
+  }
+
+  /// @param id the SPIR-V ID
+  /// @returns the name for the ID. It must have been registered.
+  const std::string& GetName(uint32_t id) const {
+    return id_to_name_.find(id)->second;
+  }
+
+  /// Gets a unique name for the ID. If one already exists, then return
+  /// that, otherwise synthesize a name and remember it for later.
+  /// @param id the SPIR-V ID
+  /// @returns a name for the given ID. Generates a name if non exists.
+  const std::string& Name(uint32_t id) {
+    if (!HasName(id)) {
+      SuggestSanitizedName(id, "x_" + std::to_string(id));
+    }
+    return GetName(id);
+  }
+
+  /// Gets the registered name for a struct member. If no name has
+  /// been registered for this member, then returns the empty string.
+  /// member index is in bounds.
+  /// @param id the SPIR-V ID of the struct type
+  /// @param member_index the index of the member, counting from 0
+  /// @returns the registered name for the ID, or an empty string if
+  /// nothing has been registered.
+  std::string GetMemberName(uint32_t id, uint32_t member_index) const;
+
+  /// Returns an unregistered name based on a given base name.
+  /// @param base_name the base name
+  /// @returns a new name
+  std::string FindUnusedDerivedName(const std::string& base_name);
+
+  /// Returns a newly registered name based on a given base name.
+  /// In the internal table `name_to_id_`, it is mapped to the invalid
+  /// SPIR-V ID 0.  It does not have an entry in `id_to_name_`.
+  /// @param base_name the base name
+  /// @returns a new name
+  std::string MakeDerivedName(const std::string& base_name);
+
+  /// Records a mapping from the given ID to a name. Emits a failure
+  /// if the ID already has a registered name.
+  /// @param id the SPIR-V ID
+  /// @param name the name to map to the ID
+  /// @returns true if the ID did not have a previously registered name.
+  bool Register(uint32_t id, const std::string& name);
+
+  /// Registers a name, but not associated to any ID. Fails and emits
+  /// a diagnostic if the name was already registered.
+  /// @param name the name to register
+  /// @returns true if the name was not already reegistered.
+  bool RegisterWithoutId(const std::string& name);
+
+  /// Saves a sanitized name for the given ID, if that ID does not yet
+  /// have a registered name, and if the sanitized name has not already
+  /// been registered to a different ID.
+  /// @param id the SPIR-V ID
+  /// @param suggested_name the suggested name
+  /// @returns true if a name was newly registered for the ID
+  bool SuggestSanitizedName(uint32_t id, const std::string& suggested_name);
+
+  /// Saves a sanitized name for a member of a struct, if that member
+  /// does not yet have a registered name.
+  /// @param struct_id the SPIR-V ID for the struct
+  /// @param member_index the index of the member inside the struct
+  /// @param suggested_name the suggested name
+  /// @returns true if a name was newly registered
+  bool SuggestSanitizedMemberName(uint32_t struct_id,
+                                  uint32_t member_index,
+                                  const std::string& suggested_name);
+
+  /// Ensure there are member names registered for members of the given struct
+  /// such that:
+  /// - Each member has a non-empty sanitized name.
+  /// - No two members in the struct have the same name.
+  /// @param struct_id the SPIR-V ID for the struct
+  /// @param num_members the number of members in the struct
+  void ResolveMemberNamesForStruct(uint32_t struct_id, uint32_t num_members);
+
+ private:
+  FailStream fail_stream_;
+
+  // Maps an ID to its registered name.
+  std::unordered_map<uint32_t, std::string> id_to_name_;
+  // Maps a name to a SPIR-V ID, or 0 (the case for derived names).
+  std::unordered_map<std::string, uint32_t> name_to_id_;
+
+  // Maps a struct id and member index to a suggested sanitized name.
+  // If entry k in the vector is an empty string, then a suggestion
+  // was recorded for a higher-numbered index, but not for index k.
+  std::unordered_map<uint32_t, std::vector<std::string>> struct_member_names_;
+
+  // Saved search id suffix for a given base name. Used by
+  // FindUnusedDerivedName().
+  std::unordered_map<std::string, uint32_t> next_unusued_derived_name_id_;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_NAMER_H_
diff --git a/src/tint/reader/spirv/namer_test.cc b/src/tint/reader/spirv/namer_test.cc
new file mode 100644
index 0000000..0aee67d
--- /dev/null
+++ b/src/tint/reader/spirv/namer_test.cc
@@ -0,0 +1,407 @@
+// Copyright 2020 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/reader/spirv/namer.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+class SpvNamerTest : public testing::Test {
+ public:
+  SpvNamerTest() : fail_stream_(&success_, &errors_) {}
+
+  /// @returns the accumulated diagnostic strings
+  std::string error() { return errors_.str(); }
+
+ protected:
+  std::stringstream errors_;
+  bool success_ = true;
+  FailStream fail_stream_;
+};
+
+TEST_F(SpvNamerTest, SanitizeEmpty) {
+  EXPECT_THAT(Namer::Sanitize(""), Eq("empty"));
+}
+
+TEST_F(SpvNamerTest, SanitizeLeadingUnderscore) {
+  EXPECT_THAT(Namer::Sanitize("_"), Eq("x_"));
+}
+
+TEST_F(SpvNamerTest, SanitizeLeadingDigit) {
+  EXPECT_THAT(Namer::Sanitize("7zip"), Eq("x7zip"));
+}
+
+TEST_F(SpvNamerTest, SanitizeOkChars) {
+  EXPECT_THAT(Namer::Sanitize("_abcdef12345"), Eq("x_abcdef12345"));
+}
+
+TEST_F(SpvNamerTest, SanitizeNonIdentifierChars) {
+  EXPECT_THAT(Namer::Sanitize("a:1.2'f\n"), "a_1_2_f_");
+}
+
+TEST_F(SpvNamerTest, NoFailureToStart) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, FailLogsError) {
+  Namer namer(fail_stream_);
+  const bool converted_result = namer.Fail() << "st. johns wood";
+  EXPECT_FALSE(converted_result);
+  EXPECT_EQ(error(), "st. johns wood");
+  EXPECT_FALSE(success_);
+}
+
+TEST_F(SpvNamerTest, NoNameRecorded) {
+  Namer namer(fail_stream_);
+
+  EXPECT_FALSE(namer.HasName(12));
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, FindUnusedDerivedName_NoRecordedName) {
+  Namer namer(fail_stream_);
+  EXPECT_THAT(namer.FindUnusedDerivedName("eleanor"), Eq("eleanor"));
+  // Prove that it wasn't registered when first found.
+  EXPECT_THAT(namer.FindUnusedDerivedName("eleanor"), Eq("eleanor"));
+}
+
+TEST_F(SpvNamerTest, FindUnusedDerivedName_HasRecordedName) {
+  Namer namer(fail_stream_);
+  namer.Register(12, "rigby");
+  EXPECT_THAT(namer.FindUnusedDerivedName("rigby"), Eq("rigby_1"));
+}
+
+TEST_F(SpvNamerTest, FindUnusedDerivedName_HasMultipleConflicts) {
+  Namer namer(fail_stream_);
+  namer.Register(12, "rigby");
+  namer.Register(13, "rigby_1");
+  namer.Register(14, "rigby_3");
+  // It picks the first non-conflicting suffix.
+  EXPECT_THAT(namer.FindUnusedDerivedName("rigby"), Eq("rigby_2"));
+}
+
+TEST_F(SpvNamerTest, IsRegistered_NoRecordedName) {
+  Namer namer(fail_stream_);
+  EXPECT_FALSE(namer.IsRegistered("abbey"));
+}
+
+TEST_F(SpvNamerTest, IsRegistered_RegisteredById) {
+  Namer namer(fail_stream_);
+  namer.Register(1, "abbey");
+  EXPECT_TRUE(namer.IsRegistered("abbey"));
+}
+
+TEST_F(SpvNamerTest, IsRegistered_RegisteredByDerivation) {
+  Namer namer(fail_stream_);
+  const auto got = namer.MakeDerivedName("abbey");
+  EXPECT_TRUE(namer.IsRegistered("abbey"));
+  EXPECT_EQ(got, "abbey");
+}
+
+TEST_F(SpvNamerTest, MakeDerivedName_NoRecordedName) {
+  Namer namer(fail_stream_);
+  EXPECT_THAT(namer.MakeDerivedName("eleanor"), Eq("eleanor"));
+  // Prove that it was registered when first found.
+  EXPECT_THAT(namer.MakeDerivedName("eleanor"), Eq("eleanor_1"));
+}
+
+TEST_F(SpvNamerTest, MakeDerivedName_HasRecordedName) {
+  Namer namer(fail_stream_);
+  namer.Register(12, "rigby");
+  EXPECT_THAT(namer.MakeDerivedName("rigby"), Eq("rigby_1"));
+}
+
+TEST_F(SpvNamerTest, MakeDerivedName_HasMultipleConflicts) {
+  Namer namer(fail_stream_);
+  namer.Register(12, "rigby");
+  namer.Register(13, "rigby_1");
+  namer.Register(14, "rigby_3");
+  // It picks the first non-conflicting suffix.
+  EXPECT_THAT(namer.MakeDerivedName("rigby"), Eq("rigby_2"));
+}
+
+TEST_F(SpvNamerTest, RegisterWithoutId_Once) {
+  Namer namer(fail_stream_);
+
+  const std::string n("abbey");
+  EXPECT_FALSE(namer.IsRegistered(n));
+  EXPECT_TRUE(namer.RegisterWithoutId(n));
+  EXPECT_TRUE(namer.IsRegistered(n));
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, RegisterWithoutId_Twice) {
+  Namer namer(fail_stream_);
+
+  const std::string n("abbey");
+  EXPECT_FALSE(namer.IsRegistered(n));
+  EXPECT_TRUE(namer.RegisterWithoutId(n));
+  // Fails on second attempt.
+  EXPECT_FALSE(namer.RegisterWithoutId(n));
+  EXPECT_FALSE(success_);
+  EXPECT_EQ(error(), "internal error: name already registered: abbey");
+}
+
+TEST_F(SpvNamerTest, RegisterWithoutId_ConflictsWithIdRegisteredName) {
+  Namer namer(fail_stream_);
+
+  const std::string n("abbey");
+  EXPECT_TRUE(namer.Register(1, n));
+  EXPECT_TRUE(namer.IsRegistered(n));
+  // Fails on attempt to register without ID.
+  EXPECT_FALSE(namer.RegisterWithoutId(n));
+  EXPECT_FALSE(success_);
+  EXPECT_EQ(error(), "internal error: name already registered: abbey");
+}
+
+TEST_F(SpvNamerTest, Register_Once) {
+  Namer namer(fail_stream_);
+
+  const uint32_t id = 9;
+  EXPECT_FALSE(namer.HasName(id));
+  const bool save_result = namer.Register(id, "abbey road");
+  EXPECT_TRUE(save_result);
+  EXPECT_TRUE(namer.HasName(id));
+  EXPECT_EQ(namer.GetName(id), "abbey road");
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, Register_TwoIds) {
+  Namer namer(fail_stream_);
+
+  EXPECT_FALSE(namer.HasName(8));
+  EXPECT_FALSE(namer.HasName(9));
+  EXPECT_TRUE(namer.Register(8, "abbey road"));
+  EXPECT_TRUE(namer.Register(9, "rubber soul"));
+  EXPECT_TRUE(namer.HasName(8));
+  EXPECT_TRUE(namer.HasName(9));
+  EXPECT_EQ(namer.GetName(9), "rubber soul");
+  EXPECT_EQ(namer.GetName(8), "abbey road");
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, Register_FailsDueToIdReuse) {
+  Namer namer(fail_stream_);
+
+  const uint32_t id = 9;
+  EXPECT_TRUE(namer.Register(id, "abbey road"));
+  EXPECT_FALSE(namer.Register(id, "rubber soul"));
+  EXPECT_TRUE(namer.HasName(id));
+  EXPECT_EQ(namer.GetName(id), "abbey road");
+  EXPECT_FALSE(success_);
+  EXPECT_FALSE(error().empty());
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedName_TakeSuggestionWhenNoConflict) {
+  Namer namer(fail_stream_);
+
+  EXPECT_TRUE(namer.SuggestSanitizedName(1, "father"));
+  EXPECT_THAT(namer.GetName(1), Eq("father"));
+}
+
+TEST_F(SpvNamerTest,
+       SuggestSanitizedName_RejectSuggestionWhenConflictOnSameId) {
+  Namer namer(fail_stream_);
+
+  namer.Register(1, "lennon");
+  EXPECT_FALSE(namer.SuggestSanitizedName(1, "mccartney"));
+  EXPECT_THAT(namer.GetName(1), Eq("lennon"));
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedName_SanitizeSuggestion) {
+  Namer namer(fail_stream_);
+
+  EXPECT_TRUE(namer.SuggestSanitizedName(9, "m:kenzie"));
+  EXPECT_THAT(namer.GetName(9), Eq("m_kenzie"));
+}
+
+TEST_F(SpvNamerTest,
+       SuggestSanitizedName_GenerateNewNameWhenConflictOnDifferentId) {
+  Namer namer(fail_stream_);
+
+  namer.Register(7, "rice");
+  EXPECT_TRUE(namer.SuggestSanitizedName(9, "rice"));
+  EXPECT_THAT(namer.GetName(9), Eq("rice_1"));
+}
+
+TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedStruct) {
+  Namer namer(fail_stream_);
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
+}
+
+TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedMember) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(1, 2, "mother");
+  EXPECT_THAT(namer.GetMemberName(1, 0), Eq(""));
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSuggestionWhenNoConflict) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSanitizedSuggestion) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "m:t%er"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("m_t_er"));
+}
+
+TEST_F(
+    SpvNamerTest,
+    SuggestSanitizedMemberName_TakeSuggestionWhenNoConflictAfterSuggestionForLowerMember) {  // NOLINT
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 7, "mother"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mary"));
+}
+
+TEST_F(SpvNamerTest,
+       SuggestSanitizedMemberName_RejectSuggestionIfConflictOnMember) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
+  EXPECT_FALSE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
+}
+
+TEST_F(SpvNamerTest, Name_GeneratesNameIfNoneRegistered) {
+  Namer namer(fail_stream_);
+  EXPECT_THAT(namer.Name(14), Eq("x_14"));
+}
+
+TEST_F(SpvNamerTest, Name_GeneratesNameWithoutConflict) {
+  Namer namer(fail_stream_);
+  namer.Register(42, "x_14");
+  EXPECT_THAT(namer.Name(14), Eq("x_14_1"));
+}
+
+TEST_F(SpvNamerTest, Name_ReturnsRegisteredName) {
+  Namer namer(fail_stream_);
+  namer.Register(14, "hello");
+  EXPECT_THAT(namer.Name(14), Eq("hello"));
+}
+
+TEST_F(SpvNamerTest,
+       ResolveMemberNamesForStruct_GeneratesRegularNamesOnItsOwn) {
+  Namer namer(fail_stream_);
+  namer.ResolveMemberNamesForStruct(2, 4);
+  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
+  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1"));
+  EXPECT_THAT(namer.GetMemberName(2, 2), Eq("field2"));
+  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("field3"));
+}
+
+TEST_F(SpvNamerTest,
+       ResolveMemberNamesForStruct_ResolvesConflictBetweenSuggestedNames) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(2, 0, "apple");
+  namer.SuggestSanitizedMemberName(2, 1, "apple");
+  namer.ResolveMemberNamesForStruct(2, 2);
+  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("apple"));
+  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("apple_1"));
+}
+
+TEST_F(SpvNamerTest, ResolveMemberNamesForStruct_FillsUnsuggestedGaps) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(2, 1, "apple");
+  namer.SuggestSanitizedMemberName(2, 2, "core");
+  namer.ResolveMemberNamesForStruct(2, 4);
+  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
+  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("apple"));
+  EXPECT_THAT(namer.GetMemberName(2, 2), Eq("core"));
+  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("field3"));
+}
+
+TEST_F(SpvNamerTest,
+       ResolveMemberNamesForStruct_GeneratedNameAvoidsConflictWithSuggestion) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(2, 0, "field1");
+  namer.ResolveMemberNamesForStruct(2, 2);
+  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field1"));
+  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1_1"));
+}
+
+TEST_F(SpvNamerTest,
+       ResolveMemberNamesForStruct_TruncatesOutOfBoundsSuggestion) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(2, 3, "sitar");
+  EXPECT_THAT(namer.GetMemberName(2, 3), Eq("sitar"));
+  namer.ResolveMemberNamesForStruct(2, 2);
+  EXPECT_THAT(namer.GetMemberName(2, 0), Eq("field0"));
+  EXPECT_THAT(namer.GetMemberName(2, 1), Eq("field1"));
+  EXPECT_THAT(namer.GetMemberName(2, 3), Eq(""));
+}
+
+using SpvNamerReservedWordTest = ::testing::TestWithParam<std::string>;
+
+TEST_P(SpvNamerReservedWordTest, ReservedWordsAreUsed) {
+  bool success;
+  std::stringstream errors;
+  FailStream fail_stream(&success, &errors);
+  Namer namer(fail_stream);
+  const std::string reserved = GetParam();
+  // Since it's reserved, it's marked as used, and we can't register an ID
+  EXPECT_THAT(namer.FindUnusedDerivedName(reserved), Eq(reserved + "_1"));
+}
+
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_ReservedWords,
+                         SpvNamerReservedWordTest,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             // Please keep this list sorted.
+                             "array",      "as",          "asm",
+                             "bf16",       "binding",     "block",
+                             "bool",       "break",       "builtin",
+                             "case",       "cast",        "compute",
+                             "const",      "continue",    "default",
+                             "discard",    "do",          "else",
+                             "elseif",     "entry_point", "enum",
+                             "f16",        "f32",         "fallthrough",
+                             "false",      "fn",          "for",
+                             "fragment",   "i16",         "i32",
+                             "i64",        "i8",          "if",
+                             "image",      "import",      "in",
+                             "let",        "location",    "loop",
+                             "mat2x2",     "mat2x3",      "mat2x4",
+                             "mat3x2",     "mat3x3",      "mat3x4",
+                             "mat4x2",     "mat4x3",      "mat4x4",
+                             "offset",     "out",         "override",
+                             "premerge",   "private",     "ptr",
+                             "regardless", "return",      "set",
+                             "storage",    "struct",      "switch",
+                             "true",       "type",        "typedef",
+                             "u16",        "u32",         "u64",
+                             "u8",         "uniform",     "uniform_constant",
+                             "unless",     "using",       "var",
+                             "vec2",       "vec3",        "vec4",
+                             "vertex",     "void",        "while",
+                             "workgroup",
+                         }));
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser.cc b/src/tint/reader/spirv/parser.cc
new file mode 100644
index 0000000..32f00af
--- /dev/null
+++ b/src/tint/reader/spirv/parser.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 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/reader/spirv/parser.h"
+
+#include <utility>
+
+#include "src/tint/reader/spirv/parser_impl.h"
+#include "src/tint/transform/decompose_strided_array.h"
+#include "src/tint/transform/decompose_strided_matrix.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/remove_unreachable_statements.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+Program Parse(const std::vector<uint32_t>& input) {
+  ParserImpl parser(input);
+  bool parsed = parser.Parse();
+
+  ProgramBuilder& builder = parser.builder();
+  if (!parsed) {
+    // TODO(bclayton): Migrate spirv::ParserImpl to using diagnostics.
+    builder.Diagnostics().add_error(diag::System::Reader, parser.error());
+    return Program(std::move(builder));
+  }
+
+  // The SPIR-V parser can construct disjoint AST nodes, which is invalid for
+  // the Resolver. Clone the Program to clean these up.
+  builder.SetResolveOnBuild(false);
+  Program program_with_disjoint_ast(std::move(builder));
+
+  ProgramBuilder output;
+  CloneContext(&output, &program_with_disjoint_ast, false).Clone();
+  auto program = Program(std::move(output));
+  if (!program.IsValid()) {
+    return program;
+  }
+
+  transform::Manager manager;
+  manager.Add<transform::Unshadow>();
+  manager.Add<transform::SimplifyPointers>();
+  manager.Add<transform::DecomposeStridedMatrix>();
+  manager.Add<transform::DecomposeStridedArray>();
+  manager.Add<transform::RemoveUnreachableStatements>();
+  return manager.Run(&program).program;
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser.h b/src/tint/reader/spirv/parser.h
new file mode 100644
index 0000000..2e5a5c2
--- /dev/null
+++ b/src/tint/reader/spirv/parser.h
@@ -0,0 +1,38 @@
+// Copyright 2020 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_READER_SPIRV_PARSER_H_
+#define SRC_TINT_READER_SPIRV_PARSER_H_
+
+#include <vector>
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// Parses the SPIR-V source data, returning the parsed program.
+/// If the source data fails to parse then the returned
+/// `program.Diagnostics.contains_errors()` will be true, and the
+/// `program.Diagnostics()` will describe the error.
+/// @param input the source data
+/// @returns the parsed program
+Program Parse(const std::vector<uint32_t>& input);
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_PARSER_H_
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
new file mode 100644
index 0000000..c2f50d3
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -0,0 +1,2796 @@
+// Copyright 2020 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/reader/spirv/parser_impl.h"
+
+#include <algorithm>
+#include <limits>
+#include <locale>
+#include <utility>
+
+#include "source/opt/build_module.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/utils/unique_vector.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+namespace {
+
+// Input SPIR-V needs only to conform to Vulkan 1.1 requirements.
+// The combination of the SPIR-V reader and the semantics of WGSL
+// tighten up the code so that the output of the SPIR-V *writer*
+// will satisfy SPV_ENV_WEBGPU_0 validation.
+const spv_target_env kInputEnv = SPV_ENV_VULKAN_1_1;
+
+// A FunctionTraverser is used to compute an ordering of functions in the
+// module such that callees precede callers.
+class FunctionTraverser {
+ public:
+  explicit FunctionTraverser(const spvtools::opt::Module& module)
+      : module_(module) {}
+
+  // @returns the functions in the modules such that callees precede callers.
+  std::vector<const spvtools::opt::Function*> TopologicallyOrderedFunctions() {
+    visited_.clear();
+    ordered_.clear();
+    id_to_func_.clear();
+    for (const auto& f : module_) {
+      id_to_func_[f.result_id()] = &f;
+    }
+    for (const auto& f : module_) {
+      Visit(f);
+    }
+    return ordered_;
+  }
+
+ private:
+  void Visit(const spvtools::opt::Function& f) {
+    if (visited_.count(&f)) {
+      return;
+    }
+    visited_.insert(&f);
+    for (const auto& bb : f) {
+      for (const auto& inst : bb) {
+        if (inst.opcode() != SpvOpFunctionCall) {
+          continue;
+        }
+        const auto* callee = id_to_func_[inst.GetSingleWordInOperand(0)];
+        if (callee) {
+          Visit(*callee);
+        }
+      }
+    }
+    ordered_.push_back(&f);
+  }
+
+  const spvtools::opt::Module& module_;
+  std::unordered_set<const spvtools::opt::Function*> visited_;
+  std::unordered_map<uint32_t, const spvtools::opt::Function*> id_to_func_;
+  std::vector<const spvtools::opt::Function*> ordered_;
+};
+
+// Returns true if the opcode operates as if its operands are signed integral.
+bool AssumesSignedOperands(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpSNegate:
+    case SpvOpSDiv:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpSLessThan:
+    case SpvOpSLessThanEqual:
+    case SpvOpSGreaterThan:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpConvertSToF:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the GLSL extended instruction expects operands to be signed.
+// @param extended_opcode GLSL.std.450 opcode
+// @returns true if all operands must be signed integral type
+bool AssumesSignedOperands(GLSLstd450 extended_opcode) {
+  switch (extended_opcode) {
+    case GLSLstd450SAbs:
+    case GLSLstd450SSign:
+    case GLSLstd450SMin:
+    case GLSLstd450SMax:
+    case GLSLstd450SClamp:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the opcode operates as if its operands are unsigned integral.
+bool AssumesUnsignedOperands(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpUDiv:
+    case SpvOpUMod:
+    case SpvOpULessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpConvertUToF:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the GLSL extended instruction expects operands to be
+// unsigned.
+// @param extended_opcode GLSL.std.450 opcode
+// @returns true if all operands must be unsigned integral type
+bool AssumesUnsignedOperands(GLSLstd450 extended_opcode) {
+  switch (extended_opcode) {
+    case GLSLstd450UMin:
+    case GLSLstd450UMax:
+    case GLSLstd450UClamp:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the corresponding WGSL operation requires
+// the signedness of the second operand to match the signedness of the
+// first operand, and it's not one of the OpU* or OpS* instructions.
+// (Those are handled via MakeOperand.)
+bool AssumesSecondOperandSignednessMatchesFirstOperand(SpvOp opcode) {
+  switch (opcode) {
+    // All the OpI* integer binary operations.
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    // All the bitwise integer binary operations.
+    case SpvOpBitwiseAnd:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the corresponding WGSL operation requires
+// the signedness of the result to match the signedness of the first operand.
+bool AssumesResultSignednessMatchesFirstOperand(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpNot:
+    case SpvOpSNegate:
+    case SpvOpBitCount:
+    case SpvOpBitReverse:
+    case SpvOpSDiv:
+    case SpvOpSMod:
+    case SpvOpSRem:
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpBitwiseAnd:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpShiftLeftLogical:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if the extended instruction requires the signedness of the
+// result to match the signedness of the first operand to the operation.
+// @param extended_opcode GLSL.std.450 opcode
+// @returns true if the result type must match the first operand type.
+bool AssumesResultSignednessMatchesFirstOperand(GLSLstd450 extended_opcode) {
+  switch (extended_opcode) {
+    case GLSLstd450SAbs:
+    case GLSLstd450SSign:
+    case GLSLstd450SMin:
+    case GLSLstd450SMax:
+    case GLSLstd450SClamp:
+    case GLSLstd450UMin:
+    case GLSLstd450UMax:
+    case GLSLstd450UClamp:
+      // TODO(dneto): FindSMsb?
+      // TODO(dneto): FindUMsb?
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+// @param a SPIR-V decoration
+// @return true when the given decoration is a pipeline decoration other than a
+// bulitin variable.
+bool IsPipelineDecoration(const Decoration& deco) {
+  if (deco.size() < 1) {
+    return false;
+  }
+  switch (deco[0]) {
+    case SpvDecorationLocation:
+    case SpvDecorationFlat:
+    case SpvDecorationNoPerspective:
+    case SpvDecorationCentroid:
+    case SpvDecorationSample:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+}  // namespace
+
+TypedExpression::TypedExpression() = default;
+
+TypedExpression::TypedExpression(const TypedExpression&) = default;
+
+TypedExpression& TypedExpression::operator=(const TypedExpression&) = default;
+
+TypedExpression::TypedExpression(const Type* type_in,
+                                 const ast::Expression* expr_in)
+    : type(type_in), expr(expr_in) {}
+
+ParserImpl::ParserImpl(const std::vector<uint32_t>& spv_binary)
+    : Reader(),
+      spv_binary_(spv_binary),
+      fail_stream_(&success_, &errors_),
+      namer_(fail_stream_),
+      enum_converter_(fail_stream_),
+      tools_context_(kInputEnv) {
+  // Create a message consumer to propagate error messages from SPIRV-Tools
+  // out as our own failures.
+  message_consumer_ = [this](spv_message_level_t level, const char* /*source*/,
+                             const spv_position_t& position,
+                             const char* message) {
+    switch (level) {
+      // Ignore info and warning message.
+      case SPV_MSG_WARNING:
+      case SPV_MSG_INFO:
+        break;
+      // Otherwise, propagate the error.
+      default:
+        // For binary validation errors, we only have the instruction
+        // number.  It's not text, so there is no column number.
+        this->Fail() << "line:" << position.index << ": " << message;
+    }
+  };
+}
+
+ParserImpl::~ParserImpl() = default;
+
+bool ParserImpl::Parse() {
+  // Set up use of SPIRV-Tools utilities.
+  spvtools::SpirvTools spv_tools(kInputEnv);
+
+  // Error messages from SPIRV-Tools are forwarded as failures, including
+  // setting |success_| to false.
+  spv_tools.SetMessageConsumer(message_consumer_);
+
+  if (!success_) {
+    return false;
+  }
+
+  // Only consider modules valid for Vulkan 1.0.  On failure, the message
+  // consumer will set the error status.
+  if (!spv_tools.Validate(spv_binary_)) {
+    success_ = false;
+    return false;
+  }
+  if (!BuildInternalModule()) {
+    return false;
+  }
+  if (!ParseInternalModule()) {
+    return false;
+  }
+
+  return success_;
+}
+
+Program ParserImpl::program() {
+  // TODO(dneto): Should we clear out spv_binary_ here, to reduce
+  // memory usage?
+  return tint::Program(std::move(builder_));
+}
+
+const Type* ParserImpl::ConvertType(uint32_t type_id, PtrAs ptr_as) {
+  if (!success_) {
+    return nullptr;
+  }
+
+  if (type_mgr_ == nullptr) {
+    Fail() << "ConvertType called when the internal module has not been built";
+    return nullptr;
+  }
+
+  auto* spirv_type = type_mgr_->GetType(type_id);
+  if (spirv_type == nullptr) {
+    Fail() << "ID is not a SPIR-V type: " << type_id;
+    return nullptr;
+  }
+
+  switch (spirv_type->kind()) {
+    case spvtools::opt::analysis::Type::kVoid:
+      return ty_.Void();
+    case spvtools::opt::analysis::Type::kBool:
+      return ty_.Bool();
+    case spvtools::opt::analysis::Type::kInteger:
+      return ConvertType(spirv_type->AsInteger());
+    case spvtools::opt::analysis::Type::kFloat:
+      return ConvertType(spirv_type->AsFloat());
+    case spvtools::opt::analysis::Type::kVector:
+      return ConvertType(spirv_type->AsVector());
+    case spvtools::opt::analysis::Type::kMatrix:
+      return ConvertType(spirv_type->AsMatrix());
+    case spvtools::opt::analysis::Type::kRuntimeArray:
+      return ConvertType(type_id, spirv_type->AsRuntimeArray());
+    case spvtools::opt::analysis::Type::kArray:
+      return ConvertType(type_id, spirv_type->AsArray());
+    case spvtools::opt::analysis::Type::kStruct:
+      return ConvertType(type_id, spirv_type->AsStruct());
+    case spvtools::opt::analysis::Type::kPointer:
+      return ConvertType(type_id, ptr_as, spirv_type->AsPointer());
+    case spvtools::opt::analysis::Type::kFunction:
+      // Tint doesn't have a Function type.
+      // We need to convert the result type and parameter types.
+      // But the SPIR-V defines those before defining the function
+      // type.  No further work is required here.
+      return nullptr;
+    case spvtools::opt::analysis::Type::kSampler:
+    case spvtools::opt::analysis::Type::kSampledImage:
+    case spvtools::opt::analysis::Type::kImage:
+      // Fake it for sampler and texture types.  These are handled in an
+      // entirely different way.
+      return ty_.Void();
+    default:
+      break;
+  }
+
+  Fail() << "unknown SPIR-V type with ID " << type_id << ": "
+         << def_use_mgr_->GetDef(type_id)->PrettyPrint();
+  return nullptr;
+}
+
+DecorationList ParserImpl::GetDecorationsFor(uint32_t id) const {
+  DecorationList result;
+  const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
+  std::unordered_set<uint32_t> visited;
+  for (const auto* inst : decorations) {
+    if (inst->opcode() != SpvOpDecorate) {
+      continue;
+    }
+    // Example: OpDecorate %struct_id Block
+    // Example: OpDecorate %array_ty ArrayStride 16
+    auto decoration_kind = inst->GetSingleWordInOperand(1);
+    switch (decoration_kind) {
+      // Restrict and RestrictPointer have no effect in graphics APIs.
+      case SpvDecorationRestrict:
+      case SpvDecorationRestrictPointer:
+        break;
+      default:
+        if (visited.emplace(decoration_kind).second) {
+          std::vector<uint32_t> inst_as_words;
+          inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
+          Decoration d(inst_as_words.begin() + 2, inst_as_words.end());
+          result.push_back(d);
+        }
+        break;
+    }
+  }
+  return result;
+}
+
+DecorationList ParserImpl::GetDecorationsForMember(
+    uint32_t id,
+    uint32_t member_index) const {
+  DecorationList result;
+  const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
+  std::unordered_set<uint32_t> visited;
+  for (const auto* inst : decorations) {
+    // Example: OpMemberDecorate %struct_id 1 Offset 16
+    if ((inst->opcode() != SpvOpMemberDecorate) ||
+        (inst->GetSingleWordInOperand(1) != member_index)) {
+      continue;
+    }
+    auto decoration_kind = inst->GetSingleWordInOperand(2);
+    switch (decoration_kind) {
+      // Restrict and RestrictPointer have no effect in graphics APIs.
+      case SpvDecorationRestrict:
+      case SpvDecorationRestrictPointer:
+        break;
+      default:
+        if (visited.emplace(decoration_kind).second) {
+          std::vector<uint32_t> inst_as_words;
+          inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
+          Decoration d(inst_as_words.begin() + 3, inst_as_words.end());
+          result.push_back(d);
+        }
+    }
+  }
+  return result;
+}
+
+std::string ParserImpl::ShowType(uint32_t type_id) {
+  if (def_use_mgr_) {
+    const auto* type_inst = def_use_mgr_->GetDef(type_id);
+    if (type_inst) {
+      return type_inst->PrettyPrint();
+    }
+  }
+  return "SPIR-V type " + std::to_string(type_id);
+}
+
+ast::AttributeList ParserImpl::ConvertMemberDecoration(
+    uint32_t struct_type_id,
+    uint32_t member_index,
+    const Type* member_ty,
+    const Decoration& decoration) {
+  if (decoration.empty()) {
+    Fail() << "malformed SPIR-V decoration: it's empty";
+    return {};
+  }
+  switch (decoration[0]) {
+    case SpvDecorationOffset:
+      if (decoration.size() != 2) {
+        Fail()
+            << "malformed Offset decoration: expected 1 literal operand, has "
+            << decoration.size() - 1 << ": member " << member_index << " of "
+            << ShowType(struct_type_id);
+        return {};
+      }
+      return {
+          create<ast::StructMemberOffsetAttribute>(Source{}, decoration[1]),
+      };
+    case SpvDecorationNonReadable:
+      // WGSL doesn't have a member decoration for this.  Silently drop it.
+      return {};
+    case SpvDecorationNonWritable:
+      // WGSL doesn't have a member decoration for this.
+      return {};
+    case SpvDecorationColMajor:
+      // WGSL only supports column major matrices.
+      return {};
+    case SpvDecorationRelaxedPrecision:
+      // WGSL doesn't support relaxed precision.
+      return {};
+    case SpvDecorationRowMajor:
+      Fail() << "WGSL does not support row-major matrices: can't "
+                "translate member "
+             << member_index << " of " << ShowType(struct_type_id);
+      return {};
+    case SpvDecorationMatrixStride: {
+      if (decoration.size() != 2) {
+        Fail() << "malformed MatrixStride decoration: expected 1 literal "
+                  "operand, has "
+               << decoration.size() - 1 << ": member " << member_index << " of "
+               << ShowType(struct_type_id);
+        return {};
+      }
+      uint32_t stride = decoration[1];
+      auto* ty = member_ty->UnwrapAlias();
+      while (auto* arr = ty->As<Array>()) {
+        ty = arr->type->UnwrapAlias();
+      }
+      auto* mat = ty->As<Matrix>();
+      if (!mat) {
+        Fail() << "MatrixStride cannot be applied to type " << ty->String();
+        return {};
+      }
+      uint32_t natural_stride = (mat->rows == 2) ? 8 : 16;
+      if (stride == natural_stride) {
+        return {};  // Decoration matches the natural stride for the matrix
+      }
+      if (!member_ty->Is<Matrix>()) {
+        Fail() << "custom matrix strides not currently supported on array of "
+                  "matrices";
+        return {};
+      }
+      return {
+          create<ast::StrideAttribute>(Source{}, decoration[1]),
+          builder_.ASTNodes().Create<ast::DisableValidationAttribute>(
+              builder_.ID(), ast::DisabledValidation::kIgnoreStrideAttribute),
+      };
+    }
+    default:
+      // TODO(dneto): Support the remaining member decorations.
+      break;
+  }
+  Fail() << "unhandled member decoration: " << decoration[0] << " on member "
+         << member_index << " of " << ShowType(struct_type_id);
+  return {};
+}
+
+bool ParserImpl::BuildInternalModule() {
+  if (!success_) {
+    return false;
+  }
+
+  const spv_context& context = tools_context_.CContext();
+  ir_context_ = spvtools::BuildModule(context->target_env, context->consumer,
+                                      spv_binary_.data(), spv_binary_.size());
+  if (!ir_context_) {
+    return Fail() << "internal error: couldn't build the internal "
+                     "representation of the module";
+  }
+  module_ = ir_context_->module();
+  def_use_mgr_ = ir_context_->get_def_use_mgr();
+  constant_mgr_ = ir_context_->get_constant_mgr();
+  type_mgr_ = ir_context_->get_type_mgr();
+  deco_mgr_ = ir_context_->get_decoration_mgr();
+
+  topologically_ordered_functions_ =
+      FunctionTraverser(*module_).TopologicallyOrderedFunctions();
+
+  return success_;
+}
+
+void ParserImpl::ResetInternalModule() {
+  ir_context_.reset(nullptr);
+  module_ = nullptr;
+  def_use_mgr_ = nullptr;
+  constant_mgr_ = nullptr;
+  type_mgr_ = nullptr;
+  deco_mgr_ = nullptr;
+
+  glsl_std_450_imports_.clear();
+}
+
+bool ParserImpl::ParseInternalModule() {
+  if (!success_) {
+    return false;
+  }
+  RegisterLineNumbers();
+  if (!ParseInternalModuleExceptFunctions()) {
+    return false;
+  }
+  if (!EmitFunctions()) {
+    return false;
+  }
+  return success_;
+}
+
+void ParserImpl::RegisterLineNumbers() {
+  Source::Location instruction_number{};
+
+  // Has there been an OpLine since the last OpNoLine or start of the module?
+  bool in_op_line_scope = false;
+  // The source location provided by the most recent OpLine instruction.
+  Source::Location op_line_source{};
+  const bool run_on_debug_insts = true;
+  module_->ForEachInst(
+      [this, &in_op_line_scope, &op_line_source,
+       &instruction_number](const spvtools::opt::Instruction* inst) {
+        ++instruction_number.line;
+        switch (inst->opcode()) {
+          case SpvOpLine:
+            in_op_line_scope = true;
+            // TODO(dneto): This ignores the File ID (operand 0), since the Tint
+            // Source concept doesn't represent that.
+            op_line_source.line = inst->GetSingleWordInOperand(1);
+            op_line_source.column = inst->GetSingleWordInOperand(2);
+            break;
+          case SpvOpNoLine:
+            in_op_line_scope = false;
+            break;
+          default:
+            break;
+        }
+        this->inst_source_[inst] =
+            in_op_line_scope ? op_line_source : instruction_number;
+      },
+      run_on_debug_insts);
+}
+
+Source ParserImpl::GetSourceForResultIdForTest(uint32_t id) const {
+  return GetSourceForInst(def_use_mgr_->GetDef(id));
+}
+
+Source ParserImpl::GetSourceForInst(
+    const spvtools::opt::Instruction* inst) const {
+  auto where = inst_source_.find(inst);
+  if (where == inst_source_.end()) {
+    return {};
+  }
+  return Source{where->second };
+}
+
+bool ParserImpl::ParseInternalModuleExceptFunctions() {
+  if (!success_) {
+    return false;
+  }
+  if (!RegisterExtendedInstructionImports()) {
+    return false;
+  }
+  if (!RegisterUserAndStructMemberNames()) {
+    return false;
+  }
+  if (!RegisterWorkgroupSizeBuiltin()) {
+    return false;
+  }
+  if (!RegisterEntryPoints()) {
+    return false;
+  }
+  if (!RegisterHandleUsage()) {
+    return false;
+  }
+  if (!RegisterTypes()) {
+    return false;
+  }
+  if (!RejectInvalidPointerRoots()) {
+    return false;
+  }
+  if (!EmitScalarSpecConstants()) {
+    return false;
+  }
+  if (!EmitModuleScopeVariables()) {
+    return false;
+  }
+  return success_;
+}
+
+bool ParserImpl::RegisterExtendedInstructionImports() {
+  for (const spvtools::opt::Instruction& import : module_->ext_inst_imports()) {
+    std::string name(
+        reinterpret_cast<const char*>(import.GetInOperand(0).words.data()));
+    // TODO(dneto): Handle other extended instruction sets when needed.
+    if (name == "GLSL.std.450") {
+      glsl_std_450_imports_.insert(import.result_id());
+    } else if (name.find("NonSemantic.") == 0) {
+      ignored_imports_.insert(import.result_id());
+    } else {
+      return Fail() << "Unrecognized extended instruction set: " << name;
+    }
+  }
+  return true;
+}
+
+bool ParserImpl::IsGlslExtendedInstruction(
+    const spvtools::opt::Instruction& inst) const {
+  return (inst.opcode() == SpvOpExtInst) &&
+         (glsl_std_450_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
+}
+
+bool ParserImpl::IsIgnoredExtendedInstruction(
+    const spvtools::opt::Instruction& inst) const {
+  return (inst.opcode() == SpvOpExtInst) &&
+         (ignored_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
+}
+
+bool ParserImpl::RegisterUserAndStructMemberNames() {
+  if (!success_) {
+    return false;
+  }
+  // Register entry point names. An entry point name is the point of contact
+  // between the API and the shader. It has the highest priority for
+  // preservation, so register it first.
+  for (const spvtools::opt::Instruction& entry_point :
+       module_->entry_points()) {
+    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
+    const std::string name = entry_point.GetInOperand(2).AsString();
+
+    // This translator requires the entry point to be a valid WGSL identifier.
+    // Allowing otherwise leads to difficulties in that the programmer needs
+    // to get a mapping from their original entry point name to the WGSL name,
+    // and we don't have a good mechanism for that.
+    if (!IsValidIdentifier(name)) {
+      return Fail() << "entry point name is not a valid WGSL identifier: "
+                    << name;
+    }
+
+    // SPIR-V allows a single function to be the implementation for more
+    // than one entry point.  In the common case, it's one-to-one, and we should
+    // try to name the function after the entry point.  Otherwise, give the
+    // function a name automatically derived from the entry point name.
+    namer_.SuggestSanitizedName(function_id, name);
+
+    // There is another many-to-one relationship to take care of:  In SPIR-V
+    // the same name can be used for multiple entry points, provided they are
+    // for different shader stages. Take action now to ensure we can use the
+    // entry point name later on, and not have it taken for another identifier
+    // by an accidental collision with a derived name made for a different ID.
+    if (!namer_.IsRegistered(name)) {
+      // The entry point name is "unoccupied" becase an earlier entry point
+      // grabbed the slot for the function that implements both entry points.
+      // Register this new entry point's name, to avoid accidental collisions
+      // with a future generated ID.
+      if (!namer_.RegisterWithoutId(name)) {
+        return false;
+      }
+    }
+  }
+
+  // Register names from OpName and OpMemberName
+  for (const auto& inst : module_->debugs2()) {
+    switch (inst.opcode()) {
+      case SpvOpName: {
+        const auto name = inst.GetInOperand(1).AsString();
+        if (!name.empty()) {
+          namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0), name);
+        }
+        break;
+      }
+      case SpvOpMemberName: {
+        const auto name = inst.GetInOperand(2).AsString();
+        if (!name.empty()) {
+          namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
+                                            inst.GetSingleWordInOperand(1),
+                                            name);
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  }
+
+  // Fill in struct member names, and disambiguate them.
+  for (const auto* type_inst : module_->GetTypes()) {
+    if (type_inst->opcode() == SpvOpTypeStruct) {
+      namer_.ResolveMemberNamesForStruct(type_inst->result_id(),
+                                         type_inst->NumInOperands());
+    }
+  }
+
+  return true;
+}
+
+bool ParserImpl::IsValidIdentifier(const std::string& str) {
+  if (str.empty()) {
+    return false;
+  }
+  std::locale c_locale("C");
+  if (str[0] == '_') {
+    if (str.length() == 1u || str[1] == '_') {
+      // https://www.w3.org/TR/WGSL/#identifiers
+      // must not be '_' (a single underscore)
+      // must not start with two underscores
+      return false;
+    }
+  } else if (!std::isalpha(str[0], c_locale)) {
+    return false;
+  }
+  for (const char& ch : str) {
+    if ((ch != '_') && !std::isalnum(ch, c_locale)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ParserImpl::RegisterWorkgroupSizeBuiltin() {
+  WorkgroupSizeInfo& info = workgroup_size_builtin_;
+  for (const spvtools::opt::Instruction& inst : module_->annotations()) {
+    if (inst.opcode() != SpvOpDecorate) {
+      continue;
+    }
+    if (inst.GetSingleWordInOperand(1) != SpvDecorationBuiltIn) {
+      continue;
+    }
+    if (inst.GetSingleWordInOperand(2) != SpvBuiltInWorkgroupSize) {
+      continue;
+    }
+    info.id = inst.GetSingleWordInOperand(0);
+  }
+  if (info.id == 0) {
+    return true;
+  }
+  // Gather the values.
+  const spvtools::opt::Instruction* composite_def =
+      def_use_mgr_->GetDef(info.id);
+  if (!composite_def) {
+    return Fail() << "Invalid WorkgroupSize builtin value";
+  }
+  // SPIR-V validation checks that the result is a 3-element vector of 32-bit
+  // integer scalars (signed or unsigned).  Rely on validation to check the
+  // type.  In theory the instruction could be OpConstantNull and still
+  // pass validation, but that would be non-sensical.  Be a little more
+  // stringent here and check for specific opcodes.  WGSL does not support
+  // const-expr yet, so avoid supporting OpSpecConstantOp here.
+  // TODO(dneto): See https://github.com/gpuweb/gpuweb/issues/1272 for WGSL
+  // const_expr proposals.
+  if ((composite_def->opcode() != SpvOpSpecConstantComposite &&
+       composite_def->opcode() != SpvOpConstantComposite)) {
+    return Fail() << "Invalid WorkgroupSize builtin.  Expected 3-element "
+                     "OpSpecConstantComposite or OpConstantComposite:  "
+                  << composite_def->PrettyPrint();
+  }
+  info.type_id = composite_def->type_id();
+  // Extract the component type from the vector type.
+  info.component_type_id =
+      def_use_mgr_->GetDef(info.type_id)->GetSingleWordInOperand(0);
+
+  /// Sets the ID and value of the index'th member of the composite constant.
+  /// Returns false and emits a diagnostic on error.
+  auto set_param = [this, composite_def](uint32_t* id_ptr, uint32_t* value_ptr,
+                                         int index) -> bool {
+    const auto id = composite_def->GetSingleWordInOperand(index);
+    const auto* def = def_use_mgr_->GetDef(id);
+    if (!def ||
+        (def->opcode() != SpvOpSpecConstant &&
+         def->opcode() != SpvOpConstant) ||
+        (def->NumInOperands() != 1)) {
+      return Fail() << "invalid component " << index << " of workgroupsize "
+                    << (def ? def->PrettyPrint()
+                            : std::string("no definition"));
+    }
+    *id_ptr = id;
+    // Use the default value of a spec constant.
+    *value_ptr = def->GetSingleWordInOperand(0);
+    return true;
+  };
+
+  return set_param(&info.x_id, &info.x_value, 0) &&
+         set_param(&info.y_id, &info.y_value, 1) &&
+         set_param(&info.z_id, &info.z_value, 2);
+}
+
+bool ParserImpl::RegisterEntryPoints() {
+  // Mapping from entry point ID to GridSize computed from LocalSize
+  // decorations.
+  std::unordered_map<uint32_t, GridSize> local_size;
+  for (const spvtools::opt::Instruction& inst : module_->execution_modes()) {
+    auto mode = static_cast<SpvExecutionMode>(inst.GetSingleWordInOperand(1));
+    if (mode == SpvExecutionModeLocalSize) {
+      if (inst.NumInOperands() != 5) {
+        // This won't even get past SPIR-V binary parsing.
+        return Fail() << "invalid LocalSize execution mode: "
+                      << inst.PrettyPrint();
+      }
+      uint32_t function_id = inst.GetSingleWordInOperand(0);
+      local_size[function_id] = GridSize{inst.GetSingleWordInOperand(2),
+                                         inst.GetSingleWordInOperand(3),
+                                         inst.GetSingleWordInOperand(4)};
+    }
+  }
+
+  for (const spvtools::opt::Instruction& entry_point :
+       module_->entry_points()) {
+    const auto stage = SpvExecutionModel(entry_point.GetSingleWordInOperand(0));
+    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
+
+    const std::string ep_name = entry_point.GetOperand(2).AsString();
+    if (!IsValidIdentifier(ep_name)) {
+      return Fail() << "entry point name is not a valid WGSL identifier: "
+                    << ep_name;
+    }
+
+    bool owns_inner_implementation = false;
+    std::string inner_implementation_name;
+
+    auto where = function_to_ep_info_.find(function_id);
+    if (where == function_to_ep_info_.end()) {
+      // If this is the first entry point to have function_id as its
+      // implementation, then this entry point is responsible for generating
+      // the inner implementation.
+      owns_inner_implementation = true;
+      inner_implementation_name = namer_.MakeDerivedName(ep_name);
+    } else {
+      // Reuse the inner implementation owned by the first entry point.
+      inner_implementation_name = where->second[0].inner_name;
+    }
+    TINT_ASSERT(Reader, !inner_implementation_name.empty());
+    TINT_ASSERT(Reader, ep_name != inner_implementation_name);
+
+    utils::UniqueVector<uint32_t> inputs;
+    utils::UniqueVector<uint32_t> outputs;
+    for (unsigned iarg = 3; iarg < entry_point.NumInOperands(); iarg++) {
+      const uint32_t var_id = entry_point.GetSingleWordInOperand(iarg);
+      if (const auto* var_inst = def_use_mgr_->GetDef(var_id)) {
+        switch (SpvStorageClass(var_inst->GetSingleWordInOperand(0))) {
+          case SpvStorageClassInput:
+            inputs.add(var_id);
+            break;
+          case SpvStorageClassOutput:
+            outputs.add(var_id);
+            break;
+          default:
+            break;
+        }
+      }
+    }
+    // Save the lists, in ID-sorted order.
+    std::vector<uint32_t> sorted_inputs(inputs);
+    std::sort(sorted_inputs.begin(), sorted_inputs.end());
+    std::vector<uint32_t> sorted_outputs(outputs);
+    std::sort(sorted_outputs.begin(), sorted_outputs.end());
+
+    const auto ast_stage = enum_converter_.ToPipelineStage(stage);
+    GridSize wgsize;
+    if (ast_stage == ast::PipelineStage::kCompute) {
+      if (workgroup_size_builtin_.id) {
+        // Store the default values.
+        // WGSL allows specializing these, but this code doesn't support that
+        // yet. https://github.com/gpuweb/gpuweb/issues/1442
+        wgsize = GridSize{workgroup_size_builtin_.x_value,
+                          workgroup_size_builtin_.y_value,
+                          workgroup_size_builtin_.z_value};
+      } else {
+        // Use the LocalSize execution mode.  This is the second choice.
+        auto where_local_size = local_size.find(function_id);
+        if (where_local_size != local_size.end()) {
+          wgsize = where_local_size->second;
+        }
+      }
+    }
+    function_to_ep_info_[function_id].emplace_back(
+        ep_name, ast_stage, owns_inner_implementation,
+        inner_implementation_name, std::move(sorted_inputs),
+        std::move(sorted_outputs), wgsize);
+  }
+
+  // The enum conversion could have failed, so return the existing status value.
+  return success_;
+}
+
+const Type* ParserImpl::ConvertType(
+    const spvtools::opt::analysis::Integer* int_ty) {
+  if (int_ty->width() == 32) {
+    return int_ty->IsSigned() ? static_cast<const Type*>(ty_.I32())
+                              : static_cast<const Type*>(ty_.U32());
+  }
+  Fail() << "unhandled integer width: " << int_ty->width();
+  return nullptr;
+}
+
+const Type* ParserImpl::ConvertType(
+    const spvtools::opt::analysis::Float* float_ty) {
+  if (float_ty->width() == 32) {
+    return ty_.F32();
+  }
+  Fail() << "unhandled float width: " << float_ty->width();
+  return nullptr;
+}
+
+const Type* ParserImpl::ConvertType(
+    const spvtools::opt::analysis::Vector* vec_ty) {
+  const auto num_elem = vec_ty->element_count();
+  auto* ast_elem_ty = ConvertType(type_mgr_->GetId(vec_ty->element_type()));
+  if (ast_elem_ty == nullptr) {
+    return ast_elem_ty;
+  }
+  return ty_.Vector(ast_elem_ty, num_elem);
+}
+
+const Type* ParserImpl::ConvertType(
+    const spvtools::opt::analysis::Matrix* mat_ty) {
+  const auto* vec_ty = mat_ty->element_type()->AsVector();
+  const auto* scalar_ty = vec_ty->element_type();
+  const auto num_rows = vec_ty->element_count();
+  const auto num_columns = mat_ty->element_count();
+  auto* ast_scalar_ty = ConvertType(type_mgr_->GetId(scalar_ty));
+  if (ast_scalar_ty == nullptr) {
+    return nullptr;
+  }
+  return ty_.Matrix(ast_scalar_ty, num_columns, num_rows);
+}
+
+const Type* ParserImpl::ConvertType(
+    uint32_t type_id,
+    const spvtools::opt::analysis::RuntimeArray* rtarr_ty) {
+  auto* ast_elem_ty = ConvertType(type_mgr_->GetId(rtarr_ty->element_type()));
+  if (ast_elem_ty == nullptr) {
+    return nullptr;
+  }
+  uint32_t array_stride = 0;
+  if (!ParseArrayDecorations(rtarr_ty, &array_stride)) {
+    return nullptr;
+  }
+  const Type* result = ty_.Array(ast_elem_ty, 0, array_stride);
+  return MaybeGenerateAlias(type_id, rtarr_ty, result);
+}
+
+const Type* ParserImpl::ConvertType(
+    uint32_t type_id,
+    const spvtools::opt::analysis::Array* arr_ty) {
+  // Get the element type. The SPIR-V optimizer's types representation
+  // deduplicates array types that have the same parameterization.
+  // We don't want that deduplication, so get the element type from
+  // the SPIR-V type directly.
+  const auto* inst = def_use_mgr_->GetDef(type_id);
+  const auto elem_type_id = inst->GetSingleWordInOperand(0);
+  auto* ast_elem_ty = ConvertType(elem_type_id);
+  if (ast_elem_ty == nullptr) {
+    return nullptr;
+  }
+  // Get the length.
+  const auto& length_info = arr_ty->length_info();
+  if (length_info.words.empty()) {
+    // The internal representation is invalid. The discriminant vector
+    // is mal-formed.
+    Fail() << "internal error: Array length info is invalid";
+    return nullptr;
+  }
+  if (length_info.words[0] !=
+      spvtools::opt::analysis::Array::LengthInfo::kConstant) {
+    Fail() << "Array type " << type_mgr_->GetId(arr_ty)
+           << " length is a specialization constant";
+    return nullptr;
+  }
+  const auto* constant = constant_mgr_->FindDeclaredConstant(length_info.id);
+  if (constant == nullptr) {
+    Fail() << "Array type " << type_mgr_->GetId(arr_ty) << " length ID "
+           << length_info.id << " does not name an OpConstant";
+    return nullptr;
+  }
+  const uint64_t num_elem = constant->GetZeroExtendedValue();
+  // For now, limit to only 32bits.
+  if (num_elem > std::numeric_limits<uint32_t>::max()) {
+    Fail() << "Array type " << type_mgr_->GetId(arr_ty)
+           << " has too many elements (more than can fit in 32 bits): "
+           << num_elem;
+    return nullptr;
+  }
+  uint32_t array_stride = 0;
+  if (!ParseArrayDecorations(arr_ty, &array_stride)) {
+    return nullptr;
+  }
+  if (remap_buffer_block_type_.count(elem_type_id)) {
+    remap_buffer_block_type_.insert(type_mgr_->GetId(arr_ty));
+  }
+  const Type* result =
+      ty_.Array(ast_elem_ty, static_cast<uint32_t>(num_elem), array_stride);
+  return MaybeGenerateAlias(type_id, arr_ty, result);
+}
+
+bool ParserImpl::ParseArrayDecorations(
+    const spvtools::opt::analysis::Type* spv_type,
+    uint32_t* array_stride) {
+  *array_stride = 0;  // Implicit stride case.
+  const auto type_id = type_mgr_->GetId(spv_type);
+  for (auto& decoration : this->GetDecorationsFor(type_id)) {
+    if (decoration.size() == 2 && decoration[0] == SpvDecorationArrayStride) {
+      const auto stride = decoration[1];
+      if (stride == 0) {
+        return Fail() << "invalid array type ID " << type_id
+                      << ": ArrayStride can't be 0";
+      }
+      *array_stride = stride;
+    } else {
+      return Fail() << "invalid array type ID " << type_id
+                    << ": unknown decoration "
+                    << (decoration.empty() ? "(empty)"
+                                           : std::to_string(decoration[0]))
+                    << " with " << decoration.size() << " total words";
+    }
+  }
+  return true;
+}
+
+const Type* ParserImpl::ConvertType(
+    uint32_t type_id,
+    const spvtools::opt::analysis::Struct* struct_ty) {
+  // Compute the struct decoration.
+  auto struct_decorations = this->GetDecorationsFor(type_id);
+  if (struct_decorations.size() == 1) {
+    const auto decoration = struct_decorations[0][0];
+    if (decoration == SpvDecorationBufferBlock) {
+      remap_buffer_block_type_.insert(type_id);
+    } else if (decoration != SpvDecorationBlock) {
+      Fail() << "struct with ID " << type_id
+             << " has unrecognized decoration: " << int(decoration);
+    }
+  } else if (struct_decorations.size() > 1) {
+    Fail() << "can't handle a struct with more than one decoration: struct "
+           << type_id << " has " << struct_decorations.size();
+    return nullptr;
+  }
+
+  // Compute members
+  ast::StructMemberList ast_members;
+  const auto members = struct_ty->element_types();
+  if (members.empty()) {
+    Fail() << "WGSL does not support empty structures. can't convert type: "
+           << def_use_mgr_->GetDef(type_id)->PrettyPrint();
+    return nullptr;
+  }
+  TypeList ast_member_types;
+  unsigned num_non_writable_members = 0;
+  for (uint32_t member_index = 0; member_index < members.size();
+       ++member_index) {
+    const auto member_type_id = type_mgr_->GetId(members[member_index]);
+    auto* ast_member_ty = ConvertType(member_type_id);
+    if (ast_member_ty == nullptr) {
+      // Already emitted diagnostics.
+      return nullptr;
+    }
+
+    ast_member_types.emplace_back(ast_member_ty);
+
+    // Scan member for built-in decorations. Some vertex built-ins are handled
+    // specially, and should not generate a structure member.
+    bool create_ast_member = true;
+    for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
+      if (decoration.empty()) {
+        Fail() << "malformed SPIR-V decoration: it's empty";
+        return nullptr;
+      }
+      if ((decoration[0] == SpvDecorationBuiltIn) && (decoration.size() > 1)) {
+        switch (decoration[1]) {
+          case SpvBuiltInPosition:
+            // Record this built-in variable specially.
+            builtin_position_.struct_type_id = type_id;
+            builtin_position_.position_member_index = member_index;
+            builtin_position_.position_member_type_id = member_type_id;
+            create_ast_member = false;  // Not part of the WGSL structure.
+            break;
+          case SpvBuiltInPointSize:  // not supported in WGSL, but ignore
+            builtin_position_.pointsize_member_index = member_index;
+            create_ast_member = false;  // Not part of the WGSL structure.
+            break;
+          case SpvBuiltInClipDistance:  // not supported in WGSL
+          case SpvBuiltInCullDistance:  // not supported in WGSL
+            create_ast_member = false;  // Not part of the WGSL structure.
+            break;
+          default:
+            Fail() << "unrecognized builtin " << decoration[1];
+            return nullptr;
+        }
+      }
+    }
+    if (!create_ast_member) {
+      // This member is decorated as a built-in, and is handled specially.
+      continue;
+    }
+
+    bool is_non_writable = false;
+    ast::AttributeList ast_member_decorations;
+    for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
+      if (IsPipelineDecoration(decoration)) {
+        // IO decorations are handled when emitting the entry point.
+        continue;
+      } else if (decoration[0] == SpvDecorationNonWritable) {
+        // WGSL doesn't represent individual members as non-writable. Instead,
+        // apply the ReadOnly access control to the containing struct if all
+        // the members are non-writable.
+        is_non_writable = true;
+      } else {
+        auto decos = ConvertMemberDecoration(type_id, member_index,
+                                             ast_member_ty, decoration);
+        for (auto* deco : decos) {
+          ast_member_decorations.emplace_back(deco);
+        }
+        if (!success_) {
+          return nullptr;
+        }
+      }
+    }
+
+    if (is_non_writable) {
+      // Count a member as non-writable only once, no matter how many
+      // NonWritable decorations are applied to it.
+      ++num_non_writable_members;
+    }
+    const auto member_name = namer_.GetMemberName(type_id, member_index);
+    auto* ast_struct_member = create<ast::StructMember>(
+        Source{}, builder_.Symbols().Register(member_name),
+        ast_member_ty->Build(builder_), std::move(ast_member_decorations));
+    ast_members.push_back(ast_struct_member);
+  }
+
+  if (ast_members.empty()) {
+    // All members were likely built-ins. Don't generate an empty AST structure.
+    return nullptr;
+  }
+
+  namer_.SuggestSanitizedName(type_id, "S");
+
+  auto name = namer_.GetName(type_id);
+
+  // Now make the struct.
+  auto sym = builder_.Symbols().Register(name);
+  auto* ast_struct = create<ast::Struct>(Source{}, sym, std::move(ast_members),
+                                         ast::AttributeList());
+  if (num_non_writable_members == members.size()) {
+    read_only_struct_types_.insert(ast_struct->name);
+  }
+  AddTypeDecl(sym, ast_struct);
+  const auto* result = ty_.Struct(sym, std::move(ast_member_types));
+  struct_id_for_symbol_[sym] = type_id;
+  return result;
+}
+
+void ParserImpl::AddTypeDecl(Symbol name, const ast::TypeDecl* decl) {
+  auto iter = declared_types_.insert(name);
+  if (iter.second) {
+    builder_.AST().AddTypeDecl(decl);
+  }
+}
+
+const Type* ParserImpl::ConvertType(uint32_t type_id,
+                                    PtrAs ptr_as,
+                                    const spvtools::opt::analysis::Pointer*) {
+  const auto* inst = def_use_mgr_->GetDef(type_id);
+  const auto pointee_type_id = inst->GetSingleWordInOperand(1);
+  const auto storage_class = SpvStorageClass(inst->GetSingleWordInOperand(0));
+
+  if (pointee_type_id == builtin_position_.struct_type_id) {
+    builtin_position_.pointer_type_id = type_id;
+    // Pipeline IO builtins map to private variables.
+    builtin_position_.storage_class = SpvStorageClassPrivate;
+    return nullptr;
+  }
+  auto* ast_elem_ty = ConvertType(pointee_type_id, PtrAs::Ptr);
+  if (ast_elem_ty == nullptr) {
+    Fail() << "SPIR-V pointer type with ID " << type_id
+           << " has invalid pointee type " << pointee_type_id;
+    return nullptr;
+  }
+
+  auto ast_storage_class = enum_converter_.ToStorageClass(storage_class);
+  if (ast_storage_class == ast::StorageClass::kInvalid) {
+    Fail() << "SPIR-V pointer type with ID " << type_id
+           << " has invalid storage class "
+           << static_cast<uint32_t>(storage_class);
+    return nullptr;
+  }
+  if (ast_storage_class == ast::StorageClass::kUniform &&
+      remap_buffer_block_type_.count(pointee_type_id)) {
+    ast_storage_class = ast::StorageClass::kStorage;
+    remap_buffer_block_type_.insert(type_id);
+  }
+
+  // Pipeline input and output variables map to private variables.
+  if (ast_storage_class == ast::StorageClass::kInput ||
+      ast_storage_class == ast::StorageClass::kOutput) {
+    ast_storage_class = ast::StorageClass::kPrivate;
+  }
+  switch (ptr_as) {
+    case PtrAs::Ref:
+      return ty_.Reference(ast_elem_ty, ast_storage_class);
+    case PtrAs::Ptr:
+      return ty_.Pointer(ast_elem_ty, ast_storage_class);
+  }
+  Fail() << "invalid value for ptr_as: " << static_cast<int>(ptr_as);
+  return nullptr;
+}
+
+bool ParserImpl::RegisterTypes() {
+  if (!success_) {
+    return false;
+  }
+
+  // First record the structure types that should have a `block` decoration
+  // in WGSL. In particular, exclude user-defined pipeline IO in a
+  // block-decorated struct.
+  for (const auto& type_or_value : module_->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    const auto& var = type_or_value;
+    const auto spirv_storage_class =
+        SpvStorageClass(var.GetSingleWordInOperand(0));
+    if ((spirv_storage_class != SpvStorageClassStorageBuffer) &&
+        (spirv_storage_class != SpvStorageClassUniform)) {
+      continue;
+    }
+    const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
+    if (ptr_type->opcode() != SpvOpTypePointer) {
+      return Fail() << "OpVariable type expected to be a pointer: "
+                    << var.PrettyPrint();
+    }
+    const auto* store_type =
+        def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+    if (store_type->opcode() == SpvOpTypeStruct) {
+      struct_types_for_buffers_.insert(store_type->result_id());
+    } else {
+      Fail() << "WGSL does not support arrays of buffers: "
+             << var.PrettyPrint();
+    }
+  }
+
+  // Now convert each type.
+  for (auto& type_or_const : module_->types_values()) {
+    const auto* type = type_mgr_->GetType(type_or_const.result_id());
+    if (type == nullptr) {
+      continue;
+    }
+    ConvertType(type_or_const.result_id());
+  }
+  // Manufacture a type for the gl_Position variable if we have to.
+  if ((builtin_position_.struct_type_id != 0) &&
+      (builtin_position_.position_member_pointer_type_id == 0)) {
+    builtin_position_.position_member_pointer_type_id =
+        type_mgr_->FindPointerToType(builtin_position_.position_member_type_id,
+                                     builtin_position_.storage_class);
+    ConvertType(builtin_position_.position_member_pointer_type_id);
+  }
+  return success_;
+}
+
+bool ParserImpl::RejectInvalidPointerRoots() {
+  if (!success_) {
+    return false;
+  }
+  for (auto& inst : module_->types_values()) {
+    if (const auto* result_type = type_mgr_->GetType(inst.type_id())) {
+      if (result_type->AsPointer()) {
+        switch (inst.opcode()) {
+          case SpvOpVariable:
+            // This is the only valid case.
+            break;
+          case SpvOpUndef:
+            return Fail() << "undef pointer is not valid: "
+                          << inst.PrettyPrint();
+          case SpvOpConstantNull:
+            return Fail() << "null pointer is not valid: "
+                          << inst.PrettyPrint();
+          default:
+            return Fail() << "module-scope pointer is not valid: "
+                          << inst.PrettyPrint();
+        }
+      }
+    }
+  }
+  return success();
+}
+
+bool ParserImpl::EmitScalarSpecConstants() {
+  if (!success_) {
+    return false;
+  }
+  // Generate a module-scope const declaration for each instruction
+  // that is OpSpecConstantTrue, OpSpecConstantFalse, or OpSpecConstant.
+  for (auto& inst : module_->types_values()) {
+    // These will be populated for a valid scalar spec constant.
+    const Type* ast_type = nullptr;
+    ast::LiteralExpression* ast_expr = nullptr;
+
+    switch (inst.opcode()) {
+      case SpvOpSpecConstantTrue:
+      case SpvOpSpecConstantFalse: {
+        ast_type = ConvertType(inst.type_id());
+        ast_expr = create<ast::BoolLiteralExpression>(
+            Source{}, inst.opcode() == SpvOpSpecConstantTrue);
+        break;
+      }
+      case SpvOpSpecConstant: {
+        ast_type = ConvertType(inst.type_id());
+        const uint32_t literal_value = inst.GetSingleWordInOperand(0);
+        if (ast_type->Is<I32>()) {
+          ast_expr = create<ast::SintLiteralExpression>(
+              Source{}, static_cast<int32_t>(literal_value));
+        } else if (ast_type->Is<U32>()) {
+          ast_expr = create<ast::UintLiteralExpression>(
+              Source{}, static_cast<uint32_t>(literal_value));
+        } else if (ast_type->Is<F32>()) {
+          float float_value;
+          // Copy the bits so we can read them as a float.
+          std::memcpy(&float_value, &literal_value, sizeof(float_value));
+          ast_expr = create<ast::FloatLiteralExpression>(Source{}, float_value);
+        } else {
+          return Fail() << " invalid result type for OpSpecConstant "
+                        << inst.PrettyPrint();
+        }
+        break;
+      }
+      default:
+        break;
+    }
+    if (ast_type && ast_expr) {
+      ast::AttributeList spec_id_decos;
+      for (const auto& deco : GetDecorationsFor(inst.result_id())) {
+        if ((deco.size() == 2) && (deco[0] == SpvDecorationSpecId)) {
+          const uint32_t id = deco[1];
+          if (id > 65535) {
+            return Fail() << "SpecId too large. WGSL override IDs must be "
+                             "between 0 and 65535: ID %"
+                          << inst.result_id() << " has SpecId " << id;
+          }
+          auto* cid = create<ast::IdAttribute>(Source{}, id);
+          spec_id_decos.push_back(cid);
+          break;
+        }
+      }
+      auto* ast_var =
+          MakeVariable(inst.result_id(), ast::StorageClass::kNone, ast_type,
+                       true, true, ast_expr, std::move(spec_id_decos));
+      if (ast_var) {
+        builder_.AST().AddGlobalVariable(ast_var);
+        scalar_spec_constants_.insert(inst.result_id());
+      }
+    }
+  }
+  return success_;
+}
+
+const Type* ParserImpl::MaybeGenerateAlias(
+    uint32_t type_id,
+    const spvtools::opt::analysis::Type* type,
+    const Type* ast_type) {
+  if (!success_) {
+    return nullptr;
+  }
+
+  // We only care about arrays, and runtime arrays.
+  switch (type->kind()) {
+    case spvtools::opt::analysis::Type::kRuntimeArray:
+      // Runtime arrays are always decorated with ArrayStride so always get a
+      // type alias.
+      namer_.SuggestSanitizedName(type_id, "RTArr");
+      break;
+    case spvtools::opt::analysis::Type::kArray:
+      // Only make a type aliase for arrays with decorations.
+      if (GetDecorationsFor(type_id).empty()) {
+        return ast_type;
+      }
+      namer_.SuggestSanitizedName(type_id, "Arr");
+      break;
+    default:
+      // Ignore constants, and any other types.
+      return ast_type;
+  }
+  auto* ast_underlying_type = ast_type;
+  if (ast_underlying_type == nullptr) {
+    Fail() << "internal error: no type registered for SPIR-V ID: " << type_id;
+    return nullptr;
+  }
+  const auto name = namer_.GetName(type_id);
+  const auto sym = builder_.Symbols().Register(name);
+  auto* ast_alias_type =
+      builder_.ty.alias(sym, ast_underlying_type->Build(builder_));
+
+  // Record this new alias as the AST type for this SPIR-V ID.
+  AddTypeDecl(sym, ast_alias_type);
+
+  return ty_.Alias(sym, ast_underlying_type);
+}
+
+bool ParserImpl::EmitModuleScopeVariables() {
+  if (!success_) {
+    return false;
+  }
+  for (const auto& type_or_value : module_->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    const auto& var = type_or_value;
+    const auto spirv_storage_class =
+        SpvStorageClass(var.GetSingleWordInOperand(0));
+
+    uint32_t type_id = var.type_id();
+    if ((type_id == builtin_position_.pointer_type_id) &&
+        ((spirv_storage_class == SpvStorageClassInput) ||
+         (spirv_storage_class == SpvStorageClassOutput))) {
+      // Skip emitting gl_PerVertex.
+      builtin_position_.per_vertex_var_id = var.result_id();
+      builtin_position_.per_vertex_var_init_id =
+          var.NumInOperands() > 1 ? var.GetSingleWordInOperand(1) : 0u;
+      continue;
+    }
+    switch (enum_converter_.ToStorageClass(spirv_storage_class)) {
+      case ast::StorageClass::kNone:
+      case ast::StorageClass::kInput:
+      case ast::StorageClass::kOutput:
+      case ast::StorageClass::kUniform:
+      case ast::StorageClass::kUniformConstant:
+      case ast::StorageClass::kStorage:
+      case ast::StorageClass::kWorkgroup:
+      case ast::StorageClass::kPrivate:
+        break;
+      default:
+        return Fail() << "invalid SPIR-V storage class "
+                      << int(spirv_storage_class)
+                      << " for module scope variable: " << var.PrettyPrint();
+    }
+    if (!success_) {
+      return false;
+    }
+    const Type* ast_type = nullptr;
+    if (spirv_storage_class == SpvStorageClassUniformConstant) {
+      // These are opaque handles: samplers or textures
+      ast_type = GetTypeForHandleVar(var);
+      if (!ast_type) {
+        return false;
+      }
+    } else {
+      ast_type = ConvertType(type_id);
+      if (ast_type == nullptr) {
+        return Fail() << "internal error: failed to register Tint AST type for "
+                         "SPIR-V type with ID: "
+                      << var.type_id();
+      }
+      if (!ast_type->Is<Pointer>()) {
+        return Fail() << "variable with ID " << var.result_id()
+                      << " has non-pointer type " << var.type_id();
+      }
+    }
+
+    auto* ast_store_type = ast_type->As<Pointer>()->type;
+    auto ast_storage_class = ast_type->As<Pointer>()->storage_class;
+    const ast::Expression* ast_constructor = nullptr;
+    if (var.NumInOperands() > 1) {
+      // SPIR-V initializers are always constants.
+      // (OpenCL also allows the ID of an OpVariable, but we don't handle that
+      // here.)
+      ast_constructor =
+          MakeConstantExpression(var.GetSingleWordInOperand(1)).expr;
+    }
+    auto* ast_var =
+        MakeVariable(var.result_id(), ast_storage_class, ast_store_type, false,
+                     false, ast_constructor, ast::AttributeList{});
+    // TODO(dneto): initializers (a.k.a. constructor expression)
+    if (ast_var) {
+      builder_.AST().AddGlobalVariable(ast_var);
+    }
+  }
+
+  // Emit gl_Position instead of gl_PerVertex
+  if (builtin_position_.per_vertex_var_id) {
+    // Make sure the variable has a name.
+    namer_.SuggestSanitizedName(builtin_position_.per_vertex_var_id,
+                                "gl_Position");
+    const ast::Expression* ast_constructor = nullptr;
+    if (builtin_position_.per_vertex_var_init_id) {
+      // The initializer is complex.
+      const auto* init =
+          def_use_mgr_->GetDef(builtin_position_.per_vertex_var_init_id);
+      switch (init->opcode()) {
+        case SpvOpConstantComposite:
+        case SpvOpSpecConstantComposite:
+          ast_constructor = MakeConstantExpression(
+                                init->GetSingleWordInOperand(
+                                    builtin_position_.position_member_index))
+                                .expr;
+          break;
+        default:
+          return Fail() << "gl_PerVertex initializer too complex. only "
+                           "OpCompositeConstruct and OpSpecConstantComposite "
+                           "are supported: "
+                        << init->PrettyPrint();
+      }
+    }
+    auto* ast_var = MakeVariable(
+        builtin_position_.per_vertex_var_id,
+        enum_converter_.ToStorageClass(builtin_position_.storage_class),
+        ConvertType(builtin_position_.position_member_type_id), false, false,
+        ast_constructor, {});
+
+    builder_.AST().AddGlobalVariable(ast_var);
+  }
+  return success_;
+}
+
+// @param var_id SPIR-V id of an OpVariable, assumed to be pointer
+// to an array
+// @returns the IntConstant for the size of the array, or nullptr
+const spvtools::opt::analysis::IntConstant* ParserImpl::GetArraySize(
+    uint32_t var_id) {
+  auto* var = def_use_mgr_->GetDef(var_id);
+  if (!var || var->opcode() != SpvOpVariable) {
+    return nullptr;
+  }
+  auto* ptr_type = def_use_mgr_->GetDef(var->type_id());
+  if (!ptr_type || ptr_type->opcode() != SpvOpTypePointer) {
+    return nullptr;
+  }
+  auto* array_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+  if (!array_type || array_type->opcode() != SpvOpTypeArray) {
+    return nullptr;
+  }
+  auto* size = constant_mgr_->FindDeclaredConstant(
+      array_type->GetSingleWordInOperand(1));
+  if (!size) {
+    return nullptr;
+  }
+  return size->AsIntConstant();
+}
+
+ast::Variable* ParserImpl::MakeVariable(uint32_t id,
+                                        ast::StorageClass sc,
+                                        const Type* storage_type,
+                                        bool is_const,
+                                        bool is_overridable,
+                                        const ast::Expression* constructor,
+                                        ast::AttributeList decorations) {
+  if (storage_type == nullptr) {
+    Fail() << "internal error: can't make ast::Variable for null type";
+    return nullptr;
+  }
+
+  ast::Access access = ast::Access::kUndefined;
+  if (sc == ast::StorageClass::kStorage) {
+    bool read_only = false;
+    if (auto* tn = storage_type->As<Named>()) {
+      read_only = read_only_struct_types_.count(tn->name) > 0;
+    }
+
+    // Apply the access(read) or access(read_write) modifier.
+    access = read_only ? ast::Access::kRead : ast::Access::kReadWrite;
+  }
+
+  // Handle variables (textures and samplers) are always in the handle
+  // storage class, so we don't mention the storage class.
+  if (sc == ast::StorageClass::kUniformConstant) {
+    sc = ast::StorageClass::kNone;
+  }
+
+  if (!ConvertDecorationsForVariable(id, &storage_type, &decorations,
+                                     sc != ast::StorageClass::kPrivate)) {
+    return nullptr;
+  }
+
+  std::string name = namer_.Name(id);
+
+  // Note: we're constructing the variable here with the *storage* type,
+  // regardless of whether this is a `let`, `override`, or `var` declaration.
+  // `var` declarations will have a resolved type of ref<storage>, but at the
+  // AST level all three are declared with the same type.
+  return create<ast::Variable>(Source{}, builder_.Symbols().Register(name), sc,
+                               access, storage_type->Build(builder_), is_const,
+                               is_overridable, constructor, decorations);
+}
+
+bool ParserImpl::ConvertDecorationsForVariable(uint32_t id,
+                                               const Type** store_type,
+                                               ast::AttributeList* decorations,
+                                               bool transfer_pipeline_io) {
+  DecorationList non_builtin_pipeline_decorations;
+  for (auto& deco : GetDecorationsFor(id)) {
+    if (deco.empty()) {
+      return Fail() << "malformed decoration on ID " << id << ": it is empty";
+    }
+    if (deco[0] == SpvDecorationBuiltIn) {
+      if (deco.size() == 1) {
+        return Fail() << "malformed BuiltIn decoration on ID " << id
+                      << ": has no operand";
+      }
+      const auto spv_builtin = static_cast<SpvBuiltIn>(deco[1]);
+      switch (spv_builtin) {
+        case SpvBuiltInPointSize:
+          special_builtins_[id] = spv_builtin;
+          return false;  // This is not an error
+        case SpvBuiltInSampleId:
+        case SpvBuiltInVertexIndex:
+        case SpvBuiltInInstanceIndex:
+        case SpvBuiltInLocalInvocationId:
+        case SpvBuiltInLocalInvocationIndex:
+        case SpvBuiltInGlobalInvocationId:
+        case SpvBuiltInWorkgroupId:
+        case SpvBuiltInNumWorkgroups:
+          // The SPIR-V variable may signed (because GLSL requires signed for
+          // some of these), but WGSL requires unsigned.  Handle specially
+          // so we always perform the conversion at load and store.
+          special_builtins_[id] = spv_builtin;
+          if (auto* forced_type = UnsignedTypeFor(*store_type)) {
+            // Requires conversion and special handling in code generation.
+            if (transfer_pipeline_io) {
+              *store_type = forced_type;
+            }
+          }
+          break;
+        case SpvBuiltInSampleMask: {
+          // In SPIR-V this is used for both input and output variable.
+          // The SPIR-V variable has store type of array of integer scalar,
+          // either signed or unsigned.
+          // WGSL requires the store type to be u32.
+          auto* size = GetArraySize(id);
+          if (!size || size->GetZeroExtendedValue() != 1) {
+            Fail() << "WGSL supports a sample mask of at most 32 bits. "
+                      "SampleMask must be an array of 1 element.";
+          }
+          special_builtins_[id] = spv_builtin;
+          if (transfer_pipeline_io) {
+            *store_type = ty_.U32();
+          }
+          break;
+        }
+        default:
+          break;
+      }
+      auto ast_builtin = enum_converter_.ToBuiltin(spv_builtin);
+      if (ast_builtin == ast::Builtin::kNone) {
+        // A diagnostic has already been emitted.
+        return false;
+      }
+      if (transfer_pipeline_io) {
+        decorations->emplace_back(
+            create<ast::BuiltinAttribute>(Source{}, ast_builtin));
+      }
+    }
+    if (transfer_pipeline_io && IsPipelineDecoration(deco)) {
+      non_builtin_pipeline_decorations.push_back(deco);
+    }
+    if (deco[0] == SpvDecorationDescriptorSet) {
+      if (deco.size() == 1) {
+        return Fail() << "malformed DescriptorSet decoration on ID " << id
+                      << ": has no operand";
+      }
+      decorations->emplace_back(create<ast::GroupAttribute>(Source{}, deco[1]));
+    }
+    if (deco[0] == SpvDecorationBinding) {
+      if (deco.size() == 1) {
+        return Fail() << "malformed Binding decoration on ID " << id
+                      << ": has no operand";
+      }
+      decorations->emplace_back(
+          create<ast::BindingAttribute>(Source{}, deco[1]));
+    }
+  }
+
+  if (transfer_pipeline_io) {
+    if (!ConvertPipelineDecorations(
+            *store_type, non_builtin_pipeline_decorations, decorations)) {
+      return false;
+    }
+  }
+
+  return success();
+}
+
+DecorationList ParserImpl::GetMemberPipelineDecorations(
+    const Struct& struct_type,
+    int member_index) {
+  // Yes, I could have used std::copy_if or std::copy_if.
+  DecorationList result;
+  for (const auto& deco : GetDecorationsForMember(
+           struct_id_for_symbol_[struct_type.name], member_index)) {
+    if (IsPipelineDecoration(deco)) {
+      result.emplace_back(deco);
+    }
+  }
+  return result;
+}
+
+const ast::Attribute* ParserImpl::SetLocation(
+    ast::AttributeList* attributes,
+    const ast::Attribute* replacement) {
+  if (!replacement) {
+    return nullptr;
+  }
+  for (auto*& attribute : *attributes) {
+    if (attribute->Is<ast::LocationAttribute>()) {
+      // Replace this location attribute with the replacement.
+      // The old one doesn't leak because it's kept in the builder's AST node
+      // list.
+      const ast::Attribute* result = nullptr;
+      result = attribute;
+      attribute = replacement;
+      return result;  // Assume there is only one such decoration.
+    }
+  }
+  // The list didn't have a location. Add it.
+  attributes->push_back(replacement);
+  return nullptr;
+}
+
+bool ParserImpl::ConvertPipelineDecorations(const Type* store_type,
+                                            const DecorationList& decorations,
+                                            ast::AttributeList* attributes) {
+  // Vulkan defaults to perspective-correct interpolation.
+  ast::InterpolationType type = ast::InterpolationType::kPerspective;
+  ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
+
+  for (const auto& deco : decorations) {
+    TINT_ASSERT(Reader, deco.size() > 0);
+    switch (deco[0]) {
+      case SpvDecorationLocation:
+        if (deco.size() != 2) {
+          return Fail() << "malformed Location decoration on ID requires one "
+                           "literal operand";
+        }
+        SetLocation(attributes,
+                    create<ast::LocationAttribute>(Source{}, deco[1]));
+        if (store_type->IsIntegerScalarOrVector()) {
+          // Default to flat interpolation for integral user-defined IO types.
+          type = ast::InterpolationType::kFlat;
+        }
+        break;
+      case SpvDecorationFlat:
+        type = ast::InterpolationType::kFlat;
+        break;
+      case SpvDecorationNoPerspective:
+        if (store_type->IsIntegerScalarOrVector()) {
+          // This doesn't capture the array or struct case.
+          return Fail() << "NoPerspective is invalid on integral IO";
+        }
+        type = ast::InterpolationType::kLinear;
+        break;
+      case SpvDecorationCentroid:
+        if (store_type->IsIntegerScalarOrVector()) {
+          // This doesn't capture the array or struct case.
+          return Fail()
+                 << "Centroid interpolation sampling is invalid on integral IO";
+        }
+        sampling = ast::InterpolationSampling::kCentroid;
+        break;
+      case SpvDecorationSample:
+        if (store_type->IsIntegerScalarOrVector()) {
+          // This doesn't capture the array or struct case.
+          return Fail()
+                 << "Sample interpolation sampling is invalid on integral IO";
+        }
+        sampling = ast::InterpolationSampling::kSample;
+        break;
+      default:
+        break;
+    }
+  }
+
+  // Apply interpolation.
+  if (type == ast::InterpolationType::kPerspective &&
+      sampling == ast::InterpolationSampling::kNone) {
+    // This is the default. Don't add a decoration.
+  } else {
+    attributes->emplace_back(create<ast::InterpolateAttribute>(type, sampling));
+  }
+
+  return success();
+}
+
+bool ParserImpl::CanMakeConstantExpression(uint32_t id) {
+  if ((id == workgroup_size_builtin_.id) ||
+      (id == workgroup_size_builtin_.x_id) ||
+      (id == workgroup_size_builtin_.y_id) ||
+      (id == workgroup_size_builtin_.z_id)) {
+    return true;
+  }
+  const auto* inst = def_use_mgr_->GetDef(id);
+  if (!inst) {
+    return false;
+  }
+  if (inst->opcode() == SpvOpUndef) {
+    return true;
+  }
+  return nullptr != constant_mgr_->FindDeclaredConstant(id);
+}
+
+TypedExpression ParserImpl::MakeConstantExpression(uint32_t id) {
+  if (!success_) {
+    return {};
+  }
+
+  // Handle the special cases for workgroup sizing.
+  if (id == workgroup_size_builtin_.id) {
+    auto x = MakeConstantExpression(workgroup_size_builtin_.x_id);
+    auto y = MakeConstantExpression(workgroup_size_builtin_.y_id);
+    auto z = MakeConstantExpression(workgroup_size_builtin_.z_id);
+    auto* ast_type = ty_.Vector(x.type, 3);
+    return {ast_type,
+            builder_.Construct(Source{}, ast_type->Build(builder_),
+                               ast::ExpressionList{x.expr, y.expr, z.expr})};
+  } else if (id == workgroup_size_builtin_.x_id) {
+    return MakeConstantExpressionForScalarSpirvConstant(
+        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
+        constant_mgr_->GetConstant(
+            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
+            {workgroup_size_builtin_.x_value}));
+  } else if (id == workgroup_size_builtin_.y_id) {
+    return MakeConstantExpressionForScalarSpirvConstant(
+        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
+        constant_mgr_->GetConstant(
+            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
+            {workgroup_size_builtin_.y_value}));
+  } else if (id == workgroup_size_builtin_.z_id) {
+    return MakeConstantExpressionForScalarSpirvConstant(
+        Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
+        constant_mgr_->GetConstant(
+            type_mgr_->GetType(workgroup_size_builtin_.component_type_id),
+            {workgroup_size_builtin_.z_value}));
+  }
+
+  // Handle the general case where a constant is already registered
+  // with the SPIR-V optimizer's analysis framework.
+  const auto* inst = def_use_mgr_->GetDef(id);
+  if (inst == nullptr) {
+    Fail() << "ID " << id << " is not a registered instruction";
+    return {};
+  }
+  auto source = GetSourceForInst(inst);
+
+  // TODO(dneto): Handle spec constants too?
+
+  auto* original_ast_type = ConvertType(inst->type_id());
+  if (original_ast_type == nullptr) {
+    return {};
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpUndef:  // Remap undef to null.
+    case SpvOpConstantNull:
+      return {original_ast_type, MakeNullValue(original_ast_type)};
+    case SpvOpConstantTrue:
+    case SpvOpConstantFalse:
+    case SpvOpConstant: {
+      const auto* spirv_const = constant_mgr_->FindDeclaredConstant(id);
+      if (spirv_const == nullptr) {
+        Fail() << "ID " << id << " is not a constant";
+        return {};
+      }
+      return MakeConstantExpressionForScalarSpirvConstant(
+          source, original_ast_type, spirv_const);
+    }
+    case SpvOpConstantComposite: {
+      // Handle vector, matrix, array, and struct
+
+      // Generate a composite from explicit components.
+      ast::ExpressionList ast_components;
+      if (!inst->WhileEachInId([&](const uint32_t* id_ref) -> bool {
+            auto component = MakeConstantExpression(*id_ref);
+            if (!component) {
+              this->Fail() << "invalid constant with ID " << *id_ref;
+              return false;
+            }
+            ast_components.emplace_back(component.expr);
+            return true;
+          })) {
+        // We've already emitted a diagnostic.
+        return {};
+      }
+      return {original_ast_type,
+              builder_.Construct(source, original_ast_type->Build(builder_),
+                                 std::move(ast_components))};
+    }
+    default:
+      break;
+  }
+  Fail() << "unhandled constant instruction " << inst->PrettyPrint();
+  return {};
+}
+
+TypedExpression ParserImpl::MakeConstantExpressionForScalarSpirvConstant(
+    Source source,
+    const Type* original_ast_type,
+    const spvtools::opt::analysis::Constant* spirv_const) {
+  auto* ast_type = original_ast_type->UnwrapAlias();
+
+  // TODO(dneto): Note: NullConstant for int, uint, float map to a regular 0.
+  // So canonicalization should map that way too.
+  // Currently "null<type>" is missing from the WGSL parser.
+  // See https://bugs.chromium.org/p/tint/issues/detail?id=34
+  if (ast_type->Is<U32>()) {
+    return {ty_.U32(),
+            create<ast::UintLiteralExpression>(source, spirv_const->GetU32())};
+  }
+  if (ast_type->Is<I32>()) {
+    return {ty_.I32(),
+            create<ast::SintLiteralExpression>(source, spirv_const->GetS32())};
+  }
+  if (ast_type->Is<F32>()) {
+    return {ty_.F32(), create<ast::FloatLiteralExpression>(
+                           source, spirv_const->GetFloat())};
+  }
+  if (ast_type->Is<Bool>()) {
+    const bool value = spirv_const->AsNullConstant()
+                           ? false
+                           : spirv_const->AsBoolConstant()->value();
+    return {ty_.Bool(), create<ast::BoolLiteralExpression>(source, value)};
+  }
+  Fail() << "expected scalar constant";
+  return {};
+}
+
+const ast::Expression* ParserImpl::MakeNullValue(const Type* type) {
+  // TODO(dneto): Use the no-operands constructor syntax when it becomes
+  // available in Tint.
+  // https://github.com/gpuweb/gpuweb/issues/685
+  // https://bugs.chromium.org/p/tint/issues/detail?id=34
+
+  if (!type) {
+    Fail() << "trying to create null value for a null type";
+    return nullptr;
+  }
+
+  auto* original_type = type;
+  type = type->UnwrapAlias();
+
+  if (type->Is<Bool>()) {
+    return create<ast::BoolLiteralExpression>(Source{}, false);
+  }
+  if (type->Is<U32>()) {
+    return create<ast::UintLiteralExpression>(Source{}, 0u);
+  }
+  if (type->Is<I32>()) {
+    return create<ast::SintLiteralExpression>(Source{}, 0);
+  }
+  if (type->Is<F32>()) {
+    return create<ast::FloatLiteralExpression>(Source{}, 0.0f);
+  }
+  if (type->IsAnyOf<Vector, Matrix, Array>()) {
+    return builder_.Construct(Source{}, type->Build(builder_));
+  }
+  if (auto* struct_ty = type->As<Struct>()) {
+    ast::ExpressionList ast_components;
+    for (auto* member : struct_ty->members) {
+      ast_components.emplace_back(MakeNullValue(member));
+    }
+    return builder_.Construct(Source{}, original_type->Build(builder_),
+                              std::move(ast_components));
+  }
+  Fail() << "can't make null value for type: " << type->TypeInfo().name;
+  return nullptr;
+}
+
+TypedExpression ParserImpl::MakeNullExpression(const Type* type) {
+  return {type, MakeNullValue(type)};
+}
+
+const Type* ParserImpl::UnsignedTypeFor(const Type* type) {
+  if (type->Is<I32>()) {
+    return ty_.U32();
+  }
+  if (auto* v = type->As<Vector>()) {
+    if (v->type->Is<I32>()) {
+      return ty_.Vector(ty_.U32(), v->size);
+    }
+  }
+  return {};
+}
+
+const Type* ParserImpl::SignedTypeFor(const Type* type) {
+  if (type->Is<U32>()) {
+    return ty_.I32();
+  }
+  if (auto* v = type->As<Vector>()) {
+    if (v->type->Is<U32>()) {
+      return ty_.Vector(ty_.I32(), v->size);
+    }
+  }
+  return {};
+}
+
+TypedExpression ParserImpl::RectifyOperandSignedness(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression&& expr) {
+  bool requires_signed = false;
+  bool requires_unsigned = false;
+  if (IsGlslExtendedInstruction(inst)) {
+    const auto extended_opcode =
+        static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
+    requires_signed = AssumesSignedOperands(extended_opcode);
+    requires_unsigned = AssumesUnsignedOperands(extended_opcode);
+  } else {
+    const auto opcode = inst.opcode();
+    requires_signed = AssumesSignedOperands(opcode);
+    requires_unsigned = AssumesUnsignedOperands(opcode);
+  }
+  if (!requires_signed && !requires_unsigned) {
+    // No conversion is required, assuming our tables are complete.
+    return std::move(expr);
+  }
+  if (!expr) {
+    Fail() << "internal error: RectifyOperandSignedness given a null expr\n";
+    return {};
+  }
+  auto* type = expr.type;
+  if (!type) {
+    Fail() << "internal error: unmapped type for: "
+           << expr.expr->TypeInfo().name << "\n";
+    return {};
+  }
+  if (requires_unsigned) {
+    if (auto* unsigned_ty = UnsignedTypeFor(type)) {
+      // Conversion is required.
+      return {unsigned_ty,
+              create<ast::BitcastExpression>(
+                  Source{}, unsigned_ty->Build(builder_), expr.expr)};
+    }
+  } else if (requires_signed) {
+    if (auto* signed_ty = SignedTypeFor(type)) {
+      // Conversion is required.
+      return {signed_ty, create<ast::BitcastExpression>(
+                             Source{}, signed_ty->Build(builder_), expr.expr)};
+    }
+  }
+  // We should not reach here.
+  return std::move(expr);
+}
+
+TypedExpression ParserImpl::RectifySecondOperandSignedness(
+    const spvtools::opt::Instruction& inst,
+    const Type* first_operand_type,
+    TypedExpression&& second_operand_expr) {
+  if ((first_operand_type != second_operand_expr.type) &&
+      AssumesSecondOperandSignednessMatchesFirstOperand(inst.opcode())) {
+    // Conversion is required.
+    return {first_operand_type,
+            create<ast::BitcastExpression>(Source{},
+                                           first_operand_type->Build(builder_),
+                                           second_operand_expr.expr)};
+  }
+  // No conversion necessary.
+  return std::move(second_operand_expr);
+}
+
+const Type* ParserImpl::ForcedResultType(const spvtools::opt::Instruction& inst,
+                                         const Type* first_operand_type) {
+  const auto opcode = inst.opcode();
+  if (AssumesResultSignednessMatchesFirstOperand(opcode)) {
+    return first_operand_type;
+  }
+  if (IsGlslExtendedInstruction(inst)) {
+    const auto extended_opcode =
+        static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
+    if (AssumesResultSignednessMatchesFirstOperand(extended_opcode)) {
+      return first_operand_type;
+    }
+  }
+  return nullptr;
+}
+
+const Type* ParserImpl::GetSignedIntMatchingShape(const Type* other) {
+  if (other == nullptr) {
+    Fail() << "no type provided";
+  }
+  if (other->Is<F32>() || other->Is<U32>() || other->Is<I32>()) {
+    return ty_.I32();
+  }
+  if (auto* vec_ty = other->As<Vector>()) {
+    return ty_.Vector(ty_.I32(), vec_ty->size);
+  }
+  Fail() << "required numeric scalar or vector, but got "
+         << other->TypeInfo().name;
+  return nullptr;
+}
+
+const Type* ParserImpl::GetUnsignedIntMatchingShape(const Type* other) {
+  if (other == nullptr) {
+    Fail() << "no type provided";
+    return nullptr;
+  }
+  if (other->Is<F32>() || other->Is<U32>() || other->Is<I32>()) {
+    return ty_.U32();
+  }
+  if (auto* vec_ty = other->As<Vector>()) {
+    return ty_.Vector(ty_.U32(), vec_ty->size);
+  }
+  Fail() << "required numeric scalar or vector, but got "
+         << other->TypeInfo().name;
+  return nullptr;
+}
+
+TypedExpression ParserImpl::RectifyForcedResultType(
+    TypedExpression expr,
+    const spvtools::opt::Instruction& inst,
+    const Type* first_operand_type) {
+  auto* forced_result_ty = ForcedResultType(inst, first_operand_type);
+  if ((!forced_result_ty) || (forced_result_ty == expr.type)) {
+    return expr;
+  }
+  return {expr.type, create<ast::BitcastExpression>(
+                         Source{}, expr.type->Build(builder_), expr.expr)};
+}
+
+TypedExpression ParserImpl::AsUnsigned(TypedExpression expr) {
+  if (expr.type && expr.type->IsSignedScalarOrVector()) {
+    auto* new_type = GetUnsignedIntMatchingShape(expr.type);
+    return {new_type, create<ast::BitcastExpression>(
+                          Source{}, new_type->Build(builder_), expr.expr)};
+  }
+  return expr;
+}
+
+TypedExpression ParserImpl::AsSigned(TypedExpression expr) {
+  if (expr.type && expr.type->IsUnsignedScalarOrVector()) {
+    auto* new_type = GetSignedIntMatchingShape(expr.type);
+    return {new_type, create<ast::BitcastExpression>(
+                          Source{}, new_type->Build(builder_), expr.expr)};
+  }
+  return expr;
+}
+
+bool ParserImpl::EmitFunctions() {
+  if (!success_) {
+    return false;
+  }
+  for (const auto* f : topologically_ordered_functions_) {
+    if (!success_) {
+      return false;
+    }
+
+    auto id = f->result_id();
+    auto it = function_to_ep_info_.find(id);
+    if (it == function_to_ep_info_.end()) {
+      FunctionEmitter emitter(this, *f, nullptr);
+      success_ = emitter.Emit();
+    } else {
+      for (const auto& ep : it->second) {
+        FunctionEmitter emitter(this, *f, &ep);
+        success_ = emitter.Emit();
+        if (!success_) {
+          return false;
+        }
+      }
+    }
+  }
+  return success_;
+}
+
+const spvtools::opt::Instruction*
+ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id,
+                                                bool follow_image) {
+  auto saved_id = id;
+  auto local_fail = [this, saved_id, id,
+                     follow_image]() -> const spvtools::opt::Instruction* {
+    const auto* inst = def_use_mgr_->GetDef(id);
+    Fail() << "Could not find memory object declaration for the "
+           << (follow_image ? "image" : "sampler") << " underlying id " << id
+           << " (from original id " << saved_id << ") "
+           << (inst ? inst->PrettyPrint() : std::string());
+    return nullptr;
+  };
+
+  auto& memo_table =
+      (follow_image ? mem_obj_decl_image_ : mem_obj_decl_sampler_);
+
+  // Use a visited set to defend against bad input which might have long
+  // chains or even loops.
+  std::unordered_set<uint32_t> visited;
+
+  // Trace backward in the SSA data flow until we hit a memory object
+  // declaration.
+  while (true) {
+    auto where = memo_table.find(id);
+    if (where != memo_table.end()) {
+      return where->second;
+    }
+    // Protect against loops.
+    auto visited_iter = visited.find(id);
+    if (visited_iter != visited.end()) {
+      // We've hit a loop. Mark all the visited nodes
+      // as dead ends.
+      for (auto iter : visited) {
+        memo_table[iter] = nullptr;
+      }
+      return nullptr;
+    }
+    visited.insert(id);
+
+    const auto* inst = def_use_mgr_->GetDef(id);
+    if (inst == nullptr) {
+      return local_fail();
+    }
+    switch (inst->opcode()) {
+      case SpvOpFunctionParameter:
+      case SpvOpVariable:
+        // We found the memory object declaration.
+        // Remember it as the answer for the whole path.
+        for (auto iter : visited) {
+          memo_table[iter] = inst;
+        }
+        return inst;
+      case SpvOpLoad:
+        // Follow the pointer being loaded
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpCopyObject:
+        // Follow the object being copied.
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        // Follow the base pointer.
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpSampledImage:
+        // Follow the image or the sampler, depending on the follow_image
+        // parameter.
+        id = inst->GetSingleWordInOperand(follow_image ? 0 : 1);
+        break;
+      case SpvOpImage:
+        // Follow the sampled image
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        // Can't trace further.
+        // Remember it as the answer for the whole path.
+        for (auto iter : visited) {
+          memo_table[iter] = nullptr;
+        }
+        return nullptr;
+    }
+  }
+}
+
+const spvtools::opt::Instruction*
+ParserImpl::GetSpirvTypeForHandleMemoryObjectDeclaration(
+    const spvtools::opt::Instruction& var) {
+  if (!success()) {
+    return nullptr;
+  }
+  // The WGSL handle type is determined by looking at information from
+  // several sources:
+  //    - the usage of the handle by image access instructions
+  //    - the SPIR-V type declaration
+  // Each source does not have enough information to completely determine
+  // the result.
+
+  // Messages are phrased in terms of images and samplers because those
+  // are the only SPIR-V handles supported by WGSL.
+
+  // Get the SPIR-V handle type.
+  const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
+  if (!ptr_type || (ptr_type->opcode() != SpvOpTypePointer)) {
+    Fail() << "Invalid type for variable or function parameter "
+           << var.PrettyPrint();
+    return nullptr;
+  }
+  const auto* raw_handle_type =
+      def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+  if (!raw_handle_type) {
+    Fail() << "Invalid pointer type for variable or function parameter "
+           << var.PrettyPrint();
+    return nullptr;
+  }
+  switch (raw_handle_type->opcode()) {
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      // The expected cases.
+      break;
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      Fail()
+          << "arrays of textures or samplers are not supported in WGSL; can't "
+             "translate variable or function parameter: "
+          << var.PrettyPrint();
+      return nullptr;
+    case SpvOpTypeSampledImage:
+      Fail() << "WGSL does not support combined image-samplers: "
+             << var.PrettyPrint();
+      return nullptr;
+    default:
+      Fail() << "invalid type for image or sampler variable or function "
+                "parameter: "
+             << var.PrettyPrint();
+      return nullptr;
+  }
+  return raw_handle_type;
+}
+
+const Pointer* ParserImpl::GetTypeForHandleVar(
+    const spvtools::opt::Instruction& var) {
+  auto where = handle_type_.find(&var);
+  if (where != handle_type_.end()) {
+    return where->second;
+  }
+
+  const spvtools::opt::Instruction* raw_handle_type =
+      GetSpirvTypeForHandleMemoryObjectDeclaration(var);
+  if (!raw_handle_type) {
+    return nullptr;
+  }
+
+  // The variable could be a sampler or image.
+  // Where possible, determine which one it is from the usage inferred
+  // for the variable.
+  Usage usage = handle_usage_[&var];
+  if (!usage.IsValid()) {
+    Fail() << "Invalid sampler or texture usage for variable "
+           << var.PrettyPrint() << "\n"
+           << usage;
+    return nullptr;
+  }
+  // Infer a handle type, if usage didn't already tell us.
+  if (!usage.IsComplete()) {
+    // In SPIR-V you could statically reference a texture or sampler without
+    // using it in a way that gives us a clue on how to declare it.  Look inside
+    // the store type to infer a usage.
+    if (raw_handle_type->opcode() == SpvOpTypeSampler) {
+      usage.AddSampler();
+    } else {
+      // It's a texture.
+      if (raw_handle_type->NumInOperands() != 7) {
+        Fail() << "invalid SPIR-V image type: expected 7 operands: "
+               << raw_handle_type->PrettyPrint();
+        return nullptr;
+      }
+      const auto sampled_param = raw_handle_type->GetSingleWordInOperand(5);
+      const auto format_param = raw_handle_type->GetSingleWordInOperand(6);
+      // Only storage images have a format.
+      if ((format_param != SpvImageFormatUnknown) ||
+          sampled_param == 2 /* without sampler */) {
+        // Get NonWritable and NonReadable attributes of the variable.
+        bool is_nonwritable = false;
+        bool is_nonreadable = false;
+        for (const auto& deco : GetDecorationsFor(var.result_id())) {
+          if (deco.size() != 1) {
+            continue;
+          }
+          if (deco[0] == SpvDecorationNonWritable) {
+            is_nonwritable = true;
+          }
+          if (deco[0] == SpvDecorationNonReadable) {
+            is_nonreadable = true;
+          }
+        }
+        if (is_nonwritable && is_nonreadable) {
+          Fail() << "storage image variable is both NonWritable and NonReadable"
+                 << var.PrettyPrint();
+        }
+        if (!is_nonwritable && !is_nonreadable) {
+          Fail()
+              << "storage image variable is neither NonWritable nor NonReadable"
+              << var.PrettyPrint();
+        }
+        // Let's make it one of the storage textures.
+        if (is_nonwritable) {
+          usage.AddStorageReadTexture();
+        } else {
+          usage.AddStorageWriteTexture();
+        }
+      } else {
+        usage.AddSampledTexture();
+      }
+    }
+    if (!usage.IsComplete()) {
+      Fail()
+          << "internal error: should have inferred a complete handle type. got "
+          << usage.to_str();
+      return nullptr;
+    }
+  }
+
+  // Construct the Tint handle type.
+  const Type* ast_store_type = nullptr;
+  if (usage.IsSampler()) {
+    ast_store_type = ty_.Sampler(usage.IsComparisonSampler()
+                                     ? ast::SamplerKind::kComparisonSampler
+                                     : ast::SamplerKind::kSampler);
+  } else if (usage.IsTexture()) {
+    const spvtools::opt::analysis::Image* image_type =
+        type_mgr_->GetType(raw_handle_type->result_id())->AsImage();
+    if (!image_type) {
+      Fail() << "internal error: Couldn't look up image type"
+             << raw_handle_type->PrettyPrint();
+      return nullptr;
+    }
+
+    if (image_type->is_arrayed()) {
+      // Give a nicer error message here, where we have the offending variable
+      // in hand, rather than inside the enum converter.
+      switch (image_type->dim()) {
+        case SpvDim2D:
+        case SpvDimCube:
+          break;
+        default:
+          Fail() << "WGSL arrayed textures must be 2d_array or cube_array: "
+                    "invalid multisampled texture variable "
+                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+          return nullptr;
+      }
+    }
+
+    const ast::TextureDimension dim =
+        enum_converter_.ToDim(image_type->dim(), image_type->is_arrayed());
+    if (dim == ast::TextureDimension::kNone) {
+      return nullptr;
+    }
+
+    // WGSL textures are always formatted.  Unformatted textures are always
+    // sampled.
+    if (usage.IsSampledTexture() || usage.IsStorageReadTexture() ||
+        (image_type->format() == SpvImageFormatUnknown)) {
+      // Make a sampled texture type.
+      auto* ast_sampled_component_type =
+          ConvertType(raw_handle_type->GetSingleWordInOperand(0));
+
+      // Vulkan ignores the depth parameter on OpImage, so pay attention to the
+      // usage as well.  That is, it's valid for a Vulkan shader to use an
+      // OpImage variable with an OpImage*Dref* instruction.  In WGSL we must
+      // treat that as a depth texture.
+      if (image_type->depth() || usage.IsDepthTexture()) {
+        if (image_type->is_multisampled()) {
+          ast_store_type = ty_.DepthMultisampledTexture(dim);
+        } else {
+          ast_store_type = ty_.DepthTexture(dim);
+        }
+      } else if (image_type->is_multisampled()) {
+        if (dim != ast::TextureDimension::k2d) {
+          Fail() << "WGSL multisampled textures must be 2d and non-arrayed: "
+                    "invalid multisampled texture variable "
+                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+        }
+        // Multisampled textures are never depth textures.
+        ast_store_type =
+            ty_.MultisampledTexture(dim, ast_sampled_component_type);
+      } else {
+        ast_store_type = ty_.SampledTexture(dim, ast_sampled_component_type);
+      }
+    } else {
+      const auto access = ast::Access::kWrite;
+      const auto format = enum_converter_.ToTexelFormat(image_type->format());
+      if (format == ast::TexelFormat::kNone) {
+        return nullptr;
+      }
+      ast_store_type = ty_.StorageTexture(dim, format, access);
+    }
+  } else {
+    Fail() << "unsupported: UniformConstant variable is not a recognized "
+              "sampler or texture"
+           << var.PrettyPrint();
+    return nullptr;
+  }
+
+  // Form the pointer type.
+  auto* result =
+      ty_.Pointer(ast_store_type, ast::StorageClass::kUniformConstant);
+  // Remember it for later.
+  handle_type_[&var] = result;
+  return result;
+}
+
+const Type* ParserImpl::GetComponentTypeForFormat(ast::TexelFormat format) {
+  switch (format) {
+    case ast::TexelFormat::kR32Uint:
+    case ast::TexelFormat::kRgba8Uint:
+    case ast::TexelFormat::kRg32Uint:
+    case ast::TexelFormat::kRgba16Uint:
+    case ast::TexelFormat::kRgba32Uint:
+      return ty_.U32();
+
+    case ast::TexelFormat::kR32Sint:
+    case ast::TexelFormat::kRgba8Sint:
+    case ast::TexelFormat::kRg32Sint:
+    case ast::TexelFormat::kRgba16Sint:
+    case ast::TexelFormat::kRgba32Sint:
+      return ty_.I32();
+
+    case ast::TexelFormat::kRgba8Unorm:
+    case ast::TexelFormat::kRgba8Snorm:
+    case ast::TexelFormat::kR32Float:
+    case ast::TexelFormat::kRg32Float:
+    case ast::TexelFormat::kRgba16Float:
+    case ast::TexelFormat::kRgba32Float:
+      return ty_.F32();
+    default:
+      break;
+  }
+  Fail() << "unknown format " << int(format);
+  return nullptr;
+}
+
+unsigned ParserImpl::GetChannelCountForFormat(ast::TexelFormat format) {
+  switch (format) {
+    case ast::TexelFormat::kR32Float:
+    case ast::TexelFormat::kR32Sint:
+    case ast::TexelFormat::kR32Uint:
+      // One channel
+      return 1;
+
+    case ast::TexelFormat::kRg32Float:
+    case ast::TexelFormat::kRg32Sint:
+    case ast::TexelFormat::kRg32Uint:
+      // Two channels
+      return 2;
+
+    case ast::TexelFormat::kRgba16Float:
+    case ast::TexelFormat::kRgba16Sint:
+    case ast::TexelFormat::kRgba16Uint:
+    case ast::TexelFormat::kRgba32Float:
+    case ast::TexelFormat::kRgba32Sint:
+    case ast::TexelFormat::kRgba32Uint:
+    case ast::TexelFormat::kRgba8Sint:
+    case ast::TexelFormat::kRgba8Snorm:
+    case ast::TexelFormat::kRgba8Uint:
+    case ast::TexelFormat::kRgba8Unorm:
+      // Four channels
+      return 4;
+
+    default:
+      break;
+  }
+  Fail() << "unknown format " << int(format);
+  return 0;
+}
+
+const Type* ParserImpl::GetTexelTypeForFormat(ast::TexelFormat format) {
+  const auto* component_type = GetComponentTypeForFormat(format);
+  if (!component_type) {
+    return nullptr;
+  }
+  return ty_.Vector(component_type, 4);
+}
+
+bool ParserImpl::RegisterHandleUsage() {
+  if (!success_) {
+    return false;
+  }
+
+  // Map a function ID to the list of its function parameter instructions, in
+  // order.
+  std::unordered_map<uint32_t, std::vector<const spvtools::opt::Instruction*>>
+      function_params;
+  for (const auto* f : topologically_ordered_functions_) {
+    // Record the instructions defining this function's parameters.
+    auto& params = function_params[f->result_id()];
+    f->ForEachParam([&params](const spvtools::opt::Instruction* param) {
+      params.push_back(param);
+    });
+  }
+
+  // Returns the memory object declaration for an image underlying the first
+  // operand of the given image instruction.
+  auto get_image = [this](const spvtools::opt::Instruction& image_inst) {
+    return this->GetMemoryObjectDeclarationForHandle(
+        image_inst.GetSingleWordInOperand(0), true);
+  };
+  // Returns the memory object declaration for a sampler underlying the first
+  // operand of the given image instruction.
+  auto get_sampler = [this](const spvtools::opt::Instruction& image_inst) {
+    return this->GetMemoryObjectDeclarationForHandle(
+        image_inst.GetSingleWordInOperand(0), false);
+  };
+
+  // Scan the bodies of functions for image operations, recording their implied
+  // usage properties on the memory object declarations (i.e. variables or
+  // function parameters).  We scan the functions in an order so that callees
+  // precede callers. That way the usage on a function parameter is already
+  // computed before we see the call to that function.  So when we reach
+  // a function call, we can add the usage from the callee formal parameters.
+  for (const auto* f : topologically_ordered_functions_) {
+    for (const auto& bb : *f) {
+      for (const auto& inst : bb) {
+        switch (inst.opcode()) {
+            // Single texel reads and writes
+
+          case SpvOpImageRead:
+            handle_usage_[get_image(inst)].AddStorageReadTexture();
+            break;
+          case SpvOpImageWrite:
+            handle_usage_[get_image(inst)].AddStorageWriteTexture();
+            break;
+          case SpvOpImageFetch:
+            handle_usage_[get_image(inst)].AddSampledTexture();
+            break;
+
+            // Sampling and gathering from a sampled image.
+
+          case SpvOpImageSampleImplicitLod:
+          case SpvOpImageSampleExplicitLod:
+          case SpvOpImageSampleProjImplicitLod:
+          case SpvOpImageSampleProjExplicitLod:
+          case SpvOpImageGather:
+            handle_usage_[get_image(inst)].AddSampledTexture();
+            handle_usage_[get_sampler(inst)].AddSampler();
+            break;
+          case SpvOpImageSampleDrefImplicitLod:
+          case SpvOpImageSampleDrefExplicitLod:
+          case SpvOpImageSampleProjDrefImplicitLod:
+          case SpvOpImageSampleProjDrefExplicitLod:
+          case SpvOpImageDrefGather:
+            // Depth reference access implies usage as a depth texture, which
+            // in turn is a sampled texture.
+            handle_usage_[get_image(inst)].AddDepthTexture();
+            handle_usage_[get_sampler(inst)].AddComparisonSampler();
+            break;
+
+            // Image queries
+
+          case SpvOpImageQuerySizeLod:
+            // Vulkan requires Sampled=1 for this. SPIR-V already requires MS=0.
+            handle_usage_[get_image(inst)].AddSampledTexture();
+            break;
+          case SpvOpImageQuerySize:
+            // Applies to either MS=1 or Sampled=0 or 2.
+            // So we can't force it to be multisampled, or storage image.
+            break;
+          case SpvOpImageQueryLod:
+            handle_usage_[get_image(inst)].AddSampledTexture();
+            handle_usage_[get_sampler(inst)].AddSampler();
+            break;
+          case SpvOpImageQueryLevels:
+            // We can't tell anything more than that it's an image.
+            handle_usage_[get_image(inst)].AddTexture();
+            break;
+          case SpvOpImageQuerySamples:
+            handle_usage_[get_image(inst)].AddMultisampledTexture();
+            break;
+
+            // Function calls
+
+          case SpvOpFunctionCall: {
+            // Propagate handle usages from callee function formal parameters to
+            // the matching caller parameters.  This is where we rely on the
+            // fact that callees have been processed earlier in the flow.
+            const auto num_in_operands = inst.NumInOperands();
+            // The first operand of the call is the function ID.
+            // The remaining operands are the operands to the function.
+            if (num_in_operands < 1) {
+              return Fail() << "Call instruction must have at least one operand"
+                            << inst.PrettyPrint();
+            }
+            const auto function_id = inst.GetSingleWordInOperand(0);
+            const auto& formal_params = function_params[function_id];
+            if (formal_params.size() != (num_in_operands - 1)) {
+              return Fail() << "Called function has " << formal_params.size()
+                            << " parameters, but function call has "
+                            << (num_in_operands - 1) << " parameters"
+                            << inst.PrettyPrint();
+            }
+            for (uint32_t i = 1; i < num_in_operands; ++i) {
+              auto where = handle_usage_.find(formal_params[i - 1]);
+              if (where == handle_usage_.end()) {
+                // We haven't recorded any handle usage on the formal parameter.
+                continue;
+              }
+              const Usage& formal_param_usage = where->second;
+              const auto operand_id = inst.GetSingleWordInOperand(i);
+              const auto* operand_as_sampler =
+                  GetMemoryObjectDeclarationForHandle(operand_id, false);
+              const auto* operand_as_image =
+                  GetMemoryObjectDeclarationForHandle(operand_id, true);
+              if (operand_as_sampler) {
+                handle_usage_[operand_as_sampler].Add(formal_param_usage);
+              }
+              if (operand_as_image &&
+                  (operand_as_image != operand_as_sampler)) {
+                handle_usage_[operand_as_image].Add(formal_param_usage);
+              }
+            }
+            break;
+          }
+
+          default:
+            break;
+        }
+      }
+    }
+  }
+  return success_;
+}
+
+Usage ParserImpl::GetHandleUsage(uint32_t id) const {
+  const auto where = handle_usage_.find(def_use_mgr_->GetDef(id));
+  if (where != handle_usage_.end()) {
+    return where->second;
+  }
+  return Usage();
+}
+
+const spvtools::opt::Instruction* ParserImpl::GetInstructionForTest(
+    uint32_t id) const {
+  return def_use_mgr_ ? def_use_mgr_->GetDef(id) : nullptr;
+}
+
+std::string ParserImpl::GetMemberName(const Struct& struct_type,
+                                      int member_index) {
+  auto where = struct_id_for_symbol_.find(struct_type.name);
+  if (where == struct_id_for_symbol_.end()) {
+    Fail() << "no structure type registered for symbol";
+    return "";
+  }
+  return namer_.GetMemberName(where->second, member_index);
+}
+
+WorkgroupSizeInfo::WorkgroupSizeInfo() = default;
+
+WorkgroupSizeInfo::~WorkgroupSizeInfo() = default;
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl.h b/src/tint/reader/spirv/parser_impl.h
new file mode 100644
index 0000000..ee31004
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl.h
@@ -0,0 +1,888 @@
+// Copyright 2020 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_READER_SPIRV_PARSER_IMPL_H_
+#define SRC_TINT_READER_SPIRV_PARSER_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#if TINT_BUILD_SPV_READER
+#include "source/opt/ir_context.h"
+#endif
+
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/reader.h"
+#include "src/tint/reader/spirv/entry_point_info.h"
+#include "src/tint/reader/spirv/enum_converter.h"
+#include "src/tint/reader/spirv/namer.h"
+#include "src/tint/reader/spirv/parser_type.h"
+#include "src/tint/reader/spirv/usage.h"
+
+/// This is the implementation of the SPIR-V parser for Tint.
+
+/// Notes on terminology:
+///
+/// A WGSL "handle" is an opaque object used for accessing a resource via
+/// special builtins.  In SPIR-V, a handle is stored a variable in the
+/// UniformConstant storage class.  The handles supported by SPIR-V are:
+///   - images, both sampled texture and storage image
+///   - samplers
+///   - combined image+sampler
+///   - acceleration structures for raytracing.
+///
+/// WGSL only supports samplers and images, but calls images "textures".
+/// When emitting errors, we aim to use terminology most likely to be
+/// familiar to Vulkan SPIR-V developers.  We will tend to use "image"
+/// and "sampler" instead of "handle".
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// The binary representation of a SPIR-V decoration enum followed by its
+/// operands, if any.
+/// Example:   { SpvDecorationBlock }
+/// Example:   { SpvDecorationArrayStride, 16 }
+using Decoration = std::vector<uint32_t>;
+using DecorationList = std::vector<Decoration>;
+
+/// An AST expression with its type.
+struct TypedExpression {
+  /// Constructor
+  TypedExpression();
+
+  /// Copy constructor
+  TypedExpression(const TypedExpression&);
+
+  /// Constructor
+  /// @param type_in the type of the expression
+  /// @param expr_in the expression
+  TypedExpression(const Type* type_in, const ast::Expression* expr_in);
+
+  /// Assignment operator
+  /// @returns this TypedExpression
+  TypedExpression& operator=(const TypedExpression&);
+
+  /// @returns true if both type and expr are not nullptr
+  operator bool() const { return type && expr; }
+
+  /// The type
+  const Type* type = nullptr;
+  /// The expression
+  const ast::Expression* expr = nullptr;
+};
+
+/// Info about the WorkgroupSize builtin.
+struct WorkgroupSizeInfo {
+  /// Constructor
+  WorkgroupSizeInfo();
+  /// Destructor
+  ~WorkgroupSizeInfo();
+  /// The SPIR-V ID of the WorkgroupSize builtin, if any.
+  uint32_t id = 0u;
+  /// The SPIR-V type ID of the WorkgroupSize builtin, if any.
+  uint32_t type_id = 0u;
+  /// The SPIR-V type IDs of the x, y, and z components.
+  uint32_t component_type_id = 0u;
+  /// The SPIR-V IDs of the X, Y, and Z components of the workgroup size
+  /// builtin.
+  uint32_t x_id = 0u;  /// X component ID
+  uint32_t y_id = 0u;  /// Y component ID
+  uint32_t z_id = 0u;  /// Z component ID
+  /// The effective workgroup size, if this is a compute shader.
+  uint32_t x_value = 0u;  /// X workgroup size
+  uint32_t y_value = 0u;  /// Y workgroup size
+  uint32_t z_value = 0u;  /// Z workgroup size
+};
+
+/// Parser implementation for SPIR-V.
+class ParserImpl : Reader {
+ public:
+  /// Creates a new parser
+  /// @param input the input data to parse
+  explicit ParserImpl(const std::vector<uint32_t>& input);
+  /// Destructor
+  ~ParserImpl() override;
+
+  /// Run the parser
+  /// @returns true if the parse was successful, false otherwise.
+  bool Parse() override;
+
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
+  Program program() override;
+
+  /// @returns a reference to the internal builder, without building the
+  /// program. To be used only for testing.
+  ProgramBuilder& builder() { return builder_; }
+
+  /// @returns the type manager
+  TypeManager& type_manager() { return ty_; }
+
+  /// Logs failure, ands return a failure stream to accumulate diagnostic
+  /// messages. By convention, a failure should only be logged along with
+  /// a non-empty string diagnostic.
+  /// @returns the failure stream
+  FailStream& Fail() {
+    success_ = false;
+    return fail_stream_;
+  }
+
+  /// @return true if failure has not yet occurred
+  bool success() const { return success_; }
+
+  /// @returns the accumulated error string
+  const std::string error() { return errors_.str(); }
+
+  /// Builds an internal representation of the SPIR-V binary,
+  /// and parses it into a Tint AST module.  Diagnostics are emitted
+  /// to the error stream.
+  /// @returns true if it was successful.
+  bool BuildAndParseInternalModule() {
+    return BuildInternalModule() && ParseInternalModule();
+  }
+  /// Builds an internal representation of the SPIR-V binary,
+  /// and parses the module, except functions, into a Tint AST module.
+  /// Diagnostics are emitted to the error stream.
+  /// @returns true if it was successful.
+  bool BuildAndParseInternalModuleExceptFunctions() {
+    return BuildInternalModule() && ParseInternalModuleExceptFunctions();
+  }
+
+  /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450"
+  /// extended instruction set.
+  const std::unordered_set<uint32_t>& glsl_std_450_imports() const {
+    return glsl_std_450_imports_;
+  }
+
+  /// Desired handling of SPIR-V pointers by ConvertType()
+  enum class PtrAs {
+    // SPIR-V pointer is converted to a spirv::Pointer
+    Ptr,
+    // SPIR-V pointer is converted to a spirv::Reference
+    Ref
+  };
+
+  /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup.
+  /// If the type is only used for builtins, then register that specially,
+  /// and return null.  If the type is a sampler, image, or sampled image, then
+  /// return the Void type, because those opaque types are handled in a
+  /// different way.
+  /// On failure, logs an error and returns null.  This should only be called
+  /// after the internal representation of the module has been built.
+  /// @param type_id the SPIR-V ID of a type.
+  /// @param ptr_as if the SPIR-V type is a pointer and ptr_as is equal to
+  /// PtrAs::Ref then a Reference will be returned, otherwise a Pointer will be
+  /// returned for a SPIR-V pointer
+  /// @returns a Tint type, or nullptr
+  const Type* ConvertType(uint32_t type_id, PtrAs ptr_as = PtrAs::Ptr);
+
+  /// Emits an alias type declaration for array or runtime-sized array type,
+  /// when needed to distinguish between differently-decorated underlying types.
+  /// Updates the mapping of the SPIR-V type ID to the alias type.
+  /// This is a no-op if the parser has already failed.
+  /// @param type_id the SPIR-V ID for the type
+  /// @param type the type that might get an alias
+  /// @param ast_type the ast type that might get an alias
+  /// @returns an alias type or `ast_type` if no alias was created
+  const Type* MaybeGenerateAlias(uint32_t type_id,
+                                 const spvtools::opt::analysis::Type* type,
+                                 const Type* ast_type);
+
+  /// Adds `decl` as a declared type if it hasn't been added yet.
+  /// @param name the type's unique name
+  /// @param decl the type declaration to add
+  void AddTypeDecl(Symbol name, const ast::TypeDecl* decl);
+
+  /// @returns the fail stream object
+  FailStream& fail_stream() { return fail_stream_; }
+  /// @returns the namer object
+  Namer& namer() { return namer_; }
+  /// @returns a borrowed pointer to the internal representation of the module.
+  /// This is null until BuildInternalModule has been called.
+  spvtools::opt::IRContext* ir_context() { return ir_context_.get(); }
+
+  /// Gets the list of unique decorations for a SPIR-V result ID.  Returns an
+  /// empty vector if the ID is not a result ID, or if no decorations target
+  /// that ID. The internal representation must have already been built.
+  /// Ignores decorations that have no effect in graphics APIs, e.g. Restrict
+  /// and RestrictPointer.
+  /// @param id SPIR-V ID
+  /// @returns the list of decorations on the given ID
+  DecorationList GetDecorationsFor(uint32_t id) const;
+  /// Gets the list of unique decorations for the member of a struct.  Returns
+  /// an empty list if the `id` is not the ID of a struct, or if the member
+  /// index is out of range, or if the target member has no decorations. The
+  /// internal representation must have already been built.
+  /// Ignores decorations that have no effect in graphics APIs, e.g. Restrict
+  /// and RestrictPointer.
+  /// @param id SPIR-V ID of a struct
+  /// @param member_index the member within the struct
+  /// @returns the list of decorations on the member
+  DecorationList GetDecorationsForMember(uint32_t id,
+                                         uint32_t member_index) const;
+
+  /// Converts SPIR-V decorations for the variable with the given ID.
+  /// Registers the IDs of variables that require special handling by code
+  /// generation.  If the WGSL type differs from the store type for SPIR-V,
+  /// then the `type` parameter is updated.  Returns false on failure (with
+  /// a diagnostic), or when the variable should not be emitted, e.g. for a
+  /// PointSize builtin.
+  /// @param id the ID of the SPIR-V variable
+  /// @param store_type the WGSL store type for the variable, which should be
+  /// prepopulatd
+  /// @param attributes the attribute list to populate
+  /// @param transfer_pipeline_io true if pipeline IO decorations (builtins,
+  /// or locations) will update the store type and the decorations list
+  /// @returns false when the variable should not be emitted as a variable
+  bool ConvertDecorationsForVariable(uint32_t id,
+                                     const Type** store_type,
+                                     ast::AttributeList* attributes,
+                                     bool transfer_pipeline_io);
+
+  /// Converts SPIR-V decorations for pipeline IO into AST decorations.
+  /// @param store_type the store type for the variable or member
+  /// @param decorations the SPIR-V interpolation decorations
+  /// @param attributes the attribute list to populate.
+  /// @returns false if conversion fails
+  bool ConvertPipelineDecorations(const Type* store_type,
+                                  const DecorationList& decorations,
+                                  ast::AttributeList* attributes);
+
+  /// Updates the attribute list, placing a non-null location decoration into
+  /// the list, replacing an existing one if it exists. Does nothing if the
+  /// replacement is nullptr.
+  /// Assumes the list contains at most one Location decoration.
+  /// @param decos the attribute list to modify
+  /// @param replacement the location decoration to place into the list
+  /// @returns the location decoration that was replaced, if one was replaced,
+  /// or null otherwise.
+  const ast::Attribute* SetLocation(ast::AttributeList* decos,
+                                    const ast::Attribute* replacement);
+
+  /// Converts a SPIR-V struct member decoration into a number of AST
+  /// decorations. If the decoration is recognized but deliberately dropped,
+  /// then returns an empty list without a diagnostic. On failure, emits a
+  /// diagnostic and returns an empty list.
+  /// @param struct_type_id the ID of the struct type
+  /// @param member_index the index of the member
+  /// @param member_ty the type of the member
+  /// @param decoration an encoded SPIR-V Decoration
+  /// @returns the AST decorations
+  ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
+                                             uint32_t member_index,
+                                             const Type* member_ty,
+                                             const Decoration& decoration);
+
+  /// Returns a string for the given type.  If the type ID is invalid,
+  /// then the resulting string only names the type ID.
+  /// @param type_id the SPIR-V ID for the type
+  /// @returns a string description of the type.
+  std::string ShowType(uint32_t type_id);
+
+  /// Builds the internal representation of the SPIR-V module.
+  /// Assumes the module is somewhat well-formed.  Normally you
+  /// would want to validate the SPIR-V module before attempting
+  /// to build this internal representation. Also computes a topological
+  /// ordering of the functions.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
+  bool BuildInternalModule();
+
+  /// Walks the internal representation of the module to populate
+  /// the AST form of the module.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
+  bool ParseInternalModule();
+
+  /// Records line numbers for each instruction.
+  void RegisterLineNumbers();
+
+  /// Walks the internal representation of the module, except for function
+  /// definitions, to populate the AST form of the module.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
+  bool ParseInternalModuleExceptFunctions();
+
+  /// Destroys the internal representation of the SPIR-V module.
+  void ResetInternalModule();
+
+  /// Registers extended instruction imports.  Only "GLSL.std.450" is supported.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterExtendedInstructionImports();
+
+  /// Returns true when the given instruction is an extended instruction
+  /// for GLSL.std.450.
+  /// @param inst a SPIR-V instruction
+  /// @returns true if its an SpvOpExtInst for GLSL.std.450
+  bool IsGlslExtendedInstruction(const spvtools::opt::Instruction& inst) const;
+
+  /// Returns true when the given instruction is an extended instruction
+  /// from an ignored extended instruction set.
+  /// @param inst a SPIR-V instruction
+  /// @returns true if its an SpvOpExtInst for an ignored extended instruction
+  bool IsIgnoredExtendedInstruction(
+      const spvtools::opt::Instruction& inst) const;
+
+  /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
+  /// Also synthesizes struct field names.  Ensures uniqueness for names for
+  /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterUserAndStructMemberNames();
+
+  /// Register the WorkgroupSize builtin and its associated constant value.
+  /// @returns true if parser is still successful.
+  bool RegisterWorkgroupSizeBuiltin();
+
+  /// @returns the workgroup size builtin
+  const WorkgroupSizeInfo& workgroup_size_builtin() {
+    return workgroup_size_builtin_;
+  }
+
+  /// Register entry point information.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterEntryPoints();
+
+  /// Register Tint AST types for SPIR-V types, including type aliases as
+  /// needed.  This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterTypes();
+
+  /// Fail if there are any module-scope pointer values other than those
+  /// declared by OpVariable.
+  /// @returns true if parser is still successful.
+  bool RejectInvalidPointerRoots();
+
+  /// Register sampler and texture usage for memory object declarations.
+  /// This must be called after we've registered line numbers for all
+  /// instructions. This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterHandleUsage();
+
+  /// Emit const definitions for scalar specialization constants generated
+  /// by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool EmitScalarSpecConstants();
+
+  /// Emits module-scope variables.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool EmitModuleScopeVariables();
+
+  /// Emits functions, with callees preceding their callers.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool EmitFunctions();
+
+  /// Emits a single function, if it has a body.
+  /// This is a no-op if the parser has already failed.
+  /// @param f the function to emit
+  /// @returns true if parser is still successful.
+  bool EmitFunction(const spvtools::opt::Function& f);
+
+  /// Returns the integer constant for the array size of the given variable.
+  /// @param var_id SPIR-V ID for an array variable
+  /// @returns the integer constant for its array size, or nullptr.
+  const spvtools::opt::analysis::IntConstant* GetArraySize(uint32_t var_id);
+
+  /// Returns the member name for the struct member.
+  /// @param struct_type the parser's structure type.
+  /// @param member_index the member index
+  /// @returns the field name
+  std::string GetMemberName(const Struct& struct_type, int member_index);
+
+  /// Returns the SPIR-V decorations for pipeline IO, if any, on a struct
+  /// member.
+  /// @param struct_type the parser's structure type.
+  /// @param member_index the member index
+  /// @returns a list of SPIR-V decorations.
+  DecorationList GetMemberPipelineDecorations(const Struct& struct_type,
+                                              int member_index);
+
+  /// Creates an AST Variable node for a SPIR-V ID, including any attached
+  /// decorations, unless it's an ignorable builtin variable.
+  /// @param id the SPIR-V result ID
+  /// @param sc the storage class, which cannot be ast::StorageClass::kNone
+  /// @param storage_type the storage type of the variable
+  /// @param is_const if true, the variable is const
+  /// @param is_overridable if true, the variable is pipeline-overridable
+  /// @param constructor the variable constructor
+  /// @param decorations the variable decorations
+  /// @returns a new Variable node, or null in the ignorable variable case and
+  /// in the error case
+  ast::Variable* MakeVariable(uint32_t id,
+                              ast::StorageClass sc,
+                              const Type* storage_type,
+                              bool is_const,
+                              bool is_overridable,
+                              const ast::Expression* constructor,
+                              ast::AttributeList decorations);
+
+  /// Returns true if a constant expression can be generated.
+  /// @param id the SPIR-V ID of the value
+  /// @returns true if a constant expression can be generated
+  bool CanMakeConstantExpression(uint32_t id);
+
+  /// Creates an AST expression node for a SPIR-V ID.  This is valid to call
+  /// when `CanMakeConstantExpression` returns true.
+  /// @param id the SPIR-V ID of the constant
+  /// @returns a new expression
+  TypedExpression MakeConstantExpression(uint32_t id);
+
+  /// Creates an AST expression node for a scalar SPIR-V constant.
+  /// @param source the source location
+  /// @param ast_type the AST type for the value
+  /// @param spirv_const the internal representation of the SPIR-V constant.
+  /// @returns a new expression
+  TypedExpression MakeConstantExpressionForScalarSpirvConstant(
+      Source source,
+      const Type* ast_type,
+      const spvtools::opt::analysis::Constant* spirv_const);
+
+  /// Creates an AST expression node for the null value for the given type.
+  /// @param type the AST type
+  /// @returns a new expression
+  const ast::Expression* MakeNullValue(const Type* type);
+
+  /// Make a typed expression for the null value for the given type.
+  /// @param type the AST type
+  /// @returns a new typed expression
+  TypedExpression MakeNullExpression(const Type* type);
+
+  /// Converts a given expression to the signedness demanded for an operand
+  /// of the given SPIR-V instruction, if required.  If the instruction assumes
+  /// signed integer operands, and `expr` is unsigned, then return an
+  /// as-cast expression converting it to signed. Otherwise, return
+  /// `expr` itself.  Similarly, convert as required from unsigned
+  /// to signed. Assumes all SPIR-V types have been mapped to AST types.
+  /// @param inst the SPIR-V instruction
+  /// @param expr an expression
+  /// @returns expr, or a cast of expr
+  TypedExpression RectifyOperandSignedness(
+      const spvtools::opt::Instruction& inst,
+      TypedExpression&& expr);
+
+  /// Converts a second operand to the signedness of the first operand
+  /// of a binary operator, if the WGSL operator requires they be the same.
+  /// Returns the converted expression, or the original expression if the
+  /// conversion is not needed.
+  /// @param inst the SPIR-V instruction
+  /// @param first_operand_type the type of the first operand to the instruction
+  /// @param second_operand_expr the second operand of the instruction
+  /// @returns second_operand_expr, or a cast of it
+  TypedExpression RectifySecondOperandSignedness(
+      const spvtools::opt::Instruction& inst,
+      const Type* first_operand_type,
+      TypedExpression&& second_operand_expr);
+
+  /// Returns the "forced" result type for the given SPIR-V instruction.
+  /// If the WGSL result type for an operation has a more strict rule than
+  /// requried by SPIR-V, then we say the result type is "forced".  This occurs
+  /// for signed integer division (OpSDiv), for example, where the result type
+  /// in WGSL must match the operand types.
+  /// @param inst the SPIR-V instruction
+  /// @param first_operand_type the AST type for the first operand.
+  /// @returns the forced AST result type, or nullptr if no forcing is required.
+  const Type* ForcedResultType(const spvtools::opt::Instruction& inst,
+                               const Type* first_operand_type);
+
+  /// Returns a signed integer scalar or vector type matching the shape (scalar,
+  /// vector, and component bit width) of another type, which itself is a
+  /// numeric scalar or vector. Returns null if the other type does not meet the
+  /// requirement.
+  /// @param other the type whose shape must be matched
+  /// @returns the signed scalar or vector type
+  const Type* GetSignedIntMatchingShape(const Type* other);
+
+  /// Returns a signed integer scalar or vector type matching the shape (scalar,
+  /// vector, and component bit width) of another type, which itself is a
+  /// numeric scalar or vector. Returns null if the other type does not meet the
+  /// requirement.
+  /// @param other the type whose shape must be matched
+  /// @returns the unsigned scalar or vector type
+  const Type* GetUnsignedIntMatchingShape(const Type* other);
+
+  /// Wraps the given expression in an as-cast to the given expression's type,
+  /// when the underlying operation produces a forced result type different
+  /// from the expression's result type. Otherwise, returns the given expression
+  /// unchanged.
+  /// @param expr the expression to pass through or to wrap
+  /// @param inst the SPIR-V instruction
+  /// @param first_operand_type the AST type for the first operand.
+  /// @returns the forced AST result type, or nullptr if no forcing is required.
+  TypedExpression RectifyForcedResultType(
+      TypedExpression expr,
+      const spvtools::opt::Instruction& inst,
+      const Type* first_operand_type);
+
+  /// Returns the given expression, but ensuring it's an unsigned type of the
+  /// same shape as the operand. Wraps the expression with a bitcast if needed.
+  /// Assumes the given expresion is a integer scalar or vector.
+  /// @param expr an integer scalar or integer vector expression.
+  /// @return the potentially cast TypedExpression
+  TypedExpression AsUnsigned(TypedExpression expr);
+
+  /// Returns the given expression, but ensuring it's a signed type of the
+  /// same shape as the operand. Wraps the expression with a bitcast if needed.
+  /// Assumes the given expresion is a integer scalar or vector.
+  /// @param expr an integer scalar or integer vector expression.
+  /// @return the potentially cast TypedExpression
+  TypedExpression AsSigned(TypedExpression expr);
+
+  /// Bookkeeping used for tracking the "position" builtin variable.
+  struct BuiltInPositionInfo {
+    /// The ID for the gl_PerVertex struct containing the Position builtin.
+    uint32_t struct_type_id = 0;
+    /// The member index for the Position builtin within the struct.
+    uint32_t position_member_index = 0;
+    /// The member index for the PointSize builtin within the struct.
+    uint32_t pointsize_member_index = 0;
+    /// The ID for the member type, which should map to vec4<f32>.
+    uint32_t position_member_type_id = 0;
+    /// The ID of the type of a pointer to the struct in the Output storage
+    /// class class.
+    uint32_t pointer_type_id = 0;
+    /// The SPIR-V storage class.
+    SpvStorageClass storage_class = SpvStorageClassOutput;
+    /// The ID of the type of a pointer to the Position member.
+    uint32_t position_member_pointer_type_id = 0;
+    /// The ID of the gl_PerVertex variable, if it was declared.
+    /// We'll use this for the gl_Position variable instead.
+    uint32_t per_vertex_var_id = 0;
+    /// The ID of the initializer to gl_PerVertex, if any.
+    uint32_t per_vertex_var_init_id = 0;
+  };
+  /// @returns info about the gl_Position builtin variable.
+  const BuiltInPositionInfo& GetBuiltInPositionInfo() {
+    return builtin_position_;
+  }
+
+  /// Returns the source record for the SPIR-V instruction with the given
+  /// result ID.
+  /// @param id the SPIR-V result id.
+  /// @return the Source record, or a default one
+  Source GetSourceForResultIdForTest(uint32_t id) const;
+  /// Returns the source record for the given instruction.
+  /// @param inst the SPIR-V instruction
+  /// @return the Source record, or a default one
+  Source GetSourceForInst(const spvtools::opt::Instruction* inst) const;
+
+  /// @param str a candidate identifier
+  /// @returns true if the given string is a valid WGSL identifier.
+  static bool IsValidIdentifier(const std::string& str);
+
+  /// Returns true if the given SPIR-V ID is a declared specialization constant,
+  /// generated by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant
+  /// @param id a SPIR-V result ID
+  /// @returns true if the ID is a scalar spec constant.
+  bool IsScalarSpecConstant(uint32_t id) {
+    return scalar_spec_constants_.find(id) != scalar_spec_constants_.end();
+  }
+
+  /// For a SPIR-V ID that might define a sampler, image, or sampled image
+  /// value, return the SPIR-V instruction that represents the memory object
+  /// declaration for the object.  If we encounter an OpSampledImage along the
+  /// way, follow the image operand when follow_image is true; otherwise follow
+  /// the sampler operand. Returns nullptr if we can't trace back to a memory
+  /// object declaration.  Emits an error and returns nullptr when the scan
+  /// fails due to a malformed module. This method can be used any time after
+  /// BuildInternalModule has been invoked.
+  /// @param id the SPIR-V ID of the sampler, image, or sampled image
+  /// @param follow_image indicates whether to follow the image operand of
+  /// OpSampledImage
+  /// @returns the memory object declaration for the handle, or nullptr
+  const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(
+      uint32_t id,
+      bool follow_image);
+
+  /// Returns the handle usage for a memory object declaration.
+  /// @param id SPIR-V ID of a sampler or image OpVariable or
+  /// OpFunctionParameter
+  /// @returns the handle usage, or an empty usage object.
+  Usage GetHandleUsage(uint32_t id) const;
+
+  /// Returns the SPIR-V type for the sampler or image type for the given
+  /// variable in UniformConstant storage class, or function parameter pointing
+  /// into the UniformConstant storage class .  Returns null and emits an
+  /// error on failure.
+  /// @param var the OpVariable instruction or OpFunctionParameter
+  /// @returns the Tint AST type for the sampler or texture, or null on error
+  const spvtools::opt::Instruction*
+  GetSpirvTypeForHandleMemoryObjectDeclaration(
+      const spvtools::opt::Instruction& var);
+
+  /// Returns the AST type for the pointer-to-sampler or pointer-to-texture type
+  /// for the given variable in UniformConstant storage class.  Returns null and
+  /// emits an error on failure.
+  /// @param var the OpVariable instruction
+  /// @returns the Tint AST type for the poiner-to-{sampler|texture} or null on
+  /// error
+  const Pointer* GetTypeForHandleVar(const spvtools::opt::Instruction& var);
+
+  /// Returns the channel component type corresponding to the given image
+  /// format.
+  /// @param format image texel format
+  /// @returns the component type, one of f32, i32, u32
+  const Type* GetComponentTypeForFormat(ast::TexelFormat format);
+
+  /// Returns the number of channels in the given image format.
+  /// @param format image texel format
+  /// @returns the number of channels in the format
+  unsigned GetChannelCountForFormat(ast::TexelFormat format);
+
+  /// Returns the texel type corresponding to the given image format.
+  /// This the WGSL type used for the texel parameter to textureStore.
+  /// It's always a 4-element vector.
+  /// @param format image texel format
+  /// @returns the texel format
+  const Type* GetTexelTypeForFormat(ast::TexelFormat format);
+
+  /// Returns the SPIR-V instruction with the given ID, or nullptr.
+  /// @param id the SPIR-V result ID
+  /// @returns the instruction, or nullptr on error
+  const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const;
+
+  /// A map of SPIR-V identifiers to builtins
+  using BuiltInsMap = std::unordered_map<uint32_t, SpvBuiltIn>;
+
+  /// @returns a map of builtins that should be handled specially by code
+  /// generation. Either the builtin does not exist in WGSL, or a type
+  /// conversion must be implemented on load and store.
+  const BuiltInsMap& special_builtins() const { return special_builtins_; }
+
+  /// @param builtin the SPIR-V builtin variable kind
+  /// @returns the SPIR-V ID for the variable defining the given builtin, or 0
+  uint32_t IdForSpecialBuiltIn(SpvBuiltIn builtin) const {
+    // Do a linear search.
+    for (const auto& entry : special_builtins_) {
+      if (entry.second == builtin) {
+        return entry.first;
+      }
+    }
+    return 0;
+  }
+
+  /// @param entry_point the SPIR-V ID of an entry point.
+  /// @returns the entry point info for the given ID
+  const std::vector<EntryPointInfo>& GetEntryPointInfo(uint32_t entry_point) {
+    return function_to_ep_info_[entry_point];
+  }
+
+  /// @returns the SPIR-V binary.
+  const std::vector<uint32_t>& spv_binary() { return spv_binary_; }
+
+ private:
+  /// Converts a specific SPIR-V type to a Tint type. Integer case
+  const Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Float case
+  const Type* ConvertType(const spvtools::opt::analysis::Float* float_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Vector case
+  const Type* ConvertType(const spvtools::opt::analysis::Vector* vec_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Matrix case
+  const Type* ConvertType(const spvtools::opt::analysis::Matrix* mat_ty);
+  /// Converts a specific SPIR-V type to a Tint type. RuntimeArray case
+  /// Distinct SPIR-V array types map to distinct Tint array types.
+  /// @param rtarr_ty the Tint type
+  const Type* ConvertType(
+      uint32_t type_id,
+      const spvtools::opt::analysis::RuntimeArray* rtarr_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Array case
+  /// Distinct SPIR-V array types map to distinct Tint array types.
+  /// @param arr_ty the Tint type
+  const Type* ConvertType(uint32_t type_id,
+                          const spvtools::opt::analysis::Array* arr_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Struct case.
+  /// SPIR-V allows distinct struct type definitions for two OpTypeStruct
+  /// that otherwise have the same set of members (and struct and member
+  /// decorations).  However, the SPIRV-Tools always produces a unique
+  /// `spvtools::opt::analysis::Struct` object in these cases. For this type
+  /// conversion, we need to have the original SPIR-V ID because we can't always
+  /// recover it from the optimizer's struct type object. This also lets us
+  /// preserve member names, which are given by OpMemberName which is normally
+  /// not significant to the optimizer's module representation.
+  /// @param type_id the SPIR-V ID for the type.
+  /// @param struct_ty the Tint type
+  const Type* ConvertType(uint32_t type_id,
+                          const spvtools::opt::analysis::Struct* struct_ty);
+  /// Converts a specific SPIR-V type to a Tint type. Pointer / Reference case
+  /// The pointer to gl_PerVertex maps to nullptr, and instead is recorded
+  /// in member #builtin_position_.
+  /// @param type_id the SPIR-V ID for the type.
+  /// @param ptr_as if PtrAs::Ref then a Reference will be returned, otherwise
+  /// Pointer
+  /// @param ptr_ty the Tint type
+  const Type* ConvertType(uint32_t type_id,
+                          PtrAs ptr_as,
+                          const spvtools::opt::analysis::Pointer* ptr_ty);
+
+  /// If `type` is a signed integral, or vector of signed integral,
+  /// returns the unsigned type, otherwise returns `type`.
+  /// @param type the possibly signed type
+  /// @returns the unsigned type
+  const Type* UnsignedTypeFor(const Type* type);
+
+  /// If `type` is a unsigned integral, or vector of unsigned integral,
+  /// returns the signed type, otherwise returns `type`.
+  /// @param type the possibly unsigned type
+  /// @returns the signed type
+  const Type* SignedTypeFor(const Type* type);
+
+  /// Parses the array or runtime-array decorations. Sets 0 if no explicit
+  /// stride was found, and therefore the implicit stride should be used.
+  /// @param spv_type the SPIR-V array or runtime-array type.
+  /// @param array_stride pointer to the array stride
+  /// @returns true on success.
+  bool ParseArrayDecorations(const spvtools::opt::analysis::Type* spv_type,
+                             uint32_t* array_stride);
+
+  /// Creates a new `ast::Node` owned by the ProgramBuilder.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  T* create(ARGS&&... args) {
+    return builder_.create<T>(std::forward<ARGS>(args)...);
+  }
+
+  // The SPIR-V binary we're parsing
+  std::vector<uint32_t> spv_binary_;
+
+  // The program builder.
+  ProgramBuilder builder_;
+
+  // The type manager.
+  TypeManager ty_;
+
+  // Is the parse successful?
+  bool success_ = true;
+  // Collector for diagnostic messages.
+  std::stringstream errors_;
+  FailStream fail_stream_;
+  spvtools::MessageConsumer message_consumer_;
+
+  // An object used to store and generate names for SPIR-V objects.
+  Namer namer_;
+  // An object used to convert SPIR-V enums to Tint enums
+  EnumConverter enum_converter_;
+
+  // The internal representation of the SPIR-V module and its context.
+  spvtools::Context tools_context_;
+  // All the state is owned by ir_context_.
+  std::unique_ptr<spvtools::opt::IRContext> ir_context_;
+  // The following are borrowed pointers to the internal state of ir_context_.
+  spvtools::opt::Module* module_ = nullptr;
+  spvtools::opt::analysis::DefUseManager* def_use_mgr_ = nullptr;
+  spvtools::opt::analysis::ConstantManager* constant_mgr_ = nullptr;
+  spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr;
+  spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr;
+
+  // The functions ordered so that callees precede their callers.
+  std::vector<const spvtools::opt::Function*> topologically_ordered_functions_;
+
+  // Maps an instruction to its source location. If no OpLine information
+  // is in effect for the instruction, map the instruction to its position
+  // in the SPIR-V module, counting by instructions, where the first
+  // instruction is line 1.
+  std::unordered_map<const spvtools::opt::Instruction*, Source::Location>
+      inst_source_;
+
+  // The set of IDs that are imports of the GLSL.std.450 extended instruction
+  // sets.
+  std::unordered_set<uint32_t> glsl_std_450_imports_;
+  // The set of IDs of imports that are ignored. For example, any
+  // "NonSemanticInfo." import is ignored.
+  std::unordered_set<uint32_t> ignored_imports_;
+
+  // The SPIR-V IDs of structure types that are the store type for buffer
+  // variables, either UBO or SSBO.
+  std::unordered_set<uint32_t> struct_types_for_buffers_;
+
+  // Bookkeeping for the gl_Position builtin.
+  // In Vulkan SPIR-V, it's the 0 member of the gl_PerVertex structure.
+  // But in WGSL we make a module-scope variable:
+  //    [[position]] var<in> gl_Position : vec4<f32>;
+  // The builtin variable was detected if and only if the struct_id is non-zero.
+  BuiltInPositionInfo builtin_position_;
+
+  // SPIR-V type IDs that are either:
+  // - a struct type decorated by BufferBlock
+  // - an array, runtime array containing one of these
+  // - a pointer type to one of these
+  // These are the types "enclosing" a buffer block with the old style
+  // representation: using Uniform storage class and BufferBlock decoration
+  // on the struct.  The new style is to use the StorageBuffer storage class
+  // and Block decoration.
+  std::unordered_set<uint32_t> remap_buffer_block_type_;
+
+  // The ast::Struct type names with only read-only members.
+  std::unordered_set<Symbol> read_only_struct_types_;
+
+  // The IDs of scalar spec constants
+  std::unordered_set<uint32_t> scalar_spec_constants_;
+
+  // Maps function_id to a list of entrypoint information
+  std::unordered_map<uint32_t, std::vector<EntryPointInfo>>
+      function_to_ep_info_;
+
+  // Maps from a SPIR-V ID to its underlying memory object declaration,
+  // following image paths. This a memoization table for
+  // GetMemoryObjectDeclarationForHandle. (A SPIR-V memory object declaration is
+  // an OpVariable or an OpFunctinParameter with pointer type).
+  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
+      mem_obj_decl_image_;
+  // Maps from a SPIR-V ID to its underlying memory object declaration,
+  // following sampler paths. This a memoization table for
+  // GetMemoryObjectDeclarationForHandle.
+  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
+      mem_obj_decl_sampler_;
+
+  // Maps a memory-object-declaration instruction to any sampler or texture
+  // usages implied by usages of the memory-object-declaration.
+  std::unordered_map<const spvtools::opt::Instruction*, Usage> handle_usage_;
+  // The inferred pointer type for the given handle variable.
+  std::unordered_map<const spvtools::opt::Instruction*, const Pointer*>
+      handle_type_;
+
+  // Set of symbols of declared type that have been added, used to avoid
+  // adding duplicates.
+  std::unordered_set<Symbol> declared_types_;
+
+  // Maps a struct type name to the SPIR-V ID for the structure type.
+  std::unordered_map<Symbol, uint32_t> struct_id_for_symbol_;
+
+  /// Maps the SPIR-V ID of a module-scope builtin variable that should be
+  /// ignored or type-converted, to its builtin kind.
+  /// See also BuiltInPositionInfo which is a separate mechanism for a more
+  /// complex case of replacing an entire structure.
+  BuiltInsMap special_builtins_;
+
+  /// Info about the WorkgroupSize builtin. If it's not present, then the 'id'
+  /// field will be 0. Sadly, in SPIR-V right now, there's only one workgroup
+  /// size object in the module.
+  WorkgroupSizeInfo workgroup_size_builtin_;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_PARSER_IMPL_H_
diff --git a/src/tint/reader/spirv/parser_impl_barrier_test.cc b/src/tint/reader/spirv/parser_impl_barrier_test.cc
new file mode 100644
index 0000000..a718ab3
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_barrier_test.cc
@@ -0,0 +1,213 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+#include "src/tint/sem/call.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::StartsWith;
+
+Program ParseAndBuild(std::string spirv) {
+  const char* preamble = R"(OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint GLCompute %main "main"
+            OpExecutionMode %main LocalSize 1 1 1
+            OpName %main "main"
+)";
+
+  auto p = std::make_unique<ParserImpl>(test::Assemble(preamble + spirv));
+  if (!p->BuildAndParseInternalModule()) {
+    ProgramBuilder builder;
+    builder.Diagnostics().add_error(diag::System::Reader, p->error());
+    return Program(std::move(builder));
+  }
+  return p->program();
+}
+
+TEST_F(SpvParserTest, WorkgroupBarrier) {
+  auto program = ParseAndBuild(R"(
+               OpName %helper "helper"
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+     %helper = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+               OpReturn
+               OpFunctionEnd
+     %main = OpFunction %void None %1
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )");
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+  auto* helper =
+      program.AST().Functions().Find(program.Symbols().Get("helper"));
+  ASSERT_NE(helper, nullptr);
+  ASSERT_GT(helper->body->statements.size(), 0u);
+  auto* call = helper->body->statements[0]->As<ast::CallStatement>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->expr->args.size(), 0u);
+  auto* sem_call = program.Sem().Get(call->expr);
+  ASSERT_NE(sem_call, nullptr);
+  auto* builtin = sem_call->Target()->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+  EXPECT_EQ(builtin->Type(), sem::BuiltinType::kWorkgroupBarrier);
+}
+
+TEST_F(SpvParserTest, StorageBarrier) {
+  auto program = ParseAndBuild(R"(
+               OpName %helper "helper"
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_1 = OpConstant %uint 1
+    %uint_72 = OpConstant %uint 72
+     %helper = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_1 %uint_72
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %1
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )");
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+  auto* helper =
+      program.AST().Functions().Find(program.Symbols().Get("helper"));
+  ASSERT_NE(helper, nullptr);
+  ASSERT_GT(helper->body->statements.size(), 0u);
+  auto* call = helper->body->statements[0]->As<ast::CallStatement>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->expr->args.size(), 0u);
+  auto* sem_call = program.Sem().Get(call->expr);
+  ASSERT_NE(sem_call, nullptr);
+  auto* builtin = sem_call->Target()->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+  EXPECT_EQ(builtin->Type(), sem::BuiltinType::kStorageBarrier);
+}
+
+TEST_F(SpvParserTest, ErrBarrierInvalidExecution) {
+  auto program = ParseAndBuild(R"(
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+       %main = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_0 %uint_2 %uint_264
+               OpReturn
+               OpFunctionEnd
+  )");
+  EXPECT_FALSE(program.IsValid());
+  EXPECT_THAT(program.Diagnostics().str(),
+              HasSubstr("unsupported control barrier execution scope"));
+}
+
+TEST_F(SpvParserTest, ErrBarrierSemanticsMissingAcquireRelease) {
+  auto program = ParseAndBuild(R"(
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_0 = OpConstant %uint 0
+       %main = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_0
+               OpReturn
+               OpFunctionEnd
+  )");
+  EXPECT_FALSE(program.IsValid());
+  EXPECT_THAT(
+      program.Diagnostics().str(),
+      HasSubstr("control barrier semantics requires acquire and release"));
+}
+
+TEST_F(SpvParserTest, ErrBarrierInvalidSemantics) {
+  auto program = ParseAndBuild(R"(
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_9 = OpConstant %uint 9
+       %main = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_9
+               OpReturn
+               OpFunctionEnd
+  )");
+  EXPECT_FALSE(program.IsValid());
+  EXPECT_THAT(program.Diagnostics().str(),
+              HasSubstr("unsupported control barrier semantics"));
+}
+
+TEST_F(SpvParserTest, ErrWorkgroupBarrierInvalidMemory) {
+  auto program = ParseAndBuild(R"(
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_8 = OpConstant %uint 8
+   %uint_264 = OpConstant %uint 264
+       %main = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_8 %uint_264
+               OpReturn
+               OpFunctionEnd
+  )");
+  EXPECT_FALSE(program.IsValid());
+  EXPECT_THAT(program.Diagnostics().str(),
+              HasSubstr("workgroupBarrier requires workgroup memory scope"));
+}
+
+TEST_F(SpvParserTest, ErrStorageBarrierInvalidMemory) {
+  auto program = ParseAndBuild(R"(
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+     %uint_8 = OpConstant %uint 8
+    %uint_72 = OpConstant %uint 72
+       %main = OpFunction %void None %1
+          %4 = OpLabel
+               OpControlBarrier %uint_2 %uint_8 %uint_72
+               OpReturn
+               OpFunctionEnd
+  )");
+  EXPECT_FALSE(program.IsValid());
+  EXPECT_THAT(program.Diagnostics().str(),
+              HasSubstr("storageBarrier requires device memory scope"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
new file mode 100644
index 0000000..245e0e8
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
@@ -0,0 +1,157 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Empty) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result = p->ConvertMemberDecoration(1, 1, nullptr, {});
+  EXPECT_TRUE(result.empty());
+  EXPECT_THAT(p->error(), Eq("malformed SPIR-V decoration: it's empty"));
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithoutOperand) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result =
+      p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset});
+  EXPECT_TRUE(result.empty());
+  EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
+                             "operand, has 0: member 13 of SPIR-V type 12"));
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithTooManyOperands) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result =
+      p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset, 3, 4});
+  EXPECT_TRUE(result.empty());
+  EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
+                             "operand, has 2: member 13 of SPIR-V type 12"));
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Offset) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result =
+      p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationOffset, 8});
+  ASSERT_FALSE(result.empty());
+  EXPECT_TRUE(result[0]->Is<ast::StructMemberOffsetAttribute>());
+  auto* offset_deco = result[0]->As<ast::StructMemberOffsetAttribute>();
+  ASSERT_NE(offset_deco, nullptr);
+  EXPECT_EQ(offset_deco->offset, 8u);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x2_Stride_Natural) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  spirv::F32 f32;
+  spirv::Matrix matrix(&f32, 2, 2);
+  auto result =
+      p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 8});
+  EXPECT_TRUE(result.empty());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x2_Stride_Custom) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  spirv::F32 f32;
+  spirv::Matrix matrix(&f32, 2, 2);
+  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
+                                           {SpvDecorationMatrixStride, 16});
+  ASSERT_FALSE(result.empty());
+  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
+  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
+  ASSERT_NE(stride_deco, nullptr);
+  EXPECT_EQ(stride_deco->stride, 16u);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x4_Stride_Natural) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  spirv::F32 f32;
+  spirv::Matrix matrix(&f32, 2, 4);
+  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
+                                           {SpvDecorationMatrixStride, 16});
+  EXPECT_TRUE(result.empty());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x4_Stride_Custom) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  spirv::F32 f32;
+  spirv::Matrix matrix(&f32, 2, 4);
+  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
+                                           {SpvDecorationMatrixStride, 64});
+  ASSERT_FALSE(result.empty());
+  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
+  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
+  ASSERT_NE(stride_deco, nullptr);
+  EXPECT_EQ(stride_deco->stride, 64u);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_Matrix2x3_Stride_Custom) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  spirv::F32 f32;
+  spirv::Matrix matrix(&f32, 2, 3);
+  auto result = p->ConvertMemberDecoration(1, 1, &matrix,
+                                           {SpvDecorationMatrixStride, 32});
+  ASSERT_FALSE(result.empty());
+  EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
+  auto* stride_deco = result[0]->As<ast::StrideAttribute>();
+  ASSERT_NE(stride_deco, nullptr);
+  EXPECT_EQ(stride_deco->stride, 32u);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_RelaxedPrecision) {
+  // WGSL does not support relaxed precision. Drop it.
+  // It's functionally correct to use full precision f32 instead of
+  // relaxed precision f32.
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result = p->ConvertMemberDecoration(1, 1, nullptr,
+                                           {SpvDecorationRelaxedPrecision});
+  EXPECT_TRUE(result.empty());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertMemberDecoration_UnhandledDecoration) {
+  auto p = parser(std::vector<uint32_t>{});
+
+  auto result = p->ConvertMemberDecoration(12, 13, nullptr, {12345678});
+  EXPECT_TRUE(result.empty());
+  EXPECT_THAT(p->error(), Eq("unhandled member decoration: 12345678 on member "
+                             "13 of SPIR-V type 12"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_convert_type_test.cc b/src/tint/reader/spirv/parser_impl_convert_type_test.cc
new file mode 100644
index 0000000..7bdae7a
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_convert_type_test.cc
@@ -0,0 +1,935 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %main "x_100"
+    OpExecutionMode %main OriginUpperLeft
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %main = OpFunction %void None %voidfn
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
+TEST_F(SpvParserTest, ConvertType_PreservesExistingFailure) {
+  auto p = parser(std::vector<uint32_t>{});
+  p->Fail() << "boing";
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(), Eq("boing"));
+}
+
+TEST_F(SpvParserTest, ConvertType_RequiresInternalRepresntation) {
+  auto p = parser(std::vector<uint32_t>{});
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(
+      p->error(),
+      Eq("ConvertType called when the internal module has not been built"));
+}
+
+TEST_F(SpvParserTest, ConvertType_NotAnId) {
+  auto assembly = Preamble() + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(900);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_EQ(nullptr, type);
+  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 900"));
+}
+
+TEST_F(SpvParserTest, ConvertType_IdExistsButIsNotAType) {
+  auto assembly = R"(
+     OpCapability Shader
+     %1 = OpExtInstImport "GLSL.std.450"
+     OpMemoryModel Logical Simple
+     OpEntryPoint Fragment %main "x_100"
+     OpExecutionMode %main OriginUpperLeft
+)" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(1);
+  EXPECT_EQ(nullptr, type);
+  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 1"));
+}
+
+TEST_F(SpvParserTest, ConvertType_UnhandledType) {
+  // Pipes are an OpenCL type. Tint doesn't support them.
+  auto p = parser(test::Assemble("%70 = OpTypePipe WriteOnly"));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(70);
+  EXPECT_EQ(nullptr, type);
+  EXPECT_THAT(p->error(),
+              Eq("unknown SPIR-V type with ID 70: %70 = OpTypePipe WriteOnly"));
+}
+
+TEST_F(SpvParserTest, ConvertType_Void) {
+  auto p = parser(test::Assemble(Preamble() + "%1 = OpTypeVoid" + R"(
+   %voidfn = OpTypeFunction %1
+   %main = OpFunction %1 None %voidfn
+   %entry = OpLabel
+   OpReturn
+   OpFunctionEnd
+  )"));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(1);
+  EXPECT_TRUE(type->Is<Void>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_Bool) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%100 = OpTypeBool" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(100);
+  EXPECT_TRUE(type->Is<Bool>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_I32) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%2 = OpTypeInt 32 1" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(2);
+  EXPECT_TRUE(type->Is<I32>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_U32) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%3 = OpTypeInt 32 0" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<U32>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_F32) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%4 = OpTypeFloat 32" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(4);
+  EXPECT_TRUE(type->Is<F32>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_BadIntWidth) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%5 = OpTypeInt 17 1" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(5);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(), Eq("unhandled integer width: 17"));
+}
+
+TEST_F(SpvParserTest, ConvertType_BadFloatWidth) {
+  auto p =
+      parser(test::Assemble(Preamble() + "%6 = OpTypeFloat 19" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(6);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(), Eq("unhandled float width: 19"));
+}
+
+TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidVectorElement) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %5 = OpTypePipe ReadOnly
+    %20 = OpTypeVector %5 2
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(20);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(), Eq("unknown SPIR-V type: 5"));
+}
+
+TEST_F(SpvParserTest, ConvertType_VecOverF32) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %float = OpTypeFloat 32
+    %20 = OpTypeVector %float 2
+    %30 = OpTypeVector %float 3
+    %40 = OpTypeVector %float 4
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* v2xf32 = p->ConvertType(20);
+  EXPECT_TRUE(v2xf32->Is<Vector>());
+  EXPECT_TRUE(v2xf32->As<Vector>()->type->Is<F32>());
+  EXPECT_EQ(v2xf32->As<Vector>()->size, 2u);
+
+  auto* v3xf32 = p->ConvertType(30);
+  EXPECT_TRUE(v3xf32->Is<Vector>());
+  EXPECT_TRUE(v3xf32->As<Vector>()->type->Is<F32>());
+  EXPECT_EQ(v3xf32->As<Vector>()->size, 3u);
+
+  auto* v4xf32 = p->ConvertType(40);
+  EXPECT_TRUE(v4xf32->Is<Vector>());
+  EXPECT_TRUE(v4xf32->As<Vector>()->type->Is<F32>());
+  EXPECT_EQ(v4xf32->As<Vector>()->size, 4u);
+
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_VecOverI32) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %int = OpTypeInt 32 1
+    %20 = OpTypeVector %int 2
+    %30 = OpTypeVector %int 3
+    %40 = OpTypeVector %int 4
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* v2xi32 = p->ConvertType(20);
+  EXPECT_TRUE(v2xi32->Is<Vector>());
+  EXPECT_TRUE(v2xi32->As<Vector>()->type->Is<I32>());
+  EXPECT_EQ(v2xi32->As<Vector>()->size, 2u);
+
+  auto* v3xi32 = p->ConvertType(30);
+  EXPECT_TRUE(v3xi32->Is<Vector>());
+  EXPECT_TRUE(v3xi32->As<Vector>()->type->Is<I32>());
+  EXPECT_EQ(v3xi32->As<Vector>()->size, 3u);
+
+  auto* v4xi32 = p->ConvertType(40);
+  EXPECT_TRUE(v4xi32->Is<Vector>());
+  EXPECT_TRUE(v4xi32->As<Vector>()->type->Is<I32>());
+  EXPECT_EQ(v4xi32->As<Vector>()->size, 4u);
+
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_VecOverU32) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %20 = OpTypeVector %uint 2
+    %30 = OpTypeVector %uint 3
+    %40 = OpTypeVector %uint 4
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* v2xu32 = p->ConvertType(20);
+  EXPECT_TRUE(v2xu32->Is<Vector>());
+  EXPECT_TRUE(v2xu32->As<Vector>()->type->Is<U32>());
+  EXPECT_EQ(v2xu32->As<Vector>()->size, 2u);
+
+  auto* v3xu32 = p->ConvertType(30);
+  EXPECT_TRUE(v3xu32->Is<Vector>());
+  EXPECT_TRUE(v3xu32->As<Vector>()->type->Is<U32>());
+  EXPECT_EQ(v3xu32->As<Vector>()->size, 3u);
+
+  auto* v4xu32 = p->ConvertType(40);
+  EXPECT_TRUE(v4xu32->Is<Vector>());
+  EXPECT_TRUE(v4xu32->As<Vector>()->type->Is<U32>());
+  EXPECT_EQ(v4xu32->As<Vector>()->size, 4u);
+
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidMatrixElement) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %5 = OpTypePipe ReadOnly
+    %10 = OpTypeVector %5 2
+    %20 = OpTypeMatrix %10 2
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(20);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(), Eq("unknown SPIR-V type: 5"));
+}
+
+TEST_F(SpvParserTest, ConvertType_MatrixOverF32) {
+  // Matrices are only defined over floats.
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %float = OpTypeFloat 32
+    %v2 = OpTypeVector %float 2
+    %v3 = OpTypeVector %float 3
+    %v4 = OpTypeVector %float 4
+    ; First digit is rows
+    ; Second digit is columns
+    %22 = OpTypeMatrix %v2 2
+    %23 = OpTypeMatrix %v2 3
+    %24 = OpTypeMatrix %v2 4
+    %32 = OpTypeMatrix %v3 2
+    %33 = OpTypeMatrix %v3 3
+    %34 = OpTypeMatrix %v3 4
+    %42 = OpTypeMatrix %v4 2
+    %43 = OpTypeMatrix %v4 3
+    %44 = OpTypeMatrix %v4 4
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* m22 = p->ConvertType(22);
+  EXPECT_TRUE(m22->Is<Matrix>());
+  EXPECT_TRUE(m22->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m22->As<Matrix>()->rows, 2u);
+  EXPECT_EQ(m22->As<Matrix>()->columns, 2u);
+
+  auto* m23 = p->ConvertType(23);
+  EXPECT_TRUE(m23->Is<Matrix>());
+  EXPECT_TRUE(m23->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m23->As<Matrix>()->rows, 2u);
+  EXPECT_EQ(m23->As<Matrix>()->columns, 3u);
+
+  auto* m24 = p->ConvertType(24);
+  EXPECT_TRUE(m24->Is<Matrix>());
+  EXPECT_TRUE(m24->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m24->As<Matrix>()->rows, 2u);
+  EXPECT_EQ(m24->As<Matrix>()->columns, 4u);
+
+  auto* m32 = p->ConvertType(32);
+  EXPECT_TRUE(m32->Is<Matrix>());
+  EXPECT_TRUE(m32->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m32->As<Matrix>()->rows, 3u);
+  EXPECT_EQ(m32->As<Matrix>()->columns, 2u);
+
+  auto* m33 = p->ConvertType(33);
+  EXPECT_TRUE(m33->Is<Matrix>());
+  EXPECT_TRUE(m33->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m33->As<Matrix>()->rows, 3u);
+  EXPECT_EQ(m33->As<Matrix>()->columns, 3u);
+
+  auto* m34 = p->ConvertType(34);
+  EXPECT_TRUE(m34->Is<Matrix>());
+  EXPECT_TRUE(m34->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m34->As<Matrix>()->rows, 3u);
+  EXPECT_EQ(m34->As<Matrix>()->columns, 4u);
+
+  auto* m42 = p->ConvertType(42);
+  EXPECT_TRUE(m42->Is<Matrix>());
+  EXPECT_TRUE(m42->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m42->As<Matrix>()->rows, 4u);
+  EXPECT_EQ(m42->As<Matrix>()->columns, 2u);
+
+  auto* m43 = p->ConvertType(43);
+  EXPECT_TRUE(m43->Is<Matrix>());
+  EXPECT_TRUE(m43->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m43->As<Matrix>()->rows, 4u);
+  EXPECT_EQ(m43->As<Matrix>()->columns, 3u);
+
+  auto* m44 = p->ConvertType(44);
+  EXPECT_TRUE(m44->Is<Matrix>());
+  EXPECT_TRUE(m44->As<Matrix>()->type->Is<F32>());
+  EXPECT_EQ(m44->As<Matrix>()->rows, 4u);
+  EXPECT_EQ(m44->As<Matrix>()->columns, 4u);
+
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_RuntimeArray) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeRuntimeArray %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->UnwrapAll()->Is<Array>());
+  auto* arr_type = type->UnwrapAll()->As<Array>();
+  ASSERT_NE(arr_type, nullptr);
+  EXPECT_EQ(arr_type->size, 0u);
+  EXPECT_EQ(arr_type->stride, 0u);
+  auto* elem_type = arr_type->type;
+  ASSERT_NE(elem_type, nullptr);
+  EXPECT_TRUE(elem_type->Is<U32>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_RuntimeArray_InvalidDecoration) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 Block
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeRuntimeArray %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(
+      p->error(),
+      Eq("invalid array type ID 10: unknown decoration 2 with 1 total words"));
+}
+
+TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_Valid) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 ArrayStride 64
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeRuntimeArray %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  auto* arr_type = type->UnwrapAll()->As<Array>();
+  ASSERT_NE(arr_type, nullptr);
+  EXPECT_EQ(arr_type->size, 0u);
+  EXPECT_EQ(arr_type->stride, 64u);
+}
+
+TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_ZeroIsError) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 ArrayStride 0
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeRuntimeArray %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("invalid array type ID 10: ArrayStride can't be 0"));
+}
+
+TEST_F(SpvParserTest, ConvertType_Array) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpConstant %uint 42
+    %10 = OpTypeArray %uint %uint_42
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->Is<Array>());
+  auto* arr_type = type->As<Array>();
+  ASSERT_NE(arr_type, nullptr);
+  EXPECT_EQ(arr_type->size, 42u);
+  EXPECT_EQ(arr_type->stride, 0u);
+  auto* elem_type = arr_type->type;
+  ASSERT_NE(elem_type, nullptr);
+  EXPECT_TRUE(elem_type->Is<U32>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantValue) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %uint_42 SpecId 12
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpSpecConstant %uint 42
+    %10 = OpTypeArray %uint %uint_42
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("Array type 10 length is a specialization constant"));
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantExpr) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpConstant %uint 42
+    %sum = OpSpecConstantOp %uint IAdd %uint_42 %uint_42
+    %10 = OpTypeArray %uint %sum
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("Array type 10 length is a specialization constant"));
+}
+
+// TODO(dneto): Maybe add a test where the length operand is not a constant.
+// E.g. it's the ID of a type.  That won't validate, and the SPIRV-Tools
+// optimizer representation doesn't handle it and asserts out instead.
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadTooBig) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint64 = OpTypeInt 64 0
+    %uint64_big = OpConstant %uint64 5000000000
+    %10 = OpTypeArray %uint64 %uint64_big
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  // TODO(dneto): Right now it's rejected earlier in the flow because
+  // we can't even utter the uint64 type.
+  EXPECT_THAT(p->error(), Eq("unhandled integer width: 64"));
+}
+
+TEST_F(SpvParserTest, ConvertType_Array_InvalidDecoration) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 Block
+    %uint = OpTypeInt 32 0
+    %uint_5 = OpConstant %uint 5
+    %10 = OpTypeArray %uint %uint_5
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(
+      p->error(),
+      Eq("invalid array type ID 10: unknown decoration 2 with 1 total words"));
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayStride_Valid) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %uint_5 = OpConstant %uint 5
+    %10 = OpTypeArray %uint %uint_5
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->UnwrapAll()->Is<Array>());
+  auto* arr_type = type->UnwrapAll()->As<Array>();
+  ASSERT_NE(arr_type, nullptr);
+  EXPECT_EQ(arr_type->stride, 8u);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayStride_ZeroIsError) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 ArrayStride 0
+    %uint = OpTypeInt 32 0
+    %uint_5 = OpConstant %uint 5
+    %10 = OpTypeArray %uint %uint_5
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("invalid array type ID 10: ArrayStride can't be 0"));
+}
+
+TEST_F(SpvParserTest, ConvertType_StructEmpty) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %10 = OpTypeStruct
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(10);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_EQ(p->error(),
+            "WGSL does not support empty structures. can't convert type: %10 = "
+            "OpTypeStruct");
+}
+
+TEST_F(SpvParserTest, ConvertType_StructTwoMembers) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %uint %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->Is<Struct>());
+
+  auto* str = type->Build(p->builder());
+  Program program = p->program();
+  EXPECT_EQ(test::ToString(program, str), "S");
+}
+
+TEST_F(SpvParserTest, ConvertType_StructWithBlockDecoration) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpDecorate %10 Block
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeStruct %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->Is<Struct>());
+
+  auto* str = type->Build(p->builder());
+  Program program = p->program();
+  EXPECT_EQ(test::ToString(program, str), "S");
+}
+
+TEST_F(SpvParserTest, ConvertType_StructWithMemberDecorations) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpMemberDecorate %10 0 Offset 0
+    OpMemberDecorate %10 1 Offset 8
+    OpMemberDecorate %10 2 Offset 16
+    %float = OpTypeFloat 32
+    %vec = OpTypeVector %float 2
+    %mat = OpTypeMatrix %vec 2
+    %10 = OpTypeStruct %float %vec %mat
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->Is<Struct>());
+
+  auto* str = type->Build(p->builder());
+  Program program = p->program();
+  EXPECT_EQ(test::ToString(program, str), "S");
+}
+
+TEST_F(SpvParserTest, ConvertType_Struct_NoDeduplication) {
+  // Prove that distinct SPIR-V structs map to distinct WGSL types.
+  auto p = parser(test::Assemble(Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeStruct %uint
+    %11 = OpTypeStruct %uint
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type10 = p->ConvertType(10);
+  ASSERT_NE(type10, nullptr);
+  EXPECT_TRUE(type10->Is<Struct>());
+  auto* struct_type10 = type10->As<Struct>();
+  ASSERT_NE(struct_type10, nullptr);
+  EXPECT_EQ(struct_type10->members.size(), 1u);
+  EXPECT_TRUE(struct_type10->members[0]->Is<U32>());
+
+  auto* type11 = p->ConvertType(11);
+  ASSERT_NE(type11, nullptr);
+  EXPECT_TRUE(type11->Is<Struct>());
+  auto* struct_type11 = type11->As<Struct>();
+  ASSERT_NE(struct_type11, nullptr);
+  EXPECT_EQ(struct_type11->members.size(), 1u);
+  EXPECT_TRUE(struct_type11->members[0]->Is<U32>());
+
+  // They map to distinct types in WGSL
+  EXPECT_NE(type11, type10);
+}
+
+TEST_F(SpvParserTest, ConvertType_Array_NoDeduplication) {
+  // Prove that distinct SPIR-V arrays map to distinct WGSL types.
+  auto assembly = Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeStruct %uint
+    %11 = OpTypeStruct %uint
+    %uint_1 = OpConstant %uint 1
+    %20 = OpTypeArray %10 %uint_1
+    %21 = OpTypeArray %11 %uint_1
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type20 = p->ConvertType(20);
+  ASSERT_NE(type20, nullptr);
+  EXPECT_TRUE(type20->Is<Array>());
+
+  auto* type21 = p->ConvertType(21);
+  ASSERT_NE(type21, nullptr);
+  EXPECT_TRUE(type21->Is<Array>());
+
+  // They map to distinct types in WGSL
+  EXPECT_NE(type21, type20);
+}
+
+TEST_F(SpvParserTest, ConvertType_RuntimeArray_NoDeduplication) {
+  // Prove that distinct SPIR-V runtime arrays map to distinct WGSL types.
+  // The implementation already de-duplicates them because it knows
+  // runtime-arrays normally have stride decorations.
+  auto assembly = Preamble() + R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeStruct %uint
+    %11 = OpTypeStruct %uint
+    %20 = OpTypeRuntimeArray %10
+    %21 = OpTypeRuntimeArray %11
+    %22 = OpTypeRuntimeArray %10
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type20 = p->ConvertType(20);
+  ASSERT_NE(type20, nullptr);
+  EXPECT_TRUE(type20->Is<Alias>());
+  EXPECT_TRUE(type20->UnwrapAll()->Is<Array>());
+  EXPECT_EQ(type20->UnwrapAll()->As<Array>()->size, 0u);
+
+  auto* type21 = p->ConvertType(21);
+  ASSERT_NE(type21, nullptr);
+  EXPECT_TRUE(type21->Is<Alias>());
+  EXPECT_TRUE(type21->UnwrapAll()->Is<Array>());
+  EXPECT_EQ(type21->UnwrapAll()->As<Array>()->size, 0u);
+
+  auto* type22 = p->ConvertType(22);
+  ASSERT_NE(type22, nullptr);
+  EXPECT_TRUE(type22->Is<Alias>());
+  EXPECT_TRUE(type22->UnwrapAll()->Is<Array>());
+  EXPECT_EQ(type22->UnwrapAll()->As<Array>()->size, 0u);
+
+  // They map to distinct types in WGSL
+  EXPECT_NE(type21, type20);
+  EXPECT_NE(type22, type21);
+  EXPECT_NE(type22, type20);
+}
+
+// TODO(dneto): Demonstrate other member decorations. Blocked on
+// crbug.com/tint/30
+// TODO(dneto): Demonstrate multiple member deocrations. Blocked on
+// crbug.com/tint/30
+
+TEST_F(SpvParserTest, ConvertType_InvalidPointeetype) {
+  // Disallow pointer-to-function
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %void = OpTypeVoid
+  %42 = OpTypeFunction %void
+  %3 = OpTypePointer Input %42
+
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+  )"));
+  EXPECT_TRUE(p->BuildInternalModule()) << p->error();
+
+  auto* type = p->ConvertType(3);
+  EXPECT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("SPIR-V pointer type with ID 3 has invalid pointee type 42"));
+}
+
+TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidStorageClass) {
+  // Disallow invalid storage class
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %1 = OpTypeFloat 32
+  %3 = OpTypePointer !999 %1   ; Special syntax to inject 999 as the storage class
+  )" + MainBody()));
+  // TODO(dneto): I can't get it past module building.
+  EXPECT_FALSE(p->BuildInternalModule()) << p->error();
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerInput) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Input %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerOutput) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Output %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerUniform) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Uniform %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kUniform);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerWorkgroup) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Workgroup %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kWorkgroup);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerUniformConstant) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer UniformConstant %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kNone);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerStorageBuffer) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer StorageBuffer %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kStorage);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerPrivate) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Private %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerFunction) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %3 = OpTypePointer Function %float
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_TRUE(type->Is<Pointer>());
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_TRUE(ptr_ty->type->Is<F32>());
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kFunction);
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_PointerToPointer) {
+  // FYI:  The reader suports pointer-to-pointer even while WebGPU does not.
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %42 = OpTypePointer Output %float
+  %3 = OpTypePointer Input %42
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(3);
+  EXPECT_NE(type, nullptr);
+  EXPECT_TRUE(type->Is<Pointer>());
+
+  auto* ptr_ty = type->As<Pointer>();
+  EXPECT_NE(ptr_ty, nullptr);
+  EXPECT_EQ(ptr_ty->storage_class, ast::StorageClass::kPrivate);
+  EXPECT_TRUE(ptr_ty->type->Is<Pointer>());
+
+  auto* ptr_ptr_ty = ptr_ty->type->As<Pointer>();
+  EXPECT_NE(ptr_ptr_ty, nullptr);
+  EXPECT_EQ(ptr_ptr_ty->storage_class, ast::StorageClass::kPrivate);
+  EXPECT_TRUE(ptr_ptr_ty->type->Is<F32>());
+
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_Sampler_PretendVoid) {
+  // We fake the type suport for samplers, images, and sampled images.
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %1 = OpTypeSampler
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(1);
+  EXPECT_TRUE(type->Is<Void>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_Image_PretendVoid) {
+  // We fake the type suport for samplers, images, and sampled images.
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %1 = OpTypeImage %float 2D 0 0 0 1 Unknown
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(1);
+  EXPECT_TRUE(type->Is<Void>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_SampledImage_PretendVoid) {
+  auto p = parser(test::Assemble(Preamble() + R"(
+  %float = OpTypeFloat 32
+  %im = OpTypeImage %float 2D 0 0 0 1 Unknown
+  %1 = OpTypeSampledImage %im
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+
+  auto* type = p->ConvertType(1);
+  EXPECT_TRUE(type->Is<Void>());
+  EXPECT_TRUE(p->error().empty());
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_function_decl_test.cc b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
new file mode 100644
index 0000000..9e6e8bf
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
@@ -0,0 +1,475 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+std::string Caps() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+  )";
+}
+
+std::string Preamble() {
+  return Caps() + R"(
+    OpEntryPoint Fragment %main "x_100"
+    OpExecutionMode %main OriginUpperLeft
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %main = OpFunction %void None %voidfn
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
+/// @returns a SPIR-V assembly segment which assigns debug names
+/// to particular IDs.
+std::string Names(std::vector<std::string> ids) {
+  std::ostringstream outs;
+  for (auto& id : ids) {
+    outs << "    OpName %" << id << " \"" << id << "\"\n";
+  }
+  return outs.str();
+}
+
+std::string CommonTypes() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+  )";
+}
+
+std::string BuiltinPosition() {
+  return R"(OpDecorate %position BuiltIn Position
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %ptr = OpTypePointer Output %v4float
+    %position = OpVariable %ptr Output
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+)";
+}
+
+TEST_F(SpvParserTest, EmitFunctions_NoFunctions) {
+  auto p = parser(test::Assemble(
+      R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+)" + CommonTypes()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
+  p->SkipDumpingPending("Not valid for Vulkan: needs an entry point");
+}
+
+TEST_F(SpvParserTest, EmitFunctions_FunctionWithoutBody) {
+  auto p =
+      parser(test::Assemble(Preamble() + Names({"main"}) + CommonTypes() + R"(
+     %main = OpFunction %void None %voidfn
+     OpFunctionEnd
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
+  p->SkipDumpingPending("Missing an entry point body requires Linkage");
+}
+
+TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Vertex) {
+  std::string input = Caps() +
+                      R"(OpEntryPoint Vertex %main "main" %position )" +
+                      Names({"main"}) + BuiltinPosition() + R"(
+
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+)")) << program_ast;
+
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(vertex)
+fn main() -> main_out {
+)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Fragment) {
+  std::string input = Caps() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(fragment)
+fn main() {
+)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_GLCompute) {
+  std::string input = Caps() + R"(
+      OpEntryPoint GLCompute %main "main"
+      OpExecutionMode %main LocalSize 1 1 1
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(1, 1, 1)
+fn main() {
+)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_MultipleEntryPoints) {
+  std::string input = Caps() +
+                      R"(
+OpEntryPoint Fragment %main "first_shader"
+OpEntryPoint Fragment %main "second_shader"
+OpExecutionMode %main OriginUpperLeft
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(fragment)
+fn first_shader() {
+)"));
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(fragment)
+fn second_shader() {
+)"));
+}
+
+TEST_F(SpvParserTest,
+       EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only) {
+  std::string input = Caps() + R"(
+OpEntryPoint GLCompute %main "comp_main"
+OpExecutionMode %main LocalSize 2 4 8
+)" + Names({"main"}) + CommonTypes() +
+                      R"(
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(2, 4, 8)
+fn comp_main() {
+)")) << program_ast;
+}
+
+TEST_F(SpvParserTest,
+       EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only) {
+  std::string input = Caps() + R"(OpEntryPoint GLCompute %main "comp_main"
+OpDecorate %wgsize BuiltIn WorkgroupSize
+)" + CommonTypes() + R"(
+%uvec3 = OpTypeVector %uint 3
+%uint_3 = OpConstant %uint 3
+%uint_5 = OpConstant %uint 5
+%uint_7 = OpConstant %uint 7
+%wgsize = OpConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(3, 5, 7)
+fn comp_main() {
+)")) << program_ast;
+}
+
+TEST_F(
+    SpvParserTest,
+    EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only) {
+  std::string input = Caps() +
+                      R"(OpEntryPoint GLCompute %main "comp_main"
+OpDecorate %wgsize BuiltIn WorkgroupSize
+OpDecorate %uint_3 SpecId 0
+OpDecorate %uint_5 SpecId 1
+OpDecorate %uint_7 SpecId 2
+)" + CommonTypes() + R"(
+%uvec3 = OpTypeVector %uint 3
+%uint_3 = OpSpecConstant %uint 3
+%uint_5 = OpSpecConstant %uint 5
+%uint_7 = OpSpecConstant %uint 7
+%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(3, 5, 7)
+fn comp_main() {
+)")) << program_ast;
+}
+
+TEST_F(
+    SpvParserTest,
+    EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant) {
+  std::string input = Caps() +
+                      R"(OpEntryPoint GLCompute %main "comp_main"
+OpDecorate %wgsize BuiltIn WorkgroupSize
+OpDecorate %uint_3 SpecId 0
+OpDecorate %uint_7 SpecId 2
+)" + CommonTypes() + R"(
+%uvec3 = OpTypeVector %uint 3
+%uint_3 = OpSpecConstant %uint 3
+%uint_5 = OpConstant %uint 5
+%uint_7 = OpSpecConstant %uint 7
+%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(3, 5, 7)
+fn comp_main() {
+)")) << program_ast;
+}
+
+TEST_F(
+    SpvParserTest,
+    // I had to shorten the name to pass the linter.
+    EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant) {
+  // WorkgroupSize builtin wins.
+  std::string input = Caps() +
+                      R"(OpEntryPoint GLCompute %main "comp_main"
+OpExecutionMode %main LocalSize 2 4 8
+OpDecorate %wgsize BuiltIn WorkgroupSize
+OpDecorate %uint_3 SpecId 0
+OpDecorate %uint_5 SpecId 1
+OpDecorate %uint_7 SpecId 2
+)" + CommonTypes() + R"(
+%uvec3 = OpTypeVector %uint 3
+%uint_3 = OpSpecConstant %uint 3
+%uint_5 = OpSpecConstant %uint 5
+%uint_7 = OpSpecConstant %uint 7
+%wgsize = OpSpecConstantComposite %uvec3 %uint_3 %uint_5 %uint_7
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  auto p = parser(test::Assemble(input));
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  ASSERT_TRUE(p->error().empty()) << p->error();
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(
+@stage(compute) @workgroup_size(3, 5, 7)
+fn comp_main() {
+)")) << program_ast;
+}
+
+TEST_F(SpvParserTest, EmitFunctions_VoidFunctionWithoutParams) {
+  auto p = parser(test::Assemble(Preamble() + Names({"another_function"}) +
+                                 CommonTypes() + R"(
+    %another_function = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(fn another_function() {
+)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_CalleePrecedesCaller) {
+  auto p = parser(test::Assemble(
+      Preamble() +
+      Names({"root", "branch", "leaf", "leaf_result", "branch_result"}) +
+      CommonTypes() + R"(
+     %uintfn = OpTypeFunction %uint
+     %uint_0 = OpConstant %uint 0
+
+     %root = OpFunction %void None %voidfn
+     %root_entry = OpLabel
+     %branch_result = OpFunctionCall %uint %branch
+     OpReturn
+     OpFunctionEnd
+
+     %branch = OpFunction %uint None %uintfn
+     %branch_entry = OpLabel
+     %leaf_result = OpFunctionCall %uint %leaf
+     OpReturnValue %leaf_result
+     OpFunctionEnd
+
+     %leaf = OpFunction %uint None %uintfn
+     %leaf_entry = OpLabel
+     OpReturnValue %uint_0
+     OpFunctionEnd
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(fn leaf() -> u32 {
+  return 0u;
+}
+
+fn branch() -> u32 {
+  let leaf_result : u32 = leaf();
+  return leaf_result;
+}
+
+fn root() {
+  let branch_result : u32 = branch();
+  return;
+}
+)")) << program_ast;
+}
+
+TEST_F(SpvParserTest, EmitFunctions_NonVoidResultType) {
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"ret_float"}) + CommonTypes() + R"(
+     %float_0 = OpConstant %float 0.0
+     %fn_ret_float = OpTypeFunction %float
+
+     %ret_float = OpFunction %float None %fn_ret_float
+     %ret_float_entry = OpLabel
+     OpReturnValue %float_0
+     OpFunctionEnd
+)" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast, HasSubstr(R"(fn ret_float() -> f32 {
+  return 0.0;
+}
+)")) << program_ast;
+}
+
+TEST_F(SpvParserTest, EmitFunctions_MixedParamTypes) {
+  auto p = parser(test::Assemble(
+      Preamble() + Names({"mixed_params", "a", "b", "c"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
+
+     %mixed_params = OpFunction %void None %fn_mixed_params
+     %a = OpFunctionParameter %uint
+     %b = OpFunctionParameter %float
+     %c = OpFunctionParameter %int
+     %mixed_entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast,
+              HasSubstr(R"(fn mixed_params(a : u32, b : f32, c : i32) {
+  return;
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_GenerateParamNames) {
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"mixed_params"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
+
+     %mixed_params = OpFunction %void None %fn_mixed_params
+     %14 = OpFunctionParameter %uint
+     %15 = OpFunctionParameter %float
+     %16 = OpFunctionParameter %int
+     %mixed_entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  Program program = p->program();
+  const auto program_ast = test::ToString(program);
+  EXPECT_THAT(program_ast,
+              HasSubstr(R"(fn mixed_params(x_14 : u32, x_15 : f32, x_16 : i32) {
+  return;
+}
+)")) << program_ast;
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_get_decorations_test.cc b/src/tint/reader/spirv/parser_impl_get_decorations_test.cc
new file mode 100644
index 0000000..92652e4
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_get_decorations_test.cc
@@ -0,0 +1,266 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::UnorderedElementsAre;
+
+using SpvParserGetDecorationsTest = SpvParserTest;
+
+const char* kSkipReason = "This example is deliberately a SPIR-V fragment";
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NotAnId) {
+  auto p = parser(test::Assemble(""));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(42);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NoDecorations) {
+  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(1);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_OneDecoration) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %10 Block
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(10);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationBlock}));
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_Duplicate) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %10 Block
+    OpDecorate %10 Block
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(10);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationBlock}));
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_MultiDecoration) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %5 RelaxedPrecision
+    OpDecorate %5 Location 7      ; Invalid case made up for test
+    %float = OpTypeFloat 32
+    %5 = OpConstant %float 3.14
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(5);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision},
+                                   Decoration{SpvDecorationLocation, 7}));
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAnId) {
+  auto p = parser(test::Assemble(""));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsForMember(42, 9);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAStruct) {
+  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(1);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest,
+       GetDecorationsForMember_MemberWithoutDecoration) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeStruct %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsForMember(10, 0);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_RelaxedPrecision) {
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %10 0 RelaxedPrecision
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  auto decorations = p->GetDecorationsForMember(10, 0);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_Duplicate) {
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %10 0 RelaxedPrecision
+    OpMemberDecorate %10 0 RelaxedPrecision
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  auto decorations = p->GetDecorationsForMember(10, 0);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+// TODO(dneto): Enable when ArrayStride is handled
+TEST_F(SpvParserGetDecorationsTest,
+       DISABLED_GetDecorationsForMember_OneDecoration) {
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %10 1 ArrayStride 12
+    %uint = OpTypeInt 32 0
+    %uint_2 = OpConstant %uint 2
+    %arr = OpTypeArray %uint %uint_2
+    %10 = OpTypeStruct %uint %arr
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  auto decorations = p->GetDecorationsForMember(10, 1);
+  EXPECT_THAT(decorations,
+              UnorderedElementsAre(Decoration{SpvDecorationArrayStride, 12}));
+  EXPECT_TRUE(p->error().empty());
+}
+
+// TODO(dneto): Enable when ArrayStride, MatrixStride, ColMajor are handled
+// crbug.com/tint/30 for ArrayStride
+// crbug.com/tint/31 for matrix layout
+TEST_F(SpvParserGetDecorationsTest,
+       DISABLED_GetDecorationsForMember_MultiDecoration) {
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %50 1 RelaxedPrecision
+    OpMemberDecorate %50 2 ArrayStride 16
+    OpMemberDecorate %50 2 MatrixStride 8
+    OpMemberDecorate %50 2 ColMajor
+    %float = OpTypeFloat 32
+    %vec = OpTypeVector %float 2
+    %mat = OpTypeMatrix %vec 2
+    %uint = OpTypeInt 32 0
+    %uint_2 = OpConstant %uint 2
+    %arr = OpTypeArray %mat %uint_2
+    %50 = OpTypeStruct %uint %float %arr
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+
+  EXPECT_TRUE(p->GetDecorationsForMember(50, 0).empty());
+  EXPECT_THAT(p->GetDecorationsForMember(50, 1),
+              UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+  EXPECT_THAT(p->GetDecorationsForMember(50, 2),
+              UnorderedElementsAre(Decoration{SpvDecorationColMajor},
+                                   Decoration{SpvDecorationMatrixStride, 8},
+                                   Decoration{SpvDecorationArrayStride, 16}));
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_Restrict) {
+  // RestrictPointer applies to a memory object declaration. Use a variable.
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %10 Restrict
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Workgroup %float
+    %10 = OpVariable %ptr Workgroup
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsFor(10);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_Restrict) {
+  // Restrict applies to a memory object declaration.
+  // But OpMemberDecorate can only be applied to a structure type.
+  // Test the reader's ability to be resilient to more than what SPIR-V allows.
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %10 0 Restrict
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  auto decorations = p->GetDecorationsForMember(10, 0);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_RestrictPointer) {
+  // RestrictPointer applies to a memory object declaration. Use a variable.
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %10 RestrictPointer
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Workgroup %float
+    %10 = OpVariable %ptr Workgroup
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  auto decorations = p->GetDecorationsFor(10);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_RestrictPointer) {
+  // RestrictPointer applies to a memory object declaration.
+  // But OpMemberDecorate can only be applied to a structure type.
+  // Test the reader's ability to be resilient to more than what SPIR-V allows.
+  auto p = parser(test::Assemble(R"(
+    OpMemberDecorate %10 0 RestrictPointer
+    %float = OpTypeFloat 32
+    %10 = OpTypeStruct %float
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  auto decorations = p->GetDecorationsFor(10);
+  EXPECT_TRUE(decorations.empty());
+  EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_handle_test.cc b/src/tint/reader/spirv/parser_impl_handle_test.cc
new file mode 100644
index 0000000..8906eec
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_handle_test.cc
@@ -0,0 +1,3955 @@
+// Copyright 2020 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 <ostream>
+
+#include "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::StartsWith;
+
+using SpvParserHandleTest = SpvParserTest;
+
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpCapability Sampled1D
+    OpCapability Image1D
+    OpCapability StorageImageExtendedFormats
+    OpCapability ImageQuery
+    OpMemoryModel Logical Simple
+  )";
+}
+
+std::string FragMain() {
+  return R"(
+    OpEntryPoint Fragment %main "main" ; assume no IO
+    OpExecutionMode %main OriginUpperLeft
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %main = OpFunction %void None %voidfn
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
+std::string CommonBasicTypes() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+
+    %int_0 = OpConstant %int 0
+    %int_1 = OpConstant %int 1
+    %int_2 = OpConstant %int 2
+    %int_3 = OpConstant %int 3
+    %int_4 = OpConstant %int 4
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+    %uint_3 = OpConstant %uint 3
+    %uint_4 = OpConstant %uint 4
+    %uint_100 = OpConstant %uint 100
+
+    %v2int = OpTypeVector %int 2
+    %v3int = OpTypeVector %int 3
+    %v4int = OpTypeVector %int 4
+    %v2uint = OpTypeVector %uint 2
+    %v3uint = OpTypeVector %uint 3
+    %v4uint = OpTypeVector %uint 4
+    %v2float = OpTypeVector %float 2
+    %v3float = OpTypeVector %float 3
+    %v4float = OpTypeVector %float 4
+
+    %float_null = OpConstantNull %float
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_7 = OpConstant %float 7
+    %v2float_null = OpConstantNull %v2float
+    %v3float_null = OpConstantNull %v3float
+    %v4float_null = OpConstantNull %v4float
+
+    %the_vi12 = OpConstantComposite %v2int %int_1 %int_2
+    %the_vi123 = OpConstantComposite %v3int %int_1 %int_2 %int_3
+    %the_vi1234 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4
+
+    %the_vu12 = OpConstantComposite %v2uint %uint_1 %uint_2
+    %the_vu123 = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_3
+    %the_vu1234 = OpConstantComposite %v4uint %uint_1 %uint_2 %uint_3 %uint_4
+
+    %the_vf12 = OpConstantComposite %v2float %float_1 %float_2
+    %the_vf21 = OpConstantComposite %v2float %float_2 %float_1
+    %the_vf123 = OpConstantComposite %v3float %float_1 %float_2 %float_3
+    %the_vf1234 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+
+
+    %depth = OpConstant %float 0.2
+  )";
+}
+
+std::string CommonImageTypes() {
+  return R"(
+
+; Define types for all sampler and texture types that can map to WGSL,
+; modulo texel formats for storage textures. For now, we limit
+; ourselves to 2-channel 32-bit texel formats.
+
+; Because the SPIR-V reader also already generalizes so it can work with
+; combined image-samplers, we also test that too.
+
+    %sampler = OpTypeSampler
+
+    ; sampled images
+    %f_texture_1d          = OpTypeImage %float 1D   0 0 0 1 Unknown
+    %f_texture_2d          = OpTypeImage %float 2D   0 0 0 1 Unknown
+    %f_texture_2d_ms       = OpTypeImage %float 2D   0 0 1 1 Unknown
+    %f_texture_2d_array    = OpTypeImage %float 2D   0 1 0 1 Unknown
+    %f_texture_2d_ms_array = OpTypeImage %float 2D   0 1 1 1 Unknown ; not in WebGPU
+    %f_texture_3d          = OpTypeImage %float 3D   0 0 0 1 Unknown
+    %f_texture_cube        = OpTypeImage %float Cube 0 0 0 1 Unknown
+    %f_texture_cube_array  = OpTypeImage %float Cube 0 1 0 1 Unknown
+
+    ; storage images
+    %f_storage_1d         = OpTypeImage %float 1D   0 0 0 2 Rg32f
+    %f_storage_2d         = OpTypeImage %float 2D   0 0 0 2 Rg32f
+    %f_storage_2d_array   = OpTypeImage %float 2D   0 1 0 2 Rg32f
+    %f_storage_3d         = OpTypeImage %float 3D   0 0 0 2 Rg32f
+
+    ; Now all the same, but for unsigned integer sampled type.
+
+    %u_texture_1d          = OpTypeImage %uint  1D   0 0 0 1 Unknown
+    %u_texture_2d          = OpTypeImage %uint  2D   0 0 0 1 Unknown
+    %u_texture_2d_ms       = OpTypeImage %uint  2D   0 0 1 1 Unknown
+    %u_texture_2d_array    = OpTypeImage %uint  2D   0 1 0 1 Unknown
+    %u_texture_2d_ms_array = OpTypeImage %uint  2D   0 1 1 1 Unknown ; not in WebGPU
+    %u_texture_3d          = OpTypeImage %uint  3D   0 0 0 1 Unknown
+    %u_texture_cube        = OpTypeImage %uint  Cube 0 0 0 1 Unknown
+    %u_texture_cube_array  = OpTypeImage %uint  Cube 0 1 0 1 Unknown
+
+    %u_storage_1d         = OpTypeImage %uint  1D   0 0 0 2 Rg32ui
+    %u_storage_2d         = OpTypeImage %uint  2D   0 0 0 2 Rg32ui
+    %u_storage_2d_array   = OpTypeImage %uint  2D   0 1 0 2 Rg32ui
+    %u_storage_3d         = OpTypeImage %uint  3D   0 0 0 2 Rg32ui
+
+    ; Now all the same, but for signed integer sampled type.
+
+    %i_texture_1d          = OpTypeImage %int  1D   0 0 0 1 Unknown
+    %i_texture_2d          = OpTypeImage %int  2D   0 0 0 1 Unknown
+    %i_texture_2d_ms       = OpTypeImage %int  2D   0 0 1 1 Unknown
+    %i_texture_2d_array    = OpTypeImage %int  2D   0 1 0 1 Unknown
+    %i_texture_2d_ms_array = OpTypeImage %int  2D   0 1 1 1 Unknown ; not in WebGPU
+    %i_texture_3d          = OpTypeImage %int  3D   0 0 0 1 Unknown
+    %i_texture_cube        = OpTypeImage %int  Cube 0 0 0 1 Unknown
+    %i_texture_cube_array  = OpTypeImage %int  Cube 0 1 0 1 Unknown
+
+    %i_storage_1d         = OpTypeImage %int  1D   0 0 0 2 Rg32i
+    %i_storage_2d         = OpTypeImage %int  2D   0 0 0 2 Rg32i
+    %i_storage_2d_array   = OpTypeImage %int  2D   0 1 0 2 Rg32i
+    %i_storage_3d         = OpTypeImage %int  3D   0 0 0 2 Rg32i
+
+    ;; Now pointers to each of the above, so we can declare variables for them.
+
+    %ptr_sampler = OpTypePointer UniformConstant %sampler
+
+    %ptr_f_texture_1d          = OpTypePointer UniformConstant %f_texture_1d
+    %ptr_f_texture_2d          = OpTypePointer UniformConstant %f_texture_2d
+    %ptr_f_texture_2d_ms       = OpTypePointer UniformConstant %f_texture_2d_ms
+    %ptr_f_texture_2d_array    = OpTypePointer UniformConstant %f_texture_2d_array
+    %ptr_f_texture_2d_ms_array = OpTypePointer UniformConstant %f_texture_2d_ms_array
+    %ptr_f_texture_3d          = OpTypePointer UniformConstant %f_texture_3d
+    %ptr_f_texture_cube        = OpTypePointer UniformConstant %f_texture_cube
+    %ptr_f_texture_cube_array  = OpTypePointer UniformConstant %f_texture_cube_array
+
+    ; storage images
+    %ptr_f_storage_1d         = OpTypePointer UniformConstant %f_storage_1d
+    %ptr_f_storage_2d         = OpTypePointer UniformConstant %f_storage_2d
+    %ptr_f_storage_2d_array   = OpTypePointer UniformConstant %f_storage_2d_array
+    %ptr_f_storage_3d         = OpTypePointer UniformConstant %f_storage_3d
+
+    ; Now all the same, but for unsigned integer sampled type.
+
+    %ptr_u_texture_1d          = OpTypePointer UniformConstant %u_texture_1d
+    %ptr_u_texture_2d          = OpTypePointer UniformConstant %u_texture_2d
+    %ptr_u_texture_2d_ms       = OpTypePointer UniformConstant %u_texture_2d_ms
+    %ptr_u_texture_2d_array    = OpTypePointer UniformConstant %u_texture_2d_array
+    %ptr_u_texture_2d_ms_array = OpTypePointer UniformConstant %u_texture_2d_ms_array
+    %ptr_u_texture_3d          = OpTypePointer UniformConstant %u_texture_3d
+    %ptr_u_texture_cube        = OpTypePointer UniformConstant %u_texture_cube
+    %ptr_u_texture_cube_array  = OpTypePointer UniformConstant %u_texture_cube_array
+
+    %ptr_u_storage_1d         = OpTypePointer UniformConstant %u_storage_1d
+    %ptr_u_storage_2d         = OpTypePointer UniformConstant %u_storage_2d
+    %ptr_u_storage_2d_array   = OpTypePointer UniformConstant %u_storage_2d_array
+    %ptr_u_storage_3d         = OpTypePointer UniformConstant %u_storage_3d
+
+    ; Now all the same, but for signed integer sampled type.
+
+    %ptr_i_texture_1d          = OpTypePointer UniformConstant %i_texture_1d
+    %ptr_i_texture_2d          = OpTypePointer UniformConstant %i_texture_2d
+    %ptr_i_texture_2d_ms       = OpTypePointer UniformConstant %i_texture_2d_ms
+    %ptr_i_texture_2d_array    = OpTypePointer UniformConstant %i_texture_2d_array
+    %ptr_i_texture_2d_ms_array = OpTypePointer UniformConstant %i_texture_2d_ms_array
+    %ptr_i_texture_3d          = OpTypePointer UniformConstant %i_texture_3d
+    %ptr_i_texture_cube        = OpTypePointer UniformConstant %i_texture_cube
+    %ptr_i_texture_cube_array  = OpTypePointer UniformConstant %i_texture_cube_array
+
+    %ptr_i_storage_1d         = OpTypePointer UniformConstant %i_storage_1d
+    %ptr_i_storage_2d         = OpTypePointer UniformConstant %i_storage_2d
+    %ptr_i_storage_2d_array   = OpTypePointer UniformConstant %i_storage_2d_array
+    %ptr_i_storage_3d         = OpTypePointer UniformConstant %i_storage_3d
+
+  )";
+}
+
+std::string CommonTypes() {
+  return CommonBasicTypes() + CommonImageTypes();
+}
+
+std::string Bindings(std::vector<uint32_t> ids) {
+  std::ostringstream os;
+  int binding = 0;
+  for (auto id : ids) {
+    os << "  OpDecorate %" << id << " DescriptorSet 0\n"
+       << "  OpDecorate %" << id << " Binding " << binding++ << "\n";
+  }
+  return os.str();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_WellFormedButNotAHandle) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %10 = OpConstantNull %ptr_sampler
+     %20 = OpConstantNull %ptr_f_texture_1d
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule()) << assembly;
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
+
+  EXPECT_EQ(sampler, nullptr);
+  EXPECT_EQ(image, nullptr);
+  EXPECT_TRUE(p->error().empty());
+
+  p->DeliberatelyInvalidSpirv();  // WGSL does not have null pointers.
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_Direct) {
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_AccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // WGSL does not support arrays of textures and samplers.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_InBoundsAccessChain) {
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // WGSL does not support arrays of textures and samplers.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_PtrAccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  // Use VariablePointers for the OpInBoundsPtrAccessChain.
+  const auto assembly = "OpCapability VariablePointers " + Preamble() +
+                        FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // Variable pointers is not allowed for WGSL. So don't dump it.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_InBoundsPtrAccessChain) {
+  // Use VariablePointers for the OpInBoundsPtrAccessChain.
+  const auto assembly = "OpCapability VariablePointers " + Preamble() +
+                        FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // Variable pointers is not allowed for WGSL. So don't dump it.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_CopyObject) {
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpCopyObject %ptr_sampler %10
+     %120 = OpCopyObject %ptr_f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserHandleTest, GetMemoryObjectDeclarationForHandle_Variable_Load) {
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpLoad %sampler %10
+     %120 = OpLoad %f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_SampledImage) {
+  // Trace through the sampled image instruction, but in two different
+  // directions.
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_Variable_Image) {
+  const auto assembly =
+      Preamble() + FragMain() + Bindings({10, 20}) + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+     %200 = OpImage %f_texture_1d %100
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_Direct) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  p->SkipDumpingPending("crbug.com/tint/1039");
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_AccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // WGSL does not support arrays of textures or samplers
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsAccessChain) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // WGSL does not support arrays of textures or samplers
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_PtrAccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // Variable pointers is not allowed for WGSL. So don't dump it.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsPtrAccessChain) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  // Variable pointers is not allowed for WGSL. So don't dump it.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_CopyObject) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %110 = OpCopyObject %ptr_sampler %10
+     %120 = OpCopyObject %ptr_f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  p->SkipDumpingPending("crbug.com/tint/1039");
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_Load) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %110 = OpLoad %sampler %10
+     %120 = OpLoad %f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  p->SkipDumpingPending("crbug.com/tint/1039");
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_SampledImage) {
+  // Trace through the sampled image instruction, but in two different
+  // directions.
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  p->SkipDumpingPending("crbug.com/tint/1039");
+}
+
+TEST_F(SpvParserHandleTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_Image) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+     %200 = OpImage %f_texture_1d %100
+
+     OpReturn
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+
+  p->SkipDumpingPending("crbug.com/tint/1039");
+}
+
+// Test RegisterHandleUsage, sampled image cases
+
+struct UsageImageAccessCase {
+  std::string inst;
+  std::string expected_sampler_usage;
+  std::string expected_image_usage;
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                const UsageImageAccessCase& c) {
+  out << "UsageImageAccessCase(" << c.inst << ", " << c.expected_sampler_usage
+      << ", " << c.expected_image_usage << ")";
+  return out;
+}
+
+using SpvParserHandleTest_RegisterHandleUsage_SampledImage =
+    SpvParserTestBase<::testing::TestWithParam<UsageImageAccessCase>>;
+
+TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage, Variable) {
+  const std::string inst = GetParam().inst;
+  const auto assembly = Preamble() + FragMain() + Bindings({10, 20}) +
+                        CommonTypes() + R"(
+     %si_ty = OpTypeSampledImage %f_texture_2d
+     %coords = OpConstantNull %v2float
+     %coords3d = OpConstantNull %v3float ; needed for Proj variants
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_2d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %f_texture_2d %20
+     %sampled_image = OpSampledImage %si_ty %im %sam
+)" + GetParam().inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterHandleUsage());
+  EXPECT_TRUE(p->error().empty());
+  Usage su = p->GetHandleUsage(10);
+  Usage iu = p->GetHandleUsage(20);
+
+  EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
+  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
+
+  if (inst.find("ImageQueryLod") != std::string::npos) {
+    // WGSL does not support querying image level of detail.
+    // So don't emit them as part of a "passing" corpus.
+    p->DeliberatelyInvalidSpirv();
+  }
+  if (inst.find("ImageSampleDrefExplicitLod") != std::string::npos) {
+    p->SkipDumpingPending("crbug.com/tint/425");  // gpuweb issue #1319
+  }
+}
+
+TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage, FunctionParam) {
+  const std::string inst = GetParam().inst;
+  const auto assembly = Preamble() + FragMain() + Bindings({10, 20}) +
+                        CommonTypes() + R"(
+     %f_ty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_2d
+     %si_ty = OpTypeSampledImage %f_texture_2d
+     %coords = OpConstantNull %v2float
+     %coords3d = OpConstantNull %v3float ; needed for Proj variants
+     %component = OpConstant %uint 1
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_2d UniformConstant
+
+     %func = OpFunction %void None %f_ty
+     %110 = OpFunctionParameter %ptr_sampler
+     %120 = OpFunctionParameter %ptr_f_texture_2d
+     %func_entry = OpLabel
+     %sam = OpLoad %sampler %110
+     %im = OpLoad %f_texture_2d %120
+     %sampled_image = OpSampledImage %si_ty %im %sam
+
+)" + inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %foo = OpFunctionCall %void %func %10 %20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule()) << p->error() << assembly << std::endl;
+  EXPECT_TRUE(p->RegisterHandleUsage()) << p->error() << assembly << std::endl;
+  EXPECT_TRUE(p->error().empty()) << p->error() << assembly << std::endl;
+  Usage su = p->GetHandleUsage(10);
+  Usage iu = p->GetHandleUsage(20);
+
+  EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
+  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
+
+  if (inst.find("ImageQueryLod") != std::string::npos) {
+    // WGSL does not support querying image level of detail.
+    // So don't emit them as part of a "passing" corpus.
+    p->DeliberatelyInvalidSpirv();
+  }
+  p->SkipDumpingPending("crbug.com/tint/785");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Samples,
+    SpvParserHandleTest_RegisterHandleUsage_SampledImage,
+    ::testing::Values(
+
+        // OpImageGather
+        UsageImageAccessCase{"%result = OpImageGather "
+                             "%v4float %sampled_image %coords %uint_1",
+                             "Usage(Sampler( ))",
+                             "Usage(Texture( is_sampled ))"},
+        // OpImageDrefGather
+        UsageImageAccessCase{"%result = OpImageDrefGather "
+                             "%v4float %sampled_image %coords %depth",
+                             "Usage(Sampler( comparison ))",
+                             "Usage(Texture( is_sampled depth ))"},
+
+        // Sample the texture.
+
+        // OpImageSampleImplicitLod
+        UsageImageAccessCase{"%result = OpImageSampleImplicitLod "
+                             "%v4float %sampled_image %coords",
+                             "Usage(Sampler( ))",
+                             "Usage(Texture( is_sampled ))"},
+        // OpImageSampleExplicitLod
+        UsageImageAccessCase{"%result = OpImageSampleExplicitLod "
+                             "%v4float %sampled_image %coords Lod %float_null",
+                             "Usage(Sampler( ))",
+                             "Usage(Texture( is_sampled ))"},
+        // OpImageSampleDrefImplicitLod
+        UsageImageAccessCase{"%result = OpImageSampleDrefImplicitLod "
+                             "%float %sampled_image %coords %depth",
+                             "Usage(Sampler( comparison ))",
+                             "Usage(Texture( is_sampled depth ))"},
+        // OpImageSampleDrefExplicitLod
+        UsageImageAccessCase{
+            "%result = OpImageSampleDrefExplicitLod "
+            "%float %sampled_image %coords %depth Lod %float_null",
+            "Usage(Sampler( comparison ))",
+            "Usage(Texture( is_sampled depth ))"},
+
+        // Sample the texture, with *Proj* variants, even though WGSL doesn't
+        // support them.
+
+        // OpImageSampleProjImplicitLod
+        UsageImageAccessCase{"%result = OpImageSampleProjImplicitLod "
+                             "%v4float %sampled_image %coords3d",
+                             "Usage(Sampler( ))",
+                             "Usage(Texture( is_sampled ))"},
+        // OpImageSampleProjExplicitLod
+        UsageImageAccessCase{
+            "%result = OpImageSampleProjExplicitLod "
+            "%v4float %sampled_image %coords3d Lod %float_null",
+            "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"},
+        // OpImageSampleProjDrefImplicitLod
+        UsageImageAccessCase{"%result = OpImageSampleProjDrefImplicitLod "
+                             "%float %sampled_image %coords3d %depth",
+                             "Usage(Sampler( comparison ))",
+                             "Usage(Texture( is_sampled depth ))"},
+        // OpImageSampleProjDrefExplicitLod
+        UsageImageAccessCase{
+            "%result = OpImageSampleProjDrefExplicitLod "
+            "%float %sampled_image %coords3d %depth Lod %float_null",
+            "Usage(Sampler( comparison ))",
+            "Usage(Texture( is_sampled depth ))"},
+
+        // OpImageQueryLod
+        UsageImageAccessCase{
+            "%result = OpImageQueryLod %v2float %sampled_image %coords",
+            "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}));
+
+// Test RegisterHandleUsage, raw image cases.
+// For these we test the use of an image value directly, and not combined
+// with the sampler. The image still could be of sampled image type.
+
+struct UsageRawImageCase {
+  std::string type;  // Example: f_storage_1d or f_texture_1d
+  std::string inst;
+  std::string expected_image_usage;
+};
+inline std::ostream& operator<<(std::ostream& out, const UsageRawImageCase& c) {
+  out << "UsageRawImageCase(" << c.type << ", " << c.inst << ", "
+      << c.expected_image_usage << ")";
+  return out;
+}
+
+using SpvParserHandleTest_RegisterHandleUsage_RawImage =
+    SpvParserTestBase<::testing::TestWithParam<UsageRawImageCase>>;
+
+TEST_P(SpvParserHandleTest_RegisterHandleUsage_RawImage, Variable) {
+  const bool is_storage = GetParam().type.find("storage") != std::string::npos;
+  const bool is_write = GetParam().inst.find("ImageWrite") != std::string::npos;
+  const auto assembly = Preamble() + FragMain() + Bindings({20}) +
+                        (is_storage ? std::string("OpDecorate %20 ") +
+                                          std::string(is_write ? "NonReadable"
+                                                               : "NonWritable")
+                                    : std::string("")) +
+                        " " + CommonTypes() + R"(
+     %20 = OpVariable %ptr_)" +
+                        GetParam().type + R"( UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %im = OpLoad %)" + GetParam().type +
+                        R"( %20
+)" + GetParam().inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterHandleUsage());
+  EXPECT_TRUE(p->error().empty());
+
+  Usage iu = p->GetHandleUsage(20);
+  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
+
+  Usage su = p->GetHandleUsage(20);
+}
+
+TEST_P(SpvParserHandleTest_RegisterHandleUsage_RawImage, FunctionParam) {
+  const bool is_storage = GetParam().type.find("storage") != std::string::npos;
+  const bool is_write = GetParam().inst.find("ImageWrite") != std::string::npos;
+  const auto assembly = Preamble() + FragMain() + Bindings({20}) +
+                        (is_storage ? std::string("OpDecorate %20 ") +
+                                          std::string(is_write ? "NonReadable"
+                                                               : "NonWritable")
+                                    : std::string("")) +
+                        " " + CommonTypes() + R"(
+     %f_ty = OpTypeFunction %void %ptr_)" +
+                        GetParam().type + R"(
+
+     %20 = OpVariable %ptr_)" +
+                        GetParam().type + R"( UniformConstant
+
+     %func = OpFunction %void None %f_ty
+     %i_param = OpFunctionParameter %ptr_)" +
+                        GetParam().type + R"(
+     %func_entry = OpLabel
+     %im = OpLoad %)" + GetParam().type +
+                        R"( %i_param
+
+)" + GetParam().inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %foo = OpFunctionCall %void %func %20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterHandleUsage());
+  EXPECT_TRUE(p->error().empty());
+  Usage iu = p->GetHandleUsage(20);
+
+  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
+
+  // Textures and samplers not yet supported as function parameters.
+  p->SkipDumpingPending("crbug.com/tint/785");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Samples,
+    SpvParserHandleTest_RegisterHandleUsage_RawImage,
+    ::testing::Values(
+
+        // OpImageRead
+        UsageRawImageCase{"f_storage_1d",
+                          "%result = OpImageRead %v4float %im %uint_1",
+                          "Usage(Texture( read ))"},
+
+        // OpImageWrite
+        UsageRawImageCase{"f_storage_1d",
+                          "OpImageWrite %im %uint_1 %v4float_null",
+                          "Usage(Texture( write ))"},
+
+        // OpImageFetch
+        UsageRawImageCase{"f_texture_1d",
+                          "%result = OpImageFetch "
+                          "%v4float %im %uint_0",
+                          "Usage(Texture( is_sampled ))"},
+
+        // Image queries
+
+        // OpImageQuerySizeLod
+        UsageRawImageCase{"f_texture_2d",
+                          "%result = OpImageQuerySizeLod "
+                          "%v2uint %im %uint_1",
+                          "Usage(Texture( is_sampled ))"},
+
+        // OpImageQuerySize
+        // Could be MS=1 or storage image. So it's non-committal.
+        UsageRawImageCase{"f_storage_2d",
+                          "%result = OpImageQuerySize "
+                          "%v2uint %im",
+                          "Usage()"},
+
+        // OpImageQueryLevels
+        UsageRawImageCase{"f_texture_2d",
+                          "%result = OpImageQueryLevels "
+                          "%uint %im",
+                          "Usage(Texture( ))"},
+
+        // OpImageQuerySamples
+        UsageRawImageCase{"f_texture_2d_ms",
+                          "%result = OpImageQuerySamples "
+                          "%uint %im",
+                          "Usage(Texture( is_sampled ms ))"}));
+
+// Test emission of handle variables.
+
+// Test emission of variables where we don't have enough clues from their
+// use in image access instructions in executable code.  For these we have
+// to infer usage from the SPIR-V sampler or image type.
+struct DeclUnderspecifiedHandleCase {
+  std::string decorations;  // SPIR-V decorations
+  std::string inst;         // SPIR-V variable declarations
+  std::string var_decl;     // WGSL variable declaration
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                const DeclUnderspecifiedHandleCase& c) {
+  out << "DeclUnderspecifiedHandleCase(" << c.inst << "\n" << c.var_decl << ")";
+  return out;
+}
+
+using SpvParserHandleTest_DeclUnderspecifiedHandle =
+    SpvParserTestBase<::testing::TestWithParam<DeclUnderspecifiedHandleCase>>;
+
+TEST_P(SpvParserHandleTest_DeclUnderspecifiedHandle, Variable) {
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+)" + GetParam().decorations +
+                        CommonTypes() + GetParam().inst +
+                        R"(
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto program = test::ToString(p->program());
+  EXPECT_THAT(program, HasSubstr(GetParam().var_decl)) << program;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Samplers,
+    SpvParserHandleTest_DeclUnderspecifiedHandle,
+    ::testing::Values(
+
+        DeclUnderspecifiedHandleCase{
+            "", R"(
+         %ptr = OpTypePointer UniformConstant %sampler
+         %10 = OpVariable %ptr UniformConstant
+)",
+            R"(@group(0) @binding(0) var x_10 : sampler;)"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    Images,
+    SpvParserHandleTest_DeclUnderspecifiedHandle,
+    ::testing::Values(
+
+        DeclUnderspecifiedHandleCase{
+            "", R"(
+         %10 = OpVariable %ptr_f_texture_1d UniformConstant
+)",
+            R"(@group(0) @binding(0) var x_10 : texture_1d<f32>;)"},
+        DeclUnderspecifiedHandleCase{
+            R"(
+         OpDecorate %10 NonWritable
+)",
+            R"(
+         %10 = OpVariable %ptr_f_storage_1d UniformConstant
+)",
+            R"(@group(0) @binding(0) var x_10 : texture_1d<f32>;)"},
+        DeclUnderspecifiedHandleCase{
+            R"(
+         OpDecorate %10 NonReadable
+)",
+            R"(
+         %10 = OpVariable %ptr_f_storage_1d UniformConstant
+)",
+            R"(@group(0) @binding(0) var x_10 : texture_storage_1d<rg32float, write>;)"}));
+
+// Test handle declaration or error, when there is an image access.
+
+struct ImageDeclCase {
+  // SPIR-V image type, excluding result ID and opcode
+  std::string spirv_image_type_details;
+  std::string spirv_image_access;  // Optional instruction to provoke use
+  std::string expected_error;
+  std::string expected_decl;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const ImageDeclCase& c) {
+  out << "ImageDeclCase(" << c.spirv_image_type_details << "\n"
+      << "access: " << c.spirv_image_access << "\n"
+      << "error: " << c.expected_error << "\n"
+      << "decl:" << c.expected_decl << "\n)";
+  return out;
+}
+
+using SpvParserHandleTest_ImageDeclTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageDeclCase>>;
+
+TEST_P(SpvParserHandleTest_ImageDeclTest, DeclareAndUseHandle) {
+  // Only declare the sampled image type, and the associated variable
+  // if the requested image type is a sampled image type and not multisampled.
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
+  const auto assembly =
+      Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpName %float_var "float_var"
+     OpName %ptr_float "ptr_float"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %f1 "f1"
+     OpName %vf12 "vf12"
+     OpName %vf123 "vf123"
+     OpName %vf1234 "vf1234"
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+     OpDecorate %30 DescriptorSet 0
+     OpDecorate %30 Binding 1
+)" + CommonBasicTypes() +
+      R"(
+     %sampler = OpTypeSampler
+     %ptr_sampler = OpTypePointer UniformConstant %sampler
+     %im_ty = OpTypeImage )" +
+      GetParam().spirv_image_type_details + R"(
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+      R"(
+
+     %ptr_float = OpTypePointer Function %float
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_im_ty UniformConstant
+     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %float_var = OpVariable %ptr_float Function
+
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
+     %f1 = OpCopyObject %float %float_1
+     %vf12 = OpCopyObject %v2float %the_vf12
+     %vf123 = OpCopyObject %v3float %the_vf123
+     %vf1234 = OpCopyObject %v4float %the_vf1234
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %im_ty %20
+
+)" +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
+           : "") +
+      GetParam().spirv_image_access +
+      R"(
+     ; Use an anchor for the cases when the image access doesn't have a result ID.
+     %1000 = OpCopyObject %uint %uint_0
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  const bool succeeded = p->BuildAndParseInternalModule();
+  if (succeeded) {
+    EXPECT_TRUE(GetParam().expected_error.empty());
+    const auto got = test::ToString(p->program());
+    EXPECT_THAT(got, HasSubstr(GetParam().expected_decl));
+  } else {
+    EXPECT_FALSE(GetParam().expected_error.empty());
+    EXPECT_THAT(p->error(), HasSubstr(GetParam().expected_error));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Multisampled_Only2DNonArrayedIsValid,
+    SpvParserHandleTest_ImageDeclTest,
+    ::testing::ValuesIn(std::vector<ImageDeclCase>{
+        {"%float 1D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 1D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
+        {"%float 2D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "", "@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;"},
+        {"%float 2D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 3D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 3D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
+        {"%float Cube 0 0 1 1 Unknown",
+         "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float Cube 0 1 1 1 Unknown",
+         "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""}}));
+
+// Test emission of variables when we have image accesses in executable code.
+
+struct ImageAccessCase {
+  // SPIR-V image type, excluding result ID and opcode
+  std::string spirv_image_type_details;
+  std::string spirv_image_access;  // The provoking image access instruction.
+  std::string var_decl;            // WGSL variable declaration
+  std::string texture_builtin;     // WGSL texture usage.
+};
+inline std::ostream& operator<<(std::ostream& out, const ImageAccessCase& c) {
+  out << "ImageCase(" << c.spirv_image_type_details << "\n"
+      << c.spirv_image_access << "\n"
+      << c.var_decl << "\n"
+      << c.texture_builtin << ")";
+  return out;
+}
+
+using SpvParserHandleTest_SampledImageAccessTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageAccessCase>>;
+
+TEST_P(SpvParserHandleTest_SampledImageAccessTest, Variable) {
+  // Only declare the sampled image type, and the associated variable
+  // if the requested image type is a sampled image type, and not a
+  // multisampled texture
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
+  const auto assembly =
+      Preamble() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+     OpName %f1 "f1"
+     OpName %vf12 "vf12"
+     OpName %vf21 "vf21"
+     OpName %vf123 "vf123"
+     OpName %vf1234 "vf1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
+     OpName %coords1 "coords1"
+     OpName %coords12 "coords12"
+     OpName %coords123 "coords123"
+     OpName %coords1234 "coords1234"
+     OpName %offsets2d "offsets2d"
+     OpName %u_offsets2d "u_offsets2d"
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+     OpDecorate %30 DescriptorSet 0
+     OpDecorate %30 Binding 1
+)" + CommonBasicTypes() +
+      R"(
+     %sampler = OpTypeSampler
+     %ptr_sampler = OpTypePointer UniformConstant %sampler
+     %im_ty = OpTypeImage )" +
+      GetParam().spirv_image_type_details + R"(
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+      R"(
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_im_ty UniformConstant
+     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
+
+     ; ConstOffset operands must be constants
+     %offsets2d = OpConstantComposite %v2int %int_3 %int_4
+     %u_offsets2d = OpConstantComposite %v2uint %uint_3 %uint_4
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %f1 = OpCopyObject %float %float_1
+     %vf12 = OpCopyObject %v2float %the_vf12
+     %vf21 = OpCopyObject %v2float %the_vf21
+     %vf123 = OpCopyObject %v3float %the_vf123
+     %vf1234 = OpCopyObject %v4float %the_vf1234
+
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
+     %coords1 = OpCopyObject %float %float_1
+     %coords12 = OpCopyObject %v2float %vf12
+     %coords123 = OpCopyObject %v3float %vf123
+     %coords1234 = OpCopyObject %v4float %vf1234
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %im_ty %20
+)" +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam\n"
+           : "") +
+      GetParam().spirv_image_access +
+      R"(
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto program = test::ToString(p->program());
+  EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
+      << "DECLARATIONS ARE BAD " << program;
+  EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
+      << "TEXTURE BUILTIN IS BAD " << program << assembly;
+
+  const bool is_query_size =
+      GetParam().spirv_image_access.find("ImageQuerySize") != std::string::npos;
+  const bool is_1d =
+      GetParam().spirv_image_type_details.find("1D") != std::string::npos;
+  if (is_query_size && is_1d) {
+    p->SkipDumpingPending("crbug.com/tint/788");
+  }
+}
+
+// TODO(dneto): Test variable declaration and texture builtins provoked by
+// use of an image access instruction inside helper function.
+TEST_P(SpvParserHandleTest_RegisterHandleUsage_SampledImage,
+       DISABLED_FunctionParam) {}
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageGather,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // OpImageGather 2D
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+                        "textureGather(1, x_20, x_10, coords12)"},
+        // OpImageGather 2D ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            "textureGather(1, x_20, x_10, coords12, vec2<i32>(3, 4))"},
+        // OpImageGather 2D ConstOffset unsigned
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+                        "textureGather(1, x_20, x_10, coords12, "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather 2D Array
+        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)))"},
+        // OpImageGather 2D Array ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            "textureGather(1, x_20, x_10, coords123.xy, "
+            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
+        // OpImageGather 2D Array ConstOffset unsigned
+        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather Cube
+        ImageAccessCase{"%float Cube 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123)"},
+        // OpImageGather Cube Array
+        ImageAccessCase{"%float Cube 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords1234 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords1234.xyz, "
+                        "i32(round(coords1234.w)))"},
+        // OpImageGather 2DDepth
+        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+                        "textureGather(x_20, x_10, coords12)"},
+        // OpImageGather 2DDepth ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+            "textureGather(x_20, x_10, coords12, vec2<i32>(3, 4))"},
+        // OpImageGather 2DDepth ConstOffset unsigned
+        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+                        "textureGather(x_20, x_10, coords12, "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather 2DDepth Array
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+                        "textureGather(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)))"},
+        // OpImageGather 2DDepth Array ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 1 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            "textureGather(x_20, x_10, coords123.xy, "
+            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
+        // OpImageGather 2DDepth Array ConstOffset unsigned
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+                        "textureGather(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather DepthCube
+        ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube;)",
+                        "textureGather(x_20, x_10, coords123)"},
+        // OpImageGather DepthCube Array
+        ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords1234 %int_1",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
+                        "textureGather(x_20, x_10, coords1234.xyz, "
+                        "i32(round(coords1234.w)))"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageDrefGather,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // OpImageDrefGather 2DDepth
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageDrefGather "
+            "%v4float %sampled_image %coords12 %depth",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+            "textureGatherCompare(x_20, x_10, coords12, 0.200000003)"},
+        // OpImageDrefGather 2DDepth ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageDrefGather "
+            "%v4float %sampled_image %coords12 %depth ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+            "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
+            "vec2<i32>(3, 4))"},
+        // OpImageDrefGather 2DDepth ConstOffset unsigned
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageDrefGather "
+            "%v4float %sampled_image %coords12 %depth ConstOffset "
+            "%u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+            "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
+            "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageDrefGather 2DDepth Array
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageDrefGather "
+                        "%v4float %sampled_image %coords123 %depth",
+                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+                        "textureGatherCompare(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), 0.200000003)"},
+        // OpImageDrefGather 2DDepth Array ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 1 0 1 Unknown",
+            "%result = OpImageDrefGather "
+            "%v4float %sampled_image %coords123 %depth ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            "textureGatherCompare(x_20, x_10, coords123.xy, "
+            "i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4))"},
+        // OpImageDrefGather 2DDepth Array ConstOffset unsigned
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageDrefGather "
+                        "%v4float %sampled_image %coords123 %depth ConstOffset "
+                        "%u_offsets2d",
+                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+                        "textureGatherCompare(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), 0.200000003, "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageDrefGather DepthCube
+        ImageAccessCase{
+            "%float Cube 1 0 0 1 Unknown",
+            "%result = OpImageDrefGather "
+            "%v4float %sampled_image %coords123 %depth",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube;)",
+            "textureGatherCompare(x_20, x_10, coords123, 0.200000003)"},
+        // OpImageDrefGather DepthCube Array
+        ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
+                        "%result = OpImageDrefGather "
+                        "%v4float %sampled_image %coords1234 %depth",
+                        R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
+                        "textureGatherCompare(x_20, x_10, coords1234.xyz, "
+                        "i32(round(coords1234.w)), 0.200000003)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleImplicitLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleImplicitLod
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageSampleImplicitLod "
+                        "%v4float %sampled_image %coords12",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+                        "textureSample(x_20, x_10, coords12)"},
+
+        // OpImageSampleImplicitLod arrayed
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords123",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            "textureSample(x_20, x_10, coords123.xy, i32(round(coords123.z)))"},
+
+        // OpImageSampleImplicitLod with ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords12 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            "textureSample(x_20, x_10, coords12, vec2<i32>(3, 4))"},
+
+        // OpImageSampleImplicitLod arrayed with ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords123 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSample(x_20, x_10, coords123.xy, i32(round(coords123.z)), vec2<i32>(3, 4)))"},
+
+        // OpImageSampleImplicitLod with Bias
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageSampleImplicitLod "
+                        "%v4float %sampled_image %coords12 Bias %float_7",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+                        "textureSampleBias(x_20, x_10, coords12, 7.0)"},
+
+        // OpImageSampleImplicitLod arrayed with Bias
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords123 Bias %float_7",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0))"},
+
+        // OpImageSampleImplicitLod with Bias and signed ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords12 Bias|ConstOffset "
+            "%float_7 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(3, 4))"},
+
+        // OpImageSampleImplicitLod with Bias and unsigned ConstOffset
+        // Convert ConstOffset to signed
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords12 Bias|ConstOffset "
+            "%float_7 %u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageSampleImplicitLod arrayed with Bias
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords123 Bias|ConstOffset "
+            "%float_7 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0, vec2<i32>(3, 4))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // This test shows the use of a sampled image used with both regular
+    // sampling and depth-reference sampling.  The texture is a depth-texture,
+    // and we use builtins textureSample and textureSampleCompare
+    ImageSampleImplicitLod_BothDrefAndNonDref,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleImplicitLod
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown", R"(
+     %sam_dref = OpLoad %sampler %30
+     %sampled_dref_image = OpSampledImage %si_ty %im %sam_dref
+
+     %200 = OpImageSampleImplicitLod %v4float %sampled_image %coords12
+     %210 = OpImageSampleDrefImplicitLod %float %sampled_dref_image %coords12 %depth
+)",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+
+@group(0) @binding(1) var x_30 : sampler_comparison;
+)",
+                        R"(
+  let x_200 : vec4<f32> = vec4<f32>(textureSample(x_20, x_10, coords12), 0.0, 0.0, 0.0);
+  let x_210 : f32 = textureSampleCompare(x_20, x_30, coords12, 0.200000003);
+)"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleDrefImplicitLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+        // ImageSampleDrefImplicitLod
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleDrefImplicitLod "
+            "%float %sampled_image %coords12 %depth",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003))"},
+        // ImageSampleDrefImplicitLod - arrayed
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleDrefImplicitLod "
+            "%float %sampled_image %coords123 %depth",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
+        // ImageSampleDrefImplicitLod with ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleDrefImplicitLod %float "
+            "%sampled_image %coords12 %depth ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003, vec2<i32>(3, 4)))"},
+        // ImageSampleDrefImplicitLod arrayed with ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleDrefImplicitLod %float "
+            "%sampled_image %coords123 %depth ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleDrefExplicitLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    // Lod must be float constant 0 due to a Metal constraint.
+    // Another test checks cases where the Lod is not float constant 0.
+    ::testing::Values(
+        // 2D
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod "
+            "%float %sampled_image %coords12 %depth Lod %float_0",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003))"},
+        // 2D array
+        ImageAccessCase{
+            "%float 2D 1 1 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod "
+            "%float %sampled_image %coords123 %depth Lod %float_0",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
+        // 2D, ConstOffset
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod %float "
+            "%sampled_image %coords12 %depth Lod|ConstOffset "
+            "%float_0 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003, vec2<i32>(3, 4)))"},
+        // 2D array, ConstOffset
+        ImageAccessCase{
+            "%float 2D 1 1 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod %float "
+            "%sampled_image %coords123 %depth Lod|ConstOffset "
+            "%float_0 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3, 4)))"},
+        // Cube
+        ImageAccessCase{
+            "%float Cube 1 0 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod "
+            "%float %sampled_image %coords123 %depth Lod %float_0",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube;)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords123, 0.200000003))"},
+        // Cube array
+        ImageAccessCase{
+            "%float Cube 1 1 0 1 Unknown",
+            "%result = OpImageSampleDrefExplicitLod "
+            "%float %sampled_image %coords1234 %depth Lod %float_0",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
+            R"(textureSampleCompareLevel(x_20, x_10, coords1234.xyz, i32(round(coords1234.w)), 0.200000003))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleExplicitLod_UsingLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleExplicitLod - using Lod
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageSampleExplicitLod "
+                        "%v4float %sampled_image %coords12 Lod %float_null",
+                        R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0))"},
+
+        // OpImageSampleExplicitLod arrayed - using Lod
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords123 Lod %float_null",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0))"},
+
+        // OpImageSampleExplicitLod - using Lod and ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords12 Lod|ConstOffset "
+            "%float_null %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(3, 4)))"},
+
+        // OpImageSampleExplicitLod - using Lod and unsigned ConstOffset
+        // Convert the ConstOffset operand to signed
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords12 Lod|ConstOffset "
+            "%float_null %u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
+
+        // OpImageSampleExplicitLod arrayed - using Lod and ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords123 Lod|ConstOffset "
+            "%float_null %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0, vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleExplicitLod_UsingGrad,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleExplicitLod - using Grad
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords12 Grad %vf12 %vf21",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21))"},
+
+        // OpImageSampleExplicitLod arrayed - using Grad
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords123 Grad %vf12 %vf21",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21))"},
+
+        // OpImageSampleExplicitLod - using Grad and ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords12 Grad|ConstOffset "
+            "%vf12 %vf21 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2<i32>(3, 4)))"},
+
+        // OpImageSampleExplicitLod - using Grad and unsigned ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords12 Grad|ConstOffset "
+            "%vf12 %vf21 %u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2<i32>(vec2<u32>(3u, 4u)))"},
+
+        // OpImageSampleExplicitLod arrayed - using Grad and ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords123 Grad|ConstOffset "
+            "%vf12 %vf21 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2<i32>(3, 4)))"},
+
+        // OpImageSampleExplicitLod arrayed - using Grad and unsigned
+        // ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords123 Grad|ConstOffset "
+            "%vf12 %vf21 %u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2<i32>(vec2<u32>(3u, 4u))))"}));
+
+// Test crbug.com/378:
+// In WGSL, sampling from depth texture with explicit level of detail
+// requires the Lod parameter as an unsigned integer.
+// This corresponds to SPIR-V OpSampleExplicitLod and WGSL textureSampleLevel.
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleExplicitLod_DepthTexture,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Test a non-depth case.
+        // (This is already tested above in the ImageSampleExplicitLod suite,
+        // but I'm repeating here for the contrast with the depth case.)
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4float "
+         "%sampled_image %vf12 Lod %f1",
+         R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(textureSampleLevel(x_20, x_10, vf12, f1))"},
+        // Test a depth case
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4float "
+         "%sampled_image %vf12 Lod %f1",
+         R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+         R"(vec4<f32>(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0, 0.0, 0.0))"}}));
+
+/////
+// Projection sampling
+/////
+
+// Test matrix for projection sampling:
+// sampling
+//   Dimensions: 1D, 2D, 3D, 2DShadow
+//   Variations: Proj { ImplicitLod { | Bias } | ExplicitLod { Lod | Grad | } }
+//   x { | ConstOffset }
+// depth-compare sampling
+//   Dimensions: 2D
+//   Variations: Proj Dref { ImplicitLod { | Bias } | ExplicitLod { Lod | Grad |
+//   } } x { | ConstOffset }
+//
+// Expanded:
+//    ImageSampleProjImplicitLod        // { | ConstOffset }
+//    ImageSampleProjImplicitLod_Bias   // { | ConstOffset }
+//    ImageSampleProjExplicitLod_Lod    // { | ConstOffset }
+//    ImageSampleProjExplicitLod_Grad   // { | ConstOffset }
+//
+//    ImageSampleProjImplicitLod_DepthTexture
+//
+//    ImageSampleProjDrefImplicitLod        // { | ConstOffset }
+//    ImageSampleProjDrefExplicitLod_Lod    // { | ConstOffset }
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjImplicitLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleProjImplicitLod 1D
+        ImageAccessCase{
+            "%float 1D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords12",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
+            R"(textureSample(x_20, x_10, (coords12.x / coords12.y)))"},
+
+        // OpImageSampleProjImplicitLod 2D
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSample(x_20, x_10, (coords123.xy / coords123.z)))"},
+
+        // OpImageSampleProjImplicitLod 3D
+        ImageAccessCase{
+            "%float 3D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords1234",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
+            R"(textureSample(x_20, x_10, (coords1234.xyz / coords1234.w)))"},
+
+        // OpImageSampleProjImplicitLod 2D with ConstOffset
+        // (Don't need to test with 1D or 3D, as the hard part was the splatted
+        // division.) This case tests handling of the ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSample(x_20, x_10, (coords123.xy / coords123.z), vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjImplicitLod_Bias,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleProjImplicitLod with Bias
+        // Only testing 2D
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123 Bias %float_7",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0))"},
+
+        // OpImageSampleProjImplicitLod with Bias and signed ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123 Bias|ConstOffset "
+            "%float_7 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(3, 4)))"},
+
+        // OpImageSampleProjImplicitLod with Bias and unsigned ConstOffset
+        // Convert ConstOffset to signed
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123 Bias|ConstOffset "
+            "%float_7 %u_offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(vec2<u32>(3u, 4u))))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjExplicitLod_Lod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+        // OpImageSampleProjExplicitLod 2D
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjExplicitLod "
+            "%v4float %sampled_image %coords123 Lod %f1",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, (coords123.xy / coords123.z), f1))"},
+
+        // OpImageSampleProjExplicitLod 2D Lod with ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjExplicitLod "
+            "%v4float %sampled_image %coords123 Lod|ConstOffset %f1 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleLevel(x_20, x_10, (coords123.xy / coords123.z), f1, vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjExplicitLod_Grad,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+        // OpImageSampleProjExplicitLod 2D Grad
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjExplicitLod "
+            "%v4float %sampled_image %coords123 Grad %vf12 %vf21",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, (coords123.xy / coords123.z), vf12, vf21))"},
+
+        // OpImageSampleProjExplicitLod 2D Lod Grad ConstOffset
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageSampleProjExplicitLod "
+            "%v4float %sampled_image %coords123 Grad|ConstOffset "
+            "%vf12 %vf21 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+            R"(textureSampleGrad(x_20, x_10, (coords123.xy / coords123.z), vf12, vf21, vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Ordinary (non-comparison) sampling on a depth texture.
+    ImageSampleProjImplicitLod_DepthTexture,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+        // OpImageSampleProjImplicitLod 2D depth
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleProjImplicitLod "
+            "%v4float %sampled_image %coords123",
+            R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            // Sampling the depth texture yields an f32, but the
+            // SPIR-V operation yiedls vec4<f32>, so fill out the
+            // remaining components with 0.
+            R"(vec4<f32>(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0, 0.0, 0.0))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjDrefImplicitLod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // OpImageSampleProjDrefImplicitLod 2D depth-texture
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleProjDrefImplicitLod "
+            "%float %sampled_image %coords123 %f1",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), f1))"},
+
+        // OpImageSampleProjDrefImplicitLod 2D depth-texture, ConstOffset
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleProjDrefImplicitLod "
+            "%float %sampled_image %coords123 %f1 ConstOffset %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), f1, vec2<i32>(3, 4)))"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    DISABLED_ImageSampleProjDrefExplicitLod_Lod,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::Values(
+
+        // Lod must be float constant 0 due to a Metal constraint.
+        // Another test checks cases where the Lod is not float constant 0.
+
+        // OpImageSampleProjDrefExplicitLod 2D depth-texture Lod
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleProjDrefExplicitLod "
+            "%float %sampled_image %coords123 %depth Lod %float_0",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0))"},
+
+        // OpImageSampleProjDrefImplicitLod 2D depth-texture, Lod ConstOffset
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageSampleProjDrefExplicitLod "
+            "%float %sampled_image %coords123 %depth "
+            "Lod|ConstOffset %float_0 %offsets2d",
+            R"(@group(0) @binding(0) var x_10 : sampler_comparison;
+
+@group(2) @binding(1) var x_20 : texture_depth_2d;
+)",
+            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0, vec2<i32>(3, 4)))"}));
+
+/////
+// End projection sampling
+/////
+
+using SpvParserHandleTest_ImageAccessTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageAccessCase>>;
+
+TEST_P(SpvParserHandleTest_ImageAccessTest, Variable) {
+  // In this test harness, we only create an image.
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+     OpName %f1 "f1"
+     OpName %vf12 "vf12"
+     OpName %vf123 "vf123"
+     OpName %vf1234 "vf1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
+     OpName %offsets2d "offsets2d"
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+)" + CommonBasicTypes() +
+                        R"(
+     %im_ty = OpTypeImage )" +
+                        GetParam().spirv_image_type_details + R"(
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+     %20 = OpVariable %ptr_im_ty UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %f1 = OpCopyObject %float %float_1
+     %vf12 = OpCopyObject %v2float %the_vf12
+     %vf123 = OpCopyObject %v3float %the_vf123
+     %vf1234 = OpCopyObject %v4float %the_vf1234
+
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
+     %value_offset = OpCompositeConstruct %v2int %int_3 %int_4
+     %offsets2d = OpCopyObject %v2int %value_offset
+     %im = OpLoad %im_ty %20
+
+)" + GetParam().spirv_image_access +
+                        R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto program = test::ToString(p->program());
+  EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
+      << "DECLARATIONS ARE BAD " << program;
+  EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
+      << "TEXTURE BUILTIN IS BAD " << program << assembly;
+}
+
+INSTANTIATE_TEST_SUITE_P(ImageWrite_OptionalParams,
+                         SpvParserHandleTest_ImageAccessTest,
+                         ::testing::ValuesIn(std::vector<ImageAccessCase>{
+                             // OpImageWrite with no extra params
+                             {"%float 2D 0 0 0 2 Rgba32f",
+                              "OpImageWrite %im %vi12 %vf1234",
+                              "@group(2) @binding(1) var x_20 : "
+                              "texture_storage_2d<rgba32float, write>;",
+                              "textureStore(x_20, vi12, vf1234);"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // SPIR-V's texel parameter is a scalar or vector with at least as many
+    // components as there are channels in the underlying format, and the
+    // componet type matches the sampled type (modulo signed/unsigned integer).
+    // WGSL's texel parameter is a 4-element vector scalar or vector, with
+    // component type equal to the 32-bit form of the channel type.
+    ImageWrite_ConvertTexelOperand_Arity,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Source 1 component
+        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %f1",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
+         "textureStore(x_20, vi12, vec4<f32>(f1, 0.0, 0.0, 0.0));"},
+        // Source 2 component, dest 1 component
+        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf12",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
+         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
+        // Source 3 component, dest 1 component
+        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf123",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
+         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
+        // Source 4 component, dest 1 component
+        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf1234",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
+         "textureStore(x_20, vi12, vf1234);"},
+        // Source 2 component, dest 2 component
+        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf12",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
+         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
+        // Source 3 component, dest 2 component
+        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf123",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
+         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
+        // Source 4 component, dest 2 component
+        {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf1234",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
+         "textureStore(x_20, vi12, vf1234);"},
+        // WGSL does not support 3-component storage textures.
+        // Source 4 component, dest 4 component
+        {"%float 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vf1234",
+         "@group(2) @binding(1) var x_20 : "
+         "texture_storage_2d<rgba32float, write>;",
+         "textureStore(x_20, vi12, vf1234);"}}));
+
+TEST_F(SpvParserHandleTest, ImageWrite_TooFewSrcTexelComponents_1_vs_4) {
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+     OpName %f1 "f1"
+     OpName %coords12 "coords12"
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+)" + CommonBasicTypes() +
+                        R"(
+     %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgba32f
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+
+     %20 = OpVariable %ptr_im_ty UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %f1 = OpCopyObject %float %float_1
+
+     %coords12 = OpCopyObject %v2float %the_vf12
+
+     %im = OpLoad %im_ty %20
+     OpImageWrite %im %coords12 %f1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              Eq("texel has too few components for storage texture: 1 provided "
+                 "but 4 required, in: OpImageWrite %54 %3 %2"))
+      << p->error();
+}
+
+TEST_F(SpvParserHandleTest, ImageWrite_ThreeComponentStorageTexture_IsError) {
+  // SPIR-V doesn't allow a 3-element storage texture format.
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+     OpName %vf123 "vf123"
+     OpName %coords12 "coords12"
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+)" + CommonBasicTypes() +
+                        R"(
+     %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgb32f
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+
+     %20 = OpVariable %ptr_im_ty UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %vf123 = OpCopyObject %v3float %the_vf123
+
+     %coords12 = OpCopyObject %v2float %the_vf12
+
+     %im = OpLoad %im_ty %20
+     OpImageWrite %im %coords12 %vf123
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto error = test::AssembleFailure(assembly);
+  EXPECT_THAT(error, HasSubstr("Invalid image format 'Rgb32f'"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    // The texel operand signedness must match the channel type signedness.
+    // SPIR-V validation checks that.
+    // This suite is for the cases where they are integral and the same
+    // signedness.
+    ImageWrite_ConvertTexelOperand_SameSignedness,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Sampled type is unsigned int, texel is unsigned int
+        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vu1234",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)",
+         R"(textureStore(x_20, vi12, vu1234))"},
+        // Sampled type is signed int, texel is signed int
+        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vi1234",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;)",
+         R"(textureStore(x_20, vi12, vi1234))"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Error out when OpImageWrite write texel differ in float vs. integral
+    ImageWrite_ConvertTexelOperand_DifferentFloatishness_IsError,
+    // Use the ImageDeclTest so we can check the error.
+    SpvParserHandleTest_ImageDeclTest,
+    ::testing::ValuesIn(std::vector<ImageDeclCase>{
+        // Sampled type is float, texel is signed int
+        {"%uint 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vi1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32float, write>;)"},
+        // Sampled type is float, texel is unsigned int
+        {"%int 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vi12 %vu1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32float, write>;)"},
+        // Sampled type is unsigned int, texel is float
+        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vf1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)"},
+        // Sampled type is signed int, texel is float
+        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vf1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;
+  })"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Error out when OpImageWrite write texel signedness is different.
+    ImageWrite_ConvertTexelOperand_DifferentSignedness_IsError,
+    // Use the ImageDeclTest so we can check the error.
+    SpvParserHandleTest_ImageDeclTest,
+    ::testing::ValuesIn(std::vector<ImageDeclCase>{
+        // Sampled type is unsigned int, texel is signed int
+        {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vi12 %vi1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32uint, write>;)"},
+        // Sampled type is signed int, texel is unsigned int
+        {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vi12 %vu1234",
+         "invalid texel type for storage texture write: component must be "
+         "float, signed integer, or unsigned integer to match the texture "
+         "channel type: OpImageWrite",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rgba32sint, write>;
+  })"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Show that zeros of the correct integer signedness are
+    // created when expanding an integer vector.
+    ImageWrite_ConvertTexelOperand_Signedness_AndWidening,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Source unsigned, dest unsigned
+        {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu12",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
+         R"(textureStore(x_20, vi12, vec4<u32>(vu12, 0u, 0u)))"},
+        // Source signed, dest signed
+        {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
+         R"(textureStore(x_20, vi12, vec4<i32>(vi12, 0, 0)))"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageFetch_OptionalParams,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // OpImageFetch with no extra params, on sampled texture
+        // Level of detail is injected for sampled texture
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
+        // OpImageFetch with explicit level, on sampled texture
+        {"%float 2D 0 0 0 1 Unknown",
+         "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 3);)"},
+        // OpImageFetch with no extra params, on depth texture
+        // Level of detail is injected for depth texture
+        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0), 0.0, 0.0, 0.0);)"},
+        // OpImageFetch with extra params, on depth texture
+        {"%float 2D 1 0 0 1 Unknown",
+         "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 3), 0.0, 0.0, 0.0);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageFetch_Depth,
+    // In SPIR-V OpImageFetch always yields a vector of 4
+    // elements, even for depth images.  But in WGSL,
+    // textureLoad on a depth image yields f32.
+    // crbug.com/tint/439
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // ImageFetch on depth image.
+        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 ",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0), 0.0, 0.0, 0.0);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageFetch_DepthMultisampled,
+    // In SPIR-V OpImageFetch always yields a vector of 4
+    // elements, even for depth images.  But in WGSL,
+    // textureLoad on a depth image yields f32.
+    // crbug.com/tint/439
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // ImageFetch on multisampled depth image.
+        {"%float 2D 1 0 1 1 Unknown",
+         "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_multisampled_2d;)",
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, i1), 0.0, 0.0, 0.0);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageFetch_Multisampled,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // SPIR-V requires a Sample image operand when operating on a
+        // multisampled image.
+
+        // ImageFetch arrayed
+        // Not in WebGPU
+
+        // ImageFetch non-arrayed
+        {"%float 2D 0 0 1 1 Unknown",
+         "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
+         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i1);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageFetch_Multisampled_ConvertSampleOperand,
+    SpvParserHandleTest_ImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        {"%float 2D 0 0 1 1 Unknown",
+         "%99 = OpImageFetch %v4float %im %vi12 Sample %u1",
+         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i32(u1));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConvertResultSignedness,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Valid SPIR-V only has:
+        //      float scalar sampled type vs. floating result
+        //      integral scalar sampled type vs. integral result
+        // Any of the sampling, reading, or fetching use the same codepath.
+
+        // We'll test with:
+        //     OpImageFetch
+        //     OpImageRead
+        //     OpImageSampleImplicitLod - representative of sampling
+
+        //
+        // OpImageRead
+        //
+
+        // OpImageFetch requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
+        // OpImageFetch requires no conversion, uint -> v4uint
+        {"%uint 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4uint %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
+         R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0);)"},
+        // OpImageFetch requires conversion, uint -> v4int
+        // is invalid SPIR-V:
+        // "Expected Image 'Sampled Type' to be the same as Result Type
+        // components"
+
+        // OpImageFetch requires no conversion, int -> v4int
+        {"%int 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4int %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
+         R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0);)"},
+        // OpImageFetch requires conversion, int -> v4uint
+        // is invalid SPIR-V:
+        // "Expected Image 'Sampled Type' to be the same as Result Type
+        // components"
+
+        //
+        // OpImageRead
+        //
+
+        // OpImageRead requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 2 Rgba32f", "%99 = OpImageRead %v4float %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0);)"},
+        // OpImageRead requires no conversion, uint -> v4uint
+        {"%uint 2D 0 0 0 2 Rgba32ui", "%99 = OpImageRead %v4uint %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
+         R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0);)"},
+
+        // OpImageRead requires conversion, uint -> v4int
+        // is invalid SPIR-V:
+        // "Expected Image 'Sampled Type' to be the same as Result Type
+        // components"
+
+        // OpImageRead requires no conversion, int -> v4int
+        {"%int 2D 0 0 0 2 Rgba32i", "%99 = OpImageRead %v4int %im %vi12",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
+         R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0);)"},
+
+        // OpImageRead requires conversion, int -> v4uint
+        // is invalid SPIR-V:
+        // "Expected Image 'Sampled Type' to be the same as Result Type
+        // components"
+
+        //
+        // Sampling operations, using OpImageSampleImplicitLod as an example.
+        // WGSL sampling operations only work on textures with a float sampled
+        // component.  So we can only test the float -> float (non-conversion)
+        // case.
+
+        // OpImageSampleImplicitLod requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4float %sampled_image %vf12",
+         R"(@group(0) @binding(0) var x_10 : sampler;
+
+@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec4<f32> = textureSample(x_20, x_10, vf12);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQuerySize_NonArrayed_SignedResult,
+    // ImageQuerySize requires storage image or multisampled
+    // For storage image, use another instruction to indicate whether it
+    // is readonly or writeonly.
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // 1D storage image
+        {"%float 1D 0 0 0 2 Rgba32f",
+         "%99 = OpImageQuerySize %int %im \n"
+         "%98 = OpImageRead %v4float %im %i1\n",  // Implicitly mark as
+                                                  // NonWritable
+         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
+         R"(let x_99 : i32 = i32(textureDimensions(x_20));)"},
+        // 2D storage image
+        {"%float 2D 0 0 0 2 Rgba32f",
+         "%99 = OpImageQuerySize %v2int %im \n"
+         "%98 = OpImageRead %v4float %im %vi12\n",  // Implicitly mark as
+                                                    // NonWritable
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20))"},
+        // 3D storage image
+        {"%float 3D 0 0 0 2 Rgba32f",
+         "%99 = OpImageQuerySize %v3int %im \n"
+         "%98 = OpImageRead %v4float %im %vi123\n",  // Implicitly mark as
+                                                     // NonWritable
+         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20));)"},
+
+        // Multisampled
+        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySize %v2int %im \n",
+         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQuerySize_Arrayed_SignedResult,
+    // ImageQuerySize requires storage image or multisampled
+    // For storage image, use another instruction to indicate whether it
+    // is readonly or writeonly.
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // 1D array storage image doesn't exist.
+
+        // 2D array storage image
+        {"%float 2D 0 1 0 2 Rgba32f",
+         "%99 = OpImageQuerySize %v3int %im \n"
+         "%98 = OpImageRead %v4float %im %vi123\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20), textureNumLayers(x_20));)"}
+        // 3D array storage image doesn't exist.
+
+        // Multisampled array
+        // Not in WebGPU
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel,
+    // From VUID-StandaloneSpirv-OpImageQuerySizeLod-04659:
+    //  ImageQuerySizeLod requires Sampled=1
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // 1D
+        {"%float 1D 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
+         R"(let x_99 : i32 = i32(textureDimensions(x_20, i1)))"},
+
+        // 2D
+        {"%float 2D 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
+
+        // 3D
+        {"%float 3D 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1));)"},
+
+        // Cube
+        {"%float Cube 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"},
+
+        // Depth 2D
+        {"%float 2D 1 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
+
+        // Depth Cube
+        {"%float Cube 1 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_cube;)",
+         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel,
+    // ImageQuerySize requires storage image or multisampled
+    // For storage image, use another instruction to indicate whether it
+    // is readonly or writeonly.
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+
+        // There is no 1D array
+
+        // 2D array
+        {"%float 2D 0 1 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1), textureNumLayers(x_20));)"},
+
+        // There is no 3D array
+
+        // Cube array
+        //
+        // Currently textureDimension on cube returns vec3 but maybe should
+        // return vec2
+        // https://github.com/gpuweb/gpuweb/issues/1345
+        {"%float Cube 0 1 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20));)"},
+
+        // Depth 2D array
+        {"%float 2D 1 1 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1), textureNumLayers(x_20));)"},
+
+        // Depth Cube Array
+        //
+        // Currently textureDimension on cube returns vec3 but maybe should
+        // return vec2
+        // https://github.com/gpuweb/gpuweb/issues/1345
+        {"%float Cube 1 1 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
+         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // When the level-of-detail value is given as an unsigned
+    // integer, we must convert it before using it as an argument
+    // to textureDimensions.
+    ImageQuerySizeLod_NonArrayed_SignedResult_UnsignedLevel,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+
+        {"%float 1D 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %int %im %u1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
+         R"(let x_99 : i32 = i32(textureDimensions(x_20, i32(u1)));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // When SPIR-V wants the result type to be unsigned, we have to
+    // insert a type constructor or bitcast for WGSL to do the type
+    // coercion. But the algorithm already does that as a matter
+    // of course.
+    ImageQuerySizeLod_NonArrayed_UnsignedResult_SignedLevel,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+
+        {"%float 1D 0 0 0 1 Unknown",
+         "%99 = OpImageQuerySizeLod %uint %im %i1\n",
+         R"(@group(2) @binding(1) var x_20 : texture_1d<f32>;)",
+         R"(let x_99 : u32 = u32(textureDimensions(x_20, i1));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQueryLevels_SignedResult,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // In Vulkan:
+        //      Dim must be 1D, 2D, 3D, Cube
+        // WGSL allows 2d, 2d_array, 3d, cube, cube_array
+        // depth_2d, depth_2d_array, depth_cube, depth_cube_array
+
+        // 2D
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // 2D array
+        {"%float 2D 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // 3D
+        {"%float 3D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // Cube
+        {"%float Cube 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // Cube array
+        {"%float Cube 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // depth 2d
+        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // depth 2d array
+        {"%float 2D 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // depth cube
+        {"%float Cube 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_cube;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"},
+
+        // depth cube array
+        {"%float Cube 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
+         R"(let x_99 : i32 = textureNumLevels(x_20);)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Spot check that a type conversion is inserted when SPIR-V asks for
+    // an unsigned int result.
+    ImageQueryLevels_UnsignedResult,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %uint %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
+         R"(let x_99 : u32 = u32(textureNumLevels(x_20));)"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQuerySamples_SignedResult,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Multsample 2D
+        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySamples %int %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
+         R"(let x_99 : i32 = textureNumSamples(x_20);)"}  // namespace
+
+        // Multisample 2D array
+        // Not in WebGPU
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Translation must inject a type coersion from signed to unsigned.
+    ImageQuerySamples_UnsignedResult,
+    SpvParserHandleTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Multsample 2D
+        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySamples %uint %im\n",
+         R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
+         R"(let x_99 : u32 = u32(textureNumSamples(x_20));)"}
+
+        // Multisample 2D array
+        // Not in WebGPU
+    }));
+
+struct ImageCoordsCase {
+  // SPIR-V image type, excluding result ID and opcode
+  std::string spirv_image_type_details;
+  std::string spirv_image_access;
+  std::string expected_error;
+  std::vector<std::string> expected_expressions;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const ImageCoordsCase& c) {
+  out << "ImageCoordsCase(" << c.spirv_image_type_details << "\n"
+      << c.spirv_image_access << "\n"
+      << "expected_error(" << c.expected_error << ")\n";
+
+  for (auto e : c.expected_expressions) {
+    out << e << ",";
+  }
+  out << ")" << std::endl;
+  return out;
+}
+
+using SpvParserHandleTest_ImageCoordsTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageCoordsCase>>;
+
+TEST_P(SpvParserHandleTest_ImageCoordsTest,
+       MakeCoordinateOperandsForImageAccess) {
+  // Only declare the sampled image type, and the associated variable
+  // if the requested image type is a sampled image type and not multisampled.
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
+  const auto assembly =
+      Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpName %float_var "float_var"
+     OpName %ptr_float "ptr_float"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %f1 "f1"
+     OpName %vf12 "vf12"
+     OpName %vf123 "vf123"
+     OpName %vf1234 "vf1234"
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+     OpDecorate %30 DescriptorSet 0
+     OpDecorate %30 Binding 1
+)" + CommonBasicTypes() +
+      R"(
+     %sampler = OpTypeSampler
+     %ptr_sampler = OpTypePointer UniformConstant %sampler
+     %im_ty = OpTypeImage )" +
+      GetParam().spirv_image_type_details + R"(
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+      R"(
+
+     %ptr_float = OpTypePointer Function %float
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_im_ty UniformConstant
+     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %float_var = OpVariable %ptr_float Function
+
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
+     %f1 = OpCopyObject %float %float_1
+     %vf12 = OpCopyObject %v2float %the_vf12
+     %vf123 = OpCopyObject %v3float %the_vf123
+     %vf1234 = OpCopyObject %v4float %the_vf1234
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %im_ty %20
+
+)" +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
+           : "") +
+      GetParam().spirv_image_access +
+      R"(
+     ; Use an anchor for the cases when the image access doesn't have a result ID.
+     %1000 = OpCopyObject %uint %uint_0
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  if (!p->BuildAndParseInternalModule()) {
+    EXPECT_THAT(p->error(), StartsWith(GetParam().expected_error)) << assembly;
+  } else {
+    EXPECT_TRUE(p->error().empty()) << p->error();
+    auto fe = p->function_emitter(100);
+    // We actually have to generate the module to cache expressions for the
+    // result IDs, particularly the OpCopyObject
+    fe.Emit();
+
+    const spvtools::opt::Instruction* anchor = p->GetInstructionForTest(1000);
+    ASSERT_NE(anchor, nullptr);
+    const spvtools::opt::Instruction& image_access = *(anchor->PreviousNode());
+
+    ast::ExpressionList result =
+        fe.MakeCoordinateOperandsForImageAccess(image_access);
+    if (GetParam().expected_error.empty()) {
+      EXPECT_TRUE(fe.success()) << p->error();
+      EXPECT_TRUE(p->error().empty());
+      std::vector<std::string> result_strings;
+      Program program = p->program();
+      for (auto* expr : result) {
+        ASSERT_NE(expr, nullptr);
+        result_strings.push_back(test::ToString(program, expr));
+      }
+      EXPECT_THAT(result_strings,
+                  ::testing::ContainerEq(GetParam().expected_expressions));
+    } else {
+      EXPECT_FALSE(fe.success());
+      EXPECT_THAT(p->error(), Eq(GetParam().expected_error)) << assembly;
+      EXPECT_TRUE(result.empty());
+    }
+  }
+
+  const bool is_sample_level =
+      GetParam().spirv_image_access.find("ImageSampleExplicitLod") !=
+      std::string::npos;
+  const bool is_comparison_sample_level =
+      GetParam().spirv_image_access.find("ImageSampleDrefExplicitLod") !=
+      std::string::npos;
+  const bool is_1d =
+      GetParam().spirv_image_type_details.find("1D") != std::string::npos;
+  if (is_sample_level && is_1d) {
+    p->SkipDumpingPending("crbug.com/tint/789");
+  }
+  if (is_comparison_sample_level) {
+    p->SkipDumpingPending("crbug.com/tint/425");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(Good_1D,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %f1",
+                              "",
+                              {"f1"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf12",  // one excess arg
+                              "",
+                              {"vf12.x"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf123",  // two excess args
+                              "",
+                              {"vf123.x"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf1234",  // three excess args
+                              "",
+                              {"vf1234.x"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_1DArray,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf12",
+                              "",
+                              {"vf12.x", "i32(round(vf12.y))"}},
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf123",  // one excess arg
+                              "",
+                              {"vf123.x", "i32(round(vf123.y))"}},
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf1234",  // two excess args
+                              "",
+                              {"vf1234.x", "i32(round(vf1234.y))"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_2D,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf12",
+                              "",
+                              {"vf12"}},
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf123",  // one excess arg
+                              "",
+                              {"vf123.xy"}},
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf1234",  // two excess args
+                              "",
+                              {"vf1234.xy"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_2DArray,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 2D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf123",
+                              "",
+                              {"vf123.xy", "i32(round(vf123.z))"}},
+                             {"%float 2D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %vf1234",  // one excess arg
+                              "",
+                              {"vf1234.xy", "i32(round(vf1234.z))"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_3D,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 3D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %vf123",
+                              "",
+                              {"vf123"}},
+                             {"%float 3D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %vf1234",  // one excess
+                                                         // arg
+                              "",
+                              {"vf1234.xyz"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_Cube,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float Cube 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %vf123",
+                              "",
+                              {"vf123"}},
+                             {"%float Cube 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %vf1234",  // one excess
+                                                         // arg
+                              "",
+                              {"vf1234.xyz"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_CubeArray,
+                         SpvParserHandleTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float Cube 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %vf1234",
+                              "",
+                              {"vf1234.xyz", "i32(round(vf1234.w))"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    PreserveFloatCoords_NonArrayed,
+    // In SPIR-V, sampling and dref sampling operations use floating point
+    // coordinates.  Prove that we preserve floating point-ness.
+    // Test across all such instructions.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // Scalar cases
+        {"%float 1D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %f1",
+         "",
+         {"f1"}},
+        {"%float 1D 0 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4float %sampled_image %f1 Lod "
+         "%f1",
+         "",
+         {"f1"}},
+        // WGSL does not support depth textures with 1D coordinates
+        // Vector cases
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf12",
+         "",
+         {"vf12"}},
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4float %sampled_image %vf12 Lod "
+         "%f1",
+         "",
+         {"vf12"}},
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf12 "
+         "%depth",
+         "",
+         {"vf12"}},
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf12 "
+         "%depth Lod %float_0",
+         "",
+         {"vf12"}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    PreserveFloatCoords_Arrayed,
+    // In SPIR-V, sampling and dref sampling operations use floating point
+    // coordinates.  Prove that we preserve floating point-ness of the
+    // coordinate part, but convert the array index to signed integer. Test
+    // across all such instructions.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 1 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf123",
+         "",
+         {"vf123.xy", "i32(round(vf123.z))"}},
+
+        {"%float 2D 0 1 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4float %sampled_image %vf123 "
+         "Lod %f1",
+         "",
+         {"vf123.xy", "i32(round(vf123.z))"}},
+        {"%float 2D 1 1 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %float %sampled_image "
+         "%vf123 %depth",
+         "",
+         {"vf123.xy", "i32(round(vf123.z))"}},
+        {"%float 2D 1 1 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image "
+         "%vf123 %depth Lod %float_0",
+         "",
+         {"vf123.xy", "i32(round(vf123.z))"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    PreserveIntCoords_NonArrayed,
+    // In SPIR-V, image read, fetch, and write use integer coordinates.
+    // Prove that we preserve signed integer coordinates.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // Scalar cases
+        {"%float 1D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %i1",
+         "",
+         {"i1"}},
+        {"%float 1D 0 0 0 2 R32f",
+         "%result = OpImageRead %v4float %im %i1",
+         "",
+         {"i1"}},
+        {"%float 1D 0 0 0 2 R32f", "OpImageWrite %im %i1 %vf1234", "", {"i1"}},
+        // Vector cases
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %vi12",
+         "",
+         {"vi12"}},
+        {"%float 2D 0 0 0 2 R32f",
+         "%result = OpImageRead %v4float %im %vi12",
+         "",
+         {"vi12"}},
+        {"%float 2D 0 0 0 2 R32f",
+         "OpImageWrite %im %vi12 %vf1234",
+         "",
+         {"vi12"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    PreserveIntCoords_Arrayed,
+    // In SPIR-V, image read, fetch, and write use integer coordinates.
+    // Prove that we preserve signed integer coordinates.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 1 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %vi123",
+         "",
+         {"vi123.xy", "vi123.z"}},
+        {"%float 2D 0 1 0 2 R32f",
+         "%result = OpImageRead %v4float %im %vi123",
+         "",
+         {"vi123.xy", "vi123.z"}},
+        {"%float 2D 0 1 0 2 R32f",
+         "OpImageWrite %im %vi123 %vf1234",
+         "",
+         {"vi123.xy", "vi123.z"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConvertUintCoords_NonArrayed,
+    // In SPIR-V, image read, fetch, and write use integer coordinates.
+    // Prove that we convert unsigned integer coordinates to signed.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // Scalar cases
+        {"%float 1D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %u1",
+         "",
+         {"i32(u1)"}},
+        {"%float 1D 0 0 0 2 R32f",
+         "%result = OpImageRead %v4float %im %u1",
+         "",
+         {"i32(u1)"}},
+        {"%float 1D 0 0 0 2 R32f",
+         "OpImageWrite %im %u1 %vf1234",
+         "",
+         {"i32(u1)"}},
+        // Vector cases
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %vu12",
+         "",
+         {"vec2<i32>(vu12)"}},
+        {"%float 2D 0 0 0 2 R32f",
+         "%result = OpImageRead %v4float %im %vu12",
+         "",
+         {"vec2<i32>(vu12)"}},
+        {"%float 2D 0 0 0 2 R32f",
+         "OpImageWrite %im %vu12 %vf1234",
+         "",
+         {"vec2<i32>(vu12)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConvertUintCoords_Arrayed,
+    // In SPIR-V, image read, fetch, and write use integer coordinates.
+    // Prove that we convert unsigned integer coordinates to signed.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 1 0 1 Unknown",
+         "%result = OpImageFetch %v4float %im %vu123",
+         "",
+         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
+        {"%float 2D 0 1 0 2 R32f",
+         "%result = OpImageRead %v4float %im %vu123",
+         "",
+         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
+        {"%float 2D 0 1 0 2 R32f",
+         "OpImageWrite %im %vu123 %vf1234",
+         "",
+         {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    BadInstructions,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 1D 0 0 0 1 Unknown",
+         "OpNop",
+         "not an image access instruction: OpNop",
+         {}},
+        {"%float 1D 0 0 0 1 Unknown",
+         "%50 = OpCopyObject %float %float_1",
+         "internal error: couldn't find image for "
+         "%50 = OpCopyObject %18 %45",
+         {}},
+        {"%float 1D 0 0 0 1 Unknown",
+         "OpStore %float_var %float_1",
+         "invalid type for image or sampler "
+         "variable or function parameter: %1 = OpVariable %2 Function",
+         {}},
+        // An example with a missing coordinate
+        // won't assemble, so we skip it.
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    Bad_Coordinate,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 1D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod "
+         // bad type for coordinate: not a number
+         "%v4float %sampled_image %float_var",
+         "bad or unsupported coordinate type for image access: %73 = "
+         "OpImageSampleImplicitLod %42 %72 %1",
+         {}},
+        {"%float 2D 0 0 0 1 Unknown",  // 2D
+         "%result = OpImageSampleImplicitLod "
+         // 1 component, but need 2
+         "%v4float %sampled_image %f1",
+         "image access required 2 coordinate components, but only 1 provided, "
+         "in: %73 = OpImageSampleImplicitLod %42 %72 %12",
+         {}},
+        {"%float 2D 0 1 0 1 Unknown",  // 2DArray
+         "%result = OpImageSampleImplicitLod "
+         // 2 component, but need 3
+         "%v4float %sampled_image %vf12",
+         "image access required 3 coordinate components, but only 2 provided, "
+         "in: %73 = OpImageSampleImplicitLod %42 %72 %13",
+         {}},
+        {"%float 3D 0 0 0 1 Unknown",  // 3D
+         "%result = OpImageSampleImplicitLod "
+         // 2 components, but need 3
+         "%v4float %sampled_image %vf12",
+         "image access required 3 coordinate components, but only 2 provided, "
+         "in: %73 = OpImageSampleImplicitLod %42 %72 %13",
+         {}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    SampleNonFloatTexture_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // ImageSampleImplicitLod
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4uint %sampled_image %vf12",
+         "sampled image must have float component type",
+         {}},
+        {"%int 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4int %sampled_image %vf12",
+         "sampled image must have float component type",
+         {}},
+        // ImageSampleExplicitLod
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4uint %sampled_image %vf12 "
+         "Lod %f1",
+         "sampled image must have float component type",
+         {}},
+        {"%int 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleExplicitLod %v4int %sampled_image %vf12 "
+         "Lod %f1",
+         "sampled image must have float component type",
+         {}},
+        // ImageSampleDrefImplicitLod
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %uint %sampled_image %vf12 "
+         "%f1",
+         "sampled image must have float component type",
+         {}},
+        {"%int 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %int %sampled_image %vf12 "
+         "%f1",
+         "sampled image must have float component type",
+         {}},
+        // ImageSampleDrefExplicitLod
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %uint %sampled_image %vf12 "
+         "%f1 Lod %float_0",
+         "sampled image must have float component type",
+         {}},
+        {"%int 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %int %sampled_image %vf12 "
+         "%f1 Lod %float_0",
+         "sampled image must have float component type",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConstOffset_BadInstruction_Errors,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // ImageFetch
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4uint %sampled_image %vf12 ConstOffset "
+         "%the_vu12",
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
+         {}},
+        // ImageRead
+        {"%uint 2D 0 0 0 2 Rgba32ui",
+         "%result = OpImageRead %v4uint %im %vu12 ConstOffset %the_vu12",
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
+         {}},
+        // ImageWrite
+        {"%uint 2D 0 0 0 2 Rgba32ui",
+         "OpImageWrite %im %vu12 %vu1234 ConstOffset %the_vu12",
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConstOffset_BadDim_Errors,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // 1D
+        {"%uint 1D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}},
+        // Cube
+        {"%uint Cube 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}},
+        // Cube Array
+        {"%uint Cube 0 1 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleDref_Bias_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // Implicit Lod
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf1234 "
+         "%depth Bias %float_null",
+         "WGSL does not support depth-reference sampling with level-of-detail "
+         "bias: ",
+         {}},
+        // Explicit Lod
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
+         "%depth Lod|Bias %float_null %float_null",
+         "WGSL does not support depth-reference sampling with level-of-detail "
+         "bias: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleDref_Grad_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // Implicit Lod
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefImplicitLod %float %sampled_image %vf1234 "
+         "%depth Grad %float_1 %float_2",
+         "WGSL does not support depth-reference sampling with explicit "
+         "gradient: ",
+         {}},
+        // Explicit Lod
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
+         "%depth Lod|Grad %float_null %float_1  %float_2",
+         "WGSL does not support depth-reference sampling with explicit "
+         "gradient: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleDrefExplicitLod_CheckForLod0,
+    // Metal requires comparison sampling with explicit Level-of-detail to use
+    // Lod 0.  The SPIR-V reader requires the operand to be parsed as a constant
+    // 0 value. SPIR-V validation requires the Lod parameter to be a floating
+    // point value for non-fetch operations. So only test float values.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // float 0.0 works
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
+         "%depth Lod %float_0",
+         "",
+         {"vf1234.xy"}},
+        // float null works
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
+         "%depth Lod %float_0",
+         "",
+         {"vf1234.xy"}},
+        // float 1.0 fails.
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleDrefExplicitLod %float %sampled_image %vf1234 "
+         "%depth Lod %float_1",
+         "WGSL comparison sampling without derivatives requires "
+         "level-of-detail "
+         "0.0",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleProjDrefExplicitLod_CheckForLod0,
+    // This is like the previous test, but for Projection sampling.
+    //
+    // Metal requires comparison sampling with explicit Level-of-detail to use
+    // Lod 0.  The SPIR-V reader requires the operand to be parsed as a constant
+    // 0 value. SPIR-V validation requires the Lod parameter to be a floating
+    // point value for non-fetch operations. So only test float values.
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // float 0.0 works
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
+         "%vf1234 %depth Lod %float_0",
+         "",
+         {"(vf1234.xy / vf1234.z)"}},
+        // float null works
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
+         "%vf1234 %depth Lod %float_0",
+         "",
+         {"(vf1234.xy / vf1234.z)"}},
+        // float 1.0 fails.
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageSampleProjDrefExplicitLod %float %sampled_image "
+         "%vf1234 %depth Lod %float_1",
+         "WGSL comparison sampling without derivatives requires "
+         "level-of-detail "
+         "0.0",
+         {}}}));
+
+TEST_F(SpvParserHandleTest, CombinedImageSampler_IsError) {
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+
+     OpDecorate %var DescriptorSet 0
+     OpDecorate %var Binding 0
+  %float = OpTypeFloat 32
+     %im = OpTypeImage %float 2D 0 0 0 1 Unknown
+     %si = OpTypeSampledImage %im
+ %ptr_si = OpTypePointer UniformConstant %si
+    %var = OpVariable %ptr_si UniformConstant
+   %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+
+    %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+           OpReturn
+           OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_THAT(p->error(),
+              HasSubstr("WGSL does not support combined image-samplers: "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageQueryLod_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageQueryLod %v2int %sampled_image %vf12",
+         "WGSL does not support querying the level of detail of an image: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageGather_Bias_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageGather %v4float %sampled_image %vf12 %int_1 "
+         "Bias %float_null",
+         "WGSL does not support image gather with level-of-detail bias: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageDrefGather_Bias_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageDrefGather %v4float %sampled_image %vf12 %depth "
+         "Bias %float_null",
+         "WGSL does not support image gather with level-of-detail bias: ",
+         {}}}));
+
+// Note: Vulkan SPIR-V ImageGather and ImageDrefGather do not allow explicit
+// Lod. The SPIR-V validator should reject those cases already.
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageGather_Grad_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageGather %v4float %sampled_image %vf12 %int_1 "
+         "Grad %vf12 %vf12",
+         "WGSL does not support image gather with explicit gradient: ",
+         {}}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageDrefGather_Grad_IsError,
+    SpvParserHandleTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 1 0 0 1 Unknown",
+         "%result = OpImageDrefGather %v4float %sampled_image %vf12 %depth "
+         "Grad %vf12 %vf12",
+         "WGSL does not support image gather with explicit gradient: ",
+         {}}}));
+
+TEST_F(SpvParserHandleTest,
+       NeverGenerateConstDeclForHandle_UseVariableDirectly) {
+  // An ad-hoc test to prove we never had the issue
+  // feared in crbug.com/tint/265.
+  // Never create a const-declaration for a pointer to
+  // a texture or sampler. Code generation always
+  // traces back to the memory object declaration.
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+
+     OpName %var "var"
+     OpDecorate %var_im DescriptorSet 0
+     OpDecorate %var_im Binding 0
+     OpDecorate %var_s DescriptorSet 0
+     OpDecorate %var_s Binding 1
+  %float = OpTypeFloat 32
+  %v4float = OpTypeVector %float 4
+  %v2float = OpTypeVector %float 2
+  %v2_0 = OpConstantNull %v2float
+     %im = OpTypeImage %float 2D 0 0 0 1 Unknown
+     %si = OpTypeSampledImage %im
+      %s = OpTypeSampler
+ %ptr_im = OpTypePointer UniformConstant %im
+  %ptr_s = OpTypePointer UniformConstant %s
+ %var_im = OpVariable %ptr_im UniformConstant
+  %var_s = OpVariable %ptr_s UniformConstant
+   %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %ptr_v4 = OpTypePointer Function %v4float
+
+    %100 = OpFunction %void None %voidfn
+  %entry = OpLabel
+    %var = OpVariable %ptr_v4 Function
+
+; Try to induce generating a const-declaration of a pointer to
+; a sampler or texture.
+
+ %var_im_copy = OpCopyObject %ptr_im %var_im
+  %var_s_copy = OpCopyObject %ptr_s %var_s
+
+         %im0 = OpLoad %im %var_im_copy
+          %s0 = OpLoad %s %var_s_copy
+         %si0 = OpSampledImage %si %im0 %s0
+          %t0 = OpImageSampleImplicitLod %v4float %si0 %v2_0
+
+
+         %im1 = OpLoad %im %var_im_copy
+          %s1 = OpLoad %s %var_s_copy
+         %si1 = OpSampledImage %si %im1 %s1
+          %t1 = OpImageSampleImplicitLod %v4float %si1 %v2_0
+
+         %sum = OpFAdd %v4float %t0 %t1
+           OpStore %var %sum
+
+           OpReturn
+           OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << assembly;
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  auto ast_body = fe.ast_body();
+  const auto got = test::ToString(p->program(), ast_body);
+  auto* expect = R"(var var_1 : vec4<f32>;
+let x_22 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
+let x_26 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
+var_1 = (x_22 + x_26);
+return;
+)";
+  ASSERT_EQ(expect, got);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_import_test.cc b/src/tint/reader/spirv/parser_impl_import_test.cc
new file mode 100644
index 0000000..65fac1c
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_import_test.cc
@@ -0,0 +1,128 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::UnorderedElementsAre;
+
+using SpvParserImportTest = SpvParserTest;
+
+TEST_F(SpvParserImportTest, Import_NoImport) {
+  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto program_ast = test::ToString(p->program());
+  EXPECT_THAT(program_ast, Not(HasSubstr("Import")));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserImportTest, Import_ImportGlslStd450) {
+  auto p = parser(test::Assemble(R"(%1 = OpExtInstImport "GLSL.std.450")"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  EXPECT_THAT(p->glsl_std_450_imports(), ElementsAre(1));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserImportTest, Import_NonSemantic_IgnoredImport) {
+  auto p = parser(test::Assemble(
+      R"(%40 = OpExtInstImport "NonSemantic.ClspvReflection.1")"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserImportTest, Import_NonSemantic_IgnoredExtInsts) {
+  // This is the clspv-compiled output of this OpenCL C:
+  //    kernel void foo(global int*A) { A=A; }
+  // It emits NonSemantic.ClspvReflection.1 extended instructions.
+  // But *tweaked*:
+  //    - to remove gl_WorkgroupSize
+  //    - to add LocalSize execution mode
+  //    - to move one of the ExtInsts into the globals-and-constants
+  //      section
+  //    - to move one of the ExtInsts into the function body.
+  auto p = parser(test::Assemble(R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_storage_buffer_storage_class"
+               OpExtension "SPV_KHR_non_semantic_info"
+         %20 = OpExtInstImport "NonSemantic.ClspvReflection.1"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %15 "foo"
+               OpExecutionMode %15 LocalSize 1 1 1
+               OpSource OpenCL_C 120
+         %21 = OpString "foo"
+         %23 = OpString "A"
+               OpDecorate %_runtimearr_uint ArrayStride 4
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_struct_3 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+               OpDecorate %7 SpecId 0
+               OpDecorate %8 SpecId 1
+               OpDecorate %9 SpecId 2
+       %void = OpTypeVoid
+         %24 = OpExtInst %void %20 ArgumentInfo %23
+       %uint = OpTypeInt 32 0
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+  %_struct_3 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Private_v3uint = OpTypePointer Private %v3uint
+          %7 = OpSpecConstant %uint 1
+          %8 = OpSpecConstant %uint 1
+          %9 = OpSpecConstant %uint 1
+         %14 = OpTypeFunction %void
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+         %12 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+         %15 = OpFunction %void Const %14
+         %16 = OpLabel
+         %19 = OpAccessChain %_ptr_StorageBuffer_uint %12 %uint_0 %uint_0
+         %22 = OpExtInst %void %20 Kernel %15 %21
+               OpReturn
+               OpFunctionEnd
+         %25 = OpExtInst %void %20 ArgumentStorageBuffer %22 %uint_0 %uint_0 %uint_0 %24
+         %28 = OpExtInst %void %20 SpecConstantWorkgroupSize %uint_0 %uint_1 %uint_2
+)"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  p->SkipDumpingPending(
+      "crbug.com/tint/1041 track access mode in spirv-reader parser type");
+}
+
+// TODO(dneto): We don't currently support other kinds of extended instruction
+// imports.
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_module_var_test.cc b/src/tint/reader/spirv/parser_impl_module_var_test.cc
new file mode 100644
index 0000000..1a242b5
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_module_var_test.cc
@@ -0,0 +1,5378 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+#include "src/tint/utils/string.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using SpvModuleScopeVarParserTest = SpvParserTest;
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+std::string Preamble() {
+  return R"(
+   OpCapability Shader
+   OpMemoryModel Logical Simple
+)";
+}
+
+std::string FragMain() {
+  return R"(
+   OpEntryPoint Fragment %main "main"
+   OpExecutionMode %main OriginUpperLeft
+)";
+}
+
+std::string MainBody() {
+  return R"(
+   %main = OpFunction %void None %voidfn
+   %main_entry = OpLabel
+   OpReturn
+   OpFunctionEnd
+)";
+}
+
+std::string CommonCapabilities() {
+  return R"(
+    OpCapability Shader
+    OpCapability SampleRateShading
+    OpMemoryModel Logical Simple
+)";
+}
+
+std::string CommonTypes() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+
+    %ptr_bool = OpTypePointer Private %bool
+    %ptr_float = OpTypePointer Private %float
+    %ptr_uint = OpTypePointer Private %uint
+    %ptr_int = OpTypePointer Private %int
+
+    %true = OpConstantTrue %bool
+    %false = OpConstantFalse %bool
+    %float_0 = OpConstant %float 0.0
+    %float_1p5 = OpConstant %float 1.5
+    %uint_1 = OpConstant %uint 1
+    %int_m1 = OpConstant %int -1
+    %int_14 = OpConstant %int 14
+    %uint_2 = OpConstant %uint 2
+
+    %v2bool = OpTypeVector %bool 2
+    %v2uint = OpTypeVector %uint 2
+    %v2int = OpTypeVector %int 2
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+    %m3v2float = OpTypeMatrix %v2float 3
+
+    %arr2uint = OpTypeArray %uint %uint_2
+  )";
+}
+
+std::string StructTypes() {
+  return R"(
+    %strct = OpTypeStruct %uint %float %arr2uint
+)";
+}
+
+// Returns layout annotations for types in StructTypes()
+std::string CommonLayout() {
+  return R"(
+    OpMemberDecorate %strct 0 Offset 0
+    OpMemberDecorate %strct 1 Offset 4
+    OpMemberDecorate %strct 2 Offset 8
+    OpDecorate %arr2uint ArrayStride 4
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, NoVar) {
+  auto assembly = Preamble() + FragMain() + CommonTypes() + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_ast = test::ToString(p->program());
+  EXPECT_THAT(module_ast, Not(HasSubstr("Variable"))) << module_ast;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BadStorageClass_NotAWebGPUStorageClass) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer CrossWorkgroup %float
+    %52 = OpVariable %ptr CrossWorkgroup
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  // Normally we should run ParserImpl::RegisterTypes before emitting
+  // variables. But defensive coding in EmitModuleScopeVariables lets
+  // us catch this error.
+  EXPECT_FALSE(p->EmitModuleScopeVariables()) << p->error();
+  EXPECT_THAT(p->error(), HasSubstr("unknown SPIR-V storage class: 5"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BadStorageClass_Function) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Function %float
+    %52 = OpVariable %ptr Function
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  // Normally we should run ParserImpl::RegisterTypes before emitting
+  // variables. But defensive coding in EmitModuleScopeVariables lets
+  // us catch this error.
+  EXPECT_FALSE(p->EmitModuleScopeVariables()) << p->error();
+  EXPECT_THAT(p->error(),
+              HasSubstr("invalid SPIR-V storage class 7 for module scope "
+                        "variable: %52 = OpVariable %3 Function"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BadPointerType) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    %float = OpTypeFloat 32
+    %fn_ty = OpTypeFunction %float
+    %3 = OpTypePointer Private %fn_ty
+    %52 = OpVariable %3 Private
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  // Normally we should run ParserImpl::RegisterTypes before emitting
+  // variables. But defensive coding in EmitModuleScopeVariables lets
+  // us catch this error.
+  EXPECT_FALSE(p->EmitModuleScopeVariables());
+  EXPECT_THAT(p->error(), HasSubstr("internal error: failed to register Tint "
+                                    "AST type for SPIR-V type with ID: 3"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, NonPointerType) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    %float = OpTypeFloat 32
+    %5 = OpTypeFunction %float
+    %3 = OpTypePointer Private %5
+    %52 = OpVariable %float Private
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_FALSE(p->RegisterTypes());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("SPIR-V pointer type with ID 3 has invalid pointee type 5"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, AnonWorkgroupVar) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Workgroup %float
+    %52 = OpVariable %ptr Workgroup
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("var<workgroup> x_52 : f32;"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, NamedWorkgroupVar) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    OpName %52 "the_counter"
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Workgroup %float
+    %52 = OpVariable %ptr Workgroup
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("var<workgroup> the_counter : f32;"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, PrivateVar) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+    OpName %52 "my_own_private_idaho"
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Private %float
+    %52 = OpVariable %ptr Private
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> my_own_private_idaho : f32;"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinVertexIndex) {
+  // This is the simple case for the vertex_index builtin,
+  // where the SPIR-V uses the same store type as in WGSL.
+  // See later for tests where the SPIR-V store type is signed
+  // integer, as in GLSL.
+  auto p = parser(test::Assemble(Preamble() + R"(
+    OpEntryPoint Vertex %main "main" %52 %position
+    OpName %position "position"
+    OpDecorate %position BuiltIn Position
+    OpDecorate %52 BuiltIn VertexIndex
+    %uint = OpTypeInt 32 0
+    %ptr = OpTypePointer Input %uint
+    %52 = OpVariable %ptr Input
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %posty = OpTypePointer Output %v4float
+    %position = OpVariable %posty Output
+  )" + MainBody()));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("var<private> x_52 : u32;"));
+}
+
+std::string PerVertexPreamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1
+
+    OpDecorate %10 Block
+    OpMemberDecorate %10 0 BuiltIn Position
+    OpMemberDecorate %10 1 BuiltIn PointSize
+    OpMemberDecorate %10 2 BuiltIn ClipDistance
+    OpMemberDecorate %10 3 BuiltIn CullDistance
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %12 = OpTypeVector %float 4
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %arr = OpTypeArray %float %uint_1
+    %10 = OpTypeStruct %12 %float %arr %arr
+    %11 = OpTypePointer Output %10
+    %1 = OpVariable %11 Output
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_StoreWholeStruct_NotSupported) {
+  // Glslang does not generate this code pattern.
+  const std::string assembly = PerVertexPreamble() + R"(
+  %nil = OpConstantNull %10 ; the whole struct
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpStore %1 %nil  ; store the whole struct
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_THAT(p->error(), Eq("storing to the whole per-vertex structure is not "
+                             "supported: OpStore %1 %13"))
+      << p->error();
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_IntermediateWholeStruct_NotSupported) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %1000 = OpUndef %10
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+  EXPECT_THAT(p->error(), Eq("operations producing a per-vertex structure are "
+                             "not supported: %1000 = OpUndef %10"))
+      << p->error();
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_IntermediatePtrWholeStruct_NotSupported) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %1000 = OpCopyObject %11 %1
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              Eq("operations producing a pointer to a per-vertex structure are "
+                 "not supported: %1000 = OpCopyObject %11 %1"))
+      << p->error();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_StorePosition) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_v4float = OpTypePointer Output %12
+  %nil = OpConstantNull %12
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr_v4float %1 %uint_0 ; address of the Position member
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl) {
+  const std::string assembly = R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint Vertex %main "main" %1
+
+ ;  scramble the member indices
+  OpDecorate %10 Block
+  OpMemberDecorate %10 0 BuiltIn ClipDistance
+  OpMemberDecorate %10 1 BuiltIn CullDistance
+  OpMemberDecorate %10 2 BuiltIn Position
+  OpMemberDecorate %10 3 BuiltIn PointSize
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %float = OpTypeFloat 32
+  %12 = OpTypeVector %float 4
+  %uint = OpTypeInt 32 0
+  %uint_0 = OpConstant %uint 0
+  %uint_1 = OpConstant %uint 1
+  %uint_2 = OpConstant %uint 2
+  %arr = OpTypeArray %float %uint_1
+  %10 = OpTypeStruct %arr %arr %12 %float
+  %11 = OpTypePointer Output %10
+  %1 = OpVariable %11 Output
+
+  %ptr_v4float = OpTypePointer Output %12
+  %nil = OpConstantNull %12
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr_v4float %1 %uint_2 ; address of the Position member
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_StorePositionMember_OneAccessChain) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr_float %1 %uint_0 %uint_1 ; address of the Position.y member
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_StorePositionMember_TwoAccessChain) {
+  // The algorithm is smart enough to collapse it down.
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %12
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr %1 %uint_0 ; address of the Position member
+  %101 = OpAccessChain %ptr_float %100 %uint_1 ; address of the Position.y member
+  OpStore %101 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Write1_IsErased) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %one = OpConstant %float 1.0
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
+  OpStore %100 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  gl_Position : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(gl_Position);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_WriteNon1_IsError) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %999 = OpConstant %float 2.0
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
+  OpStore %100 %999
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              HasSubstr("cannot store a value other than constant 1.0 to "
+                        "PointSize builtin: OpStore %100 %999"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_ReadReplaced) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %nil = OpConstantNull %12
+  %private_ptr = OpTypePointer Private %float
+  %900 = OpVariable %private_ptr Private
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
+  %99 = OpLoad %float %100
+  OpStore %900 %99
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> x_900 : f32;
+
+var<private> gl_Position : vec4<f32>;
+
+fn main_1() {
+  x_900 = 1.0;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  gl_Position : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(gl_Position);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_WriteViaCopyObjectPriorAccess_Unsupported) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %nil = OpConstantNull %12
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %20 = OpCopyObject %11 %1
+  %100 = OpAccessChain %20 %1 %uint_1 ; address of the PointSize member
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("operations producing a pointer to a per-vertex structure are "
+                "not supported: %20 = OpCopyObject %11 %1"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %one = OpConstant %float 1.0
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
+  %101 = OpCopyObject %ptr %100
+  OpStore %101 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  gl_Position : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(gl_Position);
+}
+)") << module_str;
+}
+
+std::string LoosePointSizePreamble(std::string stage = "Vertex") {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint )" +
+         stage + R"( %500 "main" %1
+)" + (stage == "Vertex" ? " %2 " : "") +
+         +(stage == "Fragment" ? "OpExecutionMode %500 OriginUpperLeft" : "") +
+         +(stage == "Vertex" ? " OpDecorate %2 BuiltIn Position " : "") +
+         R"(
+    OpDecorate %1 BuiltIn PointSize
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %11 = OpTypePointer Output %float
+    %1 = OpVariable %11 Output
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_Write1_IsErased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpStore %1 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_2);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_WriteNon1_IsError) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %999 = OpConstant %float 2.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpStore %1 %999
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              HasSubstr("cannot store a value other than constant 1.0 to "
+                        "PointSize builtin: OpStore %1 %999"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_ReadReplaced_Vertex) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Private %float
+  %900 = OpVariable %ptr Private
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %99 = OpLoad %float %1
+  OpStore %900 %99
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+
+var<private> x_900 : f32;
+
+fn main_1() {
+  x_900 = 1.0;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_2);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_ReadReplaced_Fragment) {
+  const std::string assembly = LoosePointSizePreamble("Fragment") + R"(
+  %ptr = OpTypePointer Private %float
+  %900 = OpVariable %ptr Private
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %99 = OpLoad %float %1
+  OpStore %900 %99
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  // This example is invalid because you PointSize is not valid in Vulkan
+  // Fragment shaders.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(), HasSubstr("VUID-PointSize-PointSize-04314"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %20 = OpCopyObject %11 %1
+  %100 = OpAccessChain %11 %20
+  OpStore %100 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_2);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %11 %1
+  %101 = OpCopyObject %11 %100
+  OpStore %101 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto module_str = test::ToString(p->program());
+  EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_2);
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinClipDistance_NotSupported) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+  %uint_2 = OpConstant %uint 2
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+; address of the first entry in ClipDistance
+  %100 = OpAccessChain %ptr_float %1 %uint_2 %uint_0
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_EQ(p->error(),
+            "accessing per-vertex member 2 is not supported. Only Position is "
+            "supported, and PointSize is ignored");
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinCullDistance_NotSupported) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+  %uint_3 = OpConstant %uint 3
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+; address of the first entry in CullDistance
+  %100 = OpAccessChain %ptr_float %1 %uint_3 %uint_0
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_EQ(p->error(),
+            "accessing per-vertex member 3 is not supported. Only Position is "
+            "supported, and PointSize is ignored");
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPerVertex_MemberIndex_NotConstant) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %sum = OpIAdd %uint %uint_0 %uint_0
+  %100 = OpAccessChain %ptr_float %1 %sum
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              Eq("first index of access chain into per-vertex structure is not "
+                 "a constant: %100 = OpAccessChain %13 %1 %16"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPerVertex_MemberIndex_NotConstantInteger) {
+  const std::string assembly = PerVertexPreamble() + R"(
+  %ptr_float = OpTypePointer Output %float
+  %nil = OpConstantNull %float
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+; nil is bad here!
+  %100 = OpAccessChain %ptr_float %1 %nil
+  OpStore %100 %nil
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              Eq("first index of access chain into per-vertex structure is not "
+                 "a constant integer: %100 = OpAccessChain %13 %1 %14"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarInitializers) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %1 = OpVariable %ptr_bool Private %true
+     %2 = OpVariable %ptr_bool Private %false
+     %3 = OpVariable %ptr_int Private %int_m1
+     %4 = OpVariable %ptr_uint Private %uint_1
+     %5 = OpVariable %ptr_float Private %float_1p5
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = true;
+
+var<private> x_2 : bool = false;
+
+var<private> x_3 : i32 = -1;
+
+var<private> x_4 : u32 = 1u;
+
+var<private> x_5 : f32 = 1.5;
+)"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarNullInitializers) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %null_bool = OpConstantNull %bool
+     %null_int = OpConstantNull %int
+     %null_uint = OpConstantNull %uint
+     %null_float = OpConstantNull %float
+
+     %1 = OpVariable %ptr_bool Private %null_bool
+     %2 = OpVariable %ptr_int Private %null_int
+     %3 = OpVariable %ptr_uint Private %null_uint
+     %4 = OpVariable %ptr_float Private %null_float
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = false;
+
+var<private> x_2 : i32 = 0;
+
+var<private> x_3 : u32 = 0u;
+
+var<private> x_4 : f32 = 0.0;
+)"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarUndefInitializers) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %undef_bool = OpUndef %bool
+     %undef_int = OpUndef %int
+     %undef_uint = OpUndef %uint
+     %undef_float = OpUndef %float
+
+     %1 = OpVariable %ptr_bool Private %undef_bool
+     %2 = OpVariable %ptr_int Private %undef_int
+     %3 = OpVariable %ptr_uint Private %undef_uint
+     %4 = OpVariable %ptr_float Private %undef_float
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(var<private> x_1 : bool = false;
+
+var<private> x_2 : i32 = 0;
+
+var<private> x_3 : u32 = 0u;
+
+var<private> x_4 : f32 = 0.0;
+)"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2float
+     %two = OpConstant %float 2.0
+     %const = OpConstantComposite %v2float %float_1p5 %two
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorBoolNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2bool
+     %const = OpConstantNull %v2bool
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<bool> = vec2<bool>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorBoolUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2bool
+     %const = OpUndef %v2bool
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<bool> = vec2<bool>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorUintNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2uint
+     %const = OpConstantNull %v2uint
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorUintUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2uint
+     %const = OpUndef %v2uint
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorIntNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2int
+     %const = OpConstantNull %v2int
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorIntUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2int
+     %const = OpUndef %v2int
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorFloatNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2float
+     %const = OpConstantNull %v2float
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VectorFloatUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %v2float
+     %const = OpUndef %v2float
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, MatrixInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %m3v2float
+     %two = OpConstant %float 2.0
+     %three = OpConstant %float 3.0
+     %four = OpConstant %float 4.0
+     %v0 = OpConstantComposite %v2float %float_1p5 %two
+     %v1 = OpConstantComposite %v2float %two %three
+     %v2 = OpConstantComposite %v2float %three %four
+     %const = OpConstantComposite %m3v2float %v0 %v1 %v2
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>("
+                        "vec2<f32>(1.5, 2.0), "
+                        "vec2<f32>(2.0, 3.0), "
+                        "vec2<f32>(3.0, 4.0));"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, MatrixNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %m3v2float
+     %const = OpConstantNull %m3v2float
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, MatrixUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %m3v2float
+     %const = OpUndef %m3v2float
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str,
+              HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ArrayInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %arr2uint
+     %two = OpConstant %uint 2
+     %const = OpConstantComposite %arr2uint %uint_1 %two
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr(
+          "var<private> x_200 : array<u32, 2u> = array<u32, 2u>(1u, 2u);"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ArrayNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %arr2uint
+     %const = OpConstantNull %arr2uint
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : array<u32, 2u> = array<u32, 2u>();"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ArrayUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr = OpTypePointer Private %arr2uint
+     %const = OpUndef %arr2uint
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : array<u32, 2u> = array<u32, 2u>();"));
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, StructInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
+                                 StructTypes() + R"(
+     %ptr = OpTypePointer Private %strct
+     %two = OpConstant %uint 2
+     %arrconst = OpConstantComposite %arr2uint %uint_1 %two
+     %const = OpConstantComposite %strct %uint_1 %float_1p5 %arrconst
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, StructNullInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
+                                 StructTypes() + R"(
+     %ptr = OpTypePointer Private %strct
+     %const = OpConstantNull %strct
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, StructUndefInitializer) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonTypes() +
+                                 StructTypes() + R"(
+     %ptr = OpTypePointer Private %strct
+     %const = OpUndef %strct
+     %200 = OpVariable %ptr Private %const
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
+      << module_str;
+
+  // This example module emits ok, but is not valid SPIR-V in the first place.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       LocationDecoration_MissingOperandWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar Location
+)" + CommonTypes() + R"(
+     %ptr = OpTypePointer Input %uint
+     %myvar = OpVariable %ptr Input
+  )" + MainBody();
+  EXPECT_THAT(test::AssembleFailure(assembly),
+              Eq("10:4: Expected operand, found next instruction instead."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       LocationDecoration_TwoOperandsWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar Location 3 4
+)" + CommonTypes() + R"(
+     %ptr = OpTypePointer Input %uint
+     %myvar = OpVariable %ptr Input
+  )" + MainBody();
+  EXPECT_THAT(
+      test::AssembleFailure(assembly),
+      Eq("8:34: Expected <opcode> or <result-id> at the beginning of an "
+         "instruction, found '4'."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, DescriptorGroupDecoration_Valid) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonLayout() + R"(
+     OpDecorate %1 DescriptorSet 3
+     OpDecorate %1 Binding 9 ; Required to pass WGSL validation
+     OpDecorate %strct Block
+)" + CommonTypes() + StructTypes() +
+                                 R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %1 = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("@group(3) @binding(9) var<storage, read_write> x_1 : S;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       DescriptorGroupDecoration_MissingOperandWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + CommonLayout() + R"(
+     OpDecorate %1 DescriptorSet
+     OpDecorate %strct Block
+)" + CommonTypes() + StructTypes() +
+                        R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %1 = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody();
+  EXPECT_THAT(test::AssembleFailure(assembly),
+              Eq("13:5: Expected operand, found next instruction instead."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       DescriptorGroupDecoration_TwoOperandsWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 3 4
+     OpDecorate %strct Block
+)" + CommonTypes() + StructTypes() +
+                        R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %myvar = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody();
+  EXPECT_THAT(
+      test::AssembleFailure(assembly),
+      Eq("8:39: Expected <opcode> or <result-id> at the beginning of an "
+         "instruction, found '4'."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BindingDecoration_Valid) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0 ; WGSL validation requires this already
+     OpDecorate %1 Binding 3
+     OpDecorate %strct Block
+)" + CommonLayout() + CommonTypes() +
+                                 StructTypes() +
+                                 R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %1 = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(
+      module_str,
+      HasSubstr("@group(0) @binding(3) var<storage, read_write> x_1 : S;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BindingDecoration_MissingOperandWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar Binding
+     OpDecorate %strct Block
+)" + CommonTypes() + StructTypes() +
+                        R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %myvar = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody();
+  EXPECT_THAT(test::AssembleFailure(assembly),
+              Eq("9:5: Expected operand, found next instruction instead."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BindingDecoration_TwoOperandsWontAssemble) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar Binding 3 4
+     OpDecorate %strct Block
+)" + CommonTypes() + StructTypes() +
+                        R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %myvar = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody();
+  EXPECT_THAT(
+      test::AssembleFailure(assembly),
+      Eq("8:33: Expected <opcode> or <result-id> at the beginning of an "
+         "instruction, found '4'."));
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       StructMember_NonReadableDecoration_Dropped) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
+     OpDecorate %strct Block
+     OpMemberDecorate %strct 0 NonReadable
+)" + CommonLayout() + CommonTypes() +
+                                 StructTypes() + R"(
+     %ptr_sb_strct = OpTypePointer StorageBuffer %strct
+     %1 = OpVariable %ptr_sb_strct StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(type Arr = @stride(4) array<u32, 2u>;
+
+struct S {
+  field0 : u32;
+  field1 : f32;
+  field2 : Arr;
+}
+
+@group(0) @binding(0) var<storage, read_write> x_1 : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ColMajorDecoration_Dropped) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
+     OpDecorate %s Block
+     OpMemberDecorate %s 0 ColMajor
+     OpMemberDecorate %s 0 Offset 0
+     OpMemberDecorate %s 0 MatrixStride 8
+     %float = OpTypeFloat 32
+     %v2float = OpTypeVector %float 2
+     %m3v2float = OpTypeMatrix %v2float 3
+
+     %s = OpTypeStruct %m3v2float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %myvar = OpVariable %ptr_sb_s StorageBuffer
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  field0 : mat3x2<f32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> myvar : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, MatrixStrideDecoration_Natural_Dropped) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
+     OpDecorate %s Block
+     OpMemberDecorate %s 0 MatrixStride 8
+     OpMemberDecorate %s 0 Offset 0
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v2float = OpTypeVector %float 2
+     %m3v2float = OpTypeMatrix %v2float 3
+
+     %s = OpTypeStruct %m3v2float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %myvar = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  field0 : mat3x2<f32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> myvar : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, MatrixStrideDecoration) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
+     OpDecorate %s Block
+     OpMemberDecorate %s 0 MatrixStride 64
+     OpMemberDecorate %s 0 Offset 0
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v2float = OpTypeVector %float 2
+     %m3v2float = OpTypeMatrix %v2float 3
+
+     %s = OpTypeStruct %m3v2float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %myvar = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  @stride(64) @internal(disable_validation__ignore_stride)
+  field0 : mat3x2<f32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> myvar : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, RowMajorDecoration_IsError) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %myvar "myvar"
+     OpDecorate %s Block
+     OpMemberDecorate %s 0 RowMajor
+     OpMemberDecorate %s 0 Offset 0
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %v2float = OpTypeVector %float 2
+     %m3v2float = OpTypeMatrix %v2float 3
+
+     %s = OpTypeStruct %m3v2float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %myvar = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_THAT(
+      p->error(),
+      Eq(R"(WGSL does not support row-major matrices: can't translate member 0 of %3 = OpTypeStruct %8)"))
+      << p->error();
+}
+
+TEST_F(SpvModuleScopeVarParserTest, StorageBuffer_NonWritable_AllMembers) {
+  // Variable should have access(read)
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %s Block
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
+     OpMemberDecorate %s 0 NonWritable
+     OpMemberDecorate %s 1 NonWritable
+     OpMemberDecorate %s 0 Offset 0
+     OpMemberDecorate %s 1 Offset 4
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+
+     %s = OpTypeStruct %float %float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %1 = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  field0 : f32;
+  field1 : f32;
+}
+
+@group(0) @binding(0) var<storage, read> x_1 : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, StorageBuffer_NonWritable_NotAllMembers) {
+  // Variable should have access(read_write)
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
+     OpDecorate %s Block
+     OpMemberDecorate %s 0 NonWritable
+     OpMemberDecorate %s 0 Offset 0
+     OpMemberDecorate %s 1 Offset 4
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+
+     %s = OpTypeStruct %float %float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %1 = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  field0 : f32;
+  field1 : f32;
+}
+
+@group(0) @binding(0) var<storage, read_write> x_1 : S;
+)")) << module_str;
+}
+
+TEST_F(
+    SpvModuleScopeVarParserTest,
+    StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember) {  // NOLINT
+  // Variable should have access(read_write)
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %s Block
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
+     OpMemberDecorate %s 0 NonWritable
+     OpMemberDecorate %s 0 NonWritable ; same member. Don't double-count it
+     OpMemberDecorate %s 0 Offset 0
+     OpMemberDecorate %s 1 Offset 4
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+
+     %s = OpTypeStruct %float %float
+     %ptr_sb_s = OpTypePointer StorageBuffer %s
+     %1 = OpVariable %ptr_sb_s StorageBuffer
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  field0 : f32;
+  field1 : f32;
+}
+
+@group(0) @binding(0) var<storage, read_write> x_1 : S;
+)")) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_Id_TooBig) {
+  // Override IDs must be between 0 and 65535
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 SpecId 65536
+     %bool = OpTypeBool
+     %1 = OpSpecConstantTrue %bool
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_FALSE(p->Parse());
+  EXPECT_EQ(p->error(),
+            "SpecId too large. WGSL override IDs must be between 0 and 65535: "
+            "ID %1 has SpecId 65536");
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       ScalarSpecConstant_DeclareConst_Id_MaxValid) {
+  // Override IDs must be between 0 and 65535
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 SpecId 65535
+     %bool = OpTypeBool
+     %1 = OpSpecConstantTrue %bool
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  EXPECT_TRUE(p->Parse());
+  EXPECT_EQ(p->error(), "");
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_True) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     OpDecorate %c SpecId 12
+     %bool = OpTypeBool
+     %c = OpSpecConstantTrue %bool
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : bool = true;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_False) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     OpDecorate %c SpecId 12
+     %bool = OpTypeBool
+     %c = OpSpecConstantFalse %bool
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : bool = false;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_U32) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     OpDecorate %c SpecId 12
+     %uint = OpTypeInt 32 0
+     %c = OpSpecConstant %uint 42
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : u32 = 42u;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_I32) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     OpDecorate %c SpecId 12
+     %int = OpTypeInt 32 1
+     %c = OpSpecConstant %int 42
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : i32 = 42;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_F32) {
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     OpDecorate %c SpecId 12
+     %float = OpTypeFloat 32
+     %c = OpSpecConstant %float 2.5
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : f32 = 2.5;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       ScalarSpecConstant_DeclareConst_F32_WithoutSpecId) {
+  // When we don't have a spec ID, declare an undecorated module-scope constant.
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     %float = OpTypeFloat 32
+     %c = OpSpecConstant %float 2.5
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+  )" + MainBody()));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  EXPECT_THAT(module_str, HasSubstr("override myconst : f32 = 2.5;"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_UsedInFunction) {
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpName %c "myconst"
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+     %c = OpSpecConstant %float 2.5
+     %floatfn = OpTypeFunction %float
+     %100 = OpFunction %float None %floatfn
+     %entry = OpLabel
+     %1 = OpFAdd %float %c %c
+     OpReturnValue %1
+     OpFunctionEnd
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  auto fe = p->function_emitter(100);
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+
+  Program program = p->program();
+  const auto got = test::ToString(program, fe.ast_body());
+
+  EXPECT_THAT(got, HasSubstr("return (myconst + myconst);")) << got;
+}
+
+// Returns the start of a shader for testing SampleId,
+// parameterized by store type of %int or %uint
+std::string SampleIdPreamble(std::string store_type) {
+  return R"(
+    OpCapability Shader
+    OpCapability SampleRateShading
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %main "main" %1
+    OpExecutionMode %main OriginUpperLeft
+    OpDecorate %1 BuiltIn SampleId
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %ptr_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %1 = OpVariable %ptr_ty Input
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_Direct) {
+  const std::string assembly = SampleIdPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %int %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : i32;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_index) x_1_param : u32) {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_CopyObject) {
+  const std::string assembly = SampleIdPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected =
+      R"(Module{
+  Variable{
+    x_1
+    private
+    undefined
+    __i32
+  }
+  Function main_1 -> __void
+  ()
+  {
+    VariableDeclStatement{
+      VariableConst{
+        x_11
+        none
+        undefined
+        __ptr_private__i32
+        {
+          UnaryOp[not set]{
+            address-of
+            Identifier[not set]{x_1}
+          }
+        }
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        undefined
+        __i32
+        {
+          UnaryOp[not set]{
+            indirection
+            Identifier[not set]{x_14}
+          }
+        }
+      }
+    }
+    Return{}
+  }
+  Function main -> __void
+  StageDecoration{fragment}
+  (
+    VariableConst{
+      Decorations{
+        BuiltinDecoration{sample_index}
+      }
+      x_1_param
+      none
+      undefined
+      __u32
+    }
+  )
+  {
+    Assignment{
+      Identifier[not set]{x_1}
+      Bitcast[not set]<__i32>{
+        Identifier[not set]{x_1_param}
+      }
+    }
+    Call[not set]{
+      Identifier[not set]{main_1}
+      (
+      )
+    }
+  }
+}
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_AccessChain) {
+  const std::string assembly = SampleIdPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_index) x_1_param : u32) {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_FunctParam) {
+  const std::string assembly = SampleIdPreamble("%int") + R"(
+    %helper_ty = OpTypeFunction %int %ptr_ty
+    %helper = OpFunction %int None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %int %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %int %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+
+  // This example is invalid because you can't pass pointer-to-Input
+  // as a function parameter.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_FALSE(p->success());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Invalid storage class for pointer operand 1"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_Direct) {
+  const std::string assembly = SampleIdPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %uint %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_index) x_1_param : u32) {
+  x_1 = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_CopyObject) {
+  const std::string assembly = SampleIdPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+fn main_1() {
+  let x_11 : ptr<private, u32> = &(x_1);
+  let x_2 : u32 = *(x_11);
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_index) x_1_param : u32) {
+  x_1 = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_AccessChain) {
+  const std::string assembly = SampleIdPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_index) x_1_param : u32) {
+  x_1 = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_FunctParam) {
+  const std::string assembly = SampleIdPreamble("%uint") + R"(
+    %helper_ty = OpTypeFunction %uint %ptr_ty
+    %helper = OpFunction %uint None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %uint %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %uint %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  // This example is invalid because you can't pass pointer-to-Input
+  // as a function parameter.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Invalid storage class for pointer operand 1"));
+}
+
+// Returns the start of a shader for testing SampleMask
+// parameterized by store type.
+std::string SampleMaskPreamble(std::string store_type, uint32_t stride = 0u) {
+  return std::string(R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %main "main" %1
+    OpExecutionMode %main OriginUpperLeft
+    OpDecorate %1 BuiltIn SampleMask
+)") +
+         (stride > 0u ? R"(
+    OpDecorate %uarr1 ArrayStride 4
+    OpDecorate %uarr2 ArrayStride 4
+    OpDecorate %iarr1 ArrayStride 4
+    OpDecorate %iarr2 ArrayStride 4
+)"
+                      : "") +
+         R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %int_12 = OpConstant %int 12
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+    %uarr1 = OpTypeArray %uint %uint_1
+    %uarr2 = OpTypeArray %uint %uint_2
+    %iarr1 = OpTypeArray %int %uint_1
+    %iarr2 = OpTypeArray %int %uint_2
+    %iptr_in_ty = OpTypePointer Input %int
+    %uptr_in_ty = OpTypePointer Input %uint
+    %iptr_out_ty = OpTypePointer Output %int
+    %uptr_out_ty = OpTypePointer Output %uint
+    %in_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %out_ty = OpTypePointer Output )" +
+         store_type + R"(
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_ArraySize2_Error) {
+  const std::string assembly = SampleMaskPreamble("%uarr2") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
+    %3 = OpLoad %int %2
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              HasSubstr("WGSL supports a sample mask of at most 32 bits. "
+                        "SampleMask must be an array of 1 element"))
+      << p->error() << assembly;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_Direct) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
+    %3 = OpLoad %uint %2
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  let x_3 : u32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_CopyObject) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
+    %3 = OpCopyObject %uptr_in_ty %2
+    %4 = OpLoad %uint %3
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  let x_4 : u32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_AccessChain) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
+    %3 = OpAccessChain %uptr_in_ty %2
+    %4 = OpLoad %uint %3
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  let x_4 : u32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_Direct) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
+    %3 = OpLoad %int %2
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  let x_3 : i32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_CopyObject) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
+    %3 = OpCopyObject %iptr_in_ty %2
+    %4 = OpLoad %int %3
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  let x_4 : i32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_AccessChain) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_in_ty %1 %uint_0
+    %3 = OpAccessChain %iptr_in_ty %2
+    %4 = OpLoad %int %3
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  let x_4 : i32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_ArraySize2_Error) {
+  const std::string assembly = SampleMaskPreamble("%uarr2") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
+    OpStore %2 %uint_0
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              HasSubstr("WGSL supports a sample mask of at most 32 bits. "
+                        "SampleMask must be an array of 1 element"))
+      << p->error() << assembly;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_Direct) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
+    OpStore %2 %uint_0
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  x_1[0] = 0u;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0]);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_CopyObject) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
+    %3 = OpCopyObject %uptr_out_ty %2
+    OpStore %2 %uint_0
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  x_1[0] = 0u;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0]);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_AccessChain) {
+  const std::string assembly = SampleMaskPreamble("%uarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
+    %3 = OpAccessChain %uptr_out_ty %2
+    OpStore %2 %uint_0
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  x_1[0] = 0u;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0]);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_Direct) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
+    OpStore %2 %int_12
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  x_1[0] = 12;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(bitcast<u32>(x_1[0]));
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_CopyObject) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
+    %3 = OpCopyObject %iptr_out_ty %2
+    OpStore %2 %int_12
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  x_1[0] = 12;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(bitcast<u32>(x_1[0]));
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_AccessChain) {
+  const std::string assembly = SampleMaskPreamble("%iarr1") + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %iptr_out_ty %1 %uint_0
+    %3 = OpAccessChain %iptr_out_ty %2
+    OpStore %2 %int_12
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  x_1[0] = 12;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(bitcast<u32>(x_1[0]));
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_WithStride) {
+  const std::string assembly = SampleMaskPreamble("%uarr1", 4u) + R"(
+    %1 = OpVariable %in_ty Input
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_in_ty %1 %uint_0
+    %3 = OpLoad %uint %2
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(type Arr = @stride(4) array<u32, 1u>;
+
+type Arr_1 = @stride(4) array<u32, 2u>;
+
+type Arr_2 = @stride(4) array<i32, 1u>;
+
+type Arr_3 = @stride(4) array<i32, 2u>;
+
+var<private> x_1 : Arr;
+
+fn main_1() {
+  let x_3 : u32 = x_1[0];
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_WithStride) {
+  const std::string assembly = SampleMaskPreamble("%uarr1", 4u) + R"(
+    %1 = OpVariable %out_ty Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpAccessChain %uptr_out_ty %1 %uint_0
+    OpStore %2 %uint_0
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(type Arr = @stride(4) array<u32, 1u>;
+
+type Arr_1 = @stride(4) array<u32, 2u>;
+
+type Arr_2 = @stride(4) array<i32, 1u>;
+
+type Arr_3 = @stride(4) array<i32, 2u>;
+
+var<private> x_1 : Arr;
+
+fn main_1() {
+  x_1[0] = 0u;
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0]);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+// Returns the start of a shader for testing VertexIndex,
+// parameterized by store type of %int or %uint
+std::string VertexIndexPreamble(std::string store_type) {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %position %1
+    OpDecorate %position BuiltIn Position
+    OpDecorate %1 BuiltIn VertexIndex
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %ptr_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %1 = OpVariable %ptr_ty Input
+    %v4float = OpTypeVector %float 4
+    %posty = OpTypePointer Output %v4float
+    %position = OpVariable %posty Output
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_Direct) {
+  const std::string assembly = VertexIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %int %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_CopyObject) {
+  const std::string assembly = VertexIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_14 : ptr<private, i32> = &(x_1);
+  let x_2 : i32 = *(x_14);
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_I32_Load_AccessChain) {
+  const std::string assembly = VertexIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_Direct) {
+  const std::string assembly = VertexIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %uint %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_CopyObject) {
+  const std::string assembly = VertexIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_14 : ptr<private, u32> = &(x_1);
+  let x_2 : u32 = *(x_14);
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_Load_AccessChain) {
+  const std::string assembly = VertexIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, VertexIndex_U32_FunctParam) {
+  const std::string assembly = VertexIndexPreamble("%uint") + R"(
+    %helper_ty = OpTypeFunction %uint %ptr_ty
+    %helper = OpFunction %uint None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %uint %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %uint %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+
+  // This example is invalid because you can't pass pointer-to-Input
+  // as a function parameter.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Invalid storage class for pointer operand 1"));
+}
+
+// Returns the start of a shader for testing InstanceIndex,
+// parameterized by store type of %int or %uint
+std::string InstanceIndexPreamble(std::string store_type) {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %position %1
+    OpName %position "position"
+    OpDecorate %position BuiltIn Position
+    OpDecorate %1 BuiltIn InstanceIndex
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %ptr_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %1 = OpVariable %ptr_ty Input
+    %v4float = OpTypeVector %float 4
+    %posty = OpTypePointer Output %v4float
+    %position = OpVariable %posty Output
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_Direct) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %int %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_CopyObject) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_14 : ptr<private, i32> = &(x_1);
+  let x_2 : i32 = *(x_14);
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_AccessChain) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_FunctParam) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %helper_ty = OpTypeFunction %int %ptr_ty
+    %helper = OpFunction %int None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %int %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %int %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  // This example is invalid because you can't pass pointer-to-Input
+  // as a function parameter.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Invalid storage class for pointer operand 1"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_Direct) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %uint %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_CopyObject) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_14 : ptr<private, u32> = &(x_1);
+  let x_2 : u32 = *(x_14);
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_AccessChain) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> position : vec4<f32>;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  position_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(position);
+}
+)";
+  EXPECT_EQ(module_str, expected);
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_FunctParam) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %helper_ty = OpTypeFunction %uint %ptr_ty
+    %helper = OpFunction %uint None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %uint %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %uint %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  // This example is invalid because you can't pass pointer-to-Input
+  // as a function parameter.
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(p->error(),
+              HasSubstr("Invalid storage class for pointer operand 1"));
+}
+
+// Returns the start of a shader for testing LocalInvocationIndex,
+// parameterized by store type of %int or %uint
+std::string ComputeBuiltinInputPreamble(std::string builtin,
+                                        std::string store_type) {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint GLCompute %main "main" %1
+    OpExecutionMode %main LocalSize 1 1 1
+    OpDecorate %1 BuiltIn )" +
+         builtin + R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %v3uint = OpTypeVector %uint 3
+    %v3int = OpTypeVector %int 3
+    %ptr_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %1 = OpVariable %ptr_ty Input
+)";
+}
+
+struct ComputeBuiltinInputCase {
+  std::string spirv_builtin;
+  std::string spirv_store_type;
+  std::string wgsl_builtin;
+};
+inline std::ostream& operator<<(std::ostream& o, ComputeBuiltinInputCase c) {
+  return o << "ComputeBuiltinInputCase(" << c.spirv_builtin << " "
+           << c.spirv_store_type << " " << c.wgsl_builtin << ")";
+}
+
+std::string WgslType(std::string spirv_type) {
+  if (spirv_type == "%uint") {
+    return "u32";
+  }
+  if (spirv_type == "%int") {
+    return "i32";
+  }
+  if (spirv_type == "%v3uint") {
+    return "vec3<u32>";
+  }
+  if (spirv_type == "%v3int") {
+    return "vec3<i32>";
+  }
+  return "error";
+}
+
+std::string UnsignedWgslType(std::string wgsl_type) {
+  if (wgsl_type == "u32") {
+    return "u32";
+  }
+  if (wgsl_type == "i32") {
+    return "u32";
+  }
+  if (wgsl_type == "vec3<u32>") {
+    return "vec3<u32>";
+  }
+  if (wgsl_type == "vec3<i32>") {
+    return "vec3<u32>";
+  }
+  return "error";
+}
+
+std::string SignedWgslType(std::string wgsl_type) {
+  if (wgsl_type == "u32") {
+    return "i32";
+  }
+  if (wgsl_type == "i32") {
+    return "i32";
+  }
+  if (wgsl_type == "vec3<u32>") {
+    return "vec3<i32>";
+  }
+  if (wgsl_type == "vec3<i32>") {
+    return "vec3<i32>";
+  }
+  return "error";
+}
+
+using SpvModuleScopeVarParserTest_ComputeBuiltin =
+    SpvParserTestBase<::testing::TestWithParam<ComputeBuiltinInputCase>>;
+
+TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_Direct) {
+  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
+  const auto wgsl_builtin = GetParam().wgsl_builtin;
+  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
+  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
+  const std::string assembly =
+      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
+                                  GetParam().spirv_store_type) +
+      R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad )" +
+      GetParam().spirv_store_type + R"( %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  std::string expected = R"(var<private> x_1 : ${wgsl_type};
+
+fn main_1() {
+  let x_2 : ${wgsl_type} = x_1;
+  return;
+}
+
+@stage(compute) @workgroup_size(1, 1, 1)
+fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
+  x_1 = ${assignment_value};
+  main_1();
+}
+)";
+
+  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
+  expected =
+      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
+  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
+  expected =
+      utils::ReplaceAll(expected, "${assignment_value}",
+                        (wgsl_type == unsigned_wgsl_type)
+                            ? "x_1_param"
+                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
+
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_CopyObject) {
+  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
+  const auto wgsl_builtin = GetParam().wgsl_builtin;
+  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
+  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
+  const std::string assembly =
+      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
+                                  GetParam().spirv_store_type) +
+      R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %13 = OpCopyObject %ptr_ty %1
+    %2 = OpLoad )" +
+      GetParam().spirv_store_type + R"( %13
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  std::string expected = R"(var<private> x_1 : ${wgsl_type};
+
+fn main_1() {
+  let x_13 : ptr<private, ${wgsl_type}> = &(x_1);
+  let x_2 : ${wgsl_type} = *(x_13);
+  return;
+}
+
+@stage(compute) @workgroup_size(1, 1, 1)
+fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
+  x_1 = ${assignment_value};
+  main_1();
+}
+)";
+
+  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
+  expected =
+      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
+  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
+  expected =
+      utils::ReplaceAll(expected, "${assignment_value}",
+                        (wgsl_type == unsigned_wgsl_type)
+                            ? "x_1_param"
+                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
+
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+TEST_P(SpvModuleScopeVarParserTest_ComputeBuiltin, Load_AccessChain) {
+  const auto wgsl_type = WgslType(GetParam().spirv_store_type);
+  const auto wgsl_builtin = GetParam().wgsl_builtin;
+  const auto unsigned_wgsl_type = UnsignedWgslType(wgsl_type);
+  const auto signed_wgsl_type = SignedWgslType(wgsl_type);
+  const std::string assembly =
+      ComputeBuiltinInputPreamble(GetParam().spirv_builtin,
+                                  GetParam().spirv_store_type) +
+      R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %13 = OpAccessChain %ptr_ty %1
+    %2 = OpLoad )" +
+      GetParam().spirv_store_type + R"( %13
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = test::ToString(p->program());
+  std::string expected = R"(var<private> x_1 : ${wgsl_type};
+
+fn main_1() {
+  let x_2 : ${wgsl_type} = x_1;
+  return;
+}
+
+@stage(compute) @workgroup_size(1, 1, 1)
+fn main(@builtin(${wgsl_builtin}) x_1_param : ${unsigned_wgsl_type}) {
+  x_1 = ${assignment_value};
+  main_1();
+}
+)";
+
+  expected = utils::ReplaceAll(expected, "${wgsl_type}", wgsl_type);
+  expected =
+      utils::ReplaceAll(expected, "${unsigned_wgsl_type}", unsigned_wgsl_type);
+  expected = utils::ReplaceAll(expected, "${wgsl_builtin}", wgsl_builtin);
+  expected =
+      utils::ReplaceAll(expected, "${assignment_value}",
+                        (wgsl_type == unsigned_wgsl_type)
+                            ? "x_1_param"
+                            : "bitcast<" + signed_wgsl_type + ">(x_1_param)");
+
+  EXPECT_EQ(module_str, expected) << module_str;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Samples,
+    SpvModuleScopeVarParserTest_ComputeBuiltin,
+    ::testing::ValuesIn(std::vector<ComputeBuiltinInputCase>{
+        {"LocalInvocationIndex", "%uint", "local_invocation_index"},
+        {"LocalInvocationIndex", "%int", "local_invocation_index"},
+        {"LocalInvocationId", "%v3uint", "local_invocation_id"},
+        {"LocalInvocationId", "%v3int", "local_invocation_id"},
+        {"GlobalInvocationId", "%v3uint", "global_invocation_id"},
+        {"GlobalInvocationId", "%v3int", "global_invocation_id"},
+        {"WorkgroupId", "%v3uint", "workgroup_id"},
+        {"WorkgroupId", "%v3int", "workgroup_id"}}));
+
+// TODO(dneto): crbug.com/tint/752
+// NumWorkgroups support is blocked by crbug.com/tint/752
+// When the AST supports NumWorkgroups, add these cases:
+//        {"NumWorkgroups", "%uint", "num_workgroups"}
+//        {"NumWorkgroups", "%int", "num_workgroups"}
+
+TEST_F(SpvModuleScopeVarParserTest, RegisterInputOutputVars) {
+  const std::string assembly =
+      R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %1000 "w1000"
+    OpEntryPoint Fragment %1100 "w1100" %1
+    OpEntryPoint Fragment %1200 "w1200" %2 %15
+    ; duplication is tolerated prior to SPIR-V 1.4
+    OpEntryPoint Fragment %1300 "w1300" %1 %15 %2 %1
+    OpExecutionMode %1000 OriginUpperLeft
+    OpExecutionMode %1100 OriginUpperLeft
+    OpExecutionMode %1200 OriginUpperLeft
+    OpExecutionMode %1300 OriginUpperLeft
+
+    OpDecorate %1 Location 1
+    OpDecorate %2 Location 2
+    OpDecorate %5 Location 5
+    OpDecorate %11 Location 1
+    OpDecorate %12 Location 2
+    OpDecorate %15 Location 5
+
+)" + CommonTypes() +
+      R"(
+
+    %ptr_in_uint = OpTypePointer Input %uint
+    %ptr_out_uint = OpTypePointer Output %uint
+
+    %1 = OpVariable %ptr_in_uint Input
+    %2 = OpVariable %ptr_in_uint Input
+    %5 = OpVariable %ptr_in_uint Input
+    %11 = OpVariable %ptr_out_uint Output
+    %12 = OpVariable %ptr_out_uint Output
+    %15 = OpVariable %ptr_out_uint Output
+
+    %100 = OpFunction %void None %voidfn
+    %entry_100 = OpLabel
+    %load_100 = OpLoad %uint %1
+    OpReturn
+    OpFunctionEnd
+
+    %200 = OpFunction %void None %voidfn
+    %entry_200 = OpLabel
+    %load_200 = OpLoad %uint %2
+    OpStore %15 %load_200
+    OpStore %15 %load_200
+    OpReturn
+    OpFunctionEnd
+
+    %300 = OpFunction %void None %voidfn
+    %entry_300 = OpLabel
+    %dummy_300_1 = OpFunctionCall %void %100
+    %dummy_300_2 = OpFunctionCall %void %200
+    OpReturn
+    OpFunctionEnd
+
+    ; Call nothing
+    %1000 = OpFunction %void None %voidfn
+    %entry_1000 = OpLabel
+    OpReturn
+    OpFunctionEnd
+
+    ; Call %100
+    %1100 = OpFunction %void None %voidfn
+    %entry_1100 = OpLabel
+    %dummy_1100_1 = OpFunctionCall %void %100
+    OpReturn
+    OpFunctionEnd
+
+    ; Call %200
+    %1200 = OpFunction %void None %voidfn
+    %entry_1200 = OpLabel
+    %dummy_1200_1 = OpFunctionCall %void %200
+    OpReturn
+    OpFunctionEnd
+
+    ; Call %300
+    %1300 = OpFunction %void None %voidfn
+    %entry_1300 = OpLabel
+    %dummy_1300_1 = OpFunctionCall %void %300
+    OpReturn
+    OpFunctionEnd
+
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto& info_1000 = p->GetEntryPointInfo(1000);
+  EXPECT_EQ(1u, info_1000.size());
+  EXPECT_TRUE(info_1000[0].inputs.empty());
+  EXPECT_TRUE(info_1000[0].outputs.empty());
+
+  const auto& info_1100 = p->GetEntryPointInfo(1100);
+  EXPECT_EQ(1u, info_1100.size());
+  EXPECT_THAT(info_1100[0].inputs, ElementsAre(1));
+  EXPECT_TRUE(info_1100[0].outputs.empty());
+
+  const auto& info_1200 = p->GetEntryPointInfo(1200);
+  EXPECT_EQ(1u, info_1200.size());
+  EXPECT_THAT(info_1200[0].inputs, ElementsAre(2));
+  EXPECT_THAT(info_1200[0].outputs, ElementsAre(15));
+
+  const auto& info_1300 = p->GetEntryPointInfo(1300);
+  EXPECT_EQ(1u, info_1300.size());
+  EXPECT_THAT(info_1300[0].inputs, ElementsAre(1, 2));
+  EXPECT_THAT(info_1300[0].outputs, ElementsAre(15));
+
+  // Validation incorrectly reports an overlap for the duplicated variable %1 on
+  // shader %1300
+  p->SkipDumpingPending(
+      "https://github.com/KhronosGroup/SPIRV-Tools/issues/4403");
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InputVarsConvertedToPrivate) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr_in_uint = OpTypePointer Input %uint
+     %1 = OpVariable %ptr_in_uint Input
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = "var<private> x_1 : u32;";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, OutputVarsConvertedToPrivate) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr_out_uint = OpTypePointer Output %uint
+     %1 = OpVariable %ptr_out_uint Output
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = "var<private> x_1 : u32;";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       OutputVarsConvertedToPrivate_WithInitializer) {
+  const auto assembly = Preamble() + FragMain() + CommonTypes() + R"(
+     %ptr_out_uint = OpTypePointer Output %uint
+     %1 = OpVariable %ptr_out_uint Output %uint_1
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = "var<private> x_1 : u32 = 1u;";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       Builtin_Output_Initializer_SameSignednessAsWGSL) {
+  // Only outputs can have initializers.
+  // WGSL sample_mask store type is u32.
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() + R"(
+     %arr_ty = OpTypeArray %uint %uint_1
+     %ptr_ty = OpTypePointer Output %arr_ty
+     %arr_init = OpConstantComposite %arr_ty %uint_2
+     %1 = OpVariable %ptr_ty Output %arr_init
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      "var<private> x_1 : array<u32, 1u> = array<u32, 1u>(2u);";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       Builtin_Output_Initializer_OppositeSignednessAsWGSL) {
+  // Only outputs can have initializers.
+  // WGSL sample_mask store type is u32.  Use i32 in SPIR-V
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() + R"(
+     %arr_ty = OpTypeArray %int %uint_1
+     %ptr_ty = OpTypePointer Output %arr_ty
+     %arr_init = OpConstantComposite %arr_ty %int_14
+     %1 = OpVariable %ptr_ty Output %arr_init
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      "var<private> x_1 : array<i32, 1u> = array<i32, 1u>(14);";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Builtin_Input_SameSignednessAsWGSL) {
+  // WGSL vertex_index store type is u32.
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpDecorate %1 BuiltIn VertexIndex
+)" + CommonTypes() + R"(
+     %ptr_ty = OpTypePointer Input %uint
+     %1 = OpVariable %ptr_ty Input
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = "var<private> x_1 : u32;";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Builtin_Input_OppositeSignednessAsWGSL) {
+  // WGSL vertex_index store type is u32.  Use i32 in SPIR-V.
+  const auto assembly = Preamble() + FragMain() + R"(
+     OpDecorate %1 BuiltIn VertexIndex
+)" + CommonTypes() + R"(
+     %ptr_ty = OpTypePointer Input %int
+     %1 = OpVariable %ptr_ty Input
+  )" + MainBody();
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = "var<private> x_1 : i32;";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, EntryPointWrapping_IOLocations) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1 %2 %3 %4
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 Location 0
+     OpDecorate %2 Location 0
+     OpDecorate %3 Location 30
+     OpDecorate %4 Location 6
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_uint = OpTypePointer Input %uint
+     %ptr_out_uint = OpTypePointer Output %uint
+     %1 = OpVariable %ptr_in_uint Input
+     %2 = OpVariable %ptr_out_uint Output
+     %3 = OpVariable %ptr_in_uint Input
+     %4 = OpVariable %ptr_out_uint Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : u32;
+
+var<private> x_2 : u32;
+
+var<private> x_3 : u32;
+
+var<private> x_4 : u32;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(0) @interpolate(flat)
+  x_2_1 : u32;
+  @location(6) @interpolate(flat)
+  x_4_1 : u32;
+}
+
+@stage(fragment)
+fn main(@location(0) @interpolate(flat) x_1_param : u32, @location(30) @interpolate(flat) x_3_param : u32) -> main_out {
+  x_1 = x_1_param;
+  x_3 = x_3_param;
+  main_1();
+  return main_out(x_2, x_4);
+}
+)";
+  EXPECT_THAT(got, HasSubstr(expected)) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_Input_SameSignedness) {
+  // instance_index is u32 in WGSL. Use uint in SPIR-V.
+  // No bitcasts are used for parameter formation or return value.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Vertex %main "main" %1 %position
+     OpDecorate %position BuiltIn Position
+     OpDecorate %1 BuiltIn InstanceIndex
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_uint = OpTypePointer Input %uint
+     %1 = OpVariable %ptr_in_uint Input
+     %posty = OpTypePointer Output %v4float
+     %position = OpVariable %posty Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpLoad %uint %1 ; load same signedness
+     ;;;; %3 = OpLoad %int %1 ; loading different signedness is invalid.
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : u32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : u32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = x_1_param;
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_Input_OppositeSignedness) {
+  // instance_index is u32 in WGSL. Use int in SPIR-V.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Vertex %main "main" %position %1
+     OpDecorate %position BuiltIn Position
+     OpDecorate %1 BuiltIn InstanceIndex
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_int = OpTypePointer Input %int
+     %1 = OpVariable %ptr_in_int Input
+     %posty = OpTypePointer Output %v4float
+     %position = OpVariable %posty Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %2 = OpLoad %int %1 ; load same signedness
+     ;;; %3 = OpLoad %uint %1 ; loading different signedness is invalid
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : i32;
+
+var<private> x_4 : vec4<f32>;
+
+fn main_1() {
+  let x_2 : i32 = x_1;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_4_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@builtin(instance_index) x_1_param : u32) -> main_out {
+  x_1 = bitcast<i32>(x_1_param);
+  main_1();
+  return main_out(x_4);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+// SampleMask is an array in Vulkan SPIR-V, but a scalar in WGSL.
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned) {
+  // SampleMask is u32 in WGSL.
+  // Use unsigned array element in Vulkan.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() +
+                        R"(
+     %arr = OpTypeArray %uint %uint_1
+     %ptr_ty = OpTypePointer Input %arr
+     %1 = OpVariable %ptr_ty Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<u32, 1u>;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = x_1_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_SampleMask_In_Signed) {
+  // SampleMask is u32 in WGSL.
+  // Use signed array element in Vulkan.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() +
+                        R"(
+     %arr = OpTypeArray %int %uint_1
+     %ptr_ty = OpTypePointer Input %arr
+     %1 = OpVariable %ptr_ty Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<i32, 1u>;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@builtin(sample_mask) x_1_param : u32) {
+  x_1[0] = bitcast<i32>(x_1_param);
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer) {
+  // SampleMask is u32 in WGSL.
+  // Use unsigned array element in Vulkan.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() +
+                        R"(
+     %arr = OpTypeArray %uint %uint_1
+     %ptr_ty = OpTypePointer Output %arr
+     %zero = OpConstantNull %arr
+     %1 = OpVariable %ptr_ty Output %zero
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : array<u32, 1u> = array<u32, 1u>();
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0]);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer) {
+  // SampleMask is u32 in WGSL.
+  // Use signed array element in Vulkan.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 BuiltIn SampleMask
+)" + CommonTypes() +
+                        R"(
+     %arr = OpTypeArray %int %uint_1
+     %ptr_ty = OpTypePointer Output %arr
+     %zero = OpConstantNull %arr
+     %1 = OpVariable %ptr_ty Output %zero
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : array<i32, 1u> = array<i32, 1u>();
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(sample_mask)
+  x_1_1 : u32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(bitcast<u32>(x_1[0]));
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer) {
+  // FragDepth does not require conversion, because it's f32.
+  // The member of the return type is just the identifier corresponding
+  // to the module-scope private variable.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 BuiltIn FragDepth
+)" + CommonTypes() +
+                        R"(
+     %ptr_ty = OpTypePointer Output %float
+     %1 = OpVariable %ptr_ty Output %float_0
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : f32 = 0.0;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(frag_depth)
+  x_1_1 : f32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_BuiltIn_Position) {
+  // In Vulkan SPIR-V, Position is the first member of gl_PerVertex
+  const std::string assembly = PerVertexPreamble() + R"(
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> gl_Position : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  gl_Position : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(gl_Position);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPosition_BuiltIn_Position_Initializer) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1
+
+    OpDecorate %10 Block
+    OpMemberDecorate %10 0 BuiltIn Position
+    OpMemberDecorate %10 1 BuiltIn PointSize
+    OpMemberDecorate %10 2 BuiltIn ClipDistance
+    OpMemberDecorate %10 3 BuiltIn CullDistance
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %arr = OpTypeArray %float %uint_1
+    %10 = OpTypeStruct %v4float %float %arr %arr
+    %11 = OpTypePointer Output %10
+
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+
+    %init_pos = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+    %init_clip = OpConstantComposite %arr %float_6
+    %init_cull = OpConstantComposite %arr %float_7
+    %init_per_vertex = OpConstantComposite %10 %init_pos %float_5 %init_clip %init_cull
+
+    %1 = OpVariable %11 Output %init_per_vertex
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> gl_Position : vec4<f32> = vec4<f32>(1.0, 2.0, 3.0, 4.0);
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  gl_Position : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(gl_Position);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Input_FlattenArray_OneLevel) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+    OpDecorate %1 Location 4
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %uint_3 = OpConstant %uint 3
+    %arr = OpTypeArray %float %uint_3
+    %11 = OpTypePointer Input %arr
+
+    %1 = OpVariable %11 Input
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(4) x_1_param : f32, @location(5) x_1_param_1 : f32, @location(6) x_1_param_2 : f32) -> main_out {
+  x_1[0] = x_1_param;
+  x_1[1] = x_1_param_1;
+  x_1[2] = x_1_param_2;
+  main_1();
+  return main_out(x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Input_FlattenMatrix) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+    OpDecorate %1 Location 9
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %m2v4float = OpTypeMatrix %v4float 2
+    %uint = OpTypeInt 32 0
+
+    %11 = OpTypePointer Input %m2v4float
+
+    %1 = OpVariable %11 Input
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(9) x_1_param : vec4<f32>, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
+  x_1[0] = x_1_param;
+  x_1[1] = x_1_param_1;
+  main_1();
+  return main_out(x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Input_FlattenStruct_LocOnVariable) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+
+    OpName %strct "Communicators"
+    OpMemberName %strct 0 "alice"
+    OpMemberName %strct 1 "bob"
+
+    OpDecorate %1 Location 9
+    OpDecorate %2 BuiltIn Position
+
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %strct = OpTypeStruct %float %v4float
+
+    %11 = OpTypePointer Input %strct
+
+    %1 = OpVariable %11 Input
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(struct Communicators {
+  alice : f32;
+  bob : vec4<f32>;
+}
+
+var<private> x_1 : Communicators;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(9) x_1_param : f32, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
+  x_1.alice = x_1_param;
+  x_1.bob = x_1_param_1;
+  main_1();
+  return main_out(x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Input_FlattenNested) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+    OpDecorate %1 Location 7
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %m2v4float = OpTypeMatrix %v4float 2
+    %uint = OpTypeInt 32 0
+    %uint_2 = OpConstant %uint 2
+
+    %arr = OpTypeArray %m2v4float %uint_2
+
+    %11 = OpTypePointer Input %arr
+    %1 = OpVariable %11 Input
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<mat2x4<f32>, 2u>;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(7) x_1_param : vec4<f32>, @location(8) x_1_param_1 : vec4<f32>, @location(9) x_1_param_2 : vec4<f32>, @location(10) x_1_param_3 : vec4<f32>) -> main_out {
+  x_1[0][0] = x_1_param;
+  x_1[0][1] = x_1_param_1;
+  x_1[1][0] = x_1_param_2;
+  x_1[1][1] = x_1_param_3;
+  main_1();
+  return main_out(x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenArray_OneLevel) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+    OpDecorate %1 Location 4
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %uint_3 = OpConstant %uint 3
+    %arr = OpTypeArray %float %uint_3
+    %11 = OpTypePointer Output %arr
+
+    %1 = OpVariable %11 Output
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(4)
+  x_1_1 : f32;
+  @location(5)
+  x_1_2 : f32;
+  @location(6)
+  x_1_3 : f32;
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0], x_1[1], x_1[2], x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenMatrix) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+    OpDecorate %1 Location 9
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %m2v4float = OpTypeMatrix %v4float 2
+    %uint = OpTypeInt 32 0
+
+    %11 = OpTypePointer Output %m2v4float
+
+    %1 = OpVariable %11 Output
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(9)
+  x_1_1 : vec4<f32>;
+  @location(10)
+  x_1_2 : vec4<f32>;
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1[0], x_1[1], x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenStruct_LocOnVariable) {
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2
+
+    OpName %strct "Communicators"
+    OpMemberName %strct 0 "alice"
+    OpMemberName %strct 1 "bob"
+
+    OpDecorate %1 Location 9
+    OpDecorate %2 BuiltIn Position
+
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %strct = OpTypeStruct %float %v4float
+
+    %11 = OpTypePointer Output %strct
+
+    %1 = OpVariable %11 Output
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(struct Communicators {
+  alice : f32;
+  bob : vec4<f32>;
+}
+
+var<private> x_1 : Communicators;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(9)
+  x_1_1 : f32;
+  @location(10)
+  x_1_2 : vec4<f32>;
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1.alice, x_1.bob, x_2);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, FlattenStruct_LocOnMembers) {
+  // Block-decorated struct may have its members decorated with Location.
+  const std::string assembly = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1 %2 %3
+
+    OpName %strct "Communicators"
+    OpMemberName %strct 0 "alice"
+    OpMemberName %strct 1 "bob"
+
+    OpMemberDecorate %strct 0 Location 9
+    OpMemberDecorate %strct 1 Location 11
+    OpDecorate %strct Block
+    OpDecorate %2 BuiltIn Position
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %strct = OpTypeStruct %float %v4float
+
+    %11 = OpTypePointer Input %strct
+    %13 = OpTypePointer Output %strct
+
+    %1 = OpVariable %11 Input
+    %3 = OpVariable %13 Output
+
+    %12 = OpTypePointer Output %v4float
+    %2 = OpVariable %12 Output
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+
+  const auto got = test::ToString(p->program());
+  const std::string expected = R"(struct Communicators {
+  alice : f32;
+  bob : vec4<f32>;
+}
+
+var<private> x_1 : Communicators;
+
+var<private> x_3 : Communicators;
+
+var<private> x_2 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_2_1 : vec4<f32>;
+  @location(9)
+  x_3_1 : f32;
+  @location(11)
+  x_3_2 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(9) x_1_param : f32, @location(11) x_1_param_1 : vec4<f32>) -> main_out {
+  x_1.alice = x_1_param;
+  x_1.bob = x_1_param_1;
+  main_1();
+  return main_out(x_2, x_3.alice, x_3.bob);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Flat_Vertex_In) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+     OpDecorate %1 Flat
+     OpDecorate %2 Flat
+     OpDecorate %3 Flat
+     OpDecorate %4 Flat
+     OpDecorate %5 Flat
+     OpDecorate %6 Flat
+     OpDecorate %10 BuiltIn Position
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_uint = OpTypePointer Input %uint
+     %ptr_in_v2uint = OpTypePointer Input %v2uint
+     %ptr_in_int = OpTypePointer Input %int
+     %ptr_in_v2int = OpTypePointer Input %v2int
+     %ptr_in_float = OpTypePointer Input %float
+     %ptr_in_v2float = OpTypePointer Input %v2float
+     %1 = OpVariable %ptr_in_uint Input
+     %2 = OpVariable %ptr_in_v2uint Input
+     %3 = OpVariable %ptr_in_int Input
+     %4 = OpVariable %ptr_in_v2int Input
+     %5 = OpVariable %ptr_in_float Input
+     %6 = OpVariable %ptr_in_v2float Input
+
+     %ptr_out_v4float = OpTypePointer Output %v4float
+     %10 = OpVariable %ptr_out_v4float Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : u32;
+
+var<private> x_2 : vec2<u32>;
+
+var<private> x_3 : i32;
+
+var<private> x_4 : vec2<i32>;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : vec2<f32>;
+
+var<private> x_10 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  x_10_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) @interpolate(flat) x_5_param : f32, @location(6) @interpolate(flat) x_6_param : vec2<f32>) -> main_out {
+  x_1 = x_1_param;
+  x_2 = x_2_param;
+  x_3 = x_3_param;
+  x_4 = x_4_param;
+  x_5 = x_5_param;
+  x_6 = x_6_param;
+  main_1();
+  return main_out(x_10);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Flat_Vertex_Output) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+     OpDecorate %1 Flat
+     OpDecorate %2 Flat
+     OpDecorate %3 Flat
+     OpDecorate %4 Flat
+     OpDecorate %5 Flat
+     OpDecorate %6 Flat
+     OpDecorate %10 BuiltIn Position
+)" + CommonTypes() +
+                        R"(
+     %ptr_out_uint = OpTypePointer Output %uint
+     %ptr_out_v2uint = OpTypePointer Output %v2uint
+     %ptr_out_int = OpTypePointer Output %int
+     %ptr_out_v2int = OpTypePointer Output %v2int
+     %ptr_out_float = OpTypePointer Output %float
+     %ptr_out_v2float = OpTypePointer Output %v2float
+     %1 = OpVariable %ptr_out_uint Output
+     %2 = OpVariable %ptr_out_v2uint Output
+     %3 = OpVariable %ptr_out_int Output
+     %4 = OpVariable %ptr_out_v2int Output
+     %5 = OpVariable %ptr_out_float Output
+     %6 = OpVariable %ptr_out_v2float Output
+
+     %ptr_out_v4float = OpTypePointer Output %v4float
+     %10 = OpVariable %ptr_out_v4float Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : u32;
+
+var<private> x_2 : vec2<u32>;
+
+var<private> x_3 : i32;
+
+var<private> x_4 : vec2<i32>;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : vec2<f32>;
+
+var<private> x_10 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(1) @interpolate(flat)
+  x_1_1 : u32;
+  @location(2) @interpolate(flat)
+  x_2_1 : vec2<u32>;
+  @location(3) @interpolate(flat)
+  x_3_1 : i32;
+  @location(4) @interpolate(flat)
+  x_4_1 : vec2<i32>;
+  @location(5) @interpolate(flat)
+  x_5_1 : f32;
+  @location(6) @interpolate(flat)
+  x_6_1 : vec2<f32>;
+  @builtin(position)
+  x_10_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1, x_2, x_3, x_4, x_5, x_6, x_10);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1 %2
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 5
+     OpDecorate %1 Flat
+     OpDecorate %2 Flat
+)" + CommonTypes() +
+                        R"(
+     %arr = OpTypeArray %float %uint_2
+     %strct = OpTypeStruct %float %float
+     %ptr_in_arr = OpTypePointer Input %arr
+     %ptr_in_strct = OpTypePointer Input %strct
+     %1 = OpVariable %ptr_in_arr Input
+     %2 = OpVariable %ptr_in_strct Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(struct S {
+  field0 : f32;
+  field1 : f32;
+}
+
+var<private> x_1 : array<f32, 2u>;
+
+var<private> x_2 : S;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@location(1) @interpolate(flat) x_1_param : f32, @location(2) @interpolate(flat) x_1_param_1 : f32, @location(5) @interpolate(flat) x_2_param : f32, @location(6) @interpolate(flat) x_2_param_1 : f32) {
+  x_1[0] = x_1_param;
+  x_1[1] = x_1_param_1;
+  x_2.field0 = x_2_param;
+  x_2.field1 = x_2_param_1;
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Floating_Fragment_In) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+
+     ; %1 perspective center
+
+     OpDecorate %2 Centroid ; perspective centroid
+
+     OpDecorate %3 Sample ; perspective sample
+
+     OpDecorate %4 NoPerspective; linear center
+
+     OpDecorate %5 NoPerspective ; linear centroid
+     OpDecorate %5 Centroid
+
+     OpDecorate %6 NoPerspective ; linear sample
+     OpDecorate %6 Sample
+
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_float = OpTypePointer Input %float
+     %1 = OpVariable %ptr_in_float Input
+     %2 = OpVariable %ptr_in_float Input
+     %3 = OpVariable %ptr_in_float Input
+     %4 = OpVariable %ptr_in_float Input
+     %5 = OpVariable %ptr_in_float Input
+     %6 = OpVariable %ptr_in_float Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : f32;
+
+var<private> x_2 : f32;
+
+var<private> x_3 : f32;
+
+var<private> x_4 : f32;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : f32;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@location(1) x_1_param : f32, @location(2) @interpolate(perspective, centroid) x_2_param : f32, @location(3) @interpolate(perspective, sample) x_3_param : f32, @location(4) @interpolate(linear) x_4_param : f32, @location(5) @interpolate(linear, centroid) x_5_param : f32, @location(6) @interpolate(linear, sample) x_6_param : f32) {
+  x_1 = x_1_param;
+  x_2 = x_2_param;
+  x_3 = x_3_param;
+  x_4 = x_4_param;
+  x_5 = x_5_param;
+  x_6 = x_6_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 Location 1
+
+     ; member 0 perspective center
+
+     OpMemberDecorate %10 1 Centroid ; perspective centroid
+
+     OpMemberDecorate %10 2 Sample ; perspective sample
+
+     OpMemberDecorate %10 3 NoPerspective; linear center
+
+     OpMemberDecorate %10 4 NoPerspective ; linear centroid
+     OpMemberDecorate %10 4 Centroid
+
+     OpMemberDecorate %10 5 NoPerspective ; linear sample
+     OpMemberDecorate %10 5 Sample
+
+)" + CommonTypes() +
+                        R"(
+
+     %10 = OpTypeStruct %float %float %float %float %float %float
+     %ptr_in_strct = OpTypePointer Input %10
+     %1 = OpVariable %ptr_in_strct Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(struct S {
+  field0 : f32;
+  field1 : f32;
+  field2 : f32;
+  field3 : f32;
+  field4 : f32;
+  field5 : f32;
+}
+
+var<private> x_1 : S;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@location(1) x_1_param : f32, @location(2) @interpolate(perspective, centroid) x_1_param_1 : f32, @location(3) @interpolate(perspective, sample) x_1_param_2 : f32, @location(4) @interpolate(linear) x_1_param_3 : f32, @location(5) @interpolate(linear, centroid) x_1_param_4 : f32, @location(6) @interpolate(linear, sample) x_1_param_5 : f32) {
+  x_1.field0 = x_1_param;
+  x_1.field1 = x_1_param_1;
+  x_1.field2 = x_1_param_2;
+  x_1.field3 = x_1_param_3;
+  x_1.field4 = x_1_param_4;
+  x_1.field5 = x_1_param_5;
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Floating_Fragment_Out) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
+     OpExecutionMode %main OriginUpperLeft
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+
+     ; %1 perspective center
+
+     OpDecorate %2 Centroid ; perspective centroid
+
+     OpDecorate %3 Sample ; perspective sample
+
+     OpDecorate %4 NoPerspective; linear center
+
+     OpDecorate %5 NoPerspective ; linear centroid
+     OpDecorate %5 Centroid
+
+     OpDecorate %6 NoPerspective ; linear sample
+     OpDecorate %6 Sample
+
+)" + CommonTypes() +
+                        R"(
+     %ptr_out_float = OpTypePointer Output %float
+     %1 = OpVariable %ptr_out_float Output
+     %2 = OpVariable %ptr_out_float Output
+     %3 = OpVariable %ptr_out_float Output
+     %4 = OpVariable %ptr_out_float Output
+     %5 = OpVariable %ptr_out_float Output
+     %6 = OpVariable %ptr_out_float Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : f32;
+
+var<private> x_2 : f32;
+
+var<private> x_3 : f32;
+
+var<private> x_4 : f32;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : f32;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(1)
+  x_1_1 : f32;
+  @location(2) @interpolate(perspective, centroid)
+  x_2_1 : f32;
+  @location(3) @interpolate(perspective, sample)
+  x_3_1 : f32;
+  @location(4) @interpolate(linear)
+  x_4_1 : f32;
+  @location(5) @interpolate(linear, centroid)
+  x_5_1 : f32;
+  @location(6) @interpolate(linear, sample)
+  x_6_1 : f32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1, x_2, x_3, x_4, x_5, x_6);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out) {
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1
+     OpExecutionMode %main OriginUpperLeft
+
+     OpDecorate %1 Location 1
+
+     ; member 0 perspective center
+
+     OpMemberDecorate %10 1 Centroid ; perspective centroid
+
+     OpMemberDecorate %10 2 Sample ; perspective sample
+
+     OpMemberDecorate %10 3 NoPerspective; linear center
+
+     OpMemberDecorate %10 4 NoPerspective ; linear centroid
+     OpMemberDecorate %10 4 Centroid
+
+     OpMemberDecorate %10 5 NoPerspective ; linear sample
+     OpMemberDecorate %10 5 Sample
+
+)" + CommonTypes() +
+                        R"(
+
+     %10 = OpTypeStruct %float %float %float %float %float %float
+     %ptr_in_strct = OpTypePointer Output %10
+     %1 = OpVariable %ptr_in_strct Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(struct S {
+  field0 : f32;
+  field1 : f32;
+  field2 : f32;
+  field3 : f32;
+  field4 : f32;
+  field5 : f32;
+}
+
+var<private> x_1 : S;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(1)
+  x_1_1 : f32;
+  @location(2) @interpolate(perspective, centroid)
+  x_1_2 : f32;
+  @location(3) @interpolate(perspective, sample)
+  x_1_3 : f32;
+  @location(4) @interpolate(linear)
+  x_1_4 : f32;
+  @location(5) @interpolate(linear, centroid)
+  x_1_5 : f32;
+  @location(6) @interpolate(linear, sample)
+  x_1_6 : f32;
+}
+
+@stage(fragment)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1.field0, x_1.field1, x_1.field2, x_1.field3, x_1.field4, x_1.field5);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Default_Vertex_Output) {
+  // Integral types default to @interpolate(flat).
+  // Floating types default to @interpolate(perspective, center), which is the
+  // same as WGSL and therefore dropped.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Vertex %main "main" %1 %2 %3 %4 %5 %6 %10
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+     OpDecorate %10 BuiltIn Position
+)" + CommonTypes() +
+                        R"(
+     %ptr_out_uint = OpTypePointer Output %uint
+     %ptr_out_v2uint = OpTypePointer Output %v2uint
+     %ptr_out_int = OpTypePointer Output %int
+     %ptr_out_v2int = OpTypePointer Output %v2int
+     %ptr_out_float = OpTypePointer Output %float
+     %ptr_out_v2float = OpTypePointer Output %v2float
+     %1 = OpVariable %ptr_out_uint Output
+     %2 = OpVariable %ptr_out_v2uint Output
+     %3 = OpVariable %ptr_out_int Output
+     %4 = OpVariable %ptr_out_v2int Output
+     %5 = OpVariable %ptr_out_float Output
+     %6 = OpVariable %ptr_out_v2float Output
+
+     %ptr_out_v4float = OpTypePointer Output %v4float
+     %10 = OpVariable %ptr_out_v4float Output
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : u32;
+
+var<private> x_2 : vec2<u32>;
+
+var<private> x_3 : i32;
+
+var<private> x_4 : vec2<i32>;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : vec2<f32>;
+
+var<private> x_10 : vec4<f32>;
+
+fn main_1() {
+  return;
+}
+
+struct main_out {
+  @location(1) @interpolate(flat)
+  x_1_1 : u32;
+  @location(2) @interpolate(flat)
+  x_2_1 : vec2<u32>;
+  @location(3) @interpolate(flat)
+  x_3_1 : i32;
+  @location(4) @interpolate(flat)
+  x_4_1 : vec2<i32>;
+  @location(5)
+  x_5_1 : f32;
+  @location(6)
+  x_6_1 : vec2<f32>;
+  @builtin(position)
+  x_10_1 : vec4<f32>;
+}
+
+@stage(vertex)
+fn main() -> main_out {
+  main_1();
+  return main_out(x_1, x_2, x_3, x_4, x_5, x_6, x_10);
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       EntryPointWrapping_Interpolation_Default_Fragment_In) {
+  // Integral types default to @interpolate(flat).
+  // Floating types default to @interpolate(perspective, center), which is the
+  // same as WGSL and therefore dropped.
+  const auto assembly = CommonCapabilities() + R"(
+     OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6
+     OpDecorate %1 Location 1
+     OpDecorate %2 Location 2
+     OpDecorate %3 Location 3
+     OpDecorate %4 Location 4
+     OpDecorate %5 Location 5
+     OpDecorate %6 Location 6
+)" + CommonTypes() +
+                        R"(
+     %ptr_in_uint = OpTypePointer Input %uint
+     %ptr_in_v2uint = OpTypePointer Input %v2uint
+     %ptr_in_int = OpTypePointer Input %int
+     %ptr_in_v2int = OpTypePointer Input %v2int
+     %ptr_in_float = OpTypePointer Input %float
+     %ptr_in_v2float = OpTypePointer Input %v2float
+     %1 = OpVariable %ptr_in_uint Input
+     %2 = OpVariable %ptr_in_v2uint Input
+     %3 = OpVariable %ptr_in_int Input
+     %4 = OpVariable %ptr_in_v2int Input
+     %5 = OpVariable %ptr_in_float Input
+     %6 = OpVariable %ptr_in_v2float Input
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+
+  ASSERT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto got = test::ToString(p->program());
+  const std::string expected =
+      R"(var<private> x_1 : u32;
+
+var<private> x_2 : vec2<u32>;
+
+var<private> x_3 : i32;
+
+var<private> x_4 : vec2<i32>;
+
+var<private> x_5 : f32;
+
+var<private> x_6 : vec2<f32>;
+
+fn main_1() {
+  return;
+}
+
+@stage(fragment)
+fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) x_5_param : f32, @location(6) x_6_param : vec2<f32>) {
+  x_1 = x_1_param;
+  x_2 = x_2_param;
+  x_3 = x_3_param;
+  x_4 = x_4_param;
+  x_5 = x_5_param;
+  x_6 = x_6_param;
+  main_1();
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_named_types_test.cc b/src/tint/reader/spirv/parser_impl_named_types_test.cc
new file mode 100644
index 0000000..2dfd70f
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_named_types_test.cc
@@ -0,0 +1,159 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+TEST_F(SpvParserTest, NamedTypes_AnonStruct) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %s = OpTypeStruct %uint %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()), HasSubstr("struct S"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_NamedStruct) {
+  auto p = parser(test::Assemble(R"(
+    OpName %s "mystruct"
+    %uint = OpTypeInt 32 0
+    %s = OpTypeStruct %uint %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()), HasSubstr("struct mystruct"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_Dup_EmitBoth) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %s = OpTypeStruct %uint %uint
+    %s2 = OpTypeStruct %uint %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_THAT(test::ToString(p->program()), HasSubstr(R"(struct S {
+  field0 : u32;
+  field1 : u32;
+}
+
+struct S_1 {
+  field0 : u32;
+  field1 : u32;
+})"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+// TODO(dneto): Should we make an alias for an un-decoratrd array with
+// an OpName?
+
+TEST_F(SpvParserTest, NamedTypes_AnonRTArrayWithDecoration) {
+  // Runtime arrays are always in SSBO, and those are always laid out.
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %arr ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %arr = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()),
+              HasSubstr("RTArr = @stride(8) array<u32>;\n"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_AnonRTArray_Dup_EmitBoth) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %arr ArrayStride 8
+    OpDecorate %arr2 ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %arr = OpTypeRuntimeArray %uint
+    %arr2 = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()),
+              HasSubstr(R"(type RTArr = @stride(8) array<u32>;
+
+type RTArr_1 = @stride(8) array<u32>;
+)"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_NamedRTArray) {
+  auto p = parser(test::Assemble(R"(
+    OpName %arr "myrtarr"
+    OpDecorate %arr ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %arr = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()),
+              HasSubstr("myrtarr = @stride(8) array<u32>;\n"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_NamedArray) {
+  auto p = parser(test::Assemble(R"(
+    OpName %arr "myarr"
+    OpDecorate %arr ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %uint_5 = OpConstant %uint 5
+    %arr = OpTypeArray %uint %uint_5
+    %arr2 = OpTypeArray %uint %uint_5
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()),
+              HasSubstr("myarr = @stride(8) array<u32, 5u>;"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserTest, NamedTypes_AnonArray_Dup_EmitBoth) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %arr ArrayStride 8
+    OpDecorate %arr2 ArrayStride 8
+    %uint = OpTypeInt 32 0
+    %uint_5 = OpConstant %uint 5
+    %arr = OpTypeArray %uint %uint_5
+    %arr2 = OpTypeArray %uint %uint_5
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(test::ToString(p->program()),
+              HasSubstr(R"(type Arr = @stride(8) array<u32, 5u>;
+
+type Arr_1 = @stride(8) array<u32, 5u>;
+)"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+// TODO(dneto): Handle arrays sized by a spec constant.
+// Blocked by crbug.com/tint/32
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_test.cc b/src/tint/reader/spirv/parser_impl_test.cc
new file mode 100644
index 0000000..f50b73e
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_test.cc
@@ -0,0 +1,228 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+TEST_F(SpvParserTest, Impl_Uint32VecEmpty) {
+  std::vector<uint32_t> data;
+  auto p = parser(data);
+  EXPECT_FALSE(p->Parse());
+  // TODO(dneto): What message?
+}
+
+TEST_F(SpvParserTest, Impl_InvalidModuleFails) {
+  auto invalid_spv = test::Assemble("%ty = OpTypeInt 3 0");
+  auto p = parser(invalid_spv);
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(
+      p->error(),
+      HasSubstr("TypeInt cannot appear before the memory model instruction"));
+  EXPECT_THAT(p->error(), HasSubstr("OpTypeInt 3 0"));
+}
+
+TEST_F(SpvParserTest, Impl_GenericVulkanShader_SimpleMemoryModel) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, Impl_GenericVulkanShader_GLSL450MemoryModel) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, Impl_GenericVulkanShader_VulkanMemoryModel) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpCapability VulkanMemoryModelKHR
+  OpExtension "SPV_KHR_vulkan_memory_model"
+  OpMemoryModel Logical VulkanKHR
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, Impl_OpenCLKernel_Fails) {
+  auto spv = test::Assemble(R"(
+  OpCapability Kernel
+  OpCapability Addresses
+  OpMemoryModel Physical32 OpenCL
+  OpEntryPoint Kernel %main "main"
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_FALSE(p->Parse());
+  EXPECT_THAT(p->error(), HasSubstr("Capability Kernel is not allowed"));
+}
+
+TEST_F(SpvParserTest, Impl_Source_NoOpLine) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %5 = OpTypeInt 32 0
+  %60 = OpConstantNull %5
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  // Use instruction counting.
+  auto s5 = p->GetSourceForResultIdForTest(5);
+  EXPECT_EQ(7u, s5.range.begin.line);
+  EXPECT_EQ(0u, s5.range.begin.column);
+  auto s60 = p->GetSourceForResultIdForTest(60);
+  EXPECT_EQ(8u, s60.range.begin.line);
+  EXPECT_EQ(0u, s60.range.begin.column);
+  auto s1 = p->GetSourceForResultIdForTest(1);
+  EXPECT_EQ(10u, s1.range.begin.line);
+  EXPECT_EQ(0u, s1.range.begin.column);
+}
+
+TEST_F(SpvParserTest, Impl_Source_WithOpLine_WithOpNoLine) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %15 = OpString "myfile"
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  OpLine %15 42 53
+  %5 = OpTypeInt 32 0
+  %60 = OpConstantNull %5
+  OpNoLine
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  // Use the information from the OpLine that is still in scope.
+  auto s5 = p->GetSourceForResultIdForTest(5);
+  EXPECT_EQ(42u, s5.range.begin.line);
+  EXPECT_EQ(53u, s5.range.begin.column);
+  auto s60 = p->GetSourceForResultIdForTest(60);
+  EXPECT_EQ(42u, s60.range.begin.line);
+  EXPECT_EQ(53u, s60.range.begin.column);
+  // After OpNoLine, revert back to instruction counting.
+  auto s1 = p->GetSourceForResultIdForTest(1);
+  EXPECT_EQ(14u, s1.range.begin.line);
+  EXPECT_EQ(0u, s1.range.begin.column);
+}
+
+TEST_F(SpvParserTest, Impl_Source_InvalidId) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %15 = OpString "myfile"
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  auto s99 = p->GetSourceForResultIdForTest(99);
+  EXPECT_EQ(0u, s99.range.begin.line);
+  EXPECT_EQ(0u, s99.range.begin.column);
+}
+
+TEST_F(SpvParserTest, Impl_IsValidIdentifier) {
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier(""));  // empty
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier("_"));
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier("__"));
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("_x"));
+  EXPECT_FALSE(
+      ParserImpl::IsValidIdentifier("9"));  // leading digit, but ok later
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier(" "));    // leading space
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier("a "));   // trailing space
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier("a 1"));  // space in the middle
+  EXPECT_FALSE(ParserImpl::IsValidIdentifier("."));    // weird character
+
+  // a simple identifier
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("A"));
+  // each upper case letter
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+  // each lower case letter
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("abcdefghijklmnopqrstuvwxyz"));
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("a0123456789"));  // each digit
+  EXPECT_TRUE(ParserImpl::IsValidIdentifier("x_"));           // has underscore
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.cc b/src/tint/reader/spirv/parser_impl_test_helper.cc
new file mode 100644
index 0000000..c113048
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_test_helper.cc
@@ -0,0 +1,85 @@
+// 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/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/writer/wgsl/generator_impl.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace test {
+
+// Default to not dumping the SPIR-V assembly.
+bool ParserImplWrapperForTest::dump_successfully_converted_spirv_ = false;
+
+ParserImplWrapperForTest::ParserImplWrapperForTest(
+    const std::vector<uint32_t>& input)
+    : impl_(input) {}
+
+ParserImplWrapperForTest::~ParserImplWrapperForTest() {
+  if (dump_successfully_converted_spirv_ && !skip_dumping_spirv_ &&
+      !impl_.spv_binary().empty() && impl_.success()) {
+    std::string disassembly = Disassemble(impl_.spv_binary());
+    std::cout << "BEGIN ConvertedOk:\n"
+              << disassembly << "\nEND ConvertedOk" << std::endl;
+  }
+}
+
+std::string ToString(const Program& program) {
+  writer::wgsl::GeneratorImpl writer(&program);
+  if (!writer.Generate()) {
+    return "WGSL writer error: " + writer.error();
+  }
+  return writer.result();
+}
+
+std::string ToString(const Program& program, const ast::StatementList& stmts) {
+  writer::wgsl::GeneratorImpl writer(&program);
+  for (const auto* stmt : stmts) {
+    if (!writer.EmitStatement(stmt)) {
+      return "WGSL writer error: " + writer.error();
+    }
+  }
+  return writer.result();
+}
+
+std::string ToString(const Program& program, const ast::Node* node) {
+  writer::wgsl::GeneratorImpl writer(&program);
+  if (auto* expr = node->As<ast::Expression>()) {
+    std::stringstream out;
+    if (!writer.EmitExpression(out, expr)) {
+      return "WGSL writer error: " + writer.error();
+    }
+    return out.str();
+  } else if (auto* stmt = node->As<ast::Statement>()) {
+    if (!writer.EmitStatement(stmt)) {
+      return "WGSL writer error: " + writer.error();
+    }
+  } else if (auto* ty = node->As<ast::Type>()) {
+    std::stringstream out;
+    if (!writer.EmitType(out, ty)) {
+      return "WGSL writer error: " + writer.error();
+    }
+    return out.str();
+  } else {
+    return "<unhandled AST node type " + std::string(node->TypeInfo().name) +
+           ">";
+  }
+  return writer.result();
+}
+
+}  // namespace test
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.h b/src/tint/reader/spirv/parser_impl_test_helper.h
new file mode 100644
index 0000000..f9a6c3e
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_test_helper.h
@@ -0,0 +1,319 @@
+// Copyright 2020 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_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
+#define SRC_TINT_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#if TINT_BUILD_SPV_READER
+#include "source/opt/ir_context.h"
+#endif
+
+#include "gtest/gtest.h"
+#include "src/tint/demangler.h"
+#include "src/tint/reader/spirv/fail_stream.h"
+#include "src/tint/reader/spirv/function.h"
+#include "src/tint/reader/spirv/namer.h"
+#include "src/tint/reader/spirv/parser_impl.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+#include "src/tint/reader/spirv/usage.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace test {
+
+/// A test class that wraps ParseImpl
+class ParserImplWrapperForTest {
+ public:
+  /// Constructor
+  /// @param input the input data to parse
+  explicit ParserImplWrapperForTest(const std::vector<uint32_t>& input);
+  /// Dumps SPIR-V if the conversion succeeded, then destroys the wrapper.
+  ~ParserImplWrapperForTest();
+
+  /// Sets global state to force dumping of the assembly text of succesfully
+  /// SPIR-V.
+  static void DumpSuccessfullyConvertedSpirv() {
+    dump_successfully_converted_spirv_ = true;
+  }
+  /// Marks the test has having deliberately invalid SPIR-V
+  void DeliberatelyInvalidSpirv() { skip_dumping_spirv_ = true; }
+  /// Marks the test's SPIR-V as not being suitable for dumping, for a stated
+  /// reason.
+  void SkipDumpingPending(std::string) { skip_dumping_spirv_ = true; }
+
+  /// @returns a new function emitter for the given function ID.
+  /// Assumes ParserImpl::BuildInternalRepresentation has been run and
+  /// succeeded.
+  /// @param function_id the SPIR-V identifier of the function
+  FunctionEmitter function_emitter(uint32_t function_id) {
+    auto* spirv_function = impl_.ir_context()->GetFunction(function_id);
+    return FunctionEmitter(&impl_, *spirv_function);
+  }
+
+  /// Run the parser
+  /// @returns true if the parse was successful, false otherwise.
+  bool Parse() { return impl_.Parse(); }
+
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
+  Program program() { return impl_.program(); }
+
+  /// @returns the namer object
+  Namer& namer() { return impl_.namer(); }
+
+  /// @returns a reference to the internal builder, without building the
+  /// program. To be used only for testing.
+  ProgramBuilder& builder() { return impl_.builder(); }
+
+  /// @returns the accumulated error string
+  const std::string error() { return impl_.error(); }
+
+  /// @return true if failure has not yet occurred
+  bool success() { return impl_.success(); }
+
+  /// Logs failure, ands return a failure stream to accumulate diagnostic
+  /// messages. By convention, a failure should only be logged along with
+  /// a non-empty string diagnostic.
+  /// @returns the failure stream
+  FailStream& Fail() { return impl_.Fail(); }
+
+  /// @returns a borrowed pointer to the internal representation of the module.
+  /// This is null until BuildInternalModule has been called.
+  spvtools::opt::IRContext* ir_context() { return impl_.ir_context(); }
+
+  /// Builds the internal representation of the SPIR-V module.
+  /// Assumes the module is somewhat well-formed.  Normally you
+  /// would want to validate the SPIR-V module before attempting
+  /// to build this internal representation. Also computes a topological
+  /// ordering of the functions.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
+  bool BuildInternalModule() { return impl_.BuildInternalModule(); }
+
+  /// Builds an internal representation of the SPIR-V binary,
+  /// and parses the module, except functions, into a Tint AST module.
+  /// Diagnostics are emitted to the error stream.
+  /// @returns true if it was successful.
+  bool BuildAndParseInternalModuleExceptFunctions() {
+    return impl_.BuildAndParseInternalModuleExceptFunctions();
+  }
+
+  /// Builds an internal representation of the SPIR-V binary,
+  /// and parses it into a Tint AST module.  Diagnostics are emitted
+  /// to the error stream.
+  /// @returns true if it was successful.
+  bool BuildAndParseInternalModule() {
+    return impl_.BuildAndParseInternalModule();
+  }
+
+  /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
+  /// Also synthesizes struct field names.  Ensures uniqueness for names for
+  /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterUserAndStructMemberNames() {
+    return impl_.RegisterUserAndStructMemberNames();
+  }
+
+  /// Register Tint AST types for SPIR-V types, including type aliases as
+  /// needed.  This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterTypes() { return impl_.RegisterTypes(); }
+
+  /// Register sampler and texture usage for memory object declarations.
+  /// This must be called after we've registered line numbers for all
+  /// instructions. This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterHandleUsage() { return impl_.RegisterHandleUsage(); }
+
+  /// Emits module-scope variables.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool EmitModuleScopeVariables() { return impl_.EmitModuleScopeVariables(); }
+
+  /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450"
+  /// extended instruction set.
+  const std::unordered_set<uint32_t>& glsl_std_450_imports() const {
+    return impl_.glsl_std_450_imports();
+  }
+
+  /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup.
+  /// If the type is only used for builtins, then register that specially,
+  /// and return null.  If the type is a sampler, image, or sampled image, then
+  /// return the Void type, because those opaque types are handled in a
+  /// different way.
+  /// On failure, logs an error and returns null.  This should only be called
+  /// after the internal representation of the module has been built.
+  /// @param id the SPIR-V ID of a type.
+  /// @returns a Tint type, or nullptr
+  const Type* ConvertType(uint32_t id) { return impl_.ConvertType(id); }
+
+  /// Gets the list of decorations for a SPIR-V result ID.  Returns an empty
+  /// vector if the ID is not a result ID, or if no decorations target that ID.
+  /// The internal representation must have already been built.
+  /// @param id SPIR-V ID
+  /// @returns the list of decorations on the given ID
+  DecorationList GetDecorationsFor(uint32_t id) const {
+    return impl_.GetDecorationsFor(id);
+  }
+
+  /// Gets the list of decorations for the member of a struct.  Returns an empty
+  /// list if the `id` is not the ID of a struct, or if the member index is out
+  /// of range, or if the target member has no decorations.
+  /// The internal representation must have already been built.
+  /// @param id SPIR-V ID of a struct
+  /// @param member_index the member within the struct
+  /// @returns the list of decorations on the member
+  DecorationList GetDecorationsForMember(uint32_t id,
+                                         uint32_t member_index) const {
+    return impl_.GetDecorationsForMember(id, member_index);
+  }
+
+  /// Converts a SPIR-V struct member decoration into a number of AST
+  /// decorations. If the decoration is recognized but deliberately dropped,
+  /// then returns an empty list without a diagnostic. On failure, emits a
+  /// diagnostic and returns an empty list.
+  /// @param struct_type_id the ID of the struct type
+  /// @param member_index the index of the member
+  /// @param member_ty the type of the member
+  /// @param decoration an encoded SPIR-V Decoration
+  /// @returns the AST decorations
+  ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
+                                             uint32_t member_index,
+                                             const Type* member_ty,
+                                             const Decoration& decoration) {
+    return impl_.ConvertMemberDecoration(struct_type_id, member_index,
+                                         member_ty, decoration);
+  }
+
+  /// For a SPIR-V ID that might define a sampler, image, or sampled image
+  /// value, return the SPIR-V instruction that represents the memory object
+  /// declaration for the object.  If we encounter an OpSampledImage along the
+  /// way, follow the image operand when follow_image is true; otherwise follow
+  /// the sampler operand. Returns nullptr if we can't trace back to a memory
+  /// object declaration.  Emits an error and returns nullptr when the scan
+  /// fails due to a malformed module. This method can be used any time after
+  /// BuildInternalModule has been invoked.
+  /// @param id the SPIR-V ID of the sampler, image, or sampled image
+  /// @param follow_image indicates whether to follow the image operand of
+  /// OpSampledImage
+  /// @returns the memory object declaration for the handle, or nullptr
+  const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(
+      uint32_t id,
+      bool follow_image) {
+    return impl_.GetMemoryObjectDeclarationForHandle(id, follow_image);
+  }
+
+  /// @param entry_point the SPIR-V ID of an entry point.
+  /// @returns the entry point info for the given ID
+  const std::vector<EntryPointInfo>& GetEntryPointInfo(uint32_t entry_point) {
+    return impl_.GetEntryPointInfo(entry_point);
+  }
+
+  /// Returns the handle usage for a memory object declaration.
+  /// @param id SPIR-V ID of a sampler or image OpVariable or
+  /// OpFunctionParameter
+  /// @returns the handle usage, or an empty usage object.
+  Usage GetHandleUsage(uint32_t id) const { return impl_.GetHandleUsage(id); }
+
+  /// Returns the SPIR-V instruction with the given ID, or nullptr.
+  /// @param id the SPIR-V result ID
+  /// @returns the instruction, or nullptr on error
+  const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const {
+    return impl_.GetInstructionForTest(id);
+  }
+
+  /// @returns info about the gl_Position builtin variable.
+  const ParserImpl::BuiltInPositionInfo& GetBuiltInPositionInfo() {
+    return impl_.GetBuiltInPositionInfo();
+  }
+
+  /// Returns the source record for the SPIR-V instruction with the given
+  /// result ID.
+  /// @param id the SPIR-V result id.
+  /// @return the Source record, or a default one
+  Source GetSourceForResultIdForTest(uint32_t id) const {
+    return impl_.GetSourceForResultIdForTest(id);
+  }
+
+ private:
+  ParserImpl impl_;
+  /// When true, indicates the input SPIR-V module should not be emitted.
+  /// It's either deliberately invalid, or not supported for some pending
+  /// reason.
+  bool skip_dumping_spirv_ = false;
+  static bool dump_successfully_converted_spirv_;
+};
+
+// Sets global state to force dumping of the assembly text of succesfully
+// SPIR-V.
+inline void DumpSuccessfullyConvertedSpirv() {
+  ParserImplWrapperForTest::DumpSuccessfullyConvertedSpirv();
+}
+
+/// Returns the WGSL printed string of a program.
+/// @param program the Program
+/// @returns the WGSL printed string the program.
+std::string ToString(const Program& program);
+
+/// Returns the WGSL printed string of a statement list.
+/// @param program the Program
+/// @param stmts the statement list
+/// @returns the WGSL printed string of a statement list.
+std::string ToString(const Program& program, const ast::StatementList& stmts);
+
+/// Returns the WGSL printed string of an AST node.
+/// @param program the Program
+/// @param node the AST node
+/// @returns the WGSL printed string of the AST node.
+std::string ToString(const Program& program, const ast::Node* node);
+
+}  // namespace test
+
+/// SPIR-V Parser test class
+template <typename T>
+class SpvParserTestBase : public T {
+ public:
+  SpvParserTestBase() = default;
+  ~SpvParserTestBase() override = default;
+
+  /// Retrieves the parser from the helper
+  /// @param input the SPIR-V binary to parse
+  /// @returns a parser for the given binary
+  std::unique_ptr<test::ParserImplWrapperForTest> parser(
+      const std::vector<uint32_t>& input) {
+    auto parser = std::make_unique<test::ParserImplWrapperForTest>(input);
+
+    // Don't run the Resolver when building the program.
+    // We're not interested in type information with these tests.
+    parser->builder().SetResolveOnBuild(false);
+    return parser;
+  }
+};
+
+// Use this form when you don't need to template any further.
+using SpvParserTest = SpvParserTestBase<::testing::Test>;
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_
diff --git a/src/tint/reader/spirv/parser_impl_user_name_test.cc b/src/tint/reader/spirv/parser_impl_user_name_test.cc
new file mode 100644
index 0000000..a8d8219
--- /dev/null
+++ b/src/tint/reader/spirv/parser_impl_user_name_test.cc
@@ -0,0 +1,215 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+using SpvParserUserNameTest = SpvParserTest;
+
+TEST_F(SpvParserUserNameTest, UserName_RespectOpName) {
+  auto p = parser(test::Assemble(R"(
+     OpName %1 "the_void_type"
+     %1 = OpTypeVoid
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetName(1), Eq("the_void_type"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_IgnoreEmptyName) {
+  auto p = parser(test::Assemble(R"(
+     OpName %1 ""
+     %1 = OpTypeVoid
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_FALSE(p->namer().HasName(1));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_DistinguishDuplicateSuggestion) {
+  auto p = parser(test::Assemble(R"(
+     OpName %1 "vanilla"
+     OpName %2 "vanilla"
+     %1 = OpTypeVoid
+     %2 = OpTypeInt 32 0
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetName(1), Eq("vanilla"));
+  EXPECT_THAT(p->namer().GetName(2), Eq("vanilla_1"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_RespectOpMemberName) {
+  auto p = parser(test::Assemble(R"(
+     OpMemberName %3 0 "strawberry"
+     OpMemberName %3 1 "vanilla"
+     OpMemberName %3 2 "chocolate"
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("strawberry"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("vanilla"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("chocolate"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_IgnoreEmptyMemberName) {
+  auto p = parser(test::Assemble(R"(
+     OpMemberName %3 0 ""
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_SynthesizeMemberNames) {
+  auto p = parser(test::Assemble(R"(
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("field1"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("field2"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, UserName_MemberNamesMixUserAndSynthesized) {
+  auto p = parser(test::Assemble(R"(
+     OpMemberName %3 1 "vanilla"
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->namer().GetMemberName(3, 0), Eq("field0"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 1), Eq("vanilla"));
+  EXPECT_THAT(p->namer().GetMemberName(3, 2), Eq("field2"));
+
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, EntryPointNames_AlwaysTakePrecedence) {
+  const std::string assembly = R"(
+   OpCapability Shader
+   OpMemoryModel Logical Simple
+   OpEntryPoint Vertex %100 "main"
+   OpEntryPoint Fragment %100 "main_1"
+   OpExecutionMode %100 OriginUpperLeft
+
+   ; attempt to grab the "main_1" that would be the derived name
+   ; for the second entry point.
+   OpName %1 "main_1"
+
+   %void = OpTypeVoid
+   %voidfn = OpTypeFunction %void
+   %uint = OpTypeInt 32 0
+   %uint_0 = OpConstant %uint 0
+
+   %100 = OpFunction %void None %voidfn
+   %100_entry = OpLabel
+   %1 = OpCopyObject %uint %uint_0
+   OpReturn
+   OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  // The first entry point grabs the best name, "main"
+  EXPECT_THAT(p->namer().Name(100), Eq("main"));
+  // The OpName on %1 is overriden because the second entry point
+  // has grabbed "main_1" first.
+  EXPECT_THAT(p->namer().Name(1), Eq("main_1_1"));
+
+  const auto& ep_info = p->GetEntryPointInfo(100);
+  ASSERT_EQ(2u, ep_info.size());
+  EXPECT_EQ(ep_info[0].name, "main");
+  EXPECT_EQ(ep_info[1].name, "main_1");
+
+  // This test checks two entry point with the same implementation function.
+  // But for the shader stages supported by WGSL, the SPIR-V rules require
+  // conflicting execution modes be applied to them.
+  // I still want to test the name disambiguation behaviour, but the cases
+  // are rejected by SPIR-V validation. This is true at least for the current
+  // WGSL feature set.
+  p->DeliberatelyInvalidSpirv();
+}
+
+TEST_F(SpvParserUserNameTest, EntryPointNames_DistinctFromInnerNames) {
+  const std::string assembly = R"(
+   OpCapability Shader
+   OpMemoryModel Logical Simple
+   OpEntryPoint Vertex %100 "main"
+   OpEntryPoint Fragment %100 "main_1"
+   OpExecutionMode %100 OriginUpperLeft
+
+   ; attempt to grab the "main_1" that would be the derived name
+   ; for the second entry point.
+   OpName %1 "main_1"
+
+   %void = OpTypeVoid
+   %voidfn = OpTypeFunction %void
+   %uint = OpTypeInt 32 0
+   %uint_0 = OpConstant %uint 0
+
+   %100 = OpFunction %void None %voidfn
+   %100_entry = OpLabel
+   %1 = OpCopyObject %uint %uint_0
+   OpReturn
+   OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  // The first entry point grabs the best name, "main"
+  EXPECT_THAT(p->namer().Name(100), Eq("main"));
+  EXPECT_THAT(p->namer().Name(1), Eq("main_1_1"));
+
+  const auto ep_info = p->GetEntryPointInfo(100);
+  ASSERT_EQ(2u, ep_info.size());
+  EXPECT_EQ(ep_info[0].name, "main");
+  EXPECT_EQ(ep_info[0].inner_name, "main_2");
+  // The second entry point retains its name...
+  EXPECT_EQ(ep_info[1].name, "main_1");
+  // ...but will use the same implementation function.
+  EXPECT_EQ(ep_info[1].inner_name, "main_2");
+
+  // This test checks two entry point with the same implementation function.
+  // But for the shader stages supported by WGSL, the SPIR-V rules require
+  // conflicting execution modes be applied to them.
+  // I still want to test the name disambiguation behaviour, but the cases
+  // are rejected by SPIR-V validation. This is true at least for the current
+  // WGSL feature set.
+  p->DeliberatelyInvalidSpirv();
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_test.cc b/src/tint/reader/spirv/parser_test.cc
new file mode 100644
index 0000000..3ae802f
--- /dev/null
+++ b/src/tint/reader/spirv/parser_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 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/reader/spirv/parser.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ParserTest = testing::Test;
+
+TEST_F(ParserTest, DataEmpty) {
+  std::vector<uint32_t> data;
+  auto program = Parse(data);
+  auto errs = diag::Formatter().format(program.Diagnostics());
+  ASSERT_FALSE(program.IsValid()) << errs;
+  EXPECT_EQ(errs, "error: line:0: Invalid SPIR-V magic number.\n");
+}
+
+// TODO(dneto): uint32 vec, valid SPIR-V
+// TODO(dneto): uint32 vec, invalid SPIR-V
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_type.cc b/src/tint/reader/spirv/parser_type.cc
new file mode 100644
index 0000000..06d56c7
--- /dev/null
+++ b/src/tint/reader/spirv/parser_type.cc
@@ -0,0 +1,645 @@
+// 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 stateied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/reader/spirv/parser_type.h"
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Type);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Void);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Bool);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::U32);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::F32);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::I32);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Pointer);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Reference);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Vector);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Matrix);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Array);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Sampler);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Texture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::DepthTexture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::DepthMultisampledTexture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::MultisampledTexture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::SampledTexture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::StorageTexture);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Named);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Alias);
+TINT_INSTANTIATE_TYPEINFO(tint::reader::spirv::Struct);
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+namespace {
+struct PointerHasher {
+  size_t operator()(const Pointer& t) const {
+    return utils::Hash(t.type, t.storage_class);
+  }
+};
+
+struct ReferenceHasher {
+  size_t operator()(const Reference& t) const {
+    return utils::Hash(t.type, t.storage_class);
+  }
+};
+
+struct VectorHasher {
+  size_t operator()(const Vector& t) const {
+    return utils::Hash(t.type, t.size);
+  }
+};
+
+struct MatrixHasher {
+  size_t operator()(const Matrix& t) const {
+    return utils::Hash(t.type, t.columns, t.rows);
+  }
+};
+
+struct ArrayHasher {
+  size_t operator()(const Array& t) const {
+    return utils::Hash(t.type, t.size, t.stride);
+  }
+};
+
+struct MultisampledTextureHasher {
+  size_t operator()(const MultisampledTexture& t) const {
+    return utils::Hash(t.dims, t.type);
+  }
+};
+
+struct SampledTextureHasher {
+  size_t operator()(const SampledTexture& t) const {
+    return utils::Hash(t.dims, t.type);
+  }
+};
+
+struct StorageTextureHasher {
+  size_t operator()(const StorageTexture& t) const {
+    return utils::Hash(t.dims, t.format, t.access);
+  }
+};
+}  // namespace
+
+static bool operator==(const Pointer& a, const Pointer& b) {
+  return a.type == b.type && a.storage_class == b.storage_class;
+}
+
+static bool operator==(const Reference& a, const Reference& b) {
+  return a.type == b.type && a.storage_class == b.storage_class;
+}
+
+static bool operator==(const Vector& a, const Vector& b) {
+  return a.type == b.type && a.size == b.size;
+}
+
+static bool operator==(const Matrix& a, const Matrix& b) {
+  return a.type == b.type && a.columns == b.columns && a.rows == b.rows;
+}
+
+static bool operator==(const Array& a, const Array& b) {
+  return a.type == b.type && a.size == b.size && a.stride == b.stride;
+}
+
+static bool operator==(const MultisampledTexture& a,
+                       const MultisampledTexture& b) {
+  return a.dims == b.dims && a.type == b.type;
+}
+
+static bool operator==(const SampledTexture& a, const SampledTexture& b) {
+  return a.dims == b.dims && a.type == b.type;
+}
+
+static bool operator==(const StorageTexture& a, const StorageTexture& b) {
+  return a.dims == b.dims && a.format == b.format;
+}
+
+const ast::Type* Void::Build(ProgramBuilder& b) const {
+  return b.ty.void_();
+}
+
+const ast::Type* Bool::Build(ProgramBuilder& b) const {
+  return b.ty.bool_();
+}
+
+const ast::Type* U32::Build(ProgramBuilder& b) const {
+  return b.ty.u32();
+}
+
+const ast::Type* F32::Build(ProgramBuilder& b) const {
+  return b.ty.f32();
+}
+
+const ast::Type* I32::Build(ProgramBuilder& b) const {
+  return b.ty.i32();
+}
+
+Pointer::Pointer(const Type* t, ast::StorageClass s)
+    : type(t), storage_class(s) {}
+Pointer::Pointer(const Pointer&) = default;
+
+const ast::Type* Pointer::Build(ProgramBuilder& b) const {
+  return b.ty.pointer(type->Build(b), storage_class);
+}
+
+Reference::Reference(const Type* t, ast::StorageClass s)
+    : type(t), storage_class(s) {}
+Reference::Reference(const Reference&) = default;
+
+const ast::Type* Reference::Build(ProgramBuilder& b) const {
+  return type->Build(b);
+}
+
+Vector::Vector(const Type* t, uint32_t s) : type(t), size(s) {}
+Vector::Vector(const Vector&) = default;
+
+const ast::Type* Vector::Build(ProgramBuilder& b) const {
+  return b.ty.vec(type->Build(b), size);
+}
+
+Matrix::Matrix(const Type* t, uint32_t c, uint32_t r)
+    : type(t), columns(c), rows(r) {}
+Matrix::Matrix(const Matrix&) = default;
+
+const ast::Type* Matrix::Build(ProgramBuilder& b) const {
+  return b.ty.mat(type->Build(b), columns, rows);
+}
+
+Array::Array(const Type* t, uint32_t sz, uint32_t st)
+    : type(t), size(sz), stride(st) {}
+Array::Array(const Array&) = default;
+
+const ast::Type* Array::Build(ProgramBuilder& b) const {
+  if (size > 0) {
+    return b.ty.array(type->Build(b), size, stride);
+  } else {
+    return b.ty.array(type->Build(b), nullptr, stride);
+  }
+}
+
+Sampler::Sampler(ast::SamplerKind k) : kind(k) {}
+Sampler::Sampler(const Sampler&) = default;
+
+const ast::Type* Sampler::Build(ProgramBuilder& b) const {
+  return b.ty.sampler(kind);
+}
+
+Texture::Texture(ast::TextureDimension d) : dims(d) {}
+Texture::Texture(const Texture&) = default;
+
+DepthTexture::DepthTexture(ast::TextureDimension d) : Base(d) {}
+DepthTexture::DepthTexture(const DepthTexture&) = default;
+
+const ast::Type* DepthTexture::Build(ProgramBuilder& b) const {
+  return b.ty.depth_texture(dims);
+}
+
+DepthMultisampledTexture::DepthMultisampledTexture(ast::TextureDimension d)
+    : Base(d) {}
+DepthMultisampledTexture::DepthMultisampledTexture(
+    const DepthMultisampledTexture&) = default;
+
+const ast::Type* DepthMultisampledTexture::Build(ProgramBuilder& b) const {
+  return b.ty.depth_multisampled_texture(dims);
+}
+
+MultisampledTexture::MultisampledTexture(ast::TextureDimension d, const Type* t)
+    : Base(d), type(t) {}
+MultisampledTexture::MultisampledTexture(const MultisampledTexture&) = default;
+
+const ast::Type* MultisampledTexture::Build(ProgramBuilder& b) const {
+  return b.ty.multisampled_texture(dims, type->Build(b));
+}
+
+SampledTexture::SampledTexture(ast::TextureDimension d, const Type* t)
+    : Base(d), type(t) {}
+SampledTexture::SampledTexture(const SampledTexture&) = default;
+
+const ast::Type* SampledTexture::Build(ProgramBuilder& b) const {
+  return b.ty.sampled_texture(dims, type->Build(b));
+}
+
+StorageTexture::StorageTexture(ast::TextureDimension d,
+                               ast::TexelFormat f,
+                               ast::Access a)
+    : Base(d), format(f), access(a) {}
+StorageTexture::StorageTexture(const StorageTexture&) = default;
+
+const ast::Type* StorageTexture::Build(ProgramBuilder& b) const {
+  return b.ty.storage_texture(dims, format, access);
+}
+
+Named::Named(Symbol n) : name(n) {}
+Named::Named(const Named&) = default;
+Named::~Named() = default;
+
+Alias::Alias(Symbol n, const Type* ty) : Base(n), type(ty) {}
+Alias::Alias(const Alias&) = default;
+
+const ast::Type* Alias::Build(ProgramBuilder& b) const {
+  return b.ty.type_name(name);
+}
+
+Struct::Struct(Symbol n, TypeList m) : Base(n), members(std::move(m)) {}
+Struct::Struct(const Struct&) = default;
+Struct::~Struct() = default;
+
+const ast::Type* Struct::Build(ProgramBuilder& b) const {
+  return b.ty.type_name(name);
+}
+
+/// The PIMPL state of the Types object.
+struct TypeManager::State {
+  /// The allocator of types
+  BlockAllocator<Type> allocator_;
+  /// The lazily-created Void type
+  spirv::Void const* void_ = nullptr;
+  /// The lazily-created Bool type
+  spirv::Bool const* bool_ = nullptr;
+  /// The lazily-created U32 type
+  spirv::U32 const* u32_ = nullptr;
+  /// The lazily-created F32 type
+  spirv::F32 const* f32_ = nullptr;
+  /// The lazily-created I32 type
+  spirv::I32 const* i32_ = nullptr;
+  /// Map of Pointer to the returned Pointer type instance
+  std::unordered_map<spirv::Pointer, const spirv::Pointer*, PointerHasher>
+      pointers_;
+  /// Map of Reference to the returned Reference type instance
+  std::unordered_map<spirv::Reference, const spirv::Reference*, ReferenceHasher>
+      references_;
+  /// Map of Vector to the returned Vector type instance
+  std::unordered_map<spirv::Vector, const spirv::Vector*, VectorHasher>
+      vectors_;
+  /// Map of Matrix to the returned Matrix type instance
+  std::unordered_map<spirv::Matrix, const spirv::Matrix*, MatrixHasher>
+      matrices_;
+  /// Map of Array to the returned Array type instance
+  std::unordered_map<spirv::Array, const spirv::Array*, ArrayHasher> arrays_;
+  /// Map of type name to returned Alias instance
+  std::unordered_map<Symbol, const spirv::Alias*> aliases_;
+  /// Map of type name to returned Struct instance
+  std::unordered_map<Symbol, const spirv::Struct*> structs_;
+  /// Map of ast::SamplerKind to returned Sampler instance
+  std::unordered_map<ast::SamplerKind, const spirv::Sampler*> samplers_;
+  /// Map of ast::TextureDimension to returned DepthTexture instance
+  std::unordered_map<ast::TextureDimension, const spirv::DepthTexture*>
+      depth_textures_;
+  /// Map of ast::TextureDimension to returned DepthMultisampledTexture instance
+  std::unordered_map<ast::TextureDimension,
+                     const spirv::DepthMultisampledTexture*>
+      depth_multisampled_textures_;
+  /// Map of MultisampledTexture to the returned MultisampledTexture type
+  /// instance
+  std::unordered_map<spirv::MultisampledTexture,
+                     const spirv::MultisampledTexture*,
+                     MultisampledTextureHasher>
+      multisampled_textures_;
+  /// Map of SampledTexture to the returned SampledTexture type instance
+  std::unordered_map<spirv::SampledTexture,
+                     const spirv::SampledTexture*,
+                     SampledTextureHasher>
+      sampled_textures_;
+  /// Map of StorageTexture to the returned StorageTexture type instance
+  std::unordered_map<spirv::StorageTexture,
+                     const spirv::StorageTexture*,
+                     StorageTextureHasher>
+      storage_textures_;
+};
+
+const Type* Type::UnwrapPtr() const {
+  const Type* type = this;
+  while (auto* ptr = type->As<Pointer>()) {
+    type = ptr->type;
+  }
+  return type;
+}
+
+const Type* Type::UnwrapRef() const {
+  const Type* type = this;
+  while (auto* ptr = type->As<Reference>()) {
+    type = ptr->type;
+  }
+  return type;
+}
+
+const Type* Type::UnwrapAlias() const {
+  const Type* type = this;
+  while (auto* alias = type->As<Alias>()) {
+    type = alias->type;
+  }
+  return type;
+}
+
+const Type* Type::UnwrapAll() const {
+  auto* type = this;
+  while (true) {
+    if (auto* alias = type->As<Alias>()) {
+      type = alias->type;
+    } else if (auto* ptr = type->As<Pointer>()) {
+      type = ptr->type;
+    } else {
+      break;
+    }
+  }
+  return type;
+}
+
+bool Type::IsFloatScalar() const {
+  return Is<F32>();
+}
+
+bool Type::IsFloatScalarOrVector() const {
+  return IsFloatScalar() || IsFloatVector();
+}
+
+bool Type::IsFloatVector() const {
+  return Is([](const Vector* v) { return v->type->IsFloatScalar(); });
+}
+
+bool Type::IsIntegerScalar() const {
+  return IsAnyOf<U32, I32>();
+}
+
+bool Type::IsIntegerScalarOrVector() const {
+  return IsUnsignedScalarOrVector() || IsSignedScalarOrVector();
+}
+
+bool Type::IsScalar() const {
+  return IsAnyOf<F32, U32, I32, Bool>();
+}
+
+bool Type::IsSignedIntegerVector() const {
+  return Is([](const Vector* v) { return v->type->Is<I32>(); });
+}
+
+bool Type::IsSignedScalarOrVector() const {
+  return Is<I32>() || IsSignedIntegerVector();
+}
+
+bool Type::IsUnsignedIntegerVector() const {
+  return Is([](const Vector* v) { return v->type->Is<U32>(); });
+}
+
+bool Type::IsUnsignedScalarOrVector() const {
+  return Is<U32>() || IsUnsignedIntegerVector();
+}
+
+TypeManager::TypeManager() {
+  state = std::make_unique<State>();
+}
+
+TypeManager::~TypeManager() = default;
+
+const spirv::Void* TypeManager::Void() {
+  if (!state->void_) {
+    state->void_ = state->allocator_.Create<spirv::Void>();
+  }
+  return state->void_;
+}
+
+const spirv::Bool* TypeManager::Bool() {
+  if (!state->bool_) {
+    state->bool_ = state->allocator_.Create<spirv::Bool>();
+  }
+  return state->bool_;
+}
+
+const spirv::U32* TypeManager::U32() {
+  if (!state->u32_) {
+    state->u32_ = state->allocator_.Create<spirv::U32>();
+  }
+  return state->u32_;
+}
+
+const spirv::F32* TypeManager::F32() {
+  if (!state->f32_) {
+    state->f32_ = state->allocator_.Create<spirv::F32>();
+  }
+  return state->f32_;
+}
+
+const spirv::I32* TypeManager::I32() {
+  if (!state->i32_) {
+    state->i32_ = state->allocator_.Create<spirv::I32>();
+  }
+  return state->i32_;
+}
+
+const spirv::Pointer* TypeManager::Pointer(const Type* el,
+                                           ast::StorageClass sc) {
+  return utils::GetOrCreate(state->pointers_, spirv::Pointer(el, sc), [&] {
+    return state->allocator_.Create<spirv::Pointer>(el, sc);
+  });
+}
+
+const spirv::Reference* TypeManager::Reference(const Type* el,
+                                               ast::StorageClass sc) {
+  return utils::GetOrCreate(state->references_, spirv::Reference(el, sc), [&] {
+    return state->allocator_.Create<spirv::Reference>(el, sc);
+  });
+}
+
+const spirv::Vector* TypeManager::Vector(const Type* el, uint32_t size) {
+  return utils::GetOrCreate(state->vectors_, spirv::Vector(el, size), [&] {
+    return state->allocator_.Create<spirv::Vector>(el, size);
+  });
+}
+
+const spirv::Matrix* TypeManager::Matrix(const Type* el,
+                                         uint32_t columns,
+                                         uint32_t rows) {
+  return utils::GetOrCreate(
+      state->matrices_, spirv::Matrix(el, columns, rows), [&] {
+        return state->allocator_.Create<spirv::Matrix>(el, columns, rows);
+      });
+}
+
+const spirv::Array* TypeManager::Array(const Type* el,
+                                       uint32_t size,
+                                       uint32_t stride) {
+  return utils::GetOrCreate(
+      state->arrays_, spirv::Array(el, size, stride),
+      [&] { return state->allocator_.Create<spirv::Array>(el, size, stride); });
+}
+
+const spirv::Alias* TypeManager::Alias(Symbol name, const Type* ty) {
+  return utils::GetOrCreate(state->aliases_, name, [&] {
+    return state->allocator_.Create<spirv::Alias>(name, ty);
+  });
+}
+
+const spirv::Struct* TypeManager::Struct(Symbol name, TypeList members) {
+  return utils::GetOrCreate(state->structs_, name, [&] {
+    return state->allocator_.Create<spirv::Struct>(name, std::move(members));
+  });
+}
+
+const spirv::Sampler* TypeManager::Sampler(ast::SamplerKind kind) {
+  return utils::GetOrCreate(state->samplers_, kind, [&] {
+    return state->allocator_.Create<spirv::Sampler>(kind);
+  });
+}
+
+const spirv::DepthTexture* TypeManager::DepthTexture(
+    ast::TextureDimension dims) {
+  return utils::GetOrCreate(state->depth_textures_, dims, [&] {
+    return state->allocator_.Create<spirv::DepthTexture>(dims);
+  });
+}
+
+const spirv::DepthMultisampledTexture* TypeManager::DepthMultisampledTexture(
+    ast::TextureDimension dims) {
+  return utils::GetOrCreate(state->depth_multisampled_textures_, dims, [&] {
+    return state->allocator_.Create<spirv::DepthMultisampledTexture>(dims);
+  });
+}
+
+const spirv::MultisampledTexture* TypeManager::MultisampledTexture(
+    ast::TextureDimension dims,
+    const Type* ty) {
+  return utils::GetOrCreate(
+      state->multisampled_textures_, spirv::MultisampledTexture(dims, ty), [&] {
+        return state->allocator_.Create<spirv::MultisampledTexture>(dims, ty);
+      });
+}
+
+const spirv::SampledTexture* TypeManager::SampledTexture(
+    ast::TextureDimension dims,
+    const Type* ty) {
+  return utils::GetOrCreate(
+      state->sampled_textures_, spirv::SampledTexture(dims, ty), [&] {
+        return state->allocator_.Create<spirv::SampledTexture>(dims, ty);
+      });
+}
+
+const spirv::StorageTexture* TypeManager::StorageTexture(
+    ast::TextureDimension dims,
+    ast::TexelFormat fmt,
+    ast::Access access) {
+  return utils::GetOrCreate(
+      state->storage_textures_, spirv::StorageTexture(dims, fmt, access), [&] {
+        return state->allocator_.Create<spirv::StorageTexture>(dims, fmt,
+                                                               access);
+      });
+}
+
+// Debug String() methods for Type classes. Only enabled in debug builds.
+#ifndef NDEBUG
+std::string Void::String() const {
+  return "void";
+}
+
+std::string Bool::String() const {
+  return "bool";
+}
+
+std::string U32::String() const {
+  return "u32";
+}
+
+std::string F32::String() const {
+  return "f32";
+}
+
+std::string I32::String() const {
+  return "i32";
+}
+
+std::string Pointer::String() const {
+  std::stringstream ss;
+  ss << "ptr<" << std::string(ast::ToString(storage_class)) << ", "
+     << type->String() + ">";
+  return ss.str();
+}
+
+std::string Reference::String() const {
+  std::stringstream ss;
+  ss << "ref<" + std::string(ast::ToString(storage_class)) << ", "
+     << type->String() << ">";
+  return ss.str();
+}
+
+std::string Vector::String() const {
+  std::stringstream ss;
+  ss << "vec" << size << "<" << type->String() << ">";
+  return ss.str();
+}
+
+std::string Matrix::String() const {
+  std::stringstream ss;
+  ss << "mat" << columns << "x" << rows << "<" << type->String() << ">";
+  return ss.str();
+}
+
+std::string Array::String() const {
+  std::stringstream ss;
+  ss << "array<" << type->String() << ", " << size << ", " << stride << ">";
+  return ss.str();
+}
+
+std::string Sampler::String() const {
+  switch (kind) {
+    case ast::SamplerKind::kSampler:
+      return "sampler";
+    case ast::SamplerKind::kComparisonSampler:
+      return "sampler_comparison";
+  }
+  return "<unknown sampler>";
+}
+
+std::string DepthTexture::String() const {
+  std::stringstream ss;
+  ss << "depth_" << dims;
+  return ss.str();
+}
+
+std::string DepthMultisampledTexture::String() const {
+  std::stringstream ss;
+  ss << "depth_multisampled_" << dims;
+  return ss.str();
+}
+
+std::string MultisampledTexture::String() const {
+  std::stringstream ss;
+  ss << "texture_multisampled_" << dims << "<" << type << ">";
+  return ss.str();
+}
+
+std::string SampledTexture::String() const {
+  std::stringstream ss;
+  ss << "texture_" << dims << "<" << type << ">";
+  return ss.str();
+}
+
+std::string StorageTexture::String() const {
+  std::stringstream ss;
+  ss << "texture_storage_" << dims << "<" << format << ", " << access << ">";
+  return ss.str();
+}
+
+std::string Named::String() const {
+  return name.to_str();
+}
+#endif  // NDEBUG
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/parser_type.h b/src/tint/reader/spirv/parser_type.h
new file mode 100644
index 0000000..59e6444
--- /dev/null
+++ b/src/tint/reader/spirv/parser_type.h
@@ -0,0 +1,610 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_READER_SPIRV_PARSER_TYPE_H_
+#define SRC_TINT_READER_SPIRV_PARSER_TYPE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/texture.h"
+#include "src/tint/block_allocator.h"
+#include "src/tint/castable.h"
+
+// Forward declarations
+namespace tint {
+class ProgramBuilder;
+namespace ast {
+class Type;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// Type is the base class for all types
+class Type : public Castable<Type> {
+ public:
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  virtual const ast::Type* Build(ProgramBuilder& b) const = 0;
+
+  /// @returns the inner most store type if this is a pointer, `this` otherwise
+  const Type* UnwrapPtr() const;
+
+  /// @returns the inner most store type if this is a reference, `this`
+  /// otherwise
+  const Type* UnwrapRef() const;
+
+  /// @returns the inner most aliased type if this is an alias, `this` otherwise
+  const Type* UnwrapAlias() const;
+
+  /// @returns the type with all aliasing, access control and pointers removed
+  const Type* UnwrapAll() const;
+
+  /// @returns true if this type is a float scalar
+  bool IsFloatScalar() const;
+  /// @returns true if this type is a float scalar or vector
+  bool IsFloatScalarOrVector() const;
+  /// @returns true if this type is a float vector
+  bool IsFloatVector() const;
+  /// @returns true if this type is an integer scalar
+  bool IsIntegerScalar() const;
+  /// @returns true if this type is an integer scalar or vector
+  bool IsIntegerScalarOrVector() const;
+  /// @returns true if this type is a scalar
+  bool IsScalar() const;
+  /// @returns true if this type is a signed integer vector
+  bool IsSignedIntegerVector() const;
+  /// @returns true if this type is a signed scalar or vector
+  bool IsSignedScalarOrVector() const;
+  /// @returns true if this type is an unsigned integer vector
+  bool IsUnsignedIntegerVector() const;
+  /// @returns true if this type is an unsigned scalar or vector
+  bool IsUnsignedScalarOrVector() const;
+
+#ifdef NDEBUG
+  /// @returns "<no-type-info>", for debug purposes only
+  std::string String() const { return "<no-type-info>"; }
+#else
+  /// @returns a string representation of the type, for debug purposes only
+  virtual std::string String() const = 0;
+#endif  // NDEBUG
+};
+
+using TypeList = std::vector<const Type*>;
+
+/// `void` type
+struct Void : public Castable<Void, Type> {
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `bool` type
+struct Bool : public Castable<Bool, Type> {
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `u32` type
+struct U32 : public Castable<U32, Type> {
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `f32` type
+struct F32 : public Castable<F32, Type> {
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `i32` type
+struct I32 : public Castable<I32, Type> {
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `ptr<SC, T>` type
+struct Pointer : public Castable<Pointer, Type> {
+  /// Constructor
+  /// @param ty the store type
+  /// @param sc the pointer storage class
+  Pointer(const Type* ty, ast::StorageClass sc);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Pointer(const Pointer& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the store type
+  Type const* const type;
+  /// the pointer storage class
+  ast::StorageClass const storage_class;
+};
+
+/// `ref<SC, T>` type
+/// Note this has no AST representation, but is used for type tracking in the
+/// reader.
+struct Reference : public Castable<Reference, Type> {
+  /// Constructor
+  /// @param ty the referenced type
+  /// @param sc the reference storage class
+  Reference(const Type* ty, ast::StorageClass sc);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Reference(const Reference& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the store type
+  Type const* const type;
+  /// the pointer storage class
+  ast::StorageClass const storage_class;
+};
+
+/// `vecN<T>` type
+struct Vector : public Castable<Vector, Type> {
+  /// Constructor
+  /// @param ty the element type
+  /// @param sz the number of elements in the vector
+  Vector(const Type* ty, uint32_t sz);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Vector(const Vector& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the element type
+  Type const* const type;
+  /// the number of elements in the vector
+  const uint32_t size;
+};
+
+/// `matNxM<T>` type
+struct Matrix : public Castable<Matrix, Type> {
+  /// Constructor
+  /// @param ty the matrix element type
+  /// @param c the number of columns in the matrix
+  /// @param r the number of rows in the matrix
+  Matrix(const Type* ty, uint32_t c, uint32_t r);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Matrix(const Matrix& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the matrix element type
+  Type const* const type;
+  /// the number of columns in the matrix
+  const uint32_t columns;
+  /// the number of rows in the matrix
+  const uint32_t rows;
+};
+
+/// `array<T, N>` type
+struct Array : public Castable<Array, Type> {
+  /// Constructor
+  /// @param el the element type
+  /// @param sz the number of elements in the array. 0 represents runtime-sized
+  /// array.
+  /// @param st the byte stride of the array. 0 means use implicit stride.
+  Array(const Type* el, uint32_t sz, uint32_t st);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Array(const Array& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the element type
+  Type const* const type;
+  /// the number of elements in the array. 0 represents runtime-sized array.
+  const uint32_t size;
+  /// the byte stride of the array
+  const uint32_t stride;
+};
+
+/// `sampler` type
+struct Sampler : public Castable<Sampler, Type> {
+  /// Constructor
+  /// @param k the sampler kind
+  explicit Sampler(ast::SamplerKind k);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Sampler(const Sampler& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the sampler kind
+  ast::SamplerKind const kind;
+};
+
+/// Base class for texture types
+struct Texture : public Castable<Texture, Type> {
+  /// Constructor
+  /// @param d the texture dimensions
+  explicit Texture(ast::TextureDimension d);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Texture(const Texture& other);
+
+  /// the texture dimensions
+  ast::TextureDimension const dims;
+};
+
+/// `texture_depth_D` type
+struct DepthTexture : public Castable<DepthTexture, Texture> {
+  /// Constructor
+  /// @param d the texture dimensions
+  explicit DepthTexture(ast::TextureDimension d);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  DepthTexture(const DepthTexture& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `texture_depth_multisampled_D` type
+struct DepthMultisampledTexture
+    : public Castable<DepthMultisampledTexture, Texture> {
+  /// Constructor
+  /// @param d the texture dimensions
+  explicit DepthMultisampledTexture(ast::TextureDimension d);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  DepthMultisampledTexture(const DepthMultisampledTexture& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+};
+
+/// `texture_multisampled_D<T>` type
+struct MultisampledTexture : public Castable<MultisampledTexture, Texture> {
+  /// Constructor
+  /// @param d the texture dimensions
+  /// @param t the multisampled texture type
+  MultisampledTexture(ast::TextureDimension d, const Type* t);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  MultisampledTexture(const MultisampledTexture& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the multisampled texture type
+  Type const* const type;
+};
+
+/// `texture_D<T>` type
+struct SampledTexture : public Castable<SampledTexture, Texture> {
+  /// Constructor
+  /// @param d the texture dimensions
+  /// @param t the sampled texture type
+  SampledTexture(ast::TextureDimension d, const Type* t);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  SampledTexture(const SampledTexture& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the sampled texture type
+  Type const* const type;
+};
+
+/// `texture_storage_D<F>` type
+struct StorageTexture : public Castable<StorageTexture, Texture> {
+  /// Constructor
+  /// @param d the texture dimensions
+  /// @param f the storage image format
+  /// @param a the access control
+  StorageTexture(ast::TextureDimension d, ast::TexelFormat f, ast::Access a);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  StorageTexture(const StorageTexture& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the storage image format
+  ast::TexelFormat const format;
+
+  /// the access control
+  ast::Access const access;
+};
+
+/// Base class for named types
+struct Named : public Castable<Named, Type> {
+  /// Constructor
+  /// @param n the type name
+  explicit Named(Symbol n);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Named(const Named& other);
+
+  /// Destructor
+  ~Named() override;
+
+#ifndef NDEBUG
+  /// @returns a string representation of the type, for debug purposes only
+  std::string String() const override;
+#endif  // NDEBUG
+
+  /// the type name
+  const Symbol name;
+};
+
+/// `type T = N` type
+struct Alias : public Castable<Alias, Named> {
+  /// Constructor
+  /// @param n the alias name
+  /// @param t the aliased type
+  Alias(Symbol n, const Type* t);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Alias(const Alias& other);
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+  /// the aliased type
+  Type const* const type;
+};
+
+/// `struct N { ... };` type
+struct Struct : public Castable<Struct, Named> {
+  /// Constructor
+  /// @param n the struct name
+  /// @param m the member types
+  Struct(Symbol n, TypeList m);
+
+  /// Copy constructor
+  /// @param other the other type to copy
+  Struct(const Struct& other);
+
+  /// Destructor
+  ~Struct() override;
+
+  /// @param b the ProgramBuilder used to construct the AST types
+  /// @returns the constructed ast::Type node for the given type
+  const ast::Type* Build(ProgramBuilder& b) const override;
+
+  /// the member types
+  const TypeList members;
+};
+
+/// A manager of types
+class TypeManager {
+ public:
+  /// Constructor
+  TypeManager();
+
+  /// Destructor
+  ~TypeManager();
+
+  /// @return a Void type. Repeated calls will return the same pointer.
+  const spirv::Void* Void();
+  /// @return a Bool type. Repeated calls will return the same pointer.
+  const spirv::Bool* Bool();
+  /// @return a U32 type. Repeated calls will return the same pointer.
+  const spirv::U32* U32();
+  /// @return a F32 type. Repeated calls will return the same pointer.
+  const spirv::F32* F32();
+  /// @return a I32 type. Repeated calls will return the same pointer.
+  const spirv::I32* I32();
+  /// @param ty the store type
+  /// @param sc the pointer storage class
+  /// @return a Pointer type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Pointer* Pointer(const Type* ty, ast::StorageClass sc);
+  /// @param ty the referenced type
+  /// @param sc the reference storage class
+  /// @return a Reference type. Repeated calls with the same arguments will
+  /// return the same pointer.
+  const spirv::Reference* Reference(const Type* ty, ast::StorageClass sc);
+  /// @param ty the element type
+  /// @param sz the number of elements in the vector
+  /// @return a Vector type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Vector* Vector(const Type* ty, uint32_t sz);
+  /// @param ty the matrix element type
+  /// @param c the number of columns in the matrix
+  /// @param r the number of rows in the matrix
+  /// @return a Matrix type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Matrix* Matrix(const Type* ty, uint32_t c, uint32_t r);
+  /// @param el the element type
+  /// @param sz the number of elements in the array. 0 represents runtime-sized
+  /// array.
+  /// @param st the byte stride of the array
+  /// @return a Array type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Array* Array(const Type* el, uint32_t sz, uint32_t st);
+  /// @param n the alias name
+  /// @param t the aliased type
+  /// @return a Alias type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Alias* Alias(Symbol n, const Type* t);
+  /// @param n the struct name
+  /// @param m the member types
+  /// @return a Struct type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Struct* Struct(Symbol n, TypeList m);
+  /// @param k the sampler kind
+  /// @return a Sampler type. Repeated calls with the same arguments will return
+  /// the same pointer.
+  const spirv::Sampler* Sampler(ast::SamplerKind k);
+  /// @param d the texture dimensions
+  /// @return a DepthTexture type. Repeated calls with the same arguments will
+  /// return the same pointer.
+  const spirv::DepthTexture* DepthTexture(ast::TextureDimension d);
+  /// @param d the texture dimensions
+  /// @return a DepthMultisampledTexture type. Repeated calls with the same
+  /// arguments will return the same pointer.
+  const spirv::DepthMultisampledTexture* DepthMultisampledTexture(
+      ast::TextureDimension d);
+  /// @param d the texture dimensions
+  /// @param t the multisampled texture type
+  /// @return a MultisampledTexture type. Repeated calls with the same arguments
+  /// will return the same pointer.
+  const spirv::MultisampledTexture* MultisampledTexture(ast::TextureDimension d,
+                                                        const Type* t);
+  /// @param d the texture dimensions
+  /// @param t the sampled texture type
+  /// @return a SampledTexture type. Repeated calls with the same arguments will
+  /// return the same pointer.
+  const spirv::SampledTexture* SampledTexture(ast::TextureDimension d,
+                                              const Type* t);
+  /// @param d the texture dimensions
+  /// @param f the storage image format
+  /// @param a the access control
+  /// @return a StorageTexture type. Repeated calls with the same arguments will
+  /// return the same pointer.
+  const spirv::StorageTexture* StorageTexture(ast::TextureDimension d,
+                                              ast::TexelFormat f,
+                                              ast::Access a);
+
+ private:
+  struct State;
+  std::unique_ptr<State> state;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_PARSER_TYPE_H_
diff --git a/src/tint/reader/spirv/parser_type_test.cc b/src/tint/reader/spirv/parser_type_test.cc
new file mode 100644
index 0000000..1e90264
--- /dev/null
+++ b/src/tint/reader/spirv/parser_type_test.cc
@@ -0,0 +1,102 @@
+// Copyright 2020 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 "gtest/gtest.h"
+
+#include "src/tint/reader/spirv/parser_type.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+TEST(SpvParserTypeTest, SameArgumentsGivesSamePointer) {
+  Symbol sym(Symbol(1, {}));
+
+  TypeManager ty;
+  EXPECT_EQ(ty.Void(), ty.Void());
+  EXPECT_EQ(ty.Bool(), ty.Bool());
+  EXPECT_EQ(ty.U32(), ty.U32());
+  EXPECT_EQ(ty.F32(), ty.F32());
+  EXPECT_EQ(ty.I32(), ty.I32());
+  EXPECT_EQ(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
+            ty.Pointer(ty.I32(), ast::StorageClass::kNone));
+  EXPECT_EQ(ty.Vector(ty.I32(), 3), ty.Vector(ty.I32(), 3));
+  EXPECT_EQ(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 3, 2));
+  EXPECT_EQ(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 3, 2));
+  EXPECT_EQ(ty.Alias(sym, ty.I32()), ty.Alias(sym, ty.I32()));
+  EXPECT_EQ(ty.Struct(sym, {ty.I32()}), ty.Struct(sym, {ty.I32()}));
+  EXPECT_EQ(ty.Sampler(ast::SamplerKind::kSampler),
+            ty.Sampler(ast::SamplerKind::kSampler));
+  EXPECT_EQ(ty.DepthTexture(ast::TextureDimension::k2d),
+            ty.DepthTexture(ast::TextureDimension::k2d));
+  EXPECT_EQ(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()));
+  EXPECT_EQ(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()));
+  EXPECT_EQ(ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
+            ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead));
+}
+
+TEST(SpvParserTypeTest, DifferentArgumentsGivesDifferentPointer) {
+  Symbol sym_a(Symbol(1, {}));
+  Symbol sym_b(Symbol(2, {}));
+
+  TypeManager ty;
+  EXPECT_NE(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
+            ty.Pointer(ty.U32(), ast::StorageClass::kNone));
+  EXPECT_NE(ty.Pointer(ty.I32(), ast::StorageClass::kNone),
+            ty.Pointer(ty.I32(), ast::StorageClass::kInput));
+  EXPECT_NE(ty.Vector(ty.I32(), 3), ty.Vector(ty.U32(), 3));
+  EXPECT_NE(ty.Vector(ty.I32(), 3), ty.Vector(ty.I32(), 2));
+  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.U32(), 3, 2));
+  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 2, 2));
+  EXPECT_NE(ty.Matrix(ty.I32(), 3, 2), ty.Matrix(ty.I32(), 3, 3));
+  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.U32(), 3, 2));
+  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 2, 2));
+  EXPECT_NE(ty.Array(ty.I32(), 3, 2), ty.Array(ty.I32(), 3, 3));
+  EXPECT_NE(ty.Alias(sym_a, ty.I32()), ty.Alias(sym_b, ty.I32()));
+  EXPECT_NE(ty.Struct(sym_a, {ty.I32()}), ty.Struct(sym_b, {ty.I32()}));
+  EXPECT_NE(ty.Sampler(ast::SamplerKind::kSampler),
+            ty.Sampler(ast::SamplerKind::kComparisonSampler));
+  EXPECT_NE(ty.DepthTexture(ast::TextureDimension::k2d),
+            ty.DepthTexture(ast::TextureDimension::k1d));
+  EXPECT_NE(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.MultisampledTexture(ast::TextureDimension::k3d, ty.I32()));
+  EXPECT_NE(ty.MultisampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.MultisampledTexture(ast::TextureDimension::k2d, ty.U32()));
+  EXPECT_NE(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.SampledTexture(ast::TextureDimension::k3d, ty.I32()));
+  EXPECT_NE(ty.SampledTexture(ast::TextureDimension::k2d, ty.I32()),
+            ty.SampledTexture(ast::TextureDimension::k2d, ty.U32()));
+  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
+            ty.StorageTexture(ast::TextureDimension::k3d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead));
+  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
+            ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Sint, ast::Access::kRead));
+  EXPECT_NE(ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kRead),
+            ty.StorageTexture(ast::TextureDimension::k2d,
+                              ast::TexelFormat::kR32Uint, ast::Access::kWrite));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/spirv_tools_helpers_test.cc b/src/tint/reader/spirv/spirv_tools_helpers_test.cc
new file mode 100644
index 0000000..cb8b9cf
--- /dev/null
+++ b/src/tint/reader/spirv/spirv_tools_helpers_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 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/reader/spirv/spirv_tools_helpers_test.h"
+
+#include "gtest/gtest.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace test {
+
+std::vector<uint32_t> Assemble(const std::string& spirv_assembly) {
+  // TODO(dneto): Use ScopedTrace?
+
+  // (The target environment doesn't affect assembly.
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+  std::stringstream errors;
+  std::vector<uint32_t> result;
+  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
+                                     const spv_position_t& position,
+                                     const char* message) {
+    errors << "assembly error:" << position.line << ":" << position.column
+           << ": " << message;
+  });
+
+  const auto success = tools.Assemble(
+      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_TRUE(success) << errors.str();
+
+  return result;
+}
+
+std::string AssembleFailure(const std::string& spirv_assembly) {
+  // TODO(dneto): Use ScopedTrace?
+
+  // (The target environment doesn't affect assembly.
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+  std::stringstream errors;
+  std::vector<uint32_t> result;
+  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
+                                     const spv_position_t& position,
+                                     const char* message) {
+    errors << position.line << ":" << position.column << ": " << message;
+  });
+
+  const auto success = tools.Assemble(
+      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_FALSE(success);
+
+  return errors.str();
+}
+
+std::string Disassemble(const std::vector<uint32_t>& spirv_module) {
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+  std::stringstream errors;
+  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
+                                     const spv_position_t& position,
+                                     const char* message) {
+    errors << "disassmbly error:" << position.line << ":" << position.column
+           << ": " << message;
+  });
+
+  std::string result;
+  const auto success = tools.Disassemble(
+      spirv_module, &result, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  EXPECT_TRUE(success) << errors.str();
+
+  return result;
+}
+
+}  // namespace test
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/spirv_tools_helpers_test.h b/src/tint/reader/spirv/spirv_tools_helpers_test.h
new file mode 100644
index 0000000..342ce2c
--- /dev/null
+++ b/src/tint/reader/spirv/spirv_tools_helpers_test.h
@@ -0,0 +1,45 @@
+// Copyright 2020 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_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
+#define SRC_TINT_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
+
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace test {
+
+/// @param spirv_assembly SPIR-V assembly text
+/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
+/// are preserved.
+std::vector<uint32_t> Assemble(const std::string& spirv_assembly);
+
+/// Attempts to assemble given SPIR-V assembly text.  Expect it to fail.
+/// @param spirv_assembly the SPIR-V assembly
+/// @returns the failure message.
+std::string AssembleFailure(const std::string& spirv_assembly);
+
+/// @param spirv_module a SPIR-V binary module
+/// @returns the disassembled module
+std::string Disassemble(const std::vector<uint32_t>& spirv_module);
+
+}  // namespace test
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_SPIRV_TOOLS_HELPERS_TEST_H_
diff --git a/src/tint/reader/spirv/usage.cc b/src/tint/reader/spirv/usage.cc
new file mode 100644
index 0000000..28aec49
--- /dev/null
+++ b/src/tint/reader/spirv/usage.cc
@@ -0,0 +1,194 @@
+// Copyright 2020 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/reader/spirv/usage.h"
+
+#include <sstream>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+Usage::Usage() {}
+Usage::Usage(const Usage& other) = default;
+Usage::~Usage() = default;
+
+std::ostream& Usage::operator<<(std::ostream& out) const {
+  out << "Usage(";
+  if (IsSampler()) {
+    out << "Sampler(";
+    if (is_comparison_sampler_) {
+      out << " comparison";
+    }
+    out << " )";
+  }
+  if (IsTexture()) {
+    out << "Texture(";
+    if (is_sampled_) {
+      out << " is_sampled";
+    }
+    if (is_multisampled_) {
+      out << " ms";
+    }
+    if (is_depth_) {
+      out << " depth";
+    }
+    if (is_storage_read_) {
+      out << " read";
+    }
+    if (is_storage_write_) {
+      out << " write";
+    }
+    out << " )";
+  }
+  out << ")";
+  return out;
+}
+
+bool Usage::IsValid() const {
+  // Check sampler state internal consistency.
+  if (is_comparison_sampler_ && !is_sampler_) {
+    return false;
+  }
+
+  // Check texture state.
+  // |is_texture_| is implied by any of the later texture-based properties.
+  if ((IsStorageTexture() || is_sampled_ || is_multisampled_ || is_depth_) &&
+      !is_texture_) {
+    return false;
+  }
+  if (is_texture_) {
+    // Multisampled implies sampled.
+    if (is_multisampled_) {
+      if (!is_sampled_) {
+        return false;
+      }
+    }
+    // Depth implies sampled.
+    if (is_depth_) {
+      if (!is_sampled_) {
+        return false;
+      }
+    }
+
+    // Sampled can't be storage.
+    if (is_sampled_) {
+      if (IsStorageTexture()) {
+        return false;
+      }
+    }
+
+    // Storage can't be sampled.
+    if (IsStorageTexture()) {
+      if (is_sampled_) {
+        return false;
+      }
+    }
+    // Storage texture can't also be a sampler.
+    if (IsStorageTexture()) {
+      if (is_sampler_) {
+        return false;
+      }
+    }
+
+    // Can't be both read and write.  This is a restriction in WebGPU.
+    if (is_storage_read_ && is_storage_write_) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Usage::IsComplete() const {
+  if (!IsValid()) {
+    return false;
+  }
+  if (IsSampler()) {
+    return true;
+  }
+  if (IsTexture()) {
+    return is_sampled_ || IsStorageTexture();
+  }
+  return false;
+}
+
+bool Usage::operator==(const Usage& other) const {
+  return is_sampler_ == other.is_sampler_ &&
+         is_comparison_sampler_ == other.is_comparison_sampler_ &&
+         is_texture_ == other.is_texture_ && is_sampled_ == other.is_sampled_ &&
+         is_multisampled_ == other.is_multisampled_ &&
+         is_depth_ == other.is_depth_ &&
+         is_storage_read_ == other.is_storage_read_ &&
+         is_storage_write_ == other.is_storage_write_;
+}
+
+void Usage::Add(const Usage& other) {
+  is_sampler_ = is_sampler_ || other.is_sampler_;
+  is_comparison_sampler_ =
+      is_comparison_sampler_ || other.is_comparison_sampler_;
+  is_texture_ = is_texture_ || other.is_texture_;
+  is_sampled_ = is_sampled_ || other.is_sampled_;
+  is_multisampled_ = is_multisampled_ || other.is_multisampled_;
+  is_depth_ = is_depth_ || other.is_depth_;
+  is_storage_read_ = is_storage_read_ || other.is_storage_read_;
+  is_storage_write_ = is_storage_write_ || other.is_storage_write_;
+}
+
+void Usage::AddSampler() {
+  is_sampler_ = true;
+}
+
+void Usage::AddComparisonSampler() {
+  AddSampler();
+  is_comparison_sampler_ = true;
+}
+
+void Usage::AddTexture() {
+  is_texture_ = true;
+}
+
+void Usage::AddStorageReadTexture() {
+  AddTexture();
+  is_storage_read_ = true;
+}
+
+void Usage::AddStorageWriteTexture() {
+  AddTexture();
+  is_storage_write_ = true;
+}
+
+void Usage::AddSampledTexture() {
+  AddTexture();
+  is_sampled_ = true;
+}
+
+void Usage::AddMultisampledTexture() {
+  AddSampledTexture();
+  is_multisampled_ = true;
+}
+
+void Usage::AddDepthTexture() {
+  AddSampledTexture();
+  is_depth_ = true;
+}
+
+std::string Usage::to_str() const {
+  std::ostringstream ss;
+  ss << *this;
+  return ss.str();
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/spirv/usage.h b/src/tint/reader/spirv/usage.h
new file mode 100644
index 0000000..f2686e7
--- /dev/null
+++ b/src/tint/reader/spirv/usage.h
@@ -0,0 +1,137 @@
+// Copyright 2020 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_READER_SPIRV_USAGE_H_
+#define SRC_TINT_READER_SPIRV_USAGE_H_
+
+#include <string>
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// Records the properties of a sampler or texture based on how it's used
+/// by image instructions inside function bodies.
+///
+/// For example:
+///
+///   If %X is the "Image" parameter of an OpImageWrite instruction then
+///    - The memory object declaration underlying %X will gain
+///      AddStorageWriteTexture usage
+///
+///   If %Y is the "Sampled Image" parameter of an OpImageSampleDrefExplicitLod
+///   instruction, and %Y is composed from sampler %YSam and image %YIm, then:
+///    - The memory object declaration underlying %YSam will gain
+///      AddComparisonSampler usage
+///    - The memory object declaration unederlying %YIm will gain
+///      AddSampledTexture and AddDepthTexture usages
+class Usage {
+ public:
+  /// Constructor
+  Usage();
+  /// Copy constructor
+  /// @param other the Usage to clone
+  Usage(const Usage& other);
+  /// Destructor
+  ~Usage();
+
+  /// @returns true if this usage is internally consistent
+  bool IsValid() const;
+  /// @returns true if the usage fully determines a WebGPU binding type.
+  bool IsComplete() const;
+
+  /// @returns true if this usage is a sampler usage.
+  bool IsSampler() const { return is_sampler_; }
+  /// @returns true if this usage is a comparison sampler usage.
+  bool IsComparisonSampler() const { return is_comparison_sampler_; }
+
+  /// @returns true if this usage is a texture usage.
+  bool IsTexture() const { return is_texture_; }
+  /// @returns true if this usage is a sampled texture usage.
+  bool IsSampledTexture() const { return is_sampled_; }
+  /// @returns true if this usage is a multisampled texture usage.
+  bool IsMultisampledTexture() const { return is_multisampled_; }
+  /// @returns true if this usage is a dpeth texture usage.
+  bool IsDepthTexture() const { return is_depth_; }
+  /// @returns true if this usage is a read-only storage texture
+  bool IsStorageReadTexture() const { return is_storage_read_; }
+  /// @returns true if this usage is a write-only storage texture
+  bool IsStorageWriteTexture() const { return is_storage_write_; }
+
+  /// @returns true if this is a storage texture.
+  bool IsStorageTexture() const {
+    return is_storage_read_ || is_storage_write_;
+  }
+
+  /// Emits this usage to the given stream
+  /// @param out the output stream.
+  /// @returns the modified stream.
+  std::ostream& operator<<(std::ostream& out) const;
+
+  /// Equality operator
+  /// @param other the RHS of the equality test.
+  /// @returns true if `other` is identical to `*this`
+  bool operator==(const Usage& other) const;
+
+  /// Adds the usages from another usage object.
+  /// @param other the other usage
+  void Add(const Usage& other);
+
+  /// Records usage as a sampler.
+  void AddSampler();
+  /// Records usage as a comparison sampler.
+  void AddComparisonSampler();
+
+  /// Records usage as a texture of some kind.
+  void AddTexture();
+  /// Records usage as a read-only storage texture.
+  void AddStorageReadTexture();
+  /// Records usage as a write-only storage texture.
+  void AddStorageWriteTexture();
+  /// Records usage as a sampled texture.
+  void AddSampledTexture();
+  /// Records usage as a multisampled texture.
+  void AddMultisampledTexture();
+  /// Records usage as a depth texture.
+  void AddDepthTexture();
+
+  /// @returns this usage object as a string.
+  std::string to_str() const;
+
+ private:
+  // Sampler properties.
+  bool is_sampler_ = false;
+  // A comparison sampler is always a sampler:
+  //    |is_comparison_sampler_| implies |is_sampler_|
+  bool is_comparison_sampler_ = false;
+
+  // Texture properties.
+  // |is_texture_| is always implied by any of the others below.
+  bool is_texture_ = false;
+  bool is_sampled_ = false;
+  bool is_multisampled_ = false;  // This implies it's sampled as well.
+  bool is_depth_ = false;
+  bool is_storage_read_ = false;
+  bool is_storage_write_ = false;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Usage& u) {
+  return u.operator<<(out);
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_SPIRV_USAGE_H_
diff --git a/src/tint/reader/spirv/usage_test.cc b/src/tint/reader/spirv/usage_test.cc
new file mode 100644
index 0000000..2c63e5f
--- /dev/null
+++ b/src/tint/reader/spirv/usage_test.cc
@@ -0,0 +1,295 @@
+// Copyright 2020 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 <algorithm>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+TEST_F(SpvParserTest, Usage_Trivial_Properties) {
+  Usage u;
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_FALSE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_FALSE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+}
+
+TEST_F(SpvParserTest, Usage_Trivial_Output) {
+  std::ostringstream ss;
+  Usage u;
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage()"));
+}
+
+TEST_F(SpvParserTest, Usage_Equality_OneDifference) {
+  const int num_usages = 9;
+  std::vector<Usage> usages(num_usages);
+  usages[1].AddSampler();
+  usages[2].AddComparisonSampler();
+  usages[3].AddTexture();
+  usages[4].AddSampledTexture();
+  usages[5].AddMultisampledTexture();
+  usages[6].AddDepthTexture();
+  usages[7].AddStorageReadTexture();
+  usages[8].AddStorageWriteTexture();
+  for (int i = 0; i < num_usages; ++i) {
+    for (int j = 0; j < num_usages; ++j) {
+      const auto& lhs = usages[i];
+      const auto& rhs = usages[j];
+      if (i == j) {
+        EXPECT_TRUE(lhs == rhs);
+      } else {
+        EXPECT_FALSE(lhs == rhs);
+      }
+    }
+  }
+}
+
+TEST_F(SpvParserTest, Usage_Add) {
+  // Mix two nontrivial usages.
+  Usage a;
+  a.AddStorageReadTexture();
+
+  Usage b;
+  b.AddComparisonSampler();
+
+  a.Add(b);
+
+  EXPECT_FALSE(a.IsValid());
+  EXPECT_FALSE(a.IsComplete());
+  EXPECT_TRUE(a.IsSampler());
+  EXPECT_TRUE(a.IsComparisonSampler());
+  EXPECT_TRUE(a.IsTexture());
+  EXPECT_FALSE(a.IsSampledTexture());
+  EXPECT_FALSE(a.IsMultisampledTexture());
+  EXPECT_FALSE(a.IsDepthTexture());
+  EXPECT_TRUE(a.IsStorageReadTexture());
+  EXPECT_FALSE(a.IsStorageWriteTexture());
+
+  std::ostringstream ss;
+  ss << a;
+  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison )Texture( read ))"));
+}
+
+TEST_F(SpvParserTest, Usage_AddSampler) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddSampler();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_TRUE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_FALSE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( ))"));
+
+  // Check idempotency
+  auto copy(u);
+  u.AddSampler();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddComparisonSampler) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddComparisonSampler();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_TRUE(u.IsSampler());
+  EXPECT_TRUE(u.IsComparisonSampler());
+  EXPECT_FALSE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison ))"));
+
+  auto copy(u);
+  u.AddComparisonSampler();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_FALSE(u.IsComplete());  // Don't know if it's sampled or storage
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( ))"));
+
+  auto copy(u);
+  u.AddTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddSampledTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddSampledTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_TRUE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ))"));
+
+  auto copy(u);
+  u.AddSampledTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddMultisampledTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddMultisampledTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_TRUE(u.IsSampledTexture());
+  EXPECT_TRUE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ms ))"));
+
+  auto copy(u);
+  u.AddMultisampledTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddDepthTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddDepthTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_TRUE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_TRUE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled depth ))"));
+
+  auto copy(u);
+  u.AddDepthTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddStorageReadTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddStorageReadTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_TRUE(u.IsStorageReadTexture());
+  EXPECT_FALSE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( read ))"));
+
+  auto copy(u);
+  u.AddStorageReadTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+TEST_F(SpvParserTest, Usage_AddStorageWriteTexture) {
+  std::ostringstream ss;
+  Usage u;
+  u.AddStorageWriteTexture();
+
+  EXPECT_TRUE(u.IsValid());
+  EXPECT_TRUE(u.IsComplete());
+  EXPECT_FALSE(u.IsSampler());
+  EXPECT_FALSE(u.IsComparisonSampler());
+  EXPECT_TRUE(u.IsTexture());
+  EXPECT_FALSE(u.IsSampledTexture());
+  EXPECT_FALSE(u.IsMultisampledTexture());
+  EXPECT_FALSE(u.IsDepthTexture());
+  EXPECT_FALSE(u.IsStorageReadTexture());
+  EXPECT_TRUE(u.IsStorageWriteTexture());
+
+  ss << u;
+  EXPECT_THAT(ss.str(), Eq("Usage(Texture( write ))"));
+
+  auto copy(u);
+  u.AddStorageWriteTexture();
+  EXPECT_TRUE(u == copy);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
new file mode 100644
index 0000000..b41592c
--- /dev/null
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -0,0 +1,1104 @@
+// Copyright 2020 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/reader/wgsl/lexer.h"
+
+#include <cctype>
+#include <cmath>
+#include <cstring>
+#include <limits>
+#include <utility>
+
+#include "src/tint/debug.h"
+#include "src/tint/text/unicode.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+bool is_whitespace(char c) {
+  return std::isspace(c);
+}
+
+uint32_t dec_value(char c) {
+  if (c >= '0' && c <= '9') {
+    return static_cast<uint32_t>(c - '0');
+  }
+  return 0;
+}
+
+uint32_t hex_value(char c) {
+  if (c >= '0' && c <= '9') {
+    return static_cast<uint32_t>(c - '0');
+  }
+  if (c >= 'a' && c <= 'f') {
+    return 0xA + static_cast<uint32_t>(c - 'a');
+  }
+  if (c >= 'A' && c <= 'F') {
+    return 0xA + static_cast<uint32_t>(c - 'A');
+  }
+  return 0;
+}
+
+}  // namespace
+
+Lexer::Lexer(const Source::File* file)
+    : file_(file),
+      len_(static_cast<uint32_t>(file->content.data.size())),
+      location_{1, 1} {}
+
+Lexer::~Lexer() = default;
+
+Token Lexer::next() {
+  if (auto t = skip_whitespace_and_comments(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_hex_float(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_hex_integer(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_float(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_integer(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_ident(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  if (auto t = try_punctuation(); !t.IsUninitialized()) {
+    return t;
+  }
+
+  return {Token::Type::kError, begin_source(),
+          (is_null() ? "null character found" : "invalid character found")};
+}
+
+Source Lexer::begin_source() const {
+  Source src{};
+  src.file = file_;
+  src.range.begin = location_;
+  src.range.end = location_;
+  return src;
+}
+
+void Lexer::end_source(Source& src) const {
+  src.range.end = location_;
+}
+
+bool Lexer::is_eof() const {
+  return pos_ >= len_;
+}
+
+bool Lexer::is_null() const {
+  return (pos_ < len_) && (file_->content.data[pos_] == 0);
+}
+
+bool Lexer::is_digit(char ch) const {
+  return std::isdigit(ch);
+}
+
+bool Lexer::is_hex(char ch) const {
+  return std::isxdigit(ch);
+}
+
+bool Lexer::matches(size_t pos, std::string_view substr) {
+  if (pos >= len_)
+    return false;
+  return file_->content.data_view.substr(pos, substr.size()) == substr;
+}
+
+Token Lexer::skip_whitespace_and_comments() {
+  for (;;) {
+    auto pos = pos_;
+    while (!is_eof() && is_whitespace(file_->content.data[pos_])) {
+      if (matches(pos_, "\n")) {
+        pos_++;
+        location_.line++;
+        location_.column = 1;
+        continue;
+      }
+
+      pos_++;
+      location_.column++;
+    }
+
+    auto t = skip_comment();
+    if (!t.IsUninitialized()) {
+      return t;
+    }
+
+    // If the cursor didn't advance we didn't remove any whitespace
+    // so we're done.
+    if (pos == pos_)
+      break;
+  }
+  if (is_eof()) {
+    return {Token::Type::kEOF, begin_source()};
+  }
+
+  return {};
+}
+
+Token Lexer::skip_comment() {
+  if (matches(pos_, "//")) {
+    // Line comment: ignore everything until the end of line
+    // or end of input.
+    while (!is_eof() && !matches(pos_, "\n")) {
+      if (is_null()) {
+        return {Token::Type::kError, begin_source(), "null character found"};
+      }
+      pos_++;
+      location_.column++;
+    }
+    return {};
+  }
+
+  if (matches(pos_, "/*")) {
+    // Block comment: ignore everything until the closing '*/' token.
+
+    // Record source location of the initial '/*'
+    auto source = begin_source();
+    source.range.end.column += 1;
+
+    pos_ += 2;
+    location_.column += 2;
+
+    int depth = 1;
+    while (!is_eof() && depth > 0) {
+      if (matches(pos_, "/*")) {
+        // Start of block comment: increase nesting depth.
+        pos_ += 2;
+        location_.column += 2;
+        depth++;
+      } else if (matches(pos_, "*/")) {
+        // End of block comment: decrease nesting depth.
+        pos_ += 2;
+        location_.column += 2;
+        depth--;
+      } else if (matches(pos_, "\n")) {
+        // Newline: skip and update source location.
+        pos_++;
+        location_.line++;
+        location_.column = 1;
+      } else if (is_null()) {
+        return {Token::Type::kError, begin_source(), "null character found"};
+      } else {
+        // Anything else: skip and update source location.
+        pos_++;
+        location_.column++;
+      }
+    }
+    if (depth > 0) {
+      return {Token::Type::kError, source, "unterminated block comment"};
+    }
+  }
+  return {};
+}
+
+Token Lexer::try_float() {
+  auto start = pos_;
+  auto end = pos_;
+
+  auto source = begin_source();
+  bool has_mantissa_digits = false;
+
+  if (matches(end, "-")) {
+    end++;
+  }
+  while (end < len_ && is_digit(file_->content.data[end])) {
+    has_mantissa_digits = true;
+    end++;
+  }
+
+  bool has_point = false;
+  if (end < len_ && matches(end, ".")) {
+    has_point = true;
+    end++;
+  }
+
+  while (end < len_ && is_digit(file_->content.data[end])) {
+    has_mantissa_digits = true;
+    end++;
+  }
+
+  if (!has_mantissa_digits) {
+    return {};
+  }
+
+  // Parse the exponent if one exists
+  bool has_exponent = false;
+  if (end < len_ && (matches(end, "e") || matches(end, "E"))) {
+    end++;
+    if (end < len_ && (matches(end, "+") || matches(end, "-"))) {
+      end++;
+    }
+
+    while (end < len_ && isdigit(file_->content.data[end])) {
+      has_exponent = true;
+      end++;
+    }
+
+    // If an 'e' or 'E' was present, then the number part must also be present.
+    if (!has_exponent) {
+      const auto str = file_->content.data.substr(start, end - start);
+      return {Token::Type::kError, source,
+              "incomplete exponent for floating point literal: " + str};
+    }
+  }
+
+  bool has_f_suffix = false;
+  if (end < len_ && matches(end, "f")) {
+    end++;
+    has_f_suffix = true;
+  }
+
+  if (!has_point && !has_exponent && !has_f_suffix) {
+    // If it only has digits then it's an integer.
+    return {};
+  }
+
+  // Save the error string, for use by diagnostics.
+  const auto str = file_->content.data.substr(start, end - start);
+
+  pos_ = end;
+  location_.column += (end - start);
+
+  end_source(source);
+
+  auto res = strtod(file_->content.data.c_str() + start, nullptr);
+  // This errors out if a non-zero magnitude is too small to represent in a
+  // float. It can't be represented faithfully in an f32.
+  const auto magnitude = std::fabs(res);
+  if (0.0 < magnitude &&
+      magnitude < static_cast<double>(std::numeric_limits<float>::min())) {
+    return {Token::Type::kError, source,
+            "f32 (" + str + ") magnitude too small, not representable"};
+  }
+  // This handles if the number is really large negative number
+  if (res < static_cast<double>(std::numeric_limits<float>::lowest())) {
+    return {Token::Type::kError, source,
+            "f32 (" + str + ") too large (negative)"};
+  }
+  if (res > static_cast<double>(std::numeric_limits<float>::max())) {
+    return {Token::Type::kError, source,
+            "f32 (" + str + ") too large (positive)"};
+  }
+
+  return {source, static_cast<float>(res)};
+}
+
+Token Lexer::try_hex_float() {
+  constexpr uint32_t kTotalBits = 32;
+  constexpr uint32_t kTotalMsb = kTotalBits - 1;
+  constexpr uint32_t kMantissaBits = 23;
+  constexpr uint32_t kMantissaMsb = kMantissaBits - 1;
+  constexpr uint32_t kMantissaShiftRight = kTotalBits - kMantissaBits;
+  constexpr int32_t kExponentBias = 127;
+  constexpr int32_t kExponentMax = 255;
+  constexpr uint32_t kExponentBits = 8;
+  constexpr uint32_t kExponentMask = (1 << kExponentBits) - 1;
+  constexpr uint32_t kExponentLeftShift = kMantissaBits;
+  constexpr uint32_t kSignBit = 31;
+
+  auto start = pos_;
+  auto end = pos_;
+
+  auto source = begin_source();
+
+  // clang-format off
+  // -?0[xX]([0-9a-fA-F]*.?[0-9a-fA-F]+ | [0-9a-fA-F]+.[0-9a-fA-F]*)(p|P)(+|-)?[0-9]+  // NOLINT
+  // clang-format on
+
+  // -?
+  int32_t sign_bit = 0;
+  if (matches(end, "-")) {
+    sign_bit = 1;
+    end++;
+  }
+  // 0[xX]
+  if (matches(end, "0x") || matches(end, "0X")) {
+    end += 2;
+  } else {
+    return {};
+  }
+
+  uint32_t mantissa = 0;
+  uint32_t exponent = 0;
+
+  // TODO(dneto): Values in the normal range for the format do not explicitly
+  // store the most significant bit.  The algorithm here works hard to eliminate
+  // that bit in the representation during parsing, and then it backtracks
+  // when it sees it may have to explicitly represent it, and backtracks again
+  // when it sees the number is sub-normal (i.e. the exponent underflows).
+  // I suspect the logic can be clarified by storing it during parsing, and
+  // then removing it later only when needed.
+
+  // `set_next_mantissa_bit_to` sets next `mantissa` bit starting from msb to
+  // lsb to value 1 if `set` is true, 0 otherwise. Returns true on success, i.e.
+  // when the bit can be accommodated in the available space.
+  uint32_t mantissa_next_bit = kTotalMsb;
+  auto set_next_mantissa_bit_to = [&](bool set, bool integer_part) -> bool {
+    // If adding bits for the integer part, we can overflow whether we set the
+    // bit or not. For the fractional part, we can only overflow when setting
+    // the bit.
+    const bool check_overflow = integer_part || set;
+    // Note: mantissa_next_bit actually decrements, so comparing it as
+    // larger than a positive number relies on wraparound.
+    if (check_overflow && (mantissa_next_bit > kTotalMsb)) {
+      return false;  // Overflowed mantissa
+    }
+    if (set) {
+      mantissa |= (1 << mantissa_next_bit);
+    }
+    --mantissa_next_bit;
+    return true;
+  };
+
+  // Collect integer range (if any)
+  auto integer_range = std::make_pair(end, end);
+  while (end < len_ && is_hex(file_->content.data[end])) {
+    integer_range.second = ++end;
+  }
+
+  // .?
+  bool hex_point = false;
+  if (matches(end, ".")) {
+    hex_point = true;
+    end++;
+  }
+
+  // Collect fractional range (if any)
+  auto fractional_range = std::make_pair(end, end);
+  while (end < len_ && is_hex(file_->content.data[end])) {
+    fractional_range.second = ++end;
+  }
+
+  // Must have at least an integer or fractional part
+  if ((integer_range.first == integer_range.second) &&
+      (fractional_range.first == fractional_range.second)) {
+    return {};
+  }
+
+  // Is the binary exponent present?  It's optional.
+  const bool has_exponent = (matches(end, "p") || matches(end, "P"));
+  if (has_exponent) {
+    end++;
+  }
+  if (!has_exponent && !hex_point) {
+    // It's not a hex float. At best it's a hex integer.
+    return {};
+  }
+
+  // At this point, we know for sure our token is a hex float value,
+  // or an invalid token.
+
+  // Parse integer part
+  // [0-9a-fA-F]*
+
+  bool has_zero_integer = true;
+  // The magnitude is zero if and only if seen_prior_one_bits is false.
+  bool seen_prior_one_bits = false;
+  for (auto i = integer_range.first; i < integer_range.second; ++i) {
+    const auto nibble = hex_value(file_->content.data[i]);
+    if (nibble != 0) {
+      has_zero_integer = false;
+    }
+
+    for (int32_t bit = 3; bit >= 0; --bit) {
+      auto v = 1 & (nibble >> bit);
+
+      // Skip leading 0s and the first 1
+      if (seen_prior_one_bits) {
+        if (!set_next_mantissa_bit_to(v != 0, true)) {
+          return {Token::Type::kError, source,
+                  "mantissa is too large for hex float"};
+        }
+        ++exponent;
+      } else {
+        if (v == 1) {
+          seen_prior_one_bits = true;
+        }
+      }
+    }
+  }
+
+  // Parse fractional part
+  // [0-9a-fA-F]*
+  for (auto i = fractional_range.first; i < fractional_range.second; ++i) {
+    auto nibble = hex_value(file_->content.data[i]);
+    for (int32_t bit = 3; bit >= 0; --bit) {
+      auto v = 1 & (nibble >> bit);
+
+      if (v == 1) {
+        seen_prior_one_bits = true;
+      }
+
+      // If integer part is 0, we only start writing bits to the
+      // mantissa once we have a non-zero fractional bit. While the fractional
+      // values are 0, we adjust the exponent to avoid overflowing `mantissa`.
+      if (!seen_prior_one_bits) {
+        --exponent;
+      } else {
+        if (!set_next_mantissa_bit_to(v != 0, false)) {
+          return {Token::Type::kError, source,
+                  "mantissa is too large for hex float"};
+        }
+      }
+    }
+  }
+
+  // Determine if the value of the mantissa is zero.
+  // Note: it's not enough to check mantissa == 0 as we drop the initial bit,
+  // whether it's in the integer part or the fractional part.
+  const bool is_zero = !seen_prior_one_bits;
+  TINT_ASSERT(Reader, !is_zero || mantissa == 0);
+
+  // Parse the optional exponent.
+  // ((p|P)(\+|-)?[0-9]+)?
+  uint32_t input_exponent = 0;  // Defaults to 0 if not present
+  int32_t exponent_sign = 1;
+  // If the 'p' part is present, the rest of the exponent must exist.
+  if (has_exponent) {
+    // Parse the rest of the exponent.
+    // (+|-)?
+    if (matches(end, "+")) {
+      end++;
+    } else if (matches(end, "-")) {
+      exponent_sign = -1;
+      end++;
+    }
+
+    // Parse exponent from input
+    // [0-9]+
+    // Allow overflow (in uint32_t) when the floating point value magnitude is
+    // zero.
+    bool has_exponent_digits = false;
+    while (end < len_ && isdigit(file_->content.data[end])) {
+      has_exponent_digits = true;
+      auto prev_exponent = input_exponent;
+      input_exponent =
+          (input_exponent * 10) + dec_value(file_->content.data[end]);
+      // Check if we've overflowed input_exponent. This only matters when
+      // the mantissa is non-zero.
+      if (!is_zero && (prev_exponent > input_exponent)) {
+        return {Token::Type::kError, source,
+                "exponent is too large for hex float"};
+      }
+      end++;
+    }
+
+    // Parse optional 'f' suffix.  For a hex float, it can only exist
+    // when the exponent is present. Otherwise it will look like
+    // one of the mantissa digits.
+    if (end < len_ && matches(end, "f")) {
+      end++;
+    }
+
+    if (!has_exponent_digits) {
+      return {Token::Type::kError, source,
+              "expected an exponent value for hex float"};
+    }
+  }
+
+  pos_ = end;
+  location_.column += (end - start);
+  end_source(source);
+
+  if (is_zero) {
+    // If value is zero, then ignore the exponent and produce a zero
+    exponent = 0;
+  } else {
+    // Ensure input exponent is not too large; i.e. that it won't overflow when
+    // adding the exponent bias.
+    const uint32_t kIntMax =
+        static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
+    const uint32_t kMaxInputExponent = kIntMax - kExponentBias;
+    if (input_exponent > kMaxInputExponent) {
+      return {Token::Type::kError, source,
+              "exponent is too large for hex float"};
+    }
+
+    // Compute exponent so far
+    exponent += static_cast<uint32_t>(static_cast<int32_t>(input_exponent) *
+                                      exponent_sign);
+
+    // Bias exponent if non-zero
+    // After this, if exponent is <= 0, our value is a denormal
+    exponent += kExponentBias;
+
+    // We know the number is not zero.  The MSB is 1 (by construction), and
+    // should be eliminated because it becomes the implicit 1 that isn't
+    // explicitly represented in the binary32 format.  We'll bring it back
+    // later if we find the exponent actually underflowed, i.e. the number
+    // is sub-normal.
+    if (has_zero_integer) {
+      mantissa <<= 1;
+      --exponent;
+    }
+  }
+
+  // We can now safely work with exponent as a signed quantity, as there's no
+  // chance to overflow
+  int32_t signed_exponent = static_cast<int32_t>(exponent);
+
+  // Shift mantissa to occupy the low 23 bits
+  mantissa >>= kMantissaShiftRight;
+
+  // If denormal, shift mantissa until our exponent is zero
+  if (!is_zero) {
+    // Denorm has exponent 0 and non-zero mantissa. We set the top bit here,
+    // then shift the mantissa to make exponent zero.
+    if (signed_exponent <= 0) {
+      mantissa >>= 1;
+      mantissa |= (1 << kMantissaMsb);
+    }
+
+    while (signed_exponent < 0) {
+      mantissa >>= 1;
+      ++signed_exponent;
+
+      // If underflow, clamp to zero
+      if (mantissa == 0) {
+        signed_exponent = 0;
+      }
+    }
+  }
+
+  if (signed_exponent > kExponentMax) {
+    // Overflow: set to infinity
+    signed_exponent = kExponentMax;
+    mantissa = 0;
+  } else if (signed_exponent == kExponentMax && mantissa != 0) {
+    // NaN: set to infinity
+    mantissa = 0;
+  }
+
+  // Combine sign, mantissa, and exponent
+  uint32_t result_u32 = sign_bit << kSignBit;
+  result_u32 |= mantissa;
+  result_u32 |= (static_cast<uint32_t>(signed_exponent) & kExponentMask)
+                << kExponentLeftShift;
+
+  // Reinterpret as float and return
+  float result;
+  std::memcpy(&result, &result_u32, sizeof(result));
+  return {source, static_cast<float>(result)};
+}
+
+Token Lexer::build_token_from_int_if_possible(Source source,
+                                              size_t start,
+                                              size_t end,
+                                              int32_t base) {
+  auto res = strtoll(file_->content.data.c_str() + start, nullptr, base);
+  if (matches(pos_, "u")) {
+    if (static_cast<uint64_t>(res) >
+        static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
+      return {Token::Type::kError, source,
+              "u32 (" + file_->content.data.substr(start, end - start) +
+                  ") too large"};
+    }
+    pos_ += 1;
+    location_.column += 1;
+    end_source(source);
+    return {source, static_cast<uint32_t>(res)};
+  }
+
+  if (res < static_cast<int64_t>(std::numeric_limits<int32_t>::min())) {
+    return {Token::Type::kError, source,
+            "i32 (" + file_->content.data.substr(start, end - start) +
+                ") too small"};
+  }
+  if (res > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) {
+    return {Token::Type::kError, source,
+            "i32 (" + file_->content.data.substr(start, end - start) +
+                ") too large"};
+  }
+  end_source(source);
+  return {source, static_cast<int32_t>(res)};
+}
+
+Token Lexer::try_hex_integer() {
+  constexpr size_t kMaxDigits = 8;  // Valid for both 32-bit integer types
+  auto start = pos_;
+  auto end = pos_;
+
+  auto source = begin_source();
+
+  if (matches(end, "-")) {
+    end++;
+  }
+
+  if (matches(end, "0x") || matches(end, "0X")) {
+    end += 2;
+  } else {
+    return {};
+  }
+
+  auto first = end;
+  while (!is_eof() && is_hex(file_->content.data[end])) {
+    end++;
+
+    auto digits = end - first;
+    if (digits > kMaxDigits) {
+      return {Token::Type::kError, source,
+              "integer literal (" +
+                  file_->content.data.substr(start, end - 1 - start) +
+                  "...) has too many digits"};
+    }
+  }
+  if (first == end) {
+    return {Token::Type::kError, source,
+            "integer or float hex literal has no significant digits"};
+  }
+
+  pos_ = end;
+  location_.column += (end - start);
+
+  return build_token_from_int_if_possible(source, start, end, 16);
+}
+
+Token Lexer::try_integer() {
+  constexpr size_t kMaxDigits = 10;  // Valid for both 32-bit integer types
+  auto start = pos_;
+  auto end = start;
+
+  auto source = begin_source();
+
+  if (matches(end, "-")) {
+    end++;
+  }
+
+  if (end >= len_ || !is_digit(file_->content.data[end])) {
+    return {};
+  }
+
+  auto first = end;
+  // If the first digit is a zero this must only be zero as leading zeros
+  // are not allowed.
+  auto next = first + 1;
+  if (next < len_) {
+    if (file_->content.data[first] == '0' &&
+        is_digit(file_->content.data[next])) {
+      return {Token::Type::kError, source,
+              "integer literal (" +
+                  file_->content.data.substr(start, end - 1 - start) +
+                  "...) has leading 0s"};
+    }
+  }
+
+  while (end < len_ && is_digit(file_->content.data[end])) {
+    auto digits = end - first;
+    if (digits > kMaxDigits) {
+      return {Token::Type::kError, source,
+              "integer literal (" +
+                  file_->content.data.substr(start, end - 1 - start) +
+                  "...) has too many digits"};
+    }
+
+    end++;
+  }
+
+  pos_ = end;
+  location_.column += (end - start);
+
+  return build_token_from_int_if_possible(source, start, end, 10);
+}
+
+Token Lexer::try_ident() {
+  auto source = begin_source();
+  auto start = pos_;
+
+  // This below assumes that the size of a single std::string element is 1 byte.
+  static_assert(sizeof(file_->content.data[0]) == sizeof(uint8_t),
+                "tint::reader::wgsl requires the size of a std::string element "
+                "to be a single byte");
+
+  // Must begin with an XID_Source unicode character, or underscore
+  {
+    auto* utf8 = reinterpret_cast<const uint8_t*>(&file_->content.data[pos_]);
+    auto [code_point, n] =
+        text::utf8::Decode(utf8, file_->content.data.size() - pos_);
+    if (code_point != text::CodePoint('_') && !code_point.IsXIDStart()) {
+      return {};
+    }
+    // Consume start codepoint
+    pos_ += n;
+    location_.column += n;
+  }
+
+  while (!is_eof()) {
+    // Must continue with an XID_Continue unicode character
+    auto* utf8 = reinterpret_cast<const uint8_t*>(&file_->content.data[pos_]);
+    auto [code_point, n] =
+        text::utf8::Decode(utf8, file_->content.data.size() - pos_);
+    if (!code_point.IsXIDContinue()) {
+      break;
+    }
+
+    // Consume continuing codepoint
+    pos_ += n;
+    location_.column += n;
+  }
+
+  if (file_->content.data[start] == '_') {
+    // Check for an underscore on its own (special token), or a
+    // double-underscore (not allowed).
+    if ((pos_ == start + 1) || (file_->content.data[start + 1] == '_')) {
+      location_.column -= (pos_ - start);
+      pos_ = start;
+      return {};
+    }
+  }
+
+  auto str = file_->content.data_view.substr(start, pos_ - start);
+  end_source(source);
+
+  auto t = check_keyword(source, str);
+  if (!t.IsUninitialized()) {
+    return t;
+  }
+
+  return {Token::Type::kIdentifier, source, str};
+}
+
+Token Lexer::try_punctuation() {
+  auto source = begin_source();
+  auto type = Token::Type::kUninitialized;
+
+  if (matches(pos_, "@")) {
+    type = Token::Type::kAttr;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "[[")) {
+    type = Token::Type::kAttrLeft;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "]]")) {
+    type = Token::Type::kAttrRight;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "(")) {
+    type = Token::Type::kParenLeft;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ")")) {
+    type = Token::Type::kParenRight;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "[")) {
+    type = Token::Type::kBracketLeft;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "]")) {
+    type = Token::Type::kBracketRight;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "{")) {
+    type = Token::Type::kBraceLeft;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "}")) {
+    type = Token::Type::kBraceRight;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "&&")) {
+    type = Token::Type::kAndAnd;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "&")) {
+    type = Token::Type::kAnd;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "/")) {
+    type = Token::Type::kForwardSlash;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "!=")) {
+    type = Token::Type::kNotEqual;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "!")) {
+    type = Token::Type::kBang;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ":")) {
+    type = Token::Type::kColon;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ",")) {
+    type = Token::Type::kComma;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "==")) {
+    type = Token::Type::kEqualEqual;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "=")) {
+    type = Token::Type::kEqual;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ">=")) {
+    type = Token::Type::kGreaterThanEqual;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, ">>")) {
+    type = Token::Type::kShiftRight;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, ">")) {
+    type = Token::Type::kGreaterThan;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "<=")) {
+    type = Token::Type::kLessThanEqual;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "<<")) {
+    type = Token::Type::kShiftLeft;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "<")) {
+    type = Token::Type::kLessThan;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "%")) {
+    type = Token::Type::kMod;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "->")) {
+    type = Token::Type::kArrow;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "--")) {
+    type = Token::Type::kMinusMinus;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "-")) {
+    type = Token::Type::kMinus;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ".")) {
+    type = Token::Type::kPeriod;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "++")) {
+    type = Token::Type::kPlusPlus;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "+")) {
+    type = Token::Type::kPlus;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "||")) {
+    type = Token::Type::kOrOr;
+    pos_ += 2;
+    location_.column += 2;
+  } else if (matches(pos_, "|")) {
+    type = Token::Type::kOr;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, ";")) {
+    type = Token::Type::kSemicolon;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "*")) {
+    type = Token::Type::kStar;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "~")) {
+    type = Token::Type::kTilde;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "_")) {
+    type = Token::Type::kUnderscore;
+    pos_ += 1;
+    location_.column += 1;
+  } else if (matches(pos_, "^")) {
+    type = Token::Type::kXor;
+    pos_ += 1;
+    location_.column += 1;
+  }
+
+  end_source(source);
+
+  return {type, source};
+}
+
+Token Lexer::check_keyword(const Source& source, std::string_view str) {
+  if (str == "array")
+    return {Token::Type::kArray, source, "array"};
+  if (str == "atomic")
+    return {Token::Type::kAtomic, source, "atomic"};
+  if (str == "bitcast")
+    return {Token::Type::kBitcast, source, "bitcast"};
+  if (str == "bool")
+    return {Token::Type::kBool, source, "bool"};
+  if (str == "break")
+    return {Token::Type::kBreak, source, "break"};
+  if (str == "case")
+    return {Token::Type::kCase, source, "case"};
+  if (str == "continue")
+    return {Token::Type::kContinue, source, "continue"};
+  if (str == "continuing")
+    return {Token::Type::kContinuing, source, "continuing"};
+  if (str == "discard")
+    return {Token::Type::kDiscard, source, "discard"};
+  if (str == "default")
+    return {Token::Type::kDefault, source, "default"};
+  if (str == "else")
+    return {Token::Type::kElse, source, "else"};
+  if (str == "elseif")
+    return {Token::Type::kElseIf, source, "elseif"};
+  if (str == "f32")
+    return {Token::Type::kF32, source, "f32"};
+  if (str == "fallthrough")
+    return {Token::Type::kFallthrough, source, "fallthrough"};
+  if (str == "false")
+    return {Token::Type::kFalse, source, "false"};
+  if (str == "fn")
+    return {Token::Type::kFn, source, "fn"};
+  if (str == "for")
+    return {Token::Type::kFor, source, "for"};
+  if (str == "function")
+    return {Token::Type::kFunction, source, "function"};
+  if (str == "i32")
+    return {Token::Type::kI32, source, "i32"};
+  if (str == "if")
+    return {Token::Type::kIf, source, "if"};
+  if (str == "import")
+    return {Token::Type::kImport, source, "import"};
+  if (str == "let")
+    return {Token::Type::kLet, source, "let"};
+  if (str == "loop")
+    return {Token::Type::kLoop, source, "loop"};
+  if (str == "mat2x2")
+    return {Token::Type::kMat2x2, source, "mat2x2"};
+  if (str == "mat2x3")
+    return {Token::Type::kMat2x3, source, "mat2x3"};
+  if (str == "mat2x4")
+    return {Token::Type::kMat2x4, source, "mat2x4"};
+  if (str == "mat3x2")
+    return {Token::Type::kMat3x2, source, "mat3x2"};
+  if (str == "mat3x3")
+    return {Token::Type::kMat3x3, source, "mat3x3"};
+  if (str == "mat3x4")
+    return {Token::Type::kMat3x4, source, "mat3x4"};
+  if (str == "mat4x2")
+    return {Token::Type::kMat4x2, source, "mat4x2"};
+  if (str == "mat4x3")
+    return {Token::Type::kMat4x3, source, "mat4x3"};
+  if (str == "mat4x4")
+    return {Token::Type::kMat4x4, source, "mat4x4"};
+  if (str == "override")
+    return {Token::Type::kOverride, source, "override"};
+  if (str == "private")
+    return {Token::Type::kPrivate, source, "private"};
+  if (str == "ptr")
+    return {Token::Type::kPtr, source, "ptr"};
+  if (str == "return")
+    return {Token::Type::kReturn, source, "return"};
+  if (str == "sampler")
+    return {Token::Type::kSampler, source, "sampler"};
+  if (str == "sampler_comparison")
+    return {Token::Type::kComparisonSampler, source, "sampler_comparison"};
+  if (str == "storage_buffer" || str == "storage")
+    return {Token::Type::kStorage, source, "storage"};
+  if (str == "struct")
+    return {Token::Type::kStruct, source, "struct"};
+  if (str == "switch")
+    return {Token::Type::kSwitch, source, "switch"};
+  if (str == "texture_1d")
+    return {Token::Type::kTextureSampled1d, source, "texture_1d"};
+  if (str == "texture_2d")
+    return {Token::Type::kTextureSampled2d, source, "texture_2d"};
+  if (str == "texture_2d_array")
+    return {Token::Type::kTextureSampled2dArray, source, "texture_2d_array"};
+  if (str == "texture_3d")
+    return {Token::Type::kTextureSampled3d, source, "texture_3d"};
+  if (str == "texture_cube")
+    return {Token::Type::kTextureSampledCube, source, "texture_cube"};
+  if (str == "texture_cube_array") {
+    return {Token::Type::kTextureSampledCubeArray, source,
+            "texture_cube_array"};
+  }
+  if (str == "texture_depth_2d")
+    return {Token::Type::kTextureDepth2d, source, "texture_depth_2d"};
+  if (str == "texture_depth_2d_array") {
+    return {Token::Type::kTextureDepth2dArray, source,
+            "texture_depth_2d_array"};
+  }
+  if (str == "texture_depth_cube")
+    return {Token::Type::kTextureDepthCube, source, "texture_depth_cube"};
+  if (str == "texture_depth_cube_array") {
+    return {Token::Type::kTextureDepthCubeArray, source,
+            "texture_depth_cube_array"};
+  }
+  if (str == "texture_depth_multisampled_2d") {
+    return {Token::Type::kTextureDepthMultisampled2d, source,
+            "texture_depth_multisampled_2d"};
+  }
+  if (str == "texture_external") {
+    return {Token::Type::kTextureExternal, source, "texture_external"};
+  }
+  if (str == "texture_multisampled_2d") {
+    return {Token::Type::kTextureMultisampled2d, source,
+            "texture_multisampled_2d"};
+  }
+  if (str == "texture_storage_1d") {
+    return {Token::Type::kTextureStorage1d, source, "texture_storage_1d"};
+  }
+  if (str == "texture_storage_2d") {
+    return {Token::Type::kTextureStorage2d, source, "texture_storage_2d"};
+  }
+  if (str == "texture_storage_2d_array") {
+    return {Token::Type::kTextureStorage2dArray, source,
+            "texture_storage_2d_array"};
+  }
+  if (str == "texture_storage_3d") {
+    return {Token::Type::kTextureStorage3d, source, "texture_storage_3d"};
+  }
+  if (str == "true")
+    return {Token::Type::kTrue, source, "true"};
+  if (str == "type")
+    return {Token::Type::kType, source, "type"};
+  if (str == "u32")
+    return {Token::Type::kU32, source, "u32"};
+  if (str == "uniform")
+    return {Token::Type::kUniform, source, "uniform"};
+  if (str == "var")
+    return {Token::Type::kVar, source, "var"};
+  if (str == "vec2")
+    return {Token::Type::kVec2, source, "vec2"};
+  if (str == "vec3")
+    return {Token::Type::kVec3, source, "vec3"};
+  if (str == "vec4")
+    return {Token::Type::kVec4, source, "vec4"};
+  if (str == "workgroup")
+    return {Token::Type::kWorkgroup, source, "workgroup"};
+  return {};
+}
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/lexer.h b/src/tint/reader/wgsl/lexer.h
new file mode 100644
index 0000000..710b10e
--- /dev/null
+++ b/src/tint/reader/wgsl/lexer.h
@@ -0,0 +1,100 @@
+// Copyright 2020 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_READER_WGSL_LEXER_H_
+#define SRC_TINT_READER_WGSL_LEXER_H_
+
+#include <string>
+
+#include "src/tint/reader/wgsl/token.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+/// Converts the input stream into a series of Tokens
+class Lexer {
+ public:
+  /// Creates a new Lexer
+  /// @param file the source file
+  explicit Lexer(const Source::File* file);
+  ~Lexer();
+
+  /// Returns the next token in the input stream.
+  /// @return Token
+  Token next();
+
+ private:
+  /// Advances past whitespace and comments, if present
+  /// at the current position.
+  /// @returns error token, EOF, or uninitialized
+  Token skip_whitespace_and_comments();
+  /// Advances past a comment at the current position, if one exists.
+  /// Returns an error if there was an unterminated block comment,
+  /// or a null character was present.
+  /// @returns uninitialized token on success, or error
+  Token skip_comment();
+
+  Token build_token_from_int_if_possible(Source source,
+                                         size_t start,
+                                         size_t end,
+                                         int32_t base);
+  Token check_keyword(const Source&, std::string_view);
+
+  /// The try_* methods have the following in common:
+  /// - They assume there is at least one character to be consumed,
+  ///   i.e. the input has not yet reached end of file.
+  /// - They return an initialized token when they match and consume
+  ///   a token of the specified kind.
+  /// - Some can return an error token.
+  /// - Otherwise they return an uninitialized token when they did not
+  ///   match a token of the specfied kind.
+  Token try_float();
+  Token try_hex_float();
+  Token try_hex_integer();
+  Token try_ident();
+  Token try_integer();
+  Token try_punctuation();
+
+  Source begin_source() const;
+  void end_source(Source&) const;
+
+  /// @returns true if the end of the input has been reached.
+  bool is_eof() const;
+  /// @returns true if there is another character on the input and
+  /// it is not null.
+  bool is_null() const;
+  /// @param ch a character
+  /// @returns true if 'ch' is a decimal digit
+  bool is_digit(char ch) const;
+  /// @param ch a character
+  /// @returns true if 'ch' is a hexadecimal digit
+  bool is_hex(char ch) const;
+  bool matches(size_t pos, std::string_view substr);
+
+  /// The source file content
+  Source::File const* const file_;
+  /// The length of the input
+  uint32_t len_ = 0;
+  /// The current position in utf-8 code units (bytes) within the input
+  uint32_t pos_ = 0;
+  /// The current location within the input
+  Source::Location location_;
+};
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_LEXER_H_
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
new file mode 100644
index 0000000..3df5b4e
--- /dev/null
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -0,0 +1,847 @@
+// Copyright 2020 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/reader/wgsl/lexer.h"
+
+#include <limits>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+using LexerTest = testing::Test;
+
+TEST_F(LexerTest, Empty) {
+  Source::File file("", "");
+  Lexer l(&file);
+  auto t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Whitespace) {
+  Source::File file("", "\t\r\n\t    ident\t\n\t  \r ");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 2u);
+  EXPECT_EQ(t.source().range.begin.column, 6u);
+  EXPECT_EQ(t.source().range.end.line, 2u);
+  EXPECT_EQ(t.source().range.end.column, 11u);
+  EXPECT_EQ(t.to_str(), "ident");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Comments_Line) {
+  Source::File file("", R"(//starts with comment
+ident1 //ends with comment
+// blank line
+ ident2)");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 2u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 2u);
+  EXPECT_EQ(t.source().range.end.column, 7u);
+  EXPECT_EQ(t.to_str(), "ident1");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 4u);
+  EXPECT_EQ(t.source().range.begin.column, 2u);
+  EXPECT_EQ(t.source().range.end.line, 4u);
+  EXPECT_EQ(t.source().range.end.column, 8u);
+  EXPECT_EQ(t.to_str(), "ident2");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Comments_Block) {
+  Source::File file("", R"(/* comment
+text */ident)");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 2u);
+  EXPECT_EQ(t.source().range.begin.column, 8u);
+  EXPECT_EQ(t.source().range.end.line, 2u);
+  EXPECT_EQ(t.source().range.end.column, 13u);
+  EXPECT_EQ(t.to_str(), "ident");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Comments_Block_Nested) {
+  Source::File file("", R"(/* comment
+text // nested line comments are ignored /* more text
+/////**/ */*/ident)");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 3u);
+  EXPECT_EQ(t.source().range.begin.column, 14u);
+  EXPECT_EQ(t.source().range.end.line, 3u);
+  EXPECT_EQ(t.source().range.end.column, 19u);
+  EXPECT_EQ(t.to_str(), "ident");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Comments_Block_Unterminated) {
+  // I had to break up the /* because otherwise the clang readability check
+  // errored out saying it could not find the end of a multi-line comment.
+  Source::File file("", R"(
+  /)"
+                        R"(*
+abcd)");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "unterminated block comment");
+  EXPECT_EQ(t.source().range.begin.line, 2u);
+  EXPECT_EQ(t.source().range.begin.column, 3u);
+  EXPECT_EQ(t.source().range.end.line, 2u);
+  EXPECT_EQ(t.source().range.end.column, 4u);
+}
+
+TEST_F(LexerTest, Null_InWhitespace_IsError) {
+  Source::File file("", std::string{' ', 0, ' '});
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsError());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 2u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 2u);
+  EXPECT_EQ(t.to_str(), "null character found");
+}
+
+TEST_F(LexerTest, Null_InLineComment_IsError) {
+  Source::File file("", std::string{'/', '/', ' ', 0, ' '});
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsError());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 4u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 4u);
+  EXPECT_EQ(t.to_str(), "null character found");
+}
+
+TEST_F(LexerTest, Null_InBlockComment_IsError) {
+  Source::File file("", std::string{'/', '*', ' ', 0, '*', '/'});
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsError());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 4u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 4u);
+  EXPECT_EQ(t.to_str(), "null character found");
+}
+
+TEST_F(LexerTest, Null_InIdentifier_IsError) {
+  // Try inserting a null in an identifier. Other valid token
+  // kinds will behave similarly, so use the identifier case
+  // as a representative.
+  Source::File file("", std::string{'a', 0, 'c'});
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.to_str(), "a");
+  t = l.next();
+  EXPECT_TRUE(t.IsError());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 2u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 2u);
+  EXPECT_EQ(t.to_str(), "null character found");
+}
+
+struct FloatData {
+  const char* input;
+  float result;
+};
+inline std::ostream& operator<<(std::ostream& out, FloatData data) {
+  out << std::string(data.input);
+  return out;
+}
+using FloatTest = testing::TestWithParam<FloatData>;
+TEST_P(FloatTest, Parse) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral));
+  EXPECT_EQ(t.to_f32(), params.result);
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+INSTANTIATE_TEST_SUITE_P(LexerTest,
+                         FloatTest,
+                         testing::Values(
+                             // No decimal, with 'f' suffix
+                             FloatData{"0f", 0.0f},
+                             FloatData{"1f", 1.0f},
+                             FloatData{"-0f", 0.0f},
+                             FloatData{"-1f", -1.0f},
+
+                             // Zero, with decimal.
+                             FloatData{"0.0", 0.0f},
+                             FloatData{"0.", 0.0f},
+                             FloatData{".0", 0.0f},
+                             FloatData{"-0.0", 0.0f},
+                             FloatData{"-0.", 0.0f},
+                             FloatData{"-.0", 0.0f},
+                             // Zero, with decimal and 'f' suffix
+                             FloatData{"0.0f", 0.0f},
+                             FloatData{"0.f", 0.0f},
+                             FloatData{".0f", 0.0f},
+                             FloatData{"-0.0f", 0.0f},
+                             FloatData{"-0.f", 0.0f},
+                             FloatData{"-.0", 0.0f},
+
+                             // Non-zero with decimal
+                             FloatData{"5.7", 5.7f},
+                             FloatData{"5.", 5.f},
+                             FloatData{".7", .7f},
+                             FloatData{"-5.7", -5.7f},
+                             FloatData{"-5.", -5.f},
+                             FloatData{"-.7", -.7f},
+                             // Non-zero with decimal and 'f' suffix
+                             FloatData{"5.7f", 5.7f},
+                             FloatData{"5.f", 5.f},
+                             FloatData{".7f", .7f},
+                             FloatData{"-5.7f", -5.7f},
+                             FloatData{"-5.f", -5.f},
+                             FloatData{"-.7f", -.7f},
+
+                             // No decimal, with exponent
+                             FloatData{"1e5", 1e5f},
+                             FloatData{"1E5", 1e5f},
+                             FloatData{"1e-5", 1e-5f},
+                             FloatData{"1E-5", 1e-5f},
+                             // No decimal, with exponent and 'f' suffix
+                             FloatData{"1e5f", 1e5f},
+                             FloatData{"1E5f", 1e5f},
+                             FloatData{"1e-5f", 1e-5f},
+                             FloatData{"1E-5f", 1e-5f},
+                             // With decimal and exponents
+                             FloatData{"0.2e+12", 0.2e12f},
+                             FloatData{"1.2e-5", 1.2e-5f},
+                             FloatData{"2.57e23", 2.57e23f},
+                             FloatData{"2.5e+0", 2.5f},
+                             FloatData{"2.5e-0", 2.5f},
+                             // With decimal and exponents and 'f' suffix
+                             FloatData{"0.2e+12f", 0.2e12f},
+                             FloatData{"1.2e-5f", 1.2e-5f},
+                             FloatData{"2.57e23f", 2.57e23f},
+                             FloatData{"2.5e+0f", 2.5f},
+                             FloatData{"2.5e-0f", 2.5f}));
+
+using FloatTest_Invalid = testing::TestWithParam<const char*>;
+TEST_P(FloatTest_Invalid, Handles) {
+  Source::File file("", GetParam());
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_FALSE(t.Is(Token::Type::kFloatLiteral));
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    FloatTest_Invalid,
+    testing::Values(".",
+                    "-.",
+                    // Need a mantissa digit
+                    ".e5",
+                    ".E5",
+                    // Need exponent digits
+                    ".e",
+                    ".e+",
+                    ".e-",
+                    ".E",
+                    ".e+",
+                    ".e-",
+                    // Overflow
+                    "2.5e+256",
+                    "-2.5e+127",
+                    // Magnitude smaller than smallest positive f32.
+                    "2.5e-300",
+                    "-2.5e-300",
+                    // Decimal exponent must immediately
+                    // follow the 'e'.
+                    "2.5e 12",
+                    "2.5e +12",
+                    "2.5e -12",
+                    "2.5e+ 123",
+                    "2.5e- 123",
+                    "2.5E 12",
+                    "2.5E +12",
+                    "2.5E -12",
+                    "2.5E+ 123",
+                    "2.5E- 123"));
+
+using AsciiIdentifierTest = testing::TestWithParam<const char*>;
+TEST_P(AsciiIdentifierTest, Parse) {
+  Source::File file("", GetParam());
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(GetParam()));
+  EXPECT_EQ(t.to_str(), GetParam());
+}
+INSTANTIATE_TEST_SUITE_P(LexerTest,
+                         AsciiIdentifierTest,
+                         testing::Values("a",
+                                         "test",
+                                         "test01",
+                                         "test_",
+                                         "_test",
+                                         "test_01",
+                                         "ALLCAPS",
+                                         "MiXeD_CaSe",
+                                         "abcdefghijklmnopqrstuvwxyz",
+                                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+                                         "alldigits_0123456789"));
+
+struct UnicodeCase {
+  const char* utf8;
+  size_t code_units;
+};
+
+using UnicodeIdentifierTest = testing::TestWithParam<UnicodeCase>;
+TEST_P(UnicodeIdentifierTest, Parse) {
+  Source::File file("", GetParam().utf8);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + GetParam().code_units);
+  EXPECT_EQ(t.to_str(), GetParam().utf8);
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    UnicodeIdentifierTest,
+    testing::ValuesIn({
+        UnicodeCase{// "𝐢𝐝𝐞𝐧𝐭𝐢𝐟𝐢𝐞𝐫"
+                    "\xf0\x9d\x90\xa2\xf0\x9d\x90\x9d\xf0\x9d\x90\x9e\xf0\x9d"
+                    "\x90\xa7\xf0\x9d\x90\xad\xf0\x9d\x90\xa2\xf0\x9d\x90\x9f"
+                    "\xf0\x9d\x90\xa2\xf0\x9d\x90\x9e\xf0\x9d\x90\xab",
+                    40},
+        UnicodeCase{// "𝑖𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟"
+                    "\xf0\x9d\x91\x96\xf0\x9d\x91\x91\xf0\x9d\x91\x92\xf0\x9d"
+                    "\x91\x9b\xf0\x9d\x91\xa1\xf0\x9d\x91\x96\xf0\x9d\x91\x93"
+                    "\xf0\x9d\x91\x96\xf0\x9d\x91\x92\xf0\x9d\x91\x9f",
+                    40},
+        UnicodeCase{
+            // "identifier"
+            "\xef\xbd\x89\xef\xbd\x84\xef\xbd\x85\xef\xbd\x8e\xef\xbd\x94\xef"
+            "\xbd\x89\xef\xbd\x86\xef\xbd\x89\xef\xbd\x85\xef\xbd\x92",
+            30},
+        UnicodeCase{// "𝕚𝕕𝕖𝕟𝕥𝕚𝕗𝕚𝕖𝕣𝟙𝟚𝟛"
+                    "\xf0\x9d\x95\x9a\xf0\x9d\x95\x95\xf0\x9d\x95\x96\xf0\x9d"
+                    "\x95\x9f\xf0\x9d\x95\xa5\xf0\x9d\x95\x9a\xf0\x9d\x95\x97"
+                    "\xf0\x9d\x95\x9a\xf0\x9d\x95\x96\xf0\x9d\x95\xa3\xf0\x9d"
+                    "\x9f\x99\xf0\x9d\x9f\x9a\xf0\x9d\x9f\x9b",
+                    52},
+        UnicodeCase{
+            // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
+            "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
+            "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
+            "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33",
+            43},
+    }));
+
+TEST_F(LexerTest, IdentifierTest_SingleUnderscoreDoesNotMatch) {
+  Source::File file("", "_");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_FALSE(t.IsIdentifier());
+}
+
+TEST_F(LexerTest, IdentifierTest_DoesNotStartWithDoubleUnderscore) {
+  Source::File file("", "__test");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_FALSE(t.IsIdentifier());
+}
+
+TEST_F(LexerTest, IdentifierTest_DoesNotStartWithNumber) {
+  Source::File file("", "01test");
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_FALSE(t.IsIdentifier());
+}
+
+struct HexSignedIntData {
+  const char* input;
+  int32_t result;
+};
+inline std::ostream& operator<<(std::ostream& out, HexSignedIntData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+using IntegerTest_HexSigned = testing::TestWithParam<HexSignedIntData>;
+TEST_P(IntegerTest_HexSigned, Matches) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(Token::Type::kSintLiteral));
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+  EXPECT_EQ(t.to_i32(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    IntegerTest_HexSigned,
+    testing::Values(
+        HexSignedIntData{"0x0", 0},
+        HexSignedIntData{"0X0", 0},
+        HexSignedIntData{"0x42", 66},
+        HexSignedIntData{"0X42", 66},
+        HexSignedIntData{"-0x42", -66},
+        HexSignedIntData{"-0X42", -66},
+        HexSignedIntData{"0xeF1Abc9", 250719177},
+        HexSignedIntData{"0XeF1Abc9", 250719177},
+        HexSignedIntData{"-0x80000000", std::numeric_limits<int32_t>::min()},
+        HexSignedIntData{"-0X80000000", std::numeric_limits<int32_t>::min()},
+        HexSignedIntData{"0x7FFFFFFF", std::numeric_limits<int32_t>::max()},
+        HexSignedIntData{"0X7FFFFFFF", std::numeric_limits<int32_t>::max()}));
+
+TEST_F(LexerTest, HexPrefixOnly_IsError) {
+  // Could be the start of a hex integer or hex float, but is neither.
+  Source::File file("", "0x");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(),
+            "integer or float hex literal has no significant digits");
+}
+
+TEST_F(LexerTest, HexPrefixUpperCaseOnly_IsError) {
+  // Could be the start of a hex integer or hex float, but is neither.
+  Source::File file("", "0X");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(),
+            "integer or float hex literal has no significant digits");
+}
+
+TEST_F(LexerTest, NegativeHexPrefixOnly_IsError) {
+  // Could be the start of a hex integer or hex float, but is neither.
+  Source::File file("", "-0x");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(),
+            "integer or float hex literal has no significant digits");
+}
+
+TEST_F(LexerTest, NegativeHexPrefixUpperCaseOnly_IsError) {
+  // Could be the start of a hex integer or hex float, but is neither.
+  Source::File file("", "-0X");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(),
+            "integer or float hex literal has no significant digits");
+}
+
+TEST_F(LexerTest, IntegerTest_HexSignedTooLarge) {
+  Source::File file("", "0x80000000");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "i32 (0x80000000) too large");
+}
+
+TEST_F(LexerTest, IntegerTest_HexSignedTooSmall) {
+  Source::File file("", "-0x8000000F");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "i32 (-0x8000000F) too small");
+}
+
+TEST_F(LexerTest, IntegerTest_HexSignedTooManyDigits) {
+  {
+    Source::File file("", "-0x100000000000000000000000");
+    Lexer l(&file);
+
+    auto t = l.next();
+    ASSERT_TRUE(t.Is(Token::Type::kError));
+    EXPECT_EQ(t.to_str(),
+              "integer literal (-0x10000000...) has too many digits");
+  }
+  {
+    Source::File file("", "0x100000000000000");
+    Lexer l(&file);
+
+    auto t = l.next();
+    ASSERT_TRUE(t.Is(Token::Type::kError));
+    EXPECT_EQ(t.to_str(),
+              "integer literal (0x10000000...) has too many digits");
+  }
+}
+
+struct HexUnsignedIntData {
+  const char* input;
+  uint32_t result;
+};
+inline std::ostream& operator<<(std::ostream& out, HexUnsignedIntData data) {
+  out << std::string(data.input);
+  return out;
+}
+using IntegerTest_HexUnsigned = testing::TestWithParam<HexUnsignedIntData>;
+TEST_P(IntegerTest_HexUnsigned, Matches) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(Token::Type::kUintLiteral));
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+  EXPECT_EQ(t.to_u32(), params.result);
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    IntegerTest_HexUnsigned,
+    testing::Values(HexUnsignedIntData{"0x0u", 0},
+                    HexUnsignedIntData{"0x42u", 66},
+                    HexUnsignedIntData{"0xeF1Abc9u", 250719177},
+                    HexUnsignedIntData{"0x0u",
+                                       std::numeric_limits<uint32_t>::min()},
+                    HexUnsignedIntData{"0xFFFFFFFFu",
+                                       std::numeric_limits<uint32_t>::max()}));
+
+TEST_F(LexerTest, IntegerTest_HexUnsignedTooManyDigits) {
+  Source::File file("", "0x1000000000000000000000u");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "integer literal (0x10000000...) has too many digits");
+}
+
+struct UnsignedIntData {
+  const char* input;
+  uint32_t result;
+};
+inline std::ostream& operator<<(std::ostream& out, UnsignedIntData data) {
+  out << std::string(data.input);
+  return out;
+}
+using IntegerTest_Unsigned = testing::TestWithParam<UnsignedIntData>;
+TEST_P(IntegerTest_Unsigned, Matches) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(Token::Type::kUintLiteral));
+  EXPECT_EQ(t.to_u32(), params.result);
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+}
+INSTANTIATE_TEST_SUITE_P(LexerTest,
+                         IntegerTest_Unsigned,
+                         testing::Values(UnsignedIntData{"0u", 0u},
+                                         UnsignedIntData{"123u", 123u},
+                                         UnsignedIntData{"4294967295u",
+                                                         4294967295u}));
+
+TEST_F(LexerTest, IntegerTest_UnsignedTooManyDigits) {
+  Source::File file("", "10000000000000000000000u");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "integer literal (1000000000...) has too many digits");
+}
+
+struct SignedIntData {
+  const char* input;
+  int32_t result;
+};
+inline std::ostream& operator<<(std::ostream& out, SignedIntData data) {
+  out << std::string(data.input);
+  return out;
+}
+using IntegerTest_Signed = testing::TestWithParam<SignedIntData>;
+TEST_P(IntegerTest_Signed, Matches) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(Token::Type::kSintLiteral));
+  EXPECT_EQ(t.to_i32(), params.result);
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    IntegerTest_Signed,
+    testing::Values(SignedIntData{"0", 0},
+                    SignedIntData{"-2", -2},
+                    SignedIntData{"2", 2},
+                    SignedIntData{"123", 123},
+                    SignedIntData{"2147483647", 2147483647},
+                    SignedIntData{"-2147483648", -2147483648LL}));
+
+TEST_F(LexerTest, IntegerTest_SignedTooManyDigits) {
+  Source::File file("", "-10000000000000000");
+  Lexer l(&file);
+
+  auto t = l.next();
+  ASSERT_TRUE(t.Is(Token::Type::kError));
+  EXPECT_EQ(t.to_str(), "integer literal (-1000000000...) has too many digits");
+}
+
+using IntegerTest_Invalid = testing::TestWithParam<const char*>;
+TEST_P(IntegerTest_Invalid, Parses) {
+  Source::File file("", GetParam());
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_FALSE(t.Is(Token::Type::kSintLiteral));
+  EXPECT_FALSE(t.Is(Token::Type::kUintLiteral));
+}
+INSTANTIATE_TEST_SUITE_P(LexerTest,
+                         IntegerTest_Invalid,
+                         testing::Values("2147483648",
+                                         "4294967296u",
+                                         "01234",
+                                         "0000",
+                                         "-00",
+                                         "00u"));
+
+struct TokenData {
+  const char* input;
+  Token::Type type;
+};
+inline std::ostream& operator<<(std::ostream& out, TokenData data) {
+  out << std::string(data.input);
+  return out;
+}
+using PunctuationTest = testing::TestWithParam<TokenData>;
+TEST_P(PunctuationTest, Parses) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(params.type));
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+
+  t = l.next();
+  EXPECT_EQ(t.source().range.begin.column,
+            1 + std::string(params.input).size());
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    PunctuationTest,
+    testing::Values(TokenData{"&", Token::Type::kAnd},
+                    TokenData{"&&", Token::Type::kAndAnd},
+                    TokenData{"->", Token::Type::kArrow},
+                    TokenData{"@", Token::Type::kAttr},
+                    TokenData{"[[", Token::Type::kAttrLeft},
+                    TokenData{"]]", Token::Type::kAttrRight},
+                    TokenData{"/", Token::Type::kForwardSlash},
+                    TokenData{"!", Token::Type::kBang},
+                    TokenData{"[", Token::Type::kBracketLeft},
+                    TokenData{"]", Token::Type::kBracketRight},
+                    TokenData{"{", Token::Type::kBraceLeft},
+                    TokenData{"}", Token::Type::kBraceRight},
+                    TokenData{":", Token::Type::kColon},
+                    TokenData{",", Token::Type::kComma},
+                    TokenData{"=", Token::Type::kEqual},
+                    TokenData{"==", Token::Type::kEqualEqual},
+                    TokenData{">", Token::Type::kGreaterThan},
+                    TokenData{">=", Token::Type::kGreaterThanEqual},
+                    TokenData{">>", Token::Type::kShiftRight},
+                    TokenData{"<", Token::Type::kLessThan},
+                    TokenData{"<=", Token::Type::kLessThanEqual},
+                    TokenData{"<<", Token::Type::kShiftLeft},
+                    TokenData{"%", Token::Type::kMod},
+                    TokenData{"!=", Token::Type::kNotEqual},
+                    TokenData{"-", Token::Type::kMinus},
+                    TokenData{"--", Token::Type::kMinusMinus},
+                    TokenData{".", Token::Type::kPeriod},
+                    TokenData{"+", Token::Type::kPlus},
+                    TokenData{"++", Token::Type::kPlusPlus},
+                    TokenData{"|", Token::Type::kOr},
+                    TokenData{"||", Token::Type::kOrOr},
+                    TokenData{"(", Token::Type::kParenLeft},
+                    TokenData{")", Token::Type::kParenRight},
+                    TokenData{";", Token::Type::kSemicolon},
+                    TokenData{"*", Token::Type::kStar},
+                    TokenData{"~", Token::Type::kTilde},
+                    TokenData{"_", Token::Type::kUnderscore},
+                    TokenData{"^", Token::Type::kXor}));
+
+using KeywordTest = testing::TestWithParam<TokenData>;
+TEST_P(KeywordTest, Parses) {
+  auto params = GetParam();
+  Source::File file("", params.input);
+  Lexer l(&file);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.Is(params.type)) << params.input;
+  EXPECT_EQ(t.source().range.begin.line, 1u);
+  EXPECT_EQ(t.source().range.begin.column, 1u);
+  EXPECT_EQ(t.source().range.end.line, 1u);
+  EXPECT_EQ(t.source().range.end.column, 1u + strlen(params.input));
+
+  t = l.next();
+  EXPECT_EQ(t.source().range.begin.column,
+            1 + std::string(params.input).size());
+}
+INSTANTIATE_TEST_SUITE_P(
+    LexerTest,
+    KeywordTest,
+    testing::Values(
+        TokenData{"array", Token::Type::kArray},
+        TokenData{"bitcast", Token::Type::kBitcast},
+        TokenData{"bool", Token::Type::kBool},
+        TokenData{"break", Token::Type::kBreak},
+        TokenData{"case", Token::Type::kCase},
+        TokenData{"continue", Token::Type::kContinue},
+        TokenData{"continuing", Token::Type::kContinuing},
+        TokenData{"default", Token::Type::kDefault},
+        TokenData{"discard", Token::Type::kDiscard},
+        TokenData{"else", Token::Type::kElse},
+        TokenData{"elseif", Token::Type::kElseIf},
+        TokenData{"f32", Token::Type::kF32},
+        TokenData{"fallthrough", Token::Type::kFallthrough},
+        TokenData{"false", Token::Type::kFalse},
+        TokenData{"fn", Token::Type::kFn},
+        TokenData{"for", Token::Type::kFor},
+        TokenData{"function", Token::Type::kFunction},
+        TokenData{"i32", Token::Type::kI32},
+        TokenData{"if", Token::Type::kIf},
+        TokenData{"import", Token::Type::kImport},
+        TokenData{"let", Token::Type::kLet},
+        TokenData{"loop", Token::Type::kLoop},
+        TokenData{"mat2x2", Token::Type::kMat2x2},
+        TokenData{"mat2x3", Token::Type::kMat2x3},
+        TokenData{"mat2x4", Token::Type::kMat2x4},
+        TokenData{"mat3x2", Token::Type::kMat3x2},
+        TokenData{"mat3x3", Token::Type::kMat3x3},
+        TokenData{"mat3x4", Token::Type::kMat3x4},
+        TokenData{"mat4x2", Token::Type::kMat4x2},
+        TokenData{"mat4x3", Token::Type::kMat4x3},
+        TokenData{"mat4x4", Token::Type::kMat4x4},
+        TokenData{"override", Token::Type::kOverride},
+        TokenData{"private", Token::Type::kPrivate},
+        TokenData{"ptr", Token::Type::kPtr},
+        TokenData{"return", Token::Type::kReturn},
+        TokenData{"sampler", Token::Type::kSampler},
+        TokenData{"sampler_comparison", Token::Type::kComparisonSampler},
+        TokenData{"storage", Token::Type::kStorage},
+        TokenData{"storage_buffer", Token::Type::kStorage},
+        TokenData{"struct", Token::Type::kStruct},
+        TokenData{"switch", Token::Type::kSwitch},
+        TokenData{"texture_1d", Token::Type::kTextureSampled1d},
+        TokenData{"texture_2d", Token::Type::kTextureSampled2d},
+        TokenData{"texture_2d_array", Token::Type::kTextureSampled2dArray},
+        TokenData{"texture_3d", Token::Type::kTextureSampled3d},
+        TokenData{"texture_cube", Token::Type::kTextureSampledCube},
+        TokenData{"texture_cube_array", Token::Type::kTextureSampledCubeArray},
+        TokenData{"texture_depth_2d", Token::Type::kTextureDepth2d},
+        TokenData{"texture_depth_2d_array", Token::Type::kTextureDepth2dArray},
+        TokenData{"texture_depth_cube", Token::Type::kTextureDepthCube},
+        TokenData{"texture_depth_cube_array",
+                  Token::Type::kTextureDepthCubeArray},
+        TokenData{"texture_depth_multisampled_2d",
+                  Token::Type::kTextureDepthMultisampled2d},
+        TokenData{"texture_multisampled_2d",
+                  Token::Type::kTextureMultisampled2d},
+        TokenData{"texture_storage_1d", Token::Type::kTextureStorage1d},
+        TokenData{"texture_storage_2d", Token::Type::kTextureStorage2d},
+        TokenData{"texture_storage_2d_array",
+                  Token::Type::kTextureStorage2dArray},
+        TokenData{"texture_storage_3d", Token::Type::kTextureStorage3d},
+        TokenData{"true", Token::Type::kTrue},
+        TokenData{"type", Token::Type::kType},
+        TokenData{"u32", Token::Type::kU32},
+        TokenData{"uniform", Token::Type::kUniform},
+        TokenData{"var", Token::Type::kVar},
+        TokenData{"vec2", Token::Type::kVec2},
+        TokenData{"vec3", Token::Type::kVec3},
+        TokenData{"vec4", Token::Type::kVec4},
+        TokenData{"workgroup", Token::Type::kWorkgroup}));
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser.cc b/src/tint/reader/wgsl/parser.cc
new file mode 100644
index 0000000..d5e1158
--- /dev/null
+++ b/src/tint/reader/wgsl/parser.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 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/reader/wgsl/parser.h"
+
+#include <utility>
+
+#include "src/tint/reader/wgsl/parser_impl.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+Program Parse(Source::File const* file) {
+  ParserImpl parser(file);
+  parser.Parse();
+  return Program(std::move(parser.builder()));
+}
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser.h b/src/tint/reader/wgsl/parser.h
new file mode 100644
index 0000000..f0e8e3e
--- /dev/null
+++ b/src/tint/reader/wgsl/parser.h
@@ -0,0 +1,36 @@
+// Copyright 2020 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_READER_WGSL_PARSER_H_
+#define SRC_TINT_READER_WGSL_PARSER_H_
+
+#include "src/tint/program.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+/// Parses the WGSL source, returning the parsed program.
+/// If the source fails to parse then the returned
+/// `program.Diagnostics.contains_errors()` will be true, and the
+/// `program.Diagnostics()` will describe the error.
+/// @param file the source file
+/// @returns the parsed program
+Program Parse(Source::File const* file);
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_PARSER_H_
diff --git a/src/tint/reader/wgsl/parser_bench.cc b/src/tint/reader/wgsl/parser_bench.cc
new file mode 100644
index 0000000..471bba0
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_bench.cc
@@ -0,0 +1,40 @@
+// 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 <string>
+
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::reader::wgsl {
+namespace {
+
+void ParseWGSL(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadInputFile(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& file = std::get<Source::File>(res);
+  for (auto _ : state) {
+    auto res = Parse(&file);
+    if (res.Diagnostics().contains_errors()) {
+      state.SkipWithError(res.Diagnostics().str().c_str());
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(ParseWGSL);
+
+}  // namespace
+}  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
new file mode 100644
index 0000000..fa43f1f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -0,0 +1,3364 @@
+// Copyright 2020 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/reader/wgsl/parser_impl.h"
+
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/external_texture.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/invariant_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/reader/wgsl/lexer.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+template <typename T>
+using Expect = ParserImpl::Expect<T>;
+
+template <typename T>
+using Maybe = ParserImpl::Maybe<T>;
+
+/// Controls the maximum number of times we'll call into the sync() and
+/// unary_expression() functions from themselves. This is to guard against stack
+/// overflow when there is an excessive number of blocks.
+constexpr uint32_t kMaxParseDepth = 128;
+
+/// The maximum number of tokens to look ahead to try and sync the
+/// parser on error.
+constexpr size_t const kMaxResynchronizeLookahead = 32;
+
+const char kVertexStage[] = "vertex";
+const char kFragmentStage[] = "fragment";
+const char kComputeStage[] = "compute";
+
+const char kReadAccess[] = "read";
+const char kWriteAccess[] = "write";
+const char kReadWriteAccess[] = "read_write";
+
+ast::Builtin ident_to_builtin(std::string_view str) {
+  if (str == "position") {
+    return ast::Builtin::kPosition;
+  }
+  if (str == "vertex_index") {
+    return ast::Builtin::kVertexIndex;
+  }
+  if (str == "instance_index") {
+    return ast::Builtin::kInstanceIndex;
+  }
+  if (str == "front_facing") {
+    return ast::Builtin::kFrontFacing;
+  }
+  if (str == "frag_depth") {
+    return ast::Builtin::kFragDepth;
+  }
+  if (str == "local_invocation_id") {
+    return ast::Builtin::kLocalInvocationId;
+  }
+  if (str == "local_invocation_idx" || str == "local_invocation_index") {
+    return ast::Builtin::kLocalInvocationIndex;
+  }
+  if (str == "global_invocation_id") {
+    return ast::Builtin::kGlobalInvocationId;
+  }
+  if (str == "workgroup_id") {
+    return ast::Builtin::kWorkgroupId;
+  }
+  if (str == "num_workgroups") {
+    return ast::Builtin::kNumWorkgroups;
+  }
+  if (str == "sample_index") {
+    return ast::Builtin::kSampleIndex;
+  }
+  if (str == "sample_mask") {
+    return ast::Builtin::kSampleMask;
+  }
+  return ast::Builtin::kNone;
+}
+
+const char kBindingAttribute[] = "binding";
+const char kBlockAttribute[] = "block";
+const char kBuiltinAttribute[] = "builtin";
+const char kGroupAttribute[] = "group";
+const char kIdAttribute[] = "id";
+const char kInterpolateAttribute[] = "interpolate";
+const char kInvariantAttribute[] = "invariant";
+const char kLocationAttribute[] = "location";
+const char kSizeAttribute[] = "size";
+const char kAlignAttribute[] = "align";
+const char kStageAttribute[] = "stage";
+const char kStrideAttribute[] = "stride";
+const char kWorkgroupSizeAttribute[] = "workgroup_size";
+
+bool is_attribute(Token t) {
+  return t == kAlignAttribute || t == kBindingAttribute ||
+         t == kBlockAttribute || t == kBuiltinAttribute ||
+         t == kGroupAttribute || t == kIdAttribute ||
+         t == kInterpolateAttribute || t == kLocationAttribute ||
+         t == kSizeAttribute || t == kStageAttribute || t == kStrideAttribute ||
+         t == kWorkgroupSizeAttribute;
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl.html#reserved-keywords
+bool is_reserved(Token t) {
+  return t == "asm" || t == "bf16" || t == "const" || t == "do" ||
+         t == "enum" || t == "f16" || t == "f64" || t == "handle" ||
+         t == "i8" || t == "i16" || t == "i64" || t == "mat" ||
+         t == "premerge" || t == "regardless" || t == "typedef" || t == "u8" ||
+         t == "u16" || t == "u64" || t == "unless" || t == "using" ||
+         t == "vec" || t == "void" || t == "while";
+}
+
+/// Enter-exit counters for block token types.
+/// Used by sync_to() to skip over closing block tokens that were opened during
+/// the forward scan.
+struct BlockCounters {
+  int attrs = 0;    // [[ ]]
+  int brace = 0;    // {   }
+  int bracket = 0;  // [   ]
+  int paren = 0;    // (   )
+
+  /// @return the current enter-exit depth for the given block token type. If
+  /// `t` is not a block token type, then 0 is always returned.
+  int consume(const Token& t) {
+    if (t.Is(Token::Type::kAttrLeft))  // [DEPRECATED]
+      return attrs++;
+    if (t.Is(Token::Type::kAttrRight))  // [DEPRECATED]
+      return attrs--;
+    if (t.Is(Token::Type::kBraceLeft))
+      return brace++;
+    if (t.Is(Token::Type::kBraceRight))
+      return brace--;
+    if (t.Is(Token::Type::kBracketLeft))
+      return bracket++;
+    if (t.Is(Token::Type::kBracketRight))
+      return bracket--;
+    if (t.Is(Token::Type::kParenLeft))
+      return paren++;
+    if (t.Is(Token::Type::kParenRight))
+      return paren--;
+    return 0;
+  }
+};
+}  // namespace
+
+/// RAII helper that combines a Source on construction with the last token's
+/// source when implicitly converted to `Source`.
+class ParserImpl::MultiTokenSource {
+ public:
+  /// Constructor that starts with Source at the current peek position
+  /// @param parser the parser
+  explicit MultiTokenSource(ParserImpl* parser)
+      : MultiTokenSource(parser, parser->peek().source().Begin()) {}
+
+  /// Constructor that starts with the input `start` Source
+  /// @param parser the parser
+  /// @param start the start source of the range
+  MultiTokenSource(ParserImpl* parser, const Source& start)
+      : parser_(parser), start_(start) {}
+
+  /// Implicit conversion to Source that returns the combined source from start
+  /// to the current last token's source.
+  operator Source() const {
+    Source end = parser_->last_token().source().End();
+    if (end < start_) {
+      end = start_;
+    }
+    return Source::Combine(start_, end);
+  }
+
+ private:
+  ParserImpl* parser_;
+  Source start_;
+};
+
+ParserImpl::TypedIdentifier::TypedIdentifier() = default;
+
+ParserImpl::TypedIdentifier::TypedIdentifier(const TypedIdentifier&) = default;
+
+ParserImpl::TypedIdentifier::TypedIdentifier(const ast::Type* type_in,
+                                             std::string name_in,
+                                             Source source_in)
+    : type(type_in), name(std::move(name_in)), source(std::move(source_in)) {}
+
+ParserImpl::TypedIdentifier::~TypedIdentifier() = default;
+
+ParserImpl::FunctionHeader::FunctionHeader() = default;
+
+ParserImpl::FunctionHeader::FunctionHeader(const FunctionHeader&) = default;
+
+ParserImpl::FunctionHeader::FunctionHeader(Source src,
+                                           std::string n,
+                                           ast::VariableList p,
+                                           const ast::Type* ret_ty,
+                                           ast::AttributeList ret_attrs)
+    : source(src),
+      name(n),
+      params(p),
+      return_type(ret_ty),
+      return_type_attributes(ret_attrs) {}
+
+ParserImpl::FunctionHeader::~FunctionHeader() = default;
+
+ParserImpl::FunctionHeader& ParserImpl::FunctionHeader::operator=(
+    const FunctionHeader& rhs) = default;
+
+ParserImpl::VarDeclInfo::VarDeclInfo() = default;
+
+ParserImpl::VarDeclInfo::VarDeclInfo(const VarDeclInfo&) = default;
+
+ParserImpl::VarDeclInfo::VarDeclInfo(Source source_in,
+                                     std::string name_in,
+                                     ast::StorageClass storage_class_in,
+                                     ast::Access access_in,
+                                     const ast::Type* type_in)
+    : source(std::move(source_in)),
+      name(std::move(name_in)),
+      storage_class(storage_class_in),
+      access(access_in),
+      type(type_in) {}
+
+ParserImpl::VarDeclInfo::~VarDeclInfo() = default;
+
+ParserImpl::ParserImpl(Source::File const* file)
+    : lexer_(std::make_unique<Lexer>(file)) {}
+
+ParserImpl::~ParserImpl() = default;
+
+ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
+                                                   std::string_view err,
+                                                   std::string_view use) {
+  std::stringstream msg;
+  msg << err;
+  if (!use.empty()) {
+    msg << " for " << use;
+  }
+  add_error(source, msg.str());
+  return Failure::kErrored;
+}
+
+ParserImpl::Failure::Errored ParserImpl::add_error(const Token& t,
+                                                   const std::string& err) {
+  add_error(t.source(), err);
+  return Failure::kErrored;
+}
+
+ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
+                                                   const std::string& err) {
+  if (silence_errors_ == 0) {
+    builder_.Diagnostics().add_error(diag::System::Reader, err, source);
+  }
+  return Failure::kErrored;
+}
+
+void ParserImpl::deprecated(const Source& source, const std::string& msg) {
+  builder_.Diagnostics().add_warning(
+      diag::System::Reader, "use of deprecated language feature: " + msg,
+      source);
+}
+
+Token ParserImpl::next() {
+  if (!token_queue_.empty()) {
+    auto t = token_queue_.front();
+    token_queue_.pop_front();
+    last_token_ = t;
+    return last_token_;
+  }
+  last_token_ = lexer_->next();
+  return last_token_;
+}
+
+Token ParserImpl::peek(size_t idx) {
+  while (token_queue_.size() < (idx + 1))
+    token_queue_.push_back(lexer_->next());
+  return token_queue_[idx];
+}
+
+bool ParserImpl::peek_is(Token::Type tok, size_t idx) {
+  return peek(idx).Is(tok);
+}
+
+Token ParserImpl::last_token() const {
+  return last_token_;
+}
+
+bool ParserImpl::Parse() {
+  translation_unit();
+  return !has_error();
+}
+
+// translation_unit
+//  : global_decl* EOF
+void ParserImpl::translation_unit() {
+  while (continue_parsing()) {
+    auto p = peek();
+    if (p.IsEof()) {
+      break;
+    }
+    expect_global_decl();
+    if (builder_.Diagnostics().error_count() >= max_errors_) {
+      add_error(Source{{}, p.source().file},
+                "stopping after " + std::to_string(max_errors_) + " errors");
+      break;
+    }
+  }
+}
+
+// global_decl
+//  : SEMICOLON
+//  | global_variable_decl SEMICLON
+//  | global_constant_decl SEMICOLON
+//  | type_alias SEMICOLON
+//  | struct_decl
+//  | function_decl
+Expect<bool> ParserImpl::expect_global_decl() {
+  if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF))
+    return true;
+
+  bool errored = false;
+
+  auto attrs = attribute_list();
+  if (attrs.errored)
+    errored = true;
+  if (!continue_parsing())
+    return Failure::kErrored;
+
+  auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> {
+    auto gv = global_variable_decl(attrs.value);
+    if (gv.errored)
+      return Failure::kErrored;
+    if (gv.matched) {
+      if (!expect("variable declaration", Token::Type::kSemicolon))
+        return Failure::kErrored;
+
+      builder_.AST().AddGlobalVariable(gv.value);
+      return true;
+    }
+
+    auto gc = global_constant_decl(attrs.value);
+    if (gc.errored)
+      return Failure::kErrored;
+
+    if (gc.matched) {
+      if (!expect("let declaration", Token::Type::kSemicolon))
+        return Failure::kErrored;
+
+      builder_.AST().AddGlobalVariable(gc.value);
+      return true;
+    }
+
+    auto ta = type_alias();
+    if (ta.errored)
+      return Failure::kErrored;
+
+    if (ta.matched) {
+      if (!expect("type alias", Token::Type::kSemicolon))
+        return Failure::kErrored;
+
+      builder_.AST().AddTypeDecl(ta.value);
+      return true;
+    }
+
+    auto str = struct_decl(attrs.value);
+    if (str.errored)
+      return Failure::kErrored;
+
+    if (str.matched) {
+      builder_.AST().AddTypeDecl(str.value);
+      return true;
+    }
+
+    return Failure::kNoMatch;
+  });
+
+  if (decl.errored) {
+    errored = true;
+  }
+  if (decl.matched) {
+    return expect_attributes_consumed(attrs.value);
+  }
+
+  auto func = function_decl(attrs.value);
+  if (func.errored) {
+    errored = true;
+  }
+  if (func.matched) {
+    builder_.AST().AddFunction(func.value);
+    return true;
+  }
+
+  if (errored) {
+    return Failure::kErrored;
+  }
+
+  // Invalid syntax found - try and determine the best error message
+
+  // We have attributes parsed, but nothing to consume them?
+  if (attrs.value.size() > 0) {
+    return add_error(next(), "expected declaration after attributes");
+  }
+
+  // We have a statement outside of a function?
+  auto t = peek();
+  auto stat = without_error([&] { return statement(); });
+  if (stat.matched) {
+    // Attempt to jump to the next '}' - the function might have just been
+    // missing an opening line.
+    sync_to(Token::Type::kBraceRight, true);
+    return add_error(t, "statement found outside of function body");
+  }
+  if (!stat.errored) {
+    // No match, no error - the parser might not have progressed.
+    // Ensure we always make _some_ forward progress.
+    next();
+  }
+
+  // The token might itself be an error.
+  if (t.IsError()) {
+    next();  // Consume it.
+    return add_error(t.source(), t.to_str());
+  }
+
+  // Exhausted all attempts to make sense of where we're at.
+  // Spew a generic error.
+
+  return add_error(t, "unexpected token");
+}
+
+// global_variable_decl
+//  : variable_attribute_list* variable_decl
+//  | variable_attribute_list* variable_decl EQUAL const_expr
+Maybe<const ast::Variable*> ParserImpl::global_variable_decl(
+    ast::AttributeList& attrs) {
+  auto decl = variable_decl();
+  if (decl.errored)
+    return Failure::kErrored;
+  if (!decl.matched)
+    return Failure::kNoMatch;
+
+  const ast::Expression* constructor = nullptr;
+  if (match(Token::Type::kEqual)) {
+    auto expr = expect_const_expr();
+    if (expr.errored)
+      return Failure::kErrored;
+    constructor = expr.value;
+  }
+
+  return create<ast::Variable>(
+      decl->source,                             // source
+      builder_.Symbols().Register(decl->name),  // symbol
+      decl->storage_class,                      // storage class
+      decl->access,                             // access control
+      decl->type,                               // type
+      false,                                    // is_const
+      false,                                    // is_overridable
+      constructor,                              // constructor
+      std::move(attrs));                        // attributes
+}
+
+// global_constant_decl :
+//  | LET (ident | variable_ident_decl) global_const_initializer
+//  | attribute* override (ident | variable_ident_decl) (equal expression)?
+// global_const_initializer
+//  : EQUAL const_expr
+Maybe<const ast::Variable*> ParserImpl::global_constant_decl(
+    ast::AttributeList& attrs) {
+  bool is_overridable = false;
+  const char* use = nullptr;
+  if (match(Token::Type::kLet)) {
+    use = "let declaration";
+  } else if (match(Token::Type::kOverride)) {
+    use = "override declaration";
+    is_overridable = true;
+  } else {
+    return Failure::kNoMatch;
+  }
+
+  auto decl = expect_variable_ident_decl(use, /* allow_inferred = */ true);
+  if (decl.errored)
+    return Failure::kErrored;
+
+  const ast::Expression* initializer = nullptr;
+  if (match(Token::Type::kEqual)) {
+    auto init = expect_const_expr();
+    if (init.errored) {
+      return Failure::kErrored;
+    }
+    initializer = std::move(init.value);
+  }
+
+  return create<ast::Variable>(
+      decl->source,                             // source
+      builder_.Symbols().Register(decl->name),  // symbol
+      ast::StorageClass::kNone,                 // storage class
+      ast::Access::kUndefined,                  // access control
+      decl->type,                               // type
+      true,                                     // is_const
+      is_overridable,                           // is_overridable
+      initializer,                              // constructor
+      std::move(attrs));                        // attributes
+}
+
+// variable_decl
+//   : VAR variable_qualifier? variable_ident_decl
+Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl(bool allow_inferred) {
+  Source source;
+  if (!match(Token::Type::kVar, &source))
+    return Failure::kNoMatch;
+
+  VariableQualifier vq;
+  auto explicit_vq = variable_qualifier();
+  if (explicit_vq.errored)
+    return Failure::kErrored;
+  if (explicit_vq.matched) {
+    vq = explicit_vq.value;
+  }
+
+  auto decl =
+      expect_variable_ident_decl("variable declaration", allow_inferred);
+  if (decl.errored)
+    return Failure::kErrored;
+
+  return VarDeclInfo{decl->source, decl->name, vq.storage_class, vq.access,
+                     decl->type};
+}
+
+// texture_sampler_types
+//  : sampler_type
+//  | depth_texture_type
+//  | sampled_texture_type LESS_THAN type_decl GREATER_THAN
+//  | multisampled_texture_type LESS_THAN type_decl GREATER_THAN
+//  | storage_texture_type LESS_THAN texel_format
+//                         COMMA access GREATER_THAN
+Maybe<const ast::Type*> ParserImpl::texture_sampler_types() {
+  auto type = sampler_type();
+  if (type.matched)
+    return type;
+
+  type = depth_texture_type();
+  if (type.matched)
+    return type;
+
+  type = external_texture_type();
+  if (type.matched)
+    return type.value;
+
+  auto source_range = make_source_range();
+
+  auto dim = sampled_texture_type();
+  if (dim.matched) {
+    const char* use = "sampled texture type";
+
+    auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (subtype.errored)
+      return Failure::kErrored;
+
+    return builder_.ty.sampled_texture(source_range, dim.value, subtype.value);
+  }
+
+  auto ms_dim = multisampled_texture_type();
+  if (ms_dim.matched) {
+    const char* use = "multisampled texture type";
+
+    auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (subtype.errored)
+      return Failure::kErrored;
+
+    return builder_.ty.multisampled_texture(source_range, ms_dim.value,
+                                            subtype.value);
+  }
+
+  auto storage = storage_texture_type();
+  if (storage.matched) {
+    const char* use = "storage texture type";
+    using StorageTextureInfo =
+        std::pair<tint::ast::TexelFormat, tint::ast::Access>;
+    auto params = expect_lt_gt_block(use, [&]() -> Expect<StorageTextureInfo> {
+      auto format = expect_texel_format(use);
+      if (format.errored) {
+        return Failure::kErrored;
+      }
+
+      if (!expect("access control", Token::Type::kComma)) {
+        return Failure::kErrored;
+      }
+
+      auto access = expect_access("access control");
+      if (access.errored) {
+        return Failure::kErrored;
+      }
+
+      return std::make_pair(format.value, access.value);
+    });
+
+    if (params.errored) {
+      return Failure::kErrored;
+    }
+
+    return builder_.ty.storage_texture(source_range, storage.value,
+                                       params->first, params->second);
+  }
+
+  return Failure::kNoMatch;
+}
+
+// sampler_type
+//  : SAMPLER
+//  | SAMPLER_COMPARISON
+Maybe<const ast::Type*> ParserImpl::sampler_type() {
+  Source source;
+  if (match(Token::Type::kSampler, &source))
+    return builder_.ty.sampler(source, ast::SamplerKind::kSampler);
+
+  if (match(Token::Type::kComparisonSampler, &source))
+    return builder_.ty.sampler(source, ast::SamplerKind::kComparisonSampler);
+
+  return Failure::kNoMatch;
+}
+
+// sampled_texture_type
+//  : TEXTURE_SAMPLED_1D
+//  | TEXTURE_SAMPLED_2D
+//  | TEXTURE_SAMPLED_2D_ARRAY
+//  | TEXTURE_SAMPLED_3D
+//  | TEXTURE_SAMPLED_CUBE
+//  | TEXTURE_SAMPLED_CUBE_ARRAY
+Maybe<const ast::TextureDimension> ParserImpl::sampled_texture_type() {
+  if (match(Token::Type::kTextureSampled1d))
+    return ast::TextureDimension::k1d;
+
+  if (match(Token::Type::kTextureSampled2d))
+    return ast::TextureDimension::k2d;
+
+  if (match(Token::Type::kTextureSampled2dArray))
+    return ast::TextureDimension::k2dArray;
+
+  if (match(Token::Type::kTextureSampled3d))
+    return ast::TextureDimension::k3d;
+
+  if (match(Token::Type::kTextureSampledCube))
+    return ast::TextureDimension::kCube;
+
+  if (match(Token::Type::kTextureSampledCubeArray))
+    return ast::TextureDimension::kCubeArray;
+
+  return Failure::kNoMatch;
+}
+
+// external_texture_type
+//  : TEXTURE_EXTERNAL
+Maybe<const ast::Type*> ParserImpl::external_texture_type() {
+  Source source;
+  if (match(Token::Type::kTextureExternal, &source)) {
+    return builder_.ty.external_texture(source);
+  }
+
+  return Failure::kNoMatch;
+}
+
+// multisampled_texture_type
+//  : TEXTURE_MULTISAMPLED_2D
+Maybe<const ast::TextureDimension> ParserImpl::multisampled_texture_type() {
+  if (match(Token::Type::kTextureMultisampled2d))
+    return ast::TextureDimension::k2d;
+
+  return Failure::kNoMatch;
+}
+
+// storage_texture_type
+//  : TEXTURE_STORAGE_1D
+//  | TEXTURE_STORAGE_2D
+//  | TEXTURE_STORAGE_2D_ARRAY
+//  | TEXTURE_STORAGE_3D
+Maybe<const ast::TextureDimension> ParserImpl::storage_texture_type() {
+  if (match(Token::Type::kTextureStorage1d))
+    return ast::TextureDimension::k1d;
+  if (match(Token::Type::kTextureStorage2d))
+    return ast::TextureDimension::k2d;
+  if (match(Token::Type::kTextureStorage2dArray))
+    return ast::TextureDimension::k2dArray;
+  if (match(Token::Type::kTextureStorage3d))
+    return ast::TextureDimension::k3d;
+
+  return Failure::kNoMatch;
+}
+
+// depth_texture_type
+//  : TEXTURE_DEPTH_2D
+//  | TEXTURE_DEPTH_2D_ARRAY
+//  | TEXTURE_DEPTH_CUBE
+//  | TEXTURE_DEPTH_CUBE_ARRAY
+//  | TEXTURE_DEPTH_MULTISAMPLED_2D
+Maybe<const ast::Type*> ParserImpl::depth_texture_type() {
+  Source source;
+  if (match(Token::Type::kTextureDepth2d, &source)) {
+    return builder_.ty.depth_texture(source, ast::TextureDimension::k2d);
+  }
+  if (match(Token::Type::kTextureDepth2dArray, &source)) {
+    return builder_.ty.depth_texture(source, ast::TextureDimension::k2dArray);
+  }
+  if (match(Token::Type::kTextureDepthCube, &source)) {
+    return builder_.ty.depth_texture(source, ast::TextureDimension::kCube);
+  }
+  if (match(Token::Type::kTextureDepthCubeArray, &source)) {
+    return builder_.ty.depth_texture(source, ast::TextureDimension::kCubeArray);
+  }
+  if (match(Token::Type::kTextureDepthMultisampled2d, &source)) {
+    return builder_.ty.depth_multisampled_texture(source,
+                                                  ast::TextureDimension::k2d);
+  }
+  return Failure::kNoMatch;
+}
+
+// texel_format
+//  : 'rgba8unorm'
+//  | 'rgba8snorm'
+//  | 'rgba8uint'
+//  | 'rgba8sint'
+//  | 'rgba16uint'
+//  | 'rgba16sint'
+//  | 'rgba16float'
+//  | 'r32uint'
+//  | 'r32sint'
+//  | 'r32float'
+//  | 'rg32uint'
+//  | 'rg32sint'
+//  | 'rg32float'
+//  | 'rgba32uint'
+//  | 'rgba32sint'
+//  | 'rgba32float'
+Expect<ast::TexelFormat> ParserImpl::expect_texel_format(std::string_view use) {
+  auto t = next();
+  if (t == "rgba8unorm") {
+    return ast::TexelFormat::kRgba8Unorm;
+  }
+  if (t == "rgba8snorm") {
+    return ast::TexelFormat::kRgba8Snorm;
+  }
+  if (t == "rgba8uint") {
+    return ast::TexelFormat::kRgba8Uint;
+  }
+  if (t == "rgba8sint") {
+    return ast::TexelFormat::kRgba8Sint;
+  }
+  if (t == "rgba16uint") {
+    return ast::TexelFormat::kRgba16Uint;
+  }
+  if (t == "rgba16sint") {
+    return ast::TexelFormat::kRgba16Sint;
+  }
+  if (t == "rgba16float") {
+    return ast::TexelFormat::kRgba16Float;
+  }
+  if (t == "r32uint") {
+    return ast::TexelFormat::kR32Uint;
+  }
+  if (t == "r32sint") {
+    return ast::TexelFormat::kR32Sint;
+  }
+  if (t == "r32float") {
+    return ast::TexelFormat::kR32Float;
+  }
+  if (t == "rg32uint") {
+    return ast::TexelFormat::kRg32Uint;
+  }
+  if (t == "rg32sint") {
+    return ast::TexelFormat::kRg32Sint;
+  }
+  if (t == "rg32float") {
+    return ast::TexelFormat::kRg32Float;
+  }
+  if (t == "rgba32uint") {
+    return ast::TexelFormat::kRgba32Uint;
+  }
+  if (t == "rgba32sint") {
+    return ast::TexelFormat::kRgba32Sint;
+  }
+  if (t == "rgba32float") {
+    return ast::TexelFormat::kRgba32Float;
+  }
+  return add_error(t.source(), "invalid format", use);
+}
+
+// variable_ident_decl
+//   : IDENT COLON variable_attribute_list* type_decl
+Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
+    std::string_view use,
+    bool allow_inferred) {
+  auto ident = expect_ident(use);
+  if (ident.errored)
+    return Failure::kErrored;
+
+  if (allow_inferred && !peek_is(Token::Type::kColon)) {
+    return TypedIdentifier{nullptr, ident.value, ident.source};
+  }
+
+  if (!expect(use, Token::Type::kColon))
+    return Failure::kErrored;
+
+  auto attrs = attribute_list();
+  if (attrs.errored)
+    return Failure::kErrored;
+
+  auto t = peek();
+  auto type = type_decl(attrs.value);
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return add_error(t.source(), "invalid type", use);
+
+  if (!expect_attributes_consumed(attrs.value))
+    return Failure::kErrored;
+
+  return TypedIdentifier{type.value, ident.value, ident.source};
+}
+
+Expect<ast::Access> ParserImpl::expect_access(std::string_view use) {
+  auto ident = expect_ident(use);
+  if (ident.errored)
+    return Failure::kErrored;
+
+  if (ident.value == kReadAccess)
+    return {ast::Access::kRead, ident.source};
+  if (ident.value == kWriteAccess)
+    return {ast::Access::kWrite, ident.source};
+  if (ident.value == kReadWriteAccess)
+    return {ast::Access::kReadWrite, ident.source};
+
+  return add_error(ident.source, "invalid value for access control");
+}
+
+// variable_qualifier
+//   : LESS_THAN storage_class (COMMA access_mode)? GREATER_THAN
+Maybe<ParserImpl::VariableQualifier> ParserImpl::variable_qualifier() {
+  if (!peek_is(Token::Type::kLessThan)) {
+    return Failure::kNoMatch;
+  }
+
+  auto* use = "variable declaration";
+  auto vq = expect_lt_gt_block(use, [&]() -> Expect<VariableQualifier> {
+    auto source = make_source_range();
+    auto sc = expect_storage_class(use);
+    if (sc.errored) {
+      return Failure::kErrored;
+    }
+    if (match(Token::Type::kComma)) {
+      auto ac = expect_access(use);
+      if (ac.errored) {
+        return Failure::kErrored;
+      }
+      return VariableQualifier{sc.value, ac.value};
+    }
+    return Expect<VariableQualifier>{
+        VariableQualifier{sc.value, ast::Access::kUndefined}, source};
+  });
+
+  if (vq.errored) {
+    return Failure::kErrored;
+  }
+
+  return vq;
+}
+
+// type_alias
+//   : TYPE IDENT EQUAL type_decl
+Maybe<const ast::Alias*> ParserImpl::type_alias() {
+  if (!peek_is(Token::Type::kType))
+    return Failure::kNoMatch;
+
+  auto t = next();
+  const char* use = "type alias";
+
+  auto name = expect_ident(use);
+  if (name.errored)
+    return Failure::kErrored;
+
+  if (!expect(use, Token::Type::kEqual))
+    return Failure::kErrored;
+
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return add_error(peek(), "invalid type alias");
+
+  return builder_.ty.alias(make_source_range_from(t.source()), name.value,
+                           type.value);
+}
+
+// type_decl
+//   : IDENTIFIER
+//   | BOOL
+//   | FLOAT32
+//   | INT32
+//   | UINT32
+//   | VEC2 LESS_THAN type_decl GREATER_THAN
+//   | VEC3 LESS_THAN type_decl GREATER_THAN
+//   | VEC4 LESS_THAN type_decl GREATER_THAN
+//   | PTR LESS_THAN storage_class, type_decl (COMMA access_mode)? GREATER_THAN
+//   | array_attribute_list* ARRAY LESS_THAN type_decl COMMA
+//          INT_LITERAL GREATER_THAN
+//   | array_attribute_list* ARRAY LESS_THAN type_decl
+//          GREATER_THAN
+//   | MAT2x2 LESS_THAN type_decl GREATER_THAN
+//   | MAT2x3 LESS_THAN type_decl GREATER_THAN
+//   | MAT2x4 LESS_THAN type_decl GREATER_THAN
+//   | MAT3x2 LESS_THAN type_decl GREATER_THAN
+//   | MAT3x3 LESS_THAN type_decl GREATER_THAN
+//   | MAT3x4 LESS_THAN type_decl GREATER_THAN
+//   | MAT4x2 LESS_THAN type_decl GREATER_THAN
+//   | MAT4x3 LESS_THAN type_decl GREATER_THAN
+//   | MAT4x4 LESS_THAN type_decl GREATER_THAN
+//   | texture_sampler_types
+Maybe<const ast::Type*> ParserImpl::type_decl() {
+  auto attrs = attribute_list();
+  if (attrs.errored)
+    return Failure::kErrored;
+
+  auto type = type_decl(attrs.value);
+  if (type.errored) {
+    return Failure::kErrored;
+  }
+  if (!expect_attributes_consumed(attrs.value)) {
+    return Failure::kErrored;
+  }
+  if (!type.matched) {
+    return Failure::kNoMatch;
+  }
+
+  return type;
+}
+
+Maybe<const ast::Type*> ParserImpl::type_decl(ast::AttributeList& attrs) {
+  auto t = peek();
+  Source source;
+  if (match(Token::Type::kIdentifier, &source)) {
+    return builder_.create<ast::TypeName>(
+        source, builder_.Symbols().Register(t.to_str()));
+  }
+
+  if (match(Token::Type::kBool, &source))
+    return builder_.ty.bool_(source);
+
+  if (match(Token::Type::kF32, &source))
+    return builder_.ty.f32(source);
+
+  if (match(Token::Type::kI32, &source))
+    return builder_.ty.i32(source);
+
+  if (match(Token::Type::kU32, &source))
+    return builder_.ty.u32(source);
+
+  if (t.IsVector()) {
+    next();  // Consume the peek
+    return expect_type_decl_vector(t);
+  }
+
+  if (match(Token::Type::kPtr)) {
+    return expect_type_decl_pointer(t);
+  }
+
+  if (match(Token::Type::kAtomic)) {
+    return expect_type_decl_atomic(t);
+  }
+
+  if (match(Token::Type::kArray, &source)) {
+    return expect_type_decl_array(t, std::move(attrs));
+  }
+
+  if (t.IsMatrix()) {
+    next();  // Consume the peek
+    return expect_type_decl_matrix(t);
+  }
+
+  auto texture_or_sampler = texture_sampler_types();
+  if (texture_or_sampler.errored)
+    return Failure::kErrored;
+  if (texture_or_sampler.matched)
+    return texture_or_sampler;
+
+  return Failure::kNoMatch;
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return add_error(peek().source(), "invalid type", use);
+  return type.value;
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type_decl_pointer(Token t) {
+  const char* use = "ptr declaration";
+
+  auto storage_class = ast::StorageClass::kNone;
+  auto access = ast::Access::kUndefined;
+
+  auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
+    auto sc = expect_storage_class(use);
+    if (sc.errored) {
+      return Failure::kErrored;
+    }
+    storage_class = sc.value;
+
+    if (!expect(use, Token::Type::kComma)) {
+      return Failure::kErrored;
+    }
+
+    auto type = expect_type(use);
+    if (type.errored) {
+      return Failure::kErrored;
+    }
+
+    if (match(Token::Type::kComma)) {
+      auto ac = expect_access("access control");
+      if (ac.errored) {
+        return Failure::kErrored;
+      }
+      access = ac.value;
+    }
+
+    return type.value;
+  });
+
+  if (subtype.errored) {
+    return Failure::kErrored;
+  }
+
+  return builder_.ty.pointer(make_source_range_from(t.source()), subtype.value,
+                             storage_class, access);
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(Token t) {
+  const char* use = "atomic declaration";
+
+  auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
+  if (subtype.errored) {
+    return Failure::kErrored;
+  }
+
+  return builder_.ty.atomic(make_source_range_from(t.source()), subtype.value);
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(Token t) {
+  uint32_t count = 2;
+  if (t.Is(Token::Type::kVec3)) {
+    count = 3;
+  } else if (t.Is(Token::Type::kVec4)) {
+    count = 4;
+  }
+
+  const ast::Type* subtype = nullptr;
+  if (peek_is(Token::Type::kLessThan)) {
+    const char* use = "vector";
+    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (ty.errored) {
+      return Failure::kErrored;
+    }
+    subtype = ty.value;
+  }
+
+  return builder_.ty.vec(make_source_range_from(t.source()), subtype, count);
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type_decl_array(
+    Token t,
+    ast::AttributeList attrs) {
+  const char* use = "array declaration";
+
+  const ast::Expression* size = nullptr;
+
+  auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
+    auto type = expect_type(use);
+    if (type.errored)
+      return Failure::kErrored;
+
+    if (match(Token::Type::kComma)) {
+      auto expr = primary_expression();
+      if (expr.errored) {
+        return Failure::kErrored;
+      } else if (!expr.matched) {
+        return add_error(peek(), "expected array size expression");
+      }
+
+      size = std::move(expr.value);
+    }
+
+    return type.value;
+  });
+
+  if (subtype.errored) {
+    return Failure::kErrored;
+  }
+
+  return builder_.ty.array(make_source_range_from(t.source()), subtype.value,
+                           size, std::move(attrs));
+}
+
+Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(Token t) {
+  uint32_t rows = 2;
+  uint32_t columns = 2;
+  if (t.IsMat3xN()) {
+    columns = 3;
+  } else if (t.IsMat4xN()) {
+    columns = 4;
+  }
+  if (t.IsMatNx3()) {
+    rows = 3;
+  } else if (t.IsMatNx4()) {
+    rows = 4;
+  }
+
+  const ast::Type* subtype = nullptr;
+  if (peek_is(Token::Type::kLessThan)) {
+    const char* use = "matrix";
+    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (ty.errored) {
+      return Failure::kErrored;
+    }
+    subtype = ty.value;
+  }
+
+  return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns,
+                         rows);
+}
+
+// storage_class
+//  : INPUT
+//  | OUTPUT
+//  | UNIFORM
+//  | WORKGROUP
+//  | STORAGE
+//  | PRIVATE
+//  | FUNCTION
+Expect<ast::StorageClass> ParserImpl::expect_storage_class(
+    std::string_view use) {
+  auto source = peek().source();
+
+  if (match(Token::Type::kUniform))
+    return {ast::StorageClass::kUniform, source};
+
+  if (match(Token::Type::kWorkgroup))
+    return {ast::StorageClass::kWorkgroup, source};
+
+  if (match(Token::Type::kStorage))
+    return {ast::StorageClass::kStorage, source};
+
+  if (match(Token::Type::kPrivate))
+    return {ast::StorageClass::kPrivate, source};
+
+  if (match(Token::Type::kFunction))
+    return {ast::StorageClass::kFunction, source};
+
+  return add_error(source, "invalid storage class", use);
+}
+
+// struct_decl
+//   : struct_attribute_decl* STRUCT IDENT struct_body_decl
+Maybe<const ast::Struct*> ParserImpl::struct_decl(ast::AttributeList& attrs) {
+  auto t = peek();
+  auto source = t.source();
+
+  if (!match(Token::Type::kStruct))
+    return Failure::kNoMatch;
+
+  auto name = expect_ident("struct declaration");
+  if (name.errored)
+    return Failure::kErrored;
+
+  auto body = expect_struct_body_decl();
+  if (body.errored)
+    return Failure::kErrored;
+
+  auto sym = builder_.Symbols().Register(name.value);
+  return create<ast::Struct>(source, sym, std::move(body.value),
+                             std::move(attrs));
+}
+
+// struct_body_decl
+//   : BRACKET_LEFT struct_member* BRACKET_RIGHT
+Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
+  return expect_brace_block(
+      "struct declaration", [&]() -> Expect<ast::StructMemberList> {
+        bool errored = false;
+
+        ast::StructMemberList members;
+
+        while (continue_parsing() && !peek_is(Token::Type::kBraceRight) &&
+               !peek_is(Token::Type::kEOF)) {
+          auto member = sync(Token::Type::kSemicolon,
+                             [&]() -> Expect<ast::StructMember*> {
+                               auto attrs = attribute_list();
+                               if (attrs.errored) {
+                                 errored = true;
+                               }
+                               if (!synchronized_) {
+                                 return Failure::kErrored;
+                               }
+                               return expect_struct_member(attrs.value);
+                             });
+
+          if (member.errored) {
+            errored = true;
+          } else {
+            members.push_back(member.value);
+          }
+        }
+
+        if (errored)
+          return Failure::kErrored;
+
+        return members;
+      });
+}
+
+// struct_member
+//   : struct_member_attribute_decl+ variable_ident_decl SEMICOLON
+Expect<ast::StructMember*> ParserImpl::expect_struct_member(
+    ast::AttributeList& attrs) {
+  auto decl = expect_variable_ident_decl("struct member");
+  if (decl.errored)
+    return Failure::kErrored;
+
+  if (!expect("struct member", Token::Type::kSemicolon))
+    return Failure::kErrored;
+
+  return create<ast::StructMember>(decl->source,
+                                   builder_.Symbols().Register(decl->name),
+                                   decl->type, std::move(attrs));
+}
+
+// function_decl
+//   : function_header body_stmt
+Maybe<const ast::Function*> ParserImpl::function_decl(
+    ast::AttributeList& attrs) {
+  auto header = function_header();
+  if (header.errored) {
+    if (sync_to(Token::Type::kBraceLeft, /* consume: */ false)) {
+      // There were errors in the function header, but the parser has managed to
+      // resynchronize with the opening brace. As there's no outer
+      // synchronization token for function declarations, attempt to parse the
+      // function body. The AST isn't used as we've already errored, but this
+      // catches any errors inside the body, and can help keep the parser in
+      // sync.
+      expect_body_stmt();
+    }
+    return Failure::kErrored;
+  }
+  if (!header.matched)
+    return Failure::kNoMatch;
+
+  bool errored = false;
+
+  auto body = expect_body_stmt();
+  if (body.errored)
+    errored = true;
+
+  if (errored)
+    return Failure::kErrored;
+
+  return create<ast::Function>(
+      header->source, builder_.Symbols().Register(header->name), header->params,
+      header->return_type, body.value, attrs, header->return_type_attributes);
+}
+
+// function_header
+//   : FN IDENT PAREN_LEFT param_list PAREN_RIGHT return_type_decl_optional
+// return_type_decl_optional
+//   :
+//   | ARROW attribute_list* type_decl
+Maybe<ParserImpl::FunctionHeader> ParserImpl::function_header() {
+  Source source;
+  if (!match(Token::Type::kFn, &source)) {
+    return Failure::kNoMatch;
+  }
+
+  const char* use = "function declaration";
+  bool errored = false;
+
+  auto name = expect_ident(use);
+  if (name.errored) {
+    errored = true;
+    if (!sync_to(Token::Type::kParenLeft, /* consume: */ false)) {
+      return Failure::kErrored;
+    }
+  }
+
+  auto params = expect_paren_block(use, [&] { return expect_param_list(); });
+  if (params.errored) {
+    errored = true;
+    if (!synchronized_) {
+      return Failure::kErrored;
+    }
+  }
+
+  const ast::Type* return_type = nullptr;
+  ast::AttributeList return_attributes;
+
+  if (match(Token::Type::kArrow)) {
+    auto attrs = attribute_list();
+    if (attrs.errored) {
+      return Failure::kErrored;
+    }
+    return_attributes = attrs.value;
+
+    // Apply stride attributes to the type node instead of the function.
+    ast::AttributeList type_attributes;
+    auto itr =
+        std::find_if(return_attributes.begin(), return_attributes.end(),
+                     [](auto* attr) { return Is<ast::StrideAttribute>(attr); });
+    if (itr != return_attributes.end()) {
+      type_attributes.emplace_back(*itr);
+      return_attributes.erase(itr);
+    }
+
+    auto tok = peek();
+
+    auto type = type_decl(type_attributes);
+    if (type.errored) {
+      errored = true;
+    } else if (!type.matched) {
+      return add_error(peek(), "unable to determine function return type");
+    } else {
+      return_type = type.value;
+    }
+  } else {
+    return_type = builder_.ty.void_();
+  }
+
+  if (errored) {
+    return Failure::kErrored;
+  }
+
+  return FunctionHeader{source, name.value, std::move(params.value),
+                        return_type, std::move(return_attributes)};
+}
+
+// param_list
+//   :
+//   | (param COMMA)* param COMMA?
+Expect<ast::VariableList> ParserImpl::expect_param_list() {
+  ast::VariableList ret;
+  while (continue_parsing()) {
+    // Check for the end of the list.
+    auto t = peek();
+    if (!t.IsIdentifier() && !t.Is(Token::Type::kAttr) &&
+        !t.Is(Token::Type::kAttrLeft)) {
+      break;
+    }
+
+    auto param = expect_param();
+    if (param.errored)
+      return Failure::kErrored;
+    ret.push_back(param.value);
+
+    if (!match(Token::Type::kComma))
+      break;
+  }
+
+  return ret;
+}
+
+// param
+//   : attribute_list* variable_ident_decl
+Expect<ast::Variable*> ParserImpl::expect_param() {
+  auto attrs = attribute_list();
+
+  auto decl = expect_variable_ident_decl("parameter");
+  if (decl.errored)
+    return Failure::kErrored;
+
+  auto* var =
+      create<ast::Variable>(decl->source,                             // source
+                            builder_.Symbols().Register(decl->name),  // symbol
+                            ast::StorageClass::kNone,  // storage class
+                            ast::Access::kUndefined,   // access control
+                            decl->type,                // type
+                            true,                      // is_const
+                            false,                     // is_overridable
+                            nullptr,                   // constructor
+                            std::move(attrs.value));   // attributes
+  // Formal parameters are treated like a const declaration where the
+  // initializer value is provided by the call's argument.  The key point is
+  // that it's not updatable after initially set.  This is unlike C or GLSL
+  // which treat formal parameters like local variables that can be updated.
+
+  return var;
+}
+
+// pipeline_stage
+//   : VERTEX
+//   | FRAGMENT
+//   | COMPUTE
+Expect<ast::PipelineStage> ParserImpl::expect_pipeline_stage() {
+  auto t = peek();
+  if (t == kVertexStage) {
+    next();  // Consume the peek
+    return {ast::PipelineStage::kVertex, t.source()};
+  }
+  if (t == kFragmentStage) {
+    next();  // Consume the peek
+    return {ast::PipelineStage::kFragment, t.source()};
+  }
+  if (t == kComputeStage) {
+    next();  // Consume the peek
+    return {ast::PipelineStage::kCompute, t.source()};
+  }
+  return add_error(peek(), "invalid value for stage attribute");
+}
+
+Expect<ast::Builtin> ParserImpl::expect_builtin() {
+  auto ident = expect_ident("builtin");
+  if (ident.errored)
+    return Failure::kErrored;
+
+  ast::Builtin builtin = ident_to_builtin(ident.value);
+  if (builtin == ast::Builtin::kNone)
+    return add_error(ident.source, "invalid value for builtin attribute");
+
+  return {builtin, ident.source};
+}
+
+// body_stmt
+//   : BRACE_LEFT statements BRACE_RIGHT
+Expect<ast::BlockStatement*> ParserImpl::expect_body_stmt() {
+  return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> {
+    auto stmts = expect_statements();
+    if (stmts.errored)
+      return Failure::kErrored;
+    return create<ast::BlockStatement>(Source{}, stmts.value);
+  });
+}
+
+// paren_rhs_stmt
+//   : PAREN_LEFT logical_or_expression PAREN_RIGHT
+Expect<const ast::Expression*> ParserImpl::expect_paren_rhs_stmt() {
+  return expect_paren_block("", [&]() -> Expect<const ast::Expression*> {
+    auto expr = logical_or_expression();
+    if (expr.errored)
+      return Failure::kErrored;
+    if (!expr.matched)
+      return add_error(peek(), "unable to parse expression");
+
+    return expr.value;
+  });
+}
+
+// statements
+//   : statement*
+Expect<ast::StatementList> ParserImpl::expect_statements() {
+  bool errored = false;
+  ast::StatementList stmts;
+
+  while (continue_parsing()) {
+    auto stmt = statement();
+    if (stmt.errored) {
+      errored = true;
+    } else if (stmt.matched) {
+      stmts.emplace_back(stmt.value);
+    } else {
+      break;
+    }
+  }
+
+  if (errored)
+    return Failure::kErrored;
+
+  return stmts;
+}
+
+// statement
+//   : SEMICOLON
+//   | body_stmt?
+//   | if_stmt
+//   | switch_stmt
+//   | loop_stmt
+//   | for_stmt
+//   | non_block_statement
+//      : return_stmt SEMICOLON
+//      | func_call_stmt SEMICOLON
+//      | variable_stmt SEMICOLON
+//      | break_stmt SEMICOLON
+//      | continue_stmt SEMICOLON
+//      | DISCARD SEMICOLON
+//      | assignment_stmt SEMICOLON
+Maybe<const ast::Statement*> ParserImpl::statement() {
+  while (match(Token::Type::kSemicolon)) {
+    // Skip empty statements
+  }
+
+  // Non-block statments that error can resynchronize on semicolon.
+  auto stmt =
+      sync(Token::Type::kSemicolon, [&] { return non_block_statement(); });
+
+  if (stmt.errored)
+    return Failure::kErrored;
+  if (stmt.matched)
+    return stmt;
+
+  auto stmt_if = if_stmt();
+  if (stmt_if.errored)
+    return Failure::kErrored;
+  if (stmt_if.matched)
+    return stmt_if.value;
+
+  auto sw = switch_stmt();
+  if (sw.errored)
+    return Failure::kErrored;
+  if (sw.matched)
+    return sw.value;
+
+  auto loop = loop_stmt();
+  if (loop.errored)
+    return Failure::kErrored;
+  if (loop.matched)
+    return loop.value;
+
+  auto stmt_for = for_stmt();
+  if (stmt_for.errored)
+    return Failure::kErrored;
+  if (stmt_for.matched)
+    return stmt_for.value;
+
+  if (peek_is(Token::Type::kBraceLeft)) {
+    auto body = expect_body_stmt();
+    if (body.errored)
+      return Failure::kErrored;
+    return body.value;
+  }
+
+  return Failure::kNoMatch;
+}
+
+// statement (continued)
+//   : return_stmt SEMICOLON
+//   | func_call_stmt SEMICOLON
+//   | variable_stmt SEMICOLON
+//   | break_stmt SEMICOLON
+//   | continue_stmt SEMICOLON
+//   | DISCARD SEMICOLON
+//   | assignment_stmt SEMICOLON
+Maybe<const ast::Statement*> ParserImpl::non_block_statement() {
+  auto stmt = [&]() -> Maybe<const ast::Statement*> {
+    auto ret_stmt = return_stmt();
+    if (ret_stmt.errored)
+      return Failure::kErrored;
+    if (ret_stmt.matched)
+      return ret_stmt.value;
+
+    auto func = func_call_stmt();
+    if (func.errored)
+      return Failure::kErrored;
+    if (func.matched)
+      return func.value;
+
+    auto var = variable_stmt();
+    if (var.errored)
+      return Failure::kErrored;
+    if (var.matched)
+      return var.value;
+
+    auto b = break_stmt();
+    if (b.errored)
+      return Failure::kErrored;
+    if (b.matched)
+      return b.value;
+
+    auto cont = continue_stmt();
+    if (cont.errored)
+      return Failure::kErrored;
+    if (cont.matched)
+      return cont.value;
+
+    auto assign = assignment_stmt();
+    if (assign.errored)
+      return Failure::kErrored;
+    if (assign.matched)
+      return assign.value;
+
+    Source source;
+    if (match(Token::Type::kDiscard, &source))
+      return create<ast::DiscardStatement>(source);
+
+    return Failure::kNoMatch;
+  }();
+
+  if (stmt.matched && !expect(stmt->Name(), Token::Type::kSemicolon))
+    return Failure::kErrored;
+
+  return stmt;
+}
+
+// return_stmt
+//   : RETURN logical_or_expression?
+Maybe<const ast::ReturnStatement*> ParserImpl::return_stmt() {
+  Source source;
+  if (!match(Token::Type::kReturn, &source))
+    return Failure::kNoMatch;
+
+  if (peek_is(Token::Type::kSemicolon))
+    return create<ast::ReturnStatement>(source, nullptr);
+
+  auto expr = logical_or_expression();
+  if (expr.errored)
+    return Failure::kErrored;
+
+  // TODO(bclayton): Check matched?
+  return create<ast::ReturnStatement>(source, expr.value);
+}
+
+// variable_stmt
+//   : variable_decl
+//   | variable_decl EQUAL logical_or_expression
+//   | CONST variable_ident_decl EQUAL logical_or_expression
+Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
+  if (match(Token::Type::kLet)) {
+    auto decl = expect_variable_ident_decl("let declaration",
+                                           /*allow_inferred = */ true);
+    if (decl.errored)
+      return Failure::kErrored;
+
+    if (!expect("let declaration", Token::Type::kEqual))
+      return Failure::kErrored;
+
+    auto constructor = logical_or_expression();
+    if (constructor.errored)
+      return Failure::kErrored;
+    if (!constructor.matched)
+      return add_error(peek(), "missing constructor for let declaration");
+
+    auto* var = create<ast::Variable>(
+        decl->source,                             // source
+        builder_.Symbols().Register(decl->name),  // symbol
+        ast::StorageClass::kNone,                 // storage class
+        ast::Access::kUndefined,                  // access control
+        decl->type,                               // type
+        true,                                     // is_const
+        false,                                    // is_overridable
+        constructor.value,                        // constructor
+        ast::AttributeList{});                    // attributes
+
+    return create<ast::VariableDeclStatement>(decl->source, var);
+  }
+
+  auto decl = variable_decl(/*allow_inferred = */ true);
+  if (decl.errored)
+    return Failure::kErrored;
+  if (!decl.matched)
+    return Failure::kNoMatch;
+
+  const ast::Expression* constructor = nullptr;
+  if (match(Token::Type::kEqual)) {
+    auto constructor_expr = logical_or_expression();
+    if (constructor_expr.errored)
+      return Failure::kErrored;
+    if (!constructor_expr.matched)
+      return add_error(peek(), "missing constructor for variable declaration");
+
+    constructor = constructor_expr.value;
+  }
+
+  auto* var =
+      create<ast::Variable>(decl->source,                             // source
+                            builder_.Symbols().Register(decl->name),  // symbol
+                            decl->storage_class,    // storage class
+                            decl->access,           // access control
+                            decl->type,             // type
+                            false,                  // is_const
+                            false,                  // is_overridable
+                            constructor,            // constructor
+                            ast::AttributeList{});  // attributes
+
+  return create<ast::VariableDeclStatement>(var->source, var);
+}
+
+// if_stmt
+//   : IF paren_rhs_stmt body_stmt ( ELSE else_stmts ) ?
+Maybe<const ast::IfStatement*> ParserImpl::if_stmt() {
+  Source source;
+  if (!match(Token::Type::kIf, &source))
+    return Failure::kNoMatch;
+
+  auto condition = expect_paren_rhs_stmt();
+  if (condition.errored)
+    return Failure::kErrored;
+
+  auto body = expect_body_stmt();
+  if (body.errored)
+    return Failure::kErrored;
+
+  auto el = else_stmts();
+  if (el.errored) {
+    return Failure::kErrored;
+  }
+
+  return create<ast::IfStatement>(source, condition.value, body.value,
+                                  std::move(el.value));
+}
+
+// else_stmts
+//  : body_stmt
+//  | if_stmt
+Expect<ast::ElseStatementList> ParserImpl::else_stmts() {
+  ast::ElseStatementList stmts;
+  while (continue_parsing()) {
+    Source start;
+
+    bool else_if = false;
+    if (match(Token::Type::kElse, &start)) {
+      else_if = match(Token::Type::kIf);
+    } else if (match(Token::Type::kElseIf, &start)) {
+      deprecated(start, "'elseif' is now 'else if'");
+      else_if = true;
+    } else {
+      break;
+    }
+
+    const ast::Expression* cond = nullptr;
+    if (else_if) {
+      auto condition = expect_paren_rhs_stmt();
+      if (condition.errored) {
+        return Failure::kErrored;
+      }
+      cond = condition.value;
+    }
+
+    auto body = expect_body_stmt();
+    if (body.errored) {
+      return Failure::kErrored;
+    }
+
+    Source source = make_source_range_from(start);
+    stmts.emplace_back(create<ast::ElseStatement>(source, cond, body.value));
+  }
+
+  return stmts;
+}
+
+// switch_stmt
+//   : SWITCH paren_rhs_stmt BRACKET_LEFT switch_body+ BRACKET_RIGHT
+Maybe<const ast::SwitchStatement*> ParserImpl::switch_stmt() {
+  Source source;
+  if (!match(Token::Type::kSwitch, &source))
+    return Failure::kNoMatch;
+
+  auto condition = expect_paren_rhs_stmt();
+  if (condition.errored)
+    return Failure::kErrored;
+
+  auto body = expect_brace_block("switch statement",
+                                 [&]() -> Expect<ast::CaseStatementList> {
+                                   bool errored = false;
+                                   ast::CaseStatementList list;
+                                   while (continue_parsing()) {
+                                     auto stmt = switch_body();
+                                     if (stmt.errored) {
+                                       errored = true;
+                                       continue;
+                                     }
+                                     if (!stmt.matched)
+                                       break;
+                                     list.push_back(stmt.value);
+                                   }
+                                   if (errored)
+                                     return Failure::kErrored;
+                                   return list;
+                                 });
+
+  if (body.errored)
+    return Failure::kErrored;
+
+  return create<ast::SwitchStatement>(source, condition.value, body.value);
+}
+
+// switch_body
+//   : CASE case_selectors COLON BRACKET_LEFT case_body BRACKET_RIGHT
+//   | DEFAULT COLON BRACKET_LEFT case_body BRACKET_RIGHT
+Maybe<const ast::CaseStatement*> ParserImpl::switch_body() {
+  if (!peek_is(Token::Type::kCase) && !peek_is(Token::Type::kDefault))
+    return Failure::kNoMatch;
+
+  auto t = next();
+  auto source = t.source();
+
+  ast::CaseSelectorList selector_list;
+  if (t.Is(Token::Type::kCase)) {
+    auto selectors = expect_case_selectors();
+    if (selectors.errored)
+      return Failure::kErrored;
+
+    selector_list = std::move(selectors.value);
+  }
+
+  const char* use = "case statement";
+
+  if (!expect(use, Token::Type::kColon))
+    return Failure::kErrored;
+
+  auto body = expect_brace_block(use, [&] { return case_body(); });
+
+  if (body.errored)
+    return Failure::kErrored;
+  if (!body.matched)
+    return add_error(body.source, "expected case body");
+
+  return create<ast::CaseStatement>(source, selector_list, body.value);
+}
+
+// case_selectors
+//   : const_literal (COMMA const_literal)* COMMA?
+Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() {
+  ast::CaseSelectorList selectors;
+
+  while (continue_parsing()) {
+    auto cond = const_literal();
+    if (cond.errored) {
+      return Failure::kErrored;
+    } else if (!cond.matched) {
+      break;
+    } else if (!cond->Is<ast::IntLiteralExpression>()) {
+      return add_error(cond.value->source,
+                       "invalid case selector must be an integer value");
+    }
+
+    selectors.push_back(cond.value->As<ast::IntLiteralExpression>());
+
+    if (!match(Token::Type::kComma)) {
+      break;
+    }
+  }
+
+  if (selectors.empty())
+    return add_error(peek(), "unable to parse case selectors");
+
+  return selectors;
+}
+
+// case_body
+//   :
+//   | statement case_body
+//   | FALLTHROUGH SEMICOLON
+Maybe<const ast::BlockStatement*> ParserImpl::case_body() {
+  ast::StatementList stmts;
+  while (continue_parsing()) {
+    Source source;
+    if (match(Token::Type::kFallthrough, &source)) {
+      if (!expect("fallthrough statement", Token::Type::kSemicolon))
+        return Failure::kErrored;
+
+      stmts.emplace_back(create<ast::FallthroughStatement>(source));
+      break;
+    }
+
+    auto stmt = statement();
+    if (stmt.errored)
+      return Failure::kErrored;
+    if (!stmt.matched)
+      break;
+
+    stmts.emplace_back(stmt.value);
+  }
+
+  return create<ast::BlockStatement>(Source{}, stmts);
+}
+
+// loop_stmt
+//   : LOOP BRACKET_LEFT statements continuing_stmt? BRACKET_RIGHT
+Maybe<const ast::LoopStatement*> ParserImpl::loop_stmt() {
+  Source source;
+  if (!match(Token::Type::kLoop, &source))
+    return Failure::kNoMatch;
+
+  return expect_brace_block("loop", [&]() -> Maybe<const ast::LoopStatement*> {
+    auto stmts = expect_statements();
+    if (stmts.errored)
+      return Failure::kErrored;
+
+    auto continuing = continuing_stmt();
+    if (continuing.errored)
+      return Failure::kErrored;
+
+    auto* body = create<ast::BlockStatement>(source, stmts.value);
+    return create<ast::LoopStatement>(source, body, continuing.value);
+  });
+}
+
+ForHeader::ForHeader(const ast::Statement* init,
+                     const ast::Expression* cond,
+                     const ast::Statement* cont)
+    : initializer(init), condition(cond), continuing(cont) {}
+
+ForHeader::~ForHeader() = default;
+
+// (variable_stmt | assignment_stmt | func_call_stmt)?
+Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
+  auto call = func_call_stmt();
+  if (call.errored)
+    return Failure::kErrored;
+  if (call.matched)
+    return call.value;
+
+  auto var = variable_stmt();
+  if (var.errored)
+    return Failure::kErrored;
+  if (var.matched)
+    return var.value;
+
+  auto assign = assignment_stmt();
+  if (assign.errored)
+    return Failure::kErrored;
+  if (assign.matched)
+    return assign.value;
+
+  return Failure::kNoMatch;
+}
+
+// (assignment_stmt | func_call_stmt)?
+Maybe<const ast::Statement*> ParserImpl::for_header_continuing() {
+  auto call_stmt = func_call_stmt();
+  if (call_stmt.errored)
+    return Failure::kErrored;
+  if (call_stmt.matched)
+    return call_stmt.value;
+
+  auto assign = assignment_stmt();
+  if (assign.errored)
+    return Failure::kErrored;
+  if (assign.matched)
+    return assign.value;
+
+  return Failure::kNoMatch;
+}
+
+// for_header
+//   : (variable_stmt | assignment_stmt | func_call_stmt)?
+//   SEMICOLON
+//      logical_or_expression? SEMICOLON
+//      (assignment_stmt | func_call_stmt)?
+Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
+  auto initializer = for_header_initializer();
+  if (initializer.errored)
+    return Failure::kErrored;
+
+  if (!expect("initializer in for loop", Token::Type::kSemicolon))
+    return Failure::kErrored;
+
+  auto condition = logical_or_expression();
+  if (condition.errored)
+    return Failure::kErrored;
+
+  if (!expect("condition in for loop", Token::Type::kSemicolon))
+    return Failure::kErrored;
+
+  auto continuing = for_header_continuing();
+  if (continuing.errored)
+    return Failure::kErrored;
+
+  return std::make_unique<ForHeader>(initializer.value, condition.value,
+                                     continuing.value);
+}
+
+// for_statement
+//   : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
+Maybe<const ast::ForLoopStatement*> ParserImpl::for_stmt() {
+  Source source;
+  if (!match(Token::Type::kFor, &source))
+    return Failure::kNoMatch;
+
+  auto header =
+      expect_paren_block("for loop", [&] { return expect_for_header(); });
+  if (header.errored)
+    return Failure::kErrored;
+
+  auto stmts =
+      expect_brace_block("for loop", [&] { return expect_statements(); });
+  if (stmts.errored)
+    return Failure::kErrored;
+
+  return create<ast::ForLoopStatement>(
+      source, header->initializer, header->condition, header->continuing,
+      create<ast::BlockStatement>(stmts.value));
+}
+
+// func_call_stmt
+//    : IDENT argument_expression_list
+Maybe<const ast::CallStatement*> ParserImpl::func_call_stmt() {
+  auto t = peek();
+  auto t2 = peek(1);
+  if (!t.IsIdentifier() || !t2.Is(Token::Type::kParenLeft))
+    return Failure::kNoMatch;
+
+  next();  // Consume the first peek
+
+  auto source = t.source();
+  auto name = t.to_str();
+
+  auto params = expect_argument_expression_list("function call");
+  if (params.errored)
+    return Failure::kErrored;
+
+  return create<ast::CallStatement>(
+      source, create<ast::CallExpression>(
+                  source,
+                  create<ast::IdentifierExpression>(
+                      source, builder_.Symbols().Register(name)),
+                  std::move(params.value)));
+}
+
+// break_stmt
+//   : BREAK
+Maybe<const ast::BreakStatement*> ParserImpl::break_stmt() {
+  Source source;
+  if (!match(Token::Type::kBreak, &source))
+    return Failure::kNoMatch;
+
+  return create<ast::BreakStatement>(source);
+}
+
+// continue_stmt
+//   : CONTINUE
+Maybe<const ast::ContinueStatement*> ParserImpl::continue_stmt() {
+  Source source;
+  if (!match(Token::Type::kContinue, &source))
+    return Failure::kNoMatch;
+
+  return create<ast::ContinueStatement>(source);
+}
+
+// continuing_stmt
+//   : CONTINUING body_stmt
+Maybe<const ast::BlockStatement*> ParserImpl::continuing_stmt() {
+  if (!match(Token::Type::kContinuing))
+    return create<ast::BlockStatement>(Source{}, ast::StatementList{});
+
+  return expect_body_stmt();
+}
+
+// primary_expression
+//   : IDENT argument_expression_list?
+//   | type_decl argument_expression_list
+//   | const_literal
+//   | paren_rhs_stmt
+//   | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
+Maybe<const ast::Expression*> ParserImpl::primary_expression() {
+  auto t = peek();
+  auto source = t.source();
+
+  auto lit = const_literal();
+  if (lit.errored) {
+    return Failure::kErrored;
+  }
+  if (lit.matched) {
+    return lit.value;
+  }
+
+  if (t.Is(Token::Type::kParenLeft)) {
+    auto paren = expect_paren_rhs_stmt();
+    if (paren.errored) {
+      return Failure::kErrored;
+    }
+
+    return paren.value;
+  }
+
+  if (match(Token::Type::kBitcast)) {
+    const char* use = "bitcast expression";
+
+    auto type = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (type.errored)
+      return Failure::kErrored;
+
+    auto params = expect_paren_rhs_stmt();
+    if (params.errored)
+      return Failure::kErrored;
+
+    return create<ast::BitcastExpression>(source, type.value, params.value);
+  }
+
+  if (t.IsIdentifier()) {
+    next();
+
+    auto* ident = create<ast::IdentifierExpression>(
+        t.source(), builder_.Symbols().Register(t.to_str()));
+
+    if (peek_is(Token::Type::kParenLeft)) {
+      auto params = expect_argument_expression_list("function call");
+      if (params.errored)
+        return Failure::kErrored;
+
+      return create<ast::CallExpression>(source, ident,
+                                         std::move(params.value));
+    }
+
+    return ident;
+  }
+
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (type.matched) {
+    auto params = expect_argument_expression_list("type constructor");
+    if (params.errored)
+      return Failure::kErrored;
+
+    return builder_.Construct(source, type.value, std::move(params.value));
+  }
+
+  return Failure::kNoMatch;
+}
+
+// postfix_expression
+//   :
+//   | BRACE_LEFT logical_or_expression BRACE_RIGHT postfix_expr
+//   | PERIOD IDENTIFIER postfix_expr
+Maybe<const ast::Expression*> ParserImpl::postfix_expression(
+    const ast::Expression* prefix) {
+  Source source;
+
+  while (continue_parsing()) {
+    if (match(Token::Type::kPlusPlus, &source) ||
+        match(Token::Type::kMinusMinus, &source)) {
+      add_error(source,
+                "postfix increment and decrement operators are reserved for a "
+                "future WGSL version");
+      return Failure::kErrored;
+    }
+
+    if (match(Token::Type::kBracketLeft, &source)) {
+      auto res = sync(
+          Token::Type::kBracketRight, [&]() -> Maybe<const ast::Expression*> {
+            auto param = logical_or_expression();
+            if (param.errored)
+              return Failure::kErrored;
+            if (!param.matched) {
+              return add_error(peek(), "unable to parse expression inside []");
+            }
+
+            if (!expect("index accessor", Token::Type::kBracketRight)) {
+              return Failure::kErrored;
+            }
+
+            return create<ast::IndexAccessorExpression>(source, prefix,
+                                                        param.value);
+          });
+
+      if (res.errored) {
+        return res;
+      }
+      prefix = res.value;
+      continue;
+    }
+
+    if (match(Token::Type::kPeriod)) {
+      auto ident = expect_ident("member accessor");
+      if (ident.errored) {
+        return Failure::kErrored;
+      }
+
+      prefix = create<ast::MemberAccessorExpression>(
+          ident.source, prefix,
+          create<ast::IdentifierExpression>(
+              ident.source, builder_.Symbols().Register(ident.value)));
+      continue;
+    }
+
+    return prefix;
+  }
+
+  return Failure::kErrored;
+}
+
+// singular_expression
+//   : primary_expression postfix_expr
+Maybe<const ast::Expression*> ParserImpl::singular_expression() {
+  auto prefix = primary_expression();
+  if (prefix.errored)
+    return Failure::kErrored;
+  if (!prefix.matched)
+    return Failure::kNoMatch;
+
+  return postfix_expression(prefix.value);
+}
+
+// argument_expression_list
+//   : PAREN_LEFT ((logical_or_expression COMMA)* logical_or_expression COMMA?)?
+//   PAREN_RIGHT
+Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list(
+    std::string_view use) {
+  return expect_paren_block(use, [&]() -> Expect<ast::ExpressionList> {
+    ast::ExpressionList ret;
+    while (continue_parsing()) {
+      auto arg = logical_or_expression();
+      if (arg.errored) {
+        return Failure::kErrored;
+      } else if (!arg.matched) {
+        break;
+      }
+      ret.push_back(arg.value);
+
+      if (!match(Token::Type::kComma)) {
+        break;
+      }
+    }
+    return ret;
+  });
+}
+
+// unary_expression
+//   : singular_expression
+//   | MINUS unary_expression
+//   | BANG unary_expression
+//   | TILDE unary_expression
+//   | STAR unary_expression
+//   | AND unary_expression
+Maybe<const ast::Expression*> ParserImpl::unary_expression() {
+  auto t = peek();
+
+  if (match(Token::Type::kPlusPlus) || match(Token::Type::kMinusMinus)) {
+    add_error(t.source(),
+              "prefix increment and decrement operators are reserved for a "
+              "future WGSL version");
+    return Failure::kErrored;
+  }
+
+  ast::UnaryOp op;
+  if (match(Token::Type::kMinus)) {
+    op = ast::UnaryOp::kNegation;
+  } else if (match(Token::Type::kBang)) {
+    op = ast::UnaryOp::kNot;
+  } else if (match(Token::Type::kTilde)) {
+    op = ast::UnaryOp::kComplement;
+  } else if (match(Token::Type::kStar)) {
+    op = ast::UnaryOp::kIndirection;
+  } else if (match(Token::Type::kAnd)) {
+    op = ast::UnaryOp::kAddressOf;
+  } else {
+    return singular_expression();
+  }
+
+  if (parse_depth_ >= kMaxParseDepth) {
+    // We've hit a maximum parser recursive depth.
+    // We can't call into unary_expression() as we might stack overflow.
+    // Instead, report an error
+    add_error(peek(), "maximum parser recursive depth reached");
+    return Failure::kErrored;
+  }
+
+  ++parse_depth_;
+  auto expr = unary_expression();
+  --parse_depth_;
+
+  if (expr.errored) {
+    return Failure::kErrored;
+  }
+  if (!expr.matched) {
+    return add_error(peek(), "unable to parse right side of " +
+                                 std::string(t.to_name()) + " expression");
+  }
+
+  return create<ast::UnaryOpExpression>(t.source(), op, expr.value);
+}
+
+// multiplicative_expr
+//   :
+//   | STAR unary_expression multiplicative_expr
+//   | FORWARD_SLASH unary_expression multiplicative_expr
+//   | MODULO unary_expression multiplicative_expr
+Expect<const ast::Expression*> ParserImpl::expect_multiplicative_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    ast::BinaryOp op = ast::BinaryOp::kNone;
+    if (peek_is(Token::Type::kStar))
+      op = ast::BinaryOp::kMultiply;
+    else if (peek_is(Token::Type::kForwardSlash))
+      op = ast::BinaryOp::kDivide;
+    else if (peek_is(Token::Type::kMod))
+      op = ast::BinaryOp::kModulo;
+    else
+      return lhs;
+
+    auto t = next();
+    auto source = t.source();
+    auto name = t.to_name();
+
+    auto rhs = unary_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched) {
+      return add_error(peek(), "unable to parse right side of " +
+                                   std::string(name) + " expression");
+    }
+
+    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// multiplicative_expression
+//   : unary_expression multiplicative_expr
+Maybe<const ast::Expression*> ParserImpl::multiplicative_expression() {
+  auto lhs = unary_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_multiplicative_expr(lhs.value);
+}
+
+// additive_expr
+//   :
+//   | PLUS multiplicative_expression additive_expr
+//   | MINUS multiplicative_expression additive_expr
+Expect<const ast::Expression*> ParserImpl::expect_additive_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    ast::BinaryOp op = ast::BinaryOp::kNone;
+    if (peek_is(Token::Type::kPlus))
+      op = ast::BinaryOp::kAdd;
+    else if (peek_is(Token::Type::kMinus))
+      op = ast::BinaryOp::kSubtract;
+    else
+      return lhs;
+
+    auto t = next();
+    auto source = t.source();
+
+    auto rhs = multiplicative_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of + expression");
+
+    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// additive_expression
+//   : multiplicative_expression additive_expr
+Maybe<const ast::Expression*> ParserImpl::additive_expression() {
+  auto lhs = multiplicative_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_additive_expr(lhs.value);
+}
+
+// shift_expr
+//   :
+//   | SHIFT_LEFT additive_expression shift_expr
+//   | SHIFT_RIGHT additive_expression shift_expr
+Expect<const ast::Expression*> ParserImpl::expect_shift_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    auto* name = "";
+    ast::BinaryOp op = ast::BinaryOp::kNone;
+    if (peek_is(Token::Type::kShiftLeft)) {
+      op = ast::BinaryOp::kShiftLeft;
+      name = "<<";
+    } else if (peek_is(Token::Type::kShiftRight)) {
+      op = ast::BinaryOp::kShiftRight;
+      name = ">>";
+    } else {
+      return lhs;
+    }
+
+    auto t = next();
+    auto source = t.source();
+    auto rhs = additive_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched) {
+      return add_error(peek(), std::string("unable to parse right side of ") +
+                                   name + " expression");
+    }
+
+    return lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// shift_expression
+//   : additive_expression shift_expr
+Maybe<const ast::Expression*> ParserImpl::shift_expression() {
+  auto lhs = additive_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_shift_expr(lhs.value);
+}
+
+// relational_expr
+//   :
+//   | LESS_THAN shift_expression relational_expr
+//   | GREATER_THAN shift_expression relational_expr
+//   | LESS_THAN_EQUAL shift_expression relational_expr
+//   | GREATER_THAN_EQUAL shift_expression relational_expr
+Expect<const ast::Expression*> ParserImpl::expect_relational_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    ast::BinaryOp op = ast::BinaryOp::kNone;
+    if (peek_is(Token::Type::kLessThan))
+      op = ast::BinaryOp::kLessThan;
+    else if (peek_is(Token::Type::kGreaterThan))
+      op = ast::BinaryOp::kGreaterThan;
+    else if (peek_is(Token::Type::kLessThanEqual))
+      op = ast::BinaryOp::kLessThanEqual;
+    else if (peek_is(Token::Type::kGreaterThanEqual))
+      op = ast::BinaryOp::kGreaterThanEqual;
+    else
+      return lhs;
+
+    auto t = next();
+    auto source = t.source();
+    auto name = t.to_name();
+
+    auto rhs = shift_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched) {
+      return add_error(peek(), "unable to parse right side of " +
+                                   std::string(name) + " expression");
+    }
+
+    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// relational_expression
+//   : shift_expression relational_expr
+Maybe<const ast::Expression*> ParserImpl::relational_expression() {
+  auto lhs = shift_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_relational_expr(lhs.value);
+}
+
+// equality_expr
+//   :
+//   | EQUAL_EQUAL relational_expression equality_expr
+//   | NOT_EQUAL relational_expression equality_expr
+Expect<const ast::Expression*> ParserImpl::expect_equality_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    ast::BinaryOp op = ast::BinaryOp::kNone;
+    if (peek_is(Token::Type::kEqualEqual))
+      op = ast::BinaryOp::kEqual;
+    else if (peek_is(Token::Type::kNotEqual))
+      op = ast::BinaryOp::kNotEqual;
+    else
+      return lhs;
+
+    auto t = next();
+    auto source = t.source();
+    auto name = t.to_name();
+
+    auto rhs = relational_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched) {
+      return add_error(peek(), "unable to parse right side of " +
+                                   std::string(name) + " expression");
+    }
+
+    lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// equality_expression
+//   : relational_expression equality_expr
+Maybe<const ast::Expression*> ParserImpl::equality_expression() {
+  auto lhs = relational_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_equality_expr(lhs.value);
+}
+
+// and_expr
+//   :
+//   | AND equality_expression and_expr
+Expect<const ast::Expression*> ParserImpl::expect_and_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    if (!peek_is(Token::Type::kAnd)) {
+      return lhs;
+    }
+
+    auto t = next();
+    auto source = t.source();
+
+    auto rhs = equality_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of & expression");
+
+    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kAnd, lhs,
+                                        rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// and_expression
+//   : equality_expression and_expr
+Maybe<const ast::Expression*> ParserImpl::and_expression() {
+  auto lhs = equality_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_and_expr(lhs.value);
+}
+
+// exclusive_or_expr
+//   :
+//   | XOR and_expression exclusive_or_expr
+Expect<const ast::Expression*> ParserImpl::expect_exclusive_or_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    Source source;
+    if (!match(Token::Type::kXor, &source))
+      return lhs;
+
+    auto rhs = and_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of ^ expression");
+
+    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kXor, lhs,
+                                        rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// exclusive_or_expression
+//   : and_expression exclusive_or_expr
+Maybe<const ast::Expression*> ParserImpl::exclusive_or_expression() {
+  auto lhs = and_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_exclusive_or_expr(lhs.value);
+}
+
+// inclusive_or_expr
+//   :
+//   | OR exclusive_or_expression inclusive_or_expr
+Expect<const ast::Expression*> ParserImpl::expect_inclusive_or_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    Source source;
+    if (!match(Token::Type::kOr))
+      return lhs;
+
+    auto rhs = exclusive_or_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of | expression");
+
+    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kOr, lhs,
+                                        rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// inclusive_or_expression
+//   : exclusive_or_expression inclusive_or_expr
+Maybe<const ast::Expression*> ParserImpl::inclusive_or_expression() {
+  auto lhs = exclusive_or_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_inclusive_or_expr(lhs.value);
+}
+
+// logical_and_expr
+//   :
+//   | AND_AND inclusive_or_expression logical_and_expr
+Expect<const ast::Expression*> ParserImpl::expect_logical_and_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    if (!peek_is(Token::Type::kAndAnd)) {
+      return lhs;
+    }
+
+    auto t = next();
+    auto source = t.source();
+
+    auto rhs = inclusive_or_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of && expression");
+
+    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalAnd, lhs,
+                                        rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// logical_and_expression
+//   : inclusive_or_expression logical_and_expr
+Maybe<const ast::Expression*> ParserImpl::logical_and_expression() {
+  auto lhs = inclusive_or_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_logical_and_expr(lhs.value);
+}
+
+// logical_or_expr
+//   :
+//   | OR_OR logical_and_expression logical_or_expr
+Expect<const ast::Expression*> ParserImpl::expect_logical_or_expr(
+    const ast::Expression* lhs) {
+  while (continue_parsing()) {
+    Source source;
+    if (!match(Token::Type::kOrOr))
+      return lhs;
+
+    auto rhs = logical_and_expression();
+    if (rhs.errored)
+      return Failure::kErrored;
+    if (!rhs.matched)
+      return add_error(peek(), "unable to parse right side of || expression");
+
+    lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalOr, lhs,
+                                        rhs.value);
+  }
+  return Failure::kErrored;
+}
+
+// logical_or_expression
+//   : logical_and_expression logical_or_expr
+Maybe<const ast::Expression*> ParserImpl::logical_or_expression() {
+  auto lhs = logical_and_expression();
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
+
+  return expect_logical_or_expr(lhs.value);
+}
+
+// assignment_stmt
+//   : (unary_expression | underscore) EQUAL logical_or_expression
+Maybe<const ast::AssignmentStatement*> ParserImpl::assignment_stmt() {
+  auto t = peek();
+  auto source = t.source();
+
+  // tint:295 - Test for `ident COLON` - this is invalid grammar, and without
+  // special casing will error as "missing = for assignment", which is less
+  // helpful than this error message:
+  if (peek_is(Token::Type::kIdentifier) && peek_is(Token::Type::kColon, 1)) {
+    return add_error(peek(0).source(),
+                     "expected 'var' for variable declaration");
+  }
+
+  auto lhs = unary_expression();
+  if (lhs.errored) {
+    return Failure::kErrored;
+  }
+  if (!lhs.matched) {
+    if (!match(Token::Type::kUnderscore, &source)) {
+      return Failure::kNoMatch;
+    }
+    lhs = create<ast::PhonyExpression>(source);
+  }
+
+  if (!expect("assignment", Token::Type::kEqual)) {
+    return Failure::kErrored;
+  }
+
+  auto rhs = logical_or_expression();
+  if (rhs.errored) {
+    return Failure::kErrored;
+  }
+  if (!rhs.matched) {
+    return add_error(peek(), "unable to parse right side of assignment");
+  }
+
+  return create<ast::AssignmentStatement>(source, lhs.value, rhs.value);
+}
+
+// const_literal
+//   : INT_LITERAL
+//   | UINT_LITERAL
+//   | FLOAT_LITERAL
+//   | TRUE
+//   | FALSE
+Maybe<const ast::LiteralExpression*> ParserImpl::const_literal() {
+  auto t = peek();
+  if (t.IsError()) {
+    return add_error(t.source(), t.to_str());
+  }
+  if (match(Token::Type::kTrue)) {
+    return create<ast::BoolLiteralExpression>(t.source(), true);
+  }
+  if (match(Token::Type::kFalse)) {
+    return create<ast::BoolLiteralExpression>(t.source(), false);
+  }
+  if (match(Token::Type::kSintLiteral)) {
+    return create<ast::SintLiteralExpression>(t.source(), t.to_i32());
+  }
+  if (match(Token::Type::kUintLiteral)) {
+    return create<ast::UintLiteralExpression>(t.source(), t.to_u32());
+  }
+  if (match(Token::Type::kFloatLiteral)) {
+    return create<ast::FloatLiteralExpression>(t.source(), t.to_f32());
+  }
+  return Failure::kNoMatch;
+}
+
+// const_expr
+//   : type_decl PAREN_LEFT ((const_expr COMMA)? const_expr COMMA?)? PAREN_RIGHT
+//   | const_literal
+Expect<const ast::Expression*> ParserImpl::expect_const_expr() {
+  auto t = peek();
+  auto source = t.source();
+  if (t.IsLiteral()) {
+    auto lit = const_literal();
+    if (lit.errored) {
+      return Failure::kErrored;
+    }
+    if (!lit.matched) {
+      return add_error(peek(), "unable to parse constant literal");
+    }
+    return lit.value;
+  }
+
+  if (peek_is(Token::Type::kParenLeft, 1) ||
+      peek_is(Token::Type::kLessThan, 1)) {
+    auto type = expect_type("const_expr");
+    if (type.errored) {
+      return Failure::kErrored;
+    }
+
+    auto params = expect_paren_block(
+        "type constructor", [&]() -> Expect<ast::ExpressionList> {
+          ast::ExpressionList list;
+          while (continue_parsing()) {
+            if (peek_is(Token::Type::kParenRight)) {
+              break;
+            }
+
+            auto arg = expect_const_expr();
+            if (arg.errored) {
+              return Failure::kErrored;
+            }
+            list.emplace_back(arg.value);
+
+            if (!match(Token::Type::kComma)) {
+              break;
+            }
+          }
+          return list;
+        });
+
+    if (params.errored)
+      return Failure::kErrored;
+
+    return builder_.Construct(source, type.value, params.value);
+  }
+  return add_error(peek(), "unable to parse const_expr");
+}
+
+Maybe<ast::AttributeList> ParserImpl::attribute_list() {
+  bool errored = false;
+  bool matched = false;
+  ast::AttributeList attrs;
+
+  while (continue_parsing()) {
+    if (match(Token::Type::kAttr)) {
+      if (auto attr = expect_attribute(); attr.errored) {
+        errored = true;
+      } else {
+        attrs.emplace_back(attr.value);
+      }
+    } else {  // [DEPRECATED] - old [[attribute]] style
+      auto list = attribute_bracketed_list(attrs);
+      if (list.errored) {
+        errored = true;
+      }
+      if (!list.matched) {
+        break;
+      }
+    }
+
+    matched = true;
+  }
+
+  if (errored)
+    return Failure::kErrored;
+
+  if (!matched)
+    return Failure::kNoMatch;
+
+  return attrs;
+}
+
+Maybe<bool> ParserImpl::attribute_bracketed_list(ast::AttributeList& attrs) {
+  const char* use = "attribute list";
+
+  Source source;
+  if (!match(Token::Type::kAttrLeft, &source)) {
+    return Failure::kNoMatch;
+  }
+
+  deprecated(source,
+             "[[attribute]] style attributes have been replaced with "
+             "@attribute style");
+
+  if (match(Token::Type::kAttrRight, &source))
+    return add_error(source, "empty attribute list");
+
+  return sync(Token::Type::kAttrRight, [&]() -> Expect<bool> {
+    bool errored = false;
+
+    while (continue_parsing()) {
+      auto attr = expect_attribute();
+      if (attr.errored) {
+        errored = true;
+      }
+      attrs.emplace_back(attr.value);
+
+      if (match(Token::Type::kComma)) {
+        continue;
+      }
+
+      if (is_attribute(peek())) {
+        // We have two attributes in a bracket without a separating comma.
+        // e.g. @location(1) group(2)
+        //                    ^^^ expected comma
+        expect(use, Token::Type::kComma);
+        return Failure::kErrored;
+      }
+
+      break;
+    }
+
+    if (errored) {
+      return Failure::kErrored;
+    }
+
+    if (!expect(use, Token::Type::kAttrRight)) {
+      return Failure::kErrored;
+    }
+
+    return true;
+  });
+}
+
+Expect<const ast::Attribute*> ParserImpl::expect_attribute() {
+  auto t = peek();
+  auto attr = attribute();
+  if (attr.errored)
+    return Failure::kErrored;
+  if (attr.matched)
+    return attr.value;
+  return add_error(t, "expected attribute");
+}
+
+Maybe<const ast::Attribute*> ParserImpl::attribute() {
+  using Result = Maybe<const ast::Attribute*>;
+  auto t = next();
+
+  if (!t.IsIdentifier()) {
+    return Failure::kNoMatch;
+  }
+
+  if (t == kLocationAttribute) {
+    const char* use = "location attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::LocationAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kBindingAttribute) {
+    const char* use = "binding attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::BindingAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kGroupAttribute) {
+    const char* use = "group attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::GroupAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kInterpolateAttribute) {
+    return expect_paren_block("interpolate attribute", [&]() -> Result {
+      ast::InterpolationType type;
+      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
+
+      auto type_tok = next();
+      if (type_tok == "perspective") {
+        type = ast::InterpolationType::kPerspective;
+      } else if (type_tok == "linear") {
+        type = ast::InterpolationType::kLinear;
+      } else if (type_tok == "flat") {
+        type = ast::InterpolationType::kFlat;
+      } else {
+        return add_error(type_tok, "invalid interpolation type");
+      }
+
+      if (match(Token::Type::kComma)) {
+        auto sampling_tok = next();
+        if (sampling_tok == "center") {
+          sampling = ast::InterpolationSampling::kCenter;
+        } else if (sampling_tok == "centroid") {
+          sampling = ast::InterpolationSampling::kCentroid;
+        } else if (sampling_tok == "sample") {
+          sampling = ast::InterpolationSampling::kSample;
+        } else {
+          return add_error(sampling_tok, "invalid interpolation sampling");
+        }
+      }
+
+      return create<ast::InterpolateAttribute>(t.source(), type, sampling);
+    });
+  }
+
+  if (t == kInvariantAttribute) {
+    return create<ast::InvariantAttribute>(t.source());
+  }
+
+  if (t == kBuiltinAttribute) {
+    return expect_paren_block("builtin attribute", [&]() -> Result {
+      auto builtin = expect_builtin();
+      if (builtin.errored)
+        return Failure::kErrored;
+
+      return create<ast::BuiltinAttribute>(t.source(), builtin.value);
+    });
+  }
+
+  if (t == kWorkgroupSizeAttribute) {
+    return expect_paren_block("workgroup_size attribute", [&]() -> Result {
+      const ast::Expression* x = nullptr;
+      const ast::Expression* y = nullptr;
+      const ast::Expression* z = nullptr;
+
+      auto expr = primary_expression();
+      if (expr.errored) {
+        return Failure::kErrored;
+      } else if (!expr.matched) {
+        return add_error(peek(), "expected workgroup_size x parameter");
+      }
+      x = std::move(expr.value);
+
+      if (match(Token::Type::kComma)) {
+        expr = primary_expression();
+        if (expr.errored) {
+          return Failure::kErrored;
+        } else if (!expr.matched) {
+          return add_error(peek(), "expected workgroup_size y parameter");
+        }
+        y = std::move(expr.value);
+
+        if (match(Token::Type::kComma)) {
+          expr = primary_expression();
+          if (expr.errored) {
+            return Failure::kErrored;
+          } else if (!expr.matched) {
+            return add_error(peek(), "expected workgroup_size z parameter");
+          }
+          z = std::move(expr.value);
+        }
+      }
+
+      return create<ast::WorkgroupAttribute>(t.source(), x, y, z);
+    });
+  }
+
+  if (t == kStageAttribute) {
+    return expect_paren_block("stage attribute", [&]() -> Result {
+      auto stage = expect_pipeline_stage();
+      if (stage.errored)
+        return Failure::kErrored;
+
+      return create<ast::StageAttribute>(t.source(), stage.value);
+    });
+  }
+
+  if (t == kBlockAttribute) {
+    deprecated(t.source(), "[[block]] attributes have been removed from WGSL");
+    return create<ast::StructBlockAttribute>(t.source());
+  }
+
+  if (t == kStrideAttribute) {
+    const char* use = "stride attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_nonzero_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+      deprecated(t.source(),
+                 "the @stride attribute is deprecated; use a larger type if "
+                 "necessary");
+      return create<ast::StrideAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kSizeAttribute) {
+    const char* use = "size attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::StructMemberSizeAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kAlignAttribute) {
+    const char* use = "align attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::StructMemberAlignAttribute>(t.source(), val.value);
+    });
+  }
+
+  if (t == kIdAttribute) {
+    const char* use = "id attribute";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::IdAttribute>(t.source(), val.value);
+    });
+  }
+
+  return Failure::kNoMatch;
+}
+
+bool ParserImpl::expect_attributes_consumed(ast::AttributeList& in) {
+  if (in.empty()) {
+    return true;
+  }
+  add_error(in[0]->source, "unexpected attributes");
+  return false;
+}
+
+bool ParserImpl::match(Token::Type tok, Source* source /*= nullptr*/) {
+  auto t = peek();
+
+  if (source != nullptr)
+    *source = t.source();
+
+  if (t.Is(tok)) {
+    next();
+    return true;
+  }
+  return false;
+}
+
+bool ParserImpl::expect(std::string_view use, Token::Type tok) {
+  auto t = peek();
+  if (t.Is(tok)) {
+    next();
+    synchronized_ = true;
+    return true;
+  }
+
+  // Special case to split `>>` and `>=` tokens if we are looking for a `>`.
+  if (tok == Token::Type::kGreaterThan &&
+      (t.Is(Token::Type::kShiftRight) ||
+       t.Is(Token::Type::kGreaterThanEqual))) {
+    next();
+
+    // Push the second character to the token queue.
+    auto source = t.source();
+    source.range.begin.column++;
+    if (t.Is(Token::Type::kShiftRight)) {
+      token_queue_.push_front(Token(Token::Type::kGreaterThan, source));
+    } else if (t.Is(Token::Type::kGreaterThanEqual)) {
+      token_queue_.push_front(Token(Token::Type::kEqual, source));
+    }
+
+    synchronized_ = true;
+    return true;
+  }
+
+  // Handle the case when `]` is expected but the actual token is `]]`.
+  // For example, in `arr1[arr2[0]]`.
+  if (tok == Token::Type::kBracketRight && t.Is(Token::Type::kAttrRight)) {
+    next();
+    auto source = t.source();
+    source.range.begin.column++;
+    token_queue_.push_front({Token::Type::kBracketRight, source});
+    synchronized_ = true;
+    return true;
+  }
+
+  std::stringstream err;
+  err << "expected '" << Token::TypeToName(tok) << "'";
+  if (!use.empty()) {
+    err << " for " << use;
+  }
+  add_error(t, err.str());
+  synchronized_ = false;
+  return false;
+}
+
+Expect<int32_t> ParserImpl::expect_sint(std::string_view use) {
+  auto t = peek();
+  if (!t.Is(Token::Type::kSintLiteral))
+    return add_error(t.source(), "expected signed integer literal", use);
+
+  next();
+  return {t.to_i32(), t.source()};
+}
+
+Expect<uint32_t> ParserImpl::expect_positive_sint(std::string_view use) {
+  auto sint = expect_sint(use);
+  if (sint.errored)
+    return Failure::kErrored;
+
+  if (sint.value < 0)
+    return add_error(sint.source, std::string(use) + " must be positive");
+
+  return {static_cast<uint32_t>(sint.value), sint.source};
+}
+
+Expect<uint32_t> ParserImpl::expect_nonzero_positive_sint(
+    std::string_view use) {
+  auto sint = expect_sint(use);
+  if (sint.errored)
+    return Failure::kErrored;
+
+  if (sint.value <= 0)
+    return add_error(sint.source, std::string(use) + " must be greater than 0");
+
+  return {static_cast<uint32_t>(sint.value), sint.source};
+}
+
+Expect<std::string> ParserImpl::expect_ident(std::string_view use) {
+  auto t = peek();
+  if (t.IsIdentifier()) {
+    synchronized_ = true;
+    next();
+
+    if (is_reserved(t)) {
+      return add_error(t.source(),
+                       "'" + t.to_str() + "' is a reserved keyword");
+    }
+
+    return {t.to_str(), t.source()};
+  }
+  synchronized_ = false;
+  return add_error(t.source(), "expected identifier", use);
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_block(Token::Type start,
+                           Token::Type end,
+                           std::string_view use,
+                           F&& body) {
+  if (!expect(use, start)) {
+    return Failure::kErrored;
+  }
+
+  return sync(end, [&]() -> T {
+    auto res = body();
+
+    if (res.errored)
+      return Failure::kErrored;
+
+    if (!expect(use, end))
+      return Failure::kErrored;
+
+    return res;
+  });
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_paren_block(std::string_view use, F&& body) {
+  return expect_block(Token::Type::kParenLeft, Token::Type::kParenRight, use,
+                      std::forward<F>(body));
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_brace_block(std::string_view use, F&& body) {
+  return expect_block(Token::Type::kBraceLeft, Token::Type::kBraceRight, use,
+                      std::forward<F>(body));
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_lt_gt_block(std::string_view use, F&& body) {
+  return expect_block(Token::Type::kLessThan, Token::Type::kGreaterThan, use,
+                      std::forward<F>(body));
+}
+
+template <typename F, typename T>
+T ParserImpl::sync(Token::Type tok, F&& body) {
+  if (parse_depth_ >= kMaxParseDepth) {
+    // We've hit a maximum parser recursive depth.
+    // We can't call into body() as we might stack overflow.
+    // Instead, report an error...
+    add_error(peek(), "maximum parser recursive depth reached");
+    // ...and try to resynchronize. If we cannot resynchronize to `tok` then
+    // synchronized_ is set to false, and the parser knows that forward progress
+    // is not being made.
+    sync_to(tok, /* consume: */ true);
+    return Failure::kErrored;
+  }
+
+  sync_tokens_.push_back(tok);
+
+  ++parse_depth_;
+  auto result = body();
+  --parse_depth_;
+
+  if (sync_tokens_.back() != tok) {
+    TINT_ICE(Reader, builder_.Diagnostics()) << "sync_tokens is out of sync";
+  }
+  sync_tokens_.pop_back();
+
+  if (result.errored) {
+    sync_to(tok, /* consume: */ true);
+  }
+
+  return result;
+}
+
+bool ParserImpl::sync_to(Token::Type tok, bool consume) {
+  // Clear the synchronized state - gets set to true again on success.
+  synchronized_ = false;
+
+  BlockCounters counters;
+
+  for (size_t i = 0; i < kMaxResynchronizeLookahead; i++) {
+    auto t = peek(i);
+    if (counters.consume(t) > 0) {
+      continue;  // Nested block
+    }
+    if (!t.Is(tok) && !is_sync_token(t)) {
+      continue;  // Not a synchronization point
+    }
+
+    // Synchronization point found.
+
+    // Skip any tokens we don't understand, bringing us to just before the
+    // resync point.
+    while (i-- > 0) {
+      next();
+    }
+
+    // Is this synchronization token |tok|?
+    if (t.Is(tok)) {
+      if (consume) {
+        next();
+      }
+      synchronized_ = true;
+      return true;
+    }
+    break;
+  }
+
+  return false;
+}
+
+bool ParserImpl::is_sync_token(const Token& t) const {
+  for (auto r : sync_tokens_) {
+    if (t.Is(r)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+template <typename F, typename T>
+T ParserImpl::without_error(F&& body) {
+  silence_errors_++;
+  auto result = body();
+  silence_errors_--;
+  return result;
+}
+
+ParserImpl::MultiTokenSource ParserImpl::make_source_range() {
+  return MultiTokenSource(this);
+}
+
+ParserImpl::MultiTokenSource ParserImpl::make_source_range_from(
+    const Source& start) {
+  return MultiTokenSource(this, start);
+}
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
new file mode 100644
index 0000000..74eb18f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -0,0 +1,891 @@
+// Copyright 2020 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_READER_WGSL_PARSER_IMPL_H_
+#define SRC_TINT_READER_WGSL_PARSER_IMPL_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/wgsl/parser_impl_detail.h"
+#include "src/tint/reader/wgsl/token.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace ast {
+class AssignmentStatement;
+class BreakStatement;
+class CallStatement;
+class ContinueStatement;
+class IfStatement;
+class LoopStatement;
+class ReturnStatement;
+class SwitchStatement;
+class VariableDeclStatement;
+}  // namespace ast
+
+namespace reader {
+namespace wgsl {
+
+class Lexer;
+
+/// Struct holding information for a for loop
+struct ForHeader {
+  /// Constructor
+  /// @param init the initializer statement
+  /// @param cond the condition statement
+  /// @param cont the continuing statement
+  ForHeader(const ast::Statement* init,
+            const ast::Expression* cond,
+            const ast::Statement* cont);
+
+  ~ForHeader();
+
+  /// The for loop initializer
+  const ast::Statement* initializer = nullptr;
+  /// The for loop condition
+  const ast::Expression* condition = nullptr;
+  /// The for loop continuing statement
+  const ast::Statement* continuing = nullptr;
+};
+
+/// ParserImpl for WGSL source data
+class ParserImpl {
+  /// Failure holds enumerator values used for the constructing an Expect and
+  /// Match in an errored state.
+  struct Failure {
+    enum Errored { kErrored };
+    enum NoMatch { kNoMatch };
+  };
+
+ public:
+  /// Expect is the return type of the parser methods that are expected to
+  /// return a parsed value of type T, unless there was an parse error.
+  /// In the case of a parse error the called method will have called
+  /// add_error() and #errored will be set to true.
+  template <typename T>
+  struct Expect {
+    /// An alias to the templated type T.
+    using type = T;
+
+    /// Don't allow an Expect to take a nullptr.
+    inline Expect(std::nullptr_t) = delete;  // NOLINT
+
+    /// Constructor for a successful parse.
+    /// @param val the result value of the parse
+    /// @param s the optional source of the value
+    template <typename U>
+    inline Expect(U&& val, const Source& s = {})  // NOLINT
+        : value(std::forward<U>(val)), source(s) {}
+
+    /// Constructor for parse error.
+    inline Expect(Failure::Errored) : errored(true) {}  // NOLINT
+
+    /// Copy constructor
+    inline Expect(const Expect&) = default;
+    /// Move constructor
+    inline Expect(Expect&&) = default;
+    /// Assignment operator
+    /// @return this Expect
+    inline Expect& operator=(const Expect&) = default;
+    /// Assignment move operator
+    /// @return this Expect
+    inline Expect& operator=(Expect&&) = default;
+
+    /// @return a pointer to the returned value. If T is a pointer or
+    /// std::unique_ptr, operator->() automatically dereferences so that the
+    /// return type will always be a pointer to a non-pointer type. #errored
+    /// must be false to call.
+    inline typename detail::OperatorArrow<T>::type operator->() {
+      TINT_ASSERT(Reader, !errored);
+      return detail::OperatorArrow<T>::ptr(value);
+    }
+
+    /// The expected value of a successful parse.
+    /// Zero-initialized when there was a parse error.
+    T value{};
+    /// Optional source of the value.
+    Source source;
+    /// True if there was a error parsing.
+    bool errored = false;
+  };
+
+  /// Maybe is the return type of the parser methods that attempts to match a
+  /// grammar and return a parsed value of type T, or may parse part of the
+  /// grammar and then hit a parse error.
+  /// In the case of a successful grammar match, the Maybe will have #matched
+  /// set to true.
+  /// In the case of a parse error the called method will have called
+  /// add_error() and the Maybe will have #errored set to true.
+  template <typename T>
+  struct Maybe {
+    inline Maybe(std::nullptr_t) = delete;  // NOLINT
+
+    /// Constructor for a successful parse.
+    /// @param val the result value of the parse
+    /// @param s the optional source of the value
+    template <typename U>
+    inline Maybe(U&& val, const Source& s = {})  // NOLINT
+        : value(std::forward<U>(val)), source(s), matched(true) {}
+
+    /// Constructor for parse error state.
+    inline Maybe(Failure::Errored) : errored(true) {}  // NOLINT
+
+    /// Constructor for the no-match state.
+    inline Maybe(Failure::NoMatch) {}  // NOLINT
+
+    /// Constructor from an Expect.
+    /// @param e the Expect to copy this Maybe from
+    template <typename U>
+    inline Maybe(const Expect<U>& e)  // NOLINT
+        : value(e.value),
+          source(e.value),
+          errored(e.errored),
+          matched(!e.errored) {}
+
+    /// Move from an Expect.
+    /// @param e the Expect to move this Maybe from
+    template <typename U>
+    inline Maybe(Expect<U>&& e)  // NOLINT
+        : value(std::move(e.value)),
+          source(std::move(e.source)),
+          errored(e.errored),
+          matched(!e.errored) {}
+
+    /// Copy constructor
+    inline Maybe(const Maybe&) = default;
+    /// Move constructor
+    inline Maybe(Maybe&&) = default;
+    /// Assignment operator
+    /// @return this Maybe
+    inline Maybe& operator=(const Maybe&) = default;
+    /// Assignment move operator
+    /// @return this Maybe
+    inline Maybe& operator=(Maybe&&) = default;
+
+    /// @return a pointer to the returned value. If T is a pointer or
+    /// std::unique_ptr, operator->() automatically dereferences so that the
+    /// return type will always be a pointer to a non-pointer type. #errored
+    /// must be false to call.
+    inline typename detail::OperatorArrow<T>::type operator->() {
+      TINT_ASSERT(Reader, !errored);
+      return detail::OperatorArrow<T>::ptr(value);
+    }
+
+    /// The value of a successful parse.
+    /// Zero-initialized when there was a parse error.
+    T value{};
+    /// Optional source of the value.
+    Source source;
+    /// True if there was a error parsing.
+    bool errored = false;
+    /// True if there was a error parsing.
+    bool matched = false;
+  };
+
+  /// TypedIdentifier holds a parsed identifier and type. Returned by
+  /// variable_ident_decl().
+  struct TypedIdentifier {
+    /// Constructor
+    TypedIdentifier();
+    /// Copy constructor
+    /// @param other the FunctionHeader to copy
+    TypedIdentifier(const TypedIdentifier& other);
+    /// Constructor
+    /// @param type_in parsed type
+    /// @param name_in parsed identifier
+    /// @param source_in source to the identifier
+    TypedIdentifier(const ast::Type* type_in,
+                    std::string name_in,
+                    Source source_in);
+    /// Destructor
+    ~TypedIdentifier();
+
+    /// Parsed type. May be nullptr for inferred types.
+    const ast::Type* type = nullptr;
+    /// Parsed identifier.
+    std::string name;
+    /// Source to the identifier.
+    Source source;
+  };
+
+  /// FunctionHeader contains the parsed information for a function header.
+  struct FunctionHeader {
+    /// Constructor
+    FunctionHeader();
+    /// Copy constructor
+    /// @param other the FunctionHeader to copy
+    FunctionHeader(const FunctionHeader& other);
+    /// Constructor
+    /// @param src parsed header source
+    /// @param n function name
+    /// @param p function parameters
+    /// @param ret_ty function return type
+    /// @param ret_attrs return type attributes
+    FunctionHeader(Source src,
+                   std::string n,
+                   ast::VariableList p,
+                   const ast::Type* ret_ty,
+                   ast::AttributeList ret_attrs);
+    /// Destructor
+    ~FunctionHeader();
+    /// Assignment operator
+    /// @param other the FunctionHeader to copy
+    /// @returns this FunctionHeader
+    FunctionHeader& operator=(const FunctionHeader& other);
+
+    /// Parsed header source
+    Source source;
+    /// Function name
+    std::string name;
+    /// Function parameters
+    ast::VariableList params;
+    /// Function return type
+    const ast::Type* return_type = nullptr;
+    /// Function return type attributes
+    ast::AttributeList return_type_attributes;
+  };
+
+  /// VarDeclInfo contains the parsed information for variable declaration.
+  struct VarDeclInfo {
+    /// Constructor
+    VarDeclInfo();
+    /// Copy constructor
+    /// @param other the VarDeclInfo to copy
+    VarDeclInfo(const VarDeclInfo& other);
+    /// Constructor
+    /// @param source_in variable declaration source
+    /// @param name_in variable name
+    /// @param storage_class_in variable storage class
+    /// @param access_in variable access control
+    /// @param type_in variable type
+    VarDeclInfo(Source source_in,
+                std::string name_in,
+                ast::StorageClass storage_class_in,
+                ast::Access access_in,
+                const ast::Type* type_in);
+    /// Destructor
+    ~VarDeclInfo();
+
+    /// Variable declaration source
+    Source source;
+    /// Variable name
+    std::string name;
+    /// Variable storage class
+    ast::StorageClass storage_class = ast::StorageClass::kNone;
+    /// Variable access control
+    ast::Access access = ast::Access::kUndefined;
+    /// Variable type
+    const ast::Type* type = nullptr;
+  };
+
+  /// VariableQualifier contains the parsed information for a variable qualifier
+  struct VariableQualifier {
+    /// The variable's storage class
+    ast::StorageClass storage_class = ast::StorageClass::kNone;
+    /// The variable's access control
+    ast::Access access = ast::Access::kUndefined;
+  };
+
+  /// Creates a new parser using the given file
+  /// @param file the input source file to parse
+  explicit ParserImpl(Source::File const* file);
+  ~ParserImpl();
+
+  /// Run the parser
+  /// @returns true if the parse was successful, false otherwise.
+  bool Parse();
+
+  /// set_max_diagnostics sets the maximum number of reported errors before
+  /// aborting parsing.
+  /// @param limit the new maximum number of errors
+  void set_max_errors(size_t limit) { max_errors_ = limit; }
+
+  /// @return the number of maximum number of reported errors before aborting
+  /// parsing.
+  size_t get_max_errors() const { return max_errors_; }
+
+  /// @returns true if an error was encountered.
+  bool has_error() const { return builder_.Diagnostics().contains_errors(); }
+
+  /// @returns the parser error string
+  std::string error() const {
+    diag::Formatter formatter{{false, false, false, false}};
+    return formatter.format(builder_.Diagnostics());
+  }
+
+  /// @returns the Program. The program builder in the parser will be reset
+  /// after this.
+  Program program() { return Program(std::move(builder_)); }
+
+  /// @returns the program builder.
+  ProgramBuilder& builder() { return builder_; }
+
+  /// @returns the next token
+  Token next();
+  /// Peeks ahead and returns the token at `idx` ahead of the current position
+  /// @param idx the index of the token to return
+  /// @returns the token `idx` positions ahead without advancing
+  Token peek(size_t idx = 0);
+  /// Peeks ahead and returns true if the token at `idx` ahead of the current
+  /// position is |tok|
+  /// @param idx the index of the token to return
+  /// @param tok the token to look for
+  /// @returns true if the token `idx` positions ahead is |tok|
+  bool peek_is(Token::Type tok, size_t idx = 0);
+  /// @returns the last token that was returned by `next()`
+  Token last_token() const;
+  /// Appends an error at `t` with the message `msg`
+  /// @param t the token to associate the error with
+  /// @param msg the error message
+  /// @return `Failure::Errored::kError` so that you can combine an add_error()
+  /// call and return on the same line.
+  Failure::Errored add_error(const Token& t, const std::string& msg);
+  /// Appends an error raised when parsing `use` at `t` with the message
+  /// `msg`
+  /// @param source the source to associate the error with
+  /// @param msg the error message
+  /// @param use a description of what was being parsed when the error was
+  /// raised.
+  /// @return `Failure::Errored::kError` so that you can combine an add_error()
+  /// call and return on the same line.
+  Failure::Errored add_error(const Source& source,
+                             std::string_view msg,
+                             std::string_view use);
+  /// Appends an error at `source` with the message `msg`
+  /// @param source the source to associate the error with
+  /// @param msg the error message
+  /// @return `Failure::Errored::kError` so that you can combine an add_error()
+  /// call and return on the same line.
+  Failure::Errored add_error(const Source& source, const std::string& msg);
+  /// Appends a deprecated-language-feature warning at `source` with the message
+  /// `msg`
+  /// @param source the source to associate the error with
+  /// @param msg the warning message
+  void deprecated(const Source& source, const std::string& msg);
+  /// Parses the `translation_unit` grammar element
+  void translation_unit();
+  /// Parses the `global_decl` grammar element, erroring on parse failure.
+  /// @return true on parse success, otherwise an error.
+  Expect<bool> expect_global_decl();
+  /// Parses a `global_variable_decl` grammar element with the initial
+  /// `variable_attribute_list*` provided as `attrs`
+  /// @returns the variable parsed or nullptr
+  /// @param attrs the list of attributes for the variable declaration.
+  Maybe<const ast::Variable*> global_variable_decl(ast::AttributeList& attrs);
+  /// Parses a `global_constant_decl` grammar element with the initial
+  /// `variable_attribute_list*` provided as `attrs`
+  /// @returns the const object or nullptr
+  /// @param attrs the list of attributes for the constant declaration.
+  Maybe<const ast::Variable*> global_constant_decl(ast::AttributeList& attrs);
+  /// Parses a `variable_decl` grammar element
+  /// @param allow_inferred if true, do not fail if variable decl does not
+  /// specify type
+  /// @returns the parsed variable declaration info
+  Maybe<VarDeclInfo> variable_decl(bool allow_inferred = false);
+  /// Parses a `variable_ident_decl` grammar element, erroring on parse
+  /// failure.
+  /// @param use a description of what was being parsed if an error was raised.
+  /// @param allow_inferred if true, do not fail if variable decl does not
+  /// specify type
+  /// @returns the identifier and type parsed or empty otherwise
+  Expect<TypedIdentifier> expect_variable_ident_decl(
+      std::string_view use,
+      bool allow_inferred = false);
+  /// Parses a `variable_qualifier` grammar element
+  /// @returns the variable qualifier information
+  Maybe<VariableQualifier> variable_qualifier();
+  /// Parses a `type_alias` grammar element
+  /// @returns the type alias or nullptr on error
+  Maybe<const ast::Alias*> type_alias();
+  /// Parses a `type_decl` grammar element
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<const ast::Type*> type_decl();
+  /// Parses a `type_decl` grammar element with the given pre-parsed
+  /// attributes.
+  /// @param attrs the list of attributes for the type.
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<const ast::Type*> type_decl(ast::AttributeList& attrs);
+  /// Parses a `storage_class` grammar element, erroring on parse failure.
+  /// @param use a description of what was being parsed if an error was raised.
+  /// @returns the storage class or StorageClass::kNone if none matched
+  Expect<ast::StorageClass> expect_storage_class(std::string_view use);
+  /// Parses a `struct_decl` grammar element with the initial
+  /// `struct_attribute_decl*` provided as `attrs`.
+  /// @returns the struct type or nullptr on error
+  /// @param attrs the list of attributes for the struct declaration.
+  Maybe<const ast::Struct*> struct_decl(ast::AttributeList& attrs);
+  /// Parses a `struct_body_decl` grammar element, erroring on parse failure.
+  /// @returns the struct members
+  Expect<ast::StructMemberList> expect_struct_body_decl();
+  /// Parses a `struct_member` grammar element with the initial
+  /// `struct_member_attribute_decl+` provided as `attrs`, erroring on parse
+  /// failure.
+  /// @param attrs the list of attributes for the struct member.
+  /// @returns the struct member or nullptr
+  Expect<ast::StructMember*> expect_struct_member(ast::AttributeList& attrs);
+  /// Parses a `function_decl` grammar element with the initial
+  /// `function_attribute_decl*` provided as `attrs`.
+  /// @param attrs the list of attributes for the function declaration.
+  /// @returns the parsed function, nullptr otherwise
+  Maybe<const ast::Function*> function_decl(ast::AttributeList& attrs);
+  /// Parses a `texture_sampler_types` grammar element
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<const ast::Type*> texture_sampler_types();
+  /// Parses a `sampler_type` grammar element
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<const ast::Type*> sampler_type();
+  /// Parses a `multisampled_texture_type` grammar element
+  /// @returns returns the multisample texture dimension or kNone if none
+  /// matched.
+  Maybe<const ast::TextureDimension> multisampled_texture_type();
+  /// Parses a `sampled_texture_type` grammar element
+  /// @returns returns the sample texture dimension or kNone if none matched.
+  Maybe<const ast::TextureDimension> sampled_texture_type();
+  /// Parses a `storage_texture_type` grammar element
+  /// @returns returns the storage texture dimension.
+  /// Returns kNone if none matched.
+  Maybe<const ast::TextureDimension> storage_texture_type();
+  /// Parses a `depth_texture_type` grammar element
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<const ast::Type*> depth_texture_type();
+  /// Parses a 'texture_external_type' grammar element
+  /// @returns the parsed Type or nullptr if none matched
+  Maybe<const ast::Type*> external_texture_type();
+  /// Parses a `texel_format` grammar element
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns returns the texel format or kNone if none matched.
+  Expect<ast::TexelFormat> expect_texel_format(std::string_view use);
+  /// Parses a `function_header` grammar element
+  /// @returns the parsed function header
+  Maybe<FunctionHeader> function_header();
+  /// Parses a `param_list` grammar element, erroring on parse failure.
+  /// @returns the parsed variables
+  Expect<ast::VariableList> expect_param_list();
+  /// Parses a `param` grammar element, erroring on parse failure.
+  /// @returns the parsed variable
+  Expect<ast::Variable*> expect_param();
+  /// Parses a `pipeline_stage` grammar element, erroring if the next token does
+  /// not match a stage name.
+  /// @returns the pipeline stage.
+  Expect<ast::PipelineStage> expect_pipeline_stage();
+  /// Parses an access control identifier, erroring if the next token does not
+  /// match a valid access control.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the parsed access control.
+  Expect<ast::Access> expect_access(std::string_view use);
+  /// Parses a builtin identifier, erroring if the next token does not match a
+  /// valid builtin name.
+  /// @returns the parsed builtin.
+  Expect<ast::Builtin> expect_builtin();
+  /// Parses a `body_stmt` grammar element, erroring on parse failure.
+  /// @returns the parsed statements
+  Expect<ast::BlockStatement*> expect_body_stmt();
+  /// Parses a `paren_rhs_stmt` grammar element, erroring on parse failure.
+  /// @returns the parsed element or nullptr
+  Expect<const ast::Expression*> expect_paren_rhs_stmt();
+  /// Parses a `statements` grammar element
+  /// @returns the statements parsed
+  Expect<ast::StatementList> expect_statements();
+  /// Parses a `statement` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::Statement*> statement();
+  /// Parses a `break_stmt` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::BreakStatement*> break_stmt();
+  /// Parses a `return_stmt` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::ReturnStatement*> return_stmt();
+  /// Parses a `continue_stmt` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::ContinueStatement*> continue_stmt();
+  /// Parses a `variable_stmt` grammar element
+  /// @returns the parsed variable or nullptr
+  Maybe<const ast::VariableDeclStatement*> variable_stmt();
+  /// Parses a `if_stmt` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::IfStatement*> if_stmt();
+  /// Parses a list of `else_stmt` grammar elements
+  /// @returns the parsed statement or nullptr
+  Expect<ast::ElseStatementList> else_stmts();
+  /// Parses a `switch_stmt` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::SwitchStatement*> switch_stmt();
+  /// Parses a `switch_body` grammar element
+  /// @returns the parsed statement or nullptr
+  Maybe<const ast::CaseStatement*> switch_body();
+  /// Parses a `case_selectors` grammar element
+  /// @returns the list of literals
+  Expect<ast::CaseSelectorList> expect_case_selectors();
+  /// Parses a `case_body` grammar element
+  /// @returns the parsed statements
+  Maybe<const ast::BlockStatement*> case_body();
+  /// Parses a `func_call_stmt` grammar element
+  /// @returns the parsed function call or nullptr
+  Maybe<const ast::CallStatement*> func_call_stmt();
+  /// Parses a `loop_stmt` grammar element
+  /// @returns the parsed loop or nullptr
+  Maybe<const ast::LoopStatement*> loop_stmt();
+  /// Parses a `for_header` grammar element, erroring on parse failure.
+  /// @returns the parsed for header or nullptr
+  Expect<std::unique_ptr<ForHeader>> expect_for_header();
+  /// Parses a `for_stmt` grammar element
+  /// @returns the parsed for loop or nullptr
+  Maybe<const ast::ForLoopStatement*> for_stmt();
+  /// Parses a `continuing_stmt` grammar element
+  /// @returns the parsed statements
+  Maybe<const ast::BlockStatement*> continuing_stmt();
+  /// Parses a `const_literal` grammar element
+  /// @returns the const literal parsed or nullptr if none found
+  Maybe<const ast::LiteralExpression*> const_literal();
+  /// Parses a `const_expr` grammar element, erroring on parse failure.
+  /// @returns the parsed constructor expression or nullptr on error
+  Expect<const ast::Expression*> expect_const_expr();
+  /// Parses a `primary_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> primary_expression();
+  /// Parses a `argument_expression_list` grammar element, erroring on parse
+  /// failure.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the list of arguments
+  Expect<ast::ExpressionList> expect_argument_expression_list(
+      std::string_view use);
+  /// Parses the recursive portion of the postfix_expression
+  /// @param prefix the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> postfix_expression(
+      const ast::Expression* prefix);
+  /// Parses a `singular_expression` grammar elment
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> singular_expression();
+  /// Parses a `unary_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> unary_expression();
+  /// Parses the recursive part of the `multiplicative_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_multiplicative_expr(
+      const ast::Expression* lhs);
+  /// Parses the `multiplicative_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> multiplicative_expression();
+  /// Parses the recursive part of the `additive_expression`, erroring on parse
+  /// failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_additive_expr(
+      const ast::Expression* lhs);
+  /// Parses the `additive_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> additive_expression();
+  /// Parses the recursive part of the `shift_expression`, erroring on parse
+  /// failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_shift_expr(const ast::Expression* lhs);
+  /// Parses the `shift_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> shift_expression();
+  /// Parses the recursive part of the `relational_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_relational_expr(
+      const ast::Expression* lhs);
+  /// Parses the `relational_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> relational_expression();
+  /// Parses the recursive part of the `equality_expression`, erroring on parse
+  /// failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_equality_expr(
+      const ast::Expression* lhs);
+  /// Parses the `equality_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> equality_expression();
+  /// Parses the recursive part of the `and_expression`, erroring on parse
+  /// failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_and_expr(const ast::Expression* lhs);
+  /// Parses the `and_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> and_expression();
+  /// Parses the recursive part of the `exclusive_or_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_exclusive_or_expr(
+      const ast::Expression* lhs);
+  /// Parses the `exclusive_or_expression` grammar elememnt
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> exclusive_or_expression();
+  /// Parses the recursive part of the `inclusive_or_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_inclusive_or_expr(
+      const ast::Expression* lhs);
+  /// Parses the `inclusive_or_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> inclusive_or_expression();
+  /// Parses the recursive part of the `logical_and_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_logical_and_expr(
+      const ast::Expression* lhs);
+  /// Parses a `logical_and_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> logical_and_expression();
+  /// Parses the recursive part of the `logical_or_expression`, erroring on
+  /// parse failure.
+  /// @param lhs the left side of the expression
+  /// @returns the parsed expression or nullptr
+  Expect<const ast::Expression*> expect_logical_or_expr(
+      const ast::Expression* lhs);
+  /// Parses a `logical_or_expression` grammar element
+  /// @returns the parsed expression or nullptr
+  Maybe<const ast::Expression*> logical_or_expression();
+  /// Parses a `assignment_stmt` grammar element
+  /// @returns the parsed assignment or nullptr
+  Maybe<const ast::AssignmentStatement*> assignment_stmt();
+  /// Parses one or more attribute lists.
+  /// @return the parsed attribute list, or an empty list on error.
+  Maybe<ast::AttributeList> attribute_list();
+  /// Parses a list of attributes between `ATTR_LEFT` and `ATTR_RIGHT`
+  /// brackets.
+  /// @param attrs the list to append newly parsed attributes to.
+  /// @return true if any attributes were be parsed, otherwise false.
+  Maybe<bool> attribute_bracketed_list(ast::AttributeList& attrs);
+  /// Parses a single attribute of the following types:
+  /// * `struct_attribute`
+  /// * `struct_member_attribute`
+  /// * `array_attribute`
+  /// * `variable_attribute`
+  /// * `global_const_attribute`
+  /// * `function_attribute`
+  /// @return the parsed attribute, or nullptr.
+  Maybe<const ast::Attribute*> attribute();
+  /// Parses a single attribute, reporting an error if the next token does not
+  /// represent a attribute.
+  /// @see #attribute for the full list of attributes this method parses.
+  /// @return the parsed attribute, or nullptr on error.
+  Expect<const ast::Attribute*> expect_attribute();
+
+ private:
+  /// ReturnType resolves to the return type for the function or lambda F.
+  template <typename F>
+  using ReturnType = typename std::invoke_result<F>::type;
+
+  /// ResultType resolves to `T` for a `RESULT` of type Expect<T>.
+  template <typename RESULT>
+  using ResultType = typename RESULT::type;
+
+  /// @returns true and consumes the next token if it equals `tok`
+  /// @param source if not nullptr, the next token's source is written to this
+  /// pointer, regardless of success or error
+  bool match(Token::Type tok, Source* source = nullptr);
+  /// Errors if the next token is not equal to `tok`
+  /// Consumes the next token on match.
+  /// expect() also updates #synchronized_, setting it to `true` if the next
+  /// token is equal to `tok`, otherwise `false`.
+  /// @param use a description of what was being parsed if an error was raised.
+  /// @param tok the token to test against
+  /// @returns true if the next token equals `tok`
+  bool expect(std::string_view use, Token::Type tok);
+  /// Parses a signed integer from the next token in the stream, erroring if the
+  /// next token is not a signed integer.
+  /// Consumes the next token on match.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the parsed integer.
+  Expect<int32_t> expect_sint(std::string_view use);
+  /// Parses a signed integer from the next token in the stream, erroring if
+  /// the next token is not a signed integer or is negative.
+  /// Consumes the next token if it is a signed integer (not necessarily
+  /// negative).
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the parsed integer.
+  Expect<uint32_t> expect_positive_sint(std::string_view use);
+  /// Parses a non-zero signed integer from the next token in the stream,
+  /// erroring if the next token is not a signed integer or is less than 1.
+  /// Consumes the next token if it is a signed integer (not necessarily
+  /// >= 1).
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the parsed integer.
+  Expect<uint32_t> expect_nonzero_positive_sint(std::string_view use);
+  /// Errors if the next token is not an identifier.
+  /// Consumes the next token on match.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @returns the parsed identifier.
+  Expect<std::string> expect_ident(std::string_view use);
+  /// Parses a lexical block starting with the token `start` and ending with
+  /// the token `end`. `body` is called to parse the lexical block body
+  /// between the `start` and `end` tokens. If the `start` or `end` tokens
+  /// are not matched then an error is generated and a zero-initialized `T` is
+  /// returned. If `body` raises an error while parsing then a zero-initialized
+  /// `T` is returned.
+  /// @param start the token that begins the lexical block
+  /// @param end the token that ends the lexical block
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
+  /// @return the value returned by `body` if no errors are raised, otherwise
+  /// an Expect with error state.
+  template <typename F, typename T = ReturnType<F>>
+  T expect_block(Token::Type start,
+                 Token::Type end,
+                 std::string_view use,
+                 F&& body);
+  /// A convenience function that calls expect_block() passing
+  /// `Token::Type::kParenLeft` and `Token::Type::kParenRight` for the `start`
+  /// and `end` arguments, respectively.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
+  /// @return the value returned by `body` if no errors are raised, otherwise
+  /// an Expect with error state.
+  template <typename F, typename T = ReturnType<F>>
+  T expect_paren_block(std::string_view use, F&& body);
+  /// A convenience function that calls `expect_block` passing
+  /// `Token::Type::kBraceLeft` and `Token::Type::kBraceRight` for the `start`
+  /// and `end` arguments, respectively.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
+  /// @return the value returned by `body` if no errors are raised, otherwise
+  /// an Expect with error state.
+  template <typename F, typename T = ReturnType<F>>
+  T expect_brace_block(std::string_view use, F&& body);
+  /// A convenience function that calls `expect_block` passing
+  /// `Token::Type::kLessThan` and `Token::Type::kGreaterThan` for the `start`
+  /// and `end` arguments, respectively.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature: `Expect<Result>()` or `Maybe<Result>()`.
+  /// @return the value returned by `body` if no errors are raised, otherwise
+  /// an Expect with error state.
+  template <typename F, typename T = ReturnType<F>>
+  T expect_lt_gt_block(std::string_view use, F&& body);
+
+  /// sync() calls the function `func`, and attempts to resynchronize the
+  /// parser to the next found resynchronization token if `func` fails. If the
+  /// next found resynchronization token is `tok`, then sync will also consume
+  /// `tok`.
+  ///
+  /// sync() will transiently add `tok` to the parser's stack of
+  /// synchronization tokens for the duration of the call to `func`. Once @p
+  /// func returns,
+  /// `tok` is removed from the stack of resynchronization tokens. sync calls
+  /// may be nested, and so the number of resynchronization tokens is equal to
+  /// the number of sync() calls in the current stack frame.
+  ///
+  /// sync() updates #synchronized_, setting it to `true` if the next
+  /// resynchronization token found was `tok`, otherwise `false`.
+  ///
+  /// @param tok the token to attempt to synchronize the parser to if `func`
+  /// fails.
+  /// @param func a function or lambda with the signature: `Expect<Result>()` or
+  /// `Maybe<Result>()`.
+  /// @return the value returned by `func`
+  template <typename F, typename T = ReturnType<F>>
+  T sync(Token::Type tok, F&& func);
+  /// sync_to() attempts to resynchronize the parser to the next found
+  /// resynchronization token or `tok` (whichever comes first).
+  ///
+  /// Synchronization tokens are transiently defined by calls to sync().
+  ///
+  /// sync_to() updates #synchronized_, setting it to `true` if a
+  /// resynchronization token was found and it was `tok`, otherwise `false`.
+  ///
+  /// @param tok the token to attempt to synchronize the parser to.
+  /// @param consume if true and the next found resynchronization token is
+  /// `tok` then sync_to() will also consume `tok`.
+  /// @return the state of #synchronized_.
+  /// @see sync().
+  bool sync_to(Token::Type tok, bool consume);
+  /// @return true if `t` is in the stack of resynchronization tokens.
+  /// @see sync().
+  bool is_sync_token(const Token& t) const;
+
+  /// @returns true if #synchronized_ is true and the number of reported errors
+  /// is less than #max_errors_.
+  bool continue_parsing() {
+    return synchronized_ && builder_.Diagnostics().error_count() < max_errors_;
+  }
+
+  /// without_error() calls the function `func` muting any grammatical errors
+  /// found while executing the function. This can be used as a best-effort to
+  /// produce a meaningful error message when the parser is out of sync.
+  /// @param func a function or lambda with the signature: `Expect<Result>()` or
+  /// `Maybe<Result>()`.
+  /// @return the value returned by `func`
+  template <typename F, typename T = ReturnType<F>>
+  T without_error(F&& func);
+
+  /// Reports an error if the attribute list `list` is not empty.
+  /// Used to ensure that all attributes are consumed.
+  bool expect_attributes_consumed(ast::AttributeList& list);
+
+  Expect<const ast::Type*> expect_type_decl_pointer(Token t);
+  Expect<const ast::Type*> expect_type_decl_atomic(Token t);
+  Expect<const ast::Type*> expect_type_decl_vector(Token t);
+  Expect<const ast::Type*> expect_type_decl_array(Token t,
+                                                  ast::AttributeList attrs);
+  Expect<const ast::Type*> expect_type_decl_matrix(Token t);
+
+  Expect<const ast::Type*> expect_type(std::string_view use);
+
+  Maybe<const ast::Statement*> non_block_statement();
+  Maybe<const ast::Statement*> for_header_initializer();
+  Maybe<const ast::Statement*> for_header_continuing();
+
+  class MultiTokenSource;
+  MultiTokenSource make_source_range();
+  MultiTokenSource make_source_range_from(const Source& start);
+
+  /// Creates a new `ast::Node` owned by the Module. When the Module is
+  /// destructed, the `ast::Node` will also be destructed.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  T* create(ARGS&&... args) {
+    return builder_.create<T>(std::forward<ARGS>(args)...);
+  }
+
+  std::unique_ptr<Lexer> lexer_;
+  std::deque<Token> token_queue_;
+  Token last_token_;
+  bool synchronized_ = true;
+  uint32_t parse_depth_ = 0;
+  std::vector<Token::Type> sync_tokens_;
+  int silence_errors_ = 0;
+  ProgramBuilder builder_;
+  size_t max_errors_ = 25;
+};
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_PARSER_IMPL_H_
diff --git a/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc b/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc
new file mode 100644
index 0000000..f0c9842
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_Plus) {
+  auto p = parser("a + true");
+  auto e = p->additive_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_Minus) {
+  auto p = parser("a - true");
+  auto e = p->additive_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_InvalidLHS) {
+  auto p = parser("if (a) {} + true");
+  auto e = p->additive_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_InvalidRHS) {
+  auto p = parser("true + if (a) {}");
+  auto e = p->additive_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of + expression");
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->additive_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_and_expression_test.cc b/src/tint/reader/wgsl/parser_impl_and_expression_test.cc
new file mode 100644
index 0000000..4991a5b
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_and_expression_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AndExpression_Parses) {
+  auto p = parser("a & true");
+  auto e = p->and_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kAnd, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, AndExpression_InvalidLHS) {
+  auto p = parser("if (a) {} & true");
+  auto e = p->and_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, AndExpression_InvalidRHS) {
+  auto p = parser("true & if (a) {}");
+  auto e = p->and_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of & expression");
+}
+
+TEST_F(ParserImplTest, AndExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->and_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc b/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc
new file mode 100644
index 0000000..e52fe97
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ArgumentExpressionList_Parses) {
+  auto p = parser("(a)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+
+  ASSERT_EQ(e.value.size(), 1u);
+  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_ParsesEmptyList) {
+  auto p = parser("()");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+
+  ASSERT_EQ(e.value.size(), 0u);
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_ParsesMultiple) {
+  auto p = parser("(a, -33, 1+2)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+
+  ASSERT_EQ(e.value.size(), 3u);
+  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
+  ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
+  ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_TrailingComma) {
+  auto p = parser("(a, 42,)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+
+  ASSERT_EQ(e.value.size(), 2u);
+  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
+  ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingLeftParen) {
+  auto p = parser("a)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:1: expected '(' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingRightParen) {
+  auto p = parser("(a");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:3: expected ')' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_0) {
+  auto p = parser("(,)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:2: expected ')' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_1) {
+  auto p = parser("(a, ,)");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:5: expected ')' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesInvalidExpression) {
+  auto p = parser("(if(a) {})");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:2: expected ')' for argument list");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
new file mode 100644
index 0000000..f2e950d
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -0,0 +1,142 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AssignmentStmt_Parses_ToVariable) {
+  auto p = parser("a = 123");
+  auto e = p->assignment_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
+  ASSERT_NE(e->lhs, nullptr);
+  ASSERT_NE(e->rhs, nullptr);
+
+  ASSERT_TRUE(e->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = e->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_NE(e->rhs, nullptr);
+  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
+}
+
+TEST_F(ParserImplTest, AssignmentStmt_Parses_ToMember) {
+  auto p = parser("a.b.c[2].d = 123");
+  auto e = p->assignment_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
+  ASSERT_NE(e->lhs, nullptr);
+  ASSERT_NE(e->rhs, nullptr);
+
+  ASSERT_NE(e->rhs, nullptr);
+  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
+
+  ASSERT_TRUE(e->lhs->Is<ast::MemberAccessorExpression>());
+  auto* mem = e->lhs->As<ast::MemberAccessorExpression>();
+
+  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
+  auto* ident = mem->member->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
+
+  ASSERT_TRUE(mem->structure->Is<ast::IndexAccessorExpression>());
+  auto* idx = mem->structure->As<ast::IndexAccessorExpression>();
+
+  ASSERT_NE(idx->index, nullptr);
+  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
+
+  ASSERT_TRUE(idx->object->Is<ast::MemberAccessorExpression>());
+  mem = idx->object->As<ast::MemberAccessorExpression>();
+  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
+  ident = mem->member->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+
+  ASSERT_TRUE(mem->structure->Is<ast::MemberAccessorExpression>());
+  mem = mem->structure->As<ast::MemberAccessorExpression>();
+
+  ASSERT_TRUE(mem->structure->Is<ast::IdentifierExpression>());
+  ident = mem->structure->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
+  ident = mem->member->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, AssignmentStmt_Parses_ToPhony) {
+  auto p = parser("_ = 123");
+  auto e = p->assignment_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
+  ASSERT_NE(e->lhs, nullptr);
+  ASSERT_NE(e->rhs, nullptr);
+
+  ASSERT_NE(e->rhs, nullptr);
+  ASSERT_TRUE(e->rhs->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(e->rhs->As<ast::SintLiteralExpression>()->value, 123);
+
+  ASSERT_TRUE(e->lhs->Is<ast::PhonyExpression>());
+}
+
+TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
+  auto p = parser("a.b.c[2].d 123");
+  auto e = p->assignment_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected '=' for assignment");
+}
+
+TEST_F(ParserImplTest, AssignmentStmt_InvalidLHS) {
+  auto p = parser("if (true) {} = 123");
+  auto e = p->assignment_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, AssignmentStmt_InvalidRHS) {
+  auto p = parser("a.b.c[2].d = if (true) {}");
+  auto e = p->assignment_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: unable to parse right side of assignment");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_body_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_body_stmt_test.cc
new file mode 100644
index 0000000..0404e1a
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_body_stmt_test.cc
@@ -0,0 +1,63 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, BodyStmt) {
+  auto p = parser(R"({
+  discard;
+  return 1 + b / 2;
+})");
+  auto e = p->expect_body_stmt();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_EQ(e->statements.size(), 2u);
+  EXPECT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
+  EXPECT_TRUE(e->statements[1]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, BodyStmt_Empty) {
+  auto p = parser("{}");
+  auto e = p->expect_body_stmt();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  EXPECT_EQ(e->statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, BodyStmt_InvalidStmt) {
+  auto p = parser("{fn main() {}}");
+  auto e = p->expect_body_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:2: expected '}'");
+}
+
+TEST_F(ParserImplTest, BodyStmt_MissingRightParen) {
+  auto p = parser("{return;");
+  auto e = p->expect_body_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:9: expected '}'");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc
new file mode 100644
index 0000000..0e26372
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, BreakStmt) {
+  auto p = parser("break");
+  auto e = p->break_stmt();
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::BreakStatement>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_bug_cases_test.cc b/src/tint/reader/wgsl/parser_impl_bug_cases_test.cc
new file mode 100644
index 0000000..22e4b66
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_bug_cases_test.cc
@@ -0,0 +1,32 @@
+// 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Bug_chromium_1180130) {
+  auto p = parser(
+      R"(a;{}}a;}};{{{;{}};{};{}}a;{}};{{{}};{{{;{}};{};{}}a;{}};{{{}}{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{{{;u[([[,a;{}}a;{}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{z{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}}{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}}i;{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};{}{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{{;u[({}};{{{}};{{}a;{}};{{{}};{{{;{}};{};}a;{}};{{{}};{{;u[[a,([}};{{{;{}})");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
new file mode 100644
index 0000000..f6cf061
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Statement_Call) {
+  auto p = parser("a();");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 1u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 2u);
+
+  ASSERT_TRUE(e->Is<ast::CallStatement>());
+  auto* c = e->As<ast::CallStatement>()->expr;
+
+  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+
+  EXPECT_EQ(c->args.size(), 0u);
+}
+
+TEST_F(ParserImplTest, Statement_Call_WithParams) {
+  auto p = parser("a(1, b, 2 + 3 / b);");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+
+  ASSERT_TRUE(e->Is<ast::CallStatement>());
+  auto* c = e->As<ast::CallStatement>()->expr;
+
+  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+
+  EXPECT_EQ(c->args.size(), 3u);
+  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
+  EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, Statement_Call_WithParams_TrailingComma) {
+  auto p = parser("a(1, b,);");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+
+  ASSERT_TRUE(e->Is<ast::CallStatement>());
+  auto* c = e->As<ast::CallStatement>()->expr;
+
+  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+
+  EXPECT_EQ(c->args.size(), 2u);
+  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
+}
+
+TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) {
+  auto p = parser("a(");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
+}
+
+TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
+  auto p = parser("a()");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(p->error(), "1:4: expected ';' for function call");
+}
+
+TEST_F(ParserImplTest, Statement_Call_Bad_ArgList) {
+  auto p = parser("a(b c);");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(p->error(), "1:5: expected ')' for function call");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_case_body_test.cc b/src/tint/reader/wgsl/parser_impl_case_body_test.cc
new file mode 100644
index 0000000..254c314
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_case_body_test.cc
@@ -0,0 +1,78 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, CaseBody_Empty) {
+  auto p = parser("");
+  auto e = p->case_body();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  EXPECT_EQ(e->statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, CaseBody_Statements) {
+  auto p = parser(R"(
+  var a: i32;
+  a = 2;)");
+
+  auto e = p->case_body();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  ASSERT_EQ(e->statements.size(), 2u);
+  EXPECT_TRUE(e->statements[0]->Is<ast::VariableDeclStatement>());
+  EXPECT_TRUE(e->statements[1]->Is<ast::AssignmentStatement>());
+}
+
+TEST_F(ParserImplTest, CaseBody_InvalidStatement) {
+  auto p = parser("a =");
+  auto e = p->case_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, CaseBody_Fallthrough) {
+  auto p = parser("fallthrough;");
+  auto e = p->case_body();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  ASSERT_EQ(e->statements.size(), 1u);
+  EXPECT_TRUE(e->statements[0]->Is<ast::FallthroughStatement>());
+}
+
+TEST_F(ParserImplTest, CaseBody_Fallthrough_MissingSemicolon) {
+  auto p = parser("fallthrough");
+  auto e = p->case_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected ';' for fallthrough statement");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_const_expr_test.cc b/src/tint/reader/wgsl/parser_impl_const_expr_test.cc
new file mode 100644
index 0000000..da853dc
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_const_expr_test.cc
@@ -0,0 +1,174 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl) {
+  auto p = parser("vec2<f32>(1., 2.)");
+  auto e = p->expect_const_expr();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+
+  auto* t = e->As<ast::CallExpression>();
+  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
+  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
+
+  ASSERT_EQ(t->args.size(), 2u);
+
+  ASSERT_TRUE(t->args[0]->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(t->args[0]->As<ast::FloatLiteralExpression>()->value, 1.);
+
+  ASSERT_TRUE(t->args[1]->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(t->args[1]->As<ast::FloatLiteralExpression>()->value, 2.);
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl_Empty) {
+  auto p = parser("vec2<f32>()");
+  auto e = p->expect_const_expr();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+
+  auto* t = e->As<ast::CallExpression>();
+  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
+  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
+
+  ASSERT_EQ(t->args.size(), 0u);
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl_TrailingComma) {
+  auto p = parser("vec2<f32>(1., 2.,)");
+  auto e = p->expect_const_expr();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+
+  auto* t = e->As<ast::CallExpression>();
+  ASSERT_TRUE(t->target.type->Is<ast::Vector>());
+  EXPECT_EQ(t->target.type->As<ast::Vector>()->width, 2u);
+
+  ASSERT_EQ(t->args.size(), 2u);
+  ASSERT_TRUE(t->args[0]->Is<ast::LiteralExpression>());
+  ASSERT_TRUE(t->args[1]->Is<ast::LiteralExpression>());
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingRightParen) {
+  auto p = parser("vec2<f32>(1., 2.");
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:17: expected ')' for type constructor");
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingLeftParen) {
+  auto p = parser("vec2<f32> 1., 2.)");
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor");
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingComma) {
+  auto p = parser("vec2<f32>(1. 2.");
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:14: expected ')' for type constructor");
+}
+
+TEST_F(ParserImplTest, ConstExpr_InvalidExpr) {
+  auto p = parser("vec2<f32>(1., if(a) {})");
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:15: invalid type for const_expr");
+}
+
+TEST_F(ParserImplTest, ConstExpr_ConstLiteral) {
+  auto p = parser("true");
+  auto e = p->expect_const_expr();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->Is<ast::BoolLiteralExpression>());
+  EXPECT_TRUE(e.value->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ConstExpr_ConstLiteral_Invalid) {
+  auto p = parser("invalid");
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:1: unable to parse const_expr");
+}
+
+TEST_F(ParserImplTest, ConstExpr_TypeConstructor) {
+  auto p = parser("S(0)");
+
+  auto e = p->expect_const_expr();
+  ASSERT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  ASSERT_NE(e->As<ast::CallExpression>()->target.type, nullptr);
+  ASSERT_TRUE(e->As<ast::CallExpression>()->target.type->Is<ast::TypeName>());
+  EXPECT_EQ(
+      e->As<ast::CallExpression>()->target.type->As<ast::TypeName>()->name,
+      p->builder().Symbols().Get("S"));
+}
+
+TEST_F(ParserImplTest, ConstExpr_Recursion) {
+  std::stringstream out;
+  for (size_t i = 0; i < 200; i++) {
+    out << "f32(";
+  }
+  out << "1.0";
+  for (size_t i = 0; i < 200; i++) {
+    out << ")";
+  }
+  auto p = parser(out.str());
+  auto e = p->expect_const_expr();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:517: maximum parser recursive depth reached");
+}
+
+TEST_F(ParserImplTest, UnaryOp_Recursion) {
+  std::stringstream out;
+  for (size_t i = 0; i < 200; i++) {
+    out << "!";
+  }
+  out << "1.0";
+  auto p = parser(out.str());
+  auto e = p->unary_expression();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:130: maximum parser recursive depth reached");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
new file mode 100644
index 0000000..8dc1ceb
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
@@ -0,0 +1,525 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+#include <cmath>
+#include <cstring>
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+// Makes an IEEE 754 binary32 floating point number with
+// - 0 sign if sign is 0, 1 otherwise
+// - 'exponent_bits' is placed in the exponent space.
+//   So, the exponent bias must already be included.
+float MakeFloat(int sign, int biased_exponent, int mantissa) {
+  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
+  // The binary32 exponent is 8 bits, just below the sign.
+  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
+  // The mantissa is the bottom 23 bits.
+  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
+
+  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
+  float result = 0.0f;
+  static_assert(sizeof(result) == sizeof(bits),
+                "expected float and uint32_t to be the same size");
+  std::memcpy(&result, &bits, sizeof(bits));
+  return result;
+}
+
+TEST_F(ParserImplTest, ConstLiteral_Int) {
+  auto p = parser("-234");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, -234);
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_Uint) {
+  auto p = parser("234u");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::UintLiteralExpression>());
+  EXPECT_EQ(c->As<ast::UintLiteralExpression>()->value, 234u);
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_Float) {
+  auto p = parser("234.e12");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value, 234e12f);
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_IncompleteExponent) {
+  auto p = parser("1.0e+");
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_TRUE(c.errored);
+  EXPECT_EQ(p->error(),
+            "1:1: incomplete exponent for floating point literal: 1.0e+");
+  ASSERT_EQ(c.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooSmallMagnitude) {
+  auto p = parser("1e-256");
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_TRUE(c.errored);
+  EXPECT_EQ(p->error(),
+            "1:1: f32 (1e-256) magnitude too small, not representable");
+  ASSERT_EQ(c.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargeNegative) {
+  auto p = parser("-1.2e+256");
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_TRUE(c.errored);
+  EXPECT_EQ(p->error(), "1:1: f32 (-1.2e+256) too large (negative)");
+  ASSERT_EQ(c.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargePositive) {
+  auto p = parser("1.2e+256");
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_TRUE(c.errored);
+  EXPECT_EQ(p->error(), "1:1: f32 (1.2e+256) too large (positive)");
+  ASSERT_EQ(c.value, nullptr);
+}
+
+// Returns true if the given non-Nan float numbers are equal.
+bool FloatEqual(float a, float b) {
+  // Avoid Clang complaining about equality test on float.
+  // -Wfloat-equal.
+  return (a <= b) && (a >= b);
+}
+
+struct FloatLiteralTestCase {
+  std::string input;
+  float expected;
+  bool operator==(const FloatLiteralTestCase& other) const {
+    return (input == other.input) && FloatEqual(expected, other.expected);
+  }
+};
+
+inline std::ostream& operator<<(std::ostream& out, FloatLiteralTestCase data) {
+  out << data.input;
+  return out;
+}
+
+class ParserImplFloatLiteralTest
+    : public ParserImplTestWithParam<FloatLiteralTestCase> {};
+TEST_P(ParserImplFloatLiteralTest, Parse) {
+  auto params = GetParam();
+  SCOPED_TRACE(params.input);
+  auto p = parser(params.input);
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value, params.expected);
+}
+
+using FloatLiteralTestCaseList = std::vector<FloatLiteralTestCase>;
+
+FloatLiteralTestCaseList DecimalFloatCases() {
+  return FloatLiteralTestCaseList{
+      {"0.0", 0.0f},                         // Zero
+      {"1.0", 1.0f},                         // One
+      {"-1.0", -1.0f},                       // MinusOne
+      {"1000000000.0", 1e9f},                // Billion
+      {"-0.0", std::copysign(0.0f, -5.0f)},  // NegativeZero
+      {"0.0", MakeFloat(0, 0, 0)},           // Zero
+      {"-0.0", MakeFloat(1, 0, 0)},          // NegativeZero
+      {"1.0", MakeFloat(0, 127, 0)},         // One
+      {"-1.0", MakeFloat(1, 127, 0)},        // NegativeOne
+  };
+}
+
+INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_Float,
+                         ParserImplFloatLiteralTest,
+                         testing::ValuesIn(DecimalFloatCases()));
+
+const float NegInf = MakeFloat(1, 255, 0);
+const float PosInf = MakeFloat(0, 255, 0);
+FloatLiteralTestCaseList HexFloatCases() {
+  return FloatLiteralTestCaseList{
+      // Regular numbers
+      {"0x0p+0", 0.f},
+      {"0x1p+0", 1.f},
+      {"0x1p+1", 2.f},
+      {"0x1.8p+1", 3.f},
+      {"0x1.99999ap-4", 0.1f},
+      {"0x1p-1", 0.5f},
+      {"0x1p-2", 0.25f},
+      {"0x1.8p-1", 0.75f},
+      {"-0x0p+0", -0.f},
+      {"-0x1p+0", -1.f},
+      {"-0x1p-1", -0.5f},
+      {"-0x1p-2", -0.25f},
+      {"-0x1.8p-1", -0.75f},
+
+      // Large numbers
+      {"0x1p+9", 512.f},
+      {"0x1p+10", 1024.f},
+      {"0x1.02p+10", 1024.f + 8.f},
+      {"-0x1p+9", -512.f},
+      {"-0x1p+10", -1024.f},
+      {"-0x1.02p+10", -1024.f - 8.f},
+
+      // Small numbers
+      {"0x1p-9", 1.0f / 512.f},
+      {"0x1p-10", 1.0f / 1024.f},
+      {"0x1.02p-3", 1.0f / 1024.f + 1.0f / 8.f},
+      {"-0x1p-9", 1.0f / -512.f},
+      {"-0x1p-10", 1.0f / -1024.f},
+      {"-0x1.02p-3", 1.0f / -1024.f - 1.0f / 8.f},
+
+      // Near lowest non-denorm
+      {"0x1p-124", std::ldexp(1.f * 8.f, -127)},
+      {"0x1p-125", std::ldexp(1.f * 4.f, -127)},
+      {"-0x1p-124", -std::ldexp(1.f * 8.f, -127)},
+      {"-0x1p-125", -std::ldexp(1.f * 4.f, -127)},
+
+      // Lowest non-denorm
+      {"0x1p-126", std::ldexp(1.f * 2.f, -127)},
+      {"-0x1p-126", -std::ldexp(1.f * 2.f, -127)},
+
+      // Denormalized values
+      {"0x1p-127", std::ldexp(1.f, -127)},
+      {"0x1p-128", std::ldexp(1.f / 2.f, -127)},
+      {"0x1p-129", std::ldexp(1.f / 4.f, -127)},
+      {"0x1p-130", std::ldexp(1.f / 8.f, -127)},
+      {"-0x1p-127", -std::ldexp(1.f, -127)},
+      {"-0x1p-128", -std::ldexp(1.f / 2.f, -127)},
+      {"-0x1p-129", -std::ldexp(1.f / 4.f, -127)},
+      {"-0x1p-130", -std::ldexp(1.f / 8.f, -127)},
+
+      {"0x1.8p-127", std::ldexp(1.f, -127) + (std::ldexp(1.f, -127) / 2.f)},
+      {"0x1.8p-128",
+       std::ldexp(1.f, -127) / 2.f + (std::ldexp(1.f, -127) / 4.f)},
+
+      {"0x1p-149", MakeFloat(0, 0, 1)},                 // +SmallestDenormal
+      {"0x1p-148", MakeFloat(0, 0, 2)},                 // +BiggerDenormal
+      {"0x1.fffffcp-127", MakeFloat(0, 0, 0x7fffff)},   // +LargestDenormal
+      {"-0x1p-149", MakeFloat(1, 0, 1)},                // -SmallestDenormal
+      {"-0x1p-148", MakeFloat(1, 0, 2)},                // -BiggerDenormal
+      {"-0x1.fffffcp-127", MakeFloat(1, 0, 0x7fffff)},  // -LargestDenormal
+
+      {"0x1.2bfaf8p-127", MakeFloat(0, 0, 0xcafebe)},   // +Subnormal
+      {"-0x1.2bfaf8p-127", MakeFloat(1, 0, 0xcafebe)},  // -Subnormal
+      {"0x1.55554p-130", MakeFloat(0, 0, 0xaaaaa)},     // +Subnormal
+      {"-0x1.55554p-130", MakeFloat(1, 0, 0xaaaaa)},    // -Subnormal
+
+      // Nan -> Infinity
+      {"0x1.8p+128", PosInf},
+      {"0x1.0002p+128", PosInf},
+      {"0x1.0018p+128", PosInf},
+      {"0x1.01ep+128", PosInf},
+      {"0x1.fffffep+128", PosInf},
+      {"-0x1.8p+128", NegInf},
+      {"-0x1.0002p+128", NegInf},
+      {"-0x1.0018p+128", NegInf},
+      {"-0x1.01ep+128", NegInf},
+      {"-0x1.fffffep+128", NegInf},
+
+      // Infinity
+      {"0x1p+128", PosInf},
+      {"-0x1p+128", NegInf},
+      {"0x32p+127", PosInf},
+      {"0x32p+500", PosInf},
+      {"-0x32p+127", NegInf},
+      {"-0x32p+500", NegInf},
+
+      // Overflow -> Infinity
+      {"0x1p+129", PosInf},
+      {"0x1.1p+128", PosInf},
+      {"-0x1p+129", NegInf},
+      {"-0x1.1p+128", NegInf},
+      {"0x1.0p2147483520", PosInf},  // INT_MAX - 127 (largest valid exponent)
+
+      // Underflow -> Zero
+      {"0x1p-500", 0.f},  // Exponent underflows
+      {"-0x1p-500", -0.f},
+      {"0x0.00000000001p-126", 0.f},  // Fraction causes underflow
+      {"-0x0.0000000001p-127", -0.f},
+      {"0x0.01p-142", 0.f},
+      {"-0x0.01p-142", -0.f},    // Fraction causes additional underflow
+      {"0x1.0p-2147483520", 0},  // -(INT_MAX - 127) (smallest valid exponent)
+
+      // Zero with non-zero exponent -> Zero
+      {"0x0p+0", 0.f},
+      {"0x0p+1", 0.f},
+      {"0x0p-1", 0.f},
+      {"0x0p+9999999999", 0.f},
+      {"0x0p-9999999999", 0.f},
+      // Same, but with very large positive exponents that would cause overflow
+      // if the mantissa were non-zero.
+      {"0x0p+4000000000", 0.f},    // 4 billion:
+      {"0x0p+40000000000", 0.f},   // 40 billion
+      {"-0x0p+40000000000", 0.f},  // As above 2, but negative mantissa
+      {"-0x0p+400000000000", 0.f},
+      {"0x0.00p+4000000000", 0.f},  // As above 4, but with fractional part
+      {"0x0.00p+40000000000", 0.f},
+      {"-0x0.00p+40000000000", 0.f},
+      {"-0x0.00p+400000000000", 0.f},
+      {"0x0p-4000000000", 0.f},  // As above 8, but with negative exponents
+      {"0x0p-40000000000", 0.f},
+      {"-0x0p-40000000000", 0.f},
+      {"-0x0p-400000000000", 0.f},
+      {"0x0.00p-4000000000", 0.f},
+      {"0x0.00p-40000000000", 0.f},
+      {"-0x0.00p-40000000000", 0.f},
+      {"-0x0.00p-400000000000", 0.f},
+
+      // Test parsing
+      {"0x0p0", 0.f},
+      {"0x0p-0", 0.f},
+      {"0x0p+000", 0.f},
+      {"0x00000000000000p+000000000000000", 0.f},
+      {"0x00000000000000p-000000000000000", 0.f},
+      {"0x00000000000001p+000000000000000", 1.f},
+      {"0x00000000000001p-000000000000000", 1.f},
+      {"0x0000000000000000000001.99999ap-000000000000000004", 0.1f},
+      {"0x2p+0", 2.f},
+      {"0xFFp+0", 255.f},
+      {"0x0.8p+0", 0.5f},
+      {"0x0.4p+0", 0.25f},
+      {"0x0.4p+1", 2 * 0.25f},
+      {"0x0.4p+2", 4 * 0.25f},
+      {"0x123Ep+1", 9340.f},
+      {"-0x123Ep+1", -9340.f},
+      {"0x1a2b3cP12", 7.024656e+09f},
+      {"-0x1a2b3cP12", -7.024656e+09f},
+
+      // Examples without a binary exponent part.
+      {"0x1.", 1.0f},
+      {"0x.8", 0.5f},
+      {"0x1.8", 1.5f},
+      {"-0x1.", -1.0f},
+      {"-0x.8", -0.5f},
+      {"-0x1.8", -1.5f},
+
+      // Examples with a binary exponent and a 'f' suffix.
+      {"0x1.p0f", 1.0f},
+      {"0x.8p2f", 2.0f},
+      {"0x1.8p-1f", 0.75f},
+      {"0x2p-2f", 0.5f},  // No binary point
+      {"-0x1.p0f", -1.0f},
+      {"-0x.8p2f", -2.0f},
+      {"-0x1.8p-1f", -0.75f},
+      {"-0x2p-2f", -0.5f},  // No binary point
+  };
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat,
+                         ParserImplFloatLiteralTest,
+                         testing::ValuesIn(HexFloatCases()));
+
+// Now test all the same hex float cases, but with 0X instead of 0x
+template <typename ARR>
+std::vector<FloatLiteralTestCase> UpperCase0X(const ARR& cases) {
+  std::vector<FloatLiteralTestCase> result;
+  result.reserve(cases.size());
+  for (const auto& c : cases) {
+    result.emplace_back(c);
+    auto& input = result.back().input;
+    const auto where = input.find("0x");
+    if (where != std::string::npos) {
+      input[where+1] = 'X';
+    }
+  }
+  return result;
+}
+
+using UpperCase0XTest = ::testing::Test;
+TEST_F(UpperCase0XTest, Samples) {
+  const auto cases = FloatLiteralTestCaseList{
+      {"absent", 0.0}, {"0x", 1.0},      {"0X", 2.0},      {"-0x", 3.0},
+      {"-0X", 4.0},    {"  0x1p1", 5.0}, {"  -0x1p", 6.0}, {" examine ", 7.0}};
+  const auto expected = FloatLiteralTestCaseList{
+      {"absent", 0.0}, {"0X", 1.0},      {"0X", 2.0},      {"-0X", 3.0},
+      {"-0X", 4.0},    {"  0X1p1", 5.0}, {"  -0X1p", 6.0}, {" examine ", 7.0}};
+
+  auto result = UpperCase0X(cases);
+  EXPECT_THAT(result, ::testing::ElementsAreArray(expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat_UpperCase0X,
+                         ParserImplFloatLiteralTest,
+                         testing::ValuesIn(UpperCase0X(HexFloatCases())));
+
+struct InvalidLiteralTestCase {
+  const char* input;
+  const char* error_msg;
+};
+class ParserImplInvalidLiteralTest
+    : public ParserImplTestWithParam<InvalidLiteralTestCase> {};
+TEST_P(ParserImplInvalidLiteralTest, Parse) {
+  auto params = GetParam();
+  SCOPED_TRACE(params.input);
+  auto p = parser(params.input);
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_TRUE(c.errored);
+  EXPECT_EQ(p->error(), params.error_msg);
+  ASSERT_EQ(c.value, nullptr);
+}
+
+InvalidLiteralTestCase invalid_hexfloat_mantissa_too_large_cases[] = {
+    {"0x1.ffffffff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1f.fffffff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1ff.ffffff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1fff.fffff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1ffff.ffff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1fffff.fff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1ffffff.ff8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1fffffff.f8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1ffffffff.8p0", "1:1: mantissa is too large for hex float"},
+    {"0x1ffffffff8.p0", "1:1: mantissa is too large for hex float"},
+};
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplInvalidLiteralTest_HexFloatMantissaTooLarge,
+    ParserImplInvalidLiteralTest,
+    testing::ValuesIn(invalid_hexfloat_mantissa_too_large_cases));
+
+InvalidLiteralTestCase invalid_hexfloat_exponent_too_large_cases[] = {
+    {"0x1p+2147483521", "1:1: exponent is too large for hex float"},
+    {"0x1p-2147483521", "1:1: exponent is too large for hex float"},
+    {"0x1p+4294967296", "1:1: exponent is too large for hex float"},
+    {"0x1p-4294967296", "1:1: exponent is too large for hex float"},
+};
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplInvalidLiteralTest_HexFloatExponentTooLarge,
+    ParserImplInvalidLiteralTest,
+    testing::ValuesIn(invalid_hexfloat_exponent_too_large_cases));
+
+InvalidLiteralTestCase invalid_hexfloat_exponent_missing_cases[] = {
+    // Lower case p
+    {"0x0p", "1:1: expected an exponent value for hex float"},
+    {"0x0p+", "1:1: expected an exponent value for hex float"},
+    {"0x0p-", "1:1: expected an exponent value for hex float"},
+    {"0x1.0p", "1:1: expected an exponent value for hex float"},
+    {"0x0.1p", "1:1: expected an exponent value for hex float"},
+    // Upper case p
+    {"0x0P", "1:1: expected an exponent value for hex float"},
+    {"0x0P+", "1:1: expected an exponent value for hex float"},
+    {"0x0P-", "1:1: expected an exponent value for hex float"},
+    {"0x1.0P", "1:1: expected an exponent value for hex float"},
+    {"0x0.1P", "1:1: expected an exponent value for hex float"},
+};
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplInvalidLiteralTest_HexFloatExponentMissing,
+    ParserImplInvalidLiteralTest,
+    testing::ValuesIn(invalid_hexfloat_exponent_missing_cases));
+
+TEST_F(ParserImplTest, ConstLiteral_FloatHighest) {
+  const auto highest = std::numeric_limits<float>::max();
+  const auto expected_highest = 340282346638528859811704183484516925440.0f;
+  if (highest < expected_highest || highest > expected_highest) {
+    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+                    "this target";
+  }
+  auto p = parser("340282346638528859811704183484516925440.0");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value,
+                  std::numeric_limits<float>::max());
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 42u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_FloatLowest) {
+  // Some compilers complain if you test floating point numbers for equality.
+  // So say it via two inequalities.
+  const auto lowest = std::numeric_limits<float>::lowest();
+  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+  if (lowest < expected_lowest || lowest > expected_lowest) {
+    GTEST_SKIP()
+        << "std::numeric_limits<float>::lowest() is not as expected for "
+           "this target";
+  }
+
+  auto p = parser("-340282346638528859811704183484516925440.0");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
+  EXPECT_FLOAT_EQ(c->As<ast::FloatLiteralExpression>()->value,
+                  std::numeric_limits<float>::lowest());
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 43u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_True) {
+  auto p = parser("true");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::BoolLiteralExpression>());
+  EXPECT_TRUE(c->As<ast::BoolLiteralExpression>()->value);
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_False) {
+  auto p = parser("false");
+  auto c = p->const_literal();
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c->Is<ast::BoolLiteralExpression>());
+  EXPECT_FALSE(c->As<ast::BoolLiteralExpression>()->value);
+  EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 6u}}));
+}
+
+TEST_F(ParserImplTest, ConstLiteral_NoMatch) {
+  auto p = parser("another-token");
+  auto c = p->const_literal();
+  EXPECT_FALSE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(c.value, nullptr);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc
new file mode 100644
index 0000000..5708cb2
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ContinueStmt) {
+  auto p = parser("continue");
+  auto e = p->continue_stmt();
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::ContinueStatement>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
new file mode 100644
index 0000000..394603d
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ContinuingStmt) {
+  auto p = parser("continuing { discard; }");
+  auto e = p->continuing_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e->statements.size(), 1u);
+  ASSERT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
+}
+
+TEST_F(ParserImplTest, ContinuingStmt_InvalidBody) {
+  auto p = parser("continuing { discard }");
+  auto e = p->continuing_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:22: expected ';' for discard statement");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_depth_texture_type_test.cc b/src/tint/reader/wgsl/parser_impl_depth_texture_type_test.cc
new file mode 100644
index 0000000..3ae5478
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_depth_texture_type_test.cc
@@ -0,0 +1,99 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+#include "src/tint/sem/depth_texture_type.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, DepthTextureType_Invalid) {
+  auto p = parser("1234");
+  auto t = p->depth_texture_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, DepthTextureType_2d) {
+  auto p = parser("texture_depth_2d");
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, DepthTextureType_2dArray) {
+  auto p = parser("texture_depth_2d_array");
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2dArray);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 23u}}));
+}
+
+TEST_F(ParserImplTest, DepthTextureType_Cube) {
+  auto p = parser("texture_depth_cube");
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::kCube);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
+}
+
+TEST_F(ParserImplTest, DepthTextureType_CubeArray) {
+  auto p = parser("texture_depth_cube_array");
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::kCubeArray);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
+}
+
+TEST_F(ParserImplTest, DepthTextureType_Multisampled2d) {
+  auto p = parser("texture_depth_multisampled_2d");
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthMultisampledTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 30u}}));
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_detail.h b/src/tint/reader/wgsl/parser_impl_detail.h
new file mode 100644
index 0000000..285aa0a
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_detail.h
@@ -0,0 +1,70 @@
+// Copyright 2020 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_READER_WGSL_PARSER_IMPL_DETAIL_H_
+#define SRC_TINT_READER_WGSL_PARSER_IMPL_DETAIL_H_
+
+#include <memory>
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace detail {
+
+/// OperatorArrow is a traits helper for ParserImpl::Expect<T>::operator->() and
+/// ParserImpl::Maybe<T>::operator->() so that pointer types are automatically
+/// dereferenced. This simplifies usage by allowing
+///  `result.value->field`
+/// to be written as:
+///  `result->field`
+/// As well as reducing the amount of code, using the operator->() asserts that
+/// the Expect<T> or Maybe<T> is not in an error state before dereferencing.
+template <typename T>
+struct OperatorArrow {
+  /// type resolves to the return type for the operator->()
+  using type = T*;
+  /// @param val the value held by `ParserImpl::Expect<T>` or
+  /// `ParserImpl::Maybe<T>`.
+  /// @return a pointer to `val`
+  static inline T* ptr(T& val) { return &val; }
+};
+
+/// OperatorArrow template specialization for std::unique_ptr<>.
+template <typename T>
+struct OperatorArrow<std::unique_ptr<T>> {
+  /// type resolves to the return type for the operator->()
+  using type = T*;
+  /// @param val the value held by `ParserImpl::Expect<T>` or
+  /// `ParserImpl::Maybe<T>`.
+  /// @return the raw pointer held by `val`.
+  static inline T* ptr(std::unique_ptr<T>& val) { return val.get(); }
+};
+
+/// OperatorArrow template specialization for T*.
+template <typename T>
+struct OperatorArrow<T*> {
+  /// type resolves to the return type for the operator->()
+  using type = T*;
+  /// @param val the value held by `ParserImpl::Expect<T>` or
+  /// `ParserImpl::Maybe<T>`.
+  /// @return `val`.
+  static inline T* ptr(T* val) { return val; }
+};
+
+}  // namespace detail
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_PARSER_IMPL_DETAIL_H_
diff --git a/src/tint/reader/wgsl/parser_impl_elseif_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_elseif_stmt_test.cc
new file mode 100644
index 0000000..8608771
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_elseif_stmt_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ElseStmts) {
+  auto p = parser("else if (a == 4) { a = b; c = d; }");
+  auto e = p->else_stmts();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value.size(), 1u);
+
+  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[0]->condition, nullptr);
+  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
+}
+
+TEST_F(ParserImplTest, ElseStmts_Multiple) {
+  auto p = parser("else if (a == 4) { a = b; c = d; } else if(c) { d = 2; }");
+  auto e = p->else_stmts();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value.size(), 2u);
+
+  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[0]->condition, nullptr);
+  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
+
+  ASSERT_TRUE(e.value[1]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[1]->condition, nullptr);
+  ASSERT_TRUE(e.value[1]->condition->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(e.value[1]->body->statements.size(), 1u);
+}
+
+TEST_F(ParserImplTest, ElseStmts_InvalidBody) {
+  auto p = parser("else if (true) { fn main() {}}");
+  auto e = p->else_stmts();
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:18: expected '}'");
+}
+
+TEST_F(ParserImplTest, ElseStmts_MissingBody) {
+  auto p = parser("else if (true)");
+  auto e = p->else_stmts();
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: expected '{'");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The tests below use the deprecated 'elseif' syntax
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(ParserImplTest, DEPRECATED_ElseStmts) {
+  auto p = parser("elseif (a == 4) { a = b; c = d; }");
+  auto e = p->else_stmts();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if')");
+  ASSERT_EQ(e.value.size(), 1u);
+
+  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[0]->condition, nullptr);
+  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
+}
+
+TEST_F(ParserImplTest, DEPRECATED_ElseStmts_Multiple) {
+  auto p = parser("elseif (a == 4) { a = b; c = d; } elseif(c) { d = 2; }");
+  auto e = p->else_stmts();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
+1:35: use of deprecated language feature: 'elseif' is now 'else if')");
+  ASSERT_EQ(e.value.size(), 2u);
+
+  ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[0]->condition, nullptr);
+  ASSERT_TRUE(e.value[0]->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e.value[0]->body->statements.size(), 2u);
+
+  ASSERT_TRUE(e.value[1]->Is<ast::ElseStatement>());
+  ASSERT_NE(e.value[1]->condition, nullptr);
+  ASSERT_TRUE(e.value[1]->condition->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(e.value[1]->body->statements.size(), 1u);
+}
+
+TEST_F(ParserImplTest, DEPRECATED_ElseStmts_InvalidBody) {
+  auto p = parser("elseif (true) { fn main() {}}");
+  auto e = p->else_stmts();
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
+1:17: expected '}')");
+}
+
+TEST_F(ParserImplTest, DEPRECATED_ElseStmts_MissingBody) {
+  auto p = parser("elseif (true)");
+  auto e = p->else_stmts();
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: 'elseif' is now 'else if'
+1:14: expected '{')");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_equality_expression_test.cc b/src/tint/reader/wgsl/parser_impl_equality_expression_test.cc
new file mode 100644
index 0000000..75d7484
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_equality_expression_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, EqualityExpression_Parses_Equal) {
+  auto p = parser("a == true");
+  auto e = p->equality_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kEqual, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, EqualityExpression_Parses_NotEqual) {
+  auto p = parser("a != true");
+  auto e = p->equality_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kNotEqual, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, EqualityExpression_InvalidLHS) {
+  auto p = parser("if (a) {} == true");
+  auto e = p->equality_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, EqualityExpression_InvalidRHS) {
+  auto p = parser("true == if (a) {}");
+  auto e = p->equality_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: unable to parse right side of == expression");
+}
+
+TEST_F(ParserImplTest, EqualityExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->equality_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
new file mode 100644
index 0000000..08c48a9
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -0,0 +1,1695 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+const diag::Formatter::Style formatter_style{
+    /* print_file: */ true, /* print_severity: */ true,
+    /* print_line: */ true, /* print_newline_at_end: */ false};
+
+class ParserImplErrorTest : public ParserImplTest {};
+
+#define EXPECT(SOURCE, EXPECTED)                                               \
+  do {                                                                         \
+    std::string source = SOURCE;                                               \
+    std::string expected = EXPECTED;                                           \
+    auto p = parser(source);                                                   \
+    p->set_max_errors(5);                                                      \
+    EXPECT_EQ(false, p->Parse());                                              \
+    auto diagnostics = p->builder().Diagnostics();                             \
+    EXPECT_EQ(true, diagnostics.contains_errors());                            \
+    EXPECT_EQ(expected, diag::Formatter(formatter_style).format(diagnostics)); \
+  } while (false)
+
+TEST_F(ParserImplErrorTest, AdditiveInvalidExpr) {
+  EXPECT("fn f() { return 1.0 + <; }",
+         R"(test.wgsl:1:23 error: unable to parse right side of + expression
+fn f() { return 1.0 + <; }
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AndInvalidExpr) {
+  EXPECT("fn f() { return 1 & >; }",
+         R"(test.wgsl:1:21 error: unable to parse right side of & expression
+fn f() { return 1 & >; }
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AliasDeclInvalidAttribute) {
+  EXPECT("@invariant type e=u32;",
+         R"(test.wgsl:1:2 error: unexpected attributes
+@invariant type e=u32;
+ ^^^^^^^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_AliasDeclInvalidAttribute) {
+  EXPECT(
+      "[[invariant]]type e=u32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[invariant]]type e=u32;
+^^
+
+test.wgsl:1:3 error: unexpected attributes
+[[invariant]]type e=u32;
+  ^^^^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, IndexExprInvalidExpr) {
+  EXPECT("fn f() { x = y[^]; }",
+         R"(test.wgsl:1:16 error: unable to parse expression inside []
+fn f() { x = y[^]; }
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, IndexExprMissingRBracket) {
+  EXPECT("fn f() { x = y[1; }",
+         R"(test.wgsl:1:17 error: expected ']' for index accessor
+fn f() { x = y[1; }
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment) {
+  EXPECT("fn f() { a; }", R"(test.wgsl:1:11 error: expected '=' for assignment
+fn f() { a; }
+          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment2) {
+  EXPECT("fn f() { a : i32; }",
+         R"(test.wgsl:1:10 error: expected 'var' for variable declaration
+fn f() { a : i32; }
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AssignmentStmtMissingSemicolon) {
+  EXPECT("fn f() { a = 1 }",
+         R"(test.wgsl:1:16 error: expected ';' for assignment statement
+fn f() { a = 1 }
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AssignmentStmtInvalidLHS_BuiltinFunctionName) {
+  EXPECT("normalize = 5;",
+         R"(test.wgsl:1:1 error: statement found outside of function body
+normalize = 5;
+^^^^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, AssignmentStmtInvalidRHS) {
+  EXPECT("fn f() { a = >; }",
+         R"(test.wgsl:1:14 error: unable to parse right side of assignment
+fn f() { a = >; }
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, BitcastExprMissingLessThan) {
+  EXPECT("fn f() { x = bitcast(y); }",
+         R"(test.wgsl:1:21 error: expected '<' for bitcast expression
+fn f() { x = bitcast(y); }
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, BitcastExprMissingGreaterThan) {
+  EXPECT("fn f() { x = bitcast<u32(y); }",
+         R"(test.wgsl:1:25 error: expected '>' for bitcast expression
+fn f() { x = bitcast<u32(y); }
+                        ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, BitcastExprMissingType) {
+  EXPECT("fn f() { x = bitcast<>(y); }",
+         R"(test.wgsl:1:22 error: invalid type for bitcast expression
+fn f() { x = bitcast<>(y); }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, BreakStmtMissingSemicolon) {
+  EXPECT("fn f() { loop { break } }",
+         R"(test.wgsl:1:23 error: expected ';' for break statement
+fn f() { loop { break } }
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallExprMissingRParen) {
+  EXPECT("fn f() { x = f(1.; }",
+         R"(test.wgsl:1:18 error: expected ')' for function call
+fn f() { x = f(1.; }
+                 ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallStmtMissingRParen) {
+  EXPECT("fn f() { f(1.; }",
+         R"(test.wgsl:1:14 error: expected ')' for function call
+fn f() { f(1.; }
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallStmtInvalidArgument0) {
+  EXPECT("fn f() { f(<); }",
+         R"(test.wgsl:1:12 error: expected ')' for function call
+fn f() { f(<); }
+           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallStmtInvalidArgument1) {
+  EXPECT("fn f() { f(1.0, <); }",
+         R"(test.wgsl:1:17 error: expected ')' for function call
+fn f() { f(1.0, <); }
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallStmtMissingSemicolon) {
+  EXPECT("fn f() { f() }",
+         R"(test.wgsl:1:14 error: expected ';' for function call
+fn f() { f() }
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ConstructorExprMissingLParen) {
+  EXPECT("fn f() { x = vec2<u32>1,2); }",
+         R"(test.wgsl:1:23 error: expected '(' for type constructor
+fn f() { x = vec2<u32>1,2); }
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ConstructorExprMissingRParen) {
+  EXPECT("fn f() { x = vec2<u32>(1,2; }",
+         R"(test.wgsl:1:27 error: expected ')' for type constructor
+fn f() { x = vec2<u32>(1,2; }
+                          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ConstVarStmtInvalid) {
+  EXPECT("fn f() { let >; }",
+         R"(test.wgsl:1:14 error: expected identifier for let declaration
+fn f() { let >; }
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ConstVarStmtMissingAssignment) {
+  EXPECT("fn f() { let a : i32; }",
+         R"(test.wgsl:1:21 error: expected '=' for let declaration
+fn f() { let a : i32; }
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ConstVarStmtMissingConstructor) {
+  EXPECT("fn f() { let a : i32 = >; }",
+         R"(test.wgsl:1:24 error: missing constructor for let declaration
+fn f() { let a : i32 = >; }
+                       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ContinueStmtMissingSemicolon) {
+  EXPECT("fn f() { loop { continue } }",
+         R"(test.wgsl:1:26 error: expected ';' for continue statement
+fn f() { loop { continue } }
+                         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, DiscardStmtMissingSemicolon) {
+  EXPECT("fn f() { discard }",
+         R"(test.wgsl:1:18 error: expected ';' for discard statement
+fn f() { discard }
+                 ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, EqualityInvalidExpr) {
+  EXPECT("fn f() { return 1 == >; }",
+         R"(test.wgsl:1:22 error: unable to parse right side of == expression
+fn f() { return 1 == >; }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopInitializerMissingSemicolon) {
+  EXPECT("fn f() { for (var i : i32 = 0 i < 8; i=i+1) {} }",
+         R"(test.wgsl:1:31 error: expected ';' for initializer in for loop
+fn f() { for (var i : i32 = 0 i < 8; i=i+1) {} }
+                              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopInitializerMissingVar) {
+  EXPECT("fn f() { for (i : i32 = 0; i < 8; i=i+1) {} }",
+         R"(test.wgsl:1:15 error: expected 'var' for variable declaration
+fn f() { for (i : i32 = 0; i < 8; i=i+1) {} }
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopConditionMissingSemicolon) {
+  EXPECT("fn f() { for (var i : i32 = 0; i < 8 i=i+1) {} }",
+         R"(test.wgsl:1:38 error: expected ';' for condition in for loop
+fn f() { for (var i : i32 = 0; i < 8 i=i+1) {} }
+                                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopMissingLParen) {
+  EXPECT("fn f() { for var i : i32 = 0; i < 8; i=i+1) {} }",
+         R"(test.wgsl:1:14 error: expected '(' for for loop
+fn f() { for var i : i32 = 0; i < 8; i=i+1) {} }
+             ^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopMissingRParen) {
+  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1 {} }",
+         R"(test.wgsl:1:45 error: expected ')' for for loop
+fn f() { for (var i : i32 = 0; i < 8; i=i+1 {} }
+                                            ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopMissingLBrace) {
+  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1) }",
+         R"(test.wgsl:1:46 error: expected '{' for for loop
+fn f() { for (var i : i32 = 0; i < 8; i=i+1) }
+                                             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ForLoopMissingRBrace) {
+  EXPECT("fn f() { for (var i : i32 = 0; i < 8; i=i+1) {",
+         R"(test.wgsl:1:47 error: expected '}' for for loop
+fn f() { for (var i : i32 = 0; i < 8; i=i+1) {
+                                              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStageMissingLParen) {
+  EXPECT("@stage vertex) fn f() {}",
+         R"(test.wgsl:1:8 error: expected '(' for stage attribute
+@stage vertex) fn f() {}
+       ^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStageMissingRParen) {
+  EXPECT("@stage(vertex fn f() {}",
+         R"(test.wgsl:1:15 error: expected ')' for stage attribute
+@stage(vertex fn f() {}
+              ^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclStageMissingLParen) {
+  EXPECT(
+      "[[stage vertex]] fn f() {}",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[stage vertex]] fn f() {}
+^^
+
+test.wgsl:1:9 error: expected '(' for stage attribute
+[[stage vertex]] fn f() {}
+        ^^^^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclStageMissingRParen) {
+  EXPECT(
+      "[[stage(vertex]] fn f() {}",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[stage(vertex]] fn f() {}
+^^
+
+test.wgsl:1:15 error: expected ')' for stage attribute
+[[stage(vertex]] fn f() {}
+              ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStageInvalid) {
+  EXPECT("@stage(x) fn f() {}",
+         R"(test.wgsl:1:8 error: invalid value for stage attribute
+@stage(x) fn f() {}
+       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStageTypeInvalid) {
+  EXPECT("@shader(vertex) fn main() {}",
+         R"(test.wgsl:1:2 error: expected attribute
+@shader(vertex) fn main() {}
+ ^^^^^^
+
+test.wgsl:1:8 error: unexpected token
+@shader(vertex) fn main() {}
+       ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclWorkgroupSizeMissingLParen) {
+  EXPECT(
+      "[[workgroup_size 1]] fn f() {}",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[workgroup_size 1]] fn f() {}
+^^
+
+test.wgsl:1:18 error: expected '(' for workgroup_size attribute
+[[workgroup_size 1]] fn f() {}
+                 ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_FunctionDeclWorkgroupSizeMissingRParen) {
+  EXPECT(
+      "[[workgroup_size(1]] fn f() {}",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[workgroup_size(1]] fn f() {}
+^^
+
+test.wgsl:1:19 error: expected ')' for workgroup_size attribute
+[[workgroup_size(1]] fn f() {}
+                  ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeXInvalid) {
+  EXPECT("@workgroup_size() fn f() {}",
+         R"(test.wgsl:1:17 error: expected workgroup_size x parameter
+@workgroup_size() fn f() {}
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeYInvalid) {
+  EXPECT("@workgroup_size(1, ) fn f() {}",
+         R"(test.wgsl:1:20 error: expected workgroup_size y parameter
+@workgroup_size(1, ) fn f() {}
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclWorkgroupSizeZInvalid) {
+  EXPECT("@workgroup_size(1, 2, ) fn f() {}",
+         R"(test.wgsl:1:23 error: expected workgroup_size z parameter
+@workgroup_size(1, 2, ) fn f() {}
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingIdentifier) {
+  EXPECT("fn () {}",
+         R"(test.wgsl:1:4 error: expected identifier for function declaration
+fn () {}
+   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingLParen) {
+  EXPECT("fn f) {}",
+         R"(test.wgsl:1:5 error: expected '(' for function declaration
+fn f) {}
+    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingRParen) {
+  EXPECT("fn f( {}",
+         R"(test.wgsl:1:7 error: expected ')' for function declaration
+fn f( {}
+      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingArrow) {
+  EXPECT("fn f() f32 {}", R"(test.wgsl:1:8 error: expected '{'
+fn f() f32 {}
+       ^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclInvalidReturnType) {
+  EXPECT("fn f() -> 1 {}",
+         R"(test.wgsl:1:11 error: unable to determine function return type
+fn f() -> 1 {}
+          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclParamMissingColon) {
+  EXPECT("fn f(x) {}", R"(test.wgsl:1:7 error: expected ':' for parameter
+fn f(x) {}
+      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclParamInvalidType) {
+  EXPECT("fn f(x : 1) {}", R"(test.wgsl:1:10 error: invalid type for parameter
+fn f(x : 1) {}
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclParamMissing) {
+  EXPECT("fn f(x : i32, ,) {}",
+         R"(test.wgsl:1:15 error: expected ')' for function declaration
+fn f(x : i32, ,) {}
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingLBrace) {
+  EXPECT("fn f() }", R"(test.wgsl:1:8 error: expected '{'
+fn f() }
+       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclMissingRBrace) {
+  EXPECT("fn f() {", R"(test.wgsl:1:9 error: expected '}'
+fn f() {
+        ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionScopeUnusedDecl) {
+  EXPECT("fn f(a:i32)->i32{return a;@size(1)}",
+         R"(test.wgsl:1:28 error: unexpected attributes
+fn f(a:i32)->i32{return a;@size(1)}
+                           ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionMissingOpenLine) {
+  EXPECT(R"(let bar : vec2<f32> = vec2<f32>(1., 2.);
+  var a : f32 = bar[0];
+  return;
+})",
+         R"(test.wgsl:2:17 error: unable to parse const_expr
+  var a : f32 = bar[0];
+                ^^^
+
+test.wgsl:3:3 error: statement found outside of function body
+  return;
+  ^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) {
+  EXPECT("let ^ : i32 = 1;",
+         R"(test.wgsl:1:5 error: expected identifier for let declaration
+let ^ : i32 = 1;
+    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) {
+  EXPECT("let i : i32 = 1",
+         R"(test.wgsl:1:16 error: expected ';' for let declaration
+let i : i32 = 1
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstMissingLParen) {
+  EXPECT("let i : vec2<i32> = vec2<i32>;",
+         R"(test.wgsl:1:30 error: expected '(' for type constructor
+let i : vec2<i32> = vec2<i32>;
+                             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) {
+  EXPECT("let i : vec2<i32> = vec2<i32>(1., 2.;",
+         R"(test.wgsl:1:37 error: expected ')' for type constructor
+let i : vec2<i32> = vec2<i32>(1., 2.;
+                                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) {
+  EXPECT("let i : vec2<i32> = vec2<i32>(!);",
+         R"(test.wgsl:1:31 error: unable to parse const_expr
+let i : vec2<i32> = vec2<i32>(!);
+                              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) {
+  EXPECT("let i = 1 < 2;",
+         R"(test.wgsl:1:11 error: expected ';' for let declaration
+let i = 1 < 2;
+          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) {
+  EXPECT(
+      "let a = 1;\n"
+      "let b = a;",
+      R"(test.wgsl:2:9 error: unable to parse const_expr
+let b = a;
+        ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) {
+  uint32_t kMaxDepth = 128;
+
+  std::stringstream src;
+  std::stringstream mkr;
+  src << "let i : i32 = ";
+  mkr << "              ";
+  for (size_t i = 0; i < kMaxDepth + 8; i++) {
+    src << "f32(";
+    if (i < kMaxDepth) {
+      mkr << "    ";
+    } else if (i == kMaxDepth) {
+      mkr << "^^^";
+    }
+  }
+  src << "1.0";
+  for (size_t i = 0; i < 200; i++) {
+    src << ")";
+  }
+  src << ";";
+  std::stringstream err;
+  err << "test.wgsl:1:527 error: maximum parser recursive depth reached\n"
+      << src.str() << "\n"
+      << mkr.str() << "\n";
+  EXPECT(src.str().c_str(), err.str().c_str());
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingLParen) {
+  EXPECT("let i : vec2<i32> = vec2<i32> 1, 2);",
+         R"(test.wgsl:1:31 error: expected '(' for type constructor
+let i : vec2<i32> = vec2<i32> 1, 2);
+                              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) {
+  EXPECT("let i : vec2<i32> = vec2<i32>(1, 2;",
+         R"(test.wgsl:1:35 error: expected ')' for type constructor
+let i : vec2<i32> = vec2<i32>(1, 2;
+                                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclInvalidAttribute) {
+  EXPECT("@stage(vertex) x;",
+         R"(test.wgsl:1:16 error: expected declaration after attributes
+@stage(vertex) x;
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureMissingLessThan) {
+  EXPECT("var x : texture_1d;",
+         R"(test.wgsl:1:19 error: expected '<' for sampled texture type
+var x : texture_1d;
+                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureMissingGreaterThan) {
+  EXPECT("var x : texture_1d<f32;",
+         R"(test.wgsl:1:23 error: expected '>' for sampled texture type
+var x : texture_1d<f32;
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclSampledTextureInvalidSubtype) {
+  EXPECT("var x : texture_1d<1>;",
+         R"(test.wgsl:1:20 error: invalid type for sampled texture type
+var x : texture_1d<1>;
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureMissingLessThan) {
+  EXPECT("var x : texture_multisampled_2d;",
+         R"(test.wgsl:1:32 error: expected '<' for multisampled texture type
+var x : texture_multisampled_2d;
+                               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureMissingGreaterThan) {
+  EXPECT("var x : texture_multisampled_2d<f32;",
+         R"(test.wgsl:1:36 error: expected '>' for multisampled texture type
+var x : texture_multisampled_2d<f32;
+                                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclMultisampledTextureInvalidSubtype) {
+  EXPECT("var x : texture_multisampled_2d<1>;",
+         R"(test.wgsl:1:33 error: invalid type for multisampled texture type
+var x : texture_multisampled_2d<1>;
+                                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingLessThan) {
+  EXPECT("var x : texture_storage_2d;",
+         R"(test.wgsl:1:27 error: expected '<' for storage texture type
+var x : texture_storage_2d;
+                          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingGreaterThan) {
+  EXPECT("var x : texture_storage_2d<r32uint, read;",
+         R"(test.wgsl:1:41 error: expected '>' for storage texture type
+var x : texture_storage_2d<r32uint, read;
+                                        ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingSubtype) {
+  EXPECT("var x : texture_storage_2d<>;",
+         R"(test.wgsl:1:28 error: invalid format for storage texture type
+var x : texture_storage_2d<>;
+                           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingInvalidSubtype) {
+  EXPECT("var x : texture_storage_2d<1>;",
+         R"(test.wgsl:1:28 error: invalid format for storage texture type
+var x : texture_storage_2d<1>;
+                           ^
+)");
+}
+
+// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructAttrMissingStruct) {
+  EXPECT(
+      "[[block]];",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[block]];
+^^
+
+test.wgsl:1:3 warning: use of deprecated language feature: [[block]] attributes have been removed from WGSL
+[[block]];
+  ^^^^^
+
+test.wgsl:1:10 error: expected declaration after attributes
+[[block]];
+         ^
+)");
+}
+
+// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructAttrMissingEnd) {
+  EXPECT(
+      "[[block struct {};",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[block struct {};
+^^
+
+test.wgsl:1:3 warning: use of deprecated language feature: [[block]] attributes have been removed from WGSL
+[[block struct {};
+  ^^^^^
+
+test.wgsl:1:9 error: expected ']]' for attribute list
+[[block struct {};
+        ^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingIdentifier) {
+  EXPECT("struct {};",
+         R"(test.wgsl:1:8 error: expected identifier for struct declaration
+struct {};
+       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingLBrace) {
+  EXPECT("struct S };",
+         R"(test.wgsl:1:10 error: expected '{' for struct declaration
+struct S };
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingRBrace) {
+  EXPECT("struct S { i : i32;",
+         R"(test.wgsl:1:20 error: expected '}' for struct declaration
+struct S { i : i32;
+                   ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructMemberAttrEmpty) {
+  EXPECT(
+      "struct S { [[]] i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[]] i : i32; };
+           ^^
+
+test.wgsl:1:14 error: empty attribute list
+struct S { [[]] i : i32; };
+             ^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclStructMemberAttrMissingEnd) {
+  EXPECT(
+      "struct S { [[ i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[ i : i32; };
+           ^^
+
+test.wgsl:1:15 error: expected attribute
+struct S { [[ i : i32; };
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberInvalidIdentifier) {
+  EXPECT("struct S { 1 : i32; };",
+         R"(test.wgsl:1:12 error: expected identifier for struct member
+struct S { 1 : i32; };
+           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberMissingSemicolon) {
+  EXPECT("struct S { i : i32 };",
+         R"(test.wgsl:1:20 error: expected ';' for struct member
+struct S { i : i32 };
+                   ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest,
+       DEPRECATED_GlobalDeclStructMemberAlignMissingLParen) {
+  EXPECT(
+      "struct S { [[align 1)]] i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[align 1)]] i : i32; };
+           ^^
+
+test.wgsl:1:20 error: expected '(' for align attribute
+struct S { [[align 1)]] i : i32; };
+                   ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest,
+       DEPRECATED_GlobalDeclStructMemberAlignMissingRParen) {
+  EXPECT(
+      "struct S { [[align(1]] i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[align(1]] i : i32; };
+           ^^
+
+test.wgsl:1:21 error: expected ')' for align attribute
+struct S { [[align(1]] i : i32; };
+                    ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignInvaldValue) {
+  EXPECT(
+      "struct S { @align(x) i : i32; };",
+      R"(test.wgsl:1:19 error: expected signed integer literal for align attribute
+struct S { @align(x) i : i32; };
+                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignNegativeValue) {
+  EXPECT("struct S { @align(-2) i : i32; };",
+         R"(test.wgsl:1:19 error: align attribute must be positive
+struct S { @align(-2) i : i32; };
+                  ^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest,
+       DEPRECATED_GlobalDeclStructMemberSizeMissingLParen) {
+  EXPECT(
+      "struct S { [[size 1)]] i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[size 1)]] i : i32; };
+           ^^
+
+test.wgsl:1:19 error: expected '(' for size attribute
+struct S { [[size 1)]] i : i32; };
+                  ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest,
+       DEPRECATED_GlobalDeclStructMemberSizeMissingRParen) {
+  EXPECT(
+      "struct S { [[size(1]] i : i32; };",
+      R"(test.wgsl:1:12 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+struct S { [[size(1]] i : i32; };
+           ^^
+
+test.wgsl:1:20 error: expected ')' for size attribute
+struct S { [[size(1]] i : i32; };
+                   ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeInvaldValue) {
+  EXPECT(
+      "struct S { @size(x) i : i32; };",
+      R"(test.wgsl:1:18 error: expected signed integer literal for size attribute
+struct S { @size(x) i : i32; };
+                 ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeNegativeValue) {
+  EXPECT("struct S { @size(-2) i : i32; };",
+         R"(test.wgsl:1:18 error: size attribute must be positive
+struct S { @size(-2) i : i32; };
+                 ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingIdentifier) {
+  EXPECT("type 1 = f32;",
+         R"(test.wgsl:1:6 error: expected identifier for type alias
+type 1 = f32;
+     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasInvalidType) {
+  EXPECT("type meow = 1;", R"(test.wgsl:1:13 error: invalid type alias
+type meow = 1;
+            ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingAssignment) {
+  EXPECT("type meow f32", R"(test.wgsl:1:11 error: expected '=' for type alias
+type meow f32
+          ^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingSemicolon) {
+  EXPECT("type meow = f32", R"(test.wgsl:1:16 error: expected ';' for type alias
+type meow = f32
+               ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclTypeAttrInvalid) {
+  EXPECT(
+      "var x : [[]] i32;",
+      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+var x : [[]] i32;
+        ^^
+
+test.wgsl:1:11 error: empty attribute list
+var x : [[]] i32;
+          ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingLessThan) {
+  EXPECT("var i : array;",
+         R"(test.wgsl:1:14 error: expected '<' for array declaration
+var i : array;
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingGreaterThan) {
+  EXPECT("var i : array<u32, 3;",
+         R"(test.wgsl:1:21 error: expected '>' for array declaration
+var i : array<u32, 3;
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayAttrNotArray) {
+  EXPECT("var i : @location(1) i32;",
+         R"(test.wgsl:1:10 error: unexpected attributes
+var i : @location(1) i32;
+         ^^^^^^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayAttrMissingEnd) {
+  EXPECT(
+      "var i : [[location(1) array<i32>;",
+      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+var i : [[location(1) array<i32>;
+        ^^
+
+test.wgsl:1:23 error: expected ']]' for attribute list
+var i : [[location(1) array<i32>;
+                      ^^^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayStrideMissingLParen) {
+  EXPECT(
+      "var i : [[stride 1)]] array<i32>;",
+      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+var i : [[stride 1)]] array<i32>;
+        ^^
+
+test.wgsl:1:18 error: expected '(' for stride attribute
+var i : [[stride 1)]] array<i32>;
+                 ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarArrayStrideMissingRParen) {
+  EXPECT(
+      "var i : [[location(1]] array<i32>;",
+      R"(test.wgsl:1:9 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+var i : [[location(1]] array<i32>;
+        ^^
+
+test.wgsl:1:21 error: expected ')' for location attribute
+var i : [[location(1]] array<i32>;
+                    ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayStrideInvalid) {
+  EXPECT(
+      "var i : @stride(x) array<i32>;",
+      R"(test.wgsl:1:17 error: expected signed integer literal for stride attribute
+var i : @stride(x) array<i32>;
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayStrideNegative) {
+  EXPECT("var i : @stride(-1) array<i32>;",
+         R"(test.wgsl:1:17 error: stride attribute must be greater than 0
+var i : @stride(-1) array<i32>;
+                ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingType) {
+  EXPECT("var i : array<1, 3>;",
+         R"(test.wgsl:1:15 error: invalid type for array declaration
+var i : array<1, 3>;
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingSize) {
+  EXPECT("var i : array<u32, >;",
+         R"(test.wgsl:1:20 error: expected array size expression
+var i : array<u32, >;
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarArrayInvalidSize) {
+  EXPECT("var i : array<u32, !>;",
+         R"(test.wgsl:1:20 error: expected array size expression
+var i : array<u32, !>;
+                   ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListEmpty) {
+  EXPECT(
+      "[[]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[]] var i : i32;
+^^
+
+test.wgsl:1:3 error: empty attribute list
+[[]] var i : i32;
+  ^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListInvalid) {
+  EXPECT(
+      "[[location(1), meow]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[location(1), meow]] var i : i32;
+^^
+
+test.wgsl:1:16 error: expected attribute
+[[location(1), meow]] var i : i32;
+               ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrListMissingComma) {
+  EXPECT("@location(1) group(2) var i : i32;",
+         R"(test.wgsl:1:14 error: expected declaration after attributes
+@location(1) group(2) var i : i32;
+             ^^^^^
+
+test.wgsl:1:19 error: unexpected token
+@location(1) group(2) var i : i32;
+                  ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrListMissingEnd) {
+  EXPECT(
+      "[[location(1) meow]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[location(1) meow]] var i : i32;
+^^
+
+test.wgsl:1:15 error: expected ']]' for attribute list
+[[location(1) meow]] var i : i32;
+              ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationMissingLParen) {
+  EXPECT("@location 1) var i : i32;",
+         R"(test.wgsl:1:11 error: expected '(' for location attribute
+@location 1) var i : i32;
+          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationMissingRParen) {
+  EXPECT("@location (1 var i : i32;",
+         R"(test.wgsl:1:14 error: expected ')' for location attribute
+@location (1 var i : i32;
+             ^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrLocationMissingLParen) {
+  EXPECT(
+      "[[location 1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[location 1]] var i : i32;
+^^
+
+test.wgsl:1:12 error: expected '(' for location attribute
+[[location 1]] var i : i32;
+           ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrLocationMissingRParen) {
+  EXPECT(
+      "[[location (1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[location (1]] var i : i32;
+^^
+
+test.wgsl:1:14 error: expected ')' for location attribute
+[[location (1]] var i : i32;
+             ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationInvalidValue) {
+  EXPECT(
+      "@location(x) var i : i32;",
+      R"(test.wgsl:1:11 error: expected signed integer literal for location attribute
+@location(x) var i : i32;
+          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinMissingLParen) {
+  EXPECT("@builtin position) var i : i32;",
+         R"(test.wgsl:1:10 error: expected '(' for builtin attribute
+@builtin position) var i : i32;
+         ^^^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinMissingRParen) {
+  EXPECT("@builtin(position var i : i32;",
+         R"(test.wgsl:1:19 error: expected ')' for builtin attribute
+@builtin(position var i : i32;
+                  ^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBuiltinMissingLParen) {
+  EXPECT(
+      "[[builtin position]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[builtin position]] var i : i32;
+^^
+
+test.wgsl:1:11 error: expected '(' for builtin attribute
+[[builtin position]] var i : i32;
+          ^^^^^^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBuiltinMissingRParen) {
+  EXPECT(
+      "[[builtin(position]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[builtin(position]] var i : i32;
+^^
+
+test.wgsl:1:19 error: expected ')' for builtin attribute
+[[builtin(position]] var i : i32;
+                  ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidIdentifer) {
+  EXPECT("@builtin(1) var i : i32;",
+         R"(test.wgsl:1:10 error: expected identifier for builtin
+@builtin(1) var i : i32;
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidValue) {
+  EXPECT("@builtin(x) var i : i32;",
+         R"(test.wgsl:1:10 error: invalid value for builtin attribute
+@builtin(x) var i : i32;
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingMissingLParen) {
+  EXPECT("@binding 1) var i : i32;",
+         R"(test.wgsl:1:10 error: expected '(' for binding attribute
+@binding 1) var i : i32;
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingMissingRParen) {
+  EXPECT("@binding(1 var i : i32;",
+         R"(test.wgsl:1:12 error: expected ')' for binding attribute
+@binding(1 var i : i32;
+           ^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBindingMissingLParen) {
+  EXPECT(
+      "[[binding 1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[binding 1]] var i : i32;
+^^
+
+test.wgsl:1:11 error: expected '(' for binding attribute
+[[binding 1]] var i : i32;
+          ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrBindingMissingRParen) {
+  EXPECT(
+      "[[binding(1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[binding(1]] var i : i32;
+^^
+
+test.wgsl:1:12 error: expected ')' for binding attribute
+[[binding(1]] var i : i32;
+           ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingInvalidValue) {
+  EXPECT(
+      "@binding(x) var i : i32;",
+      R"(test.wgsl:1:10 error: expected signed integer literal for binding attribute
+@binding(x) var i : i32;
+         ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrGroupMissingLParen) {
+  EXPECT("@group 1) var i : i32;",
+         R"(test.wgsl:1:8 error: expected '(' for group attribute
+@group 1) var i : i32;
+       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrGroupMissingRParen) {
+  EXPECT("@group(1 var i : i32;",
+         R"(test.wgsl:1:10 error: expected ')' for group attribute
+@group(1 var i : i32;
+         ^^^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrGroupMissingLParen) {
+  EXPECT(
+      "[[group 1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[group 1]] var i : i32;
+^^
+
+test.wgsl:1:9 error: expected '(' for group attribute
+[[group 1]] var i : i32;
+        ^
+)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplErrorTest, DEPRECATED_GlobalDeclVarAttrGroupMissingRParen) {
+  EXPECT(
+      "[[group(1]] var i : i32;",
+      R"(test.wgsl:1:1 warning: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+[[group(1]] var i : i32;
+^^
+
+test.wgsl:1:10 error: expected ')' for group attribute
+[[group(1]] var i : i32;
+         ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingGroupValue) {
+  EXPECT(
+      "@group(x) var i : i32;",
+      R"(test.wgsl:1:8 error: expected signed integer literal for group attribute
+@group(x) var i : i32;
+       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarInvalidIdentifier) {
+  EXPECT("var ^ : mat4x4;",
+         R"(test.wgsl:1:5 error: expected identifier for variable declaration
+var ^ : mat4x4;
+    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingGreaterThan) {
+  EXPECT("var i : mat4x4<u32;", R"(test.wgsl:1:19 error: expected '>' for matrix
+var i : mat4x4<u32;
+                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingType) {
+  EXPECT("var i : mat4x4<1>;", R"(test.wgsl:1:16 error: invalid type for matrix
+var i : mat4x4<1>;
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarMissingSemicolon) {
+  EXPECT("var i : i32",
+         R"(test.wgsl:1:12 error: expected ';' for variable declaration
+var i : i32
+           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingLessThan) {
+  EXPECT("var i : ptr;",
+         R"(test.wgsl:1:12 error: expected '<' for ptr declaration
+var i : ptr;
+           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingGreaterThan) {
+  EXPECT("var i : ptr<private, u32;",
+         R"(test.wgsl:1:25 error: expected '>' for ptr declaration
+var i : ptr<private, u32;
+                        ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingComma) {
+  EXPECT("var i : ptr<private u32>;",
+         R"(test.wgsl:1:21 error: expected ',' for ptr declaration
+var i : ptr<private u32>;
+                    ^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingStorageClass) {
+  EXPECT("var i : ptr<meow, u32>;",
+         R"(test.wgsl:1:13 error: invalid storage class for ptr declaration
+var i : ptr<meow, u32>;
+            ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarPtrMissingType) {
+  EXPECT("var i : ptr<private, 1>;",
+         R"(test.wgsl:1:22 error: invalid type for ptr declaration
+var i : ptr<private, 1>;
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAtomicMissingLessThan) {
+  EXPECT("var i : atomic;",
+         R"(test.wgsl:1:15 error: expected '<' for atomic declaration
+var i : atomic;
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAtomicMissingGreaterThan) {
+  EXPECT("var i : atomic<u32 x;",
+         R"(test.wgsl:1:20 error: expected '>' for atomic declaration
+var i : atomic<u32 x;
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarStorageDeclInvalidClass) {
+  EXPECT("var<fish> i : i32",
+         R"(test.wgsl:1:5 error: invalid storage class for variable declaration
+var<fish> i : i32
+    ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarStorageDeclMissingGThan) {
+  EXPECT("var<private i : i32",
+         R"(test.wgsl:1:13 error: expected '>' for variable declaration
+var<private i : i32
+            ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingGreaterThan) {
+  EXPECT("var i : vec3<u32;", R"(test.wgsl:1:17 error: expected '>' for vector
+var i : vec3<u32;
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingType) {
+  EXPECT("var i : vec3<1>;", R"(test.wgsl:1:14 error: invalid type for vector
+var i : vec3<1>;
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, IfStmtMissingLParen) {
+  EXPECT("fn f() { if true) {} }", R"(test.wgsl:1:13 error: expected '('
+fn f() { if true) {} }
+            ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, IfStmtMissingRParen) {
+  EXPECT("fn f() { if (true {} }", R"(test.wgsl:1:19 error: expected ')'
+fn f() { if (true {} }
+                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, IfStmtInvalidCond) {
+  EXPECT("fn f() { if (>) {} }",
+         R"(test.wgsl:1:14 error: unable to parse expression
+fn f() { if (>) {} }
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, LogicalAndInvalidExpr) {
+  EXPECT("fn f() { return 1 && >; }",
+         R"(test.wgsl:1:22 error: unable to parse right side of && expression
+fn f() { return 1 && >; }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, LogicalOrInvalidExpr) {
+  EXPECT("fn f() { return 1 || >; }",
+         R"(test.wgsl:1:22 error: unable to parse right side of || expression
+fn f() { return 1 || >; }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, LoopMissingLBrace) {
+  EXPECT("fn f() { loop }", R"(test.wgsl:1:15 error: expected '{' for loop
+fn f() { loop }
+              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, LoopMissingRBrace) {
+  EXPECT("fn f() { loop {", R"(test.wgsl:1:16 error: expected '}' for loop
+fn f() { loop {
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, MaxErrorsReached) {
+  EXPECT("x; x; x; x; x; x; x; x;", R"(test.wgsl:1:1 error: unexpected token
+x; x; x; x; x; x; x; x;
+^
+
+test.wgsl:1:4 error: unexpected token
+x; x; x; x; x; x; x; x;
+   ^
+
+test.wgsl:1:7 error: unexpected token
+x; x; x; x; x; x; x; x;
+      ^
+
+test.wgsl:1:10 error: unexpected token
+x; x; x; x; x; x; x; x;
+         ^
+
+test.wgsl:1:13 error: unexpected token
+x; x; x; x; x; x; x; x;
+            ^
+
+test.wgsl error: stopping after 5 errors)");
+}
+
+TEST_F(ParserImplErrorTest, MemberExprMissingIdentifier) {
+  EXPECT("fn f() { x = a.; }",
+         R"(test.wgsl:1:16 error: expected identifier for member accessor
+fn f() { x = a.; }
+               ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, MultiplicativeInvalidExpr) {
+  EXPECT("fn f() { return 1.0 * <; }",
+         R"(test.wgsl:1:23 error: unable to parse right side of * expression
+fn f() { return 1.0 * <; }
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, OrInvalidExpr) {
+  EXPECT("fn f() { return 1 | >; }",
+         R"(test.wgsl:1:21 error: unable to parse right side of | expression
+fn f() { return 1 | >; }
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, RelationalInvalidExpr) {
+  EXPECT("fn f() { return 1 < >; }",
+         R"(test.wgsl:1:21 error: unable to parse right side of < expression
+fn f() { return 1 < >; }
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ReturnStmtMissingSemicolon) {
+  EXPECT("fn f() { return }",
+         R"(test.wgsl:1:17 error: expected ';' for return statement
+fn f() { return }
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, ShiftInvalidExpr) {
+  EXPECT("fn f() { return 1 << >; }",
+         R"(test.wgsl:1:22 error: unable to parse right side of << expression
+fn f() { return 1 << >; }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtMissingLBrace) {
+  EXPECT("fn f() { switch(1) }",
+         R"(test.wgsl:1:20 error: expected '{' for switch statement
+fn f() { switch(1) }
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtMissingRBrace) {
+  EXPECT("fn f() { switch(1) {",
+         R"(test.wgsl:1:21 error: expected '}' for switch statement
+fn f() { switch(1) {
+                    ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtInvalidCase) {
+  EXPECT("fn f() { switch(1) { case ^: } }",
+         R"(test.wgsl:1:27 error: unable to parse case selectors
+fn f() { switch(1) { case ^: } }
+                          ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtInvalidCase2) {
+  EXPECT("fn f() { switch(1) { case false: } }",
+         R"(test.wgsl:1:27 error: invalid case selector must be an integer value
+fn f() { switch(1) { case false: } }
+                          ^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingColon) {
+  EXPECT("fn f() { switch(1) { case 1 {} } }",
+         R"(test.wgsl:1:29 error: expected ':' for case statement
+fn f() { switch(1) { case 1 {} } }
+                            ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingLBrace) {
+  EXPECT("fn f() { switch(1) { case 1: } }",
+         R"(test.wgsl:1:30 error: expected '{' for case statement
+fn f() { switch(1) { case 1: } }
+                             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingRBrace) {
+  EXPECT("fn f() { switch(1) { case 1: {",
+         R"(test.wgsl:1:31 error: expected '}' for case statement
+fn f() { switch(1) { case 1: {
+                              ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, SwitchStmtCaseFallthroughMissingSemicolon) {
+  EXPECT("fn f() { switch(1) { case 1: { fallthrough } case 2: {} } }",
+         R"(test.wgsl:1:44 error: expected ';' for fallthrough statement
+fn f() { switch(1) { case 1: { fallthrough } case 2: {} } }
+                                           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, VarStmtMissingSemicolon) {
+  EXPECT("fn f() { var a : u32 }",
+         R"(test.wgsl:1:22 error: expected ';' for variable declaration
+fn f() { var a : u32 }
+                     ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, VarStmtInvalidAssignment) {
+  EXPECT("fn f() { var a : u32 = >; }",
+         R"(test.wgsl:1:24 error: missing constructor for variable declaration
+fn f() { var a : u32 = >; }
+                       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, UnaryInvalidExpr) {
+  EXPECT("fn f() { return !<; }",
+         R"(test.wgsl:1:18 error: unable to parse right side of ! expression
+fn f() { return !<; }
+                 ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, UnexpectedToken) {
+  EXPECT("unexpected", R"(test.wgsl:1:1 error: unexpected token
+unexpected
+^^^^^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, XorInvalidExpr) {
+  EXPECT("fn f() { return 1 ^ >; }",
+         R"(test.wgsl:1:21 error: unable to parse right side of ^ expression
+fn f() { return 1 ^ >; }
+                    ^
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_error_resync_test.cc b/src/tint/reader/wgsl/parser_impl_error_resync_test.cc
new file mode 100644
index 0000000..14ff303
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_error_resync_test.cc
@@ -0,0 +1,182 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+const diag::Formatter::Style formatter_style{
+    /* print_file: */ true, /* print_severity: */ true,
+    /* print_line: */ true, /* print_newline_at_end: */ false};
+
+class ParserImplErrorResyncTest : public ParserImplTest {};
+
+#define EXPECT(SOURCE, EXPECTED)                                               \
+  do {                                                                         \
+    std::string source = SOURCE;                                               \
+    std::string expected = EXPECTED;                                           \
+    auto p = parser(source);                                                   \
+    EXPECT_EQ(false, p->Parse());                                              \
+    auto diagnostics = p->builder().Diagnostics();                             \
+    EXPECT_EQ(true, diagnostics.contains_errors());                            \
+    EXPECT_EQ(expected, diag::Formatter(formatter_style).format(diagnostics)); \
+  } while (false)
+
+TEST_F(ParserImplErrorResyncTest, BadFunctionDecls) {
+  EXPECT(R"(
+fn .() -> . {}
+fn x(.) {}
+@_ fn -> {}
+fn good() {}
+)",
+         R"(test.wgsl:2:4 error: expected identifier for function declaration
+fn .() -> . {}
+   ^
+
+test.wgsl:2:11 error: unable to determine function return type
+fn .() -> . {}
+          ^
+
+test.wgsl:3:6 error: expected ')' for function declaration
+fn x(.) {}
+     ^
+
+test.wgsl:4:2 error: expected attribute
+@_ fn -> {}
+ ^
+
+test.wgsl:4:7 error: expected identifier for function declaration
+@_ fn -> {}
+      ^^
+)");
+}
+
+TEST_F(ParserImplErrorResyncTest, AssignmentStatement) {
+  EXPECT(R"(
+fn f() {
+  blah blah blah blah;
+  good = 1;
+  blah blah blah blah;
+  x = .;
+  good = 1;
+}
+)",
+         R"(test.wgsl:3:8 error: expected '=' for assignment
+  blah blah blah blah;
+       ^^^^
+
+test.wgsl:5:8 error: expected '=' for assignment
+  blah blah blah blah;
+       ^^^^
+
+test.wgsl:6:7 error: unable to parse right side of assignment
+  x = .;
+      ^
+)");
+}
+
+TEST_F(ParserImplErrorResyncTest, DiscardStatement) {
+  EXPECT(R"(
+fn f() {
+  discard blah blah blah;
+  a = 1;
+  discard blah blah blah;
+}
+)",
+         R"(test.wgsl:3:11 error: expected ';' for discard statement
+  discard blah blah blah;
+          ^^^^
+
+test.wgsl:5:11 error: expected ';' for discard statement
+  discard blah blah blah;
+          ^^^^
+)");
+}
+
+TEST_F(ParserImplErrorResyncTest, StructMembers) {
+  EXPECT(R"(
+struct S {
+    blah blah blah;
+    a : i32;
+    blah blah blah;
+    b : i32;
+    @- x : i32;
+    c : i32;
+}
+)",
+         R"(test.wgsl:3:10 error: expected ':' for struct member
+    blah blah blah;
+         ^^^^
+
+test.wgsl:5:10 error: expected ':' for struct member
+    blah blah blah;
+         ^^^^
+
+test.wgsl:7:6 error: expected attribute
+    @- x : i32;
+     ^
+)");
+}
+
+// Check that the forward scan in resynchronize() stop at nested sync points.
+// In this test the inner resynchronize() is looking for a terminating ';', and
+// the outer resynchronize() is looking for a terminating '}' for the function
+// scope.
+TEST_F(ParserImplErrorResyncTest, NestedSyncPoints) {
+  EXPECT(R"(
+fn f() {
+  x = 1;
+  discard
+}
+struct S { blah };
+)",
+         R"(test.wgsl:5:1 error: expected ';' for discard statement
+}
+^
+
+test.wgsl:6:17 error: expected ':' for struct member
+struct S { blah };
+                ^
+)");
+}
+
+TEST_F(ParserImplErrorResyncTest, BracketCounting) {
+  EXPECT(
+      R"(
+fn f(x(((())))) {
+  meow = {{{}}}
+}
+struct S { blah };
+)",
+      R"(test.wgsl:2:7 error: expected ':' for parameter
+fn f(x(((())))) {
+      ^
+
+test.wgsl:3:10 error: unable to parse right side of assignment
+  meow = {{{}}}
+         ^
+
+test.wgsl:5:17 error: expected ':' for struct member
+struct S { blah };
+                ^
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_exclusive_or_expression_test.cc b/src/tint/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
new file mode 100644
index 0000000..3226f21
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ExclusiveOrExpression_Parses) {
+  auto p = parser("a ^ true");
+  auto e = p->exclusive_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kXor, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidLHS) {
+  auto p = parser("if (a) {} ^ true");
+  auto e = p->exclusive_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidRHS) {
+  auto p = parser("true ^ if (a) {}");
+  auto e = p->exclusive_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of ^ expression");
+}
+
+TEST_F(ParserImplTest, ExclusiveOrExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->exclusive_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_external_texture_type_test.cc b/src/tint/reader/wgsl/parser_impl_external_texture_type_test.cc
new file mode 100644
index 0000000..fbb661a
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_external_texture_type_test.cc
@@ -0,0 +1,41 @@
+// 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ExternalTextureType_Invalid) {
+  auto p = parser("1234");
+  auto t = p->external_texture_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ExternalTextureType) {
+  auto p = parser("texture_external");
+  auto t = p->external_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
new file mode 100644
index 0000000..fba74d7
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -0,0 +1,299 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+#include "src/tint/ast/discard_statement.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+using ForStmtTest = ParserImplTest;
+
+// Test an empty for loop.
+TEST_F(ForStmtTest, Empty) {
+  auto p = parser("for (;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_EQ(fl->initializer, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop with non-empty body.
+TEST_F(ForStmtTest, Body) {
+  auto p = parser("for (;;) { discard; }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_EQ(fl->initializer, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  ASSERT_EQ(fl->body->statements.size(), 1u);
+  EXPECT_TRUE(fl->body->statements[0]->Is<ast::DiscardStatement>());
+}
+
+// Test a for loop declaring a variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementDecl) {
+  auto p = parser("for (var i: i32 ;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
+  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
+  EXPECT_FALSE(var->is_const);
+  EXPECT_EQ(var->constructor, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop declaring and initializing a variable in the initializer
+// statement.
+TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
+  auto p = parser("for (var i: i32 = 0 ;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
+  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
+  EXPECT_FALSE(var->is_const);
+  EXPECT_NE(var->constructor, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop declaring a const variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementConstDecl) {
+  auto p = parser("for (let i: i32 = 0 ;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer));
+  auto* var = fl->initializer->As<ast::VariableDeclStatement>()->variable;
+  EXPECT_TRUE(var->is_const);
+  EXPECT_NE(var->constructor, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop assigning a variable in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementAssignment) {
+  auto p = parser("for (i = 0 ;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->initializer));
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop calling a function in the initializer statement.
+TEST_F(ForStmtTest, InitializerStatementFuncCall) {
+  auto p = parser("for (a(b,c) ;;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_TRUE(Is<ast::CallStatement>(fl->initializer));
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop with a break condition
+TEST_F(ForStmtTest, BreakCondition) {
+  auto p = parser("for (; 0 == 1;) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_EQ(fl->initializer, nullptr);
+  EXPECT_TRUE(Is<ast::BinaryExpression>(fl->condition));
+  EXPECT_EQ(fl->continuing, nullptr);
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop assigning a variable in the continuing statement.
+TEST_F(ForStmtTest, ContinuingAssignment) {
+  auto p = parser("for (;; x = 2) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_EQ(fl->initializer, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->continuing));
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+// Test a for loop calling a function in the continuing statement.
+TEST_F(ForStmtTest, ContinuingFuncCall) {
+  auto p = parser("for (;; a(b,c)) { }");
+  auto fl = p->for_stmt();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(fl.errored);
+  ASSERT_TRUE(fl.matched);
+  EXPECT_EQ(fl->initializer, nullptr);
+  EXPECT_EQ(fl->condition, nullptr);
+  EXPECT_TRUE(Is<ast::CallStatement>(fl->continuing));
+  EXPECT_TRUE(fl->body->Empty());
+}
+
+class ForStmtErrorTest : public ParserImplTest {
+ public:
+  void TestForWithError(std::string for_str, std::string error_str) {
+    auto p_for = parser(for_str);
+    auto e_for = p_for->for_stmt();
+
+    EXPECT_FALSE(e_for.matched);
+    EXPECT_TRUE(e_for.errored);
+    EXPECT_TRUE(p_for->has_error());
+    ASSERT_EQ(e_for.value, nullptr);
+    EXPECT_EQ(p_for->error(), error_str);
+  }
+};
+
+// Test a for loop with missing left parenthesis is invalid.
+TEST_F(ForStmtErrorTest, MissingLeftParen) {
+  std::string for_str = "for { }";
+  std::string error_str = "1:5: expected '(' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing first semicolon is invalid.
+TEST_F(ForStmtErrorTest, MissingFirstSemicolon) {
+  std::string for_str = "for () {}";
+  std::string error_str = "1:6: expected ';' for initializer in for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing second semicolon is invalid.
+TEST_F(ForStmtErrorTest, MissingSecondSemicolon) {
+  std::string for_str = "for (;) {}";
+  std::string error_str = "1:7: expected ';' for condition in for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing right parenthesis is invalid.
+TEST_F(ForStmtErrorTest, MissingRightParen) {
+  std::string for_str = "for (;; {}";
+  std::string error_str = "1:9: expected ')' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing left brace is invalid.
+TEST_F(ForStmtErrorTest, MissingLeftBrace) {
+  std::string for_str = "for (;;)";
+  std::string error_str = "1:9: expected '{' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with missing right brace is invalid.
+TEST_F(ForStmtErrorTest, MissingRightBrace) {
+  std::string for_str = "for (;;) {";
+  std::string error_str = "1:11: expected '}' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid initializer statement.
+TEST_F(ForStmtErrorTest, InvalidInitializerAsConstDecl) {
+  std::string for_str = "for (let x: i32;;) { }";
+  std::string error_str = "1:16: expected '=' for let declaration";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a initializer statement not matching
+// variable_stmt | assignment_stmt | func_call_stmt.
+TEST_F(ForStmtErrorTest, InvalidInitializerMatch) {
+  std::string for_str = "for (if (true) {} ;;) { }";
+  std::string error_str = "1:6: expected ';' for initializer in for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid break condition.
+TEST_F(ForStmtErrorTest, InvalidBreakConditionAsExpression) {
+  std::string for_str = "for (; (0 == 1; ) { }";
+  std::string error_str = "1:15: expected ')'";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a break condition not matching
+// logical_or_expression.
+TEST_F(ForStmtErrorTest, InvalidBreakConditionMatch) {
+  std::string for_str = "for (; var i: i32 = 0;) { }";
+  std::string error_str = "1:8: expected ';' for condition in for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid continuing statement.
+TEST_F(ForStmtErrorTest, InvalidContinuingAsFuncCall) {
+  std::string for_str = "for (;; a(,) ) { }";
+  std::string error_str = "1:11: expected ')' for function call";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a continuing statement not matching
+// assignment_stmt | func_call_stmt.
+TEST_F(ForStmtErrorTest, InvalidContinuingMatch) {
+  std::string for_str = "for (;; var i: i32 = 0) { }";
+  std::string error_str = "1:9: expected ')' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with an invalid body.
+TEST_F(ForStmtErrorTest, InvalidBody) {
+  std::string for_str = "for (;;) { let x: i32; }";
+  std::string error_str = "1:22: expected '=' for let declaration";
+
+  TestForWithError(for_str, error_str);
+}
+
+// Test a for loop with a body not matching statements
+TEST_F(ForStmtErrorTest, InvalidBodyMatch) {
+  std::string for_str = "for (;;) { fn main() {} }";
+  std::string error_str = "1:12: expected '}' for for loop";
+
+  TestForWithError(for_str, error_str);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
new file mode 100644
index 0000000..5ca1dac
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
@@ -0,0 +1,162 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AttributeList_Parses) {
+  auto p = parser("@workgroup_size(2) @stage(compute)");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 2u);
+
+  auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
+  auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
+  ASSERT_NE(attr_0, nullptr);
+  ASSERT_NE(attr_1, nullptr);
+
+  ASSERT_TRUE(attr_0->Is<ast::WorkgroupAttribute>());
+  const ast::Expression* x = attr_0->As<ast::WorkgroupAttribute>()->x;
+  ASSERT_NE(x, nullptr);
+  auto* x_literal = x->As<ast::LiteralExpression>();
+  ASSERT_NE(x_literal, nullptr);
+  ASSERT_TRUE(x_literal->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(x_literal->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(attr_1->Is<ast::StageAttribute>());
+  EXPECT_EQ(attr_1->As<ast::StageAttribute>()->stage,
+            ast::PipelineStage::kCompute);
+}
+
+TEST_F(ParserImplTest, AttributeList_Invalid) {
+  auto p = parser("@invalid");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(), "1:2: expected attribute");
+}
+
+TEST_F(ParserImplTest, AttributeList_ExtraComma) {
+  auto p = parser("[[workgroup_size(2), ]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:22: expected attribute)");
+}
+
+TEST_F(ParserImplTest, AttributeList_BadAttribute) {
+  auto p = parser("@stage()");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(p->error(), "1:8: invalid value for stage attribute");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_Empty) {
+  auto p = parser("[[]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: empty attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_Invalid) {
+  auto p = parser("[[invalid]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: expected attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_ExtraComma) {
+  auto p = parser("[[workgroup_size(2), ]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:22: expected attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingComma) {
+  auto p = parser("[[workgroup_size(2) workgroup_size(2)]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:21: expected ',' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_BadAttribute) {
+  auto p = parser("[[stage()]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:9: invalid value for stage attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingRightAttr) {
+  auto p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:45: expected ']]' for attribute list)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
new file mode 100644
index 0000000..a13bd3f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
@@ -0,0 +1,260 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Attribute_Workgroup) {
+  auto p = parser("workgroup_size(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  auto* func_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(func_attr, nullptr);
+  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
+
+  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  EXPECT_EQ(values[1], nullptr);
+  EXPECT_EQ(values[2], nullptr);
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_2Param) {
+  auto p = parser("workgroup_size(4, 5)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  auto* func_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(func_attr, nullptr) << p->error();
+  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
+
+  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
+
+  EXPECT_EQ(values[2], nullptr);
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_3Param) {
+  auto p = parser("workgroup_size(4, 5, 6)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  auto* func_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(func_attr, nullptr);
+  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
+
+  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
+
+  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_WithIdent) {
+  auto p = parser("workgroup_size(4, height)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  auto* func_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(func_attr, nullptr);
+  ASSERT_TRUE(func_attr->Is<ast::WorkgroupAttribute>());
+
+  auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_NE(values[1], nullptr);
+  auto* y_ident = values[1]->As<ast::IdentifierExpression>();
+  ASSERT_NE(y_ident, nullptr);
+  EXPECT_EQ(p->builder().Symbols().NameFor(y_ident->symbol), "height");
+
+  ASSERT_EQ(values[2], nullptr);
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_TooManyValues) {
+  auto p = parser("workgroup_size(1, 2, 3, 4)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_MissingLeftParam) {
+  auto p = parser("workgroup_size 4, 5, 6)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected '(' for workgroup_size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_MissingRightParam) {
+  auto p = parser("workgroup_size(4, 5, 6");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_MissingValues) {
+  auto p = parser("workgroup_size()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected workgroup_size x parameter");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_Missing_X_Value) {
+  auto p = parser("workgroup_size(, 2, 3)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected workgroup_size x parameter");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Y_Comma) {
+  auto p = parser("workgroup_size(1 2, 3)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:18: expected ')' for workgroup_size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Y_Value) {
+  auto p = parser("workgroup_size(1, , 3)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:19: expected workgroup_size y parameter");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Z_Comma) {
+  auto p = parser("workgroup_size(1, 2 3)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:21: expected ')' for workgroup_size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Workgroup_Missing_Z_Value) {
+  auto p = parser("workgroup_size(1, 2, )");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:22: expected workgroup_size z parameter");
+}
+
+TEST_F(ParserImplTest, Attribute_Stage) {
+  auto p = parser("stage(compute)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  auto* func_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(func_attr, nullptr);
+  ASSERT_TRUE(func_attr->Is<ast::StageAttribute>());
+  EXPECT_EQ(func_attr->As<ast::StageAttribute>()->stage,
+            ast::PipelineStage::kCompute);
+}
+
+TEST_F(ParserImplTest, Attribute_Stage_MissingValue) {
+  auto p = parser("stage()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: invalid value for stage attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Stage_MissingInvalid) {
+  auto p = parser("stage(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: invalid value for stage attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Stage_MissingLeftParen) {
+  auto p = parser("stage compute)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected '(' for stage attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Stage_MissingRightParen) {
+  auto p = parser("stage(compute");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: expected ')' for stage attribute");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
new file mode 100644
index 0000000..fc21d54
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
@@ -0,0 +1,297 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, FunctionDecl) {
+  auto p = parser("fn main(a : i32, b : f32) { return; }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+
+  ASSERT_EQ(f->params.size(), 2u);
+  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
+
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_Unicode) {
+  const std::string function_ident =  // "𝗳𝘂𝗻𝗰𝘁𝗶𝗼𝗻"
+      "\xf0\x9d\x97\xb3\xf0\x9d\x98\x82\xf0\x9d\x97\xbb\xf0\x9d\x97\xb0\xf0\x9d"
+      "\x98\x81\xf0\x9d\x97\xb6\xf0\x9d\x97\xbc\xf0\x9d\x97\xbb";
+
+  const std::string param_a_ident =  // "𝓹𝓪𝓻𝓪𝓶_𝓪"
+      "\xf0\x9d\x93\xb9\xf0\x9d\x93\xaa\xf0\x9d\x93\xbb\xf0\x9d\x93\xaa\xf0\x9d"
+      "\x93\xb6\x5f\xf0\x9d\x93\xaa";
+
+  const std::string param_b_ident =  // "𝕡𝕒𝕣𝕒𝕞_𝕓"
+      "\xf0\x9d\x95\xa1\xf0\x9d\x95\x92\xf0\x9d\x95\xa3\xf0\x9d\x95\x92\xf0\x9d"
+      "\x95\x9e\x5f\xf0\x9d\x95\x93";
+
+  std::string src = "fn $function($param_a : i32, $param_b : f32) { return; }";
+  src = utils::ReplaceAll(src, "$function", function_ident);
+  src = utils::ReplaceAll(src, "$param_a", param_a_ident);
+  src = utils::ReplaceAll(src, "$param_b", param_b_ident);
+
+  auto p = parser(src);
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get(function_ident));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+
+  ASSERT_EQ(f->params.size(), 2u);
+  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get(param_a_ident));
+  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get(param_b_ident));
+
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_AttributeList) {
+  auto p = parser("@workgroup_size(2, 3, 4) fn main() { return; }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  ASSERT_TRUE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+  ASSERT_EQ(f->params.size(), 0u);
+
+  auto& attributes = f->attributes;
+  ASSERT_EQ(attributes.size(), 1u);
+  ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
+
+  auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+
+  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_AttributeList_MultipleEntries) {
+  auto p = parser(R"(
+@workgroup_size(2, 3, 4) @stage(compute)
+fn main() { return; })");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  ASSERT_TRUE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+  ASSERT_EQ(f->params.size(), 0u);
+
+  auto& attributes = f->attributes;
+  ASSERT_EQ(attributes.size(), 2u);
+
+  ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
+  auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+
+  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_TRUE(attributes[1]->Is<ast::StageAttribute>());
+  EXPECT_EQ(attributes[1]->As<ast::StageAttribute>()->stage,
+            ast::PipelineStage::kCompute);
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_AttributeList_MultipleLists) {
+  auto p = parser(R"(
+@workgroup_size(2, 3, 4)
+@stage(compute)
+fn main() { return; })");
+  auto attributes = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attributes.errored);
+  ASSERT_TRUE(attributes.matched);
+  auto f = p->function_decl(attributes.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+  ASSERT_EQ(f->params.size(), 0u);
+
+  auto& attrs = f->attributes;
+  ASSERT_EQ(attrs.size(), 2u);
+
+  ASSERT_TRUE(attrs[0]->Is<ast::WorkgroupAttribute>());
+  auto values = attrs[0]->As<ast::WorkgroupAttribute>()->Values();
+
+  ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+
+  ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+
+  ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
+  EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+
+  ASSERT_TRUE(attrs[1]->Is<ast::StageAttribute>());
+  EXPECT_EQ(attrs[1]->As<ast::StageAttribute>()->stage,
+            ast::PipelineStage::kCompute);
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_ReturnTypeAttributeList) {
+  auto p = parser("fn main() -> @location(1) f32 { return 1.0; }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
+
+  EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
+  ASSERT_NE(f->return_type, nullptr);
+  EXPECT_TRUE(f->return_type->Is<ast::F32>());
+  ASSERT_EQ(f->params.size(), 0u);
+
+  auto& attributes = f->attributes;
+  EXPECT_EQ(attributes.size(), 0u);
+
+  auto& ret_type_attributes = f->return_type_attributes;
+  ASSERT_EQ(ret_type_attributes.size(), 1u);
+  auto* loc = ret_type_attributes[0]->As<ast::LocationAttribute>();
+  ASSERT_TRUE(loc != nullptr);
+  EXPECT_EQ(loc->value, 1u);
+
+  auto* body = f->body;
+  ASSERT_EQ(body->statements.size(), 1u);
+  EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, FunctionDecl_InvalidHeader) {
+  auto p = parser("fn main() -> { }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
+  EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
+}
+
+TEST_F(ParserImplTest, FunctionDecl_InvalidBody) {
+  auto p = parser("fn main() { return }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
+  EXPECT_EQ(p->error(), "1:20: expected ';' for return statement");
+}
+
+TEST_F(ParserImplTest, FunctionDecl_MissingLeftBrace) {
+  auto p = parser("fn main() return; }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto f = p->function_decl(attrs.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
+  EXPECT_EQ(p->error(), "1:11: expected '{'");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_function_header_test.cc b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
new file mode 100644
index 0000000..6416aca
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
@@ -0,0 +1,156 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, FunctionHeader) {
+  auto p = parser("fn main(a : i32, b: f32)");
+  auto f = p->function_header();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+
+  EXPECT_EQ(f->name, "main");
+  ASSERT_EQ(f->params.size(), 2u);
+  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+}
+
+TEST_F(ParserImplTest, FunctionHeader_TrailingComma) {
+  auto p = parser("fn main(a :i32,)");
+  auto f = p->function_header();
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+
+  EXPECT_EQ(f->name, "main");
+  ASSERT_EQ(f->params.size(), 1u);
+  EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_TRUE(f->return_type->Is<ast::Void>());
+}
+
+TEST_F(ParserImplTest, FunctionHeader_AttributeReturnType) {
+  auto p = parser("fn main() -> @location(1) f32");
+  auto f = p->function_header();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+
+  EXPECT_EQ(f->name, "main");
+  EXPECT_EQ(f->params.size(), 0u);
+  EXPECT_TRUE(f->return_type->Is<ast::F32>());
+  ASSERT_EQ(f->return_type_attributes.size(), 1u);
+  auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
+  ASSERT_TRUE(loc != nullptr);
+  EXPECT_EQ(loc->value, 1u);
+}
+
+TEST_F(ParserImplTest, FunctionHeader_InvariantReturnType) {
+  auto p = parser("fn main() -> [[invariant]] f32");
+  auto f = p->function_header();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+
+  EXPECT_EQ(f->name, "main");
+  EXPECT_EQ(f->params.size(), 0u);
+  EXPECT_TRUE(f->return_type->Is<ast::F32>());
+  ASSERT_EQ(f->return_type_attributes.size(), 1u);
+  EXPECT_TRUE(f->return_type_attributes[0]->Is<ast::InvariantAttribute>());
+}
+
+TEST_F(ParserImplTest, FunctionHeader_AttributeReturnType_WithArrayStride) {
+  auto p = parser("fn main() -> @location(1) @stride(16) array<f32, 4>");
+  auto f = p->function_header();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+
+  EXPECT_EQ(f->name, "main");
+  EXPECT_EQ(f->params.size(), 0u);
+  ASSERT_EQ(f->return_type_attributes.size(), 1u);
+  auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
+  ASSERT_TRUE(loc != nullptr);
+  EXPECT_EQ(loc->value, 1u);
+
+  auto* array_type = f->return_type->As<ast::Array>();
+  ASSERT_EQ(array_type->attributes.size(), 1u);
+  auto* stride = array_type->attributes[0]->As<ast::StrideAttribute>();
+  ASSERT_TRUE(stride != nullptr);
+  EXPECT_EQ(stride->stride, 16u);
+}
+
+TEST_F(ParserImplTest, FunctionHeader_MissingIdent) {
+  auto p = parser("fn ()");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
+}
+
+TEST_F(ParserImplTest, FunctionHeader_InvalidIdent) {
+  auto p = parser("fn 133main() -> i32");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
+}
+
+TEST_F(ParserImplTest, FunctionHeader_MissingParenLeft) {
+  auto p = parser("fn main) -> i32");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected '(' for function declaration");
+}
+
+TEST_F(ParserImplTest, FunctionHeader_InvalidParamList) {
+  auto p = parser("fn main(a :i32, ,) -> i32");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:17: expected ')' for function declaration");
+}
+
+TEST_F(ParserImplTest, FunctionHeader_MissingParenRight) {
+  auto p = parser("fn main( -> i32");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: expected ')' for function declaration");
+}
+
+TEST_F(ParserImplTest, FunctionHeader_MissingReturnType) {
+  auto p = parser("fn main() ->");
+  auto f = p->function_header();
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: unable to determine function return type");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
new file mode 100644
index 0000000..d09826a
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -0,0 +1,196 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, GlobalConstantDecl) {
+  auto p = parser("let a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const);
+  EXPECT_FALSE(e->is_overridable);
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type, nullptr);
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 5u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 6u);
+
+  ASSERT_NE(e->constructor, nullptr);
+  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
+}
+
+TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) {
+  auto p = parser("let a = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const);
+  EXPECT_FALSE(e->is_overridable);
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_EQ(e->type, nullptr);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 5u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 6u);
+
+  ASSERT_NE(e->constructor, nullptr);
+  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
+}
+
+TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
+  auto p = parser("let a : f32 = if (a) {}");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:15: invalid type for const_expr");
+}
+
+TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) {
+  auto p = parser("let a : f32 =");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:14: unable to parse const_expr");
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_Override_WithId) {
+  auto p = parser("@id(7) override a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const);
+  EXPECT_TRUE(e->is_overridable);
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type, nullptr);
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 17u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 18u);
+
+  ASSERT_NE(e->constructor, nullptr);
+  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
+
+  auto* override_attr =
+      ast::GetAttribute<ast::IdAttribute>(e.value->attributes);
+  ASSERT_NE(override_attr, nullptr);
+  EXPECT_EQ(override_attr->value, 7u);
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_Override_WithoutId) {
+  auto p = parser("override a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const);
+  EXPECT_TRUE(e->is_overridable);
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type, nullptr);
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 10u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 11u);
+
+  ASSERT_NE(e->constructor, nullptr);
+  EXPECT_TRUE(e->constructor->Is<ast::LiteralExpression>());
+
+  auto* id_attr = ast::GetAttribute<ast::IdAttribute>(e.value->attributes);
+  ASSERT_EQ(id_attr, nullptr);
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_Override_MissingId) {
+  auto p = parser("@id() override a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:5: expected signed integer literal for id attribute");
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_Override_InvalidId) {
+  auto p = parser("@id(-7) override a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto e = p->global_constant_decl(attrs.value);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: id attribute must be positive");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
new file mode 100644
index 0000000..a0ac35c
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
@@ -0,0 +1,224 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, GlobalDecl_Semicolon) {
+  auto p = parser(";");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalVariable) {
+  auto p = parser("var<private> a : vec2<i32> = vec2<i32>(1, 2);");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+
+  auto* v = program.AST().GlobalVariables()[0];
+  EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Inferred_Invalid) {
+  auto p = parser("var<private> a = vec2<i32>(1, 2);");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected ':' for variable declaration");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_MissingSemicolon) {
+  auto p = parser("var<private> a : vec2<i32>");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:27: expected ';' for variable declaration");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) {
+  auto p = parser("let a : i32 = 2;");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+
+  auto* v = program.AST().GlobalVariables()[0];
+  EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) {
+  auto p = parser("let a : vec2<i32> 1.0;");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:19: expected ';' for let declaration");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingSemicolon) {
+  auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:36: expected ';' for let declaration");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_TypeAlias) {
+  auto p = parser("type A = i32;");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+  ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Alias>());
+  EXPECT_EQ(program.Symbols().NameFor(
+                program.AST().TypeDecls()[0]->As<ast::Alias>()->name),
+            "A");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_TypeAlias_StructIdent) {
+  auto p = parser(R"(struct A {
+  a : f32;
+}
+type B = A;)");
+  p->expect_global_decl();
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().TypeDecls().size(), 2u);
+  ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Struct>());
+  auto* str = program.AST().TypeDecls()[0]->As<ast::Struct>();
+  EXPECT_EQ(str->name, program.Symbols().Get("A"));
+
+  ASSERT_TRUE(program.AST().TypeDecls()[1]->Is<ast::Alias>());
+  auto* alias = program.AST().TypeDecls()[1]->As<ast::Alias>();
+  EXPECT_EQ(alias->name, program.Symbols().Get("B"));
+  auto* tn = alias->type->As<ast::TypeName>();
+  EXPECT_NE(tn, nullptr);
+  EXPECT_EQ(tn->name, str->name);
+}
+
+TEST_F(ParserImplTest, GlobalDecl_TypeAlias_MissingSemicolon) {
+  auto p = parser("type A = i32");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: expected ';' for type alias");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_Function) {
+  auto p = parser("fn main() { return; }");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().Functions().size(), 1u);
+  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol),
+            "main");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_Function_WithAttribute) {
+  auto p = parser("@workgroup_size(2) fn main() { return; }");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().Functions().size(), 1u);
+  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol),
+            "main");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_Function_Invalid) {
+  auto p = parser("fn main() -> { return; }");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
+}
+
+TEST_F(ParserImplTest, GlobalDecl_ParsesStruct) {
+  auto p = parser("struct A { b: i32; c: f32;}");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+
+  auto* t = program.AST().TypeDecls()[0];
+  ASSERT_NE(t, nullptr);
+  ASSERT_TRUE(t->Is<ast::Struct>());
+
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name, program.Symbols().Get("A"));
+  EXPECT_EQ(str->members.size(), 2u);
+}
+
+TEST_F(ParserImplTest, GlobalDecl_Struct_WithStride) {
+  auto p = parser("struct A { data: @stride(4) array<f32>; }");
+
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+
+  auto* t = program.AST().TypeDecls()[0];
+  ASSERT_NE(t, nullptr);
+  ASSERT_TRUE(t->Is<ast::Struct>());
+
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name, program.Symbols().Get("A"));
+  EXPECT_EQ(str->members.size(), 1u);
+
+  const auto* ty = str->members[0]->type;
+  ASSERT_TRUE(ty->Is<ast::Array>());
+  const auto* arr = ty->As<ast::Array>();
+
+  ASSERT_EQ(arr->attributes.size(), 1u);
+  auto* stride = arr->attributes[0];
+  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
+  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 4u);
+}
+
+// TODO(crbug.com/tint/1324): DEPRECATED: Remove when @block is removed.
+TEST_F(ParserImplTest, GlobalDecl_Struct_WithAttribute) {
+  auto p = parser("[[block]] struct A { data: f32; }");
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto program = p->program();
+  ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+
+  auto* t = program.AST().TypeDecls()[0];
+  ASSERT_NE(t, nullptr);
+  ASSERT_TRUE(t->Is<ast::Struct>());
+
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name, program.Symbols().Get("A"));
+  EXPECT_EQ(str->members.size(), 1u);
+}
+
+TEST_F(ParserImplTest, GlobalDecl_Struct_Invalid) {
+  auto p = parser("A {}");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:1: unexpected token");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
new file mode 100644
index 0000000..c6b93f1
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -0,0 +1,173 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, GlobalVariableDecl_WithoutConstructor) {
+  auto p = parser("var<private> a : f32");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_variable_decl(attrs.value);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kPrivate);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 14u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 15u);
+
+  ASSERT_EQ(e->constructor, nullptr);
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_WithConstructor) {
+  auto p = parser("var<private> a : f32 = 1.");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_variable_decl(attrs.value);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kPrivate);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 14u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 15u);
+
+  ASSERT_NE(e->constructor, nullptr);
+  ASSERT_TRUE(e->constructor->Is<ast::FloatLiteralExpression>());
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_WithAttribute) {
+  auto p = parser("@binding(2) @group(1) var<uniform> a : f32");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  auto e = p->global_variable_decl(attrs.value);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type, nullptr);
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kUniform);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 36u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 37u);
+
+  ASSERT_EQ(e->constructor, nullptr);
+
+  auto& attributes = e->attributes;
+  ASSERT_EQ(attributes.size(), 2u);
+  ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
+  ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_WithAttribute_MulitpleGroups) {
+  auto p = parser("@binding(2) @group(1) var<uniform> a : f32");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+
+  auto e = p->global_variable_decl(attrs.value);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_EQ(e->symbol, p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type, nullptr);
+  EXPECT_TRUE(e->type->Is<ast::F32>());
+  EXPECT_EQ(e->declared_storage_class, ast::StorageClass::kUniform);
+
+  EXPECT_EQ(e->source.range.begin.line, 1u);
+  EXPECT_EQ(e->source.range.begin.column, 36u);
+  EXPECT_EQ(e->source.range.end.line, 1u);
+  EXPECT_EQ(e->source.range.end.column, 37u);
+
+  ASSERT_EQ(e->constructor, nullptr);
+
+  auto& attributes = e->attributes;
+  ASSERT_EQ(attributes.size(), 2u);
+  ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
+  ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_InvalidAttribute) {
+  auto p = parser("@binding() var<uniform> a : f32");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto e = p->global_variable_decl(attrs.value);
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  EXPECT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:10: expected signed integer literal for binding attribute");
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_InvalidConstExpr) {
+  auto p = parser("var<private> a : f32 = if (a) {}");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_variable_decl(attrs.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:24: invalid type for const_expr");
+}
+
+TEST_F(ParserImplTest, GlobalVariableDecl_InvalidVariableDecl) {
+  auto p = parser("var<invalid> a : f32;");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  auto e = p->global_variable_decl(attrs.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:5: invalid storage class for variable declaration");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
new file mode 100644
index 0000000..8f97550
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, IfStmt) {
+  auto p = parser("if (a == 4) { a = b; c = d; }");
+  auto e = p->if_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::IfStatement>());
+  ASSERT_NE(e->condition, nullptr);
+  ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e->body->statements.size(), 2u);
+  EXPECT_EQ(e->else_statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, IfStmt_WithElse) {
+  auto p =
+      parser("if (a == 4) { a = b; c = d; } else if(c) { d = 2; } else {}");
+  auto e = p->if_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::IfStatement>());
+  ASSERT_NE(e->condition, nullptr);
+  ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
+  EXPECT_EQ(e->body->statements.size(), 2u);
+
+  ASSERT_EQ(e->else_statements.size(), 2u);
+  ASSERT_NE(e->else_statements[0]->condition, nullptr);
+  ASSERT_TRUE(
+      e->else_statements[0]->condition->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(e->else_statements[0]->body->statements.size(), 1u);
+
+  ASSERT_EQ(e->else_statements[1]->condition, nullptr);
+  EXPECT_EQ(e->else_statements[1]->body->statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, IfStmt_InvalidCondition) {
+  auto p = parser("if (a = 3) {}");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected ')'");
+}
+
+TEST_F(ParserImplTest, IfStmt_MissingCondition) {
+  auto p = parser("if {}");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: expected '('");
+}
+
+TEST_F(ParserImplTest, IfStmt_InvalidBody) {
+  auto p = parser("if (a) { fn main() {}}");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: expected '}'");
+}
+
+TEST_F(ParserImplTest, IfStmt_MissingBody) {
+  auto p = parser("if (a)");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected '{'");
+}
+
+TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
+  auto p = parser("if (a) {} else if (a) { fn main() -> a{}}");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:25: expected '}'");
+}
+
+TEST_F(ParserImplTest, IfStmt_InvalidElse) {
+  auto p = parser("if (a) {} else { fn main() -> a{}}");
+  auto e = p->if_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:18: expected '}'");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_inclusive_or_expression_test.cc b/src/tint/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
new file mode 100644
index 0000000..aa7f8e1
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, InclusiveOrExpression_Parses) {
+  auto p = parser("a | true");
+  auto e = p->inclusive_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kOr, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, InclusiveOrExpression_InvalidLHS) {
+  auto p = parser("if (a) {} | true");
+  auto e = p->inclusive_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, InclusiveOrExpression_InvalidRHS) {
+  auto p = parser("true | if (a) {}");
+  auto e = p->inclusive_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of | expression");
+}
+
+TEST_F(ParserImplTest, InclusiveOrExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->inclusive_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_logical_and_expression_test.cc b/src/tint/reader/wgsl/parser_impl_logical_and_expression_test.cc
new file mode 100644
index 0000000..81dcde7
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_logical_and_expression_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, LogicalAndExpression_Parses) {
+  auto p = parser("a && true");
+  auto e = p->logical_and_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kLogicalAnd, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, LogicalAndExpression_InvalidLHS) {
+  auto p = parser("if (a) {} && true");
+  auto e = p->logical_and_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, LogicalAndExpression_InvalidRHS) {
+  auto p = parser("true && if (a) {}");
+  auto e = p->logical_and_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: unable to parse right side of && expression");
+}
+
+TEST_F(ParserImplTest, LogicalAndExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->logical_and_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_logical_or_expression_test.cc b/src/tint/reader/wgsl/parser_impl_logical_or_expression_test.cc
new file mode 100644
index 0000000..6d8bf19
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_logical_or_expression_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, LogicalOrExpression_Parses) {
+  auto p = parser("a || true");
+  auto e = p->logical_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kLogicalOr, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, LogicalOrExpression_InvalidLHS) {
+  auto p = parser("if (a) {} || true");
+  auto e = p->logical_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, LogicalOrExpression_InvalidRHS) {
+  auto p = parser("true || if (a) {}");
+  auto e = p->logical_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: unable to parse right side of || expression");
+}
+
+TEST_F(ParserImplTest, LogicalOrExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->logical_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
new file mode 100644
index 0000000..1d35c1a
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -0,0 +1,118 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, LoopStmt_BodyNoContinuing) {
+  auto p = parser("loop { discard; }");
+  auto e = p->loop_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_EQ(e->body->statements.size(), 1u);
+  EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
+
+  EXPECT_EQ(e->continuing->statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, LoopStmt_BodyWithContinuing) {
+  auto p = parser("loop { discard; continuing { discard; }}");
+  auto e = p->loop_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_EQ(e->body->statements.size(), 1u);
+  EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
+
+  EXPECT_EQ(e->continuing->statements.size(), 1u);
+  EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
+}
+
+TEST_F(ParserImplTest, LoopStmt_NoBodyNoContinuing) {
+  auto p = parser("loop { }");
+  auto e = p->loop_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_EQ(e->body->statements.size(), 0u);
+  ASSERT_EQ(e->continuing->statements.size(), 0u);
+}
+
+TEST_F(ParserImplTest, LoopStmt_NoBodyWithContinuing) {
+  auto p = parser("loop { continuing { discard; }}");
+  auto e = p->loop_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_EQ(e->body->statements.size(), 0u);
+  ASSERT_EQ(e->continuing->statements.size(), 1u);
+  EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
+}
+
+TEST_F(ParserImplTest, LoopStmt_MissingBracketLeft) {
+  auto p = parser("loop discard; }");
+  auto e = p->loop_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
+}
+
+TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) {
+  auto p = parser("loop { discard; ");
+  auto e = p->loop_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:17: expected '}' for loop");
+}
+
+TEST_F(ParserImplTest, LoopStmt_InvalidStatements) {
+  auto p = parser("loop { discard }");
+  auto e = p->loop_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected ';' for discard statement");
+}
+
+TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) {
+  auto p = parser("loop { continuing { discard }}");
+  auto e = p->loop_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:29: expected ';' for discard statement");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc
new file mode 100644
index 0000000..c288ce9
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc
@@ -0,0 +1,114 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply) {
+  auto p = parser("a * true");
+  auto e = p->multiplicative_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Divide) {
+  auto p = parser("a / true");
+  auto e = p->multiplicative_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kDivide, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Modulo) {
+  auto p = parser("a % true");
+  auto e = p->multiplicative_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kModulo, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_InvalidLHS) {
+  auto p = parser("if (a) {} * true");
+  auto e = p->multiplicative_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_InvalidRHS) {
+  auto p = parser("true * if (a) {}");
+  auto e = p->multiplicative_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of * expression");
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->multiplicative_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_param_list_test.cc b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
new file mode 100644
index 0000000..0915434
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ParamList_Single) {
+  auto p = parser("a : i32");
+
+  auto e = p->expect_param_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  EXPECT_EQ(e.value.size(), 1u);
+
+  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
+  EXPECT_TRUE(e.value[0]->is_const);
+
+  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.begin.column, 1u);
+  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.end.column, 2u);
+}
+
+TEST_F(ParserImplTest, ParamList_Multiple) {
+  auto p = parser("a : i32, b: f32, c: vec2<f32>");
+
+  auto e = p->expect_param_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  EXPECT_EQ(e.value.size(), 3u);
+
+  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
+  EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
+  EXPECT_TRUE(e.value[0]->is_const);
+
+  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.begin.column, 1u);
+  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.end.column, 2u);
+
+  EXPECT_EQ(e.value[1]->symbol, p->builder().Symbols().Get("b"));
+  EXPECT_TRUE(e.value[1]->type->Is<ast::F32>());
+  EXPECT_TRUE(e.value[1]->is_const);
+
+  ASSERT_EQ(e.value[1]->source.range.begin.line, 1u);
+  ASSERT_EQ(e.value[1]->source.range.begin.column, 10u);
+  ASSERT_EQ(e.value[1]->source.range.end.line, 1u);
+  ASSERT_EQ(e.value[1]->source.range.end.column, 11u);
+
+  EXPECT_EQ(e.value[2]->symbol, p->builder().Symbols().Get("c"));
+  ASSERT_TRUE(e.value[2]->type->Is<ast::Vector>());
+  ASSERT_TRUE(e.value[2]->type->As<ast::Vector>()->type->Is<ast::F32>());
+  EXPECT_EQ(e.value[2]->type->As<ast::Vector>()->width, 2u);
+  EXPECT_TRUE(e.value[2]->is_const);
+
+  ASSERT_EQ(e.value[2]->source.range.begin.line, 1u);
+  ASSERT_EQ(e.value[2]->source.range.begin.column, 18u);
+  ASSERT_EQ(e.value[2]->source.range.end.line, 1u);
+  ASSERT_EQ(e.value[2]->source.range.end.column, 19u);
+}
+
+TEST_F(ParserImplTest, ParamList_Empty) {
+  auto p = parser("");
+  auto e = p->expect_param_list();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(e.errored);
+  EXPECT_EQ(e.value.size(), 0u);
+}
+
+TEST_F(ParserImplTest, ParamList_TrailingComma) {
+  auto p = parser("a : i32,");
+  auto e = p->expect_param_list();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(e.errored);
+  EXPECT_EQ(e.value.size(), 1u);
+}
+
+TEST_F(ParserImplTest, ParamList_Attributes) {
+  auto p =
+      parser("@builtin(position) coord : vec4<f32>, @location(1) loc1 : f32");
+
+  auto e = p->expect_param_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_EQ(e.value.size(), 2u);
+
+  EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("coord"));
+  ASSERT_TRUE(e.value[0]->type->Is<ast::Vector>());
+  EXPECT_TRUE(e.value[0]->type->As<ast::Vector>()->type->Is<ast::F32>());
+  EXPECT_EQ(e.value[0]->type->As<ast::Vector>()->width, 4u);
+  EXPECT_TRUE(e.value[0]->is_const);
+  auto attrs_0 = e.value[0]->attributes;
+  ASSERT_EQ(attrs_0.size(), 1u);
+  EXPECT_TRUE(attrs_0[0]->Is<ast::BuiltinAttribute>());
+  EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin,
+            ast::Builtin::kPosition);
+
+  ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.begin.column, 20u);
+  ASSERT_EQ(e.value[0]->source.range.end.line, 1u);
+  ASSERT_EQ(e.value[0]->source.range.end.column, 25u);
+
+  EXPECT_EQ(e.value[1]->symbol, p->builder().Symbols().Get("loc1"));
+  EXPECT_TRUE(e.value[1]->type->Is<ast::F32>());
+  EXPECT_TRUE(e.value[1]->is_const);
+  auto attrs_1 = e.value[1]->attributes;
+  ASSERT_EQ(attrs_1.size(), 1u);
+  EXPECT_TRUE(attrs_1[0]->Is<ast::LocationAttribute>());
+  EXPECT_EQ(attrs_1[0]->As<ast::LocationAttribute>()->value, 1u);
+
+  EXPECT_EQ(e.value[1]->source.range.begin.line, 1u);
+  EXPECT_EQ(e.value[1]->source.range.begin.column, 52u);
+  EXPECT_EQ(e.value[1]->source.range.end.line, 1u);
+  EXPECT_EQ(e.value[1]->source.range.end.column, 56u);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
new file mode 100644
index 0000000..61767f1
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ParenRhsStmt) {
+  auto p = parser("(a + b)");
+  auto e = p->expect_paren_rhs_stmt();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ParenRhsStmt_MissingLeftParen) {
+  auto p = parser("true)");
+  auto e = p->expect_paren_rhs_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:1: expected '('");
+}
+
+TEST_F(ParserImplTest, ParenRhsStmt_MissingRightParen) {
+  auto p = parser("(true");
+  auto e = p->expect_paren_rhs_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected ')'");
+}
+
+TEST_F(ParserImplTest, ParenRhsStmt_InvalidExpression) {
+  auto p = parser("(if (a() {})");
+  auto e = p->expect_paren_rhs_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
+}
+
+TEST_F(ParserImplTest, ParenRhsStmt_MissingExpression) {
+  auto p = parser("()");
+  auto e = p->expect_paren_rhs_stmt();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  ASSERT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_pipeline_stage_test.cc b/src/tint/reader/wgsl/parser_impl_pipeline_stage_test.cc
new file mode 100644
index 0000000..0bdc1fe
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_pipeline_stage_test.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+struct PipelineStageData {
+  std::string input;
+  ast::PipelineStage result;
+};
+inline std::ostream& operator<<(std::ostream& out, PipelineStageData data) {
+  return out << data.input;
+}
+
+class PipelineStageTest : public ParserImplTestWithParam<PipelineStageData> {};
+
+TEST_P(PipelineStageTest, Parses) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+
+  auto stage = p->expect_pipeline_stage();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(stage.errored);
+  EXPECT_EQ(stage.value, params.result);
+  EXPECT_EQ(stage.source.range.begin.line, 1u);
+  EXPECT_EQ(stage.source.range.begin.column, 1u);
+  EXPECT_EQ(stage.source.range.end.line, 1u);
+  EXPECT_EQ(stage.source.range.end.column, 1u + params.input.size());
+
+  auto t = p->next();
+  EXPECT_TRUE(t.IsEof());
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    PipelineStageTest,
+    testing::Values(
+        PipelineStageData{"vertex", ast::PipelineStage::kVertex},
+        PipelineStageData{"fragment", ast::PipelineStage::kFragment},
+        PipelineStageData{"compute", ast::PipelineStage::kCompute}));
+
+TEST_F(ParserImplTest, PipelineStage_NoMatch) {
+  auto p = parser("not-a-stage");
+  auto stage = p->expect_pipeline_stage();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(stage.errored);
+  ASSERT_EQ(p->error(), "1:1: invalid value for stage attribute");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
new file mode 100644
index 0000000..0b5a4bf
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -0,0 +1,317 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, PrimaryExpression_Ident) {
+  auto p = parser("a");
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+  auto* ident = e->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl) {
+  auto p = parser("vec4<i32>(1, 2, 3, 4))");
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* call = e->As<ast::CallExpression>();
+
+  EXPECT_NE(call->target.type, nullptr);
+
+  ASSERT_EQ(call->args.size(), 4u);
+  const auto& val = call->args;
+  ASSERT_TRUE(val[0]->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(val[0]->As<ast::SintLiteralExpression>()->value, 1);
+
+  ASSERT_TRUE(val[1]->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(val[1]->As<ast::SintLiteralExpression>()->value, 2);
+
+  ASSERT_TRUE(val[2]->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(val[2]->As<ast::SintLiteralExpression>()->value, 3);
+
+  ASSERT_TRUE(val[3]->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(val[3]->As<ast::SintLiteralExpression>()->value, 4);
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_ZeroConstructor) {
+  auto p = parser("vec4<i32>()");
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* call = e->As<ast::CallExpression>();
+
+  ASSERT_EQ(call->args.size(), 0u);
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidTypeDecl) {
+  auto p = parser("vec4<if>(2., 3., 4., 5.)");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:6: invalid type for vector");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingLeftParen) {
+  auto p = parser("vec4<f32> 2., 3., 4., 5.)");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingRightParen) {
+  auto p = parser("vec4<f32>(2., 3., 4., 5.");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:25: expected ')' for type constructor");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidValue) {
+  auto p = parser("i32(if(a) {})");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: expected ')' for type constructor");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_Empty) {
+  auto p = parser(R"(
+  struct S { a : i32; b : f32; }
+  S()
+  )");
+
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* call = e->As<ast::CallExpression>();
+
+  ASSERT_NE(call->target.name, nullptr);
+  EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
+
+  ASSERT_EQ(call->args.size(), 0u);
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_NotEmpty) {
+  auto p = parser(R"(
+  struct S { a : i32; b : f32; }
+  S(1u, 2.0)
+  )");
+
+  p->expect_global_decl();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* call = e->As<ast::CallExpression>();
+
+  ASSERT_NE(call->target.name, nullptr);
+  EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
+
+  ASSERT_EQ(call->args.size(), 2u);
+
+  ASSERT_TRUE(call->args[0]->Is<ast::UintLiteralExpression>());
+  EXPECT_EQ(call->args[0]->As<ast::UintLiteralExpression>()->value, 1u);
+
+  ASSERT_TRUE(call->args[1]->Is<ast::FloatLiteralExpression>());
+  EXPECT_EQ(call->args[1]->As<ast::FloatLiteralExpression>()->value, 2.f);
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_ConstLiteral_True) {
+  auto p = parser("true");
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::BoolLiteralExpression>());
+  EXPECT_TRUE(e->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_ParenExpr) {
+  auto p = parser("(a == b)");
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingRightParen) {
+  auto p = parser("(a == b");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected ')'");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingExpr) {
+  auto p = parser("()");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_InvalidExpr) {
+  auto p = parser("(if (a) {})");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:2: unable to parse expression");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Cast) {
+  auto p = parser("f32(1)");
+
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* call = e->As<ast::CallExpression>();
+
+  ASSERT_TRUE(call->target.type->Is<ast::F32>());
+  ASSERT_EQ(call->args.size(), 1u);
+
+  ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast) {
+  auto p = parser("bitcast<f32>(1)");
+
+  auto e = p->primary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::BitcastExpression>());
+
+  auto* c = e->As<ast::BitcastExpression>();
+  ASSERT_TRUE(c->type->Is<ast::F32>());
+  ASSERT_TRUE(c->expr->Is<ast::IntLiteralExpression>());
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingGreaterThan) {
+  auto p = parser("bitcast<f32(1)");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:12: expected '>' for bitcast expression");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingType) {
+  auto p = parser("bitcast<>(1)");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: invalid type for bitcast expression");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingLeftParen) {
+  auto p = parser("bitcast<f32>1)");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: expected '('");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingRightParen) {
+  auto p = parser("bitcast<f32>(1");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: expected ')'");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingExpression) {
+  auto p = parser("bitcast<f32>()");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: unable to parse expression");
+}
+
+TEST_F(ParserImplTest, PrimaryExpression_bitcast_InvalidExpression) {
+  auto p = parser("bitcast<f32>(if (a) {})");
+  auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: unable to parse expression");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc b/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc
new file mode 100644
index 0000000..534b3e2
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, RelationalExpression_Parses_LessThan) {
+  auto p = parser("a < true");
+  auto e = p->relational_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kLessThan, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThan) {
+  auto p = parser("a > true");
+  auto e = p->relational_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kGreaterThan, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_Parses_LessThanEqual) {
+  auto p = parser("a <= true");
+  auto e = p->relational_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kLessThanEqual, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThanEqual) {
+  auto p = parser("a >= true");
+  auto e = p->relational_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_InvalidLHS) {
+  auto p = parser("if (a) {} < true");
+  auto e = p->relational_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_InvalidRHS) {
+  auto p = parser("true < if (a) {}");
+  auto e = p->relational_expression();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
+}
+
+TEST_F(ParserImplTest, RelationalExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->relational_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
new file mode 100644
index 0000000..1966ec7
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
@@ -0,0 +1,115 @@
+// 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+using ParserImplReservedKeywordTest = ParserImplTestWithParam<std::string>;
+TEST_P(ParserImplReservedKeywordTest, Function) {
+  auto name = GetParam();
+  auto p = parser("fn " + name + "() {}");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, ModuleLet) {
+  auto name = GetParam();
+  auto p = parser("let " + name + " : i32 = 1;");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, ModuleVar) {
+  auto name = GetParam();
+  auto p = parser("var " + name + " : i32 = 1;");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, FunctionLet) {
+  auto name = GetParam();
+  auto p = parser("fn f() { let " + name + " : i32 = 1; }");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, FunctionVar) {
+  auto name = GetParam();
+  auto p = parser("fn f() { var " + name + " : i32 = 1; }");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, FunctionParam) {
+  auto name = GetParam();
+  auto p = parser("fn f(" + name + " : i32) {}");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, Struct) {
+  auto name = GetParam();
+  auto p = parser("struct " + name + " {};");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, StructMember) {
+  auto name = GetParam();
+  auto p = parser("struct S { " + name + " : i32; };");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:12: '" + name + "' is a reserved keyword");
+}
+TEST_P(ParserImplReservedKeywordTest, Alias) {
+  auto name = GetParam();
+  auto p = parser("type " + name + " = i32;");
+  EXPECT_FALSE(p->Parse());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest,
+                         ParserImplReservedKeywordTest,
+                         testing::Values("asm",
+                                         "bf16",
+                                         "const",
+                                         "do",
+                                         "enum",
+                                         "f16",
+                                         "f64",
+                                         "handle",
+                                         "i8",
+                                         "i16",
+                                         "i64",
+                                         "mat",
+                                         "premerge",
+                                         "regardless",
+                                         "typedef",
+                                         "u8",
+                                         "u16",
+                                         "u64",
+                                         "unless",
+                                         "using",
+                                         "vec",
+                                         "void",
+                                         "while"));
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_sampled_texture_type_test.cc b/src/tint/reader/wgsl/parser_impl_sampled_texture_type_test.cc
new file mode 100644
index 0000000..20a8643
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_sampled_texture_type_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, SampledTextureType_Invalid) {
+  auto p = parser("1234");
+  auto t = p->sampled_texture_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_1d) {
+  auto p = parser("texture_1d");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k1d);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_2d) {
+  auto p = parser("texture_2d");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k2d);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_2dArray) {
+  auto p = parser("texture_2d_array");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k2dArray);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_3d) {
+  auto p = parser("texture_3d");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k3d);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_Cube) {
+  auto p = parser("texture_cube");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::kCube);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SampledTextureType_kCubeArray) {
+  auto p = parser("texture_cube_array");
+  auto t = p->sampled_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::kCubeArray);
+  EXPECT_FALSE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_sampler_type_test.cc b/src/tint/reader/wgsl/parser_impl_sampler_type_test.cc
new file mode 100644
index 0000000..d04b7ef
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_sampler_type_test.cc
@@ -0,0 +1,58 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, SamplerType_Invalid) {
+  auto p = parser("1234");
+  auto t = p->sampler_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, SamplerType_Sampler) {
+  auto p = parser("sampler");
+  auto t = p->sampler_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Sampler>());
+  EXPECT_FALSE(t->As<ast::Sampler>()->IsComparison());
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
+}
+
+TEST_F(ParserImplTest, SamplerType_ComparisonSampler) {
+  auto p = parser("sampler_comparison");
+  auto t = p->sampler_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Sampler>());
+  EXPECT_TRUE(t->As<ast::Sampler>()->IsComparison());
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc b/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc
new file mode 100644
index 0000000..cfca8bb
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftLeft) {
+  auto p = parser("a << true");
+  auto e = p->shift_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftRight) {
+  auto p = parser("a >> true");
+  auto e = p->shift_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
+  EXPECT_EQ(ast::BinaryOp::kShiftRight, rel->op);
+
+  ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+  ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceLeft) {
+  auto p = parser("a < < true");
+  auto e = p->shift_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceRight) {
+  auto p = parser("a > > true");
+  auto e = p->shift_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ShiftExpression_InvalidLHS) {
+  auto p = parser("if (a) {} << true");
+  auto e = p->shift_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_InvalidRHS) {
+  auto p = parser("true << if (a) {}");
+  auto e = p->shift_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:9: unable to parse right side of << expression");
+}
+
+TEST_F(ParserImplTest, ShiftExpression_NoOr_ReturnsLHS) {
+  auto p = parser("a true");
+  auto e = p->shift_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
new file mode 100644
index 0000000..115f676
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -0,0 +1,263 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, SingularExpression_Array_ConstantIndex) {
+  auto p = parser("a[1]");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
+  auto* idx = e->As<ast::IndexAccessorExpression>();
+
+  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
+  auto* ident = idx->object->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 1);
+}
+
+TEST_F(ParserImplTest, SingularExpression_Array_ExpressionIndex) {
+  auto p = parser("a[1 + b / 4]");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
+  auto* idx = e->As<ast::IndexAccessorExpression>();
+
+  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
+  auto* ident = idx->object->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(idx->index->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, SingularExpression_Array_MissingIndex) {
+  auto p = parser("a[]");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
+}
+
+TEST_F(ParserImplTest, SingularExpression_Array_MissingRightBrace) {
+  auto p = parser("a[1");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: expected ']' for index accessor");
+}
+
+TEST_F(ParserImplTest, SingularExpression_Array_InvalidIndex) {
+  auto p = parser("a[if(a() {})]");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
+}
+
+TEST_F(ParserImplTest, SingularExpression_Call_Empty) {
+  auto p = parser("a()");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* c = e->As<ast::CallExpression>();
+
+  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+
+  EXPECT_EQ(c->args.size(), 0u);
+}
+
+TEST_F(ParserImplTest, SingularExpression_Call_WithArgs) {
+  auto p = parser("test(1, b, 2 + 3 / b)");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* c = e->As<ast::CallExpression>();
+
+  EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("test"));
+
+  EXPECT_EQ(c->args.size(), 3u);
+  EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
+  EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
+  EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, SingularExpression_Call_TrailingComma) {
+  auto p = parser("a(b, )");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* c = e->As<ast::CallExpression>();
+  EXPECT_EQ(c->args.size(), 1u);
+}
+
+TEST_F(ParserImplTest, SingularExpression_Call_InvalidArg) {
+  auto p = parser("a(if(a) {})");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
+}
+
+TEST_F(ParserImplTest, SingularExpression_Call_MissingRightParen) {
+  auto p = parser("a(");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: expected ')' for function call");
+}
+
+TEST_F(ParserImplTest, SingularExpression_MemberAccessor) {
+  auto p = parser("a.b");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::MemberAccessorExpression>());
+
+  auto* m = e->As<ast::MemberAccessorExpression>();
+  ASSERT_TRUE(m->structure->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(m->structure->As<ast::IdentifierExpression>()->symbol,
+            p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(m->member->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(m->member->As<ast::IdentifierExpression>()->symbol,
+            p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, SingularExpression_MemberAccesssor_InvalidIdent) {
+  auto p = parser("a.if");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
+}
+
+TEST_F(ParserImplTest, SingularExpression_MemberAccessor_MissingIdent) {
+  auto p = parser("a.");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
+}
+
+TEST_F(ParserImplTest, SingularExpression_NonMatch_returnLHS) {
+  auto p = parser("a b");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+}
+
+TEST_F(ParserImplTest, SingularExpression_Array_NestedIndexAccessor) {
+  auto p = parser("a[b[c]]");
+  auto e = p->singular_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  const auto* outer_accessor = e->As<ast::IndexAccessorExpression>();
+  ASSERT_TRUE(outer_accessor);
+
+  const auto* outer_object =
+      outer_accessor->object->As<ast::IdentifierExpression>();
+  ASSERT_TRUE(outer_object);
+  EXPECT_EQ(outer_object->symbol, p->builder().Symbols().Get("a"));
+
+  const auto* inner_accessor =
+      outer_accessor->index->As<ast::IndexAccessorExpression>();
+  ASSERT_TRUE(inner_accessor);
+
+  const auto* inner_object =
+      inner_accessor->object->As<ast::IdentifierExpression>();
+  ASSERT_TRUE(inner_object);
+  EXPECT_EQ(inner_object->symbol, p->builder().Symbols().Get("b"));
+
+  const auto* index_expr =
+      inner_accessor->index->As<ast::IdentifierExpression>();
+  ASSERT_TRUE(index_expr);
+  EXPECT_EQ(index_expr->symbol, p->builder().Symbols().Get("c"));
+}
+
+TEST_F(ParserImplTest, SingularExpression_PostfixPlusPlus) {
+  auto p = parser("a++");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:2: postfix increment and decrement operators are reserved for a "
+            "future WGSL version");
+}
+
+TEST_F(ParserImplTest, SingularExpression_PostfixMinusMinus) {
+  auto p = parser("a--");
+  auto e = p->singular_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:2: postfix increment and decrement operators are reserved for a "
+            "future WGSL version");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc
new file mode 100644
index 0000000..47db2d1
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc
@@ -0,0 +1,282 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Statement) {
+  auto p = parser("return;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Semicolon) {
+  auto p = parser(";");
+  p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+}
+
+TEST_F(ParserImplTest, Statement_Return_NoValue) {
+  auto p = parser("return;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
+  auto* ret = e->As<ast::ReturnStatement>();
+  ASSERT_EQ(ret->value, nullptr);
+}
+
+TEST_F(ParserImplTest, Statement_Return_Value) {
+  auto p = parser("return a + b * (.1 - .2);");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::ReturnStatement>());
+  auto* ret = e->As<ast::ReturnStatement>();
+  ASSERT_NE(ret->value, nullptr);
+  EXPECT_TRUE(ret->value->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, Statement_Return_MissingSemi) {
+  auto p = parser("return");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:7: expected ';' for return statement");
+}
+
+TEST_F(ParserImplTest, Statement_Return_Invalid) {
+  auto p = parser("return if(a) {};");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: expected ';' for return statement");
+}
+
+TEST_F(ParserImplTest, Statement_If) {
+  auto p = parser("if (a) {}");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::IfStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_If_Invalid) {
+  auto p = parser("if (a) { fn main() -> {}}");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:10: expected '}'");
+}
+
+TEST_F(ParserImplTest, Statement_Variable) {
+  auto p = parser("var a : i32 = 1;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Variable_Invalid) {
+  auto p = parser("var a : i32 =;");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:14: missing constructor for variable declaration");
+}
+
+TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) {
+  auto p = parser("var a : i32");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected ';' for variable declaration");
+}
+
+TEST_F(ParserImplTest, Statement_Switch) {
+  auto p = parser("switch (a) {}");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Switch_Invalid) {
+  auto p = parser("switch (a) { case: {}}");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:18: unable to parse case selectors");
+}
+
+TEST_F(ParserImplTest, Statement_Loop) {
+  auto p = parser("loop {}");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::LoopStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Loop_Invalid) {
+  auto p = parser("loop discard; }");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
+}
+
+TEST_F(ParserImplTest, Statement_Assignment) {
+  auto p = parser("a = b;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::AssignmentStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Assignment_Invalid) {
+  auto p = parser("a = if(b) {};");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:5: unable to parse right side of assignment");
+}
+
+TEST_F(ParserImplTest, Statement_Assignment_MissingSemicolon) {
+  auto p = parser("a = b");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected ';' for assignment statement");
+}
+
+TEST_F(ParserImplTest, Statement_Break) {
+  auto p = parser("break;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::BreakStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Break_MissingSemicolon) {
+  auto p = parser("break");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected ';' for break statement");
+}
+
+TEST_F(ParserImplTest, Statement_Continue) {
+  auto p = parser("continue;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::ContinueStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Continue_MissingSemicolon) {
+  auto p = parser("continue");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:9: expected ';' for continue statement");
+}
+
+TEST_F(ParserImplTest, Statement_Discard) {
+  auto p = parser("discard;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::DiscardStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Discard_MissingSemicolon) {
+  auto p = parser("discard");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(p->error(), "1:8: expected ';' for discard statement");
+}
+
+TEST_F(ParserImplTest, Statement_Body) {
+  auto p = parser("{ var i: i32; }");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e->Is<ast::BlockStatement>());
+  EXPECT_TRUE(e->As<ast::BlockStatement>()
+                  ->statements[0]
+                  ->Is<ast::VariableDeclStatement>());
+}
+
+TEST_F(ParserImplTest, Statement_Body_Invalid) {
+  auto p = parser("{ fn main() -> {}}");
+  auto e = p->statement();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:3: expected '}'");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_statements_test.cc b/src/tint/reader/wgsl/parser_impl_statements_test.cc
new file mode 100644
index 0000000..ac505ea
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_statements_test.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Statements) {
+  auto p = parser("discard; return;");
+  auto e = p->expect_statements();
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e->size(), 2u);
+  EXPECT_TRUE(e.value[0]->Is<ast::DiscardStatement>());
+  EXPECT_TRUE(e.value[1]->Is<ast::ReturnStatement>());
+}
+
+TEST_F(ParserImplTest, Statements_Empty) {
+  auto p = parser("");
+  auto e = p->expect_statements();
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e->size(), 0u);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
new file mode 100644
index 0000000..87338ff
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+struct StorageClassData {
+  const char* input;
+  ast::StorageClass result;
+};
+inline std::ostream& operator<<(std::ostream& out, StorageClassData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class StorageClassTest : public ParserImplTestWithParam<StorageClassData> {};
+
+TEST_P(StorageClassTest, Parses) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+
+  auto sc = p->expect_storage_class("test");
+  EXPECT_FALSE(sc.errored);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(sc.value, params.result);
+
+  auto t = p->next();
+  EXPECT_TRUE(t.IsEof());
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    StorageClassTest,
+    testing::Values(
+        StorageClassData{"uniform", ast::StorageClass::kUniform},
+        StorageClassData{"workgroup", ast::StorageClass::kWorkgroup},
+        StorageClassData{"storage", ast::StorageClass::kStorage},
+        StorageClassData{"storage_buffer", ast::StorageClass::kStorage},
+        StorageClassData{"private", ast::StorageClass::kPrivate},
+        StorageClassData{"function", ast::StorageClass::kFunction}));
+
+TEST_F(ParserImplTest, StorageClass_NoMatch) {
+  auto p = parser("not-a-storage-class");
+  auto sc = p->expect_storage_class("test");
+  EXPECT_EQ(sc.errored, true);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:1: invalid storage class for test");
+
+  auto t = p->next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.to_str(), "not");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_storage_texture_type_test.cc b/src/tint/reader/wgsl/parser_impl_storage_texture_type_test.cc
new file mode 100644
index 0000000..39d1846
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_storage_texture_type_test.cc
@@ -0,0 +1,69 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, StorageTextureType_Invalid) {
+  auto p = parser("abc");
+  auto t = p->storage_texture_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, StorageTextureType_1d) {
+  auto p = parser("texture_storage_1d");
+  auto t = p->storage_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k1d);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, StorageTextureType_2d) {
+  auto p = parser("texture_storage_2d");
+  auto t = p->storage_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k2d);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, StorageTextureType_2dArray) {
+  auto p = parser("texture_storage_2d_array");
+  auto t = p->storage_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k2dArray);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, StorageTextureType_3d) {
+  auto p = parser("texture_storage_3d");
+  auto t = p->storage_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TextureDimension::k3d);
+  EXPECT_FALSE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
new file mode 100644
index 0000000..d7ad8b1
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/invariant_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AttributeDecl_Parses) {
+  auto p = parser("@invariant");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 1u);
+  auto* invariant = attrs.value[0]->As<ast::Attribute>();
+  EXPECT_TRUE(invariant->Is<ast::InvariantAttribute>());
+}
+
+TEST_F(ParserImplTest, AttributeDecl_MissingParenLeft) {
+  auto p = parser("@location 1)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(), "1:11: expected '(' for location attribute");
+}
+
+TEST_F(ParserImplTest, AttributeDecl_MissingValue) {
+  auto p = parser("@location()");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(),
+            "1:11: expected signed integer literal for location attribute");
+}
+
+TEST_F(ParserImplTest, AttributeDecl_MissingParenRight) {
+  auto p = parser("@location(1");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(), "1:12: expected ')' for location attribute");
+}
+
+TEST_F(ParserImplTest, AttributeDecl_Invalidattribute) {
+  auto p = parser("@invalid");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_attributeDecl_Parses) {
+  auto p = parser("[[invariant]]");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 1u);
+  auto* invariant_attr = attrs.value[0]->As<ast::Attribute>();
+  EXPECT_TRUE(invariant_attr->Is<ast::InvariantAttribute>());
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_attributeDecl_MissingAttrRight) {
+  auto p = parser("[[invariant");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:12: expected ']]' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_attributeDecl_Invalidattribute) {
+  auto p = parser("[[invalid]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_struct_attribute_test.cc
new file mode 100644
index 0000000..451f749
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_attribute_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+struct AttributeData {
+  const char* input;
+  bool is_block;
+};
+inline std::ostream& operator<<(std::ostream& out, AttributeData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class AttributeTest : public ParserImplTestWithParam<AttributeData> {};
+
+TEST_P(AttributeTest, Parses) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+
+  auto attr = p->attribute();
+  ASSERT_FALSE(p->has_error());
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* struct_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(struct_attr, nullptr);
+  EXPECT_EQ(struct_attr->Is<ast::StructBlockAttribute>(), params.is_block);
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         AttributeTest,
+                         testing::Values(AttributeData{"block", true}));
+
+TEST_F(ParserImplTest, Attribute_NoMatch) {
+  auto p = parser("not-a-stage");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_EQ(attr.value, nullptr);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
new file mode 100644
index 0000000..d03b2d3
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -0,0 +1,93 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, StructBodyDecl_Parses) {
+  auto p = parser("{a : i32;}");
+
+  auto& builder = p->builder();
+
+  auto m = p->expect_struct_body_decl();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_EQ(m.value.size(), 1u);
+
+  const auto* mem = m.value[0];
+  EXPECT_EQ(mem->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(mem->type->Is<ast::I32>());
+  EXPECT_EQ(mem->attributes.size(), 0u);
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_ParsesEmpty) {
+  auto p = parser("{}");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_EQ(m.value.size(), 0u);
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_InvalidAlign) {
+  auto p = parser(R"(
+{
+  @align(nan) a : i32;
+})");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  EXPECT_EQ(p->error(),
+            "3:10: expected signed integer literal for align attribute");
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_InvalidSize) {
+  auto p = parser(R"(
+{
+  @size(nan) a : i32;
+})");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  EXPECT_EQ(p->error(),
+            "3:9: expected signed integer literal for size attribute");
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_MissingClosingBracket) {
+  auto p = parser("{a : i32;");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  EXPECT_EQ(p->error(), "1:10: expected '}' for struct declaration");
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_InvalidToken) {
+  auto p = parser(R"(
+{
+  a : i32;
+  1.23
+} )");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  EXPECT_EQ(p->error(), "4:3: expected identifier for struct member");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc
new file mode 100644
index 0000000..448b31f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -0,0 +1,222 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, StructDecl_Parses) {
+  auto p = parser(R"(
+struct S {
+  a : i32;
+  b : f32;
+})");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 0u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s->name, p->builder().Symbols().Register("S"));
+  ASSERT_EQ(s->members.size(), 2u);
+  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
+  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
+}
+
+TEST_F(ParserImplTest, StructDecl_Unicode_Parses) {
+  const std::string struct_ident =  // "𝓼𝓽𝓻𝓾𝓬𝓽𝓾𝓻𝓮"
+      "\xf0\x9d\x93\xbc\xf0\x9d\x93\xbd\xf0\x9d\x93\xbb\xf0\x9d\x93\xbe\xf0\x9d"
+      "\x93\xac\xf0\x9d\x93\xbd\xf0\x9d\x93\xbe\xf0\x9d\x93\xbb\xf0\x9d\x93"
+      "\xae";
+  const std::string member_a_ident =  // "𝕞𝕖𝕞𝕓𝕖𝕣_𝕒"
+      "\xf0\x9d\x95\x9e\xf0\x9d\x95\x96\xf0\x9d\x95\x9e\xf0\x9d\x95\x93\xf0\x9d"
+      "\x95\x96\xf0\x9d\x95\xa3\x5f\xf0\x9d\x95\x92";
+  const std::string member_b_ident =  // "𝔪𝔢𝔪𝔟𝔢𝔯_𝔟"
+      "\xf0\x9d\x94\xaa\xf0\x9d\x94\xa2\xf0\x9d\x94\xaa\xf0\x9d\x94\x9f\xf0\x9d"
+      "\x94\xa2\xf0\x9d\x94\xaf\x5f\xf0\x9d\x94\x9f";
+
+  std::string src = R"(
+struct $struct {
+  $member_a : i32;
+  $member_b : f32;
+})";
+  src = utils::ReplaceAll(src, "$struct", struct_ident);
+  src = utils::ReplaceAll(src, "$member_a", member_a_ident);
+  src = utils::ReplaceAll(src, "$member_b", member_b_ident);
+
+  auto p = parser(src);
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 0u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s->name, p->builder().Symbols().Register(struct_ident));
+  ASSERT_EQ(s->members.size(), 2u);
+  EXPECT_EQ(s->members[0]->symbol,
+            p->builder().Symbols().Register(member_a_ident));
+  EXPECT_EQ(s->members[1]->symbol,
+            p->builder().Symbols().Register(member_b_ident));
+}
+
+TEST_F(ParserImplTest, StructDecl_ParsesWithAttribute) {
+  auto p = parser(R"(
+[[block]] struct B {
+  a : f32;
+  b : f32;
+})");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 1u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s->name, p->builder().Symbols().Register("B"));
+  ASSERT_EQ(s->members.size(), 2u);
+  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
+  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
+  ASSERT_EQ(s->attributes.size(), 1u);
+  EXPECT_TRUE(s->attributes[0]->Is<ast::StructBlockAttribute>());
+}
+
+TEST_F(ParserImplTest, StructDecl_ParsesWithMultipleAttribute) {
+  auto p = parser(R"(
+[[block]]
+[[block]] struct S {
+  a : f32;
+  b : f32;
+})");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 2u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s->name, p->builder().Symbols().Register("S"));
+  ASSERT_EQ(s->members.size(), 2u);
+  EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
+  EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
+  ASSERT_EQ(s->attributes.size(), 2u);
+  EXPECT_TRUE(s->attributes[0]->Is<ast::StructBlockAttribute>());
+  EXPECT_TRUE(s->attributes[1]->Is<ast::StructBlockAttribute>());
+}
+
+TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
+  auto p = parser("struct S {}");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 0u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s->members.size(), 0u);
+}
+
+TEST_F(ParserImplTest, StructDecl_MissingIdent) {
+  auto p = parser("struct {}");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 0u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_TRUE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected identifier for struct declaration");
+}
+
+TEST_F(ParserImplTest, StructDecl_MissingBracketLeft) {
+  auto p = parser("struct S }");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 0u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_TRUE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: expected '{' for struct declaration");
+}
+
+// TODO(crbug.com/tint/1324): DEPRECATED: Remove when @block is removed.
+TEST_F(ParserImplTest, StructDecl_InvalidAttributeDecl) {
+  auto p = parser("[[block struct S { a : i32; }");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  EXPECT_NE(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: use of deprecated language feature: [[block]] attributes have been removed from WGSL
+1:9: expected ']]' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1324): DEPRECATED: Remove when [[block]] is removed.
+TEST_F(ParserImplTest, StructDecl_MissingStruct) {
+  auto p = parser("[[block]] S {}");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 1u);
+
+  auto s = p->struct_decl(attrs.value);
+  EXPECT_FALSE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_FALSE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
new file mode 100644
index 0000000..0c2140f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AttributeDecl_EmptyStr) {
+  auto p = parser("");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 0u);
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeDecl_EmptyBlock) {
+  auto p = parser("[[]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 0u);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: empty attribute list)");
+}
+
+TEST_F(ParserImplTest, AttributeDecl_Single) {
+  auto p = parser("@size(4)");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 1u);
+  auto* attr = attrs.value[0]->As<ast::Attribute>();
+  ASSERT_NE(attr, nullptr);
+  EXPECT_TRUE(attr->Is<ast::StructMemberSizeAttribute>());
+}
+
+TEST_F(ParserImplTest, AttributeDecl_InvalidAttribute) {
+  auto p = parser("@size(nan)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for size attribute");
+}
+
+TEST_F(ParserImplTest, AttributeDecl_MissingClose) {
+  auto p = parser("[[size(4)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: expected ']]' for attribute list)");
+}
+
+TEST_F(ParserImplTest, StructMemberAttributeDecl_SizeMissingClose) {
+  auto p = parser("[[size(4)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: expected ']]' for attribute list)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_test.cc
new file mode 100644
index 0000000..9adf664
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_test.cc
@@ -0,0 +1,141 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Attribute_Size) {
+  auto p = parser("size(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  ASSERT_FALSE(p->has_error());
+
+  auto* member_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(member_attr, nullptr);
+  ASSERT_TRUE(member_attr->Is<ast::StructMemberSizeAttribute>());
+
+  auto* o = member_attr->As<ast::StructMemberSizeAttribute>();
+  EXPECT_EQ(o->size, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Size_MissingLeftParen) {
+  auto p = parser("size 4)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:6: expected '(' for size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Size_MissingRightParen) {
+  auto p = parser("size(4");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected ')' for size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Size_MissingValue) {
+  auto p = parser("size()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:6: expected signed integer literal for size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Size_MissingInvalid) {
+  auto p = parser("size(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:6: expected signed integer literal for size attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Align) {
+  auto p = parser("align(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  ASSERT_FALSE(p->has_error());
+
+  auto* member_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(member_attr, nullptr);
+  ASSERT_TRUE(member_attr->Is<ast::StructMemberAlignAttribute>());
+
+  auto* o = member_attr->As<ast::StructMemberAlignAttribute>();
+  EXPECT_EQ(o->align, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Align_MissingLeftParen) {
+  auto p = parser("align 4)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected '(' for align attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Align_MissingRightParen) {
+  auto p = parser("align(4");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected ')' for align attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Align_MissingValue) {
+  auto p = parser("align()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for align attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Align_MissingInvalid) {
+  auto p = parser("align(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for align attribute");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
new file mode 100644
index 0000000..5aafabc
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
@@ -0,0 +1,179 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, StructMember_Parses) {
+  auto p = parser("a : i32;");
+
+  auto& builder = p->builder();
+
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 0u);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(m->type->Is<ast::I32>());
+  EXPECT_EQ(m->attributes.size(), 0u);
+
+  EXPECT_EQ(m->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
+  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 5u}, {1u, 8u}}));
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithAlignAttribute) {
+  auto p = parser("@align(2) a : i32;");
+
+  auto& builder = p->builder();
+
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 1u);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(m->type->Is<ast::I32>());
+  EXPECT_EQ(m->attributes.size(), 1u);
+  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberAlignAttribute>());
+  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberAlignAttribute>()->align, 2u);
+
+  EXPECT_EQ(m->source.range, (Source::Range{{1u, 11u}, {1u, 12u}}));
+  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 15u}, {1u, 18u}}));
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithSizeAttribute) {
+  auto p = parser("@size(2) a : i32;");
+
+  auto& builder = p->builder();
+
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 1u);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(m->type->Is<ast::I32>());
+  EXPECT_EQ(m->attributes.size(), 1u);
+  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
+  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
+
+  EXPECT_EQ(m->source.range, (Source::Range{{1u, 10u}, {1u, 11u}}));
+  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithAttribute) {
+  auto p = parser("@size(2) a : i32;");
+
+  auto& builder = p->builder();
+
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 1u);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(m->type->Is<ast::I32>());
+  EXPECT_EQ(m->attributes.size(), 1u);
+  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
+  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
+
+  EXPECT_EQ(m->source.range, (Source::Range{{1u, 10u}, {1u, 11u}}));
+  EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithMultipleattributes) {
+  auto p = parser(R"(@size(2)
+@align(4) a : i32;)");
+
+  auto& builder = p->builder();
+
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_TRUE(attrs.matched);
+  EXPECT_EQ(attrs.value.size(), 2u);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
+  EXPECT_TRUE(m->type->Is<ast::I32>());
+  EXPECT_EQ(m->attributes.size(), 2u);
+  EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
+  EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
+  EXPECT_TRUE(m->attributes[1]->Is<ast::StructMemberAlignAttribute>());
+  EXPECT_EQ(m->attributes[1]->As<ast::StructMemberAlignAttribute>()->align, 4u);
+
+  EXPECT_EQ(m->source.range, (Source::Range{{2u, 11u}, {2u, 12u}}));
+  EXPECT_EQ(m->type->source.range, (Source::Range{{2u, 15u}, {2u, 18u}}));
+}
+
+TEST_F(ParserImplTest, StructMember_InvalidAttribute) {
+  auto p = parser("@size(nan) a : i32;");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for size attribute");
+}
+
+TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
+  auto p = parser("a : i32");
+  auto attrs = p->attribute_list();
+  EXPECT_FALSE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+
+  auto m = p->expect_struct_member(attrs.value);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  ASSERT_EQ(m.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: expected ';' for struct member");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_switch_body_test.cc b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
new file mode 100644
index 0000000..3f9ffc9
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
@@ -0,0 +1,224 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, SwitchBody_Case) {
+  auto p = parser("case 1: { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::CaseStatement>());
+  EXPECT_FALSE(e->IsDefault());
+  auto* stmt = e->As<ast::CaseStatement>();
+  ASSERT_EQ(stmt->selectors.size(), 1u);
+  EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
+  ASSERT_EQ(e->body->statements.size(), 1u);
+  EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_TrailingComma) {
+  auto p = parser("case 1, 2,: { }");
+  auto e = p->switch_body();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::CaseStatement>());
+  EXPECT_FALSE(e->IsDefault());
+  auto* stmt = e->As<ast::CaseStatement>();
+  ASSERT_EQ(stmt->selectors.size(), 2u);
+  EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
+  EXPECT_EQ(stmt->selectors[1]->ValueAsU32(), 2u);
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_InvalidConstLiteral) {
+  auto p = parser("case a == 4: { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: unable to parse case selectors");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_InvalidSelector_bool) {
+  auto p = parser("case true: { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: invalid case selector must be an integer value");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MissingConstLiteral) {
+  auto p = parser("case: { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:5: unable to parse case selectors");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MissingColon) {
+  auto p = parser("case 1 { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketLeft) {
+  auto p = parser("case 1: a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:9: expected '{' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketRight) {
+  auto p = parser("case 1: { a = 4; ");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:18: expected '}' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_InvalidCaseBody) {
+  auto p = parser("case 1: { fn main() {} }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:11: expected '}' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectors) {
+  auto p = parser("case 1, 2: { }");
+  auto e = p->switch_body();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::CaseStatement>());
+  EXPECT_FALSE(e->IsDefault());
+  ASSERT_EQ(e->body->statements.size(), 0u);
+  ASSERT_EQ(e->selectors.size(), 2u);
+  ASSERT_EQ(e->selectors[0]->ValueAsI32(), 1);
+  ASSERT_EQ(e->selectors[1]->ValueAsI32(), 2);
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingColon) {
+  auto p = parser("case 1, 2 { }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:11: expected ':' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingComma) {
+  auto p = parser("case 1 2: { }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsStartsWithComma) {
+  auto p = parser("case , 1, 2: { }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: unable to parse case selectors");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Default) {
+  auto p = parser("default: { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::CaseStatement>());
+  EXPECT_TRUE(e->IsDefault());
+  ASSERT_EQ(e->body->statements.size(), 1u);
+  EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
+}
+
+TEST_F(ParserImplTest, SwitchBody_Default_MissingColon) {
+  auto p = parser("default { a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:9: expected ':' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketLeft) {
+  auto p = parser("default: a = 4; }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:10: expected '{' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketRight) {
+  auto p = parser("default: { a = 4; ");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:19: expected '}' for case statement");
+}
+
+TEST_F(ParserImplTest, SwitchBody_Default_InvalidCaseBody) {
+  auto p = parser("default: { fn main() {} }");
+  auto e = p->switch_body();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected '}' for case statement");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
new file mode 100644
index 0000000..1d8fb2f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, SwitchStmt_WithoutDefault) {
+  auto p = parser(R"(switch(a) {
+  case 1: {}
+  case 2: {}
+})");
+  auto e = p->switch_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
+  ASSERT_EQ(e->body.size(), 2u);
+  EXPECT_FALSE(e->body[0]->IsDefault());
+  EXPECT_FALSE(e->body[1]->IsDefault());
+}
+
+TEST_F(ParserImplTest, SwitchStmt_Empty) {
+  auto p = parser("switch(a) { }");
+  auto e = p->switch_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
+  ASSERT_EQ(e->body.size(), 0u);
+}
+
+TEST_F(ParserImplTest, SwitchStmt_DefaultInMiddle) {
+  auto p = parser(R"(switch(a) {
+  case 1: {}
+  default: {}
+  case 2: {}
+})");
+  auto e = p->switch_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::SwitchStatement>());
+
+  ASSERT_EQ(e->body.size(), 3u);
+  ASSERT_FALSE(e->body[0]->IsDefault());
+  ASSERT_TRUE(e->body[1]->IsDefault());
+  ASSERT_FALSE(e->body[2]->IsDefault());
+}
+
+TEST_F(ParserImplTest, SwitchStmt_InvalidExpression) {
+  auto p = parser("switch(a=b) {}");
+  auto e = p->switch_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: expected ')'");
+}
+
+TEST_F(ParserImplTest, SwitchStmt_MissingExpression) {
+  auto p = parser("switch {}");
+  auto e = p->switch_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected '('");
+}
+
+TEST_F(ParserImplTest, SwitchStmt_MissingBracketLeft) {
+  auto p = parser("switch(a) }");
+  auto e = p->switch_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:11: expected '{' for switch statement");
+}
+
+TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
+  auto p = parser("switch(a) {");
+  auto e = p->switch_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:12: expected '}' for switch statement");
+}
+
+TEST_F(ParserImplTest, SwitchStmt_InvalidBody) {
+  auto p = parser(R"(switch(a) {
+  case: {}
+})");
+  auto e = p->switch_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "2:7: unable to parse case selectors");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_test.cc b/src/tint/reader/wgsl/parser_impl_test.cc
new file mode 100644
index 0000000..c0348da
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_test.cc
@@ -0,0 +1,144 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Empty) {
+  auto p = parser("");
+  ASSERT_TRUE(p->Parse()) << p->error();
+}
+
+TEST_F(ParserImplTest, Parses) {
+  auto p = parser(R"(
+@stage(fragment)
+fn main() -> @location(0) vec4<f32> {
+  return vec4<f32>(.4, .2, .3, 1);
+}
+)");
+  ASSERT_TRUE(p->Parse()) << p->error();
+
+  Program program = p->program();
+  ASSERT_EQ(1u, program.AST().Functions().size());
+}
+
+TEST_F(ParserImplTest, Parses_ExtraSemicolons) {
+  auto p = parser(R"(
+;
+struct S {
+  a : f32;
+};;
+;
+fn foo() -> S {
+  ;
+  return S();;;
+  ;
+};;
+;
+)");
+  ASSERT_TRUE(p->Parse()) << p->error();
+
+  Program program = p->program();
+  ASSERT_EQ(1u, program.AST().Functions().size());
+  ASSERT_EQ(1u, program.AST().TypeDecls().size());
+}
+
+TEST_F(ParserImplTest, HandlesError) {
+  auto p = parser(R"(
+fn main() ->  {  // missing return type
+  return;
+})");
+
+  ASSERT_FALSE(p->Parse());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "2:15: unable to determine function return type");
+}
+
+TEST_F(ParserImplTest, HandlesUnexpectedToken) {
+  auto p = parser(R"(
+fn main() {
+}
+foobar
+)");
+
+  ASSERT_FALSE(p->Parse());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "4:1: unexpected token");
+}
+
+TEST_F(ParserImplTest, HandlesBadToken_InMiddle) {
+  auto p = parser(R"(
+fn main() {
+  let f = 0x1p500000000000; // Exponent too big for hex float
+  return;
+})");
+
+  ASSERT_FALSE(p->Parse());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "3:11: exponent is too large for hex float");
+}
+
+TEST_F(ParserImplTest, HandlesBadToken_AtModuleScope) {
+  auto p = parser(R"(
+fn main() {
+  return;
+}
+0x1p5000000000000
+)");
+
+  ASSERT_FALSE(p->Parse());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "5:1: exponent is too large for hex float");
+}
+
+TEST_F(ParserImplTest, Comments_TerminatedBlockComment) {
+  auto p = parser(R"(
+/**
+ * Here is my shader.
+ *
+ * /* I can nest /**/ comments. */
+ * // I can nest line comments too.
+ **/
+@stage(fragment) // This is the stage
+fn main(/*
+no
+parameters
+*/) -> @location(0) vec4<f32> {
+  return/*block_comments_delimit_tokens*/vec4<f32>(.4, .2, .3, 1);
+}/* block comments are OK at EOF...*/)");
+
+  ASSERT_TRUE(p->Parse()) << p->error();
+  ASSERT_EQ(1u, p->program().AST().Functions().size());
+}
+
+TEST_F(ParserImplTest, Comments_UnterminatedBlockComment) {
+  auto p = parser(R"(
+@stage(fragment)
+fn main() -> @location(0) vec4<f32> {
+  return vec4<f32>(.4, .2, .3, 1);
+} /* unterminated block comments are invalid ...)");
+
+  ASSERT_FALSE(p->Parse());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "5:3: unterminated block comment") << p->error();
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_test_helper.cc b/src/tint/reader/wgsl/parser_impl_test_helper.cc
new file mode 100644
index 0000000..443c840
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_test_helper.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+ParserImplTest::ParserImplTest() = default;
+
+ParserImplTest::~ParserImplTest() = default;
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_test_helper.h b/src/tint/reader/wgsl/parser_impl_test_helper.h
new file mode 100644
index 0000000..b9a12b3
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_test_helper.h
@@ -0,0 +1,78 @@
+// Copyright 2020 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_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
+#define SRC_TINT_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/tint/reader/wgsl/parser_impl.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+/// WGSL Parser test class
+class ParserImplTest : public testing::Test, public ProgramBuilder {
+ public:
+  /// Constructor
+  ParserImplTest();
+  ~ParserImplTest() override;
+
+  /// Retrieves the parser from the helper
+  /// @param str the string to parse
+  /// @returns the parser implementation
+  std::unique_ptr<ParserImpl> parser(const std::string& str) {
+    auto file = std::make_unique<Source::File>("test.wgsl", str);
+    auto impl = std::make_unique<ParserImpl>(file.get());
+    files_.emplace_back(std::move(file));
+    return impl;
+  }
+
+ private:
+  std::vector<std::unique_ptr<Source::File>> files_;
+};
+
+/// WGSL Parser test class with param
+template <typename T>
+class ParserImplTestWithParam : public testing::TestWithParam<T>,
+                                public ProgramBuilder {
+ public:
+  /// Constructor
+  ParserImplTestWithParam() = default;
+  ~ParserImplTestWithParam() override = default;
+
+  /// Retrieves the parser from the helper
+  /// @param str the string to parse
+  /// @returns the parser implementation
+  std::unique_ptr<ParserImpl> parser(const std::string& str) {
+    auto file = std::make_unique<Source::File>("test.wgsl", str);
+    auto impl = std::make_unique<ParserImpl>(file.get());
+    files_.emplace_back(std::move(file));
+    return impl;
+  }
+
+ private:
+  std::vector<std::unique_ptr<Source::File>> files_;
+};
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_PARSER_IMPL_TEST_HELPER_H_
diff --git a/src/tint/reader/wgsl/parser_impl_texel_format_test.cc b/src/tint/reader/wgsl/parser_impl_texel_format_test.cc
new file mode 100644
index 0000000..9a17f48
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_texel_format_test.cc
@@ -0,0 +1,161 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ImageStorageType_Invalid) {
+  auto p = parser("1234");
+  auto t = p->expect_texel_format("test");
+  EXPECT_TRUE(t.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:1: invalid format for test");
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Uint) {
+  auto p = parser("r32uint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kR32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Sint) {
+  auto p = parser("r32sint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kR32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Float) {
+  auto p = parser("r32float");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kR32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Unorm) {
+  auto p = parser("rgba8unorm");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Snorm) {
+  auto p = parser("rgba8snorm");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Snorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Uint) {
+  auto p = parser("rgba8uint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Sint) {
+  auto p = parser("rgba8sint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba8Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Uint) {
+  auto p = parser("rg32uint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Sint) {
+  auto p = parser("rg32sint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Float) {
+  auto p = parser("rg32float");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRg32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Uint) {
+  auto p = parser("rgba16uint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Sint) {
+  auto p = parser("rgba16sint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Float) {
+  auto p = parser("rgba16float");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba16Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Uint) {
+  auto p = parser("rgba32uint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Sint) {
+  auto p = parser("rgba32sint");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Float) {
+  auto p = parser("rgba32float");
+  auto t = p->expect_texel_format("test");
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::TexelFormat::kRgba32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_texture_sampler_types_test.cc b/src/tint/reader/wgsl/parser_impl_texture_sampler_types_test.cc
new file mode 100644
index 0000000..dda2c2d
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_texture_sampler_types_test.cc
@@ -0,0 +1,267 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, TextureSamplerTypes_Invalid) {
+  auto p = parser("1234");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_Sampler) {
+  auto p = parser("sampler");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Sampler>());
+  ASSERT_FALSE(t->As<ast::Sampler>()->IsComparison());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SamplerComparison) {
+  auto p = parser("sampler_comparison");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Sampler>());
+  ASSERT_TRUE(t->As<ast::Sampler>()->IsComparison());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_DepthTexture) {
+  auto p = parser("texture_depth_2d");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::DepthTexture>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32) {
+  auto p = parser("texture_1d<f32>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::SampledTexture>());
+  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::F32>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k1d);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32) {
+  auto p = parser("texture_2d<i32>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::SampledTexture>());
+  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::I32>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
+  auto p = parser("texture_3d<u32>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::SampledTexture>());
+  ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::U32>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k3d);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
+  auto p = parser("texture_1d<>");
+  auto t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:12: invalid type for sampled texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan) {
+  auto p = parser("texture_1d");
+  auto t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:11: expected '<' for sampled texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingGreaterThan) {
+  auto p = parser("texture_1d<u32");
+  auto t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:15: expected '>' for sampled texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
+  auto p = parser("texture_multisampled_2d<i32>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::MultisampledTexture>());
+  ASSERT_TRUE(t->As<ast::MultisampledTexture>()->type->Is<ast::I32>());
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 29u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
+  auto p = parser("texture_multisampled_2d<>");
+  auto t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:25: invalid type for multisampled texture type");
+}
+
+TEST_F(ParserImplTest,
+       TextureSamplerTypes_MultisampledTexture_MissingLessThan) {
+  auto p = parser("texture_multisampled_2d");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:24: expected '<' for multisampled texture type");
+}
+
+TEST_F(ParserImplTest,
+       TextureSamplerTypes_MultisampledTexture_MissingGreaterThan) {
+  auto p = parser("texture_multisampled_2d<u32");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:28: expected '>' for multisampled texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Readonly1dRg32Float) {
+  auto p = parser("texture_storage_1d<rg32float, read>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::StorageTexture>());
+  EXPECT_EQ(t->As<ast::StorageTexture>()->format, ast::TexelFormat::kRg32Float);
+  EXPECT_EQ(t->As<ast::StorageTexture>()->access, ast::Access::kRead);
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k1d);
+  EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 36u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Writeonly2dR32Uint) {
+  auto p = parser("texture_storage_2d<r32uint, write>");
+  auto t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+
+  ASSERT_TRUE(t->Is<ast::Texture>());
+  ASSERT_TRUE(t->Is<ast::StorageTexture>());
+  EXPECT_EQ(t->As<ast::StorageTexture>()->format, ast::TexelFormat::kR32Uint);
+  EXPECT_EQ(t->As<ast::StorageTexture>()->access, ast::Access::kWrite);
+  EXPECT_EQ(t->As<ast::Texture>()->dim, ast::TextureDimension::k2d);
+  EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 35u}}));
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType) {
+  auto p = parser("texture_storage_1d<abc, read>");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:20: invalid format for storage texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) {
+  auto p = parser("texture_storage_1d<r32float, abc>");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:30: invalid value for access control");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType) {
+  auto p = parser("texture_storage_1d<>");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:20: invalid format for storage texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingLessThan) {
+  auto p = parser("texture_storage_1d");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:19: expected '<' for storage texture type");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingGreaterThan) {
+  auto p = parser("texture_storage_1d<r32uint, read");
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
+  EXPECT_EQ(p->error(), "1:33: expected '>' for storage texture type");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_type_alias_test.cc b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
new file mode 100644
index 0000000..ae77ad6
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, TypeDecl_ParsesType) {
+  auto p = parser("type a = i32");
+
+  auto t = p->type_alias();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(t.errored);
+  EXPECT_TRUE(t.matched);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t->Is<ast::Alias>());
+  auto* alias = t->As<ast::Alias>();
+  ASSERT_TRUE(alias->type->Is<ast::I32>());
+
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 13u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Parses_Ident) {
+  auto p = parser("type a = B");
+
+  auto t = p->type_alias();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(t.errored);
+  EXPECT_TRUE(t.matched);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->Is<ast::Alias>());
+  auto* alias = t.value->As<ast::Alias>();
+  EXPECT_EQ(p->builder().Symbols().NameFor(alias->name), "a");
+  EXPECT_TRUE(alias->type->Is<ast::TypeName>());
+  EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Unicode_Parses_Ident) {
+  const std::string ident =  // "𝓶𝔂_𝓽𝔂𝓹𝓮"
+      "\xf0\x9d\x93\xb6\xf0\x9d\x94\x82\x5f\xf0\x9d\x93\xbd\xf0\x9d\x94\x82\xf0"
+      "\x9d\x93\xb9\xf0\x9d\x93\xae";
+
+  auto p = parser("type " + ident + " = i32");
+
+  auto t = p->type_alias();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(t.errored);
+  EXPECT_TRUE(t.matched);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->Is<ast::Alias>());
+  auto* alias = t.value->As<ast::Alias>();
+  EXPECT_EQ(p->builder().Symbols().NameFor(alias->name), ident);
+  EXPECT_TRUE(alias->type->Is<ast::I32>());
+  EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 37u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_MissingIdent) {
+  auto p = parser("type = i32");
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
+}
+
+TEST_F(ParserImplTest, TypeDecl_InvalidIdent) {
+  auto p = parser("type 123 = i32");
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
+}
+
+TEST_F(ParserImplTest, TypeDecl_MissingEqual) {
+  auto p = parser("type a i32");
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_EQ(p->error(), "1:8: expected '=' for type alias");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
new file mode 100644
index 0000000..dbb408f
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -0,0 +1,893 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+#include "src/tint/sem/sampled_texture_type.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, TypeDecl_Invalid) {
+  auto p = parser("1234");
+  auto t = p->type_decl();
+  EXPECT_EQ(t.errored, false);
+  EXPECT_EQ(t.matched, false);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, TypeDecl_Identifier) {
+  auto p = parser("A");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  auto* type_name = t.value->As<ast::TypeName>();
+  ASSERT_NE(type_name, nullptr);
+  EXPECT_EQ(p->builder().Symbols().Get("A"), type_name->name);
+  EXPECT_EQ(type_name->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Bool) {
+  auto p = parser("bool");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_TRUE(t.value->Is<ast::Bool>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_F32) {
+  auto p = parser("f32");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_TRUE(t.value->Is<ast::F32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_I32) {
+  auto p = parser("i32");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_TRUE(t.value->Is<ast::I32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_U32) {
+  auto p = parser("u32");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_TRUE(t.value->Is<ast::U32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
+}
+
+struct VecData {
+  const char* input;
+  size_t count;
+  Source::Range range;
+};
+inline std::ostream& operator<<(std::ostream& out, VecData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class VecTest : public ParserImplTestWithParam<VecData> {};
+
+TEST_P(VecTest, Parse) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  EXPECT_TRUE(t.value->Is<ast::Vector>());
+  EXPECT_EQ(t.value->As<ast::Vector>()->width, params.count);
+  EXPECT_EQ(t.value->source.range, params.range);
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    VecTest,
+    testing::Values(VecData{"vec2<f32>", 2, {{1u, 1u}, {1u, 10u}}},
+                    VecData{"vec3<f32>", 3, {{1u, 1u}, {1u, 10u}}},
+                    VecData{"vec4<f32>", 4, {{1u, 1u}, {1u, 10u}}}));
+
+class VecMissingGreaterThanTest : public ParserImplTestWithParam<VecData> {};
+
+TEST_P(VecMissingGreaterThanTest, Handles_Missing_GreaterThan) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:9: expected '>' for vector");
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         VecMissingGreaterThanTest,
+                         testing::Values(VecData{"vec2<f32", 2, {}},
+                                         VecData{"vec3<f32", 3, {}},
+                                         VecData{"vec4<f32", 4, {}}));
+
+class VecMissingType : public ParserImplTestWithParam<VecData> {};
+
+TEST_P(VecMissingType, Handles_Missing_Type) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:6: invalid type for vector");
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         VecMissingType,
+                         testing::Values(VecData{"vec2<>", 2, {}},
+                                         VecData{"vec3<>", 3, {}},
+                                         VecData{"vec4<>", 4, {}}));
+
+TEST_F(ParserImplTest, TypeDecl_Ptr) {
+  auto p = parser("ptr<function, f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Pointer>());
+
+  auto* ptr = t.value->As<ast::Pointer>();
+  ASSERT_TRUE(ptr->type->Is<ast::F32>());
+  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_WithAccess) {
+  auto p = parser("ptr<function, f32, read>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Pointer>());
+
+  auto* ptr = t.value->As<ast::Pointer>();
+  ASSERT_TRUE(ptr->type->Is<ast::F32>());
+  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
+  ASSERT_EQ(ptr->access, ast::Access::kRead);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_ToVec) {
+  auto p = parser("ptr<function, vec2<f32>>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Pointer>());
+
+  auto* ptr = t.value->As<ast::Pointer>();
+  ASSERT_TRUE(ptr->type->Is<ast::Vector>());
+  ASSERT_EQ(ptr->storage_class, ast::StorageClass::kFunction);
+
+  auto* vec = ptr->type->As<ast::Vector>();
+  ASSERT_EQ(vec->width, 2u);
+  ASSERT_TRUE(vec->type->Is<ast::F32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingLessThan) {
+  auto p = parser("ptr private, f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:5: expected '<' for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterType) {
+  auto p = parser("ptr<function, f32");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:18: expected '>' for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterAccess) {
+  auto p = parser("ptr<function, f32, read");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:24: expected '>' for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterStorageClass) {
+  auto p = parser("ptr<function f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:14: expected ',' for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterAccess) {
+  auto p = parser("ptr<function, f32 read>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:19: expected '>' for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingStorageClass) {
+  auto p = parser("ptr<, f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingType) {
+  auto p = parser("ptr<function,>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:14: invalid type for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingAccess) {
+  auto p = parser("ptr<function, i32, >");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:20: expected identifier for access control");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_MissingParams) {
+  auto p = parser("ptr<>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
+  auto p = parser("ptr<unknown, f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Ptr_BadAccess) {
+  auto p = parser("ptr<function, i32, unknown>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:20: invalid value for access control");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Atomic) {
+  auto p = parser("atomic<f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Atomic>());
+
+  auto* atomic = t.value->As<ast::Atomic>();
+  ASSERT_TRUE(atomic->type->Is<ast::F32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 12u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Atomic_ToVec) {
+  auto p = parser("atomic<vec2<f32>>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Atomic>());
+
+  auto* atomic = t.value->As<ast::Atomic>();
+  ASSERT_TRUE(atomic->type->Is<ast::Vector>());
+
+  auto* vec = atomic->type->As<ast::Vector>();
+  ASSERT_EQ(vec->width, 2u);
+  ASSERT_TRUE(vec->type->Is<ast::F32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Atomic_MissingLessThan) {
+  auto p = parser("atomic f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:8: expected '<' for atomic declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Atomic_MissingGreaterThan) {
+  auto p = parser("atomic<f32");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:11: expected '>' for atomic declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Atomic_MissingType) {
+  auto p = parser("atomic<>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:8: invalid type for atomic declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_SintLiteralSize) {
+  auto p = parser("array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_FALSE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+  EXPECT_EQ(a->attributes.size(), 0u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
+
+  auto* size = a->count->As<ast::SintLiteralExpression>();
+  ASSERT_NE(size, nullptr);
+  EXPECT_EQ(size->ValueAsI32(), 5);
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_UintLiteralSize) {
+  auto p = parser("array<f32, 5u>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_FALSE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+  EXPECT_EQ(a->attributes.size(), 0u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
+
+  auto* size = a->count->As<ast::UintLiteralExpression>();
+  ASSERT_NE(size, nullptr);
+  EXPECT_EQ(size->ValueAsU32(), 5u);
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_ConstantSize) {
+  auto p = parser("array<f32, size>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_FALSE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+  EXPECT_EQ(a->attributes.size(), 0u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+
+  auto* count_expr = a->count->As<ast::IdentifierExpression>();
+  ASSERT_NE(count_expr, nullptr);
+  EXPECT_EQ(p->builder().Symbols().NameFor(count_expr->symbol), "size");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride) {
+  auto p = parser("@stride(16) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_FALSE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+
+  auto* size = a->count->As<ast::SintLiteralExpression>();
+  ASSERT_NE(size, nullptr);
+  EXPECT_EQ(size->ValueAsI32(), 5);
+
+  ASSERT_EQ(a->attributes.size(), 1u);
+  auto* stride = a->attributes[0];
+  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
+  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 16u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 13u}, {1u, 26u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Stride) {
+  auto p = parser("@stride(16) array<f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+
+  ASSERT_EQ(a->attributes.size(), 1u);
+  auto* stride = a->attributes[0];
+  ASSERT_TRUE(stride->Is<ast::StrideAttribute>());
+  ASSERT_EQ(stride->As<ast::StrideAttribute>()->stride, 16u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 13u}, {1u, 23u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MultipleAttributes_OneBlock) {
+  auto p = parser("@stride(16) @stride(32) array<f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+
+  auto& attrs = a->attributes;
+  ASSERT_EQ(attrs.size(), 2u);
+  EXPECT_TRUE(attrs[0]->Is<ast::StrideAttribute>());
+  EXPECT_EQ(attrs[0]->As<ast::StrideAttribute>()->stride, 16u);
+  EXPECT_TRUE(attrs[1]->Is<ast::StrideAttribute>());
+  EXPECT_EQ(attrs[1]->As<ast::StrideAttribute>()->stride, 32u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 25u}, {1u, 35u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MultipleAttributes_MultipleBlocks) {
+  auto p = parser("@stride(16) @stride(32) array<f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::F32>());
+
+  auto& attrs = a->attributes;
+  ASSERT_EQ(attrs.size(), 2u);
+  EXPECT_TRUE(attrs[0]->Is<ast::StrideAttribute>());
+  EXPECT_EQ(attrs[0]->As<ast::StrideAttribute>()->stride, 16u);
+  EXPECT_TRUE(attrs[1]->Is<ast::StrideAttribute>());
+  EXPECT_EQ(attrs[1]->As<ast::StrideAttribute>()->stride, 32u);
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 25u}, {1u, 35u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Attribute_MissingArray) {
+  auto p = parser("@stride(16) f32");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:2: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+1:2: unexpected attributes)");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Attribute_UnknownAttribute) {
+  auto p = parser("@unknown(16) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), R"(1:2: expected attribute)");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingLeftParen) {
+  auto p = parser("@stride 4) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), R"(1:9: expected '(' for stride attribute)");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingRightParen) {
+  auto p = parser("@stride(4 array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:2: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+1:11: expected ')' for stride attribute)");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingValue) {
+  auto p = parser("@stride() array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:9: expected signed integer literal for stride attribute");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue) {
+  auto p = parser("@stride(invalid) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:9: expected signed integer literal for stride attribute");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue_Negative) {
+  auto p = parser("@stride(-1) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: stride attribute must be greater than 0");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Attribute_MissingClosingAttr) {
+  auto p = parser("[[stride(16) array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+1:14: expected ']]' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingLeftParen) {
+  auto p = parser("[[stride 4)]] array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: expected '(' for stride attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingRightParen) {
+  auto p = parser("[[stride(4]] array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+1:11: expected ')' for stride attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_MissingValue) {
+  auto p = parser("[[stride()]] array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: expected signed integer literal for stride attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_InvalidValue) {
+  auto p = parser("[[stride(invalid)]] array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: expected signed integer literal for stride attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_TypeDecl_Array_Stride_InvalidValue_Negative) {
+  auto p = parser("[[stride(-1)]] array<f32, 5>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:10: stride attribute must be greater than 0)");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
+  auto p = parser("array<u32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::U32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Vec) {
+  auto p = parser("array<vec4<u32>>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t.value->Is<ast::Array>());
+
+  auto* a = t.value->As<ast::Array>();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type->Is<ast::Vector>());
+  EXPECT_EQ(a->type->As<ast::Vector>()->width, 4u);
+  EXPECT_TRUE(a->type->As<ast::Vector>()->type->Is<ast::U32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
+  auto p = parser("array<f32, !>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:12: expected array size expression");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MissingSize) {
+  auto p = parser("array<f32,>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:11: expected array size expression");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MissingLessThan) {
+  auto p = parser("array f32>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:7: expected '<' for array declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MissingGreaterThan) {
+  auto p = parser("array<f32");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:10: expected '>' for array declaration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_MissingComma) {
+  auto p = parser("array<f32 3>");
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:11: expected '>' for array declaration");
+}
+
+struct MatrixData {
+  const char* input;
+  size_t columns;
+  size_t rows;
+  Source::Range range;
+};
+inline std::ostream& operator<<(std::ostream& out, MatrixData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class MatrixTest : public ParserImplTestWithParam<MatrixData> {};
+
+TEST_P(MatrixTest, Parse) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_FALSE(p->has_error());
+  EXPECT_TRUE(t.value->Is<ast::Matrix>());
+  auto* mat = t.value->As<ast::Matrix>();
+  EXPECT_EQ(mat->rows, params.rows);
+  EXPECT_EQ(mat->columns, params.columns);
+  EXPECT_EQ(t.value->source.range, params.range);
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    MatrixTest,
+    testing::Values(MatrixData{"mat2x2<f32>", 2, 2, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat2x3<f32>", 2, 3, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat2x4<f32>", 2, 4, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat3x2<f32>", 3, 2, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat3x3<f32>", 3, 3, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat3x4<f32>", 3, 4, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat4x2<f32>", 4, 2, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat4x3<f32>", 4, 3, {{1u, 1u}, {1u, 12u}}},
+                    MatrixData{"mat4x4<f32>", 4, 4, {{1u, 1u}, {1u, 12u}}}));
+
+class MatrixMissingGreaterThanTest
+    : public ParserImplTestWithParam<MatrixData> {};
+
+TEST_P(MatrixMissingGreaterThanTest, Handles_Missing_GreaterThan) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:11: expected '>' for matrix");
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         MatrixMissingGreaterThanTest,
+                         testing::Values(MatrixData{"mat2x2<f32", 2, 2, {}},
+                                         MatrixData{"mat2x3<f32", 2, 3, {}},
+                                         MatrixData{"mat2x4<f32", 2, 4, {}},
+                                         MatrixData{"mat3x2<f32", 3, 2, {}},
+                                         MatrixData{"mat3x3<f32", 3, 3, {}},
+                                         MatrixData{"mat3x4<f32", 3, 4, {}},
+                                         MatrixData{"mat4x2<f32", 4, 2, {}},
+                                         MatrixData{"mat4x3<f32", 4, 3, {}},
+                                         MatrixData{"mat4x4<f32", 4, 4, {}}));
+
+class MatrixMissingType : public ParserImplTestWithParam<MatrixData> {};
+
+TEST_P(MatrixMissingType, Handles_Missing_Type) {
+  auto params = GetParam();
+  auto p = parser(params.input);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
+  ASSERT_TRUE(p->has_error());
+  ASSERT_EQ(p->error(), "1:8: invalid type for matrix");
+}
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         MatrixMissingType,
+                         testing::Values(MatrixData{"mat2x2<>", 2, 2, {}},
+                                         MatrixData{"mat2x3<>", 2, 3, {}},
+                                         MatrixData{"mat2x4<>", 2, 4, {}},
+                                         MatrixData{"mat3x2<>", 3, 2, {}},
+                                         MatrixData{"mat3x3<>", 3, 3, {}},
+                                         MatrixData{"mat3x4<>", 3, 4, {}},
+                                         MatrixData{"mat4x2<>", 4, 2, {}},
+                                         MatrixData{"mat4x3<>", 4, 3, {}},
+                                         MatrixData{"mat4x4<>", 4, 4, {}}));
+
+TEST_F(ParserImplTest, TypeDecl_Sampler) {
+  auto p = parser("sampler");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  ASSERT_TRUE(t.value->Is<ast::Sampler>());
+  ASSERT_FALSE(t.value->As<ast::Sampler>()->IsComparison());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
+}
+
+TEST_F(ParserImplTest, TypeDecl_Texture) {
+  auto p = parser("texture_cube<f32>");
+
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->Is<ast::Texture>());
+  ASSERT_TRUE(t.value->Is<ast::SampledTexture>());
+  ASSERT_TRUE(t.value->As<ast::SampledTexture>()->type->Is<ast::F32>());
+  EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc
new file mode 100644
index 0000000..d95c093
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc
@@ -0,0 +1,192 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, UnaryExpression_Postix) {
+  auto p = parser("a[2]");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+
+  ASSERT_TRUE(e->Is<ast::IndexAccessorExpression>());
+  auto* idx = e->As<ast::IndexAccessorExpression>();
+  ASSERT_TRUE(idx->object->Is<ast::IdentifierExpression>());
+  auto* ident = idx->object->As<ast::IdentifierExpression>();
+  EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
+  ASSERT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Minus) {
+  auto p = parser("- 1");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  ASSERT_EQ(u->op, ast::UnaryOp::kNegation);
+
+  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+}
+
+TEST_F(ParserImplTest, UnaryExpression_AddressOf) {
+  auto p = parser("&x");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  EXPECT_EQ(u->op, ast::UnaryOp::kAddressOf);
+  EXPECT_TRUE(u->expr->Is<ast::IdentifierExpression>());
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Dereference) {
+  auto p = parser("*x");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  EXPECT_EQ(u->op, ast::UnaryOp::kIndirection);
+  EXPECT_TRUE(u->expr->Is<ast::IdentifierExpression>());
+}
+
+TEST_F(ParserImplTest, UnaryExpression_AddressOf_Precedence) {
+  auto p = parser("&x.y");
+  auto e = p->logical_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  EXPECT_EQ(u->op, ast::UnaryOp::kAddressOf);
+  EXPECT_TRUE(u->expr->Is<ast::MemberAccessorExpression>());
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Dereference_Precedence) {
+  auto p = parser("*x.y");
+  auto e = p->logical_or_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  EXPECT_EQ(u->op, ast::UnaryOp::kIndirection);
+  EXPECT_TRUE(u->expr->Is<ast::MemberAccessorExpression>());
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Minus_InvalidRHS) {
+  auto p = parser("-if(a) {}");
+  auto e = p->unary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:2: unable to parse right side of - expression");
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Bang) {
+  auto p = parser("!1");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  ASSERT_EQ(u->op, ast::UnaryOp::kNot);
+
+  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Bang_InvalidRHS) {
+  auto p = parser("!if (a) {}");
+  auto e = p->unary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:2: unable to parse right side of ! expression");
+}
+
+TEST_F(ParserImplTest, UnaryExpression_Tilde) {
+  auto p = parser("~1");
+  auto e = p->unary_expression();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
+
+  auto* u = e->As<ast::UnaryOpExpression>();
+  ASSERT_EQ(u->op, ast::UnaryOp::kComplement);
+
+  ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
+  EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+}
+
+TEST_F(ParserImplTest, UnaryExpression_PrefixPlusPlus) {
+  auto p = parser("++a");
+  auto e = p->unary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:1: prefix increment and decrement operators are reserved for a "
+            "future WGSL version");
+}
+
+TEST_F(ParserImplTest, UnaryExpression_PrefixMinusMinus) {
+  auto p = parser("--a");
+  auto e = p->unary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:1: prefix increment and decrement operators are reserved for a "
+            "future WGSL version");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
new file mode 100644
index 0000000..b751f87
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
@@ -0,0 +1,135 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, AttributeList_Parses) {
+  auto p = parser(R"(@location(4) @builtin(position))");
+  auto attrs = p->attribute_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(attrs.errored);
+  ASSERT_TRUE(attrs.matched);
+  ASSERT_EQ(attrs.value.size(), 2u);
+
+  auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
+  auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
+  ASSERT_NE(attr_0, nullptr);
+  ASSERT_NE(attr_1, nullptr);
+
+  ASSERT_TRUE(attr_0->Is<ast::LocationAttribute>());
+  EXPECT_EQ(attr_0->As<ast::LocationAttribute>()->value, 4u);
+  ASSERT_TRUE(attr_1->Is<ast::BuiltinAttribute>());
+  EXPECT_EQ(attr_1->As<ast::BuiltinAttribute>()->builtin,
+            ast::Builtin::kPosition);
+}
+
+TEST_F(ParserImplTest, AttributeList_Invalid) {
+  auto p = parser(R"(@invalid)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(), R"(1:2: expected attribute)");
+}
+
+TEST_F(ParserImplTest, AttributeList_InvalidValue) {
+  auto p = parser("@builtin(invalid)");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(p->error(), "1:10: invalid value for builtin attribute");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_Empty) {
+  auto p = parser(R"([[]])");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: empty attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_Invalid) {
+  auto p = parser(R"([[invalid]])");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:3: expected attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_ExtraComma) {
+  auto p = parser(R"([[builtin(position), ]])");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:22: expected attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_MissingComma) {
+  auto p = parser(R"([[binding(4) location(5)]])");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:14: expected ',' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_AttributeList_InvalidValue) {
+  auto p = parser("[[builtin(invalid)]]");
+  auto attrs = p->attribute_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(attrs.errored);
+  EXPECT_FALSE(attrs.matched);
+  EXPECT_TRUE(attrs.value.empty());
+  EXPECT_EQ(
+      p->error(),
+      R"(1:1: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:11: invalid value for builtin attribute)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
new file mode 100644
index 0000000..88c6e4b
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -0,0 +1,417 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, Attribute_Location) {
+  auto p = parser("location(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::LocationAttribute>());
+
+  auto* loc = var_attr->As<ast::LocationAttribute>();
+  EXPECT_EQ(loc->value, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Location_MissingLeftParen) {
+  auto p = parser("location 4)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: expected '(' for location attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Location_MissingRightParen) {
+  auto p = parser("location(4");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:11: expected ')' for location attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Location_MissingValue) {
+  auto p = parser("location()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:10: expected signed integer literal for location attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Location_MissingInvalid) {
+  auto p = parser("location(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:10: expected signed integer literal for location attribute");
+}
+
+struct BuiltinData {
+  const char* input;
+  ast::Builtin result;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class BuiltinTest : public ParserImplTestWithParam<BuiltinData> {};
+
+TEST_P(BuiltinTest, Attribute_Builtin) {
+  auto params = GetParam();
+  auto p = parser(std::string("builtin(") + params.input + ")");
+
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_TRUE(var_attr->Is<ast::BuiltinAttribute>());
+
+  auto* builtin = var_attr->As<ast::BuiltinAttribute>();
+  EXPECT_EQ(builtin->builtin, params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    BuiltinTest,
+    testing::Values(
+        BuiltinData{"position", ast::Builtin::kPosition},
+        BuiltinData{"vertex_index", ast::Builtin::kVertexIndex},
+        BuiltinData{"instance_index", ast::Builtin::kInstanceIndex},
+        BuiltinData{"front_facing", ast::Builtin::kFrontFacing},
+        BuiltinData{"frag_depth", ast::Builtin::kFragDepth},
+        BuiltinData{"local_invocation_id", ast::Builtin::kLocalInvocationId},
+        BuiltinData{"local_invocation_idx",
+                    ast::Builtin::kLocalInvocationIndex},
+        BuiltinData{"local_invocation_index",
+                    ast::Builtin::kLocalInvocationIndex},
+        BuiltinData{"global_invocation_id", ast::Builtin::kGlobalInvocationId},
+        BuiltinData{"workgroup_id", ast::Builtin::kWorkgroupId},
+        BuiltinData{"num_workgroups", ast::Builtin::kNumWorkgroups},
+        BuiltinData{"sample_index", ast::Builtin::kSampleIndex},
+        BuiltinData{"sample_mask", ast::Builtin::kSampleMask}));
+
+TEST_F(ParserImplTest, Attribute_Builtin_MissingLeftParen) {
+  auto p = parser("builtin position)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: expected '(' for builtin attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Builtin_MissingRightParen) {
+  auto p = parser("builtin(position");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:17: expected ')' for builtin attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Builtin_MissingValue) {
+  auto p = parser("builtin()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
+}
+
+TEST_F(ParserImplTest, Attribute_Builtin_InvalidValue) {
+  auto p = parser("builtin(other_thingy)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: invalid value for builtin attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Builtin_MissingInvalid) {
+  auto p = parser("builtin(3)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_Flat) {
+  auto p = parser("interpolate(flat)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+  auto* interp = var_attr->As<ast::InterpolateAttribute>();
+  EXPECT_EQ(interp->type, ast::InterpolationType::kFlat);
+  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kNone);
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Center) {
+  auto p = parser("interpolate(perspective, center)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+  auto* interp = var_attr->As<ast::InterpolateAttribute>();
+  EXPECT_EQ(interp->type, ast::InterpolationType::kPerspective);
+  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kCenter);
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Centroid) {
+  auto p = parser("interpolate(perspective, centroid)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+  auto* interp = var_attr->As<ast::InterpolateAttribute>();
+  EXPECT_EQ(interp->type, ast::InterpolationType::kPerspective);
+  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kCentroid);
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_Linear_Sample) {
+  auto p = parser("interpolate(linear, sample)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+  auto* interp = var_attr->As<ast::InterpolateAttribute>();
+  EXPECT_EQ(interp->type, ast::InterpolationType::kLinear);
+  EXPECT_EQ(interp->sampling, ast::InterpolationSampling::kSample);
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_MissingLeftParen) {
+  auto p = parser("interpolate flat)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: expected '(' for interpolate attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_MissingRightParen) {
+  auto p = parser("interpolate(flat");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:17: expected ')' for interpolate attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_MissingFirstValue) {
+  auto p = parser("interpolate()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_InvalidFirstValue) {
+  auto p = parser("interpolate(other_thingy)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_MissingSecondValue) {
+  auto p = parser("interpolate(perspective,)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:25: invalid interpolation sampling");
+}
+
+TEST_F(ParserImplTest, Attribute_Interpolate_InvalidSecondValue) {
+  auto p = parser("interpolate(perspective, nope)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:26: invalid interpolation sampling");
+}
+
+TEST_F(ParserImplTest, Attribute_Binding) {
+  auto p = parser("binding(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_attr->Is<ast::BindingAttribute>());
+
+  auto* binding = var_attr->As<ast::BindingAttribute>();
+  EXPECT_EQ(binding->value, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Binding_MissingLeftParen) {
+  auto p = parser("binding 4)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: expected '(' for binding attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Binding_MissingRightParen) {
+  auto p = parser("binding(4");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: expected ')' for binding attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Binding_MissingValue) {
+  auto p = parser("binding()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:9: expected signed integer literal for binding attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Binding_MissingInvalid) {
+  auto p = parser("binding(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:9: expected signed integer literal for binding attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_group) {
+  auto p = parser("group(4)");
+  auto attr = p->attribute();
+  EXPECT_TRUE(attr.matched);
+  EXPECT_FALSE(attr.errored);
+  ASSERT_NE(attr.value, nullptr);
+  auto* var_attr = attr.value->As<ast::Attribute>();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_NE(var_attr, nullptr);
+  ASSERT_TRUE(var_attr->Is<ast::GroupAttribute>());
+
+  auto* group = var_attr->As<ast::GroupAttribute>();
+  EXPECT_EQ(group->value, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Group_MissingLeftParen) {
+  auto p = parser("group 2)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:7: expected '(' for group attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Group_MissingRightParen) {
+  auto p = parser("group(2");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:8: expected ')' for group attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Group_MissingValue) {
+  auto p = parser("group()");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for group attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Group_MissingInvalid) {
+  auto p = parser("group(nan)");
+  auto attr = p->attribute();
+  EXPECT_FALSE(attr.matched);
+  EXPECT_TRUE(attr.errored);
+  EXPECT_EQ(attr.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:7: expected signed integer literal for group attribute");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
new file mode 100644
index 0000000..96ae806
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
@@ -0,0 +1,114 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+TEST_F(ParserImplTest, VariableDecl_Parses) {
+  auto p = parser("var my_var : f32");
+  auto v = p->variable_decl();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_EQ(v->name, "my_var");
+  EXPECT_NE(v->type, nullptr);
+  EXPECT_TRUE(v->type->Is<ast::F32>());
+
+  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
+  EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
+}
+
+TEST_F(ParserImplTest, VariableDecl_Unicode_Parses) {
+  const std::string ident =  // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
+      "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
+      "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
+      "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33";
+
+  auto p = parser("var " + ident + " : f32");
+  auto v = p->variable_decl();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_EQ(v->name, ident);
+  EXPECT_NE(v->type, nullptr);
+  EXPECT_TRUE(v->type->Is<ast::F32>());
+
+  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 48u}}));
+  EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 51u}, {1u, 54u}}));
+}
+
+TEST_F(ParserImplTest, VariableDecl_Inferred_Parses) {
+  auto p = parser("var my_var = 1.0");
+  auto v = p->variable_decl(/*allow_inferred = */ true);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_EQ(v->name, "my_var");
+  EXPECT_EQ(v->type, nullptr);
+
+  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
+}
+
+TEST_F(ParserImplTest, VariableDecl_MissingVar) {
+  auto p = parser("my_var : f32");
+  auto v = p->variable_decl();
+  EXPECT_FALSE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_FALSE(p->has_error());
+
+  auto t = p->next();
+  ASSERT_TRUE(t.IsIdentifier());
+}
+
+TEST_F(ParserImplTest, VariableDecl_InvalidIdentDecl) {
+  auto p = parser("var my_var f32");
+  auto v = p->variable_decl();
+  EXPECT_FALSE(v.matched);
+  EXPECT_TRUE(v.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:12: expected ':' for variable declaration");
+}
+
+TEST_F(ParserImplTest, VariableDecl_WithStorageClass) {
+  auto p = parser("var<private> my_var : f32");
+  auto v = p->variable_decl();
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_EQ(v->name, "my_var");
+  EXPECT_TRUE(v->type->Is<ast::F32>());
+  EXPECT_EQ(v->storage_class, ast::StorageClass::kPrivate);
+
+  EXPECT_EQ(v->source.range.begin.line, 1u);
+  EXPECT_EQ(v->source.range.begin.column, 14u);
+  EXPECT_EQ(v->source.range.end.line, 1u);
+  EXPECT_EQ(v->source.range.end.column, 20u);
+}
+
+TEST_F(ParserImplTest, VariableDecl_InvalidStorageClass) {
+  auto p = parser("var<unknown> my_var : f32");
+  auto v = p->variable_decl();
+  EXPECT_FALSE(v.matched);
+  EXPECT_TRUE(v.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:5: invalid storage class for variable declaration");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
new file mode 100644
index 0000000..e3f193b
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -0,0 +1,165 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, VariableIdentDecl_Parses) {
+  auto p = parser("my_var : f32");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decl.errored);
+  ASSERT_EQ(decl->name, "my_var");
+  ASSERT_NE(decl->type, nullptr);
+  ASSERT_TRUE(decl->type->Is<ast::F32>());
+
+  EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
+  EXPECT_EQ(decl->type->source.range, (Source::Range{{1u, 10u}, {1u, 13u}}));
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_Inferred_Parses) {
+  auto p = parser("my_var = 1.0");
+  auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decl.errored);
+  ASSERT_EQ(decl->name, "my_var");
+  ASSERT_EQ(decl->type, nullptr);
+
+  EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_MissingIdent) {
+  auto p = parser(": f32");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:1: expected identifier for test");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_MissingColon) {
+  auto p = parser("my_var f32");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:8: expected ':' for test");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_MissingType) {
+  auto p = parser("my_var :");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:9: invalid type for test");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_InvalidIdent) {
+  auto p = parser("123 : f32");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:1: expected identifier for test");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_NonAccessAttrFail) {
+  auto p = parser("my_var : @location(1) S");
+
+  auto* mem = Member("a", ty.i32(), ast::AttributeList{});
+  ast::StructMemberList members;
+  members.push_back(mem);
+
+  auto* block_attr = create<ast::StructBlockAttribute>();
+  ast::AttributeList attrs;
+  attrs.push_back(block_attr);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:11: unexpected attributes");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_AttributeMissingRightParen) {
+  auto p = parser("my_var : @location(4 S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:22: expected ')' for location attribute");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_AttributeMissingLeftParen) {
+  auto p = parser("my_var : @stride 4) S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:18: expected '(' for stride attribute");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest,
+       DEPRECATED_VariableIdentDecl_AttributeMissingRightBlock) {
+  auto p = parser("my_var : [[location(4) S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(
+      p->error(),
+      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:24: expected ']]' for attribute list)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest,
+       DEPRECATED_VariableIdentDecl_AttributeMissingRightParen) {
+  auto p = parser("my_var : [[location(4]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(
+      p->error(),
+      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:22: expected ')' for location attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_VariableIdentDecl_AttributeMissingLeftParen) {
+  auto p = parser("my_var : [[stride 4)]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(
+      p->error(),
+      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:19: expected '(' for stride attribute)");
+}
+
+// TODO(crbug.com/tint/1382): Remove
+TEST_F(ParserImplTest, DEPRECATED_VariableIdentDecl_AttributeEmpty) {
+  auto p = parser("my_var : [[]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(
+      p->error(),
+      R"(1:10: use of deprecated language feature: [[attribute]] style attributes have been replaced with @attribute style
+1:12: empty attribute list)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc b/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc
new file mode 100644
index 0000000..621c21e
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+struct VariableStorageData {
+  const char* input;
+  ast::StorageClass storage_class;
+  ast::Access access;
+};
+inline std::ostream& operator<<(std::ostream& out, VariableStorageData data) {
+  out << std::string(data.input);
+  return out;
+}
+
+class VariableQualifierTest
+    : public ParserImplTestWithParam<VariableStorageData> {};
+
+TEST_P(VariableQualifierTest, ParsesStorageClass) {
+  auto params = GetParam();
+  auto p = parser(std::string("<") + params.input + ">");
+
+  auto sc = p->variable_qualifier();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(sc.errored);
+  EXPECT_TRUE(sc.matched);
+  EXPECT_EQ(sc->storage_class, params.storage_class);
+  EXPECT_EQ(sc->access, params.access);
+
+  auto t = p->next();
+  EXPECT_TRUE(t.IsEof());
+}
+INSTANTIATE_TEST_SUITE_P(
+    ParserImplTest,
+    VariableQualifierTest,
+    testing::Values(
+        VariableStorageData{"uniform", ast::StorageClass::kUniform,
+                            ast::Access::kUndefined},
+        VariableStorageData{"workgroup", ast::StorageClass::kWorkgroup,
+                            ast::Access::kUndefined},
+        VariableStorageData{"storage", ast::StorageClass::kStorage,
+                            ast::Access::kUndefined},
+        VariableStorageData{"storage_buffer", ast::StorageClass::kStorage,
+                            ast::Access::kUndefined},
+        VariableStorageData{"private", ast::StorageClass::kPrivate,
+                            ast::Access::kUndefined},
+        VariableStorageData{"function", ast::StorageClass::kFunction,
+                            ast::Access::kUndefined},
+        VariableStorageData{"storage, read", ast::StorageClass::kStorage,
+                            ast::Access::kRead},
+        VariableStorageData{"storage, write", ast::StorageClass::kStorage,
+                            ast::Access::kWrite},
+        VariableStorageData{"storage, read_write", ast::StorageClass::kStorage,
+                            ast::Access::kReadWrite}));
+
+TEST_F(ParserImplTest, VariableQualifier_NoMatch) {
+  auto p = parser("<not-a-storage-class>");
+  auto sc = p->variable_qualifier();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable declaration");
+}
+
+TEST_F(ParserImplTest, VariableQualifier_Empty) {
+  auto p = parser("<>");
+  auto sc = p->variable_qualifier();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable declaration");
+}
+
+TEST_F(ParserImplTest, VariableQualifier_MissingLessThan) {
+  auto p = parser("private>");
+  auto sc = p->variable_qualifier();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+
+  auto t = p->next();
+  ASSERT_TRUE(t.Is(Token::Type::kPrivate));
+}
+
+TEST_F(ParserImplTest, VariableQualifier_MissingLessThan_AfterSC) {
+  auto p = parser("private, >");
+  auto sc = p->variable_qualifier();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+
+  auto t = p->next();
+  ASSERT_TRUE(t.Is(Token::Type::kPrivate));
+}
+
+TEST_F(ParserImplTest, VariableQualifier_MissingGreaterThan) {
+  auto p = parser("<private");
+  auto sc = p->variable_qualifier();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:9: expected '>' for variable declaration");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
new file mode 100644
index 0000000..b61de3e
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -0,0 +1,191 @@
+// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl) {
+  auto p = parser("var a : i32;");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_EQ(e->source.range.begin.line, 1u);
+  ASSERT_EQ(e->source.range.begin.column, 5u);
+  ASSERT_EQ(e->source.range.end.line, 1u);
+  ASSERT_EQ(e->source.range.end.column, 6u);
+
+  EXPECT_EQ(e->variable->constructor, nullptr);
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_WithInit) {
+  auto p = parser("var a : i32 = 1;");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_EQ(e->source.range.begin.line, 1u);
+  ASSERT_EQ(e->source.range.begin.column, 5u);
+  ASSERT_EQ(e->source.range.end.line, 1u);
+  ASSERT_EQ(e->source.range.end.column, 6u);
+
+  ASSERT_NE(e->variable->constructor, nullptr);
+  EXPECT_TRUE(e->variable->constructor->Is<ast::LiteralExpression>());
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
+  auto p = parser("var a : i32 = if(a) {}");
+  auto e = p->variable_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: missing constructor for variable declaration");
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) {
+  auto p = parser("var a : array<i32> = array<i32>();");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_NE(e->variable->constructor, nullptr);
+  auto* call = e->variable->constructor->As<ast::CallExpression>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->target.name, nullptr);
+  EXPECT_NE(call->target.type, nullptr);
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit_NoSpace) {
+  auto p = parser("var a : array<i32>=array<i32>();");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_NE(e->variable->constructor, nullptr);
+  auto* call = e->variable->constructor->As<ast::CallExpression>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->target.name, nullptr);
+  EXPECT_NE(call->target.type, nullptr);
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit) {
+  auto p = parser("var a : vec2<i32> = vec2<i32>();");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_NE(e->variable->constructor, nullptr);
+  auto* call = e->variable->constructor->As<ast::CallExpression>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->target.name, nullptr);
+  EXPECT_NE(call->target.type, nullptr);
+}
+
+TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit_NoSpace) {
+  auto p = parser("var a : vec2<i32>=vec2<i32>();");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+  ASSERT_NE(e->variable, nullptr);
+  EXPECT_EQ(e->variable->symbol, p->builder().Symbols().Get("a"));
+
+  ASSERT_NE(e->variable->constructor, nullptr);
+  auto* call = e->variable->constructor->As<ast::CallExpression>();
+  ASSERT_NE(call, nullptr);
+  EXPECT_EQ(call->target.name, nullptr);
+  EXPECT_NE(call->target.type, nullptr);
+}
+
+TEST_F(ParserImplTest, VariableStmt_Let) {
+  auto p = parser("let a : i32 = 1");
+  auto e = p->variable_stmt();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
+
+  ASSERT_EQ(e->source.range.begin.line, 1u);
+  ASSERT_EQ(e->source.range.begin.column, 5u);
+  ASSERT_EQ(e->source.range.end.line, 1u);
+  ASSERT_EQ(e->source.range.end.column, 6u);
+}
+
+TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
+  auto p = parser("let a : i32 1");
+  auto e = p->variable_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: expected '=' for let declaration");
+}
+
+TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) {
+  auto p = parser("let a : i32 =");
+  auto e = p->variable_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:14: missing constructor for let declaration");
+}
+
+TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
+  auto p = parser("let a : i32 = if (a) {}");
+  auto e = p->variable_stmt();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: missing constructor for let declaration");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/parser_test.cc b/src/tint/reader/wgsl/parser_test.cc
new file mode 100644
index 0000000..efafde0
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_test.cc
@@ -0,0 +1,69 @@
+// Copyright 2020 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/reader/wgsl/parser.h"
+
+#include "gtest/gtest.h"
+
+#include "src/tint/ast/module.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+using ParserTest = testing::Test;
+
+TEST_F(ParserTest, Empty) {
+  Source::File file("test.wgsl", "");
+  auto program = Parse(&file);
+  auto errs = diag::Formatter().format(program.Diagnostics());
+  ASSERT_TRUE(program.IsValid()) << errs;
+}
+
+TEST_F(ParserTest, Parses) {
+  Source::File file("test.wgsl", R"(
+@stage(fragment)
+fn main() -> @location(0) vec4<f32> {
+  return vec4<f32>(.4, .2, .3, 1.);
+}
+)");
+  auto program = Parse(&file);
+  auto errs = diag::Formatter().format(program.Diagnostics());
+  ASSERT_TRUE(program.IsValid()) << errs;
+
+  ASSERT_EQ(1u, program.AST().Functions().size());
+}
+
+TEST_F(ParserTest, HandlesError) {
+  Source::File file("test.wgsl", R"(
+fn main() ->  {  // missing return type
+  return;
+})");
+
+  auto program = Parse(&file);
+  auto errs = diag::Formatter().format(program.Diagnostics());
+  ASSERT_FALSE(program.IsValid()) << errs;
+  EXPECT_EQ(errs,
+            R"(test.wgsl:2:15 error: unable to determine function return type
+fn main() ->  {  // missing return type
+              ^
+
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc
new file mode 100644
index 0000000..abc0dc1
--- /dev/null
+++ b/src/tint/reader/wgsl/token.cc
@@ -0,0 +1,328 @@
+// Copyright 2020 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/reader/wgsl/token.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+// static
+std::string_view Token::TypeToName(Type type) {
+  switch (type) {
+    case Token::Type::kError:
+      return "kError";
+    case Token::Type::kEOF:
+      return "kEOF";
+    case Token::Type::kIdentifier:
+      return "kIdentifier";
+    case Token::Type::kFloatLiteral:
+      return "kFloatLiteral";
+    case Token::Type::kSintLiteral:
+      return "kSintLiteral";
+    case Token::Type::kUintLiteral:
+      return "kUintLiteral";
+    case Token::Type::kUninitialized:
+      return "kUninitialized";
+
+    case Token::Type::kAnd:
+      return "&";
+    case Token::Type::kAndAnd:
+      return "&&";
+    case Token::Type::kArrow:
+      return "->";
+    case Token::Type::kAttr:
+      return "@";
+    case Token::Type::kAttrLeft:
+      return "[[";
+    case Token::Type::kAttrRight:
+      return "]]";
+    case Token::Type::kForwardSlash:
+      return "/";
+    case Token::Type::kBang:
+      return "!";
+    case Token::Type::kBracketLeft:
+      return "[";
+    case Token::Type::kBracketRight:
+      return "]";
+    case Token::Type::kBraceLeft:
+      return "{";
+    case Token::Type::kBraceRight:
+      return "}";
+    case Token::Type::kColon:
+      return ":";
+    case Token::Type::kComma:
+      return ",";
+    case Token::Type::kEqual:
+      return "=";
+    case Token::Type::kEqualEqual:
+      return "==";
+    case Token::Type::kGreaterThan:
+      return ">";
+    case Token::Type::kGreaterThanEqual:
+      return ">=";
+    case Token::Type::kShiftRight:
+      return ">>";
+    case Token::Type::kLessThan:
+      return "<";
+    case Token::Type::kLessThanEqual:
+      return "<=";
+    case Token::Type::kShiftLeft:
+      return "<<";
+    case Token::Type::kMod:
+      return "%";
+    case Token::Type::kNotEqual:
+      return "!=";
+    case Token::Type::kMinus:
+      return "-";
+    case Token::Type::kMinusMinus:
+      return "--";
+    case Token::Type::kPeriod:
+      return ".";
+    case Token::Type::kPlus:
+      return "+";
+    case Token::Type::kPlusPlus:
+      return "++";
+    case Token::Type::kOr:
+      return "|";
+    case Token::Type::kOrOr:
+      return "||";
+    case Token::Type::kParenLeft:
+      return "(";
+    case Token::Type::kParenRight:
+      return ")";
+    case Token::Type::kSemicolon:
+      return ";";
+    case Token::Type::kStar:
+      return "*";
+    case Token::Type::kTilde:
+      return "~";
+    case Token::Type::kUnderscore:
+      return "_";
+    case Token::Type::kXor:
+      return "^";
+
+    case Token::Type::kArray:
+      return "array";
+    case Token::Type::kAtomic:
+      return "atomic";
+    case Token::Type::kBitcast:
+      return "bitcast";
+    case Token::Type::kBool:
+      return "bool";
+    case Token::Type::kBreak:
+      return "break";
+    case Token::Type::kCase:
+      return "case";
+    case Token::Type::kContinue:
+      return "continue";
+    case Token::Type::kContinuing:
+      return "continuing";
+    case Token::Type::kDiscard:
+      return "discard";
+    case Token::Type::kDefault:
+      return "default";
+    case Token::Type::kElse:
+      return "else";
+    case Token::Type::kElseIf:
+      return "elseif";
+    case Token::Type::kF32:
+      return "f32";
+    case Token::Type::kFallthrough:
+      return "fallthrough";
+    case Token::Type::kFalse:
+      return "false";
+    case Token::Type::kFn:
+      return "fn";
+    case Token::Type::kFor:
+      return "for";
+    case Token::Type::kFunction:
+      return "function";
+    case Token::Type::kI32:
+      return "i32";
+    case Token::Type::kIf:
+      return "if";
+    case Token::Type::kImport:
+      return "import";
+    case Token::Type::kLet:
+      return "let";
+    case Token::Type::kLoop:
+      return "loop";
+    case Token::Type::kMat2x2:
+      return "mat2x2";
+    case Token::Type::kMat2x3:
+      return "mat2x3";
+    case Token::Type::kMat2x4:
+      return "mat2x4";
+    case Token::Type::kMat3x2:
+      return "mat3x2";
+    case Token::Type::kMat3x3:
+      return "mat3x3";
+    case Token::Type::kMat3x4:
+      return "mat3x4";
+    case Token::Type::kMat4x2:
+      return "mat4x2";
+    case Token::Type::kMat4x3:
+      return "mat4x3";
+    case Token::Type::kMat4x4:
+      return "mat4x4";
+    case Token::Type::kOverride:
+      return "override";
+    case Token::Type::kPrivate:
+      return "private";
+    case Token::Type::kPtr:
+      return "ptr";
+    case Token::Type::kReturn:
+      return "return";
+    case Token::Type::kSampler:
+      return "sampler";
+    case Token::Type::kComparisonSampler:
+      return "sampler_comparison";
+    case Token::Type::kStorage:
+      return "storage";
+    case Token::Type::kStruct:
+      return "struct";
+    case Token::Type::kSwitch:
+      return "switch";
+    case Token::Type::kTextureDepth2d:
+      return "texture_depth_2d";
+    case Token::Type::kTextureDepth2dArray:
+      return "texture_depth_2d_array";
+    case Token::Type::kTextureDepthCube:
+      return "texture_depth_cube";
+    case Token::Type::kTextureDepthCubeArray:
+      return "texture_depth_cube_array";
+    case Token::Type::kTextureDepthMultisampled2d:
+      return "texture_depth_multisampled_2d";
+    case Token::Type::kTextureExternal:
+      return "texture_external";
+    case Token::Type::kTextureMultisampled2d:
+      return "texture_multisampled_2d";
+    case Token::Type::kTextureSampled1d:
+      return "texture_1d";
+    case Token::Type::kTextureSampled2d:
+      return "texture_2d";
+    case Token::Type::kTextureSampled2dArray:
+      return "texture_2d_array";
+    case Token::Type::kTextureSampled3d:
+      return "texture_3d";
+    case Token::Type::kTextureSampledCube:
+      return "texture_cube";
+    case Token::Type::kTextureSampledCubeArray:
+      return "texture_cube_array";
+    case Token::Type::kTextureStorage1d:
+      return "texture_storage_1d";
+    case Token::Type::kTextureStorage2d:
+      return "texture_storage_2d";
+    case Token::Type::kTextureStorage2dArray:
+      return "texture_storage_2d_array";
+    case Token::Type::kTextureStorage3d:
+      return "texture_storage_3d";
+    case Token::Type::kTrue:
+      return "true";
+    case Token::Type::kType:
+      return "type";
+    case Token::Type::kU32:
+      return "u32";
+    case Token::Type::kUniform:
+      return "uniform";
+    case Token::Type::kVar:
+      return "var";
+    case Token::Type::kVec2:
+      return "vec2";
+    case Token::Type::kVec3:
+      return "vec3";
+    case Token::Type::kVec4:
+      return "vec4";
+    case Token::Type::kWorkgroup:
+      return "workgroup";
+  }
+
+  return "<unknown>";
+}
+
+Token::Token() : type_(Type::kUninitialized) {}
+
+Token::Token(Type type, const Source& source, const std::string_view& view)
+    : type_(type), source_(source), value_(view) {}
+
+Token::Token(Type type, const Source& source, const std::string& str)
+    : type_(type), source_(source), value_(str) {}
+
+Token::Token(Type type, const Source& source, const char* str)
+    : type_(type), source_(source), value_(std::string_view(str)) {}
+
+Token::Token(const Source& source, uint32_t val)
+    : type_(Type::kUintLiteral), source_(source), value_(val) {}
+
+Token::Token(const Source& source, int32_t val)
+    : type_(Type::kSintLiteral), source_(source), value_(val) {}
+
+Token::Token(const Source& source, float val)
+    : type_(Type::kFloatLiteral), source_(source), value_(val) {}
+
+Token::Token(Type type, const Source& source) : type_(type), source_(source) {}
+
+Token::Token(Token&&) = default;
+
+Token::Token(const Token&) = default;
+
+Token::~Token() = default;
+
+Token& Token::operator=(const Token& rhs) = default;
+
+bool Token::operator==(std::string_view ident) {
+  if (type_ != Type::kIdentifier) {
+    return false;
+  }
+  if (auto* view = std::get_if<std::string_view>(&value_)) {
+    return *view == ident;
+  }
+  return std::get<std::string>(value_) == ident;
+}
+
+std::string Token::to_str() const {
+  switch (type_) {
+    case Type::kFloatLiteral:
+      return std::to_string(std::get<float>(value_));
+    case Type::kSintLiteral:
+      return std::to_string(std::get<int32_t>(value_));
+    case Type::kUintLiteral:
+      return std::to_string(std::get<uint32_t>(value_));
+    case Type::kIdentifier:
+    case Type::kError:
+      if (auto* view = std::get_if<std::string_view>(&value_)) {
+        return std::string(*view);
+      }
+      return std::get<std::string>(value_);
+    default:
+      return "";
+  }
+}
+
+float Token::to_f32() const {
+  return std::get<float>(value_);
+}
+
+uint32_t Token::to_u32() const {
+  return std::get<uint32_t>(value_);
+}
+
+int32_t Token::to_i32() const {
+  return std::get<int32_t>(value_);
+}
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
new file mode 100644
index 0000000..58bb92a
--- /dev/null
+++ b/src/tint/reader/wgsl/token.h
@@ -0,0 +1,412 @@
+// Copyright 2020 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_READER_WGSL_TOKEN_H_
+#define SRC_TINT_READER_WGSL_TOKEN_H_
+
+#include <string>
+#include <string_view>
+#include <variant>  // NOLINT: cpplint doesn't recognise this
+
+#include "src/tint/source.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+
+/// Stores tokens generated by the Lexer
+class Token {
+ public:
+  /// The type of the parsed token
+  enum class Type {
+    /// Error result
+    kError = -2,
+    /// Uninitialized token
+    kUninitialized = 0,
+    /// End of input string reached
+    kEOF,
+
+    /// An identifier
+    kIdentifier,
+    /// A float value
+    kFloatLiteral,
+    /// An signed int value
+    kSintLiteral,
+    /// A unsigned int value
+    kUintLiteral,
+
+    /// A '&'
+    kAnd,
+    /// A '&&'
+    kAndAnd,
+    /// A '->'
+    kArrow,
+    /// A '@'
+    kAttr,
+    /// A '[[' - [DEPRECATED] now '@'
+    kAttrLeft,
+    /// A ']]' - [DEPRECATED] now '@'
+    kAttrRight,
+    /// A '/'
+    kForwardSlash,
+    /// A '!'
+    kBang,
+    /// A '['
+    kBracketLeft,
+    /// A ']'
+    kBracketRight,
+    /// A '{'
+    kBraceLeft,
+    /// A '}'
+    kBraceRight,
+    /// A ':'
+    kColon,
+    /// A ','
+    kComma,
+    /// A '='
+    kEqual,
+    /// A '=='
+    kEqualEqual,
+    /// A '>'
+    kGreaterThan,
+    /// A '>='
+    kGreaterThanEqual,
+    /// A '>>'
+    kShiftRight,
+    /// A '<'
+    kLessThan,
+    /// A '<='
+    kLessThanEqual,
+    /// A '<<'
+    kShiftLeft,
+    /// A '%'
+    kMod,
+    /// A '-'
+    kMinus,
+    /// A '--'
+    kMinusMinus,
+    /// A '!='
+    kNotEqual,
+    /// A '.'
+    kPeriod,
+    /// A '+'
+    kPlus,
+    /// A '++'
+    kPlusPlus,
+    /// A '|'
+    kOr,
+    /// A '||'
+    kOrOr,
+    /// A '('
+    kParenLeft,
+    /// A ')'
+    kParenRight,
+    /// A ';'
+    kSemicolon,
+    /// A '*'
+    kStar,
+    /// A '~'
+    kTilde,
+    /// A '_'
+    kUnderscore,
+    /// A '^'
+    kXor,
+
+    /// A 'array'
+    kArray,
+    /// A 'atomic'
+    kAtomic,
+    /// A 'bitcast'
+    kBitcast,
+    /// A 'bool'
+    kBool,
+    /// A 'break'
+    kBreak,
+    /// A 'case'
+    kCase,
+    /// A 'continue'
+    kContinue,
+    /// A 'continuing'
+    kContinuing,
+    /// A 'discard'
+    kDiscard,
+    /// A 'default'
+    kDefault,
+    /// A 'else'
+    kElse,
+    /// A 'elseif'
+    kElseIf,
+    /// A 'f32'
+    kF32,
+    /// A 'fallthrough'
+    kFallthrough,
+    /// A 'false'
+    kFalse,
+    /// A 'fn'
+    kFn,
+    // A 'for'
+    kFor,
+    /// A 'function'
+    kFunction,
+    /// A 'i32'
+    kI32,
+    /// A 'if'
+    kIf,
+    /// A 'import'
+    kImport,
+    /// A 'let'
+    kLet,
+    /// A 'loop'
+    kLoop,
+    /// A 'mat2x2'
+    kMat2x2,
+    /// A 'mat2x3'
+    kMat2x3,
+    /// A 'mat2x4'
+    kMat2x4,
+    /// A 'mat3x2'
+    kMat3x2,
+    /// A 'mat3x3'
+    kMat3x3,
+    /// A 'mat3x4'
+    kMat3x4,
+    /// A 'mat4x2'
+    kMat4x2,
+    /// A 'mat4x3'
+    kMat4x3,
+    /// A 'mat4x4'
+    kMat4x4,
+    /// A 'override'
+    kOverride,
+    /// A 'private'
+    kPrivate,
+    /// A 'ptr'
+    kPtr,
+    /// A 'return'
+    kReturn,
+    /// A 'sampler'
+    kSampler,
+    /// A 'sampler_comparison'
+    kComparisonSampler,
+    /// A 'storage'
+    kStorage,
+    /// A 'struct'
+    kStruct,
+    /// A 'switch'
+    kSwitch,
+    /// A 'texture_depth_2d'
+    kTextureDepth2d,
+    /// A 'texture_depth_2d_array'
+    kTextureDepth2dArray,
+    /// A 'texture_depth_cube'
+    kTextureDepthCube,
+    /// A 'texture_depth_cube_array'
+    kTextureDepthCubeArray,
+    /// A 'texture_depth_multisampled_2d'
+    kTextureDepthMultisampled2d,
+    /// A 'texture_external'
+    kTextureExternal,
+    /// A 'texture_multisampled_2d'
+    kTextureMultisampled2d,
+    /// A 'texture_1d'
+    kTextureSampled1d,
+    /// A 'texture_2d'
+    kTextureSampled2d,
+    /// A 'texture_2d_array'
+    kTextureSampled2dArray,
+    /// A 'texture_3d'
+    kTextureSampled3d,
+    /// A 'texture_cube'
+    kTextureSampledCube,
+    /// A 'texture_cube_array'
+    kTextureSampledCubeArray,
+    /// A 'texture_storage_1d'
+    kTextureStorage1d,
+    /// A 'texture_storage_2d'
+    kTextureStorage2d,
+    /// A 'texture_storage_2d_array'
+    kTextureStorage2dArray,
+    /// A 'texture_storage_3d'
+    kTextureStorage3d,
+    /// A 'true'
+    kTrue,
+    /// A 'type'
+    kType,
+    /// A 'u32'
+    kU32,
+    /// A 'uniform'
+    kUniform,
+    /// A 'var'
+    kVar,
+    /// A 'vec2'
+    kVec2,
+    /// A 'vec3'
+    kVec3,
+    /// A 'vec4'
+    kVec4,
+    /// A 'workgroup'
+    kWorkgroup,
+  };
+
+  /// Converts a token type to a name
+  /// @param type the type to convert
+  /// @returns the token type as as string
+  static std::string_view TypeToName(Type type);
+
+  /// Creates an uninitialized token
+  Token();
+  /// Create a Token
+  /// @param type the Token::Type of the token
+  /// @param source the source of the token
+  Token(Type type, const Source& source);
+
+  /// Create a string Token
+  /// @param type the Token::Type of the token
+  /// @param source the source of the token
+  /// @param view the source string view for the token
+  Token(Type type, const Source& source, const std::string_view& view);
+  /// Create a string Token
+  /// @param type the Token::Type of the token
+  /// @param source the source of the token
+  /// @param str the source string for the token
+  Token(Type type, const Source& source, const std::string& str);
+  /// Create a string Token
+  /// @param type the Token::Type of the token
+  /// @param source the source of the token
+  /// @param str the source string for the token
+  Token(Type type, const Source& source, const char* str);
+  /// Create a unsigned integer Token
+  /// @param source the source of the token
+  /// @param val the source unsigned for the token
+  Token(const Source& source, uint32_t val);
+  /// Create a signed integer Token
+  /// @param source the source of the token
+  /// @param val the source integer for the token
+  Token(const Source& source, int32_t val);
+  /// Create a float Token
+  /// @param source the source of the token
+  /// @param val the source float for the token
+  Token(const Source& source, float val);
+  /// Move constructor
+  Token(Token&&);
+  /// Copy constructor
+  Token(const Token&);
+  ~Token();
+
+  /// Assignment operator
+  /// @param b the token to copy
+  /// @return Token
+  Token& operator=(const Token& b);
+
+  /// Equality operator with an identifier
+  /// @param ident the identifier string
+  /// @return true if this token is an identifier and is equal to ident.
+  bool operator==(std::string_view ident);
+
+  /// Returns true if the token is of the given type
+  /// @param t the type to check against.
+  /// @returns true if the token is of type `t`
+  bool Is(Type t) const { return type_ == t; }
+
+  /// @returns true if the token is uninitialized
+  bool IsUninitialized() const { return type_ == Type::kUninitialized; }
+  /// @returns true if the token is EOF
+  bool IsEof() const { return type_ == Type::kEOF; }
+  /// @returns true if the token is Error
+  bool IsError() const { return type_ == Type::kError; }
+  /// @returns true if the token is an identifier
+  bool IsIdentifier() const { return type_ == Type::kIdentifier; }
+  /// @returns true if the token is a literal
+  bool IsLiteral() const {
+    return type_ == Type::kSintLiteral || type_ == Type::kFalse ||
+           type_ == Type::kUintLiteral || type_ == Type::kTrue ||
+           type_ == Type::kFloatLiteral;
+  }
+  /// @returns true if token is a 'matNxM'
+  bool IsMatrix() const {
+    return type_ == Type::kMat2x2 || type_ == Type::kMat2x3 ||
+           type_ == Type::kMat2x4 || type_ == Type::kMat3x2 ||
+           type_ == Type::kMat3x3 || type_ == Type::kMat3x4 ||
+           type_ == Type::kMat4x2 || type_ == Type::kMat4x3 ||
+           type_ == Type::kMat4x4;
+  }
+  /// @returns true if token is a 'mat3xM'
+  bool IsMat3xN() const {
+    return type_ == Type::kMat3x2 || type_ == Type::kMat3x3 ||
+           type_ == Type::kMat3x4;
+  }
+  /// @returns true if token is a 'mat4xM'
+  bool IsMat4xN() const {
+    return type_ == Type::kMat4x2 || type_ == Type::kMat4x3 ||
+           type_ == Type::kMat4x4;
+  }
+  /// @returns true if token is a 'matNx3'
+  bool IsMatNx3() const {
+    return type_ == Type::kMat2x3 || type_ == Type::kMat3x3 ||
+           type_ == Type::kMat4x3;
+  }
+  /// @returns true if token is a 'matNx4'
+  bool IsMatNx4() const {
+    return type_ == Type::kMat2x4 || type_ == Type::kMat3x4 ||
+           type_ == Type::kMat4x4;
+  }
+
+  /// @returns true if token is a 'vecN'
+  bool IsVector() const {
+    return type_ == Type::kVec2 || type_ == Type::kVec3 || type_ == Type::kVec4;
+  }
+
+  /// @returns the source information for this token
+  Source source() const { return source_; }
+
+  /// Returns the string value of the token
+  /// @return std::string
+  std::string to_str() const;
+  /// Returns the float value of the token. 0 is returned if the token does not
+  /// contain a float value.
+  /// @return float
+  float to_f32() const;
+  /// Returns the uint32 value of the token. 0 is returned if the token does not
+  /// contain a unsigned integer value.
+  /// @return uint32_t
+  uint32_t to_u32() const;
+  /// Returns the int32 value of the token. 0 is returned if the token does not
+  /// contain a signed integer value.
+  /// @return int32_t
+  int32_t to_i32() const;
+
+  /// @returns the token type as string
+  std::string_view to_name() const { return Token::TypeToName(type_); }
+
+ private:
+  /// The Token::Type of the token
+  Type type_ = Type::kError;
+  /// The source where the token appeared
+  Source source_;
+  /// The value represented by the token
+  std::variant<int32_t, uint32_t, float, std::string, std::string_view> value_;
+};
+
+#ifndef NDEBUG
+inline std::ostream& operator<<(std::ostream& out, Token::Type type) {
+  out << Token::TypeToName(type);
+  return out;
+}
+#endif  // NDEBUG
+
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_TINT_READER_WGSL_TOKEN_H_
diff --git a/src/tint/reader/wgsl/token_test.cc b/src/tint/reader/wgsl/token_test.cc
new file mode 100644
index 0000000..132d301
--- /dev/null
+++ b/src/tint/reader/wgsl/token_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2020 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/reader/wgsl/token.h"
+
+#include <limits>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+using TokenTest = testing::Test;
+
+TEST_F(TokenTest, ReturnsF32) {
+  Token t1(Source{}, -2.345f);
+  EXPECT_EQ(t1.to_f32(), -2.345f);
+
+  Token t2(Source{}, 2.345f);
+  EXPECT_EQ(t2.to_f32(), 2.345f);
+}
+
+TEST_F(TokenTest, ReturnsI32) {
+  Token t1(Source{}, -2345);
+  EXPECT_EQ(t1.to_i32(), -2345);
+
+  Token t2(Source{}, 2345);
+  EXPECT_EQ(t2.to_i32(), 2345);
+}
+
+TEST_F(TokenTest, HandlesMaxI32) {
+  Token t1(Source{}, std::numeric_limits<int32_t>::max());
+  EXPECT_EQ(t1.to_i32(), std::numeric_limits<int32_t>::max());
+}
+
+TEST_F(TokenTest, HandlesMinI32) {
+  Token t1(Source{}, std::numeric_limits<int32_t>::min());
+  EXPECT_EQ(t1.to_i32(), std::numeric_limits<int32_t>::min());
+}
+
+TEST_F(TokenTest, ReturnsU32) {
+  Token t2(Source{}, 2345u);
+  EXPECT_EQ(t2.to_u32(), 2345u);
+}
+
+TEST_F(TokenTest, ReturnsMaxU32) {
+  Token t1(Source{}, std::numeric_limits<uint32_t>::max());
+  EXPECT_EQ(t1.to_u32(), std::numeric_limits<uint32_t>::max());
+}
+
+TEST_F(TokenTest, Source) {
+  Source src;
+  src.range.begin = Source::Location{3, 9};
+  src.range.end = Source::Location{4, 3};
+
+  Token t(Token::Type::kUintLiteral, src);
+  EXPECT_EQ(t.source().range.begin.line, 3u);
+  EXPECT_EQ(t.source().range.begin.column, 9u);
+  EXPECT_EQ(t.source().range.end.line, 4u);
+  EXPECT_EQ(t.source().range.end.column, 3u);
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/tint/resolver/array_accessor_test.cc b/src/tint/resolver/array_accessor_test.cc
new file mode 100644
index 0000000..c8fcb4e
--- /dev/null
+++ b/src/tint/resolver/array_accessor_test.cc
@@ -0,0 +1,312 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/reference_type.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverIndexAccessorTest = ResolverTest;
+
+TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_F32) {
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 1.0f));
+  WrapInFunction(acc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_Ref) {
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
+  auto* acc = IndexAccessor("my_var", idx);
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions_Dynamic_Ref) {
+  Global("my_var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
+  auto* idx = Var("idx", ty.u32(), Expr(3u));
+  auto* idy = Var("idy", ty.u32(), Expr(2u));
+  auto* acc = IndexAccessor(IndexAccessor("my_var", idx), idy);
+  WrapInFunction(Decl(idx), Decl(idy), acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic) {
+  GlobalConst("my_const", ty.mat2x3<f32>(), Construct(ty.mat2x3<f32>()));
+  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
+  auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "");
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_XDimension_Dynamic) {
+  GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
+  auto* idx = Var("idx", ty.u32(), Expr(3u));
+  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "");
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_BothDimension_Dynamic) {
+  GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
+  auto* idx = Var("idy", ty.u32(), Expr(2u));
+  auto* acc =
+      IndexAccessor(IndexAccessor("my_var", Expr(Source{{12, 34}}, idx)), 1);
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "");
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix) {
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* acc = IndexAccessor("my_var", 2);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(acc)->As<sem::Reference>();
+  ASSERT_TRUE(ref->StoreType()->Is<sem::Vector>());
+  EXPECT_EQ(ref->StoreType()->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions) {
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* acc = IndexAccessor(IndexAccessor("my_var", 2), 1);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(acc)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Vector_F32) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 2.0f));
+  WrapInFunction(acc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
+}
+
+TEST_F(ResolverIndexAccessorTest, Vector_Dynamic_Ref) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* idx = Var("idx", ty.i32(), Expr(2));
+  auto* acc = IndexAccessor("my_var", idx);
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverIndexAccessorTest, Vector_Dynamic) {
+  GlobalConst("my_var", ty.vec3<f32>(), Construct(ty.vec3<f32>()));
+  auto* idx = Var("idx", ty.i32(), Expr(2));
+  auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
+  WrapInFunction(Decl(idx), acc);
+
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverIndexAccessorTest, Vector) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* acc = IndexAccessor("my_var", 2);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(acc)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Array) {
+  auto* idx = Expr(2);
+  Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
+
+  auto* acc = IndexAccessor("my_var", idx);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(acc)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Alias_Array) {
+  auto* aary = Alias("myarrty", ty.array<f32, 3>());
+
+  Global("my_var", ty.Of(aary), ast::StorageClass::kPrivate);
+
+  auto* acc = IndexAccessor("my_var", 2);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(acc)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Constant) {
+  GlobalConst("my_var", ty.array<f32, 3>(), array<f32, 3>());
+
+  auto* acc = IndexAccessor("my_var", 2);
+  WrapInFunction(acc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(acc), nullptr);
+  EXPECT_TRUE(TypeOf(acc)->Is<sem::F32>()) << TypeOf(acc)->type_name();
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Dynamic_I32) {
+  // let a : array<f32, 3> = 0;
+  // var idx : i32 = 0;
+  // var f : f32 = a[idx];
+  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
+  auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
+  auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(idx),
+           Decl(f),
+       },
+       ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "");
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Literal_F32) {
+  // let a : array<f32, 3>;
+  // var f : f32 = a[2.0f];
+  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
+  auto* f =
+      Var("a_2", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, 2.0f)));
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(f),
+       },
+       ast::AttributeList{});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Literal_I32) {
+  // let a : array<f32, 3>;
+  // var f : f32 = a[2];
+  auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
+  auto* f = Var("a_2", ty.f32(), IndexAccessor("a", 2));
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(f),
+       },
+       ast::AttributeList{});
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncGoodParent) {
+  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
+  //     let idx: u32 = u32();
+  //     let x: f32 = (*p)[idx];
+  //     return x;
+  // }
+  auto* p =
+      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
+  auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
+  auto* star_p = Deref(p);
+  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, star_p, idx);
+  auto* x = Var("x", ty.f32(), accessor_expr);
+  Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncBadParent) {
+  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
+  //     let idx: u32 = u32();
+  //     let x: f32 = *p[idx];
+  //     return x;
+  // }
+  auto* p =
+      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
+  auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
+  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, p, idx);
+  auto* star_p = Deref(accessor_expr);
+  auto* x = Var("x", ty.f32(), star_p);
+  Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
+}
+
+TEST_F(ResolverIndexAccessorTest, Exr_Deref_BadParent) {
+  // var param: vec4<f32>
+  // let x: f32 = *(&param)[0];
+  auto* param = Var("param", ty.vec4<f32>());
+  auto* idx = Var("idx", ty.u32(), Construct(ty.u32()));
+  auto* addressOf_expr = AddressOf(param);
+  auto* accessor_expr = IndexAccessor(Source{{12, 34}}, addressOf_expr, idx);
+  auto* star_p = Deref(accessor_expr);
+  auto* x = Var("x", ty.f32(), star_p);
+  WrapInFunction(param, idx, x);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
new file mode 100644
index 0000000..a69d6a0
--- /dev/null
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -0,0 +1,403 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverAssignmentValidationTest = ResolverTest;
+
+TEST_F(ResolverAssignmentValidationTest, ReadOnlyBuffer) {
+  // [[block]] struct S { m : i32 };
+  // @group(0) @binding(0)
+  // var<storage,read> a : S;
+  auto* s = Structure("S", {Member("m", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("a", "m"), 1));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: cannot store into a read-only type 'ref<storage, "
+            "i32, read>'");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignIncompatibleTypes) {
+  // {
+  //  var a : i32 = 2;
+  //  a = 2.3;
+  // }
+
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+
+  auto* assign = Assign(Source{{12, 34}}, "a", 2.3f);
+  WrapInFunction(var, assign);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignArraysWithDifferentSizeExpressions_Pass) {
+  // let len = 4u;
+  // {
+  //   var a : array<f32, 4>;
+  //   var b : array<f32, len>;
+  //   a = b;
+  // }
+
+  GlobalConst("len", nullptr, Expr(4u));
+
+  auto* a = Var("a", ty.array(ty.f32(), 4));
+  auto* b = Var("b", ty.array(ty.f32(), "len"));
+
+  auto* assign = Assign(Source{{12, 34}}, "a", "b");
+  WrapInFunction(a, b, assign);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignArraysWithDifferentSizeExpressions_Fail) {
+  // let len = 5u;
+  // {
+  //   var a : array<f32, 4>;
+  //   var b : array<f32, len>;
+  //   a = b;
+  // }
+
+  GlobalConst("len", nullptr, Expr(5u));
+
+  auto* a = Var("a", ty.array(ty.f32(), 4));
+  auto* b = Var("b", ty.array(ty.f32(), "len"));
+
+  auto* assign = Assign(Source{{12, 34}}, "a", "b");
+  WrapInFunction(a, b, assign);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot assign 'array<f32, 5>' to 'array<f32, 4>'");
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignCompatibleTypesInBlockStatement_Pass) {
+  // {
+  //  var a : i32 = 2;
+  //  a = 2
+  // }
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  WrapInFunction(var, Assign("a", 2));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignIncompatibleTypesInBlockStatement_Fail) {
+  // {
+  //  var a : i32 = 2;
+  //  a = 2.3;
+  // }
+
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2.3f));
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignIncompatibleTypesInNestedBlockStatement_Fail) {
+  // {
+  //  {
+  //   var a : i32 = 2;
+  //   a = 2.3;
+  //  }
+  // }
+
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  auto* inner_block = Block(Decl(var), Assign(Source{{12, 34}}, "a", 2.3f));
+  auto* outer_block = Block(inner_block);
+  WrapInFunction(outer_block);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToScalar_Fail) {
+  // var my_var : i32 = 2;
+  // 1 = my_var;
+
+  auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  WrapInFunction(var, Assign(Expr(Source{{12, 34}}, 1), "my_var"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: cannot assign to value of type 'i32'");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
+  // var a : i32 = 2;
+  // a = 2
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignCompatibleTypesThroughAlias_Pass) {
+  // alias myint = i32;
+  // var a : myint = 2;
+  // a = 2
+  auto* myint = Alias("myint", ty.i32());
+  auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2));
+  WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignCompatibleTypesInferRHSLoad_Pass) {
+  // var a : i32 = 2;
+  // var b : i32 = 3;
+  // a = b;
+  auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
+  WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, "a", "b"));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignThroughPointer_Pass) {
+  // var a : i32;
+  // let b : ptr<function,i32> = &a;
+  // *b = 2;
+  const auto func = ast::StorageClass::kFunction;
+  auto* var_a = Var("a", ty.i32(), func, Expr(2));
+  auto* var_b = Const("b", ty.pointer<int>(func), AddressOf(Expr("a")));
+  WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, Deref("b"), 2));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
+  // {
+  //  let a : i32 = 2;
+  //  a = 2
+  // }
+  auto* var = Const("a", ty.i32(), Expr(2));
+  WrapInFunction(var, Assign(Expr(Source{{12, 34}}, "a"), 2));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot assign to const\nnote: 'a' is declared here:");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) {
+  // var a : texture_storage_1d<rgba8unorm, write>;
+  // var b : texture_storage_1d<rgba8unorm, write>;
+  // a = b;
+
+  auto make_type = [&] {
+    return ty.storage_texture(ast::TextureDimension::k1d,
+                              ast::TexelFormat::kRgba8Unorm,
+                              ast::Access::kWrite);
+  };
+
+  Global("a", make_type(), ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+  Global("b", make_type(), ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, "a", "b"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: storage type of assignment must be constructible");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Atomic) {
+  // [[block]] struct S { a : atomic<i32>; };
+  // @group(0) @binding(0) var<storage, read_write> v : S;
+  // v.a = v.a;
+
+  auto* s = Structure("S", {Member("a", ty.atomic(ty.i32()))},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
+                        MemberAccessor("v", "a")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: storage type of assignment must be constructible");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_RuntimeArray) {
+  // [[block]] struct S { a : array<f32>; };
+  // @group(0) @binding(0) var<storage, read_write> v : S;
+  // v.a = v.a;
+
+  auto* s = Structure("S", {Member("a", ty.array(ty.f32()))},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
+                        MemberAccessor("v", "a")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: storage type of assignment must be constructible");
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+       AssignToPhony_NonConstructibleStruct_Fail) {
+  // [[block]]
+  // struct S {
+  //   arr: array<i32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  // fn f() {
+  //   _ = s;
+  // }
+  auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
+  Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
+
+  WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot assign 'S' to '_'. "
+            "'_' can only be assigned a constructible, pointer, texture or "
+            "sampler type");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToPhony_DynamicArray_Fail) {
+  // [[block]]
+  // struct S {
+  //   arr: array<i32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  // fn f() {
+  //   _ = s.arr;
+  // }
+  auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
+  Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
+
+  WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: cannot assign 'array<i32>' to '_'. "
+      "'_' can only be assigned a constructible, pointer, texture or sampler "
+      "type");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToPhony_Pass) {
+  // [[block]]
+  // struct S {
+  //   i:   i32;
+  //   arr: array<i32>;
+  // };
+  // [[block]]
+  // struct U {
+  //   i:   i32;
+  // };
+  // @group(0) @binding(0) var tex texture_2d;
+  // @group(0) @binding(1) var smp sampler;
+  // @group(0) @binding(2) var<uniform> u : U;
+  // @group(0) @binding(3) var<storage, read_write> s : S;
+  // var<workgroup> wg : array<f32, 10>
+  // fn f() {
+  //   _ = 1;
+  //   _ = 2u;
+  //   _ = 3.0;
+  //   _ = vec2<bool>();
+  //   _ = tex;
+  //   _ = smp;
+  //   _ = &s;
+  //   _ = s.i;
+  //   _ = &s.arr;
+  //   _ = u;
+  //   _ = u.i;
+  //   _ = wg;
+  //   _ = wg[3];
+  // }
+  auto* S = Structure("S",
+                      {
+                          Member("i", ty.i32()),
+                          Member("arr", ty.array<i32>()),
+                      },
+                      {StructBlock()});
+  auto* U = Structure("U", {Member("i", ty.i32())}, {StructBlock()});
+  Global("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(0, 0));
+  Global("smp", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 1));
+  Global("u", ty.Of(U), ast::StorageClass::kUniform, GroupAndBinding(0, 2));
+  Global("s", ty.Of(S), ast::StorageClass::kStorage, GroupAndBinding(0, 3));
+  Global("wg", ty.array<f32, 10>(), ast::StorageClass::kWorkgroup);
+
+  WrapInFunction(Assign(Phony(), 1),                                      //
+                 Assign(Phony(), 2),                                      //
+                 Assign(Phony(), 3),                                      //
+                 Assign(Phony(), vec2<bool>()),                           //
+                 Assign(Phony(), "tex"),                                  //
+                 Assign(Phony(), "smp"),                                  //
+                 Assign(Phony(), AddressOf("s")),                         //
+                 Assign(Phony(), MemberAccessor("s", "i")),               //
+                 Assign(Phony(), AddressOf(MemberAccessor("s", "arr"))),  //
+                 Assign(Phony(), "u"),                                    //
+                 Assign(Phony(), MemberAccessor("u", "i")),               //
+                 Assign(Phony(), "wg"),                                   //
+                 Assign(Phony(), IndexAccessor("wg", 3)));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/atomics_test.cc b/src/tint/resolver/atomics_test.cc
new file mode 100644
index 0000000..04592b0
--- /dev/null
+++ b/src/tint/resolver/atomics_test.cc
@@ -0,0 +1,74 @@
+// 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/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/reference_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverAtomicTest : public resolver::TestHelper,
+                            public testing::Test {};
+
+TEST_F(ResolverAtomicTest, GlobalWorkgroupI32) {
+  auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+                   ast::StorageClass::kWorkgroup);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
+  auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
+  ASSERT_NE(atomic, nullptr);
+  EXPECT_TRUE(atomic->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverAtomicTest, GlobalWorkgroupU32) {
+  auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.u32()),
+                   ast::StorageClass::kWorkgroup);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
+  auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
+  ASSERT_NE(atomic, nullptr);
+  EXPECT_TRUE(atomic->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
+  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+                      {create<ast::StructBlockAttribute>()});
+  auto* g = Global("g", ty.Of(s), ast::StorageClass::kStorage,
+                   ast::Access::kReadWrite,
+                   ast::AttributeList{
+                       create<ast::BindingAttribute>(0),
+                       create<ast::GroupAttribute>(0),
+                   });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
+  auto* str = TypeOf(g)->UnwrapRef()->As<sem::Struct>();
+  ASSERT_NE(str, nullptr);
+  ASSERT_EQ(str->Members().size(), 1u);
+  auto* atomic = str->Members()[0]->Type()->As<sem::Atomic>();
+  ASSERT_NE(atomic, nullptr);
+  ASSERT_TRUE(atomic->Type()->Is<sem::I32>());
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/atomics_validation_test.cc b/src/tint/resolver/atomics_validation_test.cc
new file mode 100644
index 0000000..47da93b
--- /dev/null
+++ b/src/tint/resolver/atomics_validation_test.cc
@@ -0,0 +1,332 @@
+// 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/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/reference_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverAtomicValidationTest : public resolver::TestHelper,
+                                      public testing::Test {};
+
+TEST_F(ResolverAtomicValidationTest, StorageClass_WorkGroup) {
+  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+         ast::StorageClass::kWorkgroup);
+
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverAtomicValidationTest, StorageClass_Storage) {
+  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+                      {StructBlock()});
+  Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         GroupAndBinding(0, 0));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidType) {
+  Global("a", ty.atomic(ty.f32(Source{{12, 34}})),
+         ast::StorageClass::kWorkgroup);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: atomic only supports i32 or u32 types");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Simple) {
+  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: atomic variables must have <storage> or <workgroup> "
+            "storage class");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Array) {
+  Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: atomic variables must have <storage> or <workgroup> "
+            "storage class");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Struct) {
+  auto* s =
+      Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "note: atomic sub-type of 's' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_StructOfStruct) {
+  // struct Inner { m : atomic<i32>; };
+  // struct Outer { m : array<Inner, 4>; };
+  // var<private> g : Outer;
+
+  auto* Inner =
+      Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
+  auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+  Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest,
+       InvalidStorageClass_StructOfStructOfArray) {
+  // struct Inner { m : array<atomic<i32>, 4>; };
+  // struct Outer { m : array<Inner, 4>; };
+  // var<private> g : Outer;
+
+  auto* Inner =
+      Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
+  auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+  Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "12:34 note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfArray) {
+  // type AtomicArray = array<atomic<i32>, 5>;
+  // var<private> v: array<s, 5>;
+
+  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+                             ty.atomic(Source{{12, 34}}, ty.i32()));
+  Global(Source{{56, 78}}, "v", ty.Of(atomic_array),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStruct) {
+  // struct S{
+  //   m: atomic<u32>;
+  // };
+  // var<private> v: array<S, 5>;
+
+  auto* s = Structure("S", {Member("m", ty.atomic<u32>())});
+  Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "note: atomic sub-type of 'array<S, 5>' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStructOfArray) {
+  // type AtomicArray = array<atomic<i32>, 5>;
+  // struct S{
+  //   m: AtomicArray;
+  // };
+  // var<private> v: array<S, 5>;
+
+  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+                             ty.atomic(Source{{12, 34}}, ty.i32()));
+  auto* s = Structure("S", {Member("m", ty.Of(atomic_array))});
+  Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "note: atomic sub-type of 'array<S, 5>' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Complex) {
+  // type AtomicArray = array<atomic<i32>, 5>;
+  // struct S6 { x: array<i32, 4>; };
+  // struct S5 { x: S6;
+  //             y: AtomicArray;
+  //             z: array<atomic<u32>, 8>; };
+  // struct S4 { x: S6;
+  //             y: S5;
+  //             z: array<atomic<i32>, 4>; };
+  // struct S3 { x: S4; };
+  // struct S2 { x: S3; };
+  // struct S1 { x: S2; };
+  // struct S0 { x: S1; };
+  // var<private> g : S0;
+
+  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+                             ty.atomic(Source{{12, 34}}, ty.i32()));
+  auto* array_i32_4 = ty.array(ty.i32(), 4);
+  auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
+  auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
+
+  auto* s6 = Structure("S6", {Member("x", array_i32_4)});
+  auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
+                              Member("y", ty.Of(atomic_array)),   //
+                              Member("z", array_atomic_u32_8)});  //
+  auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
+                              Member("y", ty.Of(s5)),             //
+                              Member("z", array_atomic_i32_4)});  //
+  auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
+  auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
+  auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
+  auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
+  Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables must have <storage> or <workgroup> "
+            "storage class\n"
+            "note: atomic sub-type of 'S0' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
+  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+                      {StructBlock()});
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead, GroupAndBinding(0, 0));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "error: atomic variables in <storage> storage class must have read_write "
+      "access mode\n"
+      "note: atomic sub-type of 's' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
+  auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+                      {StructBlock()});
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead, GroupAndBinding(0, 0));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "error: atomic variables in <storage> storage class must have read_write "
+      "access mode\n"
+      "note: atomic sub-type of 's' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStruct) {
+  // struct Inner { m : atomic<i32>; };
+  // struct Outer { m : array<Inner, 4>; };
+  // var<storage, read> g : Outer;
+
+  auto* Inner =
+      Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
+  auto* Outer =
+      Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
+  Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
+         ast::Access::kRead, GroupAndBinding(0, 0));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "error: atomic variables in <storage> storage class must have read_write "
+      "access mode\n"
+      "note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStructOfArray) {
+  // struct Inner { m : array<atomic<i32>, 4>; };
+  // struct Outer { m : array<Inner, 4>; };
+  // var<storage, read> g : Outer;
+
+  auto* Inner =
+      Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
+  auto* Outer =
+      Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
+  Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
+         ast::Access::kRead, GroupAndBinding(0, 0));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables in <storage> storage class must have "
+            "read_write access mode\n"
+            "12:34 note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Complex) {
+  // type AtomicArray = array<atomic<i32>, 5>;
+  // struct S6 { x: array<i32, 4>; };
+  // struct S5 { x: S6;
+  //             y: AtomicArray;
+  //             z: array<atomic<u32>, 8>; };
+  // struct S4 { x: S6;
+  //             y: S5;
+  //             z: array<atomic<i32>, 4>; };
+  // struct S3 { x: S4; };
+  // struct S2 { x: S3; };
+  // struct S1 { x: S2; };
+  // struct S0 { x: S1; };
+  // var<storage, read> g : S0;
+
+  auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+                             ty.atomic(Source{{12, 34}}, ty.i32()));
+  auto* array_i32_4 = ty.array(ty.i32(), 4);
+  auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
+  auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
+
+  auto* s6 = Structure("S6", {Member("x", array_i32_4)});
+  auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
+                              Member("y", ty.Of(atomic_array)),   //
+                              Member("z", array_atomic_u32_8)});  //
+  auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
+                              Member("y", ty.Of(s5)),             //
+                              Member("z", array_atomic_i32_4)});  //
+  auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
+  auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
+  auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
+  auto* s0 = Structure("S0", {Member("x", ty.Of(s1))}, {StructBlock()});
+  Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kStorage,
+         ast::Access::kRead, GroupAndBinding(0, 0));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: atomic variables in <storage> storage class must have "
+            "read_write access mode\n"
+            "note: atomic sub-type of 'S0' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, Local) {
+  WrapInFunction(Var("a", ty.atomic(Source{{12, 34}}, ty.i32())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function variable must have a constructible type");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
new file mode 100644
index 0000000..7231596
--- /dev/null
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -0,0 +1,1404 @@
+// 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/ast/disable_validation_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <typename T, int ID = 0>
+using alias = builder::alias<T, ID>;
+template <typename T>
+using alias1 = builder::alias1<T>;
+template <typename T>
+using alias2 = builder::alias2<T>;
+template <typename T>
+using alias3 = builder::alias3<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+namespace AttributeTests {
+namespace {
+enum class AttributeKind {
+  kAlign,
+  kBinding,
+  kBuiltin,
+  kGroup,
+  kId,
+  kInterpolate,
+  kInvariant,
+  kLocation,
+  kOffset,
+  kSize,
+  kStage,
+  kStride,
+  kStructBlock,
+  kWorkgroup,
+
+  kBindingAndGroup,
+};
+
+static bool IsBindingAttribute(AttributeKind kind) {
+  switch (kind) {
+    case AttributeKind::kBinding:
+    case AttributeKind::kGroup:
+    case AttributeKind::kBindingAndGroup:
+      return true;
+    default:
+      return false;
+  }
+}
+
+struct TestParams {
+  AttributeKind kind;
+  bool should_pass;
+};
+struct TestWithParams : ResolverTestWithParam<TestParams> {};
+
+static ast::AttributeList createAttributes(const Source& source,
+                                           ProgramBuilder& builder,
+                                           AttributeKind kind) {
+  switch (kind) {
+    case AttributeKind::kAlign:
+      return {builder.create<ast::StructMemberAlignAttribute>(source, 4u)};
+    case AttributeKind::kBinding:
+      return {builder.create<ast::BindingAttribute>(source, 1u)};
+    case AttributeKind::kBuiltin:
+      return {builder.Builtin(source, ast::Builtin::kPosition)};
+    case AttributeKind::kGroup:
+      return {builder.create<ast::GroupAttribute>(source, 1u)};
+    case AttributeKind::kId:
+      return {builder.create<ast::IdAttribute>(source, 0u)};
+    case AttributeKind::kInterpolate:
+      return {builder.Interpolate(source, ast::InterpolationType::kLinear,
+                                  ast::InterpolationSampling::kCenter)};
+    case AttributeKind::kInvariant:
+      return {builder.Invariant(source)};
+    case AttributeKind::kLocation:
+      return {builder.Location(source, 1)};
+    case AttributeKind::kOffset:
+      return {builder.create<ast::StructMemberOffsetAttribute>(source, 4u)};
+    case AttributeKind::kSize:
+      return {builder.create<ast::StructMemberSizeAttribute>(source, 16u)};
+    case AttributeKind::kStage:
+      return {builder.Stage(source, ast::PipelineStage::kCompute)};
+    case AttributeKind::kStride:
+      return {builder.create<ast::StrideAttribute>(source, 4u)};
+    case AttributeKind::kStructBlock:
+      return {builder.create<ast::StructBlockAttribute>(source)};
+    case AttributeKind::kWorkgroup:
+      return {builder.create<ast::WorkgroupAttribute>(source, builder.Expr(1))};
+    case AttributeKind::kBindingAndGroup:
+      return {builder.create<ast::BindingAttribute>(source, 1u),
+              builder.create<ast::GroupAttribute>(source, 1u)};
+  }
+  return {};
+}
+
+namespace FunctionInputAndOutputTests {
+using FunctionParameterAttributeTest = TestWithParams;
+TEST_P(FunctionParameterAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  Func("main",
+       ast::VariableList{Param("a", ty.vec4<f32>(),
+                               createAttributes({}, *this, params.kind))},
+       ty.void_(), {});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "error: attribute is not valid for non-entry point function "
+              "parameters");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    FunctionParameterAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using FunctionReturnTypeAttributeTest = TestWithParams;
+TEST_P(FunctionReturnTypeAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
+       {}, createAttributes({}, *this, params.kind));
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "error: attribute is not valid for non-entry point function "
+              "return types");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    FunctionReturnTypeAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+}  // namespace FunctionInputAndOutputTests
+
+namespace EntryPointInputAndOutputTests {
+using ComputeShaderParameterAttributeTest = TestWithParams;
+TEST_P(ComputeShaderParameterAttributeTest, IsValid) {
+  auto& params = GetParam();
+  auto* p = Param("a", ty.vec4<f32>(),
+                  createAttributes(Source{{12, 34}}, *this, params.kind));
+  Func("main", ast::VariableList{p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (params.kind == AttributeKind::kBuiltin) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: builtin(position) cannot be used in input of "
+                "compute pipeline stage");
+    } else if (params.kind == AttributeKind::kInterpolate ||
+               params.kind == AttributeKind::kLocation ||
+               params.kind == AttributeKind::kInvariant) {
+      EXPECT_EQ(
+          r()->error(),
+          "12:34 error: attribute is not valid for compute shader inputs");
+    } else {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for function parameters");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    ComputeShaderParameterAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using FragmentShaderParameterAttributeTest = TestWithParams;
+TEST_P(FragmentShaderParameterAttributeTest, IsValid) {
+  auto& params = GetParam();
+  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
+  if (params.kind != AttributeKind::kBuiltin &&
+      params.kind != AttributeKind::kLocation) {
+    attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+  }
+  auto* p = Param("a", ty.vec4<f32>(), attrs);
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: attribute is not valid for function parameters");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    FragmentShaderParameterAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, true},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    // kInterpolate tested separately (requires [[location]])
+                    TestParams{AttributeKind::kInvariant, true},
+                    TestParams{AttributeKind::kLocation, true},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using VertexShaderParameterAttributeTest = TestWithParams;
+TEST_P(VertexShaderParameterAttributeTest, IsValid) {
+  auto& params = GetParam();
+  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
+  if (params.kind != AttributeKind::kLocation) {
+    attrs.push_back(Location(Source{{34, 56}}, 2));
+  }
+  auto* p = Param("a", ty.vec4<f32>(), attrs);
+  Func("vertex_main", ast::VariableList{p}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (params.kind == AttributeKind::kBuiltin) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: builtin(position) cannot be used in input of "
+                "vertex pipeline stage");
+    } else if (params.kind == AttributeKind::kInvariant) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: invariant attribute must only be applied to a "
+                "position builtin");
+    } else {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for function parameters");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    VertexShaderParameterAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, true},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, true},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using ComputeShaderReturnTypeAttributeTest = TestWithParams;
+TEST_P(ComputeShaderReturnTypeAttributeTest, IsValid) {
+  auto& params = GetParam();
+  Func("main", ast::VariableList{}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>(), 1.f))},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)},
+       createAttributes(Source{{12, 34}}, *this, params.kind));
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (params.kind == AttributeKind::kBuiltin) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: builtin(position) cannot be used in output of "
+                "compute pipeline stage");
+    } else if (params.kind == AttributeKind::kInterpolate ||
+               params.kind == AttributeKind::kLocation ||
+               params.kind == AttributeKind::kInvariant) {
+      EXPECT_EQ(
+          r()->error(),
+          "12:34 error: attribute is not valid for compute shader output");
+    } else {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for entry point return "
+                "types");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    ComputeShaderReturnTypeAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using FragmentShaderReturnTypeAttributeTest = TestWithParams;
+TEST_P(FragmentShaderReturnTypeAttributeTest, IsValid) {
+  auto& params = GetParam();
+  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
+  attrs.push_back(Location(Source{{34, 56}}, 2));
+  Func("frag_main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kFragment)}, attrs);
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (params.kind == AttributeKind::kBuiltin) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: builtin(position) cannot be used in output of "
+                "fragment pipeline stage");
+    } else if (params.kind == AttributeKind::kInvariant) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: invariant attribute must only be applied to a "
+                "position builtin");
+    } else if (params.kind == AttributeKind::kLocation) {
+      EXPECT_EQ(r()->error(),
+                "34:56 error: duplicate location attribute\n"
+                "12:34 note: first attribute declared here");
+    } else {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for entry point return "
+                "types");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    FragmentShaderReturnTypeAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, true},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using VertexShaderReturnTypeAttributeTest = TestWithParams;
+TEST_P(VertexShaderReturnTypeAttributeTest, IsValid) {
+  auto& params = GetParam();
+  auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
+  // a vertex shader must include the 'position' builtin in its return type
+  if (params.kind != AttributeKind::kBuiltin) {
+    attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+  }
+  Func("vertex_main", ast::VariableList{}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)}, attrs);
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (params.kind == AttributeKind::kLocation) {
+      EXPECT_EQ(r()->error(),
+                "34:56 error: multiple entry point IO attributes\n"
+                "12:34 note: previously consumed location(1)");
+    } else {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for entry point return "
+                "types");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    VertexShaderReturnTypeAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, true},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    // kInterpolate tested separately (requires [[location]])
+                    TestParams{AttributeKind::kInvariant, true},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using EntryPointParameterAttributeTest = TestWithParams;
+TEST_F(EntryPointParameterAttributeTest, DuplicateAttribute) {
+  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
+       {Stage(ast::PipelineStage::kFragment)},
+       {
+           Location(Source{{12, 34}}, 2),
+           Location(Source{{56, 78}}, 3),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate location attribute
+12:34 note: first attribute declared here)");
+}
+
+TEST_F(EntryPointParameterAttributeTest, DuplicateInternalAttribute) {
+  auto* s = Param("s", ty.sampler(ast::SamplerKind::kSampler),
+                  ast::AttributeList{
+                      create<ast::BindingAttribute>(0),
+                      create<ast::GroupAttribute>(0),
+                      Disable(ast::DisabledValidation::kBindingPointCollision),
+                      Disable(ast::DisabledValidation::kEntryPointParameter),
+                  });
+  Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+using EntryPointReturnTypeAttributeTest = ResolverTest;
+TEST_F(EntryPointReturnTypeAttributeTest, DuplicateAttribute) {
+  Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{
+           Location(Source{{12, 34}}, 2),
+           Location(Source{{56, 78}}, 3),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate location attribute
+12:34 note: first attribute declared here)");
+}
+
+TEST_F(EntryPointReturnTypeAttributeTest, DuplicateInternalAttribute) {
+  Func("f", {}, ty.i32(), {Return(1)}, {Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{
+           Disable(ast::DisabledValidation::kBindingPointCollision),
+           Disable(ast::DisabledValidation::kEntryPointParameter),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+}  // namespace EntryPointInputAndOutputTests
+
+namespace StructAndStructMemberTests {
+using StructAttributeTest = TestWithParams;
+TEST_P(StructAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  Structure("mystruct", {Member("a", ty.f32())},
+            createAttributes(Source{{12, 34}}, *this, params.kind));
+
+  WrapInFunction();
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: attribute is not valid for struct declarations");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    StructAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, true},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+TEST_F(StructAttributeTest, DuplicateAttribute) {
+  Structure("mystruct",
+            {
+                Member("a", ty.i32()),
+            },
+            {
+                create<ast::StructBlockAttribute>(Source{{12, 34}}),
+                create<ast::StructBlockAttribute>(Source{{56, 78}}),
+            });
+  WrapInFunction();
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate block attribute
+12:34 note: first attribute declared here)");
+}
+using StructMemberAttributeTest = TestWithParams;
+TEST_P(StructMemberAttributeTest, IsValid) {
+  auto& params = GetParam();
+  ast::StructMemberList members;
+  if (params.kind == AttributeKind::kBuiltin) {
+    members.push_back(
+        {Member("a", ty.vec4<f32>(),
+                createAttributes(Source{{12, 34}}, *this, params.kind))});
+  } else {
+    members.push_back(
+        {Member("a", ty.f32(),
+                createAttributes(Source{{12, 34}}, *this, params.kind))});
+  }
+  Structure("mystruct", members);
+  WrapInFunction();
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: attribute is not valid for structure members");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    StructMemberAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, true},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, true},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    // kInterpolate tested separately (requires [[location]])
+                    // kInvariant tested separately (requires position builtin)
+                    TestParams{AttributeKind::kLocation, true},
+                    TestParams{AttributeKind::kOffset, true},
+                    TestParams{AttributeKind::kSize, true},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+TEST_F(StructMemberAttributeTest, DuplicateAttribute) {
+  Structure(
+      "mystruct",
+      {
+          Member(
+              "a", ty.i32(),
+              {
+                  create<ast::StructMemberAlignAttribute>(Source{{12, 34}}, 4u),
+                  create<ast::StructMemberAlignAttribute>(Source{{56, 78}}, 8u),
+              }),
+      });
+  WrapInFunction();
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate align attribute
+12:34 note: first attribute declared here)");
+}
+TEST_F(StructMemberAttributeTest, InvariantAttributeWithPosition) {
+  Structure("mystruct", {
+                            Member("a", ty.vec4<f32>(),
+                                   {
+                                       Invariant(),
+                                       Builtin(ast::Builtin::kPosition),
+                                   }),
+                        });
+  WrapInFunction();
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+TEST_F(StructMemberAttributeTest, InvariantAttributeWithoutPosition) {
+  Structure("mystruct", {
+                            Member("a", ty.vec4<f32>(),
+                                   {
+                                       Invariant(Source{{12, 34}}),
+                                   }),
+                        });
+  WrapInFunction();
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invariant attribute must only be applied to a "
+            "position builtin");
+}
+
+}  // namespace StructAndStructMemberTests
+
+using ArrayAttributeTest = TestWithParams;
+TEST_P(ArrayAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  auto* arr = ty.array(ty.f32(), nullptr,
+                       createAttributes(Source{{12, 34}}, *this, params.kind));
+  Structure("mystruct",
+            {
+                Member("a", arr),
+            },
+            {create<ast::StructBlockAttribute>()});
+
+  WrapInFunction();
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: attribute is not valid for array types");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    ArrayAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, true},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using VariableAttributeTest = TestWithParams;
+TEST_P(VariableAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  if (IsBindingAttribute(params.kind)) {
+    Global("a", ty.sampler(ast::SamplerKind::kSampler),
+           ast::StorageClass::kNone, nullptr,
+           createAttributes(Source{{12, 34}}, *this, params.kind));
+  } else {
+    Global("a", ty.f32(), ast::StorageClass::kPrivate, nullptr,
+           createAttributes(Source{{12, 34}}, *this, params.kind));
+  }
+
+  WrapInFunction();
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    if (!IsBindingAttribute(params.kind)) {
+      EXPECT_EQ(r()->error(),
+                "12:34 error: attribute is not valid for variables");
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    VariableAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, false},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, true}));
+
+TEST_F(VariableAttributeTest, DuplicateAttribute) {
+  Global("a", ty.sampler(ast::SamplerKind::kSampler),
+         ast::AttributeList{
+             create<ast::BindingAttribute>(Source{{12, 34}}, 2),
+             create<ast::GroupAttribute>(2),
+             create<ast::BindingAttribute>(Source{{56, 78}}, 3),
+         });
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate binding attribute
+12:34 note: first attribute declared here)");
+}
+
+TEST_F(VariableAttributeTest, LocalVariable) {
+  auto* v = Var("a", ty.f32(),
+                ast::AttributeList{
+                    create<ast::BindingAttribute>(Source{{12, 34}}, 2),
+                });
+
+  WrapInFunction(v);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: attributes are not valid on local variables");
+}
+
+using ConstantAttributeTest = TestWithParams;
+TEST_P(ConstantAttributeTest, IsValid) {
+  auto& params = GetParam();
+
+  GlobalConst("a", ty.f32(), Expr(1.23f),
+              createAttributes(Source{{12, 34}}, *this, params.kind));
+
+  WrapInFunction();
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: attribute is not valid for constants");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    ConstantAttributeTest,
+    testing::Values(TestParams{AttributeKind::kAlign, false},
+                    TestParams{AttributeKind::kBinding, false},
+                    TestParams{AttributeKind::kBuiltin, false},
+                    TestParams{AttributeKind::kGroup, false},
+                    TestParams{AttributeKind::kId, true},
+                    TestParams{AttributeKind::kInterpolate, false},
+                    TestParams{AttributeKind::kInvariant, false},
+                    TestParams{AttributeKind::kLocation, false},
+                    TestParams{AttributeKind::kOffset, false},
+                    TestParams{AttributeKind::kSize, false},
+                    TestParams{AttributeKind::kStage, false},
+                    TestParams{AttributeKind::kStride, false},
+                    TestParams{AttributeKind::kStructBlock, false},
+                    TestParams{AttributeKind::kWorkgroup, false},
+                    TestParams{AttributeKind::kBindingAndGroup, false}));
+
+TEST_F(ConstantAttributeTest, DuplicateAttribute) {
+  GlobalConst("a", ty.f32(), Expr(1.23f),
+              ast::AttributeList{
+                  create<ast::IdAttribute>(Source{{12, 34}}, 0),
+                  create<ast::IdAttribute>(Source{{56, 78}}, 1),
+              });
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate id attribute
+12:34 note: first attribute declared here)");
+}
+
+}  // namespace
+}  // namespace AttributeTests
+
+namespace ArrayStrideTests {
+namespace {
+
+struct Params {
+  builder::ast_type_func_ptr create_el_type;
+  uint32_t stride;
+  bool should_pass;
+};
+
+template <typename T>
+constexpr Params ParamsFor(uint32_t stride, bool should_pass) {
+  return Params{DataType<T>::AST, stride, should_pass};
+}
+
+struct TestWithParams : ResolverTestWithParam<Params> {};
+
+using ArrayStrideTest = TestWithParams;
+TEST_P(ArrayStrideTest, All) {
+  auto& params = GetParam();
+  auto* el_ty = params.create_el_type(*this);
+
+  std::stringstream ss;
+  ss << "el_ty: " << FriendlyName(el_ty) << ", stride: " << params.stride
+     << ", should_pass: " << params.should_pass;
+  SCOPED_TRACE(ss.str());
+
+  auto* arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
+
+  Global("myarray", arr, ast::StorageClass::kPrivate);
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: arrays decorated with the stride attribute must "
+              "have a stride that is at least the size of the element type, "
+              "and be a multiple of the element type's alignment value.");
+  }
+}
+
+struct SizeAndAlignment {
+  uint32_t size;
+  uint32_t align;
+};
+constexpr SizeAndAlignment default_u32 = {4, 4};
+constexpr SizeAndAlignment default_i32 = {4, 4};
+constexpr SizeAndAlignment default_f32 = {4, 4};
+constexpr SizeAndAlignment default_vec2 = {8, 8};
+constexpr SizeAndAlignment default_vec3 = {12, 16};
+constexpr SizeAndAlignment default_vec4 = {16, 16};
+constexpr SizeAndAlignment default_mat2x2 = {16, 8};
+constexpr SizeAndAlignment default_mat3x3 = {48, 16};
+constexpr SizeAndAlignment default_mat4x4 = {64, 16};
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    ArrayStrideTest,
+    testing::Values(
+        // Succeed because stride >= element size (while being multiple of
+        // element alignment)
+        ParamsFor<u32>(default_u32.size, true),
+        ParamsFor<i32>(default_i32.size, true),
+        ParamsFor<f32>(default_f32.size, true),
+        ParamsFor<vec2<f32>>(default_vec2.size, true),
+        // vec3's default size is not a multiple of its alignment
+        // ParamsFor<vec3<f32>, default_vec3.size, true},
+        ParamsFor<vec4<f32>>(default_vec4.size, true),
+        ParamsFor<mat2x2<f32>>(default_mat2x2.size, true),
+        ParamsFor<mat3x3<f32>>(default_mat3x3.size, true),
+        ParamsFor<mat4x4<f32>>(default_mat4x4.size, true),
+
+        // Fail because stride is < element size
+        ParamsFor<u32>(default_u32.size - 1, false),
+        ParamsFor<i32>(default_i32.size - 1, false),
+        ParamsFor<f32>(default_f32.size - 1, false),
+        ParamsFor<vec2<f32>>(default_vec2.size - 1, false),
+        ParamsFor<vec3<f32>>(default_vec3.size - 1, false),
+        ParamsFor<vec4<f32>>(default_vec4.size - 1, false),
+        ParamsFor<mat2x2<f32>>(default_mat2x2.size - 1, false),
+        ParamsFor<mat3x3<f32>>(default_mat3x3.size - 1, false),
+        ParamsFor<mat4x4<f32>>(default_mat4x4.size - 1, false),
+
+        // Succeed because stride equals multiple of element alignment
+        ParamsFor<u32>(default_u32.align * 7, true),
+        ParamsFor<i32>(default_i32.align * 7, true),
+        ParamsFor<f32>(default_f32.align * 7, true),
+        ParamsFor<vec2<f32>>(default_vec2.align * 7, true),
+        ParamsFor<vec3<f32>>(default_vec3.align * 7, true),
+        ParamsFor<vec4<f32>>(default_vec4.align * 7, true),
+        ParamsFor<mat2x2<f32>>(default_mat2x2.align * 7, true),
+        ParamsFor<mat3x3<f32>>(default_mat3x3.align * 7, true),
+        ParamsFor<mat4x4<f32>>(default_mat4x4.align * 7, true),
+
+        // Fail because stride is not multiple of element alignment
+        ParamsFor<u32>((default_u32.align - 1) * 7, false),
+        ParamsFor<i32>((default_i32.align - 1) * 7, false),
+        ParamsFor<f32>((default_f32.align - 1) * 7, false),
+        ParamsFor<vec2<f32>>((default_vec2.align - 1) * 7, false),
+        ParamsFor<vec3<f32>>((default_vec3.align - 1) * 7, false),
+        ParamsFor<vec4<f32>>((default_vec4.align - 1) * 7, false),
+        ParamsFor<mat2x2<f32>>((default_mat2x2.align - 1) * 7, false),
+        ParamsFor<mat3x3<f32>>((default_mat3x3.align - 1) * 7, false),
+        ParamsFor<mat4x4<f32>>((default_mat4x4.align - 1) * 7, false)));
+
+TEST_F(ArrayStrideTest, DuplicateAttribute) {
+  auto* arr = ty.array(Source{{12, 34}}, ty.i32(), 4,
+                       {
+                           create<ast::StrideAttribute>(Source{{12, 34}}, 4),
+                           create<ast::StrideAttribute>(Source{{56, 78}}, 4),
+                       });
+
+  Global("myarray", arr, ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate stride attribute
+12:34 note: first attribute declared here)");
+}
+
+}  // namespace
+}  // namespace ArrayStrideTests
+
+namespace ResourceTests {
+namespace {
+
+using ResourceAttributeTest = ResolverTest;
+TEST_F(ResourceAttributeTest, UniformBufferMissingBinding) {
+  auto* s = Structure("S", {Member("x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kUniform);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, StorageBufferMissingBinding) {
+  auto* s = Structure("S", {Member("x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, TextureMissingBinding) {
+  Global(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d),
+         ast::StorageClass::kNone);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, SamplerMissingBinding) {
+  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
+         ast::StorageClass::kNone);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
+  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::GroupAttribute>(1),
+         });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
+  Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+         });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: resource variables require @group and @binding attributes)");
+}
+
+TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
+  Global(Source{{12, 34}}, "A",
+         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+  Global(Source{{56, 78}}, "B",
+         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("F", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
+                    Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
+           Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
+                    Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: entry point 'F' references multiple variables that use the same resource binding @group(2), @binding(1)
+12:34 note: first resource binding usage declared here)");
+}
+
+TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
+  Global(Source{{12, 34}}, "A",
+         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+  Global(Source{{56, 78}}, "B",
+         ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         ast::StorageClass::kNone,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("F_A", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
+                    Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+  Func("F_B", {}, ty.void_(),
+       {
+           Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
+                    Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
+  Global(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: non-resource variables must not have @group or @binding attributes)");
+}
+
+}  // namespace
+}  // namespace ResourceTests
+
+namespace InvariantAttributeTests {
+namespace {
+using InvariantAttributeTests = ResolverTest;
+TEST_F(InvariantAttributeTests, InvariantWithPosition) {
+  auto* param = Param("p", ty.vec4<f32>(),
+                      {Invariant(Source{{12, 34}}),
+                       Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
+  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{
+           Location(0),
+       });
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(InvariantAttributeTests, InvariantWithoutPosition) {
+  auto* param =
+      Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
+  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{
+           Location(0),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invariant attribute must only be applied to a "
+            "position builtin");
+}
+}  // namespace
+}  // namespace InvariantAttributeTests
+
+namespace WorkgroupAttributeTests {
+namespace {
+
+using WorkgroupAttribute = ResolverTest;
+TEST_F(WorkgroupAttribute, ComputeShaderPass) {
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(WorkgroupAttribute, Missing) {
+  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: a compute shader must include 'workgroup_size' in its "
+      "attributes");
+}
+
+TEST_F(WorkgroupAttribute, NotAnEntryPoint) {
+  Func("main", {}, ty.void_(), {},
+       {create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: the workgroup_size attribute is only valid for "
+            "compute stages");
+}
+
+TEST_F(WorkgroupAttribute, NotAComputeShader) {
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: the workgroup_size attribute is only valid for "
+            "compute stages");
+}
+
+TEST_F(WorkgroupAttribute, DuplicateAttribute) {
+  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Source{{12, 34}}, 1, nullptr, nullptr),
+           WorkgroupSize(Source{{56, 78}}, 2, nullptr, nullptr),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate workgroup_size attribute
+12:34 note: first attribute declared here)");
+}
+
+}  // namespace
+}  // namespace WorkgroupAttributeTests
+
+namespace InterpolateTests {
+namespace {
+
+using InterpolateTest = ResolverTest;
+
+struct Params {
+  ast::InterpolationType type;
+  ast::InterpolationSampling sampling;
+  bool should_pass;
+};
+
+struct TestWithParams : ResolverTestWithParam<Params> {};
+
+using InterpolateParameterTest = TestWithParams;
+TEST_P(InterpolateParameterTest, All) {
+  auto& params = GetParam();
+
+  Func("main",
+       ast::VariableList{Param(
+           "a", ty.f32(),
+           {Location(0),
+            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
+       ty.void_(), {},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: flat interpolation attribute must not have a "
+              "sampling parameter");
+  }
+}
+
+TEST_P(InterpolateParameterTest, IntegerScalar) {
+  auto& params = GetParam();
+
+  Func("main",
+       ast::VariableList{Param(
+           "a", ty.i32(),
+           {Location(0),
+            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
+       ty.void_(), {},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  if (params.type != ast::InterpolationType::kFlat) {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: interpolation type must be 'flat' for integral "
+              "user-defined IO types");
+  } else if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: flat interpolation attribute must not have a "
+              "sampling parameter");
+  }
+}
+
+TEST_P(InterpolateParameterTest, IntegerVector) {
+  auto& params = GetParam();
+
+  Func("main",
+       ast::VariableList{Param(
+           "a", ty.vec4<u32>(),
+           {Location(0),
+            Interpolate(Source{{12, 34}}, params.type, params.sampling)})},
+       ty.void_(), {},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  if (params.type != ast::InterpolationType::kFlat) {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: interpolation type must be 'flat' for integral "
+              "user-defined IO types");
+  } else if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: flat interpolation attribute must not have a "
+              "sampling parameter");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverAttributeValidationTest,
+    InterpolateParameterTest,
+    testing::Values(Params{ast::InterpolationType::kPerspective,
+                           ast::InterpolationSampling::kNone, true},
+                    Params{ast::InterpolationType::kPerspective,
+                           ast::InterpolationSampling::kCenter, true},
+                    Params{ast::InterpolationType::kPerspective,
+                           ast::InterpolationSampling::kCentroid, true},
+                    Params{ast::InterpolationType::kPerspective,
+                           ast::InterpolationSampling::kSample, true},
+                    Params{ast::InterpolationType::kLinear,
+                           ast::InterpolationSampling::kNone, true},
+                    Params{ast::InterpolationType::kLinear,
+                           ast::InterpolationSampling::kCenter, true},
+                    Params{ast::InterpolationType::kLinear,
+                           ast::InterpolationSampling::kCentroid, true},
+                    Params{ast::InterpolationType::kLinear,
+                           ast::InterpolationSampling::kSample, true},
+                    // flat interpolation must not have a sampling type
+                    Params{ast::InterpolationType::kFlat,
+                           ast::InterpolationSampling::kNone, true},
+                    Params{ast::InterpolationType::kFlat,
+                           ast::InterpolationSampling::kCenter, false},
+                    Params{ast::InterpolationType::kFlat,
+                           ast::InterpolationSampling::kCentroid, false},
+                    Params{ast::InterpolationType::kFlat,
+                           ast::InterpolationSampling::kSample, false}));
+
+TEST_F(InterpolateTest, FragmentInput_Integer_MissingFlatInterpolation) {
+  Func("main",
+       ast::VariableList{Param(Source{{12, 34}}, "a", ty.i32(), {Location(0)})},
+       ty.void_(), {},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: integral user-defined fragment inputs must have a flat interpolation attribute)");
+}
+
+TEST_F(InterpolateTest, VertexOutput_Integer_MissingFlatInterpolation) {
+  auto* s = Structure(
+      "S",
+      {
+          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+          Member(Source{{12, 34}}, "u", ty.u32(), {Location(0)}),
+      },
+      {});
+  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: integral user-defined vertex outputs must have a flat interpolation attribute
+note: while analysing entry point 'main')");
+}
+
+TEST_F(InterpolateTest, MissingLocationAttribute_Parameter) {
+  Func("main",
+       ast::VariableList{
+           Param("a", ty.vec4<f32>(),
+                 {Builtin(ast::Builtin::kPosition),
+                  Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
+                              ast::InterpolationSampling::kNone)})},
+       ty.void_(), {},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: interpolate attribute must only be used with @location)");
+}
+
+TEST_F(InterpolateTest, MissingLocationAttribute_ReturnType) {
+  Func("main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition),
+        Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
+                    ast::InterpolationSampling::kNone)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: interpolate attribute must only be used with @location)");
+}
+
+TEST_F(InterpolateTest, MissingLocationAttribute_Struct) {
+  Structure(
+      "S", {Member("a", ty.f32(),
+                   {Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
+                                ast::InterpolationSampling::kNone)})});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: interpolate attribute must only be used with @location)");
+}
+
+}  // namespace
+}  // namespace InterpolateTests
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/bitcast_validation_test.cc b/src/tint/resolver/bitcast_validation_test.cc
new file mode 100644
index 0000000..d13ffd9
--- /dev/null
+++ b/src/tint/resolver/bitcast_validation_test.cc
@@ -0,0 +1,228 @@
+// 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/ast/bitcast_expression.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct Type {
+  template <typename T>
+  static constexpr Type Create() {
+    return Type{builder::DataType<T>::AST, builder::DataType<T>::Sem,
+                builder::DataType<T>::Expr};
+  }
+
+  builder::ast_type_func_ptr ast;
+  builder::sem_type_func_ptr sem;
+  builder::ast_expr_func_ptr expr;
+};
+
+static constexpr Type kNumericScalars[] = {
+    Type::Create<builder::f32>(),
+    Type::Create<builder::i32>(),
+    Type::Create<builder::u32>(),
+};
+static constexpr Type kVec2NumericScalars[] = {
+    Type::Create<builder::vec2<builder::f32>>(),
+    Type::Create<builder::vec2<builder::i32>>(),
+    Type::Create<builder::vec2<builder::u32>>(),
+};
+static constexpr Type kVec3NumericScalars[] = {
+    Type::Create<builder::vec3<builder::f32>>(),
+    Type::Create<builder::vec3<builder::i32>>(),
+    Type::Create<builder::vec3<builder::u32>>(),
+};
+static constexpr Type kVec4NumericScalars[] = {
+    Type::Create<builder::vec4<builder::f32>>(),
+    Type::Create<builder::vec4<builder::i32>>(),
+    Type::Create<builder::vec4<builder::u32>>(),
+};
+static constexpr Type kInvalid[] = {
+    // A non-exhaustive selection of uncastable types
+    Type::Create<bool>(),
+    Type::Create<builder::vec2<bool>>(),
+    Type::Create<builder::vec3<bool>>(),
+    Type::Create<builder::vec4<bool>>(),
+    Type::Create<builder::array<2, builder::i32>>(),
+    Type::Create<builder::array<3, builder::u32>>(),
+    Type::Create<builder::array<4, builder::f32>>(),
+    Type::Create<builder::array<5, bool>>(),
+    Type::Create<builder::mat2x2<builder::f32>>(),
+    Type::Create<builder::mat3x3<builder::f32>>(),
+    Type::Create<builder::mat4x4<builder::f32>>(),
+    Type::Create<builder::ptr<builder::i32>>(),
+    Type::Create<builder::ptr<builder::array<2, builder::i32>>>(),
+    Type::Create<builder::ptr<builder::mat2x2<builder::f32>>>(),
+};
+
+using ResolverBitcastValidationTest =
+    ResolverTestWithParam<std::tuple<Type, Type>>;
+
+////////////////////////////////////////////////////////////////////////////////
+// Valid bitcasts
+////////////////////////////////////////////////////////////////////////////////
+using ResolverBitcastValidationTestPass = ResolverBitcastValidationTest;
+TEST_P(ResolverBitcastValidationTestPass, Test) {
+  auto src = std::get<0>(GetParam());
+  auto dst = std::get<1>(GetParam());
+
+  auto* cast = Bitcast(dst.ast(*this), src.expr(*this, 0));
+  WrapInFunction(cast);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(cast), dst.sem(*this));
+}
+INSTANTIATE_TEST_SUITE_P(Scalars,
+                         ResolverBitcastValidationTestPass,
+                         testing::Combine(testing::ValuesIn(kNumericScalars),
+                                          testing::ValuesIn(kNumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec2,
+    ResolverBitcastValidationTestPass,
+    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
+                     testing::ValuesIn(kVec2NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec3,
+    ResolverBitcastValidationTestPass,
+    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
+                     testing::ValuesIn(kVec3NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec4,
+    ResolverBitcastValidationTestPass,
+    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
+                     testing::ValuesIn(kVec4NumericScalars)));
+
+////////////////////////////////////////////////////////////////////////////////
+// Invalid source type for bitcasts
+////////////////////////////////////////////////////////////////////////////////
+using ResolverBitcastValidationTestInvalidSrcTy = ResolverBitcastValidationTest;
+TEST_P(ResolverBitcastValidationTestInvalidSrcTy, Test) {
+  auto src = std::get<0>(GetParam());
+  auto dst = std::get<1>(GetParam());
+
+  auto* cast = Bitcast(dst.ast(*this), Expr(Source{{12, 34}}, "src"));
+  WrapInFunction(Const("src", nullptr, src.expr(*this, 0)), cast);
+
+  auto expected = "12:34 error: '" + src.sem(*this)->FriendlyName(Symbols()) +
+                  "' cannot be bitcast";
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), expected);
+}
+INSTANTIATE_TEST_SUITE_P(Scalars,
+                         ResolverBitcastValidationTestInvalidSrcTy,
+                         testing::Combine(testing::ValuesIn(kInvalid),
+                                          testing::ValuesIn(kNumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec2,
+    ResolverBitcastValidationTestInvalidSrcTy,
+    testing::Combine(testing::ValuesIn(kInvalid),
+                     testing::ValuesIn(kVec2NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec3,
+    ResolverBitcastValidationTestInvalidSrcTy,
+    testing::Combine(testing::ValuesIn(kInvalid),
+                     testing::ValuesIn(kVec3NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec4,
+    ResolverBitcastValidationTestInvalidSrcTy,
+    testing::Combine(testing::ValuesIn(kInvalid),
+                     testing::ValuesIn(kVec4NumericScalars)));
+
+////////////////////////////////////////////////////////////////////////////////
+// Invalid target type for bitcasts
+////////////////////////////////////////////////////////////////////////////////
+using ResolverBitcastValidationTestInvalidDstTy = ResolverBitcastValidationTest;
+TEST_P(ResolverBitcastValidationTestInvalidDstTy, Test) {
+  auto src = std::get<0>(GetParam());
+  auto dst = std::get<1>(GetParam());
+
+  // Use an alias so we can put a Source on the bitcast type
+  Alias("T", dst.ast(*this));
+  WrapInFunction(
+      Bitcast(ty.type_name(Source{{12, 34}}, "T"), src.expr(*this, 0)));
+
+  auto expected = "12:34 error: cannot bitcast to '" +
+                  dst.sem(*this)->FriendlyName(Symbols()) + "'";
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), expected);
+}
+INSTANTIATE_TEST_SUITE_P(Scalars,
+                         ResolverBitcastValidationTestInvalidDstTy,
+                         testing::Combine(testing::ValuesIn(kNumericScalars),
+                                          testing::ValuesIn(kInvalid)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec2,
+    ResolverBitcastValidationTestInvalidDstTy,
+    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
+                     testing::ValuesIn(kInvalid)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec3,
+    ResolverBitcastValidationTestInvalidDstTy,
+    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
+                     testing::ValuesIn(kInvalid)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec4,
+    ResolverBitcastValidationTestInvalidDstTy,
+    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
+                     testing::ValuesIn(kInvalid)));
+
+////////////////////////////////////////////////////////////////////////////////
+// Incompatible bitcast, but both src and dst types are valid
+////////////////////////////////////////////////////////////////////////////////
+using ResolverBitcastValidationTestIncompatible = ResolverBitcastValidationTest;
+TEST_P(ResolverBitcastValidationTestIncompatible, Test) {
+  auto src = std::get<0>(GetParam());
+  auto dst = std::get<1>(GetParam());
+
+  WrapInFunction(Bitcast(Source{{12, 34}}, dst.ast(*this), src.expr(*this, 0)));
+
+  auto expected = "12:34 error: cannot bitcast from '" +
+                  src.sem(*this)->FriendlyName(Symbols()) + "' to '" +
+                  dst.sem(*this)->FriendlyName(Symbols()) + "'";
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), expected);
+}
+INSTANTIATE_TEST_SUITE_P(
+    ScalarToVec2,
+    ResolverBitcastValidationTestIncompatible,
+    testing::Combine(testing::ValuesIn(kNumericScalars),
+                     testing::ValuesIn(kVec2NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec2ToVec3,
+    ResolverBitcastValidationTestIncompatible,
+    testing::Combine(testing::ValuesIn(kVec2NumericScalars),
+                     testing::ValuesIn(kVec3NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec3ToVec4,
+    ResolverBitcastValidationTestIncompatible,
+    testing::Combine(testing::ValuesIn(kVec3NumericScalars),
+                     testing::ValuesIn(kVec4NumericScalars)));
+INSTANTIATE_TEST_SUITE_P(
+    Vec4ToScalar,
+    ResolverBitcastValidationTestIncompatible,
+    testing::Combine(testing::ValuesIn(kVec4NumericScalars),
+                     testing::ValuesIn(kNumericScalars)));
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
new file mode 100644
index 0000000..8914783
--- /dev/null
+++ b/src/tint/resolver/builtin_test.cc
@@ -0,0 +1,2061 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using BuiltinType = sem::BuiltinType;
+
+using ResolverBuiltinTest = ResolverTest;
+
+using ResolverBuiltinDerivativeTest = ResolverTestWithParam<std::string>;
+TEST_P(ResolverBuiltinDerivativeTest, Scalar) {
+  auto name = GetParam();
+
+  Global("ident", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "ident");
+  Func("func", {}, ty.void_(), {Ignore(expr)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
+}
+
+TEST_P(ResolverBuiltinDerivativeTest, Vector) {
+  auto name = GetParam();
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "ident");
+  Func("func", {}, ty.void_(), {Ignore(expr)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_P(ResolverBuiltinDerivativeTest, MissingParam) {
+  auto name = GetParam();
+
+  auto* expr = Call(name);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> f32\n  " + name +
+                              "(vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverBuiltinDerivativeTest,
+                         testing::Values("dpdx",
+                                         "dpdxCoarse",
+                                         "dpdxFine",
+                                         "dpdy",
+                                         "dpdyCoarse",
+                                         "dpdyFine",
+                                         "fwidth",
+                                         "fwidthCoarse",
+                                         "fwidthFine"));
+
+using ResolverBuiltinTest_BoolMethod = ResolverTestWithParam<std::string>;
+TEST_P(ResolverBuiltinTest_BoolMethod, Scalar) {
+  auto name = GetParam();
+
+  Global("my_var", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
+}
+TEST_P(ResolverBuiltinTest_BoolMethod, Vector) {
+  auto name = GetParam();
+
+  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverBuiltinTest_BoolMethod,
+                         testing::Values("any", "all"));
+
+using ResolverBuiltinTest_FloatMethod = ResolverTestWithParam<std::string>;
+TEST_P(ResolverBuiltinTest_FloatMethod, Vector) {
+  auto name = GetParam();
+
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_FloatMethod, Scalar) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::Bool>());
+}
+
+TEST_P(ResolverBuiltinTest_FloatMethod, MissingParam) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> bool\n  " + name +
+                              "(vecN<f32>) -> vecN<bool>\n");
+}
+
+TEST_P(ResolverBuiltinTest_FloatMethod, TooManyParams) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call(name, "my_var", 1.23f);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "(f32, f32)\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> bool\n  " + name +
+                              "(vecN<f32>) -> vecN<bool>\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_FloatMethod,
+    testing::Values("isInf", "isNan", "isFinite", "isNormal"));
+
+enum class Texture { kF32, kI32, kU32 };
+inline std::ostream& operator<<(std::ostream& out, Texture data) {
+  if (data == Texture::kF32) {
+    out << "f32";
+  } else if (data == Texture::kI32) {
+    out << "i32";
+  } else {
+    out << "u32";
+  }
+  return out;
+}
+
+struct TextureTestParams {
+  ast::TextureDimension dim;
+  Texture type = Texture::kF32;
+  ast::TexelFormat format = ast::TexelFormat::kR32Float;
+};
+inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
+  out << data.dim << "_" << data.type;
+  return out;
+}
+
+class ResolverBuiltinTest_TextureOperation
+    : public ResolverTestWithParam<TextureTestParams> {
+ public:
+  /// Gets an appropriate type for the coords parameter depending the the
+  /// dimensionality of the texture being sampled.
+  /// @param dim dimensionality of the texture being sampled
+  /// @param scalar the scalar type
+  /// @returns a pointer to a type appropriate for the coord param
+  const ast::Type* GetCoordsType(ast::TextureDimension dim,
+                                 const ast::Type* scalar) {
+    switch (dim) {
+      case ast::TextureDimension::k1d:
+        return scalar;
+      case ast::TextureDimension::k2d:
+      case ast::TextureDimension::k2dArray:
+        return ty.vec(scalar, 2);
+      case ast::TextureDimension::k3d:
+      case ast::TextureDimension::kCube:
+      case ast::TextureDimension::kCubeArray:
+        return ty.vec(scalar, 3);
+      default:
+        [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
+    }
+    return nullptr;
+  }
+
+  void add_call_param(std::string name,
+                      const ast::Type* type,
+                      ast::ExpressionList* call_params) {
+    if (type->IsAnyOf<ast::Texture, ast::Sampler>()) {
+      Global(name, type,
+             ast::AttributeList{
+                 create<ast::BindingAttribute>(0),
+                 create<ast::GroupAttribute>(0),
+             });
+
+    } else {
+      Global(name, type, ast::StorageClass::kPrivate);
+    }
+
+    call_params->push_back(Expr(name));
+  }
+  const ast::Type* subtype(Texture type) {
+    if (type == Texture::kF32) {
+      return ty.f32();
+    }
+    if (type == Texture::kI32) {
+      return ty.i32();
+    }
+    return ty.u32();
+  }
+};
+
+using ResolverBuiltinTest_SampledTextureOperation =
+    ResolverBuiltinTest_TextureOperation;
+TEST_P(ResolverBuiltinTest_SampledTextureOperation, TextureLoadSampled) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  auto* s = subtype(type);
+  auto* coords_type = GetCoordsType(dim, ty.i32());
+  auto* texture_type = ty.sampled_texture(dim, s);
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("coords", coords_type, &call_params);
+  if (dim == ast::TextureDimension::k2dArray) {
+    add_call_param("array_index", ty.i32(), &call_params);
+  }
+  add_call_param("level", ty.i32(), &call_params);
+
+  auto* expr = Call("textureLoad", call_params);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Vector>());
+  if (type == Texture::kF32) {
+    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
+  } else if (type == Texture::kI32) {
+    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::I32>());
+  } else {
+    EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::U32>());
+  }
+  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 4u);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_SampledTextureOperation,
+    testing::Values(TextureTestParams{ast::TextureDimension::k1d},
+                    TextureTestParams{ast::TextureDimension::k2d},
+                    TextureTestParams{ast::TextureDimension::k2dArray},
+                    TextureTestParams{ast::TextureDimension::k3d}));
+
+TEST_F(ResolverBuiltinTest, Dot_Vec2) {
+  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Dot_Vec3) {
+  Global("my_var", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::I32>());
+}
+
+TEST_F(ResolverBuiltinTest, Dot_Vec4) {
+  Global("my_var", ty.vec4<u32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::U32>());
+}
+
+TEST_F(ResolverBuiltinTest, Dot_Error_Scalar) {
+  auto* expr = Call("dot", 1.0f, 1.0f);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to dot(f32, f32)
+
+1 candidate function:
+  dot(vecN<T>, vecN<T>) -> T  where: T is f32, i32 or u32
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Select) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("select", "my_var", "my_var", "bool_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::Vector>());
+  EXPECT_EQ(TypeOf(expr)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(TypeOf(expr)->As<sem::Vector>()->type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Select_Error_NoParams) {
+  auto* expr = Call("select");
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select()
+
+3 candidate functions:
+  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Select_Error_SelectorInt) {
+  auto* expr = Call("select", 1, 1, 1);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(i32, i32, i32)
+
+3 candidate functions:
+  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Select_Error_Matrix) {
+  auto* expr = Call(
+      "select", mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)),
+      mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)), Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool)
+
+3 candidate functions:
+  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Select_Error_MismatchTypes) {
+  auto* expr = Call("select", 1.0f, vec2<f32>(2.0f, 3.0f), Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(f32, vec2<f32>, bool)
+
+3 candidate functions:
+  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Select_Error_MismatchVectorSize) {
+  auto* expr = Call("select", vec2<f32>(1.0f, 2.0f),
+                    vec3<f32>(3.0f, 4.0f, 5.0f), Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool)
+
+3 candidate functions:
+  select(T, T, bool) -> T  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, bool) -> vecN<T>  where: T is f32, i32, u32 or bool
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is f32, i32, u32 or bool
+)");
+}
+
+struct BuiltinData {
+  const char* name;
+  BuiltinType builtin;
+};
+
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.name;
+  return out;
+}
+
+using ResolverBuiltinTest_Barrier = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_Barrier, InferType) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(CallStmt(call));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::Void>());
+}
+
+TEST_P(ResolverBuiltinTest_Barrier, Error_TooManyParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f);
+  WrapInFunction(CallStmt(call));
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_Barrier,
+    testing::Values(BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
+                    BuiltinData{"workgroupBarrier",
+                                BuiltinType::kWorkgroupBarrier}));
+
+using ResolverBuiltinTest_DataPacking = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_DataPacking, InferType) {
+  auto param = GetParam();
+
+  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
+               param.builtin == BuiltinType::kPack4x8unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f))
+                     : Call(param.name, vec2<f32>(1.f, 2.f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
+}
+
+TEST_P(ResolverBuiltinTest_DataPacking, Error_IncorrectParamType) {
+  auto param = GetParam();
+
+  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
+               param.builtin == BuiltinType::kPack4x8unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<i32>(1, 2, 3, 4))
+                     : Call(param.name, vec2<i32>(1, 2));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+TEST_P(ResolverBuiltinTest_DataPacking, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+TEST_P(ResolverBuiltinTest_DataPacking, Error_TooManyParams) {
+  auto param = GetParam();
+
+  bool pack4 = param.builtin == BuiltinType::kPack4x8snorm ||
+               param.builtin == BuiltinType::kPack4x8unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f)
+                     : Call(param.name, vec2<f32>(1.f, 2.f), 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_DataPacking,
+    testing::Values(BuiltinData{"pack4x8snorm", BuiltinType::kPack4x8snorm},
+                    BuiltinData{"pack4x8unorm", BuiltinType::kPack4x8unorm},
+                    BuiltinData{"pack2x16snorm", BuiltinType::kPack2x16snorm},
+                    BuiltinData{"pack2x16unorm", BuiltinType::kPack2x16unorm},
+                    BuiltinData{"pack2x16float", BuiltinType::kPack2x16float}));
+
+using ResolverBuiltinTest_DataUnpacking = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_DataUnpacking, InferType) {
+  auto param = GetParam();
+
+  bool pack4 = param.builtin == BuiltinType::kUnpack4x8snorm ||
+               param.builtin == BuiltinType::kUnpack4x8unorm;
+
+  auto* call = Call(param.name, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  if (pack4) {
+    EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 4u);
+  } else {
+    EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 2u);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_DataUnpacking,
+    testing::Values(
+        BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4x8snorm},
+        BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4x8unorm},
+        BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2x16snorm},
+        BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2x16unorm},
+        BuiltinData{"unpack2x16float", BuiltinType::kUnpack2x16float}));
+
+using ResolverBuiltinTest_SingleParam = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_SingleParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32) -> f32\n  " +
+                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam, Error_TooManyParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 2, 3);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "(i32, i32, i32)\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32) -> f32\n  " +
+                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_SingleParam,
+    testing::Values(BuiltinData{"acos", BuiltinType::kAcos},
+                    BuiltinData{"asin", BuiltinType::kAsin},
+                    BuiltinData{"atan", BuiltinType::kAtan},
+                    BuiltinData{"ceil", BuiltinType::kCeil},
+                    BuiltinData{"cos", BuiltinType::kCos},
+                    BuiltinData{"cosh", BuiltinType::kCosh},
+                    BuiltinData{"exp", BuiltinType::kExp},
+                    BuiltinData{"exp2", BuiltinType::kExp2},
+                    BuiltinData{"floor", BuiltinType::kFloor},
+                    BuiltinData{"fract", BuiltinType::kFract},
+                    BuiltinData{"inverseSqrt", BuiltinType::kInverseSqrt},
+                    BuiltinData{"log", BuiltinType::kLog},
+                    BuiltinData{"log2", BuiltinType::kLog2},
+                    BuiltinData{"round", BuiltinType::kRound},
+                    BuiltinData{"sign", BuiltinType::kSign},
+                    BuiltinData{"sin", BuiltinType::kSin},
+                    BuiltinData{"sinh", BuiltinType::kSinh},
+                    BuiltinData{"sqrt", BuiltinType::kSqrt},
+                    BuiltinData{"tan", BuiltinType::kTan},
+                    BuiltinData{"tanh", BuiltinType::kTanh},
+                    BuiltinData{"trunc", BuiltinType::kTrunc}));
+
+using ResolverBuiltinDataTest = ResolverTest;
+
+TEST_F(ResolverBuiltinDataTest, ArrayLength_Vector) {
+  auto* ary = ty.array<i32>();
+  auto* str =
+      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  auto* call = Call("arrayLength", AddressOf(MemberAccessor("a", "x")));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
+}
+
+TEST_F(ResolverBuiltinDataTest, ArrayLength_Error_ArraySized) {
+  Global("arr", ty.array<int, 4>(), ast::StorageClass::kPrivate);
+  auto* call = Call("arrayLength", AddressOf("arr"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to arrayLength(ptr<private, array<i32, 4>, read_write>)
+
+1 candidate function:
+  arrayLength(ptr<storage, array<T>, A>) -> u32
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Normalize_Vector) {
+  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_F(ResolverBuiltinDataTest, Normalize_Error_NoParams) {
+  auto* call = Call("normalize");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
+
+1 candidate function:
+  normalize(vecN<f32>) -> vecN<f32>
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, FrexpScalar) {
+  auto* call = Call("frexp", 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  auto* ty = TypeOf(call)->As<sem::Struct>();
+  ASSERT_NE(ty, nullptr);
+  ASSERT_EQ(ty->Members().size(), 2u);
+
+  auto* sig = ty->Members()[0];
+  EXPECT_TRUE(sig->Type()->Is<sem::F32>());
+  EXPECT_EQ(sig->Offset(), 0u);
+  EXPECT_EQ(sig->Size(), 4u);
+  EXPECT_EQ(sig->Align(), 4u);
+  EXPECT_EQ(sig->Name(), Sym("sig"));
+
+  auto* exp = ty->Members()[1];
+  EXPECT_TRUE(exp->Type()->Is<sem::I32>());
+  EXPECT_EQ(exp->Offset(), 4u);
+  EXPECT_EQ(exp->Size(), 4u);
+  EXPECT_EQ(exp->Align(), 4u);
+  EXPECT_EQ(exp->Name(), Sym("exp"));
+
+  EXPECT_EQ(ty->Size(), 8u);
+  EXPECT_EQ(ty->SizeNoPadding(), 8u);
+}
+
+TEST_F(ResolverBuiltinDataTest, FrexpVector) {
+  auto* call = Call("frexp", vec3<f32>());
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  auto* ty = TypeOf(call)->As<sem::Struct>();
+  ASSERT_NE(ty, nullptr);
+  ASSERT_EQ(ty->Members().size(), 2u);
+
+  auto* sig = ty->Members()[0];
+  ASSERT_TRUE(sig->Type()->Is<sem::Vector>());
+  EXPECT_EQ(sig->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(sig->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sig->Offset(), 0u);
+  EXPECT_EQ(sig->Size(), 12u);
+  EXPECT_EQ(sig->Align(), 16u);
+  EXPECT_EQ(sig->Name(), Sym("sig"));
+
+  auto* exp = ty->Members()[1];
+  ASSERT_TRUE(exp->Type()->Is<sem::Vector>());
+  EXPECT_EQ(exp->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(exp->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(exp->Offset(), 16u);
+  EXPECT_EQ(exp->Size(), 12u);
+  EXPECT_EQ(exp->Align(), 16u);
+  EXPECT_EQ(exp->Name(), Sym("exp"));
+
+  EXPECT_EQ(ty->Size(), 32u);
+  EXPECT_EQ(ty->SizeNoPadding(), 28u);
+}
+
+TEST_F(ResolverBuiltinDataTest, Frexp_Error_FirstParamInt) {
+  Global("v", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", 1, AddressOf("v"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to frexp(i32, ptr<workgroup, i32, read_write>)
+
+2 candidate functions:
+  frexp(f32) -> __frexp_result
+  frexp(vecN<f32>) -> __frexp_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Frexp_Error_SecondParamFloatPtr) {
+  Global("v", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", 1.0f, AddressOf("v"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to frexp(f32, ptr<workgroup, f32, read_write>)
+
+2 candidate functions:
+  frexp(f32) -> __frexp_result
+  frexp(vecN<f32>) -> __frexp_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Frexp_Error_SecondParamNotAPointer) {
+  auto* call = Call("frexp", 1.0f, 1);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to frexp(f32, i32)
+
+2 candidate functions:
+  frexp(f32) -> __frexp_result
+  frexp(vecN<f32>) -> __frexp_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Frexp_Error_VectorSizesDontMatch) {
+  Global("v", ty.vec4<i32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", vec2<f32>(1.0f, 2.0f), AddressOf("v"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to frexp(vec2<f32>, ptr<workgroup, vec4<i32>, read_write>)
+
+2 candidate functions:
+  frexp(vecN<f32>) -> __frexp_result_vecN
+  frexp(f32) -> __frexp_result
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, ModfScalar) {
+  auto* call = Call("modf", 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  auto* ty = TypeOf(call)->As<sem::Struct>();
+  ASSERT_NE(ty, nullptr);
+  ASSERT_EQ(ty->Members().size(), 2u);
+
+  auto* fract = ty->Members()[0];
+  EXPECT_TRUE(fract->Type()->Is<sem::F32>());
+  EXPECT_EQ(fract->Offset(), 0u);
+  EXPECT_EQ(fract->Size(), 4u);
+  EXPECT_EQ(fract->Align(), 4u);
+  EXPECT_EQ(fract->Name(), Sym("fract"));
+
+  auto* whole = ty->Members()[1];
+  EXPECT_TRUE(whole->Type()->Is<sem::F32>());
+  EXPECT_EQ(whole->Offset(), 4u);
+  EXPECT_EQ(whole->Size(), 4u);
+  EXPECT_EQ(whole->Align(), 4u);
+  EXPECT_EQ(whole->Name(), Sym("whole"));
+
+  EXPECT_EQ(ty->Size(), 8u);
+  EXPECT_EQ(ty->SizeNoPadding(), 8u);
+}
+
+TEST_F(ResolverBuiltinDataTest, ModfVector) {
+  auto* call = Call("modf", vec3<f32>());
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  auto* ty = TypeOf(call)->As<sem::Struct>();
+  ASSERT_NE(ty, nullptr);
+  ASSERT_EQ(ty->Members().size(), 2u);
+
+  auto* fract = ty->Members()[0];
+  ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
+  EXPECT_EQ(fract->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(fract->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(fract->Offset(), 0u);
+  EXPECT_EQ(fract->Size(), 12u);
+  EXPECT_EQ(fract->Align(), 16u);
+  EXPECT_EQ(fract->Name(), Sym("fract"));
+
+  auto* whole = ty->Members()[1];
+  ASSERT_TRUE(whole->Type()->Is<sem::Vector>());
+  EXPECT_EQ(whole->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(whole->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(whole->Offset(), 16u);
+  EXPECT_EQ(whole->Size(), 12u);
+  EXPECT_EQ(whole->Align(), 16u);
+  EXPECT_EQ(whole->Name(), Sym("whole"));
+
+  EXPECT_EQ(ty->Size(), 32u);
+  EXPECT_EQ(ty->SizeNoPadding(), 28u);
+}
+
+TEST_F(ResolverBuiltinDataTest, Modf_Error_FirstParamInt) {
+  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", 1, AddressOf("whole"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to modf(i32, ptr<workgroup, f32, read_write>)
+
+2 candidate functions:
+  modf(f32) -> __modf_result
+  modf(vecN<f32>) -> __modf_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Modf_Error_SecondParamIntPtr) {
+  Global("whole", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", 1.0f, AddressOf("whole"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to modf(f32, ptr<workgroup, i32, read_write>)
+
+2 candidate functions:
+  modf(f32) -> __modf_result
+  modf(vecN<f32>) -> __modf_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Modf_Error_SecondParamNotAPointer) {
+  auto* call = Call("modf", 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to modf(f32, f32)
+
+2 candidate functions:
+  modf(f32) -> __modf_result
+  modf(vecN<f32>) -> __modf_result_vecN
+)");
+}
+
+TEST_F(ResolverBuiltinDataTest, Modf_Error_VectorSizesDontMatch) {
+  Global("whole", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", vec2<f32>(1.0f, 2.0f), AddressOf("whole"));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: no matching call to modf(vec2<f32>, ptr<workgroup, vec4<f32>, read_write>)
+
+2 candidate functions:
+  modf(vecN<f32>) -> __modf_result_vecN
+  modf(f32) -> __modf_result
+)");
+}
+
+using ResolverBuiltinTest_SingleParam_FloatOrInt =
+    ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Float_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Float_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Sint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, -1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Sint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Uint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Uint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_SingleParam_FloatOrInt, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverBuiltinTest_SingleParam_FloatOrInt,
+                         testing::Values(BuiltinData{"abs",
+                                                     BuiltinType::kAbs}));
+
+TEST_F(ResolverBuiltinTest, Length_Scalar) {
+  auto* call = Call("length", 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_F(ResolverBuiltinTest, Length_FloatVector) {
+  auto* call = Call("length", vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+using ResolverBuiltinTest_TwoParam = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_TwoParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_TwoParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_TwoParam, Error_NoTooManyParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 2, 3);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "(i32, i32, i32)\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32, f32) -> f32\n  " +
+                std::string(param.name) +
+                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
+}
+
+TEST_P(ResolverBuiltinTest_TwoParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32, f32) -> f32\n  " +
+                std::string(param.name) +
+                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_TwoParam,
+    testing::Values(BuiltinData{"atan2", BuiltinType::kAtan2},
+                    BuiltinData{"pow", BuiltinType::kPow},
+                    BuiltinData{"step", BuiltinType::kStep}));
+
+TEST_F(ResolverBuiltinTest, Distance_Scalar) {
+  auto* call = Call("distance", 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_F(ResolverBuiltinTest, Distance_Vector) {
+  auto* call = Call("distance", vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Cross) {
+  auto* call =
+      Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_F(ResolverBuiltinTest, Cross_Error_NoArgs) {
+  auto* call = Call("cross");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to cross()
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Cross_Error_Scalar) {
+  auto* call = Call("cross", 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to cross(f32, f32)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Cross_Error_Vec3Int) {
+  auto* call = Call("cross", vec3<i32>(1, 2, 3), vec3<i32>(1, 2, 3));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec3<i32>, vec3<i32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Cross_Error_Vec4) {
+  auto* call = Call("cross", vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f),
+                    vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
+
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec4<f32>, vec4<f32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Cross_Error_TooManyParams) {
+  auto* call = Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f),
+                    vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
+
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec3<f32>, vec3<f32>, vec3<f32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+TEST_F(ResolverBuiltinTest, Normalize) {
+  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_F(ResolverBuiltinTest, Normalize_NoArgs) {
+  auto* call = Call("normalize");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
+
+1 candidate function:
+  normalize(vecN<f32>) -> vecN<f32>
+)");
+}
+
+using ResolverBuiltinTest_ThreeParam = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_ThreeParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+TEST_P(ResolverBuiltinTest_ThreeParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name) + "()"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_ThreeParam,
+    testing::Values(BuiltinData{"mix", BuiltinType::kMix},
+                    BuiltinData{"smoothStep", BuiltinType::kSmoothStep},
+                    BuiltinData{"fma", BuiltinType::kFma}));
+
+using ResolverBuiltinTest_ThreeParam_FloatOrInt =
+    ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Float_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Float_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Sint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 1, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Sint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3),
+                    vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Uint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1u, 1u, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Uint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u),
+                    vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_ThreeParam_FloatOrInt, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T, T, T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>, vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 "
+                "or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverBuiltinTest_ThreeParam_FloatOrInt,
+                         testing::Values(BuiltinData{"clamp",
+                                                     BuiltinType::kClamp}));
+
+using ResolverBuiltinTest_Int_SingleParam = ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_Int_SingleParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_integer_scalar());
+}
+
+TEST_P(ResolverBuiltinTest_Int_SingleParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_Int_SingleParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " +
+                              std::string(param.name) +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              std::string(param.name) +
+                              "(T) -> T  where: T is i32 or u32\n  " +
+                              std::string(param.name) +
+                              "(vecN<T>) -> vecN<T>  where: T is i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_Int_SingleParam,
+    testing::Values(BuiltinData{"countOneBits", BuiltinType::kCountOneBits},
+                    BuiltinData{"reverseBits", BuiltinType::kReverseBits}));
+
+using ResolverBuiltinTest_FloatOrInt_TwoParam =
+    ResolverTestWithParam<BuiltinData>;
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Signed) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Unsigned) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1u, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::U32>());
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Scalar_Float) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Signed) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Unsigned) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Vector_Float) {
+  auto param = GetParam();
+
+  auto* call =
+      Call(param.name, vec3<f32>(1.f, 1.f, 3.f), vec3<f32>(1.f, 1.f, 3.f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_P(ResolverBuiltinTest_FloatOrInt_TwoParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T, T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverBuiltinTest_FloatOrInt_TwoParam,
+                         testing::Values(BuiltinData{"min", BuiltinType::kMin},
+                                         BuiltinData{"max",
+                                                     BuiltinType::kMax}));
+
+TEST_F(ResolverBuiltinTest, Determinant_2x2) {
+  Global("var", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Determinant_3x3) {
+  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Determinant_4x4) {
+  Global("var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinTest, Determinant_NotSquare) {
+  Global("var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to determinant(mat2x3<f32>)
+
+1 candidate function:
+  determinant(matNxN<f32>) -> f32
+)");
+}
+
+TEST_F(ResolverBuiltinTest, Determinant_NotMatrix) {
+  Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to determinant(f32)
+
+1 candidate function:
+  determinant(matNxN<f32>) -> f32
+)");
+}
+
+using ResolverBuiltinTest_Texture =
+    ResolverTestWithParam<ast::builtin::test::TextureOverloadCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_Texture,
+    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
+
+std::string to_str(const std::string& function,
+                   const sem::ParameterList& params) {
+  std::stringstream out;
+  out << function << "(";
+  bool first = true;
+  for (auto* param : params) {
+    if (!first) {
+      out << ", ";
+    }
+    out << sem::str(param->Usage());
+    first = false;
+  }
+  out << ")";
+  return out.str();
+}
+
+const char* expected_texture_overload(
+    ast::builtin::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+    case ValidTextureOverload::kDimensions2d:
+    case ValidTextureOverload::kDimensions2dArray:
+    case ValidTextureOverload::kDimensions3d:
+    case ValidTextureOverload::kDimensionsCube:
+    case ValidTextureOverload::kDimensionsCubeArray:
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+    case ValidTextureOverload::kDimensionsDepth2d:
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+    case ValidTextureOverload::kDimensionsDepthCube:
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return R"(textureDimensions(texture))";
+    case ValidTextureOverload::kGather2dF32:
+      return R"(textureGather(component, texture, sampler, coords))";
+    case ValidTextureOverload::kGather2dOffsetF32:
+      return R"(textureGather(component, texture, sampler, coords, offset))";
+    case ValidTextureOverload::kGather2dArrayF32:
+      return R"(textureGather(component, texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kGather2dArrayOffsetF32:
+      return R"(textureGather(component, texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kGatherCubeF32:
+      return R"(textureGather(component, texture, sampler, coords))";
+    case ValidTextureOverload::kGatherCubeArrayF32:
+      return R"(textureGather(component, texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kGatherDepth2dF32:
+      return R"(textureGather(texture, sampler, coords))";
+    case ValidTextureOverload::kGatherDepth2dOffsetF32:
+      return R"(textureGather(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kGatherDepth2dArrayF32:
+      return R"(textureGather(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
+      return R"(textureGather(texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kGatherDepthCubeF32:
+      return R"(textureGather(texture, sampler, coords))";
+    case ValidTextureOverload::kGatherDepthCubeArrayF32:
+      return R"(textureGather(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kGatherCompareDepth2dF32:
+      return R"(textureGatherCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
+      return R"(textureGatherCompare(texture, sampler, coords, depth_ref, offset))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
+      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
+      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref, offset))";
+    case ValidTextureOverload::kGatherCompareDepthCubeF32:
+      return R"(textureGatherCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
+      return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kNumLayers2dArray:
+    case ValidTextureOverload::kNumLayersCubeArray:
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return R"(textureNumLayers(texture))";
+    case ValidTextureOverload::kNumLevels2d:
+    case ValidTextureOverload::kNumLevels2dArray:
+    case ValidTextureOverload::kNumLevels3d:
+    case ValidTextureOverload::kNumLevelsCube:
+    case ValidTextureOverload::kNumLevelsCubeArray:
+    case ValidTextureOverload::kNumLevelsDepth2d:
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+    case ValidTextureOverload::kNumLevelsDepthCube:
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return R"(textureNumLevels(texture))";
+    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+      return R"(textureNumSamples(texture))";
+    case ValidTextureOverload::kDimensions2dLevel:
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+    case ValidTextureOverload::kDimensions3dLevel:
+    case ValidTextureOverload::kDimensionsCubeLevel:
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return R"(textureDimensions(texture, level))";
+    case ValidTextureOverload::kSample1dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(textureSample(texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(textureSample(texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias, offset))";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kLoad1dLevelF32:
+    case ValidTextureOverload::kLoad1dLevelU32:
+    case ValidTextureOverload::kLoad1dLevelI32:
+    case ValidTextureOverload::kLoad2dLevelF32:
+    case ValidTextureOverload::kLoad2dLevelU32:
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kLoad3dLevelF32:
+    case ValidTextureOverload::kLoad3dLevelU32:
+    case ValidTextureOverload::kLoad3dLevelI32:
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return R"(textureLoad(texture, coords, sample_index))";
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return R"(textureStore(texture, coords, value))";
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return R"(textureStore(texture, coords, array_index, value))";
+  }
+  return "<unmatched texture overload>";
+}
+
+TEST_P(ResolverBuiltinTest_Texture, Call) {
+  auto param = GetParam();
+
+  param.BuildTextureVariable(this);
+  param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  auto* stmt = CallStmt(call);
+  Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  if (std::string(param.function) == "textureDimensions") {
+    switch (param.texture_dimension) {
+      default:
+        FAIL() << "invalid texture dimensions: " << param.texture_dimension;
+      case ast::TextureDimension::k1d:
+        EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+        break;
+      case ast::TextureDimension::k2d:
+      case ast::TextureDimension::k2dArray:
+      case ast::TextureDimension::kCube:
+      case ast::TextureDimension::kCubeArray: {
+        auto* vec = As<sem::Vector>(TypeOf(call));
+        ASSERT_NE(vec, nullptr);
+        EXPECT_EQ(vec->Width(), 2u);
+        EXPECT_TRUE(vec->type()->Is<sem::I32>());
+        break;
+      }
+      case ast::TextureDimension::k3d: {
+        auto* vec = As<sem::Vector>(TypeOf(call));
+        ASSERT_NE(vec, nullptr);
+        EXPECT_EQ(vec->Width(), 3u);
+        EXPECT_TRUE(vec->type()->Is<sem::I32>());
+        break;
+      }
+    }
+  } else if (std::string(param.function) == "textureNumLayers") {
+    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+  } else if (std::string(param.function) == "textureNumLevels") {
+    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+  } else if (std::string(param.function) == "textureNumSamples") {
+    EXPECT_TRUE(TypeOf(call)->Is<sem::I32>());
+  } else if (std::string(param.function) == "textureStore") {
+    EXPECT_TRUE(TypeOf(call)->Is<sem::Void>());
+  } else if (std::string(param.function) == "textureGather") {
+    auto* vec = As<sem::Vector>(TypeOf(call));
+    ASSERT_NE(vec, nullptr);
+    EXPECT_EQ(vec->Width(), 4u);
+    switch (param.texture_data_type) {
+      case ast::builtin::test::TextureDataType::kF32:
+        EXPECT_TRUE(vec->type()->Is<sem::F32>());
+        break;
+      case ast::builtin::test::TextureDataType::kU32:
+        EXPECT_TRUE(vec->type()->Is<sem::U32>());
+        break;
+      case ast::builtin::test::TextureDataType::kI32:
+        EXPECT_TRUE(vec->type()->Is<sem::I32>());
+        break;
+    }
+  } else if (std::string(param.function) == "textureGatherCompare") {
+    auto* vec = As<sem::Vector>(TypeOf(call));
+    ASSERT_NE(vec, nullptr);
+    EXPECT_EQ(vec->Width(), 4u);
+    EXPECT_TRUE(vec->type()->Is<sem::F32>());
+  } else {
+    switch (param.texture_kind) {
+      case ast::builtin::test::TextureKind::kRegular:
+      case ast::builtin::test::TextureKind::kMultisampled:
+      case ast::builtin::test::TextureKind::kStorage: {
+        auto* vec = TypeOf(call)->As<sem::Vector>();
+        ASSERT_NE(vec, nullptr);
+        switch (param.texture_data_type) {
+          case ast::builtin::test::TextureDataType::kF32:
+            EXPECT_TRUE(vec->type()->Is<sem::F32>());
+            break;
+          case ast::builtin::test::TextureDataType::kU32:
+            EXPECT_TRUE(vec->type()->Is<sem::U32>());
+            break;
+          case ast::builtin::test::TextureDataType::kI32:
+            EXPECT_TRUE(vec->type()->Is<sem::I32>());
+            break;
+        }
+        break;
+      }
+      case ast::builtin::test::TextureKind::kDepth:
+      case ast::builtin::test::TextureKind::kDepthMultisampled: {
+        EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+        break;
+      }
+    }
+  }
+
+  auto* call_sem = Sem().Get(call);
+  ASSERT_NE(call_sem, nullptr);
+  auto* target = call_sem->Target();
+  ASSERT_NE(target, nullptr);
+
+  auto got = resolver::to_str(param.function, target->Parameters());
+  auto* expected = expected_texture_overload(param.overload);
+  EXPECT_EQ(got, expected);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/builtin_validation_test.cc b/src/tint/resolver/builtin_validation_test.cc
new file mode 100644
index 0000000..0ed2f50
--- /dev/null
+++ b/src/tint/resolver/builtin_validation_test.cc
@@ -0,0 +1,402 @@
+// 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/ast/builtin_texture_helper_test.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverBuiltinValidationTest = ResolverTest;
+
+TEST_F(ResolverBuiltinValidationTest,
+       FunctionTypeMustMatchReturnStatementType_void_fail) {
+  // fn func { return workgroupBarrier(); }
+  Func("func", {}, ty.void_(),
+       {
+           Return(Call(Source{Source::Location{12, 34}}, "workgroupBarrier")),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: builtin 'workgroupBarrier' does not return a value");
+}
+
+TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageDirect) {
+  // @stage(compute) @workgroup_size(1) fn func { return dpdx(1.0); }
+
+  auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
+                                           ast::ExpressionList{Expr(1.0f)});
+  Func(Source{{1, 2}}, "func", ast::VariableList{}, ty.void_(),
+       {CallStmt(dpdx)},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "3:4 error: built-in cannot be used by compute pipeline stage");
+}
+
+TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageIndirect) {
+  // fn f0 { return dpdx(1.0); }
+  // fn f1 { f0(); }
+  // fn f2 { f1(); }
+  // @stage(compute) @workgroup_size(1) fn main { return f2(); }
+
+  auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
+                                           ast::ExpressionList{Expr(1.0f)});
+  Func(Source{{1, 2}}, "f0", {}, ty.void_(), {CallStmt(dpdx)});
+
+  Func(Source{{3, 4}}, "f1", {}, ty.void_(), {CallStmt(Call("f0"))});
+
+  Func(Source{{5, 6}}, "f2", {}, ty.void_(), {CallStmt(Call("f1"))});
+
+  Func(Source{{7, 8}}, "main", {}, ty.void_(), {CallStmt(Call("f2"))},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(3:4 error: built-in cannot be used by compute pipeline stage
+1:2 note: called by function 'f0'
+3:4 note: called by function 'f1'
+5:6 note: called by function 'f2'
+7:8 note: called by entry point 'main')");
+}
+
+TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsFunction) {
+  Func(Source{{12, 34}}, "mix", {}, ty.i32(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a function)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalLet) {
+  GlobalConst(Source{{12, 34}}, "mix", ty.i32(), Expr(1));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope let)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalVar) {
+  Global(Source{{12, 34}}, "mix", ty.i32(), Expr(1),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope var)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsAlias) {
+  Alias(Source{{12, 34}}, "mix", ty.i32());
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as an alias)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsStruct) {
+  Structure(Source{{12, 34}}, "mix", {Member("m", ty.i32())});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a struct)");
+}
+
+namespace texture_constexpr_args {
+
+using TextureOverloadCase = ast::builtin::test::TextureOverloadCase;
+using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+using TextureKind = ast::builtin::test::TextureKind;
+using TextureDataType = ast::builtin::test::TextureDataType;
+using u32 = ProgramBuilder::u32;
+using i32 = ProgramBuilder::i32;
+using f32 = ProgramBuilder::f32;
+
+static std::vector<TextureOverloadCase> TextureCases(
+    std::unordered_set<ValidTextureOverload> overloads) {
+  std::vector<TextureOverloadCase> cases;
+  for (auto c : TextureOverloadCase::ValidCases()) {
+    if (overloads.count(c.overload)) {
+      cases.push_back(c);
+    }
+  }
+  return cases;
+}
+
+enum class Position {
+  kFirst,
+  kLast,
+};
+
+struct Parameter {
+  const char* const name;
+  const Position position;
+  int min;
+  int max;
+};
+
+class Constexpr {
+ public:
+  enum class Kind {
+    kScalar,
+    kVec2,
+    kVec3,
+    kVec3_Scalar_Vec2,
+    kVec3_Vec2_Scalar,
+    kEmptyVec2,
+    kEmptyVec3,
+  };
+
+  Constexpr(int32_t invalid_idx,
+            Kind k,
+            int32_t x = 0,
+            int32_t y = 0,
+            int32_t z = 0)
+      : invalid_index(invalid_idx), kind(k), values{x, y, z} {}
+
+  const ast::Expression* operator()(Source src, ProgramBuilder& b) {
+    switch (kind) {
+      case Kind::kScalar:
+        return b.Expr(src, values[0]);
+      case Kind::kVec2:
+        return b.Construct(src, b.ty.vec2<i32>(), values[0], values[1]);
+      case Kind::kVec3:
+        return b.Construct(src, b.ty.vec3<i32>(), values[0], values[1],
+                           values[2]);
+      case Kind::kVec3_Scalar_Vec2:
+        return b.Construct(src, b.ty.vec3<i32>(), values[0],
+                           b.vec2<i32>(values[1], values[2]));
+      case Kind::kVec3_Vec2_Scalar:
+        return b.Construct(src, b.ty.vec3<i32>(),
+                           b.vec2<i32>(values[0], values[1]), values[2]);
+      case Kind::kEmptyVec2:
+        return b.Construct(src, b.ty.vec2<i32>());
+      case Kind::kEmptyVec3:
+        return b.Construct(src, b.ty.vec3<i32>());
+    }
+    return nullptr;
+  }
+
+  static const constexpr int32_t kValid = -1;
+  const int32_t invalid_index;  // Expected error value, or kValid
+  const Kind kind;
+  const std::array<int32_t, 3> values;
+};
+
+static std::ostream& operator<<(std::ostream& out, Parameter param) {
+  return out << param.name;
+}
+
+static std::ostream& operator<<(std::ostream& out, Constexpr expr) {
+  switch (expr.kind) {
+    case Constexpr::Kind::kScalar:
+      return out << expr.values[0];
+    case Constexpr::Kind::kVec2:
+      return out << "vec2(" << expr.values[0] << ", " << expr.values[1] << ")";
+    case Constexpr::Kind::kVec3:
+      return out << "vec3(" << expr.values[0] << ", " << expr.values[1] << ", "
+                 << expr.values[2] << ")";
+    case Constexpr::Kind::kVec3_Scalar_Vec2:
+      return out << "vec3(" << expr.values[0] << ", vec2(" << expr.values[1]
+                 << ", " << expr.values[2] << "))";
+    case Constexpr::Kind::kVec3_Vec2_Scalar:
+      return out << "vec3(vec2(" << expr.values[0] << ", " << expr.values[1]
+                 << "), " << expr.values[2] << ")";
+    case Constexpr::Kind::kEmptyVec2:
+      return out << "vec2()";
+    case Constexpr::Kind::kEmptyVec3:
+      return out << "vec3()";
+  }
+  return out;
+}
+
+using BuiltinTextureConstExprArgValidationTest = ResolverTestWithParam<
+    std::tuple<TextureOverloadCase, Parameter, Constexpr>>;
+
+TEST_P(BuiltinTextureConstExprArgValidationTest, Immediate) {
+  auto& p = GetParam();
+  auto overload = std::get<0>(p);
+  auto param = std::get<1>(p);
+  auto expr = std::get<2>(p);
+
+  overload.BuildTextureVariable(this);
+  overload.BuildSamplerVariable(this);
+
+  auto args = overload.args(this);
+  auto*& arg_to_replace =
+      (param.position == Position::kFirst) ? args.front() : args.back();
+
+  // BuildTextureVariable() uses a Literal for scalars, and a CallExpression for
+  // a vector constructor.
+  bool is_vector = arg_to_replace->Is<ast::CallExpression>();
+
+  // Make the expression to be replaced, reachable. This keeps the resolver
+  // happy.
+  WrapInFunction(arg_to_replace);
+
+  arg_to_replace = expr(Source{{12, 34}}, *this);
+
+  // Call the builtin with the constexpr argument replaced
+  Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  if (expr.invalid_index == Constexpr::kValid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    std::stringstream err;
+    if (is_vector) {
+      err << "12:34 error: each component of the " << param.name
+          << " argument must be at least " << param.min << " and at most "
+          << param.max << ". " << param.name << " component "
+          << expr.invalid_index << " is "
+          << std::to_string(expr.values[expr.invalid_index]);
+    } else {
+      err << "12:34 error: the " << param.name << " argument must be at least "
+          << param.min << " and at most " << param.max << ". " << param.name
+          << " is " << std::to_string(expr.values[expr.invalid_index]);
+    }
+    EXPECT_EQ(r()->error(), err.str());
+  }
+}
+
+TEST_P(BuiltinTextureConstExprArgValidationTest, GlobalConst) {
+  auto& p = GetParam();
+  auto overload = std::get<0>(p);
+  auto param = std::get<1>(p);
+  auto expr = std::get<2>(p);
+
+  // Build the global texture and sampler variables
+  overload.BuildTextureVariable(this);
+  overload.BuildSamplerVariable(this);
+
+  // Build the module-scope let 'G' with the offset value
+  GlobalConst("G", nullptr, expr({}, *this));
+
+  auto args = overload.args(this);
+  auto*& arg_to_replace =
+      (param.position == Position::kFirst) ? args.front() : args.back();
+
+  // Make the expression to be replaced, reachable. This keeps the resolver
+  // happy.
+  WrapInFunction(arg_to_replace);
+
+  arg_to_replace = Expr(Source{{12, 34}}, "G");
+
+  // Call the builtin with the constexpr argument replaced
+  Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  std::stringstream err;
+  err << "12:34 error: the " << param.name
+      << " argument must be a const_expression";
+  EXPECT_EQ(r()->error(), err.str());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Offset2D,
+    BuiltinTextureConstExprArgValidationTest,
+    testing::Combine(
+        testing::ValuesIn(TextureCases({
+            ValidTextureOverload::kSample2dOffsetF32,
+            ValidTextureOverload::kSample2dArrayOffsetF32,
+            ValidTextureOverload::kSampleDepth2dOffsetF32,
+            ValidTextureOverload::kSampleDepth2dArrayOffsetF32,
+            ValidTextureOverload::kSampleBias2dOffsetF32,
+            ValidTextureOverload::kSampleBias2dArrayOffsetF32,
+            ValidTextureOverload::kSampleLevel2dOffsetF32,
+            ValidTextureOverload::kSampleLevel2dArrayOffsetF32,
+            ValidTextureOverload::kSampleLevelDepth2dOffsetF32,
+            ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32,
+            ValidTextureOverload::kSampleGrad2dOffsetF32,
+            ValidTextureOverload::kSampleGrad2dArrayOffsetF32,
+            ValidTextureOverload::kSampleCompareDepth2dOffsetF32,
+            ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32,
+            ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32,
+            ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32,
+        })),
+        testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
+        testing::Values(
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec2},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, -1, 1},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, 7, -8},
+            Constexpr{0, Constexpr::Kind::kVec2, 8, 0},
+            Constexpr{1, Constexpr::Kind::kVec2, 0, 8},
+            Constexpr{0, Constexpr::Kind::kVec2, -9, 0},
+            Constexpr{1, Constexpr::Kind::kVec2, 0, -9},
+            Constexpr{0, Constexpr::Kind::kVec2, 8, 8},
+            Constexpr{0, Constexpr::Kind::kVec2, -9, -9})));
+
+INSTANTIATE_TEST_SUITE_P(
+    Offset3D,
+    BuiltinTextureConstExprArgValidationTest,
+    testing::Combine(
+        testing::ValuesIn(TextureCases({
+            ValidTextureOverload::kSample3dOffsetF32,
+            ValidTextureOverload::kSampleBias3dOffsetF32,
+            ValidTextureOverload::kSampleLevel3dOffsetF32,
+            ValidTextureOverload::kSampleGrad3dOffsetF32,
+        })),
+        testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
+        testing::Values(
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec3},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 0, 0, 0},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 7, -8, 7},
+            Constexpr{0, Constexpr::Kind::kVec3, 10, 0, 0},
+            Constexpr{1, Constexpr::Kind::kVec3, 0, 10, 0},
+            Constexpr{2, Constexpr::Kind::kVec3, 0, 0, 10},
+            Constexpr{0, Constexpr::Kind::kVec3, 10, 11, 12},
+            Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 0, 0},
+            Constexpr{1, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 10, 0},
+            Constexpr{2, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 0, 10},
+            Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 11, 12},
+            Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 0, 0},
+            Constexpr{1, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 10, 0},
+            Constexpr{2, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 0, 10},
+            Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 11, 12})));
+
+INSTANTIATE_TEST_SUITE_P(
+    Component,
+    BuiltinTextureConstExprArgValidationTest,
+    testing::Combine(
+        testing::ValuesIn(
+            TextureCases({ValidTextureOverload::kGather2dF32,
+                          ValidTextureOverload::kGather2dOffsetF32,
+                          ValidTextureOverload::kGather2dArrayF32,
+                          ValidTextureOverload::kGather2dArrayOffsetF32,
+                          ValidTextureOverload::kGatherCubeF32,
+                          ValidTextureOverload::kGatherCubeArrayF32})),
+        testing::Values(Parameter{"component", Position::kFirst, 0, 3}),
+        testing::Values(
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 0},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 1},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 2},
+            Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 3},
+            Constexpr{0, Constexpr::Kind::kScalar, 4},
+            Constexpr{0, Constexpr::Kind::kScalar, 123},
+            Constexpr{0, Constexpr::Kind::kScalar, -1})));
+
+}  // namespace texture_constexpr_args
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/builtins_validation_test.cc b/src/tint/resolver/builtins_validation_test.cc
new file mode 100644
index 0000000..7fd5bd6
--- /dev/null
+++ b/src/tint/resolver/builtins_validation_test.cc
@@ -0,0 +1,1292 @@
+// 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/ast/call_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+template <typename T>
+using DataType = builder::DataType<T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+class ResolverBuiltinsValidationTest : public resolver::TestHelper,
+                                       public testing::Test {};
+namespace StageTest {
+struct Params {
+  builder::ast_type_func_ptr type;
+  ast::Builtin builtin;
+  ast::PipelineStage stage;
+  bool is_valid;
+};
+
+template <typename T>
+constexpr Params ParamsFor(ast::Builtin builtin,
+                           ast::PipelineStage stage,
+                           bool is_valid) {
+  return Params{DataType<T>::AST, builtin, stage, is_valid};
+}
+static constexpr Params cases[] = {
+    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+                         ast::PipelineStage::kFragment,
+                         true),
+    ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+                         ast::PipelineStage::kCompute,
+                         false),
+
+    ParamsFor<u32>(ast::Builtin::kVertexIndex,
+                   ast::PipelineStage::kVertex,
+                   true),
+    ParamsFor<u32>(ast::Builtin::kVertexIndex,
+                   ast::PipelineStage::kFragment,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kVertexIndex,
+                   ast::PipelineStage::kCompute,
+                   false),
+
+    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
+                   ast::PipelineStage::kVertex,
+                   true),
+    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
+                   ast::PipelineStage::kFragment,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kInstanceIndex,
+                   ast::PipelineStage::kCompute,
+                   false),
+
+    ParamsFor<bool>(ast::Builtin::kFrontFacing,
+                    ast::PipelineStage::kVertex,
+                    false),
+    ParamsFor<bool>(ast::Builtin::kFrontFacing,
+                    ast::PipelineStage::kFragment,
+                    true),
+    ParamsFor<bool>(ast::Builtin::kFrontFacing,
+                    ast::PipelineStage::kCompute,
+                    false),
+
+    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId,
+                         ast::PipelineStage::kCompute,
+                         true),
+
+    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
+                   ast::PipelineStage::kVertex,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
+                   ast::PipelineStage::kFragment,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex,
+                   ast::PipelineStage::kCompute,
+                   true),
+
+    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId,
+                         ast::PipelineStage::kCompute,
+                         true),
+
+    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId,
+                         ast::PipelineStage::kCompute,
+                         true),
+
+    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups,
+                         ast::PipelineStage::kCompute,
+                         true),
+
+    ParamsFor<u32>(ast::Builtin::kSampleIndex,
+                   ast::PipelineStage::kVertex,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kSampleIndex,
+                   ast::PipelineStage::kFragment,
+                   true),
+    ParamsFor<u32>(ast::Builtin::kSampleIndex,
+                   ast::PipelineStage::kCompute,
+                   false),
+
+    ParamsFor<u32>(ast::Builtin::kSampleMask,
+                   ast::PipelineStage::kVertex,
+                   false),
+    ParamsFor<u32>(ast::Builtin::kSampleMask,
+                   ast::PipelineStage::kFragment,
+                   true),
+    ParamsFor<u32>(ast::Builtin::kSampleMask,
+                   ast::PipelineStage::kCompute,
+                   false),
+};
+
+using ResolverBuiltinsStageTest = ResolverTestWithParam<Params>;
+TEST_P(ResolverBuiltinsStageTest, All_input) {
+  const Params& params = GetParam();
+
+  auto* p = Global("p", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  auto* input =
+      Param("input", params.type(*this),
+            ast::AttributeList{Builtin(Source{{12, 34}}, params.builtin)});
+  switch (params.stage) {
+    case ast::PipelineStage::kVertex:
+      Func("main", {input}, ty.vec4<f32>(), {Return(p)},
+           {Stage(ast::PipelineStage::kVertex)},
+           {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+      break;
+    case ast::PipelineStage::kFragment:
+      Func("main", {input}, ty.void_(), {},
+           {Stage(ast::PipelineStage::kFragment)}, {});
+      break;
+    case ast::PipelineStage::kCompute:
+      Func("main", {input}, ty.void_(), {},
+           ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                              WorkgroupSize(1)});
+      break;
+    default:
+      break;
+  }
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    std::stringstream err;
+    err << "12:34 error: builtin(" << params.builtin << ")";
+    err << " cannot be used in input of " << params.stage << " pipeline stage";
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), err.str());
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         ResolverBuiltinsStageTest,
+                         testing::ValuesIn(cases));
+
+TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(frag_depth) fd: f32,
+  // ) -> @location(0) f32 { return 1.0; }
+  auto* fd = Param(
+      "fd", ty.f32(),
+      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
+  Func("fs_main", ast::VariableList{fd}, ty.f32(), {Return(1.0f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: builtin(frag_depth) cannot be used in input of "
+            "fragment pipeline stage");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
+  // struct MyInputs {
+  //   @builtin(frag_depth) ff: f32;
+  // };
+  // @stage(fragment)
+  // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* s = Structure(
+      "MyInputs", {Member("frag_depth", ty.f32(),
+                          ast::AttributeList{Builtin(
+                              Source{{12, 34}}, ast::Builtin::kFragDepth)})});
+
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: builtin(frag_depth) cannot be used in input of "
+            "fragment pipeline stage\n"
+            "note: while analysing entry point 'fragShader'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, StructBuiltinInsideEntryPoint_Ignored) {
+  // struct S {
+  //   @builtin(vertex_index) idx: u32;
+  // };
+  // @stage(fragment)
+  // fn fragShader() { var s : S; }
+
+  Structure("S",
+            {Member("idx", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)})});
+
+  Func("fragShader", {}, ty.void_(), {Decl(Var("s", ty.type_name("S")))},
+       {Stage(ast::PipelineStage::kFragment)});
+  EXPECT_TRUE(r()->Resolve());
+}
+
+}  // namespace StageTest
+
+TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_Struct_Fail) {
+  // struct MyInputs {
+  //   @builtin(kPosition) p: vec4<u32>;
+  // };
+  // @stage(fragment)
+  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* m = Member(
+      "position", ty.vec4<u32>(),
+      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+  auto* s = Structure("MyInputs", {m});
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_ReturnType_Fail) {
+  // @stage(vertex)
+  // fn main() -> @builtin(position) f32 { return 1.0; }
+  Func("main", {}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FragDepthNotF32_Struct_Fail) {
+  // struct MyInputs {
+  //   @builtin(kFragDepth) p: i32;
+  // };
+  // @stage(fragment)
+  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* m = Member(
+      "frag_depth", ty.i32(),
+      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
+  auto* s = Structure("MyInputs", {m});
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(frag_depth) must be 'f32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_Struct_Fail) {
+  // struct MyInputs {
+  //   @builtin(sample_mask) m: f32;
+  // };
+  // @stage(fragment)
+  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* s = Structure(
+      "MyInputs", {Member("m", ty.f32(),
+                          ast::AttributeList{Builtin(
+                              Source{{12, 34}}, ast::Builtin::kSampleMask)})});
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_ReturnType_Fail) {
+  // @stage(fragment)
+  // fn main() -> @builtin(sample_mask) i32 { return 1; }
+  Func("main", {}, ty.i32(), {Return(1)},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, SampleMaskIsNotU32_Fail) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(sample_mask) arg: bool
+  // ) -> @location(0) f32 { return 1.0; }
+  auto* arg = Param(
+      "arg", ty.bool_(),
+      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
+  Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(sample_mask) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Struct_Fail) {
+  // struct MyInputs {
+  //   @builtin(sample_index) m: f32;
+  // };
+  // @stage(fragment)
+  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* s = Structure(
+      "MyInputs", {Member("m", ty.f32(),
+                          ast::AttributeList{Builtin(
+                              Source{{12, 34}}, ast::Builtin::kSampleIndex)})});
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(sample_index) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Fail) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(sample_index) arg: bool
+  // ) -> @location(0) f32 { return 1.0; }
+  auto* arg = Param("arg", ty.bool_(),
+                    ast::AttributeList{
+                        Builtin(Source{{12, 34}}, ast::Builtin::kSampleIndex)});
+  Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(sample_index) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, PositionIsNotF32_Fail) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(kPosition) p: vec3<f32>,
+  // ) -> @location(0) f32 { return 1.0; }
+  auto* p = Param(
+      "p", ty.vec3<f32>(),
+      ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+  Func("fs_main", ast::VariableList{p}, ty.f32(), {Return(1.0f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FragDepthIsNotF32_Fail) {
+  // @stage(fragment)
+  // fn fs_main() -> @builtin(kFragDepth) f32 { var fd: i32; return fd; }
+  auto* fd = Var("fd", ty.i32());
+  Func("fs_main", {}, ty.i32(), {Decl(fd), Return(fd)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(frag_depth) must be 'f32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, VertexIndexIsNotU32_Fail) {
+  // @stage(vertex)
+  // fn main(
+  //   @builtin(kVertexIndex) vi : f32,
+  //   @builtin(kPosition) p :vec4<f32>
+  // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
+  auto* p = Param("p", ty.vec4<f32>(),
+                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+  auto* vi = Param("vi", ty.f32(),
+                   ast::AttributeList{
+                       Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
+  Func("main", ast::VariableList{vi, p}, ty.vec4<f32>(), {Return(Expr("p"))},
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
+       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(vertex_index) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, InstanceIndexIsNotU32) {
+  // @stage(vertex)
+  // fn main(
+  //   @builtin(kInstanceIndex) ii : f32,
+  //   @builtin(kPosition) p :vec4<f32>
+  // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
+  auto* p = Param("p", ty.vec4<f32>(),
+                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+  auto* ii = Param("ii", ty.f32(),
+                   ast::AttributeList{Builtin(Source{{12, 34}},
+                                              ast::Builtin::kInstanceIndex)});
+  Func("main", ast::VariableList{ii, p}, ty.vec4<f32>(), {Return(Expr("p"))},
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
+       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(instance_index) must be 'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltin_Pass) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(kPosition) p: vec4<f32>,
+  //   @builtin(front_facing) ff: bool,
+  //   @builtin(sample_index) si: u32,
+  //   @builtin(sample_mask) sm : u32
+  // ) -> @builtin(frag_depth) f32 { var fd: f32; return fd; }
+  auto* p = Param("p", ty.vec4<f32>(),
+                  ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+  auto* ff = Param("ff", ty.bool_(),
+                   ast::AttributeList{Builtin(ast::Builtin::kFrontFacing)});
+  auto* si = Param("si", ty.u32(),
+                   ast::AttributeList{Builtin(ast::Builtin::kSampleIndex)});
+  auto* sm = Param("sm", ty.u32(),
+                   ast::AttributeList{Builtin(ast::Builtin::kSampleMask)});
+  auto* var_fd = Var("fd", ty.f32());
+  Func("fs_main", ast::VariableList{p, ff, si, sm}, ty.f32(),
+       {Decl(var_fd), Return(var_fd)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{Builtin(ast::Builtin::kFragDepth)});
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, VertexBuiltin_Pass) {
+  // @stage(vertex)
+  // fn main(
+  //   @builtin(vertex_index) vi : u32,
+  //   @builtin(instance_index) ii : u32,
+  // ) -> @builtin(position) vec4<f32> { var p :vec4<f32>; return p; }
+  auto* vi = Param("vi", ty.u32(),
+                   ast::AttributeList{
+                       Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
+
+  auto* ii = Param("ii", ty.u32(),
+                   ast::AttributeList{Builtin(Source{{12, 34}},
+                                              ast::Builtin::kInstanceIndex)});
+  auto* p = Var("p", ty.vec4<f32>());
+  Func("main", ast::VariableList{vi, ii}, ty.vec4<f32>(),
+       {
+           Decl(p),
+           Return(p),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
+       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_Pass) {
+  // @stage(compute) @workgroup_size(1)
+  // fn main(
+  //   @builtin(local_invocationId) li_id: vec3<u32>,
+  //   @builtin(local_invocationIndex) li_index: u32,
+  //   @builtin(global_invocationId) gi: vec3<u32>,
+  //   @builtin(workgroup_id) wi: vec3<u32>,
+  //   @builtin(num_workgroups) nwgs: vec3<u32>,
+  // ) {}
+
+  auto* li_id =
+      Param("li_id", ty.vec3<u32>(),
+            ast::AttributeList{Builtin(ast::Builtin::kLocalInvocationId)});
+  auto* li_index =
+      Param("li_index", ty.u32(),
+            ast::AttributeList{Builtin(ast::Builtin::kLocalInvocationIndex)});
+  auto* gi =
+      Param("gi", ty.vec3<u32>(),
+            ast::AttributeList{Builtin(ast::Builtin::kGlobalInvocationId)});
+  auto* wi = Param("wi", ty.vec3<u32>(),
+                   ast::AttributeList{Builtin(ast::Builtin::kWorkgroupId)});
+  auto* nwgs = Param("nwgs", ty.vec3<u32>(),
+                     ast::AttributeList{Builtin(ast::Builtin::kNumWorkgroups)});
+
+  Func("main", ast::VariableList{li_id, li_index, gi, wi, nwgs}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_WorkGroupIdNotVec3U32) {
+  auto* wi = Param("wi", ty.f32(),
+                   ast::AttributeList{
+                       Builtin(Source{{12, 34}}, ast::Builtin::kWorkgroupId)});
+  Func("main", ast::VariableList{wi}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(workgroup_id) must be "
+            "'vec3<u32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_NumWorkgroupsNotVec3U32) {
+  auto* nwgs = Param("nwgs", ty.f32(),
+                     ast::AttributeList{Builtin(Source{{12, 34}},
+                                                ast::Builtin::kNumWorkgroups)});
+  Func("main", ast::VariableList{nwgs}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(num_workgroups) must be "
+            "'vec3<u32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest,
+       ComputeBuiltin_GlobalInvocationNotVec3U32) {
+  auto* gi = Param("gi", ty.vec3<i32>(),
+                   ast::AttributeList{Builtin(
+                       Source{{12, 34}}, ast::Builtin::kGlobalInvocationId)});
+  Func("main", ast::VariableList{gi}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(global_invocation_id) must be "
+            "'vec3<u32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest,
+       ComputeBuiltin_LocalInvocationIndexNotU32) {
+  auto* li_index =
+      Param("li_index", ty.vec3<u32>(),
+            ast::AttributeList{Builtin(Source{{12, 34}},
+                                       ast::Builtin::kLocalInvocationIndex)});
+  Func("main", ast::VariableList{li_index}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: store type of builtin(local_invocation_index) must be "
+      "'u32'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest,
+       ComputeBuiltin_LocalInvocationNotVec3U32) {
+  auto* li_id = Param("li_id", ty.vec2<u32>(),
+                      ast::AttributeList{Builtin(
+                          Source{{12, 34}}, ast::Builtin::kLocalInvocationId)});
+  Func("main", ast::VariableList{li_id}, ty.void_(), {},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(local_invocation_id) must be "
+            "'vec3<u32>'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltinStruct_Pass) {
+  // Struct MyInputs {
+  //   @builtin(kPosition) p: vec4<f32>;
+  //   @builtin(frag_depth) fd: f32;
+  //   @builtin(sample_index) si: u32;
+  //   @builtin(sample_mask) sm : u32;;
+  // };
+  // @stage(fragment)
+  // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* s = Structure(
+      "MyInputs",
+      {Member("position", ty.vec4<f32>(),
+              ast::AttributeList{Builtin(ast::Builtin::kPosition)}),
+       Member("front_facing", ty.bool_(),
+              ast::AttributeList{Builtin(ast::Builtin::kFrontFacing)}),
+       Member("sample_index", ty.u32(),
+              ast::AttributeList{Builtin(ast::Builtin::kSampleIndex)}),
+       Member("sample_mask", ty.u32(),
+              ast::AttributeList{Builtin(ast::Builtin::kSampleMask)})});
+  Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsNotBool_Fail) {
+  // @stage(fragment)
+  // fn fs_main(
+  //   @builtin(front_facing) is_front: i32;
+  // ) -> @location(0) f32 { return 1.0; }
+
+  auto* is_front = Param("is_front", ty.i32(),
+                         ast::AttributeList{Builtin(
+                             Source{{12, 34}}, ast::Builtin::kFrontFacing)});
+  Func("fs_main", ast::VariableList{is_front}, ty.f32(), {Return(1.0f)},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(front_facing) must be 'bool'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) {
+  // struct MyInputs {
+  //   @builtin(front_facing) pos: f32;
+  // };
+  // @stage(fragment)
+  // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
+
+  auto* s = Structure(
+      "MyInputs", {Member("pos", ty.f32(),
+                          ast::AttributeList{Builtin(
+                              Source{{12, 34}}, ast::Builtin::kFrontFacing)})});
+  Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of builtin(front_facing) must be 'bool'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) {
+  auto* builtin = Call("length", 1.0f);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec2) {
+  auto* builtin = Call("length", vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec3) {
+  auto* builtin = Call("length", vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Length_Float_Vec4) {
+  auto* builtin = Call("length", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Scalar) {
+  auto* builtin = Call("distance", 1.0f, 1.0f);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec2) {
+  auto* builtin =
+      Call("distance", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec3) {
+  auto* builtin = Call("distance", vec3<f32>(1.0f, 1.0f, 1.0f),
+                       vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Distance_Float_Vec4) {
+  auto* builtin = Call("distance", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
+                       vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat2x2) {
+  auto* builtin = Call(
+      "determinant", mat2x2<f32>(vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f)));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat3x3) {
+  auto* builtin = Call("determinant", mat3x3<f32>(vec3<f32>(1.0f, 1.0f, 1.0f),
+                                                  vec3<f32>(1.0f, 1.0f, 1.0f),
+                                                  vec3<f32>(1.0f, 1.0f, 1.0f)));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Determinant_Mat4x4) {
+  auto* builtin =
+      Call("determinant", mat4x4<f32>(vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
+                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
+                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
+                                      vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f)));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Frexp_Scalar) {
+  auto* builtin = Call("frexp", 1.0f);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(members[1]->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec2) {
+  auto* builtin = Call("frexp", vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 2u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec3) {
+  auto* builtin = Call("frexp", vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Frexp_Vec4) {
+  auto* builtin = Call("frexp", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Modf_Scalar) {
+  auto* builtin = Call("modf", 1.0f);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(members[1]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Modf_Vec2) {
+  auto* builtin = Call("modf", vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 2u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Modf_Vec3) {
+  auto* builtin = Call("modf", vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Modf_Vec4) {
+  auto* builtin = Call("modf", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
+  ASSERT_TRUE(res_ty != nullptr);
+  auto& members = res_ty->Members();
+  ASSERT_EQ(members.size(), 2u);
+  ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
+  ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
+  EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(members[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(members[1]->Type()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(members[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Cross_Float_Vec3) {
+  auto* builtin =
+      Call("cross", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec2) {
+  auto* builtin = Call("dot", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec3) {
+  auto* builtin =
+      Call("dot", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Dot_Float_Vec4) {
+  auto* builtin = Call("dot", vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f),
+                       vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Float_Scalar) {
+  auto* builtin = Call("select", Expr(1.0f), Expr(1.0f), Expr(true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Integer_Scalar) {
+  auto* builtin = Call("select", Expr(1), Expr(1), Expr(true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Boolean_Scalar) {
+  auto* builtin = Call("select", Expr(true), Expr(true), Expr(true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Float_Vec2) {
+  auto* builtin = Call("select", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
+                       vec2<bool>(true, true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Integer_Vec2) {
+  auto* builtin =
+      Call("select", vec2<int>(1, 1), vec2<int>(1, 1), vec2<bool>(true, true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverBuiltinsValidationTest, Select_Boolean_Vec2) {
+  auto* builtin = Call("select", vec2<bool>(true, true), vec2<bool>(true, true),
+                       vec2<bool>(true, true));
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+template <typename T>
+class ResolverBuiltinsValidationTestWithParams
+    : public resolver::TestHelper,
+      public testing::TestWithParam<T> {};
+
+using FloatAllMatching =
+    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
+
+TEST_P(FloatAllMatching, Scalar) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(Expr(1.0f));
+  }
+  auto* builtin = Call(name, params);
+  Func("func", {}, ty.void_(), {CallStmt(builtin)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->Is<sem::F32>());
+}
+
+TEST_P(FloatAllMatching, Vec2) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec2<f32>(1.0f, 1.0f));
+  }
+  auto* builtin = Call(name, params);
+  Func("func", {}, ty.void_(), {CallStmt(builtin)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
+}
+
+TEST_P(FloatAllMatching, Vec3) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec3<f32>(1.0f, 1.0f, 1.0f));
+  }
+  auto* builtin = Call(name, params);
+  Func("func", {}, ty.void_(), {CallStmt(builtin)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
+}
+
+TEST_P(FloatAllMatching, Vec4) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  }
+  auto* builtin = Call(name, params);
+  Func("func", {}, ty.void_(), {CallStmt(builtin)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         FloatAllMatching,
+                         ::testing::Values(std::make_tuple("abs", 1),
+                                           std::make_tuple("acos", 1),
+                                           std::make_tuple("asin", 1),
+                                           std::make_tuple("atan", 1),
+                                           std::make_tuple("atan2", 2),
+                                           std::make_tuple("ceil", 1),
+                                           std::make_tuple("clamp", 3),
+                                           std::make_tuple("cos", 1),
+                                           std::make_tuple("cosh", 1),
+                                           std::make_tuple("dpdx", 1),
+                                           std::make_tuple("dpdxCoarse", 1),
+                                           std::make_tuple("dpdxFine", 1),
+                                           std::make_tuple("dpdy", 1),
+                                           std::make_tuple("dpdyCoarse", 1),
+                                           std::make_tuple("dpdyFine", 1),
+                                           std::make_tuple("exp", 1),
+                                           std::make_tuple("exp2", 1),
+                                           std::make_tuple("floor", 1),
+                                           std::make_tuple("fma", 3),
+                                           std::make_tuple("fract", 1),
+                                           std::make_tuple("fwidth", 1),
+                                           std::make_tuple("fwidthCoarse", 1),
+                                           std::make_tuple("fwidthFine", 1),
+                                           std::make_tuple("inverseSqrt", 1),
+                                           std::make_tuple("log", 1),
+                                           std::make_tuple("log2", 1),
+                                           std::make_tuple("max", 2),
+                                           std::make_tuple("min", 2),
+                                           std::make_tuple("mix", 3),
+                                           std::make_tuple("pow", 2),
+                                           std::make_tuple("round", 1),
+                                           std::make_tuple("sign", 1),
+                                           std::make_tuple("sin", 1),
+                                           std::make_tuple("sinh", 1),
+                                           std::make_tuple("smoothStep", 3),
+                                           std::make_tuple("sqrt", 1),
+                                           std::make_tuple("step", 2),
+                                           std::make_tuple("tan", 1),
+                                           std::make_tuple("tanh", 1),
+                                           std::make_tuple("trunc", 1)));
+
+using IntegerAllMatching =
+    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
+
+TEST_P(IntegerAllMatching, ScalarUnsigned) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(Construct<uint32_t>(1));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->Is<sem::U32>());
+}
+
+TEST_P(IntegerAllMatching, Vec2Unsigned) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec2<uint32_t>(1u, 1u));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
+}
+
+TEST_P(IntegerAllMatching, Vec3Unsigned) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec3<uint32_t>(1u, 1u, 1u));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
+}
+
+TEST_P(IntegerAllMatching, Vec4Unsigned) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec4<uint32_t>(1u, 1u, 1u, 1u));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_unsigned_integer_vector());
+}
+
+TEST_P(IntegerAllMatching, ScalarSigned) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(Construct<int32_t>(1));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->Is<sem::I32>());
+}
+
+TEST_P(IntegerAllMatching, Vec2Signed) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec2<int32_t>(1, 1));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
+}
+
+TEST_P(IntegerAllMatching, Vec3Signed) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec3<int32_t>(1, 1, 1));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
+}
+
+TEST_P(IntegerAllMatching, Vec4Signed) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec4<int32_t>(1, 1, 1, 1));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_TRUE(TypeOf(builtin)->is_signed_integer_vector());
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         IntegerAllMatching,
+                         ::testing::Values(std::make_tuple("abs", 1),
+                                           std::make_tuple("clamp", 3),
+                                           std::make_tuple("countOneBits", 1),
+                                           std::make_tuple("max", 2),
+                                           std::make_tuple("min", 2),
+                                           std::make_tuple("reverseBits", 1)));
+
+using BooleanVectorInput =
+    ResolverBuiltinsValidationTestWithParams<std::tuple<std::string, uint32_t>>;
+
+TEST_P(BooleanVectorInput, Vec2) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec2<bool>(true, true));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(BooleanVectorInput, Vec3) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec3<bool>(true, true, true));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(BooleanVectorInput, Vec4) {
+  std::string name = std::get<0>(GetParam());
+  uint32_t num_params = std::get<1>(GetParam());
+
+  ast::ExpressionList params;
+  for (uint32_t i = 0; i < num_params; ++i) {
+    params.push_back(vec4<bool>(true, true, true, true));
+  }
+  auto* builtin = Call(name, params);
+  WrapInFunction(builtin);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         BooleanVectorInput,
+                         ::testing::Values(std::make_tuple("all", 1),
+                                           std::make_tuple("any", 1)));
+
+using DataPacking4x8 = ResolverBuiltinsValidationTestWithParams<std::string>;
+
+TEST_P(DataPacking4x8, Float_Vec4) {
+  auto name = GetParam();
+  auto* builtin = Call(name, vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f));
+  WrapInFunction(builtin);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         DataPacking4x8,
+                         ::testing::Values("pack4x8snorm", "pack4x8unorm"));
+
+using DataPacking2x16 = ResolverBuiltinsValidationTestWithParams<std::string>;
+
+TEST_P(DataPacking2x16, Float_Vec2) {
+  auto name = GetParam();
+  auto* builtin = Call(name, vec2<f32>(1.0f, 1.0f));
+  WrapInFunction(builtin);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
+                         DataPacking2x16,
+                         ::testing::Values("pack2x16snorm",
+                                           "pack2x16unorm",
+                                           "pack2x16float"));
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/call_test.cc b/src/tint/resolver/call_test.cc
new file mode 100644
index 0000000..038654d
--- /dev/null
+++ b/src/tint/resolver/call_test.cc
@@ -0,0 +1,118 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <int N, typename T>
+using vec = builder::vec<N, T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <int N, int M, typename T>
+using mat = builder::mat<N, M, T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat2x3 = builder::mat2x3<T>;
+template <typename T>
+using mat3x2 = builder::mat3x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <typename T, int ID = 0>
+using alias = builder::alias<T, ID>;
+template <typename T>
+using alias1 = builder::alias1<T>;
+template <typename T>
+using alias2 = builder::alias2<T>;
+template <typename T>
+using alias3 = builder::alias3<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+using ResolverCallTest = ResolverTest;
+
+struct Params {
+  builder::ast_expr_func_ptr create_value;
+  builder::ast_type_func_ptr create_type;
+};
+
+template <typename T>
+constexpr Params ParamsFor() {
+  return Params{DataType<T>::Expr, DataType<T>::AST};
+}
+
+static constexpr Params all_param_types[] = {
+    ParamsFor<bool>(),         //
+    ParamsFor<u32>(),          //
+    ParamsFor<i32>(),          //
+    ParamsFor<f32>(),          //
+    ParamsFor<vec3<bool>>(),   //
+    ParamsFor<vec3<i32>>(),    //
+    ParamsFor<vec3<u32>>(),    //
+    ParamsFor<vec3<f32>>(),    //
+    ParamsFor<mat3x3<f32>>(),  //
+    ParamsFor<mat2x3<f32>>(),  //
+    ParamsFor<mat3x2<f32>>()   //
+};
+
+TEST_F(ResolverCallTest, Valid) {
+  ast::VariableList params;
+  ast::ExpressionList args;
+  for (auto& p : all_param_types) {
+    params.push_back(Param(Sym(), p.create_type(*this)));
+    args.push_back(p.create_value(*this, 0));
+  }
+
+  auto* func = Func("foo", std::move(params), ty.f32(), {Return(1.23f)});
+  auto* call_expr = Call("foo", std::move(args));
+  WrapInFunction(call_expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* call = Sem().Get(call_expr);
+  EXPECT_NE(call, nullptr);
+  EXPECT_EQ(call->Target(), Sem().Get(func));
+}
+
+TEST_F(ResolverCallTest, OutOfOrder) {
+  auto* call_expr = Call("b");
+  Func("a", {}, ty.void_(), {CallStmt(call_expr)});
+  auto* b = Func("b", {}, ty.void_(), {});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* call = Sem().Get(call_expr);
+  EXPECT_NE(call, nullptr);
+  EXPECT_EQ(call->Target(), Sem().Get(b));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
new file mode 100644
index 0000000..c8be5e4
--- /dev/null
+++ b/src/tint/resolver/call_validation_test.cc
@@ -0,0 +1,288 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverCallValidationTest = ResolverTest;
+
+TEST_F(ResolverCallValidationTest, TooFewArgs) {
+  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
+       {Return()});
+  auto* call = Call(Source{{12, 34}}, "foo", 1);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: too few arguments in call to 'foo', expected 2, got 1");
+}
+
+TEST_F(ResolverCallValidationTest, TooManyArgs) {
+  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
+       {Return()});
+  auto* call = Call(Source{{12, 34}}, "foo", 1, 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: too many arguments in call to 'foo', expected 2, got 3");
+}
+
+TEST_F(ResolverCallValidationTest, MismatchedArgs) {
+  Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
+       {Return()});
+  auto* call = Call("foo", Expr(Source{{12, 34}}, true), 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type mismatch for argument 1 in call to 'foo', "
+            "expected 'i32', got 'bool'");
+}
+
+TEST_F(ResolverCallValidationTest, UnusedRetval) {
+  // fn func() -> f32 { return 1.0; }
+  // fn main() {func(); return; }
+
+  Func("func", {}, ty.f32(), {Return(Expr(1.0f))}, {});
+
+  Func("main", {}, ty.void_(),
+       {
+           CallStmt(Source{{12, 34}}, Call("func")),
+           Return(),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverCallValidationTest, PointerArgument_VariableIdentExpr) {
+  // fn foo(p: ptr<function, i32>) {}
+  // fn main() {
+  //   var z: i32 = 1;
+  //   foo(&z);
+  // }
+  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
+  Func("foo", {param}, ty.void_(), {});
+  Func("main", {}, ty.void_(),
+       {
+           Decl(Var("z", ty.i32(), Expr(1))),
+           CallStmt(Call("foo", AddressOf(Source{{12, 34}}, Expr("z")))),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverCallValidationTest, PointerArgument_ConstIdentExpr) {
+  // fn foo(p: ptr<function, i32>) {}
+  // fn main() {
+  //   let z: i32 = 1;
+  //   foo(&z);
+  // }
+  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
+  Func("foo", {param}, ty.void_(), {});
+  Func("main", {}, ty.void_(),
+       {
+           Decl(Const("z", ty.i32(), Expr(1))),
+           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_NotIdentExprVar) {
+  // struct S { m: i32; };
+  // fn foo(p: ptr<function, i32>) {}
+  // fn main() {
+  //   var v: S;
+  //   foo(&v.m);
+  // }
+  auto* S = Structure("S", {Member("m", ty.i32())});
+  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
+  Func("foo", {param}, ty.void_(), {});
+  Func("main", {}, ty.void_(),
+       {
+           Decl(Var("v", ty.Of(S))),
+           CallStmt(Call(
+               "foo", AddressOf(Source{{12, 34}}, MemberAccessor("v", "m")))),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected an address-of expression of a variable "
+            "identifier expression or a function parameter");
+}
+
+TEST_F(ResolverCallValidationTest, PointerArgument_AddressOfMemberAccessor) {
+  // struct S { m: i32; };
+  // fn foo(p: ptr<function, i32>) {}
+  // fn main() {
+  //   let v: S = S();
+  //   foo(&v.m);
+  // }
+  auto* S = Structure("S", {Member("m", ty.i32())});
+  auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
+  Func("foo", {param}, ty.void_(), {});
+  Func("main", {}, ty.void_(),
+       {
+           Decl(Const("v", ty.Of(S), Construct(ty.Of(S)))),
+           CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}},
+                                               MemberAccessor("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>) {
+  // foo(p);
+  // }
+  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
+       ty.void_(), {});
+  Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
+       ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParamWithMain) {
+  // fn foo(p: ptr<function, i32>) {}
+  // fn bar(p: ptr<function, i32>) {
+  // foo(p);
+  // }
+  // @stage(fragment)
+  // fn main() {
+  //   var v: i32;
+  //   bar(&v);
+  // }
+  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
+       ty.void_(), {});
+  Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
+       ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Var("v", ty.i32(), Expr(1))),
+           CallStmt(Call("foo", AddressOf(Expr("v")))),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverCallValidationTest, LetPointer) {
+  // fn x(p : ptr<function, i32>) -> i32 {}
+  // @stage(fragment)
+  // fn main() {
+  //   var v: i32;
+  //   let p: ptr<function, i32> = &v;
+  //   var c: i32 = x(p);
+  // }
+  Func("x", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
+       ty.void_(), {});
+  auto* v = Var("v", ty.i32());
+  auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
+                  AddressOf(v));
+  auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
+                Call("x", Expr(Source{{12, 34}}, p)));
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(v),
+           Decl(p),
+           Decl(c),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected an address-of expression of a variable "
+            "identifier expression or a function parameter");
+}
+
+TEST_F(ResolverCallValidationTest, LetPointerPrivate) {
+  // let p: ptr<private, i32> = &v;
+  // fn foo(p : ptr<private, i32>) -> i32 {}
+  // var v: i32;
+  // @stage(fragment)
+  // fn main() {
+  //   var c: i32 = foo(p);
+  // }
+  Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kPrivate))},
+       ty.void_(), {});
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
+                  AddressOf(v));
+  auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
+                Call("foo", Expr(Source{{12, 34}}, p)));
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(p),
+           Decl(c),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected an address-of expression of a variable "
+            "identifier expression or a function parameter");
+}
+
+TEST_F(ResolverCallValidationTest, CallVariable) {
+  // var v : i32;
+  // fn f() {
+  //   v();
+  // }
+  Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  Func("f", {}, ty.void_(), {CallStmt(Call(Source{{12, 34}}, "v"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(error: cannot call variable 'v'
+note: 'v' declared here)");
+}
+
+TEST_F(ResolverCallValidationTest, CallVariableShadowsFunction) {
+  // fn x() {}
+  // fn f() {
+  //   var x : i32;
+  //   x();
+  // }
+  Func("x", {}, ty.void_(), {});
+  Func("f", {}, ty.void_(),
+       {
+           Decl(Var(Source{{56, 78}}, "x", ty.i32())),
+           CallStmt(Call(Source{{12, 34}}, "x")),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(error: cannot call variable 'x'
+56:78 note: 'x' declared here)");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/compound_statement_test.cc b/src/tint/resolver/compound_statement_test.cc
new file mode 100644
index 0000000..bf130ca
--- /dev/null
+++ b/src/tint/resolver/compound_statement_test.cc
@@ -0,0 +1,380 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/loop_statement.h"
+#include "src/tint/sem/switch_statement.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverCompoundStatementTest = ResolverTest;
+
+TEST_F(ResolverCompoundStatementTest, FunctionBlock) {
+  // fn F() {
+  //   var x : 32;
+  // }
+  auto* stmt = Decl(Var("x", ty.i32()));
+  auto* f = Func("F", {}, ty.void_(), {stmt});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* s = Sem().Get(stmt);
+  ASSERT_NE(s, nullptr);
+  ASSERT_NE(s->Block(), nullptr);
+  ASSERT_TRUE(s->Block()->Is<sem::FunctionBlockStatement>());
+  EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
+  EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
+  EXPECT_EQ(s->Function()->Declaration(), f);
+  EXPECT_EQ(s->Block()->Parent(), nullptr);
+}
+
+TEST_F(ResolverCompoundStatementTest, Block) {
+  // fn F() {
+  //   {
+  //     var x : 32;
+  //   }
+  // }
+  auto* stmt = Decl(Var("x", ty.i32()));
+  auto* block = Block(stmt);
+  auto* f = Func("F", {}, ty.void_(), {block});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  {
+    auto* s = Sem().Get(block);
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::BlockStatement>());
+    EXPECT_EQ(s, s->Block());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+  {
+    auto* s = Sem().Get(stmt);
+    ASSERT_NE(s, nullptr);
+    ASSERT_NE(s->Block(), nullptr);
+    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Block()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+    ASSERT_TRUE(s->Block()->Parent()->Is<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Function()->Declaration(), f);
+    EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
+  }
+}
+
+TEST_F(ResolverCompoundStatementTest, Loop) {
+  // fn F() {
+  //   loop {
+  //     break;
+  //     continuing {
+  //       stmt;
+  //     }
+  //   }
+  // }
+  auto* brk = Break();
+  auto* stmt = Ignore(1);
+  auto* loop = Loop(Block(brk), Block(stmt));
+  auto* f = Func("F", {}, ty.void_(), {loop});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  {
+    auto* s = Sem().Get(loop);
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::LoopStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* s = Sem().Get(brk);
+    ASSERT_NE(s, nullptr);
+    ASSERT_NE(s->Block(), nullptr);
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::LoopBlockStatement>());
+
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::LoopStatement>());
+    EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()));
+
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_TRUE(
+        Is<sem::FunctionBlockStatement>(s->Parent()->Parent()->Parent()));
+
+    EXPECT_EQ(s->Function()->Declaration(), f);
+
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), nullptr);
+  }
+  {
+    auto* s = Sem().Get(stmt);
+    ASSERT_NE(s, nullptr);
+    ASSERT_NE(s->Block(), nullptr);
+    EXPECT_EQ(s->Parent(), s->Block());
+
+    EXPECT_EQ(s->Parent(),
+              s->FindFirstParent<sem::LoopContinuingBlockStatement>());
+    EXPECT_TRUE(Is<sem::LoopContinuingBlockStatement>(s->Parent()));
+
+    EXPECT_EQ(s->Parent()->Parent(),
+              s->FindFirstParent<sem::LoopBlockStatement>());
+    EXPECT_TRUE(Is<sem::LoopBlockStatement>(s->Parent()->Parent()));
+
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::LoopStatement>());
+    EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()->Parent()));
+
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(
+        s->Parent()->Parent()->Parent()->Parent()));
+    EXPECT_EQ(s->Function()->Declaration(), f);
+
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent()->Parent(), nullptr);
+  }
+}
+
+TEST_F(ResolverCompoundStatementTest, ForLoop) {
+  // fn F() {
+  //   for (var i : u32; true; i = i + 1u) {
+  //     return;
+  //   }
+  // }
+  auto* init = Decl(Var("i", ty.u32()));
+  auto* cond = Expr(true);
+  auto* cont = Assign("i", Add("i", 1u));
+  auto* stmt = Return();
+  auto* body = Block(stmt);
+  auto* for_ = For(init, cond, cont, body);
+  auto* f = Func("F", {}, ty.void_(), {for_});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  {
+    auto* s = Sem().Get(for_);
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::ForLoopStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* s = Sem().Get(init);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
+    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
+    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
+  }
+  {  // Condition expression's statement is the for-loop itself
+    auto* e = Sem().Get(cond);
+    ASSERT_NE(e, nullptr);
+    auto* s = e->Stmt();
+    ASSERT_NE(s, nullptr);
+    ASSERT_TRUE(Is<sem::ForLoopStatement>(s));
+    ASSERT_NE(s->Parent(), nullptr);
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()));
+  }
+  {
+    auto* s = Sem().Get(cont);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
+    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
+    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
+  }
+  {
+    auto* s = Sem().Get(stmt);
+    ASSERT_NE(s, nullptr);
+    ASSERT_NE(s->Block(), nullptr);
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Block(), s->FindFirstParent<sem::LoopBlockStatement>());
+    EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()->Parent()));
+    EXPECT_EQ(s->Block()->Parent(),
+              s->FindFirstParent<sem::ForLoopStatement>());
+    ASSERT_TRUE(
+        Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
+    EXPECT_EQ(s->Block()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Function()->Declaration(), f);
+    EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
+  }
+}
+
+TEST_F(ResolverCompoundStatementTest, If) {
+  // fn F() {
+  //   if (cond_a) {
+  //     stat_a;
+  //   } else if (cond_b) {
+  //     stat_b;
+  //   } else {
+  //     stat_c;
+  //   }
+  // }
+
+  auto* cond_a = Expr(true);
+  auto* stmt_a = Ignore(1);
+  auto* cond_b = Expr(true);
+  auto* stmt_b = Ignore(1);
+  auto* stmt_c = Ignore(1);
+  auto* if_stmt = If(cond_a, Block(stmt_a), Else(cond_b, Block(stmt_b)),
+                     Else(nullptr, Block(stmt_c)));
+  WrapInFunction(if_stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  {
+    auto* s = Sem().Get(if_stmt);
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::IfStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* e = Sem().Get(cond_a);
+    ASSERT_NE(e, nullptr);
+    auto* s = e->Stmt();
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::IfStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* s = Sem().Get(stmt_a);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::IfStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+  {
+    auto* e = Sem().Get(cond_b);
+    ASSERT_NE(e, nullptr);
+    auto* s = e->Stmt();
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::ElseStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::IfStatement>());
+    EXPECT_EQ(s->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent()->Parent(), s->Block());
+  }
+  {
+    auto* s = Sem().Get(stmt_b);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::IfStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+  {
+    auto* s = Sem().Get(stmt_c);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::IfStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+}
+
+TEST_F(ResolverCompoundStatementTest, Switch) {
+  // fn F() {
+  //   switch (expr) {
+  //     case 1: {
+  //        stmt_a;
+  //     }
+  //     case 2: {
+  //        stmt_b;
+  //     }
+  //     default: {
+  //        stmt_c;
+  //     }
+  //   }
+  // }
+
+  auto* expr = Expr(5);
+  auto* stmt_a = Ignore(1);
+  auto* stmt_b = Ignore(1);
+  auto* stmt_c = Ignore(1);
+  auto* swi = Switch(expr, Case(Expr(1), Block(stmt_a)),
+                     Case(Expr(2), Block(stmt_b)), DefaultCase(Block(stmt_c)));
+  WrapInFunction(swi);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  {
+    auto* s = Sem().Get(swi);
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::SwitchStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* e = Sem().Get(expr);
+    ASSERT_NE(e, nullptr);
+    auto* s = e->Stmt();
+    ASSERT_NE(s, nullptr);
+    EXPECT_TRUE(s->Is<sem::SwitchStatement>());
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+  }
+  {
+    auto* s = Sem().Get(stmt_a);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::SwitchStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+  {
+    auto* s = Sem().Get(stmt_b);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::SwitchStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+  {
+    auto* s = Sem().Get(stmt_c);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
+    EXPECT_EQ(s->Parent(), s->Block());
+    EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::SwitchStatement>());
+    EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
+              s->FindFirstParent<sem::FunctionBlockStatement>());
+  }
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/control_block_validation_test.cc b/src/tint/resolver/control_block_validation_test.cc
new file mode 100644
index 0000000..9406da4
--- /dev/null
+++ b/src/tint/resolver/control_block_validation_test.cc
@@ -0,0 +1,364 @@
+// 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/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace {
+
+class ResolverControlBlockValidationTest : public resolver::TestHelper,
+                                           public testing::Test {};
+
+TEST_F(ResolverControlBlockValidationTest,
+       SwitchSelectorExpressionNoneIntegerType_Fail) {
+  // var a : f32 = 3.14;
+  // switch (a) {
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.f32(), Expr(3.14f));
+
+  auto* block = Block(Decl(var), Switch(Expr(Source{{12, 34}}, "a"),  //
+                                        DefaultCase()));
+
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: switch statement selector expression must be of a "
+            "scalar integer type");
+}
+
+TEST_F(ResolverControlBlockValidationTest, SwitchWithoutDefault_Fail) {
+  // var a : i32 = 2;
+  // switch (a) {
+  //   case 1: {}
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  auto* block = Block(Decl(var),                     //
+                      Switch(Source{{12, 34}}, "a",  //
+                             Case(Expr(1))));
+
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: switch statement must have a default clause");
+}
+
+TEST_F(ResolverControlBlockValidationTest, SwitchWithTwoDefault_Fail) {
+  // var a : i32 = 2;
+  // switch (a) {
+  //   default: {}
+  //   case 1: {}
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  auto* block = Block(Decl(var),             //
+                      Switch("a",            //
+                             DefaultCase(),  //
+                             Case(Expr(1)),  //
+                             DefaultCase(Source{{12, 34}})));
+
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: switch statement must have exactly one default clause");
+}
+
+TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
+  // loop {
+  //   if (false) { break; }
+  //   var z: i32;
+  //   continue;
+  //   z = 1;
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* cont = Continue();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(
+      Loop(Block(If(false, Block(Break())), decl_z, cont, assign_z)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       UnreachableCode_Loop_continue_InBlocks) {
+  // loop {
+  //   if (false) { break; }
+  //   var z: i32;
+  //   {{{continue;}}}
+  //   z = 1;
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* cont = Continue();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(Loop(Block(If(false, Block(Break())), decl_z,
+                            Block(Block(Block(cont))), assign_z)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
+  // for (;false;) {
+  //   var z: i32;
+  //   continue;
+  //   z = 1;
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* cont = Continue();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(For(nullptr, false, nullptr,  //
+                     Block(decl_z, cont, assign_z)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       UnreachableCode_ForLoop_continue_InBlocks) {
+  // for (;false;) {
+  //   var z: i32;
+  //   {{{continue;}}}
+  //   z = 1;
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* cont = Continue();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(For(nullptr, false, nullptr,
+                     Block(decl_z, Block(Block(Block(cont))), assign_z)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(cont)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
+  // switch (1) {
+  //   case 1: {
+  //     var z: i32;
+  //     break;
+  //     z = 1;
+  //   default: {}
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* brk = Break();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(                                                //
+      Block(Switch(1,                                            //
+                   Case(Expr(1), Block(decl_z, brk, assign_z)),  //
+                   DefaultCase())));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(brk)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
+  // loop {
+  //   switch (1) {
+  //     case 1: { {{{break;}}} var a : u32 = 2;}
+  //     default: {}
+  //   }
+  //   break;
+  // }
+  auto* decl_z = Decl(Var("z", ty.i32()));
+  auto* brk = Break();
+  auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
+  WrapInFunction(Loop(Block(
+      Switch(1,  //
+             Case(Expr(1), Block(decl_z, Block(Block(Block(brk))), assign_z)),
+             DefaultCase()),  //
+      Break())));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
+  EXPECT_TRUE(Sem().Get(brk)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       SwitchConditionTypeMustMatchSelectorType2_Fail) {
+  // var a : u32 = 2;
+  // switch (a) {
+  //   case 1: {}
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  auto* block = Block(Decl(var), Switch("a",                                 //
+                                        Case(Source{{12, 34}}, {Expr(1u)}),  //
+                                        DefaultCase()));
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: the case selector values must have the same type as "
+            "the selector expression.");
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       SwitchConditionTypeMustMatchSelectorType_Fail) {
+  // var a : u32 = 2;
+  // switch (a) {
+  //   case -1: {}
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.u32(), Expr(2u));
+
+  auto* block = Block(Decl(var),                                  //
+                      Switch("a",                                 //
+                             Case(Source{{12, 34}}, {Expr(-1)}),  //
+                             DefaultCase()));
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: the case selector values must have the same type as "
+            "the selector expression.");
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       NonUniqueCaseSelectorValueUint_Fail) {
+  // var a : u32 = 3;
+  // switch (a) {
+  //   case 0u: {}
+  //   case 2u, 3u, 2u: {}
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.u32(), Expr(3u));
+
+  auto* block = Block(Decl(var),   //
+                      Switch("a",  //
+                             Case(Expr(0u)),
+                             Case({
+                                 Expr(Source{{12, 34}}, 2u),
+                                 Expr(3u),
+                                 Expr(Source{{56, 78}}, 2u),
+                             }),
+                             DefaultCase()));
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: duplicate switch case '2'\n"
+            "12:34 note: previous case declared here");
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       NonUniqueCaseSelectorValueSint_Fail) {
+  // var a : i32 = 2;
+  // switch (a) {
+  //   case -10: {}
+  //   case 0,1,2,-10: {}
+  //   default: {}
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  auto* block = Block(Decl(var),   //
+                      Switch("a",  //
+                             Case(Expr(Source{{12, 34}}, -10)),
+                             Case({
+                                 Expr(0),
+                                 Expr(1),
+                                 Expr(2),
+                                 Expr(Source{{56, 78}}, -10),
+                             }),
+                             DefaultCase()));
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: duplicate switch case '-10'\n"
+            "12:34 note: previous case declared here");
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       LastClauseLastStatementIsFallthrough_Fail) {
+  // var a : i32 = 2;
+  // switch (a) {
+  //   default: { fallthrough; }
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+  auto* fallthrough = create<ast::FallthroughStatement>(Source{{12, 34}});
+  auto* block = Block(Decl(var),   //
+                      Switch("a",  //
+                             DefaultCase(Block(fallthrough))));
+  WrapInFunction(block);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a fallthrough statement must not be used in the last "
+            "switch case");
+}
+
+TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
+  // var a : i32 = 2;
+  // switch (a) {
+  //   default: {}
+  //   case 5: {}
+  // }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  auto* block = Block(Decl(var),                             //
+                      Switch("a",                            //
+                             DefaultCase(Source{{12, 34}}),  //
+                             Case(Expr(5))));
+  WrapInFunction(block);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverControlBlockValidationTest, SwitchCaseAlias_Pass) {
+  // type MyInt = u32;
+  // var v: MyInt;
+  // switch(v){
+  //   default: {}
+  // }
+
+  auto* my_int = Alias("MyInt", ty.u32());
+  auto* var = Var("a", ty.Of(my_int), Expr(2u));
+  auto* block = Block(Decl(var),  //
+                      Switch("a", DefaultCase(Source{{12, 34}})));
+
+  WrapInFunction(block);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
new file mode 100644
index 0000000..8d58018
--- /dev/null
+++ b/src/tint/resolver/dependency_graph.cc
@@ -0,0 +1,736 @@
+// 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/resolver/dependency_graph.h"
+
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/builtin.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/unique_vector.h"
+
+#define TINT_DUMP_DEPENDENCY_GRAPH 0
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Forward declaration
+struct Global;
+
+/// Dependency describes how one global depends on another global
+struct DependencyInfo {
+  /// The source of the symbol that forms the dependency
+  Source source;
+  /// A string describing how the dependency is referenced. e.g. 'calls'
+  const char* action = nullptr;
+};
+
+/// DependencyEdge describes the two Globals used to define a dependency
+/// relationship.
+struct DependencyEdge {
+  /// The Global that depends on #to
+  const Global* from;
+  /// The Global that is depended on by #from
+  const Global* to;
+};
+
+/// DependencyEdgeCmp implements the contracts of std::equal_to<DependencyEdge>
+/// and std::hash<DependencyEdge>.
+struct DependencyEdgeCmp {
+  /// Equality operator
+  bool operator()(const DependencyEdge& lhs, const DependencyEdge& rhs) const {
+    return lhs.from == rhs.from && lhs.to == rhs.to;
+  }
+  /// Hashing operator
+  inline std::size_t operator()(const DependencyEdge& d) const {
+    return utils::Hash(d.from, d.to);
+  }
+};
+
+/// A map of DependencyEdge to DependencyInfo
+using DependencyEdges = std::unordered_map<DependencyEdge,
+                                           DependencyInfo,
+                                           DependencyEdgeCmp,
+                                           DependencyEdgeCmp>;
+
+/// Global describes a module-scope variable, type or function.
+struct Global {
+  explicit Global(const ast::Node* n) : node(n) {}
+
+  /// The declaration ast::Node
+  const ast::Node* node;
+  /// A list of dependencies that this global depends on
+  std::vector<Global*> deps;
+};
+
+/// A map of global name to Global
+using GlobalMap = std::unordered_map<Symbol, Global*>;
+
+/// Raises an ICE that a global ast::Node type was not handled by this system.
+void UnhandledNode(diag::List& diagnostics, const ast::Node* node) {
+  TINT_ICE(Resolver, diagnostics)
+      << "unhandled node type: " << node->TypeInfo().name;
+}
+
+/// Raises an error diagnostic with the given message and source.
+void AddError(diag::List& diagnostics,
+              const std::string& msg,
+              const Source& source) {
+  diagnostics.add_error(diag::System::Resolver, msg, source);
+}
+
+/// Raises a note diagnostic with the given message and source.
+void AddNote(diag::List& diagnostics,
+             const std::string& msg,
+             const Source& source) {
+  diagnostics.add_note(diag::System::Resolver, msg, source);
+}
+
+/// DependencyScanner is used to traverse a module to build the list of
+/// global-to-global dependencies.
+class DependencyScanner {
+ public:
+  /// Constructor
+  /// @param syms the program symbol table
+  /// @param globals_by_name map of global symbol to Global pointer
+  /// @param diagnostics diagnostic messages, appended with any errors found
+  /// @param graph the dependency graph to populate with resolved symbols
+  /// @param edges the map of globals-to-global dependency edges, which will
+  /// be populated by calls to Scan()
+  DependencyScanner(const SymbolTable& syms,
+                    const GlobalMap& globals_by_name,
+                    diag::List& diagnostics,
+                    DependencyGraph& graph,
+                    DependencyEdges& edges)
+      : symbols_(syms),
+        globals_(globals_by_name),
+        diagnostics_(diagnostics),
+        graph_(graph),
+        dependency_edges_(edges) {
+    // Register all the globals at global-scope
+    for (auto it : globals_by_name) {
+      scope_stack_.Set(it.first, it.second->node);
+    }
+  }
+
+  /// Walks the global declarations, resolving symbols, and determining the
+  /// dependencies of each global.
+  void Scan(Global* global) {
+    TINT_SCOPED_ASSIGNMENT(current_global_, global);
+    Switch(
+        global->node,
+        [&](const ast::Struct* str) {
+          Declare(str->name, str);
+          for (auto* member : str->members) {
+            TraverseType(member->type);
+          }
+        },
+        [&](const ast::Alias* alias) {
+          Declare(alias->name, alias);
+          TraverseType(alias->type);
+        },
+        [&](const ast::Function* func) {
+          Declare(func->symbol, func);
+          TraverseAttributes(func->attributes);
+          TraverseFunction(func);
+        },
+        [&](const ast::Variable* var) {
+          Declare(var->symbol, var);
+          TraverseType(var->type);
+          if (var->constructor) {
+            TraverseExpression(var->constructor);
+          }
+        },
+        [&](Default) { UnhandledNode(diagnostics_, global->node); });
+  }
+
+ private:
+  /// Traverses the function, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseFunction(const ast::Function* func) {
+    // Perform symbol resolution on all the parameter types before registering
+    // the parameters themselves. This allows the case of declaring a parameter
+    // with the same identifier as its type.
+    for (auto* param : func->params) {
+      TraverseType(param->type);
+    }
+    // Resolve the return type
+    TraverseType(func->return_type);
+
+    // Push the scope stack for the parameters and function body.
+    scope_stack_.Push();
+    TINT_DEFER(scope_stack_.Pop());
+
+    for (auto* param : func->params) {
+      if (auto* shadows = scope_stack_.Get(param->symbol)) {
+        graph_.shadows.emplace(param, shadows);
+      }
+      Declare(param->symbol, param);
+    }
+    if (func->body) {
+      TraverseStatements(func->body->statements);
+    }
+  }
+
+  /// Traverses the statements, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseStatements(const ast::StatementList& stmts) {
+    for (auto* s : stmts) {
+      TraverseStatement(s);
+    }
+  }
+
+  /// Traverses the statement, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseStatement(const ast::Statement* stmt) {
+    if (!stmt) {
+      return;
+    }
+    Switch(
+        stmt,  //
+        [&](const ast::AssignmentStatement* a) {
+          TraverseExpression(a->lhs);
+          TraverseExpression(a->rhs);
+        },
+        [&](const ast::BlockStatement* b) {
+          scope_stack_.Push();
+          TINT_DEFER(scope_stack_.Pop());
+          TraverseStatements(b->statements);
+        },
+        [&](const ast::CallStatement* r) {  //
+          TraverseExpression(r->expr);
+        },
+        [&](const ast::ForLoopStatement* l) {
+          scope_stack_.Push();
+          TINT_DEFER(scope_stack_.Pop());
+          TraverseStatement(l->initializer);
+          TraverseExpression(l->condition);
+          TraverseStatement(l->continuing);
+          TraverseStatement(l->body);
+        },
+        [&](const ast::LoopStatement* l) {
+          scope_stack_.Push();
+          TINT_DEFER(scope_stack_.Pop());
+          TraverseStatements(l->body->statements);
+          TraverseStatement(l->continuing);
+        },
+        [&](const ast::IfStatement* i) {
+          TraverseExpression(i->condition);
+          TraverseStatement(i->body);
+          for (auto* e : i->else_statements) {
+            TraverseExpression(e->condition);
+            TraverseStatement(e->body);
+          }
+        },
+        [&](const ast::ReturnStatement* r) {  //
+          TraverseExpression(r->value);
+        },
+        [&](const ast::SwitchStatement* s) {
+          TraverseExpression(s->condition);
+          for (auto* c : s->body) {
+            for (auto* sel : c->selectors) {
+              TraverseExpression(sel);
+            }
+            TraverseStatement(c->body);
+          }
+        },
+        [&](const ast::VariableDeclStatement* v) {
+          if (auto* shadows = scope_stack_.Get(v->variable->symbol)) {
+            graph_.shadows.emplace(v->variable, shadows);
+          }
+          TraverseType(v->variable->type);
+          TraverseExpression(v->variable->constructor);
+          Declare(v->variable->symbol, v->variable);
+        },
+        [&](Default) {
+          if (!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
+                             ast::DiscardStatement,
+                             ast::FallthroughStatement>()) {
+            UnhandledNode(diagnostics_, stmt);
+          }
+        });
+  }
+
+  /// Adds the symbol definition to the current scope, raising an error if two
+  /// symbols collide within the same scope.
+  void Declare(Symbol symbol, const ast::Node* node) {
+    auto* old = scope_stack_.Set(symbol, node);
+    if (old != nullptr && node != old) {
+      auto name = symbols_.NameFor(symbol);
+      AddError(diagnostics_, "redeclaration of '" + name + "'", node->source);
+      AddNote(diagnostics_, "'" + name + "' previously declared here",
+              old->source);
+    }
+  }
+
+  /// Traverses the expression, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseExpression(const ast::Expression* root) {
+    if (!root) {
+      return;
+    }
+    ast::TraverseExpressions(
+        root, diagnostics_, [&](const ast::Expression* expr) {
+          Switch(
+              expr,
+              [&](const ast::IdentifierExpression* ident) {
+                AddDependency(ident, ident->symbol, "identifier", "references");
+              },
+              [&](const ast::CallExpression* call) {
+                if (call->target.name) {
+                  AddDependency(call->target.name, call->target.name->symbol,
+                                "function", "calls");
+                }
+                if (call->target.type) {
+                  TraverseType(call->target.type);
+                }
+              },
+              [&](const ast::BitcastExpression* cast) {
+                TraverseType(cast->type);
+              });
+          return ast::TraverseAction::Descend;
+        });
+  }
+
+  /// Traverses the type node, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseType(const ast::Type* ty) {
+    if (!ty) {
+      return;
+    }
+    Switch(
+        ty,  //
+        [&](const ast::Array* arr) {
+          TraverseType(arr->type);  //
+          TraverseExpression(arr->count);
+        },
+        [&](const ast::Atomic* atomic) {  //
+          TraverseType(atomic->type);
+        },
+        [&](const ast::Matrix* mat) {  //
+          TraverseType(mat->type);
+        },
+        [&](const ast::Pointer* ptr) {  //
+          TraverseType(ptr->type);
+        },
+        [&](const ast::TypeName* tn) {  //
+          AddDependency(tn, tn->name, "type", "references");
+        },
+        [&](const ast::Vector* vec) {  //
+          TraverseType(vec->type);
+        },
+        [&](const ast::SampledTexture* tex) {  //
+          TraverseType(tex->type);
+        },
+        [&](const ast::MultisampledTexture* tex) {  //
+          TraverseType(tex->type);
+        },
+        [&](Default) {
+          if (!ty->IsAnyOf<ast::Void, ast::Bool, ast::I32, ast::U32, ast::F32,
+                           ast::DepthTexture, ast::DepthMultisampledTexture,
+                           ast::StorageTexture, ast::ExternalTexture,
+                           ast::Sampler>()) {
+            UnhandledNode(diagnostics_, ty);
+          }
+        });
+  }
+
+  /// Traverses the attribute list, performing symbol resolution and
+  /// determining global dependencies.
+  void TraverseAttributes(const ast::AttributeList& attrs) {
+    for (auto* attr : attrs) {
+      TraverseAttribute(attr);
+    }
+  }
+
+  /// Traverses the attribute, performing symbol resolution and determining
+  /// global dependencies.
+  void TraverseAttribute(const ast::Attribute* attr) {
+    if (auto* wg = attr->As<ast::WorkgroupAttribute>()) {
+      TraverseExpression(wg->x);
+      TraverseExpression(wg->y);
+      TraverseExpression(wg->z);
+      return;
+    }
+    if (attr->IsAnyOf<
+            ast::BindingAttribute, ast::BuiltinAttribute, ast::GroupAttribute,
+            ast::IdAttribute, ast::InternalAttribute, ast::InterpolateAttribute,
+            ast::InvariantAttribute, ast::LocationAttribute,
+            ast::StageAttribute, ast::StrideAttribute,
+            ast::StructBlockAttribute, ast::StructMemberAlignAttribute,
+            ast::StructMemberOffsetAttribute,
+            ast::StructMemberSizeAttribute>()) {
+      return;
+    }
+
+    UnhandledNode(diagnostics_, attr);
+  }
+
+  /// Adds the dependency from `from` to `to`, erroring if `to` cannot be
+  /// resolved.
+  void AddDependency(const ast::Node* from,
+                     Symbol to,
+                     const char* use,
+                     const char* action) {
+    auto* resolved = scope_stack_.Get(to);
+    if (!resolved) {
+      if (!IsBuiltin(to)) {
+        UnknownSymbol(to, from->source, use);
+        return;
+      }
+    }
+
+    if (auto* global = utils::Lookup(globals_, to);
+        global && global->node == resolved) {
+      if (dependency_edges_
+              .emplace(DependencyEdge{current_global_, global},
+                       DependencyInfo{from->source, action})
+              .second) {
+        current_global_->deps.emplace_back(global);
+      }
+    }
+
+    graph_.resolved_symbols.emplace(from, resolved);
+  }
+
+  /// @returns true if `name` is the name of a builtin function
+  bool IsBuiltin(Symbol name) const {
+    return sem::ParseBuiltinType(symbols_.NameFor(name)) !=
+           sem::BuiltinType::kNone;
+  }
+
+  /// Appends an error to the diagnostics that the given symbol cannot be
+  /// resolved.
+  void UnknownSymbol(Symbol name, Source source, const char* use) {
+    AddError(
+        diagnostics_,
+        "unknown " + std::string(use) + ": '" + symbols_.NameFor(name) + "'",
+        source);
+  }
+
+  using VariableMap = std::unordered_map<Symbol, const ast::Variable*>;
+  const SymbolTable& symbols_;
+  const GlobalMap& globals_;
+  diag::List& diagnostics_;
+  DependencyGraph& graph_;
+  DependencyEdges& dependency_edges_;
+
+  ScopeStack<const ast::Node*> scope_stack_;
+  Global* current_global_ = nullptr;
+};
+
+/// The global dependency analysis system
+struct DependencyAnalysis {
+ public:
+  /// Constructor
+  DependencyAnalysis(const SymbolTable& symbols,
+                     diag::List& diagnostics,
+                     DependencyGraph& graph)
+      : symbols_(symbols), diagnostics_(diagnostics), graph_(graph) {}
+
+  /// Performs global dependency analysis on the module, emitting any errors to
+  /// #diagnostics.
+  /// @returns true if analysis found no errors, otherwise false.
+  bool Run(const ast::Module& module) {
+    // Collect all the named globals from the AST module
+    GatherGlobals(module);
+
+    // Traverse the named globals to build the dependency graph
+    DetermineDependencies();
+
+    // Sort the globals into dependency order
+    SortGlobals();
+
+    // Dump the dependency graph if TINT_DUMP_DEPENDENCY_GRAPH is non-zero
+    DumpDependencyGraph();
+
+    graph_.ordered_globals = std::move(sorted_);
+
+    return !diagnostics_.contains_errors();
+  }
+
+ private:
+  /// @param node the ast::Node of the global declaration
+  /// @returns the symbol of the global declaration node
+  /// @note will raise an ICE if the node is not a type, function or variable
+  /// declaration
+  Symbol SymbolOf(const ast::Node* node) const {
+    return Switch(
+        node,  //
+        [&](const ast::TypeDecl* td) { return td->name; },
+        [&](const ast::Function* func) { return func->symbol; },
+        [&](const ast::Variable* var) { return var->symbol; },
+        [&](Default) {
+          UnhandledNode(diagnostics_, node);
+          return Symbol{};
+        });
+  }
+
+  /// @param node the ast::Node of the global declaration
+  /// @returns the name of the global declaration node
+  /// @note will raise an ICE if the node is not a type, function or variable
+  /// declaration
+  std::string NameOf(const ast::Node* node) const {
+    return symbols_.NameFor(SymbolOf(node));
+  }
+
+  /// @param node the ast::Node of the global declaration
+  /// @returns a string representation of the global declaration kind
+  /// @note will raise an ICE if the node is not a type, function or variable
+  /// declaration
+  std::string KindOf(const ast::Node* node) {
+    return Switch(
+        node,  //
+        [&](const ast::Struct*) { return "struct"; },
+        [&](const ast::Alias*) { return "alias"; },
+        [&](const ast::Function*) { return "function"; },
+        [&](const ast::Variable* var) { return var->is_const ? "let" : "var"; },
+        [&](Default) {
+          UnhandledNode(diagnostics_, node);
+          return "<error>";
+        });
+  }
+
+  /// Traverses `module`, collecting all the global declarations and populating
+  /// the #globals and #declaration_order fields.
+  void GatherGlobals(const ast::Module& module) {
+    for (auto* node : module.GlobalDeclarations()) {
+      auto* global = allocator_.Create(node);
+      globals_.emplace(SymbolOf(node), global);
+      declaration_order_.emplace_back(global);
+    }
+  }
+
+  /// Walks the global declarations, determining the dependencies of each global
+  /// and adding these to each global's Global::deps field.
+  void DetermineDependencies() {
+    DependencyScanner scanner(symbols_, globals_, diagnostics_, graph_,
+                              dependency_edges_);
+    for (auto* global : declaration_order_) {
+      scanner.Scan(global);
+    }
+  }
+
+  /// Performs a depth-first traversal of `root`'s dependencies, calling `enter`
+  /// as the function decends into each dependency and `exit` when bubbling back
+  /// up towards the root.
+  /// @param enter is a function with the signature: `bool(Global*)`. The
+  /// `enter` function returns true if TraverseDependencies() should traverse
+  /// the dependency, otherwise it will be skipped.
+  /// @param exit is a function with the signature: `void(Global*)`. The `exit`
+  /// function is only called if the corresponding `enter` call returned true.
+  template <typename ENTER, typename EXIT>
+  void TraverseDependencies(const Global* root, ENTER&& enter, EXIT&& exit) {
+    // Entry is a single entry in the traversal stack. Entry points to a
+    // dep_idx'th dependency of Entry::global.
+    struct Entry {
+      const Global* global;  // The parent global
+      size_t dep_idx;        // The dependency index in `global->deps`
+    };
+
+    if (!enter(root)) {
+      return;
+    }
+
+    std::vector<Entry> stack{Entry{root, 0}};
+    while (true) {
+      auto& entry = stack.back();
+      // Have we exhausted the dependencies of entry.global?
+      if (entry.dep_idx < entry.global->deps.size()) {
+        // No, there's more dependencies to traverse.
+        auto& dep = entry.global->deps[entry.dep_idx];
+        // Does the caller want to enter this dependency?
+        if (enter(dep)) {                  // Yes.
+          stack.push_back(Entry{dep, 0});  // Enter the dependency.
+        } else {
+          entry.dep_idx++;  // No. Skip this node.
+        }
+      } else {
+        // Yes. Time to back up.
+        // Exit this global, pop the stack, and if there's another parent node,
+        // increment its dependency index, and loop again.
+        exit(entry.global);
+        stack.pop_back();
+        if (stack.empty()) {
+          return;  // All done.
+        }
+        stack.back().dep_idx++;
+      }
+    }
+  }
+
+  /// SortGlobals sorts the globals into dependency order, erroring if cyclic
+  /// dependencies are found. The sorted dependencies are assigned to #sorted.
+  void SortGlobals() {
+    if (diagnostics_.contains_errors()) {
+      return;  // This code assumes there are no undeclared identifiers.
+    }
+
+    std::unordered_set<const Global*> visited;
+    for (auto* global : declaration_order_) {
+      utils::UniqueVector<const Global*> stack;
+      TraverseDependencies(
+          global,
+          [&](const Global* g) {  // Enter
+            if (!stack.add(g)) {
+              CyclicDependencyFound(g, stack);
+              return false;
+            }
+            if (sorted_.contains(g->node)) {
+              // Visited this global already.
+              // stack was pushed, but exit() will not be called when we return
+              // false, so pop here.
+              stack.pop_back();
+              return false;
+            }
+            return true;
+          },
+          [&](const Global* g) {  // Exit. Only called if Enter returned true.
+            sorted_.add(g->node);
+            stack.pop_back();
+          });
+
+      sorted_.add(global->node);
+
+      if (!stack.empty()) {
+        // Each stack.push() must have a corresponding stack.pop_back().
+        TINT_ICE(Resolver, diagnostics_)
+            << "stack not empty after returning from TraverseDependencies()";
+      }
+    }
+  }
+
+  /// DepInfoFor() looks up the global dependency information for the dependency
+  /// of global `from` depending on `to`.
+  /// @note will raise an ICE if the edge is not found.
+  DependencyInfo DepInfoFor(const Global* from, const Global* to) const {
+    auto it = dependency_edges_.find(DependencyEdge{from, to});
+    if (it != dependency_edges_.end()) {
+      return it->second;
+    }
+    TINT_ICE(Resolver, diagnostics_)
+        << "failed to find dependency info for edge: '" << NameOf(from->node)
+        << "' -> '" << NameOf(to->node) << "'";
+    return {};
+  }
+
+  /// CyclicDependencyFound() emits an error diagnostic for a cyclic dependency.
+  /// @param root is the global that starts the cyclic dependency, which must be
+  /// found in `stack`.
+  /// @param stack is the global dependency stack that contains a loop.
+  void CyclicDependencyFound(const Global* root,
+                             const std::vector<const Global*>& stack) {
+    std::stringstream msg;
+    msg << "cyclic dependency found: ";
+    constexpr size_t kLoopNotStarted = ~0u;
+    size_t loop_start = kLoopNotStarted;
+    for (size_t i = 0; i < stack.size(); i++) {
+      auto* e = stack[i];
+      if (loop_start == kLoopNotStarted && e == root) {
+        loop_start = i;
+      }
+      if (loop_start != kLoopNotStarted) {
+        msg << "'" << NameOf(e->node) << "' -> ";
+      }
+    }
+    msg << "'" << NameOf(root->node) << "'";
+    AddError(diagnostics_, msg.str(), root->node->source);
+    for (size_t i = loop_start; i < stack.size(); i++) {
+      auto* from = stack[i];
+      auto* to = (i + 1 < stack.size()) ? stack[i + 1] : stack[loop_start];
+      auto info = DepInfoFor(from, to);
+      AddNote(diagnostics_,
+              KindOf(from->node) + " '" + NameOf(from->node) + "' " +
+                  info.action + " " + KindOf(to->node) + " '" +
+                  NameOf(to->node) + "' here",
+              info.source);
+    }
+  }
+
+  void DumpDependencyGraph() {
+#if TINT_DUMP_DEPENDENCY_GRAPH == 0
+    if ((true)) {
+      return;
+    }
+#endif  // TINT_DUMP_DEPENDENCY_GRAPH
+    printf("=========================\n");
+    printf("------ declaration ------ \n");
+    for (auto* global : declaration_order_) {
+      printf("%s\n", NameOf(global->node).c_str());
+    }
+    printf("------ dependencies ------ \n");
+    for (auto* node : sorted_) {
+      auto symbol = SymbolOf(node);
+      auto* global = globals_.at(symbol);
+      printf("%s depends on:\n", symbols_.NameFor(symbol).c_str());
+      for (auto* dep : global->deps) {
+        printf("  %s\n", NameOf(dep->node).c_str());
+      }
+    }
+    printf("=========================\n");
+  }
+
+  /// Program symbols
+  const SymbolTable& symbols_;
+
+  /// Program diagnostics
+  diag::List& diagnostics_;
+
+  /// The resulting dependency graph
+  DependencyGraph& graph_;
+
+  /// Allocator of Globals
+  BlockAllocator<Global> allocator_;
+
+  /// Global map, keyed by name. Populated by GatherGlobals().
+  GlobalMap globals_;
+
+  /// Map of DependencyEdge to DependencyInfo. Populated by
+  /// DetermineDependencies().
+  DependencyEdges dependency_edges_;
+
+  /// Globals in declaration order. Populated by GatherGlobals().
+  std::vector<Global*> declaration_order_;
+
+  /// Globals in sorted dependency order. Populated by SortGlobals().
+  utils::UniqueVector<const ast::Node*> sorted_;
+};
+
+}  // namespace
+
+DependencyGraph::DependencyGraph() = default;
+DependencyGraph::DependencyGraph(DependencyGraph&&) = default;
+DependencyGraph::~DependencyGraph() = default;
+
+bool DependencyGraph::Build(const ast::Module& module,
+                            const SymbolTable& symbols,
+                            diag::List& diagnostics,
+                            DependencyGraph& output) {
+  DependencyAnalysis da{symbols, diagnostics, output};
+  return da.Run(module);
+}
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/dependency_graph.h b/src/tint/resolver/dependency_graph.h
new file mode 100644
index 0000000..a943708
--- /dev/null
+++ b/src/tint/resolver/dependency_graph.h
@@ -0,0 +1,66 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
+#define SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "src/tint/ast/module.h"
+#include "src/tint/diagnostic/diagnostic.h"
+
+namespace tint {
+namespace resolver {
+
+/// DependencyGraph holds information about module-scope declaration dependency
+/// analysis and symbol resolutions.
+struct DependencyGraph {
+  /// Constructor
+  DependencyGraph();
+  /// Move-constructor
+  DependencyGraph(DependencyGraph&&);
+  /// Destructor
+  ~DependencyGraph();
+
+  /// Build() performs symbol resolution and dependency analysis on `module`,
+  /// populating `output` with the resulting dependency graph.
+  /// @param module the AST module to analyse
+  /// @param symbols the symbol table
+  /// @param diagnostics the diagnostic list to populate with errors / warnings
+  /// @param output the resulting DependencyGraph
+  /// @returns true on success, false on error
+  static bool Build(const ast::Module& module,
+                    const SymbolTable& symbols,
+                    diag::List& diagnostics,
+                    DependencyGraph& output);
+
+  /// All globals in dependency-sorted order.
+  std::vector<const ast::Node*> ordered_globals;
+
+  /// Map of ast::IdentifierExpression or ast::TypeName to a type, function, or
+  /// variable that declares the symbol.
+  std::unordered_map<const ast::Node*, const ast::Node*> resolved_symbols;
+
+  /// Map of ast::Variable to a type, function, or variable that is shadowed by
+  /// the variable key. A declaration (X) shadows another (Y) if X and Y use
+  /// the same symbol, and X is declared in a sub-scope of the scope that
+  /// declares Y.
+  std::unordered_map<const ast::Variable*, const ast::Node*> shadows;
+};
+
+}  // namespace resolver
+}  // namespace tint
+
+#endif  // SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
new file mode 100644
index 0000000..094c908
--- /dev/null
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -0,0 +1,1342 @@
+// 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 <string>
+#include <tuple>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/dependency_graph.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ::testing::ElementsAre;
+
+template <typename T>
+class ResolverDependencyGraphTestWithParam : public ResolverTestWithParam<T> {
+ public:
+  DependencyGraph Build(std::string expected_error = "") {
+    DependencyGraph graph;
+    auto result = DependencyGraph::Build(this->AST(), this->Symbols(),
+                                         this->Diagnostics(), graph);
+    if (expected_error.empty()) {
+      EXPECT_TRUE(result) << this->Diagnostics().str();
+    } else {
+      EXPECT_FALSE(result);
+      EXPECT_EQ(expected_error, this->Diagnostics().str());
+    }
+    return graph;
+  }
+};
+
+using ResolverDependencyGraphTest =
+    ResolverDependencyGraphTestWithParam<::testing::Test>;
+
+////////////////////////////////////////////////////////////////////////////////
+// Parameterized test helpers
+////////////////////////////////////////////////////////////////////////////////
+
+/// SymbolDeclKind is used by parameterized tests to enumerate the different
+/// kinds of symbol declarations.
+enum class SymbolDeclKind {
+  GlobalVar,
+  GlobalLet,
+  Alias,
+  Struct,
+  Function,
+  Parameter,
+  LocalVar,
+  LocalLet,
+  NestedLocalVar,
+  NestedLocalLet,
+};
+
+static constexpr SymbolDeclKind kAllSymbolDeclKinds[] = {
+    SymbolDeclKind::GlobalVar,      SymbolDeclKind::GlobalLet,
+    SymbolDeclKind::Alias,          SymbolDeclKind::Struct,
+    SymbolDeclKind::Function,       SymbolDeclKind::Parameter,
+    SymbolDeclKind::LocalVar,       SymbolDeclKind::LocalLet,
+    SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet,
+};
+
+static constexpr SymbolDeclKind kTypeDeclKinds[] = {
+    SymbolDeclKind::Alias,
+    SymbolDeclKind::Struct,
+};
+
+static constexpr SymbolDeclKind kValueDeclKinds[] = {
+    SymbolDeclKind::GlobalVar,      SymbolDeclKind::GlobalLet,
+    SymbolDeclKind::Parameter,      SymbolDeclKind::LocalVar,
+    SymbolDeclKind::LocalLet,       SymbolDeclKind::NestedLocalVar,
+    SymbolDeclKind::NestedLocalLet,
+};
+
+static constexpr SymbolDeclKind kGlobalDeclKinds[] = {
+    SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalLet, SymbolDeclKind::Alias,
+    SymbolDeclKind::Struct,    SymbolDeclKind::Function,
+};
+
+static constexpr SymbolDeclKind kLocalDeclKinds[] = {
+    SymbolDeclKind::Parameter,      SymbolDeclKind::LocalVar,
+    SymbolDeclKind::LocalLet,       SymbolDeclKind::NestedLocalVar,
+    SymbolDeclKind::NestedLocalLet,
+};
+
+static constexpr SymbolDeclKind kGlobalValueDeclKinds[] = {
+    SymbolDeclKind::GlobalVar,
+    SymbolDeclKind::GlobalLet,
+};
+
+static constexpr SymbolDeclKind kFuncDeclKinds[] = {
+    SymbolDeclKind::Function,
+};
+
+/// SymbolUseKind is used by parameterized tests to enumerate the different
+/// kinds of symbol uses.
+enum class SymbolUseKind {
+  GlobalVarType,
+  GlobalVarArrayElemType,
+  GlobalVarArraySizeValue,
+  GlobalVarVectorElemType,
+  GlobalVarMatrixElemType,
+  GlobalVarSampledTexElemType,
+  GlobalVarMultisampledTexElemType,
+  GlobalVarValue,
+  GlobalLetType,
+  GlobalLetArrayElemType,
+  GlobalLetArraySizeValue,
+  GlobalLetVectorElemType,
+  GlobalLetMatrixElemType,
+  GlobalLetValue,
+  AliasType,
+  StructMemberType,
+  CallFunction,
+  ParameterType,
+  LocalVarType,
+  LocalVarArrayElemType,
+  LocalVarArraySizeValue,
+  LocalVarVectorElemType,
+  LocalVarMatrixElemType,
+  LocalVarValue,
+  LocalLetType,
+  LocalLetValue,
+  NestedLocalVarType,
+  NestedLocalVarValue,
+  NestedLocalLetType,
+  NestedLocalLetValue,
+  WorkgroupSizeValue,
+};
+
+static constexpr SymbolUseKind kTypeUseKinds[] = {
+    SymbolUseKind::GlobalVarType,
+    SymbolUseKind::GlobalVarArrayElemType,
+    SymbolUseKind::GlobalVarArraySizeValue,
+    SymbolUseKind::GlobalVarVectorElemType,
+    SymbolUseKind::GlobalVarMatrixElemType,
+    SymbolUseKind::GlobalVarSampledTexElemType,
+    SymbolUseKind::GlobalVarMultisampledTexElemType,
+    SymbolUseKind::GlobalLetType,
+    SymbolUseKind::GlobalLetArrayElemType,
+    SymbolUseKind::GlobalLetArraySizeValue,
+    SymbolUseKind::GlobalLetVectorElemType,
+    SymbolUseKind::GlobalLetMatrixElemType,
+    SymbolUseKind::AliasType,
+    SymbolUseKind::StructMemberType,
+    SymbolUseKind::ParameterType,
+    SymbolUseKind::LocalVarType,
+    SymbolUseKind::LocalVarArrayElemType,
+    SymbolUseKind::LocalVarArraySizeValue,
+    SymbolUseKind::LocalVarVectorElemType,
+    SymbolUseKind::LocalVarMatrixElemType,
+    SymbolUseKind::LocalLetType,
+    SymbolUseKind::NestedLocalVarType,
+    SymbolUseKind::NestedLocalLetType,
+};
+
+static constexpr SymbolUseKind kValueUseKinds[] = {
+    SymbolUseKind::GlobalVarValue,      SymbolUseKind::GlobalLetValue,
+    SymbolUseKind::LocalVarValue,       SymbolUseKind::LocalLetValue,
+    SymbolUseKind::NestedLocalVarValue, SymbolUseKind::NestedLocalLetValue,
+    SymbolUseKind::WorkgroupSizeValue,
+};
+
+static constexpr SymbolUseKind kFuncUseKinds[] = {
+    SymbolUseKind::CallFunction,
+};
+
+/// @returns the description of the symbol declaration kind.
+/// @note: This differs from the strings used in diagnostic messages.
+std::ostream& operator<<(std::ostream& out, SymbolDeclKind kind) {
+  switch (kind) {
+    case SymbolDeclKind::GlobalVar:
+      return out << "global var";
+    case SymbolDeclKind::GlobalLet:
+      return out << "global let";
+    case SymbolDeclKind::Alias:
+      return out << "alias";
+    case SymbolDeclKind::Struct:
+      return out << "struct";
+    case SymbolDeclKind::Function:
+      return out << "function";
+    case SymbolDeclKind::Parameter:
+      return out << "parameter";
+    case SymbolDeclKind::LocalVar:
+      return out << "local var";
+    case SymbolDeclKind::LocalLet:
+      return out << "local let";
+    case SymbolDeclKind::NestedLocalVar:
+      return out << "nested local var";
+    case SymbolDeclKind::NestedLocalLet:
+      return out << "nested local let";
+  }
+  return out << "<unknown>";
+}
+
+/// @returns the description of the symbol use kind.
+/// @note: This differs from the strings used in diagnostic messages.
+std::ostream& operator<<(std::ostream& out, SymbolUseKind kind) {
+  switch (kind) {
+    case SymbolUseKind::GlobalVarType:
+      return out << "global var type";
+    case SymbolUseKind::GlobalVarValue:
+      return out << "global var value";
+    case SymbolUseKind::GlobalVarArrayElemType:
+      return out << "global var array element type";
+    case SymbolUseKind::GlobalVarArraySizeValue:
+      return out << "global var array size value";
+    case SymbolUseKind::GlobalVarVectorElemType:
+      return out << "global var vector element type";
+    case SymbolUseKind::GlobalVarMatrixElemType:
+      return out << "global var matrix element type";
+    case SymbolUseKind::GlobalVarSampledTexElemType:
+      return out << "global var sampled_texture element type";
+    case SymbolUseKind::GlobalVarMultisampledTexElemType:
+      return out << "global var multisampled_texture element type";
+    case SymbolUseKind::GlobalLetType:
+      return out << "global let type";
+    case SymbolUseKind::GlobalLetValue:
+      return out << "global let value";
+    case SymbolUseKind::GlobalLetArrayElemType:
+      return out << "global let array element type";
+    case SymbolUseKind::GlobalLetArraySizeValue:
+      return out << "global let array size value";
+    case SymbolUseKind::GlobalLetVectorElemType:
+      return out << "global let vector element type";
+    case SymbolUseKind::GlobalLetMatrixElemType:
+      return out << "global let matrix element type";
+    case SymbolUseKind::AliasType:
+      return out << "alias type";
+    case SymbolUseKind::StructMemberType:
+      return out << "struct member type";
+    case SymbolUseKind::CallFunction:
+      return out << "call function";
+    case SymbolUseKind::ParameterType:
+      return out << "parameter type";
+    case SymbolUseKind::LocalVarType:
+      return out << "local var type";
+    case SymbolUseKind::LocalVarArrayElemType:
+      return out << "local var array element type";
+    case SymbolUseKind::LocalVarArraySizeValue:
+      return out << "local var array size value";
+    case SymbolUseKind::LocalVarVectorElemType:
+      return out << "local var vector element type";
+    case SymbolUseKind::LocalVarMatrixElemType:
+      return out << "local var matrix element type";
+    case SymbolUseKind::LocalVarValue:
+      return out << "local var value";
+    case SymbolUseKind::LocalLetType:
+      return out << "local let type";
+    case SymbolUseKind::LocalLetValue:
+      return out << "local let value";
+    case SymbolUseKind::NestedLocalVarType:
+      return out << "nested local var type";
+    case SymbolUseKind::NestedLocalVarValue:
+      return out << "nested local var value";
+    case SymbolUseKind::NestedLocalLetType:
+      return out << "nested local let type";
+    case SymbolUseKind::NestedLocalLetValue:
+      return out << "nested local let value";
+    case SymbolUseKind::WorkgroupSizeValue:
+      return out << "workgroup size value";
+  }
+  return out << "<unknown>";
+}
+
+/// @returns the the diagnostic message name used for the given use
+std::string DiagString(SymbolUseKind kind) {
+  switch (kind) {
+    case SymbolUseKind::GlobalVarType:
+    case SymbolUseKind::GlobalVarArrayElemType:
+    case SymbolUseKind::GlobalVarVectorElemType:
+    case SymbolUseKind::GlobalVarMatrixElemType:
+    case SymbolUseKind::GlobalVarSampledTexElemType:
+    case SymbolUseKind::GlobalVarMultisampledTexElemType:
+    case SymbolUseKind::GlobalLetType:
+    case SymbolUseKind::GlobalLetArrayElemType:
+    case SymbolUseKind::GlobalLetVectorElemType:
+    case SymbolUseKind::GlobalLetMatrixElemType:
+    case SymbolUseKind::AliasType:
+    case SymbolUseKind::StructMemberType:
+    case SymbolUseKind::ParameterType:
+    case SymbolUseKind::LocalVarType:
+    case SymbolUseKind::LocalVarArrayElemType:
+    case SymbolUseKind::LocalVarVectorElemType:
+    case SymbolUseKind::LocalVarMatrixElemType:
+    case SymbolUseKind::LocalLetType:
+    case SymbolUseKind::NestedLocalVarType:
+    case SymbolUseKind::NestedLocalLetType:
+      return "type";
+    case SymbolUseKind::GlobalVarValue:
+    case SymbolUseKind::GlobalVarArraySizeValue:
+    case SymbolUseKind::GlobalLetValue:
+    case SymbolUseKind::GlobalLetArraySizeValue:
+    case SymbolUseKind::LocalVarValue:
+    case SymbolUseKind::LocalVarArraySizeValue:
+    case SymbolUseKind::LocalLetValue:
+    case SymbolUseKind::NestedLocalVarValue:
+    case SymbolUseKind::NestedLocalLetValue:
+    case SymbolUseKind::WorkgroupSizeValue:
+      return "identifier";
+    case SymbolUseKind::CallFunction:
+      return "function";
+  }
+  return "<unknown>";
+}
+
+/// @returns the declaration scope depth for the symbol declaration kind.
+///          Globals are at depth 0, parameters and locals are at depth 1,
+///          nested locals are at depth 2.
+int ScopeDepth(SymbolDeclKind kind) {
+  switch (kind) {
+    case SymbolDeclKind::GlobalVar:
+    case SymbolDeclKind::GlobalLet:
+    case SymbolDeclKind::Alias:
+    case SymbolDeclKind::Struct:
+    case SymbolDeclKind::Function:
+      return 0;
+    case SymbolDeclKind::Parameter:
+    case SymbolDeclKind::LocalVar:
+    case SymbolDeclKind::LocalLet:
+      return 1;
+    case SymbolDeclKind::NestedLocalVar:
+    case SymbolDeclKind::NestedLocalLet:
+      return 2;
+  }
+  return -1;
+}
+
+/// @returns the use depth for the symbol use kind.
+///          Globals are at depth 0, parameters and locals are at depth 1,
+///          nested locals are at depth 2.
+int ScopeDepth(SymbolUseKind kind) {
+  switch (kind) {
+    case SymbolUseKind::GlobalVarType:
+    case SymbolUseKind::GlobalVarValue:
+    case SymbolUseKind::GlobalVarArrayElemType:
+    case SymbolUseKind::GlobalVarArraySizeValue:
+    case SymbolUseKind::GlobalVarVectorElemType:
+    case SymbolUseKind::GlobalVarMatrixElemType:
+    case SymbolUseKind::GlobalVarSampledTexElemType:
+    case SymbolUseKind::GlobalVarMultisampledTexElemType:
+    case SymbolUseKind::GlobalLetType:
+    case SymbolUseKind::GlobalLetValue:
+    case SymbolUseKind::GlobalLetArrayElemType:
+    case SymbolUseKind::GlobalLetArraySizeValue:
+    case SymbolUseKind::GlobalLetVectorElemType:
+    case SymbolUseKind::GlobalLetMatrixElemType:
+    case SymbolUseKind::AliasType:
+    case SymbolUseKind::StructMemberType:
+    case SymbolUseKind::WorkgroupSizeValue:
+      return 0;
+    case SymbolUseKind::CallFunction:
+    case SymbolUseKind::ParameterType:
+    case SymbolUseKind::LocalVarType:
+    case SymbolUseKind::LocalVarArrayElemType:
+    case SymbolUseKind::LocalVarArraySizeValue:
+    case SymbolUseKind::LocalVarVectorElemType:
+    case SymbolUseKind::LocalVarMatrixElemType:
+    case SymbolUseKind::LocalVarValue:
+    case SymbolUseKind::LocalLetType:
+    case SymbolUseKind::LocalLetValue:
+      return 1;
+    case SymbolUseKind::NestedLocalVarType:
+    case SymbolUseKind::NestedLocalVarValue:
+    case SymbolUseKind::NestedLocalLetType:
+    case SymbolUseKind::NestedLocalLetValue:
+      return 2;
+  }
+  return -1;
+}
+
+/// A helper for building programs that exercise symbol declaration tests.
+struct SymbolTestHelper {
+  /// The program builder
+  ProgramBuilder* const builder;
+  /// Parameters to a function that may need to be built
+  std::vector<const ast::Variable*> parameters;
+  /// Shallow function var / let declaration statements
+  std::vector<const ast::Statement*> statements;
+  /// Nested function local var / let declaration statements
+  std::vector<const ast::Statement*> nested_statements;
+  /// Function attributes
+  ast::AttributeList func_attrs;
+
+  /// Constructor
+  /// @param builder the program builder
+  explicit SymbolTestHelper(ProgramBuilder* builder);
+
+  /// Destructor.
+  ~SymbolTestHelper();
+
+  /// Declares a symbol with the given kind
+  /// @param kind the kind of symbol declaration
+  /// @param symbol the symbol to use for the declaration
+  /// @param source the source of the declaration
+  /// @returns the declaration node
+  const ast::Node* Add(SymbolDeclKind kind, Symbol symbol, Source source);
+
+  /// Declares a use of a symbol with the given kind
+  /// @param kind the kind of symbol use
+  /// @param symbol the declaration symbol to use
+  /// @param source the source of the use
+  /// @returns the use node
+  const ast::Node* Add(SymbolUseKind kind, Symbol symbol, Source source);
+
+  /// Builds a function, if any parameter or local declarations have been added
+  void Build();
+};
+
+SymbolTestHelper::SymbolTestHelper(ProgramBuilder* b) : builder(b) {}
+
+SymbolTestHelper::~SymbolTestHelper() {}
+
+const ast::Node* SymbolTestHelper::Add(SymbolDeclKind kind,
+                                       Symbol symbol,
+                                       Source source) {
+  auto& b = *builder;
+  switch (kind) {
+    case SymbolDeclKind::GlobalVar:
+      return b.Global(source, symbol, b.ty.i32(), ast::StorageClass::kPrivate);
+    case SymbolDeclKind::GlobalLet:
+      return b.GlobalConst(source, symbol, b.ty.i32(), b.Expr(1));
+    case SymbolDeclKind::Alias:
+      return b.Alias(source, symbol, b.ty.i32());
+    case SymbolDeclKind::Struct:
+      return b.Structure(source, symbol, {b.Member("m", b.ty.i32())});
+    case SymbolDeclKind::Function:
+      return b.Func(source, symbol, {}, b.ty.void_(), {});
+    case SymbolDeclKind::Parameter: {
+      auto* node = b.Param(source, symbol, b.ty.i32());
+      parameters.emplace_back(node);
+      return node;
+    }
+    case SymbolDeclKind::LocalVar: {
+      auto* node = b.Var(source, symbol, b.ty.i32());
+      statements.emplace_back(b.Decl(node));
+      return node;
+    }
+    case SymbolDeclKind::LocalLet: {
+      auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1));
+      statements.emplace_back(b.Decl(node));
+      return node;
+    }
+    case SymbolDeclKind::NestedLocalVar: {
+      auto* node = b.Var(source, symbol, b.ty.i32());
+      nested_statements.emplace_back(b.Decl(node));
+      return node;
+    }
+    case SymbolDeclKind::NestedLocalLet: {
+      auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1));
+      nested_statements.emplace_back(b.Decl(node));
+      return node;
+    }
+  }
+  return nullptr;
+}
+
+const ast::Node* SymbolTestHelper::Add(SymbolUseKind kind,
+                                       Symbol symbol,
+                                       Source source) {
+  auto& b = *builder;
+  switch (kind) {
+    case SymbolUseKind::GlobalVarType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(), node, ast::StorageClass::kPrivate);
+      return node;
+    }
+    case SymbolUseKind::GlobalVarArrayElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(), b.ty.array(node, 4), ast::StorageClass::kPrivate);
+      return node;
+    }
+    case SymbolUseKind::GlobalVarArraySizeValue: {
+      auto* node = b.Expr(source, symbol);
+      b.Global(b.Sym(), b.ty.array(b.ty.i32(), node),
+               ast::StorageClass::kPrivate);
+      return node;
+    }
+    case SymbolUseKind::GlobalVarVectorElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(), b.ty.vec3(node), ast::StorageClass::kPrivate);
+      return node;
+    }
+    case SymbolUseKind::GlobalVarMatrixElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(), b.ty.mat3x4(node), ast::StorageClass::kPrivate);
+      return node;
+    }
+    case SymbolUseKind::GlobalVarSampledTexElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(), b.ty.sampled_texture(ast::TextureDimension::k2d, node));
+      return node;
+    }
+    case SymbolUseKind::GlobalVarMultisampledTexElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Global(b.Sym(),
+               b.ty.multisampled_texture(ast::TextureDimension::k2d, node));
+      return node;
+    }
+    case SymbolUseKind::GlobalVarValue: {
+      auto* node = b.Expr(source, symbol);
+      b.Global(b.Sym(), b.ty.i32(), ast::StorageClass::kPrivate, node);
+      return node;
+    }
+    case SymbolUseKind::GlobalLetType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.GlobalConst(b.Sym(), node, b.Expr(1));
+      return node;
+    }
+    case SymbolUseKind::GlobalLetArrayElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.GlobalConst(b.Sym(), b.ty.array(node, 4), b.Expr(1));
+      return node;
+    }
+    case SymbolUseKind::GlobalLetArraySizeValue: {
+      auto* node = b.Expr(source, symbol);
+      b.GlobalConst(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1));
+      return node;
+    }
+    case SymbolUseKind::GlobalLetVectorElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1));
+      return node;
+    }
+    case SymbolUseKind::GlobalLetMatrixElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1));
+      return node;
+    }
+    case SymbolUseKind::GlobalLetValue: {
+      auto* node = b.Expr(source, symbol);
+      b.GlobalConst(b.Sym(), b.ty.i32(), node);
+      return node;
+    }
+    case SymbolUseKind::AliasType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Alias(b.Sym(), node);
+      return node;
+    }
+    case SymbolUseKind::StructMemberType: {
+      auto* node = b.ty.type_name(source, symbol);
+      b.Structure(b.Sym(), {b.Member("m", node)});
+      return node;
+    }
+    case SymbolUseKind::CallFunction: {
+      auto* node = b.Expr(source, symbol);
+      statements.emplace_back(b.CallStmt(b.Call(node)));
+      return node;
+    }
+    case SymbolUseKind::ParameterType: {
+      auto* node = b.ty.type_name(source, symbol);
+      parameters.emplace_back(b.Param(b.Sym(), node));
+      return node;
+    }
+    case SymbolUseKind::LocalVarType: {
+      auto* node = b.ty.type_name(source, symbol);
+      statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
+      return node;
+    }
+    case SymbolUseKind::LocalVarArrayElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      statements.emplace_back(
+          b.Decl(b.Var(b.Sym(), b.ty.array(node, 4), b.Expr(1))));
+      return node;
+    }
+    case SymbolUseKind::LocalVarArraySizeValue: {
+      auto* node = b.Expr(source, symbol);
+      statements.emplace_back(
+          b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1))));
+      return node;
+    }
+    case SymbolUseKind::LocalVarVectorElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.vec3(node))));
+      return node;
+    }
+    case SymbolUseKind::LocalVarMatrixElemType: {
+      auto* node = b.ty.type_name(source, symbol);
+      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node))));
+      return node;
+    }
+    case SymbolUseKind::LocalVarValue: {
+      auto* node = b.Expr(source, symbol);
+      statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
+      return node;
+    }
+    case SymbolUseKind::LocalLetType: {
+      auto* node = b.ty.type_name(source, symbol);
+      statements.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1))));
+      return node;
+    }
+    case SymbolUseKind::LocalLetValue: {
+      auto* node = b.Expr(source, symbol);
+      statements.emplace_back(b.Decl(b.Const(b.Sym(), b.ty.i32(), node)));
+      return node;
+    }
+    case SymbolUseKind::NestedLocalVarType: {
+      auto* node = b.ty.type_name(source, symbol);
+      nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
+      return node;
+    }
+    case SymbolUseKind::NestedLocalVarValue: {
+      auto* node = b.Expr(source, symbol);
+      nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
+      return node;
+    }
+    case SymbolUseKind::NestedLocalLetType: {
+      auto* node = b.ty.type_name(source, symbol);
+      nested_statements.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1))));
+      return node;
+    }
+    case SymbolUseKind::NestedLocalLetValue: {
+      auto* node = b.Expr(source, symbol);
+      nested_statements.emplace_back(
+          b.Decl(b.Const(b.Sym(), b.ty.i32(), node)));
+      return node;
+    }
+    case SymbolUseKind::WorkgroupSizeValue: {
+      auto* node = b.Expr(source, symbol);
+      func_attrs.emplace_back(b.WorkgroupSize(1, node, 2));
+      return node;
+    }
+  }
+  return nullptr;
+}
+
+void SymbolTestHelper::Build() {
+  auto& b = *builder;
+  if (!nested_statements.empty()) {
+    statements.emplace_back(b.Block(nested_statements));
+    nested_statements.clear();
+  }
+  if (!parameters.empty() || !statements.empty() || !func_attrs.empty()) {
+    b.Func("func", parameters, b.ty.void_(), statements, func_attrs);
+    parameters.clear();
+    statements.clear();
+    func_attrs.clear();
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Used-before-declarated tests
+////////////////////////////////////////////////////////////////////////////////
+namespace used_before_decl_tests {
+
+using ResolverDependencyGraphUsedBeforeDeclTest = ResolverDependencyGraphTest;
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, FuncCall) {
+  // fn A() { B(); }
+  // fn B() {}
+
+  Func("A", {}, ty.void_(), {CallStmt(Call(Expr(Source{{12, 34}}, "B")))});
+  Func(Source{{56, 78}}, "B", {}, ty.void_(), {Return()});
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeConstructed) {
+  // fn F() {
+  //   { _ = T(); }
+  // }
+  // type T = i32;
+
+  Func("F", {}, ty.void_(),
+       {Block(Ignore(Construct(ty.type_name(Source{{12, 34}}, "T"))))});
+  Alias(Source{{56, 78}}, "T", ty.i32());
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByLocal) {
+  // fn F() {
+  //   { var v : T; }
+  // }
+  // type T = i32;
+
+  Func("F", {}, ty.void_(),
+       {Block(Decl(Var("v", ty.type_name(Source{{12, 34}}, "T"))))});
+  Alias(Source{{56, 78}}, "T", ty.i32());
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByParam) {
+  // fn F(p : T) {}
+  // type T = i32;
+
+  Func("F", {Param("p", ty.type_name(Source{{12, 34}}, "T"))}, ty.void_(), {});
+  Alias(Source{{56, 78}}, "T", ty.i32());
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedAsReturnType) {
+  // fn F() -> T {}
+  // type T = i32;
+
+  Func("F", {}, ty.type_name(Source{{12, 34}}, "T"), {});
+  Alias(Source{{56, 78}}, "T", ty.i32());
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeByStructMember) {
+  // struct S { m : T };
+  // type T = i32;
+
+  Structure("S", {Member("m", ty.type_name(Source{{12, 34}}, "T"))});
+  Alias(Source{{56, 78}}, "T", ty.i32());
+
+  Build();
+}
+
+TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, VarUsed) {
+  // fn F() {
+  //   { G = 3.14f; }
+  // }
+  // var G: f32 = 2.1;
+
+  Func("F", ast::VariableList{}, ty.void_(),
+       {Block(Assign(Expr(Source{{12, 34}}, "G"), 3.14f))});
+
+  Global(Source{{56, 78}}, "G", ty.f32(), ast::StorageClass::kPrivate,
+         Expr(2.1f));
+
+  Build();
+}
+
+}  // namespace used_before_decl_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// Undeclared symbol tests
+////////////////////////////////////////////////////////////////////////////////
+namespace undeclared_tests {
+
+using ResolverDependencyGraphUndeclaredSymbolTest =
+    ResolverDependencyGraphTestWithParam<SymbolUseKind>;
+
+TEST_P(ResolverDependencyGraphUndeclaredSymbolTest, Test) {
+  const Symbol symbol = Sym("SYMBOL");
+  const auto use_kind = GetParam();
+
+  // Build a use of a non-existent symbol
+  SymbolTestHelper helper(this);
+  helper.Add(use_kind, symbol, Source{{56, 78}});
+  helper.Build();
+
+  Build("56:78 error: unknown " + DiagString(use_kind) + ": 'SYMBOL'");
+}
+
+INSTANTIATE_TEST_SUITE_P(Types,
+                         ResolverDependencyGraphUndeclaredSymbolTest,
+                         testing::ValuesIn(kTypeUseKinds));
+
+INSTANTIATE_TEST_SUITE_P(Values,
+                         ResolverDependencyGraphUndeclaredSymbolTest,
+                         testing::ValuesIn(kValueUseKinds));
+
+INSTANTIATE_TEST_SUITE_P(Functions,
+                         ResolverDependencyGraphUndeclaredSymbolTest,
+                         testing::ValuesIn(kFuncUseKinds));
+
+}  // namespace undeclared_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// Self reference by decl
+////////////////////////////////////////////////////////////////////////////////
+namespace undeclared_tests {
+
+using ResolverDependencyGraphDeclSelfUse = ResolverDependencyGraphTest;
+
+TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalVar) {
+  const Symbol symbol = Sym("SYMBOL");
+  Global(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123));
+  Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL'
+12:34 note: var 'SYMBOL' references var 'SYMBOL' here)");
+}
+
+TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalLet) {
+  const Symbol symbol = Sym("SYMBOL");
+  GlobalConst(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123));
+  Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL'
+12:34 note: let 'SYMBOL' references let 'SYMBOL' here)");
+}
+
+TEST_F(ResolverDependencyGraphDeclSelfUse, LocalVar) {
+  const Symbol symbol = Sym("SYMBOL");
+  WrapInFunction(
+      Decl(Var(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123))));
+  Build("12:34 error: unknown identifier: 'SYMBOL'");
+}
+
+TEST_F(ResolverDependencyGraphDeclSelfUse, LocalLet) {
+  const Symbol symbol = Sym("SYMBOL");
+  WrapInFunction(
+      Decl(Const(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123))));
+  Build("12:34 error: unknown identifier: 'SYMBOL'");
+}
+
+}  // namespace undeclared_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// Recursive dependency tests
+////////////////////////////////////////////////////////////////////////////////
+namespace recursive_tests {
+
+using ResolverDependencyGraphCyclicRefTest = ResolverDependencyGraphTest;
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, DirectCall) {
+  // fn main() { main(); }
+
+  Func(Source{{12, 34}}, "main", {}, ty.void_(),
+       {CallStmt(Call(Expr(Source{{56, 78}}, "main")))});
+
+  Build(R"(12:34 error: cyclic dependency found: 'main' -> 'main'
+56:78 note: function 'main' calls function 'main' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, IndirectCall) {
+  // 1: fn a() { b(); }
+  // 2: fn e() { }
+  // 3: fn d() { e(); b(); }
+  // 4: fn c() { d(); }
+  // 5: fn b() { c(); }
+
+  Func(Source{{1, 1}}, "a", {}, ty.void_(),
+       {CallStmt(Call(Expr(Source{{1, 10}}, "b")))});
+  Func(Source{{2, 1}}, "e", {}, ty.void_(), {});
+  Func(Source{{3, 1}}, "d", {}, ty.void_(),
+       {
+           CallStmt(Call(Expr(Source{{3, 10}}, "e"))),
+           CallStmt(Call(Expr(Source{{3, 10}}, "b"))),
+       });
+  Func(Source{{4, 1}}, "c", {}, ty.void_(),
+       {CallStmt(Call(Expr(Source{{4, 10}}, "d")))});
+  Func(Source{{5, 1}}, "b", {}, ty.void_(),
+       {CallStmt(Call(Expr(Source{{5, 10}}, "c")))});
+
+  Build(R"(5:1 error: cyclic dependency found: 'b' -> 'c' -> 'd' -> 'b'
+5:10 note: function 'b' calls function 'c' here
+4:10 note: function 'c' calls function 'd' here
+3:10 note: function 'd' calls function 'b' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Direct) {
+  // type T = T;
+
+  Alias(Source{{12, 34}}, "T", ty.type_name(Source{{56, 78}}, "T"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: cyclic dependency found: 'T' -> 'T'
+56:78 note: alias 'T' references alias 'T' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Indirect) {
+  // 1: type Y = Z;
+  // 2: type X = Y;
+  // 3: type Z = X;
+
+  Alias(Source{{1, 1}}, "Y", ty.type_name(Source{{1, 10}}, "Z"));
+  Alias(Source{{2, 1}}, "X", ty.type_name(Source{{2, 10}}, "Y"));
+  Alias(Source{{3, 1}}, "Z", ty.type_name(Source{{3, 10}}, "X"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
+1:10 note: alias 'Y' references alias 'Z' here
+3:10 note: alias 'Z' references alias 'X' here
+2:10 note: alias 'X' references alias 'Y' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Direct) {
+  // struct S {
+  //   a: S;
+  // };
+
+  Structure(Source{{12, 34}}, "S",
+            {Member("a", ty.type_name(Source{{56, 78}}, "S"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: cyclic dependency found: 'S' -> 'S'
+56:78 note: struct 'S' references struct 'S' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Indirect) {
+  // 1: struct Y { z: Z; };
+  // 2: struct X { y: Y; };
+  // 3: struct Z { x: X; };
+
+  Structure(Source{{1, 1}}, "Y",
+            {Member("z", ty.type_name(Source{{1, 10}}, "Z"))});
+  Structure(Source{{2, 1}}, "X",
+            {Member("y", ty.type_name(Source{{2, 10}}, "Y"))});
+  Structure(Source{{3, 1}}, "Z",
+            {Member("x", ty.type_name(Source{{3, 10}}, "X"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
+1:10 note: struct 'Y' references struct 'Z' here
+3:10 note: struct 'Z' references struct 'X' here
+2:10 note: struct 'X' references struct 'Y' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Direct) {
+  // var<private> V : i32 = V;
+
+  Global(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: cyclic dependency found: 'V' -> 'V'
+56:78 note: var 'V' references var 'V' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalLet_Direct) {
+  // let V : i32 = V;
+
+  GlobalConst(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: cyclic dependency found: 'V' -> 'V'
+56:78 note: let 'V' references let 'V' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Indirect) {
+  // 1: var<private> Y : i32 = Z;
+  // 2: var<private> X : i32 = Y;
+  // 3: var<private> Z : i32 = X;
+
+  Global(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z"));
+  Global(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y"));
+  Global(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
+1:10 note: var 'Y' references var 'Z' here
+3:10 note: var 'Z' references var 'X' here
+2:10 note: var 'X' references var 'Y' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalLet_Indirect) {
+  // 1: let Y : i32 = Z;
+  // 2: let X : i32 = Y;
+  // 3: let Z : i32 = X;
+
+  GlobalConst(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z"));
+  GlobalConst(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y"));
+  GlobalConst(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y'
+1:10 note: let 'Y' references let 'Z' here
+3:10 note: let 'Z' references let 'X' here
+2:10 note: let 'X' references let 'Y' here)");
+}
+
+TEST_F(ResolverDependencyGraphCyclicRefTest, Mixed_RecursiveDependencies) {
+  // 1: fn F() -> R { return Z; }
+  // 2: type A = S;
+  // 3: struct S { a : A };
+  // 4: var Z = L;
+  // 5: type R = A;
+  // 6: let L : S = Z;
+
+  Func(Source{{1, 1}}, "F", {}, ty.type_name(Source{{1, 5}}, "R"),
+       {Return(Expr(Source{{1, 10}}, "Z"))});
+  Alias(Source{{2, 1}}, "A", ty.type_name(Source{{2, 10}}, "S"));
+  Structure(Source{{3, 1}}, "S",
+            {Member("a", ty.type_name(Source{{3, 10}}, "A"))});
+  Global(Source{{4, 1}}, "Z", nullptr, Expr(Source{{4, 10}}, "L"));
+  Alias(Source{{5, 1}}, "R", ty.type_name(Source{{5, 10}}, "A"));
+  GlobalConst(Source{{6, 1}}, "L", ty.type_name(Source{{5, 5}}, "S"),
+              Expr(Source{{5, 10}}, "Z"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(2:1 error: cyclic dependency found: 'A' -> 'S' -> 'A'
+2:10 note: alias 'A' references struct 'S' here
+3:10 note: struct 'S' references alias 'A' here
+4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z'
+4:10 note: var 'Z' references let 'L' here
+5:10 note: let 'L' references var 'Z' here)");
+}
+
+}  // namespace recursive_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// Symbol Redeclaration tests
+////////////////////////////////////////////////////////////////////////////////
+namespace redeclaration_tests {
+
+using ResolverDependencyGraphRedeclarationTest =
+    ResolverDependencyGraphTestWithParam<
+        std::tuple<SymbolDeclKind, SymbolDeclKind>>;
+
+TEST_P(ResolverDependencyGraphRedeclarationTest, Test) {
+  const auto symbol = Sym("SYMBOL");
+
+  auto a_kind = std::get<0>(GetParam());
+  auto b_kind = std::get<1>(GetParam());
+
+  auto a_source = Source{{12, 34}};
+  auto b_source = Source{{56, 78}};
+
+  if (a_kind != SymbolDeclKind::Parameter &&
+      b_kind == SymbolDeclKind::Parameter) {
+    std::swap(a_source, b_source);  // Parameters are declared before locals
+  }
+
+  SymbolTestHelper helper(this);
+  helper.Add(a_kind, symbol, a_source);
+  helper.Add(b_kind, symbol, b_source);
+  helper.Build();
+
+  bool error = ScopeDepth(a_kind) == ScopeDepth(b_kind);
+
+  Build(error ? R"(56:78 error: redeclaration of 'SYMBOL'
+12:34 note: 'SYMBOL' previously declared here)"
+              : "");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverDependencyGraphRedeclarationTest,
+    testing::Combine(testing::ValuesIn(kAllSymbolDeclKinds),
+                     testing::ValuesIn(kAllSymbolDeclKinds)));
+
+}  // namespace redeclaration_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// Ordered global tests
+////////////////////////////////////////////////////////////////////////////////
+namespace ordered_globals {
+
+using ResolverDependencyGraphOrderedGlobalsTest =
+    ResolverDependencyGraphTestWithParam<
+        std::tuple<SymbolDeclKind, SymbolUseKind>>;
+
+TEST_P(ResolverDependencyGraphOrderedGlobalsTest, InOrder) {
+  const Symbol symbol = Sym("SYMBOL");
+  const auto decl_kind = std::get<0>(GetParam());
+  const auto use_kind = std::get<1>(GetParam());
+
+  // Declaration before use
+  SymbolTestHelper helper(this);
+  helper.Add(decl_kind, symbol, Source{{12, 34}});
+  helper.Add(use_kind, symbol, Source{{56, 78}});
+  helper.Build();
+
+  ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
+
+  auto* decl = AST().GlobalDeclarations()[0];
+  auto* use = AST().GlobalDeclarations()[1];
+  EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use));
+}
+
+TEST_P(ResolverDependencyGraphOrderedGlobalsTest, OutOfOrder) {
+  const Symbol symbol = Sym("SYMBOL");
+  const auto decl_kind = std::get<0>(GetParam());
+  const auto use_kind = std::get<1>(GetParam());
+
+  // Use before declaration
+  SymbolTestHelper helper(this);
+  helper.Add(use_kind, symbol, Source{{56, 78}});
+  helper.Build();  // If the use is in a function, then ensure this function is
+                   // built before the symbol declaration
+  helper.Add(decl_kind, symbol, Source{{12, 34}});
+  helper.Build();
+
+  ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
+
+  auto* use = AST().GlobalDeclarations()[0];
+  auto* decl = AST().GlobalDeclarations()[1];
+  EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use));
+}
+
+INSTANTIATE_TEST_SUITE_P(Types,
+                         ResolverDependencyGraphOrderedGlobalsTest,
+                         testing::Combine(testing::ValuesIn(kTypeDeclKinds),
+                                          testing::ValuesIn(kTypeUseKinds)));
+
+INSTANTIATE_TEST_SUITE_P(
+    Values,
+    ResolverDependencyGraphOrderedGlobalsTest,
+    testing::Combine(testing::ValuesIn(kGlobalValueDeclKinds),
+                     testing::ValuesIn(kValueUseKinds)));
+
+INSTANTIATE_TEST_SUITE_P(Functions,
+                         ResolverDependencyGraphOrderedGlobalsTest,
+                         testing::Combine(testing::ValuesIn(kFuncDeclKinds),
+                                          testing::ValuesIn(kFuncUseKinds)));
+}  // namespace ordered_globals
+
+////////////////////////////////////////////////////////////////////////////////
+// Resolved symbols tests
+////////////////////////////////////////////////////////////////////////////////
+namespace resolved_symbols {
+
+using ResolverDependencyGraphResolvedSymbolTest =
+    ResolverDependencyGraphTestWithParam<
+        std::tuple<SymbolDeclKind, SymbolUseKind>>;
+
+TEST_P(ResolverDependencyGraphResolvedSymbolTest, Test) {
+  const Symbol symbol = Sym("SYMBOL");
+  const auto decl_kind = std::get<0>(GetParam());
+  const auto use_kind = std::get<1>(GetParam());
+
+  // Build a symbol declaration and a use of that symbol
+  SymbolTestHelper helper(this);
+  auto* decl = helper.Add(decl_kind, symbol, Source{{12, 34}});
+  auto* use = helper.Add(use_kind, symbol, Source{{56, 78}});
+  helper.Build();
+
+  // If the declaration is visible to the use, then we expect the analysis to
+  // succeed.
+  bool expect_pass = ScopeDepth(decl_kind) <= ScopeDepth(use_kind);
+  auto graph =
+      Build(expect_pass ? "" : "56:78 error: unknown identifier: 'SYMBOL'");
+
+  if (expect_pass) {
+    // Check that the use resolves to the declaration
+    auto* resolved_symbol = graph.resolved_symbols[use];
+    EXPECT_EQ(resolved_symbol, decl)
+        << "resolved: "
+        << (resolved_symbol ? resolved_symbol->TypeInfo().name : "<null>")
+        << "\n"
+        << "decl:     " << decl->TypeInfo().name;
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(Types,
+                         ResolverDependencyGraphResolvedSymbolTest,
+                         testing::Combine(testing::ValuesIn(kTypeDeclKinds),
+                                          testing::ValuesIn(kTypeUseKinds)));
+
+INSTANTIATE_TEST_SUITE_P(Values,
+                         ResolverDependencyGraphResolvedSymbolTest,
+                         testing::Combine(testing::ValuesIn(kValueDeclKinds),
+                                          testing::ValuesIn(kValueUseKinds)));
+
+INSTANTIATE_TEST_SUITE_P(Functions,
+                         ResolverDependencyGraphResolvedSymbolTest,
+                         testing::Combine(testing::ValuesIn(kFuncDeclKinds),
+                                          testing::ValuesIn(kFuncUseKinds)));
+
+}  // namespace resolved_symbols
+
+////////////////////////////////////////////////////////////////////////////////
+// Shadowing tests
+////////////////////////////////////////////////////////////////////////////////
+namespace shadowing {
+
+using ResolverDependencyShadowTest = ResolverDependencyGraphTestWithParam<
+    std::tuple<SymbolDeclKind, SymbolDeclKind>>;
+
+TEST_P(ResolverDependencyShadowTest, Test) {
+  const Symbol symbol = Sym("SYMBOL");
+  const auto outer_kind = std::get<0>(GetParam());
+  const auto inner_kind = std::get<1>(GetParam());
+
+  // Build a symbol declaration and a use of that symbol
+  SymbolTestHelper helper(this);
+  auto* outer = helper.Add(outer_kind, symbol, Source{{12, 34}});
+  helper.Add(inner_kind, symbol, Source{{56, 78}});
+  auto* inner_var = helper.nested_statements.size()
+                        ? helper.nested_statements[0]
+                              ->As<ast::VariableDeclStatement>()
+                              ->variable
+                        : helper.statements.size()
+                              ? helper.statements[0]
+                                    ->As<ast::VariableDeclStatement>()
+                                    ->variable
+                              : helper.parameters[0];
+  helper.Build();
+
+  EXPECT_EQ(Build().shadows[inner_var], outer);
+}
+
+INSTANTIATE_TEST_SUITE_P(LocalShadowGlobal,
+                         ResolverDependencyShadowTest,
+                         testing::Combine(testing::ValuesIn(kGlobalDeclKinds),
+                                          testing::ValuesIn(kLocalDeclKinds)));
+
+INSTANTIATE_TEST_SUITE_P(
+    NestedLocalShadowLocal,
+    ResolverDependencyShadowTest,
+    testing::Combine(testing::Values(SymbolDeclKind::Parameter,
+                                     SymbolDeclKind::LocalVar,
+                                     SymbolDeclKind::LocalLet),
+                     testing::Values(SymbolDeclKind::NestedLocalVar,
+                                     SymbolDeclKind::NestedLocalLet)));
+
+}  // namespace shadowing
+
+////////////////////////////////////////////////////////////////////////////////
+// AST traversal tests
+////////////////////////////////////////////////////////////////////////////////
+namespace ast_traversal {
+
+using ResolverDependencyGraphTraversalTest = ResolverDependencyGraphTest;
+
+TEST_F(ResolverDependencyGraphTraversalTest, SymbolsReached) {
+  const auto value_sym = Sym("VALUE");
+  const auto type_sym = Sym("TYPE");
+  const auto func_sym = Sym("FUNC");
+
+  const auto* value_decl =
+      Global(value_sym, ty.i32(), ast::StorageClass::kPrivate);
+  const auto* type_decl = Alias(type_sym, ty.i32());
+  const auto* func_decl = Func(func_sym, {}, ty.void_(), {});
+
+  struct SymbolUse {
+    const ast::Node* decl = nullptr;
+    const ast::Node* use = nullptr;
+    std::string where = nullptr;
+  };
+
+  std::vector<SymbolUse> symbol_uses;
+
+  auto add_use = [&](const ast::Node* decl, auto* use, int line,
+                     const char* kind) {
+    symbol_uses.emplace_back(SymbolUse{
+        decl, use,
+        std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
+    return use;
+  };
+#define V add_use(value_decl, Expr(value_sym), __LINE__, "V()")
+#define T add_use(type_decl, ty.type_name(type_sym), __LINE__, "T()")
+#define F add_use(func_decl, Expr(func_sym), __LINE__, "F()")
+
+  Alias(Sym(), T);
+  Structure(Sym(), {Member(Sym(), T)});
+  Global(Sym(), T, V);
+  GlobalConst(Sym(), T, V);
+  Func(Sym(),              //
+       {Param(Sym(), T)},  //
+       T,                  // Return type
+       {
+           Decl(Var(Sym(), T, V)),                    //
+           Decl(Const(Sym(), T, V)),                  //
+           CallStmt(Call(F, V)),                      //
+           Block(                                     //
+               Assign(V, V)),                         //
+           If(V,                                      //
+              Block(Assign(V, V)),                    //
+              Else(V,                                 //
+                   Block(Assign(V, V)))),             //
+           Ignore(Bitcast(T, V)),                     //
+           For(Decl(Var(Sym(), T, V)),                //
+               Equal(V, V),                           //
+               Assign(V, V),                          //
+               Block(                                 //
+                   Assign(V, V))),                    //
+           Loop(Block(Assign(V, V)),                  //
+                Block(Assign(V, V))),                 //
+           Switch(V,                                  //
+                  Case(Expr(1),                       //
+                       Block(Assign(V, V))),          //
+                  Case(Expr(2),                       //
+                       Block(Fallthrough())),         //
+                  DefaultCase(Block(Assign(V, V)))),  //
+           Return(V),                                 //
+           Break(),                                   //
+           Discard(),                                 //
+       });                                            //
+  // Exercise type traversal
+  Global(Sym(), ty.atomic(T));
+  Global(Sym(), ty.bool_());
+  Global(Sym(), ty.i32());
+  Global(Sym(), ty.u32());
+  Global(Sym(), ty.f32());
+  Global(Sym(), ty.array(T, V, 4));
+  Global(Sym(), ty.vec3(T));
+  Global(Sym(), ty.mat3x2(T));
+  Global(Sym(), ty.pointer(T, ast::StorageClass::kPrivate));
+  Global(Sym(), ty.sampled_texture(ast::TextureDimension::k2d, T));
+  Global(Sym(), ty.depth_texture(ast::TextureDimension::k2d));
+  Global(Sym(), ty.depth_multisampled_texture(ast::TextureDimension::k2d));
+  Global(Sym(), ty.external_texture());
+  Global(Sym(), ty.multisampled_texture(ast::TextureDimension::k2d, T));
+  Global(Sym(), ty.storage_texture(ast::TextureDimension::k2d,
+                                   ast::TexelFormat::kR32Float,
+                                   ast::Access::kRead));  //
+  Global(Sym(), ty.sampler(ast::SamplerKind::kSampler));
+  Func(Sym(), {}, ty.void_(), {});
+#undef V
+#undef T
+#undef F
+
+  auto graph = Build();
+  for (auto use : symbol_uses) {
+    auto* resolved_symbol = graph.resolved_symbols[use.use];
+    EXPECT_EQ(resolved_symbol, use.decl) << use.where;
+  }
+}
+
+TEST_F(ResolverDependencyGraphTraversalTest, InferredType) {
+  // Check that the nullptr of the var / let type doesn't make things explode
+  Global("a", nullptr, Expr(1));
+  GlobalConst("b", nullptr, Expr(1));
+  WrapInFunction(Var("c", nullptr, Expr(1)),  //
+                 Const("d", nullptr, Expr(1)));
+  Build();
+}
+
+// Reproduces an unbalanced stack push / pop bug in
+// DependencyAnalysis::SortGlobals(), found by clusterfuzz.
+// See: crbug.com/chromium/1273451
+TEST_F(ResolverDependencyGraphTraversalTest, chromium_1273451) {
+  Structure("A", {Member("a", ty.i32())});
+  Structure("B", {Member("b", ty.i32())});
+  Func("f", {Param("a", ty.type_name("A"))}, ty.type_name("B"),
+       {
+           Return(Construct(ty.type_name("B"))),
+       });
+  Build();
+}
+
+}  // namespace ast_traversal
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/entry_point_validation_test.cc b/src/tint/resolver/entry_point_validation_test.cc
new file mode 100644
index 0000000..1f61452
--- /dev/null
+++ b/src/tint/resolver/entry_point_validation_test.cc
@@ -0,0 +1,804 @@
+// 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/ast/builtin_attribute.h"
+#include "src/tint/ast/location_attribute.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <typename T>
+using alias = builder::alias<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+class ResolverEntryPointValidationTest : public TestHelper,
+                                         public testing::Test {};
+
+TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Location) {
+  // @stage(fragment)
+  // fn main() -> @location(0) f32 { return 1.0; }
+  Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(1.0f)},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Builtin) {
+  // @stage(vertex)
+  // fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }
+  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Missing) {
+  // @stage(vertex)
+  // fn main() -> f32 {
+  //   return 1.0;
+  // }
+  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: missing entry point IO attribute on return type");
+}
+
+TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Multiple) {
+  // @stage(vertex)
+  // fn main() -> @location(0) @builtin(position) vec4<f32> {
+  //   return vec4<f32>();
+  // }
+  Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Location(Source{{13, 43}}, 0),
+        Builtin(Source{{14, 52}}, ast::Builtin::kPosition)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
+13:43 note: previously consumed location(0))");
+}
+
+TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_Valid) {
+  // struct Output {
+  //   @location(0) a : f32;
+  //   @builtin(frag_depth) b : f32;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output", {Member("a", ty.f32(), {Location(0)}),
+                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverEntryPointValidationTest,
+       ReturnType_Struct_MemberMultipleAttributes) {
+  // struct Output {
+  //   @location(0) @builtin(frag_depth) a : f32;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output",
+      {Member("a", ty.f32(),
+              {Location(Source{{13, 43}}, 0),
+               Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
+13:43 note: previously consumed location(0)
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest,
+       ReturnType_Struct_MemberMissingAttribute) {
+  // struct Output {
+  //   @location(0) a : f32;
+  //   b : f32;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
+                 Member(Source{{14, 52}}, "b", ty.f32(), {})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(14:52 error: missing entry point IO attribute
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
+  // struct Output {
+  //   @builtin(frag_depth) a : f32;
+  //   @builtin(frag_depth) b : f32;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
+                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
+  // @stage(fragment)
+  // fn main(@location(0) param : f32) {}
+  auto* param = Param("param", ty.f32(), {Location(0)});
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Missing) {
+  // @stage(fragment)
+  // fn main(param : f32) {}
+  auto* param = Param(Source{{13, 43}}, "param", ty.vec4<f32>());
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "13:43 error: missing entry point IO attribute on parameter");
+}
+
+TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
+  // @stage(fragment)
+  // fn main(@location(0) @builtin(sample_index) param : u32) {}
+  auto* param = Param("param", ty.u32(),
+                      {Location(Source{{13, 43}}, 0),
+                       Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)});
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
+13:43 note: previously consumed location(0))");
+}
+
+TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
+  // struct Input {
+  //   @location(0) a : f32;
+  //   @builtin(sample_index) b : u32;
+  // };
+  // @stage(fragment)
+  // fn main(param : Input) {}
+  auto* input = Structure(
+      "Input", {Member("a", ty.f32(), {Location(0)}),
+                Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverEntryPointValidationTest,
+       Parameter_Struct_MemberMultipleAttributes) {
+  // struct Input {
+  //   @location(0) @builtin(sample_index) a : u32;
+  // };
+  // @stage(fragment)
+  // fn main(param : Input) {}
+  auto* input = Structure(
+      "Input",
+      {Member("a", ty.u32(),
+              {Location(Source{{13, 43}}, 0),
+               Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
+13:43 note: previously consumed location(0)
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest,
+       Parameter_Struct_MemberMissingAttribute) {
+  // struct Input {
+  //   @location(0) a : f32;
+  //   b : f32;
+  // };
+  // @stage(fragment)
+  // fn main(param : Input) {}
+  auto* input = Structure(
+      "Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
+                Member(Source{{14, 52}}, "b", ty.f32(), {})});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
+  // @stage(fragment)
+  // fn main(@builtin(sample_index) param_a : u32,
+  //         @builtin(sample_index) param_b : u32) {}
+  auto* param_a =
+      Param("param_a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+  auto* param_b =
+      Param("param_b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: builtin(sample_index) attribute appears multiple times as "
+      "pipeline input");
+}
+
+TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
+  // struct InputA {
+  //   @builtin(sample_index) a : u32;
+  // };
+  // struct InputB {
+  //   @builtin(sample_index) a : u32;
+  // };
+  // @stage(fragment)
+  // fn main(param_a : InputA, param_b : InputB) {}
+  auto* input_a = Structure(
+      "InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+  auto* input_b = Structure(
+      "InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+  auto* param_a = Param("param_a", ty.Of(input_a));
+  auto* param_b = Param("param_b", ty.Of(input_b));
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input
+12:34 note: while analysing entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
+  // @stage(vertex)
+  // fn main() {}
+  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a vertex shader must include the 'position' builtin "
+            "in its return type");
+}
+
+namespace TypeValidationTests {
+struct Params {
+  builder::ast_type_func_ptr create_ast_type;
+  bool is_valid;
+};
+
+template <typename T>
+constexpr Params ParamsFor(bool is_valid) {
+  return Params{DataType<T>::AST, is_valid};
+}
+
+using TypeValidationTest = resolver::ResolverTestWithParam<Params>;
+
+static constexpr Params cases[] = {
+    ParamsFor<f32>(true),           //
+    ParamsFor<i32>(true),           //
+    ParamsFor<u32>(true),           //
+    ParamsFor<bool>(false),         //
+    ParamsFor<vec2<f32>>(true),     //
+    ParamsFor<vec3<f32>>(true),     //
+    ParamsFor<vec4<f32>>(true),     //
+    ParamsFor<mat2x2<f32>>(false),  //
+    ParamsFor<mat3x3<f32>>(false),  //
+    ParamsFor<mat4x4<f32>>(false),  //
+    ParamsFor<alias<f32>>(true),    //
+    ParamsFor<alias<i32>>(true),    //
+    ParamsFor<alias<u32>>(true),    //
+    ParamsFor<alias<bool>>(false),  //
+};
+
+TEST_P(TypeValidationTest, BareInputs) {
+  // @stage(fragment)
+  // fn main(@location(0) @interpolate(flat) a : *) {}
+  auto params = GetParam();
+  auto* a = Param("a", params.create_ast_type(*this), {Location(0), Flat()});
+  Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+
+TEST_P(TypeValidationTest, StructInputs) {
+  // struct Input {
+  //   @location(0) @interpolate(flat) a : *;
+  // };
+  // @stage(fragment)
+  // fn main(a : Input) {}
+  auto params = GetParam();
+  auto* input = Structure("Input", {Member("a", params.create_ast_type(*this),
+                                           {Location(0), Flat()})});
+  auto* a = Param("a", ty.Of(input), {});
+  Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+
+TEST_P(TypeValidationTest, BareOutputs) {
+  // @stage(fragment)
+  // fn main() -> @location(0) * {
+  //   return *();
+  // }
+  auto params = GetParam();
+  Func(Source{{12, 34}}, "main", {}, params.create_ast_type(*this),
+       {Return(Construct(params.create_ast_type(*this)))},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+
+TEST_P(TypeValidationTest, StructOutputs) {
+  // struct Output {
+  //   @location(0) a : *;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto params = GetParam();
+  auto* output = Structure(
+      "Output", {Member("a", params.create_ast_type(*this), {Location(0)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverEntryPointValidationTest,
+                         TypeValidationTest,
+                         testing::ValuesIn(cases));
+
+}  // namespace TypeValidationTests
+
+namespace LocationAttributeTests {
+namespace {
+using LocationAttributeTests = ResolverTest;
+
+TEST_F(LocationAttributeTests, Pass) {
+  // @stage(fragment)
+  // fn frag_main(@location(0) @interpolate(flat) a: i32) {}
+
+  auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0), Flat()});
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(LocationAttributeTests, BadType_Input_bool) {
+  // @stage(fragment)
+  // fn frag_main(@location(0) a: bool) {}
+
+  auto* p =
+      Param(Source{{12, 34}}, "a", ty.bool_(), {Location(Source{{34, 56}}, 0)});
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'bool'\n"
+            "34:56 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadType_Output_Array) {
+  // @stage(fragment)
+  // fn frag_main()->@location(0) array<f32, 2> { return array<f32, 2>(); }
+
+  Func(Source{{12, 34}}, "frag_main", {}, ty.array<f32, 2>(),
+       {Return(Construct(ty.array<f32, 2>()))},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(Source{{34, 56}}, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32, 2>'\n"
+            "34:56 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadType_Input_Struct) {
+  // struct Input {
+  //   a : f32;
+  // };
+  // @stage(fragment)
+  // fn main(@location(0) param : Input) {}
+  auto* input = Structure("Input", {Member("a", ty.f32())});
+  auto* param = Param(Source{{12, 34}}, "param", ty.Of(input),
+                      {Location(Source{{13, 43}}, 0)});
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'Input'\n"
+            "13:43 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadType_Input_Struct_NestedStruct) {
+  // struct Inner {
+  //   @location(0) b : f32;
+  // };
+  // struct Input {
+  //   a : Inner;
+  // };
+  // @stage(fragment)
+  // fn main(param : Input) {}
+  auto* inner = Structure(
+      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
+  auto* input =
+      Structure("Input", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "14:52 error: nested structures cannot be used for entry point IO\n"
+            "12:34 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationAttributeTests, BadType_Input_Struct_RuntimeArray) {
+  // [[block]]
+  // struct Input {
+  //   @location(0) a : array<f32>;
+  // };
+  // @stage(fragment)
+  // fn main(param : Input) {}
+  auto* input = Structure(
+      "Input",
+      {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
+      {create<ast::StructBlockAttribute>()});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "13:43 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32>'\n"
+            "note: 'location' attribute must only be applied to declarations "
+            "of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadMemberType_Input) {
+  // [[block]]
+  // struct S { @location(0) m: array<i32>; };
+  // @stage(fragment)
+  // fn frag_main( a: S) {}
+
+  auto* m = Member(Source{{34, 56}}, "m", ty.array<i32>(),
+                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m}, ast::AttributeList{StructBlock()});
+  auto* p = Param("a", ty.Of(s));
+
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<i32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadMemberType_Output) {
+  // struct S { @location(0) m: atomic<i32>; };
+  // @stage(fragment)
+  // fn frag_main() -> S {}
+  auto* m = Member(Source{{34, 56}}, "m", ty.atomic<i32>(),
+                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+
+  Func("frag_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'atomic<i32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, BadMemberType_Unused) {
+  // struct S { @location(0) m: mat3x2<f32>; };
+
+  auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
+                   ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  Structure("S", {m});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'mat3x2<f32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, ReturnType_Struct_Valid) {
+  // struct Output {
+  //   @location(0) a : f32;
+  //   @builtin(frag_depth) b : f32;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output", {Member("a", ty.f32(), {Location(0)}),
+                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(LocationAttributeTests, ReturnType_Struct) {
+  // struct Output {
+  //   a : f32;
+  // };
+  // @stage(vertex)
+  // fn main() -> @location(0) Output {
+  //   return Output();
+  // }
+  auto* output = Structure("Output", {Member("a", ty.f32())});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
+       {Location(Source{{13, 43}}, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'Output'\n"
+            "13:43 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, ReturnType_Struct_NestedStruct) {
+  // struct Inner {
+  //   @location(0) b : f32;
+  // };
+  // struct Output {
+  //   a : Inner;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output { return Output(); }
+  auto* inner = Structure(
+      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
+  auto* output =
+      Structure("Output", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "14:52 error: nested structures cannot be used for entry point IO\n"
+            "12:34 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationAttributeTests, ReturnType_Struct_RuntimeArray) {
+  // [[block]]
+  // struct Output {
+  //   @location(0) a : array<f32>;
+  // };
+  // @stage(fragment)
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure("Output",
+                           {Member(Source{{13, 43}}, "a", ty.array<float>(),
+                                   {Location(Source{{12, 34}}, 0)})},
+                           {create<ast::StructBlockAttribute>()});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "13:43 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationAttributeTests, ComputeShaderLocation_Input) {
+  Func("main", {}, ty.i32(), {Return(Expr(1))},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))},
+       ast::AttributeList{Location(Source{{12, 34}}, 1)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: attribute is not valid for compute shader output");
+}
+
+TEST_F(LocationAttributeTests, ComputeShaderLocation_Output) {
+  auto* input = Param("input", ty.i32(),
+                      ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  Func("main", {input}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: attribute is not valid for compute shader inputs");
+}
+
+TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Output) {
+  auto* m =
+      Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+  Func(Source{{56, 78}}, "main", {}, ty.Of(s),
+       ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: attribute is not valid for compute shader output\n"
+            "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Input) {
+  auto* m =
+      Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+  auto* input = Param("input", ty.Of(s));
+  Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: attribute is not valid for compute shader inputs\n"
+            "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationAttributeTests, Duplicate_input) {
+  // @stage(fragment)
+  // fn main(@location(1) param_a : f32,
+  //         @location(1) param_b : f32) {}
+  auto* param_a = Param("param_a", ty.f32(), {Location(1)});
+  auto* param_b = Param("param_b", ty.f32(), {Location(Source{{12, 34}}, 1)});
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: location(1) attribute appears multiple times");
+}
+
+TEST_F(LocationAttributeTests, Duplicate_struct) {
+  // struct InputA {
+  //   @location(1) a : f32;
+  // };
+  // struct InputB {
+  //   @location(1) a : f32;
+  // };
+  // @stage(fragment)
+  // fn main(param_a : InputA, param_b : InputB) {}
+  auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
+  auto* input_b = Structure(
+      "InputB", {Member("a", ty.f32(), {Location(Source{{34, 56}}, 1)})});
+  auto* param_a = Param("param_a", ty.Of(input_a));
+  auto* param_b = Param("param_b", ty.Of(input_b));
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: location(1) attribute appears multiple times\n"
+            "12:34 note: while analysing entry point 'main'");
+}
+
+}  // namespace
+}  // namespace LocationAttributeTests
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/function_validation_test.cc b/src/tint/resolver/function_validation_test.cc
new file mode 100644
index 0000000..4efd00b
--- /dev/null
+++ b/src/tint/resolver/function_validation_test.cc
@@ -0,0 +1,830 @@
+// 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/ast/discard_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace {
+
+class ResolverFunctionValidationTest : public resolver::TestHelper,
+                                       public testing::Test {};
+
+TEST_F(ResolverFunctionValidationTest, DuplicateParameterName) {
+  // fn func_a(common_name : f32) { }
+  // fn func_b(common_name : f32) { }
+  Func("func_a", {Param("common_name", ty.f32())}, ty.void_(), {});
+  Func("func_b", {Param("common_name", ty.f32())}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterMayShadowGlobal) {
+  // var<private> common_name : f32;
+  // fn func(common_name : f32) { }
+  Global("common_name", ty.f32(), ast::StorageClass::kPrivate);
+  Func("func", {Param("common_name", ty.f32())}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, LocalConflictsWithParameter) {
+  // fn func(common_name : f32) {
+  //   let common_name = 1;
+  // }
+  Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
+       {Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1)))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), R"(56:78 error: redeclaration of 'common_name'
+12:34 note: 'common_name' previously declared here)");
+}
+
+TEST_F(ResolverFunctionValidationTest, NestedLocalMayShadowParameter) {
+  // fn func(common_name : f32) {
+  //   {
+  //     let common_name = 1;
+  //   }
+  // }
+  Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
+       {Block(Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1))))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       VoidFunctionEndWithoutReturnStatement_Pass) {
+  // fn func { var a:i32 = 2; }
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+       });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionUsingSameVariableName_Pass) {
+  // fn func() -> i32 {
+  //   var func:i32 = 0;
+  //   return func;
+  // }
+
+  auto* var = Var("func", ty.i32(), Expr(0));
+  Func("func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           Decl(var),
+           Return(Source{{12, 34}}, Expr("func")),
+       },
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionNameSameAsFunctionScopeVariableName_Pass) {
+  // fn a() -> void { var b:i32 = 0; }
+  // fn b() -> i32 { return 2; }
+
+  auto* var = Var("b", ty.i32(), Expr(0));
+  Func("a", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+       },
+       ast::AttributeList{});
+
+  Func(Source{{12, 34}}, "b", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           Return(2),
+       },
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_return) {
+  // fn func() -> {
+  //  var a : i32;
+  //  return;
+  //  a = 2;
+  //}
+
+  auto* decl_a = Decl(Var("a", ty.i32()));
+  auto* ret = Return();
+  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
+
+  Func("func", ast::VariableList{}, ty.void_(), {decl_a, ret, assign_a});
+
+  ASSERT_TRUE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
+  EXPECT_TRUE(Sem().Get(ret)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
+  // fn func() -> {
+  //  var a : i32;
+  //  {{{return;}}}
+  //  a = 2;
+  //}
+
+  auto* decl_a = Decl(Var("a", ty.i32()));
+  auto* ret = Return();
+  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {decl_a, Block(Block(Block(ret))), assign_a});
+
+  ASSERT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
+  EXPECT_TRUE(Sem().Get(ret)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
+  // fn func() -> {
+  //  var a : i32;
+  //  discard;
+  //  a = 2;
+  //}
+
+  auto* decl_a = Decl(Var("a", ty.i32()));
+  auto* discard = Discard();
+  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
+
+  Func("func", ast::VariableList{}, ty.void_(), {decl_a, discard, assign_a});
+
+  ASSERT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
+  EXPECT_TRUE(Sem().Get(discard)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
+  // fn func() -> {
+  //  var a : i32;
+  //  {{{discard;}}}
+  //  a = 2;
+  //}
+
+  auto* decl_a = Decl(Var("a", ty.i32()));
+  auto* discard = Discard();
+  auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {decl_a, Block(Block(Block(discard))), assign_a});
+
+  ASSERT_TRUE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
+  EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
+  EXPECT_TRUE(Sem().Get(discard)->IsReachable());
+  EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
+  // fn func() -> int { var a:i32 = 2; }
+
+  auto* var = Var("a", ty.i32(), Expr(2));
+
+  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           Decl(var),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
+  // fn func {}
+
+  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionEndWithoutReturnStatementEmptyBody_Fail) {
+  // fn func() -> int {}
+
+  Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{}, ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementType_Pass) {
+  // fn func { return; }
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementType_fail) {
+  // fn func { return 2; }
+  Func("func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(Source{{12, 34}}, Expr(2)),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'i32', expected 'void'");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementType_void_fail) {
+  // fn v { return; }
+  // fn func { return v(); }
+  Func("v", {}, ty.void_(), {Return()});
+  Func("func", {}, ty.void_(),
+       {
+           Return(Call(Source{{12, 34}}, "v")),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: function 'v' does not return a value");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementTypeMissing_fail) {
+  // fn func() -> f32 { return; }
+  Func("func", ast::VariableList{}, ty.f32(),
+       ast::StatementList{
+           Return(Source{{12, 34}}, nullptr),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'void', expected 'f32'");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementTypeF32_pass) {
+  // fn func() -> f32 { return 2.0; }
+  Func("func", ast::VariableList{}, ty.f32(),
+       ast::StatementList{
+           Return(Source{{12, 34}}, Expr(2.f)),
+       },
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementTypeF32_fail) {
+  // fn func() -> f32 { return 2; }
+  Func("func", ast::VariableList{}, ty.f32(),
+       ast::StatementList{
+           Return(Source{{12, 34}}, Expr(2)),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'i32', expected 'f32'");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementTypeF32Alias_pass) {
+  // type myf32 = f32;
+  // fn func() -> myf32 { return 2.0; }
+  auto* myf32 = Alias("myf32", ty.f32());
+  Func("func", ast::VariableList{}, ty.Of(myf32),
+       ast::StatementList{
+           Return(Source{{12, 34}}, Expr(2.f)),
+       },
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionTypeMustMatchReturnStatementTypeF32Alias_fail) {
+  // type myf32 = f32;
+  // fn func() -> myf32 { return 2; }
+  auto* myf32 = Alias("myf32", ty.f32());
+  Func("func", ast::VariableList{}, ty.Of(myf32),
+       ast::StatementList{
+           Return(Source{{12, 34}}, Expr(2u)),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'u32', expected 'f32'");
+}
+
+TEST_F(ResolverFunctionValidationTest, CannotCallEntryPoint) {
+  // @stage(compute) @workgroup_size(1) fn entrypoint() {}
+  // fn func() { return entrypoint(); }
+  Func("entrypoint", ast::VariableList{}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {
+           CallStmt(Call(Source{{12, 34}}, "entrypoint")),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+
+      R"(12:34 error: entry point functions cannot be the target of a function call)");
+}
+
+TEST_F(ResolverFunctionValidationTest, PipelineStage_MustBeUnique_Fail) {
+  // @stage(fragment)
+  // @stage(vertex)
+  // fn main() { return; }
+  Func(Source{{12, 34}}, "main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{
+           Stage(Source{{12, 34}}, ast::PipelineStage::kVertex),
+           Stage(Source{{56, 78}}, ast::PipelineStage::kFragment),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(56:78 error: duplicate stage attribute
+12:34 note: first attribute declared here)");
+}
+
+TEST_F(ResolverFunctionValidationTest, NoPipelineEntryPoints) {
+  Func("vtx_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionVarInitWithParam) {
+  // fn foo(bar : f32){
+  //   var baz : f32 = bar;
+  // }
+
+  auto* bar = Param("bar", ty.f32());
+  auto* baz = Var("baz", ty.f32(), Expr("bar"));
+
+  Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionConstInitWithParam) {
+  // fn foo(bar : f32){
+  //   let baz : f32 = bar;
+  // }
+
+  auto* bar = Param("bar", ty.f32());
+  auto* baz = Const("baz", ty.f32(), Expr("bar"));
+
+  Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
+       ast::AttributeList{});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionParamsConst) {
+  Func("foo", {Param(Sym("arg"), ty.i32())}, ty.void_(),
+       {Assign(Expr(Source{{12, 34}}, "arg"), Expr(1)), Return()});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot assign to function parameter\nnote: 'arg' is "
+            "declared here:");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_ConstU32) {
+  // let x = 4u;
+  // let x = 8u;
+  // @stage(compute) @workgroup_size(x, y, 16u)
+  // fn main() {}
+  auto* x = GlobalConst("x", ty.u32(), Expr(4u));
+  auto* y = GlobalConst("y", ty.u32(), Expr(8u));
+  auto* func = Func("main", {}, ty.void_(), {},
+                    {Stage(ast::PipelineStage::kCompute),
+                     WorkgroupSize(Expr("x"), Expr("y"), Expr(16u))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem_func = Sem().Get(func);
+  auto* sem_x = Sem().Get<sem::GlobalVariable>(x);
+  auto* sem_y = Sem().Get<sem::GlobalVariable>(y);
+
+  ASSERT_NE(sem_func, nullptr);
+  ASSERT_NE(sem_x, nullptr);
+  ASSERT_NE(sem_y, nullptr);
+
+  EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_x));
+  EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_y));
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_U32) {
+  // [[stage(compute), workgroup_size(1u, 2u, 3u)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Source{{12, 34}}, Expr(1u), Expr(2u), Expr(3u))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeU32) {
+  // [[stage(compute), workgroup_size(1u, 2u, 3)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(1u), Expr(2u), Expr(Source{{12, 34}}, 3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size arguments must be of the same type, "
+            "either i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeI32) {
+  // [[stage(compute), workgroup_size(1, 2u, 3)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, 2u), Expr(3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size arguments must be of the same type, "
+            "either i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch) {
+  // let x = 64u;
+  // [[stage(compute), workgroup_size(1, x)]
+  // fn main() {}
+  GlobalConst("x", ty.u32(), Expr(64u));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size arguments must be of the same type, "
+            "either i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch2) {
+  // let x = 64u;
+  // let y = 32;
+  // [[stage(compute), workgroup_size(x, y)]
+  // fn main() {}
+  GlobalConst("x", ty.u32(), Expr(64u));
+  GlobalConst("y", ty.i32(), Expr(32));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr("x"), Expr(Source{{12, 34}}, "y"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size arguments must be of the same type, "
+            "either i32 or u32");
+}
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Mismatch_ConstU32) {
+  // let x = 4u;
+  // let x = 8u;
+  // [[stage(compute), workgroup_size(x, y, 16]
+  // fn main() {}
+  GlobalConst("x", ty.u32(), Expr(4u));
+  GlobalConst("y", ty.u32(), Expr(8u));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr("x"), Expr("y"), Expr(Source{{12, 34}}, 16))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size arguments must be of the same type, "
+            "either i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_BadType) {
+  // [[stage(compute), workgroup_size(64.0)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, 64.f))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be either literal or "
+            "module-scope constant of type i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Negative) {
+  // [[stage(compute), workgroup_size(-2)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, -2))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be at least 1");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Zero) {
+  // [[stage(compute), workgroup_size(0)]
+  // fn main() {}
+
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, 0))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be at least 1");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_BadType) {
+  // let x = 64.0;
+  // [[stage(compute), workgroup_size(x)]
+  // fn main() {}
+  GlobalConst("x", ty.f32(), Expr(64.f));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be either literal or "
+            "module-scope constant of type i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Negative) {
+  // let x = -2;
+  // [[stage(compute), workgroup_size(x)]
+  // fn main() {}
+  GlobalConst("x", ty.i32(), Expr(-2));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be at least 1");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Zero) {
+  // let x = 0;
+  // [[stage(compute), workgroup_size(x)]
+  // fn main() {}
+  GlobalConst("x", ty.i32(), Expr(0));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be at least 1");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       WorkgroupSize_Const_NestedZeroValueConstructor) {
+  // let x = i32(i32(i32()));
+  // [[stage(compute), workgroup_size(x)]
+  // fn main() {}
+  GlobalConst("x", ty.i32(),
+              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32()))));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be at least 1");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_NonConst) {
+  // var<private> x = 0;
+  // [[stage(compute), workgroup_size(x)]
+  // fn main() {}
+  Global("x", ty.i32(), ast::StorageClass::kPrivate, Expr(64));
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be either literal or "
+            "module-scope constant of type i32 or u32");
+}
+
+TEST_F(ResolverFunctionValidationTest, WorkgroupSize_InvalidExpr) {
+  // [[stage(compute), workgroup_size(i32(1))]
+  // fn main() {}
+  Func("main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        WorkgroupSize(Construct(Source{{12, 34}}, ty.i32(), 1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: workgroup_size argument must be either a literal or "
+            "a module-scope constant");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_NonPlain) {
+  auto* ret_type =
+      ty.pointer(Source{{12, 34}}, ty.i32(), ast::StorageClass::kFunction);
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function return type must be a constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_AtomicInt) {
+  auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function return type must be a constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_ArrayOfAtomic) {
+  auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10);
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function return type must be a constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_StructOfAtomic) {
+  Structure("S", {Member("m", ty.atomic(ty.i32()))});
+  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function return type must be a constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
+  auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function return type must be a constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterStoreType_NonAtomicFree) {
+  Structure("S", {Member("m", ty.atomic(ty.i32()))});
+  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
+  auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
+  Func("f", ast::VariableList{bar}, ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: store type of function parameter must be a "
+            "constructible type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterSotreType_AtomicFree) {
+  Structure("S", {Member("m", ty.i32())});
+  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
+  auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
+  Func("f", ast::VariableList{bar}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, ParametersAtLimit) {
+  ast::VariableList params;
+  for (int i = 0; i < 255; i++) {
+    params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
+  }
+  Func(Source{{12, 34}}, "f", params, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverFunctionValidationTest, ParametersOverLimit) {
+  ast::VariableList params;
+  for (int i = 0; i < 256; i++) {
+    params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
+  }
+  Func(Source{{12, 34}}, "f", params, ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: functions may declare at most 255 parameters");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
+  // fn f(p : vec3) {}
+
+  Func(Source{{12, 34}}, "f",
+       {Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))},
+       ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
+  // fn f(p : vec3) {}
+
+  Func(Source{{12, 34}}, "f",
+       {Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))},
+       ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
+struct TestParams {
+  ast::StorageClass storage_class;
+  bool should_pass;
+};
+
+struct TestWithParams : resolver::ResolverTestWithParam<TestParams> {};
+
+using ResolverFunctionParameterValidationTest = TestWithParams;
+TEST_P(ResolverFunctionParameterValidationTest, StorageClass) {
+  auto& param = GetParam();
+  auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.storage_class);
+  auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
+  Func("f", ast::VariableList{arg}, ty.void_(), {});
+
+  if (param.should_pass) {
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    std::stringstream ss;
+    ss << param.storage_class;
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: function parameter of pointer type cannot be in '" +
+                  ss.str() + "' storage class");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverFunctionParameterValidationTest,
+    testing::Values(TestParams{ast::StorageClass::kNone, false},
+                    TestParams{ast::StorageClass::kInput, false},
+                    TestParams{ast::StorageClass::kOutput, false},
+                    TestParams{ast::StorageClass::kUniform, false},
+                    TestParams{ast::StorageClass::kWorkgroup, true},
+                    TestParams{ast::StorageClass::kUniformConstant, false},
+                    TestParams{ast::StorageClass::kStorage, false},
+                    TestParams{ast::StorageClass::kPrivate, true},
+                    TestParams{ast::StorageClass::kFunction, true}));
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/resolver/host_shareable_validation_test.cc b/src/tint/resolver/host_shareable_validation_test.cc
new file mode 100644
index 0000000..f876bfd
--- /dev/null
+++ b/src/tint/resolver/host_shareable_validation_test.cc
@@ -0,0 +1,151 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/struct.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverHostShareableValidationTest = ResolverTest;
+
+TEST_F(ResolverHostShareableValidationTest, BoolMember) {
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
+12:34 note: while analysing structure member S.x
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'vec3<bool>' cannot be used in storage class 'storage' as it is non-host-shareable
+12:34 note: while analysing structure member S.x
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverHostShareableValidationTest, Aliases) {
+  auto* a1 = Alias("a1", ty.bool_());
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.Of(a1))},
+                      {create<ast::StructBlockAttribute>()});
+  auto* a2 = Alias("a2", ty.Of(s));
+  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
+12:34 note: while analysing structure member S.x
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
+  auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
+  auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", ty.Of(i1))});
+  auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", ty.Of(i2))});
+
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(9:10 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
+1:2 note: while analysing structure member I1.x
+3:4 note: while analysing structure member I2.y
+5:6 note: while analysing structure member I3.z
+7:8 note: while analysing structure member S.m
+9:10 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverHostShareableValidationTest, NoError) {
+  auto* i1 =
+      Structure("I1", {
+                          Member(Source{{1, 1}}, "x1", ty.f32()),
+                          Member(Source{{2, 1}}, "y1", ty.vec3<f32>()),
+                          Member(Source{{3, 1}}, "z1", ty.array<i32, 4>()),
+                      });
+  auto* a1 = Alias("a1", ty.Of(i1));
+  auto* i2 = Structure("I2", {
+                                 Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
+                                 Member(Source{{5, 1}}, "y2", ty.Of(i1)),
+                             });
+  auto* a2 = Alias("a2", ty.Of(i2));
+  auto* i3 = Structure("I3", {
+                                 Member(Source{{4, 1}}, "x3", ty.Of(a1)),
+                                 Member(Source{{5, 1}}, "y3", ty.Of(i2)),
+                                 Member(Source{{6, 1}}, "z3", ty.Of(a2)),
+                             });
+
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+  WrapInFunction();
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
new file mode 100644
index 0000000..689f813
--- /dev/null
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -0,0 +1,176 @@
+// 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/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <typename T>
+using alias = builder::alias<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+struct ResolverInferredTypeTest : public resolver::TestHelper,
+                                  public testing::Test {};
+
+struct Params {
+  builder::ast_expr_func_ptr create_value;
+  builder::sem_type_func_ptr create_expected_type;
+};
+
+template <typename T>
+constexpr Params ParamsFor() {
+  return Params{DataType<T>::Expr, DataType<T>::Sem};
+}
+
+Params all_cases[] = {
+    ParamsFor<bool>(),                //
+    ParamsFor<u32>(),                 //
+    ParamsFor<i32>(),                 //
+    ParamsFor<f32>(),                 //
+    ParamsFor<vec3<bool>>(),          //
+    ParamsFor<vec3<i32>>(),           //
+    ParamsFor<vec3<u32>>(),           //
+    ParamsFor<vec3<f32>>(),           //
+    ParamsFor<mat3x3<f32>>(),         //
+    ParamsFor<alias<bool>>(),         //
+    ParamsFor<alias<u32>>(),          //
+    ParamsFor<alias<i32>>(),          //
+    ParamsFor<alias<f32>>(),          //
+    ParamsFor<alias<vec3<bool>>>(),   //
+    ParamsFor<alias<vec3<i32>>>(),    //
+    ParamsFor<alias<vec3<u32>>>(),    //
+    ParamsFor<alias<vec3<f32>>>(),    //
+    ParamsFor<alias<mat3x3<f32>>>(),  //
+};
+
+using ResolverInferredTypeParamTest = ResolverTestWithParam<Params>;
+
+TEST_P(ResolverInferredTypeParamTest, GlobalLet_Pass) {
+  auto& params = GetParam();
+
+  auto* expected_type = params.create_expected_type(*this);
+
+  // let a = <type constructor>;
+  auto* ctor_expr = params.create_value(*this, 0);
+  auto* var = GlobalConst("a", nullptr, ctor_expr);
+  WrapInFunction();
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var), expected_type);
+}
+
+TEST_P(ResolverInferredTypeParamTest, GlobalVar_Fail) {
+  auto& params = GetParam();
+
+  // var a = <type constructor>;
+  auto* ctor_expr = params.create_value(*this, 0);
+  Global(Source{{12, 34}}, "a", nullptr, ast::StorageClass::kPrivate,
+         ctor_expr);
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: global var declaration must specify a type");
+}
+
+TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) {
+  auto& params = GetParam();
+
+  auto* expected_type = params.create_expected_type(*this);
+
+  // let a = <type constructor>;
+  auto* ctor_expr = params.create_value(*this, 0);
+  auto* var = Const("a", nullptr, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var), expected_type);
+}
+
+TEST_P(ResolverInferredTypeParamTest, LocalVar_Pass) {
+  auto& params = GetParam();
+
+  auto* expected_type = params.create_expected_type(*this);
+
+  // var a = <type constructor>;
+  auto* ctor_expr = params.create_value(*this, 0);
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverInferredTypeParamTest,
+                         testing::ValuesIn(all_cases));
+
+TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
+  auto* type = ty.array(ty.u32(), 10);
+  auto* expected_type =
+      create<sem::Array>(create<sem::U32>(), 10, 4, 4 * 10, 4, 4);
+
+  auto* ctor_expr = Construct(type);
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
+  auto* member = Member("x", ty.i32());
+  auto* str = Structure("S", {member}, {create<ast::StructBlockAttribute>()});
+
+  auto* expected_type = create<sem::Struct>(
+      str, str->name,
+      sem::StructMemberList{create<sem::StructMember>(
+          member, member->symbol, create<sem::I32>(), 0, 0, 0, 4)},
+      0, 4, 4);
+
+  auto* ctor_expr = Construct(ty.Of(str));
+
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/is_host_shareable_test.cc b/src/tint/resolver/is_host_shareable_test.cc
new file mode 100644
index 0000000..9c992ab
--- /dev/null
+++ b/src/tint/resolver/is_host_shareable_test.cc
@@ -0,0 +1,115 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/atomic_type.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverIsHostShareable = ResolverTest;
+
+TEST_F(ResolverIsHostShareable, Void) {
+  EXPECT_FALSE(r()->IsHostShareable(create<sem::Void>()));
+}
+
+TEST_F(ResolverIsHostShareable, Bool) {
+  EXPECT_FALSE(r()->IsHostShareable(create<sem::Bool>()));
+}
+
+TEST_F(ResolverIsHostShareable, NumericScalar) {
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::I32>()));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::U32>()));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::F32>()));
+}
+
+TEST_F(ResolverIsHostShareable, NumericVector) {
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 4)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 4)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 4)));
+}
+
+TEST_F(ResolverIsHostShareable, BoolVector) {
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
+  EXPECT_FALSE(
+      r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
+}
+
+TEST_F(ResolverIsHostShareable, Matrix) {
+  auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
+  auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
+  auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
+
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 4)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 4)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 2)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 3)));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 4)));
+}
+
+TEST_F(ResolverIsHostShareable, Pointer) {
+  auto* ptr = create<sem::Pointer>(
+      create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
+  EXPECT_FALSE(r()->IsHostShareable(ptr));
+}
+
+TEST_F(ResolverIsHostShareable, Atomic) {
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::I32>())));
+  EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::U32>())));
+}
+
+TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
+  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
+  EXPECT_TRUE(r()->IsHostShareable(arr));
+}
+
+TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
+  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
+  EXPECT_TRUE(r()->IsHostShareable(arr));
+}
+
+// Note: Structure tests covered in host_shareable_validation_test.cc
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/is_storeable_test.cc b/src/tint/resolver/is_storeable_test.cc
new file mode 100644
index 0000000..cc3323a
--- /dev/null
+++ b/src/tint/resolver/is_storeable_test.cc
@@ -0,0 +1,140 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/atomic_type.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverIsStorableTest = ResolverTest;
+
+TEST_F(ResolverIsStorableTest, Void) {
+  EXPECT_FALSE(r()->IsStorable(create<sem::Void>()));
+}
+
+TEST_F(ResolverIsStorableTest, Scalar) {
+  EXPECT_TRUE(r()->IsStorable(create<sem::Bool>()));
+  EXPECT_TRUE(r()->IsStorable(create<sem::I32>()));
+  EXPECT_TRUE(r()->IsStorable(create<sem::U32>()));
+  EXPECT_TRUE(r()->IsStorable(create<sem::F32>()));
+}
+
+TEST_F(ResolverIsStorableTest, Vector) {
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 4)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 4)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 4)));
+}
+
+TEST_F(ResolverIsStorableTest, Matrix) {
+  auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
+  auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
+  auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 4)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 4)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 2)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 3)));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 4)));
+}
+
+TEST_F(ResolverIsStorableTest, Pointer) {
+  auto* ptr = create<sem::Pointer>(
+      create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
+  EXPECT_FALSE(r()->IsStorable(ptr));
+}
+
+TEST_F(ResolverIsStorableTest, Atomic) {
+  EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::I32>())));
+  EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::U32>())));
+}
+
+TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
+  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
+  EXPECT_TRUE(r()->IsStorable(arr));
+}
+
+TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
+  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
+  EXPECT_TRUE(r()->IsStorable(arr));
+}
+
+TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ty.f32()),
+                 });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
+}
+
+TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
+  auto* storable = Structure("Storable", {
+                                             Member("a", ty.i32()),
+                                             Member("b", ty.f32()),
+                                         });
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ty.Of(storable)),
+                 });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
+  auto* non_storable =
+      Structure("nonstorable",
+                {
+                    Member("a", ty.i32()),
+                    Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
+                });
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ty.Of(non_storable)),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/pipeline_overridable_constant_test.cc b/src/tint/resolver/pipeline_overridable_constant_test.cc
new file mode 100644
index 0000000..9672174
--- /dev/null
+++ b/src/tint/resolver/pipeline_overridable_constant_test.cc
@@ -0,0 +1,108 @@
+// 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/resolver/resolver.h"
+
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+class ResolverPipelineOverridableConstantTest : public ResolverTest {
+ protected:
+  /// Verify that the AST node `var` was resolved to an overridable constant
+  /// with an ID equal to `id`.
+  /// @param var the overridable constant AST node
+  /// @param id the expected constant ID
+  void ExpectConstantId(const ast::Variable* var, uint16_t id) {
+    auto* sem = Sem().Get<sem::GlobalVariable>(var);
+    ASSERT_NE(sem, nullptr);
+    EXPECT_EQ(sem->Declaration(), var);
+    EXPECT_TRUE(sem->IsOverridable());
+    EXPECT_EQ(sem->ConstantId(), id);
+    EXPECT_FALSE(sem->ConstantValue());
+  }
+};
+
+TEST_F(ResolverPipelineOverridableConstantTest, NonOverridable) {
+  auto* a = GlobalConst("a", ty.f32(), Expr(1.f));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem_a = Sem().Get<sem::GlobalVariable>(a);
+  ASSERT_NE(sem_a, nullptr);
+  EXPECT_EQ(sem_a->Declaration(), a);
+  EXPECT_FALSE(sem_a->IsOverridable());
+  EXPECT_TRUE(sem_a->ConstantValue());
+}
+
+TEST_F(ResolverPipelineOverridableConstantTest, WithId) {
+  auto* a = Override("a", ty.f32(), Expr(1.f), {Id(7u)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ExpectConstantId(a, 7u);
+}
+
+TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
+  auto* a = Override("a", ty.f32(), Expr(1.f));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ExpectConstantId(a, 0u);
+}
+
+TEST_F(ResolverPipelineOverridableConstantTest, WithAndWithoutIds) {
+  std::vector<ast::Variable*> variables;
+  auto* a = Override("a", ty.f32(), Expr(1.f));
+  auto* b = Override("b", ty.f32(), Expr(1.f));
+  auto* c = Override("c", ty.f32(), Expr(1.f), {Id(2u)});
+  auto* d = Override("d", ty.f32(), Expr(1.f), {Id(4u)});
+  auto* e = Override("e", ty.f32(), Expr(1.f));
+  auto* f = Override("f", ty.f32(), Expr(1.f), {Id(1u)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  // Verify that constant id allocation order is deterministic.
+  ExpectConstantId(a, 0u);
+  ExpectConstantId(b, 3u);
+  ExpectConstantId(c, 2u);
+  ExpectConstantId(d, 4u);
+  ExpectConstantId(e, 5u);
+  ExpectConstantId(f, 1u);
+}
+
+TEST_F(ResolverPipelineOverridableConstantTest, DuplicateIds) {
+  Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 7u)});
+  Override("b", ty.f32(), Expr(1.f), {Id(Source{{56, 78}}, 7u)});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(56:78 error: pipeline constant IDs must be unique
+12:34 note: a pipeline constant with an ID of 7 was previously declared here:)");
+}
+
+TEST_F(ResolverPipelineOverridableConstantTest, IdTooLarge) {
+  Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 65536u)});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: pipeline constant IDs must be between 0 and 65535");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/ptr_ref_test.cc b/src/tint/resolver/ptr_ref_test.cc
new file mode 100644
index 0000000..fa26304
--- /dev/null
+++ b/src/tint/resolver/ptr_ref_test.cc
@@ -0,0 +1,126 @@
+// 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/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/reference_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverPtrRefTest : public resolver::TestHelper,
+                            public testing::Test {};
+
+TEST_F(ResolverPtrRefTest, AddressOf) {
+  // var v : i32;
+  // &v
+
+  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+  auto* expr = AddressOf(v);
+
+  WrapInFunction(v, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Pointer>());
+  EXPECT_TRUE(TypeOf(expr)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(expr)->As<sem::Pointer>()->StorageClass(),
+            ast::StorageClass::kFunction);
+}
+
+TEST_F(ResolverPtrRefTest, AddressOfThenDeref) {
+  // var v : i32;
+  // *(&v)
+
+  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+  auto* expr = Deref(AddressOf(v));
+
+  WrapInFunction(v, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
+  EXPECT_TRUE(TypeOf(expr)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, DefaultPtrStorageClass) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
+
+  auto* buf = Structure("S", {Member("m", ty.i32())},
+                        {create<ast::StructBlockAttribute>()});
+  auto* function = Var("f", ty.i32());
+  auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
+  auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(0),
+                             create<ast::GroupAttribute>(0),
+                         });
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(1),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  auto* function_ptr =
+      Const("f_ptr", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
+            AddressOf(function));
+  auto* private_ptr =
+      Const("p_ptr", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
+            AddressOf(private_));
+  auto* workgroup_ptr =
+      Const("w_ptr", ty.pointer(ty.i32(), ast::StorageClass::kWorkgroup),
+            AddressOf(workgroup));
+  auto* uniform_ptr =
+      Const("ub_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kUniform),
+            AddressOf(uniform));
+  auto* storage_ptr =
+      Const("sb_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kStorage),
+            AddressOf(storage));
+
+  WrapInFunction(function, function_ptr, private_ptr, workgroup_ptr,
+                 uniform_ptr, storage_ptr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(function_ptr)->Is<sem::Pointer>())
+      << "function_ptr is " << TypeOf(function_ptr)->TypeInfo().name;
+  ASSERT_TRUE(TypeOf(private_ptr)->Is<sem::Pointer>())
+      << "private_ptr is " << TypeOf(private_ptr)->TypeInfo().name;
+  ASSERT_TRUE(TypeOf(workgroup_ptr)->Is<sem::Pointer>())
+      << "workgroup_ptr is " << TypeOf(workgroup_ptr)->TypeInfo().name;
+  ASSERT_TRUE(TypeOf(uniform_ptr)->Is<sem::Pointer>())
+      << "uniform_ptr is " << TypeOf(uniform_ptr)->TypeInfo().name;
+  ASSERT_TRUE(TypeOf(storage_ptr)->Is<sem::Pointer>())
+      << "storage_ptr is " << TypeOf(storage_ptr)->TypeInfo().name;
+
+  EXPECT_EQ(TypeOf(function_ptr)->As<sem::Pointer>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(private_ptr)->As<sem::Pointer>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(workgroup_ptr)->As<sem::Pointer>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(uniform_ptr)->As<sem::Pointer>()->Access(),
+            ast::Access::kRead);
+  EXPECT_EQ(TypeOf(storage_ptr)->As<sem::Pointer>()->Access(),
+            ast::Access::kRead);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/ptr_ref_validation_test.cc b/src/tint/resolver/ptr_ref_validation_test.cc
new file mode 100644
index 0000000..2bec6ef
--- /dev/null
+++ b/src/tint/resolver/ptr_ref_validation_test.cc
@@ -0,0 +1,176 @@
+// 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/ast/bitcast_expression.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/reference_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverPtrRefValidationTest : public resolver::TestHelper,
+                                      public testing::Test {};
+
+TEST_F(ResolverPtrRefValidationTest, AddressOfLiteral) {
+  // &1
+
+  auto* expr = AddressOf(Expr(Source{{12, 34}}, 1));
+
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
+}
+
+TEST_F(ResolverPtrRefValidationTest, AddressOfLet) {
+  // let l : i32 = 1;
+  // &l
+  auto* l = Const("l", ty.i32(), Expr(1));
+  auto* expr = AddressOf(Expr(Source{{12, 34}}, "l"));
+
+  WrapInFunction(l, expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
+}
+
+TEST_F(ResolverPtrRefValidationTest, AddressOfHandle) {
+  // @group(0) @binding(0) var t: texture_3d<f32>;
+  // &t
+  Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
+         GroupAndBinding(0u, 0u));
+  auto* expr = AddressOf(Expr(Source{{12, 34}}, "t"));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot take the address of expression in handle "
+            "storage class");
+}
+
+TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_MemberAccessor) {
+  // var v : vec4<i32>;
+  // &v.y
+  auto* v = Var("v", ty.vec4<i32>());
+  auto* expr = AddressOf(MemberAccessor(Source{{12, 34}}, "v", "y"));
+
+  WrapInFunction(v, expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot take the address of a vector component");
+}
+
+TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_IndexAccessor) {
+  // var v : vec4<i32>;
+  // &v[2]
+  auto* v = Var("v", ty.vec4<i32>());
+  auto* expr = AddressOf(IndexAccessor(Source{{12, 34}}, "v", 2));
+
+  WrapInFunction(v, expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot take the address of a vector component");
+}
+
+TEST_F(ResolverPtrRefValidationTest, IndirectOfAddressOfHandle) {
+  // @group(0) @binding(0) var t: texture_3d<f32>;
+  // *&t
+  Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
+         GroupAndBinding(0u, 0u));
+  auto* expr = Deref(AddressOf(Expr(Source{{12, 34}}, "t")));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot take the address of expression in handle "
+            "storage class");
+}
+
+TEST_F(ResolverPtrRefValidationTest, DerefOfLiteral) {
+  // *1
+
+  auto* expr = Deref(Expr(Source{{12, 34}}, 1));
+
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot dereference expression of type 'i32'");
+}
+
+TEST_F(ResolverPtrRefValidationTest, DerefOfVar) {
+  // var v : i32 = 1;
+  // *1
+  auto* v = Var("v", ty.i32());
+  auto* expr = Deref(Expr(Source{{12, 34}}, "v"));
+
+  WrapInFunction(v, expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot dereference expression of type 'i32'");
+}
+
+TEST_F(ResolverPtrRefValidationTest, InferredPtrAccessMismatch) {
+  // struct Inner {
+  //    arr: array<i32, 4>;
+  // }
+  // [[block]] struct S {
+  //    inner: Inner;
+  // }
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  // fn f() {
+  //   let p : pointer<storage, i32> = &s.inner.arr[2];
+  // }
+  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
+                        {create<ast::StructBlockAttribute>()});
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(0),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  auto* expr =
+      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
+  auto* ptr =
+      Const(Source{{12, 34}}, "p", ty.pointer<i32>(ast::StorageClass::kStorage),
+            AddressOf(expr));
+
+  WrapInFunction(ptr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot initialize let of type "
+            "'ptr<storage, i32, read>' with value of type "
+            "'ptr<storage, i32, read_write>'");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
new file mode 100644
index 0000000..42f8cb1
--- /dev/null
+++ b/src/tint/resolver/resolver.cc
@@ -0,0 +1,2917 @@
+// Copyright 2020 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/resolver/resolver.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iomanip>
+#include <limits>
+#include <utility>
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/depth_texture.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/sampled_texture.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/loop_statement.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/pointer_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/reverse.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/transform.h"
+
+namespace tint {
+namespace resolver {
+
+Resolver::Resolver(ProgramBuilder* builder)
+    : builder_(builder),
+      diagnostics_(builder->Diagnostics()),
+      builtin_table_(BuiltinTable::Create(*builder)) {}
+
+Resolver::~Resolver() = default;
+
+bool Resolver::Resolve() {
+  if (builder_->Diagnostics().contains_errors()) {
+    return false;
+  }
+
+  if (!DependencyGraph::Build(builder_->AST(), builder_->Symbols(),
+                              builder_->Diagnostics(), dependencies_)) {
+    return false;
+  }
+
+  // Create the semantic module
+  builder_->Sem().SetModule(
+      builder_->create<sem::Module>(dependencies_.ordered_globals));
+
+  bool result = ResolveInternal();
+
+  if (!result && !diagnostics_.contains_errors()) {
+    TINT_ICE(Resolver, diagnostics_)
+        << "resolving failed, but no error was raised";
+    return false;
+  }
+
+  return result;
+}
+
+bool Resolver::ResolveInternal() {
+  Mark(&builder_->AST());
+
+  // Process all module-scope declarations in dependency order.
+  for (auto* decl : dependencies_.ordered_globals) {
+    Mark(decl);
+    if (!Switch(
+            decl,                           //
+            [&](const ast::TypeDecl* td) {  //
+              return TypeDecl(td) != nullptr;
+            },
+            [&](const ast::Function* func) {
+              return Function(func) != nullptr;
+            },
+            [&](const ast::Variable* var) {
+              return GlobalVariable(var) != nullptr;
+            },
+            [&](Default) {
+              TINT_UNREACHABLE(Resolver, diagnostics_)
+                  << "unhandled global declaration: " << decl->TypeInfo().name;
+              return false;
+            })) {
+      return false;
+    }
+  }
+
+  AllocateOverridableConstantIds();
+
+  SetShadows();
+
+  if (!ValidatePipelineStages()) {
+    return false;
+  }
+
+  bool result = true;
+  for (auto* node : builder_->ASTNodes().Objects()) {
+    if (marked_.count(node) == 0) {
+      TINT_ICE(Resolver, diagnostics_) << "AST node '" << node->TypeInfo().name
+                                       << "' was not reached by the resolver\n"
+                                       << "At: " << node->source << "\n"
+                                       << "Pointer: " << node;
+      result = false;
+    }
+  }
+
+  return result;
+}
+
+sem::Type* Resolver::Type(const ast::Type* ty) {
+  Mark(ty);
+  auto* s = Switch(
+      ty,
+      [&](const ast::Void*) -> sem::Type* {
+        return builder_->create<sem::Void>();
+      },
+      [&](const ast::Bool*) -> sem::Type* {
+        return builder_->create<sem::Bool>();
+      },
+      [&](const ast::I32*) -> sem::Type* {
+        return builder_->create<sem::I32>();
+      },
+      [&](const ast::U32*) -> sem::Type* {
+        return builder_->create<sem::U32>();
+      },
+      [&](const ast::F32*) -> sem::Type* {
+        return builder_->create<sem::F32>();
+      },
+      [&](const ast::Vector* t) -> sem::Type* {
+        if (!t->type) {
+          AddError("missing vector element type", t->source.End());
+          return nullptr;
+        }
+        if (auto* el = Type(t->type)) {
+          if (auto* vector = builder_->create<sem::Vector>(el, t->width)) {
+            if (ValidateVector(vector, t->source)) {
+              return vector;
+            }
+          }
+        }
+        return nullptr;
+      },
+      [&](const ast::Matrix* t) -> sem::Type* {
+        if (!t->type) {
+          AddError("missing matrix element type", t->source.End());
+          return nullptr;
+        }
+        if (auto* el = Type(t->type)) {
+          if (auto* column_type = builder_->create<sem::Vector>(el, t->rows)) {
+            if (auto* matrix =
+                    builder_->create<sem::Matrix>(column_type, t->columns)) {
+              if (ValidateMatrix(matrix, t->source)) {
+                return matrix;
+              }
+            }
+          }
+        }
+        return nullptr;
+      },
+      [&](const ast::Array* t) -> sem::Type* { return Array(t); },
+      [&](const ast::Atomic* t) -> sem::Type* {
+        if (auto* el = Type(t->type)) {
+          auto* a = builder_->create<sem::Atomic>(el);
+          if (!ValidateAtomic(t, a)) {
+            return nullptr;
+          }
+          return a;
+        }
+        return nullptr;
+      },
+      [&](const ast::Pointer* t) -> sem::Type* {
+        if (auto* el = Type(t->type)) {
+          auto access = t->access;
+          if (access == ast::kUndefined) {
+            access = DefaultAccessForStorageClass(t->storage_class);
+          }
+          return builder_->create<sem::Pointer>(el, t->storage_class, access);
+        }
+        return nullptr;
+      },
+      [&](const ast::Sampler* t) -> sem::Type* {
+        return builder_->create<sem::Sampler>(t->kind);
+      },
+      [&](const ast::SampledTexture* t) -> sem::Type* {
+        if (auto* el = Type(t->type)) {
+          return builder_->create<sem::SampledTexture>(t->dim, el);
+        }
+        return nullptr;
+      },
+      [&](const ast::MultisampledTexture* t) -> sem::Type* {
+        if (auto* el = Type(t->type)) {
+          return builder_->create<sem::MultisampledTexture>(t->dim, el);
+        }
+        return nullptr;
+      },
+      [&](const ast::DepthTexture* t) -> sem::Type* {
+        return builder_->create<sem::DepthTexture>(t->dim);
+      },
+      [&](const ast::DepthMultisampledTexture* t) -> sem::Type* {
+        return builder_->create<sem::DepthMultisampledTexture>(t->dim);
+      },
+      [&](const ast::StorageTexture* t) -> sem::Type* {
+        if (auto* el = Type(t->type)) {
+          if (!ValidateStorageTexture(t)) {
+            return nullptr;
+          }
+          return builder_->create<sem::StorageTexture>(t->dim, t->format,
+                                                       t->access, el);
+        }
+        return nullptr;
+      },
+      [&](const ast::ExternalTexture*) -> sem::Type* {
+        return builder_->create<sem::ExternalTexture>();
+      },
+      [&](Default) -> sem::Type* {
+        auto* resolved = ResolvedSymbol(ty);
+        return Switch(
+            resolved,  //
+            [&](sem::Type* type) { return type; },
+            [&](sem::Variable* var) {
+              auto name =
+                  builder_->Symbols().NameFor(var->Declaration()->symbol);
+              AddError("cannot use variable '" + name + "' as type",
+                       ty->source);
+              AddNote("'" + name + "' declared here",
+                      var->Declaration()->source);
+              return nullptr;
+            },
+            [&](sem::Function* func) {
+              auto name =
+                  builder_->Symbols().NameFor(func->Declaration()->symbol);
+              AddError("cannot use function '" + name + "' as type",
+                       ty->source);
+              AddNote("'" + name + "' declared here",
+                      func->Declaration()->source);
+              return nullptr;
+            },
+            [&](Default) {
+              TINT_UNREACHABLE(Resolver, diagnostics_)
+                  << "Unhandled resolved type '"
+                  << (resolved ? resolved->TypeInfo().name : "<null>")
+                  << "' resolved from ast::Type '" << ty->TypeInfo().name
+                  << "'";
+              return nullptr;
+            });
+      });
+
+  if (s) {
+    builder_->Sem().Add(ty, s);
+  }
+  return s;
+}
+
+sem::Variable* Resolver::Variable(const ast::Variable* var,
+                                  VariableKind kind,
+                                  uint32_t index /* = 0 */) {
+  const sem::Type* storage_ty = nullptr;
+
+  // If the variable has a declared type, resolve it.
+  if (auto* ty = var->type) {
+    storage_ty = Type(ty);
+    if (!storage_ty) {
+      return nullptr;
+    }
+  }
+
+  const sem::Expression* rhs = nullptr;
+
+  // Does the variable have a constructor?
+  if (var->constructor) {
+    rhs = Expression(var->constructor);
+    if (!rhs) {
+      return nullptr;
+    }
+
+    // If the variable has no declared type, infer it from the RHS
+    if (!storage_ty) {
+      if (!var->is_const && kind == VariableKind::kGlobal) {
+        AddError("global var declaration must specify a type", var->source);
+        return nullptr;
+      }
+
+      storage_ty = rhs->Type()->UnwrapRef();  // Implicit load of RHS
+    }
+  } else if (var->is_const && !var->is_overridable &&
+             kind != VariableKind::kParameter) {
+    AddError("let declaration must have an initializer", var->source);
+    return nullptr;
+  } else if (!var->type) {
+    AddError(
+        (kind == VariableKind::kGlobal)
+            ? "module scope var declaration requires a type and initializer"
+            : "function scope var declaration requires a type or initializer",
+        var->source);
+    return nullptr;
+  }
+
+  if (!storage_ty) {
+    TINT_ICE(Resolver, diagnostics_)
+        << "failed to determine storage type for variable '" +
+               builder_->Symbols().NameFor(var->symbol) + "'\n"
+        << "Source: " << var->source;
+    return nullptr;
+  }
+
+  auto storage_class = var->declared_storage_class;
+  if (storage_class == ast::StorageClass::kNone && !var->is_const) {
+    // No declared storage class. Infer from usage / type.
+    if (kind == VariableKind::kLocal) {
+      storage_class = ast::StorageClass::kFunction;
+    } else if (storage_ty->UnwrapRef()->is_handle()) {
+      // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
+      // If the store type is a texture type or a sampler type, then the
+      // variable declaration must not have a storage class attribute. The
+      // storage class will always be handle.
+      storage_class = ast::StorageClass::kUniformConstant;
+    }
+  }
+
+  if (kind == VariableKind::kLocal && !var->is_const &&
+      storage_class != ast::StorageClass::kFunction &&
+      IsValidationEnabled(var->attributes,
+                          ast::DisabledValidation::kIgnoreStorageClass)) {
+    AddError("function variable has a non-function storage class", var->source);
+    return nullptr;
+  }
+
+  auto access = var->declared_access;
+  if (access == ast::Access::kUndefined) {
+    access = DefaultAccessForStorageClass(storage_class);
+  }
+
+  auto* var_ty = storage_ty;
+  if (!var->is_const) {
+    // Variable declaration. Unlike `let`, `var` has storage.
+    // Variables are always of a reference type to the declared storage type.
+    var_ty =
+        builder_->create<sem::Reference>(storage_ty, storage_class, access);
+  }
+
+  if (rhs && !ValidateVariableConstructorOrCast(var, storage_class, storage_ty,
+                                                rhs->Type())) {
+    return nullptr;
+  }
+
+  if (!ApplyStorageClassUsageToType(
+          storage_class, const_cast<sem::Type*>(var_ty), var->source)) {
+    AddNote(
+        std::string("while instantiating ") +
+            ((kind == VariableKind::kParameter) ? "parameter " : "variable ") +
+            builder_->Symbols().NameFor(var->symbol),
+        var->source);
+    return nullptr;
+  }
+
+  if (kind == VariableKind::kParameter) {
+    if (auto* ptr = var_ty->As<sem::Pointer>()) {
+      // For MSL, we push module-scope variables into the entry point as pointer
+      // parameters, so we also need to handle their store type.
+      if (!ApplyStorageClassUsageToType(
+              ptr->StorageClass(), const_cast<sem::Type*>(ptr->StoreType()),
+              var->source)) {
+        AddNote("while instantiating parameter " +
+                    builder_->Symbols().NameFor(var->symbol),
+                var->source);
+        return nullptr;
+      }
+    }
+  }
+
+  switch (kind) {
+    case VariableKind::kGlobal: {
+      sem::BindingPoint binding_point;
+      if (auto bp = var->BindingPoint()) {
+        binding_point = {bp.group->value, bp.binding->value};
+      }
+
+      bool has_const_val = rhs && var->is_const && !var->is_overridable;
+      auto* global = builder_->create<sem::GlobalVariable>(
+          var, var_ty, storage_class, access,
+          has_const_val ? rhs->ConstantValue() : sem::Constant{},
+          binding_point);
+
+      if (var->is_overridable) {
+        global->SetIsOverridable();
+        if (auto* id = ast::GetAttribute<ast::IdAttribute>(var->attributes)) {
+          global->SetConstantId(static_cast<uint16_t>(id->value));
+        }
+      }
+
+      global->SetConstructor(rhs);
+
+      builder_->Sem().Add(var, global);
+      return global;
+    }
+    case VariableKind::kLocal: {
+      auto* local = builder_->create<sem::LocalVariable>(
+          var, var_ty, storage_class, access, current_statement_,
+          (rhs && var->is_const) ? rhs->ConstantValue() : sem::Constant{});
+      builder_->Sem().Add(var, local);
+      local->SetConstructor(rhs);
+      return local;
+    }
+    case VariableKind::kParameter: {
+      auto* param = builder_->create<sem::Parameter>(var, index, var_ty,
+                                                     storage_class, access);
+      builder_->Sem().Add(var, param);
+      return param;
+    }
+  }
+
+  TINT_UNREACHABLE(Resolver, diagnostics_)
+      << "unhandled VariableKind " << static_cast<int>(kind);
+  return nullptr;
+}
+
+ast::Access Resolver::DefaultAccessForStorageClass(
+    ast::StorageClass storage_class) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
+  switch (storage_class) {
+    case ast::StorageClass::kStorage:
+    case ast::StorageClass::kUniform:
+    case ast::StorageClass::kUniformConstant:
+      return ast::Access::kRead;
+    default:
+      break;
+  }
+  return ast::Access::kReadWrite;
+}
+
+void Resolver::AllocateOverridableConstantIds() {
+  // The next pipeline constant ID to try to allocate.
+  uint16_t next_constant_id = 0;
+
+  // Allocate constant IDs in global declaration order, so that they are
+  // deterministic.
+  // TODO(crbug.com/tint/1192): If a transform changes the order or removes an
+  // unused constant, the allocation may change on the next Resolver pass.
+  for (auto* decl : builder_->AST().GlobalDeclarations()) {
+    auto* var = decl->As<ast::Variable>();
+    if (!var || !var->is_overridable) {
+      continue;
+    }
+
+    uint16_t constant_id;
+    if (auto* id_attr = ast::GetAttribute<ast::IdAttribute>(var->attributes)) {
+      constant_id = static_cast<uint16_t>(id_attr->value);
+    } else {
+      // No ID was specified, so allocate the next available ID.
+      constant_id = next_constant_id;
+      while (constant_ids_.count(constant_id)) {
+        if (constant_id == UINT16_MAX) {
+          TINT_ICE(Resolver, builder_->Diagnostics())
+              << "no more pipeline constant IDs available";
+          return;
+        }
+        constant_id++;
+      }
+      next_constant_id = constant_id + 1;
+    }
+
+    auto* sem = Sem<sem::GlobalVariable>(var);
+    const_cast<sem::GlobalVariable*>(sem)->SetConstantId(constant_id);
+  }
+}
+
+void Resolver::SetShadows() {
+  for (auto it : dependencies_.shadows) {
+    Switch(
+        Sem(it.first),  //
+        [&](sem::LocalVariable* local) { local->SetShadows(Sem(it.second)); },
+        [&](sem::Parameter* param) { param->SetShadows(Sem(it.second)); });
+  }
+}  // namespace resolver
+
+sem::GlobalVariable* Resolver::GlobalVariable(const ast::Variable* var) {
+  auto* sem = Variable(var, VariableKind::kGlobal);
+  if (!sem) {
+    return nullptr;
+  }
+
+  auto storage_class = sem->StorageClass();
+  if (!var->is_const && storage_class == ast::StorageClass::kNone) {
+    AddError("global variables must have a storage class", var->source);
+    return nullptr;
+  }
+  if (var->is_const && storage_class != ast::StorageClass::kNone) {
+    AddError("global constants shouldn't have a storage class", var->source);
+    return nullptr;
+  }
+
+  for (auto* attr : var->attributes) {
+    Mark(attr);
+
+    if (auto* id_attr = attr->As<ast::IdAttribute>()) {
+      // Track the constant IDs that are specified in the shader.
+      constant_ids_.emplace(id_attr->value, sem);
+    }
+  }
+
+  if (!ValidateNoDuplicateAttributes(var->attributes)) {
+    return nullptr;
+  }
+
+  if (!ValidateGlobalVariable(sem)) {
+    return nullptr;
+  }
+
+  // TODO(bclayton): Call this at the end of resolve on all uniform and storage
+  // referenced structs
+  if (!ValidateStorageClassLayout(sem)) {
+    return nullptr;
+  }
+
+  return sem->As<sem::GlobalVariable>();
+}
+
+sem::Function* Resolver::Function(const ast::Function* decl) {
+  uint32_t parameter_index = 0;
+  std::unordered_map<Symbol, Source> parameter_names;
+  std::vector<sem::Parameter*> parameters;
+
+  // Resolve all the parameters
+  for (auto* param : decl->params) {
+    Mark(param);
+
+    {  // Check the parameter name is unique for the function
+      auto emplaced = parameter_names.emplace(param->symbol, param->source);
+      if (!emplaced.second) {
+        auto name = builder_->Symbols().NameFor(param->symbol);
+        AddError("redefinition of parameter '" + name + "'", param->source);
+        AddNote("previous definition is here", emplaced.first->second);
+        return nullptr;
+      }
+    }
+
+    auto* var = As<sem::Parameter>(
+        Variable(param, VariableKind::kParameter, parameter_index++));
+    if (!var) {
+      return nullptr;
+    }
+
+    for (auto* attr : param->attributes) {
+      Mark(attr);
+    }
+    if (!ValidateNoDuplicateAttributes(param->attributes)) {
+      return nullptr;
+    }
+
+    parameters.emplace_back(var);
+
+    auto* var_ty = const_cast<sem::Type*>(var->Type());
+    if (auto* str = var_ty->As<sem::Struct>()) {
+      switch (decl->PipelineStage()) {
+        case ast::PipelineStage::kVertex:
+          str->AddUsage(sem::PipelineStageUsage::kVertexInput);
+          break;
+        case ast::PipelineStage::kFragment:
+          str->AddUsage(sem::PipelineStageUsage::kFragmentInput);
+          break;
+        case ast::PipelineStage::kCompute:
+          str->AddUsage(sem::PipelineStageUsage::kComputeInput);
+          break;
+        case ast::PipelineStage::kNone:
+          break;
+      }
+    }
+  }
+
+  // Resolve the return type
+  sem::Type* return_type = nullptr;
+  if (auto* ty = decl->return_type) {
+    return_type = Type(ty);
+    if (!return_type) {
+      return nullptr;
+    }
+  } else {
+    return_type = builder_->create<sem::Void>();
+  }
+
+  if (auto* str = return_type->As<sem::Struct>()) {
+    if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, str,
+                                      decl->source)) {
+      AddNote("while instantiating return type for " +
+                  builder_->Symbols().NameFor(decl->symbol),
+              decl->source);
+      return nullptr;
+    }
+
+    switch (decl->PipelineStage()) {
+      case ast::PipelineStage::kVertex:
+        str->AddUsage(sem::PipelineStageUsage::kVertexOutput);
+        break;
+      case ast::PipelineStage::kFragment:
+        str->AddUsage(sem::PipelineStageUsage::kFragmentOutput);
+        break;
+      case ast::PipelineStage::kCompute:
+        str->AddUsage(sem::PipelineStageUsage::kComputeOutput);
+        break;
+      case ast::PipelineStage::kNone:
+        break;
+    }
+  }
+
+  auto* func = builder_->create<sem::Function>(decl, return_type, parameters);
+  builder_->Sem().Add(decl, func);
+
+  TINT_SCOPED_ASSIGNMENT(current_function_, func);
+
+  if (!WorkgroupSize(decl)) {
+    return nullptr;
+  }
+
+  if (decl->IsEntryPoint()) {
+    entry_points_.emplace_back(func);
+  }
+
+  if (decl->body) {
+    Mark(decl->body);
+    if (current_compound_statement_) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "Resolver::Function() called with a current compound statement";
+      return nullptr;
+    }
+    auto* body = StatementScope(
+        decl->body, builder_->create<sem::FunctionBlockStatement>(func),
+        [&] { return Statements(decl->body->statements); });
+    if (!body) {
+      return nullptr;
+    }
+    func->Behaviors() = body->Behaviors();
+    if (func->Behaviors().Contains(sem::Behavior::kReturn)) {
+      // https://www.w3.org/TR/WGSL/#behaviors-rules
+      // We assign a behavior to each function: it is its body’s behavior
+      // (treating the body as a regular statement), with any "Return" replaced
+      // by "Next".
+      func->Behaviors().Remove(sem::Behavior::kReturn);
+      func->Behaviors().Add(sem::Behavior::kNext);
+    }
+  }
+
+  for (auto* attr : decl->attributes) {
+    Mark(attr);
+  }
+  if (!ValidateNoDuplicateAttributes(decl->attributes)) {
+    return nullptr;
+  }
+
+  for (auto* attr : decl->return_type_attributes) {
+    Mark(attr);
+  }
+  if (!ValidateNoDuplicateAttributes(decl->return_type_attributes)) {
+    return nullptr;
+  }
+
+  if (!ValidateFunction(func)) {
+    return nullptr;
+  }
+
+  // If this is an entry point, mark all transitively called functions as being
+  // used by this entry point.
+  if (decl->IsEntryPoint()) {
+    for (auto* f : func->TransitivelyCalledFunctions()) {
+      const_cast<sem::Function*>(f)->AddAncestorEntryPoint(func);
+    }
+  }
+
+  return func;
+}
+
+bool Resolver::WorkgroupSize(const ast::Function* func) {
+  // Set work-group size defaults.
+  sem::WorkgroupSize ws;
+  for (int i = 0; i < 3; i++) {
+    ws[i].value = 1;
+    ws[i].overridable_const = nullptr;
+  }
+
+  auto* attr = ast::GetAttribute<ast::WorkgroupAttribute>(func->attributes);
+  if (!attr) {
+    return true;
+  }
+
+  auto values = attr->Values();
+  auto any_i32 = false;
+  auto any_u32 = false;
+  for (int i = 0; i < 3; i++) {
+    // Each argument to this attribute can either be a literal, an
+    // identifier for a module-scope constants, or nullptr if not specified.
+
+    auto* expr = values[i];
+    if (!expr) {
+      // Not specified, just use the default.
+      continue;
+    }
+
+    auto* expr_sem = Expression(expr);
+    if (!expr_sem) {
+      return false;
+    }
+
+    constexpr const char* kErrBadType =
+        "workgroup_size argument must be either literal or module-scope "
+        "constant of type i32 or u32";
+    constexpr const char* kErrInconsistentType =
+        "workgroup_size arguments must be of the same type, either i32 "
+        "or u32";
+
+    auto* ty = TypeOf(expr);
+    bool is_i32 = ty->UnwrapRef()->Is<sem::I32>();
+    bool is_u32 = ty->UnwrapRef()->Is<sem::U32>();
+    if (!is_i32 && !is_u32) {
+      AddError(kErrBadType, expr->source);
+      return false;
+    }
+
+    any_i32 = any_i32 || is_i32;
+    any_u32 = any_u32 || is_u32;
+    if (any_i32 && any_u32) {
+      AddError(kErrInconsistentType, expr->source);
+      return false;
+    }
+
+    sem::Constant value;
+
+    if (auto* user = Sem(expr)->As<sem::VariableUser>()) {
+      // We have an variable of a module-scope constant.
+      auto* decl = user->Variable()->Declaration();
+      if (!decl->is_const) {
+        AddError(kErrBadType, expr->source);
+        return false;
+      }
+      // Capture the constant if it is pipeline-overridable.
+      if (decl->is_overridable) {
+        ws[i].overridable_const = decl;
+      }
+
+      if (decl->constructor) {
+        value = Sem(decl->constructor)->ConstantValue();
+      } else {
+        // No constructor means this value must be overriden by the user.
+        ws[i].value = 0;
+        continue;
+      }
+    } else if (expr->Is<ast::LiteralExpression>()) {
+      value = Sem(expr)->ConstantValue();
+    } else {
+      AddError(
+          "workgroup_size argument must be either a literal or a "
+          "module-scope constant",
+          values[i]->source);
+      return false;
+    }
+
+    if (!value) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "could not resolve constant workgroup_size constant value";
+      continue;
+    }
+    // Validate and set the default value for this dimension.
+    if (is_i32 ? value.Elements()[0].i32 < 1 : value.Elements()[0].u32 < 1) {
+      AddError("workgroup_size argument must be at least 1", values[i]->source);
+      return false;
+    }
+
+    ws[i].value = is_i32 ? static_cast<uint32_t>(value.Elements()[0].i32)
+                         : value.Elements()[0].u32;
+  }
+
+  current_function_->SetWorkgroupSize(std::move(ws));
+  return true;
+}
+
+bool Resolver::Statements(const ast::StatementList& stmts) {
+  sem::Behaviors behaviors{sem::Behavior::kNext};
+
+  bool reachable = true;
+  for (auto* stmt : stmts) {
+    Mark(stmt);
+    auto* sem = Statement(stmt);
+    if (!sem) {
+      return false;
+    }
+    // s1 s2:(B1∖{Next}) ∪ B2
+    sem->SetIsReachable(reachable);
+    if (reachable) {
+      behaviors = (behaviors - sem::Behavior::kNext) + sem->Behaviors();
+    }
+    reachable = reachable && sem->Behaviors().Contains(sem::Behavior::kNext);
+  }
+
+  current_statement_->Behaviors() = behaviors;
+
+  if (!ValidateStatements(stmts)) {
+    return false;
+  }
+
+  return true;
+}
+
+sem::Statement* Resolver::Statement(const ast::Statement* stmt) {
+  return Switch(
+      stmt,
+      // Compound statements. These create their own sem::CompoundStatement
+      // bindings.
+      [&](const ast::BlockStatement* b) -> sem::Statement* {
+        return BlockStatement(b);
+      },
+      [&](const ast::ForLoopStatement* l) -> sem::Statement* {
+        return ForLoopStatement(l);
+      },
+      [&](const ast::LoopStatement* l) -> sem::Statement* {
+        return LoopStatement(l);
+      },
+      [&](const ast::IfStatement* i) -> sem::Statement* {
+        return IfStatement(i);
+      },
+      [&](const ast::SwitchStatement* s) -> sem::Statement* {
+        return SwitchStatement(s);
+      },
+
+      // Non-Compound statements
+      [&](const ast::AssignmentStatement* a) -> sem::Statement* {
+        return AssignmentStatement(a);
+      },
+      [&](const ast::BreakStatement* b) -> sem::Statement* {
+        return BreakStatement(b);
+      },
+      [&](const ast::CallStatement* c) -> sem::Statement* {
+        return CallStatement(c);
+      },
+      [&](const ast::ContinueStatement* c) -> sem::Statement* {
+        return ContinueStatement(c);
+      },
+      [&](const ast::DiscardStatement* d) -> sem::Statement* {
+        return DiscardStatement(d);
+      },
+      [&](const ast::FallthroughStatement* f) -> sem::Statement* {
+        return FallthroughStatement(f);
+      },
+      [&](const ast::ReturnStatement* r) -> sem::Statement* {
+        return ReturnStatement(r);
+      },
+      [&](const ast::VariableDeclStatement* v) -> sem::Statement* {
+        return VariableDeclStatement(v);
+      },
+
+      // Error cases
+      [&](const ast::CaseStatement*) -> sem::Statement* {
+        AddError("case statement can only be used inside a switch statement",
+                 stmt->source);
+        return nullptr;
+      },
+      [&](const ast::ElseStatement*) -> sem::Statement* {
+        TINT_ICE(Resolver, diagnostics_)
+            << "Resolver::Statement() encountered an Else statement. Else "
+               "statements are embedded in If statements, so should never be "
+               "encountered as top-level statements";
+        return nullptr;
+      },
+      [&](Default) -> sem::Statement* {
+        AddError(
+            "unknown statement type: " + std::string(stmt->TypeInfo().name),
+            stmt->source);
+        return nullptr;
+      });
+}
+
+sem::CaseStatement* Resolver::CaseStatement(const ast::CaseStatement* stmt) {
+  auto* sem = builder_->create<sem::CaseStatement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    for (auto* sel : stmt->selectors) {
+      Mark(sel);
+    }
+    Mark(stmt->body);
+    auto* body = BlockStatement(stmt->body);
+    if (!body) {
+      return false;
+    }
+    sem->SetBlock(body);
+    sem->Behaviors() = body->Behaviors();
+    return true;
+  });
+}
+
+sem::IfStatement* Resolver::IfStatement(const ast::IfStatement* stmt) {
+  auto* sem = builder_->create<sem::IfStatement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    auto* cond = Expression(stmt->condition);
+    if (!cond) {
+      return false;
+    }
+    sem->SetCondition(cond);
+    sem->Behaviors() = cond->Behaviors();
+    sem->Behaviors().Remove(sem::Behavior::kNext);
+
+    Mark(stmt->body);
+    auto* body = builder_->create<sem::BlockStatement>(
+        stmt->body, current_compound_statement_, current_function_);
+    if (!StatementScope(stmt->body, body,
+                        [&] { return Statements(stmt->body->statements); })) {
+      return false;
+    }
+    sem->Behaviors().Add(body->Behaviors());
+
+    for (auto* else_stmt : stmt->else_statements) {
+      Mark(else_stmt);
+      auto* else_sem = ElseStatement(else_stmt);
+      if (!else_sem) {
+        return false;
+      }
+      sem->Behaviors().Add(else_sem->Behaviors());
+    }
+
+    if (stmt->else_statements.empty() ||
+        stmt->else_statements.back()->condition != nullptr) {
+      // https://www.w3.org/TR/WGSL/#behaviors-rules
+      // if statements without an else branch are treated as if they had an
+      // empty else branch (which adds Next to their behavior)
+      sem->Behaviors().Add(sem::Behavior::kNext);
+    }
+
+    return ValidateIfStatement(sem);
+  });
+}
+
+sem::ElseStatement* Resolver::ElseStatement(const ast::ElseStatement* stmt) {
+  auto* sem = builder_->create<sem::ElseStatement>(
+      stmt, current_compound_statement_->As<sem::IfStatement>(),
+      current_function_);
+  return StatementScope(stmt, sem, [&] {
+    if (auto* cond_expr = stmt->condition) {
+      auto* cond = Expression(cond_expr);
+      if (!cond) {
+        return false;
+      }
+      sem->SetCondition(cond);
+      // https://www.w3.org/TR/WGSL/#behaviors-rules
+      // if statements with else if branches are treated as if they were nested
+      // simple if/else statements
+      sem->Behaviors() = cond->Behaviors();
+    }
+    sem->Behaviors().Remove(sem::Behavior::kNext);
+
+    Mark(stmt->body);
+    auto* body = builder_->create<sem::BlockStatement>(
+        stmt->body, current_compound_statement_, current_function_);
+    if (!StatementScope(stmt->body, body,
+                        [&] { return Statements(stmt->body->statements); })) {
+      return false;
+    }
+    sem->Behaviors().Add(body->Behaviors());
+
+    return ValidateElseStatement(sem);
+  });
+}
+
+sem::BlockStatement* Resolver::BlockStatement(const ast::BlockStatement* stmt) {
+  auto* sem = builder_->create<sem::BlockStatement>(
+      stmt->As<ast::BlockStatement>(), current_compound_statement_,
+      current_function_);
+  return StatementScope(stmt, sem,
+                        [&] { return Statements(stmt->statements); });
+}
+
+sem::LoopStatement* Resolver::LoopStatement(const ast::LoopStatement* stmt) {
+  auto* sem = builder_->create<sem::LoopStatement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    Mark(stmt->body);
+
+    auto* body = builder_->create<sem::LoopBlockStatement>(
+        stmt->body, current_compound_statement_, current_function_);
+    return StatementScope(stmt->body, body, [&] {
+      if (!Statements(stmt->body->statements)) {
+        return false;
+      }
+      auto& behaviors = sem->Behaviors();
+      behaviors = body->Behaviors();
+
+      if (stmt->continuing) {
+        Mark(stmt->continuing);
+        if (!stmt->continuing->Empty()) {
+          auto* continuing = StatementScope(
+              stmt->continuing,
+              builder_->create<sem::LoopContinuingBlockStatement>(
+                  stmt->continuing, current_compound_statement_,
+                  current_function_),
+              [&] { return Statements(stmt->continuing->statements); });
+          if (!continuing) {
+            return false;
+          }
+          behaviors.Add(continuing->Behaviors());
+        }
+      }
+
+      if (behaviors.Contains(sem::Behavior::kBreak)) {  // Does the loop exit?
+        behaviors.Add(sem::Behavior::kNext);
+      } else {
+        behaviors.Remove(sem::Behavior::kNext);
+      }
+      behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kContinue);
+
+      return ValidateLoopStatement(sem);
+    });
+  });
+}
+
+sem::ForLoopStatement* Resolver::ForLoopStatement(
+    const ast::ForLoopStatement* stmt) {
+  auto* sem = builder_->create<sem::ForLoopStatement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    auto& behaviors = sem->Behaviors();
+    if (auto* initializer = stmt->initializer) {
+      Mark(initializer);
+      auto* init = Statement(initializer);
+      if (!init) {
+        return false;
+      }
+      behaviors.Add(init->Behaviors());
+    }
+
+    if (auto* cond_expr = stmt->condition) {
+      auto* cond = Expression(cond_expr);
+      if (!cond) {
+        return false;
+      }
+      sem->SetCondition(cond);
+      behaviors.Add(cond->Behaviors());
+    }
+
+    if (auto* continuing = stmt->continuing) {
+      Mark(continuing);
+      auto* cont = Statement(continuing);
+      if (!cont) {
+        return false;
+      }
+      behaviors.Add(cont->Behaviors());
+    }
+
+    Mark(stmt->body);
+
+    auto* body = builder_->create<sem::LoopBlockStatement>(
+        stmt->body, current_compound_statement_, current_function_);
+    if (!StatementScope(stmt->body, body,
+                        [&] { return Statements(stmt->body->statements); })) {
+      return false;
+    }
+
+    behaviors.Add(body->Behaviors());
+    if (stmt->condition ||
+        behaviors.Contains(sem::Behavior::kBreak)) {  // Does the loop exit?
+      behaviors.Add(sem::Behavior::kNext);
+    } else {
+      behaviors.Remove(sem::Behavior::kNext);
+    }
+    behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kContinue);
+
+    return ValidateForLoopStatement(sem);
+  });
+}
+
+sem::Expression* Resolver::Expression(const ast::Expression* root) {
+  std::vector<const ast::Expression*> sorted;
+  bool mark_failed = false;
+  if (!ast::TraverseExpressions<ast::TraverseOrder::RightToLeft>(
+          root, diagnostics_, [&](const ast::Expression* expr) {
+            if (!Mark(expr)) {
+              mark_failed = true;
+              return ast::TraverseAction::Stop;
+            }
+            sorted.emplace_back(expr);
+            return ast::TraverseAction::Descend;
+          })) {
+    return nullptr;
+  }
+
+  if (mark_failed) {
+    return nullptr;
+  }
+
+  for (auto* expr : utils::Reverse(sorted)) {
+    auto* sem_expr = Switch(
+        expr,
+        [&](const ast::IndexAccessorExpression* array) -> sem::Expression* {
+          return IndexAccessor(array);
+        },
+        [&](const ast::BinaryExpression* bin_op) -> sem::Expression* {
+          return Binary(bin_op);
+        },
+        [&](const ast::BitcastExpression* bitcast) -> sem::Expression* {
+          return Bitcast(bitcast);
+        },
+        [&](const ast::CallExpression* call) -> sem::Expression* {
+          return Call(call);
+        },
+        [&](const ast::IdentifierExpression* ident) -> sem::Expression* {
+          return Identifier(ident);
+        },
+        [&](const ast::LiteralExpression* literal) -> sem::Expression* {
+          return Literal(literal);
+        },
+        [&](const ast::MemberAccessorExpression* member) -> sem::Expression* {
+          return MemberAccessor(member);
+        },
+        [&](const ast::UnaryOpExpression* unary) -> sem::Expression* {
+          return UnaryOp(unary);
+        },
+        [&](const ast::PhonyExpression*) -> sem::Expression* {
+          return builder_->create<sem::Expression>(
+              expr, builder_->create<sem::Void>(), current_statement_,
+              sem::Constant{}, /* has_side_effects */ false);
+        },
+        [&](Default) {
+          TINT_ICE(Resolver, diagnostics_)
+              << "unhandled expression type: " << expr->TypeInfo().name;
+          return nullptr;
+        });
+    if (!sem_expr) {
+      return nullptr;
+    }
+
+    builder_->Sem().Add(expr, sem_expr);
+    if (expr == root) {
+      return sem_expr;
+    }
+  }
+
+  TINT_ICE(Resolver, diagnostics_) << "Expression() did not find root node";
+  return nullptr;
+}
+
+sem::Expression* Resolver::IndexAccessor(
+    const ast::IndexAccessorExpression* expr) {
+  auto* idx = Sem(expr->index);
+  auto* obj = Sem(expr->object);
+  auto* obj_raw_ty = obj->Type();
+  auto* obj_ty = obj_raw_ty->UnwrapRef();
+  auto* ty = Switch(
+      obj_ty,  //
+      [&](const sem::Array* arr) -> const sem::Type* {
+        return arr->ElemType();
+      },
+      [&](const sem::Vector* vec) -> const sem::Type* {  //
+        return vec->type();
+      },
+      [&](const sem::Matrix* mat) -> const sem::Type* {
+        return builder_->create<sem::Vector>(mat->type(), mat->rows());
+      },
+      [&](Default) -> const sem::Type* {
+        AddError("cannot index type '" + TypeNameOf(obj_ty) + "'",
+                 expr->source);
+        return nullptr;
+      });
+  if (ty == nullptr) {
+    return nullptr;
+  }
+
+  auto* idx_ty = idx->Type()->UnwrapRef();
+  if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
+    AddError("index must be of type 'i32' or 'u32', found: '" +
+                 TypeNameOf(idx_ty) + "'",
+             idx->Declaration()->source);
+    return nullptr;
+  }
+
+  // If we're extracting from a reference, we return a reference.
+  if (auto* ref = obj_raw_ty->As<sem::Reference>()) {
+    ty = builder_->create<sem::Reference>(ty, ref->StorageClass(),
+                                          ref->Access());
+  }
+
+  auto val = EvaluateConstantValue(expr, ty);
+  bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
+  auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+                                                val, has_side_effects);
+  sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
+  return sem;
+}
+
+sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
+  auto* inner = Sem(expr->expr);
+  auto* ty = Type(expr->type);
+  if (!ty) {
+    return nullptr;
+  }
+
+  auto val = EvaluateConstantValue(expr, ty);
+  auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+                                                val, inner->HasSideEffects());
+
+  sem->Behaviors() = inner->Behaviors();
+
+  if (!ValidateBitcast(expr, ty)) {
+    return nullptr;
+  }
+
+  return sem;
+}
+
+sem::Call* Resolver::Call(const ast::CallExpression* expr) {
+  std::vector<const sem::Expression*> args(expr->args.size());
+  std::vector<const sem::Type*> arg_tys(args.size());
+  sem::Behaviors arg_behaviors;
+
+  // The element type of all the arguments. Nullptr if argument types are
+  // different.
+  const sem::Type* arg_el_ty = nullptr;
+
+  for (size_t i = 0; i < expr->args.size(); i++) {
+    auto* arg = Sem(expr->args[i]);
+    if (!arg) {
+      return nullptr;
+    }
+    args[i] = arg;
+    arg_tys[i] = args[i]->Type();
+    arg_behaviors.Add(arg->Behaviors());
+
+    // Determine the common argument element type
+    auto* el_ty = arg_tys[i]->UnwrapRef();
+    if (auto* vec = el_ty->As<sem::Vector>()) {
+      el_ty = vec->type();
+    } else if (auto* mat = el_ty->As<sem::Matrix>()) {
+      el_ty = mat->type();
+    }
+    if (i == 0) {
+      arg_el_ty = el_ty;
+    } else if (arg_el_ty != el_ty) {
+      arg_el_ty = nullptr;
+    }
+  }
+
+  arg_behaviors.Remove(sem::Behavior::kNext);
+
+  auto type_ctor_or_conv = [&](const sem::Type* ty) -> sem::Call* {
+    // The call has resolved to a type constructor or cast.
+    if (args.size() == 1) {
+      auto* target = ty;
+      auto* source = args[0]->Type()->UnwrapRef();
+      if ((source != target) &&  //
+          ((source->is_scalar() && target->is_scalar()) ||
+           (source->Is<sem::Vector>() && target->Is<sem::Vector>()) ||
+           (source->Is<sem::Matrix>() && target->Is<sem::Matrix>()))) {
+        // Note: Matrix types currently cannot be converted (the element type
+        // must only be f32). We implement this for the day we support other
+        // matrix element types.
+        return TypeConversion(expr, ty, args[0], arg_tys[0]);
+      }
+    }
+    return TypeConstructor(expr, ty, std::move(args), std::move(arg_tys));
+  };
+
+  // Resolve the target of the CallExpression to determine whether this is a
+  // function call, cast or type constructor expression.
+  if (expr->target.type) {
+    const sem::Type* ty = nullptr;
+
+    auto err_cannot_infer_el_ty = [&](std::string name) {
+      AddError(
+          "cannot infer " + name +
+              " element type, as constructor arguments have different types",
+          expr->source);
+      for (size_t i = 0; i < args.size(); i++) {
+        auto* arg = args[i];
+        AddNote("argument " + std::to_string(i) + " has type " +
+                    arg->Type()->FriendlyName(builder_->Symbols()),
+                arg->Declaration()->source);
+      }
+    };
+
+    if (!expr->args.empty()) {
+      // vecN() without explicit element type?
+      // Try to infer element type from args
+      if (auto* vec = expr->target.type->As<ast::Vector>()) {
+        if (!vec->type) {
+          if (!arg_el_ty) {
+            err_cannot_infer_el_ty("vector");
+            return nullptr;
+          }
+
+          Mark(vec);
+          auto* v = builder_->create<sem::Vector>(
+              arg_el_ty, static_cast<uint32_t>(vec->width));
+          if (!ValidateVector(v, vec->source)) {
+            return nullptr;
+          }
+          builder_->Sem().Add(vec, v);
+          ty = v;
+        }
+      }
+
+      // matNxM() without explicit element type?
+      // Try to infer element type from args
+      if (auto* mat = expr->target.type->As<ast::Matrix>()) {
+        if (!mat->type) {
+          if (!arg_el_ty) {
+            err_cannot_infer_el_ty("matrix");
+            return nullptr;
+          }
+
+          Mark(mat);
+          auto* column_type =
+              builder_->create<sem::Vector>(arg_el_ty, mat->rows);
+          auto* m = builder_->create<sem::Matrix>(column_type, mat->columns);
+          if (!ValidateMatrix(m, mat->source)) {
+            return nullptr;
+          }
+          builder_->Sem().Add(mat, m);
+          ty = m;
+        }
+      }
+    }
+
+    if (ty == nullptr) {
+      ty = Type(expr->target.type);
+      if (!ty) {
+        return nullptr;
+      }
+    }
+
+    return type_ctor_or_conv(ty);
+  }
+
+  auto* ident = expr->target.name;
+  Mark(ident);
+
+  auto* resolved = ResolvedSymbol(ident);
+  return Switch(
+      resolved,  //
+      [&](sem::Type* type) { return type_ctor_or_conv(type); },
+      [&](sem::Function* func) {
+        return FunctionCall(expr, func, std::move(args), arg_behaviors);
+      },
+      [&](sem::Variable* var) {
+        auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+        AddError("cannot call variable '" + name + "'", ident->source);
+        AddNote("'" + name + "' declared here", var->Declaration()->source);
+        return nullptr;
+      },
+      [&](Default) -> sem::Call* {
+        auto name = builder_->Symbols().NameFor(ident->symbol);
+        auto builtin_type = sem::ParseBuiltinType(name);
+        if (builtin_type != sem::BuiltinType::kNone) {
+          return BuiltinCall(expr, builtin_type, std::move(args),
+                             std::move(arg_tys));
+        }
+
+        TINT_ICE(Resolver, diagnostics_)
+            << expr->source << " unresolved CallExpression target:\n"
+            << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>")
+            << "\n"
+            << "name: " << builder_->Symbols().NameFor(ident->symbol);
+        return nullptr;
+      });
+}
+
+sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
+                                 sem::BuiltinType builtin_type,
+                                 const std::vector<const sem::Expression*> args,
+                                 const std::vector<const sem::Type*> arg_tys) {
+  auto* builtin =
+      builtin_table_->Lookup(builtin_type, std::move(arg_tys), expr->source);
+  if (!builtin) {
+    return nullptr;
+  }
+
+  if (builtin->IsDeprecated()) {
+    AddWarning("use of deprecated builtin", expr->source);
+  }
+
+  bool has_side_effects = builtin->HasSideEffects() ||
+                          std::any_of(args.begin(), args.end(), [](auto* e) {
+                            return e->HasSideEffects();
+                          });
+  auto* call = builder_->create<sem::Call>(expr, builtin, std::move(args),
+                                           current_statement_, sem::Constant{},
+                                           has_side_effects);
+
+  current_function_->AddDirectlyCalledBuiltin(builtin);
+
+  if (IsTextureBuiltin(builtin_type)) {
+    if (!ValidateTextureBuiltinFunction(call)) {
+      return nullptr;
+    }
+    // Collect a texture/sampler pair for this builtin.
+    const auto& signature = builtin->Signature();
+    int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
+    if (texture_index == -1) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "texture builtin without texture parameter";
+    }
+
+    auto* texture = args[texture_index]->As<sem::VariableUser>()->Variable();
+    if (!texture->Type()->UnwrapRef()->Is<sem::StorageTexture>()) {
+      int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
+      const sem::Variable* sampler =
+          sampler_index != -1
+              ? args[sampler_index]->As<sem::VariableUser>()->Variable()
+              : nullptr;
+      current_function_->AddTextureSamplerPair(texture, sampler);
+    }
+  }
+
+  if (!ValidateBuiltinCall(call)) {
+    return nullptr;
+  }
+
+  current_function_->AddDirectCall(call);
+
+  return call;
+}
+
+sem::Call* Resolver::FunctionCall(
+    const ast::CallExpression* expr,
+    sem::Function* target,
+    const std::vector<const sem::Expression*> args,
+    sem::Behaviors arg_behaviors) {
+  auto sym = expr->target.name->symbol;
+  auto name = builder_->Symbols().NameFor(sym);
+
+  // TODO(crbug.com/tint/1420): For now, assume all function calls have side
+  // effects.
+  bool has_side_effects = true;
+  auto* call = builder_->create<sem::Call>(expr, target, std::move(args),
+                                           current_statement_, sem::Constant{},
+                                           has_side_effects);
+
+  if (current_function_) {
+    // Note: Requires called functions to be resolved first.
+    // This is currently guaranteed as functions must be declared before
+    // use.
+    current_function_->AddTransitivelyCalledFunction(target);
+    current_function_->AddDirectCall(call);
+    for (auto* transitive_call : target->TransitivelyCalledFunctions()) {
+      current_function_->AddTransitivelyCalledFunction(transitive_call);
+    }
+
+    // We inherit any referenced variables from the callee.
+    for (auto* var : target->TransitivelyReferencedGlobals()) {
+      current_function_->AddTransitivelyReferencedGlobal(var);
+    }
+
+    // Map all texture/sampler pairs from the target function to the
+    // current function. These can only be global or parameter
+    // variables. Resolve any parameter variables to the corresponding
+    // argument passed to the current function. Leave global variables
+    // as-is. Then add the mapped pair to the current function's list of
+    // texture/sampler pairs.
+    for (sem::VariablePair pair : target->TextureSamplerPairs()) {
+      const sem::Variable* texture = pair.first;
+      const sem::Variable* sampler = pair.second;
+      if (auto* param = texture->As<sem::Parameter>()) {
+        texture = args[param->Index()]->As<sem::VariableUser>()->Variable();
+      }
+      if (sampler) {
+        if (auto* param = sampler->As<sem::Parameter>()) {
+          sampler = args[param->Index()]->As<sem::VariableUser>()->Variable();
+        }
+      }
+      current_function_->AddTextureSamplerPair(texture, sampler);
+    }
+  }
+
+  target->AddCallSite(call);
+
+  call->Behaviors() = arg_behaviors + target->Behaviors();
+
+  if (!ValidateFunctionCall(call)) {
+    return nullptr;
+  }
+
+  return call;
+}
+
+sem::Call* Resolver::TypeConversion(const ast::CallExpression* expr,
+                                    const sem::Type* target,
+                                    const sem::Expression* arg,
+                                    const sem::Type* source) {
+  // It is not valid to have a type-cast call expression inside a call
+  // statement.
+  if (IsCallStatement(expr)) {
+    AddError("type cast evaluated but not used", expr->source);
+    return nullptr;
+  }
+
+  auto* call_target = utils::GetOrCreate(
+      type_conversions_, TypeConversionSig{target, source},
+      [&]() -> sem::TypeConversion* {
+        // Now that the argument types have been determined, make sure that
+        // they obey the conversion rules laid out in
+        // https://gpuweb.github.io/gpuweb/wgsl/#conversion-expr.
+        bool ok = Switch(
+            target,
+            [&](const sem::Vector* vec_type) {
+              return ValidateVectorConstructorOrCast(expr, vec_type);
+            },
+            [&](const sem::Matrix* mat_type) {
+              // Note: Matrix types currently cannot be converted (the element
+              // type must only be f32). We implement this for the day we
+              // support other matrix element types.
+              return ValidateMatrixConstructorOrCast(expr, mat_type);
+            },
+            [&](const sem::Array* arr_type) {
+              return ValidateArrayConstructorOrCast(expr, arr_type);
+            },
+            [&](const sem::Struct* struct_type) {
+              return ValidateStructureConstructorOrCast(expr, struct_type);
+            },
+            [&](Default) {
+              if (target->is_scalar()) {
+                return ValidateScalarConstructorOrCast(expr, target);
+              }
+              AddError("type is not constructible", expr->source);
+              return false;
+            });
+        if (!ok) {
+          return nullptr;
+        }
+
+        auto* param = builder_->create<sem::Parameter>(
+            nullptr,                   // declaration
+            0,                         // index
+            source->UnwrapRef(),       // type
+            ast::StorageClass::kNone,  // storage_class
+            ast::Access::kUndefined);  // access
+        return builder_->create<sem::TypeConversion>(target, param);
+      });
+
+  if (!call_target) {
+    return nullptr;
+  }
+
+  auto val = EvaluateConstantValue(expr, target);
+  bool has_side_effects = arg->HasSideEffects();
+  return builder_->create<sem::Call>(expr, call_target,
+                                     std::vector<const sem::Expression*>{arg},
+                                     current_statement_, val, has_side_effects);
+}
+
+sem::Call* Resolver::TypeConstructor(
+    const ast::CallExpression* expr,
+    const sem::Type* ty,
+    const std::vector<const sem::Expression*> args,
+    const std::vector<const sem::Type*> arg_tys) {
+  // It is not valid to have a type-constructor call expression as a call
+  // statement.
+  if (IsCallStatement(expr)) {
+    AddError("type constructor evaluated but not used", expr->source);
+    return nullptr;
+  }
+
+  auto* call_target = utils::GetOrCreate(
+      type_ctors_, TypeConstructorSig{ty, arg_tys},
+      [&]() -> sem::TypeConstructor* {
+        // Now that the argument types have been determined, make sure that
+        // they obey the constructor type rules laid out in
+        // https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr.
+        bool ok = Switch(
+            ty,
+            [&](const sem::Vector* vec_type) {
+              return ValidateVectorConstructorOrCast(expr, vec_type);
+            },
+            [&](const sem::Matrix* mat_type) {
+              return ValidateMatrixConstructorOrCast(expr, mat_type);
+            },
+            [&](const sem::Array* arr_type) {
+              return ValidateArrayConstructorOrCast(expr, arr_type);
+            },
+            [&](const sem::Struct* struct_type) {
+              return ValidateStructureConstructorOrCast(expr, struct_type);
+            },
+            [&](Default) {
+              if (ty->is_scalar()) {
+                return ValidateScalarConstructorOrCast(expr, ty);
+              }
+              AddError("type is not constructible", expr->source);
+              return false;
+            });
+        if (!ok) {
+          return nullptr;
+        }
+
+        return builder_->create<sem::TypeConstructor>(
+            ty, utils::Transform(
+                    arg_tys,
+                    [&](const sem::Type* t, size_t i) -> const sem::Parameter* {
+                      return builder_->create<sem::Parameter>(
+                          nullptr,                   // declaration
+                          static_cast<uint32_t>(i),  // index
+                          t->UnwrapRef(),            // type
+                          ast::StorageClass::kNone,  // storage_class
+                          ast::Access::kUndefined);  // access
+                    }));
+      });
+
+  if (!call_target) {
+    return nullptr;
+  }
+
+  auto val = EvaluateConstantValue(expr, ty);
+  bool has_side_effects = std::any_of(
+      args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
+  return builder_->create<sem::Call>(expr, call_target, std::move(args),
+                                     current_statement_, val, has_side_effects);
+}
+
+sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
+  auto* ty = TypeOf(literal);
+  if (!ty) {
+    return nullptr;
+  }
+
+  auto val = EvaluateConstantValue(literal, ty);
+  return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
+                                           /* has_side_effects */ false);
+}
+
+sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
+  auto symbol = expr->symbol;
+  auto* resolved = ResolvedSymbol(expr);
+  if (auto* var = As<sem::Variable>(resolved)) {
+    auto* user =
+        builder_->create<sem::VariableUser>(expr, current_statement_, var);
+
+    if (current_statement_) {
+      // If identifier is part of a loop continuing block, make sure it
+      // doesn't refer to a variable that is bypassed by a continue statement
+      // in the loop's body block.
+      if (auto* continuing_block =
+              current_statement_
+                  ->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
+        auto* loop_block =
+            continuing_block->FindFirstParent<sem::LoopBlockStatement>();
+        if (loop_block->FirstContinue()) {
+          auto& decls = loop_block->Decls();
+          // If our identifier is in loop_block->decls, make sure its index is
+          // less than first_continue
+          auto iter =
+              std::find_if(decls.begin(), decls.end(),
+                           [&symbol](auto* v) { return v->symbol == symbol; });
+          if (iter != decls.end()) {
+            auto var_decl_index =
+                static_cast<size_t>(std::distance(decls.begin(), iter));
+            if (var_decl_index >= loop_block->NumDeclsAtFirstContinue()) {
+              AddError("continue statement bypasses declaration of '" +
+                           builder_->Symbols().NameFor(symbol) + "'",
+                       loop_block->FirstContinue()->source);
+              AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
+                          "' declared here",
+                      (*iter)->source);
+              AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
+                          "' referenced in continuing block here",
+                      expr->source);
+              return nullptr;
+            }
+          }
+        }
+      }
+    }
+
+    if (current_function_) {
+      if (auto* global = var->As<sem::GlobalVariable>()) {
+        current_function_->AddDirectlyReferencedGlobal(global);
+      }
+    }
+
+    var->AddUser(user);
+    return user;
+  }
+
+  if (Is<sem::Function>(resolved)) {
+    AddError("missing '(' for function call", expr->source.End());
+    return nullptr;
+  }
+
+  if (IsBuiltin(symbol)) {
+    AddError("missing '(' for builtin call", expr->source.End());
+    return nullptr;
+  }
+
+  if (resolved->Is<sem::Type>()) {
+    AddError("missing '(' for type constructor or cast", expr->source.End());
+    return nullptr;
+  }
+
+  TINT_ICE(Resolver, diagnostics_)
+      << expr->source << " unresolved identifier:\n"
+      << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>")
+      << "\n"
+      << "name: " << builder_->Symbols().NameFor(symbol);
+  return nullptr;
+}
+
+sem::Expression* Resolver::MemberAccessor(
+    const ast::MemberAccessorExpression* expr) {
+  auto* structure = TypeOf(expr->structure);
+  auto* storage_ty = structure->UnwrapRef();
+
+  const sem::Type* ret = nullptr;
+  std::vector<uint32_t> swizzle;
+
+  if (auto* str = storage_ty->As<sem::Struct>()) {
+    Mark(expr->member);
+    auto symbol = expr->member->symbol;
+
+    const sem::StructMember* member = nullptr;
+    for (auto* m : str->Members()) {
+      if (m->Name() == symbol) {
+        ret = m->Type();
+        member = m;
+        break;
+      }
+    }
+
+    if (ret == nullptr) {
+      AddError(
+          "struct member " + builder_->Symbols().NameFor(symbol) + " not found",
+          expr->source);
+      return nullptr;
+    }
+
+    // If we're extracting from a reference, we return a reference.
+    if (auto* ref = structure->As<sem::Reference>()) {
+      ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
+                                             ref->Access());
+    }
+
+    // Structure may be a side-effecting expression (e.g. function call).
+    auto* sem_structure = Sem(expr->structure);
+    bool has_side_effects = sem_structure && sem_structure->HasSideEffects();
+
+    return builder_->create<sem::StructMemberAccess>(
+        expr, ret, current_statement_, member, has_side_effects);
+  }
+
+  if (auto* vec = storage_ty->As<sem::Vector>()) {
+    Mark(expr->member);
+    std::string s = builder_->Symbols().NameFor(expr->member->symbol);
+    auto size = s.size();
+    swizzle.reserve(s.size());
+
+    for (auto c : s) {
+      switch (c) {
+        case 'x':
+        case 'r':
+          swizzle.emplace_back(0);
+          break;
+        case 'y':
+        case 'g':
+          swizzle.emplace_back(1);
+          break;
+        case 'z':
+        case 'b':
+          swizzle.emplace_back(2);
+          break;
+        case 'w':
+        case 'a':
+          swizzle.emplace_back(3);
+          break;
+        default:
+          AddError("invalid vector swizzle character",
+                   expr->member->source.Begin() + swizzle.size());
+          return nullptr;
+      }
+
+      if (swizzle.back() >= vec->Width()) {
+        AddError("invalid vector swizzle member", expr->member->source);
+        return nullptr;
+      }
+    }
+
+    if (size < 1 || size > 4) {
+      AddError("invalid vector swizzle size", expr->member->source);
+      return nullptr;
+    }
+
+    // All characters are valid, check if they're being mixed
+    auto is_rgba = [](char c) {
+      return c == 'r' || c == 'g' || c == 'b' || c == 'a';
+    };
+    auto is_xyzw = [](char c) {
+      return c == 'x' || c == 'y' || c == 'z' || c == 'w';
+    };
+    if (!std::all_of(s.begin(), s.end(), is_rgba) &&
+        !std::all_of(s.begin(), s.end(), is_xyzw)) {
+      AddError("invalid mixing of vector swizzle characters rgba with xyzw",
+               expr->member->source);
+      return nullptr;
+    }
+
+    if (size == 1) {
+      // A single element swizzle is just the type of the vector.
+      ret = vec->type();
+      // If we're extracting from a reference, we return a reference.
+      if (auto* ref = structure->As<sem::Reference>()) {
+        ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
+                                               ref->Access());
+      }
+    } else {
+      // The vector will have a number of components equal to the length of
+      // the swizzle.
+      ret = builder_->create<sem::Vector>(vec->type(),
+                                          static_cast<uint32_t>(size));
+    }
+    return builder_->create<sem::Swizzle>(expr, ret, current_statement_,
+                                          std::move(swizzle));
+  }
+
+  AddError(
+      "invalid member accessor expression. Expected vector or struct, got '" +
+          TypeNameOf(storage_ty) + "'",
+      expr->structure->source);
+  return nullptr;
+}
+
+sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
+  using Bool = sem::Bool;
+  using F32 = sem::F32;
+  using I32 = sem::I32;
+  using U32 = sem::U32;
+  using Matrix = sem::Matrix;
+  using Vector = sem::Vector;
+
+  auto* lhs = Sem(expr->lhs);
+  auto* rhs = Sem(expr->rhs);
+
+  auto* lhs_ty = lhs->Type()->UnwrapRef();
+  auto* rhs_ty = rhs->Type()->UnwrapRef();
+
+  auto* lhs_vec = lhs_ty->As<Vector>();
+  auto* lhs_vec_elem_type = lhs_vec ? lhs_vec->type() : nullptr;
+  auto* rhs_vec = rhs_ty->As<Vector>();
+  auto* rhs_vec_elem_type = rhs_vec ? rhs_vec->type() : nullptr;
+
+  const bool matching_vec_elem_types =
+      lhs_vec_elem_type && rhs_vec_elem_type &&
+      (lhs_vec_elem_type == rhs_vec_elem_type) &&
+      (lhs_vec->Width() == rhs_vec->Width());
+
+  const bool matching_types = matching_vec_elem_types || (lhs_ty == rhs_ty);
+
+  auto build = [&](const sem::Type* ty) {
+    auto val = EvaluateConstantValue(expr, ty);
+    bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
+    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+                                                  val, has_side_effects);
+    sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
+    return sem;
+  };
+
+  // Binary logical expressions
+  if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
+    if (matching_types && lhs_ty->Is<Bool>()) {
+      return build(lhs_ty);
+    }
+  }
+  if (expr->IsOr() || expr->IsAnd()) {
+    if (matching_types && lhs_ty->Is<Bool>()) {
+      return build(lhs_ty);
+    }
+    if (matching_types && lhs_vec_elem_type && lhs_vec_elem_type->Is<Bool>()) {
+      return build(lhs_ty);
+    }
+  }
+
+  // Arithmetic expressions
+  if (expr->IsArithmetic()) {
+    // Binary arithmetic expressions over scalars
+    if (matching_types && lhs_ty->is_numeric_scalar()) {
+      return build(lhs_ty);
+    }
+
+    // Binary arithmetic expressions over vectors
+    if (matching_types && lhs_vec_elem_type &&
+        lhs_vec_elem_type->is_numeric_scalar()) {
+      return build(lhs_ty);
+    }
+
+    // Binary arithmetic expressions with mixed scalar and vector operands
+    if (lhs_vec_elem_type && (lhs_vec_elem_type == rhs_ty)) {
+      if (expr->IsModulo()) {
+        if (rhs_ty->is_integer_scalar()) {
+          return build(lhs_ty);
+        }
+      } else if (rhs_ty->is_numeric_scalar()) {
+        return build(lhs_ty);
+      }
+    }
+    if (rhs_vec_elem_type && (rhs_vec_elem_type == lhs_ty)) {
+      if (expr->IsModulo()) {
+        if (lhs_ty->is_integer_scalar()) {
+          return build(rhs_ty);
+        }
+      } else if (lhs_ty->is_numeric_scalar()) {
+        return build(rhs_ty);
+      }
+    }
+  }
+
+  // Matrix arithmetic
+  auto* lhs_mat = lhs_ty->As<Matrix>();
+  auto* lhs_mat_elem_type = lhs_mat ? lhs_mat->type() : nullptr;
+  auto* rhs_mat = rhs_ty->As<Matrix>();
+  auto* rhs_mat_elem_type = rhs_mat ? rhs_mat->type() : nullptr;
+  // Addition and subtraction of float matrices
+  if ((expr->IsAdd() || expr->IsSubtract()) && lhs_mat_elem_type &&
+      lhs_mat_elem_type->Is<F32>() && rhs_mat_elem_type &&
+      rhs_mat_elem_type->Is<F32>() &&
+      (lhs_mat->columns() == rhs_mat->columns()) &&
+      (lhs_mat->rows() == rhs_mat->rows())) {
+    return build(rhs_ty);
+  }
+  if (expr->IsMultiply()) {
+    // Multiplication of a matrix and a scalar
+    if (lhs_ty->Is<F32>() && rhs_mat_elem_type &&
+        rhs_mat_elem_type->Is<F32>()) {
+      return build(rhs_ty);
+    }
+    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
+        rhs_ty->Is<F32>()) {
+      return build(lhs_ty);
+    }
+
+    // Vector times matrix
+    if (lhs_vec_elem_type && lhs_vec_elem_type->Is<F32>() &&
+        rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
+        (lhs_vec->Width() == rhs_mat->rows())) {
+      return build(
+          builder_->create<sem::Vector>(lhs_vec->type(), rhs_mat->columns()));
+    }
+
+    // Matrix times vector
+    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
+        rhs_vec_elem_type && rhs_vec_elem_type->Is<F32>() &&
+        (lhs_mat->columns() == rhs_vec->Width())) {
+      return build(
+          builder_->create<sem::Vector>(rhs_vec->type(), lhs_mat->rows()));
+    }
+
+    // Matrix times matrix
+    if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
+        rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
+        (lhs_mat->columns() == rhs_mat->rows())) {
+      return build(builder_->create<sem::Matrix>(
+          builder_->create<sem::Vector>(lhs_mat_elem_type, lhs_mat->rows()),
+          rhs_mat->columns()));
+    }
+  }
+
+  // Comparison expressions
+  if (expr->IsComparison()) {
+    if (matching_types) {
+      // Special case for bools: only == and !=
+      if (lhs_ty->Is<Bool>() && (expr->IsEqual() || expr->IsNotEqual())) {
+        return build(builder_->create<sem::Bool>());
+      }
+
+      // For the rest, we can compare i32, u32, and f32
+      if (lhs_ty->IsAnyOf<I32, U32, F32>()) {
+        return build(builder_->create<sem::Bool>());
+      }
+    }
+
+    // Same for vectors
+    if (matching_vec_elem_types) {
+      if (lhs_vec_elem_type->Is<Bool>() &&
+          (expr->IsEqual() || expr->IsNotEqual())) {
+        return build(builder_->create<sem::Vector>(
+            builder_->create<sem::Bool>(), lhs_vec->Width()));
+      }
+
+      if (lhs_vec_elem_type->is_numeric_scalar()) {
+        return build(builder_->create<sem::Vector>(
+            builder_->create<sem::Bool>(), lhs_vec->Width()));
+      }
+    }
+  }
+
+  // Binary bitwise operations
+  if (expr->IsBitwise()) {
+    if (matching_types && lhs_ty->is_integer_scalar_or_vector()) {
+      return build(lhs_ty);
+    }
+  }
+
+  // Bit shift expressions
+  if (expr->IsBitshift()) {
+    // Type validation rules are the same for left or right shift, despite
+    // differences in computation rules (i.e. right shift can be arithmetic or
+    // logical depending on lhs type).
+
+    if (lhs_ty->IsAnyOf<I32, U32>() && rhs_ty->Is<U32>()) {
+      return build(lhs_ty);
+    }
+
+    if (lhs_vec_elem_type && lhs_vec_elem_type->IsAnyOf<I32, U32>() &&
+        rhs_vec_elem_type && rhs_vec_elem_type->Is<U32>()) {
+      return build(lhs_ty);
+    }
+  }
+
+  AddError("Binary expression operand types are invalid for this operation: " +
+               TypeNameOf(lhs_ty) + " " + FriendlyName(expr->op) + " " +
+               TypeNameOf(rhs_ty),
+           expr->source);
+  return nullptr;
+}
+
+sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
+  auto* expr = Sem(unary->expr);
+  auto* expr_ty = expr->Type();
+  if (!expr_ty) {
+    return nullptr;
+  }
+
+  const sem::Type* ty = nullptr;
+
+  switch (unary->op) {
+    case ast::UnaryOp::kNot:
+      // Result type matches the deref'd inner type.
+      ty = expr_ty->UnwrapRef();
+      if (!ty->Is<sem::Bool>() && !ty->is_bool_vector()) {
+        AddError(
+            "cannot logical negate expression of type '" + TypeNameOf(expr_ty),
+            unary->expr->source);
+        return nullptr;
+      }
+      break;
+
+    case ast::UnaryOp::kComplement:
+      // Result type matches the deref'd inner type.
+      ty = expr_ty->UnwrapRef();
+      if (!ty->is_integer_scalar_or_vector()) {
+        AddError("cannot bitwise complement expression of type '" +
+                     TypeNameOf(expr_ty),
+                 unary->expr->source);
+        return nullptr;
+      }
+      break;
+
+    case ast::UnaryOp::kNegation:
+      // Result type matches the deref'd inner type.
+      ty = expr_ty->UnwrapRef();
+      if (!(ty->IsAnyOf<sem::F32, sem::I32>() ||
+            ty->is_signed_integer_vector() || ty->is_float_vector())) {
+        AddError("cannot negate expression of type '" + TypeNameOf(expr_ty),
+                 unary->expr->source);
+        return nullptr;
+      }
+      break;
+
+    case ast::UnaryOp::kAddressOf:
+      if (auto* ref = expr_ty->As<sem::Reference>()) {
+        if (ref->StoreType()->UnwrapRef()->is_handle()) {
+          AddError(
+              "cannot take the address of expression in handle storage class",
+              unary->expr->source);
+          return nullptr;
+        }
+
+        auto* array = unary->expr->As<ast::IndexAccessorExpression>();
+        auto* member = unary->expr->As<ast::MemberAccessorExpression>();
+        if ((array && TypeOf(array->object)->UnwrapRef()->Is<sem::Vector>()) ||
+            (member &&
+             TypeOf(member->structure)->UnwrapRef()->Is<sem::Vector>())) {
+          AddError("cannot take the address of a vector component",
+                   unary->expr->source);
+          return nullptr;
+        }
+
+        ty = builder_->create<sem::Pointer>(ref->StoreType(),
+                                            ref->StorageClass(), ref->Access());
+      } else {
+        AddError("cannot take the address of expression", unary->expr->source);
+        return nullptr;
+      }
+      break;
+
+    case ast::UnaryOp::kIndirection:
+      if (auto* ptr = expr_ty->As<sem::Pointer>()) {
+        ty = builder_->create<sem::Reference>(
+            ptr->StoreType(), ptr->StorageClass(), ptr->Access());
+      } else {
+        AddError("cannot dereference expression of type '" +
+                     TypeNameOf(expr_ty) + "'",
+                 unary->expr->source);
+        return nullptr;
+      }
+      break;
+  }
+
+  auto val = EvaluateConstantValue(unary, ty);
+  auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_,
+                                                val, expr->HasSideEffects());
+  sem->Behaviors() = expr->Behaviors();
+  return sem;
+}
+
+sem::Type* Resolver::TypeDecl(const ast::TypeDecl* named_type) {
+  sem::Type* result = nullptr;
+  if (auto* alias = named_type->As<ast::Alias>()) {
+    result = Alias(alias);
+  } else if (auto* str = named_type->As<ast::Struct>()) {
+    result = Structure(str);
+  } else {
+    TINT_UNREACHABLE(Resolver, diagnostics_) << "Unhandled TypeDecl";
+  }
+
+  if (!result) {
+    return nullptr;
+  }
+
+  builder_->Sem().Add(named_type, result);
+  return result;
+}
+
+sem::Type* Resolver::TypeOf(const ast::Expression* expr) {
+  auto* sem = Sem(expr);
+  return sem ? const_cast<sem::Type*>(sem->Type()) : nullptr;
+}
+
+std::string Resolver::TypeNameOf(const sem::Type* ty) {
+  return RawTypeNameOf(ty->UnwrapRef());
+}
+
+std::string Resolver::RawTypeNameOf(const sem::Type* ty) {
+  return ty->FriendlyName(builder_->Symbols());
+}
+
+sem::Type* Resolver::TypeOf(const ast::LiteralExpression* lit) {
+  return Switch(
+      lit,
+      [&](const ast::SintLiteralExpression*) -> sem::Type* {
+        return builder_->create<sem::I32>();
+      },
+      [&](const ast::UintLiteralExpression*) -> sem::Type* {
+        return builder_->create<sem::U32>();
+      },
+      [&](const ast::FloatLiteralExpression*) -> sem::Type* {
+        return builder_->create<sem::F32>();
+      },
+      [&](const ast::BoolLiteralExpression*) -> sem::Type* {
+        return builder_->create<sem::Bool>();
+      },
+      [&](Default) -> sem::Type* {
+        TINT_UNREACHABLE(Resolver, diagnostics_)
+            << "Unhandled literal type: " << lit->TypeInfo().name;
+        return nullptr;
+      });
+}
+
+sem::Array* Resolver::Array(const ast::Array* arr) {
+  auto source = arr->source;
+
+  auto* elem_type = Type(arr->type);
+  if (!elem_type) {
+    return nullptr;
+  }
+
+  if (!IsPlain(elem_type)) {  // Check must come before GetDefaultAlignAndSize()
+    AddError(TypeNameOf(elem_type) +
+                 " cannot be used as an element type of an array",
+             source);
+    return nullptr;
+  }
+
+  uint32_t el_align = elem_type->Align();
+  uint32_t el_size = elem_type->Size();
+
+  if (!ValidateNoDuplicateAttributes(arr->attributes)) {
+    return nullptr;
+  }
+
+  // Look for explicit stride via @stride(n) attribute
+  uint32_t explicit_stride = 0;
+  for (auto* attr : arr->attributes) {
+    Mark(attr);
+    if (auto* sd = attr->As<ast::StrideAttribute>()) {
+      explicit_stride = sd->stride;
+      if (!ValidateArrayStrideAttribute(sd, el_size, el_align, source)) {
+        return nullptr;
+      }
+      continue;
+    }
+
+    AddError("attribute is not valid for array types", attr->source);
+    return nullptr;
+  }
+
+  // Calculate implicit stride
+  uint64_t implicit_stride = utils::RoundUp<uint64_t>(el_align, el_size);
+
+  uint64_t stride = explicit_stride ? explicit_stride : implicit_stride;
+
+  // Evaluate the constant array size expression.
+  // sem::Array uses a size of 0 for a runtime-sized array.
+  uint32_t count = 0;
+  if (auto* count_expr = arr->count) {
+    auto* count_sem = Expression(count_expr);
+    if (!count_sem) {
+      return nullptr;
+    }
+
+    auto size_source = count_expr->source;
+
+    auto* ty = count_sem->Type()->UnwrapRef();
+    if (!ty->is_integer_scalar()) {
+      AddError("array size must be integer scalar", size_source);
+      return nullptr;
+    }
+
+    if (auto* ident = count_expr->As<ast::IdentifierExpression>()) {
+      // Make sure the identifier is a non-overridable module-scope constant.
+      auto* var = ResolvedSymbol<sem::GlobalVariable>(ident);
+      if (!var || !var->Declaration()->is_const) {
+        AddError("array size identifier must be a module-scope constant",
+                 size_source);
+        return nullptr;
+      }
+      if (var->IsOverridable()) {
+        AddError("array size expression must not be pipeline-overridable",
+                 size_source);
+        return nullptr;
+      }
+
+      count_expr = var->Declaration()->constructor;
+    } else if (!count_expr->Is<ast::LiteralExpression>()) {
+      AddError(
+          "array size expression must be either a literal or a module-scope "
+          "constant",
+          size_source);
+      return nullptr;
+    }
+
+    auto count_val = count_sem->ConstantValue();
+    if (!count_val) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "could not resolve array size expression";
+      return nullptr;
+    }
+
+    if (ty->is_signed_integer_scalar() ? count_val.Elements()[0].i32 < 1
+                                       : count_val.Elements()[0].u32 < 1u) {
+      AddError("array size must be at least 1", size_source);
+      return nullptr;
+    }
+
+    count = count_val.Elements()[0].u32;
+  }
+
+  auto size = std::max<uint64_t>(count, 1) * stride;
+  if (size > std::numeric_limits<uint32_t>::max()) {
+    std::stringstream msg;
+    msg << "array size in bytes must not exceed 0x" << std::hex
+        << std::numeric_limits<uint32_t>::max() << ", but is 0x" << std::hex
+        << size;
+    AddError(msg.str(), arr->source);
+    return nullptr;
+  }
+  if (stride > std::numeric_limits<uint32_t>::max() ||
+      implicit_stride > std::numeric_limits<uint32_t>::max()) {
+    TINT_ICE(Resolver, diagnostics_)
+        << "calculated array stride exceeds uint32";
+    return nullptr;
+  }
+  auto* out = builder_->create<sem::Array>(
+      elem_type, count, el_align, static_cast<uint32_t>(size),
+      static_cast<uint32_t>(stride), static_cast<uint32_t>(implicit_stride));
+
+  if (!ValidateArray(out, source)) {
+    return nullptr;
+  }
+
+  if (elem_type->Is<sem::Atomic>()) {
+    atomic_composite_info_.emplace(out, arr->type->source);
+  } else {
+    auto found = atomic_composite_info_.find(elem_type);
+    if (found != atomic_composite_info_.end()) {
+      atomic_composite_info_.emplace(out, found->second);
+    }
+  }
+
+  return out;
+}
+
+sem::Type* Resolver::Alias(const ast::Alias* alias) {
+  auto* ty = Type(alias->type);
+  if (!ty) {
+    return nullptr;
+  }
+  if (!ValidateAlias(alias)) {
+    return nullptr;
+  }
+  return ty;
+}
+
+sem::Struct* Resolver::Structure(const ast::Struct* str) {
+  if (!ValidateNoDuplicateAttributes(str->attributes)) {
+    return nullptr;
+  }
+  for (auto* attr : str->attributes) {
+    Mark(attr);
+  }
+
+  sem::StructMemberList sem_members;
+  sem_members.reserve(str->members.size());
+
+  // Calculate the effective size and alignment of each field, and the overall
+  // size of the structure.
+  // For size, use the size attribute if provided, otherwise use the default
+  // size for the type.
+  // For alignment, use the alignment attribute if provided, otherwise use the
+  // default alignment for the member type.
+  // Diagnostic errors are raised if a basic rule is violated.
+  // Validation of storage-class rules requires analysing the actual variable
+  // usage of the structure, and so is performed as part of the variable
+  // validation.
+  uint64_t struct_size = 0;
+  uint64_t struct_align = 1;
+  std::unordered_map<Symbol, const ast::StructMember*> member_map;
+
+  for (auto* member : str->members) {
+    Mark(member);
+    auto result = member_map.emplace(member->symbol, member);
+    if (!result.second) {
+      AddError("redefinition of '" +
+                   builder_->Symbols().NameFor(member->symbol) + "'",
+               member->source);
+      AddNote("previous definition is here", result.first->second->source);
+      return nullptr;
+    }
+
+    // Resolve member type
+    auto* type = Type(member->type);
+    if (!type) {
+      return nullptr;
+    }
+
+    // Validate member type
+    if (!IsPlain(type)) {
+      AddError(TypeNameOf(type) +
+                   " cannot be used as the type of a structure member",
+               member->source);
+      return nullptr;
+    }
+
+    uint64_t offset = struct_size;
+    uint64_t align = type->Align();
+    uint64_t size = type->Size();
+
+    if (!ValidateNoDuplicateAttributes(member->attributes)) {
+      return nullptr;
+    }
+
+    bool has_offset_attr = false;
+    bool has_align_attr = false;
+    bool has_size_attr = false;
+    for (auto* attr : member->attributes) {
+      Mark(attr);
+      if (auto* o = attr->As<ast::StructMemberOffsetAttribute>()) {
+        // Offset attributes are not part of the WGSL spec, but are emitted
+        // by the SPIR-V reader.
+        if (o->offset < struct_size) {
+          AddError("offsets must be in ascending order", o->source);
+          return nullptr;
+        }
+        offset = o->offset;
+        align = 1;
+        has_offset_attr = true;
+      } else if (auto* a = attr->As<ast::StructMemberAlignAttribute>()) {
+        if (a->align <= 0 || !utils::IsPowerOfTwo(a->align)) {
+          AddError("align value must be a positive, power-of-two integer",
+                   a->source);
+          return nullptr;
+        }
+        align = a->align;
+        has_align_attr = true;
+      } else if (auto* s = attr->As<ast::StructMemberSizeAttribute>()) {
+        if (s->size < size) {
+          AddError("size must be at least as big as the type's size (" +
+                       std::to_string(size) + ")",
+                   s->source);
+          return nullptr;
+        }
+        size = s->size;
+        has_size_attr = true;
+      }
+    }
+
+    if (has_offset_attr && (has_align_attr || has_size_attr)) {
+      AddError("offset attributes cannot be used with align or size attributes",
+               member->source);
+      return nullptr;
+    }
+
+    offset = utils::RoundUp(align, offset);
+    if (offset > std::numeric_limits<uint32_t>::max()) {
+      std::stringstream msg;
+      msg << "struct member has byte offset 0x" << std::hex << offset
+          << ", but must not exceed 0x" << std::hex
+          << std::numeric_limits<uint32_t>::max();
+      AddError(msg.str(), member->source);
+      return nullptr;
+    }
+
+    auto* sem_member = builder_->create<sem::StructMember>(
+        member, 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));
+    builder_->Sem().Add(member, sem_member);
+    sem_members.emplace_back(sem_member);
+
+    struct_size = offset + size;
+    struct_align = std::max(struct_align, align);
+  }
+
+  uint64_t size_no_padding = struct_size;
+  struct_size = utils::RoundUp(struct_align, struct_size);
+
+  if (struct_size > std::numeric_limits<uint32_t>::max()) {
+    std::stringstream msg;
+    msg << "struct size in bytes must not exceed 0x" << std::hex
+        << std::numeric_limits<uint32_t>::max() << ", but is 0x" << std::hex
+        << struct_size;
+    AddError(msg.str(), str->source);
+    return nullptr;
+  }
+  if (struct_align > std::numeric_limits<uint32_t>::max()) {
+    TINT_ICE(Resolver, diagnostics_)
+        << "calculated struct stride exceeds uint32";
+    return nullptr;
+  }
+
+  auto* out = builder_->create<sem::Struct>(
+      str, 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_.emplace(out,
+                                     sem_members[i]->Declaration()->source);
+      break;
+    } else {
+      auto found = atomic_composite_info_.find(mem_type);
+      if (found != atomic_composite_info_.end()) {
+        atomic_composite_info_.emplace(out, found->second);
+        break;
+      }
+    }
+  }
+
+  if (!ValidateStructure(out)) {
+    return nullptr;
+  }
+
+  return out;
+}
+
+sem::Statement* Resolver::ReturnStatement(const ast::ReturnStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    auto& behaviors = current_statement_->Behaviors();
+    behaviors = sem::Behavior::kReturn;
+
+    if (auto* value = stmt->value) {
+      auto* expr = Expression(value);
+      if (!expr) {
+        return false;
+      }
+      behaviors.Add(expr->Behaviors() - sem::Behavior::kNext);
+    }
+
+    // Validate after processing the return value expression so that its type
+    // is available for validation.
+    return ValidateReturn(stmt);
+  });
+}
+
+sem::SwitchStatement* Resolver::SwitchStatement(
+    const ast::SwitchStatement* stmt) {
+  auto* sem = builder_->create<sem::SwitchStatement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    auto& behaviors = sem->Behaviors();
+
+    auto* cond = Expression(stmt->condition);
+    if (!cond) {
+      return false;
+    }
+    behaviors = cond->Behaviors() - sem::Behavior::kNext;
+
+    for (auto* case_stmt : stmt->body) {
+      Mark(case_stmt);
+      auto* c = CaseStatement(case_stmt);
+      if (!c) {
+        return false;
+      }
+      behaviors.Add(c->Behaviors());
+    }
+
+    if (behaviors.Contains(sem::Behavior::kBreak)) {
+      behaviors.Add(sem::Behavior::kNext);
+    }
+    behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kFallthrough);
+
+    return ValidateSwitch(stmt);
+  });
+}
+
+sem::Statement* Resolver::VariableDeclStatement(
+    const ast::VariableDeclStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    Mark(stmt->variable);
+
+    auto* var = Variable(stmt->variable, VariableKind::kLocal);
+    if (!var) {
+      return false;
+    }
+
+    for (auto* attr : stmt->variable->attributes) {
+      Mark(attr);
+      if (!attr->Is<ast::InternalAttribute>()) {
+        AddError("attributes are not valid on local variables", attr->source);
+        return false;
+      }
+    }
+
+    if (current_block_) {  // Not all statements are inside a block
+      current_block_->AddDecl(stmt->variable);
+    }
+
+    if (auto* ctor = var->Constructor()) {
+      sem->Behaviors() = ctor->Behaviors();
+    }
+
+    return ValidateVariable(var);
+  });
+}
+
+sem::Statement* Resolver::AssignmentStatement(
+    const ast::AssignmentStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    auto* lhs = Expression(stmt->lhs);
+    if (!lhs) {
+      return false;
+    }
+
+    auto* rhs = Expression(stmt->rhs);
+    if (!rhs) {
+      return false;
+    }
+
+    auto& behaviors = sem->Behaviors();
+    behaviors = rhs->Behaviors();
+    if (!stmt->lhs->Is<ast::PhonyExpression>()) {
+      behaviors.Add(lhs->Behaviors());
+    }
+
+    return ValidateAssignment(stmt);
+  });
+}
+
+sem::Statement* Resolver::BreakStatement(const ast::BreakStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    sem->Behaviors() = sem::Behavior::kBreak;
+
+    return ValidateBreakStatement(sem);
+  });
+}
+
+sem::Statement* Resolver::CallStatement(const ast::CallStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    if (auto* expr = Expression(stmt->expr)) {
+      sem->Behaviors() = expr->Behaviors();
+      return true;
+    }
+    return false;
+  });
+}
+
+sem::Statement* Resolver::ContinueStatement(
+    const ast::ContinueStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    sem->Behaviors() = sem::Behavior::kContinue;
+
+    // Set if we've hit the first continue statement in our parent loop
+    if (auto* block = sem->FindFirstParent<sem::LoopBlockStatement>()) {
+      if (!block->FirstContinue()) {
+        const_cast<sem::LoopBlockStatement*>(block)->SetFirstContinue(
+            stmt, block->Decls().size());
+      }
+    }
+
+    return ValidateContinueStatement(sem);
+  });
+}
+
+sem::Statement* Resolver::DiscardStatement(const ast::DiscardStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    sem->Behaviors() = sem::Behavior::kDiscard;
+    current_function_->SetHasDiscard();
+
+    return ValidateDiscardStatement(sem);
+  });
+}
+
+sem::Statement* Resolver::FallthroughStatement(
+    const ast::FallthroughStatement* stmt) {
+  auto* sem = builder_->create<sem::Statement>(
+      stmt, current_compound_statement_, current_function_);
+  return StatementScope(stmt, sem, [&] {
+    sem->Behaviors() = sem::Behavior::kFallthrough;
+
+    return ValidateFallthroughStatement(sem);
+  });
+}
+
+bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
+                                            sem::Type* ty,
+                                            const Source& usage) {
+  ty = const_cast<sem::Type*>(ty->UnwrapRef());
+
+  if (auto* str = ty->As<sem::Struct>()) {
+    if (str->StorageClassUsage().count(sc)) {
+      return true;  // Already applied
+    }
+
+    str->AddUsage(sc);
+
+    for (auto* member : str->Members()) {
+      if (!ApplyStorageClassUsageToType(sc, member->Type(), usage)) {
+        std::stringstream err;
+        err << "while analysing structure member " << TypeNameOf(str) << "."
+            << builder_->Symbols().NameFor(member->Declaration()->symbol);
+        AddNote(err.str(), member->Declaration()->source);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  if (auto* arr = ty->As<sem::Array>()) {
+    if (arr->IsRuntimeSized() && sc != ast::StorageClass::kStorage) {
+      AddError(
+          "runtime-sized arrays can only be used in the <storage> storage "
+          "class",
+          usage);
+      return false;
+    }
+
+    return ApplyStorageClassUsageToType(
+        sc, const_cast<sem::Type*>(arr->ElemType()), usage);
+  }
+
+  if (ast::IsHostShareable(sc) && !IsHostShareable(ty)) {
+    std::stringstream err;
+    err << "Type '" << TypeNameOf(ty) << "' cannot be used in storage class '"
+        << sc << "' as it is non-host-shareable";
+    AddError(err.str(), usage);
+    return false;
+  }
+
+  return true;
+}
+
+template <typename SEM, typename F>
+SEM* Resolver::StatementScope(const ast::Statement* ast,
+                              SEM* sem,
+                              F&& callback) {
+  builder_->Sem().Add(ast, sem);
+
+  auto* as_compound =
+      As<sem::CompoundStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
+  auto* as_block =
+      As<sem::BlockStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
+
+  TINT_SCOPED_ASSIGNMENT(current_statement_, sem);
+  TINT_SCOPED_ASSIGNMENT(
+      current_compound_statement_,
+      as_compound ? as_compound : current_compound_statement_);
+  TINT_SCOPED_ASSIGNMENT(current_block_, as_block ? as_block : current_block_);
+
+  if (!callback()) {
+    return nullptr;
+  }
+
+  return sem;
+}
+
+std::string Resolver::VectorPretty(uint32_t size,
+                                   const sem::Type* element_type) {
+  sem::Vector vec_type(element_type, size);
+  return vec_type.FriendlyName(builder_->Symbols());
+}
+
+bool Resolver::Mark(const ast::Node* node) {
+  if (node == nullptr) {
+    TINT_ICE(Resolver, diagnostics_) << "Resolver::Mark() called with nullptr";
+    return false;
+  }
+  if (marked_.emplace(node).second) {
+    return true;
+  }
+  TINT_ICE(Resolver, diagnostics_)
+      << "AST node '" << node->TypeInfo().name
+      << "' was encountered twice in the same AST of a Program\n"
+      << "At: " << node->source << "\n"
+      << "Pointer: " << node;
+  return false;
+}
+
+void Resolver::AddError(const std::string& msg, const Source& source) const {
+  diagnostics_.add_error(diag::System::Resolver, msg, source);
+}
+
+void Resolver::AddWarning(const std::string& msg, const Source& source) const {
+  diagnostics_.add_warning(diag::System::Resolver, msg, source);
+}
+
+void Resolver::AddNote(const std::string& msg, const Source& source) const {
+  diagnostics_.add_note(diag::System::Resolver, msg, source);
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section
+bool Resolver::IsPlain(const sem::Type* type) const {
+  return type->is_scalar() ||
+         type->IsAnyOf<sem::Atomic, sem::Vector, sem::Matrix, sem::Array,
+                       sem::Struct>();
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types
+bool Resolver::IsFixedFootprint(const sem::Type* type) const {
+  return Switch(
+      type,                                      //
+      [&](const sem::Vector*) { return true; },  //
+      [&](const sem::Matrix*) { return true; },  //
+      [&](const sem::Atomic*) { return true; },
+      [&](const sem::Array* arr) {
+        return !arr->IsRuntimeSized() && IsFixedFootprint(arr->ElemType());
+      },
+      [&](const sem::Struct* str) {
+        for (auto* member : str->Members()) {
+          if (!IsFixedFootprint(member->Type())) {
+            return false;
+          }
+        }
+        return true;
+      },
+      [&](Default) { return type->is_scalar(); });
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl.html#storable-types
+bool Resolver::IsStorable(const sem::Type* type) const {
+  return IsPlain(type) || type->IsAnyOf<sem::Texture, sem::Sampler>();
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable-types
+bool Resolver::IsHostShareable(const sem::Type* type) const {
+  if (type->IsAnyOf<sem::I32, sem::U32, sem::F32>()) {
+    return true;
+  }
+  return Switch(
+      type,  //
+      [&](const sem::Vector* vec) { return IsHostShareable(vec->type()); },
+      [&](const sem::Matrix* mat) { return IsHostShareable(mat->type()); },
+      [&](const sem::Array* arr) { return IsHostShareable(arr->ElemType()); },
+      [&](const sem::Struct* str) {
+        for (auto* member : str->Members()) {
+          if (!IsHostShareable(member->Type())) {
+            return false;
+          }
+        }
+        return true;
+      },
+      [&](const sem::Atomic* atomic) {
+        return IsHostShareable(atomic->Type());
+      });
+}
+
+bool Resolver::IsBuiltin(Symbol symbol) const {
+  std::string name = builder_->Symbols().NameFor(symbol);
+  return sem::ParseBuiltinType(name) != sem::BuiltinType::kNone;
+}
+
+bool Resolver::IsCallStatement(const ast::Expression* expr) const {
+  return current_statement_ &&
+         Is<ast::CallStatement>(current_statement_->Declaration(),
+                                [&](auto* stmt) { return stmt->expr == expr; });
+}
+
+const ast::Statement* Resolver::ClosestContinuing(bool stop_at_loop) const {
+  for (const auto* s = current_statement_; s != nullptr; s = s->Parent()) {
+    if (stop_at_loop && s->Is<sem::LoopStatement>()) {
+      break;
+    }
+    if (s->Is<sem::LoopContinuingBlockStatement>()) {
+      return s->Declaration();
+    }
+    if (auto* f = As<sem::ForLoopStatement>(s->Parent())) {
+      if (f->Declaration()->continuing == s->Declaration()) {
+        return s->Declaration();
+      }
+      if (stop_at_loop) {
+        break;
+      }
+    }
+  }
+  return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Resolver::TypeConversionSig
+////////////////////////////////////////////////////////////////////////////////
+bool Resolver::TypeConversionSig::operator==(
+    const TypeConversionSig& rhs) const {
+  return target == rhs.target && source == rhs.source;
+}
+std::size_t Resolver::TypeConversionSig::Hasher::operator()(
+    const TypeConversionSig& sig) const {
+  return utils::Hash(sig.target, sig.source);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Resolver::TypeConstructorSig
+////////////////////////////////////////////////////////////////////////////////
+Resolver::TypeConstructorSig::TypeConstructorSig(
+    const sem::Type* ty,
+    const std::vector<const sem::Type*> params)
+    : type(ty), parameters(params) {}
+Resolver::TypeConstructorSig::TypeConstructorSig(const TypeConstructorSig&) =
+    default;
+Resolver::TypeConstructorSig::~TypeConstructorSig() = default;
+
+bool Resolver::TypeConstructorSig::operator==(
+    const TypeConstructorSig& rhs) const {
+  return type == rhs.type && parameters == rhs.parameters;
+}
+std::size_t Resolver::TypeConstructorSig::Hasher::operator()(
+    const TypeConstructorSig& sig) const {
+  return utils::Hash(sig.type, sig.parameters);
+}
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
new file mode 100644
index 0000000..fe7e865
--- /dev/null
+++ b/src/tint/resolver/resolver.h
@@ -0,0 +1,545 @@
+// Copyright 2020 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_RESOLVER_RESOLVER_H_
+#define SRC_TINT_RESOLVER_RESOLVER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/builtin_table.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/resolver/dependency_graph.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/constant.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/unique_vector.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class IndexAccessorExpression;
+class BinaryExpression;
+class BitcastExpression;
+class CallExpression;
+class CallStatement;
+class CaseStatement;
+class ForLoopStatement;
+class Function;
+class IdentifierExpression;
+class LoopStatement;
+class MemberAccessorExpression;
+class ReturnStatement;
+class SwitchStatement;
+class UnaryOpExpression;
+class Variable;
+}  // namespace ast
+namespace sem {
+class Array;
+class Atomic;
+class BlockStatement;
+class Builtin;
+class CaseStatement;
+class ElseStatement;
+class ForLoopStatement;
+class IfStatement;
+class LoopStatement;
+class Statement;
+class SwitchStatement;
+class TypeConstructor;
+}  // namespace sem
+
+namespace resolver {
+
+/// Resolves types for all items in the given tint program
+class Resolver {
+ public:
+  /// Constructor
+  /// @param builder the program builder
+  explicit Resolver(ProgramBuilder* builder);
+
+  /// Destructor
+  ~Resolver();
+
+  /// @returns error messages from the resolver
+  std::string error() const { return diagnostics_.str(); }
+
+  /// @returns true if the resolver was successful
+  bool Resolve();
+
+  /// @param type the given type
+  /// @returns true if the given type is a plain type
+  bool IsPlain(const sem::Type* type) const;
+
+  /// @param type the given type
+  /// @returns true if the given type is a fixed-footprint type
+  bool IsFixedFootprint(const sem::Type* type) const;
+
+  /// @param type the given type
+  /// @returns true if the given type is storable
+  bool IsStorable(const sem::Type* type) const;
+
+  /// @param type the given type
+  /// @returns true if the given type is host-shareable
+  bool IsHostShareable(const sem::Type* type) const;
+
+ private:
+  /// Describes the context in which a variable is declared
+  enum class VariableKind { kParameter, kLocal, kGlobal };
+
+  std::set<std::pair<const sem::Type*, ast::StorageClass>>
+      valid_type_storage_layouts_;
+
+  /// Structure holding semantic information about a block (i.e. scope), such as
+  /// parent block and variables declared in the block.
+  /// Used to validate variable scoping rules.
+  struct BlockInfo {
+    enum class Type { kGeneric, kLoop, kLoopContinuing, kSwitchCase };
+
+    BlockInfo(const ast::BlockStatement* block, Type type, BlockInfo* parent);
+    ~BlockInfo();
+
+    template <typename Pred>
+    BlockInfo* FindFirstParent(Pred&& pred) {
+      BlockInfo* curr = this;
+      while (curr && !pred(curr)) {
+        curr = curr->parent;
+      }
+      return curr;
+    }
+
+    BlockInfo* FindFirstParent(BlockInfo::Type ty) {
+      return FindFirstParent(
+          [ty](auto* block_info) { return block_info->type == ty; });
+    }
+
+    ast::BlockStatement const* const block;
+    const Type type;
+    BlockInfo* const parent;
+    std::vector<const ast::Variable*> decls;
+
+    // first_continue is set to the index of the first variable in decls
+    // declared after the first continue statement in a loop block, if any.
+    constexpr static size_t kNoContinue = size_t(~0);
+    size_t first_continue = kNoContinue;
+  };
+
+  // Structure holding information for a TypeDecl
+  struct TypeDeclInfo {
+    ast::TypeDecl const* const ast;
+    sem::Type* const sem;
+  };
+
+  /// Resolves the program, without creating final the semantic nodes.
+  /// @returns true on success, false on error
+  bool ResolveInternal();
+
+  bool ValidatePipelineStages();
+
+  /// Creates the nodes and adds them to the sem::Info mappings of the
+  /// ProgramBuilder.
+  void CreateSemanticNodes() const;
+
+  /// Retrieves information for the requested import.
+  /// @param src the source of the import
+  /// @param path the import path
+  /// @param name the method name to get information on
+  /// @param params the parameters to the method call
+  /// @param id out parameter for the external call ID. Must not be a nullptr.
+  /// @returns the return type of `name` in `path` or nullptr on error.
+  sem::Type* GetImportData(const Source& src,
+                           const std::string& path,
+                           const std::string& name,
+                           const ast::ExpressionList& params,
+                           uint32_t* id);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // AST and Type traversal methods
+  //////////////////////////////////////////////////////////////////////////////
+
+  // Expression resolving methods
+  // Returns the semantic node pointer on success, nullptr on failure.
+  sem::Expression* IndexAccessor(const ast::IndexAccessorExpression*);
+  sem::Expression* Binary(const ast::BinaryExpression*);
+  sem::Expression* Bitcast(const ast::BitcastExpression*);
+  sem::Call* Call(const ast::CallExpression*);
+  sem::Expression* Expression(const ast::Expression*);
+  sem::Function* Function(const ast::Function*);
+  sem::Call* FunctionCall(const ast::CallExpression*,
+                          sem::Function* target,
+                          const std::vector<const sem::Expression*> args,
+                          sem::Behaviors arg_behaviors);
+  sem::Expression* Identifier(const ast::IdentifierExpression*);
+  sem::Call* BuiltinCall(const ast::CallExpression*,
+                         sem::BuiltinType,
+                         const std::vector<const sem::Expression*> args,
+                         const std::vector<const sem::Type*> arg_tys);
+  sem::Expression* Literal(const ast::LiteralExpression*);
+  sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
+  sem::Call* TypeConversion(const ast::CallExpression* expr,
+                            const sem::Type* ty,
+                            const sem::Expression* arg,
+                            const sem::Type* arg_ty);
+  sem::Call* TypeConstructor(const ast::CallExpression* expr,
+                             const sem::Type* ty,
+                             const std::vector<const sem::Expression*> args,
+                             const std::vector<const sem::Type*> arg_tys);
+  sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
+
+  // Statement resolving methods
+  // Each return true on success, false on failure.
+  sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
+  sem::BlockStatement* BlockStatement(const ast::BlockStatement*);
+  sem::Statement* BreakStatement(const ast::BreakStatement*);
+  sem::Statement* CallStatement(const ast::CallStatement*);
+  sem::CaseStatement* CaseStatement(const ast::CaseStatement*);
+  sem::Statement* ContinueStatement(const ast::ContinueStatement*);
+  sem::Statement* DiscardStatement(const ast::DiscardStatement*);
+  sem::ElseStatement* ElseStatement(const ast::ElseStatement*);
+  sem::Statement* FallthroughStatement(const ast::FallthroughStatement*);
+  sem::ForLoopStatement* ForLoopStatement(const ast::ForLoopStatement*);
+  sem::GlobalVariable* GlobalVariable(const ast::Variable*);
+  sem::Statement* Parameter(const ast::Variable*);
+  sem::IfStatement* IfStatement(const ast::IfStatement*);
+  sem::LoopStatement* LoopStatement(const ast::LoopStatement*);
+  sem::Statement* ReturnStatement(const ast::ReturnStatement*);
+  sem::Statement* Statement(const ast::Statement*);
+  sem::SwitchStatement* SwitchStatement(const ast::SwitchStatement* s);
+  sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
+  bool Statements(const ast::StatementList&);
+
+  // AST and Type validation methods
+  // Each return true on success, false on failure.
+  bool ValidateAlias(const ast::Alias*);
+  bool ValidateArray(const sem::Array* arr, const Source& source);
+  bool ValidateArrayStrideAttribute(const ast::StrideAttribute* attr,
+                                    uint32_t el_size,
+                                    uint32_t el_align,
+                                    const Source& source);
+  bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
+  bool ValidateAtomicVariable(const sem::Variable* var);
+  bool ValidateAssignment(const ast::AssignmentStatement* a);
+  bool ValidateBitcast(const ast::BitcastExpression* cast, const sem::Type* to);
+  bool ValidateBreakStatement(const sem::Statement* stmt);
+  bool ValidateBuiltinAttribute(const ast::BuiltinAttribute* attr,
+                                const sem::Type* storage_type,
+                                const bool is_input);
+  bool ValidateContinueStatement(const sem::Statement* stmt);
+  bool ValidateDiscardStatement(const sem::Statement* stmt);
+  bool ValidateElseStatement(const sem::ElseStatement* stmt);
+  bool ValidateEntryPoint(const sem::Function* func);
+  bool ValidateForLoopStatement(const sem::ForLoopStatement* stmt);
+  bool ValidateFallthroughStatement(const sem::Statement* stmt);
+  bool ValidateFunction(const sem::Function* func);
+  bool ValidateFunctionCall(const sem::Call* call);
+  bool ValidateGlobalVariable(const sem::Variable* var);
+  bool ValidateIfStatement(const sem::IfStatement* stmt);
+  bool ValidateInterpolateAttribute(const ast::InterpolateAttribute* attr,
+                                    const sem::Type* storage_type);
+  bool ValidateBuiltinCall(const sem::Call* call);
+  bool ValidateLocationAttribute(const ast::LocationAttribute* location,
+                                 const sem::Type* type,
+                                 std::unordered_set<uint32_t>& locations,
+                                 const Source& source,
+                                 const bool is_input = false);
+  bool ValidateLoopStatement(const sem::LoopStatement* stmt);
+  bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
+  bool ValidateFunctionParameter(const ast::Function* func,
+                                 const sem::Variable* var);
+  bool ValidateParameter(const ast::Function* func, const sem::Variable* var);
+  bool ValidateReturn(const ast::ReturnStatement* ret);
+  bool ValidateStatements(const ast::StatementList& stmts);
+  bool ValidateStorageTexture(const ast::StorageTexture* t);
+  bool ValidateStructure(const sem::Struct* str);
+  bool ValidateStructureConstructorOrCast(const ast::CallExpression* ctor,
+                                          const sem::Struct* struct_type);
+  bool ValidateSwitch(const ast::SwitchStatement* s);
+  bool ValidateVariable(const sem::Variable* var);
+  bool ValidateVariableConstructorOrCast(const ast::Variable* var,
+                                         ast::StorageClass storage_class,
+                                         const sem::Type* storage_type,
+                                         const sem::Type* rhs_type);
+  bool ValidateVector(const sem::Vector* ty, const Source& source);
+  bool ValidateVectorConstructorOrCast(const ast::CallExpression* ctor,
+                                       const sem::Vector* vec_type);
+  bool ValidateMatrixConstructorOrCast(const ast::CallExpression* ctor,
+                                       const sem::Matrix* matrix_type);
+  bool ValidateScalarConstructorOrCast(const ast::CallExpression* ctor,
+                                       const sem::Type* type);
+  bool ValidateArrayConstructorOrCast(const ast::CallExpression* ctor,
+                                      const sem::Array* arr_type);
+  bool ValidateTextureBuiltinFunction(const sem::Call* call);
+  bool ValidateNoDuplicateAttributes(const ast::AttributeList& attributes);
+  bool ValidateStorageClassLayout(const sem::Type* type,
+                                  ast::StorageClass sc,
+                                  Source source);
+  bool ValidateStorageClassLayout(const sem::Variable* var);
+
+  /// @returns true if the attribute list contains a
+  /// ast::DisableValidationAttribute with the validation mode equal to
+  /// `validation`
+  bool IsValidationDisabled(const ast::AttributeList& attributes,
+                            ast::DisabledValidation validation) const;
+
+  /// @returns true if the attribute list does not contains a
+  /// ast::DisableValidationAttribute with the validation mode equal to
+  /// `validation`
+  bool IsValidationEnabled(const ast::AttributeList& attributes,
+                           ast::DisabledValidation validation) const;
+
+  /// Resolves the WorkgroupSize for the given function, assigning it to
+  /// current_function_
+  bool WorkgroupSize(const ast::Function*);
+
+  /// @returns the sem::Type for the ast::Type `ty`, building it if it
+  /// hasn't been constructed already. If an error is raised, nullptr is
+  /// returned.
+  /// @param ty the ast::Type
+  sem::Type* Type(const ast::Type* ty);
+
+  /// @param named_type the named type to resolve
+  /// @returns the resolved semantic type
+  sem::Type* TypeDecl(const ast::TypeDecl* named_type);
+
+  /// Builds and returns the semantic information for the array `arr`.
+  /// This method does not mark the ast::Array node, nor attach the generated
+  /// semantic information to the AST node.
+  /// @returns the semantic Array information, or nullptr if an error is
+  /// raised.
+  /// @param arr the Array to get semantic information for
+  sem::Array* Array(const ast::Array* arr);
+
+  /// Builds and returns the semantic information for the alias `alias`.
+  /// This method does not mark the ast::Alias node, nor attach the generated
+  /// semantic information to the AST node.
+  /// @returns the aliased type, or nullptr if an error is raised.
+  sem::Type* Alias(const ast::Alias* alias);
+
+  /// Builds and returns the semantic information for the structure `str`.
+  /// This method does not mark the ast::Struct node, nor attach the generated
+  /// semantic information to the AST node.
+  /// @returns the semantic Struct information, or nullptr if an error is
+  /// raised.
+  sem::Struct* Structure(const ast::Struct* str);
+
+  /// @returns the semantic info for the variable `var`. If an error is
+  /// raised, nullptr is returned.
+  /// @note this method does not resolve the attributes as these are
+  /// context-dependent (global, local, parameter)
+  /// @param var the variable to create or return the `VariableInfo` for
+  /// @param kind what kind of variable we are declaring
+  /// @param index the index of the parameter, if this variable is a parameter
+  sem::Variable* Variable(const ast::Variable* var,
+                          VariableKind kind,
+                          uint32_t index = 0);
+
+  /// Records the storage class usage for the given type, and any transient
+  /// dependencies of the type. Validates that the type can be used for the
+  /// given storage class, erroring if it cannot.
+  /// @param sc the storage class to apply to the type and transitent types
+  /// @param ty the type to apply the storage class on
+  /// @param usage the Source of the root variable declaration that uses the
+  /// given type and storage class. Used for generating sensible error
+  /// messages.
+  /// @returns true on success, false on error
+  bool ApplyStorageClassUsageToType(ast::StorageClass sc,
+                                    sem::Type* ty,
+                                    const Source& usage);
+
+  /// @param storage_class the storage class
+  /// @returns the default access control for the given storage class
+  ast::Access DefaultAccessForStorageClass(ast::StorageClass storage_class);
+
+  /// Allocate constant IDs for pipeline-overridable constants.
+  void AllocateOverridableConstantIds();
+
+  /// Set the shadowing information on variable declarations.
+  /// @note this method must only be called after all semantic nodes are built.
+  void SetShadows();
+
+  /// @returns the resolved type of the ast::Expression `expr`
+  /// @param expr the expression
+  sem::Type* TypeOf(const ast::Expression* expr);
+
+  /// @returns the type name of the given semantic type, unwrapping
+  /// references.
+  std::string TypeNameOf(const sem::Type* ty);
+
+  /// @returns the type name of the given semantic type, without unwrapping
+  /// references.
+  std::string RawTypeNameOf(const sem::Type* ty);
+
+  /// @returns the semantic type of the AST literal `lit`
+  /// @param lit the literal
+  sem::Type* TypeOf(const ast::LiteralExpression* lit);
+
+  /// StatementScope() does the following:
+  /// * Creates the AST -> SEM mapping.
+  /// * Assigns `sem` to #current_statement_
+  /// * Assigns `sem` to #current_compound_statement_ if `sem` derives from
+  ///   sem::CompoundStatement.
+  /// * Assigns `sem` to #current_block_ if `sem` derives from
+  ///   sem::BlockStatement.
+  /// * Then calls `callback`.
+  /// * Before returning #current_statement_, #current_compound_statement_, and
+  ///   #current_block_ are restored to their original values.
+  /// @returns `sem` if `callback` returns true, otherwise `nullptr`.
+  template <typename SEM, typename F>
+  SEM* StatementScope(const ast::Statement* ast, SEM* sem, F&& callback);
+
+  /// Returns a human-readable string representation of the vector type name
+  /// with the given parameters.
+  /// @param size the vector dimension
+  /// @param element_type scalar vector sub-element type
+  /// @return pretty string representation
+  std::string VectorPretty(uint32_t size, const sem::Type* element_type);
+
+  /// Mark records that the given AST node has been visited, and asserts that
+  /// the given node has not already been seen. Diamonds in the AST are
+  /// illegal.
+  /// @param node the AST node.
+  /// @returns true on success, false on error
+  bool Mark(const ast::Node* node);
+
+  /// Adds the given error message to the diagnostics
+  void AddError(const std::string& msg, const Source& source) const;
+
+  /// Adds the given warning message to the diagnostics
+  void AddWarning(const std::string& msg, const Source& source) const;
+
+  /// Adds the given note message to the diagnostics
+  void AddNote(const std::string& msg, const Source& source) const;
+
+  //////////////////////////////////////////////////////////////////////////////
+  /// Constant value evaluation methods
+  //////////////////////////////////////////////////////////////////////////////
+  /// Cast `Value` to `target_type`
+  /// @return the casted value
+  sem::Constant ConstantCast(const sem::Constant& value,
+                             const sem::Type* target_elem_type);
+
+  sem::Constant EvaluateConstantValue(const ast::Expression* expr,
+                                      const sem::Type* type);
+  sem::Constant EvaluateConstantValue(const ast::LiteralExpression* literal,
+                                      const sem::Type* type);
+  sem::Constant EvaluateConstantValue(const ast::CallExpression* call,
+                                      const sem::Type* type);
+
+  /// Sem is a helper for obtaining the semantic node for the given AST node.
+  template <typename SEM = sem::Info::InferFromAST,
+            typename AST_OR_TYPE = CastableBase>
+  auto* Sem(const AST_OR_TYPE* ast) {
+    using T = sem::Info::GetResultType<SEM, AST_OR_TYPE>;
+    auto* sem = builder_->Sem().Get(ast);
+    if (!sem) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "AST node '" << ast->TypeInfo().name << "' had no semantic info\n"
+          << "At: " << ast->source << "\n"
+          << "Pointer: " << ast;
+    }
+    return const_cast<T*>(As<T>(sem));
+  }
+
+  /// @returns true if the symbol is the name of a builtin function.
+  bool IsBuiltin(Symbol) const;
+
+  /// @returns true if `expr` is the current CallStatement's CallExpression
+  bool IsCallStatement(const ast::Expression* expr) const;
+
+  /// Searches the current statement and up through parents of the current
+  /// statement looking for a loop or for-loop continuing statement.
+  /// @returns the closest continuing statement to the current statement that
+  /// (transitively) owns the current statement.
+  /// @param stop_at_loop if true then the function will return nullptr if a
+  /// loop or for-loop was found before the continuing.
+  const ast::Statement* ClosestContinuing(bool stop_at_loop) const;
+
+  /// @returns the resolved symbol (function, type or variable) for the given
+  /// ast::Identifier or ast::TypeName cast to the given semantic type.
+  template <typename SEM = sem::Node>
+  SEM* ResolvedSymbol(const ast::Node* node) {
+    auto* resolved = utils::Lookup(dependencies_.resolved_symbols, node);
+    return resolved ? const_cast<SEM*>(builder_->Sem().Get<SEM>(resolved))
+                    : nullptr;
+  }
+
+  struct TypeConversionSig {
+    const sem::Type* target;
+    const sem::Type* source;
+
+    bool operator==(const TypeConversionSig&) const;
+
+    /// Hasher provides a hash function for the TypeConversionSig
+    struct Hasher {
+      /// @param sig the TypeConversionSig to create a hash for
+      /// @return the hash value
+      std::size_t operator()(const TypeConversionSig& sig) const;
+    };
+  };
+
+  struct TypeConstructorSig {
+    const sem::Type* type;
+    const std::vector<const sem::Type*> parameters;
+
+    TypeConstructorSig(const sem::Type* ty,
+                       const std::vector<const sem::Type*> params);
+    TypeConstructorSig(const TypeConstructorSig&);
+    ~TypeConstructorSig();
+    bool operator==(const TypeConstructorSig&) const;
+
+    /// Hasher provides a hash function for the TypeConstructorSig
+    struct Hasher {
+      /// @param sig the TypeConstructorSig to create a hash for
+      /// @return the hash value
+      std::size_t operator()(const TypeConstructorSig& sig) const;
+    };
+  };
+
+  ProgramBuilder* const builder_;
+  diag::List& diagnostics_;
+  std::unique_ptr<BuiltinTable> const builtin_table_;
+  DependencyGraph dependencies_;
+  std::vector<sem::Function*> entry_points_;
+  std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
+  std::unordered_set<const ast::Node*> marked_;
+  std::unordered_map<uint32_t, const sem::Variable*> constant_ids_;
+  std::unordered_map<TypeConversionSig,
+                     sem::CallTarget*,
+                     TypeConversionSig::Hasher>
+      type_conversions_;
+  std::unordered_map<TypeConstructorSig,
+                     sem::CallTarget*,
+                     TypeConstructorSig::Hasher>
+      type_ctors_;
+
+  sem::Function* current_function_ = nullptr;
+  sem::Statement* current_statement_ = nullptr;
+  sem::CompoundStatement* current_compound_statement_ = nullptr;
+  sem::BlockStatement* current_block_ = nullptr;
+};
+
+}  // namespace resolver
+}  // namespace tint
+
+#endif  // SRC_TINT_RESOLVER_RESOLVER_H_
diff --git a/src/tint/resolver/resolver_behavior_test.cc b/src/tint/resolver/resolver_behavior_test.cc
new file mode 100644
index 0000000..7cc6cb1
--- /dev/null
+++ b/src/tint/resolver/resolver_behavior_test.cc
@@ -0,0 +1,659 @@
+// 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/resolver/resolver.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/if_statement.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+class ResolverBehaviorTest : public ResolverTest {
+ protected:
+  void SetUp() override {
+    // Create a function called 'DiscardOrNext' which returns an i32, and has
+    // the behavior of {Discard, Return}, which when called, will have the
+    // behavior {Discard, Next}.
+    Func("DiscardOrNext", {}, ty.i32(),
+         {
+             If(true, Block(Discard())),
+             Return(1),
+         });
+  }
+};
+
+TEST_F(ResolverBehaviorTest, ExprBinaryOp_LHS) {
+  auto* stmt = Decl(Var("lhs", ty.i32(), Add(Call("DiscardOrNext"), 1)));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, ExprBinaryOp_RHS) {
+  auto* stmt = Decl(Var("lhs", ty.i32(), Add(1, Call("DiscardOrNext"))));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, ExprBitcastOp) {
+  auto* stmt = Decl(Var("lhs", ty.u32(), Bitcast<u32>(Call("DiscardOrNext"))));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, ExprIndex_Arr) {
+  Func("ArrayDiscardOrNext", {}, ty.array<i32, 4>(),
+       {
+           If(true, Block(Discard())),
+           Return(Construct(ty.array<i32, 4>())),
+       });
+
+  auto* stmt =
+      Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1)));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, ExprIndex_Idx) {
+  auto* stmt =
+      Decl(Var("lhs", ty.i32(), IndexAccessor("arr", Call("DiscardOrNext"))));
+  WrapInFunction(Decl(Var("arr", ty.array<i32, 4>())),  //
+                 stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, ExprUnaryOp) {
+  auto* stmt = Decl(Var("lhs", ty.i32(),
+                        create<ast::UnaryOpExpression>(
+                            ast::UnaryOp::kComplement, Call("DiscardOrNext"))));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtAssign) {
+  auto* stmt = Assign("lhs", "rhs");
+  WrapInFunction(Decl(Var("lhs", ty.i32())),  //
+                 Decl(Var("rhs", ty.i32())),  //
+                 stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtAssign_LHSDiscardOrNext) {
+  auto* stmt = Assign(IndexAccessor("lhs", Call("DiscardOrNext")), 1);
+  WrapInFunction(Decl(Var("lhs", ty.array<i32, 4>())),  //
+                 stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtAssign_RHSDiscardOrNext) {
+  auto* stmt = Assign("lhs", Call("DiscardOrNext"));
+  WrapInFunction(Decl(Var("lhs", ty.i32())),  //
+                 stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtBlockEmpty) {
+  auto* stmt = Block();
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtBlockSingleStmt) {
+  auto* stmt = Block(Discard());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtCallReturn) {
+  Func("f", {}, ty.void_(), {Return()});
+  auto* stmt = CallStmt(Call("f"));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtCallFuncDiscard) {
+  Func("f", {}, ty.void_(), {Discard()});
+  auto* stmt = CallStmt(Call("f"));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtCallFuncMayDiscard) {
+  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
+                   nullptr, Block(Break()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtBreak) {
+  auto* stmt = Break();
+  WrapInFunction(Loop(Block(stmt)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kBreak);
+}
+
+TEST_F(ResolverBehaviorTest, StmtContinue) {
+  auto* stmt = Continue();
+  WrapInFunction(Loop(Block(If(true, Block(Break())),  //
+                            stmt)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kContinue);
+}
+
+TEST_F(ResolverBehaviorTest, StmtDiscard) {
+  auto* stmt = Discard();
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_NoExit) {
+  auto* stmt = For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block());
+  WrapInFunction(stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopBreak) {
+  auto* stmt = For(nullptr, nullptr, nullptr, Block(Break()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopContinue_NoExit) {
+  auto* stmt =
+      For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block(Continue()));
+  WrapInFunction(stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopDiscard) {
+  auto* stmt = For(nullptr, nullptr, nullptr, Block(Discard()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopReturn) {
+  auto* stmt = For(nullptr, nullptr, nullptr, Block(Return()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopBreak_InitCallFuncMayDiscard) {
+  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
+                   nullptr, Block(Break()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_InitCallFuncMayDiscard) {
+  auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
+                   nullptr, Block());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondTrue) {
+  auto* stmt = For(nullptr, true, nullptr, Block());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondCallFuncMayDiscard) {
+  auto* stmt = For(nullptr, Equal(Call("DiscardOrNext"), 1), nullptr, Block());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock) {
+  auto* stmt = If(true, Block());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard) {
+  auto* stmt = If(true, Block(Discard()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseDiscard) {
+  auto* stmt = If(true, Block(), Else(Block(Discard())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard_ElseDiscard) {
+  auto* stmt = If(true, Block(Discard()), Else(Block(Discard())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfCallFuncMayDiscard_ThenEmptyBlock) {
+  auto* stmt = If(Equal(Call("DiscardOrNext"), 1), Block());
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseCallFuncMayDiscard) {
+  auto* stmt = If(true, Block(),  //
+                  Else(Equal(Call("DiscardOrNext"), 1), Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtLetDecl) {
+  auto* stmt = Decl(Const("v", ty.i32(), Expr(1)));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtLetDecl_RHSDiscardOrNext) {
+  auto* stmt = Decl(Const("lhs", ty.i32(), Call("DiscardOrNext")));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopEmpty_NoExit) {
+  auto* stmt = Loop(Source{{12, 34}}, Block());
+  WrapInFunction(stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopBreak) {
+  auto* stmt = Loop(Block(Break()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopContinue_NoExit) {
+  auto* stmt = Loop(Source{{12, 34}}, Block(Continue()));
+  WrapInFunction(stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopDiscard) {
+  auto* stmt = Loop(Block(Discard()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopReturn) {
+  auto* stmt = Loop(Block(Return()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContEmpty_NoExit) {
+  auto* stmt = Loop(Source{{12, 34}}, Block(), Block());
+  WrapInFunction(stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
+}
+
+TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContIfTrueBreak) {
+  auto* stmt = Loop(Block(), Block(If(true, Block(Break()))));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtReturn) {
+  auto* stmt = Return();
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
+}
+
+TEST_F(ResolverBehaviorTest, StmtReturn_DiscardOrNext) {
+  auto* stmt = Return(Call("DiscardOrNext"));
+  Func("F", {}, ty.i32(), {stmt});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kReturn, sem::Behavior::kDiscard));
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondTrue_DefaultEmpty) {
+  auto* stmt = Switch(1, DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultEmpty) {
+  auto* stmt = Switch(1, DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultDiscard) {
+  auto* stmt = Switch(1, DefaultCase(Block(Discard())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultReturn) {
+  auto* stmt = Switch(1, DefaultCase(Block(Return())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultEmpty) {
+  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultDiscard) {
+  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Discard())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kDiscard));
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultReturn) {
+  auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Return())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kReturn));
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultEmpty) {
+  auto* stmt = Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest,
+       StmtSwitch_CondLiteral_Case0Discard_DefaultDiscard) {
+  auto* stmt =
+      Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Discard())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
+}
+
+TEST_F(ResolverBehaviorTest,
+       StmtSwitch_CondLiteral_Case0Discard_DefaultReturn) {
+  auto* stmt =
+      Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Return())));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kReturn));
+}
+
+TEST_F(ResolverBehaviorTest,
+       StmtSwitch_CondLiteral_Case0Discard_Case1Return_DefaultEmpty) {
+  auto* stmt = Switch(1,                                //
+                      Case(Expr(0), Block(Discard())),  //
+                      Case(Expr(1), Block(Return())),   //
+                      DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext,
+                           sem::Behavior::kReturn));
+}
+
+TEST_F(ResolverBehaviorTest, StmtSwitch_CondCallFuncMayDiscard_DefaultEmpty) {
+  auto* stmt = Switch(Call("DiscardOrNext"), DefaultCase(Block()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+TEST_F(ResolverBehaviorTest, StmtVarDecl) {
+  auto* stmt = Decl(Var("v", ty.i32()));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
+TEST_F(ResolverBehaviorTest, StmtVarDecl_RHSDiscardOrNext) {
+  auto* stmt = Decl(Var("lhs", ty.i32(), Call("DiscardOrNext")));
+  WrapInFunction(stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(stmt);
+  EXPECT_EQ(sem->Behaviors(),
+            sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver_constants.cc b/src/tint/resolver/resolver_constants.cc
new file mode 100644
index 0000000..a83ae73
--- /dev/null
+++ b/src/tint/resolver/resolver_constants.cc
@@ -0,0 +1,144 @@
+// 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/resolver/resolver.h"
+
+#include "src/tint/sem/constant.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/utils/map.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+}  // namespace
+
+sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr,
+                                              const sem::Type* type) {
+  if (auto* e = expr->As<ast::LiteralExpression>()) {
+    return EvaluateConstantValue(e, type);
+  }
+  if (auto* e = expr->As<ast::CallExpression>()) {
+    return EvaluateConstantValue(e, type);
+  }
+  return {};
+}
+
+sem::Constant Resolver::EvaluateConstantValue(
+    const ast::LiteralExpression* literal,
+    const sem::Type* type) {
+  if (auto* lit = literal->As<ast::SintLiteralExpression>()) {
+    return {type, {lit->ValueAsI32()}};
+  }
+  if (auto* lit = literal->As<ast::UintLiteralExpression>()) {
+    return {type, {lit->ValueAsU32()}};
+  }
+  if (auto* lit = literal->As<ast::FloatLiteralExpression>()) {
+    return {type, {lit->value}};
+  }
+  if (auto* lit = literal->As<ast::BoolLiteralExpression>()) {
+    return {type, {lit->value}};
+  }
+  TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
+  return {};
+}
+
+sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
+                                              const sem::Type* type) {
+  auto* vec = type->As<sem::Vector>();
+
+  // For now, only fold scalars and vectors
+  if (!type->is_scalar() && !vec) {
+    return {};
+  }
+
+  auto* elem_type = vec ? vec->type() : type;
+  int result_size = vec ? static_cast<int>(vec->Width()) : 1;
+
+  // For zero value init, return 0s
+  if (call->args.empty()) {
+    if (elem_type->Is<sem::I32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0));
+    }
+    if (elem_type->Is<sem::U32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0u));
+    }
+    if (elem_type->Is<sem::F32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f));
+    }
+    if (elem_type->Is<sem::Bool>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, false));
+    }
+  }
+
+  // Build value for type_ctor from each child value by casting to
+  // type_ctor's type.
+  sem::Constant::Scalars elems;
+  for (auto* expr : call->args) {
+    auto* arg = builder_->Sem().Get(expr);
+    if (!arg || !arg->ConstantValue()) {
+      return {};
+    }
+    auto cast = ConstantCast(arg->ConstantValue(), elem_type);
+    elems.insert(elems.end(), cast.Elements().begin(), cast.Elements().end());
+  }
+
+  // Splat single-value initializers
+  if (elems.size() == 1) {
+    for (int i = 0; i < result_size - 1; ++i) {
+      elems.emplace_back(elems[0]);
+    }
+  }
+
+  return sem::Constant(type, std::move(elems));
+}
+
+sem::Constant Resolver::ConstantCast(const sem::Constant& value,
+                                     const sem::Type* target_elem_type) {
+  if (value.ElementType() == target_elem_type) {
+    return value;
+  }
+
+  sem::Constant::Scalars elems;
+  for (size_t i = 0; i < value.Elements().size(); ++i) {
+    if (target_elem_type->Is<sem::I32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<i32>(s); }));
+    } else if (target_elem_type->Is<sem::U32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<u32>(s); }));
+    } else if (target_elem_type->Is<sem::F32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<f32>(s); }));
+    } else if (target_elem_type->Is<sem::Bool>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<bool>(s); }));
+    }
+  }
+
+  auto* target_type =
+      value.Type()->Is<sem::Vector>()
+          ? builder_->create<sem::Vector>(target_elem_type,
+                                          static_cast<uint32_t>(elems.size()))
+          : target_elem_type;
+
+  return sem::Constant(target_type, elems);
+}
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver_constants_test.cc b/src/tint/resolver/resolver_constants_test.cc
new file mode 100644
index 0000000..6d06bef
--- /dev/null
+++ b/src/tint/resolver/resolver_constants_test.cc
@@ -0,0 +1,433 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/resolver/resolver.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/expression.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using Scalar = sem::Constant::Scalar;
+
+using ResolverConstantsTest = ResolverTest;
+
+TEST_F(ResolverConstantsTest, Scalar_i32) {
+  auto* expr = Expr(99);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::I32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_u32) {
+  auto* expr = Expr(99u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::U32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_f32) {
+  auto* expr = Expr(9.9f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::F32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_bool) {
+  auto* expr = Expr(true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_i32) {
+  auto* expr = vec3<i32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 0);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 0);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 0);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_u32) {
+  auto* expr = vec3<u32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 0u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) {
+  auto* expr = vec3<f32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) {
+  auto* expr = vec3<bool>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, false);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_i32) {
+  auto* expr = vec3<i32>(99);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 99);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 99);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_u32) {
+  auto* expr = vec3<u32>(99u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 99u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 99u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_f32) {
+  auto* expr = vec3<f32>(9.9f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 9.9f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 9.9f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_bool) {
+  auto* expr = vec3<bool>(true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_i32) {
+  auto* expr = vec3<i32>(1, 2, 3);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_u32) {
+  auto* expr = vec3<u32>(1u, 2u, 3u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_f32) {
+  auto* expr = vec3<f32>(1.f, 2.f, 3.f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_bool) {
+  auto* expr = vec3<bool>(true, false, true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_i32) {
+  auto* expr = vec3<i32>(1, vec2<i32>(2, 3));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_u32) {
+  auto* expr = vec3<u32>(vec2<u32>(1u, 2u), 3u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_f32) {
+  auto* expr = vec3<f32>(1.f, vec2<f32>(2.f, 3.f));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
+  auto* expr = vec3<bool>(vec2<bool>(true, false), true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_32) {
+  auto* expr = vec3<i32>(vec3<f32>(1.1f, 2.2f, 3.3f));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
+  auto* expr = vec3<f32>(vec3<u32>(10u, 20u, 30u));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 10.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 20.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 30.f);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
new file mode 100644
index 0000000..ec0b26d
--- /dev/null
+++ b/src/tint/resolver/resolver_test.cc
@@ -0,0 +1,2189 @@
+// Copyright 2020 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/resolver/resolver.h"
+
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <int N, typename T>
+using vec = builder::vec<N, T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <int N, int M, typename T>
+using mat = builder::mat<N, M, T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat2x3 = builder::mat2x3<T>;
+template <typename T>
+using mat3x2 = builder::mat3x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <typename T, int ID = 0>
+using alias = builder::alias<T, ID>;
+template <typename T>
+using alias1 = builder::alias1<T>;
+template <typename T>
+using alias2 = builder::alias2<T>;
+template <typename T>
+using alias3 = builder::alias3<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+using Op = ast::BinaryOp;
+
+TEST_F(ResolverTest, Stmt_Assign) {
+  auto* v = Var("v", ty.f32());
+  auto* lhs = Expr("v");
+  auto* rhs = Expr(2.3f);
+
+  auto* assign = Assign(lhs, rhs);
+  WrapInFunction(v, assign);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+
+  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(lhs), assign);
+  EXPECT_EQ(StmtOf(rhs), assign);
+}
+
+TEST_F(ResolverTest, Stmt_Case) {
+  auto* v = Var("v", ty.f32());
+  auto* lhs = Expr("v");
+  auto* rhs = Expr(2.3f);
+
+  auto* assign = Assign(lhs, rhs);
+  auto* block = Block(assign);
+  ast::CaseSelectorList lit;
+  lit.push_back(create<ast::SintLiteralExpression>(3));
+  auto* cse = create<ast::CaseStatement>(lit, block);
+  auto* cond_var = Var("c", ty.i32());
+  auto* sw = Switch(cond_var, cse, DefaultCase());
+  WrapInFunction(v, cond_var, sw);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(lhs), assign);
+  EXPECT_EQ(StmtOf(rhs), assign);
+  EXPECT_EQ(BlockOf(assign), block);
+}
+
+TEST_F(ResolverTest, Stmt_Block) {
+  auto* v = Var("v", ty.f32());
+  auto* lhs = Expr("v");
+  auto* rhs = Expr(2.3f);
+
+  auto* assign = Assign(lhs, rhs);
+  auto* block = Block(assign);
+  WrapInFunction(v, block);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(lhs), assign);
+  EXPECT_EQ(StmtOf(rhs), assign);
+  EXPECT_EQ(BlockOf(lhs), block);
+  EXPECT_EQ(BlockOf(rhs), block);
+  EXPECT_EQ(BlockOf(assign), block);
+}
+
+TEST_F(ResolverTest, Stmt_If) {
+  auto* v = Var("v", ty.f32());
+  auto* else_lhs = Expr("v");
+  auto* else_rhs = Expr(2.3f);
+
+  auto* else_body = Block(Assign(else_lhs, else_rhs));
+
+  auto* else_cond = Expr(true);
+  auto* else_stmt = create<ast::ElseStatement>(else_cond, else_body);
+
+  auto* lhs = Expr("v");
+  auto* rhs = Expr(2.3f);
+
+  auto* assign = Assign(lhs, rhs);
+  auto* body = Block(assign);
+  auto* cond = Expr(true);
+  auto* stmt =
+      create<ast::IfStatement>(cond, body, ast::ElseStatementList{else_stmt});
+  WrapInFunction(v, stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(stmt->condition), nullptr);
+  ASSERT_NE(TypeOf(else_lhs), nullptr);
+  ASSERT_NE(TypeOf(else_rhs), nullptr);
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+  EXPECT_TRUE(TypeOf(stmt->condition)->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(else_lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(else_rhs)->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(lhs), assign);
+  EXPECT_EQ(StmtOf(rhs), assign);
+  EXPECT_EQ(StmtOf(cond), stmt);
+  EXPECT_EQ(StmtOf(else_cond), else_stmt);
+  EXPECT_EQ(BlockOf(lhs), body);
+  EXPECT_EQ(BlockOf(rhs), body);
+  EXPECT_EQ(BlockOf(else_lhs), else_body);
+  EXPECT_EQ(BlockOf(else_rhs), else_body);
+}
+
+TEST_F(ResolverTest, Stmt_Loop) {
+  auto* v = Var("v", ty.f32());
+  auto* body_lhs = Expr("v");
+  auto* body_rhs = Expr(2.3f);
+
+  auto* body = Block(Assign(body_lhs, body_rhs), Break());
+  auto* continuing_lhs = Expr("v");
+  auto* continuing_rhs = Expr(2.3f);
+
+  auto* continuing = Block(Assign(continuing_lhs, continuing_rhs));
+  auto* stmt = Loop(body, continuing);
+  WrapInFunction(v, stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(body_lhs), nullptr);
+  ASSERT_NE(TypeOf(body_rhs), nullptr);
+  ASSERT_NE(TypeOf(continuing_lhs), nullptr);
+  ASSERT_NE(TypeOf(continuing_rhs), nullptr);
+  EXPECT_TRUE(TypeOf(body_lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(body_rhs)->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(continuing_lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(continuing_rhs)->Is<sem::F32>());
+  EXPECT_EQ(BlockOf(body_lhs), body);
+  EXPECT_EQ(BlockOf(body_rhs), body);
+  EXPECT_EQ(BlockOf(continuing_lhs), continuing);
+  EXPECT_EQ(BlockOf(continuing_rhs), continuing);
+}
+
+TEST_F(ResolverTest, Stmt_Return) {
+  auto* cond = Expr(2);
+
+  auto* ret = Return(cond);
+  Func("test", {}, ty.i32(), {ret}, {});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(cond), nullptr);
+  EXPECT_TRUE(TypeOf(cond)->Is<sem::I32>());
+}
+
+TEST_F(ResolverTest, Stmt_Return_WithoutValue) {
+  auto* ret = Return();
+  WrapInFunction(ret);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, Stmt_Switch) {
+  auto* v = Var("v", ty.f32());
+  auto* lhs = Expr("v");
+  auto* rhs = Expr(2.3f);
+  auto* case_block = Block(Assign(lhs, rhs));
+  auto* stmt = Switch(Expr(2), Case(Expr(3), case_block), DefaultCase());
+  WrapInFunction(v, stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(stmt->condition), nullptr);
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+
+  EXPECT_TRUE(TypeOf(stmt->condition)->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(lhs)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(rhs)->Is<sem::F32>());
+  EXPECT_EQ(BlockOf(lhs), case_block);
+  EXPECT_EQ(BlockOf(rhs), case_block);
+}
+
+TEST_F(ResolverTest, Stmt_Call) {
+  ast::VariableList params;
+  Func("my_func", params, ty.void_(), {Return()}, ast::AttributeList{});
+
+  auto* expr = Call("my_func");
+
+  auto* call = CallStmt(expr);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::Void>());
+  EXPECT_EQ(StmtOf(expr), call);
+}
+
+TEST_F(ResolverTest, Stmt_VariableDecl) {
+  auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  auto* init = var->constructor;
+
+  auto* decl = Decl(var);
+  WrapInFunction(decl);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(init), nullptr);
+  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
+}
+
+TEST_F(ResolverTest, Stmt_VariableDecl_Alias) {
+  auto* my_int = Alias("MyInt", ty.i32());
+  auto* var = Var("my_var", ty.Of(my_int), ast::StorageClass::kNone, Expr(2));
+  auto* init = var->constructor;
+
+  auto* decl = Decl(var);
+  WrapInFunction(decl);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(init), nullptr);
+  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
+}
+
+TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScope) {
+  auto* init = Expr(2);
+  Global("my_var", ty.i32(), ast::StorageClass::kPrivate, init);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(init), nullptr);
+  EXPECT_TRUE(TypeOf(init)->Is<sem::I32>());
+  EXPECT_EQ(StmtOf(init), nullptr);
+}
+
+TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
+  // fn func_i32() {
+  //   {
+  //     var foo : i32 = 2;
+  //     var bar : i32 = foo;
+  //   }
+  //   var foo : f32 = 2.0;
+  //   var bar : f32 = foo;
+  // }
+
+  ast::VariableList params;
+
+  // Declare i32 "foo" inside a block
+  auto* foo_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  auto* foo_i32_init = foo_i32->constructor;
+  auto* foo_i32_decl = Decl(foo_i32);
+
+  // Reference "foo" inside the block
+  auto* bar_i32 = Var("bar", ty.i32(), ast::StorageClass::kNone, Expr("foo"));
+  auto* bar_i32_init = bar_i32->constructor;
+  auto* bar_i32_decl = Decl(bar_i32);
+
+  auto* inner = Block(foo_i32_decl, bar_i32_decl);
+
+  // Declare f32 "foo" at function scope
+  auto* foo_f32 = Var("foo", ty.f32(), ast::StorageClass::kNone, Expr(2.f));
+  auto* foo_f32_init = foo_f32->constructor;
+  auto* foo_f32_decl = Decl(foo_f32);
+
+  // Reference "foo" at function scope
+  auto* bar_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
+  auto* bar_f32_init = bar_f32->constructor;
+  auto* bar_f32_decl = Decl(bar_f32);
+
+  Func("func", params, ty.void_(), {inner, foo_f32_decl, bar_f32_decl},
+       ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(foo_i32_init), nullptr);
+  EXPECT_TRUE(TypeOf(foo_i32_init)->Is<sem::I32>());
+  ASSERT_NE(TypeOf(foo_f32_init), nullptr);
+  EXPECT_TRUE(TypeOf(foo_f32_init)->Is<sem::F32>());
+  ASSERT_NE(TypeOf(bar_i32_init), nullptr);
+  EXPECT_TRUE(TypeOf(bar_i32_init)->UnwrapRef()->Is<sem::I32>());
+  ASSERT_NE(TypeOf(bar_f32_init), nullptr);
+  EXPECT_TRUE(TypeOf(bar_f32_init)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(foo_i32_init), foo_i32_decl);
+  EXPECT_EQ(StmtOf(bar_i32_init), bar_i32_decl);
+  EXPECT_EQ(StmtOf(foo_f32_init), foo_f32_decl);
+  EXPECT_EQ(StmtOf(bar_f32_init), bar_f32_decl);
+  EXPECT_TRUE(CheckVarUsers(foo_i32, {bar_i32->constructor}));
+  EXPECT_TRUE(CheckVarUsers(foo_f32, {bar_f32->constructor}));
+  ASSERT_NE(VarOf(bar_i32->constructor), nullptr);
+  EXPECT_EQ(VarOf(bar_i32->constructor)->Declaration(), foo_i32);
+  ASSERT_NE(VarOf(bar_f32->constructor), nullptr);
+  EXPECT_EQ(VarOf(bar_f32->constructor)->Declaration(), foo_f32);
+}
+
+TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
+  // fn func_i32() {
+  //   var foo : i32 = 2;
+  // }
+  // var foo : f32 = 2.0;
+  // fn func_f32() {
+  //   var bar : f32 = foo;
+  // }
+
+  ast::VariableList params;
+
+  // Declare i32 "foo" inside a function
+  auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
+  auto* fn_i32_init = fn_i32->constructor;
+  auto* fn_i32_decl = Decl(fn_i32);
+  Func("func_i32", params, ty.void_(), {fn_i32_decl}, ast::AttributeList{});
+
+  // Declare f32 "foo" at module scope
+  auto* mod_f32 = Var("foo", ty.f32(), ast::StorageClass::kPrivate, Expr(2.f));
+  auto* mod_init = mod_f32->constructor;
+  AST().AddGlobalVariable(mod_f32);
+
+  // Reference "foo" in another function
+  auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
+  auto* fn_f32_init = fn_f32->constructor;
+  auto* fn_f32_decl = Decl(fn_f32);
+  Func("func_f32", params, ty.void_(), {fn_f32_decl}, ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(mod_init), nullptr);
+  EXPECT_TRUE(TypeOf(mod_init)->Is<sem::F32>());
+  ASSERT_NE(TypeOf(fn_i32_init), nullptr);
+  EXPECT_TRUE(TypeOf(fn_i32_init)->Is<sem::I32>());
+  ASSERT_NE(TypeOf(fn_f32_init), nullptr);
+  EXPECT_TRUE(TypeOf(fn_f32_init)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(fn_i32_init), fn_i32_decl);
+  EXPECT_EQ(StmtOf(mod_init), nullptr);
+  EXPECT_EQ(StmtOf(fn_f32_init), fn_f32_decl);
+  EXPECT_TRUE(CheckVarUsers(fn_i32, {}));
+  EXPECT_TRUE(CheckVarUsers(mod_f32, {fn_f32->constructor}));
+  ASSERT_NE(VarOf(fn_f32->constructor), nullptr);
+  EXPECT_EQ(VarOf(fn_f32->constructor)->Declaration(), mod_f32);
+}
+
+TEST_F(ResolverTest, ArraySize_UnsignedLiteral) {
+  // var<private> a : array<f32, 10u>;
+  auto* a =
+      Global("a", ty.array(ty.f32(), Expr(10u)), ast::StorageClass::kPrivate);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(a), nullptr);
+  auto* ref = TypeOf(a)->As<sem::Reference>();
+  ASSERT_NE(ref, nullptr);
+  auto* ary = ref->StoreType()->As<sem::Array>();
+  EXPECT_EQ(ary->Count(), 10u);
+}
+
+TEST_F(ResolverTest, ArraySize_SignedLiteral) {
+  // var<private> a : array<f32, 10>;
+  auto* a =
+      Global("a", ty.array(ty.f32(), Expr(10)), ast::StorageClass::kPrivate);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(a), nullptr);
+  auto* ref = TypeOf(a)->As<sem::Reference>();
+  ASSERT_NE(ref, nullptr);
+  auto* ary = ref->StoreType()->As<sem::Array>();
+  EXPECT_EQ(ary->Count(), 10u);
+}
+
+TEST_F(ResolverTest, ArraySize_UnsignedConstant) {
+  // let size = 0u;
+  // var<private> a : array<f32, 10u>;
+  GlobalConst("size", nullptr, Expr(10u));
+  auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
+                   ast::StorageClass::kPrivate);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(a), nullptr);
+  auto* ref = TypeOf(a)->As<sem::Reference>();
+  ASSERT_NE(ref, nullptr);
+  auto* ary = ref->StoreType()->As<sem::Array>();
+  EXPECT_EQ(ary->Count(), 10u);
+}
+
+TEST_F(ResolverTest, ArraySize_SignedConstant) {
+  // let size = 0;
+  // var<private> a : array<f32, 10>;
+  GlobalConst("size", nullptr, Expr(10));
+  auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
+                   ast::StorageClass::kPrivate);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(a), nullptr);
+  auto* ref = TypeOf(a)->As<sem::Reference>();
+  ASSERT_NE(ref, nullptr);
+  auto* ary = ref->StoreType()->As<sem::Array>();
+  EXPECT_EQ(ary->Count(), 10u);
+}
+
+TEST_F(ResolverTest, Expr_Bitcast) {
+  Global("name", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr("name"));
+  WrapInFunction(bitcast);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(bitcast), nullptr);
+  EXPECT_TRUE(TypeOf(bitcast)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Call) {
+  ast::VariableList params;
+  Func("my_func", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
+  ast::VariableList params;
+  Func("func", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
+
+  auto* expr = Add(Call("func"), Call("func"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Call_WithParams) {
+  Func("my_func", {Param(Sym(), ty.f32())}, ty.f32(),
+       {
+           Return(1.2f),
+       });
+
+  auto* param = Expr(2.4f);
+
+  auto* call = Call("my_func", param);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(param), nullptr);
+  EXPECT_TRUE(TypeOf(param)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Call_Builtin) {
+  auto* call = Call("round", 2.4f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Cast) {
+  Global("name", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* cast = Construct(ty.f32(), "name");
+  WrapInFunction(cast);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(cast), nullptr);
+  EXPECT_TRUE(TypeOf(cast)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Scalar) {
+  auto* s = Expr(1.0f);
+  WrapInFunction(s);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(s), nullptr);
+  EXPECT_TRUE(TypeOf(s)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec2) {
+  auto* tc = vec2<f32>(1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec3) {
+  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec4) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTest, Expr_Identifier_GlobalVariable) {
+  auto* my_var = Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* ident = Expr("my_var");
+  WrapInFunction(ident);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(ident), nullptr);
+  ASSERT_TRUE(TypeOf(ident)->Is<sem::Reference>());
+  EXPECT_TRUE(TypeOf(ident)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
+  ASSERT_NE(VarOf(ident), nullptr);
+  EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
+}
+
+TEST_F(ResolverTest, Expr_Identifier_GlobalConstant) {
+  auto* my_var = GlobalConst("my_var", ty.f32(), Construct(ty.f32()));
+
+  auto* ident = Expr("my_var");
+  WrapInFunction(ident);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(ident), nullptr);
+  EXPECT_TRUE(TypeOf(ident)->Is<sem::F32>());
+  EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
+  ASSERT_NE(VarOf(ident), nullptr);
+  EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
+}
+
+TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
+  auto* my_var_a = Expr("my_var");
+  auto* var = Const("my_var", ty.f32(), Construct(ty.f32()));
+  auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kNone, my_var_a));
+
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           decl,
+       },
+       ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(my_var_a), nullptr);
+  EXPECT_TRUE(TypeOf(my_var_a)->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(my_var_a), decl);
+  EXPECT_TRUE(CheckVarUsers(var, {my_var_a}));
+  ASSERT_NE(VarOf(my_var_a), nullptr);
+  EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
+}
+
+TEST_F(ResolverTest, IndexAccessor_Dynamic_Ref_F32) {
+  // var a : array<bool, 10> = 0;
+  // var idx : f32 = f32();
+  // var f : f32 = a[idx];
+  auto* a = Var("a", ty.array<bool, 10>(), array<bool, 10>());
+  auto* idx = Var("idx", ty.f32(), Construct(ty.f32()));
+  auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(idx),
+           Decl(f),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
+}
+
+TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {
+  auto* my_var_a = Expr("my_var");
+  auto* my_var_b = Expr("my_var");
+  auto* assign = Assign(my_var_a, my_var_b);
+
+  auto* var = Var("my_var", ty.f32());
+
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           assign,
+       },
+       ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(my_var_a), nullptr);
+  ASSERT_TRUE(TypeOf(my_var_a)->Is<sem::Reference>());
+  EXPECT_TRUE(TypeOf(my_var_a)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(my_var_a), assign);
+  ASSERT_NE(TypeOf(my_var_b), nullptr);
+  ASSERT_TRUE(TypeOf(my_var_b)->Is<sem::Reference>());
+  EXPECT_TRUE(TypeOf(my_var_b)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(my_var_b), assign);
+  EXPECT_TRUE(CheckVarUsers(var, {my_var_a, my_var_b}));
+  ASSERT_NE(VarOf(my_var_a), nullptr);
+  EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
+  ASSERT_NE(VarOf(my_var_b), nullptr);
+  EXPECT_EQ(VarOf(my_var_b)->Declaration(), var);
+}
+
+TEST_F(ResolverTest, Expr_Identifier_Function_Ptr) {
+  auto* v = Expr("v");
+  auto* p = Expr("p");
+  auto* v_decl = Decl(Var("v", ty.f32()));
+  auto* p_decl = Decl(
+      Const("p", ty.pointer<f32>(ast::StorageClass::kFunction), AddressOf(v)));
+  auto* assign = Assign(Deref(p), 1.23f);
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           v_decl,
+           p_decl,
+           assign,
+       },
+       ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(v), nullptr);
+  ASSERT_TRUE(TypeOf(v)->Is<sem::Reference>());
+  EXPECT_TRUE(TypeOf(v)->UnwrapRef()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(v), p_decl);
+  ASSERT_NE(TypeOf(p), nullptr);
+  ASSERT_TRUE(TypeOf(p)->Is<sem::Pointer>());
+  EXPECT_TRUE(TypeOf(p)->UnwrapPtr()->Is<sem::F32>());
+  EXPECT_EQ(StmtOf(p), assign);
+}
+
+TEST_F(ResolverTest, Expr_Call_Function) {
+  Func("my_func", ast::VariableList{}, ty.f32(), {Return(0.0f)},
+       ast::AttributeList{});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<sem::F32>());
+}
+
+TEST_F(ResolverTest, Expr_Identifier_Unknown) {
+  auto* a = Expr("a");
+  WrapInFunction(a);
+
+  EXPECT_FALSE(r()->Resolve());
+}
+
+TEST_F(ResolverTest, Function_Parameters) {
+  auto* param_a = Param("a", ty.f32());
+  auto* param_b = Param("b", ty.i32());
+  auto* param_c = Param("c", ty.u32());
+
+  auto* func = Func("my_func",
+                    ast::VariableList{
+                        param_a,
+                        param_b,
+                        param_c,
+                    },
+                    ty.void_(), {});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+  EXPECT_EQ(func_sem->Parameters().size(), 3u);
+  EXPECT_TRUE(func_sem->Parameters()[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(func_sem->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(func_sem->Parameters()[2]->Type()->Is<sem::U32>());
+  EXPECT_EQ(func_sem->Parameters()[0]->Declaration(), param_a);
+  EXPECT_EQ(func_sem->Parameters()[1]->Declaration(), param_b);
+  EXPECT_EQ(func_sem->Parameters()[2]->Declaration(), param_c);
+  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
+}
+
+TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
+  auto* s = Structure("S", {Member("m", ty.u32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                        ast::Access::kReadWrite,
+                        ast::AttributeList{
+                            create<ast::BindingAttribute>(0),
+                            create<ast::GroupAttribute>(0),
+                        });
+  auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    {
+                        Assign("wg_var", "wg_var"),
+                        Assign("sb_var", "sb_var"),
+                        Assign("priv_var", "priv_var"),
+                    });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+  EXPECT_EQ(func_sem->Parameters().size(), 0u);
+  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
+
+  const auto& vars = func_sem->TransitivelyReferencedGlobals();
+  ASSERT_EQ(vars.size(), 3u);
+  EXPECT_EQ(vars[0]->Declaration(), wg_var);
+  EXPECT_EQ(vars[1]->Declaration(), sb_var);
+  EXPECT_EQ(vars[2]->Declaration(), priv_var);
+}
+
+TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
+  auto* s = Structure("S", {Member("m", ty.u32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                        ast::Access::kReadWrite,
+                        ast::AttributeList{
+                            create<ast::BindingAttribute>(0),
+                            create<ast::GroupAttribute>(0),
+                        });
+  auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  Func("my_func", ast::VariableList{}, ty.f32(),
+       {Assign("wg_var", "wg_var"), Assign("sb_var", "sb_var"),
+        Assign("priv_var", "priv_var"), Return(0.0f)},
+       ast::AttributeList{});
+
+  auto* func2 = Func("func", ast::VariableList{}, ty.void_(),
+                     {
+                         WrapInStatement(Call("my_func")),
+                     },
+                     ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func2_sem = Sem().Get(func2);
+  ASSERT_NE(func2_sem, nullptr);
+  EXPECT_EQ(func2_sem->Parameters().size(), 0u);
+
+  const auto& vars = func2_sem->TransitivelyReferencedGlobals();
+  ASSERT_EQ(vars.size(), 3u);
+  EXPECT_EQ(vars[0]->Declaration(), wg_var);
+  EXPECT_EQ(vars[1]->Declaration(), sb_var);
+  EXPECT_EQ(vars[2]->Declaration(), priv_var);
+}
+
+TEST_F(ResolverTest, Function_NotRegisterFunctionVariable) {
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    {
+                        Decl(Var("var", ty.f32())),
+                        Assign("var", 1.f),
+                    });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
+  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
+}
+
+TEST_F(ResolverTest, Function_NotRegisterFunctionConstant) {
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    {
+                        Decl(Const("var", ty.f32(), Construct(ty.f32()))),
+                    });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
+  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
+}
+
+TEST_F(ResolverTest, Function_NotRegisterFunctionParams) {
+  auto* func = Func("my_func", {Const("var", ty.f32(), Construct(ty.f32()))},
+                    ty.void_(), {});
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->TransitivelyReferencedGlobals().size(), 0u);
+  EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
+}
+
+TEST_F(ResolverTest, Function_CallSites) {
+  auto* foo = Func("foo", ast::VariableList{}, ty.void_(), {});
+
+  auto* call_1 = Call("foo");
+  auto* call_2 = Call("foo");
+  auto* bar = Func("bar", ast::VariableList{}, ty.void_(),
+                   {
+                       CallStmt(call_1),
+                       CallStmt(call_2),
+                   });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* foo_sem = Sem().Get(foo);
+  ASSERT_NE(foo_sem, nullptr);
+  ASSERT_EQ(foo_sem->CallSites().size(), 2u);
+  EXPECT_EQ(foo_sem->CallSites()[0]->Declaration(), call_1);
+  EXPECT_EQ(foo_sem->CallSites()[1]->Declaration(), call_2);
+
+  auto* bar_sem = Sem().Get(bar);
+  ASSERT_NE(bar_sem, nullptr);
+  EXPECT_EQ(bar_sem->CallSites().size(), 0u);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_NotSet) {
+  // @stage(compute) @workgroup_size(1)
+  // fn main() {}
+  auto* func = Func("main", ast::VariableList{}, ty.void_(), {}, {});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 1u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 1u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 1u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_Literals) {
+  // @stage(compute) @workgroup_size(8, 2, 3)
+  // fn main() {}
+  auto* func =
+      Func("main", ast::VariableList{}, ty.void_(), {},
+           {Stage(ast::PipelineStage::kCompute), WorkgroupSize(8, 2, 3)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 2u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 3u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_Consts) {
+  // let width = 16;
+  // let height = 8;
+  // let depth = 2;
+  // @stage(compute) @workgroup_size(width, height, depth)
+  // fn main() {}
+  GlobalConst("width", ty.i32(), Expr(16));
+  GlobalConst("height", ty.i32(), Expr(8));
+  GlobalConst("depth", ty.i32(), Expr(2));
+  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
+                    {Stage(ast::PipelineStage::kCompute),
+                     WorkgroupSize("width", "height", "depth")});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 16u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 8u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 2u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_Consts_NestedInitializer) {
+  // let width = i32(i32(i32(8)));
+  // let height = i32(i32(i32(4)));
+  // @stage(compute) @workgroup_size(width, height)
+  // fn main() {}
+  GlobalConst("width", ty.i32(),
+              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 8))));
+  GlobalConst("height", ty.i32(),
+              Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 4))));
+  auto* func = Func(
+      "main", ast::VariableList{}, ty.void_(), {},
+      {Stage(ast::PipelineStage::kCompute), WorkgroupSize("width", "height")});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 4u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 1u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_OverridableConsts) {
+  // @id(0) override width = 16;
+  // @id(1) override height = 8;
+  // @id(2) override depth = 2;
+  // @stage(compute) @workgroup_size(width, height, depth)
+  // fn main() {}
+  auto* width = Override("width", ty.i32(), Expr(16), {Id(0)});
+  auto* height = Override("height", ty.i32(), Expr(8), {Id(1)});
+  auto* depth = Override("depth", ty.i32(), Expr(2), {Id(2)});
+  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
+                    {Stage(ast::PipelineStage::kCompute),
+                     WorkgroupSize("width", "height", "depth")});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 16u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 8u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 2u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, width);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, depth);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_OverridableConsts_NoInit) {
+  // @id(0) override width : i32;
+  // @id(1) override height : i32;
+  // @id(2) override depth : i32;
+  // @stage(compute) @workgroup_size(width, height, depth)
+  // fn main() {}
+  auto* width = Override("width", ty.i32(), nullptr, {Id(0)});
+  auto* height = Override("height", ty.i32(), nullptr, {Id(1)});
+  auto* depth = Override("depth", ty.i32(), nullptr, {Id(2)});
+  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
+                    {Stage(ast::PipelineStage::kCompute),
+                     WorkgroupSize("width", "height", "depth")});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 0u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 0u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 0u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, width);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, depth);
+}
+
+TEST_F(ResolverTest, Function_WorkgroupSize_Mixed) {
+  // @id(1) override height = 2;
+  // let depth = 3;
+  // @stage(compute) @workgroup_size(8, height, depth)
+  // fn main() {}
+  auto* height = Override("height", ty.i32(), Expr(2), {Id(0)});
+  GlobalConst("depth", ty.i32(), Expr(3));
+  auto* func = Func("main", ast::VariableList{}, ty.void_(), {},
+                    {Stage(ast::PipelineStage::kCompute),
+                     WorkgroupSize(8, "height", "depth")});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_sem = Sem().Get(func);
+  ASSERT_NE(func_sem, nullptr);
+
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].value, 8u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].value, 2u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].value, 3u);
+  EXPECT_EQ(func_sem->WorkgroupSize()[0].overridable_const, nullptr);
+  EXPECT_EQ(func_sem->WorkgroupSize()[1].overridable_const, height);
+  EXPECT_EQ(func_sem->WorkgroupSize()[2].overridable_const, nullptr);
+}
+
+TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
+  auto* st = Structure("S", {Member("first_member", ty.i32()),
+                             Member("second_member", ty.f32())});
+  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
+
+  auto* mem = MemberAccessor("my_struct", "second_member");
+  WrapInFunction(mem);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(mem), nullptr);
+  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(mem)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+  auto* sma = Sem().Get(mem)->As<sem::StructMemberAccess>();
+  ASSERT_NE(sma, nullptr);
+  EXPECT_TRUE(sma->Member()->Type()->Is<sem::F32>());
+  EXPECT_EQ(sma->Member()->Index(), 1u);
+  EXPECT_EQ(sma->Member()->Declaration()->symbol,
+            Symbols().Get("second_member"));
+}
+
+TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
+  auto* st = Structure("S", {Member("first_member", ty.i32()),
+                             Member("second_member", ty.f32())});
+  auto* alias = Alias("alias", ty.Of(st));
+  Global("my_struct", ty.Of(alias), ast::StorageClass::kPrivate);
+
+  auto* mem = MemberAccessor("my_struct", "second_member");
+  WrapInFunction(mem);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(mem), nullptr);
+  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(mem)->As<sem::Reference>();
+  EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+  auto* sma = Sem().Get(mem)->As<sem::StructMemberAccess>();
+  ASSERT_NE(sma, nullptr);
+  EXPECT_TRUE(sma->Member()->Type()->Is<sem::F32>());
+  EXPECT_EQ(sma->Member()->Index(), 1u);
+}
+
+TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle) {
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* mem = MemberAccessor("my_vec", "xzyw");
+  WrapInFunction(mem);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(mem), nullptr);
+  ASSERT_TRUE(TypeOf(mem)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(mem)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(mem)->As<sem::Vector>()->Width(), 4u);
+  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
+  EXPECT_THAT(Sem().Get(mem)->As<sem::Swizzle>()->Indices(),
+              ElementsAre(0, 2, 1, 3));
+}
+
+TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_SingleElement) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* mem = MemberAccessor("my_vec", "b");
+  WrapInFunction(mem);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(mem), nullptr);
+  ASSERT_TRUE(TypeOf(mem)->Is<sem::Reference>());
+
+  auto* ref = TypeOf(mem)->As<sem::Reference>();
+  ASSERT_TRUE(ref->StoreType()->Is<sem::F32>());
+  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
+  EXPECT_THAT(Sem().Get(mem)->As<sem::Swizzle>()->Indices(), ElementsAre(2));
+}
+
+TEST_F(ResolverTest, Expr_Accessor_MultiLevel) {
+  // struct b {
+  //   vec4<f32> foo
+  // }
+  // struct A {
+  //   array<b, 3> mem
+  // }
+  // var c : A
+  // c.mem[0].foo.yx
+  //   -> vec2<f32>
+  //
+  // fn f() {
+  //   c.mem[0].foo
+  // }
+  //
+
+  auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
+  auto* stA = Structure("A", {Member("mem", ty.array(ty.Of(stB), 3))});
+  Global("c", ty.Of(stA), ast::StorageClass::kPrivate);
+
+  auto* mem = MemberAccessor(
+      MemberAccessor(IndexAccessor(MemberAccessor("c", "mem"), 0), "foo"),
+      "yx");
+  WrapInFunction(mem);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(mem), nullptr);
+  ASSERT_TRUE(TypeOf(mem)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(mem)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(mem)->As<sem::Vector>()->Width(), 2u);
+  ASSERT_TRUE(Sem().Get(mem)->Is<sem::Swizzle>());
+}
+
+TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
+  auto* st = Structure("S", {Member("first_member", ty.f32()),
+                             Member("second_member", ty.f32())});
+  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
+
+  auto* expr = Add(MemberAccessor("my_struct", "first_member"),
+                   MemberAccessor("my_struct", "second_member"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<sem::F32>());
+}
+
+namespace ExprBinaryTest {
+
+template <typename T, int ID>
+struct Aliased {
+  using type = alias<T, ID>;
+};
+
+template <int N, typename T, int ID>
+struct Aliased<vec<N, T>, ID> {
+  using type = vec<N, alias<T, ID>>;
+};
+
+template <int N, int M, typename T, int ID>
+struct Aliased<mat<N, M, T>, ID> {
+  using type = mat<N, M, alias<T, ID>>;
+};
+
+struct Params {
+  ast::BinaryOp op;
+  builder::ast_type_func_ptr create_lhs_type;
+  builder::ast_type_func_ptr create_rhs_type;
+  builder::ast_type_func_ptr create_lhs_alias_type;
+  builder::ast_type_func_ptr create_rhs_alias_type;
+  builder::sem_type_func_ptr create_result_type;
+};
+
+template <typename LHS, typename RHS, typename RES>
+constexpr Params ParamsFor(ast::BinaryOp op) {
+  return Params{op,
+                DataType<LHS>::AST,
+                DataType<RHS>::AST,
+                DataType<typename Aliased<LHS, 0>::type>::AST,
+                DataType<typename Aliased<RHS, 1>::type>::AST,
+                DataType<RES>::Sem};
+}
+
+static constexpr ast::BinaryOp all_ops[] = {
+    ast::BinaryOp::kAnd,
+    ast::BinaryOp::kOr,
+    ast::BinaryOp::kXor,
+    ast::BinaryOp::kLogicalAnd,
+    ast::BinaryOp::kLogicalOr,
+    ast::BinaryOp::kEqual,
+    ast::BinaryOp::kNotEqual,
+    ast::BinaryOp::kLessThan,
+    ast::BinaryOp::kGreaterThan,
+    ast::BinaryOp::kLessThanEqual,
+    ast::BinaryOp::kGreaterThanEqual,
+    ast::BinaryOp::kShiftLeft,
+    ast::BinaryOp::kShiftRight,
+    ast::BinaryOp::kAdd,
+    ast::BinaryOp::kSubtract,
+    ast::BinaryOp::kMultiply,
+    ast::BinaryOp::kDivide,
+    ast::BinaryOp::kModulo,
+};
+
+static constexpr builder::ast_type_func_ptr all_create_type_funcs[] = {
+    DataType<bool>::AST,         //
+    DataType<u32>::AST,          //
+    DataType<i32>::AST,          //
+    DataType<f32>::AST,          //
+    DataType<vec3<bool>>::AST,   //
+    DataType<vec3<i32>>::AST,    //
+    DataType<vec3<u32>>::AST,    //
+    DataType<vec3<f32>>::AST,    //
+    DataType<mat3x3<f32>>::AST,  //
+    DataType<mat2x3<f32>>::AST,  //
+    DataType<mat3x2<f32>>::AST   //
+};
+
+// A list of all valid test cases for 'lhs op rhs', except that for vecN and
+// matNxN, we only test N=3.
+static constexpr Params all_valid_cases[] = {
+    // Logical expressions
+    // https://gpuweb.github.io/gpuweb/wgsl.html#logical-expr
+
+    // Binary logical expressions
+    ParamsFor<bool, bool, bool>(Op::kLogicalAnd),
+    ParamsFor<bool, bool, bool>(Op::kLogicalOr),
+
+    ParamsFor<bool, bool, bool>(Op::kAnd),
+    ParamsFor<bool, bool, bool>(Op::kOr),
+    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kAnd),
+    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kOr),
+
+    // Arithmetic expressions
+    // https://gpuweb.github.io/gpuweb/wgsl.html#arithmetic-expr
+
+    // Binary arithmetic expressions over scalars
+    ParamsFor<i32, i32, i32>(Op::kAdd),
+    ParamsFor<i32, i32, i32>(Op::kSubtract),
+    ParamsFor<i32, i32, i32>(Op::kMultiply),
+    ParamsFor<i32, i32, i32>(Op::kDivide),
+    ParamsFor<i32, i32, i32>(Op::kModulo),
+
+    ParamsFor<u32, u32, u32>(Op::kAdd),
+    ParamsFor<u32, u32, u32>(Op::kSubtract),
+    ParamsFor<u32, u32, u32>(Op::kMultiply),
+    ParamsFor<u32, u32, u32>(Op::kDivide),
+    ParamsFor<u32, u32, u32>(Op::kModulo),
+
+    ParamsFor<f32, f32, f32>(Op::kAdd),
+    ParamsFor<f32, f32, f32>(Op::kSubtract),
+    ParamsFor<f32, f32, f32>(Op::kMultiply),
+    ParamsFor<f32, f32, f32>(Op::kDivide),
+    ParamsFor<f32, f32, f32>(Op::kModulo),
+
+    // Binary arithmetic expressions over vectors
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kAdd),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kSubtract),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kMultiply),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kDivide),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kModulo),
+
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kAdd),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kSubtract),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kMultiply),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kDivide),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kModulo),
+
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kAdd),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kSubtract),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kMultiply),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kDivide),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<f32>>(Op::kModulo),
+
+    // Binary arithmetic expressions with mixed scalar and vector operands
+    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kAdd),
+    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kSubtract),
+    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kMultiply),
+    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kDivide),
+    ParamsFor<vec3<i32>, i32, vec3<i32>>(Op::kModulo),
+
+    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kAdd),
+    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kSubtract),
+    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kMultiply),
+    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kDivide),
+    ParamsFor<i32, vec3<i32>, vec3<i32>>(Op::kModulo),
+
+    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kAdd),
+    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kSubtract),
+    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kMultiply),
+    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kDivide),
+    ParamsFor<vec3<u32>, u32, vec3<u32>>(Op::kModulo),
+
+    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kAdd),
+    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kSubtract),
+    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kMultiply),
+    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kDivide),
+    ParamsFor<u32, vec3<u32>, vec3<u32>>(Op::kModulo),
+
+    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kAdd),
+    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kSubtract),
+    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kMultiply),
+    ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kDivide),
+    // NOTE: no kModulo for vec3<f32>, f32
+    // ParamsFor<vec3<f32>, f32, vec3<f32>>(Op::kModulo),
+
+    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kAdd),
+    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kSubtract),
+    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kMultiply),
+    ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kDivide),
+    // NOTE: no kModulo for f32, vec3<f32>
+    // ParamsFor<f32, vec3<f32>, vec3<f32>>(Op::kModulo),
+
+    // Matrix arithmetic
+    ParamsFor<mat2x3<f32>, f32, mat2x3<f32>>(Op::kMultiply),
+    ParamsFor<mat3x2<f32>, f32, mat3x2<f32>>(Op::kMultiply),
+    ParamsFor<mat3x3<f32>, f32, mat3x3<f32>>(Op::kMultiply),
+
+    ParamsFor<f32, mat2x3<f32>, mat2x3<f32>>(Op::kMultiply),
+    ParamsFor<f32, mat3x2<f32>, mat3x2<f32>>(Op::kMultiply),
+    ParamsFor<f32, mat3x3<f32>, mat3x3<f32>>(Op::kMultiply),
+
+    ParamsFor<vec3<f32>, mat2x3<f32>, vec2<f32>>(Op::kMultiply),
+    ParamsFor<vec2<f32>, mat3x2<f32>, vec3<f32>>(Op::kMultiply),
+    ParamsFor<vec3<f32>, mat3x3<f32>, vec3<f32>>(Op::kMultiply),
+
+    ParamsFor<mat3x2<f32>, vec3<f32>, vec2<f32>>(Op::kMultiply),
+    ParamsFor<mat2x3<f32>, vec2<f32>, vec3<f32>>(Op::kMultiply),
+    ParamsFor<mat3x3<f32>, vec3<f32>, vec3<f32>>(Op::kMultiply),
+
+    ParamsFor<mat2x3<f32>, mat3x2<f32>, mat3x3<f32>>(Op::kMultiply),
+    ParamsFor<mat3x2<f32>, mat2x3<f32>, mat2x2<f32>>(Op::kMultiply),
+    ParamsFor<mat3x2<f32>, mat3x3<f32>, mat3x2<f32>>(Op::kMultiply),
+    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kMultiply),
+    ParamsFor<mat3x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kMultiply),
+
+    ParamsFor<mat2x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kAdd),
+    ParamsFor<mat3x2<f32>, mat3x2<f32>, mat3x2<f32>>(Op::kAdd),
+    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kAdd),
+
+    ParamsFor<mat2x3<f32>, mat2x3<f32>, mat2x3<f32>>(Op::kSubtract),
+    ParamsFor<mat3x2<f32>, mat3x2<f32>, mat3x2<f32>>(Op::kSubtract),
+    ParamsFor<mat3x3<f32>, mat3x3<f32>, mat3x3<f32>>(Op::kSubtract),
+
+    // Comparison expressions
+    // https://gpuweb.github.io/gpuweb/wgsl.html#comparison-expr
+
+    // Comparisons over scalars
+    ParamsFor<bool, bool, bool>(Op::kEqual),
+    ParamsFor<bool, bool, bool>(Op::kNotEqual),
+
+    ParamsFor<i32, i32, bool>(Op::kEqual),
+    ParamsFor<i32, i32, bool>(Op::kNotEqual),
+    ParamsFor<i32, i32, bool>(Op::kLessThan),
+    ParamsFor<i32, i32, bool>(Op::kLessThanEqual),
+    ParamsFor<i32, i32, bool>(Op::kGreaterThan),
+    ParamsFor<i32, i32, bool>(Op::kGreaterThanEqual),
+
+    ParamsFor<u32, u32, bool>(Op::kEqual),
+    ParamsFor<u32, u32, bool>(Op::kNotEqual),
+    ParamsFor<u32, u32, bool>(Op::kLessThan),
+    ParamsFor<u32, u32, bool>(Op::kLessThanEqual),
+    ParamsFor<u32, u32, bool>(Op::kGreaterThan),
+    ParamsFor<u32, u32, bool>(Op::kGreaterThanEqual),
+
+    ParamsFor<f32, f32, bool>(Op::kEqual),
+    ParamsFor<f32, f32, bool>(Op::kNotEqual),
+    ParamsFor<f32, f32, bool>(Op::kLessThan),
+    ParamsFor<f32, f32, bool>(Op::kLessThanEqual),
+    ParamsFor<f32, f32, bool>(Op::kGreaterThan),
+    ParamsFor<f32, f32, bool>(Op::kGreaterThanEqual),
+
+    // Comparisons over vectors
+    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kEqual),
+    ParamsFor<vec3<bool>, vec3<bool>, vec3<bool>>(Op::kNotEqual),
+
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kEqual),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kNotEqual),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kLessThan),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kLessThanEqual),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kGreaterThan),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<bool>>(Op::kGreaterThanEqual),
+
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kEqual),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kNotEqual),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kLessThan),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kLessThanEqual),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kGreaterThan),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<bool>>(Op::kGreaterThanEqual),
+
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kEqual),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kNotEqual),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kLessThan),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kLessThanEqual),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kGreaterThan),
+    ParamsFor<vec3<f32>, vec3<f32>, vec3<bool>>(Op::kGreaterThanEqual),
+
+    // Binary bitwise operations
+    ParamsFor<i32, i32, i32>(Op::kOr),
+    ParamsFor<i32, i32, i32>(Op::kAnd),
+    ParamsFor<i32, i32, i32>(Op::kXor),
+
+    ParamsFor<u32, u32, u32>(Op::kOr),
+    ParamsFor<u32, u32, u32>(Op::kAnd),
+    ParamsFor<u32, u32, u32>(Op::kXor),
+
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kOr),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kAnd),
+    ParamsFor<vec3<i32>, vec3<i32>, vec3<i32>>(Op::kXor),
+
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kOr),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kAnd),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kXor),
+
+    // Bit shift expressions
+    ParamsFor<i32, u32, i32>(Op::kShiftLeft),
+    ParamsFor<vec3<i32>, vec3<u32>, vec3<i32>>(Op::kShiftLeft),
+
+    ParamsFor<u32, u32, u32>(Op::kShiftLeft),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kShiftLeft),
+
+    ParamsFor<i32, u32, i32>(Op::kShiftRight),
+    ParamsFor<vec3<i32>, vec3<u32>, vec3<i32>>(Op::kShiftRight),
+
+    ParamsFor<u32, u32, u32>(Op::kShiftRight),
+    ParamsFor<vec3<u32>, vec3<u32>, vec3<u32>>(Op::kShiftRight),
+};
+
+using Expr_Binary_Test_Valid = ResolverTestWithParam<Params>;
+TEST_P(Expr_Binary_Test_Valid, All) {
+  auto& params = GetParam();
+
+  auto* lhs_type = params.create_lhs_type(*this);
+  auto* rhs_type = params.create_rhs_type(*this);
+  auto* result_type = params.create_result_type(*this);
+
+  std::stringstream ss;
+  ss << FriendlyName(lhs_type) << " " << params.op << " "
+     << FriendlyName(rhs_type);
+  SCOPED_TRACE(ss.str());
+
+  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
+  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+
+  auto* expr =
+      create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr) == result_type);
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         Expr_Binary_Test_Valid,
+                         testing::ValuesIn(all_valid_cases));
+
+enum class BinaryExprSide { Left, Right, Both };
+using Expr_Binary_Test_WithAlias_Valid =
+    ResolverTestWithParam<std::tuple<Params, BinaryExprSide>>;
+TEST_P(Expr_Binary_Test_WithAlias_Valid, All) {
+  const Params& params = std::get<0>(GetParam());
+  BinaryExprSide side = std::get<1>(GetParam());
+
+  auto* create_lhs_type =
+      (side == BinaryExprSide::Left || side == BinaryExprSide::Both)
+          ? params.create_lhs_alias_type
+          : params.create_lhs_type;
+  auto* create_rhs_type =
+      (side == BinaryExprSide::Right || side == BinaryExprSide::Both)
+          ? params.create_rhs_alias_type
+          : params.create_rhs_type;
+
+  auto* lhs_type = create_lhs_type(*this);
+  auto* rhs_type = create_rhs_type(*this);
+
+  std::stringstream ss;
+  ss << FriendlyName(lhs_type) << " " << params.op << " "
+     << FriendlyName(rhs_type);
+
+  ss << ", After aliasing: " << FriendlyName(lhs_type) << " " << params.op
+     << " " << FriendlyName(rhs_type);
+  SCOPED_TRACE(ss.str());
+
+  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
+  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+
+  auto* expr =
+      create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(expr), nullptr);
+  // TODO(amaiorano): Bring this back once we have a way to get the canonical
+  // type
+  // auto* *result_type = params.create_result_type(*this);
+  // ASSERT_TRUE(TypeOf(expr) == result_type);
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    Expr_Binary_Test_WithAlias_Valid,
+    testing::Combine(testing::ValuesIn(all_valid_cases),
+                     testing::Values(BinaryExprSide::Left,
+                                     BinaryExprSide::Right,
+                                     BinaryExprSide::Both)));
+
+// This test works by taking the cartesian product of all possible
+// (type * type * op), and processing only the triplets that are not found in
+// the `all_valid_cases` table.
+using Expr_Binary_Test_Invalid =
+    ResolverTestWithParam<std::tuple<builder::ast_type_func_ptr,
+                                     builder::ast_type_func_ptr,
+                                     ast::BinaryOp>>;
+TEST_P(Expr_Binary_Test_Invalid, All) {
+  const builder::ast_type_func_ptr& lhs_create_type_func =
+      std::get<0>(GetParam());
+  const builder::ast_type_func_ptr& rhs_create_type_func =
+      std::get<1>(GetParam());
+  const ast::BinaryOp op = std::get<2>(GetParam());
+
+  // Skip if valid case
+  // TODO(amaiorano): replace linear lookup with O(1) if too slow
+  for (auto& c : all_valid_cases) {
+    if (c.create_lhs_type == lhs_create_type_func &&
+        c.create_rhs_type == rhs_create_type_func && c.op == op) {
+      return;
+    }
+  }
+
+  auto* lhs_type = lhs_create_type_func(*this);
+  auto* rhs_type = rhs_create_type_func(*this);
+
+  std::stringstream ss;
+  ss << FriendlyName(lhs_type) << " " << op << " " << FriendlyName(rhs_type);
+  SCOPED_TRACE(ss.str());
+
+  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
+  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(Source{{12, 34}}, op, Expr("lhs"),
+                                             Expr("rhs"));
+  WrapInFunction(expr);
+
+  ASSERT_FALSE(r()->Resolve());
+  ASSERT_EQ(r()->error(),
+            "12:34 error: Binary expression operand types are invalid for "
+            "this operation: " +
+                FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
+                " " + FriendlyName(rhs_type));
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    Expr_Binary_Test_Invalid,
+    testing::Combine(testing::ValuesIn(all_create_type_funcs),
+                     testing::ValuesIn(all_create_type_funcs),
+                     testing::ValuesIn(all_ops)));
+
+using Expr_Binary_Test_Invalid_VectorMatrixMultiply =
+    ResolverTestWithParam<std::tuple<bool, uint32_t, uint32_t, uint32_t>>;
+TEST_P(Expr_Binary_Test_Invalid_VectorMatrixMultiply, All) {
+  bool vec_by_mat = std::get<0>(GetParam());
+  uint32_t vec_size = std::get<1>(GetParam());
+  uint32_t mat_rows = std::get<2>(GetParam());
+  uint32_t mat_cols = std::get<3>(GetParam());
+
+  const ast::Type* lhs_type = nullptr;
+  const ast::Type* rhs_type = nullptr;
+  const sem::Type* result_type = nullptr;
+  bool is_valid_expr;
+
+  if (vec_by_mat) {
+    lhs_type = ty.vec<f32>(vec_size);
+    rhs_type = ty.mat<f32>(mat_cols, mat_rows);
+    result_type = create<sem::Vector>(create<sem::F32>(), mat_cols);
+    is_valid_expr = vec_size == mat_rows;
+  } else {
+    lhs_type = ty.mat<f32>(mat_cols, mat_rows);
+    rhs_type = ty.vec<f32>(vec_size);
+    result_type = create<sem::Vector>(create<sem::F32>(), mat_rows);
+    is_valid_expr = vec_size == mat_cols;
+  }
+
+  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
+  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+
+  auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  if (is_valid_expr) {
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+    ASSERT_TRUE(TypeOf(expr) == result_type);
+  } else {
+    ASSERT_FALSE(r()->Resolve());
+    ASSERT_EQ(r()->error(),
+              "12:34 error: Binary expression operand types are invalid for "
+              "this operation: " +
+                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
+                  " " + FriendlyName(rhs_type));
+  }
+}
+auto all_dimension_values = testing::Values(2u, 3u, 4u);
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         Expr_Binary_Test_Invalid_VectorMatrixMultiply,
+                         testing::Combine(testing::Values(true, false),
+                                          all_dimension_values,
+                                          all_dimension_values,
+                                          all_dimension_values));
+
+using Expr_Binary_Test_Invalid_MatrixMatrixMultiply =
+    ResolverTestWithParam<std::tuple<uint32_t, uint32_t, uint32_t, uint32_t>>;
+TEST_P(Expr_Binary_Test_Invalid_MatrixMatrixMultiply, All) {
+  uint32_t lhs_mat_rows = std::get<0>(GetParam());
+  uint32_t lhs_mat_cols = std::get<1>(GetParam());
+  uint32_t rhs_mat_rows = std::get<2>(GetParam());
+  uint32_t rhs_mat_cols = std::get<3>(GetParam());
+
+  auto* lhs_type = ty.mat<f32>(lhs_mat_cols, lhs_mat_rows);
+  auto* rhs_type = ty.mat<f32>(rhs_mat_cols, rhs_mat_rows);
+
+  auto* f32 = create<sem::F32>();
+  auto* col = create<sem::Vector>(f32, lhs_mat_rows);
+  auto* result_type = create<sem::Matrix>(col, rhs_mat_cols);
+
+  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
+  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+
+  auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  bool is_valid_expr = lhs_mat_cols == rhs_mat_rows;
+  if (is_valid_expr) {
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+    ASSERT_TRUE(TypeOf(expr) == result_type);
+  } else {
+    ASSERT_FALSE(r()->Resolve());
+    ASSERT_EQ(r()->error(),
+              "12:34 error: Binary expression operand types are invalid for "
+              "this operation: " +
+                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op) +
+                  " " + FriendlyName(rhs_type));
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         Expr_Binary_Test_Invalid_MatrixMatrixMultiply,
+                         testing::Combine(all_dimension_values,
+                                          all_dimension_values,
+                                          all_dimension_values,
+                                          all_dimension_values));
+
+}  // namespace ExprBinaryTest
+
+using UnaryOpExpressionTest = ResolverTestWithParam<ast::UnaryOp>;
+TEST_P(UnaryOpExpressionTest, Expr_UnaryOp) {
+  auto op = GetParam();
+
+  if (op == ast::UnaryOp::kNot) {
+    Global("ident", ty.vec4<bool>(), ast::StorageClass::kPrivate);
+  } else if (op == ast::UnaryOp::kNegation || op == ast::UnaryOp::kComplement) {
+    Global("ident", ty.vec4<i32>(), ast::StorageClass::kPrivate);
+  } else {
+    Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  }
+  auto* der = create<ast::UnaryOpExpression>(op, Expr("ident"));
+  WrapInFunction(der);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(der), nullptr);
+  ASSERT_TRUE(TypeOf(der)->Is<sem::Vector>());
+  if (op == ast::UnaryOp::kNot) {
+    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  } else if (op == ast::UnaryOp::kNegation || op == ast::UnaryOp::kComplement) {
+    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::I32>());
+  } else {
+    EXPECT_TRUE(TypeOf(der)->As<sem::Vector>()->type()->Is<sem::F32>());
+  }
+  EXPECT_EQ(TypeOf(der)->As<sem::Vector>()->Width(), 4u);
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         UnaryOpExpressionTest,
+                         testing::Values(ast::UnaryOp::kComplement,
+                                         ast::UnaryOp::kNegation,
+                                         ast::UnaryOp::kNot));
+
+TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
+  auto* var = Var("var", ty.i32());
+
+  auto* stmt = Decl(var);
+  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kFunction);
+}
+
+TEST_F(ResolverTest, StorageClass_SetForSampler) {
+  auto* t = ty.sampler(ast::SamplerKind::kSampler);
+  auto* var = Global("var", t,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(0),
+                     });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->StorageClass(),
+            ast::StorageClass::kUniformConstant);
+}
+
+TEST_F(ResolverTest, StorageClass_SetForTexture) {
+  auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  auto* var = Global("var", t,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(0),
+                     });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->StorageClass(),
+            ast::StorageClass::kUniformConstant);
+}
+
+TEST_F(ResolverTest, StorageClass_DoesNotSetOnConst) {
+  auto* var = Const("var", ty.i32(), Construct(ty.i32()));
+  auto* stmt = Decl(var);
+  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kNone);
+}
+
+TEST_F(ResolverTest, Access_SetForStorageBuffer) {
+  // [[block]] struct S { x : i32 };
+  // var<storage> g : S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* var =
+      Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+             ast::AttributeList{
+                 create<ast::BindingAttribute>(0),
+                 create<ast::GroupAttribute>(0),
+             });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->Access(), ast::Access::kRead);
+}
+
+TEST_F(ResolverTest, BindingPoint_SetForResources) {
+  // @group(1) @binding(2) var s1 : sampler;
+  // @group(3) @binding(4) var s2 : sampler;
+  auto* s1 = Global(Sym(), ty.sampler(ast::SamplerKind::kSampler),
+                    ast::AttributeList{create<ast::GroupAttribute>(1),
+                                       create<ast::BindingAttribute>(2)});
+  auto* s2 = Global(Sym(), ty.sampler(ast::SamplerKind::kSampler),
+                    ast::AttributeList{create<ast::GroupAttribute>(3),
+                                       create<ast::BindingAttribute>(4)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s1)->BindingPoint(),
+            (sem::BindingPoint{1u, 2u}));
+  EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s2)->BindingPoint(),
+            (sem::BindingPoint{3u, 4u}));
+}
+
+TEST_F(ResolverTest, Function_EntryPoints_StageAttribute) {
+  // fn b() {}
+  // fn c() { b(); }
+  // fn a() { c(); }
+  // fn ep_1() { a(); b(); }
+  // fn ep_2() { c();}
+  //
+  // c -> {ep_1, ep_2}
+  // a -> {ep_1}
+  // b -> {ep_1, ep_2}
+  // ep_1 -> {}
+  // ep_2 -> {}
+
+  Global("first", ty.f32(), ast::StorageClass::kPrivate);
+  Global("second", ty.f32(), ast::StorageClass::kPrivate);
+  Global("call_a", ty.f32(), ast::StorageClass::kPrivate);
+  Global("call_b", ty.f32(), ast::StorageClass::kPrivate);
+  Global("call_c", ty.f32(), ast::StorageClass::kPrivate);
+
+  ast::VariableList params;
+  auto* func_b =
+      Func("b", params, ty.f32(), {Return(0.0f)}, ast::AttributeList{});
+  auto* func_c =
+      Func("c", params, ty.f32(), {Assign("second", Call("b")), Return(0.0f)},
+           ast::AttributeList{});
+
+  auto* func_a =
+      Func("a", params, ty.f32(), {Assign("first", Call("c")), Return(0.0f)},
+           ast::AttributeList{});
+
+  auto* ep_1 = Func("ep_1", params, ty.void_(),
+                    {
+                        Assign("call_a", Call("a")),
+                        Assign("call_b", Call("b")),
+                    },
+                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                       WorkgroupSize(1)});
+
+  auto* ep_2 = Func("ep_2", params, ty.void_(),
+                    {
+                        Assign("call_c", Call("c")),
+                    },
+                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                       WorkgroupSize(1)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func_b_sem = Sem().Get(func_b);
+  auto* func_a_sem = Sem().Get(func_a);
+  auto* func_c_sem = Sem().Get(func_c);
+  auto* ep_1_sem = Sem().Get(ep_1);
+  auto* ep_2_sem = Sem().Get(ep_2);
+  ASSERT_NE(func_b_sem, nullptr);
+  ASSERT_NE(func_a_sem, nullptr);
+  ASSERT_NE(func_c_sem, nullptr);
+  ASSERT_NE(ep_1_sem, nullptr);
+  ASSERT_NE(ep_2_sem, nullptr);
+
+  EXPECT_EQ(func_b_sem->Parameters().size(), 0u);
+  EXPECT_EQ(func_a_sem->Parameters().size(), 0u);
+  EXPECT_EQ(func_c_sem->Parameters().size(), 0u);
+
+  const auto& b_eps = func_b_sem->AncestorEntryPoints();
+  ASSERT_EQ(2u, b_eps.size());
+  EXPECT_EQ(Symbols().Register("ep_1"), b_eps[0]->Declaration()->symbol);
+  EXPECT_EQ(Symbols().Register("ep_2"), b_eps[1]->Declaration()->symbol);
+
+  const auto& a_eps = func_a_sem->AncestorEntryPoints();
+  ASSERT_EQ(1u, a_eps.size());
+  EXPECT_EQ(Symbols().Register("ep_1"), a_eps[0]->Declaration()->symbol);
+
+  const auto& c_eps = func_c_sem->AncestorEntryPoints();
+  ASSERT_EQ(2u, c_eps.size());
+  EXPECT_EQ(Symbols().Register("ep_1"), c_eps[0]->Declaration()->symbol);
+  EXPECT_EQ(Symbols().Register("ep_2"), c_eps[1]->Declaration()->symbol);
+
+  EXPECT_TRUE(ep_1_sem->AncestorEntryPoints().empty());
+  EXPECT_TRUE(ep_2_sem->AncestorEntryPoints().empty());
+}
+
+// Check for linear-time traversal of functions reachable from entry points.
+// See: crbug.com/tint/245
+TEST_F(ResolverTest, Function_EntryPoints_LinearTime) {
+  // fn lNa() { }
+  // fn lNb() { }
+  // ...
+  // fn l2a() { l3a(); l3b(); }
+  // fn l2b() { l3a(); l3b(); }
+  // fn l1a() { l2a(); l2b(); }
+  // fn l1b() { l2a(); l2b(); }
+  // fn main() { l1a(); l1b(); }
+
+  static constexpr int levels = 64;
+
+  auto fn_a = [](int level) { return "l" + std::to_string(level + 1) + "a"; };
+  auto fn_b = [](int level) { return "l" + std::to_string(level + 1) + "b"; };
+
+  Func(fn_a(levels), {}, ty.void_(), {}, {});
+  Func(fn_b(levels), {}, ty.void_(), {}, {});
+
+  for (int i = levels - 1; i >= 0; i--) {
+    Func(fn_a(i), {}, ty.void_(),
+         {
+             CallStmt(Call(fn_a(i + 1))),
+             CallStmt(Call(fn_b(i + 1))),
+         },
+         {});
+    Func(fn_b(i), {}, ty.void_(),
+         {
+             CallStmt(Call(fn_a(i + 1))),
+             CallStmt(Call(fn_b(i + 1))),
+         },
+         {});
+  }
+
+  Func("main", {}, ty.void_(),
+       {
+           CallStmt(Call(fn_a(0))),
+           CallStmt(Call(fn_b(0))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Test for crbug.com/tint/728
+TEST_F(ResolverTest, ASTNodesAreReached) {
+  Structure("A", {Member("x", ty.array<f32, 4>(4))});
+  Structure("B", {Member("x", ty.array<f32, 4>(4))});
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, ASTNodeNotReached) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.Expr("expr");
+        Resolver(&b).Resolve();
+      },
+      "internal compiler error: AST node 'tint::ast::IdentifierExpression' was "
+      "not reached by the resolver");
+}
+
+TEST_F(ResolverTest, ASTNodeReachedTwice) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        auto* expr = b.Expr(1);
+        b.Global("a", b.ty.i32(), ast::StorageClass::kPrivate, expr);
+        b.Global("b", b.ty.i32(), ast::StorageClass::kPrivate, expr);
+        Resolver(&b).Resolve();
+      },
+      "internal compiler error: AST node 'tint::ast::SintLiteralExpression' "
+      "was encountered twice in the same AST of a Program");
+}
+
+TEST_F(ResolverTest, UnaryOp_Not) {
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
+                                             Expr(Source{{12, 34}}, "ident"));
+  WrapInFunction(der);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot logical negate expression of type 'vec4<f32>");
+}
+
+TEST_F(ResolverTest, UnaryOp_Complement) {
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement,
+                                             Expr(Source{{12, 34}}, "ident"));
+  WrapInFunction(der);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: cannot bitwise complement expression of type 'vec4<f32>");
+}
+
+TEST_F(ResolverTest, UnaryOp_Negation) {
+  Global("ident", ty.u32(), ast::StorageClass::kPrivate);
+  auto* der = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation,
+                                             Expr(Source{{12, 34}}, "ident"));
+  WrapInFunction(der);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: cannot negate expression of type 'u32");
+}
+
+TEST_F(ResolverTest, TextureSampler_TextureSample) {
+  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 1));
+  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+
+  auto* call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
+  const ast::Function* f = Func("test_function", {}, ty.void_(), {call},
+                                {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  const sem::Function* sf = Sem().Get(f);
+  auto pairs = sf->TextureSamplerPairs();
+  ASSERT_EQ(pairs.size(), 1u);
+  EXPECT_TRUE(pairs[0].first != nullptr);
+  EXPECT_TRUE(pairs[0].second != nullptr);
+}
+
+TEST_F(ResolverTest, TextureSampler_TextureSampleInFunction) {
+  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 1));
+  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+
+  auto* inner_call =
+      CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
+  const ast::Function* inner_func =
+      Func("inner_func", {}, ty.void_(), {inner_call});
+  auto* outer_call = CallStmt(Call("inner_func"));
+  const ast::Function* outer_func =
+      Func("outer_func", {}, ty.void_(), {outer_call},
+           {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto inner_pairs = Sem().Get(inner_func)->TextureSamplerPairs();
+  ASSERT_EQ(inner_pairs.size(), 1u);
+  EXPECT_TRUE(inner_pairs[0].first != nullptr);
+  EXPECT_TRUE(inner_pairs[0].second != nullptr);
+
+  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
+  ASSERT_EQ(outer_pairs.size(), 1u);
+  EXPECT_TRUE(outer_pairs[0].first != nullptr);
+  EXPECT_TRUE(outer_pairs[0].second != nullptr);
+}
+
+TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondSameVariables) {
+  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 1));
+  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+
+  auto* inner_call_1 =
+      CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
+  const ast::Function* inner_func_1 =
+      Func("inner_func_1", {}, ty.void_(), {inner_call_1});
+  auto* inner_call_2 =
+      CallStmt(Call("textureSample", "t", "s", vec2<f32>(3.0f, 4.0f)));
+  const ast::Function* inner_func_2 =
+      Func("inner_func_2", {}, ty.void_(), {inner_call_2});
+  auto* outer_call_1 = CallStmt(Call("inner_func_1"));
+  auto* outer_call_2 = CallStmt(Call("inner_func_2"));
+  const ast::Function* outer_func =
+      Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
+           {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
+  ASSERT_EQ(inner_pairs_1.size(), 1u);
+  EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
+  EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
+
+  auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
+  ASSERT_EQ(inner_pairs_1.size(), 1u);
+  EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
+  EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
+
+  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
+  ASSERT_EQ(outer_pairs.size(), 1u);
+  EXPECT_TRUE(outer_pairs[0].first != nullptr);
+  EXPECT_TRUE(outer_pairs[0].second != nullptr);
+}
+
+TEST_F(ResolverTest,
+       TextureSampler_TextureSampleFunctionDiamondDifferentVariables) {
+  Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 1));
+  Global("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 2));
+  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 3));
+
+  auto* inner_call_1 =
+      CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1.0f, 2.0f)));
+  const ast::Function* inner_func_1 =
+      Func("inner_func_1", {}, ty.void_(), {inner_call_1});
+  auto* inner_call_2 =
+      CallStmt(Call("textureSample", "t2", "s", vec2<f32>(3.0f, 4.0f)));
+  const ast::Function* inner_func_2 =
+      Func("inner_func_2", {}, ty.void_(), {inner_call_2});
+  auto* outer_call_1 = CallStmt(Call("inner_func_1"));
+  auto* outer_call_2 = CallStmt(Call("inner_func_2"));
+  const ast::Function* outer_func =
+      Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
+           {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
+  ASSERT_EQ(inner_pairs_1.size(), 1u);
+  EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
+  EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
+
+  auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
+  ASSERT_EQ(inner_pairs_2.size(), 1u);
+  EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
+  EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
+
+  auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
+  ASSERT_EQ(outer_pairs.size(), 2u);
+  EXPECT_TRUE(outer_pairs[0].first == inner_pairs_1[0].first);
+  EXPECT_TRUE(outer_pairs[0].second == inner_pairs_1[0].second);
+  EXPECT_TRUE(outer_pairs[1].first == inner_pairs_2[0].first);
+  EXPECT_TRUE(outer_pairs[1].second == inner_pairs_2[0].second);
+}
+
+TEST_F(ResolverTest, TextureSampler_TextureDimensions) {
+  Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+         GroupAndBinding(1, 2));
+
+  auto* call = Call("textureDimensions", "t");
+  const ast::Function* f = WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  const sem::Function* sf = Sem().Get(f);
+  auto pairs = sf->TextureSamplerPairs();
+  ASSERT_EQ(pairs.size(), 1u);
+  EXPECT_TRUE(pairs[0].first != nullptr);
+  EXPECT_TRUE(pairs[0].second == nullptr);
+}
+
+TEST_F(ResolverTest, ModuleDependencyOrderedDeclarations) {
+  auto* f0 = Func("f0", {}, ty.void_(), {});
+  auto* v0 = Global("v0", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a0 = Alias("a0", ty.i32());
+  auto* s0 = Structure("s0", {Member("m", ty.i32())});
+  auto* f1 = Func("f1", {}, ty.void_(), {});
+  auto* v1 = Global("v1", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a1 = Alias("a1", ty.i32());
+  auto* s1 = Structure("s1", {Member("m", ty.i32())});
+  auto* f2 = Func("f2", {}, ty.void_(), {});
+  auto* v2 = Global("v2", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a2 = Alias("a2", ty.i32());
+  auto* s2 = Structure("s2", {Member("m", ty.i32())});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(Sem().Module(), nullptr);
+  EXPECT_THAT(Sem().Module()->DependencyOrderedDeclarations(),
+              ElementsAre(f0, v0, a0, s0, f1, v1, a1, s1, f2, v2, a2, s2));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver_test_helper.cc b/src/tint/resolver/resolver_test_helper.cc
new file mode 100644
index 0000000..aea14cf
--- /dev/null
+++ b/src/tint/resolver/resolver_test_helper.cc
@@ -0,0 +1,27 @@
+// 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/resolver/resolver_test_helper.h"
+
+#include <memory>
+
+namespace tint {
+namespace resolver {
+
+TestHelper::TestHelper() : resolver_(std::make_unique<Resolver>(this)) {}
+
+TestHelper::~TestHelper() = default;
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
new file mode 100644
index 0000000..b128a77
--- /dev/null
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -0,0 +1,489 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
+#define SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace resolver {
+
+/// Helper class for testing
+class TestHelper : public ProgramBuilder {
+ public:
+  /// Constructor
+  TestHelper();
+
+  /// Destructor
+  ~TestHelper() override;
+
+  /// @return a pointer to the Resolver
+  Resolver* r() const { return resolver_.get(); }
+
+  /// Returns the statement that holds the given expression.
+  /// @param expr the ast::Expression
+  /// @return the ast::Statement of the ast::Expression, or nullptr if the
+  /// expression is not owned by a statement.
+  const ast::Statement* StmtOf(const ast::Expression* expr) {
+    auto* sem_stmt = Sem().Get(expr)->Stmt();
+    return sem_stmt ? sem_stmt->Declaration() : nullptr;
+  }
+
+  /// Returns the BlockStatement that holds the given statement.
+  /// @param stmt the ast::Statement
+  /// @return the ast::BlockStatement that holds the ast::Statement, or nullptr
+  /// if the statement is not owned by a BlockStatement.
+  const ast::BlockStatement* BlockOf(const ast::Statement* stmt) {
+    auto* sem_stmt = Sem().Get(stmt);
+    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
+  }
+
+  /// Returns the BlockStatement that holds the given expression.
+  /// @param expr the ast::Expression
+  /// @return the ast::Statement of the ast::Expression, or nullptr if the
+  /// expression is not indirectly owned by a BlockStatement.
+  const ast::BlockStatement* BlockOf(const ast::Expression* expr) {
+    auto* sem_stmt = Sem().Get(expr)->Stmt();
+    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
+  }
+
+  /// Returns the semantic variable for the given identifier expression.
+  /// @param expr the identifier expression
+  /// @return the resolved sem::Variable of the identifier, or nullptr if
+  /// the expression did not resolve to a variable.
+  const sem::Variable* VarOf(const ast::Expression* expr) {
+    auto* sem_ident = Sem().Get(expr);
+    auto* var_user = sem_ident ? sem_ident->As<sem::VariableUser>() : nullptr;
+    return var_user ? var_user->Variable() : nullptr;
+  }
+
+  /// Checks that all the users of the given variable are as expected
+  /// @param var the variable to check
+  /// @param expected_users the expected users of the variable
+  /// @return true if all users are as expected
+  bool CheckVarUsers(const ast::Variable* var,
+                     std::vector<const ast::Expression*>&& expected_users) {
+    auto& var_users = Sem().Get(var)->Users();
+    if (var_users.size() != expected_users.size()) {
+      return false;
+    }
+    for (size_t i = 0; i < var_users.size(); i++) {
+      if (var_users[i]->Declaration() != expected_users[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /// @param type a type
+  /// @returns the name for `type` that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const ast::Type* type) {
+    return type->FriendlyName(Symbols());
+  }
+
+  /// @param type a type
+  /// @returns the name for `type` that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const sem::Type* type) {
+    return type->FriendlyName(Symbols());
+  }
+
+ private:
+  std::unique_ptr<Resolver> resolver_;
+};
+
+class ResolverTest : public TestHelper, public testing::Test {};
+
+template <typename T>
+class ResolverTestWithParam : public TestHelper,
+                              public testing::TestWithParam<T> {};
+
+namespace builder {
+
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+template <int N, typename T>
+struct vec {};
+
+template <typename T>
+using vec2 = vec<2, T>;
+
+template <typename T>
+using vec3 = vec<3, T>;
+
+template <typename T>
+using vec4 = vec<4, T>;
+
+template <int N, int M, typename T>
+struct mat {};
+
+template <typename T>
+using mat2x2 = mat<2, 2, T>;
+
+template <typename T>
+using mat2x3 = mat<2, 3, T>;
+
+template <typename T>
+using mat3x2 = mat<3, 2, T>;
+
+template <typename T>
+using mat3x3 = mat<3, 3, T>;
+
+template <typename T>
+using mat4x4 = mat<4, 4, T>;
+
+template <int N, typename T>
+struct array {};
+
+template <typename TO, int ID = 0>
+struct alias {};
+
+template <typename TO>
+using alias1 = alias<TO, 1>;
+
+template <typename TO>
+using alias2 = alias<TO, 2>;
+
+template <typename TO>
+using alias3 = alias<TO, 3>;
+
+template <typename TO>
+struct ptr {};
+
+using ast_type_func_ptr = const ast::Type* (*)(ProgramBuilder& b);
+using ast_expr_func_ptr = const ast::Expression* (*)(ProgramBuilder& b,
+                                                     int elem_value);
+using sem_type_func_ptr = const sem::Type* (*)(ProgramBuilder& b);
+
+template <typename T>
+struct DataType {};
+
+/// Helper for building bool types and expressions
+template <>
+struct DataType<bool> {
+  /// false as bool is not a composite type
+  static constexpr bool is_composite = false;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST bool type
+  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); }
+  /// @param b the ProgramBuilder
+  /// @return the semantic bool type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::Bool>();
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the b
+  /// @return a new AST expression of the bool type
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Expr(elem_value == 0);
+  }
+};
+
+/// Helper for building i32 types and expressions
+template <>
+struct DataType<i32> {
+  /// false as i32 is not a composite type
+  static constexpr bool is_composite = false;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST i32 type
+  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); }
+  /// @param b the ProgramBuilder
+  /// @return the semantic i32 type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::I32>();
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value i32 will be initialized with
+  /// @return a new AST i32 literal value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Expr(static_cast<i32>(elem_value));
+  }
+};
+
+/// Helper for building u32 types and expressions
+template <>
+struct DataType<u32> {
+  /// false as u32 is not a composite type
+  static constexpr bool is_composite = false;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST u32 type
+  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); }
+  /// @param b the ProgramBuilder
+  /// @return the semantic u32 type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::U32>();
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value u32 will be initialized with
+  /// @return a new AST u32 literal value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Expr(static_cast<u32>(elem_value));
+  }
+};
+
+/// Helper for building f32 types and expressions
+template <>
+struct DataType<f32> {
+  /// false as f32 is not a composite type
+  static constexpr bool is_composite = false;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST f32 type
+  static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); }
+  /// @param b the ProgramBuilder
+  /// @return the semantic f32 type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::F32>();
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value f32 will be initialized with
+  /// @return a new AST f32 literal value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Expr(static_cast<f32>(elem_value));
+  }
+};
+
+/// Helper for building vector types and expressions
+template <int N, typename T>
+struct DataType<vec<N, T>> {
+  /// true as vectors are a composite type
+  static constexpr bool is_composite = true;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST vector type
+  static inline const ast::Type* AST(ProgramBuilder& b) {
+    return b.ty.vec(DataType<T>::AST(b), N);
+  }
+  /// @param b the ProgramBuilder
+  /// @return the semantic vector type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::Vector>(DataType<T>::Sem(b), N);
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element in the vector will be initialized
+  /// with
+  /// @return a new AST vector value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Construct(AST(b), ExprArgs(b, elem_value));
+  }
+
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element will be initialized with
+  /// @return the list of expressions that are used to construct the vector
+  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
+                                             int elem_value) {
+    ast::ExpressionList args;
+    for (int i = 0; i < N; i++) {
+      args.emplace_back(DataType<T>::Expr(b, elem_value));
+    }
+    return args;
+  }
+};
+
+/// Helper for building matrix types and expressions
+template <int N, int M, typename T>
+struct DataType<mat<N, M, T>> {
+  /// true as matrices are a composite type
+  static constexpr bool is_composite = true;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST matrix type
+  static inline const ast::Type* AST(ProgramBuilder& b) {
+    return b.ty.mat(DataType<T>::AST(b), N, M);
+  }
+  /// @param b the ProgramBuilder
+  /// @return the semantic matrix type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    auto* column_type = b.create<sem::Vector>(DataType<T>::Sem(b), M);
+    return b.create<sem::Matrix>(column_type, N);
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element in the matrix will be initialized
+  /// with
+  /// @return a new AST matrix value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Construct(AST(b), ExprArgs(b, elem_value));
+  }
+
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element will be initialized with
+  /// @return the list of expressions that are used to construct the matrix
+  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
+                                             int elem_value) {
+    ast::ExpressionList args;
+    for (int i = 0; i < N; i++) {
+      args.emplace_back(DataType<vec<M, T>>::Expr(b, elem_value));
+    }
+    return args;
+  }
+};
+
+/// Helper for building alias types and expressions
+template <typename T, int ID>
+struct DataType<alias<T, ID>> {
+  /// true if the aliased type is a composite type
+  static constexpr bool is_composite = DataType<T>::is_composite;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST alias type
+  static inline const ast::Type* AST(ProgramBuilder& b) {
+    auto name = b.Symbols().Register("alias_" + std::to_string(ID));
+    if (!b.AST().LookupType(name)) {
+      auto* type = DataType<T>::AST(b);
+      b.AST().AddTypeDecl(b.ty.alias(name, type));
+    }
+    return b.create<ast::TypeName>(name);
+  }
+  /// @param b the ProgramBuilder
+  /// @return the semantic aliased type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return DataType<T>::Sem(b);
+  }
+
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value nested elements will be initialized with
+  /// @return a new AST expression of the alias type
+  template <bool IS_COMPOSITE = is_composite>
+  static inline traits::EnableIf<!IS_COMPOSITE, const ast::Expression*> Expr(
+      ProgramBuilder& b,
+      int elem_value) {
+    // Cast
+    return b.Construct(AST(b), DataType<T>::Expr(b, elem_value));
+  }
+
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value nested elements will be initialized with
+  /// @return a new AST expression of the alias type
+  template <bool IS_COMPOSITE = is_composite>
+  static inline traits::EnableIf<IS_COMPOSITE, const ast::Expression*> Expr(
+      ProgramBuilder& b,
+      int elem_value) {
+    // Construct
+    return b.Construct(AST(b), DataType<T>::ExprArgs(b, elem_value));
+  }
+};
+
+/// Helper for building pointer types and expressions
+template <typename T>
+struct DataType<ptr<T>> {
+  /// true if the pointer type is a composite type
+  static constexpr bool is_composite = false;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST alias type
+  static inline const ast::Type* AST(ProgramBuilder& b) {
+    return b.create<ast::Pointer>(DataType<T>::AST(b),
+                                  ast::StorageClass::kPrivate,
+                                  ast::Access::kReadWrite);
+  }
+  /// @param b the ProgramBuilder
+  /// @return the semantic aliased type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    return b.create<sem::Pointer>(DataType<T>::Sem(b),
+                                  ast::StorageClass::kPrivate,
+                                  ast::Access::kReadWrite);
+  }
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST expression of the alias type
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int /*unused*/) {
+    auto sym = b.Symbols().New("global_for_ptr");
+    b.Global(sym, DataType<T>::AST(b), ast::StorageClass::kPrivate);
+    return b.AddressOf(sym);
+  }
+};
+
+/// Helper for building array types and expressions
+template <int N, typename T>
+struct DataType<array<N, T>> {
+  /// true as arrays are a composite type
+  static constexpr bool is_composite = true;
+
+  /// @param b the ProgramBuilder
+  /// @return a new AST array type
+  static inline const ast::Type* AST(ProgramBuilder& b) {
+    return b.ty.array(DataType<T>::AST(b), N);
+  }
+  /// @param b the ProgramBuilder
+  /// @return the semantic array type
+  static inline const sem::Type* Sem(ProgramBuilder& b) {
+    auto* el = DataType<T>::Sem(b);
+    return b.create<sem::Array>(
+        /* element */ el,
+        /* count */ N,
+        /* align */ el->Align(),
+        /* size */ el->Size(),
+        /* stride */ el->Align(),
+        /* implicit_stride */ el->Align());
+  }
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element in the array will be initialized
+  /// with
+  /// @return a new AST array value expression
+  static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
+    return b.Construct(AST(b), ExprArgs(b, elem_value));
+  }
+
+  /// @param b the ProgramBuilder
+  /// @param elem_value the value each element will be initialized with
+  /// @return the list of expressions that are used to construct the array
+  static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
+                                             int elem_value) {
+    ast::ExpressionList args;
+    for (int i = 0; i < N; i++) {
+      args.emplace_back(DataType<T>::Expr(b, elem_value));
+    }
+    return args;
+  }
+};
+
+/// Struct of all creation pointer types
+struct CreatePtrs {
+  /// ast node type create function
+  ast_type_func_ptr ast;
+  /// ast expression type create function
+  ast_expr_func_ptr expr;
+  /// sem type create function
+  sem_type_func_ptr sem;
+};
+
+/// Returns a CreatePtrs struct instance with all creation pointer types for
+/// type `T`
+template <typename T>
+constexpr CreatePtrs CreatePtrsFor() {
+  return {DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem};
+}
+
+}  // namespace builder
+
+}  // namespace resolver
+}  // namespace tint
+
+#endif  // SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
diff --git a/src/tint/resolver/resolver_validation.cc b/src/tint/resolver/resolver_validation.cc
new file mode 100644
index 0000000..a23079d
--- /dev/null
+++ b/src/tint/resolver/resolver_validation.cc
@@ -0,0 +1,2369 @@
+// Copyright 2020 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/resolver/resolver.h"
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/depth_texture.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/sampled_texture.h"
+#include "src/tint/ast/sampler.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/loop_statement.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/pointer_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/reverse.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/transform.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+bool IsValidStorageTextureDimension(ast::TextureDimension dim) {
+  switch (dim) {
+    case ast::TextureDimension::k1d:
+    case ast::TextureDimension::k2d:
+    case ast::TextureDimension::k2dArray:
+    case ast::TextureDimension::k3d:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool IsValidStorageTextureTexelFormat(ast::TexelFormat format) {
+  switch (format) {
+    case ast::TexelFormat::kR32Uint:
+    case ast::TexelFormat::kR32Sint:
+    case ast::TexelFormat::kR32Float:
+    case ast::TexelFormat::kRg32Uint:
+    case ast::TexelFormat::kRg32Sint:
+    case ast::TexelFormat::kRg32Float:
+    case ast::TexelFormat::kRgba8Unorm:
+    case ast::TexelFormat::kRgba8Snorm:
+    case ast::TexelFormat::kRgba8Uint:
+    case ast::TexelFormat::kRgba8Sint:
+    case ast::TexelFormat::kRgba16Uint:
+    case ast::TexelFormat::kRgba16Sint:
+    case ast::TexelFormat::kRgba16Float:
+    case ast::TexelFormat::kRgba32Uint:
+    case ast::TexelFormat::kRgba32Sint:
+    case ast::TexelFormat::kRgba32Float:
+      return true;
+    default:
+      return false;
+  }
+}
+
+// Helper to stringify a pipeline IO attribute.
+std::string attr_to_str(const ast::Attribute* attr) {
+  std::stringstream str;
+  if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
+    str << "builtin(" << builtin->builtin << ")";
+  } else if (auto* location = attr->As<ast::LocationAttribute>()) {
+    str << "location(" << location->value << ")";
+  }
+  return str.str();
+}
+
+template <typename CALLBACK>
+void TraverseCallChain(diag::List& diagnostics,
+                       const sem::Function* from,
+                       const sem::Function* to,
+                       CALLBACK&& callback) {
+  for (auto* f : from->TransitivelyCalledFunctions()) {
+    if (f == to) {
+      callback(f);
+      return;
+    }
+    if (f->TransitivelyCalledFunctions().contains(to)) {
+      TraverseCallChain(diagnostics, f, to, callback);
+      callback(f);
+      return;
+    }
+  }
+  TINT_ICE(Resolver, diagnostics)
+      << "TraverseCallChain() 'from' does not transitively call 'to'";
+}
+
+}  // namespace
+
+bool Resolver::ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
+  // T must be either u32 or i32.
+  if (!s->Type()->IsAnyOf<sem::U32, sem::I32>()) {
+    AddError("atomic only supports i32 or u32 types",
+             a->type ? a->type->source : a->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateStorageTexture(const ast::StorageTexture* t) {
+  switch (t->access) {
+    case ast::Access::kWrite:
+      break;
+    case ast::Access::kUndefined:
+      AddError("storage texture missing access control", t->source);
+      return false;
+    default:
+      AddError("storage textures currently only support 'write' access control",
+               t->source);
+      return false;
+  }
+
+  if (!IsValidStorageTextureDimension(t->dim)) {
+    AddError("cube dimensions for storage textures are not supported",
+             t->source);
+    return false;
+  }
+
+  if (!IsValidStorageTextureTexelFormat(t->format)) {
+    AddError(
+        "image format must be one of the texel formats specified for storage "
+        "textues in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats",
+        t->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateVariableConstructorOrCast(
+    const ast::Variable* var,
+    ast::StorageClass storage_class,
+    const sem::Type* storage_ty,
+    const sem::Type* rhs_ty) {
+  auto* value_type = rhs_ty->UnwrapRef();  // Implicit load of RHS
+
+  // Value type has to match storage type
+  if (storage_ty != value_type) {
+    std::string decl = var->is_const ? "let" : "var";
+    AddError("cannot initialize " + decl + " of type '" +
+                 TypeNameOf(storage_ty) + "' with value of type '" +
+                 TypeNameOf(rhs_ty) + "'",
+             var->source);
+    return false;
+  }
+
+  if (!var->is_const) {
+    switch (storage_class) {
+      case ast::StorageClass::kPrivate:
+      case ast::StorageClass::kFunction:
+        break;  // Allowed an initializer
+      default:
+        // https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
+        // Optionally has an initializer expression, if the variable is in the
+        // private or function storage classes.
+        AddError("var of storage class '" +
+                     std::string(ast::ToString(storage_class)) +
+                     "' cannot have an initializer. var initializers are only "
+                     "supported for the storage classes "
+                     "'private' and 'function'",
+                 var->source);
+        return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateStorageClassLayout(const sem::Type* store_ty,
+                                          ast::StorageClass sc,
+                                          Source source) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class-layout-constraints
+
+  auto is_uniform_struct_or_array = [sc](const sem::Type* ty) {
+    return sc == ast::StorageClass::kUniform &&
+           ty->IsAnyOf<sem::Array, sem::Struct>();
+  };
+
+  auto is_uniform_struct = [sc](const sem::Type* ty) {
+    return sc == ast::StorageClass::kUniform && ty->Is<sem::Struct>();
+  };
+
+  auto required_alignment_of = [&](const sem::Type* ty) {
+    uint32_t actual_align = ty->Align();
+    uint32_t required_align = actual_align;
+    if (is_uniform_struct_or_array(ty)) {
+      required_align = utils::RoundUp(16u, actual_align);
+    }
+    return required_align;
+  };
+
+  auto member_name_of = [this](const sem::StructMember* sm) {
+    return builder_->Symbols().NameFor(sm->Declaration()->symbol);
+  };
+
+  // Cache result of type + storage class pair.
+  if (!valid_type_storage_layouts_.emplace(store_ty, sc).second) {
+    return true;
+  }
+
+  if (!ast::IsHostShareable(sc)) {
+    return true;
+  }
+
+  if (auto* str = store_ty->As<sem::Struct>()) {
+    for (size_t i = 0; i < str->Members().size(); ++i) {
+      auto* const m = str->Members()[i];
+      uint32_t required_align = required_alignment_of(m->Type());
+
+      // Recurse into the member type.
+      if (!ValidateStorageClassLayout(m->Type(), sc,
+                                      m->Declaration()->type->source)) {
+        AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
+                str->Declaration()->source);
+        return false;
+      }
+
+      // Validate that member is at a valid byte offset
+      if (m->Offset() % required_align != 0) {
+        AddError("the offset of a struct member of type '" +
+                     m->Type()->UnwrapRef()->FriendlyName(builder_->Symbols()) +
+                     "' in storage class '" + ast::ToString(sc) +
+                     "' must be a multiple of " +
+                     std::to_string(required_align) + " 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);
+
+        AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
+                str->Declaration()->source);
+
+        if (auto* member_str = m->Type()->As<sem::Struct>()) {
+          AddNote("and layout of struct member:\n" +
+                      member_str->Layout(builder_->Symbols()),
+                  member_str->Declaration()->source);
+        }
+
+        return false;
+      }
+
+      // For uniform buffers, validate that the number of bytes between the
+      // previous member of type struct and the current is a multiple of 16
+      // bytes.
+      auto* const prev_member = (i == 0) ? nullptr : str->Members()[i - 1];
+      if (prev_member && is_uniform_struct(prev_member->Type())) {
+        const uint32_t prev_to_curr_offset =
+            m->Offset() - prev_member->Offset();
+        if (prev_to_curr_offset % 16 != 0) {
+          AddError(
+              "uniform storage requires that the number of bytes between the "
+              "start of the previous member of type struct and the current "
+              "member be a multiple of 16 bytes, but there are currently " +
+                  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);
+
+          AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
+                  str->Declaration()->source);
+
+          auto* prev_member_str = prev_member->Type()->As<sem::Struct>();
+          AddNote("and layout of previous member struct:\n" +
+                      prev_member_str->Layout(builder_->Symbols()),
+                  prev_member_str->Declaration()->source);
+          return false;
+        }
+      }
+    }
+  }
+
+  // For uniform buffer array members, validate that array elements are
+  // aligned to 16 bytes
+  if (auto* arr = store_ty->As<sem::Array>()) {
+    // Recurse into the element type.
+    // TODO(crbug.com/tint/1388): Ideally we'd pass the source for nested
+    // element type here, but we can't easily get that from the semantic node.
+    // We should consider recursing through the AST type nodes instead.
+    if (!ValidateStorageClassLayout(arr->ElemType(), sc, source)) {
+      return false;
+    }
+
+    if (sc == ast::StorageClass::kUniform) {
+      // We already validated that this array member is itself aligned to 16
+      // bytes above, so we only need to validate that stride is a multiple
+      // of 16 bytes.
+      if (arr->Stride() % 16 != 0) {
+        // Since WGSL has no stride attribute, try to provide a useful hint
+        // for how the shader author can resolve the issue.
+        std::string hint;
+        if (arr->ElemType()->is_scalar()) {
+          hint =
+              "Consider using a vector or struct as the element type "
+              "instead.";
+        } else if (auto* vec = arr->ElemType()->As<sem::Vector>();
+                   vec && vec->type()->Size() == 4) {
+          hint = "Consider using a vec4 instead.";
+        } else if (arr->ElemType()->Is<sem::Struct>()) {
+          hint =
+              "Consider using the @size attribute on the last struct "
+              "member.";
+        } else {
+          hint =
+              "Consider wrapping the element type in a struct and using "
+              "the "
+              "@size attribute.";
+        }
+        AddError(
+            "uniform storage requires that array elements be aligned to 16 "
+            "bytes, but array element alignment is currently " +
+                std::to_string(arr->Stride()) + ". " + hint,
+            source);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateStorageClassLayout(const sem::Variable* var) {
+  if (auto* str = var->Type()->UnwrapRef()->As<sem::Struct>()) {
+    if (!ValidateStorageClassLayout(str, var->StorageClass(),
+                                    str->Declaration()->source)) {
+      AddNote("see declaration of variable", var->Declaration()->source);
+      return false;
+    }
+  } else {
+    Source source = var->Declaration()->source;
+    if (var->Declaration()->type) {
+      source = var->Declaration()->type->source;
+    }
+    if (!ValidateStorageClassLayout(var->Type()->UnwrapRef(),
+                                    var->StorageClass(), source)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateGlobalVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  if (!ValidateNoDuplicateAttributes(decl->attributes)) {
+    return false;
+  }
+
+  for (auto* attr : decl->attributes) {
+    if (decl->is_const) {
+      if (auto* id_attr = attr->As<ast::IdAttribute>()) {
+        uint32_t id = id_attr->value;
+        auto it = constant_ids_.find(id);
+        if (it != constant_ids_.end() && it->second != var) {
+          AddError("pipeline constant IDs must be unique", attr->source);
+          AddNote("a pipeline constant with an ID of " + std::to_string(id) +
+                      " was previously declared "
+                      "here:",
+                  ast::GetAttribute<ast::IdAttribute>(
+                      it->second->Declaration()->attributes)
+                      ->source);
+          return false;
+        }
+        if (id > 65535) {
+          AddError("pipeline constant IDs must be between 0 and 65535",
+                   attr->source);
+          return false;
+        }
+      } else {
+        AddError("attribute is not valid for constants", attr->source);
+        return false;
+      }
+    } else {
+      bool is_shader_io_attribute =
+          attr->IsAnyOf<ast::BuiltinAttribute, ast::InterpolateAttribute,
+                        ast::InvariantAttribute, ast::LocationAttribute>();
+      bool has_io_storage_class =
+          var->StorageClass() == ast::StorageClass::kInput ||
+          var->StorageClass() == ast::StorageClass::kOutput;
+      if (!(attr->IsAnyOf<ast::BindingAttribute, ast::GroupAttribute,
+                          ast::InternalAttribute>()) &&
+          (!is_shader_io_attribute || !has_io_storage_class)) {
+        AddError("attribute is not valid for variables", attr->source);
+        return false;
+      }
+    }
+  }
+
+  if (var->StorageClass() == ast::StorageClass::kFunction) {
+    AddError(
+        "variables declared at module scope must not be in the function "
+        "storage class",
+        decl->source);
+    return false;
+  }
+
+  auto binding_point = decl->BindingPoint();
+  switch (var->StorageClass()) {
+    case ast::StorageClass::kUniform:
+    case ast::StorageClass::kStorage:
+    case ast::StorageClass::kUniformConstant: {
+      // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
+      // Each resource variable must be declared with both group and binding
+      // attributes.
+      if (!binding_point) {
+        AddError(
+            "resource variables require @group and @binding "
+            "attributes",
+            decl->source);
+        return false;
+      }
+      break;
+    }
+    default:
+      if (binding_point.binding || binding_point.group) {
+        // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
+        // Must only be applied to a resource variable
+        AddError(
+            "non-resource variables must not have @group or @binding "
+            "attributes",
+            decl->source);
+        return false;
+      }
+  }
+
+  // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
+  // The access mode always has a default, and except for variables in the
+  // storage storage class, must not be written.
+  if (var->StorageClass() != ast::StorageClass::kStorage &&
+      decl->declared_access != ast::Access::kUndefined) {
+    AddError(
+        "only variables in <storage> storage class may declare an access mode",
+        decl->source);
+    return false;
+  }
+
+  if (!decl->is_const) {
+    if (!ValidateAtomicVariable(var)) {
+      return false;
+    }
+  }
+
+  return ValidateVariable(var);
+}
+
+// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
+// Atomic types may only be instantiated by variables in the workgroup storage
+// class or by storage buffer variables with a read_write access mode.
+bool Resolver::ValidateAtomicVariable(const sem::Variable* var) {
+  auto sc = var->StorageClass();
+  auto* decl = var->Declaration();
+  auto access = var->Access();
+  auto* type = var->Type()->UnwrapRef();
+  auto source = decl->type ? decl->type->source : decl->source;
+
+  if (type->Is<sem::Atomic>()) {
+    if (sc != ast::StorageClass::kWorkgroup) {
+      AddError(
+          "atomic variables must have <storage> or <workgroup> storage class",
+          source);
+      return false;
+    }
+  } else if (type->IsAnyOf<sem::Struct, sem::Array>()) {
+    auto found = atomic_composite_info_.find(type);
+    if (found != atomic_composite_info_.end()) {
+      if (sc != ast::StorageClass::kStorage &&
+          sc != ast::StorageClass::kWorkgroup) {
+        AddError(
+            "atomic variables must have <storage> or <workgroup> storage class",
+            source);
+        AddNote(
+            "atomic sub-type of '" + TypeNameOf(type) + "' is declared here",
+            found->second);
+        return false;
+      } else if (sc == ast::StorageClass::kStorage &&
+                 access != ast::Access::kReadWrite) {
+        AddError(
+            "atomic variables in <storage> storage class must have read_write "
+            "access mode",
+            source);
+        AddNote(
+            "atomic sub-type of '" + TypeNameOf(type) + "' is declared here",
+            found->second);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto* storage_ty = var->Type()->UnwrapRef();
+
+  if (var->Is<sem::GlobalVariable>()) {
+    auto name = builder_->Symbols().NameFor(decl->symbol);
+    if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
+      auto* kind = var->Declaration()->is_const ? "let" : "var";
+      AddError(
+          "'" + name +
+              "' is a builtin and cannot be redeclared as a module-scope " +
+              kind,
+          decl->source);
+      return false;
+    }
+  }
+
+  if (!decl->is_const && !IsStorable(storage_ty)) {
+    AddError(TypeNameOf(storage_ty) + " cannot be used as the type of a var",
+             decl->source);
+    return false;
+  }
+
+  if (decl->is_const && !var->Is<sem::Parameter>() &&
+      !(storage_ty->IsConstructible() || storage_ty->Is<sem::Pointer>())) {
+    AddError(TypeNameOf(storage_ty) + " cannot be used as the type of a let",
+             decl->source);
+    return false;
+  }
+
+  if (auto* r = storage_ty->As<sem::MultisampledTexture>()) {
+    if (r->dim() != ast::TextureDimension::k2d) {
+      AddError("only 2d multisampled textures are supported", decl->source);
+      return false;
+    }
+
+    if (!r->type()->UnwrapRef()->is_numeric_scalar()) {
+      AddError("texture_multisampled_2d<type>: type must be f32, i32 or u32",
+               decl->source);
+      return false;
+    }
+  }
+
+  if (var->Is<sem::LocalVariable>() && !decl->is_const &&
+      IsValidationEnabled(decl->attributes,
+                          ast::DisabledValidation::kIgnoreStorageClass)) {
+    if (!var->Type()->UnwrapRef()->IsConstructible()) {
+      AddError("function variable must have a constructible type",
+               decl->type ? decl->type->source : decl->source);
+      return false;
+    }
+  }
+
+  if (storage_ty->is_handle() &&
+      decl->declared_storage_class != ast::StorageClass::kNone) {
+    // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
+    // If the store type is a texture type or a sampler type, then the
+    // variable declaration must not have a storage class attribute. The
+    // storage class will always be handle.
+    AddError("variables of type '" + TypeNameOf(storage_ty) +
+                 "' must not have a storage class",
+             decl->source);
+    return false;
+  }
+
+  if (IsValidationEnabled(decl->attributes,
+                          ast::DisabledValidation::kIgnoreStorageClass) &&
+      (decl->declared_storage_class == ast::StorageClass::kInput ||
+       decl->declared_storage_class == ast::StorageClass::kOutput)) {
+    AddError("invalid use of input/output storage class", decl->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateFunctionParameter(const ast::Function* func,
+                                         const sem::Variable* var) {
+  if (!ValidateVariable(var)) {
+    return false;
+  }
+
+  auto* decl = var->Declaration();
+
+  for (auto* attr : decl->attributes) {
+    if (!func->IsEntryPoint() && !attr->Is<ast::InternalAttribute>()) {
+      AddError("attribute is not valid for non-entry point function parameters",
+               attr->source);
+      return false;
+    } else if (!attr->IsAnyOf<ast::BuiltinAttribute, ast::InvariantAttribute,
+                              ast::LocationAttribute, ast::InterpolateAttribute,
+                              ast::InternalAttribute>() &&
+               (IsValidationEnabled(
+                    decl->attributes,
+                    ast::DisabledValidation::kEntryPointParameter) &&
+                IsValidationEnabled(
+                    decl->attributes,
+                    ast::DisabledValidation::
+                        kIgnoreConstructibleFunctionParameter))) {
+      AddError("attribute is not valid for function parameters", attr->source);
+      return false;
+    }
+  }
+
+  if (auto* ref = var->Type()->As<sem::Pointer>()) {
+    auto sc = ref->StorageClass();
+    if (!(sc == ast::StorageClass::kFunction ||
+          sc == ast::StorageClass::kPrivate ||
+          sc == ast::StorageClass::kWorkgroup) &&
+        IsValidationEnabled(decl->attributes,
+                            ast::DisabledValidation::kIgnoreStorageClass)) {
+      std::stringstream ss;
+      ss << "function parameter of pointer type cannot be in '" << sc
+         << "' storage class";
+      AddError(ss.str(), decl->source);
+      return false;
+    }
+  }
+
+  if (IsPlain(var->Type())) {
+    if (!var->Type()->IsConstructible() &&
+        IsValidationEnabled(
+            decl->attributes,
+            ast::DisabledValidation::kIgnoreConstructibleFunctionParameter)) {
+      AddError("store type of function parameter must be a constructible type",
+               decl->source);
+      return false;
+    }
+  } else if (!var->Type()
+                  ->IsAnyOf<sem::Texture, sem::Sampler, sem::Pointer>()) {
+    AddError(
+        "store type of function parameter cannot be " + TypeNameOf(var->Type()),
+        decl->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateBuiltinAttribute(const ast::BuiltinAttribute* attr,
+                                        const sem::Type* storage_ty,
+                                        const bool is_input) {
+  auto* type = storage_ty->UnwrapRef();
+  const auto stage = current_function_
+                         ? current_function_->Declaration()->PipelineStage()
+                         : ast::PipelineStage::kNone;
+  std::stringstream stage_name;
+  stage_name << stage;
+  bool is_stage_mismatch = false;
+  bool is_output = !is_input;
+  switch (attr->builtin) {
+    case ast::Builtin::kPosition:
+      if (stage != ast::PipelineStage::kNone &&
+          !((is_input && stage == ast::PipelineStage::kFragment) ||
+            (is_output && stage == ast::PipelineStage::kVertex))) {
+        is_stage_mismatch = true;
+      }
+      if (!(type->is_float_vector() && type->As<sem::Vector>()->Width() == 4)) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'vec4<f32>'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kGlobalInvocationId:
+    case ast::Builtin::kLocalInvocationId:
+    case ast::Builtin::kNumWorkgroups:
+    case ast::Builtin::kWorkgroupId:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kCompute && is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!(type->is_unsigned_integer_vector() &&
+            type->As<sem::Vector>()->Width() == 3)) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'vec3<u32>'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kFragDepth:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kFragment && !is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::F32>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'f32'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kFrontFacing:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kFragment && is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::Bool>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'bool'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kLocalInvocationIndex:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kCompute && is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::U32>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kVertexIndex:
+    case ast::Builtin::kInstanceIndex:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kVertex && is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::U32>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kSampleMask:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kFragment)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::U32>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
+                 attr->source);
+        return false;
+      }
+      break;
+    case ast::Builtin::kSampleIndex:
+      if (stage != ast::PipelineStage::kNone &&
+          !(stage == ast::PipelineStage::kFragment && is_input)) {
+        is_stage_mismatch = true;
+      }
+      if (!type->Is<sem::U32>()) {
+        AddError("store type of " + attr_to_str(attr) + " must be 'u32'",
+                 attr->source);
+        return false;
+      }
+      break;
+    default:
+      break;
+  }
+
+  if (is_stage_mismatch) {
+    AddError(attr_to_str(attr) + " cannot be used in " +
+                 (is_input ? "input of " : "output of ") + stage_name.str() +
+                 " pipeline stage",
+             attr->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateInterpolateAttribute(
+    const ast::InterpolateAttribute* attr,
+    const sem::Type* storage_ty) {
+  auto* type = storage_ty->UnwrapRef();
+
+  if (type->is_integer_scalar_or_vector() &&
+      attr->type != ast::InterpolationType::kFlat) {
+    AddError(
+        "interpolation type must be 'flat' for integral user-defined IO types",
+        attr->source);
+    return false;
+  }
+
+  if (attr->type == ast::InterpolationType::kFlat &&
+      attr->sampling != ast::InterpolationSampling::kNone) {
+    AddError("flat interpolation attribute must not have a sampling parameter",
+             attr->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateFunction(const sem::Function* func) {
+  auto* decl = func->Declaration();
+
+  auto name = builder_->Symbols().NameFor(decl->symbol);
+  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
+    AddError(
+        "'" + name + "' is a builtin and cannot be redeclared as a function",
+        decl->source);
+    return false;
+  }
+
+  auto workgroup_attr_count = 0;
+  for (auto* attr : decl->attributes) {
+    if (attr->Is<ast::WorkgroupAttribute>()) {
+      workgroup_attr_count++;
+      if (decl->PipelineStage() != ast::PipelineStage::kCompute) {
+        AddError(
+            "the workgroup_size attribute is only valid for compute stages",
+            attr->source);
+        return false;
+      }
+    } else if (!attr->IsAnyOf<ast::StageAttribute, ast::InternalAttribute>()) {
+      AddError("attribute is not valid for functions", attr->source);
+      return false;
+    }
+  }
+
+  if (decl->params.size() > 255) {
+    AddError("functions may declare at most 255 parameters", decl->source);
+    return false;
+  }
+
+  for (size_t i = 0; i < decl->params.size(); i++) {
+    if (!ValidateFunctionParameter(decl, func->Parameters()[i])) {
+      return false;
+    }
+  }
+
+  if (!func->ReturnType()->Is<sem::Void>()) {
+    if (!func->ReturnType()->IsConstructible()) {
+      AddError("function return type must be a constructible type",
+               decl->return_type->source);
+      return false;
+    }
+
+    if (decl->body) {
+      sem::Behaviors behaviors{sem::Behavior::kNext};
+      if (auto* last = decl->body->Last()) {
+        behaviors = Sem(last)->Behaviors();
+      }
+      if (behaviors.Contains(sem::Behavior::kNext)) {
+        AddError("missing return at end of function", decl->source);
+        return false;
+      }
+    } else if (IsValidationEnabled(
+                   decl->attributes,
+                   ast::DisabledValidation::kFunctionHasNoBody)) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "Function " << builder_->Symbols().NameFor(decl->symbol)
+          << " has no body";
+    }
+
+    for (auto* attr : decl->return_type_attributes) {
+      if (!decl->IsEntryPoint()) {
+        AddError(
+            "attribute is not valid for non-entry point function return types",
+            attr->source);
+        return false;
+      }
+      if (!attr->IsAnyOf<ast::BuiltinAttribute, ast::InternalAttribute,
+                         ast::LocationAttribute, ast::InterpolateAttribute,
+                         ast::InvariantAttribute>() &&
+          (IsValidationEnabled(decl->attributes,
+                               ast::DisabledValidation::kEntryPointParameter) &&
+           IsValidationEnabled(decl->attributes,
+                               ast::DisabledValidation::
+                                   kIgnoreConstructibleFunctionParameter))) {
+        AddError("attribute is not valid for entry point return types",
+                 attr->source);
+        return false;
+      }
+    }
+  }
+
+  if (decl->IsEntryPoint()) {
+    if (!ValidateEntryPoint(func)) {
+      return false;
+    }
+  }
+
+  // https://www.w3.org/TR/WGSL/#behaviors-rules
+  // a function behavior is always one of {}, {Next}, {Discard}, or
+  // {Next, Discard}.
+  if (func->Behaviors() != sem::Behaviors{} &&  // NOLINT: bad warning
+      func->Behaviors() != sem::Behavior::kNext &&
+      func->Behaviors() != sem::Behavior::kDiscard &&
+      func->Behaviors() != sem::Behaviors{sem::Behavior::kNext,  //
+                                          sem::Behavior::kDiscard}) {
+    TINT_ICE(Resolver, diagnostics_)
+        << "function '" << name << "' behaviors are: " << func->Behaviors();
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateEntryPoint(const sem::Function* func) {
+  auto* decl = func->Declaration();
+
+  // Use a lambda to validate the entry point attributes for a type.
+  // Persistent state is used to track which builtins and locations have
+  // already been seen, in order to catch conflicts.
+  // TODO(jrprice): This state could be stored in sem::Function instead, and
+  // then passed to sem::Function since it would be useful there too.
+  std::unordered_set<ast::Builtin> builtins;
+  std::unordered_set<uint32_t> locations;
+  enum class ParamOrRetType {
+    kParameter,
+    kReturnType,
+  };
+
+  // Inner lambda that is applied to a type and all of its members.
+  auto validate_entry_point_attributes_inner = [&](const ast::AttributeList&
+                                                       attrs,
+                                                   const sem::Type* ty,
+                                                   Source source,
+                                                   ParamOrRetType param_or_ret,
+                                                   bool is_struct_member) {
+    // Scan attributes for pipeline IO attributes.
+    // Check for overlap with attributes that have been seen previously.
+    const ast::Attribute* pipeline_io_attribute = nullptr;
+    const ast::InterpolateAttribute* interpolate_attribute = nullptr;
+    const ast::InvariantAttribute* invariant_attribute = nullptr;
+    for (auto* attr : attrs) {
+      auto is_invalid_compute_shader_attribute = false;
+      if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
+        if (pipeline_io_attribute) {
+          AddError("multiple entry point IO attributes", attr->source);
+          AddNote("previously consumed " + attr_to_str(pipeline_io_attribute),
+                  pipeline_io_attribute->source);
+          return false;
+        }
+        pipeline_io_attribute = attr;
+
+        if (builtins.count(builtin->builtin)) {
+          AddError(attr_to_str(builtin) +
+                       " attribute appears multiple times as pipeline " +
+                       (param_or_ret == ParamOrRetType::kParameter ? "input"
+                                                                   : "output"),
+                   decl->source);
+          return false;
+        }
+
+        if (!ValidateBuiltinAttribute(
+                builtin, ty,
+                /* is_input */ param_or_ret == ParamOrRetType::kParameter)) {
+          return false;
+        }
+        builtins.emplace(builtin->builtin);
+      } else if (auto* location = attr->As<ast::LocationAttribute>()) {
+        if (pipeline_io_attribute) {
+          AddError("multiple entry point IO attributes", attr->source);
+          AddNote("previously consumed " + attr_to_str(pipeline_io_attribute),
+                  pipeline_io_attribute->source);
+          return false;
+        }
+        pipeline_io_attribute = attr;
+
+        bool is_input = param_or_ret == ParamOrRetType::kParameter;
+        if (!ValidateLocationAttribute(location, ty, locations, source,
+                                       is_input)) {
+          return false;
+        }
+      } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
+        if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
+          is_invalid_compute_shader_attribute = true;
+        } else if (!ValidateInterpolateAttribute(interpolate, ty)) {
+          return false;
+        }
+        interpolate_attribute = interpolate;
+      } else if (auto* invariant = attr->As<ast::InvariantAttribute>()) {
+        if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
+          is_invalid_compute_shader_attribute = true;
+        }
+        invariant_attribute = invariant;
+      }
+      if (is_invalid_compute_shader_attribute) {
+        std::string input_or_output =
+            param_or_ret == ParamOrRetType::kParameter ? "inputs" : "output";
+        AddError("attribute is not valid for compute shader " + input_or_output,
+                 attr->source);
+        return false;
+      }
+    }
+
+    if (IsValidationEnabled(attrs,
+                            ast::DisabledValidation::kEntryPointParameter)) {
+      if (is_struct_member && ty->Is<sem::Struct>()) {
+        AddError("nested structures cannot be used for entry point IO", source);
+        return false;
+      }
+
+      if (!ty->Is<sem::Struct>() && !pipeline_io_attribute) {
+        std::string err = "missing entry point IO attribute";
+        if (!is_struct_member) {
+          err +=
+              (param_or_ret == ParamOrRetType::kParameter ? " on parameter"
+                                                          : " on return type");
+        }
+        AddError(err, source);
+        return false;
+      }
+
+      if (pipeline_io_attribute &&
+          pipeline_io_attribute->Is<ast::LocationAttribute>()) {
+        if (ty->is_integer_scalar_or_vector() && !interpolate_attribute) {
+          if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
+              param_or_ret == ParamOrRetType::kReturnType) {
+            AddError(
+                "integral user-defined vertex outputs must have a flat "
+                "interpolation attribute",
+                source);
+            return false;
+          }
+          if (decl->PipelineStage() == ast::PipelineStage::kFragment &&
+              param_or_ret == ParamOrRetType::kParameter) {
+            AddError(
+                "integral user-defined fragment inputs must have a flat "
+                "interpolation attribute",
+                source);
+            return false;
+          }
+        }
+      }
+
+      if (interpolate_attribute) {
+        if (!pipeline_io_attribute ||
+            !pipeline_io_attribute->Is<ast::LocationAttribute>()) {
+          AddError("interpolate attribute must only be used with @location",
+                   interpolate_attribute->source);
+          return false;
+        }
+      }
+
+      if (invariant_attribute) {
+        bool has_position = false;
+        if (pipeline_io_attribute) {
+          if (auto* builtin =
+                  pipeline_io_attribute->As<ast::BuiltinAttribute>()) {
+            has_position = (builtin->builtin == ast::Builtin::kPosition);
+          }
+        }
+        if (!has_position) {
+          AddError(
+              "invariant attribute must only be applied to a position "
+              "builtin",
+              invariant_attribute->source);
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+  // Outer lambda for validating the entry point attributes for a type.
+  auto validate_entry_point_attributes = [&](const ast::AttributeList& attrs,
+                                             const sem::Type* ty, Source source,
+                                             ParamOrRetType param_or_ret) {
+    if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
+                                               /*is_struct_member*/ false)) {
+      return false;
+    }
+
+    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,
+                /*is_struct_member*/ true)) {
+          AddNote("while analysing entry point '" +
+                      builder_->Symbols().NameFor(decl->symbol) + "'",
+                  decl->source);
+          return false;
+        }
+      }
+    }
+
+    return true;
+  };
+
+  for (auto* param : func->Parameters()) {
+    auto* param_decl = param->Declaration();
+    if (!validate_entry_point_attributes(param_decl->attributes, param->Type(),
+                                         param_decl->source,
+                                         ParamOrRetType::kParameter)) {
+      return false;
+    }
+  }
+
+  // Clear IO sets after parameter validation. Builtin and location attributes
+  // in return types should be validated independently from those used in
+  // parameters.
+  builtins.clear();
+  locations.clear();
+
+  if (!func->ReturnType()->Is<sem::Void>()) {
+    if (!validate_entry_point_attributes(decl->return_type_attributes,
+                                         func->ReturnType(), decl->source,
+                                         ParamOrRetType::kReturnType)) {
+      return false;
+    }
+  }
+
+  if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
+      builtins.count(ast::Builtin::kPosition) == 0) {
+    // Check module-scope variables, as the SPIR-V sanitizer generates these.
+    bool found = false;
+    for (auto* global : func->TransitivelyReferencedGlobals()) {
+      if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
+              global->Declaration()->attributes)) {
+        if (builtin->builtin == ast::Builtin::kPosition) {
+          found = true;
+          break;
+        }
+      }
+    }
+    if (!found) {
+      AddError(
+          "a vertex shader must include the 'position' builtin in its return "
+          "type",
+          decl->source);
+      return false;
+    }
+  }
+
+  if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
+    if (!ast::HasAttribute<ast::WorkgroupAttribute>(decl->attributes)) {
+      AddError(
+          "a compute shader must include 'workgroup_size' in its "
+          "attributes",
+          decl->source);
+      return false;
+    }
+  }
+
+  // Validate there are no resource variable binding collisions
+  std::unordered_map<sem::BindingPoint, const ast::Variable*> binding_points;
+  for (auto* var : func->TransitivelyReferencedGlobals()) {
+    auto* var_decl = var->Declaration();
+    if (!var_decl->BindingPoint()) {
+      continue;
+    }
+    auto bp = var->BindingPoint();
+    auto res = binding_points.emplace(bp, var_decl);
+    if (!res.second &&
+        IsValidationEnabled(decl->attributes,
+                            ast::DisabledValidation::kBindingPointCollision) &&
+        IsValidationEnabled(res.first->second->attributes,
+                            ast::DisabledValidation::kBindingPointCollision)) {
+      // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
+      // Bindings must not alias within a shader stage: two different
+      // variables in the resource interface of a given shader must not have
+      // the same group and binding values, when considered as a pair of
+      // values.
+      auto func_name = builder_->Symbols().NameFor(decl->symbol);
+      AddError("entry point '" + func_name +
+                   "' references multiple variables that use the "
+                   "same resource binding @group(" +
+                   std::to_string(bp.group) + "), @binding(" +
+                   std::to_string(bp.binding) + ")",
+               var_decl->source);
+      AddNote("first resource binding usage declared here",
+              res.first->second->source);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateStatements(const ast::StatementList& stmts) {
+  for (auto* stmt : stmts) {
+    if (!Sem(stmt)->IsReachable()) {
+      /// TODO(https://github.com/gpuweb/gpuweb/issues/2378): This may need to
+      /// become an error.
+      AddWarning("code is unreachable", stmt->source);
+      break;
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateBitcast(const ast::BitcastExpression* cast,
+                               const sem::Type* to) {
+  auto* from = TypeOf(cast->expr)->UnwrapRef();
+  if (!from->is_numeric_scalar_or_vector()) {
+    AddError("'" + TypeNameOf(from) + "' cannot be bitcast",
+             cast->expr->source);
+    return false;
+  }
+  if (!to->is_numeric_scalar_or_vector()) {
+    AddError("cannot bitcast to '" + TypeNameOf(to) + "'", cast->type->source);
+    return false;
+  }
+
+  auto width = [&](const sem::Type* ty) {
+    if (auto* vec = ty->As<sem::Vector>()) {
+      return vec->Width();
+    }
+    return 1u;
+  };
+
+  if (width(from) != width(to)) {
+    AddError("cannot bitcast from '" + TypeNameOf(from) + "' to '" +
+                 TypeNameOf(to) + "'",
+             cast->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateBreakStatement(const sem::Statement* stmt) {
+  if (!stmt->FindFirstParent<sem::LoopBlockStatement, sem::CaseStatement>()) {
+    AddError("break statement must be in a loop or switch case",
+             stmt->Declaration()->source);
+    return false;
+  }
+  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true)) {
+    auto fail = [&](const char* note_msg, const Source& note_src) {
+      constexpr const char* kErrorMsg =
+          "break statement in a continuing block must be the single statement "
+          "of an if statement's true or false block, and that if statement "
+          "must be the last statement of the continuing block";
+      AddError(kErrorMsg, stmt->Declaration()->source);
+      AddNote(note_msg, note_src);
+      return false;
+    };
+
+    if (auto* block = stmt->Parent()->As<sem::BlockStatement>()) {
+      auto* block_parent = block->Parent();
+      auto* if_stmt = block_parent->As<sem::IfStatement>();
+      auto* el_stmt = block_parent->As<sem::ElseStatement>();
+      if (el_stmt) {
+        if_stmt = el_stmt->Parent();
+      }
+      if (!if_stmt) {
+        return fail("break statement is not directly in if statement block",
+                    stmt->Declaration()->source);
+      }
+      if (block->Declaration()->statements.size() != 1) {
+        return fail("if statement block contains multiple statements",
+                    block->Declaration()->source);
+      }
+      for (auto* el : if_stmt->Declaration()->else_statements) {
+        if (el->condition) {
+          return fail("else has condition", el->condition->source);
+        }
+        bool el_contains_break = el_stmt && el == el_stmt->Declaration();
+        if (el_contains_break) {
+          if (auto* true_block = if_stmt->Declaration()->body;
+              !true_block->Empty()) {
+            return fail("non-empty true block", true_block->source);
+          }
+        } else {
+          if (!el->body->Empty()) {
+            return fail("non-empty false block", el->body->source);
+          }
+        }
+      }
+      if (if_stmt->Parent()->Declaration() != continuing) {
+        return fail(
+            "if statement containing break statement is not directly in "
+            "continuing block",
+            if_stmt->Declaration()->source);
+      }
+      if (auto* cont_block = continuing->As<ast::BlockStatement>()) {
+        if (if_stmt->Declaration() != cont_block->Last()) {
+          return fail(
+              "if statement containing break statement is not the last "
+              "statement of the continuing block",
+              if_stmt->Declaration()->source);
+        }
+      }
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateContinueStatement(const sem::Statement* stmt) {
+  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true)) {
+    AddError("continuing blocks must not contain a continue statement",
+             stmt->Declaration()->source);
+    if (continuing != stmt->Declaration() &&
+        continuing != stmt->Parent()->Declaration()) {
+      AddNote("see continuing block here", continuing->source);
+    }
+    return false;
+  }
+
+  if (!stmt->FindFirstParent<sem::LoopBlockStatement>()) {
+    AddError("continue statement must be in a loop",
+             stmt->Declaration()->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateDiscardStatement(const sem::Statement* stmt) {
+  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
+    AddError("continuing blocks must not contain a discard statement",
+             stmt->Declaration()->source);
+    if (continuing != stmt->Declaration() &&
+        continuing != stmt->Parent()->Declaration()) {
+      AddNote("see continuing block here", continuing->source);
+    }
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateFallthroughStatement(const sem::Statement* stmt) {
+  if (auto* block = As<sem::BlockStatement>(stmt->Parent())) {
+    if (auto* c = As<sem::CaseStatement>(block->Parent())) {
+      if (block->Declaration()->Last() == stmt->Declaration()) {
+        if (auto* s = As<sem::SwitchStatement>(c->Parent())) {
+          if (c->Declaration() != s->Declaration()->body.back()) {
+            return true;
+          }
+          AddError(
+              "a fallthrough statement must not be used in the last switch "
+              "case",
+              stmt->Declaration()->source);
+          return false;
+        }
+      }
+    }
+  }
+  AddError(
+      "fallthrough must only be used as the last statement of a case block",
+      stmt->Declaration()->source);
+  return false;
+}
+
+bool Resolver::ValidateElseStatement(const sem::ElseStatement* stmt) {
+  if (auto* cond = stmt->Condition()) {
+    auto* cond_ty = cond->Type()->UnwrapRef();
+    if (!cond_ty->Is<sem::Bool>()) {
+      AddError(
+          "else statement condition must be bool, got " + TypeNameOf(cond_ty),
+          stmt->Condition()->Declaration()->source);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateLoopStatement(const sem::LoopStatement* stmt) {
+  if (stmt->Behaviors().Empty()) {
+    AddError("loop does not exit", stmt->Declaration()->source.Begin());
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateForLoopStatement(const sem::ForLoopStatement* stmt) {
+  if (stmt->Behaviors().Empty()) {
+    AddError("for-loop does not exit", stmt->Declaration()->source.Begin());
+    return false;
+  }
+  if (auto* cond = stmt->Condition()) {
+    auto* cond_ty = cond->Type()->UnwrapRef();
+    if (!cond_ty->Is<sem::Bool>()) {
+      AddError("for-loop condition must be bool, got " + TypeNameOf(cond_ty),
+               stmt->Condition()->Declaration()->source);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateIfStatement(const sem::IfStatement* stmt) {
+  auto* cond_ty = stmt->Condition()->Type()->UnwrapRef();
+  if (!cond_ty->Is<sem::Bool>()) {
+    AddError("if statement condition must be bool, got " + TypeNameOf(cond_ty),
+             stmt->Condition()->Declaration()->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateBuiltinCall(const sem::Call* call) {
+  if (call->Type()->Is<sem::Void>()) {
+    bool is_call_statement = false;
+    if (auto* call_stmt = As<ast::CallStatement>(call->Stmt()->Declaration())) {
+      if (call_stmt->expr == call->Declaration()) {
+        is_call_statement = true;
+      }
+    }
+    if (!is_call_statement) {
+      // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
+      // If the called function does not return a value, a function call
+      // statement should be used instead.
+      auto* ident = call->Declaration()->target.name;
+      auto name = builder_->Symbols().NameFor(ident->symbol);
+      AddError("builtin '" + name + "' does not return a value",
+               call->Declaration()->source);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateTextureBuiltinFunction(const sem::Call* call) {
+  auto* builtin = call->Target()->As<sem::Builtin>();
+  if (!builtin) {
+    return false;
+  }
+
+  std::string func_name = builtin->str();
+  auto& signature = builtin->Signature();
+
+  auto check_arg_is_constexpr = [&](sem::ParameterUsage usage, int min,
+                                    int max) {
+    auto index = signature.IndexOf(usage);
+    if (index < 0) {
+      return true;
+    }
+    std::string name = sem::str(usage);
+    auto* arg = call->Arguments()[index];
+    if (auto values = arg->ConstantValue()) {
+      // Assert that the constant values are of the expected type.
+      if (!values.Type()->IsAnyOf<sem::I32, sem::Vector>() ||
+          !values.ElementType()->Is<sem::I32>()) {
+        TINT_ICE(Resolver, diagnostics_)
+            << "failed to resolve '" + func_name + "' " << name
+            << " parameter type";
+        return false;
+      }
+
+      // Currently const_expr is restricted to literals and type constructors.
+      // Check that that's all we have for the parameter.
+      bool is_const_expr = true;
+      ast::TraverseExpressions(
+          arg->Declaration(), diagnostics_, [&](const ast::Expression* e) {
+            if (e->IsAnyOf<ast::LiteralExpression, ast::CallExpression>()) {
+              return ast::TraverseAction::Descend;
+            }
+            is_const_expr = false;
+            return ast::TraverseAction::Stop;
+          });
+      if (is_const_expr) {
+        auto vector = builtin->Parameters()[index]->Type()->Is<sem::Vector>();
+        for (size_t i = 0; i < values.Elements().size(); i++) {
+          auto value = values.Elements()[i].i32;
+          if (value < min || value > max) {
+            if (vector) {
+              AddError("each component of the " + name +
+                           " argument must be at least " + std::to_string(min) +
+                           " and at most " + std::to_string(max) + ". " + name +
+                           " component " + std::to_string(i) + " is " +
+                           std::to_string(value),
+                       arg->Declaration()->source);
+            } else {
+              AddError("the " + name + " argument must be at least " +
+                           std::to_string(min) + " and at most " +
+                           std::to_string(max) + ". " + name + " is " +
+                           std::to_string(value),
+                       arg->Declaration()->source);
+            }
+            return false;
+          }
+        }
+        return true;
+      }
+    }
+    AddError("the " + name + " argument must be a const_expression",
+             arg->Declaration()->source);
+    return false;
+  };
+
+  return check_arg_is_constexpr(sem::ParameterUsage::kOffset, -8, 7) &&
+         check_arg_is_constexpr(sem::ParameterUsage::kComponent, 0, 3);
+}
+
+bool Resolver::ValidateFunctionCall(const sem::Call* call) {
+  auto* decl = call->Declaration();
+  auto* target = call->Target()->As<sem::Function>();
+  auto sym = decl->target.name->symbol;
+  auto name = builder_->Symbols().NameFor(sym);
+
+  if (target->Declaration()->IsEntryPoint()) {
+    // https://www.w3.org/TR/WGSL/#function-restriction
+    // An entry point must never be the target of a function call.
+    AddError("entry point functions cannot be the target of a function call",
+             decl->source);
+    return false;
+  }
+
+  if (decl->args.size() != target->Parameters().size()) {
+    bool more = decl->args.size() > target->Parameters().size();
+    AddError("too " + (more ? std::string("many") : std::string("few")) +
+                 " arguments in call to '" + name + "', expected " +
+                 std::to_string(target->Parameters().size()) + ", got " +
+                 std::to_string(call->Arguments().size()),
+             decl->source);
+    return false;
+  }
+
+  for (size_t i = 0; i < call->Arguments().size(); ++i) {
+    const sem::Variable* param = target->Parameters()[i];
+    const ast::Expression* arg_expr = decl->args[i];
+    auto* param_type = param->Type();
+    auto* arg_type = TypeOf(arg_expr)->UnwrapRef();
+
+    if (param_type != arg_type) {
+      AddError("type mismatch for argument " + std::to_string(i + 1) +
+                   " in call to '" + name + "', expected '" +
+                   TypeNameOf(param_type) + "', got '" + TypeNameOf(arg_type) +
+                   "'",
+               arg_expr->source);
+      return false;
+    }
+
+    if (param_type->Is<sem::Pointer>()) {
+      auto is_valid = false;
+      if (auto* ident_expr = arg_expr->As<ast::IdentifierExpression>()) {
+        auto* var = ResolvedSymbol<sem::Variable>(ident_expr);
+        if (!var) {
+          TINT_ICE(Resolver, diagnostics_) << "failed to resolve identifier";
+          return false;
+        }
+        if (var->Is<sem::Parameter>()) {
+          is_valid = true;
+        }
+      } else if (auto* unary = arg_expr->As<ast::UnaryOpExpression>()) {
+        if (unary->op == ast::UnaryOp::kAddressOf) {
+          if (auto* ident_unary =
+                  unary->expr->As<ast::IdentifierExpression>()) {
+            auto* var = ResolvedSymbol<sem::Variable>(ident_unary);
+            if (!var) {
+              TINT_ICE(Resolver, diagnostics_)
+                  << "failed to resolve identifier";
+              return false;
+            }
+            if (var->Declaration()->is_const) {
+              TINT_ICE(Resolver, diagnostics_)
+                  << "Resolver::FunctionCall() encountered an address-of "
+                     "expression of a constant identifier expression";
+              return false;
+            }
+            is_valid = true;
+          }
+        }
+      }
+
+      if (!is_valid &&
+          IsValidationEnabled(
+              param->Declaration()->attributes,
+              ast::DisabledValidation::kIgnoreInvalidPointerArgument)) {
+        AddError(
+            "expected an address-of expression of a variable identifier "
+            "expression or a function parameter",
+            arg_expr->source);
+        return false;
+      }
+    }
+  }
+
+  if (call->Type()->Is<sem::Void>()) {
+    bool is_call_statement = false;
+    if (auto* call_stmt = As<ast::CallStatement>(call->Stmt()->Declaration())) {
+      if (call_stmt->expr == call->Declaration()) {
+        is_call_statement = true;
+      }
+    }
+    if (!is_call_statement) {
+      // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
+      // If the called function does not return a value, a function call
+      // statement should be used instead.
+      AddError("function '" + name + "' does not return a value", decl->source);
+      return false;
+    }
+  }
+
+  if (call->Behaviors().Contains(sem::Behavior::kDiscard)) {
+    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
+      AddError(
+          "cannot call a function that may discard inside a continuing block",
+          call->Declaration()->source);
+      if (continuing != call->Stmt()->Declaration() &&
+          continuing != call->Stmt()->Parent()->Declaration()) {
+        AddNote("see continuing block here", continuing->source);
+      }
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateStructureConstructorOrCast(
+    const ast::CallExpression* ctor,
+    const sem::Struct* struct_type) {
+  if (!struct_type->IsConstructible()) {
+    AddError("struct constructor has non-constructible type", ctor->source);
+    return false;
+  }
+
+  if (ctor->args.size() > 0) {
+    if (ctor->args.size() != struct_type->Members().size()) {
+      std::string fm =
+          ctor->args.size() < struct_type->Members().size() ? "few" : "many";
+      AddError("struct constructor has too " + fm + " inputs: expected " +
+                   std::to_string(struct_type->Members().size()) + ", found " +
+                   std::to_string(ctor->args.size()),
+               ctor->source);
+      return false;
+    }
+    for (auto* member : struct_type->Members()) {
+      auto* value = ctor->args[member->Index()];
+      auto* value_ty = TypeOf(value);
+      if (member->Type() != value_ty->UnwrapRef()) {
+        AddError(
+            "type in struct constructor does not match struct member type: "
+            "expected '" +
+                TypeNameOf(member->Type()) + "', found '" +
+                TypeNameOf(value_ty) + "'",
+            value->source);
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateArrayConstructorOrCast(const ast::CallExpression* ctor,
+                                              const sem::Array* array_type) {
+  auto& values = ctor->args;
+  auto* elem_ty = array_type->ElemType();
+  for (auto* value : values) {
+    auto* value_ty = TypeOf(value)->UnwrapRef();
+    if (value_ty != elem_ty) {
+      AddError(
+          "type in array constructor does not match array type: "
+          "expected '" +
+              TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_ty) + "'",
+          value->source);
+      return false;
+    }
+  }
+
+  if (array_type->IsRuntimeSized()) {
+    AddError("cannot init a runtime-sized array", ctor->source);
+    return false;
+  } else if (!elem_ty->IsConstructible()) {
+    AddError("array constructor has non-constructible element type",
+             ctor->source);
+    return false;
+  } else if (!values.empty() && (values.size() != array_type->Count())) {
+    std::string fm = values.size() < array_type->Count() ? "few" : "many";
+    AddError("array constructor has too " + fm + " elements: expected " +
+                 std::to_string(array_type->Count()) + ", found " +
+                 std::to_string(values.size()),
+             ctor->source);
+    return false;
+  } else if (values.size() > array_type->Count()) {
+    AddError("array constructor has too many elements: expected " +
+                 std::to_string(array_type->Count()) + ", found " +
+                 std::to_string(values.size()),
+             ctor->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateVectorConstructorOrCast(const ast::CallExpression* ctor,
+                                               const sem::Vector* vec_type) {
+  auto& values = ctor->args;
+  auto* elem_ty = vec_type->type();
+  size_t value_cardinality_sum = 0;
+  for (auto* value : values) {
+    auto* value_ty = TypeOf(value)->UnwrapRef();
+    if (value_ty->is_scalar()) {
+      if (elem_ty != value_ty) {
+        AddError(
+            "type in vector constructor does not match vector type: "
+            "expected '" +
+                TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_ty) + "'",
+            value->source);
+        return false;
+      }
+
+      value_cardinality_sum++;
+    } else if (auto* value_vec = value_ty->As<sem::Vector>()) {
+      auto* value_elem_ty = value_vec->type();
+      // A mismatch of vector type parameter T is only an error if multiple
+      // arguments are present. A single argument constructor constitutes a
+      // type conversion expression.
+      if (elem_ty != value_elem_ty && values.size() > 1u) {
+        AddError(
+            "type in vector constructor does not match vector type: "
+            "expected '" +
+                TypeNameOf(elem_ty) + "', found '" + TypeNameOf(value_elem_ty) +
+                "'",
+            value->source);
+        return false;
+      }
+
+      value_cardinality_sum += value_vec->Width();
+    } else {
+      // A vector constructor can only accept vectors and scalars.
+      AddError("expected vector or scalar type in vector constructor; found: " +
+                   TypeNameOf(value_ty),
+               value->source);
+      return false;
+    }
+  }
+
+  // A correct vector constructor must either be a zero-value expression,
+  // a single-value initializer (splat) expression, or the number of components
+  // of all constructor arguments must add up to the vector cardinality.
+  if (value_cardinality_sum > 1 && value_cardinality_sum != vec_type->Width()) {
+    if (values.empty()) {
+      TINT_ICE(Resolver, diagnostics_)
+          << "constructor arguments expected to be non-empty!";
+    }
+    const Source& values_start = values[0]->source;
+    const Source& values_end = values[values.size() - 1]->source;
+    AddError("attempted to construct '" + TypeNameOf(vec_type) + "' with " +
+                 std::to_string(value_cardinality_sum) + " component(s)",
+             Source::Combine(values_start, values_end));
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateVector(const sem::Vector* ty, const Source& source) {
+  if (!ty->type()->is_scalar()) {
+    AddError("vector element type must be 'bool', 'f32', 'i32' or 'u32'",
+             source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateMatrix(const sem::Matrix* ty, const Source& source) {
+  if (!ty->is_float_matrix()) {
+    AddError("matrix element type must be 'f32'", source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateMatrixConstructorOrCast(const ast::CallExpression* ctor,
+                                               const sem::Matrix* matrix_ty) {
+  auto& values = ctor->args;
+  // Zero Value expression
+  if (values.empty()) {
+    return true;
+  }
+
+  if (!ValidateMatrix(matrix_ty, ctor->source)) {
+    return false;
+  }
+
+  std::vector<const sem::Type*> arg_tys;
+  arg_tys.reserve(values.size());
+  for (auto* value : values) {
+    arg_tys.emplace_back(TypeOf(value)->UnwrapRef());
+  }
+
+  auto* elem_type = matrix_ty->type();
+  auto num_elements = matrix_ty->columns() * matrix_ty->rows();
+
+  // Print a generic error for an invalid matrix constructor, showing the
+  // available overloads.
+  auto print_error = [&]() {
+    const Source& values_start = values[0]->source;
+    const Source& values_end = values[values.size() - 1]->source;
+    auto type_name = TypeNameOf(matrix_ty);
+    auto elem_type_name = TypeNameOf(elem_type);
+    std::stringstream ss;
+    ss << "no matching constructor " + type_name << "(";
+    for (size_t i = 0; i < values.size(); i++) {
+      if (i > 0) {
+        ss << ", ";
+      }
+      ss << arg_tys[i]->FriendlyName(builder_->Symbols());
+    }
+    ss << ")" << std::endl << std::endl;
+    ss << "3 candidates available:" << std::endl;
+    ss << "  " << type_name << "()" << std::endl;
+    ss << "  " << type_name << "(" << elem_type_name << ",...,"
+       << elem_type_name << ")"
+       << " // " << std::to_string(num_elements) << " arguments" << std::endl;
+    ss << "  " << type_name << "(";
+    for (uint32_t c = 0; c < matrix_ty->columns(); c++) {
+      if (c > 0) {
+        ss << ", ";
+      }
+      ss << VectorPretty(matrix_ty->rows(), elem_type);
+    }
+    ss << ")" << std::endl;
+    AddError(ss.str(), Source::Combine(values_start, values_end));
+  };
+
+  const sem::Type* expected_arg_type = nullptr;
+  if (num_elements == values.size()) {
+    // Column-major construction from scalar elements.
+    expected_arg_type = matrix_ty->type();
+  } else if (matrix_ty->columns() == values.size()) {
+    // Column-by-column construction from vectors.
+    expected_arg_type = matrix_ty->ColumnType();
+  } else {
+    print_error();
+    return false;
+  }
+
+  for (auto* arg_ty : arg_tys) {
+    if (arg_ty != expected_arg_type) {
+      print_error();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateScalarConstructorOrCast(const ast::CallExpression* ctor,
+                                               const sem::Type* ty) {
+  if (ctor->args.size() == 0) {
+    return true;
+  }
+  if (ctor->args.size() > 1) {
+    AddError("expected zero or one value in constructor, got " +
+                 std::to_string(ctor->args.size()),
+             ctor->source);
+    return false;
+  }
+
+  // Validate constructor
+  auto* value = ctor->args[0];
+  auto* value_ty = TypeOf(value)->UnwrapRef();
+
+  using Bool = sem::Bool;
+  using I32 = sem::I32;
+  using U32 = sem::U32;
+  using F32 = sem::F32;
+
+  const bool is_valid = (ty->Is<Bool>() && value_ty->is_scalar()) ||
+                        (ty->Is<I32>() && value_ty->is_scalar()) ||
+                        (ty->Is<U32>() && value_ty->is_scalar()) ||
+                        (ty->Is<F32>() && value_ty->is_scalar());
+  if (!is_valid) {
+    AddError("cannot construct '" + TypeNameOf(ty) +
+                 "' with a value of type '" + TypeNameOf(value_ty) + "'",
+             ctor->source);
+
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidatePipelineStages() {
+  auto check_workgroup_storage = [&](const sem::Function* func,
+                                     const sem::Function* entry_point) {
+    auto stage = entry_point->Declaration()->PipelineStage();
+    if (stage != ast::PipelineStage::kCompute) {
+      for (auto* var : func->DirectlyReferencedGlobals()) {
+        if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
+          std::stringstream stage_name;
+          stage_name << stage;
+          for (auto* user : var->Users()) {
+            if (func == user->Stmt()->Function()) {
+              AddError("workgroup memory cannot be used by " +
+                           stage_name.str() + " pipeline stage",
+                       user->Declaration()->source);
+              break;
+            }
+          }
+          AddNote("variable is declared here", var->Declaration()->source);
+          if (func != entry_point) {
+            TraverseCallChain(diagnostics_, entry_point, func,
+                              [&](const sem::Function* f) {
+                                AddNote("called by function '" +
+                                            builder_->Symbols().NameFor(
+                                                f->Declaration()->symbol) +
+                                            "'",
+                                        f->Declaration()->source);
+                              });
+            AddNote("called by entry point '" +
+                        builder_->Symbols().NameFor(
+                            entry_point->Declaration()->symbol) +
+                        "'",
+                    entry_point->Declaration()->source);
+          }
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+  for (auto* entry_point : entry_points_) {
+    if (!check_workgroup_storage(entry_point, entry_point)) {
+      return false;
+    }
+    for (auto* func : entry_point->TransitivelyCalledFunctions()) {
+      if (!check_workgroup_storage(func, entry_point)) {
+        return false;
+      }
+    }
+  }
+
+  auto check_builtin_calls = [&](const sem::Function* func,
+                                 const sem::Function* entry_point) {
+    auto stage = entry_point->Declaration()->PipelineStage();
+    for (auto* builtin : func->DirectlyCalledBuiltins()) {
+      if (!builtin->SupportedStages().Contains(stage)) {
+        auto* call = func->FindDirectCallTo(builtin);
+        std::stringstream err;
+        err << "built-in cannot be used by " << stage << " pipeline stage";
+        AddError(err.str(), call ? call->Declaration()->source
+                                 : func->Declaration()->source);
+        if (func != entry_point) {
+          TraverseCallChain(
+              diagnostics_, entry_point, func, [&](const sem::Function* f) {
+                AddNote(
+                    "called by function '" +
+                        builder_->Symbols().NameFor(f->Declaration()->symbol) +
+                        "'",
+                    f->Declaration()->source);
+              });
+          AddNote("called by entry point '" +
+                      builder_->Symbols().NameFor(
+                          entry_point->Declaration()->symbol) +
+                      "'",
+                  entry_point->Declaration()->source);
+        }
+        return false;
+      }
+    }
+    return true;
+  };
+
+  for (auto* entry_point : entry_points_) {
+    if (!check_builtin_calls(entry_point, entry_point)) {
+      return false;
+    }
+    for (auto* func : entry_point->TransitivelyCalledFunctions()) {
+      if (!check_builtin_calls(func, entry_point)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateArray(const sem::Array* arr, const Source& source) {
+  auto* el_ty = arr->ElemType();
+
+  if (!IsFixedFootprint(el_ty)) {
+    AddError("an array element type cannot contain a runtime-sized array",
+             source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateArrayStrideAttribute(const ast::StrideAttribute* attr,
+                                            uint32_t el_size,
+                                            uint32_t el_align,
+                                            const Source& source) {
+  auto stride = attr->stride;
+  bool is_valid_stride =
+      (stride >= el_size) && (stride >= el_align) && (stride % el_align == 0);
+  if (!is_valid_stride) {
+    // https://gpuweb.github.io/gpuweb/wgsl/#array-layout-rules
+    // Arrays decorated with the stride attribute must have a stride that is
+    // at least the size of the element type, and be a multiple of the
+    // element type's alignment value.
+    AddError(
+        "arrays decorated with the stride attribute must have a stride "
+        "that is at least the size of the element type, and be a multiple "
+        "of the element type's alignment value.",
+        source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateAlias(const ast::Alias* alias) {
+  auto name = builder_->Symbols().NameFor(alias->name);
+  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
+    AddError("'" + name + "' is a builtin and cannot be redeclared as an alias",
+             alias->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateStructure(const sem::Struct* str) {
+  auto name = builder_->Symbols().NameFor(str->Declaration()->name);
+  if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
+    AddError("'" + name + "' is a builtin and cannot be redeclared as a struct",
+             str->Declaration()->source);
+    return false;
+  }
+
+  if (str->Members().empty()) {
+    AddError("structures must have at least one member",
+             str->Declaration()->source);
+    return false;
+  }
+
+  std::unordered_set<uint32_t> locations;
+  for (auto* member : str->Members()) {
+    if (auto* r = member->Type()->As<sem::Array>()) {
+      if (r->IsRuntimeSized()) {
+        if (member != str->Members().back()) {
+          AddError(
+              "runtime arrays may only appear as the last member of a struct",
+              member->Declaration()->source);
+          return false;
+        }
+      }
+    } else if (!IsFixedFootprint(member->Type())) {
+      AddError(
+          "a struct that contains a runtime array cannot be nested inside "
+          "another struct",
+          member->Declaration()->source);
+      return false;
+    }
+
+    auto has_location = false;
+    auto has_position = false;
+    const ast::InvariantAttribute* invariant_attribute = nullptr;
+    const ast::InterpolateAttribute* interpolate_attribute = nullptr;
+    for (auto* attr : member->Declaration()->attributes) {
+      if (!attr->IsAnyOf<ast::BuiltinAttribute,             //
+                         ast::InternalAttribute,            //
+                         ast::InterpolateAttribute,         //
+                         ast::InvariantAttribute,           //
+                         ast::LocationAttribute,            //
+                         ast::StructMemberOffsetAttribute,  //
+                         ast::StructMemberSizeAttribute,    //
+                         ast::StructMemberAlignAttribute>()) {
+        if (attr->Is<ast::StrideAttribute>() &&
+            IsValidationDisabled(
+                member->Declaration()->attributes,
+                ast::DisabledValidation::kIgnoreStrideAttribute)) {
+          continue;
+        }
+        AddError("attribute is not valid for structure members", attr->source);
+        return false;
+      }
+
+      if (auto* invariant = attr->As<ast::InvariantAttribute>()) {
+        invariant_attribute = invariant;
+      } else if (auto* location = attr->As<ast::LocationAttribute>()) {
+        has_location = true;
+        if (!ValidateLocationAttribute(location, member->Type(), locations,
+                                       member->Declaration()->source)) {
+          return false;
+        }
+      } else if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
+        if (!ValidateBuiltinAttribute(builtin, member->Type(),
+                                      /* is_input */ false)) {
+          return false;
+        }
+        if (builtin->builtin == ast::Builtin::kPosition) {
+          has_position = true;
+        }
+      } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
+        interpolate_attribute = interpolate;
+        if (!ValidateInterpolateAttribute(interpolate, member->Type())) {
+          return false;
+        }
+      }
+    }
+
+    if (invariant_attribute && !has_position) {
+      AddError("invariant attribute must only be applied to a position builtin",
+               invariant_attribute->source);
+      return false;
+    }
+
+    if (interpolate_attribute && !has_location) {
+      AddError("interpolate attribute must only be used with @location",
+               interpolate_attribute->source);
+      return false;
+    }
+  }
+
+  for (auto* attr : str->Declaration()->attributes) {
+    if (!(attr->IsAnyOf<ast::StructBlockAttribute, ast::InternalAttribute>())) {
+      AddError("attribute is not valid for struct declarations", attr->source);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateLocationAttribute(
+    const ast::LocationAttribute* location,
+    const sem::Type* type,
+    std::unordered_set<uint32_t>& locations,
+    const Source& source,
+    const bool is_input) {
+  std::string inputs_or_output = is_input ? "inputs" : "output";
+  if (current_function_ && current_function_->Declaration()->PipelineStage() ==
+                               ast::PipelineStage::kCompute) {
+    AddError("attribute is not valid for compute shader " + inputs_or_output,
+             location->source);
+    return false;
+  }
+
+  if (!type->is_numeric_scalar_or_vector()) {
+    std::string invalid_type = TypeNameOf(type);
+    AddError("cannot apply 'location' attribute to declaration of type '" +
+                 invalid_type + "'",
+             source);
+    AddNote(
+        "'location' attribute must only be applied to declarations of "
+        "numeric scalar or numeric vector type",
+        location->source);
+    return false;
+  }
+
+  if (locations.count(location->value)) {
+    AddError(attr_to_str(location) + " attribute appears multiple times",
+             location->source);
+    return false;
+  }
+  locations.emplace(location->value);
+
+  return true;
+}
+
+bool Resolver::ValidateReturn(const ast::ReturnStatement* ret) {
+  auto* func_type = current_function_->ReturnType();
+
+  auto* ret_type = ret->value ? TypeOf(ret->value)->UnwrapRef()
+                              : builder_->create<sem::Void>();
+
+  if (func_type->UnwrapRef() != ret_type) {
+    AddError(
+        "return statement type must match its function "
+        "return type, returned '" +
+            TypeNameOf(ret_type) + "', expected '" + TypeNameOf(func_type) +
+            "'",
+        ret->source);
+    return false;
+  }
+
+  auto* sem = Sem(ret);
+  if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
+    AddError("continuing blocks must not contain a return statement",
+             ret->source);
+    if (continuing != sem->Declaration() &&
+        continuing != sem->Parent()->Declaration()) {
+      AddNote("see continuing block here", continuing->source);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
+  auto* cond_ty = TypeOf(s->condition)->UnwrapRef();
+  if (!cond_ty->is_integer_scalar()) {
+    AddError(
+        "switch statement selector expression must be of a "
+        "scalar integer type",
+        s->condition->source);
+    return false;
+  }
+
+  bool has_default = false;
+  std::unordered_map<uint32_t, Source> selectors;
+
+  for (auto* case_stmt : s->body) {
+    if (case_stmt->IsDefault()) {
+      if (has_default) {
+        // More than one default clause
+        AddError("switch statement must have exactly one default clause",
+                 case_stmt->source);
+        return false;
+      }
+      has_default = true;
+    }
+
+    for (auto* selector : case_stmt->selectors) {
+      if (cond_ty != TypeOf(selector)) {
+        AddError(
+            "the case selector values must have the same "
+            "type as the selector expression.",
+            case_stmt->source);
+        return false;
+      }
+
+      auto v = selector->ValueAsU32();
+      auto it = selectors.find(v);
+      if (it != selectors.end()) {
+        auto val = selector->Is<ast::IntLiteralExpression>()
+                       ? std::to_string(selector->ValueAsI32())
+                       : std::to_string(selector->ValueAsU32());
+        AddError("duplicate switch case '" + val + "'", selector->source);
+        AddNote("previous case declared here", it->second);
+        return false;
+      }
+      selectors.emplace(v, selector->source);
+    }
+  }
+
+  if (!has_default) {
+    // No default clause
+    AddError("switch statement must have a default clause", s->source);
+    return false;
+  }
+
+  return true;
+}
+
+bool Resolver::ValidateAssignment(const ast::AssignmentStatement* a) {
+  auto const* rhs_ty = TypeOf(a->rhs);
+
+  if (a->lhs->Is<ast::PhonyExpression>()) {
+    // https://www.w3.org/TR/WGSL/#phony-assignment-section
+    auto* ty = rhs_ty->UnwrapRef();
+    if (!ty->IsConstructible() &&
+        !ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler>()) {
+      AddError(
+          "cannot assign '" + TypeNameOf(rhs_ty) +
+              "' to '_'. '_' can only be assigned a constructible, pointer, "
+              "texture or sampler type",
+          a->rhs->source);
+      return false;
+    }
+    return true;  // RHS can be anything.
+  }
+
+  // https://gpuweb.github.io/gpuweb/wgsl/#assignment-statement
+  auto const* lhs_ty = TypeOf(a->lhs);
+
+  if (auto* var = ResolvedSymbol<sem::Variable>(a->lhs)) {
+    auto* decl = var->Declaration();
+    if (var->Is<sem::Parameter>()) {
+      AddError("cannot assign to function parameter", a->lhs->source);
+      AddNote("'" + builder_->Symbols().NameFor(decl->symbol) +
+                  "' is declared here:",
+              decl->source);
+      return false;
+    }
+    if (decl->is_const) {
+      AddError("cannot assign to const", a->lhs->source);
+      AddNote("'" + builder_->Symbols().NameFor(decl->symbol) +
+                  "' is declared here:",
+              decl->source);
+      return false;
+    }
+  }
+
+  auto* lhs_ref = lhs_ty->As<sem::Reference>();
+  if (!lhs_ref) {
+    // LHS is not a reference, so it has no storage.
+    AddError("cannot assign to value of type '" + TypeNameOf(lhs_ty) + "'",
+             a->lhs->source);
+    return false;
+  }
+
+  auto* storage_ty = lhs_ref->StoreType();
+  auto* value_type = rhs_ty->UnwrapRef();  // Implicit load of RHS
+
+  // Value type has to match storage type
+  if (storage_ty != value_type) {
+    AddError("cannot assign '" + TypeNameOf(rhs_ty) + "' to '" +
+                 TypeNameOf(lhs_ty) + "'",
+             a->source);
+    return false;
+  }
+  if (!storage_ty->IsConstructible()) {
+    AddError("storage type of assignment must be constructible", a->source);
+    return false;
+  }
+  if (lhs_ref->Access() == ast::Access::kRead) {
+    AddError(
+        "cannot store into a read-only type '" + RawTypeNameOf(lhs_ty) + "'",
+        a->source);
+    return false;
+  }
+  return true;
+}
+
+bool Resolver::ValidateNoDuplicateAttributes(
+    const ast::AttributeList& attributes) {
+  std::unordered_map<const TypeInfo*, Source> seen;
+  for (auto* d : attributes) {
+    auto res = seen.emplace(&d->TypeInfo(), d->source);
+    if (!res.second && !d->Is<ast::InternalAttribute>()) {
+      AddError("duplicate " + d->Name() + " attribute", d->source);
+      AddNote("first attribute declared here", res.first->second);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Resolver::IsValidationDisabled(const ast::AttributeList& attributes,
+                                    ast::DisabledValidation validation) const {
+  for (auto* attribute : attributes) {
+    if (auto* dv = attribute->As<ast::DisableValidationAttribute>()) {
+      if (dv->validation == validation) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool Resolver::IsValidationEnabled(const ast::AttributeList& attributes,
+                                   ast::DisabledValidation validation) const {
+  return !IsValidationDisabled(attributes, validation);
+}
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
new file mode 100644
index 0000000..944ff5d
--- /dev/null
+++ b/src/tint/resolver/side_effects_test.cc
@@ -0,0 +1,371 @@
+// 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/resolver/resolver.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+
+namespace tint::resolver {
+namespace {
+
+struct SideEffectsTest : ResolverTest {
+  template <typename T>
+  void MakeSideEffectFunc(const char* name) {
+    auto global = Sym();
+    Global(global, ty.Of<T>(), ast::StorageClass::kPrivate);
+    auto local = Sym();
+    Func(name, {}, ty.Of<T>(),
+         {
+             Decl(Var(local, ty.Of<T>())),
+             Assign(global, local),
+             Return(global),
+         });
+  }
+
+  template <typename MAKE_TYPE_FUNC>
+  void MakeSideEffectFunc(const char* name, MAKE_TYPE_FUNC make_type) {
+    auto global = Sym();
+    Global(global, make_type(), ast::StorageClass::kPrivate);
+    auto local = Sym();
+    Func(name, {}, make_type(),
+         {
+             Decl(Var(local, make_type())),
+             Assign(global, local),
+             Return(global),
+         });
+  }
+};
+
+TEST_F(SideEffectsTest, Phony) {
+  auto* expr = Phony();
+  auto* body = Assign(expr, 1);
+  WrapInFunction(body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Literal) {
+  auto* expr = Expr(1);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, VariableUser) {
+  auto* var = Decl(Var("a", ty.i32()));
+  auto* expr = Expr("a");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::VariableUser>());
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_Builtin_NoSE) {
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call("dpdx", "a");
+  Func("f", {}, ty.void_(), {Ignore(expr)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_Builtin_NoSE_WithSEArg) {
+  MakeSideEffectFunc<f32>("se");
+  auto* expr = Call("dpdx", Call("se"));
+  Func("f", {}, ty.void_(), {Ignore(expr)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_Builtin_SE) {
+  Global("a", ty.atomic(ty.i32()), ast::StorageClass::kWorkgroup);
+  auto* expr = Call("atomicAdd", AddressOf("a"), 1);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_Function) {
+  Func("f", {}, ty.i32(), {Return(1)});
+  auto* expr = Call("f");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_TypeConversion_NoSE) {
+  auto* var = Decl(Var("a", ty.i32()));
+  auto* expr = Construct(ty.f32(), "a");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_TypeConversion_SE) {
+  MakeSideEffectFunc<i32>("se");
+  auto* expr = Construct(ty.f32(), Call("se"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_TypeConstructor_NoSE) {
+  auto* var = Decl(Var("a", ty.f32()));
+  auto* expr = Construct(ty.f32(), "a");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Call_TypeConstructor_SE) {
+  MakeSideEffectFunc<f32>("se");
+  auto* expr = Construct(ty.f32(), Call("se"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Is<sem::Call>());
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, MemberAccessor_Struct_NoSE) {
+  auto* s = Structure("S", {Member("m", ty.i32())});
+  auto* var = Decl(Var("a", ty.Of(s)));
+  auto* expr = MemberAccessor("a", "m");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, MemberAccessor_Struct_SE) {
+  auto* s = Structure("S", {Member("m", ty.i32())});
+  MakeSideEffectFunc("se", [&] { return ty.Of(s); });
+  auto* expr = MemberAccessor(Call("se"), "m");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, MemberAccessor_Vector) {
+  auto* var = Decl(Var("a", ty.vec4<f32>()));
+  auto* expr = MemberAccessor("a", "x");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  EXPECT_TRUE(sem->Is<sem::MemberAccessorExpression>());
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, MemberAccessor_VectorSwizzle) {
+  auto* var = Decl(Var("a", ty.vec4<f32>()));
+  auto* expr = MemberAccessor("a", "xzyw");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  EXPECT_TRUE(sem->Is<sem::Swizzle>());
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Binary_NoSE) {
+  auto* a = Decl(Var("a", ty.i32()));
+  auto* b = Decl(Var("b", ty.i32()));
+  auto* expr = Add("a", "b");
+  WrapInFunction(a, b, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Binary_LeftSE) {
+  MakeSideEffectFunc<i32>("se");
+  auto* b = Decl(Var("b", ty.i32()));
+  auto* expr = Add(Call("se"), "b");
+  WrapInFunction(b, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Binary_RightSE) {
+  MakeSideEffectFunc<i32>("se");
+  auto* a = Decl(Var("a", ty.i32()));
+  auto* expr = Add("a", Call("se"));
+  WrapInFunction(a, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Binary_BothSE) {
+  MakeSideEffectFunc<i32>("se1");
+  MakeSideEffectFunc<i32>("se2");
+  auto* expr = Add(Call("se1"), Call("se2"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Unary_NoSE) {
+  auto* var = Decl(Var("a", ty.bool_()));
+  auto* expr = Not("a");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Unary_SE) {
+  MakeSideEffectFunc<bool>("se");
+  auto* expr = Not(Call("se"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, IndexAccessor_NoSE) {
+  auto* var = Decl(Var("a", ty.array<i32, 10>()));
+  auto* expr = IndexAccessor("a", 0);
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, IndexAccessor_ObjSE) {
+  MakeSideEffectFunc("se", [&] { return ty.array<i32, 10>(); });
+  auto* expr = IndexAccessor(Call("se"), 0);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, IndexAccessor_IndexSE) {
+  MakeSideEffectFunc<i32>("se");
+  auto* var = Decl(Var("a", ty.array<i32, 10>()));
+  auto* expr = IndexAccessor("a", Call("se"));
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, IndexAccessor_BothSE) {
+  MakeSideEffectFunc("se1", [&] { return ty.array<i32, 10>(); });
+  MakeSideEffectFunc<i32>("se2");
+  auto* expr = IndexAccessor(Call("se1"), Call("se2"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Bitcast_NoSE) {
+  auto* var = Decl(Var("a", ty.i32()));
+  auto* expr = Bitcast<f32>("a");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_FALSE(sem->HasSideEffects());
+}
+
+TEST_F(SideEffectsTest, Bitcast_SE) {
+  MakeSideEffectFunc<i32>("se");
+  auto* expr = Bitcast<f32>(Call("se"));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  auto* sem = Sem().Get(expr);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->HasSideEffects());
+}
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/storage_class_layout_validation_test.cc b/src/tint/resolver/storage_class_layout_validation_test.cc
new file mode 100644
index 0000000..406fdb8
--- /dev/null
+++ b/src/tint/resolver/storage_class_layout_validation_test.cc
@@ -0,0 +1,573 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStorageClassLayoutValidationTest = ResolverTest;
+
+// Detect unaligned member for storage buffers
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       StorageBuffer_UnalignedMember) {
+  // [[block]]
+  // struct S {
+  //     @size(5) a : f32;
+  //     @align(1) b : f32;
+  // };
+  // @group(0) @binding(0)
+  // var<storage> a : S;
+
+  Structure(Source{{12, 34}}, "S",
+            {Member("a", ty.f32(), {MemberSize(5)}),
+             Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(1)})},
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
+         GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: the offset of a struct member of type 'f32' in storage class 'storage' must be a multiple of 4 bytes, but 'b' is currently at offset 5. Consider setting @align(4) on this member
+12:34 note: see layout of struct:
+/*           align(4) size(12) */ struct S {
+/* offset(0) align(4) size( 5) */   a : f32;
+/* offset(5) align(1) size( 4) */   b : f32;
+/* offset(9) align(1) size( 3) */   // -- implicit struct size padding --;
+/*                             */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       StorageBuffer_UnalignedMember_SuggestedFix) {
+  // [[block]]
+  // struct S {
+  //     @size(5) a : f32;
+  //     @align(4) b : f32;
+  // };
+  // @group(0) @binding(0)
+  // var<storage> a : S;
+
+  Structure(Source{{12, 34}}, "S",
+            {Member("a", ty.f32(), {MemberSize(5)}),
+             Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(4)})},
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
+         GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Detect unaligned struct member for uniform buffers
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_UnalignedMember_Struct) {
+  // struct Inner {
+  //   scalar : i32;
+  // };
+  //
+  // [[block]]
+  // struct Outer {
+  //   scalar : f32;
+  //   inner : Inner;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
+
+  Structure(Source{{34, 56}}, "Outer",
+            {
+                Member("scalar", ty.f32()),
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: the offset of a struct member of type 'Inner' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
+34:56 note: see layout of struct:
+/*           align(4) size(8) */ struct Outer {
+/* offset(0) align(4) size(4) */   scalar : f32;
+/* offset(4) align(4) size(4) */   inner : Inner;
+/*                            */ };
+12:34 note: and layout of struct member:
+/*           align(4) size(4) */ struct Inner {
+/* offset(0) align(4) size(4) */   scalar : i32;
+/*                            */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_UnalignedMember_Struct_SuggestedFix) {
+  // struct Inner {
+  //   scalar : i32;
+  // };
+  //
+  // [[block]]
+  // struct Outer {
+  //   scalar : f32;
+  //   @align(16) inner : Inner;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
+
+  Structure(Source{{34, 56}}, "Outer",
+            {
+                Member("scalar", ty.f32()),
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner"),
+                       {MemberAlign(16)}),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Detect unaligned array member for uniform buffers
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_UnalignedMember_Array) {
+  // type Inner = @stride(16) array<f32, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   scalar : f32;
+  //   inner : Inner;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+  Alias("Inner", ty.array(ty.f32(), 10, 16));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("scalar", ty.f32()),
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: the offset of a struct member of type '@stride(16) array<f32, 10>' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
+12:34 note: see layout of struct:
+/*             align(4) size(164) */ struct Outer {
+/* offset(  0) align(4) size(  4) */   scalar : f32;
+/* offset(  4) align(4) size(160) */   inner : @stride(16) array<f32, 10>;
+/*                                */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_UnalignedMember_Array_SuggestedFix) {
+  // type Inner = @stride(16) array<f32, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   scalar : f32;
+  //   @align(16) inner : Inner;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+  Alias("Inner", ty.array(ty.f32(), 10, 16));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("scalar", ty.f32()),
+                Member(Source{{34, 56}}, "inner", ty.type_name("Inner"),
+                       {MemberAlign(16)}),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Detect uniform buffers with byte offset between 2 members that is not a
+// multiple of 16 bytes
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_MembersOffsetNotMultipleOf16) {
+  // struct Inner {
+  //   @align(1) @size(5) scalar : i32;
+  // };
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Structure(Source{{12, 34}}, "Inner",
+            {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
+
+  Structure(Source{{34, 56}}, "Outer",
+            {
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
+                Member(Source{{78, 90}}, "scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 8 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
+34:56 note: see layout of struct:
+/*            align(4) size(12) */ struct Outer {
+/* offset( 0) align(1) size( 5) */   inner : Inner;
+/* offset( 5) align(1) size( 3) */   // -- implicit field alignment padding --;
+/* offset( 8) align(4) size( 4) */   scalar : i32;
+/*                              */ };
+12:34 note: and layout of previous member struct:
+/*           align(1) size(5) */ struct Inner {
+/* offset(0) align(1) size(5) */   scalar : i32;
+/*                            */ };
+22:24 note: see declaration of variable)");
+}
+
+// See https://crbug.com/tint/1344
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_MembersOffsetNotMultipleOf16_InnerMoreMembersThanOuter) {
+  // struct Inner {
+  //   a : i32;
+  //   b : i32;
+  //   c : i32;
+  //   @align(1) @size(5) scalar : i32;
+  // };
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Structure(Source{{12, 34}}, "Inner",
+            {
+                Member("a", ty.i32()),
+                Member("b", ty.i32()),
+                Member("c", ty.i32()),
+                Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)}),
+            });
+
+  Structure(Source{{34, 56}}, "Outer",
+            {
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
+                Member(Source{{78, 90}}, "scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 20 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
+34:56 note: see layout of struct:
+/*            align(4) size(24) */ struct Outer {
+/* offset( 0) align(4) size(20) */   inner : Inner;
+/* offset(20) align(4) size( 4) */   scalar : i32;
+/*                              */ };
+12:34 note: and layout of previous member struct:
+/*            align(4) size(20) */ struct Inner {
+/* offset( 0) align(4) size( 4) */   a : i32;
+/* offset( 4) align(4) size( 4) */   b : i32;
+/* offset( 8) align(4) size( 4) */   c : i32;
+/* offset(12) align(1) size( 5) */   scalar : i32;
+/* offset(17) align(1) size( 3) */   // -- implicit struct size padding --;
+/*                              */ };
+22:24 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_MembersOffsetNotMultipleOf16_SuggestedFix) {
+  // struct Inner {
+  //   @align(1) @size(5) scalar : i32;
+  // };
+  //
+  // [[block]]
+  // struct Outer {
+  //   @align(16) inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Structure(Source{{12, 34}}, "Inner",
+            {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
+
+  Structure(Source{{34, 56}}, "Outer",
+            {
+                Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
+                Member(Source{{78, 90}}, "scalar", ty.i32(), {MemberAlign(16)}),
+            },
+            {StructBlock()});
+
+  Global(Source{{22, 34}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Make sure that this doesn't fail validation because vec3's align is 16, but
+// size is 12. 's' should be at offset 12, which is okay here.
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_Vec3MemberOffset_NoFail) {
+  // [[block]]
+  // struct ScalarPackedAtEndOfVec3 {
+  //     v : vec3<f32>;
+  //     s : f32;
+  // };
+  // @group(0) @binding(0)
+  // var<uniform> a : ScalarPackedAtEndOfVec3;
+
+  Structure("ScalarPackedAtEndOfVec3",
+            {
+                Member("v", ty.vec3(ty.f32())),
+                Member("s", ty.f32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// Detect array stride must be a multiple of 16 bytes for uniform buffers
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_Scalar) {
+  // type Inner = array<f32, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Alias("Inner", ty.array(ty.f32(), 10));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
+                Member("scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
+12:34 note: see layout of struct:
+/*            align(4) size(44) */ struct Outer {
+/* offset( 0) align(4) size(40) */   inner : array<f32, 10>;
+/* offset(40) align(4) size( 4) */   scalar : i32;
+/*                              */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_Vector) {
+  // type Inner = array<vec2<f32>, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Alias("Inner", ty.array(ty.vec2<f32>(), 10));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
+                Member("scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using a vec4 instead.
+12:34 note: see layout of struct:
+/*            align(8) size(88) */ struct Outer {
+/* offset( 0) align(8) size(80) */   inner : array<vec2<f32>, 10>;
+/* offset(80) align(4) size( 4) */   scalar : i32;
+/* offset(84) align(1) size( 4) */   // -- implicit struct size padding --;
+/*                              */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_Struct) {
+  // struct ArrayElem {
+  //   a : f32;
+  //   b : i32;
+  // }
+  // type Inner = array<ArrayElem, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  auto* array_elem = Structure("ArrayElem", {
+                                                Member("a", ty.f32()),
+                                                Member("b", ty.i32()),
+                                            });
+  Alias("Inner", ty.array(ty.Of(array_elem), 10));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
+                Member("scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using the @size attribute on the last struct member.
+12:34 note: see layout of struct:
+/*            align(4) size(84) */ struct Outer {
+/* offset( 0) align(4) size(80) */   inner : array<ArrayElem, 10>;
+/* offset(80) align(4) size( 4) */   scalar : i32;
+/*                              */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_TopLevelArray) {
+  // @group(0) @binding(0)
+  // var<uniform> a : array<f32, 4>;
+  Global(Source{{78, 90}}, "a", ty.array(Source{{34, 56}}, ty.f32(), 4),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_NestedArray) {
+  // struct Outer {
+  //   inner : array<array<f32, 4>, 4>
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : array<Outer, 4>;
+
+  Structure(
+      Source{{12, 34}}, "Outer",
+      {
+          Member("inner", ty.array(Source{{34, 56}}, ty.array(ty.f32(), 4), 4)),
+      },
+      {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
+12:34 note: see layout of struct:
+/*            align(4) size(64) */ struct Outer {
+/* offset( 0) align(4) size(64) */   inner : array<array<f32, 4>, 4>;
+/*                              */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest,
+       UniformBuffer_InvalidArrayStride_SuggestedFix) {
+  // type Inner = @stride(16) array<f32, 10>;
+  //
+  // [[block]]
+  // struct Outer {
+  //   inner : Inner;
+  //   scalar : i32;
+  // };
+  //
+  // @group(0) @binding(0)
+  // var<uniform> a : Outer;
+
+  Alias("Inner", ty.array(ty.f32(), 10, 16));
+
+  Structure(Source{{12, 34}}, "Outer",
+            {
+                Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
+                Member("scalar", ty.i32()),
+            },
+            {StructBlock()});
+
+  Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
+         ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/storage_class_validation_test.cc b/src/tint/resolver/storage_class_validation_test.cc
new file mode 100644
index 0000000..0c33b8b
--- /dev/null
+++ b/src/tint/resolver/storage_class_validation_test.cc
@@ -0,0 +1,370 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/struct.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStorageClassValidationTest = ResolverTest;
+
+TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
+  // var g : f32;
+  Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: global variables must have a storage class");
+}
+
+TEST_F(ResolverStorageClassValidationTest,
+       GlobalVariableFunctionStorageClass_Fail) {
+  // var<function> g : f32;
+  Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kFunction);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: variables declared at module scope must not be in "
+            "the function storage class");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) {
+  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArrayInStruct) {
+  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArray) {
+  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
+         ast::StorageClass::kWorkgroup);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArrayInStruct) {
+  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kWorkgroup);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
+  // var<storage> g : bool;
+  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
+  // var<storage> g : ptr<private, f32>;
+  Global(Source{{56, 78}}, "g",
+         ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
+         ast::StorageClass::kStorage,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'storage' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
+  // var<storage> g : i32;
+  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferVector) {
+  // var<storage> g : vec4<f32>;
+  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
+  // var<storage, read> g : array<S, 3>;
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = ty.array(ty.Of(s), 3);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
+  // type a = bool;
+  // var<storage, read> g : a;
+  auto* a = Alias("a", ty.bool_());
+  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, NotStorage_AccessMode) {
+  // var<private, read> g : a;
+  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kPrivate,
+         ast::Access::kRead);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: only variables in <storage> storage class may declare an access mode)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
+  // [[block]] struct S { x : i32 };
+  // var<storage, read> g : S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
+  // [[block]] struct S { x : i32 };
+  // type a1 = S;
+  // var<storage, read> g : a1;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* a1 = Alias("a1", ty.Of(s));
+  auto* a2 = Alias("a2", ty.Of(a1));
+  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
+         ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBuffer_Struct_Runtime) {
+  // [[block]] struct S { m:  array<f32>; };
+  // @group(0) @binding(0) var<uniform, > svar : S;
+
+  auto* s = Structure(Source{{12, 34}}, "S", {Member("m", ty.array<i32>())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+56:78 note: while instantiating variable svar)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
+  // var<uniform> g : bool;
+  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
+  // var<uniform> g : ptr<private, f32>;
+  Global(Source{{56, 78}}, "g",
+         ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
+         ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'uniform' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
+  // var<uniform> g : i32;
+  Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferVector) {
+  // var<uniform> g : vec4<f32>;
+  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
+  // struct S {
+  //   @size(16) f : f32;
+  // }
+  // var<uniform> g : array<S, 3>;
+  auto* s = Structure("S", {Member("a", ty.f32(), {MemberSize(16)})});
+  auto* a = ty.array(ty.Of(s), 3);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
+  // type a = bool;
+  // var<uniform> g : a;
+  auto* a = Alias("a", ty.bool_());
+  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
+  // [[block]] struct S { x : i32 };
+  // var<uniform> g :  S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Aliases) {
+  // [[block]] struct S { x : i32 };
+  // type a1 = S;
+  // var<uniform> g : a1;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* a1 = Alias("a1", ty.Of(s));
+  Global(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/struct_layout_test.cc b/src/tint/resolver/struct_layout_test.cc
new file mode 100644
index 0000000..f8e76fd
--- /dev/null
+++ b/src/tint/resolver/struct_layout_test.cc
@@ -0,0 +1,410 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/struct.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStructLayoutTest = ResolverTest;
+
+TEST_F(ResolverStructLayoutTest, Scalars) {
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.u32()),
+                               Member("c", ty.i32()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 12u);
+  EXPECT_EQ(sem->SizeNoPadding(), 12u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 8u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, Alias) {
+  auto* alias_a = Alias("a", ty.f32());
+  auto* alias_b = Alias("b", ty.f32());
+
+  auto* s = Structure("S", {
+                               Member("a", ty.Of(alias_a)),
+                               Member("b", ty.Of(alias_b)),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 8u);
+  EXPECT_EQ(sem->SizeNoPadding(), 8u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 2u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
+  auto* s = Structure("S", {
+                               Member("a", ty.array<i32, 3>()),
+                               Member("b", ty.array<f32, 5>()),
+                               Member("c", ty.array<f32, 1>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 36u);
+  EXPECT_EQ(sem->SizeNoPadding(), 36u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 12u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 12u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 20u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
+  auto* s = Structure("S", {
+                               Member("a", ty.array<i32, 3>(/*stride*/ 8)),
+                               Member("b", ty.array<f32, 5>(/*stride*/ 16)),
+                               Member("c", ty.array<f32, 1>(/*stride*/ 32)),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 136u);
+  EXPECT_EQ(sem->SizeNoPadding(), 136u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 24u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 24u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 80u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 104u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
+  auto* s = Structure("S",
+                      {
+                          Member("c", ty.array<f32>()),
+                      },
+                      ast::AttributeList{create<ast::StructBlockAttribute>()});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 4u);
+  EXPECT_EQ(sem->SizeNoPadding(), 4u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
+  auto* s = Structure("S",
+                      {
+                          Member("c", ty.array<f32>(/*stride*/ 32)),
+                      },
+                      ast::AttributeList{create<ast::StructBlockAttribute>()});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 32u);
+  EXPECT_EQ(sem->SizeNoPadding(), 32u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
+  auto* inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
+  auto* outer = ty.array(inner, 12);              // size: 12 * 32
+  auto* s = Structure("S", {
+                               Member("c", outer),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 384u);
+  EXPECT_EQ(sem->SizeNoPadding(), 384u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 384u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec2<i32>()),
+                                       Member("b", ty.vec3<i32>()),
+                                       Member("c", ty.vec4<i32>()),
+                                   });       // size: 48
+  auto* outer = ty.array(ty.Of(inner), 12);  // size: 12 * 48
+  auto* s = Structure("S", {
+                               Member("c", outer),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 576u);
+  EXPECT_EQ(sem->SizeNoPadding(), 576u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 576u);
+}
+
+TEST_F(ResolverStructLayoutTest, Vector) {
+  auto* s = Structure("S", {
+                               Member("a", ty.vec2<i32>()),
+                               Member("b", ty.vec3<i32>()),
+                               Member("c", ty.vec4<i32>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 48u);
+  EXPECT_EQ(sem->SizeNoPadding(), 48u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // vec2
+  EXPECT_EQ(sem->Members()[0]->Align(), 8u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 8u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // vec3
+  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 12u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);  // vec4
+  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 16u);
+}
+
+TEST_F(ResolverStructLayoutTest, Matrix) {
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<f32>()),
+                               Member("b", ty.mat2x3<f32>()),
+                               Member("c", ty.mat2x4<f32>()),
+                               Member("d", ty.mat3x2<f32>()),
+                               Member("e", ty.mat3x3<f32>()),
+                               Member("f", ty.mat3x4<f32>()),
+                               Member("g", ty.mat4x2<f32>()),
+                               Member("h", ty.mat4x3<f32>()),
+                               Member("i", ty.mat4x4<f32>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 368u);
+  EXPECT_EQ(sem->SizeNoPadding(), 368u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 9u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // mat2x2
+  EXPECT_EQ(sem->Members()[0]->Align(), 8u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // mat2x3
+  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 48u);  // mat2x4
+  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 80u);  // mat3x2
+  EXPECT_EQ(sem->Members()[3]->Align(), 8u);
+  EXPECT_EQ(sem->Members()[3]->Size(), 24u);
+  EXPECT_EQ(sem->Members()[4]->Offset(), 112u);  // mat3x3
+  EXPECT_EQ(sem->Members()[4]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[4]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[5]->Offset(), 160u);  // mat3x4
+  EXPECT_EQ(sem->Members()[5]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[5]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[6]->Offset(), 208u);  // mat4x2
+  EXPECT_EQ(sem->Members()[6]->Align(), 8u);
+  EXPECT_EQ(sem->Members()[6]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[7]->Offset(), 240u);  // mat4x3
+  EXPECT_EQ(sem->Members()[7]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[7]->Size(), 64u);
+  EXPECT_EQ(sem->Members()[8]->Offset(), 304u);  // mat4x4
+  EXPECT_EQ(sem->Members()[8]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[8]->Size(), 64u);
+}
+
+TEST_F(ResolverStructLayoutTest, NestedStruct) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.mat3x3<f32>()),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.Of(inner)),
+                               Member("c", ty.i32()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 80u);
+  EXPECT_EQ(sem->SizeNoPadding(), 68u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 64u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, SizeAttributes) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.f32(), {MemberSize(8)}),
+                                       Member("b", ty.f32(), {MemberSize(16)}),
+                                       Member("c", ty.f32(), {MemberSize(8)}),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.f32(), {MemberSize(4)}),
+                               Member("b", ty.u32(), {MemberSize(8)}),
+                               Member("c", ty.Of(inner)),
+                               Member("d", ty.i32(), {MemberSize(32)}),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 76u);
+  EXPECT_EQ(sem->SizeNoPadding(), 76u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 8u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 12u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 44u);
+  EXPECT_EQ(sem->Members()[3]->Align(), 4u);
+  EXPECT_EQ(sem->Members()[3]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, AlignAttributes) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.f32(), {MemberAlign(8)}),
+                                       Member("b", ty.f32(), {MemberAlign(16)}),
+                                       Member("c", ty.f32(), {MemberAlign(4)}),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.f32(), {MemberAlign(4)}),
+                               Member("b", ty.u32(), {MemberAlign(8)}),
+                               Member("c", ty.Of(inner)),
+                               Member("d", ty.i32(), {MemberAlign(32)}),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 96u);
+  EXPECT_EQ(sem->SizeNoPadding(), 68u);
+  EXPECT_EQ(sem->Align(), 32u);
+  ASSERT_EQ(sem->Members().size(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  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(), 8u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 16u);
+  EXPECT_EQ(sem->Members()[2]->Align(), 16u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 64u);
+  EXPECT_EQ(sem->Members()[3]->Align(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, StructWithLotsOfPadding) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32(), {MemberAlign(1024)}),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 1024u);
+  EXPECT_EQ(sem->SizeNoPadding(), 4u);
+  EXPECT_EQ(sem->Align(), 1024u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Align(), 1024u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/struct_pipeline_stage_use_test.cc b/src/tint/resolver/struct_pipeline_stage_use_test.cc
new file mode 100644
index 0000000..e13b5f2
--- /dev/null
+++ b/src/tint/resolver/struct_pipeline_stage_use_test.cc
@@ -0,0 +1,191 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/struct.h"
+
+using ::testing::UnorderedElementsAre;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverPipelineStageUseTest = ResolverTest;
+
+TEST_F(ResolverPipelineStageUseTest, UnusedStruct) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->PipelineStageUses().empty());
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  Func("foo", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->PipelineStageUses().empty());
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  Func("foo", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))}, {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->PipelineStageUses().empty());
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
+  auto* s = Structure(
+      "S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+
+  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+
+  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
+  auto* s = Structure(
+      "S",
+      {Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
+
+  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kComputeInput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
+  auto* s = Structure(
+      "S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+
+  Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput,
+                                   sem::PipelineStageUsage::kFragmentInput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto* s_alias = Alias("S_alias", ty.Of(s));
+
+  Func("main", {Param("param", ty.Of(s_alias))}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
+}
+
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto* s_alias = Alias("S_alias", ty.Of(s));
+
+  Func("main", {}, ty.Of(s_alias),
+       {Return(Construct(ty.Of(s_alias), Expr(0.f)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->PipelineStageUses(),
+              UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/struct_storage_class_use_test.cc b/src/tint/resolver/struct_storage_class_use_test.cc
new file mode 100644
index 0000000..2c0e9cf
--- /dev/null
+++ b/src/tint/resolver/struct_storage_class_use_test.cc
@@ -0,0 +1,197 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/struct.h"
+
+using ::testing::UnorderedElementsAre;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStorageClassUseTest = ResolverTest;
+
+TEST_F(ResolverStorageClassUseTest, UnreachableStruct) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->StorageClassUsage().empty());
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+
+  Func("f", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kNone));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+
+  Func("f", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kNone));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kPrivate));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = Alias("A", ty.Of(s));
+  Global("g", ty.Of(a), ast::StorageClass::kPrivate);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kPrivate));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* o = Structure("O", {Member("a", ty.Of(s))});
+  Global("g", ty.Of(o), ast::StorageClass::kPrivate);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kPrivate));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = ty.array(ty.Of(s), 3);
+  Global("g", a, ast::StorageClass::kPrivate);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kPrivate));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+
+  WrapInFunction(Var("g", ty.Of(s)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kFunction));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = Alias("A", ty.Of(s));
+  WrapInFunction(Var("g", ty.Of(a)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kFunction));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* o = Structure("O", {Member("a", ty.Of(s))});
+  WrapInFunction(Var("g", ty.Of(o)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kFunction));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = ty.array(ty.Of(s), 3);
+  WrapInFunction(Var("g", a));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kFunction));
+}
+
+TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
+  auto* s = Structure("S", {Member("a", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("x", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+  Global("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(0),
+         });
+  WrapInFunction(Var("g", ty.Of(s)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = TypeOf(s)->As<sem::Struct>();
+  ASSERT_NE(sem, nullptr);
+  EXPECT_THAT(sem->StorageClassUsage(),
+              UnorderedElementsAre(ast::StorageClass::kUniform,
+                                   ast::StorageClass::kStorage,
+                                   ast::StorageClass::kFunction));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/type_constructor_validation_test.cc b/src/tint/resolver/type_constructor_validation_test.cc
new file mode 100644
index 0000000..21bc698
--- /dev/null
+++ b/src/tint/resolver/type_constructor_validation_test.cc
@@ -0,0 +1,2937 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ::testing::HasSubstr;
+
+// Helpers and typedefs
+using builder::alias;
+using builder::alias1;
+using builder::alias2;
+using builder::alias3;
+using builder::CreatePtrs;
+using builder::CreatePtrsFor;
+using builder::DataType;
+using builder::f32;
+using builder::i32;
+using builder::mat2x2;
+using builder::mat2x3;
+using builder::mat3x2;
+using builder::mat3x3;
+using builder::mat4x4;
+using builder::u32;
+using builder::vec2;
+using builder::vec3;
+using builder::vec4;
+
+class ResolverTypeConstructorValidationTest : public resolver::TestHelper,
+                                              public testing::Test {};
+
+namespace InferTypeTest {
+struct Params {
+  builder::ast_type_func_ptr create_rhs_ast_type;
+  builder::ast_expr_func_ptr create_rhs_ast_value;
+  builder::sem_type_func_ptr create_rhs_sem_type;
+};
+
+template <typename T>
+constexpr Params ParamsFor() {
+  return Params{DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem};
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferTypeTest_Simple) {
+  // var a = 1;
+  // var b = a;
+  auto* a = Var("a", nullptr, ast::StorageClass::kNone, Expr(1));
+  auto* b = Var("b", nullptr, ast::StorageClass::kNone, Expr("a"));
+  auto* a_ident = Expr("a");
+  auto* b_ident = Expr("b");
+
+  WrapInFunction(a, b, Assign(a_ident, "a"), Assign(b_ident, "b"));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_TRUE(TypeOf(a_ident)->Is<sem::Reference>());
+  EXPECT_TRUE(
+      TypeOf(a_ident)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(a_ident)->As<sem::Reference>()->StorageClass(),
+            ast::StorageClass::kFunction);
+  ASSERT_TRUE(TypeOf(b_ident)->Is<sem::Reference>());
+  EXPECT_TRUE(
+      TypeOf(b_ident)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(b_ident)->As<sem::Reference>()->StorageClass(),
+            ast::StorageClass::kFunction);
+}
+
+using InferTypeTest_FromConstructorExpression = ResolverTestWithParam<Params>;
+TEST_P(InferTypeTest_FromConstructorExpression, All) {
+  // e.g. for vec3<f32>
+  // {
+  //   var a = vec3<f32>(0.0, 0.0, 0.0)
+  // }
+  auto& params = GetParam();
+
+  auto* constructor_expr = params.create_rhs_ast_value(*this, 0);
+
+  auto* a = Var("a", nullptr, ast::StorageClass::kNone, constructor_expr);
+  // Self-assign 'a' to force the expression to be resolved so we can test its
+  // type below
+  auto* a_ident = Expr("a");
+  WrapInFunction(Decl(a), Assign(a_ident, "a"));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  auto* got = TypeOf(a_ident);
+  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
+                                          ast::StorageClass::kFunction,
+                                          ast::Access::kReadWrite);
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
+}
+
+static constexpr Params from_constructor_expression_cases[] = {
+    ParamsFor<bool>(),
+    ParamsFor<i32>(),
+    ParamsFor<u32>(),
+    ParamsFor<f32>(),
+    ParamsFor<vec3<i32>>(),
+    ParamsFor<vec3<u32>>(),
+    ParamsFor<vec3<f32>>(),
+    ParamsFor<mat3x3<f32>>(),
+    ParamsFor<alias<bool>>(),
+    ParamsFor<alias<i32>>(),
+    ParamsFor<alias<u32>>(),
+    ParamsFor<alias<f32>>(),
+    ParamsFor<alias<vec3<i32>>>(),
+    ParamsFor<alias<vec3<u32>>>(),
+    ParamsFor<alias<vec3<f32>>>(),
+    ParamsFor<alias<mat3x3<f32>>>(),
+};
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         InferTypeTest_FromConstructorExpression,
+                         testing::ValuesIn(from_constructor_expression_cases));
+
+using InferTypeTest_FromArithmeticExpression = ResolverTestWithParam<Params>;
+TEST_P(InferTypeTest_FromArithmeticExpression, All) {
+  // e.g. for vec3<f32>
+  // {
+  //   var a = vec3<f32>(2.0, 2.0, 2.0) * 3.0;
+  // }
+  auto& params = GetParam();
+
+  auto* arith_lhs_expr = params.create_rhs_ast_value(*this, 2);
+  auto* arith_rhs_expr = params.create_rhs_ast_value(*this, 3);
+  auto* constructor_expr = Mul(arith_lhs_expr, arith_rhs_expr);
+
+  auto* a = Var("a", nullptr, constructor_expr);
+  // Self-assign 'a' to force the expression to be resolved so we can test its
+  // type below
+  auto* a_ident = Expr("a");
+  WrapInFunction(Decl(a), Assign(a_ident, "a"));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  auto* got = TypeOf(a_ident);
+  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
+                                          ast::StorageClass::kFunction,
+                                          ast::Access::kReadWrite);
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
+}
+static constexpr Params from_arithmetic_expression_cases[] = {
+    ParamsFor<i32>(),       ParamsFor<u32>(),         ParamsFor<f32>(),
+    ParamsFor<vec3<f32>>(), ParamsFor<mat3x3<f32>>(),
+
+    // TODO(amaiorano): Uncomment once https://crbug.com/tint/680 is fixed
+    // ParamsFor<alias<ty_i32>>(),
+    // ParamsFor<alias<ty_u32>>(),
+    // ParamsFor<alias<ty_f32>>(),
+    // ParamsFor<alias<ty_vec3<f32>>>(),
+    // ParamsFor<alias<ty_mat3x3<f32>>>(),
+};
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         InferTypeTest_FromArithmeticExpression,
+                         testing::ValuesIn(from_arithmetic_expression_cases));
+
+using InferTypeTest_FromCallExpression = ResolverTestWithParam<Params>;
+TEST_P(InferTypeTest_FromCallExpression, All) {
+  // e.g. for vec3<f32>
+  //
+  // fn foo() -> vec3<f32> {
+  //   return vec3<f32>();
+  // }
+  //
+  // fn bar()
+  // {
+  //   var a = foo();
+  // }
+  auto& params = GetParam();
+
+  Func("foo", {}, params.create_rhs_ast_type(*this),
+       {Return(Construct(params.create_rhs_ast_type(*this)))}, {});
+
+  auto* a = Var("a", nullptr, Call("foo"));
+  // Self-assign 'a' to force the expression to be resolved so we can test its
+  // type below
+  auto* a_ident = Expr("a");
+  WrapInFunction(Decl(a), Assign(a_ident, "a"));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+  auto* got = TypeOf(a_ident);
+  auto* expected = create<sem::Reference>(params.create_rhs_sem_type(*this),
+                                          ast::StorageClass::kFunction,
+                                          ast::Access::kReadWrite);
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
+}
+static constexpr Params from_call_expression_cases[] = {
+    ParamsFor<bool>(),
+    ParamsFor<i32>(),
+    ParamsFor<u32>(),
+    ParamsFor<f32>(),
+    ParamsFor<vec3<i32>>(),
+    ParamsFor<vec3<u32>>(),
+    ParamsFor<vec3<f32>>(),
+    ParamsFor<mat3x3<f32>>(),
+    ParamsFor<alias<bool>>(),
+    ParamsFor<alias<i32>>(),
+    ParamsFor<alias<u32>>(),
+    ParamsFor<alias<f32>>(),
+    ParamsFor<alias<vec3<i32>>>(),
+    ParamsFor<alias<vec3<u32>>>(),
+    ParamsFor<alias<vec3<f32>>>(),
+    ParamsFor<alias<mat3x3<f32>>>(),
+};
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         InferTypeTest_FromCallExpression,
+                         testing::ValuesIn(from_call_expression_cases));
+
+}  // namespace InferTypeTest
+
+namespace ConversionConstructTest {
+enum class Kind {
+  Construct,
+  Conversion,
+};
+
+struct Params {
+  Kind kind;
+  builder::ast_type_func_ptr lhs_type;
+  builder::ast_type_func_ptr rhs_type;
+  builder::ast_expr_func_ptr rhs_value_expr;
+};
+
+template <typename LhsType, typename RhsType>
+constexpr Params ParamsFor(Kind kind) {
+  return Params{kind, DataType<LhsType>::AST, DataType<RhsType>::AST,
+                DataType<RhsType>::Expr};
+}
+
+static constexpr Params valid_cases[] = {
+    // Direct init (non-conversions)
+    ParamsFor<bool, bool>(Kind::Construct),              //
+    ParamsFor<i32, i32>(Kind::Construct),                //
+    ParamsFor<u32, u32>(Kind::Construct),                //
+    ParamsFor<f32, f32>(Kind::Construct),                //
+    ParamsFor<vec3<bool>, vec3<bool>>(Kind::Construct),  //
+    ParamsFor<vec3<i32>, vec3<i32>>(Kind::Construct),    //
+    ParamsFor<vec3<u32>, vec3<u32>>(Kind::Construct),    //
+    ParamsFor<vec3<f32>, vec3<f32>>(Kind::Construct),    //
+
+    // Splat
+    ParamsFor<vec3<bool>, bool>(Kind::Construct),  //
+    ParamsFor<vec3<i32>, i32>(Kind::Construct),    //
+    ParamsFor<vec3<u32>, u32>(Kind::Construct),    //
+    ParamsFor<vec3<f32>, f32>(Kind::Construct),    //
+
+    // Conversion
+    ParamsFor<bool, u32>(Kind::Conversion),  //
+    ParamsFor<bool, i32>(Kind::Conversion),  //
+    ParamsFor<bool, f32>(Kind::Conversion),  //
+
+    ParamsFor<i32, bool>(Kind::Conversion),  //
+    ParamsFor<i32, u32>(Kind::Conversion),   //
+    ParamsFor<i32, f32>(Kind::Conversion),   //
+
+    ParamsFor<u32, bool>(Kind::Conversion),  //
+    ParamsFor<u32, i32>(Kind::Conversion),   //
+    ParamsFor<u32, f32>(Kind::Conversion),   //
+
+    ParamsFor<f32, bool>(Kind::Conversion),  //
+    ParamsFor<f32, u32>(Kind::Conversion),   //
+    ParamsFor<f32, i32>(Kind::Conversion),   //
+
+    ParamsFor<vec3<bool>, vec3<u32>>(Kind::Conversion),  //
+    ParamsFor<vec3<bool>, vec3<i32>>(Kind::Conversion),  //
+    ParamsFor<vec3<bool>, vec3<f32>>(Kind::Conversion),  //
+
+    ParamsFor<vec3<i32>, vec3<bool>>(Kind::Conversion),  //
+    ParamsFor<vec3<i32>, vec3<u32>>(Kind::Conversion),   //
+    ParamsFor<vec3<i32>, vec3<f32>>(Kind::Conversion),   //
+
+    ParamsFor<vec3<u32>, vec3<bool>>(Kind::Conversion),  //
+    ParamsFor<vec3<u32>, vec3<i32>>(Kind::Conversion),   //
+    ParamsFor<vec3<u32>, vec3<f32>>(Kind::Conversion),   //
+
+    ParamsFor<vec3<f32>, vec3<bool>>(Kind::Conversion),  //
+    ParamsFor<vec3<f32>, vec3<u32>>(Kind::Conversion),   //
+    ParamsFor<vec3<f32>, vec3<i32>>(Kind::Conversion),   //
+};
+
+using ConversionConstructorValidTest = ResolverTestWithParam<Params>;
+TEST_P(ConversionConstructorValidTest, All) {
+  auto& params = GetParam();
+
+  // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
+  auto* lhs_type1 = params.lhs_type(*this);
+  auto* lhs_type2 = params.lhs_type(*this);
+  auto* rhs_type = params.rhs_type(*this);
+  auto* rhs_value_expr = params.rhs_value_expr(*this, 0);
+
+  std::stringstream ss;
+  ss << FriendlyName(lhs_type1) << " = " << FriendlyName(lhs_type2) << "("
+     << FriendlyName(rhs_type) << "(<rhs value expr>))";
+  SCOPED_TRACE(ss.str());
+
+  auto* arg = Construct(rhs_type, rhs_value_expr);
+  auto* tc = Construct(lhs_type2, arg);
+  auto* a = Var("a", lhs_type1, ast::StorageClass::kNone, tc);
+
+  // Self-assign 'a' to force the expression to be resolved so we can test its
+  // type below
+  auto* a_ident = Expr("a");
+  WrapInFunction(Decl(a), Assign(a_ident, "a"));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  switch (params.kind) {
+    case Kind::Construct: {
+      auto* ctor = call->Target()->As<sem::TypeConstructor>();
+      ASSERT_NE(ctor, nullptr);
+      EXPECT_EQ(call->Type(), ctor->ReturnType());
+      ASSERT_EQ(ctor->Parameters().size(), 1u);
+      EXPECT_EQ(ctor->Parameters()[0]->Type(), TypeOf(arg));
+      break;
+    }
+    case Kind::Conversion: {
+      auto* conv = call->Target()->As<sem::TypeConversion>();
+      ASSERT_NE(conv, nullptr);
+      EXPECT_EQ(call->Type(), conv->ReturnType());
+      ASSERT_EQ(conv->Parameters().size(), 1u);
+      EXPECT_EQ(conv->Parameters()[0]->Type(), TypeOf(arg));
+      break;
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         ConversionConstructorValidTest,
+                         testing::ValuesIn(valid_cases));
+
+constexpr CreatePtrs all_types[] = {
+    CreatePtrsFor<bool>(),         //
+    CreatePtrsFor<u32>(),          //
+    CreatePtrsFor<i32>(),          //
+    CreatePtrsFor<f32>(),          //
+    CreatePtrsFor<vec3<bool>>(),   //
+    CreatePtrsFor<vec3<i32>>(),    //
+    CreatePtrsFor<vec3<u32>>(),    //
+    CreatePtrsFor<vec3<f32>>(),    //
+    CreatePtrsFor<mat3x3<i32>>(),  //
+    CreatePtrsFor<mat3x3<u32>>(),  //
+    CreatePtrsFor<mat3x3<f32>>(),  //
+    CreatePtrsFor<mat2x3<i32>>(),  //
+    CreatePtrsFor<mat2x3<u32>>(),  //
+    CreatePtrsFor<mat2x3<f32>>(),  //
+    CreatePtrsFor<mat3x2<i32>>(),  //
+    CreatePtrsFor<mat3x2<u32>>(),  //
+    CreatePtrsFor<mat3x2<f32>>()   //
+};
+
+using ConversionConstructorInvalidTest =
+    ResolverTestWithParam<std::tuple<CreatePtrs,  // lhs
+                                     CreatePtrs   // rhs
+                                     >>;
+TEST_P(ConversionConstructorInvalidTest, All) {
+  auto& params = GetParam();
+
+  auto& lhs_params = std::get<0>(params);
+  auto& rhs_params = std::get<1>(params);
+
+  // Skip test for valid cases
+  for (auto& v : valid_cases) {
+    if (v.lhs_type == lhs_params.ast && v.rhs_type == rhs_params.ast &&
+        v.rhs_value_expr == rhs_params.expr) {
+      return;
+    }
+  }
+  // Skip non-conversions
+  if (lhs_params.ast == rhs_params.ast) {
+    return;
+  }
+
+  // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
+  auto* lhs_type1 = lhs_params.ast(*this);
+  auto* lhs_type2 = lhs_params.ast(*this);
+  auto* rhs_type = rhs_params.ast(*this);
+  auto* rhs_value_expr = rhs_params.expr(*this, 0);
+
+  std::stringstream ss;
+  ss << FriendlyName(lhs_type1) << " = " << FriendlyName(lhs_type2) << "("
+     << FriendlyName(rhs_type) << "(<rhs value expr>))";
+  SCOPED_TRACE(ss.str());
+
+  auto* a = Var("a", lhs_type1, ast::StorageClass::kNone,
+                Construct(lhs_type2, Construct(rhs_type, rhs_value_expr)));
+
+  // Self-assign 'a' to force the expression to be resolved so we can test its
+  // type below
+  auto* a_ident = Expr("a");
+  WrapInFunction(Decl(a), Assign(a_ident, "a"));
+
+  ASSERT_FALSE(r()->Resolve());
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         ConversionConstructorInvalidTest,
+                         testing::Combine(testing::ValuesIn(all_types),
+                                          testing::ValuesIn(all_types)));
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       ConversionConstructorInvalid_TooManyInitializers) {
+  auto* a = Var("a", ty.f32(), ast::StorageClass::kNone,
+                Construct(Source{{12, 34}}, ty.f32(), Expr(1.0f), Expr(2.0f)));
+  WrapInFunction(a);
+
+  ASSERT_FALSE(r()->Resolve());
+  ASSERT_EQ(r()->error(),
+            "12:34 error: expected zero or one value in constructor, got 2");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       ConversionConstructorInvalid_InvalidInitializer) {
+  auto* a =
+      Var("a", ty.f32(), ast::StorageClass::kNone,
+          Construct(Source{{12, 34}}, ty.f32(), Construct(ty.array<f32, 4>())));
+  WrapInFunction(a);
+
+  ASSERT_FALSE(r()->Resolve());
+  ASSERT_EQ(r()->error(),
+            "12:34 error: cannot construct 'f32' with a value of type "
+            "'array<f32, 4>'");
+}
+
+}  // namespace ConversionConstructTest
+
+namespace ArrayConstructor {
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_ZeroValue_Pass) {
+  // array<u32, 10>();
+  auto* tc = array<u32, 10>();
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  EXPECT_TRUE(call->Type()->Is<sem::Array>());
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 0u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_type_match) {
+  // array<u32, 3>(0u, 10u. 20u);
+  auto* tc = array<u32, 3>(Expr(0u), Expr(10u), Expr(20u));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  EXPECT_TRUE(call->Type()->Is<sem::Array>());
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_type_Mismatch_U32F32) {
+  // array<u32, 3>(0u, 1.0f, 20u);
+  auto* tc = array<u32, 3>(Expr(0u), Expr(Source{{12, 34}}, 1.0f), Expr(20u));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'u32', found 'f32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_ScalarArgumentTypeMismatch_F32I32) {
+  // array<f32, 1>(1);
+  auto* tc = array<f32, 1>(Expr(Source{{12, 34}}, 1));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_ScalarArgumentTypeMismatch_U32I32) {
+  // array<u32, 6>(1, 0u, 0u, 0u, 0u, 0u);
+  auto* tc = array<u32, 1>(Expr(Source{{12, 34}}, 1), Expr(0u), Expr(0u),
+                           Expr(0u), Expr(0u));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_ScalarArgumentTypeMismatch_Vec2) {
+  // array<i32, 3>(1, vec2<i32>());
+  auto* tc =
+      array<i32, 3>(Expr(1), Construct(Source{{12, 34}}, ty.vec2<i32>()));
+  WrapInFunction(tc);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'i32', found 'vec2<i32>'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32U32) {
+  // array<vec3<i32>, 2>(vec3<i32>(), vec3<u32>());
+  auto* e0 = vec3<i32>();
+  SetSource(Source::Location({12, 34}));
+  auto* e1 = vec3<u32>();
+  auto* t = Construct(ty.array(ty.vec3<i32>(), 2), e0, e1);
+  WrapInFunction(t);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'vec3<i32>', found 'vec3<u32>'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32Bool) {
+  // array<vec3<i32>, 2>(vec3<i32>(), vec3<bool>(true, true, false));
+  SetSource(Source::Location({12, 34}));
+  auto* e0 = vec3<bool>(true, true, false);
+  auto* e1 = vec3<i32>();
+  auto* t = Construct(ty.array(ty.vec3<i32>(), 2), e0, e1);
+  WrapInFunction(t);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'vec3<i32>', found 'vec3<bool>'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_ArrayOfArray_SubElemSizeMismatch) {
+  // array<array<i32, 2>, 2>(array<i32, 3>(), array<i32, 2>());
+  SetSource(Source::Location({12, 34}));
+  auto* e0 = array<i32, 3>();
+  auto* e1 = array<i32, 2>();
+  auto* t = Construct(ty.array(ty.array<i32, 2>(), 2), e0, e1);
+  WrapInFunction(t);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'array<i32, 2>', found 'array<i32, 3>'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_ArrayOfArray_SubElemTypeMismatch) {
+  // array<array<i32, 2>, 2>(array<i32, 2>(), array<u32, 2>());
+  auto* e0 = array<i32, 2>();
+  SetSource(Source::Location({12, 34}));
+  auto* e1 = array<u32, 2>();
+  auto* t = Construct(ty.array(ty.array<i32, 2>(), 2), e0, e1);
+  WrapInFunction(t);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in array constructor does not match array type: "
+            "expected 'array<i32, 2>', found 'array<u32, 2>'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_TooFewElements) {
+  // array<i32, 4>(1, 2, 3);
+  SetSource(Source::Location({12, 34}));
+  auto* tc = array<i32, 4>(Expr(1), Expr(2), Expr(3));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: array constructor has too few elements: expected 4, "
+            "found 3");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_TooManyElements) {
+  // array<i32, 4>(1, 2, 3, 4, 5);
+  SetSource(Source::Location({12, 34}));
+  auto* tc = array<i32, 4>(Expr(1), Expr(2), Expr(3), Expr(4), Expr(5));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: array constructor has too many "
+            "elements: expected 4, "
+            "found 5");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_Runtime) {
+  // array<i32>(1);
+  auto* tc = array(ty.i32(), nullptr, Expr(Source{{12, 34}}, 1));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Array_RuntimeZeroValue) {
+  // array<i32>();
+  auto* tc = array(ty.i32(), nullptr);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
+}
+
+}  // namespace ArrayConstructor
+
+namespace ScalarConstructor {
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_i32_Success) {
+  auto* expr = Construct<i32>(Expr(123));
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::I32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_u32_Success) {
+  auto* expr = Construct<u32>(Expr(123u));
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::U32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_f32_Success) {
+  auto* expr = Construct<f32>(Expr(1.23f));
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_f32_to_i32_Success) {
+  auto* expr = Construct<i32>(1.23f);
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::I32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConversion>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_i32_to_u32_Success) {
+  auto* expr = Construct<u32>(123);
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::U32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConversion>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_u32_to_f32_Success) {
+  auto* expr = Construct<f32>(123u);
+  WrapInFunction(expr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::F32>());
+
+  auto* call = Sem().Get(expr);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConversion>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+}
+
+}  // namespace ScalarConstructor
+
+namespace VectorConstructor {
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<f32>(Expr(Source{{12, 34}}, 1), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<u32>(1u, Expr(Source{{12, 34}}, 1));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<i32>(Expr(Source{{12, 34}}, 1u), 1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<bool>(true, Expr(Source{{12, 34}}, 1));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_Vec3ArgumentCardinalityTooLarge) {
+  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_Vec4ArgumentCardinalityTooLarge) {
+  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec4<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsScalar) {
+  auto* tc =
+      vec2<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
+                Expr(Source{{12, 46}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsVector) {
+  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsVectorAndScalar) {
+  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Expr(Source{{12, 40}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Error_InvalidArgumentType) {
+  auto* tc = vec2<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Success_ZeroValue) {
+  auto* tc = vec2<f32>();
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 0u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2F32_Success_Scalar) {
+  auto* tc = vec2<f32>(1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2U32_Success_Scalar) {
+  auto* tc = vec2<u32>(1u, 1u);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2I32_Success_Scalar) {
+  auto* tc = vec2<i32>(1, 1);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2Bool_Success_Scalar) {
+  auto* tc = vec2<bool>(true, false);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Bool>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Success_Identity) {
+  auto* tc = vec2<f32>(vec2<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec2_Success_Vec2TypeConversion) {
+  auto* tc = vec2<f32>(vec2<i32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 2u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConversion>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<f32>(1.0f, 1.0f, Expr(Source{{12, 34}}, 1));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<u32>(1u, Expr(Source{{12, 34}}, 1), 1u);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<i32>(1, Expr(Source{{12, 34}}, 1u), 1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<bool>(true, Expr(Source{{12, 34}}, 1), false);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_Vec4ArgumentCardinalityTooLarge) {
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec4<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooFewArgumentsScalar) {
+  auto* tc =
+      vec3<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsScalar) {
+  auto* tc =
+      vec3<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
+                Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooFewArgumentsVec2) {
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2) {
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2AndScalar) {
+  auto* tc =
+      vec3<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                Expr(Source{{12, 40}}, 1.0f), Expr(Source{{12, 46}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec3) {
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
+                       Expr(Source{{12, 40}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Error_InvalidArgumentType) {
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Success_ZeroValue) {
+  auto* tc = vec3<f32>();
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 0u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3F32_Success_Scalar) {
+  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3U32_Success_Scalar) {
+  auto* tc = vec3<u32>(1u, 1u, 1u);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3I32_Success_Scalar) {
+  auto* tc = vec3<i32>(1, 1, 1);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3Bool_Success_Scalar) {
+  auto* tc = vec3<bool>(true, false, true);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Bool>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::Bool>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Success_Vec2AndScalar) {
+  auto* tc = vec3<f32>(vec2<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Success_ScalarAndVec2) {
+  auto* tc = vec3<f32>(1.0f, vec2<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Vector>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Success_Identity) {
+  auto* tc = vec3<f32>(vec3<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec3_Success_Vec3TypeConversion) {
+  auto* tc = vec3<f32>(vec3<i32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 3u);
+
+  auto* call = Sem().Get(tc);
+  ASSERT_NE(call, nullptr);
+  auto* ctor = call->Target()->As<sem::TypeConversion>();
+  ASSERT_NE(ctor, nullptr);
+  EXPECT_EQ(call->Type(), ctor->ReturnType());
+  ASSERT_EQ(ctor->Parameters().size(), 1u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, Expr(Source{{12, 34}}, 1), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<u32>(1u, 1u, Expr(Source{{12, 34}}, 1), 1u);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<i32>(1, 1, Expr(Source{{12, 34}}, 1u), 1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<bool>(true, false, Expr(Source{{12, 34}}, 1), true);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsScalar) {
+  auto* tc =
+      vec4<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
+                Expr(Source{{12, 46}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsScalar) {
+  auto* tc =
+      vec4<f32>(Expr(Source{{12, 34}}, 1.0f), Expr(Source{{12, 40}}, 1.0f),
+                Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f),
+                Expr(Source{{12, 58}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsVec2AndScalar) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Expr(Source{{12, 40}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndScalars) {
+  auto* tc = vec4<f32>(
+      Construct(Source{{12, 34}}, ty.vec2<f32>()), Expr(Source{{12, 40}}, 1.0f),
+      Expr(Source{{12, 46}}, 1.0f), Expr(Source{{12, 52}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Scalar) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()),
+                       Expr(Source{{12, 46}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Vec2) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsVec3) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndScalars) {
+  auto* tc =
+      vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
+                Expr(Source{{12, 40}}, 1.0f), Expr(Source{{12, 46}}, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec2) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndVec3) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec2<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec3<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec3) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.vec3<f32>()),
+                       Construct(Source{{12, 40}}, ty.vec3<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Error_InvalidArgumentType) {
+  auto* tc = vec4<f32>(Construct(Source{{12, 34}}, ty.mat2x2<f32>()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_ZeroValue) {
+  auto* tc = vec4<f32>();
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4F32_Success_Scalar) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4U32_Success_Scalar) {
+  auto* tc = vec4<u32>(1u, 1u, 1u, 1u);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4I32_Success_Scalar) {
+  auto* tc = vec4<i32>(1, 1, 1, 1);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4Bool_Success_Scalar) {
+  auto* tc = vec4<bool>(true, false, true, false);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_Vec2ScalarScalar) {
+  auto* tc = vec4<f32>(vec2<f32>(), 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_ScalarVec2Scalar) {
+  auto* tc = vec4<f32>(1.0f, vec2<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_ScalarScalarVec2) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, vec2<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_Vec2AndVec2) {
+  auto* tc = vec4<f32>(vec2<f32>(), vec2<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_Vec3AndScalar) {
+  auto* tc = vec4<f32>(vec3<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_ScalarAndVec3) {
+  auto* tc = vec4<f32>(1.0f, vec3<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_Identity) {
+  auto* tc = vec4<f32>(vec4<f32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vec4_Success_Vec4TypeConversion) {
+  auto* tc = vec4<f32>(vec4<i32>());
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_NestedVectorConstructors_InnerError) {
+  auto* tc = vec4<f32>(vec4<f32>(1.0f, 1.0f,
+                                 vec3<f32>(Expr(Source{{12, 34}}, 1.0f),
+                                           Expr(Source{{12, 34}}, 1.0f))),
+                       1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_NestedVectorConstructors_Success) {
+  auto* tc = vec4<f32>(vec3<f32>(vec2<f32>(1.0f, 1.0f), 1.0f), 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_Alias_Argument_Error) {
+  auto* alias = Alias("UnsignedInt", ty.u32());
+  Global("uint_var", ty.Of(alias), ast::StorageClass::kPrivate);
+
+  auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'u32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_Alias_Argument_Success) {
+  auto* f32_alias = Alias("Float32", ty.f32());
+  auto* vec2_alias = Alias("VectorFloat2", ty.vec2<f32>());
+  Global("my_f32", ty.Of(f32_alias), ast::StorageClass::kPrivate);
+  Global("my_vec2", ty.Of(vec2_alias), ast::StorageClass::kPrivate);
+
+  auto* tc = vec3<f32>("my_vec2", "my_f32");
+  WrapInFunction(tc);
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_ElementTypeAlias_Error) {
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  // vec2<Float32>(1.0f, 1u)
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+  auto* tc =
+      Construct(Source{{12, 34}}, vec_type, 1.0f, Expr(Source{{12, 40}}, 1u));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:40 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'u32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_ElementTypeAlias_Success) {
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  // vec2<Float32>(1.0f, 1.0f)
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+  auto* tc = Construct(Source{{12, 34}}, vec_type, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_ArgumentElementTypeAlias_Error) {
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  // vec3<u32>(vec<Float32>(), 1.0f)
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+  auto* tc = vec3<u32>(Construct(Source{{12, 34}}, vec_type), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'f32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_Constructor_Vector_ArgumentElementTypeAlias_Success) {
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  // vec3<f32>(vec<Float32>(), 1.0f)
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+  auto* tc = vec3<f32>(Construct(Source{{12, 34}}, vec_type), 1.0f);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromScalars) {
+  auto* vec2_bool =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(true), Expr(false));
+  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), Expr(1), Expr(2));
+  auto* vec2_u32 =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(1u), Expr(2u));
+  auto* vec2_f32 =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(1.0f), Expr(2.0f));
+  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
+  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
+  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
+  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromVec2) {
+  auto* vec2_bool =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<bool>(true, false));
+  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), vec2<i32>(1, 2));
+  auto* vec2_u32 =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<u32>(1u, 2u));
+  auto* vec2_f32 =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<f32>(1.0f, 2.0f));
+  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
+  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
+  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
+  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromScalars) {
+  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
+                              Expr(false), Expr(true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1), Expr(2), Expr(3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), Expr(2u), Expr(3u));
+  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
+                             Expr(2.0f), Expr(3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromVec3) {
+  auto* vec3_bool =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<bool>(true, false, true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<i32>(1, 2, 3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<u32>(1u, 2u, 3u));
+  auto* vec3_f32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<f32>(1.0f, 2.0f, 3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec3ElementTypeFromScalarAndVec2) {
+  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
+                              vec2<bool>(false, true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1), vec2<i32>(2, 3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), vec2<u32>(2u, 3u));
+  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
+                             vec2<f32>(2.0f, 3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromScalars) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
+                              Expr(false), Expr(true), Expr(false));
+  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1), Expr(2),
+                             Expr(3), Expr(4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
+                             Expr(2u), Expr(3u), Expr(4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
+                             Expr(2.0f), Expr(3.0f), Expr(4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromVec4) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
+                              vec4<bool>(true, false, true, false));
+  auto* vec4_i32 =
+      Construct(create<ast::Vector>(nullptr, 4), vec4<i32>(1, 2, 3, 4));
+  auto* vec4_u32 =
+      Construct(create<ast::Vector>(nullptr, 4), vec4<u32>(1u, 2u, 3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
+                             vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec4ElementTypeFromScalarAndVec3) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
+                              vec3<bool>(false, true, false));
+  auto* vec4_i32 =
+      Construct(create<ast::Vector>(nullptr, 4), Expr(1), vec3<i32>(2, 3, 4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
+                             vec3<u32>(2u, 3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
+                             vec3<f32>(2.0f, 3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec4ElementTypeFromVec2AndVec2) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
+                              vec2<bool>(true, false), vec2<bool>(true, false));
+  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), vec2<i32>(1, 2),
+                             vec2<i32>(3, 4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), vec2<u32>(1u, 2u),
+                             vec2<u32>(3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
+                             vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVectorElementTypeWithoutArgs) {
+  WrapInFunction(Construct(create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec2ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 2),
+                           Expr(Source{{1, 2}}, 1),  //
+                           Expr(Source{{1, 3}}, 2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type u32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec3ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
+                           Expr(Source{{1, 2}}, 1),   //
+                           Expr(Source{{1, 3}}, 2u),  //
+                           Expr(Source{{1, 4}}, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type u32
+1:4 note: argument 2 has type i32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec3ElementTypeFromScalarAndVec2Mismatch) {
+  WrapInFunction(
+      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
+                Expr(Source{{1, 2}}, 1),  //
+                Construct(Source{{1, 3}}, ty.vec2<f32>(), 2.0f, 3.0f)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type vec2<f32>)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                           Expr(Source{{1, 2}}, 1),     //
+                           Expr(Source{{1, 3}}, 2),     //
+                           Expr(Source{{1, 4}}, 3.0f),  //
+                           Expr(Source{{1, 5}}, 4)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type i32
+1:4 note: argument 2 has type f32
+1:5 note: argument 3 has type i32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromScalarAndVec3Mismatch) {
+  WrapInFunction(
+      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                Expr(Source{{1, 2}}, 1),  //
+                Construct(Source{{1, 3}}, ty.vec3<u32>(), 2u, 3u, 4u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type vec3<u32>)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromVec2AndVec2Mismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                           Construct(Source{{1, 2}}, ty.vec2<i32>(), 3, 4),  //
+                           Construct(Source{{1, 3}}, ty.vec2<u32>(), 3u, 4u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type vec2<i32>
+1:3 note: argument 1 has type vec2<u32>)");
+}
+
+}  // namespace VectorConstructor
+
+namespace MatrixConstructor {
+struct MatrixDimensions {
+  uint32_t rows;
+  uint32_t columns;
+};
+
+static std::string MatrixStr(const MatrixDimensions& dimensions) {
+  return "mat" + std::to_string(dimensions.columns) + "x" +
+         std::to_string(dimensions.rows) + "<f32>";
+}
+
+using MatrixConstructorTest = ResolverTestWithParam<MatrixDimensions>;
+
+TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooFewArguments) {
+  // matNxM<f32>(vecM<f32>(), ...); with N - 1 arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns - 1; i++) {
+    auto* vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooFewArguments) {
+  // matNxM<f32>(f32,...,f32); with N*M - 1 arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns * param.rows - 1; i++) {
+    args.push_back(Construct(Source{{12, i}}, ty.f32()));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "f32";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooManyArguments) {
+  // matNxM<f32>(vecM<f32>(), ...); with N + 1 arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns + 1; i++) {
+    auto* vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooManyArguments) {
+  // matNxM<f32>(f32,...,f32); with N*M + 1 arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns * param.rows + 1; i++) {
+    args.push_back(Construct(Source{{12, i}}, ty.f32()));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "f32";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest,
+       Expr_ColumnConstructor_Error_InvalidArgumentType) {
+  // matNxM<f32>(vec<u32>, vec<u32>, ...); N arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec<u32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest,
+       Expr_ElementConstructor_Error_InvalidArgumentType) {
+  // matNxM<f32>(u32, u32, ...); N*M arguments
+
+  const auto param = GetParam();
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    args.push_back(Expr(Source{{12, i}}, 1u));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "u32";
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest,
+       Expr_ColumnConstructor_Error_TooFewRowsInVectorArgument) {
+  // matNxM<f32>(vecM<f32>(),...,vecM-1<f32>());
+
+  const auto param = GetParam();
+
+  // Skip the test if parameters would have resulted in an invalid vec1 type.
+  if (param.rows == 2) {
+    return;
+  }
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns - 1; i++) {
+    auto* valid_vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, valid_vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
+  }
+  const size_t kInvalidLoc = 2 * (param.columns - 1);
+  auto* invalid_vec_type = ty.vec<f32>(param.rows - 1);
+  args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
+  args_tys << ", vec" << (param.rows - 1) << "<f32>";
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest,
+       Expr_ColumnConstructor_Error_TooManyRowsInVectorArgument) {
+  // matNxM<f32>(vecM<f32>(),...,vecM+1<f32>());
+
+  const auto param = GetParam();
+
+  // Skip the test if parameters would have resulted in an invalid vec5 type.
+  if (param.rows == 4) {
+    return;
+  }
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns - 1; i++) {
+    auto* valid_vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, valid_vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
+  }
+  const size_t kInvalidLoc = 2 * (param.columns - 1);
+  auto* invalid_vec_type = ty.vec<f32>(param.rows + 1);
+  args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
+  args_tys << ", vec" << (param.rows + 1) << "<f32>";
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_ZeroValue_Success) {
+  // matNxM<f32>();
+
+  const auto param = GetParam();
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{{12, 40}}, matrix_type);
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_WithColumns_Success) {
+  // matNxM<f32>(vecM<f32>(), ...); with N arguments
+
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_WithElements_Success) {
+  // matNxM<f32>(f32,...,f32); with N*M arguments
+
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns * param.rows; i++) {
+    args.push_back(Construct(Source{{12, i}}, ty.f32()));
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Error) {
+  // matNxM<Float32>(vecM<u32>(), ...); with N arguments
+
+  const auto param = GetParam();
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec(ty.u32(), param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
+  }
+
+  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Success) {
+  // matNxM<Float32>(vecM<f32>(), ...); with N arguments
+
+  const auto param = GetParam();
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec<f32>(param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+  }
+
+  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
+  auto* alias = Alias("VectorUnsigned2", ty.vec2<u32>());
+  auto* tc =
+      mat2x2<f32>(Construct(Source{{12, 34}}, ty.Of(alias)), vec2<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: no matching constructor mat2x2<f32>(vec2<u32>, vec2<f32>)
+
+3 candidates available:
+  mat2x2<f32>()
+  mat2x2<f32>(f32,...,f32) // 4 arguments
+  mat2x2<f32>(vec2<f32>, vec2<f32>)
+)");
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentTypeAlias_Success) {
+  const auto param = GetParam();
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* vec_type = ty.vec<f32>(param.rows);
+  auto* vec_alias = Alias("VectorFloat2", vec_type);
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    args.push_back(Construct(Source{{12, i}}, ty.Of(vec_alias)));
+  }
+
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentElementTypeAlias_Error) {
+  const auto param = GetParam();
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* f32_alias = Alias("UnsignedInt", ty.u32());
+
+  std::stringstream args_tys;
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
+  }
+
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
+}
+
+TEST_P(MatrixConstructorTest,
+       Expr_Constructor_ArgumentElementTypeAlias_Success) {
+  const auto param = GetParam();
+  auto* f32_alias = Alias("Float32", ty.f32());
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
+    args.push_back(Construct(Source{{12, i}}, vec_type));
+  }
+
+  auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, InferElementTypeFromVectors) {
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    args.push_back(Construct(ty.vec<f32>(param.rows)));
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, InferElementTypeFromScalars) {
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
+    args.push_back(Expr(static_cast<f32>(i)));
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, CannotInferElementTypeFromVectors_Mismatch) {
+  const auto param = GetParam();
+
+  std::stringstream err;
+  err << "12:34 error: cannot infer matrix element type, as constructor "
+         "arguments have different types";
+
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.columns; i++) {
+    err << "\n";
+    auto src = Source{{1, 10 + i}};
+    if (i == 1) {
+      // Odd one out
+      args.push_back(Construct(src, ty.vec<i32>(param.rows)));
+      err << src << " note: argument " << i << " has type vec" << param.rows
+          << "<i32>";
+    } else {
+      args.push_back(Construct(src, ty.vec<f32>(param.rows)));
+      err << src << " note: argument " << i << " has type vec" << param.rows
+          << "<f32>";
+    }
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), err.str());
+}
+
+TEST_P(MatrixConstructorTest, CannotInferElementTypeFromScalars_Mismatch) {
+  const auto param = GetParam();
+
+  std::stringstream err;
+  err << "12:34 error: cannot infer matrix element type, as constructor "
+         "arguments have different types";
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
+    err << "\n";
+    auto src = Source{{1, 10 + i}};
+    if (i == 3) {
+      args.push_back(Expr(src, static_cast<i32>(i)));  // The odd one out
+      err << src << " note: argument " << i << " has type i32";
+    } else {
+      args.push_back(Expr(src, static_cast<f32>(i)));
+      err << src << " note: argument " << i << " has type f32";
+    }
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), err.str());
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         MatrixConstructorTest,
+                         testing::Values(MatrixDimensions{2, 2},
+                                         MatrixDimensions{3, 2},
+                                         MatrixDimensions{4, 2},
+                                         MatrixDimensions{2, 3},
+                                         MatrixDimensions{3, 3},
+                                         MatrixDimensions{4, 3},
+                                         MatrixDimensions{2, 4},
+                                         MatrixDimensions{3, 4},
+                                         MatrixDimensions{4, 4}));
+}  // namespace MatrixConstructor
+
+namespace StructConstructor {
+using builder::CreatePtrs;
+using builder::CreatePtrsFor;
+using builder::f32;
+using builder::i32;
+using builder::mat2x2;
+using builder::mat3x3;
+using builder::mat4x4;
+using builder::u32;
+using builder::vec2;
+using builder::vec3;
+using builder::vec4;
+
+constexpr CreatePtrs all_types[] = {
+    CreatePtrsFor<bool>(),         //
+    CreatePtrsFor<u32>(),          //
+    CreatePtrsFor<i32>(),          //
+    CreatePtrsFor<f32>(),          //
+    CreatePtrsFor<vec4<bool>>(),   //
+    CreatePtrsFor<vec2<i32>>(),    //
+    CreatePtrsFor<vec3<u32>>(),    //
+    CreatePtrsFor<vec4<f32>>(),    //
+    CreatePtrsFor<mat2x2<f32>>(),  //
+    CreatePtrsFor<mat3x3<f32>>(),  //
+    CreatePtrsFor<mat4x4<f32>>()   //
+};
+
+auto number_of_members = testing::Values(2u, 32u, 64u);
+
+using StructConstructorInputsTest =
+    ResolverTestWithParam<std::tuple<CreatePtrs,  // struct member type
+                                     uint32_t>>;  // number of struct members
+TEST_P(StructConstructorInputsTest, TooFew) {
+  auto& param = GetParam();
+  auto& str_params = std::get<0>(param);
+  uint32_t N = std::get<1>(param);
+
+  ast::StructMemberList members;
+  ast::ExpressionList values;
+  for (uint32_t i = 0; i < N; i++) {
+    auto* struct_type = str_params.ast(*this);
+    members.push_back(Member("member_" + std::to_string(i), struct_type));
+    if (i < N - 1) {
+      auto* ctor_value_expr = str_params.expr(*this, 0);
+      values.push_back(ctor_value_expr);
+    }
+  }
+  auto* s = Structure("s", members);
+  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), values);
+  WrapInFunction(tc);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: struct constructor has too few inputs: expected " +
+                std::to_string(N) + ", found " + std::to_string(N - 1));
+}
+
+TEST_P(StructConstructorInputsTest, TooMany) {
+  auto& param = GetParam();
+  auto& str_params = std::get<0>(param);
+  uint32_t N = std::get<1>(param);
+
+  ast::StructMemberList members;
+  ast::ExpressionList values;
+  for (uint32_t i = 0; i < N + 1; i++) {
+    if (i < N) {
+      auto* struct_type = str_params.ast(*this);
+      members.push_back(Member("member_" + std::to_string(i), struct_type));
+    }
+    auto* ctor_value_expr = str_params.expr(*this, 0);
+    values.push_back(ctor_value_expr);
+  }
+  auto* s = Structure("s", members);
+  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), values);
+  WrapInFunction(tc);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: struct constructor has too many inputs: expected " +
+                std::to_string(N) + ", found " + std::to_string(N + 1));
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         StructConstructorInputsTest,
+                         testing::Combine(testing::ValuesIn(all_types),
+                                          number_of_members));
+using StructConstructorTypeTest =
+    ResolverTestWithParam<std::tuple<CreatePtrs,  // struct member type
+                                     CreatePtrs,  // constructor value type
+                                     uint32_t>>;  // number of struct members
+TEST_P(StructConstructorTypeTest, AllTypes) {
+  auto& param = GetParam();
+  auto& str_params = std::get<0>(param);
+  auto& ctor_params = std::get<1>(param);
+  uint32_t N = std::get<2>(param);
+
+  if (str_params.ast == ctor_params.ast) {
+    return;
+  }
+
+  ast::StructMemberList members;
+  ast::ExpressionList values;
+  // make the last value of the constructor to have a different type
+  uint32_t constructor_value_with_different_type = N - 1;
+  for (uint32_t i = 0; i < N; i++) {
+    auto* struct_type = str_params.ast(*this);
+    members.push_back(Member("member_" + std::to_string(i), struct_type));
+    auto* ctor_value_expr = (i == constructor_value_with_different_type)
+                                ? ctor_params.expr(*this, 0)
+                                : str_params.expr(*this, 0);
+    values.push_back(ctor_value_expr);
+  }
+  auto* s = Structure("s", members);
+  auto* tc = Construct(ty.Of(s), values);
+  WrapInFunction(tc);
+
+  std::string found = FriendlyName(ctor_params.ast(*this));
+  std::string expected = FriendlyName(str_params.ast(*this));
+  std::stringstream err;
+  err << "error: type in struct constructor does not match struct member ";
+  err << "type: expected '" << expected << "', found '" << found << "'";
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), err.str());
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
+                         StructConstructorTypeTest,
+                         testing::Combine(testing::ValuesIn(all_types),
+                                          testing::ValuesIn(all_types),
+                                          number_of_members));
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Nested) {
+  auto* inner_m = Member("m", ty.i32());
+  auto* inner_s = Structure("inner_s", {inner_m});
+
+  auto* m0 = Member("m0", ty.i32());
+  auto* m1 = Member("m1", ty.Of(inner_s));
+  auto* m2 = Member("m2", ty.i32());
+  auto* s = Structure("s", {m0, m1, m2});
+
+  auto* tc = Construct(Source{{12, 34}}, ty.Of(s), 1, 1, 1);
+  WrapInFunction(tc);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: type in struct constructor does not match struct member "
+            "type: expected 'inner_s', found 'i32'");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct) {
+  auto* m = Member("m", ty.i32());
+  auto* s = Structure("MyInputs", {m});
+  auto* tc = Construct(Source{{12, 34}}, ty.Of(s));
+  WrapInFunction(tc);
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Empty) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str)));
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+}  // namespace StructConstructor
+
+TEST_F(ResolverTypeConstructorValidationTest, NonConstructibleType_Atomic) {
+  WrapInFunction(
+      Assign(Phony(), Construct(Source{{12, 34}}, ty.atomic(ty.i32()))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       NonConstructibleType_AtomicArray) {
+  WrapInFunction(Assign(
+      Phony(), Construct(Source{{12, 34}}, ty.array(ty.atomic(ty.i32()), 4))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: array constructor has non-constructible element type");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       NonConstructibleType_AtomicStructMember) {
+  auto* str = Structure("S", {Member("a", ty.atomic(ty.i32()))});
+  WrapInFunction(Assign(Phony(), Construct(Source{{12, 34}}, ty.Of(str))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: struct constructor has non-constructible type");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, NonConstructibleType_Sampler) {
+  WrapInFunction(Assign(
+      Phony(),
+      Construct(Source{{12, 34}}, ty.sampler(ast::SamplerKind::kSampler))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, TypeConstructorAsStatement) {
+  WrapInFunction(
+      CallStmt(Construct(Source{{12, 34}}, ty.vec2<f32>(), 1.f, 2.f)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type constructor evaluated but not used");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, TypeConversionAsStatement) {
+  WrapInFunction(CallStmt(Construct(Source{{12, 34}}, ty.f32(), 1)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: type cast evaluated but not used");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
new file mode 100644
index 0000000..1d41858
--- /dev/null
+++ b/src/tint/resolver/type_validation_test.cc
@@ -0,0 +1,1163 @@
+// 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/ast/id_attribute.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Helpers and typedefs
+template <typename T>
+using DataType = builder::DataType<T>;
+template <typename T>
+using vec2 = builder::vec2<T>;
+template <typename T>
+using vec3 = builder::vec3<T>;
+template <typename T>
+using vec4 = builder::vec4<T>;
+template <typename T>
+using mat2x2 = builder::mat2x2<T>;
+template <typename T>
+using mat3x3 = builder::mat3x3<T>;
+template <typename T>
+using mat4x4 = builder::mat4x4<T>;
+template <int N, typename T>
+using array = builder::array<N, T>;
+template <typename T>
+using alias = builder::alias<T>;
+template <typename T>
+using alias1 = builder::alias1<T>;
+template <typename T>
+using alias2 = builder::alias2<T>;
+template <typename T>
+using alias3 = builder::alias3<T>;
+using f32 = builder::f32;
+using i32 = builder::i32;
+using u32 = builder::u32;
+
+class ResolverTypeValidationTest : public resolver::TestHelper,
+                                   public testing::Test {};
+
+TEST_F(ResolverTypeValidationTest, VariableDeclNoConstructor_Pass) {
+  // {
+  // var a :i32;
+  // a = 2;
+  // }
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, nullptr);
+  auto* lhs = Expr("a");
+  auto* rhs = Expr(2);
+
+  auto* body =
+      Block(Decl(var), Assign(Source{Source::Location{12, 34}}, lhs, rhs));
+
+  WrapInFunction(body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(lhs), nullptr);
+  ASSERT_NE(TypeOf(rhs), nullptr);
+}
+
+TEST_F(ResolverTypeValidationTest, GlobalConstantNoConstructor_Pass) {
+  // @id(0) override a :i32;
+  Override(Source{{12, 34}}, "a", ty.i32(), nullptr, ast::AttributeList{Id(0)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, GlobalVariableWithStorageClass_Pass) {
+  // var<private> global_var: f32;
+  Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kPrivate);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
+  // const<private> global_var: f32;
+  AST().AddGlobalVariable(create<ast::Variable>(
+      Source{{12, 34}}, Symbols().Register("global_var"),
+      ast::StorageClass::kPrivate, ast::Access::kUndefined, ty.f32(), true,
+      false, Expr(1.23f), ast::AttributeList{}));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: global constants shouldn't have a storage class");
+}
+
+TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) {
+  // let global_var: f32;
+  GlobalConst(Source{{12, 34}}, "global_var", ty.f32(), Construct(ty.f32()));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, GlobalVariableUnique_Pass) {
+  // var global_var0 : f32 = 0.1;
+  // var global_var1 : i32 = 0;
+
+  Global("global_var0", ty.f32(), ast::StorageClass::kPrivate, Expr(0.1f));
+
+  Global(Source{{12, 34}}, "global_var1", ty.f32(), ast::StorageClass::kPrivate,
+         Expr(1.0f));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest,
+       GlobalVariableFunctionVariableNotUnique_Pass) {
+  // fn my_func() {
+  //   var a: f32 = 2.0;
+  // }
+  // var a: f32 = 2.1;
+
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+
+  Func("my_func", ast::VariableList{}, ty.void_(), {Decl(var)});
+
+  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScope_Pass) {
+  // {
+  // if (true) { var a : f32 = 2.0; }
+  // var a : f32 = 3.14;
+  // }
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+
+  auto* cond = Expr(true);
+  auto* body = Block(Decl(var));
+
+  auto* var_a_float = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
+
+  auto* outer_body =
+      Block(create<ast::IfStatement>(cond, body, ast::ElseStatementList{}),
+            Decl(Source{{12, 34}}, var_a_float));
+
+  WrapInFunction(outer_body);
+
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScopeBlock_Pass) {
+  // {
+  //  { var a : f32; }
+  //  var a : f32;
+  // }
+  auto* var_inner = Var("a", ty.f32(), ast::StorageClass::kNone);
+  auto* inner = Block(Decl(Source{{12, 34}}, var_inner));
+
+  auto* var_outer = Var("a", ty.f32(), ast::StorageClass::kNone);
+  auto* outer_body = Block(inner, Decl(var_outer));
+
+  WrapInFunction(outer_body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest,
+       RedeclaredIdentifierDifferentFunctions_Pass) {
+  // func0 { var a : f32 = 2.0; return; }
+  // func1 { var a : f32 = 3.0; return; }
+  auto* var0 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+
+  auto* var1 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1.0f));
+
+  Func("func0", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Source{{12, 34}}, var0),
+           Return(),
+       },
+       ast::AttributeList{});
+
+  Func("func1", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Source{{13, 34}}, var1),
+           Return(),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Pass) {
+  // var<private> a : array<f32, 4u>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4u)),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Pass) {
+  // var<private> a : array<f32, 4>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4)),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Pass) {
+  // let size = 4u;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(4u));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Pass) {
+  // let size = 4;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(4));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Zero) {
+  // var<private> a : array<f32, 0u>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0u)),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Zero) {
+  // var<private> a : array<f32, 0>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0)),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Negative) {
+  // var<private> a : array<f32, -10>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, -10)),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Zero) {
+  // let size = 0u;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(0u));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Zero) {
+  // let size = 0;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(0));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Negative) {
+  // let size = -10;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(-10));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_FloatLiteral) {
+  // var<private> a : array<f32, 10.0>;
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 10.f)),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_IVecLiteral) {
+  // var<private> a : array<f32, vec2<i32>(10, 10)>;
+  Global(
+      "a",
+      ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.vec2<i32>(), 10, 10)),
+      ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_FloatConstant) {
+  // let size = 10.0;
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Expr(10.f));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_IVecConstant) {
+  // let size = vec2<i32>(100, 100);
+  // var<private> a : array<f32, size>;
+  GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100, 100));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ImplicitStride) {
+  // var<private> a : array<f32, 0x40000000>;
+  Global("a", ty.array(Source{{12, 34}}, ty.f32(), 0x40000000),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: array size in bytes must not exceed 0xffffffff, but "
+            "is 0x100000000");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ExplicitStride) {
+  // var<private> a : @stride(8) array<f32, 0x20000000>;
+  Global("a", ty.array(Source{{12, 34}}, ty.f32(), 0x20000000, 8),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: array size in bytes must not exceed 0xffffffff, but "
+            "is 0x100000000");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_OverridableConstant) {
+  // override size = 10;
+  // var<private> a : array<f32, size>;
+  Override("size", nullptr, Expr(10));
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: array size expression must not be pipeline-overridable");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
+  // var<private> size : i32 = 10;
+  // var<private> a : array<f32, size>;
+  Global("size", ty.i32(), Expr(10), ast::StorageClass::kPrivate);
+  Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: array size identifier must be a module-scope constant");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_FunctionConstant) {
+  // {
+  //   let size = 10;
+  //   var a : array<f32, size>;
+  // }
+  auto* size = Const("size", nullptr, Expr(10));
+  auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
+  WrapInFunction(Block(Decl(size), Decl(a)));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: array size identifier must be a module-scope constant");
+}
+
+TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
+  // var a : array<f32, i32(4)>;
+  auto* size = Const("size", nullptr, Expr(10));
+  auto* a =
+      Var("a", ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.i32(), 4)));
+  WrapInFunction(Block(Decl(size), Decl(a)));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: array size expression must be either a literal or a "
+            "module-scope constant");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) {
+  /// @stage(vertex)
+  // fn func() { var a : array<i32>; }
+
+  auto* var =
+      Var(Source{{12, 34}}, "a", ty.array<i32>(), ast::StorageClass::kNone);
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kVertex),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable a)");
+}
+
+TEST_F(ResolverTypeValidationTest, Struct_Member_VectorNoType) {
+  // struct S {
+  //   a: vec3;
+  // };
+
+  Structure("S",
+            {Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverTypeValidationTest, Struct_Member_MatrixNoType) {
+  // struct S {
+  //   a: mat3x3;
+  // };
+  Structure(
+      "S", {Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
+TEST_F(ResolverTypeValidationTest, Struct_TooBig) {
+  // struct Foo {
+  //   a: array<f32, 0x20000000>;
+  //   b: array<f32, 0x20000000>;
+  // };
+
+  Structure(Source{{12, 34}}, "Foo",
+            {
+                Member("a", ty.array<f32, 0x20000000>()),
+                Member("b", ty.array<f32, 0x20000000>()),
+            });
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: struct size in bytes must not exceed 0xffffffff, but "
+            "is 0x100000000");
+}
+
+TEST_F(ResolverTypeValidationTest, Struct_MemberOffset_TooBig) {
+  // struct Foo {
+  //   a: array<f32, 0x3fffffff>;
+  //   b: f32;
+  //   c: f32;
+  // };
+
+  Structure("Foo", {
+                       Member("a", ty.array<f32, 0x3fffffff>()),
+                       Member("b", ty.f32()),
+                       Member(Source{{12, 34}}, "c", ty.f32()),
+                   });
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: struct member has byte offset 0x100000000, but must "
+            "not exceed 0xffffffff");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLast_Pass) {
+  // [[block]]
+  // struct Foo {
+  //   vf: f32;
+  //   rt: array<f32>;
+  // };
+
+  Structure("Foo",
+            {
+                Member("vf", ty.f32()),
+                Member("rt", ty.array<f32>()),
+            },
+            {create<ast::StructBlockAttribute>()});
+
+  WrapInFunction();
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInArray) {
+  // struct Foo {
+  //   rt : array<array<f32>, 4>;
+  // };
+
+  Structure("Foo",
+            {Member("rt", ty.array(Source{{12, 34}}, ty.array<f32>(), 4))});
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: an array element type cannot contain a runtime-sized "
+            "array");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInArray) {
+  // struct Foo {
+  //   rt : array<f32>;
+  // };
+  // var<private> a : array<Foo, 4>;
+
+  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
+  Global("v", ty.array(Source{{12, 34}}, ty.Of(foo), 4),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: an array element type cannot contain a runtime-sized "
+            "array");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInStruct) {
+  // struct Foo {
+  //   rt : array<f32>;
+  // };
+  // struct Outer {
+  //   inner : Foo;
+  // };
+
+  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
+  Structure("Outer", {Member(Source{{12, 34}}, "inner", ty.Of(foo))});
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a struct that contains a runtime array cannot be "
+            "nested inside another struct");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
+  // [[block]]
+  // struct Foo {
+  //   rt: array<f32>;
+  //   vf: f32;
+  // };
+
+  Structure("Foo",
+            {
+                Member(Source{{12, 34}}, "rt", ty.array<f32>()),
+                Member("vf", ty.f32()),
+            },
+            {create<ast::StructBlockAttribute>()});
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime arrays may only appear as the last member of a struct)");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayAsGlobalVariable) {
+  Global(Source{{56, 78}}, "g", ty.array<i32>(), ast::StorageClass::kPrivate);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
+  auto* v = Var(Source{{56, 78}}, "g", ty.array<i32>());
+  WrapInFunction(v);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while instantiating variable g)");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
+  // fn func(a : array<u32>) {}
+  // @stage(vertex) fn main() {}
+
+  auto* param = Param(Source{{12, 34}}, "a", ty.array<i32>());
+
+  Func("func", ast::VariableList{param}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{});
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kVertex),
+       });
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating parameter a)");
+}
+
+TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsParameter_Fail) {
+  // fn func(a : ptr<workgroup, array<u32>>) {}
+
+  auto* param =
+      Param(Source{{12, 34}}, "a",
+            ty.pointer(ty.array<i32>(), ast::StorageClass::kWorkgroup));
+
+  Func("func", ast::VariableList{param}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating parameter a)");
+}
+
+TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
+  // [[block]]
+  // type RTArr = array<u32>;
+  // struct s {
+  //  b: RTArr;
+  //  a: u32;
+  //}
+
+  auto* alias = Alias("RTArr", ty.array<u32>());
+  Structure("s",
+            {
+                Member(Source{{12, 34}}, "b", ty.Of(alias)),
+                Member("a", ty.u32()),
+            },
+            {create<ast::StructBlockAttribute>()});
+
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: runtime arrays may only appear as the last member of "
+            "a struct");
+}
+
+TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsLast_Pass) {
+  // [[block]]
+  // type RTArr = array<u32>;
+  // struct s {
+  //  a: u32;
+  //  b: RTArr;
+  //}
+
+  auto* alias = Alias("RTArr", ty.array<u32>());
+  Structure("s",
+            {
+                Member("a", ty.u32()),
+                Member("b", ty.Of(alias)),
+            },
+            {create<ast::StructBlockAttribute>()});
+
+  WrapInFunction();
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTypeValidationTest, ArrayOfNonStorableType) {
+  auto* tex_ty = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
+  Global("arr", ty.array(Source{{12, 34}}, tex_ty, 4),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: texture_2d<f32> cannot be used as an element type of "
+            "an array");
+}
+
+TEST_F(ResolverTypeValidationTest, VariableAsType) {
+  // var<private> a : i32;
+  // var<private> b : a;
+  Global("a", ty.i32(), ast::StorageClass::kPrivate);
+  Global("b", ty.type_name("a"), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(error: cannot use variable 'a' as type
+note: 'a' declared here)");
+}
+
+TEST_F(ResolverTypeValidationTest, FunctionAsType) {
+  // fn f() {}
+  // var<private> v : f;
+  Func("f", {}, ty.void_(), {});
+  Global("v", ty.type_name("f"), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(error: cannot use function 'f' as type
+note: 'f' declared here)");
+}
+
+namespace GetCanonicalTests {
+struct Params {
+  builder::ast_type_func_ptr create_ast_type;
+  builder::sem_type_func_ptr create_sem_type;
+};
+
+template <typename T>
+constexpr Params ParamsFor() {
+  return Params{DataType<T>::AST, DataType<T>::Sem};
+}
+
+static constexpr Params cases[] = {
+    ParamsFor<bool>(),
+    ParamsFor<alias<bool>>(),
+    ParamsFor<alias1<alias<bool>>>(),
+
+    ParamsFor<vec3<f32>>(),
+    ParamsFor<alias<vec3<f32>>>(),
+    ParamsFor<alias1<alias<vec3<f32>>>>(),
+
+    ParamsFor<vec3<alias<f32>>>(),
+    ParamsFor<alias1<vec3<alias<f32>>>>(),
+    ParamsFor<alias2<alias1<vec3<alias<f32>>>>>(),
+    ParamsFor<alias3<alias2<vec3<alias1<alias<f32>>>>>>(),
+
+    ParamsFor<mat3x3<alias<f32>>>(),
+    ParamsFor<alias1<mat3x3<alias<f32>>>>(),
+    ParamsFor<alias2<alias1<mat3x3<alias<f32>>>>>(),
+    ParamsFor<alias3<alias2<mat3x3<alias1<alias<f32>>>>>>(),
+
+    ParamsFor<alias1<alias<bool>>>(),
+    ParamsFor<alias1<alias<vec3<f32>>>>(),
+    ParamsFor<alias1<alias<mat3x3<f32>>>>(),
+};
+
+using CanonicalTest = ResolverTestWithParam<Params>;
+TEST_P(CanonicalTest, All) {
+  auto& params = GetParam();
+
+  auto* type = params.create_ast_type(*this);
+
+  auto* var = Var("v", type);
+  auto* expr = Expr("v");
+  WrapInFunction(var, expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* got = TypeOf(expr)->UnwrapRef();
+  auto* expected = params.create_sem_type(*this);
+
+  EXPECT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         CanonicalTest,
+                         testing::ValuesIn(cases));
+
+}  // namespace GetCanonicalTests
+
+namespace MultisampledTextureTests {
+struct DimensionParams {
+  ast::TextureDimension dim;
+  bool is_valid;
+};
+
+static constexpr DimensionParams dimension_cases[] = {
+    DimensionParams{ast::TextureDimension::k1d, false},
+    DimensionParams{ast::TextureDimension::k2d, true},
+    DimensionParams{ast::TextureDimension::k2dArray, false},
+    DimensionParams{ast::TextureDimension::k3d, false},
+    DimensionParams{ast::TextureDimension::kCube, false},
+    DimensionParams{ast::TextureDimension::kCubeArray, false}};
+
+using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
+TEST_P(MultisampledTextureDimensionTest, All) {
+  auto& params = GetParam();
+  Global(Source{{12, 34}}, "a", ty.multisampled_texture(params.dim, ty.i32()),
+         ast::StorageClass::kNone, nullptr,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: only 2d multisampled textures are supported");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         MultisampledTextureDimensionTest,
+                         testing::ValuesIn(dimension_cases));
+
+struct TypeParams {
+  builder::ast_type_func_ptr type_func;
+  bool is_valid;
+};
+
+template <typename T>
+constexpr TypeParams TypeParamsFor(bool is_valid) {
+  return TypeParams{DataType<T>::AST, is_valid};
+}
+
+static constexpr TypeParams type_cases[] = {
+    TypeParamsFor<bool>(false),
+    TypeParamsFor<i32>(true),
+    TypeParamsFor<u32>(true),
+    TypeParamsFor<f32>(true),
+
+    TypeParamsFor<alias<bool>>(false),
+    TypeParamsFor<alias<i32>>(true),
+    TypeParamsFor<alias<u32>>(true),
+    TypeParamsFor<alias<f32>>(true),
+
+    TypeParamsFor<vec3<f32>>(false),
+    TypeParamsFor<mat3x3<f32>>(false),
+
+    TypeParamsFor<alias<vec3<f32>>>(false),
+    TypeParamsFor<alias<mat3x3<f32>>>(false),
+};
+
+using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
+TEST_P(MultisampledTextureTypeTest, All) {
+  auto& params = GetParam();
+  Global(Source{{12, 34}}, "a",
+         ty.multisampled_texture(ast::TextureDimension::k2d,
+                                 params.type_func(*this)),
+         ast::StorageClass::kNone, nullptr,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: texture_multisampled_2d<type>: type must be f32, "
+              "i32 or u32");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         MultisampledTextureTypeTest,
+                         testing::ValuesIn(type_cases));
+
+}  // namespace MultisampledTextureTests
+
+namespace StorageTextureTests {
+struct DimensionParams {
+  ast::TextureDimension dim;
+  bool is_valid;
+};
+
+static constexpr DimensionParams Dimension_cases[] = {
+    DimensionParams{ast::TextureDimension::k1d, true},
+    DimensionParams{ast::TextureDimension::k2d, true},
+    DimensionParams{ast::TextureDimension::k2dArray, true},
+    DimensionParams{ast::TextureDimension::k3d, true},
+    DimensionParams{ast::TextureDimension::kCube, false},
+    DimensionParams{ast::TextureDimension::kCubeArray, false}};
+
+using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
+TEST_P(StorageTextureDimensionTest, All) {
+  // @group(0) @binding(0)
+  // var a : texture_storage_*<ru32int, write>;
+  auto& params = GetParam();
+
+  auto* st =
+      ty.storage_texture(Source{{12, 34}}, params.dim,
+                         ast::TexelFormat::kR32Uint, ast::Access::kWrite);
+
+  Global("a", st, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: cube dimensions for storage textures are not "
+              "supported");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         StorageTextureDimensionTest,
+                         testing::ValuesIn(Dimension_cases));
+
+struct FormatParams {
+  ast::TexelFormat format;
+  bool is_valid;
+};
+
+static constexpr FormatParams format_cases[] = {
+    FormatParams{ast::TexelFormat::kR32Float, true},
+    FormatParams{ast::TexelFormat::kR32Sint, true},
+    FormatParams{ast::TexelFormat::kR32Uint, true},
+    FormatParams{ast::TexelFormat::kRg32Float, true},
+    FormatParams{ast::TexelFormat::kRg32Sint, true},
+    FormatParams{ast::TexelFormat::kRg32Uint, true},
+    FormatParams{ast::TexelFormat::kRgba16Float, true},
+    FormatParams{ast::TexelFormat::kRgba16Sint, true},
+    FormatParams{ast::TexelFormat::kRgba16Uint, true},
+    FormatParams{ast::TexelFormat::kRgba32Float, true},
+    FormatParams{ast::TexelFormat::kRgba32Sint, true},
+    FormatParams{ast::TexelFormat::kRgba32Uint, true},
+    FormatParams{ast::TexelFormat::kRgba8Sint, true},
+    FormatParams{ast::TexelFormat::kRgba8Snorm, true},
+    FormatParams{ast::TexelFormat::kRgba8Uint, true},
+    FormatParams{ast::TexelFormat::kRgba8Unorm, true}};
+
+using StorageTextureFormatTest = ResolverTestWithParam<FormatParams>;
+TEST_P(StorageTextureFormatTest, All) {
+  auto& params = GetParam();
+  // @group(0) @binding(0)
+  // var a : texture_storage_1d<*, write>;
+  // @group(0) @binding(1)
+  // var b : texture_storage_2d<*, write>;
+  // @group(0) @binding(2)
+  // var c : texture_storage_2d_array<*, write>;
+  // @group(0) @binding(3)
+  // var d : texture_storage_3d<*, write>;
+
+  auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                                  params.format, ast::Access::kWrite);
+  Global("a", st_a, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format,
+                                  ast::Access::kWrite);
+  Global("b", st_b, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 1)});
+
+  auto* st_c = ty.storage_texture(ast::TextureDimension::k2dArray,
+                                  params.format, ast::Access::kWrite);
+  Global("c", st_c, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 2)});
+
+  auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format,
+                                  ast::Access::kWrite);
+  Global("d", st_d, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 3)});
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: image format must be one of the texel formats "
+              "specified for storage textues in "
+              "https://gpuweb.github.io/gpuweb/wgsl/#texel-formats");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         StorageTextureFormatTest,
+                         testing::ValuesIn(format_cases));
+
+using StorageTextureAccessTest = ResolverTest;
+
+TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
+  // @group(0) @binding(0)
+  // var a : texture_storage_1d<ru32int>;
+
+  auto* st =
+      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                         ast::TexelFormat::kR32Uint, ast::Access::kUndefined);
+
+  Global("a", st, ast::StorageClass::kNone,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: storage texture missing access control");
+}
+
+TEST_F(StorageTextureAccessTest, RWAccess_Fail) {
+  // @group(0) @binding(0)
+  // var a : texture_storage_1d<ru32int, read_write>;
+
+  auto* st =
+      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                         ast::TexelFormat::kR32Uint, ast::Access::kReadWrite);
+
+  Global("a", st, ast::StorageClass::kNone, nullptr,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: storage textures currently only support 'write' "
+            "access control");
+}
+
+TEST_F(StorageTextureAccessTest, ReadOnlyAccess_Fail) {
+  // @group(0) @binding(0)
+  // var a : texture_storage_1d<ru32int, read>;
+
+  auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                                ast::TexelFormat::kR32Uint, ast::Access::kRead);
+
+  Global("a", st, ast::StorageClass::kNone, nullptr,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: storage textures currently only support 'write' "
+            "access control");
+}
+
+TEST_F(StorageTextureAccessTest, WriteOnlyAccess_Pass) {
+  // @group(0) @binding(0)
+  // var a : texture_storage_1d<ru32int, write>;
+
+  auto* st =
+      ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Uint,
+                         ast::Access::kWrite);
+
+  Global("a", st, ast::StorageClass::kNone, nullptr,
+         ast::AttributeList{GroupAndBinding(0, 0)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+}  // namespace StorageTextureTests
+
+namespace MatrixTests {
+struct Params {
+  uint32_t columns;
+  uint32_t rows;
+  builder::ast_type_func_ptr elem_ty;
+};
+
+template <typename T>
+constexpr Params ParamsFor(uint32_t columns, uint32_t rows) {
+  return Params{columns, rows, DataType<T>::AST};
+}
+
+using ValidMatrixTypes = ResolverTestWithParam<Params>;
+TEST_P(ValidMatrixTypes, Okay) {
+  // var a : matNxM<EL_TY>;
+  auto& params = GetParam();
+  Global("a", ty.mat(params.elem_ty(*this), params.columns, params.rows),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         ValidMatrixTypes,
+                         testing::Values(ParamsFor<f32>(2, 2),
+                                         ParamsFor<f32>(2, 3),
+                                         ParamsFor<f32>(2, 4),
+                                         ParamsFor<f32>(3, 2),
+                                         ParamsFor<f32>(3, 3),
+                                         ParamsFor<f32>(3, 4),
+                                         ParamsFor<f32>(4, 2),
+                                         ParamsFor<f32>(4, 3),
+                                         ParamsFor<f32>(4, 4),
+                                         ParamsFor<alias<f32>>(4, 2),
+                                         ParamsFor<alias<f32>>(4, 3),
+                                         ParamsFor<alias<f32>>(4, 4)));
+
+using InvalidMatrixElementTypes = ResolverTestWithParam<Params>;
+TEST_P(InvalidMatrixElementTypes, InvalidElementType) {
+  // var a : matNxM<EL_TY>;
+  auto& params = GetParam();
+  Global("a",
+         ty.mat(Source{{12, 34}}, params.elem_ty(*this), params.columns,
+                params.rows),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: matrix element type must be 'f32'");
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         InvalidMatrixElementTypes,
+                         testing::Values(ParamsFor<bool>(4, 2),
+                                         ParamsFor<i32>(4, 3),
+                                         ParamsFor<u32>(4, 4),
+                                         ParamsFor<vec2<f32>>(2, 2),
+                                         ParamsFor<vec3<i32>>(2, 3),
+                                         ParamsFor<vec4<u32>>(2, 4),
+                                         ParamsFor<mat2x2<f32>>(3, 2),
+                                         ParamsFor<mat3x3<f32>>(3, 3),
+                                         ParamsFor<mat4x4<f32>>(3, 4),
+                                         ParamsFor<array<2, f32>>(4, 2)));
+}  // namespace MatrixTests
+
+namespace VectorTests {
+struct Params {
+  uint32_t width;
+  builder::ast_type_func_ptr elem_ty;
+};
+
+template <typename T>
+constexpr Params ParamsFor(uint32_t width) {
+  return Params{width, DataType<T>::AST};
+}
+
+using ValidVectorTypes = ResolverTestWithParam<Params>;
+TEST_P(ValidVectorTypes, Okay) {
+  // var a : vecN<EL_TY>;
+  auto& params = GetParam();
+  Global("a", ty.vec(params.elem_ty(*this), params.width),
+         ast::StorageClass::kPrivate);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         ValidVectorTypes,
+                         testing::Values(ParamsFor<bool>(2),
+                                         ParamsFor<f32>(2),
+                                         ParamsFor<i32>(2),
+                                         ParamsFor<u32>(2),
+                                         ParamsFor<bool>(3),
+                                         ParamsFor<f32>(3),
+                                         ParamsFor<i32>(3),
+                                         ParamsFor<u32>(3),
+                                         ParamsFor<bool>(4),
+                                         ParamsFor<f32>(4),
+                                         ParamsFor<i32>(4),
+                                         ParamsFor<u32>(4),
+                                         ParamsFor<alias<bool>>(4),
+                                         ParamsFor<alias<f32>>(4),
+                                         ParamsFor<alias<i32>>(4),
+                                         ParamsFor<alias<u32>>(4)));
+
+using InvalidVectorElementTypes = ResolverTestWithParam<Params>;
+TEST_P(InvalidVectorElementTypes, InvalidElementType) {
+  // var a : vecN<EL_TY>;
+  auto& params = GetParam();
+  Global("a", ty.vec(Source{{12, 34}}, params.elem_ty(*this), params.width),
+         ast::StorageClass::kPrivate);
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: vector element type must be 'bool', 'f32', 'i32' "
+            "or 'u32'");
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         InvalidVectorElementTypes,
+                         testing::Values(ParamsFor<vec2<f32>>(2),
+                                         ParamsFor<vec3<i32>>(2),
+                                         ParamsFor<vec4<u32>>(2),
+                                         ParamsFor<mat2x2<f32>>(2),
+                                         ParamsFor<mat3x3<f32>>(2),
+                                         ParamsFor<mat4x4<f32>>(2),
+                                         ParamsFor<array<2, f32>>(2)));
+}  // namespace VectorTests
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
new file mode 100644
index 0000000..9198487
--- /dev/null
+++ b/src/tint/resolver/validation_test.cc
@@ -0,0 +1,1320 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverValidationTest = ResolverTest;
+
+class FakeStmt : public Castable<FakeStmt, ast::Statement> {
+ public:
+  FakeStmt(ProgramID pid, Source src) : Base(pid, src) {}
+  FakeStmt* Clone(CloneContext*) const override { return nullptr; }
+};
+
+class FakeExpr : public Castable<FakeExpr, ast::Expression> {
+ public:
+  FakeExpr(ProgramID pid, Source src) : Base(pid, src) {}
+  FakeExpr* Clone(CloneContext*) const override { return nullptr; }
+};
+
+TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInVertexStage) {
+  Global(Source{{1, 2}}, "wg", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
+  Global("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
+
+  Func(Source{{9, 10}}, "f0", ast::VariableList{}, ty.vec4<f32>(),
+       {stmt, Return(Expr("dst"))},
+       ast::AttributeList{Stage(ast::PipelineStage::kVertex)},
+       ast::AttributeList{Builtin(ast::Builtin::kPosition)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "3:4 error: workgroup memory cannot be used by vertex pipeline "
+            "stage\n1:2 note: variable is declared here");
+}
+
+TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInFragmentStage) {
+  // var<workgroup> wg : vec4<f32>;
+  // var<workgroup> dst : vec4<f32>;
+  // fn f2(){ dst = wg; }
+  // fn f1() { f2(); }
+  // @stage(fragment)
+  // fn f0() {
+  //  f1();
+  //}
+
+  Global(Source{{1, 2}}, "wg", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
+  Global("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
+
+  Func(Source{{5, 6}}, "f2", {}, ty.void_(), {stmt});
+  Func(Source{{7, 8}}, "f1", {}, ty.void_(), {CallStmt(Call("f2"))});
+  Func(Source{{9, 10}}, "f0", {}, ty.void_(), {CallStmt(Call("f1"))},
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:4 error: workgroup memory cannot be used by fragment pipeline stage
+1:2 note: variable is declared here
+5:6 note: called by function 'f2'
+7:8 note: called by function 'f1'
+9:10 note: called by entry point 'f0')");
+}
+
+TEST_F(ResolverValidationTest, UnhandledStmt) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.WrapInFunction(b.create<FakeStmt>());
+        Program(std::move(b));
+      },
+      "internal compiler error: unhandled node type: tint::resolver::FakeStmt");
+}
+
+TEST_F(ResolverValidationTest, Stmt_If_NonBool) {
+  // if (1.23f) {}
+
+  WrapInFunction(If(Expr(Source{{12, 34}}, 1.23f), Block()));
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: if statement condition must be bool, got f32");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Else_NonBool) {
+  // else (1.23f) {}
+
+  WrapInFunction(
+      If(Expr(true), Block(), Else(Expr(Source{{12, 34}}, 1.23f), Block())));
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: else statement condition must be bool, got f32");
+}
+
+TEST_F(ResolverValidationTest, Expr_ErrUnknownExprType) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.WrapInFunction(b.create<FakeExpr>());
+        Resolver(&b).Resolve();
+      },
+      "internal compiler error: unhandled expression type: "
+      "tint::resolver::FakeExpr");
+}
+
+TEST_F(ResolverValidationTest, Expr_DontCall_Function) {
+  Func("func", {}, ty.void_(), {}, {});
+  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "func"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for function call");
+}
+
+TEST_F(ResolverValidationTest, Expr_DontCall_Builtin) {
+  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "round"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for builtin call");
+}
+
+TEST_F(ResolverValidationTest, Expr_DontCall_Type) {
+  Alias("T", ty.u32());
+  WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "T"));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "3:8 error: missing '(' for type constructor or cast");
+}
+
+TEST_F(ResolverValidationTest, AssignmentStmt_InvalidLHS_BuiltinFunctionName) {
+  // normalize = 2;
+
+  auto* lhs = Expr(Source{{12, 34}}, "normalize");
+  auto* rhs = Expr(2);
+  auto* assign = Assign(lhs, rhs);
+  WrapInFunction(assign);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing '(' for builtin call");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariable_Fail) {
+  // b = 2;
+
+  auto* lhs = Expr(Source{{12, 34}}, "b");
+  auto* rhs = Expr(2);
+  auto* assign = Assign(lhs, rhs);
+  WrapInFunction(assign);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'b'");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
+  // {
+  //  b = 2;
+  // }
+
+  auto* lhs = Expr(Source{{12, 34}}, "b");
+  auto* rhs = Expr(2);
+
+  auto* body = Block(Assign(lhs, rhs));
+  WrapInFunction(body);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'b'");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableGlobalVariable_Pass) {
+  // var global_var: f32 = 2.1;
+  // fn my_func() {
+  //   global_var = 3.14;
+  //   return;
+  // }
+
+  Global("global_var", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
+
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Assign(Expr(Source{{12, 34}}, "global_var"), 3.14f),
+           Return(),
+       });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableInnerScope_Fail) {
+  // {
+  //   if (true) { var a : f32 = 2.0; }
+  //   a = 3.14;
+  // }
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+
+  auto* cond = Expr(true);
+  auto* body = Block(Decl(var));
+
+  SetSource(Source{{12, 34}});
+  auto* lhs = Expr(Source{{12, 34}}, "a");
+  auto* rhs = Expr(3.14f);
+
+  auto* outer_body =
+      Block(create<ast::IfStatement>(cond, body, ast::ElseStatementList{}),
+            Assign(lhs, rhs));
+
+  WrapInFunction(outer_body);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'a'");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableOuterScope_Pass) {
+  // {
+  //   var a : f32 = 2.0;
+  //   if (true) { a = 3.14; }
+  // }
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+
+  auto* lhs = Expr(Source{{12, 34}}, "a");
+  auto* rhs = Expr(3.14f);
+
+  auto* cond = Expr(true);
+  auto* body = Block(Assign(lhs, rhs));
+
+  auto* outer_body =
+      Block(Decl(var),
+            create<ast::IfStatement>(cond, body, ast::ElseStatementList{}));
+
+  WrapInFunction(outer_body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableDifferentScope_Fail) {
+  // {
+  //  { var a : f32 = 2.0; }
+  //  { a = 3.14; }
+  // }
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
+  auto* first_body = Block(Decl(var));
+
+  auto* lhs = Expr(Source{{12, 34}}, "a");
+  auto* rhs = Expr(3.14f);
+  auto* second_body = Block(Assign(lhs, rhs));
+
+  auto* outer_body = Block(first_body, second_body);
+
+  WrapInFunction(outer_body);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: 'a'");
+}
+
+TEST_F(ResolverValidationTest, StorageClass_FunctionVariableWorkgroupClass) {
+  auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
+
+  auto* stmt = Decl(var);
+  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: function variable has a non-function storage class");
+}
+
+TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) {
+  auto* var = Var("s", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* stmt = Decl(var);
+  Func("func", ast::VariableList{}, ty.void_(), {stmt}, ast::AttributeList{});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: function variable has a non-function storage class");
+}
+
+TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
+  auto* t = ty.sampler(ast::SamplerKind::kSampler);
+  Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: variables of type 'sampler' must not have a storage class)");
+}
+
+TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) {
+  auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: variables of type 'texture_1d<f32>' must not have a storage class)");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* ident = Expr(Source{{{3, 3}, {3, 7}}}, "xyqz");
+
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:5 error: invalid vector swizzle character");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* ident = Expr(Source{{{3, 3}, {3, 7}}}, "rgyw");
+
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "3:3 error: invalid mixing of vector swizzle characters rgba with xyzw");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* ident = Expr(Source{{{3, 3}, {3, 8}}}, "zzzzz");
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle size");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadIndex) {
+  Global("my_vec", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* ident = Expr(Source{{3, 3}}, "z");
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle member");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_BadParent) {
+  // var param: vec4<f32>
+  // let ret: f32 = *(&param).x;
+  auto* param = Var("param", ty.vec4<f32>());
+  auto* x = Expr(Source{{{3, 3}, {3, 8}}}, "x");
+
+  auto* addressOf_expr = AddressOf(Source{{12, 34}}, param);
+  auto* accessor_expr = MemberAccessor(addressOf_expr, x);
+  auto* star_p = Deref(accessor_expr);
+  auto* ret = Var("r", ty.f32(), star_p);
+  WrapInFunction(Decl(param), Decl(ret));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invalid member accessor expression. Expected vector "
+            "or struct, got 'ptr<function, vec4<f32>, read_write>'");
+}
+
+TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncGoodParent) {
+  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
+  //     let x: f32 = (*p).z;
+  //     return x;
+  // }
+  auto* p =
+      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
+  auto* star_p = Deref(p);
+  auto* z = Expr(Source{{{3, 3}, {3, 8}}}, "z");
+  auto* accessor_expr = MemberAccessor(star_p, z);
+  auto* x = Var("x", ty.f32(), accessor_expr);
+  Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncBadParent) {
+  // fn func(p: ptr<function, vec4<f32>>) -> f32 {
+  //     let x: f32 = *p.z;
+  //     return x;
+  // }
+  auto* p =
+      Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
+  auto* z = Expr(Source{{{3, 3}, {3, 8}}}, "z");
+  auto* accessor_expr = MemberAccessor(p, z);
+  auto* star_p = Deref(accessor_expr);
+  auto* x = Var("x", ty.f32(), star_p);
+  Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "error: invalid member accessor expression. "
+      "Expected vector or struct, got 'ptr<function, vec4<f32>, read_write>'");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
+  // loop  {
+  //     continue; // Bypasses z decl
+  //     var z : i32; // unreachable
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto error_loc = Source{{12, 34}};
+  auto* body =
+      Block(Continue(),
+            Decl(error_loc, Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Assign(Expr("z"), 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            R"(12:34 warning: code is unreachable
+error: continue statement bypasses declaration of 'z'
+note: identifier 'z' declared here
+note: identifier 'z' referenced in continuing block here)");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing_InBlocks) {
+  // loop  {
+  //     if (false) { break; }
+  //     var z : i32;
+  //     {{{continue;}}}
+  //     continue; // Ok
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto* body = Block(If(false, Block(Break())),  //
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                     Block(Block(Block(Continue()))));
+  auto* continuing = Block(Assign(Expr("z"), 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto cont_loc = Source{{12, 34}};
+  auto decl_loc = Source{{56, 78}};
+  auto ref_loc = Source{{90, 12}};
+  auto* body =
+      Block(If(Expr(true), Block(Continue(cont_loc))),
+            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Assign(Expr(ref_loc, "z"), 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: continue statement bypasses declaration of 'z'
+56:78 note: identifier 'z' declared here
+90:12 note: identifier 'z' referenced in continuing block here)");
+}
+
+TEST_F(
+    ResolverValidationTest,
+    Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         if (true) {
+  //             z = 2; // Must fail even if z is in a sub-scope
+  //         }
+  //     }
+  // }
+
+  auto cont_loc = Source{{12, 34}};
+  auto decl_loc = Source{{56, 78}};
+  auto ref_loc = Source{{90, 12}};
+  auto* body =
+      Block(If(Expr(true), Block(Continue(cont_loc))),
+            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+
+  auto* continuing =
+      Block(If(Expr(true), Block(Assign(Expr(ref_loc, "z"), 2))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: continue statement bypasses declaration of 'z'
+56:78 note: identifier 'z' declared here
+90:12 note: identifier 'z' referenced in continuing block here)");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageOutsideBlock) {
+  // loop  {
+  //     if (true) {
+  //         continue; // bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         // Must fail even if z is used in an expression that isn't
+  //         // directly contained inside a block.
+  //         if (z < 2) {
+  //         }
+  //     }
+  // }
+
+  auto cont_loc = Source{{12, 34}};
+  auto decl_loc = Source{{56, 78}};
+  auto ref_loc = Source{{90, 12}};
+  auto* body =
+      Block(If(Expr(true), Block(Continue(cont_loc))),
+            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+  auto* compare = create<ast::BinaryExpression>(ast::BinaryOp::kLessThan,
+                                                Expr(ref_loc, "z"), Expr(2));
+  auto* continuing = Block(If(compare, Block()));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: continue statement bypasses declaration of 'z'
+56:78 note: identifier 'z' declared here
+90:12 note: identifier 'z' referenced in continuing block here)");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingLoop) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         loop {
+  //             z = 2; // Must fail even if z is in a sub-scope
+  //         }
+  //     }
+  // }
+
+  auto cont_loc = Source{{12, 34}};
+  auto decl_loc = Source{{56, 78}};
+  auto ref_loc = Source{{90, 12}};
+  auto* body =
+      Block(If(Expr(true), Block(Continue(cont_loc))),
+            Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+
+  auto* continuing = Block(Loop(Block(Assign(Expr(ref_loc, "z"), 2))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            R"(12:34 error: continue statement bypasses declaration of 'z'
+56:78 note: identifier 'z' declared here
+90:12 note: identifier 'z' referenced in continuing block here)");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
+  // loop  {
+  //     loop {
+  //         if (true) { continue; } // OK: not part of the outer loop
+  //         break;
+  //     }
+  //     var z : i32;
+  //     break;
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(    //
+      If(true, Block(Continue())),  //
+      Break()));
+  auto* body = Block(inner_loop,                                          //
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+                     Break());
+  auto* continuing = Block(Assign("z", 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
+  // loop  {
+  //     loop {
+  //         if (true) { continue; } // OK: not part of the outer loop
+  //         break;
+  //     }
+  //     var z : i32;
+  //     break;
+  //     continuing {
+  //         if (true) {
+  //             z = 2;
+  //         }
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
+                                Break()));
+  auto* body = Block(inner_loop,                                          //
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+                     Break());
+  auto* continuing = Block(If(Expr(true), Block(Assign("z", 2))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
+  // loop  {
+  //     loop {
+  //         if (true) { continue; } // OK: not part of the outer loop
+  //         break;
+  //     }
+  //     var z : i32;
+  //     break;
+  //     continuing {
+  //         loop {
+  //             z = 2;
+  //             break;
+  //         }
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
+                                Break()));
+  auto* body = Block(inner_loop,                                          //
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+                     Break());
+  auto* continuing = Block(Loop(Block(Assign("z", 2),  //
+                                      Break())));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
+  // loop  {
+  //     var z : i32;
+  //     if (true) { continue; }
+  //     break;
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto error_loc = Source{{12, 34}};
+  auto* body = Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                     If(true, Block(Continue())),  //
+                     Break());
+  auto* continuing = Block(Assign(Expr(error_loc, "z"), 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverTest, Stmt_Loop_ReturnInContinuing_Direct) {
+  // loop  {
+  //   continuing {
+  //     return;
+  //   }
+  // }
+
+  WrapInFunction(Loop(  // loop
+      Block(),          //   loop block
+      Block(            //   loop continuing block
+          Return(Source{{12, 34}}))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a return statement)");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_ReturnInContinuing_Indirect) {
+  // loop {
+  //   if (false) { break; }
+  //   continuing {
+  //     loop {
+  //       return;
+  //     }
+  //   }
+  // }
+
+  WrapInFunction(Loop(                   // outer loop
+      Block(If(false, Block(Break()))),  //   outer loop block
+      Block(Source{{56, 78}},            //   outer loop continuing block
+            Loop(                        //     inner loop
+                Block(                   //       inner loop block
+                    Return(Source{{12, 34}}))))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a return statement
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Direct) {
+  // loop  {
+  //   continuing {
+  //     discard;
+  //   }
+  // }
+
+  WrapInFunction(Loop(  // loop
+      Block(),          //   loop block
+      Block(            //   loop continuing block
+          Discard(Source{{12, 34}}))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a discard statement)");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect) {
+  // loop {
+  //   if (false) { break; }
+  //   continuing {
+  //     loop { discard; }
+  //   }
+  // }
+
+  WrapInFunction(Loop(                   // outer loop
+      Block(If(false, Block(Break()))),  //   outer loop block
+      Block(Source{{56, 78}},            //   outer loop continuing block
+            Loop(                        //     inner loop
+                Block(                   //       inner loop block
+                    Discard(Source{{12, 34}}))))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a discard statement
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect_ViaCall) {
+  // fn MayDiscard() { if (true) { discard; } }
+  // fn F() { MayDiscard(); }
+  // loop {
+  //   continuing {
+  //     loop { F(); }
+  //   }
+  // }
+
+  Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
+  Func("SomeFunc", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
+
+  WrapInFunction(Loop(         // outer loop
+      Block(),                 //   outer loop block
+      Block(Source{{56, 78}},  //   outer loop continuing block
+            Loop(              //     inner loop
+                Block(         //       inner loop block
+                    CallStmt(Call(Source{{12, 34}}, "SomeFunc")))))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: cannot call a function that may discard inside a continuing block
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Direct) {
+  // loop  {
+  //     continuing {
+  //         continue;
+  //     }
+  // }
+
+  WrapInFunction(Loop(         // loop
+      Block(),                 //   loop block
+      Block(Source{{56, 78}},  //   loop continuing block
+            Continue(Source{{12, 34}}))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: continuing blocks must not contain a continue statement");
+}
+
+TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Indirect) {
+  // loop {
+  //   if (false) { break; }
+  //   continuing {
+  //     loop {
+  //       if (false) { break; }
+  //       continue;
+  //     }
+  //   }
+  // }
+
+  WrapInFunction(Loop(                        // outer loop
+      Block(                                  //   outer loop block
+          If(false, Block(Break()))),         //     if (false) { break; }
+      Block(                                  //   outer loop continuing block
+          Loop(                               //     inner loop
+              Block(                          //       inner loop block
+                  If(false, Block(Break())),  //          if (false) { break; }
+                  Continue(Source{{12, 34}}))))));  //    continue
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Direct) {
+  // for(;; return) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr, Return(Source{{12, 34}}),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a return statement)");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Indirect) {
+  // for(;; loop { return }) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr,
+                     Loop(Source{{56, 78}},                  //
+                          Block(Return(Source{{12, 34}}))),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a return statement
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Direct) {
+  // for(;; discard) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr, Discard(Source{{12, 34}}),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a discard statement)");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect) {
+  // for(;; loop { discard }) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr,
+                     Loop(Source{{56, 78}},                   //
+                          Block(Discard(Source{{12, 34}}))),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: continuing blocks must not contain a discard statement
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect_ViaCall) {
+  // fn MayDiscard() { if (true) { discard; } }
+  // fn F() { MayDiscard(); }
+  // for(;; loop { F() }) {
+  //   break;
+  // }
+
+  Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
+  Func("F", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
+
+  WrapInFunction(For(nullptr, nullptr,
+                     Loop(Source{{56, 78}},                               //
+                          Block(CallStmt(Call(Source{{12, 34}}, "F")))),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: cannot call a function that may discard inside a continuing block
+56:78 note: see continuing block here)");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Direct) {
+  // for(;; continue) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr, Continue(Source{{12, 34}}),  //
+                     Block(Break())));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: continuing blocks must not contain a continue statement");
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Indirect) {
+  // for(;; loop { if (false) { break; } continue }) {
+  //   break;
+  // }
+
+  WrapInFunction(For(nullptr, nullptr,
+                     Loop(                                    //
+                         Block(If(false, Block(Break())),     //
+                               Continue(Source{{12, 34}}))),  //
+                     Block(Break())));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_CondIsBoolRef) {
+  // var cond : bool = true;
+  // for (; cond; ) {
+  // }
+
+  auto* cond = Var("cond", ty.bool_(), Expr(true));
+  WrapInFunction(Decl(cond), For(nullptr, "cond", nullptr, Block()));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, Stmt_ForLoop_CondIsNotBool) {
+  // for (; 1.0f; ) {
+  // }
+
+  WrapInFunction(For(nullptr, Expr(Source{{12, 34}}, 1.0f), nullptr, Block()));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: for-loop condition must be bool, got f32");
+}
+
+TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
+  WrapInFunction(Loop(Block(If(false, Block(Break())),  //
+                            Continue(Source{{12, 34}}))));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
+  WrapInFunction(Continue(Source{{12, 34}}));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: continue statement must be in a loop");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInLoop) {
+  WrapInFunction(Loop(Block(Break(Source{{12, 34}}))));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInSwitch) {
+  WrapInFunction(Loop(Block(Switch(Expr(1),               //
+                                   Case(Expr(1),          //
+                                        Block(Break())),  //
+                                   DefaultCase()),        //
+                            Break())));                   //
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
+  auto* cont = Block(                           // continuing {
+      If(true, Block(                           //   if(true) {
+                   Break(Source{{12, 34}}))));  //     break;
+                                                //   }
+                                                // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfElseInContinuing) {
+  auto* cont = Block(                      // continuing {
+      If(true, Block(),                    //   if(true) {
+         Else(Block(                       //   } else {
+             Break(Source{{12, 34}})))));  //     break;
+                                           //   }
+                                           // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInContinuing) {
+  auto* cont = Block(                   // continuing {
+      Block(Break(Source{{12, 34}})));  //   break;
+                                        // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "12:34 note: break statement is not directly in if statement block");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfInIfInContinuing) {
+  auto* cont = Block(                                      // continuing {
+      If(true, Block(                                      //   if(true) {
+                   If(Source{{56, 78}}, true,              //     if(true) {
+                      Block(Break(Source{{12, 34}}))))));  //       break;
+                                                           //     }
+                                                           //   }
+                                                           // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: if statement containing break statement is not directly in "
+      "continuing block");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueMultipleStmtsInContinuing) {
+  auto* cont = Block(                             // continuing {
+      If(true, Block(Source{{56, 78}},            //   if(true) {
+                     Assign(Phony(), 1),          //     _ = 1;
+                     Break(Source{{12, 34}}))));  //     break;
+                                                  //   }
+                                                  // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: if statement block contains multiple statements");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfElseMultipleStmtsInContinuing) {
+  auto* cont = Block(                             // continuing {
+      If(true, Block(),                           //   if(true) {
+         Else(Block(Source{{56, 78}},             //   } else {
+                    Assign(Phony(), 1),           //     _ = 1;
+                    Break(Source{{12, 34}})))));  //     break;
+                                                  //   }
+                                                  // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: if statement block contains multiple statements");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfElseIfInContinuing) {
+  auto* cont = Block(                             // continuing {
+      If(true, Block(),                           //   if(true) {
+         Else(Expr(Source{{56, 78}}, true),       //   } else if (true) {
+              Block(Break(Source{{12, 34}})))));  //     break;
+                                                  //   }
+                                                  // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: else has condition");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfNonEmptyElseInContinuing) {
+  auto* cont = Block(                        // continuing {
+      If(true,                               //   if(true) {
+         Block(Break(Source{{12, 34}})),     //     break;
+         Else(Block(Source{{56, 78}},        //   } else {
+                    Assign(Phony(), 1)))));  //     _ = 1;
+                                             //   }
+                                             // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: non-empty false block");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfElseNonEmptyTrueInContinuing) {
+  auto* cont = Block(                                  // continuing {
+      If(true,                                         //   if(true) {
+         Block(Source{{56, 78}}, Assign(Phony(), 1)),  //     _ = 1;
+         Else(Block(                                   //   } else {
+             Break(Source{{12, 34}})))));              //     break;
+                                                       //   }
+                                                       // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: non-empty true block");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInIfInContinuingNotLast) {
+  auto* cont = Block(                      // continuing {
+      If(Source{{56, 78}}, true,           //   if(true) {
+         Block(Break(Source{{12, 34}}))),  //     break;
+                                           //   }
+      Assign(Phony(), 1));                 //   _ = 1;
+                                           // }
+  WrapInFunction(Loop(Block(), cont));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: break statement in a continuing block must be the single "
+      "statement of an if statement's true or false block, and that if "
+      "statement must be the last statement of the continuing block\n"
+      "56:78 note: if statement containing break statement is not the last "
+      "statement of the continuing block");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakNotInLoopOrSwitch) {
+  WrapInFunction(Break(Source{{12, 34}}));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: break statement must be in a loop or switch case");
+}
+
+TEST_F(ResolverValidationTest, StructMemberDuplicateName) {
+  Structure("S", {Member(Source{{12, 34}}, "a", ty.i32()),
+                  Member(Source{{56, 78}}, "a", ty.i32())});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: redefinition of 'a'\n12:34 note: previous definition "
+            "is here");
+}
+TEST_F(ResolverValidationTest, StructMemberDuplicateNameDifferentTypes) {
+  Structure("S", {Member(Source{{12, 34}}, "a", ty.bool_()),
+                  Member(Source{{12, 34}}, "a", ty.vec3<f32>())});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: redefinition of 'a'\n12:34 note: previous definition "
+            "is here");
+}
+TEST_F(ResolverValidationTest, StructMemberDuplicateNamePass) {
+  Structure("S", {Member("a", ty.i32()), Member("b", ty.f32())});
+  Structure("S1", {Member("a", ty.i32()), Member("b", ty.f32())});
+  EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverValidationTest, NonPOTStructMemberAlignAttribute) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 3)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: align value must be a positive, power-of-two integer");
+}
+
+TEST_F(ResolverValidationTest, ZeroStructMemberAlignAttribute) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 0)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: align value must be a positive, power-of-two integer");
+}
+
+TEST_F(ResolverValidationTest, ZeroStructMemberSizeAttribute) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberSize(Source{{12, 34}}, 0)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: size must be at least as big as the type's size (4)");
+}
+
+TEST_F(ResolverValidationTest, OffsetAndSizeAttribute) {
+  Structure("S", {
+                     Member(Source{{12, 34}}, "a", ty.f32(),
+                            {MemberOffset(0), MemberSize(4)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: offset attributes cannot be used with align or size "
+            "attributes");
+}
+
+TEST_F(ResolverValidationTest, OffsetAndAlignAttribute) {
+  Structure("S", {
+                     Member(Source{{12, 34}}, "a", ty.f32(),
+                            {MemberOffset(0), MemberAlign(4)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: offset attributes cannot be used with align or size "
+            "attributes");
+}
+
+TEST_F(ResolverValidationTest, OffsetAndAlignAndSizeAttribute) {
+  Structure("S", {
+                     Member(Source{{12, 34}}, "a", ty.f32(),
+                            {MemberOffset(0), MemberAlign(4), MemberSize(4)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: offset attributes cannot be used with align or size "
+            "attributes");
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Cast_Pointer) {
+  auto* vf = Var("vf", ty.f32());
+  auto* c =
+      Construct(Source{{12, 34}}, ty.pointer<i32>(ast::StorageClass::kFunction),
+                ExprList(vf));
+  auto* ip = Const("ip", ty.pointer<i32>(ast::StorageClass::kFunction), c);
+  WrapInFunction(Decl(vf), Decl(ip));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: type is not constructible");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
+
+TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeStmt);
+TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeExpr);
diff --git a/src/tint/resolver/var_let_test.cc b/src/tint/resolver/var_let_test.cc
new file mode 100644
index 0000000..79f2f0a
--- /dev/null
+++ b/src/tint/resolver/var_let_test.cc
@@ -0,0 +1,701 @@
+// 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/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/sem/reference_type.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverVarLetTest : public resolver::TestHelper,
+                            public testing::Test {};
+
+TEST_F(ResolverVarLetTest, VarDeclWithoutConstructor) {
+  // struct S { i : i32; }
+  // alias A = S;
+  // fn F(){
+  //   var i : i32;
+  //   var u : u32;
+  //   var f : f32;
+  //   var b : bool;
+  //   var s : S;
+  //   var a : A;
+  // }
+
+  auto* S = Structure("S", {Member("i", ty.i32())});
+  auto* A = Alias("A", ty.Of(S));
+
+  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
+  auto* u = Var("u", ty.u32(), ast::StorageClass::kNone);
+  auto* f = Var("f", ty.f32(), ast::StorageClass::kNone);
+  auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone);
+  auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
+  auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
+
+  Func("F", {}, ty.void_(),
+       {
+           Decl(i),
+           Decl(u),
+           Decl(f),
+           Decl(b),
+           Decl(s),
+           Decl(a),
+       });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  // `var` declarations are always of reference type
+  ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
+
+  EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
+  EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
+
+  EXPECT_EQ(Sem().Get(i)->Constructor(), nullptr);
+  EXPECT_EQ(Sem().Get(u)->Constructor(), nullptr);
+  EXPECT_EQ(Sem().Get(f)->Constructor(), nullptr);
+  EXPECT_EQ(Sem().Get(b)->Constructor(), nullptr);
+  EXPECT_EQ(Sem().Get(s)->Constructor(), nullptr);
+  EXPECT_EQ(Sem().Get(a)->Constructor(), nullptr);
+}
+
+TEST_F(ResolverVarLetTest, VarDeclWithConstructor) {
+  // struct S { i : i32; }
+  // alias A = S;
+  // fn F(){
+  //   var i : i32 = 1;
+  //   var u : u32 = 1u;
+  //   var f : f32 = 1.f;
+  //   var b : bool = true;
+  //   var s : S = S(1);
+  //   var a : A = A(1);
+  // }
+
+  auto* S = Structure("S", {Member("i", ty.i32())});
+  auto* A = Alias("A", ty.Of(S));
+
+  auto* i_c = Expr(1);
+  auto* u_c = Expr(1u);
+  auto* f_c = Expr(1.f);
+  auto* b_c = Expr(true);
+  auto* s_c = Construct(ty.Of(S), Expr(1));
+  auto* a_c = Construct(ty.Of(A), Expr(1));
+
+  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone, i_c);
+  auto* u = Var("u", ty.u32(), ast::StorageClass::kNone, u_c);
+  auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, f_c);
+  auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone, b_c);
+  auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone, s_c);
+  auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone, a_c);
+
+  Func("F", {}, ty.void_(),
+       {
+           Decl(i),
+           Decl(u),
+           Decl(f),
+           Decl(b),
+           Decl(s),
+           Decl(a),
+       });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  // `var` declarations are always of reference type
+  ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
+
+  EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
+  EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
+  EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
+
+  EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
+  EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
+  EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
+  EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
+  EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
+  EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
+}
+
+TEST_F(ResolverVarLetTest, LetDecl) {
+  // struct S { i : i32; }
+  // fn F(){
+  //   var v : i32;
+  //   let i : i32 = 1;
+  //   let u : u32 = 1u;
+  //   let f : f32 = 1.;
+  //   let b : bool = true;
+  //   let s : S = S(1);
+  //   let a : A = A(1);
+  //   let p : pointer<function, i32> = &v;
+  // }
+
+  auto* S = Structure("S", {Member("i", ty.i32())});
+  auto* A = Alias("A", ty.Of(S));
+  auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+
+  auto* i_c = Expr(1);
+  auto* u_c = Expr(1u);
+  auto* f_c = Expr(1.f);
+  auto* b_c = Expr(true);
+  auto* s_c = Construct(ty.Of(S), Expr(1));
+  auto* a_c = Construct(ty.Of(A), Expr(1));
+  auto* p_c = AddressOf(v);
+
+  auto* i = Const("i", ty.i32(), i_c);
+  auto* u = Const("u", ty.u32(), u_c);
+  auto* f = Const("f", ty.f32(), f_c);
+  auto* b = Const("b", ty.bool_(), b_c);
+  auto* s = Const("s", ty.Of(S), s_c);
+  auto* a = Const("a", ty.Of(A), a_c);
+  auto* p = Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), p_c);
+
+  Func("F", {}, ty.void_(),
+       {
+           Decl(v),
+           Decl(i),
+           Decl(u),
+           Decl(f),
+           Decl(b),
+           Decl(s),
+           Decl(a),
+           Decl(p),
+       });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  // `let` declarations are always of the storage type
+  ASSERT_TRUE(TypeOf(i)->Is<sem::I32>());
+  ASSERT_TRUE(TypeOf(u)->Is<sem::U32>());
+  ASSERT_TRUE(TypeOf(f)->Is<sem::F32>());
+  ASSERT_TRUE(TypeOf(b)->Is<sem::Bool>());
+  ASSERT_TRUE(TypeOf(s)->Is<sem::Struct>());
+  ASSERT_TRUE(TypeOf(a)->Is<sem::Struct>());
+  ASSERT_TRUE(TypeOf(p)->Is<sem::Pointer>());
+  ASSERT_TRUE(TypeOf(p)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
+
+  EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
+  EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
+  EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
+  EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
+  EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
+  EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
+  EXPECT_EQ(Sem().Get(p)->Constructor()->Declaration(), p_c);
+}
+
+TEST_F(ResolverVarLetTest, DefaultVarStorageClass) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
+
+  auto* buf = Structure("S", {Member("m", ty.i32())},
+                        {create<ast::StructBlockAttribute>()});
+  auto* function = Var("f", ty.i32());
+  auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
+  auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(0),
+                             create<ast::GroupAttribute>(0),
+                         });
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(1),
+                             create<ast::GroupAttribute>(0),
+                         });
+  auto* handle = Global("h", ty.depth_texture(ast::TextureDimension::k2d),
+                        ast::AttributeList{
+                            create<ast::BindingAttribute>(2),
+                            create<ast::GroupAttribute>(0),
+                        });
+
+  WrapInFunction(function);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(function)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(private_)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(workgroup)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(uniform)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(handle)->Is<sem::Reference>());
+
+  EXPECT_EQ(TypeOf(function)->As<sem::Reference>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(private_)->As<sem::Reference>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(workgroup)->As<sem::Reference>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(uniform)->As<sem::Reference>()->Access(),
+            ast::Access::kRead);
+  EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
+            ast::Access::kRead);
+  EXPECT_EQ(TypeOf(handle)->As<sem::Reference>()->Access(), ast::Access::kRead);
+}
+
+TEST_F(ResolverVarLetTest, ExplicitVarStorageClass) {
+  // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
+
+  auto* buf = Structure("S", {Member("m", ty.i32())},
+                        {create<ast::StructBlockAttribute>()});
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(1),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
+
+  EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
+            ast::Access::kReadWrite);
+}
+
+TEST_F(ResolverVarLetTest, LetInheritsAccessFromOriginatingVariable) {
+  // struct Inner {
+  //    arr: array<i32, 4>;
+  // }
+  // [[block]] struct S {
+  //    inner: Inner;
+  // }
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  // fn f() {
+  //   let p = &s.inner.arr[2];
+  // }
+  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
+                        {create<ast::StructBlockAttribute>()});
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(0),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  auto* expr =
+      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
+  auto* ptr = Const("p", nullptr, AddressOf(expr));
+
+  WrapInFunction(ptr);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
+  ASSERT_TRUE(TypeOf(ptr)->Is<sem::Pointer>());
+
+  EXPECT_EQ(TypeOf(expr)->As<sem::Reference>()->Access(),
+            ast::Access::kReadWrite);
+  EXPECT_EQ(TypeOf(ptr)->As<sem::Pointer>()->Access(), ast::Access::kReadWrite);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsAlias) {
+  // type a = i32;
+  //
+  // fn X() {
+  //   var a = false;
+  // }
+  //
+  // fn Y() {
+  //   let a = true;
+  // }
+
+  auto* t = Alias("a", ty.i32());
+  auto* v = Var("a", nullptr, Expr(false));
+  auto* l = Const("a", nullptr, Expr(false));
+  Func("X", {}, ty.void_(), {Decl(v)});
+  Func("Y", {}, ty.void_(), {Decl(l)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* type_t = Sem().Get(t);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), type_t);
+  EXPECT_EQ(local_l->Shadows(), type_t);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsStruct) {
+  // struct a {
+  //   m : i32;
+  // };
+  //
+  // fn X() {
+  //   var a = true;
+  // }
+  //
+  // fn Y() {
+  //   let a = false;
+  // }
+
+  auto* t = Structure("a", {Member("m", ty.i32())});
+  auto* v = Var("a", nullptr, Expr(false));
+  auto* l = Const("a", nullptr, Expr(false));
+  Func("X", {}, ty.void_(), {Decl(v)});
+  Func("Y", {}, ty.void_(), {Decl(l)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* type_t = Sem().Get(t);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), type_t);
+  EXPECT_EQ(local_l->Shadows(), type_t);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsFunction) {
+  // fn a() {
+  //   var a = true;
+  // }
+  //
+  // fn b() {
+  //   let b = false;
+  // }
+
+  auto* v = Var("a", nullptr, Expr(false));
+  auto* l = Const("b", nullptr, Expr(false));
+  auto* fa = Func("a", {}, ty.void_(), {Decl(v)});
+  auto* fb = Func("b", {}, ty.void_(), {Decl(l)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+  auto* func_a = Sem().Get(fa);
+  auto* func_b = Sem().Get(fb);
+
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+  ASSERT_NE(func_a, nullptr);
+  ASSERT_NE(func_b, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), func_a);
+  EXPECT_EQ(local_l->Shadows(), func_b);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsGlobalVar) {
+  // var<private> a : i32;
+  //
+  // fn X() {
+  //   var a = a;
+  // }
+  //
+  // fn Y() {
+  //   let a = a;
+  // }
+
+  auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+  auto* v = Var("a", nullptr, Expr("a"));
+  auto* l = Const("a", nullptr, Expr("a"));
+  Func("X", {}, ty.void_(), {Decl(v)});
+  Func("Y", {}, ty.void_(), {Decl(l)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* global = Sem().Get(g);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), global);
+  EXPECT_EQ(local_l->Shadows(), global);
+
+  auto* user_v =
+      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
+  auto* user_l =
+      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
+
+  ASSERT_NE(user_v, nullptr);
+  ASSERT_NE(user_l, nullptr);
+
+  EXPECT_EQ(user_v->Variable(), global);
+  EXPECT_EQ(user_l->Variable(), global);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsGlobalLet) {
+  // let a : i32 = 1;
+  //
+  // fn X() {
+  //   var a = (a == 123);
+  // }
+  //
+  // fn Y() {
+  //   let a = (a == 321);
+  // }
+
+  auto* g = GlobalConst("a", ty.i32(), Expr(1));
+  auto* v = Var("a", nullptr, Expr("a"));
+  auto* l = Const("a", nullptr, Expr("a"));
+  Func("X", {}, ty.void_(), {Decl(v)});
+  Func("Y", {}, ty.void_(), {Decl(l)});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* global = Sem().Get(g);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), global);
+  EXPECT_EQ(local_l->Shadows(), global);
+
+  auto* user_v =
+      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
+  auto* user_l =
+      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
+
+  ASSERT_NE(user_v, nullptr);
+  ASSERT_NE(user_l, nullptr);
+
+  EXPECT_EQ(user_v->Variable(), global);
+  EXPECT_EQ(user_l->Variable(), global);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsLocalVar) {
+  // fn X() {
+  //   var a : i32;
+  //   {
+  //     var a = a;
+  //   }
+  //   {
+  //     let a = a;
+  //   }
+  // }
+
+  auto* s = Var("a", ty.i32(), Expr(1));
+  auto* v = Var("a", nullptr, Expr("a"));
+  auto* l = Const("a", nullptr, Expr("a"));
+  Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* local_s = Sem().Get<sem::LocalVariable>(s);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_s, nullptr);
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), local_s);
+  EXPECT_EQ(local_l->Shadows(), local_s);
+
+  auto* user_v =
+      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
+  auto* user_l =
+      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
+
+  ASSERT_NE(user_v, nullptr);
+  ASSERT_NE(user_l, nullptr);
+
+  EXPECT_EQ(user_v->Variable(), local_s);
+  EXPECT_EQ(user_l->Variable(), local_s);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsLocalLet) {
+  // fn X() {
+  //   let a = 1;
+  //   {
+  //     var a = (a == 123);
+  //   }
+  //   {
+  //     let a = (a == 321);
+  //   }
+  // }
+
+  auto* s = Const("a", ty.i32(), Expr(1));
+  auto* v = Var("a", nullptr, Expr("a"));
+  auto* l = Const("a", nullptr, Expr("a"));
+  Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* local_s = Sem().Get<sem::LocalVariable>(s);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(local_s, nullptr);
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), local_s);
+  EXPECT_EQ(local_l->Shadows(), local_s);
+
+  auto* user_v =
+      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
+  auto* user_l =
+      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
+
+  ASSERT_NE(user_v, nullptr);
+  ASSERT_NE(user_l, nullptr);
+
+  EXPECT_EQ(user_v->Variable(), local_s);
+  EXPECT_EQ(user_l->Variable(), local_s);
+}
+
+TEST_F(ResolverVarLetTest, LocalShadowsParam) {
+  // fn F(a : i32) {
+  //   {
+  //     var a = a;
+  //   }
+  //   {
+  //     let a = a;
+  //   }
+  // }
+
+  auto* p = Param("a", ty.i32());
+  auto* v = Var("a", nullptr, Expr("a"));
+  auto* l = Const("a", nullptr, Expr("a"));
+  Func("X", {p}, ty.void_(), {Block(Decl(v)), Block(Decl(l))});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* param = Sem().Get<sem::Parameter>(p);
+  auto* local_v = Sem().Get<sem::LocalVariable>(v);
+  auto* local_l = Sem().Get<sem::LocalVariable>(l);
+
+  ASSERT_NE(param, nullptr);
+  ASSERT_NE(local_v, nullptr);
+  ASSERT_NE(local_l, nullptr);
+
+  EXPECT_EQ(local_v->Shadows(), param);
+  EXPECT_EQ(local_l->Shadows(), param);
+
+  auto* user_v =
+      Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
+  auto* user_l =
+      Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
+
+  ASSERT_NE(user_v, nullptr);
+  ASSERT_NE(user_l, nullptr);
+
+  EXPECT_EQ(user_v->Variable(), param);
+  EXPECT_EQ(user_l->Variable(), param);
+}
+
+TEST_F(ResolverVarLetTest, ParamShadowsFunction) {
+  // fn a(a : bool) {
+  // }
+
+  auto* p = Param("a", ty.bool_());
+  auto* f = Func("a", {p}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* func = Sem().Get(f);
+  auto* param = Sem().Get<sem::Parameter>(p);
+
+  ASSERT_NE(func, nullptr);
+  ASSERT_NE(param, nullptr);
+
+  EXPECT_EQ(param->Shadows(), func);
+}
+
+TEST_F(ResolverVarLetTest, ParamShadowsGlobalVar) {
+  // var<private> a : i32;
+  //
+  // fn F(a : bool) {
+  // }
+
+  auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+  auto* p = Param("a", ty.bool_());
+  Func("F", {p}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* global = Sem().Get(g);
+  auto* param = Sem().Get<sem::Parameter>(p);
+
+  ASSERT_NE(global, nullptr);
+  ASSERT_NE(param, nullptr);
+
+  EXPECT_EQ(param->Shadows(), global);
+}
+
+TEST_F(ResolverVarLetTest, ParamShadowsGlobalLet) {
+  // let a : i32 = 1;
+  //
+  // fn F(a : bool) {
+  // }
+
+  auto* g = GlobalConst("a", ty.i32(), Expr(1));
+  auto* p = Param("a", ty.bool_());
+  Func("F", {p}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* global = Sem().Get(g);
+  auto* param = Sem().Get<sem::Parameter>(p);
+
+  ASSERT_NE(global, nullptr);
+  ASSERT_NE(param, nullptr);
+
+  EXPECT_EQ(param->Shadows(), global);
+}
+
+TEST_F(ResolverVarLetTest, ParamShadowsAlias) {
+  // type a = i32;
+  //
+  // fn F(a : a) {
+  // }
+
+  auto* a = Alias("a", ty.i32());
+  auto* p = Param("a", ty.type_name("a"));
+  Func("F", {p}, ty.void_(), {});
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* alias = Sem().Get(a);
+  auto* param = Sem().Get<sem::Parameter>(p);
+
+  ASSERT_NE(alias, nullptr);
+  ASSERT_NE(param, nullptr);
+
+  EXPECT_EQ(param->Shadows(), alias);
+  EXPECT_EQ(param->Type(), alias);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/resolver/var_let_validation_test.cc b/src/tint/resolver/var_let_validation_test.cc
new file mode 100644
index 0000000..fbb570e
--- /dev/null
+++ b/src/tint/resolver/var_let_validation_test.cc
@@ -0,0 +1,352 @@
+// 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/ast/struct_block_attribute.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+struct ResolverVarLetValidationTest : public resolver::TestHelper,
+                                      public testing::Test {};
+
+TEST_F(ResolverVarLetValidationTest, LetNoInitializer) {
+  // let a : i32;
+  WrapInFunction(Const(Source{{12, 34}}, "a", ty.i32(), nullptr));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: let declaration must have an initializer");
+}
+
+TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) {
+  // let a : i32;
+  GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: let declaration must have an initializer");
+}
+
+TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) {
+  // var a;
+  WrapInFunction(Var(Source{{12, 34}}, "a", nullptr));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function scope var declaration requires a type or "
+            "initializer");
+}
+
+TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) {
+  // var a;
+  Global(Source{{12, 34}}, "a", nullptr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: module scope var declaration requires a type and "
+            "initializer");
+}
+
+TEST_F(ResolverVarLetValidationTest, VarTypeNotStorable) {
+  // var i : i32;
+  // var p : pointer<function, i32> = &v;
+  auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
+  auto* p =
+      Var(Source{{56, 78}}, "a", ty.pointer<i32>(ast::StorageClass::kFunction),
+          ast::StorageClass::kNone, AddressOf(Source{{12, 34}}, "i"));
+  WrapInFunction(i, p);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: ptr<function, i32, read_write> cannot be used as the "
+            "type of a var");
+}
+
+TEST_F(ResolverVarLetValidationTest, LetTypeNotConstructible) {
+  // @group(0) @binding(0) var t1 : texture_2d<f32>;
+  // let t2 : t1;
+  auto* t1 =
+      Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+             GroupAndBinding(0, 0));
+  auto* t2 = Const(Source{{56, 78}}, "t2", nullptr, Expr(t1));
+  WrapInFunction(t2);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: texture_2d<f32> cannot be used as the type of a let");
+}
+
+TEST_F(ResolverVarLetValidationTest, LetConstructorWrongType) {
+  // var v : i32 = 2u
+  WrapInFunction(Const(Source{{3, 3}}, "v", ty.i32(), Expr(2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
+}
+
+TEST_F(ResolverVarLetValidationTest, VarConstructorWrongType) {
+  // var v : i32 = 2u
+  WrapInFunction(
+      Var(Source{{3, 3}}, "v", ty.i32(), ast::StorageClass::kNone, Expr(2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
+}
+
+TEST_F(ResolverVarLetValidationTest, LetConstructorWrongTypeViaAlias) {
+  auto* a = Alias("I32", ty.i32());
+  WrapInFunction(Const(Source{{3, 3}}, "v", ty.Of(a), Expr(2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
+}
+
+TEST_F(ResolverVarLetValidationTest, VarConstructorWrongTypeViaAlias) {
+  auto* a = Alias("I32", ty.i32());
+  WrapInFunction(
+      Var(Source{{3, 3}}, "v", ty.Of(a), ast::StorageClass::kNone, Expr(2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
+}
+
+TEST_F(ResolverVarLetValidationTest, LetOfPtrConstructedWithRef) {
+  // var a : f32;
+  // let b : ptr<function,f32> = a;
+  const auto priv = ast::StorageClass::kFunction;
+  auto* var_a = Var("a", ty.f32(), priv);
+  auto* var_b =
+      Const(Source{{12, 34}}, "b", ty.pointer<float>(priv), Expr("a"), {});
+  WrapInFunction(var_a, var_b);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: cannot initialize let of type 'ptr<function, f32, read_write>' with value of type 'f32')");
+}
+
+TEST_F(ResolverVarLetValidationTest, LocalLetRedeclared) {
+  // let l : f32 = 1.;
+  // let l : i32 = 0;
+  auto* l1 = Const("l", ty.f32(), Expr(1.f));
+  auto* l2 = Const(Source{{12, 34}}, "l", ty.i32(), Expr(0));
+  WrapInFunction(l1, l2);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: redeclaration of 'l'\nnote: 'l' previously declared here");
+}
+
+TEST_F(ResolverVarLetValidationTest, GlobalVarRedeclaredAsLocal) {
+  // var v : f32 = 2.1;
+  // fn my_func() {
+  //   var v : f32 = 2.0;
+  //   return 0;
+  // }
+
+  Global("v", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
+
+  WrapInFunction(Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
+                     Expr(2.0f)));
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverVarLetValidationTest, VarRedeclaredInInnerBlock) {
+  // {
+  //  var v : f32;
+  //  { var v : f32; }
+  // }
+  auto* var_outer = Var("v", ty.f32(), ast::StorageClass::kNone);
+  auto* var_inner =
+      Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone);
+  auto* inner = Block(Decl(var_inner));
+  auto* outer_body = Block(Decl(var_outer), inner);
+
+  WrapInFunction(outer_body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverVarLetValidationTest, VarRedeclaredInIfBlock) {
+  // {
+  //   var v : f32 = 3.14;
+  //   if (true) { var v : f32 = 2.0; }
+  // }
+  auto* var_a_float = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
+
+  auto* var = Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
+                  Expr(2.0f));
+
+  auto* cond = Expr(true);
+  auto* body = Block(Decl(var));
+
+  auto* outer_body =
+      Block(Decl(var_a_float),
+            create<ast::IfStatement>(cond, body, ast::ElseStatementList{}));
+
+  WrapInFunction(outer_body);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverVarLetValidationTest, InferredPtrStorageAccessMismatch) {
+  // struct Inner {
+  //    arr: array<i32, 4>;
+  // }
+  // [[block]] struct S {
+  //    inner: Inner;
+  // }
+  // @group(0) @binding(0) var<storage> s : S;
+  // fn f() {
+  //   let p : pointer<storage, i32, read_write> = &s.inner.arr[2];
+  // }
+  auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
+                        {create<ast::StructBlockAttribute>()});
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(0),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  auto* expr =
+      IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
+  auto* ptr = Const(
+      Source{{12, 34}}, "p",
+      ty.pointer<i32>(ast::StorageClass::kStorage, ast::Access::kReadWrite),
+      AddressOf(expr));
+
+  WrapInFunction(ptr);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot initialize let of type "
+            "'ptr<storage, i32, read_write>' with value of type "
+            "'ptr<storage, i32, read>'");
+}
+
+TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Atomic) {
+  auto* v = Var("v", ty.atomic(Source{{12, 34}}, ty.i32()));
+  WrapInFunction(v);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function variable must have a constructible type");
+}
+
+TEST_F(ResolverVarLetValidationTest, NonConstructibleType_RuntimeArray) {
+  auto* s = Structure("S", {Member(Source{{56, 78}}, "m", ty.array(ty.i32()))},
+                      {StructBlock()});
+  auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
+  WrapInFunction(v);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Struct_WithAtomic) {
+  auto* s = Structure("S", {Member("m", ty.atomic(ty.i32()))});
+  auto* v = Var("v", ty.Of(s));
+  WrapInFunction(v);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "error: function variable must have a constructible type");
+}
+
+TEST_F(ResolverVarLetValidationTest, NonConstructibleType_InferredType) {
+  // @group(0) @binding(0) var s : sampler;
+  // fn foo() {
+  //   var v = s;
+  // }
+  Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 0));
+  auto* v = Var(Source{{12, 34}}, "v", nullptr, Expr("s"));
+  WrapInFunction(v);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: function variable must have a constructible type");
+}
+
+TEST_F(ResolverVarLetValidationTest, InvalidStorageClassForInitializer) {
+  // var<workgroup> v : f32 = 1.23;
+  Global(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kWorkgroup,
+         Expr(1.23f));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: var of storage class 'workgroup' cannot have "
+            "an initializer. var initializers are only supported for the "
+            "storage classes 'private' and 'function'");
+}
+
+TEST_F(ResolverVarLetValidationTest, VectorLetNoType) {
+  // let a : mat3x3 = mat3x3<f32>();
+  WrapInFunction(Const("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3),
+                       vec3<f32>()));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, VectorVarNoType) {
+  // var a : mat3x3;
+  WrapInFunction(Var("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, MatrixLetNoType) {
+  // let a : mat3x3 = mat3x3<f32>();
+  WrapInFunction(Const("a",
+                       create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3),
+                       mat3x3<f32>()));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, MatrixVarNoType) {
+  // var a : mat3x3;
+  WrapInFunction(
+      Var("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/tint/scope_stack.h b/src/tint/scope_stack.h
new file mode 100644
index 0000000..1244584
--- /dev/null
+++ b/src/tint/scope_stack.h
@@ -0,0 +1,80 @@
+// Copyright 2020 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_SCOPE_STACK_H_
+#define SRC_TINT_SCOPE_STACK_H_
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/symbol.h"
+
+namespace tint {
+
+/// Used to store a stack of scope information.
+/// The stack starts with a global scope which can not be popped.
+template <class T>
+class ScopeStack {
+ public:
+  /// Constructor
+  ScopeStack() {
+    // Push global bucket
+    stack_.push_back({});
+  }
+  /// Copy Constructor
+  ScopeStack(const ScopeStack&) = default;
+  ~ScopeStack() = default;
+
+  /// Push a new scope on to the stack
+  void Push() { stack_.push_back({}); }
+
+  /// Pop the scope off the top of the stack
+  void Pop() {
+    if (stack_.size() > 1) {
+      stack_.pop_back();
+    }
+  }
+
+  /// Assigns the value into the top most scope of the stack.
+  /// @param symbol the symbol of the value
+  /// @param val the value
+  /// @returns the old value if there was an existing symbol at the top of the
+  /// stack, otherwise the zero initializer for type T.
+  T Set(const Symbol& symbol, T val) {
+    std::swap(val, stack_.back()[symbol]);
+    return val;
+  }
+
+  /// Retrieves a value from the stack
+  /// @param symbol the symbol to look for
+  /// @returns the value, or the zero initializer if the value was not found
+  T Get(const Symbol& symbol) const {
+    for (auto iter = stack_.rbegin(); iter != stack_.rend(); ++iter) {
+      auto& map = *iter;
+      auto val = map.find(symbol);
+      if (val != map.end()) {
+        return val->second;
+      }
+    }
+
+    return T{};
+  }
+
+ private:
+  std::vector<std::unordered_map<Symbol, T>> stack_;
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_SCOPE_STACK_H_
diff --git a/src/tint/scope_stack_test.cc b/src/tint/scope_stack_test.cc
new file mode 100644
index 0000000..3754a41
--- /dev/null
+++ b/src/tint/scope_stack_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 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/scope_stack.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace {
+
+class ScopeStackTest : public ProgramBuilder, public testing::Test {};
+
+TEST_F(ScopeStackTest, Get) {
+  ScopeStack<uint32_t> s;
+  Symbol a(1, ID());
+  Symbol b(3, ID());
+  s.Push();
+  s.Set(a, 5u);
+  s.Set(b, 10u);
+
+  EXPECT_EQ(s.Get(a), 5u);
+  EXPECT_EQ(s.Get(b), 10u);
+
+  s.Push();
+
+  s.Set(a, 15u);
+  EXPECT_EQ(s.Get(a), 15u);
+  EXPECT_EQ(s.Get(b), 10u);
+
+  s.Pop();
+  EXPECT_EQ(s.Get(a), 5u);
+  EXPECT_EQ(s.Get(b), 10u);
+}
+
+TEST_F(ScopeStackTest, Get_MissingSymbol) {
+  ScopeStack<uint32_t> s;
+  Symbol sym(1, ID());
+  EXPECT_EQ(s.Get(sym), 0u);
+}
+
+TEST_F(ScopeStackTest, Set) {
+  ScopeStack<uint32_t> s;
+  Symbol a(1, ID());
+  Symbol b(2, ID());
+
+  EXPECT_EQ(s.Set(a, 5u), 0u);
+  EXPECT_EQ(s.Get(a), 5u);
+
+  EXPECT_EQ(s.Set(b, 10u), 0u);
+  EXPECT_EQ(s.Get(b), 10u);
+
+  EXPECT_EQ(s.Set(a, 20u), 5u);
+  EXPECT_EQ(s.Get(a), 20u);
+
+  EXPECT_EQ(s.Set(b, 25u), 10u);
+  EXPECT_EQ(s.Get(b), 25u);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/sem/array.cc b/src/tint/sem/array.cc
new file mode 100644
index 0000000..8b20aa6
--- /dev/null
+++ b/src/tint/sem/array.cc
@@ -0,0 +1,80 @@
+// 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.h"
+
+#include <string>
+
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Array);
+
+namespace tint {
+namespace sem {
+
+Array::Array(const Type* element,
+             uint32_t count,
+             uint32_t align,
+             uint32_t size,
+             uint32_t stride,
+             uint32_t implicit_stride)
+    : element_(element),
+      count_(count),
+      align_(align),
+      size_(size),
+      stride_(stride),
+      implicit_stride_(implicit_stride),
+      constructible_(count > 0  // Runtime-sized arrays are not constructible
+                     && element->IsConstructible()) {
+  TINT_ASSERT(Semantic, element_);
+}
+
+bool Array::IsConstructible() const {
+  return constructible_;
+}
+
+std::string Array::type_name() const {
+  std::string type_name = "__array" + element_->type_name();
+  type_name += "_count_" + std::to_string(count_);
+  type_name += "_align_" + std::to_string(align_);
+  type_name += "_size_" + std::to_string(size_);
+  type_name += "_stride_" + std::to_string(stride_);
+  // Note: implicit_stride is not part of the type_name string as this is
+  // derived from the element type
+  return type_name;
+}
+
+std::string Array::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  if (!IsStrideImplicit()) {
+    out << "@stride(" << stride_ << ") ";
+  }
+  out << "array<" << element_->FriendlyName(symbols);
+  if (!IsRuntimeSized()) {
+    out << ", " << count_;
+  }
+  out << ">";
+  return out.str();
+}
+
+uint32_t Array::Align() const {
+  return align_;
+}
+
+uint32_t Array::Size() const {
+  return size_;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/array.h b/src/tint/sem/array.h
new file mode 100644
index 0000000..bba467d
--- /dev/null
+++ b/src/tint/sem/array.h
@@ -0,0 +1,113 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_ARRAY_H_
+#define SRC_TINT_SEM_ARRAY_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "src/tint/sem/node.h"
+#include "src/tint/sem/type.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class Array;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Array holds the semantic information for Array nodes.
+class Array : public Castable<Array, Type> {
+ public:
+  /// Constructor
+  /// @param element the array element type
+  /// @param count the number of elements in the array. 0 represents a
+  /// runtime-sized array.
+  /// @param align the byte alignment of the array
+  /// @param size the byte size of the array
+  /// @param stride the number of bytes from the start of one element of the
+  /// array to the start of the next element
+  /// @param implicit_stride the number of bytes from the start of one element
+  /// of the array to the start of the next element, if there was no `@stride`
+  /// attribute applied.
+  Array(Type const* element,
+        uint32_t count,
+        uint32_t align,
+        uint32_t size,
+        uint32_t stride,
+        uint32_t implicit_stride);
+
+  /// @return the array element type
+  Type const* ElemType() const { return element_; }
+
+  /// @returns the number of elements in the array. 0 represents a runtime-sized
+  /// array.
+  uint32_t Count() const { return count_; }
+
+  /// @returns the byte alignment of the array
+  /// @note this may differ from the alignment of a structure member of this
+  /// array type, if the member is annotated with the `@align(n)` attribute.
+  uint32_t Align() const override;
+
+  /// @returns the byte size of the array
+  /// @note this may differ from the size of a structure member of this array
+  /// type, if the member is annotated with the `@size(n)` attribute.
+  uint32_t Size() const override;
+
+  /// @returns the number of bytes from the start of one element of the
+  /// array to the start of the next element
+  uint32_t Stride() const { return stride_; }
+
+  /// @returns the number of bytes from the start of one element of the
+  /// array to the start of the next element, if there was no `@stride`
+  /// attribute applied
+  uint32_t ImplicitStride() const { return implicit_stride_; }
+
+  /// @returns true if the value returned by Stride() matches the element's
+  /// natural stride
+  bool IsStrideImplicit() const { return stride_ == implicit_stride_; }
+
+  /// @returns true if this array is runtime sized
+  bool IsRuntimeSized() const { return count_ == 0; }
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the name for the type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  Type const* const element_;
+  const uint32_t count_;
+  const uint32_t align_;
+  const uint32_t size_;
+  const uint32_t stride_;
+  const uint32_t implicit_stride_;
+  const bool constructible_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_ARRAY_H_
diff --git a/src/tint/sem/atomic_type.cc b/src/tint/sem/atomic_type.cc
new file mode 100644
index 0000000..a57639b
--- /dev/null
+++ b/src/tint/sem/atomic_type.cc
@@ -0,0 +1,58 @@
+// 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/atomic_type.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/reference_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Atomic);
+
+namespace tint {
+namespace sem {
+
+Atomic::Atomic(const sem::Type* subtype) : subtype_(subtype) {
+  TINT_ASSERT(AST, !subtype->Is<Reference>());
+}
+
+std::string Atomic::type_name() const {
+  std::ostringstream out;
+  out << "__atomic" << subtype_->type_name();
+  return out.str();
+}
+
+std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "atomic<" << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+uint32_t Atomic::Size() const {
+  return subtype_->Size();
+}
+
+uint32_t Atomic::Align() const {
+  return subtype_->Align();
+}
+
+bool Atomic::IsConstructible() const {
+  return false;
+}
+
+Atomic::Atomic(Atomic&&) = default;
+
+Atomic::~Atomic() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/atomic_type.h b/src/tint/sem/atomic_type.h
new file mode 100644
index 0000000..7a70d0a
--- /dev/null
+++ b/src/tint/sem/atomic_type.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_ATOMIC_TYPE_H_
+#define SRC_TINT_SEM_ATOMIC_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A atomic type.
+class Atomic : public Castable<Atomic, Type> {
+ public:
+  /// Constructor
+  /// @param subtype the atomic type
+  explicit Atomic(const sem::Type* subtype);
+
+  /// Move constructor
+  Atomic(Atomic&&);
+  ~Atomic() override;
+
+  /// @returns the atomic type
+  const sem::Type* Type() const { return subtype_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns the size in bytes of the type.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type.
+  uint32_t Align() const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-typesd
+  bool IsConstructible() const override;
+
+ private:
+  sem::Type const* const subtype_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_ATOMIC_TYPE_H_
diff --git a/src/tint/sem/atomic_type_test.cc b/src/tint/sem/atomic_type_test.cc
new file mode 100644
index 0000000..68b5809
--- /dev/null
+++ b/src/tint/sem/atomic_type_test.cc
@@ -0,0 +1,42 @@
+// 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/atomic_type.h"
+
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using AtomicTest = TestHelper;
+
+TEST_F(AtomicTest, Creation) {
+  auto* a = create<Atomic>(create<I32>());
+  EXPECT_TRUE(a->Type()->Is<sem::I32>());
+}
+
+TEST_F(AtomicTest, TypeName) {
+  auto* a = create<Atomic>(create<I32>());
+  EXPECT_EQ(a->type_name(), "__atomic__i32");
+}
+
+TEST_F(AtomicTest, FriendlyName) {
+  auto* a = create<Atomic>(create<I32>());
+  EXPECT_EQ(a->FriendlyName(Symbols()), "atomic<i32>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/behavior.cc b/src/tint/sem/behavior.cc
new file mode 100644
index 0000000..a443d33
--- /dev/null
+++ b/src/tint/sem/behavior.cc
@@ -0,0 +1,39 @@
+// 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/behavior.h"
+
+namespace tint {
+namespace sem {
+
+std::ostream& operator<<(std::ostream& out, Behavior behavior) {
+  switch (behavior) {
+    case Behavior::kReturn:
+      return out << "Return";
+    case Behavior::kDiscard:
+      return out << "Discard";
+    case Behavior::kBreak:
+      return out << "Break";
+    case Behavior::kContinue:
+      return out << "Continue";
+    case Behavior::kFallthrough:
+      return out << "Fallthrough";
+    case Behavior::kNext:
+      return out << "Next";
+  }
+  return out << "<unknown>";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/behavior.h b/src/tint/sem/behavior.h
new file mode 100644
index 0000000..f921080
--- /dev/null
+++ b/src/tint/sem/behavior.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_BEHAVIOR_H_
+#define SRC_TINT_SEM_BEHAVIOR_H_
+
+#include "src/tint/utils/enum_set.h"
+
+namespace tint {
+namespace sem {
+
+/// Behavior enumerates the possible behaviors of an expression or statement.
+/// @see https://www.w3.org/TR/WGSL/#behaviors
+enum class Behavior {
+  kReturn,
+  kDiscard,
+  kBreak,
+  kContinue,
+  kFallthrough,
+  kNext,
+};
+
+/// Behaviors is a set of Behavior
+using Behaviors = utils::EnumSet<Behavior>;
+
+/// Writes the Behavior to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param behavior the Behavior to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, Behavior behavior);
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_BEHAVIOR_H_
diff --git a/src/tint/sem/binding_point.h b/src/tint/sem/binding_point.h
new file mode 100644
index 0000000..ed968e2
--- /dev/null
+++ b/src/tint/sem/binding_point.h
@@ -0,0 +1,70 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_BINDING_POINT_H_
+#define SRC_TINT_SEM_BINDING_POINT_H_
+
+#include <stdint.h>
+
+#include <functional>
+
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace sem {
+
+/// BindingPoint holds a group and binding index.
+struct BindingPoint {
+  /// The `[[group]]` part of the binding point
+  uint32_t group = 0;
+  /// The `[[binding]]` part of the binding point
+  uint32_t binding = 0;
+
+  /// Equality operator
+  /// @param rhs the BindingPoint to compare against
+  /// @returns true if this BindingPoint is equal to `rhs`
+  inline bool operator==(const BindingPoint& rhs) const {
+    return group == rhs.group && binding == rhs.binding;
+  }
+
+  /// Inequality operator
+  /// @param rhs the BindingPoint to compare against
+  /// @returns true if this BindingPoint is not equal to `rhs`
+  inline bool operator!=(const BindingPoint& rhs) const {
+    return !(*this == rhs);
+  }
+};
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::sem::BindingPoint so
+/// BindingPoints can be used as keys for std::unordered_map and
+/// std::unordered_set.
+template <>
+class hash<tint::sem::BindingPoint> {
+ public:
+  /// @param binding_point the binding point to create a hash for
+  /// @return the hash value
+  inline std::size_t operator()(
+      const tint::sem::BindingPoint& binding_point) const {
+    return tint::utils::Hash(binding_point.group, binding_point.binding);
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_BINDING_POINT_H_
diff --git a/src/tint/sem/block_statement.cc b/src/tint/sem/block_statement.cc
new file mode 100644
index 0000000..fe2bac2
--- /dev/null
+++ b/src/tint/sem/block_statement.cc
@@ -0,0 +1,67 @@
+// 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/block_statement.h"
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/function.h"
+#include "src/tint/sem/function.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BlockStatement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::FunctionBlockStatement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopBlockStatement);
+
+namespace tint {
+namespace sem {
+
+BlockStatement::BlockStatement(const ast::BlockStatement* declaration,
+                               const CompoundStatement* parent,
+                               const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+BlockStatement::~BlockStatement() = default;
+
+const ast::BlockStatement* BlockStatement::Declaration() const {
+  return Base::Declaration()->As<ast::BlockStatement>();
+}
+
+void BlockStatement::AddDecl(const ast::Variable* var) {
+  decls_.push_back(var);
+}
+
+FunctionBlockStatement::FunctionBlockStatement(const sem::Function* function)
+    : Base(function->Declaration()->body, nullptr, function) {
+  TINT_ASSERT(Semantic, function);
+}
+
+FunctionBlockStatement::~FunctionBlockStatement() = default;
+
+LoopBlockStatement::LoopBlockStatement(const ast::BlockStatement* declaration,
+                                       const CompoundStatement* parent,
+                                       const sem::Function* function)
+    : Base(declaration, parent, function) {
+  TINT_ASSERT(Semantic, parent);
+  TINT_ASSERT(Semantic, function);
+}
+LoopBlockStatement::~LoopBlockStatement() = default;
+
+void LoopBlockStatement::SetFirstContinue(
+    const ast::ContinueStatement* first_continue,
+    size_t num_decls) {
+  first_continue_ = first_continue;
+  num_decls_at_first_continue_ = num_decls;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/block_statement.h b/src/tint/sem/block_statement.h
new file mode 100644
index 0000000..2b54447
--- /dev/null
+++ b/src/tint/sem/block_statement.h
@@ -0,0 +1,122 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_BLOCK_STATEMENT_H_
+#define SRC_TINT_SEM_BLOCK_STATEMENT_H_
+
+#include <cstddef>
+#include <vector>
+
+#include "src/tint/sem/statement.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class BlockStatement;
+class ContinueStatement;
+class Function;
+class Variable;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Holds semantic information about a block, such as parent block and variables
+/// declared in the block.
+class BlockStatement : public Castable<BlockStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this block statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  BlockStatement(const ast::BlockStatement* declaration,
+                 const CompoundStatement* parent,
+                 const sem::Function* function);
+
+  /// Destructor
+  ~BlockStatement() override;
+
+  /// @returns the AST block statement associated with this semantic block
+  /// statement
+  const ast::BlockStatement* Declaration() const;
+
+  /// @returns the declarations associated with this block
+  const std::vector<const ast::Variable*>& Decls() const { return decls_; }
+
+  /// Associates a declaration with this block.
+  /// @param var a variable declaration to be added to the block
+  void AddDecl(const ast::Variable* var);
+
+ private:
+  std::vector<const ast::Variable*> decls_;
+};
+
+/// The root block statement for a function
+class FunctionBlockStatement
+    : public Castable<FunctionBlockStatement, BlockStatement> {
+ public:
+  /// Constructor
+  /// @param function the owning function
+  explicit FunctionBlockStatement(const sem::Function* function);
+
+  /// Destructor
+  ~FunctionBlockStatement() override;
+};
+
+/// Holds semantic information about a loop body block or for-loop body block
+class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this block statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  LoopBlockStatement(const ast::BlockStatement* declaration,
+                     const CompoundStatement* parent,
+                     const sem::Function* function);
+
+  /// Destructor
+  ~LoopBlockStatement() override;
+
+  /// @returns the first continue statement in this loop block, or nullptr if
+  /// there are no continue statements in the block
+  const ast::ContinueStatement* FirstContinue() const {
+    return first_continue_;
+  }
+
+  /// @returns the number of variables declared before the first continue
+  /// statement
+  size_t NumDeclsAtFirstContinue() const {
+    return num_decls_at_first_continue_;
+  }
+
+  /// Allows the resolver to record the first continue statement in the block
+  /// and the number of variables declared prior to that statement.
+  /// @param first_continue the first continue statement in the block
+  /// @param num_decls the number of variable declarations before that continue
+  void SetFirstContinue(const ast::ContinueStatement* first_continue,
+                        size_t num_decls);
+
+ private:
+  /// The first continue statement in this loop block.
+  const ast::ContinueStatement* first_continue_ = nullptr;
+
+  /// The number of variables declared before the first continue statement.
+  size_t num_decls_at_first_continue_ = 0;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_BLOCK_STATEMENT_H_
diff --git a/src/tint/sem/bool_type.cc b/src/tint/sem/bool_type.cc
new file mode 100644
index 0000000..abbc23c
--- /dev/null
+++ b/src/tint/sem/bool_type.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/bool_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Bool);
+
+namespace tint {
+namespace sem {
+
+Bool::Bool() = default;
+
+Bool::Bool(Bool&&) = default;
+
+Bool::~Bool() = default;
+
+std::string Bool::type_name() const {
+  return "__bool";
+}
+
+std::string Bool::FriendlyName(const SymbolTable&) const {
+  return "bool";
+}
+
+bool Bool::IsConstructible() const {
+  return true;
+}
+
+uint32_t Bool::Size() const {
+  return 4;
+}
+
+uint32_t Bool::Align() const {
+  return 4;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/bool_type.h b/src/tint/sem/bool_type.h
new file mode 100644
index 0000000..85a8977
--- /dev/null
+++ b/src/tint/sem/bool_type.h
@@ -0,0 +1,66 @@
+// Copyright 2020 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_BOOL_TYPE_H_
+#define SRC_TINT_SEM_BOOL_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+// X11 likes to #define Bool leading to confusing error messages.
+// If its defined, undefine it.
+#ifdef Bool
+#undef Bool
+#endif
+
+namespace tint {
+namespace sem {
+
+/// A boolean type
+class Bool : public Castable<Bool, Type> {
+ public:
+  /// Constructor
+  Bool();
+  /// Move constructor
+  Bool(Bool&&);
+  ~Bool() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the size in bytes of the type.
+  /// @note: booleans are not host-sharable, but still may exist in workgroup
+  /// storage.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type.
+  /// @note: booleans are not host-sharable, but still may exist in workgroup
+  /// storage.
+  uint32_t Align() const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_BOOL_TYPE_H_
diff --git a/src/tint/sem/bool_type_test.cc b/src/tint/sem/bool_type_test.cc
new file mode 100644
index 0000000..f9b48e8
--- /dev/null
+++ b/src/tint/sem/bool_type_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using BoolTest = TestHelper;
+
+TEST_F(BoolTest, TypeName) {
+  Bool b;
+  EXPECT_EQ(b.type_name(), "__bool");
+}
+
+TEST_F(BoolTest, FriendlyName) {
+  Bool b;
+  EXPECT_EQ(b.FriendlyName(Symbols()), "bool");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
new file mode 100644
index 0000000..90187ea
--- /dev/null
+++ b/src/tint/sem/builtin.cc
@@ -0,0 +1,176 @@
+// Copyright 2020 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.
+
+// Doxygen seems to trip over this file for some unknown reason. Disable.
+//! @cond Doxygen_Suppress
+
+#include "src/tint/sem/builtin.h"
+
+#include <vector>
+
+#include "src/tint/utils/to_const_ptr_vec.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Builtin);
+
+namespace tint {
+namespace sem {
+
+const char* Builtin::str() const {
+  return sem::str(type_);
+}
+
+bool IsCoarseDerivativeBuiltin(BuiltinType i) {
+  return i == BuiltinType::kDpdxCoarse || i == BuiltinType::kDpdyCoarse ||
+         i == BuiltinType::kFwidthCoarse;
+}
+
+bool IsFineDerivativeBuiltin(BuiltinType i) {
+  return i == BuiltinType::kDpdxFine || i == BuiltinType::kDpdyFine ||
+         i == BuiltinType::kFwidthFine;
+}
+
+bool IsDerivativeBuiltin(BuiltinType i) {
+  return i == BuiltinType::kDpdx || i == BuiltinType::kDpdy ||
+         i == BuiltinType::kFwidth || IsCoarseDerivativeBuiltin(i) ||
+         IsFineDerivativeBuiltin(i);
+}
+
+bool IsFloatClassificationBuiltin(BuiltinType i) {
+  return i == BuiltinType::kIsFinite || i == BuiltinType::kIsInf ||
+         i == BuiltinType::kIsNan || i == BuiltinType::kIsNormal;
+}
+
+bool IsTextureBuiltin(BuiltinType i) {
+  return IsImageQueryBuiltin(i) || i == BuiltinType::kTextureLoad ||
+         i == BuiltinType::kTextureGather ||
+         i == BuiltinType::kTextureGatherCompare ||
+         i == BuiltinType::kTextureSample ||
+         i == BuiltinType::kTextureSampleLevel ||
+         i == BuiltinType::kTextureSampleBias ||
+         i == BuiltinType::kTextureSampleCompare ||
+         i == BuiltinType::kTextureSampleCompareLevel ||
+         i == BuiltinType::kTextureSampleGrad ||
+         i == BuiltinType::kTextureStore;
+}
+
+bool IsImageQueryBuiltin(BuiltinType i) {
+  return i == BuiltinType::kTextureDimensions ||
+         i == BuiltinType::kTextureNumLayers ||
+         i == BuiltinType::kTextureNumLevels ||
+         i == BuiltinType::kTextureNumSamples;
+}
+
+bool IsDataPackingBuiltin(BuiltinType i) {
+  return i == BuiltinType::kPack4x8snorm || i == BuiltinType::kPack4x8unorm ||
+         i == BuiltinType::kPack2x16snorm || i == BuiltinType::kPack2x16unorm ||
+         i == BuiltinType::kPack2x16float;
+}
+
+bool IsDataUnpackingBuiltin(BuiltinType i) {
+  return i == BuiltinType::kUnpack4x8snorm ||
+         i == BuiltinType::kUnpack4x8unorm ||
+         i == BuiltinType::kUnpack2x16snorm ||
+         i == BuiltinType::kUnpack2x16unorm ||
+         i == BuiltinType::kUnpack2x16float;
+}
+
+bool IsBarrierBuiltin(BuiltinType i) {
+  return i == BuiltinType::kWorkgroupBarrier ||
+         i == BuiltinType::kStorageBarrier;
+}
+
+bool IsAtomicBuiltin(BuiltinType i) {
+  return i == sem::BuiltinType::kAtomicLoad ||
+         i == sem::BuiltinType::kAtomicStore ||
+         i == sem::BuiltinType::kAtomicAdd ||
+         i == sem::BuiltinType::kAtomicSub ||
+         i == sem::BuiltinType::kAtomicMax ||
+         i == sem::BuiltinType::kAtomicMin ||
+         i == sem::BuiltinType::kAtomicAnd ||
+         i == sem::BuiltinType::kAtomicOr ||
+         i == sem::BuiltinType::kAtomicXor ||
+         i == sem::BuiltinType::kAtomicExchange ||
+         i == sem::BuiltinType::kAtomicCompareExchangeWeak;
+}
+
+Builtin::Builtin(BuiltinType type,
+                 const sem::Type* return_type,
+                 std::vector<Parameter*> parameters,
+                 PipelineStageSet supported_stages,
+                 bool is_deprecated)
+    : Base(return_type, utils::ToConstPtrVec(parameters)),
+      type_(type),
+      supported_stages_(supported_stages),
+      is_deprecated_(is_deprecated) {
+  for (auto* parameter : parameters) {
+    parameter->SetOwner(this);
+  }
+}
+
+Builtin::~Builtin() = default;
+
+bool Builtin::IsCoarseDerivative() const {
+  return IsCoarseDerivativeBuiltin(type_);
+}
+
+bool Builtin::IsFineDerivative() const {
+  return IsFineDerivativeBuiltin(type_);
+}
+
+bool Builtin::IsDerivative() const {
+  return IsDerivativeBuiltin(type_);
+}
+
+bool Builtin::IsFloatClassification() const {
+  return IsFloatClassificationBuiltin(type_);
+}
+
+bool Builtin::IsTexture() const {
+  return IsTextureBuiltin(type_);
+}
+
+bool Builtin::IsImageQuery() const {
+  return IsImageQueryBuiltin(type_);
+}
+
+bool Builtin::IsDataPacking() const {
+  return IsDataPackingBuiltin(type_);
+}
+
+bool Builtin::IsDataUnpacking() const {
+  return IsDataUnpackingBuiltin(type_);
+}
+
+bool Builtin::IsBarrier() const {
+  return IsBarrierBuiltin(type_);
+}
+
+bool Builtin::IsAtomic() const {
+  return IsAtomicBuiltin(type_);
+}
+
+bool Builtin::HasSideEffects() const {
+  if (IsAtomic() && type_ != sem::BuiltinType::kAtomicLoad) {
+    return true;
+  }
+  if (type_ == sem::BuiltinType::kTextureStore) {
+    return true;
+  }
+  return false;
+}
+
+}  // namespace sem
+}  // namespace tint
+
+//! @endcond
diff --git a/src/tint/sem/builtin.h b/src/tint/sem/builtin.h
new file mode 100644
index 0000000..6ad4b00
--- /dev/null
+++ b/src/tint/sem/builtin.h
@@ -0,0 +1,177 @@
+// Copyright 2020 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_BUILTIN_H_
+#define SRC_TINT_SEM_BUILTIN_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/sem/builtin_type.h"
+#include "src/tint/sem/call_target.h"
+#include "src/tint/sem/pipeline_stage_set.h"
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace sem {
+
+/// Determines if the given `i` is a coarse derivative
+/// @param i the builtin type
+/// @returns true if the given derivative is coarse.
+bool IsCoarseDerivativeBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a fine derivative
+/// @param i the builtin type
+/// @returns true if the given derivative is fine.
+bool IsFineDerivativeBuiltin(BuiltinType i);
+
+/// Determine if the given `i` is a derivative builtin
+/// @param i the builtin type
+/// @returns true if the given `i` is a derivative builtin
+bool IsDerivativeBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a float classification builtin
+/// @param i the builtin type
+/// @returns true if the given `i` is a float builtin
+bool IsFloatClassificationBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a texture operation builtin
+/// @param i the builtin type
+/// @returns true if the given `i` is a texture operation builtin
+bool IsTextureBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a image query builtin
+/// @param i the builtin type
+/// @returns true if the given `i` is a image query builtin
+bool IsImageQueryBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a data packing builtin
+/// @param i the builtin
+/// @returns true if the given `i` is a data packing builtin
+bool IsDataPackingBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a data unpacking builtin
+/// @param i the builtin
+/// @returns true if the given `i` is a data unpacking builtin
+bool IsDataUnpackingBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a barrier builtin
+/// @param i the builtin
+/// @returns true if the given `i` is a barrier builtin
+bool IsBarrierBuiltin(BuiltinType i);
+
+/// Determines if the given `i` is a atomic builtin
+/// @param i the builtin
+/// @returns true if the given `i` is a atomic builtin
+bool IsAtomicBuiltin(BuiltinType i);
+
+/// Builtin holds the semantic information for a builtin function.
+class Builtin : public Castable<Builtin, CallTarget> {
+ public:
+  /// Constructor
+  /// @param type the builtin type
+  /// @param return_type the return type for the builtin call
+  /// @param parameters the parameters for the builtin overload
+  /// @param supported_stages the pipeline stages that this builtin can be
+  /// used in
+  /// @param is_deprecated true if the particular overload is considered
+  /// deprecated
+  Builtin(BuiltinType type,
+          const sem::Type* return_type,
+          std::vector<Parameter*> parameters,
+          PipelineStageSet supported_stages,
+          bool is_deprecated);
+
+  /// Destructor
+  ~Builtin() override;
+
+  /// @return the type of the builtin
+  BuiltinType Type() const { return type_; }
+
+  /// @return the pipeline stages that this builtin can be used in
+  PipelineStageSet SupportedStages() const { return supported_stages_; }
+
+  /// @return true if the builtin overload is considered deprecated
+  bool IsDeprecated() const { return is_deprecated_; }
+
+  /// @returns the name of the builtin function type. The spelling, including
+  /// case, matches the name in the WGSL spec.
+  const char* str() const;
+
+  /// @returns true if builtin is a coarse derivative builtin
+  bool IsCoarseDerivative() const;
+
+  /// @returns true if builtin is a fine a derivative builtin
+  bool IsFineDerivative() const;
+
+  /// @returns true if builtin is a derivative builtin
+  bool IsDerivative() const;
+
+  /// @returns true if builtin is a float builtin
+  bool IsFloatClassification() const;
+
+  /// @returns true if builtin is a texture operation builtin
+  bool IsTexture() const;
+
+  /// @returns true if builtin is a image query builtin
+  bool IsImageQuery() const;
+
+  /// @returns true if builtin is a data packing builtin
+  bool IsDataPacking() const;
+
+  /// @returns true if builtin is a data unpacking builtin
+  bool IsDataUnpacking() const;
+
+  /// @returns true if builtin is a barrier builtin
+  bool IsBarrier() const;
+
+  /// @returns true if builtin is a atomic builtin
+  bool IsAtomic() const;
+
+  /// @returns true if intrinsic may have side-effects (i.e. writes to at least
+  /// one of its inputs)
+  bool HasSideEffects() const;
+
+ private:
+  const BuiltinType type_;
+  const PipelineStageSet supported_stages_;
+  const bool is_deprecated_;
+};
+
+/// Constant value used by the degrees() builtin
+static constexpr double kRadToDeg = 57.295779513082322865;
+
+/// Constant value used by the radians() builtin
+static constexpr double kDegToRad = 0.017453292519943295474;
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::sem::Builtin
+template <>
+class hash<tint::sem::Builtin> {
+ public:
+  /// @param i the Builtin to create a hash for
+  /// @return the hash value
+  inline std::size_t operator()(const tint::sem::Builtin& i) const {
+    return tint::utils::Hash(i.Type(), i.SupportedStages(), i.ReturnType(),
+                             i.Parameters(), i.IsDeprecated());
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_BUILTIN_H_
diff --git a/src/tint/sem/builtin_test.cc b/src/tint/sem/builtin_test.cc
new file mode 100644
index 0000000..f489003
--- /dev/null
+++ b/src/tint/sem/builtin_test.cc
@@ -0,0 +1,131 @@
+// 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/builtin.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+struct BuiltinData {
+  const char* name;
+  BuiltinType builtin;
+};
+
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.name;
+  return out;
+}
+
+using BuiltinTypeTest = testing::TestWithParam<BuiltinData>;
+
+TEST_P(BuiltinTypeTest, Parse) {
+  auto param = GetParam();
+  EXPECT_EQ(ParseBuiltinType(param.name), param.builtin);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinTypeTest,
+    BuiltinTypeTest,
+    testing::Values(
+        BuiltinData{"abs", BuiltinType::kAbs},
+        BuiltinData{"acos", BuiltinType::kAcos},
+        BuiltinData{"all", BuiltinType::kAll},
+        BuiltinData{"any", BuiltinType::kAny},
+        BuiltinData{"arrayLength", BuiltinType::kArrayLength},
+        BuiltinData{"asin", BuiltinType::kAsin},
+        BuiltinData{"atan", BuiltinType::kAtan},
+        BuiltinData{"atan2", BuiltinType::kAtan2},
+        BuiltinData{"ceil", BuiltinType::kCeil},
+        BuiltinData{"clamp", BuiltinType::kClamp},
+        BuiltinData{"cos", BuiltinType::kCos},
+        BuiltinData{"cosh", BuiltinType::kCosh},
+        BuiltinData{"countOneBits", BuiltinType::kCountOneBits},
+        BuiltinData{"cross", BuiltinType::kCross},
+        BuiltinData{"determinant", BuiltinType::kDeterminant},
+        BuiltinData{"distance", BuiltinType::kDistance},
+        BuiltinData{"dot", BuiltinType::kDot},
+        BuiltinData{"dpdx", BuiltinType::kDpdx},
+        BuiltinData{"dpdxCoarse", BuiltinType::kDpdxCoarse},
+        BuiltinData{"dpdxFine", BuiltinType::kDpdxFine},
+        BuiltinData{"dpdy", BuiltinType::kDpdy},
+        BuiltinData{"dpdyCoarse", BuiltinType::kDpdyCoarse},
+        BuiltinData{"dpdyFine", BuiltinType::kDpdyFine},
+        BuiltinData{"exp", BuiltinType::kExp},
+        BuiltinData{"exp2", BuiltinType::kExp2},
+        BuiltinData{"faceForward", BuiltinType::kFaceForward},
+        BuiltinData{"floor", BuiltinType::kFloor},
+        BuiltinData{"fma", BuiltinType::kFma},
+        BuiltinData{"fract", BuiltinType::kFract},
+        BuiltinData{"frexp", BuiltinType::kFrexp},
+        BuiltinData{"fwidth", BuiltinType::kFwidth},
+        BuiltinData{"fwidthCoarse", BuiltinType::kFwidthCoarse},
+        BuiltinData{"fwidthFine", BuiltinType::kFwidthFine},
+        BuiltinData{"inverseSqrt", BuiltinType::kInverseSqrt},
+        BuiltinData{"isFinite", BuiltinType::kIsFinite},
+        BuiltinData{"isInf", BuiltinType::kIsInf},
+        BuiltinData{"isNan", BuiltinType::kIsNan},
+        BuiltinData{"isNormal", BuiltinType::kIsNormal},
+        BuiltinData{"ldexp", BuiltinType::kLdexp},
+        BuiltinData{"length", BuiltinType::kLength},
+        BuiltinData{"log", BuiltinType::kLog},
+        BuiltinData{"log2", BuiltinType::kLog2},
+        BuiltinData{"max", BuiltinType::kMax},
+        BuiltinData{"min", BuiltinType::kMin},
+        BuiltinData{"mix", BuiltinType::kMix},
+        BuiltinData{"modf", BuiltinType::kModf},
+        BuiltinData{"normalize", BuiltinType::kNormalize},
+        BuiltinData{"pow", BuiltinType::kPow},
+        BuiltinData{"reflect", BuiltinType::kReflect},
+        BuiltinData{"reverseBits", BuiltinType::kReverseBits},
+        BuiltinData{"round", BuiltinType::kRound},
+        BuiltinData{"select", BuiltinType::kSelect},
+        BuiltinData{"sign", BuiltinType::kSign},
+        BuiltinData{"sin", BuiltinType::kSin},
+        BuiltinData{"sinh", BuiltinType::kSinh},
+        BuiltinData{"smoothStep", BuiltinType::kSmoothStep},
+        BuiltinData{"sqrt", BuiltinType::kSqrt},
+        BuiltinData{"step", BuiltinType::kStep},
+        BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
+        BuiltinData{"tan", BuiltinType::kTan},
+        BuiltinData{"tanh", BuiltinType::kTanh},
+        BuiltinData{"textureDimensions", BuiltinType::kTextureDimensions},
+        BuiltinData{"textureLoad", BuiltinType::kTextureLoad},
+        BuiltinData{"textureNumLayers", BuiltinType::kTextureNumLayers},
+        BuiltinData{"textureNumLevels", BuiltinType::kTextureNumLevels},
+        BuiltinData{"textureNumSamples", BuiltinType::kTextureNumSamples},
+        BuiltinData{"textureSample", BuiltinType::kTextureSample},
+        BuiltinData{"textureSampleBias", BuiltinType::kTextureSampleBias},
+        BuiltinData{"textureSampleCompare", BuiltinType::kTextureSampleCompare},
+        BuiltinData{"textureSampleCompareLevel",
+                    BuiltinType::kTextureSampleCompareLevel},
+        BuiltinData{"textureSampleGrad", BuiltinType::kTextureSampleGrad},
+        BuiltinData{"textureSampleLevel", BuiltinType::kTextureSampleLevel},
+        BuiltinData{"trunc", BuiltinType::kTrunc},
+        BuiltinData{"unpack2x16float", BuiltinType::kUnpack2x16float},
+        BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2x16snorm},
+        BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2x16unorm},
+        BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4x8snorm},
+        BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4x8unorm},
+        BuiltinData{"workgroupBarrier", BuiltinType::kWorkgroupBarrier}));
+
+TEST_F(BuiltinTypeTest, ParseNoMatch) {
+  EXPECT_EQ(ParseBuiltinType("not_builtin"), BuiltinType::kNone);
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/builtin_type.cc b/src/tint/sem/builtin_type.cc
new file mode 100644
index 0000000..1a59272
--- /dev/null
+++ b/src/tint/sem/builtin_type.cc
@@ -0,0 +1,560 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/builtin-gen
+// using the template:
+//   src/tint/sem/builtin_type.cc.tmpl
+// and the builtin defintion file:
+//   src/builtins.def
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/sem/builtin_type.h"
+
+#include <sstream>
+
+namespace tint {
+namespace sem {
+
+BuiltinType ParseBuiltinType(const std::string& name) {
+  if (name == "abs") {
+    return BuiltinType::kAbs;
+  }
+  if (name == "acos") {
+    return BuiltinType::kAcos;
+  }
+  if (name == "all") {
+    return BuiltinType::kAll;
+  }
+  if (name == "any") {
+    return BuiltinType::kAny;
+  }
+  if (name == "arrayLength") {
+    return BuiltinType::kArrayLength;
+  }
+  if (name == "asin") {
+    return BuiltinType::kAsin;
+  }
+  if (name == "atan") {
+    return BuiltinType::kAtan;
+  }
+  if (name == "atan2") {
+    return BuiltinType::kAtan2;
+  }
+  if (name == "ceil") {
+    return BuiltinType::kCeil;
+  }
+  if (name == "clamp") {
+    return BuiltinType::kClamp;
+  }
+  if (name == "cos") {
+    return BuiltinType::kCos;
+  }
+  if (name == "cosh") {
+    return BuiltinType::kCosh;
+  }
+  if (name == "countOneBits") {
+    return BuiltinType::kCountOneBits;
+  }
+  if (name == "cross") {
+    return BuiltinType::kCross;
+  }
+  if (name == "degrees") {
+    return BuiltinType::kDegrees;
+  }
+  if (name == "determinant") {
+    return BuiltinType::kDeterminant;
+  }
+  if (name == "distance") {
+    return BuiltinType::kDistance;
+  }
+  if (name == "dot") {
+    return BuiltinType::kDot;
+  }
+  if (name == "dpdx") {
+    return BuiltinType::kDpdx;
+  }
+  if (name == "dpdxCoarse") {
+    return BuiltinType::kDpdxCoarse;
+  }
+  if (name == "dpdxFine") {
+    return BuiltinType::kDpdxFine;
+  }
+  if (name == "dpdy") {
+    return BuiltinType::kDpdy;
+  }
+  if (name == "dpdyCoarse") {
+    return BuiltinType::kDpdyCoarse;
+  }
+  if (name == "dpdyFine") {
+    return BuiltinType::kDpdyFine;
+  }
+  if (name == "exp") {
+    return BuiltinType::kExp;
+  }
+  if (name == "exp2") {
+    return BuiltinType::kExp2;
+  }
+  if (name == "faceForward") {
+    return BuiltinType::kFaceForward;
+  }
+  if (name == "floor") {
+    return BuiltinType::kFloor;
+  }
+  if (name == "fma") {
+    return BuiltinType::kFma;
+  }
+  if (name == "fract") {
+    return BuiltinType::kFract;
+  }
+  if (name == "frexp") {
+    return BuiltinType::kFrexp;
+  }
+  if (name == "fwidth") {
+    return BuiltinType::kFwidth;
+  }
+  if (name == "fwidthCoarse") {
+    return BuiltinType::kFwidthCoarse;
+  }
+  if (name == "fwidthFine") {
+    return BuiltinType::kFwidthFine;
+  }
+  if (name == "inverseSqrt") {
+    return BuiltinType::kInverseSqrt;
+  }
+  if (name == "isFinite") {
+    return BuiltinType::kIsFinite;
+  }
+  if (name == "isInf") {
+    return BuiltinType::kIsInf;
+  }
+  if (name == "isNan") {
+    return BuiltinType::kIsNan;
+  }
+  if (name == "isNormal") {
+    return BuiltinType::kIsNormal;
+  }
+  if (name == "ldexp") {
+    return BuiltinType::kLdexp;
+  }
+  if (name == "length") {
+    return BuiltinType::kLength;
+  }
+  if (name == "log") {
+    return BuiltinType::kLog;
+  }
+  if (name == "log2") {
+    return BuiltinType::kLog2;
+  }
+  if (name == "max") {
+    return BuiltinType::kMax;
+  }
+  if (name == "min") {
+    return BuiltinType::kMin;
+  }
+  if (name == "mix") {
+    return BuiltinType::kMix;
+  }
+  if (name == "modf") {
+    return BuiltinType::kModf;
+  }
+  if (name == "normalize") {
+    return BuiltinType::kNormalize;
+  }
+  if (name == "pack2x16float") {
+    return BuiltinType::kPack2x16float;
+  }
+  if (name == "pack2x16snorm") {
+    return BuiltinType::kPack2x16snorm;
+  }
+  if (name == "pack2x16unorm") {
+    return BuiltinType::kPack2x16unorm;
+  }
+  if (name == "pack4x8snorm") {
+    return BuiltinType::kPack4x8snorm;
+  }
+  if (name == "pack4x8unorm") {
+    return BuiltinType::kPack4x8unorm;
+  }
+  if (name == "pow") {
+    return BuiltinType::kPow;
+  }
+  if (name == "radians") {
+    return BuiltinType::kRadians;
+  }
+  if (name == "reflect") {
+    return BuiltinType::kReflect;
+  }
+  if (name == "refract") {
+    return BuiltinType::kRefract;
+  }
+  if (name == "reverseBits") {
+    return BuiltinType::kReverseBits;
+  }
+  if (name == "round") {
+    return BuiltinType::kRound;
+  }
+  if (name == "select") {
+    return BuiltinType::kSelect;
+  }
+  if (name == "sign") {
+    return BuiltinType::kSign;
+  }
+  if (name == "sin") {
+    return BuiltinType::kSin;
+  }
+  if (name == "sinh") {
+    return BuiltinType::kSinh;
+  }
+  if (name == "smoothStep") {
+    return BuiltinType::kSmoothStep;
+  }
+  if (name == "sqrt") {
+    return BuiltinType::kSqrt;
+  }
+  if (name == "step") {
+    return BuiltinType::kStep;
+  }
+  if (name == "storageBarrier") {
+    return BuiltinType::kStorageBarrier;
+  }
+  if (name == "tan") {
+    return BuiltinType::kTan;
+  }
+  if (name == "tanh") {
+    return BuiltinType::kTanh;
+  }
+  if (name == "transpose") {
+    return BuiltinType::kTranspose;
+  }
+  if (name == "trunc") {
+    return BuiltinType::kTrunc;
+  }
+  if (name == "unpack2x16float") {
+    return BuiltinType::kUnpack2x16float;
+  }
+  if (name == "unpack2x16snorm") {
+    return BuiltinType::kUnpack2x16snorm;
+  }
+  if (name == "unpack2x16unorm") {
+    return BuiltinType::kUnpack2x16unorm;
+  }
+  if (name == "unpack4x8snorm") {
+    return BuiltinType::kUnpack4x8snorm;
+  }
+  if (name == "unpack4x8unorm") {
+    return BuiltinType::kUnpack4x8unorm;
+  }
+  if (name == "workgroupBarrier") {
+    return BuiltinType::kWorkgroupBarrier;
+  }
+  if (name == "textureDimensions") {
+    return BuiltinType::kTextureDimensions;
+  }
+  if (name == "textureGather") {
+    return BuiltinType::kTextureGather;
+  }
+  if (name == "textureGatherCompare") {
+    return BuiltinType::kTextureGatherCompare;
+  }
+  if (name == "textureNumLayers") {
+    return BuiltinType::kTextureNumLayers;
+  }
+  if (name == "textureNumLevels") {
+    return BuiltinType::kTextureNumLevels;
+  }
+  if (name == "textureNumSamples") {
+    return BuiltinType::kTextureNumSamples;
+  }
+  if (name == "textureSample") {
+    return BuiltinType::kTextureSample;
+  }
+  if (name == "textureSampleBias") {
+    return BuiltinType::kTextureSampleBias;
+  }
+  if (name == "textureSampleCompare") {
+    return BuiltinType::kTextureSampleCompare;
+  }
+  if (name == "textureSampleCompareLevel") {
+    return BuiltinType::kTextureSampleCompareLevel;
+  }
+  if (name == "textureSampleGrad") {
+    return BuiltinType::kTextureSampleGrad;
+  }
+  if (name == "textureSampleLevel") {
+    return BuiltinType::kTextureSampleLevel;
+  }
+  if (name == "textureStore") {
+    return BuiltinType::kTextureStore;
+  }
+  if (name == "textureLoad") {
+    return BuiltinType::kTextureLoad;
+  }
+  if (name == "atomicLoad") {
+    return BuiltinType::kAtomicLoad;
+  }
+  if (name == "atomicStore") {
+    return BuiltinType::kAtomicStore;
+  }
+  if (name == "atomicAdd") {
+    return BuiltinType::kAtomicAdd;
+  }
+  if (name == "atomicSub") {
+    return BuiltinType::kAtomicSub;
+  }
+  if (name == "atomicMax") {
+    return BuiltinType::kAtomicMax;
+  }
+  if (name == "atomicMin") {
+    return BuiltinType::kAtomicMin;
+  }
+  if (name == "atomicAnd") {
+    return BuiltinType::kAtomicAnd;
+  }
+  if (name == "atomicOr") {
+    return BuiltinType::kAtomicOr;
+  }
+  if (name == "atomicXor") {
+    return BuiltinType::kAtomicXor;
+  }
+  if (name == "atomicExchange") {
+    return BuiltinType::kAtomicExchange;
+  }
+  if (name == "atomicCompareExchangeWeak") {
+    return BuiltinType::kAtomicCompareExchangeWeak;
+  }
+  return BuiltinType::kNone;
+}
+
+const char* str(BuiltinType i) {
+  switch (i) {
+    case BuiltinType::kNone:
+      return "<none>";
+    case BuiltinType::kAbs:
+      return "abs";
+    case BuiltinType::kAcos:
+      return "acos";
+    case BuiltinType::kAll:
+      return "all";
+    case BuiltinType::kAny:
+      return "any";
+    case BuiltinType::kArrayLength:
+      return "arrayLength";
+    case BuiltinType::kAsin:
+      return "asin";
+    case BuiltinType::kAtan:
+      return "atan";
+    case BuiltinType::kAtan2:
+      return "atan2";
+    case BuiltinType::kCeil:
+      return "ceil";
+    case BuiltinType::kClamp:
+      return "clamp";
+    case BuiltinType::kCos:
+      return "cos";
+    case BuiltinType::kCosh:
+      return "cosh";
+    case BuiltinType::kCountOneBits:
+      return "countOneBits";
+    case BuiltinType::kCross:
+      return "cross";
+    case BuiltinType::kDegrees:
+      return "degrees";
+    case BuiltinType::kDeterminant:
+      return "determinant";
+    case BuiltinType::kDistance:
+      return "distance";
+    case BuiltinType::kDot:
+      return "dot";
+    case BuiltinType::kDpdx:
+      return "dpdx";
+    case BuiltinType::kDpdxCoarse:
+      return "dpdxCoarse";
+    case BuiltinType::kDpdxFine:
+      return "dpdxFine";
+    case BuiltinType::kDpdy:
+      return "dpdy";
+    case BuiltinType::kDpdyCoarse:
+      return "dpdyCoarse";
+    case BuiltinType::kDpdyFine:
+      return "dpdyFine";
+    case BuiltinType::kExp:
+      return "exp";
+    case BuiltinType::kExp2:
+      return "exp2";
+    case BuiltinType::kFaceForward:
+      return "faceForward";
+    case BuiltinType::kFloor:
+      return "floor";
+    case BuiltinType::kFma:
+      return "fma";
+    case BuiltinType::kFract:
+      return "fract";
+    case BuiltinType::kFrexp:
+      return "frexp";
+    case BuiltinType::kFwidth:
+      return "fwidth";
+    case BuiltinType::kFwidthCoarse:
+      return "fwidthCoarse";
+    case BuiltinType::kFwidthFine:
+      return "fwidthFine";
+    case BuiltinType::kInverseSqrt:
+      return "inverseSqrt";
+    case BuiltinType::kIsFinite:
+      return "isFinite";
+    case BuiltinType::kIsInf:
+      return "isInf";
+    case BuiltinType::kIsNan:
+      return "isNan";
+    case BuiltinType::kIsNormal:
+      return "isNormal";
+    case BuiltinType::kLdexp:
+      return "ldexp";
+    case BuiltinType::kLength:
+      return "length";
+    case BuiltinType::kLog:
+      return "log";
+    case BuiltinType::kLog2:
+      return "log2";
+    case BuiltinType::kMax:
+      return "max";
+    case BuiltinType::kMin:
+      return "min";
+    case BuiltinType::kMix:
+      return "mix";
+    case BuiltinType::kModf:
+      return "modf";
+    case BuiltinType::kNormalize:
+      return "normalize";
+    case BuiltinType::kPack2x16float:
+      return "pack2x16float";
+    case BuiltinType::kPack2x16snorm:
+      return "pack2x16snorm";
+    case BuiltinType::kPack2x16unorm:
+      return "pack2x16unorm";
+    case BuiltinType::kPack4x8snorm:
+      return "pack4x8snorm";
+    case BuiltinType::kPack4x8unorm:
+      return "pack4x8unorm";
+    case BuiltinType::kPow:
+      return "pow";
+    case BuiltinType::kRadians:
+      return "radians";
+    case BuiltinType::kReflect:
+      return "reflect";
+    case BuiltinType::kRefract:
+      return "refract";
+    case BuiltinType::kReverseBits:
+      return "reverseBits";
+    case BuiltinType::kRound:
+      return "round";
+    case BuiltinType::kSelect:
+      return "select";
+    case BuiltinType::kSign:
+      return "sign";
+    case BuiltinType::kSin:
+      return "sin";
+    case BuiltinType::kSinh:
+      return "sinh";
+    case BuiltinType::kSmoothStep:
+      return "smoothStep";
+    case BuiltinType::kSqrt:
+      return "sqrt";
+    case BuiltinType::kStep:
+      return "step";
+    case BuiltinType::kStorageBarrier:
+      return "storageBarrier";
+    case BuiltinType::kTan:
+      return "tan";
+    case BuiltinType::kTanh:
+      return "tanh";
+    case BuiltinType::kTranspose:
+      return "transpose";
+    case BuiltinType::kTrunc:
+      return "trunc";
+    case BuiltinType::kUnpack2x16float:
+      return "unpack2x16float";
+    case BuiltinType::kUnpack2x16snorm:
+      return "unpack2x16snorm";
+    case BuiltinType::kUnpack2x16unorm:
+      return "unpack2x16unorm";
+    case BuiltinType::kUnpack4x8snorm:
+      return "unpack4x8snorm";
+    case BuiltinType::kUnpack4x8unorm:
+      return "unpack4x8unorm";
+    case BuiltinType::kWorkgroupBarrier:
+      return "workgroupBarrier";
+    case BuiltinType::kTextureDimensions:
+      return "textureDimensions";
+    case BuiltinType::kTextureGather:
+      return "textureGather";
+    case BuiltinType::kTextureGatherCompare:
+      return "textureGatherCompare";
+    case BuiltinType::kTextureNumLayers:
+      return "textureNumLayers";
+    case BuiltinType::kTextureNumLevels:
+      return "textureNumLevels";
+    case BuiltinType::kTextureNumSamples:
+      return "textureNumSamples";
+    case BuiltinType::kTextureSample:
+      return "textureSample";
+    case BuiltinType::kTextureSampleBias:
+      return "textureSampleBias";
+    case BuiltinType::kTextureSampleCompare:
+      return "textureSampleCompare";
+    case BuiltinType::kTextureSampleCompareLevel:
+      return "textureSampleCompareLevel";
+    case BuiltinType::kTextureSampleGrad:
+      return "textureSampleGrad";
+    case BuiltinType::kTextureSampleLevel:
+      return "textureSampleLevel";
+    case BuiltinType::kTextureStore:
+      return "textureStore";
+    case BuiltinType::kTextureLoad:
+      return "textureLoad";
+    case BuiltinType::kAtomicLoad:
+      return "atomicLoad";
+    case BuiltinType::kAtomicStore:
+      return "atomicStore";
+    case BuiltinType::kAtomicAdd:
+      return "atomicAdd";
+    case BuiltinType::kAtomicSub:
+      return "atomicSub";
+    case BuiltinType::kAtomicMax:
+      return "atomicMax";
+    case BuiltinType::kAtomicMin:
+      return "atomicMin";
+    case BuiltinType::kAtomicAnd:
+      return "atomicAnd";
+    case BuiltinType::kAtomicOr:
+      return "atomicOr";
+    case BuiltinType::kAtomicXor:
+      return "atomicXor";
+    case BuiltinType::kAtomicExchange:
+      return "atomicExchange";
+    case BuiltinType::kAtomicCompareExchangeWeak:
+      return "atomicCompareExchangeWeak";
+  }
+  return "<unknown>";
+}
+
+std::ostream& operator<<(std::ostream& out, BuiltinType i) {
+  out << str(i);
+  return out;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/builtin_type.cc.tmpl b/src/tint/sem/builtin_type.cc.tmpl
new file mode 100644
index 0000000..0a44d0e
--- /dev/null
+++ b/src/tint/sem/builtin_type.cc.tmpl
@@ -0,0 +1,45 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/builtin-gen to generate builtin_type.cc
+
+See:
+* tools/cmd/builtin-gen/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#include "src/tint/sem/builtin_type.h"
+
+#include <sstream>
+
+namespace tint {
+namespace sem {
+
+BuiltinType ParseBuiltinType(const std::string& name) {
+{{- range .Sem.Functions  }}
+  if (name == "{{.Name}}") {
+    return BuiltinType::k{{Title .Name}};
+  }
+{{- end  }}
+  return BuiltinType::kNone;
+}
+
+const char* str(BuiltinType i) {
+  switch (i) {
+    case BuiltinType::kNone:
+      return "<none>";
+{{- range .Sem.Functions  }}
+    case BuiltinType::k{{Title .Name}}:
+      return "{{.Name}}";
+{{- end  }}
+  }
+  return "<unknown>";
+}
+
+std::ostream& operator<<(std::ostream& out, BuiltinType i) {
+  out << str(i);
+  return out;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/builtin_type.h b/src/tint/sem/builtin_type.h
new file mode 100644
index 0000000..e8fc934
--- /dev/null
+++ b/src/tint/sem/builtin_type.h
@@ -0,0 +1,158 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/builtin-gen
+// using the template:
+//   src/tint/sem/builtin_type.h.tmpl
+// and the builtin defintion file:
+//   src/builtins.def
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_SEM_BUILTIN_TYPE_H_
+#define SRC_TINT_SEM_BUILTIN_TYPE_H_
+
+#include <sstream>
+#include <string>
+
+namespace tint {
+namespace sem {
+
+/// Enumerator of all builtin functions
+enum class BuiltinType {
+  kNone = -1,
+  kAbs,
+  kAcos,
+  kAll,
+  kAny,
+  kArrayLength,
+  kAsin,
+  kAtan,
+  kAtan2,
+  kCeil,
+  kClamp,
+  kCos,
+  kCosh,
+  kCountOneBits,
+  kCross,
+  kDegrees,
+  kDeterminant,
+  kDistance,
+  kDot,
+  kDpdx,
+  kDpdxCoarse,
+  kDpdxFine,
+  kDpdy,
+  kDpdyCoarse,
+  kDpdyFine,
+  kExp,
+  kExp2,
+  kFaceForward,
+  kFloor,
+  kFma,
+  kFract,
+  kFrexp,
+  kFwidth,
+  kFwidthCoarse,
+  kFwidthFine,
+  kInverseSqrt,
+  kIsFinite,
+  kIsInf,
+  kIsNan,
+  kIsNormal,
+  kLdexp,
+  kLength,
+  kLog,
+  kLog2,
+  kMax,
+  kMin,
+  kMix,
+  kModf,
+  kNormalize,
+  kPack2x16float,
+  kPack2x16snorm,
+  kPack2x16unorm,
+  kPack4x8snorm,
+  kPack4x8unorm,
+  kPow,
+  kRadians,
+  kReflect,
+  kRefract,
+  kReverseBits,
+  kRound,
+  kSelect,
+  kSign,
+  kSin,
+  kSinh,
+  kSmoothStep,
+  kSqrt,
+  kStep,
+  kStorageBarrier,
+  kTan,
+  kTanh,
+  kTranspose,
+  kTrunc,
+  kUnpack2x16float,
+  kUnpack2x16snorm,
+  kUnpack2x16unorm,
+  kUnpack4x8snorm,
+  kUnpack4x8unorm,
+  kWorkgroupBarrier,
+  kTextureDimensions,
+  kTextureGather,
+  kTextureGatherCompare,
+  kTextureNumLayers,
+  kTextureNumLevels,
+  kTextureNumSamples,
+  kTextureSample,
+  kTextureSampleBias,
+  kTextureSampleCompare,
+  kTextureSampleCompareLevel,
+  kTextureSampleGrad,
+  kTextureSampleLevel,
+  kTextureStore,
+  kTextureLoad,
+  kAtomicLoad,
+  kAtomicStore,
+  kAtomicAdd,
+  kAtomicSub,
+  kAtomicMax,
+  kAtomicMin,
+  kAtomicAnd,
+  kAtomicOr,
+  kAtomicXor,
+  kAtomicExchange,
+  kAtomicCompareExchangeWeak,
+};
+
+/// Matches the BuiltinType by name
+/// @param name the builtin name to parse
+/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
+/// match any builtin.
+BuiltinType ParseBuiltinType(const std::string& name);
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(BuiltinType i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+std::ostream& operator<<(std::ostream& out, BuiltinType i);
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/sem/builtin_type.h.tmpl b/src/tint/sem/builtin_type.h.tmpl
new file mode 100644
index 0000000..135ed0d
--- /dev/null
+++ b/src/tint/sem/builtin_type.h.tmpl
@@ -0,0 +1,45 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/builtin-gen to generate builtin_type.h
+
+See:
+* tools/cmd/builtin-gen/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#ifndef SRC_TINT_SEM_BUILTIN_TYPE_H_
+#define SRC_TINT_SEM_BUILTIN_TYPE_H_
+
+#include <sstream>
+#include <string>
+
+namespace tint {
+namespace sem {
+
+/// Enumerator of all builtin functions
+enum class BuiltinType {
+  kNone = -1,
+{{- range .Sem.Functions }}
+  k{{Title .Name}},
+{{- end }}
+};
+
+/// Matches the BuiltinType by name
+/// @param name the builtin name to parse
+/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
+/// match any builtin.
+BuiltinType ParseBuiltinType(const std::string& name);
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(BuiltinType i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+std::ostream& operator<<(std::ostream& out, BuiltinType i);
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/sem/call.cc b/src/tint/sem/call.cc
new file mode 100644
index 0000000..682c802
--- /dev/null
+++ b/src/tint/sem/call.cc
@@ -0,0 +1,42 @@
+// 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/call.h"
+
+#include <utility>
+#include <vector>
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Call);
+
+namespace tint {
+namespace sem {
+
+Call::Call(const ast::CallExpression* declaration,
+           const CallTarget* target,
+           std::vector<const sem::Expression*> arguments,
+           const Statement* statement,
+           Constant constant,
+           bool has_side_effects)
+    : Base(declaration,
+           target->ReturnType(),
+           statement,
+           std::move(constant),
+           has_side_effects),
+      target_(target),
+      arguments_(std::move(arguments)) {}
+
+Call::~Call() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/call.h b/src/tint/sem/call.h
new file mode 100644
index 0000000..c967445
--- /dev/null
+++ b/src/tint/sem/call.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_CALL_H_
+#define SRC_TINT_SEM_CALL_H_
+
+#include <vector>
+
+#include "src/tint/sem/builtin.h"
+#include "src/tint/sem/expression.h"
+
+namespace tint {
+namespace sem {
+
+/// Call is the base class for semantic nodes that hold semantic information for
+/// ast::CallExpression nodes.
+class Call : public Castable<Call, Expression> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node
+  /// @param target the call target
+  /// @param arguments the call arguments
+  /// @param statement the statement that owns this expression
+  /// @param constant the constant value of this expression
+  /// @param has_side_effects whether this expression may have side effects
+  Call(const ast::CallExpression* declaration,
+       const CallTarget* target,
+       std::vector<const sem::Expression*> arguments,
+       const Statement* statement,
+       Constant constant,
+       bool has_side_effects);
+
+  /// Destructor
+  ~Call() override;
+
+  /// @return the target of the call
+  const CallTarget* Target() const { return target_; }
+
+  /// @return the call arguments
+  const std::vector<const sem::Expression*>& Arguments() const {
+    return arguments_;
+  }
+
+  /// @returns the AST node
+  const ast::CallExpression* Declaration() const {
+    return static_cast<const ast::CallExpression*>(declaration_);
+  }
+
+ private:
+  CallTarget const* const target_;
+  std::vector<const sem::Expression*> arguments_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_CALL_H_
diff --git a/src/tint/sem/call_target.cc b/src/tint/sem/call_target.cc
new file mode 100644
index 0000000..3e1264d
--- /dev/null
+++ b/src/tint/sem/call_target.cc
@@ -0,0 +1,78 @@
+// 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/call_target.h"
+
+#include "src/tint/symbol_table.h"
+#include "src/tint/utils/hash.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::CallTarget);
+
+namespace tint {
+namespace sem {
+
+CallTarget::CallTarget(const sem::Type* return_type,
+                       const ParameterList& parameters)
+    : signature_{return_type, parameters} {
+  TINT_ASSERT(Semantic, return_type);
+}
+
+CallTarget::CallTarget(const CallTarget&) = default;
+CallTarget::~CallTarget() = default;
+
+CallTargetSignature::CallTargetSignature(const sem::Type* ret_ty,
+                                         const ParameterList& params)
+    : return_type(ret_ty), parameters(params) {}
+CallTargetSignature::CallTargetSignature(const CallTargetSignature&) = default;
+CallTargetSignature::~CallTargetSignature() = default;
+
+int CallTargetSignature::IndexOf(ParameterUsage usage) const {
+  for (size_t i = 0; i < parameters.size(); i++) {
+    if (parameters[i]->Usage() == usage) {
+      return static_cast<int>(i);
+    }
+  }
+  return -1;
+}
+
+bool CallTargetSignature::operator==(const CallTargetSignature& other) const {
+  if (return_type != other.return_type ||
+      parameters.size() != other.parameters.size()) {
+    return false;
+  }
+  for (size_t i = 0; i < parameters.size(); i++) {
+    auto* a = parameters[i];
+    auto* b = other.parameters[i];
+    if (a->Type() != b->Type() || a->Usage() != b->Usage()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+std::size_t hash<tint::sem::CallTargetSignature>::operator()(
+    const tint::sem::CallTargetSignature& sig) const {
+  size_t hash = tint::utils::Hash(sig.parameters.size());
+  for (auto* p : sig.parameters) {
+    tint::utils::HashCombine(&hash, p->Type(), p->Usage());
+  }
+  return tint::utils::Hash(hash, sig.return_type);
+}
+
+}  // namespace std
diff --git a/src/tint/sem/call_target.h b/src/tint/sem/call_target.h
new file mode 100644
index 0000000..8faa099
--- /dev/null
+++ b/src/tint/sem/call_target.h
@@ -0,0 +1,105 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_CALL_TARGET_H_
+#define SRC_TINT_SEM_CALL_TARGET_H_
+
+#include <vector>
+
+#include "src/tint/sem/node.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+namespace sem {
+// Forward declarations
+class Type;
+
+/// CallTargetSignature holds the return type and parameters for a call target
+struct CallTargetSignature {
+  /// Constructor
+  /// @param ret_ty the call target return type
+  /// @param params the call target parameters
+  CallTargetSignature(const sem::Type* ret_ty, const ParameterList& params);
+
+  /// Copy constructor
+  CallTargetSignature(const CallTargetSignature&);
+
+  /// Destructor
+  ~CallTargetSignature();
+
+  /// The type of the call target return value
+  const sem::Type* const return_type = nullptr;
+  /// The parameters of the call target
+  const ParameterList parameters;
+
+  /// Equality operator
+  /// @param other the signature to compare this to
+  /// @returns true if this signature is equal to other
+  bool operator==(const CallTargetSignature& other) const;
+
+  /// @param usage the parameter usage to find
+  /// @returns the index of the parameter with the given usage, or -1 if no
+  /// parameter with the given usage exists.
+  int IndexOf(ParameterUsage usage) const;
+};
+
+/// CallTarget is the base for callable functions, builtins, type constructors
+/// and type casts.
+class CallTarget : public Castable<CallTarget, Node> {
+ public:
+  /// Constructor
+  /// @param return_type the return type of the call target
+  /// @param parameters the parameters for the call target
+  CallTarget(const sem::Type* return_type, const ParameterList& parameters);
+
+  /// Copy constructor
+  CallTarget(const CallTarget&);
+
+  /// Destructor
+  ~CallTarget() override;
+
+  /// @return the return type of the call target
+  const sem::Type* ReturnType() const { return signature_.return_type; }
+
+  /// @return the parameters of the call target
+  const ParameterList& Parameters() const { return signature_.parameters; }
+
+  /// @return the signature of the call target
+  const CallTargetSignature& Signature() const { return signature_; }
+
+ private:
+  CallTargetSignature signature_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::sem::CallTargetSignature so
+/// CallTargetSignature can be used as keys for std::unordered_map and
+/// std::unordered_set.
+template <>
+class hash<tint::sem::CallTargetSignature> {
+ public:
+  /// @param sig the CallTargetSignature to hash
+  /// @return the hash value
+  std::size_t operator()(const tint::sem::CallTargetSignature& sig) const;
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_CALL_TARGET_H_
diff --git a/src/tint/sem/constant.cc b/src/tint/sem/constant.cc
new file mode 100644
index 0000000..53fc3ba
--- /dev/null
+++ b/src/tint/sem/constant.cc
@@ -0,0 +1,84 @@
+// 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/constant.h"
+
+#include <functional>
+#include <utility>
+
+#include "src/tint/debug.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+namespace {
+
+const Type* ElemType(const Type* ty, size_t num_elements) {
+  diag::List diag;
+  if (ty->is_scalar()) {
+    if (num_elements != 1) {
+      TINT_ICE(Semantic, diag)
+          << "sem::Constant() type <-> num_element mismatch. type: '"
+          << ty->type_name() << "' num_elements: " << num_elements;
+    }
+    return ty;
+  }
+  if (auto* vec = ty->As<Vector>()) {
+    if (num_elements != vec->Width()) {
+      TINT_ICE(Semantic, diag)
+          << "sem::Constant() type <-> num_element mismatch. type: '"
+          << ty->type_name() << "' num_elements: " << num_elements;
+    }
+    TINT_ASSERT(Semantic, vec->type()->is_scalar());
+    return vec->type();
+  }
+  TINT_UNREACHABLE(Semantic, diag) << "Unsupported sem::Constant type";
+  return nullptr;
+}
+
+}  // namespace
+
+Constant::Constant() {}
+
+Constant::Constant(const sem::Type* ty, Scalars els)
+    : type_(ty), elem_type_(ElemType(ty, els.size())), elems_(std::move(els)) {}
+
+Constant::Constant(const Constant&) = default;
+
+Constant::~Constant() = default;
+
+Constant& Constant::operator=(const Constant& rhs) = default;
+
+bool Constant::AnyZero() const {
+  for (size_t i = 0; i < Elements().size(); ++i) {
+    if (WithScalarAt(i, [&](auto&& s) {
+          // Use std::equal_to to work around -Wfloat-equal warnings
+          auto equals_to =
+              std::equal_to<std::remove_reference_t<decltype(s)>>{};
+
+          if (equals_to(s, 0)) {
+            return true;
+          }
+          return false;
+        })) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/constant.h b/src/tint/sem/constant.h
new file mode 100644
index 0000000..48f3f31
--- /dev/null
+++ b/src/tint/sem/constant.h
@@ -0,0 +1,145 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_CONSTANT_H_
+#define SRC_TINT_SEM_CONSTANT_H_
+
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A Constant is compile-time known expression value, expressed as a flattened
+/// list of scalar values. Value may be of a scalar or vector type.
+class Constant {
+  using i32 = ProgramBuilder::i32;
+  using u32 = ProgramBuilder::u32;
+  using f32 = ProgramBuilder::f32;
+
+ public:
+  /// Scalar holds a single constant scalar value, as a union of an i32, u32,
+  /// f32 or boolean.
+  union Scalar {
+    /// The scalar value as a i32
+    int32_t i32;
+    /// The scalar value as a u32
+    uint32_t u32;
+    /// The scalar value as a f32
+    float f32;
+    /// The scalar value as a bool
+    bool bool_;
+
+    /// Constructs the scalar with the i32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::i32 v) : i32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the u32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::u32 v) : u32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the f32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::f32 v) : f32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the bool value `v`
+    /// @param v the value of the Scalar
+    Scalar(bool v) : bool_(v) {}  // NOLINT
+  };
+
+  /// Scalars is a list of scalar values
+  using Scalars = std::vector<Scalar>;
+
+  /// Constructs an invalid Constant
+  Constant();
+
+  /// Constructs a Constant of the given type and element values
+  /// @param ty the Constant type
+  /// @param els the Constant element values
+  Constant(const Type* ty, Scalars els);
+
+  /// Copy constructor
+  Constant(const Constant&);
+
+  /// Destructor
+  ~Constant();
+
+  /// Copy assignment
+  /// @param other the Constant to copy
+  /// @returns this Constant
+  Constant& operator=(const Constant& other);
+
+  /// @returns true if the Constant has been initialized
+  bool IsValid() const { return type_ != nullptr; }
+
+  /// @return true if the Constant has been initialized
+  operator bool() const { return IsValid(); }
+
+  /// @returns the type of the Constant
+  const sem::Type* Type() const { return type_; }
+
+  /// @returns the element type of the Constant
+  const sem::Type* ElementType() const { return elem_type_; }
+
+  /// @returns the constant's scalar elements
+  const Scalars& Elements() const { return elems_; }
+
+  /// @returns true if any scalar element is zero
+  bool AnyZero() const;
+
+  /// Calls `func(s)` with s being the current scalar value at `index`.
+  /// `func` is typically a lambda of the form '[](auto&& s)'.
+  /// @param index the index of the scalar value
+  /// @param func a function with signature `T(S)`
+  /// @return the value returned by func.
+  template <typename Func>
+  auto WithScalarAt(size_t index, Func&& func) const {
+    auto* elem_type = ElementType();
+    if (elem_type->Is<I32>()) {
+      return func(elems_[index].i32);
+    }
+    if (elem_type->Is<U32>()) {
+      return func(elems_[index].u32);
+    }
+    if (elem_type->Is<F32>()) {
+      return func(elems_[index].f32);
+    }
+    if (elem_type->Is<Bool>()) {
+      return func(elems_[index].bool_);
+    }
+    diag::List diags;
+    TINT_UNREACHABLE(Semantic, diags)
+        << "invalid scalar type " << type_->type_name();
+    return func(~0);
+  }
+
+  /// @param index the index of the scalar value
+  /// @return the value of the scalar `static_cast` to type T.
+  template <typename T>
+  T ElementAs(size_t index) const {
+    return WithScalarAt(index, [](auto val) { return static_cast<T>(val); });
+  }
+
+ private:
+  const sem::Type* type_ = nullptr;
+  const sem::Type* elem_type_ = nullptr;
+  Scalars elems_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_CONSTANT_H_
diff --git a/src/tint/sem/depth_multisampled_texture_type.cc b/src/tint/sem/depth_multisampled_texture_type.cc
new file mode 100644
index 0000000..3130c9a
--- /dev/null
+++ b/src/tint/sem/depth_multisampled_texture_type.cc
@@ -0,0 +1,54 @@
+// 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/depth_multisampled_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::DepthMultisampledTexture);
+
+namespace tint {
+namespace sem {
+namespace {
+
+bool IsValidDepthDimension(ast::TextureDimension dim) {
+  return dim == ast::TextureDimension::k2d;
+}
+
+}  // namespace
+
+DepthMultisampledTexture::DepthMultisampledTexture(ast::TextureDimension dim)
+    : Base(dim) {
+  TINT_ASSERT(Semantic, IsValidDepthDimension(dim));
+}
+
+DepthMultisampledTexture::DepthMultisampledTexture(DepthMultisampledTexture&&) =
+    default;
+
+DepthMultisampledTexture::~DepthMultisampledTexture() = default;
+
+std::string DepthMultisampledTexture::type_name() const {
+  std::ostringstream out;
+  out << "__depth_multisampled_texture_" << dim();
+  return out.str();
+}
+
+std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_depth_multisampled_" << dim();
+  return out.str();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/depth_multisampled_texture_type.h b/src/tint/sem/depth_multisampled_texture_type.h
new file mode 100644
index 0000000..08b1239
--- /dev/null
+++ b/src/tint/sem/depth_multisampled_texture_type.h
@@ -0,0 +1,48 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+/// A multisampled depth texture type.
+class DepthMultisampledTexture
+    : public Castable<DepthMultisampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  explicit DepthMultisampledTexture(ast::TextureDimension dim);
+  /// Move constructor
+  DepthMultisampledTexture(DepthMultisampledTexture&&);
+  ~DepthMultisampledTexture() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_DEPTH_MULTISAMPLED_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/depth_multisampled_texture_type_test.cc b/src/tint/sem/depth_multisampled_texture_type_test.cc
new file mode 100644
index 0000000..063594b
--- /dev/null
+++ b/src/tint/sem/depth_multisampled_texture_type_test.cc
@@ -0,0 +1,46 @@
+// 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/depth_multisampled_texture_type.h"
+
+#include "src/tint/sem/test_helper.h"
+
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using DepthMultisampledTextureTest = TestHelper;
+
+TEST_F(DepthMultisampledTextureTest, Dim) {
+  DepthMultisampledTexture d(ast::TextureDimension::k2d);
+  EXPECT_EQ(d.dim(), ast::TextureDimension::k2d);
+}
+
+TEST_F(DepthMultisampledTextureTest, TypeName) {
+  DepthMultisampledTexture d(ast::TextureDimension::k2d);
+  EXPECT_EQ(d.type_name(), "__depth_multisampled_texture_2d");
+}
+
+TEST_F(DepthMultisampledTextureTest, FriendlyName) {
+  DepthMultisampledTexture d(ast::TextureDimension::k2d);
+  EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_multisampled_2d");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/depth_texture_type.cc b/src/tint/sem/depth_texture_type.cc
new file mode 100644
index 0000000..751d042
--- /dev/null
+++ b/src/tint/sem/depth_texture_type.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 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/depth_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::DepthTexture);
+
+namespace tint {
+namespace sem {
+namespace {
+
+bool IsValidDepthDimension(ast::TextureDimension dim) {
+  return dim == ast::TextureDimension::k2d ||
+         dim == ast::TextureDimension::k2dArray ||
+         dim == ast::TextureDimension::kCube ||
+         dim == ast::TextureDimension::kCubeArray;
+}
+
+}  // namespace
+
+DepthTexture::DepthTexture(ast::TextureDimension dim) : Base(dim) {
+  TINT_ASSERT(Semantic, IsValidDepthDimension(dim));
+}
+
+DepthTexture::DepthTexture(DepthTexture&&) = default;
+
+DepthTexture::~DepthTexture() = default;
+
+std::string DepthTexture::type_name() const {
+  std::ostringstream out;
+  out << "__depth_texture_" << dim();
+  return out.str();
+}
+
+std::string DepthTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_depth_" << dim();
+  return out.str();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/depth_texture_type.h b/src/tint/sem/depth_texture_type.h
new file mode 100644
index 0000000..a8315ed
--- /dev/null
+++ b/src/tint/sem/depth_texture_type.h
@@ -0,0 +1,47 @@
+// Copyright 2020 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_DEPTH_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_DEPTH_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+/// A depth texture type.
+class DepthTexture : public Castable<DepthTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  explicit DepthTexture(ast::TextureDimension dim);
+  /// Move constructor
+  DepthTexture(DepthTexture&&);
+  ~DepthTexture() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_DEPTH_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/depth_texture_type_test.cc b/src/tint/sem/depth_texture_type_test.cc
new file mode 100644
index 0000000..797d854
--- /dev/null
+++ b/src/tint/sem/depth_texture_type_test.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 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/depth_texture_type.h"
+
+#include "src/tint/sem/test_helper.h"
+
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using DepthTextureTest = TestHelper;
+
+TEST_F(DepthTextureTest, IsTexture) {
+  DepthTexture d(ast::TextureDimension::kCube);
+  Texture* ty = &d;
+  EXPECT_TRUE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<ExternalTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(DepthTextureTest, Dim) {
+  DepthTexture d(ast::TextureDimension::kCube);
+  EXPECT_EQ(d.dim(), ast::TextureDimension::kCube);
+}
+
+TEST_F(DepthTextureTest, TypeName) {
+  DepthTexture d(ast::TextureDimension::kCube);
+  EXPECT_EQ(d.type_name(), "__depth_texture_cube");
+}
+
+TEST_F(DepthTextureTest, FriendlyName) {
+  DepthTexture d(ast::TextureDimension::kCube);
+  EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_cube");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/expression.cc b/src/tint/sem/expression.cc
new file mode 100644
index 0000000..774ac62
--- /dev/null
+++ b/src/tint/sem/expression.cc
@@ -0,0 +1,40 @@
+// 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/expression.h"
+
+#include <utility>
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
+
+namespace tint {
+namespace sem {
+
+Expression::Expression(const ast::Expression* declaration,
+                       const sem::Type* type,
+                       const Statement* statement,
+                       Constant constant,
+                       bool has_side_effects)
+    : declaration_(declaration),
+      type_(type),
+      statement_(statement),
+      constant_(std::move(constant)),
+      has_side_effects_(has_side_effects) {
+  TINT_ASSERT(Semantic, type_);
+}
+
+Expression::~Expression() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/expression.h b/src/tint/sem/expression.h
new file mode 100644
index 0000000..85640b1
--- /dev/null
+++ b/src/tint/sem/expression.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_EXPRESSION_H_
+#define SRC_TINT_SEM_EXPRESSION_H_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/sem/behavior.h"
+#include "src/tint/sem/constant.h"
+#include "src/tint/sem/node.h"
+
+namespace tint {
+namespace sem {
+// Forward declarations
+class Statement;
+class Type;
+
+/// Expression holds the semantic information for expression nodes.
+class Expression : public Castable<Expression, Node> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node
+  /// @param type the resolved type of the expression
+  /// @param statement the statement that owns this expression
+  /// @param constant the constant value of the expression. May be invalid
+  /// @param has_side_effects true if this expression may have side-effects
+  Expression(const ast::Expression* declaration,
+             const sem::Type* type,
+             const Statement* statement,
+             Constant constant,
+             bool has_side_effects);
+
+  /// Destructor
+  ~Expression() override;
+
+  /// @returns the AST node
+  const ast::Expression* Declaration() const { return declaration_; }
+
+  /// @return the resolved type of the expression
+  const sem::Type* Type() const { return type_; }
+
+  /// @return the statement that owns this expression
+  const Statement* Stmt() const { return statement_; }
+
+  /// @return the constant value of this expression
+  const Constant& ConstantValue() const { return constant_; }
+
+  /// @return the behaviors of this statement
+  const sem::Behaviors& Behaviors() const { return behaviors_; }
+
+  /// @return the behaviors of this statement
+  sem::Behaviors& Behaviors() { return behaviors_; }
+
+  /// @return true of this expression may have side effects
+  bool HasSideEffects() const { return has_side_effects_; }
+
+ protected:
+  /// The AST expression node for this semantic expression
+  const ast::Expression* const declaration_;
+
+ private:
+  const sem::Type* const type_;
+  const Statement* const statement_;
+  const Constant constant_;
+  sem::Behaviors behaviors_{sem::Behavior::kNext};
+  const bool has_side_effects_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_EXPRESSION_H_
diff --git a/src/tint/sem/external_texture_type.cc b/src/tint/sem/external_texture_type.cc
new file mode 100644
index 0000000..5e71d90
--- /dev/null
+++ b/src/tint/sem/external_texture_type.cc
@@ -0,0 +1,39 @@
+// 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/external_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ExternalTexture);
+
+namespace tint {
+namespace sem {
+
+ExternalTexture::ExternalTexture() : Base(ast::TextureDimension::k2d) {}
+
+ExternalTexture::ExternalTexture(ExternalTexture&&) = default;
+
+ExternalTexture::~ExternalTexture() = default;
+
+std::string ExternalTexture::type_name() const {
+  return "__external_texture";
+}
+
+std::string ExternalTexture::FriendlyName(const SymbolTable&) const {
+  return "texture_external";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/external_texture_type.h b/src/tint/sem/external_texture_type.h
new file mode 100644
index 0000000..6e383a8
--- /dev/null
+++ b/src/tint/sem/external_texture_type.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_EXTERNAL_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_EXTERNAL_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+/// An external texture type
+class ExternalTexture : public Castable<ExternalTexture, Texture> {
+ public:
+  /// Constructor
+  ExternalTexture();
+
+  /// Move constructor
+  ExternalTexture(ExternalTexture&&);
+  ~ExternalTexture() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_EXTERNAL_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/external_texture_type_test.cc b/src/tint/sem/external_texture_type_test.cc
new file mode 100644
index 0000000..0b5ffe5
--- /dev/null
+++ b/src/tint/sem/external_texture_type_test.cc
@@ -0,0 +1,59 @@
+// 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/external_texture_type.h"
+
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using ExternalTextureTest = TestHelper;
+
+TEST_F(ExternalTextureTest, IsTexture) {
+  F32 f32;
+  ExternalTexture s;
+  Texture* ty = &s;
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<ExternalTexture>());
+  EXPECT_FALSE(ty->Is<MultisampledTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(ExternalTextureTest, Dim) {
+  F32 f32;
+  ExternalTexture s;
+  EXPECT_EQ(s.dim(), ast::TextureDimension::k2d);
+}
+
+TEST_F(ExternalTextureTest, TypeName) {
+  F32 f32;
+  ExternalTexture s;
+  EXPECT_EQ(s.type_name(), "__external_texture");
+}
+
+TEST_F(ExternalTextureTest, FriendlyName) {
+  ExternalTexture s;
+  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_external");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/f32_type.cc b/src/tint/sem/f32_type.cc
new file mode 100644
index 0000000..7a071ee
--- /dev/null
+++ b/src/tint/sem/f32_type.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/f32_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::F32);
+
+namespace tint {
+namespace sem {
+
+F32::F32() = default;
+
+F32::F32(F32&&) = default;
+
+F32::~F32() = default;
+
+std::string F32::type_name() const {
+  return "__f32";
+}
+
+std::string F32::FriendlyName(const SymbolTable&) const {
+  return "f32";
+}
+
+bool F32::IsConstructible() const {
+  return true;
+}
+
+uint32_t F32::Size() const {
+  return 4;
+}
+
+uint32_t F32::Align() const {
+  return 4;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/f32_type.h b/src/tint/sem/f32_type.h
new file mode 100644
index 0000000..ddca10f
--- /dev/null
+++ b/src/tint/sem/f32_type.h
@@ -0,0 +1,56 @@
+// Copyright 2020 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_F32_TYPE_H_
+#define SRC_TINT_SEM_F32_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A float 32 type
+class F32 : public Castable<F32, Type> {
+ public:
+  /// Constructor
+  F32();
+  /// Move constructor
+  F32(F32&&);
+  ~F32() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the size in bytes of the type.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type.
+  uint32_t Align() const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_F32_TYPE_H_
diff --git a/src/tint/sem/f32_type_test.cc b/src/tint/sem/f32_type_test.cc
new file mode 100644
index 0000000..439f231
--- /dev/null
+++ b/src/tint/sem/f32_type_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using F32Test = TestHelper;
+
+TEST_F(F32Test, TypeName) {
+  F32 f;
+  EXPECT_EQ(f.type_name(), "__f32");
+}
+
+TEST_F(F32Test, FriendlyName) {
+  F32 f;
+  EXPECT_EQ(f.FriendlyName(Symbols()), "f32");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/for_loop_statement.cc b/src/tint/sem/for_loop_statement.cc
new file mode 100644
index 0000000..4321a14
--- /dev/null
+++ b/src/tint/sem/for_loop_statement.cc
@@ -0,0 +1,36 @@
+// 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/for_loop_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ForLoopStatement);
+
+namespace tint {
+namespace sem {
+
+ForLoopStatement::ForLoopStatement(const ast::ForLoopStatement* declaration,
+                                   const CompoundStatement* parent,
+                                   const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+ForLoopStatement::~ForLoopStatement() = default;
+
+const ast::ForLoopStatement* ForLoopStatement::Declaration() const {
+  return static_cast<const ast::ForLoopStatement*>(Base::Declaration());
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/for_loop_statement.h b/src/tint/sem/for_loop_statement.h
new file mode 100644
index 0000000..8681089
--- /dev/null
+++ b/src/tint/sem/for_loop_statement.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_FOR_LOOP_STATEMENT_H_
+#define SRC_TINT_SEM_FOR_LOOP_STATEMENT_H_
+
+#include "src/tint/sem/statement.h"
+
+namespace tint {
+namespace ast {
+class ForLoopStatement;
+}  // namespace ast
+namespace sem {
+class Expression;
+}  // namespace sem
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Holds semantic information about a for-loop statement
+class ForLoopStatement : public Castable<ForLoopStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this for-loop statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  ForLoopStatement(const ast::ForLoopStatement* declaration,
+                   const CompoundStatement* parent,
+                   const sem::Function* function);
+
+  /// Destructor
+  ~ForLoopStatement() override;
+
+  /// @returns the AST node
+  const ast::ForLoopStatement* Declaration() const;
+
+  /// @returns the for-loop condition expression
+  const Expression* Condition() const { return condition_; }
+
+  /// Sets the for-loop condition expression
+  /// @param condition the for-loop condition expression
+  void SetCondition(const Expression* condition) { condition_ = condition; }
+
+ private:
+  const Expression* condition_ = nullptr;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_FOR_LOOP_STATEMENT_H_
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
new file mode 100644
index 0000000..83ba6f8
--- /dev/null
+++ b/src/tint/sem/function.cc
@@ -0,0 +1,197 @@
+// 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/function.h"
+
+#include "src/tint/ast/function.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/to_const_ptr_vec.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Function);
+
+namespace tint {
+namespace sem {
+
+Function::Function(const ast::Function* declaration,
+                   Type* return_type,
+                   std::vector<Parameter*> parameters)
+    : Base(return_type, utils::ToConstPtrVec(parameters)),
+      declaration_(declaration),
+      workgroup_size_{WorkgroupDimension{1}, WorkgroupDimension{1},
+                      WorkgroupDimension{1}} {
+  for (auto* parameter : parameters) {
+    parameter->SetOwner(this);
+  }
+}  // namespace sem
+
+Function::~Function() = default;
+
+std::vector<std::pair<const Variable*, const ast::LocationAttribute*>>
+Function::TransitivelyReferencedLocationVariables() const {
+  std::vector<std::pair<const Variable*, const ast::LocationAttribute*>> ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    for (auto* attr : var->Declaration()->attributes) {
+      if (auto* location = attr->As<ast::LocationAttribute>()) {
+        ret.push_back({var, location});
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+Function::VariableBindings Function::TransitivelyReferencedUniformVariables()
+    const {
+  VariableBindings ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    if (var->StorageClass() != ast::StorageClass::kUniform) {
+      continue;
+    }
+
+    if (auto binding_point = var->Declaration()->BindingPoint()) {
+      ret.push_back({var, binding_point});
+    }
+  }
+  return ret;
+}
+
+Function::VariableBindings
+Function::TransitivelyReferencedStorageBufferVariables() const {
+  VariableBindings ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    if (var->StorageClass() != ast::StorageClass::kStorage) {
+      continue;
+    }
+
+    if (auto binding_point = var->Declaration()->BindingPoint()) {
+      ret.push_back({var, binding_point});
+    }
+  }
+  return ret;
+}
+
+std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>>
+Function::TransitivelyReferencedBuiltinVariables() const {
+  std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>> ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    for (auto* attr : var->Declaration()->attributes) {
+      if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
+        ret.push_back({var, builtin});
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+Function::VariableBindings Function::TransitivelyReferencedSamplerVariables()
+    const {
+  return TransitivelyReferencedSamplerVariablesImpl(ast::SamplerKind::kSampler);
+}
+
+Function::VariableBindings
+Function::TransitivelyReferencedComparisonSamplerVariables() const {
+  return TransitivelyReferencedSamplerVariablesImpl(
+      ast::SamplerKind::kComparisonSampler);
+}
+
+Function::VariableBindings
+Function::TransitivelyReferencedSampledTextureVariables() const {
+  return TransitivelyReferencedSampledTextureVariablesImpl(false);
+}
+
+Function::VariableBindings
+Function::TransitivelyReferencedMultisampledTextureVariables() const {
+  return TransitivelyReferencedSampledTextureVariablesImpl(true);
+}
+
+Function::VariableBindings Function::TransitivelyReferencedVariablesOfType(
+    const tint::TypeInfo* type) const {
+  VariableBindings ret;
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    auto* unwrapped_type = var->Type()->UnwrapRef();
+    if (unwrapped_type->TypeInfo().Is(type)) {
+      if (auto binding_point = var->Declaration()->BindingPoint()) {
+        ret.push_back({var, binding_point});
+      }
+    }
+  }
+  return ret;
+}
+
+bool Function::HasAncestorEntryPoint(Symbol symbol) const {
+  for (const auto* point : ancestor_entry_points_) {
+    if (point->Declaration()->symbol == symbol) {
+      return true;
+    }
+  }
+  return false;
+}
+
+Function::VariableBindings Function::TransitivelyReferencedSamplerVariablesImpl(
+    ast::SamplerKind kind) const {
+  VariableBindings ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    auto* unwrapped_type = var->Type()->UnwrapRef();
+    auto* sampler = unwrapped_type->As<sem::Sampler>();
+    if (sampler == nullptr || sampler->kind() != kind) {
+      continue;
+    }
+
+    if (auto binding_point = var->Declaration()->BindingPoint()) {
+      ret.push_back({var, binding_point});
+    }
+  }
+  return ret;
+}
+
+Function::VariableBindings
+Function::TransitivelyReferencedSampledTextureVariablesImpl(
+    bool multisampled) const {
+  VariableBindings ret;
+
+  for (auto* var : TransitivelyReferencedGlobals()) {
+    auto* unwrapped_type = var->Type()->UnwrapRef();
+    auto* texture = unwrapped_type->As<sem::Texture>();
+    if (texture == nullptr) {
+      continue;
+    }
+
+    auto is_multisampled = texture->Is<sem::MultisampledTexture>();
+    auto is_sampled = texture->Is<sem::SampledTexture>();
+
+    if ((multisampled && !is_multisampled) || (!multisampled && !is_sampled)) {
+      continue;
+    }
+
+    if (auto binding_point = var->Declaration()->BindingPoint()) {
+      ret.push_back({var, binding_point});
+    }
+  }
+
+  return ret;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
new file mode 100644
index 0000000..90657e7
--- /dev/null
+++ b/src/tint/sem/function.h
@@ -0,0 +1,289 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_FUNCTION_H_
+#define SRC_TINT_SEM_FUNCTION_H_
+
+#include <array>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/variable.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/utils/unique_vector.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class BuiltinAttribute;
+class Function;
+class LocationAttribute;
+class ReturnStatement;
+}  // namespace ast
+
+namespace sem {
+
+class Builtin;
+class Variable;
+
+/// WorkgroupDimension describes the size of a single dimension of an entry
+/// point's workgroup size.
+struct WorkgroupDimension {
+  /// The size of this dimension.
+  uint32_t value;
+  /// A pipeline-overridable constant that overrides the size, or nullptr if
+  /// this dimension is not overridable.
+  const ast::Variable* overridable_const = nullptr;
+};
+
+/// WorkgroupSize is a three-dimensional array of WorkgroupDimensions.
+using WorkgroupSize = std::array<WorkgroupDimension, 3>;
+
+/// Function holds the semantic information for function nodes.
+class Function : public Castable<Function, CallTarget> {
+ public:
+  /// A vector of [Variable*, ast::VariableBindingPoint] pairs
+  using VariableBindings =
+      std::vector<std::pair<const Variable*, ast::VariableBindingPoint>>;
+
+  /// Constructor
+  /// @param declaration the ast::Function
+  /// @param return_type the return type of the function
+  /// @param parameters the parameters to the function
+  Function(const ast::Function* declaration,
+           Type* return_type,
+           std::vector<Parameter*> parameters);
+
+  /// Destructor
+  ~Function() override;
+
+  /// @returns the ast::Function declaration
+  const ast::Function* Declaration() const { return declaration_; }
+
+  /// @returns the workgroup size {x, y, z} for the function.
+  const sem::WorkgroupSize& WorkgroupSize() const { return workgroup_size_; }
+
+  /// Sets the workgroup size {x, y, z} for the function.
+  /// @param workgroup_size the new workgroup size of the function
+  void SetWorkgroupSize(sem::WorkgroupSize workgroup_size) {
+    workgroup_size_ = std::move(workgroup_size);
+  }
+
+  /// @returns all directly referenced global variables
+  const utils::UniqueVector<const GlobalVariable*>& DirectlyReferencedGlobals()
+      const {
+    return directly_referenced_globals_;
+  }
+
+  /// Records that this function directly references the given global variable.
+  /// Note: Implicitly adds this global to the transtively-called globals.
+  /// @param global the module-scope variable
+  void AddDirectlyReferencedGlobal(const sem::GlobalVariable* global) {
+    directly_referenced_globals_.add(global);
+    transitively_referenced_globals_.add(global);
+  }
+
+  /// @returns all transitively referenced global variables
+  const utils::UniqueVector<const GlobalVariable*>&
+  TransitivelyReferencedGlobals() const {
+    return transitively_referenced_globals_;
+  }
+
+  /// Records that this function transitively references the given global
+  /// variable.
+  /// @param global the module-scoped variable
+  void AddTransitivelyReferencedGlobal(const sem::GlobalVariable* global) {
+    transitively_referenced_globals_.add(global);
+  }
+
+  /// @returns the list of functions that this function transitively calls.
+  const utils::UniqueVector<const Function*>& TransitivelyCalledFunctions()
+      const {
+    return transitively_called_functions_;
+  }
+
+  /// Records that this function transitively calls `function`.
+  /// @param function the function this function transitively calls
+  void AddTransitivelyCalledFunction(const Function* function) {
+    transitively_called_functions_.add(function);
+  }
+
+  /// @returns the list of builtins that this function directly calls.
+  const utils::UniqueVector<const Builtin*>& DirectlyCalledBuiltins() const {
+    return directly_called_builtins_;
+  }
+
+  /// Records that this function transitively calls `builtin`.
+  /// @param builtin the builtin this function directly calls
+  void AddDirectlyCalledBuiltin(const Builtin* builtin) {
+    directly_called_builtins_.add(builtin);
+  }
+
+  /// Adds the given texture/sampler pair to the list of unique pairs
+  /// that this function uses (directly or indirectly). These can only
+  /// be parameters to this function or global variables. Uniqueness is
+  /// ensured by texture_sampler_pairs_ being a UniqueVector.
+  /// @param texture the texture (must be non-null)
+  /// @param sampler the sampler (null indicates a texture-only reference)
+  void AddTextureSamplerPair(const sem::Variable* texture,
+                             const sem::Variable* sampler) {
+    texture_sampler_pairs_.add(VariablePair(texture, sampler));
+  }
+
+  /// @returns the list of texture/sampler pairs that this function uses
+  /// (directly or indirectly).
+  const std::vector<VariablePair>& TextureSamplerPairs() const {
+    return texture_sampler_pairs_;
+  }
+
+  /// @returns the list of direct calls to functions / builtins made by this
+  /// function
+  std::vector<const Call*> DirectCallStatements() const {
+    return direct_calls_;
+  }
+
+  /// Adds a record of the direct function / builtin calls made by this
+  /// function
+  /// @param call the call
+  void AddDirectCall(const Call* call) { direct_calls_.emplace_back(call); }
+
+  /// @param target the target of a call
+  /// @returns the Call to the given CallTarget, or nullptr the target was not
+  /// called by this function.
+  const Call* FindDirectCallTo(const CallTarget* target) const {
+    for (auto* call : direct_calls_) {
+      if (call->Target() == target) {
+        return call;
+      }
+    }
+    return nullptr;
+  }
+
+  /// @returns the list of callsites of this function
+  std::vector<const Call*> CallSites() const { return callsites_; }
+
+  /// Adds a record of a callsite to this function
+  /// @param call the callsite
+  void AddCallSite(const Call* call) { callsites_.emplace_back(call); }
+
+  /// @returns the ancestor entry points
+  const std::vector<const Function*>& AncestorEntryPoints() const {
+    return ancestor_entry_points_;
+  }
+
+  /// Adds a record that the given entry point transitively calls this function
+  /// @param entry_point the entry point that transtively calls this function
+  void AddAncestorEntryPoint(const sem::Function* entry_point) {
+    ancestor_entry_points_.emplace_back(entry_point);
+  }
+
+  /// Retrieves any referenced location variables
+  /// @returns the <variable, attribute> pair.
+  std::vector<std::pair<const Variable*, const ast::LocationAttribute*>>
+  TransitivelyReferencedLocationVariables() const;
+
+  /// Retrieves any referenced builtin variables
+  /// @returns the <variable, attribute> pair.
+  std::vector<std::pair<const Variable*, const ast::BuiltinAttribute*>>
+  TransitivelyReferencedBuiltinVariables() const;
+
+  /// Retrieves any referenced uniform variables. Note, the variables must be
+  /// decorated with both binding and group attributes.
+  /// @returns the referenced uniforms
+  VariableBindings TransitivelyReferencedUniformVariables() const;
+
+  /// Retrieves any referenced storagebuffer variables. Note, the variables
+  /// must be decorated with both binding and group attributes.
+  /// @returns the referenced storagebuffers
+  VariableBindings TransitivelyReferencedStorageBufferVariables() const;
+
+  /// Retrieves any referenced regular Sampler variables. Note, the
+  /// variables must be decorated with both binding and group attributes.
+  /// @returns the referenced storagebuffers
+  VariableBindings TransitivelyReferencedSamplerVariables() const;
+
+  /// Retrieves any referenced comparison Sampler variables. Note, the
+  /// variables must be decorated with both binding and group attributes.
+  /// @returns the referenced storagebuffers
+  VariableBindings TransitivelyReferencedComparisonSamplerVariables() const;
+
+  /// Retrieves any referenced sampled textures variables. Note, the
+  /// variables must be decorated with both binding and group attributes.
+  /// @returns the referenced sampled textures
+  VariableBindings TransitivelyReferencedSampledTextureVariables() const;
+
+  /// Retrieves any referenced multisampled textures variables. Note, the
+  /// variables must be decorated with both binding and group attributes.
+  /// @returns the referenced sampled textures
+  VariableBindings TransitivelyReferencedMultisampledTextureVariables() const;
+
+  /// Retrieves any referenced variables of the given type. Note, the variables
+  /// must be decorated with both binding and group attributes.
+  /// @param type the type of the variables to find
+  /// @returns the referenced variables
+  VariableBindings TransitivelyReferencedVariablesOfType(
+      const tint::TypeInfo* type) const;
+
+  /// Retrieves any referenced variables of the given type. Note, the variables
+  /// must be decorated with both binding and group attributes.
+  /// @returns the referenced variables
+  template <typename T>
+  VariableBindings TransitivelyReferencedVariablesOfType() const {
+    return TransitivelyReferencedVariablesOfType(&TypeInfo::Of<T>());
+  }
+
+  /// Checks if the given entry point is an ancestor
+  /// @param sym the entry point symbol
+  /// @returns true if `sym` is an ancestor entry point of this function
+  bool HasAncestorEntryPoint(Symbol sym) const;
+
+  /// Sets that this function has a discard statement
+  void SetHasDiscard() { has_discard_ = true; }
+
+  /// Returns true if this function has a discard statement
+  /// @returns true if this function has a discard statement
+  bool HasDiscard() const { return has_discard_; }
+
+  /// @return the behaviors of this function
+  const sem::Behaviors& Behaviors() const { return behaviors_; }
+
+  /// @return the behaviors of this function
+  sem::Behaviors& Behaviors() { return behaviors_; }
+
+ private:
+  VariableBindings TransitivelyReferencedSamplerVariablesImpl(
+      ast::SamplerKind kind) const;
+  VariableBindings TransitivelyReferencedSampledTextureVariablesImpl(
+      bool multisampled) const;
+
+  const ast::Function* const declaration_;
+
+  sem::WorkgroupSize workgroup_size_;
+  utils::UniqueVector<const GlobalVariable*> directly_referenced_globals_;
+  utils::UniqueVector<const GlobalVariable*> transitively_referenced_globals_;
+  utils::UniqueVector<const Function*> transitively_called_functions_;
+  utils::UniqueVector<const Builtin*> directly_called_builtins_;
+  utils::UniqueVector<VariablePair> texture_sampler_pairs_;
+  std::vector<const Call*> direct_calls_;
+  std::vector<const Call*> callsites_;
+  std::vector<const Function*> ancestor_entry_points_;
+  bool has_discard_ = false;
+  sem::Behaviors behaviors_{sem::Behavior::kNext};
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_FUNCTION_H_
diff --git a/src/tint/sem/i32_type.cc b/src/tint/sem/i32_type.cc
new file mode 100644
index 0000000..33e12a0
--- /dev/null
+++ b/src/tint/sem/i32_type.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/i32_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::I32);
+
+namespace tint {
+namespace sem {
+
+I32::I32() = default;
+
+I32::I32(I32&&) = default;
+
+I32::~I32() = default;
+
+std::string I32::type_name() const {
+  return "__i32";
+}
+
+std::string I32::FriendlyName(const SymbolTable&) const {
+  return "i32";
+}
+
+bool I32::IsConstructible() const {
+  return true;
+}
+
+uint32_t I32::Size() const {
+  return 4;
+}
+
+uint32_t I32::Align() const {
+  return 4;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/i32_type.h b/src/tint/sem/i32_type.h
new file mode 100644
index 0000000..2128573
--- /dev/null
+++ b/src/tint/sem/i32_type.h
@@ -0,0 +1,56 @@
+// Copyright 2020 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_I32_TYPE_H_
+#define SRC_TINT_SEM_I32_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A signed int 32 type.
+class I32 : public Castable<I32, Type> {
+ public:
+  /// Constructor
+  I32();
+  /// Move constructor
+  I32(I32&&);
+  ~I32() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the size in bytes of the type.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type.
+  uint32_t Align() const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_I32_TYPE_H_
diff --git a/src/tint/sem/i32_type_test.cc b/src/tint/sem/i32_type_test.cc
new file mode 100644
index 0000000..093bd7d
--- /dev/null
+++ b/src/tint/sem/i32_type_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using I32Test = TestHelper;
+
+TEST_F(I32Test, TypeName) {
+  I32 i;
+  EXPECT_EQ(i.type_name(), "__i32");
+}
+
+TEST_F(I32Test, FriendlyName) {
+  I32 i;
+  EXPECT_EQ(i.FriendlyName(Symbols()), "i32");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/if_statement.cc b/src/tint/sem/if_statement.cc
new file mode 100644
index 0000000..d5f8fb3
--- /dev/null
+++ b/src/tint/sem/if_statement.cc
@@ -0,0 +1,44 @@
+// 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/if_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::IfStatement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ElseStatement);
+
+namespace tint {
+namespace sem {
+
+IfStatement::IfStatement(const ast::IfStatement* declaration,
+                         const CompoundStatement* parent,
+                         const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+IfStatement::~IfStatement() = default;
+
+const ast::IfStatement* IfStatement::Declaration() const {
+  return static_cast<const ast::IfStatement*>(Base::Declaration());
+}
+
+ElseStatement::ElseStatement(const ast::ElseStatement* declaration,
+                             const IfStatement* parent,
+                             const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+ElseStatement::~ElseStatement() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/if_statement.h b/src/tint/sem/if_statement.h
new file mode 100644
index 0000000..eb6b6a4
--- /dev/null
+++ b/src/tint/sem/if_statement.h
@@ -0,0 +1,95 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_IF_STATEMENT_H_
+#define SRC_TINT_SEM_IF_STATEMENT_H_
+
+#include "src/tint/sem/statement.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class IfStatement;
+class ElseStatement;
+}  // namespace ast
+namespace sem {
+class Expression;
+}  // namespace sem
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Holds semantic information about an if statement
+class IfStatement : public Castable<IfStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this if statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  IfStatement(const ast::IfStatement* declaration,
+              const CompoundStatement* parent,
+              const sem::Function* function);
+
+  /// Destructor
+  ~IfStatement() override;
+
+  /// @returns the AST node
+  const ast::IfStatement* Declaration() const;
+
+  /// @returns the if-statement condition expression
+  const Expression* Condition() const { return condition_; }
+
+  /// Sets the if-statement condition expression
+  /// @param condition the if condition expression
+  void SetCondition(const Expression* condition) { condition_ = condition; }
+
+ private:
+  const Expression* condition_ = nullptr;
+};
+
+/// Holds semantic information about an else statement
+class ElseStatement : public Castable<ElseStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this else statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  ElseStatement(const ast::ElseStatement* declaration,
+                const IfStatement* parent,
+                const sem::Function* function);
+
+  /// Destructor
+  ~ElseStatement() override;
+
+  /// @returns the else-statement condition expression
+  const Expression* Condition() const { return condition_; }
+
+  /// @return the statement that encloses this statement
+  const IfStatement* Parent() const {
+    return static_cast<const IfStatement*>(Statement::Parent());
+  }
+
+  /// Sets the else-statement condition expression
+  /// @param condition the else condition expression
+  void SetCondition(const Expression* condition) { condition_ = condition; }
+
+ private:
+  const Expression* condition_ = nullptr;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_IF_STATEMENT_H_
diff --git a/src/tint/sem/info.cc b/src/tint/sem/info.cc
new file mode 100644
index 0000000..f839d8a
--- /dev/null
+++ b/src/tint/sem/info.cc
@@ -0,0 +1,29 @@
+// 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/info.h"
+
+namespace tint {
+namespace sem {
+
+Info::Info() = default;
+
+Info::Info(Info&&) = default;
+
+Info::~Info() = default;
+
+Info& Info::operator=(Info&&) = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/info.h b/src/tint/sem/info.h
new file mode 100644
index 0000000..ccc31bd
--- /dev/null
+++ b/src/tint/sem/info.h
@@ -0,0 +1,115 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_INFO_H_
+#define SRC_TINT_SEM_INFO_H_
+
+#include <type_traits>
+#include <unordered_map>
+
+#include "src/tint/debug.h"
+#include "src/tint/sem/node.h"
+#include "src/tint/sem/type_mappings.h"
+
+namespace tint::sem {
+
+// Forward declarations
+class Module;
+
+/// Info holds all the resolved semantic information for a Program.
+class Info {
+ public:
+  /// Placeholder type used by Get() to provide a default value for EXPLICIT_SEM
+  using InferFromAST = std::nullptr_t;
+
+  /// Resolves to the return type of the Get() method given the desired sementic
+  /// type and AST type.
+  template <typename SEM, typename AST_OR_TYPE>
+  using GetResultType =
+      std::conditional_t<std::is_same<SEM, InferFromAST>::value,
+                         SemanticNodeTypeFor<AST_OR_TYPE>,
+                         SEM>;
+
+  /// Constructor
+  Info();
+
+  /// Move constructor
+  Info(Info&&);
+
+  /// Destructor
+  ~Info();
+
+  /// Move assignment operator
+  /// @param rhs the Program to move
+  /// @return this Program
+  Info& operator=(Info&& rhs);
+
+  /// Get looks up the semantic information for the AST or type node `node`.
+  /// @param node the AST or type node
+  /// @returns a pointer to the semantic node if found, otherwise nullptr
+  template <typename SEM = InferFromAST,
+            typename AST_OR_TYPE = CastableBase,
+            typename RESULT = GetResultType<SEM, AST_OR_TYPE>>
+  const RESULT* Get(const AST_OR_TYPE* node) const {
+    auto it = map_.find(node);
+    if (it == map_.end()) {
+      return nullptr;
+    }
+    return As<RESULT>(it->second);
+  }
+
+  /// Add registers the semantic node `sem_node` for the AST or type node
+  /// `node`.
+  /// @param node the AST or type node
+  /// @param sem_node the semantic node
+  template <typename AST_OR_TYPE>
+  void Add(const AST_OR_TYPE* node,
+           const SemanticNodeTypeFor<AST_OR_TYPE>* sem_node) {
+    // Check there's no semantic info already existing for the node
+    TINT_ASSERT(Semantic, Get(node) == nullptr);
+    map_.emplace(node, sem_node);
+  }
+
+  /// Wrap returns a new Info created with the contents of `inner`.
+  /// The Info returned by Wrap is intended to temporarily extend the contents
+  /// of an existing immutable Info.
+  /// As the copied contents are owned by `inner`, `inner` must not be
+  /// destructed or assigned while using the returned Info.
+  /// @param inner the immutable Info to extend
+  /// @return the Info that wraps `inner`
+  static Info Wrap(const Info& inner) {
+    Info out;
+    out.map_ = inner.map_;
+    out.module_ = inner.module_;
+    return out;
+  }
+
+  /// Assigns the semantic module.
+  /// @param module the module to assign.
+  void SetModule(sem::Module* module) { module_ = module; }
+
+  /// @returns the semantic module.
+  const sem::Module* Module() const { return module_; }
+
+ private:
+  // TODO(crbug.com/tint/724): Once finished, this map should be:
+  // std::unordered_map<const ast::Node*, const sem::Node*>
+  std::unordered_map<const CastableBase*, const CastableBase*> map_;
+  // The semantic module
+  sem::Module* module_ = nullptr;
+};
+
+}  // namespace tint::sem
+
+#endif  // SRC_TINT_SEM_INFO_H_
diff --git a/src/tint/sem/loop_statement.cc b/src/tint/sem/loop_statement.cc
new file mode 100644
index 0000000..4be6e05
--- /dev/null
+++ b/src/tint/sem/loop_statement.cc
@@ -0,0 +1,46 @@
+// 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/loop_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopStatement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement);
+
+namespace tint {
+namespace sem {
+
+LoopStatement::LoopStatement(const ast::LoopStatement* declaration,
+                             const CompoundStatement* parent,
+                             const sem::Function* function)
+    : Base(declaration, parent, function) {
+  TINT_ASSERT(Semantic, parent);
+  TINT_ASSERT(Semantic, function);
+}
+
+LoopStatement::~LoopStatement() = default;
+
+LoopContinuingBlockStatement::LoopContinuingBlockStatement(
+    const ast::BlockStatement* declaration,
+    const CompoundStatement* parent,
+    const sem::Function* function)
+    : Base(declaration, parent, function) {
+  TINT_ASSERT(Semantic, parent);
+  TINT_ASSERT(Semantic, function);
+}
+LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/loop_statement.h b/src/tint/sem/loop_statement.h
new file mode 100644
index 0000000..7168c31
--- /dev/null
+++ b/src/tint/sem/loop_statement.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_LOOP_STATEMENT_H_
+#define SRC_TINT_SEM_LOOP_STATEMENT_H_
+
+#include "src/tint/sem/block_statement.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class LoopStatement;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Holds semantic information about a loop statement
+class LoopStatement : public Castable<LoopStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this loop statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  LoopStatement(const ast::LoopStatement* declaration,
+                const CompoundStatement* parent,
+                const sem::Function* function);
+
+  /// Destructor
+  ~LoopStatement() override;
+};
+
+/// Holds semantic information about a loop continuing block
+class LoopContinuingBlockStatement
+    : public Castable<LoopContinuingBlockStatement, BlockStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this block statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  LoopContinuingBlockStatement(const ast::BlockStatement* declaration,
+                               const CompoundStatement* parent,
+                               const sem::Function* function);
+
+  /// Destructor
+  ~LoopContinuingBlockStatement() override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_LOOP_STATEMENT_H_
diff --git a/src/tint/sem/matrix_type.cc b/src/tint/sem/matrix_type.cc
new file mode 100644
index 0000000..8be69c3
--- /dev/null
+++ b/src/tint/sem/matrix_type.cc
@@ -0,0 +1,69 @@
+// Copyright 2020 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/matrix_type.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/vector_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Matrix);
+
+namespace tint {
+namespace sem {
+
+Matrix::Matrix(const Vector* column_type, uint32_t columns)
+    : subtype_(column_type->type()),
+      column_type_(column_type),
+      rows_(column_type->Width()),
+      columns_(columns) {
+  TINT_ASSERT(AST, rows_ > 1);
+  TINT_ASSERT(AST, rows_ < 5);
+  TINT_ASSERT(AST, columns_ > 1);
+  TINT_ASSERT(AST, columns_ < 5);
+}
+
+Matrix::Matrix(Matrix&&) = default;
+
+Matrix::~Matrix() = default;
+
+std::string Matrix::type_name() const {
+  return "__mat_" + std::to_string(rows_) + "_" + std::to_string(columns_) +
+         subtype_->type_name();
+}
+
+std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "mat" << columns_ << "x" << rows_ << "<"
+      << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+bool Matrix::IsConstructible() const {
+  return true;
+}
+
+uint32_t Matrix::Size() const {
+  return column_type_->Align() * columns();
+}
+
+uint32_t Matrix::Align() const {
+  return column_type_->Align();
+}
+
+uint32_t Matrix::ColumnStride() const {
+  return column_type_->Align();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/matrix_type.h b/src/tint/sem/matrix_type.h
new file mode 100644
index 0000000..bc5eb2a
--- /dev/null
+++ b/src/tint/sem/matrix_type.h
@@ -0,0 +1,81 @@
+// Copyright 2020 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_MATRIX_TYPE_H_
+#define SRC_TINT_SEM_MATRIX_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+// Forward declaration
+class Vector;
+
+/// A matrix type
+class Matrix : public Castable<Matrix, Type> {
+ public:
+  /// Constructor
+  /// @param column_type the type of a column of the matrix
+  /// @param columns the number of columns in the matrix
+  Matrix(const Vector* column_type, uint32_t columns);
+  /// Move constructor
+  Matrix(Matrix&&);
+  ~Matrix() override;
+
+  /// @returns the type of the matrix
+  const Type* type() const { return subtype_; }
+  /// @returns the number of rows in the matrix
+  uint32_t rows() const { return rows_; }
+  /// @returns the number of columns in the matrix
+  uint32_t columns() const { return columns_; }
+
+  /// @returns the column-vector type of the matrix
+  const Vector* ColumnType() const { return column_type_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the size in bytes of the type. This may include tail padding.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type. This may include tail
+  /// padding.
+  uint32_t Align() const override;
+
+  /// @returns the number of bytes between columns of the matrix
+  uint32_t ColumnStride() const;
+
+ private:
+  const Type* const subtype_;
+  const Vector* const column_type_;
+  const uint32_t rows_;
+  const uint32_t columns_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_MATRIX_TYPE_H_
diff --git a/src/tint/sem/matrix_type_test.cc b/src/tint/sem/matrix_type_test.cc
new file mode 100644
index 0000000..cb93ea4
--- /dev/null
+++ b/src/tint/sem/matrix_type_test.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using MatrixTest = TestHelper;
+
+TEST_F(MatrixTest, Creation) {
+  I32 i32;
+  Vector c{&i32, 2};
+  Matrix m{&c, 4};
+  EXPECT_EQ(m.type(), &i32);
+  EXPECT_EQ(m.rows(), 2u);
+  EXPECT_EQ(m.columns(), 4u);
+}
+
+TEST_F(MatrixTest, TypeName) {
+  I32 i32;
+  Vector c{&i32, 2};
+  Matrix m{&c, 3};
+  EXPECT_EQ(m.type_name(), "__mat_2_3__i32");
+}
+
+TEST_F(MatrixTest, FriendlyName) {
+  I32 i32;
+  Vector c{&i32, 3};
+  Matrix m{&c, 2};
+  EXPECT_EQ(m.FriendlyName(Symbols()), "mat2x3<i32>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/member_accessor_expression.cc b/src/tint/sem/member_accessor_expression.cc
new file mode 100644
index 0000000..26f3842
--- /dev/null
+++ b/src/tint/sem/member_accessor_expression.cc
@@ -0,0 +1,56 @@
+// 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/ast/member_accessor_expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+
+#include <utility>
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::MemberAccessorExpression);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMemberAccess);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Swizzle);
+
+namespace tint {
+namespace sem {
+
+MemberAccessorExpression::MemberAccessorExpression(
+    const ast::MemberAccessorExpression* declaration,
+    const sem::Type* type,
+    const Statement* statement,
+    bool has_side_effects)
+    : Base(declaration, type, statement, Constant{}, has_side_effects) {}
+
+MemberAccessorExpression::~MemberAccessorExpression() = default;
+
+StructMemberAccess::StructMemberAccess(
+    const ast::MemberAccessorExpression* declaration,
+    const sem::Type* type,
+    const Statement* statement,
+    const StructMember* member,
+    bool has_side_effects)
+    : Base(declaration, type, statement, has_side_effects), member_(member) {}
+
+StructMemberAccess::~StructMemberAccess() = default;
+
+Swizzle::Swizzle(const ast::MemberAccessorExpression* declaration,
+                 const sem::Type* type,
+                 const Statement* statement,
+                 std::vector<uint32_t> indices)
+    : Base(declaration, type, statement, /* has_side_effects */ false),
+      indices_(std::move(indices)) {}
+
+Swizzle::~Swizzle() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/member_accessor_expression.h b/src/tint/sem/member_accessor_expression.h
new file mode 100644
index 0000000..7a1a2a3
--- /dev/null
+++ b/src/tint/sem/member_accessor_expression.h
@@ -0,0 +1,109 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
+#define SRC_TINT_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
+
+#include <vector>
+
+#include "src/tint/sem/expression.h"
+
+namespace tint {
+
+/// Forward declarations
+namespace ast {
+class MemberAccessorExpression;
+}  // namespace ast
+
+namespace sem {
+
+/// Forward declarations
+class Struct;
+class StructMember;
+
+/// MemberAccessorExpression holds the semantic information for a
+/// ast::MemberAccessorExpression node.
+class MemberAccessorExpression
+    : public Castable<MemberAccessorExpression, Expression> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node
+  /// @param type the resolved type of the expression
+  /// @param statement the statement that owns this expression
+  /// @param has_side_effects whether this expression may have side effects
+  MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
+                           const sem::Type* type,
+                           const Statement* statement,
+                           bool has_side_effects);
+
+  /// Destructor
+  ~MemberAccessorExpression() override;
+};
+
+/// StructMemberAccess holds the semantic information for a
+/// ast::MemberAccessorExpression node that represents an access to a structure
+/// member.
+class StructMemberAccess
+    : public Castable<StructMemberAccess, MemberAccessorExpression> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node
+  /// @param type the resolved type of the expression
+  /// @param statement the statement that owns this expression
+  /// @param member the structure member
+  /// @param has_side_effects whether this expression may have side effects
+  StructMemberAccess(const ast::MemberAccessorExpression* declaration,
+                     const sem::Type* type,
+                     const Statement* statement,
+                     const StructMember* member,
+                     bool has_side_effects);
+
+  /// Destructor
+  ~StructMemberAccess() override;
+
+  /// @returns the structure member
+  StructMember const* Member() const { return member_; }
+
+ private:
+  StructMember const* const member_;
+};
+
+/// Swizzle holds the semantic information for a ast::MemberAccessorExpression
+/// node that represents a vector swizzle.
+class Swizzle : public Castable<Swizzle, MemberAccessorExpression> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node
+  /// @param type the resolved type of the expression
+  /// @param statement the statement that owns this expression
+  /// @param indices the swizzle indices
+  Swizzle(const ast::MemberAccessorExpression* declaration,
+          const sem::Type* type,
+          const Statement* statement,
+          std::vector<uint32_t> indices);
+
+  /// Destructor
+  ~Swizzle() override;
+
+  /// @return the swizzle indices, if this is a vector swizzle
+  const std::vector<uint32_t>& Indices() const { return indices_; }
+
+ private:
+  std::vector<uint32_t> const indices_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
diff --git a/src/tint/sem/module.cc b/src/tint/sem/module.cc
new file mode 100644
index 0000000..83b7136
--- /dev/null
+++ b/src/tint/sem/module.cc
@@ -0,0 +1,29 @@
+// 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/sem/module.h"
+
+#include <utility>
+#include <vector>
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Module);
+
+namespace tint::sem {
+
+Module::Module(std::vector<const ast::Node*> dep_ordered_decls)
+    : dep_ordered_decls_(std::move(dep_ordered_decls)) {}
+
+Module::~Module() = default;
+
+}  // namespace tint::sem
diff --git a/src/tint/sem/module.h b/src/tint/sem/module.h
new file mode 100644
index 0000000..a21579e
--- /dev/null
+++ b/src/tint/sem/module.h
@@ -0,0 +1,52 @@
+// 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_MODULE_H_
+#define SRC_TINT_SEM_MODULE_H_
+
+#include <vector>
+
+#include "src/tint/sem/node.h"
+
+// Forward declarations
+namespace tint::ast {
+class Node;
+class Module;
+}  // namespace tint::ast
+
+namespace tint::sem {
+
+/// Module holds the top-level semantic types, functions and global variables
+/// used by a Program.
+class Module : public Castable<Module, Node> {
+ public:
+  /// Constructor
+  /// @param dep_ordered_decls the dependency-ordered module-scope declarations
+  explicit Module(std::vector<const ast::Node*> dep_ordered_decls);
+
+  /// Destructor
+  ~Module() override;
+
+  /// @returns the dependency-ordered global declarations for the module
+  const std::vector<const ast::Node*>& DependencyOrderedDeclarations() const {
+    return dep_ordered_decls_;
+  }
+
+ private:
+  const std::vector<const ast::Node*> dep_ordered_decls_;
+};
+
+}  // namespace tint::sem
+
+#endif  // SRC_TINT_SEM_MODULE_H_
diff --git a/src/tint/sem/multisampled_texture_type.cc b/src/tint/sem/multisampled_texture_type.cc
new file mode 100644
index 0000000..717e5ba
--- /dev/null
+++ b/src/tint/sem/multisampled_texture_type.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 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/multisampled_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::MultisampledTexture);
+
+namespace tint {
+namespace sem {
+
+MultisampledTexture::MultisampledTexture(ast::TextureDimension dim,
+                                         const Type* type)
+    : Base(dim), type_(type) {
+  TINT_ASSERT(Semantic, type_);
+}
+
+MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
+
+MultisampledTexture::~MultisampledTexture() = default;
+
+std::string MultisampledTexture::type_name() const {
+  std::ostringstream out;
+  out << "__multisampled_texture_" << dim() << type_->type_name();
+  return out.str();
+}
+
+std::string MultisampledTexture::FriendlyName(
+    const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_multisampled_" << dim() << "<" << type_->FriendlyName(symbols)
+      << ">";
+  return out.str();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/multisampled_texture_type.h b/src/tint/sem/multisampled_texture_type.h
new file mode 100644
index 0000000..cb0f74b
--- /dev/null
+++ b/src/tint/sem/multisampled_texture_type.h
@@ -0,0 +1,54 @@
+// Copyright 2020 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_MULTISAMPLED_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_MULTISAMPLED_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+/// A multisampled texture type.
+class MultisampledTexture : public Castable<MultisampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the multisampled texture
+  MultisampledTexture(ast::TextureDimension dim, const Type* type);
+  /// Move constructor
+  MultisampledTexture(MultisampledTexture&&);
+  ~MultisampledTexture() override;
+
+  /// @returns the subtype of the sampled texture
+  const Type* type() const { return type_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  const Type* const type_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_MULTISAMPLED_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/multisampled_texture_type_test.cc b/src/tint/sem/multisampled_texture_type_test.cc
new file mode 100644
index 0000000..29c088d
--- /dev/null
+++ b/src/tint/sem/multisampled_texture_type_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 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/multisampled_texture_type.h"
+
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using MultisampledTextureTest = TestHelper;
+
+TEST_F(MultisampledTextureTest, IsTexture) {
+  F32 f32;
+  MultisampledTexture s(ast::TextureDimension::kCube, &f32);
+  Texture* ty = &s;
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<ExternalTexture>());
+  EXPECT_TRUE(ty->Is<MultisampledTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(MultisampledTextureTest, Dim) {
+  F32 f32;
+  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.dim(), ast::TextureDimension::k3d);
+}
+
+TEST_F(MultisampledTextureTest, Type) {
+  F32 f32;
+  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type(), &f32);
+}
+
+TEST_F(MultisampledTextureTest, TypeName) {
+  F32 f32;
+  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type_name(), "__multisampled_texture_3d__f32");
+}
+
+TEST_F(MultisampledTextureTest, FriendlyName) {
+  F32 f32;
+  MultisampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/node.cc b/src/tint/sem/node.cc
new file mode 100644
index 0000000..3a61890
--- /dev/null
+++ b/src/tint/sem/node.cc
@@ -0,0 +1,29 @@
+// 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/node.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Node);
+
+namespace tint {
+namespace sem {
+
+Node::Node() = default;
+
+Node::Node(const Node&) = default;
+
+Node::~Node() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/node.h b/src/tint/sem/node.h
new file mode 100644
index 0000000..ecf630d
--- /dev/null
+++ b/src/tint/sem/node.h
@@ -0,0 +1,39 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_NODE_H_
+#define SRC_TINT_SEM_NODE_H_
+
+#include "src/tint/castable.h"
+
+namespace tint {
+namespace sem {
+
+/// Node is the base class for all semantic nodes
+class Node : public Castable<Node> {
+ public:
+  /// Constructor
+  Node();
+
+  /// Copy constructor
+  Node(const Node&);
+
+  /// Destructor
+  ~Node() override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_NODE_H_
diff --git a/src/tint/sem/parameter_usage.cc b/src/tint/sem/parameter_usage.cc
new file mode 100644
index 0000000..c703f64
--- /dev/null
+++ b/src/tint/sem/parameter_usage.cc
@@ -0,0 +1,65 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/builtin-gen
+// using the template:
+//   src/tint/sem/parameter_usage.cc.tmpl
+// and the builtin defintion file:
+//   src/builtins.def
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/sem/parameter_usage.h"
+
+namespace tint {
+namespace sem {
+
+const char* str(ParameterUsage usage) {
+  switch (usage) {
+    case ParameterUsage::kNone:
+      return "none";
+    case ParameterUsage::kArrayIndex:
+      return "array_index";
+    case ParameterUsage::kBias:
+      return "bias";
+    case ParameterUsage::kComponent:
+      return "component";
+    case ParameterUsage::kCoords:
+      return "coords";
+    case ParameterUsage::kDdx:
+      return "ddx";
+    case ParameterUsage::kDdy:
+      return "ddy";
+    case ParameterUsage::kDepthRef:
+      return "depth_ref";
+    case ParameterUsage::kLevel:
+      return "level";
+    case ParameterUsage::kOffset:
+      return "offset";
+    case ParameterUsage::kSampleIndex:
+      return "sample_index";
+    case ParameterUsage::kSampler:
+      return "sampler";
+    case ParameterUsage::kTexture:
+      return "texture";
+    case ParameterUsage::kValue:
+      return "value";
+  }
+  return "<unknown>";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/parameter_usage.cc.tmpl b/src/tint/sem/parameter_usage.cc.tmpl
new file mode 100644
index 0000000..2a9a060
--- /dev/null
+++ b/src/tint/sem/parameter_usage.cc.tmpl
@@ -0,0 +1,29 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/builtin-gen to generate parameter_usage.cc
+
+See:
+* tools/cmd/builtin-gen/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#include "src/tint/sem/parameter_usage.h"
+
+namespace tint {
+namespace sem {
+
+const char* str(ParameterUsage usage) {
+  switch (usage) {
+    case ParameterUsage::kNone:
+      return "none";
+{{- range .Sem.UniqueParameterNames  }}
+    case ParameterUsage::k{{PascalCase .}}:
+      return "{{.}}";
+{{- end  }}
+  }
+  return "<unknown>";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/parameter_usage.h b/src/tint/sem/parameter_usage.h
new file mode 100644
index 0000000..7175714
--- /dev/null
+++ b/src/tint/sem/parameter_usage.h
@@ -0,0 +1,56 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/builtin-gen
+// using the template:
+//   src/tint/sem/parameter_usage.h.tmpl
+// and the builtin defintion file:
+//   src/builtins.def
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_SEM_PARAMETER_USAGE_H_
+#define SRC_TINT_SEM_PARAMETER_USAGE_H_
+
+namespace tint {
+namespace sem {
+
+/// ParameterUsage is extra metadata for identifying a parameter based on its
+/// overload position
+enum class ParameterUsage {
+  kNone = -1,
+  kArrayIndex,
+  kBias,
+  kComponent,
+  kCoords,
+  kDdx,
+  kDdy,
+  kDepthRef,
+  kLevel,
+  kOffset,
+  kSampleIndex,
+  kSampler,
+  kTexture,
+  kValue,
+};
+
+/// @returns a string representation of the given parameter usage.
+const char* str(ParameterUsage usage);
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_PARAMETER_USAGE_H_
diff --git a/src/tint/sem/parameter_usage.h.tmpl b/src/tint/sem/parameter_usage.h.tmpl
new file mode 100644
index 0000000..00a2f99
--- /dev/null
+++ b/src/tint/sem/parameter_usage.h.tmpl
@@ -0,0 +1,32 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/builtin-gen to generate parameter_usage.h
+
+See:
+* tools/cmd/builtin-gen/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#ifndef SRC_TINT_SEM_PARAMETER_USAGE_H_
+#define SRC_TINT_SEM_PARAMETER_USAGE_H_
+
+namespace tint {
+namespace sem {
+
+/// ParameterUsage is extra metadata for identifying a parameter based on its
+/// overload position
+enum class ParameterUsage {
+  kNone = -1,
+{{- range .Sem.UniqueParameterNames  }}
+  k{{PascalCase .}},
+{{- end  }}
+};
+
+/// @returns a string representation of the given parameter usage.
+const char* str(ParameterUsage usage);
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_PARAMETER_USAGE_H_
diff --git a/src/tint/sem/pipeline_stage_set.h b/src/tint/sem/pipeline_stage_set.h
new file mode 100644
index 0000000..8b96da9
--- /dev/null
+++ b/src/tint/sem/pipeline_stage_set.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_PIPELINE_STAGE_SET_H_
+#define SRC_TINT_SEM_PIPELINE_STAGE_SET_H_
+
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/utils/enum_set.h"
+
+namespace tint {
+namespace sem {
+
+using PipelineStageSet = utils::EnumSet<ast::PipelineStage>;
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_PIPELINE_STAGE_SET_H_
diff --git a/src/tint/sem/pointer_type.cc b/src/tint/sem/pointer_type.cc
new file mode 100644
index 0000000..a548742
--- /dev/null
+++ b/src/tint/sem/pointer_type.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 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/pointer_type.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/reference_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Pointer);
+
+namespace tint {
+namespace sem {
+
+Pointer::Pointer(const Type* subtype,
+                 ast::StorageClass storage_class,
+                 ast::Access access)
+    : subtype_(subtype), storage_class_(storage_class), access_(access) {
+  TINT_ASSERT(Semantic, !subtype->Is<Reference>());
+  TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
+}
+
+std::string Pointer::type_name() const {
+  std::ostringstream out;
+  out << "__ptr_" << storage_class_ << subtype_->type_name() << "__" << access_;
+  return out.str();
+}
+
+std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "ptr<";
+  if (storage_class_ != ast::StorageClass::kNone) {
+    out << storage_class_ << ", ";
+  }
+  out << subtype_->FriendlyName(symbols) << ", " << access_;
+  out << ">";
+  return out.str();
+}
+
+Pointer::Pointer(Pointer&&) = default;
+
+Pointer::~Pointer() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/pointer_type.h b/src/tint/sem/pointer_type.h
new file mode 100644
index 0000000..7bc8001
--- /dev/null
+++ b/src/tint/sem/pointer_type.h
@@ -0,0 +1,68 @@
+// Copyright 2020 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_POINTER_TYPE_H_
+#define SRC_TINT_SEM_POINTER_TYPE_H_
+
+#include <string>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A pointer type.
+class Pointer : public Castable<Pointer, Type> {
+ public:
+  /// Constructor
+  /// @param subtype the pointee type
+  /// @param storage_class the storage class of the pointer
+  /// @param access the resolved access control of the reference
+  Pointer(const Type* subtype,
+          ast::StorageClass storage_class,
+          ast::Access access);
+
+  /// Move constructor
+  Pointer(Pointer&&);
+  ~Pointer() override;
+
+  /// @returns the pointee type
+  const Type* StoreType() const { return subtype_; }
+
+  /// @returns the storage class of the pointer
+  ast::StorageClass StorageClass() const { return storage_class_; }
+
+  /// @returns the access control of the reference
+  ast::Access Access() const { return access_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  Type const* const subtype_;
+  ast::StorageClass const storage_class_;
+  ast::Access const access_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_POINTER_TYPE_H_
diff --git a/src/tint/sem/pointer_type_test.cc b/src/tint/sem/pointer_type_test.cc
new file mode 100644
index 0000000..8f6d1d0
--- /dev/null
+++ b/src/tint/sem/pointer_type_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using PointerTest = TestHelper;
+
+TEST_F(PointerTest, Creation) {
+  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kStorage,
+                            ast::Access::kReadWrite);
+  EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
+  EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
+  EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
+}
+
+TEST_F(PointerTest, TypeName) {
+  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
+                            ast::Access::kReadWrite);
+  EXPECT_EQ(r->type_name(), "__ptr_workgroup__i32__read_write");
+}
+
+TEST_F(PointerTest, FriendlyName) {
+  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kNone,
+                            ast::Access::kRead);
+  EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<i32, read>");
+}
+
+TEST_F(PointerTest, FriendlyNameWithStorageClass) {
+  auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
+                            ast::Access::kRead);
+  EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<workgroup, i32, read>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/reference_type.cc b/src/tint/sem/reference_type.cc
new file mode 100644
index 0000000..daa0984
--- /dev/null
+++ b/src/tint/sem/reference_type.cc
@@ -0,0 +1,54 @@
+// 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/reference_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Reference);
+
+namespace tint {
+namespace sem {
+
+Reference::Reference(const Type* subtype,
+                     ast::StorageClass storage_class,
+                     ast::Access access)
+    : subtype_(subtype), storage_class_(storage_class), access_(access) {
+  TINT_ASSERT(Semantic, !subtype->Is<Reference>());
+  TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
+}
+
+std::string Reference::type_name() const {
+  std::ostringstream out;
+  out << "__ref_" << storage_class_ << subtype_->type_name() << "__" << access_;
+  return out.str();
+}
+
+std::string Reference::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "ref<";
+  if (storage_class_ != ast::StorageClass::kNone) {
+    out << storage_class_ << ", ";
+  }
+  out << subtype_->FriendlyName(symbols) << ", " << access_;
+  out << ">";
+  return out.str();
+}
+
+Reference::Reference(Reference&&) = default;
+
+Reference::~Reference() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/reference_type.h b/src/tint/sem/reference_type.h
new file mode 100644
index 0000000..67bc145
--- /dev/null
+++ b/src/tint/sem/reference_type.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_REFERENCE_TYPE_H_
+#define SRC_TINT_SEM_REFERENCE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A reference type.
+class Reference : public Castable<Reference, Type> {
+ public:
+  /// Constructor
+  /// @param subtype the pointee type
+  /// @param storage_class the storage class of the reference
+  /// @param access the resolved access control of the reference
+  Reference(const Type* subtype,
+            ast::StorageClass storage_class,
+            ast::Access access);
+
+  /// Move constructor
+  Reference(Reference&&);
+  ~Reference() override;
+
+  /// @returns the pointee type
+  const Type* StoreType() const { return subtype_; }
+
+  /// @returns the storage class of the reference
+  ast::StorageClass StorageClass() const { return storage_class_; }
+
+  /// @returns the resolved access control of the reference.
+  ast::Access Access() const { return access_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  Type const* const subtype_;
+  ast::StorageClass const storage_class_;
+  ast::Access const access_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_REFERENCE_TYPE_H_
diff --git a/src/tint/sem/reference_type_test.cc b/src/tint/sem/reference_type_test.cc
new file mode 100644
index 0000000..4b2ce48
--- /dev/null
+++ b/src/tint/sem/reference_type_test.cc
@@ -0,0 +1,52 @@
+// 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/reference_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using ReferenceTest = TestHelper;
+
+TEST_F(ReferenceTest, Creation) {
+  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kStorage,
+                              ast::Access::kReadWrite);
+  EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
+  EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
+  EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
+}
+
+TEST_F(ReferenceTest, TypeName) {
+  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
+                              ast::Access::kReadWrite);
+  EXPECT_EQ(r->type_name(), "__ref_workgroup__i32__read_write");
+}
+
+TEST_F(ReferenceTest, FriendlyName) {
+  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kNone,
+                              ast::Access::kRead);
+  EXPECT_EQ(r->FriendlyName(Symbols()), "ref<i32, read>");
+}
+
+TEST_F(ReferenceTest, FriendlyNameWithStorageClass) {
+  auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
+                              ast::Access::kRead);
+  EXPECT_EQ(r->FriendlyName(Symbols()), "ref<workgroup, i32, read>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sampled_texture_type.cc b/src/tint/sem/sampled_texture_type.cc
new file mode 100644
index 0000000..d14742e
--- /dev/null
+++ b/src/tint/sem/sampled_texture_type.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 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/sampled_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::SampledTexture);
+
+namespace tint {
+namespace sem {
+
+SampledTexture::SampledTexture(ast::TextureDimension dim, const Type* type)
+    : Base(dim), type_(type) {
+  TINT_ASSERT(Semantic, type_);
+}
+
+SampledTexture::SampledTexture(SampledTexture&&) = default;
+
+SampledTexture::~SampledTexture() = default;
+
+std::string SampledTexture::type_name() const {
+  std::ostringstream out;
+  out << "__sampled_texture_" << dim() << type_->type_name();
+  return out.str();
+}
+
+std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_" << dim() << "<" << type_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sampled_texture_type.h b/src/tint/sem/sampled_texture_type.h
new file mode 100644
index 0000000..34f3c6d
--- /dev/null
+++ b/src/tint/sem/sampled_texture_type.h
@@ -0,0 +1,54 @@
+// Copyright 2020 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_SAMPLED_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_SAMPLED_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+/// A sampled texture type.
+class SampledTexture : public Castable<SampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the sampled texture
+  SampledTexture(ast::TextureDimension dim, const Type* type);
+  /// Move constructor
+  SampledTexture(SampledTexture&&);
+  ~SampledTexture() override;
+
+  /// @returns the subtype of the sampled texture
+  Type* type() const { return const_cast<Type*>(type_); }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  const Type* const type_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_SAMPLED_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/sampled_texture_type_test.cc b/src/tint/sem/sampled_texture_type_test.cc
new file mode 100644
index 0000000..f21b478
--- /dev/null
+++ b/src/tint/sem/sampled_texture_type_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 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/sampled_texture_type.h"
+
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using SampledTextureTest = TestHelper;
+
+TEST_F(SampledTextureTest, IsTexture) {
+  F32 f32;
+  SampledTexture s(ast::TextureDimension::kCube, &f32);
+  Texture* ty = &s;
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<ExternalTexture>());
+  EXPECT_TRUE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(SampledTextureTest, Dim) {
+  F32 f32;
+  SampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.dim(), ast::TextureDimension::k3d);
+}
+
+TEST_F(SampledTextureTest, Type) {
+  F32 f32;
+  SampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type(), &f32);
+}
+
+TEST_F(SampledTextureTest, TypeName) {
+  F32 f32;
+  SampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type_name(), "__sampled_texture_3d__f32");
+}
+
+TEST_F(SampledTextureTest, FriendlyName) {
+  F32 f32;
+  SampledTexture s(ast::TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_3d<f32>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sampler_texture_pair.h b/src/tint/sem/sampler_texture_pair.h
new file mode 100644
index 0000000..4773dcf
--- /dev/null
+++ b/src/tint/sem/sampler_texture_pair.h
@@ -0,0 +1,71 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_SAMPLER_TEXTURE_PAIR_H_
+#define SRC_TINT_SEM_SAMPLER_TEXTURE_PAIR_H_
+
+#include <cstdint>
+#include <functional>
+
+#include "src/tint/sem/binding_point.h"
+
+namespace tint {
+namespace sem {
+
+/// Mapping of a sampler to a texture it samples.
+struct SamplerTexturePair {
+  /// group & binding values for a sampler.
+  BindingPoint sampler_binding_point;
+  /// group & binding values for a texture samepled by the sampler.
+  BindingPoint texture_binding_point;
+
+  /// Equality operator
+  /// @param rhs the SamplerTexturePair to compare against
+  /// @returns true if this SamplerTexturePair is equal to `rhs`
+  inline bool operator==(const SamplerTexturePair& rhs) const {
+    return sampler_binding_point == rhs.sampler_binding_point &&
+           texture_binding_point == rhs.texture_binding_point;
+  }
+
+  /// Inequality operator
+  /// @param rhs the SamplerTexturePair to compare against
+  /// @returns true if this SamplerTexturePair is not equal to `rhs`
+  inline bool operator!=(const SamplerTexturePair& rhs) const {
+    return !(*this == rhs);
+  }
+};
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::sem::SamplerTexturePair so
+/// SamplerTexturePairs be used as keys for std::unordered_map and
+/// std::unordered_set.
+template <>
+class hash<tint::sem::SamplerTexturePair> {
+ public:
+  /// @param stp the texture pair to create a hash for
+  /// @return the hash value
+  inline std::size_t operator()(
+      const tint::sem::SamplerTexturePair& stp) const {
+    return tint::utils::Hash(stp.sampler_binding_point,
+                             stp.texture_binding_point);
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_SAMPLER_TEXTURE_PAIR_H_
diff --git a/src/tint/sem/sampler_type.cc b/src/tint/sem/sampler_type.cc
new file mode 100644
index 0000000..20c3a8a
--- /dev/null
+++ b/src/tint/sem/sampler_type.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 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/sampler_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Sampler);
+
+namespace tint {
+namespace sem {
+
+Sampler::Sampler(ast::SamplerKind kind) : kind_(kind) {}
+
+Sampler::Sampler(Sampler&&) = default;
+
+Sampler::~Sampler() = default;
+
+std::string Sampler::type_name() const {
+  return std::string("__sampler_") +
+         (kind_ == ast::SamplerKind::kSampler ? "sampler" : "comparison");
+}
+
+std::string Sampler::FriendlyName(const SymbolTable&) const {
+  return kind_ == ast::SamplerKind::kSampler ? "sampler" : "sampler_comparison";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sampler_type.h b/src/tint/sem/sampler_type.h
new file mode 100644
index 0000000..ca06991
--- /dev/null
+++ b/src/tint/sem/sampler_type.h
@@ -0,0 +1,59 @@
+// Copyright 2020 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_SAMPLER_TYPE_H_
+#define SRC_TINT_SEM_SAMPLER_TYPE_H_
+
+#include <string>
+
+#include "src/tint/ast/sampler.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A sampler type.
+class Sampler : public Castable<Sampler, Type> {
+ public:
+  /// Constructor
+  /// @param kind the kind of sampler
+  explicit Sampler(ast::SamplerKind kind);
+  /// Move constructor
+  Sampler(Sampler&&);
+  ~Sampler() override;
+
+  /// @returns the sampler type
+  ast::SamplerKind kind() const { return kind_; }
+
+  /// @returns true if this is a comparison sampler
+  bool IsComparison() const {
+    return kind_ == ast::SamplerKind::kComparisonSampler;
+  }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+ private:
+  ast::SamplerKind const kind_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_SAMPLER_TYPE_H_
diff --git a/src/tint/sem/sampler_type_test.cc b/src/tint/sem/sampler_type_test.cc
new file mode 100644
index 0000000..4722f8c
--- /dev/null
+++ b/src/tint/sem/sampler_type_test.cc
@@ -0,0 +1,58 @@
+// Copyright 2020 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/sampler_type.h"
+#include "src/tint/sem/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using SamplerTest = TestHelper;
+
+TEST_F(SamplerTest, Creation) {
+  Sampler s{ast::SamplerKind::kSampler};
+  EXPECT_EQ(s.kind(), ast::SamplerKind::kSampler);
+}
+
+TEST_F(SamplerTest, Creation_ComparisonSampler) {
+  Sampler s{ast::SamplerKind::kComparisonSampler};
+  EXPECT_EQ(s.kind(), ast::SamplerKind::kComparisonSampler);
+  EXPECT_TRUE(s.IsComparison());
+}
+
+TEST_F(SamplerTest, TypeName_Sampler) {
+  Sampler s{ast::SamplerKind::kSampler};
+  EXPECT_EQ(s.type_name(), "__sampler_sampler");
+}
+
+TEST_F(SamplerTest, TypeName_Comparison) {
+  Sampler s{ast::SamplerKind::kComparisonSampler};
+  EXPECT_EQ(s.type_name(), "__sampler_comparison");
+}
+
+TEST_F(SamplerTest, FriendlyNameSampler) {
+  Sampler s{ast::SamplerKind::kSampler};
+  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler");
+}
+
+TEST_F(SamplerTest, FriendlyNameComparisonSampler) {
+  Sampler s{ast::SamplerKind::kComparisonSampler};
+  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler_comparison");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sem_array_test.cc b/src/tint/sem/sem_array_test.cc
new file mode 100644
index 0000000..8d16c13
--- /dev/null
+++ b/src/tint/sem/sem_array_test.cc
@@ -0,0 +1,84 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using ArrayTest = TestHelper;
+
+TEST_F(ArrayTest, CreateSizedArray) {
+  U32 u32;
+  auto* arr = create<Array>(&u32, 2, 4, 8, 32, 16);
+  EXPECT_EQ(arr->ElemType(), &u32);
+  EXPECT_EQ(arr->Count(), 2u);
+  EXPECT_EQ(arr->Align(), 4u);
+  EXPECT_EQ(arr->Size(), 8u);
+  EXPECT_EQ(arr->Stride(), 32u);
+  EXPECT_EQ(arr->ImplicitStride(), 16u);
+  EXPECT_FALSE(arr->IsStrideImplicit());
+  EXPECT_FALSE(arr->IsRuntimeSized());
+}
+
+TEST_F(ArrayTest, CreateRuntimeArray) {
+  U32 u32;
+  auto* arr = create<Array>(&u32, 0, 4, 8, 32, 32);
+  EXPECT_EQ(arr->ElemType(), &u32);
+  EXPECT_EQ(arr->Count(), 0u);
+  EXPECT_EQ(arr->Align(), 4u);
+  EXPECT_EQ(arr->Size(), 8u);
+  EXPECT_EQ(arr->Stride(), 32u);
+  EXPECT_EQ(arr->ImplicitStride(), 32u);
+  EXPECT_TRUE(arr->IsStrideImplicit());
+  EXPECT_TRUE(arr->IsRuntimeSized());
+}
+
+TEST_F(ArrayTest, TypeName) {
+  I32 i32;
+  auto* arr = create<Array>(&i32, 2, 0, 4, 4, 4);
+  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_0_size_4_stride_4");
+}
+
+TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
+  auto* arr = create<Array>(create<I32>(), 0, 0, 4, 4, 4);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
+}
+
+TEST_F(ArrayTest, FriendlyNameStaticSized) {
+  auto* arr = create<Array>(create<I32>(), 5, 4, 20, 4, 4);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
+}
+
+TEST_F(ArrayTest, FriendlyNameRuntimeSizedNonImplicitStride) {
+  auto* arr = create<Array>(create<I32>(), 0, 0, 4, 8, 4);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32>");
+}
+
+TEST_F(ArrayTest, FriendlyNameStaticSizedNonImplicitStride) {
+  auto* arr = create<Array>(create<I32>(), 5, 4, 20, 8, 4);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32, 5>");
+}
+
+TEST_F(ArrayTest, TypeName_RuntimeArray) {
+  I32 i32;
+  auto* arr = create<Array>(&i32, 2, 4, 8, 16, 16);
+  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_4_size_8_stride_16");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/sem_struct_test.cc b/src/tint/sem/sem_struct_test.cc
new file mode 100644
index 0000000..9134aab
--- /dev/null
+++ b/src/tint/sem/sem_struct_test.cc
@@ -0,0 +1,102 @@
+// Copyright 2020 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/struct.h"
+#include "src/tint/sem/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using StructTest = TestHelper;
+
+TEST_F(StructTest, Creation) {
+  auto name = Sym("S");
+  auto* impl =
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
+  auto* ptr = impl;
+  auto* s =
+      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
+                          8 /* size */, 16 /* size_no_padding */);
+  EXPECT_EQ(s->Declaration(), ptr);
+  EXPECT_EQ(s->Align(), 4u);
+  EXPECT_EQ(s->Size(), 8u);
+  EXPECT_EQ(s->SizeNoPadding(), 16u);
+}
+
+TEST_F(StructTest, TypeName) {
+  auto name = Sym("my_struct");
+  auto* impl =
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
+  auto* s =
+      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
+                          4 /* size */, 4 /* size_no_padding */);
+  EXPECT_EQ(s->type_name(), "__struct_$1");
+}
+
+TEST_F(StructTest, FriendlyName) {
+  auto name = Sym("my_struct");
+  auto* impl =
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
+  auto* s =
+      create<sem::Struct>(impl, impl->name, StructMemberList{}, 4 /* align */,
+                          4 /* size */, 4 /* size_no_padding */);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
+}
+
+TEST_F(StructTest, Layout) {
+  auto* inner_st =  //
+      Structure("Inner", {
+                             Member("a", ty.i32()),
+                             Member("b", ty.u32()),
+                             Member("c", ty.f32()),
+                             Member("d", ty.vec3<f32>()),
+                             Member("e", ty.mat4x2<f32>()),
+                         });
+
+  auto* outer_st =
+      Structure("Outer", {
+                             Member("inner", ty.type_name("Inner")),
+                             Member("a", ty.i32()),
+                         });
+
+  auto p = Build();
+  ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+
+  auto* sem_inner_st = p.Sem().Get(inner_st);
+  auto* sem_outer_st = p.Sem().Get(outer_st);
+
+  EXPECT_EQ(sem_inner_st->Layout(p.Symbols()),
+            R"(/*            align(16) size(64) */ struct Inner {
+/* offset( 0) align( 4) size( 4) */   a : i32;
+/* offset( 4) align( 4) size( 4) */   b : u32;
+/* offset( 8) align( 4) size( 4) */   c : f32;
+/* offset(12) align( 1) size( 4) */   // -- implicit field alignment padding --;
+/* offset(16) align(16) size(12) */   d : vec3<f32>;
+/* offset(28) align( 1) size( 4) */   // -- implicit field alignment padding --;
+/* offset(32) align( 8) size(32) */   e : mat4x2<f32>;
+/*                               */ };)");
+
+  EXPECT_EQ(sem_outer_st->Layout(p.Symbols()),
+            R"(/*            align(16) size(80) */ struct Outer {
+/* offset( 0) align(16) size(64) */   inner : Inner;
+/* offset(64) align( 4) size( 4) */   a : i32;
+/* offset(68) align( 1) size(12) */   // -- implicit struct size padding --;
+/*                               */ };)");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/statement.cc b/src/tint/sem/statement.cc
new file mode 100644
index 0000000..149507d
--- /dev/null
+++ b/src/tint/sem/statement.cc
@@ -0,0 +1,48 @@
+// 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 <algorithm>
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/statement.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::CompoundStatement);
+
+namespace tint {
+namespace sem {
+
+Statement::Statement(const ast::Statement* declaration,
+                     const CompoundStatement* parent,
+                     const sem::Function* function)
+    : declaration_(declaration), parent_(parent), function_(function) {}
+
+Statement::~Statement() = default;
+
+const BlockStatement* Statement::Block() const {
+  return FindFirstParent<BlockStatement>();
+}
+
+CompoundStatement::CompoundStatement(const ast::Statement* declaration,
+                                     const CompoundStatement* parent,
+                                     const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+CompoundStatement::~CompoundStatement() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/statement.h b/src/tint/sem/statement.h
new file mode 100644
index 0000000..708e741
--- /dev/null
+++ b/src/tint/sem/statement.h
@@ -0,0 +1,188 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_STATEMENT_H_
+#define SRC_TINT_SEM_STATEMENT_H_
+
+#include "src/tint/sem/behavior.h"
+#include "src/tint/sem/node.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class Function;
+class Statement;
+}  // namespace ast
+namespace sem {
+class BlockStatement;
+}  // namespace sem
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Forward declaration
+class CompoundStatement;
+class Function;
+
+namespace detail {
+/// FindFirstParentReturn is a traits helper for determining the return type for
+/// the template member function Statement::FindFirstParent().
+/// For zero or multiple template arguments, FindFirstParentReturn::type
+/// resolves to CompoundStatement.
+template <typename... TYPES>
+struct FindFirstParentReturn {
+  /// The pointer type returned by Statement::FindFirstParent()
+  using type = CompoundStatement;
+};
+
+/// A specialization of FindFirstParentReturn for a single template argument.
+/// FindFirstParentReturn::type resolves to the single template argument.
+template <typename T>
+struct FindFirstParentReturn<T> {
+  /// The pointer type returned by Statement::FindFirstParent()
+  using type = T;
+};
+
+template <typename... TYPES>
+using FindFirstParentReturnType =
+    typename FindFirstParentReturn<TYPES...>::type;
+}  // namespace detail
+
+/// Statement holds the semantic information for a statement.
+class Statement : public Castable<Statement, Node> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  Statement(const ast::Statement* declaration,
+            const CompoundStatement* parent,
+            const sem::Function* function);
+
+  /// Destructor
+  ~Statement() override;
+
+  /// @return the AST node for this statement
+  const ast::Statement* Declaration() const { return declaration_; }
+
+  /// @return the statement that encloses this statement
+  const CompoundStatement* Parent() const { return parent_; }
+
+  /// @returns the closest enclosing parent that satisfies the given predicate,
+  /// which may be the statement itself, or nullptr if no match is found.
+  /// @param pred a predicate that the resulting block must satisfy
+  template <typename Pred>
+  const CompoundStatement* FindFirstParent(Pred&& pred) const;
+
+  /// @returns the closest enclosing parent that is of one of the types in
+  /// `TYPES`, which may be the statement itself, or nullptr if no match is
+  /// found. If `TYPES` is a single template argument, the return type is a
+  /// pointer to that template argument type, otherwise a CompoundStatement
+  /// pointer is returned.
+  template <typename... TYPES>
+  const detail::FindFirstParentReturnType<TYPES...>* FindFirstParent() const;
+
+  /// @return the closest enclosing block for this statement
+  const BlockStatement* Block() const;
+
+  /// @returns the function that owns this statement
+  const sem::Function* Function() const { return function_; }
+
+  /// @return the behaviors of this statement
+  const sem::Behaviors& Behaviors() const { return behaviors_; }
+
+  /// @return the behaviors of this statement
+  sem::Behaviors& Behaviors() { return behaviors_; }
+
+  /// @returns true if this statement is reachable by control flow according to
+  /// the behavior analysis
+  bool IsReachable() const { return is_reachable_; }
+
+  /// @param is_reachable whether this statement is reachable by control flow
+  /// according to the behavior analysis
+  void SetIsReachable(bool is_reachable) { is_reachable_ = is_reachable; }
+
+ private:
+  const ast::Statement* const declaration_;
+  const CompoundStatement* const parent_;
+  const sem::Function* const function_;
+  sem::Behaviors behaviors_{sem::Behavior::kNext};
+  bool is_reachable_ = true;
+};
+
+/// CompoundStatement is the base class of statements that can hold other
+/// statements.
+class CompoundStatement : public Castable<Statement, Statement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this statement
+  /// @param statement the owning statement
+  /// @param function the owning function
+  CompoundStatement(const ast::Statement* declaration,
+                    const CompoundStatement* statement,
+                    const sem::Function* function);
+
+  /// Destructor
+  ~CompoundStatement() override;
+};
+
+template <typename Pred>
+const CompoundStatement* Statement::FindFirstParent(Pred&& pred) const {
+  if (auto* self = As<CompoundStatement>()) {
+    if (pred(self)) {
+      return self;
+    }
+  }
+  const auto* curr = parent_;
+  while (curr && !pred(curr)) {
+    curr = curr->Parent();
+  }
+  return curr;
+}
+
+template <typename... TYPES>
+const detail::FindFirstParentReturnType<TYPES...>* Statement::FindFirstParent()
+    const {
+  using ReturnType = detail::FindFirstParentReturnType<TYPES...>;
+  if (sizeof...(TYPES) == 1) {
+    if (auto* p = As<ReturnType>()) {
+      return p;
+    }
+    const auto* curr = parent_;
+    while (curr) {
+      if (auto* p = curr->As<ReturnType>()) {
+        return p;
+      }
+      curr = curr->Parent();
+    }
+  } else {
+    if (IsAnyOf<TYPES...>()) {
+      return As<ReturnType>();
+    }
+    const auto* curr = parent_;
+    while (curr) {
+      if (curr->IsAnyOf<TYPES...>()) {
+        return curr->As<ReturnType>();
+      }
+      curr = curr->Parent();
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_STATEMENT_H_
diff --git a/src/tint/sem/storage_texture_type.cc b/src/tint/sem/storage_texture_type.cc
new file mode 100644
index 0000000..dbff8e8
--- /dev/null
+++ b/src/tint/sem/storage_texture_type.cc
@@ -0,0 +1,84 @@
+// Copyright 2020 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/storage_texture_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::StorageTexture);
+
+namespace tint {
+namespace sem {
+
+StorageTexture::StorageTexture(ast::TextureDimension dim,
+                               ast::TexelFormat format,
+                               ast::Access access,
+                               sem::Type* subtype)
+    : Base(dim), texel_format_(format), access_(access), subtype_(subtype) {}
+
+StorageTexture::StorageTexture(StorageTexture&&) = default;
+
+StorageTexture::~StorageTexture() = default;
+
+std::string StorageTexture::type_name() const {
+  std::ostringstream out;
+  out << "__storage_texture_" << dim() << "_" << texel_format_ << "_"
+      << access_;
+  return out.str();
+}
+
+std::string StorageTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_storage_" << dim() << "<" << texel_format_ << ", " << access_
+      << ">";
+  return out.str();
+}
+
+sem::Type* StorageTexture::SubtypeFor(ast::TexelFormat format,
+                                      sem::Manager& type_mgr) {
+  switch (format) {
+    case ast::TexelFormat::kR32Uint:
+    case ast::TexelFormat::kRgba8Uint:
+    case ast::TexelFormat::kRg32Uint:
+    case ast::TexelFormat::kRgba16Uint:
+    case ast::TexelFormat::kRgba32Uint: {
+      return type_mgr.Get<sem::U32>();
+    }
+
+    case ast::TexelFormat::kR32Sint:
+    case ast::TexelFormat::kRgba8Sint:
+    case ast::TexelFormat::kRg32Sint:
+    case ast::TexelFormat::kRgba16Sint:
+    case ast::TexelFormat::kRgba32Sint: {
+      return type_mgr.Get<sem::I32>();
+    }
+
+    case ast::TexelFormat::kRgba8Unorm:
+    case ast::TexelFormat::kRgba8Snorm:
+    case ast::TexelFormat::kR32Float:
+    case ast::TexelFormat::kRg32Float:
+    case ast::TexelFormat::kRgba16Float:
+    case ast::TexelFormat::kRgba32Float: {
+      return type_mgr.Get<sem::F32>();
+    }
+
+    case ast::TexelFormat::kNone:
+      break;
+  }
+
+  return nullptr;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/storage_texture_type.h b/src/tint/sem/storage_texture_type.h
new file mode 100644
index 0000000..23c904d
--- /dev/null
+++ b/src/tint/sem/storage_texture_type.h
@@ -0,0 +1,77 @@
+// Copyright 2020 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_STORAGE_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_STORAGE_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+
+class Manager;
+
+/// A storage texture type.
+class StorageTexture : public Castable<StorageTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  /// @param format the texel format of the texture
+  /// @param access the access control type of the texture
+  /// @param subtype the storage subtype. Use SubtypeFor() to calculate this.
+  StorageTexture(ast::TextureDimension dim,
+                 ast::TexelFormat format,
+                 ast::Access access,
+                 sem::Type* subtype);
+
+  /// Move constructor
+  StorageTexture(StorageTexture&&);
+  ~StorageTexture() override;
+
+  /// @returns the storage subtype
+  Type* type() const { return subtype_; }
+
+  /// @returns the texel format
+  ast::TexelFormat texel_format() const { return texel_format_; }
+
+  /// @returns the access control
+  ast::Access access() const { return access_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @param format the storage texture image format
+  /// @param type_mgr the sem::Manager used to build the returned type
+  /// @returns the storage texture subtype for the given TexelFormat
+  static sem::Type* SubtypeFor(ast::TexelFormat format, sem::Manager& type_mgr);
+
+ private:
+  ast::TexelFormat const texel_format_;
+  ast::Access const access_;
+  Type* const subtype_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_STORAGE_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/storage_texture_type_test.cc b/src/tint/sem/storage_texture_type_test.cc
new file mode 100644
index 0000000..07174b2
--- /dev/null
+++ b/src/tint/sem/storage_texture_type_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2020 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/storage_texture_type.h"
+
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/external_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using StorageTextureTest = TestHelper;
+
+TEST_F(StorageTextureTest, Dim) {
+  auto* subtype =
+      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
+  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Float,
+                                   ast::Access::kReadWrite, subtype);
+  EXPECT_EQ(s->dim(), ast::TextureDimension::k2dArray);
+}
+
+TEST_F(StorageTextureTest, Format) {
+  auto* subtype =
+      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
+  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Float,
+                                   ast::Access::kReadWrite, subtype);
+  EXPECT_EQ(s->texel_format(), ast::TexelFormat::kRgba32Float);
+}
+
+TEST_F(StorageTextureTest, TypeName) {
+  auto* subtype =
+      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
+  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Float,
+                                   ast::Access::kReadWrite, subtype);
+  EXPECT_EQ(s->type_name(),
+            "__storage_texture_2d_array_rgba32float_read_write");
+}
+
+TEST_F(StorageTextureTest, FriendlyName) {
+  auto* subtype =
+      StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
+  auto* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Float,
+                                   ast::Access::kReadWrite, subtype);
+  EXPECT_EQ(s->FriendlyName(Symbols()),
+            "texture_storage_2d_array<rgba32float, read_write>");
+}
+
+TEST_F(StorageTextureTest, F32) {
+  auto* subtype =
+      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Float, Types());
+  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Float,
+                                   ast::Access::kReadWrite, subtype);
+
+  auto program = Build();
+
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<F32>());
+}
+
+TEST_F(StorageTextureTest, U32) {
+  auto* subtype =
+      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRg32Uint, Types());
+  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRg32Uint,
+                                   ast::Access::kReadWrite, subtype);
+
+  auto program = Build();
+
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<U32>());
+}
+
+TEST_F(StorageTextureTest, I32) {
+  auto* subtype =
+      sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Sint, Types());
+  Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray,
+                                   ast::TexelFormat::kRgba32Sint,
+                                   ast::Access::kReadWrite, subtype);
+
+  auto program = Build();
+
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
new file mode 100644
index 0000000..6168a9d
--- /dev/null
+++ b/src/tint/sem/struct.cc
@@ -0,0 +1,177 @@
+// 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/struct.h"
+
+#include <cmath>
+#include <iomanip>
+#include <string>
+#include <utility>
+
+#include "src/tint/ast/struct_member.h"
+#include "src/tint/symbol_table.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Struct);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMember);
+
+namespace tint {
+namespace sem {
+
+Struct::Struct(const ast::Struct* declaration,
+               Symbol name,
+               StructMemberList members,
+               uint32_t align,
+               uint32_t size,
+               uint32_t size_no_padding)
+    : declaration_(declaration),
+      name_(name),
+      members_(std::move(members)),
+      align_(align),
+      size_(size),
+      size_no_padding_(size_no_padding) {
+  constructible_ = true;
+  for (auto* member : members_) {
+    if (!member->Type()->IsConstructible()) {
+      constructible_ = false;
+      break;
+    }
+  }
+}
+
+Struct::~Struct() = default;
+
+const StructMember* Struct::FindMember(Symbol name) const {
+  for (auto* member : members_) {
+    if (member->Declaration()->symbol == name) {
+      return member;
+    }
+  }
+  return nullptr;
+}
+
+std::string Struct::type_name() const {
+  return "__struct_" + name_.to_str();
+}
+
+uint32_t Struct::Align() const {
+  return align_;
+}
+
+uint32_t Struct::Size() const {
+  return size_;
+}
+
+std::string Struct::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(name_);
+}
+
+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);
+  };
+
+  if (Members().empty()) {
+    return {};
+  }
+  const auto* const last_member = Members().back();
+  const uint32_t last_member_struct_padding_offset =
+      last_member->Offset() + last_member->Size();
+
+  // Compute max widths to align output
+  const auto offset_w =
+      static_cast<int>(::log10(last_member_struct_padding_offset)) + 1;
+  const auto size_w = static_cast<int>(::log10(Size())) + 1;
+  const auto align_w = static_cast<int>(::log10(Align())) + 1;
+
+  auto print_struct_begin_line = [&](size_t align, size_t size,
+                                     std::string struct_name) {
+    ss << "/*          " << std::setw(offset_w) << " "
+       << "align(" << std::setw(align_w) << align << ") size("
+       << std::setw(size_w) << size << ") */ struct " << struct_name << " {\n";
+  };
+
+  auto print_struct_end_line = [&]() {
+    ss << "/*                         "
+       << std::setw(offset_w + size_w + align_w) << " "
+       << "*/ };";
+  };
+
+  auto print_member_line = [&](size_t offset, size_t align, size_t size,
+                               std::string s) {
+    ss << "/* offset(" << std::setw(offset_w) << offset << ") align("
+       << std::setw(align_w) << align << ") size(" << std::setw(size_w) << size
+       << ") */   " << s << ";\n";
+  };
+
+  print_struct_begin_line(Align(), Size(), UnwrapRef()->FriendlyName(symbols));
+
+  for (size_t i = 0; i < Members().size(); ++i) {
+    auto* const m = Members()[i];
+
+    // Output field alignment padding, if any
+    auto* const prev_member = (i == 0) ? nullptr : Members()[i - 1];
+    if (prev_member) {
+      uint32_t padding =
+          m->Offset() - (prev_member->Offset() + prev_member->Size());
+      if (padding > 0) {
+        size_t padding_offset = m->Offset() - padding;
+        print_member_line(padding_offset, 1, padding,
+                          "// -- implicit field alignment padding --");
+      }
+    }
+
+    // Output member
+    std::string member_name = member_name_of(m);
+    print_member_line(
+        m->Offset(), m->Align(), m->Size(),
+        member_name + " : " + m->Type()->UnwrapRef()->FriendlyName(symbols));
+  }
+
+  // Output struct size padding, if any
+  uint32_t struct_padding = Size() - last_member_struct_padding_offset;
+  if (struct_padding > 0) {
+    print_member_line(last_member_struct_padding_offset, 1, struct_padding,
+                      "// -- implicit struct size padding --");
+  }
+
+  print_struct_end_line();
+
+  return ss.str();
+}
+
+bool Struct::IsConstructible() const {
+  return constructible_;
+}
+
+StructMember::StructMember(const ast::StructMember* declaration,
+                           Symbol name,
+                           sem::Type* type,
+                           uint32_t index,
+                           uint32_t offset,
+                           uint32_t align,
+                           uint32_t size)
+    : declaration_(declaration),
+      name_(name),
+      type_(type),
+      index_(index),
+      offset_(offset),
+      align_(align),
+      size_(size) {}
+
+StructMember::~StructMember() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/struct.h b/src/tint/sem/struct.h
new file mode 100644
index 0000000..acb146e
--- /dev/null
+++ b/src/tint/sem/struct.h
@@ -0,0 +1,232 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_STRUCT_H_
+#define SRC_TINT_SEM_STRUCT_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/ast/struct.h"
+#include "src/tint/sem/node.h"
+#include "src/tint/sem/type.h"
+#include "src/tint/symbol.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class StructMember;
+}  // namespace ast
+
+namespace sem {
+
+// Forward declarations
+class StructMember;
+class Type;
+
+/// A vector of StructMember pointers.
+using StructMemberList = std::vector<const StructMember*>;
+
+/// Metadata to capture how a structure is used in a shader module.
+enum class PipelineStageUsage {
+  kVertexInput,
+  kVertexOutput,
+  kFragmentInput,
+  kFragmentOutput,
+  kComputeInput,
+  kComputeOutput,
+};
+
+/// Struct holds the semantic information for structures.
+class Struct : public Castable<Struct, Type> {
+ public:
+  /// Constructor
+  /// @param declaration the AST structure declaration
+  /// @param name the name of the structure
+  /// @param members the structure members
+  /// @param align the byte alignment of the structure
+  /// @param size the byte size of the structure
+  /// @param size_no_padding size of the members without the end of structure
+  /// alignment padding
+  Struct(const ast::Struct* declaration,
+         Symbol name,
+         StructMemberList members,
+         uint32_t align,
+         uint32_t size,
+         uint32_t size_no_padding);
+
+  /// Destructor
+  ~Struct() override;
+
+  /// @returns the struct
+  const ast::Struct* Declaration() const { return declaration_; }
+
+  /// @returns the name of the structure
+  Symbol Name() const { return name_; }
+
+  /// @returns the members of the structure
+  const StructMemberList& Members() const { return members_; }
+
+  /// @param name the member name to look for
+  /// @returns the member with the given name, or nullptr if it was not found.
+  const StructMember* FindMember(Symbol name) const;
+
+  /// @returns the byte alignment of the structure
+  /// @note this may differ from the alignment of a structure member of this
+  /// structure type, if the member is annotated with the `@align(n)`
+  /// attribute.
+  uint32_t Align() const override;
+
+  /// @returns the byte size of the structure
+  /// @note this may differ from the size of a structure member of this
+  /// structure type, if the member is annotated with the `@size(n)`
+  /// attribute.
+  uint32_t Size() const override;
+
+  /// @returns the byte size of the members without the end of structure
+  /// alignment padding
+  uint32_t SizeNoPadding() const { return size_no_padding_; }
+
+  /// Adds the StorageClass usage to the structure.
+  /// @param usage the storage usage
+  void AddUsage(ast::StorageClass usage) {
+    storage_class_usage_.emplace(usage);
+  }
+
+  /// @returns the set of storage class uses of this structure
+  const std::unordered_set<ast::StorageClass>& StorageClassUsage() const {
+    return storage_class_usage_;
+  }
+
+  /// @param usage the ast::StorageClass usage type to query
+  /// @returns true iff this structure has been used as the given storage class
+  bool UsedAs(ast::StorageClass usage) const {
+    return storage_class_usage_.count(usage) > 0;
+  }
+
+  /// @returns true iff this structure has been used by storage class that's
+  /// host-shareable.
+  bool IsHostShareable() const {
+    for (auto sc : storage_class_usage_) {
+      if (ast::IsHostShareable(sc)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Adds the pipeline stage usage to the structure.
+  /// @param usage the storage usage
+  void AddUsage(PipelineStageUsage usage) {
+    pipeline_stage_uses_.emplace(usage);
+  }
+
+  /// @returns the set of entry point uses of this structure
+  const std::unordered_set<PipelineStageUsage>& PipelineStageUses() const {
+    return pipeline_stage_uses_;
+  }
+
+  /// @returns the name for the type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns a multiline string that describes the layout of this struct,
+  /// including size and alignment information.
+  std::string Layout(const tint::SymbolTable& symbols) const;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+ private:
+  uint64_t LargestMemberBaseAlignment(MemoryLayout mem_layout) const;
+
+  ast::Struct const* const declaration_;
+  const Symbol name_;
+  const StructMemberList members_;
+  const uint32_t align_;
+  const uint32_t size_;
+  const uint32_t size_no_padding_;
+  std::unordered_set<ast::StorageClass> storage_class_usage_;
+  std::unordered_set<PipelineStageUsage> pipeline_stage_uses_;
+  bool constructible_;
+};
+
+/// StructMember holds the semantic information for structure members.
+class StructMember : public Castable<StructMember, Node> {
+ public:
+  /// Constructor
+  /// @param declaration the AST declaration node
+  /// @param name the name of the structure
+  /// @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
+  /// @param align the byte alignment of the member
+  /// @param size the byte size of the member
+  StructMember(const ast::StructMember* declaration,
+               Symbol name,
+               sem::Type* type,
+               uint32_t index,
+               uint32_t offset,
+               uint32_t align,
+               uint32_t size);
+
+  /// Destructor
+  ~StructMember() override;
+
+  /// @returns the AST declaration node
+  const ast::StructMember* Declaration() const { return declaration_; }
+
+  /// @returns the name of the structure
+  Symbol Name() const { return name_; }
+
+  /// @returns the type of the member
+  sem::Type* Type() const { return type_; }
+
+  /// @returns the member index
+  uint32_t Index() const { return index_; }
+
+  /// @returns byte offset from base of structure
+  uint32_t Offset() const { return offset_; }
+
+  /// @returns the alignment of the member in bytes
+  uint32_t Align() const { return align_; }
+
+  /// @returns byte size
+  uint32_t Size() const { return size_; }
+
+ private:
+  const ast::StructMember* const declaration_;
+  const Symbol name_;
+  sem::Type* const type_;
+  const uint32_t index_;
+  const uint32_t offset_;
+  const uint32_t align_;
+  const uint32_t size_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_STRUCT_H_
diff --git a/src/tint/sem/switch_statement.cc b/src/tint/sem/switch_statement.cc
new file mode 100644
index 0000000..403ae92
--- /dev/null
+++ b/src/tint/sem/switch_statement.cc
@@ -0,0 +1,53 @@
+// 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/switch_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::CaseStatement);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchStatement);
+
+namespace tint {
+namespace sem {
+
+SwitchStatement::SwitchStatement(const ast::SwitchStatement* declaration,
+                                 const CompoundStatement* parent,
+                                 const sem::Function* function)
+    : Base(declaration, parent, function) {
+  TINT_ASSERT(Semantic, parent);
+  TINT_ASSERT(Semantic, function);
+}
+
+SwitchStatement::~SwitchStatement() = default;
+
+const ast::SwitchStatement* SwitchStatement::Declaration() const {
+  return static_cast<const ast::SwitchStatement*>(Base::Declaration());
+}
+
+CaseStatement::CaseStatement(const ast::CaseStatement* declaration,
+                             const CompoundStatement* parent,
+                             const sem::Function* function)
+    : Base(declaration, parent, function) {
+  TINT_ASSERT(Semantic, parent);
+  TINT_ASSERT(Semantic, function);
+}
+CaseStatement::~CaseStatement() = default;
+
+const ast::CaseStatement* CaseStatement::Declaration() const {
+  return static_cast<const ast::CaseStatement*>(Base::Declaration());
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/switch_statement.h b/src/tint/sem/switch_statement.h
new file mode 100644
index 0000000..ab9e4eb
--- /dev/null
+++ b/src/tint/sem/switch_statement.h
@@ -0,0 +1,79 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_SWITCH_STATEMENT_H_
+#define SRC_TINT_SEM_SWITCH_STATEMENT_H_
+
+#include "src/tint/sem/block_statement.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class CaseStatement;
+class SwitchStatement;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace sem {
+
+/// Holds semantic information about an switch statement
+class SwitchStatement : public Castable<SwitchStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this switch statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  SwitchStatement(const ast::SwitchStatement* declaration,
+                  const CompoundStatement* parent,
+                  const sem::Function* function);
+
+  /// Destructor
+  ~SwitchStatement() override;
+
+  /// @return the AST node for this statement
+  const ast::SwitchStatement* Declaration() const;
+};
+
+/// Holds semantic information about a switch case statement
+class CaseStatement : public Castable<CaseStatement, CompoundStatement> {
+ public:
+  /// Constructor
+  /// @param declaration the AST node for this case statement
+  /// @param parent the owning statement
+  /// @param function the owning function
+  CaseStatement(const ast::CaseStatement* declaration,
+                const CompoundStatement* parent,
+                const sem::Function* function);
+
+  /// Destructor
+  ~CaseStatement() override;
+
+  /// @return the AST node for this statement
+  const ast::CaseStatement* Declaration() const;
+
+  /// @param body the case body block statement
+  void SetBlock(const BlockStatement* body) { body_ = body; }
+
+  /// @returns the case body block statement
+  const BlockStatement* Body() const { return body_; }
+
+ private:
+  const BlockStatement* body_ = nullptr;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_SWITCH_STATEMENT_H_
diff --git a/src/tint/sem/test_helper.h b/src/tint/sem/test_helper.h
new file mode 100644
index 0000000..1f87769
--- /dev/null
+++ b/src/tint/sem/test_helper.h
@@ -0,0 +1,49 @@
+// Copyright 2020 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_TEST_HELPER_H_
+#define SRC_TINT_SEM_TEST_HELPER_H_
+
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace sem {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {
+ public:
+  /// Builds and returns the program. Must only be called once per test
+  /// @return the built program
+  Program Build() {
+    diag::Formatter formatter;
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << formatter.format(Diagnostics());
+    }();
+    return Program(std::move(*this));
+  }
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TEST_HELPER_H_
diff --git a/src/tint/sem/texture_type.cc b/src/tint/sem/texture_type.cc
new file mode 100644
index 0000000..0fa7cb9
--- /dev/null
+++ b/src/tint/sem/texture_type.cc
@@ -0,0 +1,29 @@
+// Copyright 2020 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/texture_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Texture);
+
+namespace tint {
+namespace sem {
+
+Texture::Texture(ast::TextureDimension dim) : dim_(dim) {}
+
+Texture::Texture(Texture&&) = default;
+
+Texture::~Texture() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/texture_type.h b/src/tint/sem/texture_type.h
new file mode 100644
index 0000000..23f997b
--- /dev/null
+++ b/src/tint/sem/texture_type.h
@@ -0,0 +1,44 @@
+// Copyright 2020 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_TEXTURE_TYPE_H_
+#define SRC_TINT_SEM_TEXTURE_TYPE_H_
+
+#include "src/tint/ast/texture.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A texture type.
+class Texture : public Castable<Texture, Type> {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  explicit Texture(ast::TextureDimension dim);
+  /// Move constructor
+  Texture(Texture&&);
+  ~Texture() override;
+
+  /// @returns the texture dimension
+  ast::TextureDimension dim() const { return dim_; }
+
+ private:
+  ast::TextureDimension const dim_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TEXTURE_TYPE_H_
diff --git a/src/tint/sem/texture_type_test.cc b/src/tint/sem/texture_type_test.cc
new file mode 100644
index 0000000..d36e825
--- /dev/null
+++ b/src/tint/sem/texture_type_test.cc
@@ -0,0 +1,46 @@
+// 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/texture_type.h"
+
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/test_helper.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using TextureTypeDimTest = TestParamHelper<ast::TextureDimension>;
+
+TEST_P(TextureTypeDimTest, DimMustMatch) {
+  // Check that the dim() query returns the right dimensionality.
+  F32 f32;
+  // TextureType is an abstract class, so use concrete class
+  // SampledTexture in its stead.
+  SampledTexture st(GetParam(), &f32);
+  EXPECT_EQ(st.dim(), GetParam());
+}
+
+INSTANTIATE_TEST_SUITE_P(Dimensions,
+                         TextureTypeDimTest,
+                         ::testing::Values(ast::TextureDimension::k1d,
+                                           ast::TextureDimension::k2d,
+                                           ast::TextureDimension::k2dArray,
+                                           ast::TextureDimension::k3d,
+                                           ast::TextureDimension::kCube,
+                                           ast::TextureDimension::kCubeArray));
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
new file mode 100644
index 0000000..88dbf14
--- /dev/null
+++ b/src/tint/sem/type.cc
@@ -0,0 +1,152 @@
+// Copyright 2020 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/type.h"
+
+#include "src/tint/sem/bool_type.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/matrix_type.h"
+#include "src/tint/sem/pointer_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/texture_type.h"
+#include "src/tint/sem/u32_type.h"
+#include "src/tint/sem/vector_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Type);
+
+namespace tint {
+namespace sem {
+
+Type::Type() = default;
+
+Type::Type(Type&&) = default;
+
+Type::~Type() = default;
+
+const Type* Type::UnwrapPtr() const {
+  auto* type = this;
+  while (auto* ptr = type->As<sem::Pointer>()) {
+    type = ptr->StoreType();
+  }
+  return type;
+}
+
+const Type* Type::UnwrapRef() const {
+  auto* type = this;
+  if (auto* ref = type->As<sem::Reference>()) {
+    type = ref->StoreType();
+  }
+  return type;
+}
+
+uint32_t Type::Size() const {
+  return 0;
+}
+
+uint32_t Type::Align() const {
+  return 0;
+}
+
+bool Type::IsConstructible() const {
+  return false;
+}
+
+bool Type::is_scalar() const {
+  return IsAnyOf<F32, U32, I32, Bool>();
+}
+
+bool Type::is_numeric_scalar() const {
+  return IsAnyOf<F32, U32, I32>();
+}
+
+bool Type::is_float_scalar() const {
+  return Is<F32>();
+}
+
+bool Type::is_float_matrix() const {
+  return Is([](const Matrix* m) { return m->type()->is_float_scalar(); });
+}
+
+bool Type::is_float_vector() const {
+  return Is([](const Vector* v) { return v->type()->is_float_scalar(); });
+}
+
+bool Type::is_float_scalar_or_vector() const {
+  return is_float_scalar() || is_float_vector();
+}
+
+bool Type::is_float_scalar_or_vector_or_matrix() const {
+  return is_float_scalar() || is_float_vector() || is_float_matrix();
+}
+
+bool Type::is_integer_scalar() const {
+  return IsAnyOf<U32, I32>();
+}
+
+bool Type::is_signed_integer_scalar() const {
+  return Is<I32>();
+}
+
+bool Type::is_unsigned_integer_scalar() const {
+  return Is<U32>();
+}
+
+bool Type::is_signed_integer_vector() const {
+  return Is([](const Vector* v) { return v->type()->Is<I32>(); });
+}
+
+bool Type::is_unsigned_integer_vector() const {
+  return Is([](const Vector* v) { return v->type()->Is<U32>(); });
+}
+
+bool Type::is_unsigned_scalar_or_vector() const {
+  return Is<U32>() || is_unsigned_integer_vector();
+}
+
+bool Type::is_signed_scalar_or_vector() const {
+  return Is<I32>() || is_signed_integer_vector();
+}
+
+bool Type::is_integer_scalar_or_vector() const {
+  return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector();
+}
+
+bool Type::is_bool_vector() const {
+  return Is([](const Vector* v) { return v->type()->Is<Bool>(); });
+}
+
+bool Type::is_bool_scalar_or_vector() const {
+  return Is<Bool>() || is_bool_vector();
+}
+
+bool Type::is_numeric_vector() const {
+  return Is([](const Vector* v) { return v->type()->is_numeric_scalar(); });
+}
+
+bool Type::is_scalar_vector() const {
+  return Is([](const Vector* v) { return v->type()->is_scalar(); });
+}
+
+bool Type::is_numeric_scalar_or_vector() const {
+  return is_numeric_scalar() || is_numeric_vector();
+}
+
+bool Type::is_handle() const {
+  return IsAnyOf<Sampler, Texture>();
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h
new file mode 100644
index 0000000..97cc658
--- /dev/null
+++ b/src/tint/sem/type.h
@@ -0,0 +1,118 @@
+// Copyright 2020 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_TYPE_H_
+#define SRC_TINT_SEM_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/node.h"
+
+namespace tint {
+
+// Forward declarations
+class ProgramBuilder;
+class SymbolTable;
+
+namespace sem {
+
+/// Supported memory layouts for calculating sizes
+enum class MemoryLayout { kUniformBuffer, kStorageBuffer };
+
+/// Base class for a type in the system
+class Type : public Castable<Type, Node> {
+ public:
+  /// Move constructor
+  Type(Type&&);
+  ~Type() override;
+
+  /// @returns the name for this type. The type name is unique over all types.
+  virtual std::string type_name() const = 0;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
+
+  /// @returns the inner most pointee type if this is a pointer, `this`
+  /// otherwise
+  const Type* UnwrapPtr() const;
+
+  /// @returns the inner type if this is a reference, `this` otherwise
+  const Type* UnwrapRef() const;
+
+  /// @returns the size in bytes of the type. This may include tail padding.
+  /// @note opaque types will return a size of 0.
+  virtual uint32_t Size() const;
+
+  /// @returns the alignment in bytes of the type. This may include tail
+  /// padding.
+  /// @note opaque types will return a size of 0.
+  virtual uint32_t Align() const;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  virtual bool IsConstructible() const;
+
+  /// @returns true if this type is a scalar
+  bool is_scalar() const;
+  /// @returns true if this type is a numeric scalar
+  bool is_numeric_scalar() const;
+  /// @returns true if this type is a float scalar
+  bool is_float_scalar() const;
+  /// @returns true if this type is a float matrix
+  bool is_float_matrix() const;
+  /// @returns true if this type is a float vector
+  bool is_float_vector() const;
+  /// @returns true if this type is a float scalar or vector
+  bool is_float_scalar_or_vector() const;
+  /// @returns true if this type is a float scalar or vector or matrix
+  bool is_float_scalar_or_vector_or_matrix() const;
+  /// @returns true if this type is an integer scalar
+  bool is_integer_scalar() const;
+  /// @returns true if this type is a signed integer scalar
+  bool is_signed_integer_scalar() const;
+  /// @returns true if this type is an unsigned integer scalar
+  bool is_unsigned_integer_scalar() const;
+  /// @returns true if this type is a signed integer vector
+  bool is_signed_integer_vector() const;
+  /// @returns true if this type is an unsigned vector
+  bool is_unsigned_integer_vector() const;
+  /// @returns true if this type is an unsigned scalar or vector
+  bool is_unsigned_scalar_or_vector() const;
+  /// @returns true if this type is a signed scalar or vector
+  bool is_signed_scalar_or_vector() const;
+  /// @returns true if this type is an integer scalar or vector
+  bool is_integer_scalar_or_vector() const;
+  /// @returns true if this type is a boolean vector
+  bool is_bool_vector() const;
+  /// @returns true if this type is boolean scalar or vector
+  bool is_bool_scalar_or_vector() const;
+  /// @returns true if this type is a numeric vector
+  bool is_numeric_vector() const;
+  /// @returns true if this type is a vector of scalar type
+  bool is_scalar_vector() const;
+  /// @returns true if this type is a numeric scale or vector
+  bool is_numeric_scalar_or_vector() const;
+  /// @returns true if this type is a handle type
+  bool is_handle() const;
+
+ protected:
+  Type();
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TYPE_H_
diff --git a/src/tint/sem/type_constructor.cc b/src/tint/sem/type_constructor.cc
new file mode 100644
index 0000000..8ad9388
--- /dev/null
+++ b/src/tint/sem/type_constructor.cc
@@ -0,0 +1,29 @@
+// 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/type_constructor.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::TypeConstructor);
+
+namespace tint {
+namespace sem {
+
+TypeConstructor::TypeConstructor(const sem::Type* type,
+                                 const ParameterList& parameters)
+    : Base(type, parameters) {}
+
+TypeConstructor::~TypeConstructor() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type_constructor.h b/src/tint/sem/type_constructor.h
new file mode 100644
index 0000000..6126949
--- /dev/null
+++ b/src/tint/sem/type_constructor.h
@@ -0,0 +1,38 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_TYPE_CONSTRUCTOR_H_
+#define SRC_TINT_SEM_TYPE_CONSTRUCTOR_H_
+
+#include "src/tint/sem/call_target.h"
+
+namespace tint {
+namespace sem {
+
+/// TypeConstructor is the CallTarget for a type constructor.
+class TypeConstructor : public Castable<TypeConstructor, CallTarget> {
+ public:
+  /// Constructor
+  /// @param type the type that's being constructed
+  /// @param parameters the type constructor parameters
+  TypeConstructor(const sem::Type* type, const ParameterList& parameters);
+
+  /// Destructor
+  ~TypeConstructor() override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TYPE_CONSTRUCTOR_H_
diff --git a/src/tint/sem/type_conversion.cc b/src/tint/sem/type_conversion.cc
new file mode 100644
index 0000000..7def17b
--- /dev/null
+++ b/src/tint/sem/type_conversion.cc
@@ -0,0 +1,29 @@
+// 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/type_conversion.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::TypeConversion);
+
+namespace tint {
+namespace sem {
+
+TypeConversion::TypeConversion(const sem::Type* type,
+                               const sem::Parameter* parameter)
+    : Base(type, ParameterList{parameter}) {}
+
+TypeConversion::~TypeConversion() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type_conversion.h b/src/tint/sem/type_conversion.h
new file mode 100644
index 0000000..ae67711
--- /dev/null
+++ b/src/tint/sem/type_conversion.h
@@ -0,0 +1,44 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_TYPE_CONVERSION_H_
+#define SRC_TINT_SEM_TYPE_CONVERSION_H_
+
+#include "src/tint/sem/call_target.h"
+
+namespace tint {
+namespace sem {
+
+/// TypeConversion is the CallTarget for a type conversion (cast).
+class TypeConversion : public Castable<TypeConversion, CallTarget> {
+ public:
+  /// Constructor
+  /// @param type the target type of the cast
+  /// @param parameter the type cast parameter
+  TypeConversion(const sem::Type* type, const sem::Parameter* parameter);
+
+  /// Destructor
+  ~TypeConversion() override;
+
+  /// @returns the cast source type
+  const sem::Type* Source() const { return Parameters()[0]->Type(); }
+
+  /// @returns the cast target type
+  const sem::Type* Target() const { return ReturnType(); }
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TYPE_CONVERSION_H_
diff --git a/src/tint/sem/type_manager.cc b/src/tint/sem/type_manager.cc
new file mode 100644
index 0000000..e9161d1
--- /dev/null
+++ b/src/tint/sem/type_manager.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 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/type_manager.h"
+
+namespace tint {
+namespace sem {
+
+Manager::Manager() = default;
+Manager::Manager(Manager&&) = default;
+Manager& Manager::operator=(Manager&& rhs) = default;
+Manager::~Manager() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type_manager.h b/src/tint/sem/type_manager.h
new file mode 100644
index 0000000..a248d44
--- /dev/null
+++ b/src/tint/sem/type_manager.h
@@ -0,0 +1,100 @@
+// Copyright 2020 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_TYPE_MANAGER_H_
+#define SRC_TINT_SEM_TYPE_MANAGER_H_
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/block_allocator.h"
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// The type manager holds all the pointers to the known types.
+class Manager {
+ public:
+  /// Iterator is the type returned by begin() and end()
+  using Iterator = BlockAllocator<sem::Type>::ConstIterator;
+
+  /// Constructor
+  Manager();
+
+  /// Move constructor
+  Manager(Manager&&);
+
+  /// Move assignment operator
+  /// @param rhs the Manager to move
+  /// @return this Manager
+  Manager& operator=(Manager&& rhs);
+
+  /// Destructor
+  ~Manager();
+
+  /// Get the given type `T` from the type manager
+  /// @param args the arguments to pass to the type constructor
+  /// @return the pointer to the registered type
+  template <typename T, typename... ARGS>
+  T* Get(ARGS&&... args) {
+    // Note: We do not use std::forward here, as we may need to use the
+    // arguments again for the call to Create<T>() below.
+    auto name = T(args...).type_name();
+    auto it = by_name_.find(name);
+    if (it != by_name_.end()) {
+      return static_cast<T*>(it->second);
+    }
+
+    auto* type = types_.Create<T>(std::forward<ARGS>(args)...);
+    by_name_.emplace(name, type);
+    return type;
+  }
+
+  /// Wrap returns a new Manager created with the types of `inner`.
+  /// The Manager returned by Wrap is intended to temporarily extend the types
+  /// of an existing immutable Manager.
+  /// As the copied types are owned by `inner`, `inner` must not be destructed
+  /// or assigned while using the returned Manager.
+  /// TODO(bclayton) - Evaluate whether there are safer alternatives to this
+  /// function. See crbug.com/tint/460.
+  /// @param inner the immutable Manager to extend
+  /// @return the Manager that wraps `inner`
+  static Manager Wrap(const Manager& inner) {
+    Manager out;
+    out.by_name_ = inner.by_name_;
+    return out;
+  }
+
+  /// Returns the type map
+  /// @returns the mapping from name string to type.
+  const std::unordered_map<std::string, sem::Type*>& types() const {
+    return by_name_;
+  }
+
+  /// @returns an iterator to the beginning of the types
+  Iterator begin() const { return types_.Objects().begin(); }
+  /// @returns an iterator to the end of the types
+  Iterator end() const { return types_.Objects().end(); }
+
+ private:
+  std::unordered_map<std::string, sem::Type*> by_name_;
+  BlockAllocator<sem::Type> types_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TYPE_MANAGER_H_
diff --git a/src/tint/sem/type_manager_test.cc b/src/tint/sem/type_manager_test.cc
new file mode 100644
index 0000000..7fc96d9
--- /dev/null
+++ b/src/tint/sem/type_manager_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2020 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/type_manager.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/u32_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+template <typename T>
+size_t count(const T& range_loopable) {
+  size_t n = 0;
+  for (auto it : range_loopable) {
+    (void)it;
+    n++;
+  }
+  return n;
+}
+
+using TypeManagerTest = testing::Test;
+
+TEST_F(TypeManagerTest, GetUnregistered) {
+  Manager tm;
+  auto* t = tm.Get<I32>();
+  ASSERT_NE(t, nullptr);
+  EXPECT_TRUE(t->Is<I32>());
+}
+
+TEST_F(TypeManagerTest, GetSameTypeReturnsSamePtr) {
+  Manager tm;
+  auto* t = tm.Get<I32>();
+  ASSERT_NE(t, nullptr);
+  EXPECT_TRUE(t->Is<I32>());
+
+  auto* t2 = tm.Get<I32>();
+  EXPECT_EQ(t, t2);
+}
+
+TEST_F(TypeManagerTest, GetDifferentTypeReturnsDifferentPtr) {
+  Manager tm;
+  Type* t = tm.Get<I32>();
+  ASSERT_NE(t, nullptr);
+  EXPECT_TRUE(t->Is<I32>());
+
+  Type* t2 = tm.Get<U32>();
+  ASSERT_NE(t2, nullptr);
+  EXPECT_NE(t, t2);
+  EXPECT_TRUE(t2->Is<U32>());
+}
+
+TEST_F(TypeManagerTest, WrapDoesntAffectInner) {
+  Manager inner;
+  Manager outer = Manager::Wrap(inner);
+
+  inner.Get<I32>();
+
+  EXPECT_EQ(count(inner), 1u);
+  EXPECT_EQ(count(outer), 0u);
+
+  outer.Get<U32>();
+
+  EXPECT_EQ(count(inner), 1u);
+  EXPECT_EQ(count(outer), 1u);
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/type_mappings.h b/src/tint/sem/type_mappings.h
new file mode 100644
index 0000000..1ca2b6d
--- /dev/null
+++ b/src/tint/sem/type_mappings.h
@@ -0,0 +1,91 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_TYPE_MAPPINGS_H_
+#define SRC_TINT_SEM_TYPE_MAPPINGS_H_
+
+#include <type_traits>
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class Array;
+class CallExpression;
+class Expression;
+class ElseStatement;
+class ForLoopStatement;
+class Function;
+class IfStatement;
+class MemberAccessorExpression;
+class Node;
+class Statement;
+class Struct;
+class StructMember;
+class Type;
+class TypeDecl;
+class Variable;
+}  // namespace ast
+
+namespace sem {
+// Forward declarations
+class Array;
+class Call;
+class Expression;
+class ElseStatement;
+class ForLoopStatement;
+class Function;
+class IfStatement;
+class MemberAccessorExpression;
+class Node;
+class Statement;
+class Struct;
+class StructMember;
+class Type;
+class Variable;
+
+/// TypeMappings is a struct that holds undefined `operator()` methods that's
+/// used by SemanticNodeTypeFor to map AST / type node types to their
+/// corresponding semantic node types. The standard operator overload resolving
+/// rules will be used to infer the return type based on the argument type.
+struct TypeMappings {
+  //! @cond Doxygen_Suppress
+  Array* operator()(ast::Array*);
+  Call* operator()(ast::CallExpression*);
+  Expression* operator()(ast::Expression*);
+  ElseStatement* operator()(ast::ElseStatement*);
+  ForLoopStatement* operator()(ast::ForLoopStatement*);
+  Function* operator()(ast::Function*);
+  IfStatement* operator()(ast::IfStatement*);
+  MemberAccessorExpression* operator()(ast::MemberAccessorExpression*);
+  Node* operator()(ast::Node*);
+  Statement* operator()(ast::Statement*);
+  Struct* operator()(ast::Struct*);
+  StructMember* operator()(ast::StructMember*);
+  Type* operator()(ast::Type*);
+  Type* operator()(ast::TypeDecl*);
+  Variable* operator()(ast::Variable*);
+  //! @endcond
+};
+
+/// SemanticNodeTypeFor resolves to the appropriate sem::Node type for the
+/// AST or type node `AST_OR_TYPE`.
+template <typename AST_OR_TYPE>
+using SemanticNodeTypeFor = typename std::remove_pointer<decltype(
+    TypeMappings()(std::declval<AST_OR_TYPE*>()))>::type;
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_TYPE_MAPPINGS_H_
diff --git a/src/tint/sem/u32_type.cc b/src/tint/sem/u32_type.cc
new file mode 100644
index 0000000..30cf2c9
--- /dev/null
+++ b/src/tint/sem/u32_type.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/u32_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::U32);
+
+namespace tint {
+namespace sem {
+
+U32::U32() = default;
+
+U32::~U32() = default;
+
+U32::U32(U32&&) = default;
+
+std::string U32::type_name() const {
+  return "__u32";
+}
+
+std::string U32::FriendlyName(const SymbolTable&) const {
+  return "u32";
+}
+
+bool U32::IsConstructible() const {
+  return true;
+}
+
+uint32_t U32::Size() const {
+  return 4;
+}
+
+uint32_t U32::Align() const {
+  return 4;
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/u32_type.h b/src/tint/sem/u32_type.h
new file mode 100644
index 0000000..af5706c
--- /dev/null
+++ b/src/tint/sem/u32_type.h
@@ -0,0 +1,56 @@
+// Copyright 2020 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_U32_TYPE_H_
+#define SRC_TINT_SEM_U32_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A unsigned int 32 type.
+class U32 : public Castable<U32, Type> {
+ public:
+  /// Constructor
+  U32();
+  /// Move constructor
+  U32(U32&&);
+  ~U32() override;
+
+  /// @returns the name for th type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the size in bytes of the type.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type.
+  uint32_t Align() const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_U32_TYPE_H_
diff --git a/src/tint/sem/u32_type_test.cc b/src/tint/sem/u32_type_test.cc
new file mode 100644
index 0000000..38fa2e9
--- /dev/null
+++ b/src/tint/sem/u32_type_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using U32Test = TestHelper;
+
+TEST_F(U32Test, TypeName) {
+  U32 u;
+  EXPECT_EQ(u.type_name(), "__u32");
+}
+
+TEST_F(U32Test, FriendlyName) {
+  U32 u;
+  EXPECT_EQ(u.FriendlyName(Symbols()), "u32");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/variable.cc b/src/tint/sem/variable.cc
new file mode 100644
index 0000000..b8e4a49
--- /dev/null
+++ b/src/tint/sem/variable.cc
@@ -0,0 +1,89 @@
+// 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/variable.h"
+
+#include <utility>
+
+#include "src/tint/ast/identifier_expression.h"
+#include "src/tint/ast/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Variable);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::GlobalVariable);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::LocalVariable);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Parameter);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::VariableUser);
+
+namespace tint {
+namespace sem {
+
+Variable::Variable(const ast::Variable* declaration,
+                   const sem::Type* type,
+                   ast::StorageClass storage_class,
+                   ast::Access access,
+                   Constant constant_value)
+    : declaration_(declaration),
+      type_(type),
+      storage_class_(storage_class),
+      access_(access),
+      constant_value_(constant_value) {}
+
+Variable::~Variable() = default;
+
+LocalVariable::LocalVariable(const ast::Variable* declaration,
+                             const sem::Type* type,
+                             ast::StorageClass storage_class,
+                             ast::Access access,
+                             const sem::Statement* statement,
+                             Constant constant_value)
+    : Base(declaration, type, storage_class, access, std::move(constant_value)),
+      statement_(statement) {}
+
+LocalVariable::~LocalVariable() = default;
+
+GlobalVariable::GlobalVariable(const ast::Variable* declaration,
+                               const sem::Type* type,
+                               ast::StorageClass storage_class,
+                               ast::Access access,
+                               Constant constant_value,
+                               sem::BindingPoint binding_point)
+    : Base(declaration, type, storage_class, access, std::move(constant_value)),
+      binding_point_(binding_point) {}
+
+GlobalVariable::~GlobalVariable() = default;
+
+Parameter::Parameter(const ast::Variable* declaration,
+                     uint32_t index,
+                     const sem::Type* type,
+                     ast::StorageClass storage_class,
+                     ast::Access access,
+                     const ParameterUsage usage /* = ParameterUsage::kNone */)
+    : Base(declaration, type, storage_class, access, Constant{}),
+      index_(index),
+      usage_(usage) {}
+
+Parameter::~Parameter() = default;
+
+VariableUser::VariableUser(const ast::IdentifierExpression* declaration,
+                           Statement* statement,
+                           sem::Variable* variable)
+    : Base(declaration,
+           variable->Type(),
+           statement,
+           variable->ConstantValue(),
+           /* has_side_effects */ false),
+      variable_(variable) {}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
new file mode 100644
index 0000000..b2f29dd
--- /dev/null
+++ b/src/tint/sem/variable.h
@@ -0,0 +1,273 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_VARIABLE_H_
+#define SRC_TINT_SEM_VARIABLE_H_
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/parameter_usage.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class IdentifierExpression;
+class Variable;
+}  // namespace ast
+
+namespace sem {
+
+// Forward declarations
+class CallTarget;
+class Type;
+class VariableUser;
+
+/// Variable is the base class for local variables, global variables and
+/// parameters.
+class Variable : public Castable<Variable, Node> {
+ public:
+  /// Constructor
+  /// @param declaration the AST declaration node
+  /// @param type the variable type
+  /// @param storage_class the variable storage class
+  /// @param access the variable access control type
+  /// @param constant_value the constant value for the variable. May be invalid
+  Variable(const ast::Variable* declaration,
+           const sem::Type* type,
+           ast::StorageClass storage_class,
+           ast::Access access,
+           Constant constant_value);
+
+  /// Destructor
+  ~Variable() override;
+
+  /// @returns the AST declaration node
+  const ast::Variable* Declaration() const { return declaration_; }
+
+  /// @returns the canonical type for the variable
+  const sem::Type* Type() const { return type_; }
+
+  /// @returns the storage class for the variable
+  ast::StorageClass StorageClass() const { return storage_class_; }
+
+  /// @returns the access control for the variable
+  ast::Access Access() const { return access_; }
+
+  /// @return the constant value of this expression
+  const Constant& ConstantValue() const { return constant_value_; }
+
+  /// @returns the variable constructor expression, or nullptr if the variable
+  /// does not have one.
+  const Expression* Constructor() const { return constructor_; }
+
+  /// Sets the variable constructor expression.
+  /// @param constructor the constructor expression to assign to this variable.
+  void SetConstructor(const Expression* constructor) {
+    constructor_ = constructor;
+  }
+
+  /// @returns the expressions that use the variable
+  const std::vector<const VariableUser*>& Users() const { return users_; }
+
+  /// @param user the user to add
+  void AddUser(const VariableUser* user) { users_.emplace_back(user); }
+
+ private:
+  const ast::Variable* const declaration_;
+  const sem::Type* const type_;
+  const ast::StorageClass storage_class_;
+  const ast::Access access_;
+  const Constant constant_value_;
+  const Expression* constructor_ = nullptr;
+  std::vector<const VariableUser*> users_;
+};
+
+/// LocalVariable is a function-scope variable
+class LocalVariable : public Castable<LocalVariable, Variable> {
+ public:
+  /// Constructor
+  /// @param declaration the AST declaration node
+  /// @param type the variable type
+  /// @param storage_class the variable storage class
+  /// @param access the variable access control type
+  /// @param statement the statement that declared this local variable
+  /// @param constant_value the constant value for the variable. May be invalid
+  LocalVariable(const ast::Variable* declaration,
+                const sem::Type* type,
+                ast::StorageClass storage_class,
+                ast::Access access,
+                const sem::Statement* statement,
+                Constant constant_value);
+
+  /// Destructor
+  ~LocalVariable() override;
+
+  /// @returns the statement that declares this local variable
+  const sem::Statement* Statement() const { return statement_; }
+
+  /// @returns the Type, Function or Variable that this local variable shadows
+  const sem::Node* Shadows() const { return shadows_; }
+
+  /// Sets the Type, Function or Variable that this local variable shadows
+  /// @param shadows the Type, Function or Variable that this variable shadows
+  void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
+
+ private:
+  const sem::Statement* const statement_;
+  const sem::Node* shadows_ = nullptr;
+};
+
+/// GlobalVariable is a module-scope variable
+class GlobalVariable : public Castable<GlobalVariable, Variable> {
+ public:
+  /// Constructor
+  /// @param declaration the AST declaration node
+  /// @param type the variable type
+  /// @param storage_class the variable storage class
+  /// @param access the variable access control type
+  /// @param constant_value the constant value for the variable. May be invalid
+  /// @param binding_point the optional resource binding point of the variable
+  GlobalVariable(const ast::Variable* declaration,
+                 const sem::Type* type,
+                 ast::StorageClass storage_class,
+                 ast::Access access,
+                 Constant constant_value,
+                 sem::BindingPoint binding_point = {});
+
+  /// Destructor
+  ~GlobalVariable() override;
+
+  /// @returns the resource binding point for the variable
+  sem::BindingPoint BindingPoint() const { return binding_point_; }
+
+  /// @param id the constant identifier to assign to this variable
+  void SetConstantId(uint16_t id) {
+    constant_id_ = id;
+    is_overridable_ = true;
+  }
+
+  /// @returns the pipeline constant ID associated with the variable
+  uint16_t ConstantId() const { return constant_id_; }
+
+  /// @param is_overridable true if this is a pipeline overridable constant
+  void SetIsOverridable(bool is_overridable = true) {
+    is_overridable_ = is_overridable;
+  }
+
+  /// @returns true if this is pipeline overridable constant
+  bool IsOverridable() const { return is_overridable_; }
+
+ private:
+  const sem::BindingPoint binding_point_;
+
+  bool is_overridable_ = false;
+  uint16_t constant_id_ = 0;
+};
+
+/// Parameter is a function parameter
+class Parameter : public Castable<Parameter, Variable> {
+ public:
+  /// Constructor for function parameters
+  /// @param declaration the AST declaration node
+  /// @param index the index of the parmeter in the function
+  /// @param type the variable type
+  /// @param storage_class the variable storage class
+  /// @param access the variable access control type
+  /// @param usage the semantic usage for the parameter
+  Parameter(const ast::Variable* declaration,
+            uint32_t index,
+            const sem::Type* type,
+            ast::StorageClass storage_class,
+            ast::Access access,
+            const ParameterUsage usage = ParameterUsage::kNone);
+
+  /// Destructor
+  ~Parameter() override;
+
+  /// @return the index of the parmeter in the function
+  uint32_t Index() const { return index_; }
+
+  /// @returns the semantic usage for the parameter
+  ParameterUsage Usage() const { return usage_; }
+
+  /// @returns the CallTarget owner of this parameter
+  CallTarget const* Owner() const { return owner_; }
+
+  /// @param owner the CallTarget owner of this parameter
+  void SetOwner(CallTarget const* owner) { owner_ = owner; }
+
+  /// @returns the Type, Function or Variable that this local variable shadows
+  const sem::Node* Shadows() const { return shadows_; }
+
+  /// Sets the Type, Function or Variable that this local variable shadows
+  /// @param shadows the Type, Function or Variable that this variable shadows
+  void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
+
+ private:
+  const uint32_t index_;
+  const ParameterUsage usage_;
+  CallTarget const* owner_ = nullptr;
+  const sem::Node* shadows_ = nullptr;
+};
+
+/// ParameterList is a list of Parameter
+using ParameterList = std::vector<const Parameter*>;
+
+/// VariableUser holds the semantic information for an identifier expression
+/// node that resolves to a variable.
+class VariableUser : public Castable<VariableUser, Expression> {
+ public:
+  /// Constructor
+  /// @param declaration the AST identifier node
+  /// @param statement the statement that owns this expression
+  /// @param variable the semantic variable
+  VariableUser(const ast::IdentifierExpression* declaration,
+               Statement* statement,
+               sem::Variable* variable);
+
+  /// @returns the variable that this expression refers to
+  const sem::Variable* Variable() const { return variable_; }
+
+ private:
+  const sem::Variable* const variable_;
+};
+
+/// A pair of sem::Variables. Can be hashed.
+typedef std::pair<const Variable*, const Variable*> VariablePair;
+
+}  // namespace sem
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for VariablePair
+template <>
+class hash<tint::sem::VariablePair> {
+ public:
+  /// @param i the variable pair to create a hash for
+  /// @return the hash value
+  inline std::size_t operator()(const tint::sem::VariablePair& i) const {
+    return tint::utils::Hash(i.first, i.second);
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_VARIABLE_H_
diff --git a/src/tint/sem/vector_type.cc b/src/tint/sem/vector_type.cc
new file mode 100644
index 0000000..c0bc890
--- /dev/null
+++ b/src/tint/sem/vector_type.cc
@@ -0,0 +1,81 @@
+// Copyright 2020 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/vector_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Vector);
+
+namespace tint {
+namespace sem {
+
+Vector::Vector(Type const* subtype, uint32_t width)
+    : subtype_(subtype), width_(width) {
+  TINT_ASSERT(Semantic, width_ > 1);
+  TINT_ASSERT(Semantic, width_ < 5);
+}
+
+Vector::Vector(Vector&&) = default;
+
+Vector::~Vector() = default;
+
+std::string Vector::type_name() const {
+  return "__vec_" + std::to_string(width_) + subtype_->type_name();
+}
+
+std::string Vector::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "vec" << width_ << "<" << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
+bool Vector::IsConstructible() const {
+  return true;
+}
+
+uint32_t Vector::Size() const {
+  return SizeOf(width_);
+}
+
+uint32_t Vector::Align() const {
+  return AlignOf(width_);
+}
+
+uint32_t Vector::SizeOf(uint32_t width) {
+  switch (width) {
+    case 2:
+      return 8;
+    case 3:
+      return 12;
+    case 4:
+      return 16;
+  }
+  return 0;  // Unreachable
+}
+
+uint32_t Vector::AlignOf(uint32_t width) {
+  switch (width) {
+    case 2:
+      return 8;
+    case 3:
+      return 16;
+    case 4:
+      return 16;
+  }
+  return 0;  // Unreachable
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/vector_type.h b/src/tint/sem/vector_type.h
new file mode 100644
index 0000000..cbce602
--- /dev/null
+++ b/src/tint/sem/vector_type.h
@@ -0,0 +1,77 @@
+// Copyright 2020 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_VECTOR_TYPE_H_
+#define SRC_TINT_SEM_VECTOR_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A vector type.
+class Vector : public Castable<Vector, Type> {
+ public:
+  /// Constructor
+  /// @param subtype the vector element type
+  /// @param size the number of elements in the vector
+  Vector(Type const* subtype, uint32_t size);
+  /// Move constructor
+  Vector(Vector&&);
+  ~Vector() override;
+
+  /// @returns the type of the vector elements
+  const Type* type() const { return subtype_; }
+
+  /// @returns the name for th type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
+  /// @returns true if constructible as per
+  /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
+  bool IsConstructible() const override;
+
+  /// @returns the number of elements in the vector
+  uint32_t Width() const { return width_; }
+
+  /// @returns the size in bytes of the type. This may include tail padding.
+  uint32_t Size() const override;
+
+  /// @returns the alignment in bytes of the type. This may include tail
+  /// padding.
+  uint32_t Align() const override;
+
+  /// @param width the width of the vector
+  /// @returns the size in bytes of a vector of the given width.
+  static uint32_t SizeOf(uint32_t width);
+
+  /// @param width the width of the vector
+  /// @returns the alignment in bytes of a vector of the given width.
+  static uint32_t AlignOf(uint32_t width);
+
+ private:
+  Type const* const subtype_;
+  const uint32_t width_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_VECTOR_TYPE_H_
diff --git a/src/tint/sem/vector_type_test.cc b/src/tint/sem/vector_type_test.cc
new file mode 100644
index 0000000..c853b9b
--- /dev/null
+++ b/src/tint/sem/vector_type_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 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/test_helper.h"
+#include "src/tint/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using VectorTest = TestHelper;
+
+TEST_F(VectorTest, Creation) {
+  I32 i32;
+  Vector v{&i32, 2};
+  EXPECT_EQ(v.type(), &i32);
+  EXPECT_EQ(v.Width(), 2u);
+}
+
+TEST_F(VectorTest, TypeName) {
+  auto* i32 = create<I32>();
+  auto* v = create<Vector>(i32, 3);
+  EXPECT_EQ(v->type_name(), "__vec_3__i32");
+}
+
+TEST_F(VectorTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* v = create<Vector>(f32, 3);
+  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/void_type.cc b/src/tint/sem/void_type.cc
new file mode 100644
index 0000000..4a7abed
--- /dev/null
+++ b/src/tint/sem/void_type.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 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/void_type.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Void);
+
+namespace tint {
+namespace sem {
+
+Void::Void() = default;
+
+Void::Void(Void&&) = default;
+
+Void::~Void() = default;
+
+std::string Void::type_name() const {
+  return "__void";
+}
+
+std::string Void::FriendlyName(const SymbolTable&) const {
+  return "void";
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/tint/sem/void_type.h b/src/tint/sem/void_type.h
new file mode 100644
index 0000000..593029e
--- /dev/null
+++ b/src/tint/sem/void_type.h
@@ -0,0 +1,46 @@
+// Copyright 2020 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_VOID_TYPE_H_
+#define SRC_TINT_SEM_VOID_TYPE_H_
+
+#include <string>
+
+#include "src/tint/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A void type
+class Void : public Castable<Void, Type> {
+ public:
+  /// Constructor
+  Void();
+  /// Move constructor
+  Void(Void&&);
+  ~Void() override;
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_TINT_SEM_VOID_TYPE_H_
diff --git a/src/tint/source.cc b/src/tint/source.cc
new file mode 100644
index 0000000..c381f91
--- /dev/null
+++ b/src/tint/source.cc
@@ -0,0 +1,121 @@
+// Copyright 2020 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/source.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string_view>
+#include <utility>
+
+namespace tint {
+namespace {
+std::vector<std::string_view> SplitLines(std::string_view str) {
+  std::vector<std::string_view> lines;
+
+  size_t lineStart = 0;
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (str[i] == '\n') {
+      lines.push_back(str.substr(lineStart, i - lineStart));
+      lineStart = i + 1;
+    }
+  }
+  if (lineStart < str.size()) {
+    lines.push_back(str.substr(lineStart));
+  }
+
+  return lines;
+}
+
+std::vector<std::string_view> CopyRelativeStringViews(
+    const std::vector<std::string_view>& src_list,
+    const std::string_view& src_view,
+    const std::string_view& dst_view) {
+  std::vector<std::string_view> out(src_list.size());
+  for (size_t i = 0; i < src_list.size(); i++) {
+    auto offset = static_cast<size_t>(&src_list[i].front() - &src_view.front());
+    auto count = src_list[i].length();
+    out[i] = dst_view.substr(offset, count);
+  }
+  return out;
+}
+
+}  // namespace
+
+Source::FileContent::FileContent(const std::string& body)
+    : data(body), data_view(data), lines(SplitLines(data_view)) {}
+
+Source::FileContent::FileContent(const FileContent& rhs)
+    : data(rhs.data),
+      data_view(data),
+      lines(CopyRelativeStringViews(rhs.lines, rhs.data_view, data_view)) {}
+
+Source::FileContent::~FileContent() = default;
+
+Source::File::~File() = default;
+
+std::ostream& operator<<(std::ostream& out, const Source& source) {
+  auto rng = source.range;
+
+  if (source.file) {
+    out << source.file->path << ":";
+  }
+  if (rng.begin.line) {
+    out << rng.begin.line << ":";
+    if (rng.begin.column) {
+      out << rng.begin.column;
+    }
+
+    if (source.file) {
+      out << std::endl << std::endl;
+
+      auto repeat = [&](char c, size_t n) {
+        while (n--) {
+          out << c;
+        }
+      };
+
+      for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
+        if (line < source.file->content.lines.size() + 1) {
+          auto len = source.file->content.lines[line - 1].size();
+
+          out << source.file->content.lines[line - 1];
+
+          out << std::endl;
+
+          if (line == rng.begin.line && line == rng.end.line) {
+            // Single line
+            repeat(' ', rng.begin.column - 1);
+            repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
+          } else if (line == rng.begin.line) {
+            // Start of multi-line
+            repeat(' ', rng.begin.column - 1);
+            repeat('^', len - (rng.begin.column - 1));
+          } else if (line == rng.end.line) {
+            // End of multi-line
+            repeat('^', rng.end.column - 1);
+          } else {
+            // Middle of multi-line
+            repeat('^', len);
+          }
+
+          out << std::endl;
+        }
+      }
+    }
+  }
+  return out;
+}
+
+}  // namespace tint
diff --git a/src/tint/source.h b/src/tint/source.h
new file mode 100644
index 0000000..931cdf1
--- /dev/null
+++ b/src/tint/source.h
@@ -0,0 +1,238 @@
+
+// Copyright 2020 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_SOURCE_H_
+#define SRC_TINT_SOURCE_H_
+
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+namespace tint {
+
+/// Source describes a range of characters within a source file.
+class Source {
+ public:
+  /// FileContent describes the content of a source file encoded using utf-8.
+  class FileContent {
+   public:
+    /// Constructs the FileContent with the given file content.
+    /// @param data the file contents
+    explicit FileContent(const std::string& data);
+
+    /// Copy constructor
+    /// @param rhs the FileContent to copy
+    FileContent(const FileContent& rhs);
+
+    /// Destructor
+    ~FileContent();
+
+    /// The original un-split file content
+    const std::string data;
+    /// A string_view over #data
+    const std::string_view data_view;
+    /// #data split by lines
+    const std::vector<std::string_view> lines;
+  };
+
+  /// File describes a source file, including path and content.
+  class File {
+   public:
+    /// Constructs the File with the given file path and content.
+    /// @param p the path for this file
+    /// @param c the file contents
+    inline File(const std::string& p, const std::string& c)
+        : path(p), content(c) {}
+
+    /// Copy constructor
+    File(const File&) = default;
+
+    /// Move constructor
+    File(File&&) = default;
+
+    /// Destructor
+    ~File();
+
+    /// file path
+    const std::string path;
+    /// file content
+    const FileContent content;
+  };
+
+  /// Location holds a 1-based line and column index.
+  class Location {
+   public:
+    /// the 1-based line number. 0 represents no line information.
+    size_t line = 0;
+    /// the 1-based column number in utf8-code units (bytes).
+    /// 0 represents no column information.
+    size_t column = 0;
+
+    /// Returns true of `this` location is lexicographically less than `rhs`
+    /// @param rhs location to compare against
+    /// @returns true if `this` < `rhs`
+    inline bool operator<(const Source::Location& rhs) {
+      return std::tie(line, column) < std::tie(rhs.line, rhs.column);
+    }
+
+    /// Returns true of `this` location is equal to `rhs`
+    /// @param rhs location to compare against
+    /// @returns true if `this` == `rhs`
+    inline bool operator==(const Location& rhs) const {
+      return line == rhs.line && column == rhs.column;
+    }
+
+    /// Returns true of `this` location is not equal to `rhs`
+    /// @param rhs location to compare against
+    /// @returns true if `this` != `rhs`
+    inline bool operator!=(const Location& rhs) const {
+      return !(*this == rhs);
+    }
+  };
+
+  /// Range holds a Location interval described by [begin, end).
+  class Range {
+   public:
+    /// Constructs a zero initialized Range.
+    inline Range() = default;
+
+    /// Constructs a zero-length Range starting at `loc`
+    /// @param loc the start and end location for the range
+    inline constexpr explicit Range(const Location& loc)
+        : begin(loc), end(loc) {}
+
+    /// Constructs the Range beginning at `b` and ending at `e`
+    /// @param b the range start location
+    /// @param e the range end location
+    inline constexpr Range(const Location& b, const Location& e)
+        : begin(b), end(e) {}
+
+    /// Return a column-shifted Range
+    /// @param n the number of characters to shift by
+    /// @returns a Range with a #begin and #end column shifted by `n`
+    inline Range operator+(size_t n) const {
+      return Range{{begin.line, begin.column + n}, {end.line, end.column + n}};
+    }
+
+    /// Returns true of `this` range is not equal to `rhs`
+    /// @param rhs range to compare against
+    /// @returns true if `this` != `rhs`
+    inline bool operator==(const Range& rhs) const {
+      return begin == rhs.begin && end == rhs.end;
+    }
+
+    /// Returns true of `this` range is equal to `rhs`
+    /// @param rhs range to compare against
+    /// @returns true if `this` == `rhs`
+    inline bool operator!=(const Range& rhs) const { return !(*this == rhs); }
+
+    /// The location of the first character in the range.
+    Location begin;
+    /// The location of one-past the last character in the range.
+    Location end;
+  };
+
+  /// Constructs the Source with an zero initialized Range and null File.
+  inline Source() : range() {}
+
+  /// Constructs the Source with the Range `rng` and a null File
+  /// @param rng the source range
+  inline explicit Source(const Range& rng) : range(rng) {}
+
+  /// Constructs the Source with the Range `loc` and a null File
+  /// @param loc the start and end location for the source range
+  inline explicit Source(const Location& loc) : range(Range(loc)) {}
+
+  /// Constructs the Source with the Range `rng` and File `file`
+  /// @param rng the source range
+  /// @param f the source file
+  inline Source(const Range& rng, File const* f) : range(rng), file(f) {}
+
+  /// @returns a Source that points to the begin range of this Source.
+  inline Source Begin() const { return Source(Range{range.begin}, file); }
+
+  /// @returns a Source that points to the end range of this Source.
+  inline Source End() const { return Source(Range{range.end}, file); }
+
+  /// Return a column-shifted Source
+  /// @param n the number of characters to shift by
+  /// @returns a Source with the range's columns shifted by `n`
+  inline Source operator+(size_t n) const { return Source(range + n, file); }
+
+  /// Returns true of `this` Source is lexicographically less than `rhs`
+  /// @param rhs source to compare against
+  /// @returns true if `this` < `rhs`
+  inline bool operator<(const Source& rhs) {
+    if (file != rhs.file) {
+      return false;
+    }
+    return range.begin < rhs.range.begin;
+  }
+
+  /// Helper function that returns the range union of two source locations. The
+  /// `start` and `end` locations are assumed to refer to the same source file.
+  /// @param start the start source of the range
+  /// @param end the end source of the range
+  /// @returns the combined source
+  inline static Source Combine(const Source& start, const Source& end) {
+    return Source(Source::Range(start.range.begin, end.range.end), start.file);
+  }
+
+  /// range is the span of text this source refers to in #file
+  Range range;
+  /// file is the optional source content this source refers to
+  const File* file = nullptr;
+};
+
+/// Writes the Source::Location to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param loc the location to write
+/// @returns out so calls can be chained
+inline std::ostream& operator<<(std::ostream& out,
+                                const Source::Location& loc) {
+  out << loc.line << ":" << loc.column;
+  return out;
+}
+
+/// Writes the Source::Range to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param range the range to write
+/// @returns out so calls can be chained
+inline std::ostream& operator<<(std::ostream& out, const Source::Range& range) {
+  out << "[" << range.begin << ", " << range.end << "]";
+  return out;
+}
+
+/// Writes the Source to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param source the source to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, const Source& source);
+
+/// Writes the Source::FileContent to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param content the file content to write
+/// @returns out so calls can be chained
+inline std::ostream& operator<<(std::ostream& out,
+                                const Source::FileContent& content) {
+  out << content.data;
+  return out;
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_SOURCE_H_
diff --git a/src/tint/source_test.cc b/src/tint/source_test.cc
new file mode 100644
index 0000000..a3b9825
--- /dev/null
+++ b/src/tint/source_test.cc
@@ -0,0 +1,66 @@
+// 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/source.h"
+
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+static constexpr const char* kSource = R"(line one
+line two
+line three)";
+
+using SourceFileContentTest = testing::Test;
+
+TEST_F(SourceFileContentTest, Ctor) {
+  Source::FileContent fc(kSource);
+  EXPECT_EQ(fc.data, kSource);
+  EXPECT_EQ(fc.data_view, kSource);
+  ASSERT_EQ(fc.lines.size(), 3u);
+  EXPECT_EQ(fc.lines[0], "line one");
+  EXPECT_EQ(fc.lines[1], "line two");
+  EXPECT_EQ(fc.lines[2], "line three");
+}
+
+TEST_F(SourceFileContentTest, CopyCtor) {
+  auto src = std::make_unique<Source::FileContent>(kSource);
+  Source::FileContent fc{*src};
+  src.reset();
+  EXPECT_EQ(fc.data, kSource);
+  EXPECT_EQ(fc.data_view, kSource);
+  ASSERT_EQ(fc.lines.size(), 3u);
+  EXPECT_EQ(fc.lines[0], "line one");
+  EXPECT_EQ(fc.lines[1], "line two");
+  EXPECT_EQ(fc.lines[2], "line three");
+}
+
+TEST_F(SourceFileContentTest, MoveCtor) {
+  auto src = std::make_unique<Source::FileContent>(kSource);
+  Source::FileContent fc{std::move(*src)};
+  src.reset();
+  EXPECT_EQ(fc.data, kSource);
+  EXPECT_EQ(fc.data_view, kSource);
+  ASSERT_EQ(fc.lines.size(), 3u);
+  EXPECT_EQ(fc.lines[0], "line one");
+  EXPECT_EQ(fc.lines[1], "line two");
+  EXPECT_EQ(fc.lines[2], "line three");
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/symbol.cc b/src/tint/symbol.cc
new file mode 100644
index 0000000..43218a7
--- /dev/null
+++ b/src/tint/symbol.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 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/symbol.h"
+
+#include <utility>
+
+namespace tint {
+
+Symbol::Symbol() = default;
+
+Symbol::Symbol(uint32_t val, tint::ProgramID program_id)
+    : val_(val), program_id_(program_id) {}
+
+#if TINT_SYMBOL_STORE_DEBUG_NAME
+Symbol::Symbol(uint32_t val, tint::ProgramID program_id, std::string debug_name)
+    : val_(val), program_id_(program_id), debug_name_(std::move(debug_name)) {}
+#endif
+
+Symbol::Symbol(const Symbol& o) = default;
+
+Symbol::Symbol(Symbol&& o) = default;
+
+Symbol::~Symbol() = default;
+
+Symbol& Symbol::operator=(const Symbol& o) = default;
+
+Symbol& Symbol::operator=(Symbol&& o) = default;
+
+bool Symbol::operator==(const Symbol& other) const {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_,
+                                         other.program_id_);
+  return val_ == other.val_;
+}
+
+bool Symbol::operator<(const Symbol& other) const {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_,
+                                         other.program_id_);
+  return val_ < other.val_;
+}
+
+std::string Symbol::to_str() const {
+  return "$" + std::to_string(val_);
+}
+
+}  // namespace tint
diff --git a/src/tint/symbol.h b/src/tint/symbol.h
new file mode 100644
index 0000000..801fe62
--- /dev/null
+++ b/src/tint/symbol.h
@@ -0,0 +1,121 @@
+// Copyright 2020 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_SYMBOL_H_
+#define SRC_TINT_SYMBOL_H_
+
+// If TINT_SYMBOL_STORE_DEBUG_NAME is 1, Symbol instances store a `debug_name_`
+// member initialized with the name of the identifier they represent. This
+// member is not exposed, but is useful for debugging purposes.
+#ifndef TINT_SYMBOL_STORE_DEBUG_NAME
+#define TINT_SYMBOL_STORE_DEBUG_NAME 0
+#endif
+
+#include <string>
+
+#include "src/tint/program_id.h"
+
+namespace tint {
+
+/// A symbol representing a string in the system
+class Symbol {
+ public:
+  /// Constructor
+  /// An invalid symbol
+  Symbol();
+  /// Constructor
+  /// @param val the symbol value
+  /// @param program_id the identifier of the program that owns this Symbol
+  Symbol(uint32_t val, tint::ProgramID program_id);
+#if TINT_SYMBOL_STORE_DEBUG_NAME
+  /// Constructor
+  /// @param val the symbol value
+  /// @param program_id the identifier of the program that owns this Symbol
+  /// @param debug_name name of symbols used only for debugging
+  Symbol(uint32_t val, tint::ProgramID program_id, std::string debug_name);
+#endif
+  /// Copy constructor
+  /// @param o the symbol to copy
+  Symbol(const Symbol& o);
+  /// Move constructor
+  /// @param o the symbol to move
+  Symbol(Symbol&& o);
+  /// Destructor
+  ~Symbol();
+
+  /// Copy assignment
+  /// @param o the other symbol
+  /// @returns the symbol after doing the copy
+  Symbol& operator=(const Symbol& o);
+  /// Move assignment
+  /// @param o the other symbol
+  /// @returns teh symbol after doing the move
+  Symbol& operator=(Symbol&& o);
+
+  /// Comparison operator
+  /// @param o the other symbol
+  /// @returns true if the symbols are the same
+  bool operator==(const Symbol& o) const;
+
+  /// Less-than operator
+  /// @param o the other symbol
+  /// @returns true if this symbol is ordered before symbol `o`
+  bool operator<(const Symbol& o) const;
+
+  /// @returns true if the symbol is valid
+  bool IsValid() const { return val_ != static_cast<uint32_t>(-1); }
+
+  /// @returns the value for the symbol
+  uint32_t value() const { return val_; }
+
+  /// Convert the symbol to a string
+  /// @return the string representation of the symbol
+  std::string to_str() const;
+
+  /// @returns the identifier of the Program that owns this symbol.
+  tint::ProgramID ProgramID() const { return program_id_; }
+
+ private:
+  uint32_t val_ = static_cast<uint32_t>(-1);
+  tint::ProgramID program_id_;
+#if TINT_SYMBOL_STORE_DEBUG_NAME
+  std::string debug_name_;
+#endif
+};
+
+/// @param sym the Symbol
+/// @returns the ProgramID that owns the given Symbol
+inline ProgramID ProgramIDOf(Symbol sym) {
+  return sym.IsValid() ? sym.ProgramID() : ProgramID();
+}
+
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::Symbol so symbols can be used as
+/// keys for std::unordered_map and std::unordered_set.
+template <>
+class hash<tint::Symbol> {
+ public:
+  /// @param sym the symbol to return
+  /// @return the Symbol internal value
+  inline std::size_t operator()(const tint::Symbol& sym) const {
+    return static_cast<std::size_t>(sym.value());
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SYMBOL_H_
diff --git a/src/tint/symbol_table.cc b/src/tint/symbol_table.cc
new file mode 100644
index 0000000..6b382dc
--- /dev/null
+++ b/src/tint/symbol_table.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 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/symbol_table.h"
+
+#include "src/tint/debug.h"
+
+namespace tint {
+
+SymbolTable::SymbolTable(tint::ProgramID program_id)
+    : program_id_(program_id) {}
+
+SymbolTable::SymbolTable(const SymbolTable&) = default;
+
+SymbolTable::SymbolTable(SymbolTable&&) = default;
+
+SymbolTable::~SymbolTable() = default;
+
+SymbolTable& SymbolTable::operator=(const SymbolTable& other) = default;
+
+SymbolTable& SymbolTable::operator=(SymbolTable&&) = default;
+
+Symbol SymbolTable::Register(const std::string& name) {
+  TINT_ASSERT(Symbol, !name.empty());
+
+  auto it = name_to_symbol_.find(name);
+  if (it != name_to_symbol_.end())
+    return it->second;
+
+#if TINT_SYMBOL_STORE_DEBUG_NAME
+  Symbol sym(next_symbol_, program_id_, name);
+#else
+  Symbol sym(next_symbol_, program_id_);
+#endif
+  ++next_symbol_;
+
+  name_to_symbol_[name] = sym;
+  symbol_to_name_[sym] = name;
+
+  return sym;
+}
+
+Symbol SymbolTable::Get(const std::string& name) const {
+  auto it = name_to_symbol_.find(name);
+  return it != name_to_symbol_.end() ? it->second : Symbol();
+}
+
+std::string SymbolTable::NameFor(const Symbol symbol) const {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL(Symbol, program_id_, symbol);
+  auto it = symbol_to_name_.find(symbol);
+  if (it == symbol_to_name_.end()) {
+    return symbol.to_str();
+  }
+
+  return it->second;
+}
+
+Symbol SymbolTable::New(std::string prefix /* = "" */) {
+  if (prefix.empty()) {
+    prefix = "tint_symbol";
+  }
+  auto it = name_to_symbol_.find(prefix);
+  if (it == name_to_symbol_.end()) {
+    return Register(prefix);
+  }
+  std::string name;
+  size_t i = 1;
+  do {
+    name = prefix + "_" + std::to_string(i++);
+  } while (name_to_symbol_.count(name));
+  return Register(name);
+}
+
+}  // namespace tint
diff --git a/src/tint/symbol_table.h b/src/tint/symbol_table.h
new file mode 100644
index 0000000..214916e
--- /dev/null
+++ b/src/tint/symbol_table.h
@@ -0,0 +1,101 @@
+// Copyright 2020 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_SYMBOL_TABLE_H_
+#define SRC_TINT_SYMBOL_TABLE_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/symbol.h"
+
+namespace tint {
+
+/// Holds mappings from symbols to their associated string names
+class SymbolTable {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this symbol
+  /// table
+  explicit SymbolTable(tint::ProgramID program_id);
+  /// Copy constructor
+  SymbolTable(const SymbolTable&);
+  /// Move Constructor
+  SymbolTable(SymbolTable&&);
+  /// Destructor
+  ~SymbolTable();
+
+  /// Copy assignment
+  /// @param other the symbol table to copy
+  /// @returns the new symbol table
+  SymbolTable& operator=(const SymbolTable& other);
+  /// Move assignment
+  /// @param other the symbol table to move
+  /// @returns the symbol table
+  SymbolTable& operator=(SymbolTable&& other);
+
+  /// Registers a name into the symbol table, returning the Symbol.
+  /// @param name the name to register
+  /// @returns the symbol representing the given name
+  Symbol Register(const std::string& name);
+
+  /// Returns the symbol for the given `name`
+  /// @param name the name to lookup
+  /// @returns the symbol for the name or symbol::kInvalid if not found.
+  Symbol Get(const std::string& name) const;
+
+  /// Returns the name for the given symbol
+  /// @param symbol the symbol to retrieve the name for
+  /// @returns the symbol name or "" if not found
+  std::string NameFor(const Symbol symbol) const;
+
+  /// Returns a new unique symbol with the given name, possibly suffixed with a
+  /// unique number.
+  /// @param name the symbol name
+  /// @returns a new, unnamed symbol with the given name. If the name is already
+  /// taken then this will be suffixed with an underscore and a unique numerical
+  /// value
+  Symbol New(std::string name = "");
+
+  /// Foreach calls the callback function `F` for each symbol in the table.
+  /// @param callback must be a function or function-like object with the
+  /// signature: `void(Symbol, const std::string&)`
+  template <typename F>
+  void Foreach(F&& callback) const {
+    for (auto it : symbol_to_name_) {
+      callback(it.first, it.second);
+    }
+  }
+
+  /// @returns the identifier of the Program that owns this symbol table.
+  tint::ProgramID ProgramID() const { return program_id_; }
+
+ private:
+  // The value to be associated to the next registered symbol table entry.
+  uint32_t next_symbol_ = 1;
+
+  std::unordered_map<Symbol, std::string> symbol_to_name_;
+  std::unordered_map<std::string, Symbol> name_to_symbol_;
+  tint::ProgramID program_id_;
+};
+
+/// @param symbol_table the SymbolTable
+/// @returns the ProgramID that owns the given SymbolTable
+inline ProgramID ProgramIDOf(const SymbolTable& symbol_table) {
+  return symbol_table.ProgramID();
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_SYMBOL_TABLE_H_
diff --git a/src/tint/symbol_table_test.cc b/src/tint/symbol_table_test.cc
new file mode 100644
index 0000000..0905f8b
--- /dev/null
+++ b/src/tint/symbol_table_test.cc
@@ -0,0 +1,63 @@
+// Copyright 2020 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/symbol_table.h"
+
+#include "gtest/gtest-spi.h"
+
+namespace tint {
+namespace {
+
+using SymbolTableTest = testing::Test;
+
+TEST_F(SymbolTableTest, GeneratesSymbolForName) {
+  auto program_id = ProgramID::New();
+  SymbolTable s{program_id};
+  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
+  EXPECT_EQ(Symbol(2, program_id), s.Register("another_name"));
+}
+
+TEST_F(SymbolTableTest, DeduplicatesNames) {
+  auto program_id = ProgramID::New();
+  SymbolTable s{program_id};
+  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
+  EXPECT_EQ(Symbol(2, program_id), s.Register("another_name"));
+  EXPECT_EQ(Symbol(1, program_id), s.Register("name"));
+}
+
+TEST_F(SymbolTableTest, ReturnsNameForSymbol) {
+  auto program_id = ProgramID::New();
+  SymbolTable s{program_id};
+  auto sym = s.Register("name");
+  EXPECT_EQ("name", s.NameFor(sym));
+}
+
+TEST_F(SymbolTableTest, ReturnsBlankForMissingSymbol) {
+  auto program_id = ProgramID::New();
+  SymbolTable s{program_id};
+  EXPECT_EQ("$2", s.NameFor(Symbol(2, program_id)));
+}
+
+TEST_F(SymbolTableTest, AssertsForBlankString) {
+  EXPECT_FATAL_FAILURE(
+      {
+        auto program_id = ProgramID::New();
+        SymbolTable s{program_id};
+        s.Register("");
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/symbol_test.cc b/src/tint/symbol_test.cc
new file mode 100644
index 0000000..8e3f6c0
--- /dev/null
+++ b/src/tint/symbol_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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/symbol.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+using SymbolTest = testing::Test;
+
+TEST_F(SymbolTest, ToStr) {
+  Symbol sym(1, ProgramID::New());
+  EXPECT_EQ("$1", sym.to_str());
+}
+
+TEST_F(SymbolTest, CopyAssign) {
+  Symbol sym1(1, ProgramID::New());
+  Symbol sym2;
+
+  EXPECT_FALSE(sym2.IsValid());
+  sym2 = sym1;
+  EXPECT_TRUE(sym2.IsValid());
+  EXPECT_EQ(sym2, sym1);
+}
+
+TEST_F(SymbolTest, Comparison) {
+  auto program_id = ProgramID::New();
+  Symbol sym1(1, program_id);
+  Symbol sym2(2, program_id);
+  Symbol sym3(1, program_id);
+
+  EXPECT_TRUE(sym1 == sym3);
+  EXPECT_FALSE(sym1 == sym2);
+  EXPECT_FALSE(sym3 == sym2);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/test_main.cc b/src/tint/test_main.cc
new file mode 100644
index 0000000..d44f9c8
--- /dev/null
+++ b/src/tint/test_main.cc
@@ -0,0 +1,83 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/program.h"
+
+#if TINT_BUILD_SPV_READER
+#include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#endif
+
+#if TINT_BUILD_WGSL_WRITER
+#include "src/tint/writer/wgsl/generator.h"
+#endif
+
+namespace {
+
+void TintInternalCompilerErrorReporter(const tint::diag::List& diagnostics) {
+  FAIL() << diagnostics.str();
+}
+
+struct Flags {
+  bool spirv_reader_dump_converted = false;
+
+  bool parse(int argc, char** argv) {
+    bool errored = false;
+    for (int i = 1; i < argc && !errored; i++) {
+      auto match = [&](std::string name) { return name == argv[i]; };
+
+      if (match("--dump-spirv")) {
+        spirv_reader_dump_converted = true;
+      } else {
+        std::cout << "Unknown flag '" << argv[i] << "'" << std::endl;
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+}  // namespace
+
+// Entry point for tint unit tests
+int main(int argc, char** argv) {
+  testing::InitGoogleMock(&argc, argv);
+
+#if TINT_BUILD_WGSL_WRITER
+  tint::Program::printer = [](const tint::Program* program) {
+    auto result = tint::writer::wgsl::Generate(program, {});
+    if (!result.error.empty()) {
+      return "error: " + result.error;
+    }
+    return result.wgsl;
+  };
+#endif  // TINT_BUILD_WGSL_WRITER
+
+  Flags flags;
+  if (!flags.parse(argc, argv)) {
+    return -1;
+  }
+
+#if TINT_BUILD_SPV_READER
+  if (flags.spirv_reader_dump_converted) {
+    tint::reader::spirv::test::DumpSuccessfullyConvertedSpirv();
+  }
+#endif  // TINT_BUILD_SPV_READER
+
+  tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+
+  auto res = RUN_ALL_TESTS();
+
+  return res;
+}
diff --git a/src/tint/text/unicode.cc b/src/tint/text/unicode.cc
new file mode 100644
index 0000000..56e8292
--- /dev/null
+++ b/src/tint/text/unicode.cc
@@ -0,0 +1,506 @@
+// 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/text/unicode.h"
+
+#include <algorithm>
+
+namespace tint::text {
+namespace {
+
+struct CodePointRange {
+  uint32_t first;  // First code point in the interval
+  uint32_t last;   // Last code point in the interval (inclusive)
+};
+
+inline bool operator<(CodePoint code_point, CodePointRange range) {
+  return code_point < range.first;
+}
+inline bool operator<(CodePointRange range, CodePoint code_point) {
+  return range.last < code_point;
+}
+
+// Interval ranges of all code points in the Unicode 14 XID_Start set
+// This array needs to be in ascending order.
+constexpr CodePointRange kXIDStartRanges[] = {
+    {0x00041, 0x0005a}, {0x00061, 0x0007a}, {0x000aa, 0x000aa},
+    {0x000b5, 0x000b5}, {0x000ba, 0x000ba}, {0x000c0, 0x000d6},
+    {0x000d8, 0x000f6}, {0x000f8, 0x002c1}, {0x002c6, 0x002d1},
+    {0x002e0, 0x002e4}, {0x002ec, 0x002ec}, {0x002ee, 0x002ee},
+    {0x00370, 0x00374}, {0x00376, 0x00377}, {0x0037b, 0x0037d},
+    {0x0037f, 0x0037f}, {0x00386, 0x00386}, {0x00388, 0x0038a},
+    {0x0038c, 0x0038c}, {0x0038e, 0x003a1}, {0x003a3, 0x003f5},
+    {0x003f7, 0x00481}, {0x0048a, 0x0052f}, {0x00531, 0x00556},
+    {0x00559, 0x00559}, {0x00560, 0x00588}, {0x005d0, 0x005ea},
+    {0x005ef, 0x005f2}, {0x00620, 0x0064a}, {0x0066e, 0x0066f},
+    {0x00671, 0x006d3}, {0x006d5, 0x006d5}, {0x006e5, 0x006e6},
+    {0x006ee, 0x006ef}, {0x006fa, 0x006fc}, {0x006ff, 0x006ff},
+    {0x00710, 0x00710}, {0x00712, 0x0072f}, {0x0074d, 0x007a5},
+    {0x007b1, 0x007b1}, {0x007ca, 0x007ea}, {0x007f4, 0x007f5},
+    {0x007fa, 0x007fa}, {0x00800, 0x00815}, {0x0081a, 0x0081a},
+    {0x00824, 0x00824}, {0x00828, 0x00828}, {0x00840, 0x00858},
+    {0x00860, 0x0086a}, {0x00870, 0x00887}, {0x00889, 0x0088e},
+    {0x008a0, 0x008c9}, {0x00904, 0x00939}, {0x0093d, 0x0093d},
+    {0x00950, 0x00950}, {0x00958, 0x00961}, {0x00971, 0x00980},
+    {0x00985, 0x0098c}, {0x0098f, 0x00990}, {0x00993, 0x009a8},
+    {0x009aa, 0x009b0}, {0x009b2, 0x009b2}, {0x009b6, 0x009b9},
+    {0x009bd, 0x009bd}, {0x009ce, 0x009ce}, {0x009dc, 0x009dd},
+    {0x009df, 0x009e1}, {0x009f0, 0x009f1}, {0x009fc, 0x009fc},
+    {0x00a05, 0x00a0a}, {0x00a0f, 0x00a10}, {0x00a13, 0x00a28},
+    {0x00a2a, 0x00a30}, {0x00a32, 0x00a33}, {0x00a35, 0x00a36},
+    {0x00a38, 0x00a39}, {0x00a59, 0x00a5c}, {0x00a5e, 0x00a5e},
+    {0x00a72, 0x00a74}, {0x00a85, 0x00a8d}, {0x00a8f, 0x00a91},
+    {0x00a93, 0x00aa8}, {0x00aaa, 0x00ab0}, {0x00ab2, 0x00ab3},
+    {0x00ab5, 0x00ab9}, {0x00abd, 0x00abd}, {0x00ad0, 0x00ad0},
+    {0x00ae0, 0x00ae1}, {0x00af9, 0x00af9}, {0x00b05, 0x00b0c},
+    {0x00b0f, 0x00b10}, {0x00b13, 0x00b28}, {0x00b2a, 0x00b30},
+    {0x00b32, 0x00b33}, {0x00b35, 0x00b39}, {0x00b3d, 0x00b3d},
+    {0x00b5c, 0x00b5d}, {0x00b5f, 0x00b61}, {0x00b71, 0x00b71},
+    {0x00b83, 0x00b83}, {0x00b85, 0x00b8a}, {0x00b8e, 0x00b90},
+    {0x00b92, 0x00b95}, {0x00b99, 0x00b9a}, {0x00b9c, 0x00b9c},
+    {0x00b9e, 0x00b9f}, {0x00ba3, 0x00ba4}, {0x00ba8, 0x00baa},
+    {0x00bae, 0x00bb9}, {0x00bd0, 0x00bd0}, {0x00c05, 0x00c0c},
+    {0x00c0e, 0x00c10}, {0x00c12, 0x00c28}, {0x00c2a, 0x00c39},
+    {0x00c3d, 0x00c3d}, {0x00c58, 0x00c5a}, {0x00c5d, 0x00c5d},
+    {0x00c60, 0x00c61}, {0x00c80, 0x00c80}, {0x00c85, 0x00c8c},
+    {0x00c8e, 0x00c90}, {0x00c92, 0x00ca8}, {0x00caa, 0x00cb3},
+    {0x00cb5, 0x00cb9}, {0x00cbd, 0x00cbd}, {0x00cdd, 0x00cde},
+    {0x00ce0, 0x00ce1}, {0x00cf1, 0x00cf2}, {0x00d04, 0x00d0c},
+    {0x00d0e, 0x00d10}, {0x00d12, 0x00d3a}, {0x00d3d, 0x00d3d},
+    {0x00d4e, 0x00d4e}, {0x00d54, 0x00d56}, {0x00d5f, 0x00d61},
+    {0x00d7a, 0x00d7f}, {0x00d85, 0x00d96}, {0x00d9a, 0x00db1},
+    {0x00db3, 0x00dbb}, {0x00dbd, 0x00dbd}, {0x00dc0, 0x00dc6},
+    {0x00e01, 0x00e30}, {0x00e32, 0x00e32}, {0x00e40, 0x00e46},
+    {0x00e81, 0x00e82}, {0x00e84, 0x00e84}, {0x00e86, 0x00e8a},
+    {0x00e8c, 0x00ea3}, {0x00ea5, 0x00ea5}, {0x00ea7, 0x00eb0},
+    {0x00eb2, 0x00eb2}, {0x00ebd, 0x00ebd}, {0x00ec0, 0x00ec4},
+    {0x00ec6, 0x00ec6}, {0x00edc, 0x00edf}, {0x00f00, 0x00f00},
+    {0x00f40, 0x00f47}, {0x00f49, 0x00f6c}, {0x00f88, 0x00f8c},
+    {0x01000, 0x0102a}, {0x0103f, 0x0103f}, {0x01050, 0x01055},
+    {0x0105a, 0x0105d}, {0x01061, 0x01061}, {0x01065, 0x01066},
+    {0x0106e, 0x01070}, {0x01075, 0x01081}, {0x0108e, 0x0108e},
+    {0x010a0, 0x010c5}, {0x010c7, 0x010c7}, {0x010cd, 0x010cd},
+    {0x010d0, 0x010fa}, {0x010fc, 0x01248}, {0x0124a, 0x0124d},
+    {0x01250, 0x01256}, {0x01258, 0x01258}, {0x0125a, 0x0125d},
+    {0x01260, 0x01288}, {0x0128a, 0x0128d}, {0x01290, 0x012b0},
+    {0x012b2, 0x012b5}, {0x012b8, 0x012be}, {0x012c0, 0x012c0},
+    {0x012c2, 0x012c5}, {0x012c8, 0x012d6}, {0x012d8, 0x01310},
+    {0x01312, 0x01315}, {0x01318, 0x0135a}, {0x01380, 0x0138f},
+    {0x013a0, 0x013f5}, {0x013f8, 0x013fd}, {0x01401, 0x0166c},
+    {0x0166f, 0x0167f}, {0x01681, 0x0169a}, {0x016a0, 0x016ea},
+    {0x016ee, 0x016f8}, {0x01700, 0x01711}, {0x0171f, 0x01731},
+    {0x01740, 0x01751}, {0x01760, 0x0176c}, {0x0176e, 0x01770},
+    {0x01780, 0x017b3}, {0x017d7, 0x017d7}, {0x017dc, 0x017dc},
+    {0x01820, 0x01878}, {0x01880, 0x018a8}, {0x018aa, 0x018aa},
+    {0x018b0, 0x018f5}, {0x01900, 0x0191e}, {0x01950, 0x0196d},
+    {0x01970, 0x01974}, {0x01980, 0x019ab}, {0x019b0, 0x019c9},
+    {0x01a00, 0x01a16}, {0x01a20, 0x01a54}, {0x01aa7, 0x01aa7},
+    {0x01b05, 0x01b33}, {0x01b45, 0x01b4c}, {0x01b83, 0x01ba0},
+    {0x01bae, 0x01baf}, {0x01bba, 0x01be5}, {0x01c00, 0x01c23},
+    {0x01c4d, 0x01c4f}, {0x01c5a, 0x01c7d}, {0x01c80, 0x01c88},
+    {0x01c90, 0x01cba}, {0x01cbd, 0x01cbf}, {0x01ce9, 0x01cec},
+    {0x01cee, 0x01cf3}, {0x01cf5, 0x01cf6}, {0x01cfa, 0x01cfa},
+    {0x01d00, 0x01dbf}, {0x01e00, 0x01f15}, {0x01f18, 0x01f1d},
+    {0x01f20, 0x01f45}, {0x01f48, 0x01f4d}, {0x01f50, 0x01f57},
+    {0x01f59, 0x01f59}, {0x01f5b, 0x01f5b}, {0x01f5d, 0x01f5d},
+    {0x01f5f, 0x01f7d}, {0x01f80, 0x01fb4}, {0x01fb6, 0x01fbc},
+    {0x01fbe, 0x01fbe}, {0x01fc2, 0x01fc4}, {0x01fc6, 0x01fcc},
+    {0x01fd0, 0x01fd3}, {0x01fd6, 0x01fdb}, {0x01fe0, 0x01fec},
+    {0x01ff2, 0x01ff4}, {0x01ff6, 0x01ffc}, {0x02071, 0x02071},
+    {0x0207f, 0x0207f}, {0x02090, 0x0209c}, {0x02102, 0x02102},
+    {0x02107, 0x02107}, {0x0210a, 0x02113}, {0x02115, 0x02115},
+    {0x02118, 0x0211d}, {0x02124, 0x02124}, {0x02126, 0x02126},
+    {0x02128, 0x02128}, {0x0212a, 0x02139}, {0x0213c, 0x0213f},
+    {0x02145, 0x02149}, {0x0214e, 0x0214e}, {0x02160, 0x02188},
+    {0x02c00, 0x02ce4}, {0x02ceb, 0x02cee}, {0x02cf2, 0x02cf3},
+    {0x02d00, 0x02d25}, {0x02d27, 0x02d27}, {0x02d2d, 0x02d2d},
+    {0x02d30, 0x02d67}, {0x02d6f, 0x02d6f}, {0x02d80, 0x02d96},
+    {0x02da0, 0x02da6}, {0x02da8, 0x02dae}, {0x02db0, 0x02db6},
+    {0x02db8, 0x02dbe}, {0x02dc0, 0x02dc6}, {0x02dc8, 0x02dce},
+    {0x02dd0, 0x02dd6}, {0x02dd8, 0x02dde}, {0x03005, 0x03007},
+    {0x03021, 0x03029}, {0x03031, 0x03035}, {0x03038, 0x0303c},
+    {0x03041, 0x03096}, {0x0309d, 0x0309f}, {0x030a1, 0x030fa},
+    {0x030fc, 0x030ff}, {0x03105, 0x0312f}, {0x03131, 0x0318e},
+    {0x031a0, 0x031bf}, {0x031f0, 0x031ff}, {0x03400, 0x04dbf},
+    {0x04e00, 0x0a48c}, {0x0a4d0, 0x0a4fd}, {0x0a500, 0x0a60c},
+    {0x0a610, 0x0a61f}, {0x0a62a, 0x0a62b}, {0x0a640, 0x0a66e},
+    {0x0a67f, 0x0a69d}, {0x0a6a0, 0x0a6ef}, {0x0a717, 0x0a71f},
+    {0x0a722, 0x0a788}, {0x0a78b, 0x0a7ca}, {0x0a7d0, 0x0a7d1},
+    {0x0a7d3, 0x0a7d3}, {0x0a7d5, 0x0a7d9}, {0x0a7f2, 0x0a801},
+    {0x0a803, 0x0a805}, {0x0a807, 0x0a80a}, {0x0a80c, 0x0a822},
+    {0x0a840, 0x0a873}, {0x0a882, 0x0a8b3}, {0x0a8f2, 0x0a8f7},
+    {0x0a8fb, 0x0a8fb}, {0x0a8fd, 0x0a8fe}, {0x0a90a, 0x0a925},
+    {0x0a930, 0x0a946}, {0x0a960, 0x0a97c}, {0x0a984, 0x0a9b2},
+    {0x0a9cf, 0x0a9cf}, {0x0a9e0, 0x0a9e4}, {0x0a9e6, 0x0a9ef},
+    {0x0a9fa, 0x0a9fe}, {0x0aa00, 0x0aa28}, {0x0aa40, 0x0aa42},
+    {0x0aa44, 0x0aa4b}, {0x0aa60, 0x0aa76}, {0x0aa7a, 0x0aa7a},
+    {0x0aa7e, 0x0aaaf}, {0x0aab1, 0x0aab1}, {0x0aab5, 0x0aab6},
+    {0x0aab9, 0x0aabd}, {0x0aac0, 0x0aac0}, {0x0aac2, 0x0aac2},
+    {0x0aadb, 0x0aadd}, {0x0aae0, 0x0aaea}, {0x0aaf2, 0x0aaf4},
+    {0x0ab01, 0x0ab06}, {0x0ab09, 0x0ab0e}, {0x0ab11, 0x0ab16},
+    {0x0ab20, 0x0ab26}, {0x0ab28, 0x0ab2e}, {0x0ab30, 0x0ab5a},
+    {0x0ab5c, 0x0ab69}, {0x0ab70, 0x0abe2}, {0x0ac00, 0x0d7a3},
+    {0x0d7b0, 0x0d7c6}, {0x0d7cb, 0x0d7fb}, {0x0f900, 0x0fa6d},
+    {0x0fa70, 0x0fad9}, {0x0fb00, 0x0fb06}, {0x0fb13, 0x0fb17},
+    {0x0fb1d, 0x0fb1d}, {0x0fb1f, 0x0fb28}, {0x0fb2a, 0x0fb36},
+    {0x0fb38, 0x0fb3c}, {0x0fb3e, 0x0fb3e}, {0x0fb40, 0x0fb41},
+    {0x0fb43, 0x0fb44}, {0x0fb46, 0x0fbb1}, {0x0fbd3, 0x0fc5d},
+    {0x0fc64, 0x0fd3d}, {0x0fd50, 0x0fd8f}, {0x0fd92, 0x0fdc7},
+    {0x0fdf0, 0x0fdf9}, {0x0fe71, 0x0fe71}, {0x0fe73, 0x0fe73},
+    {0x0fe77, 0x0fe77}, {0x0fe79, 0x0fe79}, {0x0fe7b, 0x0fe7b},
+    {0x0fe7d, 0x0fe7d}, {0x0fe7f, 0x0fefc}, {0x0ff21, 0x0ff3a},
+    {0x0ff41, 0x0ff5a}, {0x0ff66, 0x0ff9d}, {0x0ffa0, 0x0ffbe},
+    {0x0ffc2, 0x0ffc7}, {0x0ffca, 0x0ffcf}, {0x0ffd2, 0x0ffd7},
+    {0x0ffda, 0x0ffdc}, {0x10000, 0x1000b}, {0x1000d, 0x10026},
+    {0x10028, 0x1003a}, {0x1003c, 0x1003d}, {0x1003f, 0x1004d},
+    {0x10050, 0x1005d}, {0x10080, 0x100fa}, {0x10140, 0x10174},
+    {0x10280, 0x1029c}, {0x102a0, 0x102d0}, {0x10300, 0x1031f},
+    {0x1032d, 0x1034a}, {0x10350, 0x10375}, {0x10380, 0x1039d},
+    {0x103a0, 0x103c3}, {0x103c8, 0x103cf}, {0x103d1, 0x103d5},
+    {0x10400, 0x1049d}, {0x104b0, 0x104d3}, {0x104d8, 0x104fb},
+    {0x10500, 0x10527}, {0x10530, 0x10563}, {0x10570, 0x1057a},
+    {0x1057c, 0x1058a}, {0x1058c, 0x10592}, {0x10594, 0x10595},
+    {0x10597, 0x105a1}, {0x105a3, 0x105b1}, {0x105b3, 0x105b9},
+    {0x105bb, 0x105bc}, {0x10600, 0x10736}, {0x10740, 0x10755},
+    {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107b0},
+    {0x107b2, 0x107ba}, {0x10800, 0x10805}, {0x10808, 0x10808},
+    {0x1080a, 0x10835}, {0x10837, 0x10838}, {0x1083c, 0x1083c},
+    {0x1083f, 0x10855}, {0x10860, 0x10876}, {0x10880, 0x1089e},
+    {0x108e0, 0x108f2}, {0x108f4, 0x108f5}, {0x10900, 0x10915},
+    {0x10920, 0x10939}, {0x10980, 0x109b7}, {0x109be, 0x109bf},
+    {0x10a00, 0x10a00}, {0x10a10, 0x10a13}, {0x10a15, 0x10a17},
+    {0x10a19, 0x10a35}, {0x10a60, 0x10a7c}, {0x10a80, 0x10a9c},
+    {0x10ac0, 0x10ac7}, {0x10ac9, 0x10ae4}, {0x10b00, 0x10b35},
+    {0x10b40, 0x10b55}, {0x10b60, 0x10b72}, {0x10b80, 0x10b91},
+    {0x10c00, 0x10c48}, {0x10c80, 0x10cb2}, {0x10cc0, 0x10cf2},
+    {0x10d00, 0x10d23}, {0x10e80, 0x10ea9}, {0x10eb0, 0x10eb1},
+    {0x10f00, 0x10f1c}, {0x10f27, 0x10f27}, {0x10f30, 0x10f45},
+    {0x10f70, 0x10f81}, {0x10fb0, 0x10fc4}, {0x10fe0, 0x10ff6},
+    {0x11003, 0x11037}, {0x11071, 0x11072}, {0x11075, 0x11075},
+    {0x11083, 0x110af}, {0x110d0, 0x110e8}, {0x11103, 0x11126},
+    {0x11144, 0x11144}, {0x11147, 0x11147}, {0x11150, 0x11172},
+    {0x11176, 0x11176}, {0x11183, 0x111b2}, {0x111c1, 0x111c4},
+    {0x111da, 0x111da}, {0x111dc, 0x111dc}, {0x11200, 0x11211},
+    {0x11213, 0x1122b}, {0x11280, 0x11286}, {0x11288, 0x11288},
+    {0x1128a, 0x1128d}, {0x1128f, 0x1129d}, {0x1129f, 0x112a8},
+    {0x112b0, 0x112de}, {0x11305, 0x1130c}, {0x1130f, 0x11310},
+    {0x11313, 0x11328}, {0x1132a, 0x11330}, {0x11332, 0x11333},
+    {0x11335, 0x11339}, {0x1133d, 0x1133d}, {0x11350, 0x11350},
+    {0x1135d, 0x11361}, {0x11400, 0x11434}, {0x11447, 0x1144a},
+    {0x1145f, 0x11461}, {0x11480, 0x114af}, {0x114c4, 0x114c5},
+    {0x114c7, 0x114c7}, {0x11580, 0x115ae}, {0x115d8, 0x115db},
+    {0x11600, 0x1162f}, {0x11644, 0x11644}, {0x11680, 0x116aa},
+    {0x116b8, 0x116b8}, {0x11700, 0x1171a}, {0x11740, 0x11746},
+    {0x11800, 0x1182b}, {0x118a0, 0x118df}, {0x118ff, 0x11906},
+    {0x11909, 0x11909}, {0x1190c, 0x11913}, {0x11915, 0x11916},
+    {0x11918, 0x1192f}, {0x1193f, 0x1193f}, {0x11941, 0x11941},
+    {0x119a0, 0x119a7}, {0x119aa, 0x119d0}, {0x119e1, 0x119e1},
+    {0x119e3, 0x119e3}, {0x11a00, 0x11a00}, {0x11a0b, 0x11a32},
+    {0x11a3a, 0x11a3a}, {0x11a50, 0x11a50}, {0x11a5c, 0x11a89},
+    {0x11a9d, 0x11a9d}, {0x11ab0, 0x11af8}, {0x11c00, 0x11c08},
+    {0x11c0a, 0x11c2e}, {0x11c40, 0x11c40}, {0x11c72, 0x11c8f},
+    {0x11d00, 0x11d06}, {0x11d08, 0x11d09}, {0x11d0b, 0x11d30},
+    {0x11d46, 0x11d46}, {0x11d60, 0x11d65}, {0x11d67, 0x11d68},
+    {0x11d6a, 0x11d89}, {0x11d98, 0x11d98}, {0x11ee0, 0x11ef2},
+    {0x11fb0, 0x11fb0}, {0x12000, 0x12399}, {0x12400, 0x1246e},
+    {0x12480, 0x12543}, {0x12f90, 0x12ff0}, {0x13000, 0x1342e},
+    {0x14400, 0x14646}, {0x16800, 0x16a38}, {0x16a40, 0x16a5e},
+    {0x16a70, 0x16abe}, {0x16ad0, 0x16aed}, {0x16b00, 0x16b2f},
+    {0x16b40, 0x16b43}, {0x16b63, 0x16b77}, {0x16b7d, 0x16b8f},
+    {0x16e40, 0x16e7f}, {0x16f00, 0x16f4a}, {0x16f50, 0x16f50},
+    {0x16f93, 0x16f9f}, {0x16fe0, 0x16fe1}, {0x16fe3, 0x16fe3},
+    {0x17000, 0x187f7}, {0x18800, 0x18cd5}, {0x18d00, 0x18d08},
+    {0x1aff0, 0x1aff3}, {0x1aff5, 0x1affb}, {0x1affd, 0x1affe},
+    {0x1b000, 0x1b122}, {0x1b150, 0x1b152}, {0x1b164, 0x1b167},
+    {0x1b170, 0x1b2fb}, {0x1bc00, 0x1bc6a}, {0x1bc70, 0x1bc7c},
+    {0x1bc80, 0x1bc88}, {0x1bc90, 0x1bc99}, {0x1d400, 0x1d454},
+    {0x1d456, 0x1d49c}, {0x1d49e, 0x1d49f}, {0x1d4a2, 0x1d4a2},
+    {0x1d4a5, 0x1d4a6}, {0x1d4a9, 0x1d4ac}, {0x1d4ae, 0x1d4b9},
+    {0x1d4bb, 0x1d4bb}, {0x1d4bd, 0x1d4c3}, {0x1d4c5, 0x1d505},
+    {0x1d507, 0x1d50a}, {0x1d50d, 0x1d514}, {0x1d516, 0x1d51c},
+    {0x1d51e, 0x1d539}, {0x1d53b, 0x1d53e}, {0x1d540, 0x1d544},
+    {0x1d546, 0x1d546}, {0x1d54a, 0x1d550}, {0x1d552, 0x1d6a5},
+    {0x1d6a8, 0x1d6c0}, {0x1d6c2, 0x1d6da}, {0x1d6dc, 0x1d6fa},
+    {0x1d6fc, 0x1d714}, {0x1d716, 0x1d734}, {0x1d736, 0x1d74e},
+    {0x1d750, 0x1d76e}, {0x1d770, 0x1d788}, {0x1d78a, 0x1d7a8},
+    {0x1d7aa, 0x1d7c2}, {0x1d7c4, 0x1d7cb}, {0x1df00, 0x1df1e},
+    {0x1e100, 0x1e12c}, {0x1e137, 0x1e13d}, {0x1e14e, 0x1e14e},
+    {0x1e290, 0x1e2ad}, {0x1e2c0, 0x1e2eb}, {0x1e7e0, 0x1e7e6},
+    {0x1e7e8, 0x1e7eb}, {0x1e7ed, 0x1e7ee}, {0x1e7f0, 0x1e7fe},
+    {0x1e800, 0x1e8c4}, {0x1e900, 0x1e943}, {0x1e94b, 0x1e94b},
+    {0x1ee00, 0x1ee03}, {0x1ee05, 0x1ee1f}, {0x1ee21, 0x1ee22},
+    {0x1ee24, 0x1ee24}, {0x1ee27, 0x1ee27}, {0x1ee29, 0x1ee32},
+    {0x1ee34, 0x1ee37}, {0x1ee39, 0x1ee39}, {0x1ee3b, 0x1ee3b},
+    {0x1ee42, 0x1ee42}, {0x1ee47, 0x1ee47}, {0x1ee49, 0x1ee49},
+    {0x1ee4b, 0x1ee4b}, {0x1ee4d, 0x1ee4f}, {0x1ee51, 0x1ee52},
+    {0x1ee54, 0x1ee54}, {0x1ee57, 0x1ee57}, {0x1ee59, 0x1ee59},
+    {0x1ee5b, 0x1ee5b}, {0x1ee5d, 0x1ee5d}, {0x1ee5f, 0x1ee5f},
+    {0x1ee61, 0x1ee62}, {0x1ee64, 0x1ee64}, {0x1ee67, 0x1ee6a},
+    {0x1ee6c, 0x1ee72}, {0x1ee74, 0x1ee77}, {0x1ee79, 0x1ee7c},
+    {0x1ee7e, 0x1ee7e}, {0x1ee80, 0x1ee89}, {0x1ee8b, 0x1ee9b},
+    {0x1eea1, 0x1eea3}, {0x1eea5, 0x1eea9}, {0x1eeab, 0x1eebb},
+    {0x20000, 0x2a6df}, {0x2a700, 0x2b738}, {0x2b740, 0x2b81d},
+    {0x2b820, 0x2cea1}, {0x2ceb0, 0x2ebe0}, {0x2f800, 0x2fa1d},
+    {0x30000, 0x3134a},
+};
+
+// Number of ranges in kXIDStartRanges
+constexpr size_t kNumXIDStartRanges =
+    sizeof(kXIDStartRanges) / sizeof(kXIDStartRanges[0]);
+
+// The additional code point interval ranges for the Unicode 14 XID_Continue
+// set. This extends the values in kXIDStartRanges.
+// This array needs to be in ascending order.
+constexpr CodePointRange kXIDContinueRanges[] = {
+    {0x00030, 0x00039}, {0x0005f, 0x0005f}, {0x000b7, 0x000b7},
+    {0x00300, 0x0036f}, {0x00387, 0x00387}, {0x00483, 0x00487},
+    {0x00591, 0x005bd}, {0x005bf, 0x005bf}, {0x005c1, 0x005c2},
+    {0x005c4, 0x005c5}, {0x005c7, 0x005c7}, {0x00610, 0x0061a},
+    {0x0064b, 0x00669}, {0x00670, 0x00670}, {0x006d6, 0x006dc},
+    {0x006df, 0x006e4}, {0x006e7, 0x006e8}, {0x006ea, 0x006ed},
+    {0x006f0, 0x006f9}, {0x00711, 0x00711}, {0x00730, 0x0074a},
+    {0x007a6, 0x007b0}, {0x007c0, 0x007c9}, {0x007eb, 0x007f3},
+    {0x007fd, 0x007fd}, {0x00816, 0x00819}, {0x0081b, 0x00823},
+    {0x00825, 0x00827}, {0x00829, 0x0082d}, {0x00859, 0x0085b},
+    {0x00898, 0x0089f}, {0x008ca, 0x008e1}, {0x008e3, 0x00903},
+    {0x0093a, 0x0093c}, {0x0093e, 0x0094f}, {0x00951, 0x00957},
+    {0x00962, 0x00963}, {0x00966, 0x0096f}, {0x00981, 0x00983},
+    {0x009bc, 0x009bc}, {0x009be, 0x009c4}, {0x009c7, 0x009c8},
+    {0x009cb, 0x009cd}, {0x009d7, 0x009d7}, {0x009e2, 0x009e3},
+    {0x009e6, 0x009ef}, {0x009fe, 0x009fe}, {0x00a01, 0x00a03},
+    {0x00a3c, 0x00a3c}, {0x00a3e, 0x00a42}, {0x00a47, 0x00a48},
+    {0x00a4b, 0x00a4d}, {0x00a51, 0x00a51}, {0x00a66, 0x00a71},
+    {0x00a75, 0x00a75}, {0x00a81, 0x00a83}, {0x00abc, 0x00abc},
+    {0x00abe, 0x00ac5}, {0x00ac7, 0x00ac9}, {0x00acb, 0x00acd},
+    {0x00ae2, 0x00ae3}, {0x00ae6, 0x00aef}, {0x00afa, 0x00aff},
+    {0x00b01, 0x00b03}, {0x00b3c, 0x00b3c}, {0x00b3e, 0x00b44},
+    {0x00b47, 0x00b48}, {0x00b4b, 0x00b4d}, {0x00b55, 0x00b57},
+    {0x00b62, 0x00b63}, {0x00b66, 0x00b6f}, {0x00b82, 0x00b82},
+    {0x00bbe, 0x00bc2}, {0x00bc6, 0x00bc8}, {0x00bca, 0x00bcd},
+    {0x00bd7, 0x00bd7}, {0x00be6, 0x00bef}, {0x00c00, 0x00c04},
+    {0x00c3c, 0x00c3c}, {0x00c3e, 0x00c44}, {0x00c46, 0x00c48},
+    {0x00c4a, 0x00c4d}, {0x00c55, 0x00c56}, {0x00c62, 0x00c63},
+    {0x00c66, 0x00c6f}, {0x00c81, 0x00c83}, {0x00cbc, 0x00cbc},
+    {0x00cbe, 0x00cc4}, {0x00cc6, 0x00cc8}, {0x00cca, 0x00ccd},
+    {0x00cd5, 0x00cd6}, {0x00ce2, 0x00ce3}, {0x00ce6, 0x00cef},
+    {0x00d00, 0x00d03}, {0x00d3b, 0x00d3c}, {0x00d3e, 0x00d44},
+    {0x00d46, 0x00d48}, {0x00d4a, 0x00d4d}, {0x00d57, 0x00d57},
+    {0x00d62, 0x00d63}, {0x00d66, 0x00d6f}, {0x00d81, 0x00d83},
+    {0x00dca, 0x00dca}, {0x00dcf, 0x00dd4}, {0x00dd6, 0x00dd6},
+    {0x00dd8, 0x00ddf}, {0x00de6, 0x00def}, {0x00df2, 0x00df3},
+    {0x00e31, 0x00e31}, {0x00e33, 0x00e3a}, {0x00e47, 0x00e4e},
+    {0x00e50, 0x00e59}, {0x00eb1, 0x00eb1}, {0x00eb3, 0x00ebc},
+    {0x00ec8, 0x00ecd}, {0x00ed0, 0x00ed9}, {0x00f18, 0x00f19},
+    {0x00f20, 0x00f29}, {0x00f35, 0x00f35}, {0x00f37, 0x00f37},
+    {0x00f39, 0x00f39}, {0x00f3e, 0x00f3f}, {0x00f71, 0x00f84},
+    {0x00f86, 0x00f87}, {0x00f8d, 0x00f97}, {0x00f99, 0x00fbc},
+    {0x00fc6, 0x00fc6}, {0x0102b, 0x0103e}, {0x01040, 0x01049},
+    {0x01056, 0x01059}, {0x0105e, 0x01060}, {0x01062, 0x01064},
+    {0x01067, 0x0106d}, {0x01071, 0x01074}, {0x01082, 0x0108d},
+    {0x0108f, 0x0109d}, {0x0135d, 0x0135f}, {0x01369, 0x01371},
+    {0x01712, 0x01715}, {0x01732, 0x01734}, {0x01752, 0x01753},
+    {0x01772, 0x01773}, {0x017b4, 0x017d3}, {0x017dd, 0x017dd},
+    {0x017e0, 0x017e9}, {0x0180b, 0x0180d}, {0x0180f, 0x01819},
+    {0x018a9, 0x018a9}, {0x01920, 0x0192b}, {0x01930, 0x0193b},
+    {0x01946, 0x0194f}, {0x019d0, 0x019da}, {0x01a17, 0x01a1b},
+    {0x01a55, 0x01a5e}, {0x01a60, 0x01a7c}, {0x01a7f, 0x01a89},
+    {0x01a90, 0x01a99}, {0x01ab0, 0x01abd}, {0x01abf, 0x01ace},
+    {0x01b00, 0x01b04}, {0x01b34, 0x01b44}, {0x01b50, 0x01b59},
+    {0x01b6b, 0x01b73}, {0x01b80, 0x01b82}, {0x01ba1, 0x01bad},
+    {0x01bb0, 0x01bb9}, {0x01be6, 0x01bf3}, {0x01c24, 0x01c37},
+    {0x01c40, 0x01c49}, {0x01c50, 0x01c59}, {0x01cd0, 0x01cd2},
+    {0x01cd4, 0x01ce8}, {0x01ced, 0x01ced}, {0x01cf4, 0x01cf4},
+    {0x01cf7, 0x01cf9}, {0x01dc0, 0x01dff}, {0x0203f, 0x02040},
+    {0x02054, 0x02054}, {0x020d0, 0x020dc}, {0x020e1, 0x020e1},
+    {0x020e5, 0x020f0}, {0x02cef, 0x02cf1}, {0x02d7f, 0x02d7f},
+    {0x02de0, 0x02dff}, {0x0302a, 0x0302f}, {0x03099, 0x0309a},
+    {0x0a620, 0x0a629}, {0x0a66f, 0x0a66f}, {0x0a674, 0x0a67d},
+    {0x0a69e, 0x0a69f}, {0x0a6f0, 0x0a6f1}, {0x0a802, 0x0a802},
+    {0x0a806, 0x0a806}, {0x0a80b, 0x0a80b}, {0x0a823, 0x0a827},
+    {0x0a82c, 0x0a82c}, {0x0a880, 0x0a881}, {0x0a8b4, 0x0a8c5},
+    {0x0a8d0, 0x0a8d9}, {0x0a8e0, 0x0a8f1}, {0x0a8ff, 0x0a909},
+    {0x0a926, 0x0a92d}, {0x0a947, 0x0a953}, {0x0a980, 0x0a983},
+    {0x0a9b3, 0x0a9c0}, {0x0a9d0, 0x0a9d9}, {0x0a9e5, 0x0a9e5},
+    {0x0a9f0, 0x0a9f9}, {0x0aa29, 0x0aa36}, {0x0aa43, 0x0aa43},
+    {0x0aa4c, 0x0aa4d}, {0x0aa50, 0x0aa59}, {0x0aa7b, 0x0aa7d},
+    {0x0aab0, 0x0aab0}, {0x0aab2, 0x0aab4}, {0x0aab7, 0x0aab8},
+    {0x0aabe, 0x0aabf}, {0x0aac1, 0x0aac1}, {0x0aaeb, 0x0aaef},
+    {0x0aaf5, 0x0aaf6}, {0x0abe3, 0x0abea}, {0x0abec, 0x0abed},
+    {0x0abf0, 0x0abf9}, {0x0fb1e, 0x0fb1e}, {0x0fe00, 0x0fe0f},
+    {0x0fe20, 0x0fe2f}, {0x0fe33, 0x0fe34}, {0x0fe4d, 0x0fe4f},
+    {0x0ff10, 0x0ff19}, {0x0ff3f, 0x0ff3f}, {0x0ff9e, 0x0ff9f},
+    {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, {0x10376, 0x1037a},
+    {0x104a0, 0x104a9}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
+    {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
+    {0x10ae5, 0x10ae6}, {0x10d24, 0x10d27}, {0x10d30, 0x10d39},
+    {0x10eab, 0x10eac}, {0x10f46, 0x10f50}, {0x10f82, 0x10f85},
+    {0x11000, 0x11002}, {0x11038, 0x11046}, {0x11066, 0x11070},
+    {0x11073, 0x11074}, {0x1107f, 0x11082}, {0x110b0, 0x110ba},
+    {0x110c2, 0x110c2}, {0x110f0, 0x110f9}, {0x11100, 0x11102},
+    {0x11127, 0x11134}, {0x11136, 0x1113f}, {0x11145, 0x11146},
+    {0x11173, 0x11173}, {0x11180, 0x11182}, {0x111b3, 0x111c0},
+    {0x111c9, 0x111cc}, {0x111ce, 0x111d9}, {0x1122c, 0x11237},
+    {0x1123e, 0x1123e}, {0x112df, 0x112ea}, {0x112f0, 0x112f9},
+    {0x11300, 0x11303}, {0x1133b, 0x1133c}, {0x1133e, 0x11344},
+    {0x11347, 0x11348}, {0x1134b, 0x1134d}, {0x11357, 0x11357},
+    {0x11362, 0x11363}, {0x11366, 0x1136c}, {0x11370, 0x11374},
+    {0x11435, 0x11446}, {0x11450, 0x11459}, {0x1145e, 0x1145e},
+    {0x114b0, 0x114c3}, {0x114d0, 0x114d9}, {0x115af, 0x115b5},
+    {0x115b8, 0x115c0}, {0x115dc, 0x115dd}, {0x11630, 0x11640},
+    {0x11650, 0x11659}, {0x116ab, 0x116b7}, {0x116c0, 0x116c9},
+    {0x1171d, 0x1172b}, {0x11730, 0x11739}, {0x1182c, 0x1183a},
+    {0x118e0, 0x118e9}, {0x11930, 0x11935}, {0x11937, 0x11938},
+    {0x1193b, 0x1193e}, {0x11940, 0x11940}, {0x11942, 0x11943},
+    {0x11950, 0x11959}, {0x119d1, 0x119d7}, {0x119da, 0x119e0},
+    {0x119e4, 0x119e4}, {0x11a01, 0x11a0a}, {0x11a33, 0x11a39},
+    {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a5b},
+    {0x11a8a, 0x11a99}, {0x11c2f, 0x11c36}, {0x11c38, 0x11c3f},
+    {0x11c50, 0x11c59}, {0x11c92, 0x11ca7}, {0x11ca9, 0x11cb6},
+    {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, {0x11d3c, 0x11d3d},
+    {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, {0x11d50, 0x11d59},
+    {0x11d8a, 0x11d8e}, {0x11d90, 0x11d91}, {0x11d93, 0x11d97},
+    {0x11da0, 0x11da9}, {0x11ef3, 0x11ef6}, {0x16a60, 0x16a69},
+    {0x16ac0, 0x16ac9}, {0x16af0, 0x16af4}, {0x16b30, 0x16b36},
+    {0x16b50, 0x16b59}, {0x16f4f, 0x16f4f}, {0x16f51, 0x16f87},
+    {0x16f8f, 0x16f92}, {0x16fe4, 0x16fe4}, {0x16ff0, 0x16ff1},
+    {0x1bc9d, 0x1bc9e}, {0x1cf00, 0x1cf2d}, {0x1cf30, 0x1cf46},
+    {0x1d165, 0x1d169}, {0x1d16d, 0x1d172}, {0x1d17b, 0x1d182},
+    {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244},
+    {0x1d7ce, 0x1d7ff}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c},
+    {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
+    {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018},
+    {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a},
+    {0x1e130, 0x1e136}, {0x1e140, 0x1e149}, {0x1e2ae, 0x1e2ae},
+    {0x1e2ec, 0x1e2f9}, {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a},
+    {0x1e950, 0x1e959}, {0x1fbf0, 0x1fbf9}, {0xe0100, 0xe01ef},
+};
+
+// Number of ranges in kXIDContinueRanges
+constexpr size_t kNumXIDContinueRanges =
+    sizeof(kXIDContinueRanges) / sizeof(kXIDContinueRanges[0]);
+
+}  // namespace
+
+bool CodePoint::IsXIDStart() const {
+  return std::binary_search(kXIDStartRanges,
+                            kXIDStartRanges + kNumXIDStartRanges, *this);
+}
+
+bool CodePoint::IsXIDContinue() const {
+  return IsXIDStart() ||
+         std::binary_search(kXIDContinueRanges,
+                            kXIDContinueRanges + kNumXIDContinueRanges, *this);
+}
+
+std::ostream& operator<<(std::ostream& out, CodePoint code_point) {
+  if (code_point < 0x7f) {
+    // See https://en.cppreference.com/w/cpp/language/escape
+    switch (code_point) {
+      case '\a':
+        return out << R"('\a')";
+      case '\b':
+        return out << R"('\b')";
+      case '\f':
+        return out << R"('\f')";
+      case '\n':
+        return out << R"('\n')";
+      case '\r':
+        return out << R"('\r')";
+      case '\t':
+        return out << R"('\t')";
+      case '\v':
+        return out << R"('\v')";
+    }
+    return out << "'" << static_cast<char>(code_point) << "'";
+  }
+  return out << "'U+" << std::hex << code_point.value << "'";
+}
+
+namespace utf8 {
+
+std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len) {
+  if (len < 1) {
+    return {};
+  }
+
+  // Lookup table for the first byte of a UTF-8 sequence.
+  // 0 indicates an invalid length.
+  // Note that bit encodings that can fit in a smaller number of bytes are
+  // invalid (e.g. 0xc0). Code points that exceed the unicode maximum of
+  // 0x10FFFF are also invalid (0xf5+).
+  // See: https://en.wikipedia.org/wiki/UTF-8#Encoding and
+  //      https://datatracker.ietf.org/doc/html/rfc3629#section-3
+  static constexpr uint8_t kSequenceLength[256] = {
+      //         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
+      /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x10 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x20 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      /* 0xc0 */ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+      /* 0xd0 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+      /* 0xe0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+      /* 0xf0 */ 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  };
+
+  uint8_t n = kSequenceLength[ptr[0]];
+  if (n > len) {
+    return {};
+  }
+
+  CodePoint c;
+
+  switch (n) {
+    // Note: n=0 (invalid) is correctly handled without a case.
+    case 1:
+      c = CodePoint{ptr[0]};
+      break;
+    case 2:
+      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00011111) << 6) |
+                    (static_cast<uint32_t>(ptr[1] & 0b00111111))};
+      break;
+    case 3:
+      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00001111) << 12) |
+                    (static_cast<uint32_t>(ptr[1] & 0b00111111) << 6) |
+                    (static_cast<uint32_t>(ptr[2] & 0b00111111))};
+      break;
+    case 4:
+      c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00000111) << 18) |
+                    (static_cast<uint32_t>(ptr[1] & 0b00111111) << 12) |
+                    (static_cast<uint32_t>(ptr[2] & 0b00111111) << 6) |
+                    (static_cast<uint32_t>(ptr[3] & 0b00111111))};
+      break;
+  }
+  return {c, n};
+}
+
+bool IsASCII(std::string_view str) {
+  for (auto c : str) {
+    if (c & 0x80) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace utf8
+
+}  // namespace tint::text
diff --git a/src/tint/text/unicode.h b/src/tint/text/unicode.h
new file mode 100644
index 0000000..1d2a1b0
--- /dev/null
+++ b/src/tint/text/unicode.h
@@ -0,0 +1,80 @@
+// 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_TEXT_UNICODE_H_
+#define SRC_TINT_TEXT_UNICODE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <ostream>
+#include <utility>
+
+namespace tint::text {
+
+/// CodePoint is a unicode code point.
+struct CodePoint {
+  /// Constructor
+  inline CodePoint() = default;
+
+  /// Constructor
+  /// @param v the code point value
+  inline explicit CodePoint(uint32_t v) : value(v) {}
+
+  /// @returns the code point value
+  inline operator uint32_t() const { return value; }
+
+  /// Assignment operator
+  /// @param v the new value for the code point
+  /// @returns this CodePoint
+  inline CodePoint& operator=(uint32_t v) {
+    value = v;
+    return *this;
+  }
+
+  /// @returns true if this CodePoint is in the XID_Start set.
+  /// @see https://unicode.org/reports/tr31/
+  bool IsXIDStart() const;
+
+  /// @returns true if this CodePoint is in the XID_Continue set.
+  /// @see https://unicode.org/reports/tr31/
+  bool IsXIDContinue() const;
+
+  /// The code point value
+  uint32_t value = 0;
+};
+
+/// Writes the CodePoint to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param codepoint the CodePoint to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, CodePoint codepoint);
+
+namespace utf8 {
+
+/// Decodes the first code point in the utf8 string.
+/// @param ptr the pointer to the first byte of the utf8 sequence
+/// @param len the maximum number of bytes to read
+/// @returns a pair of CodePoint and width in code units (bytes).
+///          If the next code point cannot be decoded then returns [0,0].
+std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len);
+
+/// @returns true if all the utf-8 code points in the string are ASCII
+/// (code-points 0x00..0x7f).
+bool IsASCII(std::string_view);
+
+}  // namespace utf8
+
+}  // namespace tint::text
+
+#endif  // SRC_TINT_TEXT_UNICODE_H_
diff --git a/src/tint/text/unicode_test.cc b/src/tint/text/unicode_test.cc
new file mode 100644
index 0000000..deffae9
--- /dev/null
+++ b/src/tint/text/unicode_test.cc
@@ -0,0 +1,485 @@
+// 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/text/unicode.h"
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+
+/// Helper for constructing a CodePoint
+#define C(x) CodePoint(x)
+
+namespace tint::text {
+
+////////////////////////////////////////////////////////////////////////////////
+// CodePoint character set tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+
+struct CodePointCase {
+  CodePoint code_point;
+  bool is_xid_start;
+  bool is_xid_continue;
+};
+
+std::ostream& operator<<(std::ostream& out, CodePointCase c) {
+  return out << c.code_point;
+}
+
+class CodePointTest : public testing::TestWithParam<CodePointCase> {};
+
+TEST_P(CodePointTest, CharacterSets) {
+  auto param = GetParam();
+  EXPECT_EQ(param.code_point.IsXIDStart(), param.is_xid_start);
+  EXPECT_EQ(param.code_point.IsXIDContinue(), param.is_xid_continue);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    CodePointTest,
+    CodePointTest,
+    ::testing::ValuesIn({
+        CodePointCase{C(' '), /* start */ false, /* continue */ false},
+        CodePointCase{C('\t'), /* start */ false, /* continue */ false},
+        CodePointCase{C('\n'), /* start */ false, /* continue */ false},
+        CodePointCase{C('\r'), /* start */ false, /* continue */ false},
+        CodePointCase{C('!'), /* start */ false, /* continue */ false},
+        CodePointCase{C('"'), /* start */ false, /* continue */ false},
+        CodePointCase{C('#'), /* start */ false, /* continue */ false},
+        CodePointCase{C('$'), /* start */ false, /* continue */ false},
+        CodePointCase{C('%'), /* start */ false, /* continue */ false},
+        CodePointCase{C('&'), /* start */ false, /* continue */ false},
+        CodePointCase{C('\\'), /* start */ false, /* continue */ false},
+        CodePointCase{C('/'), /* start */ false, /* continue */ false},
+        CodePointCase{C('('), /* start */ false, /* continue */ false},
+        CodePointCase{C(')'), /* start */ false, /* continue */ false},
+        CodePointCase{C('*'), /* start */ false, /* continue */ false},
+        CodePointCase{C(','), /* start */ false, /* continue */ false},
+        CodePointCase{C('-'), /* start */ false, /* continue */ false},
+        CodePointCase{C('/'), /* start */ false, /* continue */ false},
+        CodePointCase{C('`'), /* start */ false, /* continue */ false},
+        CodePointCase{C('@'), /* start */ false, /* continue */ false},
+        CodePointCase{C('^'), /* start */ false, /* continue */ false},
+        CodePointCase{C('['), /* start */ false, /* continue */ false},
+        CodePointCase{C(']'), /* start */ false, /* continue */ false},
+        CodePointCase{C('|'), /* start */ false, /* continue */ false},
+        CodePointCase{C('('), /* start */ false, /* continue */ false},
+        CodePointCase{C(','), /* start */ false, /* continue */ false},
+        CodePointCase{C('}'), /* start */ false, /* continue */ false},
+        CodePointCase{C('a'), /* start */ true, /* continue */ true},
+        CodePointCase{C('b'), /* start */ true, /* continue */ true},
+        CodePointCase{C('c'), /* start */ true, /* continue */ true},
+        CodePointCase{C('x'), /* start */ true, /* continue */ true},
+        CodePointCase{C('y'), /* start */ true, /* continue */ true},
+        CodePointCase{C('z'), /* start */ true, /* continue */ true},
+        CodePointCase{C('A'), /* start */ true, /* continue */ true},
+        CodePointCase{C('B'), /* start */ true, /* continue */ true},
+        CodePointCase{C('C'), /* start */ true, /* continue */ true},
+        CodePointCase{C('X'), /* start */ true, /* continue */ true},
+        CodePointCase{C('Y'), /* start */ true, /* continue */ true},
+        CodePointCase{C('Z'), /* start */ true, /* continue */ true},
+        CodePointCase{C('_'), /* start */ false, /* continue */ true},
+        CodePointCase{C('0'), /* start */ false, /* continue */ true},
+        CodePointCase{C('1'), /* start */ false, /* continue */ true},
+        CodePointCase{C('2'), /* start */ false, /* continue */ true},
+        CodePointCase{C('8'), /* start */ false, /* continue */ true},
+        CodePointCase{C('9'), /* start */ false, /* continue */ true},
+        CodePointCase{C('0'), /* start */ false, /* continue */ true},
+
+        // First in XID_Start
+        CodePointCase{C(0x00041), /* start */ true, /* continue */ true},
+        // Last in XID_Start
+        CodePointCase{C(0x3134a), /* start */ true, /* continue */ true},
+
+        // Random selection from XID_Start, using the interval's first
+        CodePointCase{C(0x002ee), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x005ef), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x009f0), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00d3d), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00d54), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00e86), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00edc), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x01c00), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x01c80), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x02071), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x02dd0), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x0a4d0), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x0aac0), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x0ab5c), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x0ffda), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x11313), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x1ee49), /* start */ true, /* continue */ true},
+
+        // Random selection from XID_Start, using the interval's last
+        CodePointCase{C(0x00710), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00b83), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00b9a), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x00ec4), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x01081), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x012be), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x02107), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x03029), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x03035), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x0aadd), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x10805), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x11075), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x1d4a2), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x1e7fe), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x1ee27), /* start */ true, /* continue */ true},
+        CodePointCase{C(0x2b738), /* start */ true, /* continue */ true},
+
+        // Random selection from XID_Continue, using the interval's first
+        CodePointCase{C(0x16ac0), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00dca), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x16f4f), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0fe00), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00ec8), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x009be), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x11d47), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x11d50), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0a926), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0aac1), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00f18), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x11145), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x017dd), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0aaeb), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x11173), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00a51), /* start */ false, /* continue */ true},
+
+        // Random selection from XID_Continue, using the interval's last
+        CodePointCase{C(0x00f84), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x10a3a), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x1e018), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0a827), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x01abd), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x009d7), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00b6f), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0096f), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x11146), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x10eac), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00f39), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x1e136), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00def), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0fe34), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x009c8), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00fbc), /* start */ false, /* continue */ true},
+
+        // Random code points that are one less than an interval of XID_Start
+        CodePointCase{C(0x003f6), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x005ee), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x009ef), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00d3c), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x00d53), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00e85), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00edb), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x01bff), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x02070), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x02dcf), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x0a4cf), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x0aabf), /* start */ false, /* continue */ true},
+        CodePointCase{C(0x0ab5b), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x0ffd9), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x11312), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x1ee48), /* start */ false, /* continue */ false},
+
+        // Random code points that are one more than an interval of XID_Continue
+        CodePointCase{C(0x00060), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00a4e), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00a84), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00cce), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00eda), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x00f85), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x01b74), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x01c38), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x0fe30), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x11174), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x112eb), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x115de), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x1172c), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x11a3f), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x11c37), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x11d92), /* start */ false, /* continue */ false},
+        CodePointCase{C(0x1e2af), /* start */ false, /* continue */ false},
+    }));
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DecodeUTF8 valid tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+
+struct CodePointAndWidth {
+  CodePoint code_point;
+  size_t width;
+};
+
+bool operator==(const CodePointAndWidth& a, const CodePointAndWidth& b) {
+  return a.code_point == b.code_point && a.width == b.width;
+}
+
+std::ostream& operator<<(std::ostream& out, CodePointAndWidth cpw) {
+  return out << "code_point: " << cpw.code_point << ", width: " << cpw.width;
+}
+
+struct DecodeUTF8Case {
+  std::string string;
+  std::vector<CodePointAndWidth> expected;
+};
+
+std::ostream& operator<<(std::ostream& out, DecodeUTF8Case c) {
+  return out << "'" << c.string << "'";
+}
+
+class DecodeUTF8Test : public testing::TestWithParam<DecodeUTF8Case> {};
+
+TEST_P(DecodeUTF8Test, Valid) {
+  auto param = GetParam();
+
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(param.string.data());
+  const size_t len = param.string.size();
+
+  std::vector<CodePointAndWidth> got;
+  size_t offset = 0;
+  while (offset < len) {
+    auto [code_point, width] = utf8::Decode(data + offset, len - offset);
+    if (width == 0) {
+      FAIL() << "Decode() failed at byte offset " << offset;
+    }
+    offset += width;
+    got.emplace_back(CodePointAndWidth{code_point, width});
+  }
+
+  EXPECT_THAT(got, ::testing::ElementsAreArray(param.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AsciiLetters,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({
+        DecodeUTF8Case{"a", {{C('a'), 1}}},
+        DecodeUTF8Case{"abc", {{C('a'), 1}, {C('b'), 1}, {C('c'), 1}}},
+        DecodeUTF8Case{"def", {{C('d'), 1}, {C('e'), 1}, {C('f'), 1}}},
+        DecodeUTF8Case{"gh", {{C('g'), 1}, {C('h'), 1}}},
+        DecodeUTF8Case{"ij", {{C('i'), 1}, {C('j'), 1}}},
+        DecodeUTF8Case{"klm", {{C('k'), 1}, {C('l'), 1}, {C('m'), 1}}},
+        DecodeUTF8Case{"nop", {{C('n'), 1}, {C('o'), 1}, {C('p'), 1}}},
+        DecodeUTF8Case{"qr", {{C('q'), 1}, {C('r'), 1}}},
+        DecodeUTF8Case{"stu", {{C('s'), 1}, {C('t'), 1}, {C('u'), 1}}},
+        DecodeUTF8Case{"vw", {{C('v'), 1}, {C('w'), 1}}},
+        DecodeUTF8Case{"xyz", {{C('x'), 1}, {C('y'), 1}, {C('z'), 1}}},
+        DecodeUTF8Case{"A", {{C('A'), 1}}},
+        DecodeUTF8Case{"ABC", {{C('A'), 1}, {C('B'), 1}, {C('C'), 1}}},
+        DecodeUTF8Case{"DEF", {{C('D'), 1}, {C('E'), 1}, {C('F'), 1}}},
+        DecodeUTF8Case{"GH", {{C('G'), 1}, {C('H'), 1}}},
+        DecodeUTF8Case{"IJ", {{C('I'), 1}, {C('J'), 1}}},
+        DecodeUTF8Case{"KLM", {{C('K'), 1}, {C('L'), 1}, {C('M'), 1}}},
+        DecodeUTF8Case{"NOP", {{C('N'), 1}, {C('O'), 1}, {C('P'), 1}}},
+        DecodeUTF8Case{"QR", {{C('Q'), 1}, {C('R'), 1}}},
+        DecodeUTF8Case{"STU", {{C('S'), 1}, {C('T'), 1}, {C('U'), 1}}},
+        DecodeUTF8Case{"VW", {{C('V'), 1}, {C('W'), 1}}},
+        DecodeUTF8Case{"XYZ", {{C('X'), 1}, {C('Y'), 1}, {C('Z'), 1}}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    AsciiNumbers,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({
+        DecodeUTF8Case{"012", {{C('0'), 1}, {C('1'), 1}, {C('2'), 1}}},
+        DecodeUTF8Case{"345", {{C('3'), 1}, {C('4'), 1}, {C('5'), 1}}},
+        DecodeUTF8Case{"678", {{C('6'), 1}, {C('7'), 1}, {C('8'), 1}}},
+        DecodeUTF8Case{"9", {{C('9'), 1}}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    AsciiSymbols,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({
+        DecodeUTF8Case{"!\"#", {{C('!'), 1}, {C('"'), 1}, {C('#'), 1}}},
+        DecodeUTF8Case{"$%&", {{C('$'), 1}, {C('%'), 1}, {C('&'), 1}}},
+        DecodeUTF8Case{"'()", {{C('\''), 1}, {C('('), 1}, {C(')'), 1}}},
+        DecodeUTF8Case{"*,-", {{C('*'), 1}, {C(','), 1}, {C('-'), 1}}},
+        DecodeUTF8Case{"/`@", {{C('/'), 1}, {C('`'), 1}, {C('@'), 1}}},
+        DecodeUTF8Case{"^\\[", {{C('^'), 1}, {C('\\'), 1}, {C('['), 1}}},
+        DecodeUTF8Case{"]_|", {{C(']'), 1}, {C('_'), 1}, {C('|'), 1}}},
+        DecodeUTF8Case{"{}", {{C('{'), 1}, {C('}'), 1}}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    AsciiSpecial,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({
+        DecodeUTF8Case{"", {}},
+        DecodeUTF8Case{" \t\n", {{C(' '), 1}, {C('\t'), 1}, {C('\n'), 1}}},
+        DecodeUTF8Case{"\a\b\f", {{C('\a'), 1}, {C('\b'), 1}, {C('\f'), 1}}},
+        DecodeUTF8Case{"\n\r\t", {{C('\n'), 1}, {C('\r'), 1}, {C('\t'), 1}}},
+        DecodeUTF8Case{"\v", {{C('\v'), 1}}},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    Hindi,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({DecodeUTF8Case{
+        // नमस्ते दुनिया
+        "\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5"
+        "\x87\x20\xe0\xa4\xa6\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xaf"
+        "\xe0\xa4\xbe",
+        {
+            {C(0x0928), 3},  // न
+            {C(0x092e), 3},  // म
+            {C(0x0938), 3},  // स
+            {C(0x094d), 3},  // ्
+            {C(0x0924), 3},  // त
+            {C(0x0947), 3},  // े
+            {C(' '), 1},
+            {C(0x0926), 3},  // द
+            {C(0x0941), 3},  // ु
+            {C(0x0928), 3},  // न
+            {C(0x093f), 3},  // ि
+            {C(0x092f), 3},  // य
+            {C(0x093e), 3},  // ा
+        },
+    }}));
+
+INSTANTIATE_TEST_SUITE_P(Mandarin,
+                         DecodeUTF8Test,
+                         ::testing::ValuesIn({DecodeUTF8Case{
+                             // 你好世界
+                             "\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c",
+                             {
+                                 {C(0x4f60), 3},  // 你
+                                 {C(0x597d), 3},  // 好
+                                 {C(0x4e16), 3},  // 世
+                                 {C(0x754c), 3},  // 界
+                             },
+                         }}));
+
+INSTANTIATE_TEST_SUITE_P(Japanese,
+                         DecodeUTF8Test,
+                         ::testing::ValuesIn({DecodeUTF8Case{
+                             // こんにちは世界
+                             "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1"
+                             "\xe3\x81\xaf\xe4\xb8\x96\xe7\x95\x8c",
+                             {
+                                 {C(0x3053), 3},  // こ
+                                 {C(0x3093), 3},  // ん
+                                 {C(0x306B), 3},  // に
+                                 {C(0x3061), 3},  // ち
+                                 {C(0x306F), 3},  // は
+                                 {C(0x4E16), 3},  // 世
+                                 {C(0x754C), 3},  // 界
+                             },
+                         }}));
+
+INSTANTIATE_TEST_SUITE_P(Korean,
+                         DecodeUTF8Test,
+                         ::testing::ValuesIn({DecodeUTF8Case{
+                             // 안녕하세요 세계
+                             "\xec\x95\x88\xeb\x85\x95\xed\x95\x98\xec\x84\xb8"
+                             "\xec\x9a\x94\x20\xec\x84\xb8\xea\xb3\x84",
+                             {
+                                 {C(0xc548), 3},  // 안
+                                 {C(0xb155), 3},  // 녕
+                                 {C(0xd558), 3},  // 하
+                                 {C(0xc138), 3},  // 세
+                                 {C(0xc694), 3},  // 요
+                                 {C(' '), 1},     //
+                                 {C(0xc138), 3},  // 세
+                                 {C(0xacc4), 3},  // 계
+                             },
+                         }}));
+
+INSTANTIATE_TEST_SUITE_P(Emoji,
+                         DecodeUTF8Test,
+                         ::testing::ValuesIn({DecodeUTF8Case{
+                             // 👋🌎
+                             "\xf0\x9f\x91\x8b\xf0\x9f\x8c\x8e",
+                             {
+                                 {C(0x1f44b), 4},  // 👋
+                                 {C(0x1f30e), 4},  // 🌎
+                             },
+                         }}));
+
+INSTANTIATE_TEST_SUITE_P(
+    Random,
+    DecodeUTF8Test,
+    ::testing::ValuesIn({DecodeUTF8Case{
+        // Øⓑꚫ쁹Ǵ𐌒岾🥍ⴵ㍨又ᮗ
+        "\xc3\x98\xe2\x93\x91\xea\x9a\xab\xec\x81\xb9\xc7\xb4\xf0\x90\x8c\x92"
+        "\xe5\xb2\xbe\xf0\x9f\xa5\x8d\xe2\xb4\xb5\xe3\x8d\xa8\xe5\x8f\x88\xe1"
+        "\xae\x97",
+        {
+            {C(0x000d8), 2},  // Ø
+            {C(0x024d1), 3},  // ⓑ
+            {C(0x0a6ab), 3},  // ꚫ
+            {C(0x0c079), 3},  // 쁹
+            {C(0x001f4), 2},  // Ǵ
+            {C(0x10312), 4},  // 𐌒
+            {C(0x05cbe), 3},  // 岾
+            {C(0x1f94d), 4},  // 🥍
+            {C(0x02d35), 3},  // ⴵ
+            {C(0x03368), 3},  // ㍨
+            {C(0x053c8), 3},  // 又
+            {C(0x01b97), 3},  // ᮗ
+        },
+    }}));
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DecodeUTF8 invalid tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+class DecodeUTF8InvalidTest : public testing::TestWithParam<const char*> {};
+
+TEST_P(DecodeUTF8InvalidTest, Invalid) {
+  auto* param = GetParam();
+
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(param);
+  const size_t len = std::string(param).size();
+
+  auto [code_point, width] = utf8::Decode(data, len);
+  EXPECT_EQ(code_point, CodePoint(0));
+  EXPECT_EQ(width, 0u);
+}
+
+INSTANTIATE_TEST_SUITE_P(Invalid,
+                         DecodeUTF8InvalidTest,
+                         ::testing::ValuesIn({
+                             "\x80\x80\x80\x80",  // 10000000
+                             "\x81\x80\x80\x80",  // 10000001
+                             "\x8f\x80\x80\x80",  // 10001111
+                             "\x90\x80\x80\x80",  // 10010000
+                             "\x91\x80\x80\x80",  // 10010001
+                             "\x9f\x80\x80\x80",  // 10011111
+                             "\xa0\x80\x80\x80",  // 10100000
+                             "\xa1\x80\x80\x80",  // 10100001
+                             "\xaf\x80\x80\x80",  // 10101111
+                             "\xb0\x80\x80\x80",  // 10110000
+                             "\xb1\x80\x80\x80",  // 10110001
+                             "\xbf\x80\x80\x80",  // 10111111
+                             "\xc0\x80\x80\x80",  // 11000000
+                             "\xc1\x80\x80\x80",  // 11000001
+                             "\xf5\x80\x80\x80",  // 11110101
+                             "\xf6\x80\x80\x80",  // 11110110
+                             "\xf7\x80\x80\x80",  // 11110111
+                             "\xf8\x80\x80\x80",  // 11111000
+                             "\xfe\x80\x80\x80",  // 11111110
+                             "\xff\x80\x80\x80",  // 11111111
+                         }));
+
+}  // namespace
+
+}  // namespace tint::text
diff --git a/src/tint.natvis b/src/tint/tint.natvis
similarity index 100%
rename from src/tint.natvis
rename to src/tint/tint.natvis
diff --git a/src/tint/traits.h b/src/tint/traits.h
new file mode 100644
index 0000000..dc104cc
--- /dev/null
+++ b/src/tint/traits.h
@@ -0,0 +1,162 @@
+// Copyright 2020 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_TRAITS_H_
+#define SRC_TINT_TRAITS_H_
+
+#include <tuple>
+#include <utility>
+
+namespace tint::traits {
+
+/// Convience type definition for std::decay<T>::type
+template <typename T>
+using Decay = typename std::decay<T>::type;
+
+/// NthTypeOf returns the `N`th type in `Types`
+template <int N, typename... Types>
+using NthTypeOf = typename std::tuple_element<N, std::tuple<Types...>>::type;
+
+/// Signature describes the signature of a function.
+template <typename RETURN, typename... PARAMETERS>
+struct Signature {
+  /// The return type of the function signature
+  using ret = RETURN;
+  /// The parameters of the function signature held in a std::tuple
+  using parameters = std::tuple<PARAMETERS...>;
+  /// The type of the Nth parameter of function signature
+  template <std::size_t N>
+  using parameter = NthTypeOf<N, PARAMETERS...>;
+  /// The total number of parameters
+  static constexpr std::size_t parameter_count = sizeof...(PARAMETERS);
+};
+
+/// SignatureOf is a traits helper that infers the signature of the function,
+/// method, static method, lambda, or function-like object `F`.
+template <typename F>
+struct SignatureOf {
+  /// The signature of the function-like object `F`
+  using type = typename SignatureOf<decltype(&F::operator())>::type;
+};
+
+/// SignatureOf specialization for a regular function or static method.
+template <typename R, typename... ARGS>
+struct SignatureOf<R (*)(ARGS...)> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOf specialization for a non-static method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...)> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOf specialization for a non-static, const method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...) const> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOfT is an alias to `typename SignatureOf<F>::type`.
+template <typename F>
+using SignatureOfT = typename SignatureOf<F>::type;
+
+/// ParameterType is an alias to `typename SignatureOf<F>::type::parameter<N>`.
+template <typename F, std::size_t N>
+using ParameterType = typename SignatureOfT<F>::template parameter<N>;
+
+/// ReturnType is an alias to `typename SignatureOf<F>::type::ret`.
+template <typename F>
+using ReturnType = typename SignatureOfT<F>::ret;
+
+/// IsTypeOrDerived<T, BASE> is true iff `T` is of type `BASE`, or derives from
+/// `BASE`.
+template <typename T, typename BASE>
+static constexpr bool IsTypeOrDerived =
+    std::is_base_of<BASE, Decay<T>>::value ||
+    std::is_same<BASE, Decay<T>>::value;
+
+/// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
+/// invalid type.
+template <bool CONDITION, typename T>
+using EnableIf = typename std::enable_if<CONDITION, T>::type;
+
+/// If `T` is of type `BASE`, or derives from `BASE`, then EnableIfIsType
+/// resolves to type `T`, otherwise an invalid type.
+template <typename T, typename BASE>
+using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>, T>;
+
+/// If `T` is not of type `BASE`, or does not derive from `BASE`, then
+/// EnableIfIsNotType resolves to type `T`, otherwise an invalid type.
+template <typename T, typename BASE>
+using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>, T>;
+
+/// @returns the std::index_sequence with all the indices shifted by OFFSET.
+template <std::size_t OFFSET, std::size_t... INDICES>
+constexpr auto Shift(std::index_sequence<INDICES...>) {
+  return std::integer_sequence<std::size_t, OFFSET + INDICES...>{};
+}
+
+/// @returns a std::integer_sequence with the integers `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT>
+constexpr auto Range() {
+  return Shift<OFFSET>(std::make_index_sequence<COUNT>{});
+}
+
+namespace detail {
+
+/// @returns the tuple `t` swizzled by `INDICES`
+template <typename TUPLE, std::size_t... INDICES>
+constexpr auto Swizzle(TUPLE&& t, std::index_sequence<INDICES...>)
+    -> std::tuple<
+        std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>...> {
+  return {std::forward<
+      std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>>(
+      std::get<INDICES>(std::forward<TUPLE>(t)))...};
+}
+
+/// @returns a nullptr of the tuple type `TUPLE` swizzled by `INDICES`.
+/// @note: This function is intended to be used in a `decltype()` expression,
+/// and returns a pointer-to-tuple as the tuple may hold non-constructable
+/// types.
+template <typename TUPLE, std::size_t... INDICES>
+constexpr auto* SwizzlePtrTy(std::index_sequence<INDICES...>) {
+  using Swizzled = std::tuple<std::tuple_element_t<INDICES, TUPLE>...>;
+  return static_cast<Swizzled*>(nullptr);
+}
+
+}  // namespace detail
+
+/// @returns the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+constexpr auto Slice(TUPLE&& t) {
+  return detail::Swizzle<TUPLE>(std::forward<TUPLE>(t), Range<OFFSET, COUNT>());
+}
+
+/// Resolves to the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+using SliceTuple = std::remove_pointer_t<decltype(
+    detail::SwizzlePtrTy<TUPLE>(Range<OFFSET, COUNT>()))>;
+
+}  // namespace tint::traits
+
+#endif  // SRC_TINT_TRAITS_H_
diff --git a/src/tint/traits_test.cc b/src/tint/traits_test.cc
new file mode 100644
index 0000000..afe3475
--- /dev/null
+++ b/src/tint/traits_test.cc
@@ -0,0 +1,235 @@
+// Copyright 2020 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/traits.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace traits {
+
+namespace {
+struct S {};
+void F1(S) {}
+void F3(int, S, float) {}
+}  // namespace
+
+TEST(ParamType, Function) {
+  F1({});        // Avoid unused method warning
+  F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same_v<ParameterType<decltype(&F1), 0>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 0>, int>);
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 1>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 2>, float>);
+  static_assert(std::is_same_v<ReturnType<decltype(&F1)>, void>);
+  static_assert(std::is_same_v<ReturnType<decltype(&F3)>, void>);
+  static_assert(SignatureOfT<decltype(&F1)>::parameter_count == 1);
+  static_assert(SignatureOfT<decltype(&F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, Method) {
+  class C {
+   public:
+    void F1(S) {}
+    void F3(int, S, float) {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, ConstMethod) {
+  class C {
+   public:
+    void F1(S) const {}
+    void F3(int, S, float) const {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, StaticMethod) {
+  class C {
+   public:
+    static void F1(S) {}
+    static void F3(int, S, float) {}
+  };
+  C::F1({});        // Avoid unused method warning
+  C::F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, FunctionLike) {
+  using F1 = std::function<void(S)>;
+  using F3 = std::function<void(int, S, float)>;
+  static_assert(std::is_same_v<ParameterType<F1, 0>, S>);
+  static_assert(std::is_same_v<ParameterType<F3, 0>, int>);
+  static_assert(std::is_same_v<ParameterType<F3, 1>, S>);
+  static_assert(std::is_same_v<ParameterType<F3, 2>, float>);
+  static_assert(std::is_same_v<ReturnType<F1>, void>);
+  static_assert(std::is_same_v<ReturnType<F3>, void>);
+  static_assert(SignatureOfT<F1>::parameter_count == 1);
+  static_assert(SignatureOfT<F3>::parameter_count == 3);
+}
+
+TEST(ParamType, Lambda) {
+  auto l1 = [](S) {};
+  auto l3 = [](int, S, float) {};
+  static_assert(std::is_same_v<ParameterType<decltype(l1), 0>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 0>, int>);
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 1>, S>);
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 2>, float>);
+  static_assert(std::is_same_v<ReturnType<decltype(l1)>, void>);
+  static_assert(std::is_same_v<ReturnType<decltype(l3)>, void>);
+  static_assert(SignatureOfT<decltype(l1)>::parameter_count == 1);
+  static_assert(SignatureOfT<decltype(l3)>::parameter_count == 3);
+}
+
+TEST(Slice, Empty) {
+  auto sliced = Slice<0, 0>(std::make_tuple<>());
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, SingleElementSliceEmpty) {
+  auto sliced = Slice<0, 0>(std::make_tuple<int>(1));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, SingleElementSliceFull) {
+  auto sliced = Slice<0, 1>(std::make_tuple<int>(1));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 1);
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  EXPECT_EQ(std::get<0>(sliced), 1);
+}
+
+TEST(Slice, MixedTupleSliceEmpty) {
+  auto sliced = Slice<1, 0>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, MixedTupleSliceFull) {
+  auto sliced = Slice<0, 3>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 3);
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
+                "");
+  static_assert(
+      std::is_same_v<std::tuple_element_t<2, decltype(sliced)>, float>);
+  EXPECT_EQ(std::get<0>(sliced), 1);
+  EXPECT_EQ(std::get<1>(sliced), true);
+  EXPECT_EQ(std::get<2>(sliced), 2.0f);
+}
+
+TEST(Slice, MixedTupleSliceLowPart) {
+  auto sliced = Slice<0, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 2);
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
+                "");
+  EXPECT_EQ(std::get<0>(sliced), 1);
+  EXPECT_EQ(std::get<1>(sliced), true);
+}
+
+TEST(Slice, MixedTupleSliceHighPart) {
+  auto sliced = Slice<1, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 2);
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, bool>,
+                "");
+  static_assert(
+      std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, float>);
+  EXPECT_EQ(std::get<0>(sliced), true);
+  EXPECT_EQ(std::get<1>(sliced), 2.0f);
+}
+
+TEST(Slice, PreservesRValueRef) {
+  int i;
+  int& int_ref = i;
+  auto tuple = std::forward_as_tuple(std::move(int_ref));
+  static_assert(std::is_same_v<int&&,  //
+                               std::tuple_element_t<0, decltype(tuple)>>);
+  auto sliced = Slice<0, 1>(std::move(tuple));
+  static_assert(std::is_same_v<int&&,  //
+                               std::tuple_element_t<0, decltype(sliced)>>);
+}
+
+TEST(SliceTuple, Empty) {
+  using sliced = SliceTuple<0, 0, std::tuple<>>;
+  static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, SingleElementSliceEmpty) {
+  using sliced = SliceTuple<0, 0, std::tuple<int>>;
+  static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, SingleElementSliceFull) {
+  using sliced = SliceTuple<0, 1, std::tuple<int>>;
+  static_assert(std::tuple_size_v<sliced> == 1);
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+}
+
+TEST(SliceTuple, MixedTupleSliceEmpty) {
+  using sliced = SliceTuple<1, 0, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, MixedTupleSliceFull) {
+  using sliced = SliceTuple<0, 3, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 3);
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
+  static_assert(std::is_same_v<std::tuple_element_t<2, sliced>, float>);
+}
+
+TEST(SliceTuple, MixedTupleSliceLowPart) {
+  using sliced = SliceTuple<0, 2, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 2);
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
+}
+
+TEST(SliceTuple, MixedTupleSliceHighPart) {
+  using sliced = SliceTuple<1, 2, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 2);
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, bool>);
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, float>);
+}
+
+}  // namespace traits
+}  // namespace tint
diff --git a/src/tint/transform/add_empty_entry_point.cc b/src/tint/transform/add_empty_entry_point.cc
new file mode 100644
index 0000000..5e33ce8
--- /dev/null
+++ b/src/tint/transform/add_empty_entry_point.cc
@@ -0,0 +1,51 @@
+// 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/transform/add_empty_entry_point.h"
+
+#include <utility>
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::AddEmptyEntryPoint);
+
+namespace tint {
+namespace transform {
+
+AddEmptyEntryPoint::AddEmptyEntryPoint() = default;
+
+AddEmptyEntryPoint::~AddEmptyEntryPoint() = default;
+
+bool AddEmptyEntryPoint::ShouldRun(const Program* program,
+                                   const DataMap&) const {
+  for (auto* func : program->AST().Functions()) {
+    if (func->IsEntryPoint()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void AddEmptyEntryPoint::Run(CloneContext& ctx,
+                             const DataMap&,
+                             DataMap&) const {
+  ctx.dst->Func(ctx.dst->Symbols().New("unused_entry_point"), {},
+                ctx.dst->ty.void_(), {},
+                {ctx.dst->Stage(ast::PipelineStage::kCompute),
+                 ctx.dst->WorkgroupSize(1)});
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/add_empty_entry_point.h b/src/tint/transform/add_empty_entry_point.h
new file mode 100644
index 0000000..36e250e
--- /dev/null
+++ b/src/tint/transform/add_empty_entry_point.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
+#define SRC_TINT_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Add an empty entry point to the module, if no other entry points exist.
+class AddEmptyEntryPoint : public Castable<AddEmptyEntryPoint, Transform> {
+ public:
+  /// Constructor
+  AddEmptyEntryPoint();
+  /// Destructor
+  ~AddEmptyEntryPoint() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
diff --git a/src/tint/transform/add_empty_entry_point_test.cc b/src/tint/transform/add_empty_entry_point_test.cc
new file mode 100644
index 0000000..b5ab815
--- /dev/null
+++ b/src/tint/transform/add_empty_entry_point_test.cc
@@ -0,0 +1,88 @@
+// 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/transform/add_empty_entry_point.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using AddEmptyEntryPointTest = TransformTest;
+
+TEST_F(AddEmptyEntryPointTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_TRUE(ShouldRun<AddEmptyEntryPoint>(src));
+}
+
+TEST_F(AddEmptyEntryPointTest, ShouldRunExistingEntryPoint) {
+  auto* src = R"(
+[[stage(compute), workgroup_size(1)]]
+fn existing() {}
+)";
+
+  EXPECT_FALSE(ShouldRun<AddEmptyEntryPoint>(src));
+}
+
+TEST_F(AddEmptyEntryPointTest, EmptyModule) {
+  auto* src = R"()";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn unused_entry_point() {
+}
+)";
+
+  auto got = Run<AddEmptyEntryPoint>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddEmptyEntryPointTest, ExistingEntryPoint) {
+  auto* src = R"(
+@stage(fragment)
+fn main() {
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<AddEmptyEntryPoint>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddEmptyEntryPointTest, NameClash) {
+  auto* src = R"(var<private> unused_entry_point : f32;)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn unused_entry_point_1() {
+}
+
+var<private> unused_entry_point : f32;
+)";
+
+  auto got = Run<AddEmptyEntryPoint>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/add_spirv_block_attribute.cc b/src/tint/transform/add_spirv_block_attribute.cc
new file mode 100644
index 0000000..ffd3896
--- /dev/null
+++ b/src/tint/transform/add_spirv_block_attribute.cc
@@ -0,0 +1,122 @@
+// 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/transform/add_spirv_block_attribute.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::AddSpirvBlockAttribute);
+TINT_INSTANTIATE_TYPEINFO(
+    tint::transform::AddSpirvBlockAttribute::SpirvBlockAttribute);
+
+namespace tint {
+namespace transform {
+
+AddSpirvBlockAttribute::AddSpirvBlockAttribute() = default;
+
+AddSpirvBlockAttribute::~AddSpirvBlockAttribute() = default;
+
+void AddSpirvBlockAttribute::Run(CloneContext& ctx,
+                                 const DataMap&,
+                                 DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  // Collect the set of structs that are nested in other types.
+  std::unordered_set<const sem::Struct*> nested_structs;
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* arr = sem.Get<sem::Array>(node->As<ast::Array>())) {
+      if (auto* nested_str = arr->ElemType()->As<sem::Struct>()) {
+        nested_structs.insert(nested_str);
+      }
+    } else if (auto* str = sem.Get<sem::Struct>(node->As<ast::Struct>())) {
+      for (auto* member : str->Members()) {
+        if (auto* nested_str = member->Type()->As<sem::Struct>()) {
+          nested_structs.insert(nested_str);
+        }
+      }
+    }
+  }
+
+  // A map from a type in the source program to a block-decorated wrapper that
+  // contains it in the destination program.
+  std::unordered_map<const sem::Type*, const ast::Struct*> wrapper_structs;
+
+  // Process global variables that are buffers.
+  for (auto* var : ctx.src->AST().GlobalVariables()) {
+    auto* sem_var = sem.Get<sem::GlobalVariable>(var);
+    if (var->declared_storage_class != ast::StorageClass::kStorage &&
+        var->declared_storage_class != ast::StorageClass::kUniform) {
+      continue;
+    }
+
+    auto* ty = sem.Get(var->type);
+    auto* str = ty->As<sem::Struct>();
+    if (!str || nested_structs.count(str)) {
+      const char* kMemberName = "inner";
+
+      // This is a non-struct or a struct that is nested somewhere else, so we
+      // need to wrap it first.
+      auto* wrapper = utils::GetOrCreate(wrapper_structs, ty, [&]() {
+        auto* block =
+            ctx.dst->ASTNodes().Create<SpirvBlockAttribute>(ctx.dst->ID());
+        auto wrapper_name = ctx.src->Symbols().NameFor(var->symbol) + "_block";
+        auto* ret = ctx.dst->create<ast::Struct>(
+            ctx.dst->Symbols().New(wrapper_name),
+            ast::StructMemberList{
+                ctx.dst->Member(kMemberName, CreateASTTypeFor(ctx, ty))},
+            ast::AttributeList{block});
+        ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), var, ret);
+        return ret;
+      });
+      ctx.Replace(var->type, ctx.dst->ty.Of(wrapper));
+
+      // Insert a member accessor to get the original type from the wrapper at
+      // any usage of the original variable.
+      for (auto* user : sem_var->Users()) {
+        ctx.Replace(
+            user->Declaration(),
+            ctx.dst->MemberAccessor(ctx.Clone(var->symbol), kMemberName));
+      }
+    } else {
+      // Add a block attribute to this struct directly.
+      auto* block =
+          ctx.dst->ASTNodes().Create<SpirvBlockAttribute>(ctx.dst->ID());
+      ctx.InsertFront(str->Declaration()->attributes, block);
+    }
+  }
+
+  ctx.Clone();
+}
+
+AddSpirvBlockAttribute::SpirvBlockAttribute::SpirvBlockAttribute(ProgramID pid)
+    : Base(pid) {}
+AddSpirvBlockAttribute::SpirvBlockAttribute::~SpirvBlockAttribute() = default;
+std::string AddSpirvBlockAttribute::SpirvBlockAttribute::InternalName() const {
+  return "spirv_block";
+}
+
+const AddSpirvBlockAttribute::SpirvBlockAttribute*
+AddSpirvBlockAttribute::SpirvBlockAttribute::Clone(CloneContext* ctx) const {
+  return ctx->dst->ASTNodes()
+      .Create<AddSpirvBlockAttribute::SpirvBlockAttribute>(ctx->dst->ID());
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/add_spirv_block_attribute.h b/src/tint/transform/add_spirv_block_attribute.h
new file mode 100644
index 0000000..dc8258d
--- /dev/null
+++ b/src/tint/transform/add_spirv_block_attribute.h
@@ -0,0 +1,76 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
+#define SRC_TINT_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// AddSpirvBlockAttribute is a transform that adds an
+/// `@internal(spirv_block)` attribute to any structure that is used as the
+/// store type of a buffer. If that structure is nested inside another structure
+/// or an array, then it is wrapped inside another structure which gets the
+/// `@internal(spirv_block)` attribute instead.
+class AddSpirvBlockAttribute
+    : public Castable<AddSpirvBlockAttribute, Transform> {
+ public:
+  /// SpirvBlockAttribute is an InternalAttribute that is used to decorate a
+  // structure that needs a SPIR-V block attribute.
+  class SpirvBlockAttribute
+      : public Castable<SpirvBlockAttribute, ast::InternalAttribute> {
+   public:
+    /// Constructor
+    /// @param program_id the identifier of the program that owns this node
+    explicit SpirvBlockAttribute(ProgramID program_id);
+    /// Destructor
+    ~SpirvBlockAttribute() override;
+
+    /// @return a short description of the internal attribute which will be
+    /// displayed as `@internal(<name>)`
+    std::string InternalName() const override;
+
+    /// Performs a deep clone of this object using the CloneContext `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned object
+    const SpirvBlockAttribute* Clone(CloneContext* ctx) const override;
+  };
+
+  /// Constructor
+  AddSpirvBlockAttribute();
+
+  /// Destructor
+  ~AddSpirvBlockAttribute() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
diff --git a/src/tint/transform/add_spirv_block_attribute_test.cc b/src/tint/transform/add_spirv_block_attribute_test.cc
new file mode 100644
index 0000000..5968cf4
--- /dev/null
+++ b/src/tint/transform/add_spirv_block_attribute_test.cc
@@ -0,0 +1,615 @@
+// 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/transform/add_spirv_block_attribute.h"
+
+#include <memory>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using AddSpirvBlockAttributeTest = TransformTest;
+
+TEST_F(AddSpirvBlockAttributeTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Noop_UsedForPrivateVar) {
+  auto* src = R"(
+struct S {
+  f : f32;
+}
+
+var<private> p : S;
+
+@stage(fragment)
+fn main() {
+  p.f = 1.0;
+}
+)";
+  auto* expect = src;
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Noop_UsedForShaderIO) {
+  auto* src = R"(
+struct S {
+  @location(0)
+  f : f32;
+}
+
+@stage(fragment)
+fn main() -> S {
+  return S();
+}
+)";
+  auto* expect = src;
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, BasicScalar) {
+  auto* src = R"(
+@group(0) @binding(0)
+var<uniform> u : f32;
+
+@stage(fragment)
+fn main() {
+  let f = u;
+}
+)";
+  auto* expect = R"(
+@internal(spirv_block)
+struct u_block {
+  inner : f32;
+}
+
+@group(0) @binding(0) var<uniform> u : u_block;
+
+@stage(fragment)
+fn main() {
+  let f = u.inner;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, BasicArray) {
+  auto* src = R"(
+@group(0) @binding(0)
+var<uniform> u : array<vec4<f32>, 4u>;
+
+@stage(fragment)
+fn main() {
+  let a = u;
+}
+)";
+  auto* expect = R"(
+@internal(spirv_block)
+struct u_block {
+  inner : array<vec4<f32>, 4u>;
+}
+
+@group(0) @binding(0) var<uniform> u : u_block;
+
+@stage(fragment)
+fn main() {
+  let a = u.inner;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, BasicArray_Alias) {
+  auto* src = R"(
+type Numbers = array<vec4<f32>, 4u>;
+
+@group(0) @binding(0)
+var<uniform> u : Numbers;
+
+@stage(fragment)
+fn main() {
+  let a = u;
+}
+)";
+  auto* expect = R"(
+type Numbers = array<vec4<f32>, 4u>;
+
+@internal(spirv_block)
+struct u_block {
+  inner : array<vec4<f32>, 4u>;
+}
+
+@group(0) @binding(0) var<uniform> u : u_block;
+
+@stage(fragment)
+fn main() {
+  let a = u.inner;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, BasicStruct) {
+  auto* src = R"(
+struct S {
+  f : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u : S;
+
+@stage(fragment)
+fn main() {
+  let f = u.f;
+}
+)";
+  auto* expect = R"(
+@internal(spirv_block)
+struct S {
+  f : f32;
+}
+
+@group(0) @binding(0) var<uniform> u : S;
+
+@stage(fragment)
+fn main() {
+  let f = u.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Nested_OuterBuffer_InnerNotBuffer) {
+  auto* src = R"(
+struct Inner {
+  f : f32;
+};
+
+struct Outer {
+  i : Inner;
+};
+
+@group(0) @binding(0)
+var<uniform> u : Outer;
+
+@stage(fragment)
+fn main() {
+  let f = u.i.f;
+}
+)";
+  auto* expect = R"(
+struct Inner {
+  f : f32;
+}
+
+@internal(spirv_block)
+struct Outer {
+  i : Inner;
+}
+
+@group(0) @binding(0) var<uniform> u : Outer;
+
+@stage(fragment)
+fn main() {
+  let f = u.i.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Nested_OuterBuffer_InnerBuffer) {
+  auto* src = R"(
+struct Inner {
+  f : f32;
+};
+
+struct Outer {
+  i : Inner;
+};
+
+@group(0) @binding(0)
+var<uniform> u0 : Outer;
+
+@group(0) @binding(1)
+var<uniform> u1 : Inner;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.f;
+}
+)";
+  auto* expect = R"(
+struct Inner {
+  f : f32;
+}
+
+@internal(spirv_block)
+struct Outer {
+  i : Inner;
+}
+
+@group(0) @binding(0) var<uniform> u0 : Outer;
+
+@internal(spirv_block)
+struct u1_block {
+  inner : Inner;
+}
+
+@group(0) @binding(1) var<uniform> u1 : u1_block;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.inner.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Nested_OuterNotBuffer_InnerBuffer) {
+  auto* src = R"(
+struct Inner {
+  f : f32;
+};
+
+struct Outer {
+  i : Inner;
+};
+
+var<private> p : Outer;
+
+@group(0) @binding(1)
+var<uniform> u : Inner;
+
+@stage(fragment)
+fn main() {
+  let f0 = p.i.f;
+  let f1 = u.f;
+}
+)";
+  auto* expect = R"(
+struct Inner {
+  f : f32;
+}
+
+struct Outer {
+  i : Inner;
+}
+
+var<private> p : Outer;
+
+@internal(spirv_block)
+struct u_block {
+  inner : Inner;
+}
+
+@group(0) @binding(1) var<uniform> u : u_block;
+
+@stage(fragment)
+fn main() {
+  let f0 = p.i.f;
+  let f1 = u.inner.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Nested_InnerUsedForMultipleBuffers) {
+  auto* src = R"(
+struct Inner {
+  f : f32;
+};
+
+struct S {
+  i : Inner;
+};
+
+@group(0) @binding(0)
+var<uniform> u0 : S;
+
+@group(0) @binding(1)
+var<uniform> u1 : Inner;
+
+@group(0) @binding(2)
+var<uniform> u2 : Inner;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.f;
+  let f2 = u2.f;
+}
+)";
+  auto* expect = R"(
+struct Inner {
+  f : f32;
+}
+
+@internal(spirv_block)
+struct S {
+  i : Inner;
+}
+
+@group(0) @binding(0) var<uniform> u0 : S;
+
+@internal(spirv_block)
+struct u1_block {
+  inner : Inner;
+}
+
+@group(0) @binding(1) var<uniform> u1 : u1_block;
+
+@group(0) @binding(2) var<uniform> u2 : u1_block;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.inner.f;
+  let f2 = u2.inner.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, StructInArray) {
+  auto* src = R"(
+struct S {
+  f : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u : S;
+
+@stage(fragment)
+fn main() {
+  let f = u.f;
+  let a = array<S, 4>();
+}
+)";
+  auto* expect = R"(
+struct S {
+  f : f32;
+}
+
+@internal(spirv_block)
+struct u_block {
+  inner : S;
+}
+
+@group(0) @binding(0) var<uniform> u : u_block;
+
+@stage(fragment)
+fn main() {
+  let f = u.inner.f;
+  let a = array<S, 4>();
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, StructInArray_MultipleBuffers) {
+  auto* src = R"(
+struct S {
+  f : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u0 : S;
+
+@group(0) @binding(1)
+var<uniform> u1 : S;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.f;
+  let f1 = u1.f;
+  let a = array<S, 4>();
+}
+)";
+  auto* expect = R"(
+struct S {
+  f : f32;
+}
+
+@internal(spirv_block)
+struct u0_block {
+  inner : S;
+}
+
+@group(0) @binding(0) var<uniform> u0 : u0_block;
+
+@group(0) @binding(1) var<uniform> u1 : u0_block;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.inner.f;
+  let f1 = u1.inner.f;
+  let a = array<S, 4>();
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, Aliases_Nested_OuterBuffer_InnerBuffer) {
+  auto* src = R"(
+struct Inner {
+  f : f32;
+};
+
+type MyInner = Inner;
+
+struct Outer {
+  i : MyInner;
+};
+
+type MyOuter = Outer;
+
+@group(0) @binding(0)
+var<uniform> u0 : MyOuter;
+
+@group(0) @binding(1)
+var<uniform> u1 : MyInner;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.f;
+}
+)";
+  auto* expect = R"(
+struct Inner {
+  f : f32;
+}
+
+type MyInner = Inner;
+
+@internal(spirv_block)
+struct Outer {
+  i : MyInner;
+}
+
+type MyOuter = Outer;
+
+@group(0) @binding(0) var<uniform> u0 : MyOuter;
+
+@internal(spirv_block)
+struct u1_block {
+  inner : Inner;
+}
+
+@group(0) @binding(1) var<uniform> u1 : u1_block;
+
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.inner.f;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest,
+       Aliases_Nested_OuterBuffer_InnerBuffer_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.f;
+}
+
+@group(0) @binding(1)
+var<uniform> u1 : MyInner;
+
+type MyInner = Inner;
+
+@group(0) @binding(0)
+var<uniform> u0 : MyOuter;
+
+type MyOuter = Outer;
+
+struct Outer {
+  i : MyInner;
+};
+
+struct Inner {
+  f : f32;
+};
+)";
+  auto* expect = R"(
+@stage(fragment)
+fn main() {
+  let f0 = u0.i.f;
+  let f1 = u1.inner.f;
+}
+
+@internal(spirv_block)
+struct u1_block {
+  inner : Inner;
+}
+
+@group(0) @binding(1) var<uniform> u1 : u1_block;
+
+type MyInner = Inner;
+
+@group(0) @binding(0) var<uniform> u0 : MyOuter;
+
+type MyOuter = Outer;
+
+@internal(spirv_block)
+struct Outer {
+  i : MyInner;
+}
+
+struct Inner {
+  f : f32;
+}
+)";
+
+  auto got = Run<AddSpirvBlockAttribute>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
new file mode 100644
index 0000000..ff733ff
--- /dev/null
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -0,0 +1,233 @@
+// 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/transform/array_length_from_uniform.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/simplify_pointers.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Config);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Result);
+
+namespace tint {
+namespace transform {
+
+ArrayLengthFromUniform::ArrayLengthFromUniform() = default;
+ArrayLengthFromUniform::~ArrayLengthFromUniform() = default;
+
+/// Iterate over all arrayLength() builtins that operate on
+/// storage buffer variables.
+/// @param ctx the CloneContext.
+/// @param functor of type void(const ast::CallExpression*, const
+/// sem::VariableUser, const sem::GlobalVariable*). It takes in an
+/// ast::CallExpression of the arrayLength call expression node, a
+/// sem::VariableUser of the used storage buffer variable, and the
+/// sem::GlobalVariable for the storage buffer.
+template <typename F>
+static void IterateArrayLengthOnStorageVar(CloneContext& ctx, F&& functor) {
+  auto& sem = ctx.src->Sem();
+
+  // Find all calls to the arrayLength() builtin.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    auto* call_expr = node->As<ast::CallExpression>();
+    if (!call_expr) {
+      continue;
+    }
+
+    auto* call = sem.Get(call_expr);
+    auto* builtin = call->Target()->As<sem::Builtin>();
+    if (!builtin || builtin->Type() != sem::BuiltinType::kArrayLength) {
+      continue;
+    }
+
+    // Get the storage buffer that contains the runtime array.
+    // Since we require SimplifyPointers, we can assume that the arrayLength()
+    // call has one of two forms:
+    //   arrayLength(&struct_var.array_member)
+    //   arrayLength(&array_var)
+    auto* param = call_expr->args[0]->As<ast::UnaryOpExpression>();
+    if (!param || param->op != ast::UnaryOp::kAddressOf) {
+      TINT_ICE(Transform, ctx.dst->Diagnostics())
+          << "expected form of arrayLength argument to be &array_var or "
+             "&struct_var.array_member";
+      break;
+    }
+    auto* storage_buffer_expr = param->expr;
+    if (auto* accessor = param->expr->As<ast::MemberAccessorExpression>()) {
+      storage_buffer_expr = accessor->structure;
+    }
+    auto* storage_buffer_sem = sem.Get<sem::VariableUser>(storage_buffer_expr);
+    if (!storage_buffer_sem) {
+      TINT_ICE(Transform, ctx.dst->Diagnostics())
+          << "expected form of arrayLength argument to be &array_var or "
+             "&struct_var.array_member";
+      break;
+    }
+
+    // Get the index to use for the buffer size array.
+    auto* var = tint::As<sem::GlobalVariable>(storage_buffer_sem->Variable());
+    if (!var) {
+      TINT_ICE(Transform, ctx.dst->Diagnostics())
+          << "storage buffer is not a global variable";
+      break;
+    }
+    functor(call_expr, storage_buffer_sem, var);
+  }
+}
+
+bool ArrayLengthFromUniform::ShouldRun(const Program* program,
+                                       const DataMap&) const {
+  for (auto* fn : program->AST().Functions()) {
+    if (auto* sem_fn = program->Sem().Get(fn)) {
+      for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
+        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+void ArrayLengthFromUniform::Run(CloneContext& ctx,
+                                 const DataMap& inputs,
+                                 DataMap& outputs) const {
+  auto* cfg = inputs.Get<Config>();
+  if (cfg == nullptr) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  const char* kBufferSizeMemberName = "buffer_size";
+
+  // Determine the size of the buffer size array.
+  uint32_t max_buffer_size_index = 0;
+
+  IterateArrayLengthOnStorageVar(
+      ctx, [&](const ast::CallExpression*, const sem::VariableUser*,
+               const sem::GlobalVariable* var) {
+        auto binding = var->BindingPoint();
+        auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
+        if (idx_itr == cfg->bindpoint_to_size_index.end()) {
+          return;
+        }
+        if (idx_itr->second > max_buffer_size_index) {
+          max_buffer_size_index = idx_itr->second;
+        }
+      });
+
+  // Get (or create, on first call) the uniform buffer that will receive the
+  // size of each storage buffer in the module.
+  const ast::Variable* buffer_size_ubo = nullptr;
+  auto get_ubo = [&]() {
+    if (!buffer_size_ubo) {
+      // Emit an array<vec4<u32>, N>, where N is 1/4 number of elements.
+      // We do this because UBOs require an element stride that is 16-byte
+      // aligned.
+      auto* buffer_size_struct = ctx.dst->Structure(
+          ctx.dst->Sym(),
+          {ctx.dst->Member(
+              kBufferSizeMemberName,
+              ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
+                                (max_buffer_size_index / 4) + 1))});
+      buffer_size_ubo = ctx.dst->Global(
+          ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct),
+          ast::StorageClass::kUniform,
+          ast::AttributeList{ctx.dst->GroupAndBinding(
+              cfg->ubo_binding.group, cfg->ubo_binding.binding)});
+    }
+    return buffer_size_ubo;
+  };
+
+  std::unordered_set<uint32_t> used_size_indices;
+
+  IterateArrayLengthOnStorageVar(
+      ctx, [&](const ast::CallExpression* call_expr,
+               const sem::VariableUser* storage_buffer_sem,
+               const sem::GlobalVariable* var) {
+        auto binding = var->BindingPoint();
+        auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
+        if (idx_itr == cfg->bindpoint_to_size_index.end()) {
+          return;
+        }
+
+        uint32_t size_index = idx_itr->second;
+        used_size_indices.insert(size_index);
+
+        // Load the total storage buffer size from the UBO.
+        uint32_t array_index = size_index / 4;
+        auto* vec_expr = ctx.dst->IndexAccessor(
+            ctx.dst->MemberAccessor(get_ubo()->symbol, kBufferSizeMemberName),
+            array_index);
+        uint32_t vec_index = size_index % 4;
+        auto* total_storage_buffer_size =
+            ctx.dst->IndexAccessor(vec_expr, vec_index);
+
+        // Calculate actual array length
+        //                total_storage_buffer_size - array_offset
+        // array_length = ----------------------------------------
+        //                             array_stride
+        const ast::Expression* total_size = total_storage_buffer_size;
+        auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
+        const sem::Array* array_type = nullptr;
+        if (auto* str = storage_buffer_type->As<sem::Struct>()) {
+          // The variable is a struct, so subtract the byte offset of the array
+          // member.
+          auto* array_member_sem = str->Members().back();
+          array_type = array_member_sem->Type()->As<sem::Array>();
+          total_size = ctx.dst->Sub(total_storage_buffer_size,
+                                    array_member_sem->Offset());
+        } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
+          array_type = arr;
+        } else {
+          TINT_ICE(Transform, ctx.dst->Diagnostics())
+              << "expected form of arrayLength argument to be &array_var or "
+                 "&struct_var.array_member";
+          return;
+        }
+        auto* array_length = ctx.dst->Div(total_size, array_type->Stride());
+
+        ctx.Replace(call_expr, array_length);
+      });
+
+  ctx.Clone();
+
+  outputs.Add<Result>(used_size_indices);
+}
+
+ArrayLengthFromUniform::Config::Config(sem::BindingPoint ubo_bp)
+    : ubo_binding(ubo_bp) {}
+ArrayLengthFromUniform::Config::Config(const Config&) = default;
+ArrayLengthFromUniform::Config& ArrayLengthFromUniform::Config::operator=(
+    const Config&) = default;
+ArrayLengthFromUniform::Config::~Config() = default;
+
+ArrayLengthFromUniform::Result::Result(
+    std::unordered_set<uint32_t> used_size_indices_in)
+    : used_size_indices(std::move(used_size_indices_in)) {}
+ArrayLengthFromUniform::Result::Result(const Result&) = default;
+ArrayLengthFromUniform::Result::~Result() = default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/array_length_from_uniform.h b/src/tint/transform/array_length_from_uniform.h
new file mode 100644
index 0000000..109db8e
--- /dev/null
+++ b/src/tint/transform/array_length_from_uniform.h
@@ -0,0 +1,125 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
+#define SRC_TINT_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace transform {
+
+/// ArrayLengthFromUniform is a transform that implements calls to arrayLength()
+/// by calculating the length from the total size of the storage buffer, which
+/// is received via a uniform buffer.
+///
+/// The generated uniform buffer will have the form:
+/// ```
+/// struct buffer_size_struct {
+///  buffer_size : array<u32, 8>;
+/// };
+///
+/// @group(0) @binding(30)
+/// var<uniform> buffer_size_ubo : buffer_size_struct;
+/// ```
+/// The binding group and number used for this uniform buffer is provided via
+/// the `Config` transform input. The `Config` struct also defines the mapping
+/// from a storage buffer's `BindingPoint` to the array index that will be used
+/// to get the size of that buffer.
+///
+/// This transform assumes that the `SimplifyPointers`
+/// transforms have been run before it so that arguments to the arrayLength
+/// builtin always have the form `&resource.array`.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * SimplifyPointers
+class ArrayLengthFromUniform
+    : public Castable<ArrayLengthFromUniform, Transform> {
+ public:
+  /// Constructor
+  ArrayLengthFromUniform();
+  /// Destructor
+  ~ArrayLengthFromUniform() override;
+
+  /// Configuration options for the ArrayLengthFromUniform transform.
+  struct Config : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param ubo_bp the binding point to use for the generated uniform buffer.
+    explicit Config(sem::BindingPoint ubo_bp);
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Copy assignment
+    /// @return this Config
+    Config& operator=(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// The binding point to use for the generated uniform buffer.
+    sem::BindingPoint ubo_binding;
+
+    /// The mapping from binding point to the index for the buffer size lookup.
+    std::unordered_map<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
+  };
+
+  /// Information produced about what the transform did.
+  /// If there were no calls to the arrayLength() builtin, then no Result will
+  /// be emitted.
+  struct Result : public Castable<Result, transform::Data> {
+    /// Constructor
+    /// @param used_size_indices Indices into the UBO that are statically used.
+    explicit Result(std::unordered_set<uint32_t> used_size_indices);
+
+    /// Copy constructor
+    Result(const Result&);
+
+    /// Destructor
+    ~Result() override;
+
+    /// Indices into the UBO that are statically used.
+    const std::unordered_set<uint32_t> used_size_indices;
+  };
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
diff --git a/src/tint/transform/array_length_from_uniform_test.cc b/src/tint/transform/array_length_from_uniform_test.cc
new file mode 100644
index 0000000..9170d65
--- /dev/null
+++ b/src/tint/transform/array_length_from_uniform_test.cc
@@ -0,0 +1,589 @@
+// 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/transform/array_length_from_uniform.h"
+
+#include <utility>
+
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ArrayLengthFromUniformTest = TransformTest;
+
+TEST_F(ArrayLengthFromUniformTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<ArrayLengthFromUniform>(src));
+}
+
+TEST_F(ArrayLengthFromUniformTest, ShouldRunNoArrayLength) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+[[group(0), binding(0)]] var<storage, read> sb : SB;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+}
+)";
+
+  EXPECT_FALSE(ShouldRun<ArrayLengthFromUniform>(src));
+}
+
+TEST_F(ArrayLengthFromUniformTest, ShouldRunWithArrayLength) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+[[group(0), binding(0)]] var<storage, read> sb : SB;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<ArrayLengthFromUniform>(src));
+}
+
+TEST_F(ArrayLengthFromUniformTest, Error_MissingTransformData) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+[[group(0), binding(0)]] var<storage, read> sb : SB;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect =
+      "error: missing transform data for "
+      "tint::transform::ArrayLengthFromUniform";
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ArrayLengthFromUniformTest, Basic) {
+  auto* src = R"(
+@group(0) @binding(0) var<storage, read> sb : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var<storage, read> sb : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 4u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, BasicInStruct) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, WithStride) {
+  auto* src = R"(
+@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 64u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, WithStride_InStruct) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  y : f32;
+  arr : @stride(64) array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB {
+  x : i32;
+  y : f32;
+  arr : @stride(64) array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 8u) / 64u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) {
+  auto* src = R"(
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+};
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+};
+struct SB4 {
+  x : i32;
+  arr4 : array<vec4<f32>>;
+};
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
+@group(3) @binding(2) var<storage, read> sb4 : SB4;
+@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = arrayLength(&(sb1.arr1));
+  var len2 : u32 = arrayLength(&(sb2.arr2));
+  var len3 : u32 = arrayLength(&sb3);
+  var len4 : u32 = arrayLength(&(sb4.arr4));
+  var len5 : u32 = arrayLength(&sb5);
+  var x : u32 = (len1 + len2 + len3 + len4 + len5);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 2u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+}
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+}
+
+struct SB4 {
+  x : i32;
+  arr4 : array<vec4<f32>>;
+}
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+
+@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
+
+@group(3) @binding(2) var<storage, read> sb4 : SB4;
+
+@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+  var len2 : u32 = ((tint_symbol_1.buffer_size[0u][1u] - 16u) / 16u);
+  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
+  var len4 : u32 = ((tint_symbol_1.buffer_size[0u][3u] - 16u) / 16u);
+  var len5 : u32 = (tint_symbol_1.buffer_size[1u][0u] / 16u);
+  var x : u32 = ((((len1 + len2) + len3) + len4) + len5);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0, 1, 2, 3, 4}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, MultipleUnusedStorageBuffers) {
+  auto* src = R"(
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+};
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+};
+struct SB4 {
+  x : i32;
+  arr4 : array<vec4<f32>>;
+};
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
+@group(3) @binding(2) var<storage, read> sb4 : SB4;
+@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = arrayLength(&(sb1.arr1));
+  var len3 : u32 = arrayLength(&sb3);
+  var x : u32 = (len1 + len3);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+}
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+}
+
+struct SB4 {
+  x : i32;
+  arr4 : array<vec4<f32>>;
+}
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+
+@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
+
+@group(3) @binding(2) var<storage, read> sb4 : SB4;
+
+@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
+  var x : u32 = (len1 + len3);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3);
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0, 2}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, NoArrayLengthCalls) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = &(sb.arr);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(src, str(got));
+  EXPECT_EQ(got.data.Get<ArrayLengthFromUniform::Result>(), nullptr);
+}
+
+TEST_F(ArrayLengthFromUniformTest, MissingBindingPointToIndexMapping) {
+  auto* src = R"(
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+};
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+};
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = arrayLength(&(sb1.arr1));
+  var len2 : u32 = arrayLength(&(sb2.arr2));
+  var x : u32 = (len1 + len2);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+}
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+}
+
+@group(0) @binding(2) var<storage, read> sb1 : SB1;
+
+@group(1) @binding(2) var<storage, read> sb2 : SB2;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+  var len2 : u32 = arrayLength(&(sb2.arr2));
+  var x : u32 = (len1 + len2);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/binding_remapper.cc b/src/tint/transform/binding_remapper.cc
new file mode 100644
index 0000000..21155df
--- /dev/null
+++ b/src/tint/transform/binding_remapper.cc
@@ -0,0 +1,163 @@
+// 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/transform/binding_remapper.h"
+
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper::Remappings);
+
+namespace tint {
+namespace transform {
+
+BindingRemapper::Remappings::Remappings(BindingPoints bp,
+                                        AccessControls ac,
+                                        bool may_collide)
+    : binding_points(std::move(bp)),
+      access_controls(std::move(ac)),
+      allow_collisions(may_collide) {}
+
+BindingRemapper::Remappings::Remappings(const Remappings&) = default;
+BindingRemapper::Remappings::~Remappings() = default;
+
+BindingRemapper::BindingRemapper() = default;
+BindingRemapper::~BindingRemapper() = default;
+
+bool BindingRemapper::ShouldRun(const Program*, const DataMap& inputs) const {
+  if (auto* remappings = inputs.Get<Remappings>()) {
+    return !remappings->binding_points.empty() ||
+           !remappings->access_controls.empty();
+  }
+  return false;
+}
+
+void BindingRemapper::Run(CloneContext& ctx,
+                          const DataMap& inputs,
+                          DataMap&) const {
+  auto* remappings = inputs.Get<Remappings>();
+  if (!remappings) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  // A set of post-remapped binding points that need to be decorated with a
+  // DisableValidationAttribute to disable binding-point-collision validation
+  std::unordered_set<sem::BindingPoint> add_collision_attr;
+
+  if (remappings->allow_collisions) {
+    // Scan for binding point collisions generated by this transform.
+    // Populate all collisions in the `add_collision_attr` set.
+    for (auto* func_ast : ctx.src->AST().Functions()) {
+      if (!func_ast->IsEntryPoint()) {
+        continue;
+      }
+      auto* func = ctx.src->Sem().Get(func_ast);
+      std::unordered_map<sem::BindingPoint, int> binding_point_counts;
+      for (auto* var : func->TransitivelyReferencedGlobals()) {
+        if (auto binding_point = var->Declaration()->BindingPoint()) {
+          BindingPoint from{binding_point.group->value,
+                            binding_point.binding->value};
+          auto bp_it = remappings->binding_points.find(from);
+          if (bp_it != remappings->binding_points.end()) {
+            // Remapped
+            BindingPoint to = bp_it->second;
+            if (binding_point_counts[to]++) {
+              add_collision_attr.emplace(to);
+            }
+          } else {
+            // No remapping
+            if (binding_point_counts[from]++) {
+              add_collision_attr.emplace(from);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  for (auto* var : ctx.src->AST().GlobalVariables()) {
+    if (auto binding_point = var->BindingPoint()) {
+      // The original binding point
+      BindingPoint from{binding_point.group->value,
+                        binding_point.binding->value};
+
+      // The binding point after remapping
+      BindingPoint bp = from;
+
+      // Replace any group or binding attributes.
+      // Note: This has to be performed *before* remapping access controls, as
+      // `ctx.Clone(var->attributes)` depend on these replacements.
+      auto bp_it = remappings->binding_points.find(from);
+      if (bp_it != remappings->binding_points.end()) {
+        BindingPoint to = bp_it->second;
+        auto* new_group = ctx.dst->create<ast::GroupAttribute>(to.group);
+        auto* new_binding = ctx.dst->create<ast::BindingAttribute>(to.binding);
+
+        ctx.Replace(binding_point.group, new_group);
+        ctx.Replace(binding_point.binding, new_binding);
+        bp = to;
+      }
+
+      // Replace any access controls.
+      auto ac_it = remappings->access_controls.find(from);
+      if (ac_it != remappings->access_controls.end()) {
+        ast::Access ac = ac_it->second;
+        if (ac > ast::Access::kLastValid) {
+          ctx.dst->Diagnostics().add_error(
+              diag::System::Transform,
+              "invalid access mode (" +
+                  std::to_string(static_cast<uint32_t>(ac)) + ")");
+          return;
+        }
+        auto* sem = ctx.src->Sem().Get(var);
+        if (sem->StorageClass() != ast::StorageClass::kStorage) {
+          ctx.dst->Diagnostics().add_error(
+              diag::System::Transform,
+              "cannot apply access control to variable with storage class " +
+                  std::string(ast::ToString(sem->StorageClass())));
+          return;
+        }
+        auto* ty = sem->Type()->UnwrapRef();
+        const ast::Type* inner_ty = CreateASTTypeFor(ctx, ty);
+        auto* new_var = ctx.dst->create<ast::Variable>(
+            ctx.Clone(var->source), ctx.Clone(var->symbol),
+            var->declared_storage_class, ac, inner_ty, false, false,
+            ctx.Clone(var->constructor), ctx.Clone(var->attributes));
+        ctx.Replace(var, new_var);
+      }
+
+      // Add `DisableValidationAttribute`s if required
+      if (add_collision_attr.count(bp)) {
+        auto* attribute =
+            ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
+        ctx.InsertBefore(var->attributes, *var->attributes.begin(), attribute);
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/binding_remapper.h b/src/tint/transform/binding_remapper.h
new file mode 100644
index 0000000..5cfa3ec
--- /dev/null
+++ b/src/tint/transform/binding_remapper.h
@@ -0,0 +1,92 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_BINDING_REMAPPER_H_
+#define SRC_TINT_TRANSFORM_BINDING_REMAPPER_H_
+
+#include <unordered_map>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// BindingPoint is an alias to sem::BindingPoint
+using BindingPoint = sem::BindingPoint;
+
+/// BindingRemapper is a transform used to remap resource binding points and
+/// access controls.
+class BindingRemapper : public Castable<BindingRemapper, Transform> {
+ public:
+  /// BindingPoints is a map of old binding point to new binding point
+  using BindingPoints = std::unordered_map<BindingPoint, BindingPoint>;
+
+  /// AccessControls is a map of old binding point to new access control
+  using AccessControls = std::unordered_map<BindingPoint, ast::Access>;
+
+  /// Remappings is consumed by the BindingRemapper transform.
+  /// Data holds information about shader usage and constant buffer offsets.
+  struct Remappings : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param bp a map of new binding points
+    /// @param ac a map of new access controls
+    /// @param may_collide If true, then validation will be disabled for
+    /// binding point collisions generated by this transform
+    Remappings(BindingPoints bp, AccessControls ac, bool may_collide = true);
+
+    /// Copy constructor
+    Remappings(const Remappings&);
+
+    /// Destructor
+    ~Remappings() override;
+
+    /// A map of old binding point to new binding point
+    const BindingPoints binding_points;
+
+    /// A map of old binding point to new access controls
+    const AccessControls access_controls;
+
+    /// If true, then validation will be disabled for binding point collisions
+    /// generated by this transform
+    const bool allow_collisions;
+  };
+
+  /// Constructor
+  BindingRemapper();
+  ~BindingRemapper() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_BINDING_REMAPPER_H_
diff --git a/src/tint/transform/binding_remapper_test.cc b/src/tint/transform/binding_remapper_test.cc
new file mode 100644
index 0000000..7816db3
--- /dev/null
+++ b/src/tint/transform/binding_remapper_test.cc
@@ -0,0 +1,423 @@
+// 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/transform/binding_remapper.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using BindingRemapperTest = TransformTest;
+
+TEST_F(BindingRemapperTest, ShouldRunNoRemappings) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<BindingRemapper>(src));
+}
+
+TEST_F(BindingRemapperTest, ShouldRunEmptyRemappings) {
+  auto* src = R"()";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
+                                        BindingRemapper::AccessControls{});
+
+  EXPECT_FALSE(ShouldRun<BindingRemapper>(src, data));
+}
+
+TEST_F(BindingRemapperTest, ShouldRunBindingPointRemappings) {
+  auto* src = R"()";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{
+          {{2, 1}, {1, 2}},
+      },
+      BindingRemapper::AccessControls{});
+
+  EXPECT_TRUE(ShouldRun<BindingRemapper>(src, data));
+}
+
+TEST_F(BindingRemapperTest, ShouldRunAccessControlRemappings) {
+  auto* src = R"()";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
+                                        BindingRemapper::AccessControls{
+                                            {{2, 1}, ast::Access::kWrite},
+                                        });
+
+  EXPECT_TRUE(ShouldRun<BindingRemapper>(src, data));
+}
+
+TEST_F(BindingRemapperTest, NoRemappings) {
+  auto* src = R"(
+struct S {
+  a : f32;
+}
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
+                                        BindingRemapper::AccessControls{});
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, RemapBindingPoints) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+@group(1) @binding(2) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{
+          {{2, 1}, {1, 2}},  // Remap
+          {{4, 5}, {6, 7}},  // Not found
+                             // Keep @group(3) @binding(2) as is
+      },
+      BindingRemapper::AccessControls{});
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, RemapAccessControls) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, write> b : S;
+
+@group(4) @binding(3) var<storage, read> c : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+@group(2) @binding(1) var<storage, write> a : S;
+
+@group(3) @binding(2) var<storage, write> b : S;
+
+@group(4) @binding(3) var<storage, read> c : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{},
+      BindingRemapper::AccessControls{
+          {{2, 1}, ast::Access::kWrite},  // Modify access control
+          // Keep @group(3) @binding(2) as is
+          {{4, 3}, ast::Access::kRead},  // Add access control
+      });
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// TODO(crbug.com/676): Possibly enable if the spec allows for access
+// attributes in type aliases. If not, just remove.
+TEST_F(BindingRemapperTest, DISABLED_RemapAccessControlsWithAliases) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+type, read ReadOnlyS = S;
+
+type, write WriteOnlyS = S;
+
+type A = S;
+
+@group(2) @binding(1) var<storage> a : ReadOnlyS;
+
+@group(3) @binding(2) var<storage> b : WriteOnlyS;
+
+@group(4) @binding(3) var<storage> c : A;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+};
+
+type, read ReadOnlyS = S;
+
+type, write WriteOnlyS = S;
+
+type A = S;
+
+@group(2) @binding(1) var<storage, write> a : S;
+
+@group(3) @binding(2) var<storage> b : WriteOnlyS;
+
+@group(4) @binding(3) var<storage, write> c : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{},
+      BindingRemapper::AccessControls{
+          {{2, 1}, ast::Access::kWrite},  // Modify access control
+          // Keep @group(3) @binding(2) as is
+          {{4, 3}, ast::Access::kRead},  // Add access control
+      });
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, RemapAll) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+@group(4) @binding(5) var<storage, write> a : S;
+
+@group(6) @binding(7) var<storage, write> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{
+          {{2, 1}, {4, 5}},
+          {{3, 2}, {6, 7}},
+      },
+      BindingRemapper::AccessControls{
+          {{2, 1}, ast::Access::kWrite},
+          {{3, 2}, ast::Access::kWrite},
+      });
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, BindingCollisionsSameEntryPoint) {
+  auto* src = R"(
+struct S {
+  i : i32;
+};
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@group(4) @binding(3) var<storage, read> c : S;
+
+@group(5) @binding(4) var<storage, read> d : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : i32 = (((a.i + b.i) + c.i) + d.i);
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  i : i32;
+}
+
+@internal(disable_validation__binding_point_collision) @group(1) @binding(1) var<storage, read> a : S;
+
+@internal(disable_validation__binding_point_collision) @group(1) @binding(1) var<storage, read> b : S;
+
+@internal(disable_validation__binding_point_collision) @group(5) @binding(4) var<storage, read> c : S;
+
+@internal(disable_validation__binding_point_collision) @group(5) @binding(4) var<storage, read> d : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : i32 = (((a.i + b.i) + c.i) + d.i);
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{
+          {{2, 1}, {1, 1}},
+          {{3, 2}, {1, 1}},
+          {{4, 3}, {5, 4}},
+      },
+      BindingRemapper::AccessControls{}, true);
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, BindingCollisionsDifferentEntryPoints) {
+  auto* src = R"(
+struct S {
+  i : i32;
+};
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@group(4) @binding(3) var<storage, read> c : S;
+
+@group(5) @binding(4) var<storage, read> d : S;
+
+@stage(compute) @workgroup_size(1)
+fn f1() {
+  let x : i32 = (a.i + c.i);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f2() {
+  let x : i32 = (b.i + d.i);
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  i : i32;
+}
+
+@group(1) @binding(1) var<storage, read> a : S;
+
+@group(1) @binding(1) var<storage, read> b : S;
+
+@group(5) @binding(4) var<storage, read> c : S;
+
+@group(5) @binding(4) var<storage, read> d : S;
+
+@stage(compute) @workgroup_size(1)
+fn f1() {
+  let x : i32 = (a.i + c.i);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f2() {
+  let x : i32 = (b.i + d.i);
+}
+)";
+
+  DataMap data;
+  data.Add<BindingRemapper::Remappings>(
+      BindingRemapper::BindingPoints{
+          {{2, 1}, {1, 1}},
+          {{3, 2}, {1, 1}},
+          {{4, 3}, {5, 4}},
+      },
+      BindingRemapper::AccessControls{}, true);
+  auto got = Run<BindingRemapper>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BindingRemapperTest, NoData) {
+  auto* src = R"(
+struct S {
+  a : f32;
+}
+
+@group(2) @binding(1) var<storage, read> a : S;
+
+@group(3) @binding(2) var<storage, read> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<BindingRemapper>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
new file mode 100644
index 0000000..05fcb0f
--- /dev/null
+++ b/src/tint/transform/calculate_array_length.cc
@@ -0,0 +1,243 @@
+// 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/transform/calculate_array_length.h"
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::CalculateArrayLength);
+TINT_INSTANTIATE_TYPEINFO(
+    tint::transform::CalculateArrayLength::BufferSizeIntrinsic);
+
+namespace tint {
+namespace transform {
+
+namespace {
+
+/// ArrayUsage describes a runtime array usage.
+/// It is used as a key by the array_length_by_usage map.
+struct ArrayUsage {
+  ast::BlockStatement const* const block;
+  sem::Variable const* const buffer;
+  bool operator==(const ArrayUsage& rhs) const {
+    return block == rhs.block && buffer == rhs.buffer;
+  }
+  struct Hasher {
+    inline std::size_t operator()(const ArrayUsage& u) const {
+      return utils::Hash(u.block, u.buffer);
+    }
+  };
+};
+
+}  // namespace
+
+CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic(ProgramID pid)
+    : Base(pid) {}
+CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default;
+std::string CalculateArrayLength::BufferSizeIntrinsic::InternalName() const {
+  return "intrinsic_buffer_size";
+}
+
+const CalculateArrayLength::BufferSizeIntrinsic*
+CalculateArrayLength::BufferSizeIntrinsic::Clone(CloneContext* ctx) const {
+  return ctx->dst->ASTNodes().Create<CalculateArrayLength::BufferSizeIntrinsic>(
+      ctx->dst->ID());
+}
+
+CalculateArrayLength::CalculateArrayLength() = default;
+CalculateArrayLength::~CalculateArrayLength() = default;
+
+bool CalculateArrayLength::ShouldRun(const Program* program,
+                                     const DataMap&) const {
+  for (auto* fn : program->AST().Functions()) {
+    if (auto* sem_fn = program->Sem().Get(fn)) {
+      for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
+        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+void CalculateArrayLength::Run(CloneContext& ctx,
+                               const DataMap&,
+                               DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  // get_buffer_size_intrinsic() emits the function decorated with
+  // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
+  // [RW]ByteAddressBuffer.GetDimensions().
+  std::unordered_map<const sem::Type*, Symbol> buffer_size_intrinsics;
+  auto get_buffer_size_intrinsic = [&](const sem::Type* buffer_type) {
+    return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
+      auto name = ctx.dst->Sym();
+      auto* type = CreateASTTypeFor(ctx, buffer_type);
+      auto* disable_validation = ctx.dst->Disable(
+          ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
+      ctx.dst->AST().AddFunction(ctx.dst->create<ast::Function>(
+          name,
+          ast::VariableList{
+              // Note: The buffer parameter requires the kStorage StorageClass
+              // in order for HLSL to emit this as a ByteAddressBuffer.
+              ctx.dst->create<ast::Variable>(
+                  ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
+                  ast::Access::kUndefined, type, true, false, nullptr,
+                  ast::AttributeList{disable_validation}),
+              ctx.dst->Param("result",
+                             ctx.dst->ty.pointer(ctx.dst->ty.u32(),
+                                                 ast::StorageClass::kFunction)),
+          },
+          ctx.dst->ty.void_(), nullptr,
+          ast::AttributeList{
+              ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
+          },
+          ast::AttributeList{}));
+
+      return name;
+    });
+  };
+
+  std::unordered_map<ArrayUsage, Symbol, ArrayUsage::Hasher>
+      array_length_by_usage;
+
+  // Find all the arrayLength() calls...
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* call_expr = node->As<ast::CallExpression>()) {
+      auto* call = sem.Get(call_expr);
+      if (auto* builtin = call->Target()->As<sem::Builtin>()) {
+        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+          // We're dealing with an arrayLength() call
+
+          // A runtime-sized array can only appear as the store type of a
+          // variable, or the last element of a structure (which cannot itself
+          // be nested). Given that we require SimplifyPointers, we can assume
+          // that the arrayLength() call has one of two forms:
+          //   arrayLength(&struct_var.array_member)
+          //   arrayLength(&array_var)
+          auto* arg = call_expr->args[0];
+          auto* address_of = arg->As<ast::UnaryOpExpression>();
+          if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
+            TINT_ICE(Transform, ctx.dst->Diagnostics())
+                << "arrayLength() expected address-of, got "
+                << arg->TypeInfo().name;
+          }
+          auto* storage_buffer_expr = address_of->expr;
+          if (auto* accessor =
+                  storage_buffer_expr->As<ast::MemberAccessorExpression>()) {
+            storage_buffer_expr = accessor->structure;
+          }
+          auto* storage_buffer_sem =
+              sem.Get<sem::VariableUser>(storage_buffer_expr);
+          if (!storage_buffer_sem) {
+            TINT_ICE(Transform, ctx.dst->Diagnostics())
+                << "expected form of arrayLength argument to be &array_var or "
+                   "&struct_var.array_member";
+            break;
+          }
+          auto* storage_buffer_var = storage_buffer_sem->Variable();
+          auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
+
+          // Generate BufferSizeIntrinsic for this storage type if we haven't
+          // already
+          auto buffer_size = get_buffer_size_intrinsic(storage_buffer_type);
+
+          // Find the current statement block
+          auto* block = call->Stmt()->Block()->Declaration();
+
+          auto array_length = utils::GetOrCreate(
+              array_length_by_usage, {block, storage_buffer_var}, [&] {
+                // First time this array length is used for this block.
+                // Let's calculate it.
+
+                // Construct the variable that'll hold the result of
+                // RWByteAddressBuffer.GetDimensions()
+                auto* buffer_size_result = ctx.dst->Decl(
+                    ctx.dst->Var(ctx.dst->Sym(), ctx.dst->ty.u32(),
+                                 ast::StorageClass::kNone, ctx.dst->Expr(0u)));
+
+                // Call storage_buffer.GetDimensions(&buffer_size_result)
+                auto* call_get_dims = ctx.dst->CallStmt(ctx.dst->Call(
+                    // BufferSizeIntrinsic(X, ARGS...) is
+                    // translated to:
+                    //  X.GetDimensions(ARGS..) by the writer
+                    buffer_size, ctx.Clone(storage_buffer_expr),
+                    ctx.dst->AddressOf(
+                        ctx.dst->Expr(buffer_size_result->variable->symbol))));
+
+                // Calculate actual array length
+                //                total_storage_buffer_size - array_offset
+                // array_length = ----------------------------------------
+                //                             array_stride
+                auto name = ctx.dst->Sym();
+                const ast::Expression* total_size =
+                    ctx.dst->Expr(buffer_size_result->variable);
+                const sem::Array* array_type = nullptr;
+                if (auto* str = storage_buffer_type->As<sem::Struct>()) {
+                  // The variable is a struct, so subtract the byte offset of
+                  // the array member.
+                  auto* array_member_sem = str->Members().back();
+                  array_type = array_member_sem->Type()->As<sem::Array>();
+                  total_size =
+                      ctx.dst->Sub(total_size, array_member_sem->Offset());
+                } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
+                  array_type = arr;
+                } else {
+                  TINT_ICE(Transform, ctx.dst->Diagnostics())
+                      << "expected form of arrayLength argument to be "
+                         "&array_var or &struct_var.array_member";
+                  return name;
+                }
+                uint32_t array_stride = array_type->Size();
+                auto* array_length_var = ctx.dst->Decl(
+                    ctx.dst->Const(name, ctx.dst->ty.u32(),
+                                   ctx.dst->Div(total_size, array_stride)));
+
+                // Insert the array length calculations at the top of the block
+                ctx.InsertBefore(block->statements, block->statements[0],
+                                 buffer_size_result);
+                ctx.InsertBefore(block->statements, block->statements[0],
+                                 call_get_dims);
+                ctx.InsertBefore(block->statements, block->statements[0],
+                                 array_length_var);
+                return name;
+              });
+
+          // Replace the call to arrayLength() with the array length variable
+          ctx.Replace(call_expr, ctx.dst->Expr(array_length));
+        }
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/calculate_array_length.h b/src/tint/transform/calculate_array_length.h
new file mode 100644
index 0000000..cc18faa
--- /dev/null
+++ b/src/tint/transform/calculate_array_length.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
+#define SRC_TINT_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
+
+#include <string>
+
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace transform {
+
+/// CalculateArrayLength is a transform used to replace calls to arrayLength()
+/// with a value calculated from the size of the storage buffer.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * SimplifyPointers
+class CalculateArrayLength : public Castable<CalculateArrayLength, Transform> {
+ public:
+  /// BufferSizeIntrinsic is an InternalAttribute that's applied to intrinsic
+  /// functions used to obtain the runtime size of a storage buffer.
+  class BufferSizeIntrinsic
+      : public Castable<BufferSizeIntrinsic, ast::InternalAttribute> {
+   public:
+    /// Constructor
+    /// @param program_id the identifier of the program that owns this node
+    explicit BufferSizeIntrinsic(ProgramID program_id);
+    /// Destructor
+    ~BufferSizeIntrinsic() override;
+
+    /// @return "buffer_size"
+    std::string InternalName() const override;
+
+    /// Performs a deep clone of this object using the CloneContext `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned object
+    const BufferSizeIntrinsic* Clone(CloneContext* ctx) const override;
+  };
+
+  /// Constructor
+  CalculateArrayLength();
+  /// Destructor
+  ~CalculateArrayLength() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
diff --git a/src/tint/transform/calculate_array_length_test.cc b/src/tint/transform/calculate_array_length_test.cc
new file mode 100644
index 0000000..9bd7eb0
--- /dev/null
+++ b/src/tint/transform/calculate_array_length_test.cc
@@ -0,0 +1,625 @@
+// 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/transform/calculate_array_length.h"
+
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using CalculateArrayLengthTest = TransformTest;
+
+TEST_F(CalculateArrayLengthTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<CalculateArrayLength>(src));
+}
+
+TEST_F(CalculateArrayLengthTest, ShouldRunNoArrayLength) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+[[group(0), binding(0)]] var<storage, read> sb : SB;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+}
+)";
+
+  EXPECT_FALSE(ShouldRun<CalculateArrayLength>(src));
+}
+
+TEST_F(CalculateArrayLengthTest, ShouldRunWithArrayLength) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+[[group(0), binding(0)]] var<storage, read> sb : SB;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<CalculateArrayLength>(src));
+}
+
+TEST_F(CalculateArrayLengthTest, BasicArray) {
+  auto* src = R"(
+@group(0) @binding(0) var<storage, read> sb : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
+
+@group(0) @binding(0) var<storage, read> sb : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, BasicInStruct) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, ArrayOfStruct) {
+  auto* src = R"(
+struct S {
+  f : f32;
+}
+
+@group(0) @binding(0) var<storage, read> arr : array<S>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let len = arrayLength(&arr);
+}
+)";
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<S>, result : ptr<function, u32>)
+
+struct S {
+  f : f32;
+}
+
+@group(0) @binding(0) var<storage, read> arr : array<S>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(arr, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
+  let len = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, ArrayOfArrayOfStruct) {
+  auto* src = R"(
+struct S {
+  f : f32;
+}
+
+@group(0) @binding(0) var<storage, read> arr : array<array<S, 4>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let len = arrayLength(&arr);
+}
+)";
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<array<S, 4u>>, result : ptr<function, u32>)
+
+struct S {
+  f : f32;
+}
+
+@group(0) @binding(0) var<storage, read> arr : array<array<S, 4>>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(arr, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 16u);
+  let len = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, InSameBlock) {
+  auto* src = R"(
+@group(0) @binding(0) var<storage, read> sb : array<i32>;;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : u32 = arrayLength(&sb);
+  var b : u32 = arrayLength(&sb);
+  var c : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
+
+@group(0) @binding(0) var<storage, read> sb : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
+  var a : u32 = tint_symbol_2;
+  var b : u32 = tint_symbol_2;
+  var c : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, InSameBlock_Struct) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : u32 = arrayLength(&sb.arr);
+  var b : u32 = arrayLength(&sb.arr);
+  var c : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+  var a : u32 = tint_symbol_2;
+  var b : u32 = tint_symbol_2;
+  var c : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, WithStride) {
+  auto* src = R"(
+@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : @stride(64) array<i32>, result : ptr<function, u32>)
+
+@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 64u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, WithStride_InStruct) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  y : f32;
+  arr : @stride(64) array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len : u32 = arrayLength(&sb.arr);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
+
+struct SB {
+  x : i32;
+  y : f32;
+  arr : @stride(64) array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 8u) / 64u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, Nested) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  if (true) {
+    var len : u32 = arrayLength(&sb.arr);
+  } else {
+    if (true) {
+      var len : u32 = arrayLength(&sb.arr);
+    }
+  }
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  if (true) {
+    var tint_symbol_1 : u32 = 0u;
+    tint_symbol(sb, &(tint_symbol_1));
+    let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+    var len : u32 = tint_symbol_2;
+  } else {
+    if (true) {
+      var tint_symbol_3 : u32 = 0u;
+      tint_symbol(sb, &(tint_symbol_3));
+      let tint_symbol_4 : u32 = ((tint_symbol_3 - 4u) / 4u);
+      var len : u32 = tint_symbol_4;
+    }
+  }
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, MultipleStorageBuffers) {
+  auto* src = R"(
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+};
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+};
+
+@group(0) @binding(0) var<storage, read> sb1 : SB1;
+
+@group(0) @binding(1) var<storage, read> sb2 : SB2;
+
+@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = arrayLength(&(sb1.arr1));
+  var len2 : u32 = arrayLength(&(sb2.arr2));
+  var len3 : u32 = arrayLength(&sb3);
+  var x : u32 = (len1 + len2 + len3);
+}
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB1, result : ptr<function, u32>)
+
+@internal(intrinsic_buffer_size)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB2, result : ptr<function, u32>)
+
+@internal(intrinsic_buffer_size)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+}
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+}
+
+@group(0) @binding(0) var<storage, read> sb1 : SB1;
+
+@group(0) @binding(1) var<storage, read> sb2 : SB2;
+
+@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb1, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+  var tint_symbol_4 : u32 = 0u;
+  tint_symbol_3(sb2, &(tint_symbol_4));
+  let tint_symbol_5 : u32 = ((tint_symbol_4 - 16u) / 16u);
+  var tint_symbol_7 : u32 = 0u;
+  tint_symbol_6(sb3, &(tint_symbol_7));
+  let tint_symbol_8 : u32 = (tint_symbol_7 / 4u);
+  var len1 : u32 = tint_symbol_2;
+  var len2 : u32 = tint_symbol_5;
+  var len3 : u32 = tint_symbol_8;
+  var x : u32 = ((len1 + len2) + len3);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, Shadowing) {
+  auto* src = R"(
+struct SB {
+  x : i32;
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read> a : SB;
+@group(0) @binding(1) var<storage, read> b : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = &a;
+  var a : u32 = arrayLength(&a.arr);
+  {
+    var b : u32 = arrayLength(&((*x).arr));
+  }
+}
+)";
+
+  auto* expect =
+      R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
+
+struct SB {
+  x : i32;
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read> a : SB;
+
+@group(0) @binding(1) var<storage, read> b : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(a, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+  var a_1 : u32 = tint_symbol_2;
+  {
+    var tint_symbol_3 : u32 = 0u;
+    tint_symbol(a, &(tint_symbol_3));
+    let tint_symbol_4 : u32 = ((tint_symbol_3 - 4u) / 4u);
+    var b_1 : u32 = tint_symbol_4;
+  }
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var len1 : u32 = arrayLength(&(sb1.arr1));
+  var len2 : u32 = arrayLength(&(sb2.arr2));
+  var len3 : u32 = arrayLength(&sb3);
+  var x : u32 = (len1 + len2 + len3);
+}
+
+@group(0) @binding(0) var<storage, read> sb1 : SB1;
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+};
+
+@group(0) @binding(1) var<storage, read> sb2 : SB2;
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+};
+
+@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB1, result : ptr<function, u32>)
+
+@internal(intrinsic_buffer_size)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB2, result : ptr<function, u32>)
+
+@internal(intrinsic_buffer_size)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb1, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+  var tint_symbol_4 : u32 = 0u;
+  tint_symbol_3(sb2, &(tint_symbol_4));
+  let tint_symbol_5 : u32 = ((tint_symbol_4 - 16u) / 16u);
+  var tint_symbol_7 : u32 = 0u;
+  tint_symbol_6(sb3, &(tint_symbol_7));
+  let tint_symbol_8 : u32 = (tint_symbol_7 / 4u);
+  var len1 : u32 = tint_symbol_2;
+  var len2 : u32 = tint_symbol_5;
+  var len3 : u32 = tint_symbol_8;
+  var x : u32 = ((len1 + len2) + len3);
+}
+
+@group(0) @binding(0) var<storage, read> sb1 : SB1;
+
+struct SB1 {
+  x : i32;
+  arr1 : array<i32>;
+}
+
+@group(0) @binding(1) var<storage, read> sb2 : SB2;
+
+struct SB2 {
+  x : i32;
+  arr2 : array<vec4<f32>>;
+}
+
+@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
new file mode 100644
index 0000000..1eaa5db
--- /dev/null
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -0,0 +1,779 @@
+// 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/transform/canonicalize_entry_point_io.h"
+
+#include <algorithm>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/transform/unshadow.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::CanonicalizeEntryPointIO);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::CanonicalizeEntryPointIO::Config);
+
+namespace tint {
+namespace transform {
+
+CanonicalizeEntryPointIO::CanonicalizeEntryPointIO() = default;
+CanonicalizeEntryPointIO::~CanonicalizeEntryPointIO() = default;
+
+namespace {
+
+// Comparison function used to reorder struct members such that all members with
+// location attributes appear first (ordered by location slot), followed by
+// those with builtin attributes.
+bool StructMemberComparator(const ast::StructMember* a,
+                            const ast::StructMember* b) {
+  auto* a_loc = ast::GetAttribute<ast::LocationAttribute>(a->attributes);
+  auto* b_loc = ast::GetAttribute<ast::LocationAttribute>(b->attributes);
+  auto* a_blt = ast::GetAttribute<ast::BuiltinAttribute>(a->attributes);
+  auto* b_blt = ast::GetAttribute<ast::BuiltinAttribute>(b->attributes);
+  if (a_loc) {
+    if (!b_loc) {
+      // `a` has location attribute and `b` does not: `a` goes first.
+      return true;
+    }
+    // Both have location attributes: smallest goes first.
+    return a_loc->value < b_loc->value;
+  } else {
+    if (b_loc) {
+      // `b` has location attribute and `a` does not: `b` goes first.
+      return false;
+    }
+    // Both are builtins: order doesn't matter, just use enum value.
+    return a_blt->builtin < b_blt->builtin;
+  }
+}
+
+// Returns true if `attr` is a shader IO attribute.
+bool IsShaderIOAttribute(const ast::Attribute* attr) {
+  return attr->IsAnyOf<ast::BuiltinAttribute, ast::InterpolateAttribute,
+                       ast::InvariantAttribute, ast::LocationAttribute>();
+}
+
+// Returns true if `attrs` contains a `sample_mask` builtin.
+bool HasSampleMask(const ast::AttributeList& attrs) {
+  auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attrs);
+  return builtin && builtin->builtin == ast::Builtin::kSampleMask;
+}
+
+}  // namespace
+
+/// State holds the current transform state for a single entry point.
+struct CanonicalizeEntryPointIO::State {
+  /// OutputValue represents a shader result that the wrapper function produces.
+  struct OutputValue {
+    /// The name of the output value.
+    std::string name;
+    /// The type of the output value.
+    const ast::Type* type;
+    /// The shader IO attributes.
+    ast::AttributeList attributes;
+    /// The value itself.
+    const ast::Expression* value;
+  };
+
+  /// The clone context.
+  CloneContext& ctx;
+  /// The transform config.
+  CanonicalizeEntryPointIO::Config const cfg;
+  /// The entry point function (AST).
+  const ast::Function* func_ast;
+  /// The entry point function (SEM).
+  const sem::Function* func_sem;
+
+  /// The new entry point wrapper function's parameters.
+  ast::VariableList wrapper_ep_parameters;
+  /// The members of the wrapper function's struct parameter.
+  ast::StructMemberList wrapper_struct_param_members;
+  /// The name of the wrapper function's struct parameter.
+  Symbol wrapper_struct_param_name;
+  /// The parameters that will be passed to the original function.
+  ast::ExpressionList inner_call_parameters;
+  /// The members of the wrapper function's struct return type.
+  ast::StructMemberList wrapper_struct_output_members;
+  /// The wrapper function output values.
+  std::vector<OutputValue> wrapper_output_values;
+  /// The body of the wrapper function.
+  ast::StatementList wrapper_body;
+  /// Input names used by the entrypoint
+  std::unordered_set<std::string> input_names;
+
+  /// Constructor
+  /// @param context the clone context
+  /// @param config the transform config
+  /// @param function the entry point function
+  State(CloneContext& context,
+        const CanonicalizeEntryPointIO::Config& config,
+        const ast::Function* function)
+      : ctx(context),
+        cfg(config),
+        func_ast(function),
+        func_sem(ctx.src->Sem().Get(function)) {}
+
+  /// Clones the shader IO attributes from `src`.
+  /// @param src the attributes to clone
+  /// @param do_interpolate whether to clone InterpolateAttribute
+  /// @return the cloned attributes
+  ast::AttributeList CloneShaderIOAttributes(const ast::AttributeList& src,
+                                             bool do_interpolate) {
+    ast::AttributeList new_attributes;
+    for (auto* attr : src) {
+      if (IsShaderIOAttribute(attr) &&
+          (do_interpolate || !attr->Is<ast::InterpolateAttribute>())) {
+        new_attributes.push_back(ctx.Clone(attr));
+      }
+    }
+    return new_attributes;
+  }
+
+  /// Create or return a symbol for the wrapper function's struct parameter.
+  /// @returns the symbol for the struct parameter
+  Symbol InputStructSymbol() {
+    if (!wrapper_struct_param_name.IsValid()) {
+      wrapper_struct_param_name = ctx.dst->Sym();
+    }
+    return wrapper_struct_param_name;
+  }
+
+  /// Add a shader input to the entry point.
+  /// @param name the name of the shader input
+  /// @param type the type of the shader input
+  /// @param attributes the attributes to apply to the shader input
+  /// @returns an expression which evaluates to the value of the shader input
+  const ast::Expression* AddInput(std::string name,
+                                  const sem::Type* type,
+                                  ast::AttributeList attributes) {
+    auto* ast_type = CreateASTTypeFor(ctx, type);
+    if (cfg.shader_style == ShaderStyle::kSpirv ||
+        cfg.shader_style == ShaderStyle::kGlsl) {
+      // Vulkan requires that integer user-defined fragment inputs are
+      // always decorated with `Flat`.
+      // TODO(crbug.com/tint/1224): Remove this once a flat interpolation
+      // attribute is required for integers.
+      if (type->is_integer_scalar_or_vector() &&
+          ast::HasAttribute<ast::LocationAttribute>(attributes) &&
+          !ast::HasAttribute<ast::InterpolateAttribute>(attributes) &&
+          func_ast->PipelineStage() == ast::PipelineStage::kFragment) {
+        attributes.push_back(ctx.dst->Interpolate(
+            ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone));
+      }
+
+      // Disable validation for use of the `input` storage class.
+      attributes.push_back(
+          ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+
+      // In GLSL, if it's a builtin, override the name with the
+      // corresponding gl_ builtin name
+      auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attributes);
+      if (cfg.shader_style == ShaderStyle::kGlsl && builtin) {
+        name = GLSLBuiltinToString(builtin->builtin, func_ast->PipelineStage(),
+                                   ast::StorageClass::kInput);
+      }
+      auto symbol = ctx.dst->Symbols().New(name);
+
+      // Create the global variable and use its value for the shader input.
+      const ast::Expression* value = ctx.dst->Expr(symbol);
+
+      if (builtin) {
+        if (cfg.shader_style == ShaderStyle::kGlsl) {
+          value = FromGLSLBuiltin(builtin->builtin, value, ast_type);
+        } else if (builtin->builtin == ast::Builtin::kSampleMask) {
+          // Vulkan requires the type of a SampleMask builtin to be an array.
+          // Declare it as array<u32, 1> and then load the first element.
+          ast_type = ctx.dst->ty.array(ast_type, 1);
+          value = ctx.dst->IndexAccessor(value, 0);
+        }
+      }
+      ctx.dst->Global(symbol, ast_type, ast::StorageClass::kInput,
+                      std::move(attributes));
+      return value;
+    } else if (cfg.shader_style == ShaderStyle::kMsl &&
+               ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
+      // If this input is a builtin and we are targeting MSL, then add it to the
+      // parameter list and pass it directly to the inner function.
+      Symbol symbol = input_names.emplace(name).second
+                          ? ctx.dst->Symbols().Register(name)
+                          : ctx.dst->Symbols().New(name);
+      wrapper_ep_parameters.push_back(
+          ctx.dst->Param(symbol, ast_type, std::move(attributes)));
+      return ctx.dst->Expr(symbol);
+    } else {
+      // Otherwise, move it to the new structure member list.
+      Symbol symbol = input_names.emplace(name).second
+                          ? ctx.dst->Symbols().Register(name)
+                          : ctx.dst->Symbols().New(name);
+      wrapper_struct_param_members.push_back(
+          ctx.dst->Member(symbol, ast_type, std::move(attributes)));
+      return ctx.dst->MemberAccessor(InputStructSymbol(), symbol);
+    }
+  }
+
+  /// Add a shader output to the entry point.
+  /// @param name the name of the shader output
+  /// @param type the type of the shader output
+  /// @param attributes the attributes to apply to the shader output
+  /// @param value the value of the shader output
+  void AddOutput(std::string name,
+                 const sem::Type* type,
+                 ast::AttributeList attributes,
+                 const ast::Expression* value) {
+    // Vulkan requires that integer user-defined vertex outputs are
+    // always decorated with `Flat`.
+    // TODO(crbug.com/tint/1224): Remove this once a flat interpolation
+    // attribute is required for integers.
+    if (cfg.shader_style == ShaderStyle::kSpirv &&
+        type->is_integer_scalar_or_vector() &&
+        ast::HasAttribute<ast::LocationAttribute>(attributes) &&
+        !ast::HasAttribute<ast::InterpolateAttribute>(attributes) &&
+        func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
+      attributes.push_back(ctx.dst->Interpolate(
+          ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone));
+    }
+
+    // In GLSL, if it's a builtin, override the name with the
+    // corresponding gl_ builtin name
+    if (cfg.shader_style == ShaderStyle::kGlsl) {
+      if (auto* b = ast::GetAttribute<ast::BuiltinAttribute>(attributes)) {
+        name = GLSLBuiltinToString(b->builtin, func_ast->PipelineStage(),
+                                   ast::StorageClass::kOutput);
+        value = ToGLSLBuiltin(b->builtin, value, type);
+      }
+    }
+
+    OutputValue output;
+    output.name = name;
+    output.type = CreateASTTypeFor(ctx, type);
+    output.attributes = std::move(attributes);
+    output.value = value;
+    wrapper_output_values.push_back(output);
+  }
+
+  /// Process a non-struct parameter.
+  /// This creates a new object for the shader input, moving the shader IO
+  /// attributes to it. It also adds an expression to the list of parameters
+  /// that will be passed to the original function.
+  /// @param param the original function parameter
+  void ProcessNonStructParameter(const sem::Parameter* param) {
+    // Remove the shader IO attributes from the inner function parameter, and
+    // attach them to the new object instead.
+    ast::AttributeList attributes;
+    for (auto* attr : param->Declaration()->attributes) {
+      if (IsShaderIOAttribute(attr)) {
+        ctx.Remove(param->Declaration()->attributes, attr);
+        attributes.push_back(ctx.Clone(attr));
+      }
+    }
+
+    auto name = ctx.src->Symbols().NameFor(param->Declaration()->symbol);
+    auto* input_expr = AddInput(name, param->Type(), std::move(attributes));
+    inner_call_parameters.push_back(input_expr);
+  }
+
+  /// Process a struct parameter.
+  /// This creates new objects for each struct member, moving the shader IO
+  /// attributes to them. It also creates the structure that will be passed to
+  /// the original function.
+  /// @param param the original function parameter
+  void ProcessStructParameter(const sem::Parameter* param) {
+    auto* str = param->Type()->As<sem::Struct>();
+
+    // Recreate struct members in the outer entry point and build an initializer
+    // list to pass them through to the inner function.
+    ast::ExpressionList inner_struct_values;
+    for (auto* member : str->Members()) {
+      if (member->Type()->Is<sem::Struct>()) {
+        TINT_ICE(Transform, ctx.dst->Diagnostics()) << "nested IO struct";
+        continue;
+      }
+
+      auto* member_ast = member->Declaration();
+      auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
+
+      // In GLSL, do not add interpolation attributes on vertex input
+      bool do_interpolate = true;
+      if (cfg.shader_style == ShaderStyle::kGlsl &&
+          func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
+        do_interpolate = false;
+      }
+      auto attributes =
+          CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
+      auto* input_expr = AddInput(name, member->Type(), std::move(attributes));
+      inner_struct_values.push_back(input_expr);
+    }
+
+    // Construct the original structure using the new shader input objects.
+    inner_call_parameters.push_back(ctx.dst->Construct(
+        ctx.Clone(param->Declaration()->type), inner_struct_values));
+  }
+
+  /// Process the entry point return type.
+  /// This generates a list of output values that are returned by the original
+  /// function.
+  /// @param inner_ret_type the original function return type
+  /// @param original_result the result object produced by the original function
+  void ProcessReturnType(const sem::Type* inner_ret_type,
+                         Symbol original_result) {
+    bool do_interpolate = true;
+    // In GLSL, do not add interpolation attributes on fragment output
+    if (cfg.shader_style == ShaderStyle::kGlsl &&
+        func_ast->PipelineStage() == ast::PipelineStage::kFragment) {
+      do_interpolate = false;
+    }
+    if (auto* str = inner_ret_type->As<sem::Struct>()) {
+      for (auto* member : str->Members()) {
+        if (member->Type()->Is<sem::Struct>()) {
+          TINT_ICE(Transform, ctx.dst->Diagnostics()) << "nested IO struct";
+          continue;
+        }
+
+        auto* member_ast = member->Declaration();
+        auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
+        auto attributes =
+            CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
+
+        // Extract the original structure member.
+        AddOutput(name, member->Type(), std::move(attributes),
+                  ctx.dst->MemberAccessor(original_result, name));
+      }
+    } else if (!inner_ret_type->Is<sem::Void>()) {
+      auto attributes = CloneShaderIOAttributes(
+          func_ast->return_type_attributes, do_interpolate);
+
+      // Propagate the non-struct return value as is.
+      AddOutput("value", func_sem->ReturnType(), std::move(attributes),
+                ctx.dst->Expr(original_result));
+    }
+  }
+
+  /// Add a fixed sample mask to the wrapper function output.
+  /// If there is already a sample mask, bitwise-and it with the fixed mask.
+  /// Otherwise, create a new output value from the fixed mask.
+  void AddFixedSampleMask() {
+    // Check the existing output values for a sample mask builtin.
+    for (auto& outval : wrapper_output_values) {
+      if (HasSampleMask(outval.attributes)) {
+        // Combine the authored sample mask with the fixed mask.
+        outval.value = ctx.dst->And(outval.value, cfg.fixed_sample_mask);
+        return;
+      }
+    }
+
+    // No existing sample mask builtin was found, so create a new output value
+    // using the fixed sample mask.
+    AddOutput("fixed_sample_mask", ctx.dst->create<sem::U32>(),
+              {ctx.dst->Builtin(ast::Builtin::kSampleMask)},
+              ctx.dst->Expr(cfg.fixed_sample_mask));
+  }
+
+  /// Add a point size builtin to the wrapper function output.
+  void AddVertexPointSize() {
+    // Create a new output value and assign it a literal 1.0 value.
+    AddOutput("vertex_point_size", ctx.dst->create<sem::F32>(),
+              {ctx.dst->Builtin(ast::Builtin::kPointSize)}, ctx.dst->Expr(1.f));
+  }
+
+  /// Create an expression for gl_Position.[component]
+  /// @param component the component of gl_Position to access
+  /// @returns the new expression
+  const ast::Expression* GLPosition(const char* component) {
+    Symbol pos = ctx.dst->Symbols().Register("gl_Position");
+    Symbol c = ctx.dst->Symbols().Register(component);
+    return ctx.dst->MemberAccessor(ctx.dst->Expr(pos), ctx.dst->Expr(c));
+  }
+
+  /// Create the wrapper function's struct parameter and type objects.
+  void CreateInputStruct() {
+    // Sort the struct members to satisfy HLSL interfacing matching rules.
+    std::sort(wrapper_struct_param_members.begin(),
+              wrapper_struct_param_members.end(), StructMemberComparator);
+
+    // Create the new struct type.
+    auto struct_name = ctx.dst->Sym();
+    auto* in_struct = ctx.dst->create<ast::Struct>(
+        struct_name, wrapper_struct_param_members, ast::AttributeList{});
+    ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, in_struct);
+
+    // Create a new function parameter using this struct type.
+    auto* param =
+        ctx.dst->Param(InputStructSymbol(), ctx.dst->ty.type_name(struct_name));
+    wrapper_ep_parameters.push_back(param);
+  }
+
+  /// Create and return the wrapper function's struct result object.
+  /// @returns the struct type
+  ast::Struct* CreateOutputStruct() {
+    ast::StatementList assignments;
+
+    auto wrapper_result = ctx.dst->Symbols().New("wrapper_result");
+
+    // Create the struct members and their corresponding assignment statements.
+    std::unordered_set<std::string> member_names;
+    for (auto& outval : wrapper_output_values) {
+      // Use the original output name, unless that is already taken.
+      Symbol name;
+      if (member_names.count(outval.name)) {
+        name = ctx.dst->Symbols().New(outval.name);
+      } else {
+        name = ctx.dst->Symbols().Register(outval.name);
+      }
+      member_names.insert(ctx.dst->Symbols().NameFor(name));
+
+      wrapper_struct_output_members.push_back(
+          ctx.dst->Member(name, outval.type, std::move(outval.attributes)));
+      assignments.push_back(ctx.dst->Assign(
+          ctx.dst->MemberAccessor(wrapper_result, name), outval.value));
+    }
+
+    // Sort the struct members to satisfy HLSL interfacing matching rules.
+    std::sort(wrapper_struct_output_members.begin(),
+              wrapper_struct_output_members.end(), StructMemberComparator);
+
+    // Create the new struct type.
+    auto* out_struct = ctx.dst->create<ast::Struct>(
+        ctx.dst->Sym(), wrapper_struct_output_members, ast::AttributeList{});
+    ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, out_struct);
+
+    // Create the output struct object, assign its members, and return it.
+    auto* result_object =
+        ctx.dst->Var(wrapper_result, ctx.dst->ty.type_name(out_struct->name));
+    wrapper_body.push_back(ctx.dst->Decl(result_object));
+    wrapper_body.insert(wrapper_body.end(), assignments.begin(),
+                        assignments.end());
+    wrapper_body.push_back(ctx.dst->Return(wrapper_result));
+
+    return out_struct;
+  }
+
+  /// Create and assign the wrapper function's output variables.
+  void CreateGlobalOutputVariables() {
+    for (auto& outval : wrapper_output_values) {
+      // Disable validation for use of the `output` storage class.
+      ast::AttributeList attributes = std::move(outval.attributes);
+      attributes.push_back(
+          ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+
+      // Create the global variable and assign it the output value.
+      auto name = ctx.dst->Symbols().New(outval.name);
+      auto* type = outval.type;
+      const ast::Expression* lhs = ctx.dst->Expr(name);
+      if (HasSampleMask(attributes)) {
+        // Vulkan requires the type of a SampleMask builtin to be an array.
+        // Declare it as array<u32, 1> and then store to the first element.
+        type = ctx.dst->ty.array(type, 1);
+        lhs = ctx.dst->IndexAccessor(lhs, 0);
+      }
+      ctx.dst->Global(name, type, ast::StorageClass::kOutput,
+                      std::move(attributes));
+      wrapper_body.push_back(ctx.dst->Assign(lhs, outval.value));
+    }
+  }
+
+  // Recreate the original function without entry point attributes and call it.
+  /// @returns the inner function call expression
+  const ast::CallExpression* CallInnerFunction() {
+    Symbol inner_name;
+    if (cfg.shader_style == ShaderStyle::kGlsl) {
+      // In GLSL, clone the original entry point name, as the wrapper will be
+      // called "main".
+      inner_name = ctx.Clone(func_ast->symbol);
+    } else {
+      // Add a suffix to the function name, as the wrapper function will take
+      // the original entry point name.
+      auto ep_name = ctx.src->Symbols().NameFor(func_ast->symbol);
+      inner_name = ctx.dst->Symbols().New(ep_name + "_inner");
+    }
+
+    // Clone everything, dropping the function and return type attributes.
+    // The parameter attributes will have already been stripped during
+    // processing.
+    auto* inner_function = ctx.dst->create<ast::Function>(
+        inner_name, ctx.Clone(func_ast->params),
+        ctx.Clone(func_ast->return_type), ctx.Clone(func_ast->body),
+        ast::AttributeList{}, ast::AttributeList{});
+    ctx.Replace(func_ast, inner_function);
+
+    // Call the function.
+    return ctx.dst->Call(inner_function->symbol, inner_call_parameters);
+  }
+
+  /// Process the entry point function.
+  void Process() {
+    bool needs_fixed_sample_mask = false;
+    bool needs_vertex_point_size = false;
+    if (func_ast->PipelineStage() == ast::PipelineStage::kFragment &&
+        cfg.fixed_sample_mask != 0xFFFFFFFF) {
+      needs_fixed_sample_mask = true;
+    }
+    if (func_ast->PipelineStage() == ast::PipelineStage::kVertex &&
+        cfg.emit_vertex_point_size) {
+      needs_vertex_point_size = true;
+    }
+
+    // Exit early if there is no shader IO to handle.
+    if (func_sem->Parameters().size() == 0 &&
+        func_sem->ReturnType()->Is<sem::Void>() && !needs_fixed_sample_mask &&
+        !needs_vertex_point_size && cfg.shader_style != ShaderStyle::kGlsl) {
+      return;
+    }
+
+    // Process the entry point parameters, collecting those that need to be
+    // aggregated into a single structure.
+    if (!func_sem->Parameters().empty()) {
+      for (auto* param : func_sem->Parameters()) {
+        if (param->Type()->Is<sem::Struct>()) {
+          ProcessStructParameter(param);
+        } else {
+          ProcessNonStructParameter(param);
+        }
+      }
+
+      // Create a structure parameter for the outer entry point if necessary.
+      if (!wrapper_struct_param_members.empty()) {
+        CreateInputStruct();
+      }
+    }
+
+    // Recreate the original function and call it.
+    auto* call_inner = CallInnerFunction();
+
+    // Process the return type, and start building the wrapper function body.
+    std::function<const ast::Type*()> wrapper_ret_type = [&] {
+      return ctx.dst->ty.void_();
+    };
+    if (func_sem->ReturnType()->Is<sem::Void>()) {
+      // The function call is just a statement with no result.
+      wrapper_body.push_back(ctx.dst->CallStmt(call_inner));
+    } else {
+      // Capture the result of calling the original function.
+      auto* inner_result = ctx.dst->Const(
+          ctx.dst->Symbols().New("inner_result"), nullptr, call_inner);
+      wrapper_body.push_back(ctx.dst->Decl(inner_result));
+
+      // Process the original return type to determine the outputs that the
+      // outer function needs to produce.
+      ProcessReturnType(func_sem->ReturnType(), inner_result->symbol);
+    }
+
+    // Add a fixed sample mask, if necessary.
+    if (needs_fixed_sample_mask) {
+      AddFixedSampleMask();
+    }
+
+    // Add the pointsize builtin, if necessary.
+    if (needs_vertex_point_size) {
+      AddVertexPointSize();
+    }
+
+    // Produce the entry point outputs, if necessary.
+    if (!wrapper_output_values.empty()) {
+      if (cfg.shader_style == ShaderStyle::kSpirv ||
+          cfg.shader_style == ShaderStyle::kGlsl) {
+        CreateGlobalOutputVariables();
+      } else {
+        auto* output_struct = CreateOutputStruct();
+        wrapper_ret_type = [&, output_struct] {
+          return ctx.dst->ty.type_name(output_struct->name);
+        };
+      }
+    }
+
+    if (cfg.shader_style == ShaderStyle::kGlsl &&
+        func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
+      auto* pos_y = GLPosition("y");
+      auto* negate_pos_y = ctx.dst->create<ast::UnaryOpExpression>(
+          ast::UnaryOp::kNegation, GLPosition("y"));
+      wrapper_body.push_back(ctx.dst->Assign(pos_y, negate_pos_y));
+
+      auto* two_z = ctx.dst->Mul(ctx.dst->Expr(2.0f), GLPosition("z"));
+      auto* fixed_z = ctx.dst->Sub(two_z, GLPosition("w"));
+      wrapper_body.push_back(ctx.dst->Assign(GLPosition("z"), fixed_z));
+    }
+
+    // Create the wrapper entry point function.
+    // For GLSL, use "main", otherwise take the name of the original
+    // entry point function.
+    Symbol name;
+    if (cfg.shader_style == ShaderStyle::kGlsl) {
+      name = ctx.dst->Symbols().New("main");
+    } else {
+      name = ctx.Clone(func_ast->symbol);
+    }
+
+    auto* wrapper_func = ctx.dst->create<ast::Function>(
+        name, wrapper_ep_parameters, wrapper_ret_type(),
+        ctx.dst->Block(wrapper_body), ctx.Clone(func_ast->attributes),
+        ast::AttributeList{});
+    ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), func_ast,
+                    wrapper_func);
+  }
+
+  /// Retrieve the gl_ string corresponding to a builtin.
+  /// @param builtin the builtin
+  /// @param stage the current pipeline stage
+  /// @param storage_class the storage class (input or output)
+  /// @returns the gl_ string corresponding to that builtin
+  const char* GLSLBuiltinToString(ast::Builtin builtin,
+                                  ast::PipelineStage stage,
+                                  ast::StorageClass storage_class) {
+    switch (builtin) {
+      case ast::Builtin::kPosition:
+        switch (stage) {
+          case ast::PipelineStage::kVertex:
+            return "gl_Position";
+          case ast::PipelineStage::kFragment:
+            return "gl_FragCoord";
+          default:
+            return "";
+        }
+      case ast::Builtin::kVertexIndex:
+        return "gl_VertexID";
+      case ast::Builtin::kInstanceIndex:
+        return "gl_InstanceID";
+      case ast::Builtin::kFrontFacing:
+        return "gl_FrontFacing";
+      case ast::Builtin::kFragDepth:
+        return "gl_FragDepth";
+      case ast::Builtin::kLocalInvocationId:
+        return "gl_LocalInvocationID";
+      case ast::Builtin::kLocalInvocationIndex:
+        return "gl_LocalInvocationIndex";
+      case ast::Builtin::kGlobalInvocationId:
+        return "gl_GlobalInvocationID";
+      case ast::Builtin::kNumWorkgroups:
+        return "gl_NumWorkGroups";
+      case ast::Builtin::kWorkgroupId:
+        return "gl_WorkGroupID";
+      case ast::Builtin::kSampleIndex:
+        return "gl_SampleID";
+      case ast::Builtin::kSampleMask:
+        if (storage_class == ast::StorageClass::kInput) {
+          return "gl_SampleMaskIn";
+        } else {
+          return "gl_SampleMask";
+        }
+      default:
+        return "";
+    }
+  }
+
+  /// Convert a given GLSL builtin value to the corresponding WGSL value.
+  /// @param builtin the builtin variable
+  /// @param value the value to convert
+  /// @param ast_type (inout) the incoming WGSL and outgoing GLSL types
+  /// @returns an expression representing the GLSL builtin converted to what
+  /// WGSL expects
+  const ast::Expression* FromGLSLBuiltin(ast::Builtin builtin,
+                                         const ast::Expression* value,
+                                         const ast::Type*& ast_type) {
+    switch (builtin) {
+      case ast::Builtin::kVertexIndex:
+      case ast::Builtin::kInstanceIndex:
+      case ast::Builtin::kSampleIndex:
+        // GLSL uses i32 for these, so bitcast to u32.
+        value = ctx.dst->Bitcast(ast_type, value);
+        ast_type = ctx.dst->ty.i32();
+        break;
+      case ast::Builtin::kSampleMask:
+        // gl_SampleMask is an array of i32. Retrieve the first element and
+        // bitcast it to u32.
+        value = ctx.dst->IndexAccessor(value, 0);
+        value = ctx.dst->Bitcast(ast_type, value);
+        ast_type = ctx.dst->ty.array(ctx.dst->ty.i32(), 1);
+        break;
+      default:
+        break;
+    }
+    return value;
+  }
+
+  /// Convert a given WGSL value to the type expected when assigning to a
+  /// GLSL builtin.
+  /// @param builtin the builtin variable
+  /// @param value the value to convert
+  /// @param type (out) the type to which the value was converted
+  /// @returns the converted value which can be assigned to the GLSL builtin
+  const ast::Expression* ToGLSLBuiltin(ast::Builtin builtin,
+                                       const ast::Expression* value,
+                                       const sem::Type*& type) {
+    switch (builtin) {
+      case ast::Builtin::kVertexIndex:
+      case ast::Builtin::kInstanceIndex:
+      case ast::Builtin::kSampleIndex:
+      case ast::Builtin::kSampleMask:
+        type = ctx.dst->create<sem::I32>();
+        value = ctx.dst->Bitcast(CreateASTTypeFor(ctx, type), value);
+        break;
+      default:
+        break;
+    }
+    return value;
+  }
+};
+
+void CanonicalizeEntryPointIO::Run(CloneContext& ctx,
+                                   const DataMap& inputs,
+                                   DataMap&) const {
+  auto* cfg = inputs.Get<Config>();
+  if (cfg == nullptr) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  // Remove entry point IO attributes from struct declarations.
+  // New structures will be created for each entry point, as necessary.
+  for (auto* ty : ctx.src->AST().TypeDecls()) {
+    if (auto* struct_ty = ty->As<ast::Struct>()) {
+      for (auto* member : struct_ty->members) {
+        for (auto* attr : member->attributes) {
+          if (IsShaderIOAttribute(attr)) {
+            ctx.Remove(member->attributes, attr);
+          }
+        }
+      }
+    }
+  }
+
+  for (auto* func_ast : ctx.src->AST().Functions()) {
+    if (!func_ast->IsEntryPoint()) {
+      continue;
+    }
+
+    State state(ctx, *cfg, func_ast);
+    state.Process();
+  }
+
+  ctx.Clone();
+}
+
+CanonicalizeEntryPointIO::Config::Config(ShaderStyle style,
+                                         uint32_t sample_mask,
+                                         bool emit_point_size)
+    : shader_style(style),
+      fixed_sample_mask(sample_mask),
+      emit_vertex_point_size(emit_point_size) {}
+
+CanonicalizeEntryPointIO::Config::Config(const Config&) = default;
+CanonicalizeEntryPointIO::Config::~Config() = default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/canonicalize_entry_point_io.h b/src/tint/transform/canonicalize_entry_point_io.h
new file mode 100644
index 0000000..1d65e41
--- /dev/null
+++ b/src/tint/transform/canonicalize_entry_point_io.h
@@ -0,0 +1,149 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
+#define SRC_TINT_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// CanonicalizeEntryPointIO is a transform used to rewrite shader entry point
+/// interfaces into a form that the generators can handle. Each entry point
+/// function is stripped of all shader IO attributes and wrapped in a function
+/// that provides the shader interface.
+/// The transform config determines whether to use global variables, structures,
+/// or parameters for the shader inputs and outputs, and optionally adds
+/// additional builtins to the shader interface.
+///
+/// Before:
+/// ```
+/// struct Locations{
+///   @location(1) loc1 : f32;
+///   @location(2) loc2 : vec4<u32>;
+/// };
+///
+/// @stage(fragment)
+/// fn frag_main(@builtin(position) coord : vec4<f32>,
+///              locations : Locations) -> @location(0) f32 {
+///   if (coord.w > 1.0) {
+///     return 0.0;
+///   }
+///   var col : f32 = (coord.x * locations.loc1);
+///   return col;
+/// }
+/// ```
+///
+/// After (using structures for all parameters):
+/// ```
+/// struct Locations{
+///   loc1 : f32;
+///   loc2 : vec4<u32>;
+/// };
+///
+/// struct frag_main_in {
+///   @builtin(position) coord : vec4<f32>;
+///   @location(1) loc1 : f32;
+///   @location(2) loc2 : vec4<u32>
+/// };
+///
+/// struct frag_main_out {
+///   @location(0) loc0 : f32;
+/// };
+///
+/// fn frag_main_inner(coord : vec4<f32>,
+///                    locations : Locations) -> f32 {
+///   if (coord.w > 1.0) {
+///     return 0.0;
+///   }
+///   var col : f32 = (coord.x * locations.loc1);
+///   return col;
+/// }
+///
+/// @stage(fragment)
+/// fn frag_main(in : frag_main_in) -> frag_main_out {
+///   let inner_retval = frag_main_inner(in.coord, Locations(in.loc1, in.loc2));
+///   var wrapper_result : frag_main_out;
+///   wrapper_result.loc0 = inner_retval;
+///   return wrapper_result;
+/// }
+/// ```
+///
+/// @note Depends on the following transforms to have been run first:
+/// * Unshadow
+class CanonicalizeEntryPointIO
+    : public Castable<CanonicalizeEntryPointIO, Transform> {
+ public:
+  /// ShaderStyle is an enumerator of different ways to emit shader IO.
+  enum class ShaderStyle {
+    /// Target SPIR-V (using global variables).
+    kSpirv,
+    /// Target GLSL (using global variables).
+    kGlsl,
+    /// Target MSL (using non-struct function parameters for builtins).
+    kMsl,
+    /// Target HLSL (using structures for all IO).
+    kHlsl,
+  };
+
+  /// Configuration options for the transform.
+  struct Config : public Castable<Config, Data> {
+    /// Constructor
+    /// @param style the approach to use for emitting shader IO.
+    /// @param sample_mask an optional sample mask to combine with shader masks
+    /// @param emit_vertex_point_size `true` to generate a pointsize builtin
+    explicit Config(ShaderStyle style,
+                    uint32_t sample_mask = 0xFFFFFFFF,
+                    bool emit_vertex_point_size = false);
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// The approach to use for emitting shader IO.
+    const ShaderStyle shader_style;
+
+    /// A fixed sample mask to combine into masks produced by fragment shaders.
+    const uint32_t fixed_sample_mask;
+
+    /// Set to `true` to generate a pointsize builtin and have it set to 1.0
+    /// from all vertex shaders in the module.
+    const bool emit_vertex_point_size;
+  };
+
+  /// Constructor
+  CanonicalizeEntryPointIO();
+  ~CanonicalizeEntryPointIO() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+  struct State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
diff --git a/src/tint/transform/canonicalize_entry_point_io_test.cc b/src/tint/transform/canonicalize_entry_point_io_test.cc
new file mode 100644
index 0000000..b4bcfb8
--- /dev/null
+++ b/src/tint/transform/canonicalize_entry_point_io_test.cc
@@ -0,0 +1,4041 @@
+// 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/transform/canonicalize_entry_point_io.h"
+
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using CanonicalizeEntryPointIOTest = TransformTest;
+
+TEST_F(CanonicalizeEntryPointIOTest, Error_MissingTransformData) {
+  auto* src = "";
+
+  auto* expect =
+      "error: missing transform data for "
+      "tint::transform::CanonicalizeEntryPointIO";
+
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, NoShaderIO) {
+  // Test that we do not introduce wrapper functions when there is no shader IO
+  // to process.
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() {
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main() {
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Parameters_Spirv) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(1) loc1 : f32,
+             @location(2) @interpolate(flat) loc2 : vec4<u32>,
+             @builtin(position) coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+)";
+
+  auto* expect = R"(
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
+
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+
+@stage(fragment)
+fn frag_main() {
+  frag_main_inner(loc1_1, loc2_1, coord_1);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Parameters_Msl) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(1) loc1 : f32,
+             @location(2) @interpolate(flat) loc2 : vec4<u32>,
+             @builtin(position) coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+}
+
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+
+@stage(fragment)
+fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, coord);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Parameters_Hlsl) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(1) loc1 : f32,
+             @location(2) @interpolate(flat) loc2 : vec4<u32>,
+             @builtin(position) coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+  @builtin(position)
+  coord : vec4<f32>;
+}
+
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
+  var col : f32 = (coord.x * loc1);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, tint_symbol.coord);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Parameter_TypeAlias) {
+  auto* src = R"(
+type myf32 = f32;
+
+@stage(fragment)
+fn frag_main(@location(1) loc1 : myf32) {
+  var x : myf32 = loc1;
+}
+)";
+
+  auto* expect = R"(
+type myf32 = f32;
+
+struct tint_symbol_1 {
+  @location(1)
+  loc1 : f32;
+}
+
+fn frag_main_inner(loc1 : myf32) {
+  var x : myf32 = loc1;
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc1);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Parameter_TypeAlias_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(1) loc1 : myf32) {
+  var x : myf32 = loc1;
+}
+
+type myf32 = f32;
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(1)
+  loc1 : f32;
+}
+
+fn frag_main_inner(loc1 : myf32) {
+  var x : myf32 = loc1;
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc1);
+}
+
+type myf32 = f32;
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv) {
+  auto* src = R"(
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> loc0_1 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
+
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main() {
+  frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> loc0_1 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main() {
+  frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
+}
+
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl) {
+  auto* src = R"(
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+)";
+
+  auto* expect = R"(
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  loc0 : f32;
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  loc0 : f32;
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
+}
+
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl) {
+  auto* src = R"(
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+)";
+
+  auto* expect = R"(
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  loc0 : f32;
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+  @builtin(position)
+  coord : vec4<f32>;
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(@location(0) loc0 : f32,
+             locations : FragLocations,
+             builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+struct FragBuiltins {
+  @builtin(position) coord : vec4<f32>;
+};
+struct FragLocations {
+  @location(1) loc1 : f32;
+  @location(2) @interpolate(flat) loc2 : vec4<u32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  loc0 : f32;
+  @location(1)
+  loc1 : f32;
+  @location(2) @interpolate(flat)
+  loc2 : vec4<u32>;
+  @builtin(position)
+  coord : vec4<f32>;
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
+  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
+}
+
+struct FragBuiltins {
+  coord : vec4<f32>;
+}
+
+struct FragLocations {
+  loc1 : f32;
+  loc2 : vec4<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Spirv) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> @builtin(frag_depth) f32 {
+  return 1.0;
+}
+)";
+
+  auto* expect = R"(
+@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> value : f32;
+
+fn frag_main_inner() -> f32 {
+  return 1.0;
+}
+
+@stage(fragment)
+fn frag_main() {
+  let inner_result = frag_main_inner();
+  value = inner_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Msl) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> @builtin(frag_depth) f32 {
+  return 1.0;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(frag_depth)
+  value : f32;
+}
+
+fn frag_main_inner() -> f32 {
+  return 1.0;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Hlsl) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> @builtin(frag_depth) f32 {
+  return 1.0;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(frag_depth)
+  value : f32;
+}
+
+fn frag_main_inner() -> f32 {
+  return 1.0;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Spirv) {
+  auto* src = R"(
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<out> color_1 : vec4<f32>;
+
+@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> depth_1 : f32;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> mask_1 : array<u32, 1>;
+
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() {
+  let inner_result = frag_main_inner();
+  color_1 = inner_result.color;
+  depth_1 = inner_result.depth;
+  mask_1[0] = inner_result.mask;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Spirv_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<out> color_1 : vec4<f32>;
+
+@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> depth_1 : f32;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> mask_1 : array<u32, 1>;
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() {
+  let inner_result = frag_main_inner();
+  color_1 = inner_result.color;
+  depth_1 = inner_result.depth;
+  mask_1[0] = inner_result.mask;
+}
+
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Msl) {
+  auto* src = R"(
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+)";
+
+  auto* expect = R"(
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+
+struct tint_symbol {
+  @location(0)
+  color : vec4<f32>;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = inner_result.mask;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Msl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  color : vec4<f32>;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = inner_result.mask;
+  return wrapper_result;
+}
+
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Hlsl) {
+  auto* src = R"(
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+)";
+
+  auto* expect = R"(
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+
+struct tint_symbol {
+  @location(0)
+  color : vec4<f32>;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = inner_result.mask;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Hlsl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+struct FragOutput {
+  @location(0) color : vec4<f32>;
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  color : vec4<f32>;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> FragOutput {
+  var output : FragOutput;
+  output.depth = 1.0;
+  output.mask = 7u;
+  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = inner_result.mask;
+  return wrapper_result;
+}
+
+struct FragOutput {
+  color : vec4<f32>;
+  depth : f32;
+  mask : u32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Spirv) {
+  auto* src = R"(
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_1 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_1 : f32;
+
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_2 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_2 : f32;
+
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1() {
+  frag_main1_inner(FragmentInput(value_1, mul_1));
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2() {
+  frag_main2_inner(FragmentInput(value_2, mul_2));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Spirv_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_1 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_1 : f32;
+
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_2 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_2 : f32;
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1() {
+  frag_main1_inner(FragmentInput(value_1, mul_1));
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2() {
+  frag_main2_inner(FragmentInput(value_2, mul_2));
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Msl) {
+  auto* src = R"(
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+)";
+
+  auto* expect = R"(
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
+}
+
+struct tint_symbol_3 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(tint_symbol_2 : tint_symbol_3) {
+  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Msl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
+}
+
+struct tint_symbol_3 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(tint_symbol_2 : tint_symbol_3) {
+  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Hlsl) {
+  auto* src = R"(
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+)";
+
+  auto* expect = R"(
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
+}
+
+struct tint_symbol_3 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(tint_symbol_2 : tint_symbol_3) {
+  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       StructParameters_SharedDeviceFunction_Hlsl_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return x.value * x.mul;
+}
+
+struct FragmentInput {
+  @location(0) value : f32;
+  @location(1) mul : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
+}
+
+struct tint_symbol_3 {
+  @location(0)
+  value : f32;
+  @location(1)
+  mul : f32;
+}
+
+fn frag_main2_inner(inputs : FragmentInput) {
+  var x : f32 = foo(inputs);
+}
+
+@stage(fragment)
+fn frag_main2(tint_symbol_2 : tint_symbol_3) {
+  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
+}
+
+fn foo(x : FragmentInput) -> f32 {
+  return (x.value * x.mul);
+}
+
+struct FragmentInput {
+  value : f32;
+  mul : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_ModuleScopeVariable) {
+  auto* src = R"(
+struct FragmentInput {
+  @location(0) col1 : f32;
+  @location(1) col2 : f32;
+};
+
+var<private> global_inputs : FragmentInput;
+
+fn foo() -> f32 {
+  return global_inputs.col1 * 0.5;
+}
+
+fn bar() -> f32 {
+  return global_inputs.col2 * 2.0;
+}
+
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+ global_inputs = inputs;
+ var r : f32 = foo();
+ var g : f32 = bar();
+}
+)";
+
+  auto* expect = R"(
+struct FragmentInput {
+  col1 : f32;
+  col2 : f32;
+}
+
+var<private> global_inputs : FragmentInput;
+
+fn foo() -> f32 {
+  return (global_inputs.col1 * 0.5);
+}
+
+fn bar() -> f32 {
+  return (global_inputs.col2 * 2.0);
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  global_inputs = inputs;
+  var r : f32 = foo();
+  var g : f32 = bar();
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.col1, tint_symbol.col2));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_ModuleScopeVariable_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main1(inputs : FragmentInput) {
+ global_inputs = inputs;
+ var r : f32 = foo();
+ var g : f32 = bar();
+}
+
+fn foo() -> f32 {
+  return global_inputs.col1 * 0.5;
+}
+
+fn bar() -> f32 {
+  return global_inputs.col2 * 2.0;
+}
+
+var<private> global_inputs : FragmentInput;
+
+struct FragmentInput {
+  @location(0) col1 : f32;
+  @location(1) col2 : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+fn frag_main1_inner(inputs : FragmentInput) {
+  global_inputs = inputs;
+  var r : f32 = foo();
+  var g : f32 = bar();
+}
+
+@stage(fragment)
+fn frag_main1(tint_symbol : tint_symbol_1) {
+  frag_main1_inner(FragmentInput(tint_symbol.col1, tint_symbol.col2));
+}
+
+fn foo() -> f32 {
+  return (global_inputs.col1 * 0.5);
+}
+
+fn bar() -> f32 {
+  return (global_inputs.col2 * 2.0);
+}
+
+var<private> global_inputs : FragmentInput;
+
+struct FragmentInput {
+  col1 : f32;
+  col2 : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_TypeAliases) {
+  auto* src = R"(
+type myf32 = f32;
+
+struct FragmentInput {
+  @location(0) col1 : myf32;
+  @location(1) col2 : myf32;
+};
+
+struct FragmentOutput {
+  @location(0) col1 : myf32;
+  @location(1) col2 : myf32;
+};
+
+type MyFragmentInput = FragmentInput;
+
+type MyFragmentOutput = FragmentOutput;
+
+fn foo(x : MyFragmentInput) -> myf32 {
+  return x.col1;
+}
+
+@stage(fragment)
+fn frag_main(inputs : MyFragmentInput) -> MyFragmentOutput {
+  var x : myf32 = foo(inputs);
+  return MyFragmentOutput(x, inputs.col2);
+}
+)";
+
+  auto* expect = R"(
+type myf32 = f32;
+
+struct FragmentInput {
+  col1 : myf32;
+  col2 : myf32;
+}
+
+struct FragmentOutput {
+  col1 : myf32;
+  col2 : myf32;
+}
+
+type MyFragmentInput = FragmentInput;
+
+type MyFragmentOutput = FragmentOutput;
+
+fn foo(x : MyFragmentInput) -> myf32 {
+  return x.col1;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+fn frag_main_inner(inputs : MyFragmentInput) -> MyFragmentOutput {
+  var x : myf32 = foo(inputs);
+  return MyFragmentOutput(x, inputs.col2);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = frag_main_inner(MyFragmentInput(tint_symbol.col1, tint_symbol.col2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_TypeAliases_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(inputs : MyFragmentInput) -> MyFragmentOutput {
+  var x : myf32 = foo(inputs);
+  return MyFragmentOutput(x, inputs.col2);
+}
+
+type MyFragmentInput = FragmentInput;
+
+type MyFragmentOutput = FragmentOutput;
+
+fn foo(x : MyFragmentInput) -> myf32 {
+  return x.col1;
+}
+
+struct FragmentInput {
+  @location(0) col1 : myf32;
+  @location(1) col2 : myf32;
+};
+
+struct FragmentOutput {
+  @location(0) col1 : myf32;
+  @location(1) col2 : myf32;
+};
+
+type myf32 = f32;
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  col1 : f32;
+  @location(1)
+  col2 : f32;
+}
+
+fn frag_main_inner(inputs : MyFragmentInput) -> MyFragmentOutput {
+  var x : myf32 = foo(inputs);
+  return MyFragmentOutput(x, inputs.col2);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = frag_main_inner(MyFragmentInput(tint_symbol.col1, tint_symbol.col2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  return wrapper_result;
+}
+
+type MyFragmentInput = FragmentInput;
+
+type MyFragmentOutput = FragmentOutput;
+
+fn foo(x : MyFragmentInput) -> myf32 {
+  return x.col1;
+}
+
+struct FragmentInput {
+  col1 : myf32;
+  col2 : myf32;
+}
+
+struct FragmentOutput {
+  col1 : myf32;
+  col2 : myf32;
+}
+
+type myf32 = f32;
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes) {
+  auto* src = R"(
+struct VertexOut {
+  @builtin(position) pos : vec4<f32>;
+  @location(1) @interpolate(flat) loc1: f32;
+  @location(2) @interpolate(linear, sample) loc2 : f32;
+  @location(3) @interpolate(perspective, centroid) loc3 : f32;
+};
+
+struct FragmentIn {
+  @location(1) @interpolate(flat) loc1: f32;
+  @location(2) @interpolate(linear, sample) loc2 : f32;
+};
+
+@stage(vertex)
+fn vert_main() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(fragment)
+fn frag_main(inputs : FragmentIn,
+             @location(3) @interpolate(perspective, centroid) loc3 : f32) {
+  let x = inputs.loc1 + inputs.loc2 + loc3;
+}
+)";
+
+  auto* expect = R"(
+struct VertexOut {
+  pos : vec4<f32>;
+  loc1 : f32;
+  loc2 : f32;
+  loc3 : f32;
+}
+
+struct FragmentIn {
+  loc1 : f32;
+  loc2 : f32;
+}
+
+struct tint_symbol {
+  @location(1) @interpolate(flat)
+  loc1 : f32;
+  @location(2) @interpolate(linear, sample)
+  loc2 : f32;
+  @location(3) @interpolate(perspective, centroid)
+  loc3 : f32;
+  @builtin(position)
+  pos : vec4<f32>;
+}
+
+fn vert_main_inner() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  @location(1) @interpolate(flat)
+  loc1 : f32;
+  @location(2) @interpolate(linear, sample)
+  loc2 : f32;
+  @location(3) @interpolate(perspective, centroid)
+  loc3 : f32;
+}
+
+fn frag_main_inner(inputs : FragmentIn, loc3 : f32) {
+  let x = ((inputs.loc1 + inputs.loc2) + loc3);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol_1 : tint_symbol_2) {
+  frag_main_inner(FragmentIn(tint_symbol_1.loc1, tint_symbol_1.loc2), tint_symbol_1.loc3);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(inputs : FragmentIn,
+             @location(3) @interpolate(perspective, centroid) loc3 : f32) {
+  let x = inputs.loc1 + inputs.loc2 + loc3;
+}
+
+@stage(vertex)
+fn vert_main() -> VertexOut {
+  return VertexOut();
+}
+
+struct VertexOut {
+  @builtin(position) pos : vec4<f32>;
+  @location(1) @interpolate(flat) loc1: f32;
+  @location(2) @interpolate(linear, sample) loc2 : f32;
+  @location(3) @interpolate(perspective, centroid) loc3 : f32;
+};
+
+struct FragmentIn {
+  @location(1) @interpolate(flat) loc1: f32;
+  @location(2) @interpolate(linear, sample) loc2 : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(1) @interpolate(flat)
+  loc1 : f32;
+  @location(2) @interpolate(linear, sample)
+  loc2 : f32;
+  @location(3) @interpolate(perspective, centroid)
+  loc3 : f32;
+}
+
+fn frag_main_inner(inputs : FragmentIn, loc3 : f32) {
+  let x = ((inputs.loc1 + inputs.loc2) + loc3);
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) {
+  frag_main_inner(FragmentIn(tint_symbol.loc1, tint_symbol.loc2), tint_symbol.loc3);
+}
+
+struct tint_symbol_2 {
+  @location(1) @interpolate(flat)
+  loc1 : f32;
+  @location(2) @interpolate(linear, sample)
+  loc2 : f32;
+  @location(3) @interpolate(perspective, centroid)
+  loc3 : f32;
+  @builtin(position)
+  pos : vec4<f32>;
+}
+
+fn vert_main_inner() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol_2 {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  return wrapper_result;
+}
+
+struct VertexOut {
+  pos : vec4<f32>;
+  loc1 : f32;
+  loc2 : f32;
+  loc3 : f32;
+}
+
+struct FragmentIn {
+  loc1 : f32;
+  loc2 : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes_Integers_Spirv) {
+  // Test that we add a Flat attribute to integers that are vertex outputs and
+  // fragment inputs, but not vertex inputs or fragment outputs.
+  auto* src = R"(
+struct VertexIn {
+  @location(0) i : i32;
+  @location(1) u : u32;
+  @location(2) vi : vec4<i32>;
+  @location(3) vu : vec4<u32>;
+};
+
+struct VertexOut {
+  @location(0) @interpolate(flat) i : i32;
+  @location(1) @interpolate(flat) u : u32;
+  @location(2) @interpolate(flat) vi : vec4<i32>;
+  @location(3) @interpolate(flat) vu : vec4<u32>;
+  @builtin(position) pos : vec4<f32>;
+};
+
+struct FragmentInterface {
+  @location(0) @interpolate(flat) i : i32;
+  @location(1) @interpolate(flat) u : u32;
+  @location(2) @interpolate(flat) vi : vec4<i32>;
+  @location(3) @interpolate(flat) vu : vec4<u32>;
+};
+
+@stage(vertex)
+fn vert_main(in : VertexIn) -> VertexOut {
+  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
+}
+
+@stage(fragment)
+fn frag_main(inputs : FragmentInterface) -> FragmentInterface {
+  return inputs;
+}
+)";
+
+  auto* expect =
+      R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> i_1 : i32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> u_1 : u32;
+
+@location(2) @internal(disable_validation__ignore_storage_class) var<in> vi_1 : vec4<i32>;
+
+@location(3) @internal(disable_validation__ignore_storage_class) var<in> vu_1 : vec4<u32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_2 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_2 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_2 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_2 : vec4<u32>;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> i_3 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> u_3 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vi_3 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vu_3 : vec4<u32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_4 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_4 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_4 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_4 : vec4<u32>;
+
+struct VertexIn {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+}
+
+struct VertexOut {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+  pos : vec4<f32>;
+}
+
+struct FragmentInterface {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+}
+
+fn vert_main_inner(in : VertexIn) -> VertexOut {
+  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner(VertexIn(i_1, u_1, vi_1, vu_1));
+  i_2 = inner_result.i;
+  u_2 = inner_result.u;
+  vi_2 = inner_result.vi;
+  vu_2 = inner_result.vu;
+  pos_1 = inner_result.pos;
+}
+
+fn frag_main_inner(inputs : FragmentInterface) -> FragmentInterface {
+  return inputs;
+}
+
+@stage(fragment)
+fn frag_main() {
+  let inner_result_1 = frag_main_inner(FragmentInterface(i_3, u_3, vi_3, vu_3));
+  i_4 = inner_result_1.i;
+  u_4 = inner_result_1.u;
+  vi_4 = inner_result_1.vi;
+  vu_4 = inner_result_1.vu;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       InterpolateAttributes_Integers_Spirv_OutOfOrder) {
+  // Test that we add a Flat attribute to integers that are vertex outputs and
+  // fragment inputs, but not vertex inputs or fragment outputs.
+  auto* src = R"(
+@stage(vertex)
+fn vert_main(in : VertexIn) -> VertexOut {
+  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
+}
+
+@stage(fragment)
+fn frag_main(inputs : FragmentInterface) -> FragmentInterface {
+  return inputs;
+}
+
+struct VertexIn {
+  @location(0) i : i32;
+  @location(1) u : u32;
+  @location(2) vi : vec4<i32>;
+  @location(3) vu : vec4<u32>;
+};
+
+struct VertexOut {
+  @location(0) @interpolate(flat) i : i32;
+  @location(1) @interpolate(flat) u : u32;
+  @location(2) @interpolate(flat) vi : vec4<i32>;
+  @location(3) @interpolate(flat) vu : vec4<u32>;
+  @builtin(position) pos : vec4<f32>;
+};
+
+struct FragmentInterface {
+  @location(0) @interpolate(flat) i : i32;
+  @location(1) @interpolate(flat) u : u32;
+  @location(2) @interpolate(flat) vi : vec4<i32>;
+  @location(3) @interpolate(flat) vu : vec4<u32>;
+};
+)";
+
+  auto* expect =
+      R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> i_1 : i32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> u_1 : u32;
+
+@location(2) @internal(disable_validation__ignore_storage_class) var<in> vi_1 : vec4<i32>;
+
+@location(3) @internal(disable_validation__ignore_storage_class) var<in> vu_1 : vec4<u32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_2 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_2 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_2 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_2 : vec4<u32>;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> i_3 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> u_3 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vi_3 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vu_3 : vec4<u32>;
+
+@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_4 : i32;
+
+@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_4 : u32;
+
+@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_4 : vec4<i32>;
+
+@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_4 : vec4<u32>;
+
+fn vert_main_inner(in : VertexIn) -> VertexOut {
+  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner(VertexIn(i_1, u_1, vi_1, vu_1));
+  i_2 = inner_result.i;
+  u_2 = inner_result.u;
+  vi_2 = inner_result.vi;
+  vu_2 = inner_result.vu;
+  pos_1 = inner_result.pos;
+}
+
+fn frag_main_inner(inputs : FragmentInterface) -> FragmentInterface {
+  return inputs;
+}
+
+@stage(fragment)
+fn frag_main() {
+  let inner_result_1 = frag_main_inner(FragmentInterface(i_3, u_3, vi_3, vu_3));
+  i_4 = inner_result_1.i;
+  u_4 = inner_result_1.u;
+  vi_4 = inner_result_1.vi;
+  vu_4 = inner_result_1.vu;
+}
+
+struct VertexIn {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+}
+
+struct VertexOut {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+  pos : vec4<f32>;
+}
+
+struct FragmentInterface {
+  i : i32;
+  u : u32;
+  vi : vec4<i32>;
+  vu : vec4<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, InvariantAttributes) {
+  auto* src = R"(
+struct VertexOut {
+  [[builtin(position), invariant]] pos : vec4<f32>;
+};
+
+@stage(vertex)
+fn main1() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn main2() -> [[builtin(position), invariant]] vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct VertexOut {
+  pos : vec4<f32>;
+}
+
+struct tint_symbol {
+  @builtin(position) @invariant
+  pos : vec4<f32>;
+}
+
+fn main1_inner() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn main1() -> tint_symbol {
+  let inner_result = main1_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  @builtin(position) @invariant
+  value : vec4<f32>;
+}
+
+fn main2_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn main2() -> tint_symbol_1 {
+  let inner_result_1 = main2_inner();
+  var wrapper_result_1 : tint_symbol_1;
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, InvariantAttributes_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn main1() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn main2() -> [[builtin(position), invariant]] vec4<f32> {
+  return vec4<f32>();
+}
+
+struct VertexOut {
+  [[builtin(position), invariant]] pos : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(position) @invariant
+  pos : vec4<f32>;
+}
+
+fn main1_inner() -> VertexOut {
+  return VertexOut();
+}
+
+@stage(vertex)
+fn main1() -> tint_symbol {
+  let inner_result = main1_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  @builtin(position) @invariant
+  value : vec4<f32>;
+}
+
+fn main2_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn main2() -> tint_symbol_1 {
+  let inner_result_1 = main2_inner();
+  var wrapper_result_1 : tint_symbol_1;
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+
+struct VertexOut {
+  pos : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutAttributes) {
+  auto* src = R"(
+struct FragmentInput {
+  @size(16) @location(1) value : f32;
+  @builtin(position) @align(32) coord : vec4<f32>;
+  @location(0) @interpolate(linear, sample) @align(128) loc0 : f32;
+};
+
+struct FragmentOutput {
+  @size(16) @location(1) @interpolate(flat) value : f32;
+};
+
+@stage(fragment)
+fn frag_main(inputs : FragmentInput) -> FragmentOutput {
+  return FragmentOutput(inputs.coord.x * inputs.value + inputs.loc0);
+}
+)";
+
+  auto* expect = R"(
+struct FragmentInput {
+  @size(16)
+  value : f32;
+  @align(32)
+  coord : vec4<f32>;
+  @align(128)
+  loc0 : f32;
+}
+
+struct FragmentOutput {
+  @size(16)
+  value : f32;
+}
+
+struct tint_symbol_1 {
+  @location(0) @interpolate(linear, sample)
+  loc0 : f32;
+  @location(1)
+  value : f32;
+  @builtin(position)
+  coord : vec4<f32>;
+}
+
+struct tint_symbol_2 {
+  @location(1) @interpolate(flat)
+  value : f32;
+}
+
+fn frag_main_inner(inputs : FragmentInput) -> FragmentOutput {
+  return FragmentOutput(((inputs.coord.x * inputs.value) + inputs.loc0));
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = frag_main_inner(FragmentInput(tint_symbol.value, tint_symbol.coord, tint_symbol.loc0));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.value = inner_result.value;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutAttributes_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main(inputs : FragmentInput) -> FragmentOutput {
+  return FragmentOutput(inputs.coord.x * inputs.value + inputs.loc0);
+}
+
+struct FragmentInput {
+  @size(16) @location(1) value : f32;
+  @builtin(position) @align(32) coord : vec4<f32>;
+  @location(0) @interpolate(linear, sample) @align(128) loc0 : f32;
+};
+
+struct FragmentOutput {
+  @size(16) @location(1) @interpolate(flat) value : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0) @interpolate(linear, sample)
+  loc0 : f32;
+  @location(1)
+  value : f32;
+  @builtin(position)
+  coord : vec4<f32>;
+}
+
+struct tint_symbol_2 {
+  @location(1) @interpolate(flat)
+  value : f32;
+}
+
+fn frag_main_inner(inputs : FragmentInput) -> FragmentOutput {
+  return FragmentOutput(((inputs.coord.x * inputs.value) + inputs.loc0));
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = frag_main_inner(FragmentInput(tint_symbol.value, tint_symbol.coord, tint_symbol.loc0));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.value = inner_result.value;
+  return wrapper_result;
+}
+
+struct FragmentInput {
+  @size(16)
+  value : f32;
+  @align(32)
+  coord : vec4<f32>;
+  @align(128)
+  loc0 : f32;
+}
+
+struct FragmentOutput {
+  @size(16)
+  value : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, SortedMembers) {
+  auto* src = R"(
+struct VertexOutput {
+  @location(1) @interpolate(flat) b : u32;
+  @builtin(position) pos : vec4<f32>;
+  @location(3) @interpolate(flat) d : u32;
+  @location(0) a : f32;
+  @location(2) @interpolate(flat) c : i32;
+};
+
+struct FragmentInputExtra {
+  @location(3) @interpolate(flat) d : u32;
+  @builtin(position) pos : vec4<f32>;
+  @location(0) a : f32;
+};
+
+@stage(vertex)
+fn vert_main() -> VertexOutput {
+  return VertexOutput();
+}
+
+@stage(fragment)
+fn frag_main(@builtin(front_facing) ff : bool,
+             @location(2) @interpolate(flat) c : i32,
+             inputs : FragmentInputExtra,
+             @location(1) @interpolate(flat) b : u32) {
+}
+)";
+
+  auto* expect = R"(
+struct VertexOutput {
+  b : u32;
+  pos : vec4<f32>;
+  d : u32;
+  a : f32;
+  c : i32;
+}
+
+struct FragmentInputExtra {
+  d : u32;
+  pos : vec4<f32>;
+  a : f32;
+}
+
+struct tint_symbol {
+  @location(0)
+  a : f32;
+  @location(1) @interpolate(flat)
+  b : u32;
+  @location(2) @interpolate(flat)
+  c : i32;
+  @location(3) @interpolate(flat)
+  d : u32;
+  @builtin(position)
+  pos : vec4<f32>;
+}
+
+fn vert_main_inner() -> VertexOutput {
+  return VertexOutput();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.b = inner_result.b;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.d = inner_result.d;
+  wrapper_result.a = inner_result.a;
+  wrapper_result.c = inner_result.c;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  a : f32;
+  @location(1) @interpolate(flat)
+  b : u32;
+  @location(2) @interpolate(flat)
+  c : i32;
+  @location(3) @interpolate(flat)
+  d : u32;
+  @builtin(position)
+  pos : vec4<f32>;
+  @builtin(front_facing)
+  ff : bool;
+}
+
+fn frag_main_inner(ff : bool, c : i32, inputs : FragmentInputExtra, b : u32) {
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol_1 : tint_symbol_2) {
+  frag_main_inner(tint_symbol_1.ff, tint_symbol_1.c, FragmentInputExtra(tint_symbol_1.d, tint_symbol_1.pos, tint_symbol_1.a), tint_symbol_1.b);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, SortedMembers_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> VertexOutput {
+  return VertexOutput();
+}
+
+@stage(fragment)
+fn frag_main(@builtin(front_facing) ff : bool,
+             @location(2) @interpolate(flat) c : i32,
+             inputs : FragmentInputExtra,
+             @location(1) @interpolate(flat) b : u32) {
+}
+
+struct VertexOutput {
+  @location(1) @interpolate(flat) b : u32;
+  @builtin(position) pos : vec4<f32>;
+  @location(3) @interpolate(flat) d : u32;
+  @location(0) a : f32;
+  @location(2) @interpolate(flat) c : i32;
+};
+
+struct FragmentInputExtra {
+  @location(3) @interpolate(flat) d : u32;
+  @builtin(position) pos : vec4<f32>;
+  @location(0) a : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  a : f32;
+  @location(1) @interpolate(flat)
+  b : u32;
+  @location(2) @interpolate(flat)
+  c : i32;
+  @location(3) @interpolate(flat)
+  d : u32;
+  @builtin(position)
+  pos : vec4<f32>;
+}
+
+fn vert_main_inner() -> VertexOutput {
+  return VertexOutput();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.b = inner_result.b;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.d = inner_result.d;
+  wrapper_result.a = inner_result.a;
+  wrapper_result.c = inner_result.c;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  a : f32;
+  @location(1) @interpolate(flat)
+  b : u32;
+  @location(2) @interpolate(flat)
+  c : i32;
+  @location(3) @interpolate(flat)
+  d : u32;
+  @builtin(position)
+  pos : vec4<f32>;
+  @builtin(front_facing)
+  ff : bool;
+}
+
+fn frag_main_inner(ff : bool, c : i32, inputs : FragmentInputExtra, b : u32) {
+}
+
+@stage(fragment)
+fn frag_main(tint_symbol_1 : tint_symbol_2) {
+  frag_main_inner(tint_symbol_1.ff, tint_symbol_1.c, FragmentInputExtra(tint_symbol_1.d, tint_symbol_1.pos, tint_symbol_1.a), tint_symbol_1.b);
+}
+
+struct VertexOutput {
+  b : u32;
+  pos : vec4<f32>;
+  d : u32;
+  a : f32;
+  c : i32;
+}
+
+struct FragmentInputExtra {
+  d : u32;
+  pos : vec4<f32>;
+  a : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, DontRenameSymbols) {
+  auto* src = R"(
+@stage(fragment)
+fn tint_symbol_1(@location(0) col : f32) {
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  @location(0)
+  col : f32;
+}
+
+fn tint_symbol_1_inner(col : f32) {
+}
+
+@stage(fragment)
+fn tint_symbol_1(tint_symbol : tint_symbol_2) {
+  tint_symbol_1_inner(tint_symbol.col);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidNoReturn) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() {
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main_inner() {
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.fixed_sample_mask = 3u;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidWithReturn) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() {
+  return;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main_inner() {
+  return;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.fixed_sample_mask = 3u;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithAuthoredMask) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> @builtin(sample_mask) u32 {
+  return 7u;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(sample_mask)
+  value : u32;
+}
+
+fn frag_main_inner() -> u32 {
+  return 7u;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = (inner_result & 3u);
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithoutAuthoredMask) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> @location(0) f32 {
+  return 1.0;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  value : f32;
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main_inner() -> f32 {
+  return 1.0;
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = inner_result;
+  wrapper_result.fixed_sample_mask = 3u;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_StructWithAuthoredMask) {
+  auto* src = R"(
+struct Output {
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+  @location(0) value : f32;
+};
+
+@stage(fragment)
+fn frag_main() -> Output {
+  return Output(0.5, 7u, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct Output {
+  depth : f32;
+  mask : u32;
+  value : f32;
+}
+
+struct tint_symbol {
+  @location(0)
+  value : f32;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> Output {
+  return Output(0.5, 7u, 1.0);
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = (inner_result.mask & 3u);
+  wrapper_result.value = inner_result.value;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       FixedSampleMask_StructWithAuthoredMask_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> Output {
+  return Output(0.5, 7u, 1.0);
+}
+
+struct Output {
+  @builtin(frag_depth) depth : f32;
+  @builtin(sample_mask) mask : u32;
+  @location(0) value : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  value : f32;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  mask : u32;
+}
+
+fn frag_main_inner() -> Output {
+  return Output(0.5, 7u, 1.0);
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.mask = (inner_result.mask & 3u);
+  wrapper_result.value = inner_result.value;
+  return wrapper_result;
+}
+
+struct Output {
+  depth : f32;
+  mask : u32;
+  value : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       FixedSampleMask_StructWithoutAuthoredMask) {
+  auto* src = R"(
+struct Output {
+  @builtin(frag_depth) depth : f32;
+  @location(0) value : f32;
+};
+
+@stage(fragment)
+fn frag_main() -> Output {
+  return Output(0.5, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct Output {
+  depth : f32;
+  value : f32;
+}
+
+struct tint_symbol {
+  @location(0)
+  value : f32;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main_inner() -> Output {
+  return Output(0.5, 1.0);
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.value = inner_result.value;
+  wrapper_result.fixed_sample_mask = 3u;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       FixedSampleMask_StructWithoutAuthoredMask_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main() -> Output {
+  return Output(0.5, 1.0);
+}
+
+struct Output {
+  @builtin(frag_depth) depth : f32;
+  @location(0) value : f32;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @location(0)
+  value : f32;
+  @builtin(frag_depth)
+  depth : f32;
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main_inner() -> Output {
+  return Output(0.5, 1.0);
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.depth = inner_result.depth;
+  wrapper_result.value = inner_result.value;
+  wrapper_result.fixed_sample_mask = 3u;
+  return wrapper_result;
+}
+
+struct Output {
+  depth : f32;
+  value : f32;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_MultipleShaders) {
+  auto* src = R"(
+@stage(fragment)
+fn frag_main1() -> @builtin(sample_mask) u32 {
+  return 7u;
+}
+
+@stage(fragment)
+fn frag_main2() -> @location(0) f32 {
+  return 1.0;
+}
+
+@stage(vertex)
+fn vert_main1() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(sample_mask)
+  value : u32;
+}
+
+fn frag_main1_inner() -> u32 {
+  return 7u;
+}
+
+@stage(fragment)
+fn frag_main1() -> tint_symbol {
+  let inner_result = frag_main1_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = (inner_result & 3u);
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  value : f32;
+  @builtin(sample_mask)
+  fixed_sample_mask : u32;
+}
+
+fn frag_main2_inner() -> f32 {
+  return 1.0;
+}
+
+@stage(fragment)
+fn frag_main2() -> tint_symbol_1 {
+  let inner_result_1 = frag_main2_inner();
+  var wrapper_result_1 : tint_symbol_1;
+  wrapper_result_1.value = inner_result_1;
+  wrapper_result_1.fixed_sample_mask = 3u;
+  return wrapper_result_1;
+}
+
+struct tint_symbol_2 {
+  @builtin(position)
+  value : vec4<f32>;
+}
+
+fn vert_main1_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn vert_main1() -> tint_symbol_2 {
+  let inner_result_2 = vert_main1_inner();
+  var wrapper_result_2 : tint_symbol_2;
+  wrapper_result_2.value = inner_result_2;
+  return wrapper_result_2;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_AvoidNameClash) {
+  auto* src = R"(
+struct FragOut {
+  @location(0) fixed_sample_mask : vec4<f32>;
+  @location(1) fixed_sample_mask_1 : vec4<f32>;
+};
+
+@stage(fragment)
+fn frag_main() -> FragOut {
+  return FragOut();
+}
+)";
+
+  auto* expect = R"(
+struct FragOut {
+  fixed_sample_mask : vec4<f32>;
+  fixed_sample_mask_1 : vec4<f32>;
+}
+
+struct tint_symbol {
+  @location(0)
+  fixed_sample_mask : vec4<f32>;
+  @location(1)
+  fixed_sample_mask_1 : vec4<f32>;
+  @builtin(sample_mask)
+  fixed_sample_mask_2 : u32;
+}
+
+fn frag_main_inner() -> FragOut {
+  return FragOut();
+}
+
+@stage(fragment)
+fn frag_main() -> tint_symbol {
+  let inner_result = frag_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.fixed_sample_mask = inner_result.fixed_sample_mask;
+  wrapper_result.fixed_sample_mask_1 = inner_result.fixed_sample_mask_1;
+  wrapper_result.fixed_sample_mask_2 = 3u;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_ReturnNonStruct_Spirv) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> value : vec4<f32>;
+
+@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+
+fn vert_main_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner();
+  value = inner_result;
+  vertex_point_size = 1.0;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct_Msl) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(position)
+  value : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size : f32;
+}
+
+fn vert_main_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = inner_result;
+  wrapper_result.vertex_point_size = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Spirv) {
+  auto* src = R"(
+struct VertOut {
+  @builtin(position) pos : vec4<f32>;
+};
+
+@stage(vertex)
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
+
+@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+
+struct VertOut {
+  pos : vec4<f32>;
+}
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner();
+  pos_1 = inner_result.pos;
+  vertex_point_size = 1.0;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_ReturnStruct_Spirv_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+
+struct VertOut {
+  @builtin(position) pos : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
+
+@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner();
+  pos_1 = inner_result.pos;
+  vertex_point_size = 1.0;
+}
+
+struct VertOut {
+  pos : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Msl) {
+  auto* src = R"(
+struct VertOut {
+  @builtin(position) pos : vec4<f32>;
+};
+
+@stage(vertex)
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+struct VertOut {
+  pos : vec4<f32>;
+}
+
+struct tint_symbol {
+  @builtin(position)
+  pos : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size : f32;
+}
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.vertex_point_size = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_ReturnStruct_Msl_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+
+struct VertOut {
+  @builtin(position) pos : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  @builtin(position)
+  pos : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size : f32;
+}
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.vertex_point_size = 1.0;
+  return wrapper_result;
+}
+
+struct VertOut {
+  pos : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Spirv) {
+  auto* src = R"(
+var<private> vertex_point_size : f32;
+var<private> vertex_point_size_1 : f32;
+var<private> vertex_point_size_2 : f32;
+
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+struct VertOut {
+  @location(0) vertex_point_size : f32;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> collide_2 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> collide_3 : f32;
+
+@location(0) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_3 : f32;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
+
+@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
+
+var<private> vertex_point_size : f32;
+
+var<private> vertex_point_size_1 : f32;
+
+var<private> vertex_point_size_2 : f32;
+
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+struct VertOut {
+  vertex_point_size : f32;
+  vertex_point_size_1 : vec4<f32>;
+}
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
+  vertex_point_size_3 = inner_result.vertex_point_size;
+  vertex_point_size_1_1 = inner_result.vertex_point_size_1;
+  vertex_point_size_4 = 1.0;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_AvoidNameClash_Spirv_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+var<private> vertex_point_size : f32;
+var<private> vertex_point_size_1 : f32;
+var<private> vertex_point_size_2 : f32;
+
+struct VertOut {
+  @location(0) vertex_point_size : f32;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+@location(0) @internal(disable_validation__ignore_storage_class) var<in> collide_2 : f32;
+
+@location(1) @internal(disable_validation__ignore_storage_class) var<in> collide_3 : f32;
+
+@location(0) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_3 : f32;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
+
+@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main() {
+  let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
+  vertex_point_size_3 = inner_result.vertex_point_size;
+  vertex_point_size_1_1 = inner_result.vertex_point_size_1;
+  vertex_point_size_4 = 1.0;
+}
+
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+var<private> vertex_point_size : f32;
+
+var<private> vertex_point_size_1 : f32;
+
+var<private> vertex_point_size_2 : f32;
+
+struct VertOut {
+  vertex_point_size : f32;
+  vertex_point_size_1 : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Msl) {
+  auto* src = R"(
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+struct VertOut {
+  @location(0) vertex_point_size : vec4<f32>;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+struct VertOut {
+  vertex_point_size : vec4<f32>;
+  vertex_point_size_1 : vec4<f32>;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  collide : f32;
+  @location(1)
+  collide_2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  vertex_point_size : vec4<f32>;
+  @builtin(position)
+  vertex_point_size_1 : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size_2 : f32;
+}
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
+  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
+  wrapper_result.vertex_point_size_2 = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_AvoidNameClash_Msl_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+struct VertOut {
+  @location(0) vertex_point_size : vec4<f32>;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  collide : f32;
+  @location(1)
+  collide_2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  vertex_point_size : vec4<f32>;
+  @builtin(position)
+  vertex_point_size_1 : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size_2 : f32;
+}
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
+  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
+  wrapper_result.vertex_point_size_2 = 1.0;
+  return wrapper_result;
+}
+
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+struct VertOut {
+  vertex_point_size : vec4<f32>;
+  vertex_point_size_1 : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Hlsl) {
+  auto* src = R"(
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+struct VertOut {
+  @location(0) vertex_point_size : vec4<f32>;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+struct VertOut {
+  vertex_point_size : vec4<f32>;
+  vertex_point_size_1 : vec4<f32>;
+}
+
+struct tint_symbol_1 {
+  @location(0)
+  collide : f32;
+  @location(1)
+  collide_2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  vertex_point_size : vec4<f32>;
+  @builtin(position)
+  vertex_point_size_1 : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size_2 : f32;
+}
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
+  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
+  wrapper_result.vertex_point_size_2 = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest,
+       EmitVertexPointSize_AvoidNameClash_Hlsl_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = collide.collide + collide_1.collide;
+  return VertOut();
+}
+
+struct VertIn1 {
+  @location(0) collide : f32;
+};
+
+struct VertIn2 {
+  @location(1) collide : f32;
+};
+
+struct VertOut {
+  @location(0) vertex_point_size : vec4<f32>;
+  @builtin(position) vertex_point_size_1 : vec4<f32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  @location(0)
+  collide : f32;
+  @location(1)
+  collide_2 : f32;
+}
+
+struct tint_symbol_2 {
+  @location(0)
+  vertex_point_size : vec4<f32>;
+  @builtin(position)
+  vertex_point_size_1 : vec4<f32>;
+  @builtin(pointsize)
+  vertex_point_size_2 : f32;
+}
+
+fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
+  let x = (collide.collide + collide_1.collide);
+  return VertOut();
+}
+
+@stage(vertex)
+fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
+  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
+  var wrapper_result : tint_symbol_2;
+  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
+  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
+  wrapper_result.vertex_point_size_2 = 1.0;
+  return wrapper_result;
+}
+
+struct VertIn1 {
+  collide : f32;
+}
+
+struct VertIn2 {
+  collide : f32;
+}
+
+struct VertOut {
+  vertex_point_size : vec4<f32>;
+  vertex_point_size_1 : vec4<f32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl, 0xFFFFFFFF, true);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, SpirvSampleMaskBuiltins) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(sample_index) sample_index : u32,
+        @builtin(sample_mask) mask_in : u32
+        ) -> @builtin(sample_mask) u32 {
+  return mask_in;
+}
+)";
+
+  auto* expect = R"(
+@builtin(sample_index) @internal(disable_validation__ignore_storage_class) var<in> sample_index_1 : u32;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<in> mask_in_1 : array<u32, 1>;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> value : array<u32, 1>;
+
+fn main_inner(sample_index : u32, mask_in : u32) -> u32 {
+  return mask_in;
+}
+
+@stage(fragment)
+fn main() {
+  let inner_result = main_inner(sample_index_1, mask_in_1[0]);
+  value[0] = inner_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, GLSLSampleMaskBuiltins) {
+  auto* src = R"(
+@stage(fragment)
+fn fragment_main(@builtin(sample_index) sample_index : u32,
+                 @builtin(sample_mask) mask_in : u32
+                 ) -> @builtin(sample_mask) u32 {
+  return mask_in;
+}
+)";
+
+  auto* expect = R"(
+@builtin(sample_index) @internal(disable_validation__ignore_storage_class) var<in> gl_SampleID : i32;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<in> gl_SampleMaskIn : array<i32, 1>;
+
+@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> gl_SampleMask : array<i32, 1>;
+
+fn fragment_main(sample_index : u32, mask_in : u32) -> u32 {
+  return mask_in;
+}
+
+@stage(fragment)
+fn main() {
+  let inner_result = fragment_main(bitcast<u32>(gl_SampleID), bitcast<u32>(gl_SampleMaskIn[0]));
+  gl_SampleMask[0] = bitcast<i32>(inner_result);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, GLSLVertexInstanceIndexBuiltins) {
+  auto* src = R"(
+@stage(vertex)
+fn vertex_main(@builtin(vertex_index) vertexID : u32,
+               @builtin(instance_index) instanceID : u32
+               ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(f32(vertexID) + f32(instanceID));
+}
+)";
+
+  auto* expect = R"(
+@builtin(vertex_index) @internal(disable_validation__ignore_storage_class) var<in> gl_VertexID : i32;
+
+@builtin(instance_index) @internal(disable_validation__ignore_storage_class) var<in> gl_InstanceID : i32;
+
+@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> gl_Position : vec4<f32>;
+
+fn vertex_main(vertexID : u32, instanceID : u32) -> vec4<f32> {
+  return vec4<f32>((f32(vertexID) + f32(instanceID)));
+}
+
+@stage(vertex)
+fn main() {
+  let inner_result = vertex_main(bitcast<u32>(gl_VertexID), bitcast<u32>(gl_InstanceID));
+  gl_Position = inner_result;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0 * gl_Position.z) - gl_Position.w);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
new file mode 100644
index 0000000..ab0b90d
--- /dev/null
+++ b/src/tint/transform/combine_samplers.cc
@@ -0,0 +1,355 @@
+// 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/combine_samplers.h"
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers::BindingInfo);
+
+namespace {
+
+bool IsGlobal(const tint::sem::VariablePair& pair) {
+  return pair.first->Is<tint::sem::GlobalVariable>() &&
+         (!pair.second || pair.second->Is<tint::sem::GlobalVariable>());
+}
+
+}  // namespace
+
+namespace tint {
+namespace transform {
+
+CombineSamplers::BindingInfo::BindingInfo(const BindingMap& map,
+                                          const sem::BindingPoint& placeholder)
+    : binding_map(map), placeholder_binding_point(placeholder) {}
+CombineSamplers::BindingInfo::BindingInfo(const BindingInfo& other) = default;
+CombineSamplers::BindingInfo::~BindingInfo() = default;
+
+/// The PIMPL state for the CombineSamplers transform
+struct CombineSamplers::State {
+  /// The clone context
+  CloneContext& ctx;
+
+  /// The binding info
+  const BindingInfo* binding_info;
+
+  /// Map from a texture/sampler pair to the corresponding combined sampler
+  /// variable
+  using CombinedTextureSamplerMap =
+      std::unordered_map<sem::VariablePair, const ast::Variable*>;
+
+  /// Use sem::BindingPoint without scope.
+  using BindingPoint = sem::BindingPoint;
+
+  /// A map of all global texture/sampler variable pairs to the global
+  /// combined sampler variable that will replace it.
+  CombinedTextureSamplerMap global_combined_texture_samplers_;
+
+  /// A map of all texture/sampler variable pairs that contain a function
+  /// parameter to the combined sampler function paramter that will replace it.
+  std::unordered_map<const sem::Function*, CombinedTextureSamplerMap>
+      function_combined_texture_samplers_;
+
+  /// Placeholder global samplers used when a function contains texture-only
+  /// references (one comparison sampler, one regular). These are also used as
+  /// temporary sampler parameters to the texture builtins to satisfy the WGSL
+  /// resolver, but are then ignored and removed by the GLSL writer.
+  const ast::Variable* placeholder_samplers_[2] = {};
+
+  /// Group and binding attributes used by all combined sampler globals.
+  /// Group 0 and binding 0 are used, with collisions disabled.
+  /// @returns the newly-created attribute list
+  ast::AttributeList Attributes() const {
+    auto attributes = ctx.dst->GroupAndBinding(0, 0);
+    attributes.push_back(
+        ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
+    return attributes;
+  }
+
+  /// Constructor
+  /// @param context the clone context
+  /// @param info the binding map information
+  State(CloneContext& context, const BindingInfo* info)
+      : ctx(context), binding_info(info) {}
+
+  /// Creates a combined sampler global variables.
+  /// (Note this is actually a Texture node at the AST level, but it will be
+  /// written as the corresponding sampler (eg., sampler2D) on GLSL output.)
+  /// @param texture_var the texture (global) variable
+  /// @param sampler_var the sampler (global) variable
+  /// @param name the default name to use (may be overridden by map lookup)
+  /// @returns the newly-created global variable
+  const ast::Variable* CreateCombinedGlobal(const sem::Variable* texture_var,
+                                            const sem::Variable* sampler_var,
+                                            std::string name) {
+    SamplerTexturePair bp_pair;
+    bp_pair.texture_binding_point =
+        texture_var->As<sem::GlobalVariable>()->BindingPoint();
+    bp_pair.sampler_binding_point =
+        sampler_var ? sampler_var->As<sem::GlobalVariable>()->BindingPoint()
+                    : binding_info->placeholder_binding_point;
+    auto it = binding_info->binding_map.find(bp_pair);
+    if (it != binding_info->binding_map.end()) {
+      name = it->second;
+    }
+    const ast::Type* type = CreateCombinedASTTypeFor(texture_var, sampler_var);
+    Symbol symbol = ctx.dst->Symbols().New(name);
+    return ctx.dst->Global(symbol, type, Attributes());
+  }
+
+  /// Creates placeholder global sampler variables.
+  /// @param kind the sampler kind to create for
+  /// @returns the newly-created global variable
+  const ast::Variable* CreatePlaceholder(ast::SamplerKind kind) {
+    const ast::Type* type = ctx.dst->ty.sampler(kind);
+    const char* name = kind == ast::SamplerKind::kComparisonSampler
+                           ? "placeholder_comparison_sampler"
+                           : "placeholder_sampler";
+    Symbol symbol = ctx.dst->Symbols().New(name);
+    return ctx.dst->Global(symbol, type, Attributes());
+  }
+
+  /// Creates ast::Type for a given texture and sampler variable pair.
+  /// Depth textures with no samplers are turned into the corresponding
+  /// f32 texture (e.g., texture_depth_2d -> texture_2d<f32>).
+  /// @param texture the texture variable of interest
+  /// @param sampler the texture variable of interest
+  /// @returns the newly-created type
+  const ast::Type* CreateCombinedASTTypeFor(const sem::Variable* texture,
+                                            const sem::Variable* sampler) {
+    const sem::Type* texture_type = texture->Type()->UnwrapRef();
+    const sem::DepthTexture* depth = texture_type->As<sem::DepthTexture>();
+    if (depth && !sampler) {
+      return ctx.dst->create<ast::SampledTexture>(depth->dim(),
+                                                  ctx.dst->create<ast::F32>());
+    } else {
+      return CreateASTTypeFor(ctx, texture_type);
+    }
+  }
+
+  /// Performs the transformation
+  void Run() {
+    auto& sem = ctx.src->Sem();
+
+    // Remove all texture and sampler global variables. These will be replaced
+    // by combined samplers.
+    for (auto* var : ctx.src->AST().GlobalVariables()) {
+      auto* type = sem.Get(var->type);
+      if (type && type->IsAnyOf<sem::Texture, sem::Sampler>() &&
+          !type->Is<sem::StorageTexture>()) {
+        ctx.Remove(ctx.src->AST().GlobalDeclarations(), var);
+      } else if (auto binding_point = var->BindingPoint()) {
+        if (binding_point.group->value == 0 &&
+            binding_point.binding->value == 0) {
+          auto* attribute =
+              ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
+          ctx.InsertFront(var->attributes, attribute);
+        }
+      }
+    }
+
+    // Rewrite all function signatures to use combined samplers, and remove
+    // separate textures & samplers. Create new combined globals where found.
+    ctx.ReplaceAll([&](const ast::Function* src) -> const ast::Function* {
+      if (auto* func = sem.Get(src)) {
+        auto pairs = func->TextureSamplerPairs();
+        if (pairs.empty()) {
+          return nullptr;
+        }
+        ast::VariableList params;
+        for (auto pair : func->TextureSamplerPairs()) {
+          const sem::Variable* texture_var = pair.first;
+          const sem::Variable* sampler_var = pair.second;
+          std::string name =
+              ctx.src->Symbols().NameFor(texture_var->Declaration()->symbol);
+          if (sampler_var) {
+            name += "_" + ctx.src->Symbols().NameFor(
+                              sampler_var->Declaration()->symbol);
+          }
+          if (IsGlobal(pair)) {
+            // Both texture and sampler are global; add a new global variable
+            // to represent the combined sampler (if not already created).
+            utils::GetOrCreate(global_combined_texture_samplers_, pair, [&] {
+              return CreateCombinedGlobal(texture_var, sampler_var, name);
+            });
+          } else {
+            // Either texture or sampler (or both) is a function parameter;
+            // add a new function parameter to represent the combined sampler.
+            const ast::Type* type =
+                CreateCombinedASTTypeFor(texture_var, sampler_var);
+            const ast::Variable* var =
+                ctx.dst->Param(ctx.dst->Symbols().New(name), type);
+            params.push_back(var);
+            function_combined_texture_samplers_[func][pair] = var;
+          }
+        }
+        // Filter out separate textures and samplers from the original
+        // function signature.
+        for (auto* var : src->params) {
+          if (!sem.Get(var->type)->IsAnyOf<sem::Texture, sem::Sampler>()) {
+            params.push_back(ctx.Clone(var));
+          }
+        }
+        // Create a new function signature that differs only in the parameter
+        // list.
+        auto symbol = ctx.Clone(src->symbol);
+        auto* return_type = ctx.Clone(src->return_type);
+        auto* body = ctx.Clone(src->body);
+        auto attributes = ctx.Clone(src->attributes);
+        auto return_type_attributes = ctx.Clone(src->return_type_attributes);
+        return ctx.dst->create<ast::Function>(
+            symbol, params, return_type, body, std::move(attributes),
+            std::move(return_type_attributes));
+      }
+      return nullptr;
+    });
+
+    // Replace all function call expressions containing texture or
+    // sampler parameters to use the current function's combined samplers or
+    // the combined global samplers, as appropriate.
+    ctx.ReplaceAll([&](const ast::CallExpression* expr)
+                       -> const ast::Expression* {
+      if (auto* call = sem.Get(expr)) {
+        ast::ExpressionList args;
+        // Replace all texture builtin calls.
+        if (auto* builtin = call->Target()->As<sem::Builtin>()) {
+          const auto& signature = builtin->Signature();
+          int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
+          int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
+          if (texture_index == -1) {
+            return nullptr;
+          }
+          const sem::Expression* texture = call->Arguments()[texture_index];
+          // We don't want to combine storage textures with anything, since
+          // they never have associated samplers in GLSL.
+          if (texture->Type()->UnwrapRef()->Is<sem::StorageTexture>()) {
+            return nullptr;
+          }
+          const sem::Expression* sampler =
+              sampler_index != -1 ? call->Arguments()[sampler_index] : nullptr;
+          auto* texture_var = texture->As<sem::VariableUser>()->Variable();
+          auto* sampler_var =
+              sampler ? sampler->As<sem::VariableUser>()->Variable() : nullptr;
+          sem::VariablePair new_pair(texture_var, sampler_var);
+          for (auto* arg : expr->args) {
+            auto* type = ctx.src->TypeOf(arg)->UnwrapRef();
+            if (type->Is<sem::Texture>()) {
+              const ast::Variable* var =
+                  IsGlobal(new_pair)
+                      ? global_combined_texture_samplers_[new_pair]
+                      : function_combined_texture_samplers_
+                            [call->Stmt()->Function()][new_pair];
+              args.push_back(ctx.dst->Expr(var->symbol));
+            } else if (auto* sampler_type = type->As<sem::Sampler>()) {
+              ast::SamplerKind kind = sampler_type->kind();
+              int index = (kind == ast::SamplerKind::kSampler) ? 0 : 1;
+              const ast::Variable*& p = placeholder_samplers_[index];
+              if (!p) {
+                p = CreatePlaceholder(kind);
+              }
+              args.push_back(ctx.dst->Expr(p->symbol));
+            } else {
+              args.push_back(ctx.Clone(arg));
+            }
+          }
+          const ast::Expression* value =
+              ctx.dst->Call(ctx.Clone(expr->target.name), args);
+          if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
+              texture_var->Type()->UnwrapRef()->Is<sem::DepthTexture>() &&
+              !call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
+            value = ctx.dst->MemberAccessor(value, "x");
+          }
+          return value;
+        }
+        // Replace all function calls.
+        if (auto* callee = call->Target()->As<sem::Function>()) {
+          for (auto pair : callee->TextureSamplerPairs()) {
+            // Global pairs used by the callee do not require a function
+            // parameter at the call site.
+            if (IsGlobal(pair)) {
+              continue;
+            }
+            const sem::Variable* texture_var = pair.first;
+            const sem::Variable* sampler_var = pair.second;
+            if (auto* param = texture_var->As<sem::Parameter>()) {
+              const sem::Expression* texture =
+                  call->Arguments()[param->Index()];
+              texture_var = texture->As<sem::VariableUser>()->Variable();
+            }
+            if (sampler_var) {
+              if (auto* param = sampler_var->As<sem::Parameter>()) {
+                const sem::Expression* sampler =
+                    call->Arguments()[param->Index()];
+                sampler_var = sampler->As<sem::VariableUser>()->Variable();
+              }
+            }
+            sem::VariablePair new_pair(texture_var, sampler_var);
+            // If both texture and sampler are (now) global, pass that
+            // global variable to the callee. Otherwise use the caller's
+            // function parameter for this pair.
+            const ast::Variable* var =
+                IsGlobal(new_pair) ? global_combined_texture_samplers_[new_pair]
+                                   : function_combined_texture_samplers_
+                                         [call->Stmt()->Function()][new_pair];
+            auto* arg = ctx.dst->Expr(var->symbol);
+            args.push_back(arg);
+          }
+          // Append all of the remaining non-texture and non-sampler
+          // parameters.
+          for (auto* arg : expr->args) {
+            if (!ctx.src->TypeOf(arg)
+                     ->UnwrapRef()
+                     ->IsAnyOf<sem::Texture, sem::Sampler>()) {
+              args.push_back(ctx.Clone(arg));
+            }
+          }
+          return ctx.dst->Call(ctx.Clone(expr->target.name), args);
+        }
+      }
+      return nullptr;
+    });
+
+    ctx.Clone();
+  }
+};
+
+CombineSamplers::CombineSamplers() = default;
+
+CombineSamplers::~CombineSamplers() = default;
+
+void CombineSamplers::Run(CloneContext& ctx,
+                          const DataMap& inputs,
+                          DataMap&) const {
+  auto* binding_info = inputs.Get<BindingInfo>();
+  if (!binding_info) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  State(ctx, binding_info).Run();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/combine_samplers.h b/src/tint/transform/combine_samplers.h
new file mode 100644
index 0000000..e545786
--- /dev/null
+++ b/src/tint/transform/combine_samplers.h
@@ -0,0 +1,110 @@
+// 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_COMBINE_SAMPLERS_H_
+#define SRC_TINT_TRANSFORM_COMBINE_SAMPLERS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/sem/sampler_texture_pair.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// This transform converts all separate texture/sampler refences in a
+/// program into combined texture/samplers. This is required for GLSL,
+/// which does not support separate texture/samplers.
+///
+/// It utilizes the texture/sampler information collected by the
+/// Resolver and stored on each sem::Function. For each function, all
+/// separate texture/sampler parameters in the function signature are
+/// removed. For each unique pair, if both texture and sampler are
+/// global variables, the function passes the corresponding combined
+/// global stored in global_combined_texture_samplers_ at the call
+/// site. Otherwise, either the texture or sampler must be a function
+/// parameter. In this case, a new parameter is added to the function
+/// signature. All separate texture/sampler parameters are removed.
+///
+/// All texture builtin callsites are modified to pass the combined
+/// texture/sampler as the first argument, and separate texture/sampler
+/// arguments are removed.
+///
+/// Note that the sampler may be null, indicating that only a texture
+/// reference was required (e.g., textureLoad). In this case, a
+/// placeholder global sampler is used at the AST level. This will be
+/// combined with the original texture to give a combined global, and
+/// the placeholder removed (ignored) by the GLSL writer.
+///
+/// Note that the combined samplers are actually represented by a
+/// Texture node at the AST level, since this contains all the
+/// information needed to represent a combined sampler in GLSL
+/// (dimensionality, component type, etc). The GLSL writer outputs such
+/// (Tint) Textures as (GLSL) Samplers.
+class CombineSamplers : public Castable<CombineSamplers, Transform> {
+ public:
+  /// A pair of binding points.
+  using SamplerTexturePair = sem::SamplerTexturePair;
+
+  /// A map from a sampler/texture pair to a named global.
+  using BindingMap = std::unordered_map<SamplerTexturePair, std::string>;
+
+  /// The client-provided mapping from separate texture and sampler binding
+  /// points to combined sampler binding point.
+  struct BindingInfo : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param map the map of all (texture, sampler) -> (combined) pairs
+    /// @param placeholder the binding point to use for placeholder samplers.
+    BindingInfo(const BindingMap& map, const sem::BindingPoint& placeholder);
+
+    /// Copy constructor
+    /// @param other the other BindingInfo to copy
+    BindingInfo(const BindingInfo& other);
+
+    /// Destructor
+    ~BindingInfo() override;
+
+    /// A map of bindings from (texture, sampler) -> combined sampler.
+    BindingMap binding_map;
+
+    /// The binding point to use for placeholder samplers.
+    sem::BindingPoint placeholder_binding_point;
+  };
+
+  /// Constructor
+  CombineSamplers();
+
+  /// Destructor
+  ~CombineSamplers() override;
+
+ protected:
+  /// The PIMPL state for this transform
+  struct State;
+
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_COMBINE_SAMPLERS_H_
diff --git a/src/tint/transform/combine_samplers_test.cc b/src/tint/transform/combine_samplers_test.cc
new file mode 100644
index 0000000..f223cb1
--- /dev/null
+++ b/src/tint/transform/combine_samplers_test.cc
@@ -0,0 +1,1012 @@
+// 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/combine_samplers.h"
+
+#include <memory>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using CombineSamplersTest = TransformTest;
+
+TEST_F(CombineSamplersTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePair) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePair_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairInAFunction) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+fn main() -> vec4<f32> {
+  return sample(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, coords);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return sample(t_s_1, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairInAFunction_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return sample(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+@group(0) @binding(1) var s : sampler;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return sample(t_s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s_1, placeholder_sampler, coords);
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairRename) {
+  auto* src = R"(
+@group(0) @binding(1) var t : texture_2d<f32>;
+
+@group(2) @binding(3) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fuzzy : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(fuzzy, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  CombineSamplers::BindingMap map;
+  sem::SamplerTexturePair pair;
+  pair.texture_binding_point.group = 0;
+  pair.texture_binding_point.binding = 1;
+  pair.sampler_binding_point.group = 2;
+  pair.sampler_binding_point.binding = 3;
+  map[pair] = "fuzzy";
+  sem::BindingPoint placeholder{1024, 0};
+  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairRenameMiss) {
+  auto* src = R"(
+@group(0) @binding(1) var t : texture_2d<f32>;
+
+@group(2) @binding(3) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  CombineSamplers::BindingMap map;
+  sem::SamplerTexturePair pair;
+  pair.texture_binding_point.group = 3;
+  pair.texture_binding_point.binding = 2;
+  pair.sampler_binding_point.group = 1;
+  pair.sampler_binding_point.binding = 0;
+  map[pair] = "fuzzy";
+  sem::BindingPoint placeholder{1024, 0};
+  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, AliasedTypes) {
+  auto* src = R"(
+
+type Tex2d = texture_2d<f32>;
+
+@group(0) @binding(0) var t : Tex2d;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample(t : Tex2d, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+fn main() -> vec4<f32> {
+  return sample(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+type Tex2d = texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, coords);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return sample(t_s_1, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, AliasedTypes_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return sample(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn sample(t : Tex2d, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+@group(0) @binding(0) var t : Tex2d;
+@group(0) @binding(1) var s : sampler;
+
+type Tex2d = texture_2d<f32>;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return sample(t_s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s_1, placeholder_sampler, coords);
+}
+
+type Tex2d = texture_2d<f32>;
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairInTwoFunctions) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn g(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+fn f(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return g(t, s, coords);
+}
+
+fn main() -> vec4<f32> {
+  return f(t, s, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn g(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, coords);
+}
+
+fn f(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return g(t_s_1, coords);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_2 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(t_s_2, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, SimplePairInTwoFunctions_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return f(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn f(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return g(t, s, coords);
+}
+
+fn g(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(0) var t : texture_2d<f32>;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(t_s, vec2<f32>(1.0, 2.0));
+}
+
+fn f(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return g(t_s_1, coords);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn g(t_s_2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s_2, placeholder_sampler, coords);
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TwoFunctionsGenerateSamePair) {
+  auto* src = R"(
+@group(1) @binding(0) var tex : texture_2d<f32>;
+
+@group(1) @binding(1) var samp : sampler;
+
+fn f() -> vec4<f32> {
+  return textureSample(tex, samp, vec2<f32>(1.0, 2.0));
+}
+
+fn g() -> vec4<f32> {
+  return textureSample(tex, samp, vec2<f32>(3.0, 4.0));
+}
+
+fn main() -> vec4<f32> {
+  return f() + g();
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn f() -> vec4<f32> {
+  return textureSample(tex_samp, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+
+fn g() -> vec4<f32> {
+  return textureSample(tex_samp, placeholder_sampler, vec2<f32>(3.0, 4.0));
+}
+
+fn main() -> vec4<f32> {
+  return (f() + g());
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, ThreeTexturesThreeSamplers) {
+  auto* src = R"(
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+@group(0) @binding(2) var tex3 : texture_2d<f32>;
+
+@group(1) @binding(0) var samp1 : sampler;
+@group(1) @binding(1) var samp2: sampler;
+@group(1) @binding(2) var samp3: sampler;
+
+fn sample(t : texture_2d<f32>, s : sampler) -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn main() -> vec4<f32> {
+  return sample(tex1, samp1)
+       + sample(tex1, samp2)
+       + sample(tex1, samp3)
+       + sample(tex2, samp1)
+       + sample(tex2, samp2)
+       + sample(tex2, samp3)
+       + sample(tex3, samp1)
+       + sample(tex3, samp2)
+       + sample(tex3, samp3);
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s : texture_2d<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp2 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp3 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp2 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp3 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp2 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp3 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return ((((((((sample(tex1_samp1) + sample(tex1_samp2)) + sample(tex1_samp3)) + sample(tex2_samp1)) + sample(tex2_samp2)) + sample(tex2_samp3)) + sample(tex3_samp1)) + sample(tex3_samp2)) + sample(tex3_samp3));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TwoFunctionsTwoTexturesDiamond) {
+  auto* src = R"(
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+
+@group(0) @binding(2) var samp : sampler;
+
+fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return sample(t1, s, coords) + sample(t2, s, coords);
+}
+
+fn main() -> vec4<f32> {
+  return f(tex1, tex2, samp, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, coords);
+}
+
+fn f(t1_s : texture_2d<f32>, t2_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (sample(t1_s, coords) + sample(t2_s, coords));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TwoFunctionsTwoSamplersDiamond) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+@group(0) @binding(1) var samp1 : sampler;
+
+@group(0) @binding(2) var samp2 : sampler;
+
+fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t, s, coords);
+}
+
+fn f(t : texture_2d<f32>, s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return sample(t, s1, coords) + sample(t, s2, coords);
+}
+
+fn main() -> vec4<f32> {
+  return f(tex, samp1, samp2, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t_s, placeholder_sampler, coords);
+}
+
+fn f(t_s1 : texture_2d<f32>, t_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (sample(t_s1, coords) + sample(t_s2, coords));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, GlobalTextureLocalSampler) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+@group(0) @binding(1) var samp1 : sampler;
+
+@group(0) @binding(2) var samp2 : sampler;
+
+fn f(s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(tex, s1, coords) + textureSample(tex, s2, coords);
+}
+
+fn main() -> vec4<f32> {
+  return f(samp1, samp2, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn f(tex_s1 : texture_2d<f32>, tex_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, GlobalTextureLocalSampler_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return f(samp1, samp2, vec2<f32>(1.0, 2.0));
+}
+
+fn f(s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(tex, s1, coords) + textureSample(tex, s2, coords);
+}
+
+@group(0) @binding(1) var samp1 : sampler;
+@group(0) @binding(2) var samp2 : sampler;
+@group(0) @binding(0) var tex : texture_2d<f32>;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn f(tex_s1 : texture_2d<f32>, tex_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, LocalTextureGlobalSampler) {
+  auto* src = R"(
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+
+@group(0) @binding(2) var samp : sampler;
+
+fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t1, samp, coords) + textureSample(t2, samp, coords);
+}
+
+fn main() -> vec4<f32> {
+  return f(tex1, tex2, vec2<f32>(1.0, 2.0));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn f(t1_samp : texture_2d<f32>, t2_samp : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, LocalTextureGlobalSampler_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return f(tex1, tex2, vec2<f32>(1.0, 2.0));
+}
+
+fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return textureSample(t1, samp, coords) + textureSample(t2, samp, coords);
+}
+
+@group(0) @binding(2) var samp : sampler;
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn f(t1_samp : texture_2d<f32>, t2_samp : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
+  return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TextureLoadNoSampler) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f(t : texture_2d<f32>, coords : vec2<i32>) -> vec4<f32> {
+  return textureLoad(t, coords, 0);
+}
+
+fn main() -> vec4<f32> {
+  return f(tex, vec2<i32>(1, 2));
+}
+)";
+  auto* expect = R"(
+fn f(t_1 : texture_2d<f32>, coords : vec2<i32>) -> vec4<f32> {
+  return textureLoad(t_1, coords, 0);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return f(fred, vec2<i32>(1, 2));
+}
+)";
+
+  sem::BindingPoint placeholder{1024, 0};
+  sem::SamplerTexturePair pair;
+  pair.texture_binding_point.group = 0;
+  pair.texture_binding_point.binding = 0;
+  pair.sampler_binding_point.group = placeholder.group;
+  pair.sampler_binding_point.binding = placeholder.binding;
+  CombineSamplers::BindingMap map;
+  map[pair] = "fred";
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TextureWithAndWithoutSampler) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+@group(0) @binding(1) var samp : sampler;
+
+fn main() -> vec4<f32> {
+  return textureLoad(tex, vec2<i32>(), 0) +
+         textureSample(tex, samp, vec2<f32>());
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var barney : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return (textureLoad(fred, vec2<i32>(), 0) + textureSample(barney, placeholder_sampler, vec2<f32>()));
+}
+)";
+
+  sem::BindingPoint placeholder{1024, 0};
+  sem::BindingPoint tex{0, 0};
+  sem::BindingPoint samp{0, 1};
+  sem::SamplerTexturePair pair, placeholder_pair;
+  pair.texture_binding_point.group = tex.group;
+  pair.texture_binding_point.binding = tex.binding;
+  pair.sampler_binding_point.group = samp.group;
+  pair.sampler_binding_point.binding = samp.binding;
+  placeholder_pair.texture_binding_point.group = tex.group;
+  placeholder_pair.texture_binding_point.binding = tex.binding;
+  placeholder_pair.sampler_binding_point.group = placeholder.group;
+  placeholder_pair.sampler_binding_point.binding = placeholder.binding;
+  CombineSamplers::BindingMap map;
+  map[pair] = "barney";
+  map[placeholder_pair] = "fred";
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TextureSampleCompare) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_depth_2d;
+
+@group(0) @binding(1) var samp : sampler_comparison;
+
+fn main() -> vec4<f32> {
+  return vec4<f32>(textureSampleCompare(tex, samp, vec2<f32>(1.0, 2.0), 0.5));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
+
+fn main() -> vec4<f32> {
+  return vec4<f32>(textureSampleCompare(tex_samp, placeholder_comparison_sampler, vec2<f32>(1.0, 2.0), 0.5));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction) {
+  auto* src = R"(
+@group(0) @binding(0) var tex : texture_depth_2d;
+
+@group(0) @binding(1) var samp : sampler_comparison;
+
+fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2<f32>) -> f32 {
+  return textureSampleCompare(t, s, coords, 5.0f);
+}
+
+fn main() -> vec4<f32> {
+  return vec4<f32>(f(tex, samp, vec2<f32>(1.0, 2.0)));
+}
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
+
+fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
+  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
+
+fn main() -> vec4<f32> {
+  return vec4<f32>(f(tex_samp, vec2<f32>(1.0, 2.0)));
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return vec4<f32>(f(tex, samp, vec2<f32>(1.0, 2.0)));
+}
+
+fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2<f32>) -> f32 {
+  return textureSampleCompare(t, s, coords, 5.0f);
+}
+
+@group(0) @binding(0) var tex : texture_depth_2d;
+@group(0) @binding(1) var samp : sampler_comparison;
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
+
+fn main() -> vec4<f32> {
+  return vec4<f32>(f(tex_samp, vec2<f32>(1.0, 2.0)));
+}
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
+
+fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
+  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, BindingPointCollision) {
+  auto* src = R"(
+@group(1) @binding(0) var tex : texture_2d<f32>;
+
+@group(1) @binding(1) var samp : sampler;
+
+@group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
+
+fn main() -> vec4<f32> {
+  return textureSample(tex, samp, gcoords);
+}
+)";
+  auto* expect = R"(
+@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(tex_samp, placeholder_sampler, gcoords);
+}
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CombineSamplersTest, BindingPointCollision_OutOfOrder) {
+  auto* src = R"(
+fn main() -> vec4<f32> {
+  return textureSample(tex, samp, gcoords);
+}
+
+@group(1) @binding(1) var samp : sampler;
+@group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
+@group(1) @binding(0) var tex : texture_2d<f32>;
+
+)";
+  auto* expect = R"(
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
+
+@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(tex_samp, placeholder_sampler, gcoords);
+}
+
+@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
+)";
+
+  DataMap data;
+  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                         sem::BindingPoint());
+  auto got = Run<CombineSamplers>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
new file mode 100644
index 0000000..f251a22
--- /dev/null
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -0,0 +1,998 @@
+// 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/transform/decompose_memory_access.h"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/unary_op.h"
+#include "src/tint/block_allocator.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeMemoryAccess);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeMemoryAccess::Intrinsic);
+
+namespace tint {
+namespace transform {
+
+namespace {
+
+/// Offset is a simple ast::Expression builder interface, used to build byte
+/// offsets for storage and uniform buffer accesses.
+struct Offset : Castable<Offset> {
+  /// @returns builds and returns the ast::Expression in `ctx.dst`
+  virtual const ast::Expression* Build(CloneContext& ctx) const = 0;
+};
+
+/// OffsetExpr is an implementation of Offset that clones and casts the given
+/// expression to `u32`.
+struct OffsetExpr : Offset {
+  const ast::Expression* const expr = nullptr;
+
+  explicit OffsetExpr(const ast::Expression* e) : expr(e) {}
+
+  const ast::Expression* Build(CloneContext& ctx) const override {
+    auto* type = ctx.src->Sem().Get(expr)->Type()->UnwrapRef();
+    auto* res = ctx.Clone(expr);
+    if (!type->Is<sem::U32>()) {
+      res = ctx.dst->Construct<ProgramBuilder::u32>(res);
+    }
+    return res;
+  }
+};
+
+/// OffsetLiteral is an implementation of Offset that constructs a u32 literal
+/// value.
+struct OffsetLiteral : Castable<OffsetLiteral, Offset> {
+  uint32_t const literal = 0;
+
+  explicit OffsetLiteral(uint32_t lit) : literal(lit) {}
+
+  const ast::Expression* Build(CloneContext& ctx) const override {
+    return ctx.dst->Expr(literal);
+  }
+};
+
+/// OffsetBinOp is an implementation of Offset that constructs a binary-op of
+/// two Offsets.
+struct OffsetBinOp : Offset {
+  ast::BinaryOp op;
+  Offset const* lhs = nullptr;
+  Offset const* rhs = nullptr;
+
+  const ast::Expression* Build(CloneContext& ctx) const override {
+    return ctx.dst->create<ast::BinaryExpression>(op, lhs->Build(ctx),
+                                                  rhs->Build(ctx));
+  }
+};
+
+/// LoadStoreKey is the unordered map key to a load or store intrinsic.
+struct LoadStoreKey {
+  ast::StorageClass const storage_class;  // buffer storage class
+  sem::Type const* buf_ty = nullptr;      // buffer type
+  sem::Type const* el_ty = nullptr;       // element type
+  bool operator==(const LoadStoreKey& rhs) const {
+    return storage_class == rhs.storage_class && buf_ty == rhs.buf_ty &&
+           el_ty == rhs.el_ty;
+  }
+  struct Hasher {
+    inline std::size_t operator()(const LoadStoreKey& u) const {
+      return utils::Hash(u.storage_class, u.buf_ty, u.el_ty);
+    }
+  };
+};
+
+/// AtomicKey is the unordered map key to an atomic intrinsic.
+struct AtomicKey {
+  sem::Type const* buf_ty = nullptr;  // buffer type
+  sem::Type const* el_ty = nullptr;   // element type
+  sem::BuiltinType const op;          // atomic op
+  bool operator==(const AtomicKey& rhs) const {
+    return buf_ty == rhs.buf_ty && el_ty == rhs.el_ty && op == rhs.op;
+  }
+  struct Hasher {
+    inline std::size_t operator()(const AtomicKey& u) const {
+      return utils::Hash(u.buf_ty, u.el_ty, u.op);
+    }
+  };
+};
+
+bool IntrinsicDataTypeFor(const sem::Type* ty,
+                          DecomposeMemoryAccess::Intrinsic::DataType& out) {
+  if (ty->Is<sem::I32>()) {
+    out = DecomposeMemoryAccess::Intrinsic::DataType::kI32;
+    return true;
+  }
+  if (ty->Is<sem::U32>()) {
+    out = DecomposeMemoryAccess::Intrinsic::DataType::kU32;
+    return true;
+  }
+  if (ty->Is<sem::F32>()) {
+    out = DecomposeMemoryAccess::Intrinsic::DataType::kF32;
+    return true;
+  }
+  if (auto* vec = ty->As<sem::Vector>()) {
+    switch (vec->Width()) {
+      case 2:
+        if (vec->type()->Is<sem::I32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2I32;
+          return true;
+        }
+        if (vec->type()->Is<sem::U32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2U32;
+          return true;
+        }
+        if (vec->type()->Is<sem::F32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2F32;
+          return true;
+        }
+        break;
+      case 3:
+        if (vec->type()->Is<sem::I32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3I32;
+          return true;
+        }
+        if (vec->type()->Is<sem::U32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3U32;
+          return true;
+        }
+        if (vec->type()->Is<sem::F32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3F32;
+          return true;
+        }
+        break;
+      case 4:
+        if (vec->type()->Is<sem::I32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4I32;
+          return true;
+        }
+        if (vec->type()->Is<sem::U32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4U32;
+          return true;
+        }
+        if (vec->type()->Is<sem::F32>()) {
+          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4F32;
+          return true;
+        }
+        break;
+    }
+    return false;
+  }
+
+  return false;
+}
+
+/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
+/// to a stub function to load the type `ty`.
+DecomposeMemoryAccess::Intrinsic* IntrinsicLoadFor(
+    ProgramBuilder* builder,
+    ast::StorageClass storage_class,
+    const sem::Type* ty) {
+  DecomposeMemoryAccess::Intrinsic::DataType type;
+  if (!IntrinsicDataTypeFor(ty, type)) {
+    return nullptr;
+  }
+  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
+      builder->ID(), DecomposeMemoryAccess::Intrinsic::Op::kLoad, storage_class,
+      type);
+}
+
+/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
+/// to a stub function to store the type `ty`.
+DecomposeMemoryAccess::Intrinsic* IntrinsicStoreFor(
+    ProgramBuilder* builder,
+    ast::StorageClass storage_class,
+    const sem::Type* ty) {
+  DecomposeMemoryAccess::Intrinsic::DataType type;
+  if (!IntrinsicDataTypeFor(ty, type)) {
+    return nullptr;
+  }
+  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
+      builder->ID(), DecomposeMemoryAccess::Intrinsic::Op::kStore,
+      storage_class, type);
+}
+
+/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
+/// to a stub function for the atomic op and the type `ty`.
+DecomposeMemoryAccess::Intrinsic* IntrinsicAtomicFor(ProgramBuilder* builder,
+                                                     sem::BuiltinType ity,
+                                                     const sem::Type* ty) {
+  auto op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
+  switch (ity) {
+    case sem::BuiltinType::kAtomicLoad:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
+      break;
+    case sem::BuiltinType::kAtomicStore:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicStore;
+      break;
+    case sem::BuiltinType::kAtomicAdd:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAdd;
+      break;
+    case sem::BuiltinType::kAtomicSub:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicSub;
+      break;
+    case sem::BuiltinType::kAtomicMax:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMax;
+      break;
+    case sem::BuiltinType::kAtomicMin:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMin;
+      break;
+    case sem::BuiltinType::kAtomicAnd:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAnd;
+      break;
+    case sem::BuiltinType::kAtomicOr:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicOr;
+      break;
+    case sem::BuiltinType::kAtomicXor:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicXor;
+      break;
+    case sem::BuiltinType::kAtomicExchange:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicExchange;
+      break;
+    case sem::BuiltinType::kAtomicCompareExchangeWeak:
+      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicCompareExchangeWeak;
+      break;
+    default:
+      TINT_ICE(Transform, builder->Diagnostics())
+          << "invalid IntrinsicType for DecomposeMemoryAccess::Intrinsic: "
+          << ty->type_name();
+      break;
+  }
+
+  DecomposeMemoryAccess::Intrinsic::DataType type;
+  if (!IntrinsicDataTypeFor(ty, type)) {
+    return nullptr;
+  }
+  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
+      builder->ID(), op, ast::StorageClass::kStorage, type);
+}
+
+/// BufferAccess describes a single storage or uniform buffer access
+struct BufferAccess {
+  sem::Expression const* var = nullptr;  // Storage buffer variable
+  Offset const* offset = nullptr;        // The byte offset on var
+  sem::Type const* type = nullptr;       // The type of the access
+  operator bool() const { return var; }  // Returns true if valid
+};
+
+/// Store describes a single storage or uniform buffer write
+struct Store {
+  const ast::AssignmentStatement* assignment;  // The AST assignment statement
+  BufferAccess target;                         // The target for the write
+};
+
+}  // namespace
+
+/// State holds the current transform state
+struct DecomposeMemoryAccess::State {
+  /// The clone context
+  CloneContext& ctx;
+  /// Alias to `*ctx.dst`
+  ProgramBuilder& b;
+  /// Map of AST expression to storage or uniform buffer access
+  /// This map has entries added when encountered, and removed when outer
+  /// expressions chain the access.
+  /// Subset of #expression_order, as expressions are not removed from
+  /// #expression_order.
+  std::unordered_map<const ast::Expression*, BufferAccess> accesses;
+  /// The visited order of AST expressions (superset of #accesses)
+  std::vector<const ast::Expression*> expression_order;
+  /// [buffer-type, element-type] -> load function name
+  std::unordered_map<LoadStoreKey, Symbol, LoadStoreKey::Hasher> load_funcs;
+  /// [buffer-type, element-type] -> store function name
+  std::unordered_map<LoadStoreKey, Symbol, LoadStoreKey::Hasher> store_funcs;
+  /// [buffer-type, element-type, atomic-op] -> load function name
+  std::unordered_map<AtomicKey, Symbol, AtomicKey::Hasher> atomic_funcs;
+  /// List of storage or uniform buffer writes
+  std::vector<Store> stores;
+  /// Allocations for offsets
+  BlockAllocator<Offset> offsets_;
+
+  /// Constructor
+  /// @param context the CloneContext
+  explicit State(CloneContext& context) : ctx(context), b(*ctx.dst) {}
+
+  /// @param offset the offset value to wrap in an Offset
+  /// @returns an Offset for the given literal value
+  const Offset* ToOffset(uint32_t offset) {
+    return offsets_.Create<OffsetLiteral>(offset);
+  }
+
+  /// @param expr the expression to convert to an Offset
+  /// @returns an Offset for the given ast::Expression
+  const Offset* ToOffset(const ast::Expression* expr) {
+    if (auto* u32 = expr->As<ast::UintLiteralExpression>()) {
+      return offsets_.Create<OffsetLiteral>(u32->value);
+    } else if (auto* i32 = expr->As<ast::SintLiteralExpression>()) {
+      if (i32->value > 0) {
+        return offsets_.Create<OffsetLiteral>(i32->value);
+      }
+    }
+    return offsets_.Create<OffsetExpr>(expr);
+  }
+
+  /// @param offset the Offset that is returned
+  /// @returns the given offset (pass-through)
+  const Offset* ToOffset(const Offset* offset) { return offset; }
+
+  /// @param lhs_ the left-hand side of the add expression
+  /// @param rhs_ the right-hand side of the add expression
+  /// @return an Offset that is a sum of lhs and rhs, performing basic constant
+  /// folding if possible
+  template <typename LHS, typename RHS>
+  const Offset* Add(LHS&& lhs_, RHS&& rhs_) {
+    auto* lhs = ToOffset(std::forward<LHS>(lhs_));
+    auto* rhs = ToOffset(std::forward<RHS>(rhs_));
+    auto* lhs_lit = tint::As<OffsetLiteral>(lhs);
+    auto* rhs_lit = tint::As<OffsetLiteral>(rhs);
+    if (lhs_lit && lhs_lit->literal == 0) {
+      return rhs;
+    }
+    if (rhs_lit && rhs_lit->literal == 0) {
+      return lhs;
+    }
+    if (lhs_lit && rhs_lit) {
+      if (static_cast<uint64_t>(lhs_lit->literal) +
+              static_cast<uint64_t>(rhs_lit->literal) <=
+          0xffffffff) {
+        return offsets_.Create<OffsetLiteral>(lhs_lit->literal +
+                                              rhs_lit->literal);
+      }
+    }
+    auto* out = offsets_.Create<OffsetBinOp>();
+    out->op = ast::BinaryOp::kAdd;
+    out->lhs = lhs;
+    out->rhs = rhs;
+    return out;
+  }
+
+  /// @param lhs_ the left-hand side of the multiply expression
+  /// @param rhs_ the right-hand side of the multiply expression
+  /// @return an Offset that is the multiplication of lhs and rhs, performing
+  /// basic constant folding if possible
+  template <typename LHS, typename RHS>
+  const Offset* Mul(LHS&& lhs_, RHS&& rhs_) {
+    auto* lhs = ToOffset(std::forward<LHS>(lhs_));
+    auto* rhs = ToOffset(std::forward<RHS>(rhs_));
+    auto* lhs_lit = tint::As<OffsetLiteral>(lhs);
+    auto* rhs_lit = tint::As<OffsetLiteral>(rhs);
+    if (lhs_lit && lhs_lit->literal == 0) {
+      return offsets_.Create<OffsetLiteral>(0);
+    }
+    if (rhs_lit && rhs_lit->literal == 0) {
+      return offsets_.Create<OffsetLiteral>(0);
+    }
+    if (lhs_lit && lhs_lit->literal == 1) {
+      return rhs;
+    }
+    if (rhs_lit && rhs_lit->literal == 1) {
+      return lhs;
+    }
+    if (lhs_lit && rhs_lit) {
+      return offsets_.Create<OffsetLiteral>(lhs_lit->literal *
+                                            rhs_lit->literal);
+    }
+    auto* out = offsets_.Create<OffsetBinOp>();
+    out->op = ast::BinaryOp::kMultiply;
+    out->lhs = lhs;
+    out->rhs = rhs;
+    return out;
+  }
+
+  /// AddAccess() adds the `expr -> access` map item to #accesses, and `expr`
+  /// to #expression_order.
+  /// @param expr the expression that performs the access
+  /// @param access the access
+  void AddAccess(const ast::Expression* expr, const BufferAccess& access) {
+    TINT_ASSERT(Transform, access.type);
+    accesses.emplace(expr, access);
+    expression_order.emplace_back(expr);
+  }
+
+  /// TakeAccess() removes the `node` item from #accesses (if it exists),
+  /// returning the BufferAccess. If #accesses does not hold an item for
+  /// `node`, an invalid BufferAccess is returned.
+  /// @param node the expression that performed an access
+  /// @return the BufferAccess for the given expression
+  BufferAccess TakeAccess(const ast::Expression* node) {
+    auto lhs_it = accesses.find(node);
+    if (lhs_it == accesses.end()) {
+      return {};
+    }
+    auto access = lhs_it->second;
+    accesses.erase(node);
+    return access;
+  }
+
+  /// LoadFunc() returns a symbol to an intrinsic function that loads an element
+  /// of type `el_ty` from a storage or uniform buffer of type `buf_ty`.
+  /// The emitted function has the signature:
+  ///   `fn load(buf : buf_ty, offset : u32) -> el_ty`
+  /// @param buf_ty the storage or uniform buffer type
+  /// @param el_ty the storage or uniform buffer element type
+  /// @param var_user the variable user
+  /// @return the name of the function that performs the load
+  Symbol LoadFunc(const sem::Type* buf_ty,
+                  const sem::Type* el_ty,
+                  const sem::VariableUser* var_user) {
+    auto storage_class = var_user->Variable()->StorageClass();
+    return utils::GetOrCreate(
+        load_funcs, LoadStoreKey{storage_class, buf_ty, el_ty}, [&] {
+          auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
+          auto* disable_validation = b.Disable(
+              ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
+
+          ast::VariableList params = {
+              // Note: The buffer parameter requires the StorageClass in
+              // order for HLSL to emit this as a ByteAddressBuffer or cbuffer
+              // array.
+              b.create<ast::Variable>(b.Sym("buffer"), storage_class,
+                                      var_user->Variable()->Access(),
+                                      buf_ast_ty, true, false, nullptr,
+                                      ast::AttributeList{disable_validation}),
+              b.Param("offset", b.ty.u32()),
+          };
+
+          auto name = b.Sym();
+
+          if (auto* intrinsic =
+                  IntrinsicLoadFor(ctx.dst, storage_class, el_ty)) {
+            auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
+            auto* func = b.create<ast::Function>(
+                name, params, el_ast_ty, nullptr,
+                ast::AttributeList{
+                    intrinsic,
+                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                },
+                ast::AttributeList{});
+            b.AST().AddFunction(func);
+          } else if (auto* arr_ty = el_ty->As<sem::Array>()) {
+            // fn load_func(buf : buf_ty, offset : u32) -> array<T, N> {
+            //   var arr : array<T, N>;
+            //   for (var i = 0u; i < array_count; i = i + 1) {
+            //     arr[i] = el_load_func(buf, offset + i * array_stride)
+            //   }
+            //   return arr;
+            // }
+            auto load =
+                LoadFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
+            auto* arr =
+                b.Var(b.Symbols().New("arr"), CreateASTTypeFor(ctx, arr_ty));
+            auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0u));
+            auto* for_init = b.Decl(i);
+            auto* for_cond = b.create<ast::BinaryExpression>(
+                ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(arr_ty->Count()));
+            auto* for_cont = b.Assign(i, b.Add(i, 1u));
+            auto* arr_el = b.IndexAccessor(arr, i);
+            auto* el_offset =
+                b.Add(b.Expr("offset"), b.Mul(i, arr_ty->Stride()));
+            auto* el_val = b.Call(load, "buffer", el_offset);
+            auto* for_loop = b.For(for_init, for_cond, for_cont,
+                                   b.Block(b.Assign(arr_el, el_val)));
+
+            b.Func(name, params, CreateASTTypeFor(ctx, arr_ty),
+                   {
+                       b.Decl(arr),
+                       for_loop,
+                       b.Return(arr),
+                   });
+          } else {
+            ast::ExpressionList values;
+            if (auto* mat_ty = el_ty->As<sem::Matrix>()) {
+              auto* vec_ty = mat_ty->ColumnType();
+              Symbol load = LoadFunc(buf_ty, vec_ty, var_user);
+              for (uint32_t i = 0; i < mat_ty->columns(); i++) {
+                auto* offset = b.Add("offset", i * mat_ty->ColumnStride());
+                values.emplace_back(b.Call(load, "buffer", offset));
+              }
+            } else if (auto* str = el_ty->As<sem::Struct>()) {
+              for (auto* member : str->Members()) {
+                auto* offset = b.Add("offset", member->Offset());
+                Symbol load =
+                    LoadFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
+                values.emplace_back(b.Call(load, "buffer", offset));
+              }
+            }
+            b.Func(
+                name, params, CreateASTTypeFor(ctx, el_ty),
+                {
+                    b.Return(b.Construct(CreateASTTypeFor(ctx, el_ty), values)),
+                });
+          }
+          return name;
+        });
+  }
+
+  /// StoreFunc() returns a symbol to an intrinsic function that stores an
+  /// element of type `el_ty` to a storage buffer of type `buf_ty`.
+  /// The function has the signature:
+  ///   `fn store(buf : buf_ty, offset : u32, value : el_ty)`
+  /// @param buf_ty the storage buffer type
+  /// @param el_ty the storage buffer element type
+  /// @param var_user the variable user
+  /// @return the name of the function that performs the store
+  Symbol StoreFunc(const sem::Type* buf_ty,
+                   const sem::Type* el_ty,
+                   const sem::VariableUser* var_user) {
+    auto storage_class = var_user->Variable()->StorageClass();
+    return utils::GetOrCreate(
+        store_funcs, LoadStoreKey{storage_class, buf_ty, el_ty}, [&] {
+          auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
+          auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
+          auto* disable_validation = b.Disable(
+              ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
+          ast::VariableList params{
+              // Note: The buffer parameter requires the StorageClass in
+              // order for HLSL to emit this as a ByteAddressBuffer.
+
+              b.create<ast::Variable>(b.Sym("buffer"), storage_class,
+                                      var_user->Variable()->Access(),
+                                      buf_ast_ty, true, false, nullptr,
+                                      ast::AttributeList{disable_validation}),
+              b.Param("offset", b.ty.u32()),
+              b.Param("value", el_ast_ty),
+          };
+
+          auto name = b.Sym();
+
+          if (auto* intrinsic =
+                  IntrinsicStoreFor(ctx.dst, storage_class, el_ty)) {
+            auto* func = b.create<ast::Function>(
+                name, params, b.ty.void_(), nullptr,
+                ast::AttributeList{
+                    intrinsic,
+                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                },
+                ast::AttributeList{});
+            b.AST().AddFunction(func);
+          } else {
+            ast::StatementList body;
+            if (auto* arr_ty = el_ty->As<sem::Array>()) {
+              // fn store_func(buf : buf_ty, offset : u32, value : el_ty) {
+              //   var array = value; // No dynamic indexing on constant arrays
+              //   for (var i = 0u; i < array_count; i = i + 1) {
+              //     arr[i] = el_store_func(buf, offset + i * array_stride,
+              //                            value[i])
+              //   }
+              //   return arr;
+              // }
+              auto* array =
+                  b.Var(b.Symbols().New("array"), nullptr, b.Expr("value"));
+              auto store =
+                  StoreFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
+              auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0u));
+              auto* for_init = b.Decl(i);
+              auto* for_cond = b.create<ast::BinaryExpression>(
+                  ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(arr_ty->Count()));
+              auto* for_cont = b.Assign(i, b.Add(i, 1u));
+              auto* arr_el = b.IndexAccessor(array, i);
+              auto* el_offset =
+                  b.Add(b.Expr("offset"), b.Mul(i, arr_ty->Stride()));
+              auto* store_stmt =
+                  b.CallStmt(b.Call(store, "buffer", el_offset, arr_el));
+              auto* for_loop =
+                  b.For(for_init, for_cond, for_cont, b.Block(store_stmt));
+
+              body = {b.Decl(array), for_loop};
+            } else if (auto* mat_ty = el_ty->As<sem::Matrix>()) {
+              auto* vec_ty = mat_ty->ColumnType();
+              Symbol store = StoreFunc(buf_ty, vec_ty, var_user);
+              for (uint32_t i = 0; i < mat_ty->columns(); i++) {
+                auto* offset = b.Add("offset", i * mat_ty->ColumnStride());
+                auto* access = b.IndexAccessor("value", i);
+                auto* call = b.Call(store, "buffer", offset, access);
+                body.emplace_back(b.CallStmt(call));
+              }
+            } else if (auto* str = el_ty->As<sem::Struct>()) {
+              for (auto* member : str->Members()) {
+                auto* offset = b.Add("offset", member->Offset());
+                auto* access = b.MemberAccessor(
+                    "value", ctx.Clone(member->Declaration()->symbol));
+                Symbol store =
+                    StoreFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
+                auto* call = b.Call(store, "buffer", offset, access);
+                body.emplace_back(b.CallStmt(call));
+              }
+            }
+            b.Func(name, params, b.ty.void_(), body);
+          }
+
+          return name;
+        });
+  }
+
+  /// AtomicFunc() returns a symbol to an intrinsic function that performs an
+  /// atomic operation from a storage buffer of type `buf_ty`. The function has
+  /// the signature:
+  // `fn atomic_op(buf : buf_ty, offset : u32, ...) -> T`
+  /// @param buf_ty the storage buffer type
+  /// @param el_ty the storage buffer element type
+  /// @param intrinsic the atomic intrinsic
+  /// @param var_user the variable user
+  /// @return the name of the function that performs the load
+  Symbol AtomicFunc(const sem::Type* buf_ty,
+                    const sem::Type* el_ty,
+                    const sem::Builtin* intrinsic,
+                    const sem::VariableUser* var_user) {
+    auto op = intrinsic->Type();
+    return utils::GetOrCreate(atomic_funcs, AtomicKey{buf_ty, el_ty, op}, [&] {
+      auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
+      auto* disable_validation = b.Disable(
+          ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
+      // The first parameter to all WGSL atomics is the expression to the
+      // atomic. This is replaced with two parameters: the buffer and offset.
+
+      ast::VariableList params = {
+          // Note: The buffer parameter requires the kStorage StorageClass in
+          // order for HLSL to emit this as a ByteAddressBuffer.
+          b.create<ast::Variable>(b.Sym("buffer"), ast::StorageClass::kStorage,
+                                  var_user->Variable()->Access(), buf_ast_ty,
+                                  true, false, nullptr,
+                                  ast::AttributeList{disable_validation}),
+          b.Param("offset", b.ty.u32()),
+      };
+
+      // Other parameters are copied as-is:
+      for (size_t i = 1; i < intrinsic->Parameters().size(); i++) {
+        auto* param = intrinsic->Parameters()[i];
+        auto* ty = CreateASTTypeFor(ctx, param->Type());
+        params.emplace_back(b.Param("param_" + std::to_string(i), ty));
+      }
+
+      auto* atomic = IntrinsicAtomicFor(ctx.dst, op, el_ty);
+      if (atomic == nullptr) {
+        TINT_ICE(Transform, b.Diagnostics())
+            << "IntrinsicAtomicFor() returned nullptr for op " << op
+            << " and type " << el_ty->type_name();
+      }
+
+      auto* ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
+      auto* func = b.create<ast::Function>(
+          b.Sym(), params, ret_ty, nullptr,
+          ast::AttributeList{
+              atomic,
+              b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+          },
+          ast::AttributeList{});
+
+      b.AST().AddFunction(func);
+      return func->symbol;
+    });
+  }
+};
+
+DecomposeMemoryAccess::Intrinsic::Intrinsic(ProgramID pid,
+                                            Op o,
+                                            ast::StorageClass sc,
+                                            DataType ty)
+    : Base(pid), op(o), storage_class(sc), type(ty) {}
+DecomposeMemoryAccess::Intrinsic::~Intrinsic() = default;
+std::string DecomposeMemoryAccess::Intrinsic::InternalName() const {
+  std::stringstream ss;
+  switch (op) {
+    case Op::kLoad:
+      ss << "intrinsic_load_";
+      break;
+    case Op::kStore:
+      ss << "intrinsic_store_";
+      break;
+    case Op::kAtomicLoad:
+      ss << "intrinsic_atomic_load_";
+      break;
+    case Op::kAtomicStore:
+      ss << "intrinsic_atomic_store_";
+      break;
+    case Op::kAtomicAdd:
+      ss << "intrinsic_atomic_add_";
+      break;
+    case Op::kAtomicSub:
+      ss << "intrinsic_atomic_sub_";
+      break;
+    case Op::kAtomicMax:
+      ss << "intrinsic_atomic_max_";
+      break;
+    case Op::kAtomicMin:
+      ss << "intrinsic_atomic_min_";
+      break;
+    case Op::kAtomicAnd:
+      ss << "intrinsic_atomic_and_";
+      break;
+    case Op::kAtomicOr:
+      ss << "intrinsic_atomic_or_";
+      break;
+    case Op::kAtomicXor:
+      ss << "intrinsic_atomic_xor_";
+      break;
+    case Op::kAtomicExchange:
+      ss << "intrinsic_atomic_exchange_";
+      break;
+    case Op::kAtomicCompareExchangeWeak:
+      ss << "intrinsic_atomic_compare_exchange_weak_";
+      break;
+  }
+  ss << storage_class << "_";
+  switch (type) {
+    case DataType::kU32:
+      ss << "u32";
+      break;
+    case DataType::kF32:
+      ss << "f32";
+      break;
+    case DataType::kI32:
+      ss << "i32";
+      break;
+    case DataType::kVec2U32:
+      ss << "vec2_u32";
+      break;
+    case DataType::kVec2F32:
+      ss << "vec2_f32";
+      break;
+    case DataType::kVec2I32:
+      ss << "vec2_i32";
+      break;
+    case DataType::kVec3U32:
+      ss << "vec3_u32";
+      break;
+    case DataType::kVec3F32:
+      ss << "vec3_f32";
+      break;
+    case DataType::kVec3I32:
+      ss << "vec3_i32";
+      break;
+    case DataType::kVec4U32:
+      ss << "vec4_u32";
+      break;
+    case DataType::kVec4F32:
+      ss << "vec4_f32";
+      break;
+    case DataType::kVec4I32:
+      ss << "vec4_i32";
+      break;
+  }
+  return ss.str();
+}
+
+const DecomposeMemoryAccess::Intrinsic* DecomposeMemoryAccess::Intrinsic::Clone(
+    CloneContext* ctx) const {
+  return ctx->dst->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
+      ctx->dst->ID(), op, storage_class, type);
+}
+
+DecomposeMemoryAccess::DecomposeMemoryAccess() = default;
+DecomposeMemoryAccess::~DecomposeMemoryAccess() = default;
+
+bool DecomposeMemoryAccess::ShouldRun(const Program* program,
+                                      const DataMap&) const {
+  for (auto* decl : program->AST().GlobalDeclarations()) {
+    if (auto* var = program->Sem().Get<sem::Variable>(decl)) {
+      if (var->StorageClass() == ast::StorageClass::kStorage ||
+          var->StorageClass() == ast::StorageClass::kUniform) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void DecomposeMemoryAccess::Run(CloneContext& ctx,
+                                const DataMap&,
+                                DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  State state(ctx);
+
+  // Scan the AST nodes for storage and uniform buffer accesses. Complex
+  // expression chains (e.g. `storage_buffer.foo.bar[20].x`) are handled by
+  // maintaining an offset chain via the `state.TakeAccess()`,
+  // `state.AddAccess()` methods.
+  //
+  // Inner-most expression nodes are guaranteed to be visited first because AST
+  // nodes are fully immutable and require their children to be constructed
+  // first so their pointer can be passed to the parent's constructor.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* ident = node->As<ast::IdentifierExpression>()) {
+      // X
+      if (auto* var = sem.Get<sem::VariableUser>(ident)) {
+        if (var->Variable()->StorageClass() == ast::StorageClass::kStorage ||
+            var->Variable()->StorageClass() == ast::StorageClass::kUniform) {
+          // Variable to a storage or uniform buffer
+          state.AddAccess(ident, {
+                                     var,
+                                     state.ToOffset(0u),
+                                     var->Type()->UnwrapRef(),
+                                 });
+        }
+      }
+      continue;
+    }
+
+    if (auto* accessor = node->As<ast::MemberAccessorExpression>()) {
+      // X.Y
+      auto* accessor_sem = sem.Get(accessor);
+      if (auto* swizzle = accessor_sem->As<sem::Swizzle>()) {
+        if (swizzle->Indices().size() == 1) {
+          if (auto access = state.TakeAccess(accessor->structure)) {
+            auto* vec_ty = access.type->As<sem::Vector>();
+            auto* offset =
+                state.Mul(vec_ty->type()->Size(), swizzle->Indices()[0]);
+            state.AddAccess(accessor, {
+                                          access.var,
+                                          state.Add(access.offset, offset),
+                                          vec_ty->type()->UnwrapRef(),
+                                      });
+          }
+        }
+      } else {
+        if (auto access = state.TakeAccess(accessor->structure)) {
+          auto* str_ty = access.type->As<sem::Struct>();
+          auto* member = str_ty->FindMember(accessor->member->symbol);
+          auto offset = member->Offset();
+          state.AddAccess(accessor, {
+                                        access.var,
+                                        state.Add(access.offset, offset),
+                                        member->Type()->UnwrapRef(),
+                                    });
+        }
+      }
+      continue;
+    }
+
+    if (auto* accessor = node->As<ast::IndexAccessorExpression>()) {
+      if (auto access = state.TakeAccess(accessor->object)) {
+        // X[Y]
+        if (auto* arr = access.type->As<sem::Array>()) {
+          auto* offset = state.Mul(arr->Stride(), accessor->index);
+          state.AddAccess(accessor, {
+                                        access.var,
+                                        state.Add(access.offset, offset),
+                                        arr->ElemType()->UnwrapRef(),
+                                    });
+          continue;
+        }
+        if (auto* vec_ty = access.type->As<sem::Vector>()) {
+          auto* offset = state.Mul(vec_ty->type()->Size(), accessor->index);
+          state.AddAccess(accessor, {
+                                        access.var,
+                                        state.Add(access.offset, offset),
+                                        vec_ty->type()->UnwrapRef(),
+                                    });
+          continue;
+        }
+        if (auto* mat_ty = access.type->As<sem::Matrix>()) {
+          auto* offset = state.Mul(mat_ty->ColumnStride(), accessor->index);
+          state.AddAccess(accessor, {
+                                        access.var,
+                                        state.Add(access.offset, offset),
+                                        mat_ty->ColumnType(),
+                                    });
+          continue;
+        }
+      }
+    }
+
+    if (auto* op = node->As<ast::UnaryOpExpression>()) {
+      if (op->op == ast::UnaryOp::kAddressOf) {
+        // &X
+        if (auto access = state.TakeAccess(op->expr)) {
+          // HLSL does not support pointers, so just take the access from the
+          // reference and place it on the pointer.
+          state.AddAccess(op, access);
+          continue;
+        }
+      }
+    }
+
+    if (auto* assign = node->As<ast::AssignmentStatement>()) {
+      // X = Y
+      // Move the LHS access to a store.
+      if (auto lhs = state.TakeAccess(assign->lhs)) {
+        state.stores.emplace_back(Store{assign, lhs});
+      }
+    }
+
+    if (auto* call_expr = node->As<ast::CallExpression>()) {
+      auto* call = sem.Get(call_expr);
+      if (auto* builtin = call->Target()->As<sem::Builtin>()) {
+        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+          // arrayLength(X)
+          // Don't convert X into a load, this builtin actually requires the
+          // real pointer.
+          state.TakeAccess(call_expr->args[0]);
+          continue;
+        }
+        if (builtin->IsAtomic()) {
+          if (auto access = state.TakeAccess(call_expr->args[0])) {
+            // atomic___(X)
+            ctx.Replace(call_expr, [=, &ctx, &state] {
+              auto* buf = access.var->Declaration();
+              auto* offset = access.offset->Build(ctx);
+              auto* buf_ty = access.var->Type()->UnwrapRef();
+              auto* el_ty = access.type->UnwrapRef()->As<sem::Atomic>()->Type();
+              Symbol func = state.AtomicFunc(
+                  buf_ty, el_ty, builtin, access.var->As<sem::VariableUser>());
+
+              ast::ExpressionList args{ctx.Clone(buf), offset};
+              for (size_t i = 1; i < call_expr->args.size(); i++) {
+                auto* arg = call_expr->args[i];
+                args.emplace_back(ctx.Clone(arg));
+              }
+              return ctx.dst->Call(func, args);
+            });
+          }
+        }
+      }
+    }
+  }
+
+  // All remaining accesses are loads, transform these into calls to the
+  // corresponding load function
+  for (auto* expr : state.expression_order) {
+    auto access_it = state.accesses.find(expr);
+    if (access_it == state.accesses.end()) {
+      continue;
+    }
+    BufferAccess access = access_it->second;
+    ctx.Replace(expr, [=, &ctx, &state] {
+      auto* buf = access.var->Declaration();
+      auto* offset = access.offset->Build(ctx);
+      auto* buf_ty = access.var->Type()->UnwrapRef();
+      auto* el_ty = access.type->UnwrapRef();
+      Symbol func =
+          state.LoadFunc(buf_ty, el_ty, access.var->As<sem::VariableUser>());
+      return ctx.dst->Call(func, ctx.CloneWithoutTransform(buf), offset);
+    });
+  }
+
+  // And replace all storage and uniform buffer assignments with stores
+  for (auto store : state.stores) {
+    ctx.Replace(store.assignment, [=, &ctx, &state] {
+      auto* buf = store.target.var->Declaration();
+      auto* offset = store.target.offset->Build(ctx);
+      auto* buf_ty = store.target.var->Type()->UnwrapRef();
+      auto* el_ty = store.target.type->UnwrapRef();
+      auto* value = store.assignment->rhs;
+      Symbol func = state.StoreFunc(buf_ty, el_ty,
+                                    store.target.var->As<sem::VariableUser>());
+      auto* call = ctx.dst->Call(func, ctx.CloneWithoutTransform(buf), offset,
+                                 ctx.Clone(value));
+      return ctx.dst->CallStmt(call);
+    });
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Offset);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::OffsetLiteral);
diff --git a/src/tint/transform/decompose_memory_access.h b/src/tint/transform/decompose_memory_access.h
new file mode 100644
index 0000000..11b4450
--- /dev/null
+++ b/src/tint/transform/decompose_memory_access.h
@@ -0,0 +1,131 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
+#define SRC_TINT_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
+
+#include <string>
+
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace transform {
+
+/// DecomposeMemoryAccess is a transform used to replace storage and uniform
+/// buffer accesses with a combination of load, store or atomic functions on
+/// primitive types.
+class DecomposeMemoryAccess
+    : public Castable<DecomposeMemoryAccess, Transform> {
+ public:
+  /// Intrinsic is an InternalAttribute that's used to decorate a stub function
+  /// so that the HLSL transforms this into calls to
+  /// `[RW]ByteAddressBuffer.Load[N]()` or `[RW]ByteAddressBuffer.Store[N]()`,
+  /// with a possible cast.
+  class Intrinsic : public Castable<Intrinsic, ast::InternalAttribute> {
+   public:
+    /// Intrinsic op
+    enum class Op {
+      kLoad,
+      kStore,
+      kAtomicLoad,
+      kAtomicStore,
+      kAtomicAdd,
+      kAtomicSub,
+      kAtomicMax,
+      kAtomicMin,
+      kAtomicAnd,
+      kAtomicOr,
+      kAtomicXor,
+      kAtomicExchange,
+      kAtomicCompareExchangeWeak,
+    };
+
+    /// Intrinsic data type
+    enum class DataType {
+      kU32,
+      kF32,
+      kI32,
+      kVec2U32,
+      kVec2F32,
+      kVec2I32,
+      kVec3U32,
+      kVec3F32,
+      kVec3I32,
+      kVec4U32,
+      kVec4F32,
+      kVec4I32,
+    };
+
+    /// Constructor
+    /// @param program_id the identifier of the program that owns this node
+    /// @param o the op of the intrinsic
+    /// @param sc the storage class of the buffer
+    /// @param ty the data type of the intrinsic
+    Intrinsic(ProgramID program_id, Op o, ast::StorageClass sc, DataType ty);
+    /// Destructor
+    ~Intrinsic() override;
+
+    /// @return a short description of the internal attribute which will be
+    /// displayed as `@internal(<name>)`
+    std::string InternalName() const override;
+
+    /// Performs a deep clone of this object using the CloneContext `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned object
+    const Intrinsic* Clone(CloneContext* ctx) const override;
+
+    /// The op of the intrinsic
+    const Op op;
+
+    /// The storage class of the buffer this intrinsic operates on
+    ast::StorageClass const storage_class;
+
+    /// The type of the intrinsic
+    const DataType type;
+  };
+
+  /// Constructor
+  DecomposeMemoryAccess();
+  /// Destructor
+  ~DecomposeMemoryAccess() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+  struct State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
diff --git a/src/tint/transform/decompose_memory_access_test.cc b/src/tint/transform/decompose_memory_access_test.cc
new file mode 100644
index 0000000..91476f3
--- /dev/null
+++ b/src/tint/transform/decompose_memory_access_test.cc
@@ -0,0 +1,2800 @@
+// 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/transform/decompose_memory_access.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using DecomposeMemoryAccessTest = TransformTest;
+
+TEST_F(DecomposeMemoryAccessTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<DecomposeMemoryAccess>(src));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ShouldRunStorageBuffer) {
+  auto* src = R"(
+struct Buffer {
+  i : i32;
+};
+[[group(0), binding(0)]] var<storage, read_write> sb : Buffer;
+)";
+
+  EXPECT_TRUE(ShouldRun<DecomposeMemoryAccess>(src));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ShouldRunUniformBuffer) {
+  auto* src = R"(
+struct Buffer {
+  i : i32;
+};
+[[group(0), binding(0)]] var<uniform> ub : Buffer;
+)";
+
+  EXPECT_TRUE(ShouldRun<DecomposeMemoryAccess>(src));
+}
+
+TEST_F(DecomposeMemoryAccessTest, SB_BasicLoad) {
+  auto* src = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = sb.a;
+  var b : u32 = sb.b;
+  var c : f32 = sb.c;
+  var d : vec2<i32> = sb.d;
+  var e : vec2<u32> = sb.e;
+  var f : vec2<f32> = sb.f;
+  var g : vec3<i32> = sb.g;
+  var h : vec3<u32> = sb.h;
+  var i : vec3<f32> = sb.i;
+  var j : vec4<i32> = sb.j;
+  var k : vec4<u32> = sb.k;
+  var l : vec4<f32> = sb.l;
+  var m : mat2x2<f32> = sb.m;
+  var n : mat2x3<f32> = sb.n;
+  var o : mat2x4<f32> = sb.o;
+  var p : mat3x2<f32> = sb.p;
+  var q : mat3x3<f32> = sb.q;
+  var r : mat3x4<f32> = sb.r;
+  var s : mat4x2<f32> = sb.s;
+  var t : mat4x3<f32> = sb.t;
+  var u : mat4x4<f32> = sb.u;
+  var v : array<vec3<f32>, 2> = sb.v;
+}
+)";
+
+  auto* expect = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = tint_symbol(sb, 0u);
+  var b : u32 = tint_symbol_1(sb, 4u);
+  var c : f32 = tint_symbol_2(sb, 8u);
+  var d : vec2<i32> = tint_symbol_3(sb, 16u);
+  var e : vec2<u32> = tint_symbol_4(sb, 24u);
+  var f : vec2<f32> = tint_symbol_5(sb, 32u);
+  var g : vec3<i32> = tint_symbol_6(sb, 48u);
+  var h : vec3<u32> = tint_symbol_7(sb, 64u);
+  var i : vec3<f32> = tint_symbol_8(sb, 80u);
+  var j : vec4<i32> = tint_symbol_9(sb, 96u);
+  var k : vec4<u32> = tint_symbol_10(sb, 112u);
+  var l : vec4<f32> = tint_symbol_11(sb, 128u);
+  var m : mat2x2<f32> = tint_symbol_12(sb, 144u);
+  var n : mat2x3<f32> = tint_symbol_13(sb, 160u);
+  var o : mat2x4<f32> = tint_symbol_14(sb, 192u);
+  var p : mat3x2<f32> = tint_symbol_15(sb, 224u);
+  var q : mat3x3<f32> = tint_symbol_16(sb, 256u);
+  var r : mat3x4<f32> = tint_symbol_17(sb, 304u);
+  var s : mat4x2<f32> = tint_symbol_18(sb, 352u);
+  var t : mat4x3<f32> = tint_symbol_19(sb, 384u);
+  var u : mat4x4<f32> = tint_symbol_20(sb, 448u);
+  var v : array<vec3<f32>, 2> = tint_symbol_21(sb, 512u);
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, SB_BasicLoad_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = sb.a;
+  var b : u32 = sb.b;
+  var c : f32 = sb.c;
+  var d : vec2<i32> = sb.d;
+  var e : vec2<u32> = sb.e;
+  var f : vec2<f32> = sb.f;
+  var g : vec3<i32> = sb.g;
+  var h : vec3<u32> = sb.h;
+  var i : vec3<f32> = sb.i;
+  var j : vec4<i32> = sb.j;
+  var k : vec4<u32> = sb.k;
+  var l : vec4<f32> = sb.l;
+  var m : mat2x2<f32> = sb.m;
+  var n : mat2x3<f32> = sb.n;
+  var o : mat2x4<f32> = sb.o;
+  var p : mat3x2<f32> = sb.p;
+  var q : mat3x3<f32> = sb.q;
+  var r : mat3x4<f32> = sb.r;
+  var s : mat4x2<f32> = sb.s;
+  var t : mat4x3<f32> = sb.t;
+  var u : mat4x4<f32> = sb.u;
+  var v : array<vec3<f32>, 2> = sb.v;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = tint_symbol(sb, 0u);
+  var b : u32 = tint_symbol_1(sb, 4u);
+  var c : f32 = tint_symbol_2(sb, 8u);
+  var d : vec2<i32> = tint_symbol_3(sb, 16u);
+  var e : vec2<u32> = tint_symbol_4(sb, 24u);
+  var f : vec2<f32> = tint_symbol_5(sb, 32u);
+  var g : vec3<i32> = tint_symbol_6(sb, 48u);
+  var h : vec3<u32> = tint_symbol_7(sb, 64u);
+  var i : vec3<f32> = tint_symbol_8(sb, 80u);
+  var j : vec4<i32> = tint_symbol_9(sb, 96u);
+  var k : vec4<u32> = tint_symbol_10(sb, 112u);
+  var l : vec4<f32> = tint_symbol_11(sb, 128u);
+  var m : mat2x2<f32> = tint_symbol_12(sb, 144u);
+  var n : mat2x3<f32> = tint_symbol_13(sb, 160u);
+  var o : mat2x4<f32> = tint_symbol_14(sb, 192u);
+  var p : mat3x2<f32> = tint_symbol_15(sb, 224u);
+  var q : mat3x3<f32> = tint_symbol_16(sb, 256u);
+  var r : mat3x4<f32> = tint_symbol_17(sb, 304u);
+  var s : mat4x2<f32> = tint_symbol_18(sb, 352u);
+  var t : mat4x3<f32> = tint_symbol_19(sb, 384u);
+  var u : mat4x4<f32> = tint_symbol_20(sb, 448u);
+  var v : array<vec3<f32>, 2> = tint_symbol_21(sb, 512u);
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, UB_BasicLoad) {
+  auto* src = R"(
+struct UB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+
+@group(0) @binding(0) var<uniform> ub : UB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = ub.a;
+  var b : u32 = ub.b;
+  var c : f32 = ub.c;
+  var d : vec2<i32> = ub.d;
+  var e : vec2<u32> = ub.e;
+  var f : vec2<f32> = ub.f;
+  var g : vec3<i32> = ub.g;
+  var h : vec3<u32> = ub.h;
+  var i : vec3<f32> = ub.i;
+  var j : vec4<i32> = ub.j;
+  var k : vec4<u32> = ub.k;
+  var l : vec4<f32> = ub.l;
+  var m : mat2x2<f32> = ub.m;
+  var n : mat2x3<f32> = ub.n;
+  var o : mat2x4<f32> = ub.o;
+  var p : mat3x2<f32> = ub.p;
+  var q : mat3x3<f32> = ub.q;
+  var r : mat3x4<f32> = ub.r;
+  var s : mat4x2<f32> = ub.s;
+  var t : mat4x3<f32> = ub.t;
+  var u : mat4x4<f32> = ub.u;
+  var v : array<vec3<f32>, 2> = ub.v;
+}
+)";
+
+  auto* expect = R"(
+struct UB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+
+@group(0) @binding(0) var<uniform> ub : UB;
+
+@internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> i32
+
+@internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> u32
+
+@internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> f32
+
+@internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = tint_symbol(ub, 0u);
+  var b : u32 = tint_symbol_1(ub, 4u);
+  var c : f32 = tint_symbol_2(ub, 8u);
+  var d : vec2<i32> = tint_symbol_3(ub, 16u);
+  var e : vec2<u32> = tint_symbol_4(ub, 24u);
+  var f : vec2<f32> = tint_symbol_5(ub, 32u);
+  var g : vec3<i32> = tint_symbol_6(ub, 48u);
+  var h : vec3<u32> = tint_symbol_7(ub, 64u);
+  var i : vec3<f32> = tint_symbol_8(ub, 80u);
+  var j : vec4<i32> = tint_symbol_9(ub, 96u);
+  var k : vec4<u32> = tint_symbol_10(ub, 112u);
+  var l : vec4<f32> = tint_symbol_11(ub, 128u);
+  var m : mat2x2<f32> = tint_symbol_12(ub, 144u);
+  var n : mat2x3<f32> = tint_symbol_13(ub, 160u);
+  var o : mat2x4<f32> = tint_symbol_14(ub, 192u);
+  var p : mat3x2<f32> = tint_symbol_15(ub, 224u);
+  var q : mat3x3<f32> = tint_symbol_16(ub, 256u);
+  var r : mat3x4<f32> = tint_symbol_17(ub, 304u);
+  var s : mat4x2<f32> = tint_symbol_18(ub, 352u);
+  var t : mat4x3<f32> = tint_symbol_19(ub, 384u);
+  var u : mat4x4<f32> = tint_symbol_20(ub, 448u);
+  var v : array<vec3<f32>, 2> = tint_symbol_21(ub, 512u);
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, UB_BasicLoad_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = ub.a;
+  var b : u32 = ub.b;
+  var c : f32 = ub.c;
+  var d : vec2<i32> = ub.d;
+  var e : vec2<u32> = ub.e;
+  var f : vec2<f32> = ub.f;
+  var g : vec3<i32> = ub.g;
+  var h : vec3<u32> = ub.h;
+  var i : vec3<f32> = ub.i;
+  var j : vec4<i32> = ub.j;
+  var k : vec4<u32> = ub.k;
+  var l : vec4<f32> = ub.l;
+  var m : mat2x2<f32> = ub.m;
+  var n : mat2x3<f32> = ub.n;
+  var o : mat2x4<f32> = ub.o;
+  var p : mat3x2<f32> = ub.p;
+  var q : mat3x3<f32> = ub.q;
+  var r : mat3x4<f32> = ub.r;
+  var s : mat4x2<f32> = ub.s;
+  var t : mat4x3<f32> = ub.t;
+  var u : mat4x4<f32> = ub.u;
+  var v : array<vec3<f32>, 2> = ub.v;
+}
+
+@group(0) @binding(0) var<uniform> ub : UB;
+
+struct UB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> i32
+
+@internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> u32
+
+@internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> f32
+
+@internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a : i32 = tint_symbol(ub, 0u);
+  var b : u32 = tint_symbol_1(ub, 4u);
+  var c : f32 = tint_symbol_2(ub, 8u);
+  var d : vec2<i32> = tint_symbol_3(ub, 16u);
+  var e : vec2<u32> = tint_symbol_4(ub, 24u);
+  var f : vec2<f32> = tint_symbol_5(ub, 32u);
+  var g : vec3<i32> = tint_symbol_6(ub, 48u);
+  var h : vec3<u32> = tint_symbol_7(ub, 64u);
+  var i : vec3<f32> = tint_symbol_8(ub, 80u);
+  var j : vec4<i32> = tint_symbol_9(ub, 96u);
+  var k : vec4<u32> = tint_symbol_10(ub, 112u);
+  var l : vec4<f32> = tint_symbol_11(ub, 128u);
+  var m : mat2x2<f32> = tint_symbol_12(ub, 144u);
+  var n : mat2x3<f32> = tint_symbol_13(ub, 160u);
+  var o : mat2x4<f32> = tint_symbol_14(ub, 192u);
+  var p : mat3x2<f32> = tint_symbol_15(ub, 224u);
+  var q : mat3x3<f32> = tint_symbol_16(ub, 256u);
+  var r : mat3x4<f32> = tint_symbol_17(ub, 304u);
+  var s : mat4x2<f32> = tint_symbol_18(ub, 352u);
+  var t : mat4x3<f32> = tint_symbol_19(ub, 384u);
+  var u : mat4x4<f32> = tint_symbol_20(ub, 448u);
+  var v : array<vec3<f32>, 2> = tint_symbol_21(ub, 512u);
+}
+
+@group(0) @binding(0) var<uniform> ub : UB;
+
+struct UB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, SB_BasicStore) {
+  auto* src = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  sb.a = i32();
+  sb.b = u32();
+  sb.c = f32();
+  sb.d = vec2<i32>();
+  sb.e = vec2<u32>();
+  sb.f = vec2<f32>();
+  sb.g = vec3<i32>();
+  sb.h = vec3<u32>();
+  sb.i = vec3<f32>();
+  sb.j = vec4<i32>();
+  sb.k = vec4<u32>();
+  sb.l = vec4<f32>();
+  sb.m = mat2x2<f32>();
+  sb.n = mat2x3<f32>();
+  sb.o = mat2x4<f32>();
+  sb.p = mat3x2<f32>();
+  sb.q = mat3x3<f32>();
+  sb.r = mat3x4<f32>();
+  sb.s = mat4x2<f32>();
+  sb.t = mat4x3<f32>();
+  sb.u = mat4x4<f32>();
+  sb.v = array<vec3<f32>, 2>();
+}
+)";
+
+  auto* expect = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
+
+@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
+
+@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
+
+@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
+
+@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
+
+@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
+
+@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
+
+@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
+
+@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
+
+@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
+
+@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
+
+@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+  tint_symbol_5(buffer, (offset + 16u), value[2u]);
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+  tint_symbol_8(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+  tint_symbol_11(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+  tint_symbol_5(buffer, (offset + 16u), value[2u]);
+  tint_symbol_5(buffer, (offset + 24u), value[3u]);
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+  tint_symbol_8(buffer, (offset + 32u), value[2u]);
+  tint_symbol_8(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+  tint_symbol_11(buffer, (offset + 32u), value[2u]);
+  tint_symbol_11(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
+  var array = value;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    tint_symbol_8(buffer, (offset + (i_1 * 16u)), array[i_1]);
+  }
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 0u, i32());
+  tint_symbol_1(sb, 4u, u32());
+  tint_symbol_2(sb, 8u, f32());
+  tint_symbol_3(sb, 16u, vec2<i32>());
+  tint_symbol_4(sb, 24u, vec2<u32>());
+  tint_symbol_5(sb, 32u, vec2<f32>());
+  tint_symbol_6(sb, 48u, vec3<i32>());
+  tint_symbol_7(sb, 64u, vec3<u32>());
+  tint_symbol_8(sb, 80u, vec3<f32>());
+  tint_symbol_9(sb, 96u, vec4<i32>());
+  tint_symbol_10(sb, 112u, vec4<u32>());
+  tint_symbol_11(sb, 128u, vec4<f32>());
+  tint_symbol_12(sb, 144u, mat2x2<f32>());
+  tint_symbol_13(sb, 160u, mat2x3<f32>());
+  tint_symbol_14(sb, 192u, mat2x4<f32>());
+  tint_symbol_15(sb, 224u, mat3x2<f32>());
+  tint_symbol_16(sb, 256u, mat3x3<f32>());
+  tint_symbol_17(sb, 304u, mat3x4<f32>());
+  tint_symbol_18(sb, 352u, mat4x2<f32>());
+  tint_symbol_19(sb, 384u, mat4x3<f32>());
+  tint_symbol_20(sb, 448u, mat4x4<f32>());
+  tint_symbol_21(sb, 512u, array<vec3<f32>, 2>());
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, SB_BasicStore_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  sb.a = i32();
+  sb.b = u32();
+  sb.c = f32();
+  sb.d = vec2<i32>();
+  sb.e = vec2<u32>();
+  sb.f = vec2<f32>();
+  sb.g = vec3<i32>();
+  sb.h = vec3<u32>();
+  sb.i = vec3<f32>();
+  sb.j = vec4<i32>();
+  sb.k = vec4<u32>();
+  sb.l = vec4<f32>();
+  sb.m = mat2x2<f32>();
+  sb.n = mat2x3<f32>();
+  sb.o = mat2x4<f32>();
+  sb.p = mat3x2<f32>();
+  sb.q = mat3x3<f32>();
+  sb.r = mat3x4<f32>();
+  sb.s = mat4x2<f32>();
+  sb.t = mat4x3<f32>();
+  sb.u = mat4x4<f32>();
+  sb.v = array<vec3<f32>, 2>();
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
+
+@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
+
+@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
+
+@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
+
+@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
+
+@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
+
+@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
+
+@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
+
+@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
+
+@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
+
+@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
+
+@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
+
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+}
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+  tint_symbol_5(buffer, (offset + 16u), value[2u]);
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+  tint_symbol_8(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+  tint_symbol_11(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
+  tint_symbol_5(buffer, (offset + 0u), value[0u]);
+  tint_symbol_5(buffer, (offset + 8u), value[1u]);
+  tint_symbol_5(buffer, (offset + 16u), value[2u]);
+  tint_symbol_5(buffer, (offset + 24u), value[3u]);
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
+  tint_symbol_8(buffer, (offset + 0u), value[0u]);
+  tint_symbol_8(buffer, (offset + 16u), value[1u]);
+  tint_symbol_8(buffer, (offset + 32u), value[2u]);
+  tint_symbol_8(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
+  tint_symbol_11(buffer, (offset + 0u), value[0u]);
+  tint_symbol_11(buffer, (offset + 16u), value[1u]);
+  tint_symbol_11(buffer, (offset + 32u), value[2u]);
+  tint_symbol_11(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
+  var array = value;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    tint_symbol_8(buffer, (offset + (i_1 * 16u)), array[i_1]);
+  }
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 0u, i32());
+  tint_symbol_1(sb, 4u, u32());
+  tint_symbol_2(sb, 8u, f32());
+  tint_symbol_3(sb, 16u, vec2<i32>());
+  tint_symbol_4(sb, 24u, vec2<u32>());
+  tint_symbol_5(sb, 32u, vec2<f32>());
+  tint_symbol_6(sb, 48u, vec3<i32>());
+  tint_symbol_7(sb, 64u, vec3<u32>());
+  tint_symbol_8(sb, 80u, vec3<f32>());
+  tint_symbol_9(sb, 96u, vec4<i32>());
+  tint_symbol_10(sb, 112u, vec4<u32>());
+  tint_symbol_11(sb, 128u, vec4<f32>());
+  tint_symbol_12(sb, 144u, mat2x2<f32>());
+  tint_symbol_13(sb, 160u, mat2x3<f32>());
+  tint_symbol_14(sb, 192u, mat2x4<f32>());
+  tint_symbol_15(sb, 224u, mat3x2<f32>());
+  tint_symbol_16(sb, 256u, mat3x3<f32>());
+  tint_symbol_17(sb, 304u, mat3x4<f32>());
+  tint_symbol_18(sb, 352u, mat4x2<f32>());
+  tint_symbol_19(sb, 384u, mat4x3<f32>());
+  tint_symbol_20(sb, 448u, mat4x4<f32>());
+  tint_symbol_21(sb, 512u, array<vec3<f32>, 2>());
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, LoadStructure) {
+  auto* src = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : SB = sb;
+}
+)";
+
+  auto* expect = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)), tint_symbol_6(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)), tint_symbol_9(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_9(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> SB {
+  return SB(tint_symbol_1(buffer, (offset + 0u)), tint_symbol_2(buffer, (offset + 4u)), tint_symbol_3(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)), tint_symbol_6(buffer, (offset + 32u)), tint_symbol_7(buffer, (offset + 48u)), tint_symbol_8(buffer, (offset + 64u)), tint_symbol_9(buffer, (offset + 80u)), tint_symbol_10(buffer, (offset + 96u)), tint_symbol_11(buffer, (offset + 112u)), tint_symbol_12(buffer, (offset + 128u)), tint_symbol_13(buffer, (offset + 144u)), tint_symbol_14(buffer, (offset + 160u)), tint_symbol_15(buffer, (offset + 192u)), tint_symbol_16(buffer, (offset + 224u)), tint_symbol_17(buffer, (offset + 256u)), tint_symbol_18(buffer, (offset + 304u)), tint_symbol_19(buffer, (offset + 352u)), tint_symbol_20(buffer, (offset + 384u)), tint_symbol_21(buffer, (offset + 448u)), tint_symbol_22(buffer, (offset + 512u)));
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : SB = tint_symbol(sb, 0u);
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, LoadStructure_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : SB = sb;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
+
+@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
+
+@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
+
+@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
+
+@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
+
+@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
+
+@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
+
+@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
+
+@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
+  return mat2x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)));
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
+  return mat2x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
+  return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
+  return mat3x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)));
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
+  return mat3x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
+  return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
+  return mat4x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)), tint_symbol_6(buffer, (offset + 24u)));
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
+  return mat4x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)), tint_symbol_9(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
+  return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
+}
+
+fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
+  var arr : array<vec3<f32>, 2u>;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    arr[i_1] = tint_symbol_9(buffer, (offset + (i_1 * 16u)));
+  }
+  return arr;
+}
+
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> SB {
+  return SB(tint_symbol_1(buffer, (offset + 0u)), tint_symbol_2(buffer, (offset + 4u)), tint_symbol_3(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)), tint_symbol_6(buffer, (offset + 32u)), tint_symbol_7(buffer, (offset + 48u)), tint_symbol_8(buffer, (offset + 64u)), tint_symbol_9(buffer, (offset + 80u)), tint_symbol_10(buffer, (offset + 96u)), tint_symbol_11(buffer, (offset + 112u)), tint_symbol_12(buffer, (offset + 128u)), tint_symbol_13(buffer, (offset + 144u)), tint_symbol_14(buffer, (offset + 160u)), tint_symbol_15(buffer, (offset + 192u)), tint_symbol_16(buffer, (offset + 224u)), tint_symbol_17(buffer, (offset + 256u)), tint_symbol_18(buffer, (offset + 304u)), tint_symbol_19(buffer, (offset + 352u)), tint_symbol_20(buffer, (offset + 384u)), tint_symbol_21(buffer, (offset + 448u)), tint_symbol_22(buffer, (offset + 512u)));
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : SB = tint_symbol(sb, 0u);
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, StoreStructure) {
+  auto* src = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  sb = SB();
+}
+)";
+
+  auto* expect = R"(
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
+
+@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
+
+@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
+
+@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
+
+@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
+
+@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
+
+@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
+
+@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
+
+@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
+
+@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
+
+@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
+
+@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+  tint_symbol_6(buffer, (offset + 16u), value[2u]);
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+  tint_symbol_9(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+  tint_symbol_12(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+  tint_symbol_6(buffer, (offset + 16u), value[2u]);
+  tint_symbol_6(buffer, (offset + 24u), value[3u]);
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+  tint_symbol_9(buffer, (offset + 32u), value[2u]);
+  tint_symbol_9(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+  tint_symbol_12(buffer, (offset + 32u), value[2u]);
+  tint_symbol_12(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
+  var array = value;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    tint_symbol_9(buffer, (offset + (i_1 * 16u)), array[i_1]);
+  }
+}
+
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : SB) {
+  tint_symbol_1(buffer, (offset + 0u), value.a);
+  tint_symbol_2(buffer, (offset + 4u), value.b);
+  tint_symbol_3(buffer, (offset + 8u), value.c);
+  tint_symbol_4(buffer, (offset + 16u), value.d);
+  tint_symbol_5(buffer, (offset + 24u), value.e);
+  tint_symbol_6(buffer, (offset + 32u), value.f);
+  tint_symbol_7(buffer, (offset + 48u), value.g);
+  tint_symbol_8(buffer, (offset + 64u), value.h);
+  tint_symbol_9(buffer, (offset + 80u), value.i);
+  tint_symbol_10(buffer, (offset + 96u), value.j);
+  tint_symbol_11(buffer, (offset + 112u), value.k);
+  tint_symbol_12(buffer, (offset + 128u), value.l);
+  tint_symbol_13(buffer, (offset + 144u), value.m);
+  tint_symbol_14(buffer, (offset + 160u), value.n);
+  tint_symbol_15(buffer, (offset + 192u), value.o);
+  tint_symbol_16(buffer, (offset + 224u), value.p);
+  tint_symbol_17(buffer, (offset + 256u), value.q);
+  tint_symbol_18(buffer, (offset + 304u), value.r);
+  tint_symbol_19(buffer, (offset + 352u), value.s);
+  tint_symbol_20(buffer, (offset + 384u), value.t);
+  tint_symbol_21(buffer, (offset + 448u), value.u);
+  tint_symbol_22(buffer, (offset + 512u), value.v);
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 0u, SB());
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, StoreStructure_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  sb = SB();
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
+
+@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
+
+@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
+
+@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
+
+@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
+
+@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
+
+@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
+
+@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
+
+@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
+
+@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
+
+@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
+
+@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
+
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+}
+
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+}
+
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+  tint_symbol_6(buffer, (offset + 16u), value[2u]);
+}
+
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+  tint_symbol_9(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+  tint_symbol_12(buffer, (offset + 32u), value[2u]);
+}
+
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
+  tint_symbol_6(buffer, (offset + 0u), value[0u]);
+  tint_symbol_6(buffer, (offset + 8u), value[1u]);
+  tint_symbol_6(buffer, (offset + 16u), value[2u]);
+  tint_symbol_6(buffer, (offset + 24u), value[3u]);
+}
+
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
+  tint_symbol_9(buffer, (offset + 0u), value[0u]);
+  tint_symbol_9(buffer, (offset + 16u), value[1u]);
+  tint_symbol_9(buffer, (offset + 32u), value[2u]);
+  tint_symbol_9(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
+  tint_symbol_12(buffer, (offset + 0u), value[0u]);
+  tint_symbol_12(buffer, (offset + 16u), value[1u]);
+  tint_symbol_12(buffer, (offset + 32u), value[2u]);
+  tint_symbol_12(buffer, (offset + 48u), value[3u]);
+}
+
+fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
+  var array = value;
+  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
+    tint_symbol_9(buffer, (offset + (i_1 * 16u)), array[i_1]);
+  }
+}
+
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : SB) {
+  tint_symbol_1(buffer, (offset + 0u), value.a);
+  tint_symbol_2(buffer, (offset + 4u), value.b);
+  tint_symbol_3(buffer, (offset + 8u), value.c);
+  tint_symbol_4(buffer, (offset + 16u), value.d);
+  tint_symbol_5(buffer, (offset + 24u), value.e);
+  tint_symbol_6(buffer, (offset + 32u), value.f);
+  tint_symbol_7(buffer, (offset + 48u), value.g);
+  tint_symbol_8(buffer, (offset + 64u), value.h);
+  tint_symbol_9(buffer, (offset + 80u), value.i);
+  tint_symbol_10(buffer, (offset + 96u), value.j);
+  tint_symbol_11(buffer, (offset + 112u), value.k);
+  tint_symbol_12(buffer, (offset + 128u), value.l);
+  tint_symbol_13(buffer, (offset + 144u), value.m);
+  tint_symbol_14(buffer, (offset + 160u), value.n);
+  tint_symbol_15(buffer, (offset + 192u), value.o);
+  tint_symbol_16(buffer, (offset + 224u), value.p);
+  tint_symbol_17(buffer, (offset + 256u), value.q);
+  tint_symbol_18(buffer, (offset + 304u), value.r);
+  tint_symbol_19(buffer, (offset + 352u), value.s);
+  tint_symbol_20(buffer, (offset + 384u), value.t);
+  tint_symbol_21(buffer, (offset + 448u), value.u);
+  tint_symbol_22(buffer, (offset + 512u), value.v);
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 0u, SB());
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  a : i32;
+  b : u32;
+  c : f32;
+  d : vec2<i32>;
+  e : vec2<u32>;
+  f : vec2<f32>;
+  g : vec3<i32>;
+  h : vec3<u32>;
+  i : vec3<f32>;
+  j : vec4<i32>;
+  k : vec4<u32>;
+  l : vec4<f32>;
+  m : mat2x2<f32>;
+  n : mat2x3<f32>;
+  o : mat2x4<f32>;
+  p : mat3x2<f32>;
+  q : mat3x3<f32>;
+  r : mat3x4<f32>;
+  s : mat4x2<f32>;
+  t : mat4x3<f32>;
+  u : mat4x4<f32>;
+  v : array<vec3<f32>, 2>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain) {
+  auto* src = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+};
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : f32 = sb.b[4].b[1].b.z;
+}
+)";
+
+  // sb.b[4].b[1].b.z
+  //    ^  ^ ^  ^ ^ ^
+  //    |  | |  | | |
+  //  128  | |1200| 1224
+  //       | |    |
+  //    1152 1168 1216
+
+  auto* expect = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+}
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : f32 = tint_symbol(sb, 1224u);
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : f32 = sb.b[4].b[1].b.z;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+};
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+};
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+)";
+
+  // sb.b[4].b[1].b.z
+  //    ^  ^ ^  ^ ^ ^
+  //    |  | |  | | |
+  //  128  | |1200| 1224
+  //       | |    |
+  //    1152 1168 1216
+
+  auto* expect = R"(
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var x : f32 = tint_symbol(sb, 1224u);
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+}
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+}
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChain) {
+  auto* src = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+};
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = sb.b[i].b[j].b[k];
+}
+)";
+
+  auto* expect = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+}
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChain_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = sb.b[i].b[j].b[k];
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+};
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+};
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : @stride(256) array<S2>;
+}
+
+struct S2 {
+  a : i32;
+  b : @stride(32) array<S1, 3>;
+  c : i32;
+}
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChainWithAliases) {
+  auto* src = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+
+type A1 = S1;
+
+type A1_Array = @stride(32) array<S1, 3>;
+
+struct S2 {
+  a : i32;
+  b : A1_Array;
+  c : i32;
+};
+
+type A2 = S2;
+
+type A2_Array = @stride(256) array<S2>;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : A2_Array;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = sb.b[i].b[j].b[k];
+}
+)";
+
+  auto* expect = R"(
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+
+type A1 = S1;
+
+type A1_Array = @stride(32) array<S1, 3>;
+
+struct S2 {
+  a : i32;
+  b : A1_Array;
+  c : i32;
+}
+
+type A2 = S2;
+
+type A2_Array = @stride(256) array<S2>;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : A2_Array;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest,
+       ComplexDynamicAccessChainWithAliases_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = sb.b[i].b[j].b[k];
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : A2_Array;
+};
+
+type A2_Array = @stride(256) array<S2>;
+
+type A2 = S2;
+
+struct S2 {
+  a : i32;
+  b : A1_Array;
+  c : i32;
+};
+
+type A1 = S1;
+
+type A1_Array = @stride(32) array<S1, 3>;
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var i : i32 = 4;
+  var j : u32 = 1u;
+  var k : i32 = 2;
+  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  @size(128)
+  a : i32;
+  b : A2_Array;
+}
+
+type A2_Array = @stride(256) array<S2>;
+
+type A2 = S2;
+
+struct S2 {
+  a : i32;
+  b : A1_Array;
+  c : i32;
+}
+
+type A1 = S1;
+
+type A1_Array = @stride(32) array<S1, 3>;
+
+struct S1 {
+  a : i32;
+  b : vec3<f32>;
+  c : i32;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, StorageBufferAtomics) {
+  auto* src = R"(
+struct SB {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  atomicStore(&sb.a, 123);
+  atomicLoad(&sb.a);
+  atomicAdd(&sb.a, 123);
+  atomicSub(&sb.a, 123);
+  atomicMax(&sb.a, 123);
+  atomicMin(&sb.a, 123);
+  atomicAnd(&sb.a, 123);
+  atomicOr(&sb.a, 123);
+  atomicXor(&sb.a, 123);
+  atomicExchange(&sb.a, 123);
+  atomicCompareExchangeWeak(&sb.a, 123, 345);
+
+  atomicStore(&sb.b, 123u);
+  atomicLoad(&sb.b);
+  atomicAdd(&sb.b, 123u);
+  atomicSub(&sb.b, 123u);
+  atomicMax(&sb.b, 123u);
+  atomicMin(&sb.b, 123u);
+  atomicAnd(&sb.b, 123u);
+  atomicOr(&sb.b, 123u);
+  atomicXor(&sb.b, 123u);
+  atomicExchange(&sb.b, 123u);
+  atomicCompareExchangeWeak(&sb.b, 123u, 345u);
+}
+)";
+
+  auto* expect = R"(
+struct SB {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
+
+@internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
+
+@internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
+
+@internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 16u, 123);
+  tint_symbol_1(sb, 16u);
+  tint_symbol_2(sb, 16u, 123);
+  tint_symbol_3(sb, 16u, 123);
+  tint_symbol_4(sb, 16u, 123);
+  tint_symbol_5(sb, 16u, 123);
+  tint_symbol_6(sb, 16u, 123);
+  tint_symbol_7(sb, 16u, 123);
+  tint_symbol_8(sb, 16u, 123);
+  tint_symbol_9(sb, 16u, 123);
+  tint_symbol_10(sb, 16u, 123, 345);
+  tint_symbol_11(sb, 20u, 123u);
+  tint_symbol_12(sb, 20u);
+  tint_symbol_13(sb, 20u, 123u);
+  tint_symbol_14(sb, 20u, 123u);
+  tint_symbol_15(sb, 20u, 123u);
+  tint_symbol_16(sb, 20u, 123u);
+  tint_symbol_17(sb, 20u, 123u);
+  tint_symbol_18(sb, 20u, 123u);
+  tint_symbol_19(sb, 20u, 123u);
+  tint_symbol_20(sb, 20u, 123u);
+  tint_symbol_21(sb, 20u, 123u, 345u);
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, StorageBufferAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  atomicStore(&sb.a, 123);
+  atomicLoad(&sb.a);
+  atomicAdd(&sb.a, 123);
+  atomicSub(&sb.a, 123);
+  atomicMax(&sb.a, 123);
+  atomicMin(&sb.a, 123);
+  atomicAnd(&sb.a, 123);
+  atomicOr(&sb.a, 123);
+  atomicXor(&sb.a, 123);
+  atomicExchange(&sb.a, 123);
+  atomicCompareExchangeWeak(&sb.a, 123, 345);
+
+  atomicStore(&sb.b, 123u);
+  atomicLoad(&sb.b);
+  atomicAdd(&sb.b, 123u);
+  atomicSub(&sb.b, 123u);
+  atomicMax(&sb.b, 123u);
+  atomicMin(&sb.b, 123u);
+  atomicAnd(&sb.b, 123u);
+  atomicOr(&sb.b, 123u);
+  atomicXor(&sb.b, 123u);
+  atomicExchange(&sb.b, 123u);
+  atomicCompareExchangeWeak(&sb.b, 123u, 345u);
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+};
+)";
+
+  auto* expect = R"(
+@internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
+
+@internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+
+@internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+@internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
+
+@internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
+
+@internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+
+@internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+@internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
+fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  tint_symbol(sb, 16u, 123);
+  tint_symbol_1(sb, 16u);
+  tint_symbol_2(sb, 16u, 123);
+  tint_symbol_3(sb, 16u, 123);
+  tint_symbol_4(sb, 16u, 123);
+  tint_symbol_5(sb, 16u, 123);
+  tint_symbol_6(sb, 16u, 123);
+  tint_symbol_7(sb, 16u, 123);
+  tint_symbol_8(sb, 16u, 123);
+  tint_symbol_9(sb, 16u, 123);
+  tint_symbol_10(sb, 16u, 123, 345);
+  tint_symbol_11(sb, 20u, 123u);
+  tint_symbol_12(sb, 20u);
+  tint_symbol_13(sb, 20u, 123u);
+  tint_symbol_14(sb, 20u, 123u);
+  tint_symbol_15(sb, 20u, 123u);
+  tint_symbol_16(sb, 20u, 123u);
+  tint_symbol_17(sb, 20u, 123u);
+  tint_symbol_18(sb, 20u, 123u);
+  tint_symbol_19(sb, 20u, 123u);
+  tint_symbol_20(sb, 20u, 123u);
+  tint_symbol_21(sb, 20u, 123u, 345u);
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+struct SB {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+}
+)";
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, WorkgroupBufferAtomics) {
+  auto* src = R"(
+struct S {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+}
+
+var<workgroup> w : S;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  atomicStore(&(w.a), 123);
+  atomicLoad(&(w.a));
+  atomicAdd(&(w.a), 123);
+  atomicSub(&(w.a), 123);
+  atomicMax(&(w.a), 123);
+  atomicMin(&(w.a), 123);
+  atomicAnd(&(w.a), 123);
+  atomicOr(&(w.a), 123);
+  atomicXor(&(w.a), 123);
+  atomicExchange(&(w.a), 123);
+  atomicCompareExchangeWeak(&(w.a), 123, 345);
+  atomicStore(&(w.b), 123u);
+  atomicLoad(&(w.b));
+  atomicAdd(&(w.b), 123u);
+  atomicSub(&(w.b), 123u);
+  atomicMax(&(w.b), 123u);
+  atomicMin(&(w.b), 123u);
+  atomicAnd(&(w.b), 123u);
+  atomicOr(&(w.b), 123u);
+  atomicXor(&(w.b), 123u);
+  atomicExchange(&(w.b), 123u);
+  atomicCompareExchangeWeak(&(w.b), 123u, 345u);
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeMemoryAccessTest, WorkgroupBufferAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  atomicStore(&(w.a), 123);
+  atomicLoad(&(w.a));
+  atomicAdd(&(w.a), 123);
+  atomicSub(&(w.a), 123);
+  atomicMax(&(w.a), 123);
+  atomicMin(&(w.a), 123);
+  atomicAnd(&(w.a), 123);
+  atomicOr(&(w.a), 123);
+  atomicXor(&(w.a), 123);
+  atomicExchange(&(w.a), 123);
+  atomicCompareExchangeWeak(&(w.a), 123, 345);
+  atomicStore(&(w.b), 123u);
+  atomicLoad(&(w.b));
+  atomicAdd(&(w.b), 123u);
+  atomicSub(&(w.b), 123u);
+  atomicMax(&(w.b), 123u);
+  atomicMin(&(w.b), 123u);
+  atomicAnd(&(w.b), 123u);
+  atomicOr(&(w.b), 123u);
+  atomicXor(&(w.b), 123u);
+  atomicExchange(&(w.b), 123u);
+  atomicCompareExchangeWeak(&(w.b), 123u, 345u);
+}
+
+var<workgroup> w : S;
+
+struct S {
+  padding : vec4<f32>;
+  a : atomic<i32>;
+  b : atomic<u32>;
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<DecomposeMemoryAccess>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/decompose_strided_array.cc b/src/tint/transform/decompose_strided_array.cc
new file mode 100644
index 0000000..0265704
--- /dev/null
+++ b/src/tint/transform/decompose_strided_array.cc
@@ -0,0 +1,162 @@
+// 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/decompose_strided_array.h"
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeStridedArray);
+
+namespace tint {
+namespace transform {
+namespace {
+
+using DecomposedArrays = std::unordered_map<const sem::Array*, Symbol>;
+
+}  // namespace
+
+DecomposeStridedArray::DecomposeStridedArray() = default;
+
+DecomposeStridedArray::~DecomposeStridedArray() = default;
+
+bool DecomposeStridedArray::ShouldRun(const Program* program,
+                                      const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* ast = node->As<ast::Array>()) {
+      if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void DecomposeStridedArray::Run(CloneContext& ctx,
+                                const DataMap&,
+                                DataMap&) const {
+  const auto& sem = ctx.src->Sem();
+
+  static constexpr const char* kMemberName = "el";
+
+  // Maps an array type in the source program to the name of the struct wrapper
+  // type in the target program.
+  std::unordered_map<const sem::Array*, Symbol> decomposed;
+
+  // Find and replace all arrays with a @stride attribute with a array that has
+  // the @stride removed. If the source array stride does not match the natural
+  // stride for the array element type, then replace the array element type with
+  // a structure, holding a single field with a @size attribute equal to the
+  // array stride.
+  ctx.ReplaceAll([&](const ast::Array* ast) -> const ast::Array* {
+    if (auto* arr = sem.Get(ast)) {
+      if (!arr->IsStrideImplicit()) {
+        auto el_ty = utils::GetOrCreate(decomposed, arr, [&] {
+          auto name = ctx.dst->Symbols().New("strided_arr");
+          auto* member_ty = ctx.Clone(ast->type);
+          auto* member = ctx.dst->Member(kMemberName, member_ty,
+                                         {ctx.dst->MemberSize(arr->Stride())});
+          ctx.dst->Structure(name, {member});
+          return name;
+        });
+        auto* count = ctx.Clone(ast->count);
+        return ctx.dst->ty.array(ctx.dst->ty.type_name(el_ty), count);
+      }
+      if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
+        // Strip the @stride attribute
+        auto* ty = ctx.Clone(ast->type);
+        auto* count = ctx.Clone(ast->count);
+        return ctx.dst->ty.array(ty, count);
+      }
+    }
+    return nullptr;
+  });
+
+  // Find all array index-accessors expressions for arrays that have had their
+  // element changed to a single field structure. These expressions are adjusted
+  // to insert an additional member accessor for the single structure field.
+  // Example: `arr[i]` -> `arr[i].el`
+  ctx.ReplaceAll(
+      [&](const ast::IndexAccessorExpression* idx) -> const ast::Expression* {
+        if (auto* ty = ctx.src->TypeOf(idx->object)) {
+          if (auto* arr = ty->UnwrapRef()->As<sem::Array>()) {
+            if (!arr->IsStrideImplicit()) {
+              auto* expr = ctx.CloneWithoutTransform(idx);
+              return ctx.dst->MemberAccessor(expr, kMemberName);
+            }
+          }
+        }
+        return nullptr;
+      });
+
+  // Find all array type constructor expressions for array types that have had
+  // their element changed to a single field structure. These constructors are
+  // adjusted to wrap each of the arguments with an additional constructor for
+  // the new element structure type.
+  // Example:
+  //   `@stride(32) array<i32, 3>(1, 2, 3)`
+  // ->
+  //   `array<strided_arr, 3>(strided_arr(1), strided_arr(2), strided_arr(3))`
+  ctx.ReplaceAll(
+      [&](const ast::CallExpression* expr) -> const ast::Expression* {
+        if (!expr->args.empty()) {
+          if (auto* call = sem.Get(expr)) {
+            if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
+              if (auto* arr = ctor->ReturnType()->As<sem::Array>()) {
+                // Begin by cloning the array constructor type or name
+                // If this is an unaliased array, this may add a new entry to
+                // decomposed.
+                // If this is an aliased array, decomposed should already be
+                // populated with any strided aliases.
+                ast::CallExpression::Target target;
+                if (expr->target.type) {
+                  target.type = ctx.Clone(expr->target.type);
+                } else {
+                  target.name = ctx.Clone(expr->target.name);
+                }
+
+                ast::ExpressionList args;
+                if (auto it = decomposed.find(arr); it != decomposed.end()) {
+                  args.reserve(expr->args.size());
+                  for (auto* arg : expr->args) {
+                    args.emplace_back(
+                        ctx.dst->Call(it->second, ctx.Clone(arg)));
+                  }
+                } else {
+                  args = ctx.Clone(expr->args);
+                }
+
+                return target.type ? ctx.dst->Construct(target.type, args)
+                                   : ctx.dst->Call(target.name, args);
+              }
+            }
+          }
+        }
+        return nullptr;
+      });
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/decompose_strided_array.h b/src/tint/transform/decompose_strided_array.h
new file mode 100644
index 0000000..07d3353
--- /dev/null
+++ b/src/tint/transform/decompose_strided_array.h
@@ -0,0 +1,61 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
+#define SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// DecomposeStridedArray transforms replaces arrays with a non-default
+/// `@stride` attribute with an array of structure elements, where the
+/// structure contains a single field with an equivalent `@size` attribute.
+/// `@stride` attributes on arrays that match the default stride are also
+/// removed.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * SimplifyPointers
+class DecomposeStridedArray
+    : public Castable<DecomposeStridedArray, Transform> {
+ public:
+  /// Constructor
+  DecomposeStridedArray();
+
+  /// Destructor
+  ~DecomposeStridedArray() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
diff --git a/src/tint/transform/decompose_strided_array_test.cc b/src/tint/transform/decompose_strided_array_test.cc
new file mode 100644
index 0000000..0fbbd8e
--- /dev/null
+++ b/src/tint/transform/decompose_strided_array_test.cc
@@ -0,0 +1,698 @@
+// 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/decompose_strided_array.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using DecomposeStridedArrayTest = TransformTest;
+using f32 = ProgramBuilder::f32;
+
+TEST_F(DecomposeStridedArrayTest, ShouldRunEmptyModule) {
+  ProgramBuilder b;
+  EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
+}
+
+TEST_F(DecomposeStridedArrayTest, ShouldRunNonStridedArray) {
+  // var<private> arr : array<f32, 4>
+
+  ProgramBuilder b;
+  b.Global("arr", b.ty.array<f32, 4>(), ast::StorageClass::kPrivate);
+  EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
+}
+
+TEST_F(DecomposeStridedArrayTest, ShouldRunDefaultStridedArray) {
+  // var<private> arr : @stride(4) array<f32, 4>
+
+  ProgramBuilder b;
+  b.Global("arr", b.ty.array<f32, 4>(4), ast::StorageClass::kPrivate);
+  EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
+}
+
+TEST_F(DecomposeStridedArrayTest, ShouldRunExplicitStridedArray) {
+  // var<private> arr : @stride(16) array<f32, 4>
+
+  ProgramBuilder b;
+  b.Global("arr", b.ty.array<f32, 4>(16), ast::StorageClass::kPrivate);
+  EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
+}
+
+TEST_F(DecomposeStridedArrayTest, Empty) {
+  auto* src = R"()";
+  auto* expect = src;
+
+  auto got = Run<DecomposeStridedArray>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, PrivateDefaultStridedArray) {
+  // var<private> arr : @stride(4) array<f32, 4>
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(4) array<f32, 4> = a;
+  //   let b : f32 = arr[1];
+  // }
+
+  ProgramBuilder b;
+  b.Global("arr", b.ty.array<f32, 4>(4), ast::StorageClass::kPrivate);
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array<f32, 4>(4), b.Expr("arr"))),
+             b.Decl(b.Const("b", b.ty.f32(), b.IndexAccessor("arr", 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+var<private> arr : array<f32, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<f32, 4> = arr;
+  let b : f32 = arr[1];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, PrivateStridedArray) {
+  // var<private> arr : @stride(32) array<f32, 4>
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(32) array<f32, 4> = a;
+  //   let b : f32 = arr[1];
+  // }
+
+  ProgramBuilder b;
+  b.Global("arr", b.ty.array<f32, 4>(32), ast::StorageClass::kPrivate);
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array<f32, 4>(32), b.Expr("arr"))),
+             b.Decl(b.Const("b", b.ty.f32(), b.IndexAccessor("arr", 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+var<private> arr : array<strided_arr, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<strided_arr, 4> = arr;
+  let b : f32 = arr[1].el;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, ReadUniformStridedArray) {
+  // struct S {
+  //   a : @stride(32) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<uniform> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(32) array<f32, 4> = s.a;
+  //   let b : f32 = s.a[1];
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
+           b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array<f32, 4>(32),
+                            b.MemberAccessor("s", "a"))),
+             b.Decl(b.Const("b", b.ty.f32(),
+                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+struct S {
+  a : array<strided_arr, 4>;
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<strided_arr, 4> = s.a;
+  let b : f32 = s.a[1].el;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, ReadUniformDefaultStridedArray) {
+  // struct S {
+  //   a : @stride(16) array<vec4<f32>, 4>;
+  // };
+  // @group(0) @binding(0) var<uniform> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(16) array<vec4<f32>, 4> = s.a;
+  //   let b : f32 = s.a[1][2];
+  // }
+  ProgramBuilder b;
+  auto* S =
+      b.Structure("S", {b.Member("a", b.ty.array(b.ty.vec4<f32>(), 4, 16))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
+           b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array(b.ty.vec4<f32>(), 4, 16),
+                            b.MemberAccessor("s", "a"))),
+             b.Decl(b.Const(
+                 "b", b.ty.f32(),
+                 b.IndexAccessor(b.IndexAccessor(b.MemberAccessor("s", "a"), 1),
+                                 2))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect =
+      R"(
+struct S {
+  a : array<vec4<f32>, 4>;
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<vec4<f32>, 4> = s.a;
+  let b : f32 = s.a[1][2];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, ReadStorageStridedArray) {
+  // struct S {
+  //   a : @stride(32) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<storage> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(32) array<f32, 4> = s.a;
+  //   let b : f32 = s.a[1];
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array<f32, 4>(32),
+                            b.MemberAccessor("s", "a"))),
+             b.Decl(b.Const("b", b.ty.f32(),
+                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+struct S {
+  a : array<strided_arr, 4>;
+}
+
+@group(0) @binding(0) var<storage> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<strided_arr, 4> = s.a;
+  let b : f32 = s.a[1].el;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, ReadStorageDefaultStridedArray) {
+  // struct S {
+  //   a : @stride(4) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<storage> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : @stride(4) array<f32, 4> = s.a;
+  //   let b : f32 = s.a[1];
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(4))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.array<f32, 4>(4),
+                            b.MemberAccessor("s", "a"))),
+             b.Decl(b.Const("b", b.ty.f32(),
+                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  a : array<f32, 4>;
+}
+
+@group(0) @binding(0) var<storage> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : array<f32, 4> = s.a;
+  let b : f32 = s.a[1];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, WriteStorageStridedArray) {
+  // struct S {
+  //   a : @stride(32) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   s.a = @stride(32) array<f32, 4>();
+  //   s.a = @stride(32) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
+  //   s.a[1] = 5.0;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.array<f32, 4>(32))),
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.array<f32, 4>(32), 1.0f, 2.0f, 3.0f, 4.0f)),
+          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect =
+      R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+struct S {
+  a : array<strided_arr, 4>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  s.a = array<strided_arr, 4>();
+  s.a = array<strided_arr, 4>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
+  s.a[1].el = 5.0;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, WriteStorageDefaultStridedArray) {
+  // struct S {
+  //   a : @stride(4) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   s.a = @stride(4) array<f32, 4>();
+  //   s.a = @stride(4) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
+  //   s.a[1] = 5.0;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(4))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.array<f32, 4>(4))),
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.array<f32, 4>(4), 1.0f, 2.0f, 3.0f, 4.0f)),
+          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect =
+      R"(
+struct S {
+  a : array<f32, 4>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  s.a = array<f32, 4>();
+  s.a = array<f32, 4>(1.0, 2.0, 3.0, 4.0);
+  s.a[1] = 5.0;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, ReadWriteViaPointerLets) {
+  // struct S {
+  //   a : @stride(32) array<f32, 4>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a = &s.a;
+  //   let b = &*&*(a);
+  //   let c = *b;
+  //   let d = (*b)[1];
+  //   (*b) = @stride(32) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
+  //   (*b)[1] = 5.0;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", nullptr,
+                            b.AddressOf(b.MemberAccessor("s", "a")))),
+             b.Decl(b.Const("b", nullptr,
+                            b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
+             b.Decl(b.Const("c", nullptr, b.Deref("b"))),
+             b.Decl(b.Const("d", nullptr, b.IndexAccessor(b.Deref("b"), 1))),
+             b.Assign(b.Deref("b"), b.Construct(b.ty.array<f32, 4>(32), 1.0f,
+                                                2.0f, 3.0f, 4.0f)),
+             b.Assign(b.IndexAccessor(b.Deref("b"), 1), 5.0f),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect =
+      R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+struct S {
+  a : array<strided_arr, 4>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let c = s.a;
+  let d = s.a[1].el;
+  s.a = array<strided_arr, 4>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
+  s.a[1].el = 5.0;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, PrivateAliasedStridedArray) {
+  // type ARR = @stride(32) array<f32, 4>;
+  // struct S {
+  //   a : ARR;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : ARR = s.a;
+  //   let b : f32 = s.a[1];
+  //   s.a = ARR();
+  //   s.a = ARR(1.0, 2.0, 3.0, 4.0);
+  //   s.a[1] = 5.0;
+  // }
+  ProgramBuilder b;
+  b.Alias("ARR", b.ty.array<f32, 4>(32));
+  auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR"))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(
+              b.Const("a", b.ty.type_name("ARR"), b.MemberAccessor("s", "a"))),
+          b.Decl(b.Const("b", b.ty.f32(),
+                         b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.type_name("ARR"))),
+          b.Assign(b.MemberAccessor("s", "a"),
+                   b.Construct(b.ty.type_name("ARR"), 1.0f, 2.0f, 3.0f, 4.0f)),
+          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct strided_arr {
+  @size(32)
+  el : f32;
+}
+
+type ARR = array<strided_arr, 4>;
+
+struct S {
+  a : ARR;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : ARR = s.a;
+  let b : f32 = s.a[1].el;
+  s.a = ARR();
+  s.a = ARR(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
+  s.a[1].el = 5.0;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedArrayTest, PrivateNestedStridedArray) {
+  // type ARR_A = @stride(8) array<f32, 2>;
+  // type ARR_B = @stride(128) array<@stride(16) array<ARR_A, 3>, 4>;
+  // struct S {
+  //   a : ARR_B;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a : ARR_B = s.a;
+  //   let b : array<@stride(8) array<f32, 2>, 3> = s.a[3];
+  //   let c = s.a[3][2];
+  //   let d = s.a[3][2][1];
+  //   s.a = ARR_B();
+  //   s.a[3][2][1] = 5.0;
+  // }
+
+  ProgramBuilder b;
+  b.Alias("ARR_A", b.ty.array<f32, 2>(8));
+  b.Alias("ARR_B",
+          b.ty.array(                                      //
+              b.ty.array(b.ty.type_name("ARR_A"), 3, 16),  //
+              4, 128));
+  auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR_B"))});
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("a", b.ty.type_name("ARR_B"),
+                            b.MemberAccessor("s", "a"))),
+             b.Decl(b.Const("b", b.ty.array(b.ty.type_name("ARR_A"), 3, 16),
+                            b.IndexAccessor(                 //
+                                b.MemberAccessor("s", "a"),  //
+                                3))),
+             b.Decl(b.Const("c", b.ty.type_name("ARR_A"),
+                            b.IndexAccessor(                     //
+                                b.IndexAccessor(                 //
+                                    b.MemberAccessor("s", "a"),  //
+                                    3),
+                                2))),
+             b.Decl(b.Const("d", b.ty.f32(),
+                            b.IndexAccessor(                         //
+                                b.IndexAccessor(                     //
+                                    b.IndexAccessor(                 //
+                                        b.MemberAccessor("s", "a"),  //
+                                        3),
+                                    2),
+                                1))),
+             b.Assign(b.MemberAccessor("s", "a"),
+                      b.Construct(b.ty.type_name("ARR_B"))),
+             b.Assign(b.IndexAccessor(                         //
+                          b.IndexAccessor(                     //
+                              b.IndexAccessor(                 //
+                                  b.MemberAccessor("s", "a"),  //
+                                  3),
+                              2),
+                          1),
+                      5.0f),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect =
+      R"(
+struct strided_arr {
+  @size(8)
+  el : f32;
+}
+
+type ARR_A = array<strided_arr, 2>;
+
+struct strided_arr_1 {
+  @size(128)
+  el : array<ARR_A, 3>;
+}
+
+type ARR_B = array<strided_arr_1, 4>;
+
+struct S {
+  a : ARR_B;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let a : ARR_B = s.a;
+  let b : array<ARR_A, 3> = s.a[3].el;
+  let c : ARR_A = s.a[3].el[2];
+  let d : f32 = s.a[3].el[2][1].el;
+  s.a = ARR_B();
+  s.a[3].el[2][1].el = 5.0;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/decompose_strided_matrix.cc b/src/tint/transform/decompose_strided_matrix.cc
new file mode 100644
index 0000000..6f06c80
--- /dev/null
+++ b/src/tint/transform/decompose_strided_matrix.cc
@@ -0,0 +1,251 @@
+// 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/transform/decompose_strided_matrix.h"
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeStridedMatrix);
+
+namespace tint {
+namespace transform {
+namespace {
+
+/// MatrixInfo describes a matrix member with a custom stride
+struct MatrixInfo {
+  /// The stride in bytes between columns of the matrix
+  uint32_t stride = 0;
+  /// The type of the matrix
+  const sem::Matrix* matrix = nullptr;
+
+  /// @returns a new ast::Array that holds an vector column for each row of the
+  /// matrix.
+  const ast::Array* array(ProgramBuilder* b) const {
+    return b->ty.array(b->ty.vec<ProgramBuilder::f32>(matrix->rows()),
+                       matrix->columns(), stride);
+  }
+
+  /// Equality operator
+  bool operator==(const MatrixInfo& info) const {
+    return stride == info.stride && matrix == info.matrix;
+  }
+  /// Hash function
+  struct Hasher {
+    size_t operator()(const MatrixInfo& t) const {
+      return utils::Hash(t.stride, t.matrix);
+    }
+  };
+};
+
+/// Return type of the callback function of GatherCustomStrideMatrixMembers
+enum GatherResult { kContinue, kStop };
+
+/// GatherCustomStrideMatrixMembers scans `program` for all matrix members of
+/// storage and uniform structs, which are of a matrix type, and have a custom
+/// matrix stride attribute. For each matrix member found, `callback` is called.
+/// `callback` is a function with the signature:
+///      GatherResult(const sem::StructMember* member,
+///                   sem::Matrix* matrix,
+///                   uint32_t stride)
+/// If `callback` return GatherResult::kStop, then the scanning will immediately
+/// terminate, and GatherCustomStrideMatrixMembers() will return, otherwise
+/// scanning will continue.
+template <typename F>
+void GatherCustomStrideMatrixMembers(const Program* program, F&& callback) {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* str = node->As<ast::Struct>()) {
+      auto* str_ty = program->Sem().Get(str);
+      if (!str_ty->UsedAs(ast::StorageClass::kUniform) &&
+          !str_ty->UsedAs(ast::StorageClass::kStorage)) {
+        continue;
+      }
+      for (auto* member : str_ty->Members()) {
+        auto* matrix = member->Type()->As<sem::Matrix>();
+        if (!matrix) {
+          continue;
+        }
+        auto* attr = ast::GetAttribute<ast::StrideAttribute>(
+            member->Declaration()->attributes);
+        if (!attr) {
+          continue;
+        }
+        uint32_t stride = attr->stride;
+        if (matrix->ColumnStride() == stride) {
+          continue;
+        }
+        if (callback(member, matrix, stride) == GatherResult::kStop) {
+          return;
+        }
+      }
+    }
+  }
+}
+
+}  // namespace
+
+DecomposeStridedMatrix::DecomposeStridedMatrix() = default;
+
+DecomposeStridedMatrix::~DecomposeStridedMatrix() = default;
+
+bool DecomposeStridedMatrix::ShouldRun(const Program* program,
+                                       const DataMap&) const {
+  bool should_run = false;
+  GatherCustomStrideMatrixMembers(
+      program, [&](const sem::StructMember*, sem::Matrix*, uint32_t) {
+        should_run = true;
+        return GatherResult::kStop;
+      });
+  return should_run;
+}
+
+void DecomposeStridedMatrix::Run(CloneContext& ctx,
+                                 const DataMap&,
+                                 DataMap&) const {
+  // Scan the program for all storage and uniform structure matrix members with
+  // a custom stride attribute. Replace these matrices with an equivalent array,
+  // and populate the `decomposed` map with the members that have been replaced.
+  std::unordered_map<const ast::StructMember*, MatrixInfo> decomposed;
+  GatherCustomStrideMatrixMembers(
+      ctx.src, [&](const sem::StructMember* member, sem::Matrix* matrix,
+                   uint32_t stride) {
+        // We've got ourselves a struct member of a matrix type with a custom
+        // stride. Replace this with an array of column vectors.
+        MatrixInfo info{stride, matrix};
+        auto* replacement = ctx.dst->Member(
+            member->Offset(), ctx.Clone(member->Name()), info.array(ctx.dst));
+        ctx.Replace(member->Declaration(), replacement);
+        decomposed.emplace(member->Declaration(), info);
+        return GatherResult::kContinue;
+      });
+
+  // For all expressions where a single matrix column vector was indexed, we can
+  // preserve these without calling conversion functions.
+  // Example:
+  //   ssbo.mat[2] -> ssbo.mat[2]
+  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr)
+                     -> const ast::IndexAccessorExpression* {
+    if (auto* access =
+            ctx.src->Sem().Get<sem::StructMemberAccess>(expr->object)) {
+      auto it = decomposed.find(access->Member()->Declaration());
+      if (it != decomposed.end()) {
+        auto* obj = ctx.CloneWithoutTransform(expr->object);
+        auto* idx = ctx.Clone(expr->index);
+        return ctx.dst->IndexAccessor(obj, idx);
+      }
+    }
+    return nullptr;
+  });
+
+  // For all struct member accesses to the matrix on the LHS of an assignment,
+  // we need to convert the matrix to the array before assigning to the
+  // structure.
+  // Example:
+  //   ssbo.mat = mat_to_arr(m)
+  std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> mat_to_arr;
+  ctx.ReplaceAll([&](const ast::AssignmentStatement* stmt)
+                     -> const ast::Statement* {
+    if (auto* access = ctx.src->Sem().Get<sem::StructMemberAccess>(stmt->lhs)) {
+      auto it = decomposed.find(access->Member()->Declaration());
+      if (it == decomposed.end()) {
+        return nullptr;
+      }
+      MatrixInfo info = it->second;
+      auto fn = utils::GetOrCreate(mat_to_arr, info, [&] {
+        auto name = ctx.dst->Symbols().New(
+            "mat" + std::to_string(info.matrix->columns()) + "x" +
+            std::to_string(info.matrix->rows()) + "_stride_" +
+            std::to_string(info.stride) + "_to_arr");
+
+        auto matrix = [&] { return CreateASTTypeFor(ctx, info.matrix); };
+        auto array = [&] { return info.array(ctx.dst); };
+
+        auto mat = ctx.dst->Sym("m");
+        ast::ExpressionList columns(info.matrix->columns());
+        for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size()); i++) {
+          columns[i] = ctx.dst->IndexAccessor(mat, i);
+        }
+        ctx.dst->Func(name,
+                      {
+                          ctx.dst->Param(mat, matrix()),
+                      },
+                      array(),
+                      {
+                          ctx.dst->Return(ctx.dst->Construct(array(), columns)),
+                      });
+        return name;
+      });
+      auto* lhs = ctx.CloneWithoutTransform(stmt->lhs);
+      auto* rhs = ctx.dst->Call(fn, ctx.Clone(stmt->rhs));
+      return ctx.dst->Assign(lhs, rhs);
+    }
+    return nullptr;
+  });
+
+  // For all other struct member accesses, we need to convert the array to the
+  // matrix type. Example:
+  //   m = arr_to_mat(ssbo.mat)
+  std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> arr_to_mat;
+  ctx.ReplaceAll(
+      [&](const ast::MemberAccessorExpression* expr) -> const ast::Expression* {
+        if (auto* access = ctx.src->Sem().Get<sem::StructMemberAccess>(expr)) {
+          auto it = decomposed.find(access->Member()->Declaration());
+          if (it == decomposed.end()) {
+            return nullptr;
+          }
+          MatrixInfo info = it->second;
+          auto fn = utils::GetOrCreate(arr_to_mat, info, [&] {
+            auto name = ctx.dst->Symbols().New(
+                "arr_to_mat" + std::to_string(info.matrix->columns()) + "x" +
+                std::to_string(info.matrix->rows()) + "_stride_" +
+                std::to_string(info.stride));
+
+            auto matrix = [&] { return CreateASTTypeFor(ctx, info.matrix); };
+            auto array = [&] { return info.array(ctx.dst); };
+
+            auto arr = ctx.dst->Sym("arr");
+            ast::ExpressionList columns(info.matrix->columns());
+            for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size());
+                 i++) {
+              columns[i] = ctx.dst->IndexAccessor(arr, i);
+            }
+            ctx.dst->Func(
+                name,
+                {
+                    ctx.dst->Param(arr, array()),
+                },
+                matrix(),
+                {
+                    ctx.dst->Return(ctx.dst->Construct(matrix(), columns)),
+                });
+            return name;
+          });
+          return ctx.dst->Call(fn, ctx.CloneWithoutTransform(expr));
+        }
+        return nullptr;
+      });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/decompose_strided_matrix.h b/src/tint/transform/decompose_strided_matrix.h
new file mode 100644
index 0000000..eb908ff
--- /dev/null
+++ b/src/tint/transform/decompose_strided_matrix.h
@@ -0,0 +1,61 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
+#define SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// DecomposeStridedMatrix transforms replaces matrix members of storage or
+/// uniform buffer structures, that have a [[stride]] attribute, into an array
+/// of N column vectors.
+/// This transform is used by the SPIR-V reader to handle the SPIR-V
+/// MatrixStride attribute.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * SimplifyPointers
+class DecomposeStridedMatrix
+    : public Castable<DecomposeStridedMatrix, Transform> {
+ public:
+  /// Constructor
+  DecomposeStridedMatrix();
+
+  /// Destructor
+  ~DecomposeStridedMatrix() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
diff --git a/src/tint/transform/decompose_strided_matrix_test.cc b/src/tint/transform/decompose_strided_matrix_test.cc
new file mode 100644
index 0000000..b2098bb
--- /dev/null
+++ b/src/tint/transform/decompose_strided_matrix_test.cc
@@ -0,0 +1,671 @@
+// 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/transform/decompose_strided_matrix.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using DecomposeStridedMatrixTest = TransformTest;
+using f32 = ProgramBuilder::f32;
+
+TEST_F(DecomposeStridedMatrixTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<DecomposeStridedMatrix>(src));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ShouldRunNonStridedMatrox) {
+  auto* src = R"(
+var<private> m : mat3x2<f32>;
+)";
+
+  EXPECT_FALSE(ShouldRun<DecomposeStridedMatrix>(src));
+}
+
+TEST_F(DecomposeStridedMatrixTest, Empty) {
+  auto* src = R"()";
+  auto* expect = src;
+
+  auto got = Run<DecomposeStridedMatrix>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadUniformMatrix) {
+  // struct S {
+  //   @offset(16) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<uniform> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : mat2x2<f32> = s.m;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(16),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
+           b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct S {
+  @size(16)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
+  return mat2x2<f32>(arr[0u], arr[1u]);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : mat2x2<f32> = arr_to_mat2x2_stride_32(s.m);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadUniformColumn) {
+  // struct S {
+  //   @offset(16) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<uniform> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : vec2<f32> = s.m[1];
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(16),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
+           b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("x", b.ty.vec2<f32>(),
+                            b.IndexAccessor(b.MemberAccessor("s", "m"), 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  @size(16)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : vec2<f32> = s.m[1];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadUniformMatrix_DefaultStride) {
+  // struct S {
+  //   @offset(16) @stride(8)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<uniform> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : mat2x2<f32> = s.m;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(16),
+                  b.create<ast::StrideAttribute>(8),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
+           b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct S {
+  @size(16)
+  padding : u32;
+  @stride(8) @internal(disable_validation__ignore_stride)
+  m : mat2x2<f32>;
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : mat2x2<f32> = s.m;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadStorageMatrix) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : mat2x2<f32> = s.m;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
+  return mat2x2<f32>(arr[0u], arr[1u]);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : mat2x2<f32> = arr_to_mat2x2_stride_32(s.m);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadStorageColumn) {
+  // struct S {
+  //   @offset(16) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : vec2<f32> = s.m[1];
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(16),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Decl(b.Const("x", b.ty.vec2<f32>(),
+                            b.IndexAccessor(b.MemberAccessor("s", "m"), 1))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  @size(16)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : vec2<f32> = s.m[1];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, WriteStorageMatrix) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Assign(b.MemberAccessor("s", "m"),
+                      b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
+                                    b.vec2<f32>(3.0f, 4.0f))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn mat2x2_stride_32_to_arr(m : mat2x2<f32>) -> @stride(32) array<vec2<f32>, 2u> {
+  return @stride(32) array<vec2<f32>, 2u>(m[0u], m[1u]);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, WriteStorageColumn) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   s.m[1] = vec2<f32>(1.0, 2.0);
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Assign(b.IndexAccessor(b.MemberAccessor("s", "m"), 1),
+                      b.vec2<f32>(1.0f, 2.0f)),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  s.m[1] = vec2<f32>(1.0, 2.0);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadWriteViaPointerLets) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let a = &s.m;
+  //   let b = &*&*(a);
+  //   let x = *b;
+  //   let y = (*b)[1];
+  //   let z = x[1];
+  //   (*b) = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
+  //   (*b)[1] = vec2<f32>(5.0, 6.0);
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
+           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(
+              b.Const("a", nullptr, b.AddressOf(b.MemberAccessor("s", "m")))),
+          b.Decl(b.Const("b", nullptr,
+                         b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
+          b.Decl(b.Const("x", nullptr, b.Deref("b"))),
+          b.Decl(b.Const("y", nullptr, b.IndexAccessor(b.Deref("b"), 1))),
+          b.Decl(b.Const("z", nullptr, b.IndexAccessor("x", 1))),
+          b.Assign(b.Deref("b"), b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
+                                               b.vec2<f32>(3.0f, 4.0f))),
+          b.Assign(b.IndexAccessor(b.Deref("b"), 1), b.vec2<f32>(5.0f, 6.0f)),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  m : @stride(32) array<vec2<f32>, 2u>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
+  return mat2x2<f32>(arr[0u], arr[1u]);
+}
+
+fn mat2x2_stride_32_to_arr(m : mat2x2<f32>) -> @stride(32) array<vec2<f32>, 2u> {
+  return @stride(32) array<vec2<f32>, 2u>(m[0u], m[1u]);
+}
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x = arr_to_mat2x2_stride_32(s.m);
+  let y = s.m[1];
+  let z = x[1];
+  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
+  s.m[1] = vec2<f32>(5.0, 6.0);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, ReadPrivateMatrix) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // var<private> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   let x : mat2x2<f32> = s.m;
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kPrivate);
+  b.Func(
+      "f", {}, b.ty.void_(),
+      {
+          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
+      },
+      {
+          b.Stage(ast::PipelineStage::kCompute),
+          b.WorkgroupSize(1),
+      });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  @stride(32) @internal(disable_validation__ignore_stride)
+  m : mat2x2<f32>;
+}
+
+var<private> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  let x : mat2x2<f32> = s.m;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DecomposeStridedMatrixTest, WritePrivateMatrix) {
+  // struct S {
+  //   @offset(8) @stride(32)
+  //   @internal(ignore_stride_attribute)
+  //   m : mat2x2<f32>;
+  // };
+  // var<private> s : S;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn f() {
+  //   s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
+  // }
+  ProgramBuilder b;
+  auto* S = b.Structure(
+      "S",
+      {
+          b.Member(
+              "m", b.ty.mat2x2<f32>(),
+              {
+                  b.create<ast::StructMemberOffsetAttribute>(8),
+                  b.create<ast::StrideAttribute>(32),
+                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
+              }),
+      });
+  b.Global("s", b.ty.Of(S), ast::StorageClass::kPrivate);
+  b.Func("f", {}, b.ty.void_(),
+         {
+             b.Assign(b.MemberAccessor("s", "m"),
+                      b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
+                                    b.vec2<f32>(3.0f, 4.0f))),
+         },
+         {
+             b.Stage(ast::PipelineStage::kCompute),
+             b.WorkgroupSize(1),
+         });
+
+  auto* expect = R"(
+struct S {
+  @size(8)
+  padding : u32;
+  @stride(32) @internal(disable_validation__ignore_stride)
+  m : mat2x2<f32>;
+}
+
+var<private> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
+      Program(std::move(b)));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/external_texture_transform.cc b/src/tint/transform/external_texture_transform.cc
new file mode 100644
index 0000000..eefa4c0
--- /dev/null
+++ b/src/tint/transform/external_texture_transform.cc
@@ -0,0 +1,131 @@
+// 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/transform/external_texture_transform.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ExternalTextureTransform);
+
+namespace tint {
+namespace transform {
+
+ExternalTextureTransform::ExternalTextureTransform() = default;
+ExternalTextureTransform::~ExternalTextureTransform() = default;
+
+void ExternalTextureTransform::Run(CloneContext& ctx,
+                                   const DataMap&,
+                                   DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  // Within this transform, usages of texture_external are replaced with a
+  // texture_2d<f32>, which will allow us perform operations on a
+  // texture_external without maintaining texture_external-specific code
+  // generation paths in the backends.
+
+  // When replacing instances of texture_external with texture_2d<f32> we must
+  // also modify calls to the texture_external overloads of textureLoad and
+  // textureSampleLevel, which unlike their texture_2d<f32> overloads do not
+  // require a level parameter. To do this we identify calls to textureLoad and
+  // textureSampleLevel that use texture_external as the first parameter and add
+  // a parameter for the level (which is always 0).
+
+  // Scan the AST nodes for calls to textureLoad or textureSampleLevel.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* call_expr = node->As<ast::CallExpression>()) {
+      if (auto* builtin = sem.Get(call_expr)->Target()->As<sem::Builtin>()) {
+        if (builtin->Type() == sem::BuiltinType::kTextureLoad ||
+            builtin->Type() == sem::BuiltinType::kTextureSampleLevel) {
+          // When a textureLoad or textureSampleLevel has been identified, check
+          // if the first parameter is an external texture.
+          if (auto* var =
+                  sem.Get(call_expr->args[0])->As<sem::VariableUser>()) {
+            if (var->Variable()
+                    ->Type()
+                    ->UnwrapRef()
+                    ->Is<sem::ExternalTexture>()) {
+              if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
+                  call_expr->args.size() != 2) {
+                TINT_ICE(Transform, ctx.dst->Diagnostics())
+                    << "expected textureLoad call with a texture_external to "
+                       "have 2 parameters, found "
+                    << call_expr->args.size() << " parameters";
+              }
+
+              if (builtin->Type() == sem::BuiltinType::kTextureSampleLevel &&
+                  call_expr->args.size() != 3) {
+                TINT_ICE(Transform, ctx.dst->Diagnostics())
+                    << "expected textureSampleLevel call with a "
+                       "texture_external to have 3 parameters, found "
+                    << call_expr->args.size() << " parameters";
+              }
+
+              // Replace the call with another that has the same parameters in
+              // addition to a level parameter (always zero for external
+              // textures).
+              auto* exp = ctx.Clone(call_expr->target.name);
+              auto* externalTextureParam = ctx.Clone(call_expr->args[0]);
+
+              ast::ExpressionList params;
+              if (builtin->Type() == sem::BuiltinType::kTextureLoad) {
+                auto* coordsParam = ctx.Clone(call_expr->args[1]);
+                auto* levelParam = ctx.dst->Expr(0);
+                params = {externalTextureParam, coordsParam, levelParam};
+              } else if (builtin->Type() ==
+                         sem::BuiltinType::kTextureSampleLevel) {
+                auto* samplerParam = ctx.Clone(call_expr->args[1]);
+                auto* coordsParam = ctx.Clone(call_expr->args[2]);
+                auto* levelParam = ctx.dst->Expr(0.0f);
+                params = {externalTextureParam, samplerParam, coordsParam,
+                          levelParam};
+              }
+
+              auto* newCall = ctx.dst->create<ast::CallExpression>(exp, params);
+              ctx.Replace(call_expr, newCall);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Scan the AST nodes for external texture declarations.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* var = node->As<ast::Variable>()) {
+      if (::tint::Is<ast::ExternalTexture>(var->type)) {
+        // Replace a single-plane external texture with a 2D, f32 sampled
+        // texture.
+        auto* newType = ctx.dst->ty.sampled_texture(ast::TextureDimension::k2d,
+                                                    ctx.dst->ty.f32());
+        auto clonedSrc = ctx.Clone(var->source);
+        auto clonedSym = ctx.Clone(var->symbol);
+        auto* clonedConstructor = ctx.Clone(var->constructor);
+        auto clonedAttributes = ctx.Clone(var->attributes);
+        auto* newVar = ctx.dst->create<ast::Variable>(
+            clonedSrc, clonedSym, var->declared_storage_class,
+            var->declared_access, newType, false, false, clonedConstructor,
+            clonedAttributes);
+
+        ctx.Replace(var, newVar);
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/external_texture_transform.h b/src/tint/transform/external_texture_transform.h
new file mode 100644
index 0000000..f0669f4
--- /dev/null
+++ b/src/tint/transform/external_texture_transform.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
+#define SRC_TINT_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
+
+#include <utility>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Because an external texture is comprised of 1-3 texture views we can simply
+/// transform external textures into the appropriate number of sampled textures.
+/// This allows us to share SPIR-V/HLSL writer paths for sampled textures
+/// instead of adding dedicated writer paths for external textures.
+/// ExternalTextureTransform performs this transformation.
+class ExternalTextureTransform
+    : public Castable<ExternalTextureTransform, Transform> {
+ public:
+  /// Constructor
+  ExternalTextureTransform();
+  /// Destructor
+  ~ExternalTextureTransform() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
diff --git a/src/tint/transform/external_texture_transform_test.cc b/src/tint/transform/external_texture_transform_test.cc
new file mode 100644
index 0000000..5434039
--- /dev/null
+++ b/src/tint/transform/external_texture_transform_test.cc
@@ -0,0 +1,187 @@
+// 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/transform/external_texture_transform.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ExternalTextureTransformTest = TransformTest;
+
+TEST_F(ExternalTextureTransformTest, SampleLevelSinglePlane) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var t : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)));
+}
+)";
+
+  auto* expect = R"(
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var t : texture_2d<f32>;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)), 0.0);
+}
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExternalTextureTransformTest, SampleLevelSinglePlane_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)));
+}
+
+@group(0) @binding(1) var t : texture_external;
+
+@group(0) @binding(0) var s : sampler;
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)), 0.0);
+}
+
+@group(0) @binding(1) var t : texture_2d<f32>;
+
+@group(0) @binding(0) var s : sampler;
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExternalTextureTransformTest, LoadSinglePlane) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(t, vec2<i32>(1, 1));
+}
+)";
+
+  auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(t, vec2<i32>(1, 1), 0);
+}
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExternalTextureTransformTest, LoadSinglePlane_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(t, vec2<i32>(1, 1));
+}
+
+@group(0) @binding(0) var t : texture_external;
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(t, vec2<i32>(1, 1), 0);
+}
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExternalTextureTransformTest, DimensionsSinglePlane) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(t);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+)";
+
+  auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(t);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExternalTextureTransformTest, DimensionsSinglePlane_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(t);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+
+@group(0) @binding(0) var t : texture_external;
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(t);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+)";
+
+  auto got = Run<ExternalTextureTransform>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
new file mode 100644
index 0000000..0a21017
--- /dev/null
+++ b/src/tint/transform/first_index_offset.cc
@@ -0,0 +1,188 @@
+// Copyright 2020 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/first_index_offset.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset::BindingPoint);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset::Data);
+
+namespace tint {
+namespace transform {
+namespace {
+
+// Uniform buffer member names
+constexpr char kFirstVertexName[] = "first_vertex_index";
+constexpr char kFirstInstanceName[] = "first_instance_index";
+
+}  // namespace
+
+FirstIndexOffset::BindingPoint::BindingPoint() = default;
+FirstIndexOffset::BindingPoint::BindingPoint(uint32_t b, uint32_t g)
+    : binding(b), group(g) {}
+FirstIndexOffset::BindingPoint::~BindingPoint() = default;
+
+FirstIndexOffset::Data::Data(bool has_vtx_index,
+                             bool has_inst_index,
+                             uint32_t first_vtx_offset,
+                             uint32_t first_inst_offset)
+    : has_vertex_index(has_vtx_index),
+      has_instance_index(has_inst_index),
+      first_vertex_offset(first_vtx_offset),
+      first_instance_offset(first_inst_offset) {}
+FirstIndexOffset::Data::Data(const Data&) = default;
+FirstIndexOffset::Data::~Data() = default;
+
+FirstIndexOffset::FirstIndexOffset() = default;
+FirstIndexOffset::~FirstIndexOffset() = default;
+
+bool FirstIndexOffset::ShouldRun(const Program* program, const DataMap&) const {
+  for (auto* fn : program->AST().Functions()) {
+    if (fn->PipelineStage() == ast::PipelineStage::kVertex) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void FirstIndexOffset::Run(CloneContext& ctx,
+                           const DataMap& inputs,
+                           DataMap& outputs) const {
+  // Get the uniform buffer binding point
+  uint32_t ub_binding = binding_;
+  uint32_t ub_group = group_;
+  if (auto* binding_point = inputs.Get<BindingPoint>()) {
+    ub_binding = binding_point->binding;
+    ub_group = binding_point->group;
+  }
+
+  // Map of builtin usages
+  std::unordered_map<const sem::Variable*, const char*> builtin_vars;
+  std::unordered_map<const sem::StructMember*, const char*> builtin_members;
+
+  bool has_vertex_index = false;
+  bool has_instance_index = false;
+
+  // Traverse the AST scanning for builtin accesses via variables (includes
+  // parameters) or structure member accesses.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* var = node->As<ast::Variable>()) {
+      for (auto* attr : var->attributes) {
+        if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
+          ast::Builtin builtin = builtin_attr->builtin;
+          if (builtin == ast::Builtin::kVertexIndex) {
+            auto* sem_var = ctx.src->Sem().Get(var);
+            builtin_vars.emplace(sem_var, kFirstVertexName);
+            has_vertex_index = true;
+          }
+          if (builtin == ast::Builtin::kInstanceIndex) {
+            auto* sem_var = ctx.src->Sem().Get(var);
+            builtin_vars.emplace(sem_var, kFirstInstanceName);
+            has_instance_index = true;
+          }
+        }
+      }
+    }
+    if (auto* member = node->As<ast::StructMember>()) {
+      for (auto* attr : member->attributes) {
+        if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
+          ast::Builtin builtin = builtin_attr->builtin;
+          if (builtin == ast::Builtin::kVertexIndex) {
+            auto* sem_mem = ctx.src->Sem().Get(member);
+            builtin_members.emplace(sem_mem, kFirstVertexName);
+            has_vertex_index = true;
+          }
+          if (builtin == ast::Builtin::kInstanceIndex) {
+            auto* sem_mem = ctx.src->Sem().Get(member);
+            builtin_members.emplace(sem_mem, kFirstInstanceName);
+            has_instance_index = true;
+          }
+        }
+      }
+    }
+  }
+
+  // Byte offsets on the uniform buffer
+  uint32_t vertex_index_offset = 0;
+  uint32_t instance_index_offset = 0;
+
+  if (has_vertex_index || has_instance_index) {
+    // Add uniform buffer members and calculate byte offsets
+    uint32_t offset = 0;
+    ast::StructMemberList members;
+    if (has_vertex_index) {
+      members.push_back(ctx.dst->Member(kFirstVertexName, ctx.dst->ty.u32()));
+      vertex_index_offset = offset;
+      offset += 4;
+    }
+    if (has_instance_index) {
+      members.push_back(ctx.dst->Member(kFirstInstanceName, ctx.dst->ty.u32()));
+      instance_index_offset = offset;
+      offset += 4;
+    }
+    auto* struct_ = ctx.dst->Structure(ctx.dst->Sym(), std::move(members));
+
+    // Create a global to hold the uniform buffer
+    Symbol buffer_name = ctx.dst->Sym();
+    ctx.dst->Global(buffer_name, ctx.dst->ty.Of(struct_),
+                    ast::StorageClass::kUniform, nullptr,
+                    ast::AttributeList{
+                        ctx.dst->create<ast::BindingAttribute>(ub_binding),
+                        ctx.dst->create<ast::GroupAttribute>(ub_group),
+                    });
+
+    // Fix up all references to the builtins with the offsets
+    ctx.ReplaceAll(
+        [=, &ctx](const ast::Expression* expr) -> const ast::Expression* {
+          if (auto* sem = ctx.src->Sem().Get(expr)) {
+            if (auto* user = sem->As<sem::VariableUser>()) {
+              auto it = builtin_vars.find(user->Variable());
+              if (it != builtin_vars.end()) {
+                return ctx.dst->Add(
+                    ctx.CloneWithoutTransform(expr),
+                    ctx.dst->MemberAccessor(buffer_name, it->second));
+              }
+            }
+            if (auto* access = sem->As<sem::StructMemberAccess>()) {
+              auto it = builtin_members.find(access->Member());
+              if (it != builtin_members.end()) {
+                return ctx.dst->Add(
+                    ctx.CloneWithoutTransform(expr),
+                    ctx.dst->MemberAccessor(buffer_name, it->second));
+              }
+            }
+          }
+          // Not interested in this experssion. Just clone.
+          return nullptr;
+        });
+  }
+
+  ctx.Clone();
+
+  outputs.Add<Data>(has_vertex_index, has_instance_index, vertex_index_offset,
+                    instance_index_offset);
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/first_index_offset.h b/src/tint/transform/first_index_offset.h
new file mode 100644
index 0000000..ce3135e
--- /dev/null
+++ b/src/tint/transform/first_index_offset.h
@@ -0,0 +1,143 @@
+// Copyright 2020 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_FIRST_INDEX_OFFSET_H_
+#define SRC_TINT_TRANSFORM_FIRST_INDEX_OFFSET_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Adds firstVertex/Instance (injected via root constants) to
+/// vertex/instance index builtins.
+///
+/// This transform assumes that Name transform has been run before.
+///
+/// Unlike other APIs, D3D always starts vertex and instance numbering at 0,
+/// regardless of the firstVertex/Instance value specified. This transformer
+/// adds the value of firstVertex/Instance to each builtin. This action is
+/// performed by adding a new constant equal to original builtin +
+/// firstVertex/Instance to each function that references one of these builtins.
+///
+/// Note that D3D does not have any semantics for firstVertex/Instance.
+/// Therefore, these values must by passed to the shader.
+///
+/// Before:
+/// ```
+///   @builtin(vertex_index) var<in> vert_idx : u32;
+///   fn func() -> u32 {
+///     return vert_idx;
+///   }
+/// ```
+///
+/// After:
+/// ```
+///   struct TintFirstIndexOffsetData {
+///     tint_first_vertex_index : u32;
+///     tint_first_instance_index : u32;
+///   };
+///   @builtin(vertex_index) var<in> tint_first_index_offset_vert_idx : u32;
+///   @binding(N) @group(M) var<uniform> tint_first_index_data :
+///                                                    TintFirstIndexOffsetData;
+///   fn func() -> u32 {
+///     const vert_idx = (tint_first_index_offset_vert_idx +
+///                       tint_first_index_data.tint_first_vertex_index);
+///     return vert_idx;
+///   }
+/// ```
+///
+class FirstIndexOffset : public Castable<FirstIndexOffset, Transform> {
+ public:
+  /// BindingPoint is consumed by the FirstIndexOffset transform.
+  /// BindingPoint specifies the binding point of the first index uniform
+  /// buffer.
+  struct BindingPoint : public Castable<BindingPoint, transform::Data> {
+    /// Constructor
+    BindingPoint();
+
+    /// Constructor
+    /// @param b the binding index
+    /// @param g the binding group
+    BindingPoint(uint32_t b, uint32_t g);
+
+    /// Destructor
+    ~BindingPoint() override;
+
+    /// `@binding()` for the first vertex / first instance uniform buffer
+    uint32_t binding = 0;
+    /// `@group()` for the first vertex / first instance uniform buffer
+    uint32_t group = 0;
+  };
+
+  /// Data is outputted by the FirstIndexOffset transform.
+  /// Data holds information about shader usage and constant buffer offsets.
+  struct Data : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param has_vtx_index True if the shader uses vertex_index
+    /// @param has_inst_index True if the shader uses instance_index
+    /// @param first_vtx_offset Offset of first vertex into constant buffer
+    /// @param first_inst_offset Offset of first instance into constant buffer
+    Data(bool has_vtx_index,
+         bool has_inst_index,
+         uint32_t first_vtx_offset,
+         uint32_t first_inst_offset);
+
+    /// Copy constructor
+    Data(const Data&);
+
+    /// Destructor
+    ~Data() override;
+
+    /// True if the shader uses vertex_index
+    const bool has_vertex_index;
+    /// True if the shader uses instance_index
+    const bool has_instance_index;
+    /// Offset of first vertex into constant buffer
+    const uint32_t first_vertex_offset;
+    /// Offset of first instance into constant buffer
+    const uint32_t first_instance_offset;
+  };
+
+  /// Constructor
+  FirstIndexOffset();
+  /// Destructor
+  ~FirstIndexOffset() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  uint32_t binding_ = 0;
+  uint32_t group_ = 0;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_FIRST_INDEX_OFFSET_H_
diff --git a/src/tint/transform/first_index_offset_test.cc b/src/tint/transform/first_index_offset_test.cc
new file mode 100644
index 0000000..d482114
--- /dev/null
+++ b/src/tint/transform/first_index_offset_test.cc
@@ -0,0 +1,650 @@
+// Copyright 2020 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/first_index_offset.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using FirstIndexOffsetTest = TransformTest;
+
+TEST_F(FirstIndexOffsetTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<FirstIndexOffset>(src));
+}
+
+TEST_F(FirstIndexOffsetTest, ShouldRunFragmentStage) {
+  auto* src = R"(
+[[stage(fragment)]]
+fn entry() {
+  return;
+}
+)";
+
+  EXPECT_FALSE(ShouldRun<FirstIndexOffset>(src));
+}
+
+TEST_F(FirstIndexOffsetTest, ShouldRunVertexStage) {
+  auto* src = R"(
+[[stage(vertex)]]
+fn entry() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<FirstIndexOffset>(src));
+}
+
+TEST_F(FirstIndexOffsetTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(0, 0);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  EXPECT_EQ(data, nullptr);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicVertexShader) {
+  auto* src = R"(
+@stage(vertex)
+fn entry() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+  auto* expect = src;
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(0, 0);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, false);
+  EXPECT_EQ(data->has_instance_index, false);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleVertexIndex) {
+  auto* src = R"(
+fn test(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  test(vert_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+fn test(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  test((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, false);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleVertexIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  test(vert_idx);
+  return vec4<f32>();
+}
+
+fn test(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  test((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+
+fn test(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, false);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleInstanceIndex) {
+  auto* src = R"(
+fn test(inst_idx : u32) -> u32 {
+  return inst_idx;
+}
+
+@stage(vertex)
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  test(inst_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_instance_index : u32;
+}
+
+@binding(1) @group(7) var<uniform> tint_symbol_1 : tint_symbol;
+
+fn test(inst_idx : u32) -> u32 {
+  return inst_idx;
+}
+
+@stage(vertex)
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  test((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 7);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, false);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleInstanceIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  test(inst_idx);
+  return vec4<f32>();
+}
+
+fn test(inst_idx : u32) -> u32 {
+  return inst_idx;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_instance_index : u32;
+}
+
+@binding(1) @group(7) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(vertex)
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  test((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
+}
+
+fn test(inst_idx : u32) -> u32 {
+  return inst_idx;
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 7);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, false);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleBothIndex) {
+  auto* src = R"(
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+  return instance_idx + vert_idx;
+}
+
+struct Inputs {
+  @builtin(instance_index) instance_idx : u32;
+  @builtin(vertex_index) vert_idx : u32;
+};
+
+@stage(vertex)
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  test(inputs.instance_idx, inputs.vert_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+  first_instance_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+  return (instance_idx + vert_idx);
+}
+
+struct Inputs {
+  @builtin(instance_index)
+  instance_idx : u32;
+  @builtin(vertex_index)
+  vert_idx : u32;
+}
+
+@stage(vertex)
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  test((inputs.instance_idx + tint_symbol_1.first_instance_index), (inputs.vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 4u);
+}
+
+TEST_F(FirstIndexOffsetTest, BasicModuleBothIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  test(inputs.instance_idx, inputs.vert_idx);
+  return vec4<f32>();
+}
+
+struct Inputs {
+  @builtin(instance_index) instance_idx : u32;
+  @builtin(vertex_index) vert_idx : u32;
+};
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+  return instance_idx + vert_idx;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+  first_instance_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(vertex)
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  test((inputs.instance_idx + tint_symbol_1.first_instance_index), (inputs.vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+
+struct Inputs {
+  @builtin(instance_index)
+  instance_idx : u32;
+  @builtin(vertex_index)
+  vert_idx : u32;
+}
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+  return (instance_idx + vert_idx);
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 4u);
+}
+
+TEST_F(FirstIndexOffsetTest, NestedCalls) {
+  auto* src = R"(
+fn func1(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+
+fn func2(vert_idx : u32) -> u32 {
+  return func1(vert_idx);
+}
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func2(vert_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+fn func1(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+
+fn func2(vert_idx : u32) -> u32 {
+  return func1(vert_idx);
+}
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func2((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, false);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, NestedCalls_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func2(vert_idx);
+  return vec4<f32>();
+}
+
+fn func2(vert_idx : u32) -> u32 {
+  return func1(vert_idx);
+}
+
+fn func1(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func2((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+
+fn func2(vert_idx : u32) -> u32 {
+  return func1(vert_idx);
+}
+
+fn func1(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, false);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 0u);
+}
+
+TEST_F(FirstIndexOffsetTest, MultipleEntryPoints) {
+  auto* src = R"(
+fn func(i : u32) -> u32 {
+  return i;
+}
+
+@stage(vertex)
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func(vert_idx);
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(vert_idx + inst_idx);
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(inst_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+  first_instance_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+fn func(i : u32) -> u32 {
+  return i;
+}
+
+@stage(vertex)
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(((vert_idx + tint_symbol_1.first_vertex_index) + (inst_idx + tint_symbol_1.first_instance_index)));
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 4u);
+}
+
+TEST_F(FirstIndexOffsetTest, MultipleEntryPoints_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func(vert_idx);
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(vert_idx + inst_idx);
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(inst_idx);
+  return vec4<f32>();
+}
+
+fn func(i : u32) -> u32 {
+  return i;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  first_vertex_index : u32;
+  first_instance_index : u32;
+}
+
+@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
+
+@stage(vertex)
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+  func((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func(((vert_idx + tint_symbol_1.first_vertex_index) + (inst_idx + tint_symbol_1.first_instance_index)));
+  return vec4<f32>();
+}
+
+@stage(vertex)
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+  func((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
+}
+
+fn func(i : u32) -> u32 {
+  return i;
+}
+)";
+
+  DataMap config;
+  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
+  auto got = Run<FirstIndexOffset>(src, std::move(config));
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<FirstIndexOffset::Data>();
+
+  ASSERT_NE(data, nullptr);
+  EXPECT_EQ(data->has_vertex_index, true);
+  EXPECT_EQ(data->has_instance_index, true);
+  EXPECT_EQ(data->first_vertex_offset, 0u);
+  EXPECT_EQ(data->first_instance_offset, 4u);
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/fold_constants.cc b/src/tint/transform/fold_constants.cc
new file mode 100644
index 0000000..08995eb
--- /dev/null
+++ b/src/tint/transform/fold_constants.cc
@@ -0,0 +1,99 @@
+// 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/transform/fold_constants.h"
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldConstants);
+
+namespace tint {
+namespace transform {
+
+FoldConstants::FoldConstants() = default;
+
+FoldConstants::~FoldConstants() = default;
+
+void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  ctx.ReplaceAll([&](const ast::Expression* expr) -> const ast::Expression* {
+    auto* call = ctx.src->Sem().Get<sem::Call>(expr);
+    if (!call) {
+      return nullptr;
+    }
+
+    auto value = call->ConstantValue();
+    if (!value.IsValid()) {
+      return nullptr;
+    }
+
+    auto* ty = call->Type();
+
+    if (!call->Target()->IsAnyOf<sem::TypeConversion, sem::TypeConstructor>()) {
+      return nullptr;
+    }
+
+    // If original ctor expression had no init values, don't replace the
+    // expression
+    if (call->Arguments().empty()) {
+      return nullptr;
+    }
+
+    if (auto* vec = ty->As<sem::Vector>()) {
+      uint32_t vec_size = static_cast<uint32_t>(vec->Width());
+
+      // We'd like to construct the new vector with the same number of
+      // constructor args that the original node had, but after folding
+      // constants, cases like the following are problematic:
+      //
+      // vec3<f32> = vec3<f32>(vec2<f32>, 1.0) // vec_size=3, ctor_size=2
+      //
+      // In this case, creating a vec3 with 2 args is invalid, so we should
+      // create it with 3. So what we do is construct with vec_size args,
+      // except if the original vector was single-value initialized, in
+      // which case, we only construct with one arg again.
+      uint32_t ctor_size = (call->Arguments().size() == 1) ? 1 : vec_size;
+
+      ast::ExpressionList ctors;
+      for (uint32_t i = 0; i < ctor_size; ++i) {
+        value.WithScalarAt(
+            i, [&](auto&& s) { ctors.emplace_back(ctx.dst->Expr(s)); });
+      }
+
+      auto* el_ty = CreateASTTypeFor(ctx, vec->type());
+      return ctx.dst->vec(el_ty, vec_size, ctors);
+    }
+
+    if (ty->is_scalar()) {
+      return value.WithScalarAt(0,
+                                [&](auto&& s) -> const ast::LiteralExpression* {
+                                  return ctx.dst->Expr(s);
+                                });
+    }
+
+    return nullptr;
+  });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/fold_constants.h b/src/tint/transform/fold_constants.h
new file mode 100644
index 0000000..564ade0
--- /dev/null
+++ b/src/tint/transform/fold_constants.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_FOLD_CONSTANTS_H_
+#define SRC_TINT_TRANSFORM_FOLD_CONSTANTS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// FoldConstants transforms the AST by folding constant expressions
+class FoldConstants : public Castable<FoldConstants, Transform> {
+ public:
+  /// Constructor
+  FoldConstants();
+
+  /// Destructor
+  ~FoldConstants() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_FOLD_CONSTANTS_H_
diff --git a/src/tint/transform/fold_constants_test.cc b/src/tint/transform/fold_constants_test.cc
new file mode 100644
index 0000000..fd57c92
--- /dev/null
+++ b/src/tint/transform/fold_constants_test.cc
@@ -0,0 +1,427 @@
+// 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/transform/fold_constants.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using FoldConstantsTest = TransformTest;
+
+TEST_F(FoldConstantsTest, Module_Scalar_NoConversion) {
+  auto* src = R"(
+var<private> a : i32 = i32(123);
+var<private> b : u32 = u32(123u);
+var<private> c : f32 = f32(123.0);
+var<private> d : bool = bool(true);
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : i32 = 123;
+
+var<private> b : u32 = 123u;
+
+var<private> c : f32 = 123.0;
+
+var<private> d : bool = true;
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Scalar_Conversion) {
+  auto* src = R"(
+var<private> a : i32 = i32(123.0);
+var<private> b : u32 = u32(123);
+var<private> c : f32 = f32(123u);
+var<private> d : bool = bool(123);
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : i32 = 123;
+
+var<private> b : u32 = 123u;
+
+var<private> c : f32 = 123.0;
+
+var<private> d : bool = true;
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Scalar_MultipleConversions) {
+  auto* src = R"(
+var<private> a : i32 = i32(u32(f32(u32(i32(123.0)))));
+var<private> b : u32 = u32(i32(f32(i32(u32(123)))));
+var<private> c : f32 = f32(u32(i32(u32(f32(123u)))));
+var<private> d : bool = bool(i32(f32(i32(u32(123)))));
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : i32 = 123;
+
+var<private> b : u32 = 123u;
+
+var<private> c : f32 = 123.0;
+
+var<private> d : bool = true;
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Vector_NoConversion) {
+  auto* src = R"(
+var<private> a : vec3<i32> = vec3<i32>(123);
+var<private> b : vec3<u32> = vec3<u32>(123u);
+var<private> c : vec3<f32> = vec3<f32>(123.0);
+var<private> d : vec3<bool> = vec3<bool>(true);
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<i32> = vec3<i32>(123);
+
+var<private> b : vec3<u32> = vec3<u32>(123u);
+
+var<private> c : vec3<f32> = vec3<f32>(123.0);
+
+var<private> d : vec3<bool> = vec3<bool>(true);
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Vector_Conversion) {
+  auto* src = R"(
+var<private> a : vec3<i32> = vec3<i32>(vec3<f32>(123.0));
+var<private> b : vec3<u32> = vec3<u32>(vec3<i32>(123));
+var<private> c : vec3<f32> = vec3<f32>(vec3<u32>(123u));
+var<private> d : vec3<bool> = vec3<bool>(vec3<i32>(123));
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<i32> = vec3<i32>(123);
+
+var<private> b : vec3<u32> = vec3<u32>(123u);
+
+var<private> c : vec3<f32> = vec3<f32>(123.0);
+
+var<private> d : vec3<bool> = vec3<bool>(true);
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Vector_MultipleConversions) {
+  auto* src = R"(
+var<private> a : vec3<i32> = vec3<i32>(vec3<u32>(vec3<f32>(vec3<u32>(u32(123.0)))));
+var<private> b : vec3<u32> = vec3<u32>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
+var<private> c : vec3<f32> = vec3<f32>(vec3<u32>(vec3<i32>(vec3<u32>(u32(123u)))));
+var<private> d : vec3<bool> = vec3<bool>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<i32> = vec3<i32>(123);
+
+var<private> b : vec3<u32> = vec3<u32>(123u);
+
+var<private> c : vec3<f32> = vec3<f32>(123.0);
+
+var<private> d : vec3<bool> = vec3<bool>(true);
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Module_Vector_MixedSizeConversions) {
+  auto* src = R"(
+var<private> a : vec4<i32> = vec4<i32>(vec3<i32>(vec3<u32>(1u, 2u, 3u)), 4);
+var<private> b : vec4<i32> = vec4<i32>(vec2<i32>(vec2<u32>(1u, 2u)), vec2<i32>(4, 5));
+var<private> c : vec4<i32> = vec4<i32>(1, vec2<i32>(vec2<f32>(2.0, 3.0)), 4);
+var<private> d : vec4<i32> = vec4<i32>(1, 2, vec2<i32>(vec2<f32>(3.0, 4.0)));
+var<private> e : vec4<bool> = vec4<bool>(false, bool(f32(1.0)), vec2<bool>(vec2<i32>(0, i32(4u))));
+
+fn f() {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+
+var<private> b : vec4<i32> = vec4<i32>(1, 2, 4, 5);
+
+var<private> c : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+
+var<private> d : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+
+var<private> e : vec4<bool> = vec4<bool>(false, true, false, true);
+
+fn f() {
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Scalar_NoConversion) {
+  auto* src = R"(
+fn f() {
+  var a : i32 = i32(123);
+  var b : u32 = u32(123u);
+  var c : f32 = f32(123.0);
+  var d : bool = bool(true);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : i32 = 123;
+  var b : u32 = 123u;
+  var c : f32 = 123.0;
+  var d : bool = true;
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Scalar_Conversion) {
+  auto* src = R"(
+fn f() {
+  var a : i32 = i32(123.0);
+  var b : u32 = u32(123);
+  var c : f32 = f32(123u);
+  var d : bool = bool(123);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : i32 = 123;
+  var b : u32 = 123u;
+  var c : f32 = 123.0;
+  var d : bool = true;
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Scalar_MultipleConversions) {
+  auto* src = R"(
+fn f() {
+  var a : i32 = i32(u32(f32(u32(i32(123.0)))));
+  var b : u32 = u32(i32(f32(i32(u32(123)))));
+  var c : f32 = f32(u32(i32(u32(f32(123u)))));
+  var d : bool = bool(i32(f32(i32(u32(123)))));
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : i32 = 123;
+  var b : u32 = 123u;
+  var c : f32 = 123.0;
+  var d : bool = true;
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Vector_NoConversion) {
+  auto* src = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(123);
+  var b : vec3<u32> = vec3<u32>(123u);
+  var c : vec3<f32> = vec3<f32>(123.0);
+  var d : vec3<bool> = vec3<bool>(true);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(123);
+  var b : vec3<u32> = vec3<u32>(123u);
+  var c : vec3<f32> = vec3<f32>(123.0);
+  var d : vec3<bool> = vec3<bool>(true);
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Vector_Conversion) {
+  auto* src = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(vec3<f32>(123.0));
+  var b : vec3<u32> = vec3<u32>(vec3<i32>(123));
+  var c : vec3<f32> = vec3<f32>(vec3<u32>(123u));
+  var d : vec3<bool> = vec3<bool>(vec3<i32>(123));
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(123);
+  var b : vec3<u32> = vec3<u32>(123u);
+  var c : vec3<f32> = vec3<f32>(123.0);
+  var d : vec3<bool> = vec3<bool>(true);
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Vector_MultipleConversions) {
+  auto* src = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(vec3<u32>(vec3<f32>(vec3<u32>(u32(123.0)))));
+  var b : vec3<u32> = vec3<u32>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
+  var c : vec3<f32> = vec3<f32>(vec3<u32>(vec3<i32>(vec3<u32>(u32(123u)))));
+  var d : vec3<bool> = vec3<bool>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : vec3<i32> = vec3<i32>(123);
+  var b : vec3<u32> = vec3<u32>(123u);
+  var c : vec3<f32> = vec3<f32>(123.0);
+  var d : vec3<bool> = vec3<bool>(true);
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Vector_MixedSizeConversions) {
+  auto* src = R"(
+fn f() {
+  var a : vec4<i32> = vec4<i32>(vec3<i32>(vec3<u32>(1u, 2u, 3u)), 4);
+  var b : vec4<i32> = vec4<i32>(vec2<i32>(vec2<u32>(1u, 2u)), vec2<i32>(4, 5));
+  var c : vec4<i32> = vec4<i32>(1, vec2<i32>(vec2<f32>(2.0, 3.0)), 4);
+  var d : vec4<i32> = vec4<i32>(1, 2, vec2<i32>(vec2<f32>(3.0, 4.0)));
+  var e : vec4<bool> = vec4<bool>(false, bool(f32(1.0)), vec2<bool>(vec2<i32>(0, i32(4u))));
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+  var b : vec4<i32> = vec4<i32>(1, 2, 4, 5);
+  var c : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+  var d : vec4<i32> = vec4<i32>(1, 2, 3, 4);
+  var e : vec4<bool> = vec4<bool>(false, true, false, true);
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldConstantsTest, Function_Vector_ConstantWithNonConstant) {
+  auto* src = R"(
+fn f() {
+  var a : f32 = f32();
+  var b : vec2<f32> = vec2<f32>(f32(i32(1)), a);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : f32 = f32();
+  var b : vec2<f32> = vec2<f32>(1.0, a);
+}
+)";
+
+  auto got = Run<FoldConstants>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/fold_trivial_single_use_lets.cc b/src/tint/transform/fold_trivial_single_use_lets.cc
new file mode 100644
index 0000000..33dc55d
--- /dev/null
+++ b/src/tint/transform/fold_trivial_single_use_lets.cc
@@ -0,0 +1,92 @@
+// 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/transform/fold_trivial_single_use_lets.h"
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldTrivialSingleUseLets);
+
+namespace tint {
+namespace transform {
+namespace {
+
+const ast::VariableDeclStatement* AsTrivialLetDecl(const ast::Statement* stmt) {
+  auto* var_decl = stmt->As<ast::VariableDeclStatement>();
+  if (!var_decl) {
+    return nullptr;
+  }
+  auto* var = var_decl->variable;
+  if (!var->is_const) {
+    return nullptr;
+  }
+  auto* ctor = var->constructor;
+  if (!IsAnyOf<ast::IdentifierExpression, ast::LiteralExpression>(ctor)) {
+    return nullptr;
+  }
+  return var_decl;
+}
+
+}  // namespace
+
+FoldTrivialSingleUseLets::FoldTrivialSingleUseLets() = default;
+
+FoldTrivialSingleUseLets::~FoldTrivialSingleUseLets() = default;
+
+void FoldTrivialSingleUseLets::Run(CloneContext& ctx,
+                                   const DataMap&,
+                                   DataMap&) const {
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* block = node->As<ast::BlockStatement>()) {
+      auto& stmts = block->statements;
+      for (size_t stmt_idx = 0; stmt_idx < stmts.size(); stmt_idx++) {
+        auto* stmt = stmts[stmt_idx];
+        if (auto* let_decl = AsTrivialLetDecl(stmt)) {
+          auto* let = let_decl->variable;
+          auto* sem_let = ctx.src->Sem().Get(let);
+          auto& users = sem_let->Users();
+          if (users.size() != 1) {
+            continue;  // Does not have a single user.
+          }
+
+          auto* user = users[0];
+          auto* user_stmt = user->Stmt()->Declaration();
+
+          for (size_t i = stmt_idx; i < stmts.size(); i++) {
+            if (user_stmt == stmts[i]) {
+              auto* user_expr = user->Declaration();
+              ctx.Remove(stmts, let_decl);
+              ctx.Replace(user_expr, ctx.Clone(let->constructor));
+            }
+            if (!AsTrivialLetDecl(stmts[i])) {
+              // Stop if we hit a statement that isn't the single use of the
+              // let, and isn't a let itself.
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/fold_trivial_single_use_lets.h b/src/tint/transform/fold_trivial_single_use_lets.h
new file mode 100644
index 0000000..780fcee
--- /dev/null
+++ b/src/tint/transform/fold_trivial_single_use_lets.h
@@ -0,0 +1,61 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
+#define SRC_TINT_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// FoldTrivialSingleUseLets is an optimizer for folding away trivial `let`
+/// statements into their single place of use. This transform is intended to
+/// clean up the SSA `let`s produced by the SPIR-V reader.
+/// `let`s can only be folded if:
+/// * There is a single usage of the `let` value.
+/// * The `let` is constructed with a ScalarConstructorExpression, or with an
+///   IdentifierExpression.
+/// * There are only other foldable `let`s between the `let` declaration and its
+///   single usage.
+/// These rules prevent any hoisting of the let that may affect execution
+/// behaviour.
+class FoldTrivialSingleUseLets
+    : public Castable<FoldTrivialSingleUseLets, Transform> {
+ public:
+  /// Constructor
+  FoldTrivialSingleUseLets();
+
+  /// Destructor
+  ~FoldTrivialSingleUseLets() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
diff --git a/src/tint/transform/fold_trivial_single_use_lets_test.cc b/src/tint/transform/fold_trivial_single_use_lets_test.cc
new file mode 100644
index 0000000..09939e1
--- /dev/null
+++ b/src/tint/transform/fold_trivial_single_use_lets_test.cc
@@ -0,0 +1,188 @@
+// 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/transform/fold_trivial_single_use_lets.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using FoldTrivialSingleUseLetsTest = TransformTest;
+
+TEST_F(FoldTrivialSingleUseLetsTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Single) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  _ = x;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  _ = 1;
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Multiple) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  let y = 2;
+  let z = 3;
+  _ = x + y + z;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  _ = ((1 + 2) + 3);
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Chained) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  let y = x;
+  let z = y;
+  _ = z;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  _ = 1;
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_NonTrivialLet) {
+  auto* src = R"(
+fn function_with_posssible_side_effect() -> i32 {
+  return 1;
+}
+
+fn f() {
+  let x = 1;
+  let y = function_with_posssible_side_effect();
+  _ = (x + y);
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_NonTrivialLet_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  let y = function_with_posssible_side_effect();
+  _ = (x + y);
+}
+
+fn function_with_posssible_side_effect() -> i32 {
+  return 1;
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_UseInSubBlock) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  {
+    _ = x;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_MultipleUses) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  _ = (x + x);
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_Shadowing) {
+  auto* src = R"(
+fn f() {
+  var y = 1;
+  let x = y;
+  {
+    let y = false;
+    _ = (x + x);
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/for_loop_to_loop.cc b/src/tint/transform/for_loop_to_loop.cc
new file mode 100644
index 0000000..f7fda57
--- /dev/null
+++ b/src/tint/transform/for_loop_to_loop.cc
@@ -0,0 +1,76 @@
+// 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/transform/for_loop_to_loop.h"
+
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ForLoopToLoop);
+
+namespace tint {
+namespace transform {
+ForLoopToLoop::ForLoopToLoop() = default;
+
+ForLoopToLoop::~ForLoopToLoop() = default;
+
+bool ForLoopToLoop::ShouldRun(const Program* program, const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (node->Is<ast::ForLoopStatement>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void ForLoopToLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  ctx.ReplaceAll(
+      [&](const ast::ForLoopStatement* for_loop) -> const ast::Statement* {
+        ast::StatementList stmts;
+        if (auto* cond = for_loop->condition) {
+          // !condition
+          auto* not_cond = ctx.dst->create<ast::UnaryOpExpression>(
+              ast::UnaryOp::kNot, ctx.Clone(cond));
+
+          // { break; }
+          auto* break_body =
+              ctx.dst->Block(ctx.dst->create<ast::BreakStatement>());
+
+          // if (!condition) { break; }
+          stmts.emplace_back(ctx.dst->If(not_cond, break_body));
+        }
+        for (auto* stmt : for_loop->body->statements) {
+          stmts.emplace_back(ctx.Clone(stmt));
+        }
+
+        const ast::BlockStatement* continuing = nullptr;
+        if (auto* cont = for_loop->continuing) {
+          continuing = ctx.dst->Block(ctx.Clone(cont));
+        }
+
+        auto* body = ctx.dst->Block(stmts);
+        auto* loop = ctx.dst->create<ast::LoopStatement>(body, continuing);
+
+        if (auto* init = for_loop->initializer) {
+          return ctx.dst->Block(ctx.Clone(init), loop);
+        }
+
+        return loop;
+      });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/for_loop_to_loop.h b/src/tint/transform/for_loop_to_loop.h
new file mode 100644
index 0000000..c400448
--- /dev/null
+++ b/src/tint/transform/for_loop_to_loop.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_FOR_LOOP_TO_LOOP_H_
+#define SRC_TINT_TRANSFORM_FOR_LOOP_TO_LOOP_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// ForLoopToLoop is a Transform that converts a for-loop statement into a loop
+/// statement. This is required by the SPIR-V writer.
+class ForLoopToLoop : public Castable<ForLoopToLoop, Transform> {
+ public:
+  /// Constructor
+  ForLoopToLoop();
+
+  /// Destructor
+  ~ForLoopToLoop() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_FOR_LOOP_TO_LOOP_H_
diff --git a/src/tint/transform/for_loop_to_loop_test.cc b/src/tint/transform/for_loop_to_loop_test.cc
new file mode 100644
index 0000000..07fe2a9
--- /dev/null
+++ b/src/tint/transform/for_loop_to_loop_test.cc
@@ -0,0 +1,374 @@
+// 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/transform/for_loop_to_loop.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ForLoopToLoopTest = TransformTest;
+
+TEST_F(ForLoopToLoopTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<ForLoopToLoop>(src));
+}
+
+TEST_F(ForLoopToLoopTest, ShouldRunHasForLoop) {
+  auto* src = R"(
+fn f() {
+  for (;;) {
+    break;
+  }
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<ForLoopToLoop>(src));
+}
+
+TEST_F(ForLoopToLoopTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = src;
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test an empty for loop.
+TEST_F(ForLoopToLoopTest, Empty) {
+  auto* src = R"(
+fn f() {
+  for (;;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    break;
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with non-empty body.
+TEST_F(ForLoopToLoopTest, Body) {
+  auto* src = R"(
+fn f() {
+  for (;;) {
+    discard;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    discard;
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring a variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementDecl) {
+  auto* src = R"(
+fn f() {
+  for (var i: i32;;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    var i : i32;
+    loop {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring and initializing a variable in the initializer
+// statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementDeclEqual) {
+  auto* src = R"(
+fn f() {
+  for (var i: i32 = 0;;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    var i : i32 = 0;
+    loop {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring a const variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementConstDecl) {
+  auto* src = R"(
+fn f() {
+  for (let i: i32 = 0;;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    let i : i32 = 0;
+    loop {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop assigning a variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementAssignment) {
+  auto* src = R"(
+fn f() {
+  var i: i32;
+  for (i = 0;;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  {
+    i = 0;
+    loop {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop calling a function in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementFuncCall) {
+  auto* src = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  for (a(b,c);;) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  {
+    a(b, c);
+    loop {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with a break condition
+TEST_F(ForLoopToLoopTest, BreakCondition) {
+  auto* src = R"(
+fn f() {
+  for (; 0 == 1;) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    if (!((0 == 1))) {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop assigning a variable in the continuing statement.
+TEST_F(ForLoopToLoopTest, ContinuingAssignment) {
+  auto* src = R"(
+fn f() {
+  var x: i32;
+  for (;;x = 2) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var x : i32;
+  loop {
+    break;
+
+    continuing {
+      x = 2;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop calling a function in the continuing statement.
+TEST_F(ForLoopToLoopTest, ContinuingFuncCall) {
+  auto* src = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  for (;;a(b,c)) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  loop {
+    break;
+
+    continuing {
+      a(b, c);
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with all statements non-empty.
+TEST_F(ForLoopToLoopTest, All) {
+  auto* src = R"(
+fn f() {
+  var a : i32;
+  for(var i : i32 = 0; i < 4; i = i + 1) {
+    if (a == 0) {
+      continue;
+    }
+    a = a + 2;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : i32;
+  {
+    var i : i32 = 0;
+    loop {
+      if (!((i < 4))) {
+        break;
+      }
+      if ((a == 0)) {
+        continue;
+      }
+      a = (a + 2);
+
+      continuing {
+        i = (i + 1);
+      }
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/glsl.cc b/src/tint/transform/glsl.cc
new file mode 100644
index 0000000..0086828
--- /dev/null
+++ b/src/tint/transform/glsl.cc
@@ -0,0 +1,118 @@
+// 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/transform/glsl.h"
+
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/transform/add_empty_entry_point.h"
+#include "src/tint/transform/add_spirv_block_attribute.h"
+#include "src/tint/transform/binding_remapper.h"
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/combine_samplers.h"
+#include "src/tint/transform/decompose_memory_access.h"
+#include "src/tint/transform/external_texture_transform.h"
+#include "src/tint/transform/fold_trivial_single_use_lets.h"
+#include "src/tint/transform/loop_to_for_loop.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/pad_array_elements.h"
+#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/remove_phonies.h"
+#include "src/tint/transform/renamer.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/single_entry_point.h"
+#include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/zero_init_workgroup_memory.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl::Config);
+
+namespace tint {
+namespace transform {
+
+Glsl::Glsl() = default;
+Glsl::~Glsl() = default;
+
+Output Glsl::Run(const Program* in, const DataMap& inputs) const {
+  Manager manager;
+  DataMap data;
+
+  auto* cfg = inputs.Get<Config>();
+
+  if (cfg && !cfg->entry_point.empty()) {
+    manager.Add<SingleEntryPoint>();
+    data.Add<SingleEntryPoint::Config>(cfg->entry_point);
+  }
+  manager.Add<Renamer>();
+  data.Add<Renamer::Config>(Renamer::Target::kGlslKeywords,
+                            /* preserve_unicode */ false);
+  manager.Add<Unshadow>();
+
+  // Attempt to convert `loop`s into for-loops. This is to try and massage the
+  // output into something that will not cause FXC to choke or misbehave.
+  manager.Add<FoldTrivialSingleUseLets>();
+  manager.Add<LoopToForLoop>();
+
+  if (!cfg || !cfg->disable_workgroup_init) {
+    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
+    // ZeroInitWorkgroupMemory may inject new builtin parameters.
+    manager.Add<ZeroInitWorkgroupMemory>();
+  }
+  manager.Add<CanonicalizeEntryPointIO>();
+  manager.Add<SimplifyPointers>();
+
+  manager.Add<RemovePhonies>();
+  manager.Add<CombineSamplers>();
+  if (auto* binding_info = inputs.Get<CombineSamplers::BindingInfo>()) {
+    data.Add<CombineSamplers::BindingInfo>(*binding_info);
+  } else {
+    data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
+                                           sem::BindingPoint());
+  }
+  manager.Add<BindingRemapper>();
+  if (auto* remappings = inputs.Get<BindingRemapper::Remappings>()) {
+    data.Add<BindingRemapper::Remappings>(*remappings);
+  } else {
+    BindingRemapper::BindingPoints bp;
+    BindingRemapper::AccessControls ac;
+    data.Add<BindingRemapper::Remappings>(bp, ac, /* mayCollide */ true);
+  }
+  manager.Add<ExternalTextureTransform>();
+  manager.Add<PromoteInitializersToConstVar>();
+
+  manager.Add<PadArrayElements>();
+  manager.Add<AddEmptyEntryPoint>();
+  manager.Add<AddSpirvBlockAttribute>();
+
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
+  auto out = manager.Run(in, data);
+  if (!out.program.IsValid()) {
+    return out;
+  }
+
+  ProgramBuilder builder;
+  CloneContext ctx(&builder, &out.program);
+  ctx.Clone();
+  return Output{Program(std::move(builder))};
+}
+
+Glsl::Config::Config(const std::string& ep, bool disable_wi)
+    : entry_point(ep), disable_workgroup_init(disable_wi) {}
+Glsl::Config::Config(const Config&) = default;
+Glsl::Config::~Config() = default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/glsl.h b/src/tint/transform/glsl.h
new file mode 100644
index 0000000..b637056
--- /dev/null
+++ b/src/tint/transform/glsl.h
@@ -0,0 +1,70 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_GLSL_H_
+#define SRC_TINT_TRANSFORM_GLSL_H_
+
+#include <string>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace transform {
+
+/// Glsl is a transform used to sanitize a Program for use with the Glsl writer.
+/// Passing a non-sanitized Program to the Glsl writer will result in undefined
+/// behavior.
+class Glsl : public Castable<Glsl, Transform> {
+ public:
+  /// Configuration options for the Glsl sanitizer transform.
+  struct Config : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param entry_point the root entry point function to generate
+    /// @param disable_workgroup_init `true` to disable workgroup memory zero
+    ///        initialization
+    explicit Config(const std::string& entry_point,
+                    bool disable_workgroup_init = false);
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// GLSL generator wraps a single entry point in a main() function.
+    std::string entry_point;
+
+    /// Set to `true` to disable workgroup memory zero initialization
+    bool disable_workgroup_init = false;
+  };
+
+  /// Constructor
+  Glsl();
+  ~Glsl() override;
+
+  /// Runs the transform on `program`, returning the transformation result.
+  /// @param program the source program to transform
+  /// @param data optional extra transform-specific data
+  /// @returns the transformation result
+  Output Run(const Program* program, const DataMap& data = {}) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_GLSL_H_
diff --git a/src/tint/transform/glsl_test.cc b/src/tint/transform/glsl_test.cc
new file mode 100644
index 0000000..5a2d872
--- /dev/null
+++ b/src/tint/transform/glsl_test.cc
@@ -0,0 +1,41 @@
+// 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/transform/glsl.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using GlslTest = TransformTest;
+
+TEST_F(GlslTest, AddEmptyEntryPoint) {
+  auto* src = R"()";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn unused_entry_point() {
+}
+)";
+
+  auto got = Run<Glsl>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
new file mode 100644
index 0000000..ff2289b
--- /dev/null
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -0,0 +1,224 @@
+// 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/transform/localize_struct_array_assignment.h"
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::LocalizeStructArrayAssignment);
+
+namespace tint {
+namespace transform {
+
+/// Private implementation of LocalizeStructArrayAssignment transform
+class LocalizeStructArrayAssignment::State {
+ private:
+  CloneContext& ctx;
+  ProgramBuilder& b;
+
+  /// Returns true if `expr` contains an index accessor expression to a
+  /// structure member of array type.
+  bool ContainsStructArrayIndex(const ast::Expression* expr) {
+    bool result = false;
+    ast::TraverseExpressions(
+        expr, b.Diagnostics(), [&](const ast::IndexAccessorExpression* ia) {
+          // Indexing using a runtime value?
+          auto* idx_sem = ctx.src->Sem().Get(ia->index);
+          if (!idx_sem->ConstantValue().IsValid()) {
+            // Indexing a member access expr?
+            if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
+              // That accesses an array?
+              if (ctx.src->TypeOf(ma)->UnwrapRef()->Is<sem::Array>()) {
+                result = true;
+                return ast::TraverseAction::Stop;
+              }
+            }
+          }
+          return ast::TraverseAction::Descend;
+        });
+
+    return result;
+  }
+
+  // Returns the type and storage class of the originating variable of the lhs
+  // of the assignment statement.
+  // See https://www.w3.org/TR/WGSL/#originating-variable-section
+  std::pair<const sem::Type*, ast::StorageClass>
+  GetOriginatingTypeAndStorageClass(
+      const ast::AssignmentStatement* assign_stmt) {
+    // Get first IdentifierExpr from lhs of assignment, which should resolve to
+    // the pointer or reference of the originating variable of the assignment.
+    // TraverseExpressions traverses left to right, and this code depends on the
+    // fact that for an assignment statement, the variable will be the left-most
+    // expression.
+    // TODO(crbug.com/tint/1341): do this in the Resolver, setting the
+    // originating variable on sem::Expression.
+    const ast::IdentifierExpression* ident = nullptr;
+    ast::TraverseExpressions(assign_stmt->lhs, b.Diagnostics(),
+                             [&](const ast::IdentifierExpression* id) {
+                               ident = id;
+                               return ast::TraverseAction::Stop;
+                             });
+    auto* sem_var_user = ctx.src->Sem().Get<sem::VariableUser>(ident);
+    if (!sem_var_user) {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "Expected to find variable of lhs of assignment statement";
+      return {};
+    }
+
+    auto* var = sem_var_user->Variable();
+    if (auto* ptr = var->Type()->As<sem::Pointer>()) {
+      return {ptr->StoreType(), ptr->StorageClass()};
+    }
+
+    auto* ref = var->Type()->As<sem::Reference>();
+    if (!ref) {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "Expecting to find variable of type pointer or reference on lhs "
+             "of assignment statement";
+      return {};
+    }
+
+    return {ref->StoreType(), ref->StorageClass()};
+  }
+
+ public:
+  /// Constructor
+  /// @param ctx_in the CloneContext primed with the input program and
+  /// ProgramBuilder
+  explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
+
+  /// Runs the transform
+  void Run() {
+    struct Shared {
+      bool process_nested_nodes = false;
+      ast::StatementList insert_before_stmts;
+      ast::StatementList insert_after_stmts;
+    } s;
+
+    ctx.ReplaceAll([&](const ast::AssignmentStatement* assign_stmt)
+                       -> const ast::Statement* {
+      // Process if it's an assignment statement to a dynamically indexed array
+      // within a struct on a function or private storage variable. This
+      // specific use-case is what FXC fails to compile with:
+      // error X3500: array reference cannot be used as an l-value; not natively
+      // addressable
+      if (!ContainsStructArrayIndex(assign_stmt->lhs)) {
+        return nullptr;
+      }
+      auto og = GetOriginatingTypeAndStorageClass(assign_stmt);
+      if (!(og.first->Is<sem::Struct>() &&
+            (og.second == ast::StorageClass::kFunction ||
+             og.second == ast::StorageClass::kPrivate))) {
+        return nullptr;
+      }
+
+      // Reset shared state for this assignment statement
+      s = Shared{};
+
+      const ast::Expression* new_lhs = nullptr;
+      {
+        TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, true);
+        new_lhs = ctx.Clone(assign_stmt->lhs);
+      }
+
+      auto* new_assign_stmt = b.Assign(new_lhs, ctx.Clone(assign_stmt->rhs));
+
+      // Combine insert_before_stmts + new_assign_stmt + insert_after_stmts into
+      // a block and return it
+      ast::StatementList stmts = std::move(s.insert_before_stmts);
+      stmts.reserve(1 + s.insert_after_stmts.size());
+      stmts.emplace_back(new_assign_stmt);
+      stmts.insert(stmts.end(), s.insert_after_stmts.begin(),
+                   s.insert_after_stmts.end());
+
+      return b.Block(std::move(stmts));
+    });
+
+    ctx.ReplaceAll([&](const ast::IndexAccessorExpression* index_access)
+                       -> const ast::Expression* {
+      if (!s.process_nested_nodes) {
+        return nullptr;
+      }
+
+      // Indexing a member access expr?
+      auto* mem_access =
+          index_access->object->As<ast::MemberAccessorExpression>();
+      if (!mem_access) {
+        return nullptr;
+      }
+
+      // Process any nested IndexAccessorExpressions
+      mem_access = ctx.Clone(mem_access);
+
+      // Store the address of the member access into a let as we need to read
+      // the value twice e.g. let tint_symbol = &(s.a1);
+      auto mem_access_ptr = b.Sym();
+      s.insert_before_stmts.push_back(
+          b.Decl(b.Const(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
+
+      // Disable further transforms when cloning
+      TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, false);
+
+      // Copy entire array out of struct into local temp var
+      // e.g. var tint_symbol_1 = *(tint_symbol);
+      auto tmp_var = b.Sym();
+      s.insert_before_stmts.push_back(
+          b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
+
+      // Replace input index_access with a clone of itself, but with its
+      // .object replaced by the new temp var. This is returned from this
+      // function to modify the original assignment statement. e.g.
+      // tint_symbol_1[uniforms.i]
+      auto* new_index_access =
+          b.IndexAccessor(tmp_var, ctx.Clone(index_access->index));
+
+      // Assign temp var back to array
+      // e.g. *(tint_symbol) = tint_symbol_1;
+      auto* assign_rhs_to_temp = b.Assign(b.Deref(mem_access_ptr), tmp_var);
+      s.insert_after_stmts.insert(s.insert_after_stmts.begin(),
+                                  assign_rhs_to_temp);  // push_front
+
+      return new_index_access;
+    });
+
+    ctx.Clone();
+  }
+};
+
+LocalizeStructArrayAssignment::LocalizeStructArrayAssignment() = default;
+
+LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
+
+void LocalizeStructArrayAssignment::Run(CloneContext& ctx,
+                                        const DataMap&,
+                                        DataMap&) const {
+  State state(ctx);
+  state.Run();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/localize_struct_array_assignment.h b/src/tint/transform/localize_struct_array_assignment.h
new file mode 100644
index 0000000..ec5faa6
--- /dev/null
+++ b/src/tint/transform/localize_struct_array_assignment.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
+#define SRC_TINT_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// This transforms replaces assignment to dynamically-indexed fixed-size arrays
+/// in structs on shader-local variables with code that copies the arrays to a
+/// temporary local variable, assigns to the local variable, and copies the
+/// array back. This is to work around FXC's compilation failure for these cases
+/// (see crbug.com/tint/1206).
+///
+/// @note Depends on the following transforms to have been run first:
+/// * SimplifyPointers
+class LocalizeStructArrayAssignment
+    : public Castable<LocalizeStructArrayAssignment, Transform> {
+ public:
+  /// Constructor
+  LocalizeStructArrayAssignment();
+
+  /// Destructor
+  ~LocalizeStructArrayAssignment() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  class State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
diff --git a/src/tint/transform/localize_struct_array_assignment_test.cc b/src/tint/transform/localize_struct_array_assignment_test.cc
new file mode 100644
index 0000000..349858d
--- /dev/null
+++ b/src/tint/transform/localize_struct_array_assignment_test.cc
@@ -0,0 +1,884 @@
+// 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/transform/localize_struct_array_assignment.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/unshadow.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using LocalizeStructArrayAssignmentTest = TransformTest;
+
+TEST_F(LocalizeStructArrayAssignmentTest, EmptyModule) {
+  auto* src = R"()";
+  auto* expect = src;
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArray) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.a1[uniforms.i] = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArray_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.a1[uniforms.i] = v;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+@block struct Uniforms {
+  i : u32;
+};
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+@block
+struct Uniforms {
+  i : u32;
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructStructArray) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct S1 {
+  a : array<InnerS, 8>;
+};
+
+struct OuterS {
+  s2 : S1;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.s2.a[uniforms.i] = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct S1 {
+  a : array<InnerS, 8>;
+}
+
+struct OuterS {
+  s2 : S1;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.s2.a);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructStructArray_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.s2.a[uniforms.i] = v;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+struct OuterS {
+  s2 : S1;
+};
+
+struct S1 {
+  a : array<InnerS, 8>;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+@block struct Uniforms {
+  i : u32;
+};
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.s2.a);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+struct OuterS {
+  s2 : S1;
+}
+
+struct S1 {
+  a : array<InnerS, 8>;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+@block
+struct Uniforms {
+  i : u32;
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayArray) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+  j : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct OuterS {
+  a1 : array<array<InnerS, 8>, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.a1[uniforms.i][uniforms.j] = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+  j : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct OuterS {
+  a1 : array<array<InnerS, 8>, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i][uniforms.j] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStruct) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct S1 {
+  s2 : InnerS;
+};
+
+struct OuterS {
+  a1 : array<S1, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  s1.a1[uniforms.i].s2 = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct S1 {
+  s2 : InnerS;
+}
+
+struct OuterS {
+  a1 : array<S1, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  {
+    let tint_symbol = &(s1.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i].s2 = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStructArray) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+  j : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+  a1 : array<S1, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  s.a1[uniforms.i].a2[uniforms.j] = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+  j : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+}
+
+struct OuterS {
+  a1 : array<S1, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  {
+    let tint_symbol = &(s.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    let tint_symbol_2 = &(tint_symbol_1[uniforms.i].a2);
+    var tint_symbol_3 = *(tint_symbol_2);
+    tint_symbol_3[uniforms.j] = v;
+    *(tint_symbol_2) = tint_symbol_3;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, IndexingWithSideEffectFunc) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+  j : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+  a1 : array<S1, 8>;
+};
+
+var<private> nextIndex : u32;
+fn getNextIndex() -> u32 {
+  nextIndex = nextIndex + 1u;
+  return nextIndex;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  s.a1[getNextIndex()].a2[uniforms.j] = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+  j : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+}
+
+struct OuterS {
+  a1 : array<S1, 8>;
+}
+
+var<private> nextIndex : u32;
+
+fn getNextIndex() -> u32 {
+  nextIndex = (nextIndex + 1u);
+  return nextIndex;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  {
+    let tint_symbol = &(s.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    let tint_symbol_2 = &(tint_symbol_1[getNextIndex()].a2);
+    var tint_symbol_3 = *(tint_symbol_2);
+    tint_symbol_3[uniforms.j] = v;
+    *(tint_symbol_2) = tint_symbol_3;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest,
+       IndexingWithSideEffectFunc_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  s.a1[getNextIndex()].a2[uniforms.j] = v;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@block struct Uniforms {
+  i : u32;
+  j : u32;
+};
+
+var<private> nextIndex : u32;
+fn getNextIndex() -> u32 {
+  nextIndex = nextIndex + 1u;
+  return nextIndex;
+}
+
+struct OuterS {
+  a1 : array<S1, 8>;
+};
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+};
+
+struct InnerS {
+  v : i32;
+};
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s : OuterS;
+  {
+    let tint_symbol = &(s.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    let tint_symbol_2 = &(tint_symbol_1[getNextIndex()].a2);
+    var tint_symbol_3 = *(tint_symbol_2);
+    tint_symbol_3[uniforms.j] = v;
+    *(tint_symbol_2) = tint_symbol_3;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@block
+struct Uniforms {
+  i : u32;
+  j : u32;
+}
+
+var<private> nextIndex : u32;
+
+fn getNextIndex() -> u32 {
+  nextIndex = (nextIndex + 1u);
+  return nextIndex;
+}
+
+struct OuterS {
+  a1 : array<S1, 8>;
+}
+
+struct S1 {
+  a2 : array<InnerS, 8>;
+}
+
+struct InnerS {
+  v : i32;
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg) {
+  auto* src = R"(
+@block struct Uniforms {
+  i : u32;
+};
+struct InnerS {
+  v : i32;
+};
+struct OuterS {
+  a1 : array<InnerS, 8>;
+};
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+  var v : InnerS;
+  (*p).a1[uniforms.i] = v;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var s1 : OuterS;
+  f(&s1);
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+  var v : InnerS;
+  {
+    let tint_symbol = &((*(p)).a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var s1 : OuterS;
+  f(&(s1));
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var s1 : OuterS;
+  f(&s1);
+}
+
+fn f(p : ptr<function, OuterS>) {
+  var v : InnerS;
+  (*p).a1[uniforms.i] = v;
+}
+
+struct InnerS {
+  v : i32;
+};
+struct OuterS {
+  a1 : array<InnerS, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@block struct Uniforms {
+  i : u32;
+};
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var s1 : OuterS;
+  f(&(s1));
+}
+
+fn f(p : ptr<function, OuterS>) {
+  var v : InnerS;
+  {
+    let tint_symbol = &((*(p)).a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[uniforms.i] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@block
+struct Uniforms {
+  i : u32;
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerVar) {
+  auto* src = R"(
+@block
+struct Uniforms {
+  i : u32;
+};
+
+struct InnerS {
+  v : i32;
+};
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, InnerS>, v : InnerS) {
+  *(p) = v;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  let p = &(s1.a1[uniforms.i]);
+  *(p) = v;
+}
+)";
+
+  auto* expect = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+struct InnerS {
+  v : i32;
+}
+
+struct OuterS {
+  a1 : array<InnerS, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, InnerS>, v : InnerS) {
+  *(p) = v;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var v : InnerS;
+  var s1 : OuterS;
+  let p_save = uniforms.i;
+  {
+    let tint_symbol = &(s1.a1);
+    var tint_symbol_1 = *(tint_symbol);
+    tint_symbol_1[p_save] = v;
+    *(tint_symbol) = tint_symbol_1;
+  }
+}
+)";
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, VectorAssignment) {
+  auto* src = R"(
+@block
+struct Uniforms {
+  i : u32;
+}
+
+@block
+struct OuterS {
+  a1 : array<u32, 8>;
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(i : u32) -> u32 {
+  return (i + 1u);
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var s1 : OuterS;
+  var v : vec3<f32>;
+  v[s1.a1[uniforms.i]] = 1.0;
+  v[f(s1.a1[uniforms.i])] = 1.0;
+}
+)";
+
+  // Transform does nothing here as we're not actually assigning to the array in
+  // the struct.
+  auto* expect = src;
+
+  auto got =
+      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/loop_to_for_loop.cc b/src/tint/transform/loop_to_for_loop.cc
new file mode 100644
index 0000000..ac205c0
--- /dev/null
+++ b/src/tint/transform/loop_to_for_loop.cc
@@ -0,0 +1,145 @@
+// 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/transform/loop_to_for_loop.h"
+
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::LoopToForLoop);
+
+namespace tint {
+namespace transform {
+namespace {
+
+bool IsBlockWithSingleBreak(const ast::BlockStatement* block) {
+  if (block->statements.size() != 1) {
+    return false;
+  }
+  return block->statements[0]->Is<ast::BreakStatement>();
+}
+
+bool IsVarUsedByStmt(const sem::Info& sem,
+                     const ast::Variable* var,
+                     const ast::Statement* stmt) {
+  auto* var_sem = sem.Get(var);
+  for (auto* user : var_sem->Users()) {
+    if (auto* s = user->Stmt()) {
+      if (s->Declaration() == stmt) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
+LoopToForLoop::LoopToForLoop() = default;
+
+LoopToForLoop::~LoopToForLoop() = default;
+
+bool LoopToForLoop::ShouldRun(const Program* program, const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (node->Is<ast::LoopStatement>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void LoopToForLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  ctx.ReplaceAll([&](const ast::LoopStatement* loop) -> const ast::Statement* {
+    // For loop condition is taken from the first statement in the loop.
+    // This requires an if-statement with either:
+    //  * A true block with no else statements, and the true block contains a
+    //    single 'break' statement.
+    //  * An empty true block with a single, no-condition else statement
+    //    containing a single 'break' statement.
+    // Examples:
+    //   loop {  if (condition) { break; } ... }
+    //   loop {  if (condition) {} else { break; } ... }
+    auto& stmts = loop->body->statements;
+    if (stmts.empty()) {
+      return nullptr;
+    }
+    auto* if_stmt = stmts[0]->As<ast::IfStatement>();
+    if (!if_stmt) {
+      return nullptr;
+    }
+
+    bool negate_condition = false;
+    if (IsBlockWithSingleBreak(if_stmt->body) &&
+        if_stmt->else_statements.empty()) {
+      negate_condition = true;
+    } else if (if_stmt->body->Empty() && if_stmt->else_statements.size() == 1 &&
+               if_stmt->else_statements[0]->condition == nullptr &&
+               IsBlockWithSingleBreak(if_stmt->else_statements[0]->body)) {
+      negate_condition = false;
+    } else {
+      return nullptr;
+    }
+
+    // The continuing block must be empty or contain a single, assignment or
+    // function call statement.
+    const ast::Statement* continuing = nullptr;
+    if (auto* loop_cont = loop->continuing) {
+      if (loop_cont->statements.size() != 1) {
+        return nullptr;
+      }
+
+      continuing = loop_cont->statements[0];
+      if (!continuing
+               ->IsAnyOf<ast::AssignmentStatement, ast::CallStatement>()) {
+        return nullptr;
+      }
+
+      // And the continuing statement must not use any of the variables declared
+      // in the loop body.
+      for (auto* stmt : loop->body->statements) {
+        if (auto* var_decl = stmt->As<ast::VariableDeclStatement>()) {
+          if (IsVarUsedByStmt(ctx.src->Sem(), var_decl->variable, continuing)) {
+            return nullptr;
+          }
+        }
+      }
+
+      continuing = ctx.Clone(continuing);
+    }
+
+    auto* condition = ctx.Clone(if_stmt->condition);
+    if (negate_condition) {
+      condition = ctx.dst->create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
+                                                          condition);
+    }
+
+    ast::Statement* initializer = nullptr;
+
+    ctx.Remove(loop->body->statements, if_stmt);
+    auto* body = ctx.Clone(loop->body);
+    return ctx.dst->create<ast::ForLoopStatement>(initializer, condition,
+                                                  continuing, body);
+  });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/loop_to_for_loop.h b/src/tint/transform/loop_to_for_loop.h
new file mode 100644
index 0000000..94e09f6
--- /dev/null
+++ b/src/tint/transform/loop_to_for_loop.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_LOOP_TO_FOR_LOOP_H_
+#define SRC_TINT_TRANSFORM_LOOP_TO_FOR_LOOP_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// LoopToForLoop is a Transform that attempts to convert WGSL `loop {}`
+/// statements into a for-loop statement.
+class LoopToForLoop : public Castable<LoopToForLoop, Transform> {
+ public:
+  /// Constructor
+  LoopToForLoop();
+
+  /// Destructor
+  ~LoopToForLoop() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_LOOP_TO_FOR_LOOP_H_
diff --git a/src/tint/transform/loop_to_for_loop_test.cc b/src/tint/transform/loop_to_for_loop_test.cc
new file mode 100644
index 0000000..df51e10
--- /dev/null
+++ b/src/tint/transform/loop_to_for_loop_test.cc
@@ -0,0 +1,308 @@
+// 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/transform/loop_to_for_loop.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using LoopToForLoopTest = TransformTest;
+
+TEST_F(LoopToForLoopTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<LoopToForLoop>(src));
+}
+
+TEST_F(LoopToForLoopTest, ShouldRunHasForLoop) {
+  auto* src = R"(
+fn f() {
+  loop {
+    break;
+  }
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<LoopToForLoop>(src));
+}
+
+TEST_F(LoopToForLoopTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, IfBreak) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if (i > 15) {
+      break;
+    }
+
+    _ = 123;
+
+    continuing {
+      i = i + 1;
+    }
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  for(; !((i > 15)); i = (i + 1)) {
+    _ = 123;
+  }
+}
+)";
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, IfElseBreak) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if (i < 15) {
+    } else {
+      break;
+    }
+
+    _ = 123;
+
+    continuing {
+      i = i + 1;
+    }
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  for(; (i < 15); i = (i + 1)) {
+    _ = 123;
+  }
+}
+)";
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, Nested) {
+  auto* src = R"(
+let N = 16u;
+
+fn f() {
+  var i : u32 = 0u;
+  loop {
+    if (i >= N) {
+      break;
+    }
+    {
+      var j : u32 = 0u;
+      loop {
+        if (j >= N) {
+          break;
+        }
+
+        _ = i;
+        _ = j;
+
+        continuing {
+          j = (j + 1u);
+        }
+      }
+    }
+
+    continuing {
+      i = (i + 1u);
+    }
+  }
+}
+)";
+
+  auto* expect = R"(
+let N = 16u;
+
+fn f() {
+  var i : u32 = 0u;
+  for(; !((i >= N)); i = (i + 1u)) {
+    {
+      var j : u32 = 0u;
+      for(; !((j >= N)); j = (j + 1u)) {
+        _ = i;
+        _ = j;
+      }
+    }
+  }
+}
+)";
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, NoTransform_IfMultipleStmts) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if ((i < 15)) {
+      _ = i;
+      break;
+    }
+    _ = 123;
+
+    continuing {
+      i = (i + 1);
+    }
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, NoTransform_IfElseMultipleStmts) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if ((i < 15)) {
+    } else {
+      _ = i;
+      break;
+    }
+    _ = 123;
+
+    continuing {
+      i = (i + 1);
+    }
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, NoTransform_ContinuingIsCompound) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if ((i < 15)) {
+      break;
+    }
+    _ = 123;
+
+    continuing {
+      if (false) {
+      }
+    }
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, NoTransform_ContinuingMultipleStmts) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if ((i < 15)) {
+      break;
+    }
+    _ = 123;
+
+    continuing {
+      i = (i + 1);
+      _ = i;
+    }
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LoopToForLoopTest, NoTransform_ContinuingUsesVarDeclInLoopBody) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  i = 0;
+  loop {
+    if ((i < 15)) {
+      break;
+    }
+    var j : i32;
+
+    continuing {
+      i = (i + j);
+    }
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<LoopToForLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/manager.cc b/src/tint/transform/manager.cc
new file mode 100644
index 0000000..158a28e
--- /dev/null
+++ b/src/tint/transform/manager.cc
@@ -0,0 +1,86 @@
+// Copyright 2020 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/manager.h"
+
+/// If set to 1 then the transform::Manager will dump the WGSL of the program
+/// before and after each transform. Helpful for debugging bad output.
+#define TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM 0
+
+#if TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
+#define TINT_IF_PRINT_PROGRAM(x) x
+#else  // TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
+#define TINT_IF_PRINT_PROGRAM(x)
+#endif  // TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Manager);
+
+namespace tint {
+namespace transform {
+
+Manager::Manager() = default;
+Manager::~Manager() = default;
+
+Output Manager::Run(const Program* program, const DataMap& data) const {
+  const Program* in = program;
+
+#if TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
+  auto print_program = [&](const char* msg, const Transform* transform) {
+    auto wgsl = Program::printer(in);
+    std::cout << "---------------------------------------------------------"
+              << std::endl;
+    std::cout << "-- " << msg << " " << transform->TypeInfo().name << ":"
+              << std::endl;
+    std::cout << "---------------------------------------------------------"
+              << std::endl;
+    std::cout << wgsl << std::endl;
+    std::cout << "---------------------------------------------------------"
+              << std::endl
+              << std::endl;
+  };
+#endif
+
+  Output out;
+  for (const auto& transform : transforms_) {
+    if (!transform->ShouldRun(in, data)) {
+      TINT_IF_PRINT_PROGRAM(std::cout << "Skipping "
+                                      << transform->TypeInfo().name);
+      continue;
+    }
+    TINT_IF_PRINT_PROGRAM(print_program("Input to", transform.get()));
+
+    auto res = transform->Run(in, data);
+    out.program = std::move(res.program);
+    out.data.Add(std::move(res.data));
+    in = &out.program;
+    if (!in->IsValid()) {
+      TINT_IF_PRINT_PROGRAM(
+          print_program("Invalid output of", transform.get()));
+      return out;
+    }
+
+    if (transform == transforms_.back()) {
+      TINT_IF_PRINT_PROGRAM(print_program("Output of", transform.get()));
+    }
+  }
+
+  if (program == in) {
+    out.program = program->Clone();
+  }
+
+  return out;
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/manager.h b/src/tint/transform/manager.h
new file mode 100644
index 0000000..092b568
--- /dev/null
+++ b/src/tint/transform/manager.h
@@ -0,0 +1,64 @@
+// Copyright 2020 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_MANAGER_H_
+#define SRC_TINT_TRANSFORM_MANAGER_H_
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// A collection of Transforms that act as a single Transform.
+/// The inner transforms will execute in the appended order.
+/// If any inner transform fails the manager will return immediately and
+/// the error can be retrieved with the Output's diagnostics.
+class Manager : public Castable<Manager, Transform> {
+ public:
+  /// Constructor
+  Manager();
+  ~Manager() override;
+
+  /// Add pass to the manager
+  /// @param transform the transform to append
+  void append(std::unique_ptr<Transform> transform) {
+    transforms_.push_back(std::move(transform));
+  }
+
+  /// Add pass to the manager of type `T`, constructed with the provided
+  /// arguments.
+  /// @param args the arguments to forward to the `T` constructor
+  template <typename T, typename... ARGS>
+  void Add(ARGS&&... args) {
+    transforms_.emplace_back(std::make_unique<T>(std::forward<ARGS>(args)...));
+  }
+
+  /// Runs the transforms on `program`, returning the transformation result.
+  /// @param program the source program to transform
+  /// @param data optional extra transform-specific input data
+  /// @returns the transformed program and diagnostics
+  Output Run(const Program* program, const DataMap& data = {}) const override;
+
+ private:
+  std::vector<std::unique_ptr<Transform>> transforms_;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_MANAGER_H_
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
new file mode 100644
index 0000000..e213c45
--- /dev/null
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.cc
@@ -0,0 +1,399 @@
+// 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/transform/module_scope_var_to_entry_point_param.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ModuleScopeVarToEntryPointParam);
+
+namespace tint {
+namespace transform {
+namespace {
+// Returns `true` if `type` is or contains a matrix type.
+bool ContainsMatrix(const sem::Type* type) {
+  type = type->UnwrapRef();
+  if (type->Is<sem::Matrix>()) {
+    return true;
+  } else if (auto* ary = type->As<sem::Array>()) {
+    return ContainsMatrix(ary->ElemType());
+  } else if (auto* str = type->As<sem::Struct>()) {
+    for (auto* member : str->Members()) {
+      if (ContainsMatrix(member->Type())) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+}  // namespace
+
+/// State holds the current transform state.
+struct ModuleScopeVarToEntryPointParam::State {
+  /// The clone context.
+  CloneContext& ctx;
+
+  /// Constructor
+  /// @param context the clone context
+  explicit State(CloneContext& context) : ctx(context) {}
+
+  /// Clone any struct types that are contained in `ty` (including `ty` itself),
+  /// and add it to the global declarations now, so that they precede new global
+  /// declarations that need to reference them.
+  /// @param ty the type to clone
+  void CloneStructTypes(const sem::Type* ty) {
+    if (auto* str = ty->As<sem::Struct>()) {
+      if (!cloned_structs_.emplace(str).second) {
+        // The struct has already been cloned.
+        return;
+      }
+
+      // Recurse into members.
+      for (auto* member : str->Members()) {
+        CloneStructTypes(member->Type());
+      }
+
+      // Clone the struct and add it to the global declaration list.
+      // Remove the old declaration.
+      auto* ast_str = str->Declaration();
+      ctx.dst->AST().AddTypeDecl(ctx.Clone(ast_str));
+      ctx.Remove(ctx.src->AST().GlobalDeclarations(), ast_str);
+    } else if (auto* arr = ty->As<sem::Array>()) {
+      CloneStructTypes(arr->ElemType());
+    }
+  }
+
+  /// Process the module.
+  void Process() {
+    // Predetermine the list of function calls that need to be replaced.
+    using CallList = std::vector<const ast::CallExpression*>;
+    std::unordered_map<const ast::Function*, CallList> calls_to_replace;
+
+    std::vector<const ast::Function*> functions_to_process;
+
+    // Build a list of functions that transitively reference any module-scope
+    // variables.
+    for (auto* func_ast : ctx.src->AST().Functions()) {
+      auto* func_sem = ctx.src->Sem().Get(func_ast);
+
+      bool needs_processing = false;
+      for (auto* var : func_sem->TransitivelyReferencedGlobals()) {
+        if (var->StorageClass() != ast::StorageClass::kNone) {
+          needs_processing = true;
+          break;
+        }
+      }
+      if (needs_processing) {
+        functions_to_process.push_back(func_ast);
+
+        // Find all of the calls to this function that will need to be replaced.
+        for (auto* call : func_sem->CallSites()) {
+          calls_to_replace[call->Stmt()->Function()->Declaration()].push_back(
+              call->Declaration());
+        }
+      }
+    }
+
+    // Build a list of `&ident` expressions. We'll use this later to avoid
+    // generating expressions of the form `&*ident`, which break WGSL validation
+    // rules when this expression is passed to a function.
+    // TODO(jrprice): We should add support for bidirectional SEM tree traversal
+    // so that we can do this on the fly instead.
+    std::unordered_map<const ast::IdentifierExpression*,
+                       const ast::UnaryOpExpression*>
+        ident_to_address_of;
+    for (auto* node : ctx.src->ASTNodes().Objects()) {
+      auto* address_of = node->As<ast::UnaryOpExpression>();
+      if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
+        continue;
+      }
+      if (auto* ident = address_of->expr->As<ast::IdentifierExpression>()) {
+        ident_to_address_of[ident] = address_of;
+      }
+    }
+
+    for (auto* func_ast : functions_to_process) {
+      auto* func_sem = ctx.src->Sem().Get(func_ast);
+      bool is_entry_point = func_ast->IsEntryPoint();
+
+      // Map module-scope variables onto their replacement.
+      struct NewVar {
+        Symbol symbol;
+        bool is_pointer;
+        bool is_wrapped;
+      };
+      const char* kWrappedArrayMemberName = "arr";
+      std::unordered_map<const sem::Variable*, NewVar> var_to_newvar;
+
+      // We aggregate all workgroup variables into a struct to avoid hitting
+      // MSL's limit for threadgroup memory arguments.
+      Symbol workgroup_parameter_symbol;
+      ast::StructMemberList workgroup_parameter_members;
+      auto workgroup_param = [&]() {
+        if (!workgroup_parameter_symbol.IsValid()) {
+          workgroup_parameter_symbol = ctx.dst->Sym();
+        }
+        return workgroup_parameter_symbol;
+      };
+
+      for (auto* var : func_sem->TransitivelyReferencedGlobals()) {
+        auto sc = var->StorageClass();
+        auto* ty = var->Type()->UnwrapRef();
+        if (sc == ast::StorageClass::kNone) {
+          continue;
+        }
+        if (sc != ast::StorageClass::kPrivate &&
+            sc != ast::StorageClass::kStorage &&
+            sc != ast::StorageClass::kUniform &&
+            sc != ast::StorageClass::kUniformConstant &&
+            sc != ast::StorageClass::kWorkgroup) {
+          TINT_ICE(Transform, ctx.dst->Diagnostics())
+              << "unhandled module-scope storage class (" << sc << ")";
+        }
+
+        // This is the symbol for the variable that replaces the module-scope
+        // var.
+        auto new_var_symbol = ctx.dst->Sym();
+
+        // Helper to create an AST node for the store type of the variable.
+        auto store_type = [&]() { return CreateASTTypeFor(ctx, ty); };
+
+        // Track whether the new variable is a pointer or not.
+        bool is_pointer = false;
+
+        // Track whether the new variable was wrapped in a struct or not.
+        bool is_wrapped = false;
+
+        if (is_entry_point) {
+          if (var->Type()->UnwrapRef()->is_handle()) {
+            // For a texture or sampler variable, redeclare it as an entry point
+            // parameter. Disable entry point parameter validation.
+            auto* disable_validation =
+                ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
+            auto attrs = ctx.Clone(var->Declaration()->attributes);
+            attrs.push_back(disable_validation);
+            auto* param = ctx.dst->Param(new_var_symbol, store_type(), attrs);
+            ctx.InsertFront(func_ast->params, param);
+          } else if (sc == ast::StorageClass::kStorage ||
+                     sc == ast::StorageClass::kUniform) {
+            // Variables into the Storage and Uniform storage classes are
+            // redeclared as entry point parameters with a pointer type.
+            auto attributes = ctx.Clone(var->Declaration()->attributes);
+            attributes.push_back(ctx.dst->Disable(
+                ast::DisabledValidation::kEntryPointParameter));
+            attributes.push_back(
+                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+
+            auto* param_type = store_type();
+            if (auto* arr = ty->As<sem::Array>();
+                arr && arr->IsRuntimeSized()) {
+              // 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.
+              CloneStructTypes(ty);
+              auto* wrapper = ctx.dst->Structure(
+                  ctx.dst->Sym(),
+                  {ctx.dst->Member(kWrappedArrayMemberName, param_type)});
+              param_type = ctx.dst->ty.Of(wrapper);
+              is_wrapped = true;
+            }
+
+            param_type = ctx.dst->ty.pointer(
+                param_type, sc, var->Declaration()->declared_access);
+            auto* param =
+                ctx.dst->Param(new_var_symbol, param_type, attributes);
+            ctx.InsertFront(func_ast->params, param);
+            is_pointer = true;
+          } else if (sc == ast::StorageClass::kWorkgroup &&
+                     ContainsMatrix(var->Type())) {
+            // Due to a bug in the MSL compiler, we use a threadgroup memory
+            // argument for any workgroup allocation that contains a matrix.
+            // See crbug.com/tint/938.
+            // TODO(jrprice): Do this for all other workgroup variables too.
+
+            // Create a member in the workgroup parameter struct.
+            auto member = ctx.Clone(var->Declaration()->symbol);
+            workgroup_parameter_members.push_back(
+                ctx.dst->Member(member, store_type()));
+            CloneStructTypes(var->Type()->UnwrapRef());
+
+            // Create a function-scope variable that is a pointer to the member.
+            auto* member_ptr = ctx.dst->AddressOf(ctx.dst->MemberAccessor(
+                ctx.dst->Deref(workgroup_param()), member));
+            auto* local_var =
+                ctx.dst->Const(new_var_symbol,
+                               ctx.dst->ty.pointer(
+                                   store_type(), ast::StorageClass::kWorkgroup),
+                               member_ptr);
+            ctx.InsertFront(func_ast->body->statements,
+                            ctx.dst->Decl(local_var));
+            is_pointer = true;
+          } else {
+            // Variables in the Private and Workgroup storage classes are
+            // redeclared at function scope. Disable storage class validation on
+            // this variable.
+            auto* disable_validation =
+                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass);
+            auto* constructor = ctx.Clone(var->Declaration()->constructor);
+            auto* local_var =
+                ctx.dst->Var(new_var_symbol, store_type(), sc, constructor,
+                             ast::AttributeList{disable_validation});
+            ctx.InsertFront(func_ast->body->statements,
+                            ctx.dst->Decl(local_var));
+          }
+        } else {
+          // For a regular function, redeclare the variable as a parameter.
+          // Use a pointer for non-handle types.
+          auto* param_type = store_type();
+          ast::AttributeList attributes;
+          if (!var->Type()->UnwrapRef()->is_handle()) {
+            param_type = ctx.dst->ty.pointer(
+                param_type, sc, var->Declaration()->declared_access);
+            is_pointer = true;
+
+            // Disable validation of the parameter's storage class and of
+            // arguments passed it.
+            attributes.push_back(
+                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+            attributes.push_back(ctx.dst->Disable(
+                ast::DisabledValidation::kIgnoreInvalidPointerArgument));
+          }
+          ctx.InsertBack(
+              func_ast->params,
+              ctx.dst->Param(new_var_symbol, param_type, attributes));
+        }
+
+        // Replace all uses of the module-scope variable.
+        // For non-entry points, dereference non-handle pointer parameters.
+        for (auto* user : var->Users()) {
+          if (user->Stmt()->Function()->Declaration() == func_ast) {
+            const ast::Expression* expr = ctx.dst->Expr(new_var_symbol);
+            if (is_pointer) {
+              // If this identifier is used by an address-of operator, just
+              // remove the address-of instead of adding a deref, since we
+              // already have a pointer.
+              auto* ident =
+                  user->Declaration()->As<ast::IdentifierExpression>();
+              if (ident_to_address_of.count(ident)) {
+                ctx.Replace(ident_to_address_of[ident], expr);
+                continue;
+              }
+
+              expr = ctx.dst->Deref(expr);
+            }
+            if (is_wrapped) {
+              // Get the member from the wrapper structure.
+              expr = ctx.dst->MemberAccessor(expr, kWrappedArrayMemberName);
+            }
+            ctx.Replace(user->Declaration(), expr);
+          }
+        }
+
+        var_to_newvar[var] = {new_var_symbol, is_pointer, is_wrapped};
+      }
+
+      if (!workgroup_parameter_members.empty()) {
+        // Create the workgroup memory parameter.
+        // The parameter is a struct that contains members for each workgroup
+        // variable.
+        auto* str = ctx.dst->Structure(ctx.dst->Sym(),
+                                       std::move(workgroup_parameter_members));
+        auto* param_type = ctx.dst->ty.pointer(ctx.dst->ty.Of(str),
+                                               ast::StorageClass::kWorkgroup);
+        auto* disable_validation =
+            ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
+        auto* param =
+            ctx.dst->Param(workgroup_param(), param_type, {disable_validation});
+        ctx.InsertFront(func_ast->params, param);
+      }
+
+      // Pass the variables as pointers to any functions that need them.
+      for (auto* call : calls_to_replace[func_ast]) {
+        auto* target =
+            ctx.src->AST().Functions().Find(call->target.name->symbol);
+        auto* target_sem = ctx.src->Sem().Get(target);
+
+        // Add new arguments for any variables that are needed by the callee.
+        // For entry points, pass non-handle types as pointers.
+        for (auto* target_var : target_sem->TransitivelyReferencedGlobals()) {
+          auto sc = target_var->StorageClass();
+          if (sc == ast::StorageClass::kNone) {
+            continue;
+          }
+
+          auto new_var = var_to_newvar[target_var];
+          bool is_handle = target_var->Type()->UnwrapRef()->is_handle();
+          const ast::Expression* arg = ctx.dst->Expr(new_var.symbol);
+          if (new_var.is_wrapped) {
+            // The variable is wrapped in a struct, so we need to pass a pointer
+            // to the struct member instead.
+            arg = ctx.dst->AddressOf(ctx.dst->MemberAccessor(
+                ctx.dst->Deref(arg), kWrappedArrayMemberName));
+          } else if (is_entry_point && !is_handle && !new_var.is_pointer) {
+            // We need to pass a pointer and we don't already have one, so take
+            // the address of the new variable.
+            arg = ctx.dst->AddressOf(arg);
+          }
+          ctx.InsertBack(call->args, arg);
+        }
+      }
+    }
+
+    // Now remove all module-scope variables with these storage classes.
+    for (auto* var_ast : ctx.src->AST().GlobalVariables()) {
+      auto* var_sem = ctx.src->Sem().Get(var_ast);
+      if (var_sem->StorageClass() != ast::StorageClass::kNone) {
+        ctx.Remove(ctx.src->AST().GlobalDeclarations(), var_ast);
+      }
+    }
+  }
+
+ private:
+  std::unordered_set<const sem::Struct*> cloned_structs_;
+};
+
+ModuleScopeVarToEntryPointParam::ModuleScopeVarToEntryPointParam() = default;
+
+ModuleScopeVarToEntryPointParam::~ModuleScopeVarToEntryPointParam() = default;
+
+bool ModuleScopeVarToEntryPointParam::ShouldRun(const Program* program,
+                                                const DataMap&) const {
+  for (auto* decl : program->AST().GlobalDeclarations()) {
+    if (decl->Is<ast::Variable>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void ModuleScopeVarToEntryPointParam::Run(CloneContext& ctx,
+                                          const DataMap&,
+                                          DataMap&) const {
+  State state{ctx};
+  state.Process();
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param.h b/src/tint/transform/module_scope_var_to_entry_point_param.h
new file mode 100644
index 0000000..334fcd4
--- /dev/null
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
+#define SRC_TINT_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Move module-scope variables into the entry point as parameters.
+///
+/// MSL does not allow module-scope variables to have any address space other
+/// than `constant`. This transform moves all module-scope declarations into the
+/// entry point function (either as parameters or function-scope variables) and
+/// then passes them as pointer parameters to any function that references them.
+///
+/// Since WGSL does not allow entry point parameters or function-scope variables
+/// to have these storage classes, we annotate the new variable declarations
+/// with an attribute that bypasses that validation rule.
+///
+/// Before:
+/// ```
+/// struct S {
+///   f : f32;
+/// };
+/// @binding(0) @group(0)
+/// var<storage, read> s : S;
+/// var<private> p : f32 = 2.0;
+///
+/// fn foo() {
+///   p = p + f;
+/// }
+///
+/// @stage(compute) @workgroup_size(1)
+/// fn main() {
+///   foo();
+/// }
+/// ```
+///
+/// After:
+/// ```
+/// fn foo(p : ptr<private, f32>, sptr : ptr<storage, S, read>) {
+///   *p = *p + (*sptr).f;
+/// }
+///
+/// @stage(compute) @workgroup_size(1)
+/// fn main(sptr : ptr<storage, S, read>) {
+///   var<private> p : f32 = 2.0;
+///   foo(&p, sptr);
+/// }
+/// ```
+class ModuleScopeVarToEntryPointParam
+    : public Castable<ModuleScopeVarToEntryPointParam, Transform> {
+ public:
+  /// Constructor
+  ModuleScopeVarToEntryPointParam();
+  /// Destructor
+  ~ModuleScopeVarToEntryPointParam() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+  struct State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param_test.cc b/src/tint/transform/module_scope_var_to_entry_point_param_test.cc
new file mode 100644
index 0000000..341ef99
--- /dev/null
+++ b/src/tint/transform/module_scope_var_to_entry_point_param_test.cc
@@ -0,0 +1,1170 @@
+// 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/transform/module_scope_var_to_entry_point_param.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ModuleScopeVarToEntryPointParamTest = TransformTest;
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<ModuleScopeVarToEntryPointParam>(src));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, ShouldRunHasGlobal) {
+  auto* src = R"(
+var<private> v : i32;
+)";
+
+  EXPECT_TRUE(ShouldRun<ModuleScopeVarToEntryPointParam>(src));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Basic) {
+  auto* src = R"(
+var<private> p : f32;
+var<workgroup> w : f32;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  w = p;
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol : f32;
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
+  tint_symbol = tint_symbol_1;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Basic_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  w = p;
+}
+
+var<workgroup> w : f32;
+var<private> p : f32;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol : f32;
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
+  tint_symbol = tint_symbol_1;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, FunctionCalls) {
+  auto* src = R"(
+var<private> p : f32;
+var<workgroup> w : f32;
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  p = a;
+  w = b;
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  bar(a, b);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+)";
+
+  auto* expect = R"(
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<workgroup, f32>) {
+  *(tint_symbol) = a;
+  *(tint_symbol_1) = b;
+}
+
+fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<workgroup, f32>) {
+  let b : f32 = 2.0;
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_4 : f32;
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_5 : f32;
+  foo(1.0, &(tint_symbol_4), &(tint_symbol_5));
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, FunctionCalls_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  bar(a, b);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  p = a;
+  w = b;
+}
+
+var<private> p : f32;
+var<workgroup> w : f32;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
+  foo(1.0, &(tint_symbol), &(tint_symbol_1));
+}
+
+fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<workgroup, f32>) {
+  let b : f32 = 2.0;
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_5 : ptr<workgroup, f32>) {
+  *(tint_symbol_4) = a;
+  *(tint_symbol_5) = b;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Constructors) {
+  auto* src = R"(
+var<private> a : f32 = 1.0;
+var<private> b : f32 = f32();
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x : f32 = a + b;
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32 = 1.0;
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32 = f32();
+  let x : f32 = (tint_symbol + tint_symbol_1);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Constructors_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x : f32 = a + b;
+}
+
+var<private> b : f32 = f32();
+var<private> a : f32 = 1.0;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32 = 1.0;
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32 = f32();
+  let x : f32 = (tint_symbol + tint_symbol_1);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Pointers) {
+  auto* src = R"(
+var<private> p : f32;
+var<workgroup> w : f32;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let p_ptr : ptr<private, f32> = &p;
+  let w_ptr : ptr<workgroup, f32> = &w;
+  let x : f32 = *p_ptr + *w_ptr;
+  *p_ptr = x;
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
+  let p_ptr : ptr<private, f32> = &(tint_symbol);
+  let w_ptr : ptr<workgroup, f32> = &(tint_symbol_1);
+  let x : f32 = (*(p_ptr) + *(w_ptr));
+  *(p_ptr) = x;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Pointers_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let p_ptr : ptr<private, f32> = &p;
+  let w_ptr : ptr<workgroup, f32> = &w;
+  let x : f32 = *p_ptr + *w_ptr;
+  *p_ptr = x;
+}
+
+var<workgroup> w : f32;
+var<private> p : f32;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
+  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
+  let p_ptr : ptr<private, f32> = &(tint_symbol);
+  let w_ptr : ptr<workgroup, f32> = &(tint_symbol_1);
+  let x : f32 = (*(p_ptr) + *(w_ptr));
+  *(p_ptr) = x;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, FoldAddressOfDeref) {
+  auto* src = R"(
+var<private> v : f32;
+
+fn bar(p : ptr<private, f32>) {
+  (*p) = 0.0;
+}
+
+fn foo() {
+  bar(&v);
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo();
+}
+)";
+
+  auto* expect = R"(
+fn bar(p : ptr<private, f32>) {
+  *(p) = 0.0;
+}
+
+fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<private, f32>) {
+  bar(tint_symbol);
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
+  foo(&(tint_symbol_1));
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, FoldAddressOfDeref_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo();
+}
+
+fn foo() {
+  bar(&v);
+}
+
+fn bar(p : ptr<private, f32>) {
+  (*p) = 0.0;
+}
+
+var<private> v : f32;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
+  foo(&(tint_symbol));
+}
+
+fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<private, f32>) {
+  bar(tint_symbol_1);
+}
+
+fn bar(p : ptr<private, f32>) {
+  *(p) = 0.0;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_Basic) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u : S;
+@group(0) @binding(1)
+var<storage> s : S;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = u;
+  _ = s;
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
+  _ = *(tint_symbol);
+  _ = *(tint_symbol_1);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_Basic_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = u;
+  _ = s;
+}
+
+@group(0) @binding(0) var<uniform> u : S;
+@group(0) @binding(1) var<storage> s : S;
+
+struct S {
+  a : f32;
+};
+
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
+  _ = *(tint_symbol);
+  _ = *(tint_symbol_1);
+}
+
+struct S {
+  a : f32;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray) {
+  auto* src = R"(
+@group(0) @binding(0)
+var<storage> buffer : array<f32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  arr : array<f32>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+
+@group(0) @binding(0)
+var<storage> buffer : array<f32>;
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  arr : array<f32>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArrayInsideFunction) {
+  auto* src = R"(
+@group(0) @binding(0)
+var<storage> buffer : array<f32>;
+
+fn foo() {
+  _ = buffer[0];
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  arr : array<f32>;
+}
+
+fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<storage, array<f32>>) {
+  _ = (*(tint_symbol))[0];
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, tint_symbol_2>) {
+  foo(&((*(tint_symbol_1)).arr));
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest,
+       Buffer_RuntimeArrayInsideFunction_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo();
+}
+
+fn foo() {
+  _ = buffer[0];
+}
+
+@group(0) @binding(0) var<storage> buffer : array<f32>;
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  arr : array<f32>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  foo(&((*(tint_symbol)).arr));
+}
+
+fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<storage, array<f32>>) {
+  _ = (*(tint_symbol_2))[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray_Alias) {
+  auto* src = R"(
+type myarray = array<f32>;
+
+@group(0) @binding(0)
+var<storage> buffer : myarray;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  arr : array<f32>;
+}
+
+type myarray = array<f32>;
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest,
+       Buffer_RuntimeArray_Alias_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+
+@group(0) @binding(0) var<storage> buffer : myarray;
+
+type myarray = array<f32>;
+)";
+
+  auto* expect = R"(
+struct tint_symbol_1 {
+  arr : array<f32>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+
+type myarray = array<f32>;
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_ArrayOfStruct) {
+  auto* src = R"(
+struct S {
+  f : f32;
+};
+
+@group(0) @binding(0)
+var<storage> buffer : array<S>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  f : f32;
+}
+
+struct tint_symbol_1 {
+  arr : array<S>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_ArrayOfStruct_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = buffer[0];
+}
+
+@group(0) @binding(0) var<storage> buffer : array<S>;
+
+struct S {
+  f : f32;
+};
+)";
+
+  auto* expect = R"(
+struct S {
+  f : f32;
+}
+
+struct tint_symbol_1 {
+  arr : array<S>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
+  _ = (*(tint_symbol)).arr[0];
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_FunctionCalls) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u : S;
+@group(0) @binding(1)
+var<storage> s : S;
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  _ = u;
+  _ = s;
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  _ = u;
+  bar(a, b);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<storage, S>) {
+  _ = *(tint_symbol);
+  _ = *(tint_symbol_1);
+}
+
+fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<storage, S>) {
+  let b : f32 = 2.0;
+  _ = *(tint_symbol_2);
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_4 : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_5 : ptr<storage, S>) {
+  foo(1.0, tint_symbol_4, tint_symbol_5);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_FunctionCalls_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  _ = u;
+  bar(a, b);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  _ = u;
+  _ = s;
+}
+
+struct S {
+  a : f32;
+};
+
+@group(0) @binding(0)
+var<uniform> u : S;
+@group(0) @binding(1)
+var<storage> s : S;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
+  foo(1.0, tint_symbol, tint_symbol_1);
+}
+
+fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<storage, S>) {
+  let b : f32 = 2.0;
+  _ = *(tint_symbol_2);
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_5 : ptr<storage, S>) {
+  _ = *(tint_symbol_4);
+  _ = *(tint_symbol_5);
+}
+
+struct S {
+  a : f32;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, HandleTypes_Basic) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  _ = t;
+  _ = s;
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_1 : sampler) {
+  _ = tint_symbol;
+  _ = tint_symbol_1;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, HandleTypes_FunctionCalls) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  _ = t;
+  _ = s;
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  _ = t;
+  bar(a, b);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+)";
+
+  auto* expect = R"(
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, tint_symbol : texture_2d<f32>, tint_symbol_1 : sampler) {
+  _ = tint_symbol;
+  _ = tint_symbol_1;
+}
+
+fn foo(a : f32, tint_symbol_2 : texture_2d<f32>, tint_symbol_3 : sampler) {
+  let b : f32 = 2.0;
+  _ = tint_symbol_2;
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol_4 : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_5 : sampler) {
+  foo(1.0, tint_symbol_4, tint_symbol_5);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest,
+       HandleTypes_FunctionCalls_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  foo(1.0);
+}
+
+fn foo(a : f32) {
+  let b : f32 = 2.0;
+  _ = t;
+  bar(a, b);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32) {
+  _ = t;
+  _ = s;
+}
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_1 : sampler) {
+  foo(1.0, tint_symbol, tint_symbol_1);
+}
+
+fn foo(a : f32, tint_symbol_2 : texture_2d<f32>, tint_symbol_3 : sampler) {
+  let b : f32 = 2.0;
+  _ = tint_symbol_2;
+  bar(a, b, tint_symbol_2, tint_symbol_3);
+  no_uses();
+}
+
+fn no_uses() {
+}
+
+fn bar(a : f32, b : f32, tint_symbol_4 : texture_2d<f32>, tint_symbol_5 : sampler) {
+  _ = tint_symbol_4;
+  _ = tint_symbol_5;
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, Matrix) {
+  auto* src = R"(
+var<workgroup> m : mat2x2<f32>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = m;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  m : mat2x2<f32>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_2>) {
+  let tint_symbol : ptr<workgroup, mat2x2<f32>> = &((*(tint_symbol_1)).m);
+  let x = *(tint_symbol);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, NestedMatrix) {
+  auto* src = R"(
+struct S1 {
+  m : mat2x2<f32>;
+};
+struct S2 {
+  s : S1;
+};
+var<workgroup> m : array<S2, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = m;
+}
+)";
+
+  auto* expect = R"(
+struct S1 {
+  m : mat2x2<f32>;
+}
+
+struct S2 {
+  s : S1;
+}
+
+struct tint_symbol_2 {
+  m : array<S2, 4u>;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_2>) {
+  let tint_symbol : ptr<workgroup, array<S2, 4u>> = &((*(tint_symbol_1)).m);
+  let x = *(tint_symbol);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test that we do not duplicate a struct type used by multiple workgroup
+// variables that are promoted to threadgroup memory arguments.
+TEST_F(ModuleScopeVarToEntryPointParamTest, DuplicateThreadgroupArgumentTypes) {
+  auto* src = R"(
+struct S {
+  m : mat2x2<f32>;
+};
+
+var<workgroup> a : S;
+
+var<workgroup> b : S;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = a;
+  let y = b;
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  m : mat2x2<f32>;
+}
+
+struct tint_symbol_3 {
+  a : S;
+  b : S;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_3>) {
+  let tint_symbol : ptr<workgroup, S> = &((*(tint_symbol_1)).a);
+  let tint_symbol_2 : ptr<workgroup, S> = &((*(tint_symbol_1)).b);
+  let x = *(tint_symbol);
+  let y = *(tint_symbol_2);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test that we do not duplicate a struct type used by multiple workgroup
+// variables that are promoted to threadgroup memory arguments.
+TEST_F(ModuleScopeVarToEntryPointParamTest,
+       DuplicateThreadgroupArgumentTypes_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = a;
+  let y = b;
+}
+
+var<workgroup> a : S;
+var<workgroup> b : S;
+
+struct S {
+  m : mat2x2<f32>;
+};
+)";
+
+  auto* expect = R"(
+struct S {
+  m : mat2x2<f32>;
+}
+
+struct tint_symbol_3 {
+  a : S;
+  b : S;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_3>) {
+  let tint_symbol : ptr<workgroup, S> = &((*(tint_symbol_1)).a);
+  let tint_symbol_2 : ptr<workgroup, S> = &((*(tint_symbol_1)).b);
+  let x = *(tint_symbol);
+  let y = *(tint_symbol_2);
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, UnusedVariables) {
+  auto* src = R"(
+struct S {
+  a : f32;
+};
+
+var<private> p : f32;
+var<workgroup> w : f32;
+
+@group(0) @binding(0)
+var<uniform> ub : S;
+@group(0) @binding(1)
+var<storage> sb : S;
+
+@group(0) @binding(2) var t : texture_2d<f32>;
+@group(0) @binding(3) var s : sampler;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+}
+)";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ModuleScopeVarToEntryPointParamTest, EmtpyModule) {
+  auto* src = "";
+
+  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
+
+  EXPECT_EQ(src, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
new file mode 100644
index 0000000..801c593
--- /dev/null
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -0,0 +1,454 @@
+// 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/transform/multiplanar_external_texture.h"
+
+#include <string>
+#include <vector>
+
+#include "src/tint/ast/function.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture);
+TINT_INSTANTIATE_TYPEINFO(
+    tint::transform::MultiplanarExternalTexture::NewBindingPoints);
+
+namespace tint {
+namespace transform {
+namespace {
+
+/// This struct stores symbols for new bindings created as a result of
+/// transforming a texture_external instance.
+struct NewBindingSymbols {
+  Symbol params;
+  Symbol plane_0;
+  Symbol plane_1;
+};
+}  // namespace
+
+/// State holds the current transform state
+struct MultiplanarExternalTexture::State {
+  /// The clone context.
+  CloneContext& ctx;
+
+  /// ProgramBuilder for the context
+  ProgramBuilder& b;
+
+  /// Destination binding locations for the expanded texture_external provided
+  /// as input into the transform.
+  const NewBindingPoints* new_binding_points;
+
+  /// Symbol for the ExternalTextureParams struct
+  Symbol params_struct_sym;
+
+  /// Symbol for the textureLoadExternal function
+  Symbol texture_load_external_sym;
+
+  /// Symbol for the textureSampleExternal function
+  Symbol texture_sample_external_sym;
+
+  /// Storage for new bindings that have been created corresponding to an
+  /// original texture_external binding.
+  std::unordered_map<const sem::Variable*, NewBindingSymbols>
+      new_binding_symbols;
+
+  /// Constructor
+  /// @param context the clone
+  /// @param newBindingPoints the input destination binding locations for the
+  /// expanded texture_external
+  State(CloneContext& context, const NewBindingPoints* newBindingPoints)
+      : ctx(context), b(*context.dst), new_binding_points(newBindingPoints) {}
+
+  /// Processes the module
+  void Process() {
+    auto& sem = ctx.src->Sem();
+
+    // For each texture_external binding, we replace it with a texture_2d<f32>
+    // binding and create two additional bindings (one texture_2d<f32> to
+    // represent the secondary plane and one uniform buffer for the
+    // ExternalTextureParams struct).
+    for (auto* var : ctx.src->AST().GlobalVariables()) {
+      auto* sem_var = sem.Get(var);
+      if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) {
+        continue;
+      }
+
+      // If the attributes are empty, then this must be a texture_external
+      // passed as a function parameter. These variables are transformed
+      // elsewhere.
+      if (var->attributes.empty()) {
+        continue;
+      }
+
+      // If we find a texture_external binding, we know we must emit the
+      // ExternalTextureParams struct.
+      if (!params_struct_sym.IsValid()) {
+        createExtTexParamsStruct();
+      }
+
+      // The binding points for the newly introduced bindings must have been
+      // provided to this transform. We fetch the new binding points by
+      // providing the original texture_external binding points into the
+      // passed map.
+      BindingPoint bp = {var->BindingPoint().group->value,
+                         var->BindingPoint().binding->value};
+
+      BindingsMap::const_iterator it =
+          new_binding_points->bindings_map.find(bp);
+      if (it == new_binding_points->bindings_map.end()) {
+        b.Diagnostics().add_error(
+            diag::System::Transform,
+            "missing new binding points for texture_external at binding {" +
+                std::to_string(bp.group) + "," + std::to_string(bp.binding) +
+                "}");
+        continue;
+      }
+
+      BindingPoints bps = it->second;
+
+      // Symbols for the newly created bindings must be saved so they can be
+      // passed as parameters later. These are placed in a map and keyed by
+      // the source symbol associated with the texture_external binding that
+      // corresponds with the new destination bindings.
+      // NewBindingSymbols new_binding_syms;
+      auto& syms = new_binding_symbols[sem_var];
+      syms.plane_0 = ctx.Clone(var->symbol);
+      syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
+      b.Global(syms.plane_1,
+               b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
+               b.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding));
+      syms.params = b.Symbols().New("ext_tex_params");
+      b.Global(syms.params, b.ty.type_name("ExternalTextureParams"),
+               ast::StorageClass::kUniform,
+               b.GroupAndBinding(bps.params.group, bps.params.binding));
+
+      // Replace the original texture_external binding with a texture_2d<f32>
+      // binding.
+      ast::AttributeList cloned_attributes = ctx.Clone(var->attributes);
+      const ast::Expression* cloned_constructor = ctx.Clone(var->constructor);
+
+      auto* replacement =
+          b.Var(syms.plane_0,
+                b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
+                cloned_constructor, cloned_attributes);
+      ctx.Replace(var, replacement);
+    }
+
+    // We must update all the texture_external parameters for user declared
+    // functions.
+    for (auto* fn : ctx.src->AST().Functions()) {
+      for (const ast::Variable* param : fn->params) {
+        if (auto* sem_var = sem.Get(param)) {
+          if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) {
+            continue;
+          }
+          // If we find a texture_external, we must ensure the
+          // ExternalTextureParams struct exists.
+          if (!params_struct_sym.IsValid()) {
+            createExtTexParamsStruct();
+          }
+          // When a texture_external is found, we insert all components
+          // the texture_external into the parameter list. We must also place
+          // the new symbols into the transform state so they can be used when
+          // transforming function calls.
+          auto& syms = new_binding_symbols[sem_var];
+          syms.plane_0 = ctx.Clone(param->symbol);
+          syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
+          syms.params = b.Symbols().New("ext_tex_params");
+          auto tex2d_f32 = [&] {
+            return b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32());
+          };
+          ctx.Replace(param, b.Param(syms.plane_0, tex2d_f32()));
+          ctx.InsertAfter(fn->params, param,
+                          b.Param(syms.plane_1, tex2d_f32()));
+          ctx.InsertAfter(
+              fn->params, param,
+              b.Param(syms.params, b.ty.type_name(params_struct_sym)));
+        }
+      }
+    }
+
+    // Transform the original textureLoad and textureSampleLevel calls into
+    // textureLoadExternal and textureSampleExternal calls.
+    ctx.ReplaceAll(
+        [&](const ast::CallExpression* expr) -> const ast::CallExpression* {
+          auto* builtin = sem.Get(expr)->Target()->As<sem::Builtin>();
+
+          if (builtin && !builtin->Parameters().empty() &&
+              builtin->Parameters()[0]->Type()->Is<sem::ExternalTexture>() &&
+              builtin->Type() != sem::BuiltinType::kTextureDimensions) {
+            if (auto* var_user = sem.Get<sem::VariableUser>(expr->args[0])) {
+              auto it = new_binding_symbols.find(var_user->Variable());
+              if (it == new_binding_symbols.end()) {
+                // If valid new binding locations were not provided earlier, we
+                // would have been unable to create these symbols. An error
+                // message was emitted earlier, so just return early to avoid
+                // internal compiler errors and retain a clean error message.
+                return nullptr;
+              }
+              auto& syms = it->second;
+
+              if (builtin->Type() == sem::BuiltinType::kTextureLoad) {
+                return createTexLdExt(expr, syms);
+              }
+
+              if (builtin->Type() == sem::BuiltinType::kTextureSampleLevel) {
+                return createTexSmpExt(expr, syms);
+              }
+            }
+
+          } else if (sem.Get(expr)->Target()->Is<sem::Function>()) {
+            // The call expression may be to a user-defined function that
+            // contains a texture_external parameter. These need to be expanded
+            // out to multiple plane textures and the texture parameters
+            // structure.
+            for (auto* arg : expr->args) {
+              if (auto* var_user = sem.Get<sem::VariableUser>(arg)) {
+                // Check if a parameter is a texture_external by trying to find
+                // it in the transform state.
+                auto it = new_binding_symbols.find(var_user->Variable());
+                if (it != new_binding_symbols.end()) {
+                  auto& syms = it->second;
+                  // When we find a texture_external, we must unpack it into its
+                  // components.
+                  ctx.Replace(arg, b.Expr(syms.plane_0));
+                  ctx.InsertAfter(expr->args, arg, b.Expr(syms.plane_1));
+                  ctx.InsertAfter(expr->args, arg, b.Expr(syms.params));
+                }
+              }
+            }
+          }
+
+          return nullptr;
+        });
+  }
+
+  /// Creates the ExternalTextureParams struct.
+  void createExtTexParamsStruct() {
+    ast::StructMemberList member_list = {
+        b.Member("numPlanes", b.ty.u32()), b.Member("vr", b.ty.f32()),
+        b.Member("ug", b.ty.f32()), b.Member("vg", b.ty.f32()),
+        b.Member("ub", b.ty.f32())};
+
+    params_struct_sym = b.Symbols().New("ExternalTextureParams");
+
+    b.Structure(params_struct_sym, member_list);
+  }
+
+  /// Constructs a StatementList containing all the statements making up the
+  /// bodies of the textureSampleExternal and textureLoadExternal functions.
+  /// @param call_type determines which function body to generate
+  /// @returns a statement list that makes of the body of the chosen function
+  ast::StatementList createTexFnExtStatementList(sem::BuiltinType call_type) {
+    using f32 = ProgramBuilder::f32;
+    const ast::CallExpression* single_plane_call = nullptr;
+    const ast::CallExpression* plane_0_call = nullptr;
+    const ast::CallExpression* plane_1_call = nullptr;
+    if (call_type == sem::BuiltinType::kTextureSampleLevel) {
+      // textureSampleLevel(plane0, smp, coord.xy, 0.0);
+      single_plane_call =
+          b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
+      // textureSampleLevel(plane0, smp, coord.xy, 0.0);
+      plane_0_call =
+          b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
+      // textureSampleLevel(plane1, smp, coord.xy, 0.0);
+      plane_1_call =
+          b.Call("textureSampleLevel", "plane1", "smp", "coord", 0.0f);
+    } else if (call_type == sem::BuiltinType::kTextureLoad) {
+      // textureLoad(plane0, coords.xy, 0);
+      single_plane_call = b.Call("textureLoad", "plane0", "coord", 0);
+      // textureLoad(plane0, coords.xy, 0);
+      plane_0_call = b.Call("textureLoad", "plane0", "coord", 0);
+      // textureLoad(plane1, coords.xy, 0);
+      plane_1_call = b.Call("textureLoad", "plane1", "coord", 0);
+    } else {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "unhandled builtin: " << call_type;
+    }
+
+    return {
+        // if (params.numPlanes == 1u) {
+        //    return singlePlaneCall
+        // }
+        b.If(b.create<ast::BinaryExpression>(
+                 ast::BinaryOp::kEqual, b.MemberAccessor("params", "numPlanes"),
+                 b.Expr(1u)),
+             b.Block(b.Return(single_plane_call))),
+        // let y = plane0Call.r - 0.0625;
+        b.Decl(b.Const("y", nullptr,
+                       b.Sub(b.MemberAccessor(plane_0_call, "r"), 0.0625f))),
+        // let uv = plane1Call.rg - 0.5;
+        b.Decl(b.Const("uv", nullptr,
+                       b.Sub(b.MemberAccessor(plane_1_call, "rg"), 0.5f))),
+        // let u = uv.x;
+        b.Decl(b.Const("u", nullptr, b.MemberAccessor("uv", "x"))),
+        // let v = uv.y;
+        b.Decl(b.Const("v", nullptr, b.MemberAccessor("uv", "y"))),
+        // let r = 1.164 * y + params.vr * v;
+        b.Decl(b.Const("r", nullptr,
+                       b.Add(b.Mul(1.164f, "y"),
+                             b.Mul(b.MemberAccessor("params", "vr"), "v")))),
+        // let g = 1.164 * y - params.ug * u - params.vg * v;
+        b.Decl(
+            b.Const("g", nullptr,
+                    b.Sub(b.Sub(b.Mul(1.164f, "y"),
+                                b.Mul(b.MemberAccessor("params", "ug"), "u")),
+                          b.Mul(b.MemberAccessor("params", "vg"), "v")))),
+        // let b = 1.164 * y + params.ub * u;
+        b.Decl(b.Const("b", nullptr,
+                       b.Add(b.Mul(1.164f, "y"),
+                             b.Mul(b.MemberAccessor("params", "ub"), "u")))),
+        // return vec4<f32>(r, g, b, 1.0);
+        b.Return(b.vec4<f32>("r", "g", "b", 1.0f)),
+    };
+  }
+
+  /// Creates the textureSampleExternal function if needed and returns a call
+  /// expression to it.
+  /// @param expr the call expression being transformed
+  /// @param syms the expanded symbols to be used in the new call
+  /// @returns a call expression to textureSampleExternal
+  const ast::CallExpression* createTexSmpExt(const ast::CallExpression* expr,
+                                             NewBindingSymbols syms) {
+    ast::ExpressionList params;
+    const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
+
+    if (expr->args.size() != 3) {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "expected textureSampleLevel call with a "
+             "texture_external to have 3 parameters, found "
+          << expr->args.size() << " parameters";
+    }
+
+    if (!texture_sample_external_sym.IsValid()) {
+      texture_sample_external_sym = b.Symbols().New("textureSampleExternal");
+
+      // Emit the textureSampleExternal function.
+      ast::VariableList varList = {
+          b.Param("plane0",
+                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
+          b.Param("plane1",
+                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
+          b.Param("smp", b.ty.sampler(ast::SamplerKind::kSampler)),
+          b.Param("coord", b.ty.vec2(b.ty.f32())),
+          b.Param("params", b.ty.type_name(params_struct_sym))};
+
+      ast::StatementList statementList =
+          createTexFnExtStatementList(sem::BuiltinType::kTextureSampleLevel);
+
+      b.Func(texture_sample_external_sym, varList, b.ty.vec4(b.ty.f32()),
+             statementList, {});
+    }
+
+    const ast::IdentifierExpression* exp = b.Expr(texture_sample_external_sym);
+    params = {plane_0_binding_param, b.Expr(syms.plane_1),
+              ctx.Clone(expr->args[1]), ctx.Clone(expr->args[2]),
+              b.Expr(syms.params)};
+    return b.Call(exp, params);
+  }
+
+  /// Creates the textureLoadExternal function if needed and returns a call
+  /// expression to it.
+  /// @param expr the call expression being transformed
+  /// @param syms the expanded symbols to be used in the new call
+  /// @returns a call expression to textureLoadExternal
+  const ast::CallExpression* createTexLdExt(const ast::CallExpression* expr,
+                                            NewBindingSymbols syms) {
+    ast::ExpressionList params;
+    const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
+
+    if (expr->args.size() != 2) {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "expected textureLoad call with a texture_external "
+             "to have 2 parameters, found "
+          << expr->args.size() << " parameters";
+    }
+
+    if (!texture_load_external_sym.IsValid()) {
+      texture_load_external_sym = b.Symbols().New("textureLoadExternal");
+
+      // Emit the textureLoadExternal function.
+      ast::VariableList var_list = {
+          b.Param("plane0",
+                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
+          b.Param("plane1",
+                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
+          b.Param("coord", b.ty.vec2(b.ty.i32())),
+          b.Param("params", b.ty.type_name(params_struct_sym))};
+
+      ast::StatementList statement_list =
+          createTexFnExtStatementList(sem::BuiltinType::kTextureLoad);
+
+      b.Func(texture_load_external_sym, var_list, b.ty.vec4(b.ty.f32()),
+             statement_list, {});
+    }
+
+    const ast::IdentifierExpression* exp = b.Expr(texture_load_external_sym);
+    params = {plane_0_binding_param, b.Expr(syms.plane_1),
+              ctx.Clone(expr->args[1]), b.Expr(syms.params)};
+    return b.Call(exp, params);
+  }
+};
+
+MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints(
+    BindingsMap inputBindingsMap)
+    : bindings_map(std::move(inputBindingsMap)) {}
+MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default;
+
+MultiplanarExternalTexture::MultiplanarExternalTexture() = default;
+MultiplanarExternalTexture::~MultiplanarExternalTexture() = default;
+
+bool MultiplanarExternalTexture::ShouldRun(const Program* program,
+                                           const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* ty = node->As<ast::Type>()) {
+      if (program->Sem().Get<sem::ExternalTexture>(ty)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// Within this transform, an instance of a texture_external binding is unpacked
+// into two texture_2d<f32> bindings representing two possible planes of a
+// single texture and a uniform buffer binding representing a struct of
+// parameters. Calls to textureLoad or textureSampleLevel that contain a
+// texture_external parameter will be transformed into a newly generated version
+// of the function, which can perform the desired operation on a single RGBA
+// plane or on separate Y and UV planes.
+void MultiplanarExternalTexture::Run(CloneContext& ctx,
+                                     const DataMap& inputs,
+                                     DataMap&) const {
+  auto* new_binding_points = inputs.Get<NewBindingPoints>();
+
+  if (!new_binding_points) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing new binding point data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  State state(ctx, new_binding_points);
+
+  state.Process();
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/multiplanar_external_texture.h b/src/tint/transform/multiplanar_external_texture.h
new file mode 100644
index 0000000..240d520
--- /dev/null
+++ b/src/tint/transform/multiplanar_external_texture.h
@@ -0,0 +1,102 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
+#define SRC_TINT_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/ast/struct_member.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/sem/builtin_type.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// BindingPoint is an alias to sem::BindingPoint
+using BindingPoint = sem::BindingPoint;
+
+/// This struct identifies the binding groups and locations for new bindings to
+/// use when transforming a texture_external instance.
+struct BindingPoints {
+  /// The desired binding location of the texture_2d representing plane #1 when
+  /// a texture_external binding is expanded.
+  BindingPoint plane_1;
+  /// The desired binding location of the ExternalTextureParams uniform when a
+  /// texture_external binding is expanded.
+  BindingPoint params;
+};
+
+/// Within the MultiplanarExternalTexture transform, each instance of a
+/// texture_external binding is unpacked into two texture_2d<f32> bindings
+/// representing two possible planes of a texture and a uniform buffer binding
+/// representing a struct of parameters. Calls to textureLoad or
+/// textureSampleLevel that contain a texture_external parameter will be
+/// transformed into a newly generated version of the function, which can
+/// perform the desired operation on a single RGBA plane or on seperate Y and UV
+/// planes.
+class MultiplanarExternalTexture
+    : public Castable<MultiplanarExternalTexture, Transform> {
+ public:
+  /// BindingsMap is a map where the key is the binding location of a
+  /// texture_external and the value is a struct containing the desired
+  /// locations for new bindings expanded from the texture_external instance.
+  using BindingsMap = std::unordered_map<BindingPoint, BindingPoints>;
+
+  /// NewBindingPoints is consumed by the MultiplanarExternalTexture transform.
+  /// Data holds information about location of each texture_external binding and
+  /// which binding slots it should expand into.
+  struct NewBindingPoints : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param bm a map to the new binding slots to use.
+    explicit NewBindingPoints(BindingsMap bm);
+
+    /// Destructor
+    ~NewBindingPoints() override;
+
+    /// A map of new binding points to use.
+    const BindingsMap bindings_map;
+  };
+
+  /// Constructor
+  MultiplanarExternalTexture();
+  /// Destructor
+  ~MultiplanarExternalTexture() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  struct State;
+
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
diff --git a/src/tint/transform/multiplanar_external_texture_test.cc b/src/tint/transform/multiplanar_external_texture_test.cc
new file mode 100644
index 0000000..b37ef68
--- /dev/null
+++ b/src/tint/transform/multiplanar_external_texture_test.cc
@@ -0,0 +1,1297 @@
+// 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/transform/multiplanar_external_texture.h"
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using MultiplanarExternalTextureTest = TransformTest;
+
+TEST_F(MultiplanarExternalTextureTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<MultiplanarExternalTexture>(src));
+}
+
+TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureAlias) {
+  auto* src = R"(
+type ET = texture_external;
+)";
+
+  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
+}
+TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureGlobal) {
+  auto* src = R"(
+[[group(0), binding(0)]] var ext_tex : texture_external;
+)";
+
+  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
+}
+
+TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureParam) {
+  auto* src = R"(
+fn f(ext_tex : texture_external) {}
+)";
+
+  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
+}
+
+// Running the transform without passing in data for the new bindings should
+// result in an error.
+TEST_F(MultiplanarExternalTextureTest, ErrorNoPassedData) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy);
+}
+)";
+  auto* expect =
+      R"(error: missing new binding point data for tint::transform::MultiplanarExternalTexture)";
+
+  auto got = Run<MultiplanarExternalTexture>(src);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Running the transform with incorrect binding data should result in an error.
+TEST_F(MultiplanarExternalTextureTest, ErrorIncorrectBindingPont) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy);
+}
+)";
+
+  auto* expect =
+      R"(error: missing new binding points for texture_external at binding {0,1})";
+
+  DataMap data;
+  // This bindings map specifies 0,0 as the location of the texture_external,
+  // which is incorrect.
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with a textureDimensions call.
+TEST_F(MultiplanarExternalTextureTest, Dimensions) {
+  auto* src = R"(
+@group(0) @binding(0) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(ext_tex);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(ext_tex);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with a textureDimensions call.
+TEST_F(MultiplanarExternalTextureTest, Dimensions_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(ext_tex);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  var dim : vec2<i32>;
+  dim = textureDimensions(ext_tex);
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test that the transform works with a textureSampleLevel call.
+TEST_F(MultiplanarExternalTextureTest, BasicTextureSampleLevel) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var ext_tex : texture_2d<f32>;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params);
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test that the transform works with a textureSampleLevel call.
+TEST_F(MultiplanarExternalTextureTest, BasicTextureSampleLevel_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy);
+}
+
+@group(0) @binding(1) var ext_tex : texture_external;
+@group(0) @binding(0) var s : sampler;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params);
+}
+
+@group(0) @binding(1) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(0) var s : sampler;
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with a textureLoad call.
+TEST_F(MultiplanarExternalTextureTest, BasicTextureLoad) {
+  auto* src = R"(
+@group(0) @binding(0) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(ext_tex, vec2<i32>(1, 1));
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureLoad(plane0, coord, 0);
+  }
+  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
+  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params);
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with a textureLoad call.
+TEST_F(MultiplanarExternalTextureTest, BasicTextureLoad_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoad(ext_tex, vec2<i32>(1, 1));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureLoad(plane0, coord, 0);
+  }
+  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
+  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with both a textureSampleLevel and textureLoad
+// call.
+TEST_F(MultiplanarExternalTextureTest, TextureSampleAndTextureLoad) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy) + textureLoad(ext_tex, vec2<i32>(1, 1));
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var ext_tex : texture_2d<f32>;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureLoad(plane0, coord, 0);
+  }
+  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
+  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return (textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params));
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with both a textureSampleLevel and textureLoad
+// call.
+TEST_F(MultiplanarExternalTextureTest, TextureSampleAndTextureLoad_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy) + textureLoad(ext_tex, vec2<i32>(1, 1));
+}
+
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureLoad(plane0, coord, 0);
+  }
+  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
+  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return (textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params));
+}
+
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var ext_tex : texture_2d<f32>;
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with many instances of texture_external.
+TEST_F(MultiplanarExternalTextureTest, ManyTextureSampleLevel) {
+  auto* src = R"(
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var ext_tex : texture_external;
+@group(0) @binding(2) var ext_tex_1 : texture_external;
+@group(0) @binding(3) var ext_tex_2 : texture_external;
+@group(1) @binding(0) var ext_tex_3 : texture_external;
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return textureSampleLevel(ext_tex, s, coord.xy) + textureSampleLevel(ext_tex_1, s, coord.xy) + textureSampleLevel(ext_tex_2, s, coord.xy) + textureSampleLevel(ext_tex_3, s, coord.xy);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(4) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(5) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(6) var ext_tex_plane_1_1 : texture_2d<f32>;
+
+@group(0) @binding(7) var<uniform> ext_tex_params_1 : ExternalTextureParams;
+
+@group(0) @binding(8) var ext_tex_plane_1_2 : texture_2d<f32>;
+
+@group(0) @binding(9) var<uniform> ext_tex_params_2 : ExternalTextureParams;
+
+@group(1) @binding(1) var ext_tex_plane_1_3 : texture_2d<f32>;
+
+@group(1) @binding(2) var<uniform> ext_tex_params_3 : ExternalTextureParams;
+
+@group(0) @binding(0) var s : sampler;
+
+@group(0) @binding(1) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(2) var ext_tex_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var ext_tex_2 : texture_2d<f32>;
+
+@group(1) @binding(0) var ext_tex_3 : texture_2d<f32>;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
+  return (((textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureSampleExternal(ext_tex_1, ext_tex_plane_1_1, s, coord.xy, ext_tex_params_1)) + textureSampleExternal(ext_tex_2, ext_tex_plane_1_2, s, coord.xy, ext_tex_params_2)) + textureSampleExternal(ext_tex_3, ext_tex_plane_1_3, s, coord.xy, ext_tex_params_3));
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 1}, {{0, 4}, {0, 5}}},
+          {{0, 2}, {{0, 6}, {0, 7}}},
+          {{0, 3}, {{0, 8}, {0, 9}}},
+          {{1, 0}, {{1, 1}, {1, 2}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the texture_external passed as a function parameter produces the
+// correct output.
+TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParam) {
+  auto* src = R"(
+fn f(t : texture_external, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the texture_external passed as a function parameter produces the
+// correct output.
+TEST_F(MultiplanarExternalTextureTest,
+       ExternalTexturePassedAsParam_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp);
+}
+
+fn f(t : texture_external, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the texture_external passed as a parameter not in the first
+// position produces the correct output.
+TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsSecondParam) {
+  auto* src = R"(
+fn f(s : sampler, t : texture_external) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(smp, ext_tex);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(s : sampler, t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(smp, ext_tex, ext_tex_plane_1, ext_tex_params);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that multiple texture_external params passed to a function produces the
+// correct output.
+TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParamMultiple) {
+  auto* src = R"(
+fn f(t : texture_external, s : sampler, t2 : texture_external) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+  textureSampleLevel(t2, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+@group(0) @binding(2) var ext_tex2 : texture_external;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp, ext_tex2);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(4) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(5) var ext_tex_plane_1_1 : texture_2d<f32>;
+
+@group(0) @binding(6) var<uniform> ext_tex_params_1 : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
+  textureSampleExternal(t, ext_tex_plane_1_2, s, vec2<f32>(1.0, 2.0), ext_tex_params_2);
+  textureSampleExternal(t2, ext_tex_plane_1_3, s, vec2<f32>(1.0, 2.0), ext_tex_params_3);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@group(0) @binding(2) var ext_tex2 : texture_2d<f32>;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp, ext_tex2, ext_tex_plane_1_1, ext_tex_params_1);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 3}, {0, 4}}},
+          {{0, 2}, {{0, 5}, {0, 6}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that multiple texture_external params passed to a function produces the
+// correct output.
+TEST_F(MultiplanarExternalTextureTest,
+       ExternalTexturePassedAsParamMultiple_OutOfOrder) {
+  auto* src = R"(
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp, ext_tex2);
+}
+
+fn f(t : texture_external, s : sampler, t2 : texture_external) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+  textureSampleLevel(t2, s, vec2<f32>(1.0, 2.0));
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+@group(0) @binding(2) var ext_tex2 : texture_external;
+
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(4) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@group(0) @binding(5) var ext_tex_plane_1_1 : texture_2d<f32>;
+
+@group(0) @binding(6) var<uniform> ext_tex_params_1 : ExternalTextureParams;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp, ext_tex2, ext_tex_plane_1_1, ext_tex_params_1);
+}
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
+  textureSampleExternal(t, ext_tex_plane_1_2, s, vec2<f32>(1.0, 2.0), ext_tex_params_2);
+  textureSampleExternal(t2, ext_tex_plane_1_3, s, vec2<f32>(1.0, 2.0), ext_tex_params_3);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@group(0) @binding(2) var ext_tex2 : texture_2d<f32>;
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 3}, {0, 4}}},
+          {{0, 2}, {{0, 5}, {0, 6}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the texture_external passed to as a parameter to multiple
+// functions produces the correct output.
+TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParamNested) {
+  auto* src = R"(
+fn nested(t : texture_external, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn f(t : texture_external, s : sampler) {
+  nested(t, s);
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler) {
+  nested(t, ext_tex_plane_1_2, ext_tex_params_2, s);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the texture_external passed to as a parameter to multiple
+// functions produces the correct output.
+TEST_F(MultiplanarExternalTextureTest,
+       ExternalTexturePassedAsParamNested_OutOfOrder) {
+  auto* src = R"(
+fn nested(t : texture_external, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+fn f(t : texture_external, s : sampler) {
+  nested(t, s);
+}
+
+@group(0) @binding(0) var ext_tex : texture_external;
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, smp);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler) {
+  nested(t, ext_tex_plane_1_2, ext_tex_params_2, s);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the transform works with a function using an external texture,
+// even if there's no external texture declared at module scope.
+TEST_F(MultiplanarExternalTextureTest,
+       ExternalTexturePassedAsParamWithoutGlobalDecl) {
+  auto* src = R"(
+fn f(ext_tex : texture_external) -> vec2<i32> {
+  return textureDimensions(ext_tex);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+fn f(ext_tex : texture_2d<f32>, ext_tex_plane_1 : texture_2d<f32>, ext_tex_params : ExternalTextureParams) -> vec2<i32> {
+  return textureDimensions(ext_tex);
+}
+)";
+
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the the transform handles aliases to external textures
+TEST_F(MultiplanarExternalTextureTest, ExternalTextureAlias) {
+  auto* src = R"(
+type ET = texture_external;
+
+fn f(t : ET, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+[[group(0), binding(0)]] var ext_tex : ET;
+[[group(0), binding(1)]] var smp : sampler;
+
+[[stage(fragment)]]
+fn main() {
+  f(ext_tex, smp);
+}
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+type ET = texture_external;
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+// Tests that the the transform handles aliases to external textures
+TEST_F(MultiplanarExternalTextureTest, ExternalTextureAlias_OutOfOrder) {
+  auto* src = R"(
+[[stage(fragment)]]
+fn main() {
+  f(ext_tex, smp);
+}
+
+fn f(t : ET, s : sampler) {
+  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
+}
+
+[[group(0), binding(0)]] var ext_tex : ET;
+[[group(0), binding(1)]] var smp : sampler;
+
+type ET = texture_external;
+)";
+
+  auto* expect = R"(
+struct ExternalTextureParams {
+  numPlanes : u32;
+  vr : f32;
+  ug : f32;
+  vg : f32;
+  ub : f32;
+}
+
+@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
+
+@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
+
+@stage(fragment)
+fn main() {
+  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
+}
+
+fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  if ((params.numPlanes == 1u)) {
+    return textureSampleLevel(plane0, smp, coord, 0.0);
+  }
+  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
+  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
+  let u = uv.x;
+  let v = uv.y;
+  let r = ((1.164000034 * y) + (params.vr * v));
+  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
+  let b = ((1.164000034 * y) + (params.ub * u));
+  return vec4<f32>(r, g, b, 1.0);
+}
+
+fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
+  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
+}
+
+@group(0) @binding(0) var ext_tex : texture_2d<f32>;
+
+@group(0) @binding(1) var smp : sampler;
+
+type ET = texture_external;
+)";
+  DataMap data;
+  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
+      MultiplanarExternalTexture::BindingsMap{
+          {{0, 0}, {{0, 2}, {0, 3}}},
+      });
+  auto got = Run<MultiplanarExternalTexture>(src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
new file mode 100644
index 0000000..db71158
--- /dev/null
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -0,0 +1,170 @@
+// 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/transform/num_workgroups_from_uniform.h"
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/utils/hash.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::NumWorkgroupsFromUniform);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::NumWorkgroupsFromUniform::Config);
+
+namespace tint {
+namespace transform {
+namespace {
+/// Accessor describes the identifiers used in a member accessor that is being
+/// used to retrieve the num_workgroups builtin from a parameter.
+struct Accessor {
+  Symbol param;
+  Symbol member;
+
+  /// Equality operator
+  bool operator==(const Accessor& other) const {
+    return param == other.param && member == other.member;
+  }
+  /// Hash function
+  struct Hasher {
+    size_t operator()(const Accessor& a) const {
+      return utils::Hash(a.param, a.member);
+    }
+  };
+};
+}  // namespace
+
+NumWorkgroupsFromUniform::NumWorkgroupsFromUniform() = default;
+NumWorkgroupsFromUniform::~NumWorkgroupsFromUniform() = default;
+
+bool NumWorkgroupsFromUniform::ShouldRun(const Program* program,
+                                         const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* attr = node->As<ast::BuiltinAttribute>()) {
+      if (attr->builtin == ast::Builtin::kNumWorkgroups) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void NumWorkgroupsFromUniform::Run(CloneContext& ctx,
+                                   const DataMap& inputs,
+                                   DataMap&) const {
+  auto* cfg = inputs.Get<Config>();
+  if (cfg == nullptr) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+    return;
+  }
+
+  const char* kNumWorkgroupsMemberName = "num_workgroups";
+
+  // Find all entry point parameters that declare the num_workgroups builtin.
+  std::unordered_set<Accessor, Accessor::Hasher> to_replace;
+  for (auto* func : ctx.src->AST().Functions()) {
+    // num_workgroups is only valid for compute stages.
+    if (func->PipelineStage() != ast::PipelineStage::kCompute) {
+      continue;
+    }
+
+    for (auto* param : ctx.src->Sem().Get(func)->Parameters()) {
+      // Because the CanonicalizeEntryPointIO transform has been run, builtins
+      // will only appear as struct members.
+      auto* str = param->Type()->As<sem::Struct>();
+      if (!str) {
+        continue;
+      }
+
+      for (auto* member : str->Members()) {
+        auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
+            member->Declaration()->attributes);
+        if (!builtin || builtin->builtin != ast::Builtin::kNumWorkgroups) {
+          continue;
+        }
+
+        // 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});
+
+        // Remove the struct member.
+        // The CanonicalizeEntryPointIO transform will have generated this
+        // struct uniquely for this particular entry point, so we know that
+        // there will be no other uses of this struct in the module and that we
+        // can safely modify it here.
+        ctx.Remove(str->Declaration()->members, member->Declaration());
+
+        // If this is the only member, remove the struct and parameter too.
+        if (str->Members().size() == 1) {
+          ctx.Remove(func->params, param->Declaration());
+          ctx.Remove(ctx.src->AST().GlobalDeclarations(), str->Declaration());
+        }
+      }
+    }
+  }
+
+  // Get (or create, on first call) the uniform buffer that will receive the
+  // number of workgroups.
+  const ast::Variable* num_workgroups_ubo = nullptr;
+  auto get_ubo = [&]() {
+    if (!num_workgroups_ubo) {
+      auto* num_workgroups_struct = ctx.dst->Structure(
+          ctx.dst->Sym(),
+          {ctx.dst->Member(kNumWorkgroupsMemberName,
+                           ctx.dst->ty.vec3(ctx.dst->ty.u32()))});
+      num_workgroups_ubo = ctx.dst->Global(
+          ctx.dst->Sym(), ctx.dst->ty.Of(num_workgroups_struct),
+          ast::StorageClass::kUniform,
+          ast::AttributeList{ctx.dst->GroupAndBinding(
+              cfg->ubo_binding.group, cfg->ubo_binding.binding)});
+    }
+    return num_workgroups_ubo;
+  };
+
+  // Now replace all the places where the builtins are accessed with the value
+  // loaded from the uniform buffer.
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    auto* accessor = node->As<ast::MemberAccessorExpression>();
+    if (!accessor) {
+      continue;
+    }
+    auto* ident = accessor->structure->As<ast::IdentifierExpression>();
+    if (!ident) {
+      continue;
+    }
+
+    if (to_replace.count({ident->symbol, accessor->member->symbol})) {
+      ctx.Replace(accessor, ctx.dst->MemberAccessor(get_ubo()->symbol,
+                                                    kNumWorkgroupsMemberName));
+    }
+  }
+
+  ctx.Clone();
+}
+
+NumWorkgroupsFromUniform::Config::Config(sem::BindingPoint ubo_bp)
+    : ubo_binding(ubo_bp) {}
+NumWorkgroupsFromUniform::Config::Config(const Config&) = default;
+NumWorkgroupsFromUniform::Config::~Config() = default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/num_workgroups_from_uniform.h b/src/tint/transform/num_workgroups_from_uniform.h
new file mode 100644
index 0000000..0d20a02
--- /dev/null
+++ b/src/tint/transform/num_workgroups_from_uniform.h
@@ -0,0 +1,90 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
+#define SRC_TINT_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
+
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+
+// Forward declarations
+class CloneContext;
+
+namespace transform {
+
+/// NumWorkgroupsFromUniform is a transform that implements the `num_workgroups`
+/// builtin by loading it from a uniform buffer.
+///
+/// The generated uniform buffer will have the form:
+/// ```
+/// struct num_workgroups_struct {
+///  num_workgroups : vec3<u32>;
+/// };
+///
+/// @group(0) @binding(0)
+/// var<uniform> num_workgroups_ubo : num_workgroups_struct;
+/// ```
+/// The binding group and number used for this uniform buffer is provided via
+/// the `Config` transform input.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * CanonicalizeEntryPointIO
+class NumWorkgroupsFromUniform
+    : public Castable<NumWorkgroupsFromUniform, Transform> {
+ public:
+  /// Constructor
+  NumWorkgroupsFromUniform();
+  /// Destructor
+  ~NumWorkgroupsFromUniform() override;
+
+  /// Configuration options for the NumWorkgroupsFromUniform transform.
+  struct Config : public Castable<Data, transform::Data> {
+    /// Constructor
+    /// @param ubo_bp the binding point to use for the generated uniform buffer.
+    explicit Config(sem::BindingPoint ubo_bp);
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// The binding point to use for the generated uniform buffer.
+    sem::BindingPoint ubo_binding;
+  };
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
diff --git a/src/tint/transform/num_workgroups_from_uniform_test.cc b/src/tint/transform/num_workgroups_from_uniform_test.cc
new file mode 100644
index 0000000..1674433
--- /dev/null
+++ b/src/tint/transform/num_workgroups_from_uniform_test.cc
@@ -0,0 +1,456 @@
+// 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/transform/num_workgroups_from_uniform.h"
+
+#include <utility>
+
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using NumWorkgroupsFromUniformTest = TransformTest;
+
+TEST_F(NumWorkgroupsFromUniformTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<NumWorkgroupsFromUniform>(src));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, ShouldRunHasNumWorkgroups) {
+  auto* src = R"(
+[[stage(compute), workgroup_size(1)]]
+fn main([[builtin(num_workgroups)]] num_wgs : vec3<u32>) {
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<NumWorkgroupsFromUniform>(src));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, Error_MissingTransformData) {
+  auto* src = R"(
+[[stage(compute), workgroup_size(1)]]
+fn main([[builtin(num_workgroups)]] num_wgs : vec3<u32>) {
+}
+)";
+
+  auto* expect =
+      "error: missing transform data for "
+      "tint::transform::NumWorkgroupsFromUniform";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, Basic) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main(@builtin(num_workgroups) num_wgs : vec3<u32>) {
+  let groups_x = num_wgs.x;
+  let groups_y = num_wgs.y;
+  let groups_z = num_wgs.z;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+fn main_inner(num_wgs : vec3<u32>) {
+  let groups_x = num_wgs.x;
+  let groups_y = num_wgs.y;
+  let groups_z = num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  main_inner(tint_symbol_3.num_workgroups);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, StructOnlyMember) {
+  auto* src = R"(
+struct Builtins {
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+};
+
+@stage(compute) @workgroup_size(1)
+fn main(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+struct Builtins {
+  num_wgs : vec3<u32>;
+}
+
+fn main_inner(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  main_inner(Builtins(tint_symbol_3.num_workgroups));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, StructOnlyMember_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+struct Builtins {
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+};
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+fn main_inner(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  main_inner(Builtins(tint_symbol_3.num_workgroups));
+}
+
+struct Builtins {
+  num_wgs : vec3<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, StructMultipleMembers) {
+  auto* src = R"(
+struct Builtins {
+  @builtin(global_invocation_id) gid : vec3<u32>;
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+  @builtin(workgroup_id) wgid : vec3<u32>;
+};
+
+@stage(compute) @workgroup_size(1)
+fn main(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+struct Builtins {
+  gid : vec3<u32>;
+  num_wgs : vec3<u32>;
+  wgid : vec3<u32>;
+}
+
+struct tint_symbol_1 {
+  @builtin(global_invocation_id)
+  gid : vec3<u32>;
+  @builtin(workgroup_id)
+  wgid : vec3<u32>;
+}
+
+fn main_inner(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(tint_symbol : tint_symbol_1) {
+  main_inner(Builtins(tint_symbol.gid, tint_symbol_3.num_workgroups, tint_symbol.wgid));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, StructMultipleMembers_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+struct Builtins {
+  @builtin(global_invocation_id) gid : vec3<u32>;
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+  @builtin(workgroup_id) wgid : vec3<u32>;
+};
+
+)";
+
+  auto* expect = R"(
+struct tint_symbol_2 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+struct tint_symbol_1 {
+  @builtin(global_invocation_id)
+  gid : vec3<u32>;
+  @builtin(workgroup_id)
+  wgid : vec3<u32>;
+}
+
+fn main_inner(in : Builtins) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(tint_symbol : tint_symbol_1) {
+  main_inner(Builtins(tint_symbol.gid, tint_symbol_3.num_workgroups, tint_symbol.wgid));
+}
+
+struct Builtins {
+  gid : vec3<u32>;
+  num_wgs : vec3<u32>;
+  wgid : vec3<u32>;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, MultipleEntryPoints) {
+  auto* src = R"(
+struct Builtins1 {
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+};
+
+struct Builtins2 {
+  @builtin(global_invocation_id) gid : vec3<u32>;
+  @builtin(num_workgroups) num_wgs : vec3<u32>;
+  @builtin(workgroup_id) wgid : vec3<u32>;
+};
+
+@stage(compute) @workgroup_size(1)
+fn main1(in : Builtins1) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main2(in : Builtins2) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main3(@builtin(num_workgroups) num_wgs : vec3<u32>) {
+  let groups_x = num_wgs.x;
+  let groups_y = num_wgs.y;
+  let groups_z = num_wgs.z;
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol_6 {
+  num_workgroups : vec3<u32>;
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_7 : tint_symbol_6;
+
+struct Builtins1 {
+  num_wgs : vec3<u32>;
+}
+
+struct Builtins2 {
+  gid : vec3<u32>;
+  num_wgs : vec3<u32>;
+  wgid : vec3<u32>;
+}
+
+fn main1_inner(in : Builtins1) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main1() {
+  main1_inner(Builtins1(tint_symbol_7.num_workgroups));
+}
+
+struct tint_symbol_3 {
+  @builtin(global_invocation_id)
+  gid : vec3<u32>;
+  @builtin(workgroup_id)
+  wgid : vec3<u32>;
+}
+
+fn main2_inner(in : Builtins2) {
+  let groups_x = in.num_wgs.x;
+  let groups_y = in.num_wgs.y;
+  let groups_z = in.num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main2(tint_symbol_2 : tint_symbol_3) {
+  main2_inner(Builtins2(tint_symbol_2.gid, tint_symbol_7.num_workgroups, tint_symbol_2.wgid));
+}
+
+fn main3_inner(num_wgs : vec3<u32>) {
+  let groups_x = num_wgs.x;
+  let groups_y = num_wgs.y;
+  let groups_z = num_wgs.z;
+}
+
+@stage(compute) @workgroup_size(1)
+fn main3() {
+  main3_inner(tint_symbol_7.num_workgroups);
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, NoUsages) {
+  auto* src = R"(
+struct Builtins {
+  @builtin(global_invocation_id) gid : vec3<u32>;
+  @builtin(workgroup_id) wgid : vec3<u32>;
+};
+
+@stage(compute) @workgroup_size(1)
+fn main(in : Builtins) {
+}
+)";
+
+  auto* expect = R"(
+struct Builtins {
+  gid : vec3<u32>;
+  wgid : vec3<u32>;
+}
+
+struct tint_symbol_1 {
+  @builtin(global_invocation_id)
+  gid : vec3<u32>;
+  @builtin(workgroup_id)
+  wgid : vec3<u32>;
+}
+
+fn main_inner(in : Builtins) {
+}
+
+@stage(compute) @workgroup_size(1)
+fn main(tint_symbol : tint_symbol_1) {
+  main_inner(Builtins(tint_symbol.gid, tint_symbol.wgid));
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
+  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
+      src, data);
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/pad_array_elements.cc b/src/tint/transform/pad_array_elements.cc
new file mode 100644
index 0000000..bf406df
--- /dev/null
+++ b/src/tint/transform/pad_array_elements.cc
@@ -0,0 +1,177 @@
+// 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/transform/pad_array_elements.h"
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/utils/map.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::PadArrayElements);
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ArrayBuilder = std::function<const ast::Array*()>;
+
+/// PadArray returns a function that constructs a new array in `ctx.dst` with
+/// the element type padded to account for the explicit stride. PadArray will
+/// recursively pad arrays-of-arrays. The new array element type will be added
+/// to module-scope type declarations of `ctx.dst`.
+/// @param ctx the CloneContext
+/// @param create_ast_type_for Transform::CreateASTTypeFor()
+/// @param padded_arrays a map of src array type to the new array name
+/// @param array the array type
+/// @return the new AST array
+template <typename CREATE_AST_TYPE_FOR>
+ArrayBuilder PadArray(
+    CloneContext& ctx,
+    CREATE_AST_TYPE_FOR&& create_ast_type_for,
+    std::unordered_map<const sem::Array*, ArrayBuilder>& padded_arrays,
+    const sem::Array* array) {
+  if (array->IsStrideImplicit()) {
+    // We don't want to wrap arrays that have an implicit stride
+    return nullptr;
+  }
+
+  return utils::GetOrCreate(padded_arrays, array, [&] {
+    // Generate a unique name for the array element type
+    auto name = ctx.dst->Symbols().New("tint_padded_array_element");
+
+    // Examine the element type. Is it also an array?
+    const ast::Type* el_ty = nullptr;
+    if (auto* el_array = array->ElemType()->As<sem::Array>()) {
+      // Array of array - call PadArray() on the element type
+      if (auto p =
+              PadArray(ctx, create_ast_type_for, padded_arrays, el_array)) {
+        el_ty = p();
+      }
+    }
+
+    // If the element wasn't a padded array, just create the typical AST type
+    // for it
+    if (el_ty == nullptr) {
+      el_ty = create_ast_type_for(ctx, array->ElemType());
+    }
+
+    // Structure() will create and append the ast::Struct to the
+    // global declarations of `ctx.dst`. As we haven't finished building the
+    // current module-scope statement or function, this will be placed
+    // immediately before the usage.
+    ctx.dst->Structure(
+        name,
+        {ctx.dst->Member("el", el_ty, {ctx.dst->MemberSize(array->Stride())})});
+
+    auto* dst = ctx.dst;
+    return [=] {
+      if (array->IsRuntimeSized()) {
+        return dst->ty.array(dst->create<ast::TypeName>(name));
+      } else {
+        return dst->ty.array(dst->create<ast::TypeName>(name), array->Count());
+      }
+    };
+  });
+}
+
+}  // namespace
+
+PadArrayElements::PadArrayElements() = default;
+
+PadArrayElements::~PadArrayElements() = default;
+
+bool PadArrayElements::ShouldRun(const Program* program, const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* var = node->As<ast::Type>()) {
+      if (auto* arr = program->Sem().Get<sem::Array>(var)) {
+        if (!arr->IsStrideImplicit()) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+void PadArrayElements::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  std::unordered_map<const sem::Array*, ArrayBuilder> padded_arrays;
+  auto pad = [&](const sem::Array* array) {
+    return PadArray(ctx, CreateASTTypeFor, padded_arrays, array);
+  };
+
+  // Replace all array types with their corresponding padded array type
+  ctx.ReplaceAll([&](const ast::Type* ast_type) -> const ast::Type* {
+    auto* type = ctx.src->TypeOf(ast_type);
+    if (auto* array = type->UnwrapRef()->As<sem::Array>()) {
+      if (auto p = pad(array)) {
+        return p();
+      }
+    }
+    return nullptr;
+  });
+
+  // Fix up index accessors so `a[1]` becomes `a[1].el`
+  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* accessor)
+                     -> const ast::Expression* {
+    if (auto* array = tint::As<sem::Array>(
+            sem.Get(accessor->object)->Type()->UnwrapRef())) {
+      if (pad(array)) {
+        // Array element is wrapped in a structure. Emit a member accessor
+        // to get to the actual array element.
+        auto* idx = ctx.CloneWithoutTransform(accessor);
+        return ctx.dst->MemberAccessor(idx, "el");
+      }
+    }
+    return nullptr;
+  });
+
+  // Fix up array constructors so `A(1,2)` becomes
+  // `A(padded(1), padded(2))`
+  ctx.ReplaceAll(
+      [&](const ast::CallExpression* expr) -> const ast::Expression* {
+        auto* call = sem.Get(expr);
+        if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
+          if (auto* array = ctor->ReturnType()->As<sem::Array>()) {
+            if (auto p = pad(array)) {
+              auto* arr_ty = p();
+              auto el_typename = arr_ty->type->As<ast::TypeName>()->name;
+
+              ast::ExpressionList args;
+              args.reserve(call->Arguments().size());
+              for (auto* arg : call->Arguments()) {
+                auto* val = ctx.Clone(arg->Declaration());
+                args.emplace_back(ctx.dst->Construct(
+                    ctx.dst->create<ast::TypeName>(el_typename), val));
+              }
+
+              return ctx.dst->Construct(arr_ty, args);
+            }
+          }
+        }
+        return nullptr;
+      });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/pad_array_elements.h b/src/tint/transform/pad_array_elements.h
new file mode 100644
index 0000000..cf5bfee
--- /dev/null
+++ b/src/tint/transform/pad_array_elements.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
+#define SRC_TINT_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// PadArrayElements is a transform that replaces array types with an explicit
+/// stride that is larger than the implicit stride, with an array of a new
+/// structure type. This structure holds with a single field of the element
+/// type, decorated with a `@size` attribute to pad the structure to the
+/// required array stride. The new array types have no explicit stride,
+/// structure size is equal to the desired stride.
+/// Array index expressions and constructors are also adjusted to deal with this
+/// structure element type.
+/// This transform helps with backends that cannot directly return arrays or use
+/// them as parameters.
+class PadArrayElements : public Castable<PadArrayElements, Transform> {
+ public:
+  /// Constructor
+  PadArrayElements();
+
+  /// Destructor
+  ~PadArrayElements() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
diff --git a/src/tint/transform/pad_array_elements_test.cc b/src/tint/transform/pad_array_elements_test.cc
new file mode 100644
index 0000000..d0e0b4b
--- /dev/null
+++ b/src/tint/transform/pad_array_elements_test.cc
@@ -0,0 +1,518 @@
+// 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/transform/pad_array_elements.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using PadArrayElementsTest = TransformTest;
+
+TEST_F(PadArrayElementsTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<PadArrayElements>(src));
+}
+
+TEST_F(PadArrayElementsTest, ShouldRunHasImplicitArrayStride) {
+  auto* src = R"(
+var<private> arr : array<i32, 4>;
+)";
+
+  EXPECT_FALSE(ShouldRun<PadArrayElements>(src));
+}
+
+TEST_F(PadArrayElementsTest, ShouldRunHasExplicitArrayStride) {
+  auto* src = R"(
+var<private> arr : [[stride(8)]] array<i32, 4>;
+)";
+
+  EXPECT_TRUE(ShouldRun<PadArrayElements>(src));
+}
+
+TEST_F(PadArrayElementsTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ImplicitArrayStride) {
+  auto* src = R"(
+var<private> arr : array<i32, 4>;
+)";
+  auto* expect = src;
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArrayAsGlobal) {
+  auto* src = R"(
+var<private> arr : @stride(8) array<i32, 4>;
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(8)
+  el : i32;
+}
+
+var<private> arr : array<tint_padded_array_element, 4u>;
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, RuntimeArray) {
+  auto* src = R"(
+struct S {
+  rta : @stride(8) array<i32>;
+};
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(8)
+  el : i32;
+}
+
+struct S {
+  rta : array<tint_padded_array_element>;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArrayFunctionVar) {
+  auto* src = R"(
+fn f() {
+  var arr : @stride(16) array<i32, 4>;
+  arr = @stride(16) array<i32, 4>();
+  arr = @stride(16) array<i32, 4>(1, 2, 3, 4);
+  let x = arr[3];
+}
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(16)
+  el : i32;
+}
+
+fn f() {
+  var arr : array<tint_padded_array_element, 4u>;
+  arr = array<tint_padded_array_element, 4u>();
+  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
+  let x = arr[3].el;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArrayAsParam) {
+  auto* src = R"(
+fn f(a : @stride(12) array<i32, 4>) -> i32 {
+  return a[2];
+}
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(12)
+  el : i32;
+}
+
+fn f(a : array<tint_padded_array_element, 4u>) -> i32 {
+  return a[2].el;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// TODO(crbug.com/tint/781): Cannot parse the stride on the return array type.
+TEST_F(PadArrayElementsTest, DISABLED_ArrayAsReturn) {
+  auto* src = R"(
+fn f() -> @stride(8) array<i32, 4> {
+  return array<i32, 4>(1, 2, 3, 4);
+}
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  el : i32;
+  @size(4)
+  padding : u32;
+};
+
+fn f() -> array<tint_padded_array_element, 4> {
+  return array<tint_padded_array_element, 4>(tint_padded_array_element(1, 0u), tint_padded_array_element(2, 0u), tint_padded_array_element(3, 0u), tint_padded_array_element(4, 0u));
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArrayAlias) {
+  auto* src = R"(
+type Array = @stride(16) array<i32, 4>;
+
+fn f() {
+  var arr : Array;
+  arr = Array();
+  arr = Array(1, 2, 3, 4);
+  let vals : Array = Array(1, 2, 3, 4);
+  arr = vals;
+  let x = arr[3];
+}
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(16)
+  el : i32;
+}
+
+type Array = array<tint_padded_array_element, 4u>;
+
+fn f() {
+  var arr : array<tint_padded_array_element, 4u>;
+  arr = array<tint_padded_array_element, 4u>();
+  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
+  let vals : array<tint_padded_array_element, 4u> = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
+  arr = vals;
+  let x = arr[3].el;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArrayAlias_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var arr : Array;
+  arr = Array();
+  arr = Array(1, 2, 3, 4);
+  let vals : Array = Array(1, 2, 3, 4);
+  arr = vals;
+  let x = arr[3];
+}
+
+type Array = @stride(16) array<i32, 4>;
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(16)
+  el : i32;
+}
+
+fn f() {
+  var arr : array<tint_padded_array_element, 4u>;
+  arr = array<tint_padded_array_element, 4u>();
+  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
+  let vals : array<tint_padded_array_element, 4u> = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
+  arr = vals;
+  let x = arr[3].el;
+}
+
+type Array = array<tint_padded_array_element, 4u>;
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : @stride(8) array<i32, 4>;
+  b : @stride(8) array<i32, 8>;
+  c : @stride(8) array<i32, 4>;
+  d : @stride(12) array<i32, 8>;
+};
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(8)
+  el : i32;
+}
+
+struct tint_padded_array_element_1 {
+  @size(8)
+  el : i32;
+}
+
+struct tint_padded_array_element_2 {
+  @size(12)
+  el : i32;
+}
+
+struct S {
+  a : array<tint_padded_array_element, 4u>;
+  b : array<tint_padded_array_element_1, 8u>;
+  c : array<tint_padded_array_element, 4u>;
+  d : array<tint_padded_array_element_2, 8u>;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, ArraysOfArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : @stride(512) array<i32, 4>;
+  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
+  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
+};
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(512)
+  el : i32;
+}
+
+struct tint_padded_array_element_2 {
+  @size(32)
+  el : i32;
+}
+
+struct tint_padded_array_element_1 {
+  @size(512)
+  el : array<tint_padded_array_element_2, 4u>;
+}
+
+struct tint_padded_array_element_5 {
+  @size(8)
+  el : i32;
+}
+
+struct tint_padded_array_element_4 {
+  @size(64)
+  el : array<tint_padded_array_element_5, 4u>;
+}
+
+struct tint_padded_array_element_3 {
+  @size(512)
+  el : array<tint_padded_array_element_4, 4u>;
+}
+
+struct S {
+  a : array<tint_padded_array_element, 4u>;
+  b : array<tint_padded_array_element_1, 4u>;
+  c : array<tint_padded_array_element_3, 4u>;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, AccessArraysOfArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : @stride(512) array<i32, 4>;
+  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
+  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
+};
+
+fn f(s : S) -> i32 {
+  return s.a[2] + s.b[1][2] + s.c[3][1][2];
+}
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(512)
+  el : i32;
+}
+
+struct tint_padded_array_element_2 {
+  @size(32)
+  el : i32;
+}
+
+struct tint_padded_array_element_1 {
+  @size(512)
+  el : array<tint_padded_array_element_2, 4u>;
+}
+
+struct tint_padded_array_element_5 {
+  @size(8)
+  el : i32;
+}
+
+struct tint_padded_array_element_4 {
+  @size(64)
+  el : array<tint_padded_array_element_5, 4u>;
+}
+
+struct tint_padded_array_element_3 {
+  @size(512)
+  el : array<tint_padded_array_element_4, 4u>;
+}
+
+struct S {
+  a : array<tint_padded_array_element, 4u>;
+  b : array<tint_padded_array_element_1, 4u>;
+  c : array<tint_padded_array_element_3, 4u>;
+}
+
+fn f(s : S) -> i32 {
+  return ((s.a[2].el + s.b[1].el[2].el) + s.c[3].el[1].el[2].el);
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, AccessArraysOfArraysInStruct_OutOfOrder) {
+  auto* src = R"(
+fn f(s : S) -> i32 {
+  return s.a[2] + s.b[1][2] + s.c[3][1][2];
+}
+
+struct S {
+  a : @stride(512) array<i32, 4>;
+  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
+  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
+};
+)";
+  auto* expect = R"(
+struct tint_padded_array_element {
+  @size(512)
+  el : i32;
+}
+
+struct tint_padded_array_element_1 {
+  @size(32)
+  el : i32;
+}
+
+struct tint_padded_array_element_2 {
+  @size(512)
+  el : array<tint_padded_array_element_1, 4u>;
+}
+
+struct tint_padded_array_element_3 {
+  @size(8)
+  el : i32;
+}
+
+struct tint_padded_array_element_4 {
+  @size(64)
+  el : array<tint_padded_array_element_3, 4u>;
+}
+
+struct tint_padded_array_element_5 {
+  @size(512)
+  el : array<tint_padded_array_element_4, 4u>;
+}
+
+fn f(s : S) -> i32 {
+  return ((s.a[2].el + s.b[1].el[2].el) + s.c[3].el[1].el[2].el);
+}
+
+struct S {
+  a : array<tint_padded_array_element, 4u>;
+  b : array<tint_padded_array_element_2, 4u>;
+  c : array<tint_padded_array_element_5, 4u>;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PadArrayElementsTest, DeclarationOrder) {
+  auto* src = R"(
+type T0 = i32;
+
+type T1 = @stride(8) array<i32, 1>;
+
+type T2 = i32;
+
+fn f1(a : @stride(8) array<i32, 2>) {
+}
+
+type T3 = i32;
+
+fn f2() {
+  var v : @stride(8) array<i32, 3>;
+}
+)";
+  auto* expect = R"(
+type T0 = i32;
+
+struct tint_padded_array_element {
+  @size(8)
+  el : i32;
+}
+
+type T1 = array<tint_padded_array_element, 1u>;
+
+type T2 = i32;
+
+struct tint_padded_array_element_1 {
+  @size(8)
+  el : i32;
+}
+
+fn f1(a : array<tint_padded_array_element_1, 2u>) {
+}
+
+type T3 = i32;
+
+struct tint_padded_array_element_2 {
+  @size(8)
+  el : i32;
+}
+
+fn f2() {
+  var v : array<tint_padded_array_element_2, 3u>;
+}
+)";
+
+  auto got = Run<PadArrayElements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/promote_initializers_to_const_var.cc b/src/tint/transform/promote_initializers_to_const_var.cc
new file mode 100644
index 0000000..a60dd6b
--- /dev/null
+++ b/src/tint/transform/promote_initializers_to_const_var.cc
@@ -0,0 +1,83 @@
+// 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/promote_initializers_to_const_var.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteInitializersToConstVar);
+
+namespace tint::transform {
+
+PromoteInitializersToConstVar::PromoteInitializersToConstVar() = default;
+
+PromoteInitializersToConstVar::~PromoteInitializersToConstVar() = default;
+
+void PromoteInitializersToConstVar::Run(CloneContext& ctx,
+                                        const DataMap&,
+                                        DataMap&) const {
+  HoistToDeclBefore hoist_to_decl_before(ctx);
+
+  // Hoists array and structure initializers to a constant variable, declared
+  // just before the statement of usage.
+  auto type_ctor_to_let = [&](const ast::CallExpression* expr) {
+    auto* ctor = ctx.src->Sem().Get(expr);
+    if (!ctor->Target()->Is<sem::TypeConstructor>()) {
+      return true;
+    }
+    auto* sem_stmt = ctor->Stmt();
+    if (!sem_stmt) {
+      // Expression is outside of a statement. This usually means the
+      // expression is part of a global (module-scope) constant declaration.
+      // These must be constexpr, and so cannot contain the type of
+      // expressions that must be sanitized.
+      return true;
+    }
+
+    auto* stmt = sem_stmt->Declaration();
+
+    if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
+      if (src_var_decl->variable->constructor == expr) {
+        // This statement is just a variable declaration with the
+        // initializer as the constructor value. This is what we're
+        // attempting to transform to, and so ignore.
+        return true;
+      }
+    }
+
+    auto* src_ty = ctor->Type();
+    if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
+      // We only care about array and struct initializers
+      return true;
+    }
+
+    return hoist_to_decl_before.Add(ctor, expr, true);
+  };
+
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* call_expr = node->As<ast::CallExpression>()) {
+      if (!type_ctor_to_let(call_expr)) {
+        return;
+      }
+    }
+  }
+
+  hoist_to_decl_before.Apply();
+  ctx.Clone();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/promote_initializers_to_const_var.h b/src/tint/transform/promote_initializers_to_const_var.h
new file mode 100644
index 0000000..586e27d
--- /dev/null
+++ b/src/tint/transform/promote_initializers_to_const_var.h
@@ -0,0 +1,48 @@
+// 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_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+#define SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// A transform that hoists the array and structure initializers to a constant
+/// variable, declared just before the statement of usage.
+/// @see crbug.com/tint/406
+class PromoteInitializersToConstVar
+    : public Castable<PromoteInitializersToConstVar, Transform> {
+ public:
+  /// Constructor
+  PromoteInitializersToConstVar();
+
+  /// Destructor
+  ~PromoteInitializersToConstVar() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
diff --git a/src/tint/transform/promote_initializers_to_const_var_test.cc b/src/tint/transform/promote_initializers_to_const_var_test.cc
new file mode 100644
index 0000000..126f461
--- /dev/null
+++ b/src/tint/transform/promote_initializers_to_const_var_test.cc
@@ -0,0 +1,627 @@
+// 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/transform/promote_initializers_to_const_var.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using PromoteInitializersToConstVarTest = TransformTest;
+
+TEST_F(PromoteInitializersToConstVarTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, BasicArray) {
+  auto* src = R"(
+fn f() {
+  var f0 = 1.0;
+  var f1 = 2.0;
+  var f2 = 3.0;
+  var f3 = 4.0;
+  var i = array<f32, 4u>(f0, f1, f2, f3)[2];
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var f0 = 1.0;
+  var f1 = 2.0;
+  var f2 = 3.0;
+  var f3 = 4.0;
+  let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
+  var i = tint_symbol[2];
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, BasicStruct) {
+  auto* src = R"(
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+};
+
+fn f() {
+  var x = S(1, 2.0, vec3<f32>()).b;
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+}
+
+fn f() {
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  var x = tint_symbol.b;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, BasicStruct_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var x = S(1, 2.0, vec3<f32>()).b;
+}
+
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+};
+)";
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  var x = tint_symbol.b;
+}
+
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInit) {
+  auto* src = R"(
+fn f() {
+  var insert_after = 1;
+  for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  for(var i = tint_symbol[2]; ; ) {
+    break;
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit) {
+  auto* src = R"(
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+};
+
+fn f() {
+  var insert_after = 1;
+  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+}
+
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  for(var x = tint_symbol.b; ; ) {
+    break;
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var insert_after = 1;
+  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
+    break;
+  }
+}
+
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+};
+)";
+
+  auto* expect = R"(
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  for(var x = tint_symbol.b; ; ) {
+    break;
+  }
+}
+
+struct S {
+  a : i32;
+  b : f32;
+  c : vec3<f32>;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCond) {
+  auto* src = R"(
+fn f() {
+  var f = 1.0;
+  for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
+    var marker = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  loop {
+    let tint_symbol = array<f32, 1u>(f);
+    if (!((f == tint_symbol[0]))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      f = (f + 1.0);
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCont) {
+  auto* src = R"(
+fn f() {
+  var f = 0.0;
+  for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var f = 0.0;
+  loop {
+    if (!((f < 10.0))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      let tint_symbol = array<f32, 1u>(1.0);
+      f = (f + tint_symbol[0]);
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInitCondCont) {
+  auto* src = R"(
+fn f() {
+  for(var f = array<f32, 1u>(0.0)[0];
+      f < array<f32, 1u>(1.0)[0];
+      f = f + array<f32, 1u>(2.0)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = array<f32, 1u>(0.0);
+  {
+    var f = tint_symbol[0];
+    loop {
+      let tint_symbol_1 = array<f32, 1u>(1.0);
+      if (!((f < tint_symbol_1[0]))) {
+        break;
+      }
+      {
+        var marker = 1;
+      }
+
+      continuing {
+        let tint_symbol_2 = array<f32, 1u>(2.0);
+        f = (f + tint_symbol_2[0]);
+      }
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIf) {
+  auto* src = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (f == array<f32, 2u>(f, f)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else {
+    let tint_symbol = array<f32, 2u>(f, f);
+    if ((f == tint_symbol[0])) {
+      var marker = 1;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIfChain) {
+  auto* src = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (f == array<f32, 2u>(f, f)[0]) {
+    var marker = 2;
+  } else if (f == array<f32, 2u>(f, f)[1]) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    let tint_symbol = array<f32, 2u>(f, f);
+    if ((f == tint_symbol[0])) {
+      var marker = 2;
+    } else {
+      let tint_symbol_1 = array<f32, 2u>(f, f);
+      if ((f == tint_symbol_1[1])) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInArrayArray) {
+  auto* src = R"(
+fn f() {
+  var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = array<f32, 2u>(1.0, 2.0);
+  let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
+  let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
+  var i = tint_symbol_2[0][1];
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, StructNested) {
+  auto* src = R"(
+struct S1 {
+  a : i32;
+};
+
+struct S2 {
+  a : i32;
+  b : S1;
+  c : i32;
+};
+
+struct S3 {
+  a : S2;
+};
+
+fn f() {
+  var x = S3(S2(1, S1(2), 3)).a.b.a;
+}
+)";
+
+  auto* expect = R"(
+struct S1 {
+  a : i32;
+}
+
+struct S2 {
+  a : i32;
+  b : S1;
+  c : i32;
+}
+
+struct S3 {
+  a : S2;
+}
+
+fn f() {
+  let tint_symbol = S1(2);
+  let tint_symbol_1 = S2(1, tint_symbol, 3);
+  let tint_symbol_2 = S3(tint_symbol_1);
+  var x = tint_symbol_2.a.b.a;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, Mixed) {
+  auto* src = R"(
+struct S1 {
+  a : i32;
+};
+
+struct S2 {
+  a : array<S1, 3u>;
+};
+
+fn f() {
+  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
+}
+)";
+
+  auto* expect = R"(
+struct S1 {
+  a : i32;
+}
+
+struct S2 {
+  a : array<S1, 3u>;
+}
+
+fn f() {
+  let tint_symbol = S1(1);
+  let tint_symbol_1 = S1(2);
+  let tint_symbol_2 = S1(3);
+  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
+  let tint_symbol_4 = S2(tint_symbol_3);
+  var x = tint_symbol_4.a[1].a;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, Mixed_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
+}
+
+struct S2 {
+  a : array<S1, 3u>;
+};
+
+struct S1 {
+  a : i32;
+};
+)";
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = S1(1);
+  let tint_symbol_1 = S1(2);
+  let tint_symbol_2 = S1(3);
+  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
+  let tint_symbol_4 = S2(tint_symbol_3);
+  var x = tint_symbol_4.a[1].a;
+}
+
+struct S2 {
+  a : array<S1, 3u>;
+}
+
+struct S1 {
+  a : i32;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl) {
+  auto* src = R"(
+struct S {
+  a : i32;
+  b : f32;
+  c : i32;
+}
+
+fn f() {
+  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var local_str = S(1, 2.0, 3);
+}
+
+let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+
+let module_str : S = S(1, 2.0, 3);
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var local_str = S(1, 2.0, 3);
+}
+
+let module_str : S = S(1, 2.0, 3);
+
+struct S {
+  a : i32;
+  b : f32;
+  c : i32;
+}
+
+let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<PromoteInitializersToConstVar>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
new file mode 100644
index 0000000..35bad3f
--- /dev/null
+++ b/src/tint/transform/remove_phonies.cc
@@ -0,0 +1,156 @@
+// 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/transform/remove_phonies.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::RemovePhonies);
+
+namespace tint {
+namespace transform {
+namespace {
+
+struct SinkSignature {
+  std::vector<const sem::Type*> types;
+
+  bool operator==(const SinkSignature& other) const {
+    if (types.size() != other.types.size()) {
+      return false;
+    }
+    for (size_t i = 0; i < types.size(); i++) {
+      if (types[i] != other.types[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  struct Hasher {
+    /// @param sig the CallTargetSignature to hash
+    /// @return the hash value
+    std::size_t operator()(const SinkSignature& sig) const {
+      size_t hash = tint::utils::Hash(sig.types.size());
+      for (auto* ty : sig.types) {
+        tint::utils::HashCombine(&hash, ty);
+      }
+      return hash;
+    }
+  };
+};
+
+}  // namespace
+
+RemovePhonies::RemovePhonies() = default;
+
+RemovePhonies::~RemovePhonies() = default;
+
+bool RemovePhonies::ShouldRun(const Program* program, const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (node->Is<ast::PhonyExpression>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void RemovePhonies::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  std::unordered_map<SinkSignature, Symbol, SinkSignature::Hasher> sinks;
+
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* stmt = node->As<ast::AssignmentStatement>()) {
+      if (stmt->lhs->Is<ast::PhonyExpression>()) {
+        std::vector<const ast::Expression*> side_effects;
+        if (!ast::TraverseExpressions(
+                stmt->rhs, ctx.dst->Diagnostics(),
+                [&](const ast::CallExpression* call) {
+                  // ast::CallExpression may map to a function or builtin call
+                  // (both may have side-effects), or a type constructor or
+                  // type conversion (both do not have side effects).
+                  if (sem.Get(call)
+                          ->Target()
+                          ->IsAnyOf<sem::Function, sem::Builtin>()) {
+                    side_effects.push_back(call);
+                    return ast::TraverseAction::Skip;
+                  }
+                  return ast::TraverseAction::Descend;
+                })) {
+          return;
+        }
+
+        if (side_effects.empty()) {
+          // Phony assignment with no side effects.
+          // Just remove it.
+          RemoveStatement(ctx, stmt);
+          continue;
+        }
+
+        if (side_effects.size() == 1) {
+          if (auto* call = side_effects[0]->As<ast::CallExpression>()) {
+            // Phony assignment with single call side effect.
+            // Replace phony assignment with call.
+            ctx.Replace(
+                stmt, [&, call] { return ctx.dst->CallStmt(ctx.Clone(call)); });
+            continue;
+          }
+        }
+
+        // Phony assignment with multiple side effects.
+        // Generate a call to a placeholder function with the side
+        // effects as arguments.
+        ctx.Replace(stmt, [&, side_effects] {
+          SinkSignature sig;
+          for (auto* arg : side_effects) {
+            sig.types.push_back(sem.Get(arg)->Type()->UnwrapRef());
+          }
+          auto sink = utils::GetOrCreate(sinks, sig, [&] {
+            auto name = ctx.dst->Symbols().New("phony_sink");
+            ast::VariableList params;
+            for (auto* ty : sig.types) {
+              auto* ast_ty = CreateASTTypeFor(ctx, ty);
+              params.push_back(
+                  ctx.dst->Param("p" + std::to_string(params.size()), ast_ty));
+            }
+            ctx.dst->Func(name, params, ctx.dst->ty.void_(), {});
+            return name;
+          });
+          ast::ExpressionList args;
+          for (auto* arg : side_effects) {
+            args.push_back(ctx.Clone(arg));
+          }
+          return ctx.dst->CallStmt(ctx.dst->Call(sink, args));
+        });
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/remove_phonies.h b/src/tint/transform/remove_phonies.h
new file mode 100644
index 0000000..16de708
--- /dev/null
+++ b/src/tint/transform/remove_phonies.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_REMOVE_PHONIES_H_
+#define SRC_TINT_TRANSFORM_REMOVE_PHONIES_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// RemovePhonies is a Transform that removes all phony-assignment statements,
+/// while preserving function call expressions in the RHS of the assignment that
+/// may have side-effects.
+class RemovePhonies : public Castable<RemovePhonies, Transform> {
+ public:
+  /// Constructor
+  RemovePhonies();
+
+  /// Destructor
+  ~RemovePhonies() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_REMOVE_PHONIES_H_
diff --git a/src/tint/transform/remove_phonies_test.cc b/src/tint/transform/remove_phonies_test.cc
new file mode 100644
index 0000000..15ae60b
--- /dev/null
+++ b/src/tint/transform/remove_phonies_test.cc
@@ -0,0 +1,431 @@
+// 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/transform/remove_phonies.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using RemovePhoniesTest = TransformTest;
+
+TEST_F(RemovePhoniesTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<RemovePhonies>(src));
+}
+
+TEST_F(RemovePhoniesTest, ShouldRunHasPhony) {
+  auto* src = R"(
+fn f() {
+  _ = 1;
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<RemovePhonies>(src));
+}
+
+TEST_F(RemovePhoniesTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, NoSideEffects) {
+  auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn f() {
+  var v : i32;
+  _ = &v;
+  _ = 1;
+  _ = 1 + 2;
+  _ = t;
+  _ = u32(3.0);
+  _ = f32(i32(4u));
+  _ = vec2<f32>(5.0);
+  _ = vec3<i32>(6, 7, 8);
+  _ = mat2x2<f32>(9.0, 10.0, 11.0, 12.0);
+}
+)";
+
+  auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn f() {
+  var v : i32;
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, SingleSideEffects) {
+  auto* src = R"(
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn f() {
+  _ = neg(1);
+  _ = add(2, 3);
+  _ = add(neg(4), neg(5));
+  _ = u32(neg(6));
+  _ = f32(add(7, 8));
+  _ = vec2<f32>(f32(neg(9)));
+  _ = vec3<i32>(1, neg(10), 3);
+  _ = mat2x2<f32>(1.0, f32(add(11, 12)), 3.0, 4.0);
+}
+)";
+
+  auto* expect = R"(
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn f() {
+  neg(1);
+  add(2, 3);
+  add(neg(4), neg(5));
+  neg(6);
+  add(7, 8);
+  neg(9);
+  neg(10);
+  add(11, 12);
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, SingleSideEffects_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  _ = neg(1);
+  _ = add(2, 3);
+  _ = add(neg(4), neg(5));
+  _ = u32(neg(6));
+  _ = f32(add(7, 8));
+  _ = vec2<f32>(f32(neg(9)));
+  _ = vec3<i32>(1, neg(10), 3);
+  _ = mat2x2<f32>(1.0, f32(add(11, 12)), 3.0, 4.0);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  neg(1);
+  add(2, 3);
+  add(neg(4), neg(5));
+  neg(6);
+  add(7, 8);
+  neg(9);
+  neg(10);
+  add(11, 12);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, MultipleSideEffects) {
+  auto* src = R"(
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn xor(a : u32, b : u32) -> u32 {
+  return (a ^ b);
+}
+
+fn f() {
+  _ = (1 + add(2 + add(3, 4), 5)) * add(6, 7) * neg(8);
+  _ = add(9, neg(10)) + neg(11);
+  _ = xor(12u, 13u) + xor(14u, 15u);
+  _ = neg(16) / neg(17) + add(18, 19);
+}
+)";
+
+  auto* expect = R"(
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn xor(a : u32, b : u32) -> u32 {
+  return (a ^ b);
+}
+
+fn phony_sink(p0 : i32, p1 : i32, p2 : i32) {
+}
+
+fn phony_sink_1(p0 : i32, p1 : i32) {
+}
+
+fn phony_sink_2(p0 : u32, p1 : u32) {
+}
+
+fn f() {
+  phony_sink(add((2 + add(3, 4)), 5), add(6, 7), neg(8));
+  phony_sink_1(add(9, neg(10)), neg(11));
+  phony_sink_2(xor(12u, 13u), xor(14u, 15u));
+  phony_sink(neg(16), neg(17), add(18, 19));
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, MultipleSideEffects_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  _ = (1 + add(2 + add(3, 4), 5)) * add(6, 7) * neg(8);
+  _ = add(9, neg(10)) + neg(11);
+  _ = xor(12u, 13u) + xor(14u, 15u);
+  _ = neg(16) / neg(17) + add(18, 19);
+}
+
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn xor(a : u32, b : u32) -> u32 {
+  return (a ^ b);
+}
+)";
+
+  auto* expect = R"(
+fn phony_sink(p0 : i32, p1 : i32, p2 : i32) {
+}
+
+fn phony_sink_1(p0 : i32, p1 : i32) {
+}
+
+fn phony_sink_2(p0 : u32, p1 : u32) {
+}
+
+fn f() {
+  phony_sink(add((2 + add(3, 4)), 5), add(6, 7), neg(8));
+  phony_sink_1(add(9, neg(10)), neg(11));
+  phony_sink_2(xor(12u, 13u), xor(14u, 15u));
+  phony_sink(neg(16), neg(17), add(18, 19));
+}
+
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn xor(a : u32, b : u32) -> u32 {
+  return (a ^ b);
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, ForLoop) {
+  auto* src = R"(
+struct S {
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn x() -> i32 {
+  return 0;
+}
+
+fn y() -> i32 {
+  return 0;
+}
+
+fn z() -> i32 {
+  return 0;
+}
+
+fn f() {
+  for (_ = &s.arr; ;_ = &s.arr) {
+    break;
+  }
+  for (_ = x(); ;_ = y() + z()) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn x() -> i32 {
+  return 0;
+}
+
+fn y() -> i32 {
+  return 0;
+}
+
+fn z() -> i32 {
+  return 0;
+}
+
+fn phony_sink(p0 : i32, p1 : i32) {
+}
+
+fn f() {
+  for(; ; ) {
+    break;
+  }
+  for(x(); ; phony_sink(y(), z())) {
+    break;
+  }
+}
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemovePhoniesTest, ForLoop_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  for (_ = &s.arr; ;_ = &s.arr) {
+    break;
+  }
+  for (_ = x(); ;_ = y() + z()) {
+    break;
+  }
+}
+
+fn x() -> i32 {
+  return 0;
+}
+
+fn y() -> i32 {
+  return 0;
+}
+
+fn z() -> i32 {
+  return 0;
+}
+
+struct S {
+  arr : array<i32>;
+};
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+)";
+
+  auto* expect = R"(
+fn phony_sink(p0 : i32, p1 : i32) {
+}
+
+fn f() {
+  for(; ; ) {
+    break;
+  }
+  for(x(); ; phony_sink(y(), z())) {
+    break;
+  }
+}
+
+fn x() -> i32 {
+  return 0;
+}
+
+fn y() -> i32 {
+  return 0;
+}
+
+fn z() -> i32 {
+  return 0;
+}
+
+struct S {
+  arr : array<i32>;
+}
+
+@group(0) @binding(0) var<storage, read_write> s : S;
+)";
+
+  auto got = Run<RemovePhonies>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/remove_unreachable_statements.cc b/src/tint/transform/remove_unreachable_statements.cc
new file mode 100644
index 0000000..fd56ef0
--- /dev/null
+++ b/src/tint/transform/remove_unreachable_statements.cc
@@ -0,0 +1,67 @@
+// 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/transform/remove_unreachable_statements.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::RemoveUnreachableStatements);
+
+namespace tint {
+namespace transform {
+
+RemoveUnreachableStatements::RemoveUnreachableStatements() = default;
+
+RemoveUnreachableStatements::~RemoveUnreachableStatements() = default;
+
+bool RemoveUnreachableStatements::ShouldRun(const Program* program,
+                                            const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* stmt = program->Sem().Get<sem::Statement>(node)) {
+      if (!stmt->IsReachable()) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void RemoveUnreachableStatements::Run(CloneContext& ctx,
+                                      const DataMap&,
+                                      DataMap&) const {
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* stmt = ctx.src->Sem().Get<sem::Statement>(node)) {
+      if (!stmt->IsReachable()) {
+        RemoveStatement(ctx, stmt->Declaration());
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/remove_unreachable_statements.h b/src/tint/transform/remove_unreachable_statements.h
new file mode 100644
index 0000000..dfc9f15
--- /dev/null
+++ b/src/tint/transform/remove_unreachable_statements.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
+#define SRC_TINT_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// RemoveUnreachableStatements is a Transform that removes all statements
+/// marked as unreachable.
+class RemoveUnreachableStatements
+    : public Castable<RemoveUnreachableStatements, Transform> {
+ public:
+  /// Constructor
+  RemoveUnreachableStatements();
+
+  /// Destructor
+  ~RemoveUnreachableStatements() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
diff --git a/src/tint/transform/remove_unreachable_statements_test.cc b/src/tint/transform/remove_unreachable_statements_test.cc
new file mode 100644
index 0000000..4e5670f
--- /dev/null
+++ b/src/tint/transform/remove_unreachable_statements_test.cc
@@ -0,0 +1,571 @@
+// 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/transform/remove_unreachable_statements.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using RemoveUnreachableStatementsTest = TransformTest;
+
+TEST_F(RemoveUnreachableStatementsTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<RemoveUnreachableStatements>(src));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, ShouldRunHasNoUnreachable) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+    var x = 1;
+  }
+}
+)";
+
+  EXPECT_FALSE(ShouldRun<RemoveUnreachableStatements>(src));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, ShouldRunHasUnreachable) {
+  auto* src = R"(
+fn f() {
+  return;
+  if (true) {
+    var x = 1;
+  }
+}
+)";
+
+  EXPECT_TRUE(ShouldRun<RemoveUnreachableStatements>(src));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, Return) {
+  auto* src = R"(
+fn f() {
+  return;
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  return;
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, NestedReturn) {
+  auto* src = R"(
+fn f() {
+  {
+    {
+      return;
+    }
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    {
+      return;
+    }
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, Discard) {
+  auto* src = R"(
+fn f() {
+  discard;
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  discard;
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, NestedDiscard) {
+  auto* src = R"(
+fn f() {
+  {
+    {
+      discard;
+    }
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    {
+      discard;
+    }
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, CallToFuncWithDiscard) {
+  auto* src = R"(
+fn DISCARD() {
+  discard;
+}
+
+fn f() {
+  DISCARD();
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn DISCARD() {
+  discard;
+}
+
+fn f() {
+  DISCARD();
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, CallToFuncWithIfDiscard) {
+  auto* src = R"(
+fn DISCARD() {
+  if (true) {
+    discard;
+  }
+}
+
+fn f() {
+  DISCARD();
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfDiscardElseDiscard) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+    discard;
+  } else {
+    discard;
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  if (true) {
+    discard;
+  } else {
+    discard;
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfDiscardElseReturn) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+    discard;
+  } else {
+    return;
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  if (true) {
+    discard;
+  } else {
+    return;
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfDiscard) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+    discard;
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfReturn) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+    return;
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfElseDiscard) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+  } else {
+    discard;
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, IfElseReturn) {
+  auto* src = R"(
+fn f() {
+  if (true) {
+  } else {
+    return;
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, LoopWithDiscard) {
+  auto* src = R"(
+fn f() {
+  loop {
+    var a = 1;
+    discard;
+
+    continuing {
+      var b = 2;
+    }
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    var a = 1;
+    discard;
+
+    continuing {
+      var b = 2;
+    }
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, LoopWithConditionalBreak) {
+  auto* src = R"(
+fn f() {
+  loop {
+    var a = 1;
+    if (true) {
+      break;
+    }
+
+    continuing {
+      var b = 2;
+    }
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, LoopWithConditionalBreakInContinuing) {
+  auto* src = R"(
+fn f() {
+  loop {
+
+    continuing {
+      if (true) {
+        break;
+      }
+    }
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, SwitchDefaultDiscard) {
+  auto* src = R"(
+fn f() {
+  switch(1) {
+    default: {
+      discard;
+    }
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  switch(1) {
+    default: {
+      discard;
+    }
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, SwitchCaseReturnDefaultDiscard) {
+  auto* src = R"(
+fn f() {
+  switch(1) {
+    case 0: {
+      return;
+    }
+    default: {
+      discard;
+    }
+  }
+  var remove_me = 1;
+  if (true) {
+    var remove_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  switch(1) {
+    case 0: {
+      return;
+    }
+    default: {
+      discard;
+    }
+  }
+}
+)";
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, SwitchCaseBreakDefaultDiscard) {
+  auto* src = R"(
+fn f() {
+  switch(1) {
+    case 0: {
+      break;
+    }
+    default: {
+      discard;
+    }
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RemoveUnreachableStatementsTest, SwitchCaseReturnDefaultBreak) {
+  auto* src = R"(
+fn f() {
+  switch(1) {
+    case 0: {
+      return;
+    }
+    default: {
+      break;
+    }
+  }
+  var preserve_me = 1;
+  if (true) {
+    var preserve_me_too = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<RemoveUnreachableStatements>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/renamer.cc b/src/tint/transform/renamer.cc
new file mode 100644
index 0000000..9627911
--- /dev/null
+++ b/src/tint/transform/renamer.cc
@@ -0,0 +1,1366 @@
+// 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/transform/renamer.h"
+
+#include <memory>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/text/unicode.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer::Data);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer::Config);
+
+namespace tint::transform {
+
+namespace {
+
+// This list is used for a binary search and must be kept in sorted order.
+const char* kReservedKeywordsGLSL[] = {
+    "abs",
+    "acos",
+    "acosh",
+    "active",
+    "all",
+    "any",
+    "asin",
+    "asinh",
+    "asm",
+    "atan",
+    "atanh",
+    "atomicAdd",
+    "atomicAnd",
+    "atomicCompSwap",
+    "atomicCounter",
+    "atomicCounterDecrement",
+    "atomicCounterIncrement",
+    "atomicExchange",
+    "atomicMax",
+    "atomicMin",
+    "atomicOr",
+    "atomicXor",
+    "atomic_uint",
+    "attribute",
+    "barrier",
+    "bitCount",
+    "bitfieldExtract",
+    "bitfieldInsert",
+    "bitfieldReverse",
+    "bool",
+    "break",
+    "buffer",
+    "bvec2",
+    "bvec3",
+    "bvec4",
+    "case",
+    "cast",
+    "ceil",
+    "centroid",
+    "clamp",
+    "class",
+    "coherent",
+    "common",
+    "const",
+    "continue",
+    "cos",
+    "cosh",
+    "cross",
+    "dFdx",
+    "dFdy",
+    "default",
+    "degrees",
+    "determinant",
+    "discard",
+    "distance",
+    "dmat2",
+    "dmat2x2",
+    "dmat2x3",
+    "dmat2x4",
+    "dmat3",
+    "dmat3x2",
+    "dmat3x3",
+    "dmat3x4",
+    "dmat4",
+    "dmat4x2",
+    "dmat4x3",
+    "dmat4x4",
+    "do",
+    "dot",
+    "double",
+    "dvec2",
+    "dvec3",
+    "dvec4",
+    "else",
+    "enum",
+    "equal",
+    "exp",
+    "exp2",
+    "extern",
+    "external",
+    "faceforward",
+    "false",
+    "filter",
+    "findLSB",
+    "findMSB",
+    "fixed",
+    "flat",
+    "float",
+    "floatBitsToInt",
+    "floatBitsToUint",
+    "floor",
+    "for",
+    "fract",
+    "frexp",
+    "fvec2",
+    "fvec3",
+    "fvec4",
+    "fwidth",
+    "gl_BaseInstance",
+    "gl_BaseVertex",
+    "gl_ClipDistance",
+    "gl_DepthRangeParameters",
+    "gl_DrawID",
+    "gl_FragCoord",
+    "gl_FragDepth",
+    "gl_FrontFacing",
+    "gl_GlobalInvocationID",
+    "gl_InstanceID",
+    "gl_LocalInvocationID",
+    "gl_LocalInvocationIndex",
+    "gl_NumSamples",
+    "gl_NumWorkGroups",
+    "gl_PerVertex",
+    "gl_PointCoord",
+    "gl_PointSize",
+    "gl_Position",
+    "gl_PrimitiveID",
+    "gl_SampleID",
+    "gl_SampleMask",
+    "gl_SampleMaskIn",
+    "gl_SamplePosition",
+    "gl_VertexID",
+    "gl_WorkGroupID",
+    "gl_WorkGroupSize",
+    "goto",
+    "greaterThan",
+    "greaterThanEqual",
+    "groupMemoryBarrier",
+    "half",
+    "highp",
+    "hvec2",
+    "hvec3",
+    "hvec4",
+    "if",
+    "iimage1D",
+    "iimage1DArray",
+    "iimage2D",
+    "iimage2DArray",
+    "iimage2DMS",
+    "iimage2DMSArray",
+    "iimage2DRect",
+    "iimage3D",
+    "iimageBuffer",
+    "iimageCube",
+    "iimageCubeArray",
+    "image1D",
+    "image1DArray",
+    "image2D",
+    "image2DArray",
+    "image2DMS",
+    "image2DMSArray",
+    "image2DRect",
+    "image3D",
+    "imageBuffer",
+    "imageCube",
+    "imageCubeArray",
+    "imageLoad",
+    "imageSize",
+    "imageStore",
+    "imulExtended",
+    "in",
+    "inline",
+    "inout",
+    "input",
+    "int",
+    "intBitsToFloat",
+    "interface",
+    "invariant",
+    "inverse",
+    "inversesqrt",
+    "isampler1D",
+    "isampler1DArray",
+    "isampler2D",
+    "isampler2DArray",
+    "isampler2DMS",
+    "isampler2DMSArray",
+    "isampler2DRect",
+    "isampler3D",
+    "isamplerBuffer",
+    "isamplerCube",
+    "isamplerCubeArray",
+    "isinf",
+    "isnan",
+    "ivec2",
+    "ivec3",
+    "ivec4",
+    "layout",
+    "ldexp",
+    "length",
+    "lessThan",
+    "lessThanEqual",
+    "log",
+    "log2",
+    "long",
+    "lowp",
+    "main",
+    "mat2",
+    "mat2x2",
+    "mat2x3",
+    "mat2x4",
+    "mat3",
+    "mat3x2",
+    "mat3x3",
+    "mat3x4",
+    "mat4",
+    "mat4x2",
+    "mat4x3",
+    "mat4x4",
+    "matrixCompMult",
+    "max",
+    "mediump",
+    "memoryBarrier",
+    "memoryBarrierAtomicCounter",
+    "memoryBarrierBuffer",
+    "memoryBarrierImage",
+    "memoryBarrierShared",
+    "min",
+    "mix",
+    "mod",
+    "modf",
+    "namespace",
+    "noinline",
+    "noperspective",
+    "normalize",
+    "not",
+    "notEqual",
+    "out",
+    "outerProduct",
+    "output",
+    "packHalf2x16",
+    "packSnorm2x16",
+    "packSnorm4x8",
+    "packUnorm2x16",
+    "packUnorm4x8",
+    "partition",
+    "patch",
+    "pow",
+    "precise",
+    "precision",
+    "public",
+    "radians",
+    "readonly",
+    "reflect",
+    "refract",
+    "resource",
+    "restrict",
+    "return",
+    "round",
+    "roundEven",
+    "sample",
+    "sampler1D",
+    "sampler1DArray",
+    "sampler1DArrayShadow",
+    "sampler1DShadow",
+    "sampler2D",
+    "sampler2DArray",
+    "sampler2DArrayShadow",
+    "sampler2DMS",
+    "sampler2DMSArray",
+    "sampler2DRect",
+    "sampler2DRectShadow",
+    "sampler2DShadow",
+    "sampler3D",
+    "sampler3DRect",
+    "samplerBuffer",
+    "samplerCube",
+    "samplerCubeArray",
+    "samplerCubeArrayShadow",
+    "samplerCubeShadow",
+    "shared",
+    "short",
+    "sign",
+    "sin",
+    "sinh",
+    "sizeof",
+    "smooth",
+    "smoothstep",
+    "sqrt",
+    "static",
+    "step",
+    "struct",
+    "subroutine",
+    "superp",
+    "switch",
+    "tan",
+    "tanh",
+    "template",
+    "texelFetch",
+    "texelFetchOffset",
+    "texture",
+    "textureGather",
+    "textureGatherOffset",
+    "textureGrad",
+    "textureGradOffset",
+    "textureLod",
+    "textureLodOffset",
+    "textureOffset",
+    "textureProj",
+    "textureProjGrad",
+    "textureProjGradOffset",
+    "textureProjLod",
+    "textureProjLodOffset",
+    "textureProjOffset",
+    "textureSize",
+    "this",
+    "transpose",
+    "true",
+    "trunc",
+    "typedef",
+    "uaddCarry",
+    "uimage1D",
+    "uimage1DArray",
+    "uimage2D",
+    "uimage2DArray",
+    "uimage2DMS",
+    "uimage2DMSArray",
+    "uimage2DRect",
+    "uimage3D",
+    "uimageBuffer",
+    "uimageCube",
+    "uimageCubeArray",
+    "uint",
+    "uintBitsToFloat",
+    "umulExtended",
+    "uniform",
+    "union",
+    "unpackHalf2x16",
+    "unpackSnorm2x16",
+    "unpackSnorm4x8",
+    "unpackUnorm2x16",
+    "unpackUnorm4x8",
+    "unsigned",
+    "usampler1D",
+    "usampler1DArray",
+    "usampler2D",
+    "usampler2DArray",
+    "usampler2DMS",
+    "usampler2DMSArray",
+    "usampler2DRect",
+    "usampler3D",
+    "usamplerBuffer",
+    "usamplerCube",
+    "usamplerCubeArray",
+    "using",
+    "usubBorrow",
+    "uvec2",
+    "uvec3",
+    "uvec4",
+    "varying",
+    "vec2",
+    "vec3",
+    "vec4",
+    "void",
+    "volatile",
+    "while",
+    "writeonly",
+};
+
+// This list is used for a binary search and must be kept in sorted order.
+const char* kReservedKeywordsHLSL[] = {
+    "AddressU",
+    "AddressV",
+    "AddressW",
+    "AllMemoryBarrier",
+    "AllMemoryBarrierWithGroupSync",
+    "AppendStructuredBuffer",
+    "BINORMAL",
+    "BLENDINDICES",
+    "BLENDWEIGHT",
+    "BlendState",
+    "BorderColor",
+    "Buffer",
+    "ByteAddressBuffer",
+    "COLOR",
+    "CheckAccessFullyMapped",
+    "ComparisonFunc",
+    "CompileShader",
+    "ComputeShader",
+    "ConsumeStructuredBuffer",
+    "D3DCOLORtoUBYTE4",
+    "DEPTH",
+    "DepthStencilState",
+    "DepthStencilView",
+    "DeviceMemoryBarrier",
+    "DeviceMemroyBarrierWithGroupSync",
+    "DomainShader",
+    "EvaluateAttributeAtCentroid",
+    "EvaluateAttributeAtSample",
+    "EvaluateAttributeSnapped",
+    "FOG",
+    "Filter",
+    "GeometryShader",
+    "GetRenderTargetSampleCount",
+    "GetRenderTargetSamplePosition",
+    "GroupMemoryBarrier",
+    "GroupMemroyBarrierWithGroupSync",
+    "Hullshader",
+    "InputPatch",
+    "InterlockedAdd",
+    "InterlockedAnd",
+    "InterlockedCompareExchange",
+    "InterlockedCompareStore",
+    "InterlockedExchange",
+    "InterlockedMax",
+    "InterlockedMin",
+    "InterlockedOr",
+    "InterlockedXor",
+    "LineStream",
+    "MaxAnisotropy",
+    "MaxLOD",
+    "MinLOD",
+    "MipLODBias",
+    "NORMAL",
+    "NULL",
+    "Normal",
+    "OutputPatch",
+    "POSITION",
+    "POSITIONT",
+    "PSIZE",
+    "PixelShader",
+    "PointStream",
+    "Process2DQuadTessFactorsAvg",
+    "Process2DQuadTessFactorsMax",
+    "Process2DQuadTessFactorsMin",
+    "ProcessIsolineTessFactors",
+    "ProcessQuadTessFactorsAvg",
+    "ProcessQuadTessFactorsMax",
+    "ProcessQuadTessFactorsMin",
+    "ProcessTriTessFactorsAvg",
+    "ProcessTriTessFactorsMax",
+    "ProcessTriTessFactorsMin",
+    "RWBuffer",
+    "RWByteAddressBuffer",
+    "RWStructuredBuffer",
+    "RWTexture1D",
+    "RWTexture1DArray",
+    "RWTexture2D",
+    "RWTexture2DArray",
+    "RWTexture3D",
+    "RasterizerState",
+    "RenderTargetView",
+    "SV_ClipDistance",
+    "SV_Coverage",
+    "SV_CullDistance",
+    "SV_Depth",
+    "SV_DepthGreaterEqual",
+    "SV_DepthLessEqual",
+    "SV_DispatchThreadID",
+    "SV_DomainLocation",
+    "SV_GSInstanceID",
+    "SV_GroupID",
+    "SV_GroupIndex",
+    "SV_GroupThreadID",
+    "SV_InnerCoverage",
+    "SV_InsideTessFactor",
+    "SV_InstanceID",
+    "SV_IsFrontFace",
+    "SV_OutputControlPointID",
+    "SV_Position",
+    "SV_PrimitiveID",
+    "SV_RenderTargetArrayIndex",
+    "SV_SampleIndex",
+    "SV_StencilRef",
+    "SV_Target",
+    "SV_TessFactor",
+    "SV_VertexArrayIndex",
+    "SV_VertexID",
+    "Sampler",
+    "Sampler1D",
+    "Sampler2D",
+    "Sampler3D",
+    "SamplerCUBE",
+    "SamplerComparisonState",
+    "SamplerState",
+    "StructuredBuffer",
+    "TANGENT",
+    "TESSFACTOR",
+    "TEXCOORD",
+    "Texcoord",
+    "Texture",
+    "Texture1D",
+    "Texture1DArray",
+    "Texture2D",
+    "Texture2DArray",
+    "Texture2DMS",
+    "Texture2DMSArray",
+    "Texture3D",
+    "TextureCube",
+    "TextureCubeArray",
+    "TriangleStream",
+    "VFACE",
+    "VPOS",
+    "VertexShader",
+    "abort",
+    "allow_uav_condition",
+    "asdouble",
+    "asfloat",
+    "asint",
+    "asm",
+    "asm_fragment",
+    "asuint",
+    "auto",
+    "bool",
+    "bool1",
+    "bool1x1",
+    "bool1x2",
+    "bool1x3",
+    "bool1x4",
+    "bool2",
+    "bool2x1",
+    "bool2x2",
+    "bool2x3",
+    "bool2x4",
+    "bool3",
+    "bool3x1",
+    "bool3x2",
+    "bool3x3",
+    "bool3x4",
+    "bool4",
+    "bool4x1",
+    "bool4x2",
+    "bool4x3",
+    "bool4x4",
+    "branch",
+    "break",
+    "call",
+    "case",
+    "catch",
+    "cbuffer",
+    "centroid",
+    "char",
+    "class",
+    "clip",
+    "column_major",
+    "compile",
+    "compile_fragment",
+    "const",
+    "const_cast",
+    "continue",
+    "countbits",
+    "ddx",
+    "ddx_coarse",
+    "ddx_fine",
+    "ddy",
+    "ddy_coarse",
+    "ddy_fine",
+    "default",
+    "degrees",
+    "delete",
+    "discard",
+    "do",
+    "double",
+    "double1",
+    "double1x1",
+    "double1x2",
+    "double1x3",
+    "double1x4",
+    "double2",
+    "double2x1",
+    "double2x2",
+    "double2x3",
+    "double2x4",
+    "double3",
+    "double3x1",
+    "double3x2",
+    "double3x3",
+    "double3x4",
+    "double4",
+    "double4x1",
+    "double4x2",
+    "double4x3",
+    "double4x4",
+    "dst",
+    "dword",
+    "dword1",
+    "dword1x1",
+    "dword1x2",
+    "dword1x3",
+    "dword1x4",
+    "dword2",
+    "dword2x1",
+    "dword2x2",
+    "dword2x3",
+    "dword2x4",
+    "dword3",
+    "dword3x1",
+    "dword3x2",
+    "dword3x3",
+    "dword3x4",
+    "dword4",
+    "dword4x1",
+    "dword4x2",
+    "dword4x3",
+    "dword4x4",
+    "dynamic_cast",
+    "else",
+    "enum",
+    "errorf",
+    "explicit",
+    "export",
+    "extern",
+    "f16to32",
+    "f32tof16",
+    "false",
+    "fastopt",
+    "firstbithigh",
+    "firstbitlow",
+    "flatten",
+    "float",
+    "float1",
+    "float1x1",
+    "float1x2",
+    "float1x3",
+    "float1x4",
+    "float2",
+    "float2x1",
+    "float2x2",
+    "float2x3",
+    "float2x4",
+    "float3",
+    "float3x1",
+    "float3x2",
+    "float3x3",
+    "float3x4",
+    "float4",
+    "float4x1",
+    "float4x2",
+    "float4x3",
+    "float4x4",
+    "fmod",
+    "for",
+    "forcecase",
+    "frac",
+    "friend",
+    "fxgroup",
+    "goto",
+    "groupshared",
+    "half",
+    "half1",
+    "half1x1",
+    "half1x2",
+    "half1x3",
+    "half1x4",
+    "half2",
+    "half2x1",
+    "half2x2",
+    "half2x3",
+    "half2x4",
+    "half3",
+    "half3x1",
+    "half3x2",
+    "half3x3",
+    "half3x4",
+    "half4",
+    "half4x1",
+    "half4x2",
+    "half4x3",
+    "half4x4",
+    "if",
+    "in",
+    "inline",
+    "inout",
+    "int",
+    "int1",
+    "int1x1",
+    "int1x2",
+    "int1x3",
+    "int1x4",
+    "int2",
+    "int2x1",
+    "int2x2",
+    "int2x3",
+    "int2x4",
+    "int3",
+    "int3x1",
+    "int3x2",
+    "int3x3",
+    "int3x4",
+    "int4",
+    "int4x1",
+    "int4x2",
+    "int4x3",
+    "int4x4",
+    "interface",
+    "isfinite",
+    "isinf",
+    "isnan",
+    "lerp",
+    "line",
+    "lineadj",
+    "linear",
+    "lit",
+    "log10",
+    "long",
+    "loop",
+    "mad",
+    "matrix",
+    "min10float",
+    "min10float1",
+    "min10float1x1",
+    "min10float1x2",
+    "min10float1x3",
+    "min10float1x4",
+    "min10float2",
+    "min10float2x1",
+    "min10float2x2",
+    "min10float2x3",
+    "min10float2x4",
+    "min10float3",
+    "min10float3x1",
+    "min10float3x2",
+    "min10float3x3",
+    "min10float3x4",
+    "min10float4",
+    "min10float4x1",
+    "min10float4x2",
+    "min10float4x3",
+    "min10float4x4",
+    "min12int",
+    "min12int1",
+    "min12int1x1",
+    "min12int1x2",
+    "min12int1x3",
+    "min12int1x4",
+    "min12int2",
+    "min12int2x1",
+    "min12int2x2",
+    "min12int2x3",
+    "min12int2x4",
+    "min12int3",
+    "min12int3x1",
+    "min12int3x2",
+    "min12int3x3",
+    "min12int3x4",
+    "min12int4",
+    "min12int4x1",
+    "min12int4x2",
+    "min12int4x3",
+    "min12int4x4",
+    "min16float",
+    "min16float1",
+    "min16float1x1",
+    "min16float1x2",
+    "min16float1x3",
+    "min16float1x4",
+    "min16float2",
+    "min16float2x1",
+    "min16float2x2",
+    "min16float2x3",
+    "min16float2x4",
+    "min16float3",
+    "min16float3x1",
+    "min16float3x2",
+    "min16float3x3",
+    "min16float3x4",
+    "min16float4",
+    "min16float4x1",
+    "min16float4x2",
+    "min16float4x3",
+    "min16float4x4",
+    "min16int",
+    "min16int1",
+    "min16int1x1",
+    "min16int1x2",
+    "min16int1x3",
+    "min16int1x4",
+    "min16int2",
+    "min16int2x1",
+    "min16int2x2",
+    "min16int2x3",
+    "min16int2x4",
+    "min16int3",
+    "min16int3x1",
+    "min16int3x2",
+    "min16int3x3",
+    "min16int3x4",
+    "min16int4",
+    "min16int4x1",
+    "min16int4x2",
+    "min16int4x3",
+    "min16int4x4",
+    "min16uint",
+    "min16uint1",
+    "min16uint1x1",
+    "min16uint1x2",
+    "min16uint1x3",
+    "min16uint1x4",
+    "min16uint2",
+    "min16uint2x1",
+    "min16uint2x2",
+    "min16uint2x3",
+    "min16uint2x4",
+    "min16uint3",
+    "min16uint3x1",
+    "min16uint3x2",
+    "min16uint3x3",
+    "min16uint3x4",
+    "min16uint4",
+    "min16uint4x1",
+    "min16uint4x2",
+    "min16uint4x3",
+    "min16uint4x4",
+    "msad4",
+    "mul",
+    "mutable",
+    "namespace",
+    "new",
+    "nointerpolation",
+    "noise",
+    "noperspective",
+    "numthreads",
+    "operator",
+    "out",
+    "packoffset",
+    "pass",
+    "pixelfragment",
+    "pixelshader",
+    "point",
+    "precise",
+    "printf",
+    "private",
+    "protected",
+    "public",
+    "radians",
+    "rcp",
+    "refract",
+    "register",
+    "reinterpret_cast",
+    "return",
+    "row_major",
+    "rsqrt",
+    "sample",
+    "sampler",
+    "sampler1D",
+    "sampler2D",
+    "sampler3D",
+    "samplerCUBE",
+    "sampler_state",
+    "saturate",
+    "shared",
+    "short",
+    "signed",
+    "sincos",
+    "sizeof",
+    "snorm",
+    "stateblock",
+    "stateblock_state",
+    "static",
+    "static_cast",
+    "string",
+    "struct",
+    "switch",
+    "tbuffer",
+    "technique",
+    "technique10",
+    "technique11",
+    "template",
+    "tex1D",
+    "tex1Dbias",
+    "tex1Dgrad",
+    "tex1Dlod",
+    "tex1Dproj",
+    "tex2D",
+    "tex2Dbias",
+    "tex2Dgrad",
+    "tex2Dlod",
+    "tex2Dproj",
+    "tex3D",
+    "tex3Dbias",
+    "tex3Dgrad",
+    "tex3Dlod",
+    "tex3Dproj",
+    "texCUBE",
+    "texCUBEbias",
+    "texCUBEgrad",
+    "texCUBElod",
+    "texCUBEproj",
+    "texture",
+    "texture1D",
+    "texture1DArray",
+    "texture2D",
+    "texture2DArray",
+    "texture2DMS",
+    "texture2DMSArray",
+    "texture3D",
+    "textureCube",
+    "textureCubeArray",
+    "this",
+    "throw",
+    "transpose",
+    "triangle",
+    "triangleadj",
+    "true",
+    "try",
+    "typedef",
+    "typename",
+    "uint",
+    "uint1",
+    "uint1x1",
+    "uint1x2",
+    "uint1x3",
+    "uint1x4",
+    "uint2",
+    "uint2x1",
+    "uint2x2",
+    "uint2x3",
+    "uint2x4",
+    "uint3",
+    "uint3x1",
+    "uint3x2",
+    "uint3x3",
+    "uint3x4",
+    "uint4",
+    "uint4x1",
+    "uint4x2",
+    "uint4x3",
+    "uint4x4",
+    "uniform",
+    "union",
+    "unorm",
+    "unroll",
+    "unsigned",
+    "using",
+    "vector",
+    "vertexfragment",
+    "vertexshader",
+    "virtual",
+    "void",
+    "volatile",
+    "while",
+};
+
+// This list is used for a binary search and must be kept in sorted order.
+const char* kReservedKeywordsMSL[] = {
+    "HUGE_VALF",
+    "HUGE_VALH",
+    "INFINITY",
+    "MAXFLOAT",
+    "MAXHALF",
+    "M_1_PI_F",
+    "M_1_PI_H",
+    "M_2_PI_F",
+    "M_2_PI_H",
+    "M_2_SQRTPI_F",
+    "M_2_SQRTPI_H",
+    "M_E_F",
+    "M_E_H",
+    "M_LN10_F",
+    "M_LN10_H",
+    "M_LN2_F",
+    "M_LN2_H",
+    "M_LOG10E_F",
+    "M_LOG10E_H",
+    "M_LOG2E_F",
+    "M_LOG2E_H",
+    "M_PI_2_F",
+    "M_PI_2_H",
+    "M_PI_4_F",
+    "M_PI_4_H",
+    "M_PI_F",
+    "M_PI_H",
+    "M_SQRT1_2_F",
+    "M_SQRT1_2_H",
+    "M_SQRT2_F",
+    "M_SQRT2_H",
+    "NAN",
+    "access",
+    "alignas",
+    "alignof",
+    "and",
+    "and_eq",
+    "array",
+    "array_ref",
+    "as_type",
+    "asm",
+    "atomic",
+    "atomic_bool",
+    "atomic_int",
+    "atomic_uint",
+    "auto",
+    "bitand",
+    "bitor",
+    "bool",
+    "bool2",
+    "bool3",
+    "bool4",
+    "break",
+    "buffer",
+    "case",
+    "catch",
+    "char",
+    "char16_t",
+    "char2",
+    "char3",
+    "char32_t",
+    "char4",
+    "class",
+    "compl",
+    "const",
+    "const_cast",
+    "const_reference",
+    "constant",
+    "constexpr",
+    "continue",
+    "decltype",
+    "default",
+    "delete",
+    "depth2d",
+    "depth2d_array",
+    "depth2d_ms",
+    "depth2d_ms_array",
+    "depthcube",
+    "depthcube_array",
+    "device",
+    "discard_fragment",
+    "do",
+    "double",
+    "dynamic_cast",
+    "else",
+    "enum",
+    "explicit",
+    "extern",
+    "false",
+    "final",
+    "float",
+    "float2",
+    "float2x2",
+    "float2x3",
+    "float2x4",
+    "float3",
+    "float3x2",
+    "float3x3",
+    "float3x4",
+    "float4",
+    "float4x2",
+    "float4x3",
+    "float4x4",
+    "for",
+    "fragment",
+    "friend",
+    "goto",
+    "half",
+    "half2",
+    "half2x2",
+    "half2x3",
+    "half2x4",
+    "half3",
+    "half3x2",
+    "half3x3",
+    "half3x4",
+    "half4",
+    "half4x2",
+    "half4x3",
+    "half4x4",
+    "if",
+    "imageblock",
+    "infinity",
+    "inline",
+    "int",
+    "int16_t",
+    "int2",
+    "int3",
+    "int32_t",
+    "int4",
+    "int64_t",
+    "int8_t",
+    "kernel",
+    "long",
+    "long2",
+    "long3",
+    "long4",
+    "main",
+    "matrix",
+    "metal",
+    "mutable",
+    "namespace",
+    "new",
+    "noexcept",
+    "not",
+    "not_eq",
+    "nullptr",
+    "operator",
+    "or",
+    "or_eq",
+    "override",
+    "packed_bool2",
+    "packed_bool3",
+    "packed_bool4",
+    "packed_char2",
+    "packed_char3",
+    "packed_char4",
+    "packed_float2",
+    "packed_float3",
+    "packed_float4",
+    "packed_half2",
+    "packed_half3",
+    "packed_half4",
+    "packed_int2",
+    "packed_int3",
+    "packed_int4",
+    "packed_short2",
+    "packed_short3",
+    "packed_short4",
+    "packed_uchar2",
+    "packed_uchar3",
+    "packed_uchar4",
+    "packed_uint2",
+    "packed_uint3",
+    "packed_uint4",
+    "packed_ushort2",
+    "packed_ushort3",
+    "packed_ushort4",
+    "patch_control_point",
+    "private",
+    "protected",
+    "ptrdiff_t",
+    "public",
+    "r16snorm",
+    "r16unorm",
+    "r8unorm",
+    "reference",
+    "register",
+    "reinterpret_cast",
+    "return",
+    "rg11b10f",
+    "rg16snorm",
+    "rg16unorm",
+    "rg8snorm",
+    "rg8unorm",
+    "rgb10a2",
+    "rgb9e5",
+    "rgba16snorm",
+    "rgba16unorm",
+    "rgba8snorm",
+    "rgba8unorm",
+    "sampler",
+    "short",
+    "short2",
+    "short3",
+    "short4",
+    "signed",
+    "size_t",
+    "sizeof",
+    "srgba8unorm",
+    "static",
+    "static_assert",
+    "static_cast",
+    "struct",
+    "switch",
+    "template",
+    "texture",
+    "texture1d",
+    "texture1d_array",
+    "texture2d",
+    "texture2d_array",
+    "texture2d_ms",
+    "texture2d_ms_array",
+    "texture3d",
+    "texture_buffer",
+    "texturecube",
+    "texturecube_array",
+    "this",
+    "thread",
+    "thread_local",
+    "threadgroup",
+    "threadgroup_imageblock",
+    "throw",
+    "true",
+    "try",
+    "typedef",
+    "typeid",
+    "typename",
+    "uchar",
+    "uchar2",
+    "uchar3",
+    "uchar4",
+    "uint",
+    "uint16_t",
+    "uint2",
+    "uint3",
+    "uint32_t",
+    "uint4",
+    "uint64_t",
+    "uint8_t",
+    "ulong2",
+    "ulong3",
+    "ulong4",
+    "uniform",
+    "union",
+    "unsigned",
+    "ushort",
+    "ushort2",
+    "ushort3",
+    "ushort4",
+    "using",
+    "vec",
+    "vertex",
+    "virtual",
+    "void",
+    "volatile",
+    "wchar_t",
+    "while",
+    "xor",
+    "xor_eq",
+};
+
+}  // namespace
+
+Renamer::Data::Data(Remappings&& r) : remappings(std::move(r)) {}
+Renamer::Data::Data(const Data&) = default;
+Renamer::Data::~Data() = default;
+
+Renamer::Config::Config(Target t, bool pu) : target(t), preserve_unicode(pu) {}
+Renamer::Config::Config(const Config&) = default;
+Renamer::Config::~Config() = default;
+
+Renamer::Renamer() = default;
+Renamer::~Renamer() = default;
+
+Output Renamer::Run(const Program* in, const DataMap& inputs) const {
+  ProgramBuilder out;
+  // Disable auto-cloning of symbols, since we want to rename them.
+  CloneContext ctx(&out, in, false);
+
+  // Swizzles, builtin calls and builtin structure members need to keep their
+  // symbols preserved.
+  std::unordered_set<const ast::IdentifierExpression*> preserve;
+  for (auto* node : in->ASTNodes().Objects()) {
+    if (auto* member = node->As<ast::MemberAccessorExpression>()) {
+      auto* sem = in->Sem().Get(member);
+      if (!sem) {
+        TINT_ICE(Transform, out.Diagnostics())
+            << "MemberAccessorExpression has no semantic info";
+        continue;
+      }
+      if (sem->Is<sem::Swizzle>()) {
+        preserve.emplace(member->member);
+      } else if (auto* str_expr = in->Sem().Get(member->structure)) {
+        if (auto* ty = str_expr->Type()->UnwrapRef()->As<sem::Struct>()) {
+          if (ty->Declaration() == nullptr) {  // Builtin structure
+            preserve.emplace(member->member);
+          }
+        }
+      }
+    } else if (auto* call = node->As<ast::CallExpression>()) {
+      auto* sem = in->Sem().Get(call);
+      if (!sem) {
+        TINT_ICE(Transform, out.Diagnostics())
+            << "CallExpression has no semantic info";
+        continue;
+      }
+      if (sem->Target()->Is<sem::Builtin>()) {
+        preserve.emplace(call->target.name);
+      }
+    }
+  }
+
+  Data::Remappings remappings;
+
+  Target target = Target::kAll;
+  bool preserve_unicode = false;
+
+  if (auto* cfg = inputs.Get<Config>()) {
+    target = cfg->target;
+    preserve_unicode = cfg->preserve_unicode;
+  }
+
+  ctx.ReplaceAll([&](Symbol sym_in) {
+    auto name_in = ctx.src->Symbols().NameFor(sym_in);
+    if (preserve_unicode || text::utf8::IsASCII(name_in)) {
+      switch (target) {
+        case Target::kAll:
+          // Always rename.
+          break;
+        case Target::kGlslKeywords:
+          if (!std::binary_search(
+                  kReservedKeywordsGLSL,
+                  kReservedKeywordsGLSL +
+                      sizeof(kReservedKeywordsGLSL) / sizeof(const char*),
+                  name_in) &&
+              name_in.compare(0, 3, "gl_")) {
+            // No match, just reuse the original name.
+            return ctx.dst->Symbols().New(name_in);
+          }
+          break;
+        case Target::kHlslKeywords:
+          if (!std::binary_search(
+                  kReservedKeywordsHLSL,
+                  kReservedKeywordsHLSL +
+                      sizeof(kReservedKeywordsHLSL) / sizeof(const char*),
+                  name_in)) {
+            // No match, just reuse the original name.
+            return ctx.dst->Symbols().New(name_in);
+          }
+          break;
+        case Target::kMslKeywords:
+          if (!std::binary_search(
+                  kReservedKeywordsMSL,
+                  kReservedKeywordsMSL +
+                      sizeof(kReservedKeywordsMSL) / sizeof(const char*),
+                  name_in)) {
+            // No match, just reuse the original name.
+            return ctx.dst->Symbols().New(name_in);
+          }
+          break;
+      }
+    }
+
+    auto sym_out = ctx.dst->Sym();
+    remappings.emplace(name_in, ctx.dst->Symbols().NameFor(sym_out));
+    return sym_out;
+  });
+
+  ctx.ReplaceAll([&](const ast::IdentifierExpression* ident)
+                     -> const ast::IdentifierExpression* {
+    if (preserve.count(ident)) {
+      auto sym_in = ident->symbol;
+      auto str = in->Symbols().NameFor(sym_in);
+      auto sym_out = out.Symbols().Register(str);
+      return ctx.dst->create<ast::IdentifierExpression>(
+          ctx.Clone(ident->source), sym_out);
+    }
+    return nullptr;  // Clone ident. Uses the symbol remapping above.
+  });
+  ctx.Clone();
+
+  return Output(Program(std::move(out)),
+                std::make_unique<Data>(std::move(remappings)));
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/renamer.h b/src/tint/transform/renamer.h
new file mode 100644
index 0000000..ad37b0c
--- /dev/null
+++ b/src/tint/transform/renamer.h
@@ -0,0 +1,97 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_RENAMER_H_
+#define SRC_TINT_TRANSFORM_RENAMER_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// Renamer is a Transform that renames all the symbols in a program.
+class Renamer : public Castable<Renamer, Transform> {
+ public:
+  /// Data is outputted by the Renamer transform.
+  /// Data holds information about shader usage and constant buffer offsets.
+  struct Data : public Castable<Data, transform::Data> {
+    /// Remappings is a map of old symbol name to new symbol name
+    using Remappings = std::unordered_map<std::string, std::string>;
+
+    /// Constructor
+    /// @param remappings the symbol remappings
+    explicit Data(Remappings&& remappings);
+
+    /// Copy constructor
+    Data(const Data&);
+
+    /// Destructor
+    ~Data() override;
+
+    /// A map of old symbol name to new symbol name
+    const Remappings remappings;
+  };
+
+  /// Target is an enumerator of rename targets that can be used
+  enum class Target {
+    /// Rename every symbol.
+    kAll,
+    /// Only rename symbols that are reserved keywords in GLSL.
+    kGlslKeywords,
+    /// Only rename symbols that are reserved keywords in HLSL.
+    kHlslKeywords,
+    /// Only rename symbols that are reserved keywords in MSL.
+    kMslKeywords,
+  };
+
+  /// Optional configuration options for the transform.
+  /// If omitted, then the renamer will use Target::kAll.
+  struct Config : public Castable<Config, transform::Data> {
+    /// Constructor
+    /// @param tgt the targets to rename
+    /// @param keep_unicode if false, symbols with non-ascii code-points are
+    /// renamed
+    explicit Config(Target tgt, bool keep_unicode = false);
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// The targets to rename
+    Target const target = Target::kAll;
+
+    /// If false, symbols with non-ascii code-points are renamed.
+    bool preserve_unicode = false;
+  };
+
+  /// Constructor using a the configuration provided in the input Data
+  Renamer();
+
+  /// Destructor
+  ~Renamer() override;
+
+  /// Runs the transform on `program`, returning the transformation result.
+  /// @param program the source program to transform
+  /// @param data optional extra transform-specific input data
+  /// @returns the transformation result
+  Output Run(const Program* program, const DataMap& data = {}) const override;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_RENAMER_H_
diff --git a/src/tint/transform/renamer_test.cc b/src/tint/transform/renamer_test.cc
new file mode 100644
index 0000000..70d4322
--- /dev/null
+++ b/src/tint/transform/renamer_test.cc
@@ -0,0 +1,1463 @@
+// 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/transform/renamer.h"
+
+#include <memory>
+
+#include "gmock/gmock.h"
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+constexpr const char kUnicodeIdentifier[] =  // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
+    "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
+    "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
+    "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33";
+
+using ::testing::ContainerEq;
+
+using RenamerTest = TransformTest;
+
+TEST_F(RenamerTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_EQ(data->remappings.size(), 0u);
+}
+
+TEST_F(RenamerTest, BasicModuleVertexIndex) {
+  auto* src = R"(
+fn test(vert_idx : u32) -> u32 {
+  return vert_idx;
+}
+
+@stage(vertex)
+fn entry(@builtin(vertex_index) vert_idx : u32
+        ) -> @builtin(position) vec4<f32>  {
+  _ = test(vert_idx);
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+fn tint_symbol(tint_symbol_1 : u32) -> u32 {
+  return tint_symbol_1;
+}
+
+@stage(vertex)
+fn tint_symbol_2(@builtin(vertex_index) tint_symbol_1 : u32) -> @builtin(position) vec4<f32> {
+  _ = tint_symbol(tint_symbol_1);
+  return vec4<f32>();
+}
+)";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_NE(data, nullptr);
+  Renamer::Data::Remappings expected_remappings = {
+      {"vert_idx", "tint_symbol_1"},
+      {"test", "tint_symbol"},
+      {"entry", "tint_symbol_2"},
+  };
+  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
+TEST_F(RenamerTest, PreserveSwizzles) {
+  auto* src = R"(
+@stage(vertex)
+fn entry() -> @builtin(position) vec4<f32> {
+  var v : vec4<f32>;
+  var rgba : f32;
+  var xyzw : f32;
+  return v.zyxw + v.rgab;
+}
+)";
+
+  auto* expect = R"(
+@stage(vertex)
+fn tint_symbol() -> @builtin(position) vec4<f32> {
+  var tint_symbol_1 : vec4<f32>;
+  var tint_symbol_2 : f32;
+  var tint_symbol_3 : f32;
+  return (tint_symbol_1.zyxw + tint_symbol_1.rgab);
+}
+)";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_NE(data, nullptr);
+  Renamer::Data::Remappings expected_remappings = {
+      {"entry", "tint_symbol"},
+      {"v", "tint_symbol_1"},
+      {"rgba", "tint_symbol_2"},
+      {"xyzw", "tint_symbol_3"},
+  };
+  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
+TEST_F(RenamerTest, PreserveBuiltins) {
+  auto* src = R"(
+@stage(vertex)
+fn entry() -> @builtin(position) vec4<f32> {
+  var blah : vec4<f32>;
+  return abs(blah);
+}
+)";
+
+  auto* expect = R"(
+@stage(vertex)
+fn tint_symbol() -> @builtin(position) vec4<f32> {
+  var tint_symbol_1 : vec4<f32>;
+  return abs(tint_symbol_1);
+}
+)";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_NE(data, nullptr);
+  Renamer::Data::Remappings expected_remappings = {
+      {"entry", "tint_symbol"},
+      {"blah", "tint_symbol_1"},
+  };
+  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
+TEST_F(RenamerTest, PreserveBuiltinTypes) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn entry() {
+  var a = modf(1.0).whole;
+  var b = modf(1.0).fract;
+  var c = frexp(1.0).sig;
+  var d = frexp(1.0).exp;
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn tint_symbol() {
+  var tint_symbol_1 = modf(1.0).whole;
+  var tint_symbol_2 = modf(1.0).fract;
+  var tint_symbol_3 = frexp(1.0).sig;
+  var tint_symbol_4 = frexp(1.0).exp;
+}
+)";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_NE(data, nullptr);
+  Renamer::Data::Remappings expected_remappings = {
+      {"entry", "tint_symbol"}, {"a", "tint_symbol_1"}, {"b", "tint_symbol_2"},
+      {"c", "tint_symbol_3"},   {"d", "tint_symbol_4"},
+  };
+  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
+TEST_F(RenamerTest, PreserveUnicode) {
+  auto src = R"(
+@stage(fragment)
+fn frag_main() {
+  var )" + std::string(kUnicodeIdentifier) +
+             R"( : i32;
+}
+)";
+
+  auto expect = src;
+
+  DataMap inputs;
+  inputs.Add<Renamer::Config>(Renamer::Target::kMslKeywords,
+                              /* preserve_unicode */ true);
+  auto got = Run<Renamer>(src, inputs);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RenamerTest, AttemptSymbolCollision) {
+  auto* src = R"(
+@stage(vertex)
+fn entry() -> @builtin(position) vec4<f32> {
+  var tint_symbol : vec4<f32>;
+  var tint_symbol_2 : vec4<f32>;
+  var tint_symbol_4 : vec4<f32>;
+  return tint_symbol + tint_symbol_2 + tint_symbol_4;
+}
+)";
+
+  auto* expect = R"(
+@stage(vertex)
+fn tint_symbol() -> @builtin(position) vec4<f32> {
+  var tint_symbol_1 : vec4<f32>;
+  var tint_symbol_2 : vec4<f32>;
+  var tint_symbol_3 : vec4<f32>;
+  return ((tint_symbol_1 + tint_symbol_2) + tint_symbol_3);
+}
+)";
+
+  auto got = Run<Renamer>(src);
+
+  EXPECT_EQ(expect, str(got));
+
+  auto* data = got.data.Get<Renamer::Data>();
+
+  ASSERT_NE(data, nullptr);
+  Renamer::Data::Remappings expected_remappings = {
+      {"entry", "tint_symbol"},
+      {"tint_symbol", "tint_symbol_1"},
+      {"tint_symbol_2", "tint_symbol_2"},
+      {"tint_symbol_4", "tint_symbol_3"},
+  };
+  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
+using RenamerTestGlsl = TransformTestWithParam<std::string>;
+using RenamerTestHlsl = TransformTestWithParam<std::string>;
+using RenamerTestMsl = TransformTestWithParam<std::string>;
+
+TEST_P(RenamerTestGlsl, Keywords) {
+  auto keyword = GetParam();
+
+  auto src = R"(
+@stage(fragment)
+fn frag_main() {
+  var )" + keyword +
+             R"( : i32;
+}
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn frag_main() {
+  var tint_symbol : i32;
+}
+)";
+
+  DataMap inputs;
+  inputs.Add<Renamer::Config>(Renamer::Target::kGlslKeywords,
+                              /* preserve_unicode */ false);
+  auto got = Run<Renamer>(src, inputs);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RenamerTestHlsl, Keywords) {
+  auto keyword = GetParam();
+
+  auto src = R"(
+@stage(fragment)
+fn frag_main() {
+  var )" + keyword +
+             R"( : i32;
+}
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn frag_main() {
+  var tint_symbol : i32;
+}
+)";
+
+  DataMap inputs;
+  inputs.Add<Renamer::Config>(Renamer::Target::kHlslKeywords,
+                              /* preserve_unicode */ false);
+  auto got = Run<Renamer>(src, inputs);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RenamerTestMsl, Keywords) {
+  auto keyword = GetParam();
+
+  auto src = R"(
+@stage(fragment)
+fn frag_main() {
+  var )" + keyword +
+             R"( : i32;
+}
+)";
+
+  auto* expect = R"(
+@stage(fragment)
+fn frag_main() {
+  var tint_symbol : i32;
+}
+)";
+
+  DataMap inputs;
+  inputs.Add<Renamer::Config>(Renamer::Target::kMslKeywords,
+                              /* preserve_unicode */ false);
+  auto got = Run<Renamer>(src, inputs);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+INSTANTIATE_TEST_SUITE_P(RenamerTestGlsl,
+                         RenamerTestGlsl,
+                         testing::Values("active",
+                                         //    "asm",       // WGSL keyword
+                                         "atomic_uint",
+                                         "attribute",
+                                         //    "bool",      // WGSL keyword
+                                         //    "break",     // WGSL keyword
+                                         "buffer",
+                                         "bvec2",
+                                         "bvec3",
+                                         "bvec4",
+                                         //    "case",      // WGSL keyword
+                                         "cast",
+                                         "centroid",
+                                         "class",
+                                         "coherent",
+                                         "common",
+                                         //    "const",     // WGSL keyword
+                                         //    "continue",  // WGSL keyword
+                                         //    "default",   // WGSL keyword
+                                         //    "discard",   // WGSL keyword
+                                         "dmat2",
+                                         "dmat2x2",
+                                         "dmat2x3",
+                                         "dmat2x4",
+                                         "dmat3",
+                                         "dmat3x2",
+                                         "dmat3x3",
+                                         "dmat3x4",
+                                         "dmat4",
+                                         "dmat4x2",
+                                         "dmat4x3",
+                                         "dmat4x4",
+                                         //    "do",         // WGSL keyword
+                                         "double",
+                                         "dvec2",
+                                         "dvec3",
+                                         "dvec4",
+                                         //    "else"        // WGSL keyword
+                                         //    "enum",       // WGSL keyword
+                                         "extern",
+                                         "external",
+                                         //    "false",      // WGSL keyword
+                                         "filter",
+                                         "fixed",
+                                         "flat",
+                                         "float",
+                                         //    "for",        // WGSL keyword
+                                         "fvec2",
+                                         "fvec3",
+                                         "fvec4",
+                                         "gl_BaseInstance",
+                                         "gl_BaseVertex",
+                                         "gl_ClipDistance",
+                                         "gl_DepthRangeParameters",
+                                         "gl_DrawID",
+                                         "gl_FragCoord",
+                                         "gl_FragDepth",
+                                         "gl_FrontFacing",
+                                         "gl_GlobalInvocationID",
+                                         "gl_InstanceID",
+                                         "gl_LocalInvocationID",
+                                         "gl_LocalInvocationIndex",
+                                         "gl_NumSamples",
+                                         "gl_NumWorkGroups",
+                                         "gl_PerVertex",
+                                         "gl_PointCoord",
+                                         "gl_PointSize",
+                                         "gl_Position",
+                                         "gl_PrimitiveID",
+                                         "gl_SampleID",
+                                         "gl_SampleMask",
+                                         "gl_SampleMaskIn",
+                                         "gl_SamplePosition",
+                                         "gl_VertexID",
+                                         "gl_WorkGroupID",
+                                         "gl_WorkGroupSize",
+                                         "goto",
+                                         "half",
+                                         "highp",
+                                         "hvec2",
+                                         "hvec3",
+                                         "hvec4",
+                                         //    "if",         // WGSL keyword
+                                         "iimage1D",
+                                         "iimage1DArray",
+                                         "iimage2D",
+                                         "iimage2DArray",
+                                         "iimage2DMS",
+                                         "iimage2DMSArray",
+                                         "iimage2DRect",
+                                         "iimage3D",
+                                         "iimageBuffer",
+                                         "iimageCube",
+                                         "iimageCubeArray",
+                                         "image1D",
+                                         "image1DArray",
+                                         "image2D",
+                                         "image2DArray",
+                                         "image2DMS",
+                                         "image2DMSArray",
+                                         "image2DRect",
+                                         "image3D",
+                                         "imageBuffer",
+                                         "imageCube",
+                                         "imageCubeArray",
+                                         "in",
+                                         "inline",
+                                         "inout",
+                                         "input",
+                                         "int",
+                                         "interface",
+                                         "invariant",
+                                         "isampler1D",
+                                         "isampler1DArray",
+                                         "isampler2D",
+                                         "isampler2DArray",
+                                         "isampler2DMS",
+                                         "isampler2DMSArray",
+                                         "isampler2DRect",
+                                         "isampler3D",
+                                         "isamplerBuffer",
+                                         "isamplerCube",
+                                         "isamplerCubeArray",
+                                         "ivec2",
+                                         "ivec3",
+                                         "ivec4",
+                                         "layout",
+                                         "long",
+                                         "lowp",
+                                         //    "mat2x2",      // WGSL keyword
+                                         //    "mat2x3",      // WGSL keyword
+                                         //    "mat2x4",      // WGSL keyword
+                                         //    "mat2",
+                                         "mat3",
+                                         //    "mat3x2",      // WGSL keyword
+                                         //    "mat3x3",      // WGSL keyword
+                                         //    "mat3x4",      // WGSL keyword
+                                         "mat4",
+                                         //    "mat4x2",      // WGSL keyword
+                                         //    "mat4x3",      // WGSL keyword
+                                         //    "mat4x4",      // WGSL keyword
+                                         "mediump",
+                                         "namespace",
+                                         "noinline",
+                                         "noperspective",
+                                         "out",
+                                         "output",
+                                         "partition",
+                                         "patch",
+                                         "precise",
+                                         "precision",
+                                         "public",
+                                         "readonly",
+                                         "resource",
+                                         "restrict",
+                                         //    "return",     // WGSL keyword
+                                         "sample",
+                                         "sampler1D",
+                                         "sampler1DArray",
+                                         "sampler1DArrayShadow",
+                                         "sampler1DShadow",
+                                         "sampler2D",
+                                         "sampler2DArray",
+                                         "sampler2DArrayShadow",
+                                         "sampler2DMS",
+                                         "sampler2DMSArray",
+                                         "sampler2DRect",
+                                         "sampler2DRectShadow",
+                                         "sampler2DShadow",
+                                         "sampler3D",
+                                         "sampler3DRect",
+                                         "samplerBuffer",
+                                         "samplerCube",
+                                         "samplerCubeArray",
+                                         "samplerCubeArrayShadow",
+                                         "samplerCubeShadow",
+                                         "shared",
+                                         "short",
+                                         "sizeof",
+                                         "smooth",
+                                         "static",
+                                         //    "struct",     // WGSL keyword
+                                         "subroutine",
+                                         "superp",
+                                         //    "switch",     // WGSL keyword
+                                         "template",
+                                         "this",
+                                         //    "true",       // WGSL keyword
+                                         //    "typedef",    // WGSL keyword
+                                         "uimage1D",
+                                         "uimage1DArray",
+                                         "uimage2D",
+                                         "uimage2DArray",
+                                         "uimage2DMS",
+                                         "uimage2DMSArray",
+                                         "uimage2DRect",
+                                         "uimage3D",
+                                         "uimageBuffer",
+                                         "uimageCube",
+                                         "uimageCubeArray",
+                                         "uint",
+                                         //    "uniform",    // WGSL keyword
+                                         "union",
+                                         "unsigned",
+                                         "usampler1D",
+                                         "usampler1DArray",
+                                         "usampler2D",
+                                         "usampler2DArray",
+                                         "usampler2DMS",
+                                         "usampler2DMSArray",
+                                         "usampler2DRect",
+                                         "usampler3D",
+                                         "usamplerBuffer",
+                                         "usamplerCube",
+                                         "usamplerCubeArray",
+                                         //    "using",      // WGSL keyword
+                                         "uvec2",
+                                         "uvec3",
+                                         "uvec4",
+                                         "varying",
+                                         //    "vec2",       // WGSL keyword
+                                         //    "vec3",       // WGSL keyword
+                                         //    "vec4",       // WGSL keyword
+                                         //    "void",       // WGSL keyword
+                                         "volatile",
+                                         //    "while",      // WGSL keyword
+                                         "writeonly",
+                                         kUnicodeIdentifier));
+
+INSTANTIATE_TEST_SUITE_P(RenamerTestHlsl,
+                         RenamerTestHlsl,
+                         testing::Values("AddressU",
+                                         "AddressV",
+                                         "AddressW",
+                                         "AllMemoryBarrier",
+                                         "AllMemoryBarrierWithGroupSync",
+                                         "AppendStructuredBuffer",
+                                         "BINORMAL",
+                                         "BLENDINDICES",
+                                         "BLENDWEIGHT",
+                                         "BlendState",
+                                         "BorderColor",
+                                         "Buffer",
+                                         "ByteAddressBuffer",
+                                         "COLOR",
+                                         "CheckAccessFullyMapped",
+                                         "ComparisonFunc",
+                                         "CompileShader",
+                                         "ComputeShader",
+                                         "ConsumeStructuredBuffer",
+                                         "D3DCOLORtoUBYTE4",
+                                         "DEPTH",
+                                         "DepthStencilState",
+                                         "DepthStencilView",
+                                         "DeviceMemoryBarrier",
+                                         "DeviceMemroyBarrierWithGroupSync",
+                                         "DomainShader",
+                                         "EvaluateAttributeAtCentroid",
+                                         "EvaluateAttributeAtSample",
+                                         "EvaluateAttributeSnapped",
+                                         "FOG",
+                                         "Filter",
+                                         "GeometryShader",
+                                         "GetRenderTargetSampleCount",
+                                         "GetRenderTargetSamplePosition",
+                                         "GroupMemoryBarrier",
+                                         "GroupMemroyBarrierWithGroupSync",
+                                         "Hullshader",
+                                         "InputPatch",
+                                         "InterlockedAdd",
+                                         "InterlockedAnd",
+                                         "InterlockedCompareExchange",
+                                         "InterlockedCompareStore",
+                                         "InterlockedExchange",
+                                         "InterlockedMax",
+                                         "InterlockedMin",
+                                         "InterlockedOr",
+                                         "InterlockedXor",
+                                         "LineStream",
+                                         "MaxAnisotropy",
+                                         "MaxLOD",
+                                         "MinLOD",
+                                         "MipLODBias",
+                                         "NORMAL",
+                                         "NULL",
+                                         "Normal",
+                                         "OutputPatch",
+                                         "POSITION",
+                                         "POSITIONT",
+                                         "PSIZE",
+                                         "PixelShader",
+                                         "PointStream",
+                                         "Process2DQuadTessFactorsAvg",
+                                         "Process2DQuadTessFactorsMax",
+                                         "Process2DQuadTessFactorsMin",
+                                         "ProcessIsolineTessFactors",
+                                         "ProcessQuadTessFactorsAvg",
+                                         "ProcessQuadTessFactorsMax",
+                                         "ProcessQuadTessFactorsMin",
+                                         "ProcessTriTessFactorsAvg",
+                                         "ProcessTriTessFactorsMax",
+                                         "ProcessTriTessFactorsMin",
+                                         "RWBuffer",
+                                         "RWByteAddressBuffer",
+                                         "RWStructuredBuffer",
+                                         "RWTexture1D",
+                                         "RWTexture1DArray",
+                                         "RWTexture2D",
+                                         "RWTexture2DArray",
+                                         "RWTexture3D",
+                                         "RasterizerState",
+                                         "RenderTargetView",
+                                         "SV_ClipDistance",
+                                         "SV_Coverage",
+                                         "SV_CullDistance",
+                                         "SV_Depth",
+                                         "SV_DepthGreaterEqual",
+                                         "SV_DepthLessEqual",
+                                         "SV_DispatchThreadID",
+                                         "SV_DomainLocation",
+                                         "SV_GSInstanceID",
+                                         "SV_GroupID",
+                                         "SV_GroupIndex",
+                                         "SV_GroupThreadID",
+                                         "SV_InnerCoverage",
+                                         "SV_InsideTessFactor",
+                                         "SV_InstanceID",
+                                         "SV_IsFrontFace",
+                                         "SV_OutputControlPointID",
+                                         "SV_Position",
+                                         "SV_PrimitiveID",
+                                         "SV_RenderTargetArrayIndex",
+                                         "SV_SampleIndex",
+                                         "SV_StencilRef",
+                                         "SV_Target",
+                                         "SV_TessFactor",
+                                         "SV_VertexArrayIndex",
+                                         "SV_VertexID",
+                                         "Sampler",
+                                         "Sampler1D",
+                                         "Sampler2D",
+                                         "Sampler3D",
+                                         "SamplerCUBE",
+                                         "SamplerComparisonState",
+                                         "SamplerState",
+                                         "StructuredBuffer",
+                                         "TANGENT",
+                                         "TESSFACTOR",
+                                         "TEXCOORD",
+                                         "Texcoord",
+                                         "Texture",
+                                         "Texture1D",
+                                         "Texture1DArray",
+                                         "Texture2D",
+                                         "Texture2DArray",
+                                         "Texture2DMS",
+                                         "Texture2DMSArray",
+                                         "Texture3D",
+                                         "TextureCube",
+                                         "TextureCubeArray",
+                                         "TriangleStream",
+                                         "VFACE",
+                                         "VPOS",
+                                         "VertexShader",
+                                         "abort",
+                                         // "abs",  // WGSL builtin
+                                         // "acos",  // WGSL builtin
+                                         // "all",  // WGSL builtin
+                                         "allow_uav_condition",
+                                         // "any",  // WGSL builtin
+                                         "asdouble",
+                                         "asfloat",
+                                         // "asin",  // WGSL builtin
+                                         "asint",
+                                         // "asm",  // WGSL keyword
+                                         "asm_fragment",
+                                         "asuint",
+                                         // "atan",  // WGSL builtin
+                                         // "atan2",  // WGSL builtin
+                                         "auto",
+                                         // "bool",  // WGSL keyword
+                                         "bool1",
+                                         "bool1x1",
+                                         "bool1x2",
+                                         "bool1x3",
+                                         "bool1x4",
+                                         "bool2",
+                                         "bool2x1",
+                                         "bool2x2",
+                                         "bool2x3",
+                                         "bool2x4",
+                                         "bool3",
+                                         "bool3x1",
+                                         "bool3x2",
+                                         "bool3x3",
+                                         "bool3x4",
+                                         "bool4",
+                                         "bool4x1",
+                                         "bool4x2",
+                                         "bool4x3",
+                                         "bool4x4",
+                                         "branch",
+                                         // "break",  // WGSL keyword
+                                         // "call",  // WGSL builtin
+                                         // "case",  // WGSL keyword
+                                         "catch",
+                                         "cbuffer",
+                                         // "ceil",  // WGSL builtin
+                                         "centroid",
+                                         "char",
+                                         // "clamp",  // WGSL builtin
+                                         "class",
+                                         "clip",
+                                         "column_major",
+                                         "compile",
+                                         "compile_fragment",
+                                         // "const",  // WGSL keyword
+                                         "const_cast",
+                                         // "continue",  // WGSL keyword
+                                         // "cos",  // WGSL builtin
+                                         // "cosh",  // WGSL builtin
+                                         "countbits",
+                                         // "cross",  // WGSL builtin
+                                         "ddx",
+                                         "ddx_coarse",
+                                         "ddx_fine",
+                                         "ddy",
+                                         "ddy_coarse",
+                                         "ddy_fine",
+                                         // "default",  // WGSL keyword
+                                         "degrees",
+                                         "delete",
+                                         // "determinant",  // WGSL builtin
+                                         // "discard",  // WGSL keyword
+                                         // "distance",  // WGSL builtin
+                                         // "do",  // WGSL keyword
+                                         // "dot",  // WGSL builtin
+                                         "double",
+                                         "double1",
+                                         "double1x1",
+                                         "double1x2",
+                                         "double1x3",
+                                         "double1x4",
+                                         "double2",
+                                         "double2x1",
+                                         "double2x2",
+                                         "double2x3",
+                                         "double2x4",
+                                         "double3",
+                                         "double3x1",
+                                         "double3x2",
+                                         "double3x3",
+                                         "double3x4",
+                                         "double4",
+                                         "double4x1",
+                                         "double4x2",
+                                         "double4x3",
+                                         "double4x4",
+                                         "dst",
+                                         "dword",
+                                         "dword1",
+                                         "dword1x1",
+                                         "dword1x2",
+                                         "dword1x3",
+                                         "dword1x4",
+                                         "dword2",
+                                         "dword2x1",
+                                         "dword2x2",
+                                         "dword2x3",
+                                         "dword2x4",
+                                         "dword3",
+                                         "dword3x1",
+                                         "dword3x2",
+                                         "dword3x3",
+                                         "dword3x4",
+                                         "dword4",
+                                         "dword4x1",
+                                         "dword4x2",
+                                         "dword4x3",
+                                         "dword4x4",
+                                         "dynamic_cast",
+                                         // "else",  // WGSL keyword
+                                         // "enum",  // WGSL keyword
+                                         "errorf",
+                                         // "exp",  // WGSL builtin
+                                         // "exp2",  // WGSL builtin
+                                         "explicit",
+                                         "export",
+                                         "extern",
+                                         "f16to32",
+                                         "f32tof16",
+                                         // "faceforward",  // WGSL builtin
+                                         // "false",  // WGSL keyword
+                                         "fastopt",
+                                         "firstbithigh",
+                                         "firstbitlow",
+                                         "flatten",
+                                         "float",
+                                         "float1",
+                                         "float1x1",
+                                         "float1x2",
+                                         "float1x3",
+                                         "float1x4",
+                                         "float2",
+                                         "float2x1",
+                                         "float2x2",
+                                         "float2x3",
+                                         "float2x4",
+                                         "float3",
+                                         "float3x1",
+                                         "float3x2",
+                                         "float3x3",
+                                         "float3x4",
+                                         "float4",
+                                         "float4x1",
+                                         "float4x2",
+                                         "float4x3",
+                                         "float4x4",
+                                         // "floor",  // WGSL builtin
+                                         // "fma",  // WGSL builtin
+                                         "fmod",
+                                         // "for",  // WGSL keyword
+                                         "forcecase",
+                                         "frac",
+                                         // "frexp",  // WGSL builtin
+                                         "friend",
+                                         // "fwidth",  // WGSL builtin
+                                         "fxgroup",
+                                         "goto",
+                                         "groupshared",
+                                         "half",
+                                         "half1",
+                                         "half1x1",
+                                         "half1x2",
+                                         "half1x3",
+                                         "half1x4",
+                                         "half2",
+                                         "half2x1",
+                                         "half2x2",
+                                         "half2x3",
+                                         "half2x4",
+                                         "half3",
+                                         "half3x1",
+                                         "half3x2",
+                                         "half3x3",
+                                         "half3x4",
+                                         "half4",
+                                         "half4x1",
+                                         "half4x2",
+                                         "half4x3",
+                                         "half4x4",
+                                         // "if",  // WGSL keyword
+                                         // "in",  // WGSL keyword
+                                         "inline",
+                                         "inout",
+                                         "int",
+                                         "int1",
+                                         "int1x1",
+                                         "int1x2",
+                                         "int1x3",
+                                         "int1x4",
+                                         "int2",
+                                         "int2x1",
+                                         "int2x2",
+                                         "int2x3",
+                                         "int2x4",
+                                         "int3",
+                                         "int3x1",
+                                         "int3x2",
+                                         "int3x3",
+                                         "int3x4",
+                                         "int4",
+                                         "int4x1",
+                                         "int4x2",
+                                         "int4x3",
+                                         "int4x4",
+                                         "interface",
+                                         "isfinite",
+                                         "isinf",
+                                         "isnan",
+                                         // "ldexp",  // WGSL builtin
+                                         // "length",  // WGSL builtin
+                                         "lerp",
+                                         "line",
+                                         "lineadj",
+                                         "linear",
+                                         "lit",
+                                         // "log",  // WGSL builtin
+                                         "log10",
+                                         // "log2",  // WGSL builtin
+                                         "long",
+                                         // "loop",  // WGSL keyword
+                                         "mad",
+                                         "matrix",
+                                         // "max",  // WGSL builtin
+                                         // "min",  // WGSL builtin
+                                         "min10float",
+                                         "min10float1",
+                                         "min10float1x1",
+                                         "min10float1x2",
+                                         "min10float1x3",
+                                         "min10float1x4",
+                                         "min10float2",
+                                         "min10float2x1",
+                                         "min10float2x2",
+                                         "min10float2x3",
+                                         "min10float2x4",
+                                         "min10float3",
+                                         "min10float3x1",
+                                         "min10float3x2",
+                                         "min10float3x3",
+                                         "min10float3x4",
+                                         "min10float4",
+                                         "min10float4x1",
+                                         "min10float4x2",
+                                         "min10float4x3",
+                                         "min10float4x4",
+                                         "min12int",
+                                         "min12int1",
+                                         "min12int1x1",
+                                         "min12int1x2",
+                                         "min12int1x3",
+                                         "min12int1x4",
+                                         "min12int2",
+                                         "min12int2x1",
+                                         "min12int2x2",
+                                         "min12int2x3",
+                                         "min12int2x4",
+                                         "min12int3",
+                                         "min12int3x1",
+                                         "min12int3x2",
+                                         "min12int3x3",
+                                         "min12int3x4",
+                                         "min12int4",
+                                         "min12int4x1",
+                                         "min12int4x2",
+                                         "min12int4x3",
+                                         "min12int4x4",
+                                         "min16float",
+                                         "min16float1",
+                                         "min16float1x1",
+                                         "min16float1x2",
+                                         "min16float1x3",
+                                         "min16float1x4",
+                                         "min16float2",
+                                         "min16float2x1",
+                                         "min16float2x2",
+                                         "min16float2x3",
+                                         "min16float2x4",
+                                         "min16float3",
+                                         "min16float3x1",
+                                         "min16float3x2",
+                                         "min16float3x3",
+                                         "min16float3x4",
+                                         "min16float4",
+                                         "min16float4x1",
+                                         "min16float4x2",
+                                         "min16float4x3",
+                                         "min16float4x4",
+                                         "min16int",
+                                         "min16int1",
+                                         "min16int1x1",
+                                         "min16int1x2",
+                                         "min16int1x3",
+                                         "min16int1x4",
+                                         "min16int2",
+                                         "min16int2x1",
+                                         "min16int2x2",
+                                         "min16int2x3",
+                                         "min16int2x4",
+                                         "min16int3",
+                                         "min16int3x1",
+                                         "min16int3x2",
+                                         "min16int3x3",
+                                         "min16int3x4",
+                                         "min16int4",
+                                         "min16int4x1",
+                                         "min16int4x2",
+                                         "min16int4x3",
+                                         "min16int4x4",
+                                         "min16uint",
+                                         "min16uint1",
+                                         "min16uint1x1",
+                                         "min16uint1x2",
+                                         "min16uint1x3",
+                                         "min16uint1x4",
+                                         "min16uint2",
+                                         "min16uint2x1",
+                                         "min16uint2x2",
+                                         "min16uint2x3",
+                                         "min16uint2x4",
+                                         "min16uint3",
+                                         "min16uint3x1",
+                                         "min16uint3x2",
+                                         "min16uint3x3",
+                                         "min16uint3x4",
+                                         "min16uint4",
+                                         "min16uint4x1",
+                                         "min16uint4x2",
+                                         "min16uint4x3",
+                                         "min16uint4x4",
+                                         // "modf",  // WGSL builtin
+                                         "msad4",
+                                         "mul",
+                                         "mutable",
+                                         "namespace",
+                                         "new",
+                                         "nointerpolation",
+                                         "noise",
+                                         "noperspective",
+                                         // "normalize",  // WGSL builtin
+                                         "numthreads",
+                                         "operator",
+                                         // "out",  // WGSL keyword
+                                         "packoffset",
+                                         "pass",
+                                         "pixelfragment",
+                                         "pixelshader",
+                                         "point",
+                                         // "pow",  // WGSL builtin
+                                         "precise",
+                                         "printf",
+                                         // "private",  // WGSL keyword
+                                         "protected",
+                                         "public",
+                                         "radians",
+                                         "rcp",
+                                         // "reflect",  // WGSL builtin
+                                         "refract",
+                                         "register",
+                                         "reinterpret_cast",
+                                         // "return",  // WGSL keyword
+                                         // "reversebits",  // WGSL builtin
+                                         // "round",  // WGSL builtin
+                                         "row_major",
+                                         "rsqrt",
+                                         "sample",
+                                         "sampler1D",
+                                         "sampler2D",
+                                         "sampler3D",
+                                         "samplerCUBE",
+                                         "sampler_state",
+                                         "saturate",
+                                         "shared",
+                                         "short",
+                                         // "sign",  // WGSL builtin
+                                         "signed",
+                                         // "sin",  // WGSL builtin
+                                         "sincos",
+                                         // "sinh",  // WGSL builtin
+                                         "sizeof",
+                                         // "smoothstep",  // WGSL builtin
+                                         "snorm",
+                                         // "sqrt",  // WGSL builtin
+                                         "stateblock",
+                                         "stateblock_state",
+                                         "static",
+                                         "static_cast",
+                                         // "step",  // WGSL builtin
+                                         "string",
+                                         // "struct",  // WGSL keyword
+                                         // "switch",  // WGSL keyword
+                                         // "tan",  // WGSL builtin
+                                         // "tanh",  // WGSL builtin
+                                         "tbuffer",
+                                         "technique",
+                                         "technique10",
+                                         "technique11",
+                                         "template",
+                                         "tex1D",
+                                         "tex1Dbias",
+                                         "tex1Dgrad",
+                                         "tex1Dlod",
+                                         "tex1Dproj",
+                                         "tex2D",
+                                         "tex2Dbias",
+                                         "tex2Dgrad",
+                                         "tex2Dlod",
+                                         "tex2Dproj",
+                                         "tex3D",
+                                         "tex3Dbias",
+                                         "tex3Dgrad",
+                                         "tex3Dlod",
+                                         "tex3Dproj",
+                                         "texCUBE",
+                                         "texCUBEbias",
+                                         "texCUBEgrad",
+                                         "texCUBElod",
+                                         "texCUBEproj",
+                                         "texture",
+                                         "texture1D",
+                                         "texture1DArray",
+                                         "texture2D",
+                                         "texture2DArray",
+                                         "texture2DMS",
+                                         "texture2DMSArray",
+                                         "texture3D",
+                                         "textureCube",
+                                         "textureCubeArray",
+                                         "this",
+                                         "throw",
+                                         "transpose",
+                                         "triangle",
+                                         "triangleadj",
+                                         // "true",  // WGSL keyword
+                                         // "trunc",  // WGSL builtin
+                                         "try",
+                                         // "typedef",  // WGSL keyword
+                                         "typename",
+                                         "uint",
+                                         "uint1",
+                                         "uint1x1",
+                                         "uint1x2",
+                                         "uint1x3",
+                                         "uint1x4",
+                                         "uint2",
+                                         "uint2x1",
+                                         "uint2x2",
+                                         "uint2x3",
+                                         "uint2x4",
+                                         "uint3",
+                                         "uint3x1",
+                                         "uint3x2",
+                                         "uint3x3",
+                                         "uint3x4",
+                                         "uint4",
+                                         "uint4x1",
+                                         "uint4x2",
+                                         "uint4x3",
+                                         "uint4x4",
+                                         // "uniform",  // WGSL keyword
+                                         "union",
+                                         "unorm",
+                                         "unroll",
+                                         "unsigned",
+                                         // "using",  // WGSL reserved keyword
+                                         "vector",
+                                         "vertexfragment",
+                                         "vertexshader",
+                                         "virtual",
+                                         // "void",  // WGSL keyword
+                                         "volatile",
+                                         // "while"  // WGSL reserved keyword
+                                         kUnicodeIdentifier));
+
+INSTANTIATE_TEST_SUITE_P(
+    RenamerTestMsl,
+    RenamerTestMsl,
+    testing::Values(
+        // c++14 spec
+        "alignas",
+        "alignof",
+        "and",
+        "and_eq",
+        // "asm",  // Also reserved in WGSL
+        "auto",
+        "bitand",
+        "bitor",
+        // "bool",   // Also used in WGSL
+        // "break",  // Also used in WGSL
+        // "case",   // Also used in WGSL
+        "catch",
+        "char",
+        "char16_t",
+        "char32_t",
+        "class",
+        "compl",
+        // "const",     // Also used in WGSL
+        "const_cast",
+        "constexpr",
+        // "continue",  // Also used in WGSL
+        "decltype",
+        // "default",   // Also used in WGSL
+        "delete",
+        // "do",  // Also used in WGSL
+        "double",
+        "dynamic_cast",
+        // "else",  // Also used in WGSL
+        // "enum",  // Also used in WGSL
+        "explicit",
+        "extern",
+        // "false",  // Also used in WGSL
+        "final",
+        "float",
+        // "for",  // Also used in WGSL
+        "friend",
+        "goto",
+        // "if",  // Also used in WGSL
+        "inline",
+        "int",
+        "long",
+        "mutable",
+        "namespace",
+        "new",
+        "noexcept",
+        "not",
+        "not_eq",
+        "nullptr",
+        "operator",
+        "or",
+        "or_eq",
+        // "override", // Also used in WGSL
+        // "private",  // Also used in WGSL
+        "protected",
+        "public",
+        "register",
+        "reinterpret_cast",
+        // "return",  // Also used in WGSL
+        "short",
+        "signed",
+        "sizeof",
+        "static",
+        "static_assert",
+        "static_cast",
+        // "struct",  // Also used in WGSL
+        // "switch",  // Also used in WGSL
+        "template",
+        "this",
+        "thread_local",
+        "throw",
+        // "true",  // Also used in WGSL
+        "try",
+        // "typedef",  // Also used in WGSL
+        "typeid",
+        "typename",
+        "union",
+        "unsigned",
+        // "using",  // WGSL reserved keyword
+        "virtual",
+        // "void",  // Also used in WGSL
+        "volatile",
+        "wchar_t",
+        // "while",  // WGSL reserved keyword
+        "xor",
+        "xor_eq",
+
+        // Metal Spec
+        "access",
+        // "array",  // Also used in WGSL
+        "array_ref",
+        "as_type",
+        // "atomic",  // Also used in WGSL
+        "atomic_bool",
+        "atomic_int",
+        "atomic_uint",
+        "bool2",
+        "bool3",
+        "bool4",
+        "buffer",
+        "char2",
+        "char3",
+        "char4",
+        "const_reference",
+        "constant",
+        "depth2d",
+        "depth2d_array",
+        "depth2d_ms",
+        "depth2d_ms_array",
+        "depthcube",
+        "depthcube_array",
+        "device",
+        "discard_fragment",
+        "float2",
+        "float2x2",
+        "float2x3",
+        "float2x4",
+        "float3",
+        "float3x2",
+        "float3x3",
+        "float3x4",
+        "float4",
+        "float4x2",
+        "float4x3",
+        "float4x4",
+        "fragment",
+        "half",
+        "half2",
+        "half2x2",
+        "half2x3",
+        "half2x4",
+        "half3",
+        "half3x2",
+        "half3x3",
+        "half3x4",
+        "half4",
+        "half4x2",
+        "half4x3",
+        "half4x4",
+        "imageblock",
+        "int16_t",
+        "int2",
+        "int3",
+        "int32_t",
+        "int4",
+        "int64_t",
+        "int8_t",
+        "kernel",
+        "long2",
+        "long3",
+        "long4",
+        "main",  // No functions called main
+        "matrix",
+        "metal",  // The namespace
+        "packed_bool2",
+        "packed_bool3",
+        "packed_bool4",
+        "packed_char2",
+        "packed_char3",
+        "packed_char4",
+        "packed_float2",
+        "packed_float3",
+        "packed_float4",
+        "packed_half2",
+        "packed_half3",
+        "packed_half4",
+        "packed_int2",
+        "packed_int3",
+        "packed_int4",
+        "packed_short2",
+        "packed_short3",
+        "packed_short4",
+        "packed_uchar2",
+        "packed_uchar3",
+        "packed_uchar4",
+        "packed_uint2",
+        "packed_uint3",
+        "packed_uint4",
+        "packed_ushort2",
+        "packed_ushort3",
+        "packed_ushort4",
+        "patch_control_point",
+        "ptrdiff_t",
+        "r16snorm",
+        "r16unorm",
+        "r8unorm",
+        "reference",
+        "rg11b10f",
+        "rg16snorm",
+        "rg16unorm",
+        "rg8snorm",
+        "rg8unorm",
+        "rgb10a2",
+        "rgb9e5",
+        "rgba16snorm",
+        "rgba16unorm",
+        "rgba8snorm",
+        "rgba8unorm",
+        // "sampler",  // Also used in WGSL
+        "short2",
+        "short3",
+        "short4",
+        "size_t",
+        "srgba8unorm",
+        "texture",
+        "texture1d",
+        "texture1d_array",
+        "texture2d",
+        "texture2d_array",
+        "texture2d_ms",
+        "texture2d_ms_array",
+        "texture3d",
+        "texture_buffer",
+        "texturecube",
+        "texturecube_array",
+        "thread",
+        "threadgroup",
+        "threadgroup_imageblock",
+        "uchar",
+        "uchar2",
+        "uchar3",
+        "uchar4",
+        "uint",
+        "uint16_t",
+        "uint2",
+        "uint3",
+        "uint32_t",
+        "uint4",
+        "uint64_t",
+        "uint8_t",
+        "ulong2",
+        "ulong3",
+        "ulong4",
+        // "uniform",  // Also used in WGSL
+        "ushort",
+        "ushort2",
+        "ushort3",
+        "ushort4",
+        // "vec",  // WGSL reserved keyword
+        "vertex",
+
+        // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+        // Table 6.5. Constants for single-precision floating-point math
+        // functions
+        "MAXFLOAT",
+        "HUGE_VALF",
+        "INFINITY",
+        "infinity",
+        "NAN",
+        "M_E_F",
+        "M_LOG2E_F",
+        "M_LOG10E_F",
+        "M_LN2_F",
+        "M_LN10_F",
+        "M_PI_F",
+        "M_PI_2_F",
+        "M_PI_4_F",
+        "M_1_PI_F",
+        "M_2_PI_F",
+        "M_2_SQRTPI_F",
+        "M_SQRT2_F",
+        "M_SQRT1_2_F",
+        "MAXHALF",
+        "HUGE_VALH",
+        "M_E_H",
+        "M_LOG2E_H",
+        "M_LOG10E_H",
+        "M_LN2_H",
+        "M_LN10_H",
+        "M_PI_H",
+        "M_PI_2_H",
+        "M_PI_4_H",
+        "M_1_PI_H",
+        "M_2_PI_H",
+        "M_2_SQRTPI_H",
+        "M_SQRT2_H",
+        "M_SQRT1_2_H",
+        // "while"  // WGSL reserved keyword
+        kUnicodeIdentifier));
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
new file mode 100644
index 0000000..2d62b9f
--- /dev/null
+++ b/src/tint/transform/robustness.cc
@@ -0,0 +1,321 @@
+// Copyright 2020 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/robustness.h"
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness::Config);
+
+namespace tint {
+namespace transform {
+
+/// State holds the current transform state
+struct Robustness::State {
+  /// The clone context
+  CloneContext& ctx;
+
+  /// Set of storage classes to not apply the transform to
+  std::unordered_set<ast::StorageClass> omitted_classes;
+
+  /// Applies the transformation state to `ctx`.
+  void Transform() {
+    ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr) {
+      return Transform(expr);
+    });
+    ctx.ReplaceAll(
+        [&](const ast::CallExpression* expr) { return Transform(expr); });
+  }
+
+  /// Apply bounds clamping to array, vector and matrix indexing
+  /// @param expr the array, vector or matrix index expression
+  /// @return the clamped replacement expression, or nullptr if `expr` should be
+  /// cloned without changes.
+  const ast::IndexAccessorExpression* Transform(
+      const ast::IndexAccessorExpression* expr) {
+    auto* ret_type = ctx.src->Sem().Get(expr->object)->Type();
+
+    auto* ref = ret_type->As<sem::Reference>();
+    if (ref && omitted_classes.count(ref->StorageClass()) != 0) {
+      return nullptr;
+    }
+
+    auto* ret_unwrapped = ret_type->UnwrapRef();
+
+    ProgramBuilder& b = *ctx.dst;
+    using u32 = ProgramBuilder::u32;
+
+    struct Value {
+      const ast::Expression* expr = nullptr;  // If null, then is a constant
+      union {
+        uint32_t u32 = 0;  // use if is_signed == false
+        int32_t i32;       // use if is_signed == true
+      };
+      bool is_signed = false;
+    };
+
+    Value size;              // size of the array, vector or matrix
+    size.is_signed = false;  // size is always unsigned
+    if (auto* vec = ret_unwrapped->As<sem::Vector>()) {
+      size.u32 = vec->Width();
+
+    } else if (auto* arr = ret_unwrapped->As<sem::Array>()) {
+      size.u32 = arr->Count();
+    } else if (auto* mat = ret_unwrapped->As<sem::Matrix>()) {
+      // The row accessor would have been an embedded index accessor and already
+      // handled, so we just need to do columns here.
+      size.u32 = mat->columns();
+    } else {
+      return nullptr;
+    }
+
+    if (size.u32 == 0) {
+      if (!ret_unwrapped->Is<sem::Array>()) {
+        b.Diagnostics().add_error(diag::System::Transform,
+                                  "invalid 0 sized non-array", expr->source);
+        return nullptr;
+      }
+      // Runtime sized array
+      auto* arr = ctx.Clone(expr->object);
+      size.expr = b.Call("arrayLength", b.AddressOf(arr));
+    }
+
+    // Calculate the maximum possible index value (size-1u)
+    // Size must be positive (non-zero), so we can safely subtract 1 here
+    // without underflow.
+    Value limit;
+    limit.is_signed = false;  // Like size, limit is always unsigned.
+    if (size.expr) {
+      // Dynamic size
+      limit.expr = b.Sub(size.expr, 1u);
+    } else {
+      // Constant size
+      limit.u32 = size.u32 - 1u;
+    }
+
+    Value idx;  // index value
+
+    auto* idx_sem = ctx.src->Sem().Get(expr->index);
+    auto* idx_ty = idx_sem->Type()->UnwrapRef();
+    if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
+      TINT_ICE(Transform, b.Diagnostics())
+          << "index must be u32 or i32, got " << idx_sem->Type()->type_name();
+      return nullptr;
+    }
+
+    if (auto idx_constant = idx_sem->ConstantValue()) {
+      // Constant value index
+      if (idx_constant.Type()->Is<sem::I32>()) {
+        idx.i32 = idx_constant.Elements()[0].i32;
+        idx.is_signed = true;
+      } else if (idx_constant.Type()->Is<sem::U32>()) {
+        idx.u32 = idx_constant.Elements()[0].u32;
+        idx.is_signed = false;
+      } else {
+        b.Diagnostics().add_error(diag::System::Transform,
+                                  "unsupported constant value for accessor: " +
+                                      idx_constant.Type()->type_name(),
+                                  expr->source);
+        return nullptr;
+      }
+    } else {
+      // Dynamic value index
+      idx.expr = ctx.Clone(expr->index);
+      idx.is_signed = idx_ty->Is<sem::I32>();
+    }
+
+    // Clamp the index so that it cannot exceed limit.
+    if (idx.expr || limit.expr) {
+      // One of, or both of idx and limit are non-constant.
+
+      // If the index is signed, cast it to a u32 (with clamping if constant).
+      if (idx.is_signed) {
+        if (idx.expr) {
+          // We don't use a max(idx, 0) here, as that incurs a runtime
+          // performance cost, and if the unsigned value will be clamped by
+          // limit, resulting in a value between [0..limit)
+          idx.expr = b.Construct<u32>(idx.expr);
+          idx.is_signed = false;
+        } else {
+          idx.u32 = static_cast<uint32_t>(std::max(idx.i32, 0));
+          idx.is_signed = false;
+        }
+      }
+
+      // Convert idx and limit to expressions, so we can emit `min(idx, limit)`.
+      if (!idx.expr) {
+        idx.expr = b.Expr(idx.u32);
+      }
+      if (!limit.expr) {
+        limit.expr = b.Expr(limit.u32);
+      }
+
+      // Perform the clamp with `min(idx, limit)`
+      idx.expr = b.Call("min", idx.expr, limit.expr);
+    } else {
+      // Both idx and max are constant.
+      if (idx.is_signed) {
+        // The index is signed. Calculate limit as signed.
+        int32_t signed_limit = static_cast<int32_t>(
+            std::min<uint32_t>(limit.u32, std::numeric_limits<int32_t>::max()));
+        idx.i32 = std::max(idx.i32, 0);
+        idx.i32 = std::min(idx.i32, signed_limit);
+      } else {
+        // The index is unsigned.
+        idx.u32 = std::min(idx.u32, limit.u32);
+      }
+    }
+
+    // Convert idx to an expression, so we can emit the new accessor.
+    if (!idx.expr) {
+      idx.expr = idx.is_signed
+                     ? static_cast<const ast::Expression*>(b.Expr(idx.i32))
+                     : static_cast<const ast::Expression*>(b.Expr(idx.u32));
+    }
+
+    // Clone arguments outside of create() call to have deterministic ordering
+    auto src = ctx.Clone(expr->source);
+    auto* obj = ctx.Clone(expr->object);
+    return b.IndexAccessor(src, obj, idx.expr);
+  }
+
+  /// @param type builtin type
+  /// @returns true if the given builtin is a texture function that requires
+  /// argument clamping,
+  bool TextureBuiltinNeedsClamping(sem::BuiltinType type) {
+    return type == sem::BuiltinType::kTextureLoad ||
+           type == sem::BuiltinType::kTextureStore;
+  }
+
+  /// Apply bounds clamping to the coordinates, array index and level arguments
+  /// of the `textureLoad()` and `textureStore()` builtins.
+  /// @param expr the builtin call expression
+  /// @return the clamped replacement call expression, or nullptr if `expr`
+  /// should be cloned without changes.
+  const ast::CallExpression* Transform(const ast::CallExpression* expr) {
+    auto* call = ctx.src->Sem().Get(expr);
+    auto* call_target = call->Target();
+    auto* builtin = call_target->As<sem::Builtin>();
+    if (!builtin || !TextureBuiltinNeedsClamping(builtin->Type())) {
+      return nullptr;  // No transform, just clone.
+    }
+
+    ProgramBuilder& b = *ctx.dst;
+
+    // Indices of the mandatory texture and coords parameters, and the optional
+    // array and level parameters.
+    auto& signature = builtin->Signature();
+    auto texture_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
+    auto coords_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
+    auto array_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
+    auto level_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
+
+    auto* texture_arg = expr->args[texture_idx];
+    auto* coords_arg = expr->args[coords_idx];
+    auto* coords_ty = builtin->Parameters()[coords_idx]->Type();
+
+    // If the level is provided, then we need to clamp this. As the level is
+    // used by textureDimensions() and the texture[Load|Store]() calls, we need
+    // to clamp both usages.
+    // TODO(bclayton): We probably want to place this into a let so that the
+    // calculation can be reused. This is fiddly to get right.
+    std::function<const ast::Expression*()> level_arg;
+    if (level_idx >= 0) {
+      level_arg = [&] {
+        auto* arg = expr->args[level_idx];
+        auto* num_levels = b.Call("textureNumLevels", ctx.Clone(texture_arg));
+        auto* zero = b.Expr(0);
+        auto* max = ctx.dst->Sub(num_levels, 1);
+        auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
+        return clamped;
+      };
+    }
+
+    // Clamp the coordinates argument
+    {
+      auto* texture_dims =
+          level_arg
+              ? b.Call("textureDimensions", ctx.Clone(texture_arg), level_arg())
+              : b.Call("textureDimensions", ctx.Clone(texture_arg));
+      auto* zero = b.Construct(CreateASTTypeFor(ctx, coords_ty));
+      auto* max = ctx.dst->Sub(
+          texture_dims, b.Construct(CreateASTTypeFor(ctx, coords_ty), 1));
+      auto* clamped_coords = b.Call("clamp", ctx.Clone(coords_arg), zero, max);
+      ctx.Replace(coords_arg, clamped_coords);
+    }
+
+    // Clamp the array_index argument, if provided
+    if (array_idx >= 0) {
+      auto* arg = expr->args[array_idx];
+      auto* num_layers = b.Call("textureNumLayers", ctx.Clone(texture_arg));
+      auto* zero = b.Expr(0);
+      auto* max = ctx.dst->Sub(num_layers, 1);
+      auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
+      ctx.Replace(arg, clamped);
+    }
+
+    // Clamp the level argument, if provided
+    if (level_idx >= 0) {
+      auto* arg = expr->args[level_idx];
+      ctx.Replace(arg, level_arg ? level_arg() : ctx.dst->Expr(0));
+    }
+
+    return nullptr;  // Clone, which will use the argument replacements above.
+  }
+};
+
+Robustness::Config::Config() = default;
+Robustness::Config::Config(const Config&) = default;
+Robustness::Config::~Config() = default;
+Robustness::Config& Robustness::Config::operator=(const Config&) = default;
+
+Robustness::Robustness() = default;
+Robustness::~Robustness() = default;
+
+void Robustness::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) const {
+  Config cfg;
+  if (auto* cfg_data = inputs.Get<Config>()) {
+    cfg = *cfg_data;
+  }
+
+  std::unordered_set<ast::StorageClass> omitted_classes;
+  for (auto sc : cfg.omitted_classes) {
+    switch (sc) {
+      case StorageClass::kUniform:
+        omitted_classes.insert(ast::StorageClass::kUniform);
+        break;
+      case StorageClass::kStorage:
+        omitted_classes.insert(ast::StorageClass::kStorage);
+        break;
+    }
+  }
+
+  State state{ctx, std::move(omitted_classes)};
+
+  state.Transform();
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/robustness.h b/src/tint/transform/robustness.h
new file mode 100644
index 0000000..733cf47
--- /dev/null
+++ b/src/tint/transform/robustness.h
@@ -0,0 +1,88 @@
+// Copyright 2020 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_ROBUSTNESS_H_
+#define SRC_TINT_TRANSFORM_ROBUSTNESS_H_
+
+#include <unordered_set>
+
+#include "src/tint/transform/transform.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class IndexAccessorExpression;
+class CallExpression;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace transform {
+
+/// This transform is responsible for clamping all array accesses to be within
+/// the bounds of the array. Any access before the start of the array will clamp
+/// to zero and any access past the end of the array will clamp to
+/// (array length - 1).
+class Robustness : public Castable<Robustness, Transform> {
+ public:
+  /// Storage class to be skipped in the transform
+  enum class StorageClass {
+    kUniform,
+    kStorage,
+  };
+
+  /// Configuration options for the transform
+  struct Config : public Castable<Config, Data> {
+    /// Constructor
+    Config();
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// Assignment operator
+    /// @returns this Config
+    Config& operator=(const Config&);
+
+    /// Storage classes to omit from apply the transform to.
+    /// This allows for optimizing on hardware that provide safe accesses.
+    std::unordered_set<StorageClass> omitted_classes;
+  };
+
+  /// Constructor
+  Robustness();
+  /// Destructor
+  ~Robustness() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  struct State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_ROBUSTNESS_H_
diff --git a/src/tint/transform/robustness_test.cc b/src/tint/transform/robustness_test.cc
new file mode 100644
index 0000000..edc33f5
--- /dev/null
+++ b/src/tint/transform/robustness_test.cc
@@ -0,0 +1,1745 @@
+// Copyright 2020 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/robustness.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using RobustnessTest = TransformTest;
+
+TEST_F(RobustnessTest, Array_Idx_Clamp) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+let c : u32 = 1u;
+
+fn f() {
+  let b : f32 = a[c];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+let c : u32 = 1u;
+
+fn f() {
+  let b : f32 = a[1u];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Clamp_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  let b : f32 = a[c];
+}
+
+let c : u32 = 1u;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  let b : f32 = a[1u];
+}
+
+let c : u32 = 1u;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Nested_Scalar) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  var c : f32 = a[ b[i] ];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Nested_Scalar_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var c : f32 = a[ b[i] ];
+}
+
+var<private> i : u32;
+
+var<private> b : array<i32, 5>;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
+}
+
+var<private> i : u32;
+
+var<private> b : array<i32, 5>;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Scalar) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Scalar_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[1];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[1];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Expr) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[c + 2 - 3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Expr_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[c + 2 - 3];
+}
+
+var<private> c : i32;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
+}
+
+var<private> c : i32;
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Negative) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[-1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[0];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_Negative_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[-1];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[0];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_OutOfBounds) {
+  auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[2];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Array_Idx_OutOfBounds_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[3];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// TODO(crbug.com/tint/1177) - Validation currently forbids arrays larger than
+// 0xffffffff. If WGSL supports 64-bit indexing, re-enable this test.
+TEST_F(RobustnessTest, DISABLED_LargeArrays_Idx) {
+  auto* src = R"(
+struct S {
+  a : array<f32, 0x7fffffff>;
+  b : array<f32>;
+};
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  // Signed
+  var i32_a1 : f32 = s.a[ 0x7ffffffe];
+  var i32_a2 : f32 = s.a[ 1];
+  var i32_a3 : f32 = s.a[ 0];
+  var i32_a4 : f32 = s.a[-1];
+  var i32_a5 : f32 = s.a[-0x7fffffff];
+
+  var i32_b1 : f32 = s.b[ 0x7ffffffe];
+  var i32_b2 : f32 = s.b[ 1];
+  var i32_b3 : f32 = s.b[ 0];
+  var i32_b4 : f32 = s.b[-1];
+  var i32_b5 : f32 = s.b[-0x7fffffff];
+
+  // Unsigned
+  var u32_a1 : f32 = s.a[0u];
+  var u32_a2 : f32 = s.a[1u];
+  var u32_a3 : f32 = s.a[0x7ffffffeu];
+  var u32_a4 : f32 = s.a[0x7fffffffu];
+  var u32_a5 : f32 = s.a[0x80000000u];
+  var u32_a6 : f32 = s.a[0xffffffffu];
+
+  var u32_b1 : f32 = s.b[0u];
+  var u32_b2 : f32 = s.b[1u];
+  var u32_b3 : f32 = s.b[0x7ffffffeu];
+  var u32_b4 : f32 = s.b[0x7fffffffu];
+  var u32_b5 : f32 = s.b[0x80000000u];
+  var u32_b6 : f32 = s.b[0xffffffffu];
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : array<f32, 2147483647>;
+  b : array<f32>;
+};
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  var i32_a1 : f32 = s.a[2147483646];
+  var i32_a2 : f32 = s.a[1];
+  var i32_a3 : f32 = s.a[0];
+  var i32_a4 : f32 = s.a[0];
+  var i32_a5 : f32 = s.a[0];
+  var i32_b1 : f32 = s.b[min(2147483646u, (arrayLength(&(s.b)) - 1u))];
+  var i32_b2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var i32_b3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_b4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_b5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var u32_a1 : f32 = s.a[0u];
+  var u32_a2 : f32 = s.a[1u];
+  var u32_a3 : f32 = s.a[2147483646u];
+  var u32_a4 : f32 = s.a[2147483646u];
+  var u32_a5 : f32 = s.a[2147483646u];
+  var u32_a6 : f32 = s.a[2147483646u];
+  var u32_b1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var u32_b2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var u32_b3 : f32 = s.b[min(2147483646u, (arrayLength(&(s.b)) - 1u))];
+  var u32_b4 : f32 = s.b[min(2147483647u, (arrayLength(&(s.b)) - 1u))];
+  var u32_b5 : f32 = s.b[min(2147483648u, (arrayLength(&(s.b)) - 1u))];
+  var u32_b6 : f32 = s.b[min(4294967295u, (arrayLength(&(s.b)) - 1u))];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Scalar) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Scalar_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[1];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[1];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Expr) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[c + 2 - 3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Expr_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[c + 2 - 3];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a.xy[2];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a.xy[1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a.xy[2];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a.xy[1];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[c];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[min(u32(c), 1u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a.xy[c];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a.xy[min(u32(c), 1u)];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[c + 2 - 3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a.xy[c + 2 - 3];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
+}
+
+var<private> c : i32;
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Negative) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[-1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[0];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_Negative_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[-1];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[0];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_OutOfBounds) {
+  auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[2];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Vector_Idx_OutOfBounds_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[3];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2];
+}
+
+var<private> a : vec3<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Scalar) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Scalar_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[2][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Expr_Column) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[c + 2 - 3][1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Expr_Column_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[c + 2 - 3][1];
+}
+
+var<private> c : i32;
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
+}
+
+var<private> c : i32;
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Expr_Row) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[1][c + 2 - 3];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Expr_Row_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[1][c + 2 - 3];
+}
+
+var<private> c : i32;
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
+}
+
+var<private> c : i32;
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Negative_Column) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[-1][1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[0][1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Negative_Column_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[-1][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[0][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Negative_Row) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][-1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][0];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_Negative_Row_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[2][-1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2][0];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[5][1];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[5][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row) {
+  auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][5];
+}
+)";
+
+  auto* expect = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2][1];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var b : f32 = a[2][5];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto* expect = R"(
+fn f() {
+  var b : f32 = a[2][1];
+}
+
+var<private> a : mat3x2<f32>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(RobustnessTest, DISABLED_Vector_Constant_Id_Clamps) {
+  // @id(1300) override idx : i32;
+  // var a : vec3<f32>
+  // var b : f32 = a[idx]
+  //
+  // ->var b : f32 = a[min(u32(idx), 2)]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(RobustnessTest, DISABLED_Array_Constant_Id_Clamps) {
+  // @id(1300) override idx : i32;
+  // var a : array<f32, 4>
+  // var b : f32 = a[idx]
+  //
+  // -> var b : f32 = a[min(u32(idx), 3)]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(RobustnessTest, DISABLED_Matrix_Column_Constant_Id_Clamps) {
+  // @id(1300) override idx : i32;
+  // var a : mat3x2<f32>
+  // var b : f32 = a[idx][1]
+  //
+  // -> var b : f32 = a[min(u32(idx), 2)][1]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(RobustnessTest, DISABLED_Matrix_Row_Constant_Id_Clamps) {
+  // @id(1300) override idx : i32;
+  // var a : mat3x2<f32>
+  // var b : f32 = a[1][idx]
+  //
+  // -> var b : f32 = a[1][min(u32(idx), 0, 1)]
+}
+
+TEST_F(RobustnessTest, RuntimeArray_Clamps) {
+  auto* src = R"(
+struct S {
+  a : f32;
+  b : array<f32>;
+};
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  var d : f32 = s.b[25];
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, RuntimeArray_Clamps_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var d : f32 = s.b[25];
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+struct S {
+  a : f32;
+  b : array<f32>;
+};
+)";
+
+  auto* expect = R"(
+fn f() {
+  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+struct S {
+  a : f32;
+  b : array<f32>;
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Clamp textureLoad() coord, array_index and level values
+TEST_F(RobustnessTest, TextureLoad_Clamp) {
+  auto* src = R"(
+@group(0) @binding(0) var tex_1d : texture_1d<f32>;
+@group(0) @binding(0) var tex_2d : texture_2d<f32>;
+@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
+@group(0) @binding(0) var tex_3d : texture_3d<f32>;
+@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
+@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
+@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
+@group(0) @binding(0) var tex_external : texture_external;
+
+fn f() {
+  var array_idx : i32;
+  var level_idx : i32;
+  var sample_idx : i32;
+
+  textureLoad(tex_1d, 1, level_idx);
+  textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
+  textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
+  textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
+  textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
+  textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
+  textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
+  textureLoad(tex_external, vec2<i32>(1, 2));
+}
+)";
+
+  auto* expect =
+      R"(
+@group(0) @binding(0) var tex_1d : texture_1d<f32>;
+
+@group(0) @binding(0) var tex_2d : texture_2d<f32>;
+
+@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
+
+@group(0) @binding(0) var tex_3d : texture_3d<f32>;
+
+@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
+
+@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
+
+@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
+
+@group(0) @binding(0) var tex_external : texture_external;
+
+fn f() {
+  var array_idx : i32;
+  var level_idx : i32;
+  var sample_idx : i32;
+  textureLoad(tex_1d, clamp(1, i32(), (textureDimensions(tex_1d, clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1))) - i32(1))), clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1)));
+  textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d, clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1)));
+  textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1)));
+  textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3<i32>(), (textureDimensions(tex_3d, clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1))) - vec3<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1)));
+  textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_ms_2d) - vec2<i32>(1))), sample_idx);
+  textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1)));
+  textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_depth_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1)));
+  textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_external) - vec2<i32>(1))));
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Clamp textureLoad() coord, array_index and level values
+TEST_F(RobustnessTest, TextureLoad_Clamp_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var array_idx : i32;
+  var level_idx : i32;
+  var sample_idx : i32;
+
+  textureLoad(tex_1d, 1, level_idx);
+  textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
+  textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
+  textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
+  textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
+  textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
+  textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
+  textureLoad(tex_external, vec2<i32>(1, 2));
+}
+
+@group(0) @binding(0) var tex_1d : texture_1d<f32>;
+@group(0) @binding(0) var tex_2d : texture_2d<f32>;
+@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
+@group(0) @binding(0) var tex_3d : texture_3d<f32>;
+@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
+@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
+@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
+@group(0) @binding(0) var tex_external : texture_external;
+)";
+
+  auto* expect =
+      R"(
+fn f() {
+  var array_idx : i32;
+  var level_idx : i32;
+  var sample_idx : i32;
+  textureLoad(tex_1d, clamp(1, i32(), (textureDimensions(tex_1d, clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1))) - i32(1))), clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1)));
+  textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d, clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1)));
+  textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1)));
+  textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3<i32>(), (textureDimensions(tex_3d, clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1))) - vec3<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1)));
+  textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_ms_2d) - vec2<i32>(1))), sample_idx);
+  textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1)));
+  textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_depth_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1)));
+  textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_external) - vec2<i32>(1))));
+}
+
+@group(0) @binding(0) var tex_1d : texture_1d<f32>;
+
+@group(0) @binding(0) var tex_2d : texture_2d<f32>;
+
+@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
+
+@group(0) @binding(0) var tex_3d : texture_3d<f32>;
+
+@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
+
+@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
+
+@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
+
+@group(0) @binding(0) var tex_external : texture_external;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Clamp textureStore() coord, array_index and level values
+TEST_F(RobustnessTest, TextureStore_Clamp) {
+  auto* src = R"(
+@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+
+@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
+
+@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
+
+@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
+
+fn f() {
+  textureStore(tex1d, 10, vec4<i32>());
+  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
+  textureStore(tex2d_arr, vec2<i32>(10, 20), 50, vec4<i32>());
+  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
+}
+)";
+
+  auto* expect = R"(
+@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+
+@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
+
+@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
+
+@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
+
+fn f() {
+  textureStore(tex1d, clamp(10, i32(), (textureDimensions(tex1d) - i32(1))), vec4<i32>());
+  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d) - vec2<i32>(1))), vec4<i32>());
+  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d_arr) - vec2<i32>(1))), clamp(50, 0, (textureNumLayers(tex2d_arr) - 1)), vec4<i32>());
+  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3<i32>(), (textureDimensions(tex3d) - vec3<i32>(1))), vec4<i32>());
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Clamp textureStore() coord, array_index and level values
+TEST_F(RobustnessTest, TextureStore_Clamp_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  textureStore(tex1d, 10, vec4<i32>());
+  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
+  textureStore(tex2d_arr, vec2<i32>(10, 20), 50, vec4<i32>());
+  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
+}
+
+@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+
+@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
+
+@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
+
+@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
+
+)";
+
+  auto* expect = R"(
+fn f() {
+  textureStore(tex1d, clamp(10, i32(), (textureDimensions(tex1d) - i32(1))), vec4<i32>());
+  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d) - vec2<i32>(1))), vec4<i32>());
+  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d_arr) - vec2<i32>(1))), clamp(50, 0, (textureNumLayers(tex2d_arr) - 1)), vec4<i32>());
+  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3<i32>(), (textureDimensions(tex3d) - vec3<i32>(1))), vec4<i32>());
+}
+
+@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+
+@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
+
+@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
+
+@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// TODO(dsinclair): Test for scoped variables when shadowing is implemented
+TEST_F(RobustnessTest, DISABLED_Shadowed_Variable) {
+  // var a : array<f32, 3>;
+  // var i : u32;
+  // {
+  //    var a : array<f32, 5>;
+  //    var b : f32 = a[i];
+  // }
+  // var c : f32 = a[i];
+  //
+  // -> var b : f32 = a[min(u32(i), 4)];
+  //    var c : f32 = a[min(u32(i), 2)];
+  FAIL();
+}
+
+// Check that existing use of min() and arrayLength() do not get renamed.
+TEST_F(RobustnessTest, DontRenameSymbols) {
+  auto* src = R"(
+struct S {
+  a : f32;
+  b : array<f32>;
+};
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+let c : u32 = 1u;
+
+fn f() {
+  let b : f32 = s.b[c];
+  let x : i32 = min(1, 2);
+  let y : u32 = arrayLength(&s.b);
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  a : f32;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+let c : u32 = 1u;
+
+fn f() {
+  let b : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  let x : i32 = min(1, 2);
+  let y : u32 = arrayLength(&(s.b));
+}
+)";
+
+  auto got = Run<Robustness>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+const char* kOmitSourceShader = R"(
+struct S {
+  a : array<f32, 4>;
+  b : array<f32>;
+};
+@group(0) @binding(0) var<storage, read> s : S;
+
+type UArr = @stride(16) array<f32, 4>;
+struct U {
+  a : UArr;
+};
+@group(1) @binding(0) var<uniform> u : U;
+
+fn f() {
+  // Signed
+  var i32_sa1 : f32 = s.a[4];
+  var i32_sa2 : f32 = s.a[1];
+  var i32_sa3 : f32 = s.a[0];
+  var i32_sa4 : f32 = s.a[-1];
+  var i32_sa5 : f32 = s.a[-4];
+
+  var i32_sb1 : f32 = s.b[4];
+  var i32_sb2 : f32 = s.b[1];
+  var i32_sb3 : f32 = s.b[0];
+  var i32_sb4 : f32 = s.b[-1];
+  var i32_sb5 : f32 = s.b[-4];
+
+  var i32_ua1 : f32 = u.a[4];
+  var i32_ua2 : f32 = u.a[1];
+  var i32_ua3 : f32 = u.a[0];
+  var i32_ua4 : f32 = u.a[-1];
+  var i32_ua5 : f32 = u.a[-4];
+
+  // Unsigned
+  var u32_sa1 : f32 = s.a[0u];
+  var u32_sa2 : f32 = s.a[1u];
+  var u32_sa3 : f32 = s.a[3u];
+  var u32_sa4 : f32 = s.a[4u];
+  var u32_sa5 : f32 = s.a[10u];
+  var u32_sa6 : f32 = s.a[100u];
+
+  var u32_sb1 : f32 = s.b[0u];
+  var u32_sb2 : f32 = s.b[1u];
+  var u32_sb3 : f32 = s.b[3u];
+  var u32_sb4 : f32 = s.b[4u];
+  var u32_sb5 : f32 = s.b[10u];
+  var u32_sb6 : f32 = s.b[100u];
+
+  var u32_ua1 : f32 = u.a[0u];
+  var u32_ua2 : f32 = u.a[1u];
+  var u32_ua3 : f32 = u.a[3u];
+  var u32_ua4 : f32 = u.a[4u];
+  var u32_ua5 : f32 = u.a[10u];
+  var u32_ua6 : f32 = u.a[100u];
+}
+)";
+
+TEST_F(RobustnessTest, OmitNone) {
+  auto* expect = R"(
+struct S {
+  a : array<f32, 4>;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+type UArr = @stride(16) array<f32, 4>;
+
+struct U {
+  a : UArr;
+}
+
+@group(1) @binding(0) var<uniform> u : U;
+
+fn f() {
+  var i32_sa1 : f32 = s.a[3];
+  var i32_sa2 : f32 = s.a[1];
+  var i32_sa3 : f32 = s.a[0];
+  var i32_sa4 : f32 = s.a[0];
+  var i32_sa5 : f32 = s.a[0];
+  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_ua1 : f32 = u.a[3];
+  var i32_ua2 : f32 = u.a[1];
+  var i32_ua3 : f32 = u.a[0];
+  var i32_ua4 : f32 = u.a[0];
+  var i32_ua5 : f32 = u.a[0];
+  var u32_sa1 : f32 = s.a[0u];
+  var u32_sa2 : f32 = s.a[1u];
+  var u32_sa3 : f32 = s.a[3u];
+  var u32_sa4 : f32 = s.a[3u];
+  var u32_sa5 : f32 = s.a[3u];
+  var u32_sa6 : f32 = s.a[3u];
+  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
+  var u32_ua1 : f32 = u.a[0u];
+  var u32_ua2 : f32 = u.a[1u];
+  var u32_ua3 : f32 = u.a[3u];
+  var u32_ua4 : f32 = u.a[3u];
+  var u32_ua5 : f32 = u.a[3u];
+  var u32_ua6 : f32 = u.a[3u];
+}
+)";
+
+  Robustness::Config cfg;
+  DataMap data;
+  data.Add<Robustness::Config>(cfg);
+
+  auto got = Run<Robustness>(kOmitSourceShader, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, OmitStorage) {
+  auto* expect = R"(
+struct S {
+  a : array<f32, 4>;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+type UArr = @stride(16) array<f32, 4>;
+
+struct U {
+  a : UArr;
+}
+
+@group(1) @binding(0) var<uniform> u : U;
+
+fn f() {
+  var i32_sa1 : f32 = s.a[4];
+  var i32_sa2 : f32 = s.a[1];
+  var i32_sa3 : f32 = s.a[0];
+  var i32_sa4 : f32 = s.a[-1];
+  var i32_sa5 : f32 = s.a[-4];
+  var i32_sb1 : f32 = s.b[4];
+  var i32_sb2 : f32 = s.b[1];
+  var i32_sb3 : f32 = s.b[0];
+  var i32_sb4 : f32 = s.b[-1];
+  var i32_sb5 : f32 = s.b[-4];
+  var i32_ua1 : f32 = u.a[3];
+  var i32_ua2 : f32 = u.a[1];
+  var i32_ua3 : f32 = u.a[0];
+  var i32_ua4 : f32 = u.a[0];
+  var i32_ua5 : f32 = u.a[0];
+  var u32_sa1 : f32 = s.a[0u];
+  var u32_sa2 : f32 = s.a[1u];
+  var u32_sa3 : f32 = s.a[3u];
+  var u32_sa4 : f32 = s.a[4u];
+  var u32_sa5 : f32 = s.a[10u];
+  var u32_sa6 : f32 = s.a[100u];
+  var u32_sb1 : f32 = s.b[0u];
+  var u32_sb2 : f32 = s.b[1u];
+  var u32_sb3 : f32 = s.b[3u];
+  var u32_sb4 : f32 = s.b[4u];
+  var u32_sb5 : f32 = s.b[10u];
+  var u32_sb6 : f32 = s.b[100u];
+  var u32_ua1 : f32 = u.a[0u];
+  var u32_ua2 : f32 = u.a[1u];
+  var u32_ua3 : f32 = u.a[3u];
+  var u32_ua4 : f32 = u.a[3u];
+  var u32_ua5 : f32 = u.a[3u];
+  var u32_ua6 : f32 = u.a[3u];
+}
+)";
+
+  Robustness::Config cfg;
+  cfg.omitted_classes.insert(Robustness::StorageClass::kStorage);
+
+  DataMap data;
+  data.Add<Robustness::Config>(cfg);
+
+  auto got = Run<Robustness>(kOmitSourceShader, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, OmitUniform) {
+  auto* expect = R"(
+struct S {
+  a : array<f32, 4>;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+type UArr = @stride(16) array<f32, 4>;
+
+struct U {
+  a : UArr;
+}
+
+@group(1) @binding(0) var<uniform> u : U;
+
+fn f() {
+  var i32_sa1 : f32 = s.a[3];
+  var i32_sa2 : f32 = s.a[1];
+  var i32_sa3 : f32 = s.a[0];
+  var i32_sa4 : f32 = s.a[0];
+  var i32_sa5 : f32 = s.a[0];
+  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var i32_ua1 : f32 = u.a[4];
+  var i32_ua2 : f32 = u.a[1];
+  var i32_ua3 : f32 = u.a[0];
+  var i32_ua4 : f32 = u.a[-1];
+  var i32_ua5 : f32 = u.a[-4];
+  var u32_sa1 : f32 = s.a[0u];
+  var u32_sa2 : f32 = s.a[1u];
+  var u32_sa3 : f32 = s.a[3u];
+  var u32_sa4 : f32 = s.a[3u];
+  var u32_sa5 : f32 = s.a[3u];
+  var u32_sa6 : f32 = s.a[3u];
+  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
+  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
+  var u32_ua1 : f32 = u.a[0u];
+  var u32_ua2 : f32 = u.a[1u];
+  var u32_ua3 : f32 = u.a[3u];
+  var u32_ua4 : f32 = u.a[4u];
+  var u32_ua5 : f32 = u.a[10u];
+  var u32_ua6 : f32 = u.a[100u];
+}
+)";
+
+  Robustness::Config cfg;
+  cfg.omitted_classes.insert(Robustness::StorageClass::kUniform);
+
+  DataMap data;
+  data.Add<Robustness::Config>(cfg);
+
+  auto got = Run<Robustness>(kOmitSourceShader, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(RobustnessTest, OmitBoth) {
+  auto* expect = R"(
+struct S {
+  a : array<f32, 4>;
+  b : array<f32>;
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+type UArr = @stride(16) array<f32, 4>;
+
+struct U {
+  a : UArr;
+}
+
+@group(1) @binding(0) var<uniform> u : U;
+
+fn f() {
+  var i32_sa1 : f32 = s.a[4];
+  var i32_sa2 : f32 = s.a[1];
+  var i32_sa3 : f32 = s.a[0];
+  var i32_sa4 : f32 = s.a[-1];
+  var i32_sa5 : f32 = s.a[-4];
+  var i32_sb1 : f32 = s.b[4];
+  var i32_sb2 : f32 = s.b[1];
+  var i32_sb3 : f32 = s.b[0];
+  var i32_sb4 : f32 = s.b[-1];
+  var i32_sb5 : f32 = s.b[-4];
+  var i32_ua1 : f32 = u.a[4];
+  var i32_ua2 : f32 = u.a[1];
+  var i32_ua3 : f32 = u.a[0];
+  var i32_ua4 : f32 = u.a[-1];
+  var i32_ua5 : f32 = u.a[-4];
+  var u32_sa1 : f32 = s.a[0u];
+  var u32_sa2 : f32 = s.a[1u];
+  var u32_sa3 : f32 = s.a[3u];
+  var u32_sa4 : f32 = s.a[4u];
+  var u32_sa5 : f32 = s.a[10u];
+  var u32_sa6 : f32 = s.a[100u];
+  var u32_sb1 : f32 = s.b[0u];
+  var u32_sb2 : f32 = s.b[1u];
+  var u32_sb3 : f32 = s.b[3u];
+  var u32_sb4 : f32 = s.b[4u];
+  var u32_sb5 : f32 = s.b[10u];
+  var u32_sb6 : f32 = s.b[100u];
+  var u32_ua1 : f32 = u.a[0u];
+  var u32_ua2 : f32 = u.a[1u];
+  var u32_ua3 : f32 = u.a[3u];
+  var u32_ua4 : f32 = u.a[4u];
+  var u32_ua5 : f32 = u.a[10u];
+  var u32_ua6 : f32 = u.a[100u];
+}
+)";
+
+  Robustness::Config cfg;
+  cfg.omitted_classes.insert(Robustness::StorageClass::kStorage);
+  cfg.omitted_classes.insert(Robustness::StorageClass::kUniform);
+
+  DataMap data;
+  data.Add<Robustness::Config>(cfg);
+
+  auto got = Run<Robustness>(kOmitSourceShader, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/simplify_pointers.cc b/src/tint/transform/simplify_pointers.cc
new file mode 100644
index 0000000..715894b
--- /dev/null
+++ b/src/tint/transform/simplify_pointers.cc
@@ -0,0 +1,239 @@
+// 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/transform/simplify_pointers.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/unshadow.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::SimplifyPointers);
+
+namespace tint {
+namespace transform {
+
+namespace {
+
+/// PointerOp describes either possible indirection or address-of action on an
+/// expression.
+struct PointerOp {
+  /// Positive: Number of times the `expr` was dereferenced (*expr)
+  /// Negative: Number of times the `expr` was 'addressed-of' (&expr)
+  /// Zero: no pointer op on `expr`
+  int indirections = 0;
+  /// The expression being operated on
+  const ast::Expression* expr = nullptr;
+};
+
+}  // namespace
+
+/// The PIMPL state for the SimplifyPointers transform
+struct SimplifyPointers::State {
+  /// The clone context
+  CloneContext& ctx;
+
+  /// Constructor
+  /// @param context the clone context
+  explicit State(CloneContext& context) : ctx(context) {}
+
+  /// Traverses the expression `expr` looking for non-literal array indexing
+  /// expressions that would affect the computed address of a pointer
+  /// expression. The function-like argument `cb` is called for each found.
+  /// @param expr the expression to traverse
+  /// @param cb a function-like object with the signature
+  /// `void(const ast::Expression*)`, which is called for each array index
+  /// expression
+  template <typename F>
+  static void CollectSavedArrayIndices(const ast::Expression* expr, F&& cb) {
+    if (auto* a = expr->As<ast::IndexAccessorExpression>()) {
+      CollectSavedArrayIndices(a->object, cb);
+      if (!a->index->Is<ast::LiteralExpression>()) {
+        cb(a->index);
+      }
+      return;
+    }
+
+    if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+      CollectSavedArrayIndices(m->structure, cb);
+      return;
+    }
+
+    if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+      CollectSavedArrayIndices(u->expr, cb);
+      return;
+    }
+
+    // Note: Other ast::Expression types can be safely ignored as they cannot be
+    // used to generate a reference or pointer.
+    // See https://gpuweb.github.io/gpuweb/wgsl/#forming-references-and-pointers
+  }
+
+  /// Reduce walks the expression chain, collapsing all address-of and
+  /// indirection ops into a PointerOp.
+  /// @param in the expression to walk
+  /// @returns the reduced PointerOp
+  PointerOp Reduce(const ast::Expression* in) const {
+    PointerOp op{0, in};
+    while (true) {
+      if (auto* unary = op.expr->As<ast::UnaryOpExpression>()) {
+        switch (unary->op) {
+          case ast::UnaryOp::kIndirection:
+            op.indirections++;
+            op.expr = unary->expr;
+            continue;
+          case ast::UnaryOp::kAddressOf:
+            op.indirections--;
+            op.expr = unary->expr;
+            continue;
+          default:
+            break;
+        }
+      }
+      if (auto* user = ctx.src->Sem().Get<sem::VariableUser>(op.expr)) {
+        auto* var = user->Variable();
+        if (var->Is<sem::LocalVariable>() &&  //
+            var->Declaration()->is_const &&   //
+            var->Type()->Is<sem::Pointer>()) {
+          op.expr = var->Declaration()->constructor;
+          continue;
+        }
+      }
+      return op;
+    }
+  }
+
+  /// Performs the transformation
+  void Run() {
+    // A map of saved expressions to their saved variable name
+    std::unordered_map<const ast::Expression*, Symbol> saved_vars;
+
+    // Register the ast::Expression transform handler.
+    // This performs two different transformations:
+    // * Identifiers that resolve to the pointer-typed `let` declarations are
+    // replaced with the recursively inlined initializer expression for the
+    // `let` declaration.
+    // * Sub-expressions inside the pointer-typed `let` initializer expression
+    // that have been hoisted to a saved variable are replaced with the saved
+    // variable identifier.
+    ctx.ReplaceAll([&](const ast::Expression* expr) -> const ast::Expression* {
+      // Look to see if we need to swap this Expression with a saved variable.
+      auto it = saved_vars.find(expr);
+      if (it != saved_vars.end()) {
+        return ctx.dst->Expr(it->second);
+      }
+
+      // Reduce the expression, folding away chains of address-of / indirections
+      auto op = Reduce(expr);
+
+      // Clone the reduced root expression
+      expr = ctx.CloneWithoutTransform(op.expr);
+
+      // And reapply the minimum number of address-of / indirections
+      for (int i = 0; i < op.indirections; i++) {
+        expr = ctx.dst->Deref(expr);
+      }
+      for (int i = 0; i > op.indirections; i--) {
+        expr = ctx.dst->AddressOf(expr);
+      }
+      return expr;
+    });
+
+    // Find all the pointer-typed `let` declarations.
+    // Note that these must be function-scoped, as module-scoped `let`s are not
+    // permitted.
+    for (auto* node : ctx.src->ASTNodes().Objects()) {
+      if (auto* let = node->As<ast::VariableDeclStatement>()) {
+        if (!let->variable->is_const) {
+          continue;  // Not a `let` declaration. Ignore.
+        }
+
+        auto* var = ctx.src->Sem().Get(let->variable);
+        if (!var->Type()->Is<sem::Pointer>()) {
+          continue;  // Not a pointer type. Ignore.
+        }
+
+        // We're dealing with a pointer-typed `let` declaration.
+
+        // Scan the initializer expression for array index expressions that need
+        // to be hoist to temporary "saved" variables.
+        std::vector<const ast::VariableDeclStatement*> saved;
+        CollectSavedArrayIndices(
+            var->Declaration()->constructor,
+            [&](const ast::Expression* idx_expr) {
+              // We have a sub-expression that needs to be saved.
+              // Create a new variable
+              auto saved_name = ctx.dst->Symbols().New(
+                  ctx.src->Symbols().NameFor(var->Declaration()->symbol) +
+                  "_save");
+              auto* decl = ctx.dst->Decl(
+                  ctx.dst->Const(saved_name, nullptr, ctx.Clone(idx_expr)));
+              saved.emplace_back(decl);
+              // Record the substitution of `idx_expr` to the saved variable
+              // with the symbol `saved_name`. This will be used by the
+              // ReplaceAll() handler above.
+              saved_vars.emplace(idx_expr, saved_name);
+            });
+
+        // Find the place to insert the saved declarations.
+        // Special care needs to be made for lets declared as the initializer
+        // part of for-loops. In this case the block will hold the for-loop
+        // statement, not the let.
+        if (!saved.empty()) {
+          auto* stmt = ctx.src->Sem().Get(let);
+          auto* block = stmt->Block();
+          // Find the statement owned by the block (either the let decl or a
+          // for-loop)
+          while (block != stmt->Parent()) {
+            stmt = stmt->Parent();
+          }
+          // Declare the stored variables just before stmt. Order here is
+          // important as order-of-operations needs to be preserved.
+          // CollectSavedArrayIndices() visits the LHS of an index accessor
+          // before the index expression.
+          for (auto* decl : saved) {
+            // Note that repeated calls to InsertBefore() with the same `before`
+            // argument will result in nodes to inserted in the order the
+            // calls are made (last call is inserted last).
+            ctx.InsertBefore(block->Declaration()->statements,
+                             stmt->Declaration(), decl);
+          }
+        }
+
+        // As the original `let` declaration will be fully inlined, there's no
+        // need for the original declaration to exist. Remove it.
+        RemoveStatement(ctx, let);
+      }
+    }
+    ctx.Clone();
+  }
+};
+
+SimplifyPointers::SimplifyPointers() = default;
+
+SimplifyPointers::~SimplifyPointers() = default;
+
+void SimplifyPointers::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  State(ctx).Run();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/simplify_pointers.h b/src/tint/transform/simplify_pointers.h
new file mode 100644
index 0000000..39ebc26
--- /dev/null
+++ b/src/tint/transform/simplify_pointers.h
@@ -0,0 +1,60 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_SIMPLIFY_POINTERS_H_
+#define SRC_TINT_TRANSFORM_SIMPLIFY_POINTERS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// SimplifyPointers is a Transform that moves all usage of function-scope
+/// `let` statements of a pointer type into their places of usage, while also
+/// simplifying any chains of address-of or indirections operators.
+///
+/// Parameters of a pointer type are not adjusted.
+///
+/// Note: SimplifyPointers does not operate on module-scope `let`s, as these
+/// cannot be pointers: https://gpuweb.github.io/gpuweb/wgsl/#module-constants
+/// `A module-scope let-declared constant must be of constructible type.`
+///
+/// @note Depends on the following transforms to have been run first:
+/// * Unshadow
+class SimplifyPointers : public Castable<SimplifyPointers, Transform> {
+ public:
+  /// Constructor
+  SimplifyPointers();
+
+  /// Destructor
+  ~SimplifyPointers() override;
+
+ protected:
+  struct State;
+
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_SIMPLIFY_POINTERS_H_
diff --git a/src/tint/transform/simplify_pointers_test.cc b/src/tint/transform/simplify_pointers_test.cc
new file mode 100644
index 0000000..cfcb244
--- /dev/null
+++ b/src/tint/transform/simplify_pointers_test.cc
@@ -0,0 +1,370 @@
+// 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/transform/simplify_pointers.h"
+
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/unshadow.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using SimplifyPointersTest = TransformTest;
+
+TEST_F(SimplifyPointersTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, FoldPointer) {
+  auto* src = R"(
+fn f() {
+  var v : i32;
+  let p : ptr<function, i32> = &v;
+  let x : i32 = *p;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var v : i32;
+  let x : i32 = v;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, AddressOfDeref) {
+  auto* src = R"(
+fn f() {
+  var v : i32;
+  let p : ptr<function, i32> = &(v);
+  let x : ptr<function, i32> = &(*(p));
+  let y : ptr<function, i32> = &(*(&(*(p))));
+  let z : ptr<function, i32> = &(*(&(*(&(*(&(*(p))))))));
+  var a = *x;
+  var b = *y;
+  var c = *z;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var v : i32;
+  var a = v;
+  var b = v;
+  var c = v;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, DerefAddressOf) {
+  auto* src = R"(
+fn f() {
+  var v : i32;
+  let x : i32 = *(&(v));
+  let y : i32 = *(&(*(&(v))));
+  let z : i32 = *(&(*(&(*(&(*(&(v))))))));
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var v : i32;
+  let x : i32 = v;
+  let y : i32 = v;
+  let z : i32 = v;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, ComplexChain) {
+  auto* src = R"(
+fn f() {
+  var a : array<mat4x4<f32>, 4>;
+  let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
+  let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
+  let vp : ptr<function, vec4<f32>> = &(*mp)[2];
+  let v : vec4<f32> = *vp;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : array<mat4x4<f32>, 4>;
+  let v : vec4<f32> = a[3][2];
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, SavedVars) {
+  auto* src = R"(
+struct S {
+  i : i32;
+};
+
+fn arr() {
+  var a : array<S, 2>;
+  var i : i32 = 0;
+  var j : i32 = 0;
+  let p : ptr<function, i32> = &a[i + j].i;
+  i = 2;
+  *p = 4;
+}
+
+fn matrix() {
+  var m : mat3x3<f32>;
+  var i : i32 = 0;
+  var j : i32 = 0;
+  let p : ptr<function, vec3<f32>> = &m[i + j];
+  i = 2;
+  *p = vec3<f32>(4.0, 5.0, 6.0);
+}
+)";
+
+  auto* expect = R"(
+struct S {
+  i : i32;
+}
+
+fn arr() {
+  var a : array<S, 2>;
+  var i : i32 = 0;
+  var j : i32 = 0;
+  let p_save = (i + j);
+  i = 2;
+  a[p_save].i = 4;
+}
+
+fn matrix() {
+  var m : mat3x3<f32>;
+  var i : i32 = 0;
+  var j : i32 = 0;
+  let p_save_1 = (i + j);
+  i = 2;
+  m[p_save_1] = vec3<f32>(4.0, 5.0, 6.0);
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, DontSaveLiterals) {
+  auto* src = R"(
+fn f() {
+  var arr : array<i32, 2>;
+  let p1 : ptr<function, i32> = &arr[1];
+  *p1 = 4;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var arr : array<i32, 2>;
+  arr[1] = 4;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, SavedVarsChain) {
+  auto* src = R"(
+fn f() {
+  var arr : array<array<i32, 2>, 2>;
+  let i : i32 = 0;
+  let j : i32 = 1;
+  let p : ptr<function, array<i32, 2>> = &arr[i];
+  let q : ptr<function, i32> = &(*p)[j];
+  *q = 12;
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var arr : array<array<i32, 2>, 2>;
+  let i : i32 = 0;
+  let j : i32 = 1;
+  let p_save = i;
+  let q_save = j;
+  arr[p_save][q_save] = 12;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, ForLoopInit) {
+  auto* src = R"(
+fn foo() -> i32 {
+  return 1;
+}
+
+@stage(fragment)
+fn main() {
+  var arr = array<f32, 4>();
+  for (let a = &arr[foo()]; ;) {
+    let x = *a;
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn foo() -> i32 {
+  return 1;
+}
+
+@stage(fragment)
+fn main() {
+  var arr = array<f32, 4>();
+  let a_save = foo();
+  for(; ; ) {
+    let x = arr[a_save];
+    break;
+  }
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, MultiSavedVarsInSinglePtrLetExpr) {
+  auto* src = R"(
+fn x() -> i32 {
+  return 1;
+}
+
+fn y() -> i32 {
+  return 1;
+}
+
+fn z() -> i32 {
+  return 1;
+}
+
+struct Inner {
+  a : array<i32, 2>;
+};
+
+struct Outer {
+  a : array<Inner, 2>;
+};
+
+fn f() {
+  var arr : array<Outer, 2>;
+  let p : ptr<function, i32> = &arr[x()].a[y()].a[z()];
+  *p = 1;
+  *p = 2;
+}
+)";
+
+  auto* expect = R"(
+fn x() -> i32 {
+  return 1;
+}
+
+fn y() -> i32 {
+  return 1;
+}
+
+fn z() -> i32 {
+  return 1;
+}
+
+struct Inner {
+  a : array<i32, 2>;
+}
+
+struct Outer {
+  a : array<Inner, 2>;
+}
+
+fn f() {
+  var arr : array<Outer, 2>;
+  let p_save = x();
+  let p_save_1 = y();
+  let p_save_2 = z();
+  arr[p_save].a[p_save_1].a[p_save_2] = 1;
+  arr[p_save].a[p_save_1].a[p_save_2] = 2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, ShadowPointer) {
+  auto* src = R"(
+var<private> a : array<i32, 2>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  let x = &a;
+  var a : i32 = (*x)[0];
+  {
+    var a : i32 = (*x)[1];
+  }
+}
+)";
+
+  auto* expect = R"(
+var<private> a : array<i32, 2>;
+
+@stage(compute) @workgroup_size(1)
+fn main() {
+  var a_1 : i32 = a[0];
+  {
+    var a_2 : i32 = a[1];
+  }
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/single_entry_point.cc b/src/tint/transform/single_entry_point.cc
new file mode 100644
index 0000000..62a6624
--- /dev/null
+++ b/src/tint/transform/single_entry_point.cc
@@ -0,0 +1,117 @@
+// 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/transform/single_entry_point.h"
+
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint::Config);
+
+namespace tint {
+namespace transform {
+
+SingleEntryPoint::SingleEntryPoint() = default;
+
+SingleEntryPoint::~SingleEntryPoint() = default;
+
+void SingleEntryPoint::Run(CloneContext& ctx,
+                           const DataMap& inputs,
+                           DataMap&) const {
+  auto* cfg = inputs.Get<Config>();
+  if (cfg == nullptr) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "missing transform data for " + std::string(TypeInfo().name));
+
+    return;
+  }
+
+  // Find the target entry point.
+  const ast::Function* entry_point = nullptr;
+  for (auto* f : ctx.src->AST().Functions()) {
+    if (!f->IsEntryPoint()) {
+      continue;
+    }
+    if (ctx.src->Symbols().NameFor(f->symbol) == cfg->entry_point_name) {
+      entry_point = f;
+      break;
+    }
+  }
+  if (entry_point == nullptr) {
+    ctx.dst->Diagnostics().add_error(
+        diag::System::Transform,
+        "entry point '" + cfg->entry_point_name + "' not found");
+    return;
+  }
+
+  auto& sem = ctx.src->Sem();
+
+  // Build set of referenced module-scope variables for faster lookups later.
+  std::unordered_set<const ast::Variable*> referenced_vars;
+  for (auto* var : sem.Get(entry_point)->TransitivelyReferencedGlobals()) {
+    referenced_vars.emplace(var->Declaration());
+  }
+
+  // Clone any module-scope variables, types, and functions that are statically
+  // referenced by the target entry point.
+  for (auto* decl : ctx.src->AST().GlobalDeclarations()) {
+    if (auto* ty = decl->As<ast::TypeDecl>()) {
+      // TODO(jrprice): Strip unused types.
+      ctx.dst->AST().AddTypeDecl(ctx.Clone(ty));
+    } else if (auto* var = decl->As<ast::Variable>()) {
+      if (referenced_vars.count(var)) {
+        if (var->is_overridable) {
+          // It is an overridable constant
+          if (!ast::HasAttribute<ast::IdAttribute>(var->attributes)) {
+            // If the constant doesn't already have an @id() attribute, add one
+            // so that its allocated ID so that it won't be affected by other
+            // stripped away constants
+            auto* global = sem.Get(var)->As<sem::GlobalVariable>();
+            const auto* id = ctx.dst->Id(global->ConstantId());
+            ctx.InsertFront(var->attributes, id);
+          }
+        }
+        ctx.dst->AST().AddGlobalVariable(ctx.Clone(var));
+      }
+    } else if (auto* func = decl->As<ast::Function>()) {
+      if (sem.Get(func)->HasAncestorEntryPoint(entry_point->symbol)) {
+        ctx.dst->AST().AddFunction(ctx.Clone(func));
+      }
+    } else {
+      TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+          << "unhandled global declaration: " << decl->TypeInfo().name;
+      return;
+    }
+  }
+
+  // Clone the entry point.
+  ctx.dst->AST().AddFunction(ctx.Clone(entry_point));
+}
+
+SingleEntryPoint::Config::Config(std::string entry_point)
+    : entry_point_name(entry_point) {}
+
+SingleEntryPoint::Config::Config(const Config&) = default;
+SingleEntryPoint::Config::~Config() = default;
+SingleEntryPoint::Config& SingleEntryPoint::Config::operator=(const Config&) =
+    default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/single_entry_point.h b/src/tint/transform/single_entry_point.h
new file mode 100644
index 0000000..8780761
--- /dev/null
+++ b/src/tint/transform/single_entry_point.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_SINGLE_ENTRY_POINT_H_
+#define SRC_TINT_TRANSFORM_SINGLE_ENTRY_POINT_H_
+
+#include <string>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Strip all but one entry point a module.
+///
+/// All module-scope variables, types, and functions that are not used by the
+/// target entry point will also be removed.
+class SingleEntryPoint : public Castable<SingleEntryPoint, Transform> {
+ public:
+  /// Configuration options for the transform
+  struct Config : public Castable<Config, Data> {
+    /// Constructor
+    /// @param entry_point the name of the entry point to keep
+    explicit Config(std::string entry_point = "");
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// Assignment operator
+    /// @returns this Config
+    Config& operator=(const Config&);
+
+    /// The name of the entry point to keep.
+    std::string entry_point_name;
+  };
+
+  /// Constructor
+  SingleEntryPoint();
+
+  /// Destructor
+  ~SingleEntryPoint() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_SINGLE_ENTRY_POINT_H_
diff --git a/src/tint/transform/single_entry_point_test.cc b/src/tint/transform/single_entry_point_test.cc
new file mode 100644
index 0000000..b6a9d35
--- /dev/null
+++ b/src/tint/transform/single_entry_point_test.cc
@@ -0,0 +1,517 @@
+// 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/transform/single_entry_point.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using SingleEntryPointTest = TransformTest;
+
+TEST_F(SingleEntryPointTest, Error_MissingTransformData) {
+  auto* src = "";
+
+  auto* expect =
+      "error: missing transform data for tint::transform::SingleEntryPoint";
+
+  auto got = Run<SingleEntryPoint>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, Error_NoEntryPoints) {
+  auto* src = "";
+
+  auto* expect = "error: entry point 'main' not found";
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>("main");
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, Error_InvalidEntryPoint) {
+  auto* src = R"(
+@stage(vertex)
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = "error: entry point '_' not found";
+
+  SingleEntryPoint::Config cfg("_");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, Error_NotAnEntryPoint) {
+  auto* src = R"(
+fn foo() {}
+
+@stage(fragment)
+fn main() {}
+)";
+
+  auto* expect = "error: entry point 'foo' not found";
+
+  SingleEntryPoint::Config cfg("foo");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, SingleEntryPoint) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn main() {
+}
+)";
+
+  SingleEntryPoint::Config cfg("main");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(src, str(got));
+}
+
+TEST_F(SingleEntryPointTest, MultipleEntryPoints) {
+  auto* src = R"(
+@stage(vertex)
+fn vert_main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+
+@stage(fragment)
+fn frag_main() {
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+}
+)";
+
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+}
+)";
+
+  SingleEntryPoint::Config cfg("comp_main1");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, GlobalVariables) {
+  auto* src = R"(
+var<private> a : f32;
+
+var<private> b : f32;
+
+var<private> c : f32;
+
+var<private> d : f32;
+
+@stage(vertex)
+fn vert_main() -> @builtin(position) vec4<f32> {
+  a = 0.0;
+  return vec4<f32>();
+}
+
+@stage(fragment)
+fn frag_main() {
+  b = 0.0;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  c = 0.0;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+  d = 0.0;
+}
+)";
+
+  auto* expect = R"(
+var<private> c : f32;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  c = 0.0;
+}
+)";
+
+  SingleEntryPoint::Config cfg("comp_main1");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, GlobalConstants) {
+  auto* src = R"(
+let a : f32 = 1.0;
+
+let b : f32 = 1.0;
+
+let c : f32 = 1.0;
+
+let d : f32 = 1.0;
+
+@stage(vertex)
+fn vert_main() -> @builtin(position) vec4<f32> {
+  let local_a : f32 = a;
+  return vec4<f32>();
+}
+
+@stage(fragment)
+fn frag_main() {
+  let local_b : f32 = b;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  let local_c : f32 = c;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+  let local_d : f32 = d;
+}
+)";
+
+  auto* expect = R"(
+let c : f32 = 1.0;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  let local_c : f32 = c;
+}
+)";
+
+  SingleEntryPoint::Config cfg("comp_main1");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, WorkgroupSizeLetPreserved) {
+  auto* src = R"(
+let size : i32 = 1;
+
+@stage(compute) @workgroup_size(size)
+fn main() {
+}
+)";
+
+  auto* expect = src;
+
+  SingleEntryPoint::Config cfg("main");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, OverridableConstants) {
+  auto* src = R"(
+@id(1001) override c1 : u32 = 1u;
+          override c2 : u32 = 1u;
+@id(0)    override c3 : u32 = 1u;
+@id(9999) override c4 : u32 = 1u;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+    let local_d = c1;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+    let local_d = c2;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main3() {
+    let local_d = c3;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main4() {
+    let local_d = c4;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main5() {
+    let local_d = 1u;
+}
+)";
+
+  {
+    SingleEntryPoint::Config cfg("comp_main1");
+    auto* expect = R"(
+@id(1001) override c1 : u32 = 1u;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  let local_d = c1;
+}
+)";
+    DataMap data;
+    data.Add<SingleEntryPoint::Config>(cfg);
+    auto got = Run<SingleEntryPoint>(src, data);
+    EXPECT_EQ(expect, str(got));
+  }
+
+  {
+    SingleEntryPoint::Config cfg("comp_main2");
+    // The decorator is replaced with the one with explicit id
+    // And should not be affected by other constants stripped away
+    auto* expect = R"(
+@id(1) override c2 : u32 = 1u;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+  let local_d = c2;
+}
+)";
+    DataMap data;
+    data.Add<SingleEntryPoint::Config>(cfg);
+    auto got = Run<SingleEntryPoint>(src, data);
+    EXPECT_EQ(expect, str(got));
+  }
+
+  {
+    SingleEntryPoint::Config cfg("comp_main3");
+    auto* expect = R"(
+@id(0) override c3 : u32 = 1u;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main3() {
+  let local_d = c3;
+}
+)";
+    DataMap data;
+    data.Add<SingleEntryPoint::Config>(cfg);
+    auto got = Run<SingleEntryPoint>(src, data);
+    EXPECT_EQ(expect, str(got));
+  }
+
+  {
+    SingleEntryPoint::Config cfg("comp_main4");
+    auto* expect = R"(
+@id(9999) override c4 : u32 = 1u;
+
+@stage(compute) @workgroup_size(1)
+fn comp_main4() {
+  let local_d = c4;
+}
+)";
+    DataMap data;
+    data.Add<SingleEntryPoint::Config>(cfg);
+    auto got = Run<SingleEntryPoint>(src, data);
+    EXPECT_EQ(expect, str(got));
+  }
+
+  {
+    SingleEntryPoint::Config cfg("comp_main5");
+    auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn comp_main5() {
+  let local_d = 1u;
+}
+)";
+    DataMap data;
+    data.Add<SingleEntryPoint::Config>(cfg);
+    auto got = Run<SingleEntryPoint>(src, data);
+    EXPECT_EQ(expect, str(got));
+  }
+}
+
+TEST_F(SingleEntryPointTest, CalledFunctions) {
+  auto* src = R"(
+fn inner1() {
+}
+
+fn inner2() {
+}
+
+fn inner_shared() {
+}
+
+fn outer1() {
+  inner1();
+  inner_shared();
+}
+
+fn outer2() {
+  inner2();
+  inner_shared();
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  outer1();
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+  outer2();
+}
+)";
+
+  auto* expect = R"(
+fn inner1() {
+}
+
+fn inner_shared() {
+}
+
+fn outer1() {
+  inner1();
+  inner_shared();
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  outer1();
+}
+)";
+
+  SingleEntryPoint::Config cfg("comp_main1");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SingleEntryPointTest, GlobalsReferencedByCalledFunctions) {
+  auto* src = R"(
+var<private> inner1_var : f32;
+
+var<private> inner2_var : f32;
+
+var<private> inner_shared_var : f32;
+
+var<private> outer1_var : f32;
+
+var<private> outer2_var : f32;
+
+fn inner1() {
+  inner1_var = 0.0;
+}
+
+fn inner2() {
+  inner2_var = 0.0;
+}
+
+fn inner_shared() {
+  inner_shared_var = 0.0;
+}
+
+fn outer1() {
+  inner1();
+  inner_shared();
+  outer1_var = 0.0;
+}
+
+fn outer2() {
+  inner2();
+  inner_shared();
+  outer2_var = 0.0;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  outer1();
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main2() {
+  outer2();
+}
+)";
+
+  auto* expect = R"(
+var<private> inner1_var : f32;
+
+var<private> inner_shared_var : f32;
+
+var<private> outer1_var : f32;
+
+fn inner1() {
+  inner1_var = 0.0;
+}
+
+fn inner_shared() {
+  inner_shared_var = 0.0;
+}
+
+fn outer1() {
+  inner1();
+  inner_shared();
+  outer1_var = 0.0;
+}
+
+@stage(compute) @workgroup_size(1)
+fn comp_main1() {
+  outer1();
+}
+)";
+
+  SingleEntryPoint::Config cfg("comp_main1");
+
+  DataMap data;
+  data.Add<SingleEntryPoint::Config>(cfg);
+  auto got = Run<SingleEntryPoint>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/test_helper.h b/src/tint/transform/test_helper.h
new file mode 100644
index 0000000..974cba0
--- /dev/null
+++ b/src/tint/transform/test_helper.h
@@ -0,0 +1,153 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_TEST_HELPER_H_
+#define SRC_TINT_TRANSFORM_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace transform {
+
+/// @param program the program to get an output WGSL string from
+/// @returns the output program as a WGSL string, or an error string if the
+/// program is not valid.
+inline std::string str(const Program& program) {
+  diag::Formatter::Style style;
+  style.print_newline_at_end = false;
+
+  if (!program.IsValid()) {
+    return diag::Formatter(style).format(program.Diagnostics());
+  }
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  if (!result.success) {
+    return "WGSL writer failed:\n" + result.error;
+  }
+
+  auto res = result.wgsl;
+  if (res.empty()) {
+    return res;
+  }
+  // The WGSL sometimes has two trailing newlines. Strip them
+  while (res.back() == '\n') {
+    res.pop_back();
+  }
+  if (res.empty()) {
+    return res;
+  }
+  return "\n" + res + "\n";
+}
+
+/// Helper class for testing transforms
+template <typename BASE>
+class TransformTestBase : public BASE {
+ public:
+  /// Transforms and returns the WGSL source `in`, transformed using
+  /// `transform`.
+  /// @param transform the transform to apply
+  /// @param in the input WGSL source
+  /// @param data the optional DataMap to pass to Transform::Run()
+  /// @return the transformed output
+  Output Run(std::string in,
+             std::unique_ptr<transform::Transform> transform,
+             const DataMap& data = {}) {
+    std::vector<std::unique_ptr<transform::Transform>> transforms;
+    transforms.emplace_back(std::move(transform));
+    return Run(std::move(in), std::move(transforms), data);
+  }
+
+  /// Transforms and returns the WGSL source `in`, transformed using
+  /// a transform of type `TRANSFORM`.
+  /// @param in the input WGSL source
+  /// @param data the optional DataMap to pass to Transform::Run()
+  /// @return the transformed output
+  template <typename... TRANSFORMS>
+  Output Run(std::string in, const DataMap& data = {}) {
+    auto file = std::make_unique<Source::File>("test", in);
+    auto program = reader::wgsl::Parse(file.get());
+
+    // Keep this pointer alive after Transform() returns
+    files_.emplace_back(std::move(file));
+
+    return Run<TRANSFORMS...>(std::move(program), data);
+  }
+
+  /// Transforms and returns program `program`, transformed using a transform of
+  /// type `TRANSFORM`.
+  /// @param program the input Program
+  /// @param data the optional DataMap to pass to Transform::Run()
+  /// @return the transformed output
+  template <typename... TRANSFORMS>
+  Output Run(Program&& program, const DataMap& data = {}) {
+    if (!program.IsValid()) {
+      return Output(std::move(program));
+    }
+
+    Manager manager;
+    for (auto* transform_ptr :
+         std::initializer_list<Transform*>{new TRANSFORMS()...}) {
+      manager.append(std::unique_ptr<Transform>(transform_ptr));
+    }
+    return manager.Run(&program, data);
+  }
+
+  /// @param program the input program
+  /// @param data the optional DataMap to pass to Transform::Run()
+  /// @return true if the transform should be run for the given input.
+  template <typename TRANSFORM>
+  bool ShouldRun(Program&& program, const DataMap& data = {}) {
+    EXPECT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    return TRANSFORM().ShouldRun(&program, data);
+  }
+
+  /// @param in the input WGSL source
+  /// @param data the optional DataMap to pass to Transform::Run()
+  /// @return true if the transform should be run for the given input.
+  template <typename TRANSFORM>
+  bool ShouldRun(std::string in, const DataMap& data = {}) {
+    auto file = std::make_unique<Source::File>("test", in);
+    auto program = reader::wgsl::Parse(file.get());
+    return ShouldRun<TRANSFORM>(std::move(program), data);
+  }
+
+  /// @param output the output of the transform
+  /// @returns the output program as a WGSL string, or an error string if the
+  /// program is not valid.
+  std::string str(const Output& output) {
+    return transform::str(output.program);
+  }
+
+ private:
+  std::vector<std::unique_ptr<Source::File>> files_;
+};
+
+using TransformTest = TransformTestBase<testing::Test>;
+
+template <typename T>
+using TransformTestWithParam = TransformTestBase<testing::TestWithParam<T>>;
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_TEST_HELPER_H_
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
new file mode 100644
index 0000000..2254ce4
--- /dev/null
+++ b/src/tint/transform/transform.cc
@@ -0,0 +1,160 @@
+// Copyright 2020 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/transform.h"
+
+#include <algorithm>
+#include <string>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampler_type.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Transform);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Data);
+
+namespace tint {
+namespace transform {
+
+Data::Data() = default;
+Data::Data(const Data&) = default;
+Data::~Data() = default;
+Data& Data::operator=(const Data&) = default;
+
+DataMap::DataMap() = default;
+DataMap::DataMap(DataMap&&) = default;
+DataMap::~DataMap() = default;
+DataMap& DataMap::operator=(DataMap&&) = default;
+
+Output::Output() = default;
+Output::Output(Program&& p) : program(std::move(p)) {}
+Transform::Transform() = default;
+Transform::~Transform() = default;
+
+Output Transform::Run(const Program* program,
+                      const DataMap& data /* = {} */) const {
+  ProgramBuilder builder;
+  CloneContext ctx(&builder, program);
+  Output output;
+  Run(ctx, data, output.data);
+  output.program = Program(std::move(builder));
+  return output;
+}
+
+void Transform::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  TINT_UNIMPLEMENTED(Transform, ctx.dst->Diagnostics())
+      << "Transform::Run() unimplemented for " << TypeInfo().name;
+}
+
+bool Transform::ShouldRun(const Program*, const DataMap&) const {
+  return true;
+}
+
+void Transform::RemoveStatement(CloneContext& ctx, const ast::Statement* stmt) {
+  auto* sem = ctx.src->Sem().Get(stmt);
+  if (auto* block = tint::As<sem::BlockStatement>(sem->Parent())) {
+    ctx.Remove(block->Declaration()->statements, stmt);
+    return;
+  }
+  if (tint::Is<sem::ForLoopStatement>(sem->Parent())) {
+    ctx.Replace(stmt, static_cast<ast::Expression*>(nullptr));
+    return;
+  }
+  TINT_ICE(Transform, ctx.dst->Diagnostics())
+      << "unable to remove statement from parent of type "
+      << sem->TypeInfo().name;
+}
+
+const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx,
+                                             const sem::Type* ty) {
+  if (ty->Is<sem::Void>()) {
+    return ctx.dst->create<ast::Void>();
+  }
+  if (ty->Is<sem::I32>()) {
+    return ctx.dst->create<ast::I32>();
+  }
+  if (ty->Is<sem::U32>()) {
+    return ctx.dst->create<ast::U32>();
+  }
+  if (ty->Is<sem::F32>()) {
+    return ctx.dst->create<ast::F32>();
+  }
+  if (ty->Is<sem::Bool>()) {
+    return ctx.dst->create<ast::Bool>();
+  }
+  if (auto* m = ty->As<sem::Matrix>()) {
+    auto* el = CreateASTTypeFor(ctx, m->type());
+    return ctx.dst->create<ast::Matrix>(el, m->rows(), m->columns());
+  }
+  if (auto* v = ty->As<sem::Vector>()) {
+    auto* el = CreateASTTypeFor(ctx, v->type());
+    return ctx.dst->create<ast::Vector>(el, v->Width());
+  }
+  if (auto* a = ty->As<sem::Array>()) {
+    auto* el = CreateASTTypeFor(ctx, a->ElemType());
+    ast::AttributeList attrs;
+    if (!a->IsStrideImplicit()) {
+      attrs.emplace_back(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
+    }
+    if (a->IsRuntimeSized()) {
+      return ctx.dst->ty.array(el, nullptr, std::move(attrs));
+    } else {
+      return ctx.dst->ty.array(el, a->Count(), std::move(attrs));
+    }
+  }
+  if (auto* s = ty->As<sem::Struct>()) {
+    return ctx.dst->create<ast::TypeName>(ctx.Clone(s->Declaration()->name));
+  }
+  if (auto* s = ty->As<sem::Reference>()) {
+    return CreateASTTypeFor(ctx, s->StoreType());
+  }
+  if (auto* a = ty->As<sem::Atomic>()) {
+    return ctx.dst->create<ast::Atomic>(CreateASTTypeFor(ctx, a->Type()));
+  }
+  if (auto* t = ty->As<sem::DepthTexture>()) {
+    return ctx.dst->create<ast::DepthTexture>(t->dim());
+  }
+  if (auto* t = ty->As<sem::DepthMultisampledTexture>()) {
+    return ctx.dst->create<ast::DepthMultisampledTexture>(t->dim());
+  }
+  if (ty->Is<sem::ExternalTexture>()) {
+    return ctx.dst->create<ast::ExternalTexture>();
+  }
+  if (auto* t = ty->As<sem::MultisampledTexture>()) {
+    return ctx.dst->create<ast::MultisampledTexture>(
+        t->dim(), CreateASTTypeFor(ctx, t->type()));
+  }
+  if (auto* t = ty->As<sem::SampledTexture>()) {
+    return ctx.dst->create<ast::SampledTexture>(
+        t->dim(), CreateASTTypeFor(ctx, t->type()));
+  }
+  if (auto* t = ty->As<sem::StorageTexture>()) {
+    return ctx.dst->create<ast::StorageTexture>(
+        t->dim(), t->texel_format(), CreateASTTypeFor(ctx, t->type()),
+        t->access());
+  }
+  if (auto* s = ty->As<sem::Sampler>()) {
+    return ctx.dst->create<ast::Sampler>(s->kind());
+  }
+  TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+      << "Unhandled type: " << ty->TypeInfo().name;
+  return nullptr;
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/transform.h b/src/tint/transform/transform.h
new file mode 100644
index 0000000..199cb86
--- /dev/null
+++ b/src/tint/transform/transform.h
@@ -0,0 +1,199 @@
+// Copyright 2020 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_TRANSFORM_H_
+#define SRC_TINT_TRANSFORM_TRANSFORM_H_
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/castable.h"
+#include "src/tint/program.h"
+
+namespace tint {
+namespace transform {
+
+/// Data is the base class for transforms that accept extra input or emit extra
+/// output information along with a Program.
+class Data : public Castable<Data> {
+ public:
+  /// Constructor
+  Data();
+
+  /// Copy constructor
+  Data(const Data&);
+
+  /// Destructor
+  ~Data() override;
+
+  /// Assignment operator
+  /// @returns this Data
+  Data& operator=(const Data&);
+};
+
+/// DataMap is a map of Data unique pointers keyed by the Data's ClassID.
+class DataMap {
+ public:
+  /// Constructor
+  DataMap();
+
+  /// Move constructor
+  DataMap(DataMap&&);
+
+  /// Constructor
+  /// @param data_unique_ptrs a variadic list of additional data unique_ptrs
+  /// produced by the transform
+  template <typename... DATA>
+  explicit DataMap(DATA... data_unique_ptrs) {
+    PutAll(std::forward<DATA>(data_unique_ptrs)...);
+  }
+
+  /// Destructor
+  ~DataMap();
+
+  /// Move assignment operator
+  /// @param rhs the DataMap to move into this DataMap
+  /// @return this DataMap
+  DataMap& operator=(DataMap&& rhs);
+
+  /// Adds the data into DataMap keyed by the ClassID of type T.
+  /// @param data the data to add to the DataMap
+  template <typename T>
+  void Put(std::unique_ptr<T>&& data) {
+    static_assert(std::is_base_of<Data, T>::value,
+                  "T does not derive from Data");
+    map_[&TypeInfo::Of<T>()] = std::move(data);
+  }
+
+  /// Creates the data of type `T` with the provided arguments and adds it into
+  /// DataMap keyed by the ClassID of type T.
+  /// @param args the arguments forwarded to the constructor for type T
+  template <typename T, typename... ARGS>
+  void Add(ARGS&&... args) {
+    Put(std::make_unique<T>(std::forward<ARGS>(args)...));
+  }
+
+  /// @returns a pointer to the Data placed into the DataMap with a call to
+  /// Put()
+  template <typename T>
+  T const* Get() const {
+    auto it = map_.find(&TypeInfo::Of<T>());
+    if (it == map_.end()) {
+      return nullptr;
+    }
+    return static_cast<T*>(it->second.get());
+  }
+
+  /// Add moves all the data from other into this DataMap
+  /// @param other the DataMap to move into this DataMap
+  void Add(DataMap&& other) {
+    for (auto& it : other.map_) {
+      map_.emplace(it.first, std::move(it.second));
+    }
+    other.map_.clear();
+  }
+
+ private:
+  template <typename T0>
+  void PutAll(T0&& first) {
+    Put(std::forward<T0>(first));
+  }
+
+  template <typename T0, typename... Tn>
+  void PutAll(T0&& first, Tn&&... remainder) {
+    Put(std::forward<T0>(first));
+    PutAll(std::forward<Tn>(remainder)...);
+  }
+
+  std::unordered_map<const TypeInfo*, std::unique_ptr<Data>> map_;
+};
+
+/// The return type of Run()
+class Output {
+ public:
+  /// Constructor
+  Output();
+
+  /// Constructor
+  /// @param program the program to move into this Output
+  explicit Output(Program&& program);
+
+  /// Constructor
+  /// @param program_ the program to move into this Output
+  /// @param data_ a variadic list of additional data unique_ptrs produced by
+  /// the transform
+  template <typename... DATA>
+  Output(Program&& program_, DATA... data_)
+      : program(std::move(program_)), data(std::forward<DATA>(data_)...) {}
+
+  /// The transformed program. May be empty on error.
+  Program program;
+
+  /// Extra output generated by the transforms.
+  DataMap data;
+};
+
+/// Interface for Program transforms
+class Transform : public Castable<Transform> {
+ public:
+  /// Constructor
+  Transform();
+  /// Destructor
+  ~Transform() override;
+
+  /// Runs the transform on `program`, returning the transformation result.
+  /// @param program the source program to transform
+  /// @param data optional extra transform-specific input data
+  /// @returns the transformation result
+  virtual Output Run(const Program* program, const DataMap& data = {}) const;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  virtual bool ShouldRun(const Program* program,
+                         const DataMap& data = {}) const;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  virtual void Run(CloneContext& ctx,
+                   const DataMap& inputs,
+                   DataMap& outputs) const;
+
+  /// Removes the statement `stmt` from the transformed program.
+  /// RemoveStatement handles edge cases, like statements in the initializer and
+  /// continuing of for-loops.
+  /// @param ctx the clone context
+  /// @param stmt the statement to remove when the program is cloned
+  static void RemoveStatement(CloneContext& ctx, const ast::Statement* stmt);
+
+  /// CreateASTTypeFor constructs new ast::Type nodes that reconstructs the
+  /// semantic type `ty`.
+  /// @param ctx the clone context
+  /// @param ty the semantic type to reconstruct
+  /// @returns a ast::Type that when resolved, will produce the semantic type
+  /// `ty`.
+  static const ast::Type* CreateASTTypeFor(CloneContext& ctx,
+                                           const sem::Type* ty);
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_TRANSFORM_H_
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
new file mode 100644
index 0000000..1114ac0
--- /dev/null
+++ b/src/tint/transform/transform_test.cc
@@ -0,0 +1,123 @@
+// 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/transform/transform.h"
+#include "src/tint/clone_context.h"
+#include "src/tint/program_builder.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+// Inherit from Transform so we have access to protected methods
+struct CreateASTTypeForTest : public testing::Test, public Transform {
+  Output Run(const Program*, const DataMap&) const override { return {}; }
+
+  const ast::Type* create(
+      std::function<sem::Type*(ProgramBuilder&)> create_sem_type) {
+    ProgramBuilder sem_type_builder;
+    auto* sem_type = create_sem_type(sem_type_builder);
+    Program program(std::move(sem_type_builder));
+    CloneContext ctx(&ast_type_builder, &program, false);
+    return CreateASTTypeFor(ctx, sem_type);
+  }
+
+  ProgramBuilder ast_type_builder;
+};
+
+TEST_F(CreateASTTypeForTest, Basic) {
+  EXPECT_TRUE(create([](ProgramBuilder& b) {
+                return b.create<sem::I32>();
+              })->Is<ast::I32>());
+  EXPECT_TRUE(create([](ProgramBuilder& b) {
+                return b.create<sem::U32>();
+              })->Is<ast::U32>());
+  EXPECT_TRUE(create([](ProgramBuilder& b) {
+                return b.create<sem::F32>();
+              })->Is<ast::F32>());
+  EXPECT_TRUE(create([](ProgramBuilder& b) {
+                return b.create<sem::Bool>();
+              })->Is<ast::Bool>());
+  EXPECT_TRUE(create([](ProgramBuilder& b) {
+                return b.create<sem::Void>();
+              })->Is<ast::Void>());
+}
+
+TEST_F(CreateASTTypeForTest, Matrix) {
+  auto* mat = create([](ProgramBuilder& b) {
+    auto* column_type = b.create<sem::Vector>(b.create<sem::F32>(), 2u);
+    return b.create<sem::Matrix>(column_type, 3u);
+  });
+  ASSERT_TRUE(mat->Is<ast::Matrix>());
+  ASSERT_TRUE(mat->As<ast::Matrix>()->type->Is<ast::F32>());
+  ASSERT_EQ(mat->As<ast::Matrix>()->columns, 3u);
+  ASSERT_EQ(mat->As<ast::Matrix>()->rows, 2u);
+}
+
+TEST_F(CreateASTTypeForTest, Vector) {
+  auto* vec = create([](ProgramBuilder& b) {
+    return b.create<sem::Vector>(b.create<sem::F32>(), 2);
+  });
+  ASSERT_TRUE(vec->Is<ast::Vector>());
+  ASSERT_TRUE(vec->As<ast::Vector>()->type->Is<ast::F32>());
+  ASSERT_EQ(vec->As<ast::Vector>()->width, 2u);
+}
+
+TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
+  auto* arr = create([](ProgramBuilder& b) {
+    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 32u, 32u);
+  });
+  ASSERT_TRUE(arr->Is<ast::Array>());
+  ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
+  ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 0u);
+
+  auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
+  ASSERT_NE(size, nullptr);
+  EXPECT_EQ(size->ValueAsI32(), 2);
+}
+
+TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
+  auto* arr = create([](ProgramBuilder& b) {
+    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 64u, 32u);
+  });
+  ASSERT_TRUE(arr->Is<ast::Array>());
+  ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
+  ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 1u);
+  ASSERT_TRUE(arr->As<ast::Array>()->attributes[0]->Is<ast::StrideAttribute>());
+  ASSERT_EQ(
+      arr->As<ast::Array>()->attributes[0]->As<ast::StrideAttribute>()->stride,
+      64u);
+
+  auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
+  ASSERT_NE(size, nullptr);
+  EXPECT_EQ(size->ValueAsI32(), 2);
+}
+
+TEST_F(CreateASTTypeForTest, Struct) {
+  auto* str = create([](ProgramBuilder& b) {
+    auto* decl = b.Structure("S", {}, {});
+    return b.create<sem::Struct>(decl, decl->name, sem::StructMemberList{},
+                                 4 /* align */, 4 /* size */,
+                                 4 /* size_no_padding */);
+  });
+  ASSERT_TRUE(str->Is<ast::TypeName>());
+  EXPECT_EQ(ast_type_builder.Symbols().NameFor(str->As<ast::TypeName>()->name),
+            "S");
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/unshadow.cc b/src/tint/transform/unshadow.cc
new file mode 100644
index 0000000..017258f
--- /dev/null
+++ b/src/tint/transform/unshadow.cc
@@ -0,0 +1,99 @@
+// 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/transform/unshadow.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Unshadow);
+
+namespace tint {
+namespace transform {
+
+/// The PIMPL state for the Unshadow transform
+struct Unshadow::State {
+  /// The clone context
+  CloneContext& ctx;
+
+  /// Constructor
+  /// @param context the clone context
+  explicit State(CloneContext& context) : ctx(context) {}
+
+  /// Performs the transformation
+  void Run() {
+    auto& sem = ctx.src->Sem();
+
+    // Maps a variable to its new name.
+    std::unordered_map<const sem::Variable*, Symbol> renamed_to;
+
+    auto rename = [&](const sem::Variable* var) -> const ast::Variable* {
+      auto* decl = var->Declaration();
+      auto name = ctx.src->Symbols().NameFor(decl->symbol);
+      auto symbol = ctx.dst->Symbols().New(name);
+      renamed_to.emplace(var, symbol);
+
+      auto source = ctx.Clone(decl->source);
+      auto* type = ctx.Clone(decl->type);
+      auto* constructor = ctx.Clone(decl->constructor);
+      auto attributes = ctx.Clone(decl->attributes);
+      return ctx.dst->create<ast::Variable>(
+          source, symbol, decl->declared_storage_class, decl->declared_access,
+          type, decl->is_const, decl->is_overridable, constructor, attributes);
+    };
+
+    ctx.ReplaceAll([&](const ast::Variable* var) -> const ast::Variable* {
+      if (auto* local = sem.Get<sem::LocalVariable>(var)) {
+        if (local->Shadows()) {
+          return rename(local);
+        }
+      }
+      if (auto* param = sem.Get<sem::Parameter>(var)) {
+        if (param->Shadows()) {
+          return rename(param);
+        }
+      }
+      return nullptr;
+    });
+    ctx.ReplaceAll([&](const ast::IdentifierExpression* ident)
+                       -> const tint::ast::IdentifierExpression* {
+      if (auto* user = sem.Get<sem::VariableUser>(ident)) {
+        auto it = renamed_to.find(user->Variable());
+        if (it != renamed_to.end()) {
+          return ctx.dst->Expr(it->second);
+        }
+      }
+      return nullptr;
+    });
+    ctx.Clone();
+  }
+};
+
+Unshadow::Unshadow() = default;
+
+Unshadow::~Unshadow() = default;
+
+void Unshadow::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+  State(ctx).Run();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/unshadow.h b/src/tint/transform/unshadow.h
new file mode 100644
index 0000000..f8119a9
--- /dev/null
+++ b/src/tint/transform/unshadow.h
@@ -0,0 +1,50 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_UNSHADOW_H_
+#define SRC_TINT_TRANSFORM_UNSHADOW_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Unshadow is a Transform that renames any variables that shadow another
+/// variable.
+class Unshadow : public Castable<Unshadow, Transform> {
+ public:
+  /// Constructor
+  Unshadow();
+
+  /// Destructor
+  ~Unshadow() override;
+
+ protected:
+  struct State;
+
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_UNSHADOW_H_
diff --git a/src/tint/transform/unshadow_test.cc b/src/tint/transform/unshadow_test.cc
new file mode 100644
index 0000000..b533bd4
--- /dev/null
+++ b/src/tint/transform/unshadow_test.cc
@@ -0,0 +1,609 @@
+// 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/transform/unshadow.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using UnshadowTest = TransformTest;
+
+TEST_F(UnshadowTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, Noop) {
+  auto* src = R"(
+var<private> a : i32;
+
+let b : i32 = 1;
+
+fn F(c : i32) {
+  var d : i32;
+  let e : i32 = 1;
+  {
+    var f : i32;
+    let g : i32 = 1;
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsAlias) {
+  auto* src = R"(
+type a = i32;
+
+fn X() {
+  var a = false;
+}
+
+fn Y() {
+  let a = true;
+}
+)";
+
+  auto* expect = R"(
+type a = i32;
+
+fn X() {
+  var a_1 = false;
+}
+
+fn Y() {
+  let a_2 = true;
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsAlias_OutOfOrder) {
+  auto* src = R"(
+fn X() {
+  var a = false;
+}
+
+fn Y() {
+  let a = true;
+}
+
+type a = i32;
+)";
+
+  auto* expect = R"(
+fn X() {
+  var a_1 = false;
+}
+
+fn Y() {
+  let a_2 = true;
+}
+
+type a = i32;
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsStruct) {
+  auto* src = R"(
+struct a {
+  m : i32;
+};
+
+fn X() {
+  var a = true;
+}
+
+fn Y() {
+  let a = false;
+}
+)";
+
+  auto* expect = R"(
+struct a {
+  m : i32;
+}
+
+fn X() {
+  var a_1 = true;
+}
+
+fn Y() {
+  let a_2 = false;
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsStruct_OutOfOrder) {
+  auto* src = R"(
+fn X() {
+  var a = true;
+}
+
+fn Y() {
+  let a = false;
+}
+
+struct a {
+  m : i32;
+};
+
+)";
+
+  auto* expect = R"(
+fn X() {
+  var a_1 = true;
+}
+
+fn Y() {
+  let a_2 = false;
+}
+
+struct a {
+  m : i32;
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsFunction) {
+  auto* src = R"(
+fn a() {
+  var a = true;
+  var b = false;
+}
+
+fn b() {
+  let a = true;
+  let b = false;
+}
+)";
+
+  auto* expect = R"(
+fn a() {
+  var a_1 = true;
+  var b_1 = false;
+}
+
+fn b() {
+  let a_2 = true;
+  let b_2 = false;
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsFunction_OutOfOrder) {
+  auto* src = R"(
+fn b() {
+  let a = true;
+  let b = false;
+}
+
+fn a() {
+  var a = true;
+  var b = false;
+}
+
+)";
+
+  auto* expect = R"(
+fn b() {
+  let a_1 = true;
+  let b_1 = false;
+}
+
+fn a() {
+  var a_2 = true;
+  var b_2 = false;
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsGlobalVar) {
+  auto* src = R"(
+var<private> a : i32;
+
+fn X() {
+  var a = (a == 123);
+}
+
+fn Y() {
+  let a = (a == 321);
+}
+)";
+
+  auto* expect = R"(
+var<private> a : i32;
+
+fn X() {
+  var a_1 = (a == 123);
+}
+
+fn Y() {
+  let a_2 = (a == 321);
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsGlobalVar_OutOfOrder) {
+  auto* src = R"(
+fn X() {
+  var a = (a == 123);
+}
+
+fn Y() {
+  let a = (a == 321);
+}
+
+var<private> a : i32;
+)";
+
+  auto* expect = R"(
+fn X() {
+  var a_1 = (a == 123);
+}
+
+fn Y() {
+  let a_2 = (a == 321);
+}
+
+var<private> a : i32;
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsGlobalLet) {
+  auto* src = R"(
+let a : i32 = 1;
+
+fn X() {
+  var a = (a == 123);
+}
+
+fn Y() {
+  let a = (a == 321);
+}
+)";
+
+  auto* expect = R"(
+let a : i32 = 1;
+
+fn X() {
+  var a_1 = (a == 123);
+}
+
+fn Y() {
+  let a_2 = (a == 321);
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsGlobalLet_OutOfOrder) {
+  auto* src = R"(
+fn X() {
+  var a = (a == 123);
+}
+
+fn Y() {
+  let a = (a == 321);
+}
+
+let a : i32 = 1;
+)";
+
+  auto* expect = R"(
+fn X() {
+  var a_1 = (a == 123);
+}
+
+fn Y() {
+  let a_2 = (a == 321);
+}
+
+let a : i32 = 1;
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsLocalVar) {
+  auto* src = R"(
+fn X() {
+  var a : i32;
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+)";
+
+  auto* expect = R"(
+fn X() {
+  var a : i32;
+  {
+    var a_1 = (a == 123);
+  }
+  {
+    let a_2 = (a == 321);
+  }
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsLocalLet) {
+  auto* src = R"(
+fn X() {
+  let a = 1;
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+)";
+
+  auto* expect = R"(
+fn X() {
+  let a = 1;
+  {
+    var a_1 = (a == 123);
+  }
+  {
+    let a_2 = (a == 321);
+  }
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, LocalShadowsParam) {
+  auto* src = R"(
+fn F(a : i32) {
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+)";
+
+  auto* expect = R"(
+fn F(a : i32) {
+  {
+    var a_1 = (a == 123);
+  }
+  {
+    let a_2 = (a == 321);
+  }
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsFunction) {
+  auto* src = R"(
+fn a(a : i32) {
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+)";
+
+  auto* expect = R"(
+fn a(a_1 : i32) {
+  {
+    var a_2 = (a_1 == 123);
+  }
+  {
+    let a_3 = (a_1 == 321);
+  }
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsGlobalVar) {
+  auto* src = R"(
+var<private> a : i32;
+
+fn F(a : bool) {
+}
+)";
+
+  auto* expect = R"(
+var<private> a : i32;
+
+fn F(a_1 : bool) {
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsGlobalLet) {
+  auto* src = R"(
+let a : i32 = 1;
+
+fn F(a : bool) {
+}
+)";
+
+  auto* expect = R"(
+let a : i32 = 1;
+
+fn F(a_1 : bool) {
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsGlobalLet_OutOfOrder) {
+  auto* src = R"(
+fn F(a : bool) {
+}
+
+let a : i32 = 1;
+)";
+
+  auto* expect = R"(
+fn F(a_1 : bool) {
+}
+
+let a : i32 = 1;
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsAlias) {
+  auto* src = R"(
+type a = i32;
+
+fn F(a : a) {
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+)";
+
+  auto* expect = R"(
+type a = i32;
+
+fn F(a_1 : a) {
+  {
+    var a_2 = (a_1 == 123);
+  }
+  {
+    let a_3 = (a_1 == 321);
+  }
+}
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnshadowTest, ParamShadowsAlias_OutOfOrder) {
+  auto* src = R"(
+fn F(a : a) {
+  {
+    var a = (a == 123);
+  }
+  {
+    let a = (a == 321);
+  }
+}
+
+type a = i32;
+)";
+
+  auto* expect = R"(
+fn F(a_1 : a) {
+  {
+    var a_2 = (a_1 == 123);
+  }
+  {
+    let a_3 = (a_1 == 321);
+  }
+}
+
+type a = i32;
+)";
+
+  auto got = Run<Unshadow>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/utils/hoist_to_decl_before.cc b/src/tint/transform/utils/hoist_to_decl_before.cc
new file mode 100644
index 0000000..3f66150
--- /dev/null
+++ b/src/tint/transform/utils/hoist_to_decl_before.cc
@@ -0,0 +1,327 @@
+// 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/utils/hoist_to_decl_before.h"
+
+#include <unordered_map>
+
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/reverse.h"
+
+namespace tint::transform {
+
+/// Private implementation of HoistToDeclBefore transform
+class HoistToDeclBefore::State {
+  CloneContext& ctx;
+  ProgramBuilder& b;
+
+  /// Holds information about a for-loop that needs to be decomposed into a
+  /// loop, so that declaration statements can be inserted before the
+  /// condition expression or continuing statement.
+  struct LoopInfo {
+    ast::StatementList cond_decls;
+    ast::StatementList cont_decls;
+  };
+
+  /// Holds information about 'if's with 'else-if' statements that need to be
+  /// decomposed into 'if {else}' so that declaration statements can be
+  /// inserted before the condition expression.
+  struct IfInfo {
+    /// Info for each else-if that needs decomposing
+    struct ElseIfInfo {
+      /// Decls to insert before condition
+      ast::StatementList cond_decls;
+    };
+
+    /// 'else if's that need to be decomposed to 'else { if }'
+    std::unordered_map<const sem::ElseStatement*, ElseIfInfo> else_ifs;
+  };
+
+  /// For-loops that need to be decomposed to loops.
+  std::unordered_map<const sem::ForLoopStatement*, LoopInfo> loops;
+
+  /// If statements with 'else if's that need to be decomposed to 'else { if
+  /// }'
+  std::unordered_map<const sem::IfStatement*, IfInfo> ifs;
+
+  // Inserts `decl` before `sem_expr`, possibly marking a for-loop to be
+  // converted to a loop, or an else-if to an else { if }.
+  bool InsertBefore(const sem::Expression* sem_expr,
+                    const ast::VariableDeclStatement* decl) {
+    auto* sem_stmt = sem_expr->Stmt();
+    auto* stmt = sem_stmt->Declaration();
+
+    if (auto* else_if = sem_stmt->As<sem::ElseStatement>()) {
+      // Expression used in 'else if' condition.
+      // Need to convert 'else if' to 'else { if }'.
+      auto& if_info = ifs[else_if->Parent()->As<sem::IfStatement>()];
+      if_info.else_ifs[else_if].cond_decls.push_back(decl);
+      return true;
+    }
+
+    if (auto* fl = sem_stmt->As<sem::ForLoopStatement>()) {
+      // Expression used in for-loop condition.
+      // For-loop needs to be decomposed to a loop.
+      loops[fl].cond_decls.emplace_back(decl);
+      return true;
+    }
+
+    auto* parent = sem_stmt->Parent();  // The statement's parent
+    if (auto* block = parent->As<sem::BlockStatement>()) {
+      // Expression's statement sits in a block. Simple case.
+      // Insert the decl before the parent statement
+      ctx.InsertBefore(block->Declaration()->statements, stmt, decl);
+      return true;
+    }
+
+    if (auto* fl = parent->As<sem::ForLoopStatement>()) {
+      // Expression is used in a for-loop. These require special care.
+      if (fl->Declaration()->initializer == stmt) {
+        // Expression used in for-loop initializer.
+        // Insert the let above the for-loop.
+        ctx.InsertBefore(fl->Block()->Declaration()->statements,
+                         fl->Declaration(), decl);
+        return true;
+      }
+
+      if (fl->Declaration()->continuing == stmt) {
+        // Expression used in for-loop continuing.
+        // For-loop needs to be decomposed to a loop.
+        loops[fl].cont_decls.emplace_back(decl);
+        return true;
+      }
+
+      TINT_ICE(Transform, b.Diagnostics())
+          << "unhandled use of expression in for-loop";
+      return false;
+    }
+
+    TINT_ICE(Transform, b.Diagnostics())
+        << "unhandled expression parent statement type: "
+        << parent->TypeInfo().name;
+    return false;
+  }
+
+  // Converts any for-loops marked for conversion to loops, inserting
+  // registered declaration statements before the condition or continuing
+  // statement.
+  void ForLoopsToLoops() {
+    if (loops.empty()) {
+      return;
+    }
+
+    // At least one for-loop needs to be transformed into a loop.
+    ctx.ReplaceAll(
+        [&](const ast::ForLoopStatement* stmt) -> const ast::Statement* {
+          auto& sem = ctx.src->Sem();
+
+          if (auto* fl = sem.Get(stmt)) {
+            if (auto it = loops.find(fl); it != loops.end()) {
+              auto& info = it->second;
+              auto* for_loop = fl->Declaration();
+              // For-loop needs to be decomposed to a loop.
+              // Build the loop body's statements.
+              // Start with any let declarations for the conditional
+              // expression.
+              auto body_stmts = info.cond_decls;
+              // If the for-loop has a condition, emit this next as:
+              //   if (!cond) { break; }
+              if (auto* cond = for_loop->condition) {
+                // !condition
+                auto* not_cond = b.create<ast::UnaryOpExpression>(
+                    ast::UnaryOp::kNot, ctx.Clone(cond));
+                // { break; }
+                auto* break_body = b.Block(b.create<ast::BreakStatement>());
+                // if (!condition) { break; }
+                body_stmts.emplace_back(b.If(not_cond, break_body));
+              }
+              // Next emit the for-loop body
+              body_stmts.emplace_back(ctx.Clone(for_loop->body));
+
+              // Finally create the continuing block if there was one.
+              const ast::BlockStatement* continuing = nullptr;
+              if (auto* cont = for_loop->continuing) {
+                // Continuing block starts with any let declarations used by
+                // the continuing.
+                auto cont_stmts = info.cont_decls;
+                cont_stmts.emplace_back(ctx.Clone(cont));
+                continuing = b.Block(cont_stmts);
+              }
+
+              auto* body = b.Block(body_stmts);
+              auto* loop = b.Loop(body, continuing);
+              if (auto* init = for_loop->initializer) {
+                return b.Block(ctx.Clone(init), loop);
+              }
+              return loop;
+            }
+          }
+          return nullptr;
+        });
+  }
+
+  void ElseIfsToElseWithNestedIfs() {
+    if (ifs.empty()) {
+      return;
+    }
+
+    ctx.ReplaceAll([&](const ast::IfStatement* if_stmt)  //
+                   -> const ast::IfStatement* {
+      auto& sem = ctx.src->Sem();
+      auto* sem_if = sem.Get(if_stmt);
+      if (!sem_if) {
+        return nullptr;
+      }
+
+      auto it = ifs.find(sem_if);
+      if (it == ifs.end()) {
+        return nullptr;
+      }
+      auto& if_info = it->second;
+
+      // This if statement has "else if"s that need to be converted to "else
+      // { if }"s
+
+      ast::ElseStatementList next_else_stmts;
+      next_else_stmts.reserve(if_stmt->else_statements.size());
+
+      for (auto* else_stmt : utils::Reverse(if_stmt->else_statements)) {
+        if (else_stmt->condition == nullptr) {
+          // The last 'else', keep as is
+          next_else_stmts.insert(next_else_stmts.begin(), ctx.Clone(else_stmt));
+
+        } else {
+          auto* sem_else_if = sem.Get(else_stmt);
+
+          auto it2 = if_info.else_ifs.find(sem_else_if);
+          if (it2 == if_info.else_ifs.end()) {
+            // 'else if' we don't need to modify (no decls to insert), so
+            // keep as is
+            next_else_stmts.insert(next_else_stmts.begin(),
+                                   ctx.Clone(else_stmt));
+
+          } else {
+            // 'else if' we need to replace with 'else <decls> { if }'
+            auto& else_if_info = it2->second;
+
+            // Build the else body's statements, starting with let decls for
+            // the conditional expression
+            auto& body_stmts = else_if_info.cond_decls;
+
+            // Build nested if
+            auto* cond = ctx.Clone(else_stmt->condition);
+            auto* body = ctx.Clone(else_stmt->body);
+            body_stmts.emplace_back(b.If(cond, body, next_else_stmts));
+
+            // Build else
+            auto* else_with_nested_if = b.Else(b.Block(body_stmts));
+
+            // This will be used in parent if (either another nested if, or
+            // top-level if)
+            next_else_stmts = {else_with_nested_if};
+          }
+        }
+      }
+
+      // Build a new top-level if with new else statements
+      if (next_else_stmts.empty()) {
+        TINT_ICE(Transform, b.Diagnostics())
+            << "Expected else statements to insert into new if";
+      }
+      auto* cond = ctx.Clone(if_stmt->condition);
+      auto* body = ctx.Clone(if_stmt->body);
+      auto* new_if = b.If(cond, body, next_else_stmts);
+      return new_if;
+    });
+  }
+
+ public:
+  /// Constructor
+  /// @param ctx_in the clone context
+  explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
+
+  /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
+  /// before `before_expr`.
+  /// @param before_expr expression to insert `expr` before
+  /// @param expr expression to hoist
+  /// @param as_const hoist to `let` if true, otherwise to `var`
+  /// @param decl_name optional name to use for the variable/constant name
+  /// @return true on success
+  bool HoistToDeclBefore(const sem::Expression* before_expr,
+                         const ast::Expression* expr,
+                         bool as_const,
+                         const char* decl_name = "") {
+    auto name = b.Symbols().New(decl_name);
+
+    auto* sem_expr = ctx.src->Sem().Get(expr);
+    bool is_ref =
+        sem_expr &&
+        !sem_expr->Is<sem::VariableUser>()  // Don't need to take a ref to a var
+        && sem_expr->Type()->Is<sem::Reference>();
+
+    auto* expr_clone = ctx.Clone(expr);
+    if (is_ref) {
+      expr_clone = b.AddressOf(expr_clone);
+    }
+
+    // Construct the let/var that holds the hoisted expr
+    auto* v = as_const ? b.Const(name, nullptr, expr_clone)
+                       : b.Var(name, nullptr, expr_clone);
+    auto* decl = b.Decl(v);
+
+    if (!InsertBefore(before_expr, decl)) {
+      return false;
+    }
+
+    // Replace the initializer expression with a reference to the let
+    const ast::Expression* new_expr = b.Expr(name);
+    if (is_ref) {
+      new_expr = b.Deref(new_expr);
+    }
+    ctx.Replace(expr, new_expr);
+    return true;
+  }
+
+  /// Applies any scheduled insertions from previous calls to Add() to
+  /// CloneContext. Call this once before ctx.Clone().
+  /// @return true on success
+  bool Apply() {
+    ForLoopsToLoops();
+    ElseIfsToElseWithNestedIfs();
+    return true;
+  }
+};
+
+HoistToDeclBefore::HoistToDeclBefore(CloneContext& ctx)
+    : state_(std::make_unique<State>(ctx)) {}
+
+HoistToDeclBefore::~HoistToDeclBefore() {}
+
+bool HoistToDeclBefore::Add(const sem::Expression* before_expr,
+                            const ast::Expression* expr,
+                            bool as_const,
+                            const char* decl_name) {
+  return state_->HoistToDeclBefore(before_expr, expr, as_const, decl_name);
+}
+
+bool HoistToDeclBefore::Apply() {
+  return state_->Apply();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/utils/hoist_to_decl_before.h b/src/tint/transform/utils/hoist_to_decl_before.h
new file mode 100644
index 0000000..8f35a09
--- /dev/null
+++ b/src/tint/transform/utils/hoist_to_decl_before.h
@@ -0,0 +1,61 @@
+// 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_UTILS_HOIST_TO_DECL_BEFORE_H_
+#define SRC_TINT_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
+
+#include <memory>
+
+#include "src/tint/sem/expression.h"
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// Utility class that can be used to hoist expressions before other
+/// expressions, possibly converting 'for' loops to 'loop's and 'else if to
+// 'else if'.
+class HoistToDeclBefore {
+ public:
+  /// Constructor
+  /// @param ctx the clone context
+  explicit HoistToDeclBefore(CloneContext& ctx);
+
+  /// Destructor
+  ~HoistToDeclBefore();
+
+  /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
+  /// before `before_expr`.
+  /// @param before_expr expression to insert `expr` before
+  /// @param expr expression to hoist
+  /// @param as_const hoist to `let` if true, otherwise to `var`
+  /// @param decl_name optional name to use for the variable/constant name
+  /// @return true on success
+  bool Add(const sem::Expression* before_expr,
+           const ast::Expression* expr,
+           bool as_const,
+           const char* decl_name = "");
+
+  /// Applies any scheduled insertions from previous calls to Add() to
+  /// CloneContext. Call this once before ctx.Clone().
+  /// @return true on success
+  bool Apply();
+
+ private:
+  class State;
+  std::unique_ptr<State> state_;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
diff --git a/src/tint/transform/utils/hoist_to_decl_before_test.cc b/src/tint/transform/utils/hoist_to_decl_before_test.cc
new file mode 100644
index 0000000..2e6f0ab
--- /dev/null
+++ b/src/tint/transform/utils/hoist_to_decl_before_test.cc
@@ -0,0 +1,291 @@
+// 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 <utility>
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+
+namespace tint::transform {
+namespace {
+
+using HoistToDeclBeforeTest = ::testing::Test;
+
+TEST_F(HoistToDeclBeforeTest, VarInit) {
+  // fn f() {
+  //     var a = 1;
+  // }
+  ProgramBuilder b;
+  auto* expr = b.Expr(1);
+  auto* var = b.Decl(b.Var("a", nullptr, expr));
+  b.Func("f", {}, b.ty.void_(), {var});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = 1;
+  var a = tint_symbol;
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopInit) {
+  // fn f() {
+  //     for(var a = 1; true; ) {
+  //     }
+  // }
+  ProgramBuilder b;
+  auto* expr = b.Expr(1);
+  auto* s =
+      b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), {}, b.Block());
+  b.Func("f", {}, b.ty.void_(), {s});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  let tint_symbol = 1;
+  for(var a = tint_symbol; true; ) {
+  }
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopCond) {
+  // fn f() {
+  //     var a : bool;
+  //     for(; a; ) {
+  //     }
+  // }
+  ProgramBuilder b;
+  auto* var = b.Decl(b.Var("a", b.ty.bool_()));
+  auto* expr = b.Expr("a");
+  auto* s = b.For({}, expr, {}, b.Block());
+  b.Func("f", {}, b.ty.void_(), {var, s});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  var a : bool;
+  loop {
+    let tint_symbol = a;
+    if (!(tint_symbol)) {
+      break;
+    }
+    {
+    }
+  }
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopCont) {
+  // fn f() {
+  //     for(; true; var a = 1) {
+  //     }
+  // }
+  ProgramBuilder b;
+  auto* expr = b.Expr(1);
+  auto* s =
+      b.For({}, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+  b.Func("f", {}, b.ty.void_(), {s});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    if (!(true)) {
+      break;
+    }
+    {
+    }
+
+    continuing {
+      let tint_symbol = 1;
+      var a = tint_symbol;
+    }
+  }
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ElseIf) {
+  // fn f() {
+  //     var a : bool;
+  //     if (true) {
+  //     } else if (a) {
+  //     } else {
+  //     }
+  // }
+  ProgramBuilder b;
+  auto* var = b.Decl(b.Var("a", b.ty.bool_()));
+  auto* expr = b.Expr("a");
+  auto* s = b.If(b.Expr(true), b.Block(),  //
+                 b.Else(expr, b.Block()),  //
+                 b.Else(b.Block()));
+  b.Func("f", {}, b.ty.void_(), {var, s});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  var a : bool;
+  if (true) {
+  } else {
+    let tint_symbol = a;
+    if (tint_symbol) {
+    } else {
+    }
+  }
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, Array1D) {
+  // fn f() {
+  //     var a : array<i32, 10>;
+  //     var b = a[0];
+  // }
+  ProgramBuilder b;
+  auto* var1 = b.Decl(b.Var("a", b.ty.array<ProgramBuilder::i32, 10>()));
+  auto* expr = b.IndexAccessor("a", 0);
+  auto* var2 = b.Decl(b.Var("b", nullptr, expr));
+  b.Func("f", {}, b.ty.void_(), {var1, var2});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  var a : array<i32, 10>;
+  let tint_symbol = &(a[0]);
+  var b = *(tint_symbol);
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, Array2D) {
+  // fn f() {
+  //     var a : array<array<i32, 10>, 10>;
+  //     var b = a[0][0];
+  // }
+  ProgramBuilder b;
+
+  auto* var1 =
+      b.Decl(b.Var("a", b.ty.array(b.ty.array<ProgramBuilder::i32, 10>(), 10)));
+  auto* expr = b.IndexAccessor(b.IndexAccessor("a", 0), 0);
+  auto* var2 = b.Decl(b.Var("b", nullptr, expr));
+  b.Func("f", {}, b.ty.void_(), {var1, var2});
+
+  Program original(std::move(b));
+  ProgramBuilder cloned_b;
+  CloneContext ctx(&cloned_b, &original);
+
+  HoistToDeclBefore hoistToDeclBefore(ctx);
+  auto* sem_expr = ctx.src->Sem().Get(expr);
+  hoistToDeclBefore.Add(sem_expr, expr, true);
+  hoistToDeclBefore.Apply();
+
+  ctx.Clone();
+  Program cloned(std::move(cloned_b));
+
+  auto* expect = R"(
+fn f() {
+  var a : array<array<i32, 10>, 10>;
+  let tint_symbol = &(a[0][0]);
+  var b = *(tint_symbol);
+}
+)";
+
+  EXPECT_EQ(expect, str(cloned));
+}
+
+}  // namespace
+}  // namespace tint::transform
diff --git a/src/tint/transform/var_for_dynamic_index.cc b/src/tint/transform/var_for_dynamic_index.cc
new file mode 100644
index 0000000..ccd1215
--- /dev/null
+++ b/src/tint/transform/var_for_dynamic_index.cc
@@ -0,0 +1,68 @@
+// 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/var_for_dynamic_index.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+
+namespace tint::transform {
+
+VarForDynamicIndex::VarForDynamicIndex() = default;
+
+VarForDynamicIndex::~VarForDynamicIndex() = default;
+
+void VarForDynamicIndex::Run(CloneContext& ctx,
+                             const DataMap&,
+                             DataMap&) const {
+  HoistToDeclBefore hoist_to_decl_before(ctx);
+
+  // Extracts array and matrix values that are dynamically indexed to a
+  // temporary `var` local that is then indexed.
+  auto dynamic_index_to_var =
+      [&](const ast::IndexAccessorExpression* access_expr) {
+        auto* index_expr = access_expr->index;
+        auto* object_expr = access_expr->object;
+        auto& sem = ctx.src->Sem();
+
+        if (sem.Get(index_expr)->ConstantValue()) {
+          // Index expression resolves to a compile time value.
+          // As this isn't a dynamic index, we can ignore this.
+          return true;
+        }
+
+        auto* indexed = sem.Get(object_expr);
+        if (!indexed->Type()->IsAnyOf<sem::Array, sem::Matrix>()) {
+          // We only care about array and matrices.
+          return true;
+        }
+
+        // TODO(bclayton): group multiple accesses in the same object.
+        // e.g. arr[i] + arr[i+1] // Don't create two vars for this
+        return hoist_to_decl_before.Add(indexed, object_expr, false,
+                                        "var_for_index");
+      };
+
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* access_expr = node->As<ast::IndexAccessorExpression>()) {
+      if (!dynamic_index_to_var(access_expr)) {
+        return;
+      }
+    }
+  }
+
+  hoist_to_decl_before.Apply();
+  ctx.Clone();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/var_for_dynamic_index.h b/src/tint/transform/var_for_dynamic_index.h
new file mode 100644
index 0000000..e7e2815
--- /dev/null
+++ b/src/tint/transform/var_for_dynamic_index.h
@@ -0,0 +1,48 @@
+// 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_VAR_FOR_DYNAMIC_INDEX_H_
+#define SRC_TINT_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// A transform that extracts array and matrix values that are dynamically
+/// indexed to a temporary `var` local before performing the index. This
+/// transform is used by the SPIR-V writer as there is no SPIR-V instruction
+/// that can dynamically index a non-pointer composite.
+class VarForDynamicIndex : public Transform {
+ public:
+  /// Constructor
+  VarForDynamicIndex();
+
+  /// Destructor
+  ~VarForDynamicIndex() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
diff --git a/src/tint/transform/var_for_dynamic_index_test.cc b/src/tint/transform/var_for_dynamic_index_test.cc
new file mode 100644
index 0000000..2bd8a6e
--- /dev/null
+++ b/src/tint/transform/var_for_dynamic_index_test.cc
@@ -0,0 +1,553 @@
+// 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/transform/var_for_dynamic_index.h"
+#include "src/tint/transform/for_loop_to_loop.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using VarForDynamicIndexTest = TransformTest;
+
+TEST_F(VarForDynamicIndexTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<ForLoopToLoop, VarForDynamicIndex>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexDynamic) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 4>(1, 2, 3, 4);
+  let x = p[i];
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 4>(1, 2, 3, 4);
+  var var_for_index = p;
+  let x = var_for_index[i];
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexDynamic) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  let x = p[i];
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  var var_for_index = p;
+  let x = var_for_index[i];
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexDynamicChain) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  var j : i32;
+  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+  let x = p[i][j];
+}
+)";
+
+  // TODO(bclayton): Optimize this case:
+  // This output is not as efficient as it could be.
+  // We only actually need to hoist the inner-most array to a `var`
+  // (`var_for_index`), as later indexing operations will be working with
+  // references, not values.
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  var j : i32;
+  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+  var var_for_index = p;
+  var var_for_index_1 = var_for_index[i];
+  let x = var_for_index_1[j];
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopInit) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+  for(let x = p[i]; ; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+  var var_for_index = p;
+  for(let x = var_for_index[i]; ; ) {
+    break;
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopInit) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  for(let x = p[i]; ; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  var var_for_index = p;
+  for(let x = var_for_index[i]; ; ) {
+    break;
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopCond) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  for(; p[i] < 3; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  loop {
+    var var_for_index = p;
+    if (!((var_for_index[i] < 3))) {
+      break;
+    }
+    {
+      break;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCond) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  for(; p[i].x < 3.0; ) {
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  loop {
+    var var_for_index = p;
+    if (!((var_for_index[i].x < 3.0))) {
+      break;
+    }
+    {
+      break;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCondWithNestedIndex) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  for(; p[i].x < 3.0; ) {
+    if (p[i].x < 1.0) {
+        var marker = 1;
+    }
+    break;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  loop {
+    var var_for_index = p;
+    if (!((var_for_index[i].x < 3.0))) {
+      break;
+    }
+    {
+      var var_for_index_1 = p;
+      if ((var_for_index_1[i].x < 1.0)) {
+        var marker = 1;
+      }
+      break;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIf) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  if (false) {
+    var marker = 0;
+  } else if (p[i] < 3) {
+    var marker = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  if (false) {
+    var marker = 0;
+  } else {
+    var var_for_index = p;
+    if ((var_for_index[i] < 3)) {
+      var marker = 1;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIfChain) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (p[i] < 3) {
+    var marker = 2;
+  } else if (p[i] < 4) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = array<i32, 2>(1, 2);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    var var_for_index = p;
+    if ((var_for_index[i] < 3)) {
+      var marker = 2;
+    } else {
+      var var_for_index_1 = p;
+      if ((var_for_index_1[i] < 4)) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIf) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  if (false) {
+    var marker_if = 1;
+  } else if (p[i].x < 3.0) {
+    var marker_else_if = 1;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  if (false) {
+    var marker_if = 1;
+  } else {
+    var var_for_index = p;
+    if ((var_for_index[i].x < 3.0)) {
+      var marker_else_if = 1;
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIfChain) {
+  auto* src = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (p[i].x < 3.0) {
+    var marker = 2;
+  } else if (p[i].y < 3.0) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    var var_for_index = p;
+    if ((var_for_index[i].x < 3.0)) {
+      var marker = 2;
+    } else {
+      var var_for_index_1 = p;
+      if ((var_for_index_1[i].y < 3.0)) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexLiteral) {
+  auto* src = R"(
+fn f() {
+  let p = array<i32, 4>(1, 2, 3, 4);
+  let x = p[1];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexLiteral) {
+  auto* src = R"(
+fn f() {
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  let x = p[1];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexConstantLet) {
+  auto* src = R"(
+fn f() {
+  let p = array<i32, 4>(1, 2, 3, 4);
+  let c = 1;
+  let x = p[c];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexConstantLet) {
+  auto* src = R"(
+fn f() {
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  let c = 1;
+  let x = p[c];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexLiteralChain) {
+  auto* src = R"(
+fn f() {
+  let a = array<i32, 2>(1, 2);
+  let b = array<i32, 2>(3, 4);
+  let p = array<array<i32, 2>, 2>(a, b);
+  let x = p[0][1];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexLiteralChain) {
+  auto* src = R"(
+fn f() {
+  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+  let x = p[0][1];
+}
+)";
+
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<VarForDynamicIndex>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/transform/vectorize_scalar_matrix_constructors.cc
new file mode 100644
index 0000000..bccc50b
--- /dev/null
+++ b/src/tint/transform/vectorize_scalar_matrix_constructors.cc
@@ -0,0 +1,97 @@
+// 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/transform/vectorize_scalar_matrix_constructors.h"
+
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/type_constructor.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::VectorizeScalarMatrixConstructors);
+
+namespace tint {
+namespace transform {
+
+VectorizeScalarMatrixConstructors::VectorizeScalarMatrixConstructors() =
+    default;
+
+VectorizeScalarMatrixConstructors::~VectorizeScalarMatrixConstructors() =
+    default;
+
+bool VectorizeScalarMatrixConstructors::ShouldRun(const Program* program,
+                                                  const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (auto* call = program->Sem().Get<sem::Call>(node)) {
+      if (call->Target()->Is<sem::TypeConstructor>() &&
+          call->Type()->Is<sem::Matrix>()) {
+        auto& args = call->Arguments();
+        if (args.size() > 0 && args[0]->Type()->is_scalar()) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+void VectorizeScalarMatrixConstructors::Run(CloneContext& ctx,
+                                            const DataMap&,
+                                            DataMap&) const {
+  ctx.ReplaceAll(
+      [&](const ast::CallExpression* expr) -> const ast::CallExpression* {
+        auto* call = ctx.src->Sem().Get(expr);
+        auto* ty_ctor = call->Target()->As<sem::TypeConstructor>();
+        if (!ty_ctor) {
+          return nullptr;
+        }
+        // Check if this is a matrix constructor with scalar arguments.
+        auto* mat_type = call->Type()->As<sem::Matrix>();
+        if (!mat_type) {
+          return nullptr;
+        }
+
+        auto& args = call->Arguments();
+        if (args.size() == 0) {
+          return nullptr;
+        }
+        if (!args[0]->Type()->is_scalar()) {
+          return nullptr;
+        }
+
+        // Build a list of vector expressions for each column.
+        ast::ExpressionList columns;
+        for (uint32_t c = 0; c < mat_type->columns(); c++) {
+          // Build a list of scalar expressions for each value in the column.
+          ast::ExpressionList row_values;
+          for (uint32_t r = 0; r < mat_type->rows(); r++) {
+            row_values.push_back(
+                ctx.Clone(args[c * mat_type->rows() + r]->Declaration()));
+          }
+
+          // Construct the column vector.
+          auto* col = ctx.dst->vec(CreateASTTypeFor(ctx, mat_type->type()),
+                                   mat_type->rows(), row_values);
+          columns.push_back(col);
+        }
+        return ctx.dst->Construct(CreateASTTypeFor(ctx, mat_type), columns);
+      });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/vectorize_scalar_matrix_constructors.h b/src/tint/transform/vectorize_scalar_matrix_constructors.h
new file mode 100644
index 0000000..44754e1
--- /dev/null
+++ b/src/tint/transform/vectorize_scalar_matrix_constructors.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
+#define SRC_TINT_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// A transform that converts scalar matrix constructors to the vector form.
+class VectorizeScalarMatrixConstructors
+    : public Castable<VectorizeScalarMatrixConstructors, Transform> {
+ public:
+  /// Constructor
+  VectorizeScalarMatrixConstructors();
+
+  /// Destructor
+  ~VectorizeScalarMatrixConstructors() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
diff --git a/src/tint/transform/vectorize_scalar_matrix_constructors_test.cc b/src/tint/transform/vectorize_scalar_matrix_constructors_test.cc
new file mode 100644
index 0000000..d6d496c
--- /dev/null
+++ b/src/tint/transform/vectorize_scalar_matrix_constructors_test.cc
@@ -0,0 +1,124 @@
+// 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/transform/vectorize_scalar_matrix_constructors.h"
+
+#include <string>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using VectorizeScalarMatrixConstructorsTest =
+    TransformTestWithParam<std::pair<uint32_t, uint32_t>>;
+
+TEST_F(VectorizeScalarMatrixConstructorsTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
+}
+
+TEST_P(VectorizeScalarMatrixConstructorsTest, Basic) {
+  uint32_t cols = GetParam().first;
+  uint32_t rows = GetParam().second;
+  std::string mat_type =
+      "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
+  std::string vec_type = "vec" + std::to_string(rows) + "<f32>";
+  std::string scalar_values;
+  std::string vector_values;
+  for (uint32_t c = 0; c < cols; c++) {
+    if (c > 0) {
+      vector_values += ", ";
+      scalar_values += ", ";
+    }
+    vector_values += vec_type + "(";
+    for (uint32_t r = 0; r < rows; r++) {
+      if (r > 0) {
+        scalar_values += ", ";
+        vector_values += ", ";
+      }
+      auto value = std::to_string(c * rows + r) + ".0";
+      scalar_values += value;
+      vector_values += value;
+    }
+    vector_values += ")";
+  }
+
+  std::string tmpl = R"(
+@stage(fragment)
+fn main() {
+  let m = ${matrix}(${values});
+}
+)";
+  tmpl = utils::ReplaceAll(tmpl, "${matrix}", mat_type);
+  auto src = utils::ReplaceAll(tmpl, "${values}", scalar_values);
+  auto expect = utils::ReplaceAll(tmpl, "${values}", vector_values);
+
+  EXPECT_TRUE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
+
+  auto got = Run<VectorizeScalarMatrixConstructors>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(VectorizeScalarMatrixConstructorsTest, NonScalarConstructors) {
+  uint32_t cols = GetParam().first;
+  uint32_t rows = GetParam().second;
+  std::string mat_type =
+      "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
+  std::string vec_type = "vec" + std::to_string(rows) + "<f32>";
+  std::string columns;
+  for (uint32_t c = 0; c < cols; c++) {
+    if (c > 0) {
+      columns += ", ";
+    }
+    columns += vec_type + "()";
+  }
+
+  std::string tmpl = R"(
+@stage(fragment)
+fn main() {
+  let m = ${matrix}(${columns});
+}
+)";
+  tmpl = utils::ReplaceAll(tmpl, "${matrix}", mat_type);
+  auto src = utils::ReplaceAll(tmpl, "${columns}", columns);
+  auto expect = src;
+
+  EXPECT_FALSE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
+
+  auto got = Run<VectorizeScalarMatrixConstructors>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+INSTANTIATE_TEST_SUITE_P(VectorizeScalarMatrixConstructorsTest,
+                         VectorizeScalarMatrixConstructorsTest,
+                         testing::Values(std::make_pair(2, 2),
+                                         std::make_pair(2, 3),
+                                         std::make_pair(2, 4),
+                                         std::make_pair(3, 2),
+                                         std::make_pair(3, 3),
+                                         std::make_pair(3, 4),
+                                         std::make_pair(4, 2),
+                                         std::make_pair(4, 3),
+                                         std::make_pair(4, 4)));
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
new file mode 100644
index 0000000..e515f04
--- /dev/null
+++ b/src/tint/transform/vertex_pulling.cc
@@ -0,0 +1,962 @@
+// Copyright 2020 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/vertex_pulling.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/math.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config);
+
+namespace tint {
+namespace transform {
+
+namespace {
+
+/// The base type of a component.
+/// The format type is either this type or a vector of this type.
+enum class BaseType {
+  kInvalid,
+  kU32,
+  kI32,
+  kF32,
+};
+
+/// Writes the BaseType to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param format the BaseType to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, BaseType format) {
+  switch (format) {
+    case BaseType::kInvalid:
+      return out << "invalid";
+    case BaseType::kU32:
+      return out << "u32";
+    case BaseType::kI32:
+      return out << "i32";
+    case BaseType::kF32:
+      return out << "f32";
+  }
+  return out << "<unknown>";
+}
+
+/// Writes the VertexFormat to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param format the VertexFormat to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, VertexFormat format) {
+  switch (format) {
+    case VertexFormat::kUint8x2:
+      return out << "uint8x2";
+    case VertexFormat::kUint8x4:
+      return out << "uint8x4";
+    case VertexFormat::kSint8x2:
+      return out << "sint8x2";
+    case VertexFormat::kSint8x4:
+      return out << "sint8x4";
+    case VertexFormat::kUnorm8x2:
+      return out << "unorm8x2";
+    case VertexFormat::kUnorm8x4:
+      return out << "unorm8x4";
+    case VertexFormat::kSnorm8x2:
+      return out << "snorm8x2";
+    case VertexFormat::kSnorm8x4:
+      return out << "snorm8x4";
+    case VertexFormat::kUint16x2:
+      return out << "uint16x2";
+    case VertexFormat::kUint16x4:
+      return out << "uint16x4";
+    case VertexFormat::kSint16x2:
+      return out << "sint16x2";
+    case VertexFormat::kSint16x4:
+      return out << "sint16x4";
+    case VertexFormat::kUnorm16x2:
+      return out << "unorm16x2";
+    case VertexFormat::kUnorm16x4:
+      return out << "unorm16x4";
+    case VertexFormat::kSnorm16x2:
+      return out << "snorm16x2";
+    case VertexFormat::kSnorm16x4:
+      return out << "snorm16x4";
+    case VertexFormat::kFloat16x2:
+      return out << "float16x2";
+    case VertexFormat::kFloat16x4:
+      return out << "float16x4";
+    case VertexFormat::kFloat32:
+      return out << "float32";
+    case VertexFormat::kFloat32x2:
+      return out << "float32x2";
+    case VertexFormat::kFloat32x3:
+      return out << "float32x3";
+    case VertexFormat::kFloat32x4:
+      return out << "float32x4";
+    case VertexFormat::kUint32:
+      return out << "uint32";
+    case VertexFormat::kUint32x2:
+      return out << "uint32x2";
+    case VertexFormat::kUint32x3:
+      return out << "uint32x3";
+    case VertexFormat::kUint32x4:
+      return out << "uint32x4";
+    case VertexFormat::kSint32:
+      return out << "sint32";
+    case VertexFormat::kSint32x2:
+      return out << "sint32x2";
+    case VertexFormat::kSint32x3:
+      return out << "sint32x3";
+    case VertexFormat::kSint32x4:
+      return out << "sint32x4";
+  }
+  return out << "<unknown>";
+}
+
+/// A vertex attribute data format.
+struct DataType {
+  BaseType base_type;
+  uint32_t width;  // 1 for scalar, 2+ for a vector
+};
+
+DataType DataTypeOf(const sem::Type* ty) {
+  if (ty->Is<sem::I32>()) {
+    return {BaseType::kI32, 1};
+  }
+  if (ty->Is<sem::U32>()) {
+    return {BaseType::kU32, 1};
+  }
+  if (ty->Is<sem::F32>()) {
+    return {BaseType::kF32, 1};
+  }
+  if (auto* vec = ty->As<sem::Vector>()) {
+    return {DataTypeOf(vec->type()).base_type, vec->Width()};
+  }
+  return {BaseType::kInvalid, 0};
+}
+
+DataType DataTypeOf(VertexFormat format) {
+  switch (format) {
+    case VertexFormat::kUint32:
+      return {BaseType::kU32, 1};
+    case VertexFormat::kUint8x2:
+    case VertexFormat::kUint16x2:
+    case VertexFormat::kUint32x2:
+      return {BaseType::kU32, 2};
+    case VertexFormat::kUint32x3:
+      return {BaseType::kU32, 3};
+    case VertexFormat::kUint8x4:
+    case VertexFormat::kUint16x4:
+    case VertexFormat::kUint32x4:
+      return {BaseType::kU32, 4};
+    case VertexFormat::kSint32:
+      return {BaseType::kI32, 1};
+    case VertexFormat::kSint8x2:
+    case VertexFormat::kSint16x2:
+    case VertexFormat::kSint32x2:
+      return {BaseType::kI32, 2};
+    case VertexFormat::kSint32x3:
+      return {BaseType::kI32, 3};
+    case VertexFormat::kSint8x4:
+    case VertexFormat::kSint16x4:
+    case VertexFormat::kSint32x4:
+      return {BaseType::kI32, 4};
+    case VertexFormat::kFloat32:
+      return {BaseType::kF32, 1};
+    case VertexFormat::kUnorm8x2:
+    case VertexFormat::kSnorm8x2:
+    case VertexFormat::kUnorm16x2:
+    case VertexFormat::kSnorm16x2:
+    case VertexFormat::kFloat16x2:
+    case VertexFormat::kFloat32x2:
+      return {BaseType::kF32, 2};
+    case VertexFormat::kFloat32x3:
+      return {BaseType::kF32, 3};
+    case VertexFormat::kUnorm8x4:
+    case VertexFormat::kSnorm8x4:
+    case VertexFormat::kUnorm16x4:
+    case VertexFormat::kSnorm16x4:
+    case VertexFormat::kFloat16x4:
+    case VertexFormat::kFloat32x4:
+      return {BaseType::kF32, 4};
+  }
+  return {BaseType::kInvalid, 0};
+}
+
+struct State {
+  State(CloneContext& context, const VertexPulling::Config& c)
+      : ctx(context), cfg(c) {}
+  State(const State&) = default;
+  ~State() = default;
+
+  /// LocationReplacement describes an ast::Variable replacement for a
+  /// location input.
+  struct LocationReplacement {
+    /// The variable to replace in the source Program
+    ast::Variable* from;
+    /// The replacement to use in the target ProgramBuilder
+    ast::Variable* to;
+  };
+
+  struct LocationInfo {
+    std::function<const ast::Expression*()> expr;
+    const sem::Type* type;
+  };
+
+  CloneContext& ctx;
+  VertexPulling::Config const cfg;
+  std::unordered_map<uint32_t, LocationInfo> location_info;
+  std::function<const ast::Expression*()> vertex_index_expr = nullptr;
+  std::function<const ast::Expression*()> instance_index_expr = nullptr;
+  Symbol pulling_position_name;
+  Symbol struct_buffer_name;
+  std::unordered_map<uint32_t, Symbol> vertex_buffer_names;
+  ast::VariableList new_function_parameters;
+
+  /// Generate the vertex buffer binding name
+  /// @param index index to append to buffer name
+  Symbol GetVertexBufferName(uint32_t index) {
+    return utils::GetOrCreate(vertex_buffer_names, index, [&] {
+      static const char kVertexBufferNamePrefix[] =
+          "tint_pulling_vertex_buffer_";
+      return ctx.dst->Symbols().New(kVertexBufferNamePrefix +
+                                    std::to_string(index));
+    });
+  }
+
+  /// Lazily generates the structure buffer symbol
+  Symbol GetStructBufferName() {
+    if (!struct_buffer_name.IsValid()) {
+      static const char kStructBufferName[] = "tint_vertex_data";
+      struct_buffer_name = ctx.dst->Symbols().New(kStructBufferName);
+    }
+    return struct_buffer_name;
+  }
+
+  /// Adds storage buffer decorated variables for the vertex buffers
+  void AddVertexStorageBuffers() {
+    // Creating the struct type
+    static const char kStructName[] = "TintVertexData";
+    auto* struct_type = ctx.dst->Structure(
+        ctx.dst->Symbols().New(kStructName),
+        {
+            ctx.dst->Member(GetStructBufferName(),
+                            ctx.dst->ty.array<ProgramBuilder::u32>(4)),
+        });
+    for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
+      // The decorated variable with struct type
+      ctx.dst->Global(
+          GetVertexBufferName(i), ctx.dst->ty.Of(struct_type),
+          ast::StorageClass::kStorage, ast::Access::kRead,
+          ast::AttributeList{
+              ctx.dst->create<ast::BindingAttribute>(i),
+              ctx.dst->create<ast::GroupAttribute>(cfg.pulling_group),
+          });
+    }
+  }
+
+  /// Creates and returns the assignment to the variables from the buffers
+  ast::BlockStatement* CreateVertexPullingPreamble() {
+    // Assign by looking at the vertex descriptor to find attributes with
+    // matching location.
+
+    ast::StatementList stmts;
+
+    for (uint32_t buffer_idx = 0; buffer_idx < cfg.vertex_state.size();
+         ++buffer_idx) {
+      const VertexBufferLayoutDescriptor& buffer_layout =
+          cfg.vertex_state[buffer_idx];
+
+      if ((buffer_layout.array_stride & 3) != 0) {
+        ctx.dst->Diagnostics().add_error(
+            diag::System::Transform,
+            "WebGPU requires that vertex stride must be a multiple of 4 bytes, "
+            "but VertexPulling array stride for buffer " +
+                std::to_string(buffer_idx) + " was " +
+                std::to_string(buffer_layout.array_stride) + " bytes");
+        return nullptr;
+      }
+
+      auto* index_expr = buffer_layout.step_mode == VertexStepMode::kVertex
+                             ? vertex_index_expr()
+                             : instance_index_expr();
+
+      // buffer_array_base is the base array offset for all the vertex
+      // attributes. These are units of uint (4 bytes).
+      auto buffer_array_base = ctx.dst->Symbols().New(
+          "buffer_array_base_" + std::to_string(buffer_idx));
+
+      auto* attribute_offset = index_expr;
+      if (buffer_layout.array_stride != 4) {
+        attribute_offset =
+            ctx.dst->Mul(index_expr, buffer_layout.array_stride / 4u);
+      }
+
+      // let pulling_offset_n = <attribute_offset>
+      stmts.emplace_back(ctx.dst->Decl(
+          ctx.dst->Const(buffer_array_base, nullptr, attribute_offset)));
+
+      for (const VertexAttributeDescriptor& attribute_desc :
+           buffer_layout.attributes) {
+        auto it = location_info.find(attribute_desc.shader_location);
+        if (it == location_info.end()) {
+          continue;
+        }
+        auto& var = it->second;
+
+        // Data type of the target WGSL variable
+        auto var_dt = DataTypeOf(var.type);
+        // Data type of the vertex stream attribute
+        auto fmt_dt = DataTypeOf(attribute_desc.format);
+
+        // Base types must match between the vertex stream and the WGSL variable
+        if (var_dt.base_type != fmt_dt.base_type) {
+          std::stringstream err;
+          err << "VertexAttributeDescriptor for location "
+              << std::to_string(attribute_desc.shader_location)
+              << " has format " << attribute_desc.format
+              << " but shader expects "
+              << var.type->FriendlyName(ctx.src->Symbols());
+          ctx.dst->Diagnostics().add_error(diag::System::Transform, err.str());
+          return nullptr;
+        }
+
+        // Load the attribute value
+        auto* fetch = Fetch(buffer_array_base, attribute_desc.offset,
+                            buffer_idx, attribute_desc.format);
+
+        // The attribute value may not be of the desired vector width. If it is
+        // not, we'll need to either reduce the width with a swizzle, or append
+        // 0's and / or a 1.
+        auto* value = fetch;
+        if (var_dt.width < fmt_dt.width) {
+          // WGSL variable vector width is smaller than the loaded vector width
+          switch (var_dt.width) {
+            case 1:
+              value = ctx.dst->MemberAccessor(fetch, "x");
+              break;
+            case 2:
+              value = ctx.dst->MemberAccessor(fetch, "xy");
+              break;
+            case 3:
+              value = ctx.dst->MemberAccessor(fetch, "xyz");
+              break;
+            default:
+              TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+                  << var_dt.width;
+              return nullptr;
+          }
+        } else if (var_dt.width > fmt_dt.width) {
+          // WGSL variable vector width is wider than the loaded vector width
+          const ast::Type* ty = nullptr;
+          ast::ExpressionList values{fetch};
+          switch (var_dt.base_type) {
+            case BaseType::kI32:
+              ty = ctx.dst->ty.i32();
+              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
+                values.emplace_back(ctx.dst->Expr((i == 3) ? 1 : 0));
+              }
+              break;
+            case BaseType::kU32:
+              ty = ctx.dst->ty.u32();
+              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
+                values.emplace_back(ctx.dst->Expr((i == 3) ? 1u : 0u));
+              }
+              break;
+            case BaseType::kF32:
+              ty = ctx.dst->ty.f32();
+              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
+                values.emplace_back(ctx.dst->Expr((i == 3) ? 1.f : 0.f));
+              }
+              break;
+            default:
+              TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+                  << var_dt.base_type;
+              return nullptr;
+          }
+          value = ctx.dst->Construct(ctx.dst->ty.vec(ty, var_dt.width), values);
+        }
+
+        // Assign the value to the WGSL variable
+        stmts.emplace_back(ctx.dst->Assign(var.expr(), value));
+      }
+    }
+
+    if (stmts.empty()) {
+      return nullptr;
+    }
+
+    return ctx.dst->create<ast::BlockStatement>(stmts);
+  }
+
+  /// Generates an expression reading from a buffer a specific format.
+  /// @param array_base the symbol of the variable holding the base array offset
+  /// of the vertex array (each index is 4-bytes).
+  /// @param offset the byte offset of the data from `buffer_base`
+  /// @param buffer the index of the vertex buffer
+  /// @param format the format to read
+  const ast::Expression* Fetch(Symbol array_base,
+                               uint32_t offset,
+                               uint32_t buffer,
+                               VertexFormat format) {
+    using u32 = ProgramBuilder::u32;
+    using i32 = ProgramBuilder::i32;
+    using f32 = ProgramBuilder::f32;
+
+    // Returns a u32 loaded from buffer_base + offset.
+    auto load_u32 = [&] {
+      return LoadPrimitive(array_base, offset, buffer, VertexFormat::kUint32);
+    };
+
+    // Returns a i32 loaded from buffer_base + offset.
+    auto load_i32 = [&] { return ctx.dst->Bitcast<i32>(load_u32()); };
+
+    // Returns a u32 loaded from buffer_base + offset + 4.
+    auto load_next_u32 = [&] {
+      return LoadPrimitive(array_base, offset + 4, buffer,
+                           VertexFormat::kUint32);
+    };
+
+    // Returns a i32 loaded from buffer_base + offset + 4.
+    auto load_next_i32 = [&] { return ctx.dst->Bitcast<i32>(load_next_u32()); };
+
+    // Returns a u16 loaded from offset, packed in the high 16 bits of a u32.
+    // The low 16 bits are 0.
+    // `min_alignment` must be a power of two.
+    // `offset` must be `min_alignment` bytes aligned.
+    auto load_u16_h = [&] {
+      auto low_u32_offset = offset & ~3u;
+      auto* low_u32 = LoadPrimitive(array_base, low_u32_offset, buffer,
+                                    VertexFormat::kUint32);
+      switch (offset & 3) {
+        case 0:
+          return ctx.dst->Shl(low_u32, 16u);
+        case 1:
+          return ctx.dst->And(ctx.dst->Shl(low_u32, 8u), 0xffff0000u);
+        case 2:
+          return ctx.dst->And(low_u32, 0xffff0000u);
+        default: {  // 3:
+          auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer,
+                                         VertexFormat::kUint32);
+          auto* shr = ctx.dst->Shr(low_u32, 8u);
+          auto* shl = ctx.dst->Shl(high_u32, 24u);
+          return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffff0000u);
+        }
+      }
+    };
+
+    // Returns a u16 loaded from offset, packed in the low 16 bits of a u32.
+    // The high 16 bits are 0.
+    auto load_u16_l = [&] {
+      auto low_u32_offset = offset & ~3u;
+      auto* low_u32 = LoadPrimitive(array_base, low_u32_offset, buffer,
+                                    VertexFormat::kUint32);
+      switch (offset & 3) {
+        case 0:
+          return ctx.dst->And(low_u32, 0xffffu);
+        case 1:
+          return ctx.dst->And(ctx.dst->Shr(low_u32, 8u), 0xffffu);
+        case 2:
+          return ctx.dst->Shr(low_u32, 16u);
+        default: {  // 3:
+          auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer,
+                                         VertexFormat::kUint32);
+          auto* shr = ctx.dst->Shr(low_u32, 24u);
+          auto* shl = ctx.dst->Shl(high_u32, 8u);
+          return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffffu);
+        }
+      }
+    };
+
+    // Returns a i16 loaded from offset, packed in the high 16 bits of a u32.
+    // The low 16 bits are 0.
+    auto load_i16_h = [&] { return ctx.dst->Bitcast<i32>(load_u16_h()); };
+
+    // Assumptions are made that alignment must be at least as large as the size
+    // of a single component.
+    switch (format) {
+      // Basic primitives
+      case VertexFormat::kUint32:
+      case VertexFormat::kSint32:
+      case VertexFormat::kFloat32:
+        return LoadPrimitive(array_base, offset, buffer, format);
+
+        // Vectors of basic primitives
+      case VertexFormat::kUint32x2:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
+                       VertexFormat::kUint32, 2);
+      case VertexFormat::kUint32x3:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
+                       VertexFormat::kUint32, 3);
+      case VertexFormat::kUint32x4:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
+                       VertexFormat::kUint32, 4);
+      case VertexFormat::kSint32x2:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
+                       VertexFormat::kSint32, 2);
+      case VertexFormat::kSint32x3:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
+                       VertexFormat::kSint32, 3);
+      case VertexFormat::kSint32x4:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
+                       VertexFormat::kSint32, 4);
+      case VertexFormat::kFloat32x2:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
+                       VertexFormat::kFloat32, 2);
+      case VertexFormat::kFloat32x3:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
+                       VertexFormat::kFloat32, 3);
+      case VertexFormat::kFloat32x4:
+        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
+                       VertexFormat::kFloat32, 4);
+
+      case VertexFormat::kUint8x2: {
+        // yyxx0000, yyxx0000
+        auto* u16s = ctx.dst->vec2<u32>(load_u16_h());
+        // xx000000, yyxx0000
+        auto* shl = ctx.dst->Shl(u16s, ctx.dst->vec2<u32>(8u, 0u));
+        // 000000xx, 000000yy
+        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24u));
+      }
+      case VertexFormat::kUint8x4: {
+        // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx
+        auto* u32s = ctx.dst->vec4<u32>(load_u32());
+        // xx000000, yyxx0000, zzyyxx00, wwzzyyxx
+        auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec4<u32>(24u, 16u, 8u, 0u));
+        // 000000xx, 000000yy, 000000zz, 000000ww
+        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24u));
+      }
+      case VertexFormat::kUint16x2: {
+        // yyyyxxxx, yyyyxxxx
+        auto* u32s = ctx.dst->vec2<u32>(load_u32());
+        // xxxx0000, yyyyxxxx
+        auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec2<u32>(16u, 0u));
+        // 0000xxxx, 0000yyyy
+        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16u));
+      }
+      case VertexFormat::kUint16x4: {
+        // yyyyxxxx, wwwwzzzz
+        auto* u32s = ctx.dst->vec2<u32>(load_u32(), load_next_u32());
+        // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz
+        auto* xxyy = ctx.dst->MemberAccessor(u32s, "xxyy");
+        // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz
+        auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16u, 0u, 16u, 0u));
+        // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
+        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16u));
+      }
+      case VertexFormat::kSint8x2: {
+        // yyxx0000, yyxx0000
+        auto* i16s = ctx.dst->vec2<i32>(load_i16_h());
+        // xx000000, yyxx0000
+        auto* shl = ctx.dst->Shl(i16s, ctx.dst->vec2<u32>(8u, 0u));
+        // ssssssxx, ssssssyy
+        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24u));
+      }
+      case VertexFormat::kSint8x4: {
+        // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx
+        auto* i32s = ctx.dst->vec4<i32>(load_i32());
+        // xx000000, yyxx0000, zzyyxx00, wwzzyyxx
+        auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec4<u32>(24u, 16u, 8u, 0u));
+        // ssssssxx, ssssssyy, sssssszz, ssssssww
+        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24u));
+      }
+      case VertexFormat::kSint16x2: {
+        // yyyyxxxx, yyyyxxxx
+        auto* i32s = ctx.dst->vec2<i32>(load_i32());
+        // xxxx0000, yyyyxxxx
+        auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec2<u32>(16u, 0u));
+        // ssssxxxx, ssssyyyy
+        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16u));
+      }
+      case VertexFormat::kSint16x4: {
+        // yyyyxxxx, wwwwzzzz
+        auto* i32s = ctx.dst->vec2<i32>(load_i32(), load_next_i32());
+        // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz
+        auto* xxyy = ctx.dst->MemberAccessor(i32s, "xxyy");
+        // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz
+        auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16u, 0u, 16u, 0u));
+        // ssssxxxx, ssssyyyy, sssszzzz, sssswwww
+        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16u));
+      }
+      case VertexFormat::kUnorm8x2:
+        return ctx.dst->MemberAccessor(
+            ctx.dst->Call("unpack4x8unorm", load_u16_l()), "xy");
+      case VertexFormat::kSnorm8x2:
+        return ctx.dst->MemberAccessor(
+            ctx.dst->Call("unpack4x8snorm", load_u16_l()), "xy");
+      case VertexFormat::kUnorm8x4:
+        return ctx.dst->Call("unpack4x8unorm", load_u32());
+      case VertexFormat::kSnorm8x4:
+        return ctx.dst->Call("unpack4x8snorm", load_u32());
+      case VertexFormat::kUnorm16x2:
+        return ctx.dst->Call("unpack2x16unorm", load_u32());
+      case VertexFormat::kSnorm16x2:
+        return ctx.dst->Call("unpack2x16snorm", load_u32());
+      case VertexFormat::kFloat16x2:
+        return ctx.dst->Call("unpack2x16float", load_u32());
+      case VertexFormat::kUnorm16x4:
+        return ctx.dst->vec4<f32>(
+            ctx.dst->Call("unpack2x16unorm", load_u32()),
+            ctx.dst->Call("unpack2x16unorm", load_next_u32()));
+      case VertexFormat::kSnorm16x4:
+        return ctx.dst->vec4<f32>(
+            ctx.dst->Call("unpack2x16snorm", load_u32()),
+            ctx.dst->Call("unpack2x16snorm", load_next_u32()));
+      case VertexFormat::kFloat16x4:
+        return ctx.dst->vec4<f32>(
+            ctx.dst->Call("unpack2x16float", load_u32()),
+            ctx.dst->Call("unpack2x16float", load_next_u32()));
+    }
+
+    TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+        << "format " << static_cast<int>(format);
+    return nullptr;
+  }
+
+  /// Generates an expression reading an aligned basic type (u32, i32, f32) from
+  /// a vertex buffer.
+  /// @param array_base the symbol of the variable holding the base array offset
+  /// of the vertex array (each index is 4-bytes).
+  /// @param offset the byte offset of the data from `buffer_base`
+  /// @param buffer the index of the vertex buffer
+  /// @param format VertexFormat::kUint32, VertexFormat::kSint32 or
+  /// VertexFormat::kFloat32
+  const ast::Expression* LoadPrimitive(Symbol array_base,
+                                       uint32_t offset,
+                                       uint32_t buffer,
+                                       VertexFormat format) {
+    const ast::Expression* u32 = nullptr;
+    if ((offset & 3) == 0) {
+      // Aligned load.
+
+      const ast ::Expression* index = nullptr;
+      if (offset > 0) {
+        index = ctx.dst->Add(array_base, offset / 4);
+      } else {
+        index = ctx.dst->Expr(array_base);
+      }
+      u32 = ctx.dst->IndexAccessor(
+          ctx.dst->MemberAccessor(GetVertexBufferName(buffer),
+                                  GetStructBufferName()),
+          index);
+
+    } else {
+      // Unaligned load
+      uint32_t offset_aligned = offset & ~3u;
+      auto* low = LoadPrimitive(array_base, offset_aligned, buffer,
+                                VertexFormat::kUint32);
+      auto* high = LoadPrimitive(array_base, offset_aligned + 4u, buffer,
+                                 VertexFormat::kUint32);
+
+      uint32_t shift = 8u * (offset & 3u);
+
+      auto* low_shr = ctx.dst->Shr(low, shift);
+      auto* high_shl = ctx.dst->Shl(high, 32u - shift);
+      u32 = ctx.dst->Or(low_shr, high_shl);
+    }
+
+    switch (format) {
+      case VertexFormat::kUint32:
+        return u32;
+      case VertexFormat::kSint32:
+        return ctx.dst->Bitcast(ctx.dst->ty.i32(), u32);
+      case VertexFormat::kFloat32:
+        return ctx.dst->Bitcast(ctx.dst->ty.f32(), u32);
+      default:
+        break;
+    }
+    TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
+        << "invalid format for LoadPrimitive" << static_cast<int>(format);
+    return nullptr;
+  }
+
+  /// Generates an expression reading a vec2/3/4 from a vertex buffer.
+  /// @param array_base the symbol of the variable holding the base array offset
+  /// of the vertex array (each index is 4-bytes).
+  /// @param offset the byte offset of the data from `buffer_base`
+  /// @param buffer the index of the vertex buffer
+  /// @param element_stride stride between elements, in bytes
+  /// @param base_type underlying AST type
+  /// @param base_format underlying vertex format
+  /// @param count how many elements the vector has
+  const ast::Expression* LoadVec(Symbol array_base,
+                                 uint32_t offset,
+                                 uint32_t buffer,
+                                 uint32_t element_stride,
+                                 const ast::Type* base_type,
+                                 VertexFormat base_format,
+                                 uint32_t count) {
+    ast::ExpressionList expr_list;
+    for (uint32_t i = 0; i < count; ++i) {
+      // Offset read position by element_stride for each component
+      uint32_t primitive_offset = offset + element_stride * i;
+      expr_list.push_back(
+          LoadPrimitive(array_base, primitive_offset, buffer, base_format));
+    }
+
+    return ctx.dst->Construct(ctx.dst->create<ast::Vector>(base_type, count),
+                              std::move(expr_list));
+  }
+
+  /// Process a non-struct entry point parameter.
+  /// Generate function-scope variables for location parameters, and record
+  /// vertex_index and instance_index builtins if present.
+  /// @param func the entry point function
+  /// @param param the parameter to process
+  void ProcessNonStructParameter(const ast::Function* func,
+                                 const ast::Variable* param) {
+    if (auto* location =
+            ast::GetAttribute<ast::LocationAttribute>(param->attributes)) {
+      // Create a function-scope variable to replace the parameter.
+      auto func_var_sym = ctx.Clone(param->symbol);
+      auto* func_var_type = ctx.Clone(param->type);
+      auto* func_var = ctx.dst->Var(func_var_sym, func_var_type);
+      ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var));
+      // Capture mapping from location to the new variable.
+      LocationInfo info;
+      info.expr = [this, func_var]() { return ctx.dst->Expr(func_var); };
+      info.type = ctx.src->Sem().Get(param)->Type();
+      location_info[location->value] = info;
+    } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
+                   param->attributes)) {
+      // Check for existing vertex_index and instance_index builtins.
+      if (builtin->builtin == ast::Builtin::kVertexIndex) {
+        vertex_index_expr = [this, param]() {
+          return ctx.dst->Expr(ctx.Clone(param->symbol));
+        };
+      } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
+        instance_index_expr = [this, param]() {
+          return ctx.dst->Expr(ctx.Clone(param->symbol));
+        };
+      }
+      new_function_parameters.push_back(ctx.Clone(param));
+    } else {
+      TINT_ICE(Transform, ctx.dst->Diagnostics())
+          << "Invalid entry point parameter";
+    }
+  }
+
+  /// Process a struct entry point parameter.
+  /// If the struct has members with location attributes, push the parameter to
+  /// a function-scope variable and create a new struct parameter without those
+  /// attributes. Record expressions for members that are vertex_index and
+  /// instance_index builtins.
+  /// @param func the entry point function
+  /// @param param the parameter to process
+  /// @param struct_ty the structure type
+  void ProcessStructParameter(const ast::Function* func,
+                              const ast::Variable* param,
+                              const ast::Struct* struct_ty) {
+    auto param_sym = ctx.Clone(param->symbol);
+
+    // Process the struct members.
+    bool has_locations = false;
+    ast::StructMemberList members_to_clone;
+    for (auto* member : struct_ty->members) {
+      auto member_sym = ctx.Clone(member->symbol);
+      std::function<const ast::Expression*()> member_expr = [this, param_sym,
+                                                             member_sym]() {
+        return ctx.dst->MemberAccessor(param_sym, member_sym);
+      };
+
+      if (auto* location =
+              ast::GetAttribute<ast::LocationAttribute>(member->attributes)) {
+        // Capture mapping from location to struct member.
+        LocationInfo info;
+        info.expr = member_expr;
+        info.type = ctx.src->Sem().Get(member)->Type();
+        location_info[location->value] = info;
+        has_locations = true;
+      } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
+                     member->attributes)) {
+        // Check for existing vertex_index and instance_index builtins.
+        if (builtin->builtin == ast::Builtin::kVertexIndex) {
+          vertex_index_expr = member_expr;
+        } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
+          instance_index_expr = member_expr;
+        }
+        members_to_clone.push_back(member);
+      } else {
+        TINT_ICE(Transform, ctx.dst->Diagnostics())
+            << "Invalid entry point parameter";
+      }
+    }
+
+    if (!has_locations) {
+      // Nothing to do.
+      new_function_parameters.push_back(ctx.Clone(param));
+      return;
+    }
+
+    // Create a function-scope variable to replace the parameter.
+    auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->type));
+    ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var));
+
+    if (!members_to_clone.empty()) {
+      // Create a new struct without the location attributes.
+      ast::StructMemberList new_members;
+      for (auto* member : members_to_clone) {
+        auto member_sym = ctx.Clone(member->symbol);
+        auto* member_type = ctx.Clone(member->type);
+        auto member_attrs = ctx.Clone(member->attributes);
+        new_members.push_back(
+            ctx.dst->Member(member_sym, member_type, std::move(member_attrs)));
+      }
+      auto* new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members);
+
+      // Create a new function parameter with this struct.
+      auto* new_param =
+          ctx.dst->Param(ctx.dst->Sym(), ctx.dst->ty.Of(new_struct));
+      new_function_parameters.push_back(new_param);
+
+      // Copy values from the new parameter to the function-scope variable.
+      for (auto* member : members_to_clone) {
+        auto member_name = ctx.Clone(member->symbol);
+        ctx.InsertFront(
+            func->body->statements,
+            ctx.dst->Assign(ctx.dst->MemberAccessor(func_var, member_name),
+                            ctx.dst->MemberAccessor(new_param, member_name)));
+      }
+    }
+  }
+
+  /// Process an entry point function.
+  /// @param func the entry point function
+  void Process(const ast::Function* func) {
+    if (func->body->Empty()) {
+      return;
+    }
+
+    // Process entry point parameters.
+    for (auto* param : func->params) {
+      auto* sem = ctx.src->Sem().Get(param);
+      if (auto* str = sem->Type()->As<sem::Struct>()) {
+        ProcessStructParameter(func, param, str->Declaration());
+      } else {
+        ProcessNonStructParameter(func, param);
+      }
+    }
+
+    // Insert new parameters for vertex_index and instance_index if needed.
+    if (!vertex_index_expr) {
+      for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
+        if (layout.step_mode == VertexStepMode::kVertex) {
+          auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index");
+          new_function_parameters.push_back(
+              ctx.dst->Param(name, ctx.dst->ty.u32(),
+                             {ctx.dst->Builtin(ast::Builtin::kVertexIndex)}));
+          vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); };
+          break;
+        }
+      }
+    }
+    if (!instance_index_expr) {
+      for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
+        if (layout.step_mode == VertexStepMode::kInstance) {
+          auto name = ctx.dst->Symbols().New("tint_pulling_instance_index");
+          new_function_parameters.push_back(
+              ctx.dst->Param(name, ctx.dst->ty.u32(),
+                             {ctx.dst->Builtin(ast::Builtin::kInstanceIndex)}));
+          instance_index_expr = [this, name]() { return ctx.dst->Expr(name); };
+          break;
+        }
+      }
+    }
+
+    // Generate vertex pulling preamble.
+    if (auto* block = CreateVertexPullingPreamble()) {
+      ctx.InsertFront(func->body->statements, block);
+    }
+
+    // Rewrite the function header with the new parameters.
+    auto func_sym = ctx.Clone(func->symbol);
+    auto* ret_type = ctx.Clone(func->return_type);
+    auto* body = ctx.Clone(func->body);
+    auto attrs = ctx.Clone(func->attributes);
+    auto ret_attrs = ctx.Clone(func->return_type_attributes);
+    auto* new_func = ctx.dst->create<ast::Function>(
+        func->source, func_sym, new_function_parameters, ret_type, body,
+        std::move(attrs), std::move(ret_attrs));
+    ctx.Replace(func, new_func);
+  }
+};
+
+}  // namespace
+
+VertexPulling::VertexPulling() = default;
+VertexPulling::~VertexPulling() = default;
+
+void VertexPulling::Run(CloneContext& ctx,
+                        const DataMap& inputs,
+                        DataMap&) const {
+  auto cfg = cfg_;
+  if (auto* cfg_data = inputs.Get<Config>()) {
+    cfg = *cfg_data;
+  }
+
+  // Find entry point
+  auto* func = ctx.src->AST().Functions().Find(
+      ctx.src->Symbols().Get(cfg.entry_point_name),
+      ast::PipelineStage::kVertex);
+  if (func == nullptr) {
+    ctx.dst->Diagnostics().add_error(diag::System::Transform,
+                                     "Vertex stage entry point not found");
+    return;
+  }
+
+  // TODO(idanr): Need to check shader locations in descriptor cover all
+  // attributes
+
+  // TODO(idanr): Make sure we covered all error cases, to guarantee the
+  // following stages will pass
+
+  State state{ctx, cfg};
+  state.AddVertexStorageBuffers();
+  state.Process(func);
+
+  ctx.Clone();
+}
+
+VertexPulling::Config::Config() = default;
+VertexPulling::Config::Config(const Config&) = default;
+VertexPulling::Config::~Config() = default;
+VertexPulling::Config& VertexPulling::Config::operator=(const Config&) =
+    default;
+
+VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default;
+
+VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
+    uint32_t in_array_stride,
+    VertexStepMode in_step_mode,
+    std::vector<VertexAttributeDescriptor> in_attributes)
+    : array_stride(in_array_stride),
+      step_mode(in_step_mode),
+      attributes(std::move(in_attributes)) {}
+
+VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
+    const VertexBufferLayoutDescriptor& other) = default;
+
+VertexBufferLayoutDescriptor& VertexBufferLayoutDescriptor::operator=(
+    const VertexBufferLayoutDescriptor& other) = default;
+
+VertexBufferLayoutDescriptor::~VertexBufferLayoutDescriptor() = default;
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/vertex_pulling.h b/src/tint/transform/vertex_pulling.h
new file mode 100644
index 0000000..95bc25d
--- /dev/null
+++ b/src/tint/transform/vertex_pulling.h
@@ -0,0 +1,186 @@
+// Copyright 2020 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_VERTEX_PULLING_H_
+#define SRC_TINT_TRANSFORM_VERTEX_PULLING_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// Describes the format of data in a vertex buffer
+enum class VertexFormat {
+  kUint8x2,    // uint8x2
+  kUint8x4,    // uint8x4
+  kSint8x2,    // sint8x2
+  kSint8x4,    // sint8x4
+  kUnorm8x2,   // unorm8x2
+  kUnorm8x4,   // unorm8x4
+  kSnorm8x2,   // snorm8x2
+  kSnorm8x4,   // snorm8x4
+  kUint16x2,   // uint16x2
+  kUint16x4,   // uint16x4
+  kSint16x2,   // sint16x2
+  kSint16x4,   // sint16x4
+  kUnorm16x2,  // unorm16x2
+  kUnorm16x4,  // unorm16x4
+  kSnorm16x2,  // snorm16x2
+  kSnorm16x4,  // snorm16x4
+  kFloat16x2,  // float16x2
+  kFloat16x4,  // float16x4
+  kFloat32,    // float32
+  kFloat32x2,  // float32x2
+  kFloat32x3,  // float32x3
+  kFloat32x4,  // float32x4
+  kUint32,     // uint32
+  kUint32x2,   // uint32x2
+  kUint32x3,   // uint32x3
+  kUint32x4,   // uint32x4
+  kSint32,     // sint32
+  kSint32x2,   // sint32x2
+  kSint32x3,   // sint32x3
+  kSint32x4,   // sint32x4
+
+  kLastEntry = kSint32x4,
+};
+
+/// Describes if a vertex attributes increments with vertex index or instance
+/// index
+enum class VertexStepMode { kVertex, kInstance, kLastEntry = kInstance };
+
+/// Describes a vertex attribute within a buffer
+struct VertexAttributeDescriptor {
+  /// The format of the attribute
+  VertexFormat format;
+  /// The byte offset of the attribute in the buffer
+  uint32_t offset;
+  /// The shader location used for the attribute
+  uint32_t shader_location;
+};
+
+/// Describes a buffer containing multiple vertex attributes
+struct VertexBufferLayoutDescriptor {
+  /// Constructor
+  VertexBufferLayoutDescriptor();
+  /// Constructor
+  /// @param in_array_stride the array stride of the in buffer
+  /// @param in_step_mode the step mode of the in buffer
+  /// @param in_attributes the in attributes
+  VertexBufferLayoutDescriptor(
+      uint32_t in_array_stride,
+      VertexStepMode in_step_mode,
+      std::vector<VertexAttributeDescriptor> in_attributes);
+  /// Copy constructor
+  /// @param other the struct to copy
+  VertexBufferLayoutDescriptor(const VertexBufferLayoutDescriptor& other);
+
+  /// Assignment operator
+  /// @param other the struct to copy
+  /// @returns this struct
+  VertexBufferLayoutDescriptor& operator=(
+      const VertexBufferLayoutDescriptor& other);
+
+  ~VertexBufferLayoutDescriptor();
+
+  /// The array stride used in the in buffer
+  uint32_t array_stride = 0u;
+  /// The input step mode used
+  VertexStepMode step_mode = VertexStepMode::kVertex;
+  /// The vertex attributes
+  std::vector<VertexAttributeDescriptor> attributes;
+};
+
+/// Describes vertex state, which consists of many buffers containing vertex
+/// attributes
+using VertexStateDescriptor = std::vector<VertexBufferLayoutDescriptor>;
+
+/// Converts a program to use vertex pulling
+///
+/// Variables which accept vertex input are var<in> with a location attribute.
+/// This transform will convert those to be assigned from storage buffers
+/// instead. The intention is to allow vertex input to rely on a storage buffer
+/// clamping pass for out of bounds reads. We bind the storage buffers as arrays
+/// of u32, so any read to byte position `p` will actually need to read position
+/// `p / 4`, since `sizeof(u32) == 4`.
+///
+/// `VertexFormat` represents the input type of the attribute. This isn't
+/// related to the type of the variable in the shader. For example,
+/// `VertexFormat::kVec2F16` tells us that the buffer will contain `f16`
+/// elements, to be read as vec2. In the shader, a user would make a `vec2<f32>`
+/// to be able to use them. The conversion between `f16` and `f32` will need to
+/// be handled by us (using unpack functions).
+///
+/// To be clear, there won't be types such as `f16` or `u8` anywhere in WGSL
+/// code, but these are types that the data may arrive as. We need to convert
+/// these smaller types into the base types such as `f32` and `u32` for the
+/// shader to use.
+class VertexPulling : public Castable<VertexPulling, Transform> {
+ public:
+  /// Configuration options for the transform
+  struct Config : public Castable<Config, Data> {
+    /// Constructor
+    Config();
+
+    /// Copy constructor
+    Config(const Config&);
+
+    /// Destructor
+    ~Config() override;
+
+    /// Assignment operator
+    /// @returns this Config
+    Config& operator=(const Config&);
+
+    /// The entry point to add assignments into
+    std::string entry_point_name;
+
+    /// The vertex state descriptor, containing info about attributes
+    VertexStateDescriptor vertex_state;
+
+    /// The "group" we will put all our vertex buffers into (as storage buffers)
+    /// Default to 4 as it is past the limits of user-accessible groups
+    uint32_t pulling_group = 4u;
+  };
+
+  /// Constructor
+  VertexPulling();
+
+  /// Destructor
+  ~VertexPulling() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  Config cfg_;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_VERTEX_PULLING_H_
diff --git a/src/tint/transform/vertex_pulling_test.cc b/src/tint/transform/vertex_pulling_test.cc
new file mode 100644
index 0000000..c1e63a9
--- /dev/null
+++ b/src/tint/transform/vertex_pulling_test.cc
@@ -0,0 +1,1290 @@
+// Copyright 2020 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/vertex_pulling.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using VertexPullingTest = TransformTest;
+
+TEST_F(VertexPullingTest, Error_NoEntryPoint) {
+  auto* src = "";
+
+  auto* expect = "error: Vertex stage entry point not found";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>();
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, Error_InvalidEntryPoint) {
+  auto* src = R"(
+@stage(vertex)
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = "error: Vertex stage entry point not found";
+
+  VertexPulling::Config cfg;
+  cfg.entry_point_name = "_";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, Error_EntryPointWrongStage) {
+  auto* src = R"(
+@stage(fragment)
+fn main() {}
+)";
+
+  auto* expect = "error: Vertex stage entry point not found";
+
+  VertexPulling::Config cfg;
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, Error_BadStride) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect =
+      "error: WebGPU requires that vertex stride must be a multiple of 4 "
+      "bytes, but VertexPulling array stride for buffer 0 was 15 bytes";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{15, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, BasicModule) {
+  auto* src = R"(
+@stage(vertex)
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@stage(vertex)
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, OneAttribute) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  {
+    let buffer_array_base_0 = tint_pulling_vertex_index;
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+  }
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, OneInstancedAttribute) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(instance_index) tint_pulling_instance_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  {
+    let buffer_array_base_0 = tint_pulling_instance_index;
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+  }
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{4, VertexStepMode::kInstance, {{VertexFormat::kFloat32, 0, 0}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, OneAttributeDifferentOutputSet) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(5) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  {
+    let buffer_array_base_0 = tint_pulling_vertex_index;
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+  }
+  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
+  cfg.pulling_group = 5;
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, OneAttribute_Struct) {
+  auto* src = R"(
+struct Inputs {
+  @location(0) var_a : f32;
+};
+
+@stage(vertex)
+fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(inputs.var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+struct Inputs {
+  @location(0)
+  var_a : f32;
+}
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var inputs : Inputs;
+  {
+    let buffer_array_base_0 = tint_pulling_vertex_index;
+    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+  }
+  return vec4<f32>(inputs.var_a, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// We expect the transform to use an existing builtin variables if it finds them
+TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32,
+        @location(1) var_b : f32,
+        @builtin(vertex_index) custom_vertex_index : u32,
+        @builtin(instance_index) custom_instance_index : u32
+        ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(var_a, var_b, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) custom_vertex_index : u32, @builtin(instance_index) custom_instance_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  var var_b : f32;
+  {
+    let buffer_array_base_0 = custom_vertex_index;
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    let buffer_array_base_1 = custom_instance_index;
+    var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
+  }
+  return vec4<f32>(var_a, var_b, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {
+          4,
+          VertexStepMode::kVertex,
+          {{VertexFormat::kFloat32, 0, 0}},
+      },
+      {
+          4,
+          VertexStepMode::kInstance,
+          {{VertexFormat::kFloat32, 0, 1}},
+      },
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex_Struct) {
+  auto* src = R"(
+struct Inputs {
+  @location(0) var_a : f32;
+  @location(1) var_b : f32;
+  @builtin(vertex_index) custom_vertex_index : u32;
+  @builtin(instance_index) custom_instance_index : u32;
+};
+
+@stage(vertex)
+fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+struct tint_symbol {
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+
+struct Inputs {
+  @location(0)
+  var_a : f32;
+  @location(1)
+  var_b : f32;
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+
+@stage(vertex)
+fn main(tint_symbol_1 : tint_symbol) -> @builtin(position) vec4<f32> {
+  var inputs : Inputs;
+  inputs.custom_vertex_index = tint_symbol_1.custom_vertex_index;
+  inputs.custom_instance_index = tint_symbol_1.custom_instance_index;
+  {
+    let buffer_array_base_0 = inputs.custom_vertex_index;
+    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    let buffer_array_base_1 = inputs.custom_instance_index;
+    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
+  }
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {
+          4,
+          VertexStepMode::kVertex,
+          {{VertexFormat::kFloat32, 0, 0}},
+      },
+      {
+          4,
+          VertexStepMode::kInstance,
+          {{VertexFormat::kFloat32, 0, 1}},
+      },
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest,
+       ExistingVertexIndexAndInstanceIndex_Struct_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+
+struct Inputs {
+  @location(0) var_a : f32;
+  @location(1) var_b : f32;
+  @builtin(vertex_index) custom_vertex_index : u32;
+  @builtin(instance_index) custom_instance_index : u32;
+};
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+struct tint_symbol {
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+
+@stage(vertex)
+fn main(tint_symbol_1 : tint_symbol) -> @builtin(position) vec4<f32> {
+  var inputs : Inputs;
+  inputs.custom_vertex_index = tint_symbol_1.custom_vertex_index;
+  inputs.custom_instance_index = tint_symbol_1.custom_instance_index;
+  {
+    let buffer_array_base_0 = inputs.custom_vertex_index;
+    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    let buffer_array_base_1 = inputs.custom_instance_index;
+    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
+  }
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+
+struct Inputs {
+  @location(0)
+  var_a : f32;
+  @location(1)
+  var_b : f32;
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {
+          4,
+          VertexStepMode::kVertex,
+          {{VertexFormat::kFloat32, 0, 0}},
+      },
+      {
+          4,
+          VertexStepMode::kInstance,
+          {{VertexFormat::kFloat32, 0, 1}},
+      },
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex_SeparateStruct) {
+  auto* src = R"(
+struct Inputs {
+  @location(0) var_a : f32;
+  @location(1) var_b : f32;
+};
+
+struct Indices {
+  @builtin(vertex_index) custom_vertex_index : u32;
+  @builtin(instance_index) custom_instance_index : u32;
+};
+
+@stage(vertex)
+fn main(inputs : Inputs, indices : Indices) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+struct Inputs {
+  @location(0)
+  var_a : f32;
+  @location(1)
+  var_b : f32;
+}
+
+struct Indices {
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+
+@stage(vertex)
+fn main(indices : Indices) -> @builtin(position) vec4<f32> {
+  var inputs : Inputs;
+  {
+    let buffer_array_base_0 = indices.custom_vertex_index;
+    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    let buffer_array_base_1 = indices.custom_instance_index;
+    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
+  }
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {
+          4,
+          VertexStepMode::kVertex,
+          {{VertexFormat::kFloat32, 0, 0}},
+      },
+      {
+          4,
+          VertexStepMode::kInstance,
+          {{VertexFormat::kFloat32, 0, 1}},
+      },
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest,
+       ExistingVertexIndexAndInstanceIndex_SeparateStruct_OutOfOrder) {
+  auto* src = R"(
+@stage(vertex)
+fn main(inputs : Inputs, indices : Indices) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+
+struct Inputs {
+  @location(0) var_a : f32;
+  @location(1) var_b : f32;
+};
+
+struct Indices {
+  @builtin(vertex_index) custom_vertex_index : u32;
+  @builtin(instance_index) custom_instance_index : u32;
+};
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+@stage(vertex)
+fn main(indices : Indices) -> @builtin(position) vec4<f32> {
+  var inputs : Inputs;
+  {
+    let buffer_array_base_0 = indices.custom_vertex_index;
+    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    let buffer_array_base_1 = indices.custom_instance_index;
+    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
+  }
+  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
+}
+
+struct Inputs {
+  @location(0)
+  var_a : f32;
+  @location(1)
+  var_b : f32;
+}
+
+struct Indices {
+  @builtin(vertex_index)
+  custom_vertex_index : u32;
+  @builtin(instance_index)
+  custom_instance_index : u32;
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {
+          4,
+          VertexStepMode::kVertex,
+          {{VertexFormat::kFloat32, 0, 0}},
+      },
+      {
+          4,
+          VertexStepMode::kInstance,
+          {{VertexFormat::kFloat32, 0, 1}},
+      },
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, TwoAttributesSameBuffer) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32,
+        @location(1) var_b : vec4<f32>) -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  var var_b : vec4<f32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 4u);
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
+    var_b = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 3u)]));
+  }
+  return vec4<f32>();
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{16,
+        VertexStepMode::kVertex,
+        {{VertexFormat::kFloat32, 0, 0}, {VertexFormat::kFloat32x4, 0, 1}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, FloatVectorAttributes) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : vec2<f32>,
+        @location(1) var_b : vec3<f32>,
+        @location(2) var_c : vec4<f32>
+        ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+@binding(2) @group(4) var<storage, read> tint_pulling_vertex_buffer_2 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : vec2<f32>;
+  var var_b : vec3<f32>;
+  var var_c : vec4<f32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 2u);
+    var_a = vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 1u)]));
+    let buffer_array_base_1 = (tint_pulling_vertex_index * 3u);
+    var_b = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 2u)]));
+    let buffer_array_base_2 = (tint_pulling_vertex_index * 4u);
+    var_c = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[buffer_array_base_2]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 3u)]));
+  }
+  return vec4<f32>();
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{
+      {8, VertexStepMode::kVertex, {{VertexFormat::kFloat32x2, 0, 0}}},
+      {12, VertexStepMode::kVertex, {{VertexFormat::kFloat32x3, 0, 1}}},
+      {16, VertexStepMode::kVertex, {{VertexFormat::kFloat32x4, 0, 2}}},
+  }};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, AttemptSymbolCollision) {
+  auto* src = R"(
+@stage(vertex)
+fn main(@location(0) var_a : f32,
+        @location(1) var_b : vec4<f32>) -> @builtin(position) vec4<f32> {
+  var tint_pulling_vertex_index : i32;
+  var tint_pulling_vertex_buffer_0 : i32;
+  var tint_vertex_data : i32;
+  var tint_pulling_pos : i32;
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data_1 : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0_1 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index_1 : u32) -> @builtin(position) vec4<f32> {
+  var var_a : f32;
+  var var_b : vec4<f32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index_1 * 4u);
+    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[buffer_array_base_0]);
+    var_b = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 3u)]));
+  }
+  var tint_pulling_vertex_index : i32;
+  var tint_pulling_vertex_buffer_0 : i32;
+  var tint_vertex_data : i32;
+  var tint_pulling_pos : i32;
+  return vec4<f32>();
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {
+      {{16,
+        VertexStepMode::kVertex,
+        {{VertexFormat::kFloat32, 0, 0}, {VertexFormat::kFloat32x4, 0, 1}}}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, std::move(data));
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, FormatsAligned) {
+  auto* src = R"(
+@stage(vertex)
+fn main(
+    @location(0) uint8x2 : vec2<u32>,
+    @location(1) uint8x4 : vec4<u32>,
+    @location(2) sint8x2 : vec2<i32>,
+    @location(3) sint8x4 : vec4<i32>,
+    @location(4) unorm8x2 : vec2<f32>,
+    @location(5) unorm8x4 : vec4<f32>,
+    @location(6) snorm8x2 : vec2<f32>,
+    @location(7) snorm8x4 : vec4<f32>,
+    @location(8) uint16x2 : vec2<u32>,
+    @location(9) uint16x4 : vec4<u32>,
+    @location(10) sint16x2 : vec2<i32>,
+    @location(11) sint16x4 : vec4<i32>,
+    @location(12) unorm16x2 : vec2<f32>,
+    @location(13) unorm16x4 : vec4<f32>,
+    @location(14) snorm16x2 : vec2<f32>,
+    @location(15) snorm16x4 : vec4<f32>,
+    @location(16) float16x2 : vec2<f32>,
+    @location(17) float16x4 : vec4<f32>,
+    @location(18) float32 : f32,
+    @location(19) float32x2 : vec2<f32>,
+    @location(20) float32x3 : vec3<f32>,
+    @location(21) float32x4 : vec4<f32>,
+    @location(22) uint32 : u32,
+    @location(23) uint32x2 : vec2<u32>,
+    @location(24) uint32x3 : vec3<u32>,
+    @location(25) uint32x4 : vec4<u32>,
+    @location(26) sint32 : i32,
+    @location(27) sint32x2 : vec2<i32>,
+    @location(28) sint32x3 : vec3<i32>,
+    @location(29) sint32x4 : vec4<i32>
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var uint8x2 : vec2<u32>;
+  var uint8x4 : vec4<u32>;
+  var sint8x2 : vec2<i32>;
+  var sint8x4 : vec4<i32>;
+  var unorm8x2 : vec2<f32>;
+  var unorm8x4 : vec4<f32>;
+  var snorm8x2 : vec2<f32>;
+  var snorm8x4 : vec4<f32>;
+  var uint16x2 : vec2<u32>;
+  var uint16x4 : vec4<u32>;
+  var sint16x2 : vec2<i32>;
+  var sint16x4 : vec4<i32>;
+  var unorm16x2 : vec2<f32>;
+  var unorm16x4 : vec4<f32>;
+  var snorm16x2 : vec2<f32>;
+  var snorm16x4 : vec4<f32>;
+  var float16x2 : vec2<f32>;
+  var float16x4 : vec4<f32>;
+  var float32 : f32;
+  var float32x2 : vec2<f32>;
+  var float32x3 : vec3<f32>;
+  var float32x4 : vec4<f32>;
+  var uint32 : u32;
+  var uint32x2 : vec2<u32>;
+  var uint32x3 : vec3<u32>;
+  var uint32x4 : vec4<u32>;
+  var sint32 : i32;
+  var sint32x2 : vec2<i32>;
+  var sint32x3 : vec3<i32>;
+  var sint32x4 : vec4<i32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    uint8x2 = ((vec2<u32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
+    uint8x4 = ((vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
+    sint8x2 = ((vec2<i32>(bitcast<i32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
+    sint8x4 = ((vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
+    unorm8x2 = unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy;
+    unorm8x4 = unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    snorm8x2 = unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy;
+    snorm8x4 = unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    uint16x2 = ((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
+    uint16x4 = ((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
+    sint16x2 = ((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
+    sint16x4 = ((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
+    unorm16x2 = unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    unorm16x4 = vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
+    snorm16x2 = unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    snorm16x4 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
+    float16x2 = unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    float16x4 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
+    float32 = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    float32x2 = vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
+    float32x3 = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]));
+    float32x4 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]));
+    uint32 = tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)];
+    uint32x2 = vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]);
+    uint32x3 = vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]);
+    uint32x4 = vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]);
+    sint32 = bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
+    sint32x2 = vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
+    sint32x3 = vec3<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]));
+    sint32x4 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]));
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{{256,
+                        VertexStepMode::kVertex,
+                        {
+                            {VertexFormat::kUint8x2, 64, 0},
+                            {VertexFormat::kUint8x4, 64, 1},
+                            {VertexFormat::kSint8x2, 64, 2},
+                            {VertexFormat::kSint8x4, 64, 3},
+                            {VertexFormat::kUnorm8x2, 64, 4},
+                            {VertexFormat::kUnorm8x4, 64, 5},
+                            {VertexFormat::kSnorm8x2, 64, 6},
+                            {VertexFormat::kSnorm8x4, 64, 7},
+                            {VertexFormat::kUint16x2, 64, 8},
+                            {VertexFormat::kUint16x4, 64, 9},
+                            {VertexFormat::kSint16x2, 64, 10},
+                            {VertexFormat::kSint16x4, 64, 11},
+                            {VertexFormat::kUnorm16x2, 64, 12},
+                            {VertexFormat::kUnorm16x4, 64, 13},
+                            {VertexFormat::kSnorm16x2, 64, 14},
+                            {VertexFormat::kSnorm16x4, 64, 15},
+                            {VertexFormat::kFloat16x2, 64, 16},
+                            {VertexFormat::kFloat16x4, 64, 17},
+                            {VertexFormat::kFloat32, 64, 18},
+                            {VertexFormat::kFloat32x2, 64, 19},
+                            {VertexFormat::kFloat32x3, 64, 20},
+                            {VertexFormat::kFloat32x4, 64, 21},
+                            {VertexFormat::kUint32, 64, 22},
+                            {VertexFormat::kUint32x2, 64, 23},
+                            {VertexFormat::kUint32x3, 64, 24},
+                            {VertexFormat::kUint32x4, 64, 25},
+                            {VertexFormat::kSint32, 64, 26},
+                            {VertexFormat::kSint32x2, 64, 27},
+                            {VertexFormat::kSint32x3, 64, 28},
+                            {VertexFormat::kSint32x4, 64, 29},
+                        }}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, FormatsStrideUnaligned) {
+  auto* src = R"(
+@stage(vertex)
+fn main(
+    @location(0) uint8x2 : vec2<u32>,
+    @location(1) uint8x4 : vec4<u32>,
+    @location(2) sint8x2 : vec2<i32>,
+    @location(3) sint8x4 : vec4<i32>,
+    @location(4) unorm8x2 : vec2<f32>,
+    @location(5) unorm8x4 : vec4<f32>,
+    @location(6) snorm8x2 : vec2<f32>,
+    @location(7) snorm8x4 : vec4<f32>,
+    @location(8) uint16x2 : vec2<u32>,
+    @location(9) uint16x4 : vec4<u32>,
+    @location(10) sint16x2 : vec2<i32>,
+    @location(11) sint16x4 : vec4<i32>,
+    @location(12) unorm16x2 : vec2<f32>,
+    @location(13) unorm16x4 : vec4<f32>,
+    @location(14) snorm16x2 : vec2<f32>,
+    @location(15) snorm16x4 : vec4<f32>,
+    @location(16) float16x2 : vec2<f32>,
+    @location(17) float16x4 : vec4<f32>,
+    @location(18) float32 : f32,
+    @location(19) float32x2 : vec2<f32>,
+    @location(20) float32x3 : vec3<f32>,
+    @location(21) float32x4 : vec4<f32>,
+    @location(22) uint32 : u32,
+    @location(23) uint32x2 : vec2<u32>,
+    @location(24) uint32x3 : vec3<u32>,
+    @location(25) uint32x4 : vec4<u32>,
+    @location(26) sint32 : i32,
+    @location(27) sint32x2 : vec2<i32>,
+    @location(28) sint32x3 : vec3<i32>,
+    @location(29) sint32x4 : vec4<i32>
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect =
+      R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var uint8x2 : vec2<u32>;
+  var uint8x4 : vec4<u32>;
+  var sint8x2 : vec2<i32>;
+  var sint8x4 : vec4<i32>;
+  var unorm8x2 : vec2<f32>;
+  var unorm8x4 : vec4<f32>;
+  var snorm8x2 : vec2<f32>;
+  var snorm8x4 : vec4<f32>;
+  var uint16x2 : vec2<u32>;
+  var uint16x4 : vec4<u32>;
+  var sint16x2 : vec2<i32>;
+  var sint16x4 : vec4<i32>;
+  var unorm16x2 : vec2<f32>;
+  var unorm16x4 : vec4<f32>;
+  var snorm16x2 : vec2<f32>;
+  var snorm16x4 : vec4<f32>;
+  var float16x2 : vec2<f32>;
+  var float16x4 : vec4<f32>;
+  var float32 : f32;
+  var float32x2 : vec2<f32>;
+  var float32x3 : vec3<f32>;
+  var float32x4 : vec4<f32>;
+  var uint32 : u32;
+  var uint32x2 : vec2<u32>;
+  var uint32x3 : vec3<u32>;
+  var uint32x4 : vec4<u32>;
+  var sint32 : i32;
+  var sint32x2 : vec2<i32>;
+  var sint32x3 : vec3<i32>;
+  var sint32x4 : vec4<i32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    uint8x2 = ((vec2<u32>((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 8u)) & 4294901760u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
+    uint8x4 = ((vec4<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
+    sint8x2 = ((vec2<i32>(bitcast<i32>((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 8u)) & 4294901760u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
+    sint8x4 = ((vec4<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)))) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
+    unorm8x2 = unpack4x8unorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy;
+    unorm8x4 = unpack4x8unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    snorm8x2 = unpack4x8snorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy;
+    snorm8x4 = unpack4x8snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    uint16x2 = ((vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
+    uint16x4 = ((vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
+    sint16x2 = ((vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)))) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
+    sint16x4 = ((vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
+    unorm16x2 = unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    unorm16x4 = vec4<f32>(unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
+    snorm16x2 = unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    snorm16x4 = vec4<f32>(unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
+    float16x2 = unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    float16x4 = vec4<f32>(unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
+    float32 = bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    float32x2 = vec2<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
+    float32x3 = vec3<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))));
+    float32x4 = vec4<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u))));
+    uint32 = ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u));
+    uint32x2 = vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)));
+    uint32x3 = vec3<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u)));
+    uint32x4 = vec4<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u)));
+    sint32 = bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
+    sint32x2 = vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
+    sint32x3 = vec3<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))));
+    sint32x4 = vec4<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u))));
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{{256,
+                        VertexStepMode::kVertex,
+                        {
+                            {VertexFormat::kUint8x2, 63, 0},
+                            {VertexFormat::kUint8x4, 63, 1},
+                            {VertexFormat::kSint8x2, 63, 2},
+                            {VertexFormat::kSint8x4, 63, 3},
+                            {VertexFormat::kUnorm8x2, 63, 4},
+                            {VertexFormat::kUnorm8x4, 63, 5},
+                            {VertexFormat::kSnorm8x2, 63, 6},
+                            {VertexFormat::kSnorm8x4, 63, 7},
+                            {VertexFormat::kUint16x2, 63, 8},
+                            {VertexFormat::kUint16x4, 63, 9},
+                            {VertexFormat::kSint16x2, 63, 10},
+                            {VertexFormat::kSint16x4, 63, 11},
+                            {VertexFormat::kUnorm16x2, 63, 12},
+                            {VertexFormat::kUnorm16x4, 63, 13},
+                            {VertexFormat::kSnorm16x2, 63, 14},
+                            {VertexFormat::kSnorm16x4, 63, 15},
+                            {VertexFormat::kFloat16x2, 63, 16},
+                            {VertexFormat::kFloat16x4, 63, 17},
+                            {VertexFormat::kFloat32, 63, 18},
+                            {VertexFormat::kFloat32x2, 63, 19},
+                            {VertexFormat::kFloat32x3, 63, 20},
+                            {VertexFormat::kFloat32x4, 63, 21},
+                            {VertexFormat::kUint32, 63, 22},
+                            {VertexFormat::kUint32x2, 63, 23},
+                            {VertexFormat::kUint32x3, 63, 24},
+                            {VertexFormat::kUint32x4, 63, 25},
+                            {VertexFormat::kSint32, 63, 26},
+                            {VertexFormat::kSint32x2, 63, 27},
+                            {VertexFormat::kSint32x3, 63, 28},
+                            {VertexFormat::kSint32x4, 63, 29},
+                        }}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VertexPullingTest, FormatsWithVectorsResized) {
+  auto* src = R"(
+@stage(vertex)
+fn main(
+    @location(0) uint8x2 : vec3<u32>,
+    @location(1) uint8x4 : vec2<u32>,
+    @location(2) sint8x2 : i32,
+    @location(3) sint8x4 : vec2<i32>,
+    @location(4) unorm8x2 : vec4<f32>,
+    @location(5) unorm8x4 : f32,
+    @location(6) snorm8x2 : vec3<f32>,
+    @location(7) snorm8x4 : f32,
+    @location(8) uint16x2 : vec3<u32>,
+    @location(9) uint16x4 : vec2<u32>,
+    @location(10) sint16x2 : vec4<i32>,
+    @location(11) sint16x4 : i32,
+    @location(12) unorm16x2 : vec3<f32>,
+    @location(13) unorm16x4 : f32,
+    @location(14) snorm16x2 : vec4<f32>,
+    @location(15) snorm16x4 : vec3<f32>,
+    @location(16) float16x2 : vec4<f32>,
+    @location(17) float16x4 : f32,
+    @location(18) float32 : vec4<f32>,
+    @location(19) float32x2 : vec4<f32>,
+    @location(20) float32x3 : vec2<f32>,
+    @location(21) float32x4 : vec3<f32>,
+    @location(22) uint32 : vec3<u32>,
+    @location(23) uint32x2 : vec4<u32>,
+    @location(24) uint32x3 : vec4<u32>,
+    @location(25) uint32x4 : vec2<u32>,
+    @location(26) sint32 : vec4<i32>,
+    @location(27) sint32x2 : vec3<i32>,
+    @location(28) sint32x3 : i32,
+    @location(29) sint32x4 : vec2<i32>
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  auto* expect = R"(
+struct TintVertexData {
+  tint_vertex_data : @stride(4) array<u32>;
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@stage(vertex)
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var uint8x2 : vec3<u32>;
+  var uint8x4 : vec2<u32>;
+  var sint8x2 : i32;
+  var sint8x4 : vec2<i32>;
+  var unorm8x2 : vec4<f32>;
+  var unorm8x4 : f32;
+  var snorm8x2 : vec3<f32>;
+  var snorm8x4 : f32;
+  var uint16x2 : vec3<u32>;
+  var uint16x4 : vec2<u32>;
+  var sint16x2 : vec4<i32>;
+  var sint16x4 : i32;
+  var unorm16x2 : vec3<f32>;
+  var unorm16x4 : f32;
+  var snorm16x2 : vec4<f32>;
+  var snorm16x4 : vec3<f32>;
+  var float16x2 : vec4<f32>;
+  var float16x4 : f32;
+  var float32 : vec4<f32>;
+  var float32x2 : vec4<f32>;
+  var float32x3 : vec2<f32>;
+  var float32x4 : vec3<f32>;
+  var uint32 : vec3<u32>;
+  var uint32x2 : vec4<u32>;
+  var uint32x3 : vec4<u32>;
+  var uint32x4 : vec2<u32>;
+  var sint32 : vec4<i32>;
+  var sint32x2 : vec3<i32>;
+  var sint32x3 : i32;
+  var sint32x4 : vec2<i32>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    uint8x2 = vec3<u32>(((vec2<u32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u)), 0u);
+    uint8x4 = (((vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
+    sint8x2 = (((vec2<i32>(bitcast<i32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u))).x;
+    sint8x4 = (((vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
+    unorm8x2 = vec4<f32>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0, 1.0);
+    unorm8x4 = unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
+    snorm8x2 = vec3<f32>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0);
+    snorm8x4 = unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
+    uint16x2 = vec3<u32>(((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0u);
+    uint16x4 = (((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).xy;
+    sint16x2 = vec4<i32>(((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0, 1);
+    sint16x4 = (((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).x;
+    unorm16x2 = vec3<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0);
+    unorm16x4 = vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
+    snorm16x2 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
+    snorm16x4 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xyz;
+    float16x2 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
+    float16x4 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
+    float32 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 0.0, 1.0);
+    float32x2 = vec4<f32>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0.0, 1.0);
+    float32x3 = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])).xy;
+    float32x4 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])).xyz;
+    uint32 = vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], 0u, 0u);
+    uint32x2 = vec4<u32>(vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), 0u, 1u);
+    uint32x3 = vec4<u32>(vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), 1u);
+    uint32x4 = vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]).xy;
+    sint32 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0, 0, 1);
+    sint32x2 = vec3<i32>(vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0);
+    sint32x3 = vec3<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])).x;
+    sint32x4 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])).xy;
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+  VertexPulling::Config cfg;
+  cfg.vertex_state = {{{256,
+                        VertexStepMode::kVertex,
+                        {
+                            {VertexFormat::kUint8x2, 64, 0},
+                            {VertexFormat::kUint8x4, 64, 1},
+                            {VertexFormat::kSint8x2, 64, 2},
+                            {VertexFormat::kSint8x4, 64, 3},
+                            {VertexFormat::kUnorm8x2, 64, 4},
+                            {VertexFormat::kUnorm8x4, 64, 5},
+                            {VertexFormat::kSnorm8x2, 64, 6},
+                            {VertexFormat::kSnorm8x4, 64, 7},
+                            {VertexFormat::kUint16x2, 64, 8},
+                            {VertexFormat::kUint16x4, 64, 9},
+                            {VertexFormat::kSint16x2, 64, 10},
+                            {VertexFormat::kSint16x4, 64, 11},
+                            {VertexFormat::kUnorm16x2, 64, 12},
+                            {VertexFormat::kUnorm16x4, 64, 13},
+                            {VertexFormat::kSnorm16x2, 64, 14},
+                            {VertexFormat::kSnorm16x4, 64, 15},
+                            {VertexFormat::kFloat16x2, 64, 16},
+                            {VertexFormat::kFloat16x4, 64, 17},
+                            {VertexFormat::kFloat32, 64, 18},
+                            {VertexFormat::kFloat32x2, 64, 19},
+                            {VertexFormat::kFloat32x3, 64, 20},
+                            {VertexFormat::kFloat32x4, 64, 21},
+                            {VertexFormat::kUint32, 64, 22},
+                            {VertexFormat::kUint32x2, 64, 23},
+                            {VertexFormat::kUint32x3, 64, 24},
+                            {VertexFormat::kUint32x4, 64, 25},
+                            {VertexFormat::kSint32, 64, 26},
+                            {VertexFormat::kSint32x2, 64, 27},
+                            {VertexFormat::kSint32x3, 64, 28},
+                            {VertexFormat::kSint32x4, 64, 29},
+                        }}}};
+  cfg.entry_point_name = "main";
+
+  DataMap data;
+  data.Add<VertexPulling::Config>(cfg);
+  auto got = Run<VertexPulling>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/wrap_arrays_in_structs.cc b/src/tint/transform/wrap_arrays_in_structs.cc
new file mode 100644
index 0000000..f910497
--- /dev/null
+++ b/src/tint/transform/wrap_arrays_in_structs.cc
@@ -0,0 +1,171 @@
+// 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/transform/wrap_arrays_in_structs.h"
+
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/transform.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::WrapArraysInStructs);
+
+namespace tint {
+namespace transform {
+
+WrapArraysInStructs::WrappedArrayInfo::WrappedArrayInfo() = default;
+WrapArraysInStructs::WrappedArrayInfo::WrappedArrayInfo(
+    const WrappedArrayInfo&) = default;
+WrapArraysInStructs::WrappedArrayInfo::~WrappedArrayInfo() = default;
+
+WrapArraysInStructs::WrapArraysInStructs() = default;
+
+WrapArraysInStructs::~WrapArraysInStructs() = default;
+
+bool WrapArraysInStructs::ShouldRun(const Program* program,
+                                    const DataMap&) const {
+  for (auto* node : program->ASTNodes().Objects()) {
+    if (program->Sem().Get<sem::Array>(node->As<ast::Type>())) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void WrapArraysInStructs::Run(CloneContext& ctx,
+                              const DataMap&,
+                              DataMap&) const {
+  auto& sem = ctx.src->Sem();
+
+  std::unordered_map<const sem::Array*, WrappedArrayInfo> wrapped_arrays;
+  auto wrapper = [&](const sem::Array* array) {
+    return WrapArray(ctx, wrapped_arrays, array);
+  };
+  auto wrapper_typename = [&](const sem::Array* arr) -> ast::TypeName* {
+    auto info = wrapper(arr);
+    return info ? ctx.dst->create<ast::TypeName>(info.wrapper_name) : nullptr;
+  };
+
+  // Replace all array types with their corresponding wrapper
+  ctx.ReplaceAll([&](const ast::Type* ast_type) -> const ast::Type* {
+    auto* type = ctx.src->TypeOf(ast_type);
+    if (auto* array = type->UnwrapRef()->As<sem::Array>()) {
+      return wrapper_typename(array);
+    }
+    return nullptr;
+  });
+
+  // Fix up index accessors so `a[1]` becomes `a.arr[1]`
+  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* accessor)
+                     -> const ast::IndexAccessorExpression* {
+    if (auto* array = ::tint::As<sem::Array>(
+            sem.Get(accessor->object)->Type()->UnwrapRef())) {
+      if (wrapper(array)) {
+        // Array is wrapped in a structure. Emit a member accessor to get
+        // to the actual array.
+        auto* arr = ctx.Clone(accessor->object);
+        auto* idx = ctx.Clone(accessor->index);
+        auto* unwrapped = ctx.dst->MemberAccessor(arr, "arr");
+        return ctx.dst->IndexAccessor(accessor->source, unwrapped, idx);
+      }
+    }
+    return nullptr;
+  });
+
+  // Fix up array constructors so `A(1,2)` becomes `tint_array_wrapper(A(1,2))`
+  ctx.ReplaceAll(
+      [&](const ast::CallExpression* expr) -> const ast::Expression* {
+        if (auto* call = sem.Get(expr)) {
+          if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
+            if (auto* array = ctor->ReturnType()->As<sem::Array>()) {
+              if (auto w = wrapper(array)) {
+                // Wrap the array type constructor with another constructor for
+                // the wrapper
+                auto* wrapped_array_ty = ctx.dst->ty.type_name(w.wrapper_name);
+                auto* array_ty = w.array_type(ctx);
+                auto args = utils::Transform(
+                    call->Arguments(), [&](const tint::sem::Expression* s) {
+                      return ctx.Clone(s->Declaration());
+                    });
+                auto* arr_ctor = ctx.dst->Construct(array_ty, args);
+                return ctx.dst->Construct(wrapped_array_ty, arr_ctor);
+              }
+            }
+          }
+        }
+        return nullptr;
+      });
+
+  ctx.Clone();
+}
+
+WrapArraysInStructs::WrappedArrayInfo WrapArraysInStructs::WrapArray(
+    CloneContext& ctx,
+    std::unordered_map<const sem::Array*, WrappedArrayInfo>& wrapped_arrays,
+    const sem::Array* array) const {
+  if (array->IsRuntimeSized()) {
+    return {};  // We don't want to wrap runtime sized arrays
+  }
+
+  return utils::GetOrCreate(wrapped_arrays, array, [&] {
+    WrappedArrayInfo info;
+
+    // Generate a unique name for the array wrapper
+    info.wrapper_name = ctx.dst->Symbols().New("tint_array_wrapper");
+
+    // Examine the element type. Is it also an array?
+    std::function<const ast::Type*(CloneContext&)> el_type;
+    if (auto* el_array = array->ElemType()->As<sem::Array>()) {
+      // Array of array - call WrapArray() on the element type
+      if (auto el = WrapArray(ctx, wrapped_arrays, el_array)) {
+        el_type = [=](CloneContext& c) {
+          return c.dst->create<ast::TypeName>(el.wrapper_name);
+        };
+      }
+    }
+
+    // If the element wasn't an array, just create the typical AST type for it
+    if (!el_type) {
+      el_type = [=](CloneContext& c) {
+        return CreateASTTypeFor(c, array->ElemType());
+      };
+    }
+
+    // Construct the single structure field type
+    info.array_type = [=](CloneContext& c) {
+      ast::AttributeList attrs;
+      if (!array->IsStrideImplicit()) {
+        attrs.emplace_back(
+            c.dst->create<ast::StrideAttribute>(array->Stride()));
+      }
+      return c.dst->ty.array(el_type(c), array->Count(), std::move(attrs));
+    };
+
+    // Structure() will create and append the ast::Struct to the
+    // global declarations of `ctx.dst`. As we haven't finished building the
+    // current module-scope statement or function, this will be placed
+    // immediately before the usage.
+    ctx.dst->Structure(info.wrapper_name,
+                       {ctx.dst->Member("arr", info.array_type(ctx))});
+    return info;
+  });
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/wrap_arrays_in_structs.h b/src/tint/transform/wrap_arrays_in_structs.h
new file mode 100644
index 0000000..0d97a44
--- /dev/null
+++ b/src/tint/transform/wrap_arrays_in_structs.h
@@ -0,0 +1,95 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
+#define SRC_TINT_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class Type;
+}  // namespace ast
+}  // namespace tint
+
+namespace tint {
+namespace transform {
+
+/// WrapArraysInStructs is a transform that replaces all array types with a
+/// structure holding a single field of that array type.
+/// Array index expressions and constructors are also adjusted to deal with this
+/// wrapping.
+/// This transform helps with backends that cannot directly return arrays or use
+/// them as parameters.
+class WrapArraysInStructs : public Castable<WrapArraysInStructs, Transform> {
+ public:
+  /// Constructor
+  WrapArraysInStructs();
+
+  /// Destructor
+  ~WrapArraysInStructs() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  struct WrappedArrayInfo {
+    WrappedArrayInfo();
+    WrappedArrayInfo(const WrappedArrayInfo&);
+    ~WrappedArrayInfo();
+
+    Symbol wrapper_name;
+    std::function<const ast::Type*(CloneContext&)> array_type;
+
+    operator bool() { return wrapper_name.IsValid(); }
+  };
+
+  /// WrapArray wraps the fixed-size array type in a new structure (if it hasn't
+  /// already been wrapped). WrapArray will recursively wrap arrays-of-arrays.
+  /// The new structure will be added to module-scope type declarations of
+  /// `ctx.dst`.
+  /// @param ctx the CloneContext
+  /// @param wrapped_arrays a map of src array type to the wrapped structure
+  /// name
+  /// @param array the array type
+  /// @return the name of the structure that wraps the array, or an invalid
+  /// Symbol if this array should not be wrapped
+  WrappedArrayInfo WrapArray(
+      CloneContext& ctx,
+      std::unordered_map<const sem::Array*, WrappedArrayInfo>& wrapped_arrays,
+      const sem::Array* array) const;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
diff --git a/src/tint/transform/wrap_arrays_in_structs_test.cc b/src/tint/transform/wrap_arrays_in_structs_test.cc
new file mode 100644
index 0000000..231c3d6
--- /dev/null
+++ b/src/tint/transform/wrap_arrays_in_structs_test.cc
@@ -0,0 +1,424 @@
+// 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/transform/wrap_arrays_in_structs.h"
+
+#include <memory>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using WrapArraysInStructsTest = TransformTest;
+
+TEST_F(WrapArraysInStructsTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<WrapArraysInStructs>(src));
+}
+
+TEST_F(WrapArraysInStructsTest, ShouldRunHasArray) {
+  auto* src = R"(
+var<private> arr : array<i32, 4>;
+)";
+
+  EXPECT_TRUE(ShouldRun<WrapArraysInStructs>(src));
+}
+
+TEST_F(WrapArraysInStructsTest, EmptyModule) {
+  auto* src = R"()";
+  auto* expect = src;
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAsGlobal) {
+  auto* src = R"(
+var<private> arr : array<i32, 4>;
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+var<private> arr : tint_array_wrapper;
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAsFunctionVar) {
+  auto* src = R"(
+fn f() {
+  var arr : array<i32, 4>;
+  let x = arr[3];
+}
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+fn f() {
+  var arr : tint_array_wrapper;
+  let x = arr.arr[3];
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAsParam) {
+  auto* src = R"(
+fn f(a : array<i32, 4>) -> i32 {
+  return a[2];
+}
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+fn f(a : tint_array_wrapper) -> i32 {
+  return a.arr[2];
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAsReturn) {
+  auto* src = R"(
+fn f() -> array<i32, 4> {
+  return array<i32, 4>(1, 2, 3, 4);
+}
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+fn f() -> tint_array_wrapper {
+  return tint_array_wrapper(array<i32, 4u>(1, 2, 3, 4));
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAlias) {
+  auto* src = R"(
+type Inner = array<i32, 2>;
+type Array = array<Inner, 2>;
+
+fn f() {
+  var arr : Array;
+  arr = Array();
+  arr = Array(Inner(1, 2), Inner(3, 4));
+  let vals : Array = Array(Inner(1, 2), Inner(3, 4));
+  arr = vals;
+  let x = arr[3];
+}
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 2u>;
+}
+
+type Inner = tint_array_wrapper;
+
+struct tint_array_wrapper_1 {
+  arr : array<tint_array_wrapper, 2u>;
+}
+
+type Array = tint_array_wrapper_1;
+
+fn f() {
+  var arr : tint_array_wrapper_1;
+  arr = tint_array_wrapper_1(array<tint_array_wrapper, 2u>());
+  arr = tint_array_wrapper_1(array<tint_array_wrapper, 2u>(tint_array_wrapper(array<i32, 2u>(1, 2)), tint_array_wrapper(array<i32, 2u>(3, 4))));
+  let vals : tint_array_wrapper_1 = tint_array_wrapper_1(array<tint_array_wrapper, 2u>(tint_array_wrapper(array<i32, 2u>(1, 2)), tint_array_wrapper(array<i32, 2u>(3, 4))));
+  arr = vals;
+  let x = arr.arr[3];
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArrayAlias_OutOfOrder) {
+  auto* src = R"(
+fn f() {
+  var arr : Array;
+  arr = Array();
+  arr = Array(Inner(1, 2), Inner(3, 4));
+  let vals : Array = Array(Inner(1, 2), Inner(3, 4));
+  arr = vals;
+  let x = arr[3];
+}
+
+type Array = array<Inner, 2>;
+type Inner = array<i32, 2>;
+)";
+  auto* expect = R"(
+struct tint_array_wrapper_1 {
+  arr : array<i32, 2u>;
+}
+
+struct tint_array_wrapper {
+  arr : array<tint_array_wrapper_1, 2u>;
+}
+
+fn f() {
+  var arr : tint_array_wrapper;
+  arr = tint_array_wrapper(array<tint_array_wrapper_1, 2u>());
+  arr = tint_array_wrapper(array<tint_array_wrapper_1, 2u>(tint_array_wrapper_1(array<i32, 2u>(1, 2)), tint_array_wrapper_1(array<i32, 2u>(3, 4))));
+  let vals : tint_array_wrapper = tint_array_wrapper(array<tint_array_wrapper_1, 2u>(tint_array_wrapper_1(array<i32, 2u>(1, 2)), tint_array_wrapper_1(array<i32, 2u>(3, 4))));
+  arr = vals;
+  let x = arr.arr[3];
+}
+
+type Array = tint_array_wrapper;
+
+type Inner = tint_array_wrapper_1;
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : array<i32, 4>;
+  b : array<i32, 8>;
+  c : array<i32, 4>;
+};
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+struct tint_array_wrapper_1 {
+  arr : array<i32, 8u>;
+}
+
+struct S {
+  a : tint_array_wrapper;
+  b : tint_array_wrapper_1;
+  c : tint_array_wrapper;
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, ArraysOfArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : array<i32, 4>;
+  b : array<array<i32, 4>, 4>;
+  c : array<array<array<i32, 4>, 4>, 4>;
+};
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+struct tint_array_wrapper_1 {
+  arr : array<tint_array_wrapper, 4u>;
+}
+
+struct tint_array_wrapper_2 {
+  arr : array<tint_array_wrapper_1, 4u>;
+}
+
+struct S {
+  a : tint_array_wrapper;
+  b : tint_array_wrapper_1;
+  c : tint_array_wrapper_2;
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, AccessArraysOfArraysInStruct) {
+  auto* src = R"(
+struct S {
+  a : array<i32, 4>;
+  b : array<array<i32, 4>, 4>;
+  c : array<array<array<i32, 4>, 4>, 4>;
+};
+
+fn f(s : S) -> i32 {
+  return s.a[2] + s.b[1][2] + s.c[3][1][2];
+}
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 4u>;
+}
+
+struct tint_array_wrapper_1 {
+  arr : array<tint_array_wrapper, 4u>;
+}
+
+struct tint_array_wrapper_2 {
+  arr : array<tint_array_wrapper_1, 4u>;
+}
+
+struct S {
+  a : tint_array_wrapper;
+  b : tint_array_wrapper_1;
+  c : tint_array_wrapper_2;
+}
+
+fn f(s : S) -> i32 {
+  return ((s.a.arr[2] + s.b.arr[1].arr[2]) + s.c.arr[3].arr[1].arr[2]);
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, DeclarationOrder) {
+  auto* src = R"(
+type T0 = i32;
+
+type T1 = array<i32, 1>;
+
+type T2 = i32;
+
+fn f1(a : array<i32, 2>) {
+}
+
+type T3 = i32;
+
+fn f2() {
+  var v : array<i32, 3>;
+}
+)";
+  auto* expect = R"(
+type T0 = i32;
+
+struct tint_array_wrapper {
+  arr : array<i32, 1u>;
+}
+
+type T1 = tint_array_wrapper;
+
+type T2 = i32;
+
+struct tint_array_wrapper_1 {
+  arr : array<i32, 2u>;
+}
+
+fn f1(a : tint_array_wrapper_1) {
+}
+
+type T3 = i32;
+
+struct tint_array_wrapper_2 {
+  arr : array<i32, 3u>;
+}
+
+fn f2() {
+  var v : tint_array_wrapper_2;
+}
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(WrapArraysInStructsTest, DeclarationOrder_OutOfOrder) {
+  auto* src = R"(
+fn f2() {
+  var v : array<i32, 3>;
+}
+
+type T3 = i32;
+
+fn f1(a : array<i32, 2>) {
+}
+
+type T2 = i32;
+
+type T1 = array<i32, 1>;
+
+type T0 = i32;
+)";
+  auto* expect = R"(
+struct tint_array_wrapper {
+  arr : array<i32, 3u>;
+}
+
+fn f2() {
+  var v : tint_array_wrapper;
+}
+
+type T3 = i32;
+
+struct tint_array_wrapper_1 {
+  arr : array<i32, 2u>;
+}
+
+fn f1(a : tint_array_wrapper_1) {
+}
+
+type T2 = i32;
+
+struct tint_array_wrapper_2 {
+  arr : array<i32, 1u>;
+}
+
+type T1 = tint_array_wrapper_2;
+
+type T0 = i32;
+)";
+
+  auto got = Run<WrapArraysInStructs>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/zero_init_workgroup_memory.cc b/src/tint/transform/zero_init_workgroup_memory.cc
new file mode 100644
index 0000000..bf9a4a6
--- /dev/null
+++ b/src/tint/transform/zero_init_workgroup_memory.cc
@@ -0,0 +1,460 @@
+// 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/transform/zero_init_workgroup_memory.h"
+
+#include <algorithm>
+#include <map>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/unique_vector.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ZeroInitWorkgroupMemory);
+
+namespace tint {
+namespace transform {
+
+/// PIMPL state for the ZeroInitWorkgroupMemory transform
+struct ZeroInitWorkgroupMemory::State {
+  /// The clone context
+  CloneContext& ctx;
+
+  /// An alias to *ctx.dst
+  ProgramBuilder& b = *ctx.dst;
+
+  /// The constant size of the workgroup. If 0, then #workgroup_size_expr should
+  /// be used instead.
+  uint32_t workgroup_size_const = 0;
+  /// The size of the workgroup as an expression generator. Use if
+  /// #workgroup_size_const is 0.
+  std::function<const ast::Expression*()> workgroup_size_expr;
+
+  /// ArrayIndex represents a function on the local invocation index, of
+  /// the form: `array_index = (local_invocation_index % modulo) / division`
+  struct ArrayIndex {
+    /// The RHS of the modulus part of the expression
+    uint32_t modulo = 1;
+    /// The RHS of the division part of the expression
+    uint32_t division = 1;
+
+    /// Equality operator
+    /// @param i the ArrayIndex to compare to this ArrayIndex
+    /// @returns true if `i` and this ArrayIndex are equal
+    bool operator==(const ArrayIndex& i) const {
+      return modulo == i.modulo && division == i.division;
+    }
+
+    /// Hash function for the ArrayIndex type
+    struct Hasher {
+      /// @param i the ArrayIndex to calculate a hash for
+      /// @returns the hash value for the ArrayIndex `i`
+      size_t operator()(const ArrayIndex& i) const {
+        return utils::Hash(i.modulo, i.division);
+      }
+    };
+  };
+
+  /// A list of unique ArrayIndex
+  using ArrayIndices = utils::UniqueVector<ArrayIndex, ArrayIndex::Hasher>;
+
+  /// Expression holds information about an expression that is being built for a
+  /// statement will zero workgroup values.
+  struct Expression {
+    /// The AST expression node
+    const ast::Expression* expr = nullptr;
+    /// The number of iterations required to zero the value
+    uint32_t num_iterations = 0;
+    /// All array indices used by this expression
+    ArrayIndices array_indices;
+  };
+
+  /// Statement holds information about a statement that will zero workgroup
+  /// values.
+  struct Statement {
+    /// The AST statement node
+    const ast::Statement* stmt;
+    /// The number of iterations required to zero the value
+    uint32_t num_iterations;
+    /// All array indices used by this statement
+    ArrayIndices array_indices;
+  };
+
+  /// All statements that zero workgroup memory
+  std::vector<Statement> statements;
+
+  /// A map of ArrayIndex to the name reserved for the `let` declaration of that
+  /// index.
+  std::unordered_map<ArrayIndex, Symbol, ArrayIndex::Hasher> array_index_names;
+
+  /// Constructor
+  /// @param c the CloneContext used for the transform
+  explicit State(CloneContext& c) : ctx(c) {}
+
+  /// Run inserts the workgroup memory zero-initialization logic at the top of
+  /// the given function
+  /// @param fn a compute shader entry point function
+  void Run(const ast::Function* fn) {
+    auto& sem = ctx.src->Sem();
+
+    CalculateWorkgroupSize(
+        ast::GetAttribute<ast::WorkgroupAttribute>(fn->attributes));
+
+    // Generate a list of statements to zero initialize each of the
+    // workgroup storage variables used by `fn`. This will populate #statements.
+    auto* func = sem.Get(fn);
+    for (auto* var : func->TransitivelyReferencedGlobals()) {
+      if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
+        BuildZeroingStatements(
+            var->Type()->UnwrapRef(), [&](uint32_t num_values) {
+              auto var_name = ctx.Clone(var->Declaration()->symbol);
+              return Expression{b.Expr(var_name), num_values, ArrayIndices{}};
+            });
+      }
+    }
+
+    if (statements.empty()) {
+      return;  // No workgroup variables to initialize.
+    }
+
+    // Scan the entry point for an existing local_invocation_index builtin
+    // parameter
+    std::function<const ast::Expression*()> local_index;
+    for (auto* param : fn->params) {
+      if (auto* builtin =
+              ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) {
+        if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
+          local_index = [=] { return b.Expr(ctx.Clone(param->symbol)); };
+          break;
+        }
+      }
+
+      if (auto* str = sem.Get(param)->Type()->As<sem::Struct>()) {
+        for (auto* member : str->Members()) {
+          if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
+                  member->Declaration()->attributes)) {
+            if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
+              local_index = [=] {
+                auto* param_expr = b.Expr(ctx.Clone(param->symbol));
+                auto member_name = ctx.Clone(member->Declaration()->symbol);
+                return b.MemberAccessor(param_expr, member_name);
+              };
+              break;
+            }
+          }
+        }
+      }
+    }
+    if (!local_index) {
+      // No existing local index parameter. Append one to the entry point.
+      auto* param =
+          b.Param(b.Symbols().New("local_invocation_index"), b.ty.u32(),
+                  {b.Builtin(ast::Builtin::kLocalInvocationIndex)});
+      ctx.InsertBack(fn->params, param);
+      local_index = [=] { return b.Expr(param->symbol); };
+    }
+
+    // Take the zeroing statements and bin them by the number of iterations
+    // required to zero the workgroup data. We then emit these in blocks,
+    // possibly wrapped in if-statements or for-loops.
+    std::unordered_map<uint32_t, std::vector<Statement>>
+        stmts_by_num_iterations;
+    std::vector<uint32_t> num_sorted_iterations;
+    for (auto& s : statements) {
+      auto& stmts = stmts_by_num_iterations[s.num_iterations];
+      if (stmts.empty()) {
+        num_sorted_iterations.emplace_back(s.num_iterations);
+      }
+      stmts.emplace_back(s);
+    }
+    std::sort(num_sorted_iterations.begin(), num_sorted_iterations.end());
+
+    // Loop over the statements, grouped by num_iterations.
+    for (auto num_iterations : num_sorted_iterations) {
+      auto& stmts = stmts_by_num_iterations[num_iterations];
+
+      // Gather all the array indices used by all the statements in the block.
+      ArrayIndices array_indices;
+      for (auto& s : stmts) {
+        for (auto& idx : s.array_indices) {
+          array_indices.add(idx);
+        }
+      }
+
+      // Determine the block type used to emit these statements.
+
+      if (workgroup_size_const == 0 || num_iterations > workgroup_size_const) {
+        // Either the workgroup size is dynamic, or smaller than num_iterations.
+        // In either case, we need to generate a for loop to ensure we
+        // initialize all the array elements.
+        //
+        //  for (var idx : u32 = local_index;
+        //           idx < num_iterations;
+        //           idx += workgroup_size) {
+        //    ...
+        //  }
+        auto idx = b.Symbols().New("idx");
+        auto* init = b.Decl(b.Var(idx, b.ty.u32(), local_index()));
+        auto* cond = b.create<ast::BinaryExpression>(
+            ast::BinaryOp::kLessThan, b.Expr(idx), b.Expr(num_iterations));
+        auto* cont = b.Assign(
+            idx, b.Add(idx, workgroup_size_const ? b.Expr(workgroup_size_const)
+                                                 : workgroup_size_expr()));
+
+        auto block = DeclareArrayIndices(num_iterations, array_indices,
+                                         [&] { return b.Expr(idx); });
+        for (auto& s : stmts) {
+          block.emplace_back(s.stmt);
+        }
+        auto* for_loop = b.For(init, cond, cont, b.Block(block));
+        ctx.InsertFront(fn->body->statements, for_loop);
+      } else if (num_iterations < workgroup_size_const) {
+        // Workgroup size is a known constant, but is greater than
+        // num_iterations. Emit an if statement:
+        //
+        //  if (local_index < num_iterations) {
+        //    ...
+        //  }
+        auto* cond = b.create<ast::BinaryExpression>(
+            ast::BinaryOp::kLessThan, local_index(), b.Expr(num_iterations));
+        auto block = DeclareArrayIndices(num_iterations, array_indices,
+                                         [&] { return b.Expr(local_index()); });
+        for (auto& s : stmts) {
+          block.emplace_back(s.stmt);
+        }
+        auto* if_stmt = b.If(cond, b.Block(block));
+        ctx.InsertFront(fn->body->statements, if_stmt);
+      } else {
+        // Workgroup size exactly equals num_iterations.
+        // No need for any conditionals. Just emit a basic block:
+        //
+        // {
+        //    ...
+        // }
+        auto block = DeclareArrayIndices(num_iterations, array_indices,
+                                         [&] { return b.Expr(local_index()); });
+        for (auto& s : stmts) {
+          block.emplace_back(s.stmt);
+        }
+        ctx.InsertFront(fn->body->statements, b.Block(block));
+      }
+    }
+
+    // Append a single workgroup barrier after the zero initialization.
+    ctx.InsertFront(fn->body->statements,
+                    b.CallStmt(b.Call("workgroupBarrier")));
+  }
+
+  /// BuildZeroingExpr is a function that builds a sub-expression used to zero
+  /// workgroup values. `num_values` is the number of elements that the
+  /// expression will be used to zero. Returns the expression.
+  using BuildZeroingExpr = std::function<Expression(uint32_t num_values)>;
+
+  /// BuildZeroingStatements() generates the statements required to zero
+  /// initialize the workgroup storage expression of type `ty`.
+  /// @param ty the expression type
+  /// @param get_expr a function that builds the AST nodes for the expression.
+  void BuildZeroingStatements(const sem::Type* ty,
+                              const BuildZeroingExpr& get_expr) {
+    if (CanTriviallyZero(ty)) {
+      auto var = get_expr(1u);
+      auto* zero_init = b.Construct(CreateASTTypeFor(ctx, ty));
+      statements.emplace_back(Statement{b.Assign(var.expr, zero_init),
+                                        var.num_iterations, var.array_indices});
+      return;
+    }
+
+    if (auto* atomic = ty->As<sem::Atomic>()) {
+      auto* zero_init = b.Construct(CreateASTTypeFor(ctx, atomic->Type()));
+      auto expr = get_expr(1u);
+      auto* store = b.Call("atomicStore", b.AddressOf(expr.expr), zero_init);
+      statements.emplace_back(Statement{b.CallStmt(store), expr.num_iterations,
+                                        expr.array_indices});
+      return;
+    }
+
+    if (auto* str = ty->As<sem::Struct>()) {
+      for (auto* member : str->Members()) {
+        auto name = ctx.Clone(member->Declaration()->symbol);
+        BuildZeroingStatements(member->Type(), [&](uint32_t num_values) {
+          auto s = get_expr(num_values);
+          return Expression{b.MemberAccessor(s.expr, name), s.num_iterations,
+                            s.array_indices};
+        });
+      }
+      return;
+    }
+
+    if (auto* arr = ty->As<sem::Array>()) {
+      BuildZeroingStatements(arr->ElemType(), [&](uint32_t num_values) {
+        // num_values is the number of values to zero for the element type.
+        // The number of iterations required to zero the array and its elements
+        // is:
+        //      `num_values * arr->Count()`
+        // The index for this array is:
+        //      `(idx % modulo) / division`
+        auto modulo = num_values * arr->Count();
+        auto division = num_values;
+        auto a = get_expr(modulo);
+        auto array_indices = a.array_indices;
+        array_indices.add(ArrayIndex{modulo, division});
+        auto index =
+            utils::GetOrCreate(array_index_names, ArrayIndex{modulo, division},
+                               [&] { return b.Symbols().New("i"); });
+        return Expression{b.IndexAccessor(a.expr, index), a.num_iterations,
+                          array_indices};
+      });
+      return;
+    }
+
+    TINT_UNREACHABLE(Transform, b.Diagnostics())
+        << "could not zero workgroup type: " << ty->type_name();
+  }
+
+  /// DeclareArrayIndices returns a list of statements that contain the `let`
+  /// declarations for all of the ArrayIndices.
+  /// @param num_iterations the number of iterations for the block
+  /// @param array_indices the list of array indices to generate `let`
+  ///        declarations for
+  /// @param iteration a function that returns the index of the current
+  ///         iteration.
+  /// @returns the list of `let` statements that declare the array indices
+  ast::StatementList DeclareArrayIndices(
+      uint32_t num_iterations,
+      const ArrayIndices& array_indices,
+      const std::function<const ast::Expression*()>& iteration) {
+    ast::StatementList stmts;
+    std::map<Symbol, ArrayIndex> indices_by_name;
+    for (auto index : array_indices) {
+      auto name = array_index_names.at(index);
+      auto* mod =
+          (num_iterations > index.modulo)
+              ? b.create<ast::BinaryExpression>(
+                    ast::BinaryOp::kModulo, iteration(), b.Expr(index.modulo))
+              : iteration();
+      auto* div = (index.division != 1u) ? b.Div(mod, index.division) : mod;
+      auto* decl = b.Decl(b.Const(name, b.ty.u32(), div));
+      stmts.emplace_back(decl);
+    }
+    return stmts;
+  }
+
+  /// CalculateWorkgroupSize initializes the members #workgroup_size_const and
+  /// #workgroup_size_expr with the linear workgroup size.
+  /// @param attr the workgroup attribute applied to the entry point function
+  void CalculateWorkgroupSize(const ast::WorkgroupAttribute* attr) {
+    bool is_signed = false;
+    workgroup_size_const = 1u;
+    workgroup_size_expr = nullptr;
+    for (auto* expr : attr->Values()) {
+      if (!expr) {
+        continue;
+      }
+      auto* sem = ctx.src->Sem().Get(expr);
+      if (auto c = sem->ConstantValue()) {
+        if (c.ElementType()->Is<sem::I32>()) {
+          workgroup_size_const *= static_cast<uint32_t>(c.Elements()[0].i32);
+          continue;
+        } else if (c.ElementType()->Is<sem::U32>()) {
+          workgroup_size_const *= c.Elements()[0].u32;
+          continue;
+        }
+      }
+      // Constant value could not be found. Build expression instead.
+      workgroup_size_expr = [this, expr, size = workgroup_size_expr] {
+        auto* e = ctx.Clone(expr);
+        if (ctx.src->TypeOf(expr)->UnwrapRef()->Is<sem::I32>()) {
+          e = b.Construct<ProgramBuilder::u32>(e);
+        }
+        return size ? b.Mul(size(), e) : e;
+      };
+    }
+    if (workgroup_size_expr) {
+      if (workgroup_size_const != 1) {
+        // Fold workgroup_size_const in to workgroup_size_expr
+        workgroup_size_expr = [this, is_signed,
+                               const_size = workgroup_size_const,
+                               expr_size = workgroup_size_expr] {
+          return is_signed
+                     ? b.Mul(expr_size(), static_cast<int32_t>(const_size))
+                     : b.Mul(expr_size(), const_size);
+        };
+      }
+      // Indicate that workgroup_size_expr should be used instead of the
+      // constant.
+      workgroup_size_const = 0;
+    }
+  }
+
+  /// @returns true if a variable with store type `ty` can be efficiently zeroed
+  /// by assignment of a type constructor without operands. If
+  /// CanTriviallyZero() returns false, then the type needs to be
+  /// initialized by decomposing the initialization into multiple
+  /// sub-initializations.
+  /// @param ty the type to inspect
+  bool CanTriviallyZero(const sem::Type* ty) {
+    if (ty->Is<sem::Atomic>()) {
+      return false;
+    }
+    if (auto* str = ty->As<sem::Struct>()) {
+      for (auto* member : str->Members()) {
+        if (!CanTriviallyZero(member->Type())) {
+          return false;
+        }
+      }
+    }
+    if (ty->Is<sem::Array>()) {
+      return false;
+    }
+    // True for all other storable types
+    return true;
+  }
+};
+
+ZeroInitWorkgroupMemory::ZeroInitWorkgroupMemory() = default;
+
+ZeroInitWorkgroupMemory::~ZeroInitWorkgroupMemory() = default;
+
+bool ZeroInitWorkgroupMemory::ShouldRun(const Program* program,
+                                        const DataMap&) const {
+  for (auto* decl : program->AST().GlobalDeclarations()) {
+    if (auto* var = decl->As<ast::Variable>()) {
+      if (var->declared_storage_class == ast::StorageClass::kWorkgroup) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void ZeroInitWorkgroupMemory::Run(CloneContext& ctx,
+                                  const DataMap&,
+                                  DataMap&) const {
+  for (auto* fn : ctx.src->AST().Functions()) {
+    if (fn->PipelineStage() == ast::PipelineStage::kCompute) {
+      State{ctx}.Run(fn);
+    }
+  }
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/transform/zero_init_workgroup_memory.h b/src/tint/transform/zero_init_workgroup_memory.h
new file mode 100644
index 0000000..6069b12
--- /dev/null
+++ b/src/tint/transform/zero_init_workgroup_memory.h
@@ -0,0 +1,59 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
+#define SRC_TINT_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// ZeroInitWorkgroupMemory is a transform that injects code at the top of entry
+/// points to zero-initialize workgroup memory used by that entry point (and all
+/// transitive functions called by that entry point)
+class ZeroInitWorkgroupMemory
+    : public Castable<ZeroInitWorkgroupMemory, Transform> {
+ public:
+  /// Constructor
+  ZeroInitWorkgroupMemory();
+
+  /// Destructor
+  ~ZeroInitWorkgroupMemory() override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+ private:
+  struct State;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TINT_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
diff --git a/src/tint/transform/zero_init_workgroup_memory_test.cc b/src/tint/transform/zero_init_workgroup_memory_test.cc
new file mode 100644
index 0000000..2ea842b
--- /dev/null
+++ b/src/tint/transform/zero_init_workgroup_memory_test.cc
@@ -0,0 +1,1381 @@
+// 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/transform/zero_init_workgroup_memory.h"
+
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ZeroInitWorkgroupMemoryTest = TransformTest;
+
+TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunEmptyModule) {
+  auto* src = R"()";
+
+  EXPECT_FALSE(ShouldRun<ZeroInitWorkgroupMemory>(src));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunHasNoWorkgroupVars) {
+  auto* src = R"(
+var<private> v : i32;
+)";
+
+  EXPECT_FALSE(ShouldRun<ZeroInitWorkgroupMemory>(src));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunHasWorkgroupVars) {
+  auto* src = R"(
+var<workgroup> a : i32;
+)";
+
+  EXPECT_TRUE(ShouldRun<ZeroInitWorkgroupMemory>(src));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = src;
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, NoWorkgroupVars) {
+  auto* src = R"(
+var<private> v : i32;
+
+fn f() {
+  v = 1;
+}
+)";
+  auto* expect = src;
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, UnreferencedWorkgroupVars) {
+  auto* src = R"(
+var<workgroup> a : i32;
+
+var<workgroup> b : i32;
+
+var<workgroup> c : i32;
+
+fn unreferenced() {
+  b = c;
+}
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+)";
+  auto* expect = src;
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, UnreferencedWorkgroupVars_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+}
+
+fn unreferenced() {
+  b = c;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : i32;
+
+var<workgroup> c : i32;
+)";
+  auto* expect = src;
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, SingleWorkgroupVar_ExistingLocalIndex) {
+  auto* src = R"(
+var<workgroup> v : i32;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = v; // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+var<workgroup> v : i32;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       SingleWorkgroupVar_ExistingLocalIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = v; // Initialization should be inserted above this statement
+}
+
+var<workgroup> v : i32;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+
+var<workgroup> v : i32;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       SingleWorkgroupVar_ExistingLocalIndexInStruct) {
+  auto* src = R"(
+var<workgroup> v : i32;
+
+struct Params {
+  @builtin(local_invocation_index) local_idx : u32;
+};
+
+@stage(compute) @workgroup_size(1)
+fn f(params : Params) {
+  _ = v; // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+var<workgroup> v : i32;
+
+struct Params {
+  @builtin(local_invocation_index)
+  local_idx : u32;
+}
+
+@stage(compute) @workgroup_size(1)
+fn f(params : Params) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       SingleWorkgroupVar_ExistingLocalIndexInStruct_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f(params : Params) {
+  _ = v; // Initialization should be inserted above this statement
+}
+
+struct Params {
+  @builtin(local_invocation_index) local_idx : u32;
+};
+
+var<workgroup> v : i32;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(params : Params) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+
+struct Params {
+  @builtin(local_invocation_index)
+  local_idx : u32;
+}
+
+var<workgroup> v : i32;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, SingleWorkgroupVar_InjectedLocalIndex) {
+  auto* src = R"(
+var<workgroup> v : i32;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = v; // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+var<workgroup> v : i32;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       SingleWorkgroupVar_InjectedLocalIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = v; // Initialization should be inserted above this statement
+}
+
+var<workgroup> v : i32;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  _ = v;
+}
+
+var<workgroup> v : i32;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_ExistingLocalIndex_Size1) {
+  auto* src = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    b.y[i] = i32();
+  }
+  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = idx_1;
+    c[i_1].x = i32();
+  }
+  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
+    let i_2 : u32 = (idx_2 / 8u);
+    let i : u32 = (idx_2 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_ExistingLocalIndex_Size1_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    b.y[i] = i32();
+  }
+  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = idx_1;
+    c[i_1].x = i32();
+  }
+  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
+    let i_2 : u32 = (idx_2 / 8u);
+    let i : u32 = (idx_2 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_ExistingLocalIndex_Size_2_3) {
+  auto* src = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(2, 3)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(2, 3)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  if ((local_idx < 1u)) {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 6u)) {
+    let i : u32 = idx;
+    b.y[i] = i32();
+  }
+  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 6u)) {
+    let i_1 : u32 = idx_1;
+    c[i_1].x = i32();
+  }
+  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 6u)) {
+    let i_2 : u32 = (idx_2 / 8u);
+    let i : u32 = (idx_2 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_ExistingLocalIndex_Size_2_3_X) {
+  auto* src = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@id(1) override X : i32;
+
+@stage(compute) @workgroup_size(2, 3, X)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+)";
+  auto* expect =
+      R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@id(1) override X : i32;
+
+@stage(compute) @workgroup_size(2, 3, X)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  for(var idx : u32 = local_idx; (idx < 1u); idx = (idx + (u32(X) * 6u))) {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx_1 : u32 = local_idx; (idx_1 < 8u); idx_1 = (idx_1 + (u32(X) * 6u))) {
+    let i : u32 = idx_1;
+    b.y[i] = i32();
+  }
+  for(var idx_2 : u32 = local_idx; (idx_2 < 32u); idx_2 = (idx_2 + (u32(X) * 6u))) {
+    let i_1 : u32 = idx_2;
+    c[i_1].x = i32();
+  }
+  for(var idx_3 : u32 = local_idx; (idx_3 < 256u); idx_3 = (idx_3 + (u32(X) * 6u))) {
+    let i_2 : u32 = (idx_3 / 8u);
+    let i : u32 = (idx_3 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_ExistingLocalIndex_Size_5u_X_10u) {
+  auto* src = R"(
+struct S {
+  x : array<array<i32, 8>, 10>;
+  y : array<i32, 8>;
+  z : array<array<array<i32, 8>, 10>, 20>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@id(1) override X : u32;
+
+@stage(compute) @workgroup_size(5u, X, 10u)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+)";
+  auto* expect =
+      R"(
+struct S {
+  x : array<array<i32, 8>, 10>;
+  y : array<i32, 8>;
+  z : array<array<array<i32, 8>, 10>, 20>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@id(1) override X : u32;
+
+@stage(compute) @workgroup_size(5u, X, 10u)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  for(var idx : u32 = local_idx; (idx < 1u); idx = (idx + (X * 50u))) {
+    a = i32();
+  }
+  for(var idx_1 : u32 = local_idx; (idx_1 < 8u); idx_1 = (idx_1 + (X * 50u))) {
+    let i_1 : u32 = idx_1;
+    b.y[i_1] = i32();
+  }
+  for(var idx_2 : u32 = local_idx; (idx_2 < 80u); idx_2 = (idx_2 + (X * 50u))) {
+    let i : u32 = (idx_2 / 8u);
+    let i_1 : u32 = (idx_2 % 8u);
+    b.x[i][i_1] = i32();
+  }
+  for(var idx_3 : u32 = local_idx; (idx_3 < 256u); idx_3 = (idx_3 + (X * 50u))) {
+    let i_4 : u32 = (idx_3 / 8u);
+    let i_1 : u32 = (idx_3 % 8u);
+    c[i_4].y[i_1] = i32();
+  }
+  for(var idx_4 : u32 = local_idx; (idx_4 < 1600u); idx_4 = (idx_4 + (X * 50u))) {
+    let i_2 : u32 = (idx_4 / 80u);
+    let i : u32 = ((idx_4 % 80u) / 8u);
+    let i_1 : u32 = (idx_4 % 8u);
+    b.z[i_2][i][i_1] = i32();
+  }
+  for(var idx_5 : u32 = local_idx; (idx_5 < 2560u); idx_5 = (idx_5 + (X * 50u))) {
+    let i_3 : u32 = (idx_5 / 80u);
+    let i : u32 = ((idx_5 % 80u) / 8u);
+    let i_1 : u32 = (idx_5 % 8u);
+    c[i_3].x[i][i_1] = i32();
+  }
+  for(var idx_6 : u32 = local_idx; (idx_6 < 51200u); idx_6 = (idx_6 + (X * 50u))) {
+    let i_5 : u32 = (idx_6 / 1600u);
+    let i_2 : u32 = ((idx_6 % 1600u) / 80u);
+    let i : u32 = ((idx_6 % 80u) / 8u);
+    let i_1 : u32 = (idx_6 % 8u);
+    c[i_5].z[i_2][i][i_1] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, MultipleWorkgroupVar_InjectedLocalIndex) {
+  auto* src = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx : u32 = local_invocation_index; (idx < 8u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    b.y[i] = i32();
+  }
+  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = idx_1;
+    c[i_1].x = i32();
+  }
+  for(var idx_2 : u32 = local_invocation_index; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
+    let i_2 : u32 = (idx_2 / 8u);
+    let i : u32 = (idx_2 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_InjectedLocalIndex_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
+  _ = a; // Initialization should be inserted above this statement
+  _ = b;
+  _ = c;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    a = i32();
+    b.x = i32();
+  }
+  for(var idx : u32 = local_invocation_index; (idx < 8u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    b.y[i] = i32();
+  }
+  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = idx_1;
+    c[i_1].x = i32();
+  }
+  for(var idx_2 : u32 = local_invocation_index; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
+    let i_2 : u32 = (idx_2 / 8u);
+    let i : u32 = (idx_2 % 8u);
+    c[i_2].y[i] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = b;
+  _ = c;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, MultipleWorkgroupVar_MultipleEntryPoints) {
+  auto* src = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f1() {
+  _ = a; // Initialization should be inserted above this statement
+  _ = c;
+}
+
+@stage(compute) @workgroup_size(1, 2, 3)
+fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
+  _ = b; // Initialization should be inserted above this statement
+}
+
+@stage(compute) @workgroup_size(4, 5, 6)
+fn f3() {
+  _ = c; // Initialization should be inserted above this statement
+  _ = a;
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+@stage(compute) @workgroup_size(1)
+fn f1(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    a = i32();
+  }
+  for(var idx : u32 = local_invocation_index; (idx < 32u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    c[i].x = i32();
+  }
+  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 256u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = (idx_1 / 8u);
+    let i_2 : u32 = (idx_1 % 8u);
+    c[i_1].y[i_2] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = c;
+}
+
+@stage(compute) @workgroup_size(1, 2, 3)
+fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index_1 : u32) {
+  if ((local_invocation_index_1 < 1u)) {
+    b.x = i32();
+  }
+  for(var idx_2 : u32 = local_invocation_index_1; (idx_2 < 8u); idx_2 = (idx_2 + 6u)) {
+    let i_3 : u32 = idx_2;
+    b.y[i_3] = i32();
+  }
+  workgroupBarrier();
+  _ = b;
+}
+
+@stage(compute) @workgroup_size(4, 5, 6)
+fn f3(@builtin(local_invocation_index) local_invocation_index_2 : u32) {
+  if ((local_invocation_index_2 < 1u)) {
+    a = i32();
+  }
+  if ((local_invocation_index_2 < 32u)) {
+    let i_4 : u32 = local_invocation_index_2;
+    c[i_4].x = i32();
+  }
+  for(var idx_3 : u32 = local_invocation_index_2; (idx_3 < 256u); idx_3 = (idx_3 + 120u)) {
+    let i_5 : u32 = (idx_3 / 8u);
+    let i_6 : u32 = (idx_3 % 8u);
+    c[i_5].y[i_6] = i32();
+  }
+  workgroupBarrier();
+  _ = c;
+  _ = a;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       MultipleWorkgroupVar_MultipleEntryPoints_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f1() {
+  _ = a; // Initialization should be inserted above this statement
+  _ = c;
+}
+
+@stage(compute) @workgroup_size(1, 2, 3)
+fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
+  _ = b; // Initialization should be inserted above this statement
+}
+
+@stage(compute) @workgroup_size(4, 5, 6)
+fn f3() {
+  _ = c; // Initialization should be inserted above this statement
+  _ = a;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+};
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f1(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    a = i32();
+  }
+  for(var idx : u32 = local_invocation_index; (idx < 32u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    c[i].x = i32();
+  }
+  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 256u); idx_1 = (idx_1 + 1u)) {
+    let i_1 : u32 = (idx_1 / 8u);
+    let i_2 : u32 = (idx_1 % 8u);
+    c[i_1].y[i_2] = i32();
+  }
+  workgroupBarrier();
+  _ = a;
+  _ = c;
+}
+
+@stage(compute) @workgroup_size(1, 2, 3)
+fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index_1 : u32) {
+  if ((local_invocation_index_1 < 1u)) {
+    b.x = i32();
+  }
+  for(var idx_2 : u32 = local_invocation_index_1; (idx_2 < 8u); idx_2 = (idx_2 + 6u)) {
+    let i_3 : u32 = idx_2;
+    b.y[i_3] = i32();
+  }
+  workgroupBarrier();
+  _ = b;
+}
+
+@stage(compute) @workgroup_size(4, 5, 6)
+fn f3(@builtin(local_invocation_index) local_invocation_index_2 : u32) {
+  if ((local_invocation_index_2 < 1u)) {
+    a = i32();
+  }
+  if ((local_invocation_index_2 < 32u)) {
+    let i_4 : u32 = local_invocation_index_2;
+    c[i_4].x = i32();
+  }
+  for(var idx_3 : u32 = local_invocation_index_2; (idx_3 < 256u); idx_3 = (idx_3 + 120u)) {
+    let i_5 : u32 = (idx_3 / 8u);
+    let i_6 : u32 = (idx_3 % 8u);
+    c[i_5].y[i_6] = i32();
+  }
+  workgroupBarrier();
+  _ = c;
+  _ = a;
+}
+
+var<workgroup> a : i32;
+
+var<workgroup> b : S;
+
+var<workgroup> c : array<S, 32>;
+
+struct S {
+  x : i32;
+  y : array<i32, 8>;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, TransitiveUsage) {
+  auto* src = R"(
+var<workgroup> v : i32;
+
+fn use_v() {
+  _ = v;
+}
+
+fn call_use_v() {
+  use_v();
+}
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  call_use_v(); // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+var<workgroup> v : i32;
+
+fn use_v() {
+  _ = v;
+}
+
+fn call_use_v() {
+  use_v();
+}
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  call_use_v();
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, TransitiveUsage_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  call_use_v(); // Initialization should be inserted above this statement
+}
+
+fn call_use_v() {
+  use_v();
+}
+
+fn use_v() {
+  _ = v;
+}
+
+var<workgroup> v : i32;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_idx : u32) {
+  {
+    v = i32();
+  }
+  workgroupBarrier();
+  call_use_v();
+}
+
+fn call_use_v() {
+  use_v();
+}
+
+fn use_v() {
+  _ = v;
+}
+
+var<workgroup> v : i32;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupAtomics) {
+  auto* src = R"(
+var<workgroup> i : atomic<i32>;
+var<workgroup> u : atomic<u32>;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  atomicLoad(&(i)); // Initialization should be inserted above this statement
+  atomicLoad(&(u));
+}
+)";
+  auto* expect = R"(
+var<workgroup> i : atomic<i32>;
+
+var<workgroup> u : atomic<u32>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    atomicStore(&(i), i32());
+    atomicStore(&(u), u32());
+  }
+  workgroupBarrier();
+  atomicLoad(&(i));
+  atomicLoad(&(u));
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+  atomicLoad(&(i)); // Initialization should be inserted above this statement
+  atomicLoad(&(u));
+}
+
+var<workgroup> i : atomic<i32>;
+var<workgroup> u : atomic<u32>;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    atomicStore(&(i), i32());
+    atomicStore(&(u), u32());
+  }
+  workgroupBarrier();
+  atomicLoad(&(i));
+  atomicLoad(&(u));
+}
+
+var<workgroup> i : atomic<i32>;
+
+var<workgroup> u : atomic<u32>;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupStructOfAtomics) {
+  auto* src = R"(
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+};
+
+var<workgroup> w : S;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = w.a; // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+}
+
+var<workgroup> w : S;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    w.a = i32();
+    atomicStore(&(w.i), i32());
+    w.b = f32();
+    atomicStore(&(w.u), u32());
+    w.c = u32();
+  }
+  workgroupBarrier();
+  _ = w.a;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupStructOfAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = w.a; // Initialization should be inserted above this statement
+}
+
+var<workgroup> w : S;
+
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+};
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  {
+    w.a = i32();
+    atomicStore(&(w.i), i32());
+    w.b = f32();
+    atomicStore(&(w.u), u32());
+    w.c = u32();
+  }
+  workgroupBarrier();
+  _ = w.a;
+}
+
+var<workgroup> w : S;
+
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfAtomics) {
+  auto* src = R"(
+var<workgroup> w : array<atomic<u32>, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  atomicLoad(&w[0]); // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+var<workgroup> w : array<atomic<u32>, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    atomicStore(&(w[i]), u32());
+  }
+  workgroupBarrier();
+  atomicLoad(&(w[0]));
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+  atomicLoad(&w[0]); // Initialization should be inserted above this statement
+}
+
+var<workgroup> w : array<atomic<u32>, 4>;
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    let i : u32 = idx;
+    atomicStore(&(w[i]), u32());
+  }
+  workgroupBarrier();
+  atomicLoad(&(w[0]));
+}
+
+var<workgroup> w : array<atomic<u32>, 4>;
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfStructOfAtomics) {
+  auto* src = R"(
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+};
+
+var<workgroup> w : array<S, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = w[0].a; // Initialization should be inserted above this statement
+}
+)";
+  auto* expect = R"(
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+}
+
+var<workgroup> w : array<S, 4>;
+
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    let i_1 : u32 = idx;
+    w[i_1].a = i32();
+    atomicStore(&(w[i_1].i), i32());
+    w[i_1].b = f32();
+    atomicStore(&(w[i_1].u), u32());
+    w[i_1].c = u32();
+  }
+  workgroupBarrier();
+  _ = w[0].a;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ZeroInitWorkgroupMemoryTest,
+       WorkgroupArrayOfStructOfAtomics_OutOfOrder) {
+  auto* src = R"(
+@stage(compute) @workgroup_size(1)
+fn f() {
+  _ = w[0].a; // Initialization should be inserted above this statement
+}
+
+var<workgroup> w : array<S, 4>;
+
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+};
+)";
+  auto* expect = R"(
+@stage(compute) @workgroup_size(1)
+fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
+  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    let i_1 : u32 = idx;
+    w[i_1].a = i32();
+    atomicStore(&(w[i_1].i), i32());
+    w[i_1].b = f32();
+    atomicStore(&(w[i_1].u), u32());
+    w[i_1].c = u32();
+  }
+  workgroupBarrier();
+  _ = w[0].a;
+}
+
+var<workgroup> w : array<S, 4>;
+
+struct S {
+  a : i32;
+  i : atomic<i32>;
+  b : f32;
+  u : atomic<u32>;
+  c : u32;
+}
+)";
+
+  auto got = Run<ZeroInitWorkgroupMemory>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/utils/concat.h b/src/tint/utils/concat.h
new file mode 100644
index 0000000..c291da9
--- /dev/null
+++ b/src/tint/utils/concat.h
@@ -0,0 +1,22 @@
+
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONCAT_H_
+#define SRC_TINT_UTILS_CONCAT_H_
+
+#define TINT_CONCAT_2(a, b) a##b
+#define TINT_CONCAT(a, b) TINT_CONCAT_2(a, b)
+
+#endif  // SRC_TINT_UTILS_CONCAT_H_
diff --git a/src/tint/utils/crc32.h b/src/tint/utils/crc32.h
new file mode 100644
index 0000000..efe2f0e
--- /dev/null
+++ b/src/tint/utils/crc32.h
@@ -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.
+
+#ifndef SRC_TINT_UTILS_CRC32_H_
+#define SRC_TINT_UTILS_CRC32_H_
+
+#include <stdint.h>
+
+namespace tint::utils {
+
+/// @returns the CRC32 of the string `s`.
+/// @note this function can be used to calculate the CRC32 of a string literal
+/// at compile time.
+/// @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
+constexpr uint32_t CRC32(const char* s) {
+  constexpr uint32_t kLUT[] = {
+      0,          0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+      0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+      0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+      0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+      0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+      0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+      0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+      0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+      0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+      0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+      0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+      0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+      0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+      0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+      0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+      0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+      0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+      0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+      0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+      0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+      0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+      0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+      0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+      0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+      0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+      0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+      0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+      0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+      0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+      0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+      0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+      0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+      0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+      0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+      0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+      0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+      0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+      0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+      0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+      0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+      0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+      0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+      0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
+
+  uint32_t crc = 0xffffffff;
+  for (auto* p = s; *p != '\0'; ++p) {
+    crc =
+        (crc >> 8) ^ kLUT[static_cast<uint8_t>(crc) ^ static_cast<uint8_t>(*p)];
+  }
+  return crc ^ 0xffffffff;
+}
+
+}  // namespace tint::utils
+
+#endif  // SRC_TINT_UTILS_CRC32_H_
diff --git a/src/tint/utils/crc32_test.cc b/src/tint/utils/crc32_test.cc
new file mode 100644
index 0000000..3e80e78
--- /dev/null
+++ b/src/tint/utils/crc32_test.cc
@@ -0,0 +1,35 @@
+// 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/utils/crc32.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(CRC32Test, Compiletime) {
+  static_assert(CRC32("") == 0x00000000u);
+  static_assert(CRC32("hello world") == 0x0d4a1185u);
+  static_assert(CRC32("123456789") == 0xcbf43926u);
+}
+
+TEST(CRC32Test, Runtime) {
+  EXPECT_EQ(CRC32(""), 0x00000000u);
+  EXPECT_EQ(CRC32("hello world"), 0x0d4a1185u);
+  EXPECT_EQ(CRC32("123456789"), 0xcbf43926u);
+}
+
+}  // namespace
+}  // namespace tint::utils
diff --git a/src/tint/utils/debugger.cc b/src/tint/utils/debugger.cc
new file mode 100644
index 0000000..aded5b6
--- /dev/null
+++ b/src/tint/utils/debugger.cc
@@ -0,0 +1,63 @@
+// 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/utils/debugger.h"
+
+#ifdef TINT_ENABLE_BREAK_IN_DEBUGGER
+
+#ifdef _MSC_VER
+#include <Windows.h>
+#elif defined(__linux__)
+#include <signal.h>
+#include <fstream>
+#include <string>
+#endif
+
+#ifdef _MSC_VER
+#define TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {
+  if (::IsDebuggerPresent()) {
+    ::DebugBreak();
+  }
+}
+
+#elif defined(__linux__)
+
+#define TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {
+  // A process is being traced (debugged) if "/proc/self/status" contains a
+  // line with "TracerPid: <non-zero-digit>...".
+  bool is_traced = false;
+  std::ifstream fin("/proc/self/status");
+  std::string line;
+  while (!is_traced && std::getline(fin, line)) {
+    const char kPrefix[] = "TracerPid:\t";
+    static constexpr int kPrefixLen = sizeof(kPrefix) - 1;
+    if (line.length() > kPrefixLen &&
+        line.compare(0, kPrefixLen, kPrefix) == 0) {
+      is_traced = line[kPrefixLen] != '0';
+    }
+  }
+
+  if (is_traced) {
+    raise(SIGTRAP);
+  }
+}
+#endif  // platform
+
+#endif  // TINT_ENABLE_BREAK_IN_DEBUGGER
+
+#ifndef TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {}
+#endif
diff --git a/src/tint/utils/debugger.h b/src/tint/utils/debugger.h
new file mode 100644
index 0000000..240a8d9
--- /dev/null
+++ b/src/tint/utils/debugger.h
@@ -0,0 +1,27 @@
+// 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_UTILS_DEBUGGER_H_
+#define SRC_TINT_UTILS_DEBUGGER_H_
+
+namespace tint::debugger {
+
+/// If debugger is attached and the `TINT_ENABLE_BREAK_IN_DEBUGGER` preprocessor
+/// macro is defined for `debugger.cc`, calling `Break()` will cause the
+/// debugger to break at the call site.
+void Break();
+
+}  // namespace tint::debugger
+
+#endif  // SRC_TINT_UTILS_DEBUGGER_H_
diff --git a/src/tint/utils/defer.h b/src/tint/utils/defer.h
new file mode 100644
index 0000000..4be704e
--- /dev/null
+++ b/src/tint/utils/defer.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DEFER_H_
+#define SRC_TINT_UTILS_DEFER_H_
+
+#include <utility>
+
+#include "src/tint/utils/concat.h"
+
+namespace tint {
+namespace utils {
+
+/// Defer executes a function or function like object when it is destructed.
+template <typename F>
+class Defer {
+ public:
+  /// Constructor
+  /// @param f the function to call when the Defer is destructed
+  explicit Defer(F&& f) : f_(std::move(f)) {}
+
+  /// Move constructor
+  Defer(Defer&&) = default;
+
+  /// Destructor
+  /// Calls the deferred function
+  ~Defer() { f_(); }
+
+ private:
+  Defer(const Defer&) = delete;
+  Defer& operator=(const Defer&) = delete;
+
+  F f_;
+};
+
+/// Constructor
+/// @param f the function to call when the Defer is destructed
+template <typename F>
+inline Defer<F> MakeDefer(F&& f) {
+  return Defer<F>(std::forward<F>(f));
+}
+
+}  // namespace utils
+}  // namespace tint
+
+/// TINT_DEFER(S) executes the statement(s) `S` when exiting the current lexical
+/// scope.
+#define TINT_DEFER(S)                          \
+  auto TINT_CONCAT(tint_defer_, __COUNTER__) = \
+      ::tint::utils::MakeDefer([&] { S; })
+
+#endif  // SRC_TINT_UTILS_DEFER_H_
diff --git a/src/tint/utils/defer_test.cc b/src/tint/utils/defer_test.cc
new file mode 100644
index 0000000..fe1034c
--- /dev/null
+++ b/src/tint/utils/defer_test.cc
@@ -0,0 +1,44 @@
+// 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/utils/defer.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(DeferTest, Basic) {
+  bool deferCalled = false;
+  { TINT_DEFER(deferCalled = true); }
+  ASSERT_TRUE(deferCalled);
+}
+
+TEST(DeferTest, DeferOrder) {
+  int counter = 0;
+  int a = 0, b = 0, c = 0;
+  {
+    TINT_DEFER(a = ++counter);
+    TINT_DEFER(b = ++counter);
+    TINT_DEFER(c = ++counter);
+  }
+  ASSERT_EQ(a, 3);
+  ASSERT_EQ(b, 2);
+  ASSERT_EQ(c, 1);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/enum_set.h b/src/tint/utils/enum_set.h
new file mode 100644
index 0000000..a4fc621
--- /dev/null
+++ b/src/tint/utils/enum_set.h
@@ -0,0 +1,258 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_ENUM_SET_H_
+#define SRC_TINT_UTILS_ENUM_SET_H_
+
+#include <cstdint>
+#include <functional>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+namespace tint {
+namespace utils {
+
+/// EnumSet is a set of enum values.
+/// @note As the EnumSet is backed by a single uint64_t value, it can only hold
+/// enum values in the range [0 .. 63].
+template <typename ENUM>
+struct EnumSet {
+ public:
+  /// Enum is the enum type this EnumSet wraps
+  using Enum = ENUM;
+
+  /// Constructor. Initializes the EnumSet with zero.
+  constexpr EnumSet() = default;
+
+  /// Copy constructor.
+  /// @param s the set to copy
+  constexpr EnumSet(const EnumSet& s) = default;
+
+  /// Constructor. Initializes the EnumSet with the given values.
+  /// @param values the enumerator values to construct the set with
+  template <typename... VALUES>
+  explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
+
+  /// Copy assignment operator.
+  /// @param set the set to assign to this set
+  /// @return this set so calls can be chained
+  inline EnumSet& operator=(const EnumSet& set) = default;
+
+  /// Copy assignment operator.
+  /// @param e the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
+
+  /// Adds all the given values to this set
+  /// @param values the values to add
+  /// @return this set so calls can be chained
+  template <typename... VALUES>
+  inline EnumSet& Add(VALUES... values) {
+    return Add(EnumSet(std::forward<VALUES>(values)...));
+  }
+
+  /// Removes all the given values from this set
+  /// @param values the values to remove
+  /// @return this set so calls can be chained
+  template <typename... VALUES>
+  inline EnumSet& Remove(VALUES... values) {
+    return Remove(EnumSet(std::forward<VALUES>(values)...));
+  }
+
+  /// Adds all of s to this set
+  /// @param s the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
+
+  /// Removes all of s from this set
+  /// @param s the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
+
+  /// @param e the enum value
+  /// @returns a copy of this set with e added
+  inline EnumSet operator+(Enum e) const {
+    EnumSet out;
+    out.set = set | Bit(e);
+    return out;
+  }
+
+  /// @param e the enum value
+  /// @returns a copy of this set with e removed
+  inline EnumSet operator-(Enum e) const {
+    EnumSet out;
+    out.set = set & ~Bit(e);
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the union of this set with s (this ∪ rhs)
+  inline EnumSet operator+(EnumSet s) const {
+    EnumSet out;
+    out.set = set | s.set;
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the set of entries found in this but not in s (this \ s)
+  inline EnumSet operator-(EnumSet s) const {
+    EnumSet out;
+    out.set = set & ~s.set;
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the intersection of this set with s (this ∩ rhs)
+  inline EnumSet operator&(EnumSet s) const {
+    EnumSet out;
+    out.set = set & s.set;
+    return out;
+  }
+
+  /// @param e the enum value
+  /// @return true if the set contains `e`
+  inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
+
+  /// @return true if the set is empty
+  inline bool Empty() const { return set == 0; }
+
+  /// Equality operator
+  /// @param rhs the other EnumSet to compare this to
+  /// @return true if this EnumSet is equal to rhs
+  inline bool operator==(EnumSet rhs) const { return set == rhs.set; }
+
+  /// Inequality operator
+  /// @param rhs the other EnumSet to compare this to
+  /// @return true if this EnumSet is not equal to rhs
+  inline bool operator!=(EnumSet rhs) const { return set != rhs.set; }
+
+  /// Equality operator
+  /// @param rhs the enum to compare this to
+  /// @return true if this EnumSet only contains `rhs`
+  inline bool operator==(Enum rhs) const { return set == Bit(rhs); }
+
+  /// Inequality operator
+  /// @param rhs the enum to compare this to
+  /// @return false if this EnumSet only contains `rhs`
+  inline bool operator!=(Enum rhs) const { return set != Bit(rhs); }
+
+  /// @return the underlying value for the EnumSet
+  inline uint64_t Value() const { return set; }
+
+  /// Iterator provides read-only, unidirectional iterator over the enums of an
+  /// EnumSet.
+  class Iterator {
+    static constexpr int8_t kEnd = 63;
+
+    Iterator(uint64_t s, int8_t b) : set(s), pos(b) {}
+
+    /// Make the constructor accessible to the EnumSet.
+    friend struct EnumSet;
+
+   public:
+    /// @return the Enum value at this point in the iterator
+    Enum operator*() const { return static_cast<Enum>(pos); }
+
+    /// Increments the iterator
+    /// @returns this iterator
+    Iterator& operator++() {
+      while (pos < kEnd) {
+        pos++;
+        if (set & (static_cast<uint64_t>(1) << static_cast<uint64_t>(pos))) {
+          break;
+        }
+      }
+      return *this;
+    }
+
+    /// Equality operator
+    /// @param rhs the Iterator to compare this to
+    /// @return true if the two iterators are equal
+    bool operator==(const Iterator& rhs) const {
+      return set == rhs.set && pos == rhs.pos;
+    }
+
+    /// Inequality operator
+    /// @param rhs the Iterator to compare this to
+    /// @return true if the two iterators are different
+    bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
+
+   private:
+    const uint64_t set;
+    int8_t pos;
+  };
+
+  /// @returns an read-only iterator to the beginning of the set
+  Iterator begin() {
+    auto it = Iterator{set, -1};
+    ++it;  // Move to first set bit
+    return it;
+  }
+
+  /// @returns an iterator to the beginning of the set
+  Iterator end() { return Iterator{set, Iterator::kEnd}; }
+
+ private:
+  static constexpr uint64_t Bit(Enum value) {
+    return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
+  }
+
+  static constexpr uint64_t Union() { return 0; }
+
+  template <typename FIRST, typename... VALUES>
+  static constexpr uint64_t Union(FIRST first, VALUES... values) {
+    return Bit(first) | Union(values...);
+  }
+
+  uint64_t set = 0;
+};
+
+/// Writes the EnumSet to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param set the EnumSet to write
+/// @returns out so calls can be chained
+template <typename ENUM>
+inline std::ostream& operator<<(std::ostream& out, EnumSet<ENUM> set) {
+  out << "{";
+  bool first = true;
+  for (auto e : set) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+    out << e;
+  }
+  return out << "}";
+}
+
+}  // namespace utils
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::utils::EnumSet<T>
+template <typename T>
+class hash<tint::utils::EnumSet<T>> {
+ public:
+  /// @param e the EnumSet to create a hash for
+  /// @return the hash value
+  inline std::size_t operator()(const tint::utils::EnumSet<T>& e) const {
+    return std::hash<uint64_t>()(e.Value());
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_UTILS_ENUM_SET_H_
diff --git a/src/tint/utils/enum_set_test.cc b/src/tint/utils/enum_set_test.cc
new file mode 100644
index 0000000..566701c
--- /dev/null
+++ b/src/tint/utils/enum_set_test.cc
@@ -0,0 +1,244 @@
+// 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/utils/enum_set.h"
+
+#include <sstream>
+#include <vector>
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+using ::testing::ElementsAre;
+
+enum class E { A = 0, B = 3, C = 7 };
+
+std::ostream& operator<<(std::ostream& out, E e) {
+  switch (e) {
+    case E::A:
+      return out << "A";
+    case E::B:
+      return out << "B";
+    case E::C:
+      return out << "C";
+  }
+  return out << "E(" << static_cast<uint32_t>(e) << ")";
+}
+
+TEST(EnumSetTest, ConstructEmpty) {
+  EnumSet<E> set;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+  EXPECT_TRUE(set.Empty());
+}
+
+TEST(EnumSetTest, ConstructWithSingle) {
+  EnumSet<E> set(E::B);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+  EXPECT_FALSE(set.Empty());
+}
+
+TEST(EnumSetTest, ConstructWithMultiple) {
+  EnumSet<E> set(E::A, E::C);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+  EXPECT_FALSE(set.Empty());
+}
+
+TEST(EnumSetTest, AssignSet) {
+  EnumSet<E> set;
+  set = EnumSet<E>(E::A, E::C);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AssignEnum) {
+  EnumSet<E> set(E::A);
+  set = E::B;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnum) {
+  EnumSet<E> set;
+  set.Add(E::B);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnum) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(E::B);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnums) {
+  EnumSet<E> set;
+  set.Add(E::B, E::C);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnums) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(E::C, E::B);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnumSet) {
+  EnumSet<E> set;
+  set.Add(EnumSet<E>{E::B, E::C});
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnumSet) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(EnumSet<E>{E::B, E::C});
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusEnum) {
+  EnumSet<E> set = EnumSet<E>{E::B} + E::C;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusEnum) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusSet) {
+  EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusSet) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorAnd) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, EqualitySet) {
+  EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
+  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));
+}
+
+TEST(EnumSetTest, InequalitySet) {
+  EXPECT_FALSE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::B));
+  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::C));
+}
+
+TEST(EnumSetTest, EqualityEnum) {
+  EXPECT_TRUE(EnumSet<E>(E::A) == E::A);
+  EXPECT_FALSE(EnumSet<E>(E::B) == E::A);
+  EXPECT_FALSE(EnumSet<E>(E::B) == E::C);
+  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::A);
+  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::B);
+  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::C);
+}
+
+TEST(EnumSetTest, InequalityEnum) {
+  EXPECT_FALSE(EnumSet<E>(E::A) != E::A);
+  EXPECT_TRUE(EnumSet<E>(E::B) != E::A);
+  EXPECT_TRUE(EnumSet<E>(E::B) != E::C);
+  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::A);
+  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::B);
+  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
+}
+
+TEST(EnumSetTest, Hash) {
+  auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
+  EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
+  EXPECT_NE(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::C)));
+}
+
+TEST(EnumSetTest, Value) {
+  EXPECT_EQ(EnumSet<E>().Value(), 0u);
+  EXPECT_EQ(EnumSet<E>(E::A).Value(), 1u);
+  EXPECT_EQ(EnumSet<E>(E::B).Value(), 8u);
+  EXPECT_EQ(EnumSet<E>(E::C).Value(), 128u);
+  EXPECT_EQ(EnumSet<E>(E::A, E::C).Value(), 129u);
+}
+
+TEST(EnumSetTest, Iterator) {
+  auto set = EnumSet<E>(E::C, E::A);
+
+  auto it = set.begin();
+  EXPECT_EQ(*it, E::A);
+  EXPECT_NE(it, set.end());
+  ++it;
+  EXPECT_EQ(*it, E::C);
+  EXPECT_NE(it, set.end());
+  ++it;
+  EXPECT_EQ(it, set.end());
+}
+
+TEST(EnumSetTest, IteratorEmpty) {
+  auto set = EnumSet<E>();
+  EXPECT_EQ(set.begin(), set.end());
+}
+
+TEST(EnumSetTest, Loop) {
+  auto set = EnumSet<E>(E::C, E::A);
+
+  std::vector<E> seen;
+  for (auto e : set) {
+    seen.emplace_back(e);
+  }
+
+  EXPECT_THAT(seen, ElementsAre(E::A, E::C));
+}
+
+TEST(EnumSetTest, Ostream) {
+  std::stringstream ss;
+  ss << EnumSet<E>(E::A, E::C);
+  EXPECT_EQ(ss.str(), "{A, C}");
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/hash.h b/src/tint/utils/hash.h
new file mode 100644
index 0000000..1158021
--- /dev/null
+++ b/src/tint/utils/hash.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Tint Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_HASH_H_
+#define SRC_TINT_UTILS_HASH_H_
+
+#include <stdint.h>
+#include <cstdio>
+#include <functional>
+#include <vector>
+
+namespace tint {
+namespace utils {
+namespace detail {
+
+/// Helper for obtaining a seed bias value for HashCombine with a bit-width
+/// dependent on the size of size_t.
+template <int SIZE_OF_SIZE_T>
+struct HashCombineOffset {};
+
+/// Specialization of HashCombineOffset for size_t == 4.
+template <>
+struct HashCombineOffset<4> {
+  /// @returns the seed bias value for HashCombine()
+  static constexpr inline uint32_t value() { return 0x7f4a7c16; }
+};
+
+/// Specialization of HashCombineOffset for size_t == 8.
+template <>
+struct HashCombineOffset<8> {
+  /// @returns the seed bias value for HashCombine()
+  static constexpr inline uint64_t value() { return 0x9e3779b97f4a7c16; }
+};
+
+}  // namespace detail
+
+/// HashCombine "hashes" together an existing hash and hashable values.
+template <typename T>
+void HashCombine(size_t* hash, const T& value) {
+  constexpr size_t offset = detail::HashCombineOffset<sizeof(size_t)>::value();
+  *hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2);
+}
+
+/// HashCombine "hashes" together an existing hash and hashable values.
+template <typename T>
+void HashCombine(size_t* hash, const std::vector<T>& vector) {
+  HashCombine(hash, vector.size());
+  for (auto& el : vector) {
+    HashCombine(hash, el);
+  }
+}
+
+/// HashCombine "hashes" together an existing hash and hashable values.
+template <typename T, typename... ARGS>
+void HashCombine(size_t* hash, const T& value, const ARGS&... args) {
+  HashCombine(hash, value);
+  HashCombine(hash, args...);
+}
+
+/// @returns a hash of the combined arguments. The returned hash is dependent on
+/// the order of the arguments.
+template <typename... ARGS>
+size_t Hash(const ARGS&... args) {
+  size_t hash = 102931;  // seed with an arbitrary prime
+  HashCombine(&hash, args...);
+  return hash;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_HASH_H_
diff --git a/src/tint/utils/hash_test.cc b/src/tint/utils/hash_test.cc
new file mode 100644
index 0000000..caeba57
--- /dev/null
+++ b/src/tint/utils/hash_test.cc
@@ -0,0 +1,49 @@
+// 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/utils/hash.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(HashTests, Basic) {
+  EXPECT_EQ(Hash(123), Hash(123));
+  EXPECT_NE(Hash(123), Hash(321));
+  EXPECT_EQ(Hash(123, 456), Hash(123, 456));
+  EXPECT_NE(Hash(123, 456), Hash(456, 123));
+  EXPECT_NE(Hash(123, 456), Hash(123));
+  EXPECT_EQ(Hash(123, 456, false), Hash(123, 456, false));
+  EXPECT_NE(Hash(123, 456, false), Hash(123, 456));
+  EXPECT_EQ(Hash(std::string("hello")), Hash(std::string("hello")));
+  EXPECT_NE(Hash(std::string("hello")), Hash(std::string("world")));
+}
+
+TEST(HashTests, Vector) {
+  EXPECT_EQ(Hash(std::vector<int>({})), Hash(std::vector<int>({})));
+  EXPECT_EQ(Hash(std::vector<int>({1, 2, 3})),
+            Hash(std::vector<int>({1, 2, 3})));
+  EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
+            Hash(std::vector<int>({1, 2, 4})));
+  EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
+            Hash(std::vector<int>({1, 2, 3, 4})));
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/command.h b/src/tint/utils/io/command.h
new file mode 100644
index 0000000..dbf587e
--- /dev/null
+++ b/src/tint/utils/io/command.h
@@ -0,0 +1,84 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_IO_COMMAND_H_
+#define SRC_TINT_UTILS_IO_COMMAND_H_
+
+#include <string>
+#include <utility>
+
+namespace tint {
+namespace utils {
+
+/// Command is a helper used by tests for executing a process with a number of
+/// arguments and an optional stdin string, and then collecting and returning
+/// the process's stdout and stderr output as strings.
+class Command {
+ public:
+  /// Output holds the output of the process
+  struct Output {
+    /// stdout from the process
+    std::string out;
+    /// stderr from the process
+    std::string err;
+    /// process error code
+    int error_code = 0;
+  };
+
+  /// Constructor
+  /// @param path path to the executable
+  explicit Command(const std::string& path);
+
+  /// Looks for an executable with the given name in the current working
+  /// directory, and if not found there, in each of the directories in the
+  /// `PATH` environment variable.
+  /// @param executable the executable name
+  /// @returns a Command which will return true for Found() if the executable
+  /// was found.
+  static Command LookPath(const std::string& executable);
+
+  /// @return true if the executable exists at the path provided to the
+  /// constructor
+  bool Found() const;
+
+  /// @returns the path of the command
+  const std::string& Path() const { return path_; }
+
+  /// Invokes the command with the given argument strings, blocking until the
+  /// process has returned.
+  /// @param args the string arguments to pass to the process
+  /// @returns the process output
+  template <typename... ARGS>
+  Output operator()(ARGS... args) const {
+    return Exec({std::forward<ARGS>(args)...});
+  }
+
+  /// Exec invokes the command with the given argument strings, blocking until
+  /// the process has returned.
+  /// @param args the string arguments to pass to the process
+  /// @returns the process output
+  Output Exec(std::initializer_list<std::string> args) const;
+
+  /// @param input the input data to pipe to the process's stdin
+  void SetInput(const std::string& input) { input_ = input; }
+
+ private:
+  std::string const path_;
+  std::string input_;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_IO_COMMAND_H_
diff --git a/src/tint/utils/io/command_other.cc b/src/tint/utils/io/command_other.cc
new file mode 100644
index 0000000..5562e37
--- /dev/null
+++ b/src/tint/utils/io/command_other.cc
@@ -0,0 +1,37 @@
+// 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/utils/io/command.h"
+
+namespace tint {
+namespace utils {
+
+Command::Command(const std::string&) {}
+
+Command Command::LookPath(const std::string&) {
+  return Command("");
+}
+
+bool Command::Found() const {
+  return false;
+}
+
+Command::Output Command::Exec(std::initializer_list<std::string>) const {
+  Output out;
+  out.err = "Command not supported by this target";
+  return out;
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/command_posix.cc b/src/tint/utils/io/command_posix.cc
new file mode 100644
index 0000000..dc60684
--- /dev/null
+++ b/src/tint/utils/io/command_posix.cc
@@ -0,0 +1,265 @@
+// 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/utils/io/command.h"
+
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sstream>
+#include <vector>
+
+namespace tint {
+namespace utils {
+
+namespace {
+
+/// File is a simple wrapper around a POSIX file descriptor
+class File {
+  constexpr static const int kClosed = -1;
+
+ public:
+  /// Constructor
+  File() : handle_(kClosed) {}
+
+  /// Constructor
+  explicit File(int handle) : handle_(handle) {}
+
+  /// Destructor
+  ~File() { Close(); }
+
+  /// Move assignment operator
+  File& operator=(File&& rhs) {
+    Close();
+    handle_ = rhs.handle_;
+    rhs.handle_ = kClosed;
+    return *this;
+  }
+
+  /// Closes the file (if it wasn't already closed)
+  void Close() {
+    if (handle_ != kClosed) {
+      close(handle_);
+    }
+    handle_ = kClosed;
+  }
+
+  /// @returns the file handle
+  operator int() { return handle_; }
+
+  /// @returns true if the file is not closed
+  operator bool() { return handle_ != kClosed; }
+
+ private:
+  File(const File&) = delete;
+  File& operator=(const File&) = delete;
+
+  int handle_ = kClosed;
+};
+
+/// Pipe is a simple wrapper around a POSIX pipe() function
+class Pipe {
+ public:
+  /// Constructs the pipe
+  Pipe() {
+    int pipes[2] = {};
+    if (pipe(pipes) == 0) {
+      read = File(pipes[0]);
+      write = File(pipes[1]);
+    }
+  }
+
+  /// Closes both the read and write files (if they're not already closed)
+  void Close() {
+    read.Close();
+    write.Close();
+  }
+
+  /// @returns true if the pipe has an open read or write file
+  operator bool() { return read || write; }
+
+  /// The reader end of the pipe
+  File read;
+
+  /// The writer end of the pipe
+  File write;
+};
+
+bool ExecutableExists(const std::string& path) {
+  struct stat s {};
+  if (stat(path.c_str(), &s) != 0) {
+    return false;
+  }
+  return s.st_mode & S_IXUSR;
+}
+
+std::string FindExecutable(const std::string& name) {
+  if (ExecutableExists(name)) {
+    return name;
+  }
+  if (name.find("/") == std::string::npos) {
+    auto* path_env = getenv("PATH");
+    if (!path_env) {
+      return "";
+    }
+    std::istringstream path{path_env};
+    std::string dir;
+    while (getline(path, dir, ':')) {
+      auto test = dir + "/" + name;
+      if (ExecutableExists(test)) {
+        return test;
+      }
+    }
+  }
+  return "";
+}
+
+}  // namespace
+
+Command::Command(const std::string& path) : path_(path) {}
+
+Command Command::LookPath(const std::string& executable) {
+  return Command(FindExecutable(executable));
+}
+
+bool Command::Found() const {
+  return ExecutableExists(path_);
+}
+
+Command::Output Command::Exec(
+    std::initializer_list<std::string> arguments) const {
+  if (!Found()) {
+    Output out;
+    out.err = "Executable not found";
+    return out;
+  }
+
+  // Pipes used for piping std[in,out,err] to / from the target process.
+  Pipe stdin_pipe;
+  Pipe stdout_pipe;
+  Pipe stderr_pipe;
+
+  if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
+    Output output;
+    output.err = "Command::Exec(): Failed to create pipes";
+    return output;
+  }
+
+  // execv() and friends replace the current process image with the target
+  // process image. To keep process that called this function going, we need to
+  // fork() this process into a child and parent process.
+  //
+  // The child process is responsible for hooking up the pipes to
+  // std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
+  // run the target command.
+  //
+  // The parent process is responsible for feeding any input to the stdin_pipe
+  // and collectting output from the std[out,err]_pipes.
+
+  int child_id = fork();
+  if (child_id < 0) {
+    Output output;
+    output.err = "Command::Exec(): fork() failed";
+    return output;
+  }
+
+  if (child_id > 0) {
+    // fork() - parent
+
+    // Close the stdout and stderr writer pipes.
+    // This is required for getting poll() POLLHUP events.
+    stdout_pipe.write.Close();
+    stderr_pipe.write.Close();
+
+    // Write the input to the child process
+    if (!input_.empty()) {
+      ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
+      if (n != static_cast<ssize_t>(input_.size())) {
+        Output output;
+        output.err = "Command::Exec(): write() for stdin failed";
+        return output;
+      }
+    }
+    stdin_pipe.write.Close();
+
+    // Accumulate the stdout and stderr output from the child process
+    pollfd poll_fds[2];
+    poll_fds[0].fd = stdout_pipe.read;
+    poll_fds[0].events = POLLIN;
+    poll_fds[1].fd = stderr_pipe.read;
+    poll_fds[1].events = POLLIN;
+
+    Output output;
+    bool stdout_open = true;
+    bool stderr_open = true;
+    while (stdout_open || stderr_open) {
+      if (poll(poll_fds, 2, -1) < 0) {
+        break;
+      }
+      char buf[256];
+      if (poll_fds[0].revents & POLLIN) {
+        auto n = read(stdout_pipe.read, buf, sizeof(buf));
+        if (n > 0) {
+          output.out += std::string(buf, buf + n);
+        }
+      }
+      if (poll_fds[0].revents & POLLHUP) {
+        stdout_open = false;
+      }
+      if (poll_fds[1].revents & POLLIN) {
+        auto n = read(stderr_pipe.read, buf, sizeof(buf));
+        if (n > 0) {
+          output.err += std::string(buf, buf + n);
+        }
+      }
+      if (poll_fds[1].revents & POLLHUP) {
+        stderr_open = false;
+      }
+    }
+
+    // Get the resulting error code
+    waitpid(child_id, &output.error_code, 0);
+
+    return output;
+  } else {
+    // fork() - child
+
+    // Redirect the stdin, stdout, stderr pipes for the execv process
+    if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
+        (dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
+        (dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
+      fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
+      exit(errno);
+    }
+
+    // Close the pipes, once redirected above, we're now done with them.
+    stdin_pipe.Close();
+    stdout_pipe.Close();
+    stderr_pipe.Close();
+
+    // Run target executable
+    std::vector<const char*> args;
+    args.emplace_back(path_.c_str());
+    for (auto& arg : arguments) {
+      args.emplace_back(arg.c_str());
+    }
+    args.emplace_back(nullptr);
+    auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
+    exit(res);
+  }
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/command_test.cc b/src/tint/utils/io/command_test.cc
new file mode 100644
index 0000000..f76dcae
--- /dev/null
+++ b/src/tint/utils/io/command_test.cc
@@ -0,0 +1,92 @@
+// 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/utils/io/command.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+#ifdef _WIN32
+
+TEST(CommandTest, Echo) {
+  auto cmd = Command::LookPath("cmd");
+  if (!cmd.Found()) {
+    GTEST_SKIP() << "cmd not found on PATH";
+  }
+
+  auto res = cmd("/C", "echo", "hello world");
+  EXPECT_EQ(res.error_code, 0);
+  EXPECT_EQ(res.out, "hello world\r\n");
+  EXPECT_EQ(res.err, "");
+}
+
+#else
+
+TEST(CommandTest, Echo) {
+  auto cmd = Command::LookPath("echo");
+  if (!cmd.Found()) {
+    GTEST_SKIP() << "echo not found on PATH";
+  }
+
+  auto res = cmd("hello world");
+  EXPECT_EQ(res.error_code, 0);
+  EXPECT_EQ(res.out, "hello world\n");
+  EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, Cat) {
+  auto cmd = Command::LookPath("cat");
+  if (!cmd.Found()) {
+    GTEST_SKIP() << "cat not found on PATH";
+  }
+
+  cmd.SetInput("hello world");
+  auto res = cmd();
+  EXPECT_EQ(res.error_code, 0);
+  EXPECT_EQ(res.out, "hello world");
+  EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, True) {
+  auto cmd = Command::LookPath("true");
+  if (!cmd.Found()) {
+    GTEST_SKIP() << "true not found on PATH";
+  }
+
+  auto res = cmd();
+  EXPECT_EQ(res.error_code, 0);
+  EXPECT_EQ(res.out, "");
+  EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, False) {
+  auto cmd = Command::LookPath("false");
+  if (!cmd.Found()) {
+    GTEST_SKIP() << "false not found on PATH";
+  }
+
+  auto res = cmd();
+  EXPECT_NE(res.error_code, 0);
+  EXPECT_EQ(res.out, "");
+  EXPECT_EQ(res.err, "");
+}
+
+#endif
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/command_windows.cc b/src/tint/utils/io/command_windows.cc
new file mode 100644
index 0000000..576fc4c
--- /dev/null
+++ b/src/tint/utils/io/command_windows.cc
@@ -0,0 +1,249 @@
+// 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/utils/io/command.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+#include <sstream>
+#include <string>
+
+namespace tint {
+namespace utils {
+
+namespace {
+
+/// Handle is a simple wrapper around the Win32 HANDLE
+class Handle {
+ public:
+  /// Constructor
+  Handle() : handle_(nullptr) {}
+
+  /// Constructor
+  explicit Handle(HANDLE handle) : handle_(handle) {}
+
+  /// Destructor
+  ~Handle() { Close(); }
+
+  /// Move assignment operator
+  Handle& operator=(Handle&& rhs) {
+    Close();
+    handle_ = rhs.handle_;
+    rhs.handle_ = nullptr;
+    return *this;
+  }
+
+  /// Closes the handle (if it wasn't already closed)
+  void Close() {
+    if (handle_) {
+      CloseHandle(handle_);
+    }
+    handle_ = nullptr;
+  }
+
+  /// @returns the handle
+  operator HANDLE() { return handle_; }
+
+  /// @returns true if the handle is not invalid
+  operator bool() { return handle_ != nullptr; }
+
+ private:
+  Handle(const Handle&) = delete;
+  Handle& operator=(const Handle&) = delete;
+
+  HANDLE handle_ = nullptr;
+};
+
+/// Pipe is a simple wrapper around a Win32 CreatePipe() function
+class Pipe {
+ public:
+  /// Constructs the pipe
+  explicit Pipe(bool for_read) {
+    SECURITY_ATTRIBUTES sa;
+    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+    sa.bInheritHandle = TRUE;
+    sa.lpSecurityDescriptor = nullptr;
+
+    HANDLE hread;
+    HANDLE hwrite;
+    if (CreatePipe(&hread, &hwrite, &sa, 0)) {
+      read = Handle(hread);
+      write = Handle(hwrite);
+      // Ensure the read handle to the pipe is not inherited
+      if (!SetHandleInformation(for_read ? read : write, HANDLE_FLAG_INHERIT,
+                                0)) {
+        read.Close();
+        write.Close();
+      }
+    }
+  }
+
+  /// @returns true if the pipe has an open read or write file
+  operator bool() { return read || write; }
+
+  /// The reader end of the pipe
+  Handle read;
+
+  /// The writer end of the pipe
+  Handle write;
+};
+
+bool ExecutableExists(const std::string& path) {
+  DWORD type = 0;
+  return GetBinaryTypeA(path.c_str(), &type);
+}
+
+std::string FindExecutable(const std::string& name) {
+  if (ExecutableExists(name)) {
+    return name;
+  }
+  if (ExecutableExists(name + ".exe")) {
+    return name + ".exe";
+  }
+  if (name.find("/") == std::string::npos &&
+      name.find("\\") == std::string::npos) {
+    char* path_env = nullptr;
+    size_t path_env_len = 0;
+    if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
+      return "";
+    }
+    std::istringstream path{path_env};
+    free(path_env);
+    std::string dir;
+    while (getline(path, dir, ';')) {
+      auto test = dir + "\\" + name;
+      if (ExecutableExists(test)) {
+        return test;
+      }
+      if (ExecutableExists(test + ".exe")) {
+        return test + ".exe";
+      }
+    }
+  }
+  return "";
+}
+
+}  // namespace
+
+Command::Command(const std::string& path) : path_(path) {}
+
+Command Command::LookPath(const std::string& executable) {
+  return Command(FindExecutable(executable));
+}
+
+bool Command::Found() const {
+  return ExecutableExists(path_);
+}
+
+Command::Output Command::Exec(
+    std::initializer_list<std::string> arguments) const {
+  Pipe stdout_pipe(true);
+  Pipe stderr_pipe(true);
+  Pipe stdin_pipe(false);
+  if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
+    Output output;
+    output.err = "Command::Exec(): Failed to create pipes";
+    return output;
+  }
+
+  if (!input_.empty()) {
+    if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr,
+                   nullptr)) {
+      Output output;
+      output.err = "Command::Exec() Failed to write stdin";
+      return output;
+    }
+  }
+  stdin_pipe.write.Close();
+
+  STARTUPINFOA si{};
+  si.cb = sizeof(si);
+  si.dwFlags |= STARTF_USESTDHANDLES;
+  si.hStdOutput = stdout_pipe.write;
+  si.hStdError = stderr_pipe.write;
+  si.hStdInput = stdin_pipe.read;
+
+  std::stringstream args;
+  args << path_;
+  for (auto& arg : arguments) {
+    args << " " << arg;
+  }
+
+  PROCESS_INFORMATION pi{};
+  if (!CreateProcessA(nullptr,  // No module name (use command line)
+                      const_cast<LPSTR>(args.str().c_str()),  // Command line
+                      nullptr,  // Process handle not inheritable
+                      nullptr,  // Thread handle not inheritable
+                      TRUE,     // Handles are inherited
+                      0,        // No creation flags
+                      nullptr,  // Use parent's environment block
+                      nullptr,  // Use parent's starting directory
+                      &si,      // Pointer to STARTUPINFO structure
+                      &pi)) {   // Pointer to PROCESS_INFORMATION structure
+    Output out;
+    out.err = "Command::Exec() CreateProcess() failed";
+    return out;
+  }
+
+  stdin_pipe.read.Close();
+  stdout_pipe.write.Close();
+  stderr_pipe.write.Close();
+
+  struct StreamReadThreadArgs {
+    HANDLE stream;
+    std::string output;
+  };
+
+  auto stream_read_thread = [](LPVOID user) -> DWORD {
+    auto* thread_args = reinterpret_cast<StreamReadThreadArgs*>(user);
+    DWORD n = 0;
+    char buf[256];
+    while (ReadFile(thread_args->stream, buf, sizeof(buf), &n, NULL)) {
+      auto s = std::string(buf, buf + n);
+      thread_args->output += std::string(buf, buf + n);
+    }
+    return 0;
+  };
+
+  StreamReadThreadArgs stdout_read_args{stdout_pipe.read, {}};
+  auto* stdout_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
+                                            &stdout_read_args, 0, nullptr);
+
+  StreamReadThreadArgs stderr_read_args{stderr_pipe.read, {}};
+  auto* stderr_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
+                                            &stderr_read_args, 0, nullptr);
+
+  HANDLE handles[] = {pi.hProcess, stdout_read_thread, stderr_read_thread};
+  constexpr DWORD num_handles = sizeof(handles) / sizeof(handles[0]);
+
+  Output output;
+
+  auto res = WaitForMultipleObjects(num_handles, handles, /* wait_all = */ TRUE,
+                                    INFINITE);
+  if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + num_handles) {
+    output.out = stdout_read_args.output;
+    output.err = stderr_read_args.output;
+    DWORD exit_code = 0;
+    GetExitCodeProcess(pi.hProcess, &exit_code);
+    output.error_code = static_cast<int>(exit_code);
+  } else {
+    output.err = "Command::Exec() WaitForMultipleObjects() returned " +
+                 std::to_string(res);
+  }
+
+  return output;
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/tmpfile.h b/src/tint/utils/io/tmpfile.h
new file mode 100644
index 0000000..07f48af
--- /dev/null
+++ b/src/tint/utils/io/tmpfile.h
@@ -0,0 +1,76 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_IO_TMPFILE_H_
+#define SRC_TINT_UTILS_IO_TMPFILE_H_
+
+#include <sstream>
+#include <string>
+
+namespace tint {
+namespace utils {
+
+/// TmpFile constructs a temporary file that can be written to, and is
+/// automatically deleted on destruction.
+class TmpFile {
+ public:
+  /// Constructor.
+  /// Creates a new temporary file which can be written to.
+  /// The temporary file will be automatically deleted on destruction.
+  /// @param extension optional file extension to use with the file. The file
+  /// have no extension by default.
+  explicit TmpFile(std::string extension = "");
+
+  /// Destructor.
+  /// Deletes the temporary file.
+  ~TmpFile();
+
+  /// @return true if the temporary file was successfully created.
+  operator bool() { return !path_.empty(); }
+
+  /// @return the path to the temporary file
+  std::string Path() const { return path_; }
+
+  /// Opens the temporary file and appends |size| bytes from |data| to the end
+  /// of the temporary file. The temporary file is closed again before
+  /// returning, allowing other processes to open the file on operating systems
+  /// that require exclusive ownership of opened files.
+  /// @param data the data to write to the end of the file
+  /// @param size the number of bytes to write from data
+  /// @returns true on success, otherwise false
+  bool Append(const void* data, size_t size) const;
+
+  /// Appends the argument to the end of the file.
+  /// @param data the data to write to the end of the file
+  /// @return a reference to this TmpFile
+  template <typename T>
+  inline TmpFile& operator<<(T&& data) {
+    std::stringstream ss;
+    ss << data;
+    std::string str = ss.str();
+    Append(str.data(), str.size());
+    return *this;
+  }
+
+ private:
+  TmpFile(const TmpFile&) = delete;
+  TmpFile& operator=(const TmpFile&) = delete;
+
+  std::string path_;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_IO_TMPFILE_H_
diff --git a/src/tint/utils/io/tmpfile_other.cc b/src/tint/utils/io/tmpfile_other.cc
new file mode 100644
index 0000000..b4c1a18
--- /dev/null
+++ b/src/tint/utils/io/tmpfile_other.cc
@@ -0,0 +1,29 @@
+// 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/utils/io/tmpfile.h"
+
+namespace tint {
+namespace utils {
+
+TmpFile::TmpFile(std::string) {}
+
+TmpFile::~TmpFile() = default;
+
+bool TmpFile::Append(const void*, size_t) const {
+  return false;
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/tmpfile_posix.cc b/src/tint/utils/io/tmpfile_posix.cc
new file mode 100644
index 0000000..db9ee47
--- /dev/null
+++ b/src/tint/utils/io/tmpfile_posix.cc
@@ -0,0 +1,70 @@
+// 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/utils/io/tmpfile.h"
+
+#include <unistd.h>
+#include <limits>
+
+#include "src/tint/debug.h"
+
+namespace tint {
+namespace utils {
+
+namespace {
+
+std::string TmpFilePath(std::string ext) {
+  char const* dir = getenv("TMPDIR");
+  if (dir == nullptr) {
+    dir = "/tmp";
+  }
+
+  // mkstemps requires an `int` for the file extension name but STL represents
+  // size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion
+  // (when the source value exceeds the representable range) is implementation
+  // defined. While such a large file extension is unlikely in practice, we
+  // enforce this here at runtime.
+  TINT_ASSERT(Utils, ext.length() <=
+                         static_cast<size_t>(std::numeric_limits<int>::max()));
+  std::string name = std::string(dir) + "/tint_XXXXXX" + ext;
+  int file = mkstemps(&name[0], static_cast<int>(ext.length()));
+  if (file != -1) {
+    close(file);
+    return name;
+  }
+  return "";
+}
+
+}  // namespace
+
+TmpFile::TmpFile(std::string extension)
+    : path_(TmpFilePath(std::move(extension))) {}
+
+TmpFile::~TmpFile() {
+  if (!path_.empty()) {
+    remove(path_.c_str());
+  }
+}
+
+bool TmpFile::Append(const void* data, size_t size) const {
+  if (auto* file = fopen(path_.c_str(), "ab")) {
+    fwrite(data, size, 1, file);
+    fclose(file);
+    return true;
+  }
+  return false;
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/tmpfile_test.cc b/src/tint/utils/io/tmpfile_test.cc
new file mode 100644
index 0000000..abd9511
--- /dev/null
+++ b/src/tint/utils/io/tmpfile_test.cc
@@ -0,0 +1,90 @@
+// 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/utils/io/tmpfile.h"
+
+#include <fstream>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(TmpFileTest, WriteReadAppendDelete) {
+  std::string path;
+  {
+    TmpFile tmp;
+    if (!tmp) {
+      GTEST_SKIP() << "Unable to create a temporary file";
+    }
+
+    path = tmp.Path();
+
+    // Write a string to the temporary file
+    tmp << "hello world\n";
+
+    // Check the content of the file
+    {
+      std::ifstream file(path);
+      ASSERT_TRUE(file);
+      std::string line;
+      EXPECT_TRUE(std::getline(file, line));
+      EXPECT_EQ(line, "hello world");
+      EXPECT_FALSE(std::getline(file, line));
+    }
+
+    // Write some more content to the file
+    tmp << 42;
+
+    // Check the content of the file again
+    {
+      std::ifstream file(path);
+      ASSERT_TRUE(file);
+      std::string line;
+      EXPECT_TRUE(std::getline(file, line));
+      EXPECT_EQ(line, "hello world");
+      EXPECT_TRUE(std::getline(file, line));
+      EXPECT_EQ(line, "42");
+      EXPECT_FALSE(std::getline(file, line));
+    }
+  }
+
+  // Check the file has been deleted when it fell out of scope
+  std::ifstream file(path);
+  ASSERT_FALSE(file);
+}
+
+TEST(TmpFileTest, FileExtension) {
+  const std::string kExt = ".foo";
+  std::string path;
+  {
+    TmpFile tmp(kExt);
+    if (!tmp) {
+      GTEST_SKIP() << "Unable create a temporary file";
+    }
+    path = tmp.Path();
+  }
+
+  ASSERT_GT(path.length(), kExt.length());
+  EXPECT_EQ(kExt, path.substr(path.length() - kExt.length()));
+
+  // Check the file has been deleted when it fell out of scope
+  std::ifstream file(path);
+  ASSERT_FALSE(file);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/io/tmpfile_windows.cc b/src/tint/utils/io/tmpfile_windows.cc
new file mode 100644
index 0000000..2b57b68
--- /dev/null
+++ b/src/tint/utils/io/tmpfile_windows.cc
@@ -0,0 +1,63 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/io/tmpfile.h"
+
+#include <stdio.h>
+#include <cstdio>
+
+namespace tint {
+namespace utils {
+
+namespace {
+
+std::string TmpFilePath(const std::string& ext) {
+  char name[L_tmpnam];
+  // As we're adding an extension, to ensure the file is really unique, try
+  // creating it, failing if it already exists.
+  while (tmpnam_s(name, L_tmpnam - 1) == 0) {
+    std::string name_with_ext = std::string(name) + ext;
+    FILE* f = nullptr;
+    // The "x" arg forces the function to fail if the file already exists.
+    fopen_s(&f, name_with_ext.c_str(), "wbx");
+    if (f) {
+      fclose(f);
+      return name_with_ext;
+    }
+  }
+  return {};
+}
+
+}  // namespace
+
+TmpFile::TmpFile(std::string ext) : path_(TmpFilePath(ext)) {}
+
+TmpFile::~TmpFile() {
+  if (!path_.empty()) {
+    remove(path_.c_str());
+  }
+}
+
+bool TmpFile::Append(const void* data, size_t size) const {
+  FILE* file = nullptr;
+  if (fopen_s(&file, path_.c_str(), "ab") != 0) {
+    return false;
+  }
+  fwrite(data, size, 1, file);
+  fclose(file);
+  return true;
+}
+
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/map.h b/src/tint/utils/map.h
new file mode 100644
index 0000000..0f60d13
--- /dev/null
+++ b/src/tint/utils/map.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MAP_H_
+#define SRC_TINT_UTILS_MAP_H_
+
+#include <unordered_map>
+
+namespace tint {
+namespace utils {
+
+/// Lookup is a utility function for fetching a value from an unordered map if
+/// it exists, otherwise returning the `if_missing` argument.
+/// @param map the unordered_map
+/// @param key the map key of the item to query
+/// @param if_missing the value to return if the map does not contain the given
+/// key. Defaults to the zero-initializer for the value type.
+/// @return the map item value, or `if_missing` if the map does not contain the
+/// given key
+template <typename K, typename V, typename H, typename C, typename KV = K>
+V Lookup(const std::unordered_map<K, V, H, C>& map,
+         const KV& key,
+         const V& if_missing = {}) {
+  auto it = map.find(key);
+  return it != map.end() ? it->second : if_missing;
+}
+
+/// GetOrCreate is a utility function for lazily adding to an unordered map.
+/// If the map already contains the key `key` then this is returned, otherwise
+/// `create()` is called and the result is added to the map and is returned.
+/// @param map the unordered_map
+/// @param key the map key of the item to query or add
+/// @param create a callable function-like object with the signature `V()`
+/// @return the value of the item with the given key, or the newly created item
+template <typename K, typename V, typename H, typename C, typename CREATE>
+V GetOrCreate(std::unordered_map<K, V, H, C>& map,
+              const K& key,
+              CREATE&& create) {
+  auto it = map.find(key);
+  if (it != map.end()) {
+    return it->second;
+  }
+  V value = create();
+  map.emplace(key, value);
+  return value;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_MAP_H_
diff --git a/src/tint/utils/map_test.cc b/src/tint/utils/map_test.cc
new file mode 100644
index 0000000..a1e8dec
--- /dev/null
+++ b/src/tint/utils/map_test.cc
@@ -0,0 +1,58 @@
+// 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/utils/map.h"
+
+#include <unordered_map>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(Lookup, Test) {
+  std::unordered_map<int, int> map;
+  map.emplace(10, 1);
+  EXPECT_EQ(Lookup(map, 10, 0), 1);    // exists, with if_missing
+  EXPECT_EQ(Lookup(map, 10), 1);       // exists, without if_missing
+  EXPECT_EQ(Lookup(map, 20, 50), 50);  // missing, with if_missing
+  EXPECT_EQ(Lookup(map, 20), 0);       // missing, without if_missing
+}
+
+TEST(GetOrCreateTest, NewKey) {
+  std::unordered_map<int, int> map;
+  EXPECT_EQ(GetOrCreate(map, 1, [&] { return 2; }), 2);
+  EXPECT_EQ(map.size(), 1u);
+  EXPECT_EQ(map[1], 2);
+}
+
+TEST(GetOrCreateTest, ExistingKey) {
+  std::unordered_map<int, int> map;
+  map[1] = 2;
+  bool called = false;
+  EXPECT_EQ(GetOrCreate(map, 1,
+                        [&] {
+                          called = true;
+                          return -2;
+                        }),
+            2);
+  EXPECT_EQ(called, false);
+  EXPECT_EQ(map.size(), 1u);
+  EXPECT_EQ(map[1], 2);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/math.h b/src/tint/utils/math.h
new file mode 100644
index 0000000..155e39f
--- /dev/null
+++ b/src/tint/utils/math.h
@@ -0,0 +1,57 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MATH_H_
+#define SRC_TINT_UTILS_MATH_H_
+
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+namespace tint {
+namespace utils {
+
+/// @param alignment the next multiple to round `value` to
+/// @param value the value to round to the next multiple of `alignment`
+/// @return `value` rounded to the next multiple of `alignment`
+/// @note `alignment` must be positive. An alignment of zero will cause a DBZ.
+template <typename T>
+inline T RoundUp(T alignment, T value) {
+  return ((value + alignment - 1) / alignment) * alignment;
+}
+
+/// @param value the value to check whether it is a power-of-two
+/// @returns true if `value` is a power-of-two
+/// @note `value` must be positive if `T` is signed
+template <typename T>
+inline bool IsPowerOfTwo(T value) {
+  return (value & (value - 1)) == 0;
+}
+
+/// @param value the input value
+/// @returns the largest power of two that `value` is a multiple of
+template <typename T>
+inline std::enable_if_t<std::is_unsigned<T>::value, T> MaxAlignOf(T value) {
+  T pot = 1;
+  while (value && ((value & 1u) == 0)) {
+    pot <<= 1;
+    value >>= 1;
+  }
+  return pot;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_MATH_H_
diff --git a/src/tint/utils/math_test.cc b/src/tint/utils/math_test.cc
new file mode 100644
index 0000000..057f142
--- /dev/null
+++ b/src/tint/utils/math_test.cc
@@ -0,0 +1,83 @@
+// 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/utils/math.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(MathTests, RoundUp) {
+  EXPECT_EQ(RoundUp(1, 0), 0);
+  EXPECT_EQ(RoundUp(1, 1), 1);
+  EXPECT_EQ(RoundUp(1, 2), 2);
+
+  EXPECT_EQ(RoundUp(1, 1), 1);
+  EXPECT_EQ(RoundUp(2, 1), 2);
+  EXPECT_EQ(RoundUp(3, 1), 3);
+  EXPECT_EQ(RoundUp(4, 1), 4);
+
+  EXPECT_EQ(RoundUp(1, 2), 2);
+  EXPECT_EQ(RoundUp(2, 2), 2);
+  EXPECT_EQ(RoundUp(3, 2), 3);
+  EXPECT_EQ(RoundUp(4, 2), 4);
+
+  EXPECT_EQ(RoundUp(1, 3), 3);
+  EXPECT_EQ(RoundUp(2, 3), 4);
+  EXPECT_EQ(RoundUp(3, 3), 3);
+  EXPECT_EQ(RoundUp(4, 3), 4);
+
+  EXPECT_EQ(RoundUp(1, 4), 4);
+  EXPECT_EQ(RoundUp(2, 4), 4);
+  EXPECT_EQ(RoundUp(3, 4), 6);
+  EXPECT_EQ(RoundUp(4, 4), 4);
+}
+
+TEST(MathTests, IsPowerOfTwo) {
+  EXPECT_EQ(IsPowerOfTwo(1), true);
+  EXPECT_EQ(IsPowerOfTwo(2), true);
+  EXPECT_EQ(IsPowerOfTwo(3), false);
+  EXPECT_EQ(IsPowerOfTwo(4), true);
+  EXPECT_EQ(IsPowerOfTwo(5), false);
+  EXPECT_EQ(IsPowerOfTwo(6), false);
+  EXPECT_EQ(IsPowerOfTwo(7), false);
+  EXPECT_EQ(IsPowerOfTwo(8), true);
+  EXPECT_EQ(IsPowerOfTwo(9), false);
+}
+
+TEST(MathTests, MaxAlignOf) {
+  EXPECT_EQ(MaxAlignOf(0u), 1u);
+  EXPECT_EQ(MaxAlignOf(1u), 1u);
+  EXPECT_EQ(MaxAlignOf(2u), 2u);
+  EXPECT_EQ(MaxAlignOf(3u), 1u);
+  EXPECT_EQ(MaxAlignOf(4u), 4u);
+  EXPECT_EQ(MaxAlignOf(5u), 1u);
+  EXPECT_EQ(MaxAlignOf(6u), 2u);
+  EXPECT_EQ(MaxAlignOf(7u), 1u);
+  EXPECT_EQ(MaxAlignOf(8u), 8u);
+  EXPECT_EQ(MaxAlignOf(9u), 1u);
+  EXPECT_EQ(MaxAlignOf(10u), 2u);
+  EXPECT_EQ(MaxAlignOf(11u), 1u);
+  EXPECT_EQ(MaxAlignOf(12u), 4u);
+  EXPECT_EQ(MaxAlignOf(13u), 1u);
+  EXPECT_EQ(MaxAlignOf(14u), 2u);
+  EXPECT_EQ(MaxAlignOf(15u), 1u);
+  EXPECT_EQ(MaxAlignOf(16u), 16u);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/reverse.h b/src/tint/utils/reverse.h
new file mode 100644
index 0000000..17b1346
--- /dev/null
+++ b/src/tint/utils/reverse.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_REVERSE_H_
+#define SRC_TINT_UTILS_REVERSE_H_
+
+#include <iterator>
+
+namespace tint {
+namespace utils {
+
+namespace detail {
+/// Used by utils::Reverse to hold the underlying iterable.
+/// begin(ReverseIterable<T>) and end(ReverseIterable<T>) are automatically
+/// called for range-for loops, via argument-dependent lookup.
+/// See https://en.cppreference.com/w/cpp/language/range-for
+template <typename T>
+struct ReverseIterable {
+  /// The wrapped iterable object.
+  T& iterable;
+};
+
+template <typename T>
+auto begin(ReverseIterable<T> r_it) {
+  return std::rbegin(r_it.iterable);
+}
+
+template <typename T>
+auto end(ReverseIterable<T> r_it) {
+  return std::rend(r_it.iterable);
+}
+}  // namespace detail
+
+/// Reverse returns an iterable wrapper that when used in range-for loops,
+/// performs a reverse iteration over the object `iterable`.
+/// Example:
+/// ```
+/// /* Equivalent to:
+///  * for (auto it = vec.rbegin(); i != vec.rend(); ++it) {
+///  *   auto v = *it;
+///  */
+/// for (auto v : utils::Reverse(vec)) {
+/// }
+/// ```
+template <typename T>
+detail::ReverseIterable<T> Reverse(T&& iterable) {
+  return {iterable};
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_REVERSE_H_
diff --git a/src/tint/utils/reverse_test.cc b/src/tint/utils/reverse_test.cc
new file mode 100644
index 0000000..64734cd
--- /dev/null
+++ b/src/tint/utils/reverse_test.cc
@@ -0,0 +1,36 @@
+// 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/utils/reverse.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(ReverseTest, Vector) {
+  std::vector<int> vec{1, 3, 5, 7, 9};
+  std::vector<int> rev;
+  for (auto v : Reverse(vec)) {
+    rev.emplace_back(v);
+  }
+  ASSERT_THAT(rev, testing::ElementsAre(9, 7, 5, 3, 1));
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/scoped_assignment.h b/src/tint/utils/scoped_assignment.h
new file mode 100644
index 0000000..0ff3421
--- /dev/null
+++ b/src/tint/utils/scoped_assignment.h
@@ -0,0 +1,64 @@
+
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_SCOPED_ASSIGNMENT_H_
+#define SRC_TINT_UTILS_SCOPED_ASSIGNMENT_H_
+
+#include <type_traits>
+
+#include "src/tint/utils/concat.h"
+
+namespace tint {
+namespace utils {
+
+/// Helper class that temporarily assigns a value to a variable for the lifetime
+/// of the ScopedAssignment object. Once the ScopedAssignment is destructed, the
+/// original value is restored.
+template <typename T>
+class ScopedAssignment {
+ public:
+  /// Constructor
+  /// @param var the variable to temporarily assign a new value to
+  /// @param val the value to assign to `ref` for the lifetime of this
+  /// ScopedAssignment.
+  ScopedAssignment(T& var, T val) : ref_(var) {
+    old_value_ = var;
+    var = val;
+  }
+
+  /// Destructor
+  /// Restores the original value of the variable.
+  ~ScopedAssignment() { ref_ = old_value_; }
+
+ private:
+  ScopedAssignment(const ScopedAssignment&) = delete;
+  ScopedAssignment& operator=(const ScopedAssignment&) = delete;
+
+  T& ref_;
+  T old_value_;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+/// TINT_SCOPED_ASSIGNMENT(var, val) assigns `val` to `var`, and automatically
+/// restores the original value of `var` when exiting the current lexical scope.
+#define TINT_SCOPED_ASSIGNMENT(var, val)                                  \
+  ::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> \
+  TINT_CONCAT(tint_scoped_assignment_, __COUNTER__) {                     \
+    var, val                                                              \
+  }
+
+#endif  // SRC_TINT_UTILS_SCOPED_ASSIGNMENT_H_
diff --git a/src/tint/utils/scoped_assignment_test.cc b/src/tint/utils/scoped_assignment_test.cc
new file mode 100644
index 0000000..20cd8d0
--- /dev/null
+++ b/src/tint/utils/scoped_assignment_test.cc
@@ -0,0 +1,47 @@
+// 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/utils/scoped_assignment.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(ScopedAssignmentTest, Scopes) {
+  int i = 0;
+  EXPECT_EQ(i, 0);
+  {
+    EXPECT_EQ(i, 0);
+    TINT_SCOPED_ASSIGNMENT(i, 1);
+    EXPECT_EQ(i, 1);
+    {
+      EXPECT_EQ(i, 1);
+      TINT_SCOPED_ASSIGNMENT(i, 2);
+      EXPECT_EQ(i, 2);
+    }
+    {
+      EXPECT_EQ(i, 1);
+      TINT_SCOPED_ASSIGNMENT(i, 3);
+      EXPECT_EQ(i, 3);
+    }
+    EXPECT_EQ(i, 1);
+  }
+  EXPECT_EQ(i, 0);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/string.h b/src/tint/utils/string.h
new file mode 100644
index 0000000..0355819
--- /dev/null
+++ b/src/tint/utils/string.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_STRING_H_
+#define SRC_TINT_UTILS_STRING_H_
+
+#include <string>
+
+namespace tint {
+namespace utils {
+
+/// @param str the string to apply replacements to
+/// @param substr the string to search for
+/// @param replacement the replacement string to use instead of `substr`
+/// @returns `str` with all occurrences of `substr` replaced with `replacement`
+inline std::string ReplaceAll(std::string str,
+                              const std::string& substr,
+                              const std::string& replacement) {
+  size_t pos = 0;
+  while ((pos = str.find(substr, pos)) != std::string::npos) {
+    str.replace(pos, substr.length(), replacement);
+    pos += replacement.length();
+  }
+  return str;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_STRING_H_
diff --git a/src/tint/utils/string_test.cc b/src/tint/utils/string_test.cc
new file mode 100644
index 0000000..3dbb486
--- /dev/null
+++ b/src/tint/utils/string_test.cc
@@ -0,0 +1,38 @@
+// 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/utils/string.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(StringTest, ReplaceAll) {
+  ASSERT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
+  ASSERT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
+  ASSERT_EQ("aabbxy", ReplaceAll("aabbcc", "cc", "xy"));
+  ASSERT_EQ("xyxybbcc", ReplaceAll("aabbcc", "a", "xy"));
+  ASSERT_EQ("aaxyxycc", ReplaceAll("aabbcc", "b", "xy"));
+  ASSERT_EQ("aabbxyxy", ReplaceAll("aabbcc", "c", "xy"));
+  // Replacement string includes the searched-for string.
+  // This proves that the algorithm needs to advance 'pos'
+  // past the replacement.
+  ASSERT_EQ("aabxybbxybcc", ReplaceAll("aabbcc", "b", "bxyb"));
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/to_const_ptr_vec.h b/src/tint/utils/to_const_ptr_vec.h
new file mode 100644
index 0000000..e36fe0c
--- /dev/null
+++ b/src/tint/utils/to_const_ptr_vec.h
@@ -0,0 +1,39 @@
+
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TO_CONST_PTR_VEC_H_
+#define SRC_TINT_UTILS_TO_CONST_PTR_VEC_H_
+
+#include <vector>
+
+namespace tint {
+namespace utils {
+
+/// @param in a vector of `T*`
+/// @returns a vector of `const T*` with the content of `in`.
+template <typename T>
+std::vector<const T*> ToConstPtrVec(const std::vector<T*>& in) {
+  std::vector<const T*> out;
+  out.reserve(in.size());
+  for (auto* ptr : in) {
+    out.emplace_back(ptr);
+  }
+  return out;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_TO_CONST_PTR_VEC_H_
diff --git a/src/tint/utils/transform.h b/src/tint/utils/transform.h
new file mode 100644
index 0000000..97711fd
--- /dev/null
+++ b/src/tint/utils/transform.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TRANSFORM_H_
+#define SRC_TINT_UTILS_TRANSFORM_H_
+
+#include <algorithm>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "src/tint/traits.h"
+
+namespace tint {
+namespace utils {
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @returns a new vector with each element of the source vector transformed by
+/// `transform`.
+template <typename IN, typename TRANSFORMER>
+auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
+    -> std::vector<decltype(transform(in[0]))> {
+  std::vector<decltype(transform(in[0]))> result(in.size());
+  for (size_t i = 0; i < result.size(); ++i) {
+    result[i] = transform(in[i]);
+  }
+  return result;
+}
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature:
+/// `OUT(IN, size_t)`
+/// @returns a new vector with each element of the source vector transformed by
+/// `transform`.
+template <typename IN, typename TRANSFORMER>
+auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
+    -> std::vector<decltype(transform(in[0], 1u))> {
+  std::vector<decltype(transform(in[0], 1u))> result(in.size());
+  for (size_t i = 0; i < result.size(); ++i) {
+    result[i] = transform(in[i], i);
+  }
+  return result;
+}
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_TRANSFORM_H_
diff --git a/src/tint/utils/transform_test.cc b/src/tint/utils/transform_test.cc
new file mode 100644
index 0000000..6686eeb
--- /dev/null
+++ b/src/tint/utils/transform_test.cc
@@ -0,0 +1,94 @@
+// 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/utils/transform.h"
+
+#include <string>
+#include <type_traits>
+
+#include "gmock/gmock.h"
+
+#define CHECK_ELEMENT_TYPE(vector, expected)                                 \
+  static_assert(std::is_same<decltype(vector)::value_type, expected>::value, \
+                "unexpected result vector element type")
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(TransformTest, Empty) {
+  const std::vector<int> empty{};
+  {
+    auto transformed = Transform(empty, [](int) -> int {
+      [] { FAIL() << "Transform should not be called for empty vector"; }();
+      return 0;
+    });
+    CHECK_ELEMENT_TYPE(transformed, int);
+    EXPECT_EQ(transformed.size(), 0u);
+  }
+  {
+    auto transformed = Transform(empty, [](int, size_t) -> int {
+      [] { FAIL() << "Transform should not be called for empty vector"; }();
+      return 0;
+    });
+    CHECK_ELEMENT_TYPE(transformed, int);
+    EXPECT_EQ(transformed.size(), 0u);
+  }
+}
+
+TEST(TransformTest, Identity) {
+  const std::vector<int> input{1, 2, 3, 4};
+  {
+    auto transformed = Transform(input, [](int i) { return i; });
+    CHECK_ELEMENT_TYPE(transformed, int);
+    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+  }
+  {
+    auto transformed = Transform(input, [](int i, size_t) { return i; });
+    CHECK_ELEMENT_TYPE(transformed, int);
+    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+  }
+}
+
+TEST(TransformTest, Index) {
+  const std::vector<int> input{10, 20, 30, 40};
+  {
+    auto transformed = Transform(input, [](int, size_t idx) { return idx; });
+    CHECK_ELEMENT_TYPE(transformed, size_t);
+    EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
+  }
+}
+
+TEST(TransformTest, TransformSameType) {
+  const std::vector<int> input{1, 2, 3, 4};
+  {
+    auto transformed = Transform(input, [](int i) { return i * 10; });
+    CHECK_ELEMENT_TYPE(transformed, int);
+    EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
+  }
+}
+
+TEST(TransformTest, TransformDifferentType) {
+  const std::vector<int> input{1, 2, 3, 4};
+  {
+    auto transformed =
+        Transform(input, [](int i) { return std::to_string(i); });
+    CHECK_ELEMENT_TYPE(transformed, std::string);
+    EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
+  }
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/utils/unique_vector.h b/src/tint/utils/unique_vector.h
new file mode 100644
index 0000000..96d3ac3
--- /dev/null
+++ b/src/tint/utils/unique_vector.h
@@ -0,0 +1,113 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_UNIQUE_VECTOR_H_
+#define SRC_TINT_UTILS_UNIQUE_VECTOR_H_
+
+#include <cstddef>
+#include <functional>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace tint {
+namespace utils {
+
+/// UniqueVector is an ordered container that only contains unique items.
+/// Attempting to add a duplicate is a no-op.
+template <typename T,
+          typename HASH = std::hash<T>,
+          typename EQUAL = std::equal_to<T>>
+struct UniqueVector {
+  /// The iterator returned by begin() and end()
+  using ConstIterator = typename std::vector<T>::const_iterator;
+  /// The iterator returned by rbegin() and rend()
+  using ConstReverseIterator = typename std::vector<T>::const_reverse_iterator;
+
+  /// Constructor
+  UniqueVector() = default;
+
+  /// Constructor
+  /// @param v the vector to construct this UniqueVector with. Duplicate
+  /// elements will be removed.
+  explicit UniqueVector(std::vector<T>&& v) {
+    for (auto& el : v) {
+      add(el);
+    }
+  }
+
+  /// add appends the item to the end of the vector, if the vector does not
+  /// already contain the given item.
+  /// @param item the item to append to the end of the vector
+  /// @returns true if the item was added, otherwise false.
+  bool add(const T& item) {
+    if (set.count(item) == 0) {
+      vector.emplace_back(item);
+      set.emplace(item);
+      return true;
+    }
+    return false;
+  }
+
+  /// @returns true if the vector contains `item`
+  /// @param item the item
+  bool contains(const T& item) const { return set.count(item); }
+
+  /// @param i the index of the element to retrieve
+  /// @returns the element at the index `i`
+  T& operator[](size_t i) { return vector[i]; }
+
+  /// @param i the index of the element to retrieve
+  /// @returns the element at the index `i`
+  const T& operator[](size_t i) const { return vector[i]; }
+
+  /// @returns true if the vector is empty
+  bool empty() const { return vector.empty(); }
+
+  /// @returns the number of items in the vector
+  size_t size() const { return vector.size(); }
+
+  /// @returns an iterator to the beginning of the vector
+  ConstIterator begin() const { return vector.begin(); }
+
+  /// @returns an iterator to the end of the vector
+  ConstIterator end() const { return vector.end(); }
+
+  /// @returns an iterator to the beginning of the reversed vector
+  ConstReverseIterator rbegin() const { return vector.rbegin(); }
+
+  /// @returns an iterator to the end of the reversed vector
+  ConstReverseIterator rend() const { return vector.rend(); }
+
+  /// @returns a const reference to the internal vector
+  operator const std::vector<T>&() const { return vector; }
+
+  /// Removes the last element from the vector
+  /// @returns the popped element
+  T pop_back() {
+    auto el = std::move(vector.back());
+    set.erase(el);
+    vector.pop_back();
+    return el;
+  }
+
+ private:
+  std::vector<T> vector;
+  std::unordered_set<T, HASH, EQUAL> set;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_UNIQUE_VECTOR_H_
diff --git a/src/tint/utils/unique_vector_test.cc b/src/tint/utils/unique_vector_test.cc
new file mode 100644
index 0000000..892282b
--- /dev/null
+++ b/src/tint/utils/unique_vector_test.cc
@@ -0,0 +1,145 @@
+// 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/utils/unique_vector.h"
+#include "src/tint/utils/reverse.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(UniqueVectorTest, Empty) {
+  UniqueVector<int> unique_vec;
+  EXPECT_EQ(unique_vec.size(), 0u);
+  EXPECT_EQ(unique_vec.empty(), true);
+  EXPECT_EQ(unique_vec.begin(), unique_vec.end());
+}
+
+TEST(UniqueVectorTest, MoveConstructor) {
+  UniqueVector<int> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
+  EXPECT_EQ(unique_vec.size(), 4u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+  EXPECT_EQ(unique_vec[1], 3);
+  EXPECT_EQ(unique_vec[2], 2);
+  EXPECT_EQ(unique_vec[3], 1);
+}
+
+TEST(UniqueVectorTest, AddUnique) {
+  UniqueVector<int> unique_vec;
+  unique_vec.add(0);
+  unique_vec.add(1);
+  unique_vec.add(2);
+  EXPECT_EQ(unique_vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  int i = 0;
+  for (auto n : unique_vec) {
+    EXPECT_EQ(n, i);
+    i++;
+  }
+  for (auto n : Reverse(unique_vec)) {
+    i--;
+    EXPECT_EQ(n, i);
+  }
+  EXPECT_EQ(unique_vec[0], 0);
+  EXPECT_EQ(unique_vec[1], 1);
+  EXPECT_EQ(unique_vec[2], 2);
+}
+
+TEST(UniqueVectorTest, AddDuplicates) {
+  UniqueVector<int> unique_vec;
+  unique_vec.add(0);
+  unique_vec.add(0);
+  unique_vec.add(0);
+  unique_vec.add(1);
+  unique_vec.add(1);
+  unique_vec.add(2);
+  EXPECT_EQ(unique_vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  int i = 0;
+  for (auto n : unique_vec) {
+    EXPECT_EQ(n, i);
+    i++;
+  }
+  for (auto n : Reverse(unique_vec)) {
+    i--;
+    EXPECT_EQ(n, i);
+  }
+  EXPECT_EQ(unique_vec[0], 0);
+  EXPECT_EQ(unique_vec[1], 1);
+  EXPECT_EQ(unique_vec[2], 2);
+}
+
+TEST(UniqueVectorTest, AsVector) {
+  UniqueVector<int> unique_vec;
+  unique_vec.add(0);
+  unique_vec.add(0);
+  unique_vec.add(0);
+  unique_vec.add(1);
+  unique_vec.add(1);
+  unique_vec.add(2);
+
+  const std::vector<int>& vec = unique_vec;
+  EXPECT_EQ(vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  int i = 0;
+  for (auto n : vec) {
+    EXPECT_EQ(n, i);
+    i++;
+  }
+  for (auto n : Reverse(unique_vec)) {
+    i--;
+    EXPECT_EQ(n, i);
+  }
+}
+
+TEST(UniqueVectorTest, PopBack) {
+  UniqueVector<int> unique_vec;
+  unique_vec.add(0);
+  unique_vec.add(2);
+  unique_vec.add(1);
+
+  EXPECT_EQ(unique_vec.pop_back(), 1);
+  EXPECT_EQ(unique_vec.size(), 2u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+  EXPECT_EQ(unique_vec[1], 2);
+
+  EXPECT_EQ(unique_vec.pop_back(), 2);
+  EXPECT_EQ(unique_vec.size(), 1u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+
+  unique_vec.add(1);
+
+  EXPECT_EQ(unique_vec.size(), 2u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+  EXPECT_EQ(unique_vec[1], 1);
+
+  EXPECT_EQ(unique_vec.pop_back(), 1);
+  EXPECT_EQ(unique_vec.size(), 1u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+
+  EXPECT_EQ(unique_vec.pop_back(), 0);
+  EXPECT_EQ(unique_vec.size(), 0u);
+  EXPECT_EQ(unique_vec.empty(), true);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/src/tint/val/hlsl.cc b/src/tint/val/hlsl.cc
new file mode 100644
index 0000000..56ee061
--- /dev/null
+++ b/src/tint/val/hlsl.cc
@@ -0,0 +1,178 @@
+// 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/val/val.h"
+
+#include "src/tint/utils/io/command.h"
+#include "src/tint/utils/io/tmpfile.h"
+
+#ifdef _WIN32
+#include <Windows.h>
+#include <d3dcommon.h>
+#include <d3dcompiler.h>
+
+#include <wrl.h>
+using Microsoft::WRL::ComPtr;
+#endif  // _WIN32
+
+namespace tint {
+namespace val {
+
+Result HlslUsingDXC(const std::string& dxc_path,
+                    const std::string& source,
+                    const EntryPointList& entry_points) {
+  Result result;
+
+  auto dxc = utils::Command(dxc_path);
+  if (!dxc.Found()) {
+    result.output = "DXC not found at '" + std::string(dxc_path) + "'";
+    result.failed = true;
+    return result;
+  }
+
+  utils::TmpFile file;
+  file << source;
+
+  for (auto ep : entry_points) {
+    const char* profile = "";
+
+    switch (ep.second) {
+      case ast::PipelineStage::kNone:
+        result.output = "Invalid PipelineStage";
+        result.failed = true;
+        return result;
+      case ast::PipelineStage::kVertex:
+        profile = "-T vs_6_0";
+        break;
+      case ast::PipelineStage::kFragment:
+        profile = "-T ps_6_0";
+        break;
+      case ast::PipelineStage::kCompute:
+        profile = "-T cs_6_0";
+        break;
+    }
+
+    // Match Dawn's compile flags
+    // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
+    // and dawn_native\d3d12\ShaderModuleD3D12.cpp (GetDXCArguments)
+    const char* compileFlags =
+        "/Zpr "  // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
+        "/Gis";  // D3DCOMPILE_IEEE_STRICTNESS
+
+    auto res = dxc(profile, "-E " + ep.first, compileFlags, file.Path());
+    if (!res.out.empty()) {
+      if (!result.output.empty()) {
+        result.output += "\n";
+      }
+      result.output += res.out;
+    }
+    if (!res.err.empty()) {
+      if (!result.output.empty()) {
+        result.output += "\n";
+      }
+      result.output += res.err;
+    }
+    result.failed = (res.error_code != 0);
+  }
+
+  if (entry_points.empty()) {
+    result.output = "No entrypoint found";
+    result.failed = true;
+    return result;
+  }
+
+  return result;
+}
+
+#ifdef _WIN32
+Result HlslUsingFXC(const std::string& source,
+                    const EntryPointList& entry_points) {
+  Result result;
+
+  // This library leaks if an error happens in this function, but it is ok
+  // because it is loaded at most once, and the executables using HlslUsingFXC
+  // are short-lived.
+  HMODULE fxcLib = LoadLibraryA("d3dcompiler_47.dll");
+  if (fxcLib == nullptr) {
+    result.output = "Couldn't load FXC";
+    result.failed = true;
+    return result;
+  }
+
+  pD3DCompile d3dCompile =
+      reinterpret_cast<pD3DCompile>(GetProcAddress(fxcLib, "D3DCompile"));
+  if (d3dCompile == nullptr) {
+    result.output = "Couldn't load D3DCompile from FXC";
+    result.failed = true;
+    return result;
+  }
+
+  for (auto ep : entry_points) {
+    const char* profile = "";
+    switch (ep.second) {
+      case ast::PipelineStage::kNone:
+        result.output = "Invalid PipelineStage";
+        result.failed = true;
+        return result;
+      case ast::PipelineStage::kVertex:
+        profile = "vs_5_1";
+        break;
+      case ast::PipelineStage::kFragment:
+        profile = "ps_5_1";
+        break;
+      case ast::PipelineStage::kCompute:
+        profile = "cs_5_1";
+        break;
+    }
+
+    // Match Dawn's compile flags
+    // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
+    UINT compileFlags = D3DCOMPILE_OPTIMIZATION_LEVEL0 |
+                        D3DCOMPILE_PACK_MATRIX_ROW_MAJOR |
+                        D3DCOMPILE_IEEE_STRICTNESS;
+
+    ComPtr<ID3DBlob> compiledShader;
+    ComPtr<ID3DBlob> errors;
+    HRESULT cr = d3dCompile(source.c_str(),    // pSrcData
+                            source.length(),   // SrcDataSize
+                            nullptr,           // pSourceName
+                            nullptr,           // pDefines
+                            nullptr,           // pInclude
+                            ep.first.c_str(),  // pEntrypoint
+                            profile,           // pTarget
+                            compileFlags,      // Flags1
+                            0,                 // Flags2
+                            &compiledShader,   // ppCode
+                            &errors);          // ppErrorMsgs
+    if (FAILED(cr)) {
+      result.output = static_cast<char*>(errors->GetBufferPointer());
+      result.failed = true;
+      return result;
+    }
+  }
+
+  FreeLibrary(fxcLib);
+
+  if (entry_points.empty()) {
+    result.output = "No entrypoint found";
+    result.failed = true;
+    return result;
+  }
+
+  return result;
+}
+#endif  // _WIN32
+
+}  // namespace val
+}  // namespace tint
diff --git a/src/tint/val/msl.cc b/src/tint/val/msl.cc
new file mode 100644
index 0000000..30c0361
--- /dev/null
+++ b/src/tint/val/msl.cc
@@ -0,0 +1,69 @@
+// 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/val/val.h"
+
+#include "src/tint/ast/module.h"
+#include "src/tint/program.h"
+#include "src/tint/utils/io/command.h"
+#include "src/tint/utils/io/tmpfile.h"
+
+namespace tint {
+namespace val {
+
+Result Msl(const std::string& xcrun_path, const std::string& source) {
+  Result result;
+
+  auto xcrun = utils::Command(xcrun_path);
+  if (!xcrun.Found()) {
+    result.output = "xcrun not found at '" + std::string(xcrun_path) + "'";
+    result.failed = true;
+    return result;
+  }
+
+  utils::TmpFile file(".metal");
+  file << source;
+
+#ifdef _WIN32
+  // On Windows, we should actually be running metal.exe from the Metal
+  // Developer Tools for Windows
+  auto res = xcrun("-x", "metal",        //
+                   "-o", "NUL",          //
+                   "-std=osx-metal1.2",  //
+                   "-c", file.Path());
+#else
+  auto res = xcrun("-sdk", "macosx", "metal",  //
+                   "-o", "/dev/null",          //
+                   "-std=osx-metal1.2",        //
+                   "-c", file.Path());
+#endif
+  if (!res.out.empty()) {
+    if (!result.output.empty()) {
+      result.output += "\n";
+    }
+    result.output += res.out;
+  }
+  if (!res.err.empty()) {
+    if (!result.output.empty()) {
+      result.output += "\n";
+    }
+    result.output += res.err;
+  }
+  result.failed = (res.error_code != 0);
+
+  return result;
+}
+
+}  // namespace val
+}  // namespace tint
diff --git a/src/tint/val/msl_metal.mm b/src/tint/val/msl_metal.mm
new file mode 100644
index 0000000..68870ac
--- /dev/null
+++ b/src/tint/val/msl_metal.mm
@@ -0,0 +1,61 @@
+// 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.
+
+#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
+
+@import Metal;
+
+// Disable: error: treating #include as an import of module 'std.string'
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wauto-import"
+#include "src/tint/val/val.h"
+#pragma clang diagnostic pop
+
+namespace tint {
+namespace val {
+
+Result MslUsingMetalAPI(const std::string& src) {
+  tint::val::Result result;
+
+  NSError* error = nil;
+
+  id<MTLDevice> device = MTLCreateSystemDefaultDevice();
+  if (!device) {
+    result.output = "MTLCreateSystemDefaultDevice returned null";
+    result.failed = true;
+    return result;
+  }
+
+  NSString* source = [NSString stringWithCString:src.c_str()
+                                        encoding:NSUTF8StringEncoding];
+
+  MTLCompileOptions* compileOptions = [MTLCompileOptions new];
+  compileOptions.languageVersion = MTLLanguageVersion1_2;
+
+  id<MTLLibrary> library = [device newLibraryWithSource:source
+                                                options:compileOptions
+                                                  error:&error];
+  if (!library) {
+    NSString* output = [error localizedDescription];
+    result.output = [output UTF8String];
+    result.failed = true;
+  }
+
+  return result;
+}
+
+}  // namespace val
+}  // namespace tint
+
+#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
diff --git a/src/tint/val/val.h b/src/tint/val/val.h
new file mode 100644
index 0000000..a8dd94c
--- /dev/null
+++ b/src/tint/val/val.h
@@ -0,0 +1,80 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_VAL_VAL_H_
+#define SRC_TINT_VAL_VAL_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/pipeline_stage.h"
+
+// Forward declarations
+namespace tint {
+class Program;
+}  // namespace tint
+
+namespace tint {
+namespace val {
+
+using EntryPointList = std::vector<std::pair<std::string, ast::PipelineStage>>;
+
+/// The return structure of Validate()
+struct Result {
+  /// True if validation passed
+  bool failed = false;
+  /// Output of DXC.
+  std::string output;
+};
+
+/// Hlsl attempts to compile the shader with DXC, verifying that the shader
+/// compiles successfully.
+/// @param dxc_path path to DXC
+/// @param source the generated HLSL source
+/// @param entry_points the list of entry points to validate
+/// @return the result of the compile
+Result HlslUsingDXC(const std::string& dxc_path,
+                    const std::string& source,
+                    const EntryPointList& entry_points);
+
+#ifdef _WIN32
+/// Hlsl attempts to compile the shader with FXC, verifying that the shader
+/// compiles successfully.
+/// @param source the generated HLSL source
+/// @param entry_points the list of entry points to validate
+/// @return the result of the compile
+Result HlslUsingFXC(const std::string& source,
+                    const EntryPointList& entry_points);
+#endif  // _WIN32
+
+/// Msl attempts to compile the shader with the Metal Shader Compiler,
+/// verifying that the shader compiles successfully.
+/// @param xcrun_path path to xcrun
+/// @param source the generated MSL source
+/// @return the result of the compile
+Result Msl(const std::string& xcrun_path, const std::string& source);
+
+#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
+/// Msl attempts to compile the shader with the runtime Metal Shader Compiler
+/// API, verifying that the shader compiles successfully.
+/// @param source the generated MSL source
+/// @return the result of the compile
+Result MslUsingMetalAPI(const std::string& source);
+#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
+
+}  // namespace val
+}  // namespace tint
+
+#endif  // SRC_TINT_VAL_VAL_H_
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
new file mode 100644
index 0000000..18f5ee9
--- /dev/null
+++ b/src/tint/writer/append_vector.cc
@@ -0,0 +1,176 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/append_vector.h"
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/utils/transform.h"
+
+namespace tint {
+namespace writer {
+
+namespace {
+
+struct VectorConstructorInfo {
+  const sem::Call* call = nullptr;
+  const sem::TypeConstructor* ctor = nullptr;
+  operator bool() const { return call != nullptr; }
+};
+VectorConstructorInfo AsVectorConstructor(const sem::Expression* expr) {
+  if (auto* call = expr->As<sem::Call>()) {
+    if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
+      if (ctor->ReturnType()->Is<sem::Vector>()) {
+        return {call, ctor};
+      }
+    }
+  }
+  return {};
+}
+
+const sem::Expression* Zero(ProgramBuilder& b,
+                            const sem::Type* ty,
+                            const sem::Statement* stmt) {
+  const ast::Expression* expr = nullptr;
+  if (ty->Is<sem::I32>()) {
+    expr = b.Expr(0);
+  } else if (ty->Is<sem::U32>()) {
+    expr = b.Expr(0u);
+  } else if (ty->Is<sem::F32>()) {
+    expr = b.Expr(0.0f);
+  } else if (ty->Is<sem::Bool>()) {
+    expr = b.Expr(false);
+  } else {
+    TINT_UNREACHABLE(Writer, b.Diagnostics())
+        << "unsupported vector element type: " << ty->TypeInfo().name;
+    return nullptr;
+  }
+  auto* sem = b.create<sem::Expression>(expr, ty, stmt, sem::Constant{},
+                                        /* has_side_effects */ false);
+  b.Sem().Add(expr, sem);
+  return sem;
+}
+
+}  // namespace
+
+const sem::Call* AppendVector(ProgramBuilder* b,
+                              const ast::Expression* vector_ast,
+                              const ast::Expression* scalar_ast) {
+  uint32_t packed_size;
+  const sem::Type* packed_el_sem_ty;
+  auto* vector_sem = b->Sem().Get(vector_ast);
+  auto* scalar_sem = b->Sem().Get(scalar_ast);
+  auto* vector_ty = vector_sem->Type()->UnwrapRef();
+  if (auto* vec = vector_ty->As<sem::Vector>()) {
+    packed_size = vec->Width() + 1;
+    packed_el_sem_ty = vec->type();
+  } else {
+    packed_size = 2;
+    packed_el_sem_ty = vector_ty;
+  }
+
+  const ast::Type* packed_el_ast_ty = nullptr;
+  if (packed_el_sem_ty->Is<sem::I32>()) {
+    packed_el_ast_ty = b->create<ast::I32>();
+  } else if (packed_el_sem_ty->Is<sem::U32>()) {
+    packed_el_ast_ty = b->create<ast::U32>();
+  } else if (packed_el_sem_ty->Is<sem::F32>()) {
+    packed_el_ast_ty = b->create<ast::F32>();
+  } else if (packed_el_sem_ty->Is<sem::Bool>()) {
+    packed_el_ast_ty = b->create<ast::Bool>();
+  } else {
+    TINT_UNREACHABLE(Writer, b->Diagnostics())
+        << "unsupported vector element type: "
+        << packed_el_sem_ty->TypeInfo().name;
+  }
+
+  auto* statement = vector_sem->Stmt();
+
+  auto* packed_ast_ty = b->create<ast::Vector>(packed_el_ast_ty, packed_size);
+  auto* packed_sem_ty = b->create<sem::Vector>(packed_el_sem_ty, packed_size);
+
+  // If the coordinates are already passed in a vector constructor, with only
+  // scalar components supplied, extract the elements into the new vector
+  // instead of nesting a vector-in-vector.
+  // If the coordinates are a zero-constructor of the vector, then expand that
+  // to scalar zeros.
+  // The other cases for a nested vector constructor are when it is used
+  // to convert a vector of a different type, e.g. vec2<i32>(vec2<u32>()).
+  // In that case, preserve the original argument, or you'll get a type error.
+
+  std::vector<const sem::Expression*> packed;
+  if (auto vc = AsVectorConstructor(vector_sem)) {
+    const auto num_supplied = vc.call->Arguments().size();
+    if (num_supplied == 0) {
+      // Zero-value vector constructor. Populate with zeros
+      for (uint32_t i = 0; i < packed_size - 1; i++) {
+        auto* zero = Zero(*b, packed_el_sem_ty, statement);
+        packed.emplace_back(zero);
+      }
+    } else if (num_supplied + 1 == packed_size) {
+      // All vector components were supplied as scalars.  Pass them through.
+      packed = vc.call->Arguments();
+    }
+  }
+  if (packed.empty()) {
+    // The special cases didn't occur. Use the vector argument as-is.
+    packed.emplace_back(vector_sem);
+  }
+
+  if (packed_el_sem_ty != scalar_sem->Type()->UnwrapRef()) {
+    // Cast scalar to the vector element type
+    auto* scalar_cast_ast = b->Construct(packed_el_ast_ty, scalar_ast);
+    auto* scalar_cast_target = b->create<sem::TypeConversion>(
+        packed_el_sem_ty,
+        b->create<sem::Parameter>(nullptr, 0, scalar_sem->Type()->UnwrapRef(),
+                                  ast::StorageClass::kNone,
+                                  ast::Access::kUndefined));
+    auto* scalar_cast_sem = b->create<sem::Call>(
+        scalar_cast_ast, scalar_cast_target,
+        std::vector<const sem::Expression*>{scalar_sem}, statement,
+        sem::Constant{}, /* has_side_effects */ false);
+    b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
+    packed.emplace_back(scalar_cast_sem);
+  } else {
+    packed.emplace_back(scalar_sem);
+  }
+
+  auto* constructor_ast = b->Construct(
+      packed_ast_ty, utils::Transform(packed, [&](const sem::Expression* expr) {
+        return expr->Declaration();
+      }));
+  auto* constructor_target = b->create<sem::TypeConstructor>(
+      packed_sem_ty, utils::Transform(packed,
+                                      [&](const tint::sem::Expression* arg,
+                                          size_t i) -> const sem::Parameter* {
+                                        return b->create<sem::Parameter>(
+                                            nullptr, static_cast<uint32_t>(i),
+                                            arg->Type()->UnwrapRef(),
+                                            ast::StorageClass::kNone,
+                                            ast::Access::kUndefined);
+                                      }));
+  auto* constructor_sem = b->create<sem::Call>(
+      constructor_ast, constructor_target, packed, statement, sem::Constant{},
+      /* has_side_effects */ false);
+  b->Sem().Add(constructor_ast, constructor_sem);
+  return constructor_sem;
+}
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/append_vector.h b/src/tint/writer/append_vector.h
new file mode 100644
index 0000000..d8613af
--- /dev/null
+++ b/src/tint/writer/append_vector.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_APPEND_VECTOR_H_
+#define SRC_TINT_WRITER_APPEND_VECTOR_H_
+
+#include "src/tint/program_builder.h"
+
+namespace tint {
+
+namespace ast {
+class CallExpression;
+class Expression;
+}  // namespace ast
+
+namespace writer {
+
+/// A helper function used to append a vector with an additional scalar.
+/// If the scalar's type does not match the target vector element type,
+/// then it is value-converted (via TypeConstructor) before being added.
+/// All types must have been assigned to the expressions and their child nodes
+/// before calling.
+/// @param builder the program builder.
+/// @param vector the vector to be appended. May be a scalar, `vec2` or `vec3`.
+/// @param scalar the scalar to append to the vector. Must be a scalar.
+/// @returns a vector expression containing the elements of `vector` followed by
+/// the single element of `scalar` cast to the `vector` element type.
+const sem::Call* AppendVector(ProgramBuilder* builder,
+                              const ast::Expression* vector,
+                              const ast::Expression* scalar);
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_APPEND_VECTOR_H_
diff --git a/src/tint/writer/append_vector_test.cc b/src/tint/writer/append_vector_test.cc
new file mode 100644
index 0000000..433f890
--- /dev/null
+++ b/src/tint/writer/append_vector_test.cc
@@ -0,0 +1,488 @@
+// 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/writer/append_vector.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/resolver/resolver.h"
+#include "src/tint/sem/type_constructor.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace {
+
+class AppendVectorTest : public ::testing::Test, public ProgramBuilder {};
+
+// AppendVector(vec2<i32>(1, 2), 3) -> vec3<i32>(1, 2, 3)
+TEST_F(AppendVectorTest, Vec2i32_i32) {
+  auto* scalar_1 = Expr(1);
+  auto* scalar_2 = Expr(2);
+  auto* scalar_3 = Expr(3);
+  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 3u);
+  EXPECT_EQ(vec_123->args[0], scalar_1);
+  EXPECT_EQ(vec_123->args[1], scalar_2);
+  EXPECT_EQ(vec_123->args[2], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 3u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec2<i32>(1, 2), 3u) -> vec3<i32>(1, 2, i32(3u))
+TEST_F(AppendVectorTest, Vec2i32_u32) {
+  auto* scalar_1 = Expr(1);
+  auto* scalar_2 = Expr(2);
+  auto* scalar_3 = Expr(3u);
+  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 3u);
+  EXPECT_EQ(vec_123->args[0], scalar_1);
+  EXPECT_EQ(vec_123->args[1], scalar_2);
+  auto* u32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
+  ASSERT_NE(u32_to_i32, nullptr);
+  EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
+  ASSERT_EQ(u32_to_i32->args.size(), 1u);
+  EXPECT_EQ(u32_to_i32->args[0], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 3u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(u32_to_i32));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec2<i32>(vec2<u32>(1u, 2u)), 3u) ->
+//    vec3<i32>(vec2<i32>(vec2<u32>(1u, 2u)), i32(3u))
+TEST_F(AppendVectorTest, Vec2i32FromVec2u32_u32) {
+  auto* scalar_1 = Expr(1u);
+  auto* scalar_2 = Expr(2u);
+  auto* scalar_3 = Expr(3u);
+  auto* uvec_12 = vec2<u32>(scalar_1, scalar_2);
+  auto* vec_12 = vec2<i32>(uvec_12);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 2u);
+  auto* v2u32_to_v2i32 = vec_123->args[0]->As<ast::CallExpression>();
+  ASSERT_NE(v2u32_to_v2i32, nullptr);
+  ASSERT_TRUE(v2u32_to_v2i32->target.type->Is<ast::Vector>());
+  EXPECT_EQ(v2u32_to_v2i32->target.type->As<ast::Vector>()->width, 2u);
+  EXPECT_TRUE(
+      v2u32_to_v2i32->target.type->As<ast::Vector>()->type->Is<ast::I32>());
+  EXPECT_EQ(v2u32_to_v2i32->args.size(), 1u);
+  EXPECT_EQ(v2u32_to_v2i32->args[0], uvec_12);
+
+  auto* u32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
+  ASSERT_NE(u32_to_i32, nullptr);
+  EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
+  ASSERT_EQ(u32_to_i32->args.size(), 1u);
+  EXPECT_EQ(u32_to_i32->args[0], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 2u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(u32_to_i32));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec2<i32>(1, 2), 3.0f) -> vec3<i32>(1, 2, i32(3.0f))
+TEST_F(AppendVectorTest, Vec2i32_f32) {
+  auto* scalar_1 = Expr(1);
+  auto* scalar_2 = Expr(2);
+  auto* scalar_3 = Expr(3.0f);
+  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 3u);
+  EXPECT_EQ(vec_123->args[0], scalar_1);
+  EXPECT_EQ(vec_123->args[1], scalar_2);
+  auto* f32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
+  ASSERT_NE(f32_to_i32, nullptr);
+  EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
+  ASSERT_EQ(f32_to_i32->args.size(), 1u);
+  EXPECT_EQ(f32_to_i32->args[0], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 3u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(f32_to_i32));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec3<i32>(1, 2, 3), 4) -> vec4<i32>(1, 2, 3, 4)
+TEST_F(AppendVectorTest, Vec3i32_i32) {
+  auto* scalar_1 = Expr(1);
+  auto* scalar_2 = Expr(2);
+  auto* scalar_3 = Expr(3);
+  auto* scalar_4 = Expr(4);
+  auto* vec_123 = vec3<i32>(scalar_1, scalar_2, scalar_3);
+  WrapInFunction(vec_123, scalar_4);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_123, scalar_4);
+
+  auto* vec_1234 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_1234, nullptr);
+  ASSERT_EQ(vec_1234->args.size(), 4u);
+  EXPECT_EQ(vec_1234->args[0], scalar_1);
+  EXPECT_EQ(vec_1234->args[1], scalar_2);
+  EXPECT_EQ(vec_1234->args[2], scalar_3);
+  EXPECT_EQ(vec_1234->args[3], scalar_4);
+
+  auto* call = Sem().Get(vec_1234);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 4u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
+  EXPECT_EQ(call->Arguments()[3], Sem().Get(scalar_4));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 4u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[3]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec_12, 3) -> vec3<i32>(vec_12, 3)
+TEST_F(AppendVectorTest, Vec2i32Var_i32) {
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  auto* vec_12 = Expr("vec_12");
+  auto* scalar_3 = Expr(3);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 2u);
+  EXPECT_EQ(vec_123->args[0], vec_12);
+  EXPECT_EQ(vec_123->args[1], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 2u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(1, 2, scalar_3) -> vec3<i32>(1, 2, scalar_3)
+TEST_F(AppendVectorTest, Vec2i32_i32Var) {
+  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
+  auto* scalar_1 = Expr(1);
+  auto* scalar_2 = Expr(2);
+  auto* scalar_3 = Expr("scalar_3");
+  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 3u);
+  EXPECT_EQ(vec_123->args[0], scalar_1);
+  EXPECT_EQ(vec_123->args[1], scalar_2);
+  EXPECT_EQ(vec_123->args[2], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 3u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 3u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec_12, scalar_3) -> vec3<i32>(vec_12, scalar_3)
+TEST_F(AppendVectorTest, Vec2i32Var_i32Var) {
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
+  auto* vec_12 = Expr("vec_12");
+  auto* scalar_3 = Expr("scalar_3");
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 2u);
+  EXPECT_EQ(vec_123->args[0], vec_12);
+  EXPECT_EQ(vec_123->args[1], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 2u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec_12, scalar_3) -> vec3<i32>(vec_12, i32(scalar_3))
+TEST_F(AppendVectorTest, Vec2i32Var_f32Var) {
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("scalar_3", ty.f32(), ast::StorageClass::kPrivate);
+  auto* vec_12 = Expr("vec_12");
+  auto* scalar_3 = Expr("scalar_3");
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 2u);
+  EXPECT_EQ(vec_123->args[0], vec_12);
+  auto* f32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
+  ASSERT_NE(f32_to_i32, nullptr);
+  EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
+  ASSERT_EQ(f32_to_i32->args.size(), 1u);
+  EXPECT_EQ(f32_to_i32->args[0], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 2u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(f32_to_i32));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+}
+
+// AppendVector(vec_12, scalar_3) -> vec3<bool>(vec_12, scalar_3)
+TEST_F(AppendVectorTest, Vec2boolVar_boolVar) {
+  Global("vec_12", ty.vec2<bool>(), ast::StorageClass::kPrivate);
+  Global("scalar_3", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* vec_12 = Expr("vec_12");
+  auto* scalar_3 = Expr("scalar_3");
+  WrapInFunction(vec_12, scalar_3);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec_12, scalar_3);
+
+  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_123, nullptr);
+  ASSERT_EQ(vec_123->args.size(), 2u);
+  EXPECT_EQ(vec_123->args[0], vec_12);
+  EXPECT_EQ(vec_123->args[1], scalar_3);
+
+  auto* call = Sem().Get(vec_123);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 2u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 2u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
+}
+
+// AppendVector(vec3<i32>(), 4) -> vec3<bool>(0, 0, 0, 4)
+TEST_F(AppendVectorTest, ZeroVec3i32_i32) {
+  auto* scalar = Expr(4);
+  auto* vec000 = vec3<i32>();
+  WrapInFunction(vec000, scalar);
+
+  resolver::Resolver resolver(this);
+  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
+
+  auto* append = AppendVector(this, vec000, scalar);
+
+  auto* vec_0004 = As<ast::CallExpression>(append->Declaration());
+  ASSERT_NE(vec_0004, nullptr);
+  ASSERT_EQ(vec_0004->args.size(), 4u);
+  for (size_t i = 0; i < 3; i++) {
+    auto* literal = As<ast::SintLiteralExpression>(vec_0004->args[i]);
+    ASSERT_NE(literal, nullptr);
+    EXPECT_EQ(literal->value, 0);
+  }
+  EXPECT_EQ(vec_0004->args[3], scalar);
+
+  auto* call = Sem().Get(vec_0004);
+  ASSERT_NE(call, nullptr);
+  ASSERT_EQ(call->Arguments().size(), 4u);
+  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_0004->args[0]));
+  EXPECT_EQ(call->Arguments()[1], Sem().Get(vec_0004->args[1]));
+  EXPECT_EQ(call->Arguments()[2], Sem().Get(vec_0004->args[2]));
+  EXPECT_EQ(call->Arguments()[3], Sem().Get(scalar));
+
+  auto* ctor = call->Target()->As<sem::TypeConstructor>();
+  ASSERT_NE(ctor, nullptr);
+  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
+  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 4u);
+  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(ctor->ReturnType(), call->Type());
+
+  ASSERT_EQ(ctor->Parameters().size(), 4u);
+  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
+  EXPECT_TRUE(ctor->Parameters()[3]->Type()->Is<sem::I32>());
+}
+
+}  // namespace
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/array_length_from_uniform_options.cc b/src/tint/writer/array_length_from_uniform_options.cc
new file mode 100644
index 0000000..258eaae
--- /dev/null
+++ b/src/tint/writer/array_length_from_uniform_options.cc
@@ -0,0 +1,30 @@
+// 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/writer/array_length_from_uniform_options.h"
+
+namespace tint {
+namespace writer {
+
+ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions() = default;
+ArrayLengthFromUniformOptions::~ArrayLengthFromUniformOptions() = default;
+ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(
+    const ArrayLengthFromUniformOptions&) = default;
+ArrayLengthFromUniformOptions& ArrayLengthFromUniformOptions::operator=(
+    const ArrayLengthFromUniformOptions&) = default;
+ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(
+    ArrayLengthFromUniformOptions&&) = default;
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/array_length_from_uniform_options.h b/src/tint/writer/array_length_from_uniform_options.h
new file mode 100644
index 0000000..24a35cb
--- /dev/null
+++ b/src/tint/writer/array_length_from_uniform_options.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
+#define SRC_TINT_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
+
+#include <unordered_map>
+
+#include "src/tint/sem/binding_point.h"
+
+namespace tint {
+namespace writer {
+
+/// Options used to specify a mapping of binding points to indices into a UBO
+/// from which to load buffer sizes.
+struct ArrayLengthFromUniformOptions {
+  /// Constructor
+  ArrayLengthFromUniformOptions();
+  /// Destructor
+  ~ArrayLengthFromUniformOptions();
+  /// Copy constructor
+  ArrayLengthFromUniformOptions(const ArrayLengthFromUniformOptions&);
+  /// Copy assignment
+  /// @returns this ArrayLengthFromUniformOptions
+  ArrayLengthFromUniformOptions& operator=(
+      const ArrayLengthFromUniformOptions&);
+  /// Move constructor
+  ArrayLengthFromUniformOptions(ArrayLengthFromUniformOptions&&);
+
+  /// The binding point to use to generate a uniform buffer from which to read
+  /// buffer sizes.
+  sem::BindingPoint ubo_binding;
+  /// The mapping from storage buffer binding points to the index into the
+  /// uniform buffer where the length of the buffer is stored.
+  std::unordered_map<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
+
+  // NOTE: Update src/tint/fuzzers/data_builder.h when adding or changing any
+  // struct members.
+};
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
diff --git a/src/tint/writer/float_to_string.cc b/src/tint/writer/float_to_string.cc
new file mode 100644
index 0000000..25073c0
--- /dev/null
+++ b/src/tint/writer/float_to_string.cc
@@ -0,0 +1,158 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/float_to_string.h"
+
+#include <cmath>
+#include <cstring>
+#include <functional>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+
+#include "src/tint/debug.h"
+
+namespace tint {
+namespace writer {
+
+std::string FloatToString(float f) {
+  // Try printing the float in fixed point, with a smallish limit on the
+  // precision
+  std::stringstream fixed;
+  fixed.flags(fixed.flags() | std::ios_base::showpoint | std::ios_base::fixed);
+  fixed.precision(9);
+  fixed << f;
+
+  // If this string can be parsed without loss of information, use it
+  auto float_equal_no_warning = std::equal_to<float>();
+  if (float_equal_no_warning(std::stof(fixed.str()), f)) {
+    auto str = fixed.str();
+    while (str.length() >= 2 && str[str.size() - 1] == '0' &&
+           str[str.size() - 2] != '.') {
+      str.pop_back();
+    }
+
+    return str;
+  }
+
+  // Resort to scientific, with the minimum precision needed to preserve the
+  // whole float
+  std::stringstream sci;
+  sci.precision(std::numeric_limits<float>::max_digits10);
+  sci << f;
+  return sci.str();
+}
+
+std::string FloatToBitPreservingString(float f) {
+  // For the NaN case, avoid handling the number as a floating point value.
+  // Some machines will modify the top bit in the mantissa of a NaN.
+
+  std::stringstream ss;
+
+  uint32_t float_bits = 0u;
+  std::memcpy(&float_bits, &f, sizeof(float_bits));
+
+  // Handle the sign.
+  const uint32_t kSignMask = 1u << 31;
+  if (float_bits & kSignMask) {
+    // If `f` is -0.0 print -0.0.
+    ss << '-';
+    // Strip sign bit.
+    float_bits = float_bits & (~kSignMask);
+  }
+
+  switch (std::fpclassify(f)) {
+    case FP_ZERO:
+    case FP_NORMAL:
+      std::memcpy(&f, &float_bits, sizeof(float_bits));
+      ss << FloatToString(f);
+      break;
+
+    default: {
+      // Infinity, NaN, and Subnormal
+      // TODO(dneto): It's unclear how Infinity and NaN should be handled.
+      // See https://github.com/gpuweb/gpuweb/issues/1769
+
+      // std::hexfloat prints 'nan' and 'inf' instead of an
+      // explicit representation like we want. Split it out
+      // manually.
+      const int kExponentBias = 127;
+      const int kExponentMask = 0x7f800000;
+      const int kMantissaMask = 0x007fffff;
+      const int kMantissaBits = 23;
+
+      int mantissaNibbles = (kMantissaBits + 3) / 4;
+
+      const int biased_exponent =
+          static_cast<int>((float_bits & kExponentMask) >> kMantissaBits);
+      int exponent = biased_exponent - kExponentBias;
+      uint32_t mantissa = float_bits & kMantissaMask;
+
+      ss << "0x";
+
+      if (exponent == 128) {
+        if (mantissa == 0) {
+          //  Infinity case.
+          ss << "1p+128";
+        } else {
+          //  NaN case.
+          //  Emit the mantissa bits as if they are left-justified after the
+          //  binary point.  This is what SPIRV-Tools hex float emitter does,
+          //  and it's a justifiable choice independent of the bit width
+          //  of the mantissa.
+          mantissa <<= (4 - (kMantissaBits % 4));
+          // Remove trailing zeroes, for tidyness.
+          while (0 == (0xf & mantissa)) {
+            mantissa >>= 4;
+            mantissaNibbles--;
+          }
+          ss << "1." << std::hex << std::setfill('0')
+             << std::setw(mantissaNibbles) << mantissa << "p+128";
+        }
+      } else {
+        // Subnormal, and not zero.
+        TINT_ASSERT(Writer, mantissa != 0);
+        const int kTopBit = (1 << kMantissaBits);
+
+        // Shift left until we get 1.x
+        while (0 == (kTopBit & mantissa)) {
+          mantissa <<= 1;
+          exponent--;
+        }
+        // Emit the leading 1, and remove it from the mantissa.
+        ss << "1";
+        mantissa = mantissa ^ kTopBit;
+        mantissa <<= 1;
+        exponent++;
+
+        // Emit the fractional part.
+        if (mantissa) {
+          // Remove trailing zeroes, for tidyness
+          while (0 == (0xf & mantissa)) {
+            mantissa >>= 4;
+            mantissaNibbles--;
+          }
+          ss << "." << std::hex << std::setfill('0')
+             << std::setw(mantissaNibbles) << mantissa;
+        }
+        // Emit the exponent
+        ss << "p" << std::showpos << std::dec << exponent;
+      }
+    }
+  }
+  return ss.str();
+}
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/float_to_string.h b/src/tint/writer/float_to_string.h
new file mode 100644
index 0000000..44909d4
--- /dev/null
+++ b/src/tint/writer/float_to_string.h
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_FLOAT_TO_STRING_H_
+#define SRC_TINT_WRITER_FLOAT_TO_STRING_H_
+
+#include <string>
+
+namespace tint {
+namespace writer {
+
+/// Converts the float `f` to a string using fixed-point notation (not
+/// scientific). The float will be printed with the full precision required to
+/// describe the float. All trailing `0`s will be omitted after the last
+/// non-zero fractional number, unless the fractional is zero, in which case the
+/// number will end with `.0`.
+/// @return the float f formatted to a string
+std::string FloatToString(float f);
+
+/// Converts the float `f` to a string, using hex float notation for infinities,
+/// NaNs, or subnormal numbers. Otherwise behaves as FloatToString.
+/// @return the float f formatted to a string
+std::string FloatToBitPreservingString(float f);
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_FLOAT_TO_STRING_H_
diff --git a/src/tint/writer/float_to_string_test.cc b/src/tint/writer/float_to_string_test.cc
new file mode 100644
index 0000000..76806cb
--- /dev/null
+++ b/src/tint/writer/float_to_string_test.cc
@@ -0,0 +1,226 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/float_to_string.h"
+
+#include <cmath>
+#include <cstring>
+#include <limits>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace {
+
+// Makes an IEEE 754 binary32 floating point number with
+// - 0 sign if sign is 0, 1 otherwise
+// - 'exponent_bits' is placed in the exponent space.
+//   So, the exponent bias must already be included.
+float MakeFloat(int sign, int biased_exponent, int mantissa) {
+  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
+  // The binary32 exponent is 8 bits, just below the sign.
+  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
+  // The mantissa is the bottom 23 bits.
+  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
+
+  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
+  float result = 0.0f;
+  static_assert(sizeof(result) == sizeof(bits),
+                "expected float and uint32_t to be the same size");
+  std::memcpy(&result, &bits, sizeof(bits));
+  return result;
+}
+
+TEST(FloatToStringTest, Zero) {
+  EXPECT_EQ(FloatToString(0.0f), "0.0");
+}
+
+TEST(FloatToStringTest, One) {
+  EXPECT_EQ(FloatToString(1.0f), "1.0");
+}
+
+TEST(FloatToStringTest, MinusOne) {
+  EXPECT_EQ(FloatToString(-1.0f), "-1.0");
+}
+
+TEST(FloatToStringTest, Billion) {
+  EXPECT_EQ(FloatToString(1e9f), "1000000000.0");
+}
+
+TEST(FloatToStringTest, Small) {
+  EXPECT_NE(FloatToString(std::numeric_limits<float>::epsilon()), "0.0");
+}
+
+TEST(FloatToStringTest, Highest) {
+  const auto highest = std::numeric_limits<float>::max();
+  const auto expected_highest = 340282346638528859811704183484516925440.0f;
+  if (highest < expected_highest || highest > expected_highest) {
+    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+                    "this target";
+  }
+  EXPECT_EQ(FloatToString(std::numeric_limits<float>::max()),
+            "340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToStringTest, Lowest) {
+  // Some compilers complain if you test floating point numbers for equality.
+  // So say it via two inequalities.
+  const auto lowest = std::numeric_limits<float>::lowest();
+  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+  if (lowest < expected_lowest || lowest > expected_lowest) {
+    GTEST_SKIP()
+        << "std::numeric_limits<float>::lowest() is not as expected for "
+           "this target";
+  }
+  EXPECT_EQ(FloatToString(std::numeric_limits<float>::lowest()),
+            "-340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToStringTest, Precision) {
+  EXPECT_EQ(FloatToString(1e-8f), "0.00000001");
+  EXPECT_EQ(FloatToString(1e-9f), "0.000000001");
+  EXPECT_EQ(FloatToString(1e-10f), "1.00000001e-10");
+  EXPECT_EQ(FloatToString(1e-20f), "9.99999968e-21");
+}
+
+// FloatToBitPreservingString
+//
+// First replicate the tests for FloatToString
+
+TEST(FloatToBitPreservingStringTest, Zero) {
+  EXPECT_EQ(FloatToBitPreservingString(0.0f), "0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, One) {
+  EXPECT_EQ(FloatToBitPreservingString(1.0f), "1.0");
+}
+
+TEST(FloatToBitPreservingStringTest, MinusOne) {
+  EXPECT_EQ(FloatToBitPreservingString(-1.0f), "-1.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Billion) {
+  EXPECT_EQ(FloatToBitPreservingString(1e9f), "1000000000.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Small) {
+  EXPECT_NE(FloatToBitPreservingString(std::numeric_limits<float>::epsilon()),
+            "0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Highest) {
+  const auto highest = std::numeric_limits<float>::max();
+  const auto expected_highest = 340282346638528859811704183484516925440.0f;
+  if (highest < expected_highest || highest > expected_highest) {
+    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+                    "this target";
+  }
+  EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::max()),
+            "340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Lowest) {
+  // Some compilers complain if you test floating point numbers for equality.
+  // So say it via two inequalities.
+  const auto lowest = std::numeric_limits<float>::lowest();
+  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+  if (lowest < expected_lowest || lowest > expected_lowest) {
+    GTEST_SKIP()
+        << "std::numeric_limits<float>::lowest() is not as expected for "
+           "this target";
+  }
+  EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::lowest()),
+            "-340282346638528859811704183484516925440.0");
+}
+
+// Special cases for bit-preserving output.
+
+TEST(FloatToBitPreservingStringTest, NegativeZero) {
+  EXPECT_EQ(FloatToBitPreservingString(std::copysign(0.0f, -5.0f)), "-0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, ZeroAsBits) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0)), "0.0");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0)), "-0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, OneBits) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 127, 0)), "1.0");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 127, 0)), "-1.0");
+}
+
+TEST(FloatToBitPreservingStringTest, SmallestDenormal) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 1)), "0x1p-149");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 1)), "-0x1p-149");
+}
+
+TEST(FloatToBitPreservingStringTest, BiggerDenormal) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 2)), "0x1p-148");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 2)), "-0x1p-148");
+}
+
+TEST(FloatToBitPreservingStringTest, LargestDenormal) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0x7fffff)),
+            "0x1.fffffcp-127");
+}
+
+TEST(FloatToBitPreservingStringTest, Subnormal_cafebe) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0xcafebe)),
+            "0x1.2bfaf8p-127");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0xcafebe)),
+            "-0x1.2bfaf8p-127");
+}
+
+TEST(FloatToBitPreservingStringTest, Subnormal_aaaaa) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0xaaaaa)),
+            "0x1.55554p-130");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0xaaaaa)),
+            "-0x1.55554p-130");
+}
+
+TEST(FloatToBitPreservingStringTest, Infinity) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0)), "0x1p+128");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0)), "-0x1p+128");
+}
+
+// TODO(dneto): It's unclear how Infinity and NaN should be handled.
+// https://github.com/gpuweb/gpuweb/issues/1769
+// Windows x86-64 sets the high mantissa bit on NaNs.
+// Disable NaN tests for now.
+
+TEST(FloatToBitPreservingStringTest, DISABLED_NaN_MsbOnly) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x400000)),
+            "0x1.8p+128");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x400000)),
+            "-0x1.8p+128");
+}
+
+TEST(FloatToBitPreservingStringTest, DISABLED_NaN_LsbOnly) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x1)),
+            "0x1.000002p+128");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x1)),
+            "-0x1.000002p+128");
+}
+
+TEST(FloatToBitPreservingStringTest, DISABLED_NaN_NonMsb) {
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x20101f)),
+            "0x1.40203ep+128");
+  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x20101f)),
+            "-0x1.40203ep+128");
+}
+
+}  // namespace
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator.cc b/src/tint/writer/glsl/generator.cc
new file mode 100644
index 0000000..ab7079f
--- /dev/null
+++ b/src/tint/writer/glsl/generator.cc
@@ -0,0 +1,74 @@
+// 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/writer/glsl/generator.h"
+
+#include "src/tint/transform/binding_remapper.h"
+#include "src/tint/transform/combine_samplers.h"
+#include "src/tint/transform/glsl.h"
+#include "src/tint/writer/glsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+
+Options::Options() = default;
+Options::~Options() = default;
+Options::Options(const Options&) = default;
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program,
+                const Options& options,
+                const std::string& entry_point) {
+  Result result;
+
+  // Run the GLSL sanitizer.
+  transform::DataMap data;
+  data.Add<transform::BindingRemapper::Remappings>(options.binding_points,
+                                                   options.access_controls,
+                                                   options.allow_collisions);
+  data.Add<transform::CombineSamplers::BindingInfo>(
+      options.binding_map, options.placeholder_binding_point);
+  data.Add<transform::Glsl::Config>(entry_point);
+  transform::Glsl sanitizer;
+  auto output = sanitizer.Run(program, data);
+  if (!output.program.IsValid()) {
+    result.success = false;
+    result.error = output.program.Diagnostics().str();
+    return result;
+  }
+
+  // Generate the GLSL code.
+  auto impl = std::make_unique<GeneratorImpl>(&output.program, options.version);
+  result.success = impl->Generate();
+  result.error = impl->error();
+  result.glsl = impl->result();
+
+  // Collect the list of entry points in the sanitized program.
+  for (auto* func : output.program.AST().Functions()) {
+    if (func->IsEntryPoint()) {
+      auto name = output.program.Symbols().NameFor(func->symbol);
+      result.entry_points.push_back({name, func->PipelineStage()});
+    }
+  }
+
+  return result;
+}
+
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator.h b/src/tint/writer/glsl/generator.h
new file mode 100644
index 0000000..08b3e93
--- /dev/null
+++ b/src/tint/writer/glsl/generator.h
@@ -0,0 +1,116 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_GLSL_GENERATOR_H_
+#define SRC_TINT_WRITER_GLSL_GENERATOR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/sem/sampler_texture_pair.h"
+#include "src/tint/writer/glsl/version.h"
+#include "src/tint/writer/text.h"
+
+namespace tint {
+
+// Forward declarations
+class Program;
+
+namespace writer {
+namespace glsl {
+
+// Forward declarations
+class GeneratorImpl;
+
+using BindingMap = std::unordered_map<sem::SamplerTexturePair, std::string>;
+
+/// Configuration options used for generating GLSL.
+struct Options {
+  /// Constructor
+  Options();
+
+  /// Destructor
+  ~Options();
+
+  /// Copy constructor
+  Options(const Options&);
+
+  /// A map of SamplerTexturePair to combined sampler names for the
+  /// CombineSamplers transform
+  BindingMap binding_map;
+
+  /// The binding point to use for placeholder samplers.
+  sem::BindingPoint placeholder_binding_point;
+
+  /// A map of old binding point to new binding point for the BindingRemapper
+  /// transform
+  std::unordered_map<sem::BindingPoint, sem::BindingPoint> binding_points;
+
+  /// A map of old binding point to new access control for the BindingRemapper
+  /// transform
+  std::unordered_map<sem::BindingPoint, ast::Access> access_controls;
+
+  /// If true, then validation will be disabled for binding point collisions
+  /// generated by the BindingRemapper transform
+  bool allow_collisions = false;
+
+  /// The GLSL version to emit
+  Version version;
+};
+
+/// The result produced when generating GLSL.
+struct Result {
+  /// Constructor
+  Result();
+
+  /// Destructor
+  ~Result();
+
+  /// Copy constructor
+  Result(const Result&);
+
+  /// True if generation was successful.
+  bool success = false;
+
+  /// The errors generated during code generation, if any.
+  std::string error;
+
+  /// The generated GLSL.
+  std::string glsl = "";
+
+  /// The list of entry points in the generated GLSL.
+  std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
+};
+
+/// Generate GLSL for a program, according to a set of configuration options.
+/// The result will contain the GLSL, as well as success status and diagnostic
+/// information.
+/// @param program the program to translate to GLSL
+/// @param options the configuration options to use when generating GLSL
+/// @returns the resulting GLSL and supplementary information
+Result Generate(const Program* program,
+                const Options& options,
+                const std::string& entry_point);
+
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_GLSL_GENERATOR_H_
diff --git a/src/tint/writer/glsl/generator_bench.cc b/src/tint/writer/glsl/generator_bench.cc
new file mode 100644
index 0000000..ad87073
--- /dev/null
+++ b/src/tint/writer/glsl/generator_bench.cc
@@ -0,0 +1,50 @@
+// 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 <string>
+
+#include "src/tint/ast/module.h"
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::writer::glsl {
+namespace {
+
+void GenerateGLSL(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadProgram(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& program = std::get<bench::ProgramAndFile>(res).program;
+  std::vector<std::string> entry_points;
+  for (auto& fn : program.AST().Functions()) {
+    if (fn->IsEntryPoint()) {
+      entry_points.emplace_back(program.Symbols().NameFor(fn->symbol));
+    }
+  }
+
+  for (auto _ : state) {
+    for (auto& ep : entry_points) {
+      auto res = Generate(&program, {}, ep);
+      if (!res.error.empty()) {
+        state.SkipWithError(res.error.c_str());
+      }
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(GenerateGLSL);
+
+}  // namespace
+}  // namespace tint::writer::glsl
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
new file mode 100644
index 0000000..664a795
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -0,0 +1,2849 @@
+/// 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/writer/glsl/generator_impl.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iomanip>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/debug.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/glsl.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/append_vector.h"
+#include "src/tint/writer/float_to_string.h"
+
+namespace {
+
+bool IsRelational(tint::ast::BinaryOp op) {
+  return op == tint::ast::BinaryOp::kEqual ||
+         op == tint::ast::BinaryOp::kNotEqual ||
+         op == tint::ast::BinaryOp::kLessThan ||
+         op == tint::ast::BinaryOp::kGreaterThan ||
+         op == tint::ast::BinaryOp::kLessThanEqual ||
+         op == tint::ast::BinaryOp::kGreaterThanEqual;
+}
+
+bool RequiresOESSampleVariables(tint::ast::Builtin builtin) {
+  switch (builtin) {
+    case tint::ast::Builtin::kSampleIndex:
+    case tint::ast::Builtin::kSampleMask:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+const char kTempNamePrefix[] = "tint_tmp";
+const char kSpecConstantPrefix[] = "WGSL_SPEC_CONSTANT_";
+
+bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
+  return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
+}
+
+const char* convert_texel_format_to_glsl(const ast::TexelFormat format) {
+  switch (format) {
+    case ast::TexelFormat::kR32Uint:
+      return "r32ui";
+    case ast::TexelFormat::kR32Sint:
+      return "r32i";
+    case ast::TexelFormat::kR32Float:
+      return "r32f";
+    case ast::TexelFormat::kRgba8Unorm:
+      return "rgba8";
+    case ast::TexelFormat::kRgba8Snorm:
+      return "rgba8_snorm";
+    case ast::TexelFormat::kRgba8Uint:
+      return "rgba8ui";
+    case ast::TexelFormat::kRgba8Sint:
+      return "rgba8i";
+    case ast::TexelFormat::kRg32Uint:
+      return "rg32ui";
+    case ast::TexelFormat::kRg32Sint:
+      return "rg32i";
+    case ast::TexelFormat::kRg32Float:
+      return "rg32f";
+    case ast::TexelFormat::kRgba16Uint:
+      return "rgba16ui";
+    case ast::TexelFormat::kRgba16Sint:
+      return "rgba16i";
+    case ast::TexelFormat::kRgba16Float:
+      return "rgba16f";
+    case ast::TexelFormat::kRgba32Uint:
+      return "rgba32ui";
+    case ast::TexelFormat::kRgba32Sint:
+      return "rgba32i";
+    case ast::TexelFormat::kRgba32Float:
+      return "rgba32f";
+    case ast::TexelFormat::kNone:
+      return "unknown";
+  }
+  return "unknown";
+}
+
+}  // namespace
+
+GeneratorImpl::GeneratorImpl(const Program* program, const Version& version)
+    : TextGenerator(program), version_(version) {}
+
+GeneratorImpl::~GeneratorImpl() = default;
+
+bool GeneratorImpl::Generate() {
+  {
+    auto out = line();
+    out << "#version " << version_.major_version << version_.minor_version
+        << "0";
+    if (version_.IsES()) {
+      out << " es";
+    }
+  }
+
+  auto helpers_insertion_point = current_buffer_->lines.size();
+
+  line();
+
+  auto* mod = builder_.Sem().Module();
+  for (auto* decl : mod->DependencyOrderedDeclarations()) {
+    if (decl->Is<ast::Alias>()) {
+      continue;  // Ignore aliases.
+    }
+
+    if (auto* global = decl->As<ast::Variable>()) {
+      if (!EmitGlobalVariable(global)) {
+        return false;
+      }
+    } else if (auto* str = decl->As<ast::Struct>()) {
+      // Skip emission if the struct contains a runtime-sized array, since its
+      // only use will be as the store-type of a buffer and we emit those
+      // elsewhere.
+      // TODO(crbug.com/tint/1339): We could also avoid emitting any other
+      // struct that is only used as a buffer store type.
+      TINT_ASSERT(Writer, str->members.size() > 0);
+      auto* last_member = str->members[str->members.size() - 1];
+      auto* arr = last_member->type->As<ast::Array>();
+      if (!arr || !arr->IsRuntimeArray()) {
+        if (!EmitStructType(current_buffer_, builder_.Sem().Get(str))) {
+          return false;
+        }
+      }
+    } else if (auto* func = decl->As<ast::Function>()) {
+      if (func->IsEntryPoint()) {
+        if (!EmitEntryPointFunction(func)) {
+          return false;
+        }
+      } else {
+        if (!EmitFunction(func)) {
+          return false;
+        }
+      }
+    } else {
+      TINT_ICE(Writer, diagnostics_)
+          << "unhandled module-scope declaration: " << decl->TypeInfo().name;
+      return false;
+    }
+  }
+
+  TextBuffer extensions;
+
+  if (version_.IsES() && requires_oes_sample_variables_) {
+    extensions.Append("#extension GL_OES_sample_variables : require");
+  }
+
+  auto indent = current_buffer_->current_indent;
+
+  if (!extensions.lines.empty()) {
+    current_buffer_->Insert(extensions, helpers_insertion_point, indent);
+    helpers_insertion_point += extensions.lines.size();
+  }
+
+  if (version_.IsES() && requires_default_precision_qualifier_) {
+    current_buffer_->Insert("precision mediump float;",
+                            helpers_insertion_point++, indent);
+  }
+
+  if (!helpers_.lines.empty()) {
+    current_buffer_->Insert("", helpers_insertion_point++, indent);
+    current_buffer_->Insert(helpers_, helpers_insertion_point, indent);
+    helpers_insertion_point += helpers_.lines.size();
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitIndexAccessor(
+    std::ostream& out,
+    const ast::IndexAccessorExpression* expr) {
+  if (!EmitExpression(out, expr->object)) {
+    return false;
+  }
+  out << "[";
+
+  if (!EmitExpression(out, expr->index)) {
+    return false;
+  }
+  out << "]";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBitcast(std::ostream& out,
+                                const ast::BitcastExpression* expr) {
+  auto* src_type = TypeOf(expr->expr)->UnwrapRef();
+  auto* dst_type = TypeOf(expr)->UnwrapRef();
+
+  if (!dst_type->is_integer_scalar_or_vector() &&
+      !dst_type->is_float_scalar_or_vector()) {
+    diagnostics_.add_error(
+        diag::System::Writer,
+        "Unable to do bitcast to type " + dst_type->type_name());
+    return false;
+  }
+
+  if (src_type == dst_type) {
+    return EmitExpression(out, expr->expr);
+  }
+
+  if (src_type->is_float_scalar_or_vector() &&
+      dst_type->is_signed_scalar_or_vector()) {
+    out << "floatBitsToInt";
+  } else if (src_type->is_float_scalar_or_vector() &&
+             dst_type->is_unsigned_scalar_or_vector()) {
+    out << "floatBitsToUint";
+  } else if (src_type->is_signed_scalar_or_vector() &&
+             dst_type->is_float_scalar_or_vector()) {
+    out << "intBitsToFloat";
+  } else if (src_type->is_unsigned_scalar_or_vector() &&
+             dst_type->is_float_scalar_or_vector()) {
+    out << "uintBitsToFloat";
+  } else {
+    if (!EmitType(out, dst_type, ast::StorageClass::kNone,
+                  ast::Access::kReadWrite, "")) {
+      return false;
+    }
+  }
+  out << "(";
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
+  auto out = line();
+  if (!EmitExpression(out, stmt->lhs)) {
+    return false;
+  }
+  out << " = ";
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitVectorRelational(std::ostream& out,
+                                         const ast::BinaryExpression* expr) {
+  switch (expr->op) {
+    case ast::BinaryOp::kEqual:
+      out << "equal";
+      break;
+    case ast::BinaryOp::kNotEqual:
+      out << "notEqual";
+      break;
+    case ast::BinaryOp::kLessThan:
+      out << "lessThan";
+      break;
+    case ast::BinaryOp::kGreaterThan:
+      out << "greaterThan";
+      break;
+    case ast::BinaryOp::kLessThanEqual:
+      out << "lessThanEqual";
+      break;
+    case ast::BinaryOp::kGreaterThanEqual:
+      out << "greaterThanEqual";
+      break;
+    default:
+      break;
+  }
+  out << "(";
+  if (!EmitExpression(out, expr->lhs)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, expr->rhs)) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitBitwiseBoolOp(std::ostream& out,
+                                      const ast::BinaryExpression* expr) {
+  auto* bool_type = TypeOf(expr->lhs)->UnwrapRef();
+  auto* uint_type = BoolTypeToUint(bool_type);
+
+  // Cast result to bool scalar or vector type.
+  if (!EmitType(out, bool_type, ast::StorageClass::kNone,
+                ast::Access::kReadWrite, "")) {
+    return false;
+  }
+  ScopedParen outerCastParen(out);
+  // Cast LHS to uint scalar or vector type.
+  if (!EmitType(out, uint_type, ast::StorageClass::kNone,
+                ast::Access::kReadWrite, "")) {
+    return false;
+  }
+  {
+    ScopedParen innerCastParen(out);
+    // Emit LHS.
+    if (!EmitExpression(out, expr->lhs)) {
+      return false;
+    }
+  }
+  // Emit operator.
+  if (expr->op == ast::BinaryOp::kAnd) {
+    out << " & ";
+  } else if (expr->op == ast::BinaryOp::kOr) {
+    out << " | ";
+  } else {
+    TINT_ICE(Writer, diagnostics_)
+        << "unexpected binary op: " << FriendlyName(expr->op);
+    return false;
+  }
+  // Cast RHS to uint scalar or vector type.
+  if (!EmitType(out, uint_type, ast::StorageClass::kNone,
+                ast::Access::kReadWrite, "")) {
+    return false;
+  }
+  {
+    ScopedParen innerCastParen(out);
+    // Emit RHS.
+    if (!EmitExpression(out, expr->rhs)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitBinary(std::ostream& out,
+                               const ast::BinaryExpression* expr) {
+  if (IsRelational(expr->op) && !TypeOf(expr->lhs)->UnwrapRef()->is_scalar()) {
+    return EmitVectorRelational(out, expr);
+  }
+  if (expr->op == ast::BinaryOp::kLogicalAnd ||
+      expr->op == ast::BinaryOp::kLogicalOr) {
+    auto name = UniqueIdentifier(kTempNamePrefix);
+
+    {
+      auto pre = line();
+      pre << "bool " << name << " = ";
+      if (!EmitExpression(pre, expr->lhs)) {
+        return false;
+      }
+      pre << ";";
+    }
+
+    if (expr->op == ast::BinaryOp::kLogicalOr) {
+      line() << "if (!" << name << ") {";
+    } else {
+      line() << "if (" << name << ") {";
+    }
+
+    {
+      ScopedIndent si(this);
+      auto pre = line();
+      pre << name << " = ";
+      if (!EmitExpression(pre, expr->rhs)) {
+        return false;
+      }
+      pre << ";";
+    }
+
+    line() << "}";
+
+    out << "(" << name << ")";
+    return true;
+  }
+  if ((expr->op == ast::BinaryOp::kAnd || expr->op == ast::BinaryOp::kOr) &&
+      TypeOf(expr->lhs)->UnwrapRef()->is_bool_scalar_or_vector()) {
+    return EmitBitwiseBoolOp(out, expr);
+  }
+
+  out << "(";
+  if (!EmitExpression(out, expr->lhs)) {
+    return false;
+  }
+  out << " ";
+
+  switch (expr->op) {
+    case ast::BinaryOp::kAnd:
+      out << "&";
+      break;
+    case ast::BinaryOp::kOr:
+      out << "|";
+      break;
+    case ast::BinaryOp::kXor:
+      out << "^";
+      break;
+    case ast::BinaryOp::kLogicalAnd:
+    case ast::BinaryOp::kLogicalOr: {
+      // These are both handled above.
+      TINT_UNREACHABLE(Writer, diagnostics_);
+      return false;
+    }
+    case ast::BinaryOp::kEqual:
+      out << "==";
+      break;
+    case ast::BinaryOp::kNotEqual:
+      out << "!=";
+      break;
+    case ast::BinaryOp::kLessThan:
+      out << "<";
+      break;
+    case ast::BinaryOp::kGreaterThan:
+      out << ">";
+      break;
+    case ast::BinaryOp::kLessThanEqual:
+      out << "<=";
+      break;
+    case ast::BinaryOp::kGreaterThanEqual:
+      out << ">=";
+      break;
+    case ast::BinaryOp::kShiftLeft:
+      out << "<<";
+      break;
+    case ast::BinaryOp::kShiftRight:
+      // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
+      // implementation-defined behaviour for negative LHS.  We may have to
+      // generate extra code to implement WGSL-specified behaviour for negative
+      // LHS.
+      out << R"(>>)";
+      break;
+
+    case ast::BinaryOp::kAdd:
+      out << "+";
+      break;
+    case ast::BinaryOp::kSubtract:
+      out << "-";
+      break;
+    case ast::BinaryOp::kMultiply:
+      out << "*";
+      break;
+    case ast::BinaryOp::kDivide:
+      out << "/";
+      break;
+    case ast::BinaryOp::kModulo:
+      out << "%";
+      break;
+    case ast::BinaryOp::kNone:
+      diagnostics_.add_error(diag::System::Writer,
+                             "missing binary operation type");
+      return false;
+  }
+  out << " ";
+
+  if (!EmitExpression(out, expr->rhs)) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+  for (auto* s : stmts) {
+    if (!EmitStatement(s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+  ScopedIndent si(this);
+  return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+  line() << "{";
+  if (!EmitStatementsWithIndent(stmt->statements)) {
+    return false;
+  }
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
+  line() << "break;";
+  return true;
+}
+
+bool GeneratorImpl::EmitCall(std::ostream& out,
+                             const ast::CallExpression* expr) {
+  auto* call = builder_.Sem().Get(expr);
+  auto* target = call->Target();
+
+  if (target->Is<sem::Function>()) {
+    return EmitFunctionCall(out, call);
+  }
+  if (auto* builtin = target->As<sem::Builtin>()) {
+    return EmitBuiltinCall(out, call, builtin);
+  }
+  if (auto* cast = target->As<sem::TypeConversion>()) {
+    return EmitTypeConversion(out, call, cast);
+  }
+  if (auto* ctor = target->As<sem::TypeConstructor>()) {
+    return EmitTypeConstructor(out, call, ctor);
+  }
+  TINT_ICE(Writer, diagnostics_)
+      << "unhandled call target: " << target->TypeInfo().name;
+  return false;
+}
+
+bool GeneratorImpl::EmitFunctionCall(std::ostream& out, const sem::Call* call) {
+  const auto& args = call->Arguments();
+  auto* decl = call->Declaration();
+  auto* ident = decl->target.name;
+
+  auto name = builder_.Symbols().NameFor(ident->symbol);
+  auto caller_sym = ident->symbol;
+
+  out << name << "(";
+
+  bool first = true;
+  for (auto* arg : args) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  auto* expr = call->Declaration();
+  if (builtin->IsTexture()) {
+    return EmitTextureCall(out, call, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kSelect) {
+    return EmitSelectCall(out, expr);
+  }
+  if (builtin->Type() == sem::BuiltinType::kDot) {
+    return EmitDotCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kModf) {
+    return EmitModfCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kFrexp) {
+    return EmitFrexpCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kIsNormal) {
+    return EmitIsNormalCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kDegrees) {
+    return EmitDegreesCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kRadians) {
+    return EmitRadiansCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+    return EmitArrayLength(out, expr);
+  }
+  if (builtin->IsDataPacking()) {
+    return EmitDataPackingCall(out, expr, builtin);
+  }
+  if (builtin->IsDataUnpacking()) {
+    return EmitDataUnpackingCall(out, expr, builtin);
+  }
+  if (builtin->IsBarrier()) {
+    return EmitBarrierCall(out, builtin);
+  }
+  if (builtin->IsAtomic()) {
+    return EmitWorkgroupAtomicCall(out, expr, builtin);
+  }
+  auto name = generate_builtin_name(builtin);
+  if (name.empty()) {
+    return false;
+  }
+
+  out << name << "(";
+
+  bool first = true;
+  for (auto* arg : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
+                                       const sem::Call* call,
+                                       const sem::TypeConversion* conv) {
+  if (!EmitType(out, conv->Target(), ast::StorageClass::kNone,
+                ast::Access::kReadWrite, "")) {
+    return false;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
+                                        const sem::Call* call,
+                                        const sem::TypeConstructor* ctor) {
+  auto* type = ctor->ReturnType();
+
+  // If the type constructor is empty then we need to construct with the zero
+  // value for all components.
+  if (call->Arguments().empty()) {
+    return EmitZeroValue(out, type);
+  }
+
+  auto it = structure_builders_.find(As<sem::Struct>(type));
+  if (it != structure_builders_.end()) {
+    out << it->second << "(";
+  } else {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
+                  "")) {
+      return false;
+    }
+    out << "(";
+  }
+
+  bool first = true;
+  for (auto* arg : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
+                                            const ast::CallExpression* expr,
+                                            const sem::Builtin* builtin) {
+  auto call = [&](const char* name) {
+    out << name;
+    {
+      ScopedParen sp(out);
+      for (size_t i = 0; i < expr->args.size(); i++) {
+        auto* arg = expr->args[i];
+        if (i > 0) {
+          out << ", ";
+        }
+        if (!EmitExpression(out, arg)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAtomicLoad: {
+      // GLSL does not have an atomicLoad, so we emulate it with
+      // atomicOr using 0 as the OR value
+      out << "atomicOr";
+      {
+        ScopedParen sp(out);
+        if (!EmitExpression(out, expr->args[0])) {
+          return false;
+        }
+        out << ", 0";
+        if (builtin->ReturnType()->Is<sem::U32>()) {
+          out << "u";
+        }
+      }
+      return true;
+    }
+    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+      return CallBuiltinHelper(
+          out, expr, builtin,
+          [&](TextBuffer* b, const std::vector<std::string>& params) {
+            {
+              auto pre = line(b);
+              if (!EmitTypeAndName(pre, builtin->ReturnType(),
+                                   ast::StorageClass::kNone,
+                                   ast::Access::kUndefined, "result")) {
+                return false;
+              }
+              pre << ";";
+            }
+            {
+              auto pre = line(b);
+              pre << "result.x = atomicCompSwap";
+              {
+                ScopedParen sp(pre);
+                pre << params[0];
+                pre << ", " << params[1];
+                pre << ", " << params[2];
+              }
+              pre << ";";
+            }
+            {
+              auto pre = line(b);
+              pre << "result.y = result.x == " << params[2] << " ? ";
+              if (TypeOf(expr->args[2])->Is<sem::U32>()) {
+                pre << "1u : 0u;";
+              } else {
+                pre << "1 : 0;";
+              }
+            }
+            line(b) << "return result;";
+            return true;
+          });
+    }
+
+    case sem::BuiltinType::kAtomicAdd:
+    case sem::BuiltinType::kAtomicSub:
+      return call("atomicAdd");
+
+    case sem::BuiltinType::kAtomicMax:
+      return call("atomicMax");
+
+    case sem::BuiltinType::kAtomicMin:
+      return call("atomicMin");
+
+    case sem::BuiltinType::kAtomicAnd:
+      return call("atomicAnd");
+
+    case sem::BuiltinType::kAtomicOr:
+      return call("atomicOr");
+
+    case sem::BuiltinType::kAtomicXor:
+      return call("atomicXor");
+
+    case sem::BuiltinType::kAtomicExchange:
+    case sem::BuiltinType::kAtomicStore:
+      // GLSL does not have an atomicStore, so we emulate it with
+      // atomicExchange.
+      return call("atomicExchange");
+
+    default:
+      break;
+  }
+
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported atomic builtin: " << builtin->Type();
+  return false;
+}
+
+bool GeneratorImpl::EmitArrayLength(std::ostream& out,
+                                    const ast::CallExpression* expr) {
+  out << "uint(";
+  if (!EmitExpression(out, expr->args[0])) {
+    return false;
+  }
+  out << ".length())";
+  return true;
+}
+
+bool GeneratorImpl::EmitSelectCall(std::ostream& out,
+                                   const ast::CallExpression* expr) {
+  auto* expr_false = expr->args[0];
+  auto* expr_true = expr->args[1];
+  auto* expr_cond = expr->args[2];
+  // GLSL does not support ternary expressions with a bool vector conditional,
+  // but it does support mix() with same.
+  if (TypeOf(expr_cond)->UnwrapRef()->is_bool_vector()) {
+    out << "mix(";
+    if (!EmitExpression(out, expr_false)) {
+      return false;
+    }
+    out << ", ";
+    if (!EmitExpression(out, expr_true)) {
+      return false;
+    }
+    out << ", ";
+    if (!EmitExpression(out, expr_cond)) {
+      return false;
+    }
+    out << ")";
+    return true;
+  }
+  ScopedParen paren(out);
+  if (!EmitExpression(out, expr_cond)) {
+    return false;
+  }
+
+  out << " ? ";
+
+  if (!EmitExpression(out, expr_true)) {
+    return false;
+  }
+
+  out << " : ";
+
+  if (!EmitExpression(out, expr_false)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDotCall(std::ostream& out,
+                                const ast::CallExpression* expr,
+                                const sem::Builtin* builtin) {
+  auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
+  std::string fn = "dot";
+  if (vec_ty->type()->is_integer_scalar()) {
+    // GLSL does not have a builtin for dot() with integer vector types.
+    // Generate the helper function if it hasn't been created already
+    fn = utils::GetOrCreate(int_dot_funcs_, vec_ty, [&]() -> std::string {
+      TextBuffer b;
+      TINT_DEFER(helpers_.Append(b));
+
+      auto fn_name = UniqueIdentifier("tint_int_dot");
+
+      std::string v;
+      {
+        std::stringstream s;
+        if (!EmitType(s, vec_ty->type(), ast::StorageClass::kNone,
+                      ast::Access::kRead, "")) {
+          return "";
+        }
+        v = s.str();
+      }
+      {  // (u)int tint_int_dot([i|u]vecN a, [i|u]vecN b) {
+        auto l = line(&b);
+        if (!EmitType(l, vec_ty->type(), ast::StorageClass::kNone,
+                      ast::Access::kRead, "")) {
+          return "";
+        }
+        l << " " << fn_name << "(";
+        if (!EmitType(l, vec_ty, ast::StorageClass::kNone, ast::Access::kRead,
+                      "")) {
+          return "";
+        }
+        l << " a, ";
+        if (!EmitType(l, vec_ty, ast::StorageClass::kNone, ast::Access::kRead,
+                      "")) {
+          return "";
+        }
+        l << " b) {";
+      }
+      {
+        auto l = line(&b);
+        l << "  return ";
+        for (uint32_t i = 0; i < vec_ty->Width(); i++) {
+          if (i > 0) {
+            l << " + ";
+          }
+          l << "a[" << i << "]*b[" << i << "]";
+        }
+        l << ";";
+      }
+      line(&b) << "}";
+      return fn_name;
+    });
+    if (fn.empty()) {
+      return false;
+    }
+  }
+
+  out << fn << "(";
+  if (!EmitExpression(out, expr->args[0])) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, expr->args[1])) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitModfCall(std::ostream& out,
+                                 const ast::CallExpression* expr,
+                                 const sem::Builtin* builtin) {
+  if (expr->args.size() == 1) {
+    return CallBuiltinHelper(
+        out, expr, builtin,
+        [&](TextBuffer* b, const std::vector<std::string>& params) {
+          // Emit the builtin return type unique to this overload. This does not
+          // exist in the AST, so it will not be generated in Generate().
+          if (!EmitStructType(&helpers_,
+                              builtin->ReturnType()->As<sem::Struct>())) {
+            return false;
+          }
+
+          {
+            auto l = line(b);
+            if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
+                          ast::Access::kUndefined, "")) {
+              return false;
+            }
+            l << " result;";
+          }
+          line(b) << "result.fract = modf(" << params[0] << ", result.whole);";
+          line(b) << "return result;";
+          return true;
+        });
+  }
+
+  // DEPRECATED
+  out << "modf";
+  ScopedParen sp(out);
+  if (!EmitExpression(out, expr->args[0])) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, expr->args[1])) {
+    return false;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
+                                  const ast::CallExpression* expr,
+                                  const sem::Builtin* builtin) {
+  if (expr->args.size() == 1) {
+    return CallBuiltinHelper(
+        out, expr, builtin,
+        [&](TextBuffer* b, const std::vector<std::string>& params) {
+          // Emit the builtin return type unique to this overload. This does not
+          // exist in the AST, so it will not be generated in Generate().
+          if (!EmitStructType(&helpers_,
+                              builtin->ReturnType()->As<sem::Struct>())) {
+            return false;
+          }
+
+          {
+            auto l = line(b);
+            if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
+                          ast::Access::kUndefined, "")) {
+              return false;
+            }
+            l << " result;";
+          }
+          line(b) << "result.sig = frexp(" << params[0] << ", result.exp);";
+          line(b) << "return result;";
+          return true;
+        });
+  }
+  // DEPRECATED
+  // Exponent is an integer in WGSL, but HLSL wants a float.
+  // We need to make the call with a temporary float, and then cast.
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* significand_ty = builtin->Parameters()[0]->Type();
+        auto significand = params[0];
+        auto* exponent_ty = builtin->Parameters()[1]->Type();
+        auto exponent = params[1];
+
+        std::string width;
+        if (auto* vec = significand_ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        // Exponent is an integer, which HLSL does not have an overload for.
+        // We need to cast from a float.
+        line(b) << "float" << width << " float_exp;";
+        line(b) << "float" << width << " significand = frexp(" << significand
+                << ", float_exp);";
+        {
+          auto l = line(b);
+          l << exponent << " = ";
+          if (!EmitType(l, exponent_ty->UnwrapPtr(), ast::StorageClass::kNone,
+                        ast::Access::kUndefined, "")) {
+            return false;
+          }
+          l << "(float_exp);";
+        }
+        line(b) << "return significand;";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitIsNormalCall(std::ostream& out,
+                                     const ast::CallExpression* expr,
+                                     const sem::Builtin* builtin) {
+  // GLSL doesn't have a isNormal builtin, we need to emulate
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* input_ty = builtin->Parameters()[0]->Type();
+
+        std::string vec_type;
+        if (auto* vec = input_ty->As<sem::Vector>()) {
+          vec_type = "uvec" + std::to_string(vec->Width());
+        } else {
+          vec_type = "uint";
+        }
+
+        constexpr auto* kExponentMask = "0x7f80000u";
+        constexpr auto* kMinNormalExponent = "0x0080000u";
+        constexpr auto* kMaxNormalExponent = "0x7f00000u";
+
+        line(b) << vec_type << " exponent = floatBitsToUint(" << params[0]
+                << ") & " << kExponentMask << ";";
+        line(b) << vec_type << " clamped = "
+                << "clamp(exponent, " << kMinNormalExponent << ", "
+                << kMaxNormalExponent << ");";
+        if (input_ty->Is<sem::Vector>()) {
+          line(b) << "return equal(clamped, exponent);";
+        } else {
+          line(b) << "return clamped == exponent;";
+        }
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kRadToDeg << ";";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kDegToRad << ";";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDataPackingCall(std::ostream& out,
+                                        const ast::CallExpression* expr,
+                                        const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        uint32_t dims = 2;
+        bool is_signed = false;
+        uint32_t scale = 65535;
+        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kPack4x8unorm) {
+          dims = 4;
+          scale = 255;
+        }
+        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kPack2x16snorm) {
+          is_signed = true;
+          scale = (scale - 1) / 2;
+        }
+        switch (builtin->Type()) {
+          case sem::BuiltinType::kPack4x8snorm:
+          case sem::BuiltinType::kPack4x8unorm:
+          case sem::BuiltinType::kPack2x16snorm:
+          case sem::BuiltinType::kPack2x16unorm: {
+            {
+              auto l = line(b);
+              l << (is_signed ? "" : "u") << "int" << dims
+                << " i = " << (is_signed ? "" : "u") << "int" << dims
+                << "(round(clamp(" << params[0] << ", "
+                << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale
+                << ".0))";
+              if (is_signed) {
+                l << " & " << (dims == 4 ? "0xff" : "0xffff");
+              }
+              l << ";";
+            }
+            {
+              auto l = line(b);
+              l << "return ";
+              if (is_signed) {
+                l << "asuint";
+              }
+              l << "(i.x | i.y << " << (32 / dims);
+              if (dims == 4) {
+                l << " | i.z << 16 | i.w << 24";
+              }
+              l << ");";
+            }
+            break;
+          }
+          case sem::BuiltinType::kPack2x16float: {
+            line(b) << "uint2 i = f32tof16(" << params[0] << ");";
+            line(b) << "return i.x | (i.y << 16);";
+            break;
+          }
+          default:
+            diagnostics_.add_error(
+                diag::System::Writer,
+                "Internal error: unhandled data packing builtin");
+            return false;
+        }
+
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out,
+                                          const ast::CallExpression* expr,
+                                          const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        uint32_t dims = 2;
+        bool is_signed = false;
+        uint32_t scale = 65535;
+        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kUnpack4x8unorm) {
+          dims = 4;
+          scale = 255;
+        }
+        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kUnpack2x16snorm) {
+          is_signed = true;
+          scale = (scale - 1) / 2;
+        }
+        switch (builtin->Type()) {
+          case sem::BuiltinType::kUnpack4x8snorm:
+          case sem::BuiltinType::kUnpack2x16snorm: {
+            line(b) << "int j = int(" << params[0] << ");";
+            {  // Perform sign extension on the converted values.
+              auto l = line(b);
+              l << "int" << dims << " i = int" << dims << "(";
+              if (dims == 2) {
+                l << "j << 16, j) >> 16";
+              } else {
+                l << "j << 24, j << 16, j << 8, j) >> 24";
+              }
+              l << ";";
+            }
+            line(b) << "return clamp(float" << dims << "(i) / " << scale
+                    << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
+            break;
+          }
+          case sem::BuiltinType::kUnpack4x8unorm:
+          case sem::BuiltinType::kUnpack2x16unorm: {
+            line(b) << "uint j = " << params[0] << ";";
+            {
+              auto l = line(b);
+              l << "uint" << dims << " i = uint" << dims << "(";
+              l << "j & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
+              if (dims == 4) {
+                l << "(j >> " << (32 / dims)
+                  << ") & 0xff, (j >> 16) & 0xff, j >> 24";
+              } else {
+                l << "j >> " << (32 / dims);
+              }
+              l << ");";
+            }
+            line(b) << "return float" << dims << "(i) / " << scale << ".0;";
+            break;
+          }
+          case sem::BuiltinType::kUnpack2x16float:
+            line(b) << "uint i = " << params[0] << ";";
+            line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
+            break;
+          default:
+            diagnostics_.add_error(
+                diag::System::Writer,
+                "Internal error: unhandled data packing builtin");
+            return false;
+        }
+
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitBarrierCall(std::ostream& out,
+                                    const sem::Builtin* builtin) {
+  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
+  // instruction.
+  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    out << "barrier()";
+  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    out << "{ barrier(); memoryBarrierBuffer(); }";
+  } else {
+    TINT_UNREACHABLE(Writer, diagnostics_)
+        << "unexpected barrier builtin type " << sem::str(builtin->Type());
+    return false;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitTextureCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  using Usage = sem::ParameterUsage;
+
+  auto& signature = builtin->Signature();
+  auto* expr = call->Declaration();
+  auto arguments = expr->args;
+
+  // Returns the argument with the given usage
+  auto arg = [&](Usage usage) {
+    int idx = signature.IndexOf(usage);
+    return (idx >= 0) ? arguments[idx] : nullptr;
+  };
+
+  auto* texture = arg(Usage::kTexture);
+  if (!texture) {
+    TINT_ICE(Writer, diagnostics_) << "missing texture argument";
+    return false;
+  }
+
+  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureDimensions: {
+      if (texture_type->Is<sem::StorageTexture>()) {
+        out << "imageSize(";
+      } else {
+        out << "textureSize(";
+      }
+      if (!EmitExpression(out, texture)) {
+        return false;
+      }
+
+      // The LOD parameter is mandatory on textureSize() for non-multisampled
+      // textures.
+      if (!texture_type->Is<sem::StorageTexture>() &&
+          !texture_type->Is<sem::MultisampledTexture>() &&
+          !texture_type->Is<sem::DepthMultisampledTexture>()) {
+        out << ", ";
+        if (auto* level_arg = arg(Usage::kLevel)) {
+          if (!EmitExpression(out, level_arg)) {
+            return false;
+          }
+        } else {
+          out << "0";
+        }
+      }
+      out << ")";
+      // textureSize() on sampler2dArray returns the array size in the
+      // final component, so strip it out.
+      if (texture_type->dim() == ast::TextureDimension::k2dArray) {
+        out << ".xy";
+      }
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumLayers: {
+      if (texture_type->Is<sem::StorageTexture>()) {
+        out << "imageSize(";
+      } else {
+        out << "textureSize(";
+      }
+      // textureSize() on sampler2dArray returns the array size in the
+      // final component, so return it
+      if (!EmitExpression(out, texture)) {
+        return false;
+      }
+      // The LOD parameter is mandatory on textureSize() for non-multisampled
+      // textures.
+      if (!texture_type->Is<sem::StorageTexture>() &&
+          !texture_type->Is<sem::MultisampledTexture>() &&
+          !texture_type->Is<sem::DepthMultisampledTexture>()) {
+        out << ", ";
+        if (auto* level_arg = arg(Usage::kLevel)) {
+          if (!EmitExpression(out, level_arg)) {
+            return false;
+          }
+        } else {
+          out << "0";
+        }
+      }
+      out << ").z";
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumLevels: {
+      out << "textureQueryLevels(";
+      if (!EmitExpression(out, texture)) {
+        return false;
+      }
+      out << ");";
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumSamples: {
+      out << "textureSamples(";
+      if (!EmitExpression(out, texture)) {
+        return false;
+      }
+      out << ");";
+      return true;
+    }
+    default:
+      break;
+  }
+
+  uint32_t glsl_ret_width = 4u;
+  bool is_depth = texture_type->Is<sem::DepthTexture>();
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureSample:
+    case sem::BuiltinType::kTextureSampleBias:
+      out << "texture";
+      if (is_depth) {
+        glsl_ret_width = 1u;
+      }
+      break;
+    case sem::BuiltinType::kTextureSampleLevel:
+      out << "textureLod";
+      break;
+    case sem::BuiltinType::kTextureGather:
+    case sem::BuiltinType::kTextureGatherCompare:
+      out << "textureGather";
+      break;
+    case sem::BuiltinType::kTextureSampleGrad:
+      out << "textureGrad";
+      break;
+    case sem::BuiltinType::kTextureSampleCompare:
+    case sem::BuiltinType::kTextureSampleCompareLevel:
+      out << "texture";
+      glsl_ret_width = 1;
+      break;
+    case sem::BuiltinType::kTextureLoad:
+      out << "texelFetch";
+      break;
+    case sem::BuiltinType::kTextureStore:
+      out << "imageStore";
+      break;
+    default:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Internal compiler error: Unhandled texture builtin '" +
+              std::string(builtin->str()) + "'");
+      return false;
+  }
+
+  if (builtin->Signature().IndexOf(sem::ParameterUsage::kOffset) >= 0) {
+    out << "Offset";
+  }
+
+  out << "(";
+
+  if (!EmitExpression(out, texture))
+    return false;
+
+  out << ", ";
+
+  auto* param_coords = arg(Usage::kCoords);
+  if (!param_coords) {
+    TINT_ICE(Writer, diagnostics_) << "missing coords argument";
+    return false;
+  }
+
+  if (auto* array_index = arg(Usage::kArrayIndex)) {
+    // Array index needs to be appended to the coordinates.
+    param_coords =
+        AppendVector(&builder_, param_coords, array_index)->Declaration();
+  }
+  bool is_cube_array = texture_type->dim() == ast::TextureDimension::kCubeArray;
+
+  // GLSL requires Dref to be appended to the coordinates, *unless* it's
+  // samplerCubeArrayShadow, in which case it will be handled as a separate
+  // parameter [1].
+  if (is_depth && !is_cube_array) {
+    if (auto* depth_ref = arg(Usage::kDepthRef)) {
+      param_coords =
+          AppendVector(&builder_, param_coords, depth_ref)->Declaration();
+    } else if (builtin->Type() == sem::BuiltinType::kTextureSample) {
+      // Sampling a depth texture in GLSL always requires a depth reference, so
+      // append zero here.
+      auto* f32 = builder_.create<sem::F32>();
+      auto* zero = builder_.Expr(0.0f);
+      auto* stmt = builder_.Sem().Get(param_coords)->Stmt();
+      auto* sem_zero = builder_.create<sem::Expression>(
+          zero, f32, stmt, sem::Constant{}, /* has_side_effects */ false);
+      builder_.Sem().Add(zero, sem_zero);
+      param_coords = AppendVector(&builder_, param_coords, zero)->Declaration();
+    }
+  }
+
+  if (!EmitExpression(out, param_coords)) {
+    return false;
+  }
+
+  for (auto usage : {Usage::kLevel, Usage::kDdx, Usage::kDdy,
+                     Usage::kSampleIndex, Usage::kValue}) {
+    if (auto* e = arg(usage)) {
+      out << ", ";
+      if (!EmitExpression(out, e)) {
+        return false;
+      }
+    }
+  }
+
+  // GLSL's textureGather always requires a refZ parameter.
+  if (is_depth && builtin->Type() == sem::BuiltinType::kTextureGather) {
+    out << ", 0.0";
+  }
+
+  for (auto usage : {Usage::kOffset, Usage::kComponent, Usage::kBias}) {
+    if (auto* e = arg(usage)) {
+      out << ", ";
+      if (!EmitExpression(out, e)) {
+        return false;
+      }
+    }
+  }
+
+  // [1] samplerCubeArrayShadow requires a separate depthRef parameter
+  if (is_depth && is_cube_array) {
+    if (auto* e = arg(Usage::kDepthRef)) {
+      out << ", ";
+      if (!EmitExpression(out, e)) {
+        return false;
+      }
+    } else if (builtin->Type() == sem::BuiltinType::kTextureSample) {
+      out << ", 0.0f";
+    }
+  }
+
+  out << ")";
+
+  if (builtin->ReturnType()->Is<sem::Void>()) {
+    return true;
+  }
+  // If the builtin return type does not match the number of elements of the
+  // GLSL builtin, we need to swizzle the expression to generate the correct
+  // number of components.
+  uint32_t wgsl_ret_width = 1;
+  if (auto* vec = builtin->ReturnType()->As<sem::Vector>()) {
+    wgsl_ret_width = vec->Width();
+  }
+  if (wgsl_ret_width < glsl_ret_width) {
+    out << ".";
+    for (uint32_t i = 0; i < wgsl_ret_width; i++) {
+      out << "xyz"[i];
+    }
+  }
+  if (wgsl_ret_width > glsl_ret_width) {
+    TINT_ICE(Writer, diagnostics_)
+        << "WGSL return width (" << wgsl_ret_width
+        << ") is wider than GLSL return width (" << glsl_ret_width << ") for "
+        << builtin->Type();
+    return false;
+  }
+
+  return true;
+}
+
+std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAbs:
+    case sem::BuiltinType::kAcos:
+    case sem::BuiltinType::kAll:
+    case sem::BuiltinType::kAny:
+    case sem::BuiltinType::kAsin:
+    case sem::BuiltinType::kAtan:
+    case sem::BuiltinType::kCeil:
+    case sem::BuiltinType::kClamp:
+    case sem::BuiltinType::kCos:
+    case sem::BuiltinType::kCosh:
+    case sem::BuiltinType::kCross:
+    case sem::BuiltinType::kDeterminant:
+    case sem::BuiltinType::kDistance:
+    case sem::BuiltinType::kDot:
+    case sem::BuiltinType::kExp:
+    case sem::BuiltinType::kExp2:
+    case sem::BuiltinType::kFloor:
+    case sem::BuiltinType::kFrexp:
+    case sem::BuiltinType::kLdexp:
+    case sem::BuiltinType::kLength:
+    case sem::BuiltinType::kLog:
+    case sem::BuiltinType::kLog2:
+    case sem::BuiltinType::kMax:
+    case sem::BuiltinType::kMin:
+    case sem::BuiltinType::kModf:
+    case sem::BuiltinType::kNormalize:
+    case sem::BuiltinType::kPow:
+    case sem::BuiltinType::kReflect:
+    case sem::BuiltinType::kRefract:
+    case sem::BuiltinType::kRound:
+    case sem::BuiltinType::kSign:
+    case sem::BuiltinType::kSin:
+    case sem::BuiltinType::kSinh:
+    case sem::BuiltinType::kSqrt:
+    case sem::BuiltinType::kStep:
+    case sem::BuiltinType::kTan:
+    case sem::BuiltinType::kTanh:
+    case sem::BuiltinType::kTranspose:
+    case sem::BuiltinType::kTrunc:
+      return builtin->str();
+    case sem::BuiltinType::kAtan2:
+      return "atan";
+    case sem::BuiltinType::kCountOneBits:
+      return "countbits";
+    case sem::BuiltinType::kDpdx:
+      return "ddx";
+    case sem::BuiltinType::kDpdxCoarse:
+      return "ddx_coarse";
+    case sem::BuiltinType::kDpdxFine:
+      return "ddx_fine";
+    case sem::BuiltinType::kDpdy:
+      return "ddy";
+    case sem::BuiltinType::kDpdyCoarse:
+      return "ddy_coarse";
+    case sem::BuiltinType::kDpdyFine:
+      return "ddy_fine";
+    case sem::BuiltinType::kFaceForward:
+      return "faceforward";
+    case sem::BuiltinType::kFract:
+      return "frac";
+    case sem::BuiltinType::kFma:
+      return "mad";
+    case sem::BuiltinType::kFwidth:
+    case sem::BuiltinType::kFwidthCoarse:
+    case sem::BuiltinType::kFwidthFine:
+      return "fwidth";
+    case sem::BuiltinType::kInverseSqrt:
+      return "rsqrt";
+    case sem::BuiltinType::kIsFinite:
+      return "isfinite";
+    case sem::BuiltinType::kIsInf:
+      return "isinf";
+    case sem::BuiltinType::kIsNan:
+      return "isnan";
+    case sem::BuiltinType::kMix:
+      return "mix";
+    case sem::BuiltinType::kReverseBits:
+      return "reversebits";
+    case sem::BuiltinType::kSmoothStep:
+      return "smoothstep";
+    default:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Unknown builtin method: " + std::string(builtin->str()));
+  }
+
+  return "";
+}
+
+bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
+  if (stmt->IsDefault()) {
+    line() << "default: {";
+  } else {
+    for (auto* selector : stmt->selectors) {
+      auto out = line();
+      out << "case ";
+      if (!EmitLiteral(out, selector)) {
+        return false;
+      }
+      out << ":";
+      if (selector == stmt->selectors.back()) {
+        out << " {";
+      }
+    }
+  }
+
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+    if (!last_is_break_or_fallthrough(stmt->body)) {
+      line() << "break;";
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
+  if (!emit_continuing_()) {
+    return false;
+  }
+  line() << "continue;";
+  return true;
+}
+
+bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
+  // TODO(dsinclair): Verify this is correct when the discard semantics are
+  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
+  line() << "discard;";
+  return true;
+}
+
+bool GeneratorImpl::EmitExpression(std::ostream& out,
+                                   const ast::Expression* expr) {
+  if (auto* a = expr->As<ast::IndexAccessorExpression>()) {
+    return EmitIndexAccessor(out, a);
+  }
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return EmitBinary(out, b);
+  }
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return EmitBitcast(out, b);
+  }
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return EmitCall(out, c);
+  }
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return EmitIdentifier(out, i);
+  }
+  if (auto* l = expr->As<ast::LiteralExpression>()) {
+    return EmitLiteral(out, l);
+  }
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return EmitMemberAccessor(out, m);
+  }
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return EmitUnaryOp(out, u);
+  }
+
+  diagnostics_.add_error(
+      diag::System::Writer,
+      "unknown expression type: " + std::string(expr->TypeInfo().name));
+  return false;
+}
+
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
+                                   const ast::IdentifierExpression* expr) {
+  out << builder_.Symbols().NameFor(expr->symbol);
+  return true;
+}
+
+bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
+  {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      line() << "} else {";
+      increment_indent();
+
+      {
+        auto out = line();
+        out << "if (";
+        if (!EmitExpression(out, e->condition)) {
+          return false;
+        }
+        out << ") {";
+      }
+    } else {
+      line() << "} else {";
+    }
+
+    if (!EmitStatementsWithIndent(e->body->statements)) {
+      return false;
+    }
+  }
+
+  line() << "}";
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      decrement_indent();
+      line() << "}";
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitFunction(const ast::Function* func) {
+  auto* sem = builder_.Sem().Get(func);
+
+  if (ast::HasAttribute<ast::InternalAttribute>(func->attributes)) {
+    // An internal function. Do not emit.
+    return true;
+  }
+
+  {
+    auto out = line();
+    auto name = builder_.Symbols().NameFor(func->symbol);
+    if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
+                  ast::Access::kReadWrite, "")) {
+      return false;
+    }
+
+    out << " " << name << "(";
+
+    bool first = true;
+
+    for (auto* v : sem->Parameters()) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      auto const* type = v->Type();
+
+      if (auto* ptr = type->As<sem::Pointer>()) {
+        // Transform pointer parameters in to `inout` parameters.
+        // The WGSL spec is highly restrictive in what can be passed in pointer
+        // parameters, which allows for this transformation. See:
+        // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
+        out << "inout ";
+        type = ptr->StoreType();
+      }
+
+      // Note: WGSL only allows for StorageClass::kNone on parameters, however
+      // the sanitizer transforms generates load / store functions for storage
+      // or uniform buffers. These functions have a buffer parameter with
+      // StorageClass::kStorage or StorageClass::kUniform. This is required to
+      // correctly translate the parameter to a [RW]ByteAddressBuffer for
+      // storage buffers and a uint4[N] for uniform buffers.
+      if (!EmitTypeAndName(
+              out, type, v->StorageClass(), v->Access(),
+              builder_.Symbols().NameFor(v->Declaration()->symbol))) {
+        return false;
+      }
+    }
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(func->body->statements)) {
+    return false;
+  }
+
+  line() << "}";
+  line();
+
+  return true;
+}
+
+bool GeneratorImpl::EmitGlobalVariable(const ast::Variable* global) {
+  if (global->is_const) {
+    return EmitProgramConstVariable(global);
+  }
+
+  auto* sem = builder_.Sem().Get(global);
+  switch (sem->StorageClass()) {
+    case ast::StorageClass::kUniform:
+      return EmitUniformVariable(sem);
+    case ast::StorageClass::kStorage:
+      return EmitStorageVariable(sem);
+    case ast::StorageClass::kUniformConstant:
+      return EmitHandleVariable(sem);
+    case ast::StorageClass::kPrivate:
+      return EmitPrivateVariable(sem);
+    case ast::StorageClass::kWorkgroup:
+      return EmitWorkgroupVariable(sem);
+    case ast::StorageClass::kInput:
+    case ast::StorageClass::kOutput:
+      return EmitIOVariable(sem);
+    default:
+      break;
+  }
+
+  TINT_ICE(Writer, diagnostics_)
+      << "unhandled storage class " << sem->StorageClass();
+  return false;
+}
+
+bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto* type = var->Type()->UnwrapRef();
+  auto* str = type->As<sem::Struct>();
+  if (!str) {
+    TINT_ICE(Writer, builder_.Diagnostics())
+        << "storage variable must be of struct type";
+    return false;
+  }
+  ast::VariableBindingPoint bp = decl->BindingPoint();
+  {
+    auto out = line();
+    out << "layout(binding = " << bp.binding->value;
+    if (version_.IsDesktop()) {
+      out << ", std140";
+    }
+    out << ") uniform " << UniqueIdentifier(StructName(str)) << " {";
+  }
+  EmitStructMembers(current_buffer_, str, /* emit_offsets */ true);
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  line() << "} " << name << ";";
+  line();
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto* type = var->Type()->UnwrapRef();
+  auto* str = type->As<sem::Struct>();
+  if (!str) {
+    TINT_ICE(Writer, builder_.Diagnostics())
+        << "storage variable must be of struct type";
+    return false;
+  }
+  ast::VariableBindingPoint bp = decl->BindingPoint();
+  line() << "layout(binding = " << bp.binding->value << ", std430) buffer "
+         << UniqueIdentifier(StructName(str)) << " {";
+  EmitStructMembers(current_buffer_, str, /* emit_offsets */ true);
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  line() << "} " << name << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (type->Is<sem::Sampler>()) {
+    // GLSL ignores Sampler variables.
+    return true;
+  }
+  if (auto* storage = type->As<sem::StorageTexture>()) {
+    out << "layout(" << convert_texel_format_to_glsl(storage->texel_format())
+        << ") ";
+  }
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  out << " = ";
+  if (auto* constructor = decl->constructor) {
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, var->Type()->UnwrapRef())) {
+      return false;
+    }
+  }
+
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
+  out << "shared ";
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  if (auto* constructor = decl->constructor) {
+    out << " = ";
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  }
+
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitIOVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+
+  if (auto* b = ast::GetAttribute<ast::BuiltinAttribute>(decl->attributes)) {
+    // Use of gl_SampleID requires the GL_OES_sample_variables extension
+    if (RequiresOESSampleVariables(b->builtin)) {
+      requires_oes_sample_variables_ = true;
+    }
+    // Do not emit builtin (gl_) variables.
+    return true;
+  }
+
+  auto out = line();
+  EmitAttributes(out, decl->attributes);
+  EmitInterpolationQualifiers(out, decl->attributes);
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  if (auto* constructor = decl->constructor) {
+    out << " = ";
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  }
+
+  out << ";";
+  return true;
+}
+
+void GeneratorImpl::EmitInterpolationQualifiers(
+    std::ostream& out,
+    const ast::AttributeList& attributes) {
+  for (auto* attr : attributes) {
+    if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
+      switch (interpolate->type) {
+        case ast::InterpolationType::kPerspective:
+        case ast::InterpolationType::kLinear:
+          break;
+        case ast::InterpolationType::kFlat:
+          out << "flat ";
+          break;
+      }
+      switch (interpolate->sampling) {
+        case ast::InterpolationSampling::kCentroid:
+          out << "centroid ";
+          break;
+        case ast::InterpolationSampling::kSample:
+        case ast::InterpolationSampling::kCenter:
+        case ast::InterpolationSampling::kNone:
+          break;
+      }
+    }
+  }
+}
+
+bool GeneratorImpl::EmitAttributes(std::ostream& out,
+                                   const ast::AttributeList& attributes) {
+  if (attributes.empty()) {
+    return true;
+  }
+  bool first = true;
+  for (auto* attr : attributes) {
+    if (auto* location = attr->As<ast::LocationAttribute>()) {
+      out << (first ? "layout(" : ", ");
+      out << "location = " << std::to_string(location->value);
+      first = false;
+    }
+  }
+  if (!first) {
+    out << ") ";
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
+  auto* func_sem = builder_.Sem().Get(func);
+
+  if (func->PipelineStage() == ast::PipelineStage::kFragment) {
+    requires_default_precision_qualifier_ = true;
+  }
+
+  if (func->PipelineStage() == ast::PipelineStage::kCompute) {
+    auto out = line();
+    // Emit the layout(local_size) attributes.
+    auto wgsize = func_sem->WorkgroupSize();
+    out << "layout(";
+    for (int i = 0; i < 3; i++) {
+      if (i > 0) {
+        out << ", ";
+      }
+      out << "local_size_" << (i == 0 ? "x" : i == 1 ? "y" : "z") << " = ";
+
+      if (wgsize[i].overridable_const) {
+        auto* global = builder_.Sem().Get<sem::GlobalVariable>(
+            wgsize[i].overridable_const);
+        if (!global->IsOverridable()) {
+          TINT_ICE(Writer, builder_.Diagnostics())
+              << "expected a pipeline-overridable constant";
+        }
+        out << kSpecConstantPrefix << global->ConstantId();
+      } else {
+        out << std::to_string(wgsize[i].value);
+      }
+    }
+    out << ") in;";
+  }
+
+  // Emit original entry point signature
+  {
+    auto out = line();
+    out << func->return_type->FriendlyName(builder_.Symbols()) << " "
+        << builder_.Symbols().NameFor(func->symbol) << "(";
+
+    bool first = true;
+
+    // Emit entry point parameters.
+    for (auto* var : func->params) {
+      auto* sem = builder_.Sem().Get(var);
+      auto* type = sem->Type();
+      if (!type->Is<sem::Struct>()) {
+        // ICE likely indicates that the CanonicalizeEntryPointIO transform was
+        // not run, or a builtin parameter was added after it was run.
+        TINT_ICE(Writer, diagnostics_)
+            << "Unsupported non-struct entry point parameter";
+      }
+
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol))) {
+        return false;
+      }
+    }
+
+    out << ") {";
+  }
+
+  // Emit original entry point function body
+  {
+    ScopedIndent si(this);
+
+    if (!EmitStatements(func->body->statements)) {
+      return false;
+    }
+
+    if (!Is<ast::ReturnStatement>(func->body->Last())) {
+      ast::ReturnStatement ret(ProgramID(), Source{});
+      if (!EmitStatement(&ret)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitLiteral(std::ostream& out,
+                                const ast::LiteralExpression* lit) {
+  if (auto* l = lit->As<ast::BoolLiteralExpression>()) {
+    out << (l->value ? "true" : "false");
+  } else if (auto* fl = lit->As<ast::FloatLiteralExpression>()) {
+    if (std::isinf(fl->value)) {
+      out << (fl->value >= 0 ? "uintBitsToFloat(0x7f800000u)"
+                             : "uintBitsToFloat(0xff800000u)");
+    } else if (std::isnan(fl->value)) {
+      out << "uintBitsToFloat(0x7fc00000u)";
+    } else {
+      out << FloatToString(fl->value) << "f";
+    }
+  } else if (auto* sl = lit->As<ast::SintLiteralExpression>()) {
+    out << sl->value;
+  } else if (auto* ul = lit->As<ast::UintLiteralExpression>()) {
+    out << ul->value << "u";
+  } else {
+    diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+    return false;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+  if (type->Is<sem::Bool>()) {
+    out << "false";
+  } else if (type->Is<sem::F32>()) {
+    out << "0.0f";
+  } else if (type->Is<sem::I32>()) {
+    out << "0";
+  } else if (type->Is<sem::U32>()) {
+    out << "0u";
+  } else if (auto* vec = type->As<sem::Vector>()) {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
+                  "")) {
+      return false;
+    }
+    ScopedParen sp(out);
+    for (uint32_t i = 0; i < vec->Width(); i++) {
+      if (i != 0) {
+        out << ", ";
+      }
+      if (!EmitZeroValue(out, vec->type())) {
+        return false;
+      }
+    }
+  } else if (auto* mat = type->As<sem::Matrix>()) {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
+                  "")) {
+      return false;
+    }
+    ScopedParen sp(out);
+    for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) {
+      if (i != 0) {
+        out << ", ";
+      }
+      if (!EmitZeroValue(out, mat->type())) {
+        return false;
+      }
+    }
+  } else if (auto* str = type->As<sem::Struct>()) {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined,
+                  "")) {
+      return false;
+    }
+    bool first = true;
+    out << "(";
+    for (auto* member : str->Members()) {
+      if (!first) {
+        out << ", ";
+      } else {
+        first = false;
+      }
+      EmitZeroValue(out, member->Type());
+    }
+    out << ")";
+  } else if (auto* array = type->As<sem::Array>()) {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined,
+                  "")) {
+      return false;
+    }
+    out << "(";
+    for (uint32_t i = 0; i < array->Count(); i++) {
+      if (i != 0) {
+        out << ", ";
+      }
+      EmitZeroValue(out, array->ElemType());
+    }
+    out << ")";
+  } else {
+    diagnostics_.add_error(
+        diag::System::Writer,
+        "Invalid type for zero emission: " + type->type_name());
+    return false;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
+  auto emit_continuing = [this, stmt]() {
+    if (stmt->continuing && !stmt->continuing->Empty()) {
+      if (!EmitBlock(stmt->continuing)) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+  line() << "while (true) {";
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+    if (!emit_continuing_()) {
+      return false;
+    }
+  }
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+  // Nest a for loop with a new block. In HLSL the initializer scope is not
+  // nested by the for-loop, so we may get variable redefinitions.
+  line() << "{";
+  increment_indent();
+  TINT_DEFER({
+    decrement_indent();
+    line() << "}";
+  });
+
+  TextBuffer init_buf;
+  if (auto* init = stmt->initializer) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
+    if (!EmitStatement(init)) {
+      return false;
+    }
+  }
+
+  TextBuffer cond_pre;
+  std::stringstream cond_buf;
+  if (auto* cond = stmt->condition) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
+    if (!EmitExpression(cond_buf, cond)) {
+      return false;
+    }
+  }
+
+  TextBuffer cont_buf;
+  if (auto* cont = stmt->continuing) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
+    if (!EmitStatement(cont)) {
+      return false;
+    }
+  }
+
+  // If the for-loop has a multi-statement conditional and / or continuing, then
+  // we cannot emit this as a regular for-loop in HLSL. Instead we need to
+  // generate a `while(true)` loop.
+  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
+
+  // If the for-loop has multi-statement initializer, or is going to be emitted
+  // as a `while(true)` loop, then declare the initializer statement(s) before
+  // the loop.
+  if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) {
+    current_buffer_->Append(init_buf);
+    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
+  }
+
+  if (emit_as_loop) {
+    auto emit_continuing = [&]() {
+      current_buffer_->Append(cont_buf);
+      return true;
+    };
+
+    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+    line() << "while (true) {";
+    increment_indent();
+    TINT_DEFER({
+      decrement_indent();
+      line() << "}";
+    });
+
+    if (stmt->condition) {
+      current_buffer_->Append(cond_pre);
+      line() << "if (!(" << cond_buf.str() << ")) { break; }";
+    }
+
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+
+    if (!emit_continuing_()) {
+      return false;
+    }
+  } else {
+    // For-loop can be generated.
+    {
+      auto out = line();
+      out << "for";
+      {
+        ScopedParen sp(out);
+
+        if (!init_buf.lines.empty()) {
+          out << init_buf.lines[0].content << " ";
+        } else {
+          out << "; ";
+        }
+
+        out << cond_buf.str() << "; ";
+
+        if (!cont_buf.lines.empty()) {
+          out << TrimSuffix(cont_buf.lines[0].content, ";");
+        }
+      }
+      out << " {";
+    }
+    {
+      auto emit_continuing = [] { return true; };
+      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+      if (!EmitStatementsWithIndent(stmt->body->statements)) {
+        return false;
+      }
+    }
+    line() << "}";
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitMemberAccessor(
+    std::ostream& out,
+    const ast::MemberAccessorExpression* expr) {
+  if (!EmitExpression(out, expr->structure)) {
+    return false;
+  }
+  out << ".";
+
+  // Swizzles output the name directly
+  if (builder_.Sem().Get(expr)->Is<sem::Swizzle>()) {
+    out << builder_.Symbols().NameFor(expr->member->symbol);
+  } else if (!EmitExpression(out, expr->member)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
+  if (stmt->value) {
+    auto out = line();
+    out << "return ";
+    if (!EmitExpression(out, stmt->value)) {
+      return false;
+    }
+    out << ";";
+  } else {
+    line() << "return;";
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
+  if (auto* a = stmt->As<ast::AssignmentStatement>()) {
+    return EmitAssign(a);
+  }
+  if (auto* b = stmt->As<ast::BlockStatement>()) {
+    return EmitBlock(b);
+  }
+  if (auto* b = stmt->As<ast::BreakStatement>()) {
+    return EmitBreak(b);
+  }
+  if (auto* c = stmt->As<ast::CallStatement>()) {
+    auto out = line();
+    if (!EmitCall(out, c->expr)) {
+      return false;
+    }
+    out << ";";
+    return true;
+  }
+  if (auto* c = stmt->As<ast::ContinueStatement>()) {
+    return EmitContinue(c);
+  }
+  if (auto* d = stmt->As<ast::DiscardStatement>()) {
+    return EmitDiscard(d);
+  }
+  if (stmt->As<ast::FallthroughStatement>()) {
+    line() << "/* fallthrough */";
+    return true;
+  }
+  if (auto* i = stmt->As<ast::IfStatement>()) {
+    return EmitIf(i);
+  }
+  if (auto* l = stmt->As<ast::LoopStatement>()) {
+    return EmitLoop(l);
+  }
+  if (auto* l = stmt->As<ast::ForLoopStatement>()) {
+    return EmitForLoop(l);
+  }
+  if (auto* r = stmt->As<ast::ReturnStatement>()) {
+    return EmitReturn(r);
+  }
+  if (auto* s = stmt->As<ast::SwitchStatement>()) {
+    return EmitSwitch(s);
+  }
+  if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
+    return EmitVariable(v->variable);
+  }
+
+  diagnostics_.add_error(
+      diag::System::Writer,
+      "unknown statement type: " + std::string(stmt->TypeInfo().name));
+  return false;
+}
+
+bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
+  {  // switch(expr) {
+    auto out = line();
+    out << "switch(";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+    for (auto* s : stmt->body) {
+      if (!EmitCase(s)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitType(std::ostream& out,
+                             const sem::Type* type,
+                             ast::StorageClass storage_class,
+                             ast::Access access,
+                             const std::string& name,
+                             bool* name_printed /* = nullptr */) {
+  if (name_printed) {
+    *name_printed = false;
+  }
+  switch (storage_class) {
+    case ast::StorageClass::kInput: {
+      out << "in ";
+      break;
+    }
+    case ast::StorageClass::kOutput: {
+      out << "out ";
+      break;
+    }
+    case ast::StorageClass::kUniform: {
+      out << "uniform ";
+      break;
+    }
+    default:
+      break;
+  }
+
+  if (auto* ary = type->As<sem::Array>()) {
+    const sem::Type* base_type = ary;
+    std::vector<uint32_t> sizes;
+    while (auto* arr = base_type->As<sem::Array>()) {
+      sizes.push_back(arr->Count());
+      base_type = arr->ElemType();
+    }
+    if (!EmitType(out, base_type, storage_class, access, "")) {
+      return false;
+    }
+    if (!name.empty()) {
+      out << " " << name;
+      if (name_printed) {
+        *name_printed = true;
+      }
+    }
+    for (uint32_t size : sizes) {
+      if (size > 0) {
+        out << "[" << size << "]";
+      } else {
+        out << "[]";
+      }
+    }
+  } else if (type->Is<sem::Bool>()) {
+    out << "bool";
+  } else if (type->Is<sem::F32>()) {
+    out << "float";
+  } else if (type->Is<sem::I32>()) {
+    out << "int";
+  } else if (auto* mat = type->As<sem::Matrix>()) {
+    TINT_ASSERT(Writer, mat->type()->Is<sem::F32>());
+    out << "mat" << mat->columns();
+    if (mat->rows() != mat->columns()) {
+      out << "x" << mat->rows();
+    }
+  } else if (type->Is<sem::Pointer>()) {
+    TINT_ICE(Writer, diagnostics_)
+        << "Attempting to emit pointer type. These should have been removed "
+           "with the InlinePointerLets transform";
+    return false;
+  } else if (type->Is<sem::Sampler>()) {
+    return false;
+  } else if (auto* str = type->As<sem::Struct>()) {
+    out << StructName(str);
+  } else if (auto* tex = type->As<sem::Texture>()) {
+    auto* storage = tex->As<sem::StorageTexture>();
+    auto* ms = tex->As<sem::MultisampledTexture>();
+    auto* depth_ms = tex->As<sem::DepthMultisampledTexture>();
+    auto* sampled = tex->As<sem::SampledTexture>();
+
+    out << "uniform highp ";
+
+    if (storage && storage->access() != ast::Access::kRead) {
+      out << "writeonly ";
+    }
+    auto* subtype = sampled
+                        ? sampled->type()
+                        : storage ? storage->type() : ms ? ms->type() : nullptr;
+    if (!subtype || subtype->Is<sem::F32>()) {
+    } else if (subtype->Is<sem::I32>()) {
+      out << "i";
+    } else if (subtype->Is<sem::U32>()) {
+      out << "u";
+    } else {
+      TINT_ICE(Writer, diagnostics_) << "Unsupported texture type";
+      return false;
+    }
+
+    out << (storage ? "image" : "sampler");
+
+    switch (tex->dim()) {
+      case ast::TextureDimension::k1d:
+        out << "1D";
+        break;
+      case ast::TextureDimension::k2d:
+        out << ((ms || depth_ms) ? "2DMS" : "2D");
+        break;
+      case ast::TextureDimension::k2dArray:
+        out << ((ms || depth_ms) ? "2DMSArray" : "2DArray");
+        break;
+      case ast::TextureDimension::k3d:
+        out << "3D";
+        break;
+      case ast::TextureDimension::kCube:
+        out << "Cube";
+        break;
+      case ast::TextureDimension::kCubeArray:
+        out << "CubeArray";
+        break;
+      default:
+        TINT_UNREACHABLE(Writer, diagnostics_)
+            << "unexpected TextureDimension " << tex->dim();
+        return false;
+    }
+    if (tex->Is<sem::DepthTexture>()) {
+      out << "Shadow";
+    }
+  } else if (type->Is<sem::U32>()) {
+    out << "uint";
+  } else if (auto* vec = type->As<sem::Vector>()) {
+    auto width = vec->Width();
+    if (vec->type()->Is<sem::F32>() && width >= 1 && width <= 4) {
+      out << "vec" << width;
+    } else if (vec->type()->Is<sem::I32>() && width >= 1 && width <= 4) {
+      out << "ivec" << width;
+    } else if (vec->type()->Is<sem::U32>() && width >= 1 && width <= 4) {
+      out << "uvec" << width;
+    } else if (vec->type()->Is<sem::Bool>() && width >= 1 && width <= 4) {
+      out << "bvec" << width;
+    } else {
+      out << "vector<";
+      if (!EmitType(out, vec->type(), storage_class, access, "")) {
+        return false;
+      }
+      out << ", " << width << ">";
+    }
+  } else if (auto* atomic = type->As<sem::Atomic>()) {
+    if (!EmitType(out, atomic->Type(), storage_class, access, name)) {
+      return false;
+    }
+  } else if (type->Is<sem::Void>()) {
+    out << "void";
+  } else {
+    diagnostics_.add_error(diag::System::Writer, "unknown type in EmitType");
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
+                                    const sem::Type* type,
+                                    ast::StorageClass storage_class,
+                                    ast::Access access,
+                                    const std::string& name) {
+  bool printed_name = false;
+  if (!EmitType(out, type, storage_class, access, name, &printed_name)) {
+    return false;
+  }
+  if (!name.empty() && !printed_name) {
+    out << " " << name;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
+  auto storage_class_uses = str->StorageClassUsage();
+  line(b) << "struct " << StructName(str) << " {";
+  EmitStructMembers(b, str, false);
+  line(b) << "};";
+  line(b);
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStructMembers(TextBuffer* b,
+                                      const sem::Struct* str,
+                                      bool emit_offsets) {
+  ScopedIndent si(b);
+  for (auto* mem : str->Members()) {
+    auto name = builder_.Symbols().NameFor(mem->Name());
+
+    auto* ty = mem->Type();
+
+    auto out = line(b);
+
+    // Note: offsets are unsupported on GLSL ES.
+    if (emit_offsets && version_.IsDesktop() && mem->Offset() != 0) {
+      out << "layout(offset=" << mem->Offset() << ") ";
+    }
+    if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
+                         ast::Access::kReadWrite, name)) {
+      return false;
+    }
+    out << ";";
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
+                                const ast::UnaryOpExpression* expr) {
+  switch (expr->op) {
+    case ast::UnaryOp::kIndirection:
+    case ast::UnaryOp::kAddressOf:
+      return EmitExpression(out, expr->expr);
+    case ast::UnaryOp::kComplement:
+      out << "~";
+      break;
+    case ast::UnaryOp::kNot:
+      out << "!";
+      break;
+    case ast::UnaryOp::kNegation:
+      out << "-";
+      break;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitVariable(const ast::Variable* var) {
+  auto* sem = builder_.Sem().Get(var);
+  auto* type = sem->Type()->UnwrapRef();
+
+  // TODO(dsinclair): Handle variable attributes
+  if (!var->attributes.empty()) {
+    diagnostics_.add_error(diag::System::Writer,
+                           "Variable attributes are not handled yet");
+    return false;
+  }
+
+  auto out = line();
+  // TODO(senorblanco): handle const
+  if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                       builder_.Symbols().NameFor(var->symbol))) {
+    return false;
+  }
+
+  out << " = ";
+
+  if (var->constructor) {
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, type)) {
+      return false;
+    }
+  }
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
+  for (auto* d : var->attributes) {
+    if (!d->Is<ast::IdAttribute>()) {
+      diagnostics_.add_error(diag::System::Writer,
+                             "Decorated const values not valid");
+      return false;
+    }
+  }
+  if (!var->is_const) {
+    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
+    return false;
+  }
+
+  auto* sem = builder_.Sem().Get(var);
+  auto* type = sem->Type();
+
+  auto* global = sem->As<sem::GlobalVariable>();
+  if (global && global->IsOverridable()) {
+    auto const_id = global->ConstantId();
+
+    line() << "#ifndef " << kSpecConstantPrefix << const_id;
+
+    if (var->constructor != nullptr) {
+      auto out = line();
+      out << "#define " << kSpecConstantPrefix << const_id << " ";
+      if (!EmitExpression(out, var->constructor)) {
+        return false;
+      }
+    } else {
+      line() << "#error spec constant required for constant id " << const_id;
+    }
+    line() << "#endif";
+    {
+      auto out = line();
+      out << "const ";
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol))) {
+        return false;
+      }
+      out << " = " << kSpecConstantPrefix << const_id << ";";
+    }
+  } else {
+    auto out = line();
+    out << "const ";
+    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                         builder_.Symbols().NameFor(var->symbol))) {
+      return false;
+    }
+    out << " = ";
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+    out << ";";
+  }
+
+  return true;
+}
+
+template <typename F>
+bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
+                                      const ast::CallExpression* call,
+                                      const sem::Builtin* builtin,
+                                      F&& build) {
+  // Generate the helper function if it hasn't been created already
+  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
+    TextBuffer b;
+    TINT_DEFER(helpers_.Append(b));
+
+    auto fn_name =
+        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+    std::vector<std::string> parameter_names;
+    {
+      auto decl = line(&b);
+      if (!EmitTypeAndName(decl, builtin->ReturnType(),
+                           ast::StorageClass::kNone, ast::Access::kUndefined,
+                           fn_name)) {
+        return "";
+      }
+      {
+        ScopedParen sp(decl);
+        for (auto* param : builtin->Parameters()) {
+          if (!parameter_names.empty()) {
+            decl << ", ";
+          }
+          auto param_name = "param_" + std::to_string(parameter_names.size());
+          const auto* ty = param->Type();
+          if (auto* ptr = ty->As<sem::Pointer>()) {
+            decl << "inout ";
+            ty = ptr->StoreType();
+          }
+          if (!EmitTypeAndName(decl, ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, param_name)) {
+            return "";
+          }
+          parameter_names.emplace_back(std::move(param_name));
+        }
+      }
+      decl << " {";
+    }
+    {
+      ScopedIndent si(&b);
+      if (!build(&b, parameter_names)) {
+        return "";
+      }
+    }
+    line(&b) << "}";
+    line(&b);
+    return fn_name;
+  });
+
+  if (fn.empty()) {
+    return false;
+  }
+
+  // Call the helper
+  out << fn;
+  {
+    ScopedParen sp(out);
+    bool first = true;
+    for (auto* arg : call->args) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+      if (!EmitExpression(out, arg)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+sem::Type* GeneratorImpl::BoolTypeToUint(const sem::Type* type) {
+  auto* u32 = builder_.create<sem::U32>();
+  if (type->Is<sem::Bool>()) {
+    return u32;
+  } else if (auto* vec = type->As<sem::Vector>()) {
+    return builder_.create<sem::Vector>(u32, vec->Width());
+  } else {
+    return nullptr;
+  }
+}
+
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
new file mode 100644
index 0000000..25e416c
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -0,0 +1,500 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_GLSL_GENERATOR_IMPL_H_
+#define SRC_TINT_WRITER_GLSL_GENERATOR_IMPL_H_
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/transform/decompose_memory_access.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/writer/glsl/version.h"
+#include "src/tint/writer/text_generator.h"
+
+namespace tint {
+
+// Forward declarations
+namespace sem {
+class Call;
+class Builtin;
+class TypeConstructor;
+class TypeConversion;
+}  // namespace sem
+
+namespace writer {
+namespace glsl {
+
+/// Implementation class for GLSL generator
+class GeneratorImpl : public TextGenerator {
+ public:
+  /// Constructor
+  /// @param program the program to generate
+  /// @param version the GLSL version to use
+  GeneratorImpl(const Program* program, const Version& version);
+  ~GeneratorImpl();
+
+  /// @returns true on successful generation; false otherwise
+  bool Generate();
+
+  /// Handles an index accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the index accessor was emitted
+  bool EmitIndexAccessor(std::ostream& out,
+                         const ast::IndexAccessorExpression* expr);
+  /// Handles an assignment statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitAssign(const ast::AssignmentStatement* stmt);
+  /// Handles emission of bitwise operators (&|) on bool scalars and vectors
+  /// @param out the output of the expression stream
+  /// @param expr the binary expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitBitwiseBoolOp(std::ostream& out, const ast::BinaryExpression* expr);
+  /// Handles generating a binary expression
+  /// @param out the output of the expression stream
+  /// @param expr the binary expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
+  /// Handles generating a bitcast expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the binary expression was emitted
+  bool EmitVectorRelational(std::ostream& out,
+                            const ast::BinaryExpression* expr);
+  /// Handles generating a vector relational expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the vector relational expression was emitted
+  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
+  /// Emits a list of statements
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatements(const ast::StatementList& stmts);
+  /// Emits a list of statements with an indentation
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+  /// Handles a block statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBlock(const ast::BlockStatement* stmt);
+  /// Handles a break statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBreak(const ast::BreakStatement* stmt);
+  /// Handles generating a call expression
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a function call expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @returns true if the expression is emitted
+  bool EmitFunctionCall(std::ostream& out, const sem::Call* call);
+  /// Handles generating a builtin call expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the builtin being called
+  /// @returns true if the expression is emitted
+  bool EmitBuiltinCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a type conversion expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param conv the type conversion
+  /// @returns true if the expression is emitted
+  bool EmitTypeConversion(std::ostream& out,
+                          const sem::Call* call,
+                          const sem::TypeConversion* conv);
+  /// Handles generating a type constructor expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param ctor the type constructor
+  /// @returns true if the expression is emitted
+  bool EmitTypeConstructor(std::ostream& out,
+                           const sem::Call* call,
+                           const sem::TypeConstructor* ctor);
+  /// Handles generating a barrier builtin call
+  /// @param out the output of the expression stream
+  /// @param builtin the semantic information for the barrier builtin
+  /// @returns true if the call expression is emitted
+  bool EmitBarrierCall(std::ostream& out, const sem::Builtin* builtin);
+  /// Handles generating an atomic intrinsic call for a storage buffer variable
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param intrinsic the atomic intrinsic
+  /// @returns true if the call expression is emitted
+  bool EmitStorageAtomicCall(
+      std::ostream& out,
+      const ast::CallExpression* expr,
+      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
+  /// Handles generating an atomic builtin call for a workgroup variable
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the atomic builtin
+  /// @returns true if the call expression is emitted
+  bool EmitWorkgroupAtomicCall(std::ostream& out,
+                               const ast::CallExpression* expr,
+                               const sem::Builtin* builtin);
+  /// Handles generating an array.length() call
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the array length expression is emitted
+  bool EmitArrayLength(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a call to a texture function (`textureSample`,
+  /// `textureSampleGrad`, etc)
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitTextureCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `select()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitSelectCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a call to the `dot()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDotCall(std::ostream& out,
+                   const ast::CallExpression* expr,
+                   const sem::Builtin* builtin);
+  /// Handles generating a call to the `modf()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitModfCall(std::ostream& out,
+                    const ast::CallExpression* expr,
+                    const sem::Builtin* builtin);
+  /// Handles generating a call to the `frexp()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitFrexpCall(std::ostream& out,
+                     const ast::CallExpression* expr,
+                     const sem::Builtin* builtin);
+  /// Handles generating a call to the `isNormal()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitIsNormalCall(std::ostream& out,
+                        const ast::CallExpression* expr,
+                        const sem::Builtin* builtin);
+  /// Handles generating a call to the `degrees()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDegreesCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `radians()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitRadiansCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to data packing builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDataPackingCall(std::ostream& out,
+                           const ast::CallExpression* expr,
+                           const sem::Builtin* builtin);
+  /// Handles generating a call to data unpacking builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDataUnpackingCall(std::ostream& out,
+                             const ast::CallExpression* expr,
+                             const sem::Builtin* builtin);
+  /// Handles a case statement
+  /// @param stmt the statement
+  /// @returns true if the statement was emitted successfully
+  bool EmitCase(const ast::CaseStatement* stmt);
+  /// Handles generating a discard statement
+  /// @param stmt the discard statement
+  /// @returns true if the statement was successfully emitted
+  bool EmitDiscard(const ast::DiscardStatement* stmt);
+  /// Handles a continue statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitContinue(const ast::ContinueStatement* stmt);
+  /// Handles generate an Expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the expression was emitted
+  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
+  /// Handles generating a function
+  /// @param func the function to generate
+  /// @returns true if the function was emitted
+  bool EmitFunction(const ast::Function* func);
+
+  /// Handles emitting a global variable
+  /// @param global the global variable
+  /// @returns true on success
+  bool EmitGlobalVariable(const ast::Variable* global);
+
+  /// Handles emitting a global variable with the uniform storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitUniformVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the storage storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitStorageVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the handle storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitHandleVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the private storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitPrivateVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the workgroup storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitWorkgroupVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the input or output storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitIOVariable(const sem::Variable* var);
+
+  /// Handles emitting interpolation qualifiers
+  /// @param out the output of the expression stream
+  /// @param attrs the attributes
+  void EmitInterpolationQualifiers(std::ostream& out,
+                                   const ast::AttributeList& attrs);
+  /// Handles emitting attributes
+  /// @param out the output of the expression stream
+  /// @param attrs the attributes
+  /// @returns true if the attributes were emitted
+  bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
+  /// Handles emitting the entry point function
+  /// @param func the entry point
+  /// @returns true if the entry point function was emitted
+  bool EmitEntryPointFunction(const ast::Function* func);
+  /// Handles an if statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitIf(const ast::IfStatement* stmt);
+  /// Handles a literal
+  /// @param out the output stream
+  /// @param lit the literal to emit
+  /// @returns true if the literal was successfully emitted
+  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
+  /// Handles a loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitLoop(const ast::LoopStatement* stmt);
+  /// Handles a for loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitForLoop(const ast::ForLoopStatement* stmt);
+  /// Handles generating an identifier expression
+  /// @param out the output of the expression stream
+  /// @param expr the identifier expression
+  /// @returns true if the identifier was emitted
+  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
+  /// Handles a member accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the member accessor expression
+  /// @returns true if the member accessor was emitted
+  bool EmitMemberAccessor(std::ostream& out,
+                          const ast::MemberAccessorExpression* expr);
+  /// Handles return statements
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitReturn(const ast::ReturnStatement* stmt);
+  /// Handles statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitStatement(const ast::Statement* stmt);
+  /// Handles generating a switch statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitSwitch(const ast::SwitchStatement* stmt);
+  /// Handles generating type
+  /// @param out the output stream
+  /// @param type the type to generate
+  /// @param storage_class the storage class of the variable
+  /// @param access the access control type of the variable
+  /// @param name the name of the variable, used for array emission.
+  /// @param name_printed (optional) if not nullptr and an array was printed
+  /// then the boolean is set to true.
+  /// @returns true if the type is emitted
+  bool EmitType(std::ostream& out,
+                const sem::Type* type,
+                ast::StorageClass storage_class,
+                ast::Access access,
+                const std::string& name,
+                bool* name_printed = nullptr);
+  /// Handles generating type and name
+  /// @param out the output stream
+  /// @param type the type to generate
+  /// @param storage_class the storage class of the variable
+  /// @param access the access control type of the variable
+  /// @param name the name to emit
+  /// @returns true if the type is emitted
+  bool EmitTypeAndName(std::ostream& out,
+                       const sem::Type* type,
+                       ast::StorageClass storage_class,
+                       ast::Access access,
+                       const std::string& name);
+  /// Handles generating a structure declaration
+  /// @param buffer the text buffer that the type declaration will be written to
+  /// @param ty the struct to generate
+  /// @returns true if the struct is emitted
+  bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty);
+  /// Handles generating the members of a structure
+  /// @param buffer the text buffer that the struct members will be written to
+  /// @param ty the struct to generate
+  /// @param emit_offsets whether offsets should be emitted as offset=
+  /// @returns true if the struct members are emitted
+  bool EmitStructMembers(TextBuffer* buffer,
+                         const sem::Struct* ty,
+                         bool emit_offsets);
+  /// Handles a unary op expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the expression was emitted
+  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
+  /// Emits the zero value for the given type
+  /// @param out the output stream
+  /// @param type the type to emit the value for
+  /// @returns true if the zero value was successfully emitted.
+  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+  /// Handles generating a variable
+  /// @param var the variable to generate
+  /// @returns true if the variable was emitted
+  bool EmitVariable(const ast::Variable* var);
+  /// Handles generating a program scope constant variable
+  /// @param var the variable to emit
+  /// @returns true if the variable was emitted
+  bool EmitProgramConstVariable(const ast::Variable* var);
+  /// Handles generating a builtin method name
+  /// @param builtin the semantic info for the builtin
+  /// @returns the name or "" if not valid
+  std::string generate_builtin_name(const sem::Builtin* builtin);
+  /// Converts a builtin to a gl_ string
+  /// @param builtin the builtin to convert
+  /// @param stage pipeline stage in which this builtin is used
+  /// @returns the string name of the builtin or blank on error
+  const char* builtin_to_string(ast::Builtin builtin, ast::PipelineStage stage);
+  /// Converts a builtin to a sem::Type appropriate for GLSL.
+  /// @param builtin the builtin to convert
+  /// @returns the appropriate semantic type or null on error.
+  sem::Type* builtin_type(ast::Builtin builtin);
+
+ private:
+  enum class VarType { kIn, kOut };
+
+  struct EntryPointData {
+    std::string struct_name;
+    std::string var_name;
+  };
+
+  struct DMAIntrinsic {
+    transform::DecomposeMemoryAccess::Intrinsic::Op op;
+    transform::DecomposeMemoryAccess::Intrinsic::DataType type;
+    bool operator==(const DMAIntrinsic& rhs) const {
+      return op == rhs.op && type == rhs.type;
+    }
+    /// Hasher is a std::hash function for DMAIntrinsic
+    struct Hasher {
+      /// @param i the DMAIntrinsic to hash
+      /// @returns the hash of `i`
+      inline std::size_t operator()(const DMAIntrinsic& i) const {
+        return utils::Hash(i.op, i.type);
+      }
+    };
+  };
+
+  /// CallBuiltinHelper will call the builtin helper function, creating it
+  /// if it hasn't been built already. If the builtin needs to be built then
+  /// CallBuiltinHelper will generate the function signature and will call
+  /// `build` to emit the body of the function.
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @param build a function with the signature:
+  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
+  ///        Where:
+  ///          `buffer` is the body of the generated function
+  ///          `params` is the name of all the generated function parameters
+  /// @returns true if the call expression is emitted
+  template <typename F>
+  bool CallBuiltinHelper(std::ostream& out,
+                         const ast::CallExpression* call,
+                         const sem::Builtin* builtin,
+                         F&& build);
+
+  /// Create a uint type corresponding to the given bool or bool vector type.
+  /// @param type the bool or bool vector type to convert
+  /// @returns the corresponding uint type
+  sem::Type* BoolTypeToUint(const sem::Type* type);
+
+  TextBuffer helpers_;  // Helper functions emitted at the top of the output
+  std::function<bool()> emit_continuing_;
+  std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher>
+      dma_intrinsics_;
+  std::unordered_map<const sem::Builtin*, std::string> builtins_;
+  std::unordered_map<const sem::Struct*, std::string> structure_builders_;
+  std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
+  std::unordered_map<const sem::Vector*, std::string> int_dot_funcs_;
+  bool requires_oes_sample_variables_ = false;
+  bool requires_default_precision_qualifier_ = false;
+  Version version_;
+};
+
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_GLSL_GENERATOR_IMPL_H_
diff --git a/src/tint/writer/glsl/generator_impl_array_accessor_test.cc b/src/tint/writer/glsl/generator_impl_array_accessor_test.cc
new file mode 100644
index 0000000..484685c
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_array_accessor_test.cc
@@ -0,0 +1,39 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Expression = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Expression, IndexAccessor) {
+  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
+  auto* expr = IndexAccessor("ary", 5);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "ary[5]");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_assign_test.cc b/src/tint/writer/glsl/generator_impl_assign_test.cc
new file mode 100644
index 0000000..2b55ceb
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_assign_test.cc
@@ -0,0 +1,41 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Assign = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Assign, Emit_Assign) {
+  Global("lhs", ty.i32(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.i32(), ast::StorageClass::kPrivate);
+  auto* assign = Assign("lhs", "rhs");
+  WrapInFunction(assign);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
+  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_binary_test.cc b/src/tint/writer/glsl/generator_impl_binary_test.cc
new file mode 100644
index 0000000..8420ade
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_binary_test.cc
@@ -0,0 +1,515 @@
+// 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/ast/call_statement.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Binary = TestHelper;
+
+struct BinaryData {
+  const char* result;
+  ast::BinaryOp op;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << data.op;
+  return out;
+}
+
+using GlslBinaryTest = TestParamHelper<BinaryData>;
+TEST_P(GlslBinaryTest, Emit_f32) {
+  auto params = GetParam();
+
+  // Skip ops that are illegal for this type
+  if (params.op == ast::BinaryOp::kAnd || params.op == ast::BinaryOp::kOr ||
+      params.op == ast::BinaryOp::kXor ||
+      params.op == ast::BinaryOp::kShiftLeft ||
+      params.op == ast::BinaryOp::kShiftRight) {
+    return;
+  }
+
+  Global("left", ty.f32(), ast::StorageClass::kPrivate);
+  Global("right", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+TEST_P(GlslBinaryTest, Emit_u32) {
+  auto params = GetParam();
+
+  Global("left", ty.u32(), ast::StorageClass::kPrivate);
+  Global("right", ty.u32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+TEST_P(GlslBinaryTest, Emit_i32) {
+  auto params = GetParam();
+
+  // Skip ops that are illegal for this type
+  if (params.op == ast::BinaryOp::kShiftLeft ||
+      params.op == ast::BinaryOp::kShiftRight) {
+    return;
+  }
+
+  Global("left", ty.i32(), ast::StorageClass::kPrivate);
+  Global("right", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest,
+    GlslBinaryTest,
+    testing::Values(
+        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
+        BinaryData{"(left | right)", ast::BinaryOp::kOr},
+        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
+        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
+        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
+        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
+        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
+        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
+        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
+        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
+        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
+        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
+        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
+        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
+        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
+        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorScalar) {
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = Expr(1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            "(vec3(1.0f, 1.0f, 1.0f) * "
+            "1.0f)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarVector) {
+  auto* lhs = Expr(1.f);
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            "(1.0f * vec3(1.0f, 1.0f, "
+            "1.0f))");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixScalar) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr("mat");
+  auto* rhs = Expr(1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(mat * 1.0f)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr(1.f);
+  auto* rhs = Expr("mat");
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(1.0f * mat)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixVector) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr("mat");
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(mat * vec3(1.0f, 1.0f, 1.0f))");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = Expr("mat");
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(vec3(1.0f, 1.0f, 1.0f) * mat)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
+  Global("lhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                             Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(lhs * rhs)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Logical_And) {
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                             Expr("a"), Expr("b"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Logical_Multi) {
+  // (a && b) || (c || d)
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalOr,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
+                                    Expr("b")),
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"),
+                                    Expr("d")));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  bool tint_tmp_2 = c;
+  if (!tint_tmp_2) {
+    tint_tmp_2 = d;
+  }
+  tint_tmp = (tint_tmp_2);
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Logical_Or) {
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                             Expr("a"), Expr("b"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (!tint_tmp) {
+  tint_tmp = b;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, If_WithLogical) {
+  // if (a && b) {
+  //   return 1;
+  // } else if (b || c) {
+  //   return 2;
+  // } else {
+  //   return 3;
+  // }
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                Expr("a"), Expr("b")),
+                  Block(Return(1)),
+                  Else(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                     Expr("b"), Expr("c")),
+                       Block(Return(2))),
+                  Else(Block(Return(3))));
+  Func("func", {}, ty.i32(), {WrapInStatement(expr)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+if ((tint_tmp)) {
+  return 1;
+} else {
+  bool tint_tmp_1 = b;
+  if (!tint_tmp_1) {
+    tint_tmp_1 = c;
+  }
+  if ((tint_tmp_1)) {
+    return 2;
+  } else {
+    return 3;
+  }
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Return_WithLogical) {
+  // return (a && b) || c;
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = Return(create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalOr,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
+                                    Expr("b")),
+      Expr("c")));
+  Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = c;
+}
+return (tint_tmp);
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Assign_WithLogical) {
+  // a = (b || c) && d;
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = Assign(
+      Expr("a"), create<ast::BinaryExpression>(
+                     ast::BinaryOp::kLogicalAnd,
+                     create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                   Expr("b"), Expr("c")),
+                     Expr("d")));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (!tint_tmp_1) {
+  tint_tmp_1 = c;
+}
+bool tint_tmp = (tint_tmp_1);
+if (tint_tmp) {
+  tint_tmp = d;
+}
+a = (tint_tmp);
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Decl_WithLogical) {
+  // var a : bool = (b && c) || d;
+
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* var = Var("a", ty.bool_(), ast::StorageClass::kNone,
+                  create<ast::BinaryExpression>(
+                      ast::BinaryOp::kLogicalOr,
+                      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                    Expr("b"), Expr("c")),
+                      Expr("d")));
+
+  auto* decl = Decl(var);
+  WrapInFunction(decl);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (tint_tmp_1) {
+  tint_tmp_1 = c;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = d;
+}
+bool a = (tint_tmp);
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Binary, Call_WithLogical) {
+  // foo(a && b, c || d, (a || c) && (b || d))
+
+  Func("foo",
+       {
+           Param(Sym(), ty.bool_()),
+           Param(Sym(), ty.bool_()),
+           Param(Sym(), ty.bool_()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  ast::ExpressionList params;
+  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                 Expr("a"), Expr("b")));
+  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                 Expr("c"), Expr("d")));
+  params.push_back(create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalAnd,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"),
+                                    Expr("c")),
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"),
+                                    Expr("d"))));
+
+  auto* expr = CallStmt(Call("foo", params));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+bool tint_tmp_1 = c;
+if (!tint_tmp_1) {
+  tint_tmp_1 = d;
+}
+bool tint_tmp_3 = a;
+if (!tint_tmp_3) {
+  tint_tmp_3 = c;
+}
+bool tint_tmp_2 = (tint_tmp_3);
+if (tint_tmp_2) {
+  bool tint_tmp_4 = b;
+  if (!tint_tmp_4) {
+    tint_tmp_4 = d;
+  }
+  tint_tmp_2 = (tint_tmp_4);
+}
+foo((tint_tmp), (tint_tmp_1), (tint_tmp_2));
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_bitcast_test.cc b/src/tint/writer/glsl/generator_impl_bitcast_test.cc
new file mode 100644
index 0000000..1566730
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_bitcast_test.cc
@@ -0,0 +1,60 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Bitcast = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Float) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "intBitsToFloat(1)");
+}
+
+TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.i32(), Expr(1u));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "int(1u)");
+}
+
+TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "uint(1)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_block_test.cc b/src/tint/writer/glsl/generator_impl_block_test.cc
new file mode 100644
index 0000000..8a9a504
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_block_test.cc
@@ -0,0 +1,42 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Block = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Block, Emit_Block) {
+  auto* b = Block(create<ast::DiscardStatement>());
+  WrapInFunction(b);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    discard;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_break_test.cc b/src/tint/writer/glsl/generator_impl_break_test.cc
new file mode 100644
index 0000000..32a2a65
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_break_test.cc
@@ -0,0 +1,39 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Break = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Break, Emit_Break) {
+  auto* b = create<ast::BreakStatement>();
+  WrapInFunction(Loop(Block(b)));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), "  break;\n");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_builtin_test.cc b/src/tint/writer/glsl/generator_impl_builtin_test.cc
new file mode 100644
index 0000000..1a6f789
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_builtin_test.cc
@@ -0,0 +1,836 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using BuiltinType = sem::BuiltinType;
+
+using ::testing::HasSubstr;
+
+using GlslGeneratorImplTest_Builtin = TestHelper;
+
+enum class ParamType {
+  kF32,
+  kU32,
+  kBool,
+};
+
+struct BuiltinData {
+  BuiltinType builtin;
+  ParamType type;
+  const char* glsl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.glsl_name;
+  switch (data.type) {
+    case ParamType::kF32:
+      out << "f32";
+      break;
+    case ParamType::kU32:
+      out << "u32";
+      break;
+    case ParamType::kBool:
+      out << "bool";
+      break;
+  }
+  out << ">";
+  return out;
+}
+
+const ast::CallExpression* GenerateCall(BuiltinType builtin,
+                                        ParamType type,
+                                        ProgramBuilder* builder) {
+  std::string name;
+  std::ostringstream str(name);
+  str << builtin;
+  switch (builtin) {
+    case BuiltinType::kAcos:
+    case BuiltinType::kAsin:
+    case BuiltinType::kAtan:
+    case BuiltinType::kCeil:
+    case BuiltinType::kCos:
+    case BuiltinType::kCosh:
+    case BuiltinType::kDpdx:
+    case BuiltinType::kDpdxCoarse:
+    case BuiltinType::kDpdxFine:
+    case BuiltinType::kDpdy:
+    case BuiltinType::kDpdyCoarse:
+    case BuiltinType::kDpdyFine:
+    case BuiltinType::kExp:
+    case BuiltinType::kExp2:
+    case BuiltinType::kFloor:
+    case BuiltinType::kFract:
+    case BuiltinType::kFwidth:
+    case BuiltinType::kFwidthCoarse:
+    case BuiltinType::kFwidthFine:
+    case BuiltinType::kInverseSqrt:
+    case BuiltinType::kIsFinite:
+    case BuiltinType::kIsInf:
+    case BuiltinType::kIsNan:
+    case BuiltinType::kIsNormal:
+    case BuiltinType::kLength:
+    case BuiltinType::kLog:
+    case BuiltinType::kLog2:
+    case BuiltinType::kNormalize:
+    case BuiltinType::kRound:
+    case BuiltinType::kSin:
+    case BuiltinType::kSinh:
+    case BuiltinType::kSqrt:
+    case BuiltinType::kTan:
+    case BuiltinType::kTanh:
+    case BuiltinType::kTrunc:
+    case BuiltinType::kSign:
+      return builder->Call(str.str(), "f2");
+    case BuiltinType::kLdexp:
+      return builder->Call(str.str(), "f2", "i2");
+    case BuiltinType::kAtan2:
+    case BuiltinType::kDot:
+    case BuiltinType::kDistance:
+    case BuiltinType::kPow:
+    case BuiltinType::kReflect:
+    case BuiltinType::kStep:
+      return builder->Call(str.str(), "f2", "f2");
+    case BuiltinType::kCross:
+      return builder->Call(str.str(), "f3", "f3");
+    case BuiltinType::kFma:
+    case BuiltinType::kMix:
+    case BuiltinType::kFaceForward:
+    case BuiltinType::kSmoothStep:
+      return builder->Call(str.str(), "f2", "f2", "f2");
+    case BuiltinType::kAll:
+    case BuiltinType::kAny:
+      return builder->Call(str.str(), "b2");
+    case BuiltinType::kAbs:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2");
+      } else {
+        return builder->Call(str.str(), "u2");
+      }
+    case BuiltinType::kCountOneBits:
+    case BuiltinType::kReverseBits:
+      return builder->Call(str.str(), "u2");
+    case BuiltinType::kMax:
+    case BuiltinType::kMin:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2");
+      }
+    case BuiltinType::kClamp:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2", "u2");
+      }
+    case BuiltinType::kSelect:
+      return builder->Call(str.str(), "f2", "f2", "b2");
+    case BuiltinType::kDeterminant:
+      return builder->Call(str.str(), "m2x2");
+    case BuiltinType::kTranspose:
+      return builder->Call(str.str(), "m3x2");
+    default:
+      break;
+  }
+  return nullptr;
+}
+using GlslBuiltinTest = TestParamHelper<BuiltinData>;
+TEST_P(GlslBuiltinTest, Emit) {
+  auto param = GetParam();
+
+  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
+  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
+  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
+  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = GenerateCall(param.builtin, param.type, this);
+  ASSERT_NE(nullptr, call) << "Unhandled builtin";
+  Func("func", {}, ty.void_(), {CallStmt(call)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem = program->Sem().Get(call);
+  ASSERT_NE(sem, nullptr);
+  auto* target = sem->Target();
+  ASSERT_NE(target, nullptr);
+  auto* builtin = target->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+
+  EXPECT_EQ(gen.generate_builtin_name(builtin), param.glsl_name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest_Builtin,
+    GlslBuiltinTest,
+    testing::Values(
+        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "abs"},
+        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
+        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
+        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
+        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
+        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
+        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
+        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan"},
+        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
+        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
+        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
+        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "countbits"},
+        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
+        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
+        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
+        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
+        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "ddx"},
+        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "ddx_coarse"},
+        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "ddx_fine"},
+        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "ddy"},
+        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "ddy_coarse"},
+        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "ddy_fine"},
+        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
+        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
+        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
+        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
+        BuiltinData{BuiltinType::kFma, ParamType::kF32, "mad"},
+        BuiltinData{BuiltinType::kFract, ParamType::kF32, "frac"},
+        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
+        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
+        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
+        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
+        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
+        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
+        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
+        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
+        BuiltinData{BuiltinType::kMax, ParamType::kF32, "max"},
+        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
+        BuiltinData{BuiltinType::kMin, ParamType::kF32, "min"},
+        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
+        BuiltinData{BuiltinType::kMix, ParamType::kF32, "mix"},
+        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
+        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
+        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
+        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reversebits"},
+        BuiltinData{BuiltinType::kRound, ParamType::kU32, "round"},
+        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
+        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
+        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
+        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
+        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
+        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
+        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
+        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
+        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
+        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"}));
+
+TEST_F(GlslGeneratorImplTest_Builtin, DISABLED_Builtin_IsNormal) {
+  FAIL();
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Builtin_Call) {
+  auto* call = Call("dot", "param1", "param2");
+
+  Global("param1", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("param2", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "dot(param1, param2)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Select_Scalar) {
+  auto* call = Call("select", 1.0f, 2.0f, true);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "(true ? 2.0f : 1.0f)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Select_Vector) {
+  auto* call =
+      Call("select", vec2<i32>(1, 2), vec2<i32>(3, 4), vec2<bool>(true, false));
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "mix(ivec2(1, 2), ivec2(3, 4), bvec2(true, false))");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Scalar) {
+  auto* call = Call("modf", 1.0f);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct modf_result {
+  float fract;
+  float whole;
+};
+
+modf_result tint_modf(float param_0) {
+  modf_result result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+
+void test_function() {
+  tint_modf(1.0f);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Vector) {
+  auto* call = Call("modf", vec3<f32>());
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct modf_result_vec3 {
+  vec3 fract;
+  vec3 whole;
+};
+
+modf_result_vec3 tint_modf(vec3 param_0) {
+  modf_result_vec3 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+
+void test_function() {
+  tint_modf(vec3(0.0f, 0.0f, 0.0f));
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
+  auto* call = Call("frexp", 1.0f);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
+  float sig;
+  int exp;
+};
+
+frexp_result tint_frexp(float param_0) {
+  frexp_result result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+
+void test_function() {
+  tint_frexp(1.0f);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
+  auto* call = Call("frexp", vec3<f32>());
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
+
+struct frexp_result_vec3 {
+  vec3 sig;
+  ivec3 exp;
+};
+
+frexp_result_vec3 tint_frexp(vec3 param_0) {
+  frexp_result_vec3 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+
+void test_function() {
+  tint_frexp(vec3(0.0f, 0.0f, 0.0f));
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, IsNormal_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("isNormal", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(ion 310 es
+
+bool tint_isNormal(float param_0) {
+  uint exponent = floatBitsToUint(param_0) & 0x7f80000u;
+  uint clamped = clamp(exponent, 0x0080000u, 0x7f00000u);
+  return clamped == exponent;
+}
+
+
+void test_function() {
+  float val = 0.0f;
+  bool tint_symbol = tint_isNormal(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, IsNormal_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("isNormal", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"( 310 es
+
+bvec3 tint_isNormal(vec3 param_0) {
+  uvec3 exponent = floatBitsToUint(param_0) & 0x7f80000u;
+  uvec3 clamped = clamp(exponent, 0x0080000u, 0x7f00000u);
+  return equal(clamped, exponent);
+}
+
+
+void test_function() {
+  vec3 val = vec3(0.0f, 0.0f, 0.0f);
+  bvec3 tint_symbol = tint_isNormal(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Degrees_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+float tint_degrees(float param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+
+void test_function() {
+  float val = 0.0f;
+  float tint_symbol = tint_degrees(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Degrees_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+vec3 tint_degrees(vec3 param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+
+void test_function() {
+  vec3 val = vec3(0.0f, 0.0f, 0.0f);
+  vec3 tint_symbol = tint_degrees(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Radians_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+float tint_radians(float param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+
+void test_function() {
+  float val = 0.0f;
+  float tint_symbol = tint_radians(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Radians_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+vec3 tint_radians(vec3 param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+
+void test_function() {
+  vec3 val = vec3(0.0f, 0.0f, 0.0f);
+  vec3 tint_symbol = tint_radians(val);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+#if 0
+TEST_F(GlslGeneratorImplTest_Builtin, Pack4x8Snorm) {
+  auto* call = Call("pack4x8snorm", "p1");
+  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("ivec4 tint_tmp = ivec4(round(clamp(p1, "
+                                      "-1.0, 1.0) * 127.0)) & 0xff;"));
+  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 8 | "
+                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Pack4x8Unorm) {
+  auto* call = Call("pack4x8unorm", "p1");
+  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uvec4 tint_tmp = uvec4(round(clamp(p1, "
+                                      "0.0, 1.0) * 255.0));"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 8 | "
+                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Snorm) {
+  auto* call = Call("pack2x16snorm", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int2 tint_tmp = int2(round(clamp(p1, "
+                                      "-1.0, 1.0) * 32767.0)) & 0xffff;"));
+  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 16)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Unorm) {
+  auto* call = Call("pack2x16unorm", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = uint2(round(clamp(p1, "
+                                      "0.0, 1.0) * 65535.0));"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Float) {
+  auto* call = Call("pack2x16float", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = f32tof16(p1);"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Unpack4x8Snorm) {
+  auto* call = Call("unpack4x8snorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
+  EXPECT_THAT(gen.result(),
+              HasSubstr("ivec4 tint_tmp = ivec4(tint_tmp_1 << 24, tint_tmp_1 "
+                        "<< 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;"));
+  EXPECT_THAT(out.str(),
+              HasSubstr("clamp(float4(tint_tmp) / 127.0, -1.0, 1.0)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Unpack4x8Unorm) {
+  auto* call = Call("unpack4x8unorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr("uvec4 tint_tmp = uvec4(tint_tmp_1 & 0xff, (tint_tmp_1 >> "
+                "8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);"));
+  EXPECT_THAT(out.str(), HasSubstr("float4(tint_tmp) / 255.0"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Snorm) {
+  auto* call = Call("unpack2x16snorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr("int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;"));
+  EXPECT_THAT(out.str(),
+              HasSubstr("clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Unorm) {
+  auto* call = Call("unpack2x16unorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
+  EXPECT_THAT(gen.result(),
+              HasSubstr("uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, "
+                        "tint_tmp_1 >> 16);"));
+  EXPECT_THAT(out.str(), HasSubstr("float2(tint_tmp) / 65535.0"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Float) {
+  auto* call = Call("unpack2x16float", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp = p1;"));
+  EXPECT_THAT(out.str(),
+              HasSubstr("f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16))"));
+}
+
+#endif
+
+TEST_F(GlslGeneratorImplTest_Builtin, StorageBarrier) {
+  Func("main", {}, ty.void_(), {CallStmt(Call("storageBarrier"))},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  { barrier(); memoryBarrierBuffer(); };
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
+  Func("main", {}, ty.void_(), {CallStmt(Call("workgroupBarrier"))},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  barrier();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, DotI32) {
+  Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(Call("dot", "v", "v")));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+int tint_int_dot(ivec3 a, ivec3 b) {
+  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
+}
+
+ivec3 v = ivec3(0, 0, 0);
+void test_function() {
+  tint_int_dot(v, v);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, DotU32) {
+  Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(Call("dot", "v", "v")));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+uint tint_int_dot(uvec3 a, uvec3 b) {
+  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
+}
+
+uvec3 v = uvec3(0u, 0u, 0u);
+void test_function() {
+  tint_int_dot(v, v);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
new file mode 100644
index 0000000..f06aee7
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
@@ -0,0 +1,304 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+struct ExpectedResult {
+  ExpectedResult(const char* o) : out(o) {}  // NOLINT
+
+  std::string pre;
+  std::string out;
+};
+
+ExpectedResult expected_texture_overload(
+    ast::builtin::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+    case ValidTextureOverload::kDimensions2d:
+    case ValidTextureOverload::kDimensionsDepth2d:
+    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+    case ValidTextureOverload::kDimensions2dArray:
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+    case ValidTextureOverload::kDimensions3d:
+    case ValidTextureOverload::kDimensionsCube:
+    case ValidTextureOverload::kDimensionsDepthCube:
+    case ValidTextureOverload::kDimensionsCubeArray:
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+    case ValidTextureOverload::kDimensions2dLevel:
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+    case ValidTextureOverload::kDimensions3dLevel:
+    case ValidTextureOverload::kDimensionsCubeLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return {"textureSize"};
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return {"imageSize"};
+    case ValidTextureOverload::kGather2dF32:
+      return R"(textureGather(tint_symbol_sampler, vec2(1.0f, 2.0f), 0))";
+    case ValidTextureOverload::kGather2dOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(3, 4), 0))";
+    case ValidTextureOverload::kGather2dArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0))";
+    case ValidTextureOverload::kGather2dArrayOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5), 0))";
+    case ValidTextureOverload::kGatherCubeF32:
+      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 0))";
+    case ValidTextureOverload::kGatherCubeArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0))";
+    case ValidTextureOverload::kGatherDepth2dF32:
+      return R"(textureGather(tint_symbol_sampler, vec2(1.0f, 2.0f), 0.0))";
+    case ValidTextureOverload::kGatherDepth2dOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 0.0, ivec2(3, 4))";
+    case ValidTextureOverload::kGatherDepth2dArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0.0))";
+    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0.0, ivec2(4, 5)))";
+    case ValidTextureOverload::kGatherDepthCubeF32:
+      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 0.0))";
+    case ValidTextureOverload::kGatherDepthCubeArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0.0))";
+    case ValidTextureOverload::kGatherCompareDepth2dF32:
+      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f))";
+    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5)))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 4.0f)))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
+      return R"(textureGatherOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 4.0f), ivec2(5, 6)))";
+    case ValidTextureOverload::kGatherCompareDepthCubeF32:
+      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f)))";
+    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
+      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f))";
+    case ValidTextureOverload::kNumLayers2dArray:
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+    case ValidTextureOverload::kNumLayersCubeArray:
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+      return {"textureSize"};
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return {"imageSize"};
+    case ValidTextureOverload::kNumLevels2d:
+    case ValidTextureOverload::kNumLevelsCube:
+    case ValidTextureOverload::kNumLevelsDepth2d:
+    case ValidTextureOverload::kNumLevelsDepthCube:
+    case ValidTextureOverload::kNumLevels2dArray:
+    case ValidTextureOverload::kNumLevels3d:
+    case ValidTextureOverload::kNumLevelsCubeArray:
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return {"textureQueryLevels"};
+    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+      return {"textureSamples"};
+    case ValidTextureOverload::kSample1dF32:
+      return R"(texture(tint_symbol_sampler, 1.0f);)";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(texture(tint_symbol_sampler, vec2(1.0f, 2.0f));)";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(3, 4));)";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)));)";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5));)";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(4, 5, 6));)";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)));)";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 0.0f));)";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 0.0f), ivec2(3, 4));)";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 0.0f));)";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 0.0f), ivec2(4, 5));)";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f));)";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0.0f);)";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(texture(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(4, 5), 3.0f);)";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, float(4)), 3.0f);)";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(5, 6), 4.0f);)";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(5, 6, 7), 4.0f);)";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(textureLod(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(textureLodOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(textureLod(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(textureLod(tint_symbol_sampler, vec2(1.0f, 2.0f), 3).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(textureLodOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3, ivec2(4, 5)).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4, ivec2(5, 6)).x;)";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4).x;)";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(textureLod(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5).x;)";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(textureGrad(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(textureGradOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7, 7));)";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(textureGradOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f), ivec2(6, 7));)";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(textureGradOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f), ivec3(0, 1, 2));)";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(textureGrad(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), vec3(5.0f, 6.0f, 7.0f), vec3(8.0f, 9.0f, 10.0f));)";
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4), 3.0f));)";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4), 3.0f), ivec2(5, 6));)";
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)";
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
+      return R"(yyytexture(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
+      return R"(yyytextureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4)), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
+      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(4)), 3.0f, ivec2(5, 6));)";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
+      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
+      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kLoad1dLevelF32:
+    case ValidTextureOverload::kLoad1dLevelU32:
+    case ValidTextureOverload::kLoad1dLevelI32:
+      return R"(texelFetch(tint_symbol_2, 1, 3);)";
+    case ValidTextureOverload::kLoad2dLevelF32:
+    case ValidTextureOverload::kLoad2dLevelU32:
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+    case ValidTextureOverload::kLoad3dLevelF32:
+    case ValidTextureOverload::kLoad3dLevelU32:
+    case ValidTextureOverload::kLoad3dLevelI32:
+      return R"(texelFetch(tint_symbol_2, ivec3(1, 2, 3), 4);)";
+    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return R"(texelFetch(tint_symbol_2, ivec3(1, 2, 3), 4);)";
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+      return R"(imageStore(tint_symbol, 1, vec4(2.0f, 3.0f, 4.0f, 5.0f));)";
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+      return R"(imageStore(tint_symbol, ivec2(1, 2), vec4(3.0f, 4.0f, 5.0f, 6.0f));)";
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return R"(imageStore(tint_symbol, ivec3(1, 2, 3), vec4(4.0f, 5.0f, 6.0f, 7.0f));)";
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return R"(imageStore(tint_symbol, ivec3(1, 2, 3), vec4(4.0f, 5.0f, 6.0f, 7.0f));)";
+  }
+  return "<unmatched texture overload>";
+}  // NOLINT - Ignore the length of this function
+
+class GlslGeneratorBuiltinTextureTest
+    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
+
+TEST_P(GlslGeneratorBuiltinTextureTest, Call) {
+  auto param = GetParam();
+
+  param.BuildTextureVariable(this);
+  param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  auto* stmt = CallStmt(call);
+
+  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto expected = expected_texture_overload(param.overload);
+
+  EXPECT_THAT(gen.result(), HasSubstr(expected.pre));
+  EXPECT_THAT(gen.result(), HasSubstr(expected.out));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorBuiltinTextureTest,
+    GlslGeneratorBuiltinTextureTest,
+    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_call_test.cc b/src/tint/writer/glsl/generator_impl_call_test.cc
new file mode 100644
index 0000000..bf1fefa
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_call_test.cc
@@ -0,0 +1,81 @@
+// 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/ast/call_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Call = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
+  Func("my_func", {}, ty.f32(), {Return(1.23f)});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func()");
+}
+
+TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.f32(), {Return(1.23f)});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func(param1, param2)");
+}
+
+TEST_F(GlslGeneratorImplTest_Call, EmitStatement_Call) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = CallStmt(Call("my_func", "param1", "param2"));
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(call)) << gen.error();
+  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_case_test.cc b/src/tint/writer/glsl/generator_impl_case_test.cc
new file mode 100644
index 0000000..9814ff0
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_case_test.cc
@@ -0,0 +1,108 @@
+// 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/ast/fallthrough_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Case = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Case, Emit_Case) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) {
+  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::FallthroughStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    /* fallthrough */
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
+  auto* s =
+      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
+             DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5:
+  case 6: {
+    break;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Case, Emit_Case_Default) {
+  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  default: {
+    break;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_cast_test.cc b/src/tint/writer/glsl/generator_impl_cast_test.cc
new file mode 100644
index 0000000..21fb8ba
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_cast_test.cc
@@ -0,0 +1,49 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Cast = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) {
+  auto* cast = Construct<f32>(1);
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float(1)");
+}
+
+TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
+  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "vec3(ivec3(1, 2, 3))");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_constructor_test.cc b/src/tint/writer/glsl/generator_impl_constructor_test.cc
new file mode 100644
index 0000000..4d477bb
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_constructor_test.cc
@@ -0,0 +1,241 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using GlslGeneratorImplTest_Constructor = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Bool) {
+  WrapInFunction(Expr(false));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("false"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
+  WrapInFunction(Expr(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
+  WrapInFunction(Expr(56779u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
+  // Use a number close to 1<<30 but whose decimal representation ends in 0.
+  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
+  WrapInFunction(Construct<f32>(-1.2e-5f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
+  WrapInFunction(Construct<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
+  WrapInFunction(Construct<i32>(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
+  WrapInFunction(Construct<u32>(12345u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
+  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("vec3(1.0f, 2.0f, 3.0f)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
+  WrapInFunction(vec3<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("vec3(0.0f, 0.0f, 0.0f)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Float) {
+  WrapInFunction(vec3<f32>(2.0f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("vec3(2.0f)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Bool) {
+  WrapInFunction(vec3<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bvec3(true)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Int) {
+  WrapInFunction(vec3<i32>(2));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("ivec3(2)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_UInt) {
+  WrapInFunction(vec3<u32>(2u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uvec3(2u)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
+  WrapInFunction(
+      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr("mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(3.0f, 4.0f, 5.0f))"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat_Empty) {
+  WrapInFunction(mat2x3<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  EXPECT_THAT(gen.result(),
+              HasSubstr("mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
+  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3),
+                           vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f),
+                           vec3<f32>(7.f, 8.f, 9.f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("vec3[3](vec3(1.0f, 2.0f, 3.0f), "
+                                      "vec3(4.0f, 5.0f, 6.0f), "
+                                      "vec3(7.0f, 8.0f, 9.0f))"));
+}
+
+// TODO(bclayton): Zero-init arrays
+TEST_F(GlslGeneratorImplTest_Constructor,
+       DISABLED_EmitConstructor_Type_Array_Empty) {
+  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("{vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f),"
+                        " vec3(0.0f, 0.0f, 0.0f)}"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("S(1, 2.0f, ivec3(3, 4, 5))"));
+}
+
+TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str)));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("S(0"));
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_continue_test.cc b/src/tint/writer/glsl/generator_impl_continue_test.cc
new file mode 100644
index 0000000..675b408
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_continue_test.cc
@@ -0,0 +1,46 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Continue = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Continue, Emit_Continue) {
+  auto* loop = Loop(Block(If(false, Block(Break())),  //
+                          Continue()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    if (false) {
+      break;
+    }
+    continue;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_discard_test.cc b/src/tint/writer/glsl/generator_impl_discard_test.cc
new file mode 100644
index 0000000..f218c9e
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_discard_test.cc
@@ -0,0 +1,39 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Discard = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Discard, Emit_Discard) {
+  auto* stmt = create<ast::DiscardStatement>();
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  discard;\n");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
new file mode 100644
index 0000000..c2edb1d
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -0,0 +1,1003 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Function = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Function) {
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #version 310 es
+
+  void my_func() {
+    return;
+  }
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
+  Func("centroid", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(  void tint_symbol() {
+    return;
+  })"));
+}
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithParams) {
+  Func("my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
+       ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #version 310 es
+
+  void my_func(float a, int b) {
+    return;
+  }
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_NoReturn_Void) {
+  Func("func", ast::VariableList{}, ty.void_(), {/* no explicit return */},
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+void func() {
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function, PtrParameter) {
+  // fn f(foo : ptr<function, f32>) -> f32 {
+  //   return *foo;
+  // }
+  Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))},
+       ty.f32(), {Return(Deref("foo"))});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) {
+  return foo;
+}
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithInOutVars) {
+  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
+  //   return foo;
+  // }
+  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
+  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+layout(location = 0) in float foo_1;
+layout(location = 1) out float value;
+float frag_main(float foo) {
+  return foo;
+}
+
+void main() {
+  float inner_result = frag_main(foo_1);
+  value = inner_result;
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithInOut_Builtins) {
+  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
+  //   return coord.x;
+  // }
+  auto* coord_in =
+      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
+       {Return(MemberAccessor("coord", "x"))},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kFragDepth)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+float frag_main(vec4 coord) {
+  return coord.x;
+}
+
+void main() {
+  float inner_result = frag_main(gl_FragCoord);
+  gl_FragDepth = inner_result;
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
+  // struct Interface {
+  //   @builtin(position) pos : vec4<f32>;
+  //   @location(1) col1 : f32;
+  //   @location(2) col2 : f32;
+  // };
+  // fn vert_main() -> Interface {
+  //   return Interface(vec4<f32>(), 0.4, 0.6);
+  // }
+  // fn frag_main(inputs : Interface) {
+  //   const r = inputs.col1;
+  //   const g = inputs.col2;
+  //   const p = inputs.pos;
+  // }
+  auto* interface_struct = Structure(
+      "Interface",
+      {
+          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+          Member("col1", ty.f32(), {Location(1)}),
+          Member("col2", ty.f32(), {Location(2)}),
+      });
+
+  Func("vert_main", {}, ty.Of(interface_struct),
+       {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
+                         Expr(0.5f), Expr(0.25f)))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
+       {
+           Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))),
+           Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))),
+           Decl(Const("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+layout(location = 1) out float col1_1;
+layout(location = 2) out float col2_1;
+layout(location = 1) in float col1_2;
+layout(location = 2) in float col2_2;
+struct Interface {
+  vec4 pos;
+  float col1;
+  float col2;
+};
+
+Interface vert_main() {
+  Interface tint_symbol = Interface(vec4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f);
+  return tint_symbol;
+}
+
+void main() {
+  Interface inner_result = vert_main();
+  gl_Position = inner_result.pos;
+  col1_1 = inner_result.col1;
+  col2_1 = inner_result.col2;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
+void frag_main(Interface inputs) {
+  float r = inputs.col1;
+  float g = inputs.col2;
+  vec4 p = inputs.pos;
+}
+
+void main_1() {
+  Interface tint_symbol_1 = Interface(gl_FragCoord, col1_2, col2_2);
+  frag_main(tint_symbol_1);
+  return;
+}
+)");
+}
+
+#if 0
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
+  // struct VertexOutput {
+  //   @builtin(position) pos : vec4<f32>;
+  // };
+  // fn foo(x : f32) -> VertexOutput {
+  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
+  // }
+  // fn vert_main1() -> VertexOutput {
+  //   return foo(0.5);
+  // }
+  // fn vert_main2() -> VertexOutput {
+  //   return foo(0.25);
+  // }
+  auto* vertex_output_struct = Structure(
+      "VertexOutput",
+      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+
+  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
+       {});
+
+  Func("vert_main1", {}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Expr(Call("foo", Expr(0.5f)))))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("vert_main2", {}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Expr(Call("foo", Expr(0.25f)))))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct VertexOutput {
+  float4 pos;
+};
+
+VertexOutput foo(float x) {
+  const VertexOutput tint_symbol_4 = {float4(x, x, x, 1.0f)};
+  return tint_symbol_4;
+}
+
+struct tint_symbol {
+  float4 pos : SV_Position;
+};
+
+tint_symbol vert_main1() {
+  const VertexOutput tint_symbol_1 = {foo(0.5f)};
+  const tint_symbol tint_symbol_5 = {tint_symbol_1.pos};
+  return tint_symbol_5;
+}
+
+struct tint_symbol_2 {
+  float4 pos : SV_Position;
+};
+
+tint_symbol_2 vert_main2() {
+  const VertexOutput tint_symbol_3 = {foo(0.25f)};
+  const tint_symbol_2 tint_symbol_6 = {tint_symbol_3.pos};
+  return tint_symbol_6;
+}
+)");
+}
+#endif
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
+  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
+                           {create<ast::StructBlockAttribute>()});
+  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(1),
+                     });
+
+  Func("sub_func",
+       {
+           Param("param", ty.f32()),
+       },
+       ty.f32(),
+       {
+           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", {}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct UBO {
+  vec4 coord;
+};
+
+layout(binding = 0) uniform UBO_1 {
+  vec4 coord;
+} ubo;
+
+float sub_func(float param) {
+  return ubo.coord.x;
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_UniformStruct) {
+  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("uniforms", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct Uniforms {
+  vec4 coord;
+};
+
+layout(binding = 0) uniform Uniforms_1 {
+  vec4 coord;
+} uniforms;
+
+void frag_main() {
+  float v = uniforms.coord.x;
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  int a;
+  float b;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  int a;
+  float b;
+} coord;
+void frag_main() {
+  float v = coord.b;
+  return;
+}
+
+void main() {
+  frag_main();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  int a;
+  float b;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  int a;
+  float b;
+} coord;
+void frag_main() {
+  float v = coord.b;
+  return;
+}
+
+void main() {
+  frag_main();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  int a;
+  float b;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  int a;
+  float b;
+} coord;
+void frag_main() {
+  coord.b = 2.0f;
+  return;
+}
+
+void main() {
+  frag_main();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  int a;
+  float b;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  int a;
+  float b;
+} coord;
+void frag_main() {
+  coord.b = 2.0f;
+  return;
+}
+
+void main() {
+  frag_main();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
+  auto* s = Structure("S", {Member("x", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("coord", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
+       {
+           Return(MemberAccessor("coord", "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+struct S {
+  float x;
+};
+
+layout(binding = 0) uniform S_1 {
+  float x;
+} coord;
+
+float sub_func(float param) {
+  return coord.x;
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
+  auto* s = Structure("S", {Member("x", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
+       {
+           Return(MemberAccessor("coord", "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(#version 310 es
+precision mediump float;
+
+struct S {
+  float x;
+};
+
+layout(binding = 0, std430) buffer S_1 {
+  float x;
+} coord;
+float sub_func(float param) {
+  return coord.x;
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+
+void main() {
+  frag_main();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithNameCollision) {
+  Func("centroid", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+precision mediump float;
+
+void tint_symbol() {
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(2, 4, 6),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+layout(local_size_x = 2, local_size_y = 4, local_size_z = 6) in;
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) {
+  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
+  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
+  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize("width", "height", "depth"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+const int width = int(2);
+const int height = int(3);
+const int depth = int(4);
+layout(local_size_x = 2, local_size_y = 3, local_size_z = 4) in;
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
+  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
+  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
+  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize("width", "height", "depth"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+#ifndef WGSL_SPEC_CONSTANT_7
+#define WGSL_SPEC_CONSTANT_7 int(2)
+#endif
+const int width = WGSL_SPEC_CONSTANT_7;
+#ifndef WGSL_SPEC_CONSTANT_8
+#define WGSL_SPEC_CONSTANT_8 int(3)
+#endif
+const int height = WGSL_SPEC_CONSTANT_8;
+#ifndef WGSL_SPEC_CONSTANT_9
+#define WGSL_SPEC_CONSTANT_9 int(4)
+#endif
+const int depth = WGSL_SPEC_CONSTANT_9;
+layout(local_size_x = WGSL_SPEC_CONSTANT_7, local_size_y = WGSL_SPEC_CONSTANT_8, local_size_z = WGSL_SPEC_CONSTANT_9) in;
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
+  Func("my_func", ast::VariableList{Param("a", ty.array<f32, 5>())}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+void my_func(float a[5]) {
+  return;
+}
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
+  Func("my_func", {}, ty.array<f32, 5>(),
+       {
+           Return(Construct(ty.array<f32, 5>())),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+float[5] my_func() {
+  return float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+)");
+}
+
+// https://crbug.com/tint/297
+TEST_F(GlslGeneratorImplTest_Function,
+       Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
+  // [[block]] struct Data {
+  //   d : f32;
+  // };
+  // @binding(0) @group(0) var<storage> data : Data;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn a() {
+  //   var v = data.d;
+  //   return;
+  // }
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn b() {
+  //   var v = data.d;
+  //   return;
+  // }
+
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("a", ast::VariableList{}, ty.void_(),
+         {
+             Decl(var),
+             Return(),
+         },
+         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  }
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("b", ast::VariableList{}, ty.void_(),
+         {
+             Decl(var),
+             Return(),
+         },
+         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  }
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct Data {
+  float d;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  float d;
+} data;
+void a() {
+  float v = data.d;
+  return;
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  a();
+  return;
+}
+void b() {
+  float v = data.d;
+  return;
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main_1() {
+  b();
+  return;
+}
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_identifier_test.cc b/src/tint/writer/glsl/generator_impl_identifier_test.cc
new file mode 100644
index 0000000..cf86f65
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_identifier_test.cc
@@ -0,0 +1,40 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Identifier = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Identifier, EmitIdentifierExpression) {
+  Global("foo", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* i = Expr("foo");
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
+  EXPECT_EQ(out.str(), "foo");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_if_test.cc b/src/tint/writer/glsl/generator_impl_if_test.cc
new file mode 100644
index 0000000..e44a2bc
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_if_test.cc
@@ -0,0 +1,135 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_If = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_If, Emit_If) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body);
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElseIf) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElse) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    return;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_If, Emit_IfWithMultiple) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+
+  auto* else_body = Block(Return());
+
+  auto* else_body_2 = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body,
+               ast::ElseStatementList{
+                   create<ast::ElseStatement>(else_cond, else_body),
+                   create<ast::ElseStatement>(nullptr, else_body_2),
+               });
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    } else {
+      return;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_import_test.cc b/src/tint/writer/glsl/generator_impl_import_test.cc
new file mode 100644
index 0000000..3a65396
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_import_test.cc
@@ -0,0 +1,282 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Import = TestHelper;
+
+struct GlslImportData {
+  const char* name;
+  const char* glsl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, GlslImportData data) {
+  out << data.name;
+  return out;
+}
+
+using GlslImportData_SingleParamTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_SingleParamTest, FloatScalar) {
+  auto param = GetParam();
+
+  auto* ident = Expr(param.name);
+  auto* expr = Call(ident, 1.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_SingleParamTest,
+                         testing::Values(GlslImportData{"abs", "abs"},
+                                         GlslImportData{"acos", "acos"},
+                                         GlslImportData{"asin", "asin"},
+                                         GlslImportData{"atan", "atan"},
+                                         GlslImportData{"cos", "cos"},
+                                         GlslImportData{"cosh", "cosh"},
+                                         GlslImportData{"ceil", "ceil"},
+                                         GlslImportData{"exp", "exp"},
+                                         GlslImportData{"exp2", "exp2"},
+                                         GlslImportData{"floor", "floor"},
+                                         GlslImportData{"fract", "frac"},
+                                         GlslImportData{"inverseSqrt", "rsqrt"},
+                                         GlslImportData{"length", "length"},
+                                         GlslImportData{"log", "log"},
+                                         GlslImportData{"log2", "log2"},
+                                         GlslImportData{"round", "round"},
+                                         GlslImportData{"sign", "sign"},
+                                         GlslImportData{"sin", "sin"},
+                                         GlslImportData{"sinh", "sinh"},
+                                         GlslImportData{"sqrt", "sqrt"},
+                                         GlslImportData{"tan", "tan"},
+                                         GlslImportData{"tanh", "tanh"},
+                                         GlslImportData{"trunc", "trunc"}));
+
+using GlslImportData_SingleIntParamTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_SingleIntParamTest, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, Expr(1));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_SingleIntParamTest,
+                         testing::Values(GlslImportData{"abs", "abs"}));
+
+using GlslImportData_SingleVectorParamTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_SingleVectorParamTest, FloatVector) {
+  auto param = GetParam();
+
+  auto* ident = Expr(param.name);
+  auto* expr = Call(ident, vec3<f32>(1.f, 2.f, 3.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            std::string(param.glsl_name) + "(vec3(1.0f, 2.0f, 3.0f))");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_SingleVectorParamTest,
+                         testing::Values(GlslImportData{"abs", "abs"},
+                                         GlslImportData{"acos", "acos"},
+                                         GlslImportData{"asin", "asin"},
+                                         GlslImportData{"atan", "atan"},
+                                         GlslImportData{"cos", "cos"},
+                                         GlslImportData{"cosh", "cosh"},
+                                         GlslImportData{"ceil", "ceil"},
+                                         GlslImportData{"exp", "exp"},
+                                         GlslImportData{"exp2", "exp2"},
+                                         GlslImportData{"floor", "floor"},
+                                         GlslImportData{"fract", "frac"},
+                                         GlslImportData{"inverseSqrt", "rsqrt"},
+                                         GlslImportData{"length", "length"},
+                                         GlslImportData{"log", "log"},
+                                         GlslImportData{"log2", "log2"},
+                                         GlslImportData{"normalize",
+                                                        "normalize"},
+                                         GlslImportData{"round", "round"},
+                                         GlslImportData{"sign", "sign"},
+                                         GlslImportData{"sin", "sin"},
+                                         GlslImportData{"sinh", "sinh"},
+                                         GlslImportData{"sqrt", "sqrt"},
+                                         GlslImportData{"tan", "tan"},
+                                         GlslImportData{"tanh", "tanh"},
+                                         GlslImportData{"trunc", "trunc"}));
+
+using GlslImportData_DualParam_ScalarTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_DualParam_ScalarTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1.f, 2.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_DualParam_ScalarTest,
+                         testing::Values(GlslImportData{"atan2", "atan"},
+                                         GlslImportData{"distance", "distance"},
+                                         GlslImportData{"max", "max"},
+                                         GlslImportData{"min", "min"},
+                                         GlslImportData{"pow", "pow"},
+                                         GlslImportData{"step", "step"}));
+
+using GlslImportData_DualParam_VectorTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_DualParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr =
+      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) +
+                           "(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f))");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_DualParam_VectorTest,
+                         testing::Values(GlslImportData{"atan2", "atan"},
+                                         GlslImportData{"cross", "cross"},
+                                         GlslImportData{"distance", "distance"},
+                                         GlslImportData{"max", "max"},
+                                         GlslImportData{"min", "min"},
+                                         GlslImportData{"pow", "pow"},
+                                         GlslImportData{"reflect", "reflect"},
+                                         GlslImportData{"step", "step"}));
+
+using GlslImportData_DualParam_Int_Test = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_DualParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_DualParam_Int_Test,
+                         testing::Values(GlslImportData{"max", "max"},
+                                         GlslImportData{"min", "min"}));
+
+using GlslImportData_TripleParam_ScalarTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_TripleParam_ScalarTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f, 3.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_TripleParam_ScalarTest,
+                         testing::Values(GlslImportData{"fma", "mad"},
+                                         GlslImportData{"mix", "mix"},
+                                         GlslImportData{"clamp", "clamp"},
+                                         GlslImportData{"smoothStep",
+                                                        "smoothstep"}));
+
+using GlslImportData_TripleParam_VectorTest = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_TripleParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
+                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(
+      out.str(),
+      std::string(param.glsl_name) +
+          R"((vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f)))");
+}
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest_Import,
+    GlslImportData_TripleParam_VectorTest,
+    testing::Values(GlslImportData{"faceForward", "faceforward"},
+                    GlslImportData{"fma", "mad"},
+                    GlslImportData{"clamp", "clamp"},
+                    GlslImportData{"smoothStep", "smoothstep"}));
+
+TEST_F(GlslGeneratorImplTest_Import, DISABLED_GlslImportData_FMix) {
+  FAIL();
+}
+
+using GlslImportData_TripleParam_Int_Test = TestParamHelper<GlslImportData>;
+TEST_P(GlslImportData_TripleParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2, 3);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2, 3)");
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
+                         GlslImportData_TripleParam_Int_Test,
+                         testing::Values(GlslImportData{"clamp", "clamp"}));
+
+TEST_F(GlslGeneratorImplTest_Import, GlslImportData_Determinant) {
+  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("determinant", "var");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string("determinant(var)"));
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_loop_test.cc b/src/tint/writer/glsl/generator_impl_loop_test.cc
new file mode 100644
index 0000000..bbe8749
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_loop_test.cc
@@ -0,0 +1,390 @@
+// 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/ast/variable_decl_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Loop = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_Loop) {
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block();
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard;
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard;
+    {
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* inner = Loop(body, continuing);
+
+  body = Block(inner);
+
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  continuing = Block(Assign(lhs, rhs));
+
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    while (true) {
+      discard;
+      {
+        a_statement();
+      }
+    }
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithVarUsedInContinuing) {
+  // loop {
+  //   var lhs : f32 = 2.4;
+  //   var other : f32;
+  //   break;
+  //   continuing {
+  //     lhs = rhs
+  //   }
+  // }
+
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
+                     Decl(Var("other", ty.f32())),            //
+                     Break());
+  auto* continuing = Block(Assign("lhs", "rhs"));
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    float lhs = 2.400000095f;
+    float other = 0.0f;
+    break;
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoop) {
+  // for(; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, nullptr, nullptr,  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    for(; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) {
+  // for(var i : i32; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr,  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    for(int i = 0; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
+  // for(var b = true && false; ; ) {
+  //   return;
+  // }
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr,
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    bool tint_tmp = true;
+    if (tint_tmp) {
+      tint_tmp = false;
+    }
+    bool b = (tint_tmp);
+    for(; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) {
+  // for(; true; ) {
+  //   return;
+  // }
+
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* f = For(nullptr, true, nullptr, Block(CallStmt(Call("a_statement"))));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    for(; true; ) {
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
+  // for(; true && false; ) {
+  //   return;
+  // }
+
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* f =
+      For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement"))));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    while (true) {
+      bool tint_tmp = true;
+      if (tint_tmp) {
+        tint_tmp = false;
+      }
+      if (!((tint_tmp))) { break; }
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) {
+  // for(; ; i = i + 1) {
+  //   return;
+  // }
+
+  auto* v = Decl(Var("i", ty.i32()));
+  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)),  //
+                Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    for(; ; i = (i + 1)) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
+  // for(; ; i = true && false) {
+  //   return;
+  // }
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* v = Decl(Var("i", ty.bool_()));
+  auto* f = For(nullptr, nullptr, Assign("i", multi_stmt),  //
+                Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    while (true) {
+      return;
+      bool tint_tmp = true;
+      if (tint_tmp) {
+        tint_tmp = false;
+      }
+      i = (tint_tmp);
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) {
+  // for(var i : i32; true; i = i + 1) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    for(int i = 0; true; i = (i + 1)) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
+  // for(var i = true && false; true && false; i = true && false) {
+  //   return;
+  // }
+
+  auto* multi_stmt_a = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+  auto* multi_stmt_b = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+  auto* multi_stmt_c = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+
+  auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b,
+                Assign("i", multi_stmt_c),  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    bool tint_tmp = true;
+    if (tint_tmp) {
+      tint_tmp = false;
+    }
+    bool i = (tint_tmp);
+    while (true) {
+      bool tint_tmp_1 = true;
+      if (tint_tmp_1) {
+        tint_tmp_1 = false;
+      }
+      if (!((tint_tmp_1))) { break; }
+      return;
+      bool tint_tmp_2 = true;
+      if (tint_tmp_2) {
+        tint_tmp_2 = false;
+      }
+      i = (tint_tmp_2);
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
new file mode 100644
index 0000000..b15343d
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -0,0 +1,886 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using create_type_func_ptr =
+    const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+
+inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.i32();
+}
+inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.u32();
+}
+inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.f32();
+}
+template <typename T>
+inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x4<T>();
+}
+
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+template <typename BASE>
+class GlslGeneratorImplTest_MemberAccessorBase : public BASE {
+ public:
+  void SetupStorageBuffer(ast::StructMemberList members) {
+    ProgramBuilder& b = *this;
+
+    auto* s =
+        b.Structure("Data", members, {b.create<ast::StructBlockAttribute>()});
+
+    b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage,
+             ast::Access::kReadWrite,
+             ast::AttributeList{
+                 b.create<ast::BindingAttribute>(0),
+                 b.create<ast::GroupAttribute>(1),
+             });
+  }
+
+  void SetupFunction(ast::StatementList statements) {
+    ProgramBuilder& b = *this;
+    b.Func("main", ast::VariableList{}, b.ty.void_(), statements,
+           ast::AttributeList{
+               b.Stage(ast::PipelineStage::kFragment),
+           });
+  }
+};
+
+using GlslGeneratorImplTest_MemberAccessor =
+    GlslGeneratorImplTest_MemberAccessorBase<TestHelper>;
+
+template <typename T>
+using GlslGeneratorImplTest_MemberAccessorWithParam =
+    GlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
+  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
+
+  auto* expr = MemberAccessor("str", "mem");
+  WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct Data {
+  float mem;
+};
+
+Data str = Data(0.0f);
+void test_function() {
+  float expr = str.mem;
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+struct TypeCase {
+  create_type_func_ptr member_type;
+  std::string expected;
+};
+inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
+  ProgramBuilder b;
+  auto* ty = c.member_type(b.ty);
+  out << ty->FriendlyName(b.Symbols());
+  return out;
+}
+
+using GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad =
+    GlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
+TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, Test) {
+  // struct Data {
+  //   a : i32;
+  //   b : <type>;
+  // };
+  // var<storage> data : Data;
+  // data.b;
+
+  auto p = GetParam();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", p.member_type(ty)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor("data", "b"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_MemberAccessor,
+                         GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad,
+                         testing::Values(TypeCase{ty_u32, "data.b"},
+                                         TypeCase{ty_f32, "data.b"},
+                                         TypeCase{ty_i32, "data.b"},
+                                         TypeCase{ty_vec2<u32>, "data.b"},
+                                         TypeCase{ty_vec2<f32>, "data.b"},
+                                         TypeCase{ty_vec2<i32>, "data.b"},
+                                         TypeCase{ty_vec3<u32>, "data.b"},
+                                         TypeCase{ty_vec3<f32>, "data.b"},
+                                         TypeCase{ty_vec3<i32>, "data.b"},
+                                         TypeCase{ty_vec4<u32>, "data.b"},
+                                         TypeCase{ty_vec4<f32>, "data.b"},
+                                         TypeCase{ty_vec4<i32>, "data.b"},
+                                         TypeCase{ty_mat2x2<f32>, "data.b"},
+                                         TypeCase{ty_mat2x3<f32>, "data.b"},
+                                         TypeCase{ty_mat2x4<f32>, "data.b"},
+                                         TypeCase{ty_mat3x2<f32>, "data.b"},
+                                         TypeCase{ty_mat3x3<f32>, "data.b"},
+                                         TypeCase{ty_mat3x4<f32>, "data.b"},
+                                         TypeCase{ty_mat4x2<f32>, "data.b"},
+                                         TypeCase{ty_mat4x3<f32>, "data.b"},
+                                         TypeCase{ty_mat4x4<f32>, "data.b"}));
+
+using GlslGeneratorImplTest_MemberAccessor_StorageBufferStore =
+    GlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
+TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferStore, Test) {
+  // struct Data {
+  //   a : i32;
+  //   b : <type>;
+  // };
+  // var<storage> data : Data;
+  // data.b = <type>();
+
+  auto p = GetParam();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", p.member_type(ty)),
+  });
+
+  SetupFunction({
+      Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
+               Construct(p.member_type(ty)))),
+      Assign(MemberAccessor("data", "b"), Expr("value")),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest_MemberAccessor,
+    GlslGeneratorImplTest_MemberAccessor_StorageBufferStore,
+    testing::Values(TypeCase{ty_u32, "data.b = value"},
+                    TypeCase{ty_f32, "data.b = value"},
+                    TypeCase{ty_i32, "data.b = value"},
+                    TypeCase{ty_vec2<u32>, "data.b = value"},
+                    TypeCase{ty_vec2<f32>, "data.b = value"},
+                    TypeCase{ty_vec2<i32>, "data.b = value"},
+                    TypeCase{ty_vec3<u32>, "data.b = value"},
+                    TypeCase{ty_vec3<f32>, "data.b = value"},
+                    TypeCase{ty_vec3<i32>, "data.b = value"},
+                    TypeCase{ty_vec4<u32>, "data.b = value"},
+                    TypeCase{ty_vec4<f32>, "data.b = value"},
+                    TypeCase{ty_vec4<i32>, "data.b = value"},
+                    TypeCase{ty_mat2x2<f32>, "data.b = value"},
+                    TypeCase{ty_mat2x3<f32>, "data.b = value"},
+                    TypeCase{ty_mat2x4<f32>, "data.b = value"},
+                    TypeCase{ty_mat3x2<f32>, "data.b = value"},
+                    TypeCase{ty_mat3x3<f32>, "data.b = value"},
+                    TypeCase{ty_mat3x4<f32>, "data.b = value"},
+                    TypeCase{ty_mat4x2<f32>, "data.b = value"},
+                    TypeCase{ty_mat4x3<f32>, "data.b = value"},
+                    TypeCase{ty_mat4x4<f32>, "data.b = value"}));
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) {
+  // struct Data {
+  //   z : f32;
+  //   a : mat2x3<f32>;
+  // };
+  // var<storage> data : Data;
+  // data.a = mat2x3<f32>();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", ty.mat2x3<f32>()),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor("data", "b"),
+             Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  int a;
+  mat2x3 b;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  int a;
+  mat2x3 b;
+} data;
+void tint_symbol() {
+  data.b = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_Matrix_Single_Element) {
+  // struct Data {
+  //   z : f32;
+  //   a : mat4x3<f32>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2][1];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.mat4x3<f32>()),
+  });
+
+  SetupFunction({
+      Decl(
+          Var("x", nullptr, ast::StorageClass::kNone,
+              IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2), 1))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  float z;
+  mat4x3 a;
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  float z;
+  mat4x3 a;
+} data;
+void tint_symbol() {
+  float x = data.a[2][1];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               IndexAccessor(MemberAccessor("data", "a"), 2))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  float z;
+  int a[5];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  float z;
+  int a[5];
+} data;
+void tint_symbol() {
+  int x = data.a[2];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[(2 + 4) - 3];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               IndexAccessor(MemberAccessor("data", "a"),
+                             Sub(Add(2, Expr(4)), Expr(3))))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  float z;
+  int a[5];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  float z;
+  int a[5];
+} data;
+void tint_symbol() {
+  int x = data.a[((2 + 4) - 3)];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2] = 2;
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Assign(IndexAccessor(MemberAccessor("data", "a"), 2), 2),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Data {
+  float z;
+  int a[5];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  float z;
+  int a[5];
+} data;
+void tint_symbol() {
+  data.a[2] = 2;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var(
+          "x", nullptr, ast::StorageClass::kNone,
+          MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  vec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  vec3 x = data.c[2].b;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Swizzle) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.xy
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor(
+                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                  "b"),
+                   "xy"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  vec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  vec2 x = data.c[2].b.xy;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {  // NOLINT
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.g
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor(
+                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                  "b"),
+                   "g"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  vec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  float x = data.c[2].b.g;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Index) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b[1]
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var(
+          "x", nullptr, ast::StorageClass::kNone,
+          IndexAccessor(MemberAccessor(
+                            IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
+                        1))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  vec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  float x = data.c[2].b[1];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b = vec3<f32>(1.f, 2.f, 3.f);
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
+             vec3<f32>(1.f, 2.f, 3.f)),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  vec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  data.c[2].b = vec3(1.0f, 2.0f, 3.0f);
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Store_Swizzle_SingleLetter) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.y = 1.f;
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<i32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor(
+                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                "b"),
+                 "y"),
+             Expr(1.f)),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(#version 310 es
+precision mediump float;
+
+struct Inner {
+  ivec3 a;
+  vec3 b;
+};
+
+struct Data {
+  Inner c[4];
+};
+
+layout(binding = 0, std430) buffer Data_1 {
+  Inner c[4];
+} data;
+void tint_symbol() {
+  data.c[2].b.y = 1.0f;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
+  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
+                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
+  auto* expr = MemberAccessor("my_vec", "xyz");
+  WrapInFunction(var, expr);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz"));
+}
+
+TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
+  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
+                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
+  auto* expr = MemberAccessor("my_vec", "gbr");
+  WrapInFunction(var, expr);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr"));
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_module_constant_test.cc b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
new file mode 100644
index 0000000..a636041
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
@@ -0,0 +1,92 @@
+// 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/ast/id_attribute.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_ModuleConstant = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) {
+  auto* var = Const("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
+  WrapInFunction(Decl(var));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), "const float pos[3] = float[3](1.0f, 2.0f, 3.0f);\n");
+}
+
+TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) {
+  auto* var = Override("pos", ty.f32(), Expr(3.0f),
+                       ast::AttributeList{
+                           Id(23),
+                       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+#define WGSL_SPEC_CONSTANT_23 3.0f
+#endif
+const float pos = WGSL_SPEC_CONSTANT_23;
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoConstructor) {
+  auto* var = Override("pos", ty.f32(), nullptr,
+                       ast::AttributeList{
+                           Id(23),
+                       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+#error spec constant required for constant id 23
+#endif
+const float pos = WGSL_SPEC_CONSTANT_23;
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoId) {
+  auto* a = Override("a", ty.f32(), Expr(3.0f),
+                     ast::AttributeList{
+                         Id(0),
+                     });
+  auto* b = Override("b", ty.f32(), Expr(2.0f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error();
+  ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
+#define WGSL_SPEC_CONSTANT_0 3.0f
+#endif
+const float a = WGSL_SPEC_CONSTANT_0;
+#ifndef WGSL_SPEC_CONSTANT_1
+#define WGSL_SPEC_CONSTANT_1 2.0f
+#endif
+const float b = WGSL_SPEC_CONSTANT_1;
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_return_test.cc b/src/tint/writer/glsl/generator_impl_return_test.cc
new file mode 100644
index 0000000..414f0e1
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_return_test.cc
@@ -0,0 +1,51 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Return = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Return, Emit_Return) {
+  auto* r = Return();
+  WrapInFunction(r);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return;\n");
+}
+
+TEST_F(GlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
+  auto* r = Return(123);
+  Func("f", {}, ty.i32(), {r});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return 123;\n");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
new file mode 100644
index 0000000..2d353ab
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
@@ -0,0 +1,338 @@
+// 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/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslSanitizerTest = TestHelper;
+
+TEST_F(GlslSanitizerTest, Call_ArrayLength) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+layout(binding = 1, std430) buffer my_struct_1 {
+  float a[];
+} b;
+void a_func() {
+  uint len = uint(b.a.length());
+}
+
+void main() {
+  a_func();
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
+  auto* s = Structure("my_struct",
+                      {
+                          Member(0, "z", ty.f32()),
+                          Member(4, "a", ty.array<f32>(4)),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+layout(binding = 1, std430) buffer my_struct_1 {
+  float z;
+  float a[];
+} b;
+void a_func() {
+  uint len = uint(b.a.length());
+}
+
+void main() {
+  a_func();
+  return;
+}
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  auto* p = Const("p", nullptr, AddressOf("b"));
+  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(p),
+           Decl(p2),
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", p2))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+layout(binding = 1, std430) buffer my_struct_1 {
+  float a[];
+} b;
+void a_func() {
+  uint len = uint(b.a.length());
+}
+
+void main() {
+  a_func();
+  return;
+}
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) {
+  auto* array_init = array<i32, 4>(1, 2, 3, 4);
+  auto* array_index = IndexAccessor(array_init, 3);
+  auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index);
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(pos),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+void tint_symbol() {
+  int tint_symbol_1[4] = int[4](1, 2, 3, 4);
+  int pos = tint_symbol_1[3];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.vec3<f32>()),
+                                 Member("c", ty.i32()),
+                             });
+  auto* struct_init = Construct(ty.Of(str), 1, vec3<f32>(2.f, 3.f, 4.f), 4);
+  auto* struct_access = MemberAccessor(struct_init, "b");
+  auto* pos =
+      Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(pos),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+struct S {
+  int a;
+  vec3 b;
+  int c;
+};
+
+void tint_symbol() {
+  S tint_symbol_1 = S(1, vec3(2.0f, 3.0f, 4.0f), 4);
+  vec3 pos = tint_symbol_1.b;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, InlinePtrLetsBasic) {
+  // var v : i32;
+  // let p : ptr<function, i32> = &v;
+  // let x : i32 = *p;
+  auto* v = Var("v", ty.i32());
+  auto* p =
+      Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
+  auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(v),
+           Decl(p),
+           Decl(x),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+void tint_symbol() {
+  int v = 0;
+  int x = v;
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(GlslSanitizerTest, InlinePtrLetsComplexChain) {
+  // var a : array<mat4x4<f32>, 4>;
+  // let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
+  // let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
+  // let vp : ptr<function, vec4<f32>> = &(*mp)[2];
+  // let v : vec4<f32> = *vp;
+  auto* a = Var("a", ty.array(ty.mat4x4<f32>(), 4));
+  auto* ap = Const(
+      "ap",
+      ty.pointer(ty.array(ty.mat4x4<f32>(), 4), ast::StorageClass::kFunction),
+      AddressOf(a));
+  auto* mp =
+      Const("mp", ty.pointer(ty.mat4x4<f32>(), ast::StorageClass::kFunction),
+            AddressOf(IndexAccessor(Deref(ap), 3)));
+  auto* vp =
+      Const("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
+            AddressOf(IndexAccessor(Deref(mp), 2)));
+  auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(ap),
+           Decl(mp),
+           Decl(vp),
+           Decl(v),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#version 310 es
+precision mediump float;
+
+void tint_symbol() {
+  mat4 a[4] = mat4[4](mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+  vec4 v = a[3][2];
+}
+
+void main() {
+  tint_symbol();
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc b/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
new file mode 100644
index 0000000..be4e7c8
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_storage_buffer_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.
+
+#include "gmock/gmock.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_StorageBuffer = TestHelper;
+
+void TestAlign(ProgramBuilder* ctx) {
+  // struct Nephews {
+  //   @align(256) huey  : f32;
+  //   @align(256) dewey : f32;
+  //   @align(256) louie : f32;
+  // };
+  // @group(0) @binding(0) var<storage, read_write> nephews : Nephews;
+  auto* nephews = ctx->Structure(
+      "Nephews",
+      {
+          ctx->Member("huey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
+          ctx->Member("dewey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
+          ctx->Member("louie", ctx->ty.f32(), {ctx->MemberAlign(256)}),
+      });
+  ctx->Global("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage,
+              ast::AttributeList{
+                  ctx->create<ast::BindingAttribute>(0),
+                  ctx->create<ast::GroupAttribute>(0),
+              });
+}
+
+TEST_F(GlslGeneratorImplTest_StorageBuffer, Align) {
+  TestAlign(this);
+
+  GeneratorImpl& gen = Build();
+
+  // TODO(crbug.com/tint/1421) offsets do not currently work on GLSL ES.
+  // They will likely require manual padding.
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct Nephews {
+  float huey;
+  float dewey;
+  float louie;
+};
+
+layout(binding = 0, std430) buffer Nephews_1 {
+  float huey;
+  float dewey;
+  float louie;
+} nephews;
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_StorageBuffer, Align_Desktop) {
+  TestAlign(this);
+
+  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 440
+
+struct Nephews {
+  float huey;
+  float dewey;
+  float louie;
+};
+
+layout(binding = 0, std430) buffer Nephews_1 {
+  float huey;
+  layout(offset=256) float dewey;
+  layout(offset=512) float louie;
+} nephews;
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_switch_test.cc b/src/tint/writer/glsl/generator_impl_switch_test.cc
new file mode 100644
index 0000000..f185871
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_switch_test.cc
@@ -0,0 +1,64 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_Switch = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Switch, Emit_Switch) {
+  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* def_body = Block(create<ast::BreakStatement>());
+  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+
+  ast::CaseSelectorList case_val;
+  case_val.push_back(Expr(5));
+
+  auto* case_body = Block(create<ast::BreakStatement>());
+
+  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
+
+  ast::CaseStatementList body;
+  body.push_back(case_stmt);
+  body.push_back(def);
+
+  auto* cond = Expr("cond");
+  auto* s = create<ast::SwitchStatement>(cond, body);
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  switch(cond) {
+    case 5: {
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_test.cc b/src/tint/writer/glsl/generator_impl_test.cc
new file mode 100644
index 0000000..fe5acb0
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_test.cc
@@ -0,0 +1,100 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest = TestHelper;
+
+TEST_F(GlslGeneratorImplTest, Generate) {
+  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
+       ast::AttributeList{});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+void my_func() {
+}
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest, GenerateDesktop) {
+  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
+       ast::AttributeList{});
+
+  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 440
+
+void my_func() {
+}
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest, GenerateSampleIndexES) {
+  Global(
+      "gl_SampleID", ty.i32(),
+      ast::AttributeList{Builtin(ast::Builtin::kSampleIndex),
+                         Disable(ast::DisabledValidation::kIgnoreStorageClass)},
+      ast::StorageClass::kInput);
+  Func("my_func", {}, ty.i32(),
+       ast::StatementList{Return(Expr("gl_SampleID"))});
+
+  GeneratorImpl& gen = Build(Version(Version::Standard::kES, 3, 1));
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+#extension GL_OES_sample_variables : require
+
+int my_func() {
+  return gl_SampleID;
+}
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest, GenerateSampleIndexDesktop) {
+  Global(
+      "gl_SampleID", ty.i32(),
+      ast::AttributeList{Builtin(ast::Builtin::kSampleIndex),
+                         Disable(ast::DisabledValidation::kIgnoreStorageClass)},
+      ast::StorageClass::kInput);
+  Func("my_func", {}, ty.i32(),
+       ast::StatementList{Return(Expr("gl_SampleID"))});
+
+  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 440
+
+int my_func() {
+  return gl_SampleID;
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_type_test.cc b/src/tint/writer/glsl/generator_impl_type_test.cc
new file mode 100644
index 0000000..183d768
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_type_test.cc
@@ -0,0 +1,575 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using GlslGeneratorImplTest_Type = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[4]");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
+  auto* arr = ty.array(ty.array<bool, 4>(), 5);
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[5][4]");
+}
+
+// TODO(dsinclair): Is this possible? What order should it output in?
+TEST_F(GlslGeneratorImplTest_Type,
+       DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 0);
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[5][4][1]");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool[4]");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Bool) {
+  auto* bool_ = create<sem::Bool>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_F32) {
+  auto* f32 = create<sem::F32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_I32) {
+  auto* i32 = create<sem::I32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "int");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Matrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "mat2x3");
+}
+
+// TODO(dsinclair): How to annotate as workgroup?
+TEST_F(GlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
+  auto* f32 = create<sem::F32>();
+  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
+                                 ast::Access::kReadWrite);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float*");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_StructDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  int a;
+  float b;
+};
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "S");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
+  auto* s = Structure("S", {
+                               Member("double", ty.i32()),
+                               Member("float", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct S {
+  int tint_symbol;
+  float tint_symbol_1;
+};
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32(), {MemberOffset(0)}),
+                          Member("b", ty.f32(), {MemberOffset(8)}),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  int a;
+  float b;
+};
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_U32) {
+  auto* u32 = create<sem::U32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "uint");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Vector) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "vec3");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitType_Void) {
+  auto* void_ = create<sem::Void>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "void");
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitSampler) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_FALSE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
+                            ast::Access::kReadWrite, ""))
+      << gen.error();
+}
+
+TEST_F(GlslGeneratorImplTest_Type, EmitSamplerComparison) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_FALSE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
+                            ast::Access::kReadWrite, ""))
+      << gen.error();
+}
+
+struct GlslDepthTextureData {
+  ast::TextureDimension dim;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, GlslDepthTextureData data) {
+  out << data.dim;
+  return out;
+}
+using GlslDepthTexturesTest = TestParamHelper<GlslDepthTextureData>;
+TEST_P(GlslDepthTexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* t = ty.depth_texture(params.dim);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest_Type,
+    GlslDepthTexturesTest,
+    testing::Values(GlslDepthTextureData{ast::TextureDimension::k2d,
+                                         "sampler2DShadow tex;"},
+                    GlslDepthTextureData{ast::TextureDimension::k2dArray,
+                                         "sampler2DArrayShadow tex;"},
+                    GlslDepthTextureData{ast::TextureDimension::kCube,
+                                         "samplerCubeShadow tex;"},
+                    GlslDepthTextureData{ast::TextureDimension::kCubeArray,
+                                         "samplerCubeArrayShadow tex;"}));
+
+using GlslDepthMultisampledTexturesTest = TestHelper;
+TEST_F(GlslDepthMultisampledTexturesTest, Emit) {
+  auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("sampler2DMS tex;"));
+}
+
+enum class TextureDataType { F32, U32, I32 };
+struct GlslSampledTextureData {
+  ast::TextureDimension dim;
+  TextureDataType datatype;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                GlslSampledTextureData data) {
+  out << data.dim;
+  return out;
+}
+using GlslSampledTexturesTest = TestParamHelper<GlslSampledTextureData>;
+TEST_P(GlslSampledTexturesTest, Emit) {
+  auto params = GetParam();
+
+  const ast::Type* datatype = nullptr;
+  switch (params.datatype) {
+    case TextureDataType::F32:
+      datatype = ty.f32();
+      break;
+    case TextureDataType::U32:
+      datatype = ty.u32();
+      break;
+    case TextureDataType::I32:
+      datatype = ty.i32();
+      break;
+  }
+  auto* t = ty.sampled_texture(params.dim, datatype);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Type,
+                         GlslSampledTexturesTest,
+                         testing::Values(
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k1d,
+                                 TextureDataType::F32,
+                                 "sampler1D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2d,
+                                 TextureDataType::F32,
+                                 "sampler2D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2dArray,
+                                 TextureDataType::F32,
+                                 "sampler2DArray tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k3d,
+                                 TextureDataType::F32,
+                                 "sampler3D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCube,
+                                 TextureDataType::F32,
+                                 "samplerCube tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCubeArray,
+                                 TextureDataType::F32,
+                                 "samplerCubeArray tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k1d,
+                                 TextureDataType::U32,
+                                 "usampler1D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2d,
+                                 TextureDataType::U32,
+                                 "usampler2D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2dArray,
+                                 TextureDataType::U32,
+                                 "usampler2DArray tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k3d,
+                                 TextureDataType::U32,
+                                 "usampler3D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCube,
+                                 TextureDataType::U32,
+                                 "usamplerCube tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCubeArray,
+                                 TextureDataType::U32,
+                                 "usamplerCubeArray tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k1d,
+                                 TextureDataType::I32,
+                                 "isampler1D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2d,
+                                 TextureDataType::I32,
+                                 "isampler2D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k2dArray,
+                                 TextureDataType::I32,
+                                 "isampler2DArray tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::k3d,
+                                 TextureDataType::I32,
+                                 "isampler3D tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCube,
+                                 TextureDataType::I32,
+                                 "isamplerCube tex;",
+                             },
+                             GlslSampledTextureData{
+                                 ast::TextureDimension::kCubeArray,
+                                 TextureDataType::I32,
+                                 "isamplerCubeArray tex;",
+                             }));
+
+TEST_F(GlslGeneratorImplTest_Type, EmitMultisampledTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "uniform highp sampler2DMS");
+}
+
+struct GlslStorageTextureData {
+  ast::TextureDimension dim;
+  ast::TexelFormat imgfmt;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                GlslStorageTextureData data) {
+  return out << data.dim;
+}
+using GlslStorageTexturesTest = TestParamHelper<GlslStorageTextureData>;
+TEST_P(GlslStorageTexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(
+    GlslGeneratorImplTest_Type,
+    GlslStorageTexturesTest,
+    testing::Values(
+        GlslStorageTextureData{ast::TextureDimension::k1d,
+                               ast::TexelFormat::kRgba8Unorm, "image1D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2d,
+                               ast::TexelFormat::kRgba16Float, "image2D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2dArray,
+                               ast::TexelFormat::kR32Float,
+                               "image2DArray tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k3d,
+                               ast::TexelFormat::kRg32Float, "image3D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k1d,
+                               ast::TexelFormat::kRgba32Float, "image1D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2d,
+                               ast::TexelFormat::kRgba16Uint, "image2D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2dArray,
+                               ast::TexelFormat::kR32Uint, "image2DArray tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k3d,
+                               ast::TexelFormat::kRg32Uint, "image3D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k1d,
+                               ast::TexelFormat::kRgba32Uint, "image1D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2d,
+                               ast::TexelFormat::kRgba16Sint, "image2D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k2dArray,
+                               ast::TexelFormat::kR32Sint, "image2DArray tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k3d,
+                               ast::TexelFormat::kRg32Sint, "image3D tex;"},
+        GlslStorageTextureData{ast::TextureDimension::k1d,
+                               ast::TexelFormat::kRgba32Sint, "image1D tex;"}));
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_unary_op_test.cc b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
new file mode 100644
index 0000000..5f84322
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
@@ -0,0 +1,93 @@
+// 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/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslUnaryOpTest = TestHelper;
+
+TEST_F(GlslUnaryOpTest, AddressOf) {
+  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
+}
+
+TEST_F(GlslUnaryOpTest, Complement) {
+  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "~(expr)");
+}
+
+TEST_F(GlslUnaryOpTest, Indirection) {
+  Global("G", ty.f32(), ast::StorageClass::kPrivate);
+  auto* p = Const(
+      "expr", nullptr,
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
+  WrapInFunction(p, op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
+}
+
+TEST_F(GlslUnaryOpTest, Not) {
+  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "!(expr)");
+}
+
+TEST_F(GlslUnaryOpTest, Negation) {
+  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "-(expr)");
+}
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
new file mode 100644
index 0000000..32e484e
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
@@ -0,0 +1,72 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using GlslGeneratorImplTest_UniformBuffer = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple) {
+  auto* simple = Structure("Simple", {Member("member", ty.f32())});
+  Global("simple", ty.Of(simple), ast::StorageClass::kUniform,
+         GroupAndBinding(0, 0));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 310 es
+
+struct Simple {
+  float member;
+};
+
+layout(binding = 0) uniform Simple_1 {
+  float member;
+} simple;
+
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple_Desktop) {
+  auto* simple = Structure("Simple", {Member("member", ty.f32())});
+  Global("simple", ty.Of(simple), ast::StorageClass::kUniform,
+         GroupAndBinding(0, 0));
+
+  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#version 440
+
+struct Simple {
+  float member;
+};
+
+layout(binding = 0, std140) uniform Simple_1 {
+  float member;
+} simple;
+
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
new file mode 100644
index 0000000..64c3b02
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
@@ -0,0 +1,129 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using GlslGeneratorImplTest_VariableDecl = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) {
+  auto* var = Var("a", ty.f32());
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
+  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
+  auto* var = Var("a", ty.array<f32, 5>());
+
+  WrapInFunction(var, Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr("  float a[5] = float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);\n"));
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) {
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("  float a = 0.0f;\n"));
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_Private) {
+  Global("initializer", ty.f32(), ast::StorageClass::kPrivate);
+  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer;
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_ZeroVec) {
+  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(vec3 a = vec3(0.0f, 0.0f, 0.0f);
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_ZeroMat) {
+  auto* var =
+      Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+)");
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc b/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc
new file mode 100644
index 0000000..c33b168
--- /dev/null
+++ b/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc
@@ -0,0 +1,61 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/glsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+namespace {
+using ::testing::HasSubstr;
+
+using GlslGeneratorImplTest_WorkgroupVar = TestHelper;
+
+TEST_F(GlslGeneratorImplTest_WorkgroupVar, Basic) {
+  Global("wg", ty.f32(), ast::StorageClass::kWorkgroup);
+
+  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("shared float wg;\n"));
+}
+
+TEST_F(GlslGeneratorImplTest_WorkgroupVar, Aliased) {
+  auto* alias = Alias("F32", ty.f32());
+
+  Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
+
+  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("shared float wg;\n"));
+}
+
+}  // namespace
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/glsl/test_helper.h b/src/tint/writer/glsl/test_helper.h
new file mode 100644
index 0000000..cea266e
--- /dev/null
+++ b/src/tint/writer/glsl/test_helper.h
@@ -0,0 +1,109 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_GLSL_TEST_HELPER_H_
+#define SRC_TINT_WRITER_GLSL_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/transform/glsl.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/writer/glsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace glsl {
+
+/// Helper class for testing
+template <typename BODY>
+class TestHelperBase : public BODY, public ProgramBuilder {
+ public:
+  TestHelperBase() = default;
+  ~TestHelperBase() override = default;
+
+  /// Builds the program and returns a GeneratorImpl from the program.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @param version the GLSL version
+  /// @return the built generator
+  GeneratorImpl& Build(Version version = Version()) {
+    if (gen_) {
+      return *gen_;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+    gen_ = std::make_unique<GeneratorImpl>(program.get(), version);
+    return *gen_;
+  }
+
+  /// Builds the program, runs the program through the transform::Glsl sanitizer
+  /// and returns a GeneratorImpl from the sanitized program.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @param version the GLSL version
+  /// @return the built generator
+  GeneratorImpl& SanitizeAndBuild(Version version = Version()) {
+    if (gen_) {
+      return *gen_;
+    }
+    diag::Formatter formatter;
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << formatter.format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << formatter.format(program->Diagnostics());
+    }();
+
+    transform::Manager transform_manager;
+    transform::DataMap transform_data;
+    transform_manager.Add<tint::transform::Glsl>();
+    auto result = transform_manager.Run(program.get(), transform_data);
+    [&]() {
+      ASSERT_TRUE(result.program.IsValid())
+          << formatter.format(result.program.Diagnostics());
+    }();
+    *program = std::move(result.program);
+    gen_ = std::make_unique<GeneratorImpl>(program.get(), version);
+    return *gen_;
+  }
+
+  /// The program built with a call to Build()
+  std::unique_ptr<Program> program;
+
+ private:
+  std::unique_ptr<GeneratorImpl> gen_;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace glsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_GLSL_TEST_HELPER_H_
diff --git a/src/tint/writer/glsl/version.h b/src/tint/writer/glsl/version.h
new file mode 100644
index 0000000..63888a4
--- /dev/null
+++ b/src/tint/writer/glsl/version.h
@@ -0,0 +1,58 @@
+// 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_WRITER_GLSL_VERSION_H_
+#define SRC_TINT_WRITER_GLSL_VERSION_H_
+
+#include <cstdint>
+
+namespace tint::writer::glsl {
+
+/// A structure representing the version of GLSL to be generated.
+struct Version {
+  /// Is this version desktop GLSL, or GLSL ES?
+  enum class Standard {
+    kDesktop,
+    kES,
+  };
+
+  /// Constructor
+  /// @param standard_ Desktop or ES
+  /// @param major_ the major version
+  /// @param minor_ the minor version
+  Version(Standard standard_, uint32_t major_, uint32_t minor_)
+      : standard(standard_), major_version(major_), minor_version(minor_) {}
+
+  /// Default constructor (see default values below)
+  Version() = default;
+
+  /// @returns true if this version is GLSL ES
+  bool IsES() const { return standard == Standard::kES; }
+
+  /// @returns true if this version is Desktop GLSL
+  bool IsDesktop() const { return standard == Standard::kDesktop; }
+
+  /// Desktop or ES
+  Standard standard = Standard::kES;
+
+  /// Major GLSL version
+  uint32_t major_version = 3;
+
+  /// Minor GLSL version
+  uint32_t minor_version = 1;
+};
+
+}  // namespace tint::writer::glsl
+
+#endif  // SRC_TINT_WRITER_GLSL_VERSION_H_
diff --git a/src/tint/writer/hlsl/generator.cc b/src/tint/writer/hlsl/generator.cc
new file mode 100644
index 0000000..536591f
--- /dev/null
+++ b/src/tint/writer/hlsl/generator.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/generator.h"
+
+#include "src/tint/writer/hlsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+
+Options::Options() = default;
+Options::~Options() = default;
+Options::Options(const Options&) = default;
+Options& Options::operator=(const Options&) = default;
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program, const Options& options) {
+  Result result;
+
+  // Sanitize the program.
+  auto sanitized_result = Sanitize(program, options.root_constant_binding_point,
+                                   options.disable_workgroup_init,
+                                   options.array_length_from_uniform);
+  if (!sanitized_result.program.IsValid()) {
+    result.success = false;
+    result.error = sanitized_result.program.Diagnostics().str();
+    return result;
+  }
+
+  // Generate the HLSL code.
+  auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program);
+  result.success = impl->Generate();
+  result.error = impl->error();
+  result.hlsl = impl->result();
+
+  // Collect the list of entry points in the sanitized program.
+  for (auto* func : sanitized_result.program.AST().Functions()) {
+    if (func->IsEntryPoint()) {
+      auto name = sanitized_result.program.Symbols().NameFor(func->symbol);
+      result.entry_points.push_back({name, func->PipelineStage()});
+    }
+  }
+
+  result.used_array_length_from_uniform_indices =
+      std::move(sanitized_result.used_array_length_from_uniform_indices);
+
+  return result;
+}
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator.h b/src/tint/writer/hlsl/generator.h
new file mode 100644
index 0000000..63df62e
--- /dev/null
+++ b/src/tint/writer/hlsl/generator.h
@@ -0,0 +1,104 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_HLSL_GENERATOR_H_
+#define SRC_TINT_WRITER_HLSL_GENERATOR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/pipeline_stage.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/text.h"
+
+namespace tint {
+
+// Forward declarations
+class Program;
+
+namespace writer {
+namespace hlsl {
+
+// Forward declarations
+class GeneratorImpl;
+
+/// Configuration options used for generating HLSL.
+struct Options {
+  /// Constructor
+  Options();
+  /// Destructor
+  ~Options();
+  /// Copy constructor
+  Options(const Options&);
+  /// Copy assignment
+  /// @returns this Options
+  Options& operator=(const Options&);
+
+  /// The binding point to use for information passed via root constants.
+  sem::BindingPoint root_constant_binding_point;
+  /// Set to `true` to disable workgroup memory zero initialization
+  bool disable_workgroup_init = false;
+  /// Options used to specify a mapping of binding points to indices into a UBO
+  /// from which to load buffer sizes.
+  ArrayLengthFromUniformOptions array_length_from_uniform = {};
+
+  // NOTE: Update src/tint/fuzzers/data_builder.h when adding or changing any
+  // struct members.
+};
+
+/// The result produced when generating HLSL.
+struct Result {
+  /// Constructor
+  Result();
+
+  /// Destructor
+  ~Result();
+
+  /// Copy constructor
+  Result(const Result&);
+
+  /// True if generation was successful.
+  bool success = false;
+
+  /// The errors generated during code generation, if any.
+  std::string error;
+
+  /// The generated HLSL.
+  std::string hlsl = "";
+
+  /// The list of entry points in the generated HLSL.
+  std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
+
+  /// Indices into the array_length_from_uniform binding that are statically
+  /// used.
+  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
+};
+
+/// Generate HLSL for a program, according to a set of configuration options.
+/// The result will contain the HLSL, as well as success status and diagnostic
+/// information.
+/// @param program the program to translate to HLSL
+/// @param options the configuration options to use when generating HLSL
+/// @returns the resulting HLSL and supplementary information
+Result Generate(const Program* program, const Options& options);
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_HLSL_GENERATOR_H_
diff --git a/src/tint/writer/hlsl/generator_bench.cc b/src/tint/writer/hlsl/generator_bench.cc
new file mode 100644
index 0000000..27b605b
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_bench.cc
@@ -0,0 +1,40 @@
+// 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 <string>
+
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::writer::hlsl {
+namespace {
+
+void GenerateHLSL(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadProgram(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& program = std::get<bench::ProgramAndFile>(res).program;
+  for (auto _ : state) {
+    auto res = Generate(&program, {});
+    if (!res.error.empty()) {
+      state.SkipWithError(res.error.c_str());
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(GenerateHLSL);
+
+}  // namespace
+}  // namespace tint::writer::hlsl
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
new file mode 100644
index 0000000..f89ba3b
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -0,0 +1,4033 @@
+/// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/generator_impl.h"
+
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <iomanip>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/debug.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/add_empty_entry_point.h"
+#include "src/tint/transform/array_length_from_uniform.h"
+#include "src/tint/transform/calculate_array_length.h"
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/decompose_memory_access.h"
+#include "src/tint/transform/external_texture_transform.h"
+#include "src/tint/transform/fold_trivial_single_use_lets.h"
+#include "src/tint/transform/localize_struct_array_assignment.h"
+#include "src/tint/transform/loop_to_for_loop.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/num_workgroups_from_uniform.h"
+#include "src/tint/transform/pad_array_elements.h"
+#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/remove_phonies.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/zero_init_workgroup_memory.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/append_vector.h"
+#include "src/tint/writer/float_to_string.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+const char kTempNamePrefix[] = "tint_tmp";
+const char kSpecConstantPrefix[] = "WGSL_SPEC_CONSTANT_";
+
+const char* image_format_to_rwtexture_type(ast::TexelFormat image_format) {
+  switch (image_format) {
+    case ast::TexelFormat::kRgba8Unorm:
+    case ast::TexelFormat::kRgba8Snorm:
+    case ast::TexelFormat::kRgba16Float:
+    case ast::TexelFormat::kR32Float:
+    case ast::TexelFormat::kRg32Float:
+    case ast::TexelFormat::kRgba32Float:
+      return "float4";
+    case ast::TexelFormat::kRgba8Uint:
+    case ast::TexelFormat::kRgba16Uint:
+    case ast::TexelFormat::kR32Uint:
+    case ast::TexelFormat::kRg32Uint:
+    case ast::TexelFormat::kRgba32Uint:
+      return "uint4";
+    case ast::TexelFormat::kRgba8Sint:
+    case ast::TexelFormat::kRgba16Sint:
+    case ast::TexelFormat::kR32Sint:
+    case ast::TexelFormat::kRg32Sint:
+    case ast::TexelFormat::kRgba32Sint:
+      return "int4";
+    default:
+      return nullptr;
+  }
+}
+
+// Helper for writing " : register(RX, spaceY)", where R is the register, X is
+// the binding point binding value, and Y is the binding point group value.
+struct RegisterAndSpace {
+  RegisterAndSpace(char r, ast::VariableBindingPoint bp)
+      : reg(r), binding_point(bp) {}
+
+  const char reg;
+  ast::VariableBindingPoint const binding_point;
+};
+
+std::ostream& operator<<(std::ostream& s, const RegisterAndSpace& rs) {
+  s << " : register(" << rs.reg << rs.binding_point.binding->value << ", space"
+    << rs.binding_point.group->value << ")";
+  return s;
+}
+
+const char* LoopAttribute() {
+  // Force loops not to be unrolled to work around FXC compilation issues when
+  // it attempts and fails to unroll loops when it contains gradient operations.
+  // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-while
+  return "[loop] ";
+}
+
+}  // namespace
+
+SanitizedResult::SanitizedResult() = default;
+SanitizedResult::~SanitizedResult() = default;
+SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
+
+SanitizedResult Sanitize(
+    const Program* in,
+    sem::BindingPoint root_constant_binding_point,
+    bool disable_workgroup_init,
+    const ArrayLengthFromUniformOptions& array_length_from_uniform) {
+  transform::Manager manager;
+  transform::DataMap data;
+
+  // Build the config for the internal ArrayLengthFromUniform transform.
+  transform::ArrayLengthFromUniform::Config array_length_from_uniform_cfg(
+      array_length_from_uniform.ubo_binding);
+  array_length_from_uniform_cfg.bindpoint_to_size_index =
+      array_length_from_uniform.bindpoint_to_size_index;
+
+  manager.Add<transform::Unshadow>();
+
+  // LocalizeStructArrayAssignment must come after:
+  // * SimplifyPointers, because it assumes assignment to arrays in structs are
+  // done directly, not indirectly.
+  // TODO(crbug.com/tint/1340): See if we can get rid of the duplicate
+  // SimplifyPointers transform. Can't do it right now because
+  // LocalizeStructArrayAssignment introduces pointers.
+  manager.Add<transform::SimplifyPointers>();
+  manager.Add<transform::LocalizeStructArrayAssignment>();
+
+  // Attempt to convert `loop`s into for-loops. This is to try and massage the
+  // output into something that will not cause FXC to choke or misbehave.
+  manager.Add<transform::FoldTrivialSingleUseLets>();
+  manager.Add<transform::LoopToForLoop>();
+
+  if (!disable_workgroup_init) {
+    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
+    // ZeroInitWorkgroupMemory may inject new builtin parameters.
+    manager.Add<transform::ZeroInitWorkgroupMemory>();
+  }
+  manager.Add<transform::CanonicalizeEntryPointIO>();
+  // NumWorkgroupsFromUniform must come after CanonicalizeEntryPointIO, as it
+  // assumes that num_workgroups builtins only appear as struct members and are
+  // only accessed directly via member accessors.
+  manager.Add<transform::NumWorkgroupsFromUniform>();
+  manager.Add<transform::SimplifyPointers>();
+  manager.Add<transform::RemovePhonies>();
+  // ArrayLengthFromUniform must come after InlinePointerLets and Simplify, as
+  // it assumes that the form of the array length argument is &var.array.
+  manager.Add<transform::ArrayLengthFromUniform>();
+  data.Add<transform::ArrayLengthFromUniform::Config>(
+      std::move(array_length_from_uniform_cfg));
+  // DecomposeMemoryAccess must come after:
+  // * InlinePointerLets, as we cannot take the address of calls to
+  //   DecomposeMemoryAccess::Intrinsic.
+  // * Simplify, as we need to fold away the address-of and dereferences of
+  // `*(&(intrinsic_load()))` expressions.
+  // * RemovePhonies, as phonies can be assigned a pointer to a
+  //   non-constructible buffer, or dynamic array, which DMA cannot cope with.
+  manager.Add<transform::DecomposeMemoryAccess>();
+  // CalculateArrayLength must come after DecomposeMemoryAccess, as
+  // DecomposeMemoryAccess special-cases the arrayLength() intrinsic, which
+  // will be transformed by CalculateArrayLength
+  manager.Add<transform::CalculateArrayLength>();
+  manager.Add<transform::ExternalTextureTransform>();
+  manager.Add<transform::PromoteInitializersToConstVar>();
+
+  manager.Add<transform::PadArrayElements>();
+  manager.Add<transform::AddEmptyEntryPoint>();
+
+  data.Add<transform::CanonicalizeEntryPointIO::Config>(
+      transform::CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+  data.Add<transform::NumWorkgroupsFromUniform::Config>(
+      root_constant_binding_point);
+
+  auto out = manager.Run(in, data);
+
+  SanitizedResult result;
+  result.program = std::move(out.program);
+  if (auto* res = out.data.Get<transform::ArrayLengthFromUniform::Result>()) {
+    result.used_array_length_from_uniform_indices =
+        std::move(res->used_size_indices);
+  }
+  return result;
+}
+
+GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
+
+GeneratorImpl::~GeneratorImpl() = default;
+
+bool GeneratorImpl::Generate() {
+  const TypeInfo* last_kind = nullptr;
+  size_t last_padding_line = 0;
+
+  auto* mod = builder_.Sem().Module();
+  for (auto* decl : mod->DependencyOrderedDeclarations()) {
+    if (decl->Is<ast::Alias>()) {
+      continue;  // Ignore aliases.
+    }
+
+    // Emit a new line between declarations if the type of declaration has
+    // changed, or we're about to emit a function
+    auto* kind = &decl->TypeInfo();
+    if (current_buffer_->lines.size() != last_padding_line) {
+      if (last_kind && (last_kind != kind || decl->Is<ast::Function>())) {
+        line();
+        last_padding_line = current_buffer_->lines.size();
+      }
+    }
+    last_kind = kind;
+
+    bool ok = Switch(
+        decl,
+        [&](const ast::Variable* global) {  //
+          return EmitGlobalVariable(global);
+        },
+        [&](const ast::Struct* str) {
+          auto* ty = builder_.Sem().Get(str);
+          auto storage_class_uses = ty->StorageClassUsage();
+          if (storage_class_uses.size() !=
+              (storage_class_uses.count(ast::StorageClass::kStorage) +
+               storage_class_uses.count(ast::StorageClass::kUniform))) {
+            // The structure is used as something other than a storage buffer or
+            // uniform buffer, so it needs to be emitted.
+            // Storage buffer are read and written to via a ByteAddressBuffer
+            // instead of true structure.
+            // Structures used as uniform buffer are read from an array of
+            // vectors instead of true structure.
+            return EmitStructType(current_buffer_, ty);
+          }
+          return true;
+        },
+        [&](const ast::Function* func) {
+          if (func->IsEntryPoint()) {
+            return EmitEntryPointFunction(func);
+          }
+          return EmitFunction(func);
+        },
+        [&](Default) {
+          TINT_ICE(Writer, diagnostics_)
+              << "unhandled module-scope declaration: "
+              << decl->TypeInfo().name;
+          return false;
+        });
+
+    if (!ok) {
+      return false;
+    }
+  }
+
+  if (!helpers_.lines.empty()) {
+    current_buffer_->Insert(helpers_, 0, 0);
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDynamicVectorAssignment(
+    const ast::AssignmentStatement* stmt,
+    const sem::Vector* vec) {
+  auto name =
+      utils::GetOrCreate(dynamic_vector_write_, vec, [&]() -> std::string {
+        std::string fn;
+        {
+          std::ostringstream ss;
+          if (!EmitType(ss, vec, tint::ast::StorageClass::kInvalid,
+                        ast::Access::kUndefined, "")) {
+            return "";
+          }
+          fn = UniqueIdentifier("set_" + ss.str());
+        }
+        {
+          auto out = line(&helpers_);
+          out << "void " << fn << "(inout ";
+          if (!EmitTypeAndName(out, vec, ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "vec")) {
+            return "";
+          }
+          out << ", int idx, ";
+          if (!EmitTypeAndName(out, vec->type(), ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "val")) {
+            return "";
+          }
+          out << ") {";
+        }
+        {
+          ScopedIndent si(&helpers_);
+          auto out = line(&helpers_);
+          switch (vec->Width()) {
+            case 2:
+              out << "vec = (idx.xx == int2(0, 1)) ? val.xx : vec;";
+              break;
+            case 3:
+              out << "vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;";
+              break;
+            case 4:
+              out << "vec = (idx.xxxx == int4(0, 1, 2, 3)) ? val.xxxx : vec;";
+              break;
+            default:
+              TINT_UNREACHABLE(Writer, builder_.Diagnostics())
+                  << "invalid vector size " << vec->Width();
+              break;
+          }
+        }
+        line(&helpers_) << "}";
+        line(&helpers_);
+        return fn;
+      });
+
+  if (name.empty()) {
+    return false;
+  }
+
+  auto* ast_access_expr = stmt->lhs->As<ast::IndexAccessorExpression>();
+
+  auto out = line();
+  out << name << "(";
+  if (!EmitExpression(out, ast_access_expr->object)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, ast_access_expr->index)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+  out << ");";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDynamicMatrixVectorAssignment(
+    const ast::AssignmentStatement* stmt,
+    const sem::Matrix* mat) {
+  auto name = utils::GetOrCreate(
+      dynamic_matrix_vector_write_, mat, [&]() -> std::string {
+        std::string fn;
+        {
+          std::ostringstream ss;
+          if (!EmitType(ss, mat, tint::ast::StorageClass::kInvalid,
+                        ast::Access::kUndefined, "")) {
+            return "";
+          }
+          fn = UniqueIdentifier("set_vector_" + ss.str());
+        }
+        {
+          auto out = line(&helpers_);
+          out << "void " << fn << "(inout ";
+          if (!EmitTypeAndName(out, mat, ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "mat")) {
+            return "";
+          }
+          out << ", int col, ";
+          if (!EmitTypeAndName(out, mat->ColumnType(),
+                               ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "val")) {
+            return "";
+          }
+          out << ") {";
+        }
+        {
+          ScopedIndent si(&helpers_);
+          line(&helpers_) << "switch (col) {";
+          {
+            ScopedIndent si2(&helpers_);
+            for (uint32_t i = 0; i < mat->columns(); ++i) {
+              line(&helpers_)
+                  << "case " << i << ": mat[" << i << "] = val; break;";
+            }
+          }
+          line(&helpers_) << "}";
+        }
+        line(&helpers_) << "}";
+        line(&helpers_);
+        return fn;
+      });
+
+  if (name.empty()) {
+    return false;
+  }
+
+  auto* ast_access_expr = stmt->lhs->As<ast::IndexAccessorExpression>();
+
+  auto out = line();
+  out << name << "(";
+  if (!EmitExpression(out, ast_access_expr->object)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, ast_access_expr->index)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+  out << ");";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDynamicMatrixScalarAssignment(
+    const ast::AssignmentStatement* stmt,
+    const sem::Matrix* mat) {
+  auto* lhs_col_access = stmt->lhs->As<ast::IndexAccessorExpression>();
+  auto* lhs_row_access =
+      lhs_col_access->object->As<ast::IndexAccessorExpression>();
+
+  auto name = utils::GetOrCreate(
+      dynamic_matrix_scalar_write_, mat, [&]() -> std::string {
+        std::string fn;
+        {
+          std::ostringstream ss;
+          if (!EmitType(ss, mat, tint::ast::StorageClass::kInvalid,
+                        ast::Access::kUndefined, "")) {
+            return "";
+          }
+          fn = UniqueIdentifier("set_scalar_" + ss.str());
+        }
+        {
+          auto out = line(&helpers_);
+          out << "void " << fn << "(inout ";
+          if (!EmitTypeAndName(out, mat, ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "mat")) {
+            return "";
+          }
+          out << ", int col, int row, ";
+          if (!EmitTypeAndName(out, mat->type(), ast::StorageClass::kInvalid,
+                               ast::Access::kUndefined, "val")) {
+            return "";
+          }
+          out << ") {";
+        }
+        {
+          ScopedIndent si(&helpers_);
+          line(&helpers_) << "switch (col) {";
+          {
+            ScopedIndent si2(&helpers_);
+            auto* vec =
+                TypeOf(lhs_row_access->object)->UnwrapRef()->As<sem::Vector>();
+            for (uint32_t i = 0; i < mat->columns(); ++i) {
+              line(&helpers_) << "case " << i << ":";
+              {
+                auto vec_name = "mat[" + std::to_string(i) + "]";
+                ScopedIndent si3(&helpers_);
+                {
+                  auto out = line(&helpers_);
+                  switch (mat->rows()) {
+                    case 2:
+                      out << vec_name
+                          << " = (row.xx == int2(0, 1)) ? val.xx : " << vec_name
+                          << ";";
+                      break;
+                    case 3:
+                      out << vec_name
+                          << " = (row.xxx == int3(0, 1, 2)) ? val.xxx : "
+                          << vec_name << ";";
+                      break;
+                    case 4:
+                      out << vec_name
+                          << " = (row.xxxx == int4(0, 1, 2, 3)) ? val.xxxx : "
+                          << vec_name << ";";
+                      break;
+                    default:
+                      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
+                          << "invalid vector size " << vec->Width();
+                      break;
+                  }
+                }
+                line(&helpers_) << "break;";
+              }
+            }
+          }
+          line(&helpers_) << "}";
+        }
+        line(&helpers_) << "}";
+        line(&helpers_);
+        return fn;
+      });
+
+  if (name.empty()) {
+    return false;
+  }
+
+  auto out = line();
+  out << name << "(";
+  if (!EmitExpression(out, lhs_row_access->object)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, lhs_col_access->index)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, lhs_row_access->index)) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+  out << ");";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitIndexAccessor(
+    std::ostream& out,
+    const ast::IndexAccessorExpression* expr) {
+  if (!EmitExpression(out, expr->object)) {
+    return false;
+  }
+  out << "[";
+
+  if (!EmitExpression(out, expr->index)) {
+    return false;
+  }
+  out << "]";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBitcast(std::ostream& out,
+                                const ast::BitcastExpression* expr) {
+  auto* type = TypeOf(expr);
+  if (auto* vec = type->UnwrapRef()->As<sem::Vector>()) {
+    type = vec->type();
+  }
+
+  if (!type->is_integer_scalar() && !type->is_float_scalar()) {
+    diagnostics_.add_error(diag::System::Writer,
+                           "Unable to do bitcast to type " + type->type_name());
+    return false;
+  }
+
+  out << "as";
+  if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
+                "")) {
+    return false;
+  }
+  out << "(";
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
+  if (auto* lhs_access = stmt->lhs->As<ast::IndexAccessorExpression>()) {
+    // BUG(crbug.com/tint/1333): work around assignment of scalar to matrices
+    // with at least one dynamic index
+    if (auto* lhs_sub_access =
+            lhs_access->object->As<ast::IndexAccessorExpression>()) {
+      if (auto* mat =
+              TypeOf(lhs_sub_access->object)->UnwrapRef()->As<sem::Matrix>()) {
+        auto* rhs_col_idx_sem = builder_.Sem().Get(lhs_access->index);
+        auto* rhs_row_idx_sem = builder_.Sem().Get(lhs_sub_access->index);
+        if (!rhs_col_idx_sem->ConstantValue().IsValid() ||
+            !rhs_row_idx_sem->ConstantValue().IsValid()) {
+          return EmitDynamicMatrixScalarAssignment(stmt, mat);
+        }
+      }
+    }
+    // BUG(crbug.com/tint/1333): work around assignment of vector to matrices
+    // with dynamic indices
+    const auto* lhs_access_type = TypeOf(lhs_access->object)->UnwrapRef();
+    if (auto* mat = lhs_access_type->As<sem::Matrix>()) {
+      auto* lhs_index_sem = builder_.Sem().Get(lhs_access->index);
+      if (!lhs_index_sem->ConstantValue().IsValid()) {
+        return EmitDynamicMatrixVectorAssignment(stmt, mat);
+      }
+    }
+    // BUG(crbug.com/tint/534): work around assignment to vectors with dynamic
+    // indices
+    if (auto* vec = lhs_access_type->As<sem::Vector>()) {
+      auto* rhs_sem = builder_.Sem().Get(lhs_access->index);
+      if (!rhs_sem->ConstantValue().IsValid()) {
+        return EmitDynamicVectorAssignment(stmt, vec);
+      }
+    }
+  }
+
+  auto out = line();
+  if (!EmitExpression(out, stmt->lhs)) {
+    return false;
+  }
+  out << " = ";
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitExpressionOrOneIfZero(std::ostream& out,
+                                              const ast::Expression* expr) {
+  // For constants, replace literal 0 with 1.
+  sem::Constant::Scalars elems;
+  if (const auto& val = builder_.Sem().Get(expr)->ConstantValue()) {
+    if (!val.AnyZero()) {
+      return EmitExpression(out, expr);
+    }
+
+    if (val.Type()->IsAnyOf<sem::I32, sem::U32>()) {
+      return EmitValue(out, val.Type(), 1);
+    }
+
+    if (auto* vec = val.Type()->As<sem::Vector>()) {
+      auto* elem_ty = vec->type();
+
+      if (!EmitType(out, val.Type(), ast::StorageClass::kNone,
+                    ast::Access::kUndefined, "")) {
+        return false;
+      }
+
+      out << "(";
+      for (size_t i = 0; i < val.Elements().size(); ++i) {
+        if (i != 0) {
+          out << ", ";
+        }
+        if (!val.WithScalarAt(i, [&](auto&& s) -> bool {
+              // Use std::equal_to to work around -Wfloat-equal warnings
+              auto equals_to =
+                  std::equal_to<std::remove_reference_t<decltype(s)>>{};
+
+              bool is_zero = equals_to(s, 0);
+              return EmitValue(out, elem_ty, is_zero ? 1 : static_cast<int>(s));
+            })) {
+          return false;
+        }
+      }
+      out << ")";
+      return true;
+    }
+
+    TINT_ICE(Writer, diagnostics_)
+        << "EmitExpressionOrOneIfZero expects integer scalar or vector";
+    return false;
+  }
+
+  auto* ty = TypeOf(expr)->UnwrapRef();
+
+  // For non-constants, we need to emit runtime code to check if the value is 0,
+  // and return 1 in that case.
+  std::string zero;
+  {
+    std::ostringstream ss;
+    EmitValue(ss, ty, 0);
+    zero = ss.str();
+  }
+  std::string one;
+  {
+    std::ostringstream ss;
+    EmitValue(ss, ty, 1);
+    one = ss.str();
+  }
+
+  // For identifiers, no need for a function call as it's fine to evaluate
+  // `expr` more than once.
+  if (expr->Is<ast::IdentifierExpression>()) {
+    out << "(";
+    if (!EmitExpression(out, expr)) {
+      return false;
+    }
+    out << " == " << zero << " ? " << one << " : ";
+    if (!EmitExpression(out, expr)) {
+      return false;
+    }
+    out << ")";
+    return true;
+  }
+
+  // For non-identifier expressions, call a function to make sure `expr` is only
+  // evaluated once.
+  auto name =
+      utils::GetOrCreate(value_or_one_if_zero_, ty, [&]() -> std::string {
+        // Example:
+        // int4 tint_value_or_one_if_zero_int4(int4 value) {
+        //   return value == 0 ? 0 : value;
+        // }
+        std::string ty_name;
+        {
+          std::ostringstream ss;
+          if (!EmitType(ss, ty, tint::ast::StorageClass::kInvalid,
+                        ast::Access::kUndefined, "")) {
+            return "";
+          }
+          ty_name = ss.str();
+        }
+
+        std::string fn = UniqueIdentifier("value_or_one_if_zero_" + ty_name);
+        line(&helpers_) << ty_name << " " << fn << "(" << ty_name
+                        << " value) {";
+        {
+          ScopedIndent si(&helpers_);
+          line(&helpers_) << "return value == " << zero << " ? " << one
+                          << " : value;";
+        }
+        line(&helpers_) << "}";
+        line(&helpers_);
+        return fn;
+      });
+
+  if (name.empty()) {
+    return false;
+  }
+
+  out << name << "(";
+  if (!EmitExpression(out, expr)) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitBinary(std::ostream& out,
+                               const ast::BinaryExpression* expr) {
+  if (expr->op == ast::BinaryOp::kLogicalAnd ||
+      expr->op == ast::BinaryOp::kLogicalOr) {
+    auto name = UniqueIdentifier(kTempNamePrefix);
+
+    {
+      auto pre = line();
+      pre << "bool " << name << " = ";
+      if (!EmitExpression(pre, expr->lhs)) {
+        return false;
+      }
+      pre << ";";
+    }
+
+    if (expr->op == ast::BinaryOp::kLogicalOr) {
+      line() << "if (!" << name << ") {";
+    } else {
+      line() << "if (" << name << ") {";
+    }
+
+    {
+      ScopedIndent si(this);
+      auto pre = line();
+      pre << name << " = ";
+      if (!EmitExpression(pre, expr->rhs)) {
+        return false;
+      }
+      pre << ";";
+    }
+
+    line() << "}";
+
+    out << "(" << name << ")";
+    return true;
+  }
+
+  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
+  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
+  // Multiplying by a matrix requires the use of `mul` in order to get the
+  // type of multiply we desire.
+  if (expr->op == ast::BinaryOp::kMultiply &&
+      ((lhs_type->Is<sem::Vector>() && rhs_type->Is<sem::Matrix>()) ||
+       (lhs_type->Is<sem::Matrix>() && rhs_type->Is<sem::Vector>()) ||
+       (lhs_type->Is<sem::Matrix>() && rhs_type->Is<sem::Matrix>()))) {
+    // Matrices are transposed, so swap LHS and RHS.
+    out << "mul(";
+    if (!EmitExpression(out, expr->rhs)) {
+      return false;
+    }
+    out << ", ";
+    if (!EmitExpression(out, expr->lhs)) {
+      return false;
+    }
+    out << ")";
+
+    return true;
+  }
+
+  out << "(";
+  TINT_DEFER(out << ")");
+
+  if (!EmitExpression(out, expr->lhs)) {
+    return false;
+  }
+  out << " ";
+
+  switch (expr->op) {
+    case ast::BinaryOp::kAnd:
+      out << "&";
+      break;
+    case ast::BinaryOp::kOr:
+      out << "|";
+      break;
+    case ast::BinaryOp::kXor:
+      out << "^";
+      break;
+    case ast::BinaryOp::kLogicalAnd:
+    case ast::BinaryOp::kLogicalOr: {
+      // These are both handled above.
+      TINT_UNREACHABLE(Writer, diagnostics_);
+      return false;
+    }
+    case ast::BinaryOp::kEqual:
+      out << "==";
+      break;
+    case ast::BinaryOp::kNotEqual:
+      out << "!=";
+      break;
+    case ast::BinaryOp::kLessThan:
+      out << "<";
+      break;
+    case ast::BinaryOp::kGreaterThan:
+      out << ">";
+      break;
+    case ast::BinaryOp::kLessThanEqual:
+      out << "<=";
+      break;
+    case ast::BinaryOp::kGreaterThanEqual:
+      out << ">=";
+      break;
+    case ast::BinaryOp::kShiftLeft:
+      out << "<<";
+      break;
+    case ast::BinaryOp::kShiftRight:
+      // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
+      // implementation-defined behaviour for negative LHS.  We may have to
+      // generate extra code to implement WGSL-specified behaviour for negative
+      // LHS.
+      out << R"(>>)";
+      break;
+
+    case ast::BinaryOp::kAdd:
+      out << "+";
+      break;
+    case ast::BinaryOp::kSubtract:
+      out << "-";
+      break;
+    case ast::BinaryOp::kMultiply:
+      out << "*";
+      break;
+    case ast::BinaryOp::kDivide:
+      out << "/";
+      // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
+      // compile error, and undefined behavior in WGSL.
+      if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
+        out << " ";
+        return EmitExpressionOrOneIfZero(out, expr->rhs);
+      }
+      break;
+    case ast::BinaryOp::kModulo:
+      out << "%";
+      // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
+      // compile error, and undefined behavior in WGSL.
+      if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
+        out << " ";
+        return EmitExpressionOrOneIfZero(out, expr->rhs);
+      }
+      break;
+    case ast::BinaryOp::kNone:
+      diagnostics_.add_error(diag::System::Writer,
+                             "missing binary operation type");
+      return false;
+  }
+  out << " ";
+
+  if (!EmitExpression(out, expr->rhs)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+  for (auto* s : stmts) {
+    if (!EmitStatement(s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+  ScopedIndent si(this);
+  return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+  line() << "{";
+  if (!EmitStatementsWithIndent(stmt->statements)) {
+    return false;
+  }
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
+  line() << "break;";
+  return true;
+}
+
+bool GeneratorImpl::EmitCall(std::ostream& out,
+                             const ast::CallExpression* expr) {
+  auto* call = builder_.Sem().Get(expr);
+  auto* target = call->Target();
+  return Switch(
+      target,
+      [&](const sem::Function* func) {
+        return EmitFunctionCall(out, call, func);
+      },
+      [&](const sem::Builtin* builtin) {
+        return EmitBuiltinCall(out, call, builtin);
+      },
+      [&](const sem::TypeConversion* conv) {
+        return EmitTypeConversion(out, call, conv);
+      },
+      [&](const sem::TypeConstructor* ctor) {
+        return EmitTypeConstructor(out, call, ctor);
+      },
+      [&](Default) {
+        TINT_ICE(Writer, diagnostics_)
+            << "unhandled call target: " << target->TypeInfo().name;
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
+                                     const sem::Call* call,
+                                     const sem::Function* func) {
+  auto* expr = call->Declaration();
+
+  if (ast::HasAttribute<transform::CalculateArrayLength::BufferSizeIntrinsic>(
+          func->Declaration()->attributes)) {
+    // Special function generated by the CalculateArrayLength transform for
+    // calling X.GetDimensions(Y)
+    if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
+      return false;
+    }
+    out << ".GetDimensions(";
+    if (!EmitExpression(out, call->Arguments()[1]->Declaration())) {
+      return false;
+    }
+    out << ")";
+    return true;
+  }
+
+  if (auto* intrinsic =
+          ast::GetAttribute<transform::DecomposeMemoryAccess::Intrinsic>(
+              func->Declaration()->attributes)) {
+    switch (intrinsic->storage_class) {
+      case ast::StorageClass::kUniform:
+        return EmitUniformBufferAccess(out, expr, intrinsic);
+      case ast::StorageClass::kStorage:
+        return EmitStorageBufferAccess(out, expr, intrinsic);
+      default:
+        TINT_UNREACHABLE(Writer, diagnostics_)
+            << "unsupported DecomposeMemoryAccess::Intrinsic storage class:"
+            << intrinsic->storage_class;
+        return false;
+    }
+  }
+
+  out << builder_.Symbols().NameFor(func->Declaration()->symbol) << "(";
+
+  bool first = true;
+  for (auto* arg : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  auto* expr = call->Declaration();
+  if (builtin->IsTexture()) {
+    return EmitTextureCall(out, call, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kSelect) {
+    return EmitSelectCall(out, expr);
+  }
+  if (builtin->Type() == sem::BuiltinType::kModf) {
+    return EmitModfCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kFrexp) {
+    return EmitFrexpCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kIsNormal) {
+    return EmitIsNormalCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kDegrees) {
+    return EmitDegreesCall(out, expr, builtin);
+  }
+  if (builtin->Type() == sem::BuiltinType::kRadians) {
+    return EmitRadiansCall(out, expr, builtin);
+  }
+  if (builtin->IsDataPacking()) {
+    return EmitDataPackingCall(out, expr, builtin);
+  }
+  if (builtin->IsDataUnpacking()) {
+    return EmitDataUnpackingCall(out, expr, builtin);
+  }
+  if (builtin->IsBarrier()) {
+    return EmitBarrierCall(out, builtin);
+  }
+  if (builtin->IsAtomic()) {
+    return EmitWorkgroupAtomicCall(out, expr, builtin);
+  }
+  auto name = generate_builtin_name(builtin);
+  if (name.empty()) {
+    return false;
+  }
+
+  out << name << "(";
+
+  bool first = true;
+  for (auto* arg : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
+                                       const sem::Call* call,
+                                       const sem::TypeConversion* conv) {
+  if (!EmitType(out, conv->Target(), ast::StorageClass::kNone,
+                ast::Access::kReadWrite, "")) {
+    return false;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
+                                        const sem::Call* call,
+                                        const sem::TypeConstructor* ctor) {
+  auto* type = call->Type();
+
+  // If the type constructor is empty then we need to construct with the zero
+  // value for all components.
+  if (call->Arguments().empty()) {
+    return EmitZeroValue(out, type);
+  }
+
+  bool brackets = type->IsAnyOf<sem::Array, sem::Struct>();
+
+  // For single-value vector initializers, swizzle the scalar to the right
+  // vector dimension using .x
+  const bool is_single_value_vector_init =
+      type->is_scalar_vector() && call->Arguments().size() == 1 &&
+      ctor->Parameters()[0]->Type()->is_scalar();
+
+  auto it = structure_builders_.find(As<sem::Struct>(type));
+  if (it != structure_builders_.end()) {
+    out << it->second << "(";
+    brackets = false;
+  } else if (brackets) {
+    out << "{";
+  } else {
+    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
+                  "")) {
+      return false;
+    }
+    out << "(";
+  }
+
+  if (is_single_value_vector_init) {
+    out << "(";
+  }
+
+  bool first = true;
+  for (auto* e : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, e->Declaration())) {
+      return false;
+    }
+  }
+
+  if (is_single_value_vector_init) {
+    out << ")." << std::string(type->As<sem::Vector>()->Width(), 'x');
+  }
+
+  out << (brackets ? "}" : ")");
+  return true;
+}
+
+bool GeneratorImpl::EmitUniformBufferAccess(
+    std::ostream& out,
+    const ast::CallExpression* expr,
+    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
+  const auto& args = expr->args;
+  auto* offset_arg = builder_.Sem().Get(args[1]);
+
+  uint32_t scalar_offset_value = 0;
+  std::string scalar_offset_expr;
+
+  // If true, use scalar_offset_value, otherwise use scalar_offset_expr
+  bool scalar_offset_constant = false;
+
+  if (auto val = offset_arg->ConstantValue()) {
+    TINT_ASSERT(Writer, val.Type()->Is<sem::U32>());
+    scalar_offset_value = val.Elements()[0].u32;
+    scalar_offset_value /= 4;  // bytes -> scalar index
+    scalar_offset_constant = true;
+  }
+
+  if (!scalar_offset_constant) {
+    // UBO offset not compile-time known.
+    // Calculate the scalar offset into a temporary.
+    scalar_offset_expr = UniqueIdentifier("scalar_offset");
+    auto pre = line();
+    pre << "const uint " << scalar_offset_expr << " = (";
+    if (!EmitExpression(pre, args[1])) {  // offset
+      return false;
+    }
+    pre << ") / 4;";
+  }
+
+  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
+  using DataType = transform::DecomposeMemoryAccess::Intrinsic::DataType;
+  switch (intrinsic->op) {
+    case Op::kLoad: {
+      auto cast = [&](const char* to, auto&& load) {
+        out << to << "(";
+        auto result = load();
+        out << ")";
+        return result;
+      };
+      auto load_scalar = [&]() {
+        if (!EmitExpression(out, args[0])) {  // buffer
+          return false;
+        }
+        if (scalar_offset_constant) {
+          char swizzle[] = {'x', 'y', 'z', 'w'};
+          out << "[" << (scalar_offset_value / 4) << "]."
+              << swizzle[scalar_offset_value & 3];
+        } else {
+          out << "[" << scalar_offset_expr << " / 4][" << scalar_offset_expr
+              << " % 4]";
+        }
+        return true;
+      };
+      // Has a minimum alignment of 8 bytes, so is either .xy or .zw
+      auto load_vec2 = [&] {
+        if (scalar_offset_constant) {
+          if (!EmitExpression(out, args[0])) {  // buffer
+            return false;
+          }
+          out << "[" << (scalar_offset_value / 4) << "]";
+          out << ((scalar_offset_value & 2) == 0 ? ".xy" : ".zw");
+        } else {
+          std::string ubo_load = UniqueIdentifier("ubo_load");
+          {
+            auto pre = line();
+            pre << "uint4 " << ubo_load << " = ";
+            if (!EmitExpression(pre, args[0])) {  // buffer
+              return false;
+            }
+            pre << "[" << scalar_offset_expr << " / 4];";
+          }
+          out << "((" << scalar_offset_expr << " & 2) ? " << ubo_load
+              << ".zw : " << ubo_load << ".xy)";
+        }
+        return true;
+      };
+      // vec4 has a minimum alignment of 16 bytes, easiest case
+      auto load_vec4 = [&] {
+        if (!EmitExpression(out, args[0])) {  // buffer
+          return false;
+        }
+        if (scalar_offset_constant) {
+          out << "[" << (scalar_offset_value / 4) << "]";
+        } else {
+          out << "[" << scalar_offset_expr << " / 4]";
+        }
+        return true;
+      };
+      // vec3 has a minimum alignment of 16 bytes, so is just a .xyz swizzle
+      auto load_vec3 = [&] {
+        if (!load_vec4()) {
+          return false;
+        }
+        out << ".xyz";
+        return true;
+      };
+      switch (intrinsic->type) {
+        case DataType::kU32:
+          return load_scalar();
+        case DataType::kF32:
+          return cast("asfloat", load_scalar);
+        case DataType::kI32:
+          return cast("asint", load_scalar);
+        case DataType::kVec2U32:
+          return load_vec2();
+        case DataType::kVec2F32:
+          return cast("asfloat", load_vec2);
+        case DataType::kVec2I32:
+          return cast("asint", load_vec2);
+        case DataType::kVec3U32:
+          return load_vec3();
+        case DataType::kVec3F32:
+          return cast("asfloat", load_vec3);
+        case DataType::kVec3I32:
+          return cast("asint", load_vec3);
+        case DataType::kVec4U32:
+          return load_vec4();
+        case DataType::kVec4F32:
+          return cast("asfloat", load_vec4);
+        case DataType::kVec4I32:
+          return cast("asint", load_vec4);
+      }
+      TINT_UNREACHABLE(Writer, diagnostics_)
+          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
+          << static_cast<int>(intrinsic->type);
+      return false;
+    }
+    default:
+      break;
+  }
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported DecomposeMemoryAccess::Intrinsic::Op: "
+      << static_cast<int>(intrinsic->op);
+  return false;
+}
+
+bool GeneratorImpl::EmitStorageBufferAccess(
+    std::ostream& out,
+    const ast::CallExpression* expr,
+    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
+  const auto& args = expr->args;
+
+  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
+  using DataType = transform::DecomposeMemoryAccess::Intrinsic::DataType;
+  switch (intrinsic->op) {
+    case Op::kLoad: {
+      auto load = [&](const char* cast, int n) {
+        if (cast) {
+          out << cast << "(";
+        }
+        if (!EmitExpression(out, args[0])) {  // buffer
+          return false;
+        }
+        out << ".Load";
+        if (n > 1) {
+          out << n;
+        }
+        ScopedParen sp(out);
+        if (!EmitExpression(out, args[1])) {  // offset
+          return false;
+        }
+        if (cast) {
+          out << ")";
+        }
+        return true;
+      };
+      switch (intrinsic->type) {
+        case DataType::kU32:
+          return load(nullptr, 1);
+        case DataType::kF32:
+          return load("asfloat", 1);
+        case DataType::kI32:
+          return load("asint", 1);
+        case DataType::kVec2U32:
+          return load(nullptr, 2);
+        case DataType::kVec2F32:
+          return load("asfloat", 2);
+        case DataType::kVec2I32:
+          return load("asint", 2);
+        case DataType::kVec3U32:
+          return load(nullptr, 3);
+        case DataType::kVec3F32:
+          return load("asfloat", 3);
+        case DataType::kVec3I32:
+          return load("asint", 3);
+        case DataType::kVec4U32:
+          return load(nullptr, 4);
+        case DataType::kVec4F32:
+          return load("asfloat", 4);
+        case DataType::kVec4I32:
+          return load("asint", 4);
+      }
+      TINT_UNREACHABLE(Writer, diagnostics_)
+          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
+          << static_cast<int>(intrinsic->type);
+      return false;
+    }
+
+    case Op::kStore: {
+      auto store = [&](int n) {
+        if (!EmitExpression(out, args[0])) {  // buffer
+          return false;
+        }
+        out << ".Store";
+        if (n > 1) {
+          out << n;
+        }
+        ScopedParen sp1(out);
+        if (!EmitExpression(out, args[1])) {  // offset
+          return false;
+        }
+        out << ", asuint";
+        ScopedParen sp2(out);
+        if (!EmitExpression(out, args[2])) {  // value
+          return false;
+        }
+        return true;
+      };
+      switch (intrinsic->type) {
+        case DataType::kU32:
+          return store(1);
+        case DataType::kF32:
+          return store(1);
+        case DataType::kI32:
+          return store(1);
+        case DataType::kVec2U32:
+          return store(2);
+        case DataType::kVec2F32:
+          return store(2);
+        case DataType::kVec2I32:
+          return store(2);
+        case DataType::kVec3U32:
+          return store(3);
+        case DataType::kVec3F32:
+          return store(3);
+        case DataType::kVec3I32:
+          return store(3);
+        case DataType::kVec4U32:
+          return store(4);
+        case DataType::kVec4F32:
+          return store(4);
+        case DataType::kVec4I32:
+          return store(4);
+      }
+      TINT_UNREACHABLE(Writer, diagnostics_)
+          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
+          << static_cast<int>(intrinsic->type);
+      return false;
+    }
+
+    case Op::kAtomicLoad:
+    case Op::kAtomicStore:
+    case Op::kAtomicAdd:
+    case Op::kAtomicSub:
+    case Op::kAtomicMax:
+    case Op::kAtomicMin:
+    case Op::kAtomicAnd:
+    case Op::kAtomicOr:
+    case Op::kAtomicXor:
+    case Op::kAtomicExchange:
+    case Op::kAtomicCompareExchangeWeak:
+      return EmitStorageAtomicCall(out, expr, intrinsic);
+  }
+
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported DecomposeMemoryAccess::Intrinsic::Op: "
+      << static_cast<int>(intrinsic->op);
+  return false;
+}
+
+bool GeneratorImpl::EmitStorageAtomicCall(
+    std::ostream& out,
+    const ast::CallExpression* expr,
+    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
+  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
+
+  auto* result_ty = TypeOf(expr);
+
+  auto& buf = helpers_;
+
+  // generate_helper() generates a helper function that translates the
+  // DecomposeMemoryAccess::Intrinsic call into the corresponding HLSL
+  // atomic intrinsic function.
+  auto generate_helper = [&]() -> std::string {
+    auto rmw = [&](const char* wgsl, const char* hlsl) -> std::string {
+      auto name = UniqueIdentifier(wgsl);
+      {
+        auto fn = line(&buf);
+        if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                             ast::Access::kUndefined, name)) {
+          return "";
+        }
+        fn << "(RWByteAddressBuffer buffer, uint offset, ";
+        if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                             ast::Access::kUndefined, "value")) {
+          return "";
+        }
+        fn << ") {";
+      }
+
+      buf.IncrementIndent();
+      TINT_DEFER({
+        buf.DecrementIndent();
+        line(&buf) << "}";
+        line(&buf);
+      });
+
+      {
+        auto l = line(&buf);
+        if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
+                             ast::Access::kUndefined, "original_value")) {
+          return "";
+        }
+        l << " = 0;";
+      }
+      {
+        auto l = line(&buf);
+        l << "buffer." << hlsl << "(offset, ";
+        if (intrinsic->op == Op::kAtomicSub) {
+          l << "-";
+        }
+        l << "value, original_value);";
+      }
+      line(&buf) << "return original_value;";
+      return name;
+    };
+
+    switch (intrinsic->op) {
+      case Op::kAtomicAdd:
+        return rmw("atomicAdd", "InterlockedAdd");
+
+      case Op::kAtomicSub:
+        // Use add with the operand negated.
+        return rmw("atomicSub", "InterlockedAdd");
+
+      case Op::kAtomicMax:
+        return rmw("atomicMax", "InterlockedMax");
+
+      case Op::kAtomicMin:
+        return rmw("atomicMin", "InterlockedMin");
+
+      case Op::kAtomicAnd:
+        return rmw("atomicAnd", "InterlockedAnd");
+
+      case Op::kAtomicOr:
+        return rmw("atomicOr", "InterlockedOr");
+
+      case Op::kAtomicXor:
+        return rmw("atomicXor", "InterlockedXor");
+
+      case Op::kAtomicExchange:
+        return rmw("atomicExchange", "InterlockedExchange");
+
+      case Op::kAtomicLoad: {
+        // HLSL does not have an InterlockedLoad, so we emulate it with
+        // InterlockedOr using 0 as the OR value
+        auto name = UniqueIdentifier("atomicLoad");
+        {
+          auto fn = line(&buf);
+          if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, name)) {
+            return "";
+          }
+          fn << "(RWByteAddressBuffer buffer, uint offset) {";
+        }
+
+        buf.IncrementIndent();
+        TINT_DEFER({
+          buf.DecrementIndent();
+          line(&buf) << "}";
+          line(&buf);
+        });
+
+        {
+          auto l = line(&buf);
+          if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "value")) {
+            return "";
+          }
+          l << " = 0;";
+        }
+
+        line(&buf) << "buffer.InterlockedOr(offset, 0, value);";
+        line(&buf) << "return value;";
+        return name;
+      }
+      case Op::kAtomicStore: {
+        // HLSL does not have an InterlockedStore, so we emulate it with
+        // InterlockedExchange and discard the returned value
+        auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
+        auto name = UniqueIdentifier("atomicStore");
+        {
+          auto fn = line(&buf);
+          fn << "void " << name << "(RWByteAddressBuffer buffer, uint offset, ";
+          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "value")) {
+            return "";
+          }
+          fn << ") {";
+        }
+
+        buf.IncrementIndent();
+        TINT_DEFER({
+          buf.DecrementIndent();
+          line(&buf) << "}";
+          line(&buf);
+        });
+
+        {
+          auto l = line(&buf);
+          if (!EmitTypeAndName(l, value_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "ignored")) {
+            return "";
+          }
+          l << ";";
+        }
+        line(&buf) << "buffer.InterlockedExchange(offset, value, ignored);";
+        return name;
+      }
+      case Op::kAtomicCompareExchangeWeak: {
+        auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
+
+        auto name = UniqueIdentifier("atomicCompareExchangeWeak");
+        {
+          auto fn = line(&buf);
+          if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, name)) {
+            return "";
+          }
+          fn << "(RWByteAddressBuffer buffer, uint offset, ";
+          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "compare")) {
+            return "";
+          }
+          fn << ", ";
+          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "value")) {
+            return "";
+          }
+          fn << ") {";
+        }
+
+        buf.IncrementIndent();
+        TINT_DEFER({
+          buf.DecrementIndent();
+          line(&buf) << "}";
+          line(&buf);
+        });
+
+        {  // T result = {0, 0};
+          auto l = line(&buf);
+          if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, "result")) {
+            return "";
+          }
+          l << " = {0, 0};";
+        }
+        line(&buf) << "buffer.InterlockedCompareExchange(offset, compare, "
+                      "value, result.x);";
+        line(&buf) << "result.y = result.x == compare;";
+        line(&buf) << "return result;";
+        return name;
+      }
+      default:
+        break;
+    }
+    TINT_UNREACHABLE(Writer, diagnostics_)
+        << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
+        << static_cast<int>(intrinsic->op);
+    return "";
+  };
+
+  auto func = utils::GetOrCreate(dma_intrinsics_,
+                                 DMAIntrinsic{intrinsic->op, intrinsic->type},
+                                 generate_helper);
+  if (func.empty()) {
+    return false;
+  }
+
+  out << func;
+  {
+    ScopedParen sp(out);
+    bool first = true;
+    for (auto* arg : expr->args) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+      if (!EmitExpression(out, arg)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
+                                            const ast::CallExpression* expr,
+                                            const sem::Builtin* builtin) {
+  std::string result = UniqueIdentifier("atomic_result");
+
+  if (!builtin->ReturnType()->Is<sem::Void>()) {
+    auto pre = line();
+    if (!EmitTypeAndName(pre, builtin->ReturnType(), ast::StorageClass::kNone,
+                         ast::Access::kUndefined, result)) {
+      return false;
+    }
+    pre << " = ";
+    if (!EmitZeroValue(pre, builtin->ReturnType())) {
+      return false;
+    }
+    pre << ";";
+  }
+
+  auto call = [&](const char* name) {
+    auto pre = line();
+    pre << name;
+
+    {
+      ScopedParen sp(pre);
+      for (size_t i = 0; i < expr->args.size(); i++) {
+        auto* arg = expr->args[i];
+        if (i > 0) {
+          pre << ", ";
+        }
+        if (i == 1 && builtin->Type() == sem::BuiltinType::kAtomicSub) {
+          // Sub uses InterlockedAdd with the operand negated.
+          pre << "-";
+        }
+        if (!EmitExpression(pre, arg)) {
+          return false;
+        }
+      }
+
+      pre << ", " << result;
+    }
+
+    pre << ";";
+
+    out << result;
+    return true;
+  };
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAtomicLoad: {
+      // HLSL does not have an InterlockedLoad, so we emulate it with
+      // InterlockedOr using 0 as the OR value
+      auto pre = line();
+      pre << "InterlockedOr";
+      {
+        ScopedParen sp(pre);
+        if (!EmitExpression(pre, expr->args[0])) {
+          return false;
+        }
+        pre << ", 0, " << result;
+      }
+      pre << ";";
+
+      out << result;
+      return true;
+    }
+    case sem::BuiltinType::kAtomicStore: {
+      // HLSL does not have an InterlockedStore, so we emulate it with
+      // InterlockedExchange and discard the returned value
+      {  // T result = 0;
+        auto pre = line();
+        auto* value_ty = builtin->Parameters()[1]->Type()->UnwrapRef();
+        if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone,
+                             ast::Access::kUndefined, result)) {
+          return false;
+        }
+        pre << " = ";
+        if (!EmitZeroValue(pre, value_ty)) {
+          return false;
+        }
+        pre << ";";
+      }
+
+      out << "InterlockedExchange";
+      {
+        ScopedParen sp(out);
+        if (!EmitExpression(out, expr->args[0])) {
+          return false;
+        }
+        out << ", ";
+        if (!EmitExpression(out, expr->args[1])) {
+          return false;
+        }
+        out << ", " << result;
+      }
+      return true;
+    }
+    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+      auto* dest = expr->args[0];
+      auto* compare_value = expr->args[1];
+      auto* value = expr->args[2];
+
+      std::string compare = UniqueIdentifier("atomic_compare_value");
+
+      {  // T compare_value = <compare_value>;
+        auto pre = line();
+        if (!EmitTypeAndName(pre, TypeOf(compare_value),
+                             ast::StorageClass::kNone, ast::Access::kUndefined,
+                             compare)) {
+          return false;
+        }
+        pre << " = ";
+        if (!EmitExpression(pre, compare_value)) {
+          return false;
+        }
+        pre << ";";
+      }
+
+      {  // InterlockedCompareExchange(dst, compare, value, result.x);
+        auto pre = line();
+        pre << "InterlockedCompareExchange";
+        {
+          ScopedParen sp(pre);
+          if (!EmitExpression(pre, dest)) {
+            return false;
+          }
+          pre << ", " << compare << ", ";
+          if (!EmitExpression(pre, value)) {
+            return false;
+          }
+          pre << ", " << result << ".x";
+        }
+        pre << ";";
+      }
+
+      {  // result.y = result.x == compare;
+        line() << result << ".y = " << result << ".x == " << compare << ";";
+      }
+
+      out << result;
+      return true;
+    }
+
+    case sem::BuiltinType::kAtomicAdd:
+    case sem::BuiltinType::kAtomicSub:
+      return call("InterlockedAdd");
+
+    case sem::BuiltinType::kAtomicMax:
+      return call("InterlockedMax");
+
+    case sem::BuiltinType::kAtomicMin:
+      return call("InterlockedMin");
+
+    case sem::BuiltinType::kAtomicAnd:
+      return call("InterlockedAnd");
+
+    case sem::BuiltinType::kAtomicOr:
+      return call("InterlockedOr");
+
+    case sem::BuiltinType::kAtomicXor:
+      return call("InterlockedXor");
+
+    case sem::BuiltinType::kAtomicExchange:
+      return call("InterlockedExchange");
+
+    default:
+      break;
+  }
+
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported atomic builtin: " << builtin->Type();
+  return false;
+}
+
+bool GeneratorImpl::EmitSelectCall(std::ostream& out,
+                                   const ast::CallExpression* expr) {
+  auto* expr_false = expr->args[0];
+  auto* expr_true = expr->args[1];
+  auto* expr_cond = expr->args[2];
+  ScopedParen paren(out);
+  if (!EmitExpression(out, expr_cond)) {
+    return false;
+  }
+
+  out << " ? ";
+
+  if (!EmitExpression(out, expr_true)) {
+    return false;
+  }
+
+  out << " : ";
+
+  if (!EmitExpression(out, expr_false)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitModfCall(std::ostream& out,
+                                 const ast::CallExpression* expr,
+                                 const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* ty = builtin->Parameters()[0]->Type();
+        auto in = params[0];
+
+        std::string width;
+        if (auto* vec = ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        // Emit the builtin return type unique to this overload. This does not
+        // exist in the AST, so it will not be generated in Generate().
+        if (!EmitStructType(&helpers_,
+                            builtin->ReturnType()->As<sem::Struct>())) {
+          return false;
+        }
+
+        line(b) << "float" << width << " whole;";
+        line(b) << "float" << width << " fract = modf(" << in << ", whole);";
+        {
+          auto l = line(b);
+          if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
+                        ast::Access::kUndefined, "")) {
+            return false;
+          }
+          l << " result = {fract, whole};";
+        }
+        line(b) << "return result;";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
+                                  const ast::CallExpression* expr,
+                                  const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* ty = builtin->Parameters()[0]->Type();
+        auto in = params[0];
+
+        std::string width;
+        if (auto* vec = ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        // Emit the builtin return type unique to this overload. This does not
+        // exist in the AST, so it will not be generated in Generate().
+        if (!EmitStructType(&helpers_,
+                            builtin->ReturnType()->As<sem::Struct>())) {
+          return false;
+        }
+
+        line(b) << "float" << width << " exp;";
+        line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
+        {
+          auto l = line(b);
+          if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
+                        ast::Access::kUndefined, "")) {
+            return false;
+          }
+          l << " result = {sig, int" << width << "(exp)};";
+        }
+        line(b) << "return result;";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitIsNormalCall(std::ostream& out,
+                                     const ast::CallExpression* expr,
+                                     const sem::Builtin* builtin) {
+  // HLSL doesn't have a isNormal builtin, we need to emulate
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* input_ty = builtin->Parameters()[0]->Type();
+
+        std::string width;
+        if (auto* vec = input_ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        constexpr auto* kExponentMask = "0x7f80000";
+        constexpr auto* kMinNormalExponent = "0x0080000";
+        constexpr auto* kMaxNormalExponent = "0x7f00000";
+
+        line(b) << "uint" << width << " exponent = asuint(" << params[0]
+                << ") & " << kExponentMask << ";";
+        line(b) << "uint" << width << " clamped = "
+                << "clamp(exponent, " << kMinNormalExponent << ", "
+                << kMaxNormalExponent << ");";
+        line(b) << "return clamped == exponent;";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kRadToDeg << ";";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kDegToRad << ";";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDataPackingCall(std::ostream& out,
+                                        const ast::CallExpression* expr,
+                                        const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        uint32_t dims = 2;
+        bool is_signed = false;
+        uint32_t scale = 65535;
+        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kPack4x8unorm) {
+          dims = 4;
+          scale = 255;
+        }
+        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kPack2x16snorm) {
+          is_signed = true;
+          scale = (scale - 1) / 2;
+        }
+        switch (builtin->Type()) {
+          case sem::BuiltinType::kPack4x8snorm:
+          case sem::BuiltinType::kPack4x8unorm:
+          case sem::BuiltinType::kPack2x16snorm:
+          case sem::BuiltinType::kPack2x16unorm: {
+            {
+              auto l = line(b);
+              l << (is_signed ? "" : "u") << "int" << dims
+                << " i = " << (is_signed ? "" : "u") << "int" << dims
+                << "(round(clamp(" << params[0] << ", "
+                << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale
+                << ".0))";
+              if (is_signed) {
+                l << " & " << (dims == 4 ? "0xff" : "0xffff");
+              }
+              l << ";";
+            }
+            {
+              auto l = line(b);
+              l << "return ";
+              if (is_signed) {
+                l << "asuint";
+              }
+              l << "(i.x | i.y << " << (32 / dims);
+              if (dims == 4) {
+                l << " | i.z << 16 | i.w << 24";
+              }
+              l << ");";
+            }
+            break;
+          }
+          case sem::BuiltinType::kPack2x16float: {
+            line(b) << "uint2 i = f32tof16(" << params[0] << ");";
+            line(b) << "return i.x | (i.y << 16);";
+            break;
+          }
+          default:
+            diagnostics_.add_error(
+                diag::System::Writer,
+                "Internal error: unhandled data packing builtin");
+            return false;
+        }
+
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out,
+                                          const ast::CallExpression* expr,
+                                          const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        uint32_t dims = 2;
+        bool is_signed = false;
+        uint32_t scale = 65535;
+        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kUnpack4x8unorm) {
+          dims = 4;
+          scale = 255;
+        }
+        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
+            builtin->Type() == sem::BuiltinType::kUnpack2x16snorm) {
+          is_signed = true;
+          scale = (scale - 1) / 2;
+        }
+        switch (builtin->Type()) {
+          case sem::BuiltinType::kUnpack4x8snorm:
+          case sem::BuiltinType::kUnpack2x16snorm: {
+            line(b) << "int j = int(" << params[0] << ");";
+            {  // Perform sign extension on the converted values.
+              auto l = line(b);
+              l << "int" << dims << " i = int" << dims << "(";
+              if (dims == 2) {
+                l << "j << 16, j) >> 16";
+              } else {
+                l << "j << 24, j << 16, j << 8, j) >> 24";
+              }
+              l << ";";
+            }
+            line(b) << "return clamp(float" << dims << "(i) / " << scale
+                    << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
+            break;
+          }
+          case sem::BuiltinType::kUnpack4x8unorm:
+          case sem::BuiltinType::kUnpack2x16unorm: {
+            line(b) << "uint j = " << params[0] << ";";
+            {
+              auto l = line(b);
+              l << "uint" << dims << " i = uint" << dims << "(";
+              l << "j & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
+              if (dims == 4) {
+                l << "(j >> " << (32 / dims)
+                  << ") & 0xff, (j >> 16) & 0xff, j >> 24";
+              } else {
+                l << "j >> " << (32 / dims);
+              }
+              l << ");";
+            }
+            line(b) << "return float" << dims << "(i) / " << scale << ".0;";
+            break;
+          }
+          case sem::BuiltinType::kUnpack2x16float:
+            line(b) << "uint i = " << params[0] << ";";
+            line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
+            break;
+          default:
+            diagnostics_.add_error(
+                diag::System::Writer,
+                "Internal error: unhandled data packing builtin");
+            return false;
+        }
+
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitBarrierCall(std::ostream& out,
+                                    const sem::Builtin* builtin) {
+  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
+  // instruction.
+  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    out << "GroupMemoryBarrierWithGroupSync()";
+  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    out << "DeviceMemoryBarrierWithGroupSync()";
+  } else {
+    TINT_UNREACHABLE(Writer, diagnostics_)
+        << "unexpected barrier builtin type " << sem::str(builtin->Type());
+    return false;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitTextureCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  using Usage = sem::ParameterUsage;
+
+  auto& signature = builtin->Signature();
+  auto* expr = call->Declaration();
+  auto arguments = expr->args;
+
+  // Returns the argument with the given usage
+  auto arg = [&](Usage usage) {
+    int idx = signature.IndexOf(usage);
+    return (idx >= 0) ? arguments[idx] : nullptr;
+  };
+
+  auto* texture = arg(Usage::kTexture);
+  if (!texture) {
+    TINT_ICE(Writer, diagnostics_) << "missing texture argument";
+    return false;
+  }
+
+  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureDimensions:
+    case sem::BuiltinType::kTextureNumLayers:
+    case sem::BuiltinType::kTextureNumLevels:
+    case sem::BuiltinType::kTextureNumSamples: {
+      // All of these builtins use the GetDimensions() method on the texture
+      bool is_ms = texture_type->IsAnyOf<sem::MultisampledTexture,
+                                         sem::DepthMultisampledTexture>();
+      int num_dimensions = 0;
+      std::string swizzle;
+
+      switch (builtin->Type()) {
+        case sem::BuiltinType::kTextureDimensions:
+          switch (texture_type->dim()) {
+            case ast::TextureDimension::kNone:
+              TINT_ICE(Writer, diagnostics_) << "texture dimension is kNone";
+              return false;
+            case ast::TextureDimension::k1d:
+              num_dimensions = 1;
+              break;
+            case ast::TextureDimension::k2d:
+              num_dimensions = is_ms ? 3 : 2;
+              swizzle = is_ms ? ".xy" : "";
+              break;
+            case ast::TextureDimension::k2dArray:
+              num_dimensions = is_ms ? 4 : 3;
+              swizzle = ".xy";
+              break;
+            case ast::TextureDimension::k3d:
+              num_dimensions = 3;
+              break;
+            case ast::TextureDimension::kCube:
+              num_dimensions = 2;
+              break;
+            case ast::TextureDimension::kCubeArray:
+              num_dimensions = 3;
+              swizzle = ".xy";
+              break;
+          }
+          break;
+        case sem::BuiltinType::kTextureNumLayers:
+          switch (texture_type->dim()) {
+            default:
+              TINT_ICE(Writer, diagnostics_)
+                  << "texture dimension is not arrayed";
+              return false;
+            case ast::TextureDimension::k2dArray:
+              num_dimensions = is_ms ? 4 : 3;
+              swizzle = ".z";
+              break;
+            case ast::TextureDimension::kCubeArray:
+              num_dimensions = 3;
+              swizzle = ".z";
+              break;
+          }
+          break;
+        case sem::BuiltinType::kTextureNumLevels:
+          switch (texture_type->dim()) {
+            default:
+              TINT_ICE(Writer, diagnostics_)
+                  << "texture dimension does not support mips";
+              return false;
+            case ast::TextureDimension::k1d:
+              num_dimensions = 2;
+              swizzle = ".y";
+              break;
+            case ast::TextureDimension::k2d:
+            case ast::TextureDimension::kCube:
+              num_dimensions = 3;
+              swizzle = ".z";
+              break;
+            case ast::TextureDimension::k2dArray:
+            case ast::TextureDimension::k3d:
+            case ast::TextureDimension::kCubeArray:
+              num_dimensions = 4;
+              swizzle = ".w";
+              break;
+          }
+          break;
+        case sem::BuiltinType::kTextureNumSamples:
+          switch (texture_type->dim()) {
+            default:
+              TINT_ICE(Writer, diagnostics_)
+                  << "texture dimension does not support multisampling";
+              return false;
+            case ast::TextureDimension::k2d:
+              num_dimensions = 3;
+              swizzle = ".z";
+              break;
+            case ast::TextureDimension::k2dArray:
+              num_dimensions = 4;
+              swizzle = ".w";
+              break;
+          }
+          break;
+        default:
+          TINT_ICE(Writer, diagnostics_) << "unexpected builtin";
+          return false;
+      }
+
+      auto* level_arg = arg(Usage::kLevel);
+
+      if (level_arg) {
+        // `NumberOfLevels` is a non-optional argument if `MipLevel` was passed.
+        // Increment the number of dimensions for the temporary vector to
+        // accommodate this.
+        num_dimensions++;
+
+        // If the swizzle was empty, the expression will evaluate to the whole
+        // vector. As we've grown the vector by one element, we now need to
+        // swizzle to keep the result expression equivalent.
+        if (swizzle.empty()) {
+          static constexpr const char* swizzles[] = {"", ".x", ".xy", ".xyz"};
+          swizzle = swizzles[num_dimensions - 1];
+        }
+      }
+
+      if (num_dimensions > 4) {
+        TINT_ICE(Writer, diagnostics_)
+            << "Texture query builtin temporary vector has " << num_dimensions
+            << " dimensions";
+        return false;
+      }
+
+      // Declare a variable to hold the queried texture info
+      auto dims = UniqueIdentifier(kTempNamePrefix);
+      if (num_dimensions == 1) {
+        line() << "int " << dims << ";";
+      } else {
+        line() << "int" << num_dimensions << " " << dims << ";";
+      }
+
+      {  // texture.GetDimensions(...)
+        auto pre = line();
+        if (!EmitExpression(pre, texture)) {
+          return false;
+        }
+        pre << ".GetDimensions(";
+
+        if (level_arg) {
+          if (!EmitExpression(pre, level_arg)) {
+            return false;
+          }
+          pre << ", ";
+        } else if (builtin->Type() == sem::BuiltinType::kTextureNumLevels) {
+          pre << "0, ";
+        }
+
+        if (num_dimensions == 1) {
+          pre << dims;
+        } else {
+          static constexpr char xyzw[] = {'x', 'y', 'z', 'w'};
+          if (num_dimensions < 0 || num_dimensions > 4) {
+            TINT_ICE(Writer, diagnostics_)
+                << "vector dimensions are " << num_dimensions;
+            return false;
+          }
+          for (int i = 0; i < num_dimensions; i++) {
+            if (i > 0) {
+              pre << ", ";
+            }
+            pre << dims << "." << xyzw[i];
+          }
+        }
+
+        pre << ");";
+      }
+
+      // The out parameters of the GetDimensions() call is now in temporary
+      // `dims` variable. This may be packed with other data, so the final
+      // expression may require a swizzle.
+      out << dims << swizzle;
+      return true;
+    }
+    default:
+      break;
+  }
+
+  if (!EmitExpression(out, texture))
+    return false;
+
+  // If pack_level_in_coords is true, then the mip level will be appended as the
+  // last value of the coordinates argument. If the WGSL builtin overload does
+  // not have a level parameter and pack_level_in_coords is true, then a zero
+  // mip level will be inserted.
+  bool pack_level_in_coords = false;
+
+  uint32_t hlsl_ret_width = 4u;
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureSample:
+      out << ".Sample(";
+      break;
+    case sem::BuiltinType::kTextureSampleBias:
+      out << ".SampleBias(";
+      break;
+    case sem::BuiltinType::kTextureSampleLevel:
+      out << ".SampleLevel(";
+      break;
+    case sem::BuiltinType::kTextureSampleGrad:
+      out << ".SampleGrad(";
+      break;
+    case sem::BuiltinType::kTextureSampleCompare:
+      out << ".SampleCmp(";
+      hlsl_ret_width = 1;
+      break;
+    case sem::BuiltinType::kTextureSampleCompareLevel:
+      out << ".SampleCmpLevelZero(";
+      hlsl_ret_width = 1;
+      break;
+    case sem::BuiltinType::kTextureLoad:
+      out << ".Load(";
+      // Multisampled textures do not support mip-levels.
+      if (!texture_type->Is<sem::MultisampledTexture>()) {
+        pack_level_in_coords = true;
+      }
+      break;
+    case sem::BuiltinType::kTextureGather:
+      out << ".Gather";
+      if (builtin->Parameters()[0]->Usage() ==
+          sem::ParameterUsage::kComponent) {
+        switch (call->Arguments()[0]->ConstantValue().Elements()[0].i32) {
+          case 0:
+            out << "Red";
+            break;
+          case 1:
+            out << "Green";
+            break;
+          case 2:
+            out << "Blue";
+            break;
+          case 3:
+            out << "Alpha";
+            break;
+        }
+      }
+      out << "(";
+      break;
+    case sem::BuiltinType::kTextureGatherCompare:
+      out << ".GatherCmp(";
+      break;
+    case sem::BuiltinType::kTextureStore:
+      out << "[";
+      break;
+    default:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Internal compiler error: Unhandled texture builtin '" +
+              std::string(builtin->str()) + "'");
+      return false;
+  }
+
+  if (auto* sampler = arg(Usage::kSampler)) {
+    if (!EmitExpression(out, sampler))
+      return false;
+    out << ", ";
+  }
+
+  auto* param_coords = arg(Usage::kCoords);
+  if (!param_coords) {
+    TINT_ICE(Writer, diagnostics_) << "missing coords argument";
+    return false;
+  }
+
+  auto emit_vector_appended_with_i32_zero = [&](const ast::Expression* vector) {
+    auto* i32 = builder_.create<sem::I32>();
+    auto* zero = builder_.Expr(0);
+    auto* stmt = builder_.Sem().Get(vector)->Stmt();
+    builder_.Sem().Add(
+        zero, builder_.create<sem::Expression>(zero, i32, stmt, sem::Constant{},
+                                               /* has_side_effects */ false));
+    auto* packed = AppendVector(&builder_, vector, zero);
+    return EmitExpression(out, packed->Declaration());
+  };
+
+  auto emit_vector_appended_with_level = [&](const ast::Expression* vector) {
+    if (auto* level = arg(Usage::kLevel)) {
+      auto* packed = AppendVector(&builder_, vector, level);
+      return EmitExpression(out, packed->Declaration());
+    }
+    return emit_vector_appended_with_i32_zero(vector);
+  };
+
+  if (auto* array_index = arg(Usage::kArrayIndex)) {
+    // Array index needs to be appended to the coordinates.
+    auto* packed = AppendVector(&builder_, param_coords, array_index);
+    if (pack_level_in_coords) {
+      // Then mip level needs to be appended to the coordinates.
+      if (!emit_vector_appended_with_level(packed->Declaration())) {
+        return false;
+      }
+    } else {
+      if (!EmitExpression(out, packed->Declaration())) {
+        return false;
+      }
+    }
+  } else if (pack_level_in_coords) {
+    // Mip level needs to be appended to the coordinates.
+    if (!emit_vector_appended_with_level(param_coords)) {
+      return false;
+    }
+  } else {
+    if (!EmitExpression(out, param_coords)) {
+      return false;
+    }
+  }
+
+  for (auto usage : {Usage::kDepthRef, Usage::kBias, Usage::kLevel, Usage::kDdx,
+                     Usage::kDdy, Usage::kSampleIndex, Usage::kOffset}) {
+    if (usage == Usage::kLevel && pack_level_in_coords) {
+      continue;  // mip level already packed in coordinates.
+    }
+    if (auto* e = arg(usage)) {
+      out << ", ";
+      if (!EmitExpression(out, e)) {
+        return false;
+      }
+    }
+  }
+
+  if (builtin->Type() == sem::BuiltinType::kTextureStore) {
+    out << "] = ";
+    if (!EmitExpression(out, arg(Usage::kValue))) {
+      return false;
+    }
+  } else {
+    out << ")";
+
+    // If the builtin return type does not match the number of elements of the
+    // HLSL builtin, we need to swizzle the expression to generate the correct
+    // number of components.
+    uint32_t wgsl_ret_width = 1;
+    if (auto* vec = builtin->ReturnType()->As<sem::Vector>()) {
+      wgsl_ret_width = vec->Width();
+    }
+    if (wgsl_ret_width < hlsl_ret_width) {
+      out << ".";
+      for (uint32_t i = 0; i < wgsl_ret_width; i++) {
+        out << "xyz"[i];
+      }
+    }
+    if (wgsl_ret_width > hlsl_ret_width) {
+      TINT_ICE(Writer, diagnostics_)
+          << "WGSL return width (" << wgsl_ret_width
+          << ") is wider than HLSL return width (" << hlsl_ret_width << ") for "
+          << builtin->Type();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAbs:
+    case sem::BuiltinType::kAcos:
+    case sem::BuiltinType::kAll:
+    case sem::BuiltinType::kAny:
+    case sem::BuiltinType::kAsin:
+    case sem::BuiltinType::kAtan:
+    case sem::BuiltinType::kAtan2:
+    case sem::BuiltinType::kCeil:
+    case sem::BuiltinType::kClamp:
+    case sem::BuiltinType::kCos:
+    case sem::BuiltinType::kCosh:
+    case sem::BuiltinType::kCross:
+    case sem::BuiltinType::kDeterminant:
+    case sem::BuiltinType::kDistance:
+    case sem::BuiltinType::kDot:
+    case sem::BuiltinType::kExp:
+    case sem::BuiltinType::kExp2:
+    case sem::BuiltinType::kFloor:
+    case sem::BuiltinType::kFrexp:
+    case sem::BuiltinType::kLdexp:
+    case sem::BuiltinType::kLength:
+    case sem::BuiltinType::kLog:
+    case sem::BuiltinType::kLog2:
+    case sem::BuiltinType::kMax:
+    case sem::BuiltinType::kMin:
+    case sem::BuiltinType::kModf:
+    case sem::BuiltinType::kNormalize:
+    case sem::BuiltinType::kPow:
+    case sem::BuiltinType::kReflect:
+    case sem::BuiltinType::kRefract:
+    case sem::BuiltinType::kRound:
+    case sem::BuiltinType::kSign:
+    case sem::BuiltinType::kSin:
+    case sem::BuiltinType::kSinh:
+    case sem::BuiltinType::kSqrt:
+    case sem::BuiltinType::kStep:
+    case sem::BuiltinType::kTan:
+    case sem::BuiltinType::kTanh:
+    case sem::BuiltinType::kTranspose:
+    case sem::BuiltinType::kTrunc:
+      return builtin->str();
+    case sem::BuiltinType::kCountOneBits:
+      return "countbits";
+    case sem::BuiltinType::kDpdx:
+      return "ddx";
+    case sem::BuiltinType::kDpdxCoarse:
+      return "ddx_coarse";
+    case sem::BuiltinType::kDpdxFine:
+      return "ddx_fine";
+    case sem::BuiltinType::kDpdy:
+      return "ddy";
+    case sem::BuiltinType::kDpdyCoarse:
+      return "ddy_coarse";
+    case sem::BuiltinType::kDpdyFine:
+      return "ddy_fine";
+    case sem::BuiltinType::kFaceForward:
+      return "faceforward";
+    case sem::BuiltinType::kFract:
+      return "frac";
+    case sem::BuiltinType::kFma:
+      return "mad";
+    case sem::BuiltinType::kFwidth:
+    case sem::BuiltinType::kFwidthCoarse:
+    case sem::BuiltinType::kFwidthFine:
+      return "fwidth";
+    case sem::BuiltinType::kInverseSqrt:
+      return "rsqrt";
+    case sem::BuiltinType::kIsFinite:
+      return "isfinite";
+    case sem::BuiltinType::kIsInf:
+      return "isinf";
+    case sem::BuiltinType::kIsNan:
+      return "isnan";
+    case sem::BuiltinType::kMix:
+      return "lerp";
+    case sem::BuiltinType::kReverseBits:
+      return "reversebits";
+    case sem::BuiltinType::kSmoothStep:
+      return "smoothstep";
+    default:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Unknown builtin method: " + std::string(builtin->str()));
+  }
+
+  return "";
+}
+
+bool GeneratorImpl::EmitCase(const ast::SwitchStatement* s, size_t case_idx) {
+  auto* stmt = s->body[case_idx];
+  if (stmt->IsDefault()) {
+    line() << "default: {";
+  } else {
+    for (auto* selector : stmt->selectors) {
+      auto out = line();
+      out << "case ";
+      if (!EmitLiteral(out, selector)) {
+        return false;
+      }
+      out << ":";
+      if (selector == stmt->selectors.back()) {
+        out << " {";
+      }
+    }
+  }
+
+  increment_indent();
+  TINT_DEFER({
+    decrement_indent();
+    line() << "}";
+  });
+
+  // Emit the case statement
+  if (!EmitStatements(stmt->body->statements)) {
+    return false;
+  }
+
+  // Inline all fallthrough case statements. FXC cannot handle fallthroughs.
+  while (tint::Is<ast::FallthroughStatement>(stmt->body->Last())) {
+    case_idx++;
+    stmt = s->body[case_idx];
+    // Generate each fallthrough case statement in a new block. This is done to
+    // prevent symbol collision of variables declared in these cases statements.
+    if (!EmitBlock(stmt->body)) {
+      return false;
+    }
+  }
+
+  if (!tint::IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(
+          stmt->body->Last())) {
+    line() << "break;";
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
+  if (!emit_continuing_()) {
+    return false;
+  }
+  line() << "continue;";
+  return true;
+}
+
+bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
+  // TODO(dsinclair): Verify this is correct when the discard semantics are
+  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
+  line() << "discard;";
+  return true;
+}
+
+bool GeneratorImpl::EmitExpression(std::ostream& out,
+                                   const ast::Expression* expr) {
+  return Switch(
+      expr,
+      [&](const ast::IndexAccessorExpression* a) {  //
+        return EmitIndexAccessor(out, a);
+      },
+      [&](const ast::BinaryExpression* b) {  //
+        return EmitBinary(out, b);
+      },
+      [&](const ast::BitcastExpression* b) {  //
+        return EmitBitcast(out, b);
+      },
+      [&](const ast::CallExpression* c) {  //
+        return EmitCall(out, c);
+      },
+      [&](const ast::IdentifierExpression* i) {  //
+        return EmitIdentifier(out, i);
+      },
+      [&](const ast::LiteralExpression* l) {  //
+        return EmitLiteral(out, l);
+      },
+      [&](const ast::MemberAccessorExpression* m) {  //
+        return EmitMemberAccessor(out, m);
+      },
+      [&](const ast::UnaryOpExpression* u) {  //
+        return EmitUnaryOp(out, u);
+      },
+      [&](Default) {  //
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown expression type: " + std::string(expr->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
+                                   const ast::IdentifierExpression* expr) {
+  out << builder_.Symbols().NameFor(expr->symbol);
+  return true;
+}
+
+bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
+  {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      line() << "} else {";
+      increment_indent();
+
+      {
+        auto out = line();
+        out << "if (";
+        if (!EmitExpression(out, e->condition)) {
+          return false;
+        }
+        out << ") {";
+      }
+    } else {
+      line() << "} else {";
+    }
+
+    if (!EmitStatementsWithIndent(e->body->statements)) {
+      return false;
+    }
+  }
+
+  line() << "}";
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      decrement_indent();
+      line() << "}";
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitFunction(const ast::Function* func) {
+  auto* sem = builder_.Sem().Get(func);
+
+  if (ast::HasAttribute<ast::InternalAttribute>(func->attributes)) {
+    // An internal function. Do not emit.
+    return true;
+  }
+
+  {
+    auto out = line();
+    auto name = builder_.Symbols().NameFor(func->symbol);
+    // If the function returns an array, then we need to declare a typedef for
+    // this.
+    if (sem->ReturnType()->Is<sem::Array>()) {
+      auto typedef_name = UniqueIdentifier(name + "_ret");
+      auto pre = line();
+      pre << "typedef ";
+      if (!EmitTypeAndName(pre, sem->ReturnType(), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, typedef_name)) {
+        return false;
+      }
+      pre << ";";
+      out << typedef_name;
+    } else {
+      if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
+                    ast::Access::kReadWrite, "")) {
+        return false;
+      }
+    }
+
+    out << " " << name << "(";
+
+    bool first = true;
+
+    for (auto* v : sem->Parameters()) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      auto const* type = v->Type();
+
+      if (auto* ptr = type->As<sem::Pointer>()) {
+        // Transform pointer parameters in to `inout` parameters.
+        // The WGSL spec is highly restrictive in what can be passed in pointer
+        // parameters, which allows for this transformation. See:
+        // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
+        out << "inout ";
+        type = ptr->StoreType();
+      }
+
+      // Note: WGSL only allows for StorageClass::kNone on parameters, however
+      // the sanitizer transforms generates load / store functions for storage
+      // or uniform buffers. These functions have a buffer parameter with
+      // StorageClass::kStorage or StorageClass::kUniform. This is required to
+      // correctly translate the parameter to a [RW]ByteAddressBuffer for
+      // storage buffers and a uint4[N] for uniform buffers.
+      if (!EmitTypeAndName(
+              out, type, v->StorageClass(), v->Access(),
+              builder_.Symbols().NameFor(v->Declaration()->symbol))) {
+        return false;
+      }
+    }
+    out << ") {";
+  }
+
+  if (sem->HasDiscard() && !sem->ReturnType()->Is<sem::Void>()) {
+    // BUG(crbug.com/tint/1081): work around non-void functions with discard
+    // failing compilation sometimes
+    if (!EmitFunctionBodyWithDiscard(func)) {
+      return false;
+    }
+  } else {
+    if (!EmitStatementsWithIndent(func->body->statements)) {
+      return false;
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitFunctionBodyWithDiscard(const ast::Function* func) {
+  // FXC sometimes fails to compile functions that discard with 'Not all control
+  // paths return a value'. We work around this by wrapping the function body
+  // within an "if (true) { <body> } return <default return type obj>;" so that
+  // there is always an (unused) return statement.
+
+  auto* sem = builder_.Sem().Get(func);
+  TINT_ASSERT(Writer, sem->HasDiscard() && !sem->ReturnType()->Is<sem::Void>());
+
+  ScopedIndent si(this);
+  line() << "if (true) {";
+
+  if (!EmitStatementsWithIndent(func->body->statements)) {
+    return false;
+  }
+
+  line() << "}";
+
+  // Return an unused result that matches the type of the return value
+  auto name = builder_.Symbols().NameFor(builder_.Symbols().New("unused"));
+  {
+    auto out = line();
+    if (!EmitTypeAndName(out, sem->ReturnType(), ast::StorageClass::kNone,
+                         ast::Access::kReadWrite, name)) {
+      return false;
+    }
+    out << ";";
+  }
+  line() << "return " << name << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitGlobalVariable(const ast::Variable* global) {
+  if (global->is_const) {
+    return EmitProgramConstVariable(global);
+  }
+
+  auto* sem = builder_.Sem().Get(global);
+  switch (sem->StorageClass()) {
+    case ast::StorageClass::kUniform:
+      return EmitUniformVariable(sem);
+    case ast::StorageClass::kStorage:
+      return EmitStorageVariable(sem);
+    case ast::StorageClass::kUniformConstant:
+      return EmitHandleVariable(sem);
+    case ast::StorageClass::kPrivate:
+      return EmitPrivateVariable(sem);
+    case ast::StorageClass::kWorkgroup:
+      return EmitWorkgroupVariable(sem);
+    default:
+      break;
+  }
+
+  TINT_ICE(Writer, diagnostics_)
+      << "unhandled storage class " << sem->StorageClass();
+  return false;
+}
+
+bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto binding_point = decl->BindingPoint();
+  auto* type = var->Type()->UnwrapRef();
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  line() << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point)
+         << " {";
+
+  {
+    ScopedIndent si(this);
+    auto out = line();
+    if (!EmitTypeAndName(out, type, ast::StorageClass::kUniform, var->Access(),
+                         name)) {
+      return false;
+    }
+    out << ";";
+  }
+
+  line() << "};";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto* type = var->Type()->UnwrapRef();
+  auto out = line();
+  if (!EmitTypeAndName(out, type, ast::StorageClass::kStorage, var->Access(),
+                       builder_.Symbols().NameFor(decl->symbol))) {
+    return false;
+  }
+
+  out << RegisterAndSpace(var->Access() == ast::Access::kRead ? 't' : 'u',
+                          decl->BindingPoint())
+      << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto* unwrapped_type = var->Type()->UnwrapRef();
+  auto out = line();
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  const char* register_space = nullptr;
+
+  if (unwrapped_type->Is<sem::Texture>()) {
+    register_space = "t";
+    if (unwrapped_type->Is<sem::StorageTexture>()) {
+      register_space = "u";
+    }
+  } else if (unwrapped_type->Is<sem::Sampler>()) {
+    register_space = "s";
+  }
+
+  if (register_space) {
+    auto bp = decl->BindingPoint();
+    out << " : register(" << register_space << bp.binding->value << ", space"
+        << bp.group->value << ")";
+  }
+
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
+  out << "static ";
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  out << " = ";
+  if (auto* constructor = decl->constructor) {
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, var->Type()->UnwrapRef())) {
+      return false;
+    }
+  }
+
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
+  out << "groupshared ";
+
+  auto name = builder_.Symbols().NameFor(decl->symbol);
+  auto* type = var->Type()->UnwrapRef();
+  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
+    return false;
+  }
+
+  if (auto* constructor = decl->constructor) {
+    out << " = ";
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  }
+
+  out << ";";
+  return true;
+}
+
+std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
+  switch (builtin) {
+    case ast::Builtin::kPosition:
+      return "SV_Position";
+    case ast::Builtin::kVertexIndex:
+      return "SV_VertexID";
+    case ast::Builtin::kInstanceIndex:
+      return "SV_InstanceID";
+    case ast::Builtin::kFrontFacing:
+      return "SV_IsFrontFace";
+    case ast::Builtin::kFragDepth:
+      return "SV_Depth";
+    case ast::Builtin::kLocalInvocationId:
+      return "SV_GroupThreadID";
+    case ast::Builtin::kLocalInvocationIndex:
+      return "SV_GroupIndex";
+    case ast::Builtin::kGlobalInvocationId:
+      return "SV_DispatchThreadID";
+    case ast::Builtin::kWorkgroupId:
+      return "SV_GroupID";
+    case ast::Builtin::kSampleIndex:
+      return "SV_SampleIndex";
+    case ast::Builtin::kSampleMask:
+      return "SV_Coverage";
+    default:
+      break;
+  }
+  return "";
+}
+
+std::string GeneratorImpl::interpolation_to_modifiers(
+    ast::InterpolationType type,
+    ast::InterpolationSampling sampling) const {
+  std::string modifiers;
+  switch (type) {
+    case ast::InterpolationType::kPerspective:
+      modifiers += "linear ";
+      break;
+    case ast::InterpolationType::kLinear:
+      modifiers += "noperspective ";
+      break;
+    case ast::InterpolationType::kFlat:
+      modifiers += "nointerpolation ";
+      break;
+  }
+  switch (sampling) {
+    case ast::InterpolationSampling::kCentroid:
+      modifiers += "centroid ";
+      break;
+    case ast::InterpolationSampling::kSample:
+      modifiers += "sample ";
+      break;
+    case ast::InterpolationSampling::kCenter:
+    case ast::InterpolationSampling::kNone:
+      break;
+  }
+  return modifiers;
+}
+
+bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
+  auto* func_sem = builder_.Sem().Get(func);
+
+  {
+    auto out = line();
+    if (func->PipelineStage() == ast::PipelineStage::kCompute) {
+      // Emit the workgroup_size attribute.
+      auto wgsize = func_sem->WorkgroupSize();
+      out << "[numthreads(";
+      for (int i = 0; i < 3; i++) {
+        if (i > 0) {
+          out << ", ";
+        }
+
+        if (wgsize[i].overridable_const) {
+          auto* global = builder_.Sem().Get<sem::GlobalVariable>(
+              wgsize[i].overridable_const);
+          if (!global->IsOverridable()) {
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "expected a pipeline-overridable constant";
+          }
+          out << kSpecConstantPrefix << global->ConstantId();
+        } else {
+          out << std::to_string(wgsize[i].value);
+        }
+      }
+      out << ")]" << std::endl;
+    }
+
+    out << func->return_type->FriendlyName(builder_.Symbols());
+
+    out << " " << builder_.Symbols().NameFor(func->symbol) << "(";
+
+    bool first = true;
+
+    // Emit entry point parameters.
+    for (auto* var : func->params) {
+      auto* sem = builder_.Sem().Get(var);
+      auto* type = sem->Type();
+      if (!type->Is<sem::Struct>()) {
+        // ICE likely indicates that the CanonicalizeEntryPointIO transform was
+        // not run, or a builtin parameter was added after it was run.
+        TINT_ICE(Writer, diagnostics_)
+            << "Unsupported non-struct entry point parameter";
+      }
+
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol))) {
+        return false;
+      }
+    }
+
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+
+    if (!EmitStatements(func->body->statements)) {
+      return false;
+    }
+
+    if (!Is<ast::ReturnStatement>(func->body->Last())) {
+      ast::ReturnStatement ret(ProgramID(), Source{});
+      if (!EmitStatement(&ret)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitLiteral(std::ostream& out,
+                                const ast::LiteralExpression* lit) {
+  return Switch(
+      lit,
+      [&](const ast::BoolLiteralExpression* l) {
+        out << (l->value ? "true" : "false");
+        return true;
+      },
+      [&](const ast::FloatLiteralExpression* fl) {
+        if (std::isinf(fl->value)) {
+          out << (fl->value >= 0 ? "asfloat(0x7f800000u)"
+                                 : "asfloat(0xff800000u)");
+        } else if (std::isnan(fl->value)) {
+          out << "asfloat(0x7fc00000u)";
+        } else {
+          out << FloatToString(fl->value) << "f";
+        }
+        return true;
+      },
+      [&](const ast::SintLiteralExpression* sl) {
+        out << sl->value;
+        return true;
+      },
+      [&](const ast::UintLiteralExpression* ul) {
+        out << ul->value << "u";
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitValue(std::ostream& out,
+                              const sem::Type* type,
+                              int value) {
+  return Switch(
+      type,
+      [&](const sem::Bool*) {
+        out << (value == 0 ? "false" : "true");
+        return true;
+      },
+      [&](const sem::F32*) {
+        out << value << ".0f";
+        return true;
+      },
+      [&](const sem::I32*) {
+        out << value;
+        return true;
+      },
+      [&](const sem::U32*) {
+        out << value << "u";
+        return true;
+      },
+      [&](const sem::Vector* vec) {
+        if (!EmitType(out, type, ast::StorageClass::kNone,
+                      ast::Access::kReadWrite, "")) {
+          return false;
+        }
+        ScopedParen sp(out);
+        for (uint32_t i = 0; i < vec->Width(); i++) {
+          if (i != 0) {
+            out << ", ";
+          }
+          if (!EmitValue(out, vec->type(), value)) {
+            return false;
+          }
+        }
+        return true;
+      },
+      [&](const sem::Matrix* mat) {
+        if (!EmitType(out, type, ast::StorageClass::kNone,
+                      ast::Access::kReadWrite, "")) {
+          return false;
+        }
+        ScopedParen sp(out);
+        for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) {
+          if (i != 0) {
+            out << ", ";
+          }
+          if (!EmitValue(out, mat->type(), value)) {
+            return false;
+          }
+        }
+        return true;
+      },
+      [&](const sem::Struct*) {
+        out << "(";
+        TINT_DEFER(out << ")" << value);
+        return EmitType(out, type, ast::StorageClass::kNone,
+                        ast::Access::kUndefined, "");
+      },
+      [&](const sem::Array*) {
+        out << "(";
+        TINT_DEFER(out << ")" << value);
+        return EmitType(out, type, ast::StorageClass::kNone,
+                        ast::Access::kUndefined, "");
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "Invalid type for value emission: " + type->type_name());
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+  return EmitValue(out, type, 0);
+}
+
+bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
+  auto emit_continuing = [this, stmt]() {
+    if (stmt->continuing && !stmt->continuing->Empty()) {
+      if (!EmitBlock(stmt->continuing)) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+  line() << LoopAttribute() << "while (true) {";
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+    if (!emit_continuing_()) {
+      return false;
+    }
+  }
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+  // Nest a for loop with a new block. In HLSL the initializer scope is not
+  // nested by the for-loop, so we may get variable redefinitions.
+  line() << "{";
+  increment_indent();
+  TINT_DEFER({
+    decrement_indent();
+    line() << "}";
+  });
+
+  TextBuffer init_buf;
+  if (auto* init = stmt->initializer) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
+    if (!EmitStatement(init)) {
+      return false;
+    }
+  }
+
+  TextBuffer cond_pre;
+  std::stringstream cond_buf;
+  if (auto* cond = stmt->condition) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
+    if (!EmitExpression(cond_buf, cond)) {
+      return false;
+    }
+  }
+
+  TextBuffer cont_buf;
+  if (auto* cont = stmt->continuing) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
+    if (!EmitStatement(cont)) {
+      return false;
+    }
+  }
+
+  // If the for-loop has a multi-statement conditional and / or continuing, then
+  // we cannot emit this as a regular for-loop in HLSL. Instead we need to
+  // generate a `while(true)` loop.
+  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
+
+  // If the for-loop has multi-statement initializer, or is going to be emitted
+  // as a `while(true)` loop, then declare the initializer statement(s) before
+  // the loop.
+  if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) {
+    current_buffer_->Append(init_buf);
+    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
+  }
+
+  if (emit_as_loop) {
+    auto emit_continuing = [&]() {
+      current_buffer_->Append(cont_buf);
+      return true;
+    };
+
+    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+    line() << LoopAttribute() << "while (true) {";
+    increment_indent();
+    TINT_DEFER({
+      decrement_indent();
+      line() << "}";
+    });
+
+    if (stmt->condition) {
+      current_buffer_->Append(cond_pre);
+      line() << "if (!(" << cond_buf.str() << ")) { break; }";
+    }
+
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+
+    if (!emit_continuing_()) {
+      return false;
+    }
+  } else {
+    // For-loop can be generated.
+    {
+      auto out = line();
+      out << LoopAttribute() << "for";
+      {
+        ScopedParen sp(out);
+
+        if (!init_buf.lines.empty()) {
+          out << init_buf.lines[0].content << " ";
+        } else {
+          out << "; ";
+        }
+
+        out << cond_buf.str() << "; ";
+
+        if (!cont_buf.lines.empty()) {
+          out << TrimSuffix(cont_buf.lines[0].content, ";");
+        }
+      }
+      out << " {";
+    }
+    {
+      auto emit_continuing = [] { return true; };
+      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+      if (!EmitStatementsWithIndent(stmt->body->statements)) {
+        return false;
+      }
+    }
+    line() << "}";
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitMemberAccessor(
+    std::ostream& out,
+    const ast::MemberAccessorExpression* expr) {
+  if (!EmitExpression(out, expr->structure)) {
+    return false;
+  }
+  out << ".";
+
+  // Swizzles output the name directly
+  if (builder_.Sem().Get(expr)->Is<sem::Swizzle>()) {
+    out << builder_.Symbols().NameFor(expr->member->symbol);
+  } else if (!EmitExpression(out, expr->member)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
+  if (stmt->value) {
+    auto out = line();
+    out << "return ";
+    if (!EmitExpression(out, stmt->value)) {
+      return false;
+    }
+    out << ";";
+  } else {
+    line() << "return;";
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
+  return Switch(
+      stmt,
+      [&](const ast::AssignmentStatement* a) {  //
+        return EmitAssign(a);
+      },
+      [&](const ast::BlockStatement* b) {  //
+        return EmitBlock(b);
+      },
+      [&](const ast::BreakStatement* b) {  //
+        return EmitBreak(b);
+      },
+      [&](const ast::CallStatement* c) {  //
+        auto out = line();
+        if (!EmitCall(out, c->expr)) {
+          return false;
+        }
+        out << ";";
+        return true;
+      },
+      [&](const ast::ContinueStatement* c) {  //
+        return EmitContinue(c);
+      },
+      [&](const ast::DiscardStatement* d) {  //
+        return EmitDiscard(d);
+      },
+      [&](const ast::FallthroughStatement*) {  //
+        line() << "/* fallthrough */";
+        return true;
+      },
+      [&](const ast::IfStatement* i) {  //
+        return EmitIf(i);
+      },
+      [&](const ast::LoopStatement* l) {  //
+        return EmitLoop(l);
+      },
+      [&](const ast::ForLoopStatement* l) {  //
+        return EmitForLoop(l);
+      },
+      [&](const ast::ReturnStatement* r) {  //
+        return EmitReturn(r);
+      },
+      [&](const ast::SwitchStatement* s) {  //
+        return EmitSwitch(s);
+      },
+      [&](const ast::VariableDeclStatement* v) {  //
+        return EmitVariable(v->variable);
+      },
+      [&](Default) {  //
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown statement type: " + std::string(stmt->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt) {
+  TINT_ASSERT(Writer, stmt->body.size() == 1 && stmt->body[0]->IsDefault());
+
+  // FXC fails to compile a switch with just a default case, ignoring the
+  // default case body. We work around this here by emitting the default case
+  // without the switch.
+
+  // Emit the switch condition as-is in case it has side-effects (e.g.
+  // function call). Note that's it's fine not to assign the result of the
+  // expression.
+  {
+    auto out = line();
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ";";
+  }
+
+  // Emit "do { <default case body> } while(false);". We use a 'do' loop so
+  // that break statements work as expected, and make it 'while (false)' in
+  // case there isn't a break statement.
+  line() << "do {";
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body[0]->body->statements)) {
+      return false;
+    }
+  }
+  line() << "} while (false);";
+  return true;
+}
+
+bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
+  // BUG(crbug.com/tint/1188): work around default-only switches
+  if (stmt->body.size() == 1 && stmt->body[0]->IsDefault()) {
+    return EmitDefaultOnlySwitch(stmt);
+  }
+
+  {  // switch(expr) {
+    auto out = line();
+    out << "switch(";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+    for (size_t i = 0; i < stmt->body.size(); i++) {
+      if (!EmitCase(stmt, i)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitType(std::ostream& out,
+                             const sem::Type* type,
+                             ast::StorageClass storage_class,
+                             ast::Access access,
+                             const std::string& name,
+                             bool* name_printed /* = nullptr */) {
+  if (name_printed) {
+    *name_printed = false;
+  }
+  switch (storage_class) {
+    case ast::StorageClass::kStorage:
+      if (access != ast::Access::kRead) {
+        out << "RW";
+      }
+      out << "ByteAddressBuffer";
+      return true;
+    case ast::StorageClass::kUniform: {
+      auto array_length = (type->Size() + 15) / 16;
+      out << "uint4 " << name << "[" << array_length << "]";
+      if (name_printed) {
+        *name_printed = true;
+      }
+      return true;
+    }
+    default:
+      break;
+  }
+
+  return Switch(
+      type,
+      [&](const sem::Array* ary) {
+        const sem::Type* base_type = ary;
+        std::vector<uint32_t> sizes;
+        while (auto* arr = base_type->As<sem::Array>()) {
+          if (arr->IsRuntimeSized()) {
+            TINT_ICE(Writer, diagnostics_)
+                << "Runtime arrays may only exist in storage buffers, which "
+                   "should "
+                   "have been transformed into a ByteAddressBuffer";
+            return false;
+          }
+          sizes.push_back(arr->Count());
+          base_type = arr->ElemType();
+        }
+        if (!EmitType(out, base_type, storage_class, access, "")) {
+          return false;
+        }
+        if (!name.empty()) {
+          out << " " << name;
+          if (name_printed) {
+            *name_printed = true;
+          }
+        }
+        for (uint32_t size : sizes) {
+          out << "[" << size << "]";
+        }
+        return true;
+      },
+      [&](const sem::Bool*) {
+        out << "bool";
+        return true;
+      },
+      [&](const sem::F32*) {
+        out << "float";
+        return true;
+      },
+      [&](const sem::I32*) {
+        out << "int";
+        return true;
+      },
+      [&](const sem::Matrix* mat) {
+        if (!EmitType(out, mat->type(), storage_class, access, "")) {
+          return false;
+        }
+        // Note: HLSL's matrices are declared as <type>NxM, where N is the
+        // number of rows and M is the number of columns. Despite HLSL's
+        // matrices being column-major by default, the index operator and
+        // constructors actually operate on row-vectors, where as WGSL operates
+        // on column vectors. To simplify everything we use the transpose of the
+        // matrices. See:
+        // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-per-component-math#matrix-ordering
+        out << mat->columns() << "x" << mat->rows();
+        return true;
+      },
+      [&](const sem::Pointer*) {
+        TINT_ICE(Writer, diagnostics_)
+            << "Attempting to emit pointer type. These should have been "
+               "removed with the InlinePointerLets transform";
+        return false;
+      },
+      [&](const sem::Sampler* sampler) {
+        out << "Sampler";
+        if (sampler->IsComparison()) {
+          out << "Comparison";
+        }
+        out << "State";
+        return true;
+      },
+      [&](const sem::Struct* str) {
+        out << StructName(str);
+        return true;
+      },
+      [&](const sem::Texture* tex) {
+        auto* storage = tex->As<sem::StorageTexture>();
+        auto* ms = tex->As<sem::MultisampledTexture>();
+        auto* depth_ms = tex->As<sem::DepthMultisampledTexture>();
+        auto* sampled = tex->As<sem::SampledTexture>();
+
+        if (storage && storage->access() != ast::Access::kRead) {
+          out << "RW";
+        }
+        out << "Texture";
+
+        switch (tex->dim()) {
+          case ast::TextureDimension::k1d:
+            out << "1D";
+            break;
+          case ast::TextureDimension::k2d:
+            out << ((ms || depth_ms) ? "2DMS" : "2D");
+            break;
+          case ast::TextureDimension::k2dArray:
+            out << ((ms || depth_ms) ? "2DMSArray" : "2DArray");
+            break;
+          case ast::TextureDimension::k3d:
+            out << "3D";
+            break;
+          case ast::TextureDimension::kCube:
+            out << "Cube";
+            break;
+          case ast::TextureDimension::kCubeArray:
+            out << "CubeArray";
+            break;
+          default:
+            TINT_UNREACHABLE(Writer, diagnostics_)
+                << "unexpected TextureDimension " << tex->dim();
+            return false;
+        }
+
+        if (storage) {
+          auto* component =
+              image_format_to_rwtexture_type(storage->texel_format());
+          if (component == nullptr) {
+            TINT_ICE(Writer, diagnostics_)
+                << "Unsupported StorageTexture TexelFormat: "
+                << static_cast<int>(storage->texel_format());
+            return false;
+          }
+          out << "<" << component << ">";
+        } else if (depth_ms) {
+          out << "<float4>";
+        } else if (sampled || ms) {
+          auto* subtype = sampled ? sampled->type() : ms->type();
+          out << "<";
+          if (subtype->Is<sem::F32>()) {
+            out << "float4";
+          } else if (subtype->Is<sem::I32>()) {
+            out << "int4";
+          } else if (subtype->Is<sem::U32>()) {
+            out << "uint4";
+          } else {
+            TINT_ICE(Writer, diagnostics_)
+                << "Unsupported multisampled texture type";
+            return false;
+          }
+          out << ">";
+        }
+        return true;
+      },
+      [&](const sem::U32*) {
+        out << "uint";
+        return true;
+      },
+      [&](const sem::Vector* vec) {
+        auto width = vec->Width();
+        if (vec->type()->Is<sem::F32>() && width >= 1 && width <= 4) {
+          out << "float" << width;
+        } else if (vec->type()->Is<sem::I32>() && width >= 1 && width <= 4) {
+          out << "int" << width;
+        } else if (vec->type()->Is<sem::U32>() && width >= 1 && width <= 4) {
+          out << "uint" << width;
+        } else if (vec->type()->Is<sem::Bool>() && width >= 1 && width <= 4) {
+          out << "bool" << width;
+        } else {
+          out << "vector<";
+          if (!EmitType(out, vec->type(), storage_class, access, "")) {
+            return false;
+          }
+          out << ", " << width << ">";
+        }
+        return true;
+      },
+      [&](const sem::Atomic* atomic) {
+        return EmitType(out, atomic->Type(), storage_class, access, name);
+      },
+      [&](const sem::Void*) {
+        out << "void";
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(diag::System::Writer,
+                               "unknown type in EmitType");
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
+                                    const sem::Type* type,
+                                    ast::StorageClass storage_class,
+                                    ast::Access access,
+                                    const std::string& name) {
+  bool name_printed = false;
+  if (!EmitType(out, type, storage_class, access, name, &name_printed)) {
+    return false;
+  }
+  if (!name.empty() && !name_printed) {
+    out << " " << name;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
+  line(b) << "struct " << StructName(str) << " {";
+  {
+    ScopedIndent si(b);
+    for (auto* mem : str->Members()) {
+      auto mem_name = builder_.Symbols().NameFor(mem->Name());
+
+      auto* ty = mem->Type();
+
+      auto out = line(b);
+
+      std::string pre, post;
+
+      if (auto* decl = mem->Declaration()) {
+        for (auto* attr : decl->attributes) {
+          if (auto* location = attr->As<ast::LocationAttribute>()) {
+            auto& pipeline_stage_uses = str->PipelineStageUses();
+            if (pipeline_stage_uses.size() != 1) {
+              TINT_ICE(Writer, diagnostics_)
+                  << "invalid entry point IO struct uses";
+            }
+
+            if (pipeline_stage_uses.count(
+                    sem::PipelineStageUsage::kVertexInput)) {
+              post += " : TEXCOORD" + std::to_string(location->value);
+            } else if (pipeline_stage_uses.count(
+                           sem::PipelineStageUsage::kVertexOutput)) {
+              post += " : TEXCOORD" + std::to_string(location->value);
+            } else if (pipeline_stage_uses.count(
+                           sem::PipelineStageUsage::kFragmentInput)) {
+              post += " : TEXCOORD" + std::to_string(location->value);
+            } else if (pipeline_stage_uses.count(
+                           sem::PipelineStageUsage::kFragmentOutput)) {
+              post += " : SV_Target" + std::to_string(location->value);
+            } else {
+              TINT_ICE(Writer, diagnostics_)
+                  << "invalid use of location attribute";
+            }
+          } else if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
+            auto name = builtin_to_attribute(builtin->builtin);
+            if (name.empty()) {
+              diagnostics_.add_error(diag::System::Writer,
+                                     "unsupported builtin");
+              return false;
+            }
+            post += " : " + name;
+          } else if (auto* interpolate =
+                         attr->As<ast::InterpolateAttribute>()) {
+            auto mod = interpolation_to_modifiers(interpolate->type,
+                                                  interpolate->sampling);
+            if (mod.empty()) {
+              diagnostics_.add_error(diag::System::Writer,
+                                     "unsupported interpolation");
+              return false;
+            }
+            pre += mod;
+
+          } else if (attr->Is<ast::InvariantAttribute>()) {
+            // Note: `precise` is not exactly the same as `invariant`, but is
+            // stricter and therefore provides the necessary guarantees.
+            // See discussion here: https://github.com/gpuweb/gpuweb/issues/893
+            pre += "precise ";
+          } else if (!attr->IsAnyOf<ast::StructMemberAlignAttribute,
+                                    ast::StructMemberOffsetAttribute,
+                                    ast::StructMemberSizeAttribute>()) {
+            TINT_ICE(Writer, diagnostics_)
+                << "unhandled struct member attribute: " << attr->Name();
+            return false;
+          }
+        }
+      }
+
+      out << pre;
+      if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, mem_name)) {
+        return false;
+      }
+      out << post << ";";
+    }
+  }
+
+  line(b) << "};";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
+                                const ast::UnaryOpExpression* expr) {
+  switch (expr->op) {
+    case ast::UnaryOp::kIndirection:
+    case ast::UnaryOp::kAddressOf:
+      return EmitExpression(out, expr->expr);
+    case ast::UnaryOp::kComplement:
+      out << "~";
+      break;
+    case ast::UnaryOp::kNot:
+      out << "!";
+      break;
+    case ast::UnaryOp::kNegation:
+      out << "-";
+      break;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitVariable(const ast::Variable* var) {
+  auto* sem = builder_.Sem().Get(var);
+  auto* type = sem->Type()->UnwrapRef();
+
+  // TODO(dsinclair): Handle variable attributes
+  if (!var->attributes.empty()) {
+    diagnostics_.add_error(diag::System::Writer,
+                           "Variable attributes are not handled yet");
+    return false;
+  }
+
+  auto out = line();
+  if (var->is_const) {
+    out << "const ";
+  }
+  if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                       builder_.Symbols().NameFor(var->symbol))) {
+    return false;
+  }
+
+  out << " = ";
+
+  if (var->constructor) {
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, type)) {
+      return false;
+    }
+  }
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
+  for (auto* d : var->attributes) {
+    if (!d->Is<ast::IdAttribute>()) {
+      diagnostics_.add_error(diag::System::Writer,
+                             "Decorated const values not valid");
+      return false;
+    }
+  }
+  if (!var->is_const) {
+    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
+    return false;
+  }
+
+  auto* sem = builder_.Sem().Get(var);
+  auto* type = sem->Type();
+
+  auto* global = sem->As<sem::GlobalVariable>();
+  if (global && global->IsOverridable()) {
+    auto const_id = global->ConstantId();
+
+    line() << "#ifndef " << kSpecConstantPrefix << const_id;
+
+    if (var->constructor != nullptr) {
+      auto out = line();
+      out << "#define " << kSpecConstantPrefix << const_id << " ";
+      if (!EmitExpression(out, var->constructor)) {
+        return false;
+      }
+    } else {
+      line() << "#error spec constant required for constant id " << const_id;
+    }
+    line() << "#endif";
+    {
+      auto out = line();
+      out << "static const ";
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol))) {
+        return false;
+      }
+      out << " = " << kSpecConstantPrefix << const_id << ";";
+    }
+  } else {
+    auto out = line();
+    out << "static const ";
+    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                         builder_.Symbols().NameFor(var->symbol))) {
+      return false;
+    }
+    out << " = ";
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+    out << ";";
+  }
+
+  return true;
+}
+
+template <typename F>
+bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
+                                      const ast::CallExpression* call,
+                                      const sem::Builtin* builtin,
+                                      F&& build) {
+  // Generate the helper function if it hasn't been created already
+  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
+    TextBuffer b;
+    TINT_DEFER(helpers_.Append(b));
+
+    auto fn_name =
+        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+    std::vector<std::string> parameter_names;
+    {
+      auto decl = line(&b);
+      if (!EmitTypeAndName(decl, builtin->ReturnType(),
+                           ast::StorageClass::kNone, ast::Access::kUndefined,
+                           fn_name)) {
+        return "";
+      }
+      {
+        ScopedParen sp(decl);
+        for (auto* param : builtin->Parameters()) {
+          if (!parameter_names.empty()) {
+            decl << ", ";
+          }
+          auto param_name = "param_" + std::to_string(parameter_names.size());
+          const auto* ty = param->Type();
+          if (auto* ptr = ty->As<sem::Pointer>()) {
+            decl << "inout ";
+            ty = ptr->StoreType();
+          }
+          if (!EmitTypeAndName(decl, ty, ast::StorageClass::kNone,
+                               ast::Access::kUndefined, param_name)) {
+            return "";
+          }
+          parameter_names.emplace_back(std::move(param_name));
+        }
+      }
+      decl << " {";
+    }
+    {
+      ScopedIndent si(&b);
+      if (!build(&b, parameter_names)) {
+        return "";
+      }
+    }
+    line(&b) << "}";
+    line(&b);
+    return fn_name;
+  });
+
+  if (fn.empty()) {
+    return false;
+  }
+
+  // Call the helper
+  out << fn;
+  {
+    ScopedParen sp(out);
+    bool first = true;
+    for (auto* arg : call->args) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+      if (!EmitExpression(out, arg)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
new file mode 100644
index 0000000..48046ba
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -0,0 +1,553 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_HLSL_GENERATOR_IMPL_H_
+#define SRC_TINT_WRITER_HLSL_GENERATOR_IMPL_H_
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/binding_point.h"
+#include "src/tint/transform/decompose_memory_access.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/text_generator.h"
+
+namespace tint {
+
+// Forward declarations
+namespace sem {
+class Call;
+class Builtin;
+class TypeConstructor;
+class TypeConversion;
+}  // namespace sem
+
+namespace writer {
+namespace hlsl {
+
+/// The result of sanitizing a program for generation.
+struct SanitizedResult {
+  /// Constructor
+  SanitizedResult();
+  /// Destructor
+  ~SanitizedResult();
+  /// Move constructor
+  SanitizedResult(SanitizedResult&&);
+
+  /// The sanitized program.
+  Program program;
+  /// Indices into the array_length_from_uniform binding that are statically
+  /// used.
+  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
+};
+
+/// Sanitize a program in preparation for generating HLSL.
+/// @param root_constant_binding_point the binding point to use for information
+/// that will be passed via root constants
+/// @param disable_workgroup_init `true` to disable workgroup memory zero
+/// @returns the sanitized program and any supplementary information
+SanitizedResult Sanitize(
+    const Program* program,
+    sem::BindingPoint root_constant_binding_point = {},
+    bool disable_workgroup_init = false,
+    const ArrayLengthFromUniformOptions& array_length_from_uniform = {});
+
+/// Implementation class for HLSL generator
+class GeneratorImpl : public TextGenerator {
+ public:
+  /// Constructor
+  /// @param program the program to generate
+  explicit GeneratorImpl(const Program* program);
+  ~GeneratorImpl();
+
+  /// @returns true on successful generation; false otherwise
+  bool Generate();
+
+  /// Handles an index accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the index accessor was emitted
+  bool EmitIndexAccessor(std::ostream& out,
+                         const ast::IndexAccessorExpression* expr);
+  /// Handles an assignment statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitAssign(const ast::AssignmentStatement* stmt);
+  /// Emits code such that if `expr` is zero, it emits one, else `expr`
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitExpressionOrOneIfZero(std::ostream& out,
+                                 const ast::Expression* expr);
+  /// Handles generating a binary expression
+  /// @param out the output of the expression stream
+  /// @param expr the binary expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
+  /// Handles generating a bitcast expression
+  /// @param out the output of the expression stream
+  /// @param expr the as expression
+  /// @returns true if the bitcast was emitted
+  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
+  /// Emits a list of statements
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatements(const ast::StatementList& stmts);
+  /// Emits a list of statements with an indentation
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+  /// Handles a block statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBlock(const ast::BlockStatement* stmt);
+  /// Handles a break statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBreak(const ast::BreakStatement* stmt);
+  /// Handles generating a call expression
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a function call expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param function the function being called
+  /// @returns true if the expression is emitted
+  bool EmitFunctionCall(std::ostream& out,
+                        const sem::Call* call,
+                        const sem::Function* function);
+  /// Handles generating a builtin call expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the builtin being called
+  /// @returns true if the expression is emitted
+  bool EmitBuiltinCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a type conversion expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param conv the type conversion
+  /// @returns true if the expression is emitted
+  bool EmitTypeConversion(std::ostream& out,
+                          const sem::Call* call,
+                          const sem::TypeConversion* conv);
+  /// Handles generating a type constructor expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param ctor the type constructor
+  /// @returns true if the expression is emitted
+  bool EmitTypeConstructor(std::ostream& out,
+                           const sem::Call* call,
+                           const sem::TypeConstructor* ctor);
+  /// Handles generating a call expression to a
+  /// transform::DecomposeMemoryAccess::Intrinsic for a uniform buffer
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
+  /// @returns true if the call expression is emitted
+  bool EmitUniformBufferAccess(
+      std::ostream& out,
+      const ast::CallExpression* expr,
+      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
+  /// Handles generating a call expression to a
+  /// transform::DecomposeMemoryAccess::Intrinsic for a storage buffer
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
+  /// @returns true if the call expression is emitted
+  bool EmitStorageBufferAccess(
+      std::ostream& out,
+      const ast::CallExpression* expr,
+      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
+  /// Handles generating a barrier intrinsic call
+  /// @param out the output of the expression stream
+  /// @param builtin the semantic information for the barrier builtin
+  /// @returns true if the call expression is emitted
+  bool EmitBarrierCall(std::ostream& out, const sem::Builtin* builtin);
+  /// Handles generating an atomic intrinsic call for a storage buffer variable
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param intrinsic the atomic intrinsic
+  /// @returns true if the call expression is emitted
+  bool EmitStorageAtomicCall(
+      std::ostream& out,
+      const ast::CallExpression* expr,
+      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
+  /// Handles generating an atomic intrinsic call for a workgroup variable
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the atomic builtin
+  /// @returns true if the call expression is emitted
+  bool EmitWorkgroupAtomicCall(std::ostream& out,
+                               const ast::CallExpression* expr,
+                               const sem::Builtin* builtin);
+  /// Handles generating a call to a texture function (`textureSample`,
+  /// `textureSampleGrad`, etc)
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitTextureCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `select()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitSelectCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a call to the `modf()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitModfCall(std::ostream& out,
+                    const ast::CallExpression* expr,
+                    const sem::Builtin* builtin);
+  /// Handles generating a call to the `frexp()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitFrexpCall(std::ostream& out,
+                     const ast::CallExpression* expr,
+                     const sem::Builtin* builtin);
+  /// Handles generating a call to the `isNormal()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitIsNormalCall(std::ostream& out,
+                        const ast::CallExpression* expr,
+                        const sem::Builtin* builtin);
+  /// Handles generating a call to the `degrees()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDegreesCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `radians()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitRadiansCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to data packing builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDataPackingCall(std::ostream& out,
+                           const ast::CallExpression* expr,
+                           const sem::Builtin* builtin);
+  /// Handles generating a call to data unpacking builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDataUnpackingCall(std::ostream& out,
+                             const ast::CallExpression* expr,
+                             const sem::Builtin* builtin);
+  /// Handles a case statement
+  /// @param s the switch statement
+  /// @param case_idx the index of the switch case in the switch statement
+  /// @returns true if the statement was emitted successfully
+  bool EmitCase(const ast::SwitchStatement* s, size_t case_idx);
+  /// Handles generating a discard statement
+  /// @param stmt the discard statement
+  /// @returns true if the statement was successfully emitted
+  bool EmitDiscard(const ast::DiscardStatement* stmt);
+  /// Handles a continue statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitContinue(const ast::ContinueStatement* stmt);
+  /// Handles generate an Expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the expression was emitted
+  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
+  /// Handles generating a function
+  /// @param func the function to generate
+  /// @returns true if the function was emitted
+  bool EmitFunction(const ast::Function* func);
+  /// Handles emitting the function body if it discards to work around a FXC
+  /// compilation bug.
+  /// @param func the function with the body to emit
+  /// @returns true if the function was emitted
+  bool EmitFunctionBodyWithDiscard(const ast::Function* func);
+  /// Handles emitting a global variable
+  /// @param global the global variable
+  /// @returns true on success
+  bool EmitGlobalVariable(const ast::Variable* global);
+
+  /// Handles emitting a global variable with the uniform storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitUniformVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the storage storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitStorageVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the handle storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitHandleVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the private storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitPrivateVariable(const sem::Variable* var);
+
+  /// Handles emitting a global variable with the workgroup storage class
+  /// @param var the global variable
+  /// @returns true on success
+  bool EmitWorkgroupVariable(const sem::Variable* var);
+
+  /// Handles emitting the entry point function
+  /// @param func the entry point
+  /// @returns true if the entry point function was emitted
+  bool EmitEntryPointFunction(const ast::Function* func);
+  /// Handles an if statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitIf(const ast::IfStatement* stmt);
+  /// Handles a literal
+  /// @param out the output stream
+  /// @param lit the literal to emit
+  /// @returns true if the literal was successfully emitted
+  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
+  /// Handles a loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitLoop(const ast::LoopStatement* stmt);
+  /// Handles a for loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitForLoop(const ast::ForLoopStatement* stmt);
+  /// Handles generating an identifier expression
+  /// @param out the output of the expression stream
+  /// @param expr the identifier expression
+  /// @returns true if the identifeir was emitted
+  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
+  /// Handles a member accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the member accessor expression
+  /// @returns true if the member accessor was emitted
+  bool EmitMemberAccessor(std::ostream& out,
+                          const ast::MemberAccessorExpression* expr);
+  /// Handles return statements
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitReturn(const ast::ReturnStatement* stmt);
+  /// Handles statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitStatement(const ast::Statement* stmt);
+  /// Handles generating a switch statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitSwitch(const ast::SwitchStatement* stmt);
+  // Handles generating a switch statement with only a default case
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt);
+  /// Handles generating type
+  /// @param out the output stream
+  /// @param type the type to generate
+  /// @param storage_class the storage class of the variable
+  /// @param access the access control type of the variable
+  /// @param name the name of the variable, used for array emission.
+  /// @param name_printed (optional) if not nullptr and an array was printed
+  /// then the boolean is set to true.
+  /// @returns true if the type is emitted
+  bool EmitType(std::ostream& out,
+                const sem::Type* type,
+                ast::StorageClass storage_class,
+                ast::Access access,
+                const std::string& name,
+                bool* name_printed = nullptr);
+  /// Handles generating type and name
+  /// @param out the output stream
+  /// @param type the type to generate
+  /// @param storage_class the storage class of the variable
+  /// @param access the access control type of the variable
+  /// @param name the name to emit
+  /// @returns true if the type is emitted
+  bool EmitTypeAndName(std::ostream& out,
+                       const sem::Type* type,
+                       ast::StorageClass storage_class,
+                       ast::Access access,
+                       const std::string& name);
+  /// Handles generating a structure declaration
+  /// @param buffer the text buffer that the type declaration will be written to
+  /// @param ty the struct to generate
+  /// @returns true if the struct is emitted
+  bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty);
+  /// Handles a unary op expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the expression was emitted
+  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
+  /// Emits `value` for the given type
+  /// @param out the output stream
+  /// @param type the type to emit the value for
+  /// @param value the value to emit
+  /// @returns true if the value was successfully emitted.
+  bool EmitValue(std::ostream& out, const sem::Type* type, int value);
+  /// Emits the zero value for the given type
+  /// @param out the output stream
+  /// @param type the type to emit the value for
+  /// @returns true if the zero value was successfully emitted.
+  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+  /// Handles generating a variable
+  /// @param var the variable to generate
+  /// @returns true if the variable was emitted
+  bool EmitVariable(const ast::Variable* var);
+  /// Handles generating a program scope constant variable
+  /// @param var the variable to emit
+  /// @returns true if the variable was emitted
+  bool EmitProgramConstVariable(const ast::Variable* var);
+  /// Emits call to a helper vector assignment function for the input assignment
+  /// statement and vector type. This is used to work around FXC issues where
+  /// assignments to vectors with dynamic indices cause compilation failures.
+  /// @param stmt assignment statement that corresponds to a vector assignment
+  /// via an accessor expression
+  /// @param vec the vector type being assigned to
+  /// @returns true on success
+  bool EmitDynamicVectorAssignment(const ast::AssignmentStatement* stmt,
+                                   const sem::Vector* vec);
+  /// Emits call to a helper matrix assignment function for the input assignment
+  /// statement and matrix type. This is used to work around FXC issues where
+  /// assignment of a vector to a matrix with a dynamic index causes compilation
+  /// failures.
+  /// @param stmt assignment statement that corresponds to a matrix assignment
+  /// via an accessor expression
+  /// @param mat the matrix type being assigned to
+  /// @returns true on success
+  bool EmitDynamicMatrixVectorAssignment(const ast::AssignmentStatement* stmt,
+                                         const sem::Matrix* mat);
+  /// Emits call to a helper matrix assignment function for the input assignment
+  /// statement and matrix type. This is used to work around FXC issues where
+  /// assignment of a scalar to a matrix with at least one dynamic index causes
+  /// compilation failures.
+  /// @param stmt assignment statement that corresponds to a matrix assignment
+  /// via an accessor expression
+  /// @param mat the matrix type being assigned to
+  /// @returns true on success
+  bool EmitDynamicMatrixScalarAssignment(const ast::AssignmentStatement* stmt,
+                                         const sem::Matrix* mat);
+
+  /// Handles generating a builtin method name
+  /// @param builtin the semantic info for the builtin
+  /// @returns the name or "" if not valid
+  std::string generate_builtin_name(const sem::Builtin* builtin);
+  /// Converts a builtin to an attribute name
+  /// @param builtin the builtin to convert
+  /// @returns the string name of the builtin or blank on error
+  std::string builtin_to_attribute(ast::Builtin builtin) const;
+
+  /// Converts interpolation attributes to a HLSL modifiers
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  /// @returns the string name of the attribute or blank on error
+  std::string interpolation_to_modifiers(
+      ast::InterpolationType type,
+      ast::InterpolationSampling sampling) const;
+
+ private:
+  enum class VarType { kIn, kOut };
+
+  struct EntryPointData {
+    std::string struct_name;
+    std::string var_name;
+  };
+
+  struct DMAIntrinsic {
+    transform::DecomposeMemoryAccess::Intrinsic::Op op;
+    transform::DecomposeMemoryAccess::Intrinsic::DataType type;
+    bool operator==(const DMAIntrinsic& rhs) const {
+      return op == rhs.op && type == rhs.type;
+    }
+    /// Hasher is a std::hash function for DMAIntrinsic
+    struct Hasher {
+      /// @param i the DMAIntrinsic to hash
+      /// @returns the hash of `i`
+      inline std::size_t operator()(const DMAIntrinsic& i) const {
+        return utils::Hash(i.op, i.type);
+      }
+    };
+  };
+
+  /// CallBuiltinHelper will call the builtin helper function, creating it
+  /// if it hasn't been built already. If the builtin needs to be built then
+  /// CallBuiltinHelper will generate the function signature and will call
+  /// `build` to emit the body of the function.
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @param build a function with the signature:
+  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
+  ///        Where:
+  ///          `buffer` is the body of the generated function
+  ///          `params` is the name of all the generated function parameters
+  /// @returns true if the call expression is emitted
+  template <typename F>
+  bool CallBuiltinHelper(std::ostream& out,
+                         const ast::CallExpression* call,
+                         const sem::Builtin* builtin,
+                         F&& build);
+
+  TextBuffer helpers_;  // Helper functions emitted at the top of the output
+  std::function<bool()> emit_continuing_;
+  std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher>
+      dma_intrinsics_;
+  std::unordered_map<const sem::Builtin*, std::string> builtins_;
+  std::unordered_map<const sem::Struct*, std::string> structure_builders_;
+  std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
+  std::unordered_map<const sem::Matrix*, std::string>
+      dynamic_matrix_vector_write_;
+  std::unordered_map<const sem::Matrix*, std::string>
+      dynamic_matrix_scalar_write_;
+  std::unordered_map<const sem::Type*, std::string> value_or_one_if_zero_;
+};
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_HLSL_GENERATOR_IMPL_H_
diff --git a/src/tint/writer/hlsl/generator_impl_array_accessor_test.cc b/src/tint/writer/hlsl/generator_impl_array_accessor_test.cc
new file mode 100644
index 0000000..6746e7f
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_array_accessor_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Expression = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Expression, IndexAccessor) {
+  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
+  auto* expr = IndexAccessor("ary", 5);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "ary[5]");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_assign_test.cc b/src/tint/writer/hlsl/generator_impl_assign_test.cc
new file mode 100644
index 0000000..91f68d0
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_assign_test.cc
@@ -0,0 +1,211 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Assign = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Assign) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.i32())),
+           Decl(Var("rhs", ty.i32())),
+           Assign("lhs", "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(),
+            R"(void fn() {
+  int lhs = 0;
+  int rhs = 0;
+  lhs = rhs;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_ConstantIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.vec3<f32>())),
+           Decl(Var("rhs", ty.f32())),
+           Decl(Const("index", ty.u32(), Expr(0u))),
+           Assign(IndexAccessor("lhs", "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(),
+            R"(void fn() {
+  float3 lhs = float3(0.0f, 0.0f, 0.0f);
+  float rhs = 0.0f;
+  const uint index = 0u;
+  lhs[index] = rhs;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_DynamicIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.vec3<f32>())),
+           Decl(Var("rhs", ty.f32())),
+           Decl(Var("index", ty.u32())),
+           Assign(IndexAccessor("lhs", "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(),
+            R"(void set_float3(inout float3 vec, int idx, float val) {
+  vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;
+}
+
+void fn() {
+  float3 lhs = float3(0.0f, 0.0f, 0.0f);
+  float rhs = 0.0f;
+  uint index = 0u;
+  set_float3(lhs, index, rhs);
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_ConstantIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.mat4x2<f32>())),
+           Decl(Var("rhs", ty.vec2<f32>())),
+           Decl(Const("index", ty.u32(), Expr(0u))),
+           Assign(IndexAccessor("lhs", "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(),
+            R"(void fn() {
+  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  float2 rhs = float2(0.0f, 0.0f);
+  const uint index = 0u;
+  lhs[index] = rhs;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_DynamicIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.mat4x2<f32>())),
+           Decl(Var("rhs", ty.vec2<f32>())),
+           Decl(Var("index", ty.u32())),
+           Assign(IndexAccessor("lhs", "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(
+      gen.result(),
+      R"(void set_vector_float4x2(inout float4x2 mat, int col, float2 val) {
+  switch (col) {
+    case 0: mat[0] = val; break;
+    case 1: mat[1] = val; break;
+    case 2: mat[2] = val; break;
+    case 3: mat[3] = val; break;
+  }
+}
+
+void fn() {
+  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  float2 rhs = float2(0.0f, 0.0f);
+  uint index = 0u;
+  set_vector_float4x2(lhs, index, rhs);
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_ConstantIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.mat4x2<f32>())),
+           Decl(Var("rhs", ty.f32())),
+           Decl(Const("index", ty.u32(), Expr(0u))),
+           Assign(IndexAccessor(IndexAccessor("lhs", "index"), "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(),
+            R"(void fn() {
+  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  float rhs = 0.0f;
+  const uint index = 0u;
+  lhs[index][index] = rhs;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_DynamicIndex) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("lhs", ty.mat4x2<f32>())),
+           Decl(Var("rhs", ty.f32())),
+           Decl(Var("index", ty.u32())),
+           Assign(IndexAccessor(IndexAccessor("lhs", "index"), "index"), "rhs"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(
+      gen.result(),
+      R"(void set_scalar_float4x2(inout float4x2 mat, int col, int row, float val) {
+  switch (col) {
+    case 0:
+      mat[0] = (row.xx == int2(0, 1)) ? val.xx : mat[0];
+      break;
+    case 1:
+      mat[1] = (row.xx == int2(0, 1)) ? val.xx : mat[1];
+      break;
+    case 2:
+      mat[2] = (row.xx == int2(0, 1)) ? val.xx : mat[2];
+      break;
+    case 3:
+      mat[3] = (row.xx == int2(0, 1)) ? val.xx : mat[3];
+      break;
+  }
+}
+
+void fn() {
+  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  float rhs = 0.0f;
+  uint index = 0u;
+  set_scalar_float4x2(lhs, index, index, rhs);
+}
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_binary_test.cc b/src/tint/writer/hlsl/generator_impl_binary_test.cc
new file mode 100644
index 0000000..9d94a5a
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_binary_test.cc
@@ -0,0 +1,828 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Binary = TestHelper;
+
+struct BinaryData {
+  const char* result;
+  ast::BinaryOp op;
+
+  enum Types { All = 0b11, Integer = 0b10, Float = 0b01 };
+  Types valid_for = Types::All;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << data.op;
+  return out;
+}
+
+using HlslBinaryTest = TestParamHelper<BinaryData>;
+TEST_P(HlslBinaryTest, Emit_f32) {
+  auto params = GetParam();
+
+  if ((params.valid_for & BinaryData::Types::Float) == 0) {
+    return;
+  }
+
+  // Skip ops that are illegal for this type
+  if (params.op == ast::BinaryOp::kAnd || params.op == ast::BinaryOp::kOr ||
+      params.op == ast::BinaryOp::kXor ||
+      params.op == ast::BinaryOp::kShiftLeft ||
+      params.op == ast::BinaryOp::kShiftRight) {
+    return;
+  }
+
+  Global("left", ty.f32(), ast::StorageClass::kPrivate);
+  Global("right", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+TEST_P(HlslBinaryTest, Emit_u32) {
+  auto params = GetParam();
+
+  if ((params.valid_for & BinaryData::Types::Integer) == 0) {
+    return;
+  }
+
+  Global("left", ty.u32(), ast::StorageClass::kPrivate);
+  Global("right", ty.u32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+TEST_P(HlslBinaryTest, Emit_i32) {
+  auto params = GetParam();
+
+  if ((params.valid_for & BinaryData::Types::Integer) == 0) {
+    return;
+  }
+
+  // Skip ops that are illegal for this type
+  if (params.op == ast::BinaryOp::kShiftLeft ||
+      params.op == ast::BinaryOp::kShiftRight) {
+    return;
+  }
+
+  Global("left", ty.i32(), ast::StorageClass::kPrivate);
+  Global("right", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest,
+    HlslBinaryTest,
+    testing::Values(
+        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
+        BinaryData{"(left | right)", ast::BinaryOp::kOr},
+        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
+        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
+        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
+        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
+        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
+        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
+        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
+        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
+        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
+        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
+        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
+        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
+        // NOTE: Integer divide covered by DivOrModBy* tests below
+        BinaryData{"(left / right)", ast::BinaryOp::kDivide,
+                   BinaryData::Types::Float},
+        // NOTE: Integer modulo covered by DivOrModBy* tests below
+        BinaryData{"(left % right)", ast::BinaryOp::kModulo,
+                   BinaryData::Types::Float}));
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_VectorScalar) {
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = Expr(1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            "(float3(1.0f, 1.0f, 1.0f) * "
+            "1.0f)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_ScalarVector) {
+  auto* lhs = Expr(1.f);
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            "(1.0f * float3(1.0f, 1.0f, "
+            "1.0f))");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixScalar) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr("mat");
+  auto* rhs = Expr(1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(mat * 1.0f)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr(1.f);
+  auto* rhs = Expr("mat");
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(1.0f * mat)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixVector) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = Expr("mat");
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(float3(1.0f, 1.0f, 1.0f), mat)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
+  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = Expr("mat");
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(mat, float3(1.0f, 1.0f, 1.0f))");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
+  Global("lhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                             Expr("lhs"), Expr("rhs"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(rhs, lhs)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Logical_And) {
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                             Expr("a"), Expr("b"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Logical_Multi) {
+  // (a && b) || (c || d)
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalOr,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
+                                    Expr("b")),
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"),
+                                    Expr("d")));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  bool tint_tmp_2 = c;
+  if (!tint_tmp_2) {
+    tint_tmp_2 = d;
+  }
+  tint_tmp = (tint_tmp_2);
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Logical_Or) {
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                             Expr("a"), Expr("b"));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (!tint_tmp) {
+  tint_tmp = b;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, If_WithLogical) {
+  // if (a && b) {
+  //   return 1;
+  // } else if (b || c) {
+  //   return 2;
+  // } else {
+  //   return 3;
+  // }
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                Expr("a"), Expr("b")),
+                  Block(Return(1)),
+                  Else(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                     Expr("b"), Expr("c")),
+                       Block(Return(2))),
+                  Else(Block(Return(3))));
+  Func("func", {}, ty.i32(), {WrapInStatement(expr)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+if ((tint_tmp)) {
+  return 1;
+} else {
+  bool tint_tmp_1 = b;
+  if (!tint_tmp_1) {
+    tint_tmp_1 = c;
+  }
+  if ((tint_tmp_1)) {
+    return 2;
+  } else {
+    return 3;
+  }
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Return_WithLogical) {
+  // return (a && b) || c;
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = Return(create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalOr,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
+                                    Expr("b")),
+      Expr("c")));
+  Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = c;
+}
+return (tint_tmp);
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Assign_WithLogical) {
+  // a = (b || c) && d;
+
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* expr = Assign(
+      Expr("a"), create<ast::BinaryExpression>(
+                     ast::BinaryOp::kLogicalAnd,
+                     create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                   Expr("b"), Expr("c")),
+                     Expr("d")));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (!tint_tmp_1) {
+  tint_tmp_1 = c;
+}
+bool tint_tmp = (tint_tmp_1);
+if (tint_tmp) {
+  tint_tmp = d;
+}
+a = (tint_tmp);
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Decl_WithLogical) {
+  // var a : bool = (b && c) || d;
+
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* var = Var("a", ty.bool_(), ast::StorageClass::kNone,
+                  create<ast::BinaryExpression>(
+                      ast::BinaryOp::kLogicalOr,
+                      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                    Expr("b"), Expr("c")),
+                      Expr("d")));
+
+  auto* decl = Decl(var);
+  WrapInFunction(decl);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (tint_tmp_1) {
+  tint_tmp_1 = c;
+}
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = d;
+}
+bool a = (tint_tmp);
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Binary, Call_WithLogical) {
+  // foo(a && b, c || d, (a || c) && (b || d))
+
+  Func("foo",
+       {
+           Param(Sym(), ty.bool_()),
+           Param(Sym(), ty.bool_()),
+           Param(Sym(), ty.bool_()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
+
+  ast::ExpressionList params;
+  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                 Expr("a"), Expr("b")));
+  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                                 Expr("c"), Expr("d")));
+  params.push_back(create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalAnd,
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"),
+                                    Expr("c")),
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"),
+                                    Expr("d"))));
+
+  auto* expr = CallStmt(Call("foo", params));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
+if (tint_tmp) {
+  tint_tmp = b;
+}
+bool tint_tmp_1 = c;
+if (!tint_tmp_1) {
+  tint_tmp_1 = d;
+}
+bool tint_tmp_3 = a;
+if (!tint_tmp_3) {
+  tint_tmp_3 = c;
+}
+bool tint_tmp_2 = (tint_tmp_3);
+if (tint_tmp_2) {
+  bool tint_tmp_4 = b;
+  if (!tint_tmp_4) {
+    tint_tmp_4 = d;
+  }
+  tint_tmp_2 = (tint_tmp_4);
+}
+foo((tint_tmp), (tint_tmp_1), (tint_tmp_2));
+)");
+}
+
+namespace HlslGeneratorDivMod {
+
+struct Params {
+  enum class Type { Div, Mod };
+  Type type;
+};
+
+struct HlslGeneratorDivModTest : TestParamHelper<Params> {
+  std::string Token() {
+    return GetParam().type == Params::Type::Div ? "/" : "%";
+  }
+
+  template <typename... Args>
+  auto Op(Args... args) {
+    return GetParam().type == Params::Type::Div
+               ? Div(std::forward<Args>(args)...)
+               : Mod(std::forward<Args>(args)...);
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+                         HlslGeneratorDivModTest,
+                         testing::Values(Params{Params::Type::Div},
+                                         Params{Params::Type::Mod}));
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_i32) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32())),
+           Decl(Const("r", nullptr, Op("a", 0))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn() {
+  int a = 0;
+  const int r = (a )" + Token() +
+                              R"( 1);
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_u32) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.u32())),
+           Decl(Const("r", nullptr, Op("a", 0u))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn() {
+  uint a = 0u;
+  const uint r = (a )" + Token() +
+                              R"( 1u);
+}
+)");
+}  // namespace HlslGeneratorDivMod
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_vec_i32) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", nullptr, vec4<i32>(100, 100, 100, 100))),
+           Decl(Const("r", nullptr, Op("a", vec4<i32>(50, 0, 25, 0)))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn() {
+  int4 a = int4(100, 100, 100, 100);
+  const int4 r = (a )" + Token() +
+                              R"( int4(50, 1, 25, 1));
+}
+)");
+}  // namespace
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_scalar_i32) {
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", nullptr, vec4<i32>(100, 100, 100, 100))),
+           Decl(Const("r", nullptr, Op("a", 0))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn() {
+  int4 a = int4(100, 100, 100, 100);
+  const int4 r = (a )" + Token() +
+                              R"( 1);
+}
+)");
+}  // namespace hlsl
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_i32) {
+  Func("fn", {Param("b", ty.i32())}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32())),
+           Decl(Const("r", nullptr, Op("a", "b"))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn(int b) {
+  int a = 0;
+  const int r = (a )" + Token() +
+                              R"( (b == 0 ? 1 : b));
+}
+)");
+}  // namespace writer
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_u32) {
+  Func("fn", {Param("b", ty.u32())}, ty.void_(),
+       {
+           Decl(Var("a", ty.u32())),
+           Decl(Const("r", nullptr, Op("a", "b"))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn(uint b) {
+  uint a = 0u;
+  const uint r = (a )" + Token() +
+                              R"( (b == 0u ? 1u : b));
+}
+)");
+}  // namespace tint
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_vec_i32) {
+  Func("fn", {Param("b", ty.vec3<i32>())}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec3<i32>())),
+           Decl(Const("r", nullptr, Op("a", "b"))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn(int3 b) {
+  int3 a = int3(0, 0, 0);
+  const int3 r = (a )" + Token() +
+                              R"( (b == int3(0, 0, 0) ? int3(1, 1, 1) : b));
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_scalar_i32) {
+  Func("fn", {Param("b", ty.i32())}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec3<i32>())),
+           Decl(Const("r", nullptr, Op("a", "b"))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(void fn(int b) {
+  int3 a = int3(0, 0, 0);
+  const int3 r = (a )" + Token() +
+                              R"( (b == 0 ? 1 : b));
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_i32) {
+  Func("zero", {}, ty.i32(),
+       {
+           Return(Expr(0)),
+       });
+
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32())),
+           Decl(Const("r", nullptr, Op("a", Call("zero")))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
+  return value == 0 ? 1 : value;
+}
+
+int zero() {
+  return 0;
+}
+
+void fn() {
+  int a = 0;
+  const int r = (a )" + Token() +
+                              R"( value_or_one_if_zero_int(zero()));
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_u32) {
+  Func("zero", {}, ty.u32(),
+       {
+           Return(Expr(0u)),
+       });
+
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.u32())),
+           Decl(Const("r", nullptr, Op("a", Call("zero")))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(uint value_or_one_if_zero_uint(uint value) {
+  return value == 0u ? 1u : value;
+}
+
+uint zero() {
+  return 0u;
+}
+
+void fn() {
+  uint a = 0u;
+  const uint r = (a )" + Token() +
+                              R"( value_or_one_if_zero_uint(zero()));
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_vec_i32) {
+  Func("zero", {}, ty.vec3<i32>(),
+       {
+           Return(vec3<i32>(0, 0, 0)),
+       });
+
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec3<i32>())),
+           Decl(Const("r", nullptr, Op("a", Call("zero")))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(int3 value_or_one_if_zero_int3(int3 value) {
+  return value == int3(0, 0, 0) ? int3(1, 1, 1) : value;
+}
+
+int3 zero() {
+  return int3(0, 0, 0);
+}
+
+void fn() {
+  int3 a = int3(0, 0, 0);
+  const int3 r = (a )" + Token() +
+                              R"( value_or_one_if_zero_int3(zero()));
+}
+)");
+}
+
+TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_scalar_i32) {
+  Func("zero", {}, ty.i32(),
+       {
+           Return(0),
+       });
+
+  Func("fn", {}, ty.void_(),
+       {
+           Decl(Var("a", ty.vec3<i32>())),
+           Decl(Const("r", nullptr, Op("a", Call("zero")))),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate());
+  EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
+  return value == 0 ? 1 : value;
+}
+
+int zero() {
+  return 0;
+}
+
+void fn() {
+  int3 a = int3(0, 0, 0);
+  const int3 r = (a )" + Token() +
+                              R"( value_or_one_if_zero_int(zero()));
+}
+)");
+}
+}  // namespace HlslGeneratorDivMod
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_bitcast_test.cc b/src/tint/writer/hlsl/generator_impl_bitcast_test.cc
new file mode 100644
index 0000000..fcde73b
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_bitcast_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Bitcast = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Float) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asfloat(1)");
+}
+
+TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.i32(), Expr(1u));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asint(1u)");
+}
+
+TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asuint(1)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_block_test.cc b/src/tint/writer/hlsl/generator_impl_block_test.cc
new file mode 100644
index 0000000..9ad534f
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_block_test.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Block = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Block, Emit_Block) {
+  auto* b = Block(create<ast::DiscardStatement>());
+  WrapInFunction(b);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    discard;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_break_test.cc b/src/tint/writer/hlsl/generator_impl_break_test.cc
new file mode 100644
index 0000000..a3d6844
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_break_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Break = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Break, Emit_Break) {
+  auto* b = create<ast::BreakStatement>();
+  WrapInFunction(Loop(Block(b)));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), "  break;\n");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
new file mode 100644
index 0000000..5f21799
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
@@ -0,0 +1,790 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using BuiltinType = sem::BuiltinType;
+
+using ::testing::HasSubstr;
+
+using HlslGeneratorImplTest_Builtin = TestHelper;
+
+enum class ParamType {
+  kF32,
+  kU32,
+  kBool,
+};
+
+struct BuiltinData {
+  BuiltinType builtin;
+  ParamType type;
+  const char* hlsl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.hlsl_name;
+  switch (data.type) {
+    case ParamType::kF32:
+      out << "f32";
+      break;
+    case ParamType::kU32:
+      out << "u32";
+      break;
+    case ParamType::kBool:
+      out << "bool";
+      break;
+  }
+  out << ">";
+  return out;
+}
+
+const ast::CallExpression* GenerateCall(BuiltinType builtin,
+                                        ParamType type,
+                                        ProgramBuilder* builder) {
+  std::string name;
+  std::ostringstream str(name);
+  str << builtin;
+  switch (builtin) {
+    case BuiltinType::kAcos:
+    case BuiltinType::kAsin:
+    case BuiltinType::kAtan:
+    case BuiltinType::kCeil:
+    case BuiltinType::kCos:
+    case BuiltinType::kCosh:
+    case BuiltinType::kDpdx:
+    case BuiltinType::kDpdxCoarse:
+    case BuiltinType::kDpdxFine:
+    case BuiltinType::kDpdy:
+    case BuiltinType::kDpdyCoarse:
+    case BuiltinType::kDpdyFine:
+    case BuiltinType::kExp:
+    case BuiltinType::kExp2:
+    case BuiltinType::kFloor:
+    case BuiltinType::kFract:
+    case BuiltinType::kFwidth:
+    case BuiltinType::kFwidthCoarse:
+    case BuiltinType::kFwidthFine:
+    case BuiltinType::kInverseSqrt:
+    case BuiltinType::kIsFinite:
+    case BuiltinType::kIsInf:
+    case BuiltinType::kIsNan:
+    case BuiltinType::kIsNormal:
+    case BuiltinType::kLength:
+    case BuiltinType::kLog:
+    case BuiltinType::kLog2:
+    case BuiltinType::kNormalize:
+    case BuiltinType::kRound:
+    case BuiltinType::kSin:
+    case BuiltinType::kSinh:
+    case BuiltinType::kSqrt:
+    case BuiltinType::kTan:
+    case BuiltinType::kTanh:
+    case BuiltinType::kTrunc:
+    case BuiltinType::kSign:
+      return builder->Call(str.str(), "f2");
+    case BuiltinType::kLdexp:
+      return builder->Call(str.str(), "f2", "i2");
+    case BuiltinType::kAtan2:
+    case BuiltinType::kDot:
+    case BuiltinType::kDistance:
+    case BuiltinType::kPow:
+    case BuiltinType::kReflect:
+    case BuiltinType::kStep:
+      return builder->Call(str.str(), "f2", "f2");
+    case BuiltinType::kCross:
+      return builder->Call(str.str(), "f3", "f3");
+    case BuiltinType::kFma:
+    case BuiltinType::kMix:
+    case BuiltinType::kFaceForward:
+    case BuiltinType::kSmoothStep:
+      return builder->Call(str.str(), "f2", "f2", "f2");
+    case BuiltinType::kAll:
+    case BuiltinType::kAny:
+      return builder->Call(str.str(), "b2");
+    case BuiltinType::kAbs:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2");
+      } else {
+        return builder->Call(str.str(), "u2");
+      }
+    case BuiltinType::kCountOneBits:
+    case BuiltinType::kReverseBits:
+      return builder->Call(str.str(), "u2");
+    case BuiltinType::kMax:
+    case BuiltinType::kMin:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2");
+      }
+    case BuiltinType::kClamp:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2", "u2");
+      }
+    case BuiltinType::kSelect:
+      return builder->Call(str.str(), "f2", "f2", "b2");
+    case BuiltinType::kDeterminant:
+      return builder->Call(str.str(), "m2x2");
+    case BuiltinType::kTranspose:
+      return builder->Call(str.str(), "m3x2");
+    default:
+      break;
+  }
+  return nullptr;
+}
+using HlslBuiltinTest = TestParamHelper<BuiltinData>;
+TEST_P(HlslBuiltinTest, Emit) {
+  auto param = GetParam();
+
+  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
+  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
+  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
+  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = GenerateCall(param.builtin, param.type, this);
+  ASSERT_NE(nullptr, call) << "Unhandled builtin";
+  Func("func", {}, ty.void_(), {CallStmt(call)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem = program->Sem().Get(call);
+  ASSERT_NE(sem, nullptr);
+  auto* target = sem->Target();
+  ASSERT_NE(target, nullptr);
+  auto* builtin = target->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+
+  EXPECT_EQ(gen.generate_builtin_name(builtin), param.hlsl_name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_Builtin,
+    HlslBuiltinTest,
+    testing::Values(
+        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "abs"},
+        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
+        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
+        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
+        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
+        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
+        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
+        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan2"},
+        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
+        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
+        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
+        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "countbits"},
+        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
+        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
+        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
+        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
+        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "ddx"},
+        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "ddx_coarse"},
+        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "ddx_fine"},
+        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "ddy"},
+        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "ddy_coarse"},
+        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "ddy_fine"},
+        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
+        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
+        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
+        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
+        BuiltinData{BuiltinType::kFma, ParamType::kF32, "mad"},
+        BuiltinData{BuiltinType::kFract, ParamType::kF32, "frac"},
+        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
+        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
+        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
+        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
+        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
+        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
+        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
+        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
+        BuiltinData{BuiltinType::kMax, ParamType::kF32, "max"},
+        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
+        BuiltinData{BuiltinType::kMin, ParamType::kF32, "min"},
+        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
+        BuiltinData{BuiltinType::kMix, ParamType::kF32, "lerp"},
+        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
+        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
+        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
+        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reversebits"},
+        BuiltinData{BuiltinType::kRound, ParamType::kU32, "round"},
+        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
+        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
+        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
+        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
+        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
+        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
+        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
+        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
+        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
+        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"}));
+
+TEST_F(HlslGeneratorImplTest_Builtin, DISABLED_Builtin_IsNormal) {
+  FAIL();
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Builtin_Call) {
+  auto* call = Call("dot", "param1", "param2");
+
+  Global("param1", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("param2", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "dot(param1, param2)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Select_Scalar) {
+  auto* call = Call("select", 1.0f, 2.0f, true);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "(true ? 2.0f : 1.0f)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Select_Vector) {
+  auto* call =
+      Call("select", vec2<i32>(1, 2), vec2<i32>(3, 4), vec2<bool>(true, false));
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "(bool2(true, false) ? int2(3, 4) : int2(1, 2))");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Scalar) {
+  auto* call = Call("modf", 1.0f);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct modf_result {
+  float fract;
+  float whole;
+};
+modf_result tint_modf(float param_0) {
+  float whole;
+  float fract = modf(param_0, whole);
+  modf_result result = {fract, whole};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_modf(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Vector) {
+  auto* call = Call("modf", vec3<f32>());
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct modf_result_vec3 {
+  float3 fract;
+  float3 whole;
+};
+modf_result_vec3 tint_modf(float3 param_0) {
+  float3 whole;
+  float3 fract = modf(param_0, whole);
+  modf_result_vec3 result = {fract, whole};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_modf(float3(0.0f, 0.0f, 0.0f));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
+  auto* call = Call("frexp", 1.0f);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct frexp_result {
+  float sig;
+  int exp;
+};
+frexp_result tint_frexp(float param_0) {
+  float exp;
+  float sig = frexp(param_0, exp);
+  frexp_result result = {sig, int(exp)};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_frexp(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
+  auto* call = Call("frexp", vec3<f32>());
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct frexp_result_vec3 {
+  float3 sig;
+  int3 exp;
+};
+frexp_result_vec3 tint_frexp(float3 param_0) {
+  float3 exp;
+  float3 sig = frexp(param_0, exp);
+  frexp_result_vec3 result = {sig, int3(exp)};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_frexp(float3(0.0f, 0.0f, 0.0f));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, IsNormal_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("isNormal", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_isNormal(float param_0) {
+  uint exponent = asuint(param_0) & 0x7f80000;
+  uint clamped = clamp(exponent, 0x0080000, 0x7f00000);
+  return clamped == exponent;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float val = 0.0f;
+  const bool tint_symbol = tint_isNormal(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, IsNormal_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("isNormal", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool3 tint_isNormal(float3 param_0) {
+  uint3 exponent = asuint(param_0) & 0x7f80000;
+  uint3 clamped = clamp(exponent, 0x0080000, 0x7f00000);
+  return clamped == exponent;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float3 val = float3(0.0f, 0.0f, 0.0f);
+  const bool3 tint_symbol = tint_isNormal(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Degrees_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float tint_degrees(float param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float val = 0.0f;
+  const float tint_symbol = tint_degrees(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Degrees_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float3 tint_degrees(float3 param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float3 val = float3(0.0f, 0.0f, 0.0f);
+  const float3 tint_symbol = tint_degrees(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Radians_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float tint_radians(float param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float val = 0.0f;
+  const float tint_symbol = tint_radians(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Radians_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float3 tint_radians(float3 param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float3 val = float3(0.0f, 0.0f, 0.0f);
+  const float3 tint_symbol = tint_radians(val);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Pack4x8Snorm) {
+  auto* call = Call("pack4x8snorm", "p1");
+  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(uint tint_pack4x8snorm(float4 param_0) {
+  int4 i = int4(round(clamp(param_0, -1.0, 1.0) * 127.0)) & 0xff;
+  return asuint(i.x | i.y << 8 | i.z << 16 | i.w << 24);
+}
+
+static float4 p1 = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_pack4x8snorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Pack4x8Unorm) {
+  auto* call = Call("pack4x8unorm", "p1");
+  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(uint tint_pack4x8unorm(float4 param_0) {
+  uint4 i = uint4(round(clamp(param_0, 0.0, 1.0) * 255.0));
+  return (i.x | i.y << 8 | i.z << 16 | i.w << 24);
+}
+
+static float4 p1 = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_pack4x8unorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Snorm) {
+  auto* call = Call("pack2x16snorm", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16snorm(float2 param_0) {
+  int2 i = int2(round(clamp(param_0, -1.0, 1.0) * 32767.0)) & 0xffff;
+  return asuint(i.x | i.y << 16);
+}
+
+static float2 p1 = float2(0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_pack2x16snorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Unorm) {
+  auto* call = Call("pack2x16unorm", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16unorm(float2 param_0) {
+  uint2 i = uint2(round(clamp(param_0, 0.0, 1.0) * 65535.0));
+  return (i.x | i.y << 16);
+}
+
+static float2 p1 = float2(0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_pack2x16unorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Float) {
+  auto* call = Call("pack2x16float", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16float(float2 param_0) {
+  uint2 i = f32tof16(param_0);
+  return i.x | (i.y << 16);
+}
+
+static float2 p1 = float2(0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_pack2x16float(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Unpack4x8Snorm) {
+  auto* call = Call("unpack4x8snorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float4 tint_unpack4x8snorm(uint param_0) {
+  int j = int(param_0);
+  int4 i = int4(j << 24, j << 16, j << 8, j) >> 24;
+  return clamp(float4(i) / 127.0, -1.0, 1.0);
+}
+
+static uint p1 = 0u;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_unpack4x8snorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Unpack4x8Unorm) {
+  auto* call = Call("unpack4x8unorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float4 tint_unpack4x8unorm(uint param_0) {
+  uint j = param_0;
+  uint4 i = uint4(j & 0xff, (j >> 8) & 0xff, (j >> 16) & 0xff, j >> 24);
+  return float4(i) / 255.0;
+}
+
+static uint p1 = 0u;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_unpack4x8unorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Snorm) {
+  auto* call = Call("unpack2x16snorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16snorm(uint param_0) {
+  int j = int(param_0);
+  int2 i = int2(j << 16, j) >> 16;
+  return clamp(float2(i) / 32767.0, -1.0, 1.0);
+}
+
+static uint p1 = 0u;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_unpack2x16snorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Unorm) {
+  auto* call = Call("unpack2x16unorm", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16unorm(uint param_0) {
+  uint j = param_0;
+  uint2 i = uint2(j & 0xffff, j >> 16);
+  return float2(i) / 65535.0;
+}
+
+static uint p1 = 0u;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_unpack2x16unorm(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Float) {
+  auto* call = Call("unpack2x16float", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16float(uint param_0) {
+  uint i = param_0;
+  return f16tof32(uint2(i & 0xffff, i >> 16));
+}
+
+static uint p1 = 0u;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_unpack2x16float(p1);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, StorageBarrier) {
+  Func("main", {}, ty.void_(), {CallStmt(Call("storageBarrier"))},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
+void main() {
+  DeviceMemoryBarrierWithGroupSync();
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
+  Func("main", {}, ty.void_(), {CallStmt(Call("workgroupBarrier"))},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
+void main() {
+  GroupMemoryBarrierWithGroupSync();
+  return;
+}
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
new file mode 100644
index 0000000..e4e26b7
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
@@ -0,0 +1,396 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+struct ExpectedResult {
+  ExpectedResult(const char* o) : out(o) {}  // NOLINT
+  ExpectedResult(const char* p, const char* o) : pre(p), out(o) {}
+
+  std::string pre;
+  std::string out;
+};
+
+ExpectedResult expected_texture_overload(
+    ast::builtin::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+      return {
+          R"(int tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp);
+)",
+          "tint_tmp;",
+      };
+    case ValidTextureOverload::kDimensions2d:
+    case ValidTextureOverload::kDimensionsDepth2d:
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+      return {
+          R"(int2 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y);
+)",
+          "tint_tmp;",
+      };
+    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.xy;",
+      };
+
+    case ValidTextureOverload::kDimensions2dArray:
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kDimensions3d:
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp;",
+      };
+    case ValidTextureOverload::kDimensionsCube:
+    case ValidTextureOverload::kDimensionsDepthCube:
+      return {
+          R"(int2 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y);
+)",
+          "tint_tmp;",
+      };
+    case ValidTextureOverload::kDimensionsCubeArray:
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kDimensions2dLevel:
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+      return {
+          R"(int4 tint_tmp;
+  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kDimensions3dLevel:
+      return {
+          R"(int4 tint_tmp;
+  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
+)",
+          "tint_tmp.xyz;",
+      };
+    case ValidTextureOverload::kDimensionsCubeLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return {
+          R"(int4 tint_tmp;
+  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
+)",
+          "tint_tmp.xy;",
+      };
+    case ValidTextureOverload::kGather2dF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kGather2dOffsetF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kGather2dArrayF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, float(3))))";
+    case ValidTextureOverload::kGather2dArrayOffsetF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)))";
+    case ValidTextureOverload::kGatherCubeF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kGatherCubeArrayF32:
+      return R"(tint_symbol.GatherRed(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))))";
+    case ValidTextureOverload::kGatherDepth2dF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kGatherDepth2dOffsetF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kGatherDepth2dArrayF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, float(3))))";
+    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)))";
+    case ValidTextureOverload::kGatherDepthCubeF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kGatherDepthCubeArrayF32:
+      return R"(tint_symbol.Gather(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))))";
+    case ValidTextureOverload::kGatherCompareDepth2dF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6)))";
+    case ValidTextureOverload::kGatherCompareDepthCubeF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
+      return R"(tint_symbol.GatherCmp(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f))";
+    case ValidTextureOverload::kNumLayers2dArray:
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+    case ValidTextureOverload::kNumLayersCubeArray:
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.z;",
+      };
+    case ValidTextureOverload::kNumLevels2d:
+    case ValidTextureOverload::kNumLevelsCube:
+    case ValidTextureOverload::kNumLevelsDepth2d:
+    case ValidTextureOverload::kNumLevelsDepthCube:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.z;",
+      };
+    case ValidTextureOverload::kNumLevels2dArray:
+    case ValidTextureOverload::kNumLevels3d:
+    case ValidTextureOverload::kNumLevelsCubeArray:
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return {
+          R"(int4 tint_tmp;
+  tint_symbol.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
+)",
+          "tint_tmp.w;",
+      };
+    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+      return {
+          R"(int3 tint_tmp;
+  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
+)",
+          "tint_tmp.z;",
+      };
+    case ValidTextureOverload::kSample1dF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, 1.0f);)";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f));)";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4));)";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)));)";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5));)";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f));)";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), int3(4, 5, 6));)";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f));)";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)));)";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f)).x;)";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)).x;)";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3))).x;)";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)).x;)";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)).x;)";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(tint_symbol.Sample(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))).x;)";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6));)";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7));)";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(tint_symbol.SampleBias(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6));)";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7));)";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3, int2(4, 5)).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4).x;)";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4, int2(5, 6)).x;)";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4).x;)";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(tint_symbol.SampleLevel(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5).x;)";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f));)";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f), int2(7, 7));)";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, float(3)), float2(4.0f, 5.0f), float2(6.0f, 7.0f));)";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, float(3)), float2(4.0f, 5.0f), float2(6.0f, 7.0f), int2(6, 7));)";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));)";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f), int3(0, 1, 2));)";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));)";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(tint_symbol.SampleGrad(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), float3(5.0f, 6.0f, 7.0f), float3(8.0f, 9.0f, 10.0f));)";
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f, int2(5, 6));)";
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return R"(tint_symbol.SampleCmp(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f, int2(5, 6));)";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
+      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
+    case ValidTextureOverload::kLoad1dLevelF32:
+    case ValidTextureOverload::kLoad1dLevelU32:
+    case ValidTextureOverload::kLoad1dLevelI32:
+      return R"(tint_symbol.Load(int2(1, 3));)";
+    case ValidTextureOverload::kLoad2dLevelF32:
+    case ValidTextureOverload::kLoad2dLevelU32:
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return R"(tint_symbol.Load(int3(1, 2, 3));)";
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+    case ValidTextureOverload::kLoad3dLevelF32:
+    case ValidTextureOverload::kLoad3dLevelU32:
+    case ValidTextureOverload::kLoad3dLevelI32:
+      return R"(tint_symbol.Load(int4(1, 2, 3, 4));)";
+    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return R"(tint_symbol.Load(int2(1, 2), 3);)";
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+      return R"(tint_symbol.Load(int3(1, 2, 3)).x;)";
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return R"(tint_symbol.Load(int4(1, 2, 3, 4)).x;)";
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+      return R"(tint_symbol[1] = float4(2.0f, 3.0f, 4.0f, 5.0f);)";
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+      return R"(tint_symbol[int2(1, 2)] = float4(3.0f, 4.0f, 5.0f, 6.0f);)";
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return R"(tint_symbol[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f);)";
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return R"(tint_symbol[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f);)";
+  }
+  return "<unmatched texture overload>";
+}  // NOLINT - Ignore the length of this function
+
+class HlslGeneratorBuiltinTextureTest
+    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
+
+TEST_P(HlslGeneratorBuiltinTextureTest, Call) {
+  auto param = GetParam();
+
+  param.BuildTextureVariable(this);
+  param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  auto* stmt = CallStmt(call);
+
+  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto expected = expected_texture_overload(param.overload);
+
+  EXPECT_THAT(gen.result(), HasSubstr(expected.pre));
+  EXPECT_THAT(gen.result(), HasSubstr(expected.out));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorBuiltinTextureTest,
+    HlslGeneratorBuiltinTextureTest,
+    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_call_test.cc b/src/tint/writer/hlsl/generator_impl_call_test.cc
new file mode 100644
index 0000000..d1adc0e
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_call_test.cc
@@ -0,0 +1,81 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Call = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
+  Func("my_func", {}, ty.f32(), {Return(1.23f)});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func()");
+}
+
+TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.f32(), {Return(1.23f)});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func(param1, param2)");
+}
+
+TEST_F(HlslGeneratorImplTest_Call, EmitStatement_Call) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = CallStmt(Call("my_func", "param1", "param2"));
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(call)) << gen.error();
+  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_case_test.cc b/src/tint/writer/hlsl/generator_impl_case_test.cc
new file mode 100644
index 0000000..56ac282
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_case_test.cc
@@ -0,0 +1,115 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Case = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) {
+  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
+  auto* s =
+      Switch(1,                                                          //
+             Case(Expr(4), Block(create<ast::FallthroughStatement>())),  //
+             Case(Expr(5), Block(create<ast::ReturnStatement>())),       //
+             DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 4: {
+    /* fallthrough */
+    {
+      return;
+    }
+    break;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
+  auto* s =
+      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
+             DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5:
+  case 6: {
+    break;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_Default) {
+  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  default: {
+    break;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_cast_test.cc b/src/tint/writer/hlsl/generator_impl_cast_test.cc
new file mode 100644
index 0000000..f24b9d2
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_cast_test.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Cast = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) {
+  auto* cast = Construct<f32>(1);
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float(1)");
+}
+
+TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
+  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float3(int3(1, 2, 3))");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_constructor_test.cc b/src/tint/writer/hlsl/generator_impl_constructor_test.cc
new file mode 100644
index 0000000..db79af9
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_constructor_test.cc
@@ -0,0 +1,268 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using HlslGeneratorImplTest_Constructor = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Bool) {
+  WrapInFunction(Expr(false));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("false"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
+  WrapInFunction(Expr(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
+  WrapInFunction(Expr(56779u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
+  // Use a number close to 1<<30 but whose decimal representation ends in 0.
+  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
+  WrapInFunction(Construct<f32>(-1.2e-5f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
+  WrapInFunction(Construct<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
+  WrapInFunction(Construct<i32>(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
+  WrapInFunction(Construct<u32>(12345u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
+  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
+  WrapInFunction(vec3<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3(0.0f, 0.0f, 0.0f)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Float_Literal) {
+  WrapInFunction(vec3<f32>(2.0f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3((2.0f).xxx)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Float_Var) {
+  auto* var = Var("v", nullptr, Expr(2.0f));
+  auto* cast = vec3<f32>(var);
+  WrapInFunction(var, cast);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float v = 2.0f;
+  const float3 tint_symbol = float3((v).xxx);)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Bool_Literal) {
+  WrapInFunction(vec3<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool3((true).xxx)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Bool_Var) {
+  auto* var = Var("v", nullptr, Expr(true));
+  auto* cast = vec3<bool>(var);
+  WrapInFunction(var, cast);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(bool v = true;
+  const bool3 tint_symbol = bool3((v).xxx);)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Int) {
+  WrapInFunction(vec3<i32>(2));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int3((2).xxx)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_UInt) {
+  WrapInFunction(vec3<u32>(2u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint3((2u).xxx)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
+  WrapInFunction(
+      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr(
+          "float2x3(float3(1.0f, 2.0f, 3.0f), float3(3.0f, 4.0f, 5.0f))"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat_Empty) {
+  WrapInFunction(mat2x3<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  EXPECT_THAT(gen.result(),
+              HasSubstr("float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
+  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3),
+                           vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f),
+                           vec3<f32>(7.f, 8.f, 9.f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("{float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f),"
+                        " float3(7.0f, 8.0f, 9.0f)}"));
+}
+
+// TODO(bclayton): Zero-init arrays
+TEST_F(HlslGeneratorImplTest_Constructor,
+       DISABLED_EmitConstructor_Type_Array_Empty) {
+  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("{float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f),"
+                        " float3(0.0f, 0.0f, 0.0f)}"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("{1, 2.0f, int3(3, 4, 5)}"));
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str)));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("(S)0"));
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_continue_test.cc b/src/tint/writer/hlsl/generator_impl_continue_test.cc
new file mode 100644
index 0000000..f487938
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_continue_test.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Continue = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Continue, Emit_Continue) {
+  auto* loop = Loop(Block(If(false, Block(Break())),  //
+                          Continue()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
+    if (false) {
+      break;
+    }
+    continue;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_discard_test.cc b/src/tint/writer/hlsl/generator_impl_discard_test.cc
new file mode 100644
index 0000000..36322f5
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_discard_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Discard = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Discard, Emit_Discard) {
+  auto* stmt = create<ast::DiscardStatement>();
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  discard;\n");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_function_test.cc b/src/tint/writer/hlsl/generator_impl_function_test.cc
new file mode 100644
index 0000000..ca5aba7
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_function_test.cc
@@ -0,0 +1,931 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Function = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function) {
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  void my_func() {
+    return;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
+  Func("GeometryShader", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(  void tint_symbol() {
+    return;
+  })"));
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithParams) {
+  Func("my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
+       ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  void my_func(float a, int b) {
+    return;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_NoReturn_Void) {
+  Func("main", ast::VariableList{}, ty.void_(), {/* no explicit return */},
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void main() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, PtrParameter) {
+  // fn f(foo : ptr<function, f32>) -> f32 {
+  //   return *foo;
+  // }
+  Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))},
+       ty.f32(), {Return(Deref("foo"))});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) {
+  return foo;
+}
+)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithInOutVars) {
+  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
+  //   return foo;
+  // }
+  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
+  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
+  float foo : TEXCOORD0;
+};
+struct tint_symbol_2 {
+  float value : SV_Target1;
+};
+
+float frag_main_inner(float foo) {
+  return foo;
+}
+
+tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) {
+  const float inner_result = frag_main_inner(tint_symbol.foo);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithInOut_Builtins) {
+  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
+  //   return coord.x;
+  // }
+  auto* coord_in =
+      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
+       {Return(MemberAccessor("coord", "x"))},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kFragDepth)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
+  float4 coord : SV_Position;
+};
+struct tint_symbol_2 {
+  float value : SV_Depth;
+};
+
+float frag_main_inner(float4 coord) {
+  return coord.x;
+}
+
+tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) {
+  const float inner_result = frag_main_inner(tint_symbol.coord);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
+  // struct Interface {
+  //   @builtin(position) pos : vec4<f32>;
+  //   @location(1) col1 : f32;
+  //   @location(2) col2 : f32;
+  // };
+  // fn vert_main() -> Interface {
+  //   return Interface(vec4<f32>(), 0.4, 0.6);
+  // }
+  // fn frag_main(inputs : Interface) {
+  //   const r = inputs.col1;
+  //   const g = inputs.col2;
+  //   const p = inputs.pos;
+  // }
+  auto* interface_struct = Structure(
+      "Interface",
+      {
+          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+          Member("col1", ty.f32(), {Location(1)}),
+          Member("col2", ty.f32(), {Location(2)}),
+      });
+
+  Func("vert_main", {}, ty.Of(interface_struct),
+       {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
+                         Expr(0.5f), Expr(0.25f)))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
+       {
+           Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))),
+           Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))),
+           Decl(Const("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct Interface {
+  float4 pos;
+  float col1;
+  float col2;
+};
+struct tint_symbol {
+  float col1 : TEXCOORD1;
+  float col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+Interface vert_main_inner() {
+  const Interface tint_symbol_3 = {float4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f};
+  return tint_symbol_3;
+}
+
+tint_symbol vert_main() {
+  const Interface inner_result = vert_main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  float col1 : TEXCOORD1;
+  float col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+void frag_main_inner(Interface inputs) {
+  const float r = inputs.col1;
+  const float g = inputs.col2;
+  const float4 p = inputs.pos;
+}
+
+void frag_main(tint_symbol_2 tint_symbol_1) {
+  const Interface tint_symbol_4 = {tint_symbol_1.pos, tint_symbol_1.col1, tint_symbol_1.col2};
+  frag_main_inner(tint_symbol_4);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
+  // struct VertexOutput {
+  //   @builtin(position) pos : vec4<f32>;
+  // };
+  // fn foo(x : f32) -> VertexOutput {
+  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
+  // }
+  // fn vert_main1() -> VertexOutput {
+  //   return foo(0.5);
+  // }
+  // fn vert_main2() -> VertexOutput {
+  //   return foo(0.25);
+  // }
+  auto* vertex_output_struct = Structure(
+      "VertexOutput",
+      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+
+  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
+       {});
+
+  Func("vert_main1", {}, ty.Of(vertex_output_struct),
+       {Return(Call("foo", Expr(0.5f)))}, {Stage(ast::PipelineStage::kVertex)});
+
+  Func("vert_main2", {}, ty.Of(vertex_output_struct),
+       {Return(Call("foo", Expr(0.25f)))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct VertexOutput {
+  float4 pos;
+};
+
+VertexOutput foo(float x) {
+  const VertexOutput tint_symbol_2 = {float4(x, x, x, 1.0f)};
+  return tint_symbol_2;
+}
+
+struct tint_symbol {
+  float4 pos : SV_Position;
+};
+
+VertexOutput vert_main1_inner() {
+  return foo(0.5f);
+}
+
+tint_symbol vert_main1() {
+  const VertexOutput inner_result = vert_main1_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  float4 pos : SV_Position;
+};
+
+VertexOutput vert_main2_inner() {
+  return foo(0.25f);
+}
+
+tint_symbol_1 vert_main2() {
+  const VertexOutput inner_result_1 = vert_main2_inner();
+  tint_symbol_1 wrapper_result_1 = (tint_symbol_1)0;
+  wrapper_result_1.pos = inner_result_1.pos;
+  return wrapper_result_1;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
+  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
+                           {create<ast::StructBlockAttribute>()});
+  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(1),
+                     });
+
+  Func("sub_func",
+       {
+           Param("param", ty.f32()),
+       },
+       ty.f32(),
+       {
+           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", {}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_ubo : register(b0, space1) {
+  uint4 ubo[1];
+};
+
+float sub_func(float param) {
+  return asfloat(ubo[0].x);
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_UniformStruct) {
+  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("uniforms", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_uniforms : register(b0, space1) {
+  uint4 uniforms[1];
+};
+
+void frag_main() {
+  float v = uniforms.coord.x;
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(RWByteAddressBuffer coord : register(u0, space1);
+
+void frag_main() {
+  float v = asfloat(coord.Load(4u));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(ByteAddressBuffer coord : register(t0, space1);
+
+void frag_main() {
+  float v = asfloat(coord.Load(4u));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(RWByteAddressBuffer coord : register(u0, space1);
+
+void frag_main() {
+  coord.Store(4u, asuint(2.0f));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(RWByteAddressBuffer coord : register(u0, space1);
+
+void frag_main() {
+  coord.Store(4u, asuint(2.0f));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
+  auto* s = Structure("S", {Member("x", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("coord", ty.Of(s), ast::StorageClass::kUniform,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
+       {
+           Return(MemberAccessor("coord", "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_coord : register(b0, space1) {
+  uint4 coord[1];
+};
+
+float sub_func(float param) {
+  return coord.x;
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
+  auto* s = Structure("S", {Member("x", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(1),
+         });
+
+  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
+       {
+           Return(MemberAccessor("coord", "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(RWByteAddressBuffer coord : register(u0, space1);
+
+float sub_func(float param) {
+  return asfloat(coord.Load(0u));
+}
+
+void frag_main() {
+  float v = sub_func(1.0f);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_WithNameCollision) {
+  Func("GeometryShader", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void tint_symbol() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Return(),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(2, 4, 6),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(2, 4, 6)]
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) {
+  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
+  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
+  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize("width", "height", "depth"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(static const int width = int(2);
+static const int height = int(3);
+static const int depth = int(4);
+
+[numthreads(2, 3, 4)]
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
+  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
+  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
+  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
+  Func("main", ast::VariableList{}, ty.void_(), {},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize("width", "height", "depth"),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_7
+#define WGSL_SPEC_CONSTANT_7 int(2)
+#endif
+static const int width = WGSL_SPEC_CONSTANT_7;
+#ifndef WGSL_SPEC_CONSTANT_8
+#define WGSL_SPEC_CONSTANT_8 int(3)
+#endif
+static const int height = WGSL_SPEC_CONSTANT_8;
+#ifndef WGSL_SPEC_CONSTANT_9
+#define WGSL_SPEC_CONSTANT_9 int(4)
+#endif
+static const int depth = WGSL_SPEC_CONSTANT_9;
+
+[numthreads(WGSL_SPEC_CONSTANT_7, WGSL_SPEC_CONSTANT_8, WGSL_SPEC_CONSTANT_9)]
+void main() {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
+  Func("my_func", ast::VariableList{Param("a", ty.array<f32, 5>())}, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void my_func(float a[5]) {
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
+  Func("my_func", {}, ty.array<f32, 5>(),
+       {
+           Return(Construct(ty.array<f32, 5>())),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(typedef float my_func_ret[5];
+my_func_ret my_func() {
+  return (float[5])0;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithDiscardAndVoidReturn) {
+  Func("my_func", {Param("a", ty.i32())}, ty.void_(),
+       {
+           If(Equal("a", 0),  //
+              Block(create<ast::DiscardStatement>())),
+           Return(),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void my_func(int a) {
+  if ((a == 0)) {
+    discard;
+  }
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Function_WithDiscardAndNonVoidReturn) {
+  Func("my_func", {Param("a", ty.i32())}, ty.i32(),
+       {
+           If(Equal("a", 0),  //
+              Block(create<ast::DiscardStatement>())),
+           Return(42),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(int my_func(int a) {
+  if (true) {
+    if ((a == 0)) {
+      discard;
+    }
+    return 42;
+  }
+  int unused;
+  return unused;
+}
+)");
+}
+
+// https://crbug.com/tint/297
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
+  // [[block]] struct Data {
+  //   d : f32;
+  // };
+  // @binding(0) @group(0) var<storage> data : Data;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn a() {
+  //   var v = data.d;
+  //   return;
+  // }
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn b() {
+  //   var v = data.d;
+  //   return;
+  // }
+
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("a", ast::VariableList{}, ty.void_(),
+         {
+             Decl(var),
+             Return(),
+         },
+         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  }
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("b", ast::VariableList{}, ty.void_(),
+         {
+             Decl(var),
+             Return(),
+         },
+         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  }
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(RWByteAddressBuffer data : register(u0, space0);
+
+[numthreads(1, 1, 1)]
+void a() {
+  float v = asfloat(data.Load(0u));
+  return;
+}
+
+[numthreads(1, 1, 1)]
+void b() {
+  float v = asfloat(data.Load(0u));
+  return;
+}
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_identifier_test.cc b/src/tint/writer/hlsl/generator_impl_identifier_test.cc
new file mode 100644
index 0000000..56237f5
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_identifier_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Identifier = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Identifier, EmitIdentifierExpression) {
+  Global("foo", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* i = Expr("foo");
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
+  EXPECT_EQ(out.str(), "foo");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_if_test.cc b/src/tint/writer/hlsl/generator_impl_if_test.cc
new file mode 100644
index 0000000..63c551d
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_if_test.cc
@@ -0,0 +1,135 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_If = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_If, Emit_If) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body);
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElseIf) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElse) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    return;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithMultiple) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+
+  auto* else_body = Block(Return());
+
+  auto* else_body_2 = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body,
+               ast::ElseStatementList{
+                   create<ast::ElseStatement>(else_cond, else_body),
+                   create<ast::ElseStatement>(nullptr, else_body_2),
+               });
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    } else {
+      return;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_import_test.cc b/src/tint/writer/hlsl/generator_impl_import_test.cc
new file mode 100644
index 0000000..d80ba11
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_import_test.cc
@@ -0,0 +1,283 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Import = TestHelper;
+
+struct HlslImportData {
+  const char* name;
+  const char* hlsl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, HlslImportData data) {
+  out << data.name;
+  return out;
+}
+
+using HlslImportData_SingleParamTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_SingleParamTest, FloatScalar) {
+  auto param = GetParam();
+
+  auto* ident = Expr(param.name);
+  auto* expr = Call(ident, 1.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_SingleParamTest,
+                         testing::Values(HlslImportData{"abs", "abs"},
+                                         HlslImportData{"acos", "acos"},
+                                         HlslImportData{"asin", "asin"},
+                                         HlslImportData{"atan", "atan"},
+                                         HlslImportData{"cos", "cos"},
+                                         HlslImportData{"cosh", "cosh"},
+                                         HlslImportData{"ceil", "ceil"},
+                                         HlslImportData{"exp", "exp"},
+                                         HlslImportData{"exp2", "exp2"},
+                                         HlslImportData{"floor", "floor"},
+                                         HlslImportData{"fract", "frac"},
+                                         HlslImportData{"inverseSqrt", "rsqrt"},
+                                         HlslImportData{"length", "length"},
+                                         HlslImportData{"log", "log"},
+                                         HlslImportData{"log2", "log2"},
+                                         HlslImportData{"round", "round"},
+                                         HlslImportData{"sign", "sign"},
+                                         HlslImportData{"sin", "sin"},
+                                         HlslImportData{"sinh", "sinh"},
+                                         HlslImportData{"sqrt", "sqrt"},
+                                         HlslImportData{"tan", "tan"},
+                                         HlslImportData{"tanh", "tanh"},
+                                         HlslImportData{"trunc", "trunc"}));
+
+using HlslImportData_SingleIntParamTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_SingleIntParamTest, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, Expr(1));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_SingleIntParamTest,
+                         testing::Values(HlslImportData{"abs", "abs"}));
+
+using HlslImportData_SingleVectorParamTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_SingleVectorParamTest, FloatVector) {
+  auto param = GetParam();
+
+  auto* ident = Expr(param.name);
+  auto* expr = Call(ident, vec3<f32>(1.f, 2.f, 3.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            std::string(param.hlsl_name) + "(float3(1.0f, 2.0f, 3.0f))");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_SingleVectorParamTest,
+                         testing::Values(HlslImportData{"abs", "abs"},
+                                         HlslImportData{"acos", "acos"},
+                                         HlslImportData{"asin", "asin"},
+                                         HlslImportData{"atan", "atan"},
+                                         HlslImportData{"cos", "cos"},
+                                         HlslImportData{"cosh", "cosh"},
+                                         HlslImportData{"ceil", "ceil"},
+                                         HlslImportData{"exp", "exp"},
+                                         HlslImportData{"exp2", "exp2"},
+                                         HlslImportData{"floor", "floor"},
+                                         HlslImportData{"fract", "frac"},
+                                         HlslImportData{"inverseSqrt", "rsqrt"},
+                                         HlslImportData{"length", "length"},
+                                         HlslImportData{"log", "log"},
+                                         HlslImportData{"log2", "log2"},
+                                         HlslImportData{"normalize",
+                                                        "normalize"},
+                                         HlslImportData{"round", "round"},
+                                         HlslImportData{"sign", "sign"},
+                                         HlslImportData{"sin", "sin"},
+                                         HlslImportData{"sinh", "sinh"},
+                                         HlslImportData{"sqrt", "sqrt"},
+                                         HlslImportData{"tan", "tan"},
+                                         HlslImportData{"tanh", "tanh"},
+                                         HlslImportData{"trunc", "trunc"}));
+
+using HlslImportData_DualParam_ScalarTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_DualParam_ScalarTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1.f, 2.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_DualParam_ScalarTest,
+                         testing::Values(HlslImportData{"atan2", "atan2"},
+                                         HlslImportData{"distance", "distance"},
+                                         HlslImportData{"max", "max"},
+                                         HlslImportData{"min", "min"},
+                                         HlslImportData{"pow", "pow"},
+                                         HlslImportData{"step", "step"}));
+
+using HlslImportData_DualParam_VectorTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_DualParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr =
+      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            std::string(param.hlsl_name) +
+                "(float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f))");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_DualParam_VectorTest,
+                         testing::Values(HlslImportData{"atan2", "atan2"},
+                                         HlslImportData{"cross", "cross"},
+                                         HlslImportData{"distance", "distance"},
+                                         HlslImportData{"max", "max"},
+                                         HlslImportData{"min", "min"},
+                                         HlslImportData{"pow", "pow"},
+                                         HlslImportData{"reflect", "reflect"},
+                                         HlslImportData{"step", "step"}));
+
+using HlslImportData_DualParam_Int_Test = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_DualParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_DualParam_Int_Test,
+                         testing::Values(HlslImportData{"max", "max"},
+                                         HlslImportData{"min", "min"}));
+
+using HlslImportData_TripleParam_ScalarTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_TripleParam_ScalarTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f, 3.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_TripleParam_ScalarTest,
+                         testing::Values(HlslImportData{"fma", "mad"},
+                                         HlslImportData{"mix", "lerp"},
+                                         HlslImportData{"clamp", "clamp"},
+                                         HlslImportData{"smoothStep",
+                                                        "smoothstep"}));
+
+using HlslImportData_TripleParam_VectorTest = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_TripleParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
+                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(
+      out.str(),
+      std::string(param.hlsl_name) +
+          R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))");
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_Import,
+    HlslImportData_TripleParam_VectorTest,
+    testing::Values(HlslImportData{"faceForward", "faceforward"},
+                    HlslImportData{"fma", "mad"},
+                    HlslImportData{"clamp", "clamp"},
+                    HlslImportData{"smoothStep", "smoothstep"}));
+
+TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_FMix) {
+  FAIL();
+}
+
+using HlslImportData_TripleParam_Int_Test = TestParamHelper<HlslImportData>;
+TEST_P(HlslImportData_TripleParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2, 3);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2, 3)");
+}
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
+                         HlslImportData_TripleParam_Int_Test,
+                         testing::Values(HlslImportData{"clamp", "clamp"}));
+
+TEST_F(HlslGeneratorImplTest_Import, HlslImportData_Determinant) {
+  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("determinant", "var");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string("determinant(var)"));
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_loop_test.cc b/src/tint/writer/hlsl/generator_impl_loop_test.cc
new file mode 100644
index 0000000..82db029
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_loop_test.cc
@@ -0,0 +1,381 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Loop = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_Loop) {
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block();
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
+    discard;
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
+    discard;
+    {
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* inner = Loop(body, continuing);
+
+  body = Block(inner);
+
+  auto* lhs = Expr("lhs");
+  auto* rhs = Expr("rhs");
+
+  continuing = Block(Assign(lhs, rhs));
+
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
+    [loop] while (true) {
+      discard;
+      {
+        a_statement();
+      }
+    }
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithVarUsedInContinuing) {
+  // loop {
+  //   var lhs : f32 = 2.4;
+  //   var other : f32;
+  //   break;
+  //   continuing {
+  //     lhs = rhs
+  //   }
+  // }
+
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
+                     Decl(Var("other", ty.f32())),            //
+                     Break());
+
+  auto* continuing = Block(Assign("lhs", "rhs"));
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
+    float lhs = 2.400000095f;
+    float other = 0.0f;
+    break;
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoop) {
+  // for(; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, nullptr, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] for(; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) {
+  // for(var i : i32; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] for(int i = 0; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
+  // for(var b = true && false; ; ) {
+  //   return;
+  // }
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr,
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    bool tint_tmp = true;
+    if (tint_tmp) {
+      tint_tmp = false;
+    }
+    bool b = (tint_tmp);
+    [loop] for(; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) {
+  // for(; true; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, true, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] for(; true; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
+  // for(; true && false; ) {
+  //   return;
+  // }
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* f = For(nullptr, multi_stmt, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] while (true) {
+      bool tint_tmp = true;
+      if (tint_tmp) {
+        tint_tmp = false;
+      }
+      if (!((tint_tmp))) { break; }
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) {
+  // for(; ; i = i + 1) {
+  //   return;
+  // }
+
+  auto* v = Decl(Var("i", ty.i32()));
+  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] for(; ; i = (i + 1)) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
+  // for(; ; i = true && false) {
+  //   return;
+  // }
+
+  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                   Expr(true), Expr(false));
+  auto* v = Decl(Var("i", ty.bool_()));
+  auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] while (true) {
+      return;
+      bool tint_tmp = true;
+      if (tint_tmp) {
+        tint_tmp = false;
+      }
+      i = (tint_tmp);
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) {
+  // for(var i : i32; true; i = i + 1) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    [loop] for(int i = 0; true; i = (i + 1)) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
+  // for(var i = true && false; true && false; i = true && false) {
+  //   return;
+  // }
+
+  auto* multi_stmt_a = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+  auto* multi_stmt_b = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+  auto* multi_stmt_c = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                                     Expr(true), Expr(false));
+
+  auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b,
+                Assign("i", multi_stmt_c), Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    bool tint_tmp = true;
+    if (tint_tmp) {
+      tint_tmp = false;
+    }
+    bool i = (tint_tmp);
+    [loop] while (true) {
+      bool tint_tmp_1 = true;
+      if (tint_tmp_1) {
+        tint_tmp_1 = false;
+      }
+      if (!((tint_tmp_1))) { break; }
+      return;
+      bool tint_tmp_2 = true;
+      if (tint_tmp_2) {
+        tint_tmp_2 = false;
+      }
+      i = (tint_tmp_2);
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
new file mode 100644
index 0000000..6d11eb8
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -0,0 +1,770 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using create_type_func_ptr =
+    const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+
+inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.i32();
+}
+inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.u32();
+}
+inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.f32();
+}
+template <typename T>
+inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x4<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x2<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x3<T>();
+}
+template <typename T>
+inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x4<T>();
+}
+
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+template <typename BASE>
+class HlslGeneratorImplTest_MemberAccessorBase : public BASE {
+ public:
+  void SetupStorageBuffer(ast::StructMemberList members) {
+    ProgramBuilder& b = *this;
+
+    auto* s =
+        b.Structure("Data", members, {b.create<ast::StructBlockAttribute>()});
+
+    b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage,
+             ast::Access::kReadWrite,
+             ast::AttributeList{
+                 b.create<ast::BindingAttribute>(0),
+                 b.create<ast::GroupAttribute>(1),
+             });
+  }
+
+  void SetupFunction(ast::StatementList statements) {
+    ProgramBuilder& b = *this;
+    b.Func("main", ast::VariableList{}, b.ty.void_(), statements,
+           ast::AttributeList{
+               b.Stage(ast::PipelineStage::kFragment),
+           });
+  }
+};
+
+using HlslGeneratorImplTest_MemberAccessor =
+    HlslGeneratorImplTest_MemberAccessorBase<TestHelper>;
+
+template <typename T>
+using HlslGeneratorImplTest_MemberAccessorWithParam =
+    HlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
+  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
+
+  auto* expr = MemberAccessor("str", "mem");
+  WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct Data {
+  float mem;
+};
+
+static Data str = (Data)0;
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  float expr = str.mem;
+  return;
+}
+)");
+}
+
+struct TypeCase {
+  create_type_func_ptr member_type;
+  std::string expected;
+};
+inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
+  ProgramBuilder b;
+  auto* ty = c.member_type(b.ty);
+  out << ty->FriendlyName(b.Symbols());
+  return out;
+}
+
+using HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad =
+    HlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
+TEST_P(HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, Test) {
+  // struct Data {
+  //   a : i32;
+  //   b : <type>;
+  // };
+  // var<storage> data : Data;
+  // data.b;
+
+  auto p = GetParam();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", p.member_type(ty)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor("data", "b"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_MemberAccessor,
+    HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad,
+    testing::Values(
+        TypeCase{ty_u32, "data.Load(4u)"},
+        TypeCase{ty_f32, "asfloat(data.Load(4u))"},
+        TypeCase{ty_i32, "asint(data.Load(4u))"},
+        TypeCase{ty_vec2<u32>, "data.Load2(8u)"},
+        TypeCase{ty_vec2<f32>, "asfloat(data.Load2(8u))"},
+        TypeCase{ty_vec2<i32>, "asint(data.Load2(8u))"},
+        TypeCase{ty_vec3<u32>, "data.Load3(16u)"},
+        TypeCase{ty_vec3<f32>, "asfloat(data.Load3(16u))"},
+        TypeCase{ty_vec3<i32>, "asint(data.Load3(16u))"},
+        TypeCase{ty_vec4<u32>, "data.Load4(16u)"},
+        TypeCase{ty_vec4<f32>, "asfloat(data.Load4(16u))"},
+        TypeCase{ty_vec4<i32>, "asint(data.Load4(16u))"},
+        TypeCase{
+            ty_mat2x2<f32>,
+            R"(return float2x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))));)"},
+        TypeCase{
+            ty_mat2x3<f32>,
+            R"(return float2x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))));)"},
+        TypeCase{
+            ty_mat2x4<f32>,
+            R"(return float2x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))));)"},
+        TypeCase{
+            ty_mat3x2<f32>,
+            R"(return float3x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))), asfloat(buffer.Load2((offset + 16u))));)"},
+        TypeCase{
+            ty_mat3x3<f32>,
+            R"(return float3x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))), asfloat(buffer.Load3((offset + 32u))));)"},
+        TypeCase{
+            ty_mat3x4<f32>,
+            R"(return float3x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))));)"},
+        TypeCase{
+            ty_mat4x2<f32>,
+            R"(return float4x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))), asfloat(buffer.Load2((offset + 16u))), asfloat(buffer.Load2((offset + 24u))));)"},
+        TypeCase{
+            ty_mat4x3<f32>,
+            R"(return float4x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))), asfloat(buffer.Load3((offset + 32u))), asfloat(buffer.Load3((offset + 48u))));)"},
+        TypeCase{
+            ty_mat4x4<f32>,
+            R"(return float4x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))), asfloat(buffer.Load4((offset + 48u))));)"}));
+
+using HlslGeneratorImplTest_MemberAccessor_StorageBufferStore =
+    HlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
+TEST_P(HlslGeneratorImplTest_MemberAccessor_StorageBufferStore, Test) {
+  // struct Data {
+  //   a : i32;
+  //   b : <type>;
+  // };
+  // var<storage> data : Data;
+  // data.b = <type>();
+
+  auto p = GetParam();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", p.member_type(ty)),
+  });
+
+  SetupFunction({
+      Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
+               Construct(p.member_type(ty)))),
+      Assign(MemberAccessor("data", "b"), Expr("value")),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_MemberAccessor,
+    HlslGeneratorImplTest_MemberAccessor_StorageBufferStore,
+    testing::Values(TypeCase{ty_u32, "data.Store(4u, asuint(value))"},
+                    TypeCase{ty_f32, "data.Store(4u, asuint(value))"},
+                    TypeCase{ty_i32, "data.Store(4u, asuint(value))"},
+                    TypeCase{ty_vec2<u32>, "data.Store2(8u, asuint(value))"},
+                    TypeCase{ty_vec2<f32>, "data.Store2(8u, asuint(value))"},
+                    TypeCase{ty_vec2<i32>, "data.Store2(8u, asuint(value))"},
+                    TypeCase{ty_vec3<u32>, "data.Store3(16u, asuint(value))"},
+                    TypeCase{ty_vec3<f32>, "data.Store3(16u, asuint(value))"},
+                    TypeCase{ty_vec3<i32>, "data.Store3(16u, asuint(value))"},
+                    TypeCase{ty_vec4<u32>, "data.Store4(16u, asuint(value))"},
+                    TypeCase{ty_vec4<f32>, "data.Store4(16u, asuint(value))"},
+                    TypeCase{ty_vec4<i32>, "data.Store4(16u, asuint(value))"},
+                    TypeCase{ty_mat2x2<f32>, R"({
+  buffer.Store2((offset + 0u), asuint(value[0u]));
+  buffer.Store2((offset + 8u), asuint(value[1u]));
+})"},
+                    TypeCase{ty_mat2x3<f32>, R"({
+  buffer.Store3((offset + 0u), asuint(value[0u]));
+  buffer.Store3((offset + 16u), asuint(value[1u]));
+})"},
+                    TypeCase{ty_mat2x4<f32>, R"({
+  buffer.Store4((offset + 0u), asuint(value[0u]));
+  buffer.Store4((offset + 16u), asuint(value[1u]));
+})"},
+                    TypeCase{ty_mat3x2<f32>, R"({
+  buffer.Store2((offset + 0u), asuint(value[0u]));
+  buffer.Store2((offset + 8u), asuint(value[1u]));
+  buffer.Store2((offset + 16u), asuint(value[2u]));
+})"},
+                    TypeCase{ty_mat3x3<f32>, R"({
+  buffer.Store3((offset + 0u), asuint(value[0u]));
+  buffer.Store3((offset + 16u), asuint(value[1u]));
+  buffer.Store3((offset + 32u), asuint(value[2u]));
+})"},
+                    TypeCase{ty_mat3x4<f32>, R"({
+  buffer.Store4((offset + 0u), asuint(value[0u]));
+  buffer.Store4((offset + 16u), asuint(value[1u]));
+  buffer.Store4((offset + 32u), asuint(value[2u]));
+})"},
+                    TypeCase{ty_mat4x2<f32>, R"({
+  buffer.Store2((offset + 0u), asuint(value[0u]));
+  buffer.Store2((offset + 8u), asuint(value[1u]));
+  buffer.Store2((offset + 16u), asuint(value[2u]));
+  buffer.Store2((offset + 24u), asuint(value[3u]));
+})"},
+                    TypeCase{ty_mat4x3<f32>, R"({
+  buffer.Store3((offset + 0u), asuint(value[0u]));
+  buffer.Store3((offset + 16u), asuint(value[1u]));
+  buffer.Store3((offset + 32u), asuint(value[2u]));
+  buffer.Store3((offset + 48u), asuint(value[3u]));
+})"},
+                    TypeCase{ty_mat4x4<f32>, R"({
+  buffer.Store4((offset + 0u), asuint(value[0u]));
+  buffer.Store4((offset + 16u), asuint(value[1u]));
+  buffer.Store4((offset + 32u), asuint(value[2u]));
+  buffer.Store4((offset + 48u), asuint(value[3u]));
+})"}));
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) {
+  // struct Data {
+  //   z : f32;
+  //   a : mat2x3<f32>;
+  // };
+  // var<storage> data : Data;
+  // data.a = mat2x3<f32>();
+
+  SetupStorageBuffer({
+      Member("a", ty.i32()),
+      Member("b", ty.mat2x3<f32>()),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor("data", "b"),
+             Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x3 value) {
+  buffer.Store3((offset + 0u), asuint(value[0u]));
+  buffer.Store3((offset + 16u), asuint(value[1u]));
+}
+
+void main() {
+  tint_symbol(data, 16u, float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_Matrix_Single_Element) {
+  // struct Data {
+  //   z : f32;
+  //   a : mat4x3<f32>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2][1];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.mat4x3<f32>()),
+  });
+
+  SetupFunction({
+      Decl(
+          Var("x", nullptr, ast::StorageClass::kNone,
+              IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2), 1))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  float x = asfloat(data.Load(52u));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               IndexAccessor(MemberAccessor("data", "a"), 2))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  int x = asint(data.Load(12u));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[(2 + 4) - 3];
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               IndexAccessor(MemberAccessor("data", "a"),
+                             Sub(Add(2, Expr(4)), Expr(3))))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  int x = asint(data.Load((4u + (4u * uint(((2 + 4) - 3))))));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) {
+  // struct Data {
+  //   a : @stride(4) array<i32, 5>;
+  // };
+  // var<storage> data : Data;
+  // data.a[2] = 2;
+
+  SetupStorageBuffer({
+      Member("z", ty.f32()),
+      Member("a", ty.array<i32, 5>(4)),
+  });
+
+  SetupFunction({
+      Assign(IndexAccessor(MemberAccessor("data", "a"), 2), 2),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  data.Store(12u, asuint(2));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var(
+          "x", nullptr, ast::StorageClass::kNone,
+          MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  float3 x = asfloat(data.Load3(80u));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Swizzle) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.xy
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor(
+                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                  "b"),
+                   "xy"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  float2 x = asfloat(data.Load3(80u)).xy;
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {  // NOLINT
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.g
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var("x", nullptr, ast::StorageClass::kNone,
+               MemberAccessor(
+                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                  "b"),
+                   "g"))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  float x = asfloat(data.Load(84u));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Load_MultiLevel_Index) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b[1]
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Decl(Var(
+          "x", nullptr, ast::StorageClass::kNone,
+          IndexAccessor(MemberAccessor(
+                            IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
+                        1))),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  float x = asfloat(data.Load(84u));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b = vec3<f32>(1.f, 2.f, 3.f);
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<f32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
+             vec3<f32>(1.f, 2.f, 3.f)),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  data.Store3(80u, asuint(float3(1.0f, 2.0f, 3.0f)));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
+       StorageBuffer_Store_Swizzle_SingleLetter) {
+  // struct Inner {
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
+  // };
+  // struct Data {
+  //   var c : @stride(32) array<Inner, 4>;
+  // };
+  //
+  // var<storage> data : Pre;
+  // data.c[2].b.y = 1.f;
+
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec3<i32>()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  SetupStorageBuffer({
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
+  });
+
+  SetupFunction({
+      Assign(MemberAccessor(
+                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
+                                "b"),
+                 "y"),
+             Expr(1.f)),
+  });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  auto* expected =
+      R"(RWByteAddressBuffer data : register(u0, space1);
+
+void main() {
+  data.Store(84u, asuint(1.0f));
+  return;
+}
+)";
+  EXPECT_EQ(gen.result(), expected);
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
+  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
+                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
+  auto* expr = MemberAccessor("my_vec", "xyz");
+  WrapInFunction(var, expr);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz"));
+}
+
+TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
+  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
+                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
+  auto* expr = MemberAccessor("my_vec", "gbr");
+  WrapInFunction(var, expr);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr"));
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
new file mode 100644
index 0000000..1de71ec
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_ModuleConstant = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) {
+  auto* var = Const("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
+  WrapInFunction(Decl(var));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), "static const float pos[3] = {1.0f, 2.0f, 3.0f};\n");
+}
+
+TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) {
+  auto* var = Override("pos", ty.f32(), Expr(3.0f),
+                       ast::AttributeList{
+                           Id(23),
+                       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+#define WGSL_SPEC_CONSTANT_23 3.0f
+#endif
+static const float pos = WGSL_SPEC_CONSTANT_23;
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoConstructor) {
+  auto* var = Override("pos", ty.f32(), nullptr,
+                       ast::AttributeList{
+                           Id(23),
+                       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+#error spec constant required for constant id 23
+#endif
+static const float pos = WGSL_SPEC_CONSTANT_23;
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoId) {
+  auto* a = Override("a", ty.f32(), Expr(3.0f),
+                     ast::AttributeList{
+                         Id(0),
+                     });
+  auto* b = Override("b", ty.f32(), Expr(2.0f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error();
+  ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
+#define WGSL_SPEC_CONSTANT_0 3.0f
+#endif
+static const float a = WGSL_SPEC_CONSTANT_0;
+#ifndef WGSL_SPEC_CONSTANT_1
+#define WGSL_SPEC_CONSTANT_1 2.0f
+#endif
+static const float b = WGSL_SPEC_CONSTANT_1;
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_return_test.cc b/src/tint/writer/hlsl/generator_impl_return_test.cc
new file mode 100644
index 0000000..cb266f1
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_return_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Return = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Return, Emit_Return) {
+  auto* r = Return();
+  WrapInFunction(r);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return;\n");
+}
+
+TEST_F(HlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
+  auto* r = Return(123);
+  Func("f", {}, ty.i32(), {r});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return 123;\n");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
new file mode 100644
index 0000000..9c5899a
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -0,0 +1,345 @@
+// 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/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslSanitizerTest = TestHelper;
+
+TEST_F(HlslSanitizerTest, Call_ArrayLength) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
+
+void a_func() {
+  uint tint_symbol_1 = 0u;
+  b.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u);
+  uint len = tint_symbol_2;
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
+  auto* s = Structure("my_struct",
+                      {
+                          Member(0, "z", ty.f32()),
+                          Member(4, "a", ty.array<f32>(4)),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
+
+void a_func() {
+  uint tint_symbol_1 = 0u;
+  b.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = ((tint_symbol_1 - 4u) / 4u);
+  uint len = tint_symbol_2;
+  return;
+}
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, Call_ArrayLength_ViaLets) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  auto* p = Const("p", nullptr, AddressOf("b"));
+  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(p),
+           Decl(p2),
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", p2))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
+
+void a_func() {
+  uint tint_symbol_1 = 0u;
+  b.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u);
+  uint len = tint_symbol_2;
+  return;
+}
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(2),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var(
+               "len", ty.u32(), ast::StorageClass::kNone,
+               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
+                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Options options;
+  options.array_length_from_uniform.ubo_binding = {3, 4};
+  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+      sem::BindingPoint{2, 2}, 7u);
+  GeneratorImpl& gen = SanitizeAndBuild(options);
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(cbuffer cbuffer_tint_symbol_1 : register(b4, space3) {
+  uint4 tint_symbol_1[2];
+};
+ByteAddressBuffer b : register(t1, space2);
+ByteAddressBuffer c : register(t2, space2);
+
+void a_func() {
+  uint tint_symbol_4 = 0u;
+  b.GetDimensions(tint_symbol_4);
+  const uint tint_symbol_5 = ((tint_symbol_4 - 0u) / 4u);
+  uint len = (tint_symbol_5 + ((tint_symbol_1[1].w - 0u) / 4u));
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, PromoteArrayInitializerToConstVar) {
+  auto* array_init = array<i32, 4>(1, 2, 3, 4);
+  auto* array_index = IndexAccessor(array_init, 3);
+  auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index);
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(pos),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(void main() {
+  const int tint_symbol[4] = {1, 2, 3, 4};
+  int pos = tint_symbol[3];
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, PromoteStructInitializerToConstVar) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.vec3<f32>()),
+                                 Member("c", ty.i32()),
+                             });
+  auto* struct_init = Construct(ty.Of(str), 1, vec3<f32>(2.f, 3.f, 4.f), 4);
+  auto* struct_access = MemberAccessor(struct_init, "b");
+  auto* pos =
+      Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(pos),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(struct S {
+  int a;
+  float3 b;
+  int c;
+};
+
+void main() {
+  const S tint_symbol = {1, float3(2.0f, 3.0f, 4.0f), 4};
+  float3 pos = tint_symbol.b;
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, InlinePtrLetsBasic) {
+  // var v : i32;
+  // let p : ptr<function, i32> = &v;
+  // let x : i32 = *p;
+  auto* v = Var("v", ty.i32());
+  auto* p =
+      Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
+  auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(v),
+           Decl(p),
+           Decl(x),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(void main() {
+  int v = 0;
+  int x = v;
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(HlslSanitizerTest, InlinePtrLetsComplexChain) {
+  // var a : array<mat4x4<f32>, 4>;
+  // let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
+  // let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
+  // let vp : ptr<function, vec4<f32>> = &(*mp)[2];
+  // let v : vec4<f32> = *vp;
+  auto* a = Var("a", ty.array(ty.mat4x4<f32>(), 4));
+  auto* ap = Const(
+      "ap",
+      ty.pointer(ty.array(ty.mat4x4<f32>(), 4), ast::StorageClass::kFunction),
+      AddressOf(a));
+  auto* mp =
+      Const("mp", ty.pointer(ty.mat4x4<f32>(), ast::StorageClass::kFunction),
+            AddressOf(IndexAccessor(Deref(ap), 3)));
+  auto* vp =
+      Const("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
+            AddressOf(IndexAccessor(Deref(mp), 2)));
+  auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       {
+           Decl(a),
+           Decl(ap),
+           Decl(mp),
+           Decl(vp),
+           Decl(v),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(void main() {
+  float4x4 a[4] = (float4x4[4])0;
+  float4 v = a[3][2];
+  return;
+}
+)";
+  EXPECT_EQ(expect, got);
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_switch_test.cc b/src/tint/writer/hlsl/generator_impl_switch_test.cc
new file mode 100644
index 0000000..40310a5
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_switch_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest_Switch = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Switch, Emit_Switch) {
+  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
+  auto* s = Switch(                   //
+      Expr("cond"),                   //
+      Case(Expr(5), Block(Break())),  //
+      DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  switch(cond) {
+    case 5: {
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Switch, Emit_Switch_OnlyDefaultCase) {
+  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
+  Global("a", ty.i32(), ast::StorageClass::kPrivate);
+  auto* s = Switch(  //
+      Expr("cond"),  //
+      DefaultCase(Block(Assign(Expr("a"), Expr(42)))));
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  cond;
+  do {
+    a = 42;
+  } while (false);
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_test.cc b/src/tint/writer/hlsl/generator_impl_test.cc
new file mode 100644
index 0000000..d97ed31
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest = TestHelper;
+
+TEST_F(HlslGeneratorImplTest, Generate) {
+  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
+       ast::AttributeList{});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void my_func() {
+}
+)");
+}
+
+struct HlslBuiltinData {
+  ast::Builtin builtin;
+  const char* attribute_name;
+};
+inline std::ostream& operator<<(std::ostream& out, HlslBuiltinData data) {
+  out << data.builtin;
+  return out;
+}
+using HlslBuiltinConversionTest = TestParamHelper<HlslBuiltinData>;
+TEST_P(HlslBuiltinConversionTest, Emit) {
+  auto params = GetParam();
+  GeneratorImpl& gen = Build();
+
+  EXPECT_EQ(gen.builtin_to_attribute(params.builtin),
+            std::string(params.attribute_name));
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest,
+    HlslBuiltinConversionTest,
+    testing::Values(
+        HlslBuiltinData{ast::Builtin::kPosition, "SV_Position"},
+        HlslBuiltinData{ast::Builtin::kVertexIndex, "SV_VertexID"},
+        HlslBuiltinData{ast::Builtin::kInstanceIndex, "SV_InstanceID"},
+        HlslBuiltinData{ast::Builtin::kFrontFacing, "SV_IsFrontFace"},
+        HlslBuiltinData{ast::Builtin::kFragDepth, "SV_Depth"},
+        HlslBuiltinData{ast::Builtin::kLocalInvocationId, "SV_GroupThreadID"},
+        HlslBuiltinData{ast::Builtin::kLocalInvocationIndex, "SV_GroupIndex"},
+        HlslBuiltinData{ast::Builtin::kGlobalInvocationId,
+                        "SV_DispatchThreadID"},
+        HlslBuiltinData{ast::Builtin::kWorkgroupId, "SV_GroupID"},
+        HlslBuiltinData{ast::Builtin::kSampleIndex, "SV_SampleIndex"},
+        HlslBuiltinData{ast::Builtin::kSampleMask, "SV_Coverage"}));
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_type_test.cc b/src/tint/writer/hlsl/generator_impl_type_test.cc
new file mode 100644
index 0000000..6d7d9cd
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_type_test.cc
@@ -0,0 +1,606 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using HlslGeneratorImplTest_Type = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[4]");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
+  auto* arr = ty.array(ty.array<bool, 4>(), 5);
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[5][4]");
+}
+
+// TODO(dsinclair): Is this possible? What order should it output in?
+TEST_F(HlslGeneratorImplTest_Type,
+       DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5));
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[5][4][1]");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, "ary"))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool[4]");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Bool) {
+  auto* bool_ = create<sem::Bool>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "bool");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_F32) {
+  auto* f32 = create<sem::F32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_I32) {
+  auto* i32 = create<sem::I32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "int");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float2x3");
+}
+
+// TODO(dsinclair): How to annotate as workgroup?
+TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
+  auto* f32 = create<sem::F32>();
+  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
+                                 ast::Access::kReadWrite);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float*");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  int a;
+  float b;
+};
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), "RWByteAddressBuffer g : register(u0, space0);\n");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "S");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
+  auto* s = Structure("S", {
+                               Member("double", ty.i32()),
+                               Member("float", ty.f32()),
+                           });
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct S {
+  int tint_symbol;
+  float tint_symbol_1;
+};
+)"));
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32(), {MemberOffset(0)}),
+                          Member("b", ty.f32(), {MemberOffset(8)}),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  int a;
+  float b;
+};
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_U32) {
+  auto* u32 = create<sem::U32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "uint");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Vector) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "float3");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Void) {
+  auto* void_ = create<sem::Void>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "void");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitSampler) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "SamplerState");
+}
+
+TEST_F(HlslGeneratorImplTest_Type, EmitSamplerComparison) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "SamplerComparisonState");
+}
+
+struct HlslDepthTextureData {
+  ast::TextureDimension dim;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, HlslDepthTextureData data) {
+  out << data.dim;
+  return out;
+}
+using HlslDepthTexturesTest = TestParamHelper<HlslDepthTextureData>;
+TEST_P(HlslDepthTexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* t = ty.depth_texture(params.dim);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_Type,
+    HlslDepthTexturesTest,
+    testing::Values(
+        HlslDepthTextureData{ast::TextureDimension::k2d,
+                             "Texture2D tex : register(t1, space2);"},
+        HlslDepthTextureData{ast::TextureDimension::k2dArray,
+                             "Texture2DArray tex : register(t1, space2);"},
+        HlslDepthTextureData{ast::TextureDimension::kCube,
+                             "TextureCube tex : register(t1, space2);"},
+        HlslDepthTextureData{ast::TextureDimension::kCubeArray,
+                             "TextureCubeArray tex : register(t1, space2);"}));
+
+using HlslDepthMultisampledTexturesTest = TestHelper;
+TEST_F(HlslDepthMultisampledTexturesTest, Emit) {
+  auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("Texture2DMS<float4> tex : register(t1, space2);"));
+}
+
+enum class TextureDataType { F32, U32, I32 };
+struct HlslSampledTextureData {
+  ast::TextureDimension dim;
+  TextureDataType datatype;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                HlslSampledTextureData data) {
+  out << data.dim;
+  return out;
+}
+using HlslSampledTexturesTest = TestParamHelper<HlslSampledTextureData>;
+TEST_P(HlslSampledTexturesTest, Emit) {
+  auto params = GetParam();
+
+  const ast::Type* datatype = nullptr;
+  switch (params.datatype) {
+    case TextureDataType::F32:
+      datatype = ty.f32();
+      break;
+    case TextureDataType::U32:
+      datatype = ty.u32();
+      break;
+    case TextureDataType::I32:
+      datatype = ty.i32();
+      break;
+  }
+  auto* t = ty.sampled_texture(params.dim, datatype);
+
+  Global("tex", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_Type,
+    HlslSampledTexturesTest,
+    testing::Values(
+        HlslSampledTextureData{
+            ast::TextureDimension::k1d,
+            TextureDataType::F32,
+            "Texture1D<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2d,
+            TextureDataType::F32,
+            "Texture2D<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2dArray,
+            TextureDataType::F32,
+            "Texture2DArray<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k3d,
+            TextureDataType::F32,
+            "Texture3D<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCube,
+            TextureDataType::F32,
+            "TextureCube<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCubeArray,
+            TextureDataType::F32,
+            "TextureCubeArray<float4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k1d,
+            TextureDataType::U32,
+            "Texture1D<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2d,
+            TextureDataType::U32,
+            "Texture2D<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2dArray,
+            TextureDataType::U32,
+            "Texture2DArray<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k3d,
+            TextureDataType::U32,
+            "Texture3D<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCube,
+            TextureDataType::U32,
+            "TextureCube<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCubeArray,
+            TextureDataType::U32,
+            "TextureCubeArray<uint4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k1d,
+            TextureDataType::I32,
+            "Texture1D<int4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2d,
+            TextureDataType::I32,
+            "Texture2D<int4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k2dArray,
+            TextureDataType::I32,
+            "Texture2DArray<int4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::k3d,
+            TextureDataType::I32,
+            "Texture3D<int4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCube,
+            TextureDataType::I32,
+            "TextureCube<int4> tex : register(t1, space2);",
+        },
+        HlslSampledTextureData{
+            ast::TextureDimension::kCubeArray,
+            TextureDataType::I32,
+            "TextureCubeArray<int4> tex : register(t1, space2);",
+        }));
+
+TEST_F(HlslGeneratorImplTest_Type, EmitMultisampledTexture) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, ""))
+      << gen.error();
+  EXPECT_EQ(out.str(), "Texture2DMS<float4>");
+}
+
+struct HlslStorageTextureData {
+  ast::TextureDimension dim;
+  ast::TexelFormat imgfmt;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                HlslStorageTextureData data) {
+  out << data.dim;
+  return out;
+}
+using HlslStorageTexturesTest = TestParamHelper<HlslStorageTextureData>;
+TEST_P(HlslStorageTexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
+
+  Global("tex", t, ast::AttributeList{GroupAndBinding(2, 1)});
+
+  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest_Type,
+    HlslStorageTexturesTest,
+    testing::Values(
+        HlslStorageTextureData{
+            ast::TextureDimension::k1d, ast::TexelFormat::kRgba8Unorm,
+            "RWTexture1D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k2d, ast::TexelFormat::kRgba16Float,
+            "RWTexture2D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Float,
+            "RWTexture2DArray<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k3d, ast::TexelFormat::kRg32Float,
+            "RWTexture3D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Float,
+            "RWTexture1D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k2d, ast::TexelFormat::kRgba16Uint,
+            "RWTexture2D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Uint,
+            "RWTexture2DArray<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k3d, ast::TexelFormat::kRg32Uint,
+            "RWTexture3D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Uint,
+            "RWTexture1D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{ast::TextureDimension::k2d,
+                               ast::TexelFormat::kRgba16Sint,
+                               "RWTexture2D<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Sint,
+            "RWTexture2DArray<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{ast::TextureDimension::k3d,
+                               ast::TexelFormat::kRg32Sint,
+                               "RWTexture3D<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{
+            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Sint,
+            "RWTexture1D<int4> tex : register(u1, space2);"}));
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_unary_op_test.cc b/src/tint/writer/hlsl/generator_impl_unary_op_test.cc
new file mode 100644
index 0000000..2b376c0
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_unary_op_test.cc
@@ -0,0 +1,93 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslUnaryOpTest = TestHelper;
+
+TEST_F(HlslUnaryOpTest, AddressOf) {
+  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
+}
+
+TEST_F(HlslUnaryOpTest, Complement) {
+  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "~(expr)");
+}
+
+TEST_F(HlslUnaryOpTest, Indirection) {
+  Global("G", ty.f32(), ast::StorageClass::kPrivate);
+  auto* p = Const(
+      "expr", nullptr,
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
+  WrapInFunction(p, op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
+}
+
+TEST_F(HlslUnaryOpTest, Not) {
+  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "!(expr)");
+}
+
+TEST_F(HlslUnaryOpTest, Negation) {
+  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "-(expr)");
+}
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
new file mode 100644
index 0000000..7f16121
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
@@ -0,0 +1,127 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using HlslGeneratorImplTest_VariableDecl = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) {
+  auto* var = Var("a", ty.f32());
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
+  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  const float a = 0.0f;\n");
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
+  auto* var = Var("a", ty.array<f32, 5>());
+
+  WrapInFunction(var, Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("  float a[5] = (float[5])0;\n"));
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) {
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("  static float a = 0.0f;\n"));
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_Private) {
+  Global("initializer", ty.f32(), ast::StorageClass::kPrivate);
+  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer;
+)"));
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_ZeroVec) {
+  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float3 a = float3(0.0f, 0.0f, 0.0f);
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_ZeroMat) {
+  auto* var =
+      Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(),
+            R"(float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+)");
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc
new file mode 100644
index 0000000..f59c71f
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc
@@ -0,0 +1,61 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+using ::testing::HasSubstr;
+
+using HlslGeneratorImplTest_WorkgroupVar = TestHelper;
+
+TEST_F(HlslGeneratorImplTest_WorkgroupVar, Basic) {
+  Global("wg", ty.f32(), ast::StorageClass::kWorkgroup);
+
+  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
+}
+
+TEST_F(HlslGeneratorImplTest_WorkgroupVar, Aliased) {
+  auto* alias = Alias("F32", ty.f32());
+
+  Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
+
+  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
+}
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/test_helper.h b/src/tint/writer/hlsl/test_helper.h
new file mode 100644
index 0000000..5ef28a2
--- /dev/null
+++ b/src/tint/writer/hlsl/test_helper.h
@@ -0,0 +1,121 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_HLSL_TEST_HELPER_H_
+#define SRC_TINT_WRITER_HLSL_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/renamer.h"
+#include "src/tint/writer/hlsl/generator.h"
+#include "src/tint/writer/hlsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+
+/// Helper class for testing
+template <typename BODY>
+class TestHelperBase : public BODY, public ProgramBuilder {
+ public:
+  TestHelperBase() = default;
+  ~TestHelperBase() override = default;
+
+  /// Builds the program and returns a GeneratorImpl from the program.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @return the built generator
+  GeneratorImpl& Build() {
+    if (gen_) {
+      return *gen_;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+    gen_ = std::make_unique<GeneratorImpl>(program.get());
+    return *gen_;
+  }
+
+  /// Builds the program, runs the program through the HLSL sanitizer
+  /// and returns a GeneratorImpl from the sanitized program.
+  /// @param options The HLSL generator options.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @return the built generator
+  GeneratorImpl& SanitizeAndBuild(const Options& options = {}) {
+    if (gen_) {
+      return *gen_;
+    }
+    diag::Formatter formatter;
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << formatter.format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << formatter.format(program->Diagnostics());
+    }();
+
+    auto sanitized_result = Sanitize(
+        program.get(), options.root_constant_binding_point,
+        options.disable_workgroup_init, options.array_length_from_uniform);
+    [&]() {
+      ASSERT_TRUE(sanitized_result.program.IsValid())
+          << formatter.format(sanitized_result.program.Diagnostics());
+    }();
+
+    transform::Manager transform_manager;
+    transform::DataMap transform_data;
+    transform_data.Add<transform::Renamer::Config>(
+        transform::Renamer::Target::kHlslKeywords,
+        /* preserve_unicode */ true);
+    transform_manager.Add<tint::transform::Renamer>();
+    auto result =
+        transform_manager.Run(&sanitized_result.program, transform_data);
+    [&]() {
+      ASSERT_TRUE(result.program.IsValid())
+          << formatter.format(result.program.Diagnostics());
+    }();
+    *program = std::move(result.program);
+    gen_ = std::make_unique<GeneratorImpl>(program.get());
+    return *gen_;
+  }
+
+  /// The program built with a call to Build()
+  std::unique_ptr<Program> program;
+
+ private:
+  std::unique_ptr<GeneratorImpl> gen_;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_HLSL_TEST_HELPER_H_
diff --git a/src/tint/writer/msl/generator.cc b/src/tint/writer/msl/generator.cc
new file mode 100644
index 0000000..fa3c2f0
--- /dev/null
+++ b/src/tint/writer/msl/generator.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/generator.h"
+
+#include <utility>
+
+#include "src/tint/writer/msl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+
+Options::Options() = default;
+Options::~Options() = default;
+Options::Options(const Options&) = default;
+Options& Options::operator=(const Options&) = default;
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program, const Options& options) {
+  Result result;
+
+  // Sanitize the program.
+  auto sanitized_result = Sanitize(
+      program, options.buffer_size_ubo_index, options.fixed_sample_mask,
+      options.emit_vertex_point_size, options.disable_workgroup_init,
+      options.array_length_from_uniform);
+  if (!sanitized_result.program.IsValid()) {
+    result.success = false;
+    result.error = sanitized_result.program.Diagnostics().str();
+    return result;
+  }
+  result.needs_storage_buffer_sizes =
+      sanitized_result.needs_storage_buffer_sizes;
+  result.used_array_length_from_uniform_indices =
+      std::move(sanitized_result.used_array_length_from_uniform_indices);
+
+  // Generate the MSL code.
+  auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program);
+  result.success = impl->Generate();
+  result.error = impl->error();
+  result.msl = impl->result();
+  result.has_invariant_attribute = impl->HasInvariant();
+  result.workgroup_allocations = impl->DynamicWorkgroupAllocations();
+
+  return result;
+}
+
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator.h b/src/tint/writer/msl/generator.h
new file mode 100644
index 0000000..aa7f8c5
--- /dev/null
+++ b/src/tint/writer/msl/generator.h
@@ -0,0 +1,120 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_MSL_GENERATOR_H_
+#define SRC_TINT_WRITER_MSL_GENERATOR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/text.h"
+
+namespace tint {
+
+// Forward declarations
+class Program;
+
+namespace writer {
+namespace msl {
+
+class GeneratorImpl;
+
+/// Configuration options used for generating MSL.
+struct Options {
+  /// Constructor
+  Options();
+  /// Destructor
+  ~Options();
+  /// Copy constructor
+  Options(const Options&);
+  /// Copy assignment
+  /// @returns this Options
+  Options& operator=(const Options&);
+
+  /// The index to use when generating a UBO to receive storage buffer sizes.
+  /// Defaults to 30, which is the last valid buffer slot.
+  uint32_t buffer_size_ubo_index = 30;
+
+  /// The fixed sample mask to combine with fragment shader outputs.
+  /// Defaults to 0xFFFFFFFF.
+  uint32_t fixed_sample_mask = 0xFFFFFFFF;
+
+  /// Set to `true` to generate a [[point_size]] attribute which is set to 1.0
+  /// for all vertex shaders in the module.
+  bool emit_vertex_point_size = false;
+
+  /// Set to `true` to disable workgroup memory zero initialization
+  bool disable_workgroup_init = false;
+
+  /// Options used to specify a mapping of binding points to indices into a UBO
+  /// from which to load buffer sizes.
+  ArrayLengthFromUniformOptions array_length_from_uniform = {};
+
+  // NOTE: Update src/tint/fuzzers/data_builder.h when adding or changing any
+  // struct members.
+};
+
+/// The result produced when generating MSL.
+struct Result {
+  /// Constructor
+  Result();
+
+  /// Destructor
+  ~Result();
+
+  /// Copy constructor
+  Result(const Result&);
+
+  /// True if generation was successful.
+  bool success = false;
+
+  /// The errors generated during code generation, if any.
+  std::string error;
+
+  /// The generated MSL.
+  std::string msl = "";
+
+  /// True if the shader needs a UBO of buffer sizes.
+  bool needs_storage_buffer_sizes = false;
+
+  /// True if the generated shader uses the invariant attribute.
+  bool has_invariant_attribute = false;
+
+  /// A map from entry point name to a list of dynamic workgroup allocations.
+  /// Each entry in the vector is the size of the workgroup allocation that
+  /// should be created for that index.
+  std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations;
+
+  /// Indices into the array_length_from_uniform binding that are statically
+  /// used.
+  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
+};
+
+/// Generate MSL for a program, according to a set of configuration options. The
+/// result will contain the MSL, as well as success status and diagnostic
+/// information.
+/// @param program the program to translate to MSL
+/// @param options the configuration options to use when generating MSL
+/// @returns the resulting MSL and supplementary information
+Result Generate(const Program* program, const Options& options);
+
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_MSL_GENERATOR_H_
diff --git a/src/tint/writer/msl/generator_bench.cc b/src/tint/writer/msl/generator_bench.cc
new file mode 100644
index 0000000..c9d4440
--- /dev/null
+++ b/src/tint/writer/msl/generator_bench.cc
@@ -0,0 +1,40 @@
+// 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 <string>
+
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::writer::msl {
+namespace {
+
+void GenerateMSL(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadProgram(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& program = std::get<bench::ProgramAndFile>(res).program;
+  for (auto _ : state) {
+    auto res = Generate(&program, {});
+    if (!res.error.empty()) {
+      state.SkipWithError(res.error.c_str());
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(GenerateMSL);
+
+}  // namespace
+}  // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
new file mode 100644
index 0000000..8c7c10f
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -0,0 +1,3023 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/generator_impl.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iomanip>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/disable_validation_attribute.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/sint_literal_expression.h"
+#include "src/tint/ast/uint_literal_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/void.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/bool_type.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/i32_type.h"
+#include "src/tint/sem/matrix_type.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/pointer_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/u32_type.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/sem/vector_type.h"
+#include "src/tint/sem/void_type.h"
+#include "src/tint/transform/array_length_from_uniform.h"
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/external_texture_transform.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/module_scope_var_to_entry_point_param.h"
+#include "src/tint/transform/pad_array_elements.h"
+#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/remove_phonies.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/vectorize_scalar_matrix_constructors.h"
+#include "src/tint/transform/wrap_arrays_in_structs.h"
+#include "src/tint/transform/zero_init_workgroup_memory.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/float_to_string.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
+  return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
+}
+
+class ScopedBitCast {
+ public:
+  ScopedBitCast(GeneratorImpl* generator,
+                std::ostream& stream,
+                const sem::Type* curr_type,
+                const sem::Type* target_type)
+      : s(stream) {
+    auto* target_vec_type = target_type->As<sem::Vector>();
+
+    // If we need to promote from scalar to vector, bitcast the scalar to the
+    // vector element type.
+    if (curr_type->is_scalar() && target_vec_type) {
+      target_type = target_vec_type->type();
+    }
+
+    // Bit cast
+    s << "as_type<";
+    generator->EmitType(s, target_type, "");
+    s << ">(";
+  }
+
+  ~ScopedBitCast() { s << ")"; }
+
+ private:
+  std::ostream& s;
+};
+}  // namespace
+
+SanitizedResult::SanitizedResult() = default;
+SanitizedResult::~SanitizedResult() = default;
+SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
+
+SanitizedResult Sanitize(
+    const Program* in,
+    uint32_t buffer_size_ubo_index,
+    uint32_t fixed_sample_mask,
+    bool emit_vertex_point_size,
+    bool disable_workgroup_init,
+    const ArrayLengthFromUniformOptions& array_length_from_uniform) {
+  transform::Manager manager;
+  transform::DataMap data;
+
+  // Build the config for the internal ArrayLengthFromUniform transform.
+  transform::ArrayLengthFromUniform::Config array_length_from_uniform_cfg(
+      array_length_from_uniform.ubo_binding);
+  if (!array_length_from_uniform.bindpoint_to_size_index.empty()) {
+    // If |array_length_from_uniform| bindings are provided, use that config.
+    array_length_from_uniform_cfg.bindpoint_to_size_index =
+        array_length_from_uniform.bindpoint_to_size_index;
+  } else {
+    // If the binding map is empty, use the deprecated |buffer_size_ubo_index|
+    // and automatically choose indices using the binding numbers.
+    array_length_from_uniform_cfg = transform::ArrayLengthFromUniform::Config(
+        sem::BindingPoint{0, buffer_size_ubo_index});
+    // Use the SSBO binding numbers as the indices for the buffer size lookups.
+    for (auto* var : in->AST().GlobalVariables()) {
+      auto* global = in->Sem().Get<sem::GlobalVariable>(var);
+      if (global && global->StorageClass() == ast::StorageClass::kStorage) {
+        array_length_from_uniform_cfg.bindpoint_to_size_index.emplace(
+            global->BindingPoint(), global->BindingPoint().binding);
+      }
+    }
+  }
+
+  // Build the configs for the internal CanonicalizeEntryPointIO transform.
+  auto entry_point_io_cfg = transform::CanonicalizeEntryPointIO::Config(
+      transform::CanonicalizeEntryPointIO::ShaderStyle::kMsl, fixed_sample_mask,
+      emit_vertex_point_size);
+
+  manager.Add<transform::Unshadow>();
+
+  if (!disable_workgroup_init) {
+    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
+    // ZeroInitWorkgroupMemory may inject new builtin parameters.
+    manager.Add<transform::ZeroInitWorkgroupMemory>();
+  }
+  manager.Add<transform::CanonicalizeEntryPointIO>();
+  manager.Add<transform::ExternalTextureTransform>();
+  manager.Add<transform::PromoteInitializersToConstVar>();
+
+  manager.Add<transform::VectorizeScalarMatrixConstructors>();
+  manager.Add<transform::WrapArraysInStructs>();
+  manager.Add<transform::PadArrayElements>();
+  manager.Add<transform::RemovePhonies>();
+  manager.Add<transform::SimplifyPointers>();
+  // ArrayLengthFromUniform must come after SimplifyPointers, as
+  // it assumes that the form of the array length argument is &var.array.
+  manager.Add<transform::ArrayLengthFromUniform>();
+  manager.Add<transform::ModuleScopeVarToEntryPointParam>();
+  data.Add<transform::ArrayLengthFromUniform::Config>(
+      std::move(array_length_from_uniform_cfg));
+  data.Add<transform::CanonicalizeEntryPointIO::Config>(
+      std::move(entry_point_io_cfg));
+  auto out = manager.Run(in, data);
+
+  SanitizedResult result;
+  result.program = std::move(out.program);
+  if (!result.program.IsValid()) {
+    return result;
+  }
+  if (auto* res = out.data.Get<transform::ArrayLengthFromUniform::Result>()) {
+    result.used_array_length_from_uniform_indices =
+        std::move(res->used_size_indices);
+  }
+  result.needs_storage_buffer_sizes =
+      !result.used_array_length_from_uniform_indices.empty();
+  return result;
+}
+
+GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
+
+GeneratorImpl::~GeneratorImpl() = default;
+
+bool GeneratorImpl::Generate() {
+  line() << "#include <metal_stdlib>";
+  line();
+  line() << "using namespace metal;";
+
+  auto helpers_insertion_point = current_buffer_->lines.size();
+
+  auto* mod = builder_.Sem().Module();
+  for (auto* decl : mod->DependencyOrderedDeclarations()) {
+    bool ok = Switch(
+        decl,  //
+        [&](const ast::Struct* str) {
+          TINT_DEFER(line());
+          return EmitTypeDecl(TypeOf(str));
+        },
+        [&](const ast::Alias*) {
+          return true;  // folded away by the writer
+        },
+        [&](const ast::Variable* var) {
+          if (var->is_const) {
+            TINT_DEFER(line());
+            return EmitProgramConstVariable(var);
+          }
+          // These are pushed into the entry point by sanitizer transforms.
+          TINT_ICE(Writer, diagnostics_)
+              << "module-scope variables should have been handled by the MSL "
+                 "sanitizer";
+          return false;
+        },
+        [&](const ast::Function* func) {
+          TINT_DEFER(line());
+          if (func->IsEntryPoint()) {
+            return EmitEntryPointFunction(func);
+          }
+          return EmitFunction(func);
+        },
+        [&](Default) {
+          // These are pushed into the entry point by sanitizer transforms.
+          TINT_ICE(Writer, diagnostics_)
+              << "unhandled type: " << decl->TypeInfo().name;
+          return false;
+        });
+    if (!ok) {
+      return false;
+    }
+  }
+
+  if (!invariant_define_name_.empty()) {
+    // 'invariant' attribute requires MSL 2.1 or higher.
+    // WGSL can ignore the invariant attribute on pre MSL 2.1 devices.
+    // See: https://github.com/gpuweb/gpuweb/issues/893#issuecomment-745537465
+    line(&helpers_) << "#if __METAL_VERSION__ >= 210";
+    line(&helpers_) << "#define " << invariant_define_name_ << " [[invariant]]";
+    line(&helpers_) << "#else";
+    line(&helpers_) << "#define " << invariant_define_name_;
+    line(&helpers_) << "#endif";
+    line(&helpers_);
+  }
+
+  if (!helpers_.lines.empty()) {
+    current_buffer_->Insert("", helpers_insertion_point++, 0);
+    current_buffer_->Insert(helpers_, helpers_insertion_point++, 0);
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeDecl(const sem::Type* ty) {
+  if (auto* str = ty->As<sem::Struct>()) {
+    if (!EmitStructType(current_buffer_, str)) {
+      return false;
+    }
+  } else {
+    diagnostics_.add_error(diag::System::Writer,
+                           "unknown alias type: " + ty->type_name());
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitIndexAccessor(
+    std::ostream& out,
+    const ast::IndexAccessorExpression* expr) {
+  bool paren_lhs =
+      !expr->object->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
+                             ast::IdentifierExpression,
+                             ast::MemberAccessorExpression>();
+
+  if (paren_lhs) {
+    out << "(";
+  }
+  if (!EmitExpression(out, expr->object)) {
+    return false;
+  }
+  if (paren_lhs) {
+    out << ")";
+  }
+
+  out << "[";
+
+  if (!EmitExpression(out, expr->index)) {
+    return false;
+  }
+  out << "]";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBitcast(std::ostream& out,
+                                const ast::BitcastExpression* expr) {
+  out << "as_type<";
+  if (!EmitType(out, TypeOf(expr)->UnwrapRef(), "")) {
+    return false;
+  }
+
+  out << ">(";
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
+  auto out = line();
+
+  if (!EmitExpression(out, stmt->lhs)) {
+    return false;
+  }
+
+  out << " = ";
+
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBinary(std::ostream& out,
+                               const ast::BinaryExpression* expr) {
+  auto emit_op = [&] {
+    out << " ";
+
+    switch (expr->op) {
+      case ast::BinaryOp::kAnd:
+        out << "&";
+        break;
+      case ast::BinaryOp::kOr:
+        out << "|";
+        break;
+      case ast::BinaryOp::kXor:
+        out << "^";
+        break;
+      case ast::BinaryOp::kLogicalAnd:
+        out << "&&";
+        break;
+      case ast::BinaryOp::kLogicalOr:
+        out << "||";
+        break;
+      case ast::BinaryOp::kEqual:
+        out << "==";
+        break;
+      case ast::BinaryOp::kNotEqual:
+        out << "!=";
+        break;
+      case ast::BinaryOp::kLessThan:
+        out << "<";
+        break;
+      case ast::BinaryOp::kGreaterThan:
+        out << ">";
+        break;
+      case ast::BinaryOp::kLessThanEqual:
+        out << "<=";
+        break;
+      case ast::BinaryOp::kGreaterThanEqual:
+        out << ">=";
+        break;
+      case ast::BinaryOp::kShiftLeft:
+        out << "<<";
+        break;
+      case ast::BinaryOp::kShiftRight:
+        // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
+        // implementation-defined behaviour for negative LHS.  We may have to
+        // generate extra code to implement WGSL-specified behaviour for
+        // negative LHS.
+        out << R"(>>)";
+        break;
+
+      case ast::BinaryOp::kAdd:
+        out << "+";
+        break;
+      case ast::BinaryOp::kSubtract:
+        out << "-";
+        break;
+      case ast::BinaryOp::kMultiply:
+        out << "*";
+        break;
+      case ast::BinaryOp::kDivide:
+        out << "/";
+        break;
+      case ast::BinaryOp::kModulo:
+        out << "%";
+        break;
+      case ast::BinaryOp::kNone:
+        diagnostics_.add_error(diag::System::Writer,
+                               "missing binary operation type");
+        return false;
+    }
+    out << " ";
+    return true;
+  };
+
+  auto signed_type_of = [&](const sem::Type* ty) -> const sem::Type* {
+    if (ty->is_integer_scalar()) {
+      return builder_.create<sem::I32>();
+    } else if (auto* v = ty->As<sem::Vector>()) {
+      return builder_.create<sem::Vector>(builder_.create<sem::I32>(),
+                                          v->Width());
+    }
+    return {};
+  };
+
+  auto unsigned_type_of = [&](const sem::Type* ty) -> const sem::Type* {
+    if (ty->is_integer_scalar()) {
+      return builder_.create<sem::U32>();
+    } else if (auto* v = ty->As<sem::Vector>()) {
+      return builder_.create<sem::Vector>(builder_.create<sem::U32>(),
+                                          v->Width());
+    }
+    return {};
+  };
+
+  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
+  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
+
+  // Handle fmod
+  if (expr->op == ast::BinaryOp::kModulo &&
+      lhs_type->is_float_scalar_or_vector()) {
+    out << "fmod";
+    ScopedParen sp(out);
+    if (!EmitExpression(out, expr->lhs)) {
+      return false;
+    }
+    out << ", ";
+    if (!EmitExpression(out, expr->rhs)) {
+      return false;
+    }
+    return true;
+  }
+
+  // Handle +/-/* of signed values
+  if ((expr->IsAdd() || expr->IsSubtract() || expr->IsMultiply()) &&
+      lhs_type->is_signed_scalar_or_vector() &&
+      rhs_type->is_signed_scalar_or_vector()) {
+    // If lhs or rhs is a vector, use that type (support implicit scalar to
+    // vector promotion)
+    auto* target_type =
+        lhs_type->Is<sem::Vector>()
+            ? lhs_type
+            : (rhs_type->Is<sem::Vector>() ? rhs_type : lhs_type);
+
+    // WGSL defines behaviour for signed overflow, MSL does not. For these
+    // cases, bitcast operands to unsigned, then cast result to signed.
+    ScopedBitCast outer_int_cast(this, out, target_type,
+                                 signed_type_of(target_type));
+    ScopedParen sp(out);
+    {
+      ScopedBitCast lhs_uint_cast(this, out, lhs_type,
+                                  unsigned_type_of(target_type));
+      if (!EmitExpression(out, expr->lhs)) {
+        return false;
+      }
+    }
+    if (!emit_op()) {
+      return false;
+    }
+    {
+      ScopedBitCast rhs_uint_cast(this, out, rhs_type,
+                                  unsigned_type_of(target_type));
+      if (!EmitExpression(out, expr->rhs)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Handle left bit shifting a signed value
+  // TODO(crbug.com/tint/1077): This may not be necessary. The MSL spec
+  // seems to imply that left shifting a signed value is treated the same as
+  // left shifting an unsigned value, but we need to make sure.
+  if (expr->IsShiftLeft() && lhs_type->is_signed_scalar_or_vector()) {
+    // Shift left: discards top bits, so convert first operand to unsigned
+    // first, then convert result back to signed
+    ScopedBitCast outer_int_cast(this, out, lhs_type, signed_type_of(lhs_type));
+    ScopedParen sp(out);
+    {
+      ScopedBitCast lhs_uint_cast(this, out, lhs_type,
+                                  unsigned_type_of(lhs_type));
+      if (!EmitExpression(out, expr->lhs)) {
+        return false;
+      }
+    }
+    if (!emit_op()) {
+      return false;
+    }
+    if (!EmitExpression(out, expr->rhs)) {
+      return false;
+    }
+    return true;
+  }
+
+  // Emit as usual
+  ScopedParen sp(out);
+  if (!EmitExpression(out, expr->lhs)) {
+    return false;
+  }
+  if (!emit_op()) {
+    return false;
+  }
+  if (!EmitExpression(out, expr->rhs)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
+  line() << "break;";
+  return true;
+}
+
+bool GeneratorImpl::EmitCall(std::ostream& out,
+                             const ast::CallExpression* expr) {
+  auto* call = program_->Sem().Get(expr);
+  auto* target = call->Target();
+  return Switch(
+      target,
+      [&](const sem::Function* func) {
+        return EmitFunctionCall(out, call, func);
+      },
+      [&](const sem::Builtin* builtin) {
+        return EmitBuiltinCall(out, call, builtin);
+      },
+      [&](const sem::TypeConversion* conv) {
+        return EmitTypeConversion(out, call, conv);
+      },
+      [&](const sem::TypeConstructor* ctor) {
+        return EmitTypeConstructor(out, call, ctor);
+      },
+      [&](Default) {
+        TINT_ICE(Writer, diagnostics_)
+            << "unhandled call target: " << target->TypeInfo().name;
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
+                                     const sem::Call* call,
+                                     const sem::Function*) {
+  auto* ident = call->Declaration()->target.name;
+  out << program_->Symbols().NameFor(ident->symbol) << "(";
+
+  bool first = true;
+  for (auto* arg : call->Arguments()) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  auto* expr = call->Declaration();
+  if (builtin->IsAtomic()) {
+    return EmitAtomicCall(out, expr, builtin);
+  }
+  if (builtin->IsTexture()) {
+    return EmitTextureCall(out, call, builtin);
+  }
+
+  auto name = generate_builtin_name(builtin);
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kDot:
+      return EmitDotCall(out, expr, builtin);
+    case sem::BuiltinType::kModf:
+      return EmitModfCall(out, expr, builtin);
+    case sem::BuiltinType::kFrexp:
+      return EmitFrexpCall(out, expr, builtin);
+    case sem::BuiltinType::kDegrees:
+      return EmitDegreesCall(out, expr, builtin);
+    case sem::BuiltinType::kRadians:
+      return EmitRadiansCall(out, expr, builtin);
+
+    case sem::BuiltinType::kPack2x16float:
+    case sem::BuiltinType::kUnpack2x16float: {
+      if (builtin->Type() == sem::BuiltinType::kPack2x16float) {
+        out << "as_type<uint>(half2(";
+      } else {
+        out << "float2(as_type<half2>(";
+      }
+      if (!EmitExpression(out, expr->args[0])) {
+        return false;
+      }
+      out << "))";
+      return true;
+    }
+    // TODO(crbug.com/tint/661): Combine sequential barriers to a single
+    // instruction.
+    case sem::BuiltinType::kStorageBarrier: {
+      out << "threadgroup_barrier(mem_flags::mem_device)";
+      return true;
+    }
+    case sem::BuiltinType::kWorkgroupBarrier: {
+      out << "threadgroup_barrier(mem_flags::mem_threadgroup)";
+      return true;
+    }
+
+    case sem::BuiltinType::kLength: {
+      auto* sem = builder_.Sem().Get(expr->args[0]);
+      if (sem->Type()->UnwrapRef()->is_scalar()) {
+        // Emulate scalar overload using fabs(x).
+        name = "fabs";
+      }
+      break;
+    }
+
+    case sem::BuiltinType::kDistance: {
+      auto* sem = builder_.Sem().Get(expr->args[0]);
+      if (sem->Type()->UnwrapRef()->is_scalar()) {
+        // Emulate scalar overload using fabs(x - y);
+        out << "fabs";
+        ScopedParen sp(out);
+        if (!EmitExpression(out, expr->args[0])) {
+          return false;
+        }
+        out << " - ";
+        if (!EmitExpression(out, expr->args[1])) {
+          return false;
+        }
+        return true;
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  if (name.empty()) {
+    return false;
+  }
+
+  out << name << "(";
+
+  bool first = true;
+  for (auto* arg : expr->args) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg)) {
+      return false;
+    }
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
+                                       const sem::Call* call,
+                                       const sem::TypeConversion* conv) {
+  if (!EmitType(out, conv->Target(), "")) {
+    return false;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
+                                        const sem::Call* call,
+                                        const sem::TypeConstructor* ctor) {
+  auto* type = ctor->ReturnType();
+
+  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
+    out << "{";
+  } else {
+    if (!EmitType(out, type, "")) {
+      return false;
+    }
+    out << "(";
+  }
+
+  int i = 0;
+  for (auto* arg : call->Arguments()) {
+    if (i > 0) {
+      out << ", ";
+    }
+
+    if (auto* struct_ty = type->As<sem::Struct>()) {
+      // Emit field designators for structures to account for padding members.
+      auto* member = struct_ty->Members()[i]->Declaration();
+      auto name = program_->Symbols().NameFor(member->symbol);
+      out << "." << name << "=";
+    }
+
+    if (!EmitExpression(out, arg->Declaration())) {
+      return false;
+    }
+
+    i++;
+  }
+
+  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
+    out << "}";
+  } else {
+    out << ")";
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitAtomicCall(std::ostream& out,
+                                   const ast::CallExpression* expr,
+                                   const sem::Builtin* builtin) {
+  auto call = [&](const std::string& name, bool append_memory_order_relaxed) {
+    out << name;
+    {
+      ScopedParen sp(out);
+      for (size_t i = 0; i < expr->args.size(); i++) {
+        auto* arg = expr->args[i];
+        if (i > 0) {
+          out << ", ";
+        }
+        if (!EmitExpression(out, arg)) {
+          return false;
+        }
+      }
+      if (append_memory_order_relaxed) {
+        out << ", memory_order_relaxed";
+      }
+    }
+    return true;
+  };
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAtomicLoad:
+      return call("atomic_load_explicit", true);
+
+    case sem::BuiltinType::kAtomicStore:
+      return call("atomic_store_explicit", true);
+
+    case sem::BuiltinType::kAtomicAdd:
+      return call("atomic_fetch_add_explicit", true);
+
+    case sem::BuiltinType::kAtomicSub:
+      return call("atomic_fetch_sub_explicit", true);
+
+    case sem::BuiltinType::kAtomicMax:
+      return call("atomic_fetch_max_explicit", true);
+
+    case sem::BuiltinType::kAtomicMin:
+      return call("atomic_fetch_min_explicit", true);
+
+    case sem::BuiltinType::kAtomicAnd:
+      return call("atomic_fetch_and_explicit", true);
+
+    case sem::BuiltinType::kAtomicOr:
+      return call("atomic_fetch_or_explicit", true);
+
+    case sem::BuiltinType::kAtomicXor:
+      return call("atomic_fetch_xor_explicit", true);
+
+    case sem::BuiltinType::kAtomicExchange:
+      return call("atomic_exchange_explicit", true);
+
+    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+      auto* ptr_ty = TypeOf(expr->args[0])->UnwrapRef()->As<sem::Pointer>();
+      auto sc = ptr_ty->StorageClass();
+
+      auto func = utils::GetOrCreate(
+          atomicCompareExchangeWeak_, sc, [&]() -> std::string {
+            auto name = UniqueIdentifier("atomicCompareExchangeWeak");
+            auto& buf = helpers_;
+
+            line(&buf) << "template <typename A, typename T>";
+            {
+              auto f = line(&buf);
+              f << "vec<T, 2> " << name << "(";
+              if (!EmitStorageClass(f, sc)) {
+                return "";
+              }
+              f << " A* atomic, T compare, T value) {";
+            }
+
+            buf.IncrementIndent();
+            TINT_DEFER({
+              buf.DecrementIndent();
+              line(&buf) << "}";
+              line(&buf);
+            });
+
+            line(&buf) << "T prev_value = compare;";
+            line(&buf) << "bool matched = "
+                          "atomic_compare_exchange_weak_explicit(atomic, "
+                          "&prev_value, value, memory_order_relaxed, "
+                          "memory_order_relaxed);";
+            line(&buf) << "return {prev_value, matched};";
+            return name;
+          });
+
+      return call(func, false);
+    }
+
+    default:
+      break;
+  }
+
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported atomic builtin: " << builtin->Type();
+  return false;
+}
+
+bool GeneratorImpl::EmitTextureCall(std::ostream& out,
+                                    const sem::Call* call,
+                                    const sem::Builtin* builtin) {
+  using Usage = sem::ParameterUsage;
+
+  auto& signature = builtin->Signature();
+  auto* expr = call->Declaration();
+  auto& arguments = call->Arguments();
+
+  // Returns the argument with the given usage
+  auto arg = [&](Usage usage) {
+    int idx = signature.IndexOf(usage);
+    return (idx >= 0) ? arguments[idx] : nullptr;
+  };
+
+  auto* texture = arg(Usage::kTexture)->Declaration();
+  if (!texture) {
+    TINT_ICE(Writer, diagnostics_) << "missing texture arg";
+    return false;
+  }
+
+  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
+
+  // Helper to emit the texture expression, wrapped in parentheses if the
+  // expression includes an operator with lower precedence than the member
+  // accessor used for the function calls.
+  auto texture_expr = [&]() {
+    bool paren_lhs =
+        !texture->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
+                          ast::IdentifierExpression,
+                          ast::MemberAccessorExpression>();
+    if (paren_lhs) {
+      out << "(";
+    }
+    if (!EmitExpression(out, texture)) {
+      return false;
+    }
+    if (paren_lhs) {
+      out << ")";
+    }
+    return true;
+  };
+
+  // MSL requires that `lod` is a constant 0 for 1D textures.
+  bool level_is_constant_zero =
+      texture_type->dim() == ast::TextureDimension::k1d;
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureDimensions: {
+      std::vector<const char*> dims;
+      switch (texture_type->dim()) {
+        case ast::TextureDimension::kNone:
+          diagnostics_.add_error(diag::System::Writer,
+                                 "texture dimension is kNone");
+          return false;
+        case ast::TextureDimension::k1d:
+          dims = {"width"};
+          break;
+        case ast::TextureDimension::k2d:
+        case ast::TextureDimension::k2dArray:
+        case ast::TextureDimension::kCube:
+        case ast::TextureDimension::kCubeArray:
+          dims = {"width", "height"};
+          break;
+        case ast::TextureDimension::k3d:
+          dims = {"width", "height", "depth"};
+          break;
+      }
+
+      auto get_dim = [&](const char* name) {
+        if (!texture_expr()) {
+          return false;
+        }
+        out << ".get_" << name << "(";
+        if (level_is_constant_zero) {
+          out << "0";
+        } else {
+          if (auto* level = arg(Usage::kLevel)) {
+            if (!EmitExpression(out, level->Declaration())) {
+              return false;
+            }
+          }
+        }
+        out << ")";
+        return true;
+      };
+
+      if (dims.size() == 1) {
+        out << "int(";
+        get_dim(dims[0]);
+        out << ")";
+      } else {
+        EmitType(out, TypeOf(expr)->UnwrapRef(), "");
+        out << "(";
+        for (size_t i = 0; i < dims.size(); i++) {
+          if (i > 0) {
+            out << ", ";
+          }
+          get_dim(dims[i]);
+        }
+        out << ")";
+      }
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumLayers: {
+      out << "int(";
+      if (!texture_expr()) {
+        return false;
+      }
+      out << ".get_array_size())";
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumLevels: {
+      out << "int(";
+      if (!texture_expr()) {
+        return false;
+      }
+      out << ".get_num_mip_levels())";
+      return true;
+    }
+    case sem::BuiltinType::kTextureNumSamples: {
+      out << "int(";
+      if (!texture_expr()) {
+        return false;
+      }
+      out << ".get_num_samples())";
+      return true;
+    }
+    default:
+      break;
+  }
+
+  if (!texture_expr()) {
+    return false;
+  }
+
+  bool lod_param_is_named = true;
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kTextureSample:
+    case sem::BuiltinType::kTextureSampleBias:
+    case sem::BuiltinType::kTextureSampleLevel:
+    case sem::BuiltinType::kTextureSampleGrad:
+      out << ".sample(";
+      break;
+    case sem::BuiltinType::kTextureSampleCompare:
+    case sem::BuiltinType::kTextureSampleCompareLevel:
+      out << ".sample_compare(";
+      break;
+    case sem::BuiltinType::kTextureGather:
+      out << ".gather(";
+      break;
+    case sem::BuiltinType::kTextureGatherCompare:
+      out << ".gather_compare(";
+      break;
+    case sem::BuiltinType::kTextureLoad:
+      out << ".read(";
+      lod_param_is_named = false;
+      break;
+    case sem::BuiltinType::kTextureStore:
+      out << ".write(";
+      break;
+    default:
+      TINT_UNREACHABLE(Writer, diagnostics_)
+          << "Unhandled texture builtin '" << builtin->str() << "'";
+      return false;
+  }
+
+  bool first_arg = true;
+  auto maybe_write_comma = [&] {
+    if (!first_arg) {
+      out << ", ";
+    }
+    first_arg = false;
+  };
+
+  for (auto usage :
+       {Usage::kValue, Usage::kSampler, Usage::kCoords, Usage::kArrayIndex,
+        Usage::kDepthRef, Usage::kSampleIndex}) {
+    if (auto* e = arg(usage)) {
+      maybe_write_comma();
+
+      // Cast the coordinates to unsigned integers if necessary.
+      bool casted = false;
+      if (usage == Usage::kCoords &&
+          e->Type()->UnwrapRef()->is_integer_scalar_or_vector()) {
+        casted = true;
+        switch (texture_type->dim()) {
+          case ast::TextureDimension::k1d:
+            out << "uint(";
+            break;
+          case ast::TextureDimension::k2d:
+          case ast::TextureDimension::k2dArray:
+            out << "uint2(";
+            break;
+          case ast::TextureDimension::k3d:
+            out << "uint3(";
+            break;
+          default:
+            TINT_ICE(Writer, diagnostics_)
+                << "unhandled texture dimensionality";
+            break;
+        }
+      }
+
+      if (!EmitExpression(out, e->Declaration()))
+        return false;
+
+      if (casted) {
+        out << ")";
+      }
+    }
+  }
+
+  if (auto* bias = arg(Usage::kBias)) {
+    maybe_write_comma();
+    out << "bias(";
+    if (!EmitExpression(out, bias->Declaration())) {
+      return false;
+    }
+    out << ")";
+  }
+  if (auto* level = arg(Usage::kLevel)) {
+    maybe_write_comma();
+    if (lod_param_is_named) {
+      out << "level(";
+    }
+    if (level_is_constant_zero) {
+      out << "0";
+    } else {
+      if (!EmitExpression(out, level->Declaration())) {
+        return false;
+      }
+    }
+    if (lod_param_is_named) {
+      out << ")";
+    }
+  }
+  if (builtin->Type() == sem::BuiltinType::kTextureSampleCompareLevel) {
+    maybe_write_comma();
+    out << "level(0)";
+  }
+  if (auto* ddx = arg(Usage::kDdx)) {
+    auto dim = texture_type->dim();
+    switch (dim) {
+      case ast::TextureDimension::k2d:
+      case ast::TextureDimension::k2dArray:
+        maybe_write_comma();
+        out << "gradient2d(";
+        break;
+      case ast::TextureDimension::k3d:
+        maybe_write_comma();
+        out << "gradient3d(";
+        break;
+      case ast::TextureDimension::kCube:
+      case ast::TextureDimension::kCubeArray:
+        maybe_write_comma();
+        out << "gradientcube(";
+        break;
+      default: {
+        std::stringstream err;
+        err << "MSL does not support gradients for " << dim << " textures";
+        diagnostics_.add_error(diag::System::Writer, err.str());
+        return false;
+      }
+    }
+    if (!EmitExpression(out, ddx->Declaration())) {
+      return false;
+    }
+    out << ", ";
+    if (!EmitExpression(out, arg(Usage::kDdy)->Declaration())) {
+      return false;
+    }
+    out << ")";
+  }
+
+  bool has_offset = false;
+  if (auto* offset = arg(Usage::kOffset)) {
+    has_offset = true;
+    maybe_write_comma();
+    if (!EmitExpression(out, offset->Declaration())) {
+      return false;
+    }
+  }
+
+  if (auto* component = arg(Usage::kComponent)) {
+    maybe_write_comma();
+    if (!has_offset) {
+      // offset argument may need to be provided if we have a component.
+      switch (texture_type->dim()) {
+        case ast::TextureDimension::k2d:
+        case ast::TextureDimension::k2dArray:
+          out << "int2(0), ";
+          break;
+        default:
+          break;  // Other texture dimensions don't have an offset
+      }
+    }
+    auto c = component->ConstantValue().Elements()[0].i32;
+    switch (c) {
+      case 0:
+        out << "component::x";
+        break;
+      case 1:
+        out << "component::y";
+        break;
+      case 2:
+        out << "component::z";
+        break;
+      case 3:
+        out << "component::w";
+        break;
+      default:
+        TINT_ICE(Writer, diagnostics_)
+            << "invalid textureGather component: " << c;
+        break;
+    }
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDotCall(std::ostream& out,
+                                const ast::CallExpression* expr,
+                                const sem::Builtin* builtin) {
+  auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
+  std::string fn = "dot";
+  if (vec_ty->type()->is_integer_scalar()) {
+    // MSL does not have a builtin for dot() with integer vector types.
+    // Generate the helper function if it hasn't been created already
+    fn = utils::GetOrCreate(
+        int_dot_funcs_, vec_ty->Width(), [&]() -> std::string {
+          TextBuffer b;
+          TINT_DEFER(helpers_.Append(b));
+
+          auto fn_name =
+              UniqueIdentifier("tint_dot" + std::to_string(vec_ty->Width()));
+          auto v = "vec<T," + std::to_string(vec_ty->Width()) + ">";
+
+          line(&b) << "template<typename T>";
+          line(&b) << "T " << fn_name << "(" << v << " a, " << v << " b) {";
+          {
+            auto l = line(&b);
+            l << "  return ";
+            for (uint32_t i = 0; i < vec_ty->Width(); i++) {
+              if (i > 0) {
+                l << " + ";
+              }
+              l << "a[" << i << "]*b[" << i << "]";
+            }
+            l << ";";
+          }
+          line(&b) << "}";
+          return fn_name;
+        });
+  }
+
+  out << fn << "(";
+  if (!EmitExpression(out, expr->args[0])) {
+    return false;
+  }
+  out << ", ";
+  if (!EmitExpression(out, expr->args[1])) {
+    return false;
+  }
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitModfCall(std::ostream& out,
+                                 const ast::CallExpression* expr,
+                                 const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* ty = builtin->Parameters()[0]->Type();
+        auto in = params[0];
+
+        std::string width;
+        if (auto* vec = ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        // Emit the builtin return type unique to this overload. This does not
+        // exist in the AST, so it will not be generated in Generate().
+        if (!EmitStructType(&helpers_,
+                            builtin->ReturnType()->As<sem::Struct>())) {
+          return false;
+        }
+
+        line(b) << "float" << width << " whole;";
+        line(b) << "float" << width << " fract = modf(" << in << ", whole);";
+        line(b) << "return {fract, whole};";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
+                                  const ast::CallExpression* expr,
+                                  const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        auto* ty = builtin->Parameters()[0]->Type();
+        auto in = params[0];
+
+        std::string width;
+        if (auto* vec = ty->As<sem::Vector>()) {
+          width = std::to_string(vec->Width());
+        }
+
+        // Emit the builtin return type unique to this overload. This does not
+        // exist in the AST, so it will not be generated in Generate().
+        if (!EmitStructType(&helpers_,
+                            builtin->ReturnType()->As<sem::Struct>())) {
+          return false;
+        }
+
+        line(b) << "int" << width << " exp;";
+        line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
+        line(b) << "return {sig, exp};";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kRadToDeg << ";";
+        return true;
+      });
+}
+
+bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
+                                    const ast::CallExpression* expr,
+                                    const sem::Builtin* builtin) {
+  return CallBuiltinHelper(
+      out, expr, builtin,
+      [&](TextBuffer* b, const std::vector<std::string>& params) {
+        line(b) << "return " << params[0] << " * " << std::setprecision(20)
+                << sem::kDegToRad << ";";
+        return true;
+      });
+}
+
+std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
+  std::string out = "";
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAcos:
+    case sem::BuiltinType::kAll:
+    case sem::BuiltinType::kAny:
+    case sem::BuiltinType::kAsin:
+    case sem::BuiltinType::kAtan:
+    case sem::BuiltinType::kAtan2:
+    case sem::BuiltinType::kCeil:
+    case sem::BuiltinType::kCos:
+    case sem::BuiltinType::kCosh:
+    case sem::BuiltinType::kCross:
+    case sem::BuiltinType::kDeterminant:
+    case sem::BuiltinType::kDistance:
+    case sem::BuiltinType::kDot:
+    case sem::BuiltinType::kExp:
+    case sem::BuiltinType::kExp2:
+    case sem::BuiltinType::kFloor:
+    case sem::BuiltinType::kFma:
+    case sem::BuiltinType::kFract:
+    case sem::BuiltinType::kFrexp:
+    case sem::BuiltinType::kLength:
+    case sem::BuiltinType::kLdexp:
+    case sem::BuiltinType::kLog:
+    case sem::BuiltinType::kLog2:
+    case sem::BuiltinType::kMix:
+    case sem::BuiltinType::kModf:
+    case sem::BuiltinType::kNormalize:
+    case sem::BuiltinType::kPow:
+    case sem::BuiltinType::kReflect:
+    case sem::BuiltinType::kRefract:
+    case sem::BuiltinType::kSelect:
+    case sem::BuiltinType::kSin:
+    case sem::BuiltinType::kSinh:
+    case sem::BuiltinType::kSqrt:
+    case sem::BuiltinType::kStep:
+    case sem::BuiltinType::kTan:
+    case sem::BuiltinType::kTanh:
+    case sem::BuiltinType::kTranspose:
+    case sem::BuiltinType::kTrunc:
+    case sem::BuiltinType::kSign:
+    case sem::BuiltinType::kClamp:
+      out += builtin->str();
+      break;
+    case sem::BuiltinType::kAbs:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        out += "fabs";
+      } else {
+        out += "abs";
+      }
+      break;
+    case sem::BuiltinType::kCountOneBits:
+      out += "popcount";
+      break;
+    case sem::BuiltinType::kDpdx:
+    case sem::BuiltinType::kDpdxCoarse:
+    case sem::BuiltinType::kDpdxFine:
+      out += "dfdx";
+      break;
+    case sem::BuiltinType::kDpdy:
+    case sem::BuiltinType::kDpdyCoarse:
+    case sem::BuiltinType::kDpdyFine:
+      out += "dfdy";
+      break;
+    case sem::BuiltinType::kFwidth:
+    case sem::BuiltinType::kFwidthCoarse:
+    case sem::BuiltinType::kFwidthFine:
+      out += "fwidth";
+      break;
+    case sem::BuiltinType::kIsFinite:
+      out += "isfinite";
+      break;
+    case sem::BuiltinType::kIsInf:
+      out += "isinf";
+      break;
+    case sem::BuiltinType::kIsNan:
+      out += "isnan";
+      break;
+    case sem::BuiltinType::kIsNormal:
+      out += "isnormal";
+      break;
+    case sem::BuiltinType::kMax:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        out += "fmax";
+      } else {
+        out += "max";
+      }
+      break;
+    case sem::BuiltinType::kMin:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        out += "fmin";
+      } else {
+        out += "min";
+      }
+      break;
+    case sem::BuiltinType::kFaceForward:
+      out += "faceforward";
+      break;
+    case sem::BuiltinType::kPack4x8snorm:
+      out += "pack_float_to_snorm4x8";
+      break;
+    case sem::BuiltinType::kPack4x8unorm:
+      out += "pack_float_to_unorm4x8";
+      break;
+    case sem::BuiltinType::kPack2x16snorm:
+      out += "pack_float_to_snorm2x16";
+      break;
+    case sem::BuiltinType::kPack2x16unorm:
+      out += "pack_float_to_unorm2x16";
+      break;
+    case sem::BuiltinType::kReverseBits:
+      out += "reverse_bits";
+      break;
+    case sem::BuiltinType::kRound:
+      out += "rint";
+      break;
+    case sem::BuiltinType::kSmoothStep:
+      out += "smoothstep";
+      break;
+    case sem::BuiltinType::kInverseSqrt:
+      out += "rsqrt";
+      break;
+    case sem::BuiltinType::kUnpack4x8snorm:
+      out += "unpack_snorm4x8_to_float";
+      break;
+    case sem::BuiltinType::kUnpack4x8unorm:
+      out += "unpack_unorm4x8_to_float";
+      break;
+    case sem::BuiltinType::kUnpack2x16snorm:
+      out += "unpack_snorm2x16_to_float";
+      break;
+    case sem::BuiltinType::kUnpack2x16unorm:
+      out += "unpack_unorm2x16_to_float";
+      break;
+    case sem::BuiltinType::kArrayLength:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Unable to translate builtin: " + std::string(builtin->str()) +
+              "\nDid you forget to pass array_length_from_uniform generator "
+              "options?");
+      return "";
+    default:
+      diagnostics_.add_error(
+          diag::System::Writer,
+          "Unknown import method: " + std::string(builtin->str()));
+      return "";
+  }
+  return out;
+}
+
+bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
+  if (stmt->IsDefault()) {
+    line() << "default: {";
+  } else {
+    for (auto* selector : stmt->selectors) {
+      auto out = line();
+      out << "case ";
+      if (!EmitLiteral(out, selector)) {
+        return false;
+      }
+      out << ":";
+      if (selector == stmt->selectors.back()) {
+        out << " {";
+      }
+    }
+  }
+
+  {
+    ScopedIndent si(this);
+
+    for (auto* s : stmt->body->statements) {
+      if (!EmitStatement(s)) {
+        return false;
+      }
+    }
+
+    if (!last_is_break_or_fallthrough(stmt->body)) {
+      line() << "break;";
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
+  if (!emit_continuing_()) {
+    return false;
+  }
+
+  line() << "continue;";
+  return true;
+}
+
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+  return Switch(
+      type,
+      [&](const sem::Bool*) {
+        out << "false";
+        return true;
+      },
+      [&](const sem::F32*) {
+        out << "0.0f";
+        return true;
+      },
+      [&](const sem::I32*) {
+        out << "0";
+        return true;
+      },
+      [&](const sem::U32*) {
+        out << "0u";
+        return true;
+      },
+      [&](const sem::Vector* vec) {  //
+        return EmitZeroValue(out, vec->type());
+      },
+      [&](const sem::Matrix* mat) {
+        if (!EmitType(out, mat, "")) {
+          return false;
+        }
+        out << "(";
+        TINT_DEFER(out << ")");
+        return EmitZeroValue(out, mat->type());
+      },
+      [&](const sem::Array* arr) {
+        out << "{";
+        TINT_DEFER(out << "}");
+        return EmitZeroValue(out, arr->ElemType());
+      },
+      [&](const sem::Struct*) {
+        out << "{}";
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "Invalid type for zero emission: " + type->type_name());
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitLiteral(std::ostream& out,
+                                const ast::LiteralExpression* lit) {
+  return Switch(
+      lit,
+      [&](const ast::BoolLiteralExpression* l) {
+        out << (l->value ? "true" : "false");
+        return true;
+      },
+      [&](const ast::FloatLiteralExpression* l) {
+        if (std::isinf(l->value)) {
+          out << (l->value >= 0 ? "INFINITY" : "-INFINITY");
+        } else if (std::isnan(l->value)) {
+          out << "NAN";
+        } else {
+          out << FloatToString(l->value) << "f";
+        }
+        return true;
+      },
+      [&](const ast::SintLiteralExpression* l) {
+        // MSL (and C++) parse `-2147483648` as a `long` because it parses
+        // unary minus and `2147483648` as separate tokens, and the latter
+        // doesn't fit into an (32-bit) `int`. WGSL, OTOH, parses this as an
+        // `i32`. To avoid issues with `long` to `int` casts, emit
+        // `(2147483647 - 1)` instead, which ensures the expression type is
+        // `int`.
+        const auto int_min = std::numeric_limits<int32_t>::min();
+        if (l->ValueAsI32() == int_min) {
+          out << "(" << int_min + 1 << " - 1)";
+        } else {
+          out << l->value;
+        }
+        return true;
+      },
+      [&](const ast::UintLiteralExpression* l) {
+        out << l->value << "u";
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitExpression(std::ostream& out,
+                                   const ast::Expression* expr) {
+  return Switch(
+      expr,
+      [&](const ast::IndexAccessorExpression* a) {  //
+        return EmitIndexAccessor(out, a);
+      },
+      [&](const ast::BinaryExpression* b) {  //
+        return EmitBinary(out, b);
+      },
+      [&](const ast::BitcastExpression* b) {  //
+        return EmitBitcast(out, b);
+      },
+      [&](const ast::CallExpression* c) {  //
+        return EmitCall(out, c);
+      },
+      [&](const ast::IdentifierExpression* i) {  //
+        return EmitIdentifier(out, i);
+      },
+      [&](const ast::LiteralExpression* l) {  //
+        return EmitLiteral(out, l);
+      },
+      [&](const ast::MemberAccessorExpression* m) {  //
+        return EmitMemberAccessor(out, m);
+      },
+      [&](const ast::UnaryOpExpression* u) {  //
+        return EmitUnaryOp(out, u);
+      },
+      [&](Default) {  //
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown expression type: " + std::string(expr->TypeInfo().name));
+        return false;
+      });
+}
+
+void GeneratorImpl::EmitStage(std::ostream& out, ast::PipelineStage stage) {
+  switch (stage) {
+    case ast::PipelineStage::kFragment:
+      out << "fragment";
+      break;
+    case ast::PipelineStage::kVertex:
+      out << "vertex";
+      break;
+    case ast::PipelineStage::kCompute:
+      out << "kernel";
+      break;
+    case ast::PipelineStage::kNone:
+      break;
+  }
+  return;
+}
+
+bool GeneratorImpl::EmitFunction(const ast::Function* func) {
+  auto* func_sem = program_->Sem().Get(func);
+
+  {
+    auto out = line();
+    if (!EmitType(out, func_sem->ReturnType(), "")) {
+      return false;
+    }
+    out << " " << program_->Symbols().NameFor(func->symbol) << "(";
+
+    bool first = true;
+    for (auto* v : func->params) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      auto* type = program_->Sem().Get(v)->Type();
+
+      std::string param_name =
+          "const " + program_->Symbols().NameFor(v->symbol);
+      if (!EmitType(out, type, param_name)) {
+        return false;
+      }
+      // Parameter name is output as part of the type for arrays and pointers.
+      if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
+        out << " " << program_->Symbols().NameFor(v->symbol);
+      }
+    }
+
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(func->body->statements)) {
+    return false;
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
+  switch (builtin) {
+    case ast::Builtin::kPosition:
+      return "position";
+    case ast::Builtin::kVertexIndex:
+      return "vertex_id";
+    case ast::Builtin::kInstanceIndex:
+      return "instance_id";
+    case ast::Builtin::kFrontFacing:
+      return "front_facing";
+    case ast::Builtin::kFragDepth:
+      return "depth(any)";
+    case ast::Builtin::kLocalInvocationId:
+      return "thread_position_in_threadgroup";
+    case ast::Builtin::kLocalInvocationIndex:
+      return "thread_index_in_threadgroup";
+    case ast::Builtin::kGlobalInvocationId:
+      return "thread_position_in_grid";
+    case ast::Builtin::kWorkgroupId:
+      return "threadgroup_position_in_grid";
+    case ast::Builtin::kNumWorkgroups:
+      return "threadgroups_per_grid";
+    case ast::Builtin::kSampleIndex:
+      return "sample_id";
+    case ast::Builtin::kSampleMask:
+      return "sample_mask";
+    case ast::Builtin::kPointSize:
+      return "point_size";
+    default:
+      break;
+  }
+  return "";
+}
+
+std::string GeneratorImpl::interpolation_to_attribute(
+    ast::InterpolationType type,
+    ast::InterpolationSampling sampling) const {
+  std::string attr;
+  switch (sampling) {
+    case ast::InterpolationSampling::kCenter:
+      attr = "center_";
+      break;
+    case ast::InterpolationSampling::kCentroid:
+      attr = "centroid_";
+      break;
+    case ast::InterpolationSampling::kSample:
+      attr = "sample_";
+      break;
+    case ast::InterpolationSampling::kNone:
+      break;
+  }
+  switch (type) {
+    case ast::InterpolationType::kPerspective:
+      attr += "perspective";
+      break;
+    case ast::InterpolationType::kLinear:
+      attr += "no_perspective";
+      break;
+    case ast::InterpolationType::kFlat:
+      attr += "flat";
+      break;
+  }
+  return attr;
+}
+
+bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
+  auto func_name = program_->Symbols().NameFor(func->symbol);
+
+  // Returns the binding index of a variable, requiring that the group
+  // attribute have a value of zero.
+  const uint32_t kInvalidBindingIndex = std::numeric_limits<uint32_t>::max();
+  auto get_binding_index = [&](const ast::Variable* var) -> uint32_t {
+    auto bp = var->BindingPoint();
+    if (bp.group == nullptr || bp.binding == nullptr) {
+      TINT_ICE(Writer, diagnostics_)
+          << "missing binding attributes for entry point parameter";
+      return kInvalidBindingIndex;
+    }
+    if (bp.group->value != 0) {
+      TINT_ICE(Writer, diagnostics_)
+          << "encountered non-zero resource group index (use "
+             "BindingRemapper to fix)";
+      return kInvalidBindingIndex;
+    }
+    return bp.binding->value;
+  };
+
+  {
+    auto out = line();
+
+    EmitStage(out, func->PipelineStage());
+    out << " " << func->return_type->FriendlyName(program_->Symbols());
+    out << " " << func_name << "(";
+
+    // Emit entry point parameters.
+    bool first = true;
+    for (auto* var : func->params) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      auto* type = program_->Sem().Get(var)->Type()->UnwrapRef();
+
+      auto param_name = program_->Symbols().NameFor(var->symbol);
+      if (!EmitType(out, type, param_name)) {
+        return false;
+      }
+      // Parameter name is output as part of the type for arrays and pointers.
+      if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
+        out << " " << param_name;
+      }
+
+      if (type->Is<sem::Struct>()) {
+        out << " [[stage_in]]";
+      } else if (type->is_handle()) {
+        uint32_t binding = get_binding_index(var);
+        if (binding == kInvalidBindingIndex) {
+          return false;
+        }
+        if (var->type->Is<ast::Sampler>()) {
+          out << " [[sampler(" << binding << ")]]";
+        } else if (var->type->Is<ast::Texture>()) {
+          out << " [[texture(" << binding << ")]]";
+        } else {
+          TINT_ICE(Writer, diagnostics_)
+              << "invalid handle type entry point parameter";
+          return false;
+        }
+      } else if (auto* ptr = var->type->As<ast::Pointer>()) {
+        auto sc = ptr->storage_class;
+        if (sc == ast::StorageClass::kWorkgroup) {
+          auto& allocations = workgroup_allocations_[func_name];
+          out << " [[threadgroup(" << allocations.size() << ")]]";
+          allocations.push_back(program_->Sem().Get(ptr->type)->Size());
+        } else if (sc == ast::StorageClass::kStorage ||
+                   sc == ast::StorageClass::kUniform) {
+          uint32_t binding = get_binding_index(var);
+          if (binding == kInvalidBindingIndex) {
+            return false;
+          }
+          out << " [[buffer(" << binding << ")]]";
+        } else {
+          TINT_ICE(Writer, diagnostics_)
+              << "invalid pointer storage class for entry point parameter";
+          return false;
+        }
+      } else {
+        auto& attrs = var->attributes;
+        bool builtin_found = false;
+        for (auto* attr : attrs) {
+          auto* builtin = attr->As<ast::BuiltinAttribute>();
+          if (!builtin) {
+            continue;
+          }
+
+          builtin_found = true;
+
+          auto name = builtin_to_attribute(builtin->builtin);
+          if (name.empty()) {
+            diagnostics_.add_error(diag::System::Writer, "unknown builtin");
+            return false;
+          }
+          out << " [[" << name << "]]";
+        }
+        if (!builtin_found) {
+          TINT_ICE(Writer, diagnostics_) << "Unsupported entry point parameter";
+        }
+      }
+    }
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+
+    if (!EmitStatements(func->body->statements)) {
+      return false;
+    }
+
+    if (!Is<ast::ReturnStatement>(func->body->Last())) {
+      ast::ReturnStatement ret(ProgramID{}, Source{});
+      if (!EmitStatement(&ret)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
+                                   const ast::IdentifierExpression* expr) {
+  out << program_->Symbols().NameFor(expr->symbol);
+  return true;
+}
+
+bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
+  auto emit_continuing = [this, stmt]() {
+    if (stmt->continuing && !stmt->continuing->Empty()) {
+      if (!EmitBlock(stmt->continuing)) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+  line() << "while (true) {";
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+    if (!emit_continuing_()) {
+      return false;
+    }
+  }
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+  TextBuffer init_buf;
+  if (auto* init = stmt->initializer) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
+    if (!EmitStatement(init)) {
+      return false;
+    }
+  }
+
+  TextBuffer cond_pre;
+  std::stringstream cond_buf;
+  if (auto* cond = stmt->condition) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
+    if (!EmitExpression(cond_buf, cond)) {
+      return false;
+    }
+  }
+
+  TextBuffer cont_buf;
+  if (auto* cont = stmt->continuing) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
+    if (!EmitStatement(cont)) {
+      return false;
+    }
+  }
+
+  // If the for-loop has a multi-statement conditional and / or continuing,
+  // then we cannot emit this as a regular for-loop in MSL. Instead we need to
+  // generate a `while(true)` loop.
+  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
+
+  // If the for-loop has multi-statement initializer, or is going to be
+  // emitted as a `while(true)` loop, then declare the initializer
+  // statement(s) before the loop in a new block.
+  bool nest_in_block =
+      init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop);
+  if (nest_in_block) {
+    line() << "{";
+    increment_indent();
+    current_buffer_->Append(init_buf);
+    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
+  }
+  TINT_DEFER({
+    if (nest_in_block) {
+      decrement_indent();
+      line() << "}";
+    }
+  });
+
+  if (emit_as_loop) {
+    auto emit_continuing = [&]() {
+      current_buffer_->Append(cont_buf);
+      return true;
+    };
+
+    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+    line() << "while (true) {";
+    increment_indent();
+    TINT_DEFER({
+      decrement_indent();
+      line() << "}";
+    });
+
+    if (stmt->condition) {
+      current_buffer_->Append(cond_pre);
+      line() << "if (!(" << cond_buf.str() << ")) { break; }";
+    }
+
+    if (!EmitStatements(stmt->body->statements)) {
+      return false;
+    }
+
+    if (!emit_continuing_()) {
+      return false;
+    }
+  } else {
+    // For-loop can be generated.
+    {
+      auto out = line();
+      out << "for";
+      {
+        ScopedParen sp(out);
+
+        if (!init_buf.lines.empty()) {
+          out << init_buf.lines[0].content << " ";
+        } else {
+          out << "; ";
+        }
+
+        out << cond_buf.str() << "; ";
+
+        if (!cont_buf.lines.empty()) {
+          out << TrimSuffix(cont_buf.lines[0].content, ";");
+        }
+      }
+      out << " {";
+    }
+    {
+      auto emit_continuing = [] { return true; };
+      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
+      if (!EmitStatementsWithIndent(stmt->body->statements)) {
+        return false;
+      }
+    }
+    line() << "}";
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
+  // TODO(dsinclair): Verify this is correct when the discard semantics are
+  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
+  line() << "discard_fragment();";
+  return true;
+}
+
+bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
+  {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      line() << "} else {";
+      increment_indent();
+
+      {
+        auto out = line();
+        out << "if (";
+        if (!EmitExpression(out, e->condition)) {
+          return false;
+        }
+        out << ") {";
+      }
+    } else {
+      line() << "} else {";
+    }
+
+    if (!EmitStatementsWithIndent(e->body->statements)) {
+      return false;
+    }
+  }
+
+  line() << "}";
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      decrement_indent();
+      line() << "}";
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitMemberAccessor(
+    std::ostream& out,
+    const ast::MemberAccessorExpression* expr) {
+  auto write_lhs = [&] {
+    bool paren_lhs = !expr->structure->IsAnyOf<
+        ast::IndexAccessorExpression, ast::CallExpression,
+        ast::IdentifierExpression, ast::MemberAccessorExpression>();
+    if (paren_lhs) {
+      out << "(";
+    }
+    if (!EmitExpression(out, expr->structure)) {
+      return false;
+    }
+    if (paren_lhs) {
+      out << ")";
+    }
+    return true;
+  };
+
+  auto& sem = program_->Sem();
+
+  if (auto* swizzle = sem.Get(expr)->As<sem::Swizzle>()) {
+    // Metal 1.x does not support swizzling of packed vector types.
+    // For single element swizzles, we can use the index operator.
+    // For multi-element swizzles, we need to cast to a regular vector type
+    // first. Note that we do not currently allow assignments to swizzles, so
+    // the casting which will convert the l-value to r-value is fine.
+    if (swizzle->Indices().size() == 1) {
+      if (!write_lhs()) {
+        return false;
+      }
+      out << "[" << swizzle->Indices()[0] << "]";
+    } else {
+      if (!EmitType(out, sem.Get(expr->structure)->Type()->UnwrapRef(), "")) {
+        return false;
+      }
+      out << "(";
+      if (!write_lhs()) {
+        return false;
+      }
+      out << ")." << program_->Symbols().NameFor(expr->member->symbol);
+    }
+  } else {
+    if (!write_lhs()) {
+      return false;
+    }
+    out << ".";
+    if (!EmitExpression(out, expr->member)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
+  auto out = line();
+  out << "return";
+  if (stmt->value) {
+    out << " ";
+    if (!EmitExpression(out, stmt->value)) {
+      return false;
+    }
+  }
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+  line() << "{";
+
+  if (!EmitStatementsWithIndent(stmt->statements)) {
+    return false;
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
+  return Switch(
+      stmt,
+      [&](const ast::AssignmentStatement* a) {  //
+        return EmitAssign(a);
+      },
+      [&](const ast::BlockStatement* b) {  //
+        return EmitBlock(b);
+      },
+      [&](const ast::BreakStatement* b) {  //
+        return EmitBreak(b);
+      },
+      [&](const ast::CallStatement* c) {  //
+        auto out = line();
+        if (!EmitCall(out, c->expr)) {  //
+          return false;
+        }
+        out << ";";
+        return true;
+      },
+      [&](const ast::ContinueStatement* c) {  //
+        return EmitContinue(c);
+      },
+      [&](const ast::DiscardStatement* d) {  //
+        return EmitDiscard(d);
+      },
+      [&](const ast::FallthroughStatement*) {  //
+        line() << "/* fallthrough */";
+        return true;
+      },
+      [&](const ast::IfStatement* i) {  //
+        return EmitIf(i);
+      },
+      [&](const ast::LoopStatement* l) {  //
+        return EmitLoop(l);
+      },
+      [&](const ast::ForLoopStatement* l) {  //
+        return EmitForLoop(l);
+      },
+      [&](const ast::ReturnStatement* r) {  //
+        return EmitReturn(r);
+      },
+      [&](const ast::SwitchStatement* s) {  //
+        return EmitSwitch(s);
+      },
+      [&](const ast::VariableDeclStatement* v) {  //
+        auto* var = program_->Sem().Get(v->variable);
+        return EmitVariable(var);
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown statement type: " + std::string(stmt->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+  for (auto* s : stmts) {
+    if (!EmitStatement(s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+  ScopedIndent si(this);
+  return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
+  {
+    auto out = line();
+    out << "switch(";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+    for (auto* s : stmt->body) {
+      if (!EmitCase(s)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitType(std::ostream& out,
+                             const sem::Type* type,
+                             const std::string& name,
+                             bool* name_printed /* = nullptr */) {
+  if (name_printed) {
+    *name_printed = false;
+  }
+
+  return Switch(
+      type,
+      [&](const sem::Atomic* atomic) {
+        if (atomic->Type()->Is<sem::I32>()) {
+          out << "atomic_int";
+          return true;
+        }
+        if (atomic->Type()->Is<sem::U32>()) {
+          out << "atomic_uint";
+          return true;
+        }
+        TINT_ICE(Writer, diagnostics_)
+            << "unhandled atomic type " << atomic->Type()->type_name();
+        return false;
+      },
+      [&](const sem::Array* ary) {
+        const sem::Type* base_type = ary;
+        std::vector<uint32_t> sizes;
+        while (auto* arr = base_type->As<sem::Array>()) {
+          if (arr->IsRuntimeSized()) {
+            sizes.push_back(1);
+          } else {
+            sizes.push_back(arr->Count());
+          }
+          base_type = arr->ElemType();
+        }
+        if (!EmitType(out, base_type, "")) {
+          return false;
+        }
+        if (!name.empty()) {
+          out << " " << name;
+          if (name_printed) {
+            *name_printed = true;
+          }
+        }
+        for (uint32_t size : sizes) {
+          out << "[" << size << "]";
+        }
+        return true;
+      },
+      [&](const sem::Bool*) {
+        out << "bool";
+        return true;
+      },
+      [&](const sem::F32*) {
+        out << "float";
+        return true;
+      },
+      [&](const sem::I32*) {
+        out << "int";
+        return true;
+      },
+      [&](const sem::Matrix* mat) {
+        if (!EmitType(out, mat->type(), "")) {
+          return false;
+        }
+        out << mat->columns() << "x" << mat->rows();
+        return true;
+      },
+      [&](const sem::Pointer* ptr) {
+        if (ptr->Access() == ast::Access::kRead) {
+          out << "const ";
+        }
+        if (!EmitStorageClass(out, ptr->StorageClass())) {
+          return false;
+        }
+        out << " ";
+        if (ptr->StoreType()->Is<sem::Array>()) {
+          std::string inner = "(*" + name + ")";
+          if (!EmitType(out, ptr->StoreType(), inner)) {
+            return false;
+          }
+          if (name_printed) {
+            *name_printed = true;
+          }
+        } else {
+          if (!EmitType(out, ptr->StoreType(), "")) {
+            return false;
+          }
+          out << "* " << name;
+          if (name_printed) {
+            *name_printed = true;
+          }
+        }
+        return true;
+      },
+      [&](const sem::Sampler*) {
+        out << "sampler";
+        return true;
+      },
+      [&](const sem::Struct* str) {
+        // The struct type emits as just the name. The declaration would be
+        // emitted as part of emitting the declared types.
+        out << StructName(str);
+        return true;
+      },
+      [&](const sem::Texture* tex) {
+        if (tex->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
+          out << "depth";
+        } else {
+          out << "texture";
+        }
+
+        switch (tex->dim()) {
+          case ast::TextureDimension::k1d:
+            out << "1d";
+            break;
+          case ast::TextureDimension::k2d:
+            out << "2d";
+            break;
+          case ast::TextureDimension::k2dArray:
+            out << "2d_array";
+            break;
+          case ast::TextureDimension::k3d:
+            out << "3d";
+            break;
+          case ast::TextureDimension::kCube:
+            out << "cube";
+            break;
+          case ast::TextureDimension::kCubeArray:
+            out << "cube_array";
+            break;
+          default:
+            diagnostics_.add_error(diag::System::Writer,
+                                   "Invalid texture dimensions");
+            return false;
+        }
+        if (tex->IsAnyOf<sem::MultisampledTexture,
+                         sem::DepthMultisampledTexture>()) {
+          out << "_ms";
+        }
+        out << "<";
+        TINT_DEFER(out << ">");
+
+        return Switch(
+            tex,
+            [&](const sem::DepthTexture*) {
+              out << "float, access::sample";
+              return true;
+            },
+            [&](const sem::DepthMultisampledTexture*) {
+              out << "float, access::read";
+              return true;
+            },
+            [&](const sem::StorageTexture* storage) {
+              if (!EmitType(out, storage->type(), "")) {
+                return false;
+              }
+
+              std::string access_str;
+              if (storage->access() == ast::Access::kRead) {
+                out << ", access::read";
+              } else if (storage->access() == ast::Access::kWrite) {
+                out << ", access::write";
+              } else {
+                diagnostics_.add_error(
+                    diag::System::Writer,
+                    "Invalid access control for storage texture");
+                return false;
+              }
+              return true;
+            },
+            [&](const sem::MultisampledTexture* ms) {
+              if (!EmitType(out, ms->type(), "")) {
+                return false;
+              }
+              out << ", access::read";
+              return true;
+            },
+            [&](const sem::SampledTexture* sampled) {
+              if (!EmitType(out, sampled->type(), "")) {
+                return false;
+              }
+              out << ", access::sample";
+              return true;
+            },
+            [&](Default) {
+              diagnostics_.add_error(diag::System::Writer,
+                                     "invalid texture type");
+              return false;
+            });
+      },
+      [&](const sem::U32*) {
+        out << "uint";
+        return true;
+      },
+      [&](const sem::Vector* vec) {
+        if (!EmitType(out, vec->type(), "")) {
+          return false;
+        }
+        out << vec->Width();
+        return true;
+      },
+      [&](const sem::Void*) {
+        out << "void";
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown type in EmitType: " + type->type_name());
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
+                                    const sem::Type* type,
+                                    const std::string& name) {
+  bool name_printed = false;
+  if (!EmitType(out, type, name, &name_printed)) {
+    return false;
+  }
+  if (!name_printed) {
+    out << " " << name;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStorageClass(std::ostream& out, ast::StorageClass sc) {
+  switch (sc) {
+    case ast::StorageClass::kFunction:
+    case ast::StorageClass::kPrivate:
+    case ast::StorageClass::kUniformConstant:
+      out << "thread";
+      return true;
+    case ast::StorageClass::kWorkgroup:
+      out << "threadgroup";
+      return true;
+    case ast::StorageClass::kStorage:
+      out << "device";
+      return true;
+    case ast::StorageClass::kUniform:
+      out << "constant";
+      return true;
+    default:
+      break;
+  }
+  TINT_ICE(Writer, diagnostics_) << "unhandled storage class: " << sc;
+  return false;
+}
+
+bool GeneratorImpl::EmitPackedType(std::ostream& out,
+                                   const sem::Type* type,
+                                   const std::string& name) {
+  auto* vec = type->As<sem::Vector>();
+  if (vec && vec->Width() == 3) {
+    out << "packed_";
+    if (!EmitType(out, vec, "")) {
+      return false;
+    }
+
+    if (vec->is_float_vector() && !matrix_packed_vector_overloads_) {
+      // Overload operators for matrix-vector arithmetic where the vector
+      // operand is packed, as these overloads to not exist in the metal
+      // namespace.
+      TextBuffer b;
+      TINT_DEFER(helpers_.Append(b));
+      line(&b) << R"(template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+)";
+      matrix_packed_vector_overloads_ = true;
+    }
+
+    return true;
+  }
+
+  return EmitType(out, type, name);
+}
+
+bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
+  line(b) << "struct " << StructName(str) << " {";
+
+  bool is_host_shareable = str->IsHostShareable();
+
+  // Emits a `/* 0xnnnn */` byte offset comment for a struct member.
+  auto add_byte_offset_comment = [&](std::ostream& out, uint32_t offset) {
+    std::ios_base::fmtflags saved_flag_state(out.flags());
+    out << "/* 0x" << std::hex << std::setfill('0') << std::setw(4) << offset
+        << " */ ";
+    out.flags(saved_flag_state);
+  };
+
+  auto add_padding = [&](uint32_t size, uint32_t msl_offset) {
+    std::string name;
+    do {
+      name = UniqueIdentifier("tint_pad");
+    } while (str->FindMember(program_->Symbols().Get(name)));
+
+    auto out = line(b);
+    add_byte_offset_comment(out, msl_offset);
+    out << "int8_t " << name << "[" << size << "];";
+  };
+
+  b->IncrementIndent();
+
+  uint32_t msl_offset = 0;
+  for (auto* mem : str->Members()) {
+    auto out = line(b);
+    auto mem_name = program_->Symbols().NameFor(mem->Name());
+    auto wgsl_offset = mem->Offset();
+
+    if (is_host_shareable) {
+      if (wgsl_offset < msl_offset) {
+        // Unimplementable layout
+        TINT_ICE(Writer, diagnostics_)
+            << "Structure member WGSL offset (" << wgsl_offset
+            << ") is behind MSL offset (" << msl_offset << ")";
+        return false;
+      }
+
+      // Generate padding if required
+      if (auto padding = wgsl_offset - msl_offset) {
+        add_padding(padding, msl_offset);
+        msl_offset += padding;
+      }
+
+      add_byte_offset_comment(out, msl_offset);
+
+      if (!EmitPackedType(out, mem->Type(), mem_name)) {
+        return false;
+      }
+    } else {
+      if (!EmitType(out, mem->Type(), mem_name)) {
+        return false;
+      }
+    }
+
+    auto* ty = mem->Type();
+
+    // Array member name will be output with the type
+    if (!ty->Is<sem::Array>()) {
+      out << " " << mem_name;
+    }
+
+    // Emit attributes
+    if (auto* decl = mem->Declaration()) {
+      for (auto* attr : decl->attributes) {
+        bool ok = Switch(
+            attr,
+            [&](const ast::BuiltinAttribute* builtin) {
+              auto name = builtin_to_attribute(builtin->builtin);
+              if (name.empty()) {
+                diagnostics_.add_error(diag::System::Writer, "unknown builtin");
+                return false;
+              }
+              out << " [[" << name << "]]";
+              return true;
+            },
+            [&](const ast::LocationAttribute* loc) {
+              auto& pipeline_stage_uses = str->PipelineStageUses();
+              if (pipeline_stage_uses.size() != 1) {
+                TINT_ICE(Writer, diagnostics_)
+                    << "invalid entry point IO struct uses";
+                return false;
+              }
+
+              if (pipeline_stage_uses.count(
+                      sem::PipelineStageUsage::kVertexInput)) {
+                out << " [[attribute(" + std::to_string(loc->value) + ")]]";
+              } else if (pipeline_stage_uses.count(
+                             sem::PipelineStageUsage::kVertexOutput)) {
+                out << " [[user(locn" + std::to_string(loc->value) + ")]]";
+              } else if (pipeline_stage_uses.count(
+                             sem::PipelineStageUsage::kFragmentInput)) {
+                out << " [[user(locn" + std::to_string(loc->value) + ")]]";
+              } else if (pipeline_stage_uses.count(
+                             sem::PipelineStageUsage::kFragmentOutput)) {
+                out << " [[color(" + std::to_string(loc->value) + ")]]";
+              } else {
+                TINT_ICE(Writer, diagnostics_)
+                    << "invalid use of location decoration";
+                return false;
+              }
+              return true;
+            },
+            [&](const ast::InterpolateAttribute* interpolate) {
+              auto name = interpolation_to_attribute(interpolate->type,
+                                                     interpolate->sampling);
+              if (name.empty()) {
+                diagnostics_.add_error(diag::System::Writer,
+                                       "unknown interpolation attribute");
+                return false;
+              }
+              out << " [[" << name << "]]";
+              return true;
+            },
+            [&](const ast::InvariantAttribute*) {
+              if (invariant_define_name_.empty()) {
+                invariant_define_name_ = UniqueIdentifier("TINT_INVARIANT");
+              }
+              out << " " << invariant_define_name_;
+              return true;
+            },
+            [&](const ast::StructMemberOffsetAttribute*) { return true; },
+            [&](const ast::StructMemberAlignAttribute*) { return true; },
+            [&](const ast::StructMemberSizeAttribute*) { return true; },
+            [&](Default) {
+              TINT_ICE(Writer, diagnostics_)
+                  << "unhandled struct member attribute: " << attr->Name();
+              return false;
+            });
+        if (!ok) {
+          return false;
+        }
+      }
+    }
+
+    out << ";";
+
+    if (is_host_shareable) {
+      // Calculate new MSL offset
+      auto size_align = MslPackedTypeSizeAndAlign(ty);
+      if (msl_offset % size_align.align) {
+        TINT_ICE(Writer, diagnostics_)
+            << "Misaligned MSL structure member "
+            << ty->FriendlyName(program_->Symbols()) << " " << mem_name;
+        return false;
+      }
+      msl_offset += size_align.size;
+    }
+  }
+
+  if (is_host_shareable && str->Size() != msl_offset) {
+    add_padding(str->Size() - msl_offset, msl_offset);
+  }
+
+  b->DecrementIndent();
+
+  line(b) << "};";
+  return true;
+}
+
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
+                                const ast::UnaryOpExpression* expr) {
+  // Handle `-e` when `e` is signed, so that we ensure that if `e` is the
+  // largest negative value, it returns `e`.
+  auto* expr_type = TypeOf(expr->expr)->UnwrapRef();
+  if (expr->op == ast::UnaryOp::kNegation &&
+      expr_type->is_signed_scalar_or_vector()) {
+    auto fn =
+        utils::GetOrCreate(unary_minus_funcs_, expr_type, [&]() -> std::string {
+          // e.g.:
+          // int tint_unary_minus(const int v) {
+          //     return (v == -2147483648) ? v : -v;
+          // }
+          TextBuffer b;
+          TINT_DEFER(helpers_.Append(b));
+
+          auto fn_name = UniqueIdentifier("tint_unary_minus");
+          {
+            auto decl = line(&b);
+            if (!EmitTypeAndName(decl, expr_type, fn_name)) {
+              return "";
+            }
+            decl << "(const ";
+            if (!EmitType(decl, expr_type, "")) {
+              return "";
+            }
+            decl << " v) {";
+          }
+
+          {
+            ScopedIndent si(&b);
+            const auto largest_negative_value =
+                std::to_string(std::numeric_limits<int32_t>::min());
+            line(&b) << "return select(-v, v, v == " << largest_negative_value
+                     << ");";
+          }
+          line(&b) << "}";
+          line(&b);
+          return fn_name;
+        });
+
+    out << fn << "(";
+    if (!EmitExpression(out, expr->expr)) {
+      return false;
+    }
+    out << ")";
+    return true;
+  }
+
+  switch (expr->op) {
+    case ast::UnaryOp::kAddressOf:
+      out << "&";
+      break;
+    case ast::UnaryOp::kComplement:
+      out << "~";
+      break;
+    case ast::UnaryOp::kIndirection:
+      out << "*";
+      break;
+    case ast::UnaryOp::kNot:
+      out << "!";
+      break;
+    case ast::UnaryOp::kNegation:
+      out << "-";
+      break;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+
+  for (auto* attr : decl->attributes) {
+    if (!attr->Is<ast::InternalAttribute>()) {
+      TINT_ICE(Writer, diagnostics_) << "unexpected variable attribute";
+      return false;
+    }
+  }
+
+  auto out = line();
+
+  switch (var->StorageClass()) {
+    case ast::StorageClass::kFunction:
+    case ast::StorageClass::kUniformConstant:
+    case ast::StorageClass::kNone:
+      break;
+    case ast::StorageClass::kPrivate:
+      out << "thread ";
+      break;
+    case ast::StorageClass::kWorkgroup:
+      out << "threadgroup ";
+      break;
+    default:
+      TINT_ICE(Writer, diagnostics_) << "unhandled variable storage class";
+      return false;
+  }
+
+  auto* type = var->Type()->UnwrapRef();
+
+  std::string name = program_->Symbols().NameFor(decl->symbol);
+  if (decl->is_const) {
+    name = "const " + name;
+  }
+  if (!EmitType(out, type, name)) {
+    return false;
+  }
+  // Variable name is output as part of the type for arrays and pointers.
+  if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
+    out << " " << name;
+  }
+
+  if (decl->constructor != nullptr) {
+    out << " = ";
+    if (!EmitExpression(out, decl->constructor)) {
+      return false;
+    }
+  } else if (var->StorageClass() == ast::StorageClass::kPrivate ||
+             var->StorageClass() == ast::StorageClass::kFunction ||
+             var->StorageClass() == ast::StorageClass::kNone) {
+    out << " = ";
+    if (!EmitZeroValue(out, type)) {
+      return false;
+    }
+  }
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
+  for (auto* d : var->attributes) {
+    if (!d->Is<ast::IdAttribute>()) {
+      diagnostics_.add_error(diag::System::Writer,
+                             "Decorated const values not valid");
+      return false;
+    }
+  }
+  if (!var->is_const) {
+    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
+    return false;
+  }
+
+  auto out = line();
+  out << "constant ";
+  auto* type = program_->Sem().Get(var)->Type()->UnwrapRef();
+  if (!EmitType(out, type, program_->Symbols().NameFor(var->symbol))) {
+    return false;
+  }
+  if (!type->Is<sem::Array>()) {
+    out << " " << program_->Symbols().NameFor(var->symbol);
+  }
+
+  auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
+  if (global && global->IsOverridable()) {
+    out << " [[function_constant(" << global->ConstantId() << ")]]";
+  } else if (var->constructor != nullptr) {
+    out << " = ";
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+  }
+  out << ";";
+
+  return true;
+}
+
+GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(
+    const sem::Type* ty) {
+  return Switch(
+      ty,
+
+      // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+      // 2.1 Scalar Data Types
+      [&](const sem::U32*) {
+        return SizeAndAlign{4, 4};
+      },
+      [&](const sem::I32*) {
+        return SizeAndAlign{4, 4};
+      },
+      [&](const sem::F32*) {
+        return SizeAndAlign{4, 4};
+      },
+
+      [&](const sem::Vector* vec) {
+        auto num_els = vec->Width();
+        auto* el_ty = vec->type();
+        if (el_ty->IsAnyOf<sem::U32, sem::I32, sem::F32>()) {
+          // Use a packed_vec type for 3-element vectors only.
+          if (num_els == 3) {
+            // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+            // 2.2.3 Packed Vector Types
+            return SizeAndAlign{num_els * 4, 4};
+          } else {
+            // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+            // 2.2 Vector Data Types
+            return SizeAndAlign{num_els * 4, num_els * 4};
+          }
+        }
+        TINT_UNREACHABLE(Writer, diagnostics_)
+            << "Unhandled vector element type " << el_ty->TypeInfo().name;
+        return SizeAndAlign{};
+      },
+
+      [&](const sem::Matrix* mat) {
+        // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+        // 2.3 Matrix Data Types
+        auto cols = mat->columns();
+        auto rows = mat->rows();
+        auto* el_ty = mat->type();
+        if (el_ty->IsAnyOf<sem::U32, sem::I32, sem::F32>()) {
+          static constexpr SizeAndAlign table[] = {
+              /* float2x2 */ {16, 8},
+              /* float2x3 */ {32, 16},
+              /* float2x4 */ {32, 16},
+              /* float3x2 */ {24, 8},
+              /* float3x3 */ {48, 16},
+              /* float3x4 */ {48, 16},
+              /* float4x2 */ {32, 8},
+              /* float4x3 */ {64, 16},
+              /* float4x4 */ {64, 16},
+          };
+          if (cols >= 2 && cols <= 4 && rows >= 2 && rows <= 4) {
+            return table[(3 * (cols - 2)) + (rows - 2)];
+          }
+        }
+
+        TINT_UNREACHABLE(Writer, diagnostics_)
+            << "Unhandled matrix element type " << el_ty->TypeInfo().name;
+        return SizeAndAlign{};
+      },
+
+      [&](const sem::Array* arr) {
+        if (!arr->IsStrideImplicit()) {
+          TINT_ICE(Writer, diagnostics_)
+              << "arrays with explicit strides should have "
+                 "removed with the PadArrayElements transform";
+          return SizeAndAlign{};
+        }
+        auto num_els = std::max<uint32_t>(arr->Count(), 1);
+        return SizeAndAlign{arr->Stride() * num_els, arr->Align()};
+      },
+
+      [&](const sem::Struct* str) {
+        // TODO(crbug.com/tint/650): There's an assumption here that MSL's
+        // default structure size and alignment matches WGSL's. We need to
+        // confirm this.
+        return SizeAndAlign{str->Size(), str->Align()};
+      },
+
+      [&](const sem::Atomic* atomic) {
+        return MslPackedTypeSizeAndAlign(atomic->Type());
+      },
+
+      [&](Default) {
+        TINT_UNREACHABLE(Writer, diagnostics_)
+            << "Unhandled type " << ty->TypeInfo().name;
+        return SizeAndAlign{};
+      });
+}
+
+template <typename F>
+bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
+                                      const ast::CallExpression* call,
+                                      const sem::Builtin* builtin,
+                                      F&& build) {
+  // Generate the helper function if it hasn't been created already
+  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
+    TextBuffer b;
+    TINT_DEFER(helpers_.Append(b));
+
+    auto fn_name =
+        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+    std::vector<std::string> parameter_names;
+    {
+      auto decl = line(&b);
+      if (!EmitTypeAndName(decl, builtin->ReturnType(), fn_name)) {
+        return "";
+      }
+      {
+        ScopedParen sp(decl);
+        for (auto* param : builtin->Parameters()) {
+          if (!parameter_names.empty()) {
+            decl << ", ";
+          }
+          auto param_name = "param_" + std::to_string(parameter_names.size());
+          if (!EmitTypeAndName(decl, param->Type(), param_name)) {
+            return "";
+          }
+          parameter_names.emplace_back(std::move(param_name));
+        }
+      }
+      decl << " {";
+    }
+    {
+      ScopedIndent si(&b);
+      if (!build(&b, parameter_names)) {
+        return "";
+      }
+    }
+    line(&b) << "}";
+    line(&b);
+    return fn_name;
+  });
+
+  if (fn.empty()) {
+    return false;
+  }
+
+  // Call the helper
+  out << fn;
+  {
+    ScopedParen sp(out);
+    bool first = true;
+    for (auto* arg : call->args) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+      if (!EmitExpression(out, arg)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
new file mode 100644
index 0000000..17ad9bd
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl.h
@@ -0,0 +1,450 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_MSL_GENERATOR_IMPL_H_
+#define SRC_TINT_WRITER_MSL_GENERATOR_IMPL_H_
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/index_accessor_expression.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/member_accessor_expression.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/program.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/text_generator.h"
+
+namespace tint {
+
+// Forward declarations
+namespace sem {
+class Call;
+class Builtin;
+class TypeConstructor;
+class TypeConversion;
+}  // namespace sem
+
+namespace writer {
+namespace msl {
+
+/// The result of sanitizing a program for generation.
+struct SanitizedResult {
+  /// Constructor
+  SanitizedResult();
+  /// Destructor
+  ~SanitizedResult();
+  /// Move constructor
+  SanitizedResult(SanitizedResult&&);
+
+  /// The sanitized program.
+  Program program;
+  /// True if the shader needs a UBO of buffer sizes.
+  bool needs_storage_buffer_sizes = false;
+  /// Indices into the array_length_from_uniform binding that are statically
+  /// used.
+  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
+};
+
+/// Sanitize a program in preparation for generating MSL.
+/// @param buffer_size_ubo_index the index to use for the buffer size UBO
+/// @param fixed_sample_mask the fixed sample mask to use for fragment shaders
+/// @param emit_vertex_point_size `true` to emit a vertex point size builtin
+/// @param disable_workgroup_init `true` to disable workgroup memory zero
+/// @returns the sanitized program and any supplementary information
+SanitizedResult Sanitize(
+    const Program* program,
+    uint32_t buffer_size_ubo_index,
+    uint32_t fixed_sample_mask = 0xFFFFFFFF,
+    bool emit_vertex_point_size = false,
+    bool disable_workgroup_init = false,
+    const ArrayLengthFromUniformOptions& array_length_from_uniform = {});
+
+/// Implementation class for MSL generator
+class GeneratorImpl : public TextGenerator {
+ public:
+  /// Constructor
+  /// @param program the program to generate
+  explicit GeneratorImpl(const Program* program);
+  ~GeneratorImpl();
+
+  /// @returns true on successful generation; false otherwise
+  bool Generate();
+
+  /// @returns true if an invariant attribute was generated
+  bool HasInvariant() { return !invariant_define_name_.empty(); }
+
+  /// @returns a map from entry point to list of required workgroup allocations
+  const std::unordered_map<std::string, std::vector<uint32_t>>&
+  DynamicWorkgroupAllocations() const {
+    return workgroup_allocations_;
+  }
+
+  /// Handles generating a declared type
+  /// @param ty the declared type to generate
+  /// @returns true if the declared type was emitted
+  bool EmitTypeDecl(const sem::Type* ty);
+  /// Handles an index accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the index accessor was emitted
+  bool EmitIndexAccessor(std::ostream& out,
+                         const ast::IndexAccessorExpression* expr);
+  /// Handles an assignment statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitAssign(const ast::AssignmentStatement* stmt);
+  /// Handles generating a binary expression
+  /// @param out the output of the expression stream
+  /// @param expr the binary expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
+  /// Handles generating a bitcast expression
+  /// @param out the output of the expression stream
+  /// @param expr the bitcast expression
+  /// @returns true if the bitcast was emitted
+  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
+  /// Handles a block statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBlock(const ast::BlockStatement* stmt);
+  /// Handles a break statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBreak(const ast::BreakStatement* stmt);
+  /// Handles generating a call expression
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles generating a builtin call expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the builtin being called
+  /// @returns true if the call expression is emitted
+  bool EmitBuiltinCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a type conversion expression
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param conv the type conversion
+  /// @returns true if the expression is emitted
+  bool EmitTypeConversion(std::ostream& out,
+                          const sem::Call* call,
+                          const sem::TypeConversion* conv);
+  /// Handles generating a type constructor
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param ctor the type constructor
+  /// @returns true if the constructor is emitted
+  bool EmitTypeConstructor(std::ostream& out,
+                           const sem::Call* call,
+                           const sem::TypeConstructor* ctor);
+  /// Handles generating a function call
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param func the target function
+  /// @returns true if the call is emitted
+  bool EmitFunctionCall(std::ostream& out,
+                        const sem::Call* call,
+                        const sem::Function* func);
+  /// Handles generating a call to an atomic function (`atomicAdd`,
+  /// `atomicMax`, etc)
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the atomic builtin
+  /// @returns true if the call expression is emitted
+  bool EmitAtomicCall(std::ostream& out,
+                      const ast::CallExpression* expr,
+                      const sem::Builtin* builtin);
+  /// Handles generating a call to a texture function (`textureSample`,
+  /// `textureSampleGrad`, etc)
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @returns true if the call expression is emitted
+  bool EmitTextureCall(std::ostream& out,
+                       const sem::Call* call,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `dot()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDotCall(std::ostream& out,
+                   const ast::CallExpression* expr,
+                   const sem::Builtin* builtin);
+  /// Handles generating a call to the `modf()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitModfCall(std::ostream& out,
+                    const ast::CallExpression* expr,
+                    const sem::Builtin* builtin);
+  /// Handles generating a call to the `frexp()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitFrexpCall(std::ostream& out,
+                     const ast::CallExpression* expr,
+                     const sem::Builtin* builtin);
+  /// Handles generating a call to the `degrees()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitDegreesCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles generating a call to the `radians()` builtin
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @returns true if the call expression is emitted
+  bool EmitRadiansCall(std::ostream& out,
+                       const ast::CallExpression* expr,
+                       const sem::Builtin* builtin);
+  /// Handles a case statement
+  /// @param stmt the statement
+  /// @returns true if the statement was emitted successfully
+  bool EmitCase(const ast::CaseStatement* stmt);
+  /// Handles a continue statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitContinue(const ast::ContinueStatement* stmt);
+  /// Handles generating a discard statement
+  /// @param stmt the discard statement
+  /// @returns true if the statement was successfully emitted
+  bool EmitDiscard(const ast::DiscardStatement* stmt);
+  /// Handles emitting the entry point function
+  /// @param func the entry point function
+  /// @returns true if the entry point function was emitted
+  bool EmitEntryPointFunction(const ast::Function* func);
+  /// Handles generate an Expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the expression was emitted
+  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
+  /// Handles generating a function
+  /// @param func the function to generate
+  /// @returns true if the function was emitted
+  bool EmitFunction(const ast::Function* func);
+  /// Handles generating an identifier expression
+  /// @param out the output of the expression stream
+  /// @param expr the identifier expression
+  /// @returns true if the identifier was emitted
+  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
+  /// Handles an if statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitIf(const ast::IfStatement* stmt);
+  /// Handles a literal
+  /// @param out the output of the expression stream
+  /// @param lit the literal to emit
+  /// @returns true if the literal was successfully emitted
+  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
+  /// Handles a loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitLoop(const ast::LoopStatement* stmt);
+  /// Handles a for loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitForLoop(const ast::ForLoopStatement* stmt);
+  /// Handles a member accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the member accessor expression
+  /// @returns true if the member accessor was emitted
+  bool EmitMemberAccessor(std::ostream& out,
+                          const ast::MemberAccessorExpression* expr);
+  /// Handles return statements
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitReturn(const ast::ReturnStatement* stmt);
+  /// Handles emitting a pipeline stage name
+  /// @param out the output of the expression stream
+  /// @param stage the stage to emit
+  void EmitStage(std::ostream& out, ast::PipelineStage stage);
+  /// Handles statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitStatement(const ast::Statement* stmt);
+  /// Emits a list of statements
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatements(const ast::StatementList& stmts);
+  /// Emits a list of statements with an indentation
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+  /// Handles generating a switch statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitSwitch(const ast::SwitchStatement* stmt);
+  /// Handles generating a type
+  /// @param out the output of the type stream
+  /// @param type the type to generate
+  /// @param name the name of the variable, only used for array emission
+  /// @param name_printed (optional) if not nullptr and an array was printed
+  /// @returns true if the type is emitted
+  bool EmitType(std::ostream& out,
+                const sem::Type* type,
+                const std::string& name,
+                bool* name_printed = nullptr);
+  /// Handles generating type and name
+  /// @param out the output stream
+  /// @param type the type to generate
+  /// @param name the name to emit
+  /// @returns true if the type is emitted
+  bool EmitTypeAndName(std::ostream& out,
+                       const sem::Type* type,
+                       const std::string& name);
+  /// Handles generating a storage class
+  /// @param out the output of the type stream
+  /// @param sc the storage class to generate
+  /// @returns true if the storage class is emitted
+  bool EmitStorageClass(std::ostream& out, ast::StorageClass sc);
+  /// Handles generating an MSL-packed storage type.
+  /// If the type does not have a packed form, the standard non-packed form is
+  /// emitted.
+  /// @param out the output of the type stream
+  /// @param type the type to generate
+  /// @param name the name of the variable, only used for array emission
+  /// @returns true if the type is emitted
+  bool EmitPackedType(std::ostream& out,
+                      const sem::Type* type,
+                      const std::string& name);
+  /// Handles generating a struct declaration
+  /// @param buffer the text buffer that the type declaration will be written to
+  /// @param str the struct to generate
+  /// @returns true if the struct is emitted
+  bool EmitStructType(TextBuffer* buffer, const sem::Struct* str);
+  /// Handles a unary op expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the expression was emitted
+  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
+  /// Handles generating a variable
+  /// @param var the variable to generate
+  /// @returns true if the variable was emitted
+  bool EmitVariable(const sem::Variable* var);
+  /// Handles generating a program scope constant variable
+  /// @param var the variable to emit
+  /// @returns true if the variable was emitted
+  bool EmitProgramConstVariable(const ast::Variable* var);
+  /// Emits the zero value for the given type
+  /// @param out the output of the expression stream
+  /// @param type the type to emit the value for
+  /// @returns true if the zero value was successfully emitted.
+  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+
+  /// Handles generating a builtin name
+  /// @param builtin the semantic info for the builtin
+  /// @returns the name or "" if not valid
+  std::string generate_builtin_name(const sem::Builtin* builtin);
+
+  /// Converts a builtin to an attribute name
+  /// @param builtin the builtin to convert
+  /// @returns the string name of the builtin or blank on error
+  std::string builtin_to_attribute(ast::Builtin builtin) const;
+
+  /// Converts interpolation attributes to an MSL attribute
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  /// @returns the string name of the attribute or blank on error
+  std::string interpolation_to_attribute(
+      ast::InterpolationType type,
+      ast::InterpolationSampling sampling) const;
+
+ private:
+  // A pair of byte size and alignment `uint32_t`s.
+  struct SizeAndAlign {
+    uint32_t size;
+    uint32_t align;
+  };
+
+  /// CallBuiltinHelper will call the builtin helper function, creating it
+  /// if it hasn't been built already. If the builtin needs to be built then
+  /// CallBuiltinHelper will generate the function signature and will call
+  /// `build` to emit the body of the function.
+  /// @param out the output of the expression stream
+  /// @param call the call expression
+  /// @param builtin the semantic information for the builtin
+  /// @param build a function with the signature:
+  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
+  ///        Where:
+  ///          `buffer` is the body of the generated function
+  ///          `params` is the name of all the generated function parameters
+  /// @returns true if the call expression is emitted
+  template <typename F>
+  bool CallBuiltinHelper(std::ostream& out,
+                         const ast::CallExpression* call,
+                         const sem::Builtin* builtin,
+                         F&& build);
+
+  TextBuffer helpers_;  // Helper functions emitted at the top of the output
+
+  /// @returns the MSL packed type size and alignment in bytes for the given
+  /// type.
+  SizeAndAlign MslPackedTypeSizeAndAlign(const sem::Type* ty);
+
+  using StorageClassToString =
+      std::unordered_map<ast::StorageClass, std::string>;
+
+  std::function<bool()> emit_continuing_;
+
+  /// Name of atomicCompareExchangeWeak() helper for the given pointer storage
+  /// class.
+  StorageClassToString atomicCompareExchangeWeak_;
+
+  /// Unique name of the 'TINT_INVARIANT' preprocessor define. Non-empty only if
+  /// an invariant attribute has been generated.
+  std::string invariant_define_name_;
+
+  /// True if matrix-packed_vector operator overloads have been generated.
+  bool matrix_packed_vector_overloads_ = false;
+
+  /// A map from entry point name to a list of dynamic workgroup allocations.
+  /// Each entry in the vector is the size of the workgroup allocation that
+  /// should be created for that index.
+  std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations_;
+
+  std::unordered_map<const sem::Builtin*, std::string> builtins_;
+  std::unordered_map<const sem::Type*, std::string> unary_minus_funcs_;
+  std::unordered_map<uint32_t, std::string> int_dot_funcs_;
+};
+
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_MSL_GENERATOR_IMPL_H_
diff --git a/src/tint/writer/msl/generator_impl_array_accessor_test.cc b/src/tint/writer/msl/generator_impl_array_accessor_test.cc
new file mode 100644
index 0000000..3625c93
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_array_accessor_test.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, IndexAccessor) {
+  auto* ary = Var("ary", ty.array<i32, 10>());
+  auto* expr = IndexAccessor("ary", 5);
+  WrapInFunction(ary, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "ary[5]");
+}
+
+TEST_F(MslGeneratorImplTest, IndexAccessor_OfDref) {
+  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
+
+  auto* p = Const("p", nullptr, AddressOf("ary"));
+  auto* expr = IndexAccessor(Deref("p"), 5);
+  WrapInFunction(p, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(*(p))[5]");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_assign_test.cc b/src/tint/writer/msl/generator_impl_assign_test.cc
new file mode 100644
index 0000000..655ca2e
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_assign_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Assign) {
+  auto* lhs = Var("lhs", ty.i32());
+  auto* rhs = Var("rhs", ty.i32());
+  auto* assign = Assign(lhs, rhs);
+  WrapInFunction(lhs, rhs, assign);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
+  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_binary_test.cc b/src/tint/writer/msl/generator_impl_binary_test.cc
new file mode 100644
index 0000000..f99aa3c
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_binary_test.cc
@@ -0,0 +1,187 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+struct BinaryData {
+  const char* result;
+  ast::BinaryOp op;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << data.op;
+  return out;
+}
+using MslBinaryTest = TestParamHelper<BinaryData>;
+TEST_P(MslBinaryTest, Emit) {
+  auto params = GetParam();
+
+  auto type = [&] {
+    return ((params.op == ast::BinaryOp::kLogicalAnd) ||
+            (params.op == ast::BinaryOp::kLogicalOr))
+               ? static_cast<const ast::Type*>(ty.bool_())
+               : static_cast<const ast::Type*>(ty.u32());
+  };
+
+  auto* left = Var("left", type());
+  auto* right = Var("right", type());
+
+  auto* expr =
+      create<ast::BinaryExpression>(params.op, Expr(left), Expr(right));
+  WrapInFunction(left, right, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslBinaryTest,
+    testing::Values(
+        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
+        BinaryData{"(left | right)", ast::BinaryOp::kOr},
+        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
+        BinaryData{"(left && right)", ast::BinaryOp::kLogicalAnd},
+        BinaryData{"(left || right)", ast::BinaryOp::kLogicalOr},
+        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
+        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
+        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
+        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
+        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
+        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
+        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
+        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
+        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
+        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
+        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
+        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
+        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
+
+using MslBinaryTest_SignedOverflowDefinedBehaviour =
+    TestParamHelper<BinaryData>;
+TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour, Emit) {
+  auto params = GetParam();
+
+  auto* a_type = ty.i32();
+  auto* b_type = (params.op == ast::BinaryOp::kShiftLeft ||
+                  params.op == ast::BinaryOp::kShiftRight)
+                     ? static_cast<const ast::Type*>(ty.u32())
+                     : ty.i32();
+
+  auto* a = Var("a", a_type);
+  auto* b = Var("b", b_type);
+
+  auto* expr = create<ast::BinaryExpression>(params.op, Expr(a), Expr(b));
+  WrapInFunction(a, b, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+using Op = ast::BinaryOp;
+constexpr BinaryData signed_overflow_defined_behaviour_cases[] = {
+    {"as_type<int>((as_type<uint>(a) << b))", Op::kShiftLeft},
+    {"(a >> b)", Op::kShiftRight},
+    {"as_type<int>((as_type<uint>(a) + as_type<uint>(b)))", Op::kAdd},
+    {"as_type<int>((as_type<uint>(a) - as_type<uint>(b)))", Op::kSubtract},
+    {"as_type<int>((as_type<uint>(a) * as_type<uint>(b)))", Op::kMultiply}};
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslBinaryTest_SignedOverflowDefinedBehaviour,
+    testing::ValuesIn(signed_overflow_defined_behaviour_cases));
+
+using MslBinaryTest_SignedOverflowDefinedBehaviour_Chained =
+    TestParamHelper<BinaryData>;
+TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour_Chained, Emit) {
+  auto params = GetParam();
+
+  auto* a_type = ty.i32();
+  auto* b_type = (params.op == ast::BinaryOp::kShiftLeft ||
+                  params.op == ast::BinaryOp::kShiftRight)
+                     ? static_cast<const ast::Type*>(ty.u32())
+                     : ty.i32();
+
+  auto* a = Var("a", a_type);
+  auto* b = Var("b", b_type);
+
+  auto* expr1 = create<ast::BinaryExpression>(params.op, Expr(a), Expr(b));
+  auto* expr2 = create<ast::BinaryExpression>(params.op, expr1, Expr(b));
+  WrapInFunction(a, b, expr2);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr2)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+using Op = ast::BinaryOp;
+constexpr BinaryData signed_overflow_defined_behaviour_chained_cases[] = {
+    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) << b))) << "
+     "b))",
+     Op::kShiftLeft},
+    {"((a >> b) >> b)", Op::kShiftRight},
+    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) + "
+     "as_type<uint>(b)))) + as_type<uint>(b)))",
+     Op::kAdd},
+    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) - "
+     "as_type<uint>(b)))) - as_type<uint>(b)))",
+     Op::kSubtract},
+    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) * "
+     "as_type<uint>(b)))) * as_type<uint>(b)))",
+     Op::kMultiply}};
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslBinaryTest_SignedOverflowDefinedBehaviour_Chained,
+    testing::ValuesIn(signed_overflow_defined_behaviour_chained_cases));
+
+TEST_F(MslBinaryTest, ModF32) {
+  auto* left = Var("left", ty.f32());
+  auto* right = Var("right", ty.f32());
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kModulo, Expr(left),
+                                             Expr(right));
+  WrapInFunction(left, right, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "fmod(left, right)");
+}
+
+TEST_F(MslBinaryTest, ModVec3F32) {
+  auto* left = Var("left", ty.vec3<f32>());
+  auto* right = Var("right", ty.vec3<f32>());
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kModulo, Expr(left),
+                                             Expr(right));
+  WrapInFunction(left, right, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "fmod(left, right)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_bitcast_test.cc b/src/tint/writer/msl/generator_impl_bitcast_test.cc
new file mode 100644
index 0000000..b5c9e72
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_bitcast_test.cc
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Bitcast) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "as_type<float>(1)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_block_test.cc b/src/tint/writer/msl/generator_impl_block_test.cc
new file mode 100644
index 0000000..8e92726
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_block_test.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Block) {
+  auto* b = Block(create<ast::DiscardStatement>());
+  WrapInFunction(b);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    discard_fragment();
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Block_WithoutNewline) {
+  auto* b = Block(create<ast::DiscardStatement>());
+  WrapInFunction(b);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitBlock(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    discard_fragment();
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_break_test.cc b/src/tint/writer/msl/generator_impl_break_test.cc
new file mode 100644
index 0000000..2a9d313
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_break_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Break) {
+  auto* b = create<ast::BreakStatement>();
+  WrapInFunction(Loop(Block(b)));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), "  break;\n");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_builtin_test.cc b/src/tint/writer/msl/generator_impl_builtin_test.cc
new file mode 100644
index 0000000..6c64adc
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_builtin_test.cc
@@ -0,0 +1,496 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using BuiltinType = sem::BuiltinType;
+
+using MslGeneratorImplTest = TestHelper;
+
+enum class ParamType {
+  kF32,
+  kU32,
+  kBool,
+};
+
+struct BuiltinData {
+  BuiltinType builtin;
+  ParamType type;
+  const char* msl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.msl_name << "<";
+  switch (data.type) {
+    case ParamType::kF32:
+      out << "f32";
+      break;
+    case ParamType::kU32:
+      out << "u32";
+      break;
+    case ParamType::kBool:
+      out << "bool";
+      break;
+  }
+  out << ">";
+  return out;
+}
+
+const ast::CallExpression* GenerateCall(BuiltinType builtin,
+                                        ParamType type,
+                                        ProgramBuilder* builder) {
+  std::string name;
+  std::ostringstream str(name);
+  str << builtin;
+  switch (builtin) {
+    case BuiltinType::kAcos:
+    case BuiltinType::kAsin:
+    case BuiltinType::kAtan:
+    case BuiltinType::kCeil:
+    case BuiltinType::kCos:
+    case BuiltinType::kCosh:
+    case BuiltinType::kDpdx:
+    case BuiltinType::kDpdxCoarse:
+    case BuiltinType::kDpdxFine:
+    case BuiltinType::kDpdy:
+    case BuiltinType::kDpdyCoarse:
+    case BuiltinType::kDpdyFine:
+    case BuiltinType::kExp:
+    case BuiltinType::kExp2:
+    case BuiltinType::kFloor:
+    case BuiltinType::kFract:
+    case BuiltinType::kFwidth:
+    case BuiltinType::kFwidthCoarse:
+    case BuiltinType::kFwidthFine:
+    case BuiltinType::kInverseSqrt:
+    case BuiltinType::kIsFinite:
+    case BuiltinType::kIsInf:
+    case BuiltinType::kIsNan:
+    case BuiltinType::kIsNormal:
+    case BuiltinType::kLength:
+    case BuiltinType::kLog:
+    case BuiltinType::kLog2:
+    case BuiltinType::kNormalize:
+    case BuiltinType::kRound:
+    case BuiltinType::kSin:
+    case BuiltinType::kSinh:
+    case BuiltinType::kSqrt:
+    case BuiltinType::kTan:
+    case BuiltinType::kTanh:
+    case BuiltinType::kTrunc:
+    case BuiltinType::kSign:
+      return builder->Call(str.str(), "f2");
+    case BuiltinType::kLdexp:
+      return builder->Call(str.str(), "f2", "i2");
+    case BuiltinType::kAtan2:
+    case BuiltinType::kDot:
+    case BuiltinType::kDistance:
+    case BuiltinType::kPow:
+    case BuiltinType::kReflect:
+    case BuiltinType::kStep:
+      return builder->Call(str.str(), "f2", "f2");
+    case BuiltinType::kStorageBarrier:
+      return builder->Call(str.str());
+    case BuiltinType::kCross:
+      return builder->Call(str.str(), "f3", "f3");
+    case BuiltinType::kFma:
+    case BuiltinType::kMix:
+    case BuiltinType::kFaceForward:
+    case BuiltinType::kSmoothStep:
+      return builder->Call(str.str(), "f2", "f2", "f2");
+    case BuiltinType::kAll:
+    case BuiltinType::kAny:
+      return builder->Call(str.str(), "b2");
+    case BuiltinType::kAbs:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2");
+      } else {
+        return builder->Call(str.str(), "u2");
+      }
+    case BuiltinType::kCountOneBits:
+    case BuiltinType::kReverseBits:
+      return builder->Call(str.str(), "u2");
+    case BuiltinType::kMax:
+    case BuiltinType::kMin:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2");
+      }
+    case BuiltinType::kClamp:
+      if (type == ParamType::kF32) {
+        return builder->Call(str.str(), "f2", "f2", "f2");
+      } else {
+        return builder->Call(str.str(), "u2", "u2", "u2");
+      }
+    case BuiltinType::kSelect:
+      return builder->Call(str.str(), "f2", "f2", "b2");
+    case BuiltinType::kDeterminant:
+      return builder->Call(str.str(), "m2x2");
+    case BuiltinType::kPack2x16snorm:
+    case BuiltinType::kPack2x16unorm:
+      return builder->Call(str.str(), "f2");
+    case BuiltinType::kPack4x8snorm:
+    case BuiltinType::kPack4x8unorm:
+      return builder->Call(str.str(), "f4");
+    case BuiltinType::kUnpack4x8snorm:
+    case BuiltinType::kUnpack4x8unorm:
+    case BuiltinType::kUnpack2x16snorm:
+    case BuiltinType::kUnpack2x16unorm:
+      return builder->Call(str.str(), "u1");
+    case BuiltinType::kWorkgroupBarrier:
+      return builder->Call(str.str());
+    case BuiltinType::kTranspose:
+      return builder->Call(str.str(), "m3x2");
+    default:
+      break;
+  }
+  return nullptr;
+}
+
+using MslBuiltinTest = TestParamHelper<BuiltinData>;
+TEST_P(MslBuiltinTest, Emit) {
+  auto param = GetParam();
+
+  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("f4", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("u1", ty.u32(), ast::StorageClass::kPrivate);
+  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
+  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
+  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
+  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = GenerateCall(param.builtin, param.type, this);
+  ASSERT_NE(nullptr, call) << "Unhandled builtin";
+  Func("func", {}, ty.void_(), {Ignore(call)},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem = program->Sem().Get(call);
+  ASSERT_NE(sem, nullptr);
+  auto* target = sem->Target();
+  ASSERT_NE(target, nullptr);
+  auto* builtin = target->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+
+  EXPECT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslBuiltinTest,
+    testing::Values(
+        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "fabs"},
+        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
+        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
+        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
+        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
+        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
+        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
+        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan2"},
+        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
+        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
+        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
+        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
+        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "popcount"},
+        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
+        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
+        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
+        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
+        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "dfdx"},
+        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "dfdx"},
+        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "dfdx"},
+        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "dfdy"},
+        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "dfdy"},
+        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "dfdy"},
+        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
+        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
+        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
+        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
+        BuiltinData{BuiltinType::kFma, ParamType::kF32, "fma"},
+        BuiltinData{BuiltinType::kFract, ParamType::kF32, "fract"},
+        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
+        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
+        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
+        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
+        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
+        BuiltinData{BuiltinType::kIsNormal, ParamType::kF32, "isnormal"},
+        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
+        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
+        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
+        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
+        BuiltinData{BuiltinType::kMax, ParamType::kF32, "fmax"},
+        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
+        BuiltinData{BuiltinType::kMin, ParamType::kF32, "fmin"},
+        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
+        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
+        BuiltinData{BuiltinType::kPack4x8snorm, ParamType::kF32,
+                    "pack_float_to_snorm4x8"},
+        BuiltinData{BuiltinType::kPack4x8unorm, ParamType::kF32,
+                    "pack_float_to_unorm4x8"},
+        BuiltinData{BuiltinType::kPack2x16snorm, ParamType::kF32,
+                    "pack_float_to_snorm2x16"},
+        BuiltinData{BuiltinType::kPack2x16unorm, ParamType::kF32,
+                    "pack_float_to_unorm2x16"},
+        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
+        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
+        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reverse_bits"},
+        BuiltinData{BuiltinType::kRound, ParamType::kU32, "rint"},
+        BuiltinData{BuiltinType::kSelect, ParamType::kF32, "select"},
+        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
+        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
+        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
+        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
+        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
+        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
+        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
+        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
+        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
+        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"},
+        BuiltinData{BuiltinType::kUnpack4x8snorm, ParamType::kU32,
+                    "unpack_snorm4x8_to_float"},
+        BuiltinData{BuiltinType::kUnpack4x8unorm, ParamType::kU32,
+                    "unpack_unorm4x8_to_float"},
+        BuiltinData{BuiltinType::kUnpack2x16snorm, ParamType::kU32,
+                    "unpack_snorm2x16_to_float"},
+        BuiltinData{BuiltinType::kUnpack2x16unorm, ParamType::kU32,
+                    "unpack_unorm2x16_to_float"}));
+
+TEST_F(MslGeneratorImplTest, Builtin_Call) {
+  Global("param1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("param2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("dot", "param1", "param2");
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "dot(param1, param2)");
+}
+
+TEST_F(MslGeneratorImplTest, StorageBarrier) {
+  auto* call = Call("storageBarrier");
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "threadgroup_barrier(mem_flags::mem_device)");
+}
+
+TEST_F(MslGeneratorImplTest, WorkgroupBarrier) {
+  auto* call = Call("workgroupBarrier");
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "threadgroup_barrier(mem_flags::mem_threadgroup)");
+}
+
+TEST_F(MslGeneratorImplTest, Degrees_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+float tint_degrees(float param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+kernel void test_function() {
+  float val = 0.0f;
+  float const tint_symbol = tint_degrees(val);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Degrees_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("degrees", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+float3 tint_degrees(float3 param_0) {
+  return param_0 * 57.295779513082322865;
+}
+
+kernel void test_function() {
+  float3 val = 0.0f;
+  float3 const tint_symbol = tint_degrees(val);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Radians_Scalar) {
+  auto* val = Var("val", ty.f32());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+float tint_radians(float param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+kernel void test_function() {
+  float val = 0.0f;
+  float const tint_symbol = tint_radians(val);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Radians_Vector) {
+  auto* val = Var("val", ty.vec3<f32>());
+  auto* call = Call("radians", val);
+  WrapInFunction(val, call);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+float3 tint_radians(float3 param_0) {
+  return param_0 * 0.017453292519943295474;
+}
+
+kernel void test_function() {
+  float3 val = 0.0f;
+  float3 const tint_symbol = tint_radians(val);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Pack2x16Float) {
+  auto* call = Call("pack2x16float", "p1");
+  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "as_type<uint>(half2(p1))");
+}
+
+TEST_F(MslGeneratorImplTest, Unpack2x16Float) {
+  auto* call = Call("unpack2x16float", "p1");
+  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(call));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "float2(as_type<half2>(p1))");
+}
+
+TEST_F(MslGeneratorImplTest, DotI32) {
+  Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+  WrapInFunction(CallStmt(Call("dot", "v", "v")));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T>
+T tint_dot3(vec<T,3> a, vec<T,3> b) {
+  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
+}
+kernel void test_function() {
+  thread int3 tint_symbol = 0;
+  tint_dot3(tint_symbol, tint_symbol);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Ignore) {
+  Func("f", {Param("a", ty.i32()), Param("b", ty.i32()), Param("c", ty.i32())},
+       ty.i32(), {Return(Mul(Add("a", "b"), "c"))});
+
+  Func("func", {}, ty.void_(), {CallStmt(Call("f", 1, 2, 3))},
+       {
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+int f(int a, int b, int c) {
+  return as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) + as_type<uint>(b)))) * as_type<uint>(c)));
+}
+
+kernel void func() {
+  f(1, 2, 3);
+  return;
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
new file mode 100644
index 0000000..61c0a22
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
@@ -0,0 +1,305 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+std::string expected_texture_overload(
+    ast::builtin::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+      return R"(int(texture.get_width(0)))";
+    case ValidTextureOverload::kDimensions2d:
+    case ValidTextureOverload::kDimensions2dArray:
+    case ValidTextureOverload::kDimensionsCube:
+    case ValidTextureOverload::kDimensionsCubeArray:
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+    case ValidTextureOverload::kDimensionsDepth2d:
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+    case ValidTextureOverload::kDimensionsDepthCube:
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+      return R"(int2(texture.get_width(), texture.get_height()))";
+    case ValidTextureOverload::kDimensions3d:
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return R"(int3(texture.get_width(), texture.get_height(), texture.get_depth()))";
+    case ValidTextureOverload::kDimensions2dLevel:
+    case ValidTextureOverload::kDimensionsCubeLevel:
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return R"(int2(texture.get_width(1), texture.get_height(1)))";
+    case ValidTextureOverload::kDimensions3dLevel:
+      return R"(int3(texture.get_width(1), texture.get_height(1), texture.get_depth(1)))";
+    case ValidTextureOverload::kGather2dF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(0), component::x))";
+    case ValidTextureOverload::kGather2dOffsetF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(3, 4), component::x))";
+    case ValidTextureOverload::kGather2dArrayF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(0), component::x))";
+    case ValidTextureOverload::kGather2dArrayOffsetF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(4, 5), component::x))";
+    case ValidTextureOverload::kGatherCubeF32:
+      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), component::x))";
+    case ValidTextureOverload::kGatherCubeArrayF32:
+      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), 4, component::x))";
+    case ValidTextureOverload::kGatherDepth2dF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kGatherDepth2dOffsetF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kGatherDepth2dArrayF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3))";
+    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
+      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
+    case ValidTextureOverload::kGatherDepthCubeF32:
+      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kGatherDepthCubeArrayF32:
+      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
+    case ValidTextureOverload::kGatherCompareDepth2dF32:
+      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
+      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
+      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3, 4.0f))";
+    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
+      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3, 4.0f, int2(5, 6)))";
+    case ValidTextureOverload::kGatherCompareDepthCubeF32:
+      return R"(texture.gather_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
+      return R"(texture.gather_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
+    case ValidTextureOverload::kNumLayers2dArray:
+    case ValidTextureOverload::kNumLayersCubeArray:
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return R"(int(texture.get_array_size()))";
+    case ValidTextureOverload::kNumLevels2d:
+    case ValidTextureOverload::kNumLevels2dArray:
+    case ValidTextureOverload::kNumLevels3d:
+    case ValidTextureOverload::kNumLevelsCube:
+    case ValidTextureOverload::kNumLevelsCubeArray:
+    case ValidTextureOverload::kNumLevelsDepth2d:
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+    case ValidTextureOverload::kNumLevelsDepthCube:
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return R"(int(texture.get_num_mip_levels()))";
+    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+      return R"(int(texture.get_num_samples()))";
+    case ValidTextureOverload::kSample1dF32:
+      return R"(texture.sample(sampler, 1.0f))";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3))";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), int3(4, 5, 6)))";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3))";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), bias(3.0f)))";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), bias(3.0f), int2(4, 5)))";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 4, bias(3.0f)))";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, bias(4.0f), int2(5, 6)))";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f)))";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f), int3(5, 6, 7)))";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f)))";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 3, bias(4.0f)))";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3.0f)))";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3.0f), int2(4, 5)))";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4.0f)))";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4.0f), int2(5, 6)))";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f)))";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f), int3(5, 6, 7)))";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f)))";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, level(5.0f)))";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3)))";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3), int2(4, 5)))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4)))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4), int2(5, 6)))";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4)))";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, level(5)))";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), gradient2d(float2(3.0f, 4.0f), float2(5.0f, 6.0f))))";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), gradient2d(float2(3.0f, 4.0f), float2(5.0f, 6.0f)), int2(7, 7)))";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, gradient2d(float2(4.0f, 5.0f), float2(6.0f, 7.0f))))";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, gradient2d(float2(4.0f, 5.0f), float2(6.0f, 7.0f)), int2(6, 7)))";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradient3d(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f))))";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradient3d(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)), int3(0, 1, 2)))";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradientcube(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f))))";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, gradientcube(float3(5.0f, 6.0f, 7.0f), float3(8.0f, 9.0f, 10.0f))))";
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f, int2(5, 6)))";
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f))";
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
+      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f, int2(5, 6)))";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
+      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
+      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
+    case ValidTextureOverload::kLoad1dLevelF32:
+      return R"(texture.read(uint(1), 0))";
+    case ValidTextureOverload::kLoad1dLevelU32:
+      return R"(texture.read(uint(1), 0))";
+    case ValidTextureOverload::kLoad1dLevelI32:
+      return R"(texture.read(uint(1), 0))";
+    case ValidTextureOverload::kLoad2dLevelF32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoad2dLevelU32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
+    case ValidTextureOverload::kLoad3dLevelF32:
+      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
+    case ValidTextureOverload::kLoad3dLevelU32:
+      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
+    case ValidTextureOverload::kLoad3dLevelI32:
+      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
+      return R"(texture.read(uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+      return R"(texture.write(float4(2.0f, 3.0f, 4.0f, 5.0f), uint(1)))";
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+      return R"(texture.write(float4(3.0f, 4.0f, 5.0f, 6.0f), uint2(int2(1, 2))))";
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return R"(texture.write(float4(4.0f, 5.0f, 6.0f, 7.0f), uint2(int2(1, 2)), 3))";
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return R"(texture.write(float4(4.0f, 5.0f, 6.0f, 7.0f), uint3(int3(1, 2, 3))))";
+  }
+  return "<unmatched texture overload>";
+}  // NOLINT - Ignore the length of this function
+
+class MslGeneratorBuiltinTextureTest
+    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
+
+TEST_P(MslGeneratorBuiltinTextureTest, Call) {
+  auto param = GetParam();
+
+  param.BuildTextureVariable(this);
+  param.BuildSamplerVariable(this);
+
+  auto* call = Call(Expr(param.function), param.args(this));
+  auto* stmt = CallStmt(call);
+
+  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+
+  auto expected = expected_texture_overload(param.overload);
+  EXPECT_EQ(expected, out.str());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorBuiltinTextureTest,
+    MslGeneratorBuiltinTextureTest,
+    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_call_test.cc b/src/tint/writer/msl/generator_impl_call_test.cc
new file mode 100644
index 0000000..3460915
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_call_test.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
+  Func("my_func", {}, ty.f32(), {Return(1.23f)});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func()");
+}
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithParams) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.f32(), {Return(1.23f)});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func(param1, param2)");
+}
+
+TEST_F(MslGeneratorImplTest, EmitStatement_Call) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  auto* stmt = CallStmt(call);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_case_test.cc b/src/tint/writer/msl/generator_impl_case_test.cc
new file mode 100644
index 0000000..ca40a64
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_case_test.cc
@@ -0,0 +1,108 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Case) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Case_BreaksByDefault) {
+  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Case_WithFallthrough) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::FallthroughStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    /* fallthrough */
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Case_MultipleSelectors) {
+  auto* s =
+      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
+             DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5:
+  case 6: {
+    break;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Case_Default) {
+  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  default: {
+    break;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_cast_test.cc b/src/tint/writer/msl/generator_impl_cast_test.cc
new file mode 100644
index 0000000..0195d36
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_cast_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Scalar) {
+  auto* cast = Construct<f32>(1);
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float(1)");
+}
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Vector) {
+  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float3(int3(1, 2, 3))");
+}
+
+TEST_F(MslGeneratorImplTest, EmitExpression_Cast_IntMin) {
+  auto* cast = Construct<u32>(std::numeric_limits<int32_t>::min());
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "uint((-2147483647 - 1))");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_constructor_test.cc b/src/tint/writer/msl/generator_impl_constructor_test.cc
new file mode 100644
index 0000000..2b507f2
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_constructor_test.cc
@@ -0,0 +1,190 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Bool) {
+  WrapInFunction(Expr(false));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("false"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Int) {
+  WrapInFunction(Expr(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_UInt) {
+  WrapInFunction(Expr(56779u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Float) {
+  // Use a number close to 1<<30 but whose decimal representation ends in 0.
+  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Float) {
+  WrapInFunction(Construct<f32>(-1.2e-5f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Bool) {
+  WrapInFunction(Construct<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Int) {
+  WrapInFunction(Construct<i32>(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Uint) {
+  WrapInFunction(Construct<u32>(12345u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec) {
+  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec_Empty) {
+  WrapInFunction(vec3<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3()"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Mat) {
+  WrapInFunction(Construct(ty.mat2x3<f32>(), vec3<f32>(1.0f, 2.0f, 3.0f),
+                           vec3<f32>(3.0f, 4.0f, 5.0f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  // A matrix of type T with n columns and m rows can also be constructed from
+  // n vectors of type T with m components.
+  EXPECT_THAT(
+      gen.result(),
+      HasSubstr(
+          "float2x3(float3(1.0f, 2.0f, 3.0f), float3(3.0f, 4.0f, 5.0f))"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Mat_Empty) {
+  WrapInFunction(mat4x4<f32>());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float4x4()"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Array) {
+  WrapInFunction(
+      Construct(ty.array(ty.vec3<f32>(), 3), vec3<f32>(1.0f, 2.0f, 3.0f),
+                vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("{float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), "
+                        "float3(7.0f, 8.0f, 9.0f)}"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("{.a=1, .b=2.0f, .c=int3(3, 4, 5)}"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct_Empty) {
+  auto* str = Structure("S", {
+                                 Member("a", ty.i32()),
+                                 Member("b", ty.f32()),
+                                 Member("c", ty.vec3<i32>()),
+                             });
+
+  WrapInFunction(Construct(ty.Of(str)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("{}"));
+  EXPECT_THAT(gen.result(), testing::Not(HasSubstr("{{}}")));
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_continue_test.cc b/src/tint/writer/msl/generator_impl_continue_test.cc
new file mode 100644
index 0000000..895b3c1
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_continue_test.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Continue) {
+  auto* loop = Loop(Block(If(false, Block(Break())),  //
+                          Continue()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    if (false) {
+      break;
+    }
+    continue;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_discard_test.cc b/src/tint/writer/msl/generator_impl_discard_test.cc
new file mode 100644
index 0000000..0468ed2
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_discard_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Discard) {
+  auto* stmt = create<ast::DiscardStatement>();
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  discard_fragment();\n");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
new file mode 100644
index 0000000..bbf3546
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -0,0 +1,724 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Function) {
+  Func("my_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       {});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
+
+  using namespace metal;
+  void my_func() {
+    return;
+  }
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Function_WithParams) {
+  ast::VariableList params;
+  params.push_back(Param("a", ty.f32()));
+  params.push_back(Param("b", ty.i32()));
+
+  Func("my_func", params, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       {});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
+
+  using namespace metal;
+  void my_func(float a, int b) {
+    return;
+  }
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_NoReturn_Void) {
+  Func("main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{/* no explicit return */},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+fragment void main() {
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_WithInOutVars) {
+  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
+  //   return foo;
+  // }
+  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
+  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  float foo [[user(locn0)]];
+};
+
+struct tint_symbol_2 {
+  float value [[color(1)]];
+};
+
+float frag_main_inner(float foo) {
+  return foo;
+}
+
+fragment tint_symbol_2 frag_main(tint_symbol_1 tint_symbol [[stage_in]]) {
+  float const inner_result = frag_main_inner(tint_symbol.foo);
+  tint_symbol_2 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_WithInOut_Builtins) {
+  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
+  //   return coord.x;
+  // }
+  auto* coord_in =
+      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
+       {Return(MemberAccessor("coord", "x"))},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kFragDepth)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  float value [[depth(any)]];
+};
+
+float frag_main_inner(float4 coord) {
+  return coord[0];
+}
+
+fragment tint_symbol frag_main(float4 coord [[position]]) {
+  float const inner_result = frag_main_inner(coord);
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
+  // struct Interface {
+  //   @location(1) col1 : f32;
+  //   @location(2) col2 : f32;
+  //   @builtin(position) pos : vec4<f32>;
+  // };
+  // fn vert_main() -> Interface {
+  //   return Interface(0.4, 0.6, vec4<f32>());
+  // }
+  // fn frag_main(colors : Interface) {
+  //   const r = colors.col1;
+  //   const g = colors.col2;
+  // }
+  auto* interface_struct = Structure(
+      "Interface",
+      {
+          Member("col1", ty.f32(), {Location(1)}),
+          Member("col2", ty.f32(), {Location(2)}),
+          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+      });
+
+  Func("vert_main", {}, ty.Of(interface_struct),
+       {Return(Construct(ty.Of(interface_struct), Expr(0.5f), Expr(0.25f),
+                         Construct(ty.vec4<f32>())))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("frag_main", {Param("colors", ty.Of(interface_struct))}, ty.void_(),
+       {
+           WrapInStatement(
+               Const("r", ty.f32(), MemberAccessor("colors", "col1"))),
+           WrapInStatement(
+               Const("g", ty.f32(), MemberAccessor("colors", "col2"))),
+       },
+       {Stage(ast::PipelineStage::kFragment)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Interface {
+  float col1;
+  float col2;
+  float4 pos;
+};
+
+struct tint_symbol {
+  float col1 [[user(locn1)]];
+  float col2 [[user(locn2)]];
+  float4 pos [[position]];
+};
+
+Interface vert_main_inner() {
+  Interface const tint_symbol_3 = {.col1=0.5f, .col2=0.25f, .pos=float4()};
+  return tint_symbol_3;
+}
+
+vertex tint_symbol vert_main() {
+  Interface const inner_result = vert_main_inner();
+  tint_symbol wrapper_result = {};
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  float col1 [[user(locn1)]];
+  float col2 [[user(locn2)]];
+};
+
+void frag_main_inner(Interface colors) {
+  float const r = colors.col1;
+  float const g = colors.col2;
+}
+
+fragment void frag_main(float4 pos [[position]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  Interface const tint_symbol_4 = {.col1=tint_symbol_1.col1, .col2=tint_symbol_1.col2, .pos=pos};
+  frag_main_inner(tint_symbol_4);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
+  // struct VertexOutput {
+  //   @builtin(position) pos : vec4<f32>;
+  // };
+  // fn foo(x : f32) -> VertexOutput {
+  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
+  // }
+  // fn vert_main1() -> VertexOutput {
+  //   return foo(0.5);
+  // }
+  // fn vert_main2() -> VertexOutput {
+  //   return foo(0.25);
+  // }
+  auto* vertex_output_struct = Structure(
+      "VertexOutput",
+      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+
+  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
+       {});
+
+  Func("vert_main1", {}, ty.Of(vertex_output_struct),
+       {Return(Expr(Call("foo", Expr(0.5f))))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  Func("vert_main2", {}, ty.Of(vertex_output_struct),
+       {Return(Expr(Call("foo", Expr(0.25f))))},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct VertexOutput {
+  float4 pos;
+};
+
+VertexOutput foo(float x) {
+  VertexOutput const tint_symbol_2 = {.pos=float4(x, x, x, 1.0f)};
+  return tint_symbol_2;
+}
+
+struct tint_symbol {
+  float4 pos [[position]];
+};
+
+VertexOutput vert_main1_inner() {
+  return foo(0.5f);
+}
+
+vertex tint_symbol vert_main1() {
+  VertexOutput const inner_result = vert_main1_inner();
+  tint_symbol wrapper_result = {};
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  float4 pos [[position]];
+};
+
+VertexOutput vert_main2_inner() {
+  return foo(0.25f);
+}
+
+vertex tint_symbol_1 vert_main2() {
+  VertexOutput const inner_result_1 = vert_main2_inner();
+  tint_symbol_1 wrapper_result_1 = {};
+  wrapper_result_1.pos = inner_result_1.pos;
+  return wrapper_result_1;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionAttribute_EntryPoint_With_RW_StorageBuffer) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Data {
+  /* 0x0000 */ int a;
+  /* 0x0004 */ float b;
+};
+
+fragment void frag_main(device Data* tint_symbol [[buffer(0)]]) {
+  float v = (*(tint_symbol)).b;
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionAttribute_EntryPoint_With_RO_StorageBuffer) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                  MemberAccessor("coord", "b"));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Data {
+  /* 0x0000 */ int a;
+  /* 0x0004 */ float b;
+};
+
+fragment void frag_main(const device Data* tint_symbol [[buffer(0)]]) {
+  float v = (*(tint_symbol)).b;
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
+  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
+                           {create<ast::StructBlockAttribute>()});
+  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(0),
+                     });
+
+  Func("sub_func",
+       {
+           Param("param", ty.f32()),
+       },
+       ty.f32(),
+       {
+           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
+       });
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", {}, ty.void_(),
+       {
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct UBO {
+  /* 0x0000 */ float4 coord;
+};
+
+float sub_func(float param, const constant UBO* const tint_symbol) {
+  return (*(tint_symbol)).coord[0];
+}
+
+fragment void frag_main(const constant UBO* tint_symbol_1 [[buffer(0)]]) {
+  float v = sub_func(1.0f, tint_symbol_1);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionAttribute_Called_By_EntryPoint_With_RW_StorageBuffer) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ast::VariableList params;
+  params.push_back(Param("param", ty.f32()));
+
+  auto body = ast::StatementList{Return(MemberAccessor("coord", "b"))};
+
+  Func("sub_func", params, ty.f32(), body, {});
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Data {
+  /* 0x0000 */ int a;
+  /* 0x0004 */ float b;
+};
+
+float sub_func(float param, device Data* const tint_symbol) {
+  return (*(tint_symbol)).b;
+}
+
+fragment void frag_main(device Data* tint_symbol_1 [[buffer(0)]]) {
+  float v = sub_func(1.0f, tint_symbol_1);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionAttribute_Called_By_EntryPoint_With_RO_StorageBuffer) {
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  ast::VariableList params;
+  params.push_back(Param("param", ty.f32()));
+
+  auto body = ast::StatementList{Return(MemberAccessor("coord", "b"))};
+
+  Func("sub_func", params, ty.f32(), body, {});
+
+  auto* var =
+      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
+
+  Func("frag_main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(var),
+           Return(),
+       },
+       {
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Data {
+  /* 0x0000 */ int a;
+  /* 0x0004 */ float b;
+};
+
+float sub_func(float param, const device Data* const tint_symbol) {
+  return (*(tint_symbol)).b;
+}
+
+fragment void frag_main(const device Data* tint_symbol_1 [[buffer(0)]]) {
+  float v = sub_func(1.0f, tint_symbol_1);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayParams) {
+  ast::VariableList params;
+  params.push_back(Param("a", ty.array<f32, 5>()));
+
+  Func("my_func", params, ty.void_(),
+       {
+           Return(),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
+
+  using namespace metal;
+  struct tint_array_wrapper {
+    float arr[5];
+  };
+
+  void my_func(tint_array_wrapper a) {
+    return;
+  }
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayReturn) {
+  Func("my_func", {}, ty.array<f32, 5>(),
+       {
+           Return(Construct(ty.array<f32, 5>())),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
+
+  using namespace metal;
+  struct tint_array_wrapper {
+    float arr[5];
+  };
+
+  tint_array_wrapper my_func() {
+    tint_array_wrapper const tint_symbol = {.arr={}};
+    return tint_symbol;
+  }
+
+)");
+}
+
+// https://crbug.com/tint/297
+TEST_F(MslGeneratorImplTest,
+       Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
+  // [[block]] struct Data {
+  //   d : f32;
+  // };
+  // @binding(0) @group(0) var<storage> data : Data;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn a() {
+  //   return;
+  // }
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn b() {
+  //   return;
+  // }
+
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("a", ast::VariableList{}, ty.void_(),
+         ast::StatementList{
+             Decl(var),
+             Return(),
+         },
+         {
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1),
+         });
+  }
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("b", ast::VariableList{}, ty.void_(),
+         ast::StatementList{Decl(var), Return()},
+         {
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1),
+         });
+  }
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Data {
+  /* 0x0000 */ float d;
+};
+
+kernel void a(device Data* tint_symbol [[buffer(0)]]) {
+  float v = (*(tint_symbol)).d;
+  return;
+}
+
+kernel void b(device Data* tint_symbol_1 [[buffer(0)]]) {
+  float v = (*(tint_symbol_1)).d;
+  return;
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_identifier_test.cc b/src/tint/writer/msl/generator_impl_identifier_test.cc
new file mode 100644
index 0000000..d5e7559
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_identifier_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitIdentifierExpression) {
+  auto* foo = Var("foo", ty.i32());
+
+  auto* i = Expr("foo");
+  WrapInFunction(foo, i);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
+  EXPECT_EQ(out.str(), "foo");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_if_test.cc b/src/tint/writer/msl/generator_impl_if_test.cc
new file mode 100644
index 0000000..5ac9f83
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_if_test.cc
@@ -0,0 +1,106 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_If) {
+  auto* cond = Var("cond", ty.bool_());
+  auto* i = If(cond, Block(Return()));
+  WrapInFunction(cond, i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_IfWithElseIf) {
+  auto* cond = Var("cond", ty.bool_());
+  auto* else_cond = Var("else_cond", ty.bool_());
+  auto* i = If(cond, Block(Return()), Else(else_cond, Block(Return())));
+  WrapInFunction(cond, else_cond, i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_IfWithElse) {
+  auto* cond = Var("cond", ty.bool_());
+  auto* i = If(cond, Block(Return()), Else(nullptr, Block(Return())));
+  WrapInFunction(cond, i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_IfWithMultiple) {
+  auto* cond = Var("cond", ty.bool_());
+  auto* else_cond = Var("else_cond", ty.bool_());
+  auto* i = If(cond, Block(Return()), Else(else_cond, Block(Return())),
+               Else(nullptr, Block(Return())));
+  WrapInFunction(cond, else_cond, i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    if (else_cond) {
+      return;
+    } else {
+      return;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_import_test.cc b/src/tint/writer/msl/generator_impl_import_test.cc
new file mode 100644
index 0000000..0ed3fb8
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_import_test.cc
@@ -0,0 +1,258 @@
+// Copyright 2020 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/call.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+struct MslImportData {
+  const char* name;
+  const char* msl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, MslImportData data) {
+  out << data.name;
+  return out;
+}
+using MslImportData_SingleParamTest = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_SingleParamTest, FloatScalar) {
+  auto param = GetParam();
+  auto* call = Call(param.name, 1.f);
+
+  // The resolver will set the builtin data for the ident
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  auto* sem = program->Sem().Get(call);
+  ASSERT_NE(sem, nullptr);
+  auto* target = sem->Target();
+  ASSERT_NE(target, nullptr);
+  auto* builtin = target->As<sem::Builtin>();
+  ASSERT_NE(builtin, nullptr);
+
+  ASSERT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_SingleParamTest,
+                         testing::Values(MslImportData{"abs", "fabs"},
+                                         MslImportData{"acos", "acos"},
+                                         MslImportData{"asin", "asin"},
+                                         MslImportData{"atan", "atan"},
+                                         MslImportData{"ceil", "ceil"},
+                                         MslImportData{"cos", "cos"},
+                                         MslImportData{"cosh", "cosh"},
+                                         MslImportData{"exp", "exp"},
+                                         MslImportData{"exp2", "exp2"},
+                                         MslImportData{"floor", "floor"},
+                                         MslImportData{"fract", "fract"},
+                                         MslImportData{"inverseSqrt", "rsqrt"},
+                                         MslImportData{"length", "length"},
+                                         MslImportData{"log", "log"},
+                                         MslImportData{"log2", "log2"},
+                                         MslImportData{"round", "rint"},
+                                         MslImportData{"sign", "sign"},
+                                         MslImportData{"sin", "sin"},
+                                         MslImportData{"sinh", "sinh"},
+                                         MslImportData{"sqrt", "sqrt"},
+                                         MslImportData{"tan", "tan"},
+                                         MslImportData{"tanh", "tanh"},
+                                         MslImportData{"trunc", "trunc"}));
+
+TEST_F(MslGeneratorImplTest, MslImportData_SingleParamTest_IntScalar) {
+  auto* expr = Call("abs", 1);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), R"(abs(1))");
+}
+
+TEST_F(MslGeneratorImplTest, MslImportData_SingleParamTest_ScalarLength) {
+  auto* expr = Call("length", 2.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), R"(fabs(2.0f))");
+}
+
+using MslImportData_DualParam_ScalarTest = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_DualParam_ScalarTest, Float) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1.0f, 2.0f);
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1.0f, 2.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_DualParam_ScalarTest,
+                         testing::Values(MslImportData{"atan2", "atan2"},
+                                         MslImportData{"max", "fmax"},
+                                         MslImportData{"min", "fmin"},
+                                         MslImportData{"pow", "pow"},
+                                         MslImportData{"step", "step"}));
+
+TEST_F(MslGeneratorImplTest, MslImportData_DualParam_ScalarDistance) {
+  auto* expr = Call("distance", 2.f, 3.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), R"(fabs(2.0f - 3.0f))");
+}
+
+using MslImportData_DualParam_VectorTest = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_DualParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr =
+      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
+            std::string(param.msl_name) +
+                R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f)))");
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_DualParam_VectorTest,
+                         testing::Values(MslImportData{"atan2", "atan2"},
+                                         MslImportData{"cross", "cross"},
+                                         MslImportData{"distance", "distance"},
+                                         MslImportData{"max", "fmax"},
+                                         MslImportData{"min", "fmin"},
+                                         MslImportData{"pow", "pow"},
+                                         MslImportData{"reflect", "reflect"},
+                                         MslImportData{"step", "step"}));
+
+using MslImportData_DualParam_Int_Test = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_DualParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1, 2)");
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_DualParam_Int_Test,
+                         testing::Values(MslImportData{"max", "max"},
+                                         MslImportData{"min", "min"}));
+
+using MslImportData_TripleParam_ScalarTest = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_TripleParam_ScalarTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1.0f, 2.0f, 3.0f)");
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_TripleParam_ScalarTest,
+                         testing::Values(MslImportData{"fma", "fma"},
+                                         MslImportData{"mix", "mix"},
+                                         MslImportData{"clamp", "clamp"},
+                                         MslImportData{"smoothStep",
+                                                       "smoothstep"}));
+
+using MslImportData_TripleParam_VectorTest = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_TripleParam_VectorTest, Float) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
+                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(
+      out.str(),
+      std::string(param.msl_name) +
+          R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))");
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslImportData_TripleParam_VectorTest,
+    testing::Values(MslImportData{"faceForward", "faceforward"},
+                    MslImportData{"fma", "fma"},
+                    MslImportData{"clamp", "clamp"},
+                    MslImportData{"smoothStep", "smoothstep"}));
+
+using MslImportData_TripleParam_Int_Test = TestParamHelper<MslImportData>;
+TEST_P(MslImportData_TripleParam_Int_Test, IntScalar) {
+  auto param = GetParam();
+
+  auto* expr = Call(param.name, 1, 2, 3);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1, 2, 3)");
+}
+INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
+                         MslImportData_TripleParam_Int_Test,
+                         testing::Values(MslImportData{"clamp", "clamp"},
+                                         MslImportData{"clamp", "clamp"}));
+
+TEST_F(MslGeneratorImplTest, MslImportData_Determinant) {
+  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = Call("determinant", "var");
+
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string("determinant(var)"));
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_loop_test.cc b/src/tint/writer/msl/generator_impl_loop_test.cc
new file mode 100644
index 0000000..dc9b684
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_loop_test.cc
@@ -0,0 +1,352 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Loop) {
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block();
+  auto* l = Loop(body, continuing);
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard_fragment();
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* l = Loop(body, continuing);
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard_fragment();
+    {
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* inner = Loop(body, continuing);
+
+  body = Block(inner);
+
+  continuing = Block(Assign("lhs", "rhs"));
+
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    while (true) {
+      discard_fragment();
+      {
+        a_statement();
+      }
+    }
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_LoopWithVarUsedInContinuing) {
+  // loop {
+  //   var lhs : f32 = 2.4;
+  //   var other : f32;
+  //   continuing {
+  //     lhs = rhs
+  //   }
+  // }
+  //
+
+  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
+                     Decl(Var("other", ty.f32())),            //
+                     Break());
+
+  auto* continuing = Block(Assign("lhs", "rhs"));
+  auto* outer = Loop(body, continuing);
+  WrapInFunction(outer);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    float lhs = 2.400000095f;
+    float other = 0.0f;
+    break;
+    {
+      lhs = rhs;
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoop) {
+  // for(; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, nullptr, nullptr,  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(; ; ) {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInit) {
+  // for(var i : i32; ; ) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr,  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(int i = 0; ; ) {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
+  // fn f(i : i32) {}
+  //
+  // var<workgroup> a : atomic<i32>;
+  // for({f(1); f(2);}; ; ) {
+  //   return;
+  // }
+
+  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
+
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt = Block(f(1), f(2));
+  auto* loop = For(multi_stmt, nullptr, nullptr,  //
+                   Block(Return()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    {
+      f(1);
+      f(2);
+    }
+    for(; ; ) {
+      return;
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCond) {
+  // for(; true; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, true, nullptr,  //
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(; true; ) {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCont) {
+  // for(; ; i = i + 1) {
+  //   return;
+  // }
+
+  auto* v = Decl(Var("i", ty.i32()));
+  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)),  //
+                Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(
+      gen.result(),
+      R"(  for(; ; i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)))) {
+    return;
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtCont) {
+  // fn f(i : i32) {}
+  //
+  // var<workgroup> a : atomic<i32>;
+  // for(; ; { f(1); f(2); }) {
+  //   return;
+  // }
+
+  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
+
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt = Block(f(1), f(2));
+  auto* loop = For(nullptr, nullptr, multi_stmt,  //
+                   Block(Return()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
+    return;
+    {
+      f(1);
+      f(2);
+    }
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInitCondCont) {
+  // for(var i : i32; true; i = i + 1) {
+  //   return;
+  // }
+
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
+                Block(CallStmt(Call("a_statement"))));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(
+      gen.result(),
+      R"(  for(int i = 0; true; i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)))) {
+    a_statement();
+  }
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInitCondCont) {
+  // fn f(i : i32) {}
+  //
+  // var<workgroup> a : atomic<i32>;
+  // for({ f(1); f(2); }; true; { f(3); f(4); }) {
+  //   return;
+  // }
+
+  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
+
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt_a = Block(f(1), f(2));
+  auto* multi_stmt_b = Block(f(3), f(4));
+  auto* loop = For(multi_stmt_a, Expr(true), multi_stmt_b,  //
+                   Block(Return()));
+  WrapInFunction(loop);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    {
+      f(1);
+      f(2);
+    }
+    while (true) {
+      if (!(true)) { break; }
+      return;
+      {
+        f(3);
+        f(4);
+      }
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_member_accessor_test.cc b/src/tint/writer/msl/generator_impl_member_accessor_test.cc
new file mode 100644
index 0000000..3fa3b05
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_member_accessor_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor) {
+  Global("str", ty.Of(Structure("my_str", {Member("mem", ty.f32())})),
+         ast::StorageClass::kPrivate);
+  auto* expr = MemberAccessor("str", "mem");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "str.mem");
+}
+
+TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor_Swizzle_xyz) {
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = MemberAccessor("my_vec", "xyz");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "float4(my_vec).xyz");
+}
+
+TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor_Swizzle_gbr) {
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+
+  auto* expr = MemberAccessor("my_vec", "gbr");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "float4(my_vec).gbr");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_module_constant_test.cc b/src/tint/writer/msl/generator_impl_module_constant_test.cc
new file mode 100644
index 0000000..e01b989
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_module_constant_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_ModuleConstant) {
+  auto* var =
+      GlobalConst("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), "constant float pos[3] = {1.0f, 2.0f, 3.0f};\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_SpecConstant) {
+  auto* var = Override("pos", ty.f32(), Expr(3.f),
+                       ast::AttributeList{
+                           Id(23),
+                       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), "constant float pos [[function_constant(23)]];\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_SpecConstant_NoId) {
+  auto* var_a = Override("a", ty.f32(), nullptr,
+                         ast::AttributeList{
+                             Id(0),
+                         });
+  auto* var_b = Override("b", ty.f32(), nullptr);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var_a)) << gen.error();
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var_b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(constant float a [[function_constant(0)]];
+constant float b [[function_constant(1)]];
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_return_test.cc b/src/tint/writer/msl/generator_impl_return_test.cc
new file mode 100644
index 0000000..1364306
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_return_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Return) {
+  auto* r = Return();
+  WrapInFunction(r);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return;\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_ReturnWithValue) {
+  auto* r = Return(123);
+  Func("f", {}, ty.i32(), {r});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return 123;\n");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_sanitizer_test.cc b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
new file mode 100644
index 0000000..66cf16a
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -0,0 +1,268 @@
+// 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 "gmock/gmock.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using MslSanitizerTest = TestHelper;
+
+TEST_F(MslSanitizerTest, Call_ArrayLength) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+struct my_struct {
+  float a[1];
+};
+
+fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
+  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 0u) / 4u);
+  return;
+}
+
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(MslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
+  auto* s = Structure("my_struct",
+                      {
+                          Member(0, "z", ty.f32()),
+                          Member(4, "a", ty.array<f32>(4)),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+struct my_struct {
+  float z;
+  float a[1];
+};
+
+fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
+  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 4u) / 4u);
+  return;
+}
+
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(MslSanitizerTest, Call_ArrayLength_ViaLets) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  auto* p = Const("p", nullptr, AddressOf("b"));
+  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(p),
+           Decl(p2),
+           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+                    Call("arrayLength", p2))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+struct my_struct {
+  float a[1];
+};
+
+fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
+  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 0u) / 4u);
+  return;
+}
+
+)";
+
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(0),
+         });
+  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(2),
+             create<ast::GroupAttribute>(0),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var(
+               "len", ty.u32(), ast::StorageClass::kNone,
+               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
+                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Options options;
+  options.array_length_from_uniform.ubo_binding = {0, 29};
+  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+      sem::BindingPoint{0, 1}, 7u);
+  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+      sem::BindingPoint{0, 2}, 2u);
+  GeneratorImpl& gen = SanitizeAndBuild(options);
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+
+  auto got = gen.result();
+  auto* expect = R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  /* 0x0000 */ uint4 buffer_size[2];
+};
+
+struct my_struct {
+  float a[1];
+};
+
+fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(29)]]) {
+  uint len = ((((*(tint_symbol_2)).buffer_size[1u][3u] - 0u) / 4u) + (((*(tint_symbol_2)).buffer_size[0u][2u] - 0u) / 4u));
+  return;
+}
+
+)";
+  EXPECT_EQ(expect, got);
+}
+
+TEST_F(MslSanitizerTest,
+       Call_ArrayLength_ArrayLengthFromUniformMissingBinding) {
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(0),
+         });
+  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(2),
+             create<ast::GroupAttribute>(0),
+         });
+
+  Func("a_func", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var(
+               "len", ty.u32(), ast::StorageClass::kNone,
+               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
+                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  Options options;
+  options.array_length_from_uniform.ubo_binding = {0, 29};
+  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+      sem::BindingPoint{0, 2}, 2u);
+  GeneratorImpl& gen = SanitizeAndBuild(options);
+
+  ASSERT_FALSE(gen.Generate());
+  EXPECT_THAT(gen.error(),
+              HasSubstr("Unable to translate builtin: arrayLength"));
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_switch_test.cc b/src/tint/writer/msl/generator_impl_switch_test.cc
new file mode 100644
index 0000000..dda99d0
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_switch_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_Switch) {
+  auto* cond = Var("cond", ty.i32());
+
+  auto* def_body = Block(create<ast::BreakStatement>());
+  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+
+  ast::CaseSelectorList case_val;
+  case_val.push_back(Expr(5));
+
+  auto* case_body = Block(create<ast::BreakStatement>());
+
+  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
+
+  ast::CaseStatementList body;
+  body.push_back(case_stmt);
+  body.push_back(def);
+
+  auto* s = create<ast::SwitchStatement>(Expr(cond), body);
+  WrapInFunction(cond, s);
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  switch(cond) {
+    case 5: {
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_test.cc b/src/tint/writer/msl/generator_impl_test.cc
new file mode 100644
index 0000000..01caf83
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_test.cc
@@ -0,0 +1,417 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Generate) {
+  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+kernel void my_func() {
+  return;
+}
+
+)");
+}
+
+struct MslBuiltinData {
+  ast::Builtin builtin;
+  const char* attribute_name;
+};
+inline std::ostream& operator<<(std::ostream& out, MslBuiltinData data) {
+  out << data.builtin;
+  return out;
+}
+using MslBuiltinConversionTest = TestParamHelper<MslBuiltinData>;
+TEST_P(MslBuiltinConversionTest, Emit) {
+  auto params = GetParam();
+
+  GeneratorImpl& gen = Build();
+
+  EXPECT_EQ(gen.builtin_to_attribute(params.builtin),
+            std::string(params.attribute_name));
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslBuiltinConversionTest,
+    testing::Values(MslBuiltinData{ast::Builtin::kPosition, "position"},
+                    MslBuiltinData{ast::Builtin::kVertexIndex, "vertex_id"},
+                    MslBuiltinData{ast::Builtin::kInstanceIndex, "instance_id"},
+                    MslBuiltinData{ast::Builtin::kFrontFacing, "front_facing"},
+                    MslBuiltinData{ast::Builtin::kFragDepth, "depth(any)"},
+                    MslBuiltinData{ast::Builtin::kLocalInvocationId,
+                                   "thread_position_in_threadgroup"},
+                    MslBuiltinData{ast::Builtin::kLocalInvocationIndex,
+                                   "thread_index_in_threadgroup"},
+                    MslBuiltinData{ast::Builtin::kGlobalInvocationId,
+                                   "thread_position_in_grid"},
+                    MslBuiltinData{ast::Builtin::kWorkgroupId,
+                                   "threadgroup_position_in_grid"},
+                    MslBuiltinData{ast::Builtin::kNumWorkgroups,
+                                   "threadgroups_per_grid"},
+                    MslBuiltinData{ast::Builtin::kSampleIndex, "sample_id"},
+                    MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"},
+                    MslBuiltinData{ast::Builtin::kPointSize, "point_size"}));
+
+TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
+  auto* out = Structure(
+      "Out", {Member("pos", ty.vec4<f32>(),
+                     {Builtin(ast::Builtin::kPosition), Invariant()})});
+  Func("vert_main", ast::VariableList{}, ty.Of(out),
+       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_TRUE(gen.HasInvariant());
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+#if __METAL_VERSION__ >= 210
+#define TINT_INVARIANT [[invariant]]
+#else
+#define TINT_INVARIANT
+#endif
+
+struct Out {
+  float4 pos [[position]] TINT_INVARIANT;
+};
+
+vertex Out vert_main() {
+  return {};
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, HasInvariantAttribute_False) {
+  auto* out = Structure("Out", {Member("pos", ty.vec4<f32>(),
+                                       {Builtin(ast::Builtin::kPosition)})});
+  Func("vert_main", ast::VariableList{}, ty.Of(out),
+       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_FALSE(gen.HasInvariant());
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Out {
+  float4 pos [[position]];
+};
+
+vertex Out vert_main() {
+  return {};
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, WorkgroupMatrix) {
+  Global("m", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
+  Func("comp_main", ast::VariableList{}, ty.void_(),
+       {Decl(Const("x", nullptr, Expr("m")))},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_3 {
+  float2x2 m;
+};
+
+void comp_main_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol) {
+  {
+    *(tint_symbol) = float2x2();
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float2x2 const x = *(tint_symbol);
+}
+
+kernel void comp_main(threadgroup tint_symbol_3* tint_symbol_2 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x2* const tint_symbol_1 = &((*(tint_symbol_2)).m);
+  comp_main_inner(local_invocation_index, tint_symbol_1);
+  return;
+}
+
+)");
+
+  auto allocations = gen.DynamicWorkgroupAllocations();
+  ASSERT_TRUE(allocations.count("comp_main"));
+  ASSERT_EQ(allocations["comp_main"].size(), 1u);
+  EXPECT_EQ(allocations["comp_main"][0], 2u * 2u * sizeof(float));
+}
+
+TEST_F(MslGeneratorImplTest, WorkgroupMatrixInArray) {
+  Global("m", ty.array(ty.mat2x2<f32>(), 4), ast::StorageClass::kWorkgroup);
+  Func("comp_main", ast::VariableList{}, ty.void_(),
+       {Decl(Const("x", nullptr, Expr("m")))},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper {
+  float2x2 arr[4];
+};
+
+struct tint_symbol_3 {
+  tint_array_wrapper m;
+};
+
+void comp_main_inner(uint local_invocation_index, threadgroup tint_array_wrapper* const tint_symbol) {
+  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    uint const i = idx;
+    (*(tint_symbol)).arr[i] = float2x2();
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  tint_array_wrapper const x = *(tint_symbol);
+}
+
+kernel void comp_main(threadgroup tint_symbol_3* tint_symbol_2 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup tint_array_wrapper* const tint_symbol_1 = &((*(tint_symbol_2)).m);
+  comp_main_inner(local_invocation_index, tint_symbol_1);
+  return;
+}
+
+)");
+
+  auto allocations = gen.DynamicWorkgroupAllocations();
+  ASSERT_TRUE(allocations.count("comp_main"));
+  ASSERT_EQ(allocations["comp_main"].size(), 1u);
+  EXPECT_EQ(allocations["comp_main"][0], 4u * 2u * 2u * sizeof(float));
+}
+
+TEST_F(MslGeneratorImplTest, WorkgroupMatrixInStruct) {
+  Structure("S1", {
+                      Member("m1", ty.mat2x2<f32>()),
+                      Member("m2", ty.mat4x4<f32>()),
+                  });
+  Structure("S2", {
+                      Member("s", ty.type_name("S1")),
+                  });
+  Global("s", ty.type_name("S2"), ast::StorageClass::kWorkgroup);
+  Func("comp_main", ast::VariableList{}, ty.void_(),
+       {Decl(Const("x", nullptr, Expr("s")))},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct S1 {
+  float2x2 m1;
+  float4x4 m2;
+};
+
+struct S2 {
+  S1 s;
+};
+
+struct tint_symbol_4 {
+  S2 s;
+};
+
+void comp_main_inner(uint local_invocation_index, threadgroup S2* const tint_symbol_1) {
+  {
+    S2 const tint_symbol = {};
+    *(tint_symbol_1) = tint_symbol;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  S2 const x = *(tint_symbol_1);
+}
+
+kernel void comp_main(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup S2* const tint_symbol_2 = &((*(tint_symbol_3)).s);
+  comp_main_inner(local_invocation_index, tint_symbol_2);
+  return;
+}
+
+)");
+
+  auto allocations = gen.DynamicWorkgroupAllocations();
+  ASSERT_TRUE(allocations.count("comp_main"));
+  ASSERT_EQ(allocations["comp_main"].size(), 1u);
+  EXPECT_EQ(allocations["comp_main"][0],
+            (2 * 2 * sizeof(float)) + (4u * 4u * sizeof(float)));
+}
+
+TEST_F(MslGeneratorImplTest, WorkgroupMatrix_Multiples) {
+  Global("m1", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m2", ty.mat2x3<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m3", ty.mat2x4<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m4", ty.mat3x2<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m5", ty.mat3x3<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m6", ty.mat3x4<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m7", ty.mat4x2<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m8", ty.mat4x3<f32>(), ast::StorageClass::kWorkgroup);
+  Global("m9", ty.mat4x4<f32>(), ast::StorageClass::kWorkgroup);
+  Func("main1", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Const("a1", nullptr, Expr("m1"))),
+           Decl(Const("a2", nullptr, Expr("m2"))),
+           Decl(Const("a3", nullptr, Expr("m3"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  Func("main2", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Const("a1", nullptr, Expr("m4"))),
+           Decl(Const("a2", nullptr, Expr("m5"))),
+           Decl(Const("a3", nullptr, Expr("m6"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  Func("main3", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Const("a1", nullptr, Expr("m7"))),
+           Decl(Const("a2", nullptr, Expr("m8"))),
+           Decl(Const("a3", nullptr, Expr("m9"))),
+       },
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+  Func("main4_no_usages", ast::VariableList{}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_7 {
+  float2x2 m1;
+  float2x3 m2;
+  float2x4 m3;
+};
+
+struct tint_symbol_15 {
+  float3x2 m4;
+  float3x3 m5;
+  float3x4 m6;
+};
+
+struct tint_symbol_23 {
+  float4x2 m7;
+  float4x3 m8;
+  float4x4 m9;
+};
+
+void main1_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol, threadgroup float2x3* const tint_symbol_1, threadgroup float2x4* const tint_symbol_2) {
+  {
+    *(tint_symbol) = float2x2();
+    *(tint_symbol_1) = float2x3();
+    *(tint_symbol_2) = float2x4();
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float2x2 const a1 = *(tint_symbol);
+  float2x3 const a2 = *(tint_symbol_1);
+  float2x4 const a3 = *(tint_symbol_2);
+}
+
+kernel void main1(threadgroup tint_symbol_7* tint_symbol_4 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x2* const tint_symbol_3 = &((*(tint_symbol_4)).m1);
+  threadgroup float2x3* const tint_symbol_5 = &((*(tint_symbol_4)).m2);
+  threadgroup float2x4* const tint_symbol_6 = &((*(tint_symbol_4)).m3);
+  main1_inner(local_invocation_index, tint_symbol_3, tint_symbol_5, tint_symbol_6);
+  return;
+}
+
+void main2_inner(uint local_invocation_index_1, threadgroup float3x2* const tint_symbol_8, threadgroup float3x3* const tint_symbol_9, threadgroup float3x4* const tint_symbol_10) {
+  {
+    *(tint_symbol_8) = float3x2();
+    *(tint_symbol_9) = float3x3();
+    *(tint_symbol_10) = float3x4();
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float3x2 const a1 = *(tint_symbol_8);
+  float3x3 const a2 = *(tint_symbol_9);
+  float3x4 const a3 = *(tint_symbol_10);
+}
+
+kernel void main2(threadgroup tint_symbol_15* tint_symbol_12 [[threadgroup(0)]], uint local_invocation_index_1 [[thread_index_in_threadgroup]]) {
+  threadgroup float3x2* const tint_symbol_11 = &((*(tint_symbol_12)).m4);
+  threadgroup float3x3* const tint_symbol_13 = &((*(tint_symbol_12)).m5);
+  threadgroup float3x4* const tint_symbol_14 = &((*(tint_symbol_12)).m6);
+  main2_inner(local_invocation_index_1, tint_symbol_11, tint_symbol_13, tint_symbol_14);
+  return;
+}
+
+void main3_inner(uint local_invocation_index_2, threadgroup float4x2* const tint_symbol_16, threadgroup float4x3* const tint_symbol_17, threadgroup float4x4* const tint_symbol_18) {
+  {
+    *(tint_symbol_16) = float4x2();
+    *(tint_symbol_17) = float4x3();
+    *(tint_symbol_18) = float4x4();
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float4x2 const a1 = *(tint_symbol_16);
+  float4x3 const a2 = *(tint_symbol_17);
+  float4x4 const a3 = *(tint_symbol_18);
+}
+
+kernel void main3(threadgroup tint_symbol_23* tint_symbol_20 [[threadgroup(0)]], uint local_invocation_index_2 [[thread_index_in_threadgroup]]) {
+  threadgroup float4x2* const tint_symbol_19 = &((*(tint_symbol_20)).m7);
+  threadgroup float4x3* const tint_symbol_21 = &((*(tint_symbol_20)).m8);
+  threadgroup float4x4* const tint_symbol_22 = &((*(tint_symbol_20)).m9);
+  main3_inner(local_invocation_index_2, tint_symbol_19, tint_symbol_21, tint_symbol_22);
+  return;
+}
+
+kernel void main4_no_usages() {
+  return;
+}
+
+)");
+
+  auto allocations = gen.DynamicWorkgroupAllocations();
+  ASSERT_TRUE(allocations.count("main1"));
+  ASSERT_TRUE(allocations.count("main2"));
+  ASSERT_TRUE(allocations.count("main3"));
+  EXPECT_EQ(allocations.count("main4_no_usages"), 0u);
+  ASSERT_EQ(allocations["main1"].size(), 1u);
+  EXPECT_EQ(allocations["main1"][0], 20u * sizeof(float));
+  ASSERT_EQ(allocations["main2"].size(), 1u);
+  EXPECT_EQ(allocations["main2"][0], 32u * sizeof(float));
+  ASSERT_EQ(allocations["main3"].size(), 1u);
+  EXPECT_EQ(allocations["main3"][0], 40u * sizeof(float));
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_type_test.cc b/src/tint/writer/msl/generator_impl_type_test.cc
new file mode 100644
index 0000000..5f2298b
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_type_test.cc
@@ -0,0 +1,883 @@
+// Copyright 2020 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 <array>
+
+#include "gmock/gmock.h"
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/sampler_type.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using ::testing::HasSubstr;
+
+#define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN)    \
+  static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \
+  static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")
+
+// Declare C++ types that match the size and alignment of the types of the same
+// name in MSL.
+#define DECLARE_TYPE(NAME, SIZE, ALIGN) \
+  struct alignas(ALIGN) NAME {          \
+    uint8_t _[SIZE];                    \
+  };                                    \
+  CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)
+
+// Size and alignments taken from the MSL spec:
+// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+DECLARE_TYPE(float2, 8, 8);
+DECLARE_TYPE(packed_float3, 12, 4);
+DECLARE_TYPE(float4, 16, 16);
+DECLARE_TYPE(float2x2, 16, 8);
+DECLARE_TYPE(float2x3, 32, 16);
+DECLARE_TYPE(float2x4, 32, 16);
+DECLARE_TYPE(float3x2, 24, 8);
+DECLARE_TYPE(float3x3, 48, 16);
+DECLARE_TYPE(float3x4, 48, 16);
+DECLARE_TYPE(float4x2, 32, 8);
+DECLARE_TYPE(float4x3, 64, 16);
+DECLARE_TYPE(float4x4, 64, 16);
+using uint = unsigned int;
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[4]");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArray) {
+  auto* a = ty.array<bool, 4>();
+  auto* b = ty.array(a, 5);
+  Global("G", b, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(b), "ary")) << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[5][4]");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
+  auto* a = ty.array<bool, 4>();
+  auto* b = ty.array(a, 5);
+  auto* c = ty.array(b, 6);
+  Global("G", c, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(c), "ary")) << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "")) << gen.error();
+  EXPECT_EQ(out.str(), "bool[4]");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
+  auto* arr = ty.array<bool, 1>();
+  Global("G", arr, ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
+  EXPECT_EQ(out.str(), "bool ary[1]");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_ArrayWithStride) {
+  auto* s = Structure("s", {Member("arr", ty.array<f32, 4>(64))},
+                      {create<ast::StructBlockAttribute>()});
+  auto* ubo = Global("ubo", ty.Of(s), ast::StorageClass::kUniform,
+                     ast::AttributeList{
+                         create<ast::GroupAttribute>(0),
+                         create<ast::BindingAttribute>(1),
+                     });
+  WrapInFunction(MemberAccessor(ubo, "arr"));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct tint_padded_array_element {
+  /* 0x0000 */ float el;
+  /* 0x0004 */ int8_t tint_pad[60];
+};)"));
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct tint_array_wrapper {
+  /* 0x0000 */ tint_padded_array_element arr[4];
+};)"));
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct s {
+  /* 0x0000 */ tint_array_wrapper arr;
+};)"));
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Bool) {
+  auto* bool_ = create<sem::Bool>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, bool_, "")) << gen.error();
+  EXPECT_EQ(out.str(), "bool");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_F32) {
+  auto* f32 = create<sem::F32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, f32, "")) << gen.error();
+  EXPECT_EQ(out.str(), "float");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_I32) {
+  auto* i32 = create<sem::I32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, i32, "")) << gen.error();
+  EXPECT_EQ(out.str(), "int");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Matrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, mat2x3, "")) << gen.error();
+  EXPECT_EQ(out.str(), "float2x3");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Pointer) {
+  auto* f32 = create<sem::F32>();
+  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
+                                 ast::Access::kReadWrite);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, p, "")) << gen.error();
+  EXPECT_EQ(out.str(), "threadgroup float* ");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Struct) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
+  EXPECT_EQ(out.str(), "S");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  int a;
+  float b;
+};
+)");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
+  auto* s =
+      Structure("S",
+                {
+                    Member("a", ty.i32(), {MemberSize(32)}),
+                    Member("b", ty.f32(), {MemberAlign(128), MemberSize(128)}),
+                    Member("c", ty.vec2<f32>()),
+                    Member("d", ty.u32()),
+                    Member("e", ty.vec3<f32>()),
+                    Member("f", ty.u32()),
+                    Member("g", ty.vec4<f32>()),
+                    Member("h", ty.u32()),
+                    Member("i", ty.mat2x2<f32>()),
+                    Member("j", ty.u32()),
+                    Member("k", ty.mat2x3<f32>()),
+                    Member("l", ty.u32()),
+                    Member("m", ty.mat2x4<f32>()),
+                    Member("n", ty.u32()),
+                    Member("o", ty.mat3x2<f32>()),
+                    Member("p", ty.u32()),
+                    Member("q", ty.mat3x3<f32>()),
+                    Member("r", ty.u32()),
+                    Member("s", ty.mat3x4<f32>()),
+                    Member("t", ty.u32()),
+                    Member("u", ty.mat4x2<f32>()),
+                    Member("v", ty.u32()),
+                    Member("w", ty.mat4x3<f32>()),
+                    Member("x", ty.u32()),
+                    Member("y", ty.mat4x4<f32>()),
+                    Member("z", ty.f32()),
+                },
+                {create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+
+  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
+  // for each field of the structure s.
+#define ALL_FIELDS()                             \
+  FIELD(0x0000, int, a, /*NO SUFFIX*/)           \
+  FIELD(0x0004, int8_t, tint_pad, [124])         \
+  FIELD(0x0080, float, b, /*NO SUFFIX*/)         \
+  FIELD(0x0084, int8_t, tint_pad_1, [124])       \
+  FIELD(0x0100, float2, c, /*NO SUFFIX*/)        \
+  FIELD(0x0108, uint, d, /*NO SUFFIX*/)          \
+  FIELD(0x010c, int8_t, tint_pad_2, [4])         \
+  FIELD(0x0110, packed_float3, e, /*NO SUFFIX*/) \
+  FIELD(0x011c, uint, f, /*NO SUFFIX*/)          \
+  FIELD(0x0120, float4, g, /*NO SUFFIX*/)        \
+  FIELD(0x0130, uint, h, /*NO SUFFIX*/)          \
+  FIELD(0x0134, int8_t, tint_pad_3, [4])         \
+  FIELD(0x0138, float2x2, i, /*NO SUFFIX*/)      \
+  FIELD(0x0148, uint, j, /*NO SUFFIX*/)          \
+  FIELD(0x014c, int8_t, tint_pad_4, [4])         \
+  FIELD(0x0150, float2x3, k, /*NO SUFFIX*/)      \
+  FIELD(0x0170, uint, l, /*NO SUFFIX*/)          \
+  FIELD(0x0174, int8_t, tint_pad_5, [12])        \
+  FIELD(0x0180, float2x4, m, /*NO SUFFIX*/)      \
+  FIELD(0x01a0, uint, n, /*NO SUFFIX*/)          \
+  FIELD(0x01a4, int8_t, tint_pad_6, [4])         \
+  FIELD(0x01a8, float3x2, o, /*NO SUFFIX*/)      \
+  FIELD(0x01c0, uint, p, /*NO SUFFIX*/)          \
+  FIELD(0x01c4, int8_t, tint_pad_7, [12])        \
+  FIELD(0x01d0, float3x3, q, /*NO SUFFIX*/)      \
+  FIELD(0x0200, uint, r, /*NO SUFFIX*/)          \
+  FIELD(0x0204, int8_t, tint_pad_8, [12])        \
+  FIELD(0x0210, float3x4, s, /*NO SUFFIX*/)      \
+  FIELD(0x0240, uint, t, /*NO SUFFIX*/)          \
+  FIELD(0x0244, int8_t, tint_pad_9, [4])         \
+  FIELD(0x0248, float4x2, u, /*NO SUFFIX*/)      \
+  FIELD(0x0268, uint, v, /*NO SUFFIX*/)          \
+  FIELD(0x026c, int8_t, tint_pad_10, [4])        \
+  FIELD(0x0270, float4x3, w, /*NO SUFFIX*/)      \
+  FIELD(0x02b0, uint, x, /*NO SUFFIX*/)          \
+  FIELD(0x02b4, int8_t, tint_pad_11, [12])       \
+  FIELD(0x02c0, float4x4, y, /*NO SUFFIX*/)      \
+  FIELD(0x0300, float, z, /*NO SUFFIX*/)         \
+  FIELD(0x0304, int8_t, tint_pad_12, [124])
+
+  // Check that the generated string is as expected.
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
+  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
+#undef FIELD
+  EXPECT_EQ(buf.String(), expect);
+
+  // 1.4 Metal and C++14
+  // The Metal programming language is a C++14-based Specification with
+  // extensions and restrictions. Refer to the C++14 Specification (also known
+  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+  // description of the language grammar.
+  //
+  // Tint is written in C++14, so use the compiler to verify the generated
+  // layout is as expected for C++14 / MSL.
+  {
+    struct S {
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
+      ALL_FIELDS()
+#undef FIELD
+    };
+
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+    ALL_FIELDS()
+#undef FIELD
+  }
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
+  // inner_x: size(1024), align(512)
+  auto* inner_x =
+      Structure("inner_x", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32(), {MemberAlign(512)}),
+                           });
+
+  // inner_y: size(516), align(4)
+  auto* inner_y =
+      Structure("inner_y", {
+                               Member("a", ty.i32(), {MemberSize(512)}),
+                               Member("b", ty.f32()),
+                           });
+
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.Of(inner_x)),
+                          Member("c", ty.f32()),
+                          Member("d", ty.Of(inner_y)),
+                          Member("e", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+
+  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
+  // for each field of the structure s.
+#define ALL_FIELDS()                       \
+  FIELD(0x0000, int, a, /*NO SUFFIX*/)     \
+  FIELD(0x0004, int8_t, tint_pad, [508])   \
+  FIELD(0x0200, inner_x, b, /*NO SUFFIX*/) \
+  FIELD(0x0600, float, c, /*NO SUFFIX*/)   \
+  FIELD(0x0604, inner_y, d, /*NO SUFFIX*/) \
+  FIELD(0x0808, float, e, /*NO SUFFIX*/)   \
+  FIELD(0x080c, int8_t, tint_pad_1, [500])
+
+  // Check that the generated string is as expected.
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
+  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
+#undef FIELD
+  EXPECT_EQ(buf.String(), expect);
+
+  // 1.4 Metal and C++14
+  // The Metal programming language is a C++14-based Specification with
+  // extensions and restrictions. Refer to the C++14 Specification (also known
+  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+  // description of the language grammar.
+  //
+  // Tint is written in C++14, so use the compiler to verify the generated
+  // layout is as expected for C++14 / MSL.
+  {
+    struct inner_x {
+      uint32_t a;
+      alignas(512) float b;
+    };
+    CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);
+
+    struct inner_y {
+      uint32_t a[128];
+      float b;
+    };
+    CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);
+
+    struct S {
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
+      ALL_FIELDS()
+#undef FIELD
+    };
+
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+    ALL_FIELDS()
+#undef FIELD
+  }
+
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
+  // inner: size(1024), align(512)
+  auto* inner =
+      Structure("inner", {
+                             Member("a", ty.i32()),
+                             Member("b", ty.f32(), {MemberAlign(512)}),
+                         });
+
+  // array_x: size(28), align(4)
+  auto* array_x = ty.array<f32, 7>();
+
+  // array_y: size(4096), align(512)
+  auto* array_y = ty.array(ty.Of(inner), 4);
+
+  // array_z: size(4), align(4)
+  auto* array_z = ty.array<f32>();
+
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", array_x),
+                          Member("c", ty.f32()),
+                          Member("d", array_y),
+                          Member("e", ty.f32()),
+                          Member("f", array_z),
+                      },
+                      ast::AttributeList{create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+
+  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
+  // for each field of the structure s.
+#define ALL_FIELDS()                     \
+  FIELD(0x0000, int, a, /*NO SUFFIX*/)   \
+  FIELD(0x0004, float, b, [7])           \
+  FIELD(0x0020, float, c, /*NO SUFFIX*/) \
+  FIELD(0x0024, int8_t, tint_pad, [476]) \
+  FIELD(0x0200, inner, d, [4])           \
+  FIELD(0x1200, float, e, /*NO SUFFIX*/) \
+  FIELD(0x1204, float, f, [1])           \
+  FIELD(0x1208, int8_t, tint_pad_1, [504])
+
+  // Check that the generated string is as expected.
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
+  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
+#undef FIELD
+  EXPECT_EQ(buf.String(), expect);
+
+  // 1.4 Metal and C++14
+  // The Metal programming language is a C++14-based Specification with
+  // extensions and restrictions. Refer to the C++14 Specification (also known
+  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+  // description of the language grammar.
+  //
+  // Tint is written in C++14, so use the compiler to verify the generated
+  // layout is as expected for C++14 / MSL.
+  {
+    struct inner {
+      uint32_t a;
+      alignas(512) float b;
+    };
+    CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
+
+    // array_x: size(28), align(4)
+    using array_x = std::array<float, 7>;
+    CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
+
+    // array_y: size(4096), align(512)
+    using array_y = std::array<inner, 4>;
+    CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
+
+    // array_z: size(4), align(4)
+    using array_z = std::array<float, 1>;
+    CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);
+
+    struct S {
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
+      ALL_FIELDS()
+#undef FIELD
+    };
+
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+    ALL_FIELDS()
+#undef FIELD
+  }
+
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
+  // array: size(64), align(16)
+  auto* array = ty.array(ty.vec3<f32>(), 4);
+
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", array),
+                          Member("c", ty.i32()),
+                      },
+                      ast::AttributeList{create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+
+  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
+  // for each field of the structure s.
+#define ALL_FIELDS()                    \
+  FIELD(0x0000, int, a, /*NO SUFFIX*/)  \
+  FIELD(0x0004, int8_t, tint_pad, [12]) \
+  FIELD(0x0010, float3, b, [4])         \
+  FIELD(0x0050, int, c, /*NO SUFFIX*/)  \
+  FIELD(0x0054, int8_t, tint_pad_1, [12])
+
+  // Check that the generated string is as expected.
+#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
+  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
+  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
+#undef FIELD
+  EXPECT_EQ(buf.String(), expect);
+}
+
+TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
+  auto* s = Structure(
+      "S",
+      {
+          // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
+          Member("tint_pad_2", ty.i32(), {MemberSize(32)}),
+          Member("tint_pad_20", ty.f32(), {MemberAlign(128), MemberSize(128)}),
+          Member("tint_pad_33", ty.vec2<f32>()),
+          Member("tint_pad_1", ty.u32()),
+          Member("tint_pad_3", ty.vec3<f32>()),
+          Member("tint_pad_7", ty.u32()),
+          Member("tint_pad_25", ty.vec4<f32>()),
+          Member("tint_pad_5", ty.u32()),
+          Member("tint_pad_27", ty.mat2x2<f32>()),
+          Member("tint_pad_24", ty.u32()),
+          Member("tint_pad_23", ty.mat2x3<f32>()),
+          Member("tint_pad", ty.u32()),
+          Member("tint_pad_8", ty.mat2x4<f32>()),
+          Member("tint_pad_26", ty.u32()),
+          Member("tint_pad_29", ty.mat3x2<f32>()),
+          Member("tint_pad_6", ty.u32()),
+          Member("tint_pad_22", ty.mat3x3<f32>()),
+          Member("tint_pad_32", ty.u32()),
+          Member("tint_pad_34", ty.mat3x4<f32>()),
+          Member("tint_pad_35", ty.u32()),
+          Member("tint_pad_30", ty.mat4x2<f32>()),
+          Member("tint_pad_9", ty.u32()),
+          Member("tint_pad_31", ty.mat4x3<f32>()),
+          Member("tint_pad_28", ty.u32()),
+          Member("tint_pad_4", ty.mat4x4<f32>()),
+          Member("tint_pad_21", ty.f32()),
+      },
+      {create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  TextGenerator::TextBuffer buf;
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
+  EXPECT_EQ(buf.String(), R"(struct S {
+  /* 0x0000 */ int tint_pad_2;
+  /* 0x0004 */ int8_t tint_pad_10[124];
+  /* 0x0080 */ float tint_pad_20;
+  /* 0x0084 */ int8_t tint_pad_11[124];
+  /* 0x0100 */ float2 tint_pad_33;
+  /* 0x0108 */ uint tint_pad_1;
+  /* 0x010c */ int8_t tint_pad_12[4];
+  /* 0x0110 */ packed_float3 tint_pad_3;
+  /* 0x011c */ uint tint_pad_7;
+  /* 0x0120 */ float4 tint_pad_25;
+  /* 0x0130 */ uint tint_pad_5;
+  /* 0x0134 */ int8_t tint_pad_13[4];
+  /* 0x0138 */ float2x2 tint_pad_27;
+  /* 0x0148 */ uint tint_pad_24;
+  /* 0x014c */ int8_t tint_pad_14[4];
+  /* 0x0150 */ float2x3 tint_pad_23;
+  /* 0x0170 */ uint tint_pad;
+  /* 0x0174 */ int8_t tint_pad_15[12];
+  /* 0x0180 */ float2x4 tint_pad_8;
+  /* 0x01a0 */ uint tint_pad_26;
+  /* 0x01a4 */ int8_t tint_pad_16[4];
+  /* 0x01a8 */ float3x2 tint_pad_29;
+  /* 0x01c0 */ uint tint_pad_6;
+  /* 0x01c4 */ int8_t tint_pad_17[12];
+  /* 0x01d0 */ float3x3 tint_pad_22;
+  /* 0x0200 */ uint tint_pad_32;
+  /* 0x0204 */ int8_t tint_pad_18[12];
+  /* 0x0210 */ float3x4 tint_pad_34;
+  /* 0x0240 */ uint tint_pad_35;
+  /* 0x0244 */ int8_t tint_pad_19[4];
+  /* 0x0248 */ float4x2 tint_pad_30;
+  /* 0x0268 */ uint tint_pad_9;
+  /* 0x026c */ int8_t tint_pad_36[4];
+  /* 0x0270 */ float4x3 tint_pad_31;
+  /* 0x02b0 */ uint tint_pad_28;
+  /* 0x02b4 */ int8_t tint_pad_37[12];
+  /* 0x02c0 */ float4x4 tint_pad_4;
+  /* 0x0300 */ float tint_pad_21;
+  /* 0x0304 */ int8_t tint_pad_38[124];
+};
+)");
+}
+
+// TODO(dsinclair): How to translate @block
+TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithAttribute) {
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
+  EXPECT_EQ(out.str(), R"(struct {
+  /* 0x0000 */ int a;
+  /* 0x0004 */ float b;
+})");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_U32) {
+  auto* u32 = create<sem::U32>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, u32, "")) << gen.error();
+  EXPECT_EQ(out.str(), "uint");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Vector) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, vec3, "")) << gen.error();
+  EXPECT_EQ(out.str(), "float3");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Void) {
+  auto* void_ = create<sem::Void>();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, void_, "")) << gen.error();
+  EXPECT_EQ(out.str(), "void");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_Sampler) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.error();
+  EXPECT_EQ(out.str(), "sampler");
+}
+
+TEST_F(MslGeneratorImplTest, EmitType_SamplerComparison) {
+  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.error();
+  EXPECT_EQ(out.str(), "sampler");
+}
+
+struct MslDepthTextureData {
+  ast::TextureDimension dim;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, MslDepthTextureData data) {
+  out << data.dim;
+  return out;
+}
+using MslDepthTexturesTest = TestParamHelper<MslDepthTextureData>;
+TEST_P(MslDepthTexturesTest, Emit) {
+  auto params = GetParam();
+
+  sem::DepthTexture s(params.dim);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslDepthTexturesTest,
+    testing::Values(MslDepthTextureData{ast::TextureDimension::k2d,
+                                        "depth2d<float, access::sample>"},
+                    MslDepthTextureData{ast::TextureDimension::k2dArray,
+                                        "depth2d_array<float, access::sample>"},
+                    MslDepthTextureData{ast::TextureDimension::kCube,
+                                        "depthcube<float, access::sample>"},
+                    MslDepthTextureData{
+                        ast::TextureDimension::kCubeArray,
+                        "depthcube_array<float, access::sample>"}));
+
+using MslDepthMultisampledTexturesTest = TestHelper;
+TEST_F(MslDepthMultisampledTexturesTest, Emit) {
+  sem::DepthMultisampledTexture s(ast::TextureDimension::k2d);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
+  EXPECT_EQ(out.str(), "depth2d_ms<float, access::read>");
+}
+
+struct MslTextureData {
+  ast::TextureDimension dim;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, MslTextureData data) {
+  out << data.dim;
+  return out;
+}
+using MslSampledtexturesTest = TestParamHelper<MslTextureData>;
+TEST_P(MslSampledtexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(params.dim, f32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslSampledtexturesTest,
+    testing::Values(MslTextureData{ast::TextureDimension::k1d,
+                                   "texture1d<float, access::sample>"},
+                    MslTextureData{ast::TextureDimension::k2d,
+                                   "texture2d<float, access::sample>"},
+                    MslTextureData{ast::TextureDimension::k2dArray,
+                                   "texture2d_array<float, access::sample>"},
+                    MslTextureData{ast::TextureDimension::k3d,
+                                   "texture3d<float, access::sample>"},
+                    MslTextureData{ast::TextureDimension::kCube,
+                                   "texturecube<float, access::sample>"},
+                    MslTextureData{
+                        ast::TextureDimension::kCubeArray,
+                        "texturecube_array<float, access::sample>"}));
+
+TEST_F(MslGeneratorImplTest, Emit_TypeMultisampledTexture) {
+  auto* u32 = create<sem::U32>();
+  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, u32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, ms, "")) << gen.error();
+  EXPECT_EQ(out.str(), "texture2d_ms<uint, access::read>");
+}
+
+struct MslStorageTextureData {
+  ast::TextureDimension dim;
+  std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, MslStorageTextureData data) {
+  return out << data.dim;
+}
+using MslStorageTexturesTest = TestParamHelper<MslStorageTextureData>;
+TEST_P(MslStorageTexturesTest, Emit) {
+  auto params = GetParam();
+
+  auto* s = ty.storage_texture(params.dim, ast::TexelFormat::kR32Float,
+                               ast::Access::kWrite);
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    MslGeneratorImplTest,
+    MslStorageTexturesTest,
+    testing::Values(MslStorageTextureData{ast::TextureDimension::k1d,
+                                          "texture1d<float, access::write>"},
+                    MslStorageTextureData{ast::TextureDimension::k2d,
+                                          "texture2d<float, access::write>"},
+                    MslStorageTextureData{
+                        ast::TextureDimension::k2dArray,
+                        "texture2d_array<float, access::write>"},
+                    MslStorageTextureData{ast::TextureDimension::k3d,
+                                          "texture3d<float, access::write>"}));
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_unary_op_test.cc b/src/tint/writer/msl/generator_impl_unary_op_test.cc
new file mode 100644
index 0000000..1d294e7
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_unary_op_test.cc
@@ -0,0 +1,106 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using MslUnaryOpTest = TestHelper;
+
+TEST_F(MslUnaryOpTest, AddressOf) {
+  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "&(expr)");
+}
+
+TEST_F(MslUnaryOpTest, Complement) {
+  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "~(expr)");
+}
+
+TEST_F(MslUnaryOpTest, Indirection) {
+  Global("G", ty.f32(), ast::StorageClass::kPrivate);
+  auto* p = Const(
+      "expr", nullptr,
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
+  WrapInFunction(p, op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "*(expr)");
+}
+
+TEST_F(MslUnaryOpTest, Not) {
+  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "!(expr)");
+}
+
+TEST_F(MslUnaryOpTest, Negation) {
+  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "tint_unary_minus(expr)");
+}
+
+TEST_F(MslUnaryOpTest, NegationOfIntMin) {
+  auto* op = create<ast::UnaryOpExpression>(
+      ast::UnaryOp::kNegation, Expr(std::numeric_limits<int32_t>::min()));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "tint_unary_minus((-2147483647 - 1))");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
new file mode 100644
index 0000000..2e6f360
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement) {
+  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone);
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const) {
+  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float const a = float();\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Array) {
+  auto* var = Var("a", ty.array<f32, 5>(), ast::StorageClass::kNone);
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a[5] = {0.0f};\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Struct) {
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.f32()),
+                           });
+
+  auto* var = Var("a", ty.Of(s), ast::StorageClass::kNone);
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  S a = {};
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Vector) {
+  auto* var = Var("a", ty.vec2<f32>());
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float2 a = 0.0f;\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Matrix) {
+  auto* var = Var("a", ty.mat3x2<f32>());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float3x2 a = float3x2(0.0f);\n");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Private) {
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("thread float tint_symbol_1 = 0.0f;\n"));
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_Private) {
+  GlobalConst("initializer", ty.f32(), Expr(0.f));
+  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("thread float tint_symbol_1 = initializer;\n"));
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Workgroup) {
+  Global("a", ty.f32(), ast::StorageClass::kWorkgroup);
+
+  WrapInFunction(Expr("a"));
+
+  GeneratorImpl& gen = SanitizeAndBuild();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("threadgroup float tint_symbol_2;\n"));
+}
+
+TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec) {
+  auto* zero_vec = vec3<f32>();
+
+  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, zero_vec);
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float3 a = float3();
+)");
+}
+
+}  // namespace
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/msl/test_helper.h b/src/tint/writer/msl/test_helper.h
new file mode 100644
index 0000000..e1d383d
--- /dev/null
+++ b/src/tint/writer/msl/test_helper.h
@@ -0,0 +1,107 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_MSL_TEST_HELPER_H_
+#define SRC_TINT_WRITER_MSL_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/writer/msl/generator.h"
+#include "src/tint/writer/msl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace msl {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {
+ public:
+  TestHelperBase() = default;
+  ~TestHelperBase() override = default;
+
+  /// Builds and returns a GeneratorImpl from the program.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @return the built generator
+  GeneratorImpl& Build() {
+    if (gen_) {
+      return *gen_;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+    gen_ = std::make_unique<GeneratorImpl>(program.get());
+    return *gen_;
+  }
+
+  /// Builds the program, runs the program through the transform::Msl sanitizer
+  /// and returns a GeneratorImpl from the sanitized program.
+  /// @param options The MSL generator options.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @return the built generator
+  GeneratorImpl& SanitizeAndBuild(const Options& options = {}) {
+    if (gen_) {
+      return *gen_;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+
+    auto result = Sanitize(
+        program.get(), options.buffer_size_ubo_index, options.fixed_sample_mask,
+        options.emit_vertex_point_size, options.disable_workgroup_init,
+        options.array_length_from_uniform);
+    [&]() {
+      ASSERT_TRUE(result.program.IsValid())
+          << diag::Formatter().format(result.program.Diagnostics());
+    }();
+    *program = std::move(result.program);
+    gen_ = std::make_unique<GeneratorImpl>(program.get());
+    return *gen_;
+  }
+
+  /// The program built with a call to Build()
+  std::unique_ptr<Program> program;
+
+ private:
+  std::unique_ptr<GeneratorImpl> gen_;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace msl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_MSL_TEST_HELPER_H_
diff --git a/src/tint/writer/spirv/binary_writer.cc b/src/tint/writer/spirv/binary_writer.cc
new file mode 100644
index 0000000..28fcc75
--- /dev/null
+++ b/src/tint/writer/spirv/binary_writer.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/binary_writer.h"
+
+#include <cstring>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+const uint32_t kGeneratorId = 23u << 16;
+
+}  // namespace
+
+BinaryWriter::BinaryWriter() = default;
+
+BinaryWriter::~BinaryWriter() = default;
+
+void BinaryWriter::WriteBuilder(Builder* builder) {
+  out_.reserve(builder->total_size());
+  builder->iterate(
+      [this](const Instruction& inst) { this->process_instruction(inst); });
+}
+
+void BinaryWriter::WriteInstruction(const Instruction& inst) {
+  process_instruction(inst);
+}
+
+void BinaryWriter::WriteHeader(uint32_t bound) {
+  out_.push_back(spv::MagicNumber);
+  out_.push_back(0x00010300);  // Version 1.3
+  out_.push_back(kGeneratorId);
+  out_.push_back(bound);
+  out_.push_back(0);
+}
+
+void BinaryWriter::process_instruction(const Instruction& inst) {
+  out_.push_back(inst.word_length() << 16 |
+                 static_cast<uint32_t>(inst.opcode()));
+  for (const auto& op : inst.operands()) {
+    process_op(op);
+  }
+}
+
+void BinaryWriter::process_op(const Operand& op) {
+  if (op.IsFloat()) {
+    // Allocate space for the float
+    out_.push_back(0);
+    auto f = op.to_f();
+    uint8_t* ptr = reinterpret_cast<uint8_t*>(out_.data() + (out_.size() - 1));
+    memcpy(ptr, &f, 4);
+  } else if (op.IsInt()) {
+    out_.push_back(op.to_i());
+  } else {
+    auto idx = out_.size();
+    const auto& str = op.to_s();
+    out_.resize(out_.size() + op.length(), 0);
+    memcpy(out_.data() + idx, str.c_str(), str.size() + 1);
+  }
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/binary_writer.h b/src/tint/writer/spirv/binary_writer.h
new file mode 100644
index 0000000..f852a35
--- /dev/null
+++ b/src/tint/writer/spirv/binary_writer.h
@@ -0,0 +1,61 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_BINARY_WRITER_H_
+#define SRC_TINT_WRITER_SPIRV_BINARY_WRITER_H_
+
+#include <vector>
+
+#include "src/tint/writer/spirv/builder.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// Writer to convert from builder to SPIR-V binary
+class BinaryWriter {
+ public:
+  /// Constructor
+  BinaryWriter();
+  ~BinaryWriter();
+
+  /// Writes the SPIR-V header.
+  /// @param bound the bound to output
+  void WriteHeader(uint32_t bound);
+
+  /// Writes the given builder data into a binary. Note, this does not emit
+  /// the SPIR-V header. You **must** call WriteHeader() before WriteBuilder()
+  /// if you want the SPIR-V to be emitted.
+  /// @param builder the builder to assemble from
+  void WriteBuilder(Builder* builder);
+
+  /// Writes the given instruction into the binary.
+  /// @param inst the instruction to assemble
+  void WriteInstruction(const Instruction& inst);
+
+  /// @returns the assembled SPIR-V
+  const std::vector<uint32_t>& result() const { return out_; }
+
+ private:
+  void process_instruction(const Instruction& inst);
+  void process_op(const Operand& op);
+
+  std::vector<uint32_t> out_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_BINARY_WRITER_H_
diff --git a/src/tint/writer/spirv/binary_writer_test.cc b/src/tint/writer/spirv/binary_writer_test.cc
new file mode 100644
index 0000000..86420e0
--- /dev/null
+++ b/src/tint/writer/spirv/binary_writer_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BinaryWriterTest = TestHelper;
+
+TEST_F(BinaryWriterTest, Preamble) {
+  BinaryWriter bw;
+  bw.WriteHeader(5);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 5u);
+  EXPECT_EQ(res[0], spv::MagicNumber);
+  EXPECT_EQ(res[1], 0x00010300u);  // SPIR-V 1.3
+  EXPECT_EQ(res[2], 23u << 16);    // Generator ID
+  EXPECT_EQ(res[3], 5u);           // ID Bound
+  EXPECT_EQ(res[4], 0u);           // Reserved
+}
+
+TEST_F(BinaryWriterTest, Float) {
+  spirv::Builder& b = Build();
+
+  b.push_annot(spv::Op::OpKill, {Operand::Float(2.4f)});
+  BinaryWriter bw;
+  bw.WriteBuilder(&b);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 2u);
+  float f;
+  memcpy(&f, res.data() + 1, 4);
+  EXPECT_EQ(f, 2.4f);
+}
+
+TEST_F(BinaryWriterTest, Int) {
+  spirv::Builder& b = Build();
+
+  b.push_annot(spv::Op::OpKill, {Operand::Int(2)});
+  BinaryWriter bw;
+  bw.WriteBuilder(&b);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 2u);
+  EXPECT_EQ(res[1], 2u);
+}
+
+TEST_F(BinaryWriterTest, String) {
+  spirv::Builder& b = Build();
+
+  b.push_annot(spv::Op::OpKill, {Operand::String("my_string")});
+  BinaryWriter bw;
+  bw.WriteBuilder(&b);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 4u);
+
+  uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
+  EXPECT_EQ(v[0], 'm');
+  EXPECT_EQ(v[1], 'y');
+  EXPECT_EQ(v[2], '_');
+  EXPECT_EQ(v[3], 's');
+  EXPECT_EQ(v[4], 't');
+  EXPECT_EQ(v[5], 'r');
+  EXPECT_EQ(v[6], 'i');
+  EXPECT_EQ(v[7], 'n');
+  EXPECT_EQ(v[8], 'g');
+  EXPECT_EQ(v[9], '\0');
+  EXPECT_EQ(v[10], '\0');
+  EXPECT_EQ(v[11], '\0');
+}
+
+TEST_F(BinaryWriterTest, String_Multiple4Length) {
+  spirv::Builder& b = Build();
+
+  b.push_annot(spv::Op::OpKill, {Operand::String("mystring")});
+  BinaryWriter bw;
+  bw.WriteBuilder(&b);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 4u);
+
+  uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
+  EXPECT_EQ(v[0], 'm');
+  EXPECT_EQ(v[1], 'y');
+  EXPECT_EQ(v[2], 's');
+  EXPECT_EQ(v[3], 't');
+  EXPECT_EQ(v[4], 'r');
+  EXPECT_EQ(v[5], 'i');
+  EXPECT_EQ(v[6], 'n');
+  EXPECT_EQ(v[7], 'g');
+  EXPECT_EQ(v[8], '\0');
+  EXPECT_EQ(v[9], '\0');
+  EXPECT_EQ(v[10], '\0');
+  EXPECT_EQ(v[11], '\0');
+}
+
+TEST_F(BinaryWriterTest, TestInstructionWriter) {
+  Instruction i1{spv::Op::OpKill, {Operand::Int(2)}};
+  Instruction i2{spv::Op::OpKill, {Operand::Int(4)}};
+
+  BinaryWriter bw;
+  bw.WriteInstruction(i1);
+  bw.WriteInstruction(i2);
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 4u);
+  EXPECT_EQ(res[1], 2u);
+  EXPECT_EQ(res[3], 4u);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
new file mode 100644
index 0000000..9403764
--- /dev/null
+++ b/src/tint/writer/spirv/builder.cc
@@ -0,0 +1,4468 @@
+// Copyright 2020 The Tint Authors.  //
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/builder.h"
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "spirv/unified1/GLSL.std.450.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/sem/array.h"
+#include "src/tint/sem/atomic_type.h"
+#include "src/tint/sem/builtin.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/depth_multisampled_texture_type.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/reference_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/sem/vector_type.h"
+#include "src/tint/transform/add_empty_entry_point.h"
+#include "src/tint/transform/add_spirv_block_attribute.h"
+#include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/external_texture_transform.h"
+#include "src/tint/transform/fold_constants.h"
+#include "src/tint/transform/for_loop_to_loop.h"
+#include "src/tint/transform/manager.h"
+#include "src/tint/transform/remove_unreachable_statements.h"
+#include "src/tint/transform/simplify_pointers.h"
+#include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/var_for_dynamic_index.h"
+#include "src/tint/transform/vectorize_scalar_matrix_constructors.h"
+#include "src/tint/transform/zero_init_workgroup_memory.h"
+#include "src/tint/utils/defer.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/writer/append_vector.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuiltinType = sem::BuiltinType;
+
+const char kGLSLstd450[] = "GLSL.std.450";
+
+uint32_t size_of(const InstructionList& instructions) {
+  uint32_t size = 0;
+  for (const auto& inst : instructions)
+    size += inst.word_length();
+
+  return size;
+}
+
+uint32_t pipeline_stage_to_execution_model(ast::PipelineStage stage) {
+  SpvExecutionModel model = SpvExecutionModelVertex;
+
+  switch (stage) {
+    case ast::PipelineStage::kFragment:
+      model = SpvExecutionModelFragment;
+      break;
+    case ast::PipelineStage::kVertex:
+      model = SpvExecutionModelVertex;
+      break;
+    case ast::PipelineStage::kCompute:
+      model = SpvExecutionModelGLCompute;
+      break;
+    case ast::PipelineStage::kNone:
+      model = SpvExecutionModelMax;
+      break;
+  }
+  return model;
+}
+
+bool LastIsFallthrough(const ast::BlockStatement* stmts) {
+  return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
+}
+
+/// Returns the matrix type that is `type` or that is wrapped by
+/// one or more levels of an arrays inside of `type`.
+/// @param type the given type, which must not be null
+/// @returns the nested matrix type, or nullptr if none
+const sem::Matrix* GetNestedMatrixType(const sem::Type* type) {
+  while (auto* arr = type->As<sem::Array>()) {
+    type = arr->ElemType();
+  }
+  return type->As<sem::Matrix>();
+}
+
+uint32_t builtin_to_glsl_method(const sem::Builtin* builtin) {
+  switch (builtin->Type()) {
+    case BuiltinType::kAcos:
+      return GLSLstd450Acos;
+    case BuiltinType::kAsin:
+      return GLSLstd450Asin;
+    case BuiltinType::kAtan:
+      return GLSLstd450Atan;
+    case BuiltinType::kAtan2:
+      return GLSLstd450Atan2;
+    case BuiltinType::kCeil:
+      return GLSLstd450Ceil;
+    case BuiltinType::kClamp:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        return GLSLstd450NClamp;
+      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+        return GLSLstd450UClamp;
+      } else {
+        return GLSLstd450SClamp;
+      }
+    case BuiltinType::kCos:
+      return GLSLstd450Cos;
+    case BuiltinType::kCosh:
+      return GLSLstd450Cosh;
+    case BuiltinType::kCross:
+      return GLSLstd450Cross;
+    case BuiltinType::kDegrees:
+      return GLSLstd450Degrees;
+    case BuiltinType::kDeterminant:
+      return GLSLstd450Determinant;
+    case BuiltinType::kDistance:
+      return GLSLstd450Distance;
+    case BuiltinType::kExp:
+      return GLSLstd450Exp;
+    case BuiltinType::kExp2:
+      return GLSLstd450Exp2;
+    case BuiltinType::kFaceForward:
+      return GLSLstd450FaceForward;
+    case BuiltinType::kFloor:
+      return GLSLstd450Floor;
+    case BuiltinType::kFma:
+      return GLSLstd450Fma;
+    case BuiltinType::kFract:
+      return GLSLstd450Fract;
+    case BuiltinType::kFrexp:
+      return GLSLstd450FrexpStruct;
+    case BuiltinType::kInverseSqrt:
+      return GLSLstd450InverseSqrt;
+    case BuiltinType::kLdexp:
+      return GLSLstd450Ldexp;
+    case BuiltinType::kLength:
+      return GLSLstd450Length;
+    case BuiltinType::kLog:
+      return GLSLstd450Log;
+    case BuiltinType::kLog2:
+      return GLSLstd450Log2;
+    case BuiltinType::kMax:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        return GLSLstd450NMax;
+      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+        return GLSLstd450UMax;
+      } else {
+        return GLSLstd450SMax;
+      }
+    case BuiltinType::kMin:
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        return GLSLstd450NMin;
+      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+        return GLSLstd450UMin;
+      } else {
+        return GLSLstd450SMin;
+      }
+    case BuiltinType::kMix:
+      return GLSLstd450FMix;
+    case BuiltinType::kModf:
+      return GLSLstd450ModfStruct;
+    case BuiltinType::kNormalize:
+      return GLSLstd450Normalize;
+    case BuiltinType::kPack4x8snorm:
+      return GLSLstd450PackSnorm4x8;
+    case BuiltinType::kPack4x8unorm:
+      return GLSLstd450PackUnorm4x8;
+    case BuiltinType::kPack2x16snorm:
+      return GLSLstd450PackSnorm2x16;
+    case BuiltinType::kPack2x16unorm:
+      return GLSLstd450PackUnorm2x16;
+    case BuiltinType::kPack2x16float:
+      return GLSLstd450PackHalf2x16;
+    case BuiltinType::kPow:
+      return GLSLstd450Pow;
+    case BuiltinType::kRadians:
+      return GLSLstd450Radians;
+    case BuiltinType::kReflect:
+      return GLSLstd450Reflect;
+    case BuiltinType::kRefract:
+      return GLSLstd450Refract;
+    case BuiltinType::kRound:
+      return GLSLstd450RoundEven;
+    case BuiltinType::kSign:
+      return GLSLstd450FSign;
+    case BuiltinType::kSin:
+      return GLSLstd450Sin;
+    case BuiltinType::kSinh:
+      return GLSLstd450Sinh;
+    case BuiltinType::kSmoothStep:
+      return GLSLstd450SmoothStep;
+    case BuiltinType::kSqrt:
+      return GLSLstd450Sqrt;
+    case BuiltinType::kStep:
+      return GLSLstd450Step;
+    case BuiltinType::kTan:
+      return GLSLstd450Tan;
+    case BuiltinType::kTanh:
+      return GLSLstd450Tanh;
+    case BuiltinType::kTrunc:
+      return GLSLstd450Trunc;
+    case BuiltinType::kUnpack4x8snorm:
+      return GLSLstd450UnpackSnorm4x8;
+    case BuiltinType::kUnpack4x8unorm:
+      return GLSLstd450UnpackUnorm4x8;
+    case BuiltinType::kUnpack2x16snorm:
+      return GLSLstd450UnpackSnorm2x16;
+    case BuiltinType::kUnpack2x16unorm:
+      return GLSLstd450UnpackUnorm2x16;
+    case BuiltinType::kUnpack2x16float:
+      return GLSLstd450UnpackHalf2x16;
+    default:
+      break;
+  }
+  return 0;
+}
+
+/// @return the vector element type if ty is a vector, otherwise return ty.
+const sem::Type* ElementTypeOf(const sem::Type* ty) {
+  if (auto* v = ty->As<sem::Vector>()) {
+    return v->type();
+  }
+  return ty;
+}
+
+}  // namespace
+
+SanitizedResult Sanitize(const Program* in,
+                         bool emit_vertex_point_size,
+                         bool disable_workgroup_init) {
+  transform::Manager manager;
+  transform::DataMap data;
+
+  manager.Add<transform::Unshadow>();
+  if (!disable_workgroup_init) {
+    manager.Add<transform::ZeroInitWorkgroupMemory>();
+  }
+  manager.Add<transform::RemoveUnreachableStatements>();
+  manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
+  manager.Add<transform::FoldConstants>();
+  manager.Add<transform::ExternalTextureTransform>();
+  manager.Add<transform::VectorizeScalarMatrixConstructors>();
+  manager.Add<transform::ForLoopToLoop>();  // Must come after
+                                            // ZeroInitWorkgroupMemory
+  manager.Add<transform::CanonicalizeEntryPointIO>();
+  manager.Add<transform::AddEmptyEntryPoint>();
+  manager.Add<transform::AddSpirvBlockAttribute>();
+  manager.Add<transform::VarForDynamicIndex>();
+
+  data.Add<transform::CanonicalizeEntryPointIO::Config>(
+      transform::CanonicalizeEntryPointIO::Config(
+          transform::CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF,
+          emit_vertex_point_size));
+
+  SanitizedResult result;
+  result.program = std::move(manager.Run(in, data).program);
+  return result;
+}
+
+Builder::AccessorInfo::AccessorInfo() : source_id(0), source_type(nullptr) {}
+
+Builder::AccessorInfo::~AccessorInfo() {}
+
+Builder::Builder(const Program* program)
+    : builder_(ProgramBuilder::Wrap(program)), scope_stack_({}) {}
+
+Builder::~Builder() = default;
+
+bool Builder::Build() {
+  push_capability(SpvCapabilityShader);
+
+  push_memory_model(spv::Op::OpMemoryModel,
+                    {Operand::Int(SpvAddressingModelLogical),
+                     Operand::Int(SpvMemoryModelGLSL450)});
+
+  for (auto* var : builder_.AST().GlobalVariables()) {
+    if (!GenerateGlobalVariable(var)) {
+      return false;
+    }
+  }
+
+  auto* mod = builder_.Sem().Module();
+  for (auto* decl : mod->DependencyOrderedDeclarations()) {
+    if (auto* func = decl->As<ast::Function>()) {
+      if (!GenerateFunction(func)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+Operand Builder::result_op() {
+  return Operand::Int(next_id());
+}
+
+uint32_t Builder::total_size() const {
+  // The 5 covers the magic, version, generator, id bound and reserved.
+  uint32_t size = 5;
+
+  size += size_of(capabilities_);
+  size += size_of(extensions_);
+  size += size_of(ext_imports_);
+  size += size_of(memory_model_);
+  size += size_of(entry_points_);
+  size += size_of(execution_modes_);
+  size += size_of(debug_);
+  size += size_of(annotations_);
+  size += size_of(types_);
+  for (const auto& func : functions_) {
+    size += func.word_length();
+  }
+
+  return size;
+}
+
+void Builder::iterate(std::function<void(const Instruction&)> cb) const {
+  for (const auto& inst : capabilities_) {
+    cb(inst);
+  }
+  for (const auto& inst : extensions_) {
+    cb(inst);
+  }
+  for (const auto& inst : ext_imports_) {
+    cb(inst);
+  }
+  for (const auto& inst : memory_model_) {
+    cb(inst);
+  }
+  for (const auto& inst : entry_points_) {
+    cb(inst);
+  }
+  for (const auto& inst : execution_modes_) {
+    cb(inst);
+  }
+  for (const auto& inst : debug_) {
+    cb(inst);
+  }
+  for (const auto& inst : annotations_) {
+    cb(inst);
+  }
+  for (const auto& inst : types_) {
+    cb(inst);
+  }
+  for (const auto& func : functions_) {
+    func.iterate(cb);
+  }
+}
+
+void Builder::push_capability(uint32_t cap) {
+  if (capability_set_.count(cap) == 0) {
+    capability_set_.insert(cap);
+    capabilities_.push_back(
+        Instruction{spv::Op::OpCapability, {Operand::Int(cap)}});
+  }
+}
+
+bool Builder::GenerateLabel(uint32_t id) {
+  if (!push_function_inst(spv::Op::OpLabel, {Operand::Int(id)})) {
+    return false;
+  }
+  current_label_id_ = id;
+  return true;
+}
+
+bool Builder::GenerateAssignStatement(const ast::AssignmentStatement* assign) {
+  if (assign->lhs->Is<ast::PhonyExpression>()) {
+    auto rhs_id = GenerateExpression(assign->rhs);
+    if (rhs_id == 0) {
+      return false;
+    }
+    return true;
+  } else {
+    auto lhs_id = GenerateExpression(assign->lhs);
+    if (lhs_id == 0) {
+      return false;
+    }
+    auto rhs_id = GenerateExpressionWithLoadIfNeeded(assign->rhs);
+    if (rhs_id == 0) {
+      return false;
+    }
+    return GenerateStore(lhs_id, rhs_id);
+  }
+}
+
+bool Builder::GenerateBreakStatement(const ast::BreakStatement*) {
+  if (merge_stack_.empty()) {
+    error_ = "Attempted to break without a merge block";
+    return false;
+  }
+  if (!push_function_inst(spv::Op::OpBranch,
+                          {Operand::Int(merge_stack_.back())})) {
+    return false;
+  }
+  return true;
+}
+
+bool Builder::GenerateContinueStatement(const ast::ContinueStatement*) {
+  if (continue_stack_.empty()) {
+    error_ = "Attempted to continue without a continue block";
+    return false;
+  }
+  if (!push_function_inst(spv::Op::OpBranch,
+                          {Operand::Int(continue_stack_.back())})) {
+    return false;
+  }
+  return true;
+}
+
+// TODO(dsinclair): This is generating an OpKill but the semantics of kill
+// haven't been defined for WGSL yet. So, this may need to change.
+// https://github.com/gpuweb/gpuweb/issues/676
+bool Builder::GenerateDiscardStatement(const ast::DiscardStatement*) {
+  if (!push_function_inst(spv::Op::OpKill, {})) {
+    return false;
+  }
+  return true;
+}
+
+bool Builder::GenerateEntryPoint(const ast::Function* func, uint32_t id) {
+  auto stage = pipeline_stage_to_execution_model(func->PipelineStage());
+  if (stage == SpvExecutionModelMax) {
+    error_ = "Unknown pipeline stage provided";
+    return false;
+  }
+
+  OperandList operands = {
+      Operand::Int(stage), Operand::Int(id),
+      Operand::String(builder_.Symbols().NameFor(func->symbol))};
+
+  auto* func_sem = builder_.Sem().Get(func);
+  for (const auto* var : func_sem->TransitivelyReferencedGlobals()) {
+    // For SPIR-V 1.3 we only output Input/output variables. If we update to
+    // SPIR-V 1.4 or later this should be all variables.
+    if (var->StorageClass() != ast::StorageClass::kInput &&
+        var->StorageClass() != ast::StorageClass::kOutput) {
+      continue;
+    }
+
+    uint32_t var_id = scope_stack_.Get(var->Declaration()->symbol);
+    if (var_id == 0) {
+      error_ = "unable to find ID for global variable: " +
+               builder_.Symbols().NameFor(var->Declaration()->symbol);
+      return false;
+    }
+
+    operands.push_back(Operand::Int(var_id));
+  }
+  push_entry_point(spv::Op::OpEntryPoint, operands);
+
+  return true;
+}
+
+bool Builder::GenerateExecutionModes(const ast::Function* func, uint32_t id) {
+  auto* func_sem = builder_.Sem().Get(func);
+
+  // WGSL fragment shader origin is upper left
+  if (func->PipelineStage() == ast::PipelineStage::kFragment) {
+    push_execution_mode(
+        spv::Op::OpExecutionMode,
+        {Operand::Int(id), Operand::Int(SpvExecutionModeOriginUpperLeft)});
+  } else if (func->PipelineStage() == ast::PipelineStage::kCompute) {
+    auto& wgsize = func_sem->WorkgroupSize();
+
+    // Check if the workgroup_size uses pipeline-overridable constants.
+    if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
+        wgsize[2].overridable_const) {
+      if (has_overridable_workgroup_size_) {
+        // Only one stage can have a pipeline-overridable workgroup size.
+        // TODO(crbug.com/tint/810): Use LocalSizeId to handle this scenario.
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "multiple stages using pipeline-overridable workgroup sizes";
+      }
+      has_overridable_workgroup_size_ = true;
+
+      auto* vec3_u32 =
+          builder_.create<sem::Vector>(builder_.create<sem::U32>(), 3);
+      uint32_t vec3_u32_type_id = GenerateTypeIfNeeded(vec3_u32);
+      if (vec3_u32_type_id == 0) {
+        return 0;
+      }
+
+      OperandList wgsize_ops;
+      auto wgsize_result = result_op();
+      wgsize_ops.push_back(Operand::Int(vec3_u32_type_id));
+      wgsize_ops.push_back(wgsize_result);
+
+      // Generate OpConstant instructions for each dimension.
+      for (int i = 0; i < 3; i++) {
+        auto constant = ScalarConstant::U32(wgsize[i].value);
+        if (wgsize[i].overridable_const) {
+          // Make the constant specializable.
+          auto* sem_const = builder_.Sem().Get<sem::GlobalVariable>(
+              wgsize[i].overridable_const);
+          if (!sem_const->IsOverridable()) {
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "expected a pipeline-overridable constant";
+          }
+          constant.is_spec_op = true;
+          constant.constant_id = sem_const->ConstantId();
+        }
+
+        auto result = GenerateConstantIfNeeded(constant);
+        wgsize_ops.push_back(Operand::Int(result));
+      }
+
+      // Generate the WorkgroupSize builtin.
+      push_type(spv::Op::OpSpecConstantComposite, wgsize_ops);
+      push_annot(spv::Op::OpDecorate,
+                 {wgsize_result, Operand::Int(SpvDecorationBuiltIn),
+                  Operand::Int(SpvBuiltInWorkgroupSize)});
+    } else {
+      // Not overridable, so just use OpExecutionMode LocalSize.
+      uint32_t x = wgsize[0].value;
+      uint32_t y = wgsize[1].value;
+      uint32_t z = wgsize[2].value;
+      push_execution_mode(
+          spv::Op::OpExecutionMode,
+          {Operand::Int(id), Operand::Int(SpvExecutionModeLocalSize),
+           Operand::Int(x), Operand::Int(y), Operand::Int(z)});
+    }
+  }
+
+  for (auto builtin : func_sem->TransitivelyReferencedBuiltinVariables()) {
+    if (builtin.second->builtin == ast::Builtin::kFragDepth) {
+      push_execution_mode(
+          spv::Op::OpExecutionMode,
+          {Operand::Int(id), Operand::Int(SpvExecutionModeDepthReplacing)});
+    }
+  }
+
+  return true;
+}
+
+uint32_t Builder::GenerateExpression(const ast::Expression* expr) {
+  return Switch(
+      expr,
+      [&](const ast::IndexAccessorExpression* a) {  //
+        return GenerateAccessorExpression(a);
+      },
+      [&](const ast::BinaryExpression* b) {  //
+        return GenerateBinaryExpression(b);
+      },
+      [&](const ast::BitcastExpression* b) {  //
+        return GenerateBitcastExpression(b);
+      },
+      [&](const ast::CallExpression* c) {  //
+        return GenerateCallExpression(c);
+      },
+      [&](const ast::IdentifierExpression* i) {  //
+        return GenerateIdentifierExpression(i);
+      },
+      [&](const ast::LiteralExpression* l) {  //
+        return GenerateLiteralIfNeeded(nullptr, l);
+      },
+      [&](const ast::MemberAccessorExpression* m) {  //
+        return GenerateAccessorExpression(m);
+      },
+      [&](const ast::UnaryOpExpression* u) {  //
+        return GenerateUnaryOpExpression(u);
+      },
+      [&](Default) -> uint32_t {
+        error_ =
+            "unknown expression type: " + std::string(expr->TypeInfo().name);
+        return 0;
+      });
+}
+
+bool Builder::GenerateFunction(const ast::Function* func_ast) {
+  auto* func = builder_.Sem().Get(func_ast);
+
+  uint32_t func_type_id = GenerateFunctionTypeIfNeeded(func);
+  if (func_type_id == 0) {
+    return false;
+  }
+
+  auto func_op = result_op();
+  auto func_id = func_op.to_i();
+
+  push_debug(spv::Op::OpName,
+             {Operand::Int(func_id),
+              Operand::String(builder_.Symbols().NameFor(func_ast->symbol))});
+
+  auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
+  if (ret_id == 0) {
+    return false;
+  }
+
+  scope_stack_.Push();
+  TINT_DEFER(scope_stack_.Pop());
+
+  auto definition_inst = Instruction{
+      spv::Op::OpFunction,
+      {Operand::Int(ret_id), func_op, Operand::Int(SpvFunctionControlMaskNone),
+       Operand::Int(func_type_id)}};
+
+  InstructionList params;
+  for (auto* param : func->Parameters()) {
+    auto param_op = result_op();
+    auto param_id = param_op.to_i();
+
+    auto param_type_id = GenerateTypeIfNeeded(param->Type());
+    if (param_type_id == 0) {
+      return false;
+    }
+
+    push_debug(spv::Op::OpName, {Operand::Int(param_id),
+                                 Operand::String(builder_.Symbols().NameFor(
+                                     param->Declaration()->symbol))});
+    params.push_back(Instruction{spv::Op::OpFunctionParameter,
+                                 {Operand::Int(param_type_id), param_op}});
+
+    scope_stack_.Set(param->Declaration()->symbol, param_id);
+  }
+
+  push_function(Function{definition_inst, result_op(), std::move(params)});
+
+  for (auto* stmt : func_ast->body->statements) {
+    if (!GenerateStatement(stmt)) {
+      return false;
+    }
+  }
+
+  if (InsideBasicBlock()) {
+    if (func->ReturnType()->Is<sem::Void>()) {
+      push_function_inst(spv::Op::OpReturn, {});
+    } else {
+      auto zero = GenerateConstantNullIfNeeded(func->ReturnType());
+      push_function_inst(spv::Op::OpReturnValue, {Operand::Int(zero)});
+    }
+  }
+
+  if (func_ast->IsEntryPoint()) {
+    if (!GenerateEntryPoint(func_ast, func_id)) {
+      return false;
+    }
+    if (!GenerateExecutionModes(func_ast, func_id)) {
+      return false;
+    }
+  }
+
+  func_symbol_to_id_[func_ast->symbol] = func_id;
+
+  return true;
+}
+
+uint32_t Builder::GenerateFunctionTypeIfNeeded(const sem::Function* func) {
+  return utils::GetOrCreate(
+      func_sig_to_id_, func->Signature(), [&]() -> uint32_t {
+        auto func_op = result_op();
+        auto func_type_id = func_op.to_i();
+
+        auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
+        if (ret_id == 0) {
+          return 0;
+        }
+
+        OperandList ops = {func_op, Operand::Int(ret_id)};
+        for (auto* param : func->Parameters()) {
+          auto param_type_id = GenerateTypeIfNeeded(param->Type());
+          if (param_type_id == 0) {
+            return 0;
+          }
+          ops.push_back(Operand::Int(param_type_id));
+        }
+
+        push_type(spv::Op::OpTypeFunction, std::move(ops));
+        return func_type_id;
+      });
+}
+
+bool Builder::GenerateFunctionVariable(const ast::Variable* var) {
+  uint32_t init_id = 0;
+  if (var->constructor) {
+    init_id = GenerateExpressionWithLoadIfNeeded(var->constructor);
+    if (init_id == 0) {
+      return false;
+    }
+  }
+
+  if (var->is_const) {
+    if (!var->constructor) {
+      error_ = "missing constructor for constant";
+      return false;
+    }
+    scope_stack_.Set(var->symbol, init_id);
+    spirv_id_to_variable_[init_id] = var;
+    return true;
+  }
+
+  auto result = result_op();
+  auto var_id = result.to_i();
+  auto sc = ast::StorageClass::kFunction;
+  auto* type = builder_.Sem().Get(var)->Type();
+  auto type_id = GenerateTypeIfNeeded(type);
+  if (type_id == 0) {
+    return false;
+  }
+
+  push_debug(spv::Op::OpName,
+             {Operand::Int(var_id),
+              Operand::String(builder_.Symbols().NameFor(var->symbol))});
+
+  // TODO(dsinclair) We could detect if the constructor is fully const and emit
+  // an initializer value for the variable instead of doing the OpLoad.
+  auto null_id = GenerateConstantNullIfNeeded(type->UnwrapRef());
+  if (null_id == 0) {
+    return 0;
+  }
+  push_function_var({Operand::Int(type_id), result,
+                     Operand::Int(ConvertStorageClass(sc)),
+                     Operand::Int(null_id)});
+
+  if (var->constructor) {
+    if (!GenerateStore(var_id, init_id)) {
+      return false;
+    }
+  }
+
+  scope_stack_.Set(var->symbol, var_id);
+  spirv_id_to_variable_[var_id] = var;
+
+  return true;
+}
+
+bool Builder::GenerateStore(uint32_t to, uint32_t from) {
+  return push_function_inst(spv::Op::OpStore,
+                            {Operand::Int(to), Operand::Int(from)});
+}
+
+bool Builder::GenerateGlobalVariable(const ast::Variable* var) {
+  auto* sem = builder_.Sem().Get(var);
+  auto* type = sem->Type()->UnwrapRef();
+
+  uint32_t init_id = 0;
+  if (var->constructor) {
+    init_id = GenerateConstructorExpression(var, var->constructor);
+    if (init_id == 0) {
+      return false;
+    }
+  }
+
+  if (var->is_const) {
+    if (!var->constructor) {
+      // Constants must have an initializer unless they are overridable.
+      if (!var->is_overridable) {
+        error_ = "missing constructor for constant";
+        return false;
+      }
+
+      // SPIR-V requires specialization constants to have initializers.
+      if (type->Is<sem::F32>()) {
+        ast::FloatLiteralExpression l(ProgramID(), Source{}, 0.0f);
+        init_id = GenerateLiteralIfNeeded(var, &l);
+      } else if (type->Is<sem::U32>()) {
+        ast::UintLiteralExpression l(ProgramID(), Source{}, 0);
+        init_id = GenerateLiteralIfNeeded(var, &l);
+      } else if (type->Is<sem::I32>()) {
+        ast::SintLiteralExpression l(ProgramID(), Source{}, 0);
+        init_id = GenerateLiteralIfNeeded(var, &l);
+      } else if (type->Is<sem::Bool>()) {
+        ast::BoolLiteralExpression l(ProgramID(), Source{}, false);
+        init_id = GenerateLiteralIfNeeded(var, &l);
+      } else {
+        error_ = "invalid type for pipeline constant ID, must be scalar";
+        return false;
+      }
+      if (init_id == 0) {
+        return 0;
+      }
+    }
+    push_debug(spv::Op::OpName,
+               {Operand::Int(init_id),
+                Operand::String(builder_.Symbols().NameFor(var->symbol))});
+
+    scope_stack_.Set(var->symbol, init_id);
+    spirv_id_to_variable_[init_id] = var;
+    return true;
+  }
+
+  auto result = result_op();
+  auto var_id = result.to_i();
+
+  auto sc = sem->StorageClass() == ast::StorageClass::kNone
+                ? ast::StorageClass::kPrivate
+                : sem->StorageClass();
+
+  auto type_id = GenerateTypeIfNeeded(sem->Type());
+  if (type_id == 0) {
+    return false;
+  }
+
+  push_debug(spv::Op::OpName,
+             {Operand::Int(var_id),
+              Operand::String(builder_.Symbols().NameFor(var->symbol))});
+
+  OperandList ops = {Operand::Int(type_id), result,
+                     Operand::Int(ConvertStorageClass(sc))};
+
+  if (var->constructor) {
+    ops.push_back(Operand::Int(init_id));
+  } else {
+    auto* st = type->As<sem::StorageTexture>();
+    if (st || type->Is<sem::Struct>()) {
+      // type is a sem::Struct or a sem::StorageTexture
+      auto access = st ? st->access() : sem->Access();
+      switch (access) {
+        case ast::Access::kWrite:
+          push_annot(
+              spv::Op::OpDecorate,
+              {Operand::Int(var_id), Operand::Int(SpvDecorationNonReadable)});
+          break;
+        case ast::Access::kRead:
+          push_annot(
+              spv::Op::OpDecorate,
+              {Operand::Int(var_id), Operand::Int(SpvDecorationNonWritable)});
+          break;
+        case ast::Access::kUndefined:
+        case ast::Access::kReadWrite:
+          break;
+      }
+    }
+    if (!type->Is<sem::Sampler>()) {
+      // If we don't have a constructor and we're an Output or Private
+      // variable, then WGSL requires that we zero-initialize.
+      if (sem->StorageClass() == ast::StorageClass::kPrivate ||
+          sem->StorageClass() == ast::StorageClass::kOutput) {
+        init_id = GenerateConstantNullIfNeeded(type);
+        if (init_id == 0) {
+          return 0;
+        }
+        ops.push_back(Operand::Int(init_id));
+      }
+    }
+  }
+
+  push_type(spv::Op::OpVariable, std::move(ops));
+
+  for (auto* attr : var->attributes) {
+    bool ok = Switch(
+        attr,
+        [&](const ast::BuiltinAttribute* builtin) {
+          push_annot(spv::Op::OpDecorate,
+                     {Operand::Int(var_id), Operand::Int(SpvDecorationBuiltIn),
+                      Operand::Int(ConvertBuiltin(builtin->builtin,
+                                                  sem->StorageClass()))});
+          return true;
+        },
+        [&](const ast::LocationAttribute* location) {
+          push_annot(spv::Op::OpDecorate,
+                     {Operand::Int(var_id), Operand::Int(SpvDecorationLocation),
+                      Operand::Int(location->value)});
+          return true;
+        },
+        [&](const ast::InterpolateAttribute* interpolate) {
+          AddInterpolationDecorations(var_id, interpolate->type,
+                                      interpolate->sampling);
+          return true;
+        },
+        [&](const ast::InvariantAttribute*) {
+          push_annot(
+              spv::Op::OpDecorate,
+              {Operand::Int(var_id), Operand::Int(SpvDecorationInvariant)});
+          return true;
+        },
+        [&](const ast::BindingAttribute* binding) {
+          push_annot(spv::Op::OpDecorate,
+                     {Operand::Int(var_id), Operand::Int(SpvDecorationBinding),
+                      Operand::Int(binding->value)});
+          return true;
+        },
+        [&](const ast::GroupAttribute* group) {
+          push_annot(
+              spv::Op::OpDecorate,
+              {Operand::Int(var_id), Operand::Int(SpvDecorationDescriptorSet),
+               Operand::Int(group->value)});
+          return true;
+        },
+        [&](const ast::IdAttribute*) {
+          return true;  // Spec constants are handled elsewhere
+        },
+        [&](const ast::InternalAttribute*) {
+          return true;  // ignored
+        },
+        [&](Default) {
+          error_ = "unknown attribute";
+          return false;
+        });
+    if (!ok) {
+      return false;
+    }
+  }
+
+  scope_stack_.Set(var->symbol, var_id);
+  spirv_id_to_variable_[var_id] = var;
+  return true;
+}
+
+bool Builder::GenerateIndexAccessor(const ast::IndexAccessorExpression* expr,
+                                    AccessorInfo* info) {
+  auto idx_id = GenerateExpressionWithLoadIfNeeded(expr->index);
+  if (idx_id == 0) {
+    return 0;
+  }
+
+  // If the source is a reference, we access chain into it.
+  // In the future, pointers may support access-chaining.
+  // See https://github.com/gpuweb/gpuweb/pull/1580
+  if (info->source_type->Is<sem::Reference>()) {
+    info->access_chain_indices.push_back(idx_id);
+    info->source_type = TypeOf(expr);
+    return true;
+  }
+
+  auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
+  if (result_type_id == 0) {
+    return false;
+  }
+
+  // We don't have a pointer, so we can just directly extract the value.
+  auto extract = result_op();
+  auto extract_id = extract.to_i();
+
+  // If the index is compile-time constant, we use OpCompositeExtract.
+  auto* idx = builder_.Sem().Get(expr->index);
+  if (auto idx_constval = idx->ConstantValue()) {
+    if (!push_function_inst(
+            spv::Op::OpCompositeExtract,
+            {
+                Operand::Int(result_type_id),
+                extract,
+                Operand::Int(info->source_id),
+                Operand::Int(idx_constval.ElementAs<uint32_t>(0)),
+            })) {
+      return false;
+    }
+
+    info->source_id = extract_id;
+    info->source_type = TypeOf(expr);
+
+    return true;
+  }
+
+  // If the source is a vector, we use OpVectorExtractDynamic.
+  if (info->source_type->Is<sem::Vector>()) {
+    if (!push_function_inst(
+            spv::Op::OpVectorExtractDynamic,
+            {Operand::Int(result_type_id), extract,
+             Operand::Int(info->source_id), Operand::Int(idx_id)})) {
+      return false;
+    }
+
+    info->source_id = extract_id;
+    info->source_type = TypeOf(expr);
+
+    return true;
+  }
+
+  TINT_ICE(Writer, builder_.Diagnostics())
+      << "unsupported index accessor expression";
+  return false;
+}
+
+bool Builder::GenerateMemberAccessor(const ast::MemberAccessorExpression* expr,
+                                     AccessorInfo* info) {
+  auto* expr_sem = builder_.Sem().Get(expr);
+  auto* expr_type = expr_sem->Type();
+
+  if (auto* access = expr_sem->As<sem::StructMemberAccess>()) {
+    uint32_t idx = access->Member()->Index();
+
+    if (info->source_type->Is<sem::Reference>()) {
+      auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(idx));
+      if (idx_id == 0) {
+        return 0;
+      }
+      info->access_chain_indices.push_back(idx_id);
+      info->source_type = expr_type;
+    } else {
+      auto result_type_id = GenerateTypeIfNeeded(expr_type);
+      if (result_type_id == 0) {
+        return false;
+      }
+
+      auto extract = result_op();
+      auto extract_id = extract.to_i();
+      if (!push_function_inst(
+              spv::Op::OpCompositeExtract,
+              {Operand::Int(result_type_id), extract,
+               Operand::Int(info->source_id), Operand::Int(idx)})) {
+        return false;
+      }
+
+      info->source_id = extract_id;
+      info->source_type = expr_type;
+    }
+
+    return true;
+  }
+
+  if (auto* swizzle = expr_sem->As<sem::Swizzle>()) {
+    // Single element swizzle is either an access chain or a composite extract
+    auto& indices = swizzle->Indices();
+    if (indices.size() == 1) {
+      if (info->source_type->Is<sem::Reference>()) {
+        auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(indices[0]));
+        if (idx_id == 0) {
+          return 0;
+        }
+        info->access_chain_indices.push_back(idx_id);
+      } else {
+        auto result_type_id = GenerateTypeIfNeeded(expr_type);
+        if (result_type_id == 0) {
+          return 0;
+        }
+
+        auto extract = result_op();
+        auto extract_id = extract.to_i();
+        if (!push_function_inst(
+                spv::Op::OpCompositeExtract,
+                {Operand::Int(result_type_id), extract,
+                 Operand::Int(info->source_id), Operand::Int(indices[0])})) {
+          return false;
+        }
+
+        info->source_id = extract_id;
+        info->source_type = expr_type;
+      }
+      return true;
+    }
+
+    // Store the type away as it may change if we run the access chain
+    auto* incoming_type = info->source_type;
+
+    // Multi-item extract is a VectorShuffle. We have to emit any existing
+    // access chain data, then load the access chain and shuffle that.
+    if (!info->access_chain_indices.empty()) {
+      auto result_type_id = GenerateTypeIfNeeded(info->source_type);
+      if (result_type_id == 0) {
+        return 0;
+      }
+      auto extract = result_op();
+      auto extract_id = extract.to_i();
+
+      OperandList ops = {Operand::Int(result_type_id), extract,
+                         Operand::Int(info->source_id)};
+      for (auto id : info->access_chain_indices) {
+        ops.push_back(Operand::Int(id));
+      }
+
+      if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
+        return false;
+      }
+
+      info->source_id = GenerateLoadIfNeeded(expr_type, extract_id);
+      info->source_type = expr_type->UnwrapRef();
+      info->access_chain_indices.clear();
+    }
+
+    auto result_type_id = GenerateTypeIfNeeded(expr_type);
+    if (result_type_id == 0) {
+      return false;
+    }
+
+    auto vec_id = GenerateLoadIfNeeded(incoming_type, info->source_id);
+
+    auto result = result_op();
+    auto result_id = result.to_i();
+
+    OperandList ops = {Operand::Int(result_type_id), result,
+                       Operand::Int(vec_id), Operand::Int(vec_id)};
+
+    for (auto idx : indices) {
+      ops.push_back(Operand::Int(idx));
+    }
+
+    if (!push_function_inst(spv::Op::OpVectorShuffle, ops)) {
+      return false;
+    }
+    info->source_id = result_id;
+    info->source_type = expr_type;
+    return true;
+  }
+
+  TINT_ICE(Writer, builder_.Diagnostics())
+      << "unhandled member index type: " << expr_sem->TypeInfo().name;
+  return false;
+}
+
+uint32_t Builder::GenerateAccessorExpression(const ast::Expression* expr) {
+  if (!expr->IsAnyOf<ast::IndexAccessorExpression,
+                     ast::MemberAccessorExpression>()) {
+    TINT_ICE(Writer, builder_.Diagnostics()) << "expression is not an accessor";
+    return 0;
+  }
+
+  // Gather a list of all the member and index accessors that are in this chain.
+  // The list is built in reverse order as that's the order we need to access
+  // the chain.
+  std::vector<const ast::Expression*> accessors;
+  const ast::Expression* source = expr;
+  while (true) {
+    if (auto* array = source->As<ast::IndexAccessorExpression>()) {
+      accessors.insert(accessors.begin(), source);
+      source = array->object;
+    } else if (auto* member = source->As<ast::MemberAccessorExpression>()) {
+      accessors.insert(accessors.begin(), source);
+      source = member->structure;
+    } else {
+      break;
+    }
+  }
+
+  AccessorInfo info;
+  info.source_id = GenerateExpression(source);
+  if (info.source_id == 0) {
+    return 0;
+  }
+  info.source_type = TypeOf(source);
+
+  // Note: Dynamic index on array and matrix values (lets) should have been
+  // promoted to storage with the VarForDynamicIndex transform.
+
+  for (auto* accessor : accessors) {
+    bool ok = Switch(
+        accessor,
+        [&](const ast::IndexAccessorExpression* array) {
+          return GenerateIndexAccessor(array, &info);
+        },
+        [&](const ast::MemberAccessorExpression* member) {
+          return GenerateMemberAccessor(member, &info);
+        },
+        [&](Default) {
+          error_ = "invalid accessor in list: " +
+                   std::string(accessor->TypeInfo().name);
+          return false;
+        });
+    if (!ok) {
+      return false;
+    }
+  }
+
+  if (!info.access_chain_indices.empty()) {
+    auto* type = TypeOf(expr);
+    auto result_type_id = GenerateTypeIfNeeded(type);
+    if (result_type_id == 0) {
+      return 0;
+    }
+
+    auto result = result_op();
+    auto result_id = result.to_i();
+
+    OperandList ops = {Operand::Int(result_type_id), result,
+                       Operand::Int(info.source_id)};
+    for (auto id : info.access_chain_indices) {
+      ops.push_back(Operand::Int(id));
+    }
+
+    if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
+      return false;
+    }
+    info.source_id = result_id;
+  }
+
+  return info.source_id;
+}
+
+uint32_t Builder::GenerateIdentifierExpression(
+    const ast::IdentifierExpression* expr) {
+  uint32_t val = scope_stack_.Get(expr->symbol);
+  if (val == 0) {
+    error_ = "unable to find variable with identifier: " +
+             builder_.Symbols().NameFor(expr->symbol);
+  }
+  return val;
+}
+
+uint32_t Builder::GenerateExpressionWithLoadIfNeeded(
+    const sem::Expression* expr) {
+  // The semantic node directly knows both the AST node and the resolved type.
+  if (const auto id = GenerateExpression(expr->Declaration())) {
+    return GenerateLoadIfNeeded(expr->Type(), id);
+  }
+  return 0;
+}
+
+uint32_t Builder::GenerateExpressionWithLoadIfNeeded(
+    const ast::Expression* expr) {
+  if (const auto id = GenerateExpression(expr)) {
+    // Perform a lookup to get the resolved type.
+    return GenerateLoadIfNeeded(TypeOf(expr), id);
+  }
+  return 0;
+}
+
+uint32_t Builder::GenerateLoadIfNeeded(const sem::Type* type, uint32_t id) {
+  if (auto* ref = type->As<sem::Reference>()) {
+    type = ref->StoreType();
+  } else {
+    return id;
+  }
+
+  auto type_id = GenerateTypeIfNeeded(type);
+  auto result = result_op();
+  auto result_id = result.to_i();
+  if (!push_function_inst(spv::Op::OpLoad,
+                          {Operand::Int(type_id), result, Operand::Int(id)})) {
+    return 0;
+  }
+  return result_id;
+}
+
+uint32_t Builder::GenerateUnaryOpExpression(
+    const ast::UnaryOpExpression* expr) {
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  spv::Op op = spv::Op::OpNop;
+  switch (expr->op) {
+    case ast::UnaryOp::kComplement:
+      op = spv::Op::OpNot;
+      break;
+    case ast::UnaryOp::kNegation:
+      if (TypeOf(expr)->is_float_scalar_or_vector()) {
+        op = spv::Op::OpFNegate;
+      } else {
+        op = spv::Op::OpSNegate;
+      }
+      break;
+    case ast::UnaryOp::kNot:
+      op = spv::Op::OpLogicalNot;
+      break;
+    case ast::UnaryOp::kAddressOf:
+    case ast::UnaryOp::kIndirection:
+      // Address-of converts a reference to a pointer, and dereference converts
+      // a pointer to a reference. These are the same thing in SPIR-V, so this
+      // is a no-op.
+      return GenerateExpression(expr->expr);
+  }
+
+  auto val_id = GenerateExpressionWithLoadIfNeeded(expr->expr);
+  if (val_id == 0) {
+    return 0;
+  }
+
+  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
+  if (type_id == 0) {
+    return 0;
+  }
+
+  if (!push_function_inst(
+          op, {Operand::Int(type_id), result, Operand::Int(val_id)})) {
+    return false;
+  }
+
+  return result_id;
+}
+
+uint32_t Builder::GetGLSLstd450Import() {
+  auto where = import_name_to_id_.find(kGLSLstd450);
+  if (where != import_name_to_id_.end()) {
+    return where->second;
+  }
+
+  // It doesn't exist yet. Generate it.
+  auto result = result_op();
+  auto id = result.to_i();
+
+  push_ext_import(spv::Op::OpExtInstImport,
+                  {result, Operand::String(kGLSLstd450)});
+
+  // Remember it for later.
+  import_name_to_id_[kGLSLstd450] = id;
+  return id;
+}
+
+uint32_t Builder::GenerateConstructorExpression(const ast::Variable* var,
+                                                const ast::Expression* expr) {
+  if (auto* literal = expr->As<ast::LiteralExpression>()) {
+    return GenerateLiteralIfNeeded(var, literal);
+  }
+  if (auto* call = builder_.Sem().Get<sem::Call>(expr)) {
+    if (call->Target()->IsAnyOf<sem::TypeConstructor, sem::TypeConversion>()) {
+      return GenerateTypeConstructorOrConversion(call, var);
+    }
+  }
+  error_ = "unknown constructor expression";
+  return 0;
+}
+
+bool Builder::IsConstructorConst(const ast::Expression* expr) {
+  bool is_const = true;
+  ast::TraverseExpressions(expr, builder_.Diagnostics(),
+                           [&](const ast::Expression* e) {
+                             if (e->Is<ast::LiteralExpression>()) {
+                               return ast::TraverseAction::Descend;
+                             }
+                             if (auto* ce = e->As<ast::CallExpression>()) {
+                               auto* call = builder_.Sem().Get(ce);
+                               if (call->Target()->Is<sem::TypeConstructor>()) {
+                                 return ast::TraverseAction::Descend;
+                               }
+                             }
+
+                             is_const = false;
+                             return ast::TraverseAction::Stop;
+                           });
+  return is_const;
+}
+
+uint32_t Builder::GenerateTypeConstructorOrConversion(
+    const sem::Call* call,
+    const ast::Variable* var) {
+  auto& args = call->Arguments();
+  auto* global_var = builder_.Sem().Get<sem::GlobalVariable>(var);
+  auto* result_type = call->Type();
+
+  // Generate the zero initializer if there are no values provided.
+  if (args.empty()) {
+    if (global_var && global_var->IsOverridable()) {
+      auto constant_id = global_var->ConstantId();
+      if (result_type->Is<sem::I32>()) {
+        return GenerateConstantIfNeeded(
+            ScalarConstant::I32(0).AsSpecOp(constant_id));
+      }
+      if (result_type->Is<sem::U32>()) {
+        return GenerateConstantIfNeeded(
+            ScalarConstant::U32(0).AsSpecOp(constant_id));
+      }
+      if (result_type->Is<sem::F32>()) {
+        return GenerateConstantIfNeeded(
+            ScalarConstant::F32(0).AsSpecOp(constant_id));
+      }
+      if (result_type->Is<sem::Bool>()) {
+        return GenerateConstantIfNeeded(
+            ScalarConstant::Bool(false).AsSpecOp(constant_id));
+      }
+    }
+    return GenerateConstantNullIfNeeded(result_type->UnwrapRef());
+  }
+
+  std::ostringstream out;
+  out << "__const_" << result_type->FriendlyName(builder_.Symbols()) << "_";
+
+  result_type = result_type->UnwrapRef();
+  bool constructor_is_const = IsConstructorConst(call->Declaration());
+  if (has_error()) {
+    return 0;
+  }
+
+  bool can_cast_or_copy = result_type->is_scalar();
+
+  if (auto* res_vec = result_type->As<sem::Vector>()) {
+    if (res_vec->type()->is_scalar()) {
+      auto* value_type = args[0]->Type()->UnwrapRef();
+      if (auto* val_vec = value_type->As<sem::Vector>()) {
+        if (val_vec->type()->is_scalar()) {
+          can_cast_or_copy = res_vec->Width() == val_vec->Width();
+        }
+      }
+    }
+  }
+
+  if (can_cast_or_copy) {
+    return GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(),
+                                           global_var);
+  }
+
+  auto type_id = GenerateTypeIfNeeded(result_type);
+  if (type_id == 0) {
+    return 0;
+  }
+
+  bool result_is_constant_composite = constructor_is_const;
+  bool result_is_spec_composite = false;
+
+  if (auto* vec = result_type->As<sem::Vector>()) {
+    result_type = vec->type();
+  }
+
+  OperandList ops;
+  for (auto* e : args) {
+    uint32_t id = 0;
+    id = GenerateExpressionWithLoadIfNeeded(e);
+    if (id == 0) {
+      return 0;
+    }
+
+    auto* value_type = e->Type()->UnwrapRef();
+    // If the result and value types are the same we can just use the object.
+    // If the result is not a vector then we should have validated that the
+    // value type is a correctly sized vector so we can just use it directly.
+    if (result_type == value_type || result_type->Is<sem::Matrix>() ||
+        result_type->Is<sem::Array>() || result_type->Is<sem::Struct>()) {
+      out << "_" << id;
+
+      ops.push_back(Operand::Int(id));
+      continue;
+    }
+
+    // Both scalars, but not the same type so we need to generate a conversion
+    // of the value.
+    if (value_type->is_scalar() && result_type->is_scalar()) {
+      id = GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(),
+                                           global_var);
+      out << "_" << id;
+      ops.push_back(Operand::Int(id));
+      continue;
+    }
+
+    // When handling vectors as the values there a few cases to take into
+    // consideration:
+    //  1. Module scoped vec3<f32>(vec2<f32>(1, 2), 3)  -> OpSpecConstantOp
+    //  2. Function scoped vec3<f32>(vec2<f32>(1, 2), 3) ->  OpCompositeExtract
+    //  3. Either array<vec3<f32>, 1>(vec3<f32>(1, 2, 3))  -> use the ID.
+    //       -> handled above
+    //
+    // For cases 1 and 2, if the type is different we also may need to insert
+    // a type cast.
+    if (auto* vec = value_type->As<sem::Vector>()) {
+      auto* vec_type = vec->type();
+
+      auto value_type_id = GenerateTypeIfNeeded(vec_type);
+      if (value_type_id == 0) {
+        return 0;
+      }
+
+      for (uint32_t i = 0; i < vec->Width(); ++i) {
+        auto extract = result_op();
+        auto extract_id = extract.to_i();
+
+        if (!global_var) {
+          // A non-global initializer. Case 2.
+          if (!push_function_inst(spv::Op::OpCompositeExtract,
+                                  {Operand::Int(value_type_id), extract,
+                                   Operand::Int(id), Operand::Int(i)})) {
+            return false;
+          }
+
+          // We no longer have a constant composite, but have to do a
+          // composite construction as these calls are inside a function.
+          result_is_constant_composite = false;
+        } else {
+          // A global initializer, must use OpSpecConstantOp. Case 1.
+          auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(i));
+          if (idx_id == 0) {
+            return 0;
+          }
+          push_type(spv::Op::OpSpecConstantOp,
+                    {Operand::Int(value_type_id), extract,
+                     Operand::Int(SpvOpCompositeExtract), Operand::Int(id),
+                     Operand::Int(idx_id)});
+
+          result_is_spec_composite = true;
+        }
+
+        out << "_" << extract_id;
+        ops.push_back(Operand::Int(extract_id));
+      }
+    } else {
+      error_ = "Unhandled type cast value type";
+      return 0;
+    }
+  }
+
+  // For a single-value vector initializer, splat the initializer value.
+  auto* const init_result_type = call->Type()->UnwrapRef();
+  if (args.size() == 1 && init_result_type->is_scalar_vector() &&
+      args[0]->Type()->UnwrapRef()->is_scalar()) {
+    size_t vec_size = init_result_type->As<sem::Vector>()->Width();
+    for (size_t i = 0; i < (vec_size - 1); ++i) {
+      ops.push_back(ops[0]);
+    }
+  }
+
+  auto str = out.str();
+  auto val = type_constructor_to_id_.find(str);
+  if (val != type_constructor_to_id_.end()) {
+    return val->second;
+  }
+
+  auto result = result_op();
+  ops.insert(ops.begin(), result);
+  ops.insert(ops.begin(), Operand::Int(type_id));
+
+  type_constructor_to_id_[str] = result.to_i();
+
+  if (result_is_spec_composite) {
+    push_type(spv::Op::OpSpecConstantComposite, ops);
+  } else if (result_is_constant_composite) {
+    push_type(spv::Op::OpConstantComposite, ops);
+  } else {
+    if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
+      return 0;
+    }
+  }
+
+  return result.to_i();
+}
+
+uint32_t Builder::GenerateCastOrCopyOrPassthrough(
+    const sem::Type* to_type,
+    const ast::Expression* from_expr,
+    bool is_global_init) {
+  // This should not happen as we rely on constant folding to obviate
+  // casts/conversions for module-scope variables
+  if (is_global_init) {
+    TINT_ICE(Writer, builder_.Diagnostics())
+        << "Module-level conversions are not supported. Conversions should "
+           "have already been constant-folded by the FoldConstants transform.";
+    return 0;
+  }
+
+  auto elem_type_of = [](const sem::Type* t) -> const sem::Type* {
+    if (t->is_scalar()) {
+      return t;
+    }
+    if (auto* v = t->As<sem::Vector>()) {
+      return v->type();
+    }
+    return nullptr;
+  };
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  auto result_type_id = GenerateTypeIfNeeded(to_type);
+  if (result_type_id == 0) {
+    return 0;
+  }
+
+  auto val_id = GenerateExpressionWithLoadIfNeeded(from_expr);
+  if (val_id == 0) {
+    return 0;
+  }
+
+  auto* from_type = TypeOf(from_expr)->UnwrapRef();
+
+  spv::Op op = spv::Op::OpNop;
+  if ((from_type->Is<sem::I32>() && to_type->Is<sem::F32>()) ||
+      (from_type->is_signed_integer_vector() && to_type->is_float_vector())) {
+    op = spv::Op::OpConvertSToF;
+  } else if ((from_type->Is<sem::U32>() && to_type->Is<sem::F32>()) ||
+             (from_type->is_unsigned_integer_vector() &&
+              to_type->is_float_vector())) {
+    op = spv::Op::OpConvertUToF;
+  } else if ((from_type->Is<sem::F32>() && to_type->Is<sem::I32>()) ||
+             (from_type->is_float_vector() &&
+              to_type->is_signed_integer_vector())) {
+    op = spv::Op::OpConvertFToS;
+  } else if ((from_type->Is<sem::F32>() && to_type->Is<sem::U32>()) ||
+             (from_type->is_float_vector() &&
+              to_type->is_unsigned_integer_vector())) {
+    op = spv::Op::OpConvertFToU;
+  } else if ((from_type->Is<sem::Bool>() && to_type->Is<sem::Bool>()) ||
+             (from_type->Is<sem::U32>() && to_type->Is<sem::U32>()) ||
+             (from_type->Is<sem::I32>() && to_type->Is<sem::I32>()) ||
+             (from_type->Is<sem::F32>() && to_type->Is<sem::F32>()) ||
+             (from_type->Is<sem::Vector>() && (from_type == to_type))) {
+    return val_id;
+  } else if ((from_type->Is<sem::I32>() && to_type->Is<sem::U32>()) ||
+             (from_type->Is<sem::U32>() && to_type->Is<sem::I32>()) ||
+             (from_type->is_signed_integer_vector() &&
+              to_type->is_unsigned_integer_vector()) ||
+             (from_type->is_unsigned_integer_vector() &&
+              to_type->is_integer_scalar_or_vector())) {
+    op = spv::Op::OpBitcast;
+  } else if ((from_type->is_numeric_scalar() && to_type->Is<sem::Bool>()) ||
+             (from_type->is_numeric_vector() && to_type->is_bool_vector())) {
+    // Convert scalar (vector) to bool (vector)
+
+    // Return the result of comparing from_expr with zero
+    uint32_t zero = GenerateConstantNullIfNeeded(from_type);
+    const auto* from_elem_type = elem_type_of(from_type);
+    op = from_elem_type->is_integer_scalar() ? spv::Op::OpINotEqual
+                                             : spv::Op::OpFUnordNotEqual;
+    if (!push_function_inst(
+            op, {Operand::Int(result_type_id), Operand::Int(result_id),
+                 Operand::Int(val_id), Operand::Int(zero)})) {
+      return 0;
+    }
+
+    return result_id;
+  } else if (from_type->is_bool_scalar_or_vector() &&
+             to_type->is_numeric_scalar_or_vector()) {
+    // Convert bool scalar/vector to numeric scalar/vector.
+    // Use the bool to select between 1 (if true) and 0 (if false).
+
+    const auto* to_elem_type = elem_type_of(to_type);
+    uint32_t one_id;
+    uint32_t zero_id;
+    if (to_elem_type->Is<sem::F32>()) {
+      ast::FloatLiteralExpression one(ProgramID(), Source{}, 1.0f);
+      ast::FloatLiteralExpression zero(ProgramID(), Source{}, 0.0f);
+      one_id = GenerateLiteralIfNeeded(nullptr, &one);
+      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+    } else if (to_elem_type->Is<sem::U32>()) {
+      ast::UintLiteralExpression one(ProgramID(), Source{}, 1);
+      ast::UintLiteralExpression zero(ProgramID(), Source{}, 0);
+      one_id = GenerateLiteralIfNeeded(nullptr, &one);
+      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+    } else if (to_elem_type->Is<sem::I32>()) {
+      ast::SintLiteralExpression one(ProgramID(), Source{}, 1);
+      ast::SintLiteralExpression zero(ProgramID(), Source{}, 0);
+      one_id = GenerateLiteralIfNeeded(nullptr, &one);
+      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+    } else {
+      error_ = "invalid destination type for bool conversion";
+      return false;
+    }
+    if (auto* to_vec = to_type->As<sem::Vector>()) {
+      // Splat the scalars into vectors.
+      one_id = GenerateConstantVectorSplatIfNeeded(to_vec, one_id);
+      zero_id = GenerateConstantVectorSplatIfNeeded(to_vec, zero_id);
+    }
+    if (!one_id || !zero_id) {
+      return false;
+    }
+
+    op = spv::Op::OpSelect;
+    if (!push_function_inst(
+            op, {Operand::Int(result_type_id), Operand::Int(result_id),
+                 Operand::Int(val_id), Operand::Int(one_id),
+                 Operand::Int(zero_id)})) {
+      return 0;
+    }
+
+    return result_id;
+  } else {
+    TINT_ICE(Writer, builder_.Diagnostics()) << "Invalid from_type";
+  }
+
+  if (op == spv::Op::OpNop) {
+    error_ = "unable to determine conversion type for cast, from: " +
+             from_type->type_name() + " to: " + to_type->type_name();
+    return 0;
+  }
+
+  if (!push_function_inst(
+          op, {Operand::Int(result_type_id), result, Operand::Int(val_id)})) {
+    return 0;
+  }
+
+  return result_id;
+}
+
+uint32_t Builder::GenerateLiteralIfNeeded(const ast::Variable* var,
+                                          const ast::LiteralExpression* lit) {
+  ScalarConstant constant;
+
+  auto* global = builder_.Sem().Get<sem::GlobalVariable>(var);
+  if (global && global->IsOverridable()) {
+    constant.is_spec_op = true;
+    constant.constant_id = global->ConstantId();
+  }
+
+  Switch(
+      lit,
+      [&](const ast::BoolLiteralExpression* l) {
+        constant.kind = ScalarConstant::Kind::kBool;
+        constant.value.b = l->value;
+      },
+      [&](const ast::SintLiteralExpression* sl) {
+        constant.kind = ScalarConstant::Kind::kI32;
+        constant.value.i32 = sl->value;
+      },
+      [&](const ast::UintLiteralExpression* ul) {
+        constant.kind = ScalarConstant::Kind::kU32;
+        constant.value.u32 = ul->value;
+      },
+      [&](const ast::FloatLiteralExpression* fl) {
+        constant.kind = ScalarConstant::Kind::kF32;
+        constant.value.f32 = fl->value;
+      },
+      [&](Default) { error_ = "unknown literal type"; });
+
+  if (!error_.empty()) {
+    return false;
+  }
+
+  return GenerateConstantIfNeeded(constant);
+}
+
+uint32_t Builder::GenerateConstantIfNeeded(const ScalarConstant& constant) {
+  auto it = const_to_id_.find(constant);
+  if (it != const_to_id_.end()) {
+    return it->second;
+  }
+
+  uint32_t type_id = 0;
+
+  switch (constant.kind) {
+    case ScalarConstant::Kind::kU32: {
+      type_id = GenerateTypeIfNeeded(builder_.create<sem::U32>());
+      break;
+    }
+    case ScalarConstant::Kind::kI32: {
+      type_id = GenerateTypeIfNeeded(builder_.create<sem::I32>());
+      break;
+    }
+    case ScalarConstant::Kind::kF32: {
+      type_id = GenerateTypeIfNeeded(builder_.create<sem::F32>());
+      break;
+    }
+    case ScalarConstant::Kind::kBool: {
+      type_id = GenerateTypeIfNeeded(builder_.create<sem::Bool>());
+      break;
+    }
+  }
+
+  if (type_id == 0) {
+    return 0;
+  }
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  if (constant.is_spec_op) {
+    push_annot(spv::Op::OpDecorate,
+               {Operand::Int(result_id), Operand::Int(SpvDecorationSpecId),
+                Operand::Int(constant.constant_id)});
+  }
+
+  switch (constant.kind) {
+    case ScalarConstant::Kind::kU32: {
+      push_type(
+          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
+          {Operand::Int(type_id), result, Operand::Int(constant.value.u32)});
+      break;
+    }
+    case ScalarConstant::Kind::kI32: {
+      push_type(
+          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
+          {Operand::Int(type_id), result, Operand::Int(constant.value.i32)});
+      break;
+    }
+    case ScalarConstant::Kind::kF32: {
+      push_type(
+          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
+          {Operand::Int(type_id), result, Operand::Float(constant.value.f32)});
+      break;
+    }
+    case ScalarConstant::Kind::kBool: {
+      if (constant.value.b) {
+        push_type(constant.is_spec_op ? spv::Op::OpSpecConstantTrue
+                                      : spv::Op::OpConstantTrue,
+                  {Operand::Int(type_id), result});
+      } else {
+        push_type(constant.is_spec_op ? spv::Op::OpSpecConstantFalse
+                                      : spv::Op::OpConstantFalse,
+                  {Operand::Int(type_id), result});
+      }
+      break;
+    }
+  }
+
+  const_to_id_[constant] = result_id;
+  return result_id;
+}
+
+uint32_t Builder::GenerateConstantNullIfNeeded(const sem::Type* type) {
+  auto type_id = GenerateTypeIfNeeded(type);
+  if (type_id == 0) {
+    return 0;
+  }
+
+  auto name = type->type_name();
+
+  auto it = const_null_to_id_.find(name);
+  if (it != const_null_to_id_.end()) {
+    return it->second;
+  }
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  push_type(spv::Op::OpConstantNull, {Operand::Int(type_id), result});
+
+  const_null_to_id_[name] = result_id;
+  return result_id;
+}
+
+uint32_t Builder::GenerateConstantVectorSplatIfNeeded(const sem::Vector* type,
+                                                      uint32_t value_id) {
+  auto type_id = GenerateTypeIfNeeded(type);
+  if (type_id == 0 || value_id == 0) {
+    return 0;
+  }
+
+  uint64_t key = (static_cast<uint64_t>(type->Width()) << 32) + value_id;
+  return utils::GetOrCreate(const_splat_to_id_, key, [&] {
+    auto result = result_op();
+    auto result_id = result.to_i();
+
+    OperandList ops;
+    ops.push_back(Operand::Int(type_id));
+    ops.push_back(result);
+    for (uint32_t i = 0; i < type->Width(); i++) {
+      ops.push_back(Operand::Int(value_id));
+    }
+    push_type(spv::Op::OpConstantComposite, ops);
+
+    const_splat_to_id_[key] = result_id;
+    return result_id;
+  });
+}
+
+uint32_t Builder::GenerateShortCircuitBinaryExpression(
+    const ast::BinaryExpression* expr) {
+  auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
+  if (lhs_id == 0) {
+    return false;
+  }
+
+  // Get the ID of the basic block where control flow will diverge. It's the
+  // last basic block generated for the left-hand-side of the operator.
+  auto original_label_id = current_label_id_;
+
+  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
+  if (type_id == 0) {
+    return 0;
+  }
+
+  auto merge_block = result_op();
+  auto merge_block_id = merge_block.to_i();
+
+  auto block = result_op();
+  auto block_id = block.to_i();
+
+  auto true_block_id = block_id;
+  auto false_block_id = merge_block_id;
+
+  // For a logical or we want to only check the RHS if the LHS is failed.
+  if (expr->IsLogicalOr()) {
+    std::swap(true_block_id, false_block_id);
+  }
+
+  if (!push_function_inst(spv::Op::OpSelectionMerge,
+                          {Operand::Int(merge_block_id),
+                           Operand::Int(SpvSelectionControlMaskNone)})) {
+    return 0;
+  }
+  if (!push_function_inst(spv::Op::OpBranchConditional,
+                          {Operand::Int(lhs_id), Operand::Int(true_block_id),
+                           Operand::Int(false_block_id)})) {
+    return 0;
+  }
+
+  // Output block to check the RHS
+  if (!GenerateLabel(block_id)) {
+    return 0;
+  }
+  auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
+  if (rhs_id == 0) {
+    return 0;
+  }
+
+  // Get the block ID of the last basic block generated for the right-hand-side
+  // expression. That block will be an immediate predecessor to the merge block.
+  auto rhs_block_id = current_label_id_;
+  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(merge_block_id)})) {
+    return 0;
+  }
+
+  // Output the merge block
+  if (!GenerateLabel(merge_block_id)) {
+    return 0;
+  }
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  if (!push_function_inst(spv::Op::OpPhi,
+                          {Operand::Int(type_id), result, Operand::Int(lhs_id),
+                           Operand::Int(original_label_id),
+                           Operand::Int(rhs_id), Operand::Int(rhs_block_id)})) {
+    return 0;
+  }
+
+  return result_id;
+}
+
+uint32_t Builder::GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type) {
+  // Create a new vector to splat scalar into
+  auto splat_vector = result_op();
+  auto* splat_vector_type = builder_.create<sem::Pointer>(
+      vec_type, ast::StorageClass::kFunction, ast::Access::kReadWrite);
+  push_function_var(
+      {Operand::Int(GenerateTypeIfNeeded(splat_vector_type)), splat_vector,
+       Operand::Int(ConvertStorageClass(ast::StorageClass::kFunction)),
+       Operand::Int(GenerateConstantNullIfNeeded(vec_type))});
+
+  // Splat scalar into vector
+  auto splat_result = result_op();
+  OperandList ops;
+  ops.push_back(Operand::Int(GenerateTypeIfNeeded(vec_type)));
+  ops.push_back(splat_result);
+  for (size_t i = 0; i < vec_type->As<sem::Vector>()->Width(); ++i) {
+    ops.push_back(Operand::Int(scalar_id));
+  }
+  if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
+    return 0;
+  }
+
+  return splat_result.to_i();
+}
+
+uint32_t Builder::GenerateMatrixAddOrSub(uint32_t lhs_id,
+                                         uint32_t rhs_id,
+                                         const sem::Matrix* type,
+                                         spv::Op op) {
+  // Example addition of two matrices:
+  // %31 = OpLoad %mat3v4float %m34
+  // %32 = OpLoad %mat3v4float %m34
+  // %33 = OpCompositeExtract %v4float %31 0
+  // %34 = OpCompositeExtract %v4float %32 0
+  // %35 = OpFAdd %v4float %33 %34
+  // %36 = OpCompositeExtract %v4float %31 1
+  // %37 = OpCompositeExtract %v4float %32 1
+  // %38 = OpFAdd %v4float %36 %37
+  // %39 = OpCompositeExtract %v4float %31 2
+  // %40 = OpCompositeExtract %v4float %32 2
+  // %41 = OpFAdd %v4float %39 %40
+  // %42 = OpCompositeConstruct %mat3v4float %35 %38 %41
+
+  auto* column_type = builder_.create<sem::Vector>(type->type(), type->rows());
+  auto column_type_id = GenerateTypeIfNeeded(column_type);
+
+  OperandList ops;
+
+  for (uint32_t i = 0; i < type->columns(); ++i) {
+    // Extract column `i` from lhs mat
+    auto lhs_column_id = result_op();
+    if (!push_function_inst(spv::Op::OpCompositeExtract,
+                            {Operand::Int(column_type_id), lhs_column_id,
+                             Operand::Int(lhs_id), Operand::Int(i)})) {
+      return 0;
+    }
+
+    // Extract column `i` from rhs mat
+    auto rhs_column_id = result_op();
+    if (!push_function_inst(spv::Op::OpCompositeExtract,
+                            {Operand::Int(column_type_id), rhs_column_id,
+                             Operand::Int(rhs_id), Operand::Int(i)})) {
+      return 0;
+    }
+
+    // Add or subtract the two columns
+    auto result = result_op();
+    if (!push_function_inst(op, {Operand::Int(column_type_id), result,
+                                 lhs_column_id, rhs_column_id})) {
+      return 0;
+    }
+
+    ops.push_back(result);
+  }
+
+  // Create the result matrix from the added/subtracted column vectors
+  auto result_mat_id = result_op();
+  ops.insert(ops.begin(), result_mat_id);
+  ops.insert(ops.begin(), Operand::Int(GenerateTypeIfNeeded(type)));
+  if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
+    return 0;
+  }
+
+  return result_mat_id.to_i();
+}
+
+uint32_t Builder::GenerateBinaryExpression(const ast::BinaryExpression* expr) {
+  // There is special logic for short circuiting operators.
+  if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
+    return GenerateShortCircuitBinaryExpression(expr);
+  }
+
+  auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
+  if (lhs_id == 0) {
+    return 0;
+  }
+
+  auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
+  if (rhs_id == 0) {
+    return 0;
+  }
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
+  if (type_id == 0) {
+    return 0;
+  }
+
+  // Handle int and float and the vectors of those types. Other types
+  // should have been rejected by validation.
+  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
+  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
+
+  // Handle matrix-matrix addition and subtraction
+  if ((expr->IsAdd() || expr->IsSubtract()) && lhs_type->is_float_matrix() &&
+      rhs_type->is_float_matrix()) {
+    auto* lhs_mat = lhs_type->As<sem::Matrix>();
+    auto* rhs_mat = rhs_type->As<sem::Matrix>();
+
+    // This should already have been validated by resolver
+    if (lhs_mat->rows() != rhs_mat->rows() ||
+        lhs_mat->columns() != rhs_mat->columns()) {
+      error_ = "matrices must have same dimensionality for add or subtract";
+      return 0;
+    }
+
+    return GenerateMatrixAddOrSub(
+        lhs_id, rhs_id, lhs_mat,
+        expr->IsAdd() ? spv::Op::OpFAdd : spv::Op::OpFSub);
+  }
+
+  // For vector-scalar arithmetic operations, splat scalar into a vector. We
+  // skip this for multiply as we can use OpVectorTimesScalar.
+  const bool is_float_scalar_vector_multiply =
+      expr->IsMultiply() &&
+      ((lhs_type->is_float_scalar() && rhs_type->is_float_vector()) ||
+       (lhs_type->is_float_vector() && rhs_type->is_float_scalar()));
+
+  if (expr->IsArithmetic() && !is_float_scalar_vector_multiply) {
+    if (lhs_type->Is<sem::Vector>() && rhs_type->is_numeric_scalar()) {
+      uint32_t splat_vector_id = GenerateSplat(rhs_id, lhs_type);
+      if (splat_vector_id == 0) {
+        return 0;
+      }
+      rhs_id = splat_vector_id;
+      rhs_type = lhs_type;
+
+    } else if (lhs_type->is_numeric_scalar() && rhs_type->Is<sem::Vector>()) {
+      uint32_t splat_vector_id = GenerateSplat(lhs_id, rhs_type);
+      if (splat_vector_id == 0) {
+        return 0;
+      }
+      lhs_id = splat_vector_id;
+      lhs_type = rhs_type;
+    }
+  }
+
+  bool lhs_is_float_or_vec = lhs_type->is_float_scalar_or_vector();
+  bool lhs_is_bool_or_vec = lhs_type->is_bool_scalar_or_vector();
+  bool lhs_is_integer_or_vec = lhs_type->is_integer_scalar_or_vector();
+  bool lhs_is_unsigned = lhs_type->is_unsigned_scalar_or_vector();
+
+  spv::Op op = spv::Op::OpNop;
+  if (expr->IsAnd()) {
+    if (lhs_is_integer_or_vec) {
+      op = spv::Op::OpBitwiseAnd;
+    } else if (lhs_is_bool_or_vec) {
+      op = spv::Op::OpLogicalAnd;
+    } else {
+      error_ = "invalid and expression";
+      return 0;
+    }
+  } else if (expr->IsAdd()) {
+    op = lhs_is_float_or_vec ? spv::Op::OpFAdd : spv::Op::OpIAdd;
+  } else if (expr->IsDivide()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFDiv;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpUDiv;
+    } else {
+      op = spv::Op::OpSDiv;
+    }
+  } else if (expr->IsEqual()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdEqual;
+    } else if (lhs_is_bool_or_vec) {
+      op = spv::Op::OpLogicalEqual;
+    } else if (lhs_is_integer_or_vec) {
+      op = spv::Op::OpIEqual;
+    } else {
+      error_ = "invalid equal expression";
+      return 0;
+    }
+  } else if (expr->IsGreaterThan()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdGreaterThan;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpUGreaterThan;
+    } else {
+      op = spv::Op::OpSGreaterThan;
+    }
+  } else if (expr->IsGreaterThanEqual()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdGreaterThanEqual;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpUGreaterThanEqual;
+    } else {
+      op = spv::Op::OpSGreaterThanEqual;
+    }
+  } else if (expr->IsLessThan()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdLessThan;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpULessThan;
+    } else {
+      op = spv::Op::OpSLessThan;
+    }
+  } else if (expr->IsLessThanEqual()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdLessThanEqual;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpULessThanEqual;
+    } else {
+      op = spv::Op::OpSLessThanEqual;
+    }
+  } else if (expr->IsModulo()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFRem;
+    } else if (lhs_is_unsigned) {
+      op = spv::Op::OpUMod;
+    } else {
+      op = spv::Op::OpSMod;
+    }
+  } else if (expr->IsMultiply()) {
+    if (lhs_type->is_integer_scalar_or_vector()) {
+      // If the left hand side is an integer then this _has_ to be OpIMul as
+      // there there is no other integer multiplication.
+      op = spv::Op::OpIMul;
+    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_scalar()) {
+      // Float scalars multiply with OpFMul
+      op = spv::Op::OpFMul;
+    } else if (lhs_type->is_float_vector() && rhs_type->is_float_vector()) {
+      // Float vectors must be validated to be the same size and then use OpFMul
+      op = spv::Op::OpFMul;
+    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_vector()) {
+      // Scalar * Vector we need to flip lhs and rhs types
+      // because OpVectorTimesScalar expects <vector>, <scalar>
+      std::swap(lhs_id, rhs_id);
+      op = spv::Op::OpVectorTimesScalar;
+    } else if (lhs_type->is_float_vector() && rhs_type->is_float_scalar()) {
+      // float vector * scalar
+      op = spv::Op::OpVectorTimesScalar;
+    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_matrix()) {
+      // Scalar * Matrix we need to flip lhs and rhs types because
+      // OpMatrixTimesScalar expects <matrix>, <scalar>
+      std::swap(lhs_id, rhs_id);
+      op = spv::Op::OpMatrixTimesScalar;
+    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_scalar()) {
+      // float matrix * scalar
+      op = spv::Op::OpMatrixTimesScalar;
+    } else if (lhs_type->is_float_vector() && rhs_type->is_float_matrix()) {
+      // float vector * matrix
+      op = spv::Op::OpVectorTimesMatrix;
+    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_vector()) {
+      // float matrix * vector
+      op = spv::Op::OpMatrixTimesVector;
+    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_matrix()) {
+      // float matrix * matrix
+      op = spv::Op::OpMatrixTimesMatrix;
+    } else {
+      error_ = "invalid multiply expression";
+      return 0;
+    }
+  } else if (expr->IsNotEqual()) {
+    if (lhs_is_float_or_vec) {
+      op = spv::Op::OpFOrdNotEqual;
+    } else if (lhs_is_bool_or_vec) {
+      op = spv::Op::OpLogicalNotEqual;
+    } else if (lhs_is_integer_or_vec) {
+      op = spv::Op::OpINotEqual;
+    } else {
+      error_ = "invalid not-equal expression";
+      return 0;
+    }
+  } else if (expr->IsOr()) {
+    if (lhs_is_integer_or_vec) {
+      op = spv::Op::OpBitwiseOr;
+    } else if (lhs_is_bool_or_vec) {
+      op = spv::Op::OpLogicalOr;
+    } else {
+      error_ = "invalid and expression";
+      return 0;
+    }
+  } else if (expr->IsShiftLeft()) {
+    op = spv::Op::OpShiftLeftLogical;
+  } else if (expr->IsShiftRight() && lhs_type->is_signed_scalar_or_vector()) {
+    // A shift right with a signed LHS is an arithmetic shift.
+    op = spv::Op::OpShiftRightArithmetic;
+  } else if (expr->IsShiftRight()) {
+    op = spv::Op::OpShiftRightLogical;
+  } else if (expr->IsSubtract()) {
+    op = lhs_is_float_or_vec ? spv::Op::OpFSub : spv::Op::OpISub;
+  } else if (expr->IsXor()) {
+    op = spv::Op::OpBitwiseXor;
+  } else {
+    error_ = "unknown binary expression";
+    return 0;
+  }
+
+  if (!push_function_inst(op, {Operand::Int(type_id), result,
+                               Operand::Int(lhs_id), Operand::Int(rhs_id)})) {
+    return 0;
+  }
+  return result_id;
+}
+
+bool Builder::GenerateBlockStatement(const ast::BlockStatement* stmt) {
+  scope_stack_.Push();
+  TINT_DEFER(scope_stack_.Pop());
+  return GenerateBlockStatementWithoutScoping(stmt);
+}
+
+bool Builder::GenerateBlockStatementWithoutScoping(
+    const ast::BlockStatement* stmt) {
+  for (auto* block_stmt : stmt->statements) {
+    if (!GenerateStatement(block_stmt)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+uint32_t Builder::GenerateCallExpression(const ast::CallExpression* expr) {
+  auto* call = builder_.Sem().Get(expr);
+  auto* target = call->Target();
+  return Switch(
+      target,
+      [&](const sem::Function* func) {
+        return GenerateFunctionCall(call, func);
+      },
+      [&](const sem::Builtin* builtin) {
+        return GenerateBuiltinCall(call, builtin);
+      },
+      [&](const sem::TypeConversion*) {
+        return GenerateTypeConstructorOrConversion(call, nullptr);
+      },
+      [&](const sem::TypeConstructor*) {
+        return GenerateTypeConstructorOrConversion(call, nullptr);
+      },
+      [&](Default) -> uint32_t {
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unhandled call target: " << target->TypeInfo().name;
+        return 0;
+      });
+}
+
+uint32_t Builder::GenerateFunctionCall(const sem::Call* call,
+                                       const sem::Function*) {
+  auto* expr = call->Declaration();
+  auto* ident = expr->target.name;
+
+  auto type_id = GenerateTypeIfNeeded(call->Type());
+  if (type_id == 0) {
+    return 0;
+  }
+
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  OperandList ops = {Operand::Int(type_id), result};
+
+  auto func_id = func_symbol_to_id_[ident->symbol];
+  if (func_id == 0) {
+    error_ = "unable to find called function: " +
+             builder_.Symbols().NameFor(ident->symbol);
+    return 0;
+  }
+  ops.push_back(Operand::Int(func_id));
+
+  size_t arg_idx = 0;
+  for (auto* arg : expr->args) {
+    auto id = GenerateExpressionWithLoadIfNeeded(arg);
+    if (id == 0) {
+      return 0;
+    }
+    ops.push_back(Operand::Int(id));
+    arg_idx++;
+  }
+
+  if (!push_function_inst(spv::Op::OpFunctionCall, std::move(ops))) {
+    return 0;
+  }
+
+  return result_id;
+}
+
+uint32_t Builder::GenerateBuiltinCall(const sem::Call* call,
+                                      const sem::Builtin* builtin) {
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  auto result_type_id = GenerateTypeIfNeeded(builtin->ReturnType());
+  if (result_type_id == 0) {
+    return 0;
+  }
+
+  if (builtin->IsFineDerivative() || builtin->IsCoarseDerivative()) {
+    push_capability(SpvCapabilityDerivativeControl);
+  }
+
+  if (builtin->IsImageQuery()) {
+    push_capability(SpvCapabilityImageQuery);
+  }
+
+  if (builtin->IsTexture()) {
+    if (!GenerateTextureBuiltin(call, builtin, Operand::Int(result_type_id),
+                                result)) {
+      return 0;
+    }
+    return result_id;
+  }
+
+  if (builtin->IsBarrier()) {
+    if (!GenerateControlBarrierBuiltin(builtin)) {
+      return 0;
+    }
+    return result_id;
+  }
+
+  if (builtin->IsAtomic()) {
+    if (!GenerateAtomicBuiltin(call, builtin, Operand::Int(result_type_id),
+                               result)) {
+      return 0;
+    }
+    return result_id;
+  }
+
+  // Generates the SPIR-V ID for the expression for the indexed call argument,
+  // and loads it if necessary. Returns 0 on error.
+  auto get_arg_as_value_id = [&](size_t i,
+                                 bool generate_load = true) -> uint32_t {
+    auto* arg = call->Arguments()[i];
+    auto* param = builtin->Parameters()[i];
+    auto val_id = GenerateExpression(arg->Declaration());
+    if (val_id == 0) {
+      return 0;
+    }
+
+    if (generate_load && !param->Type()->Is<sem::Pointer>()) {
+      val_id = GenerateLoadIfNeeded(arg->Type(), val_id);
+    }
+    return val_id;
+  };
+
+  OperandList params = {Operand::Int(result_type_id), result};
+  spv::Op op = spv::Op::OpNop;
+
+  // Pushes the arguments for a GlslStd450 extended instruction, and sets op
+  // to OpExtInst.
+  auto glsl_std450 = [&](uint32_t inst_id) {
+    auto set_id = GetGLSLstd450Import();
+    params.push_back(Operand::Int(set_id));
+    params.push_back(Operand::Int(inst_id));
+    op = spv::Op::OpExtInst;
+  };
+
+  switch (builtin->Type()) {
+    case BuiltinType::kAny:
+      if (builtin->Parameters()[0]->Type()->Is<sem::Bool>()) {
+        // any(v: bool) just resolves to v.
+        return get_arg_as_value_id(0);
+      }
+      op = spv::Op::OpAny;
+      break;
+    case BuiltinType::kAll:
+      if (builtin->Parameters()[0]->Type()->Is<sem::Bool>()) {
+        // all(v: bool) just resolves to v.
+        return get_arg_as_value_id(0);
+      }
+      op = spv::Op::OpAll;
+      break;
+    case BuiltinType::kArrayLength: {
+      auto* address_of =
+          call->Arguments()[0]->Declaration()->As<ast::UnaryOpExpression>();
+      if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
+        error_ = "arrayLength() expected pointer to member access, got " +
+                 std::string(address_of->TypeInfo().name);
+        return 0;
+      }
+      auto* array_expr = address_of->expr;
+
+      auto* accessor = array_expr->As<ast::MemberAccessorExpression>();
+      if (!accessor) {
+        error_ =
+            "arrayLength() expected pointer to member access, got pointer to " +
+            std::string(array_expr->TypeInfo().name);
+        return 0;
+      }
+
+      auto struct_id = GenerateExpression(accessor->structure);
+      if (struct_id == 0) {
+        return 0;
+      }
+      params.push_back(Operand::Int(struct_id));
+
+      auto* type = TypeOf(accessor->structure)->UnwrapRef();
+      if (!type->Is<sem::Struct>()) {
+        error_ =
+            "invalid type (" + type->type_name() + ") for runtime array length";
+        return 0;
+      }
+      // Runtime array must be the last member in the structure
+      params.push_back(Operand::Int(uint32_t(
+          type->As<sem::Struct>()->Declaration()->members.size() - 1)));
+
+      if (!push_function_inst(spv::Op::OpArrayLength, params)) {
+        return 0;
+      }
+      return result_id;
+    }
+    case BuiltinType::kCountOneBits:
+      op = spv::Op::OpBitCount;
+      break;
+    case BuiltinType::kDot: {
+      op = spv::Op::OpDot;
+      auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
+      if (vec_ty->type()->is_integer_scalar()) {
+        // TODO(crbug.com/tint/1267): OpDot requires floating-point types, but
+        // WGSL also supports integer types. SPV_KHR_integer_dot_product adds
+        // support for integer vectors. Use it if it is available.
+        auto el_ty = Operand::Int(GenerateTypeIfNeeded(vec_ty->type()));
+        auto vec_a = Operand::Int(get_arg_as_value_id(0));
+        auto vec_b = Operand::Int(get_arg_as_value_id(1));
+        if (vec_a.to_i() == 0 || vec_b.to_i() == 0) {
+          return 0;
+        }
+
+        auto sum = Operand::Int(0);
+        for (uint32_t i = 0; i < vec_ty->Width(); i++) {
+          auto a = result_op();
+          auto b = result_op();
+          auto mul = result_op();
+          if (!push_function_inst(spv::Op::OpCompositeExtract,
+                                  {el_ty, a, vec_a, Operand::Int(i)}) ||
+              !push_function_inst(spv::Op::OpCompositeExtract,
+                                  {el_ty, b, vec_b, Operand::Int(i)}) ||
+              !push_function_inst(spv::Op::OpIMul, {el_ty, mul, a, b})) {
+            return 0;
+          }
+          if (i == 0) {
+            sum = mul;
+          } else {
+            auto prev_sum = sum;
+            auto is_last_el = i == (vec_ty->Width() - 1);
+            sum = is_last_el ? Operand::Int(result_id) : result_op();
+            if (!push_function_inst(spv::Op::OpIAdd,
+                                    {el_ty, sum, prev_sum, mul})) {
+              return 0;
+            }
+          }
+        }
+        return result_id;
+      }
+      break;
+    }
+    case BuiltinType::kDpdx:
+      op = spv::Op::OpDPdx;
+      break;
+    case BuiltinType::kDpdxCoarse:
+      op = spv::Op::OpDPdxCoarse;
+      break;
+    case BuiltinType::kDpdxFine:
+      op = spv::Op::OpDPdxFine;
+      break;
+    case BuiltinType::kDpdy:
+      op = spv::Op::OpDPdy;
+      break;
+    case BuiltinType::kDpdyCoarse:
+      op = spv::Op::OpDPdyCoarse;
+      break;
+    case BuiltinType::kDpdyFine:
+      op = spv::Op::OpDPdyFine;
+      break;
+    case BuiltinType::kFwidth:
+      op = spv::Op::OpFwidth;
+      break;
+    case BuiltinType::kFwidthCoarse:
+      op = spv::Op::OpFwidthCoarse;
+      break;
+    case BuiltinType::kFwidthFine:
+      op = spv::Op::OpFwidthFine;
+      break;
+    case BuiltinType::kIsInf:
+      op = spv::Op::OpIsInf;
+      break;
+    case BuiltinType::kIsNan:
+      op = spv::Op::OpIsNan;
+      break;
+    case BuiltinType::kIsFinite: {
+      // Implemented as:   not(IsInf or IsNan)
+      auto val_id = get_arg_as_value_id(0);
+      if (!val_id) {
+        return 0;
+      }
+      auto inf_result = result_op();
+      auto nan_result = result_op();
+      auto or_result = result_op();
+      if (push_function_inst(spv::Op::OpIsInf,
+                             {Operand::Int(result_type_id), inf_result,
+                              Operand::Int(val_id)}) &&
+          push_function_inst(spv::Op::OpIsNan,
+                             {Operand::Int(result_type_id), nan_result,
+                              Operand::Int(val_id)}) &&
+          push_function_inst(spv::Op::OpLogicalOr,
+                             {Operand::Int(result_type_id), or_result,
+                              Operand::Int(inf_result.to_i()),
+                              Operand::Int(nan_result.to_i())}) &&
+          push_function_inst(spv::Op::OpLogicalNot,
+                             {Operand::Int(result_type_id), result,
+                              Operand::Int(or_result.to_i())})) {
+        return result_id;
+      }
+      return 0;
+    }
+    case BuiltinType::kIsNormal: {
+      // A normal number is finite, non-zero, and not subnormal.
+      // Its exponent is neither of the extreme possible values.
+      // Implemented as:
+      //   exponent_bits = bitcast<u32>(f);
+      //   clamped = uclamp(1,254,exponent_bits);
+      //   result = (clamped == exponent_bits);
+      //
+      auto val_id = get_arg_as_value_id(0);
+      if (!val_id) {
+        return 0;
+      }
+
+      // These parameters are valid for IEEE 754 binary32
+      const uint32_t kExponentMask = 0x7f80000;
+      const uint32_t kMinNormalExponent = 0x0080000;
+      const uint32_t kMaxNormalExponent = 0x7f00000;
+
+      auto set_id = GetGLSLstd450Import();
+      auto* u32 = builder_.create<sem::U32>();
+
+      auto unsigned_id = GenerateTypeIfNeeded(u32);
+      auto exponent_mask_id =
+          GenerateConstantIfNeeded(ScalarConstant::U32(kExponentMask));
+      auto min_exponent_id =
+          GenerateConstantIfNeeded(ScalarConstant::U32(kMinNormalExponent));
+      auto max_exponent_id =
+          GenerateConstantIfNeeded(ScalarConstant::U32(kMaxNormalExponent));
+      if (auto* fvec_ty = builtin->ReturnType()->As<sem::Vector>()) {
+        // In the vector case, update the unsigned type to a vector type of the
+        // same size, and create vector constants by replicating the scalars.
+        // I expect backend compilers to fold these into unique constants, so
+        // there is no loss of efficiency.
+        auto* uvec_ty = builder_.create<sem::Vector>(u32, fvec_ty->Width());
+        unsigned_id = GenerateTypeIfNeeded(uvec_ty);
+        auto splat = [&](uint32_t scalar_id) -> uint32_t {
+          auto splat_result = result_op();
+          OperandList splat_params{Operand::Int(unsigned_id), splat_result};
+          for (size_t i = 0; i < fvec_ty->Width(); i++) {
+            splat_params.emplace_back(Operand::Int(scalar_id));
+          }
+          if (!push_function_inst(spv::Op::OpCompositeConstruct,
+                                  std::move(splat_params))) {
+            return 0;
+          }
+          return splat_result.to_i();
+        };
+        exponent_mask_id = splat(exponent_mask_id);
+        min_exponent_id = splat(min_exponent_id);
+        max_exponent_id = splat(max_exponent_id);
+      }
+      auto cast_result = result_op();
+      auto exponent_bits_result = result_op();
+      auto clamp_result = result_op();
+
+      if (set_id && unsigned_id && exponent_mask_id && min_exponent_id &&
+          max_exponent_id &&
+          push_function_inst(
+              spv::Op::OpBitcast,
+              {Operand::Int(unsigned_id), cast_result, Operand::Int(val_id)}) &&
+          push_function_inst(spv::Op::OpBitwiseAnd,
+                             {Operand::Int(unsigned_id), exponent_bits_result,
+                              Operand::Int(cast_result.to_i()),
+                              Operand::Int(exponent_mask_id)}) &&
+          push_function_inst(
+              spv::Op::OpExtInst,
+              {Operand::Int(unsigned_id), clamp_result, Operand::Int(set_id),
+               Operand::Int(GLSLstd450UClamp),
+               Operand::Int(exponent_bits_result.to_i()),
+               Operand::Int(min_exponent_id), Operand::Int(max_exponent_id)}) &&
+          push_function_inst(spv::Op::OpIEqual,
+                             {Operand::Int(result_type_id), result,
+                              Operand::Int(exponent_bits_result.to_i()),
+                              Operand::Int(clamp_result.to_i())})) {
+        return result_id;
+      }
+      return 0;
+    }
+    case BuiltinType::kMix: {
+      auto std450 = Operand::Int(GetGLSLstd450Import());
+
+      auto a_id = get_arg_as_value_id(0);
+      auto b_id = get_arg_as_value_id(1);
+      auto f_id = get_arg_as_value_id(2);
+      if (!a_id || !b_id || !f_id) {
+        return 0;
+      }
+
+      // If the interpolant is scalar but the objects are vectors, we need to
+      // splat the interpolant into a vector of the same size.
+      auto* result_vector_type = builtin->ReturnType()->As<sem::Vector>();
+      if (result_vector_type && builtin->Parameters()[2]->Type()->is_scalar()) {
+        f_id = GenerateSplat(f_id, builtin->Parameters()[0]->Type());
+        if (f_id == 0) {
+          return 0;
+        }
+      }
+
+      if (!push_function_inst(spv::Op::OpExtInst,
+                              {Operand::Int(result_type_id), result, std450,
+                               Operand::Int(GLSLstd450FMix), Operand::Int(a_id),
+                               Operand::Int(b_id), Operand::Int(f_id)})) {
+        return 0;
+      }
+      return result_id;
+    }
+    case BuiltinType::kReverseBits:
+      op = spv::Op::OpBitReverse;
+      break;
+    case BuiltinType::kSelect: {
+      // Note: Argument order is different in WGSL and SPIR-V
+      auto cond_id = get_arg_as_value_id(2);
+      auto true_id = get_arg_as_value_id(1);
+      auto false_id = get_arg_as_value_id(0);
+      if (!cond_id || !true_id || !false_id) {
+        return 0;
+      }
+
+      // If the condition is scalar but the objects are vectors, we need to
+      // splat the condition into a vector of the same size.
+      // TODO(jrprice): If we're targeting SPIR-V 1.4, we don't need to do this.
+      auto* result_vector_type = builtin->ReturnType()->As<sem::Vector>();
+      if (result_vector_type && builtin->Parameters()[2]->Type()->is_scalar()) {
+        auto* bool_vec_ty = builder_.create<sem::Vector>(
+            builder_.create<sem::Bool>(), result_vector_type->Width());
+        if (!GenerateTypeIfNeeded(bool_vec_ty)) {
+          return 0;
+        }
+        cond_id = GenerateSplat(cond_id, bool_vec_ty);
+        if (cond_id == 0) {
+          return 0;
+        }
+      }
+
+      if (!push_function_inst(
+              spv::Op::OpSelect,
+              {Operand::Int(result_type_id), result, Operand::Int(cond_id),
+               Operand::Int(true_id), Operand::Int(false_id)})) {
+        return 0;
+      }
+      return result_id;
+    }
+    case BuiltinType::kTranspose:
+      op = spv::Op::OpTranspose;
+      break;
+    case BuiltinType::kAbs:
+      if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+        // abs() only operates on *signed* integers.
+        // This is a no-op for unsigned integers.
+        return get_arg_as_value_id(0);
+      }
+      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
+        glsl_std450(GLSLstd450FAbs);
+      } else {
+        glsl_std450(GLSLstd450SAbs);
+      }
+      break;
+    default: {
+      auto inst_id = builtin_to_glsl_method(builtin);
+      if (inst_id == 0) {
+        error_ = "unknown method " + std::string(builtin->str());
+        return 0;
+      }
+      glsl_std450(inst_id);
+      break;
+    }
+  }
+
+  if (op == spv::Op::OpNop) {
+    error_ = "unable to determine operator for: " + std::string(builtin->str());
+    return 0;
+  }
+
+  for (size_t i = 0; i < call->Arguments().size(); i++) {
+    if (auto val_id = get_arg_as_value_id(i)) {
+      params.emplace_back(Operand::Int(val_id));
+    } else {
+      return 0;
+    }
+  }
+
+  if (!push_function_inst(op, params)) {
+    return 0;
+  }
+
+  return result_id;
+}
+
+bool Builder::GenerateTextureBuiltin(const sem::Call* call,
+                                     const sem::Builtin* builtin,
+                                     Operand result_type,
+                                     Operand result_id) {
+  using Usage = sem::ParameterUsage;
+
+  auto& signature = builtin->Signature();
+  auto& arguments = call->Arguments();
+
+  // Generates the given expression, returning the operand ID
+  auto gen = [&](const sem::Expression* expr) {
+    const auto val_id = GenerateExpressionWithLoadIfNeeded(expr);
+    return Operand::Int(val_id);
+  };
+
+  // Returns the argument with the given usage
+  auto arg = [&](Usage usage) {
+    int idx = signature.IndexOf(usage);
+    return (idx >= 0) ? arguments[idx] : nullptr;
+  };
+
+  // Generates the argument with the given usage, returning the operand ID
+  auto gen_arg = [&](Usage usage) {
+    auto* argument = arg(usage);
+    if (!argument) {
+      TINT_ICE(Writer, builder_.Diagnostics())
+          << "missing argument " << static_cast<int>(usage);
+    }
+    return gen(argument);
+  };
+
+  auto* texture = arg(Usage::kTexture);
+  if (!texture) {
+    TINT_ICE(Writer, builder_.Diagnostics()) << "missing texture argument";
+  }
+
+  auto* texture_type = texture->Type()->UnwrapRef()->As<sem::Texture>();
+
+  auto op = spv::Op::OpNop;
+
+  // Custom function to call after the texture-builtin op has been generated.
+  std::function<bool()> post_emission = [] { return true; };
+
+  // Populate the spirv_params with common parameters
+  OperandList spirv_params;
+  spirv_params.reserve(8);  // Enough to fit most parameter lists
+
+  // Extra image operands, appended to spirv_params.
+  struct ImageOperand {
+    SpvImageOperandsMask mask;
+    Operand operand;
+  };
+  std::vector<ImageOperand> image_operands;
+  image_operands.reserve(4);  // Enough to fit most parameter lists
+
+  // Appends `result_type` and `result_id` to `spirv_params`
+  auto append_result_type_and_id_to_spirv_params = [&]() {
+    spirv_params.emplace_back(std::move(result_type));
+    spirv_params.emplace_back(std::move(result_id));
+  };
+
+  // Appends a result type and id to `spirv_params`, possibly adding a
+  // post_emission step.
+  //
+  // If the texture is a depth texture, then this function wraps the result of
+  // the op with a OpCompositeExtract to evaluate to the first element of the
+  // returned vector. This is done as the WGSL texture reading functions for
+  // depths return a single float scalar instead of a vector.
+  //
+  // If the texture is not a depth texture, then this function simply delegates
+  // to calling append_result_type_and_id_to_spirv_params().
+  auto append_result_type_and_id_to_spirv_params_for_read = [&]() {
+    if (texture_type
+            ->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
+      auto* f32 = builder_.create<sem::F32>();
+      auto* spirv_result_type = builder_.create<sem::Vector>(f32, 4);
+      auto spirv_result = result_op();
+      post_emission = [=] {
+        return push_function_inst(
+            spv::Op::OpCompositeExtract,
+            {result_type, result_id, spirv_result, Operand::Int(0)});
+      };
+      auto spirv_result_type_id = GenerateTypeIfNeeded(spirv_result_type);
+      if (spirv_result_type_id == 0) {
+        return false;
+      }
+      spirv_params.emplace_back(Operand::Int(spirv_result_type_id));
+      spirv_params.emplace_back(spirv_result);
+      return true;
+    }
+
+    append_result_type_and_id_to_spirv_params();
+    return true;
+  };
+
+  // Appends a result type and id to `spirv_params`, by first swizzling the
+  // result of the op with `swizzle`.
+  auto append_result_type_and_id_to_spirv_params_swizzled =
+      [&](uint32_t spirv_result_width, std::vector<uint32_t> swizzle) {
+        if (swizzle.empty()) {
+          append_result_type_and_id_to_spirv_params();
+        } else {
+          // Assign post_emission to swizzle the result of the call to
+          // OpImageQuerySize[Lod].
+          auto* element_type = ElementTypeOf(call->Type());
+          auto spirv_result = result_op();
+          auto* spirv_result_type =
+              builder_.create<sem::Vector>(element_type, spirv_result_width);
+          if (swizzle.size() > 1) {
+            post_emission = [=] {
+              OperandList operands{
+                  result_type,
+                  result_id,
+                  spirv_result,
+                  spirv_result,
+              };
+              for (auto idx : swizzle) {
+                operands.emplace_back(Operand::Int(idx));
+              }
+              return push_function_inst(spv::Op::OpVectorShuffle, operands);
+            };
+          } else {
+            post_emission = [=] {
+              return push_function_inst(spv::Op::OpCompositeExtract,
+                                        {result_type, result_id, spirv_result,
+                                         Operand::Int(swizzle[0])});
+            };
+          }
+          auto spirv_result_type_id = GenerateTypeIfNeeded(spirv_result_type);
+          if (spirv_result_type_id == 0) {
+            return false;
+          }
+          spirv_params.emplace_back(Operand::Int(spirv_result_type_id));
+          spirv_params.emplace_back(spirv_result);
+        }
+        return true;
+      };
+
+  auto append_coords_to_spirv_params = [&]() -> bool {
+    if (auto* array_index = arg(Usage::kArrayIndex)) {
+      // Array index needs to be appended to the coordinates.
+      auto* packed = AppendVector(&builder_, arg(Usage::kCoords)->Declaration(),
+                                  array_index->Declaration());
+      auto param = GenerateExpression(packed->Declaration());
+      if (param == 0) {
+        return false;
+      }
+      spirv_params.emplace_back(Operand::Int(param));
+    } else {
+      spirv_params.emplace_back(gen_arg(Usage::kCoords));  // coordinates
+    }
+    return true;
+  };
+
+  auto append_image_and_coords_to_spirv_params = [&]() -> bool {
+    auto sampler_param = gen_arg(Usage::kSampler);
+    auto texture_param = gen_arg(Usage::kTexture);
+    auto sampled_image =
+        GenerateSampledImage(texture_type, texture_param, sampler_param);
+
+    // Populate the spirv_params with the common parameters
+    spirv_params.emplace_back(Operand::Int(sampled_image));  // sampled image
+    return append_coords_to_spirv_params();
+  };
+
+  switch (builtin->Type()) {
+    case BuiltinType::kTextureDimensions: {
+      // Number of returned elements from OpImageQuerySize[Lod] may not match
+      // those of textureDimensions().
+      // This might be due to an extra vector scalar describing the number of
+      // array elements or textureDimensions() returning a vec3 for cubes
+      // when only width / height is returned by OpImageQuerySize[Lod]
+      // (see https://github.com/gpuweb/gpuweb/issues/1345).
+      // Handle these mismatches by swizzling the returned vector.
+      std::vector<uint32_t> swizzle;
+      uint32_t spirv_dims = 0;
+      switch (texture_type->dim()) {
+        case ast::TextureDimension::kNone:
+          error_ = "texture dimension is kNone";
+          return false;
+        case ast::TextureDimension::k1d:
+        case ast::TextureDimension::k2d:
+        case ast::TextureDimension::k3d:
+        case ast::TextureDimension::kCube:
+          break;  // No swizzle needed
+        case ast::TextureDimension::kCubeArray:
+        case ast::TextureDimension::k2dArray:
+          swizzle = {0, 1};  // Strip array index
+          spirv_dims = 3;    // [width, height, array_count]
+          break;
+      }
+
+      if (!append_result_type_and_id_to_spirv_params_swizzled(spirv_dims,
+                                                              swizzle)) {
+        return false;
+      }
+
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+      if (texture_type->IsAnyOf<sem::MultisampledTexture,       //
+                                sem::DepthMultisampledTexture,  //
+                                sem::StorageTexture>()) {
+        op = spv::Op::OpImageQuerySize;
+      } else if (auto* level = arg(Usage::kLevel)) {
+        op = spv::Op::OpImageQuerySizeLod;
+        spirv_params.emplace_back(gen(level));
+      } else {
+        ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
+        op = spv::Op::OpImageQuerySizeLod;
+        spirv_params.emplace_back(
+            Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
+      }
+      break;
+    }
+    case BuiltinType::kTextureNumLayers: {
+      uint32_t spirv_dims = 0;
+      switch (texture_type->dim()) {
+        default:
+          error_ = "texture is not arrayed";
+          return false;
+        case ast::TextureDimension::k2dArray:
+        case ast::TextureDimension::kCubeArray:
+          spirv_dims = 3;
+          break;
+      }
+
+      // OpImageQuerySize[Lod] packs the array count as the last element of the
+      // returned vector. Extract this.
+      if (!append_result_type_and_id_to_spirv_params_swizzled(
+              spirv_dims, {spirv_dims - 1})) {
+        return false;
+      }
+
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+
+      if (texture_type->Is<sem::MultisampledTexture>() ||
+          texture_type->Is<sem::StorageTexture>()) {
+        op = spv::Op::OpImageQuerySize;
+      } else {
+        ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
+        op = spv::Op::OpImageQuerySizeLod;
+        spirv_params.emplace_back(
+            Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
+      }
+      break;
+    }
+    case BuiltinType::kTextureNumLevels: {
+      op = spv::Op::OpImageQueryLevels;
+      append_result_type_and_id_to_spirv_params();
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+      break;
+    }
+    case BuiltinType::kTextureNumSamples: {
+      op = spv::Op::OpImageQuerySamples;
+      append_result_type_and_id_to_spirv_params();
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+      break;
+    }
+    case BuiltinType::kTextureLoad: {
+      op = texture_type->Is<sem::StorageTexture>() ? spv::Op::OpImageRead
+                                                   : spv::Op::OpImageFetch;
+      append_result_type_and_id_to_spirv_params_for_read();
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+      if (!append_coords_to_spirv_params()) {
+        return false;
+      }
+
+      if (auto* level = arg(Usage::kLevel)) {
+        image_operands.emplace_back(
+            ImageOperand{SpvImageOperandsLodMask, gen(level)});
+      }
+
+      if (auto* sample_index = arg(Usage::kSampleIndex)) {
+        image_operands.emplace_back(
+            ImageOperand{SpvImageOperandsSampleMask, gen(sample_index)});
+      }
+
+      break;
+    }
+    case BuiltinType::kTextureStore: {
+      op = spv::Op::OpImageWrite;
+      spirv_params.emplace_back(gen_arg(Usage::kTexture));
+      if (!append_coords_to_spirv_params()) {
+        return false;
+      }
+      spirv_params.emplace_back(gen_arg(Usage::kValue));
+      break;
+    }
+    case BuiltinType::kTextureGather: {
+      op = spv::Op::OpImageGather;
+      append_result_type_and_id_to_spirv_params();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      if (signature.IndexOf(Usage::kComponent) < 0) {
+        spirv_params.emplace_back(
+            Operand::Int(GenerateConstantIfNeeded(ScalarConstant::I32(0))));
+      } else {
+        spirv_params.emplace_back(gen_arg(Usage::kComponent));
+      }
+      break;
+    }
+    case BuiltinType::kTextureGatherCompare: {
+      op = spv::Op::OpImageDrefGather;
+      append_result_type_and_id_to_spirv_params();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
+      break;
+    }
+    case BuiltinType::kTextureSample: {
+      op = spv::Op::OpImageSampleImplicitLod;
+      append_result_type_and_id_to_spirv_params_for_read();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      break;
+    }
+    case BuiltinType::kTextureSampleBias: {
+      op = spv::Op::OpImageSampleImplicitLod;
+      append_result_type_and_id_to_spirv_params_for_read();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      image_operands.emplace_back(
+          ImageOperand{SpvImageOperandsBiasMask, gen_arg(Usage::kBias)});
+      break;
+    }
+    case BuiltinType::kTextureSampleLevel: {
+      op = spv::Op::OpImageSampleExplicitLod;
+      append_result_type_and_id_to_spirv_params_for_read();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      auto level = Operand::Int(0);
+      if (arg(Usage::kLevel)->Type()->UnwrapRef()->Is<sem::I32>()) {
+        // Depth textures have i32 parameters for the level, but SPIR-V expects
+        // F32. Cast.
+        auto f32_type_id = GenerateTypeIfNeeded(builder_.create<sem::F32>());
+        if (f32_type_id == 0) {
+          return 0;
+        }
+        level = result_op();
+        if (!push_function_inst(
+                spv::Op::OpConvertSToF,
+                {Operand::Int(f32_type_id), level, gen_arg(Usage::kLevel)})) {
+          return 0;
+        }
+      } else {
+        level = gen_arg(Usage::kLevel);
+      }
+      image_operands.emplace_back(ImageOperand{SpvImageOperandsLodMask, level});
+      break;
+    }
+    case BuiltinType::kTextureSampleGrad: {
+      op = spv::Op::OpImageSampleExplicitLod;
+      append_result_type_and_id_to_spirv_params_for_read();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      image_operands.emplace_back(
+          ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdx)});
+      image_operands.emplace_back(
+          ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdy)});
+      break;
+    }
+    case BuiltinType::kTextureSampleCompare: {
+      op = spv::Op::OpImageSampleDrefImplicitLod;
+      append_result_type_and_id_to_spirv_params();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
+      break;
+    }
+    case BuiltinType::kTextureSampleCompareLevel: {
+      op = spv::Op::OpImageSampleDrefExplicitLod;
+      append_result_type_and_id_to_spirv_params();
+      if (!append_image_and_coords_to_spirv_params()) {
+        return false;
+      }
+      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
+
+      ast::FloatLiteralExpression float_0(ProgramID(), Source{}, 0.0);
+      image_operands.emplace_back(ImageOperand{
+          SpvImageOperandsLodMask,
+          Operand::Int(GenerateLiteralIfNeeded(nullptr, &float_0))});
+      break;
+    }
+    default:
+      TINT_UNREACHABLE(Writer, builder_.Diagnostics());
+      return false;
+  }
+
+  if (auto* offset = arg(Usage::kOffset)) {
+    image_operands.emplace_back(
+        ImageOperand{SpvImageOperandsConstOffsetMask, gen(offset)});
+  }
+
+  if (!image_operands.empty()) {
+    std::sort(image_operands.begin(), image_operands.end(),
+              [](auto& a, auto& b) { return a.mask < b.mask; });
+    uint32_t mask = 0;
+    for (auto& image_operand : image_operands) {
+      mask |= image_operand.mask;
+    }
+    spirv_params.emplace_back(Operand::Int(mask));
+    for (auto& image_operand : image_operands) {
+      spirv_params.emplace_back(image_operand.operand);
+    }
+  }
+
+  if (op == spv::Op::OpNop) {
+    error_ = "unable to determine operator for: " + std::string(builtin->str());
+    return false;
+  }
+
+  if (!push_function_inst(op, spirv_params)) {
+    return false;
+  }
+
+  return post_emission();
+}
+
+bool Builder::GenerateControlBarrierBuiltin(const sem::Builtin* builtin) {
+  auto const op = spv::Op::OpControlBarrier;
+  uint32_t execution = 0;
+  uint32_t memory = 0;
+  uint32_t semantics = 0;
+
+  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
+  // instruction.
+  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    execution = static_cast<uint32_t>(spv::Scope::Workgroup);
+    memory = static_cast<uint32_t>(spv::Scope::Workgroup);
+    semantics =
+        static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
+        static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
+  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    execution = static_cast<uint32_t>(spv::Scope::Workgroup);
+    memory = static_cast<uint32_t>(spv::Scope::Workgroup);
+    semantics =
+        static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
+        static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
+  } else {
+    error_ = "unexpected barrier builtin type ";
+    error_ += sem::str(builtin->Type());
+    return false;
+  }
+
+  auto execution_id = GenerateConstantIfNeeded(ScalarConstant::U32(execution));
+  auto memory_id = GenerateConstantIfNeeded(ScalarConstant::U32(memory));
+  auto semantics_id = GenerateConstantIfNeeded(ScalarConstant::U32(semantics));
+  if (execution_id == 0 || memory_id == 0 || semantics_id == 0) {
+    return false;
+  }
+
+  return push_function_inst(op, {
+                                    Operand::Int(execution_id),
+                                    Operand::Int(memory_id),
+                                    Operand::Int(semantics_id),
+                                });
+}
+
+bool Builder::GenerateAtomicBuiltin(const sem::Call* call,
+                                    const sem::Builtin* builtin,
+                                    Operand result_type,
+                                    Operand result_id) {
+  auto is_value_signed = [&] {
+    return builtin->Parameters()[1]->Type()->Is<sem::I32>();
+  };
+
+  auto storage_class =
+      builtin->Parameters()[0]->Type()->As<sem::Pointer>()->StorageClass();
+
+  uint32_t memory_id = 0;
+  switch (
+      builtin->Parameters()[0]->Type()->As<sem::Pointer>()->StorageClass()) {
+    case ast::StorageClass::kWorkgroup:
+      memory_id = GenerateConstantIfNeeded(
+          ScalarConstant::U32(static_cast<uint32_t>(spv::Scope::Workgroup)));
+      break;
+    case ast::StorageClass::kStorage:
+      memory_id = GenerateConstantIfNeeded(
+          ScalarConstant::U32(static_cast<uint32_t>(spv::Scope::Device)));
+      break;
+    default:
+      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
+          << "unhandled atomic storage class " << storage_class;
+      return false;
+  }
+  if (memory_id == 0) {
+    return false;
+  }
+
+  uint32_t semantics_id = GenerateConstantIfNeeded(ScalarConstant::U32(
+      static_cast<uint32_t>(spv::MemorySemanticsMask::MaskNone)));
+  if (semantics_id == 0) {
+    return false;
+  }
+
+  uint32_t pointer_id = GenerateExpression(call->Arguments()[0]->Declaration());
+  if (pointer_id == 0) {
+    return false;
+  }
+
+  uint32_t value_id = 0;
+  if (call->Arguments().size() > 1) {
+    value_id = GenerateExpressionWithLoadIfNeeded(call->Arguments().back());
+    if (value_id == 0) {
+      return false;
+    }
+  }
+
+  Operand pointer = Operand::Int(pointer_id);
+  Operand value = Operand::Int(value_id);
+  Operand memory = Operand::Int(memory_id);
+  Operand semantics = Operand::Int(semantics_id);
+
+  switch (builtin->Type()) {
+    case sem::BuiltinType::kAtomicLoad:
+      return push_function_inst(spv::Op::OpAtomicLoad, {
+                                                           result_type,
+                                                           result_id,
+                                                           pointer,
+                                                           memory,
+                                                           semantics,
+                                                       });
+    case sem::BuiltinType::kAtomicStore:
+      return push_function_inst(spv::Op::OpAtomicStore, {
+                                                            pointer,
+                                                            memory,
+                                                            semantics,
+                                                            value,
+                                                        });
+    case sem::BuiltinType::kAtomicAdd:
+      return push_function_inst(spv::Op::OpAtomicIAdd, {
+                                                           result_type,
+                                                           result_id,
+                                                           pointer,
+                                                           memory,
+                                                           semantics,
+                                                           value,
+                                                       });
+    case sem::BuiltinType::kAtomicSub:
+      return push_function_inst(spv::Op::OpAtomicISub, {
+                                                           result_type,
+                                                           result_id,
+                                                           pointer,
+                                                           memory,
+                                                           semantics,
+                                                           value,
+                                                       });
+    case sem::BuiltinType::kAtomicMax:
+      return push_function_inst(
+          is_value_signed() ? spv::Op::OpAtomicSMax : spv::Op::OpAtomicUMax,
+          {
+              result_type,
+              result_id,
+              pointer,
+              memory,
+              semantics,
+              value,
+          });
+    case sem::BuiltinType::kAtomicMin:
+      return push_function_inst(
+          is_value_signed() ? spv::Op::OpAtomicSMin : spv::Op::OpAtomicUMin,
+          {
+              result_type,
+              result_id,
+              pointer,
+              memory,
+              semantics,
+              value,
+          });
+    case sem::BuiltinType::kAtomicAnd:
+      return push_function_inst(spv::Op::OpAtomicAnd, {
+                                                          result_type,
+                                                          result_id,
+                                                          pointer,
+                                                          memory,
+                                                          semantics,
+                                                          value,
+                                                      });
+    case sem::BuiltinType::kAtomicOr:
+      return push_function_inst(spv::Op::OpAtomicOr, {
+                                                         result_type,
+                                                         result_id,
+                                                         pointer,
+                                                         memory,
+                                                         semantics,
+                                                         value,
+                                                     });
+    case sem::BuiltinType::kAtomicXor:
+      return push_function_inst(spv::Op::OpAtomicXor, {
+                                                          result_type,
+                                                          result_id,
+                                                          pointer,
+                                                          memory,
+                                                          semantics,
+                                                          value,
+                                                      });
+    case sem::BuiltinType::kAtomicExchange:
+      return push_function_inst(spv::Op::OpAtomicExchange, {
+                                                               result_type,
+                                                               result_id,
+                                                               pointer,
+                                                               memory,
+                                                               semantics,
+                                                               value,
+                                                           });
+    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+      auto comparator = GenerateExpression(call->Arguments()[1]->Declaration());
+      if (comparator == 0) {
+        return false;
+      }
+
+      auto* value_sem_type = TypeOf(call->Arguments()[2]->Declaration());
+
+      auto value_type = GenerateTypeIfNeeded(value_sem_type);
+      if (value_type == 0) {
+        return false;
+      }
+
+      auto* bool_sem_ty = builder_.create<sem::Bool>();
+      auto bool_type = GenerateTypeIfNeeded(bool_sem_ty);
+      if (bool_type == 0) {
+        return false;
+      }
+
+      // original_value := OpAtomicCompareExchange(pointer, memory, semantics,
+      //                                           semantics, value, comparator)
+      auto original_value = result_op();
+      if (!push_function_inst(spv::Op::OpAtomicCompareExchange,
+                              {
+                                  Operand::Int(value_type),
+                                  original_value,
+                                  pointer,
+                                  memory,
+                                  semantics,
+                                  semantics,
+                                  value,
+                                  Operand::Int(comparator),
+                              })) {
+        return false;
+      }
+
+      // values_equal := original_value == value
+      auto values_equal = result_op();
+      if (!push_function_inst(spv::Op::OpIEqual, {
+                                                     Operand::Int(bool_type),
+                                                     values_equal,
+                                                     original_value,
+                                                     value,
+                                                 })) {
+        return false;
+      }
+
+      // zero := T(0)
+      // one := T(1)
+      uint32_t zero = 0;
+      uint32_t one = 0;
+      if (value_sem_type->Is<sem::I32>()) {
+        zero = GenerateConstantIfNeeded(ScalarConstant::I32(0u));
+        one = GenerateConstantIfNeeded(ScalarConstant::I32(1u));
+      } else if (value_sem_type->Is<sem::U32>()) {
+        zero = GenerateConstantIfNeeded(ScalarConstant::U32(0u));
+        one = GenerateConstantIfNeeded(ScalarConstant::U32(1u));
+      } else {
+        TINT_UNREACHABLE(Writer, builder_.Diagnostics())
+            << "unsupported atomic type " << value_sem_type->TypeInfo().name;
+      }
+      if (zero == 0 || one == 0) {
+        return false;
+      }
+
+      // xchg_success := values_equal ? one : zero
+      auto xchg_success = result_op();
+      if (!push_function_inst(spv::Op::OpSelect, {
+                                                     Operand::Int(value_type),
+                                                     xchg_success,
+                                                     values_equal,
+                                                     Operand::Int(one),
+                                                     Operand::Int(zero),
+                                                 })) {
+        return false;
+      }
+
+      // result := vec2<T>(original_value, xchg_success)
+      return push_function_inst(spv::Op::OpCompositeConstruct,
+                                {
+                                    result_type,
+                                    result_id,
+                                    original_value,
+                                    xchg_success,
+                                });
+    }
+    default:
+      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
+          << "unhandled atomic builtin " << builtin->Type();
+      return false;
+  }
+}
+
+uint32_t Builder::GenerateSampledImage(const sem::Type* texture_type,
+                                       Operand texture_operand,
+                                       Operand sampler_operand) {
+  uint32_t sampled_image_type_id = 0;
+  auto val = texture_type_name_to_sampled_image_type_id_.find(
+      texture_type->type_name());
+  if (val != texture_type_name_to_sampled_image_type_id_.end()) {
+    // The sampled image type is already created.
+    sampled_image_type_id = val->second;
+  } else {
+    // We need to create the sampled image type and cache the result.
+    auto sampled_image_type = result_op();
+    sampled_image_type_id = sampled_image_type.to_i();
+    auto texture_type_id = GenerateTypeIfNeeded(texture_type);
+    push_type(spv::Op::OpTypeSampledImage,
+              {sampled_image_type, Operand::Int(texture_type_id)});
+    texture_type_name_to_sampled_image_type_id_[texture_type->type_name()] =
+        sampled_image_type_id;
+  }
+
+  auto sampled_image = result_op();
+  if (!push_function_inst(spv::Op::OpSampledImage,
+                          {Operand::Int(sampled_image_type_id), sampled_image,
+                           texture_operand, sampler_operand})) {
+    return 0;
+  }
+
+  return sampled_image.to_i();
+}
+
+uint32_t Builder::GenerateBitcastExpression(
+    const ast::BitcastExpression* expr) {
+  auto result = result_op();
+  auto result_id = result.to_i();
+
+  auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
+  if (result_type_id == 0) {
+    return 0;
+  }
+
+  auto val_id = GenerateExpressionWithLoadIfNeeded(expr->expr);
+  if (val_id == 0) {
+    return 0;
+  }
+
+  // Bitcast does not allow same types, just emit a CopyObject
+  auto* to_type = TypeOf(expr)->UnwrapRef();
+  auto* from_type = TypeOf(expr->expr)->UnwrapRef();
+  if (to_type->type_name() == from_type->type_name()) {
+    if (!push_function_inst(
+            spv::Op::OpCopyObject,
+            {Operand::Int(result_type_id), result, Operand::Int(val_id)})) {
+      return 0;
+    }
+    return result_id;
+  }
+
+  if (!push_function_inst(spv::Op::OpBitcast, {Operand::Int(result_type_id),
+                                               result, Operand::Int(val_id)})) {
+    return 0;
+  }
+
+  return result_id;
+}
+
+bool Builder::GenerateConditionalBlock(
+    const ast::Expression* cond,
+    const ast::BlockStatement* true_body,
+    size_t cur_else_idx,
+    const ast::ElseStatementList& else_stmts) {
+  auto cond_id = GenerateExpressionWithLoadIfNeeded(cond);
+  if (cond_id == 0) {
+    return false;
+  }
+
+  auto merge_block = result_op();
+  auto merge_block_id = merge_block.to_i();
+
+  if (!push_function_inst(spv::Op::OpSelectionMerge,
+                          {Operand::Int(merge_block_id),
+                           Operand::Int(SpvSelectionControlMaskNone)})) {
+    return false;
+  }
+
+  auto true_block = result_op();
+  auto true_block_id = true_block.to_i();
+
+  // if there are no more else statements we branch on false to the merge
+  // block otherwise we branch to the false block
+  auto false_block_id =
+      cur_else_idx < else_stmts.size() ? next_id() : merge_block_id;
+
+  if (!push_function_inst(spv::Op::OpBranchConditional,
+                          {Operand::Int(cond_id), Operand::Int(true_block_id),
+                           Operand::Int(false_block_id)})) {
+    return false;
+  }
+
+  // Output true block
+  if (!GenerateLabel(true_block_id)) {
+    return false;
+  }
+  if (!GenerateBlockStatement(true_body)) {
+    return false;
+  }
+  // We only branch if the last element of the body didn't already branch.
+  if (InsideBasicBlock()) {
+    if (!push_function_inst(spv::Op::OpBranch,
+                            {Operand::Int(merge_block_id)})) {
+      return false;
+    }
+  }
+
+  // Start the false block if needed
+  if (false_block_id != merge_block_id) {
+    if (!GenerateLabel(false_block_id)) {
+      return false;
+    }
+
+    auto* else_stmt = else_stmts[cur_else_idx];
+    // Handle the else case by just outputting the statements.
+    if (!else_stmt->condition) {
+      if (!GenerateBlockStatement(else_stmt->body)) {
+        return false;
+      }
+    } else {
+      if (!GenerateConditionalBlock(else_stmt->condition, else_stmt->body,
+                                    cur_else_idx + 1, else_stmts)) {
+        return false;
+      }
+    }
+    if (InsideBasicBlock()) {
+      if (!push_function_inst(spv::Op::OpBranch,
+                              {Operand::Int(merge_block_id)})) {
+        return false;
+      }
+    }
+  }
+
+  // Output the merge block
+  return GenerateLabel(merge_block_id);
+}
+
+bool Builder::GenerateIfStatement(const ast::IfStatement* stmt) {
+  if (!continuing_stack_.empty() &&
+      stmt == continuing_stack_.back().last_statement->As<ast::IfStatement>()) {
+    const ContinuingInfo& ci = continuing_stack_.back();
+    // Match one of two patterns: the break-if and break-unless patterns.
+    //
+    // The break-if pattern:
+    //  continuing { ...
+    //    if (cond) { break; }
+    //  }
+    //
+    // The break-unless pattern:
+    //  continuing { ...
+    //    if (cond) {} else {break;}
+    //  }
+    auto is_just_a_break = [](const ast::BlockStatement* block) {
+      return block && (block->statements.size() == 1) &&
+             block->Last()->Is<ast::BreakStatement>();
+    };
+    if (is_just_a_break(stmt->body) && stmt->else_statements.empty()) {
+      // It's a break-if.
+      TINT_ASSERT(Writer, !backedge_stack_.empty());
+      const auto cond_id = GenerateExpressionWithLoadIfNeeded(stmt->condition);
+      if (!cond_id) {
+        return false;
+      }
+      backedge_stack_.back() =
+          Backedge(spv::Op::OpBranchConditional,
+                   {Operand::Int(cond_id), Operand::Int(ci.break_target_id),
+                    Operand::Int(ci.loop_header_id)});
+      return true;
+    } else if (stmt->body->Empty()) {
+      const auto& es = stmt->else_statements;
+      if (es.size() == 1 && !es.back()->condition &&
+          is_just_a_break(es.back()->body)) {
+        // It's a break-unless.
+        TINT_ASSERT(Writer, !backedge_stack_.empty());
+        const auto cond_id =
+            GenerateExpressionWithLoadIfNeeded(stmt->condition);
+        if (!cond_id) {
+          return false;
+        }
+        backedge_stack_.back() =
+            Backedge(spv::Op::OpBranchConditional,
+                     {Operand::Int(cond_id), Operand::Int(ci.loop_header_id),
+                      Operand::Int(ci.break_target_id)});
+        return true;
+      }
+    }
+  }
+
+  if (!GenerateConditionalBlock(stmt->condition, stmt->body, 0,
+                                stmt->else_statements)) {
+    return false;
+  }
+  return true;
+}
+
+bool Builder::GenerateSwitchStatement(const ast::SwitchStatement* stmt) {
+  auto merge_block = result_op();
+  auto merge_block_id = merge_block.to_i();
+
+  merge_stack_.push_back(merge_block_id);
+
+  auto cond_id = GenerateExpressionWithLoadIfNeeded(stmt->condition);
+  if (cond_id == 0) {
+    return false;
+  }
+
+  auto default_block = result_op();
+  auto default_block_id = default_block.to_i();
+
+  OperandList params = {Operand::Int(cond_id), Operand::Int(default_block_id)};
+
+  std::vector<uint32_t> case_ids;
+  for (const auto* item : stmt->body) {
+    if (item->IsDefault()) {
+      case_ids.push_back(default_block_id);
+      continue;
+    }
+
+    auto block = result_op();
+    auto block_id = block.to_i();
+
+    case_ids.push_back(block_id);
+    for (auto* selector : item->selectors) {
+      auto* int_literal = selector->As<ast::IntLiteralExpression>();
+      if (!int_literal) {
+        error_ = "expected integer literal for switch case label";
+        return false;
+      }
+
+      params.push_back(Operand::Int(int_literal->ValueAsU32()));
+      params.push_back(Operand::Int(block_id));
+    }
+  }
+
+  if (!push_function_inst(spv::Op::OpSelectionMerge,
+                          {Operand::Int(merge_block_id),
+                           Operand::Int(SpvSelectionControlMaskNone)})) {
+    return false;
+  }
+  if (!push_function_inst(spv::Op::OpSwitch, params)) {
+    return false;
+  }
+
+  bool generated_default = false;
+  auto& body = stmt->body;
+  // We output the case statements in order they were entered in the original
+  // source. Each fallthrough goes to the next case entry, so is a forward
+  // branch, otherwise the branch is to the merge block which comes after
+  // the switch statement.
+  for (uint32_t i = 0; i < body.size(); i++) {
+    auto* item = body[i];
+
+    if (item->IsDefault()) {
+      generated_default = true;
+    }
+
+    if (!GenerateLabel(case_ids[i])) {
+      return false;
+    }
+    if (!GenerateBlockStatement(item->body)) {
+      return false;
+    }
+
+    if (LastIsFallthrough(item->body)) {
+      if (i == (body.size() - 1)) {
+        // This case is caught by Resolver validation
+        TINT_UNREACHABLE(Writer, builder_.Diagnostics());
+        return false;
+      }
+      if (!push_function_inst(spv::Op::OpBranch,
+                              {Operand::Int(case_ids[i + 1])})) {
+        return false;
+      }
+    } else if (InsideBasicBlock()) {
+      if (!push_function_inst(spv::Op::OpBranch,
+                              {Operand::Int(merge_block_id)})) {
+        return false;
+      }
+    }
+  }
+
+  if (!generated_default) {
+    if (!GenerateLabel(default_block_id)) {
+      return false;
+    }
+    if (!push_function_inst(spv::Op::OpBranch,
+                            {Operand::Int(merge_block_id)})) {
+      return false;
+    }
+  }
+
+  merge_stack_.pop_back();
+
+  return GenerateLabel(merge_block_id);
+}
+
+bool Builder::GenerateReturnStatement(const ast::ReturnStatement* stmt) {
+  if (stmt->value) {
+    auto val_id = GenerateExpressionWithLoadIfNeeded(stmt->value);
+    if (val_id == 0) {
+      return false;
+    }
+    if (!push_function_inst(spv::Op::OpReturnValue, {Operand::Int(val_id)})) {
+      return false;
+    }
+  } else {
+    if (!push_function_inst(spv::Op::OpReturn, {})) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Builder::GenerateLoopStatement(const ast::LoopStatement* stmt) {
+  auto loop_header = result_op();
+  auto loop_header_id = loop_header.to_i();
+  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(loop_header_id)})) {
+    return false;
+  }
+  if (!GenerateLabel(loop_header_id)) {
+    return false;
+  }
+
+  auto merge_block = result_op();
+  auto merge_block_id = merge_block.to_i();
+  auto continue_block = result_op();
+  auto continue_block_id = continue_block.to_i();
+
+  auto body_block = result_op();
+  auto body_block_id = body_block.to_i();
+
+  if (!push_function_inst(
+          spv::Op::OpLoopMerge,
+          {Operand::Int(merge_block_id), Operand::Int(continue_block_id),
+           Operand::Int(SpvLoopControlMaskNone)})) {
+    return false;
+  }
+
+  continue_stack_.push_back(continue_block_id);
+  merge_stack_.push_back(merge_block_id);
+
+  // Usually, the backedge is a simple branch.  This will be modified if the
+  // backedge block in the continuing construct has an exiting edge.
+  backedge_stack_.emplace_back(spv::Op::OpBranch,
+                               OperandList{Operand::Int(loop_header_id)});
+
+  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(body_block_id)})) {
+    return false;
+  }
+  if (!GenerateLabel(body_block_id)) {
+    return false;
+  }
+
+  // We need variables from the body to be visible in the continuing block, so
+  // manage scope outside of GenerateBlockStatement.
+  {
+    scope_stack_.Push();
+    TINT_DEFER(scope_stack_.Pop());
+
+    if (!GenerateBlockStatementWithoutScoping(stmt->body)) {
+      return false;
+    }
+
+    // We only branch if the last element of the body didn't already branch.
+    if (InsideBasicBlock()) {
+      if (!push_function_inst(spv::Op::OpBranch,
+                              {Operand::Int(continue_block_id)})) {
+        return false;
+      }
+    }
+
+    if (!GenerateLabel(continue_block_id)) {
+      return false;
+    }
+    if (stmt->continuing && !stmt->continuing->Empty()) {
+      continuing_stack_.emplace_back(stmt->continuing->Last(), loop_header_id,
+                                     merge_block_id);
+      if (!GenerateBlockStatementWithoutScoping(stmt->continuing)) {
+        return false;
+      }
+      continuing_stack_.pop_back();
+    }
+  }
+
+  // Generate the backedge.
+  TINT_ASSERT(Writer, !backedge_stack_.empty());
+  const Backedge& backedge = backedge_stack_.back();
+  if (!push_function_inst(backedge.opcode, backedge.operands)) {
+    return false;
+  }
+  backedge_stack_.pop_back();
+
+  merge_stack_.pop_back();
+  continue_stack_.pop_back();
+
+  return GenerateLabel(merge_block_id);
+}
+
+bool Builder::GenerateStatement(const ast::Statement* stmt) {
+  return Switch(
+      stmt,
+      [&](const ast::AssignmentStatement* a) {
+        return GenerateAssignStatement(a);
+      },
+      [&](const ast::BlockStatement* b) {  //
+        return GenerateBlockStatement(b);
+      },
+      [&](const ast::BreakStatement* b) {  //
+        return GenerateBreakStatement(b);
+      },
+      [&](const ast::CallStatement* c) {
+        return GenerateCallExpression(c->expr) != 0;
+      },
+      [&](const ast::ContinueStatement* c) {
+        return GenerateContinueStatement(c);
+      },
+      [&](const ast::DiscardStatement* d) {
+        return GenerateDiscardStatement(d);
+      },
+      [&](const ast::FallthroughStatement*) {
+        // Do nothing here, the fallthrough gets handled by the switch code.
+        return true;
+      },
+      [&](const ast::IfStatement* i) {  //
+        return GenerateIfStatement(i);
+      },
+      [&](const ast::LoopStatement* l) {  //
+        return GenerateLoopStatement(l);
+      },
+      [&](const ast::ReturnStatement* r) {  //
+        return GenerateReturnStatement(r);
+      },
+      [&](const ast::SwitchStatement* s) {  //
+        return GenerateSwitchStatement(s);
+      },
+      [&](const ast::VariableDeclStatement* v) {
+        return GenerateVariableDeclStatement(v);
+      },
+      [&](Default) {
+        error_ = "Unknown statement: " + std::string(stmt->TypeInfo().name);
+        return false;
+      });
+}
+
+bool Builder::GenerateVariableDeclStatement(
+    const ast::VariableDeclStatement* stmt) {
+  return GenerateFunctionVariable(stmt->variable);
+}
+
+uint32_t Builder::GenerateTypeIfNeeded(const sem::Type* type) {
+  if (type == nullptr) {
+    error_ = "attempting to generate type from null type";
+    return 0;
+  }
+
+  // Atomics are a type in WGSL, but aren't a distinct type in SPIR-V.
+  // Just emit the type inside the atomic.
+  if (auto* atomic = type->As<sem::Atomic>()) {
+    return GenerateTypeIfNeeded(atomic->Type());
+  }
+
+  // Pointers and references with differing accesses should not result in a
+  // different SPIR-V types, so we explicitly ignore the access.
+  // Pointers and References both map to a SPIR-V pointer type.
+  // Transform a Reference to a Pointer to prevent these having duplicated
+  // definitions in the generated SPIR-V. Note that nested pointers and
+  // references are not legal in WGSL, so only considering the top-level type is
+  // fine.
+  std::string type_name;
+  if (auto* ptr = type->As<sem::Pointer>()) {
+    type_name =
+        sem::Pointer(ptr->StoreType(), ptr->StorageClass(), ast::kReadWrite)
+            .type_name();
+  } else if (auto* ref = type->As<sem::Reference>()) {
+    type_name =
+        sem::Pointer(ref->StoreType(), ref->StorageClass(), ast::kReadWrite)
+            .type_name();
+  } else {
+    type_name = type->type_name();
+  }
+
+  return utils::GetOrCreate(type_name_to_id_, type_name, [&]() -> uint32_t {
+    auto result = result_op();
+    auto id = result.to_i();
+    bool ok = Switch(
+        type,
+        [&](const sem::Array* arr) {  //
+          return GenerateArrayType(arr, result);
+        },
+        [&](const sem::Bool*) {
+          push_type(spv::Op::OpTypeBool, {result});
+          return true;
+        },
+        [&](const sem::F32*) {
+          push_type(spv::Op::OpTypeFloat, {result, Operand::Int(32)});
+          return true;
+        },
+        [&](const sem::I32*) {
+          push_type(spv::Op::OpTypeInt,
+                    {result, Operand::Int(32), Operand::Int(1)});
+          return true;
+        },
+        [&](const sem::Matrix* mat) {  //
+          return GenerateMatrixType(mat, result);
+        },
+        [&](const sem::Pointer* ptr) {  //
+          return GeneratePointerType(ptr, result);
+        },
+        [&](const sem::Reference* ref) {  //
+          return GenerateReferenceType(ref, result);
+        },
+        [&](const sem::Struct* str) {  //
+          return GenerateStructType(str, result);
+        },
+        [&](const sem::U32*) {
+          push_type(spv::Op::OpTypeInt,
+                    {result, Operand::Int(32), Operand::Int(0)});
+          return true;
+        },
+        [&](const sem::Vector* vec) {  //
+          return GenerateVectorType(vec, result);
+        },
+        [&](const sem::Void*) {
+          push_type(spv::Op::OpTypeVoid, {result});
+          return true;
+        },
+        [&](const sem::StorageTexture* tex) {
+          if (!GenerateTextureType(tex, result)) {
+            return false;
+          }
+
+          // Register all three access types of StorageTexture names. In
+          // SPIR-V, we must output a single type, while the variable is
+          // annotated with the access type. Doing this ensures we de-dupe.
+          type_name_to_id_[builder_
+                               .create<sem::StorageTexture>(
+                                   tex->dim(), tex->texel_format(),
+                                   ast::Access::kRead, tex->type())
+                               ->type_name()] = id;
+          type_name_to_id_[builder_
+                               .create<sem::StorageTexture>(
+                                   tex->dim(), tex->texel_format(),
+                                   ast::Access::kWrite, tex->type())
+                               ->type_name()] = id;
+          type_name_to_id_[builder_
+                               .create<sem::StorageTexture>(
+                                   tex->dim(), tex->texel_format(),
+                                   ast::Access::kReadWrite, tex->type())
+                               ->type_name()] = id;
+          return true;
+        },
+        [&](const sem::Texture* tex) {
+          return GenerateTextureType(tex, result);
+        },
+        [&](const sem::Sampler*) {
+          push_type(spv::Op::OpTypeSampler, {result});
+
+          // Register both of the sampler type names. In SPIR-V they're the same
+          // sampler type, so we need to match that when we do the dedup check.
+          type_name_to_id_["__sampler_sampler"] = id;
+          type_name_to_id_["__sampler_comparison"] = id;
+          return true;
+        },
+        [&](Default) {
+          error_ = "unable to convert type: " + type->type_name();
+          return false;
+        });
+
+    if (!ok) {
+      return 0;
+    }
+
+    return id;
+  });
+}
+
+bool Builder::GenerateTextureType(const sem::Texture* texture,
+                                  const Operand& result) {
+  uint32_t array_literal = 0u;
+  const auto dim = texture->dim();
+  if (dim == ast::TextureDimension::k2dArray ||
+      dim == ast::TextureDimension::kCubeArray) {
+    array_literal = 1u;
+  }
+
+  uint32_t dim_literal = SpvDim2D;
+  if (dim == ast::TextureDimension::k1d) {
+    dim_literal = SpvDim1D;
+    if (texture->Is<sem::SampledTexture>()) {
+      push_capability(SpvCapabilitySampled1D);
+    } else if (texture->Is<sem::StorageTexture>()) {
+      push_capability(SpvCapabilityImage1D);
+    }
+  }
+  if (dim == ast::TextureDimension::k3d) {
+    dim_literal = SpvDim3D;
+  }
+  if (dim == ast::TextureDimension::kCube ||
+      dim == ast::TextureDimension::kCubeArray) {
+    dim_literal = SpvDimCube;
+  }
+
+  uint32_t ms_literal = 0u;
+  if (texture->IsAnyOf<sem::MultisampledTexture,
+                       sem::DepthMultisampledTexture>()) {
+    ms_literal = 1u;
+  }
+
+  uint32_t depth_literal = 0u;
+  if (texture->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
+    depth_literal = 1u;
+  }
+
+  uint32_t sampled_literal = 2u;
+  if (texture->IsAnyOf<sem::MultisampledTexture, sem::SampledTexture,
+                       sem::DepthTexture, sem::DepthMultisampledTexture>()) {
+    sampled_literal = 1u;
+  }
+
+  if (dim == ast::TextureDimension::kCubeArray) {
+    if (texture->IsAnyOf<sem::SampledTexture, sem::DepthTexture>()) {
+      push_capability(SpvCapabilitySampledCubeArray);
+    }
+  }
+
+  uint32_t type_id = Switch(
+      texture,
+      [&](const sem::DepthTexture*) {
+        return GenerateTypeIfNeeded(builder_.create<sem::F32>());
+      },
+      [&](const sem::DepthMultisampledTexture*) {
+        return GenerateTypeIfNeeded(builder_.create<sem::F32>());
+      },
+      [&](const sem::SampledTexture* t) {
+        return GenerateTypeIfNeeded(t->type());
+      },
+      [&](const sem::MultisampledTexture* t) {
+        return GenerateTypeIfNeeded(t->type());
+      },
+      [&](const sem::StorageTexture* t) {
+        return GenerateTypeIfNeeded(t->type());
+      },
+      [&](Default) -> uint32_t {  //
+        return 0u;
+      });
+  if (type_id == 0u) {
+    return false;
+  }
+
+  uint32_t format_literal = SpvImageFormat_::SpvImageFormatUnknown;
+  if (auto* t = texture->As<sem::StorageTexture>()) {
+    format_literal = convert_texel_format_to_spv(t->texel_format());
+  }
+
+  push_type(spv::Op::OpTypeImage,
+            {result, Operand::Int(type_id), Operand::Int(dim_literal),
+             Operand::Int(depth_literal), Operand::Int(array_literal),
+             Operand::Int(ms_literal), Operand::Int(sampled_literal),
+             Operand::Int(format_literal)});
+
+  return true;
+}
+
+bool Builder::GenerateArrayType(const sem::Array* ary, const Operand& result) {
+  auto elem_type = GenerateTypeIfNeeded(ary->ElemType());
+  if (elem_type == 0) {
+    return false;
+  }
+
+  auto result_id = result.to_i();
+  if (ary->IsRuntimeSized()) {
+    push_type(spv::Op::OpTypeRuntimeArray, {result, Operand::Int(elem_type)});
+  } else {
+    auto len_id = GenerateConstantIfNeeded(ScalarConstant::U32(ary->Count()));
+    if (len_id == 0) {
+      return false;
+    }
+
+    push_type(spv::Op::OpTypeArray,
+              {result, Operand::Int(elem_type), Operand::Int(len_id)});
+  }
+
+  push_annot(spv::Op::OpDecorate,
+             {Operand::Int(result_id), Operand::Int(SpvDecorationArrayStride),
+              Operand::Int(ary->Stride())});
+  return true;
+}
+
+bool Builder::GenerateMatrixType(const sem::Matrix* mat,
+                                 const Operand& result) {
+  auto* col_type = builder_.create<sem::Vector>(mat->type(), mat->rows());
+  auto col_type_id = GenerateTypeIfNeeded(col_type);
+  if (has_error()) {
+    return false;
+  }
+
+  push_type(spv::Op::OpTypeMatrix,
+            {result, Operand::Int(col_type_id), Operand::Int(mat->columns())});
+  return true;
+}
+
+bool Builder::GeneratePointerType(const sem::Pointer* ptr,
+                                  const Operand& result) {
+  auto subtype_id = GenerateTypeIfNeeded(ptr->StoreType());
+  if (subtype_id == 0) {
+    return false;
+  }
+
+  auto stg_class = ConvertStorageClass(ptr->StorageClass());
+  if (stg_class == SpvStorageClassMax) {
+    error_ = "invalid storage class for pointer";
+    return false;
+  }
+
+  push_type(spv::Op::OpTypePointer,
+            {result, Operand::Int(stg_class), Operand::Int(subtype_id)});
+
+  return true;
+}
+
+bool Builder::GenerateReferenceType(const sem::Reference* ref,
+                                    const Operand& result) {
+  auto subtype_id = GenerateTypeIfNeeded(ref->StoreType());
+  if (subtype_id == 0) {
+    return false;
+  }
+
+  auto stg_class = ConvertStorageClass(ref->StorageClass());
+  if (stg_class == SpvStorageClassMax) {
+    error_ = "invalid storage class for reference";
+    return false;
+  }
+
+  push_type(spv::Op::OpTypePointer,
+            {result, Operand::Int(stg_class), Operand::Int(subtype_id)});
+
+  return true;
+}
+
+bool Builder::GenerateStructType(const sem::Struct* struct_type,
+                                 const Operand& result) {
+  auto struct_id = result.to_i();
+
+  if (struct_type->Name().IsValid()) {
+    push_debug(
+        spv::Op::OpName,
+        {Operand::Int(struct_id),
+         Operand::String(builder_.Symbols().NameFor(struct_type->Name()))});
+  }
+
+  OperandList ops;
+  ops.push_back(result);
+
+  auto* decl = struct_type->Declaration();
+  if (decl &&
+      ast::HasAttribute<transform::AddSpirvBlockAttribute::SpirvBlockAttribute>(
+          decl->attributes)) {
+    push_annot(spv::Op::OpDecorate,
+               {Operand::Int(struct_id), Operand::Int(SpvDecorationBlock)});
+  }
+
+  for (uint32_t i = 0; i < struct_type->Members().size(); ++i) {
+    auto mem_id = GenerateStructMember(struct_id, i, struct_type->Members()[i]);
+    if (mem_id == 0) {
+      return false;
+    }
+
+    ops.push_back(Operand::Int(mem_id));
+  }
+
+  push_type(spv::Op::OpTypeStruct, std::move(ops));
+  return true;
+}
+
+uint32_t Builder::GenerateStructMember(uint32_t struct_id,
+                                       uint32_t idx,
+                                       const sem::StructMember* member) {
+  push_debug(spv::Op::OpMemberName,
+             {Operand::Int(struct_id), Operand::Int(idx),
+              Operand::String(builder_.Symbols().NameFor(member->Name()))});
+
+  // Note: This will generate layout annotations for *all* structs, whether or
+  // not they are used in host-shareable variables. This is officially ok in
+  // SPIR-V 1.0 through 1.3. If / when we migrate to using SPIR-V 1.4 we'll have
+  // to only generate the layout info for structs used for certain storage
+  // classes.
+
+  push_annot(
+      spv::Op::OpMemberDecorate,
+      {Operand::Int(struct_id), Operand::Int(idx),
+       Operand::Int(SpvDecorationOffset), Operand::Int(member->Offset())});
+
+  // Infer and emit matrix layout.
+  auto* matrix_type = GetNestedMatrixType(member->Type());
+  if (matrix_type) {
+    push_annot(spv::Op::OpMemberDecorate,
+               {Operand::Int(struct_id), Operand::Int(idx),
+                Operand::Int(SpvDecorationColMajor)});
+    if (!matrix_type->type()->Is<sem::F32>()) {
+      error_ = "matrix scalar element type must be f32";
+      return 0;
+    }
+    const auto scalar_elem_size = 4;
+    const auto effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
+    push_annot(spv::Op::OpMemberDecorate,
+               {Operand::Int(struct_id), Operand::Int(idx),
+                Operand::Int(SpvDecorationMatrixStride),
+                Operand::Int(effective_row_count * scalar_elem_size)});
+  }
+
+  return GenerateTypeIfNeeded(member->Type());
+}
+
+bool Builder::GenerateVectorType(const sem::Vector* vec,
+                                 const Operand& result) {
+  auto type_id = GenerateTypeIfNeeded(vec->type());
+  if (has_error()) {
+    return false;
+  }
+
+  push_type(spv::Op::OpTypeVector,
+            {result, Operand::Int(type_id), Operand::Int(vec->Width())});
+  return true;
+}
+
+SpvStorageClass Builder::ConvertStorageClass(ast::StorageClass klass) const {
+  switch (klass) {
+    case ast::StorageClass::kInvalid:
+      return SpvStorageClassMax;
+    case ast::StorageClass::kInput:
+      return SpvStorageClassInput;
+    case ast::StorageClass::kOutput:
+      return SpvStorageClassOutput;
+    case ast::StorageClass::kUniform:
+      return SpvStorageClassUniform;
+    case ast::StorageClass::kWorkgroup:
+      return SpvStorageClassWorkgroup;
+    case ast::StorageClass::kUniformConstant:
+      return SpvStorageClassUniformConstant;
+    case ast::StorageClass::kStorage:
+      return SpvStorageClassStorageBuffer;
+    case ast::StorageClass::kPrivate:
+      return SpvStorageClassPrivate;
+    case ast::StorageClass::kFunction:
+      return SpvStorageClassFunction;
+    case ast::StorageClass::kNone:
+      break;
+  }
+  return SpvStorageClassMax;
+}
+
+SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin,
+                                   ast::StorageClass storage) {
+  switch (builtin) {
+    case ast::Builtin::kPosition:
+      if (storage == ast::StorageClass::kInput) {
+        return SpvBuiltInFragCoord;
+      } else if (storage == ast::StorageClass::kOutput) {
+        return SpvBuiltInPosition;
+      } else {
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "invalid storage class for builtin";
+        break;
+      }
+    case ast::Builtin::kVertexIndex:
+      return SpvBuiltInVertexIndex;
+    case ast::Builtin::kInstanceIndex:
+      return SpvBuiltInInstanceIndex;
+    case ast::Builtin::kFrontFacing:
+      return SpvBuiltInFrontFacing;
+    case ast::Builtin::kFragDepth:
+      return SpvBuiltInFragDepth;
+    case ast::Builtin::kLocalInvocationId:
+      return SpvBuiltInLocalInvocationId;
+    case ast::Builtin::kLocalInvocationIndex:
+      return SpvBuiltInLocalInvocationIndex;
+    case ast::Builtin::kGlobalInvocationId:
+      return SpvBuiltInGlobalInvocationId;
+    case ast::Builtin::kPointSize:
+      return SpvBuiltInPointSize;
+    case ast::Builtin::kWorkgroupId:
+      return SpvBuiltInWorkgroupId;
+    case ast::Builtin::kNumWorkgroups:
+      return SpvBuiltInNumWorkgroups;
+    case ast::Builtin::kSampleIndex:
+      push_capability(SpvCapabilitySampleRateShading);
+      return SpvBuiltInSampleId;
+    case ast::Builtin::kSampleMask:
+      return SpvBuiltInSampleMask;
+    case ast::Builtin::kNone:
+      break;
+  }
+  return SpvBuiltInMax;
+}
+
+void Builder::AddInterpolationDecorations(uint32_t id,
+                                          ast::InterpolationType type,
+                                          ast::InterpolationSampling sampling) {
+  switch (type) {
+    case ast::InterpolationType::kLinear:
+      push_annot(spv::Op::OpDecorate,
+                 {Operand::Int(id), Operand::Int(SpvDecorationNoPerspective)});
+      break;
+    case ast::InterpolationType::kFlat:
+      push_annot(spv::Op::OpDecorate,
+                 {Operand::Int(id), Operand::Int(SpvDecorationFlat)});
+      break;
+    case ast::InterpolationType::kPerspective:
+      break;
+  }
+  switch (sampling) {
+    case ast::InterpolationSampling::kCentroid:
+      push_annot(spv::Op::OpDecorate,
+                 {Operand::Int(id), Operand::Int(SpvDecorationCentroid)});
+      break;
+    case ast::InterpolationSampling::kSample:
+      push_capability(SpvCapabilitySampleRateShading);
+      push_annot(spv::Op::OpDecorate,
+                 {Operand::Int(id), Operand::Int(SpvDecorationSample)});
+      break;
+    case ast::InterpolationSampling::kCenter:
+    case ast::InterpolationSampling::kNone:
+      break;
+  }
+}
+
+SpvImageFormat Builder::convert_texel_format_to_spv(
+    const ast::TexelFormat format) {
+  switch (format) {
+    case ast::TexelFormat::kR32Uint:
+      return SpvImageFormatR32ui;
+    case ast::TexelFormat::kR32Sint:
+      return SpvImageFormatR32i;
+    case ast::TexelFormat::kR32Float:
+      return SpvImageFormatR32f;
+    case ast::TexelFormat::kRgba8Unorm:
+      return SpvImageFormatRgba8;
+    case ast::TexelFormat::kRgba8Snorm:
+      return SpvImageFormatRgba8Snorm;
+    case ast::TexelFormat::kRgba8Uint:
+      return SpvImageFormatRgba8ui;
+    case ast::TexelFormat::kRgba8Sint:
+      return SpvImageFormatRgba8i;
+    case ast::TexelFormat::kRg32Uint:
+      push_capability(SpvCapabilityStorageImageExtendedFormats);
+      return SpvImageFormatRg32ui;
+    case ast::TexelFormat::kRg32Sint:
+      push_capability(SpvCapabilityStorageImageExtendedFormats);
+      return SpvImageFormatRg32i;
+    case ast::TexelFormat::kRg32Float:
+      push_capability(SpvCapabilityStorageImageExtendedFormats);
+      return SpvImageFormatRg32f;
+    case ast::TexelFormat::kRgba16Uint:
+      return SpvImageFormatRgba16ui;
+    case ast::TexelFormat::kRgba16Sint:
+      return SpvImageFormatRgba16i;
+    case ast::TexelFormat::kRgba16Float:
+      return SpvImageFormatRgba16f;
+    case ast::TexelFormat::kRgba32Uint:
+      return SpvImageFormatRgba32ui;
+    case ast::TexelFormat::kRgba32Sint:
+      return SpvImageFormatRgba32i;
+    case ast::TexelFormat::kRgba32Float:
+      return SpvImageFormatRgba32f;
+    case ast::TexelFormat::kNone:
+      return SpvImageFormatUnknown;
+  }
+  return SpvImageFormatUnknown;
+}
+
+bool Builder::push_function_inst(spv::Op op, const OperandList& operands) {
+  if (functions_.empty()) {
+    std::ostringstream ss;
+    ss << "Internal error: trying to add SPIR-V instruction " << int(op)
+       << " outside a function";
+    error_ = ss.str();
+    return false;
+  }
+  functions_.back().push_inst(op, operands);
+  return true;
+}
+
+bool Builder::InsideBasicBlock() const {
+  if (functions_.empty()) {
+    return false;
+  }
+  const auto& instructions = functions_.back().instructions();
+  if (instructions.empty()) {
+    // The Function object does not explicitly represent its entry block
+    // label.  So return *true* because an empty list means the only
+    // thing in the function is that entry block label.
+    return true;
+  }
+  const auto& inst = instructions.back();
+  switch (inst.opcode()) {
+    case spv::Op::OpBranch:
+    case spv::Op::OpBranchConditional:
+    case spv::Op::OpSwitch:
+    case spv::Op::OpReturn:
+    case spv::Op::OpReturnValue:
+    case spv::Op::OpUnreachable:
+    case spv::Op::OpKill:
+    case spv::Op::OpTerminateInvocation:
+      return false;
+    default:
+      break;
+  }
+  return true;
+}
+
+Builder::ContinuingInfo::ContinuingInfo(
+    const ast::Statement* the_last_statement,
+    uint32_t loop_id,
+    uint32_t break_id)
+    : last_statement(the_last_statement),
+      loop_header_id(loop_id),
+      break_target_id(break_id) {
+  TINT_ASSERT(Writer, last_statement != nullptr);
+  TINT_ASSERT(Writer, loop_header_id != 0u);
+  TINT_ASSERT(Writer, break_target_id != 0u);
+}
+
+Builder::Backedge::Backedge(spv::Op the_opcode, OperandList the_operands)
+    : opcode(the_opcode), operands(the_operands) {}
+
+Builder::Backedge::Backedge(const Builder::Backedge& other) = default;
+Builder::Backedge& Builder::Backedge::operator=(
+    const Builder::Backedge& other) = default;
+Builder::Backedge::~Backedge() = default;
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
new file mode 100644
index 0000000..c7eea0d
--- /dev/null
+++ b/src/tint/writer/spirv/builder.h
@@ -0,0 +1,660 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_BUILDER_H_
+#define SRC_TINT_WRITER_SPIRV_BUILDER_H_
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "spirv/unified1/spirv.h"
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/builtin.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/writer/spirv/function.h"
+#include "src/tint/writer/spirv/scalar_constant.h"
+
+namespace tint {
+
+// Forward declarations
+namespace sem {
+class Call;
+class Reference;
+class TypeConstructor;
+class TypeConversion;
+}  // namespace sem
+
+namespace writer {
+namespace spirv {
+
+/// The result of sanitizing a program for generation.
+struct SanitizedResult {
+  /// The sanitized program.
+  Program program;
+};
+
+/// Sanitize a program in preparation for generating SPIR-V.
+/// @param emit_vertex_point_size `true` to emit a vertex point size builtin
+/// @param disable_workgroup_init `true` to disable workgroup memory zero
+/// @returns the sanitized program and any supplementary information
+SanitizedResult Sanitize(const Program* program,
+                         bool emit_vertex_point_size = false,
+                         bool disable_workgroup_init = false);
+
+/// Builder class to create SPIR-V instructions from a module.
+class Builder {
+ public:
+  /// Contains information for generating accessor chains
+  struct AccessorInfo {
+    AccessorInfo();
+    ~AccessorInfo();
+
+    /// The ID of the current chain source. The chain source may change as we
+    /// evaluate the access chain. The chain source always points to the ID
+    /// which we will use to evaluate the current set of accessors. This maybe
+    /// the original variable, or maybe an intermediary if we had to evaulate
+    /// the access chain early (in the case of a swizzle of an access chain).
+    uint32_t source_id;
+    /// The type of the current chain source. This type matches the deduced
+    /// result_type of the current source defined above.
+    const sem::Type* source_type;
+    /// A list of access chain indices to emit. Note, we _only_ have access
+    /// chain indices if the source is reference.
+    std::vector<uint32_t> access_chain_indices;
+  };
+
+  /// Constructor
+  /// @param program the program
+  explicit Builder(const Program* program);
+  ~Builder();
+
+  /// Generates the SPIR-V instructions for the given program
+  /// @returns true if the SPIR-V was successfully built
+  bool Build();
+
+  /// @returns the error string or blank if no error was reported.
+  const std::string& error() const { return error_; }
+  /// @returns true if the builder encountered an error
+  bool has_error() const { return !error_.empty(); }
+
+  /// @returns the number of uint32_t's needed to make up the results
+  uint32_t total_size() const;
+
+  /// @returns the id bound for this program
+  uint32_t id_bound() const { return next_id_; }
+
+  /// @returns the next id to be used
+  uint32_t next_id() {
+    auto id = next_id_;
+    next_id_ += 1;
+    return id;
+  }
+
+  /// Iterates over all the instructions in the correct order and calls the
+  /// given callback
+  /// @param cb the callback to execute
+  void iterate(std::function<void(const Instruction&)> cb) const;
+
+  /// Adds an instruction to the list of capabilities, if the capability
+  /// hasn't already been added.
+  /// @param cap the capability to set
+  void push_capability(uint32_t cap);
+  /// @returns the capabilities
+  const InstructionList& capabilities() const { return capabilities_; }
+  /// Adds an instruction to the extensions
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_extension(spv::Op op, const OperandList& operands) {
+    extensions_.push_back(Instruction{op, operands});
+  }
+  /// @returns the extensions
+  const InstructionList& extensions() const { return extensions_; }
+  /// Adds an instruction to the ext import
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_ext_import(spv::Op op, const OperandList& operands) {
+    ext_imports_.push_back(Instruction{op, operands});
+  }
+  /// @returns the ext imports
+  const InstructionList& ext_imports() const { return ext_imports_; }
+  /// Adds an instruction to the memory model
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_memory_model(spv::Op op, const OperandList& operands) {
+    memory_model_.push_back(Instruction{op, operands});
+  }
+  /// @returns the memory model
+  const InstructionList& memory_model() const { return memory_model_; }
+  /// Adds an instruction to the entry points
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_entry_point(spv::Op op, const OperandList& operands) {
+    entry_points_.push_back(Instruction{op, operands});
+  }
+  /// @returns the entry points
+  const InstructionList& entry_points() const { return entry_points_; }
+  /// Adds an instruction to the execution modes
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_execution_mode(spv::Op op, const OperandList& operands) {
+    execution_modes_.push_back(Instruction{op, operands});
+  }
+  /// @returns the execution modes
+  const InstructionList& execution_modes() const { return execution_modes_; }
+  /// Adds an instruction to the debug
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_debug(spv::Op op, const OperandList& operands) {
+    debug_.push_back(Instruction{op, operands});
+  }
+  /// @returns the debug instructions
+  const InstructionList& debug() const { return debug_; }
+  /// Adds an instruction to the types
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_type(spv::Op op, const OperandList& operands) {
+    types_.push_back(Instruction{op, operands});
+  }
+  /// @returns the type instructions
+  const InstructionList& types() const { return types_; }
+  /// Adds an instruction to the annotations
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_annot(spv::Op op, const OperandList& operands) {
+    annotations_.push_back(Instruction{op, operands});
+  }
+  /// @returns the annotations
+  const InstructionList& annots() const { return annotations_; }
+
+  /// Adds a function to the builder
+  /// @param func the function to add
+  void push_function(const Function& func) {
+    functions_.push_back(func);
+    current_label_id_ = func.label_id();
+  }
+  /// @returns the functions
+  const std::vector<Function>& functions() const { return functions_; }
+  /// Pushes an instruction to the current function. If we're outside
+  /// a function then issue an internal error and return false.
+  /// @param op the operation
+  /// @param operands the operands
+  /// @returns true if we succeeded
+  bool push_function_inst(spv::Op op, const OperandList& operands);
+  /// Pushes a variable to the current function
+  /// @param operands the variable operands
+  void push_function_var(const OperandList& operands) {
+    if (functions_.empty()) {
+      TINT_ICE(Writer, builder_.Diagnostics())
+          << "push_function_var() called without a function";
+    }
+    functions_.back().push_var(operands);
+  }
+
+  /// @returns true if the current instruction insertion point is
+  /// inside a basic block.
+  bool InsideBasicBlock() const;
+
+  /// Converts a storage class to a SPIR-V storage class.
+  /// @param klass the storage class to convert
+  /// @returns the SPIR-V storage class or SpvStorageClassMax on error.
+  SpvStorageClass ConvertStorageClass(ast::StorageClass klass) const;
+  /// Converts a builtin to a SPIR-V builtin and pushes a capability if needed.
+  /// @param builtin the builtin to convert
+  /// @param storage the storage class that this builtin is being used with
+  /// @returns the SPIR-V builtin or SpvBuiltInMax on error.
+  SpvBuiltIn ConvertBuiltin(ast::Builtin builtin, ast::StorageClass storage);
+
+  /// Converts an interpolate attribute to SPIR-V decorations and pushes a
+  /// capability if needed.
+  /// @param id the id to decorate
+  /// @param type the interpolation type
+  /// @param sampling the interpolation sampling
+  void AddInterpolationDecorations(uint32_t id,
+                                   ast::InterpolationType type,
+                                   ast::InterpolationSampling sampling);
+
+  /// Generates a label for the given id. Emits an error and returns false if
+  /// we're currently outside a function.
+  /// @param id the id to use for the label
+  /// @returns true on success.
+  bool GenerateLabel(uint32_t id);
+  /// Generates an assignment statement
+  /// @param assign the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateAssignStatement(const ast::AssignmentStatement* assign);
+  /// Generates a block statement, wrapped in a push/pop scope
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateBlockStatement(const ast::BlockStatement* stmt);
+  /// Generates a block statement
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateBlockStatementWithoutScoping(const ast::BlockStatement* stmt);
+  /// Generates a break statement
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateBreakStatement(const ast::BreakStatement* stmt);
+  /// Generates a continue statement
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateContinueStatement(const ast::ContinueStatement* stmt);
+  /// Generates a discard statement
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was successfully generated
+  bool GenerateDiscardStatement(const ast::DiscardStatement* stmt);
+  /// Generates an entry point instruction
+  /// @param func the function
+  /// @param id the id of the function
+  /// @returns true if the instruction was generated, false otherwise
+  bool GenerateEntryPoint(const ast::Function* func, uint32_t id);
+  /// Generates execution modes for an entry point
+  /// @param func the function
+  /// @param id the id of the function
+  /// @returns false on failure
+  bool GenerateExecutionModes(const ast::Function* func, uint32_t id);
+  /// Generates an expression
+  /// @param expr the expression to generate
+  /// @returns the resulting ID of the expression or 0 on error
+  uint32_t GenerateExpression(const ast::Expression* expr);
+  /// Generates the instructions for a function
+  /// @param func the function to generate
+  /// @returns true if the instructions were generated
+  bool GenerateFunction(const ast::Function* func);
+  /// Generates a function type if not already created
+  /// @param func the function to generate for
+  /// @returns the ID to use for the function type. Returns 0 on failure.
+  uint32_t GenerateFunctionTypeIfNeeded(const sem::Function* func);
+  /// Generates access control annotations if needed
+  /// @param type the type to generate for
+  /// @param struct_id the struct id
+  /// @param member_idx the member index
+  void GenerateMemberAccessIfNeeded(const sem::Type* type,
+                                    uint32_t struct_id,
+                                    uint32_t member_idx);
+  /// Generates a function variable
+  /// @param var the variable
+  /// @returns true if the variable was generated
+  bool GenerateFunctionVariable(const ast::Variable* var);
+  /// Generates a global variable
+  /// @param var the variable to generate
+  /// @returns true if the variable is emited.
+  bool GenerateGlobalVariable(const ast::Variable* var);
+  /// Generates an index accessor expression.
+  ///
+  /// For more information on accessors see the "Pointer evaluation" section of
+  /// the WGSL specification.
+  ///
+  /// @param expr the expresssion to generate
+  /// @returns the id of the expression or 0 on failure
+  uint32_t GenerateAccessorExpression(const ast::Expression* expr);
+  /// Generates an index accessor
+  /// @param expr the accessor to generate
+  /// @param info the current accessor information
+  /// @returns true if the accessor was generated successfully
+  bool GenerateIndexAccessor(const ast::IndexAccessorExpression* expr,
+                             AccessorInfo* info);
+  /// Generates a member accessor
+  /// @param expr the accessor to generate
+  /// @param info the current accessor information
+  /// @returns true if the accessor was generated successfully
+  bool GenerateMemberAccessor(const ast::MemberAccessorExpression* expr,
+                              AccessorInfo* info);
+  /// Generates an identifier expression
+  /// @param expr the expresssion to generate
+  /// @returns the id of the expression or 0 on failure
+  uint32_t GenerateIdentifierExpression(const ast::IdentifierExpression* expr);
+  /// Generates a unary op expression
+  /// @param expr the expression to generate
+  /// @returns the id of the expression or 0 on failure
+  uint32_t GenerateUnaryOpExpression(const ast::UnaryOpExpression* expr);
+  /// Generates an if statement
+  /// @param stmt the statement to generate
+  /// @returns true on success
+  bool GenerateIfStatement(const ast::IfStatement* stmt);
+  /// Generates an import instruction for the "GLSL.std.450" extended
+  /// instruction set, if one doesn't exist yet, and returns the import ID.
+  /// @returns the import ID, or 0 on error.
+  uint32_t GetGLSLstd450Import();
+  /// Generates a constructor expression
+  /// @param var the variable generated for, nullptr if no variable associated.
+  /// @param expr the expression to generate
+  /// @returns the ID of the expression or 0 on failure.
+  uint32_t GenerateConstructorExpression(const ast::Variable* var,
+                                         const ast::Expression* expr);
+  /// Generates a literal constant if needed
+  /// @param var the variable generated for, nullptr if no variable associated.
+  /// @param lit the literal to generate
+  /// @returns the ID on success or 0 on failure
+  uint32_t GenerateLiteralIfNeeded(const ast::Variable* var,
+                                   const ast::LiteralExpression* lit);
+  /// Generates a binary expression
+  /// @param expr the expression to generate
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateBinaryExpression(const ast::BinaryExpression* expr);
+  /// Generates a bitcast expression
+  /// @param expr the expression to generate
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateBitcastExpression(const ast::BitcastExpression* expr);
+  /// Generates a short circuting binary expression
+  /// @param expr the expression to generate
+  /// @returns teh expression ID on success or 0 otherwise
+  uint32_t GenerateShortCircuitBinaryExpression(
+      const ast::BinaryExpression* expr);
+  /// Generates a call expression
+  /// @param expr the expression to generate
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateCallExpression(const ast::CallExpression* expr);
+  /// Handles generating a function call expression
+  /// @param call the call expression
+  /// @param function the function being called
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateFunctionCall(const sem::Call* call,
+                                const sem::Function* function);
+  /// Handles generating a builtin call expression
+  /// @param call the call expression
+  /// @param builtin the builtin being called
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateBuiltinCall(const sem::Call* call,
+                               const sem::Builtin* builtin);
+  /// Handles generating a type constructor or type conversion expression
+  /// @param call the call expression
+  /// @param var the variable that is being initialized. May be null.
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateTypeConstructorOrConversion(const sem::Call* call,
+                                               const ast::Variable* var);
+  /// Generates a texture builtin call. Emits an error and returns false if
+  /// we're currently outside a function.
+  /// @param call the call expression
+  /// @param builtin the semantic information for the texture builtin
+  /// @param result_type result type operand of the texture instruction
+  /// @param result_id result identifier operand of the texture instruction
+  /// parameters
+  /// @returns true on success
+  bool GenerateTextureBuiltin(const sem::Call* call,
+                              const sem::Builtin* builtin,
+                              spirv::Operand result_type,
+                              spirv::Operand result_id);
+  /// Generates a control barrier statement.
+  /// @param builtin the semantic information for the barrier builtin call
+  /// @returns true on success
+  bool GenerateControlBarrierBuiltin(const sem::Builtin* builtin);
+  /// Generates an atomic builtin call.
+  /// @param call the call expression
+  /// @param builtin the semantic information for the atomic builtin call
+  /// @param result_type result type operand of the texture instruction
+  /// @param result_id result identifier operand of the texture instruction
+  /// @returns true on success
+  bool GenerateAtomicBuiltin(const sem::Call* call,
+                             const sem::Builtin* builtin,
+                             Operand result_type,
+                             Operand result_id);
+  /// Generates a sampled image
+  /// @param texture_type the texture type
+  /// @param texture_operand the texture operand
+  /// @param sampler_operand the sampler operand
+  /// @returns the expression ID
+  uint32_t GenerateSampledImage(const sem::Type* texture_type,
+                                Operand texture_operand,
+                                Operand sampler_operand);
+  /// Generates a cast or object copy for the expression result,
+  /// or return the ID generated the expression if it is already
+  /// of the right type.
+  /// @param to_type the type we're casting too
+  /// @param from_expr the expression to cast
+  /// @param is_global_init if this is a global initializer
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateCastOrCopyOrPassthrough(const sem::Type* to_type,
+                                           const ast::Expression* from_expr,
+                                           bool is_global_init);
+  /// Generates a loop statement
+  /// @param stmt the statement to generate
+  /// @returns true on successful generation
+  bool GenerateLoopStatement(const ast::LoopStatement* stmt);
+  /// Generates a return statement
+  /// @param stmt the statement to generate
+  /// @returns true on success, false otherwise
+  bool GenerateReturnStatement(const ast::ReturnStatement* stmt);
+  /// Generates a switch statement
+  /// @param stmt the statement to generate
+  /// @returns ture on success, false otherwise
+  bool GenerateSwitchStatement(const ast::SwitchStatement* stmt);
+  /// Generates a conditional section merge block
+  /// @param cond the condition
+  /// @param true_body the statements making up the true block
+  /// @param cur_else_idx the index of the current else statement to process
+  /// @param else_stmts the list of all else statements
+  /// @returns true on success, false on failure
+  bool GenerateConditionalBlock(const ast::Expression* cond,
+                                const ast::BlockStatement* true_body,
+                                size_t cur_else_idx,
+                                const ast::ElseStatementList& else_stmts);
+  /// Generates a statement
+  /// @param stmt the statement to generate
+  /// @returns true if the statement was generated
+  bool GenerateStatement(const ast::Statement* stmt);
+  /// Generates an expression. If the WGSL expression does not have reference
+  /// type, then return the SPIR-V ID for the expression. Otherwise implement
+  /// the WGSL Load Rule: generate an OpLoad and return the ID of the result.
+  /// Returns 0 if the expression could not be generated.
+  /// @param expr the semantic expression node to be generated
+  /// @returns the the ID of the expression, or loaded expression
+  uint32_t GenerateExpressionWithLoadIfNeeded(const sem::Expression* expr);
+  /// Generates an expression. If the WGSL expression does not have reference
+  /// type, then return the SPIR-V ID for the expression. Otherwise implement
+  /// the WGSL Load Rule: generate an OpLoad and return the ID of the result.
+  /// Returns 0 if the expression could not be generated.
+  /// @param expr the AST expression to be generated
+  /// @returns the the ID of the expression, or loaded expression
+  uint32_t GenerateExpressionWithLoadIfNeeded(const ast::Expression* expr);
+  /// Generates an OpLoad on the given ID if it has reference type in WGSL,
+  /// othewrise return the ID itself.
+  /// @param type the type of the expression
+  /// @param id the SPIR-V id of the experssion
+  /// @returns the ID of the loaded value or `id` if type is not a reference
+  uint32_t GenerateLoadIfNeeded(const sem::Type* type, uint32_t id);
+  /// Generates an OpStore. Emits an error and returns false if we're
+  /// currently outside a function.
+  /// @param to the ID to store too
+  /// @param from the ID to store from
+  /// @returns true on success
+  bool GenerateStore(uint32_t to, uint32_t from);
+  /// Generates a type if not already created
+  /// @param type the type to create
+  /// @returns the ID to use for the given type. Returns 0 on unknown type.
+  uint32_t GenerateTypeIfNeeded(const sem::Type* type);
+  /// Generates a texture type declaration
+  /// @param texture the texture to generate
+  /// @param result the result operand
+  /// @returns true if the texture was successfully generated
+  bool GenerateTextureType(const sem::Texture* texture, const Operand& result);
+  /// Generates an array type declaration
+  /// @param ary the array to generate
+  /// @param result the result operand
+  /// @returns true if the array was successfully generated
+  bool GenerateArrayType(const sem::Array* ary, const Operand& result);
+  /// Generates a matrix type declaration
+  /// @param mat the matrix to generate
+  /// @param result the result operand
+  /// @returns true if the matrix was successfully generated
+  bool GenerateMatrixType(const sem::Matrix* mat, const Operand& result);
+  /// Generates a pointer type declaration
+  /// @param ptr the pointer type to generate
+  /// @param result the result operand
+  /// @returns true if the pointer was successfully generated
+  bool GeneratePointerType(const sem::Pointer* ptr, const Operand& result);
+  /// Generates a reference type declaration
+  /// @param ref the reference type to generate
+  /// @param result the result operand
+  /// @returns true if the reference was successfully generated
+  bool GenerateReferenceType(const sem::Reference* ref, const Operand& result);
+  /// Generates a vector type declaration
+  /// @param struct_type the vector to generate
+  /// @param result the result operand
+  /// @returns true if the vector was successfully generated
+  bool GenerateStructType(const sem::Struct* struct_type,
+                          const Operand& result);
+  /// Generates a struct member
+  /// @param struct_id the id of the parent structure
+  /// @param idx the index of the member
+  /// @param member the member to generate
+  /// @returns the id of the struct member or 0 on error.
+  uint32_t GenerateStructMember(uint32_t struct_id,
+                                uint32_t idx,
+                                const sem::StructMember* member);
+  /// Generates a variable declaration statement
+  /// @param stmt the statement to generate
+  /// @returns true on successfull generation
+  bool GenerateVariableDeclStatement(const ast::VariableDeclStatement* stmt);
+  /// Generates a vector type declaration
+  /// @param vec the vector to generate
+  /// @param result the result operand
+  /// @returns true if the vector was successfully generated
+  bool GenerateVectorType(const sem::Vector* vec, const Operand& result);
+
+  /// Generates instructions to splat `scalar_id` into a vector of type
+  /// `vec_type`
+  /// @param scalar_id scalar to splat
+  /// @param vec_type type of vector
+  /// @returns id of the new vector
+  uint32_t GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type);
+
+  /// Generates instructions to add or subtract two matrices
+  /// @param lhs_id id of multiplicand
+  /// @param rhs_id id of multiplier
+  /// @param type type of both matrices and of result
+  /// @param op one of `spv::Op::OpFAdd` or `spv::Op::OpFSub`
+  /// @returns id of the result matrix
+  uint32_t GenerateMatrixAddOrSub(uint32_t lhs_id,
+                                  uint32_t rhs_id,
+                                  const sem::Matrix* type,
+                                  spv::Op op);
+
+  /// Converts TexelFormat to SPIR-V and pushes an appropriate capability.
+  /// @param format AST image format type
+  /// @returns SPIR-V image format type
+  SpvImageFormat convert_texel_format_to_spv(const ast::TexelFormat format);
+
+  /// Determines if the given type constructor is created from constant values
+  /// @param expr the expression to check
+  /// @returns true if the constructor is constant
+  bool IsConstructorConst(const ast::Expression* expr);
+
+ private:
+  /// @returns an Operand with a new result ID in it. Increments the next_id_
+  /// automatically.
+  Operand result_op();
+
+  /// @returns the resolved type of the ast::Expression `expr`
+  /// @param expr the expression
+  const sem::Type* TypeOf(const ast::Expression* expr) const {
+    return builder_.TypeOf(expr);
+  }
+
+  /// Generates a scalar constant if needed
+  /// @param constant the constant to generate.
+  /// @returns the ID on success or 0 on failure
+  uint32_t GenerateConstantIfNeeded(const ScalarConstant& constant);
+
+  /// Generates a constant-null of the given type, if needed
+  /// @param type the type of the constant null to generate.
+  /// @returns the ID on success or 0 on failure
+  uint32_t GenerateConstantNullIfNeeded(const sem::Type* type);
+
+  /// Generates a vector constant splat if needed
+  /// @param type the type of the vector to generate
+  /// @param value_id the ID of the scalar value to splat
+  /// @returns the ID on success or 0 on failure
+  uint32_t GenerateConstantVectorSplatIfNeeded(const sem::Vector* type,
+                                               uint32_t value_id);
+
+  ProgramBuilder builder_;
+  std::string error_;
+  uint32_t next_id_ = 1;
+  uint32_t current_label_id_ = 0;
+  InstructionList capabilities_;
+  InstructionList extensions_;
+  InstructionList ext_imports_;
+  InstructionList memory_model_;
+  InstructionList entry_points_;
+  InstructionList execution_modes_;
+  InstructionList debug_;
+  InstructionList types_;
+  InstructionList annotations_;
+  std::vector<Function> functions_;
+
+  std::unordered_map<std::string, uint32_t> import_name_to_id_;
+  std::unordered_map<Symbol, uint32_t> func_symbol_to_id_;
+  std::unordered_map<sem::CallTargetSignature, uint32_t> func_sig_to_id_;
+  std::unordered_map<std::string, uint32_t> type_name_to_id_;
+  std::unordered_map<ScalarConstant, uint32_t> const_to_id_;
+  std::unordered_map<std::string, uint32_t> type_constructor_to_id_;
+  std::unordered_map<std::string, uint32_t> const_null_to_id_;
+  std::unordered_map<uint64_t, uint32_t> const_splat_to_id_;
+  std::unordered_map<std::string, uint32_t>
+      texture_type_name_to_sampled_image_type_id_;
+  ScopeStack<uint32_t> scope_stack_;
+  std::unordered_map<uint32_t, const ast::Variable*> spirv_id_to_variable_;
+  std::vector<uint32_t> merge_stack_;
+  std::vector<uint32_t> continue_stack_;
+  std::unordered_set<uint32_t> capability_set_;
+  bool has_overridable_workgroup_size_ = false;
+
+  struct ContinuingInfo {
+    ContinuingInfo(const ast::Statement* last_statement,
+                   uint32_t loop_header_id,
+                   uint32_t break_target_id);
+    // The last statement in the continiung block.
+    const ast::Statement* const last_statement = nullptr;
+    // The ID of the loop header
+    const uint32_t loop_header_id = 0u;
+    // The ID of the merge block for the loop.
+    const uint32_t break_target_id = 0u;
+  };
+  // Stack of nodes, where each is the last statement in a surrounding
+  // continuing block.
+  std::vector<ContinuingInfo> continuing_stack_;
+
+  // The instruction to emit as the backedge of a loop.
+  struct Backedge {
+    Backedge(spv::Op, OperandList);
+    Backedge(const Backedge&);
+    Backedge& operator=(const Backedge&);
+    ~Backedge();
+
+    spv::Op opcode;
+    OperandList operands;
+  };
+  std::vector<Backedge> backedge_stack_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_BUILDER_H_
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
new file mode 100644
index 0000000..a545613
--- /dev/null
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -0,0 +1,1047 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, IndexAccessor_VectorRef_Literal) {
+  // var ary : vec3<f32>;
+  // ary[1]  -> ref<f32>
+
+  auto* var = Var("ary", ty.vec3<f32>());
+
+  auto* ary = Expr("ary");
+  auto* idx_expr = Expr(1);
+
+  auto* expr = IndexAccessor(ary, idx_expr);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%6 = OpTypeInt 32 1
+%7 = OpConstant %6 1
+%8 = OpTypePointer Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpAccessChain %8 %1 %7
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_VectorRef_Dynamic) {
+  // var ary : vec3<f32>;
+  // var idx : i32;
+  // ary[idx]  -> ref<f32>
+
+  auto* var = Var("ary", ty.vec3<f32>());
+  auto* idx = Var("idx", ty.i32());
+
+  auto* ary = Expr("ary");
+  auto* idx_expr = Expr("idx");
+
+  auto* expr = IndexAccessor(ary, idx_expr);
+  WrapInFunction(var, idx, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunctionVariable(idx)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 12u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%8 = OpTypeInt 32 1
+%7 = OpTypePointer Function %8
+%9 = OpConstantNull %8
+%11 = OpTypePointer Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+%6 = OpVariable %7 Function %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%10 = OpLoad %8 %6
+%12 = OpAccessChain %11 %1 %10
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_VectorRef_Dynamic2) {
+  // var ary : vec3<f32>;
+  // ary[1 + 2]  -> ref<f32>
+
+  auto* var = Var("ary", ty.vec3<f32>());
+
+  auto* ary = Expr("ary");
+
+  auto* expr = IndexAccessor(ary, Add(1, 2));
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%6 = OpTypeInt 32 1
+%7 = OpConstant %6 1
+%8 = OpConstant %6 2
+%10 = OpTypePointer Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpIAdd %6 %7 %8
+%11 = OpAccessChain %10 %1 %9
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_ArrayRef_MultiLevel) {
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
+
+  // var ary : array<vec3<f32>, 4>
+  // ary[3][2];
+
+  auto* var = Var("ary", ary4);
+
+  auto* expr = IndexAccessor(IndexAccessor("ary", 3), 2);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 13u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 4
+%3 = OpTypeArray %4 %7
+%2 = OpTypePointer Function %3
+%8 = OpConstantNull %3
+%9 = OpTypeInt 32 1
+%10 = OpConstant %9 3
+%11 = OpConstant %9 2
+%12 = OpTypePointer Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %8
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%13 = OpAccessChain %12 %1 %10 %11
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_ArrayRef_ArrayWithSwizzle) {
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
+
+  // var a : array<vec3<f32>, 4>;
+  // a[2].xy;
+
+  auto* var = Var("ary", ary4);
+
+  auto* expr = MemberAccessor(IndexAccessor("ary", 2), "xy");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 15u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 4
+%3 = OpTypeArray %4 %7
+%2 = OpTypePointer Function %3
+%8 = OpConstantNull %3
+%9 = OpTypeInt 32 1
+%10 = OpConstant %9 2
+%11 = OpTypePointer Function %4
+%13 = OpTypeVector %5 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %8
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%12 = OpAccessChain %11 %1 %10
+%14 = OpLoad %4 %12
+%15 = OpVectorShuffle %13 %14 %14 0 1
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor) {
+  // my_struct {
+  //   a : f32
+  //   b : f32
+  // }
+  // var ident : my_struct
+  // ident.b
+
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.f32()),
+                                   });
+
+  auto* var = Var("ident", ty.Of(s));
+
+  auto* expr = MemberAccessor("ident", "b");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeStruct %4 %4
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%8 = OpTypePointer Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpAccessChain %8 %1 %7
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Nested) {
+  // inner_struct {
+  //   a : f32
+  //   b : f32
+  // }
+  // my_struct {
+  //   inner : inner_struct
+  // }
+  //
+  // var ident : my_struct
+  // ident.inner.a
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
+
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+
+  auto* var = Var("ident", ty.Of(s_type));
+  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeStruct %5 %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%9 = OpConstant %7 1
+%10 = OpTypePointer Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%11 = OpAccessChain %10 %1 %8 %9
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_NonPointer) {
+  // my_struct {
+  //   a : f32
+  //   b : f32
+  // }
+  // let ident : my_struct = my_struct();
+  // ident.b
+
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.f32()),
+                                   });
+
+  auto* var = Const("ident", ty.Of(s), Construct(ty.Of(s), 0.f, 0.f));
+
+  auto* expr = MemberAccessor("ident", "b");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 5u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2 %2
+%3 = OpConstant %2 0
+%4 = OpConstantComposite %1 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%5 = OpCompositeExtract %2 %4 1
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Nested_NonPointer) {
+  // inner_struct {
+  //   a : f32
+  //   b : f32
+  // }
+  // my_struct {
+  //   inner : inner_struct
+  // }
+  //
+  // let ident : my_struct = my_struct();
+  // ident.inner.a
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
+
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+
+  auto* var =
+      Const("ident", ty.Of(s_type),
+            Construct(ty.Of(s_type), Construct(ty.Of(inner_struct), 0.f, 0.f)));
+  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeStruct %3 %3
+%1 = OpTypeStruct %2
+%4 = OpConstant %3 0
+%5 = OpConstantComposite %2 %4 %4
+%6 = OpConstantComposite %1 %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%7 = OpCompositeExtract %2 %6 0
+%8 = OpCompositeExtract %3 %7 1
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Nested_WithAlias) {
+  // struct Inner {
+  //   a : f32
+  //   b : f32
+  // };
+  // type Alias = Inner;
+  // my_struct {
+  //   inner : Inner
+  // }
+  //
+  // var ident : my_struct
+  // ident.inner.a
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
+
+  auto* alias = Alias("Alias", ty.Of(inner_struct));
+  auto* s_type = Structure("Outer", {Member("inner", ty.Of(alias))});
+
+  auto* var = Var("ident", ty.Of(s_type));
+  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 10u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeStruct %5 %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%9 = OpTypePointer Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%10 = OpAccessChain %9 %1 %8 %8
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Nested_Assignment_LHS) {
+  // inner_struct {
+  //   a : f32
+  // }
+  // my_struct {
+  //   inner : inner_struct
+  // }
+  //
+  // var ident : my_struct
+  // ident.inner.a = 2.0f;
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
+
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+
+  auto* var = Var("ident", ty.Of(s_type));
+  auto* expr =
+      Assign(MemberAccessor(MemberAccessor("ident", "inner"), "a"), Expr(2.0f));
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(expr)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeStruct %5 %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%9 = OpTypePointer Function %5
+%11 = OpConstant %5 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%10 = OpAccessChain %9 %1 %8 %8
+OpStore %10 %11
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Nested_Assignment_RHS) {
+  // inner_struct {
+  //   a : f32
+  // }
+  // my_struct {
+  //   inner : inner_struct
+  // }
+  //
+  // var ident : my_struct
+  // var store : f32 = ident.inner.a
+
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
+
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+
+  auto* var = Var("ident", ty.Of(s_type));
+  auto* store = Var("store", ty.f32());
+
+  auto* rhs = MemberAccessor(MemberAccessor("ident", "inner"), "a");
+  auto* expr = Assign("store", rhs);
+  WrapInFunction(var, store, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunctionVariable(store)) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(expr)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeStruct %5 %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%8 = OpTypePointer Function %5
+%9 = OpConstantNull %5
+%10 = OpTypeInt 32 0
+%11 = OpConstant %10 0
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %6
+%7 = OpVariable %8 Function %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%12 = OpAccessChain %8 %1 %11 %11
+%13 = OpLoad %5 %12
+OpStore %7 %13
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Swizzle_Single) {
+  // ident.y
+
+  auto* var = Var("ident", ty.vec3<f32>());
+
+  auto* expr = MemberAccessor("ident", "y");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%8 = OpTypePointer Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpAccessChain %8 %1 %7
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Swizzle_MultipleNames) {
+  // ident.yx
+
+  auto* var = Var("ident", ty.vec3<f32>());
+
+  auto* expr = MemberAccessor("ident", "yx");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%6 = OpTypeVector %4 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%7 = OpLoad %3 %1
+%8 = OpVectorShuffle %6 %7 %7 1 0
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Swizzle_of_Swizzle) {
+  // ident.yxz.xz
+
+  auto* var = Var("ident", ty.vec3<f32>());
+
+  auto* expr = MemberAccessor(MemberAccessor("ident", "yxz"), "xz");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%8 = OpTypeVector %4 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%7 = OpVectorShuffle %3 %6 %6 1 0 2
+%9 = OpVectorShuffle %8 %7 %7 0 2
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Member_of_Swizzle) {
+  // ident.yxz.x
+
+  auto* var = Var("ident", ty.vec3<f32>());
+
+  auto* expr = MemberAccessor(MemberAccessor("ident", "yxz"), "x");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%7 = OpVectorShuffle %3 %6 %6 1 0 2
+%8 = OpCompositeExtract %4 %7 0
+)");
+}
+
+TEST_F(BuilderTest, MemberAccessor_Array_of_Swizzle) {
+  // index.yxz[1]
+
+  auto* var = Var("ident", ty.vec3<f32>());
+
+  auto* expr = IndexAccessor(MemberAccessor("ident", "yxz"), 1);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 10u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+%8 = OpTypeInt 32 1
+%9 = OpConstant %8 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%7 = OpVectorShuffle %3 %6 %6 1 0 2
+%10 = OpCompositeExtract %4 %7 1
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_Mixed_ArrayAndMember) {
+  // type C = struct {
+  //   baz : vec3<f32>
+  // }
+  // type B = struct {
+  //  bar : C;
+  // }
+  // type A = struct {
+  //   foo : array<B, 3>
+  // }
+  // var index : array<A, 2>
+  // index[0].foo[2].bar.baz.yx
+
+  auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
+
+  auto* b_type = Structure("B", {Member("bar", ty.Of(c_type))});
+  auto* b_ary_type = ty.array(ty.Of(b_type), 3);
+  auto* a_type = Structure("A", {Member("foo", b_ary_type)});
+
+  auto* a_ary_type = ty.array(ty.Of(a_type), 2);
+  auto* var = Var("index", a_ary_type);
+  auto* expr = MemberAccessor(
+      MemberAccessor(
+          MemberAccessor(
+              IndexAccessor(MemberAccessor(IndexAccessor("index", 0), "foo"),
+                            2),
+              "bar"),
+          "baz"),
+      "yx");
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 22u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeFloat 32
+%8 = OpTypeVector %9 3
+%7 = OpTypeStruct %8
+%6 = OpTypeStruct %7
+%10 = OpTypeInt 32 0
+%11 = OpConstant %10 3
+%5 = OpTypeArray %6 %11
+%4 = OpTypeStruct %5
+%12 = OpConstant %10 2
+%3 = OpTypeArray %4 %12
+%2 = OpTypePointer Function %3
+%13 = OpConstantNull %3
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpConstant %10 0
+%17 = OpConstant %14 2
+%18 = OpTypePointer Function %8
+%20 = OpTypeVector %9 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %13
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%19 = OpAccessChain %18 %1 %15 %16 %17 %16 %16
+%21 = OpLoad %8 %19
+%22 = OpVectorShuffle %20 %21 %21 1 0
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_Of_Vec) {
+  // let pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+  //   vec2<f32>(0.0, 0.5),
+  //   vec2<f32>(-0.5, -0.5),
+  //   vec2<f32>(0.5, -0.5));
+  // pos[1]
+
+  auto* var =
+      Const("pos", ty.array(ty.vec2<f32>(), 3),
+            Construct(ty.array(ty.vec2<f32>(), 3), vec2<f32>(0.0f, 0.5f),
+                      vec2<f32>(-0.5f, -0.5f), vec2<f32>(0.5f, -0.5f)));
+
+  auto* expr = IndexAccessor("pos", 1u);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 3
+%5 = OpTypeArray %6 %9
+%10 = OpConstant %7 0
+%11 = OpConstant %7 0.5
+%12 = OpConstantComposite %6 %10 %11
+%13 = OpConstant %7 -0.5
+%14 = OpConstantComposite %6 %13 %13
+%15 = OpConstantComposite %6 %11 %13
+%16 = OpConstantComposite %5 %12 %14 %15
+%17 = OpConstant %8 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%18 = OpCompositeExtract %6 %16 1
+OpReturn
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, IndexAccessor_Of_Array_Of_f32) {
+  // let pos : array<array<f32, 2>, 3> = array<vec2<f32, 2>, 3>(
+  //   array<f32, 2>(0.0, 0.5),
+  //   array<f32, 2>(-0.5, -0.5),
+  //   array<f32, 2>(0.5, -0.5));
+  // pos[2][1]
+
+  auto* var =
+      Const("pos", ty.array(ty.vec2<f32>(), 3),
+            Construct(ty.array(ty.vec2<f32>(), 3), vec2<f32>(0.0f, 0.5f),
+                      vec2<f32>(-0.5f, -0.5f), vec2<f32>(0.5f, -0.5f)));
+
+  auto* expr = IndexAccessor(IndexAccessor("pos", 2u), 1u);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 3
+%5 = OpTypeArray %6 %9
+%10 = OpConstant %7 0
+%11 = OpConstant %7 0.5
+%12 = OpConstantComposite %6 %10 %11
+%13 = OpConstant %7 -0.5
+%14 = OpConstantComposite %6 %13 %13
+%15 = OpConstantComposite %6 %11 %13
+%16 = OpConstantComposite %5 %12 %14 %15
+%17 = OpConstant %8 2
+%19 = OpConstant %8 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%18 = OpCompositeExtract %6 %16 2
+%20 = OpCompositeExtract %7 %18 1
+OpReturn
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, IndexAccessor_Vec_Literal) {
+  // let pos : vec2<f32> = vec2<f32>(0.0, 0.5);
+  // pos[1]
+
+  auto* var = Const("pos", ty.vec2<f32>(), vec2<f32>(0.0f, 0.5f));
+
+  auto* expr = IndexAccessor("pos", 1u);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 0
+%4 = OpConstant %2 0.5
+%5 = OpConstantComposite %1 %3 %4
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), "");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%8 = OpCompositeExtract %2 %5 1
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_Vec_Dynamic) {
+  // let pos : vec2<f32> = vec2<f32>(0.0, 0.5);
+  // idx : i32
+  // pos[idx]
+
+  auto* var = Const("pos", ty.vec2<f32>(), vec2<f32>(0.0f, 0.5f));
+  auto* idx = Var("idx", ty.i32());
+  auto* expr = IndexAccessor("pos", idx);
+
+  WrapInFunction(var, idx, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunctionVariable(idx)) << b.error();
+  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 0
+%4 = OpConstant %2 0.5
+%5 = OpConstantComposite %1 %3 %4
+%8 = OpTypeInt 32 1
+%7 = OpTypePointer Function %8
+%9 = OpConstantNull %8
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%6 = OpVariable %7 Function %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%10 = OpLoad %8 %6
+%11 = OpVectorExtractDynamic %2 %5 %10
+)");
+}
+
+TEST_F(BuilderTest, IndexAccessor_Array_Literal) {
+  // let a : array<f32, 3>;
+  // a[2]
+
+  auto* var = Const("a", ty.array<f32, 3>(),
+                    Construct(ty.array<f32, 3>(), 0.0f, 0.5f, 1.0f));
+  auto* expr = IndexAccessor("a", 2);
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 3
+%5 = OpTypeArray %6 %8
+%9 = OpConstant %6 0
+%10 = OpConstant %6 0.5
+%11 = OpConstant %6 1
+%12 = OpConstantComposite %5 %9 %10 %11
+%13 = OpTypeInt 32 1
+%14 = OpConstant %13 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), "");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%15 = OpCompositeExtract %6 %12 2
+OpReturn
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, IndexAccessor_Array_Dynamic) {
+  // let a : array<f32, 3>;
+  // idx : i32
+  // a[idx]
+
+  auto* var = Const("a", ty.array<f32, 3>(),
+                    Construct(ty.array<f32, 3>(), 0.0f, 0.5f, 1.0f));
+
+  auto* idx = Var("idx", ty.i32());
+  auto* expr = IndexAccessor("a", idx);
+
+  WrapInFunction(var, idx, expr);
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 3
+%5 = OpTypeArray %6 %8
+%9 = OpConstant %6 0
+%10 = OpConstant %6 0.5
+%11 = OpConstant %6 1
+%12 = OpConstantComposite %5 %9 %10 %11
+%15 = OpTypeInt 32 1
+%14 = OpTypePointer Function %15
+%16 = OpConstantNull %15
+%18 = OpTypePointer Function %5
+%19 = OpConstantNull %5
+%21 = OpTypePointer Function %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%13 = OpVariable %14 Function %16
+%17 = OpVariable %18 Function %19
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %17 %12
+%20 = OpLoad %15 %13
+%22 = OpAccessChain %21 %17 %20
+%23 = OpLoad %6 %22
+OpReturn
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, IndexAccessor_Matrix_Dynamic) {
+  // let a : mat2x2<f32>(vec2<f32>(1., 2.), vec2<f32>(3., 4.));
+  // idx : i32
+  // a[idx]
+
+  auto* var =
+      Const("a", ty.mat2x2<f32>(),
+            Construct(ty.mat2x2<f32>(), Construct(ty.vec2<f32>(), 1.f, 2.f),
+                      Construct(ty.vec2<f32>(), 3.f, 4.f)));
+
+  auto* idx = Var("idx", ty.i32());
+  auto* expr = IndexAccessor("a", idx);
+
+  WrapInFunction(var, idx, expr);
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%5 = OpTypeMatrix %6 2
+%8 = OpConstant %7 1
+%9 = OpConstant %7 2
+%10 = OpConstantComposite %6 %8 %9
+%11 = OpConstant %7 3
+%12 = OpConstant %7 4
+%13 = OpConstantComposite %6 %11 %12
+%14 = OpConstantComposite %5 %10 %13
+%17 = OpTypeInt 32 1
+%16 = OpTypePointer Function %17
+%18 = OpConstantNull %17
+%20 = OpTypePointer Function %5
+%21 = OpConstantNull %5
+%23 = OpTypePointer Function %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%15 = OpVariable %16 Function %18
+%19 = OpVariable %20 Function %21
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %19 %14
+%22 = OpLoad %17 %15
+%24 = OpAccessChain %23 %19 %22
+%25 = OpLoad %6 %24
+OpReturn
+)");
+
+  Validate(b);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_assign_test.cc b/src/tint/writer/spirv/builder_assign_test.cc
new file mode 100644
index 0000000..2fd1db2
--- /dev/null
+++ b/src/tint/writer/spirv/builder_assign_test.cc
@@ -0,0 +1,320 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Assign_Var) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign("var", 1.f);
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpConstant %3 1
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %1 %5
+)");
+}
+
+TEST_F(BuilderTest, Assign_Var_OutsideFunction_IsError) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign("var", Expr(1.f));
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_FALSE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_TRUE(b.has_error());
+  EXPECT_EQ(b.error(),
+            "Internal error: trying to add SPIR-V instruction 62 outside a "
+            "function");
+}
+
+TEST_F(BuilderTest, Assign_Var_ZeroConstructor) {
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* val = vec3<f32>();
+  auto* assign = Assign("var", val);
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %1 %5
+)");
+}
+
+TEST_F(BuilderTest, Assign_Var_Complex_ConstructorWithExtract) {
+  auto* init = vec3<f32>(vec2<f32>(1.f, 2.f), 3.f);
+
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign("var", init);
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%6 = OpTypeVector %4 2
+%7 = OpConstant %4 1
+%8 = OpConstant %4 2
+%9 = OpConstantComposite %6 %7 %8
+%12 = OpConstant %4 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%10 = OpCompositeExtract %4 %9 0
+%11 = OpCompositeExtract %4 %9 1
+%13 = OpCompositeConstruct %3 %10 %11 %12
+OpStore %1 %13
+)");
+}
+
+TEST_F(BuilderTest, Assign_Var_Complex_Constructor) {
+  auto* init = vec3<f32>(1.f, 2.f, 3.f);
+
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign("var", init);
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%6 = OpConstant %4 1
+%7 = OpConstant %4 2
+%8 = OpConstant %4 3
+%9 = OpConstantComposite %3 %6 %7 %8
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %1 %9
+)");
+}
+
+TEST_F(BuilderTest, Assign_StructMember) {
+  // my_struct {
+  //   a : f32
+  //   b : f32
+  // }
+  // var ident : my_struct
+  // ident.b = 4.0;
+
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.f32()),
+                                   });
+
+  auto* v = Var("ident", ty.Of(s));
+
+  auto* assign = Assign(MemberAccessor("ident", "b"), Expr(4.f));
+
+  WrapInFunction(v, assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeStruct %4 %4
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+%5 = OpTypeInt 32 0
+%6 = OpConstant %5 1
+%7 = OpTypePointer Function %4
+%9 = OpConstant %4 4
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%8 = OpAccessChain %7 %1 %6
+OpStore %8 %9
+)");
+}
+
+TEST_F(BuilderTest, Assign_Vector) {
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* val = vec3<f32>(1.f, 1.f, 3.f);
+  auto* assign = Assign("var", val);
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%6 = OpConstant %4 1
+%7 = OpConstant %4 3
+%8 = OpConstantComposite %3 %6 %6 %7
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %1 %8
+)");
+}
+
+TEST_F(BuilderTest, Assign_Vector_MemberByName) {
+  // var.y = 1
+
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign(MemberAccessor("var", "y"), Expr(1.f));
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%8 = OpTypePointer Private %4
+%10 = OpConstant %4 1
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpAccessChain %8 %1 %7
+OpStore %9 %10
+)");
+}
+
+TEST_F(BuilderTest, Assign_Vector_MemberByIndex) {
+  // var[1] = 1
+
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* assign = Assign(IndexAccessor("var", 1), Expr(1.f));
+
+  WrapInFunction(assign);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%6 = OpTypeInt 32 1
+%7 = OpConstant %6 1
+%8 = OpTypePointer Private %4
+%10 = OpConstant %4 1
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpAccessChain %8 %1 %7
+OpStore %9 %10
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_binary_expression_test.cc b/src/tint/writer/spirv/builder_binary_expression_test.cc
new file mode 100644
index 0000000..22007a9
--- /dev/null
+++ b/src/tint/writer/spirv/builder_binary_expression_test.cc
@@ -0,0 +1,1249 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+struct BinaryData {
+  ast::BinaryOp op;
+  std::string name;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << data.op;
+  return out;
+}
+
+using BinaryArithSignedIntegerTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryArithSignedIntegerTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3);
+  auto* rhs = Expr(4);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstant %1 3
+%3 = OpConstant %1 4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %1 %2 %3\n");
+}
+
+TEST_P(BinaryArithSignedIntegerTest, Vector) {
+  auto param = GetParam();
+
+  // Skip ops that are illegal for this type
+  if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
+      param.op == ast::BinaryOp::kXor) {
+    return;
+  }
+
+  auto* lhs = vec3<i32>(1, 1, 1);
+  auto* rhs = vec3<i32>(1, 1, 1);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %1 %4 %4\n");
+}
+TEST_P(BinaryArithSignedIntegerTest, Scalar_Loads) {
+  auto param = GetParam();
+
+  auto* var = Var("param", ty.i32());
+  auto* expr =
+      create<ast::BinaryExpression>(param.op, Expr("param"), Expr("param"));
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%5 = OpLoad %3 %1
+%6 = OpLoad %3 %1
+%7 = )" + param.name +
+                R"( %3 %5 %6
+)");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryArithSignedIntegerTest,
+    // NOTE: No left and right shift as they require u32 for rhs operand
+    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
+                    BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
+                    BinaryData{ast::BinaryOp::kDivide, "OpSDiv"},
+                    BinaryData{ast::BinaryOp::kModulo, "OpSMod"},
+                    BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
+                    BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
+                    BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
+                    BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
+
+using BinaryArithUnsignedIntegerTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryArithUnsignedIntegerTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3u);
+  auto* rhs = Expr(4u);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstant %1 3
+%3 = OpConstant %1 4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %1 %2 %3\n");
+}
+TEST_P(BinaryArithUnsignedIntegerTest, Vector) {
+  auto param = GetParam();
+
+  // Skip ops that are illegal for this type
+  if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
+      param.op == ast::BinaryOp::kXor) {
+    return;
+  }
+
+  auto* lhs = vec3<u32>(1u, 1u, 1u);
+  auto* rhs = vec3<u32>(1u, 1u, 1u);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %1 %4 %4\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryArithUnsignedIntegerTest,
+    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
+                    BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
+                    BinaryData{ast::BinaryOp::kDivide, "OpUDiv"},
+                    BinaryData{ast::BinaryOp::kModulo, "OpUMod"},
+                    BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
+                    BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
+                    BinaryData{ast::BinaryOp::kShiftLeft, "OpShiftLeftLogical"},
+                    BinaryData{ast::BinaryOp::kShiftRight,
+                               "OpShiftRightLogical"},
+                    BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
+                    BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
+
+using BinaryArithFloatTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryArithFloatTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3.2f);
+  auto* rhs = Expr(4.5f);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 3.20000005
+%3 = OpConstant %1 4.5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %1 %2 %3\n");
+}
+
+TEST_P(BinaryArithFloatTest, Vector) {
+  auto param = GetParam();
+
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %1 %4 %4\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryArithFloatTest,
+    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpFAdd"},
+                    BinaryData{ast::BinaryOp::kDivide, "OpFDiv"},
+                    BinaryData{ast::BinaryOp::kModulo, "OpFRem"},
+                    BinaryData{ast::BinaryOp::kMultiply, "OpFMul"},
+                    BinaryData{ast::BinaryOp::kSubtract, "OpFSub"}));
+
+using BinaryOperatorBoolTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryOperatorBoolTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(true);
+  auto* rhs = Expr(false);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+%3 = OpConstantFalse %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %1 %2 %3\n");
+}
+
+TEST_P(BinaryOperatorBoolTest, Vector) {
+  auto param = GetParam();
+
+  auto* lhs = vec3<bool>(false, true, false);
+  auto* rhs = vec3<bool>(true, false, true);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 3
+%3 = OpConstantFalse %2
+%4 = OpConstantTrue %2
+%5 = OpConstantComposite %1 %3 %4 %3
+%6 = OpConstantComposite %1 %4 %3 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%7 = " + param.name + " %1 %5 %6\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryOperatorBoolTest,
+    testing::Values(BinaryData{ast::BinaryOp::kEqual, "OpLogicalEqual"},
+                    BinaryData{ast::BinaryOp::kNotEqual, "OpLogicalNotEqual"},
+                    BinaryData{ast::BinaryOp::kAnd, "OpLogicalAnd"},
+                    BinaryData{ast::BinaryOp::kOr, "OpLogicalOr"}));
+
+using BinaryCompareUnsignedIntegerTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryCompareUnsignedIntegerTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3u);
+  auto* rhs = Expr(4u);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstant %1 3
+%3 = OpConstant %1 4
+%5 = OpTypeBool
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %5 %2 %3\n");
+}
+
+TEST_P(BinaryCompareUnsignedIntegerTest, Vector) {
+  auto param = GetParam();
+
+  auto* lhs = vec3<u32>(1u, 1u, 1u);
+  auto* rhs = vec3<u32>(1u, 1u, 1u);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+%7 = OpTypeBool
+%6 = OpTypeVector %7 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %6 %4 %4\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryCompareUnsignedIntegerTest,
+    testing::Values(
+        BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
+        BinaryData{ast::BinaryOp::kGreaterThan, "OpUGreaterThan"},
+        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpUGreaterThanEqual"},
+        BinaryData{ast::BinaryOp::kLessThan, "OpULessThan"},
+        BinaryData{ast::BinaryOp::kLessThanEqual, "OpULessThanEqual"},
+        BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
+
+using BinaryCompareSignedIntegerTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryCompareSignedIntegerTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3);
+  auto* rhs = Expr(4);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstant %1 3
+%3 = OpConstant %1 4
+%5 = OpTypeBool
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %5 %2 %3\n");
+}
+
+TEST_P(BinaryCompareSignedIntegerTest, Vector) {
+  auto param = GetParam();
+
+  auto* lhs = vec3<i32>(1, 1, 1);
+  auto* rhs = vec3<i32>(1, 1, 1);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+%7 = OpTypeBool
+%6 = OpTypeVector %7 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %6 %4 %4\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryCompareSignedIntegerTest,
+    testing::Values(
+        BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
+        BinaryData{ast::BinaryOp::kGreaterThan, "OpSGreaterThan"},
+        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpSGreaterThanEqual"},
+        BinaryData{ast::BinaryOp::kLessThan, "OpSLessThan"},
+        BinaryData{ast::BinaryOp::kLessThanEqual, "OpSLessThanEqual"},
+        BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
+
+using BinaryCompareFloatTest = TestParamHelper<BinaryData>;
+TEST_P(BinaryCompareFloatTest, Scalar) {
+  auto param = GetParam();
+
+  auto* lhs = Expr(3.2f);
+  auto* rhs = Expr(4.5f);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 3.20000005
+%3 = OpConstant %1 4.5
+%5 = OpTypeBool
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%4 = " + param.name + " %5 %2 %3\n");
+}
+
+TEST_P(BinaryCompareFloatTest, Vector) {
+  auto param = GetParam();
+
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+%7 = OpTypeBool
+%6 = OpTypeVector %7 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = " + param.name + " %6 %4 %4\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryCompareFloatTest,
+    testing::Values(
+        BinaryData{ast::BinaryOp::kEqual, "OpFOrdEqual"},
+        BinaryData{ast::BinaryOp::kGreaterThan, "OpFOrdGreaterThan"},
+        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpFOrdGreaterThanEqual"},
+        BinaryData{ast::BinaryOp::kLessThan, "OpFOrdLessThan"},
+        BinaryData{ast::BinaryOp::kLessThanEqual, "OpFOrdLessThanEqual"},
+        BinaryData{ast::BinaryOp::kNotEqual, "OpFOrdNotEqual"}));
+
+TEST_F(BuilderTest, Binary_Multiply_VectorScalar) {
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+  auto* rhs = Expr(1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = OpVectorTimesScalar %1 %4 %3\n");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_ScalarVector) {
+  auto* lhs = Expr(1.f);
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 1
+%3 = OpTypeVector %1 3
+%4 = OpConstantComposite %3 %2 %2 %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%5 = OpVectorTimesScalar %3 %4 %2\n");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_MatrixScalar) {
+  auto* var = Var("mat", ty.mat3x3<f32>());
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                             Expr("mat"), Expr(1.f));
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+%7 = OpConstant %5 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%8 = OpMatrixTimesScalar %3 %6 %7
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix) {
+  auto* var = Var("mat", ty.mat3x3<f32>());
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                             Expr(1.f), Expr("mat"));
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+%6 = OpConstant %5 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%7 = OpLoad %3 %1
+%8 = OpMatrixTimesScalar %3 %7 %6
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_MatrixVector) {
+  auto* var = Var("mat", ty.mat3x3<f32>());
+  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+%7 = OpConstant %5 1
+%8 = OpConstantComposite %4 %7 %7 %7
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%9 = OpMatrixTimesVector %4 %6 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_VectorMatrix) {
+  auto* var = Var("mat", ty.mat3x3<f32>());
+  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, Expr("mat"));
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+%6 = OpConstant %5 1
+%7 = OpConstantComposite %4 %6 %6 %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%8 = OpLoad %3 %1
+%9 = OpVectorTimesMatrix %4 %7 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_MatrixMatrix) {
+  auto* var = Var("mat", ty.mat3x3<f32>());
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                             Expr("mat"), Expr("mat"));
+
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%1 = OpVariable %2 Function
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpLoad %3 %1
+%7 = OpLoad %3 %1
+%8 = OpMatrixTimesMatrix %3 %6 %7
+)");
+}
+
+TEST_F(BuilderTest, Binary_LogicalAnd) {
+  auto* lhs =
+      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
+
+  auto* rhs =
+      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 1
+%4 = OpConstant %2 2
+%6 = OpTypeBool
+%9 = OpConstant %2 3
+%10 = OpConstant %2 4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+%5 = OpIEqual %6 %3 %4
+OpSelectionMerge %7 None
+OpBranchConditional %5 %8 %7
+%8 = OpLabel
+%11 = OpIEqual %6 %9 %10
+OpBranch %7
+%7 = OpLabel
+%12 = OpPhi %6 %5 %1 %11 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_LogicalAnd_WithLoads) {
+  auto* a_var =
+      Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
+  auto* b_var =
+      Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                             Expr("a"), Expr("b"));
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantTrue %2
+%5 = OpTypePointer Private %2
+%4 = OpVariable %5 Private %3
+%6 = OpConstantFalse %2
+%7 = OpVariable %5 Private %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+%8 = OpLoad %2 %4
+OpSelectionMerge %9 None
+OpBranchConditional %8 %10 %9
+%10 = OpLabel
+%11 = OpLoad %2 %7
+OpBranch %9
+%9 = OpLabel
+%12 = OpPhi %2 %8 %1 %11 %10
+)");
+}
+
+TEST_F(BuilderTest, Binary_logicalOr_Nested_LogicalAnd) {
+  // Test an expression like
+  //    a || (b && c)
+  // From: crbug.com/tint/355
+
+  auto* logical_and_expr = create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                             Expr(true), logical_and_expr);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantTrue %2
+%8 = OpConstantFalse %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+OpSelectionMerge %4 None
+OpBranchConditional %3 %4 %5
+%5 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %3 %7 %6
+%7 = OpLabel
+OpBranch %6
+%6 = OpLabel
+%9 = OpPhi %2 %3 %5 %8 %7
+OpBranch %4
+%4 = OpLabel
+%10 = OpPhi %2 %3 %1 %9 %6
+)");
+}
+
+TEST_F(BuilderTest, Binary_logicalAnd_Nested_LogicalOr) {
+  // Test an expression like
+  //    a && (b || c)
+  // From: crbug.com/tint/355
+
+  auto* logical_or_expr = create<ast::BinaryExpression>(
+      ast::BinaryOp::kLogicalOr, Expr(true), Expr(false));
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
+                                             Expr(true), logical_or_expr);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantTrue %2
+%8 = OpConstantFalse %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+OpSelectionMerge %4 None
+OpBranchConditional %3 %5 %4
+%5 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %3 %6 %7
+%7 = OpLabel
+OpBranch %6
+%6 = OpLabel
+%9 = OpPhi %2 %3 %5 %8 %7
+OpBranch %4
+%4 = OpLabel
+%10 = OpPhi %2 %3 %1 %9 %6
+)");
+}
+
+TEST_F(BuilderTest, Binary_LogicalOr) {
+  auto* lhs =
+      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
+
+  auto* rhs =
+      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
+
+  auto* expr =
+      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 1
+%4 = OpConstant %2 2
+%6 = OpTypeBool
+%9 = OpConstant %2 3
+%10 = OpConstant %2 4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+%5 = OpIEqual %6 %3 %4
+OpSelectionMerge %7 None
+OpBranchConditional %5 %7 %8
+%8 = OpLabel
+%11 = OpIEqual %6 %9 %10
+OpBranch %7
+%7 = OpLabel
+%12 = OpPhi %6 %5 %1 %11 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_LogicalOr_WithLoads) {
+  auto* a_var =
+      Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
+  auto* b_var =
+      Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
+
+  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
+                                             Expr("a"), Expr("b"));
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  b.GenerateLabel(b.next_id());
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantTrue %2
+%5 = OpTypePointer Private %2
+%4 = OpVariable %5 Private %3
+%6 = OpConstantFalse %2
+%7 = OpVariable %5 Private %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLabel
+%8 = OpLoad %2 %4
+OpSelectionMerge %9 None
+OpBranchConditional %8 %9 %10
+%10 = OpLabel
+%11 = OpLoad %2 %7
+OpBranch %9
+%9 = OpLabel
+%12 = OpPhi %2 %8 %1 %11 %10
+)");
+}
+
+namespace BinaryArithVectorScalar {
+
+enum class Type { f32, i32, u32 };
+static const ast::Expression* MakeVectorExpr(ProgramBuilder* builder,
+                                             Type type) {
+  switch (type) {
+    case Type::f32:
+      return builder->vec3<ProgramBuilder::f32>(1.f, 1.f, 1.f);
+    case Type::i32:
+      return builder->vec3<ProgramBuilder::i32>(1, 1, 1);
+    case Type::u32:
+      return builder->vec3<ProgramBuilder::u32>(1u, 1u, 1u);
+  }
+  return nullptr;
+}
+static const ast::Expression* MakeScalarExpr(ProgramBuilder* builder,
+                                             Type type) {
+  switch (type) {
+    case Type::f32:
+      return builder->Expr(1.f);
+    case Type::i32:
+      return builder->Expr(1);
+    case Type::u32:
+      return builder->Expr(1u);
+  }
+  return nullptr;
+}
+static std::string OpTypeDecl(Type type) {
+  switch (type) {
+    case Type::f32:
+      return "OpTypeFloat 32";
+    case Type::i32:
+      return "OpTypeInt 32 1";
+    case Type::u32:
+      return "OpTypeInt 32 0";
+  }
+  return {};
+}
+
+struct Param {
+  Type type;
+  ast::BinaryOp op;
+  std::string name;
+};
+
+using BinaryArithVectorScalarTest = TestParamHelper<Param>;
+TEST_P(BinaryArithVectorScalarTest, VectorScalar) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = MakeVectorExpr(this, param.type);
+  const ast::Expression* rhs = MakeScalarExpr(this, param.type);
+  std::string op_type_decl = OpTypeDecl(param.type);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = )" + op_type_decl + R"(
+%5 = OpTypeVector %6 3
+%7 = OpConstant %6 1
+%8 = OpConstantComposite %5 %7 %7 %7
+%11 = OpTypePointer Function %5
+%12 = OpConstantNull %5
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%10 = OpVariable %11 Function %12
+%13 = OpCompositeConstruct %5 %7 %7 %7
+%9 = )" + param.name + R"( %5 %8 %13
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+TEST_P(BinaryArithVectorScalarTest, ScalarVector) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = MakeScalarExpr(this, param.type);
+  const ast::Expression* rhs = MakeVectorExpr(this, param.type);
+  std::string op_type_decl = OpTypeDecl(param.type);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%5 = )" + op_type_decl + R"(
+%6 = OpConstant %5 1
+%7 = OpTypeVector %5 3
+%8 = OpConstantComposite %7 %6 %6 %6
+%11 = OpTypePointer Function %7
+%12 = OpConstantNull %7
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%10 = OpVariable %11 Function %12
+%13 = OpCompositeConstruct %7 %6 %6 %6
+%9 = )" + param.name + R"( %7 %13 %8
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    BinaryArithVectorScalarTest,
+    testing::Values(Param{Type::f32, ast::BinaryOp::kAdd, "OpFAdd"},
+                    Param{Type::f32, ast::BinaryOp::kDivide, "OpFDiv"},
+                    // NOTE: Modulo not allowed on mixed float scalar-vector
+                    // Param{Type::f32, ast::BinaryOp::kModulo, "OpFMod"},
+                    // NOTE: We test f32 multiplies separately as we emit
+                    // OpVectorTimesScalar for this case
+                    // Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
+                    Param{Type::f32, ast::BinaryOp::kSubtract, "OpFSub"},
+
+                    Param{Type::i32, ast::BinaryOp::kAdd, "OpIAdd"},
+                    Param{Type::i32, ast::BinaryOp::kDivide, "OpSDiv"},
+                    Param{Type::i32, ast::BinaryOp::kModulo, "OpSMod"},
+                    Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
+                    Param{Type::i32, ast::BinaryOp::kSubtract, "OpISub"},
+
+                    Param{Type::u32, ast::BinaryOp::kAdd, "OpIAdd"},
+                    Param{Type::u32, ast::BinaryOp::kDivide, "OpUDiv"},
+                    Param{Type::u32, ast::BinaryOp::kModulo, "OpUMod"},
+                    Param{Type::u32, ast::BinaryOp::kMultiply, "OpIMul"},
+                    Param{Type::u32, ast::BinaryOp::kSubtract, "OpISub"}));
+
+using BinaryArithVectorScalarMultiplyTest = TestParamHelper<Param>;
+TEST_P(BinaryArithVectorScalarMultiplyTest, VectorScalar) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = MakeVectorExpr(this, param.type);
+  const ast::Expression* rhs = MakeScalarExpr(this, param.type);
+  std::string op_type_decl = OpTypeDecl(param.type);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = )" + op_type_decl + R"(
+%5 = OpTypeVector %6 3
+%7 = OpConstant %6 1
+%8 = OpConstantComposite %5 %7 %7 %7
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%9 = OpVectorTimesScalar %5 %8 %7
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+TEST_P(BinaryArithVectorScalarMultiplyTest, ScalarVector) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = MakeScalarExpr(this, param.type);
+  const ast::Expression* rhs = MakeVectorExpr(this, param.type);
+  std::string op_type_decl = OpTypeDecl(param.type);
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%5 = )" + op_type_decl + R"(
+%6 = OpConstant %5 1
+%7 = OpTypeVector %5 3
+%8 = OpConstantComposite %7 %6 %6 %6
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%9 = OpVectorTimesScalar %7 %8 %6
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(BuilderTest,
+                         BinaryArithVectorScalarMultiplyTest,
+                         testing::Values(Param{
+                             Type::f32, ast::BinaryOp::kMultiply, "OpFMul"}));
+
+}  // namespace BinaryArithVectorScalar
+
+namespace BinaryArithMatrixMatrix {
+
+struct Param {
+  ast::BinaryOp op;
+  std::string name;
+};
+
+using BinaryArithMatrixMatrix = TestParamHelper<Param>;
+TEST_P(BinaryArithMatrixMatrix, AddOrSubtract) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = mat3x4<f32>();
+  const ast::Expression* rhs = mat3x4<f32>();
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 4
+%5 = OpTypeMatrix %6 3
+%8 = OpConstantNull %5
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%10 = OpCompositeExtract %6 %8 0
+%11 = OpCompositeExtract %6 %8 0
+%12 = )" + param.name + R"( %6 %10 %11
+%13 = OpCompositeExtract %6 %8 1
+%14 = OpCompositeExtract %6 %8 1
+%15 = )" + param.name + R"( %6 %13 %14
+%16 = OpCompositeExtract %6 %8 2
+%17 = OpCompositeExtract %6 %8 2
+%18 = )" + param.name + R"( %6 %16 %17
+%19 = OpCompositeConstruct %5 %12 %15 %18
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(  //
+    BuilderTest,
+    BinaryArithMatrixMatrix,
+    testing::Values(Param{ast::BinaryOp::kAdd, "OpFAdd"},
+                    Param{ast::BinaryOp::kSubtract, "OpFSub"}));
+
+using BinaryArithMatrixMatrixMultiply = TestParamHelper<Param>;
+TEST_P(BinaryArithMatrixMatrixMultiply, Multiply) {
+  auto& param = GetParam();
+
+  const ast::Expression* lhs = mat3x4<f32>();
+  const ast::Expression* rhs = mat4x3<f32>();
+
+  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
+
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %3 "test_function"
+OpExecutionMode %3 LocalSize 1 1 1
+OpName %3 "test_function"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 4
+%5 = OpTypeMatrix %6 3
+%8 = OpConstantNull %5
+%10 = OpTypeVector %7 3
+%9 = OpTypeMatrix %10 4
+%11 = OpConstantNull %9
+%13 = OpTypeMatrix %6 4
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%12 = OpMatrixTimesMatrix %13 %8 %11
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(  //
+    BuilderTest,
+    BinaryArithMatrixMatrixMultiply,
+    testing::Values(Param{ast::BinaryOp::kMultiply, "OpFMul"}));
+
+}  // namespace BinaryArithMatrixMatrix
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_bitcast_expression_test.cc b/src/tint/writer/spirv/builder_bitcast_expression_test.cc
new file mode 100644
index 0000000..513712b
--- /dev/null
+++ b/src/tint/writer/spirv/builder_bitcast_expression_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Bitcast) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(2.4f));
+
+  WrapInFunction(bitcast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%3 = OpTypeFloat 32
+%4 = OpConstant %3 2.4000001
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpBitcast %2 %4
+)");
+}
+
+TEST_F(BuilderTest, Bitcast_DuplicateType) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(2.4f));
+
+  WrapInFunction(bitcast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpConstant %2 2.4000001
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpCopyObject %2 %3
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_block_test.cc b/src/tint/writer/spirv/builder_block_test.cc
new file mode 100644
index 0000000..0d2b381
--- /dev/null
+++ b/src/tint/writer/spirv/builder_block_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+// TODO(amaiorano): Disabled because Resolver now emits "redeclared identifier
+// 'var'".
+TEST_F(BuilderTest, DISABLED_Block) {
+  // Note, this test uses shadow variables which aren't allowed in WGSL but
+  // serves to prove the block code is pushing new scopes as needed.
+  auto* inner = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)),
+                      Assign("var", 2.f));
+  auto* outer = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)),
+                      Assign("var", 1.f), inner, Assign("var", 3.f));
+
+  WrapInFunction(outer);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_TRUE(b.GenerateStatement(outer)) << b.error();
+  EXPECT_FALSE(b.has_error());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+%5 = OpConstant %3 1
+%7 = OpConstant %3 2
+%8 = OpConstant %3 3
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %4
+%6 = OpVariable %2 Function %4
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %1 %5
+OpStore %6 %7
+OpStore %1 %8
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
new file mode 100644
index 0000000..b8dc6d0
--- /dev/null
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -0,0 +1,2560 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/utils/string.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuiltinBuilderTest = TestHelper;
+
+template <typename T>
+using BuiltinBuilderTestWithParam = TestParamHelper<T>;
+
+struct BuiltinData {
+  std::string name;
+  std::string op;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.name;
+  return out;
+}
+
+using BuiltinBoolTest = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(BuiltinBoolTest, Call_Bool_Scalar) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  // both any and all are 'passthrough' for scalar booleans
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            "%10 = OpLoad %3 %1\nOpReturn\n");
+}
+
+TEST_P(BuiltinBoolTest, Call_Bool_Vector) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeBool
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+
+  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
+%10 = ${op} %4 %11
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         BuiltinBoolTest,
+                         testing::Values(BuiltinData{"any", "OpAny"},
+                                         BuiltinData{"all", "OpAll"}));
+
+using BuiltinFloatTest = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(BuiltinFloatTest, Call_Float_Scalar) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%10 = OpTypeBool
+)");
+
+  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
+%9 = ${op} %10 %11
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+
+TEST_P(BuiltinFloatTest, Call_Float_Vector) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%12 = OpTypeBool
+%11 = OpTypeVector %12 3
+)");
+
+  auto expected = utils::ReplaceAll(R"(%13 = OpLoad %3 %1
+%10 = ${op} %11 %13
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         BuiltinFloatTest,
+                         testing::Values(BuiltinData{"isNan", "OpIsNan"},
+                                         BuiltinData{"isInf", "OpIsInf"}));
+
+TEST_F(BuiltinBuilderTest, IsFinite_Scalar) {
+  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call("isFinite", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%10 = OpTypeBool
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%11 = OpLoad %3 %1
+%12 = OpIsInf %10 %11
+%13 = OpIsNan %10 %11
+%14 = OpLogicalOr %10 %12 %13
+%9 = OpLogicalNot %10 %14
+OpReturn
+)");
+}
+
+TEST_F(BuiltinBuilderTest, IsFinite_Vector) {
+  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("isFinite", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%12 = OpTypeBool
+%11 = OpTypeVector %12 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%13 = OpLoad %3 %1
+%14 = OpIsInf %11 %13
+%15 = OpIsNan %11 %13
+%16 = OpLogicalOr %11 %14 %15
+%10 = OpLogicalNot %11 %16
+OpReturn
+)");
+}
+
+TEST_F(BuiltinBuilderTest, IsNormal_Scalar) {
+  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call("isNormal", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  auto got = DumpBuilder(b);
+  EXPECT_EQ(got, R"(%12 = OpExtInstImport "GLSL.std.450"
+OpName %1 "v"
+OpName %7 "a_func"
+%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%10 = OpTypeBool
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 133693440
+%15 = OpConstant %13 524288
+%16 = OpConstant %13 133169152
+%7 = OpFunction %6 None %5
+%8 = OpLabel
+%11 = OpLoad %3 %1
+%17 = OpBitcast %13 %11
+%18 = OpBitwiseAnd %13 %17 %14
+%19 = OpExtInst %13 %12 UClamp %18 %15 %16
+%9 = OpIEqual %10 %18 %19
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, IsNormal_Vector) {
+  auto* var = Global("v", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("isNormal", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  auto got = DumpBuilder(b);
+  EXPECT_EQ(got, R"(%14 = OpExtInstImport "GLSL.std.450"
+OpName %1 "v"
+OpName %8 "a_func"
+%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 2
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%12 = OpTypeBool
+%11 = OpTypeVector %12 2
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 133693440
+%17 = OpConstant %15 524288
+%18 = OpConstant %15 133169152
+%19 = OpTypeVector %15 2
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%13 = OpLoad %3 %1
+%20 = OpCompositeConstruct %19 %16 %16
+%21 = OpCompositeConstruct %19 %17 %17
+%22 = OpCompositeConstruct %19 %18 %18
+%23 = OpBitcast %19 %13
+%24 = OpBitwiseAnd %19 %23 %20
+%25 = OpExtInst %19 %14 UClamp %24 %21 %22
+%10 = OpIEqual %11 %24 %25
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using BuiltinIntTest = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(BuiltinIntTest, Call_SInt_Scalar) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
+%9 = ${op} %3 %10
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+
+TEST_P(BuiltinIntTest, Call_SInt_Vector) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+
+  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
+%10 = ${op} %3 %11
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+
+TEST_P(BuiltinIntTest, Call_UInt_Scalar) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.u32(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 0
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
+%9 = ${op} %3 %10
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+
+TEST_P(BuiltinIntTest, Call_UInt_Vector) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+
+  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
+%10 = ${op} %3 %11
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    BuiltinIntTest,
+    testing::Values(BuiltinData{"countOneBits", "OpBitCount"},
+                    BuiltinData{"reverseBits", "OpBitReverse"}));
+
+TEST_F(BuiltinBuilderTest, Call_Dot_F32) {
+  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("dot", "v", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%11 = OpLoad %3 %1
+%12 = OpLoad %3 %1
+%10 = OpDot %4 %11 %12
+OpReturn
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Dot_U32) {
+  auto* var = Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("dot", "v", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%11 = OpLoad %3 %1
+%12 = OpLoad %3 %1
+%13 = OpCompositeExtract %4 %11 0
+%14 = OpCompositeExtract %4 %12 0
+%15 = OpIMul %4 %13 %14
+%16 = OpCompositeExtract %4 %11 1
+%17 = OpCompositeExtract %4 %12 1
+%18 = OpIMul %4 %16 %17
+%19 = OpIAdd %4 %15 %18
+%20 = OpCompositeExtract %4 %11 2
+%21 = OpCompositeExtract %4 %12 2
+%22 = OpIMul %4 %20 %21
+%10 = OpIAdd %4 %19 %22
+OpReturn
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Dot_I32) {
+  auto* var = Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("dot", "v", "v");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%11 = OpLoad %3 %1
+%12 = OpLoad %3 %1
+%13 = OpCompositeExtract %4 %11 0
+%14 = OpCompositeExtract %4 %12 0
+%15 = OpIMul %4 %13 %14
+%16 = OpCompositeExtract %4 %11 1
+%17 = OpCompositeExtract %4 %12 1
+%18 = OpIMul %4 %16 %17
+%19 = OpIAdd %4 %15 %18
+%20 = OpCompositeExtract %4 %11 2
+%21 = OpCompositeExtract %4 %12 2
+%22 = OpIMul %4 %20 %21
+%10 = OpIAdd %4 %19 %22
+OpReturn
+)");
+}
+
+using BuiltinDeriveTest = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(BuiltinDeriveTest, Call_Derivative_Scalar) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("func", {}, ty.void_(), {CallStmt(expr)},
+                    {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
+%9 = ${op} %3 %10
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+
+TEST_P(BuiltinDeriveTest, Call_Derivative_Vector) {
+  auto param = GetParam();
+  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call(param.name, "v");
+  auto* func = Func("func", {}, ty.void_(), {CallStmt(expr)},
+                    {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  if (param.name != "dpdx" && param.name != "dpdy" && param.name != "fwidth") {
+    EXPECT_EQ(DumpInstructions(b.capabilities()),
+              R"(OpCapability DerivativeControl
+)");
+  }
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+
+  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
+%10 = ${op} %3 %11
+OpReturn
+)",
+                                    "${op}", param.op);
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    BuiltinDeriveTest,
+    testing::Values(BuiltinData{"dpdx", "OpDPdx"},
+                    BuiltinData{"dpdxFine", "OpDPdxFine"},
+                    BuiltinData{"dpdxCoarse", "OpDPdxCoarse"},
+                    BuiltinData{"dpdy", "OpDPdy"},
+                    BuiltinData{"dpdyFine", "OpDPdyFine"},
+                    BuiltinData{"dpdyCoarse", "OpDPdyCoarse"},
+                    BuiltinData{"fwidth", "OpFwidth"},
+                    BuiltinData{"fwidthFine", "OpFwidthFine"},
+                    BuiltinData{"fwidthCoarse", "OpFwidthCoarse"}));
+
+TEST_F(BuiltinBuilderTest, Call_Select) {
+  auto* v3 = Global("v3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* bool_v3 =
+      Global("bool_v3", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("select", "v3", "v3", "bool_v3");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v3)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(bool_v3)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%9 = OpTypeBool
+%8 = OpTypeVector %9 3
+%7 = OpTypePointer Private %8
+%10 = OpConstantNull %8
+%6 = OpVariable %7 Private %10
+%12 = OpTypeVoid
+%11 = OpTypeFunction %12
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%16 = OpLoad %8 %6
+%17 = OpLoad %3 %1
+%18 = OpLoad %3 %1
+%15 = OpSelect %3 %16 %17 %18
+OpReturn
+)");
+}
+
+// This tests that we do not push OpTypeSampledImage and float_0 type twice.
+TEST_F(BuiltinBuilderTest, Call_TextureSampleCompare_Twice) {
+  auto* s = ty.sampler(ast::SamplerKind::kComparisonSampler);
+  auto* t = ty.depth_texture(ast::TextureDimension::k2d);
+
+  auto* tex = Global("texture", t,
+                     ast::AttributeList{
+                         create<ast::BindingAttribute>(0),
+                         create<ast::GroupAttribute>(0),
+                     });
+
+  auto* sampler = Global("sampler", s,
+                         ast::AttributeList{
+                             create<ast::BindingAttribute>(1),
+                             create<ast::GroupAttribute>(0),
+                         });
+
+  auto* expr1 = Call("textureSampleCompare", "texture", "sampler",
+                     vec2<f32>(1.0f, 2.0f), 2.0f);
+  auto* expr2 = Call("textureSampleCompare", "texture", "sampler",
+                     vec2<f32>(1.0f, 2.0f), 2.0f);
+
+  Func("f1", {}, ty.void_(), {CallStmt(expr1)}, {});
+  Func("f2", {}, ty.void_(), {CallStmt(expr2)}, {});
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(tex)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+
+  EXPECT_EQ(b.GenerateExpression(expr1), 8u) << b.error();
+  EXPECT_EQ(b.GenerateExpression(expr2), 17u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 2
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstantComposite %13 %14 %15
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %15
+%18 = OpLoad %7 %5
+%19 = OpLoad %3 %1
+%20 = OpSampledImage %11 %19 %18
+%17 = OpImageSampleDrefImplicitLod %4 %20 %16 %15
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_GLSLMethod_WithLoad) {
+  auto* var = Global("ident", ty.f32(), ast::StorageClass::kPrivate);
+  auto* expr = Call("round", "ident");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%10 = OpExtInstImport "GLSL.std.450"
+OpName %1 "ident"
+OpName %7 "a_func"
+%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%7 = OpFunction %6 None %5
+%8 = OpLabel
+%11 = OpLoad %3 %1
+%9 = OpExtInst %3 %10 RoundEven %11
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using Builtin_Builtin_SingleParam_Float_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_SingleParam_Float_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1.0f);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_SingleParam_Float_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_SingleParam_Float_Test,
+                         testing::Values(BuiltinData{"abs", "FAbs"},
+                                         BuiltinData{"acos", "Acos"},
+                                         BuiltinData{"asin", "Asin"},
+                                         BuiltinData{"atan", "Atan"},
+                                         BuiltinData{"ceil", "Ceil"},
+                                         BuiltinData{"cos", "Cos"},
+                                         BuiltinData{"cosh", "Cosh"},
+                                         BuiltinData{"degrees", "Degrees"},
+                                         BuiltinData{"exp", "Exp"},
+                                         BuiltinData{"exp2", "Exp2"},
+                                         BuiltinData{"floor", "Floor"},
+                                         BuiltinData{"fract", "Fract"},
+                                         BuiltinData{"inverseSqrt",
+                                                     "InverseSqrt"},
+                                         BuiltinData{"log", "Log"},
+                                         BuiltinData{"log2", "Log2"},
+                                         BuiltinData{"radians", "Radians"},
+                                         BuiltinData{"round", "RoundEven"},
+                                         BuiltinData{"sign", "FSign"},
+                                         BuiltinData{"sin", "Sin"},
+                                         BuiltinData{"sinh", "Sinh"},
+                                         BuiltinData{"sqrt", "Sqrt"},
+                                         BuiltinData{"tan", "Tan"},
+                                         BuiltinData{"tanh", "Tanh"},
+                                         BuiltinData{"trunc", "Trunc"}));
+
+TEST_F(BuiltinBuilderTest, Call_Length_Scalar) {
+  auto* expr = Call("length", 1.0f);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 Length %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Length_Vector) {
+  auto* expr = Call("length", vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpTypeVector %6 2
+%9 = OpConstant %6 1
+%10 = OpConstantComposite %8 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 Length %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Normalize) {
+  auto* expr = Call("normalize", vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 Normalize %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using Builtin_Builtin_DualParam_Float_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_DualParam_Float_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1.0f, 1.0f);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_DualParam_Float_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_DualParam_Float_Test,
+                         testing::Values(BuiltinData{"atan2", "Atan2"},
+                                         BuiltinData{"max", "NMax"},
+                                         BuiltinData{"min", "NMin"},
+                                         BuiltinData{"pow", "Pow"},
+                                         BuiltinData{"step", "Step"}));
+
+TEST_F(BuiltinBuilderTest, Call_Reflect_Vector) {
+  auto* expr = Call("reflect", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 Reflect %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Distance_Scalar) {
+  auto* expr = Call("distance", 1.0f, 1.0f);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 Distance %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Distance_Vector) {
+  auto* expr = Call("distance", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpTypeVector %6 2
+%9 = OpConstant %6 1
+%10 = OpConstantComposite %8 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 Distance %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Cross) {
+  auto* expr =
+      Call("cross", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 3
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 Cross %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using Builtin_Builtin_ThreeParam_Float_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1.0f, 1.0f, 1.0f);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
+                    vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_ThreeParam_Float_Test,
+                         testing::Values(BuiltinData{"clamp", "NClamp"},
+                                         BuiltinData{"fma", "Fma"},
+                                         BuiltinData{"mix", "FMix"},
+
+                                         BuiltinData{"smoothStep",
+                                                     "SmoothStep"}));
+
+TEST_F(BuiltinBuilderTest, Call_FaceForward_Vector) {
+  auto* expr = Call("faceForward", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
+                    vec2<f32>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 FaceForward %10 %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using Builtin_Builtin_SingleParam_Sint_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_SingleParam_Sint_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_SingleParam_Sint_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<i32>(1, 1));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 1
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_SingleParam_Sint_Test,
+                         testing::Values(BuiltinData{"abs", "SAbs"}));
+
+// Calling abs() on an unsigned integer scalar / vector is a no-op.
+using Builtin_Builtin_Abs_Uint_Test = BuiltinBuilderTest;
+TEST_F(Builtin_Builtin_Abs_Uint_Test, Call_Scalar) {
+  auto* expr = Call("abs", 1u);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(Builtin_Builtin_Abs_Uint_Test, Call_Vector) {
+  auto* expr = Call("abs", vec2<u32>(1u, 1u));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 0
+%6 = OpTypeVector %7 2
+%8 = OpConstant %7 1
+%9 = OpConstantComposite %6 %8 %8
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+using Builtin_Builtin_DualParam_SInt_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_DualParam_SInt_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1, 1);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_DualParam_SInt_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<i32>(1, 1), vec2<i32>(1, 1));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 1
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_DualParam_SInt_Test,
+                         testing::Values(BuiltinData{"max", "SMax"},
+                                         BuiltinData{"min", "SMin"}));
+
+using Builtin_Builtin_DualParam_UInt_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_DualParam_UInt_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1u, 1u);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_DualParam_UInt_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, vec2<u32>(1u, 1u), vec2<u32>(1u, 1u));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 0
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_DualParam_UInt_Test,
+                         testing::Values(BuiltinData{"max", "UMax"},
+                                         BuiltinData{"min", "UMin"}));
+
+using Builtin_Builtin_ThreeParam_Sint_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1, 1, 1);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr =
+      Call(param.name, vec2<i32>(1, 1), vec2<i32>(1, 1), vec2<i32>(1, 1));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 1
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_ThreeParam_Sint_Test,
+                         testing::Values(BuiltinData{"clamp", "SClamp"}));
+
+using Builtin_Builtin_ThreeParam_Uint_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Scalar) {
+  auto param = GetParam();
+  auto* expr = Call(param.name, 1u, 1u, 1u);
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%8 = OpConstant %6 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                R"( %8 %8 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Vector) {
+  auto param = GetParam();
+  auto* expr =
+      Call(param.name, vec2<u32>(1u, 1u), vec2<u32>(1u, 1u), vec2<u32>(1u, 1u));
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeInt 32 0
+%6 = OpTypeVector %7 2
+%9 = OpConstant %7 1
+%10 = OpConstantComposite %6 %9 %9
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                R"( %10 %10 %10
+OpReturn
+OpFunctionEnd
+)");
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
+                         Builtin_Builtin_ThreeParam_Uint_Test,
+                         testing::Values(BuiltinData{"clamp", "UClamp"}));
+
+TEST_F(BuiltinBuilderTest, Call_Modf) {
+  auto* expr = Call("modf", vec2<f32>(1.0f, 2.0f));
+  Func("a_func", {}, ty.void_(), {CallStmt(expr)},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+  auto got = DumpBuilder(b);
+  auto* expect = R"(OpCapability Shader
+%9 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
+OpName %3 "a_func"
+OpName %6 "__modf_result_vec2"
+OpMemberName %6 0 "fract"
+OpMemberName %6 1 "whole"
+OpMemberDecorate %6 0 Offset 0
+OpMemberDecorate %6 1 Offset 8
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeFloat 32
+%7 = OpTypeVector %8 2
+%6 = OpTypeStruct %7 %7
+%10 = OpConstant %8 1
+%11 = OpConstant %8 2
+%12 = OpConstantComposite %7 %10 %11
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %9 ModfStruct %12
+OpReturn
+OpFunctionEnd
+)";
+  EXPECT_EQ(expect, got);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_Frexp) {
+  auto* expr = Call("frexp", vec2<f32>(1.0f, 2.0f));
+  Func("a_func", {}, ty.void_(), {CallStmt(expr)},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+  auto got = DumpBuilder(b);
+  auto* expect = R"(OpCapability Shader
+%11 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
+OpName %3 "a_func"
+OpName %6 "__frexp_result_vec2"
+OpMemberName %6 0 "sig"
+OpMemberName %6 1 "exp"
+OpMemberDecorate %6 0 Offset 0
+OpMemberDecorate %6 1 Offset 8
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeFloat 32
+%7 = OpTypeVector %8 2
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%6 = OpTypeStruct %7 %9
+%12 = OpConstant %8 1
+%13 = OpConstant %8 2
+%14 = OpConstantComposite %7 %12 %13
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %11 FrexpStruct %14
+OpReturn
+OpFunctionEnd
+)";
+  EXPECT_EQ(expect, got);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_Determinant) {
+  auto* var = Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("determinant", "var");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(%12 = OpExtInstImport "GLSL.std.450"
+OpName %1 "var"
+OpName %9 "a_func"
+%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Private %3
+%6 = OpConstantNull %3
+%1 = OpVariable %2 Private %6
+%8 = OpTypeVoid
+%7 = OpTypeFunction %8
+%9 = OpFunction %8 None %7
+%10 = OpLabel
+%13 = OpLoad %3 %1
+%11 = OpExtInst %5 %12 Determinant %13
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_Transpose) {
+  auto* var = Global("var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  auto* expr = Call("transpose", "var");
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Assign(Phony(), expr),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "var"
+OpName %9 "a_func"
+%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 2
+%2 = OpTypePointer Private %3
+%6 = OpConstantNull %3
+%1 = OpVariable %2 Private %6
+%8 = OpTypeVoid
+%7 = OpTypeFunction %8
+%13 = OpTypeVector %5 2
+%12 = OpTypeMatrix %13 3
+%9 = OpFunction %8 None %7
+%10 = OpLabel
+%14 = OpLoad %3 %1
+%11 = OpTranspose %12 %14
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuiltinBuilderTest, Call_ArrayLength) {
+  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+  auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           CallStmt(expr),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%5 = OpTypeFloat 32
+%4 = OpTypeRuntimeArray %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpTypeInt 32 0
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
+  auto* s = Structure("my_struct",
+                      {
+                          Member("z", ty.f32()),
+                          Member(4, "a", ty.array<f32>(4)),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+  auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           CallStmt(expr),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%4 = OpTypeFloat 32
+%5 = OpTypeRuntimeArray %4
+%3 = OpTypeStruct %4 %5
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpTypeInt 32 0
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 1
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_ArrayLength_ViaLets) {
+  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  auto* p = Const("p", nullptr, AddressOf("b"));
+  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+  auto* expr = Call("arrayLength", p2);
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(p),
+           Decl(p2),
+           CallStmt(expr),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%5 = OpTypeFloat 32
+%4 = OpTypeRuntimeArray %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpTypeInt 32 0
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_ArrayLength_ViaLets_WithPtrNoise) {
+  // [[block]] struct my_struct {
+  //   a : @stride(4) array<f32>;
+  // };
+  // @binding(1) @group(2) var<storage, read> b : my_struct;
+  //
+  // fn a_func() {
+  //   let p = &*&b;
+  //   let p2 = &*p;
+  //   let p3 = &((*p).a);
+  //   arrayLength(&*p3);
+  // }
+  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  auto* p = Const("p", nullptr, AddressOf(Deref(AddressOf("b"))));
+  auto* p2 = Const("p2", nullptr, AddressOf(Deref(p)));
+  auto* p3 = Const("p3", nullptr, AddressOf(MemberAccessor(Deref(p2), "a")));
+  auto* expr = Call("arrayLength", AddressOf(Deref(p3)));
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(p),
+           Decl(p2),
+           Decl(p3),
+           CallStmt(expr),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%5 = OpTypeFloat 32
+%4 = OpTypeRuntimeArray %5
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpTypeInt 32 0
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_AtomicLoad) {
+  // [[block]] struct S {
+  //   u : atomic<u32>;
+  //   i : atomic<i32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   let u : u32 = atomicLoad(&b.u);
+  //   let i : i32 = atomicLoad(&b.i);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("u", ty.atomic<u32>()),
+                          Member("i", ty.atomic<i32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Const("u", ty.u32(),
+                      Call("atomicLoad", AddressOf(MemberAccessor("b", "u"))))),
+           Decl(Const("i", ty.i32(),
+                      Call("atomicLoad", AddressOf(MemberAccessor("b", "i"))))),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%4 = OpTypeInt 32 0
+%5 = OpTypeInt 32 1
+%3 = OpTypeStruct %4 %5
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpConstant %4 1
+%12 = OpConstant %4 0
+%14 = OpTypePointer StorageBuffer %4
+%18 = OpTypePointer StorageBuffer %5
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%15 = OpAccessChain %14 %1 %12
+%10 = OpAtomicLoad %4 %15 %11 %12
+%19 = OpAccessChain %18 %1 %11
+%16 = OpAtomicLoad %5 %19 %11 %12
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_AtomicStore) {
+  // [[block]] struct S {
+  //   u : atomic<u32>;
+  //   i : atomic<i32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   var u = 1u;
+  //   var i = 2;
+  //   atomicStore(&b.u, u);
+  //   atomicStore(&b.i, i);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("u", ty.atomic<u32>()),
+                          Member("i", ty.atomic<i32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("u", nullptr, Expr(1u))),
+           Decl(Var("i", nullptr, Expr(2))),
+           CallStmt(
+               Call("atomicStore", AddressOf(MemberAccessor("b", "u")), "u")),
+           CallStmt(
+               Call("atomicStore", AddressOf(MemberAccessor("b", "i")), "i")),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%4 = OpTypeInt 32 0
+%5 = OpTypeInt 32 1
+%3 = OpTypeStruct %4 %5
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%10 = OpConstant %4 1
+%12 = OpTypePointer Function %4
+%13 = OpConstantNull %4
+%14 = OpConstant %5 2
+%16 = OpTypePointer Function %5
+%17 = OpConstantNull %5
+%19 = OpConstant %4 0
+%21 = OpTypePointer StorageBuffer %4
+%26 = OpTypePointer StorageBuffer %5
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(OpStore %11 %10
+OpStore %15 %14
+%22 = OpAccessChain %21 %1 %19
+%23 = OpLoad %4 %11
+OpAtomicStore %22 %10 %19 %23
+%27 = OpAccessChain %26 %1 %10
+%28 = OpLoad %5 %15
+OpAtomicStore %27 %10 %19 %28
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+using Builtin_Builtin_AtomicRMW_i32 = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_AtomicRMW_i32, Test) {
+  // [[block]] struct S {
+  //   v : atomic<i32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   var v = 10;
+  //   let x : i32 = atomicOP(&b.v, v);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("v", ty.atomic<i32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("v", nullptr, Expr(10))),
+           Decl(Const("x", ty.i32(),
+                      Call(GetParam().name, AddressOf(MemberAccessor("b", "v")),
+                           "v"))),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  std::string expected_types = R"(%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%9 = OpConstant %4 10
+%11 = OpTypePointer Function %4
+%12 = OpConstantNull %4
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 1
+%16 = OpConstant %14 0
+%18 = OpTypePointer StorageBuffer %4
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  std::string expected_instructions = R"(OpStore %10 %9
+%19 = OpAccessChain %18 %1 %16
+%20 = OpLoad %4 %10
+)";
+  expected_instructions += "%13 = " + GetParam().op + " %4 %19 %15 %16 %20\n";
+  expected_instructions += "OpReturn\n";
+
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    Builtin_Builtin_AtomicRMW_i32,
+    testing::Values(BuiltinData{"atomicAdd", "OpAtomicIAdd"},
+                    BuiltinData{"atomicMax", "OpAtomicSMax"},
+                    BuiltinData{"atomicMin", "OpAtomicSMin"},
+                    BuiltinData{"atomicAnd", "OpAtomicAnd"},
+                    BuiltinData{"atomicOr", "OpAtomicOr"},
+                    BuiltinData{"atomicXor", "OpAtomicXor"}));
+
+using Builtin_Builtin_AtomicRMW_u32 = BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_AtomicRMW_u32, Test) {
+  // [[block]] struct S {
+  //   v : atomic<u32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   var v = 10u;
+  //   let x : u32 = atomicOP(&b.v, v);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("v", ty.atomic<u32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("v", nullptr, Expr(10u))),
+           Decl(Const("x", ty.u32(),
+                      Call(GetParam().name, AddressOf(MemberAccessor("b", "v")),
+                           "v"))),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  std::string expected_types = R"(%4 = OpTypeInt 32 0
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%9 = OpConstant %4 10
+%11 = OpTypePointer Function %4
+%12 = OpConstantNull %4
+%14 = OpConstant %4 1
+%15 = OpConstant %4 0
+%17 = OpTypePointer StorageBuffer %4
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  std::string expected_instructions = R"(OpStore %10 %9
+%18 = OpAccessChain %17 %1 %15
+%19 = OpLoad %4 %10
+)";
+  expected_instructions += "%13 = " + GetParam().op + " %4 %18 %14 %15 %19\n";
+  expected_instructions += "OpReturn\n";
+
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    Builtin_Builtin_AtomicRMW_u32,
+    testing::Values(BuiltinData{"atomicAdd", "OpAtomicIAdd"},
+                    BuiltinData{"atomicMax", "OpAtomicUMax"},
+                    BuiltinData{"atomicMin", "OpAtomicUMin"},
+                    BuiltinData{"atomicAnd", "OpAtomicAnd"},
+                    BuiltinData{"atomicOr", "OpAtomicOr"},
+                    BuiltinData{"atomicXor", "OpAtomicXor"}));
+
+TEST_F(BuiltinBuilderTest, Call_AtomicExchange) {
+  // [[block]] struct S {
+  //   u : atomic<u32>;
+  //   i : atomic<i32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   var u = 10u;
+  //   var i = 10;
+  //   let r : u32 = atomicExchange(&b.u, u);
+  //   let s : i32 = atomicExchange(&b.i, i);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("u", ty.atomic<u32>()),
+                          Member("i", ty.atomic<i32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("u", nullptr, Expr(10u))),
+           Decl(Var("i", nullptr, Expr(10))),
+           Decl(Const("r", ty.u32(),
+                      Call("atomicExchange",
+                           AddressOf(MemberAccessor("b", "u")), "u"))),
+           Decl(Const("s", ty.i32(),
+                      Call("atomicExchange",
+                           AddressOf(MemberAccessor("b", "i")), "i"))),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%4 = OpTypeInt 32 0
+%5 = OpTypeInt 32 1
+%3 = OpTypeStruct %4 %5
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%10 = OpConstant %4 10
+%12 = OpTypePointer Function %4
+%13 = OpConstantNull %4
+%14 = OpConstant %5 10
+%16 = OpTypePointer Function %5
+%17 = OpConstantNull %5
+%19 = OpConstant %4 1
+%20 = OpConstant %4 0
+%22 = OpTypePointer StorageBuffer %4
+%27 = OpTypePointer StorageBuffer %5
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(OpStore %11 %10
+OpStore %15 %14
+%23 = OpAccessChain %22 %1 %20
+%24 = OpLoad %4 %11
+%18 = OpAtomicExchange %4 %23 %19 %20 %24
+%28 = OpAccessChain %27 %1 %19
+%29 = OpLoad %5 %15
+%25 = OpAtomicExchange %5 %28 %19 %20 %29
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_AtomicCompareExchangeWeak) {
+  // [[block]] struct S {
+  //   u : atomic<u32>;
+  //   i : atomic<i32>;
+  // }
+  //
+  // @binding(1) @group(2) var<storage, read_write> b : S;
+  //
+  // fn a_func() {
+  //   let u : vec2<u32> = atomicCompareExchangeWeak(&b.u, 10u);
+  //   let i : vec2<i32> = atomicCompareExchangeWeak(&b.i, 10);
+  // }
+  auto* s = Structure("S",
+                      {
+                          Member("u", ty.atomic<u32>()),
+                          Member("i", ty.atomic<i32>()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Decl(Const("u", ty.vec2<u32>(),
+                      Call("atomicCompareExchangeWeak",
+                           AddressOf(MemberAccessor("b", "u")), 10u, 20u))),
+           Decl(Const("i", ty.vec2<i32>(),
+                      Call("atomicCompareExchangeWeak",
+                           AddressOf(MemberAccessor("b", "i")), 10, 20))),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%4 = OpTypeInt 32 0
+%5 = OpTypeInt 32 1
+%3 = OpTypeStruct %4 %5
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%11 = OpTypeVector %4 2
+%12 = OpConstant %4 1
+%13 = OpConstant %4 0
+%15 = OpTypePointer StorageBuffer %4
+%17 = OpConstant %4 20
+%18 = OpConstant %4 10
+%19 = OpTypeBool
+%24 = OpTypeVector %5 2
+%26 = OpTypePointer StorageBuffer %5
+%28 = OpConstant %5 20
+%29 = OpConstant %5 10
+%32 = OpConstant %5 0
+%33 = OpConstant %5 1
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(%16 = OpAccessChain %15 %1 %13
+%20 = OpAtomicCompareExchange %4 %16 %12 %13 %13 %17 %18
+%21 = OpIEqual %19 %20 %17
+%22 = OpSelect %4 %21 %12 %13
+%10 = OpCompositeConstruct %11 %20 %22
+%27 = OpAccessChain %26 %1 %12
+%30 = OpAtomicCompareExchange %5 %27 %12 %13 %13 %28 %29
+%31 = OpIEqual %19 %30 %28
+%34 = OpSelect %5 %31 %33 %32
+%23 = OpCompositeConstruct %24 %30 %34
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+using Builtin_Builtin_DataPacking_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_DataPacking_Test, Binary) {
+  auto param = GetParam();
+
+  bool pack4 = param.name == "pack4x8snorm" || param.name == "pack4x8unorm";
+  auto* call = pack4 ? Call(param.name, vec4<float>(1.0f, 1.0f, 1.0f, 1.0f))
+                     : Call(param.name, vec2<float>(1.0f, 1.0f));
+  auto* func = Func("a_func", {}, ty.void_(), {CallStmt(call)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  if (pack4) {
+    EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%9 = OpTypeFloat 32
+%8 = OpTypeVector %9 4
+%10 = OpConstant %9 1
+%11 = OpConstantComposite %8 %10 %10 %10 %10
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                  R"( %11
+OpReturn
+OpFunctionEnd
+)");
+  } else {
+    EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%9 = OpTypeFloat 32
+%8 = OpTypeVector %9 2
+%10 = OpConstant %9 1
+%11 = OpConstantComposite %8 %10 %10
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %7 )" + param.op +
+                                  R"( %11
+OpReturn
+OpFunctionEnd
+)");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    Builtin_Builtin_DataPacking_Test,
+    testing::Values(BuiltinData{"pack4x8snorm", "PackSnorm4x8"},
+                    BuiltinData{"pack4x8unorm", "PackUnorm4x8"},
+                    BuiltinData{"pack2x16snorm", "PackSnorm2x16"},
+                    BuiltinData{"pack2x16unorm", "PackUnorm2x16"},
+                    BuiltinData{"pack2x16float", "PackHalf2x16"}));
+
+using Builtin_Builtin_DataUnpacking_Test =
+    BuiltinBuilderTestWithParam<BuiltinData>;
+TEST_P(Builtin_Builtin_DataUnpacking_Test, Binary) {
+  auto param = GetParam();
+
+  bool pack4 = param.name == "unpack4x8snorm" || param.name == "unpack4x8unorm";
+  auto* func = Func("a_func", {}, ty.void_(), {CallStmt(Call(param.name, 1u))});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  if (pack4) {
+    EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 4
+%9 = OpTypeInt 32 0
+%10 = OpConstant %9 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                  R"( %10
+OpReturn
+OpFunctionEnd
+)");
+  } else {
+    EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
+OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%7 = OpTypeFloat 32
+%6 = OpTypeVector %7 2
+%9 = OpTypeInt 32 0
+%10 = OpConstant %9 1
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%5 = OpExtInst %6 %8 )" + param.op +
+                                  R"( %10
+OpReturn
+OpFunctionEnd
+)");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinBuilderTest,
+    Builtin_Builtin_DataUnpacking_Test,
+    testing::Values(BuiltinData{"unpack4x8snorm", "UnpackSnorm4x8"},
+                    BuiltinData{"unpack4x8unorm", "UnpackUnorm4x8"},
+                    BuiltinData{"unpack2x16snorm", "UnpackSnorm2x16"},
+                    BuiltinData{"unpack2x16unorm", "UnpackUnorm2x16"},
+                    BuiltinData{"unpack2x16float", "UnpackHalf2x16"}));
+
+TEST_F(BuiltinBuilderTest, Call_WorkgroupBarrier) {
+  Func("f", {}, ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("workgroupBarrier")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 2
+%8 = OpConstant %6 264
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_StorageBarrier) {
+  Func("f", {}, ty.void_(),
+       ast::StatementList{
+           CallStmt(Call("storageBarrier")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  ASSERT_EQ(b.functions().size(), 1u);
+
+  auto* expected_types = R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 2
+%8 = OpConstant %6 72
+)";
+  auto got_types = DumpInstructions(b.types());
+  EXPECT_EQ(expected_types, got_types);
+
+  auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
+OpReturn
+)";
+  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+  EXPECT_EQ(expected_instructions, got_instructions);
+
+  Validate(b);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_builtin_texture_test.cc b/src/tint/writer/spirv/builder_builtin_texture_test.cc
new file mode 100644
index 0000000..67df5ad
--- /dev/null
+++ b/src/tint/writer/spirv/builder_builtin_texture_test.cc
@@ -0,0 +1,3755 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/ast/builtin_texture_helper_test.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+struct expected_texture_overload_spirv {
+  std::string types;
+  std::string instructions;
+  std::string capabilities;
+};
+
+expected_texture_overload_spirv expected_texture_overload(
+    ast::builtin::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpConstant %9 0
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %10 %11
+)",
+          R"(
+OpCapability Sampled1D
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions2d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 0
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions2dLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 1
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions2dArray:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 0
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 1
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions3d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 3
+%12 = OpConstant %10 0
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensions3dLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 3
+%12 = OpConstant %10 1
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsCube:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 0
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsCubeLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 1
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsCubeArray:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 0
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 1
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySize %9 %11
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepth2d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 0
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 1
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 0
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 1
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepthCube:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 0
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpConstant %10 1
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySizeLod %9 %11 %12
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 0
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+%14 = OpConstant %10 1
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySizeLod %12 %13 %14
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySize %9 %11
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 1D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQuerySize %9 %10
+)",
+          R"(
+OpCapability Image1D
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySize %9 %11
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 2
+%12 = OpTypeVector %10 3
+)",
+          R"(
+%13 = OpLoad %3 %1
+%11 = OpImageQuerySize %12 %13
+%8 = OpVectorShuffle %9 %11 %11 0 1
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeInt 32 1
+%9 = OpTypeVector %10 3
+)",
+          R"(
+%11 = OpLoad %3 %1
+%8 = OpImageQuerySize %9 %11
+)",
+          R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kGather2dF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %17 %19
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGather2dOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 0
+%20 = OpTypeVector %18 2
+%21 = OpConstant %18 3
+%22 = OpConstant %18 4
+%23 = OpConstantComposite %20 %21 %22
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %17 %19 ConstOffset %23
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGather2dArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGather2dArrayOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %18 0
+%22 = OpTypeVector %18 2
+%23 = OpConstant %18 4
+%24 = OpConstant %18 5
+%25 = OpConstantComposite %22 %23 %24
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21 ConstOffset %25
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCubeF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %18 %20
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCubeArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21
+)",
+              R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kGatherDepth2dF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %17 %19
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherDepth2dOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 0
+%20 = OpTypeVector %18 2
+%21 = OpConstant %18 3
+%22 = OpConstant %18 4
+%23 = OpConstantComposite %20 %21 %22
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %17 %19 ConstOffset %23
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherDepth2dArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %18 0
+%22 = OpTypeVector %18 2
+%23 = OpConstant %18 4
+%24 = OpConstant %18 5
+%25 = OpConstantComposite %22 %23 %24
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21 ConstOffset %25
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherDepthCubeF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageGather %9 %13 %18 %20
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherDepthCubeArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %18 0
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageGather %9 %13 %20 %21
+)",
+              R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kGatherCompareDepth2dF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageDrefGather %9 %13 %17 %18
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 2
+%21 = OpConstant %20 4
+%22 = OpConstant %20 5
+%23 = OpConstantComposite %19 %21 %22
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageDrefGather %9 %13 %17 %18 ConstOffset %23
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageDrefGather %9 %13 %20 %21
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+%22 = OpTypeVector %18 2
+%23 = OpConstant %18 5
+%24 = OpConstant %18 6
+%25 = OpConstantComposite %22 %23 %24
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageDrefGather %9 %13 %20 %21 ConstOffset %25
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCompareDepthCubeF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageDrefGather %9 %13 %18 %19
+)",
+              R"(
+)"};
+    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %4 5
+)",
+              R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageDrefGather %9 %13 %20 %21
+)",
+              R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kNumLayers2dArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpTypeVector %9 3
+%13 = OpConstant %9 0
+)",
+              R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %13
+%8 = OpCompositeExtract %9 %10 2
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLayersCubeArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpTypeVector %9 3
+%13 = OpConstant %9 0
+)",
+              R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %13
+%8 = OpCompositeExtract %9 %10 2
+)",
+              R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpTypeVector %9 3
+%13 = OpConstant %9 0
+)",
+              R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %13
+%8 = OpCompositeExtract %9 %10 2
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpTypeVector %9 3
+%13 = OpConstant %9 0
+)",
+              R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %13
+%8 = OpCompositeExtract %9 %10 2
+)",
+              R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+%11 = OpTypeVector %9 3
+)",
+              R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySize %11 %12
+%8 = OpCompositeExtract %9 %10 2
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevels2d:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevels2dArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevels3d:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsCube:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsCubeArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsDepth2d:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsDepthCube:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQueryLevels %9 %10
+)",
+              R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQuerySamples %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
+      return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 1
+)",
+              R"(
+%10 = OpLoad %3 %1
+%8 = OpImageQuerySamples %9 %10
+)",
+              R"(
+OpCapability ImageQuery
+)"};
+    case ValidTextureOverload::kSample1dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %14
+)",
+          R"(
+OpCapability Sampled1D
+)"};
+    case ValidTextureOverload::kSample2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%19 = OpTypeInt 32 1
+%18 = OpTypeVector %19 2
+%20 = OpConstant %19 3
+%21 = OpConstant %19 4
+%22 = OpConstantComposite %18 %20 %21
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %17 ConstOffset %22
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSample2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpTypeVector %18 2
+%22 = OpConstant %18 4
+%23 = OpConstant %18 5
+%24 = OpConstantComposite %21 %22 %23
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20 ConstOffset %24
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSample3dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 3
+%21 = OpConstant %20 4
+%22 = OpConstant %20 5
+%23 = OpConstant %20 6
+%24 = OpConstantComposite %19 %21 %22 %23
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18 ConstOffset %24
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 2
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstantComposite %15 %16 %17
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%9 = OpImageSampleImplicitLod %10 %14 %18
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 2
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstantComposite %15 %16 %17
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 2
+%21 = OpConstant %20 3
+%22 = OpConstant %20 4
+%23 = OpConstantComposite %19 %21 %22
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%9 = OpImageSampleImplicitLod %10 %14 %18 ConstOffset %23
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 3
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %15 %16 %17 %18
+%9 = OpImageSampleImplicitLod %10 %14 %21
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 3
+%22 = OpTypeVector %19 2
+%23 = OpConstant %19 4
+%24 = OpConstant %19 5
+%25 = OpConstantComposite %22 %23 %24
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %15 %16 %17 %18
+%9 = OpImageSampleImplicitLod %10 %14 %21 ConstOffset %25
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstant %4 3
+%19 = OpConstantComposite %15 %16 %17 %18
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%9 = OpImageSampleImplicitLod %10 %14 %19
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 4
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %10 %15 %16 %17 %18
+%9 = OpImageSampleImplicitLod %10 %14 %21
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleBias2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %17 Bias %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 2
+%21 = OpConstant %20 4
+%22 = OpConstant %20 5
+%23 = OpConstantComposite %19 %21 %22
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %17 Bias|ConstOffset %18 %23
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20 Bias %21
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+%22 = OpTypeVector %18 2
+%23 = OpConstant %18 5
+%24 = OpConstant %18 6
+%25 = OpConstantComposite %22 %23 %24
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20 Bias|ConstOffset %21 %25
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBias3dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18 Bias %19
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+%21 = OpTypeInt 32 1
+%20 = OpTypeVector %21 3
+%22 = OpConstant %21 5
+%23 = OpConstant %21 6
+%24 = OpConstant %21 7
+%25 = OpConstantComposite %20 %22 %23 %24
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18 Bias|ConstOffset %19 %25
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleImplicitLod %9 %13 %18 Bias %19
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageSampleImplicitLod %9 %13 %20 Bias %21
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %17 Lod %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 2
+%21 = OpConstant %20 4
+%22 = OpConstant %20 5
+%23 = OpConstantComposite %19 %21 %22
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %17 Lod|ConstOffset %18 %23
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Lod %21
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpConstant %4 4
+%22 = OpTypeVector %18 2
+%23 = OpConstant %18 5
+%24 = OpConstant %18 6
+%25 = OpConstantComposite %22 %23 %24
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Lod|ConstOffset %21 %25
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Lod %19
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+%21 = OpTypeInt 32 1
+%20 = OpTypeVector %21 3
+%22 = OpConstant %21 5
+%23 = OpConstant %21 6
+%24 = OpConstant %21 7
+%25 = OpConstantComposite %20 %22 %23 %24
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Lod|ConstOffset %19 %25
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Lod %19
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %4 5
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Lod %21
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 2
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstantComposite %15 %16 %17
+%20 = OpTypeInt 32 1
+%21 = OpConstant %20 3
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%19 = OpConvertSToF %4 %21
+%9 = OpImageSampleExplicitLod %10 %14 %18 Lod %19
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 2
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstantComposite %15 %16 %17
+%20 = OpTypeInt 32 1
+%21 = OpConstant %20 3
+%22 = OpTypeVector %20 2
+%23 = OpConstant %20 4
+%24 = OpConstant %20 5
+%25 = OpConstantComposite %22 %23 %24
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%19 = OpConvertSToF %4 %21
+%9 = OpImageSampleExplicitLod %10 %14 %18 Lod|ConstOffset %19 %25
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 3
+%23 = OpConstant %19 4
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %15 %16 %17 %18
+%22 = OpConvertSToF %4 %23
+%9 = OpImageSampleExplicitLod %10 %14 %21 Lod %22
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 3
+%23 = OpConstant %19 4
+%24 = OpTypeVector %19 2
+%25 = OpConstant %19 5
+%26 = OpConstant %19 6
+%27 = OpConstantComposite %24 %25 %26
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %15 %16 %17 %18
+%22 = OpConvertSToF %4 %23
+%9 = OpImageSampleExplicitLod %10 %14 %21 Lod|ConstOffset %22 %27
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpTypeVector %4 3
+%16 = OpConstant %4 1
+%17 = OpConstant %4 2
+%18 = OpConstant %4 3
+%19 = OpConstantComposite %15 %16 %17 %18
+%21 = OpTypeInt 32 1
+%22 = OpConstant %21 4
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%20 = OpConvertSToF %4 %22
+%9 = OpImageSampleExplicitLod %10 %14 %19 Lod %20
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeSampledImage %3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%19 = OpTypeInt 32 1
+%20 = OpConstant %19 4
+%23 = OpConstant %19 5
+)",
+          R"(
+%11 = OpLoad %7 %5
+%12 = OpLoad %3 %1
+%14 = OpSampledImage %13 %12 %11
+%18 = OpConvertSToF %4 %20
+%21 = OpCompositeConstruct %10 %15 %16 %17 %18
+%22 = OpConvertSToF %4 %23
+%9 = OpImageSampleExplicitLod %10 %14 %21 Lod %22
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+%19 = OpConstant %4 4
+%20 = OpConstantComposite %14 %18 %19
+%21 = OpConstant %4 5
+%22 = OpConstant %4 6
+%23 = OpConstantComposite %14 %21 %22
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %17 Grad %20 %23
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 2
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstantComposite %14 %15 %16
+%18 = OpConstant %4 3
+%19 = OpConstant %4 4
+%20 = OpConstantComposite %14 %18 %19
+%21 = OpConstant %4 5
+%22 = OpConstant %4 6
+%23 = OpConstantComposite %14 %21 %22
+%25 = OpTypeInt 32 1
+%24 = OpTypeVector %25 2
+%26 = OpConstant %25 7
+%27 = OpConstantComposite %24 %26 %26
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %17 Grad|ConstOffset %20 %23 %27
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpTypeVector %4 2
+%22 = OpConstant %4 4
+%23 = OpConstant %4 5
+%24 = OpConstantComposite %21 %22 %23
+%25 = OpConstant %4 6
+%26 = OpConstant %4 7
+%27 = OpConstantComposite %21 %25 %26
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Grad %24 %27
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 3
+%21 = OpTypeVector %4 2
+%22 = OpConstant %4 4
+%23 = OpConstant %4 5
+%24 = OpConstantComposite %21 %22 %23
+%25 = OpConstant %4 6
+%26 = OpConstant %4 7
+%27 = OpConstantComposite %21 %25 %26
+%28 = OpTypeVector %18 2
+%29 = OpConstant %18 6
+%30 = OpConstant %18 7
+%31 = OpConstantComposite %28 %29 %30
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Grad|ConstOffset %24 %27 %31
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+%20 = OpConstant %4 5
+%21 = OpConstant %4 6
+%22 = OpConstantComposite %14 %19 %20 %21
+%23 = OpConstant %4 7
+%24 = OpConstant %4 8
+%25 = OpConstant %4 9
+%26 = OpConstantComposite %14 %23 %24 %25
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Grad %22 %26
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+%20 = OpConstant %4 5
+%21 = OpConstant %4 6
+%22 = OpConstantComposite %14 %19 %20 %21
+%23 = OpConstant %4 7
+%24 = OpConstant %4 8
+%25 = OpConstant %4 9
+%26 = OpConstantComposite %14 %23 %24 %25
+%28 = OpTypeInt 32 1
+%27 = OpTypeVector %28 3
+%29 = OpConstant %28 0
+%30 = OpConstant %28 1
+%31 = OpConstant %28 2
+%32 = OpConstantComposite %27 %29 %30 %31
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Grad|ConstOffset %22 %26 %32
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpTypeVector %4 3
+%15 = OpConstant %4 1
+%16 = OpConstant %4 2
+%17 = OpConstant %4 3
+%18 = OpConstantComposite %14 %15 %16 %17
+%19 = OpConstant %4 4
+%20 = OpConstant %4 5
+%21 = OpConstant %4 6
+%22 = OpConstantComposite %14 %19 %20 %21
+%23 = OpConstant %4 7
+%24 = OpConstant %4 8
+%25 = OpConstant %4 9
+%26 = OpConstantComposite %14 %23 %24 %25
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%8 = OpImageSampleExplicitLod %9 %13 %18 Grad %22 %26
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeSampledImage %3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpTypeVector %4 3
+%22 = OpConstant %4 5
+%23 = OpConstant %4 6
+%24 = OpConstant %4 7
+%25 = OpConstantComposite %21 %22 %23 %24
+%26 = OpConstant %4 8
+%27 = OpConstant %4 9
+%28 = OpConstant %4 10
+%29 = OpConstantComposite %21 %26 %27 %28
+)",
+          R"(
+%10 = OpLoad %7 %5
+%11 = OpLoad %3 %1
+%13 = OpSampledImage %12 %11 %10
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %9 %14 %15 %16 %17
+%8 = OpImageSampleExplicitLod %9 %13 %20 Grad %25 %29
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 2
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstantComposite %13 %14 %15
+%17 = OpConstant %4 3
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 2
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstantComposite %13 %14 %15
+%17 = OpConstant %4 3
+%19 = OpTypeInt 32 1
+%18 = OpTypeVector %19 2
+%20 = OpConstant %19 4
+%21 = OpConstant %19 5
+%22 = OpConstantComposite %18 %20 %21
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %17 ConstOffset %22
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%17 = OpTypeInt 32 1
+%18 = OpConstant %17 4
+%20 = OpConstant %4 3
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%16 = OpConvertSToF %4 %18
+%19 = OpCompositeConstruct %13 %14 %15 %16
+%8 = OpImageSampleDrefImplicitLod %4 %12 %19 %20
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%17 = OpTypeInt 32 1
+%18 = OpConstant %17 4
+%20 = OpConstant %4 3
+%21 = OpTypeVector %17 2
+%22 = OpConstant %17 5
+%23 = OpConstant %17 6
+%24 = OpConstantComposite %21 %22 %23
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%16 = OpConvertSToF %4 %18
+%19 = OpCompositeConstruct %13 %14 %15 %16
+%8 = OpImageSampleDrefImplicitLod %4 %12 %19 %20 ConstOffset %24
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%17 = OpConstantComposite %13 %14 %15 %16
+%18 = OpConstant %4 4
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefImplicitLod %4 %12 %17 %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 4
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %4 5
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %13 %14 %15 %16 %17
+%8 = OpImageSampleDrefImplicitLod %4 %12 %20 %21
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 2
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstantComposite %13 %14 %15
+%17 = OpConstant %4 3
+%18 = OpConstant %4 0
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefExplicitLod %4 %12 %16 %17 Lod %18
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 2
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstantComposite %13 %14 %15
+%17 = OpConstant %4 3
+%18 = OpConstant %4 0
+%20 = OpTypeInt 32 1
+%19 = OpTypeVector %20 2
+%21 = OpConstant %20 4
+%22 = OpConstant %20 5
+%23 = OpConstantComposite %19 %21 %22
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefExplicitLod %4 %12 %16 %17 Lod|ConstOffset %18 %23
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%17 = OpTypeInt 32 1
+%18 = OpConstant %17 4
+%20 = OpConstant %4 3
+%21 = OpConstant %4 0
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%16 = OpConvertSToF %4 %18
+%19 = OpCompositeConstruct %13 %14 %15 %16
+%8 = OpImageSampleDrefExplicitLod %4 %12 %19 %20 Lod %21
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%17 = OpTypeInt 32 1
+%18 = OpConstant %17 4
+%20 = OpConstant %4 3
+%21 = OpConstant %4 0
+%22 = OpTypeVector %17 2
+%23 = OpConstant %17 5
+%24 = OpConstant %17 6
+%25 = OpConstantComposite %22 %23 %24
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%16 = OpConvertSToF %4 %18
+%19 = OpCompositeConstruct %13 %14 %15 %16
+%8 = OpImageSampleDrefExplicitLod %4 %12 %19 %20 Lod|ConstOffset %21 %25
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 3
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%17 = OpConstantComposite %13 %14 %15 %16
+%18 = OpConstant %4 4
+%19 = OpConstant %4 0
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%8 = OpImageSampleDrefExplicitLod %4 %12 %17 %18 Lod %19
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%11 = OpTypeSampledImage %3
+%13 = OpTypeVector %4 4
+%14 = OpConstant %4 1
+%15 = OpConstant %4 2
+%16 = OpConstant %4 3
+%18 = OpTypeInt 32 1
+%19 = OpConstant %18 4
+%21 = OpConstant %4 5
+%22 = OpConstant %4 0
+)",
+          R"(
+%9 = OpLoad %7 %5
+%10 = OpLoad %3 %1
+%12 = OpSampledImage %11 %10 %9
+%17 = OpConvertSToF %4 %19
+%20 = OpCompositeConstruct %13 %14 %15 %16 %17
+%8 = OpImageSampleDrefExplicitLod %4 %12 %20 %21 Lod %22
+)",
+          R"(
+OpCapability SampledCubeArray
+)"};
+    case ValidTextureOverload::kLoad1dLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeInt 32 1
+%12 = OpConstant %11 1
+%13 = OpConstant %11 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %12 Lod %13
+)",
+          R"(
+OpCapability Sampled1D
+)"};
+    case ValidTextureOverload::kLoad1dLevelU32:
+      return {
+          R"(
+%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeInt 32 1
+%12 = OpConstant %11 1
+%13 = OpConstant %11 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %12 Lod %13
+)",
+          R"(
+OpCapability Sampled1D
+)"};
+    case ValidTextureOverload::kLoad1dLevelI32:
+      return {
+          R"(
+%4 = OpTypeInt 32 1
+%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpConstant %4 1
+%12 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %11 Lod %12
+)",
+          R"(
+OpCapability Sampled1D
+)"};
+    case ValidTextureOverload::kLoad2dLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 2
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstantComposite %11 %13 %14
+%16 = OpConstant %12 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Lod %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad2dLevelU32:
+      return {
+          R"(
+%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 2
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstantComposite %11 %13 %14
+%16 = OpConstant %12 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Lod %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return {
+          R"(
+%4 = OpTypeInt 32 1
+%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeVector %4 2
+%12 = OpConstant %4 1
+%13 = OpConstant %4 2
+%14 = OpConstantComposite %11 %12 %13
+%15 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %14 Lod %15
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpConstant %12 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %16 Lod %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+      return {
+          R"(
+%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpConstant %12 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %16 Lod %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+      return {
+          R"(
+%4 = OpTypeInt 32 1
+%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeVector %4 3
+%12 = OpConstant %4 1
+%13 = OpConstant %4 2
+%14 = OpConstant %4 3
+%15 = OpConstantComposite %11 %12 %13 %14
+%16 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Lod %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad3dLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpConstant %12 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %16 Lod %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad3dLevelU32:
+      return {
+          R"(
+%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpConstant %12 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %16 Lod %17
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoad3dLevelI32:
+      return {
+          R"(
+%4 = OpTypeInt 32 1
+%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeVector %4 3
+%12 = OpConstant %4 1
+%13 = OpConstant %4 2
+%14 = OpConstant %4 3
+%15 = OpConstantComposite %11 %12 %13 %14
+%16 = OpConstant %4 4
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Lod %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 2
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstantComposite %11 %13 %14
+%16 = OpConstant %12 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Sample %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+      return {
+          R"(
+%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 2
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstantComposite %11 %13 %14
+%16 = OpConstant %12 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %15 Sample %16
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return {
+          R"(
+%4 = OpTypeInt 32 1
+%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVector %4 4
+%11 = OpTypeVector %4 2
+%12 = OpConstant %4 1
+%13 = OpConstant %4 2
+%14 = OpConstantComposite %11 %12 %13
+%15 = OpConstant %4 3
+)",
+          R"(
+%10 = OpLoad %3 %1
+%8 = OpImageFetch %9 %10 %14 Sample %15
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeInt 32 1
+%12 = OpTypeVector %13 2
+%14 = OpConstant %13 1
+%15 = OpConstant %13 2
+%16 = OpConstantComposite %12 %14 %15
+%17 = OpConstant %13 3
+)",
+          R"(
+%11 = OpLoad %3 %1
+%9 = OpImageFetch %10 %11 %16 Lod %17
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeInt 32 1
+%12 = OpTypeVector %13 3
+%14 = OpConstant %13 1
+%15 = OpConstant %13 2
+%16 = OpConstant %13 3
+%17 = OpConstantComposite %12 %14 %15 %16
+%18 = OpConstant %13 4
+)",
+          R"(
+%11 = OpLoad %3 %1
+%9 = OpImageFetch %10 %11 %17 Lod %18
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%10 = OpTypeVector %4 4
+%13 = OpTypeInt 32 1
+%12 = OpTypeVector %13 3
+%14 = OpConstant %13 1
+%15 = OpConstant %13 2
+%16 = OpConstant %13 3
+%17 = OpConstantComposite %12 %14 %15 %16
+%18 = OpConstant %13 4
+)",
+          R"(
+%11 = OpLoad %3 %1
+%9 = OpImageFetch %10 %11 %17 Sample %18
+%8 = OpCompositeExtract %4 %9 0
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 1D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVoid
+%11 = OpTypeInt 32 1
+%12 = OpConstant %11 1
+%13 = OpTypeVector %4 4
+%14 = OpConstant %4 2
+%15 = OpConstant %4 3
+%16 = OpConstant %4 4
+%17 = OpConstant %4 5
+%18 = OpConstantComposite %13 %14 %15 %16 %17
+)",
+          R"(
+%10 = OpLoad %3 %1
+OpImageWrite %10 %12 %18
+)",
+          R"(
+OpCapability Image1D
+)"};
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVoid
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 2
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstantComposite %11 %13 %14
+%16 = OpTypeVector %4 4
+%17 = OpConstant %4 3
+%18 = OpConstant %4 4
+%19 = OpConstant %4 5
+%20 = OpConstant %4 6
+%21 = OpConstantComposite %16 %17 %18 %19 %20
+)",
+          R"(
+%10 = OpLoad %3 %1
+OpImageWrite %10 %15 %21
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVoid
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpTypeVector %4 4
+%18 = OpConstant %4 4
+%19 = OpConstant %4 5
+%20 = OpConstant %4 6
+%21 = OpConstant %4 7
+%22 = OpConstantComposite %17 %18 %19 %20 %21
+)",
+          R"(
+%10 = OpLoad %3 %1
+OpImageWrite %10 %16 %22
+)",
+          R"(
+)"};
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return {
+          R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 3D 0 0 0 2 Rgba32f
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeVoid
+%12 = OpTypeInt 32 1
+%11 = OpTypeVector %12 3
+%13 = OpConstant %12 1
+%14 = OpConstant %12 2
+%15 = OpConstant %12 3
+%16 = OpConstantComposite %11 %13 %14 %15
+%17 = OpTypeVector %4 4
+%18 = OpConstant %4 4
+%19 = OpConstant %4 5
+%20 = OpConstant %4 6
+%21 = OpConstant %4 7
+%22 = OpConstantComposite %17 %18 %19 %20 %21
+)",
+          R"(
+%10 = OpLoad %3 %1
+OpImageWrite %10 %16 %22
+)",
+          R"(
+)"};
+  }
+
+  return {"<unmatched texture overload>", "<unmatched texture overload>",
+          "<unmatched texture overload>"};
+}  // NOLINT - Ignore the length of this function
+
+using BuiltinTextureTest =
+    TestParamHelper<ast::builtin::test::TextureOverloadCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinTextureTest,
+    BuiltinTextureTest,
+    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
+
+TEST_P(BuiltinTextureTest, Call) {
+  auto param = GetParam();
+
+  auto* texture = param.BuildTextureVariable(this);
+  auto* sampler = param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  auto* stmt = CallStmt(call);
+  Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+
+  EXPECT_EQ(b.GenerateExpression(call), 8u) << b.error();
+
+  auto expected = expected_texture_overload(param.overload);
+  EXPECT_EQ(expected.types, "\n" + DumpInstructions(b.types()));
+  EXPECT_EQ(expected.instructions,
+            "\n" + DumpInstructions(b.functions()[0].instructions()));
+  EXPECT_EQ(expected.capabilities, "\n" + DumpInstructions(b.capabilities()));
+}
+
+// Check the SPIRV generated passes validation
+TEST_P(BuiltinTextureTest, ValidateSPIRV) {
+  auto param = GetParam();
+
+  param.BuildTextureVariable(this);
+  param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+
+  auto* stmt = CallStmt(call);
+  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  Validate(b);
+}
+
+TEST_P(BuiltinTextureTest, OutsideFunction_IsError) {
+  auto param = GetParam();
+
+  // The point of this test is to try to generate the texture
+  // builtin call outside a function.
+
+  auto* texture = param.BuildTextureVariable(this);
+  auto* sampler = param.BuildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  auto* stmt = CallStmt(call);
+  Func("func", {}, ty.void_(), {stmt},
+       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(call), 0u);
+  EXPECT_THAT(b.error(),
+              ::testing::StartsWith(
+                  "Internal error: trying to add SPIR-V instruction "));
+  EXPECT_THAT(b.error(), ::testing::EndsWith(" outside a function"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_call_test.cc b/src/tint/writer/spirv/builder_call_test.cc
new file mode 100644
index 0000000..8761bac
--- /dev/null
+++ b/src/tint/writer/spirv/builder_call_test.cc
@@ -0,0 +1,107 @@
+
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Expression_Call) {
+  ast::VariableList func_params;
+  func_params.push_back(Param("a", ty.f32()));
+  func_params.push_back(Param("b", ty.f32()));
+
+  auto* a_func = Func("a_func", func_params, ty.f32(), {Return(Add("a", "b"))});
+  auto* func =
+      Func("main", {}, ty.void_(), {Assign(Phony(), Call("a_func", 1.f, 1.f))});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+OpName %4 "a"
+OpName %5 "b"
+OpName %10 "main"
+%2 = OpTypeFloat 32
+%1 = OpTypeFunction %2 %2 %2
+%9 = OpTypeVoid
+%8 = OpTypeFunction %9
+%13 = OpConstant %2 1
+%3 = OpFunction %2 None %1
+%4 = OpFunctionParameter %2
+%5 = OpFunctionParameter %2
+%6 = OpLabel
+%7 = OpFAdd %2 %4 %5
+OpReturnValue %7
+OpFunctionEnd
+%10 = OpFunction %9 None %8
+%11 = OpLabel
+%12 = OpFunctionCall %2 %3 %13 %13
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Statement_Call) {
+  ast::VariableList func_params;
+  func_params.push_back(Param("a", ty.f32()));
+  func_params.push_back(Param("b", ty.f32()));
+
+  auto* a_func = Func("a_func", func_params, ty.f32(), {Return(Add("a", "b"))});
+
+  auto* func =
+      Func("main", {}, ty.void_(), {CallStmt(Call("a_func", 1.f, 1.f))});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+OpName %4 "a"
+OpName %5 "b"
+OpName %10 "main"
+%2 = OpTypeFloat 32
+%1 = OpTypeFunction %2 %2 %2
+%9 = OpTypeVoid
+%8 = OpTypeFunction %9
+%13 = OpConstant %2 1
+%3 = OpFunction %2 None %1
+%4 = OpFunctionParameter %2
+%5 = OpFunctionParameter %2
+%6 = OpLabel
+%7 = OpFAdd %2 %4 %5
+OpReturnValue %7
+OpFunctionEnd
+%10 = OpFunction %9 None %8
+%11 = OpLabel
+%12 = OpFunctionCall %2 %3 %13 %13
+OpReturn
+OpFunctionEnd
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_constructor_expression_test.cc b/src/tint/writer/spirv/builder_constructor_expression_test.cc
new file mode 100644
index 0000000..2615b05
--- /dev/null
+++ b/src/tint/writer/spirv/builder_constructor_expression_test.cc
@@ -0,0 +1,1858 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using SpvBuilderConstructorTest = TestHelper;
+
+TEST_F(SpvBuilderConstructorTest, Const) {
+  auto* c = Expr(42.2f);
+  auto* g = Global("g", ty.f32(), c, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateConstructorExpression(g, c), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 42.2000008
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_WithCasts_OutsideFunction_IsError) {
+  auto* t = Construct<f32>(Construct<u32>(1));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateExpression(t), 0u);
+  EXPECT_TRUE(b.has_error()) << b.error();
+  EXPECT_EQ(b.error(),
+            "Internal error: trying to add SPIR-V instruction 124 outside a "
+            "function");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type) {
+  auto* t = vec3<f32>(1.0f, 1.0f, 3.0f);
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateConstructorExpression(nullptr, t), 5u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_WithCasts) {
+  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(1));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 7u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%4 = OpTypeInt 32 1
+%5 = OpConstant %4 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%3 = OpConvertSToF %2 %5
+%6 = OpConvertSToF %2 %5
+%7 = OpCompositeConstruct %1 %3 %6
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_WithAlias) {
+  // type Int = i32
+  // cast<Int>(2.3f)
+
+  auto* alias = Alias("Int", ty.i32());
+  auto* cast = Construct(ty.Of(alias), 2.3f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeFloat 32
+%4 = OpConstant %3 2.29999995
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpConvertFToS %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_IdentifierExpression_Param) {
+  auto* var = Var("ident", ty.f32());
+
+  auto* t = vec2<f32>(1.0f, "ident");
+  WrapInFunction(var, t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateExpression(t), 8u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+%5 = OpTypeVector %3 2
+%6 = OpConstant %3 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %4
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%7 = OpLoad %3 %1
+%8 = OpCompositeConstruct %5 %6 %7
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Vector_Bitcast_Params) {
+  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 7u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%1 = OpTypeVector %2 2
+%4 = OpTypeInt 32 1
+%5 = OpConstant %4 1
+)");
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%3 = OpBitcast %2 %5
+%6 = OpBitcast %2 %5
+%7 = OpCompositeConstruct %1 %3 %6
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Bool_With_Bool) {
+  auto* cast = Construct<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(cast), 3u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantTrue %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_I32_With_I32) {
+  auto* cast = Construct<i32>(2);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 3u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_U32_With_U32) {
+  auto* cast = Construct<u32>(2u);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 3u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%3 = OpConstant %2 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_F32_With_F32) {
+  auto* cast = Construct<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 3u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpConstant %2 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Literal) {
+  auto* cast = vec2<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 2
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Var) {
+  auto* var = Var("v", nullptr, Expr(true));
+  auto* cast = vec2<bool>(var);
+  WrapInFunction(var, cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+%4 = OpTypePointer Function %1
+%5 = OpConstantNull %1
+%6 = OpTypeVector %1 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %3 %2
+%7 = OpLoad %1 %3
+%8 = OpCompositeConstruct %6 %7 %7
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_Literal) {
+  auto* cast = vec2<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_Var) {
+  auto* var = Var("v", nullptr, Expr(2.0f));
+  auto* cast = vec2<f32>(var);
+  WrapInFunction(var, cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 2
+%4 = OpTypePointer Function %1
+%5 = OpConstantNull %1
+%6 = OpTypeVector %1 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %3 %2
+%7 = OpLoad %1 %3
+%8 = OpCompositeConstruct %6 %7 %7
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_F32) {
+  auto* cast = vec2<f32>(2.0f, 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Vec2) {
+  auto* value = vec2<f32>(2.0f, 2.0f);
+  auto* cast = vec2<f32>(value);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 5u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 2
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32) {
+  auto* cast = vec3<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Bool) {
+  auto* cast = vec3<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 3
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_F32_F32) {
+  auto* cast = vec3<f32>(2.0f, 2.0f, 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_Vec2) {
+  auto* cast = vec3<f32>(2.0f, vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeConstruct %1 %3 %6 %7
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec2_F32) {
+  auto* cast = vec3<f32>(vec2<f32>(2.0f, 2.0f), 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeConstruct %1 %6 %7 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec3) {
+  auto* value = vec3<f32>(2.0f, 2.0f, 2.0f);
+  auto* cast = vec3<f32>(value);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 5u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Bool) {
+  auto* cast = vec4<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 4
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32) {
+  auto* cast = vec4<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_F32_F32) {
+  auto* cast = vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_Vec2) {
+  auto* cast = vec4<f32>(2.0f, 2.0f, vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeConstruct %1 %3 %3 %6 %7
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec2_F32) {
+  auto* cast = vec4<f32>(2.0f, vec2<f32>(2.0f, 2.0f), 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeConstruct %1 %3 %6 %7 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_F32_F32) {
+  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), 2.0f, 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 8u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeConstruct %1 %6 %7 %4 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_Vec2) {
+  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 10u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeExtract %2 %5 0
+%9 = OpCompositeExtract %2 %5 1
+%10 = OpCompositeConstruct %1 %6 %7 %8 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec3) {
+  auto* cast = vec4<f32>(2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 3
+%5 = OpConstantComposite %4 %3 %3 %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeExtract %2 %5 2
+%9 = OpCompositeConstruct %1 %3 %6 %7 %8
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec3_F32) {
+  auto* cast = vec4<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 3
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%6 = OpCompositeExtract %2 %5 0
+%7 = OpCompositeExtract %2 %5 1
+%8 = OpCompositeExtract %2 %5 2
+%9 = OpCompositeConstruct %1 %6 %7 %8 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec4) {
+  auto* value = vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f);
+  auto* cast = vec4<f32>(value);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 5u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 4
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_F32_With_F32) {
+  auto* ctor = Construct<f32>(2.0f);
+  GlobalConst("g", ty.f32(), ctor);
+
+  spirv::Builder& b = SanitizeAndBuild();
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 2
+%4 = OpTypeVoid
+%3 = OpTypeFunction %4
+)");
+  Validate(b);
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_U32_With_F32) {
+  auto* ctor = Construct<u32>(1.5f);
+  GlobalConst("g", ty.u32(), ctor);
+
+  spirv::Builder& b = SanitizeAndBuild();
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstant %1 1
+%4 = OpTypeVoid
+%3 = OpTypeFunction %4
+)");
+  Validate(b);
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_F32) {
+  auto* cast = vec2<f32>(2.0f);
+  auto* g = Global("g", ty.vec2<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_Vec2) {
+  auto* cast = vec2<f32>(vec2<f32>(2.0f, 2.0f));
+  GlobalConst("a", ty.vec2<f32>(), cast);
+
+  spirv::Builder& b = SanitizeAndBuild();
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  Validate(b);
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_Vec3) {
+  auto* cast = vec3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f));
+  GlobalConst("a", ty.vec3<f32>(), cast);
+
+  spirv::Builder& b = SanitizeAndBuild();
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  Validate(b);
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec4) {
+  auto* cast = vec4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
+  GlobalConst("a", ty.vec4<f32>(), cast);
+
+  spirv::Builder& b = SanitizeAndBuild();
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+
+  Validate(b);
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32) {
+  auto* cast = vec3<f32>(2.0f);
+  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32_Vec2) {
+  auto* cast = vec3<f32>(2.0f, vec2<f32>(2.0f, 2.0f));
+  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantComposite %1 %3 %6 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_Vec2_F32) {
+  auto* cast = vec3<f32>(vec2<f32>(2.0f, 2.0f), 2.0f);
+  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantComposite %1 %6 %9 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32) {
+  auto* cast = vec4<f32>(2.0f);
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_F32_Vec2) {
+  auto* cast = vec4<f32>(2.0f, 2.0f, vec2<f32>(2.0f, 2.0f));
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantComposite %1 %3 %3 %6 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_Vec2_F32) {
+  auto* cast = vec4<f32>(2.0f, vec2<f32>(2.0f, 2.0f), 2.0f);
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 2
+%5 = OpConstantComposite %4 %3 %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantComposite %1 %3 %6 %9 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec2_F32_F32) {
+  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), 2.0f, 2.0f);
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantComposite %1 %6 %9 %4 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec2_Vec2) {
+  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%11 = OpSpecConstantOp %2 CompositeExtract %5 8
+%12 = OpSpecConstantOp %2 CompositeExtract %5 10
+%13 = OpSpecConstantComposite %1 %6 %9 %11 %12
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_Vec3) {
+  auto* cast = vec4<f32>(2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpTypeVector %2 3
+%5 = OpConstantComposite %4 %3 %3 %3
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%12 = OpConstant %7 2
+%11 = OpSpecConstantOp %2 CompositeExtract %5 12
+%13 = OpSpecConstantComposite %1 %3 %6 %9 %11
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec3_F32) {
+  auto* cast = vec4<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), 2.0f);
+  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpTypeVector %2 3
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4 %4
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 0
+%6 = OpSpecConstantOp %2 CompositeExtract %5 8
+%10 = OpConstant %7 1
+%9 = OpSpecConstantOp %2 CompositeExtract %5 10
+%12 = OpConstant %7 2
+%11 = OpSpecConstantOp %2 CompositeExtract %5 12
+%13 = OpSpecConstantComposite %1 %6 %9 %11 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat2x2_With_Vec2_Vec2) {
+  auto* cast = mat2x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 2
+%1 = OpTypeMatrix %2 2
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4
+%6 = OpConstantComposite %1 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat3x2_With_Vec2_Vec2_Vec2) {
+  auto* cast = mat3x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f),
+                           vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 2
+%1 = OpTypeMatrix %2 3
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat4x2_With_Vec2_Vec2_Vec2_Vec2) {
+  auto* cast = mat4x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f),
+                           vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 2
+%1 = OpTypeMatrix %2 4
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat2x3_With_Vec3_Vec3) {
+  auto* cast =
+      mat2x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%1 = OpTypeMatrix %2 2
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat3x3_With_Vec3_Vec3_Vec3) {
+  auto* cast =
+      mat3x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f),
+                  vec3<f32>(2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%1 = OpTypeMatrix %2 3
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat4x3_With_Vec3_Vec3_Vec3_Vec3) {
+  auto* cast =
+      mat4x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f),
+                  vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%1 = OpTypeMatrix %2 4
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat2x4_With_Vec4_Vec4) {
+  auto* cast = mat2x4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
+                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 4
+%1 = OpTypeMatrix %2 2
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat3x4_With_Vec4_Vec4_Vec4) {
+  auto* cast = mat3x4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
+                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
+                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 4
+%1 = OpTypeMatrix %2 3
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Mat4x4_With_Vec4_Vec4_Vec4_Vec4) {
+  auto* cast = mat4x4<f32>(
+      vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f), vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
+      vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f), vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 4
+%1 = OpTypeMatrix %2 4
+%4 = OpConstant %3 2
+%5 = OpConstantComposite %2 %4 %4 %4 %4
+%6 = OpConstantComposite %1 %5 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Array_5_F32) {
+  auto* cast = array<f32, 5>(2.0f, 2.0f, 2.0f, 2.0f, 2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 5
+%1 = OpTypeArray %2 %4
+%5 = OpConstant %2 2
+%6 = OpConstantComposite %1 %5 %5 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Array_2_Vec3) {
+  auto* first = vec3<f32>(1.f, 2.f, 3.f);
+  auto* second = vec3<f32>(1.f, 2.f, 3.f);
+  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), first, second);
+  WrapInFunction(t);
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(t), 10u);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%4 = OpTypeInt 32 0
+%5 = OpConstant %4 2
+%1 = OpTypeArray %2 %5
+%6 = OpConstant %3 1
+%7 = OpConstant %3 2
+%8 = OpConstant %3 3
+%9 = OpConstantComposite %2 %6 %7 %8
+%10 = OpConstantComposite %1 %9 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoVectors) {
+  auto* v1 = vec3<f32>(2.0f, 2.0f, 2.0f);
+  auto* v2 = vec3<f32>(2.0f, 2.0f, 2.0f);
+  ast::StatementList stmts = {
+      WrapInStatement(v1),
+      WrapInStatement(v2),
+  };
+  WrapInFunction(stmts);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(v1), 4u);
+  EXPECT_EQ(b.GenerateExpression(v2), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoArrays) {
+  auto* a1 = array<f32, 3>(2.0f, 2.0f, 2.0f);
+  auto* a2 = array<f32, 3>(2.0f, 2.0f, 2.0f);
+  ast::StatementList stmts = {
+      WrapInStatement(a1),
+      WrapInStatement(a2),
+  };
+  WrapInFunction(stmts);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(a1), 6u);
+  EXPECT_EQ(b.GenerateExpression(a2), 6u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 3
+%1 = OpTypeArray %2 %4
+%5 = OpConstant %2 2
+%6 = OpConstantComposite %1 %5 %5 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, CommonInitializer_Array_VecArray) {
+  // Test that initializers of different types with the same values produce
+  // different OpConstantComposite instructions.
+  // crbug.com/tint/777
+  auto* a1 = array<f32, 2>(1.0f, 2.0f);
+  auto* a2 = vec2<f32>(1.0f, 2.0f);
+  ast::StatementList stmts = {
+      WrapInStatement(a1),
+      WrapInStatement(a2),
+  };
+  WrapInFunction(stmts);
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(a1), 7u);
+  EXPECT_EQ(b.GenerateExpression(a2), 9u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 2
+%1 = OpTypeArray %2 %4
+%5 = OpConstant %2 1
+%6 = OpConstant %2 2
+%7 = OpConstantComposite %1 %5 %6
+%8 = OpTypeVector %2 2
+%9 = OpConstantComposite %8 %5 %6
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Struct) {
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  auto* t = Construct(ty.Of(s), 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 6u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeVector %2 3
+%1 = OpTypeStruct %2 %3
+%4 = OpConstant %2 2
+%5 = OpConstantComposite %3 %4 %4 %4
+%6 = OpConstantComposite %1 %4 %5
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_F32) {
+  auto* t = Construct<f32>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_I32) {
+  auto* t = Construct<i32>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_U32) {
+  auto* t = Construct<u32>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Bool) {
+  auto* t = Construct<bool>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Vector) {
+  auto* t = vec2<i32>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 3u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeVector %2 2
+%3 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Matrix) {
+  auto* t = mat4x2<f32>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 4u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 2
+%1 = OpTypeMatrix %2 4
+%4 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Array) {
+  auto* t = array<i32, 2>();
+
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 5u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 2
+%1 = OpTypeArray %2 %4
+%5 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
+  auto* s = Structure("my_struct", {Member("a", ty.f32())});
+  auto* t = Construct(ty.Of(s));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_EQ(b.GenerateExpression(t), 3u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2
+%3 = OpConstantNull %1
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_U32_To_I32) {
+  auto* cast = Construct<i32>(2u);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpBitcast %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_I32_To_U32) {
+  auto* cast = Construct<u32>(2);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%3 = OpTypeInt 32 1
+%4 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpBitcast %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_F32_To_I32) {
+  auto* cast = Construct<i32>(2.4f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeFloat 32
+%4 = OpConstant %3 2.4000001
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpConvertFToS %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_F32_To_U32) {
+  auto* cast = Construct<u32>(2.4f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%3 = OpTypeFloat 32
+%4 = OpConstant %3 2.4000001
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpConvertFToU %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_I32_To_F32) {
+  auto* cast = Construct<f32>(2);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 1
+%4 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpConvertSToF %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_U32_To_F32) {
+  auto* cast = Construct<f32>(2u);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpConvertUToF %2 %4
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_U32_to_I32) {
+  auto* var = Global("i", ty.vec3<u32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<i32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeInt 32 1
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpBitcast %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_F32_to_I32) {
+  auto* var = Global("i", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<i32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeInt 32 1
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpConvertFToS %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_I32_to_U32) {
+  auto* var = Global("i", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<u32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeInt 32 0
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpBitcast %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_F32_to_U32) {
+  auto* var = Global("i", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<u32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeInt 32 0
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpConvertFToU %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_I32_to_F32) {
+  auto* var = Global("i", ty.vec3<i32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<f32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeFloat 32
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpConvertSToF %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_U32_to_F32) {
+  auto* var = Global("i", ty.vec3<u32>(), ast::StorageClass::kPrivate);
+
+  auto* cast = vec3<f32>("i");
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Private %3
+%5 = OpConstantNull %3
+%1 = OpVariable %2 Private %5
+%8 = OpTypeFloat 32
+%7 = OpTypeVector %8 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+%6 = OpConvertUToF %7 %9
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_GlobalVectorWithAllConstConstructors) {
+  // vec3<f32>(1.0, 2.0, 3.0)  -> true
+  auto* t = vec3<f32>(1.f, 2.f, 3.f);
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_GlobalArrayWithAllConstConstructors) {
+  // array<vec3<f32>, 2>(vec3<f32>(1.0, 2.0, 3.0), vec3<f32>(1.0, 2.0, 3.0))
+  //   -> true
+  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), vec3<f32>(1.f, 2.f, 3.f),
+                      vec3<f32>(1.f, 2.f, 3.f));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_GlobalVectorWithMatchingTypeConstructors) {
+  // vec2<f32>(f32(1.0), f32(2.0))  -> false
+
+  auto* t = vec2<f32>(Construct<f32>(1.f), Construct<f32>(2.f));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_GlobalWithTypeConversionConstructor) {
+  // vec2<f32>(f32(1), f32(2)) -> false
+
+  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_VectorWithAllConstConstructors) {
+  // vec3<f32>(1.0, 2.0, 3.0)  -> true
+
+  auto* t = vec3<f32>(1.f, 2.f, 3.f);
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Vector_WithIdent) {
+  // vec3<f32>(a, b, c)  -> false
+
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+  Global("b", ty.f32(), ast::StorageClass::kPrivate);
+  Global("c", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* t = vec3<f32>("a", "b", "c");
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_ArrayWithAllConstConstructors) {
+  // array<vec3<f32>, 2>(vec3<f32>(1.0, 2.0, 3.0), vec3<f32>(1.0, 2.0, 3.0))
+  //   -> true
+
+  auto* first = vec3<f32>(1.f, 2.f, 3.f);
+  auto* second = vec3<f32>(1.f, 2.f, 3.f);
+
+  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), first, second);
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_VectorWithTypeConversionConstConstructors) {
+  // vec2<f32>(f32(1), f32(2))  -> false
+
+  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest, IsConstructorConst_BitCastScalars) {
+  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct) {
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  auto* t = Construct(ty.Of(s), 2.f, vec3<f32>(2.f, 2.f, 2.f));
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+TEST_F(SpvBuilderConstructorTest,
+       IsConstructorConst_Struct_WithIdentSubExpression) {
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+  Global("b", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+
+  auto* t = Construct(ty.Of(s), "a", "b");
+  WrapInFunction(t);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.IsConstructorConst(t));
+  EXPECT_FALSE(b.has_error());
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_discard_test.cc b/src/tint/writer/spirv/builder_discard_test.cc
new file mode 100644
index 0000000..5a85bff
--- /dev/null
+++ b/src/tint/writer/spirv/builder_discard_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Discard) {
+  auto* expr = create<ast::DiscardStatement>();
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpKill
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
new file mode 100644
index 0000000..a860d65
--- /dev/null
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -0,0 +1,322 @@
+// 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 <memory>
+
+#include "gtest/gtest.h"
+#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/builtin_attribute.h"
+#include "src/tint/ast/location_attribute.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/storage_class.h"
+#include "src/tint/ast/variable.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/f32_type.h"
+#include "src/tint/sem/vector_type.h"
+#include "src/tint/writer/spirv/builder.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, EntryPoint_Parameters) {
+  // @stage(fragment)
+  // fn frag_main(@builtin(position) coord : vec4<f32>,
+  //              @location(1) loc1 : f32) {
+  //   var col : f32 = (coord.x * loc1);
+  // }
+  auto* coord =
+      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+  auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
+  auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
+  auto* col = Var("col", ty.f32(), ast::StorageClass::kNone, mul);
+  Func("frag_main", ast::VariableList{coord, loc1}, ty.void_(),
+       ast::StatementList{WrapInStatement(col)},
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  // Test that "coord" and "loc1" get hoisted out to global variables with the
+  // Input storage class, retaining their decorations.
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %19 "frag_main" %1 %5
+OpExecutionMode %19 OriginUpperLeft
+OpName %1 "coord_1"
+OpName %5 "loc1_1"
+OpName %9 "frag_main_inner"
+OpName %10 "coord"
+OpName %11 "loc1"
+OpName %15 "col"
+OpName %19 "frag_main"
+OpDecorate %1 BuiltIn FragCoord
+OpDecorate %5 Location 1
+%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 4
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
+%6 = OpTypePointer Input %4
+%5 = OpVariable %6 Input
+%8 = OpTypeVoid
+%7 = OpTypeFunction %8 %3 %4
+%16 = OpTypePointer Function %4
+%17 = OpConstantNull %4
+%18 = OpTypeFunction %8
+%9 = OpFunction %8 None %7
+%10 = OpFunctionParameter %3
+%11 = OpFunctionParameter %4
+%12 = OpLabel
+%15 = OpVariable %16 Function %17
+%13 = OpCompositeExtract %4 %10 0
+%14 = OpFMul %4 %13 %11
+OpStore %15 %14
+OpReturn
+OpFunctionEnd
+%19 = OpFunction %8 None %18
+%20 = OpLabel
+%22 = OpLoad %3 %1
+%23 = OpLoad %4 %5
+%21 = OpFunctionCall %8 %9 %22 %23
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, EntryPoint_ReturnValue) {
+  // @stage(fragment)
+  // fn frag_main(@location(0) @interpolate(flat) loc_in : u32)
+  //     -> @location(0) f32 {
+  //   if (loc_in > 10) {
+  //     return 0.5;
+  //   }
+  //   return 1.0;
+  // }
+  auto* loc_in = Param("loc_in", ty.u32(), {Location(0), Flat()});
+  auto* cond = create<ast::BinaryExpression>(ast::BinaryOp::kGreaterThan,
+                                             Expr("loc_in"), Expr(10u));
+  Func("frag_main", ast::VariableList{loc_in}, ty.f32(),
+       ast::StatementList{
+           If(cond, Block(Return(0.5f))),
+           Return(1.0f),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kFragment),
+       },
+       ast::AttributeList{Location(0)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  // Test that the return value gets hoisted out to a global variable with the
+  // Output storage class, and the return statements are replaced with stores.
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %21 "frag_main" %1 %4
+OpExecutionMode %21 OriginUpperLeft
+OpName %1 "loc_in_1"
+OpName %4 "value"
+OpName %9 "frag_main_inner"
+OpName %10 "loc_in"
+OpName %21 "frag_main"
+OpDecorate %1 Location 0
+OpDecorate %1 Flat
+OpDecorate %4 Location 0
+%3 = OpTypeInt 32 0
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
+%6 = OpTypeFloat 32
+%5 = OpTypePointer Output %6
+%7 = OpConstantNull %6
+%4 = OpVariable %5 Output %7
+%8 = OpTypeFunction %6 %3
+%12 = OpConstant %3 10
+%14 = OpTypeBool
+%17 = OpConstant %6 0.5
+%18 = OpConstant %6 1
+%20 = OpTypeVoid
+%19 = OpTypeFunction %20
+%9 = OpFunction %6 None %8
+%10 = OpFunctionParameter %3
+%11 = OpLabel
+%13 = OpUGreaterThan %14 %10 %12
+OpSelectionMerge %15 None
+OpBranchConditional %13 %16 %15
+%16 = OpLabel
+OpReturnValue %17
+%15 = OpLabel
+OpReturnValue %18
+OpFunctionEnd
+%21 = OpFunction %20 None %19
+%22 = OpLabel
+%24 = OpLoad %3 %1
+%23 = OpFunctionCall %6 %9 %24
+OpStore %4 %23
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, EntryPoint_SharedStruct) {
+  // struct Interface {
+  //   @location(1) value : f32;
+  //   @builtin(position) pos : vec4<f32>;
+  // };
+  //
+  // @stage(vertex)
+  // fn vert_main() -> Interface {
+  //   return Interface(42.0, vec4<f32>());
+  // }
+  //
+  // @stage(fragment)
+  // fn frag_main(inputs : Interface) -> @builtin(frag_depth) f32 {
+  //   return inputs.value;
+  // }
+
+  auto* interface = Structure(
+      "Interface",
+      {
+          Member("value", ty.f32(), ast::AttributeList{Location(1u)}),
+          Member("pos", ty.vec4<f32>(),
+                 ast::AttributeList{Builtin(ast::Builtin::kPosition)}),
+      });
+
+  auto* vert_retval =
+      Construct(ty.Of(interface), 42.f, Construct(ty.vec4<f32>()));
+  Func("vert_main", ast::VariableList{}, ty.Of(interface),
+       {Return(vert_retval)}, {Stage(ast::PipelineStage::kVertex)});
+
+  auto* frag_inputs = Param("inputs", ty.Of(interface));
+  Func("frag_main", ast::VariableList{frag_inputs}, ty.f32(),
+       {Return(MemberAccessor(Expr("inputs"), "value"))},
+       {Stage(ast::PipelineStage::kFragment)},
+       {Builtin(ast::Builtin::kFragDepth)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "vert_main" %1 %5
+OpEntryPoint Fragment %32 "frag_main" %9 %11 %13
+OpExecutionMode %32 OriginUpperLeft
+OpExecutionMode %32 DepthReplacing
+OpName %1 "value_1"
+OpName %5 "pos_1"
+OpName %9 "value_2"
+OpName %11 "pos_2"
+OpName %13 "value_3"
+OpName %15 "Interface"
+OpMemberName %15 0 "value"
+OpMemberName %15 1 "pos"
+OpName %16 "vert_main_inner"
+OpName %22 "vert_main"
+OpName %28 "frag_main_inner"
+OpName %29 "inputs"
+OpName %32 "frag_main"
+OpDecorate %1 Location 1
+OpDecorate %5 BuiltIn Position
+OpDecorate %9 Location 1
+OpDecorate %11 BuiltIn FragCoord
+OpDecorate %13 BuiltIn FragDepth
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 1 Offset 16
+%3 = OpTypeFloat 32
+%2 = OpTypePointer Output %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Output %4
+%7 = OpTypeVector %3 4
+%6 = OpTypePointer Output %7
+%8 = OpConstantNull %7
+%5 = OpVariable %6 Output %8
+%10 = OpTypePointer Input %3
+%9 = OpVariable %10 Input
+%12 = OpTypePointer Input %7
+%11 = OpVariable %12 Input
+%13 = OpVariable %2 Output %4
+%15 = OpTypeStruct %3 %7
+%14 = OpTypeFunction %15
+%18 = OpConstant %3 42
+%19 = OpConstantComposite %15 %18 %8
+%21 = OpTypeVoid
+%20 = OpTypeFunction %21
+%27 = OpTypeFunction %3 %15
+%16 = OpFunction %15 None %14
+%17 = OpLabel
+OpReturnValue %19
+OpFunctionEnd
+%22 = OpFunction %21 None %20
+%23 = OpLabel
+%24 = OpFunctionCall %15 %16
+%25 = OpCompositeExtract %3 %24 0
+OpStore %1 %25
+%26 = OpCompositeExtract %7 %24 1
+OpStore %5 %26
+OpReturn
+OpFunctionEnd
+%28 = OpFunction %3 None %27
+%29 = OpFunctionParameter %15
+%30 = OpLabel
+%31 = OpCompositeExtract %3 %29 0
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %21 None %20
+%33 = OpLabel
+%35 = OpLoad %3 %9
+%36 = OpLoad %7 %11
+%37 = OpCompositeConstruct %15 %35 %36
+%34 = OpFunctionCall %3 %28 %37
+OpStore %13 %34
+OpReturn
+OpFunctionEnd
+)");
+
+  Validate(b);
+}
+
+TEST_F(BuilderTest, SampleIndex_SampleRateShadingCapability) {
+  Func("main",
+       {Param("sample_index", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})},
+       ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build()) << b.error();
+
+  // Make sure we generate the SampleRateShading capability.
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            "OpCapability Shader\n"
+            "OpCapability SampleRateShading\n");
+  EXPECT_EQ(DumpInstructions(b.annots()), "OpDecorate %1 BuiltIn SampleId\n");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_format_conversion_test.cc b/src/tint/writer/spirv/builder_format_conversion_test.cc
new file mode 100644
index 0000000..7ff824f
--- /dev/null
+++ b/src/tint/writer/spirv/builder_format_conversion_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+struct TestData {
+  ast::TexelFormat ast_format;
+  SpvImageFormat_ spv_format;
+  bool extended_format = false;
+};
+inline std::ostream& operator<<(std::ostream& out, TestData data) {
+  out << data.ast_format;
+  return out;
+}
+using ImageFormatConversionTest = TestParamHelper<TestData>;
+
+TEST_P(ImageFormatConversionTest, ImageFormatConversion) {
+  auto param = GetParam();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.convert_texel_format_to_spv(param.ast_format), param.spv_format);
+
+  if (param.extended_format) {
+    EXPECT_EQ(DumpInstructions(b.capabilities()),
+              R"(OpCapability StorageImageExtendedFormats
+)");
+  } else {
+    EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    ImageFormatConversionTest,
+    testing::Values(
+        /* WGSL unsupported formats
+  TestData{ast::TexelFormat::kR8Unorm, SpvImageFormatR8, true},
+  TestData{ast::TexelFormat::kR8Snorm, SpvImageFormatR8Snorm, true},
+  TestData{ast::TexelFormat::kR8Uint, SpvImageFormatR8ui, true},
+  TestData{ast::TexelFormat::kR8Sint, SpvImageFormatR8i, true},
+  TestData{ast::TexelFormat::kR16Uint, SpvImageFormatR16ui, true},
+  TestData{ast::TexelFormat::kR16Sint, SpvImageFormatR16i, true},
+  TestData{ast::TexelFormat::kR16Float, SpvImageFormatR16f, true},
+  TestData{ast::TexelFormat::kRg8Unorm, SpvImageFormatRg8, true},
+  TestData{ast::TexelFormat::kRg8Snorm, SpvImageFormatRg8Snorm, true},
+  TestData{ast::TexelFormat::kRg8Uint, SpvImageFormatRg8ui, true},
+  TestData{ast::TexelFormat::kRg8Sint, SpvImageFormatRg8i, true},
+  TestData{ast::TexelFormat::kRg16Uint, SpvImageFormatRg16ui, true},
+  TestData{ast::TexelFormat::kRg16Sint, SpvImageFormatRg16i, true},
+  TestData{ast::TexelFormat::kRg16Float, SpvImageFormatRg16f, true},
+  TestData{ast::TexelFormat::kRgba8UnormSrgb, SpvImageFormatUnknown},
+  TestData{ast::TexelFormat::kBgra8Unorm, SpvImageFormatUnknown},
+  TestData{ast::TexelFormat::kBgra8UnormSrgb, SpvImageFormatUnknown},
+  TestData{ast::TexelFormat::kRgb10A2Unorm, SpvImageFormatRgb10A2, true},
+  TestData{ast::TexelFormat::kRg11B10Float, SpvImageFormatR11fG11fB10f, true},
+*/
+        TestData{ast::TexelFormat::kR32Uint, SpvImageFormatR32ui},
+        TestData{ast::TexelFormat::kR32Sint, SpvImageFormatR32i},
+        TestData{ast::TexelFormat::kR32Float, SpvImageFormatR32f},
+        TestData{ast::TexelFormat::kRgba8Unorm, SpvImageFormatRgba8},
+        TestData{ast::TexelFormat::kRgba8Snorm, SpvImageFormatRgba8Snorm},
+        TestData{ast::TexelFormat::kRgba8Uint, SpvImageFormatRgba8ui},
+        TestData{ast::TexelFormat::kRgba8Sint, SpvImageFormatRgba8i},
+        TestData{ast::TexelFormat::kRg32Uint, SpvImageFormatRg32ui, true},
+        TestData{ast::TexelFormat::kRg32Sint, SpvImageFormatRg32i, true},
+        TestData{ast::TexelFormat::kRg32Float, SpvImageFormatRg32f, true},
+        TestData{ast::TexelFormat::kRgba16Uint, SpvImageFormatRgba16ui},
+        TestData{ast::TexelFormat::kRgba16Sint, SpvImageFormatRgba16i},
+        TestData{ast::TexelFormat::kRgba16Float, SpvImageFormatRgba16f},
+        TestData{ast::TexelFormat::kRgba32Uint, SpvImageFormatRgba32ui},
+        TestData{ast::TexelFormat::kRgba32Sint, SpvImageFormatRgba32i},
+        TestData{ast::TexelFormat::kRgba32Float, SpvImageFormatRgba32f}));
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
new file mode 100644
index 0000000..3ddb7eb
--- /dev/null
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -0,0 +1,272 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Attribute_Stage) {
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kFragment),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.entry_points()),
+            R"(OpEntryPoint Fragment %3 "main"
+)");
+}
+
+struct FunctionStageData {
+  ast::PipelineStage stage;
+  SpvExecutionModel model;
+};
+inline std::ostream& operator<<(std::ostream& out, FunctionStageData data) {
+  out << data.stage;
+  return out;
+}
+using Attribute_StageTest = TestParamHelper<FunctionStageData>;
+TEST_P(Attribute_StageTest, Emit) {
+  auto params = GetParam();
+
+  const ast::Variable* var = nullptr;
+  const ast::Type* ret_type = nullptr;
+  ast::AttributeList ret_type_attrs;
+  ast::StatementList body;
+  if (params.stage == ast::PipelineStage::kVertex) {
+    ret_type = ty.vec4<f32>();
+    ret_type_attrs.push_back(Builtin(ast::Builtin::kPosition));
+    body.push_back(Return(Construct(ty.vec4<f32>())));
+  } else {
+    ret_type = ty.void_();
+  }
+
+  auto deco_list = ast::AttributeList{Stage(params.stage)};
+  if (params.stage == ast::PipelineStage::kCompute) {
+    deco_list.push_back(WorkgroupSize(1));
+  }
+
+  auto* func = Func("main", {}, ret_type, body, deco_list, ret_type_attrs);
+
+  spirv::Builder& b = Build();
+
+  if (var) {
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  }
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  auto preamble = b.entry_points();
+  ASSERT_GE(preamble.size(), 1u);
+  EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint);
+
+  ASSERT_GE(preamble[0].operands().size(), 3u);
+  EXPECT_EQ(preamble[0].operands()[0].to_i(),
+            static_cast<uint32_t>(params.model));
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    Attribute_StageTest,
+    testing::Values(FunctionStageData{ast::PipelineStage::kVertex,
+                                      SpvExecutionModelVertex},
+                    FunctionStageData{ast::PipelineStage::kFragment,
+                                      SpvExecutionModelFragment},
+                    FunctionStageData{ast::PipelineStage::kCompute,
+                                      SpvExecutionModelGLCompute}));
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_Fragment_OriginUpperLeft) {
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kFragment),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()),
+            R"(OpExecutionMode %3 OriginUpperLeft
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Default) {
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                                       WorkgroupSize(1)});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()),
+            R"(OpExecutionMode %3 LocalSize 1 1 1
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Literals) {
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        WorkgroupSize(2, 4, 6),
+                        Stage(ast::PipelineStage::kCompute),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()),
+            R"(OpExecutionMode %3 LocalSize 2 4 6
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Const) {
+  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
+  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
+  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        WorkgroupSize("width", "height", "depth"),
+                        Stage(ast::PipelineStage::kCompute),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()),
+            R"(OpExecutionMode %3 LocalSize 2 3 4
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_OverridableConst) {
+  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
+  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
+  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        WorkgroupSize("width", "height", "depth"),
+                        Stage(ast::PipelineStage::kCompute),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()), "");
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 0
+%1 = OpTypeVector %2 3
+%4 = OpSpecConstant %2 2
+%5 = OpSpecConstant %2 3
+%6 = OpSpecConstant %2 4
+%3 = OpSpecConstantComposite %1 %4 %5 %6
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()),
+            R"(OpDecorate %4 SpecId 7
+OpDecorate %5 SpecId 8
+OpDecorate %6 SpecId 9
+OpDecorate %3 BuiltIn WorkgroupSize
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) {
+  Override("height", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
+  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 3));
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::AttributeList{
+                        WorkgroupSize(4, "height", "depth"),
+                        Stage(ast::PipelineStage::kCompute),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.execution_modes()), "");
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 0
+%1 = OpTypeVector %2 3
+%4 = OpConstant %2 4
+%5 = OpSpecConstant %2 2
+%6 = OpConstant %2 3
+%3 = OpSpecConstantComposite %1 %4 %5 %6
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()),
+            R"(OpDecorate %5 SpecId 7
+OpDecorate %3 BuiltIn WorkgroupSize
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_MultipleFragment) {
+  auto* func1 = Func("main1", {}, ty.void_(), ast::StatementList{},
+                     ast::AttributeList{
+                         Stage(ast::PipelineStage::kFragment),
+                     });
+
+  auto* func2 = Func("main2", {}, ty.void_(), ast::StatementList{},
+                     ast::AttributeList{
+                         Stage(ast::PipelineStage::kFragment),
+                     });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func1)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func2)) << b.error();
+  EXPECT_EQ(DumpBuilder(b),
+            R"(OpEntryPoint Fragment %3 "main1"
+OpEntryPoint Fragment %5 "main2"
+OpExecutionMode %3 OriginUpperLeft
+OpExecutionMode %5 OriginUpperLeft
+OpName %3 "main1"
+OpName %5 "main2"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+%5 = OpFunction %2 None %1
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Decoration_ExecutionMode_FragDepth) {
+  Func("main", ast::VariableList{}, ty.f32(),
+       ast::StatementList{
+           Return(Expr(1.f)),
+       },
+       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
+       ast::AttributeList{
+           Builtin(ast::Builtin::kFragDepth),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.execution_modes()),
+            R"(OpExecutionMode %11 OriginUpperLeft
+OpExecutionMode %11 DepthReplacing
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_function_test.cc b/src/tint/writer/spirv/builder_function_test.cc
new file mode 100644
index 0000000..ec94bda
--- /dev/null
+++ b/src/tint/writer/spirv/builder_function_test.cc
@@ -0,0 +1,292 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Function_Empty) {
+  Func("a_func", {}, ty.void_(), ast::StatementList{}, ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Function_Terminator_Return) {
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Function_Terminator_ReturnValue) {
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  Func("a_func", {}, ty.f32(), ast::StatementList{Return("a")},
+       ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* var_a = program->AST().GlobalVariables()[0];
+  auto* func = program->AST().Functions()[0];
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "a"
+OpName %6 "a_func"
+%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpTypeFunction %3
+%6 = OpFunction %3 None %5
+%7 = OpLabel
+%8 = OpLoad %3 %1
+OpReturnValue %8
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Function_Terminator_Discard) {
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           create<ast::DiscardStatement>(),
+       },
+       ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpKill
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Function_WithParams) {
+  ast::VariableList params = {Param("a", ty.f32()), Param("b", ty.i32())};
+
+  Func("a_func", params, ty.f32(), ast::StatementList{Return("a")},
+       ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %4 "a_func"
+OpName %5 "a"
+OpName %6 "b"
+%2 = OpTypeFloat 32
+%3 = OpTypeInt 32 1
+%1 = OpTypeFunction %2 %2 %3
+%4 = OpFunction %2 None %1
+%5 = OpFunctionParameter %2
+%6 = OpFunctionParameter %3
+%7 = OpLabel
+OpReturnValue %5
+OpFunctionEnd
+)") << DumpBuilder(b);
+}
+
+TEST_F(BuilderTest, Function_WithBody) {
+  Func("a_func", {}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, FunctionType) {
+  Func("a_func", {}, ty.void_(), ast::StatementList{}, ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  auto* func = program->AST().Functions()[0];
+  ASSERT_TRUE(b.GenerateFunction(func));
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+)");
+}
+
+TEST_F(BuilderTest, FunctionType_DeDuplicate) {
+  auto* func1 = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                     ast::AttributeList{});
+  auto* func2 = Func("b_func", {}, ty.void_(), ast::StatementList{},
+                     ast::AttributeList{});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateFunction(func1));
+  ASSERT_TRUE(b.GenerateFunction(func2));
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+)");
+}
+
+// https://crbug.com/tint/297
+TEST_F(BuilderTest, Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
+  // [[block]] struct Data {
+  //   d : f32;
+  // };
+  // @binding(0) @group(0) var<storage> data : Data;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn a() {
+  //   return;
+  // }
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn b() {
+  //   return;
+  // }
+
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("a", ast::VariableList{}, ty.void_(),
+         ast::StatementList{
+             Decl(var),
+             Return(),
+         },
+         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                            WorkgroupSize(1)});
+  }
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("b", ast::VariableList{}, ty.void_(),
+         ast::StatementList{
+             Decl(var),
+             Return(),
+         },
+         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
+                            WorkgroupSize(1)});
+  }
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %7 "a"
+OpEntryPoint GLCompute %17 "b"
+OpExecutionMode %7 LocalSize 1 1 1
+OpExecutionMode %17 LocalSize 1 1 1
+OpName %3 "Data"
+OpMemberName %3 0 "d"
+OpName %1 "data"
+OpName %7 "a"
+OpName %14 "v"
+OpName %17 "b"
+OpName %21 "v"
+OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+%4 = OpTypeFloat 32
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+%9 = OpTypeInt 32 0
+%10 = OpConstant %9 0
+%11 = OpTypePointer StorageBuffer %4
+%15 = OpTypePointer Function %4
+%16 = OpConstantNull %4
+%7 = OpFunction %6 None %5
+%8 = OpLabel
+%14 = OpVariable %15 Function %16
+%12 = OpAccessChain %11 %1 %10
+%13 = OpLoad %4 %12
+OpStore %14 %13
+OpReturn
+OpFunctionEnd
+%17 = OpFunction %6 None %5
+%18 = OpLabel
+%21 = OpVariable %15 Function %16
+%19 = OpAccessChain %11 %1 %10
+%20 = OpLoad %4 %19
+OpStore %21 %20
+OpReturn
+OpFunctionEnd
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_function_variable_test.cc b/src/tint/writer/spirv/builder_function_variable_test.cc
new file mode 100644
index 0000000..fd64ea9
--- /dev/null
+++ b/src/tint/writer/spirv/builder_function_variable_test.cc
@@ -0,0 +1,199 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, FunctionVar_NoStorageClass) {
+  auto* v = Var("var", ty.f32(), ast::StorageClass::kFunction);
+  WrapInFunction(v);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+)");
+
+  const auto& func = b.functions()[0];
+  EXPECT_EQ(DumpInstructions(func.variables()),
+            R"(%1 = OpVariable %2 Function %4
+)");
+}
+
+TEST_F(BuilderTest, FunctionVar_WithConstantConstructor) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+  auto* v = Var("var", ty.vec3<f32>(), ast::StorageClass::kFunction, init);
+  WrapInFunction(v);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+%7 = OpTypePointer Function %1
+%8 = OpConstantNull %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%6 = OpVariable %7 Function %8
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %6 %5
+)");
+}
+
+TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructor) {
+  auto* init = vec2<f32>(1.f, Add(3.f, 3.f));
+
+  auto* v = Var("var", ty.vec2<f32>(), ast::StorageClass::kNone, init);
+  WrapInFunction(v);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %7 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%8 = OpTypePointer Function %1
+%9 = OpConstantNull %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%7 = OpVariable %8 Function %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%5 = OpFAdd %2 %4 %4
+%6 = OpCompositeConstruct %1 %3 %5
+OpStore %7 %6
+)");
+}
+
+TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructorLoadedFromVar) {
+  // var v : f32 = 1.0;
+  // var v2 : f32 = v; // Should generate the load and store automatically.
+
+  auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1.f));
+
+  auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
+  WrapInFunction(v, v2);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
+OpName %7 "v2"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 1
+%4 = OpTypePointer Function %1
+%5 = OpConstantNull %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%3 = OpVariable %4 Function %5
+%7 = OpVariable %4 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %3 %2
+%6 = OpLoad %1 %3
+OpStore %7 %6
+)");
+}
+
+TEST_F(BuilderTest, FunctionVar_ConstWithVarInitializer) {
+  // var v : f32 = 1.0;
+  // let v2 : f32 = v; // Should generate the load
+
+  auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1.f));
+
+  auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
+  WrapInFunction(v, v2);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
+OpName %7 "v2"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 1
+%4 = OpTypePointer Function %1
+%5 = OpConstantNull %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%3 = OpVariable %4 Function %5
+%7 = OpVariable %4 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpStore %3 %2
+%6 = OpLoad %1 %3
+OpStore %7 %6
+)");
+}
+
+TEST_F(BuilderTest, FunctionVar_Const) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* v = Const("var", ty.vec3<f32>(), init);
+
+  WrapInFunction(v);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
new file mode 100644
index 0000000..8fa7e75
--- /dev/null
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -0,0 +1,628 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, GlobalVar_WithStorageClass) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_WithConstructor) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate, init);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+%7 = OpTypePointer Private %1
+%6 = OpVariable %7 Private %5
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Const) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %5 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Complex_Constructor) {
+  auto* init = vec3<f32>(ast::ExpressionList{Expr(1.f), Expr(2.f), Expr(3.f)});
+
+  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 2
+%5 = OpConstant %2 3
+%6 = OpConstantComposite %1 %3 %4 %5
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Complex_ConstructorWithExtract) {
+  auto* init = vec3<f32>(vec2<f32>(1.f, 2.f), 3.f);
+
+  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 1
+%5 = OpConstant %2 2
+%6 = OpConstantComposite %3 %4 %5
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 0
+%7 = OpSpecConstantOp %2 CompositeExtract %6 9
+%11 = OpConstant %8 1
+%10 = OpSpecConstantOp %2 CompositeExtract %6 11
+%12 = OpConstant %2 3
+%13 = OpSpecConstantComposite %1 %7 %10 %12
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
+  auto* v = Global("var", ty.sampler(ast::SamplerKind::kSampler),
+                   ast::StorageClass::kNone, nullptr,
+                   ast::AttributeList{
+                       create<ast::BindingAttribute>(2),
+                       create<ast::GroupAttribute>(3),
+                   });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2
+OpDecorate %1 DescriptorSet 3
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeSampler
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Bool) {
+  auto* v = Override("var", ty.bool_(), Expr(true),
+                     ast::AttributeList{
+                         Id(1200),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpSpecConstantTrue %1
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Bool_ZeroValue) {
+  auto* v = Override("var", ty.bool_(), Construct<bool>(),
+                     ast::AttributeList{
+                         Id(1200),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpSpecConstantFalse %1
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Bool_NoConstructor) {
+  auto* v = Override("var", ty.bool_(), nullptr,
+                     ast::AttributeList{
+                         Id(1200),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpSpecConstantFalse %1
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Scalar) {
+  auto* v = Override("var", ty.f32(), Expr(2.f),
+                     ast::AttributeList{
+                         Id(0),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpSpecConstant %1 2
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Scalar_ZeroValue) {
+  auto* v = Override("var", ty.f32(), Construct<f32>(),
+                     ast::AttributeList{
+                         Id(0),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpSpecConstant %1 0
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Scalar_F32_NoConstructor) {
+  auto* v = Override("var", ty.f32(), nullptr,
+                     ast::AttributeList{
+                         Id(0),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpSpecConstant %1 0
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Scalar_I32_NoConstructor) {
+  auto* v = Override("var", ty.i32(), nullptr,
+                     ast::AttributeList{
+                         Id(0),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpSpecConstant %1 0
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_Scalar_U32_NoConstructor) {
+  auto* v = Override("var", ty.u32(), nullptr,
+                     ast::AttributeList{
+                         Id(0),
+                     });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpSpecConstant %1 0
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_Override_NoId) {
+  auto* var_a = Override("a", ty.bool_(), Expr(true),
+                         ast::AttributeList{
+                             Id(0),
+                         });
+  auto* var_b = Override("b", ty.bool_(), Expr(false));
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "a"
+OpName %3 "b"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
+OpDecorate %3 SpecId 1
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpSpecConstantTrue %1
+%3 = OpSpecConstantFalse %1
+)");
+}
+
+struct BuiltinData {
+  ast::Builtin builtin;
+  ast::StorageClass storage;
+  SpvBuiltIn result;
+};
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+  out << data.builtin;
+  return out;
+}
+using BuiltinDataTest = TestParamHelper<BuiltinData>;
+TEST_P(BuiltinDataTest, Convert) {
+  auto params = GetParam();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.ConvertBuiltin(params.builtin, params.storage), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest_Type,
+    BuiltinDataTest,
+    testing::Values(
+        BuiltinData{ast::Builtin::kNone, ast::StorageClass::kNone,
+                    SpvBuiltInMax},
+        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kInput,
+                    SpvBuiltInFragCoord},
+        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kOutput,
+                    SpvBuiltInPosition},
+        BuiltinData{
+            ast::Builtin::kVertexIndex,
+            ast::StorageClass::kInput,
+            SpvBuiltInVertexIndex,
+        },
+        BuiltinData{ast::Builtin::kInstanceIndex, ast::StorageClass::kInput,
+                    SpvBuiltInInstanceIndex},
+        BuiltinData{ast::Builtin::kFrontFacing, ast::StorageClass::kInput,
+                    SpvBuiltInFrontFacing},
+        BuiltinData{ast::Builtin::kFragDepth, ast::StorageClass::kOutput,
+                    SpvBuiltInFragDepth},
+        BuiltinData{ast::Builtin::kLocalInvocationId, ast::StorageClass::kInput,
+                    SpvBuiltInLocalInvocationId},
+        BuiltinData{ast::Builtin::kLocalInvocationIndex,
+                    ast::StorageClass::kInput, SpvBuiltInLocalInvocationIndex},
+        BuiltinData{ast::Builtin::kGlobalInvocationId,
+                    ast::StorageClass::kInput, SpvBuiltInGlobalInvocationId},
+        BuiltinData{ast::Builtin::kWorkgroupId, ast::StorageClass::kInput,
+                    SpvBuiltInWorkgroupId},
+        BuiltinData{ast::Builtin::kNumWorkgroups, ast::StorageClass::kInput,
+                    SpvBuiltInNumWorkgroups},
+        BuiltinData{ast::Builtin::kSampleIndex, ast::StorageClass::kInput,
+                    SpvBuiltInSampleId},
+        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kInput,
+                    SpvBuiltInSampleMask},
+        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kOutput,
+                    SpvBuiltInSampleMask}));
+
+TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
+  // struct A {
+  //   a : i32;
+  // };
+  // var b<storage, read> : A
+
+  auto* A = Structure("A",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.i32()),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 1 Offset 4
+OpDecorate %1 NonWritable
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
+OpMemberName %3 0 "a"
+OpMemberName %3 1 "b"
+OpName %1 "b"
+OpName %7 "unused_entry_point"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4 %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) {
+  // struct A {
+  //   a : i32;
+  // };
+  // type B = A;
+  // var b<storage, read> : B
+
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* B = Alias("B", ty.Of(A));
+  Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %1 NonWritable
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
+OpMemberName %3 0 "a"
+OpName %1 "b"
+OpName %7 "unused_entry_point"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) {
+  // struct A {
+  //   a : i32;
+  // };
+  // type B = A;
+  // var<storage, read> b : B
+
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* B = Alias("B", ty.Of(A));
+  Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %1 NonWritable
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
+OpMemberName %3 0 "a"
+OpName %1 "b"
+OpName %7 "unused_entry_point"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) {
+  // struct A {
+  //   a : i32;
+  // };
+  // var<storage, read> b : A
+  // var<storage, read_write> c : A
+
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::GroupAttribute>(0),
+             create<ast::BindingAttribute>(0),
+         });
+  Global("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::GroupAttribute>(1),
+             create<ast::BindingAttribute>(0),
+         });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+
+  EXPECT_EQ(DumpInstructions(b.annots()),
+            R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %1 NonWritable
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+OpDecorate %5 DescriptorSet 1
+OpDecorate %5 Binding 0
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
+OpMemberName %3 0 "a"
+OpName %1 "b"
+OpName %5 "c"
+OpName %8 "unused_entry_point"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%5 = OpVariable %2 StorageBuffer
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
+  // var<uniform_constant> a : texture_storage_2d<r32uint, write>;
+
+  auto* type =
+      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
+                         ast::Access::kWrite);
+
+  auto* var_a = Global("a", type,
+                       ast::AttributeList{
+                           create<ast::BindingAttribute>(0),
+                           create<ast::GroupAttribute>(0),
+                       });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 2D 0 0 0 2 R32ui
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+)");
+}
+
+// Check that multiple texture_storage types with different access modifiers
+// only produces a single OpTypeImage.
+// Test disabled as storage textures currently only support 'write' access. In
+// the future we'll likely support read_write.
+TEST_F(BuilderTest, DISABLED_GlobalVar_TextureStorageWithDifferentAccess) {
+  // var<uniform_constant> a : texture_storage_2d<r32uint, read_write>;
+  // var<uniform_constant> b : texture_storage_2d<r32uint, write>;
+
+  auto* type_a =
+      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
+                         ast::Access::kReadWrite);
+  auto* var_a = Global("a", type_a, ast::StorageClass::kNone,
+                       ast::AttributeList{
+                           create<ast::BindingAttribute>(0),
+                           create<ast::GroupAttribute>(0),
+                       });
+
+  auto* type_b =
+      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
+                         ast::Access::kWrite);
+  auto* var_b = Global("b", type_b, ast::StorageClass::kNone,
+                       ast::AttributeList{
+                           create<ast::BindingAttribute>(1),
+                           create<ast::GroupAttribute>(0),
+                       });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable
+OpDecorate %1 Binding 0
+OpDecorate %1 DescriptorSet 0
+OpDecorate %5 NonReadable
+OpDecorate %5 Binding 1
+OpDecorate %5 DescriptorSet 0
+)");
+  // There must only be one OpTypeImage declaration with the same
+  // arguments
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+%3 = OpTypeImage %4 2D 0 0 0 2 R32ui
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%6 = OpTypePointer UniformConstant %3
+%5 = OpVariable %6 UniformConstant
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_ident_expression_test.cc b/src/tint/writer/spirv/builder_ident_expression_test.cc
new file mode 100644
index 0000000..dfaf9f6
--- /dev/null
+++ b/src/tint/writer/spirv/builder_ident_expression_test.cc
@@ -0,0 +1,165 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, IdentifierExpression_GlobalConst) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
+
+  auto* expr = Expr("var");
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
+}
+
+TEST_F(BuilderTest, IdentifierExpression_GlobalVar) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Expr("var");
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+)");
+
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
+}
+
+TEST_F(BuilderTest, IdentifierExpression_FunctionConst) {
+  auto* init = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* v = Const("var", ty.vec3<f32>(), init);
+
+  auto* expr = Expr("var");
+  WrapInFunction(v, expr);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
+}
+
+TEST_F(BuilderTest, IdentifierExpression_FunctionVar) {
+  auto* v = Var("var", ty.f32(), ast::StorageClass::kFunction);
+  auto* expr = Expr("var");
+  WrapInFunction(v, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+)");
+
+  const auto& func = b.functions()[0];
+  EXPECT_EQ(DumpInstructions(func.variables()),
+            R"(%1 = OpVariable %2 Function %4
+)");
+
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
+}
+
+TEST_F(BuilderTest, IdentifierExpression_Load) {
+  auto* var = Global("var", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* expr = Add("var", "var");
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 7u)
+      << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%5 = OpLoad %3 %1
+%6 = OpLoad %3 %1
+%7 = OpIAdd %3 %5 %6
+)");
+}
+
+TEST_F(BuilderTest, IdentifierExpression_NoLoadConst) {
+  auto* var = GlobalConst("var", ty.i32(), Expr(2));
+
+  auto* expr = Add("var", "var");
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 3u)
+      << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstant %1 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%3 = OpIAdd %1 %2 %2
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_if_test.cc b/src/tint/writer/spirv/builder_if_test.cc
new file mode 100644
index 0000000..25ec9e8
--- /dev/null
+++ b/src/tint/writer/spirv/builder_if_test.cc
@@ -0,0 +1,673 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, If_Empty) {
+  // if (true) {
+  // }
+  auto* expr = If(true, Block());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %3 None
+OpBranchConditional %2 %4 %3
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_Empty_OutsideFunction_IsError) {
+  // Outside a function.
+  // if (true) {
+  // }
+
+  auto* block = Block();
+  auto* expr = If(true, block);
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_FALSE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_TRUE(b.has_error());
+  EXPECT_EQ(b.error(),
+            "Internal error: trying to add SPIR-V instruction 247 outside a "
+            "function");
+}
+
+TEST_F(BuilderTest, If_WithStatements) {
+  // if (true) {
+  //   v = 2;
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2));
+  auto* expr = If(true, body);
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%9 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %7
+%8 = OpLabel
+OpStore %1 %9
+OpBranch %7
+%7 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithElse) {
+  // if (true) {
+  //   v = 2;
+  // } else {
+  //   v = 3;
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2));
+  auto* else_body = Block(Assign("v", 3));
+
+  auto* expr = If(true, body, Else(else_body));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%10 = OpConstant %3 2
+%11 = OpConstant %3 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpStore %1 %10
+OpBranch %7
+%9 = OpLabel
+OpStore %1 %11
+OpBranch %7
+%7 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithElseIf) {
+  // if (true) {
+  //   v = 2;
+  // } else if (true) {
+  //   v = 3;
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2));
+  auto* else_body = Block(Assign("v", 3));
+
+  auto* expr = If(true, body, Else(true, else_body));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%10 = OpConstant %3 2
+%13 = OpConstant %3 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpStore %1 %10
+OpBranch %7
+%9 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %6 %12 %11
+%12 = OpLabel
+OpStore %1 %13
+OpBranch %11
+%11 = OpLabel
+OpBranch %7
+%7 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithMultiple) {
+  // if (true) {
+  //   v = 2;
+  // } else if (true) {
+  //   v = 3;
+  // } else if (false) {
+  //   v = 4;
+  // } else {
+  //   v = 5;
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2));
+  auto* elseif_1_body = Block(Assign("v", 3));
+  auto* elseif_2_body = Block(Assign("v", 4));
+  auto* else_body = Block(Assign("v", 5));
+
+  auto* expr = If(true, body,                  //
+                  Else(true, elseif_1_body),   //
+                  Else(false, elseif_2_body),  //
+                  Else(else_body));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%10 = OpConstant %3 2
+%14 = OpConstant %3 3
+%15 = OpConstantFalse %5
+%19 = OpConstant %3 4
+%20 = OpConstant %3 5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpStore %1 %10
+OpBranch %7
+%9 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %6 %12 %13
+%12 = OpLabel
+OpStore %1 %14
+OpBranch %11
+%13 = OpLabel
+OpSelectionMerge %16 None
+OpBranchConditional %15 %17 %18
+%17 = OpLabel
+OpStore %1 %19
+OpBranch %16
+%18 = OpLabel
+OpStore %1 %20
+OpBranch %16
+%16 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranch %7
+%7 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithBreak) {
+  // loop {
+  //   if (true) {
+  //     break;
+  //   }
+  // }
+
+  auto* if_body = Block(Break());
+
+  auto* if_stmt = If(true, if_body);
+
+  auto* loop_body = Block(if_stmt);
+
+  auto* expr = Loop(loop_body, Block());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %7
+%8 = OpLabel
+OpBranch %2
+%7 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithElseBreak) {
+  // loop {
+  //   if (true) {
+  //   } else {
+  //     break;
+  //   }
+  // }
+  auto* else_body = Block(Break());
+
+  auto* if_stmt = If(true, Block(), Else(else_body));
+
+  auto* loop_body = Block(if_stmt);
+
+  auto* expr = Loop(loop_body, Block());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpBranch %7
+%9 = OpLabel
+OpBranch %2
+%7 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithContinueAndBreak) {
+  // loop {
+  //   if (true) {
+  //     continue;
+  //   } else {
+  //     break;
+  //   }
+  // }
+
+  auto* if_stmt = If(true, Block(Continue()), Else(Block(Break())));
+
+  auto* expr = Loop(Block(if_stmt), Block());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpBranch %3
+%9 = OpLabel
+OpBranch %2
+%7 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithElseContinue) {
+  // loop {
+  //   if (true) {
+  //   } else {
+  //     continue;
+  //   }
+  //   break;
+  // }
+  auto* else_body = Block(create<ast::ContinueStatement>());
+
+  auto* if_stmt = If(true, Block(), Else(else_body));
+
+  auto* loop_body = Block(if_stmt, Break());
+
+  auto* expr = Loop(loop_body, Block());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpBranch %7
+%9 = OpLabel
+OpBranch %3
+%7 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, If_WithReturn) {
+  // if (true) {
+  //   return;
+  // }
+
+  auto* fn = Func("f", {}, ty.void_(), {If(true, Block(Return()))});
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %7
+%8 = OpLabel
+OpReturn
+%7 = OpLabel
+OpReturn
+)");
+}
+
+TEST_F(BuilderTest, If_WithReturnValue) {
+  // if (true) {
+  //   return false;
+  // }
+  // return true;
+
+  auto* fn = Func("f", {}, ty.bool_(),
+                  {
+                      If(true, Block(Return(false))),
+                      Return(true),
+                  });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeFunction %2
+%5 = OpConstantTrue %2
+%8 = OpConstantFalse %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %6 None
+OpBranchConditional %5 %7 %6
+%7 = OpLabel
+OpReturnValue %8
+%6 = OpLabel
+OpReturnValue %5
+)");
+}
+
+TEST_F(BuilderTest, IfElse_BothReturn) {
+  // if (true) {
+  //   return true;
+  // } else {
+  //   return true;
+  // }
+
+  auto* fn = Func("f", {}, ty.bool_(),
+                  {
+                      If(true,                 //
+                         Block(Return(true)),  //
+                         Else(Block(Return(true)))),
+                  });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeFunction %2
+%5 = OpConstantTrue %2
+%9 = OpConstantNull %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %6 None
+OpBranchConditional %5 %7 %8
+%7 = OpLabel
+OpReturnValue %5
+%8 = OpLabel
+OpReturnValue %5
+%6 = OpLabel
+OpReturnValue %9
+)");
+}
+
+TEST_F(BuilderTest, If_WithNestedBlockReturnValue) {
+  // if (true) {
+  //  {
+  //    {
+  //      {
+  //        return false;
+  //      }
+  //    }
+  //  }
+  // }
+  // return true;
+
+  auto* fn = Func("f", {}, ty.bool_(),
+                  {
+                      If(true, Block(Block(Block(Block(Return(false)))))),
+                      Return(true),
+                  });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeFunction %2
+%5 = OpConstantTrue %2
+%8 = OpConstantFalse %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %6 None
+OpBranchConditional %5 %7 %6
+%7 = OpLabel
+OpReturnValue %8
+%6 = OpLabel
+OpReturnValue %5
+)");
+}
+
+TEST_F(BuilderTest, If_WithLoad_Bug327) {
+  // var a : bool;
+  // if (a) {
+  // }
+
+  auto* var = Global("a", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* fn = Func("f", {}, ty.void_(), {If("a", Block())});
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%6 = OpTypeVoid
+%5 = OpTypeFunction %6
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%9 = OpLoad %3 %1
+OpSelectionMerge %10 None
+OpBranchConditional %9 %11 %10
+%11 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+)");
+}
+
+TEST_F(BuilderTest, If_ElseIf_WithReturn) {
+  // crbug.com/tint/1315
+  // if (false) {
+  // } else if (true) {
+  //   return;
+  // }
+
+  auto* if_stmt = If(false, Block(), Else(true, Block(Return())));
+  auto* fn = Func("f", {}, ty.void_(), {if_stmt});
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%5 = OpTypeBool
+%6 = OpConstantFalse %5
+%10 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %9
+%8 = OpLabel
+OpBranch %7
+%9 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %10 %12 %11
+%12 = OpLabel
+OpReturn
+%11 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpReturn
+)");
+}
+
+TEST_F(BuilderTest, Loop_If_ElseIf_WithBreak) {
+  // crbug.com/tint/1315
+  // loop {
+  //   if (false) {
+  //   } else if (true) {
+  //     break;
+  //   }
+  // }
+
+  auto* if_stmt = If(false, Block(), Else(true, Block(Break())));
+  auto* fn = Func("f", {}, ty.void_(), {Loop(Block(if_stmt))});
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%9 = OpTypeBool
+%10 = OpConstantFalse %9
+%14 = OpConstantTrue %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %10 %12 %13
+%12 = OpLabel
+OpBranch %11
+%13 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %14 %16 %15
+%16 = OpLabel
+OpBranch %6
+%15 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpBranch %5
+%6 = OpLabel
+OpReturn
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_literal_test.cc b/src/tint/writer/spirv/builder_literal_test.cc
new file mode 100644
index 0000000..1b68523
--- /dev/null
+++ b/src/tint/writer/spirv/builder_literal_test.cc
@@ -0,0 +1,168 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Literal_Bool_True) {
+  auto* b_true = create<ast::BoolLiteralExpression>(true);
+  WrapInFunction(b_true);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateLiteralIfNeeded(nullptr, b_true);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(2u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+)");
+}
+
+TEST_F(BuilderTest, Literal_Bool_False) {
+  auto* b_false = create<ast::BoolLiteralExpression>(false);
+  WrapInFunction(b_false);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateLiteralIfNeeded(nullptr, b_false);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(2u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantFalse %1
+)");
+}
+
+TEST_F(BuilderTest, Literal_Bool_Dedup) {
+  auto* b_true = create<ast::BoolLiteralExpression>(true);
+  auto* b_false = create<ast::BoolLiteralExpression>(false);
+  WrapInFunction(b_true, b_false);
+
+  spirv::Builder& b = Build();
+
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_true), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_false), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_true), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+%3 = OpConstantFalse %1
+)");
+}
+
+TEST_F(BuilderTest, Literal_I32) {
+  auto* i = create<ast::SintLiteralExpression>(-23);
+  WrapInFunction(i);
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(2u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstant %1 -23
+)");
+}
+
+TEST_F(BuilderTest, Literal_I32_Dedup) {
+  auto* i1 = create<ast::SintLiteralExpression>(-23);
+  auto* i2 = create<ast::SintLiteralExpression>(-23);
+  WrapInFunction(i1, i2);
+
+  spirv::Builder& b = Build();
+
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+%2 = OpConstant %1 -23
+)");
+}
+
+TEST_F(BuilderTest, Literal_U32) {
+  auto* i = create<ast::UintLiteralExpression>(23);
+  WrapInFunction(i);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(2u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstant %1 23
+)");
+}
+
+TEST_F(BuilderTest, Literal_U32_Dedup) {
+  auto* i1 = create<ast::UintLiteralExpression>(23);
+  auto* i2 = create<ast::UintLiteralExpression>(23);
+  WrapInFunction(i1, i2);
+
+  spirv::Builder& b = Build();
+
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+%2 = OpConstant %1 23
+)");
+}
+
+TEST_F(BuilderTest, Literal_F32) {
+  auto* i = create<ast::FloatLiteralExpression>(23.245f);
+  WrapInFunction(i);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(2u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 23.2450008
+)");
+}
+
+TEST_F(BuilderTest, Literal_F32_Dedup) {
+  auto* i1 = create<ast::FloatLiteralExpression>(23.245f);
+  auto* i2 = create<ast::FloatLiteralExpression>(23.245f);
+  WrapInFunction(i1, i2);
+
+  spirv::Builder& b = Build();
+
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
+  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 23.2450008
+)");
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_loop_test.cc b/src/tint/writer/spirv/builder_loop_test.cc
new file mode 100644
index 0000000..23a7ec4
--- /dev/null
+++ b/src/tint/writer/spirv/builder_loop_test.cc
@@ -0,0 +1,495 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Loop_Empty) {
+  // loop {
+  //   break;
+  // }
+
+  auto* loop = Loop(Block(Break()), Block());
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithoutContinuing) {
+  // loop {
+  //   v = 2;
+  //   break;
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2),  //
+                     Break());
+
+  auto* loop = Loop(body, Block());
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%9 = OpConstant %3 2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpStore %1 %9
+OpBranch %6
+%7 = OpLabel
+OpBranch %5
+%6 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing) {
+  // loop {
+  //   a = 2;
+  //   break;
+  //   continuing {
+  //     a = 3;
+  //   }
+  // }
+
+  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* body = Block(Assign("v", 2),  //
+                     Break());
+  auto* continuing = Block(Assign("v", 3));
+
+  auto* loop = Loop(body, continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%9 = OpConstant %3 2
+%10 = OpConstant %3 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpStore %1 %9
+OpBranch %6
+%7 = OpLabel
+OpStore %1 %10
+OpBranch %5
+%6 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithBodyVariableAccessInContinuing) {
+  // loop {
+  //   var a : i32;
+  //   break;
+  //   continuing {
+  //     a = 3;
+  //   }
+  // }
+
+  auto* body = Block(Decl(Var("a", ty.i32())),  //
+                     Break());
+  auto* continuing = Block(Assign("a", 3));
+
+  auto* loop = Loop(body, continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%7 = OpTypeInt 32 1
+%6 = OpTypePointer Function %7
+%8 = OpConstantNull %7
+%9 = OpConstant %7 3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpStore %5 %9
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinue) {
+  // loop {
+  //   if (false) { break; }
+  //   continue;
+  // }
+  auto* body = Block(If(false, Block(Break())),  //
+                     Continue());
+  auto* loop = Loop(body, Block());
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %6 %8 %7
+%8 = OpLabel
+OpBranch %2
+%7 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithBreak) {
+  // loop {
+  //   break;
+  // }
+  auto* body = Block(create<ast::BreakStatement>());
+  auto* loop = Loop(body, Block());
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakIf) {
+  // loop {
+  //   continuing {
+  //     if (true) { break; }
+  //   }
+  // }
+
+  auto* if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
+                                           ast::ElseStatementList{});
+  auto* continuing = Block(if_stmt);
+  auto* loop = Loop(Block(), continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranchConditional %6 %2 %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless) {
+  // loop {
+  //   continuing {
+  //     if (true) {} else { break; }
+  //   }
+  // }
+  auto* if_stmt = create<ast::IfStatement>(
+      Expr(true), Block(),
+      ast::ElseStatementList{Else(nullptr, Block(Break()))});
+  auto* continuing = Block(if_stmt);
+  auto* loop = Loop(Block(), continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranchConditional %6 %1 %2
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_ConditionIsVar) {
+  // loop {
+  //   continuing {
+  //     var cond = true;
+  //     if (cond) { break; }
+  //   }
+  // }
+
+  auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
+  auto* if_stmt = If(Expr("cond"), Block(Break()), ast::ElseStatementList{});
+  auto* continuing = Block(cond_var, if_stmt);
+  auto* loop = Loop(Block(), continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%8 = OpTypePointer Function %5
+%9 = OpConstantNull %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpStore %7 %6
+%10 = OpLoad %5 %7
+OpBranchConditional %10 %2 %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless_ConditionIsVar) {
+  // loop {
+  //   continuing {
+  //     var cond = true;
+  //     if (cond) {} else { break; }
+  //   }
+  // }
+  auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
+  auto* if_stmt = If(Expr("cond"), Block(),
+                     ast::ElseStatementList{Else(nullptr, Block(Break()))});
+  auto* continuing = Block(cond_var, if_stmt);
+  auto* loop = Loop(Block(), continuing);
+  WrapInFunction(loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+%6 = OpConstantTrue %5
+%8 = OpTypePointer Function %5
+%9 = OpConstantNull %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpStore %7 %6
+%10 = OpLoad %5 %7
+OpBranchConditional %10 %1 %2
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_Nested) {
+  // Make sure the right backedge and break target are used.
+  // loop {
+  //   continuing {
+  //     loop {
+  //       continuing {
+  //         if (true) { break; }
+  //       }
+  //     }
+  //     if (true) { break; }
+  //   }
+  // }
+
+  auto* inner_if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
+                                                 ast::ElseStatementList{});
+  auto* inner_continuing = Block(inner_if_stmt);
+  auto* inner_loop = Loop(Block(), inner_continuing);
+
+  auto* outer_if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
+                                                 ast::ElseStatementList{});
+  auto* outer_continuing = Block(inner_loop, outer_if_stmt);
+  auto* outer_loop = Loop(Block(), outer_continuing);
+
+  WrapInFunction(outer_loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeBool
+%10 = OpConstantTrue %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpBranchConditional %10 %6 %5
+%6 = OpLabel
+OpBranchConditional %10 %2 %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless_Nested) {
+  // Make sure the right backedge and break target are used.
+  // loop {
+  //   continuing {
+  //     loop {
+  //       continuing {
+  //         if (true) {} else { break; }
+  //       }
+  //     }
+  //     if (true) {} else { break; }
+  //   }
+  // }
+
+  auto* inner_if_stmt = create<ast::IfStatement>(
+      Expr(true), Block(),
+      ast::ElseStatementList{Else(nullptr, Block(Break()))});
+  auto* inner_continuing = Block(inner_if_stmt);
+  auto* inner_loop = Loop(Block(), inner_continuing);
+
+  auto* outer_if_stmt = create<ast::IfStatement>(
+      Expr(true), Block(),
+      ast::ElseStatementList{Else(nullptr, Block(Break()))});
+  auto* outer_continuing = Block(inner_loop, outer_if_stmt);
+  auto* outer_loop = Loop(Block(), outer_continuing);
+
+  WrapInFunction(outer_loop);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeBool
+%10 = OpConstantTrue %9
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpBranchConditional %10 %5 %6
+%6 = OpLabel
+OpBranchConditional %10 %1 %2
+%2 = OpLabel
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_return_test.cc b/src/tint/writer/spirv/builder_return_test.cc
new file mode 100644
index 0000000..73f0c1a
--- /dev/null
+++ b/src/tint/writer/spirv/builder_return_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Return) {
+  auto* ret = Return();
+  WrapInFunction(ret);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateReturnStatement(ret));
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+)");
+}
+
+TEST_F(BuilderTest, Return_WithValue) {
+  auto* val = vec3<f32>(1.f, 1.f, 3.f);
+
+  auto* ret = Return(val);
+  Func("test", {}, ty.vec3<f32>(), {ret}, {});
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateReturnStatement(ret));
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 1
+%4 = OpConstant %2 3
+%5 = OpConstantComposite %1 %3 %3 %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpReturnValue %5
+)");
+}
+
+TEST_F(BuilderTest, Return_WithValue_GeneratesLoad) {
+  auto* var = Var("param", ty.f32());
+
+  auto* ret = Return(var);
+  Func("test", {}, ty.f32(), {Decl(var), ret}, {});
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  EXPECT_TRUE(b.GenerateReturnStatement(ret)) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Function %3
+%4 = OpConstantNull %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %4
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%5 = OpLoad %3 %1
+OpReturnValue %5
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_switch_test.cc b/src/tint/writer/spirv/builder_switch_test.cc
new file mode 100644
index 0000000..4336ccf
--- /dev/null
+++ b/src/tint/writer/spirv/builder_switch_test.cc
@@ -0,0 +1,452 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, Switch_Empty) {
+  // switch (1) {
+  //   default: {}
+  // }
+
+  auto* expr = Switch(1, DefaultCase());
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+
+  EXPECT_TRUE(b.GenerateSwitchStatement(expr)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(OpSelectionMerge %1 None
+OpSwitch %3 %4
+%4 = OpLabel
+OpBranch %1
+%1 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, Switch_WithCase) {
+  // switch(a) {
+  //   case 1:
+  //     v = 1;
+  //   case 2:
+  //     v = 2;
+  //   default: {}
+  // }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Switch("a",                                   //
+                               Case(Expr(1), Block(Assign("v", 1))),  //
+                               Case(Expr(2), Block(Assign("v", 2))),  //
+                               DefaultCase()),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %8 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpVariable %2 Private %4
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14
+%13 = OpLabel
+OpStore %1 %15
+OpBranch %10
+%14 = OpLabel
+OpStore %1 %16
+OpBranch %10
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_WithCase_Unsigned) {
+  // switch(a) {
+  //   case 1u:
+  //     v = 1;
+  //   case 2u:
+  //     v = 2;
+  //   default: {}
+  // }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.u32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Switch("a",                                    //
+                               Case(Expr(1u), Block(Assign("v", 1))),  //
+                               Case(Expr(2u), Block(Assign("v", 2))),  //
+                               DefaultCase()),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %11 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%7 = OpTypeInt 32 0
+%6 = OpTypePointer Private %7
+%8 = OpConstantNull %7
+%5 = OpVariable %6 Private %8
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%18 = OpConstant %3 1
+%19 = OpConstant %3 2
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%14 = OpLoad %7 %5
+OpSelectionMerge %13 None
+OpSwitch %14 %15 1 %16 2 %17
+%16 = OpLabel
+OpStore %1 %18
+OpBranch %13
+%17 = OpLabel
+OpStore %1 %19
+OpBranch %13
+%15 = OpLabel
+OpBranch %13
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_WithDefault) {
+  // switch(true) {
+  //   default: {}
+  //     v = 1;
+  //  }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Switch("a",                                  //
+                               DefaultCase(Block(Assign("v", 1)))),  //
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %8 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpVariable %2 Private %4
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%13 = OpConstant %3 1
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12
+%12 = OpLabel
+OpStore %1 %13
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_WithCaseAndDefault) {
+  // switch(a) {
+  //   case 1:
+  //      v = 1;
+  //   case 2, 3:
+  //      v = 2;
+  //   default: {}
+  //      v = 3;
+  //  }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Switch(Expr("a"),                    //
+                               Case(Expr(1),                 //
+                                    Block(Assign("v", 1))),  //
+                               Case({Expr(2), Expr(3)},      //
+                                    Block(Assign("v", 2))),  //
+                               DefaultCase(Block(Assign("v", 3)))),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %8 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpVariable %2 Private %4
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%17 = OpConstant %3 3
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14 3 %14
+%13 = OpLabel
+OpStore %1 %15
+OpBranch %10
+%14 = OpLabel
+OpStore %1 %16
+OpBranch %10
+%12 = OpLabel
+OpStore %1 %17
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
+  // switch(a) {
+  //   case 1:
+  //      v = 1;
+  //      fallthrough;
+  //   case 2:
+  //      v = 2;
+  //   default: {}
+  //      v = 3;
+  //  }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func("a_func", {}, ty.void_(),
+                    {
+                        Switch(Expr("a"),                                   //
+                               Case(Expr(1),                                //
+                                    Block(Assign("v", 1), Fallthrough())),  //
+                               Case(Expr(2),                                //
+                                    Block(Assign("v", 2))),                 //
+                               DefaultCase(Block(Assign("v", 3)))),
+                    });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %8 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpVariable %2 Private %4
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%17 = OpConstant %3 3
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14
+%13 = OpLabel
+OpStore %1 %15
+OpBranch %14
+%14 = OpLabel
+OpStore %1 %16
+OpBranch %10
+%12 = OpLabel
+OpStore %1 %17
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_WithNestedBreak) {
+  // switch (a) {
+  //   case 1:
+  //     if (true) {
+  //       break;
+  //     }
+  //     v = 1;
+  //   default: {}
+  // }
+
+  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
+  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* func = Func(
+      "a_func", {}, ty.void_(),
+      {
+          Switch("a",           //
+                 Case(Expr(1),  //
+                      Block(    //
+                          If(Expr(true), Block(create<ast::BreakStatement>())),
+                          Assign("v", 1))),
+                 DefaultCase()),
+      });
+
+  spirv::Builder& b = Build();
+
+  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
+OpName %5 "a"
+OpName %8 "a_func"
+%3 = OpTypeInt 32 1
+%2 = OpTypePointer Private %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Private %4
+%5 = OpVariable %2 Private %4
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%14 = OpTypeBool
+%15 = OpConstantTrue %14
+%18 = OpConstant %3 1
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13
+%13 = OpLabel
+OpSelectionMerge %16 None
+OpBranchConditional %15 %17 %16
+%17 = OpLabel
+OpBranch %10
+%16 = OpLabel
+OpStore %1 %18
+OpBranch %10
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(BuilderTest, Switch_AllReturn) {
+  // switch (1) {
+  //   case 1: {
+  //     return 1;
+  //   }
+  //   case 2: {
+  //     fallthrough;
+  //   }
+  //   default: {
+  //     return 3;
+  //   }
+  // }
+
+  auto* fn = Func("f", {}, ty.i32(),
+                  {
+                      Switch(1,                                    //
+                             Case(Expr(1), Block(Return(1))),      //
+                             Case(Expr(2), Block(Fallthrough())),  //
+                             DefaultCase(Block(Return(3)))),
+                  });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "f"
+%2 = OpTypeInt 32 1
+%1 = OpTypeFunction %2
+%6 = OpConstant %2 1
+%10 = OpConstant %2 3
+%11 = OpConstantNull %2
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+OpSelectionMerge %5 None
+OpSwitch %6 %7 1 %8 2 %9
+%8 = OpLabel
+OpReturnValue %6
+%9 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpReturnValue %10
+%5 = OpLabel
+OpReturnValue %11
+OpFunctionEnd
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_test.cc b/src/tint/writer/spirv/builder_test.cc
new file mode 100644
index 0000000..6a82ce5
--- /dev/null
+++ b/src/tint/writer/spirv/builder_test.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, TracksIdBounds) {
+  spirv::Builder& b = Build();
+
+  for (size_t i = 0; i < 5; i++) {
+    EXPECT_EQ(b.next_id(), i + 1);
+  }
+
+  EXPECT_EQ(6u, b.id_bound());
+}
+
+TEST_F(BuilderTest, Capabilities_Dedup) {
+  spirv::Builder& b = Build();
+
+  b.push_capability(SpvCapabilityShader);
+  b.push_capability(SpvCapabilityShader);
+  b.push_capability(SpvCapabilityShader);
+
+  EXPECT_EQ(DumpInstructions(b.capabilities()), "OpCapability Shader\n");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
new file mode 100644
index 0000000..7fecdc1
--- /dev/null
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -0,0 +1,968 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest_Type = TestHelper;
+
+TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
+  auto* ary = ty.array(ty.i32());
+  auto* str =
+      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeRuntimeArray %2
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
+  auto* ary = ty.array(ty.i32());
+  auto* str =
+      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeRuntimeArray %2
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateArray) {
+  auto* ary = ty.array(ty.i32(), 4);
+  Global("a", ary, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 4
+%1 = OpTypeArray %2 %4
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
+  auto* ary = ty.array(ty.i32(), 4, 16u);
+  Global("a", ary, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id);
+
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 ArrayStride 16
+)");
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 4
+%1 = OpTypeArray %2 %4
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
+  auto* ary = ty.array(ty.i32(), 4);
+  Global("a", ary, ast::StorageClass::kPrivate);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 4
+%1 = OpTypeArray %2 %4
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateBool) {
+  auto* bool_ = create<sem::Bool>();
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(bool_);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  ASSERT_EQ(b.types().size(), 1u);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeBool
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedBool) {
+  auto* bool_ = create<sem::Bool>();
+  auto* i32 = create<sem::I32>();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateF32) {
+  auto* f32 = create<sem::F32>();
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(f32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  ASSERT_EQ(b.types().size(), 1u);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeFloat 32
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedF32) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateI32) {
+  auto* i32 = create<sem::I32>();
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(i32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  ASSERT_EQ(b.types().size(), 1u);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 1
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedI32) {
+  auto* f32 = create<sem::F32>();
+  auto* i32 = create<sem::I32>();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateMatrix) {
+  auto* f32 = create<sem::F32>();
+  auto* vec3 = create<sem::Vector>(f32, 3);
+  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(mat2x3);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(b.types().size(), 3u);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%1 = OpTypeMatrix %2 2
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
+  auto* i32 = create<sem::I32>();
+  auto* col = create<sem::Vector>(i32, 4);
+  auto* mat = create<sem::Matrix>(col, 3);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 3u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GeneratePtr) {
+  auto* i32 = create<sem::I32>();
+  auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
+                                   ast::Access::kReadWrite);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(ptr);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypePointer Output %2
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedPtr) {
+  auto* i32 = create<sem::I32>();
+  auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
+                                   ast::Access::kReadWrite);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ptr), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ptr), 1u);
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct) {
+  auto* s = Structure("my_struct", {Member("a", ty.f32())});
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_struct"
+OpMemberName %1 0 "a"
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.f32(), {MemberAlign(8)}),
+                           });
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2 %2
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 1 Offset 8
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_NonLayout_Matrix) {
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<f32>()),
+                               Member("b", ty.mat2x3<f32>()),
+                               Member("c", ty.mat4x4<f32>()),
+                           });
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 2
+%2 = OpTypeMatrix %3 2
+%6 = OpTypeVector %4 3
+%5 = OpTypeMatrix %6 2
+%8 = OpTypeVector %4 4
+%7 = OpTypeMatrix %8 4
+%1 = OpTypeStruct %2 %5 %7
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+OpMemberName %1 2 "c"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 ColMajor
+OpMemberDecorate %1 0 MatrixStride 8
+OpMemberDecorate %1 1 Offset 16
+OpMemberDecorate %1 1 ColMajor
+OpMemberDecorate %1 1 MatrixStride 16
+OpMemberDecorate %1 2 Offset 48
+OpMemberDecorate %1 2 ColMajor
+OpMemberDecorate %1 2 MatrixStride 16
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutMatrix) {
+  // We have to infer layout for matrix when it also has an offset.
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<f32>()),
+                               Member("b", ty.mat2x3<f32>()),
+                               Member("c", ty.mat4x4<f32>()),
+                           });
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 2
+%2 = OpTypeMatrix %3 2
+%6 = OpTypeVector %4 3
+%5 = OpTypeMatrix %6 2
+%8 = OpTypeVector %4 4
+%7 = OpTypeMatrix %8 4
+%1 = OpTypeStruct %2 %5 %7
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+OpMemberName %1 2 "c"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 ColMajor
+OpMemberDecorate %1 0 MatrixStride 8
+OpMemberDecorate %1 1 Offset 16
+OpMemberDecorate %1 1 ColMajor
+OpMemberDecorate %1 1 MatrixStride 16
+OpMemberDecorate %1 2 Offset 48
+OpMemberDecorate %1 2 ColMajor
+OpMemberDecorate %1 2 MatrixStride 16
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutArraysOfMatrix) {
+  // We have to infer layout for matrix when it also has an offset.
+  // The decoration goes on the struct member, even if the matrix is buried
+  // in levels of arrays.
+  auto* arr_mat2x2 = ty.array(ty.mat2x2<f32>(), 1);      // Singly nested array
+  auto* arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
+  auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>());       // Runtime array
+
+  auto* s = Structure("S", {
+                               Member("a", arr_mat2x2),
+                               Member("b", arr_arr_mat2x3),
+                               Member("c", rtarr_mat4x4),
+                           });
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 2
+%3 = OpTypeMatrix %4 2
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%2 = OpTypeArray %3 %7
+%10 = OpTypeVector %5 3
+%9 = OpTypeMatrix %10 2
+%8 = OpTypeArray %9 %7
+%13 = OpTypeVector %5 4
+%12 = OpTypeMatrix %13 4
+%11 = OpTypeRuntimeArray %12
+%1 = OpTypeStruct %2 %8 %11
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+OpMemberName %1 2 "c"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 ColMajor
+OpMemberDecorate %1 0 MatrixStride 8
+OpDecorate %2 ArrayStride 16
+OpMemberDecorate %1 1 Offset 16
+OpMemberDecorate %1 1 ColMajor
+OpMemberDecorate %1 1 MatrixStride 16
+OpDecorate %8 ArrayStride 32
+OpMemberDecorate %1 2 Offset 48
+OpMemberDecorate %1 2 ColMajor
+OpMemberDecorate %1 2 MatrixStride 16
+OpDecorate %11 ArrayStride 64
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateU32) {
+  auto* u32 = create<sem::U32>();
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(u32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  ASSERT_EQ(b.types().size(), 1u);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 0
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedU32) {
+  auto* u32 = create<sem::U32>();
+  auto* f32 = create<sem::F32>();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateVector) {
+  auto* vec = create<sem::Vector>(create<sem::F32>(), 3);
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(vec);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  EXPECT_EQ(b.types().size(), 2u);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedVector) {
+  auto* i32 = create<sem::I32>();
+  auto* vec = create<sem::Vector>(i32, 3);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateVoid) {
+  auto* void_ = create<sem::Void>();
+
+  spirv::Builder& b = Build();
+
+  auto id = b.GenerateTypeIfNeeded(void_);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1u);
+
+  ASSERT_EQ(b.types().size(), 1u);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeVoid
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedVoid) {
+  auto* void_ = create<sem::Void>();
+  auto* i32 = create<sem::I32>();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+struct PtrData {
+  ast::StorageClass ast_class;
+  SpvStorageClass result;
+};
+inline std::ostream& operator<<(std::ostream& out, PtrData data) {
+  out << data.ast_class;
+  return out;
+}
+using PtrDataTest = TestParamHelper<PtrData>;
+TEST_P(PtrDataTest, ConvertStorageClass) {
+  auto params = GetParam();
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.ConvertStorageClass(params.ast_class), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest_Type,
+    PtrDataTest,
+    testing::Values(
+        PtrData{ast::StorageClass::kNone, SpvStorageClassMax},
+        PtrData{ast::StorageClass::kInput, SpvStorageClassInput},
+        PtrData{ast::StorageClass::kOutput, SpvStorageClassOutput},
+        PtrData{ast::StorageClass::kUniform, SpvStorageClassUniform},
+        PtrData{ast::StorageClass::kWorkgroup, SpvStorageClassWorkgroup},
+        PtrData{ast::StorageClass::kUniformConstant,
+                SpvStorageClassUniformConstant},
+        PtrData{ast::StorageClass::kStorage, SpvStorageClassStorageBuffer},
+        PtrData{ast::StorageClass::kPrivate, SpvStorageClassPrivate},
+        PtrData{ast::StorageClass::kFunction, SpvStorageClassFunction}));
+
+TEST_F(BuilderTest_Type, DepthTexture_Generate_2d) {
+  auto* two_d = create<sem::DepthTexture>(ast::TextureDimension::k2d);
+
+  spirv::Builder& b = Build();
+
+  auto id_two_d = b.GenerateTypeIfNeeded(two_d);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id_two_d);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 1 0 0 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, DepthTexture_Generate_2dArray) {
+  auto* two_d_array =
+      create<sem::DepthTexture>(ast::TextureDimension::k2dArray);
+
+  spirv::Builder& b = Build();
+
+  auto id_two_d_array = b.GenerateTypeIfNeeded(two_d_array);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id_two_d_array);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 1 1 0 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, DepthTexture_Generate_Cube) {
+  auto* cube = create<sem::DepthTexture>(ast::TextureDimension::kCube);
+
+  spirv::Builder& b = Build();
+
+  auto id_cube = b.GenerateTypeIfNeeded(cube);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id_cube);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 Cube 1 0 0 1 Unknown
+)");
+  EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+}
+
+TEST_F(BuilderTest_Type, DepthTexture_Generate_CubeArray) {
+  auto* cube_array =
+      create<sem::DepthTexture>(ast::TextureDimension::kCubeArray);
+
+  spirv::Builder& b = Build();
+
+  auto id_cube_array = b.GenerateTypeIfNeeded(cube_array);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id_cube_array);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 Cube 1 1 0 1 Unknown
+)");
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            R"(OpCapability SampledCubeArray
+)");
+}
+
+TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_i32) {
+  auto* i32 = create<sem::I32>();
+  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, i32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(1u, b.GenerateTypeIfNeeded(ms));
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_u32) {
+  auto* u32 = create<sem::U32>();
+  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, u32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 0
+%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_f32) {
+  auto* f32 = create<sem::F32>();
+  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_i32) {
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d,
+                                        create<sem::I32>());
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 1
+%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
+)");
+
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            R"(OpCapability Sampled1D
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_u32) {
+  auto* u32 = create<sem::U32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, u32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeInt 32 0
+%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
+)");
+
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            R"(OpCapability Sampled1D
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_f32) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
+)");
+
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            R"(OpCapability Sampled1D
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_2d) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 0 0 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_2d_array) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k2dArray, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 1 0 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_3d) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k3d, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 3D 0 0 0 1 Unknown
+)");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_Cube) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::kCube, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 Cube 0 0 0 1 Unknown
+)");
+  EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+}
+
+TEST_F(BuilderTest_Type, SampledTexture_Generate_CubeArray) {
+  auto* f32 = create<sem::F32>();
+  auto* s = create<sem::SampledTexture>(ast::TextureDimension::kCubeArray, f32);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()),
+            R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 Cube 0 1 0 1 Unknown
+)");
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            R"(OpCapability SampledCubeArray
+)");
+}
+
+TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
+  auto* s =
+      ty.storage_texture(ast::TextureDimension::k1d,
+                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 1D 0 0 0 2 R32f
+)");
+}
+
+TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
+  auto* s =
+      ty.storage_texture(ast::TextureDimension::k2d,
+                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 0 0 2 R32f
+)");
+}
+
+TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
+  auto* s =
+      ty.storage_texture(ast::TextureDimension::k2dArray,
+                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 1 0 2 R32f
+)");
+}
+
+TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
+  auto* s =
+      ty.storage_texture(ast::TextureDimension::k3d,
+                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 3D 0 0 0 2 R32f
+)");
+}
+
+TEST_F(BuilderTest_Type,
+       StorageTexture_Generate_SampledTypeFloat_Format_r32float) {
+  auto* s =
+      ty.storage_texture(ast::TextureDimension::k2d,
+                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeImage %2 2D 0 0 0 2 R32f
+)");
+}
+
+TEST_F(BuilderTest_Type,
+       StorageTexture_Generate_SampledTypeSint_Format_r32sint) {
+  auto* s = ty.storage_texture(ast::TextureDimension::k2d,
+                               ast::TexelFormat::kR32Sint, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%1 = OpTypeImage %2 2D 0 0 0 2 R32i
+)");
+}
+
+TEST_F(BuilderTest_Type,
+       StorageTexture_Generate_SampledTypeUint_Format_r32uint) {
+  auto* s = ty.storage_texture(ast::TextureDimension::k2d,
+                               ast::TexelFormat::kR32Uint, ast::Access::kWrite);
+
+  Global("test_var", s,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+%1 = OpTypeImage %2 2D 0 0 0 2 R32ui
+)");
+}
+
+TEST_F(BuilderTest_Type, Sampler) {
+  sem::Sampler sampler(ast::SamplerKind::kSampler);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+}
+
+TEST_F(BuilderTest_Type, ComparisonSampler) {
+  sem::Sampler sampler(ast::SamplerKind::kComparisonSampler);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+}
+
+TEST_F(BuilderTest_Type, Dedup_Sampler_And_ComparisonSampler) {
+  sem::Sampler comp_sampler(ast::SamplerKind::kComparisonSampler);
+  sem::Sampler sampler(ast::SamplerKind::kSampler);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&comp_sampler), 1u);
+
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
+
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/builder_unary_op_expression_test.cc b/src/tint/writer/spirv/builder_unary_op_expression_test.cc
new file mode 100644
index 0000000..901d51a
--- /dev/null
+++ b/src/tint/writer/spirv/builder_unary_op_expression_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, UnaryOp_Negation_Integer) {
+  auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(1));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpSNegate %2 %3
+)");
+}
+
+TEST_F(BuilderTest, UnaryOp_Negation_Float) {
+  auto* expr =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(1.f));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%3 = OpConstant %2 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpFNegate %2 %3
+)");
+}
+
+TEST_F(BuilderTest, UnaryOp_Complement) {
+  auto* expr =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr(1));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+%3 = OpConstant %2 1
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpNot %2 %3
+)");
+}
+
+TEST_F(BuilderTest, UnaryOp_Not) {
+  auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr(false));
+  WrapInFunction(expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%3 = OpConstantFalse %2
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%1 = OpLogicalNot %2 %3
+)");
+}
+
+TEST_F(BuilderTest, UnaryOp_LoadRequired) {
+  auto* var = Var("param", ty.vec3<f32>());
+
+  auto* expr =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("param"));
+  WrapInFunction(var, expr);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 6u) << b.error();
+  ASSERT_FALSE(b.has_error()) << b.error();
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypePointer Function %3
+%5 = OpConstantNull %3
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+            R"(%1 = OpVariable %2 Function %5
+)");
+  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+            R"(%7 = OpLoad %3 %1
+%6 = OpFNegate %3 %7
+)");
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/function.cc b/src/tint/writer/spirv/function.cc
new file mode 100644
index 0000000..f8857e9
--- /dev/null
+++ b/src/tint/writer/spirv/function.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/function.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+Function::Function()
+    : declaration_(Instruction{spv::Op::OpNop, {}}),
+      label_op_(Operand::Int(0)) {}
+
+Function::Function(const Instruction& declaration,
+                   const Operand& label_op,
+                   const InstructionList& params)
+    : declaration_(declaration), label_op_(label_op), params_(params) {}
+
+Function::Function(const Function& other) = default;
+
+Function::~Function() = default;
+
+void Function::iterate(std::function<void(const Instruction&)> cb) const {
+  cb(declaration_);
+
+  for (const auto& param : params_) {
+    cb(param);
+  }
+
+  cb(Instruction{spv::Op::OpLabel, {label_op_}});
+
+  for (const auto& var : vars_) {
+    cb(var);
+  }
+  for (const auto& inst : instructions_) {
+    cb(inst);
+  }
+
+  cb(Instruction{spv::Op::OpFunctionEnd, {}});
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/function.h b/src/tint/writer/spirv/function.h
new file mode 100644
index 0000000..2f856d2
--- /dev/null
+++ b/src/tint/writer/spirv/function.h
@@ -0,0 +1,101 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_FUNCTION_H_
+#define SRC_TINT_WRITER_SPIRV_FUNCTION_H_
+
+#include <functional>
+
+#include "src/tint/writer/spirv/instruction.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// A SPIR-V function
+class Function {
+ public:
+  /// Constructor for testing purposes
+  /// This creates a bad declaration, so won't generate correct SPIR-V
+  Function();
+
+  /// Constructor
+  /// @param declaration the function declaration
+  /// @param label_op the operand for function's entry block label
+  /// @param params the function parameters
+  Function(const Instruction& declaration,
+           const Operand& label_op,
+           const InstructionList& params);
+  /// Copy constructor
+  /// @param other the function to copy
+  Function(const Function& other);
+  ~Function();
+
+  /// Iterates over the function call the cb on each instruction
+  /// @param cb the callback to call
+  void iterate(std::function<void(const Instruction&)> cb) const;
+
+  /// @returns the declaration
+  const Instruction& declaration() const { return declaration_; }
+
+  /// @returns the label ID for the function entry block
+  uint32_t label_id() const { return label_op_.to_i(); }
+
+  /// Adds an instruction to the instruction list
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_inst(spv::Op op, const OperandList& operands) {
+    instructions_.push_back(Instruction{op, operands});
+  }
+  /// @returns the instruction list
+  const InstructionList& instructions() const { return instructions_; }
+
+  /// Adds a variable to the variable list
+  /// @param operands the operands for the variable
+  void push_var(const OperandList& operands) {
+    vars_.push_back(Instruction{spv::Op::OpVariable, operands});
+  }
+  /// @returns the variable list
+  const InstructionList& variables() const { return vars_; }
+
+  /// @returns the word length of the function
+  uint32_t word_length() const {
+    // 1 for the Label and 1 for the FunctionEnd
+    uint32_t size = 2 + declaration_.word_length();
+
+    for (const auto& param : params_) {
+      size += param.word_length();
+    }
+    for (const auto& var : vars_) {
+      size += var.word_length();
+    }
+    for (const auto& inst : instructions_) {
+      size += inst.word_length();
+    }
+    return size;
+  }
+
+ private:
+  Instruction declaration_;
+  Operand label_op_;
+  InstructionList params_;
+  InstructionList vars_;
+  InstructionList instructions_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_FUNCTION_H_
diff --git a/src/tint/writer/spirv/generator.cc b/src/tint/writer/spirv/generator.cc
new file mode 100644
index 0000000..0f6f6f8
--- /dev/null
+++ b/src/tint/writer/spirv/generator.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/generator.h"
+
+#include "src/tint/writer/spirv/binary_writer.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program, const Options& options) {
+  Result result;
+
+  // Sanitize the program.
+  auto sanitized_result = Sanitize(program, options.emit_vertex_point_size,
+                                   options.disable_workgroup_init);
+  if (!sanitized_result.program.IsValid()) {
+    result.success = false;
+    result.error = sanitized_result.program.Diagnostics().str();
+    return result;
+  }
+
+  // Generate the SPIR-V code.
+  auto builder = std::make_unique<Builder>(&sanitized_result.program);
+  auto writer = std::make_unique<BinaryWriter>();
+  if (!builder->Build()) {
+    result.success = false;
+    result.error = builder->error();
+    return result;
+  }
+
+  writer->WriteHeader(builder->id_bound());
+  writer->WriteBuilder(builder.get());
+
+  result.success = true;
+  result.spirv = writer->result();
+
+  return result;
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/generator.h b/src/tint/writer/spirv/generator.h
new file mode 100644
index 0000000..3642a0f
--- /dev/null
+++ b/src/tint/writer/spirv/generator.h
@@ -0,0 +1,79 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_GENERATOR_H_
+#define SRC_TINT_WRITER_SPIRV_GENERATOR_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "src/tint/writer/writer.h"
+
+namespace tint {
+
+// Forward declarations
+class Program;
+
+namespace writer {
+namespace spirv {
+
+/// Forward declarations
+class Builder;
+class BinaryWriter;
+
+/// Configuration options used for generating SPIR-V.
+struct Options {
+  /// Set to `true` to generate a PointSize builtin and have it set to 1.0
+  /// from all vertex shaders in the module.
+  bool emit_vertex_point_size = true;
+
+  /// Set to `true` to disable workgroup memory zero initialization
+  bool disable_workgroup_init = false;
+};
+
+/// The result produced when generating SPIR-V.
+struct Result {
+  /// Constructor
+  Result();
+
+  /// Destructor
+  ~Result();
+
+  /// Copy constructor
+  Result(const Result&);
+
+  /// True if generation was successful.
+  bool success = false;
+
+  /// The errors generated during code generation, if any.
+  std::string error;
+
+  /// The generated SPIR-V.
+  std::vector<uint32_t> spirv;
+};
+
+/// Generate SPIR-V for a program, according to a set of configuration options.
+/// The result will contain the SPIR-V, as well as success status and diagnostic
+/// information.
+/// @param program the program to translate to SPIR-V
+/// @param options the configuration options to use when generating SPIR-V
+/// @returns the resulting SPIR-V and supplementary information
+Result Generate(const Program* program, const Options& options);
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_GENERATOR_H_
diff --git a/src/tint/writer/spirv/generator_bench.cc b/src/tint/writer/spirv/generator_bench.cc
new file mode 100644
index 0000000..6589740
--- /dev/null
+++ b/src/tint/writer/spirv/generator_bench.cc
@@ -0,0 +1,40 @@
+// 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 <string>
+
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+void GenerateSPIRV(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadProgram(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& program = std::get<bench::ProgramAndFile>(res).program;
+  for (auto _ : state) {
+    auto res = Generate(&program, {});
+    if (!res.error.empty()) {
+      state.SkipWithError(res.error.c_str());
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(GenerateSPIRV);
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/instruction.cc b/src/tint/writer/spirv/instruction.cc
new file mode 100644
index 0000000..29f8bca
--- /dev/null
+++ b/src/tint/writer/spirv/instruction.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/instruction.h"
+
+#include <utility>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+Instruction::Instruction(spv::Op op, OperandList operands)
+    : op_(op), operands_(std::move(operands)) {}
+
+Instruction::Instruction(const Instruction&) = default;
+
+Instruction::~Instruction() = default;
+
+uint32_t Instruction::word_length() const {
+  uint32_t size = 1;  // Initial 1 for the op and size
+  for (const auto& op : operands_) {
+    size += op.length();
+  }
+  return size;
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/instruction.h b/src/tint/writer/spirv/instruction.h
new file mode 100644
index 0000000..e2892b1
--- /dev/null
+++ b/src/tint/writer/spirv/instruction.h
@@ -0,0 +1,59 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_INSTRUCTION_H_
+#define SRC_TINT_WRITER_SPIRV_INSTRUCTION_H_
+
+#include <vector>
+
+#include "spirv/unified1/spirv.hpp11"
+#include "src/tint/writer/spirv/operand.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// A single SPIR-V instruction
+class Instruction {
+ public:
+  /// Constructor
+  /// @param op the op to generate
+  /// @param operands the operand values for the instruction
+  Instruction(spv::Op op, OperandList operands);
+  /// Copy Constructor
+  Instruction(const Instruction&);
+  ~Instruction();
+
+  /// @returns the instructions op
+  spv::Op opcode() const { return op_; }
+
+  /// @returns the instructions operands
+  const OperandList& operands() const { return operands_; }
+
+  /// @returns the number of uint32_t's needed to hold the instruction
+  uint32_t word_length() const;
+
+ private:
+  spv::Op op_ = spv::Op::OpNop;
+  OperandList operands_;
+};
+
+/// A list of instructions
+using InstructionList = std::vector<Instruction>;
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_INSTRUCTION_H_
diff --git a/src/tint/writer/spirv/instruction_test.cc b/src/tint/writer/spirv/instruction_test.cc
new file mode 100644
index 0000000..46e8d25
--- /dev/null
+++ b/src/tint/writer/spirv/instruction_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/instruction.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using InstructionTest = testing::Test;
+
+TEST_F(InstructionTest, Create) {
+  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
+                                        Operand::String("my_str")});
+  EXPECT_EQ(i.opcode(), spv::Op::OpEntryPoint);
+  ASSERT_EQ(i.operands().size(), 3u);
+
+  const auto& ops = i.operands();
+  EXPECT_TRUE(ops[0].IsFloat());
+  EXPECT_FLOAT_EQ(ops[0].to_f(), 1.2f);
+
+  EXPECT_TRUE(ops[1].IsInt());
+  EXPECT_EQ(ops[1].to_i(), 1u);
+
+  EXPECT_TRUE(ops[2].IsString());
+  EXPECT_EQ(ops[2].to_s(), "my_str");
+}
+
+TEST_F(InstructionTest, Length) {
+  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
+                                        Operand::String("my_str")});
+  EXPECT_EQ(i.word_length(), 5u);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/operand.cc b/src/tint/writer/spirv/operand.cc
new file mode 100644
index 0000000..ef85b3a
--- /dev/null
+++ b/src/tint/writer/spirv/operand.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/operand.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+// static
+Operand Operand::Float(float val) {
+  Operand o(Kind::kFloat);
+  o.set_float(val);
+  return o;
+}
+
+// static
+Operand Operand::Int(uint32_t val) {
+  Operand o(Kind::kInt);
+  o.set_int(val);
+  return o;
+}
+
+// static
+Operand Operand::String(const std::string& val) {
+  Operand o(Kind::kString);
+  o.set_string(val);
+  return o;
+}
+
+Operand::Operand(Kind kind) : kind_(kind) {}
+
+Operand::~Operand() = default;
+
+uint32_t Operand::length() const {
+  uint32_t val = 0;
+  switch (kind_) {
+    case Kind::kFloat:
+    case Kind::kInt:
+      val = 1;
+      break;
+    case Kind::kString:
+      // SPIR-V always nul-terminates strings. The length is rounded up to a
+      // multiple of 4 bytes with 0 bytes padding the end. Accounting for the
+      // nul terminator is why '+ 4u' is used here instead of '+ 3u'.
+      val = static_cast<uint32_t>((str_val_.length() + 4u) >> 2);
+      break;
+  }
+  return val;
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/operand.h b/src/tint/writer/spirv/operand.h
new file mode 100644
index 0000000..9af06ce
--- /dev/null
+++ b/src/tint/writer/spirv/operand.h
@@ -0,0 +1,99 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_OPERAND_H_
+#define SRC_TINT_WRITER_SPIRV_OPERAND_H_
+
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// A single SPIR-V instruction operand
+class Operand {
+ public:
+  /// The kind of the operand
+  // Note, the `kInt` will cover most cases as things like IDs in SPIR-V are
+  // just ints for the purpose of converting to binary.
+  enum class Kind { kInt = 0, kFloat, kString };
+
+  /// Creates a float operand
+  /// @param val the float value
+  /// @returns the operand
+  static Operand Float(float val);
+  /// Creates an int operand
+  /// @param val the int value
+  /// @returns the operand
+  static Operand Int(uint32_t val);
+  /// Creates a string operand
+  /// @param val the string value
+  /// @returns the operand
+  static Operand String(const std::string& val);
+
+  /// Constructor
+  /// @param kind the type of operand
+  explicit Operand(Kind kind);
+  /// Copy Constructor
+  Operand(const Operand&) = default;
+  ~Operand();
+
+  /// Copy assignment
+  /// @param b the operand to copy
+  /// @returns a copy of this operand
+  Operand& operator=(const Operand& b) = default;
+
+  /// Sets the float value
+  /// @param val the value to set
+  void set_float(float val) { float_val_ = val; }
+  /// Sets the int value
+  /// @param val the value to set
+  void set_int(uint32_t val) { int_val_ = val; }
+  /// Sets the string value
+  /// @param val the value to set
+  void set_string(const std::string& val) { str_val_ = val; }
+
+  /// @returns true if this is a float operand
+  bool IsFloat() const { return kind_ == Kind::kFloat; }
+  /// @returns true if this is an integer operand
+  bool IsInt() const { return kind_ == Kind::kInt; }
+  /// @returns true if this is a string operand
+  bool IsString() const { return kind_ == Kind::kString; }
+
+  /// @returns the number of uint32_t's needed for this operand
+  uint32_t length() const;
+
+  /// @returns the float value
+  float to_f() const { return float_val_; }
+  /// @returns the int value
+  uint32_t to_i() const { return int_val_; }
+  /// @returns the string value
+  const std::string& to_s() const { return str_val_; }
+
+ private:
+  Kind kind_ = Kind::kInt;
+  float float_val_ = 0.0;
+  uint32_t int_val_ = 0;
+  std::string str_val_;
+};
+
+/// A list of operands
+using OperandList = std::vector<Operand>;
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_OPERAND_H_
diff --git a/src/tint/writer/spirv/operand_test.cc b/src/tint/writer/spirv/operand_test.cc
new file mode 100644
index 0000000..36b4306
--- /dev/null
+++ b/src/tint/writer/spirv/operand_test.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/operand.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using OperandTest = testing::Test;
+
+TEST_F(OperandTest, CreateFloat) {
+  auto o = Operand::Float(1.2f);
+  EXPECT_TRUE(o.IsFloat());
+  EXPECT_FLOAT_EQ(o.to_f(), 1.2f);
+}
+
+TEST_F(OperandTest, CreateInt) {
+  auto o = Operand::Int(1);
+  EXPECT_TRUE(o.IsInt());
+  EXPECT_EQ(o.to_i(), 1u);
+}
+
+TEST_F(OperandTest, CreateString) {
+  auto o = Operand::String("my string");
+  EXPECT_TRUE(o.IsString());
+  EXPECT_EQ(o.to_s(), "my string");
+}
+
+TEST_F(OperandTest, Length_Float) {
+  auto o = Operand::Float(1.2f);
+  EXPECT_EQ(o.length(), 1u);
+}
+
+TEST_F(OperandTest, Length_Int) {
+  auto o = Operand::Int(1);
+  EXPECT_EQ(o.length(), 1u);
+}
+
+TEST_F(OperandTest, Length_String) {
+  auto o = Operand::String("my string");
+  EXPECT_EQ(o.length(), 3u);
+}
+
+TEST_F(OperandTest, Length_String_Empty) {
+  auto o = Operand::String("");
+  EXPECT_EQ(o.length(), 1u);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/scalar_constant.h b/src/tint/writer/spirv/scalar_constant.h
new file mode 100644
index 0000000..100ebcc
--- /dev/null
+++ b/src/tint/writer/spirv/scalar_constant.h
@@ -0,0 +1,153 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_SCALAR_CONSTANT_H_
+#define SRC_TINT_WRITER_SPIRV_SCALAR_CONSTANT_H_
+
+#include <stdint.h>
+
+#include <cstring>
+#include <functional>
+
+#include "src/tint/utils/hash.h"
+
+namespace tint {
+
+// Forward declarations
+namespace sem {
+class Call;
+}  // namespace sem
+
+namespace writer {
+namespace spirv {
+
+/// ScalarConstant represents a scalar constant value
+struct ScalarConstant {
+  /// The constant value
+  union Value {
+    /// The value as a bool
+    bool b;
+    /// The value as a uint32_t
+    uint32_t u32;
+    /// The value as a int32_t
+    int32_t i32;
+    /// The value as a float
+    float f32;
+
+    /// The value that is wide enough to encompass all other types (including
+    /// future 64-bit data types).
+    uint64_t u64;
+  };
+
+  /// The kind of constant
+  enum class Kind { kBool, kU32, kI32, kF32 };
+
+  /// Constructor
+  inline ScalarConstant() { value.u64 = 0; }
+
+  /// @param value the value of the constant
+  /// @returns a new ScalarConstant with the provided value and kind Kind::kU32
+  static inline ScalarConstant U32(uint32_t value) {
+    ScalarConstant c;
+    c.value.u32 = value;
+    c.kind = Kind::kU32;
+    return c;
+  }
+
+  /// @param value the value of the constant
+  /// @returns a new ScalarConstant with the provided value and kind Kind::kI32
+  static inline ScalarConstant I32(int32_t value) {
+    ScalarConstant c;
+    c.value.i32 = value;
+    c.kind = Kind::kI32;
+    return c;
+  }
+
+  /// @param value the value of the constant
+  /// @returns a new ScalarConstant with the provided value and kind Kind::kI32
+  static inline ScalarConstant F32(float value) {
+    ScalarConstant c;
+    c.value.f32 = value;
+    c.kind = Kind::kF32;
+    return c;
+  }
+
+  /// @param value the value of the constant
+  /// @returns a new ScalarConstant with the provided value and kind Kind::kBool
+  static inline ScalarConstant Bool(bool value) {
+    ScalarConstant c;
+    c.value.b = value;
+    c.kind = Kind::kBool;
+    return c;
+  }
+
+  /// Equality operator
+  /// @param rhs the ScalarConstant to compare against
+  /// @returns true if this ScalarConstant is equal to `rhs`
+  inline bool operator==(const ScalarConstant& rhs) const {
+    return value.u64 == rhs.value.u64 && kind == rhs.kind &&
+           is_spec_op == rhs.is_spec_op && constant_id == rhs.constant_id;
+  }
+
+  /// Inequality operator
+  /// @param rhs the ScalarConstant to compare against
+  /// @returns true if this ScalarConstant is not equal to `rhs`
+  inline bool operator!=(const ScalarConstant& rhs) const {
+    return !(*this == rhs);
+  }
+
+  /// @returns this ScalarConstant as a specialization op with the given
+  /// specialization constant identifier
+  /// @param id the constant identifier
+  ScalarConstant AsSpecOp(uint32_t id) const {
+    auto ret = *this;
+    ret.is_spec_op = true;
+    ret.constant_id = id;
+    return ret;
+  }
+
+  /// The constant value
+  Value value;
+  /// The constant value kind
+  Kind kind = Kind::kBool;
+  /// True if the constant is a specialization op
+  bool is_spec_op = false;
+  /// The identifier if a specialization op
+  uint32_t constant_id = 0;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::Symbol so symbols can be used as
+/// keys for std::unordered_map and std::unordered_set.
+template <>
+class hash<tint::writer::spirv::ScalarConstant> {
+ public:
+  /// @param c the ScalarConstant
+  /// @return the Symbol internal value
+  inline std::size_t operator()(
+      const tint::writer::spirv::ScalarConstant& c) const {
+    uint32_t value = 0;
+    std::memcpy(&value, &c.value, sizeof(value));
+    return tint::utils::Hash(value, c.kind);
+  }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_WRITER_SPIRV_SCALAR_CONSTANT_H_
diff --git a/src/tint/writer/spirv/scalar_constant_test.cc b/src/tint/writer/spirv/scalar_constant_test.cc
new file mode 100644
index 0000000..3113e55
--- /dev/null
+++ b/src/tint/writer/spirv/scalar_constant_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/scalar_constant.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+using SpirvScalarConstantTest = TestHelper;
+
+TEST_F(SpirvScalarConstantTest, Equality) {
+  ScalarConstant a{};
+  ScalarConstant b{};
+  EXPECT_EQ(a, b);
+
+  a.kind = ScalarConstant::Kind::kU32;
+  EXPECT_NE(a, b);
+  b.kind = ScalarConstant::Kind::kU32;
+  EXPECT_EQ(a, b);
+
+  a.value.b = true;
+  EXPECT_NE(a, b);
+  b.value.b = true;
+  EXPECT_EQ(a, b);
+
+  a.is_spec_op = true;
+  EXPECT_NE(a, b);
+  b.is_spec_op = true;
+  EXPECT_EQ(a, b);
+
+  a.constant_id = 3;
+  EXPECT_NE(a, b);
+  b.constant_id = 3;
+  EXPECT_EQ(a, b);
+}
+
+TEST_F(SpirvScalarConstantTest, U32) {
+  auto c = ScalarConstant::U32(123);
+  EXPECT_EQ(c.value.u32, 123u);
+  EXPECT_EQ(c.kind, ScalarConstant::Kind::kU32);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/spv_dump.cc b/src/tint/writer/spirv/spv_dump.cc
new file mode 100644
index 0000000..463b1fe
--- /dev/null
+++ b/src/tint/writer/spirv/spv_dump.cc
@@ -0,0 +1,89 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+
+#include "spirv-tools/libspirv.hpp"
+#include "src/tint/writer/spirv/binary_writer.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+std::string Disassemble(const std::vector<uint32_t>& data) {
+  std::string spv_errors;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
+
+  auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
+                                    const spv_position_t& position,
+                                    const char* message) {
+    switch (level) {
+      case SPV_MSG_FATAL:
+      case SPV_MSG_INTERNAL_ERROR:
+      case SPV_MSG_ERROR:
+        spv_errors += "error: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_WARNING:
+        spv_errors += "warning: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_INFO:
+        spv_errors += "info: line " + std::to_string(position.index) + ": " +
+                      message + "\n";
+        break;
+      case SPV_MSG_DEBUG:
+        break;
+    }
+  };
+
+  spvtools::SpirvTools tools(target_env);
+  tools.SetMessageConsumer(msg_consumer);
+
+  std::string result;
+  if (!tools.Disassemble(data, &result, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) {
+    return "*** Invalid SPIR-V ***\n" + spv_errors;
+  }
+  return result;
+}
+
+}  // namespace
+
+std::string DumpBuilder(Builder& builder) {
+  BinaryWriter writer;
+  writer.WriteHeader(builder.id_bound());
+  writer.WriteBuilder(&builder);
+  return Disassemble(writer.result());
+}
+
+std::string DumpInstruction(const Instruction& inst) {
+  BinaryWriter writer;
+  writer.WriteHeader(kDefaultMaxIdBound);
+  writer.WriteInstruction(inst);
+  return Disassemble(writer.result());
+}
+
+std::string DumpInstructions(const InstructionList& insts) {
+  BinaryWriter writer;
+  writer.WriteHeader(kDefaultMaxIdBound);
+  for (const auto& inst : insts) {
+    writer.WriteInstruction(inst);
+  }
+  return Disassemble(writer.result());
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/spirv/spv_dump.h b/src/tint/writer/spirv/spv_dump.h
new file mode 100644
index 0000000..6d9170c
--- /dev/null
+++ b/src/tint/writer/spirv/spv_dump.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_SPV_DUMP_H_
+#define SRC_TINT_WRITER_SPIRV_SPV_DUMP_H_
+
+#include <string>
+#include <vector>
+
+#include "src/tint/writer/spirv/builder.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// Dumps the given builder to a SPIR-V disassembly string
+/// @param builder the builder to convert
+/// @returns the builder as a SPIR-V disassembly string
+std::string DumpBuilder(Builder& builder);
+
+/// Dumps the given instruction to a SPIR-V disassembly string
+/// @param inst the instruction to dump
+/// @returns the instruction as a SPIR-V disassembly string
+std::string DumpInstruction(const Instruction& inst);
+
+/// Dumps the given instructions to a SPIR-V disassembly string
+/// @param insts the instructions to dump
+/// @returns the instruction as a SPIR-V disassembly string
+std::string DumpInstructions(const InstructionList& insts);
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_SPV_DUMP_H_
diff --git a/src/tint/writer/spirv/test_helper.h b/src/tint/writer/spirv/test_helper.h
new file mode 100644
index 0000000..6e233f0
--- /dev/null
+++ b/src/tint/writer/spirv/test_helper.h
@@ -0,0 +1,139 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_TEST_HELPER_H_
+#define SRC_TINT_WRITER_SPIRV_TEST_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "spirv-tools/libspirv.hpp"
+#include "src/tint/writer/spirv/binary_writer.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public ProgramBuilder, public BASE {
+ public:
+  TestHelperBase() = default;
+  ~TestHelperBase() override = default;
+
+  /// Builds and returns a spirv::Builder from the program.
+  /// @note The spirv::Builder is only built once. Multiple calls to Build()
+  /// will return the same spirv::Builder without rebuilding.
+  /// @return the built spirv::Builder
+  spirv::Builder& Build() {
+    if (spirv_builder) {
+      return *spirv_builder;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+    spirv_builder = std::make_unique<spirv::Builder>(program.get());
+    return *spirv_builder;
+  }
+
+  /// Builds the program, runs the program through the transform::Spirv
+  /// sanitizer and returns a spirv::Builder from the sanitized program.
+  /// @note The spirv::Builder is only built once. Multiple calls to Build()
+  /// will return the same spirv::Builder without rebuilding.
+  /// @return the built spirv::Builder
+  spirv::Builder& SanitizeAndBuild() {
+    if (spirv_builder) {
+      return *spirv_builder;
+    }
+    [&]() {
+      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                             << diag::Formatter().format(Diagnostics());
+    }();
+    program = std::make_unique<Program>(std::move(*this));
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << diag::Formatter().format(program->Diagnostics());
+    }();
+    auto result = Sanitize(program.get());
+    [&]() {
+      ASSERT_TRUE(result.program.IsValid())
+          << diag::Formatter().format(result.program.Diagnostics());
+    }();
+    *program = std::move(result.program);
+    spirv_builder = std::make_unique<spirv::Builder>(program.get());
+    return *spirv_builder;
+  }
+
+  /// Validate passes the generated SPIR-V of the builder `b` to the SPIR-V
+  /// Tools Validator. If the validator finds problems the test will fail.
+  /// @param b the spirv::Builder containing the built SPIR-V module
+  void Validate(spirv::Builder& b) {
+    BinaryWriter writer;
+    writer.WriteHeader(b.id_bound());
+    writer.WriteBuilder(&b);
+    auto binary = writer.result();
+
+    std::string spv_errors;
+    auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
+                                      const spv_position_t& position,
+                                      const char* message) {
+      switch (level) {
+        case SPV_MSG_FATAL:
+        case SPV_MSG_INTERNAL_ERROR:
+        case SPV_MSG_ERROR:
+          spv_errors += "error: line " + std::to_string(position.index) + ": " +
+                        message + "\n";
+          break;
+        case SPV_MSG_WARNING:
+          spv_errors += "warning: line " + std::to_string(position.index) +
+                        ": " + message + "\n";
+          break;
+        case SPV_MSG_INFO:
+          spv_errors += "info: line " + std::to_string(position.index) + ": " +
+                        message + "\n";
+          break;
+        case SPV_MSG_DEBUG:
+          break;
+      }
+    };
+
+    spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
+    tools.SetMessageConsumer(msg_consumer);
+    ASSERT_TRUE(tools.Validate(binary)) << spv_errors;
+  }
+
+  /// The program built with a call to Build()
+  std::unique_ptr<Program> program;
+
+ private:
+  std::unique_ptr<spirv::Builder> spirv_builder;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_SPIRV_TEST_HELPER_H_
diff --git a/src/tint/writer/text.cc b/src/tint/writer/text.cc
new file mode 100644
index 0000000..19d9052
--- /dev/null
+++ b/src/tint/writer/text.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/text.h"
+
+namespace tint {
+namespace writer {
+
+Text::~Text() = default;
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/text.h b/src/tint/writer/text.h
new file mode 100644
index 0000000..3825d0c
--- /dev/null
+++ b/src/tint/writer/text.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_TEXT_H_
+#define SRC_TINT_WRITER_TEXT_H_
+
+#include <string>
+
+#include "src/tint/writer/writer.h"
+
+namespace tint {
+namespace writer {
+
+/// Class to generate text source
+class Text : public Writer {
+ public:
+  ~Text() override;
+
+  /// @returns the result data
+  virtual std::string result() const = 0;
+};
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_TEXT_H_
diff --git a/src/tint/writer/text_generator.cc b/src/tint/writer/text_generator.cc
new file mode 100644
index 0000000..fcec369
--- /dev/null
+++ b/src/tint/writer/text_generator.cc
@@ -0,0 +1,155 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/text_generator.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "src/tint/utils/map.h"
+
+namespace tint {
+namespace writer {
+
+TextGenerator::TextGenerator(const Program* program)
+    : program_(program), builder_(ProgramBuilder::Wrap(program)) {}
+
+TextGenerator::~TextGenerator() = default;
+
+std::string TextGenerator::UniqueIdentifier(const std::string& prefix) {
+  return builder_.Symbols().NameFor(builder_.Symbols().New(prefix));
+}
+
+std::string TextGenerator::StructName(const sem::Struct* s) {
+  auto name = builder_.Symbols().NameFor(s->Name());
+  if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
+    name = utils::GetOrCreate(builtin_struct_names_, s,
+                              [&] { return UniqueIdentifier(name.substr(2)); });
+  }
+  return name;
+}
+
+std::string TextGenerator::TrimSuffix(std::string str,
+                                      const std::string& suffix) {
+  if (str.size() >= suffix.size()) {
+    if (str.substr(str.size() - suffix.size(), suffix.size()) == suffix) {
+      return str.substr(0, str.size() - suffix.size());
+    }
+  }
+  return str;
+}
+
+TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
+
+TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
+  buffer = other.buffer;
+  other.buffer = nullptr;
+}
+
+TextGenerator::LineWriter::~LineWriter() {
+  if (buffer) {
+    buffer->Append(os.str());
+  }
+}
+
+TextGenerator::TextBuffer::TextBuffer() = default;
+TextGenerator::TextBuffer::~TextBuffer() = default;
+
+void TextGenerator::TextBuffer::IncrementIndent() {
+  current_indent += 2;
+}
+
+void TextGenerator::TextBuffer::DecrementIndent() {
+  current_indent = std::max(2u, current_indent) - 2u;
+}
+
+void TextGenerator::TextBuffer::Append(const std::string& line) {
+  lines.emplace_back(Line{current_indent, line});
+}
+
+void TextGenerator::TextBuffer::Insert(const std::string& line,
+                                       size_t before,
+                                       uint32_t indent) {
+  if (before >= lines.size()) {
+    diag::List d;
+    TINT_ICE(Writer, d)
+        << "TextBuffer::Insert() called with before >= lines.size()\n"
+        << "  before:" << before << "\n"
+        << "  lines.size(): " << lines.size();
+    return;
+  }
+  lines.insert(lines.begin() + before, Line{indent, line});
+}
+
+void TextGenerator::TextBuffer::Append(const TextBuffer& tb) {
+  for (auto& line : tb.lines) {
+    // TODO(bclayton): inefficent, consider optimizing
+    lines.emplace_back(Line{current_indent + line.indent, line.content});
+  }
+}
+
+void TextGenerator::TextBuffer::Insert(const TextBuffer& tb,
+                                       size_t before,
+                                       uint32_t indent) {
+  if (before >= lines.size()) {
+    diag::List d;
+    TINT_ICE(Writer, d)
+        << "TextBuffer::Insert() called with before >= lines.size()\n"
+        << "  before:" << before << "\n"
+        << "  lines.size(): " << lines.size();
+    return;
+  }
+  size_t idx = 0;
+  for (auto& line : tb.lines) {
+    // TODO(bclayton): inefficent, consider optimizing
+    lines.insert(lines.begin() + before + idx,
+                 Line{indent + line.indent, line.content});
+    idx++;
+  }
+}
+
+std::string TextGenerator::TextBuffer::String(uint32_t indent /* = 0 */) const {
+  std::stringstream ss;
+  for (auto& line : lines) {
+    if (!line.content.empty()) {
+      for (uint32_t i = 0; i < indent + line.indent; i++) {
+        ss << " ";
+      }
+      ss << line.content;
+    }
+    ss << std::endl;
+  }
+  return ss.str();
+}
+
+TextGenerator::ScopedParen::ScopedParen(std::ostream& stream) : s(stream) {
+  s << "(";
+}
+TextGenerator::ScopedParen::~ScopedParen() {
+  s << ")";
+}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextGenerator* generator)
+    : ScopedIndent(generator->current_buffer_) {}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextBuffer* buffer)
+    : buffer_(buffer) {
+  buffer_->IncrementIndent();
+}
+TextGenerator::ScopedIndent::~ScopedIndent() {
+  buffer_->DecrementIndent();
+}
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
new file mode 100644
index 0000000..f65c6f2
--- /dev/null
+++ b/src/tint/writer/text_generator.h
@@ -0,0 +1,242 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_TEXT_GENERATOR_H_
+#define SRC_TINT_WRITER_TEXT_GENERATOR_H_
+
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace writer {
+
+/// Helper methods for generators which are creating text output
+class TextGenerator {
+ public:
+  /// Line holds a single line of text
+  struct Line {
+    /// The indentation of the line in whitespaces
+    uint32_t indent = 0;
+    /// The content of the line, without a trailing newline character
+    std::string content;
+  };
+
+  /// TextBuffer holds a list of lines of text.
+  struct TextBuffer {
+    // Constructor
+    TextBuffer();
+
+    // Destructor
+    ~TextBuffer();
+
+    /// IncrementIndent increases the indentation of lines that will be written
+    /// to the TextBuffer
+    void IncrementIndent();
+
+    /// DecrementIndent decreases the indentation of lines that will be written
+    /// to the TextBuffer
+    void DecrementIndent();
+
+    /// Appends the line to the end of the TextBuffer
+    /// @param line the line to append to the TextBuffer
+    void Append(const std::string& line);
+
+    /// Inserts the line to the TextBuffer before the line with index `before`
+    /// @param line the line to append to the TextBuffer
+    /// @param before the zero-based index of the line to insert the text before
+    /// @param indent the indentation to apply to the inserted lines
+    void Insert(const std::string& line, size_t before, uint32_t indent);
+
+    /// Appends the lines of `tb` to the end of this TextBuffer
+    /// @param tb the TextBuffer to append to the end of this TextBuffer
+    void Append(const TextBuffer& tb);
+
+    /// Inserts the lines of `tb` to the TextBuffer before the line with index
+    /// `before`
+    /// @param tb the TextBuffer to insert into this TextBuffer
+    /// @param before the zero-based index of the line to insert the text before
+    /// @param indent the indentation to apply to the inserted lines
+    void Insert(const TextBuffer& tb, size_t before, uint32_t indent);
+
+    /// @returns the buffer's content as a single string
+    /// @param indent additional indentation to apply to each line
+    std::string String(uint32_t indent = 0) const;
+
+    /// The current indentation of the TextBuffer. Lines appended to the
+    /// TextBuffer will use this indentation.
+    uint32_t current_indent = 0;
+
+    /// The lines
+    std::vector<Line> lines;
+  };
+
+  /// Constructor
+  /// @param program the program used by the generator
+  explicit TextGenerator(const Program* program);
+  ~TextGenerator();
+
+  /// Increment the emitter indent level
+  void increment_indent() { current_buffer_->IncrementIndent(); }
+  /// Decrement the emitter indent level
+  void decrement_indent() { current_buffer_->DecrementIndent(); }
+
+  /// @returns the result data
+  std::string result() const { return main_buffer_.String(); }
+
+  /// @returns the list of diagnostics raised by the generator.
+  const diag::List& Diagnostics() const { return diagnostics_; }
+
+  /// @returns the error
+  std::string error() const { return diagnostics_.str(); }
+
+  /// @return a new, unique identifier with the given prefix.
+  /// @param prefix optional prefix to apply to the generated identifier. If
+  /// empty "tint_symbol" will be used.
+  std::string UniqueIdentifier(const std::string& prefix = "");
+
+  /// @param s the semantic structure
+  /// @returns the name of the structure, taking special care of builtin
+  /// structures that start with double underscores. If the structure is a
+  /// builtin, then the returned name will be a unique name without the leading
+  /// underscores.
+  std::string StructName(const sem::Struct* s);
+
+  /// @param str the string
+  /// @param suffix the suffix to remove
+  /// @return returns str without the provided trailing suffix string. If str
+  /// doesn't end with suffix, str is returned unchanged.
+  std::string TrimSuffix(std::string str, const std::string& suffix);
+
+ protected:
+  /// LineWriter is a helper that acts as a string buffer, who's content is
+  /// emitted to the TextBuffer as a single line on destruction.
+  struct LineWriter {
+   public:
+    /// Constructor
+    /// @param buffer the TextBuffer that the LineWriter will append its
+    /// content to on destruction, at the end of the buffer.
+    explicit LineWriter(TextBuffer* buffer);
+
+    /// Move constructor
+    /// @param rhs the LineWriter to move
+    LineWriter(LineWriter&& rhs);
+    /// Destructor
+    ~LineWriter();
+
+    /// @returns the ostringstream
+    operator std::ostream&() { return os; }
+
+    /// @param rhs the value to write to the line
+    /// @returns the ostream so calls can be chained
+    template <typename T>
+    std::ostream& operator<<(T&& rhs) {
+      return os << std::forward<T>(rhs);
+    }
+
+   private:
+    LineWriter(const LineWriter&) = delete;
+    LineWriter& operator=(const LineWriter&) = delete;
+
+    std::ostringstream os;
+    TextBuffer* buffer;
+  };
+
+  /// Helper for writing a '(' on construction and a ')' destruction.
+  struct ScopedParen {
+    /// Constructor
+    /// @param stream the std::ostream that will be written to
+    explicit ScopedParen(std::ostream& stream);
+    /// Destructor
+    ~ScopedParen();
+
+   private:
+    ScopedParen(ScopedParen&& rhs) = delete;
+    ScopedParen(const ScopedParen&) = delete;
+    ScopedParen& operator=(const ScopedParen&) = delete;
+    std::ostream& s;
+  };
+
+  /// Helper for incrementing indentation on construction and decrementing
+  /// indentation on destruction.
+  struct ScopedIndent {
+    /// Constructor
+    /// @param buffer the TextBuffer that the ScopedIndent will indent
+    explicit ScopedIndent(TextBuffer* buffer);
+    /// Constructor
+    /// @param generator ScopedIndent will indent the generator's
+    /// `current_buffer_`
+    explicit ScopedIndent(TextGenerator* generator);
+    /// Destructor
+    ~ScopedIndent();
+
+   private:
+    ScopedIndent(ScopedIndent&& rhs) = delete;
+    ScopedIndent(const ScopedIndent&) = delete;
+    ScopedIndent& operator=(const ScopedIndent&) = delete;
+    TextBuffer* buffer_;
+  };
+
+  /// @returns the resolved type of the ast::Expression `expr`
+  /// @param expr the expression
+  const sem::Type* TypeOf(const ast::Expression* expr) const {
+    return builder_.TypeOf(expr);
+  }
+
+  /// @returns the resolved type of the ast::Type `type`
+  /// @param type the type
+  const sem::Type* TypeOf(const ast::Type* type) const {
+    return builder_.TypeOf(type);
+  }
+
+  /// @returns the resolved type of the ast::TypeDecl `type_decl`
+  /// @param type_decl the type
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const {
+    return builder_.TypeOf(type_decl);
+  }
+
+  /// @returns a new LineWriter, used for buffering and writing a line to
+  /// the end of #current_buffer_.
+  LineWriter line() { return LineWriter(current_buffer_); }
+
+  /// @param buffer the TextBuffer to write the line to
+  /// @returns a new LineWriter, used for buffering and writing a line to
+  /// the end of `buffer`.
+  static LineWriter line(TextBuffer* buffer) { return LineWriter(buffer); }
+
+  /// The program
+  Program const* const program_;
+  /// A ProgramBuilder that thinly wraps program_
+  ProgramBuilder builder_;
+  /// Diagnostics generated by the generator
+  diag::List diagnostics_;
+  /// The buffer the TextGenerator is currently appending lines to
+  TextBuffer* current_buffer_ = &main_buffer_;
+
+ private:
+  /// The primary text buffer that the generator will emit
+  TextBuffer main_buffer_;
+  /// Map of builtin structure to unique generated name
+  std::unordered_map<const sem::Struct*, std::string> builtin_struct_names_;
+};
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_TEXT_GENERATOR_H_
diff --git a/src/tint/writer/text_generator_test.cc b/src/tint/writer/text_generator_test.cc
new file mode 100644
index 0000000..29ec138
--- /dev/null
+++ b/src/tint/writer/text_generator_test.cc
@@ -0,0 +1,48 @@
+// 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/writer/text_generator.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace {
+
+TEST(TextGeneratorTest, UniqueIdentifier) {
+  Program program(ProgramBuilder{});
+
+  TextGenerator gen(&program);
+
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_1");
+}
+
+TEST(TextGeneratorTest, UniqueIdentifier_ConflictWithExisting) {
+  ProgramBuilder builder;
+  builder.Symbols().Register("ident_1");
+  builder.Symbols().Register("ident_2");
+  Program program(std::move(builder));
+
+  TextGenerator gen(&program);
+
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_3");
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_4");
+  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_5");
+}
+
+}  // namespace
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator.cc b/src/tint/writer/wgsl/generator.cc
new file mode 100644
index 0000000..3b639d6
--- /dev/null
+++ b/src/tint/writer/wgsl/generator.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/generator.h"
+#include "src/tint/writer/wgsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program, const Options&) {
+  Result result;
+
+  // Generate the WGSL code.
+  auto impl = std::make_unique<GeneratorImpl>(program);
+  result.success = impl->Generate();
+  result.error = impl->error();
+  result.wgsl = impl->result();
+
+  return result;
+}
+
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator.h b/src/tint/writer/wgsl/generator.h
new file mode 100644
index 0000000..28b6f84
--- /dev/null
+++ b/src/tint/writer/wgsl/generator.h
@@ -0,0 +1,69 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_WGSL_GENERATOR_H_
+#define SRC_TINT_WRITER_WGSL_GENERATOR_H_
+
+#include <memory>
+#include <string>
+
+#include "src/tint/writer/text.h"
+
+namespace tint {
+
+// Forward declarations
+class Program;
+
+namespace writer {
+namespace wgsl {
+
+class GeneratorImpl;
+
+/// Configuration options used for generating WGSL.
+struct Options {};
+
+/// The result produced when generating WGSL.
+struct Result {
+  /// Constructor
+  Result();
+
+  /// Destructor
+  ~Result();
+
+  /// Copy constructor
+  Result(const Result&);
+
+  /// True if generation was successful.
+  bool success = false;
+
+  /// The errors generated during code generation, if any.
+  std::string error;
+
+  /// The generated WGSL.
+  std::string wgsl = "";
+};
+
+/// Generate WGSL for a program, according to a set of configuration options.
+/// The result will contain the WGSL, as well as success status and diagnostic
+/// information.
+/// @param program the program to translate to WGSL
+/// @param options the configuration options to use when generating WGSL
+/// @returns the resulting WGSL and supplementary information
+Result Generate(const Program* program, const Options& options);
+
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_WGSL_GENERATOR_H_
diff --git a/src/tint/writer/wgsl/generator_bench.cc b/src/tint/writer/wgsl/generator_bench.cc
new file mode 100644
index 0000000..cb8cb68
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_bench.cc
@@ -0,0 +1,40 @@
+// 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 <string>
+
+#include "src/tint/bench/benchmark.h"
+
+namespace tint::writer::wgsl {
+namespace {
+
+void GenerateWGSL(benchmark::State& state, std::string input_name) {
+  auto res = bench::LoadProgram(input_name);
+  if (auto err = std::get_if<bench::Error>(&res)) {
+    state.SkipWithError(err->msg.c_str());
+    return;
+  }
+  auto& program = std::get<bench::ProgramAndFile>(res).program;
+  for (auto _ : state) {
+    auto res = Generate(&program, {});
+    if (!res.error.empty()) {
+      state.SkipWithError(res.error.c_str());
+    }
+  }
+}
+
+TINT_BENCHMARK_WGSL_PROGRAMS(GenerateWGSL);
+
+}  // namespace
+}  // namespace tint::writer::wgsl
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
new file mode 100644
index 0000000..05a39ff
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -0,0 +1,1190 @@
+// 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/writer/wgsl/generator_impl.h"
+
+#include <algorithm>
+
+#include "src/tint/ast/access.h"
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/array.h"
+#include "src/tint/ast/atomic.h"
+#include "src/tint/ast/bool.h"
+#include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/depth_texture.h"
+#include "src/tint/ast/external_texture.h"
+#include "src/tint/ast/f32.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/i32.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/invariant_attribute.h"
+#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/multisampled_texture.h"
+#include "src/tint/ast/pointer.h"
+#include "src/tint/ast/sampled_texture.h"
+#include "src/tint/ast/sint_literal_expression.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/storage_texture.h"
+#include "src/tint/ast/stride_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/struct_member_align_attribute.h"
+#include "src/tint/ast/struct_member_offset_attribute.h"
+#include "src/tint/ast/struct_member_size_attribute.h"
+#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/u32.h"
+#include "src/tint/ast/uint_literal_expression.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/vector.h"
+#include "src/tint/ast/void.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/float_to_string.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+
+GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
+
+GeneratorImpl::~GeneratorImpl() = default;
+
+bool GeneratorImpl::Generate() {
+  // Generate global declarations in the order they appear in the module.
+  for (auto* decl : program_->AST().GlobalDeclarations()) {
+    if (!Switch(
+            decl,  //
+            [&](const ast::TypeDecl* td) { return EmitTypeDecl(td); },
+            [&](const ast::Function* func) { return EmitFunction(func); },
+            [&](const ast::Variable* var) { return EmitVariable(line(), var); },
+            [&](Default) {
+              TINT_UNREACHABLE(Writer, diagnostics_);
+              return false;
+            })) {
+      return false;
+    }
+    if (decl != program_->AST().GlobalDeclarations().back()) {
+      line();
+    }
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitTypeDecl(const ast::TypeDecl* ty) {
+  return Switch(
+      ty,
+      [&](const ast::Alias* alias) {  //
+        auto out = line();
+        out << "type " << program_->Symbols().NameFor(alias->name) << " = ";
+        if (!EmitType(out, alias->type)) {
+          return false;
+        }
+        out << ";";
+        return true;
+      },
+      [&](const ast::Struct* str) {  //
+        return EmitStructType(str);
+      },
+      [&](Default) {  //
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown declared type: " + std::string(ty->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitExpression(std::ostream& out,
+                                   const ast::Expression* expr) {
+  return Switch(
+      expr,
+      [&](const ast::IndexAccessorExpression* a) {  //
+        return EmitIndexAccessor(out, a);
+      },
+      [&](const ast::BinaryExpression* b) {  //
+        return EmitBinary(out, b);
+      },
+      [&](const ast::BitcastExpression* b) {  //
+        return EmitBitcast(out, b);
+      },
+      [&](const ast::CallExpression* c) {  //
+        return EmitCall(out, c);
+      },
+      [&](const ast::IdentifierExpression* i) {  //
+        return EmitIdentifier(out, i);
+      },
+      [&](const ast::LiteralExpression* l) {  //
+        return EmitLiteral(out, l);
+      },
+      [&](const ast::MemberAccessorExpression* m) {  //
+        return EmitMemberAccessor(out, m);
+      },
+      [&](const ast::PhonyExpression*) {  //
+        out << "_";
+        return true;
+      },
+      [&](const ast::UnaryOpExpression* u) {  //
+        return EmitUnaryOp(out, u);
+      },
+      [&](Default) {
+        diagnostics_.add_error(diag::System::Writer, "unknown expression type");
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitIndexAccessor(
+    std::ostream& out,
+    const ast::IndexAccessorExpression* expr) {
+  bool paren_lhs =
+      !expr->object->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
+                             ast::IdentifierExpression,
+                             ast::MemberAccessorExpression>();
+  if (paren_lhs) {
+    out << "(";
+  }
+  if (!EmitExpression(out, expr->object)) {
+    return false;
+  }
+  if (paren_lhs) {
+    out << ")";
+  }
+  out << "[";
+
+  if (!EmitExpression(out, expr->index)) {
+    return false;
+  }
+  out << "]";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitMemberAccessor(
+    std::ostream& out,
+    const ast::MemberAccessorExpression* expr) {
+  bool paren_lhs =
+      !expr->structure->IsAnyOf<ast::IndexAccessorExpression,
+                                ast::CallExpression, ast::IdentifierExpression,
+                                ast::MemberAccessorExpression>();
+  if (paren_lhs) {
+    out << "(";
+  }
+  if (!EmitExpression(out, expr->structure)) {
+    return false;
+  }
+  if (paren_lhs) {
+    out << ")";
+  }
+
+  out << ".";
+
+  return EmitExpression(out, expr->member);
+}
+
+bool GeneratorImpl::EmitBitcast(std::ostream& out,
+                                const ast::BitcastExpression* expr) {
+  out << "bitcast<";
+  if (!EmitType(out, expr->type)) {
+    return false;
+  }
+
+  out << ">(";
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitCall(std::ostream& out,
+                             const ast::CallExpression* expr) {
+  if (expr->target.name) {
+    if (!EmitExpression(out, expr->target.name)) {
+      return false;
+    }
+  } else if (expr->target.type) {
+    if (!EmitType(out, expr->target.type)) {
+      return false;
+    }
+  } else {
+    TINT_ICE(Writer, diagnostics_)
+        << "CallExpression target had neither a name or type";
+    return false;
+  }
+  out << "(";
+
+  bool first = true;
+  const auto& args = expr->args;
+  for (auto* arg : args) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    if (!EmitExpression(out, arg)) {
+      return false;
+    }
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitLiteral(std::ostream& out,
+                                const ast::LiteralExpression* lit) {
+  return Switch(
+      lit,
+      [&](const ast::BoolLiteralExpression* bl) {  //
+        out << (bl->value ? "true" : "false");
+        return true;
+      },
+      [&](const ast::FloatLiteralExpression* fl) {  //
+        out << FloatToBitPreservingString(fl->value);
+        return true;
+      },
+      [&](const ast::SintLiteralExpression* sl) {  //
+        out << sl->value;
+        return true;
+      },
+      [&](const ast::UintLiteralExpression* ul) {  //
+        out << ul->value << "u";
+        return true;
+      },
+      [&](Default) {  //
+        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
+                                   const ast::IdentifierExpression* expr) {
+  out << program_->Symbols().NameFor(expr->symbol);
+  return true;
+}
+
+bool GeneratorImpl::EmitFunction(const ast::Function* func) {
+  if (func->attributes.size()) {
+    if (!EmitAttributes(line(), func->attributes)) {
+      return false;
+    }
+  }
+  {
+    auto out = line();
+    out << "fn " << program_->Symbols().NameFor(func->symbol) << "(";
+
+    bool first = true;
+    for (auto* v : func->params) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      if (!v->attributes.empty()) {
+        if (!EmitAttributes(out, v->attributes)) {
+          return false;
+        }
+        out << " ";
+      }
+
+      out << program_->Symbols().NameFor(v->symbol) << " : ";
+
+      if (!EmitType(out, v->type)) {
+        return false;
+      }
+    }
+
+    out << ")";
+
+    if (!func->return_type->Is<ast::Void>() ||
+        !func->return_type_attributes.empty()) {
+      out << " -> ";
+
+      if (!func->return_type_attributes.empty()) {
+        if (!EmitAttributes(out, func->return_type_attributes)) {
+          return false;
+        }
+        out << " ";
+      }
+
+      if (!EmitType(out, func->return_type)) {
+        return false;
+      }
+    }
+
+    if (func->body) {
+      out << " {";
+    }
+  }
+
+  if (func->body) {
+    if (!EmitStatementsWithIndent(func->body->statements)) {
+      return false;
+    }
+    line() << "}";
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitImageFormat(std::ostream& out,
+                                    const ast::TexelFormat fmt) {
+  switch (fmt) {
+    case ast::TexelFormat::kNone:
+      diagnostics_.add_error(diag::System::Writer, "unknown image format");
+      return false;
+    default:
+      out << fmt;
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitAccess(std::ostream& out, const ast::Access access) {
+  switch (access) {
+    case ast::Access::kRead:
+      out << "read";
+      return true;
+    case ast::Access::kWrite:
+      out << "write";
+      return true;
+    case ast::Access::kReadWrite:
+      out << "read_write";
+      return true;
+    default:
+      break;
+  }
+  diagnostics_.add_error(diag::System::Writer, "unknown access");
+  return false;
+}
+
+bool GeneratorImpl::EmitType(std::ostream& out, const ast::Type* ty) {
+  return Switch(
+      ty,
+      [&](const ast::Array* ary) {
+        for (auto* attr : ary->attributes) {
+          if (auto* stride = attr->As<ast::StrideAttribute>()) {
+            out << "@stride(" << stride->stride << ") ";
+          }
+        }
+
+        out << "array<";
+        if (!EmitType(out, ary->type)) {
+          return false;
+        }
+
+        if (!ary->IsRuntimeArray()) {
+          out << ", ";
+          if (!EmitExpression(out, ary->count)) {
+            return false;
+          }
+        }
+
+        out << ">";
+        return true;
+      },
+      [&](const ast::Bool*) {
+        out << "bool";
+        return true;
+      },
+      [&](const ast::F32*) {
+        out << "f32";
+        return true;
+      },
+      [&](const ast::I32*) {
+        out << "i32";
+        return true;
+      },
+      [&](const ast::Matrix* mat) {
+        out << "mat" << mat->columns << "x" << mat->rows;
+        if (auto* el_ty = mat->type) {
+          out << "<";
+          if (!EmitType(out, el_ty)) {
+            return false;
+          }
+          out << ">";
+        }
+        return true;
+      },
+      [&](const ast::Pointer* ptr) {
+        out << "ptr<" << ptr->storage_class << ", ";
+        if (!EmitType(out, ptr->type)) {
+          return false;
+        }
+        if (ptr->access != ast::Access::kUndefined) {
+          out << ", ";
+          if (!EmitAccess(out, ptr->access)) {
+            return false;
+          }
+        }
+        out << ">";
+        return true;
+      },
+      [&](const ast::Atomic* atomic) {
+        out << "atomic<";
+        if (!EmitType(out, atomic->type)) {
+          return false;
+        }
+        out << ">";
+        return true;
+      },
+      [&](const ast::Sampler* sampler) {
+        out << "sampler";
+
+        if (sampler->IsComparison()) {
+          out << "_comparison";
+        }
+        return true;
+      },
+      [&](const ast::ExternalTexture*) {
+        out << "texture_external";
+        return true;
+      },
+      [&](const ast::Texture* texture) {
+        out << "texture_";
+        bool ok = Switch(
+            texture,
+            [&](const ast::DepthTexture*) {  //
+              out << "depth_";
+              return true;
+            },
+            [&](const ast::DepthMultisampledTexture*) {  //
+              out << "depth_multisampled_";
+              return true;
+            },
+            [&](const ast::SampledTexture*) {  //
+              /* nothing to emit */
+              return true;
+            },
+            [&](const ast::MultisampledTexture*) {  //
+              out << "multisampled_";
+              return true;
+            },
+            [&](const ast::StorageTexture*) {  //
+              out << "storage_";
+              return true;
+            },
+            [&](Default) {  //
+              diagnostics_.add_error(diag::System::Writer,
+                                     "unknown texture type");
+              return false;
+            });
+        if (!ok) {
+          return false;
+        }
+
+        switch (texture->dim) {
+          case ast::TextureDimension::k1d:
+            out << "1d";
+            break;
+          case ast::TextureDimension::k2d:
+            out << "2d";
+            break;
+          case ast::TextureDimension::k2dArray:
+            out << "2d_array";
+            break;
+          case ast::TextureDimension::k3d:
+            out << "3d";
+            break;
+          case ast::TextureDimension::kCube:
+            out << "cube";
+            break;
+          case ast::TextureDimension::kCubeArray:
+            out << "cube_array";
+            break;
+          default:
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unknown texture dimension");
+            return false;
+        }
+
+        return Switch(
+            texture,
+            [&](const ast::SampledTexture* sampled) {  //
+              out << "<";
+              if (!EmitType(out, sampled->type)) {
+                return false;
+              }
+              out << ">";
+              return true;
+            },
+            [&](const ast::MultisampledTexture* ms) {  //
+              out << "<";
+              if (!EmitType(out, ms->type)) {
+                return false;
+              }
+              out << ">";
+              return true;
+            },
+            [&](const ast::StorageTexture* storage) {  //
+              out << "<";
+              if (!EmitImageFormat(out, storage->format)) {
+                return false;
+              }
+              out << ", ";
+              if (!EmitAccess(out, storage->access)) {
+                return false;
+              }
+              out << ">";
+              return true;
+            },
+            [&](Default) {  //
+              return true;
+            });
+      },
+      [&](const ast::U32*) {
+        out << "u32";
+        return true;
+      },
+      [&](const ast::Vector* vec) {
+        out << "vec" << vec->width;
+        if (auto* el_ty = vec->type) {
+          out << "<";
+          if (!EmitType(out, el_ty)) {
+            return false;
+          }
+          out << ">";
+        }
+        return true;
+      },
+      [&](const ast::Void*) {
+        out << "void";
+        return true;
+      },
+      [&](const ast::TypeName* tn) {
+        out << program_->Symbols().NameFor(tn->name);
+        return true;
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown type in EmitType: " + std::string(ty->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitStructType(const ast::Struct* str) {
+  if (str->attributes.size()) {
+    if (!EmitAttributes(line(), str->attributes)) {
+      return false;
+    }
+  }
+  line() << "struct " << program_->Symbols().NameFor(str->name) << " {";
+
+  auto add_padding = [&](uint32_t size) {
+    line() << "@size(" << size << ")";
+
+    // Note: u32 is the smallest primitive we currently support. When WGSL
+    // supports smaller types, this will need to be updated.
+    line() << UniqueIdentifier("padding") << " : u32;";
+  };
+
+  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.
+    if (auto* mem_sem = program_->Sem().Get(mem)) {
+      offset = utils::RoundUp(mem_sem->Align(), offset);
+      if (uint32_t padding = mem_sem->Offset() - offset) {
+        add_padding(padding);
+        offset += padding;
+      }
+      offset += mem_sem->Size();
+    }
+
+    // Offset attributes no longer exist in the WGSL spec, but are emitted
+    // by the SPIR-V reader and are consumed by the Resolver(). These should not
+    // be emitted, but instead struct padding fields should be emitted.
+    ast::AttributeList attributes_sanitized;
+    attributes_sanitized.reserve(mem->attributes.size());
+    for (auto* attr : mem->attributes) {
+      if (!attr->Is<ast::StructMemberOffsetAttribute>()) {
+        attributes_sanitized.emplace_back(attr);
+      }
+    }
+
+    if (!attributes_sanitized.empty()) {
+      if (!EmitAttributes(line(), attributes_sanitized)) {
+        return false;
+      }
+    }
+
+    auto out = line();
+    out << program_->Symbols().NameFor(mem->symbol) << " : ";
+    if (!EmitType(out, mem->type)) {
+      return false;
+    }
+    out << ";";
+  }
+  decrement_indent();
+
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitVariable(std::ostream& out, const ast::Variable* var) {
+  if (!var->attributes.empty()) {
+    if (!EmitAttributes(out, var->attributes)) {
+      return false;
+    }
+    out << " ";
+  }
+
+  if (var->is_overridable) {
+    out << "override";
+  } else if (var->is_const) {
+    out << "let";
+  } else {
+    out << "var";
+    auto sc = var->declared_storage_class;
+    auto ac = var->declared_access;
+    if (sc != ast::StorageClass::kNone || ac != ast::Access::kUndefined) {
+      out << "<" << sc;
+      if (ac != ast::Access::kUndefined) {
+        out << ", ";
+        if (!EmitAccess(out, ac)) {
+          return false;
+        }
+      }
+      out << ">";
+    }
+  }
+
+  out << " " << program_->Symbols().NameFor(var->symbol);
+
+  if (auto* ty = var->type) {
+    out << " : ";
+    if (!EmitType(out, ty)) {
+      return false;
+    }
+  }
+
+  if (var->constructor != nullptr) {
+    out << " = ";
+    if (!EmitExpression(out, var->constructor)) {
+      return false;
+    }
+  }
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitAttributes(std::ostream& out,
+                                   const ast::AttributeList& attrs) {
+  bool first = true;
+  for (auto* attr : attrs) {
+    if (!first) {
+      out << " ";
+    }
+    first = false;
+    out << "@";
+    bool ok = Switch(
+        attr,
+        [&](const ast::WorkgroupAttribute* workgroup) {
+          auto values = workgroup->Values();
+          out << "workgroup_size(";
+          for (int i = 0; i < 3; i++) {
+            if (values[i]) {
+              if (i > 0) {
+                out << ", ";
+              }
+              if (!EmitExpression(out, values[i])) {
+                return false;
+              }
+            }
+          }
+          out << ")";
+          return true;
+        },
+        [&](const ast::StructBlockAttribute*) {  //
+          out << "block";
+          return true;
+        },
+        [&](const ast::StageAttribute* stage) {
+          out << "stage(" << stage->stage << ")";
+          return true;
+        },
+        [&](const ast::BindingAttribute* binding) {
+          out << "binding(" << binding->value << ")";
+          return true;
+        },
+        [&](const ast::GroupAttribute* group) {
+          out << "group(" << group->value << ")";
+          return true;
+        },
+        [&](const ast::LocationAttribute* location) {
+          out << "location(" << location->value << ")";
+          return true;
+        },
+        [&](const ast::BuiltinAttribute* builtin) {
+          out << "builtin(" << builtin->builtin << ")";
+          return true;
+        },
+        [&](const ast::InterpolateAttribute* interpolate) {
+          out << "interpolate(" << interpolate->type;
+          if (interpolate->sampling != ast::InterpolationSampling::kNone) {
+            out << ", " << interpolate->sampling;
+          }
+          out << ")";
+          return true;
+        },
+        [&](const ast::InvariantAttribute*) {
+          out << "invariant";
+          return true;
+        },
+        [&](const ast::IdAttribute* override_deco) {
+          out << "id(" << override_deco->value << ")";
+          return true;
+        },
+        [&](const ast::StructMemberSizeAttribute* size) {
+          out << "size(" << size->size << ")";
+          return true;
+        },
+        [&](const ast::StructMemberAlignAttribute* align) {
+          out << "align(" << align->align << ")";
+          return true;
+        },
+        [&](const ast::StrideAttribute* stride) {
+          out << "stride(" << stride->stride << ")";
+          return true;
+        },
+        [&](const ast::InternalAttribute* internal) {
+          out << "internal(" << internal->InternalName() << ")";
+          return true;
+        },
+        [&](Default) {
+          TINT_ICE(Writer, diagnostics_)
+              << "Unsupported attribute '" << attr->TypeInfo().name << "'";
+          return false;
+        });
+
+    if (!ok) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBinary(std::ostream& out,
+                               const ast::BinaryExpression* expr) {
+  out << "(";
+
+  if (!EmitExpression(out, expr->lhs)) {
+    return false;
+  }
+  out << " ";
+
+  switch (expr->op) {
+    case ast::BinaryOp::kAnd:
+      out << "&";
+      break;
+    case ast::BinaryOp::kOr:
+      out << "|";
+      break;
+    case ast::BinaryOp::kXor:
+      out << "^";
+      break;
+    case ast::BinaryOp::kLogicalAnd:
+      out << "&&";
+      break;
+    case ast::BinaryOp::kLogicalOr:
+      out << "||";
+      break;
+    case ast::BinaryOp::kEqual:
+      out << "==";
+      break;
+    case ast::BinaryOp::kNotEqual:
+      out << "!=";
+      break;
+    case ast::BinaryOp::kLessThan:
+      out << "<";
+      break;
+    case ast::BinaryOp::kGreaterThan:
+      out << ">";
+      break;
+    case ast::BinaryOp::kLessThanEqual:
+      out << "<=";
+      break;
+    case ast::BinaryOp::kGreaterThanEqual:
+      out << ">=";
+      break;
+    case ast::BinaryOp::kShiftLeft:
+      out << "<<";
+      break;
+    case ast::BinaryOp::kShiftRight:
+      out << ">>";
+      break;
+    case ast::BinaryOp::kAdd:
+      out << "+";
+      break;
+    case ast::BinaryOp::kSubtract:
+      out << "-";
+      break;
+    case ast::BinaryOp::kMultiply:
+      out << "*";
+      break;
+    case ast::BinaryOp::kDivide:
+      out << "/";
+      break;
+    case ast::BinaryOp::kModulo:
+      out << "%";
+      break;
+    case ast::BinaryOp::kNone:
+      diagnostics_.add_error(diag::System::Writer,
+                             "missing binary operation type");
+      return false;
+  }
+  out << " ";
+
+  if (!EmitExpression(out, expr->rhs)) {
+    return false;
+  }
+
+  out << ")";
+  return true;
+}
+
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
+                                const ast::UnaryOpExpression* expr) {
+  switch (expr->op) {
+    case ast::UnaryOp::kAddressOf:
+      out << "&";
+      break;
+    case ast::UnaryOp::kComplement:
+      out << "~";
+      break;
+    case ast::UnaryOp::kIndirection:
+      out << "*";
+      break;
+    case ast::UnaryOp::kNot:
+      out << "!";
+      break;
+    case ast::UnaryOp::kNegation:
+      out << "-";
+      break;
+  }
+  out << "(";
+
+  if (!EmitExpression(out, expr->expr)) {
+    return false;
+  }
+
+  out << ")";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+  line() << "{";
+  if (!EmitStatementsWithIndent(stmt->statements)) {
+    return false;
+  }
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
+  return Switch(
+      stmt,  //
+      [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
+      [&](const ast::BlockStatement* b) { return EmitBlock(b); },
+      [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+      [&](const ast::CallStatement* c) {
+        auto out = line();
+        if (!EmitCall(out, c->expr)) {
+          return false;
+        }
+        out << ";";
+        return true;
+      },
+      [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
+      [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
+      [&](const ast::FallthroughStatement* f) { return EmitFallthrough(f); },
+      [&](const ast::IfStatement* i) { return EmitIf(i); },
+      [&](const ast::LoopStatement* l) { return EmitLoop(l); },
+      [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
+      [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
+      [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
+      [&](const ast::VariableDeclStatement* v) {
+        return EmitVariable(line(), v->variable);
+      },
+      [&](Default) {
+        diagnostics_.add_error(
+            diag::System::Writer,
+            "unknown statement type: " + std::string(stmt->TypeInfo().name));
+        return false;
+      });
+}
+
+bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+  for (auto* s : stmts) {
+    if (!EmitStatement(s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+  ScopedIndent si(this);
+  return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
+  auto out = line();
+
+  if (!EmitExpression(out, stmt->lhs)) {
+    return false;
+  }
+
+  out << " = ";
+
+  if (!EmitExpression(out, stmt->rhs)) {
+    return false;
+  }
+
+  out << ";";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
+  line() << "break;";
+  return true;
+}
+
+bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
+  if (stmt->IsDefault()) {
+    line() << "default: {";
+  } else {
+    auto out = line();
+    out << "case ";
+
+    bool first = true;
+    for (auto* selector : stmt->selectors) {
+      if (!first) {
+        out << ", ";
+      }
+
+      first = false;
+      if (!EmitLiteral(out, selector)) {
+        return false;
+      }
+    }
+    out << ": {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
+  line() << "continue;";
+  return true;
+}
+
+bool GeneratorImpl::EmitFallthrough(const ast::FallthroughStatement*) {
+  line() << "fallthrough;";
+  return true;
+}
+
+bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
+  {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  for (auto* e : stmt->else_statements) {
+    if (e->condition) {
+      auto out = line();
+      out << "} else if (";
+      if (!EmitExpression(out, e->condition)) {
+        return false;
+      }
+      out << ") {";
+    } else {
+      line() << "} else {";
+    }
+
+    if (!EmitStatementsWithIndent(e->body->statements)) {
+      return false;
+    }
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
+  line() << "discard;";
+  return true;
+}
+
+bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
+  line() << "loop {";
+  increment_indent();
+
+  if (!EmitStatements(stmt->body->statements)) {
+    return false;
+  }
+
+  if (stmt->continuing && !stmt->continuing->Empty()) {
+    line();
+    line() << "continuing {";
+    if (!EmitStatementsWithIndent(stmt->continuing->statements)) {
+      return false;
+    }
+    line() << "}";
+  }
+
+  decrement_indent();
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+  TextBuffer init_buf;
+  if (auto* init = stmt->initializer) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
+    if (!EmitStatement(init)) {
+      return false;
+    }
+  }
+
+  TextBuffer cont_buf;
+  if (auto* cont = stmt->continuing) {
+    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
+    if (!EmitStatement(cont)) {
+      return false;
+    }
+  }
+
+  {
+    auto out = line();
+    out << "for";
+    {
+      ScopedParen sp(out);
+      switch (init_buf.lines.size()) {
+        case 0:  // No initializer
+          break;
+        case 1:  // Single line initializer statement
+          out << TrimSuffix(init_buf.lines[0].content, ";");
+          break;
+        default:  // Block initializer statement
+          for (size_t i = 1; i < init_buf.lines.size(); i++) {
+            // Indent all by the first line
+            init_buf.lines[i].indent += current_buffer_->current_indent;
+          }
+          out << TrimSuffix(init_buf.String(), "\n");
+          break;
+      }
+
+      out << "; ";
+
+      if (auto* cond = stmt->condition) {
+        if (!EmitExpression(out, cond)) {
+          return false;
+        }
+      }
+
+      out << "; ";
+
+      switch (cont_buf.lines.size()) {
+        case 0:  // No continuing
+          break;
+        case 1:  // Single line continuing statement
+          out << TrimSuffix(cont_buf.lines[0].content, ";");
+          break;
+        default:  // Block continuing statement
+          for (size_t i = 1; i < cont_buf.lines.size(); i++) {
+            // Indent all by the first line
+            cont_buf.lines[i].indent += current_buffer_->current_indent;
+          }
+          out << TrimSuffix(cont_buf.String(), "\n");
+          break;
+      }
+    }
+    out << " {";
+  }
+
+  if (!EmitStatementsWithIndent(stmt->body->statements)) {
+    return false;
+  }
+
+  line() << "}";
+
+  return true;
+}
+
+bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
+  auto out = line();
+  out << "return";
+  if (stmt->value) {
+    out << " ";
+    if (!EmitExpression(out, stmt->value)) {
+      return false;
+    }
+  }
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
+  {
+    auto out = line();
+    out << "switch(";
+    if (!EmitExpression(out, stmt->condition)) {
+      return false;
+    }
+    out << ") {";
+  }
+
+  {
+    ScopedIndent si(this);
+    for (auto* s : stmt->body) {
+      if (!EmitCase(s)) {
+        return false;
+      }
+    }
+  }
+
+  line() << "}";
+  return true;
+}
+
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
new file mode 100644
index 0000000..c5b4a87
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -0,0 +1,206 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_WGSL_GENERATOR_IMPL_H_
+#define SRC_TINT_WRITER_WGSL_GENERATOR_IMPL_H_
+
+#include <string>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/index_accessor_expression.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/member_accessor_expression.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/storage_texture_type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/writer/text_generator.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+
+/// Implementation class for WGSL generator
+class GeneratorImpl : public TextGenerator {
+ public:
+  /// Constructor
+  /// @param program the program
+  explicit GeneratorImpl(const Program* program);
+  ~GeneratorImpl();
+
+  /// Generates the result data
+  /// @returns true on successful generation; false otherwise
+  bool Generate();
+
+  /// Handles generating a declared type
+  /// @param ty the declared type to generate
+  /// @returns true if the declared type was emitted
+  bool EmitTypeDecl(const ast::TypeDecl* ty);
+  /// Handles an index accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the index accessor was emitted
+  bool EmitIndexAccessor(std::ostream& out,
+                         const ast::IndexAccessorExpression* expr);
+  /// Handles an assignment statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitAssign(const ast::AssignmentStatement* stmt);
+  /// Handles generating a binary expression
+  /// @param out the output of the expression stream
+  /// @param expr the binary expression
+  /// @returns true if the expression was emitted, false otherwise
+  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
+  /// Handles generating a bitcast expression
+  /// @param out the output of the expression stream
+  /// @param expr the bitcast expression
+  /// @returns true if the bitcast was emitted
+  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
+  /// Handles a block statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBlock(const ast::BlockStatement* stmt);
+  /// Handles a break statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitBreak(const ast::BreakStatement* stmt);
+  /// Handles generating a call expression
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
+  /// Handles a case statement
+  /// @param stmt the statement
+  /// @returns true if the statment was emitted successfully
+  bool EmitCase(const ast::CaseStatement* stmt);
+  /// Handles generating a literal expression
+  /// @param out the output of the expression stream
+  /// @param expr the literal expression expression
+  /// @returns true if the literal expression is emitted
+  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* expr);
+  /// Handles a continue statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted successfully
+  bool EmitContinue(const ast::ContinueStatement* stmt);
+  /// Handles generate an Expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression
+  /// @returns true if the expression was emitted
+  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
+  /// Handles generating a fallthrough statement
+  /// @param stmt the fallthrough statement
+  /// @returns true if the statement was successfully emitted
+  bool EmitFallthrough(const ast::FallthroughStatement* stmt);
+  /// Handles generating a function
+  /// @param func the function to generate
+  /// @returns true if the function was emitted
+  bool EmitFunction(const ast::Function* func);
+  /// Handles generating an identifier expression
+  /// @param out the output of the expression stream
+  /// @param expr the identifier expression
+  /// @returns true if the identifier was emitted
+  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
+  /// Handles an if statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitIf(const ast::IfStatement* stmt);
+  /// Handles generating a discard statement
+  /// @param stmt the discard statement
+  /// @returns true if the statement was successfully emitted
+  bool EmitDiscard(const ast::DiscardStatement* stmt);
+  /// Handles a loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emtited
+  bool EmitLoop(const ast::LoopStatement* stmt);
+  /// Handles a for-loop statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emtited
+  bool EmitForLoop(const ast::ForLoopStatement* stmt);
+  /// Handles a member accessor expression
+  /// @param out the output of the expression stream
+  /// @param expr the member accessor expression
+  /// @returns true if the member accessor was emitted
+  bool EmitMemberAccessor(std::ostream& out,
+                          const ast::MemberAccessorExpression* expr);
+  /// Handles return statements
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was successfully emitted
+  bool EmitReturn(const ast::ReturnStatement* stmt);
+  /// Handles statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitStatement(const ast::Statement* stmt);
+  /// Handles a statement list
+  /// @param stmts the statements to emit
+  /// @returns true if the statements were emitted
+  bool EmitStatements(const ast::StatementList& stmts);
+  /// Handles a statement list with an increased indentation
+  /// @param stmts the statements to emit
+  /// @returns true if the statements were emitted
+  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+  /// Handles generating a switch statement
+  /// @param stmt the statement to emit
+  /// @returns true if the statement was emitted
+  bool EmitSwitch(const ast::SwitchStatement* stmt);
+  /// Handles generating type
+  /// @param out the output of the expression stream
+  /// @param type the type to generate
+  /// @returns true if the type is emitted
+  bool EmitType(std::ostream& out, const ast::Type* type);
+  /// Handles generating a struct declaration
+  /// @param str the struct
+  /// @returns true if the struct is emitted
+  bool EmitStructType(const ast::Struct* str);
+  /// Handles emitting an image format
+  /// @param out the output of the expression stream
+  /// @param fmt the format to generate
+  /// @returns true if the format is emitted
+  bool EmitImageFormat(std::ostream& out, const ast::TexelFormat fmt);
+  /// Handles emitting an access control
+  /// @param out the output of the expression stream
+  /// @param access the access to generate
+  /// @returns true if the access is emitted
+  bool EmitAccess(std::ostream& out, const ast::Access access);
+  /// Handles a unary op expression
+  /// @param out the output of the expression stream
+  /// @param expr the expression to emit
+  /// @returns true if the expression was emitted
+  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
+  /// Handles generating a variable
+  /// @param out the output of the expression stream
+  /// @param var the variable to generate
+  /// @returns true if the variable was emitted
+  bool EmitVariable(std::ostream& out, const ast::Variable* var);
+  /// Handles generating a attribute list
+  /// @param out the output of the expression stream
+  /// @param attrs the attribute list
+  /// @returns true if the attributes were emitted
+  bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
+};
+
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_WGSL_GENERATOR_IMPL_H_
diff --git a/src/tint/writer/wgsl/generator_impl_alias_type_test.cc b/src/tint/writer/wgsl/generator_impl_alias_type_test.cc
new file mode 100644
index 0000000..b1897cf
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_alias_type_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitAlias_F32) {
+  auto* alias = Alias("a", ty.f32());
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(type a = f32;
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitTypeDecl_Struct) {
+  auto* s = Structure("A", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
+
+  auto* alias = Alias("B", ty.Of(s));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitTypeDecl(s)) << gen.error();
+  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct A {
+  a : f32;
+  b : i32;
+}
+type B = A;
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitAlias_ToStruct) {
+  auto* s = Structure("A", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
+
+  auto* alias = Alias("B", ty.Of(s));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(type B = A;
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc b/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc
new file mode 100644
index 0000000..2abac54
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, IndexAccessor) {
+  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
+  auto* expr = IndexAccessor("ary", 5);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "ary[5]");
+}
+
+TEST_F(WgslGeneratorImplTest, IndexAccessor_OfDref) {
+  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
+
+  auto* p = Const("p", nullptr, AddressOf("ary"));
+  auto* expr = IndexAccessor(Deref("p"), 5);
+  WrapInFunction(p, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(*(p))[5]");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_assign_test.cc b/src/tint/writer/wgsl/generator_impl_assign_test.cc
new file mode 100644
index 0000000..f878ac7
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_assign_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Assign) {
+  auto* lhs = Global("lhs", ty.i32(), ast::StorageClass::kPrivate);
+  auto* rhs = Global("rhs", ty.i32(), ast::StorageClass::kPrivate);
+  auto* assign = Assign(lhs, rhs);
+  WrapInFunction(assign);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
+  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_binary_test.cc b/src/tint/writer/wgsl/generator_impl_binary_test.cc
new file mode 100644
index 0000000..73d7592
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_binary_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+struct BinaryData {
+  const char* result;
+  ast::BinaryOp op;
+};
+inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
+  out << data.op;
+  return out;
+}
+using WgslBinaryTest = TestParamHelper<BinaryData>;
+TEST_P(WgslBinaryTest, Emit) {
+  auto params = GetParam();
+
+  auto op_ty = [&]() -> const ast::Type* {
+    if (params.op == ast::BinaryOp::kLogicalAnd ||
+        params.op == ast::BinaryOp::kLogicalOr) {
+      return ty.bool_();
+    } else {
+      return ty.u32();
+    }
+  };
+
+  Global("left", op_ty(), ast::StorageClass::kPrivate);
+  Global("right", op_ty(), ast::StorageClass::kPrivate);
+  auto* left = Expr("left");
+  auto* right = Expr("right");
+
+  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    WgslGeneratorImplTest,
+    WgslBinaryTest,
+    testing::Values(
+        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
+        BinaryData{"(left | right)", ast::BinaryOp::kOr},
+        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
+        BinaryData{"(left && right)", ast::BinaryOp::kLogicalAnd},
+        BinaryData{"(left || right)", ast::BinaryOp::kLogicalOr},
+        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
+        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
+        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
+        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
+        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
+        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
+        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
+        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
+        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
+        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
+        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
+        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
+        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_bitcast_test.cc b/src/tint/writer/wgsl/generator_impl_bitcast_test.cc
new file mode 100644
index 0000000..447c89b
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_bitcast_test.cc
@@ -0,0 +1,38 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_Bitcast) {
+  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
+  WrapInFunction(bitcast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "bitcast<f32>(1)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_block_test.cc b/src/tint/writer/wgsl/generator_impl_block_test.cc
new file mode 100644
index 0000000..68e56ae
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_block_test.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Block) {
+  auto* b = Block(create<ast::DiscardStatement>());
+  WrapInFunction(b);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
+    discard;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_break_test.cc b/src/tint/writer/wgsl/generator_impl_break_test.cc
new file mode 100644
index 0000000..02c4c4d
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_break_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Break) {
+  auto* b = create<ast::BreakStatement>();
+  WrapInFunction(Loop(Block(b)));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), "  break;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_call_test.cc b/src/tint/writer/wgsl/generator_impl_call_test.cc
new file mode 100644
index 0000000..79d11df
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_call_test.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
+  Func("my_func", {}, ty.f32(), {Return(1.23f)});
+
+  auto* call = Call("my_func");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func()");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithParams) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.f32(), {Return(1.23f)});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  WrapInFunction(call);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func(param1, param2)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitStatement_Call) {
+  Func("my_func",
+       {
+           Param(Sym(), ty.f32()),
+           Param(Sym(), ty.f32()),
+       },
+       ty.void_(), ast::StatementList{}, ast::AttributeList{});
+  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* call = Call("my_func", "param1", "param2");
+  auto* stmt = CallStmt(call);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_case_test.cc b/src/tint/writer/wgsl/generator_impl_case_test.cc
new file mode 100644
index 0000000..bc4d6ba
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_case_test.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Case) {
+  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
+                   DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
+    break;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Case_MultipleSelectors) {
+  auto* s =
+      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
+             DefaultCase());
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5, 6: {
+    break;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Case_Default) {
+  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  default: {
+    break;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_cast_test.cc b/src/tint/writer/wgsl/generator_impl_cast_test.cc
new file mode 100644
index 0000000..78c533a
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_cast_test.cc
@@ -0,0 +1,49 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_Cast_Scalar) {
+  auto* cast = Construct<f32>(1);
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "f32(1)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_Cast_Vector) {
+  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
+  WrapInFunction(cast);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "vec3<f32>(vec3<i32>(1, 2, 3))");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_constructor_test.cc b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
new file mode 100644
index 0000000..a3b6374
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 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 "gmock/gmock.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using ::testing::HasSubstr;
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Bool) {
+  WrapInFunction(Expr(false));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("false"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Int) {
+  WrapInFunction(Expr(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_UInt) {
+  WrapInFunction(Expr(56779u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Float) {
+  // Use a number close to 1<<30 but whose decimal representation ends in 0.
+  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Float) {
+  WrapInFunction(Construct<f32>(Expr(-1.2e-5f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("f32(-0.000012)"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Bool) {
+  WrapInFunction(Construct<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Int) {
+  WrapInFunction(Construct<i32>(-12345));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("i32(-12345)"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Uint) {
+  WrapInFunction(Construct<u32>(12345u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("u32(12345u)"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Vec) {
+  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("vec3<f32>(1.0, 2.0, 3.0)"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Mat) {
+  WrapInFunction(
+      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("mat2x3<f32>(vec3<f32>(1.0, 2.0, 3.0), "
+                                      "vec3<f32>(3.0, 4.0, 5.0))"));
+}
+
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Array) {
+  WrapInFunction(
+      Construct(ty.array(ty.vec3<f32>(), 3), vec3<f32>(1.0f, 2.0f, 3.0f),
+                vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f)));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
+              HasSubstr("array<vec3<f32>, 3>(vec3<f32>(1.0, 2.0, 3.0), "
+                        "vec3<f32>(4.0, 5.0, 6.0), vec3<f32>(7.0, 8.0, 9.0))"));
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_continue_test.cc b/src/tint/writer/wgsl/generator_impl_continue_test.cc
new file mode 100644
index 0000000..1a31369
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_continue_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Continue) {
+  auto* c = Continue();
+
+  WrapInFunction(Loop(Block(If(false, Block(Break())),  //
+                            c)));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(c)) << gen.error();
+  EXPECT_EQ(gen.result(), "  continue;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_discard_test.cc b/src/tint/writer/wgsl/generator_impl_discard_test.cc
new file mode 100644
index 0000000..5666866
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_discard_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Discard) {
+  auto* stmt = create<ast::DiscardStatement>();
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  discard;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc b/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc
new file mode 100644
index 0000000..4b5da9a
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Fallthrough) {
+  auto* f = create<ast::FallthroughStatement>();
+  WrapInFunction(Switch(1,                        //
+                        Case(Expr(1), Block(f)),  //
+                        DefaultCase()));
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), "  fallthrough;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
new file mode 100644
index 0000000..ebdfcfe
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -0,0 +1,237 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Function) {
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    ast::StatementList{
+                        Return(),
+                    },
+                    ast::AttributeList{});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  fn my_func() {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Function_WithParams) {
+  auto* func = Func(
+      "my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
+      ty.void_(),
+      ast::StatementList{
+          Return(),
+      },
+      ast::AttributeList{});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  fn my_func(a : f32, b : i32) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Function_WithAttribute_WorkgroupSize) {
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    ast::StatementList{Return()},
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kCompute),
+                        WorkgroupSize(2, 4, 6),
+                    });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(2, 4, 6)
+  fn my_func() {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest,
+       Emit_Function_WithAttribute_WorkgroupSize_WithIdent) {
+  GlobalConst("height", ty.i32(), Expr(2));
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
+                    ast::StatementList{Return()},
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kCompute),
+                        WorkgroupSize(2, "height"),
+                    });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(2, height)
+  fn my_func() {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
+  auto* vec4 = ty.vec4<f32>();
+  auto* coord = Param("coord", vec4, {Builtin(ast::Builtin::kPosition)});
+  auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
+  auto* func = Func("frag_main", ast::VariableList{coord, loc1}, ty.void_(),
+                    ast::StatementList{},
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kFragment),
+                    });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  @stage(fragment)
+  fn frag_main(@builtin(position) coord : vec4<f32>, @location(1) loc1 : f32) {
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_ReturnValue) {
+  auto* func = Func("frag_main", ast::VariableList{}, ty.f32(),
+                    ast::StatementList{
+                        Return(1.f),
+                    },
+                    ast::AttributeList{
+                        Stage(ast::PipelineStage::kFragment),
+                    },
+                    ast::AttributeList{
+                        Location(1u),
+                    });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitFunction(func));
+  EXPECT_EQ(gen.result(), R"(  @stage(fragment)
+  fn frag_main() -> @location(1) f32 {
+    return 1.0;
+  }
+)");
+}
+
+// https://crbug.com/tint/297
+TEST_F(WgslGeneratorImplTest,
+       Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
+  // [[block]] struct Data {
+  //   d : f32;
+  // };
+  // @binding(0) @group(0) var<storage> data : Data;
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn a() {
+  //   return;
+  // }
+  //
+  // @stage(compute) @workgroup_size(1)
+  // fn b() {
+  //   return;
+  // }
+
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockAttribute>()});
+
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(0),
+             create<ast::GroupAttribute>(0),
+         });
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("a", ast::VariableList{}, ty.void_(),
+         ast::StatementList{
+             Decl(var),
+             Return(),
+         },
+         ast::AttributeList{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1),
+         });
+  }
+
+  {
+    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
+                    MemberAccessor("data", "d"));
+
+    Func("b", ast::VariableList{}, ty.void_(),
+         ast::StatementList{
+             Decl(var),
+             Return(),
+         },
+         ast::AttributeList{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1),
+         });
+  }
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(@block
+struct Data {
+  d : f32;
+}
+
+@binding(0) @group(0) var<storage, read_write> data : Data;
+
+@stage(compute) @workgroup_size(1)
+fn a() {
+  var v : f32 = data.d;
+  return;
+}
+
+@stage(compute) @workgroup_size(1)
+fn b() {
+  var v : f32 = data.d;
+  return;
+}
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
new file mode 100644
index 0000000..9866a65
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
@@ -0,0 +1,152 @@
+// 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/ast/stage_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_GlobalDeclAfterFunction) {
+  auto* func_var = Var("a", ty.f32());
+  WrapInFunction(func_var);
+
+  Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(1, 1, 1)
+  fn test_function() {
+    var a : f32;
+  }
+
+  var<private> a : f32;
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_GlobalsInterleaved) {
+  Global("a0", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* s0 = Structure("S0", {Member("a", ty.i32())});
+
+  Func("func", ast::VariableList{}, ty.f32(),
+       ast::StatementList{
+           Return("a0"),
+       },
+       ast::AttributeList{});
+
+  Global("a1", ty.f32(), ast::StorageClass::kPrivate);
+
+  auto* s1 = Structure("S1", {Member("a", ty.i32())});
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Decl(Var("s0", ty.Of(s0))),
+           Decl(Var("s1", ty.Of(s1))),
+           Assign("a1", Call("func")),
+       },
+       ast::AttributeList{
+           Stage(ast::PipelineStage::kCompute),
+           WorkgroupSize(1),
+       });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  var<private> a0 : f32;
+
+  struct S0 {
+    a : i32;
+  }
+
+  fn func() -> f32 {
+    return a0;
+  }
+
+  var<private> a1 : f32;
+
+  struct S1 {
+    a : i32;
+  }
+
+  @stage(compute) @workgroup_size(1)
+  fn main() {
+    var s0 : S0;
+    var s1 : S1;
+    a1 = func();
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
+  Global("s", ty.sampler(ast::SamplerKind::kSampler),
+         ast::AttributeList{
+             create<ast::GroupAttribute>(0),
+             create<ast::BindingAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), "  @group(0) @binding(0) var s : sampler;\n");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
+  auto* st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
+  Global("t", st,
+         ast::AttributeList{
+             create<ast::GroupAttribute>(0),
+             create<ast::BindingAttribute>(0),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), "  @group(0) @binding(0) var t : texture_1d<f32>;\n");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_OverridableConstants) {
+  Override("a", ty.f32(), nullptr);
+  Override("b", ty.f32(), nullptr, {Id(7u)});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  override a : f32;
+
+  @id(7) override b : f32;
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_identifier_test.cc b/src/tint/writer/wgsl/generator_impl_identifier_test.cc
new file mode 100644
index 0000000..358fe47
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_identifier_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitIdentifierExpression_Single) {
+  Global("glsl", ty.f32(), ast::StorageClass::kPrivate);
+  auto* i = Expr("glsl");
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
+  EXPECT_EQ(out.str(), "glsl");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_if_test.cc b/src/tint/writer/wgsl/generator_impl_if_test.cc
new file mode 100644
index 0000000..34308df
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_if_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_If) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body);
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_IfWithElseIf) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else if (else_cond) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_IfWithElse) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_body = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(
+      cond, body,
+      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_IfWithMultiple) {
+  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
+
+  auto* else_cond = Expr("else_cond");
+
+  auto* else_body = Block(Return());
+
+  auto* else_body_2 = Block(Return());
+
+  auto* cond = Expr("cond");
+  auto* body = Block(Return());
+  auto* i = If(cond, body,
+               ast::ElseStatementList{
+                   create<ast::ElseStatement>(else_cond, else_body),
+                   create<ast::ElseStatement>(nullptr, else_body_2),
+               });
+  WrapInFunction(i);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
+    return;
+  } else if (else_cond) {
+    return;
+  } else {
+    return;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_literal_test.cc b/src/tint/writer/wgsl/generator_impl_literal_test.cc
new file mode 100644
index 0000000..4c916aa
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_literal_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2020 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 <cstring>
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+// Makes an IEEE 754 binary32 floating point number with
+// - 0 sign if sign is 0, 1 otherwise
+// - 'exponent_bits' is placed in the exponent space.
+//   So, the exponent bias must already be included.
+float MakeFloat(int sign, int biased_exponent, int mantissa) {
+  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
+  // The binary32 exponent is 8 bits, just below the sign.
+  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
+  // The mantissa is the bottom 23 bits.
+  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
+
+  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
+  float result = 0.0f;
+  static_assert(sizeof(result) == sizeof(bits),
+                "expected float and uint32_t to be the same size");
+  std::memcpy(&result, &bits, sizeof(bits));
+  return result;
+}
+
+struct FloatData {
+  float value;
+  std::string expected;
+};
+inline std::ostream& operator<<(std::ostream& out, FloatData data) {
+  out << "{" << data.value << "," << data.expected << "}";
+  return out;
+}
+
+using WgslGenerator_FloatLiteralTest = TestParamHelper<FloatData>;
+
+TEST_P(WgslGenerator_FloatLiteralTest, Emit) {
+  auto* v = Expr(GetParam().value);
+
+  SetResolveOnBuild(false);
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitLiteral(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), GetParam().expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(Zero,
+                         WgslGenerator_FloatLiteralTest,
+                         ::testing::ValuesIn(std::vector<FloatData>{
+                             {0.0f, "0.0"},
+                             {MakeFloat(0, 0, 0), "0.0"},
+                             {MakeFloat(1, 0, 0), "-0.0"}}));
+
+INSTANTIATE_TEST_SUITE_P(Normal,
+                         WgslGenerator_FloatLiteralTest,
+                         ::testing::ValuesIn(std::vector<FloatData>{
+                             {1.0f, "1.0"},
+                             {-1.0f, "-1.0"},
+                             {101.375, "101.375"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    Subnormal,
+    WgslGenerator_FloatLiteralTest,
+    ::testing::ValuesIn(std::vector<FloatData>{
+        {MakeFloat(0, 0, 1), "0x1p-149"},  // Smallest
+        {MakeFloat(1, 0, 1), "-0x1p-149"},
+        {MakeFloat(0, 0, 2), "0x1p-148"},
+        {MakeFloat(1, 0, 2), "-0x1p-148"},
+        {MakeFloat(0, 0, 0x7fffff), "0x1.fffffcp-127"},   // Largest
+        {MakeFloat(1, 0, 0x7fffff), "-0x1.fffffcp-127"},  // Largest
+        {MakeFloat(0, 0, 0xcafebe), "0x1.2bfaf8p-127"},   // Scattered bits
+        {MakeFloat(1, 0, 0xcafebe), "-0x1.2bfaf8p-127"},  // Scattered bits
+        {MakeFloat(0, 0, 0xaaaaa), "0x1.55554p-130"},     // Scattered bits
+        {MakeFloat(1, 0, 0xaaaaa), "-0x1.55554p-130"},    // Scattered bits
+    }));
+
+INSTANTIATE_TEST_SUITE_P(Infinity,
+                         WgslGenerator_FloatLiteralTest,
+                         ::testing::ValuesIn(std::vector<FloatData>{
+                             {MakeFloat(0, 255, 0), "0x1p+128"},
+                             {MakeFloat(1, 255, 0), "-0x1p+128"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // TODO(dneto): It's unclear how Infinity and NaN should be handled.
+    // https://github.com/gpuweb/gpuweb/issues/1769
+    // This test fails on Windows x86-64 because the machine sets the high
+    // mantissa bit on NaNs.
+    DISABLED_NaN,
+    // In the NaN case, the top bit in the mantissa is often used to encode
+    // whether the NaN is signalling or quiet, but no agreement between
+    // different machine architectures on whether 1 means signalling or
+    // if 1 means quiet.
+    WgslGenerator_FloatLiteralTest,
+    ::testing::ValuesIn(std::vector<FloatData>{
+        // LSB only.  Smallest mantissa.
+        {MakeFloat(0, 255, 1), "0x1.000002p+128"},  // Smallest mantissa
+        {MakeFloat(1, 255, 1), "-0x1.000002p+128"},
+        // MSB only.
+        {MakeFloat(0, 255, 0x400000), "0x1.8p+128"},
+        {MakeFloat(1, 255, 0x400000), "-0x1.8p+128"},
+        // All 1s in the mantissa.
+        {MakeFloat(0, 255, 0x7fffff), "0x1.fffffep+128"},
+        {MakeFloat(1, 255, 0x7fffff), "-0x1.fffffep+128"},
+        // Scattered bits, with 0 in top mantissa bit.
+        {MakeFloat(0, 255, 0x20101f), "0x1.40203ep+128"},
+        {MakeFloat(1, 255, 0x20101f), "-0x1.40203ep+128"},
+        // Scattered bits, with 1 in top mantissa bit.
+        {MakeFloat(0, 255, 0x40101f), "0x1.80203ep+128"},
+        {MakeFloat(1, 255, 0x40101f), "-0x1.80203ep+128"}}));
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_loop_test.cc b/src/tint/writer/wgsl/generator_impl_loop_test.cc
new file mode 100644
index 0000000..abdb355
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_loop_test.cc
@@ -0,0 +1,205 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Loop) {
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block();
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  loop {
+    discard;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_LoopWithContinuing) {
+  Func("a_statement", {}, ty.void_(), {});
+
+  auto* body = Block(create<ast::DiscardStatement>());
+  auto* continuing = Block(CallStmt(Call("a_statement")));
+  auto* l = Loop(body, continuing);
+
+  WrapInFunction(l);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  loop {
+    discard;
+
+    continuing {
+      a_statement();
+    }
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
+  // var<workgroup> a : atomic<i32>;
+  // for({ignore(1); ignore(2);}; ; ) {
+  //   return;
+  // }
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt = Block(Ignore(1), Ignore(2));
+  auto* f = For(multi_stmt, nullptr, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for({
+    _ = 1;
+    _ = 2;
+  }; ; ) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleCond) {
+  // for(; true; ) {
+  //   return;
+  // }
+
+  auto* f = For(nullptr, true, nullptr, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(; true; ) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleCont) {
+  // for(; ; i = i + 1) {
+  //   return;
+  // }
+
+  auto* v = Decl(Var("i", ty.i32()));
+  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), Block(Return()));
+  WrapInFunction(v, f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(; ; i = (i + 1)) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtCont) {
+  // var<workgroup> a : atomic<i32>;
+  // for(; ; { ignore(1); ignore(2); }) {
+  //   return;
+  // }
+
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt = Block(Ignore(1), Ignore(2));
+  auto* f = For(nullptr, nullptr, multi_stmt, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(; ; {
+    _ = 1;
+    _ = 2;
+  }) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleInitCondCont) {
+  // for(var i : i32; true; i = i + 1) {
+  //   return;
+  // }
+
+  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
+                Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for(var i : i32; true; i = (i + 1)) {
+    return;
+  }
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtInitCondCont) {
+  // var<workgroup> a : atomic<i32>;
+  // for({ ignore(1); ignore(2); }; true; { ignore(3); ignore(4); }) {
+  //   return;
+  // }
+  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
+  auto* multi_stmt_a = Block(Ignore(1), Ignore(2));
+  auto* multi_stmt_b = Block(Ignore(3), Ignore(4));
+  auto* f = For(multi_stmt_a, Expr(true), multi_stmt_b, Block(Return()));
+  WrapInFunction(f);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  for({
+    _ = 1;
+    _ = 2;
+  }; true; {
+    _ = 3;
+    _ = 4;
+  }) {
+    return;
+  }
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc b/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
new file mode 100644
index 0000000..8926e4f
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor) {
+  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
+
+  auto* expr = MemberAccessor("str", "mem");
+  WrapInFunction(expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "str.mem");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor_OfDref) {
+  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
+
+  auto* p = Const("p", nullptr, AddressOf("str"));
+  auto* expr = MemberAccessor(Deref("p"), "mem");
+  WrapInFunction(p, expr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(*(p)).mem");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_return_test.cc b/src/tint/writer/wgsl/generator_impl_return_test.cc
new file mode 100644
index 0000000..85e55a4
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_return_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Return) {
+  auto* r = Return();
+  WrapInFunction(r);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return;\n");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_ReturnWithValue) {
+  auto* r = Return(123);
+  Func("f", {}, ty.i32(), {r});
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return 123;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_switch_test.cc b/src/tint/writer/wgsl/generator_impl_switch_test.cc
new file mode 100644
index 0000000..3814e9a
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_switch_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_Switch) {
+  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
+
+  auto* def_body = Block(create<ast::BreakStatement>());
+  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+
+  ast::CaseSelectorList case_val;
+  case_val.push_back(Expr(5));
+
+  auto* case_body = Block(create<ast::BreakStatement>());
+
+  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
+
+  ast::CaseStatementList body;
+  body.push_back(case_stmt);
+  body.push_back(def);
+
+  auto* cond = Expr("cond");
+  auto* s = create<ast::SwitchStatement>(cond, body);
+  WrapInFunction(s);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  switch(cond) {
+    case 5: {
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_test.cc b/src/tint/writer/wgsl/generator_impl_test.cc
new file mode 100644
index 0000000..95d3581
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 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/variable.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Generate) {
+  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
+       ast::AttributeList{});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(fn my_func() {
+}
+)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
new file mode 100644
index 0000000..28f9a2e
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -0,0 +1,534 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/sem/depth_texture_type.h"
+#include "src/tint/sem/multisampled_texture_type.h"
+#include "src/tint/sem/sampled_texture_type.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
+  auto* alias = Alias("alias", ty.f32());
+  auto* alias_ty = ty.Of(alias);
+  WrapInFunction(Var("make_reachable", alias_ty));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, alias_ty)) << gen.error();
+  EXPECT_EQ(out.str(), "alias");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+  Alias("make_type_reachable", arr);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, arr)) << gen.error();
+  EXPECT_EQ(out.str(), "array<bool, 4>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Array_Attribute) {
+  auto* a = ty.array(ty.bool_(), 4, 16u);
+  Alias("make_type_reachable", a);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
+  EXPECT_EQ(out.str(), "@stride(16) array<bool, 4>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_RuntimeArray) {
+  auto* a = ty.array(ty.bool_());
+  Alias("make_type_reachable", a);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
+  EXPECT_EQ(out.str(), "array<bool>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Bool) {
+  auto* bool_ = ty.bool_();
+  Alias("make_type_reachable", bool_);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, bool_)) << gen.error();
+  EXPECT_EQ(out.str(), "bool");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_F32) {
+  auto* f32 = ty.f32();
+  Alias("make_type_reachable", f32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, f32)) << gen.error();
+  EXPECT_EQ(out.str(), "f32");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_I32) {
+  auto* i32 = ty.i32();
+  Alias("make_type_reachable", i32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, i32)) << gen.error();
+  EXPECT_EQ(out.str(), "i32");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Matrix) {
+  auto* mat2x3 = ty.mat2x3<f32>();
+  Alias("make_type_reachable", mat2x3);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.error();
+  EXPECT_EQ(out.str(), "mat2x3<f32>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Pointer) {
+  auto* p = ty.pointer<f32>(ast::StorageClass::kWorkgroup);
+  Alias("make_type_reachable", p);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
+  EXPECT_EQ(out.str(), "ptr<workgroup, f32>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_PointerAccessMode) {
+  auto* p =
+      ty.pointer<f32>(ast::StorageClass::kStorage, ast::Access::kReadWrite);
+  Alias("make_type_reachable", p);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
+  EXPECT_EQ(out.str(), "ptr<storage, f32, read_write>");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Struct) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
+  auto* s_ty = ty.Of(s);
+  WrapInFunction(Var("make_reachable", s_ty));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, s_ty)) << gen.error();
+  EXPECT_EQ(out.str(), "S");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32(), {MemberOffset(8)}),
+                               Member("b", ty.f32(), {MemberOffset(16)}),
+                           });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct S {
+  @size(8)
+  padding : u32;
+  a : i32;
+  @size(4)
+  padding_1 : u32;
+  b : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl_WithSymbolCollisions) {
+  auto* s =
+      Structure("S", {
+                         Member("tint_0_padding", ty.i32(), {MemberOffset(8)}),
+                         Member("tint_2_padding", ty.f32(), {MemberOffset(16)}),
+                     });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct S {
+  @size(8)
+  padding : u32;
+  tint_0_padding : i32;
+  @size(4)
+  padding_1 : u32;
+  tint_2_padding : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_StructAlignDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32(), {MemberAlign(8)}),
+                               Member("b", ty.f32(), {MemberAlign(16)}),
+                           });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct S {
+  @align(8)
+  a : i32;
+  @align(16)
+  b : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_StructSizeDecl) {
+  auto* s = Structure("S", {
+                               Member("a", ty.i32(), {MemberSize(16)}),
+                               Member("b", ty.f32(), {MemberSize(32)}),
+                           });
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct S {
+  @size(16)
+  a : i32;
+  @size(32)
+  b : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithAttribute) {
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32(), {MemberAlign(8)}),
+                      },
+                      {create<ast::StructBlockAttribute>()});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(@block
+struct S {
+  a : i32;
+  @align(8)
+  b : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithEntryPointAttributes) {
+  ast::AttributeList attrs;
+  attrs.push_back(create<ast::StructBlockAttribute>());
+
+  auto* s = Structure(
+      "S",
+      ast::StructMemberList{
+          Member("a", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)}),
+          Member("b", ty.f32(), {Location(2u)})},
+      attrs);
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(@block
+struct S {
+  @builtin(vertex_index)
+  a : u32;
+  @location(2)
+  b : f32;
+}
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_U32) {
+  auto* u32 = ty.u32();
+  Alias("make_type_reachable", u32);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, u32)) << gen.error();
+  EXPECT_EQ(out.str(), "u32");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_Vector) {
+  auto* vec3 = ty.vec3<f32>();
+  Alias("make_type_reachable", vec3);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, vec3)) << gen.error();
+  EXPECT_EQ(out.str(), "vec3<f32>");
+}
+
+struct TextureData {
+  ast::TextureDimension dim;
+  const char* name;
+};
+inline std::ostream& operator<<(std::ostream& out, TextureData data) {
+  out << data.name;
+  return out;
+}
+using WgslGenerator_DepthTextureTest = TestParamHelper<TextureData>;
+
+TEST_P(WgslGenerator_DepthTextureTest, EmitType_DepthTexture) {
+  auto param = GetParam();
+
+  auto* d = ty.depth_texture(param.dim);
+  Alias("make_type_reachable", d);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, d)) << gen.error();
+  EXPECT_EQ(out.str(), param.name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    WgslGeneratorImplTest,
+    WgslGenerator_DepthTextureTest,
+    testing::Values(
+        TextureData{ast::TextureDimension::k2d, "texture_depth_2d"},
+        TextureData{ast::TextureDimension::k2dArray, "texture_depth_2d_array"},
+        TextureData{ast::TextureDimension::kCube, "texture_depth_cube"},
+        TextureData{ast::TextureDimension::kCubeArray,
+                    "texture_depth_cube_array"}));
+
+using WgslGenerator_SampledTextureTest = TestParamHelper<TextureData>;
+TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_F32) {
+  auto param = GetParam();
+
+  auto* t = ty.sampled_texture(param.dim, ty.f32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
+}
+
+TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_I32) {
+  auto param = GetParam();
+
+  auto* t = ty.sampled_texture(param.dim, ty.i32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
+}
+
+TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_U32) {
+  auto param = GetParam();
+
+  auto* t = ty.sampled_texture(param.dim, ty.u32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
+}
+INSTANTIATE_TEST_SUITE_P(
+    WgslGeneratorImplTest,
+    WgslGenerator_SampledTextureTest,
+    testing::Values(
+        TextureData{ast::TextureDimension::k1d, "texture_1d"},
+        TextureData{ast::TextureDimension::k2d, "texture_2d"},
+        TextureData{ast::TextureDimension::k2dArray, "texture_2d_array"},
+        TextureData{ast::TextureDimension::k3d, "texture_3d"},
+        TextureData{ast::TextureDimension::kCube, "texture_cube"},
+        TextureData{ast::TextureDimension::kCubeArray, "texture_cube_array"}));
+
+using WgslGenerator_MultiampledTextureTest = TestParamHelper<TextureData>;
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_F32) {
+  auto param = GetParam();
+
+  auto* t = ty.multisampled_texture(param.dim, ty.f32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
+}
+
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_I32) {
+  auto param = GetParam();
+
+  auto* t = ty.multisampled_texture(param.dim, ty.i32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
+}
+
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_U32) {
+  auto param = GetParam();
+
+  auto* t = ty.multisampled_texture(param.dim, ty.u32());
+  Alias("make_type_reachable", t);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
+}
+INSTANTIATE_TEST_SUITE_P(WgslGeneratorImplTest,
+                         WgslGenerator_MultiampledTextureTest,
+                         testing::Values(TextureData{
+                             ast::TextureDimension::k2d,
+                             "texture_multisampled_2d"}));
+
+struct StorageTextureData {
+  ast::TexelFormat fmt;
+  ast::TextureDimension dim;
+  ast::Access access;
+  const char* name;
+};
+inline std::ostream& operator<<(std::ostream& out, StorageTextureData data) {
+  out << data.name;
+  return out;
+}
+using WgslGenerator_StorageTextureTest = TestParamHelper<StorageTextureData>;
+TEST_P(WgslGenerator_StorageTextureTest, EmitType_StorageTexture) {
+  auto param = GetParam();
+
+  auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
+  Global("g", t,
+         ast::AttributeList{
+             create<ast::BindingAttribute>(1),
+             create<ast::GroupAttribute>(2),
+         });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+  EXPECT_EQ(out.str(), param.name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    WgslGeneratorImplTest,
+    WgslGenerator_StorageTextureTest,
+    testing::Values(
+        StorageTextureData{ast::TexelFormat::kRgba8Sint,
+                           ast::TextureDimension::k1d, ast::Access::kWrite,
+                           "texture_storage_1d<rgba8sint, write>"},
+        StorageTextureData{ast::TexelFormat::kRgba8Sint,
+                           ast::TextureDimension::k2d, ast::Access::kWrite,
+                           "texture_storage_2d<rgba8sint, write>"},
+        StorageTextureData{ast::TexelFormat::kRgba8Sint,
+                           ast::TextureDimension::k2dArray, ast::Access::kWrite,
+                           "texture_storage_2d_array<rgba8sint, write>"},
+        StorageTextureData{ast::TexelFormat::kRgba8Sint,
+                           ast::TextureDimension::k3d, ast::Access::kWrite,
+                           "texture_storage_3d<rgba8sint, write>"}));
+
+struct ImageFormatData {
+  ast::TexelFormat fmt;
+  const char* name;
+};
+inline std::ostream& operator<<(std::ostream& out, ImageFormatData data) {
+  out << data.name;
+  return out;
+}
+using WgslGenerator_ImageFormatTest = TestParamHelper<ImageFormatData>;
+TEST_P(WgslGenerator_ImageFormatTest, EmitType_StorageTexture_ImageFormat) {
+  auto param = GetParam();
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitImageFormat(out, param.fmt)) << gen.error();
+  EXPECT_EQ(out.str(), param.name);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WgslGeneratorImplTest,
+    WgslGenerator_ImageFormatTest,
+    testing::Values(
+        ImageFormatData{ast::TexelFormat::kR32Uint, "r32uint"},
+        ImageFormatData{ast::TexelFormat::kR32Sint, "r32sint"},
+        ImageFormatData{ast::TexelFormat::kR32Float, "r32float"},
+        ImageFormatData{ast::TexelFormat::kRgba8Unorm, "rgba8unorm"},
+        ImageFormatData{ast::TexelFormat::kRgba8Snorm, "rgba8snorm"},
+        ImageFormatData{ast::TexelFormat::kRgba8Uint, "rgba8uint"},
+        ImageFormatData{ast::TexelFormat::kRgba8Sint, "rgba8sint"},
+        ImageFormatData{ast::TexelFormat::kRg32Uint, "rg32uint"},
+        ImageFormatData{ast::TexelFormat::kRg32Sint, "rg32sint"},
+        ImageFormatData{ast::TexelFormat::kRg32Float, "rg32float"},
+        ImageFormatData{ast::TexelFormat::kRgba16Uint, "rgba16uint"},
+        ImageFormatData{ast::TexelFormat::kRgba16Sint, "rgba16sint"},
+        ImageFormatData{ast::TexelFormat::kRgba16Float, "rgba16float"},
+        ImageFormatData{ast::TexelFormat::kRgba32Uint, "rgba32uint"},
+        ImageFormatData{ast::TexelFormat::kRgba32Sint, "rgba32sint"},
+        ImageFormatData{ast::TexelFormat::kRgba32Float, "rgba32float"}));
+
+TEST_F(WgslGeneratorImplTest, EmitType_Sampler) {
+  auto* sampler = ty.sampler(ast::SamplerKind::kSampler);
+  Alias("make_type_reachable", sampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
+  EXPECT_EQ(out.str(), "sampler");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitType_SamplerComparison) {
+  auto* sampler = ty.sampler(ast::SamplerKind::kComparisonSampler);
+  Alias("make_type_reachable", sampler);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
+  EXPECT_EQ(out.str(), "sampler_comparison");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_unary_op_test.cc b/src/tint/writer/wgsl/generator_impl_unary_op_test.cc
new file mode 100644
index 0000000..20b75f5
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_unary_op_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslUnaryOpTest = TestHelper;
+
+TEST_F(WgslUnaryOpTest, AddressOf) {
+  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "&(expr)");
+}
+
+TEST_F(WgslUnaryOpTest, Complement) {
+  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "~(expr)");
+}
+
+TEST_F(WgslUnaryOpTest, Indirection) {
+  Global("G", ty.f32(), ast::StorageClass::kPrivate);
+  auto* p = Const(
+      "expr", nullptr,
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
+  WrapInFunction(p, op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "*(expr)");
+}
+
+TEST_F(WgslUnaryOpTest, Not) {
+  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
+  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "!(expr)");
+}
+
+TEST_F(WgslUnaryOpTest, Negation) {
+  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
+  auto* op =
+      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
+  WrapInFunction(op);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "-(expr)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
new file mode 100644
index 0000000..b203964
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement) {
+  auto* var = Var("a", ty.f32());
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  var a : f32;\n");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_InferredType) {
+  auto* var = Var("a", nullptr, ast::StorageClass::kNone, Expr(123));
+
+  auto* stmt = Decl(var);
+  WrapInFunction(stmt);
+
+  GeneratorImpl& gen = Build();
+
+  gen.increment_indent();
+
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  var a = 123;\n");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/generator_impl_variable_test.cc b/src/tint/writer/wgsl/generator_impl_variable_test.cc
new file mode 100644
index 0000000..37bf574
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_variable_test.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/struct_block_attribute.h"
+#include "src/tint/writer/wgsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, EmitVariable) {
+  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(var<private> a : f32;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_StorageClass) {
+  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate);
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(var<private> a : f32;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Read) {
+  auto* s = Structure("S", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* v =
+      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::AttributeList{
+                 create<ast::BindingAttribute>(0),
+                 create<ast::GroupAttribute>(0),
+             });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(@binding(0) @group(0) var<storage, read> a : S;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Write) {
+  auto* s = Structure("S", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* v =
+      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
+             ast::AttributeList{
+                 create<ast::BindingAttribute>(0),
+                 create<ast::GroupAttribute>(0),
+             });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(@binding(0) @group(0) var<storage, write> a : S;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Access_ReadWrite) {
+  auto* s = Structure("S", {Member("a", ty.i32())},
+                      {create<ast::StructBlockAttribute>()});
+  auto* v = Global("a", ty.Of(s), ast::StorageClass::kStorage,
+                   ast::Access::kReadWrite,
+                   ast::AttributeList{
+                       create<ast::BindingAttribute>(0),
+                       create<ast::GroupAttribute>(0),
+                   });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(),
+            R"(@binding(0) @group(0) var<storage, read_write> a : S;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Decorated) {
+  auto* v = Global("a", ty.sampler(ast::SamplerKind::kSampler),
+                   ast::StorageClass::kNone, nullptr,
+                   ast::AttributeList{
+                       create<ast::GroupAttribute>(1),
+                       create<ast::BindingAttribute>(2),
+                   });
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(@group(1) @binding(2) var a : sampler;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Constructor) {
+  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(1.0f));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(var<private> a : f32 = 1.0;)");
+}
+
+TEST_F(WgslGeneratorImplTest, EmitVariable_Const) {
+  auto* v = Const("a", ty.f32(), Expr(1.0f));
+  WrapInFunction(Decl(v));
+
+  GeneratorImpl& gen = Build();
+
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
+  EXPECT_EQ(out.str(), R"(let a : f32 = 1.0;)");
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/wgsl/test_helper.h b/src/tint/writer/wgsl/test_helper.h
new file mode 100644
index 0000000..0a2b845
--- /dev/null
+++ b/src/tint/writer/wgsl/test_helper.h
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_WGSL_TEST_HELPER_H_
+#define SRC_TINT_WRITER_WGSL_TEST_HELPER_H_
+
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/writer/wgsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace wgsl {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {
+ public:
+  TestHelperBase() = default;
+
+  ~TestHelperBase() override = default;
+
+  /// Builds and returns a GeneratorImpl from the program.
+  /// @note The generator is only built once. Multiple calls to Build() will
+  /// return the same GeneratorImpl without rebuilding.
+  /// @return the built generator
+  GeneratorImpl& Build() {
+    if (gen_) {
+      return *gen_;
+    }
+    program = std::make_unique<Program>(std::move(*this));
+    diag::Formatter formatter;
+    [&]() {
+      ASSERT_TRUE(program->IsValid())
+          << formatter.format(program->Diagnostics());
+    }();
+    gen_ = std::make_unique<GeneratorImpl>(program.get());
+    return *gen_;
+  }
+
+  /// The program built with a call to Build()
+  std::unique_ptr<Program> program;
+
+ private:
+  std::unique_ptr<GeneratorImpl> gen_;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace wgsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_WGSL_TEST_HELPER_H_
diff --git a/src/tint/writer/writer.cc b/src/tint/writer/writer.cc
new file mode 100644
index 0000000..05d438d
--- /dev/null
+++ b/src/tint/writer/writer.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/writer.h"
+
+namespace tint {
+namespace writer {
+
+Writer::~Writer() = default;
+
+}  // namespace writer
+}  // namespace tint
diff --git a/src/tint/writer/writer.h b/src/tint/writer/writer.h
new file mode 100644
index 0000000..86d799c
--- /dev/null
+++ b/src/tint/writer/writer.h
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_WRITER_H_
+#define SRC_TINT_WRITER_WRITER_H_
+
+#include <string>
+
+namespace tint {
+namespace writer {
+
+/// Base class for the output writers
+class Writer {
+ public:
+  virtual ~Writer();
+
+  /// @returns the writer error string
+  const std::string& error() const { return error_; }
+
+  /// Converts the module into the desired format
+  /// @returns true on success; false on failure
+  virtual bool Generate() = 0;
+
+ protected:
+  /// Sets the error string
+  /// @param msg the error message
+  void set_error(const std::string& msg) { error_ = msg; }
+
+  /// An error message, if an error was encountered
+  std::string error_;
+};
+
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_TINT_WRITER_WRITER_H_
diff --git a/src/traits.h b/src/traits.h
deleted file mode 100644
index ef702d6..0000000
--- a/src/traits.h
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2020 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_TRAITS_H_
-#define SRC_TRAITS_H_
-
-#include <tuple>
-#include <utility>
-
-namespace tint::traits {
-
-/// Convience type definition for std::decay<T>::type
-template <typename T>
-using Decay = typename std::decay<T>::type;
-
-/// NthTypeOf returns the `N`th type in `Types`
-template <int N, typename... Types>
-using NthTypeOf = typename std::tuple_element<N, std::tuple<Types...>>::type;
-
-/// Signature describes the signature of a function.
-template <typename RETURN, typename... PARAMETERS>
-struct Signature {
-  /// The return type of the function signature
-  using ret = RETURN;
-  /// The parameters of the function signature held in a std::tuple
-  using parameters = std::tuple<PARAMETERS...>;
-  /// The type of the Nth parameter of function signature
-  template <std::size_t N>
-  using parameter = NthTypeOf<N, PARAMETERS...>;
-  /// The total number of parameters
-  static constexpr std::size_t parameter_count = sizeof...(PARAMETERS);
-};
-
-/// SignatureOf is a traits helper that infers the signature of the function,
-/// method, static method, lambda, or function-like object `F`.
-template <typename F>
-struct SignatureOf {
-  /// The signature of the function-like object `F`
-  using type = typename SignatureOf<decltype(&F::operator())>::type;
-};
-
-/// SignatureOf specialization for a regular function or static method.
-template <typename R, typename... ARGS>
-struct SignatureOf<R (*)(ARGS...)> {
-  /// The signature of the function-like object `F`
-  using type = Signature<typename std::decay<R>::type,
-                         typename std::decay<ARGS>::type...>;
-};
-
-/// SignatureOf specialization for a non-static method.
-template <typename R, typename C, typename... ARGS>
-struct SignatureOf<R (C::*)(ARGS...)> {
-  /// The signature of the function-like object `F`
-  using type = Signature<typename std::decay<R>::type,
-                         typename std::decay<ARGS>::type...>;
-};
-
-/// SignatureOf specialization for a non-static, const method.
-template <typename R, typename C, typename... ARGS>
-struct SignatureOf<R (C::*)(ARGS...) const> {
-  /// The signature of the function-like object `F`
-  using type = Signature<typename std::decay<R>::type,
-                         typename std::decay<ARGS>::type...>;
-};
-
-/// SignatureOfT is an alias to `typename SignatureOf<F>::type`.
-template <typename F>
-using SignatureOfT = typename SignatureOf<F>::type;
-
-/// ParameterType is an alias to `typename SignatureOf<F>::type::parameter<N>`.
-template <typename F, std::size_t N>
-using ParameterType = typename SignatureOfT<F>::template parameter<N>;
-
-/// ReturnType is an alias to `typename SignatureOf<F>::type::ret`.
-template <typename F>
-using ReturnType = typename SignatureOfT<F>::ret;
-
-/// IsTypeOrDerived<T, BASE> is true iff `T` is of type `BASE`, or derives from
-/// `BASE`.
-template <typename T, typename BASE>
-static constexpr bool IsTypeOrDerived =
-    std::is_base_of<BASE, Decay<T>>::value ||
-    std::is_same<BASE, Decay<T>>::value;
-
-/// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
-/// invalid type.
-template <bool CONDITION, typename T>
-using EnableIf = typename std::enable_if<CONDITION, T>::type;
-
-/// If `T` is of type `BASE`, or derives from `BASE`, then EnableIfIsType
-/// resolves to type `T`, otherwise an invalid type.
-template <typename T, typename BASE>
-using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>, T>;
-
-/// If `T` is not of type `BASE`, or does not derive from `BASE`, then
-/// EnableIfIsNotType resolves to type `T`, otherwise an invalid type.
-template <typename T, typename BASE>
-using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>, T>;
-
-/// @returns the std::index_sequence with all the indices shifted by OFFSET.
-template <std::size_t OFFSET, std::size_t... INDICES>
-constexpr auto Shift(std::index_sequence<INDICES...>) {
-  return std::integer_sequence<std::size_t, OFFSET + INDICES...>{};
-}
-
-/// @returns a std::integer_sequence with the integers `[OFFSET..OFFSET+COUNT)`
-template <std::size_t OFFSET, std::size_t COUNT>
-constexpr auto Range() {
-  return Shift<OFFSET>(std::make_index_sequence<COUNT>{});
-}
-
-namespace detail {
-
-/// @returns the tuple `t` swizzled by `INDICES`
-template <typename TUPLE, std::size_t... INDICES>
-constexpr auto Swizzle(TUPLE&& t, std::index_sequence<INDICES...>)
-    -> std::tuple<
-        std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>...> {
-  return {std::forward<
-      std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>>(
-      std::get<INDICES>(std::forward<TUPLE>(t)))...};
-}
-
-/// @returns a nullptr of the tuple type `TUPLE` swizzled by `INDICES`.
-/// @note: This function is intended to be used in a `decltype()` expression,
-/// and returns a pointer-to-tuple as the tuple may hold non-constructable
-/// types.
-template <typename TUPLE, std::size_t... INDICES>
-constexpr auto* SwizzlePtrTy(std::index_sequence<INDICES...>) {
-  using Swizzled = std::tuple<std::tuple_element_t<INDICES, TUPLE>...>;
-  return static_cast<Swizzled*>(nullptr);
-}
-
-}  // namespace detail
-
-/// @returns the slice of the tuple `t` with the tuple elements
-/// `[OFFSET..OFFSET+COUNT)`
-template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
-constexpr auto Slice(TUPLE&& t) {
-  return detail::Swizzle<TUPLE>(std::forward<TUPLE>(t), Range<OFFSET, COUNT>());
-}
-
-/// Resolves to the slice of the tuple `t` with the tuple elements
-/// `[OFFSET..OFFSET+COUNT)`
-template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
-using SliceTuple = std::remove_pointer_t<decltype(
-    detail::SwizzlePtrTy<TUPLE>(Range<OFFSET, COUNT>()))>;
-
-}  // namespace tint::traits
-
-#endif  // SRC_TRAITS_H_
diff --git a/src/traits_test.cc b/src/traits_test.cc
deleted file mode 100644
index eab43a1..0000000
--- a/src/traits_test.cc
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2020 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/traits.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace traits {
-
-namespace {
-struct S {};
-void F1(S) {}
-void F3(int, S, float) {}
-}  // namespace
-
-TEST(ParamType, Function) {
-  F1({});        // Avoid unused method warning
-  F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same_v<ParameterType<decltype(&F1), 0>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&F3), 0>, int>);
-  static_assert(std::is_same_v<ParameterType<decltype(&F3), 1>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&F3), 2>, float>);
-  static_assert(std::is_same_v<ReturnType<decltype(&F1)>, void>);
-  static_assert(std::is_same_v<ReturnType<decltype(&F3)>, void>);
-  static_assert(SignatureOfT<decltype(&F1)>::parameter_count == 1);
-  static_assert(SignatureOfT<decltype(&F3)>::parameter_count == 3);
-}
-
-TEST(ParamType, Method) {
-  class C {
-   public:
-    void F1(S) {}
-    void F3(int, S, float) {}
-  };
-  C().F1({});        // Avoid unused method warning
-  C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
-  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
-  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
-}
-
-TEST(ParamType, ConstMethod) {
-  class C {
-   public:
-    void F1(S) const {}
-    void F3(int, S, float) const {}
-  };
-  C().F1({});        // Avoid unused method warning
-  C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
-  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
-  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
-}
-
-TEST(ParamType, StaticMethod) {
-  class C {
-   public:
-    static void F1(S) {}
-    static void F3(int, S, float) {}
-  };
-  C::F1({});        // Avoid unused method warning
-  C::F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
-  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
-  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
-  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
-}
-
-TEST(ParamType, FunctionLike) {
-  using F1 = std::function<void(S)>;
-  using F3 = std::function<void(int, S, float)>;
-  static_assert(std::is_same_v<ParameterType<F1, 0>, S>);
-  static_assert(std::is_same_v<ParameterType<F3, 0>, int>);
-  static_assert(std::is_same_v<ParameterType<F3, 1>, S>);
-  static_assert(std::is_same_v<ParameterType<F3, 2>, float>);
-  static_assert(std::is_same_v<ReturnType<F1>, void>);
-  static_assert(std::is_same_v<ReturnType<F3>, void>);
-  static_assert(SignatureOfT<F1>::parameter_count == 1);
-  static_assert(SignatureOfT<F3>::parameter_count == 3);
-}
-
-TEST(ParamType, Lambda) {
-  auto l1 = [](S) {};
-  auto l3 = [](int, S, float) {};
-  static_assert(std::is_same_v<ParameterType<decltype(l1), 0>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(l3), 0>, int>);
-  static_assert(std::is_same_v<ParameterType<decltype(l3), 1>, S>);
-  static_assert(std::is_same_v<ParameterType<decltype(l3), 2>, float>);
-  static_assert(std::is_same_v<ReturnType<decltype(l1)>, void>);
-  static_assert(std::is_same_v<ReturnType<decltype(l3)>, void>);
-  static_assert(SignatureOfT<decltype(l1)>::parameter_count == 1);
-  static_assert(SignatureOfT<decltype(l3)>::parameter_count == 3);
-}
-
-TEST(Slice, Empty) {
-  auto sliced = Slice<0, 0>(std::make_tuple<>());
-  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
-}
-
-TEST(Slice, SingleElementSliceEmpty) {
-  auto sliced = Slice<0, 0>(std::make_tuple<int>(1));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
-}
-
-TEST(Slice, SingleElementSliceFull) {
-  auto sliced = Slice<0, 1>(std::make_tuple<int>(1));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 1);
-  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
-                "");
-  EXPECT_EQ(std::get<0>(sliced), 1);
-}
-
-TEST(Slice, MixedTupleSliceEmpty) {
-  auto sliced = Slice<1, 0>(std::make_tuple<int, bool, float>(1, true, 2.0f));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 0);
-}
-
-TEST(Slice, MixedTupleSliceFull) {
-  auto sliced = Slice<0, 3>(std::make_tuple<int, bool, float>(1, true, 2.0f));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 3);
-  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
-                "");
-  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
-                "");
-  static_assert(
-      std::is_same_v<std::tuple_element_t<2, decltype(sliced)>, float>);
-  EXPECT_EQ(std::get<0>(sliced), 1);
-  EXPECT_EQ(std::get<1>(sliced), true);
-  EXPECT_EQ(std::get<2>(sliced), 2.0f);
-}
-
-TEST(Slice, MixedTupleSliceLowPart) {
-  auto sliced = Slice<0, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 2);
-  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
-                "");
-  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
-                "");
-  EXPECT_EQ(std::get<0>(sliced), 1);
-  EXPECT_EQ(std::get<1>(sliced), true);
-}
-
-TEST(Slice, MixedTupleSliceHighPart) {
-  auto sliced = Slice<1, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
-  static_assert(std::tuple_size_v<decltype(sliced)> == 2);
-  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, bool>,
-                "");
-  static_assert(
-      std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, float>);
-  EXPECT_EQ(std::get<0>(sliced), true);
-  EXPECT_EQ(std::get<1>(sliced), 2.0f);
-}
-
-TEST(Slice, PreservesRValueRef) {
-  int i;
-  int& int_ref = i;
-  auto tuple = std::forward_as_tuple(std::move(int_ref));
-  static_assert(std::is_same_v<int&&,  //
-                               std::tuple_element_t<0, decltype(tuple)>>);
-  auto sliced = Slice<0, 1>(std::move(tuple));
-  static_assert(std::is_same_v<int&&,  //
-                               std::tuple_element_t<0, decltype(sliced)>>);
-}
-
-TEST(SliceTuple, Empty) {
-  using sliced = SliceTuple<0, 0, std::tuple<>>;
-  static_assert(std::tuple_size_v<sliced> == 0);
-}
-
-TEST(SliceTuple, SingleElementSliceEmpty) {
-  using sliced = SliceTuple<0, 0, std::tuple<int>>;
-  static_assert(std::tuple_size_v<sliced> == 0);
-}
-
-TEST(SliceTuple, SingleElementSliceFull) {
-  using sliced = SliceTuple<0, 1, std::tuple<int>>;
-  static_assert(std::tuple_size_v<sliced> == 1);
-  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
-}
-
-TEST(SliceTuple, MixedTupleSliceEmpty) {
-  using sliced = SliceTuple<1, 0, std::tuple<int, bool, float>>;
-  static_assert(std::tuple_size_v<sliced> == 0);
-}
-
-TEST(SliceTuple, MixedTupleSliceFull) {
-  using sliced = SliceTuple<0, 3, std::tuple<int, bool, float>>;
-  static_assert(std::tuple_size_v<sliced> == 3);
-  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
-  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
-  static_assert(std::is_same_v<std::tuple_element_t<2, sliced>, float>);
-}
-
-TEST(SliceTuple, MixedTupleSliceLowPart) {
-  using sliced = SliceTuple<0, 2, std::tuple<int, bool, float>>;
-  static_assert(std::tuple_size_v<sliced> == 2);
-  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
-  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
-}
-
-TEST(SliceTuple, MixedTupleSliceHighPart) {
-  using sliced = SliceTuple<1, 2, std::tuple<int, bool, float>>;
-  static_assert(std::tuple_size_v<sliced> == 2);
-  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, bool>);
-  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, float>);
-}
-
-}  // namespace traits
-}  // namespace tint
diff --git a/src/transform/add_empty_entry_point.cc b/src/transform/add_empty_entry_point.cc
deleted file mode 100644
index 824c636..0000000
--- a/src/transform/add_empty_entry_point.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/add_empty_entry_point.h"
-
-#include <utility>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::AddEmptyEntryPoint);
-
-namespace tint {
-namespace transform {
-
-AddEmptyEntryPoint::AddEmptyEntryPoint() = default;
-
-AddEmptyEntryPoint::~AddEmptyEntryPoint() = default;
-
-bool AddEmptyEntryPoint::ShouldRun(const Program* program,
-                                   const DataMap&) const {
-  for (auto* func : program->AST().Functions()) {
-    if (func->IsEntryPoint()) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void AddEmptyEntryPoint::Run(CloneContext& ctx,
-                             const DataMap&,
-                             DataMap&) const {
-  ctx.dst->Func(ctx.dst->Symbols().New("unused_entry_point"), {},
-                ctx.dst->ty.void_(), {},
-                {ctx.dst->Stage(ast::PipelineStage::kCompute),
-                 ctx.dst->WorkgroupSize(1)});
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/add_empty_entry_point.h b/src/transform/add_empty_entry_point.h
deleted file mode 100644
index 31821ac..0000000
--- a/src/transform/add_empty_entry_point.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
-#define SRC_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Add an empty entry point to the module, if no other entry points exist.
-class AddEmptyEntryPoint : public Castable<AddEmptyEntryPoint, Transform> {
- public:
-  /// Constructor
-  AddEmptyEntryPoint();
-  /// Destructor
-  ~AddEmptyEntryPoint() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
diff --git a/src/transform/add_empty_entry_point_test.cc b/src/transform/add_empty_entry_point_test.cc
deleted file mode 100644
index df25e88..0000000
--- a/src/transform/add_empty_entry_point_test.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/add_empty_entry_point.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using AddEmptyEntryPointTest = TransformTest;
-
-TEST_F(AddEmptyEntryPointTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_TRUE(ShouldRun<AddEmptyEntryPoint>(src));
-}
-
-TEST_F(AddEmptyEntryPointTest, ShouldRunExistingEntryPoint) {
-  auto* src = R"(
-[[stage(compute), workgroup_size(1)]]
-fn existing() {}
-)";
-
-  EXPECT_FALSE(ShouldRun<AddEmptyEntryPoint>(src));
-}
-
-TEST_F(AddEmptyEntryPointTest, EmptyModule) {
-  auto* src = R"()";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn unused_entry_point() {
-}
-)";
-
-  auto got = Run<AddEmptyEntryPoint>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddEmptyEntryPointTest, ExistingEntryPoint) {
-  auto* src = R"(
-@stage(fragment)
-fn main() {
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<AddEmptyEntryPoint>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddEmptyEntryPointTest, NameClash) {
-  auto* src = R"(var<private> unused_entry_point : f32;)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn unused_entry_point_1() {
-}
-
-var<private> unused_entry_point : f32;
-)";
-
-  auto got = Run<AddEmptyEntryPoint>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/add_spirv_block_attribute.cc b/src/transform/add_spirv_block_attribute.cc
deleted file mode 100644
index 24a1903..0000000
--- a/src/transform/add_spirv_block_attribute.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/add_spirv_block_attribute.h"
-
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/variable.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::AddSpirvBlockAttribute);
-TINT_INSTANTIATE_TYPEINFO(
-    tint::transform::AddSpirvBlockAttribute::SpirvBlockAttribute);
-
-namespace tint {
-namespace transform {
-
-AddSpirvBlockAttribute::AddSpirvBlockAttribute() = default;
-
-AddSpirvBlockAttribute::~AddSpirvBlockAttribute() = default;
-
-void AddSpirvBlockAttribute::Run(CloneContext& ctx,
-                                 const DataMap&,
-                                 DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  // Collect the set of structs that are nested in other types.
-  std::unordered_set<const sem::Struct*> nested_structs;
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* arr = sem.Get<sem::Array>(node->As<ast::Array>())) {
-      if (auto* nested_str = arr->ElemType()->As<sem::Struct>()) {
-        nested_structs.insert(nested_str);
-      }
-    } else if (auto* str = sem.Get<sem::Struct>(node->As<ast::Struct>())) {
-      for (auto* member : str->Members()) {
-        if (auto* nested_str = member->Type()->As<sem::Struct>()) {
-          nested_structs.insert(nested_str);
-        }
-      }
-    }
-  }
-
-  // A map from a type in the source program to a block-decorated wrapper that
-  // contains it in the destination program.
-  std::unordered_map<const sem::Type*, const ast::Struct*> wrapper_structs;
-
-  // Process global variables that are buffers.
-  for (auto* var : ctx.src->AST().GlobalVariables()) {
-    auto* sem_var = sem.Get<sem::GlobalVariable>(var);
-    if (var->declared_storage_class != ast::StorageClass::kStorage &&
-        var->declared_storage_class != ast::StorageClass::kUniform) {
-      continue;
-    }
-
-    auto* ty = sem.Get(var->type);
-    auto* str = ty->As<sem::Struct>();
-    if (!str || nested_structs.count(str)) {
-      const char* kMemberName = "inner";
-
-      // This is a non-struct or a struct that is nested somewhere else, so we
-      // need to wrap it first.
-      auto* wrapper = utils::GetOrCreate(wrapper_structs, ty, [&]() {
-        auto* block =
-            ctx.dst->ASTNodes().Create<SpirvBlockAttribute>(ctx.dst->ID());
-        auto wrapper_name = ctx.src->Symbols().NameFor(var->symbol) + "_block";
-        auto* ret = ctx.dst->create<ast::Struct>(
-            ctx.dst->Symbols().New(wrapper_name),
-            ast::StructMemberList{
-                ctx.dst->Member(kMemberName, CreateASTTypeFor(ctx, ty))},
-            ast::AttributeList{block});
-        ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), var, ret);
-        return ret;
-      });
-      ctx.Replace(var->type, ctx.dst->ty.Of(wrapper));
-
-      // Insert a member accessor to get the original type from the wrapper at
-      // any usage of the original variable.
-      for (auto* user : sem_var->Users()) {
-        ctx.Replace(
-            user->Declaration(),
-            ctx.dst->MemberAccessor(ctx.Clone(var->symbol), kMemberName));
-      }
-    } else {
-      // Add a block attribute to this struct directly.
-      auto* block =
-          ctx.dst->ASTNodes().Create<SpirvBlockAttribute>(ctx.dst->ID());
-      ctx.InsertFront(str->Declaration()->attributes, block);
-    }
-  }
-
-  ctx.Clone();
-}
-
-AddSpirvBlockAttribute::SpirvBlockAttribute::SpirvBlockAttribute(ProgramID pid)
-    : Base(pid) {}
-AddSpirvBlockAttribute::SpirvBlockAttribute::~SpirvBlockAttribute() = default;
-std::string AddSpirvBlockAttribute::SpirvBlockAttribute::InternalName() const {
-  return "spirv_block";
-}
-
-const AddSpirvBlockAttribute::SpirvBlockAttribute*
-AddSpirvBlockAttribute::SpirvBlockAttribute::Clone(CloneContext* ctx) const {
-  return ctx->dst->ASTNodes()
-      .Create<AddSpirvBlockAttribute::SpirvBlockAttribute>(ctx->dst->ID());
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/add_spirv_block_attribute.h b/src/transform/add_spirv_block_attribute.h
deleted file mode 100644
index eb9ac82..0000000
--- a/src/transform/add_spirv_block_attribute.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
-#define SRC_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
-
-#include <string>
-
-#include "src/ast/internal_attribute.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// AddSpirvBlockAttribute is a transform that adds an
-/// `@internal(spirv_block)` attribute to any structure that is used as the
-/// store type of a buffer. If that structure is nested inside another structure
-/// or an array, then it is wrapped inside another structure which gets the
-/// `@internal(spirv_block)` attribute instead.
-class AddSpirvBlockAttribute
-    : public Castable<AddSpirvBlockAttribute, Transform> {
- public:
-  /// SpirvBlockAttribute is an InternalAttribute that is used to decorate a
-  // structure that needs a SPIR-V block attribute.
-  class SpirvBlockAttribute
-      : public Castable<SpirvBlockAttribute, ast::InternalAttribute> {
-   public:
-    /// Constructor
-    /// @param program_id the identifier of the program that owns this node
-    explicit SpirvBlockAttribute(ProgramID program_id);
-    /// Destructor
-    ~SpirvBlockAttribute() override;
-
-    /// @return a short description of the internal attribute which will be
-    /// displayed as `@internal(<name>)`
-    std::string InternalName() const override;
-
-    /// Performs a deep clone of this object using the CloneContext `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned object
-    const SpirvBlockAttribute* Clone(CloneContext* ctx) const override;
-  };
-
-  /// Constructor
-  AddSpirvBlockAttribute();
-
-  /// Destructor
-  ~AddSpirvBlockAttribute() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_ADD_SPIRV_BLOCK_ATTRIBUTE_H_
diff --git a/src/transform/add_spirv_block_attribute_test.cc b/src/transform/add_spirv_block_attribute_test.cc
deleted file mode 100644
index 256e8b7..0000000
--- a/src/transform/add_spirv_block_attribute_test.cc
+++ /dev/null
@@ -1,615 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/add_spirv_block_attribute.h"
-
-#include <memory>
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using AddSpirvBlockAttributeTest = TransformTest;
-
-TEST_F(AddSpirvBlockAttributeTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Noop_UsedForPrivateVar) {
-  auto* src = R"(
-struct S {
-  f : f32;
-}
-
-var<private> p : S;
-
-@stage(fragment)
-fn main() {
-  p.f = 1.0;
-}
-)";
-  auto* expect = src;
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Noop_UsedForShaderIO) {
-  auto* src = R"(
-struct S {
-  @location(0)
-  f : f32;
-}
-
-@stage(fragment)
-fn main() -> S {
-  return S();
-}
-)";
-  auto* expect = src;
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, BasicScalar) {
-  auto* src = R"(
-@group(0) @binding(0)
-var<uniform> u : f32;
-
-@stage(fragment)
-fn main() {
-  let f = u;
-}
-)";
-  auto* expect = R"(
-@internal(spirv_block)
-struct u_block {
-  inner : f32;
-}
-
-@group(0) @binding(0) var<uniform> u : u_block;
-
-@stage(fragment)
-fn main() {
-  let f = u.inner;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, BasicArray) {
-  auto* src = R"(
-@group(0) @binding(0)
-var<uniform> u : array<vec4<f32>, 4u>;
-
-@stage(fragment)
-fn main() {
-  let a = u;
-}
-)";
-  auto* expect = R"(
-@internal(spirv_block)
-struct u_block {
-  inner : array<vec4<f32>, 4u>;
-}
-
-@group(0) @binding(0) var<uniform> u : u_block;
-
-@stage(fragment)
-fn main() {
-  let a = u.inner;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, BasicArray_Alias) {
-  auto* src = R"(
-type Numbers = array<vec4<f32>, 4u>;
-
-@group(0) @binding(0)
-var<uniform> u : Numbers;
-
-@stage(fragment)
-fn main() {
-  let a = u;
-}
-)";
-  auto* expect = R"(
-type Numbers = array<vec4<f32>, 4u>;
-
-@internal(spirv_block)
-struct u_block {
-  inner : array<vec4<f32>, 4u>;
-}
-
-@group(0) @binding(0) var<uniform> u : u_block;
-
-@stage(fragment)
-fn main() {
-  let a = u.inner;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, BasicStruct) {
-  auto* src = R"(
-struct S {
-  f : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u : S;
-
-@stage(fragment)
-fn main() {
-  let f = u.f;
-}
-)";
-  auto* expect = R"(
-@internal(spirv_block)
-struct S {
-  f : f32;
-}
-
-@group(0) @binding(0) var<uniform> u : S;
-
-@stage(fragment)
-fn main() {
-  let f = u.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Nested_OuterBuffer_InnerNotBuffer) {
-  auto* src = R"(
-struct Inner {
-  f : f32;
-};
-
-struct Outer {
-  i : Inner;
-};
-
-@group(0) @binding(0)
-var<uniform> u : Outer;
-
-@stage(fragment)
-fn main() {
-  let f = u.i.f;
-}
-)";
-  auto* expect = R"(
-struct Inner {
-  f : f32;
-}
-
-@internal(spirv_block)
-struct Outer {
-  i : Inner;
-}
-
-@group(0) @binding(0) var<uniform> u : Outer;
-
-@stage(fragment)
-fn main() {
-  let f = u.i.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Nested_OuterBuffer_InnerBuffer) {
-  auto* src = R"(
-struct Inner {
-  f : f32;
-};
-
-struct Outer {
-  i : Inner;
-};
-
-@group(0) @binding(0)
-var<uniform> u0 : Outer;
-
-@group(0) @binding(1)
-var<uniform> u1 : Inner;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.f;
-}
-)";
-  auto* expect = R"(
-struct Inner {
-  f : f32;
-}
-
-@internal(spirv_block)
-struct Outer {
-  i : Inner;
-}
-
-@group(0) @binding(0) var<uniform> u0 : Outer;
-
-@internal(spirv_block)
-struct u1_block {
-  inner : Inner;
-}
-
-@group(0) @binding(1) var<uniform> u1 : u1_block;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.inner.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Nested_OuterNotBuffer_InnerBuffer) {
-  auto* src = R"(
-struct Inner {
-  f : f32;
-};
-
-struct Outer {
-  i : Inner;
-};
-
-var<private> p : Outer;
-
-@group(0) @binding(1)
-var<uniform> u : Inner;
-
-@stage(fragment)
-fn main() {
-  let f0 = p.i.f;
-  let f1 = u.f;
-}
-)";
-  auto* expect = R"(
-struct Inner {
-  f : f32;
-}
-
-struct Outer {
-  i : Inner;
-}
-
-var<private> p : Outer;
-
-@internal(spirv_block)
-struct u_block {
-  inner : Inner;
-}
-
-@group(0) @binding(1) var<uniform> u : u_block;
-
-@stage(fragment)
-fn main() {
-  let f0 = p.i.f;
-  let f1 = u.inner.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Nested_InnerUsedForMultipleBuffers) {
-  auto* src = R"(
-struct Inner {
-  f : f32;
-};
-
-struct S {
-  i : Inner;
-};
-
-@group(0) @binding(0)
-var<uniform> u0 : S;
-
-@group(0) @binding(1)
-var<uniform> u1 : Inner;
-
-@group(0) @binding(2)
-var<uniform> u2 : Inner;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.f;
-  let f2 = u2.f;
-}
-)";
-  auto* expect = R"(
-struct Inner {
-  f : f32;
-}
-
-@internal(spirv_block)
-struct S {
-  i : Inner;
-}
-
-@group(0) @binding(0) var<uniform> u0 : S;
-
-@internal(spirv_block)
-struct u1_block {
-  inner : Inner;
-}
-
-@group(0) @binding(1) var<uniform> u1 : u1_block;
-
-@group(0) @binding(2) var<uniform> u2 : u1_block;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.inner.f;
-  let f2 = u2.inner.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, StructInArray) {
-  auto* src = R"(
-struct S {
-  f : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u : S;
-
-@stage(fragment)
-fn main() {
-  let f = u.f;
-  let a = array<S, 4>();
-}
-)";
-  auto* expect = R"(
-struct S {
-  f : f32;
-}
-
-@internal(spirv_block)
-struct u_block {
-  inner : S;
-}
-
-@group(0) @binding(0) var<uniform> u : u_block;
-
-@stage(fragment)
-fn main() {
-  let f = u.inner.f;
-  let a = array<S, 4>();
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, StructInArray_MultipleBuffers) {
-  auto* src = R"(
-struct S {
-  f : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u0 : S;
-
-@group(0) @binding(1)
-var<uniform> u1 : S;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.f;
-  let f1 = u1.f;
-  let a = array<S, 4>();
-}
-)";
-  auto* expect = R"(
-struct S {
-  f : f32;
-}
-
-@internal(spirv_block)
-struct u0_block {
-  inner : S;
-}
-
-@group(0) @binding(0) var<uniform> u0 : u0_block;
-
-@group(0) @binding(1) var<uniform> u1 : u0_block;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.inner.f;
-  let f1 = u1.inner.f;
-  let a = array<S, 4>();
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest, Aliases_Nested_OuterBuffer_InnerBuffer) {
-  auto* src = R"(
-struct Inner {
-  f : f32;
-};
-
-type MyInner = Inner;
-
-struct Outer {
-  i : MyInner;
-};
-
-type MyOuter = Outer;
-
-@group(0) @binding(0)
-var<uniform> u0 : MyOuter;
-
-@group(0) @binding(1)
-var<uniform> u1 : MyInner;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.f;
-}
-)";
-  auto* expect = R"(
-struct Inner {
-  f : f32;
-}
-
-type MyInner = Inner;
-
-@internal(spirv_block)
-struct Outer {
-  i : MyInner;
-}
-
-type MyOuter = Outer;
-
-@group(0) @binding(0) var<uniform> u0 : MyOuter;
-
-@internal(spirv_block)
-struct u1_block {
-  inner : Inner;
-}
-
-@group(0) @binding(1) var<uniform> u1 : u1_block;
-
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.inner.f;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(AddSpirvBlockAttributeTest,
-       Aliases_Nested_OuterBuffer_InnerBuffer_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.f;
-}
-
-@group(0) @binding(1)
-var<uniform> u1 : MyInner;
-
-type MyInner = Inner;
-
-@group(0) @binding(0)
-var<uniform> u0 : MyOuter;
-
-type MyOuter = Outer;
-
-struct Outer {
-  i : MyInner;
-};
-
-struct Inner {
-  f : f32;
-};
-)";
-  auto* expect = R"(
-@stage(fragment)
-fn main() {
-  let f0 = u0.i.f;
-  let f1 = u1.inner.f;
-}
-
-@internal(spirv_block)
-struct u1_block {
-  inner : Inner;
-}
-
-@group(0) @binding(1) var<uniform> u1 : u1_block;
-
-type MyInner = Inner;
-
-@group(0) @binding(0) var<uniform> u0 : MyOuter;
-
-type MyOuter = Outer;
-
-@internal(spirv_block)
-struct Outer {
-  i : MyInner;
-}
-
-struct Inner {
-  f : f32;
-}
-)";
-
-  auto got = Run<AddSpirvBlockAttribute>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/array_length_from_uniform.cc b/src/transform/array_length_from_uniform.cc
deleted file mode 100644
index 9735317..0000000
--- a/src/transform/array_length_from_uniform.cc
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/array_length_from_uniform.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/variable.h"
-#include "src/transform/simplify_pointers.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Config);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Result);
-
-namespace tint {
-namespace transform {
-
-ArrayLengthFromUniform::ArrayLengthFromUniform() = default;
-ArrayLengthFromUniform::~ArrayLengthFromUniform() = default;
-
-/// Iterate over all arrayLength() builtins that operate on
-/// storage buffer variables.
-/// @param ctx the CloneContext.
-/// @param functor of type void(const ast::CallExpression*, const
-/// sem::VariableUser, const sem::GlobalVariable*). It takes in an
-/// ast::CallExpression of the arrayLength call expression node, a
-/// sem::VariableUser of the used storage buffer variable, and the
-/// sem::GlobalVariable for the storage buffer.
-template <typename F>
-static void IterateArrayLengthOnStorageVar(CloneContext& ctx, F&& functor) {
-  auto& sem = ctx.src->Sem();
-
-  // Find all calls to the arrayLength() builtin.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    auto* call_expr = node->As<ast::CallExpression>();
-    if (!call_expr) {
-      continue;
-    }
-
-    auto* call = sem.Get(call_expr);
-    auto* builtin = call->Target()->As<sem::Builtin>();
-    if (!builtin || builtin->Type() != sem::BuiltinType::kArrayLength) {
-      continue;
-    }
-
-    // Get the storage buffer that contains the runtime array.
-    // Since we require SimplifyPointers, we can assume that the arrayLength()
-    // call has one of two forms:
-    //   arrayLength(&struct_var.array_member)
-    //   arrayLength(&array_var)
-    auto* param = call_expr->args[0]->As<ast::UnaryOpExpression>();
-    if (!param || param->op != ast::UnaryOp::kAddressOf) {
-      TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "expected form of arrayLength argument to be &array_var or "
-             "&struct_var.array_member";
-      break;
-    }
-    auto* storage_buffer_expr = param->expr;
-    if (auto* accessor = param->expr->As<ast::MemberAccessorExpression>()) {
-      storage_buffer_expr = accessor->structure;
-    }
-    auto* storage_buffer_sem = sem.Get<sem::VariableUser>(storage_buffer_expr);
-    if (!storage_buffer_sem) {
-      TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "expected form of arrayLength argument to be &array_var or "
-             "&struct_var.array_member";
-      break;
-    }
-
-    // Get the index to use for the buffer size array.
-    auto* var = tint::As<sem::GlobalVariable>(storage_buffer_sem->Variable());
-    if (!var) {
-      TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "storage buffer is not a global variable";
-      break;
-    }
-    functor(call_expr, storage_buffer_sem, var);
-  }
-}
-
-bool ArrayLengthFromUniform::ShouldRun(const Program* program,
-                                       const DataMap&) const {
-  for (auto* fn : program->AST().Functions()) {
-    if (auto* sem_fn = program->Sem().Get(fn)) {
-      for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
-          return true;
-        }
-      }
-    }
-  }
-  return false;
-}
-
-void ArrayLengthFromUniform::Run(CloneContext& ctx,
-                                 const DataMap& inputs,
-                                 DataMap& outputs) const {
-  auto* cfg = inputs.Get<Config>();
-  if (cfg == nullptr) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  const char* kBufferSizeMemberName = "buffer_size";
-
-  // Determine the size of the buffer size array.
-  uint32_t max_buffer_size_index = 0;
-
-  IterateArrayLengthOnStorageVar(
-      ctx, [&](const ast::CallExpression*, const sem::VariableUser*,
-               const sem::GlobalVariable* var) {
-        auto binding = var->BindingPoint();
-        auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
-        if (idx_itr == cfg->bindpoint_to_size_index.end()) {
-          return;
-        }
-        if (idx_itr->second > max_buffer_size_index) {
-          max_buffer_size_index = idx_itr->second;
-        }
-      });
-
-  // Get (or create, on first call) the uniform buffer that will receive the
-  // size of each storage buffer in the module.
-  const ast::Variable* buffer_size_ubo = nullptr;
-  auto get_ubo = [&]() {
-    if (!buffer_size_ubo) {
-      // Emit an array<vec4<u32>, N>, where N is 1/4 number of elements.
-      // We do this because UBOs require an element stride that is 16-byte
-      // aligned.
-      auto* buffer_size_struct = ctx.dst->Structure(
-          ctx.dst->Sym(),
-          {ctx.dst->Member(
-              kBufferSizeMemberName,
-              ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
-                                (max_buffer_size_index / 4) + 1))});
-      buffer_size_ubo = ctx.dst->Global(
-          ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct),
-          ast::StorageClass::kUniform,
-          ast::AttributeList{ctx.dst->GroupAndBinding(
-              cfg->ubo_binding.group, cfg->ubo_binding.binding)});
-    }
-    return buffer_size_ubo;
-  };
-
-  std::unordered_set<uint32_t> used_size_indices;
-
-  IterateArrayLengthOnStorageVar(
-      ctx, [&](const ast::CallExpression* call_expr,
-               const sem::VariableUser* storage_buffer_sem,
-               const sem::GlobalVariable* var) {
-        auto binding = var->BindingPoint();
-        auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
-        if (idx_itr == cfg->bindpoint_to_size_index.end()) {
-          return;
-        }
-
-        uint32_t size_index = idx_itr->second;
-        used_size_indices.insert(size_index);
-
-        // Load the total storage buffer size from the UBO.
-        uint32_t array_index = size_index / 4;
-        auto* vec_expr = ctx.dst->IndexAccessor(
-            ctx.dst->MemberAccessor(get_ubo()->symbol, kBufferSizeMemberName),
-            array_index);
-        uint32_t vec_index = size_index % 4;
-        auto* total_storage_buffer_size =
-            ctx.dst->IndexAccessor(vec_expr, vec_index);
-
-        // Calculate actual array length
-        //                total_storage_buffer_size - array_offset
-        // array_length = ----------------------------------------
-        //                             array_stride
-        const ast::Expression* total_size = total_storage_buffer_size;
-        auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
-        const sem::Array* array_type = nullptr;
-        if (auto* str = storage_buffer_type->As<sem::Struct>()) {
-          // The variable is a struct, so subtract the byte offset of the array
-          // member.
-          auto* array_member_sem = str->Members().back();
-          array_type = array_member_sem->Type()->As<sem::Array>();
-          total_size = ctx.dst->Sub(total_storage_buffer_size,
-                                    array_member_sem->Offset());
-        } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
-          array_type = arr;
-        } else {
-          TINT_ICE(Transform, ctx.dst->Diagnostics())
-              << "expected form of arrayLength argument to be &array_var or "
-                 "&struct_var.array_member";
-          return;
-        }
-        auto* array_length = ctx.dst->Div(total_size, array_type->Stride());
-
-        ctx.Replace(call_expr, array_length);
-      });
-
-  ctx.Clone();
-
-  outputs.Add<Result>(used_size_indices);
-}
-
-ArrayLengthFromUniform::Config::Config(sem::BindingPoint ubo_bp)
-    : ubo_binding(ubo_bp) {}
-ArrayLengthFromUniform::Config::Config(const Config&) = default;
-ArrayLengthFromUniform::Config& ArrayLengthFromUniform::Config::operator=(
-    const Config&) = default;
-ArrayLengthFromUniform::Config::~Config() = default;
-
-ArrayLengthFromUniform::Result::Result(
-    std::unordered_set<uint32_t> used_size_indices_in)
-    : used_size_indices(std::move(used_size_indices_in)) {}
-ArrayLengthFromUniform::Result::Result(const Result&) = default;
-ArrayLengthFromUniform::Result::~Result() = default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/array_length_from_uniform.h b/src/transform/array_length_from_uniform.h
deleted file mode 100644
index 305e47c..0000000
--- a/src/transform/array_length_from_uniform.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
-#define SRC_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
-
-#include <unordered_map>
-#include <unordered_set>
-
-#include "src/sem/binding_point.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace transform {
-
-/// ArrayLengthFromUniform is a transform that implements calls to arrayLength()
-/// by calculating the length from the total size of the storage buffer, which
-/// is received via a uniform buffer.
-///
-/// The generated uniform buffer will have the form:
-/// ```
-/// struct buffer_size_struct {
-///  buffer_size : array<u32, 8>;
-/// };
-///
-/// @group(0) @binding(30)
-/// var<uniform> buffer_size_ubo : buffer_size_struct;
-/// ```
-/// The binding group and number used for this uniform buffer is provided via
-/// the `Config` transform input. The `Config` struct also defines the mapping
-/// from a storage buffer's `BindingPoint` to the array index that will be used
-/// to get the size of that buffer.
-///
-/// This transform assumes that the `SimplifyPointers`
-/// transforms have been run before it so that arguments to the arrayLength
-/// builtin always have the form `&resource.array`.
-///
-/// @note Depends on the following transforms to have been run first:
-/// * SimplifyPointers
-class ArrayLengthFromUniform
-    : public Castable<ArrayLengthFromUniform, Transform> {
- public:
-  /// Constructor
-  ArrayLengthFromUniform();
-  /// Destructor
-  ~ArrayLengthFromUniform() override;
-
-  /// Configuration options for the ArrayLengthFromUniform transform.
-  struct Config : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param ubo_bp the binding point to use for the generated uniform buffer.
-    explicit Config(sem::BindingPoint ubo_bp);
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Copy assignment
-    /// @return this Config
-    Config& operator=(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// The binding point to use for the generated uniform buffer.
-    sem::BindingPoint ubo_binding;
-
-    /// The mapping from binding point to the index for the buffer size lookup.
-    std::unordered_map<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
-  };
-
-  /// Information produced about what the transform did.
-  /// If there were no calls to the arrayLength() builtin, then no Result will
-  /// be emitted.
-  struct Result : public Castable<Result, transform::Data> {
-    /// Constructor
-    /// @param used_size_indices Indices into the UBO that are statically used.
-    explicit Result(std::unordered_set<uint32_t> used_size_indices);
-
-    /// Copy constructor
-    Result(const Result&);
-
-    /// Destructor
-    ~Result() override;
-
-    /// Indices into the UBO that are statically used.
-    const std::unordered_set<uint32_t> used_size_indices;
-  };
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
diff --git a/src/transform/array_length_from_uniform_test.cc b/src/transform/array_length_from_uniform_test.cc
deleted file mode 100644
index 1ae41ba..0000000
--- a/src/transform/array_length_from_uniform_test.cc
+++ /dev/null
@@ -1,589 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/array_length_from_uniform.h"
-
-#include <utility>
-
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ArrayLengthFromUniformTest = TransformTest;
-
-TEST_F(ArrayLengthFromUniformTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<ArrayLengthFromUniform>(src));
-}
-
-TEST_F(ArrayLengthFromUniformTest, ShouldRunNoArrayLength) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-[[group(0), binding(0)]] var<storage, read> sb : SB;
-
-[[stage(compute), workgroup_size(1)]]
-fn main() {
-}
-)";
-
-  EXPECT_FALSE(ShouldRun<ArrayLengthFromUniform>(src));
-}
-
-TEST_F(ArrayLengthFromUniformTest, ShouldRunWithArrayLength) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-[[group(0), binding(0)]] var<storage, read> sb : SB;
-
-[[stage(compute), workgroup_size(1)]]
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<ArrayLengthFromUniform>(src));
-}
-
-TEST_F(ArrayLengthFromUniformTest, Error_MissingTransformData) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-[[group(0), binding(0)]] var<storage, read> sb : SB;
-
-[[stage(compute), workgroup_size(1)]]
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect =
-      "error: missing transform data for "
-      "tint::transform::ArrayLengthFromUniform";
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ArrayLengthFromUniformTest, Basic) {
-  auto* src = R"(
-@group(0) @binding(0) var<storage, read> sb : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-@group(0) @binding(0) var<storage, read> sb : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 4u);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, BasicInStruct) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, WithStride) {
-  auto* src = R"(
-@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 64u);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, WithStride_InStruct) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  y : f32;
-  arr : @stride(64) array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-struct SB {
-  x : i32;
-  y : f32;
-  arr : @stride(64) array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 8u) / 64u);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) {
-  auto* src = R"(
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-};
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-};
-struct SB4 {
-  x : i32;
-  arr4 : array<vec4<f32>>;
-};
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
-@group(3) @binding(2) var<storage, read> sb4 : SB4;
-@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len2 : u32 = arrayLength(&(sb2.arr2));
-  var len3 : u32 = arrayLength(&sb3);
-  var len4 : u32 = arrayLength(&(sb4.arr4));
-  var len5 : u32 = arrayLength(&sb5);
-  var x : u32 = (len1 + len2 + len3 + len4 + len5);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 2u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-}
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-}
-
-struct SB4 {
-  x : i32;
-  arr4 : array<vec4<f32>>;
-}
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-
-@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
-
-@group(3) @binding(2) var<storage, read> sb4 : SB4;
-
-@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-  var len2 : u32 = ((tint_symbol_1.buffer_size[0u][1u] - 16u) / 16u);
-  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
-  var len4 : u32 = ((tint_symbol_1.buffer_size[0u][3u] - 16u) / 16u);
-  var len5 : u32 = (tint_symbol_1.buffer_size[1u][0u] / 16u);
-  var x : u32 = ((((len1 + len2) + len3) + len4) + len5);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0, 1, 2, 3, 4}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, MultipleUnusedStorageBuffers) {
-  auto* src = R"(
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-};
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-};
-struct SB4 {
-  x : i32;
-  arr4 : array<vec4<f32>>;
-};
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
-@group(3) @binding(2) var<storage, read> sb4 : SB4;
-@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len3 : u32 = arrayLength(&sb3);
-  var x : u32 = (len1 + len3);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-}
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-}
-
-struct SB4 {
-  x : i32;
-  arr4 : array<vec4<f32>>;
-}
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-
-@group(2) @binding(2) var<storage, read> sb3 : array<vec4<f32>>;
-
-@group(3) @binding(2) var<storage, read> sb4 : SB4;
-
-@group(4) @binding(2) var<storage, read> sb5 : array<vec4<f32>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
-  var x : u32 = (len1 + len3);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3);
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0, 2}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, NoArrayLengthCalls) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = &(sb.arr);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(src, str(got));
-  EXPECT_EQ(got.data.Get<ArrayLengthFromUniform::Result>(), nullptr);
-}
-
-TEST_F(ArrayLengthFromUniformTest, MissingBindingPointToIndexMapping) {
-  auto* src = R"(
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-};
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-};
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len2 : u32 = arrayLength(&(sb2.arr2));
-  var x : u32 = (len1 + len2);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-}
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-}
-
-@group(0) @binding(2) var<storage, read> sb1 : SB1;
-
-@group(1) @binding(2) var<storage, read> sb2 : SB2;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-  var len2 : u32 = arrayLength(&(sb2.arr2));
-  var x : u32 = (len1 + len2);
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-TEST_F(ArrayLengthFromUniformTest, OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  buffer_size : array<vec4<u32>, 1u>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-)";
-
-  ArrayLengthFromUniform::Config cfg({0, 30u});
-  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
-
-  DataMap data;
-  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
-
-  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
-            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/binding_remapper.cc b/src/transform/binding_remapper.cc
deleted file mode 100644
index 54fb554..0000000
--- a/src/transform/binding_remapper.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/binding_remapper.h"
-
-#include <string>
-#include <unordered_set>
-#include <utility>
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/function.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper::Remappings);
-
-namespace tint {
-namespace transform {
-
-BindingRemapper::Remappings::Remappings(BindingPoints bp,
-                                        AccessControls ac,
-                                        bool may_collide)
-    : binding_points(std::move(bp)),
-      access_controls(std::move(ac)),
-      allow_collisions(may_collide) {}
-
-BindingRemapper::Remappings::Remappings(const Remappings&) = default;
-BindingRemapper::Remappings::~Remappings() = default;
-
-BindingRemapper::BindingRemapper() = default;
-BindingRemapper::~BindingRemapper() = default;
-
-bool BindingRemapper::ShouldRun(const Program*, const DataMap& inputs) const {
-  if (auto* remappings = inputs.Get<Remappings>()) {
-    return !remappings->binding_points.empty() ||
-           !remappings->access_controls.empty();
-  }
-  return false;
-}
-
-void BindingRemapper::Run(CloneContext& ctx,
-                          const DataMap& inputs,
-                          DataMap&) const {
-  auto* remappings = inputs.Get<Remappings>();
-  if (!remappings) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  // A set of post-remapped binding points that need to be decorated with a
-  // DisableValidationAttribute to disable binding-point-collision validation
-  std::unordered_set<sem::BindingPoint> add_collision_attr;
-
-  if (remappings->allow_collisions) {
-    // Scan for binding point collisions generated by this transform.
-    // Populate all collisions in the `add_collision_attr` set.
-    for (auto* func_ast : ctx.src->AST().Functions()) {
-      if (!func_ast->IsEntryPoint()) {
-        continue;
-      }
-      auto* func = ctx.src->Sem().Get(func_ast);
-      std::unordered_map<sem::BindingPoint, int> binding_point_counts;
-      for (auto* var : func->TransitivelyReferencedGlobals()) {
-        if (auto binding_point = var->Declaration()->BindingPoint()) {
-          BindingPoint from{binding_point.group->value,
-                            binding_point.binding->value};
-          auto bp_it = remappings->binding_points.find(from);
-          if (bp_it != remappings->binding_points.end()) {
-            // Remapped
-            BindingPoint to = bp_it->second;
-            if (binding_point_counts[to]++) {
-              add_collision_attr.emplace(to);
-            }
-          } else {
-            // No remapping
-            if (binding_point_counts[from]++) {
-              add_collision_attr.emplace(from);
-            }
-          }
-        }
-      }
-    }
-  }
-
-  for (auto* var : ctx.src->AST().GlobalVariables()) {
-    if (auto binding_point = var->BindingPoint()) {
-      // The original binding point
-      BindingPoint from{binding_point.group->value,
-                        binding_point.binding->value};
-
-      // The binding point after remapping
-      BindingPoint bp = from;
-
-      // Replace any group or binding attributes.
-      // Note: This has to be performed *before* remapping access controls, as
-      // `ctx.Clone(var->attributes)` depend on these replacements.
-      auto bp_it = remappings->binding_points.find(from);
-      if (bp_it != remappings->binding_points.end()) {
-        BindingPoint to = bp_it->second;
-        auto* new_group = ctx.dst->create<ast::GroupAttribute>(to.group);
-        auto* new_binding = ctx.dst->create<ast::BindingAttribute>(to.binding);
-
-        ctx.Replace(binding_point.group, new_group);
-        ctx.Replace(binding_point.binding, new_binding);
-        bp = to;
-      }
-
-      // Replace any access controls.
-      auto ac_it = remappings->access_controls.find(from);
-      if (ac_it != remappings->access_controls.end()) {
-        ast::Access ac = ac_it->second;
-        if (ac > ast::Access::kLastValid) {
-          ctx.dst->Diagnostics().add_error(
-              diag::System::Transform,
-              "invalid access mode (" +
-                  std::to_string(static_cast<uint32_t>(ac)) + ")");
-          return;
-        }
-        auto* sem = ctx.src->Sem().Get(var);
-        if (sem->StorageClass() != ast::StorageClass::kStorage) {
-          ctx.dst->Diagnostics().add_error(
-              diag::System::Transform,
-              "cannot apply access control to variable with storage class " +
-                  std::string(ast::ToString(sem->StorageClass())));
-          return;
-        }
-        auto* ty = sem->Type()->UnwrapRef();
-        const ast::Type* inner_ty = CreateASTTypeFor(ctx, ty);
-        auto* new_var = ctx.dst->create<ast::Variable>(
-            ctx.Clone(var->source), ctx.Clone(var->symbol),
-            var->declared_storage_class, ac, inner_ty, false, false,
-            ctx.Clone(var->constructor), ctx.Clone(var->attributes));
-        ctx.Replace(var, new_var);
-      }
-
-      // Add `DisableValidationAttribute`s if required
-      if (add_collision_attr.count(bp)) {
-        auto* attribute =
-            ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
-        ctx.InsertBefore(var->attributes, *var->attributes.begin(), attribute);
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/binding_remapper.h b/src/transform/binding_remapper.h
deleted file mode 100644
index 0af33f6..0000000
--- a/src/transform/binding_remapper.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_BINDING_REMAPPER_H_
-#define SRC_TRANSFORM_BINDING_REMAPPER_H_
-
-#include <unordered_map>
-
-#include "src/ast/access.h"
-#include "src/sem/binding_point.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// BindingPoint is an alias to sem::BindingPoint
-using BindingPoint = sem::BindingPoint;
-
-/// BindingRemapper is a transform used to remap resource binding points and
-/// access controls.
-class BindingRemapper : public Castable<BindingRemapper, Transform> {
- public:
-  /// BindingPoints is a map of old binding point to new binding point
-  using BindingPoints = std::unordered_map<BindingPoint, BindingPoint>;
-
-  /// AccessControls is a map of old binding point to new access control
-  using AccessControls = std::unordered_map<BindingPoint, ast::Access>;
-
-  /// Remappings is consumed by the BindingRemapper transform.
-  /// Data holds information about shader usage and constant buffer offsets.
-  struct Remappings : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param bp a map of new binding points
-    /// @param ac a map of new access controls
-    /// @param may_collide If true, then validation will be disabled for
-    /// binding point collisions generated by this transform
-    Remappings(BindingPoints bp, AccessControls ac, bool may_collide = true);
-
-    /// Copy constructor
-    Remappings(const Remappings&);
-
-    /// Destructor
-    ~Remappings() override;
-
-    /// A map of old binding point to new binding point
-    const BindingPoints binding_points;
-
-    /// A map of old binding point to new access controls
-    const AccessControls access_controls;
-
-    /// If true, then validation will be disabled for binding point collisions
-    /// generated by this transform
-    const bool allow_collisions;
-  };
-
-  /// Constructor
-  BindingRemapper();
-  ~BindingRemapper() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_BINDING_REMAPPER_H_
diff --git a/src/transform/binding_remapper_test.cc b/src/transform/binding_remapper_test.cc
deleted file mode 100644
index 2a8d7eb..0000000
--- a/src/transform/binding_remapper_test.cc
+++ /dev/null
@@ -1,423 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/binding_remapper.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using BindingRemapperTest = TransformTest;
-
-TEST_F(BindingRemapperTest, ShouldRunNoRemappings) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<BindingRemapper>(src));
-}
-
-TEST_F(BindingRemapperTest, ShouldRunEmptyRemappings) {
-  auto* src = R"()";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
-                                        BindingRemapper::AccessControls{});
-
-  EXPECT_FALSE(ShouldRun<BindingRemapper>(src, data));
-}
-
-TEST_F(BindingRemapperTest, ShouldRunBindingPointRemappings) {
-  auto* src = R"()";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{
-          {{2, 1}, {1, 2}},
-      },
-      BindingRemapper::AccessControls{});
-
-  EXPECT_TRUE(ShouldRun<BindingRemapper>(src, data));
-}
-
-TEST_F(BindingRemapperTest, ShouldRunAccessControlRemappings) {
-  auto* src = R"()";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
-                                        BindingRemapper::AccessControls{
-                                            {{2, 1}, ast::Access::kWrite},
-                                        });
-
-  EXPECT_TRUE(ShouldRun<BindingRemapper>(src, data));
-}
-
-TEST_F(BindingRemapperTest, NoRemappings) {
-  auto* src = R"(
-struct S {
-  a : f32;
-}
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(BindingRemapper::BindingPoints{},
-                                        BindingRemapper::AccessControls{});
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, RemapBindingPoints) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-@group(1) @binding(2) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{
-          {{2, 1}, {1, 2}},  // Remap
-          {{4, 5}, {6, 7}},  // Not found
-                             // Keep @group(3) @binding(2) as is
-      },
-      BindingRemapper::AccessControls{});
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, RemapAccessControls) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, write> b : S;
-
-@group(4) @binding(3) var<storage, read> c : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-@group(2) @binding(1) var<storage, write> a : S;
-
-@group(3) @binding(2) var<storage, write> b : S;
-
-@group(4) @binding(3) var<storage, read> c : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{},
-      BindingRemapper::AccessControls{
-          {{2, 1}, ast::Access::kWrite},  // Modify access control
-          // Keep @group(3) @binding(2) as is
-          {{4, 3}, ast::Access::kRead},  // Add access control
-      });
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// TODO(crbug.com/676): Possibly enable if the spec allows for access
-// attributes in type aliases. If not, just remove.
-TEST_F(BindingRemapperTest, DISABLED_RemapAccessControlsWithAliases) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-type, read ReadOnlyS = S;
-
-type, write WriteOnlyS = S;
-
-type A = S;
-
-@group(2) @binding(1) var<storage> a : ReadOnlyS;
-
-@group(3) @binding(2) var<storage> b : WriteOnlyS;
-
-@group(4) @binding(3) var<storage> c : A;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-};
-
-type, read ReadOnlyS = S;
-
-type, write WriteOnlyS = S;
-
-type A = S;
-
-@group(2) @binding(1) var<storage, write> a : S;
-
-@group(3) @binding(2) var<storage> b : WriteOnlyS;
-
-@group(4) @binding(3) var<storage, write> c : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{},
-      BindingRemapper::AccessControls{
-          {{2, 1}, ast::Access::kWrite},  // Modify access control
-          // Keep @group(3) @binding(2) as is
-          {{4, 3}, ast::Access::kRead},  // Add access control
-      });
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, RemapAll) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-@group(4) @binding(5) var<storage, write> a : S;
-
-@group(6) @binding(7) var<storage, write> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{
-          {{2, 1}, {4, 5}},
-          {{3, 2}, {6, 7}},
-      },
-      BindingRemapper::AccessControls{
-          {{2, 1}, ast::Access::kWrite},
-          {{3, 2}, ast::Access::kWrite},
-      });
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, BindingCollisionsSameEntryPoint) {
-  auto* src = R"(
-struct S {
-  i : i32;
-};
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@group(4) @binding(3) var<storage, read> c : S;
-
-@group(5) @binding(4) var<storage, read> d : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : i32 = (((a.i + b.i) + c.i) + d.i);
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  i : i32;
-}
-
-@internal(disable_validation__binding_point_collision) @group(1) @binding(1) var<storage, read> a : S;
-
-@internal(disable_validation__binding_point_collision) @group(1) @binding(1) var<storage, read> b : S;
-
-@internal(disable_validation__binding_point_collision) @group(5) @binding(4) var<storage, read> c : S;
-
-@internal(disable_validation__binding_point_collision) @group(5) @binding(4) var<storage, read> d : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : i32 = (((a.i + b.i) + c.i) + d.i);
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{
-          {{2, 1}, {1, 1}},
-          {{3, 2}, {1, 1}},
-          {{4, 3}, {5, 4}},
-      },
-      BindingRemapper::AccessControls{}, true);
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, BindingCollisionsDifferentEntryPoints) {
-  auto* src = R"(
-struct S {
-  i : i32;
-};
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@group(4) @binding(3) var<storage, read> c : S;
-
-@group(5) @binding(4) var<storage, read> d : S;
-
-@stage(compute) @workgroup_size(1)
-fn f1() {
-  let x : i32 = (a.i + c.i);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f2() {
-  let x : i32 = (b.i + d.i);
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  i : i32;
-}
-
-@group(1) @binding(1) var<storage, read> a : S;
-
-@group(1) @binding(1) var<storage, read> b : S;
-
-@group(5) @binding(4) var<storage, read> c : S;
-
-@group(5) @binding(4) var<storage, read> d : S;
-
-@stage(compute) @workgroup_size(1)
-fn f1() {
-  let x : i32 = (a.i + c.i);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f2() {
-  let x : i32 = (b.i + d.i);
-}
-)";
-
-  DataMap data;
-  data.Add<BindingRemapper::Remappings>(
-      BindingRemapper::BindingPoints{
-          {{2, 1}, {1, 1}},
-          {{3, 2}, {1, 1}},
-          {{4, 3}, {5, 4}},
-      },
-      BindingRemapper::AccessControls{}, true);
-  auto got = Run<BindingRemapper>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(BindingRemapperTest, NoData) {
-  auto* src = R"(
-struct S {
-  a : f32;
-}
-
-@group(2) @binding(1) var<storage, read> a : S;
-
-@group(3) @binding(2) var<storage, read> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<BindingRemapper>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
deleted file mode 100644
index 87e4eb5..0000000
--- a/src/transform/calculate_array_length.cc
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/calculate_array_length.h"
-
-#include <unordered_map>
-#include <utility>
-
-#include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/struct.h"
-#include "src/sem/variable.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::CalculateArrayLength);
-TINT_INSTANTIATE_TYPEINFO(
-    tint::transform::CalculateArrayLength::BufferSizeIntrinsic);
-
-namespace tint {
-namespace transform {
-
-namespace {
-
-/// ArrayUsage describes a runtime array usage.
-/// It is used as a key by the array_length_by_usage map.
-struct ArrayUsage {
-  ast::BlockStatement const* const block;
-  sem::Variable const* const buffer;
-  bool operator==(const ArrayUsage& rhs) const {
-    return block == rhs.block && buffer == rhs.buffer;
-  }
-  struct Hasher {
-    inline std::size_t operator()(const ArrayUsage& u) const {
-      return utils::Hash(u.block, u.buffer);
-    }
-  };
-};
-
-}  // namespace
-
-CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic(ProgramID pid)
-    : Base(pid) {}
-CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default;
-std::string CalculateArrayLength::BufferSizeIntrinsic::InternalName() const {
-  return "intrinsic_buffer_size";
-}
-
-const CalculateArrayLength::BufferSizeIntrinsic*
-CalculateArrayLength::BufferSizeIntrinsic::Clone(CloneContext* ctx) const {
-  return ctx->dst->ASTNodes().Create<CalculateArrayLength::BufferSizeIntrinsic>(
-      ctx->dst->ID());
-}
-
-CalculateArrayLength::CalculateArrayLength() = default;
-CalculateArrayLength::~CalculateArrayLength() = default;
-
-bool CalculateArrayLength::ShouldRun(const Program* program,
-                                     const DataMap&) const {
-  for (auto* fn : program->AST().Functions()) {
-    if (auto* sem_fn = program->Sem().Get(fn)) {
-      for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
-          return true;
-        }
-      }
-    }
-  }
-  return false;
-}
-
-void CalculateArrayLength::Run(CloneContext& ctx,
-                               const DataMap&,
-                               DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  // get_buffer_size_intrinsic() emits the function decorated with
-  // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
-  // [RW]ByteAddressBuffer.GetDimensions().
-  std::unordered_map<const sem::Type*, Symbol> buffer_size_intrinsics;
-  auto get_buffer_size_intrinsic = [&](const sem::Type* buffer_type) {
-    return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
-      auto name = ctx.dst->Sym();
-      auto* type = CreateASTTypeFor(ctx, buffer_type);
-      auto* disable_validation = ctx.dst->Disable(
-          ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
-      ctx.dst->AST().AddFunction(ctx.dst->create<ast::Function>(
-          name,
-          ast::VariableList{
-              // Note: The buffer parameter requires the kStorage StorageClass
-              // in order for HLSL to emit this as a ByteAddressBuffer.
-              ctx.dst->create<ast::Variable>(
-                  ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
-                  ast::Access::kUndefined, type, true, false, nullptr,
-                  ast::AttributeList{disable_validation}),
-              ctx.dst->Param("result",
-                             ctx.dst->ty.pointer(ctx.dst->ty.u32(),
-                                                 ast::StorageClass::kFunction)),
-          },
-          ctx.dst->ty.void_(), nullptr,
-          ast::AttributeList{
-              ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
-          },
-          ast::AttributeList{}));
-
-      return name;
-    });
-  };
-
-  std::unordered_map<ArrayUsage, Symbol, ArrayUsage::Hasher>
-      array_length_by_usage;
-
-  // Find all the arrayLength() calls...
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* call_expr = node->As<ast::CallExpression>()) {
-      auto* call = sem.Get(call_expr);
-      if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
-          // We're dealing with an arrayLength() call
-
-          // A runtime-sized array can only appear as the store type of a
-          // variable, or the last element of a structure (which cannot itself
-          // be nested). Given that we require SimplifyPointers, we can assume
-          // that the arrayLength() call has one of two forms:
-          //   arrayLength(&struct_var.array_member)
-          //   arrayLength(&array_var)
-          auto* arg = call_expr->args[0];
-          auto* address_of = arg->As<ast::UnaryOpExpression>();
-          if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
-            TINT_ICE(Transform, ctx.dst->Diagnostics())
-                << "arrayLength() expected address-of, got "
-                << arg->TypeInfo().name;
-          }
-          auto* storage_buffer_expr = address_of->expr;
-          if (auto* accessor =
-                  storage_buffer_expr->As<ast::MemberAccessorExpression>()) {
-            storage_buffer_expr = accessor->structure;
-          }
-          auto* storage_buffer_sem =
-              sem.Get<sem::VariableUser>(storage_buffer_expr);
-          if (!storage_buffer_sem) {
-            TINT_ICE(Transform, ctx.dst->Diagnostics())
-                << "expected form of arrayLength argument to be &array_var or "
-                   "&struct_var.array_member";
-            break;
-          }
-          auto* storage_buffer_var = storage_buffer_sem->Variable();
-          auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
-
-          // Generate BufferSizeIntrinsic for this storage type if we haven't
-          // already
-          auto buffer_size = get_buffer_size_intrinsic(storage_buffer_type);
-
-          // Find the current statement block
-          auto* block = call->Stmt()->Block()->Declaration();
-
-          auto array_length = utils::GetOrCreate(
-              array_length_by_usage, {block, storage_buffer_var}, [&] {
-                // First time this array length is used for this block.
-                // Let's calculate it.
-
-                // Construct the variable that'll hold the result of
-                // RWByteAddressBuffer.GetDimensions()
-                auto* buffer_size_result = ctx.dst->Decl(
-                    ctx.dst->Var(ctx.dst->Sym(), ctx.dst->ty.u32(),
-                                 ast::StorageClass::kNone, ctx.dst->Expr(0u)));
-
-                // Call storage_buffer.GetDimensions(&buffer_size_result)
-                auto* call_get_dims = ctx.dst->CallStmt(ctx.dst->Call(
-                    // BufferSizeIntrinsic(X, ARGS...) is
-                    // translated to:
-                    //  X.GetDimensions(ARGS..) by the writer
-                    buffer_size, ctx.Clone(storage_buffer_expr),
-                    ctx.dst->AddressOf(
-                        ctx.dst->Expr(buffer_size_result->variable->symbol))));
-
-                // Calculate actual array length
-                //                total_storage_buffer_size - array_offset
-                // array_length = ----------------------------------------
-                //                             array_stride
-                auto name = ctx.dst->Sym();
-                const ast::Expression* total_size =
-                    ctx.dst->Expr(buffer_size_result->variable);
-                const sem::Array* array_type = nullptr;
-                if (auto* str = storage_buffer_type->As<sem::Struct>()) {
-                  // The variable is a struct, so subtract the byte offset of
-                  // the array member.
-                  auto* array_member_sem = str->Members().back();
-                  array_type = array_member_sem->Type()->As<sem::Array>();
-                  total_size =
-                      ctx.dst->Sub(total_size, array_member_sem->Offset());
-                } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
-                  array_type = arr;
-                } else {
-                  TINT_ICE(Transform, ctx.dst->Diagnostics())
-                      << "expected form of arrayLength argument to be "
-                         "&array_var or &struct_var.array_member";
-                  return name;
-                }
-                uint32_t array_stride = array_type->Size();
-                auto* array_length_var = ctx.dst->Decl(
-                    ctx.dst->Const(name, ctx.dst->ty.u32(),
-                                   ctx.dst->Div(total_size, array_stride)));
-
-                // Insert the array length calculations at the top of the block
-                ctx.InsertBefore(block->statements, block->statements[0],
-                                 buffer_size_result);
-                ctx.InsertBefore(block->statements, block->statements[0],
-                                 call_get_dims);
-                ctx.InsertBefore(block->statements, block->statements[0],
-                                 array_length_var);
-                return name;
-              });
-
-          // Replace the call to arrayLength() with the array length variable
-          ctx.Replace(call_expr, ctx.dst->Expr(array_length));
-        }
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/calculate_array_length.h b/src/transform/calculate_array_length.h
deleted file mode 100644
index 525a2e8..0000000
--- a/src/transform/calculate_array_length.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
-#define SRC_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
-
-#include <string>
-
-#include "src/ast/internal_attribute.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace transform {
-
-/// CalculateArrayLength is a transform used to replace calls to arrayLength()
-/// with a value calculated from the size of the storage buffer.
-///
-/// @note Depends on the following transforms to have been run first:
-/// * SimplifyPointers
-class CalculateArrayLength : public Castable<CalculateArrayLength, Transform> {
- public:
-  /// BufferSizeIntrinsic is an InternalAttribute that's applied to intrinsic
-  /// functions used to obtain the runtime size of a storage buffer.
-  class BufferSizeIntrinsic
-      : public Castable<BufferSizeIntrinsic, ast::InternalAttribute> {
-   public:
-    /// Constructor
-    /// @param program_id the identifier of the program that owns this node
-    explicit BufferSizeIntrinsic(ProgramID program_id);
-    /// Destructor
-    ~BufferSizeIntrinsic() override;
-
-    /// @return "buffer_size"
-    std::string InternalName() const override;
-
-    /// Performs a deep clone of this object using the CloneContext `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned object
-    const BufferSizeIntrinsic* Clone(CloneContext* ctx) const override;
-  };
-
-  /// Constructor
-  CalculateArrayLength();
-  /// Destructor
-  ~CalculateArrayLength() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_CALCULATE_ARRAY_LENGTH_H_
diff --git a/src/transform/calculate_array_length_test.cc b/src/transform/calculate_array_length_test.cc
deleted file mode 100644
index fa1f274..0000000
--- a/src/transform/calculate_array_length_test.cc
+++ /dev/null
@@ -1,625 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/calculate_array_length.h"
-
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using CalculateArrayLengthTest = TransformTest;
-
-TEST_F(CalculateArrayLengthTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<CalculateArrayLength>(src));
-}
-
-TEST_F(CalculateArrayLengthTest, ShouldRunNoArrayLength) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-[[group(0), binding(0)]] var<storage, read> sb : SB;
-
-[[stage(compute), workgroup_size(1)]]
-fn main() {
-}
-)";
-
-  EXPECT_FALSE(ShouldRun<CalculateArrayLength>(src));
-}
-
-TEST_F(CalculateArrayLengthTest, ShouldRunWithArrayLength) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-[[group(0), binding(0)]] var<storage, read> sb : SB;
-
-[[stage(compute), workgroup_size(1)]]
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<CalculateArrayLength>(src));
-}
-
-TEST_F(CalculateArrayLengthTest, BasicArray) {
-  auto* src = R"(
-@group(0) @binding(0) var<storage, read> sb : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
-
-@group(0) @binding(0) var<storage, read> sb : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
-  var len : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, BasicInStruct) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-  var len : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, ArrayOfStruct) {
-  auto* src = R"(
-struct S {
-  f : f32;
-}
-
-@group(0) @binding(0) var<storage, read> arr : array<S>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let len = arrayLength(&arr);
-}
-)";
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<S>, result : ptr<function, u32>)
-
-struct S {
-  f : f32;
-}
-
-@group(0) @binding(0) var<storage, read> arr : array<S>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(arr, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
-  let len = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, ArrayOfArrayOfStruct) {
-  auto* src = R"(
-struct S {
-  f : f32;
-}
-
-@group(0) @binding(0) var<storage, read> arr : array<array<S, 4>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let len = arrayLength(&arr);
-}
-)";
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<array<S, 4u>>, result : ptr<function, u32>)
-
-struct S {
-  f : f32;
-}
-
-@group(0) @binding(0) var<storage, read> arr : array<array<S, 4>>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(arr, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = (tint_symbol_1 / 16u);
-  let len = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, InSameBlock) {
-  auto* src = R"(
-@group(0) @binding(0) var<storage, read> sb : array<i32>;;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : u32 = arrayLength(&sb);
-  var b : u32 = arrayLength(&sb);
-  var c : u32 = arrayLength(&sb);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
-
-@group(0) @binding(0) var<storage, read> sb : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
-  var a : u32 = tint_symbol_2;
-  var b : u32 = tint_symbol_2;
-  var c : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, InSameBlock_Struct) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : u32 = arrayLength(&sb.arr);
-  var b : u32 = arrayLength(&sb.arr);
-  var c : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-  var a : u32 = tint_symbol_2;
-  var b : u32 = tint_symbol_2;
-  var c : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, WithStride) {
-  auto* src = R"(
-@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : @stride(64) array<i32>, result : ptr<function, u32>)
-
-@group(0) @binding(0) var<storage, read> sb : @stride(64) array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = (tint_symbol_1 / 64u);
-  var len : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, WithStride_InStruct) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  y : f32;
-  arr : @stride(64) array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len : u32 = arrayLength(&sb.arr);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
-
-struct SB {
-  x : i32;
-  y : f32;
-  arr : @stride(64) array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 8u) / 64u);
-  var len : u32 = tint_symbol_2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, Nested) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  if (true) {
-    var len : u32 = arrayLength(&sb.arr);
-  } else {
-    if (true) {
-      var len : u32 = arrayLength(&sb.arr);
-    }
-  }
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  if (true) {
-    var tint_symbol_1 : u32 = 0u;
-    tint_symbol(sb, &(tint_symbol_1));
-    let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-    var len : u32 = tint_symbol_2;
-  } else {
-    if (true) {
-      var tint_symbol_3 : u32 = 0u;
-      tint_symbol(sb, &(tint_symbol_3));
-      let tint_symbol_4 : u32 = ((tint_symbol_3 - 4u) / 4u);
-      var len : u32 = tint_symbol_4;
-    }
-  }
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, MultipleStorageBuffers) {
-  auto* src = R"(
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-};
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-};
-
-@group(0) @binding(0) var<storage, read> sb1 : SB1;
-
-@group(0) @binding(1) var<storage, read> sb2 : SB2;
-
-@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len2 : u32 = arrayLength(&(sb2.arr2));
-  var len3 : u32 = arrayLength(&sb3);
-  var x : u32 = (len1 + len2 + len3);
-}
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB1, result : ptr<function, u32>)
-
-@internal(intrinsic_buffer_size)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB2, result : ptr<function, u32>)
-
-@internal(intrinsic_buffer_size)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-}
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-}
-
-@group(0) @binding(0) var<storage, read> sb1 : SB1;
-
-@group(0) @binding(1) var<storage, read> sb2 : SB2;
-
-@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb1, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-  var tint_symbol_4 : u32 = 0u;
-  tint_symbol_3(sb2, &(tint_symbol_4));
-  let tint_symbol_5 : u32 = ((tint_symbol_4 - 16u) / 16u);
-  var tint_symbol_7 : u32 = 0u;
-  tint_symbol_6(sb3, &(tint_symbol_7));
-  let tint_symbol_8 : u32 = (tint_symbol_7 / 4u);
-  var len1 : u32 = tint_symbol_2;
-  var len2 : u32 = tint_symbol_5;
-  var len3 : u32 = tint_symbol_8;
-  var x : u32 = ((len1 + len2) + len3);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, Shadowing) {
-  auto* src = R"(
-struct SB {
-  x : i32;
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read> a : SB;
-@group(0) @binding(1) var<storage, read> b : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = &a;
-  var a : u32 = arrayLength(&a.arr);
-  {
-    var b : u32 = arrayLength(&((*x).arr));
-  }
-}
-)";
-
-  auto* expect =
-      R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, result : ptr<function, u32>)
-
-struct SB {
-  x : i32;
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read> a : SB;
-
-@group(0) @binding(1) var<storage, read> b : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(a, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-  var a_1 : u32 = tint_symbol_2;
-  {
-    var tint_symbol_3 : u32 = 0u;
-    tint_symbol(a, &(tint_symbol_3));
-    let tint_symbol_4 : u32 = ((tint_symbol_3 - 4u) / 4u);
-    var b_1 : u32 = tint_symbol_4;
-  }
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CalculateArrayLengthTest, OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len2 : u32 = arrayLength(&(sb2.arr2));
-  var len3 : u32 = arrayLength(&sb3);
-  var x : u32 = (len1 + len2 + len3);
-}
-
-@group(0) @binding(0) var<storage, read> sb1 : SB1;
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-};
-
-@group(0) @binding(1) var<storage, read> sb2 : SB2;
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-};
-
-@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_buffer_size)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB1, result : ptr<function, u32>)
-
-@internal(intrinsic_buffer_size)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB2, result : ptr<function, u32>)
-
-@internal(intrinsic_buffer_size)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : array<i32>, result : ptr<function, u32>)
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var tint_symbol_1 : u32 = 0u;
-  tint_symbol(sb1, &(tint_symbol_1));
-  let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
-  var tint_symbol_4 : u32 = 0u;
-  tint_symbol_3(sb2, &(tint_symbol_4));
-  let tint_symbol_5 : u32 = ((tint_symbol_4 - 16u) / 16u);
-  var tint_symbol_7 : u32 = 0u;
-  tint_symbol_6(sb3, &(tint_symbol_7));
-  let tint_symbol_8 : u32 = (tint_symbol_7 / 4u);
-  var len1 : u32 = tint_symbol_2;
-  var len2 : u32 = tint_symbol_5;
-  var len3 : u32 = tint_symbol_8;
-  var x : u32 = ((len1 + len2) + len3);
-}
-
-@group(0) @binding(0) var<storage, read> sb1 : SB1;
-
-struct SB1 {
-  x : i32;
-  arr1 : array<i32>;
-}
-
-@group(0) @binding(1) var<storage, read> sb2 : SB2;
-
-struct SB2 {
-  x : i32;
-  arr2 : array<vec4<f32>>;
-}
-
-@group(0) @binding(2) var<storage, read> sb3 : array<i32>;
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
deleted file mode 100644
index f62ce67..0000000
--- a/src/transform/canonicalize_entry_point_io.cc
+++ /dev/null
@@ -1,779 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/canonicalize_entry_point_io.h"
-
-#include <algorithm>
-#include <string>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/function.h"
-#include "src/transform/unshadow.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::CanonicalizeEntryPointIO);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::CanonicalizeEntryPointIO::Config);
-
-namespace tint {
-namespace transform {
-
-CanonicalizeEntryPointIO::CanonicalizeEntryPointIO() = default;
-CanonicalizeEntryPointIO::~CanonicalizeEntryPointIO() = default;
-
-namespace {
-
-// Comparison function used to reorder struct members such that all members with
-// location attributes appear first (ordered by location slot), followed by
-// those with builtin attributes.
-bool StructMemberComparator(const ast::StructMember* a,
-                            const ast::StructMember* b) {
-  auto* a_loc = ast::GetAttribute<ast::LocationAttribute>(a->attributes);
-  auto* b_loc = ast::GetAttribute<ast::LocationAttribute>(b->attributes);
-  auto* a_blt = ast::GetAttribute<ast::BuiltinAttribute>(a->attributes);
-  auto* b_blt = ast::GetAttribute<ast::BuiltinAttribute>(b->attributes);
-  if (a_loc) {
-    if (!b_loc) {
-      // `a` has location attribute and `b` does not: `a` goes first.
-      return true;
-    }
-    // Both have location attributes: smallest goes first.
-    return a_loc->value < b_loc->value;
-  } else {
-    if (b_loc) {
-      // `b` has location attribute and `a` does not: `b` goes first.
-      return false;
-    }
-    // Both are builtins: order doesn't matter, just use enum value.
-    return a_blt->builtin < b_blt->builtin;
-  }
-}
-
-// Returns true if `attr` is a shader IO attribute.
-bool IsShaderIOAttribute(const ast::Attribute* attr) {
-  return attr->IsAnyOf<ast::BuiltinAttribute, ast::InterpolateAttribute,
-                       ast::InvariantAttribute, ast::LocationAttribute>();
-}
-
-// Returns true if `attrs` contains a `sample_mask` builtin.
-bool HasSampleMask(const ast::AttributeList& attrs) {
-  auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attrs);
-  return builtin && builtin->builtin == ast::Builtin::kSampleMask;
-}
-
-}  // namespace
-
-/// State holds the current transform state for a single entry point.
-struct CanonicalizeEntryPointIO::State {
-  /// OutputValue represents a shader result that the wrapper function produces.
-  struct OutputValue {
-    /// The name of the output value.
-    std::string name;
-    /// The type of the output value.
-    const ast::Type* type;
-    /// The shader IO attributes.
-    ast::AttributeList attributes;
-    /// The value itself.
-    const ast::Expression* value;
-  };
-
-  /// The clone context.
-  CloneContext& ctx;
-  /// The transform config.
-  CanonicalizeEntryPointIO::Config const cfg;
-  /// The entry point function (AST).
-  const ast::Function* func_ast;
-  /// The entry point function (SEM).
-  const sem::Function* func_sem;
-
-  /// The new entry point wrapper function's parameters.
-  ast::VariableList wrapper_ep_parameters;
-  /// The members of the wrapper function's struct parameter.
-  ast::StructMemberList wrapper_struct_param_members;
-  /// The name of the wrapper function's struct parameter.
-  Symbol wrapper_struct_param_name;
-  /// The parameters that will be passed to the original function.
-  ast::ExpressionList inner_call_parameters;
-  /// The members of the wrapper function's struct return type.
-  ast::StructMemberList wrapper_struct_output_members;
-  /// The wrapper function output values.
-  std::vector<OutputValue> wrapper_output_values;
-  /// The body of the wrapper function.
-  ast::StatementList wrapper_body;
-  /// Input names used by the entrypoint
-  std::unordered_set<std::string> input_names;
-
-  /// Constructor
-  /// @param context the clone context
-  /// @param config the transform config
-  /// @param function the entry point function
-  State(CloneContext& context,
-        const CanonicalizeEntryPointIO::Config& config,
-        const ast::Function* function)
-      : ctx(context),
-        cfg(config),
-        func_ast(function),
-        func_sem(ctx.src->Sem().Get(function)) {}
-
-  /// Clones the shader IO attributes from `src`.
-  /// @param src the attributes to clone
-  /// @param do_interpolate whether to clone InterpolateAttribute
-  /// @return the cloned attributes
-  ast::AttributeList CloneShaderIOAttributes(const ast::AttributeList& src,
-                                             bool do_interpolate) {
-    ast::AttributeList new_attributes;
-    for (auto* attr : src) {
-      if (IsShaderIOAttribute(attr) &&
-          (do_interpolate || !attr->Is<ast::InterpolateAttribute>())) {
-        new_attributes.push_back(ctx.Clone(attr));
-      }
-    }
-    return new_attributes;
-  }
-
-  /// Create or return a symbol for the wrapper function's struct parameter.
-  /// @returns the symbol for the struct parameter
-  Symbol InputStructSymbol() {
-    if (!wrapper_struct_param_name.IsValid()) {
-      wrapper_struct_param_name = ctx.dst->Sym();
-    }
-    return wrapper_struct_param_name;
-  }
-
-  /// Add a shader input to the entry point.
-  /// @param name the name of the shader input
-  /// @param type the type of the shader input
-  /// @param attributes the attributes to apply to the shader input
-  /// @returns an expression which evaluates to the value of the shader input
-  const ast::Expression* AddInput(std::string name,
-                                  const sem::Type* type,
-                                  ast::AttributeList attributes) {
-    auto* ast_type = CreateASTTypeFor(ctx, type);
-    if (cfg.shader_style == ShaderStyle::kSpirv ||
-        cfg.shader_style == ShaderStyle::kGlsl) {
-      // Vulkan requires that integer user-defined fragment inputs are
-      // always decorated with `Flat`.
-      // TODO(crbug.com/tint/1224): Remove this once a flat interpolation
-      // attribute is required for integers.
-      if (type->is_integer_scalar_or_vector() &&
-          ast::HasAttribute<ast::LocationAttribute>(attributes) &&
-          !ast::HasAttribute<ast::InterpolateAttribute>(attributes) &&
-          func_ast->PipelineStage() == ast::PipelineStage::kFragment) {
-        attributes.push_back(ctx.dst->Interpolate(
-            ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone));
-      }
-
-      // Disable validation for use of the `input` storage class.
-      attributes.push_back(
-          ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
-
-      // In GLSL, if it's a builtin, override the name with the
-      // corresponding gl_ builtin name
-      auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attributes);
-      if (cfg.shader_style == ShaderStyle::kGlsl && builtin) {
-        name = GLSLBuiltinToString(builtin->builtin, func_ast->PipelineStage(),
-                                   ast::StorageClass::kInput);
-      }
-      auto symbol = ctx.dst->Symbols().New(name);
-
-      // Create the global variable and use its value for the shader input.
-      const ast::Expression* value = ctx.dst->Expr(symbol);
-
-      if (builtin) {
-        if (cfg.shader_style == ShaderStyle::kGlsl) {
-          value = FromGLSLBuiltin(builtin->builtin, value, ast_type);
-        } else if (builtin->builtin == ast::Builtin::kSampleMask) {
-          // Vulkan requires the type of a SampleMask builtin to be an array.
-          // Declare it as array<u32, 1> and then load the first element.
-          ast_type = ctx.dst->ty.array(ast_type, 1);
-          value = ctx.dst->IndexAccessor(value, 0);
-        }
-      }
-      ctx.dst->Global(symbol, ast_type, ast::StorageClass::kInput,
-                      std::move(attributes));
-      return value;
-    } else if (cfg.shader_style == ShaderStyle::kMsl &&
-               ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
-      // If this input is a builtin and we are targeting MSL, then add it to the
-      // parameter list and pass it directly to the inner function.
-      Symbol symbol = input_names.emplace(name).second
-                          ? ctx.dst->Symbols().Register(name)
-                          : ctx.dst->Symbols().New(name);
-      wrapper_ep_parameters.push_back(
-          ctx.dst->Param(symbol, ast_type, std::move(attributes)));
-      return ctx.dst->Expr(symbol);
-    } else {
-      // Otherwise, move it to the new structure member list.
-      Symbol symbol = input_names.emplace(name).second
-                          ? ctx.dst->Symbols().Register(name)
-                          : ctx.dst->Symbols().New(name);
-      wrapper_struct_param_members.push_back(
-          ctx.dst->Member(symbol, ast_type, std::move(attributes)));
-      return ctx.dst->MemberAccessor(InputStructSymbol(), symbol);
-    }
-  }
-
-  /// Add a shader output to the entry point.
-  /// @param name the name of the shader output
-  /// @param type the type of the shader output
-  /// @param attributes the attributes to apply to the shader output
-  /// @param value the value of the shader output
-  void AddOutput(std::string name,
-                 const sem::Type* type,
-                 ast::AttributeList attributes,
-                 const ast::Expression* value) {
-    // Vulkan requires that integer user-defined vertex outputs are
-    // always decorated with `Flat`.
-    // TODO(crbug.com/tint/1224): Remove this once a flat interpolation
-    // attribute is required for integers.
-    if (cfg.shader_style == ShaderStyle::kSpirv &&
-        type->is_integer_scalar_or_vector() &&
-        ast::HasAttribute<ast::LocationAttribute>(attributes) &&
-        !ast::HasAttribute<ast::InterpolateAttribute>(attributes) &&
-        func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
-      attributes.push_back(ctx.dst->Interpolate(
-          ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone));
-    }
-
-    // In GLSL, if it's a builtin, override the name with the
-    // corresponding gl_ builtin name
-    if (cfg.shader_style == ShaderStyle::kGlsl) {
-      if (auto* b = ast::GetAttribute<ast::BuiltinAttribute>(attributes)) {
-        name = GLSLBuiltinToString(b->builtin, func_ast->PipelineStage(),
-                                   ast::StorageClass::kOutput);
-        value = ToGLSLBuiltin(b->builtin, value, type);
-      }
-    }
-
-    OutputValue output;
-    output.name = name;
-    output.type = CreateASTTypeFor(ctx, type);
-    output.attributes = std::move(attributes);
-    output.value = value;
-    wrapper_output_values.push_back(output);
-  }
-
-  /// Process a non-struct parameter.
-  /// This creates a new object for the shader input, moving the shader IO
-  /// attributes to it. It also adds an expression to the list of parameters
-  /// that will be passed to the original function.
-  /// @param param the original function parameter
-  void ProcessNonStructParameter(const sem::Parameter* param) {
-    // Remove the shader IO attributes from the inner function parameter, and
-    // attach them to the new object instead.
-    ast::AttributeList attributes;
-    for (auto* attr : param->Declaration()->attributes) {
-      if (IsShaderIOAttribute(attr)) {
-        ctx.Remove(param->Declaration()->attributes, attr);
-        attributes.push_back(ctx.Clone(attr));
-      }
-    }
-
-    auto name = ctx.src->Symbols().NameFor(param->Declaration()->symbol);
-    auto* input_expr = AddInput(name, param->Type(), std::move(attributes));
-    inner_call_parameters.push_back(input_expr);
-  }
-
-  /// Process a struct parameter.
-  /// This creates new objects for each struct member, moving the shader IO
-  /// attributes to them. It also creates the structure that will be passed to
-  /// the original function.
-  /// @param param the original function parameter
-  void ProcessStructParameter(const sem::Parameter* param) {
-    auto* str = param->Type()->As<sem::Struct>();
-
-    // Recreate struct members in the outer entry point and build an initializer
-    // list to pass them through to the inner function.
-    ast::ExpressionList inner_struct_values;
-    for (auto* member : str->Members()) {
-      if (member->Type()->Is<sem::Struct>()) {
-        TINT_ICE(Transform, ctx.dst->Diagnostics()) << "nested IO struct";
-        continue;
-      }
-
-      auto* member_ast = member->Declaration();
-      auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
-
-      // In GLSL, do not add interpolation attributes on vertex input
-      bool do_interpolate = true;
-      if (cfg.shader_style == ShaderStyle::kGlsl &&
-          func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
-        do_interpolate = false;
-      }
-      auto attributes =
-          CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
-      auto* input_expr = AddInput(name, member->Type(), std::move(attributes));
-      inner_struct_values.push_back(input_expr);
-    }
-
-    // Construct the original structure using the new shader input objects.
-    inner_call_parameters.push_back(ctx.dst->Construct(
-        ctx.Clone(param->Declaration()->type), inner_struct_values));
-  }
-
-  /// Process the entry point return type.
-  /// This generates a list of output values that are returned by the original
-  /// function.
-  /// @param inner_ret_type the original function return type
-  /// @param original_result the result object produced by the original function
-  void ProcessReturnType(const sem::Type* inner_ret_type,
-                         Symbol original_result) {
-    bool do_interpolate = true;
-    // In GLSL, do not add interpolation attributes on fragment output
-    if (cfg.shader_style == ShaderStyle::kGlsl &&
-        func_ast->PipelineStage() == ast::PipelineStage::kFragment) {
-      do_interpolate = false;
-    }
-    if (auto* str = inner_ret_type->As<sem::Struct>()) {
-      for (auto* member : str->Members()) {
-        if (member->Type()->Is<sem::Struct>()) {
-          TINT_ICE(Transform, ctx.dst->Diagnostics()) << "nested IO struct";
-          continue;
-        }
-
-        auto* member_ast = member->Declaration();
-        auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
-        auto attributes =
-            CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
-
-        // Extract the original structure member.
-        AddOutput(name, member->Type(), std::move(attributes),
-                  ctx.dst->MemberAccessor(original_result, name));
-      }
-    } else if (!inner_ret_type->Is<sem::Void>()) {
-      auto attributes = CloneShaderIOAttributes(
-          func_ast->return_type_attributes, do_interpolate);
-
-      // Propagate the non-struct return value as is.
-      AddOutput("value", func_sem->ReturnType(), std::move(attributes),
-                ctx.dst->Expr(original_result));
-    }
-  }
-
-  /// Add a fixed sample mask to the wrapper function output.
-  /// If there is already a sample mask, bitwise-and it with the fixed mask.
-  /// Otherwise, create a new output value from the fixed mask.
-  void AddFixedSampleMask() {
-    // Check the existing output values for a sample mask builtin.
-    for (auto& outval : wrapper_output_values) {
-      if (HasSampleMask(outval.attributes)) {
-        // Combine the authored sample mask with the fixed mask.
-        outval.value = ctx.dst->And(outval.value, cfg.fixed_sample_mask);
-        return;
-      }
-    }
-
-    // No existing sample mask builtin was found, so create a new output value
-    // using the fixed sample mask.
-    AddOutput("fixed_sample_mask", ctx.dst->create<sem::U32>(),
-              {ctx.dst->Builtin(ast::Builtin::kSampleMask)},
-              ctx.dst->Expr(cfg.fixed_sample_mask));
-  }
-
-  /// Add a point size builtin to the wrapper function output.
-  void AddVertexPointSize() {
-    // Create a new output value and assign it a literal 1.0 value.
-    AddOutput("vertex_point_size", ctx.dst->create<sem::F32>(),
-              {ctx.dst->Builtin(ast::Builtin::kPointSize)}, ctx.dst->Expr(1.f));
-  }
-
-  /// Create an expression for gl_Position.[component]
-  /// @param component the component of gl_Position to access
-  /// @returns the new expression
-  const ast::Expression* GLPosition(const char* component) {
-    Symbol pos = ctx.dst->Symbols().Register("gl_Position");
-    Symbol c = ctx.dst->Symbols().Register(component);
-    return ctx.dst->MemberAccessor(ctx.dst->Expr(pos), ctx.dst->Expr(c));
-  }
-
-  /// Create the wrapper function's struct parameter and type objects.
-  void CreateInputStruct() {
-    // Sort the struct members to satisfy HLSL interfacing matching rules.
-    std::sort(wrapper_struct_param_members.begin(),
-              wrapper_struct_param_members.end(), StructMemberComparator);
-
-    // Create the new struct type.
-    auto struct_name = ctx.dst->Sym();
-    auto* in_struct = ctx.dst->create<ast::Struct>(
-        struct_name, wrapper_struct_param_members, ast::AttributeList{});
-    ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, in_struct);
-
-    // Create a new function parameter using this struct type.
-    auto* param =
-        ctx.dst->Param(InputStructSymbol(), ctx.dst->ty.type_name(struct_name));
-    wrapper_ep_parameters.push_back(param);
-  }
-
-  /// Create and return the wrapper function's struct result object.
-  /// @returns the struct type
-  ast::Struct* CreateOutputStruct() {
-    ast::StatementList assignments;
-
-    auto wrapper_result = ctx.dst->Symbols().New("wrapper_result");
-
-    // Create the struct members and their corresponding assignment statements.
-    std::unordered_set<std::string> member_names;
-    for (auto& outval : wrapper_output_values) {
-      // Use the original output name, unless that is already taken.
-      Symbol name;
-      if (member_names.count(outval.name)) {
-        name = ctx.dst->Symbols().New(outval.name);
-      } else {
-        name = ctx.dst->Symbols().Register(outval.name);
-      }
-      member_names.insert(ctx.dst->Symbols().NameFor(name));
-
-      wrapper_struct_output_members.push_back(
-          ctx.dst->Member(name, outval.type, std::move(outval.attributes)));
-      assignments.push_back(ctx.dst->Assign(
-          ctx.dst->MemberAccessor(wrapper_result, name), outval.value));
-    }
-
-    // Sort the struct members to satisfy HLSL interfacing matching rules.
-    std::sort(wrapper_struct_output_members.begin(),
-              wrapper_struct_output_members.end(), StructMemberComparator);
-
-    // Create the new struct type.
-    auto* out_struct = ctx.dst->create<ast::Struct>(
-        ctx.dst->Sym(), wrapper_struct_output_members, ast::AttributeList{});
-    ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, out_struct);
-
-    // Create the output struct object, assign its members, and return it.
-    auto* result_object =
-        ctx.dst->Var(wrapper_result, ctx.dst->ty.type_name(out_struct->name));
-    wrapper_body.push_back(ctx.dst->Decl(result_object));
-    wrapper_body.insert(wrapper_body.end(), assignments.begin(),
-                        assignments.end());
-    wrapper_body.push_back(ctx.dst->Return(wrapper_result));
-
-    return out_struct;
-  }
-
-  /// Create and assign the wrapper function's output variables.
-  void CreateGlobalOutputVariables() {
-    for (auto& outval : wrapper_output_values) {
-      // Disable validation for use of the `output` storage class.
-      ast::AttributeList attributes = std::move(outval.attributes);
-      attributes.push_back(
-          ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
-
-      // Create the global variable and assign it the output value.
-      auto name = ctx.dst->Symbols().New(outval.name);
-      auto* type = outval.type;
-      const ast::Expression* lhs = ctx.dst->Expr(name);
-      if (HasSampleMask(attributes)) {
-        // Vulkan requires the type of a SampleMask builtin to be an array.
-        // Declare it as array<u32, 1> and then store to the first element.
-        type = ctx.dst->ty.array(type, 1);
-        lhs = ctx.dst->IndexAccessor(lhs, 0);
-      }
-      ctx.dst->Global(name, type, ast::StorageClass::kOutput,
-                      std::move(attributes));
-      wrapper_body.push_back(ctx.dst->Assign(lhs, outval.value));
-    }
-  }
-
-  // Recreate the original function without entry point attributes and call it.
-  /// @returns the inner function call expression
-  const ast::CallExpression* CallInnerFunction() {
-    Symbol inner_name;
-    if (cfg.shader_style == ShaderStyle::kGlsl) {
-      // In GLSL, clone the original entry point name, as the wrapper will be
-      // called "main".
-      inner_name = ctx.Clone(func_ast->symbol);
-    } else {
-      // Add a suffix to the function name, as the wrapper function will take
-      // the original entry point name.
-      auto ep_name = ctx.src->Symbols().NameFor(func_ast->symbol);
-      inner_name = ctx.dst->Symbols().New(ep_name + "_inner");
-    }
-
-    // Clone everything, dropping the function and return type attributes.
-    // The parameter attributes will have already been stripped during
-    // processing.
-    auto* inner_function = ctx.dst->create<ast::Function>(
-        inner_name, ctx.Clone(func_ast->params),
-        ctx.Clone(func_ast->return_type), ctx.Clone(func_ast->body),
-        ast::AttributeList{}, ast::AttributeList{});
-    ctx.Replace(func_ast, inner_function);
-
-    // Call the function.
-    return ctx.dst->Call(inner_function->symbol, inner_call_parameters);
-  }
-
-  /// Process the entry point function.
-  void Process() {
-    bool needs_fixed_sample_mask = false;
-    bool needs_vertex_point_size = false;
-    if (func_ast->PipelineStage() == ast::PipelineStage::kFragment &&
-        cfg.fixed_sample_mask != 0xFFFFFFFF) {
-      needs_fixed_sample_mask = true;
-    }
-    if (func_ast->PipelineStage() == ast::PipelineStage::kVertex &&
-        cfg.emit_vertex_point_size) {
-      needs_vertex_point_size = true;
-    }
-
-    // Exit early if there is no shader IO to handle.
-    if (func_sem->Parameters().size() == 0 &&
-        func_sem->ReturnType()->Is<sem::Void>() && !needs_fixed_sample_mask &&
-        !needs_vertex_point_size && cfg.shader_style != ShaderStyle::kGlsl) {
-      return;
-    }
-
-    // Process the entry point parameters, collecting those that need to be
-    // aggregated into a single structure.
-    if (!func_sem->Parameters().empty()) {
-      for (auto* param : func_sem->Parameters()) {
-        if (param->Type()->Is<sem::Struct>()) {
-          ProcessStructParameter(param);
-        } else {
-          ProcessNonStructParameter(param);
-        }
-      }
-
-      // Create a structure parameter for the outer entry point if necessary.
-      if (!wrapper_struct_param_members.empty()) {
-        CreateInputStruct();
-      }
-    }
-
-    // Recreate the original function and call it.
-    auto* call_inner = CallInnerFunction();
-
-    // Process the return type, and start building the wrapper function body.
-    std::function<const ast::Type*()> wrapper_ret_type = [&] {
-      return ctx.dst->ty.void_();
-    };
-    if (func_sem->ReturnType()->Is<sem::Void>()) {
-      // The function call is just a statement with no result.
-      wrapper_body.push_back(ctx.dst->CallStmt(call_inner));
-    } else {
-      // Capture the result of calling the original function.
-      auto* inner_result = ctx.dst->Const(
-          ctx.dst->Symbols().New("inner_result"), nullptr, call_inner);
-      wrapper_body.push_back(ctx.dst->Decl(inner_result));
-
-      // Process the original return type to determine the outputs that the
-      // outer function needs to produce.
-      ProcessReturnType(func_sem->ReturnType(), inner_result->symbol);
-    }
-
-    // Add a fixed sample mask, if necessary.
-    if (needs_fixed_sample_mask) {
-      AddFixedSampleMask();
-    }
-
-    // Add the pointsize builtin, if necessary.
-    if (needs_vertex_point_size) {
-      AddVertexPointSize();
-    }
-
-    // Produce the entry point outputs, if necessary.
-    if (!wrapper_output_values.empty()) {
-      if (cfg.shader_style == ShaderStyle::kSpirv ||
-          cfg.shader_style == ShaderStyle::kGlsl) {
-        CreateGlobalOutputVariables();
-      } else {
-        auto* output_struct = CreateOutputStruct();
-        wrapper_ret_type = [&, output_struct] {
-          return ctx.dst->ty.type_name(output_struct->name);
-        };
-      }
-    }
-
-    if (cfg.shader_style == ShaderStyle::kGlsl &&
-        func_ast->PipelineStage() == ast::PipelineStage::kVertex) {
-      auto* pos_y = GLPosition("y");
-      auto* negate_pos_y = ctx.dst->create<ast::UnaryOpExpression>(
-          ast::UnaryOp::kNegation, GLPosition("y"));
-      wrapper_body.push_back(ctx.dst->Assign(pos_y, negate_pos_y));
-
-      auto* two_z = ctx.dst->Mul(ctx.dst->Expr(2.0f), GLPosition("z"));
-      auto* fixed_z = ctx.dst->Sub(two_z, GLPosition("w"));
-      wrapper_body.push_back(ctx.dst->Assign(GLPosition("z"), fixed_z));
-    }
-
-    // Create the wrapper entry point function.
-    // For GLSL, use "main", otherwise take the name of the original
-    // entry point function.
-    Symbol name;
-    if (cfg.shader_style == ShaderStyle::kGlsl) {
-      name = ctx.dst->Symbols().New("main");
-    } else {
-      name = ctx.Clone(func_ast->symbol);
-    }
-
-    auto* wrapper_func = ctx.dst->create<ast::Function>(
-        name, wrapper_ep_parameters, wrapper_ret_type(),
-        ctx.dst->Block(wrapper_body), ctx.Clone(func_ast->attributes),
-        ast::AttributeList{});
-    ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), func_ast,
-                    wrapper_func);
-  }
-
-  /// Retrieve the gl_ string corresponding to a builtin.
-  /// @param builtin the builtin
-  /// @param stage the current pipeline stage
-  /// @param storage_class the storage class (input or output)
-  /// @returns the gl_ string corresponding to that builtin
-  const char* GLSLBuiltinToString(ast::Builtin builtin,
-                                  ast::PipelineStage stage,
-                                  ast::StorageClass storage_class) {
-    switch (builtin) {
-      case ast::Builtin::kPosition:
-        switch (stage) {
-          case ast::PipelineStage::kVertex:
-            return "gl_Position";
-          case ast::PipelineStage::kFragment:
-            return "gl_FragCoord";
-          default:
-            return "";
-        }
-      case ast::Builtin::kVertexIndex:
-        return "gl_VertexID";
-      case ast::Builtin::kInstanceIndex:
-        return "gl_InstanceID";
-      case ast::Builtin::kFrontFacing:
-        return "gl_FrontFacing";
-      case ast::Builtin::kFragDepth:
-        return "gl_FragDepth";
-      case ast::Builtin::kLocalInvocationId:
-        return "gl_LocalInvocationID";
-      case ast::Builtin::kLocalInvocationIndex:
-        return "gl_LocalInvocationIndex";
-      case ast::Builtin::kGlobalInvocationId:
-        return "gl_GlobalInvocationID";
-      case ast::Builtin::kNumWorkgroups:
-        return "gl_NumWorkGroups";
-      case ast::Builtin::kWorkgroupId:
-        return "gl_WorkGroupID";
-      case ast::Builtin::kSampleIndex:
-        return "gl_SampleID";
-      case ast::Builtin::kSampleMask:
-        if (storage_class == ast::StorageClass::kInput) {
-          return "gl_SampleMaskIn";
-        } else {
-          return "gl_SampleMask";
-        }
-      default:
-        return "";
-    }
-  }
-
-  /// Convert a given GLSL builtin value to the corresponding WGSL value.
-  /// @param builtin the builtin variable
-  /// @param value the value to convert
-  /// @param ast_type (inout) the incoming WGSL and outgoing GLSL types
-  /// @returns an expression representing the GLSL builtin converted to what
-  /// WGSL expects
-  const ast::Expression* FromGLSLBuiltin(ast::Builtin builtin,
-                                         const ast::Expression* value,
-                                         const ast::Type*& ast_type) {
-    switch (builtin) {
-      case ast::Builtin::kVertexIndex:
-      case ast::Builtin::kInstanceIndex:
-      case ast::Builtin::kSampleIndex:
-        // GLSL uses i32 for these, so bitcast to u32.
-        value = ctx.dst->Bitcast(ast_type, value);
-        ast_type = ctx.dst->ty.i32();
-        break;
-      case ast::Builtin::kSampleMask:
-        // gl_SampleMask is an array of i32. Retrieve the first element and
-        // bitcast it to u32.
-        value = ctx.dst->IndexAccessor(value, 0);
-        value = ctx.dst->Bitcast(ast_type, value);
-        ast_type = ctx.dst->ty.array(ctx.dst->ty.i32(), 1);
-        break;
-      default:
-        break;
-    }
-    return value;
-  }
-
-  /// Convert a given WGSL value to the type expected when assigning to a
-  /// GLSL builtin.
-  /// @param builtin the builtin variable
-  /// @param value the value to convert
-  /// @param type (out) the type to which the value was converted
-  /// @returns the converted value which can be assigned to the GLSL builtin
-  const ast::Expression* ToGLSLBuiltin(ast::Builtin builtin,
-                                       const ast::Expression* value,
-                                       const sem::Type*& type) {
-    switch (builtin) {
-      case ast::Builtin::kVertexIndex:
-      case ast::Builtin::kInstanceIndex:
-      case ast::Builtin::kSampleIndex:
-      case ast::Builtin::kSampleMask:
-        type = ctx.dst->create<sem::I32>();
-        value = ctx.dst->Bitcast(CreateASTTypeFor(ctx, type), value);
-        break;
-      default:
-        break;
-    }
-    return value;
-  }
-};
-
-void CanonicalizeEntryPointIO::Run(CloneContext& ctx,
-                                   const DataMap& inputs,
-                                   DataMap&) const {
-  auto* cfg = inputs.Get<Config>();
-  if (cfg == nullptr) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  // Remove entry point IO attributes from struct declarations.
-  // New structures will be created for each entry point, as necessary.
-  for (auto* ty : ctx.src->AST().TypeDecls()) {
-    if (auto* struct_ty = ty->As<ast::Struct>()) {
-      for (auto* member : struct_ty->members) {
-        for (auto* attr : member->attributes) {
-          if (IsShaderIOAttribute(attr)) {
-            ctx.Remove(member->attributes, attr);
-          }
-        }
-      }
-    }
-  }
-
-  for (auto* func_ast : ctx.src->AST().Functions()) {
-    if (!func_ast->IsEntryPoint()) {
-      continue;
-    }
-
-    State state(ctx, *cfg, func_ast);
-    state.Process();
-  }
-
-  ctx.Clone();
-}
-
-CanonicalizeEntryPointIO::Config::Config(ShaderStyle style,
-                                         uint32_t sample_mask,
-                                         bool emit_point_size)
-    : shader_style(style),
-      fixed_sample_mask(sample_mask),
-      emit_vertex_point_size(emit_point_size) {}
-
-CanonicalizeEntryPointIO::Config::Config(const Config&) = default;
-CanonicalizeEntryPointIO::Config::~Config() = default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/canonicalize_entry_point_io.h b/src/transform/canonicalize_entry_point_io.h
deleted file mode 100644
index 3e4394c..0000000
--- a/src/transform/canonicalize_entry_point_io.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
-#define SRC_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// CanonicalizeEntryPointIO is a transform used to rewrite shader entry point
-/// interfaces into a form that the generators can handle. Each entry point
-/// function is stripped of all shader IO attributes and wrapped in a function
-/// that provides the shader interface.
-/// The transform config determines whether to use global variables, structures,
-/// or parameters for the shader inputs and outputs, and optionally adds
-/// additional builtins to the shader interface.
-///
-/// Before:
-/// ```
-/// struct Locations{
-///   @location(1) loc1 : f32;
-///   @location(2) loc2 : vec4<u32>;
-/// };
-///
-/// @stage(fragment)
-/// fn frag_main(@builtin(position) coord : vec4<f32>,
-///              locations : Locations) -> @location(0) f32 {
-///   if (coord.w > 1.0) {
-///     return 0.0;
-///   }
-///   var col : f32 = (coord.x * locations.loc1);
-///   return col;
-/// }
-/// ```
-///
-/// After (using structures for all parameters):
-/// ```
-/// struct Locations{
-///   loc1 : f32;
-///   loc2 : vec4<u32>;
-/// };
-///
-/// struct frag_main_in {
-///   @builtin(position) coord : vec4<f32>;
-///   @location(1) loc1 : f32;
-///   @location(2) loc2 : vec4<u32>
-/// };
-///
-/// struct frag_main_out {
-///   @location(0) loc0 : f32;
-/// };
-///
-/// fn frag_main_inner(coord : vec4<f32>,
-///                    locations : Locations) -> f32 {
-///   if (coord.w > 1.0) {
-///     return 0.0;
-///   }
-///   var col : f32 = (coord.x * locations.loc1);
-///   return col;
-/// }
-///
-/// @stage(fragment)
-/// fn frag_main(in : frag_main_in) -> frag_main_out {
-///   let inner_retval = frag_main_inner(in.coord, Locations(in.loc1, in.loc2));
-///   var wrapper_result : frag_main_out;
-///   wrapper_result.loc0 = inner_retval;
-///   return wrapper_result;
-/// }
-/// ```
-///
-/// @note Depends on the following transforms to have been run first:
-/// * Unshadow
-class CanonicalizeEntryPointIO
-    : public Castable<CanonicalizeEntryPointIO, Transform> {
- public:
-  /// ShaderStyle is an enumerator of different ways to emit shader IO.
-  enum class ShaderStyle {
-    /// Target SPIR-V (using global variables).
-    kSpirv,
-    /// Target GLSL (using global variables).
-    kGlsl,
-    /// Target MSL (using non-struct function parameters for builtins).
-    kMsl,
-    /// Target HLSL (using structures for all IO).
-    kHlsl,
-  };
-
-  /// Configuration options for the transform.
-  struct Config : public Castable<Config, Data> {
-    /// Constructor
-    /// @param style the approach to use for emitting shader IO.
-    /// @param sample_mask an optional sample mask to combine with shader masks
-    /// @param emit_vertex_point_size `true` to generate a pointsize builtin
-    explicit Config(ShaderStyle style,
-                    uint32_t sample_mask = 0xFFFFFFFF,
-                    bool emit_vertex_point_size = false);
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// The approach to use for emitting shader IO.
-    const ShaderStyle shader_style;
-
-    /// A fixed sample mask to combine into masks produced by fragment shaders.
-    const uint32_t fixed_sample_mask;
-
-    /// Set to `true` to generate a pointsize builtin and have it set to 1.0
-    /// from all vertex shaders in the module.
-    const bool emit_vertex_point_size;
-  };
-
-  /// Constructor
-  CanonicalizeEntryPointIO();
-  ~CanonicalizeEntryPointIO() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
-  struct State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_
diff --git a/src/transform/canonicalize_entry_point_io_test.cc b/src/transform/canonicalize_entry_point_io_test.cc
deleted file mode 100644
index 990a0c5..0000000
--- a/src/transform/canonicalize_entry_point_io_test.cc
+++ /dev/null
@@ -1,4041 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/canonicalize_entry_point_io.h"
-
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using CanonicalizeEntryPointIOTest = TransformTest;
-
-TEST_F(CanonicalizeEntryPointIOTest, Error_MissingTransformData) {
-  auto* src = "";
-
-  auto* expect =
-      "error: missing transform data for "
-      "tint::transform::CanonicalizeEntryPointIO";
-
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, NoShaderIO) {
-  // Test that we do not introduce wrapper functions when there is no shader IO
-  // to process.
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() {
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main() {
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Parameters_Spirv) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(1) loc1 : f32,
-             @location(2) @interpolate(flat) loc2 : vec4<u32>,
-             @builtin(position) coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-)";
-
-  auto* expect = R"(
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
-
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-
-@stage(fragment)
-fn frag_main() {
-  frag_main_inner(loc1_1, loc2_1, coord_1);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Parameters_Msl) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(1) loc1 : f32,
-             @location(2) @interpolate(flat) loc2 : vec4<u32>,
-             @builtin(position) coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-}
-
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-
-@stage(fragment)
-fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, coord);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Parameters_Hlsl) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(1) loc1 : f32,
-             @location(2) @interpolate(flat) loc2 : vec4<u32>,
-             @builtin(position) coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-  @builtin(position)
-  coord : vec4<f32>;
-}
-
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
-  var col : f32 = (coord.x * loc1);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, tint_symbol.coord);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Parameter_TypeAlias) {
-  auto* src = R"(
-type myf32 = f32;
-
-@stage(fragment)
-fn frag_main(@location(1) loc1 : myf32) {
-  var x : myf32 = loc1;
-}
-)";
-
-  auto* expect = R"(
-type myf32 = f32;
-
-struct tint_symbol_1 {
-  @location(1)
-  loc1 : f32;
-}
-
-fn frag_main_inner(loc1 : myf32) {
-  var x : myf32 = loc1;
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc1);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Parameter_TypeAlias_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(1) loc1 : myf32) {
-  var x : myf32 = loc1;
-}
-
-type myf32 = f32;
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(1)
-  loc1 : f32;
-}
-
-fn frag_main_inner(loc1 : myf32) {
-  var x : myf32 = loc1;
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc1);
-}
-
-type myf32 = f32;
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv) {
-  auto* src = R"(
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> loc0_1 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
-
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main() {
-  frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> loc0_1 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> loc1_1 : f32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> loc2_1 : vec4<u32>;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<in> coord_1 : vec4<f32>;
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main() {
-  frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
-}
-
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl) {
-  auto* src = R"(
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-)";
-
-  auto* expect = R"(
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  loc0 : f32;
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-}
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  loc0 : f32;
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-}
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
-}
-
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl) {
-  auto* src = R"(
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-)";
-
-  auto* expect = R"(
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  loc0 : f32;
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-  @builtin(position)
-  coord : vec4<f32>;
-}
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(@location(0) loc0 : f32,
-             locations : FragLocations,
-             builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-struct FragBuiltins {
-  @builtin(position) coord : vec4<f32>;
-};
-struct FragLocations {
-  @location(1) loc1 : f32;
-  @location(2) @interpolate(flat) loc2 : vec4<u32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  loc0 : f32;
-  @location(1)
-  loc1 : f32;
-  @location(2) @interpolate(flat)
-  loc2 : vec4<u32>;
-  @builtin(position)
-  coord : vec4<f32>;
-}
-
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
-  var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
-}
-
-struct FragBuiltins {
-  coord : vec4<f32>;
-}
-
-struct FragLocations {
-  loc1 : f32;
-  loc2 : vec4<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Spirv) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> @builtin(frag_depth) f32 {
-  return 1.0;
-}
-)";
-
-  auto* expect = R"(
-@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> value : f32;
-
-fn frag_main_inner() -> f32 {
-  return 1.0;
-}
-
-@stage(fragment)
-fn frag_main() {
-  let inner_result = frag_main_inner();
-  value = inner_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Msl) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> @builtin(frag_depth) f32 {
-  return 1.0;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(frag_depth)
-  value : f32;
-}
-
-fn frag_main_inner() -> f32 {
-  return 1.0;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Hlsl) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> @builtin(frag_depth) f32 {
-  return 1.0;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(frag_depth)
-  value : f32;
-}
-
-fn frag_main_inner() -> f32 {
-  return 1.0;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Spirv) {
-  auto* src = R"(
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<out> color_1 : vec4<f32>;
-
-@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> depth_1 : f32;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> mask_1 : array<u32, 1>;
-
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() {
-  let inner_result = frag_main_inner();
-  color_1 = inner_result.color;
-  depth_1 = inner_result.depth;
-  mask_1[0] = inner_result.mask;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Spirv_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<out> color_1 : vec4<f32>;
-
-@builtin(frag_depth) @internal(disable_validation__ignore_storage_class) var<out> depth_1 : f32;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> mask_1 : array<u32, 1>;
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() {
-  let inner_result = frag_main_inner();
-  color_1 = inner_result.color;
-  depth_1 = inner_result.depth;
-  mask_1[0] = inner_result.mask;
-}
-
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Msl) {
-  auto* src = R"(
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-)";
-
-  auto* expect = R"(
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-
-struct tint_symbol {
-  @location(0)
-  color : vec4<f32>;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.color = inner_result.color;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = inner_result.mask;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Msl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  color : vec4<f32>;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.color = inner_result.color;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = inner_result.mask;
-  return wrapper_result;
-}
-
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Hlsl) {
-  auto* src = R"(
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-)";
-
-  auto* expect = R"(
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-
-struct tint_symbol {
-  @location(0)
-  color : vec4<f32>;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.color = inner_result.color;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = inner_result.mask;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Hlsl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-struct FragOutput {
-  @location(0) color : vec4<f32>;
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  color : vec4<f32>;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> FragOutput {
-  var output : FragOutput;
-  output.depth = 1.0;
-  output.mask = 7u;
-  output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
-  return output;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.color = inner_result.color;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = inner_result.mask;
-  return wrapper_result;
-}
-
-struct FragOutput {
-  color : vec4<f32>;
-  depth : f32;
-  mask : u32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Spirv) {
-  auto* src = R"(
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_1 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_1 : f32;
-
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_2 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_2 : f32;
-
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1() {
-  frag_main1_inner(FragmentInput(value_1, mul_1));
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2() {
-  frag_main2_inner(FragmentInput(value_2, mul_2));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Spirv_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_1 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_1 : f32;
-
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> value_2 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> mul_2 : f32;
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1() {
-  frag_main1_inner(FragmentInput(value_1, mul_1));
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2() {
-  frag_main2_inner(FragmentInput(value_2, mul_2));
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Msl) {
-  auto* src = R"(
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-)";
-
-  auto* expect = R"(
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
-}
-
-struct tint_symbol_3 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(tint_symbol_2 : tint_symbol_3) {
-  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Msl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
-}
-
-struct tint_symbol_3 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(tint_symbol_2 : tint_symbol_3) {
-  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Hlsl) {
-  auto* src = R"(
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-)";
-
-  auto* expect = R"(
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
-}
-
-struct tint_symbol_3 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(tint_symbol_2 : tint_symbol_3) {
-  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       StructParameters_SharedDeviceFunction_Hlsl_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return x.value * x.mul;
-}
-
-struct FragmentInput {
-  @location(0) value : f32;
-  @location(1) mul : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul));
-}
-
-struct tint_symbol_3 {
-  @location(0)
-  value : f32;
-  @location(1)
-  mul : f32;
-}
-
-fn frag_main2_inner(inputs : FragmentInput) {
-  var x : f32 = foo(inputs);
-}
-
-@stage(fragment)
-fn frag_main2(tint_symbol_2 : tint_symbol_3) {
-  frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul));
-}
-
-fn foo(x : FragmentInput) -> f32 {
-  return (x.value * x.mul);
-}
-
-struct FragmentInput {
-  value : f32;
-  mul : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_ModuleScopeVariable) {
-  auto* src = R"(
-struct FragmentInput {
-  @location(0) col1 : f32;
-  @location(1) col2 : f32;
-};
-
-var<private> global_inputs : FragmentInput;
-
-fn foo() -> f32 {
-  return global_inputs.col1 * 0.5;
-}
-
-fn bar() -> f32 {
-  return global_inputs.col2 * 2.0;
-}
-
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
- global_inputs = inputs;
- var r : f32 = foo();
- var g : f32 = bar();
-}
-)";
-
-  auto* expect = R"(
-struct FragmentInput {
-  col1 : f32;
-  col2 : f32;
-}
-
-var<private> global_inputs : FragmentInput;
-
-fn foo() -> f32 {
-  return (global_inputs.col1 * 0.5);
-}
-
-fn bar() -> f32 {
-  return (global_inputs.col2 * 2.0);
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  global_inputs = inputs;
-  var r : f32 = foo();
-  var g : f32 = bar();
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.col1, tint_symbol.col2));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_ModuleScopeVariable_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main1(inputs : FragmentInput) {
- global_inputs = inputs;
- var r : f32 = foo();
- var g : f32 = bar();
-}
-
-fn foo() -> f32 {
-  return global_inputs.col1 * 0.5;
-}
-
-fn bar() -> f32 {
-  return global_inputs.col2 * 2.0;
-}
-
-var<private> global_inputs : FragmentInput;
-
-struct FragmentInput {
-  @location(0) col1 : f32;
-  @location(1) col2 : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-fn frag_main1_inner(inputs : FragmentInput) {
-  global_inputs = inputs;
-  var r : f32 = foo();
-  var g : f32 = bar();
-}
-
-@stage(fragment)
-fn frag_main1(tint_symbol : tint_symbol_1) {
-  frag_main1_inner(FragmentInput(tint_symbol.col1, tint_symbol.col2));
-}
-
-fn foo() -> f32 {
-  return (global_inputs.col1 * 0.5);
-}
-
-fn bar() -> f32 {
-  return (global_inputs.col2 * 2.0);
-}
-
-var<private> global_inputs : FragmentInput;
-
-struct FragmentInput {
-  col1 : f32;
-  col2 : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_TypeAliases) {
-  auto* src = R"(
-type myf32 = f32;
-
-struct FragmentInput {
-  @location(0) col1 : myf32;
-  @location(1) col2 : myf32;
-};
-
-struct FragmentOutput {
-  @location(0) col1 : myf32;
-  @location(1) col2 : myf32;
-};
-
-type MyFragmentInput = FragmentInput;
-
-type MyFragmentOutput = FragmentOutput;
-
-fn foo(x : MyFragmentInput) -> myf32 {
-  return x.col1;
-}
-
-@stage(fragment)
-fn frag_main(inputs : MyFragmentInput) -> MyFragmentOutput {
-  var x : myf32 = foo(inputs);
-  return MyFragmentOutput(x, inputs.col2);
-}
-)";
-
-  auto* expect = R"(
-type myf32 = f32;
-
-struct FragmentInput {
-  col1 : myf32;
-  col2 : myf32;
-}
-
-struct FragmentOutput {
-  col1 : myf32;
-  col2 : myf32;
-}
-
-type MyFragmentInput = FragmentInput;
-
-type MyFragmentOutput = FragmentOutput;
-
-fn foo(x : MyFragmentInput) -> myf32 {
-  return x.col1;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-fn frag_main_inner(inputs : MyFragmentInput) -> MyFragmentOutput {
-  var x : myf32 = foo(inputs);
-  return MyFragmentOutput(x, inputs.col2);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = frag_main_inner(MyFragmentInput(tint_symbol.col1, tint_symbol.col2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.col1 = inner_result.col1;
-  wrapper_result.col2 = inner_result.col2;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_TypeAliases_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(inputs : MyFragmentInput) -> MyFragmentOutput {
-  var x : myf32 = foo(inputs);
-  return MyFragmentOutput(x, inputs.col2);
-}
-
-type MyFragmentInput = FragmentInput;
-
-type MyFragmentOutput = FragmentOutput;
-
-fn foo(x : MyFragmentInput) -> myf32 {
-  return x.col1;
-}
-
-struct FragmentInput {
-  @location(0) col1 : myf32;
-  @location(1) col2 : myf32;
-};
-
-struct FragmentOutput {
-  @location(0) col1 : myf32;
-  @location(1) col2 : myf32;
-};
-
-type myf32 = f32;
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  col1 : f32;
-  @location(1)
-  col2 : f32;
-}
-
-fn frag_main_inner(inputs : MyFragmentInput) -> MyFragmentOutput {
-  var x : myf32 = foo(inputs);
-  return MyFragmentOutput(x, inputs.col2);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = frag_main_inner(MyFragmentInput(tint_symbol.col1, tint_symbol.col2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.col1 = inner_result.col1;
-  wrapper_result.col2 = inner_result.col2;
-  return wrapper_result;
-}
-
-type MyFragmentInput = FragmentInput;
-
-type MyFragmentOutput = FragmentOutput;
-
-fn foo(x : MyFragmentInput) -> myf32 {
-  return x.col1;
-}
-
-struct FragmentInput {
-  col1 : myf32;
-  col2 : myf32;
-}
-
-struct FragmentOutput {
-  col1 : myf32;
-  col2 : myf32;
-}
-
-type myf32 = f32;
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes) {
-  auto* src = R"(
-struct VertexOut {
-  @builtin(position) pos : vec4<f32>;
-  @location(1) @interpolate(flat) loc1: f32;
-  @location(2) @interpolate(linear, sample) loc2 : f32;
-  @location(3) @interpolate(perspective, centroid) loc3 : f32;
-};
-
-struct FragmentIn {
-  @location(1) @interpolate(flat) loc1: f32;
-  @location(2) @interpolate(linear, sample) loc2 : f32;
-};
-
-@stage(vertex)
-fn vert_main() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(fragment)
-fn frag_main(inputs : FragmentIn,
-             @location(3) @interpolate(perspective, centroid) loc3 : f32) {
-  let x = inputs.loc1 + inputs.loc2 + loc3;
-}
-)";
-
-  auto* expect = R"(
-struct VertexOut {
-  pos : vec4<f32>;
-  loc1 : f32;
-  loc2 : f32;
-  loc3 : f32;
-}
-
-struct FragmentIn {
-  loc1 : f32;
-  loc2 : f32;
-}
-
-struct tint_symbol {
-  @location(1) @interpolate(flat)
-  loc1 : f32;
-  @location(2) @interpolate(linear, sample)
-  loc2 : f32;
-  @location(3) @interpolate(perspective, centroid)
-  loc3 : f32;
-  @builtin(position)
-  pos : vec4<f32>;
-}
-
-fn vert_main_inner() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.loc1 = inner_result.loc1;
-  wrapper_result.loc2 = inner_result.loc2;
-  wrapper_result.loc3 = inner_result.loc3;
-  return wrapper_result;
-}
-
-struct tint_symbol_2 {
-  @location(1) @interpolate(flat)
-  loc1 : f32;
-  @location(2) @interpolate(linear, sample)
-  loc2 : f32;
-  @location(3) @interpolate(perspective, centroid)
-  loc3 : f32;
-}
-
-fn frag_main_inner(inputs : FragmentIn, loc3 : f32) {
-  let x = ((inputs.loc1 + inputs.loc2) + loc3);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol_1 : tint_symbol_2) {
-  frag_main_inner(FragmentIn(tint_symbol_1.loc1, tint_symbol_1.loc2), tint_symbol_1.loc3);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(inputs : FragmentIn,
-             @location(3) @interpolate(perspective, centroid) loc3 : f32) {
-  let x = inputs.loc1 + inputs.loc2 + loc3;
-}
-
-@stage(vertex)
-fn vert_main() -> VertexOut {
-  return VertexOut();
-}
-
-struct VertexOut {
-  @builtin(position) pos : vec4<f32>;
-  @location(1) @interpolate(flat) loc1: f32;
-  @location(2) @interpolate(linear, sample) loc2 : f32;
-  @location(3) @interpolate(perspective, centroid) loc3 : f32;
-};
-
-struct FragmentIn {
-  @location(1) @interpolate(flat) loc1: f32;
-  @location(2) @interpolate(linear, sample) loc2 : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(1) @interpolate(flat)
-  loc1 : f32;
-  @location(2) @interpolate(linear, sample)
-  loc2 : f32;
-  @location(3) @interpolate(perspective, centroid)
-  loc3 : f32;
-}
-
-fn frag_main_inner(inputs : FragmentIn, loc3 : f32) {
-  let x = ((inputs.loc1 + inputs.loc2) + loc3);
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) {
-  frag_main_inner(FragmentIn(tint_symbol.loc1, tint_symbol.loc2), tint_symbol.loc3);
-}
-
-struct tint_symbol_2 {
-  @location(1) @interpolate(flat)
-  loc1 : f32;
-  @location(2) @interpolate(linear, sample)
-  loc2 : f32;
-  @location(3) @interpolate(perspective, centroid)
-  loc3 : f32;
-  @builtin(position)
-  pos : vec4<f32>;
-}
-
-fn vert_main_inner() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol_2 {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.loc1 = inner_result.loc1;
-  wrapper_result.loc2 = inner_result.loc2;
-  wrapper_result.loc3 = inner_result.loc3;
-  return wrapper_result;
-}
-
-struct VertexOut {
-  pos : vec4<f32>;
-  loc1 : f32;
-  loc2 : f32;
-  loc3 : f32;
-}
-
-struct FragmentIn {
-  loc1 : f32;
-  loc2 : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes_Integers_Spirv) {
-  // Test that we add a Flat attribute to integers that are vertex outputs and
-  // fragment inputs, but not vertex inputs or fragment outputs.
-  auto* src = R"(
-struct VertexIn {
-  @location(0) i : i32;
-  @location(1) u : u32;
-  @location(2) vi : vec4<i32>;
-  @location(3) vu : vec4<u32>;
-};
-
-struct VertexOut {
-  @location(0) @interpolate(flat) i : i32;
-  @location(1) @interpolate(flat) u : u32;
-  @location(2) @interpolate(flat) vi : vec4<i32>;
-  @location(3) @interpolate(flat) vu : vec4<u32>;
-  @builtin(position) pos : vec4<f32>;
-};
-
-struct FragmentInterface {
-  @location(0) @interpolate(flat) i : i32;
-  @location(1) @interpolate(flat) u : u32;
-  @location(2) @interpolate(flat) vi : vec4<i32>;
-  @location(3) @interpolate(flat) vu : vec4<u32>;
-};
-
-@stage(vertex)
-fn vert_main(in : VertexIn) -> VertexOut {
-  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
-}
-
-@stage(fragment)
-fn frag_main(inputs : FragmentInterface) -> FragmentInterface {
-  return inputs;
-}
-)";
-
-  auto* expect =
-      R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> i_1 : i32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> u_1 : u32;
-
-@location(2) @internal(disable_validation__ignore_storage_class) var<in> vi_1 : vec4<i32>;
-
-@location(3) @internal(disable_validation__ignore_storage_class) var<in> vu_1 : vec4<u32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_2 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_2 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_2 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_2 : vec4<u32>;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> i_3 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> u_3 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vi_3 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vu_3 : vec4<u32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_4 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_4 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_4 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_4 : vec4<u32>;
-
-struct VertexIn {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-}
-
-struct VertexOut {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-  pos : vec4<f32>;
-}
-
-struct FragmentInterface {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-}
-
-fn vert_main_inner(in : VertexIn) -> VertexOut {
-  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner(VertexIn(i_1, u_1, vi_1, vu_1));
-  i_2 = inner_result.i;
-  u_2 = inner_result.u;
-  vi_2 = inner_result.vi;
-  vu_2 = inner_result.vu;
-  pos_1 = inner_result.pos;
-}
-
-fn frag_main_inner(inputs : FragmentInterface) -> FragmentInterface {
-  return inputs;
-}
-
-@stage(fragment)
-fn frag_main() {
-  let inner_result_1 = frag_main_inner(FragmentInterface(i_3, u_3, vi_3, vu_3));
-  i_4 = inner_result_1.i;
-  u_4 = inner_result_1.u;
-  vi_4 = inner_result_1.vi;
-  vu_4 = inner_result_1.vu;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       InterpolateAttributes_Integers_Spirv_OutOfOrder) {
-  // Test that we add a Flat attribute to integers that are vertex outputs and
-  // fragment inputs, but not vertex inputs or fragment outputs.
-  auto* src = R"(
-@stage(vertex)
-fn vert_main(in : VertexIn) -> VertexOut {
-  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
-}
-
-@stage(fragment)
-fn frag_main(inputs : FragmentInterface) -> FragmentInterface {
-  return inputs;
-}
-
-struct VertexIn {
-  @location(0) i : i32;
-  @location(1) u : u32;
-  @location(2) vi : vec4<i32>;
-  @location(3) vu : vec4<u32>;
-};
-
-struct VertexOut {
-  @location(0) @interpolate(flat) i : i32;
-  @location(1) @interpolate(flat) u : u32;
-  @location(2) @interpolate(flat) vi : vec4<i32>;
-  @location(3) @interpolate(flat) vu : vec4<u32>;
-  @builtin(position) pos : vec4<f32>;
-};
-
-struct FragmentInterface {
-  @location(0) @interpolate(flat) i : i32;
-  @location(1) @interpolate(flat) u : u32;
-  @location(2) @interpolate(flat) vi : vec4<i32>;
-  @location(3) @interpolate(flat) vu : vec4<u32>;
-};
-)";
-
-  auto* expect =
-      R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> i_1 : i32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> u_1 : u32;
-
-@location(2) @internal(disable_validation__ignore_storage_class) var<in> vi_1 : vec4<i32>;
-
-@location(3) @internal(disable_validation__ignore_storage_class) var<in> vu_1 : vec4<u32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_2 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_2 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_2 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_2 : vec4<u32>;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> i_3 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> u_3 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vi_3 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<in> vu_3 : vec4<u32>;
-
-@location(0) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> i_4 : i32;
-
-@location(1) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> u_4 : u32;
-
-@location(2) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vi_4 : vec4<i32>;
-
-@location(3) @interpolate(flat) @internal(disable_validation__ignore_storage_class) var<out> vu_4 : vec4<u32>;
-
-fn vert_main_inner(in : VertexIn) -> VertexOut {
-  return VertexOut(in.i, in.u, in.vi, in.vu, vec4<f32>());
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner(VertexIn(i_1, u_1, vi_1, vu_1));
-  i_2 = inner_result.i;
-  u_2 = inner_result.u;
-  vi_2 = inner_result.vi;
-  vu_2 = inner_result.vu;
-  pos_1 = inner_result.pos;
-}
-
-fn frag_main_inner(inputs : FragmentInterface) -> FragmentInterface {
-  return inputs;
-}
-
-@stage(fragment)
-fn frag_main() {
-  let inner_result_1 = frag_main_inner(FragmentInterface(i_3, u_3, vi_3, vu_3));
-  i_4 = inner_result_1.i;
-  u_4 = inner_result_1.u;
-  vi_4 = inner_result_1.vi;
-  vu_4 = inner_result_1.vu;
-}
-
-struct VertexIn {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-}
-
-struct VertexOut {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-  pos : vec4<f32>;
-}
-
-struct FragmentInterface {
-  i : i32;
-  u : u32;
-  vi : vec4<i32>;
-  vu : vec4<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, InvariantAttributes) {
-  auto* src = R"(
-struct VertexOut {
-  [[builtin(position), invariant]] pos : vec4<f32>;
-};
-
-@stage(vertex)
-fn main1() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn main2() -> [[builtin(position), invariant]] vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct VertexOut {
-  pos : vec4<f32>;
-}
-
-struct tint_symbol {
-  @builtin(position) @invariant
-  pos : vec4<f32>;
-}
-
-fn main1_inner() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn main1() -> tint_symbol {
-  let inner_result = main1_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.pos = inner_result.pos;
-  return wrapper_result;
-}
-
-struct tint_symbol_1 {
-  @builtin(position) @invariant
-  value : vec4<f32>;
-}
-
-fn main2_inner() -> vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn main2() -> tint_symbol_1 {
-  let inner_result_1 = main2_inner();
-  var wrapper_result_1 : tint_symbol_1;
-  wrapper_result_1.value = inner_result_1;
-  return wrapper_result_1;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, InvariantAttributes_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn main1() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn main2() -> [[builtin(position), invariant]] vec4<f32> {
-  return vec4<f32>();
-}
-
-struct VertexOut {
-  [[builtin(position), invariant]] pos : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(position) @invariant
-  pos : vec4<f32>;
-}
-
-fn main1_inner() -> VertexOut {
-  return VertexOut();
-}
-
-@stage(vertex)
-fn main1() -> tint_symbol {
-  let inner_result = main1_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.pos = inner_result.pos;
-  return wrapper_result;
-}
-
-struct tint_symbol_1 {
-  @builtin(position) @invariant
-  value : vec4<f32>;
-}
-
-fn main2_inner() -> vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn main2() -> tint_symbol_1 {
-  let inner_result_1 = main2_inner();
-  var wrapper_result_1 : tint_symbol_1;
-  wrapper_result_1.value = inner_result_1;
-  return wrapper_result_1;
-}
-
-struct VertexOut {
-  pos : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutAttributes) {
-  auto* src = R"(
-struct FragmentInput {
-  @size(16) @location(1) value : f32;
-  @builtin(position) @align(32) coord : vec4<f32>;
-  @location(0) @interpolate(linear, sample) @align(128) loc0 : f32;
-};
-
-struct FragmentOutput {
-  @size(16) @location(1) @interpolate(flat) value : f32;
-};
-
-@stage(fragment)
-fn frag_main(inputs : FragmentInput) -> FragmentOutput {
-  return FragmentOutput(inputs.coord.x * inputs.value + inputs.loc0);
-}
-)";
-
-  auto* expect = R"(
-struct FragmentInput {
-  @size(16)
-  value : f32;
-  @align(32)
-  coord : vec4<f32>;
-  @align(128)
-  loc0 : f32;
-}
-
-struct FragmentOutput {
-  @size(16)
-  value : f32;
-}
-
-struct tint_symbol_1 {
-  @location(0) @interpolate(linear, sample)
-  loc0 : f32;
-  @location(1)
-  value : f32;
-  @builtin(position)
-  coord : vec4<f32>;
-}
-
-struct tint_symbol_2 {
-  @location(1) @interpolate(flat)
-  value : f32;
-}
-
-fn frag_main_inner(inputs : FragmentInput) -> FragmentOutput {
-  return FragmentOutput(((inputs.coord.x * inputs.value) + inputs.loc0));
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = frag_main_inner(FragmentInput(tint_symbol.value, tint_symbol.coord, tint_symbol.loc0));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.value = inner_result.value;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutAttributes_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main(inputs : FragmentInput) -> FragmentOutput {
-  return FragmentOutput(inputs.coord.x * inputs.value + inputs.loc0);
-}
-
-struct FragmentInput {
-  @size(16) @location(1) value : f32;
-  @builtin(position) @align(32) coord : vec4<f32>;
-  @location(0) @interpolate(linear, sample) @align(128) loc0 : f32;
-};
-
-struct FragmentOutput {
-  @size(16) @location(1) @interpolate(flat) value : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0) @interpolate(linear, sample)
-  loc0 : f32;
-  @location(1)
-  value : f32;
-  @builtin(position)
-  coord : vec4<f32>;
-}
-
-struct tint_symbol_2 {
-  @location(1) @interpolate(flat)
-  value : f32;
-}
-
-fn frag_main_inner(inputs : FragmentInput) -> FragmentOutput {
-  return FragmentOutput(((inputs.coord.x * inputs.value) + inputs.loc0));
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = frag_main_inner(FragmentInput(tint_symbol.value, tint_symbol.coord, tint_symbol.loc0));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.value = inner_result.value;
-  return wrapper_result;
-}
-
-struct FragmentInput {
-  @size(16)
-  value : f32;
-  @align(32)
-  coord : vec4<f32>;
-  @align(128)
-  loc0 : f32;
-}
-
-struct FragmentOutput {
-  @size(16)
-  value : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, SortedMembers) {
-  auto* src = R"(
-struct VertexOutput {
-  @location(1) @interpolate(flat) b : u32;
-  @builtin(position) pos : vec4<f32>;
-  @location(3) @interpolate(flat) d : u32;
-  @location(0) a : f32;
-  @location(2) @interpolate(flat) c : i32;
-};
-
-struct FragmentInputExtra {
-  @location(3) @interpolate(flat) d : u32;
-  @builtin(position) pos : vec4<f32>;
-  @location(0) a : f32;
-};
-
-@stage(vertex)
-fn vert_main() -> VertexOutput {
-  return VertexOutput();
-}
-
-@stage(fragment)
-fn frag_main(@builtin(front_facing) ff : bool,
-             @location(2) @interpolate(flat) c : i32,
-             inputs : FragmentInputExtra,
-             @location(1) @interpolate(flat) b : u32) {
-}
-)";
-
-  auto* expect = R"(
-struct VertexOutput {
-  b : u32;
-  pos : vec4<f32>;
-  d : u32;
-  a : f32;
-  c : i32;
-}
-
-struct FragmentInputExtra {
-  d : u32;
-  pos : vec4<f32>;
-  a : f32;
-}
-
-struct tint_symbol {
-  @location(0)
-  a : f32;
-  @location(1) @interpolate(flat)
-  b : u32;
-  @location(2) @interpolate(flat)
-  c : i32;
-  @location(3) @interpolate(flat)
-  d : u32;
-  @builtin(position)
-  pos : vec4<f32>;
-}
-
-fn vert_main_inner() -> VertexOutput {
-  return VertexOutput();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.b = inner_result.b;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.d = inner_result.d;
-  wrapper_result.a = inner_result.a;
-  wrapper_result.c = inner_result.c;
-  return wrapper_result;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  a : f32;
-  @location(1) @interpolate(flat)
-  b : u32;
-  @location(2) @interpolate(flat)
-  c : i32;
-  @location(3) @interpolate(flat)
-  d : u32;
-  @builtin(position)
-  pos : vec4<f32>;
-  @builtin(front_facing)
-  ff : bool;
-}
-
-fn frag_main_inner(ff : bool, c : i32, inputs : FragmentInputExtra, b : u32) {
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol_1 : tint_symbol_2) {
-  frag_main_inner(tint_symbol_1.ff, tint_symbol_1.c, FragmentInputExtra(tint_symbol_1.d, tint_symbol_1.pos, tint_symbol_1.a), tint_symbol_1.b);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, SortedMembers_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> VertexOutput {
-  return VertexOutput();
-}
-
-@stage(fragment)
-fn frag_main(@builtin(front_facing) ff : bool,
-             @location(2) @interpolate(flat) c : i32,
-             inputs : FragmentInputExtra,
-             @location(1) @interpolate(flat) b : u32) {
-}
-
-struct VertexOutput {
-  @location(1) @interpolate(flat) b : u32;
-  @builtin(position) pos : vec4<f32>;
-  @location(3) @interpolate(flat) d : u32;
-  @location(0) a : f32;
-  @location(2) @interpolate(flat) c : i32;
-};
-
-struct FragmentInputExtra {
-  @location(3) @interpolate(flat) d : u32;
-  @builtin(position) pos : vec4<f32>;
-  @location(0) a : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  a : f32;
-  @location(1) @interpolate(flat)
-  b : u32;
-  @location(2) @interpolate(flat)
-  c : i32;
-  @location(3) @interpolate(flat)
-  d : u32;
-  @builtin(position)
-  pos : vec4<f32>;
-}
-
-fn vert_main_inner() -> VertexOutput {
-  return VertexOutput();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.b = inner_result.b;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.d = inner_result.d;
-  wrapper_result.a = inner_result.a;
-  wrapper_result.c = inner_result.c;
-  return wrapper_result;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  a : f32;
-  @location(1) @interpolate(flat)
-  b : u32;
-  @location(2) @interpolate(flat)
-  c : i32;
-  @location(3) @interpolate(flat)
-  d : u32;
-  @builtin(position)
-  pos : vec4<f32>;
-  @builtin(front_facing)
-  ff : bool;
-}
-
-fn frag_main_inner(ff : bool, c : i32, inputs : FragmentInputExtra, b : u32) {
-}
-
-@stage(fragment)
-fn frag_main(tint_symbol_1 : tint_symbol_2) {
-  frag_main_inner(tint_symbol_1.ff, tint_symbol_1.c, FragmentInputExtra(tint_symbol_1.d, tint_symbol_1.pos, tint_symbol_1.a), tint_symbol_1.b);
-}
-
-struct VertexOutput {
-  b : u32;
-  pos : vec4<f32>;
-  d : u32;
-  a : f32;
-  c : i32;
-}
-
-struct FragmentInputExtra {
-  d : u32;
-  pos : vec4<f32>;
-  a : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, DontRenameSymbols) {
-  auto* src = R"(
-@stage(fragment)
-fn tint_symbol_1(@location(0) col : f32) {
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  @location(0)
-  col : f32;
-}
-
-fn tint_symbol_1_inner(col : f32) {
-}
-
-@stage(fragment)
-fn tint_symbol_1(tint_symbol : tint_symbol_2) {
-  tint_symbol_1_inner(tint_symbol.col);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidNoReturn) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() {
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main_inner() {
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.fixed_sample_mask = 3u;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidWithReturn) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() {
-  return;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main_inner() {
-  return;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.fixed_sample_mask = 3u;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithAuthoredMask) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> @builtin(sample_mask) u32 {
-  return 7u;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(sample_mask)
-  value : u32;
-}
-
-fn frag_main_inner() -> u32 {
-  return 7u;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = (inner_result & 3u);
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithoutAuthoredMask) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> @location(0) f32 {
-  return 1.0;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  value : f32;
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main_inner() -> f32 {
-  return 1.0;
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = inner_result;
-  wrapper_result.fixed_sample_mask = 3u;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_StructWithAuthoredMask) {
-  auto* src = R"(
-struct Output {
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-  @location(0) value : f32;
-};
-
-@stage(fragment)
-fn frag_main() -> Output {
-  return Output(0.5, 7u, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct Output {
-  depth : f32;
-  mask : u32;
-  value : f32;
-}
-
-struct tint_symbol {
-  @location(0)
-  value : f32;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> Output {
-  return Output(0.5, 7u, 1.0);
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = (inner_result.mask & 3u);
-  wrapper_result.value = inner_result.value;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       FixedSampleMask_StructWithAuthoredMask_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> Output {
-  return Output(0.5, 7u, 1.0);
-}
-
-struct Output {
-  @builtin(frag_depth) depth : f32;
-  @builtin(sample_mask) mask : u32;
-  @location(0) value : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  value : f32;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  mask : u32;
-}
-
-fn frag_main_inner() -> Output {
-  return Output(0.5, 7u, 1.0);
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.mask = (inner_result.mask & 3u);
-  wrapper_result.value = inner_result.value;
-  return wrapper_result;
-}
-
-struct Output {
-  depth : f32;
-  mask : u32;
-  value : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       FixedSampleMask_StructWithoutAuthoredMask) {
-  auto* src = R"(
-struct Output {
-  @builtin(frag_depth) depth : f32;
-  @location(0) value : f32;
-};
-
-@stage(fragment)
-fn frag_main() -> Output {
-  return Output(0.5, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct Output {
-  depth : f32;
-  value : f32;
-}
-
-struct tint_symbol {
-  @location(0)
-  value : f32;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main_inner() -> Output {
-  return Output(0.5, 1.0);
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.value = inner_result.value;
-  wrapper_result.fixed_sample_mask = 3u;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       FixedSampleMask_StructWithoutAuthoredMask_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main() -> Output {
-  return Output(0.5, 1.0);
-}
-
-struct Output {
-  @builtin(frag_depth) depth : f32;
-  @location(0) value : f32;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @location(0)
-  value : f32;
-  @builtin(frag_depth)
-  depth : f32;
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main_inner() -> Output {
-  return Output(0.5, 1.0);
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.depth = inner_result.depth;
-  wrapper_result.value = inner_result.value;
-  wrapper_result.fixed_sample_mask = 3u;
-  return wrapper_result;
-}
-
-struct Output {
-  depth : f32;
-  value : f32;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_MultipleShaders) {
-  auto* src = R"(
-@stage(fragment)
-fn frag_main1() -> @builtin(sample_mask) u32 {
-  return 7u;
-}
-
-@stage(fragment)
-fn frag_main2() -> @location(0) f32 {
-  return 1.0;
-}
-
-@stage(vertex)
-fn vert_main1() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(sample_mask)
-  value : u32;
-}
-
-fn frag_main1_inner() -> u32 {
-  return 7u;
-}
-
-@stage(fragment)
-fn frag_main1() -> tint_symbol {
-  let inner_result = frag_main1_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = (inner_result & 3u);
-  return wrapper_result;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  value : f32;
-  @builtin(sample_mask)
-  fixed_sample_mask : u32;
-}
-
-fn frag_main2_inner() -> f32 {
-  return 1.0;
-}
-
-@stage(fragment)
-fn frag_main2() -> tint_symbol_1 {
-  let inner_result_1 = frag_main2_inner();
-  var wrapper_result_1 : tint_symbol_1;
-  wrapper_result_1.value = inner_result_1;
-  wrapper_result_1.fixed_sample_mask = 3u;
-  return wrapper_result_1;
-}
-
-struct tint_symbol_2 {
-  @builtin(position)
-  value : vec4<f32>;
-}
-
-fn vert_main1_inner() -> vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn vert_main1() -> tint_symbol_2 {
-  let inner_result_2 = vert_main1_inner();
-  var wrapper_result_2 : tint_symbol_2;
-  wrapper_result_2.value = inner_result_2;
-  return wrapper_result_2;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_AvoidNameClash) {
-  auto* src = R"(
-struct FragOut {
-  @location(0) fixed_sample_mask : vec4<f32>;
-  @location(1) fixed_sample_mask_1 : vec4<f32>;
-};
-
-@stage(fragment)
-fn frag_main() -> FragOut {
-  return FragOut();
-}
-)";
-
-  auto* expect = R"(
-struct FragOut {
-  fixed_sample_mask : vec4<f32>;
-  fixed_sample_mask_1 : vec4<f32>;
-}
-
-struct tint_symbol {
-  @location(0)
-  fixed_sample_mask : vec4<f32>;
-  @location(1)
-  fixed_sample_mask_1 : vec4<f32>;
-  @builtin(sample_mask)
-  fixed_sample_mask_2 : u32;
-}
-
-fn frag_main_inner() -> FragOut {
-  return FragOut();
-}
-
-@stage(fragment)
-fn frag_main() -> tint_symbol {
-  let inner_result = frag_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.fixed_sample_mask = inner_result.fixed_sample_mask;
-  wrapper_result.fixed_sample_mask_1 = inner_result.fixed_sample_mask_1;
-  wrapper_result.fixed_sample_mask_2 = 3u;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_ReturnNonStruct_Spirv) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> value : vec4<f32>;
-
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
-
-fn vert_main_inner() -> vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner();
-  value = inner_result;
-  vertex_point_size = 1.0;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct_Msl) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(position)
-  value : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size : f32;
-}
-
-fn vert_main_inner() -> vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.value = inner_result;
-  wrapper_result.vertex_point_size = 1.0;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Spirv) {
-  auto* src = R"(
-struct VertOut {
-  @builtin(position) pos : vec4<f32>;
-};
-
-@stage(vertex)
-fn vert_main() -> VertOut {
-  return VertOut();
-}
-)";
-
-  auto* expect = R"(
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
-
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
-
-struct VertOut {
-  pos : vec4<f32>;
-}
-
-fn vert_main_inner() -> VertOut {
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner();
-  pos_1 = inner_result.pos;
-  vertex_point_size = 1.0;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_ReturnStruct_Spirv_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> VertOut {
-  return VertOut();
-}
-
-struct VertOut {
-  @builtin(position) pos : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
-
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
-
-fn vert_main_inner() -> VertOut {
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner();
-  pos_1 = inner_result.pos;
-  vertex_point_size = 1.0;
-}
-
-struct VertOut {
-  pos : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Msl) {
-  auto* src = R"(
-struct VertOut {
-  @builtin(position) pos : vec4<f32>;
-};
-
-@stage(vertex)
-fn vert_main() -> VertOut {
-  return VertOut();
-}
-)";
-
-  auto* expect = R"(
-struct VertOut {
-  pos : vec4<f32>;
-}
-
-struct tint_symbol {
-  @builtin(position)
-  pos : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size : f32;
-}
-
-fn vert_main_inner() -> VertOut {
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.vertex_point_size = 1.0;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_ReturnStruct_Msl_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> VertOut {
-  return VertOut();
-}
-
-struct VertOut {
-  @builtin(position) pos : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  @builtin(position)
-  pos : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size : f32;
-}
-
-fn vert_main_inner() -> VertOut {
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() -> tint_symbol {
-  let inner_result = vert_main_inner();
-  var wrapper_result : tint_symbol;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.vertex_point_size = 1.0;
-  return wrapper_result;
-}
-
-struct VertOut {
-  pos : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Spirv) {
-  auto* src = R"(
-var<private> vertex_point_size : f32;
-var<private> vertex_point_size_1 : f32;
-var<private> vertex_point_size_2 : f32;
-
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-struct VertOut {
-  @location(0) vertex_point_size : f32;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> collide_2 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> collide_3 : f32;
-
-@location(0) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_3 : f32;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
-
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
-
-var<private> vertex_point_size : f32;
-
-var<private> vertex_point_size_1 : f32;
-
-var<private> vertex_point_size_2 : f32;
-
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-struct VertOut {
-  vertex_point_size : f32;
-  vertex_point_size_1 : vec4<f32>;
-}
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
-  vertex_point_size_3 = inner_result.vertex_point_size;
-  vertex_point_size_1_1 = inner_result.vertex_point_size_1;
-  vertex_point_size_4 = 1.0;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_AvoidNameClash_Spirv_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-var<private> vertex_point_size : f32;
-var<private> vertex_point_size_1 : f32;
-var<private> vertex_point_size_2 : f32;
-
-struct VertOut {
-  @location(0) vertex_point_size : f32;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-@location(0) @internal(disable_validation__ignore_storage_class) var<in> collide_2 : f32;
-
-@location(1) @internal(disable_validation__ignore_storage_class) var<in> collide_3 : f32;
-
-@location(0) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_3 : f32;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
-
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main() {
-  let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
-  vertex_point_size_3 = inner_result.vertex_point_size;
-  vertex_point_size_1_1 = inner_result.vertex_point_size_1;
-  vertex_point_size_4 = 1.0;
-}
-
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-var<private> vertex_point_size : f32;
-
-var<private> vertex_point_size_1 : f32;
-
-var<private> vertex_point_size_2 : f32;
-
-struct VertOut {
-  vertex_point_size : f32;
-  vertex_point_size_1 : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Msl) {
-  auto* src = R"(
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-struct VertOut {
-  @location(0) vertex_point_size : vec4<f32>;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-)";
-
-  auto* expect = R"(
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-struct VertOut {
-  vertex_point_size : vec4<f32>;
-  vertex_point_size_1 : vec4<f32>;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  collide : f32;
-  @location(1)
-  collide_2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  vertex_point_size : vec4<f32>;
-  @builtin(position)
-  vertex_point_size_1 : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size_2 : f32;
-}
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
-  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_AvoidNameClash_Msl_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-struct VertOut {
-  @location(0) vertex_point_size : vec4<f32>;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  collide : f32;
-  @location(1)
-  collide_2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  vertex_point_size : vec4<f32>;
-  @builtin(position)
-  vertex_point_size_1 : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size_2 : f32;
-}
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
-  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
-  return wrapper_result;
-}
-
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-struct VertOut {
-  vertex_point_size : vec4<f32>;
-  vertex_point_size_1 : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Hlsl) {
-  auto* src = R"(
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-struct VertOut {
-  @location(0) vertex_point_size : vec4<f32>;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-)";
-
-  auto* expect = R"(
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-struct VertOut {
-  vertex_point_size : vec4<f32>;
-  vertex_point_size_1 : vec4<f32>;
-}
-
-struct tint_symbol_1 {
-  @location(0)
-  collide : f32;
-  @location(1)
-  collide_2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  vertex_point_size : vec4<f32>;
-  @builtin(position)
-  vertex_point_size_1 : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size_2 : f32;
-}
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
-  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
-  return wrapper_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest,
-       EmitVertexPointSize_AvoidNameClash_Hlsl_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = collide.collide + collide_1.collide;
-  return VertOut();
-}
-
-struct VertIn1 {
-  @location(0) collide : f32;
-};
-
-struct VertIn2 {
-  @location(1) collide : f32;
-};
-
-struct VertOut {
-  @location(0) vertex_point_size : vec4<f32>;
-  @builtin(position) vertex_point_size_1 : vec4<f32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  @location(0)
-  collide : f32;
-  @location(1)
-  collide_2 : f32;
-}
-
-struct tint_symbol_2 {
-  @location(0)
-  vertex_point_size : vec4<f32>;
-  @builtin(position)
-  vertex_point_size_1 : vec4<f32>;
-  @builtin(pointsize)
-  vertex_point_size_2 : f32;
-}
-
-fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
-  let x = (collide.collide + collide_1.collide);
-  return VertOut();
-}
-
-@stage(vertex)
-fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 {
-  let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2));
-  var wrapper_result : tint_symbol_2;
-  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
-  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
-  return wrapper_result;
-}
-
-struct VertIn1 {
-  collide : f32;
-}
-
-struct VertIn2 {
-  collide : f32;
-}
-
-struct VertOut {
-  vertex_point_size : vec4<f32>;
-  vertex_point_size_1 : vec4<f32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl, 0xFFFFFFFF, true);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, SpirvSampleMaskBuiltins) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(sample_index) sample_index : u32,
-        @builtin(sample_mask) mask_in : u32
-        ) -> @builtin(sample_mask) u32 {
-  return mask_in;
-}
-)";
-
-  auto* expect = R"(
-@builtin(sample_index) @internal(disable_validation__ignore_storage_class) var<in> sample_index_1 : u32;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<in> mask_in_1 : array<u32, 1>;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> value : array<u32, 1>;
-
-fn main_inner(sample_index : u32, mask_in : u32) -> u32 {
-  return mask_in;
-}
-
-@stage(fragment)
-fn main() {
-  let inner_result = main_inner(sample_index_1, mask_in_1[0]);
-  value[0] = inner_result;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kSpirv);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, GLSLSampleMaskBuiltins) {
-  auto* src = R"(
-@stage(fragment)
-fn fragment_main(@builtin(sample_index) sample_index : u32,
-                 @builtin(sample_mask) mask_in : u32
-                 ) -> @builtin(sample_mask) u32 {
-  return mask_in;
-}
-)";
-
-  auto* expect = R"(
-@builtin(sample_index) @internal(disable_validation__ignore_storage_class) var<in> gl_SampleID : i32;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<in> gl_SampleMaskIn : array<i32, 1>;
-
-@builtin(sample_mask) @internal(disable_validation__ignore_storage_class) var<out> gl_SampleMask : array<i32, 1>;
-
-fn fragment_main(sample_index : u32, mask_in : u32) -> u32 {
-  return mask_in;
-}
-
-@stage(fragment)
-fn main() {
-  let inner_result = fragment_main(bitcast<u32>(gl_SampleID), bitcast<u32>(gl_SampleMaskIn[0]));
-  gl_SampleMask[0] = bitcast<i32>(inner_result);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CanonicalizeEntryPointIOTest, GLSLVertexInstanceIndexBuiltins) {
-  auto* src = R"(
-@stage(vertex)
-fn vertex_main(@builtin(vertex_index) vertexID : u32,
-               @builtin(instance_index) instanceID : u32
-               ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(f32(vertexID) + f32(instanceID));
-}
-)";
-
-  auto* expect = R"(
-@builtin(vertex_index) @internal(disable_validation__ignore_storage_class) var<in> gl_VertexID : i32;
-
-@builtin(instance_index) @internal(disable_validation__ignore_storage_class) var<in> gl_InstanceID : i32;
-
-@builtin(position) @internal(disable_validation__ignore_storage_class) var<out> gl_Position : vec4<f32>;
-
-fn vertex_main(vertexID : u32, instanceID : u32) -> vec4<f32> {
-  return vec4<f32>((f32(vertexID) + f32(instanceID)));
-}
-
-@stage(vertex)
-fn main() {
-  let inner_result = vertex_main(bitcast<u32>(gl_VertexID), bitcast<u32>(gl_InstanceID));
-  gl_Position = inner_result;
-  gl_Position.y = -(gl_Position.y);
-  gl_Position.z = ((2.0 * gl_Position.z) - gl_Position.w);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/combine_samplers.cc b/src/transform/combine_samplers.cc
deleted file mode 100644
index 5efb357..0000000
--- a/src/transform/combine_samplers.cc
+++ /dev/null
@@ -1,355 +0,0 @@
-// 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/transform/combine_samplers.h"
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers::BindingInfo);
-
-namespace {
-
-bool IsGlobal(const tint::sem::VariablePair& pair) {
-  return pair.first->Is<tint::sem::GlobalVariable>() &&
-         (!pair.second || pair.second->Is<tint::sem::GlobalVariable>());
-}
-
-}  // namespace
-
-namespace tint {
-namespace transform {
-
-CombineSamplers::BindingInfo::BindingInfo(const BindingMap& map,
-                                          const sem::BindingPoint& placeholder)
-    : binding_map(map), placeholder_binding_point(placeholder) {}
-CombineSamplers::BindingInfo::BindingInfo(const BindingInfo& other) = default;
-CombineSamplers::BindingInfo::~BindingInfo() = default;
-
-/// The PIMPL state for the CombineSamplers transform
-struct CombineSamplers::State {
-  /// The clone context
-  CloneContext& ctx;
-
-  /// The binding info
-  const BindingInfo* binding_info;
-
-  /// Map from a texture/sampler pair to the corresponding combined sampler
-  /// variable
-  using CombinedTextureSamplerMap =
-      std::unordered_map<sem::VariablePair, const ast::Variable*>;
-
-  /// Use sem::BindingPoint without scope.
-  using BindingPoint = sem::BindingPoint;
-
-  /// A map of all global texture/sampler variable pairs to the global
-  /// combined sampler variable that will replace it.
-  CombinedTextureSamplerMap global_combined_texture_samplers_;
-
-  /// A map of all texture/sampler variable pairs that contain a function
-  /// parameter to the combined sampler function paramter that will replace it.
-  std::unordered_map<const sem::Function*, CombinedTextureSamplerMap>
-      function_combined_texture_samplers_;
-
-  /// Placeholder global samplers used when a function contains texture-only
-  /// references (one comparison sampler, one regular). These are also used as
-  /// temporary sampler parameters to the texture builtins to satisfy the WGSL
-  /// resolver, but are then ignored and removed by the GLSL writer.
-  const ast::Variable* placeholder_samplers_[2] = {};
-
-  /// Group and binding attributes used by all combined sampler globals.
-  /// Group 0 and binding 0 are used, with collisions disabled.
-  /// @returns the newly-created attribute list
-  ast::AttributeList Attributes() const {
-    auto attributes = ctx.dst->GroupAndBinding(0, 0);
-    attributes.push_back(
-        ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
-    return attributes;
-  }
-
-  /// Constructor
-  /// @param context the clone context
-  /// @param info the binding map information
-  State(CloneContext& context, const BindingInfo* info)
-      : ctx(context), binding_info(info) {}
-
-  /// Creates a combined sampler global variables.
-  /// (Note this is actually a Texture node at the AST level, but it will be
-  /// written as the corresponding sampler (eg., sampler2D) on GLSL output.)
-  /// @param texture_var the texture (global) variable
-  /// @param sampler_var the sampler (global) variable
-  /// @param name the default name to use (may be overridden by map lookup)
-  /// @returns the newly-created global variable
-  const ast::Variable* CreateCombinedGlobal(const sem::Variable* texture_var,
-                                            const sem::Variable* sampler_var,
-                                            std::string name) {
-    SamplerTexturePair bp_pair;
-    bp_pair.texture_binding_point =
-        texture_var->As<sem::GlobalVariable>()->BindingPoint();
-    bp_pair.sampler_binding_point =
-        sampler_var ? sampler_var->As<sem::GlobalVariable>()->BindingPoint()
-                    : binding_info->placeholder_binding_point;
-    auto it = binding_info->binding_map.find(bp_pair);
-    if (it != binding_info->binding_map.end()) {
-      name = it->second;
-    }
-    const ast::Type* type = CreateCombinedASTTypeFor(texture_var, sampler_var);
-    Symbol symbol = ctx.dst->Symbols().New(name);
-    return ctx.dst->Global(symbol, type, Attributes());
-  }
-
-  /// Creates placeholder global sampler variables.
-  /// @param kind the sampler kind to create for
-  /// @returns the newly-created global variable
-  const ast::Variable* CreatePlaceholder(ast::SamplerKind kind) {
-    const ast::Type* type = ctx.dst->ty.sampler(kind);
-    const char* name = kind == ast::SamplerKind::kComparisonSampler
-                           ? "placeholder_comparison_sampler"
-                           : "placeholder_sampler";
-    Symbol symbol = ctx.dst->Symbols().New(name);
-    return ctx.dst->Global(symbol, type, Attributes());
-  }
-
-  /// Creates ast::Type for a given texture and sampler variable pair.
-  /// Depth textures with no samplers are turned into the corresponding
-  /// f32 texture (e.g., texture_depth_2d -> texture_2d<f32>).
-  /// @param texture the texture variable of interest
-  /// @param sampler the texture variable of interest
-  /// @returns the newly-created type
-  const ast::Type* CreateCombinedASTTypeFor(const sem::Variable* texture,
-                                            const sem::Variable* sampler) {
-    const sem::Type* texture_type = texture->Type()->UnwrapRef();
-    const sem::DepthTexture* depth = texture_type->As<sem::DepthTexture>();
-    if (depth && !sampler) {
-      return ctx.dst->create<ast::SampledTexture>(depth->dim(),
-                                                  ctx.dst->create<ast::F32>());
-    } else {
-      return CreateASTTypeFor(ctx, texture_type);
-    }
-  }
-
-  /// Performs the transformation
-  void Run() {
-    auto& sem = ctx.src->Sem();
-
-    // Remove all texture and sampler global variables. These will be replaced
-    // by combined samplers.
-    for (auto* var : ctx.src->AST().GlobalVariables()) {
-      auto* type = sem.Get(var->type);
-      if (type && type->IsAnyOf<sem::Texture, sem::Sampler>() &&
-          !type->Is<sem::StorageTexture>()) {
-        ctx.Remove(ctx.src->AST().GlobalDeclarations(), var);
-      } else if (auto binding_point = var->BindingPoint()) {
-        if (binding_point.group->value == 0 &&
-            binding_point.binding->value == 0) {
-          auto* attribute =
-              ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
-          ctx.InsertFront(var->attributes, attribute);
-        }
-      }
-    }
-
-    // Rewrite all function signatures to use combined samplers, and remove
-    // separate textures & samplers. Create new combined globals where found.
-    ctx.ReplaceAll([&](const ast::Function* src) -> const ast::Function* {
-      if (auto* func = sem.Get(src)) {
-        auto pairs = func->TextureSamplerPairs();
-        if (pairs.empty()) {
-          return nullptr;
-        }
-        ast::VariableList params;
-        for (auto pair : func->TextureSamplerPairs()) {
-          const sem::Variable* texture_var = pair.first;
-          const sem::Variable* sampler_var = pair.second;
-          std::string name =
-              ctx.src->Symbols().NameFor(texture_var->Declaration()->symbol);
-          if (sampler_var) {
-            name += "_" + ctx.src->Symbols().NameFor(
-                              sampler_var->Declaration()->symbol);
-          }
-          if (IsGlobal(pair)) {
-            // Both texture and sampler are global; add a new global variable
-            // to represent the combined sampler (if not already created).
-            utils::GetOrCreate(global_combined_texture_samplers_, pair, [&] {
-              return CreateCombinedGlobal(texture_var, sampler_var, name);
-            });
-          } else {
-            // Either texture or sampler (or both) is a function parameter;
-            // add a new function parameter to represent the combined sampler.
-            const ast::Type* type =
-                CreateCombinedASTTypeFor(texture_var, sampler_var);
-            const ast::Variable* var =
-                ctx.dst->Param(ctx.dst->Symbols().New(name), type);
-            params.push_back(var);
-            function_combined_texture_samplers_[func][pair] = var;
-          }
-        }
-        // Filter out separate textures and samplers from the original
-        // function signature.
-        for (auto* var : src->params) {
-          if (!sem.Get(var->type)->IsAnyOf<sem::Texture, sem::Sampler>()) {
-            params.push_back(ctx.Clone(var));
-          }
-        }
-        // Create a new function signature that differs only in the parameter
-        // list.
-        auto symbol = ctx.Clone(src->symbol);
-        auto* return_type = ctx.Clone(src->return_type);
-        auto* body = ctx.Clone(src->body);
-        auto attributes = ctx.Clone(src->attributes);
-        auto return_type_attributes = ctx.Clone(src->return_type_attributes);
-        return ctx.dst->create<ast::Function>(
-            symbol, params, return_type, body, std::move(attributes),
-            std::move(return_type_attributes));
-      }
-      return nullptr;
-    });
-
-    // Replace all function call expressions containing texture or
-    // sampler parameters to use the current function's combined samplers or
-    // the combined global samplers, as appropriate.
-    ctx.ReplaceAll([&](const ast::CallExpression* expr)
-                       -> const ast::Expression* {
-      if (auto* call = sem.Get(expr)) {
-        ast::ExpressionList args;
-        // Replace all texture builtin calls.
-        if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-          const auto& signature = builtin->Signature();
-          int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
-          int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
-          if (texture_index == -1) {
-            return nullptr;
-          }
-          const sem::Expression* texture = call->Arguments()[texture_index];
-          // We don't want to combine storage textures with anything, since
-          // they never have associated samplers in GLSL.
-          if (texture->Type()->UnwrapRef()->Is<sem::StorageTexture>()) {
-            return nullptr;
-          }
-          const sem::Expression* sampler =
-              sampler_index != -1 ? call->Arguments()[sampler_index] : nullptr;
-          auto* texture_var = texture->As<sem::VariableUser>()->Variable();
-          auto* sampler_var =
-              sampler ? sampler->As<sem::VariableUser>()->Variable() : nullptr;
-          sem::VariablePair new_pair(texture_var, sampler_var);
-          for (auto* arg : expr->args) {
-            auto* type = ctx.src->TypeOf(arg)->UnwrapRef();
-            if (type->Is<sem::Texture>()) {
-              const ast::Variable* var =
-                  IsGlobal(new_pair)
-                      ? global_combined_texture_samplers_[new_pair]
-                      : function_combined_texture_samplers_
-                            [call->Stmt()->Function()][new_pair];
-              args.push_back(ctx.dst->Expr(var->symbol));
-            } else if (auto* sampler_type = type->As<sem::Sampler>()) {
-              ast::SamplerKind kind = sampler_type->kind();
-              int index = (kind == ast::SamplerKind::kSampler) ? 0 : 1;
-              const ast::Variable*& p = placeholder_samplers_[index];
-              if (!p) {
-                p = CreatePlaceholder(kind);
-              }
-              args.push_back(ctx.dst->Expr(p->symbol));
-            } else {
-              args.push_back(ctx.Clone(arg));
-            }
-          }
-          const ast::Expression* value =
-              ctx.dst->Call(ctx.Clone(expr->target.name), args);
-          if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
-              texture_var->Type()->UnwrapRef()->Is<sem::DepthTexture>() &&
-              !call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
-            value = ctx.dst->MemberAccessor(value, "x");
-          }
-          return value;
-        }
-        // Replace all function calls.
-        if (auto* callee = call->Target()->As<sem::Function>()) {
-          for (auto pair : callee->TextureSamplerPairs()) {
-            // Global pairs used by the callee do not require a function
-            // parameter at the call site.
-            if (IsGlobal(pair)) {
-              continue;
-            }
-            const sem::Variable* texture_var = pair.first;
-            const sem::Variable* sampler_var = pair.second;
-            if (auto* param = texture_var->As<sem::Parameter>()) {
-              const sem::Expression* texture =
-                  call->Arguments()[param->Index()];
-              texture_var = texture->As<sem::VariableUser>()->Variable();
-            }
-            if (sampler_var) {
-              if (auto* param = sampler_var->As<sem::Parameter>()) {
-                const sem::Expression* sampler =
-                    call->Arguments()[param->Index()];
-                sampler_var = sampler->As<sem::VariableUser>()->Variable();
-              }
-            }
-            sem::VariablePair new_pair(texture_var, sampler_var);
-            // If both texture and sampler are (now) global, pass that
-            // global variable to the callee. Otherwise use the caller's
-            // function parameter for this pair.
-            const ast::Variable* var =
-                IsGlobal(new_pair) ? global_combined_texture_samplers_[new_pair]
-                                   : function_combined_texture_samplers_
-                                         [call->Stmt()->Function()][new_pair];
-            auto* arg = ctx.dst->Expr(var->symbol);
-            args.push_back(arg);
-          }
-          // Append all of the remaining non-texture and non-sampler
-          // parameters.
-          for (auto* arg : expr->args) {
-            if (!ctx.src->TypeOf(arg)
-                     ->UnwrapRef()
-                     ->IsAnyOf<sem::Texture, sem::Sampler>()) {
-              args.push_back(ctx.Clone(arg));
-            }
-          }
-          return ctx.dst->Call(ctx.Clone(expr->target.name), args);
-        }
-      }
-      return nullptr;
-    });
-
-    ctx.Clone();
-  }
-};
-
-CombineSamplers::CombineSamplers() = default;
-
-CombineSamplers::~CombineSamplers() = default;
-
-void CombineSamplers::Run(CloneContext& ctx,
-                          const DataMap& inputs,
-                          DataMap&) const {
-  auto* binding_info = inputs.Get<BindingInfo>();
-  if (!binding_info) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  State(ctx, binding_info).Run();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/combine_samplers.h b/src/transform/combine_samplers.h
deleted file mode 100644
index d6726d9..0000000
--- a/src/transform/combine_samplers.h
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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_TRANSFORM_COMBINE_SAMPLERS_H_
-#define SRC_TRANSFORM_COMBINE_SAMPLERS_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/sem/sampler_texture_pair.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// This transform converts all separate texture/sampler refences in a
-/// program into combined texture/samplers. This is required for GLSL,
-/// which does not support separate texture/samplers.
-///
-/// It utilizes the texture/sampler information collected by the
-/// Resolver and stored on each sem::Function. For each function, all
-/// separate texture/sampler parameters in the function signature are
-/// removed. For each unique pair, if both texture and sampler are
-/// global variables, the function passes the corresponding combined
-/// global stored in global_combined_texture_samplers_ at the call
-/// site. Otherwise, either the texture or sampler must be a function
-/// parameter. In this case, a new parameter is added to the function
-/// signature. All separate texture/sampler parameters are removed.
-///
-/// All texture builtin callsites are modified to pass the combined
-/// texture/sampler as the first argument, and separate texture/sampler
-/// arguments are removed.
-///
-/// Note that the sampler may be null, indicating that only a texture
-/// reference was required (e.g., textureLoad). In this case, a
-/// placeholder global sampler is used at the AST level. This will be
-/// combined with the original texture to give a combined global, and
-/// the placeholder removed (ignored) by the GLSL writer.
-///
-/// Note that the combined samplers are actually represented by a
-/// Texture node at the AST level, since this contains all the
-/// information needed to represent a combined sampler in GLSL
-/// (dimensionality, component type, etc). The GLSL writer outputs such
-/// (Tint) Textures as (GLSL) Samplers.
-class CombineSamplers : public Castable<CombineSamplers, Transform> {
- public:
-  /// A pair of binding points.
-  using SamplerTexturePair = sem::SamplerTexturePair;
-
-  /// A map from a sampler/texture pair to a named global.
-  using BindingMap = std::unordered_map<SamplerTexturePair, std::string>;
-
-  /// The client-provided mapping from separate texture and sampler binding
-  /// points to combined sampler binding point.
-  struct BindingInfo : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param map the map of all (texture, sampler) -> (combined) pairs
-    /// @param placeholder the binding point to use for placeholder samplers.
-    BindingInfo(const BindingMap& map, const sem::BindingPoint& placeholder);
-
-    /// Copy constructor
-    /// @param other the other BindingInfo to copy
-    BindingInfo(const BindingInfo& other);
-
-    /// Destructor
-    ~BindingInfo() override;
-
-    /// A map of bindings from (texture, sampler) -> combined sampler.
-    BindingMap binding_map;
-
-    /// The binding point to use for placeholder samplers.
-    sem::BindingPoint placeholder_binding_point;
-  };
-
-  /// Constructor
-  CombineSamplers();
-
-  /// Destructor
-  ~CombineSamplers() override;
-
- protected:
-  /// The PIMPL state for this transform
-  struct State;
-
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_COMBINE_SAMPLERS_H_
diff --git a/src/transform/combine_samplers_test.cc b/src/transform/combine_samplers_test.cc
deleted file mode 100644
index 481ef3d..0000000
--- a/src/transform/combine_samplers_test.cc
+++ /dev/null
@@ -1,1012 +0,0 @@
-// 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/transform/combine_samplers.h"
-
-#include <memory>
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using CombineSamplersTest = TransformTest;
-
-TEST_F(CombineSamplersTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePair) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@group(0) @binding(1) var s : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePair_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return textureSample(t, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@group(0) @binding(1) var s : sampler;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairInAFunction) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@group(0) @binding(1) var s : sampler;
-
-fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-fn main() -> vec4<f32> {
-  return sample(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, coords);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return sample(t_s_1, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairInAFunction_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return sample(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-@group(0) @binding(1) var s : sampler;
-
-@group(0) @binding(0) var t : texture_2d<f32>;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return sample(t_s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s_1, placeholder_sampler, coords);
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairRename) {
-  auto* src = R"(
-@group(0) @binding(1) var t : texture_2d<f32>;
-
-@group(2) @binding(3) var s : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fuzzy : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(fuzzy, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  CombineSamplers::BindingMap map;
-  sem::SamplerTexturePair pair;
-  pair.texture_binding_point.group = 0;
-  pair.texture_binding_point.binding = 1;
-  pair.sampler_binding_point.group = 2;
-  pair.sampler_binding_point.binding = 3;
-  map[pair] = "fuzzy";
-  sem::BindingPoint placeholder{1024, 0};
-  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairRenameMiss) {
-  auto* src = R"(
-@group(0) @binding(1) var t : texture_2d<f32>;
-
-@group(2) @binding(3) var s : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  CombineSamplers::BindingMap map;
-  sem::SamplerTexturePair pair;
-  pair.texture_binding_point.group = 3;
-  pair.texture_binding_point.binding = 2;
-  pair.sampler_binding_point.group = 1;
-  pair.sampler_binding_point.binding = 0;
-  map[pair] = "fuzzy";
-  sem::BindingPoint placeholder{1024, 0};
-  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, AliasedTypes) {
-  auto* src = R"(
-
-type Tex2d = texture_2d<f32>;
-
-@group(0) @binding(0) var t : Tex2d;
-
-@group(0) @binding(1) var s : sampler;
-
-fn sample(t : Tex2d, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-fn main() -> vec4<f32> {
-  return sample(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-type Tex2d = texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, coords);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return sample(t_s_1, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, AliasedTypes_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return sample(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn sample(t : Tex2d, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-@group(0) @binding(0) var t : Tex2d;
-@group(0) @binding(1) var s : sampler;
-
-type Tex2d = texture_2d<f32>;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return sample(t_s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s_1, placeholder_sampler, coords);
-}
-
-type Tex2d = texture_2d<f32>;
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairInTwoFunctions) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@group(0) @binding(1) var s : sampler;
-
-fn g(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-fn f(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return g(t, s, coords);
-}
-
-fn main() -> vec4<f32> {
-  return f(t, s, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn g(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, coords);
-}
-
-fn f(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return g(t_s_1, coords);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_2 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(t_s_2, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, SimplePairInTwoFunctions_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return f(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn f(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return g(t, s, coords);
-}
-
-fn g(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-@group(0) @binding(1) var s : sampler;
-@group(0) @binding(0) var t : texture_2d<f32>;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(t_s, vec2<f32>(1.0, 2.0));
-}
-
-fn f(t_s_1 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return g(t_s_1, coords);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn g(t_s_2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s_2, placeholder_sampler, coords);
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TwoFunctionsGenerateSamePair) {
-  auto* src = R"(
-@group(1) @binding(0) var tex : texture_2d<f32>;
-
-@group(1) @binding(1) var samp : sampler;
-
-fn f() -> vec4<f32> {
-  return textureSample(tex, samp, vec2<f32>(1.0, 2.0));
-}
-
-fn g() -> vec4<f32> {
-  return textureSample(tex, samp, vec2<f32>(3.0, 4.0));
-}
-
-fn main() -> vec4<f32> {
-  return f() + g();
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn f() -> vec4<f32> {
-  return textureSample(tex_samp, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-
-fn g() -> vec4<f32> {
-  return textureSample(tex_samp, placeholder_sampler, vec2<f32>(3.0, 4.0));
-}
-
-fn main() -> vec4<f32> {
-  return (f() + g());
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, ThreeTexturesThreeSamplers) {
-  auto* src = R"(
-@group(0) @binding(0) var tex1 : texture_2d<f32>;
-@group(0) @binding(1) var tex2 : texture_2d<f32>;
-@group(0) @binding(2) var tex3 : texture_2d<f32>;
-
-@group(1) @binding(0) var samp1 : sampler;
-@group(1) @binding(1) var samp2: sampler;
-@group(1) @binding(2) var samp3: sampler;
-
-fn sample(t : texture_2d<f32>, s : sampler) -> vec4<f32> {
-  return textureSample(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn main() -> vec4<f32> {
-  return sample(tex1, samp1)
-       + sample(tex1, samp2)
-       + sample(tex1, samp3)
-       + sample(tex2, samp1)
-       + sample(tex2, samp2)
-       + sample(tex2, samp3)
-       + sample(tex3, samp1)
-       + sample(tex3, samp2)
-       + sample(tex3, samp3);
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s : texture_2d<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp2 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp3 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp2 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp3 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp2 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp3 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return ((((((((sample(tex1_samp1) + sample(tex1_samp2)) + sample(tex1_samp3)) + sample(tex2_samp1)) + sample(tex2_samp2)) + sample(tex2_samp3)) + sample(tex3_samp1)) + sample(tex3_samp2)) + sample(tex3_samp3));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TwoFunctionsTwoTexturesDiamond) {
-  auto* src = R"(
-@group(0) @binding(0) var tex1 : texture_2d<f32>;
-
-@group(0) @binding(1) var tex2 : texture_2d<f32>;
-
-@group(0) @binding(2) var samp : sampler;
-
-fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return sample(t1, s, coords) + sample(t2, s, coords);
-}
-
-fn main() -> vec4<f32> {
-  return f(tex1, tex2, samp, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, coords);
-}
-
-fn f(t1_s : texture_2d<f32>, t2_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (sample(t1_s, coords) + sample(t2_s, coords));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TwoFunctionsTwoSamplersDiamond) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_2d<f32>;
-
-@group(0) @binding(1) var samp1 : sampler;
-
-@group(0) @binding(2) var samp2 : sampler;
-
-fn sample(t : texture_2d<f32>, s : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t, s, coords);
-}
-
-fn f(t : texture_2d<f32>, s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return sample(t, s1, coords) + sample(t, s2, coords);
-}
-
-fn main() -> vec4<f32> {
-  return f(tex, samp1, samp2, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn sample(t_s : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t_s, placeholder_sampler, coords);
-}
-
-fn f(t_s1 : texture_2d<f32>, t_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (sample(t_s1, coords) + sample(t_s2, coords));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, GlobalTextureLocalSampler) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_2d<f32>;
-
-@group(0) @binding(1) var samp1 : sampler;
-
-@group(0) @binding(2) var samp2 : sampler;
-
-fn f(s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(tex, s1, coords) + textureSample(tex, s2, coords);
-}
-
-fn main() -> vec4<f32> {
-  return f(samp1, samp2, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn f(tex_s1 : texture_2d<f32>, tex_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, GlobalTextureLocalSampler_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return f(samp1, samp2, vec2<f32>(1.0, 2.0));
-}
-
-fn f(s1 : sampler, s2 : sampler, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(tex, s1, coords) + textureSample(tex, s2, coords);
-}
-
-@group(0) @binding(1) var samp1 : sampler;
-@group(0) @binding(2) var samp2 : sampler;
-@group(0) @binding(0) var tex : texture_2d<f32>;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex_samp1, tex_samp2, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn f(tex_s1 : texture_2d<f32>, tex_s2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, LocalTextureGlobalSampler) {
-  auto* src = R"(
-@group(0) @binding(0) var tex1 : texture_2d<f32>;
-
-@group(0) @binding(1) var tex2 : texture_2d<f32>;
-
-@group(0) @binding(2) var samp : sampler;
-
-fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t1, samp, coords) + textureSample(t2, samp, coords);
-}
-
-fn main() -> vec4<f32> {
-  return f(tex1, tex2, vec2<f32>(1.0, 2.0));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn f(t1_samp : texture_2d<f32>, t2_samp : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, LocalTextureGlobalSampler_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return f(tex1, tex2, vec2<f32>(1.0, 2.0));
-}
-
-fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return textureSample(t1, samp, coords) + textureSample(t2, samp, coords);
-}
-
-@group(0) @binding(2) var samp : sampler;
-@group(0) @binding(0) var tex1 : texture_2d<f32>;
-@group(0) @binding(1) var tex2 : texture_2d<f32>;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(tex1_samp, tex2_samp, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn f(t1_samp : texture_2d<f32>, t2_samp : texture_2d<f32>, coords : vec2<f32>) -> vec4<f32> {
-  return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TextureLoadNoSampler) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_2d<f32>;
-
-fn f(t : texture_2d<f32>, coords : vec2<i32>) -> vec4<f32> {
-  return textureLoad(t, coords, 0);
-}
-
-fn main() -> vec4<f32> {
-  return f(tex, vec2<i32>(1, 2));
-}
-)";
-  auto* expect = R"(
-fn f(t_1 : texture_2d<f32>, coords : vec2<i32>) -> vec4<f32> {
-  return textureLoad(t_1, coords, 0);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d<f32>;
-
-fn main() -> vec4<f32> {
-  return f(fred, vec2<i32>(1, 2));
-}
-)";
-
-  sem::BindingPoint placeholder{1024, 0};
-  sem::SamplerTexturePair pair;
-  pair.texture_binding_point.group = 0;
-  pair.texture_binding_point.binding = 0;
-  pair.sampler_binding_point.group = placeholder.group;
-  pair.sampler_binding_point.binding = placeholder.binding;
-  CombineSamplers::BindingMap map;
-  map[pair] = "fred";
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TextureWithAndWithoutSampler) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_2d<f32>;
-@group(0) @binding(1) var samp : sampler;
-
-fn main() -> vec4<f32> {
-  return textureLoad(tex, vec2<i32>(), 0) +
-         textureSample(tex, samp, vec2<f32>());
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var barney : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return (textureLoad(fred, vec2<i32>(), 0) + textureSample(barney, placeholder_sampler, vec2<f32>()));
-}
-)";
-
-  sem::BindingPoint placeholder{1024, 0};
-  sem::BindingPoint tex{0, 0};
-  sem::BindingPoint samp{0, 1};
-  sem::SamplerTexturePair pair, placeholder_pair;
-  pair.texture_binding_point.group = tex.group;
-  pair.texture_binding_point.binding = tex.binding;
-  pair.sampler_binding_point.group = samp.group;
-  pair.sampler_binding_point.binding = samp.binding;
-  placeholder_pair.texture_binding_point.group = tex.group;
-  placeholder_pair.texture_binding_point.binding = tex.binding;
-  placeholder_pair.sampler_binding_point.group = placeholder.group;
-  placeholder_pair.sampler_binding_point.binding = placeholder.binding;
-  CombineSamplers::BindingMap map;
-  map[pair] = "barney";
-  map[placeholder_pair] = "fred";
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(map, placeholder);
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TextureSampleCompare) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_depth_2d;
-
-@group(0) @binding(1) var samp : sampler_comparison;
-
-fn main() -> vec4<f32> {
-  return vec4<f32>(textureSampleCompare(tex, samp, vec2<f32>(1.0, 2.0), 0.5));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
-
-fn main() -> vec4<f32> {
-  return vec4<f32>(textureSampleCompare(tex_samp, placeholder_comparison_sampler, vec2<f32>(1.0, 2.0), 0.5));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction) {
-  auto* src = R"(
-@group(0) @binding(0) var tex : texture_depth_2d;
-
-@group(0) @binding(1) var samp : sampler_comparison;
-
-fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t, s, coords, 5.0f);
-}
-
-fn main() -> vec4<f32> {
-  return vec4<f32>(f(tex, samp, vec2<f32>(1.0, 2.0)));
-}
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
-
-fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
-
-fn main() -> vec4<f32> {
-  return vec4<f32>(f(tex_samp, vec2<f32>(1.0, 2.0)));
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return vec4<f32>(f(tex, samp, vec2<f32>(1.0, 2.0)));
-}
-
-fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t, s, coords, 5.0f);
-}
-
-@group(0) @binding(0) var tex : texture_depth_2d;
-@group(0) @binding(1) var samp : sampler_comparison;
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
-
-fn main() -> vec4<f32> {
-  return vec4<f32>(f(tex_samp, vec2<f32>(1.0, 2.0)));
-}
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
-
-fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, BindingPointCollision) {
-  auto* src = R"(
-@group(1) @binding(0) var tex : texture_2d<f32>;
-
-@group(1) @binding(1) var samp : sampler;
-
-@group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
-
-fn main() -> vec4<f32> {
-  return textureSample(tex, samp, gcoords);
-}
-)";
-  auto* expect = R"(
-@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(tex_samp, placeholder_sampler, gcoords);
-}
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(CombineSamplersTest, BindingPointCollision_OutOfOrder) {
-  auto* src = R"(
-fn main() -> vec4<f32> {
-  return textureSample(tex, samp, gcoords);
-}
-
-@group(1) @binding(1) var samp : sampler;
-@group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
-@group(1) @binding(0) var tex : texture_2d<f32>;
-
-)";
-  auto* expect = R"(
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d<f32>;
-
-@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler;
-
-fn main() -> vec4<f32> {
-  return textureSample(tex_samp, placeholder_sampler, gcoords);
-}
-
-@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var<uniform> gcoords : vec2<f32>;
-)";
-
-  DataMap data;
-  data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                         sem::BindingPoint());
-  auto got = Run<CombineSamplers>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/decompose_memory_access.cc b/src/transform/decompose_memory_access.cc
deleted file mode 100644
index 647977d..0000000
--- a/src/transform/decompose_memory_access.cc
+++ /dev/null
@@ -1,998 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/decompose_memory_access.h"
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/type_name.h"
-#include "src/ast/unary_op.h"
-#include "src/block_allocator.h"
-#include "src/program_builder.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/call.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/struct.h"
-#include "src/sem/variable.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeMemoryAccess);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeMemoryAccess::Intrinsic);
-
-namespace tint {
-namespace transform {
-
-namespace {
-
-/// Offset is a simple ast::Expression builder interface, used to build byte
-/// offsets for storage and uniform buffer accesses.
-struct Offset : Castable<Offset> {
-  /// @returns builds and returns the ast::Expression in `ctx.dst`
-  virtual const ast::Expression* Build(CloneContext& ctx) const = 0;
-};
-
-/// OffsetExpr is an implementation of Offset that clones and casts the given
-/// expression to `u32`.
-struct OffsetExpr : Offset {
-  const ast::Expression* const expr = nullptr;
-
-  explicit OffsetExpr(const ast::Expression* e) : expr(e) {}
-
-  const ast::Expression* Build(CloneContext& ctx) const override {
-    auto* type = ctx.src->Sem().Get(expr)->Type()->UnwrapRef();
-    auto* res = ctx.Clone(expr);
-    if (!type->Is<sem::U32>()) {
-      res = ctx.dst->Construct<ProgramBuilder::u32>(res);
-    }
-    return res;
-  }
-};
-
-/// OffsetLiteral is an implementation of Offset that constructs a u32 literal
-/// value.
-struct OffsetLiteral : Castable<OffsetLiteral, Offset> {
-  uint32_t const literal = 0;
-
-  explicit OffsetLiteral(uint32_t lit) : literal(lit) {}
-
-  const ast::Expression* Build(CloneContext& ctx) const override {
-    return ctx.dst->Expr(literal);
-  }
-};
-
-/// OffsetBinOp is an implementation of Offset that constructs a binary-op of
-/// two Offsets.
-struct OffsetBinOp : Offset {
-  ast::BinaryOp op;
-  Offset const* lhs = nullptr;
-  Offset const* rhs = nullptr;
-
-  const ast::Expression* Build(CloneContext& ctx) const override {
-    return ctx.dst->create<ast::BinaryExpression>(op, lhs->Build(ctx),
-                                                  rhs->Build(ctx));
-  }
-};
-
-/// LoadStoreKey is the unordered map key to a load or store intrinsic.
-struct LoadStoreKey {
-  ast::StorageClass const storage_class;  // buffer storage class
-  sem::Type const* buf_ty = nullptr;      // buffer type
-  sem::Type const* el_ty = nullptr;       // element type
-  bool operator==(const LoadStoreKey& rhs) const {
-    return storage_class == rhs.storage_class && buf_ty == rhs.buf_ty &&
-           el_ty == rhs.el_ty;
-  }
-  struct Hasher {
-    inline std::size_t operator()(const LoadStoreKey& u) const {
-      return utils::Hash(u.storage_class, u.buf_ty, u.el_ty);
-    }
-  };
-};
-
-/// AtomicKey is the unordered map key to an atomic intrinsic.
-struct AtomicKey {
-  sem::Type const* buf_ty = nullptr;  // buffer type
-  sem::Type const* el_ty = nullptr;   // element type
-  sem::BuiltinType const op;          // atomic op
-  bool operator==(const AtomicKey& rhs) const {
-    return buf_ty == rhs.buf_ty && el_ty == rhs.el_ty && op == rhs.op;
-  }
-  struct Hasher {
-    inline std::size_t operator()(const AtomicKey& u) const {
-      return utils::Hash(u.buf_ty, u.el_ty, u.op);
-    }
-  };
-};
-
-bool IntrinsicDataTypeFor(const sem::Type* ty,
-                          DecomposeMemoryAccess::Intrinsic::DataType& out) {
-  if (ty->Is<sem::I32>()) {
-    out = DecomposeMemoryAccess::Intrinsic::DataType::kI32;
-    return true;
-  }
-  if (ty->Is<sem::U32>()) {
-    out = DecomposeMemoryAccess::Intrinsic::DataType::kU32;
-    return true;
-  }
-  if (ty->Is<sem::F32>()) {
-    out = DecomposeMemoryAccess::Intrinsic::DataType::kF32;
-    return true;
-  }
-  if (auto* vec = ty->As<sem::Vector>()) {
-    switch (vec->Width()) {
-      case 2:
-        if (vec->type()->Is<sem::I32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2I32;
-          return true;
-        }
-        if (vec->type()->Is<sem::U32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2U32;
-          return true;
-        }
-        if (vec->type()->Is<sem::F32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec2F32;
-          return true;
-        }
-        break;
-      case 3:
-        if (vec->type()->Is<sem::I32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3I32;
-          return true;
-        }
-        if (vec->type()->Is<sem::U32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3U32;
-          return true;
-        }
-        if (vec->type()->Is<sem::F32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec3F32;
-          return true;
-        }
-        break;
-      case 4:
-        if (vec->type()->Is<sem::I32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4I32;
-          return true;
-        }
-        if (vec->type()->Is<sem::U32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4U32;
-          return true;
-        }
-        if (vec->type()->Is<sem::F32>()) {
-          out = DecomposeMemoryAccess::Intrinsic::DataType::kVec4F32;
-          return true;
-        }
-        break;
-    }
-    return false;
-  }
-
-  return false;
-}
-
-/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
-/// to a stub function to load the type `ty`.
-DecomposeMemoryAccess::Intrinsic* IntrinsicLoadFor(
-    ProgramBuilder* builder,
-    ast::StorageClass storage_class,
-    const sem::Type* ty) {
-  DecomposeMemoryAccess::Intrinsic::DataType type;
-  if (!IntrinsicDataTypeFor(ty, type)) {
-    return nullptr;
-  }
-  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
-      builder->ID(), DecomposeMemoryAccess::Intrinsic::Op::kLoad, storage_class,
-      type);
-}
-
-/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
-/// to a stub function to store the type `ty`.
-DecomposeMemoryAccess::Intrinsic* IntrinsicStoreFor(
-    ProgramBuilder* builder,
-    ast::StorageClass storage_class,
-    const sem::Type* ty) {
-  DecomposeMemoryAccess::Intrinsic::DataType type;
-  if (!IntrinsicDataTypeFor(ty, type)) {
-    return nullptr;
-  }
-  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
-      builder->ID(), DecomposeMemoryAccess::Intrinsic::Op::kStore,
-      storage_class, type);
-}
-
-/// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied
-/// to a stub function for the atomic op and the type `ty`.
-DecomposeMemoryAccess::Intrinsic* IntrinsicAtomicFor(ProgramBuilder* builder,
-                                                     sem::BuiltinType ity,
-                                                     const sem::Type* ty) {
-  auto op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
-  switch (ity) {
-    case sem::BuiltinType::kAtomicLoad:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
-      break;
-    case sem::BuiltinType::kAtomicStore:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicStore;
-      break;
-    case sem::BuiltinType::kAtomicAdd:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAdd;
-      break;
-    case sem::BuiltinType::kAtomicSub:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicSub;
-      break;
-    case sem::BuiltinType::kAtomicMax:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMax;
-      break;
-    case sem::BuiltinType::kAtomicMin:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMin;
-      break;
-    case sem::BuiltinType::kAtomicAnd:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAnd;
-      break;
-    case sem::BuiltinType::kAtomicOr:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicOr;
-      break;
-    case sem::BuiltinType::kAtomicXor:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicXor;
-      break;
-    case sem::BuiltinType::kAtomicExchange:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicExchange;
-      break;
-    case sem::BuiltinType::kAtomicCompareExchangeWeak:
-      op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicCompareExchangeWeak;
-      break;
-    default:
-      TINT_ICE(Transform, builder->Diagnostics())
-          << "invalid IntrinsicType for DecomposeMemoryAccess::Intrinsic: "
-          << ty->type_name();
-      break;
-  }
-
-  DecomposeMemoryAccess::Intrinsic::DataType type;
-  if (!IntrinsicDataTypeFor(ty, type)) {
-    return nullptr;
-  }
-  return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
-      builder->ID(), op, ast::StorageClass::kStorage, type);
-}
-
-/// BufferAccess describes a single storage or uniform buffer access
-struct BufferAccess {
-  sem::Expression const* var = nullptr;  // Storage buffer variable
-  Offset const* offset = nullptr;        // The byte offset on var
-  sem::Type const* type = nullptr;       // The type of the access
-  operator bool() const { return var; }  // Returns true if valid
-};
-
-/// Store describes a single storage or uniform buffer write
-struct Store {
-  const ast::AssignmentStatement* assignment;  // The AST assignment statement
-  BufferAccess target;                         // The target for the write
-};
-
-}  // namespace
-
-/// State holds the current transform state
-struct DecomposeMemoryAccess::State {
-  /// The clone context
-  CloneContext& ctx;
-  /// Alias to `*ctx.dst`
-  ProgramBuilder& b;
-  /// Map of AST expression to storage or uniform buffer access
-  /// This map has entries added when encountered, and removed when outer
-  /// expressions chain the access.
-  /// Subset of #expression_order, as expressions are not removed from
-  /// #expression_order.
-  std::unordered_map<const ast::Expression*, BufferAccess> accesses;
-  /// The visited order of AST expressions (superset of #accesses)
-  std::vector<const ast::Expression*> expression_order;
-  /// [buffer-type, element-type] -> load function name
-  std::unordered_map<LoadStoreKey, Symbol, LoadStoreKey::Hasher> load_funcs;
-  /// [buffer-type, element-type] -> store function name
-  std::unordered_map<LoadStoreKey, Symbol, LoadStoreKey::Hasher> store_funcs;
-  /// [buffer-type, element-type, atomic-op] -> load function name
-  std::unordered_map<AtomicKey, Symbol, AtomicKey::Hasher> atomic_funcs;
-  /// List of storage or uniform buffer writes
-  std::vector<Store> stores;
-  /// Allocations for offsets
-  BlockAllocator<Offset> offsets_;
-
-  /// Constructor
-  /// @param context the CloneContext
-  explicit State(CloneContext& context) : ctx(context), b(*ctx.dst) {}
-
-  /// @param offset the offset value to wrap in an Offset
-  /// @returns an Offset for the given literal value
-  const Offset* ToOffset(uint32_t offset) {
-    return offsets_.Create<OffsetLiteral>(offset);
-  }
-
-  /// @param expr the expression to convert to an Offset
-  /// @returns an Offset for the given ast::Expression
-  const Offset* ToOffset(const ast::Expression* expr) {
-    if (auto* u32 = expr->As<ast::UintLiteralExpression>()) {
-      return offsets_.Create<OffsetLiteral>(u32->value);
-    } else if (auto* i32 = expr->As<ast::SintLiteralExpression>()) {
-      if (i32->value > 0) {
-        return offsets_.Create<OffsetLiteral>(i32->value);
-      }
-    }
-    return offsets_.Create<OffsetExpr>(expr);
-  }
-
-  /// @param offset the Offset that is returned
-  /// @returns the given offset (pass-through)
-  const Offset* ToOffset(const Offset* offset) { return offset; }
-
-  /// @param lhs_ the left-hand side of the add expression
-  /// @param rhs_ the right-hand side of the add expression
-  /// @return an Offset that is a sum of lhs and rhs, performing basic constant
-  /// folding if possible
-  template <typename LHS, typename RHS>
-  const Offset* Add(LHS&& lhs_, RHS&& rhs_) {
-    auto* lhs = ToOffset(std::forward<LHS>(lhs_));
-    auto* rhs = ToOffset(std::forward<RHS>(rhs_));
-    auto* lhs_lit = tint::As<OffsetLiteral>(lhs);
-    auto* rhs_lit = tint::As<OffsetLiteral>(rhs);
-    if (lhs_lit && lhs_lit->literal == 0) {
-      return rhs;
-    }
-    if (rhs_lit && rhs_lit->literal == 0) {
-      return lhs;
-    }
-    if (lhs_lit && rhs_lit) {
-      if (static_cast<uint64_t>(lhs_lit->literal) +
-              static_cast<uint64_t>(rhs_lit->literal) <=
-          0xffffffff) {
-        return offsets_.Create<OffsetLiteral>(lhs_lit->literal +
-                                              rhs_lit->literal);
-      }
-    }
-    auto* out = offsets_.Create<OffsetBinOp>();
-    out->op = ast::BinaryOp::kAdd;
-    out->lhs = lhs;
-    out->rhs = rhs;
-    return out;
-  }
-
-  /// @param lhs_ the left-hand side of the multiply expression
-  /// @param rhs_ the right-hand side of the multiply expression
-  /// @return an Offset that is the multiplication of lhs and rhs, performing
-  /// basic constant folding if possible
-  template <typename LHS, typename RHS>
-  const Offset* Mul(LHS&& lhs_, RHS&& rhs_) {
-    auto* lhs = ToOffset(std::forward<LHS>(lhs_));
-    auto* rhs = ToOffset(std::forward<RHS>(rhs_));
-    auto* lhs_lit = tint::As<OffsetLiteral>(lhs);
-    auto* rhs_lit = tint::As<OffsetLiteral>(rhs);
-    if (lhs_lit && lhs_lit->literal == 0) {
-      return offsets_.Create<OffsetLiteral>(0);
-    }
-    if (rhs_lit && rhs_lit->literal == 0) {
-      return offsets_.Create<OffsetLiteral>(0);
-    }
-    if (lhs_lit && lhs_lit->literal == 1) {
-      return rhs;
-    }
-    if (rhs_lit && rhs_lit->literal == 1) {
-      return lhs;
-    }
-    if (lhs_lit && rhs_lit) {
-      return offsets_.Create<OffsetLiteral>(lhs_lit->literal *
-                                            rhs_lit->literal);
-    }
-    auto* out = offsets_.Create<OffsetBinOp>();
-    out->op = ast::BinaryOp::kMultiply;
-    out->lhs = lhs;
-    out->rhs = rhs;
-    return out;
-  }
-
-  /// AddAccess() adds the `expr -> access` map item to #accesses, and `expr`
-  /// to #expression_order.
-  /// @param expr the expression that performs the access
-  /// @param access the access
-  void AddAccess(const ast::Expression* expr, const BufferAccess& access) {
-    TINT_ASSERT(Transform, access.type);
-    accesses.emplace(expr, access);
-    expression_order.emplace_back(expr);
-  }
-
-  /// TakeAccess() removes the `node` item from #accesses (if it exists),
-  /// returning the BufferAccess. If #accesses does not hold an item for
-  /// `node`, an invalid BufferAccess is returned.
-  /// @param node the expression that performed an access
-  /// @return the BufferAccess for the given expression
-  BufferAccess TakeAccess(const ast::Expression* node) {
-    auto lhs_it = accesses.find(node);
-    if (lhs_it == accesses.end()) {
-      return {};
-    }
-    auto access = lhs_it->second;
-    accesses.erase(node);
-    return access;
-  }
-
-  /// LoadFunc() returns a symbol to an intrinsic function that loads an element
-  /// of type `el_ty` from a storage or uniform buffer of type `buf_ty`.
-  /// The emitted function has the signature:
-  ///   `fn load(buf : buf_ty, offset : u32) -> el_ty`
-  /// @param buf_ty the storage or uniform buffer type
-  /// @param el_ty the storage or uniform buffer element type
-  /// @param var_user the variable user
-  /// @return the name of the function that performs the load
-  Symbol LoadFunc(const sem::Type* buf_ty,
-                  const sem::Type* el_ty,
-                  const sem::VariableUser* var_user) {
-    auto storage_class = var_user->Variable()->StorageClass();
-    return utils::GetOrCreate(
-        load_funcs, LoadStoreKey{storage_class, buf_ty, el_ty}, [&] {
-          auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
-          auto* disable_validation = b.Disable(
-              ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
-
-          ast::VariableList params = {
-              // Note: The buffer parameter requires the StorageClass in
-              // order for HLSL to emit this as a ByteAddressBuffer or cbuffer
-              // array.
-              b.create<ast::Variable>(b.Sym("buffer"), storage_class,
-                                      var_user->Variable()->Access(),
-                                      buf_ast_ty, true, false, nullptr,
-                                      ast::AttributeList{disable_validation}),
-              b.Param("offset", b.ty.u32()),
-          };
-
-          auto name = b.Sym();
-
-          if (auto* intrinsic =
-                  IntrinsicLoadFor(ctx.dst, storage_class, el_ty)) {
-            auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
-            auto* func = b.create<ast::Function>(
-                name, params, el_ast_ty, nullptr,
-                ast::AttributeList{
-                    intrinsic,
-                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                },
-                ast::AttributeList{});
-            b.AST().AddFunction(func);
-          } else if (auto* arr_ty = el_ty->As<sem::Array>()) {
-            // fn load_func(buf : buf_ty, offset : u32) -> array<T, N> {
-            //   var arr : array<T, N>;
-            //   for (var i = 0u; i < array_count; i = i + 1) {
-            //     arr[i] = el_load_func(buf, offset + i * array_stride)
-            //   }
-            //   return arr;
-            // }
-            auto load =
-                LoadFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
-            auto* arr =
-                b.Var(b.Symbols().New("arr"), CreateASTTypeFor(ctx, arr_ty));
-            auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0u));
-            auto* for_init = b.Decl(i);
-            auto* for_cond = b.create<ast::BinaryExpression>(
-                ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(arr_ty->Count()));
-            auto* for_cont = b.Assign(i, b.Add(i, 1u));
-            auto* arr_el = b.IndexAccessor(arr, i);
-            auto* el_offset =
-                b.Add(b.Expr("offset"), b.Mul(i, arr_ty->Stride()));
-            auto* el_val = b.Call(load, "buffer", el_offset);
-            auto* for_loop = b.For(for_init, for_cond, for_cont,
-                                   b.Block(b.Assign(arr_el, el_val)));
-
-            b.Func(name, params, CreateASTTypeFor(ctx, arr_ty),
-                   {
-                       b.Decl(arr),
-                       for_loop,
-                       b.Return(arr),
-                   });
-          } else {
-            ast::ExpressionList values;
-            if (auto* mat_ty = el_ty->As<sem::Matrix>()) {
-              auto* vec_ty = mat_ty->ColumnType();
-              Symbol load = LoadFunc(buf_ty, vec_ty, var_user);
-              for (uint32_t i = 0; i < mat_ty->columns(); i++) {
-                auto* offset = b.Add("offset", i * mat_ty->ColumnStride());
-                values.emplace_back(b.Call(load, "buffer", offset));
-              }
-            } else if (auto* str = el_ty->As<sem::Struct>()) {
-              for (auto* member : str->Members()) {
-                auto* offset = b.Add("offset", member->Offset());
-                Symbol load =
-                    LoadFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
-                values.emplace_back(b.Call(load, "buffer", offset));
-              }
-            }
-            b.Func(
-                name, params, CreateASTTypeFor(ctx, el_ty),
-                {
-                    b.Return(b.Construct(CreateASTTypeFor(ctx, el_ty), values)),
-                });
-          }
-          return name;
-        });
-  }
-
-  /// StoreFunc() returns a symbol to an intrinsic function that stores an
-  /// element of type `el_ty` to a storage buffer of type `buf_ty`.
-  /// The function has the signature:
-  ///   `fn store(buf : buf_ty, offset : u32, value : el_ty)`
-  /// @param buf_ty the storage buffer type
-  /// @param el_ty the storage buffer element type
-  /// @param var_user the variable user
-  /// @return the name of the function that performs the store
-  Symbol StoreFunc(const sem::Type* buf_ty,
-                   const sem::Type* el_ty,
-                   const sem::VariableUser* var_user) {
-    auto storage_class = var_user->Variable()->StorageClass();
-    return utils::GetOrCreate(
-        store_funcs, LoadStoreKey{storage_class, buf_ty, el_ty}, [&] {
-          auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
-          auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
-          auto* disable_validation = b.Disable(
-              ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
-          ast::VariableList params{
-              // Note: The buffer parameter requires the StorageClass in
-              // order for HLSL to emit this as a ByteAddressBuffer.
-
-              b.create<ast::Variable>(b.Sym("buffer"), storage_class,
-                                      var_user->Variable()->Access(),
-                                      buf_ast_ty, true, false, nullptr,
-                                      ast::AttributeList{disable_validation}),
-              b.Param("offset", b.ty.u32()),
-              b.Param("value", el_ast_ty),
-          };
-
-          auto name = b.Sym();
-
-          if (auto* intrinsic =
-                  IntrinsicStoreFor(ctx.dst, storage_class, el_ty)) {
-            auto* func = b.create<ast::Function>(
-                name, params, b.ty.void_(), nullptr,
-                ast::AttributeList{
-                    intrinsic,
-                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                },
-                ast::AttributeList{});
-            b.AST().AddFunction(func);
-          } else {
-            ast::StatementList body;
-            if (auto* arr_ty = el_ty->As<sem::Array>()) {
-              // fn store_func(buf : buf_ty, offset : u32, value : el_ty) {
-              //   var array = value; // No dynamic indexing on constant arrays
-              //   for (var i = 0u; i < array_count; i = i + 1) {
-              //     arr[i] = el_store_func(buf, offset + i * array_stride,
-              //                            value[i])
-              //   }
-              //   return arr;
-              // }
-              auto* array =
-                  b.Var(b.Symbols().New("array"), nullptr, b.Expr("value"));
-              auto store =
-                  StoreFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
-              auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0u));
-              auto* for_init = b.Decl(i);
-              auto* for_cond = b.create<ast::BinaryExpression>(
-                  ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(arr_ty->Count()));
-              auto* for_cont = b.Assign(i, b.Add(i, 1u));
-              auto* arr_el = b.IndexAccessor(array, i);
-              auto* el_offset =
-                  b.Add(b.Expr("offset"), b.Mul(i, arr_ty->Stride()));
-              auto* store_stmt =
-                  b.CallStmt(b.Call(store, "buffer", el_offset, arr_el));
-              auto* for_loop =
-                  b.For(for_init, for_cond, for_cont, b.Block(store_stmt));
-
-              body = {b.Decl(array), for_loop};
-            } else if (auto* mat_ty = el_ty->As<sem::Matrix>()) {
-              auto* vec_ty = mat_ty->ColumnType();
-              Symbol store = StoreFunc(buf_ty, vec_ty, var_user);
-              for (uint32_t i = 0; i < mat_ty->columns(); i++) {
-                auto* offset = b.Add("offset", i * mat_ty->ColumnStride());
-                auto* access = b.IndexAccessor("value", i);
-                auto* call = b.Call(store, "buffer", offset, access);
-                body.emplace_back(b.CallStmt(call));
-              }
-            } else if (auto* str = el_ty->As<sem::Struct>()) {
-              for (auto* member : str->Members()) {
-                auto* offset = b.Add("offset", member->Offset());
-                auto* access = b.MemberAccessor(
-                    "value", ctx.Clone(member->Declaration()->symbol));
-                Symbol store =
-                    StoreFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
-                auto* call = b.Call(store, "buffer", offset, access);
-                body.emplace_back(b.CallStmt(call));
-              }
-            }
-            b.Func(name, params, b.ty.void_(), body);
-          }
-
-          return name;
-        });
-  }
-
-  /// AtomicFunc() returns a symbol to an intrinsic function that performs an
-  /// atomic operation from a storage buffer of type `buf_ty`. The function has
-  /// the signature:
-  // `fn atomic_op(buf : buf_ty, offset : u32, ...) -> T`
-  /// @param buf_ty the storage buffer type
-  /// @param el_ty the storage buffer element type
-  /// @param intrinsic the atomic intrinsic
-  /// @param var_user the variable user
-  /// @return the name of the function that performs the load
-  Symbol AtomicFunc(const sem::Type* buf_ty,
-                    const sem::Type* el_ty,
-                    const sem::Builtin* intrinsic,
-                    const sem::VariableUser* var_user) {
-    auto op = intrinsic->Type();
-    return utils::GetOrCreate(atomic_funcs, AtomicKey{buf_ty, el_ty, op}, [&] {
-      auto* buf_ast_ty = CreateASTTypeFor(ctx, buf_ty);
-      auto* disable_validation = b.Disable(
-          ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
-      // The first parameter to all WGSL atomics is the expression to the
-      // atomic. This is replaced with two parameters: the buffer and offset.
-
-      ast::VariableList params = {
-          // Note: The buffer parameter requires the kStorage StorageClass in
-          // order for HLSL to emit this as a ByteAddressBuffer.
-          b.create<ast::Variable>(b.Sym("buffer"), ast::StorageClass::kStorage,
-                                  var_user->Variable()->Access(), buf_ast_ty,
-                                  true, false, nullptr,
-                                  ast::AttributeList{disable_validation}),
-          b.Param("offset", b.ty.u32()),
-      };
-
-      // Other parameters are copied as-is:
-      for (size_t i = 1; i < intrinsic->Parameters().size(); i++) {
-        auto* param = intrinsic->Parameters()[i];
-        auto* ty = CreateASTTypeFor(ctx, param->Type());
-        params.emplace_back(b.Param("param_" + std::to_string(i), ty));
-      }
-
-      auto* atomic = IntrinsicAtomicFor(ctx.dst, op, el_ty);
-      if (atomic == nullptr) {
-        TINT_ICE(Transform, b.Diagnostics())
-            << "IntrinsicAtomicFor() returned nullptr for op " << op
-            << " and type " << el_ty->type_name();
-      }
-
-      auto* ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
-      auto* func = b.create<ast::Function>(
-          b.Sym(), params, ret_ty, nullptr,
-          ast::AttributeList{
-              atomic,
-              b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-          },
-          ast::AttributeList{});
-
-      b.AST().AddFunction(func);
-      return func->symbol;
-    });
-  }
-};
-
-DecomposeMemoryAccess::Intrinsic::Intrinsic(ProgramID pid,
-                                            Op o,
-                                            ast::StorageClass sc,
-                                            DataType ty)
-    : Base(pid), op(o), storage_class(sc), type(ty) {}
-DecomposeMemoryAccess::Intrinsic::~Intrinsic() = default;
-std::string DecomposeMemoryAccess::Intrinsic::InternalName() const {
-  std::stringstream ss;
-  switch (op) {
-    case Op::kLoad:
-      ss << "intrinsic_load_";
-      break;
-    case Op::kStore:
-      ss << "intrinsic_store_";
-      break;
-    case Op::kAtomicLoad:
-      ss << "intrinsic_atomic_load_";
-      break;
-    case Op::kAtomicStore:
-      ss << "intrinsic_atomic_store_";
-      break;
-    case Op::kAtomicAdd:
-      ss << "intrinsic_atomic_add_";
-      break;
-    case Op::kAtomicSub:
-      ss << "intrinsic_atomic_sub_";
-      break;
-    case Op::kAtomicMax:
-      ss << "intrinsic_atomic_max_";
-      break;
-    case Op::kAtomicMin:
-      ss << "intrinsic_atomic_min_";
-      break;
-    case Op::kAtomicAnd:
-      ss << "intrinsic_atomic_and_";
-      break;
-    case Op::kAtomicOr:
-      ss << "intrinsic_atomic_or_";
-      break;
-    case Op::kAtomicXor:
-      ss << "intrinsic_atomic_xor_";
-      break;
-    case Op::kAtomicExchange:
-      ss << "intrinsic_atomic_exchange_";
-      break;
-    case Op::kAtomicCompareExchangeWeak:
-      ss << "intrinsic_atomic_compare_exchange_weak_";
-      break;
-  }
-  ss << storage_class << "_";
-  switch (type) {
-    case DataType::kU32:
-      ss << "u32";
-      break;
-    case DataType::kF32:
-      ss << "f32";
-      break;
-    case DataType::kI32:
-      ss << "i32";
-      break;
-    case DataType::kVec2U32:
-      ss << "vec2_u32";
-      break;
-    case DataType::kVec2F32:
-      ss << "vec2_f32";
-      break;
-    case DataType::kVec2I32:
-      ss << "vec2_i32";
-      break;
-    case DataType::kVec3U32:
-      ss << "vec3_u32";
-      break;
-    case DataType::kVec3F32:
-      ss << "vec3_f32";
-      break;
-    case DataType::kVec3I32:
-      ss << "vec3_i32";
-      break;
-    case DataType::kVec4U32:
-      ss << "vec4_u32";
-      break;
-    case DataType::kVec4F32:
-      ss << "vec4_f32";
-      break;
-    case DataType::kVec4I32:
-      ss << "vec4_i32";
-      break;
-  }
-  return ss.str();
-}
-
-const DecomposeMemoryAccess::Intrinsic* DecomposeMemoryAccess::Intrinsic::Clone(
-    CloneContext* ctx) const {
-  return ctx->dst->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
-      ctx->dst->ID(), op, storage_class, type);
-}
-
-DecomposeMemoryAccess::DecomposeMemoryAccess() = default;
-DecomposeMemoryAccess::~DecomposeMemoryAccess() = default;
-
-bool DecomposeMemoryAccess::ShouldRun(const Program* program,
-                                      const DataMap&) const {
-  for (auto* decl : program->AST().GlobalDeclarations()) {
-    if (auto* var = program->Sem().Get<sem::Variable>(decl)) {
-      if (var->StorageClass() == ast::StorageClass::kStorage ||
-          var->StorageClass() == ast::StorageClass::kUniform) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-void DecomposeMemoryAccess::Run(CloneContext& ctx,
-                                const DataMap&,
-                                DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  State state(ctx);
-
-  // Scan the AST nodes for storage and uniform buffer accesses. Complex
-  // expression chains (e.g. `storage_buffer.foo.bar[20].x`) are handled by
-  // maintaining an offset chain via the `state.TakeAccess()`,
-  // `state.AddAccess()` methods.
-  //
-  // Inner-most expression nodes are guaranteed to be visited first because AST
-  // nodes are fully immutable and require their children to be constructed
-  // first so their pointer can be passed to the parent's constructor.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* ident = node->As<ast::IdentifierExpression>()) {
-      // X
-      if (auto* var = sem.Get<sem::VariableUser>(ident)) {
-        if (var->Variable()->StorageClass() == ast::StorageClass::kStorage ||
-            var->Variable()->StorageClass() == ast::StorageClass::kUniform) {
-          // Variable to a storage or uniform buffer
-          state.AddAccess(ident, {
-                                     var,
-                                     state.ToOffset(0u),
-                                     var->Type()->UnwrapRef(),
-                                 });
-        }
-      }
-      continue;
-    }
-
-    if (auto* accessor = node->As<ast::MemberAccessorExpression>()) {
-      // X.Y
-      auto* accessor_sem = sem.Get(accessor);
-      if (auto* swizzle = accessor_sem->As<sem::Swizzle>()) {
-        if (swizzle->Indices().size() == 1) {
-          if (auto access = state.TakeAccess(accessor->structure)) {
-            auto* vec_ty = access.type->As<sem::Vector>();
-            auto* offset =
-                state.Mul(vec_ty->type()->Size(), swizzle->Indices()[0]);
-            state.AddAccess(accessor, {
-                                          access.var,
-                                          state.Add(access.offset, offset),
-                                          vec_ty->type()->UnwrapRef(),
-                                      });
-          }
-        }
-      } else {
-        if (auto access = state.TakeAccess(accessor->structure)) {
-          auto* str_ty = access.type->As<sem::Struct>();
-          auto* member = str_ty->FindMember(accessor->member->symbol);
-          auto offset = member->Offset();
-          state.AddAccess(accessor, {
-                                        access.var,
-                                        state.Add(access.offset, offset),
-                                        member->Type()->UnwrapRef(),
-                                    });
-        }
-      }
-      continue;
-    }
-
-    if (auto* accessor = node->As<ast::IndexAccessorExpression>()) {
-      if (auto access = state.TakeAccess(accessor->object)) {
-        // X[Y]
-        if (auto* arr = access.type->As<sem::Array>()) {
-          auto* offset = state.Mul(arr->Stride(), accessor->index);
-          state.AddAccess(accessor, {
-                                        access.var,
-                                        state.Add(access.offset, offset),
-                                        arr->ElemType()->UnwrapRef(),
-                                    });
-          continue;
-        }
-        if (auto* vec_ty = access.type->As<sem::Vector>()) {
-          auto* offset = state.Mul(vec_ty->type()->Size(), accessor->index);
-          state.AddAccess(accessor, {
-                                        access.var,
-                                        state.Add(access.offset, offset),
-                                        vec_ty->type()->UnwrapRef(),
-                                    });
-          continue;
-        }
-        if (auto* mat_ty = access.type->As<sem::Matrix>()) {
-          auto* offset = state.Mul(mat_ty->ColumnStride(), accessor->index);
-          state.AddAccess(accessor, {
-                                        access.var,
-                                        state.Add(access.offset, offset),
-                                        mat_ty->ColumnType(),
-                                    });
-          continue;
-        }
-      }
-    }
-
-    if (auto* op = node->As<ast::UnaryOpExpression>()) {
-      if (op->op == ast::UnaryOp::kAddressOf) {
-        // &X
-        if (auto access = state.TakeAccess(op->expr)) {
-          // HLSL does not support pointers, so just take the access from the
-          // reference and place it on the pointer.
-          state.AddAccess(op, access);
-          continue;
-        }
-      }
-    }
-
-    if (auto* assign = node->As<ast::AssignmentStatement>()) {
-      // X = Y
-      // Move the LHS access to a store.
-      if (auto lhs = state.TakeAccess(assign->lhs)) {
-        state.stores.emplace_back(Store{assign, lhs});
-      }
-    }
-
-    if (auto* call_expr = node->As<ast::CallExpression>()) {
-      auto* call = sem.Get(call_expr);
-      if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-        if (builtin->Type() == sem::BuiltinType::kArrayLength) {
-          // arrayLength(X)
-          // Don't convert X into a load, this builtin actually requires the
-          // real pointer.
-          state.TakeAccess(call_expr->args[0]);
-          continue;
-        }
-        if (builtin->IsAtomic()) {
-          if (auto access = state.TakeAccess(call_expr->args[0])) {
-            // atomic___(X)
-            ctx.Replace(call_expr, [=, &ctx, &state] {
-              auto* buf = access.var->Declaration();
-              auto* offset = access.offset->Build(ctx);
-              auto* buf_ty = access.var->Type()->UnwrapRef();
-              auto* el_ty = access.type->UnwrapRef()->As<sem::Atomic>()->Type();
-              Symbol func = state.AtomicFunc(
-                  buf_ty, el_ty, builtin, access.var->As<sem::VariableUser>());
-
-              ast::ExpressionList args{ctx.Clone(buf), offset};
-              for (size_t i = 1; i < call_expr->args.size(); i++) {
-                auto* arg = call_expr->args[i];
-                args.emplace_back(ctx.Clone(arg));
-              }
-              return ctx.dst->Call(func, args);
-            });
-          }
-        }
-      }
-    }
-  }
-
-  // All remaining accesses are loads, transform these into calls to the
-  // corresponding load function
-  for (auto* expr : state.expression_order) {
-    auto access_it = state.accesses.find(expr);
-    if (access_it == state.accesses.end()) {
-      continue;
-    }
-    BufferAccess access = access_it->second;
-    ctx.Replace(expr, [=, &ctx, &state] {
-      auto* buf = access.var->Declaration();
-      auto* offset = access.offset->Build(ctx);
-      auto* buf_ty = access.var->Type()->UnwrapRef();
-      auto* el_ty = access.type->UnwrapRef();
-      Symbol func =
-          state.LoadFunc(buf_ty, el_ty, access.var->As<sem::VariableUser>());
-      return ctx.dst->Call(func, ctx.CloneWithoutTransform(buf), offset);
-    });
-  }
-
-  // And replace all storage and uniform buffer assignments with stores
-  for (auto store : state.stores) {
-    ctx.Replace(store.assignment, [=, &ctx, &state] {
-      auto* buf = store.target.var->Declaration();
-      auto* offset = store.target.offset->Build(ctx);
-      auto* buf_ty = store.target.var->Type()->UnwrapRef();
-      auto* el_ty = store.target.type->UnwrapRef();
-      auto* value = store.assignment->rhs;
-      Symbol func = state.StoreFunc(buf_ty, el_ty,
-                                    store.target.var->As<sem::VariableUser>());
-      auto* call = ctx.dst->Call(func, ctx.CloneWithoutTransform(buf), offset,
-                                 ctx.Clone(value));
-      return ctx.dst->CallStmt(call);
-    });
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Offset);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::OffsetLiteral);
diff --git a/src/transform/decompose_memory_access.h b/src/transform/decompose_memory_access.h
deleted file mode 100644
index ecf4be5..0000000
--- a/src/transform/decompose_memory_access.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
-#define SRC_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
-
-#include <string>
-
-#include "src/ast/internal_attribute.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace transform {
-
-/// DecomposeMemoryAccess is a transform used to replace storage and uniform
-/// buffer accesses with a combination of load, store or atomic functions on
-/// primitive types.
-class DecomposeMemoryAccess
-    : public Castable<DecomposeMemoryAccess, Transform> {
- public:
-  /// Intrinsic is an InternalAttribute that's used to decorate a stub function
-  /// so that the HLSL transforms this into calls to
-  /// `[RW]ByteAddressBuffer.Load[N]()` or `[RW]ByteAddressBuffer.Store[N]()`,
-  /// with a possible cast.
-  class Intrinsic : public Castable<Intrinsic, ast::InternalAttribute> {
-   public:
-    /// Intrinsic op
-    enum class Op {
-      kLoad,
-      kStore,
-      kAtomicLoad,
-      kAtomicStore,
-      kAtomicAdd,
-      kAtomicSub,
-      kAtomicMax,
-      kAtomicMin,
-      kAtomicAnd,
-      kAtomicOr,
-      kAtomicXor,
-      kAtomicExchange,
-      kAtomicCompareExchangeWeak,
-    };
-
-    /// Intrinsic data type
-    enum class DataType {
-      kU32,
-      kF32,
-      kI32,
-      kVec2U32,
-      kVec2F32,
-      kVec2I32,
-      kVec3U32,
-      kVec3F32,
-      kVec3I32,
-      kVec4U32,
-      kVec4F32,
-      kVec4I32,
-    };
-
-    /// Constructor
-    /// @param program_id the identifier of the program that owns this node
-    /// @param o the op of the intrinsic
-    /// @param sc the storage class of the buffer
-    /// @param ty the data type of the intrinsic
-    Intrinsic(ProgramID program_id, Op o, ast::StorageClass sc, DataType ty);
-    /// Destructor
-    ~Intrinsic() override;
-
-    /// @return a short description of the internal attribute which will be
-    /// displayed as `@internal(<name>)`
-    std::string InternalName() const override;
-
-    /// Performs a deep clone of this object using the CloneContext `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned object
-    const Intrinsic* Clone(CloneContext* ctx) const override;
-
-    /// The op of the intrinsic
-    const Op op;
-
-    /// The storage class of the buffer this intrinsic operates on
-    ast::StorageClass const storage_class;
-
-    /// The type of the intrinsic
-    const DataType type;
-  };
-
-  /// Constructor
-  DecomposeMemoryAccess();
-  /// Destructor
-  ~DecomposeMemoryAccess() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
-  struct State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_DECOMPOSE_MEMORY_ACCESS_H_
diff --git a/src/transform/decompose_memory_access_test.cc b/src/transform/decompose_memory_access_test.cc
deleted file mode 100644
index 2f8ed59..0000000
--- a/src/transform/decompose_memory_access_test.cc
+++ /dev/null
@@ -1,2800 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/decompose_memory_access.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using DecomposeMemoryAccessTest = TransformTest;
-
-TEST_F(DecomposeMemoryAccessTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<DecomposeMemoryAccess>(src));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ShouldRunStorageBuffer) {
-  auto* src = R"(
-struct Buffer {
-  i : i32;
-};
-[[group(0), binding(0)]] var<storage, read_write> sb : Buffer;
-)";
-
-  EXPECT_TRUE(ShouldRun<DecomposeMemoryAccess>(src));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ShouldRunUniformBuffer) {
-  auto* src = R"(
-struct Buffer {
-  i : i32;
-};
-[[group(0), binding(0)]] var<uniform> ub : Buffer;
-)";
-
-  EXPECT_TRUE(ShouldRun<DecomposeMemoryAccess>(src));
-}
-
-TEST_F(DecomposeMemoryAccessTest, SB_BasicLoad) {
-  auto* src = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = sb.a;
-  var b : u32 = sb.b;
-  var c : f32 = sb.c;
-  var d : vec2<i32> = sb.d;
-  var e : vec2<u32> = sb.e;
-  var f : vec2<f32> = sb.f;
-  var g : vec3<i32> = sb.g;
-  var h : vec3<u32> = sb.h;
-  var i : vec3<f32> = sb.i;
-  var j : vec4<i32> = sb.j;
-  var k : vec4<u32> = sb.k;
-  var l : vec4<f32> = sb.l;
-  var m : mat2x2<f32> = sb.m;
-  var n : mat2x3<f32> = sb.n;
-  var o : mat2x4<f32> = sb.o;
-  var p : mat3x2<f32> = sb.p;
-  var q : mat3x3<f32> = sb.q;
-  var r : mat3x4<f32> = sb.r;
-  var s : mat4x2<f32> = sb.s;
-  var t : mat4x3<f32> = sb.t;
-  var u : mat4x4<f32> = sb.u;
-  var v : array<vec3<f32>, 2> = sb.v;
-}
-)";
-
-  auto* expect = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = tint_symbol(sb, 0u);
-  var b : u32 = tint_symbol_1(sb, 4u);
-  var c : f32 = tint_symbol_2(sb, 8u);
-  var d : vec2<i32> = tint_symbol_3(sb, 16u);
-  var e : vec2<u32> = tint_symbol_4(sb, 24u);
-  var f : vec2<f32> = tint_symbol_5(sb, 32u);
-  var g : vec3<i32> = tint_symbol_6(sb, 48u);
-  var h : vec3<u32> = tint_symbol_7(sb, 64u);
-  var i : vec3<f32> = tint_symbol_8(sb, 80u);
-  var j : vec4<i32> = tint_symbol_9(sb, 96u);
-  var k : vec4<u32> = tint_symbol_10(sb, 112u);
-  var l : vec4<f32> = tint_symbol_11(sb, 128u);
-  var m : mat2x2<f32> = tint_symbol_12(sb, 144u);
-  var n : mat2x3<f32> = tint_symbol_13(sb, 160u);
-  var o : mat2x4<f32> = tint_symbol_14(sb, 192u);
-  var p : mat3x2<f32> = tint_symbol_15(sb, 224u);
-  var q : mat3x3<f32> = tint_symbol_16(sb, 256u);
-  var r : mat3x4<f32> = tint_symbol_17(sb, 304u);
-  var s : mat4x2<f32> = tint_symbol_18(sb, 352u);
-  var t : mat4x3<f32> = tint_symbol_19(sb, 384u);
-  var u : mat4x4<f32> = tint_symbol_20(sb, 448u);
-  var v : array<vec3<f32>, 2> = tint_symbol_21(sb, 512u);
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, SB_BasicLoad_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = sb.a;
-  var b : u32 = sb.b;
-  var c : f32 = sb.c;
-  var d : vec2<i32> = sb.d;
-  var e : vec2<u32> = sb.e;
-  var f : vec2<f32> = sb.f;
-  var g : vec3<i32> = sb.g;
-  var h : vec3<u32> = sb.h;
-  var i : vec3<f32> = sb.i;
-  var j : vec4<i32> = sb.j;
-  var k : vec4<u32> = sb.k;
-  var l : vec4<f32> = sb.l;
-  var m : mat2x2<f32> = sb.m;
-  var n : mat2x3<f32> = sb.n;
-  var o : mat2x4<f32> = sb.o;
-  var p : mat3x2<f32> = sb.p;
-  var q : mat3x3<f32> = sb.q;
-  var r : mat3x4<f32> = sb.r;
-  var s : mat4x2<f32> = sb.s;
-  var t : mat4x3<f32> = sb.t;
-  var u : mat4x4<f32> = sb.u;
-  var v : array<vec3<f32>, 2> = sb.v;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = tint_symbol(sb, 0u);
-  var b : u32 = tint_symbol_1(sb, 4u);
-  var c : f32 = tint_symbol_2(sb, 8u);
-  var d : vec2<i32> = tint_symbol_3(sb, 16u);
-  var e : vec2<u32> = tint_symbol_4(sb, 24u);
-  var f : vec2<f32> = tint_symbol_5(sb, 32u);
-  var g : vec3<i32> = tint_symbol_6(sb, 48u);
-  var h : vec3<u32> = tint_symbol_7(sb, 64u);
-  var i : vec3<f32> = tint_symbol_8(sb, 80u);
-  var j : vec4<i32> = tint_symbol_9(sb, 96u);
-  var k : vec4<u32> = tint_symbol_10(sb, 112u);
-  var l : vec4<f32> = tint_symbol_11(sb, 128u);
-  var m : mat2x2<f32> = tint_symbol_12(sb, 144u);
-  var n : mat2x3<f32> = tint_symbol_13(sb, 160u);
-  var o : mat2x4<f32> = tint_symbol_14(sb, 192u);
-  var p : mat3x2<f32> = tint_symbol_15(sb, 224u);
-  var q : mat3x3<f32> = tint_symbol_16(sb, 256u);
-  var r : mat3x4<f32> = tint_symbol_17(sb, 304u);
-  var s : mat4x2<f32> = tint_symbol_18(sb, 352u);
-  var t : mat4x3<f32> = tint_symbol_19(sb, 384u);
-  var u : mat4x4<f32> = tint_symbol_20(sb, 448u);
-  var v : array<vec3<f32>, 2> = tint_symbol_21(sb, 512u);
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, UB_BasicLoad) {
-  auto* src = R"(
-struct UB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-
-@group(0) @binding(0) var<uniform> ub : UB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = ub.a;
-  var b : u32 = ub.b;
-  var c : f32 = ub.c;
-  var d : vec2<i32> = ub.d;
-  var e : vec2<u32> = ub.e;
-  var f : vec2<f32> = ub.f;
-  var g : vec3<i32> = ub.g;
-  var h : vec3<u32> = ub.h;
-  var i : vec3<f32> = ub.i;
-  var j : vec4<i32> = ub.j;
-  var k : vec4<u32> = ub.k;
-  var l : vec4<f32> = ub.l;
-  var m : mat2x2<f32> = ub.m;
-  var n : mat2x3<f32> = ub.n;
-  var o : mat2x4<f32> = ub.o;
-  var p : mat3x2<f32> = ub.p;
-  var q : mat3x3<f32> = ub.q;
-  var r : mat3x4<f32> = ub.r;
-  var s : mat4x2<f32> = ub.s;
-  var t : mat4x3<f32> = ub.t;
-  var u : mat4x4<f32> = ub.u;
-  var v : array<vec3<f32>, 2> = ub.v;
-}
-)";
-
-  auto* expect = R"(
-struct UB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-
-@group(0) @binding(0) var<uniform> ub : UB;
-
-@internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> i32
-
-@internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> u32
-
-@internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> f32
-
-@internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = tint_symbol(ub, 0u);
-  var b : u32 = tint_symbol_1(ub, 4u);
-  var c : f32 = tint_symbol_2(ub, 8u);
-  var d : vec2<i32> = tint_symbol_3(ub, 16u);
-  var e : vec2<u32> = tint_symbol_4(ub, 24u);
-  var f : vec2<f32> = tint_symbol_5(ub, 32u);
-  var g : vec3<i32> = tint_symbol_6(ub, 48u);
-  var h : vec3<u32> = tint_symbol_7(ub, 64u);
-  var i : vec3<f32> = tint_symbol_8(ub, 80u);
-  var j : vec4<i32> = tint_symbol_9(ub, 96u);
-  var k : vec4<u32> = tint_symbol_10(ub, 112u);
-  var l : vec4<f32> = tint_symbol_11(ub, 128u);
-  var m : mat2x2<f32> = tint_symbol_12(ub, 144u);
-  var n : mat2x3<f32> = tint_symbol_13(ub, 160u);
-  var o : mat2x4<f32> = tint_symbol_14(ub, 192u);
-  var p : mat3x2<f32> = tint_symbol_15(ub, 224u);
-  var q : mat3x3<f32> = tint_symbol_16(ub, 256u);
-  var r : mat3x4<f32> = tint_symbol_17(ub, 304u);
-  var s : mat4x2<f32> = tint_symbol_18(ub, 352u);
-  var t : mat4x3<f32> = tint_symbol_19(ub, 384u);
-  var u : mat4x4<f32> = tint_symbol_20(ub, 448u);
-  var v : array<vec3<f32>, 2> = tint_symbol_21(ub, 512u);
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, UB_BasicLoad_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = ub.a;
-  var b : u32 = ub.b;
-  var c : f32 = ub.c;
-  var d : vec2<i32> = ub.d;
-  var e : vec2<u32> = ub.e;
-  var f : vec2<f32> = ub.f;
-  var g : vec3<i32> = ub.g;
-  var h : vec3<u32> = ub.h;
-  var i : vec3<f32> = ub.i;
-  var j : vec4<i32> = ub.j;
-  var k : vec4<u32> = ub.k;
-  var l : vec4<f32> = ub.l;
-  var m : mat2x2<f32> = ub.m;
-  var n : mat2x3<f32> = ub.n;
-  var o : mat2x4<f32> = ub.o;
-  var p : mat3x2<f32> = ub.p;
-  var q : mat3x3<f32> = ub.q;
-  var r : mat3x4<f32> = ub.r;
-  var s : mat4x2<f32> = ub.s;
-  var t : mat4x3<f32> = ub.t;
-  var u : mat4x4<f32> = ub.u;
-  var v : array<vec3<f32>, 2> = ub.v;
-}
-
-@group(0) @binding(0) var<uniform> ub : UB;
-
-struct UB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> i32
-
-@internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> u32
-
-@internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> f32
-
-@internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_5(buffer, (offset + 0u)), tint_symbol_5(buffer, (offset + 8u)), tint_symbol_5(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 32u)), tint_symbol_11(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : UB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_8(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a : i32 = tint_symbol(ub, 0u);
-  var b : u32 = tint_symbol_1(ub, 4u);
-  var c : f32 = tint_symbol_2(ub, 8u);
-  var d : vec2<i32> = tint_symbol_3(ub, 16u);
-  var e : vec2<u32> = tint_symbol_4(ub, 24u);
-  var f : vec2<f32> = tint_symbol_5(ub, 32u);
-  var g : vec3<i32> = tint_symbol_6(ub, 48u);
-  var h : vec3<u32> = tint_symbol_7(ub, 64u);
-  var i : vec3<f32> = tint_symbol_8(ub, 80u);
-  var j : vec4<i32> = tint_symbol_9(ub, 96u);
-  var k : vec4<u32> = tint_symbol_10(ub, 112u);
-  var l : vec4<f32> = tint_symbol_11(ub, 128u);
-  var m : mat2x2<f32> = tint_symbol_12(ub, 144u);
-  var n : mat2x3<f32> = tint_symbol_13(ub, 160u);
-  var o : mat2x4<f32> = tint_symbol_14(ub, 192u);
-  var p : mat3x2<f32> = tint_symbol_15(ub, 224u);
-  var q : mat3x3<f32> = tint_symbol_16(ub, 256u);
-  var r : mat3x4<f32> = tint_symbol_17(ub, 304u);
-  var s : mat4x2<f32> = tint_symbol_18(ub, 352u);
-  var t : mat4x3<f32> = tint_symbol_19(ub, 384u);
-  var u : mat4x4<f32> = tint_symbol_20(ub, 448u);
-  var v : array<vec3<f32>, 2> = tint_symbol_21(ub, 512u);
-}
-
-@group(0) @binding(0) var<uniform> ub : UB;
-
-struct UB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, SB_BasicStore) {
-  auto* src = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  sb.a = i32();
-  sb.b = u32();
-  sb.c = f32();
-  sb.d = vec2<i32>();
-  sb.e = vec2<u32>();
-  sb.f = vec2<f32>();
-  sb.g = vec3<i32>();
-  sb.h = vec3<u32>();
-  sb.i = vec3<f32>();
-  sb.j = vec4<i32>();
-  sb.k = vec4<u32>();
-  sb.l = vec4<f32>();
-  sb.m = mat2x2<f32>();
-  sb.n = mat2x3<f32>();
-  sb.o = mat2x4<f32>();
-  sb.p = mat3x2<f32>();
-  sb.q = mat3x3<f32>();
-  sb.r = mat3x4<f32>();
-  sb.s = mat4x2<f32>();
-  sb.t = mat4x3<f32>();
-  sb.u = mat4x4<f32>();
-  sb.v = array<vec3<f32>, 2>();
-}
-)";
-
-  auto* expect = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
-
-@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
-
-@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
-
-@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
-
-@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
-
-@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
-
-@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
-
-@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
-
-@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
-
-@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
-
-@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
-
-@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-  tint_symbol_5(buffer, (offset + 16u), value[2u]);
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-  tint_symbol_8(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-  tint_symbol_11(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-  tint_symbol_5(buffer, (offset + 16u), value[2u]);
-  tint_symbol_5(buffer, (offset + 24u), value[3u]);
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-  tint_symbol_8(buffer, (offset + 32u), value[2u]);
-  tint_symbol_8(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-  tint_symbol_11(buffer, (offset + 32u), value[2u]);
-  tint_symbol_11(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_8(buffer, (offset + (i_1 * 16u)), array[i_1]);
-  }
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 0u, i32());
-  tint_symbol_1(sb, 4u, u32());
-  tint_symbol_2(sb, 8u, f32());
-  tint_symbol_3(sb, 16u, vec2<i32>());
-  tint_symbol_4(sb, 24u, vec2<u32>());
-  tint_symbol_5(sb, 32u, vec2<f32>());
-  tint_symbol_6(sb, 48u, vec3<i32>());
-  tint_symbol_7(sb, 64u, vec3<u32>());
-  tint_symbol_8(sb, 80u, vec3<f32>());
-  tint_symbol_9(sb, 96u, vec4<i32>());
-  tint_symbol_10(sb, 112u, vec4<u32>());
-  tint_symbol_11(sb, 128u, vec4<f32>());
-  tint_symbol_12(sb, 144u, mat2x2<f32>());
-  tint_symbol_13(sb, 160u, mat2x3<f32>());
-  tint_symbol_14(sb, 192u, mat2x4<f32>());
-  tint_symbol_15(sb, 224u, mat3x2<f32>());
-  tint_symbol_16(sb, 256u, mat3x3<f32>());
-  tint_symbol_17(sb, 304u, mat3x4<f32>());
-  tint_symbol_18(sb, 352u, mat4x2<f32>());
-  tint_symbol_19(sb, 384u, mat4x3<f32>());
-  tint_symbol_20(sb, 448u, mat4x4<f32>());
-  tint_symbol_21(sb, 512u, array<vec3<f32>, 2>());
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, SB_BasicStore_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  sb.a = i32();
-  sb.b = u32();
-  sb.c = f32();
-  sb.d = vec2<i32>();
-  sb.e = vec2<u32>();
-  sb.f = vec2<f32>();
-  sb.g = vec3<i32>();
-  sb.h = vec3<u32>();
-  sb.i = vec3<f32>();
-  sb.j = vec4<i32>();
-  sb.k = vec4<u32>();
-  sb.l = vec4<f32>();
-  sb.m = mat2x2<f32>();
-  sb.n = mat2x3<f32>();
-  sb.o = mat2x4<f32>();
-  sb.p = mat3x2<f32>();
-  sb.q = mat3x3<f32>();
-  sb.r = mat3x4<f32>();
-  sb.s = mat4x2<f32>();
-  sb.t = mat4x3<f32>();
-  sb.u = mat4x4<f32>();
-  sb.v = array<vec3<f32>, 2>();
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
-
-@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
-
-@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
-
-@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
-
-@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
-
-@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
-
-@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
-
-@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
-
-@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
-
-@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
-
-@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
-
-@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
-
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-}
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-  tint_symbol_5(buffer, (offset + 16u), value[2u]);
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-  tint_symbol_8(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-  tint_symbol_11(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
-  tint_symbol_5(buffer, (offset + 0u), value[0u]);
-  tint_symbol_5(buffer, (offset + 8u), value[1u]);
-  tint_symbol_5(buffer, (offset + 16u), value[2u]);
-  tint_symbol_5(buffer, (offset + 24u), value[3u]);
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
-  tint_symbol_8(buffer, (offset + 0u), value[0u]);
-  tint_symbol_8(buffer, (offset + 16u), value[1u]);
-  tint_symbol_8(buffer, (offset + 32u), value[2u]);
-  tint_symbol_8(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
-  tint_symbol_11(buffer, (offset + 0u), value[0u]);
-  tint_symbol_11(buffer, (offset + 16u), value[1u]);
-  tint_symbol_11(buffer, (offset + 32u), value[2u]);
-  tint_symbol_11(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_8(buffer, (offset + (i_1 * 16u)), array[i_1]);
-  }
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 0u, i32());
-  tint_symbol_1(sb, 4u, u32());
-  tint_symbol_2(sb, 8u, f32());
-  tint_symbol_3(sb, 16u, vec2<i32>());
-  tint_symbol_4(sb, 24u, vec2<u32>());
-  tint_symbol_5(sb, 32u, vec2<f32>());
-  tint_symbol_6(sb, 48u, vec3<i32>());
-  tint_symbol_7(sb, 64u, vec3<u32>());
-  tint_symbol_8(sb, 80u, vec3<f32>());
-  tint_symbol_9(sb, 96u, vec4<i32>());
-  tint_symbol_10(sb, 112u, vec4<u32>());
-  tint_symbol_11(sb, 128u, vec4<f32>());
-  tint_symbol_12(sb, 144u, mat2x2<f32>());
-  tint_symbol_13(sb, 160u, mat2x3<f32>());
-  tint_symbol_14(sb, 192u, mat2x4<f32>());
-  tint_symbol_15(sb, 224u, mat3x2<f32>());
-  tint_symbol_16(sb, 256u, mat3x3<f32>());
-  tint_symbol_17(sb, 304u, mat3x4<f32>());
-  tint_symbol_18(sb, 352u, mat4x2<f32>());
-  tint_symbol_19(sb, 384u, mat4x3<f32>());
-  tint_symbol_20(sb, 448u, mat4x4<f32>());
-  tint_symbol_21(sb, 512u, array<vec3<f32>, 2>());
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, LoadStructure) {
-  auto* src = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : SB = sb;
-}
-)";
-
-  auto* expect = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)), tint_symbol_6(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)), tint_symbol_9(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_9(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> SB {
-  return SB(tint_symbol_1(buffer, (offset + 0u)), tint_symbol_2(buffer, (offset + 4u)), tint_symbol_3(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)), tint_symbol_6(buffer, (offset + 32u)), tint_symbol_7(buffer, (offset + 48u)), tint_symbol_8(buffer, (offset + 64u)), tint_symbol_9(buffer, (offset + 80u)), tint_symbol_10(buffer, (offset + 96u)), tint_symbol_11(buffer, (offset + 112u)), tint_symbol_12(buffer, (offset + 128u)), tint_symbol_13(buffer, (offset + 144u)), tint_symbol_14(buffer, (offset + 160u)), tint_symbol_15(buffer, (offset + 192u)), tint_symbol_16(buffer, (offset + 224u)), tint_symbol_17(buffer, (offset + 256u)), tint_symbol_18(buffer, (offset + 304u)), tint_symbol_19(buffer, (offset + 352u)), tint_symbol_20(buffer, (offset + 384u)), tint_symbol_21(buffer, (offset + 448u)), tint_symbol_22(buffer, (offset + 512u)));
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : SB = tint_symbol(sb, 0u);
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, LoadStructure_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : SB = sb;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@internal(intrinsic_load_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<i32>
-
-@internal(intrinsic_load_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<u32>
-
-@internal(intrinsic_load_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec2<f32>
-
-@internal(intrinsic_load_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<i32>
-
-@internal(intrinsic_load_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<u32>
-
-@internal(intrinsic_load_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec3<f32>
-
-@internal(intrinsic_load_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<i32>
-
-@internal(intrinsic_load_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<u32>
-
-@internal(intrinsic_load_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> vec4<f32>
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x2<f32> {
-  return mat2x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)));
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x3<f32> {
-  return mat2x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat2x4<f32> {
-  return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x2<f32> {
-  return mat3x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)));
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x3<f32> {
-  return mat3x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat3x4<f32> {
-  return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x2<f32> {
-  return mat4x2<f32>(tint_symbol_6(buffer, (offset + 0u)), tint_symbol_6(buffer, (offset + 8u)), tint_symbol_6(buffer, (offset + 16u)), tint_symbol_6(buffer, (offset + 24u)));
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x3<f32> {
-  return mat4x3<f32>(tint_symbol_9(buffer, (offset + 0u)), tint_symbol_9(buffer, (offset + 16u)), tint_symbol_9(buffer, (offset + 32u)), tint_symbol_9(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> mat4x4<f32> {
-  return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
-}
-
-fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> array<vec3<f32>, 2u> {
-  var arr : array<vec3<f32>, 2u>;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    arr[i_1] = tint_symbol_9(buffer, (offset + (i_1 * 16u)));
-  }
-  return arr;
-}
-
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> SB {
-  return SB(tint_symbol_1(buffer, (offset + 0u)), tint_symbol_2(buffer, (offset + 4u)), tint_symbol_3(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_5(buffer, (offset + 24u)), tint_symbol_6(buffer, (offset + 32u)), tint_symbol_7(buffer, (offset + 48u)), tint_symbol_8(buffer, (offset + 64u)), tint_symbol_9(buffer, (offset + 80u)), tint_symbol_10(buffer, (offset + 96u)), tint_symbol_11(buffer, (offset + 112u)), tint_symbol_12(buffer, (offset + 128u)), tint_symbol_13(buffer, (offset + 144u)), tint_symbol_14(buffer, (offset + 160u)), tint_symbol_15(buffer, (offset + 192u)), tint_symbol_16(buffer, (offset + 224u)), tint_symbol_17(buffer, (offset + 256u)), tint_symbol_18(buffer, (offset + 304u)), tint_symbol_19(buffer, (offset + 352u)), tint_symbol_20(buffer, (offset + 384u)), tint_symbol_21(buffer, (offset + 448u)), tint_symbol_22(buffer, (offset + 512u)));
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : SB = tint_symbol(sb, 0u);
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, StoreStructure) {
-  auto* src = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  sb = SB();
-}
-)";
-
-  auto* expect = R"(
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
-
-@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
-
-@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
-
-@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
-
-@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
-
-@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
-
-@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
-
-@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
-
-@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
-
-@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
-
-@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
-
-@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-  tint_symbol_6(buffer, (offset + 16u), value[2u]);
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-  tint_symbol_9(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-  tint_symbol_12(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-  tint_symbol_6(buffer, (offset + 16u), value[2u]);
-  tint_symbol_6(buffer, (offset + 24u), value[3u]);
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-  tint_symbol_9(buffer, (offset + 32u), value[2u]);
-  tint_symbol_9(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-  tint_symbol_12(buffer, (offset + 32u), value[2u]);
-  tint_symbol_12(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_9(buffer, (offset + (i_1 * 16u)), array[i_1]);
-  }
-}
-
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : SB) {
-  tint_symbol_1(buffer, (offset + 0u), value.a);
-  tint_symbol_2(buffer, (offset + 4u), value.b);
-  tint_symbol_3(buffer, (offset + 8u), value.c);
-  tint_symbol_4(buffer, (offset + 16u), value.d);
-  tint_symbol_5(buffer, (offset + 24u), value.e);
-  tint_symbol_6(buffer, (offset + 32u), value.f);
-  tint_symbol_7(buffer, (offset + 48u), value.g);
-  tint_symbol_8(buffer, (offset + 64u), value.h);
-  tint_symbol_9(buffer, (offset + 80u), value.i);
-  tint_symbol_10(buffer, (offset + 96u), value.j);
-  tint_symbol_11(buffer, (offset + 112u), value.k);
-  tint_symbol_12(buffer, (offset + 128u), value.l);
-  tint_symbol_13(buffer, (offset + 144u), value.m);
-  tint_symbol_14(buffer, (offset + 160u), value.n);
-  tint_symbol_15(buffer, (offset + 192u), value.o);
-  tint_symbol_16(buffer, (offset + 224u), value.p);
-  tint_symbol_17(buffer, (offset + 256u), value.q);
-  tint_symbol_18(buffer, (offset + 304u), value.r);
-  tint_symbol_19(buffer, (offset + 352u), value.s);
-  tint_symbol_20(buffer, (offset + 384u), value.t);
-  tint_symbol_21(buffer, (offset + 448u), value.u);
-  tint_symbol_22(buffer, (offset + 512u), value.v);
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 0u, SB());
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, StoreStructure_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  sb = SB();
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : i32)
-
-@internal(intrinsic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : u32)
-
-@internal(intrinsic_store_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : f32)
-
-@internal(intrinsic_store_storage_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<i32>)
-
-@internal(intrinsic_store_storage_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<u32>)
-
-@internal(intrinsic_store_storage_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec2<f32>)
-
-@internal(intrinsic_store_storage_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<i32>)
-
-@internal(intrinsic_store_storage_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<u32>)
-
-@internal(intrinsic_store_storage_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec3<f32>)
-
-@internal(intrinsic_store_storage_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<i32>)
-
-@internal(intrinsic_store_storage_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<u32>)
-
-@internal(intrinsic_store_storage_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : vec4<f32>)
-
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-}
-
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat2x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-}
-
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-  tint_symbol_6(buffer, (offset + 16u), value[2u]);
-}
-
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-  tint_symbol_9(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat3x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-  tint_symbol_12(buffer, (offset + 32u), value[2u]);
-}
-
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x2<f32>) {
-  tint_symbol_6(buffer, (offset + 0u), value[0u]);
-  tint_symbol_6(buffer, (offset + 8u), value[1u]);
-  tint_symbol_6(buffer, (offset + 16u), value[2u]);
-  tint_symbol_6(buffer, (offset + 24u), value[3u]);
-}
-
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x3<f32>) {
-  tint_symbol_9(buffer, (offset + 0u), value[0u]);
-  tint_symbol_9(buffer, (offset + 16u), value[1u]);
-  tint_symbol_9(buffer, (offset + 32u), value[2u]);
-  tint_symbol_9(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : mat4x4<f32>) {
-  tint_symbol_12(buffer, (offset + 0u), value[0u]);
-  tint_symbol_12(buffer, (offset + 16u), value[1u]);
-  tint_symbol_12(buffer, (offset + 32u), value[2u]);
-  tint_symbol_12(buffer, (offset + 48u), value[3u]);
-}
-
-fn tint_symbol_22(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
-  for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_9(buffer, (offset + (i_1 * 16u)), array[i_1]);
-  }
-}
-
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, value : SB) {
-  tint_symbol_1(buffer, (offset + 0u), value.a);
-  tint_symbol_2(buffer, (offset + 4u), value.b);
-  tint_symbol_3(buffer, (offset + 8u), value.c);
-  tint_symbol_4(buffer, (offset + 16u), value.d);
-  tint_symbol_5(buffer, (offset + 24u), value.e);
-  tint_symbol_6(buffer, (offset + 32u), value.f);
-  tint_symbol_7(buffer, (offset + 48u), value.g);
-  tint_symbol_8(buffer, (offset + 64u), value.h);
-  tint_symbol_9(buffer, (offset + 80u), value.i);
-  tint_symbol_10(buffer, (offset + 96u), value.j);
-  tint_symbol_11(buffer, (offset + 112u), value.k);
-  tint_symbol_12(buffer, (offset + 128u), value.l);
-  tint_symbol_13(buffer, (offset + 144u), value.m);
-  tint_symbol_14(buffer, (offset + 160u), value.n);
-  tint_symbol_15(buffer, (offset + 192u), value.o);
-  tint_symbol_16(buffer, (offset + 224u), value.p);
-  tint_symbol_17(buffer, (offset + 256u), value.q);
-  tint_symbol_18(buffer, (offset + 304u), value.r);
-  tint_symbol_19(buffer, (offset + 352u), value.s);
-  tint_symbol_20(buffer, (offset + 384u), value.t);
-  tint_symbol_21(buffer, (offset + 448u), value.u);
-  tint_symbol_22(buffer, (offset + 512u), value.v);
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 0u, SB());
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  a : i32;
-  b : u32;
-  c : f32;
-  d : vec2<i32>;
-  e : vec2<u32>;
-  f : vec2<f32>;
-  g : vec3<i32>;
-  h : vec3<u32>;
-  i : vec3<f32>;
-  j : vec4<i32>;
-  k : vec4<u32>;
-  l : vec4<f32>;
-  m : mat2x2<f32>;
-  n : mat2x3<f32>;
-  o : mat2x4<f32>;
-  p : mat3x2<f32>;
-  q : mat3x3<f32>;
-  r : mat3x4<f32>;
-  s : mat4x2<f32>;
-  t : mat4x3<f32>;
-  u : mat4x4<f32>;
-  v : array<vec3<f32>, 2>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain) {
-  auto* src = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-};
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : f32 = sb.b[4].b[1].b.z;
-}
-)";
-
-  // sb.b[4].b[1].b.z
-  //    ^  ^ ^  ^ ^ ^
-  //    |  | |  | | |
-  //  128  | |1200| 1224
-  //       | |    |
-  //    1152 1168 1216
-
-  auto* expect = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-}
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : f32 = tint_symbol(sb, 1224u);
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : f32 = sb.b[4].b[1].b.z;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-};
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-};
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-)";
-
-  // sb.b[4].b[1].b.z
-  //    ^  ^ ^  ^ ^ ^
-  //    |  | |  | | |
-  //  128  | |1200| 1224
-  //       | |    |
-  //    1152 1168 1216
-
-  auto* expect = R"(
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var x : f32 = tint_symbol(sb, 1224u);
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-}
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-}
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChain) {
-  auto* src = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-};
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = sb.b[i].b[j].b[k];
-}
-)";
-
-  auto* expect = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-}
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChain_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = sb.b[i].b[j].b[k];
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-};
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-};
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : @stride(256) array<S2>;
-}
-
-struct S2 {
-  a : i32;
-  b : @stride(32) array<S1, 3>;
-  c : i32;
-}
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, ComplexDynamicAccessChainWithAliases) {
-  auto* src = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-
-type A1 = S1;
-
-type A1_Array = @stride(32) array<S1, 3>;
-
-struct S2 {
-  a : i32;
-  b : A1_Array;
-  c : i32;
-};
-
-type A2 = S2;
-
-type A2_Array = @stride(256) array<S2>;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : A2_Array;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = sb.b[i].b[j].b[k];
-}
-)";
-
-  auto* expect = R"(
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-
-type A1 = S1;
-
-type A1_Array = @stride(32) array<S1, 3>;
-
-struct S2 {
-  a : i32;
-  b : A1_Array;
-  c : i32;
-}
-
-type A2 = S2;
-
-type A2_Array = @stride(256) array<S2>;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : A2_Array;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest,
-       ComplexDynamicAccessChainWithAliases_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = sb.b[i].b[j].b[k];
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : A2_Array;
-};
-
-type A2_Array = @stride(256) array<S2>;
-
-type A2 = S2;
-
-struct S2 {
-  a : i32;
-  b : A1_Array;
-  c : i32;
-};
-
-type A1 = S1;
-
-type A1_Array = @stride(32) array<S1, 3>;
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> f32
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var i : i32 = 4;
-  var j : u32 = 1u;
-  var k : i32 = 2;
-  var x : f32 = tint_symbol(sb, (((((128u + (256u * u32(i))) + 16u) + (32u * j)) + 16u) + (4u * u32(k))));
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  @size(128)
-  a : i32;
-  b : A2_Array;
-}
-
-type A2_Array = @stride(256) array<S2>;
-
-type A2 = S2;
-
-struct S2 {
-  a : i32;
-  b : A1_Array;
-  c : i32;
-}
-
-type A1 = S1;
-
-type A1_Array = @stride(32) array<S1, 3>;
-
-struct S1 {
-  a : i32;
-  b : vec3<f32>;
-  c : i32;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, StorageBufferAtomics) {
-  auto* src = R"(
-struct SB {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-};
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  atomicStore(&sb.a, 123);
-  atomicLoad(&sb.a);
-  atomicAdd(&sb.a, 123);
-  atomicSub(&sb.a, 123);
-  atomicMax(&sb.a, 123);
-  atomicMin(&sb.a, 123);
-  atomicAnd(&sb.a, 123);
-  atomicOr(&sb.a, 123);
-  atomicXor(&sb.a, 123);
-  atomicExchange(&sb.a, 123);
-  atomicCompareExchangeWeak(&sb.a, 123, 345);
-
-  atomicStore(&sb.b, 123u);
-  atomicLoad(&sb.b);
-  atomicAdd(&sb.b, 123u);
-  atomicSub(&sb.b, 123u);
-  atomicMax(&sb.b, 123u);
-  atomicMin(&sb.b, 123u);
-  atomicAnd(&sb.b, 123u);
-  atomicOr(&sb.b, 123u);
-  atomicXor(&sb.b, 123u);
-  atomicExchange(&sb.b, 123u);
-  atomicCompareExchangeWeak(&sb.b, 123u, 345u);
-}
-)";
-
-  auto* expect = R"(
-struct SB {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-@internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
-
-@internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
-
-@internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
-
-@internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 16u, 123);
-  tint_symbol_1(sb, 16u);
-  tint_symbol_2(sb, 16u, 123);
-  tint_symbol_3(sb, 16u, 123);
-  tint_symbol_4(sb, 16u, 123);
-  tint_symbol_5(sb, 16u, 123);
-  tint_symbol_6(sb, 16u, 123);
-  tint_symbol_7(sb, 16u, 123);
-  tint_symbol_8(sb, 16u, 123);
-  tint_symbol_9(sb, 16u, 123);
-  tint_symbol_10(sb, 16u, 123, 345);
-  tint_symbol_11(sb, 20u, 123u);
-  tint_symbol_12(sb, 20u);
-  tint_symbol_13(sb, 20u, 123u);
-  tint_symbol_14(sb, 20u, 123u);
-  tint_symbol_15(sb, 20u, 123u);
-  tint_symbol_16(sb, 20u, 123u);
-  tint_symbol_17(sb, 20u, 123u);
-  tint_symbol_18(sb, 20u, 123u);
-  tint_symbol_19(sb, 20u, 123u);
-  tint_symbol_20(sb, 20u, 123u);
-  tint_symbol_21(sb, 20u, 123u, 345u);
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, StorageBufferAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  atomicStore(&sb.a, 123);
-  atomicLoad(&sb.a);
-  atomicAdd(&sb.a, 123);
-  atomicSub(&sb.a, 123);
-  atomicMax(&sb.a, 123);
-  atomicMin(&sb.a, 123);
-  atomicAnd(&sb.a, 123);
-  atomicOr(&sb.a, 123);
-  atomicXor(&sb.a, 123);
-  atomicExchange(&sb.a, 123);
-  atomicCompareExchangeWeak(&sb.a, 123, 345);
-
-  atomicStore(&sb.b, 123u);
-  atomicLoad(&sb.b);
-  atomicAdd(&sb.b, 123u);
-  atomicSub(&sb.b, 123u);
-  atomicMax(&sb.b, 123u);
-  atomicMin(&sb.b, 123u);
-  atomicAnd(&sb.b, 123u);
-  atomicOr(&sb.b, 123u);
-  atomicXor(&sb.b, 123u);
-  atomicExchange(&sb.b, 123u);
-  atomicCompareExchangeWeak(&sb.b, 123u, 345u);
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-};
-)";
-
-  auto* expect = R"(
-@internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
-
-@internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
-
-@internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
-
-@internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
-
-@internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
-
-@internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
-
-@internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
-
-@internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  tint_symbol(sb, 16u, 123);
-  tint_symbol_1(sb, 16u);
-  tint_symbol_2(sb, 16u, 123);
-  tint_symbol_3(sb, 16u, 123);
-  tint_symbol_4(sb, 16u, 123);
-  tint_symbol_5(sb, 16u, 123);
-  tint_symbol_6(sb, 16u, 123);
-  tint_symbol_7(sb, 16u, 123);
-  tint_symbol_8(sb, 16u, 123);
-  tint_symbol_9(sb, 16u, 123);
-  tint_symbol_10(sb, 16u, 123, 345);
-  tint_symbol_11(sb, 20u, 123u);
-  tint_symbol_12(sb, 20u);
-  tint_symbol_13(sb, 20u, 123u);
-  tint_symbol_14(sb, 20u, 123u);
-  tint_symbol_15(sb, 20u, 123u);
-  tint_symbol_16(sb, 20u, 123u);
-  tint_symbol_17(sb, 20u, 123u);
-  tint_symbol_18(sb, 20u, 123u);
-  tint_symbol_19(sb, 20u, 123u);
-  tint_symbol_20(sb, 20u, 123u);
-  tint_symbol_21(sb, 20u, 123u, 345u);
-}
-
-@group(0) @binding(0) var<storage, read_write> sb : SB;
-
-struct SB {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-}
-)";
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, WorkgroupBufferAtomics) {
-  auto* src = R"(
-struct S {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-}
-
-var<workgroup> w : S;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  atomicStore(&(w.a), 123);
-  atomicLoad(&(w.a));
-  atomicAdd(&(w.a), 123);
-  atomicSub(&(w.a), 123);
-  atomicMax(&(w.a), 123);
-  atomicMin(&(w.a), 123);
-  atomicAnd(&(w.a), 123);
-  atomicOr(&(w.a), 123);
-  atomicXor(&(w.a), 123);
-  atomicExchange(&(w.a), 123);
-  atomicCompareExchangeWeak(&(w.a), 123, 345);
-  atomicStore(&(w.b), 123u);
-  atomicLoad(&(w.b));
-  atomicAdd(&(w.b), 123u);
-  atomicSub(&(w.b), 123u);
-  atomicMax(&(w.b), 123u);
-  atomicMin(&(w.b), 123u);
-  atomicAnd(&(w.b), 123u);
-  atomicOr(&(w.b), 123u);
-  atomicXor(&(w.b), 123u);
-  atomicExchange(&(w.b), 123u);
-  atomicCompareExchangeWeak(&(w.b), 123u, 345u);
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeMemoryAccessTest, WorkgroupBufferAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  atomicStore(&(w.a), 123);
-  atomicLoad(&(w.a));
-  atomicAdd(&(w.a), 123);
-  atomicSub(&(w.a), 123);
-  atomicMax(&(w.a), 123);
-  atomicMin(&(w.a), 123);
-  atomicAnd(&(w.a), 123);
-  atomicOr(&(w.a), 123);
-  atomicXor(&(w.a), 123);
-  atomicExchange(&(w.a), 123);
-  atomicCompareExchangeWeak(&(w.a), 123, 345);
-  atomicStore(&(w.b), 123u);
-  atomicLoad(&(w.b));
-  atomicAdd(&(w.b), 123u);
-  atomicSub(&(w.b), 123u);
-  atomicMax(&(w.b), 123u);
-  atomicMin(&(w.b), 123u);
-  atomicAnd(&(w.b), 123u);
-  atomicOr(&(w.b), 123u);
-  atomicXor(&(w.b), 123u);
-  atomicExchange(&(w.b), 123u);
-  atomicCompareExchangeWeak(&(w.b), 123u, 345u);
-}
-
-var<workgroup> w : S;
-
-struct S {
-  padding : vec4<f32>;
-  a : atomic<i32>;
-  b : atomic<u32>;
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<DecomposeMemoryAccess>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/decompose_strided_array.cc b/src/transform/decompose_strided_array.cc
deleted file mode 100644
index 106fa56..0000000
--- a/src/transform/decompose_strided_array.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-// 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/transform/decompose_strided_array.h"
-
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/type_constructor.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeStridedArray);
-
-namespace tint {
-namespace transform {
-namespace {
-
-using DecomposedArrays = std::unordered_map<const sem::Array*, Symbol>;
-
-}  // namespace
-
-DecomposeStridedArray::DecomposeStridedArray() = default;
-
-DecomposeStridedArray::~DecomposeStridedArray() = default;
-
-bool DecomposeStridedArray::ShouldRun(const Program* program,
-                                      const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* ast = node->As<ast::Array>()) {
-      if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-void DecomposeStridedArray::Run(CloneContext& ctx,
-                                const DataMap&,
-                                DataMap&) const {
-  const auto& sem = ctx.src->Sem();
-
-  static constexpr const char* kMemberName = "el";
-
-  // Maps an array type in the source program to the name of the struct wrapper
-  // type in the target program.
-  std::unordered_map<const sem::Array*, Symbol> decomposed;
-
-  // Find and replace all arrays with a @stride attribute with a array that has
-  // the @stride removed. If the source array stride does not match the natural
-  // stride for the array element type, then replace the array element type with
-  // a structure, holding a single field with a @size attribute equal to the
-  // array stride.
-  ctx.ReplaceAll([&](const ast::Array* ast) -> const ast::Array* {
-    if (auto* arr = sem.Get(ast)) {
-      if (!arr->IsStrideImplicit()) {
-        auto el_ty = utils::GetOrCreate(decomposed, arr, [&] {
-          auto name = ctx.dst->Symbols().New("strided_arr");
-          auto* member_ty = ctx.Clone(ast->type);
-          auto* member = ctx.dst->Member(kMemberName, member_ty,
-                                         {ctx.dst->MemberSize(arr->Stride())});
-          ctx.dst->Structure(name, {member});
-          return name;
-        });
-        auto* count = ctx.Clone(ast->count);
-        return ctx.dst->ty.array(ctx.dst->ty.type_name(el_ty), count);
-      }
-      if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
-        // Strip the @stride attribute
-        auto* ty = ctx.Clone(ast->type);
-        auto* count = ctx.Clone(ast->count);
-        return ctx.dst->ty.array(ty, count);
-      }
-    }
-    return nullptr;
-  });
-
-  // Find all array index-accessors expressions for arrays that have had their
-  // element changed to a single field structure. These expressions are adjusted
-  // to insert an additional member accessor for the single structure field.
-  // Example: `arr[i]` -> `arr[i].el`
-  ctx.ReplaceAll(
-      [&](const ast::IndexAccessorExpression* idx) -> const ast::Expression* {
-        if (auto* ty = ctx.src->TypeOf(idx->object)) {
-          if (auto* arr = ty->UnwrapRef()->As<sem::Array>()) {
-            if (!arr->IsStrideImplicit()) {
-              auto* expr = ctx.CloneWithoutTransform(idx);
-              return ctx.dst->MemberAccessor(expr, kMemberName);
-            }
-          }
-        }
-        return nullptr;
-      });
-
-  // Find all array type constructor expressions for array types that have had
-  // their element changed to a single field structure. These constructors are
-  // adjusted to wrap each of the arguments with an additional constructor for
-  // the new element structure type.
-  // Example:
-  //   `@stride(32) array<i32, 3>(1, 2, 3)`
-  // ->
-  //   `array<strided_arr, 3>(strided_arr(1), strided_arr(2), strided_arr(3))`
-  ctx.ReplaceAll(
-      [&](const ast::CallExpression* expr) -> const ast::Expression* {
-        if (!expr->args.empty()) {
-          if (auto* call = sem.Get(expr)) {
-            if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
-              if (auto* arr = ctor->ReturnType()->As<sem::Array>()) {
-                // Begin by cloning the array constructor type or name
-                // If this is an unaliased array, this may add a new entry to
-                // decomposed.
-                // If this is an aliased array, decomposed should already be
-                // populated with any strided aliases.
-                ast::CallExpression::Target target;
-                if (expr->target.type) {
-                  target.type = ctx.Clone(expr->target.type);
-                } else {
-                  target.name = ctx.Clone(expr->target.name);
-                }
-
-                ast::ExpressionList args;
-                if (auto it = decomposed.find(arr); it != decomposed.end()) {
-                  args.reserve(expr->args.size());
-                  for (auto* arg : expr->args) {
-                    args.emplace_back(
-                        ctx.dst->Call(it->second, ctx.Clone(arg)));
-                  }
-                } else {
-                  args = ctx.Clone(expr->args);
-                }
-
-                return target.type ? ctx.dst->Construct(target.type, args)
-                                   : ctx.dst->Call(target.name, args);
-              }
-            }
-          }
-        }
-        return nullptr;
-      });
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/decompose_strided_array.h b/src/transform/decompose_strided_array.h
deleted file mode 100644
index 27d4de0..0000000
--- a/src/transform/decompose_strided_array.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
-#define SRC_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// DecomposeStridedArray transforms replaces arrays with a non-default
-/// `@stride` attribute with an array of structure elements, where the
-/// structure contains a single field with an equivalent `@size` attribute.
-/// `@stride` attributes on arrays that match the default stride are also
-/// removed.
-///
-/// @note Depends on the following transforms to have been run first:
-/// * SimplifyPointers
-class DecomposeStridedArray
-    : public Castable<DecomposeStridedArray, Transform> {
- public:
-  /// Constructor
-  DecomposeStridedArray();
-
-  /// Destructor
-  ~DecomposeStridedArray() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_DECOMPOSE_STRIDED_ARRAY_H_
diff --git a/src/transform/decompose_strided_array_test.cc b/src/transform/decompose_strided_array_test.cc
deleted file mode 100644
index e982c9a..0000000
--- a/src/transform/decompose_strided_array_test.cc
+++ /dev/null
@@ -1,698 +0,0 @@
-// 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/transform/decompose_strided_array.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using DecomposeStridedArrayTest = TransformTest;
-using f32 = ProgramBuilder::f32;
-
-TEST_F(DecomposeStridedArrayTest, ShouldRunEmptyModule) {
-  ProgramBuilder b;
-  EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
-}
-
-TEST_F(DecomposeStridedArrayTest, ShouldRunNonStridedArray) {
-  // var<private> arr : array<f32, 4>
-
-  ProgramBuilder b;
-  b.Global("arr", b.ty.array<f32, 4>(), ast::StorageClass::kPrivate);
-  EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
-}
-
-TEST_F(DecomposeStridedArrayTest, ShouldRunDefaultStridedArray) {
-  // var<private> arr : @stride(4) array<f32, 4>
-
-  ProgramBuilder b;
-  b.Global("arr", b.ty.array<f32, 4>(4), ast::StorageClass::kPrivate);
-  EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
-}
-
-TEST_F(DecomposeStridedArrayTest, ShouldRunExplicitStridedArray) {
-  // var<private> arr : @stride(16) array<f32, 4>
-
-  ProgramBuilder b;
-  b.Global("arr", b.ty.array<f32, 4>(16), ast::StorageClass::kPrivate);
-  EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
-}
-
-TEST_F(DecomposeStridedArrayTest, Empty) {
-  auto* src = R"()";
-  auto* expect = src;
-
-  auto got = Run<DecomposeStridedArray>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, PrivateDefaultStridedArray) {
-  // var<private> arr : @stride(4) array<f32, 4>
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(4) array<f32, 4> = a;
-  //   let b : f32 = arr[1];
-  // }
-
-  ProgramBuilder b;
-  b.Global("arr", b.ty.array<f32, 4>(4), ast::StorageClass::kPrivate);
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array<f32, 4>(4), b.Expr("arr"))),
-             b.Decl(b.Const("b", b.ty.f32(), b.IndexAccessor("arr", 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-var<private> arr : array<f32, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<f32, 4> = arr;
-  let b : f32 = arr[1];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, PrivateStridedArray) {
-  // var<private> arr : @stride(32) array<f32, 4>
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(32) array<f32, 4> = a;
-  //   let b : f32 = arr[1];
-  // }
-
-  ProgramBuilder b;
-  b.Global("arr", b.ty.array<f32, 4>(32), ast::StorageClass::kPrivate);
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array<f32, 4>(32), b.Expr("arr"))),
-             b.Decl(b.Const("b", b.ty.f32(), b.IndexAccessor("arr", 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-var<private> arr : array<strided_arr, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<strided_arr, 4> = arr;
-  let b : f32 = arr[1].el;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, ReadUniformStridedArray) {
-  // struct S {
-  //   a : @stride(32) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<uniform> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(32) array<f32, 4> = s.a;
-  //   let b : f32 = s.a[1];
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
-           b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array<f32, 4>(32),
-                            b.MemberAccessor("s", "a"))),
-             b.Decl(b.Const("b", b.ty.f32(),
-                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-struct S {
-  a : array<strided_arr, 4>;
-}
-
-@group(0) @binding(0) var<uniform> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<strided_arr, 4> = s.a;
-  let b : f32 = s.a[1].el;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, ReadUniformDefaultStridedArray) {
-  // struct S {
-  //   a : @stride(16) array<vec4<f32>, 4>;
-  // };
-  // @group(0) @binding(0) var<uniform> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(16) array<vec4<f32>, 4> = s.a;
-  //   let b : f32 = s.a[1][2];
-  // }
-  ProgramBuilder b;
-  auto* S =
-      b.Structure("S", {b.Member("a", b.ty.array(b.ty.vec4<f32>(), 4, 16))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
-           b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array(b.ty.vec4<f32>(), 4, 16),
-                            b.MemberAccessor("s", "a"))),
-             b.Decl(b.Const(
-                 "b", b.ty.f32(),
-                 b.IndexAccessor(b.IndexAccessor(b.MemberAccessor("s", "a"), 1),
-                                 2))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect =
-      R"(
-struct S {
-  a : array<vec4<f32>, 4>;
-}
-
-@group(0) @binding(0) var<uniform> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<vec4<f32>, 4> = s.a;
-  let b : f32 = s.a[1][2];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, ReadStorageStridedArray) {
-  // struct S {
-  //   a : @stride(32) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<storage> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(32) array<f32, 4> = s.a;
-  //   let b : f32 = s.a[1];
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array<f32, 4>(32),
-                            b.MemberAccessor("s", "a"))),
-             b.Decl(b.Const("b", b.ty.f32(),
-                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-struct S {
-  a : array<strided_arr, 4>;
-}
-
-@group(0) @binding(0) var<storage> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<strided_arr, 4> = s.a;
-  let b : f32 = s.a[1].el;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, ReadStorageDefaultStridedArray) {
-  // struct S {
-  //   a : @stride(4) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<storage> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : @stride(4) array<f32, 4> = s.a;
-  //   let b : f32 = s.a[1];
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(4))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.array<f32, 4>(4),
-                            b.MemberAccessor("s", "a"))),
-             b.Decl(b.Const("b", b.ty.f32(),
-                            b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  a : array<f32, 4>;
-}
-
-@group(0) @binding(0) var<storage> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : array<f32, 4> = s.a;
-  let b : f32 = s.a[1];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, WriteStorageStridedArray) {
-  // struct S {
-  //   a : @stride(32) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   s.a = @stride(32) array<f32, 4>();
-  //   s.a = @stride(32) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
-  //   s.a[1] = 5.0;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.array<f32, 4>(32))),
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.array<f32, 4>(32), 1.0f, 2.0f, 3.0f, 4.0f)),
-          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect =
-      R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-struct S {
-  a : array<strided_arr, 4>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  s.a = array<strided_arr, 4>();
-  s.a = array<strided_arr, 4>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1].el = 5.0;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, WriteStorageDefaultStridedArray) {
-  // struct S {
-  //   a : @stride(4) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   s.a = @stride(4) array<f32, 4>();
-  //   s.a = @stride(4) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
-  //   s.a[1] = 5.0;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(4))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.array<f32, 4>(4))),
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.array<f32, 4>(4), 1.0f, 2.0f, 3.0f, 4.0f)),
-          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect =
-      R"(
-struct S {
-  a : array<f32, 4>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  s.a = array<f32, 4>();
-  s.a = array<f32, 4>(1.0, 2.0, 3.0, 4.0);
-  s.a[1] = 5.0;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, ReadWriteViaPointerLets) {
-  // struct S {
-  //   a : @stride(32) array<f32, 4>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a = &s.a;
-  //   let b = &*&*(a);
-  //   let c = *b;
-  //   let d = (*b)[1];
-  //   (*b) = @stride(32) array<f32, 4>(1.0, 2.0, 3.0, 4.0);
-  //   (*b)[1] = 5.0;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4>(32))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", nullptr,
-                            b.AddressOf(b.MemberAccessor("s", "a")))),
-             b.Decl(b.Const("b", nullptr,
-                            b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
-             b.Decl(b.Const("c", nullptr, b.Deref("b"))),
-             b.Decl(b.Const("d", nullptr, b.IndexAccessor(b.Deref("b"), 1))),
-             b.Assign(b.Deref("b"), b.Construct(b.ty.array<f32, 4>(32), 1.0f,
-                                                2.0f, 3.0f, 4.0f)),
-             b.Assign(b.IndexAccessor(b.Deref("b"), 1), 5.0f),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect =
-      R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-struct S {
-  a : array<strided_arr, 4>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let c = s.a;
-  let d = s.a[1].el;
-  s.a = array<strided_arr, 4>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1].el = 5.0;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, PrivateAliasedStridedArray) {
-  // type ARR = @stride(32) array<f32, 4>;
-  // struct S {
-  //   a : ARR;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : ARR = s.a;
-  //   let b : f32 = s.a[1];
-  //   s.a = ARR();
-  //   s.a = ARR(1.0, 2.0, 3.0, 4.0);
-  //   s.a[1] = 5.0;
-  // }
-  ProgramBuilder b;
-  b.Alias("ARR", b.ty.array<f32, 4>(32));
-  auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR"))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(
-              b.Const("a", b.ty.type_name("ARR"), b.MemberAccessor("s", "a"))),
-          b.Decl(b.Const("b", b.ty.f32(),
-                         b.IndexAccessor(b.MemberAccessor("s", "a"), 1))),
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.type_name("ARR"))),
-          b.Assign(b.MemberAccessor("s", "a"),
-                   b.Construct(b.ty.type_name("ARR"), 1.0f, 2.0f, 3.0f, 4.0f)),
-          b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1), 5.0f),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct strided_arr {
-  @size(32)
-  el : f32;
-}
-
-type ARR = array<strided_arr, 4>;
-
-struct S {
-  a : ARR;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : ARR = s.a;
-  let b : f32 = s.a[1].el;
-  s.a = ARR();
-  s.a = ARR(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1].el = 5.0;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedArrayTest, PrivateNestedStridedArray) {
-  // type ARR_A = @stride(8) array<f32, 2>;
-  // type ARR_B = @stride(128) array<@stride(16) array<ARR_A, 3>, 4>;
-  // struct S {
-  //   a : ARR_B;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a : ARR_B = s.a;
-  //   let b : array<@stride(8) array<f32, 2>, 3> = s.a[3];
-  //   let c = s.a[3][2];
-  //   let d = s.a[3][2][1];
-  //   s.a = ARR_B();
-  //   s.a[3][2][1] = 5.0;
-  // }
-
-  ProgramBuilder b;
-  b.Alias("ARR_A", b.ty.array<f32, 2>(8));
-  b.Alias("ARR_B",
-          b.ty.array(                                      //
-              b.ty.array(b.ty.type_name("ARR_A"), 3, 16),  //
-              4, 128));
-  auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR_B"))});
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("a", b.ty.type_name("ARR_B"),
-                            b.MemberAccessor("s", "a"))),
-             b.Decl(b.Const("b", b.ty.array(b.ty.type_name("ARR_A"), 3, 16),
-                            b.IndexAccessor(                 //
-                                b.MemberAccessor("s", "a"),  //
-                                3))),
-             b.Decl(b.Const("c", b.ty.type_name("ARR_A"),
-                            b.IndexAccessor(                     //
-                                b.IndexAccessor(                 //
-                                    b.MemberAccessor("s", "a"),  //
-                                    3),
-                                2))),
-             b.Decl(b.Const("d", b.ty.f32(),
-                            b.IndexAccessor(                         //
-                                b.IndexAccessor(                     //
-                                    b.IndexAccessor(                 //
-                                        b.MemberAccessor("s", "a"),  //
-                                        3),
-                                    2),
-                                1))),
-             b.Assign(b.MemberAccessor("s", "a"),
-                      b.Construct(b.ty.type_name("ARR_B"))),
-             b.Assign(b.IndexAccessor(                         //
-                          b.IndexAccessor(                     //
-                              b.IndexAccessor(                 //
-                                  b.MemberAccessor("s", "a"),  //
-                                  3),
-                              2),
-                          1),
-                      5.0f),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect =
-      R"(
-struct strided_arr {
-  @size(8)
-  el : f32;
-}
-
-type ARR_A = array<strided_arr, 2>;
-
-struct strided_arr_1 {
-  @size(128)
-  el : array<ARR_A, 3>;
-}
-
-type ARR_B = array<strided_arr_1, 4>;
-
-struct S {
-  a : ARR_B;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let a : ARR_B = s.a;
-  let b : array<ARR_A, 3> = s.a[3].el;
-  let c : ARR_A = s.a[3].el[2];
-  let d : f32 = s.a[3].el[2][1].el;
-  s.a = ARR_B();
-  s.a[3].el[2][1].el = 5.0;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/decompose_strided_matrix.cc b/src/transform/decompose_strided_matrix.cc
deleted file mode 100644
index e866c4f..0000000
--- a/src/transform/decompose_strided_matrix.cc
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/decompose_strided_matrix.h"
-
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/sem/expression.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/utils/hash.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::DecomposeStridedMatrix);
-
-namespace tint {
-namespace transform {
-namespace {
-
-/// MatrixInfo describes a matrix member with a custom stride
-struct MatrixInfo {
-  /// The stride in bytes between columns of the matrix
-  uint32_t stride = 0;
-  /// The type of the matrix
-  const sem::Matrix* matrix = nullptr;
-
-  /// @returns a new ast::Array that holds an vector column for each row of the
-  /// matrix.
-  const ast::Array* array(ProgramBuilder* b) const {
-    return b->ty.array(b->ty.vec<ProgramBuilder::f32>(matrix->rows()),
-                       matrix->columns(), stride);
-  }
-
-  /// Equality operator
-  bool operator==(const MatrixInfo& info) const {
-    return stride == info.stride && matrix == info.matrix;
-  }
-  /// Hash function
-  struct Hasher {
-    size_t operator()(const MatrixInfo& t) const {
-      return utils::Hash(t.stride, t.matrix);
-    }
-  };
-};
-
-/// Return type of the callback function of GatherCustomStrideMatrixMembers
-enum GatherResult { kContinue, kStop };
-
-/// GatherCustomStrideMatrixMembers scans `program` for all matrix members of
-/// storage and uniform structs, which are of a matrix type, and have a custom
-/// matrix stride attribute. For each matrix member found, `callback` is called.
-/// `callback` is a function with the signature:
-///      GatherResult(const sem::StructMember* member,
-///                   sem::Matrix* matrix,
-///                   uint32_t stride)
-/// If `callback` return GatherResult::kStop, then the scanning will immediately
-/// terminate, and GatherCustomStrideMatrixMembers() will return, otherwise
-/// scanning will continue.
-template <typename F>
-void GatherCustomStrideMatrixMembers(const Program* program, F&& callback) {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* str = node->As<ast::Struct>()) {
-      auto* str_ty = program->Sem().Get(str);
-      if (!str_ty->UsedAs(ast::StorageClass::kUniform) &&
-          !str_ty->UsedAs(ast::StorageClass::kStorage)) {
-        continue;
-      }
-      for (auto* member : str_ty->Members()) {
-        auto* matrix = member->Type()->As<sem::Matrix>();
-        if (!matrix) {
-          continue;
-        }
-        auto* attr = ast::GetAttribute<ast::StrideAttribute>(
-            member->Declaration()->attributes);
-        if (!attr) {
-          continue;
-        }
-        uint32_t stride = attr->stride;
-        if (matrix->ColumnStride() == stride) {
-          continue;
-        }
-        if (callback(member, matrix, stride) == GatherResult::kStop) {
-          return;
-        }
-      }
-    }
-  }
-}
-
-}  // namespace
-
-DecomposeStridedMatrix::DecomposeStridedMatrix() = default;
-
-DecomposeStridedMatrix::~DecomposeStridedMatrix() = default;
-
-bool DecomposeStridedMatrix::ShouldRun(const Program* program,
-                                       const DataMap&) const {
-  bool should_run = false;
-  GatherCustomStrideMatrixMembers(
-      program, [&](const sem::StructMember*, sem::Matrix*, uint32_t) {
-        should_run = true;
-        return GatherResult::kStop;
-      });
-  return should_run;
-}
-
-void DecomposeStridedMatrix::Run(CloneContext& ctx,
-                                 const DataMap&,
-                                 DataMap&) const {
-  // Scan the program for all storage and uniform structure matrix members with
-  // a custom stride attribute. Replace these matrices with an equivalent array,
-  // and populate the `decomposed` map with the members that have been replaced.
-  std::unordered_map<const ast::StructMember*, MatrixInfo> decomposed;
-  GatherCustomStrideMatrixMembers(
-      ctx.src, [&](const sem::StructMember* member, sem::Matrix* matrix,
-                   uint32_t stride) {
-        // We've got ourselves a struct member of a matrix type with a custom
-        // stride. Replace this with an array of column vectors.
-        MatrixInfo info{stride, matrix};
-        auto* replacement = ctx.dst->Member(
-            member->Offset(), ctx.Clone(member->Name()), info.array(ctx.dst));
-        ctx.Replace(member->Declaration(), replacement);
-        decomposed.emplace(member->Declaration(), info);
-        return GatherResult::kContinue;
-      });
-
-  // For all expressions where a single matrix column vector was indexed, we can
-  // preserve these without calling conversion functions.
-  // Example:
-  //   ssbo.mat[2] -> ssbo.mat[2]
-  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr)
-                     -> const ast::IndexAccessorExpression* {
-    if (auto* access =
-            ctx.src->Sem().Get<sem::StructMemberAccess>(expr->object)) {
-      auto it = decomposed.find(access->Member()->Declaration());
-      if (it != decomposed.end()) {
-        auto* obj = ctx.CloneWithoutTransform(expr->object);
-        auto* idx = ctx.Clone(expr->index);
-        return ctx.dst->IndexAccessor(obj, idx);
-      }
-    }
-    return nullptr;
-  });
-
-  // For all struct member accesses to the matrix on the LHS of an assignment,
-  // we need to convert the matrix to the array before assigning to the
-  // structure.
-  // Example:
-  //   ssbo.mat = mat_to_arr(m)
-  std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> mat_to_arr;
-  ctx.ReplaceAll([&](const ast::AssignmentStatement* stmt)
-                     -> const ast::Statement* {
-    if (auto* access = ctx.src->Sem().Get<sem::StructMemberAccess>(stmt->lhs)) {
-      auto it = decomposed.find(access->Member()->Declaration());
-      if (it == decomposed.end()) {
-        return nullptr;
-      }
-      MatrixInfo info = it->second;
-      auto fn = utils::GetOrCreate(mat_to_arr, info, [&] {
-        auto name = ctx.dst->Symbols().New(
-            "mat" + std::to_string(info.matrix->columns()) + "x" +
-            std::to_string(info.matrix->rows()) + "_stride_" +
-            std::to_string(info.stride) + "_to_arr");
-
-        auto matrix = [&] { return CreateASTTypeFor(ctx, info.matrix); };
-        auto array = [&] { return info.array(ctx.dst); };
-
-        auto mat = ctx.dst->Sym("m");
-        ast::ExpressionList columns(info.matrix->columns());
-        for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size()); i++) {
-          columns[i] = ctx.dst->IndexAccessor(mat, i);
-        }
-        ctx.dst->Func(name,
-                      {
-                          ctx.dst->Param(mat, matrix()),
-                      },
-                      array(),
-                      {
-                          ctx.dst->Return(ctx.dst->Construct(array(), columns)),
-                      });
-        return name;
-      });
-      auto* lhs = ctx.CloneWithoutTransform(stmt->lhs);
-      auto* rhs = ctx.dst->Call(fn, ctx.Clone(stmt->rhs));
-      return ctx.dst->Assign(lhs, rhs);
-    }
-    return nullptr;
-  });
-
-  // For all other struct member accesses, we need to convert the array to the
-  // matrix type. Example:
-  //   m = arr_to_mat(ssbo.mat)
-  std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> arr_to_mat;
-  ctx.ReplaceAll(
-      [&](const ast::MemberAccessorExpression* expr) -> const ast::Expression* {
-        if (auto* access = ctx.src->Sem().Get<sem::StructMemberAccess>(expr)) {
-          auto it = decomposed.find(access->Member()->Declaration());
-          if (it == decomposed.end()) {
-            return nullptr;
-          }
-          MatrixInfo info = it->second;
-          auto fn = utils::GetOrCreate(arr_to_mat, info, [&] {
-            auto name = ctx.dst->Symbols().New(
-                "arr_to_mat" + std::to_string(info.matrix->columns()) + "x" +
-                std::to_string(info.matrix->rows()) + "_stride_" +
-                std::to_string(info.stride));
-
-            auto matrix = [&] { return CreateASTTypeFor(ctx, info.matrix); };
-            auto array = [&] { return info.array(ctx.dst); };
-
-            auto arr = ctx.dst->Sym("arr");
-            ast::ExpressionList columns(info.matrix->columns());
-            for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size());
-                 i++) {
-              columns[i] = ctx.dst->IndexAccessor(arr, i);
-            }
-            ctx.dst->Func(
-                name,
-                {
-                    ctx.dst->Param(arr, array()),
-                },
-                matrix(),
-                {
-                    ctx.dst->Return(ctx.dst->Construct(matrix(), columns)),
-                });
-            return name;
-          });
-          return ctx.dst->Call(fn, ctx.CloneWithoutTransform(expr));
-        }
-        return nullptr;
-      });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/decompose_strided_matrix.h b/src/transform/decompose_strided_matrix.h
deleted file mode 100644
index ea7b25d..0000000
--- a/src/transform/decompose_strided_matrix.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
-#define SRC_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// DecomposeStridedMatrix transforms replaces matrix members of storage or
-/// uniform buffer structures, that have a [[stride]] attribute, into an array
-/// of N column vectors.
-/// This transform is used by the SPIR-V reader to handle the SPIR-V
-/// MatrixStride attribute.
-///
-/// @note Depends on the following transforms to have been run first:
-/// * SimplifyPointers
-class DecomposeStridedMatrix
-    : public Castable<DecomposeStridedMatrix, Transform> {
- public:
-  /// Constructor
-  DecomposeStridedMatrix();
-
-  /// Destructor
-  ~DecomposeStridedMatrix() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_DECOMPOSE_STRIDED_MATRIX_H_
diff --git a/src/transform/decompose_strided_matrix_test.cc b/src/transform/decompose_strided_matrix_test.cc
deleted file mode 100644
index 7004e0d..0000000
--- a/src/transform/decompose_strided_matrix_test.cc
+++ /dev/null
@@ -1,671 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/decompose_strided_matrix.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/program_builder.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using DecomposeStridedMatrixTest = TransformTest;
-using f32 = ProgramBuilder::f32;
-
-TEST_F(DecomposeStridedMatrixTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<DecomposeStridedMatrix>(src));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ShouldRunNonStridedMatrox) {
-  auto* src = R"(
-var<private> m : mat3x2<f32>;
-)";
-
-  EXPECT_FALSE(ShouldRun<DecomposeStridedMatrix>(src));
-}
-
-TEST_F(DecomposeStridedMatrixTest, Empty) {
-  auto* src = R"()";
-  auto* expect = src;
-
-  auto got = Run<DecomposeStridedMatrix>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadUniformMatrix) {
-  // struct S {
-  //   @offset(16) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<uniform> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : mat2x2<f32> = s.m;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(16),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
-           b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct S {
-  @size(16)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<uniform> s : S;
-
-fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
-  return mat2x2<f32>(arr[0u], arr[1u]);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : mat2x2<f32> = arr_to_mat2x2_stride_32(s.m);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadUniformColumn) {
-  // struct S {
-  //   @offset(16) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<uniform> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : vec2<f32> = s.m[1];
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(16),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
-           b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("x", b.ty.vec2<f32>(),
-                            b.IndexAccessor(b.MemberAccessor("s", "m"), 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  @size(16)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<uniform> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : vec2<f32> = s.m[1];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadUniformMatrix_DefaultStride) {
-  // struct S {
-  //   @offset(16) @stride(8)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<uniform> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : mat2x2<f32> = s.m;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(16),
-                  b.create<ast::StrideAttribute>(8),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform,
-           b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct S {
-  @size(16)
-  padding : u32;
-  @stride(8) @internal(disable_validation__ignore_stride)
-  m : mat2x2<f32>;
-}
-
-@group(0) @binding(0) var<uniform> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : mat2x2<f32> = s.m;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadStorageMatrix) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : mat2x2<f32> = s.m;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
-  return mat2x2<f32>(arr[0u], arr[1u]);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : mat2x2<f32> = arr_to_mat2x2_stride_32(s.m);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadStorageColumn) {
-  // struct S {
-  //   @offset(16) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : vec2<f32> = s.m[1];
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(16),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Decl(b.Const("x", b.ty.vec2<f32>(),
-                            b.IndexAccessor(b.MemberAccessor("s", "m"), 1))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  @size(16)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : vec2<f32> = s.m[1];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, WriteStorageMatrix) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Assign(b.MemberAccessor("s", "m"),
-                      b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
-                                    b.vec2<f32>(3.0f, 4.0f))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-fn mat2x2_stride_32_to_arr(m : mat2x2<f32>) -> @stride(32) array<vec2<f32>, 2u> {
-  return @stride(32) array<vec2<f32>, 2u>(m[0u], m[1u]);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, WriteStorageColumn) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   s.m[1] = vec2<f32>(1.0, 2.0);
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Assign(b.IndexAccessor(b.MemberAccessor("s", "m"), 1),
-                      b.vec2<f32>(1.0f, 2.0f)),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  s.m[1] = vec2<f32>(1.0, 2.0);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadWriteViaPointerLets) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let a = &s.m;
-  //   let b = &*&*(a);
-  //   let x = *b;
-  //   let y = (*b)[1];
-  //   let z = x[1];
-  //   (*b) = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
-  //   (*b)[1] = vec2<f32>(5.0, 6.0);
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage,
-           ast::Access::kReadWrite, b.GroupAndBinding(0, 0));
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(
-              b.Const("a", nullptr, b.AddressOf(b.MemberAccessor("s", "m")))),
-          b.Decl(b.Const("b", nullptr,
-                         b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
-          b.Decl(b.Const("x", nullptr, b.Deref("b"))),
-          b.Decl(b.Const("y", nullptr, b.IndexAccessor(b.Deref("b"), 1))),
-          b.Decl(b.Const("z", nullptr, b.IndexAccessor("x", 1))),
-          b.Assign(b.Deref("b"), b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
-                                               b.vec2<f32>(3.0f, 4.0f))),
-          b.Assign(b.IndexAccessor(b.Deref("b"), 1), b.vec2<f32>(5.0f, 6.0f)),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  m : @stride(32) array<vec2<f32>, 2u>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-fn arr_to_mat2x2_stride_32(arr : @stride(32) array<vec2<f32>, 2u>) -> mat2x2<f32> {
-  return mat2x2<f32>(arr[0u], arr[1u]);
-}
-
-fn mat2x2_stride_32_to_arr(m : mat2x2<f32>) -> @stride(32) array<vec2<f32>, 2u> {
-  return @stride(32) array<vec2<f32>, 2u>(m[0u], m[1u]);
-}
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x = arr_to_mat2x2_stride_32(s.m);
-  let y = s.m[1];
-  let z = x[1];
-  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
-  s.m[1] = vec2<f32>(5.0, 6.0);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, ReadPrivateMatrix) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // var<private> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   let x : mat2x2<f32> = s.m;
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kPrivate);
-  b.Func(
-      "f", {}, b.ty.void_(),
-      {
-          b.Decl(b.Const("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
-      },
-      {
-          b.Stage(ast::PipelineStage::kCompute),
-          b.WorkgroupSize(1),
-      });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  @stride(32) @internal(disable_validation__ignore_stride)
-  m : mat2x2<f32>;
-}
-
-var<private> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  let x : mat2x2<f32> = s.m;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(DecomposeStridedMatrixTest, WritePrivateMatrix) {
-  // struct S {
-  //   @offset(8) @stride(32)
-  //   @internal(ignore_stride_attribute)
-  //   m : mat2x2<f32>;
-  // };
-  // var<private> s : S;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn f() {
-  //   s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
-  // }
-  ProgramBuilder b;
-  auto* S = b.Structure(
-      "S",
-      {
-          b.Member(
-              "m", b.ty.mat2x2<f32>(),
-              {
-                  b.create<ast::StructMemberOffsetAttribute>(8),
-                  b.create<ast::StrideAttribute>(32),
-                  b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
-              }),
-      });
-  b.Global("s", b.ty.Of(S), ast::StorageClass::kPrivate);
-  b.Func("f", {}, b.ty.void_(),
-         {
-             b.Assign(b.MemberAccessor("s", "m"),
-                      b.mat2x2<f32>(b.vec2<f32>(1.0f, 2.0f),
-                                    b.vec2<f32>(3.0f, 4.0f))),
-         },
-         {
-             b.Stage(ast::PipelineStage::kCompute),
-             b.WorkgroupSize(1),
-         });
-
-  auto* expect = R"(
-struct S {
-  @size(8)
-  padding : u32;
-  @stride(32) @internal(disable_validation__ignore_stride)
-  m : mat2x2<f32>;
-}
-
-var<private> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedMatrix>(
-      Program(std::move(b)));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/external_texture_transform.cc b/src/transform/external_texture_transform.cc
deleted file mode 100644
index b597313..0000000
--- a/src/transform/external_texture_transform.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/external_texture_transform.h"
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ExternalTextureTransform);
-
-namespace tint {
-namespace transform {
-
-ExternalTextureTransform::ExternalTextureTransform() = default;
-ExternalTextureTransform::~ExternalTextureTransform() = default;
-
-void ExternalTextureTransform::Run(CloneContext& ctx,
-                                   const DataMap&,
-                                   DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  // Within this transform, usages of texture_external are replaced with a
-  // texture_2d<f32>, which will allow us perform operations on a
-  // texture_external without maintaining texture_external-specific code
-  // generation paths in the backends.
-
-  // When replacing instances of texture_external with texture_2d<f32> we must
-  // also modify calls to the texture_external overloads of textureLoad and
-  // textureSampleLevel, which unlike their texture_2d<f32> overloads do not
-  // require a level parameter. To do this we identify calls to textureLoad and
-  // textureSampleLevel that use texture_external as the first parameter and add
-  // a parameter for the level (which is always 0).
-
-  // Scan the AST nodes for calls to textureLoad or textureSampleLevel.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* call_expr = node->As<ast::CallExpression>()) {
-      if (auto* builtin = sem.Get(call_expr)->Target()->As<sem::Builtin>()) {
-        if (builtin->Type() == sem::BuiltinType::kTextureLoad ||
-            builtin->Type() == sem::BuiltinType::kTextureSampleLevel) {
-          // When a textureLoad or textureSampleLevel has been identified, check
-          // if the first parameter is an external texture.
-          if (auto* var =
-                  sem.Get(call_expr->args[0])->As<sem::VariableUser>()) {
-            if (var->Variable()
-                    ->Type()
-                    ->UnwrapRef()
-                    ->Is<sem::ExternalTexture>()) {
-              if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
-                  call_expr->args.size() != 2) {
-                TINT_ICE(Transform, ctx.dst->Diagnostics())
-                    << "expected textureLoad call with a texture_external to "
-                       "have 2 parameters, found "
-                    << call_expr->args.size() << " parameters";
-              }
-
-              if (builtin->Type() == sem::BuiltinType::kTextureSampleLevel &&
-                  call_expr->args.size() != 3) {
-                TINT_ICE(Transform, ctx.dst->Diagnostics())
-                    << "expected textureSampleLevel call with a "
-                       "texture_external to have 3 parameters, found "
-                    << call_expr->args.size() << " parameters";
-              }
-
-              // Replace the call with another that has the same parameters in
-              // addition to a level parameter (always zero for external
-              // textures).
-              auto* exp = ctx.Clone(call_expr->target.name);
-              auto* externalTextureParam = ctx.Clone(call_expr->args[0]);
-
-              ast::ExpressionList params;
-              if (builtin->Type() == sem::BuiltinType::kTextureLoad) {
-                auto* coordsParam = ctx.Clone(call_expr->args[1]);
-                auto* levelParam = ctx.dst->Expr(0);
-                params = {externalTextureParam, coordsParam, levelParam};
-              } else if (builtin->Type() ==
-                         sem::BuiltinType::kTextureSampleLevel) {
-                auto* samplerParam = ctx.Clone(call_expr->args[1]);
-                auto* coordsParam = ctx.Clone(call_expr->args[2]);
-                auto* levelParam = ctx.dst->Expr(0.0f);
-                params = {externalTextureParam, samplerParam, coordsParam,
-                          levelParam};
-              }
-
-              auto* newCall = ctx.dst->create<ast::CallExpression>(exp, params);
-              ctx.Replace(call_expr, newCall);
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // Scan the AST nodes for external texture declarations.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* var = node->As<ast::Variable>()) {
-      if (::tint::Is<ast::ExternalTexture>(var->type)) {
-        // Replace a single-plane external texture with a 2D, f32 sampled
-        // texture.
-        auto* newType = ctx.dst->ty.sampled_texture(ast::TextureDimension::k2d,
-                                                    ctx.dst->ty.f32());
-        auto clonedSrc = ctx.Clone(var->source);
-        auto clonedSym = ctx.Clone(var->symbol);
-        auto* clonedConstructor = ctx.Clone(var->constructor);
-        auto clonedAttributes = ctx.Clone(var->attributes);
-        auto* newVar = ctx.dst->create<ast::Variable>(
-            clonedSrc, clonedSym, var->declared_storage_class,
-            var->declared_access, newType, false, false, clonedConstructor,
-            clonedAttributes);
-
-        ctx.Replace(var, newVar);
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/external_texture_transform.h b/src/transform/external_texture_transform.h
deleted file mode 100644
index 4e70bc7..0000000
--- a/src/transform/external_texture_transform.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
-#define SRC_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
-
-#include <utility>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Because an external texture is comprised of 1-3 texture views we can simply
-/// transform external textures into the appropriate number of sampled textures.
-/// This allows us to share SPIR-V/HLSL writer paths for sampled textures
-/// instead of adding dedicated writer paths for external textures.
-/// ExternalTextureTransform performs this transformation.
-class ExternalTextureTransform
-    : public Castable<ExternalTextureTransform, Transform> {
- public:
-  /// Constructor
-  ExternalTextureTransform();
-  /// Destructor
-  ~ExternalTextureTransform() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_EXTERNAL_TEXTURE_TRANSFORM_H_
diff --git a/src/transform/external_texture_transform_test.cc b/src/transform/external_texture_transform_test.cc
deleted file mode 100644
index 0c41717..0000000
--- a/src/transform/external_texture_transform_test.cc
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/external_texture_transform.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ExternalTextureTransformTest = TransformTest;
-
-TEST_F(ExternalTextureTransformTest, SampleLevelSinglePlane) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var t : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)));
-}
-)";
-
-  auto* expect = R"(
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var t : texture_2d<f32>;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)), 0.0);
-}
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ExternalTextureTransformTest, SampleLevelSinglePlane_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)));
-}
-
-@group(0) @binding(1) var t : texture_external;
-
-@group(0) @binding(0) var s : sampler;
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(t, s, (coord.xy / vec2<f32>(4.0, 4.0)), 0.0);
-}
-
-@group(0) @binding(1) var t : texture_2d<f32>;
-
-@group(0) @binding(0) var s : sampler;
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ExternalTextureTransformTest, LoadSinglePlane) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(t, vec2<i32>(1, 1));
-}
-)";
-
-  auto* expect = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(t, vec2<i32>(1, 1), 0);
-}
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ExternalTextureTransformTest, LoadSinglePlane_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(t, vec2<i32>(1, 1));
-}
-
-@group(0) @binding(0) var t : texture_external;
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(t, vec2<i32>(1, 1), 0);
-}
-
-@group(0) @binding(0) var t : texture_2d<f32>;
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ExternalTextureTransformTest, DimensionsSinglePlane) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(t);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-)";
-
-  auto* expect = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(t);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ExternalTextureTransformTest, DimensionsSinglePlane_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(t);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-
-@group(0) @binding(0) var t : texture_external;
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(t);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-
-@group(0) @binding(0) var t : texture_2d<f32>;
-)";
-
-  auto got = Run<ExternalTextureTransform>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
deleted file mode 100644
index e6a069d..0000000
--- a/src/transform/first_index_offset.cc
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2020 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/transform/first_index_offset.h"
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/struct.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset::BindingPoint);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset::Data);
-
-namespace tint {
-namespace transform {
-namespace {
-
-// Uniform buffer member names
-constexpr char kFirstVertexName[] = "first_vertex_index";
-constexpr char kFirstInstanceName[] = "first_instance_index";
-
-}  // namespace
-
-FirstIndexOffset::BindingPoint::BindingPoint() = default;
-FirstIndexOffset::BindingPoint::BindingPoint(uint32_t b, uint32_t g)
-    : binding(b), group(g) {}
-FirstIndexOffset::BindingPoint::~BindingPoint() = default;
-
-FirstIndexOffset::Data::Data(bool has_vtx_index,
-                             bool has_inst_index,
-                             uint32_t first_vtx_offset,
-                             uint32_t first_inst_offset)
-    : has_vertex_index(has_vtx_index),
-      has_instance_index(has_inst_index),
-      first_vertex_offset(first_vtx_offset),
-      first_instance_offset(first_inst_offset) {}
-FirstIndexOffset::Data::Data(const Data&) = default;
-FirstIndexOffset::Data::~Data() = default;
-
-FirstIndexOffset::FirstIndexOffset() = default;
-FirstIndexOffset::~FirstIndexOffset() = default;
-
-bool FirstIndexOffset::ShouldRun(const Program* program, const DataMap&) const {
-  for (auto* fn : program->AST().Functions()) {
-    if (fn->PipelineStage() == ast::PipelineStage::kVertex) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void FirstIndexOffset::Run(CloneContext& ctx,
-                           const DataMap& inputs,
-                           DataMap& outputs) const {
-  // Get the uniform buffer binding point
-  uint32_t ub_binding = binding_;
-  uint32_t ub_group = group_;
-  if (auto* binding_point = inputs.Get<BindingPoint>()) {
-    ub_binding = binding_point->binding;
-    ub_group = binding_point->group;
-  }
-
-  // Map of builtin usages
-  std::unordered_map<const sem::Variable*, const char*> builtin_vars;
-  std::unordered_map<const sem::StructMember*, const char*> builtin_members;
-
-  bool has_vertex_index = false;
-  bool has_instance_index = false;
-
-  // Traverse the AST scanning for builtin accesses via variables (includes
-  // parameters) or structure member accesses.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* var = node->As<ast::Variable>()) {
-      for (auto* attr : var->attributes) {
-        if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
-          ast::Builtin builtin = builtin_attr->builtin;
-          if (builtin == ast::Builtin::kVertexIndex) {
-            auto* sem_var = ctx.src->Sem().Get(var);
-            builtin_vars.emplace(sem_var, kFirstVertexName);
-            has_vertex_index = true;
-          }
-          if (builtin == ast::Builtin::kInstanceIndex) {
-            auto* sem_var = ctx.src->Sem().Get(var);
-            builtin_vars.emplace(sem_var, kFirstInstanceName);
-            has_instance_index = true;
-          }
-        }
-      }
-    }
-    if (auto* member = node->As<ast::StructMember>()) {
-      for (auto* attr : member->attributes) {
-        if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
-          ast::Builtin builtin = builtin_attr->builtin;
-          if (builtin == ast::Builtin::kVertexIndex) {
-            auto* sem_mem = ctx.src->Sem().Get(member);
-            builtin_members.emplace(sem_mem, kFirstVertexName);
-            has_vertex_index = true;
-          }
-          if (builtin == ast::Builtin::kInstanceIndex) {
-            auto* sem_mem = ctx.src->Sem().Get(member);
-            builtin_members.emplace(sem_mem, kFirstInstanceName);
-            has_instance_index = true;
-          }
-        }
-      }
-    }
-  }
-
-  // Byte offsets on the uniform buffer
-  uint32_t vertex_index_offset = 0;
-  uint32_t instance_index_offset = 0;
-
-  if (has_vertex_index || has_instance_index) {
-    // Add uniform buffer members and calculate byte offsets
-    uint32_t offset = 0;
-    ast::StructMemberList members;
-    if (has_vertex_index) {
-      members.push_back(ctx.dst->Member(kFirstVertexName, ctx.dst->ty.u32()));
-      vertex_index_offset = offset;
-      offset += 4;
-    }
-    if (has_instance_index) {
-      members.push_back(ctx.dst->Member(kFirstInstanceName, ctx.dst->ty.u32()));
-      instance_index_offset = offset;
-      offset += 4;
-    }
-    auto* struct_ = ctx.dst->Structure(ctx.dst->Sym(), std::move(members));
-
-    // Create a global to hold the uniform buffer
-    Symbol buffer_name = ctx.dst->Sym();
-    ctx.dst->Global(buffer_name, ctx.dst->ty.Of(struct_),
-                    ast::StorageClass::kUniform, nullptr,
-                    ast::AttributeList{
-                        ctx.dst->create<ast::BindingAttribute>(ub_binding),
-                        ctx.dst->create<ast::GroupAttribute>(ub_group),
-                    });
-
-    // Fix up all references to the builtins with the offsets
-    ctx.ReplaceAll(
-        [=, &ctx](const ast::Expression* expr) -> const ast::Expression* {
-          if (auto* sem = ctx.src->Sem().Get(expr)) {
-            if (auto* user = sem->As<sem::VariableUser>()) {
-              auto it = builtin_vars.find(user->Variable());
-              if (it != builtin_vars.end()) {
-                return ctx.dst->Add(
-                    ctx.CloneWithoutTransform(expr),
-                    ctx.dst->MemberAccessor(buffer_name, it->second));
-              }
-            }
-            if (auto* access = sem->As<sem::StructMemberAccess>()) {
-              auto it = builtin_members.find(access->Member());
-              if (it != builtin_members.end()) {
-                return ctx.dst->Add(
-                    ctx.CloneWithoutTransform(expr),
-                    ctx.dst->MemberAccessor(buffer_name, it->second));
-              }
-            }
-          }
-          // Not interested in this experssion. Just clone.
-          return nullptr;
-        });
-  }
-
-  ctx.Clone();
-
-  outputs.Add<Data>(has_vertex_index, has_instance_index, vertex_index_offset,
-                    instance_index_offset);
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/first_index_offset.h b/src/transform/first_index_offset.h
deleted file mode 100644
index 23e4be0..0000000
--- a/src/transform/first_index_offset.h
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2020 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_TRANSFORM_FIRST_INDEX_OFFSET_H_
-#define SRC_TRANSFORM_FIRST_INDEX_OFFSET_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Adds firstVertex/Instance (injected via root constants) to
-/// vertex/instance index builtins.
-///
-/// This transform assumes that Name transform has been run before.
-///
-/// Unlike other APIs, D3D always starts vertex and instance numbering at 0,
-/// regardless of the firstVertex/Instance value specified. This transformer
-/// adds the value of firstVertex/Instance to each builtin. This action is
-/// performed by adding a new constant equal to original builtin +
-/// firstVertex/Instance to each function that references one of these builtins.
-///
-/// Note that D3D does not have any semantics for firstVertex/Instance.
-/// Therefore, these values must by passed to the shader.
-///
-/// Before:
-/// ```
-///   @builtin(vertex_index) var<in> vert_idx : u32;
-///   fn func() -> u32 {
-///     return vert_idx;
-///   }
-/// ```
-///
-/// After:
-/// ```
-///   struct TintFirstIndexOffsetData {
-///     tint_first_vertex_index : u32;
-///     tint_first_instance_index : u32;
-///   };
-///   @builtin(vertex_index) var<in> tint_first_index_offset_vert_idx : u32;
-///   @binding(N) @group(M) var<uniform> tint_first_index_data :
-///                                                    TintFirstIndexOffsetData;
-///   fn func() -> u32 {
-///     const vert_idx = (tint_first_index_offset_vert_idx +
-///                       tint_first_index_data.tint_first_vertex_index);
-///     return vert_idx;
-///   }
-/// ```
-///
-class FirstIndexOffset : public Castable<FirstIndexOffset, Transform> {
- public:
-  /// BindingPoint is consumed by the FirstIndexOffset transform.
-  /// BindingPoint specifies the binding point of the first index uniform
-  /// buffer.
-  struct BindingPoint : public Castable<BindingPoint, transform::Data> {
-    /// Constructor
-    BindingPoint();
-
-    /// Constructor
-    /// @param b the binding index
-    /// @param g the binding group
-    BindingPoint(uint32_t b, uint32_t g);
-
-    /// Destructor
-    ~BindingPoint() override;
-
-    /// `@binding()` for the first vertex / first instance uniform buffer
-    uint32_t binding = 0;
-    /// `@group()` for the first vertex / first instance uniform buffer
-    uint32_t group = 0;
-  };
-
-  /// Data is outputted by the FirstIndexOffset transform.
-  /// Data holds information about shader usage and constant buffer offsets.
-  struct Data : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param has_vtx_index True if the shader uses vertex_index
-    /// @param has_inst_index True if the shader uses instance_index
-    /// @param first_vtx_offset Offset of first vertex into constant buffer
-    /// @param first_inst_offset Offset of first instance into constant buffer
-    Data(bool has_vtx_index,
-         bool has_inst_index,
-         uint32_t first_vtx_offset,
-         uint32_t first_inst_offset);
-
-    /// Copy constructor
-    Data(const Data&);
-
-    /// Destructor
-    ~Data() override;
-
-    /// True if the shader uses vertex_index
-    const bool has_vertex_index;
-    /// True if the shader uses instance_index
-    const bool has_instance_index;
-    /// Offset of first vertex into constant buffer
-    const uint32_t first_vertex_offset;
-    /// Offset of first instance into constant buffer
-    const uint32_t first_instance_offset;
-  };
-
-  /// Constructor
-  FirstIndexOffset();
-  /// Destructor
-  ~FirstIndexOffset() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  uint32_t binding_ = 0;
-  uint32_t group_ = 0;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_FIRST_INDEX_OFFSET_H_
diff --git a/src/transform/first_index_offset_test.cc b/src/transform/first_index_offset_test.cc
deleted file mode 100644
index 61075a6..0000000
--- a/src/transform/first_index_offset_test.cc
+++ /dev/null
@@ -1,650 +0,0 @@
-// Copyright 2020 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/transform/first_index_offset.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using FirstIndexOffsetTest = TransformTest;
-
-TEST_F(FirstIndexOffsetTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<FirstIndexOffset>(src));
-}
-
-TEST_F(FirstIndexOffsetTest, ShouldRunFragmentStage) {
-  auto* src = R"(
-[[stage(fragment)]]
-fn entry() {
-  return;
-}
-)";
-
-  EXPECT_FALSE(ShouldRun<FirstIndexOffset>(src));
-}
-
-TEST_F(FirstIndexOffsetTest, ShouldRunVertexStage) {
-  auto* src = R"(
-[[stage(vertex)]]
-fn entry() -> [[builtin(position)]] vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<FirstIndexOffset>(src));
-}
-
-TEST_F(FirstIndexOffsetTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(0, 0);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  EXPECT_EQ(data, nullptr);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicVertexShader) {
-  auto* src = R"(
-@stage(vertex)
-fn entry() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-  auto* expect = src;
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(0, 0);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, false);
-  EXPECT_EQ(data->has_instance_index, false);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleVertexIndex) {
-  auto* src = R"(
-fn test(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  test(vert_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-fn test(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  test((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, false);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleVertexIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  test(vert_idx);
-  return vec4<f32>();
-}
-
-fn test(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  test((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-
-fn test(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, false);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleInstanceIndex) {
-  auto* src = R"(
-fn test(inst_idx : u32) -> u32 {
-  return inst_idx;
-}
-
-@stage(vertex)
-fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  test(inst_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_instance_index : u32;
-}
-
-@binding(1) @group(7) var<uniform> tint_symbol_1 : tint_symbol;
-
-fn test(inst_idx : u32) -> u32 {
-  return inst_idx;
-}
-
-@stage(vertex)
-fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  test((inst_idx + tint_symbol_1.first_instance_index));
-  return vec4<f32>();
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 7);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, false);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleInstanceIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  test(inst_idx);
-  return vec4<f32>();
-}
-
-fn test(inst_idx : u32) -> u32 {
-  return inst_idx;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_instance_index : u32;
-}
-
-@binding(1) @group(7) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(vertex)
-fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  test((inst_idx + tint_symbol_1.first_instance_index));
-  return vec4<f32>();
-}
-
-fn test(inst_idx : u32) -> u32 {
-  return inst_idx;
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 7);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, false);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleBothIndex) {
-  auto* src = R"(
-fn test(instance_idx : u32, vert_idx : u32) -> u32 {
-  return instance_idx + vert_idx;
-}
-
-struct Inputs {
-  @builtin(instance_index) instance_idx : u32;
-  @builtin(vertex_index) vert_idx : u32;
-};
-
-@stage(vertex)
-fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  test(inputs.instance_idx, inputs.vert_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-  first_instance_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-fn test(instance_idx : u32, vert_idx : u32) -> u32 {
-  return (instance_idx + vert_idx);
-}
-
-struct Inputs {
-  @builtin(instance_index)
-  instance_idx : u32;
-  @builtin(vertex_index)
-  vert_idx : u32;
-}
-
-@stage(vertex)
-fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  test((inputs.instance_idx + tint_symbol_1.first_instance_index), (inputs.vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 4u);
-}
-
-TEST_F(FirstIndexOffsetTest, BasicModuleBothIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  test(inputs.instance_idx, inputs.vert_idx);
-  return vec4<f32>();
-}
-
-struct Inputs {
-  @builtin(instance_index) instance_idx : u32;
-  @builtin(vertex_index) vert_idx : u32;
-};
-
-fn test(instance_idx : u32, vert_idx : u32) -> u32 {
-  return instance_idx + vert_idx;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-  first_instance_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(vertex)
-fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  test((inputs.instance_idx + tint_symbol_1.first_instance_index), (inputs.vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-
-struct Inputs {
-  @builtin(instance_index)
-  instance_idx : u32;
-  @builtin(vertex_index)
-  vert_idx : u32;
-}
-
-fn test(instance_idx : u32, vert_idx : u32) -> u32 {
-  return (instance_idx + vert_idx);
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 4u);
-}
-
-TEST_F(FirstIndexOffsetTest, NestedCalls) {
-  auto* src = R"(
-fn func1(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-
-fn func2(vert_idx : u32) -> u32 {
-  return func1(vert_idx);
-}
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func2(vert_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-fn func1(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-
-fn func2(vert_idx : u32) -> u32 {
-  return func1(vert_idx);
-}
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func2((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, false);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, NestedCalls_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func2(vert_idx);
-  return vec4<f32>();
-}
-
-fn func2(vert_idx : u32) -> u32 {
-  return func1(vert_idx);
-}
-
-fn func1(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func2((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-
-fn func2(vert_idx : u32) -> u32 {
-  return func1(vert_idx);
-}
-
-fn func1(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, false);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 0u);
-}
-
-TEST_F(FirstIndexOffsetTest, MultipleEntryPoints) {
-  auto* src = R"(
-fn func(i : u32) -> u32 {
-  return i;
-}
-
-@stage(vertex)
-fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func(vert_idx);
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(vert_idx + inst_idx);
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(inst_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-  first_instance_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-fn func(i : u32) -> u32 {
-  return i;
-}
-
-@stage(vertex)
-fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(((vert_idx + tint_symbol_1.first_vertex_index) + (inst_idx + tint_symbol_1.first_instance_index)));
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func((inst_idx + tint_symbol_1.first_instance_index));
-  return vec4<f32>();
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 4u);
-}
-
-TEST_F(FirstIndexOffsetTest, MultipleEntryPoints_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func(vert_idx);
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(vert_idx + inst_idx);
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(inst_idx);
-  return vec4<f32>();
-}
-
-fn func(i : u32) -> u32 {
-  return i;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol {
-  first_vertex_index : u32;
-  first_instance_index : u32;
-}
-
-@binding(1) @group(2) var<uniform> tint_symbol_1 : tint_symbol;
-
-@stage(vertex)
-fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
-  func((vert_idx + tint_symbol_1.first_vertex_index));
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func(((vert_idx + tint_symbol_1.first_vertex_index) + (inst_idx + tint_symbol_1.first_instance_index)));
-  return vec4<f32>();
-}
-
-@stage(vertex)
-fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
-  func((inst_idx + tint_symbol_1.first_instance_index));
-  return vec4<f32>();
-}
-
-fn func(i : u32) -> u32 {
-  return i;
-}
-)";
-
-  DataMap config;
-  config.Add<FirstIndexOffset::BindingPoint>(1, 2);
-  auto got = Run<FirstIndexOffset>(src, std::move(config));
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<FirstIndexOffset::Data>();
-
-  ASSERT_NE(data, nullptr);
-  EXPECT_EQ(data->has_vertex_index, true);
-  EXPECT_EQ(data->has_instance_index, true);
-  EXPECT_EQ(data->first_vertex_offset, 0u);
-  EXPECT_EQ(data->first_instance_offset, 4u);
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/fold_constants.cc b/src/transform/fold_constants.cc
deleted file mode 100644
index 8ef513c..0000000
--- a/src/transform/fold_constants.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/fold_constants.h"
-
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldConstants);
-
-namespace tint {
-namespace transform {
-
-FoldConstants::FoldConstants() = default;
-
-FoldConstants::~FoldConstants() = default;
-
-void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  ctx.ReplaceAll([&](const ast::Expression* expr) -> const ast::Expression* {
-    auto* call = ctx.src->Sem().Get<sem::Call>(expr);
-    if (!call) {
-      return nullptr;
-    }
-
-    auto value = call->ConstantValue();
-    if (!value.IsValid()) {
-      return nullptr;
-    }
-
-    auto* ty = call->Type();
-
-    if (!call->Target()->IsAnyOf<sem::TypeConversion, sem::TypeConstructor>()) {
-      return nullptr;
-    }
-
-    // If original ctor expression had no init values, don't replace the
-    // expression
-    if (call->Arguments().empty()) {
-      return nullptr;
-    }
-
-    if (auto* vec = ty->As<sem::Vector>()) {
-      uint32_t vec_size = static_cast<uint32_t>(vec->Width());
-
-      // We'd like to construct the new vector with the same number of
-      // constructor args that the original node had, but after folding
-      // constants, cases like the following are problematic:
-      //
-      // vec3<f32> = vec3<f32>(vec2<f32>, 1.0) // vec_size=3, ctor_size=2
-      //
-      // In this case, creating a vec3 with 2 args is invalid, so we should
-      // create it with 3. So what we do is construct with vec_size args,
-      // except if the original vector was single-value initialized, in
-      // which case, we only construct with one arg again.
-      uint32_t ctor_size = (call->Arguments().size() == 1) ? 1 : vec_size;
-
-      ast::ExpressionList ctors;
-      for (uint32_t i = 0; i < ctor_size; ++i) {
-        value.WithScalarAt(
-            i, [&](auto&& s) { ctors.emplace_back(ctx.dst->Expr(s)); });
-      }
-
-      auto* el_ty = CreateASTTypeFor(ctx, vec->type());
-      return ctx.dst->vec(el_ty, vec_size, ctors);
-    }
-
-    if (ty->is_scalar()) {
-      return value.WithScalarAt(0,
-                                [&](auto&& s) -> const ast::LiteralExpression* {
-                                  return ctx.dst->Expr(s);
-                                });
-    }
-
-    return nullptr;
-  });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/fold_constants.h b/src/transform/fold_constants.h
deleted file mode 100644
index 87d8522..0000000
--- a/src/transform/fold_constants.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_FOLD_CONSTANTS_H_
-#define SRC_TRANSFORM_FOLD_CONSTANTS_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// FoldConstants transforms the AST by folding constant expressions
-class FoldConstants : public Castable<FoldConstants, Transform> {
- public:
-  /// Constructor
-  FoldConstants();
-
-  /// Destructor
-  ~FoldConstants() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_FOLD_CONSTANTS_H_
diff --git a/src/transform/fold_constants_test.cc b/src/transform/fold_constants_test.cc
deleted file mode 100644
index 5736c89..0000000
--- a/src/transform/fold_constants_test.cc
+++ /dev/null
@@ -1,427 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/fold_constants.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using FoldConstantsTest = TransformTest;
-
-TEST_F(FoldConstantsTest, Module_Scalar_NoConversion) {
-  auto* src = R"(
-var<private> a : i32 = i32(123);
-var<private> b : u32 = u32(123u);
-var<private> c : f32 = f32(123.0);
-var<private> d : bool = bool(true);
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : i32 = 123;
-
-var<private> b : u32 = 123u;
-
-var<private> c : f32 = 123.0;
-
-var<private> d : bool = true;
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Scalar_Conversion) {
-  auto* src = R"(
-var<private> a : i32 = i32(123.0);
-var<private> b : u32 = u32(123);
-var<private> c : f32 = f32(123u);
-var<private> d : bool = bool(123);
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : i32 = 123;
-
-var<private> b : u32 = 123u;
-
-var<private> c : f32 = 123.0;
-
-var<private> d : bool = true;
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Scalar_MultipleConversions) {
-  auto* src = R"(
-var<private> a : i32 = i32(u32(f32(u32(i32(123.0)))));
-var<private> b : u32 = u32(i32(f32(i32(u32(123)))));
-var<private> c : f32 = f32(u32(i32(u32(f32(123u)))));
-var<private> d : bool = bool(i32(f32(i32(u32(123)))));
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : i32 = 123;
-
-var<private> b : u32 = 123u;
-
-var<private> c : f32 = 123.0;
-
-var<private> d : bool = true;
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Vector_NoConversion) {
-  auto* src = R"(
-var<private> a : vec3<i32> = vec3<i32>(123);
-var<private> b : vec3<u32> = vec3<u32>(123u);
-var<private> c : vec3<f32> = vec3<f32>(123.0);
-var<private> d : vec3<bool> = vec3<bool>(true);
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<i32> = vec3<i32>(123);
-
-var<private> b : vec3<u32> = vec3<u32>(123u);
-
-var<private> c : vec3<f32> = vec3<f32>(123.0);
-
-var<private> d : vec3<bool> = vec3<bool>(true);
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Vector_Conversion) {
-  auto* src = R"(
-var<private> a : vec3<i32> = vec3<i32>(vec3<f32>(123.0));
-var<private> b : vec3<u32> = vec3<u32>(vec3<i32>(123));
-var<private> c : vec3<f32> = vec3<f32>(vec3<u32>(123u));
-var<private> d : vec3<bool> = vec3<bool>(vec3<i32>(123));
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<i32> = vec3<i32>(123);
-
-var<private> b : vec3<u32> = vec3<u32>(123u);
-
-var<private> c : vec3<f32> = vec3<f32>(123.0);
-
-var<private> d : vec3<bool> = vec3<bool>(true);
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Vector_MultipleConversions) {
-  auto* src = R"(
-var<private> a : vec3<i32> = vec3<i32>(vec3<u32>(vec3<f32>(vec3<u32>(u32(123.0)))));
-var<private> b : vec3<u32> = vec3<u32>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
-var<private> c : vec3<f32> = vec3<f32>(vec3<u32>(vec3<i32>(vec3<u32>(u32(123u)))));
-var<private> d : vec3<bool> = vec3<bool>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<i32> = vec3<i32>(123);
-
-var<private> b : vec3<u32> = vec3<u32>(123u);
-
-var<private> c : vec3<f32> = vec3<f32>(123.0);
-
-var<private> d : vec3<bool> = vec3<bool>(true);
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Module_Vector_MixedSizeConversions) {
-  auto* src = R"(
-var<private> a : vec4<i32> = vec4<i32>(vec3<i32>(vec3<u32>(1u, 2u, 3u)), 4);
-var<private> b : vec4<i32> = vec4<i32>(vec2<i32>(vec2<u32>(1u, 2u)), vec2<i32>(4, 5));
-var<private> c : vec4<i32> = vec4<i32>(1, vec2<i32>(vec2<f32>(2.0, 3.0)), 4);
-var<private> d : vec4<i32> = vec4<i32>(1, 2, vec2<i32>(vec2<f32>(3.0, 4.0)));
-var<private> e : vec4<bool> = vec4<bool>(false, bool(f32(1.0)), vec2<bool>(vec2<i32>(0, i32(4u))));
-
-fn f() {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-
-var<private> b : vec4<i32> = vec4<i32>(1, 2, 4, 5);
-
-var<private> c : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-
-var<private> d : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-
-var<private> e : vec4<bool> = vec4<bool>(false, true, false, true);
-
-fn f() {
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Scalar_NoConversion) {
-  auto* src = R"(
-fn f() {
-  var a : i32 = i32(123);
-  var b : u32 = u32(123u);
-  var c : f32 = f32(123.0);
-  var d : bool = bool(true);
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : i32 = 123;
-  var b : u32 = 123u;
-  var c : f32 = 123.0;
-  var d : bool = true;
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Scalar_Conversion) {
-  auto* src = R"(
-fn f() {
-  var a : i32 = i32(123.0);
-  var b : u32 = u32(123);
-  var c : f32 = f32(123u);
-  var d : bool = bool(123);
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : i32 = 123;
-  var b : u32 = 123u;
-  var c : f32 = 123.0;
-  var d : bool = true;
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Scalar_MultipleConversions) {
-  auto* src = R"(
-fn f() {
-  var a : i32 = i32(u32(f32(u32(i32(123.0)))));
-  var b : u32 = u32(i32(f32(i32(u32(123)))));
-  var c : f32 = f32(u32(i32(u32(f32(123u)))));
-  var d : bool = bool(i32(f32(i32(u32(123)))));
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : i32 = 123;
-  var b : u32 = 123u;
-  var c : f32 = 123.0;
-  var d : bool = true;
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Vector_NoConversion) {
-  auto* src = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(123);
-  var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
-  var d : vec3<bool> = vec3<bool>(true);
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(123);
-  var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
-  var d : vec3<bool> = vec3<bool>(true);
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Vector_Conversion) {
-  auto* src = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(vec3<f32>(123.0));
-  var b : vec3<u32> = vec3<u32>(vec3<i32>(123));
-  var c : vec3<f32> = vec3<f32>(vec3<u32>(123u));
-  var d : vec3<bool> = vec3<bool>(vec3<i32>(123));
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(123);
-  var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
-  var d : vec3<bool> = vec3<bool>(true);
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Vector_MultipleConversions) {
-  auto* src = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(vec3<u32>(vec3<f32>(vec3<u32>(u32(123.0)))));
-  var b : vec3<u32> = vec3<u32>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
-  var c : vec3<f32> = vec3<f32>(vec3<u32>(vec3<i32>(vec3<u32>(u32(123u)))));
-  var d : vec3<bool> = vec3<bool>(vec3<i32>(vec3<f32>(vec3<i32>(i32(123)))));
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : vec3<i32> = vec3<i32>(123);
-  var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
-  var d : vec3<bool> = vec3<bool>(true);
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Vector_MixedSizeConversions) {
-  auto* src = R"(
-fn f() {
-  var a : vec4<i32> = vec4<i32>(vec3<i32>(vec3<u32>(1u, 2u, 3u)), 4);
-  var b : vec4<i32> = vec4<i32>(vec2<i32>(vec2<u32>(1u, 2u)), vec2<i32>(4, 5));
-  var c : vec4<i32> = vec4<i32>(1, vec2<i32>(vec2<f32>(2.0, 3.0)), 4);
-  var d : vec4<i32> = vec4<i32>(1, 2, vec2<i32>(vec2<f32>(3.0, 4.0)));
-  var e : vec4<bool> = vec4<bool>(false, bool(f32(1.0)), vec2<bool>(vec2<i32>(0, i32(4u))));
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-  var b : vec4<i32> = vec4<i32>(1, 2, 4, 5);
-  var c : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-  var d : vec4<i32> = vec4<i32>(1, 2, 3, 4);
-  var e : vec4<bool> = vec4<bool>(false, true, false, true);
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldConstantsTest, Function_Vector_ConstantWithNonConstant) {
-  auto* src = R"(
-fn f() {
-  var a : f32 = f32();
-  var b : vec2<f32> = vec2<f32>(f32(i32(1)), a);
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : f32 = f32();
-  var b : vec2<f32> = vec2<f32>(1.0, a);
-}
-)";
-
-  auto got = Run<FoldConstants>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/fold_trivial_single_use_lets.cc b/src/transform/fold_trivial_single_use_lets.cc
deleted file mode 100644
index a213d5c..0000000
--- a/src/transform/fold_trivial_single_use_lets.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/fold_trivial_single_use_lets.h"
-
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/utils/scoped_assignment.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldTrivialSingleUseLets);
-
-namespace tint {
-namespace transform {
-namespace {
-
-const ast::VariableDeclStatement* AsTrivialLetDecl(const ast::Statement* stmt) {
-  auto* var_decl = stmt->As<ast::VariableDeclStatement>();
-  if (!var_decl) {
-    return nullptr;
-  }
-  auto* var = var_decl->variable;
-  if (!var->is_const) {
-    return nullptr;
-  }
-  auto* ctor = var->constructor;
-  if (!IsAnyOf<ast::IdentifierExpression, ast::LiteralExpression>(ctor)) {
-    return nullptr;
-  }
-  return var_decl;
-}
-
-}  // namespace
-
-FoldTrivialSingleUseLets::FoldTrivialSingleUseLets() = default;
-
-FoldTrivialSingleUseLets::~FoldTrivialSingleUseLets() = default;
-
-void FoldTrivialSingleUseLets::Run(CloneContext& ctx,
-                                   const DataMap&,
-                                   DataMap&) const {
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* block = node->As<ast::BlockStatement>()) {
-      auto& stmts = block->statements;
-      for (size_t stmt_idx = 0; stmt_idx < stmts.size(); stmt_idx++) {
-        auto* stmt = stmts[stmt_idx];
-        if (auto* let_decl = AsTrivialLetDecl(stmt)) {
-          auto* let = let_decl->variable;
-          auto* sem_let = ctx.src->Sem().Get(let);
-          auto& users = sem_let->Users();
-          if (users.size() != 1) {
-            continue;  // Does not have a single user.
-          }
-
-          auto* user = users[0];
-          auto* user_stmt = user->Stmt()->Declaration();
-
-          for (size_t i = stmt_idx; i < stmts.size(); i++) {
-            if (user_stmt == stmts[i]) {
-              auto* user_expr = user->Declaration();
-              ctx.Remove(stmts, let_decl);
-              ctx.Replace(user_expr, ctx.Clone(let->constructor));
-            }
-            if (!AsTrivialLetDecl(stmts[i])) {
-              // Stop if we hit a statement that isn't the single use of the
-              // let, and isn't a let itself.
-              break;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/fold_trivial_single_use_lets.h b/src/transform/fold_trivial_single_use_lets.h
deleted file mode 100644
index 6c8f17a..0000000
--- a/src/transform/fold_trivial_single_use_lets.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
-#define SRC_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// FoldTrivialSingleUseLets is an optimizer for folding away trivial `let`
-/// statements into their single place of use. This transform is intended to
-/// clean up the SSA `let`s produced by the SPIR-V reader.
-/// `let`s can only be folded if:
-/// * There is a single usage of the `let` value.
-/// * The `let` is constructed with a ScalarConstructorExpression, or with an
-///   IdentifierExpression.
-/// * There are only other foldable `let`s between the `let` declaration and its
-///   single usage.
-/// These rules prevent any hoisting of the let that may affect execution
-/// behaviour.
-class FoldTrivialSingleUseLets
-    : public Castable<FoldTrivialSingleUseLets, Transform> {
- public:
-  /// Constructor
-  FoldTrivialSingleUseLets();
-
-  /// Destructor
-  ~FoldTrivialSingleUseLets() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
diff --git a/src/transform/fold_trivial_single_use_lets_test.cc b/src/transform/fold_trivial_single_use_lets_test.cc
deleted file mode 100644
index 32109c6..0000000
--- a/src/transform/fold_trivial_single_use_lets_test.cc
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/fold_trivial_single_use_lets.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using FoldTrivialSingleUseLetsTest = TransformTest;
-
-TEST_F(FoldTrivialSingleUseLetsTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, Single) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  _ = x;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  _ = 1;
-}
-)";
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, Multiple) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  let y = 2;
-  let z = 3;
-  _ = x + y + z;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  _ = ((1 + 2) + 3);
-}
-)";
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, Chained) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  let y = x;
-  let z = y;
-  _ = z;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  _ = 1;
-}
-)";
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, NoFold_NonTrivialLet) {
-  auto* src = R"(
-fn function_with_posssible_side_effect() -> i32 {
-  return 1;
-}
-
-fn f() {
-  let x = 1;
-  let y = function_with_posssible_side_effect();
-  _ = (x + y);
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, NoFold_NonTrivialLet_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  let y = function_with_posssible_side_effect();
-  _ = (x + y);
-}
-
-fn function_with_posssible_side_effect() -> i32 {
-  return 1;
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, NoFold_UseInSubBlock) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  {
-    _ = x;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, NoFold_MultipleUses) {
-  auto* src = R"(
-fn f() {
-  let x = 1;
-  _ = (x + x);
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(FoldTrivialSingleUseLetsTest, NoFold_Shadowing) {
-  auto* src = R"(
-fn f() {
-  var y = 1;
-  let x = y;
-  {
-    let y = false;
-    _ = (x + x);
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<FoldTrivialSingleUseLets>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/for_loop_to_loop.cc b/src/transform/for_loop_to_loop.cc
deleted file mode 100644
index 3afd8a2..0000000
--- a/src/transform/for_loop_to_loop.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/for_loop_to_loop.h"
-
-#include "src/ast/break_statement.h"
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ForLoopToLoop);
-
-namespace tint {
-namespace transform {
-ForLoopToLoop::ForLoopToLoop() = default;
-
-ForLoopToLoop::~ForLoopToLoop() = default;
-
-bool ForLoopToLoop::ShouldRun(const Program* program, const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (node->Is<ast::ForLoopStatement>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void ForLoopToLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  ctx.ReplaceAll(
-      [&](const ast::ForLoopStatement* for_loop) -> const ast::Statement* {
-        ast::StatementList stmts;
-        if (auto* cond = for_loop->condition) {
-          // !condition
-          auto* not_cond = ctx.dst->create<ast::UnaryOpExpression>(
-              ast::UnaryOp::kNot, ctx.Clone(cond));
-
-          // { break; }
-          auto* break_body =
-              ctx.dst->Block(ctx.dst->create<ast::BreakStatement>());
-
-          // if (!condition) { break; }
-          stmts.emplace_back(ctx.dst->If(not_cond, break_body));
-        }
-        for (auto* stmt : for_loop->body->statements) {
-          stmts.emplace_back(ctx.Clone(stmt));
-        }
-
-        const ast::BlockStatement* continuing = nullptr;
-        if (auto* cont = for_loop->continuing) {
-          continuing = ctx.dst->Block(ctx.Clone(cont));
-        }
-
-        auto* body = ctx.dst->Block(stmts);
-        auto* loop = ctx.dst->create<ast::LoopStatement>(body, continuing);
-
-        if (auto* init = for_loop->initializer) {
-          return ctx.dst->Block(ctx.Clone(init), loop);
-        }
-
-        return loop;
-      });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/for_loop_to_loop.h b/src/transform/for_loop_to_loop.h
deleted file mode 100644
index 26cdc29..0000000
--- a/src/transform/for_loop_to_loop.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
-#define SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// ForLoopToLoop is a Transform that converts a for-loop statement into a loop
-/// statement. This is required by the SPIR-V writer.
-class ForLoopToLoop : public Castable<ForLoopToLoop, Transform> {
- public:
-  /// Constructor
-  ForLoopToLoop();
-
-  /// Destructor
-  ~ForLoopToLoop() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
diff --git a/src/transform/for_loop_to_loop_test.cc b/src/transform/for_loop_to_loop_test.cc
deleted file mode 100644
index 5f33593..0000000
--- a/src/transform/for_loop_to_loop_test.cc
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/for_loop_to_loop.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ForLoopToLoopTest = TransformTest;
-
-TEST_F(ForLoopToLoopTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<ForLoopToLoop>(src));
-}
-
-TEST_F(ForLoopToLoopTest, ShouldRunHasForLoop) {
-  auto* src = R"(
-fn f() {
-  for (;;) {
-    break;
-  }
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<ForLoopToLoop>(src));
-}
-
-TEST_F(ForLoopToLoopTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = src;
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test an empty for loop.
-TEST_F(ForLoopToLoopTest, Empty) {
-  auto* src = R"(
-fn f() {
-  for (;;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  loop {
-    break;
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop with non-empty body.
-TEST_F(ForLoopToLoopTest, Body) {
-  auto* src = R"(
-fn f() {
-  for (;;) {
-    discard;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  loop {
-    discard;
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop declaring a variable in the initializer statement.
-TEST_F(ForLoopToLoopTest, InitializerStatementDecl) {
-  auto* src = R"(
-fn f() {
-  for (var i: i32;;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  {
-    var i : i32;
-    loop {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop declaring and initializing a variable in the initializer
-// statement.
-TEST_F(ForLoopToLoopTest, InitializerStatementDeclEqual) {
-  auto* src = R"(
-fn f() {
-  for (var i: i32 = 0;;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  {
-    var i : i32 = 0;
-    loop {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop declaring a const variable in the initializer statement.
-TEST_F(ForLoopToLoopTest, InitializerStatementConstDecl) {
-  auto* src = R"(
-fn f() {
-  for (let i: i32 = 0;;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  {
-    let i : i32 = 0;
-    loop {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop assigning a variable in the initializer statement.
-TEST_F(ForLoopToLoopTest, InitializerStatementAssignment) {
-  auto* src = R"(
-fn f() {
-  var i: i32;
-  for (i = 0;;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  {
-    i = 0;
-    loop {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop calling a function in the initializer statement.
-TEST_F(ForLoopToLoopTest, InitializerStatementFuncCall) {
-  auto* src = R"(
-fn a(x : i32, y : i32) {
-}
-
-fn f() {
-  var b : i32;
-  var c : i32;
-  for (a(b,c);;) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn a(x : i32, y : i32) {
-}
-
-fn f() {
-  var b : i32;
-  var c : i32;
-  {
-    a(b, c);
-    loop {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop with a break condition
-TEST_F(ForLoopToLoopTest, BreakCondition) {
-  auto* src = R"(
-fn f() {
-  for (; 0 == 1;) {
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  loop {
-    if (!((0 == 1))) {
-      break;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop assigning a variable in the continuing statement.
-TEST_F(ForLoopToLoopTest, ContinuingAssignment) {
-  auto* src = R"(
-fn f() {
-  var x: i32;
-  for (;;x = 2) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var x : i32;
-  loop {
-    break;
-
-    continuing {
-      x = 2;
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop calling a function in the continuing statement.
-TEST_F(ForLoopToLoopTest, ContinuingFuncCall) {
-  auto* src = R"(
-fn a(x : i32, y : i32) {
-}
-
-fn f() {
-  var b : i32;
-  var c : i32;
-  for (;;a(b,c)) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn a(x : i32, y : i32) {
-}
-
-fn f() {
-  var b : i32;
-  var c : i32;
-  loop {
-    break;
-
-    continuing {
-      a(b, c);
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test a for loop with all statements non-empty.
-TEST_F(ForLoopToLoopTest, All) {
-  auto* src = R"(
-fn f() {
-  var a : i32;
-  for(var i : i32 = 0; i < 4; i = i + 1) {
-    if (a == 0) {
-      continue;
-    }
-    a = a + 2;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : i32;
-  {
-    var i : i32 = 0;
-    loop {
-      if (!((i < 4))) {
-        break;
-      }
-      if ((a == 0)) {
-        continue;
-      }
-      a = (a + 2);
-
-      continuing {
-        i = (i + 1);
-      }
-    }
-  }
-}
-)";
-
-  auto got = Run<ForLoopToLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/glsl.cc b/src/transform/glsl.cc
deleted file mode 100644
index 7744370..0000000
--- a/src/transform/glsl.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/glsl.h"
-
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/transform/add_empty_entry_point.h"
-#include "src/transform/add_spirv_block_attribute.h"
-#include "src/transform/binding_remapper.h"
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/transform/combine_samplers.h"
-#include "src/transform/decompose_memory_access.h"
-#include "src/transform/external_texture_transform.h"
-#include "src/transform/fold_trivial_single_use_lets.h"
-#include "src/transform/loop_to_for_loop.h"
-#include "src/transform/manager.h"
-#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_initializers_to_const_var.h"
-#include "src/transform/remove_phonies.h"
-#include "src/transform/renamer.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/single_entry_point.h"
-#include "src/transform/unshadow.h"
-#include "src/transform/zero_init_workgroup_memory.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl::Config);
-
-namespace tint {
-namespace transform {
-
-Glsl::Glsl() = default;
-Glsl::~Glsl() = default;
-
-Output Glsl::Run(const Program* in, const DataMap& inputs) const {
-  Manager manager;
-  DataMap data;
-
-  auto* cfg = inputs.Get<Config>();
-
-  if (cfg && !cfg->entry_point.empty()) {
-    manager.Add<SingleEntryPoint>();
-    data.Add<SingleEntryPoint::Config>(cfg->entry_point);
-  }
-  manager.Add<Renamer>();
-  data.Add<Renamer::Config>(Renamer::Target::kGlslKeywords,
-                            /* preserve_unicode */ false);
-  manager.Add<Unshadow>();
-
-  // Attempt to convert `loop`s into for-loops. This is to try and massage the
-  // output into something that will not cause FXC to choke or misbehave.
-  manager.Add<FoldTrivialSingleUseLets>();
-  manager.Add<LoopToForLoop>();
-
-  if (!cfg || !cfg->disable_workgroup_init) {
-    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
-    // ZeroInitWorkgroupMemory may inject new builtin parameters.
-    manager.Add<ZeroInitWorkgroupMemory>();
-  }
-  manager.Add<CanonicalizeEntryPointIO>();
-  manager.Add<SimplifyPointers>();
-
-  manager.Add<RemovePhonies>();
-  manager.Add<CombineSamplers>();
-  if (auto* binding_info = inputs.Get<CombineSamplers::BindingInfo>()) {
-    data.Add<CombineSamplers::BindingInfo>(*binding_info);
-  } else {
-    data.Add<CombineSamplers::BindingInfo>(CombineSamplers::BindingMap(),
-                                           sem::BindingPoint());
-  }
-  manager.Add<BindingRemapper>();
-  if (auto* remappings = inputs.Get<BindingRemapper::Remappings>()) {
-    data.Add<BindingRemapper::Remappings>(*remappings);
-  } else {
-    BindingRemapper::BindingPoints bp;
-    BindingRemapper::AccessControls ac;
-    data.Add<BindingRemapper::Remappings>(bp, ac, /* mayCollide */ true);
-  }
-  manager.Add<ExternalTextureTransform>();
-  manager.Add<PromoteInitializersToConstVar>();
-
-  manager.Add<PadArrayElements>();
-  manager.Add<AddEmptyEntryPoint>();
-  manager.Add<AddSpirvBlockAttribute>();
-
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
-  auto out = manager.Run(in, data);
-  if (!out.program.IsValid()) {
-    return out;
-  }
-
-  ProgramBuilder builder;
-  CloneContext ctx(&builder, &out.program);
-  ctx.Clone();
-  return Output{Program(std::move(builder))};
-}
-
-Glsl::Config::Config(const std::string& ep, bool disable_wi)
-    : entry_point(ep), disable_workgroup_init(disable_wi) {}
-Glsl::Config::Config(const Config&) = default;
-Glsl::Config::~Config() = default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/glsl.h b/src/transform/glsl.h
deleted file mode 100644
index bc6a2b9..0000000
--- a/src/transform/glsl.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_GLSL_H_
-#define SRC_TRANSFORM_GLSL_H_
-
-#include <string>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace transform {
-
-/// Glsl is a transform used to sanitize a Program for use with the Glsl writer.
-/// Passing a non-sanitized Program to the Glsl writer will result in undefined
-/// behavior.
-class Glsl : public Castable<Glsl, Transform> {
- public:
-  /// Configuration options for the Glsl sanitizer transform.
-  struct Config : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param entry_point the root entry point function to generate
-    /// @param disable_workgroup_init `true` to disable workgroup memory zero
-    ///        initialization
-    explicit Config(const std::string& entry_point,
-                    bool disable_workgroup_init = false);
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// GLSL generator wraps a single entry point in a main() function.
-    std::string entry_point;
-
-    /// Set to `true` to disable workgroup memory zero initialization
-    bool disable_workgroup_init = false;
-  };
-
-  /// Constructor
-  Glsl();
-  ~Glsl() override;
-
-  /// Runs the transform on `program`, returning the transformation result.
-  /// @param program the source program to transform
-  /// @param data optional extra transform-specific data
-  /// @returns the transformation result
-  Output Run(const Program* program, const DataMap& data = {}) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_GLSL_H_
diff --git a/src/transform/glsl_test.cc b/src/transform/glsl_test.cc
deleted file mode 100644
index 49b0dfd..0000000
--- a/src/transform/glsl_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/glsl.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using GlslTest = TransformTest;
-
-TEST_F(GlslTest, AddEmptyEntryPoint) {
-  auto* src = R"()";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn unused_entry_point() {
-}
-)";
-
-  auto got = Run<Glsl>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/localize_struct_array_assignment.cc b/src/transform/localize_struct_array_assignment.cc
deleted file mode 100644
index 4313771..0000000
--- a/src/transform/localize_struct_array_assignment.cc
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/localize_struct_array_assignment.h"
-
-#include <unordered_map>
-#include <utility>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/traverse_expressions.h"
-#include "src/program_builder.h"
-#include "src/sem/expression.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/utils/scoped_assignment.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::LocalizeStructArrayAssignment);
-
-namespace tint {
-namespace transform {
-
-/// Private implementation of LocalizeStructArrayAssignment transform
-class LocalizeStructArrayAssignment::State {
- private:
-  CloneContext& ctx;
-  ProgramBuilder& b;
-
-  /// Returns true if `expr` contains an index accessor expression to a
-  /// structure member of array type.
-  bool ContainsStructArrayIndex(const ast::Expression* expr) {
-    bool result = false;
-    ast::TraverseExpressions(
-        expr, b.Diagnostics(), [&](const ast::IndexAccessorExpression* ia) {
-          // Indexing using a runtime value?
-          auto* idx_sem = ctx.src->Sem().Get(ia->index);
-          if (!idx_sem->ConstantValue().IsValid()) {
-            // Indexing a member access expr?
-            if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
-              // That accesses an array?
-              if (ctx.src->TypeOf(ma)->UnwrapRef()->Is<sem::Array>()) {
-                result = true;
-                return ast::TraverseAction::Stop;
-              }
-            }
-          }
-          return ast::TraverseAction::Descend;
-        });
-
-    return result;
-  }
-
-  // Returns the type and storage class of the originating variable of the lhs
-  // of the assignment statement.
-  // See https://www.w3.org/TR/WGSL/#originating-variable-section
-  std::pair<const sem::Type*, ast::StorageClass>
-  GetOriginatingTypeAndStorageClass(
-      const ast::AssignmentStatement* assign_stmt) {
-    // Get first IdentifierExpr from lhs of assignment, which should resolve to
-    // the pointer or reference of the originating variable of the assignment.
-    // TraverseExpressions traverses left to right, and this code depends on the
-    // fact that for an assignment statement, the variable will be the left-most
-    // expression.
-    // TODO(crbug.com/tint/1341): do this in the Resolver, setting the
-    // originating variable on sem::Expression.
-    const ast::IdentifierExpression* ident = nullptr;
-    ast::TraverseExpressions(assign_stmt->lhs, b.Diagnostics(),
-                             [&](const ast::IdentifierExpression* id) {
-                               ident = id;
-                               return ast::TraverseAction::Stop;
-                             });
-    auto* sem_var_user = ctx.src->Sem().Get<sem::VariableUser>(ident);
-    if (!sem_var_user) {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "Expected to find variable of lhs of assignment statement";
-      return {};
-    }
-
-    auto* var = sem_var_user->Variable();
-    if (auto* ptr = var->Type()->As<sem::Pointer>()) {
-      return {ptr->StoreType(), ptr->StorageClass()};
-    }
-
-    auto* ref = var->Type()->As<sem::Reference>();
-    if (!ref) {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "Expecting to find variable of type pointer or reference on lhs "
-             "of assignment statement";
-      return {};
-    }
-
-    return {ref->StoreType(), ref->StorageClass()};
-  }
-
- public:
-  /// Constructor
-  /// @param ctx_in the CloneContext primed with the input program and
-  /// ProgramBuilder
-  explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
-
-  /// Runs the transform
-  void Run() {
-    struct Shared {
-      bool process_nested_nodes = false;
-      ast::StatementList insert_before_stmts;
-      ast::StatementList insert_after_stmts;
-    } s;
-
-    ctx.ReplaceAll([&](const ast::AssignmentStatement* assign_stmt)
-                       -> const ast::Statement* {
-      // Process if it's an assignment statement to a dynamically indexed array
-      // within a struct on a function or private storage variable. This
-      // specific use-case is what FXC fails to compile with:
-      // error X3500: array reference cannot be used as an l-value; not natively
-      // addressable
-      if (!ContainsStructArrayIndex(assign_stmt->lhs)) {
-        return nullptr;
-      }
-      auto og = GetOriginatingTypeAndStorageClass(assign_stmt);
-      if (!(og.first->Is<sem::Struct>() &&
-            (og.second == ast::StorageClass::kFunction ||
-             og.second == ast::StorageClass::kPrivate))) {
-        return nullptr;
-      }
-
-      // Reset shared state for this assignment statement
-      s = Shared{};
-
-      const ast::Expression* new_lhs = nullptr;
-      {
-        TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, true);
-        new_lhs = ctx.Clone(assign_stmt->lhs);
-      }
-
-      auto* new_assign_stmt = b.Assign(new_lhs, ctx.Clone(assign_stmt->rhs));
-
-      // Combine insert_before_stmts + new_assign_stmt + insert_after_stmts into
-      // a block and return it
-      ast::StatementList stmts = std::move(s.insert_before_stmts);
-      stmts.reserve(1 + s.insert_after_stmts.size());
-      stmts.emplace_back(new_assign_stmt);
-      stmts.insert(stmts.end(), s.insert_after_stmts.begin(),
-                   s.insert_after_stmts.end());
-
-      return b.Block(std::move(stmts));
-    });
-
-    ctx.ReplaceAll([&](const ast::IndexAccessorExpression* index_access)
-                       -> const ast::Expression* {
-      if (!s.process_nested_nodes) {
-        return nullptr;
-      }
-
-      // Indexing a member access expr?
-      auto* mem_access =
-          index_access->object->As<ast::MemberAccessorExpression>();
-      if (!mem_access) {
-        return nullptr;
-      }
-
-      // Process any nested IndexAccessorExpressions
-      mem_access = ctx.Clone(mem_access);
-
-      // Store the address of the member access into a let as we need to read
-      // the value twice e.g. let tint_symbol = &(s.a1);
-      auto mem_access_ptr = b.Sym();
-      s.insert_before_stmts.push_back(
-          b.Decl(b.Const(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
-
-      // Disable further transforms when cloning
-      TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, false);
-
-      // Copy entire array out of struct into local temp var
-      // e.g. var tint_symbol_1 = *(tint_symbol);
-      auto tmp_var = b.Sym();
-      s.insert_before_stmts.push_back(
-          b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
-
-      // Replace input index_access with a clone of itself, but with its
-      // .object replaced by the new temp var. This is returned from this
-      // function to modify the original assignment statement. e.g.
-      // tint_symbol_1[uniforms.i]
-      auto* new_index_access =
-          b.IndexAccessor(tmp_var, ctx.Clone(index_access->index));
-
-      // Assign temp var back to array
-      // e.g. *(tint_symbol) = tint_symbol_1;
-      auto* assign_rhs_to_temp = b.Assign(b.Deref(mem_access_ptr), tmp_var);
-      s.insert_after_stmts.insert(s.insert_after_stmts.begin(),
-                                  assign_rhs_to_temp);  // push_front
-
-      return new_index_access;
-    });
-
-    ctx.Clone();
-  }
-};
-
-LocalizeStructArrayAssignment::LocalizeStructArrayAssignment() = default;
-
-LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
-
-void LocalizeStructArrayAssignment::Run(CloneContext& ctx,
-                                        const DataMap&,
-                                        DataMap&) const {
-  State state(ctx);
-  state.Run();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/localize_struct_array_assignment.h b/src/transform/localize_struct_array_assignment.h
deleted file mode 100644
index beab7ba..0000000
--- a/src/transform/localize_struct_array_assignment.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
-#define SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// This transforms replaces assignment to dynamically-indexed fixed-size arrays
-/// in structs on shader-local variables with code that copies the arrays to a
-/// temporary local variable, assigns to the local variable, and copies the
-/// array back. This is to work around FXC's compilation failure for these cases
-/// (see crbug.com/tint/1206).
-///
-/// @note Depends on the following transforms to have been run first:
-/// * SimplifyPointers
-class LocalizeStructArrayAssignment
-    : public Castable<LocalizeStructArrayAssignment, Transform> {
- public:
-  /// Constructor
-  LocalizeStructArrayAssignment();
-
-  /// Destructor
-  ~LocalizeStructArrayAssignment() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  class State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
diff --git a/src/transform/localize_struct_array_assignment_test.cc b/src/transform/localize_struct_array_assignment_test.cc
deleted file mode 100644
index 7d92ba6..0000000
--- a/src/transform/localize_struct_array_assignment_test.cc
+++ /dev/null
@@ -1,884 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/localize_struct_array_assignment.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/unshadow.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using LocalizeStructArrayAssignmentTest = TransformTest;
-
-TEST_F(LocalizeStructArrayAssignmentTest, EmptyModule) {
-  auto* src = R"()";
-  auto* expect = src;
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructArray) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.a1[uniforms.i] = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructArray_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.a1[uniforms.i] = v;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-@block struct Uniforms {
-  i : u32;
-};
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-@block
-struct Uniforms {
-  i : u32;
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructStructArray) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct S1 {
-  a : array<InnerS, 8>;
-};
-
-struct OuterS {
-  s2 : S1;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.s2.a[uniforms.i] = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct S1 {
-  a : array<InnerS, 8>;
-}
-
-struct OuterS {
-  s2 : S1;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.s2.a);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructStructArray_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.s2.a[uniforms.i] = v;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-struct OuterS {
-  s2 : S1;
-};
-
-struct S1 {
-  a : array<InnerS, 8>;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-@block struct Uniforms {
-  i : u32;
-};
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.s2.a);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-struct OuterS {
-  s2 : S1;
-}
-
-struct S1 {
-  a : array<InnerS, 8>;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-@block
-struct Uniforms {
-  i : u32;
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructArrayArray) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-  j : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct OuterS {
-  a1 : array<array<InnerS, 8>, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.a1[uniforms.i][uniforms.j] = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-  j : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct OuterS {
-  a1 : array<array<InnerS, 8>, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i][uniforms.j] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStruct) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct S1 {
-  s2 : InnerS;
-};
-
-struct OuterS {
-  a1 : array<S1, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  s1.a1[uniforms.i].s2 = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct S1 {
-  s2 : InnerS;
-}
-
-struct OuterS {
-  a1 : array<S1, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  {
-    let tint_symbol = &(s1.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i].s2 = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStructArray) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-  j : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-};
-
-struct OuterS {
-  a1 : array<S1, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  s.a1[uniforms.i].a2[uniforms.j] = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-  j : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-}
-
-struct OuterS {
-  a1 : array<S1, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  {
-    let tint_symbol = &(s.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    let tint_symbol_2 = &(tint_symbol_1[uniforms.i].a2);
-    var tint_symbol_3 = *(tint_symbol_2);
-    tint_symbol_3[uniforms.j] = v;
-    *(tint_symbol_2) = tint_symbol_3;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, IndexingWithSideEffectFunc) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-  j : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-};
-
-struct OuterS {
-  a1 : array<S1, 8>;
-};
-
-var<private> nextIndex : u32;
-fn getNextIndex() -> u32 {
-  nextIndex = nextIndex + 1u;
-  return nextIndex;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  s.a1[getNextIndex()].a2[uniforms.j] = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-  j : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-}
-
-struct OuterS {
-  a1 : array<S1, 8>;
-}
-
-var<private> nextIndex : u32;
-
-fn getNextIndex() -> u32 {
-  nextIndex = (nextIndex + 1u);
-  return nextIndex;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  {
-    let tint_symbol = &(s.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    let tint_symbol_2 = &(tint_symbol_1[getNextIndex()].a2);
-    var tint_symbol_3 = *(tint_symbol_2);
-    tint_symbol_3[uniforms.j] = v;
-    *(tint_symbol_2) = tint_symbol_3;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest,
-       IndexingWithSideEffectFunc_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  s.a1[getNextIndex()].a2[uniforms.j] = v;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@block struct Uniforms {
-  i : u32;
-  j : u32;
-};
-
-var<private> nextIndex : u32;
-fn getNextIndex() -> u32 {
-  nextIndex = nextIndex + 1u;
-  return nextIndex;
-}
-
-struct OuterS {
-  a1 : array<S1, 8>;
-};
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-};
-
-struct InnerS {
-  v : i32;
-};
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s : OuterS;
-  {
-    let tint_symbol = &(s.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    let tint_symbol_2 = &(tint_symbol_1[getNextIndex()].a2);
-    var tint_symbol_3 = *(tint_symbol_2);
-    tint_symbol_3[uniforms.j] = v;
-    *(tint_symbol_2) = tint_symbol_3;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@block
-struct Uniforms {
-  i : u32;
-  j : u32;
-}
-
-var<private> nextIndex : u32;
-
-fn getNextIndex() -> u32 {
-  nextIndex = (nextIndex + 1u);
-  return nextIndex;
-}
-
-struct OuterS {
-  a1 : array<S1, 8>;
-}
-
-struct S1 {
-  a2 : array<InnerS, 8>;
-}
-
-struct InnerS {
-  v : i32;
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg) {
-  auto* src = R"(
-@block struct Uniforms {
-  i : u32;
-};
-struct InnerS {
-  v : i32;
-};
-struct OuterS {
-  a1 : array<InnerS, 8>;
-};
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-fn f(p : ptr<function, OuterS>) {
-  var v : InnerS;
-  (*p).a1[uniforms.i] = v;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var s1 : OuterS;
-  f(&s1);
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-fn f(p : ptr<function, OuterS>) {
-  var v : InnerS;
-  {
-    let tint_symbol = &((*(p)).a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var s1 : OuterS;
-  f(&(s1));
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var s1 : OuterS;
-  f(&s1);
-}
-
-fn f(p : ptr<function, OuterS>) {
-  var v : InnerS;
-  (*p).a1[uniforms.i] = v;
-}
-
-struct InnerS {
-  v : i32;
-};
-struct OuterS {
-  a1 : array<InnerS, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@block struct Uniforms {
-  i : u32;
-};
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var s1 : OuterS;
-  f(&(s1));
-}
-
-fn f(p : ptr<function, OuterS>) {
-  var v : InnerS;
-  {
-    let tint_symbol = &((*(p)).a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[uniforms.i] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-@block
-struct Uniforms {
-  i : u32;
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerVar) {
-  auto* src = R"(
-@block
-struct Uniforms {
-  i : u32;
-};
-
-struct InnerS {
-  v : i32;
-};
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-};
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-fn f(p : ptr<function, InnerS>, v : InnerS) {
-  *(p) = v;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  let p = &(s1.a1[uniforms.i]);
-  *(p) = v;
-}
-)";
-
-  auto* expect = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-struct InnerS {
-  v : i32;
-}
-
-struct OuterS {
-  a1 : array<InnerS, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-fn f(p : ptr<function, InnerS>, v : InnerS) {
-  *(p) = v;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var v : InnerS;
-  var s1 : OuterS;
-  let p_save = uniforms.i;
-  {
-    let tint_symbol = &(s1.a1);
-    var tint_symbol_1 = *(tint_symbol);
-    tint_symbol_1[p_save] = v;
-    *(tint_symbol) = tint_symbol_1;
-  }
-}
-)";
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LocalizeStructArrayAssignmentTest, VectorAssignment) {
-  auto* src = R"(
-@block
-struct Uniforms {
-  i : u32;
-}
-
-@block
-struct OuterS {
-  a1 : array<u32, 8>;
-}
-
-@group(1) @binding(4) var<uniform> uniforms : Uniforms;
-
-fn f(i : u32) -> u32 {
-  return (i + 1u);
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var s1 : OuterS;
-  var v : vec3<f32>;
-  v[s1.a1[uniforms.i]] = 1.0;
-  v[f(s1.a1[uniforms.i])] = 1.0;
-}
-)";
-
-  // Transform does nothing here as we're not actually assigning to the array in
-  // the struct.
-  auto* expect = src;
-
-  auto got =
-      Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/loop_to_for_loop.cc b/src/transform/loop_to_for_loop.cc
deleted file mode 100644
index 735a115..0000000
--- a/src/transform/loop_to_for_loop.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/loop_to_for_loop.h"
-
-#include "src/ast/break_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/utils/scoped_assignment.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::LoopToForLoop);
-
-namespace tint {
-namespace transform {
-namespace {
-
-bool IsBlockWithSingleBreak(const ast::BlockStatement* block) {
-  if (block->statements.size() != 1) {
-    return false;
-  }
-  return block->statements[0]->Is<ast::BreakStatement>();
-}
-
-bool IsVarUsedByStmt(const sem::Info& sem,
-                     const ast::Variable* var,
-                     const ast::Statement* stmt) {
-  auto* var_sem = sem.Get(var);
-  for (auto* user : var_sem->Users()) {
-    if (auto* s = user->Stmt()) {
-      if (s->Declaration() == stmt) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-}  // namespace
-
-LoopToForLoop::LoopToForLoop() = default;
-
-LoopToForLoop::~LoopToForLoop() = default;
-
-bool LoopToForLoop::ShouldRun(const Program* program, const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (node->Is<ast::LoopStatement>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void LoopToForLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  ctx.ReplaceAll([&](const ast::LoopStatement* loop) -> const ast::Statement* {
-    // For loop condition is taken from the first statement in the loop.
-    // This requires an if-statement with either:
-    //  * A true block with no else statements, and the true block contains a
-    //    single 'break' statement.
-    //  * An empty true block with a single, no-condition else statement
-    //    containing a single 'break' statement.
-    // Examples:
-    //   loop {  if (condition) { break; } ... }
-    //   loop {  if (condition) {} else { break; } ... }
-    auto& stmts = loop->body->statements;
-    if (stmts.empty()) {
-      return nullptr;
-    }
-    auto* if_stmt = stmts[0]->As<ast::IfStatement>();
-    if (!if_stmt) {
-      return nullptr;
-    }
-
-    bool negate_condition = false;
-    if (IsBlockWithSingleBreak(if_stmt->body) &&
-        if_stmt->else_statements.empty()) {
-      negate_condition = true;
-    } else if (if_stmt->body->Empty() && if_stmt->else_statements.size() == 1 &&
-               if_stmt->else_statements[0]->condition == nullptr &&
-               IsBlockWithSingleBreak(if_stmt->else_statements[0]->body)) {
-      negate_condition = false;
-    } else {
-      return nullptr;
-    }
-
-    // The continuing block must be empty or contain a single, assignment or
-    // function call statement.
-    const ast::Statement* continuing = nullptr;
-    if (auto* loop_cont = loop->continuing) {
-      if (loop_cont->statements.size() != 1) {
-        return nullptr;
-      }
-
-      continuing = loop_cont->statements[0];
-      if (!continuing
-               ->IsAnyOf<ast::AssignmentStatement, ast::CallStatement>()) {
-        return nullptr;
-      }
-
-      // And the continuing statement must not use any of the variables declared
-      // in the loop body.
-      for (auto* stmt : loop->body->statements) {
-        if (auto* var_decl = stmt->As<ast::VariableDeclStatement>()) {
-          if (IsVarUsedByStmt(ctx.src->Sem(), var_decl->variable, continuing)) {
-            return nullptr;
-          }
-        }
-      }
-
-      continuing = ctx.Clone(continuing);
-    }
-
-    auto* condition = ctx.Clone(if_stmt->condition);
-    if (negate_condition) {
-      condition = ctx.dst->create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
-                                                          condition);
-    }
-
-    ast::Statement* initializer = nullptr;
-
-    ctx.Remove(loop->body->statements, if_stmt);
-    auto* body = ctx.Clone(loop->body);
-    return ctx.dst->create<ast::ForLoopStatement>(initializer, condition,
-                                                  continuing, body);
-  });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/loop_to_for_loop.h b/src/transform/loop_to_for_loop.h
deleted file mode 100644
index b3d323b..0000000
--- a/src/transform/loop_to_for_loop.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_LOOP_TO_FOR_LOOP_H_
-#define SRC_TRANSFORM_LOOP_TO_FOR_LOOP_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// LoopToForLoop is a Transform that attempts to convert WGSL `loop {}`
-/// statements into a for-loop statement.
-class LoopToForLoop : public Castable<LoopToForLoop, Transform> {
- public:
-  /// Constructor
-  LoopToForLoop();
-
-  /// Destructor
-  ~LoopToForLoop() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_LOOP_TO_FOR_LOOP_H_
diff --git a/src/transform/loop_to_for_loop_test.cc b/src/transform/loop_to_for_loop_test.cc
deleted file mode 100644
index 5322b9f..0000000
--- a/src/transform/loop_to_for_loop_test.cc
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/loop_to_for_loop.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using LoopToForLoopTest = TransformTest;
-
-TEST_F(LoopToForLoopTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<LoopToForLoop>(src));
-}
-
-TEST_F(LoopToForLoopTest, ShouldRunHasForLoop) {
-  auto* src = R"(
-fn f() {
-  loop {
-    break;
-  }
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<LoopToForLoop>(src));
-}
-
-TEST_F(LoopToForLoopTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, IfBreak) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if (i > 15) {
-      break;
-    }
-
-    _ = 123;
-
-    continuing {
-      i = i + 1;
-    }
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  for(; !((i > 15)); i = (i + 1)) {
-    _ = 123;
-  }
-}
-)";
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, IfElseBreak) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if (i < 15) {
-    } else {
-      break;
-    }
-
-    _ = 123;
-
-    continuing {
-      i = i + 1;
-    }
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  for(; (i < 15); i = (i + 1)) {
-    _ = 123;
-  }
-}
-)";
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, Nested) {
-  auto* src = R"(
-let N = 16u;
-
-fn f() {
-  var i : u32 = 0u;
-  loop {
-    if (i >= N) {
-      break;
-    }
-    {
-      var j : u32 = 0u;
-      loop {
-        if (j >= N) {
-          break;
-        }
-
-        _ = i;
-        _ = j;
-
-        continuing {
-          j = (j + 1u);
-        }
-      }
-    }
-
-    continuing {
-      i = (i + 1u);
-    }
-  }
-}
-)";
-
-  auto* expect = R"(
-let N = 16u;
-
-fn f() {
-  var i : u32 = 0u;
-  for(; !((i >= N)); i = (i + 1u)) {
-    {
-      var j : u32 = 0u;
-      for(; !((j >= N)); j = (j + 1u)) {
-        _ = i;
-        _ = j;
-      }
-    }
-  }
-}
-)";
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, NoTransform_IfMultipleStmts) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if ((i < 15)) {
-      _ = i;
-      break;
-    }
-    _ = 123;
-
-    continuing {
-      i = (i + 1);
-    }
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, NoTransform_IfElseMultipleStmts) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if ((i < 15)) {
-    } else {
-      _ = i;
-      break;
-    }
-    _ = 123;
-
-    continuing {
-      i = (i + 1);
-    }
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, NoTransform_ContinuingIsCompound) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if ((i < 15)) {
-      break;
-    }
-    _ = 123;
-
-    continuing {
-      if (false) {
-      }
-    }
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, NoTransform_ContinuingMultipleStmts) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if ((i < 15)) {
-      break;
-    }
-    _ = 123;
-
-    continuing {
-      i = (i + 1);
-      _ = i;
-    }
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(LoopToForLoopTest, NoTransform_ContinuingUsesVarDeclInLoopBody) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  i = 0;
-  loop {
-    if ((i < 15)) {
-      break;
-    }
-    var j : i32;
-
-    continuing {
-      i = (i + j);
-    }
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<LoopToForLoop>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/manager.cc b/src/transform/manager.cc
deleted file mode 100644
index 672d629..0000000
--- a/src/transform/manager.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2020 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/transform/manager.h"
-
-/// If set to 1 then the transform::Manager will dump the WGSL of the program
-/// before and after each transform. Helpful for debugging bad output.
-#define TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM 0
-
-#if TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
-#define TINT_IF_PRINT_PROGRAM(x) x
-#else  // TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
-#define TINT_IF_PRINT_PROGRAM(x)
-#endif  // TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Manager);
-
-namespace tint {
-namespace transform {
-
-Manager::Manager() = default;
-Manager::~Manager() = default;
-
-Output Manager::Run(const Program* program, const DataMap& data) const {
-  const Program* in = program;
-
-#if TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
-  auto print_program = [&](const char* msg, const Transform* transform) {
-    auto wgsl = Program::printer(in);
-    std::cout << "---------------------------------------------------------"
-              << std::endl;
-    std::cout << "-- " << msg << " " << transform->TypeInfo().name << ":"
-              << std::endl;
-    std::cout << "---------------------------------------------------------"
-              << std::endl;
-    std::cout << wgsl << std::endl;
-    std::cout << "---------------------------------------------------------"
-              << std::endl
-              << std::endl;
-  };
-#endif
-
-  Output out;
-  for (const auto& transform : transforms_) {
-    if (!transform->ShouldRun(in, data)) {
-      TINT_IF_PRINT_PROGRAM(std::cout << "Skipping "
-                                      << transform->TypeInfo().name);
-      continue;
-    }
-    TINT_IF_PRINT_PROGRAM(print_program("Input to", transform.get()));
-
-    auto res = transform->Run(in, data);
-    out.program = std::move(res.program);
-    out.data.Add(std::move(res.data));
-    in = &out.program;
-    if (!in->IsValid()) {
-      TINT_IF_PRINT_PROGRAM(
-          print_program("Invalid output of", transform.get()));
-      return out;
-    }
-
-    if (transform == transforms_.back()) {
-      TINT_IF_PRINT_PROGRAM(print_program("Output of", transform.get()));
-    }
-  }
-
-  if (program == in) {
-    out.program = program->Clone();
-  }
-
-  return out;
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/manager.h b/src/transform/manager.h
deleted file mode 100644
index e5ef910..0000000
--- a/src/transform/manager.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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_TRANSFORM_MANAGER_H_
-#define SRC_TRANSFORM_MANAGER_H_
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// A collection of Transforms that act as a single Transform.
-/// The inner transforms will execute in the appended order.
-/// If any inner transform fails the manager will return immediately and
-/// the error can be retrieved with the Output's diagnostics.
-class Manager : public Castable<Manager, Transform> {
- public:
-  /// Constructor
-  Manager();
-  ~Manager() override;
-
-  /// Add pass to the manager
-  /// @param transform the transform to append
-  void append(std::unique_ptr<Transform> transform) {
-    transforms_.push_back(std::move(transform));
-  }
-
-  /// Add pass to the manager of type `T`, constructed with the provided
-  /// arguments.
-  /// @param args the arguments to forward to the `T` constructor
-  template <typename T, typename... ARGS>
-  void Add(ARGS&&... args) {
-    transforms_.emplace_back(std::make_unique<T>(std::forward<ARGS>(args)...));
-  }
-
-  /// Runs the transforms on `program`, returning the transformation result.
-  /// @param program the source program to transform
-  /// @param data optional extra transform-specific input data
-  /// @returns the transformed program and diagnostics
-  Output Run(const Program* program, const DataMap& data = {}) const override;
-
- private:
-  std::vector<std::unique_ptr<Transform>> transforms_;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_MANAGER_H_
diff --git a/src/transform/module_scope_var_to_entry_point_param.cc b/src/transform/module_scope_var_to_entry_point_param.cc
deleted file mode 100644
index 4601f9c..0000000
--- a/src/transform/module_scope_var_to_entry_point_param.cc
+++ /dev/null
@@ -1,399 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/module_scope_var_to_entry_point_param.h"
-
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/disable_validation_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ModuleScopeVarToEntryPointParam);
-
-namespace tint {
-namespace transform {
-namespace {
-// Returns `true` if `type` is or contains a matrix type.
-bool ContainsMatrix(const sem::Type* type) {
-  type = type->UnwrapRef();
-  if (type->Is<sem::Matrix>()) {
-    return true;
-  } else if (auto* ary = type->As<sem::Array>()) {
-    return ContainsMatrix(ary->ElemType());
-  } else if (auto* str = type->As<sem::Struct>()) {
-    for (auto* member : str->Members()) {
-      if (ContainsMatrix(member->Type())) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-}  // namespace
-
-/// State holds the current transform state.
-struct ModuleScopeVarToEntryPointParam::State {
-  /// The clone context.
-  CloneContext& ctx;
-
-  /// Constructor
-  /// @param context the clone context
-  explicit State(CloneContext& context) : ctx(context) {}
-
-  /// Clone any struct types that are contained in `ty` (including `ty` itself),
-  /// and add it to the global declarations now, so that they precede new global
-  /// declarations that need to reference them.
-  /// @param ty the type to clone
-  void CloneStructTypes(const sem::Type* ty) {
-    if (auto* str = ty->As<sem::Struct>()) {
-      if (!cloned_structs_.emplace(str).second) {
-        // The struct has already been cloned.
-        return;
-      }
-
-      // Recurse into members.
-      for (auto* member : str->Members()) {
-        CloneStructTypes(member->Type());
-      }
-
-      // Clone the struct and add it to the global declaration list.
-      // Remove the old declaration.
-      auto* ast_str = str->Declaration();
-      ctx.dst->AST().AddTypeDecl(ctx.Clone(ast_str));
-      ctx.Remove(ctx.src->AST().GlobalDeclarations(), ast_str);
-    } else if (auto* arr = ty->As<sem::Array>()) {
-      CloneStructTypes(arr->ElemType());
-    }
-  }
-
-  /// Process the module.
-  void Process() {
-    // Predetermine the list of function calls that need to be replaced.
-    using CallList = std::vector<const ast::CallExpression*>;
-    std::unordered_map<const ast::Function*, CallList> calls_to_replace;
-
-    std::vector<const ast::Function*> functions_to_process;
-
-    // Build a list of functions that transitively reference any module-scope
-    // variables.
-    for (auto* func_ast : ctx.src->AST().Functions()) {
-      auto* func_sem = ctx.src->Sem().Get(func_ast);
-
-      bool needs_processing = false;
-      for (auto* var : func_sem->TransitivelyReferencedGlobals()) {
-        if (var->StorageClass() != ast::StorageClass::kNone) {
-          needs_processing = true;
-          break;
-        }
-      }
-      if (needs_processing) {
-        functions_to_process.push_back(func_ast);
-
-        // Find all of the calls to this function that will need to be replaced.
-        for (auto* call : func_sem->CallSites()) {
-          calls_to_replace[call->Stmt()->Function()->Declaration()].push_back(
-              call->Declaration());
-        }
-      }
-    }
-
-    // Build a list of `&ident` expressions. We'll use this later to avoid
-    // generating expressions of the form `&*ident`, which break WGSL validation
-    // rules when this expression is passed to a function.
-    // TODO(jrprice): We should add support for bidirectional SEM tree traversal
-    // so that we can do this on the fly instead.
-    std::unordered_map<const ast::IdentifierExpression*,
-                       const ast::UnaryOpExpression*>
-        ident_to_address_of;
-    for (auto* node : ctx.src->ASTNodes().Objects()) {
-      auto* address_of = node->As<ast::UnaryOpExpression>();
-      if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
-        continue;
-      }
-      if (auto* ident = address_of->expr->As<ast::IdentifierExpression>()) {
-        ident_to_address_of[ident] = address_of;
-      }
-    }
-
-    for (auto* func_ast : functions_to_process) {
-      auto* func_sem = ctx.src->Sem().Get(func_ast);
-      bool is_entry_point = func_ast->IsEntryPoint();
-
-      // Map module-scope variables onto their replacement.
-      struct NewVar {
-        Symbol symbol;
-        bool is_pointer;
-        bool is_wrapped;
-      };
-      const char* kWrappedArrayMemberName = "arr";
-      std::unordered_map<const sem::Variable*, NewVar> var_to_newvar;
-
-      // We aggregate all workgroup variables into a struct to avoid hitting
-      // MSL's limit for threadgroup memory arguments.
-      Symbol workgroup_parameter_symbol;
-      ast::StructMemberList workgroup_parameter_members;
-      auto workgroup_param = [&]() {
-        if (!workgroup_parameter_symbol.IsValid()) {
-          workgroup_parameter_symbol = ctx.dst->Sym();
-        }
-        return workgroup_parameter_symbol;
-      };
-
-      for (auto* var : func_sem->TransitivelyReferencedGlobals()) {
-        auto sc = var->StorageClass();
-        auto* ty = var->Type()->UnwrapRef();
-        if (sc == ast::StorageClass::kNone) {
-          continue;
-        }
-        if (sc != ast::StorageClass::kPrivate &&
-            sc != ast::StorageClass::kStorage &&
-            sc != ast::StorageClass::kUniform &&
-            sc != ast::StorageClass::kUniformConstant &&
-            sc != ast::StorageClass::kWorkgroup) {
-          TINT_ICE(Transform, ctx.dst->Diagnostics())
-              << "unhandled module-scope storage class (" << sc << ")";
-        }
-
-        // This is the symbol for the variable that replaces the module-scope
-        // var.
-        auto new_var_symbol = ctx.dst->Sym();
-
-        // Helper to create an AST node for the store type of the variable.
-        auto store_type = [&]() { return CreateASTTypeFor(ctx, ty); };
-
-        // Track whether the new variable is a pointer or not.
-        bool is_pointer = false;
-
-        // Track whether the new variable was wrapped in a struct or not.
-        bool is_wrapped = false;
-
-        if (is_entry_point) {
-          if (var->Type()->UnwrapRef()->is_handle()) {
-            // For a texture or sampler variable, redeclare it as an entry point
-            // parameter. Disable entry point parameter validation.
-            auto* disable_validation =
-                ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
-            auto attrs = ctx.Clone(var->Declaration()->attributes);
-            attrs.push_back(disable_validation);
-            auto* param = ctx.dst->Param(new_var_symbol, store_type(), attrs);
-            ctx.InsertFront(func_ast->params, param);
-          } else if (sc == ast::StorageClass::kStorage ||
-                     sc == ast::StorageClass::kUniform) {
-            // Variables into the Storage and Uniform storage classes are
-            // redeclared as entry point parameters with a pointer type.
-            auto attributes = ctx.Clone(var->Declaration()->attributes);
-            attributes.push_back(ctx.dst->Disable(
-                ast::DisabledValidation::kEntryPointParameter));
-            attributes.push_back(
-                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
-
-            auto* param_type = store_type();
-            if (auto* arr = ty->As<sem::Array>();
-                arr && arr->IsRuntimeSized()) {
-              // 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.
-              CloneStructTypes(ty);
-              auto* wrapper = ctx.dst->Structure(
-                  ctx.dst->Sym(),
-                  {ctx.dst->Member(kWrappedArrayMemberName, param_type)});
-              param_type = ctx.dst->ty.Of(wrapper);
-              is_wrapped = true;
-            }
-
-            param_type = ctx.dst->ty.pointer(
-                param_type, sc, var->Declaration()->declared_access);
-            auto* param =
-                ctx.dst->Param(new_var_symbol, param_type, attributes);
-            ctx.InsertFront(func_ast->params, param);
-            is_pointer = true;
-          } else if (sc == ast::StorageClass::kWorkgroup &&
-                     ContainsMatrix(var->Type())) {
-            // Due to a bug in the MSL compiler, we use a threadgroup memory
-            // argument for any workgroup allocation that contains a matrix.
-            // See crbug.com/tint/938.
-            // TODO(jrprice): Do this for all other workgroup variables too.
-
-            // Create a member in the workgroup parameter struct.
-            auto member = ctx.Clone(var->Declaration()->symbol);
-            workgroup_parameter_members.push_back(
-                ctx.dst->Member(member, store_type()));
-            CloneStructTypes(var->Type()->UnwrapRef());
-
-            // Create a function-scope variable that is a pointer to the member.
-            auto* member_ptr = ctx.dst->AddressOf(ctx.dst->MemberAccessor(
-                ctx.dst->Deref(workgroup_param()), member));
-            auto* local_var =
-                ctx.dst->Const(new_var_symbol,
-                               ctx.dst->ty.pointer(
-                                   store_type(), ast::StorageClass::kWorkgroup),
-                               member_ptr);
-            ctx.InsertFront(func_ast->body->statements,
-                            ctx.dst->Decl(local_var));
-            is_pointer = true;
-          } else {
-            // Variables in the Private and Workgroup storage classes are
-            // redeclared at function scope. Disable storage class validation on
-            // this variable.
-            auto* disable_validation =
-                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass);
-            auto* constructor = ctx.Clone(var->Declaration()->constructor);
-            auto* local_var =
-                ctx.dst->Var(new_var_symbol, store_type(), sc, constructor,
-                             ast::AttributeList{disable_validation});
-            ctx.InsertFront(func_ast->body->statements,
-                            ctx.dst->Decl(local_var));
-          }
-        } else {
-          // For a regular function, redeclare the variable as a parameter.
-          // Use a pointer for non-handle types.
-          auto* param_type = store_type();
-          ast::AttributeList attributes;
-          if (!var->Type()->UnwrapRef()->is_handle()) {
-            param_type = ctx.dst->ty.pointer(
-                param_type, sc, var->Declaration()->declared_access);
-            is_pointer = true;
-
-            // Disable validation of the parameter's storage class and of
-            // arguments passed it.
-            attributes.push_back(
-                ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
-            attributes.push_back(ctx.dst->Disable(
-                ast::DisabledValidation::kIgnoreInvalidPointerArgument));
-          }
-          ctx.InsertBack(
-              func_ast->params,
-              ctx.dst->Param(new_var_symbol, param_type, attributes));
-        }
-
-        // Replace all uses of the module-scope variable.
-        // For non-entry points, dereference non-handle pointer parameters.
-        for (auto* user : var->Users()) {
-          if (user->Stmt()->Function()->Declaration() == func_ast) {
-            const ast::Expression* expr = ctx.dst->Expr(new_var_symbol);
-            if (is_pointer) {
-              // If this identifier is used by an address-of operator, just
-              // remove the address-of instead of adding a deref, since we
-              // already have a pointer.
-              auto* ident =
-                  user->Declaration()->As<ast::IdentifierExpression>();
-              if (ident_to_address_of.count(ident)) {
-                ctx.Replace(ident_to_address_of[ident], expr);
-                continue;
-              }
-
-              expr = ctx.dst->Deref(expr);
-            }
-            if (is_wrapped) {
-              // Get the member from the wrapper structure.
-              expr = ctx.dst->MemberAccessor(expr, kWrappedArrayMemberName);
-            }
-            ctx.Replace(user->Declaration(), expr);
-          }
-        }
-
-        var_to_newvar[var] = {new_var_symbol, is_pointer, is_wrapped};
-      }
-
-      if (!workgroup_parameter_members.empty()) {
-        // Create the workgroup memory parameter.
-        // The parameter is a struct that contains members for each workgroup
-        // variable.
-        auto* str = ctx.dst->Structure(ctx.dst->Sym(),
-                                       std::move(workgroup_parameter_members));
-        auto* param_type = ctx.dst->ty.pointer(ctx.dst->ty.Of(str),
-                                               ast::StorageClass::kWorkgroup);
-        auto* disable_validation =
-            ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
-        auto* param =
-            ctx.dst->Param(workgroup_param(), param_type, {disable_validation});
-        ctx.InsertFront(func_ast->params, param);
-      }
-
-      // Pass the variables as pointers to any functions that need them.
-      for (auto* call : calls_to_replace[func_ast]) {
-        auto* target =
-            ctx.src->AST().Functions().Find(call->target.name->symbol);
-        auto* target_sem = ctx.src->Sem().Get(target);
-
-        // Add new arguments for any variables that are needed by the callee.
-        // For entry points, pass non-handle types as pointers.
-        for (auto* target_var : target_sem->TransitivelyReferencedGlobals()) {
-          auto sc = target_var->StorageClass();
-          if (sc == ast::StorageClass::kNone) {
-            continue;
-          }
-
-          auto new_var = var_to_newvar[target_var];
-          bool is_handle = target_var->Type()->UnwrapRef()->is_handle();
-          const ast::Expression* arg = ctx.dst->Expr(new_var.symbol);
-          if (new_var.is_wrapped) {
-            // The variable is wrapped in a struct, so we need to pass a pointer
-            // to the struct member instead.
-            arg = ctx.dst->AddressOf(ctx.dst->MemberAccessor(
-                ctx.dst->Deref(arg), kWrappedArrayMemberName));
-          } else if (is_entry_point && !is_handle && !new_var.is_pointer) {
-            // We need to pass a pointer and we don't already have one, so take
-            // the address of the new variable.
-            arg = ctx.dst->AddressOf(arg);
-          }
-          ctx.InsertBack(call->args, arg);
-        }
-      }
-    }
-
-    // Now remove all module-scope variables with these storage classes.
-    for (auto* var_ast : ctx.src->AST().GlobalVariables()) {
-      auto* var_sem = ctx.src->Sem().Get(var_ast);
-      if (var_sem->StorageClass() != ast::StorageClass::kNone) {
-        ctx.Remove(ctx.src->AST().GlobalDeclarations(), var_ast);
-      }
-    }
-  }
-
- private:
-  std::unordered_set<const sem::Struct*> cloned_structs_;
-};
-
-ModuleScopeVarToEntryPointParam::ModuleScopeVarToEntryPointParam() = default;
-
-ModuleScopeVarToEntryPointParam::~ModuleScopeVarToEntryPointParam() = default;
-
-bool ModuleScopeVarToEntryPointParam::ShouldRun(const Program* program,
-                                                const DataMap&) const {
-  for (auto* decl : program->AST().GlobalDeclarations()) {
-    if (decl->Is<ast::Variable>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void ModuleScopeVarToEntryPointParam::Run(CloneContext& ctx,
-                                          const DataMap&,
-                                          DataMap&) const {
-  State state{ctx};
-  state.Process();
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/module_scope_var_to_entry_point_param.h b/src/transform/module_scope_var_to_entry_point_param.h
deleted file mode 100644
index 85570a0..0000000
--- a/src/transform/module_scope_var_to_entry_point_param.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
-#define SRC_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Move module-scope variables into the entry point as parameters.
-///
-/// MSL does not allow module-scope variables to have any address space other
-/// than `constant`. This transform moves all module-scope declarations into the
-/// entry point function (either as parameters or function-scope variables) and
-/// then passes them as pointer parameters to any function that references them.
-///
-/// Since WGSL does not allow entry point parameters or function-scope variables
-/// to have these storage classes, we annotate the new variable declarations
-/// with an attribute that bypasses that validation rule.
-///
-/// Before:
-/// ```
-/// struct S {
-///   f : f32;
-/// };
-/// @binding(0) @group(0)
-/// var<storage, read> s : S;
-/// var<private> p : f32 = 2.0;
-///
-/// fn foo() {
-///   p = p + f;
-/// }
-///
-/// @stage(compute) @workgroup_size(1)
-/// fn main() {
-///   foo();
-/// }
-/// ```
-///
-/// After:
-/// ```
-/// fn foo(p : ptr<private, f32>, sptr : ptr<storage, S, read>) {
-///   *p = *p + (*sptr).f;
-/// }
-///
-/// @stage(compute) @workgroup_size(1)
-/// fn main(sptr : ptr<storage, S, read>) {
-///   var<private> p : f32 = 2.0;
-///   foo(&p, sptr);
-/// }
-/// ```
-class ModuleScopeVarToEntryPointParam
-    : public Castable<ModuleScopeVarToEntryPointParam, Transform> {
- public:
-  /// Constructor
-  ModuleScopeVarToEntryPointParam();
-  /// Destructor
-  ~ModuleScopeVarToEntryPointParam() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
-  struct State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_MODULE_SCOPE_VAR_TO_ENTRY_POINT_PARAM_H_
diff --git a/src/transform/module_scope_var_to_entry_point_param_test.cc b/src/transform/module_scope_var_to_entry_point_param_test.cc
deleted file mode 100644
index 0a0d761..0000000
--- a/src/transform/module_scope_var_to_entry_point_param_test.cc
+++ /dev/null
@@ -1,1170 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/module_scope_var_to_entry_point_param.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ModuleScopeVarToEntryPointParamTest = TransformTest;
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<ModuleScopeVarToEntryPointParam>(src));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, ShouldRunHasGlobal) {
-  auto* src = R"(
-var<private> v : i32;
-)";
-
-  EXPECT_TRUE(ShouldRun<ModuleScopeVarToEntryPointParam>(src));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Basic) {
-  auto* src = R"(
-var<private> p : f32;
-var<workgroup> w : f32;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  w = p;
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol : f32;
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
-  tint_symbol = tint_symbol_1;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Basic_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  w = p;
-}
-
-var<workgroup> w : f32;
-var<private> p : f32;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol : f32;
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
-  tint_symbol = tint_symbol_1;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, FunctionCalls) {
-  auto* src = R"(
-var<private> p : f32;
-var<workgroup> w : f32;
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  p = a;
-  w = b;
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  bar(a, b);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-)";
-
-  auto* expect = R"(
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<workgroup, f32>) {
-  *(tint_symbol) = a;
-  *(tint_symbol_1) = b;
-}
-
-fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<workgroup, f32>) {
-  let b : f32 = 2.0;
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_4 : f32;
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_5 : f32;
-  foo(1.0, &(tint_symbol_4), &(tint_symbol_5));
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, FunctionCalls_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  bar(a, b);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  p = a;
-  w = b;
-}
-
-var<private> p : f32;
-var<workgroup> w : f32;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
-  foo(1.0, &(tint_symbol), &(tint_symbol_1));
-}
-
-fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<workgroup, f32>) {
-  let b : f32 = 2.0;
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<private, f32>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_5 : ptr<workgroup, f32>) {
-  *(tint_symbol_4) = a;
-  *(tint_symbol_5) = b;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Constructors) {
-  auto* src = R"(
-var<private> a : f32 = 1.0;
-var<private> b : f32 = f32();
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x : f32 = a + b;
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32 = 1.0;
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32 = f32();
-  let x : f32 = (tint_symbol + tint_symbol_1);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Constructors_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x : f32 = a + b;
-}
-
-var<private> b : f32 = f32();
-var<private> a : f32 = 1.0;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32 = 1.0;
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32 = f32();
-  let x : f32 = (tint_symbol + tint_symbol_1);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Pointers) {
-  auto* src = R"(
-var<private> p : f32;
-var<workgroup> w : f32;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let p_ptr : ptr<private, f32> = &p;
-  let w_ptr : ptr<workgroup, f32> = &w;
-  let x : f32 = *p_ptr + *w_ptr;
-  *p_ptr = x;
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
-  let p_ptr : ptr<private, f32> = &(tint_symbol);
-  let w_ptr : ptr<workgroup, f32> = &(tint_symbol_1);
-  let x : f32 = (*(p_ptr) + *(w_ptr));
-  *(p_ptr) = x;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Pointers_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let p_ptr : ptr<private, f32> = &p;
-  let w_ptr : ptr<workgroup, f32> = &w;
-  let x : f32 = *p_ptr + *w_ptr;
-  *p_ptr = x;
-}
-
-var<workgroup> w : f32;
-var<private> p : f32;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
-  @internal(disable_validation__ignore_storage_class) var<workgroup> tint_symbol_1 : f32;
-  let p_ptr : ptr<private, f32> = &(tint_symbol);
-  let w_ptr : ptr<workgroup, f32> = &(tint_symbol_1);
-  let x : f32 = (*(p_ptr) + *(w_ptr));
-  *(p_ptr) = x;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, FoldAddressOfDeref) {
-  auto* src = R"(
-var<private> v : f32;
-
-fn bar(p : ptr<private, f32>) {
-  (*p) = 0.0;
-}
-
-fn foo() {
-  bar(&v);
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo();
-}
-)";
-
-  auto* expect = R"(
-fn bar(p : ptr<private, f32>) {
-  *(p) = 0.0;
-}
-
-fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<private, f32>) {
-  bar(tint_symbol);
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol_1 : f32;
-  foo(&(tint_symbol_1));
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, FoldAddressOfDeref_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo();
-}
-
-fn foo() {
-  bar(&v);
-}
-
-fn bar(p : ptr<private, f32>) {
-  (*p) = 0.0;
-}
-
-var<private> v : f32;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  @internal(disable_validation__ignore_storage_class) var<private> tint_symbol : f32;
-  foo(&(tint_symbol));
-}
-
-fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<private, f32>) {
-  bar(tint_symbol_1);
-}
-
-fn bar(p : ptr<private, f32>) {
-  *(p) = 0.0;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_Basic) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u : S;
-@group(0) @binding(1)
-var<storage> s : S;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = u;
-  _ = s;
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
-  _ = *(tint_symbol);
-  _ = *(tint_symbol_1);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_Basic_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = u;
-  _ = s;
-}
-
-@group(0) @binding(0) var<uniform> u : S;
-@group(0) @binding(1) var<storage> s : S;
-
-struct S {
-  a : f32;
-};
-
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
-  _ = *(tint_symbol);
-  _ = *(tint_symbol_1);
-}
-
-struct S {
-  a : f32;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray) {
-  auto* src = R"(
-@group(0) @binding(0)
-var<storage> buffer : array<f32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  arr : array<f32>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-
-@group(0) @binding(0)
-var<storage> buffer : array<f32>;
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  arr : array<f32>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArrayInsideFunction) {
-  auto* src = R"(
-@group(0) @binding(0)
-var<storage> buffer : array<f32>;
-
-fn foo() {
-  _ = buffer[0];
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo();
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  arr : array<f32>;
-}
-
-fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<storage, array<f32>>) {
-  _ = (*(tint_symbol))[0];
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, tint_symbol_2>) {
-  foo(&((*(tint_symbol_1)).arr));
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest,
-       Buffer_RuntimeArrayInsideFunction_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo();
-}
-
-fn foo() {
-  _ = buffer[0];
-}
-
-@group(0) @binding(0) var<storage> buffer : array<f32>;
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  arr : array<f32>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  foo(&((*(tint_symbol)).arr));
-}
-
-fn foo(@internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<storage, array<f32>>) {
-  _ = (*(tint_symbol_2))[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_RuntimeArray_Alias) {
-  auto* src = R"(
-type myarray = array<f32>;
-
-@group(0) @binding(0)
-var<storage> buffer : myarray;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  arr : array<f32>;
-}
-
-type myarray = array<f32>;
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest,
-       Buffer_RuntimeArray_Alias_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-
-@group(0) @binding(0) var<storage> buffer : myarray;
-
-type myarray = array<f32>;
-)";
-
-  auto* expect = R"(
-struct tint_symbol_1 {
-  arr : array<f32>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-
-type myarray = array<f32>;
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_ArrayOfStruct) {
-  auto* src = R"(
-struct S {
-  f : f32;
-};
-
-@group(0) @binding(0)
-var<storage> buffer : array<S>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  f : f32;
-}
-
-struct tint_symbol_1 {
-  arr : array<S>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffer_ArrayOfStruct_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = buffer[0];
-}
-
-@group(0) @binding(0) var<storage> buffer : array<S>;
-
-struct S {
-  f : f32;
-};
-)";
-
-  auto* expect = R"(
-struct S {
-  f : f32;
-}
-
-struct tint_symbol_1 {
-  arr : array<S>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<storage, tint_symbol_1>) {
-  _ = (*(tint_symbol)).arr[0];
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_FunctionCalls) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u : S;
-@group(0) @binding(1)
-var<storage> s : S;
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  _ = u;
-  _ = s;
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  _ = u;
-  bar(a, b);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<storage, S>) {
-  _ = *(tint_symbol);
-  _ = *(tint_symbol_1);
-}
-
-fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<storage, S>) {
-  let b : f32 = 2.0;
-  _ = *(tint_symbol_2);
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_4 : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_5 : ptr<storage, S>) {
-  foo(1.0, tint_symbol_4, tint_symbol_5);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Buffers_FunctionCalls_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  _ = u;
-  bar(a, b);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  _ = u;
-  _ = s;
-}
-
-struct S {
-  a : f32;
-};
-
-@group(0) @binding(0)
-var<uniform> u : S;
-@group(0) @binding(1)
-var<storage> s : S;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol : ptr<uniform, S>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) @internal(disable_validation__ignore_storage_class) tint_symbol_1 : ptr<storage, S>) {
-  foo(1.0, tint_symbol, tint_symbol_1);
-}
-
-fn foo(a : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<storage, S>) {
-  let b : f32 = 2.0;
-  _ = *(tint_symbol_2);
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<uniform, S>, @internal(disable_validation__ignore_storage_class) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_5 : ptr<storage, S>) {
-  _ = *(tint_symbol_4);
-  _ = *(tint_symbol_5);
-}
-
-struct S {
-  a : f32;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, HandleTypes_Basic) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-@group(0) @binding(1) var s : sampler;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  _ = t;
-  _ = s;
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_1 : sampler) {
-  _ = tint_symbol;
-  _ = tint_symbol_1;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, HandleTypes_FunctionCalls) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-@group(0) @binding(1) var s : sampler;
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  _ = t;
-  _ = s;
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  _ = t;
-  bar(a, b);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-)";
-
-  auto* expect = R"(
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, tint_symbol : texture_2d<f32>, tint_symbol_1 : sampler) {
-  _ = tint_symbol;
-  _ = tint_symbol_1;
-}
-
-fn foo(a : f32, tint_symbol_2 : texture_2d<f32>, tint_symbol_3 : sampler) {
-  let b : f32 = 2.0;
-  _ = tint_symbol_2;
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol_4 : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_5 : sampler) {
-  foo(1.0, tint_symbol_4, tint_symbol_5);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest,
-       HandleTypes_FunctionCalls_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  foo(1.0);
-}
-
-fn foo(a : f32) {
-  let b : f32 = 2.0;
-  _ = t;
-  bar(a, b);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32) {
-  _ = t;
-  _ = s;
-}
-
-@group(0) @binding(0) var t : texture_2d<f32>;
-@group(0) @binding(1) var s : sampler;
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn main(@group(0) @binding(0) @internal(disable_validation__entry_point_parameter) tint_symbol : texture_2d<f32>, @group(0) @binding(1) @internal(disable_validation__entry_point_parameter) tint_symbol_1 : sampler) {
-  foo(1.0, tint_symbol, tint_symbol_1);
-}
-
-fn foo(a : f32, tint_symbol_2 : texture_2d<f32>, tint_symbol_3 : sampler) {
-  let b : f32 = 2.0;
-  _ = tint_symbol_2;
-  bar(a, b, tint_symbol_2, tint_symbol_3);
-  no_uses();
-}
-
-fn no_uses() {
-}
-
-fn bar(a : f32, b : f32, tint_symbol_4 : texture_2d<f32>, tint_symbol_5 : sampler) {
-  _ = tint_symbol_4;
-  _ = tint_symbol_5;
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, Matrix) {
-  auto* src = R"(
-var<workgroup> m : mat2x2<f32>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = m;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  m : mat2x2<f32>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_2>) {
-  let tint_symbol : ptr<workgroup, mat2x2<f32>> = &((*(tint_symbol_1)).m);
-  let x = *(tint_symbol);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, NestedMatrix) {
-  auto* src = R"(
-struct S1 {
-  m : mat2x2<f32>;
-};
-struct S2 {
-  s : S1;
-};
-var<workgroup> m : array<S2, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = m;
-}
-)";
-
-  auto* expect = R"(
-struct S1 {
-  m : mat2x2<f32>;
-}
-
-struct S2 {
-  s : S1;
-}
-
-struct tint_symbol_2 {
-  m : array<S2, 4u>;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_2>) {
-  let tint_symbol : ptr<workgroup, array<S2, 4u>> = &((*(tint_symbol_1)).m);
-  let x = *(tint_symbol);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test that we do not duplicate a struct type used by multiple workgroup
-// variables that are promoted to threadgroup memory arguments.
-TEST_F(ModuleScopeVarToEntryPointParamTest, DuplicateThreadgroupArgumentTypes) {
-  auto* src = R"(
-struct S {
-  m : mat2x2<f32>;
-};
-
-var<workgroup> a : S;
-
-var<workgroup> b : S;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = a;
-  let y = b;
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  m : mat2x2<f32>;
-}
-
-struct tint_symbol_3 {
-  a : S;
-  b : S;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_3>) {
-  let tint_symbol : ptr<workgroup, S> = &((*(tint_symbol_1)).a);
-  let tint_symbol_2 : ptr<workgroup, S> = &((*(tint_symbol_1)).b);
-  let x = *(tint_symbol);
-  let y = *(tint_symbol_2);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test that we do not duplicate a struct type used by multiple workgroup
-// variables that are promoted to threadgroup memory arguments.
-TEST_F(ModuleScopeVarToEntryPointParamTest,
-       DuplicateThreadgroupArgumentTypes_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = a;
-  let y = b;
-}
-
-var<workgroup> a : S;
-var<workgroup> b : S;
-
-struct S {
-  m : mat2x2<f32>;
-};
-)";
-
-  auto* expect = R"(
-struct S {
-  m : mat2x2<f32>;
-}
-
-struct tint_symbol_3 {
-  a : S;
-  b : S;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(@internal(disable_validation__entry_point_parameter) tint_symbol_1 : ptr<workgroup, tint_symbol_3>) {
-  let tint_symbol : ptr<workgroup, S> = &((*(tint_symbol_1)).a);
-  let tint_symbol_2 : ptr<workgroup, S> = &((*(tint_symbol_1)).b);
-  let x = *(tint_symbol);
-  let y = *(tint_symbol_2);
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, UnusedVariables) {
-  auto* src = R"(
-struct S {
-  a : f32;
-};
-
-var<private> p : f32;
-var<workgroup> w : f32;
-
-@group(0) @binding(0)
-var<uniform> ub : S;
-@group(0) @binding(1)
-var<storage> sb : S;
-
-@group(0) @binding(2) var t : texture_2d<f32>;
-@group(0) @binding(3) var s : sampler;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-}
-)";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ModuleScopeVarToEntryPointParamTest, EmtpyModule) {
-  auto* src = "";
-
-  auto got = Run<ModuleScopeVarToEntryPointParam>(src);
-
-  EXPECT_EQ(src, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/multiplanar_external_texture.cc b/src/transform/multiplanar_external_texture.cc
deleted file mode 100644
index 360a18d..0000000
--- a/src/transform/multiplanar_external_texture.cc
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/multiplanar_external_texture.h"
-
-#include <string>
-#include <vector>
-
-#include "src/ast/function.h"
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/function.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture);
-TINT_INSTANTIATE_TYPEINFO(
-    tint::transform::MultiplanarExternalTexture::NewBindingPoints);
-
-namespace tint {
-namespace transform {
-namespace {
-
-/// This struct stores symbols for new bindings created as a result of
-/// transforming a texture_external instance.
-struct NewBindingSymbols {
-  Symbol params;
-  Symbol plane_0;
-  Symbol plane_1;
-};
-}  // namespace
-
-/// State holds the current transform state
-struct MultiplanarExternalTexture::State {
-  /// The clone context.
-  CloneContext& ctx;
-
-  /// ProgramBuilder for the context
-  ProgramBuilder& b;
-
-  /// Destination binding locations for the expanded texture_external provided
-  /// as input into the transform.
-  const NewBindingPoints* new_binding_points;
-
-  /// Symbol for the ExternalTextureParams struct
-  Symbol params_struct_sym;
-
-  /// Symbol for the textureLoadExternal function
-  Symbol texture_load_external_sym;
-
-  /// Symbol for the textureSampleExternal function
-  Symbol texture_sample_external_sym;
-
-  /// Storage for new bindings that have been created corresponding to an
-  /// original texture_external binding.
-  std::unordered_map<const sem::Variable*, NewBindingSymbols>
-      new_binding_symbols;
-
-  /// Constructor
-  /// @param context the clone
-  /// @param newBindingPoints the input destination binding locations for the
-  /// expanded texture_external
-  State(CloneContext& context, const NewBindingPoints* newBindingPoints)
-      : ctx(context), b(*context.dst), new_binding_points(newBindingPoints) {}
-
-  /// Processes the module
-  void Process() {
-    auto& sem = ctx.src->Sem();
-
-    // For each texture_external binding, we replace it with a texture_2d<f32>
-    // binding and create two additional bindings (one texture_2d<f32> to
-    // represent the secondary plane and one uniform buffer for the
-    // ExternalTextureParams struct).
-    for (auto* var : ctx.src->AST().GlobalVariables()) {
-      auto* sem_var = sem.Get(var);
-      if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) {
-        continue;
-      }
-
-      // If the attributes are empty, then this must be a texture_external
-      // passed as a function parameter. These variables are transformed
-      // elsewhere.
-      if (var->attributes.empty()) {
-        continue;
-      }
-
-      // If we find a texture_external binding, we know we must emit the
-      // ExternalTextureParams struct.
-      if (!params_struct_sym.IsValid()) {
-        createExtTexParamsStruct();
-      }
-
-      // The binding points for the newly introduced bindings must have been
-      // provided to this transform. We fetch the new binding points by
-      // providing the original texture_external binding points into the
-      // passed map.
-      BindingPoint bp = {var->BindingPoint().group->value,
-                         var->BindingPoint().binding->value};
-
-      BindingsMap::const_iterator it =
-          new_binding_points->bindings_map.find(bp);
-      if (it == new_binding_points->bindings_map.end()) {
-        b.Diagnostics().add_error(
-            diag::System::Transform,
-            "missing new binding points for texture_external at binding {" +
-                std::to_string(bp.group) + "," + std::to_string(bp.binding) +
-                "}");
-        continue;
-      }
-
-      BindingPoints bps = it->second;
-
-      // Symbols for the newly created bindings must be saved so they can be
-      // passed as parameters later. These are placed in a map and keyed by
-      // the source symbol associated with the texture_external binding that
-      // corresponds with the new destination bindings.
-      // NewBindingSymbols new_binding_syms;
-      auto& syms = new_binding_symbols[sem_var];
-      syms.plane_0 = ctx.Clone(var->symbol);
-      syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
-      b.Global(syms.plane_1,
-               b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-               b.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding));
-      syms.params = b.Symbols().New("ext_tex_params");
-      b.Global(syms.params, b.ty.type_name("ExternalTextureParams"),
-               ast::StorageClass::kUniform,
-               b.GroupAndBinding(bps.params.group, bps.params.binding));
-
-      // Replace the original texture_external binding with a texture_2d<f32>
-      // binding.
-      ast::AttributeList cloned_attributes = ctx.Clone(var->attributes);
-      const ast::Expression* cloned_constructor = ctx.Clone(var->constructor);
-
-      auto* replacement =
-          b.Var(syms.plane_0,
-                b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                cloned_constructor, cloned_attributes);
-      ctx.Replace(var, replacement);
-    }
-
-    // We must update all the texture_external parameters for user declared
-    // functions.
-    for (auto* fn : ctx.src->AST().Functions()) {
-      for (const ast::Variable* param : fn->params) {
-        if (auto* sem_var = sem.Get(param)) {
-          if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) {
-            continue;
-          }
-          // If we find a texture_external, we must ensure the
-          // ExternalTextureParams struct exists.
-          if (!params_struct_sym.IsValid()) {
-            createExtTexParamsStruct();
-          }
-          // When a texture_external is found, we insert all components
-          // the texture_external into the parameter list. We must also place
-          // the new symbols into the transform state so they can be used when
-          // transforming function calls.
-          auto& syms = new_binding_symbols[sem_var];
-          syms.plane_0 = ctx.Clone(param->symbol);
-          syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
-          syms.params = b.Symbols().New("ext_tex_params");
-          auto tex2d_f32 = [&] {
-            return b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32());
-          };
-          ctx.Replace(param, b.Param(syms.plane_0, tex2d_f32()));
-          ctx.InsertAfter(fn->params, param,
-                          b.Param(syms.plane_1, tex2d_f32()));
-          ctx.InsertAfter(
-              fn->params, param,
-              b.Param(syms.params, b.ty.type_name(params_struct_sym)));
-        }
-      }
-    }
-
-    // Transform the original textureLoad and textureSampleLevel calls into
-    // textureLoadExternal and textureSampleExternal calls.
-    ctx.ReplaceAll(
-        [&](const ast::CallExpression* expr) -> const ast::CallExpression* {
-          auto* builtin = sem.Get(expr)->Target()->As<sem::Builtin>();
-
-          if (builtin && !builtin->Parameters().empty() &&
-              builtin->Parameters()[0]->Type()->Is<sem::ExternalTexture>() &&
-              builtin->Type() != sem::BuiltinType::kTextureDimensions) {
-            if (auto* var_user = sem.Get<sem::VariableUser>(expr->args[0])) {
-              auto it = new_binding_symbols.find(var_user->Variable());
-              if (it == new_binding_symbols.end()) {
-                // If valid new binding locations were not provided earlier, we
-                // would have been unable to create these symbols. An error
-                // message was emitted earlier, so just return early to avoid
-                // internal compiler errors and retain a clean error message.
-                return nullptr;
-              }
-              auto& syms = it->second;
-
-              if (builtin->Type() == sem::BuiltinType::kTextureLoad) {
-                return createTexLdExt(expr, syms);
-              }
-
-              if (builtin->Type() == sem::BuiltinType::kTextureSampleLevel) {
-                return createTexSmpExt(expr, syms);
-              }
-            }
-
-          } else if (sem.Get(expr)->Target()->Is<sem::Function>()) {
-            // The call expression may be to a user-defined function that
-            // contains a texture_external parameter. These need to be expanded
-            // out to multiple plane textures and the texture parameters
-            // structure.
-            for (auto* arg : expr->args) {
-              if (auto* var_user = sem.Get<sem::VariableUser>(arg)) {
-                // Check if a parameter is a texture_external by trying to find
-                // it in the transform state.
-                auto it = new_binding_symbols.find(var_user->Variable());
-                if (it != new_binding_symbols.end()) {
-                  auto& syms = it->second;
-                  // When we find a texture_external, we must unpack it into its
-                  // components.
-                  ctx.Replace(arg, b.Expr(syms.plane_0));
-                  ctx.InsertAfter(expr->args, arg, b.Expr(syms.plane_1));
-                  ctx.InsertAfter(expr->args, arg, b.Expr(syms.params));
-                }
-              }
-            }
-          }
-
-          return nullptr;
-        });
-  }
-
-  /// Creates the ExternalTextureParams struct.
-  void createExtTexParamsStruct() {
-    ast::StructMemberList member_list = {
-        b.Member("numPlanes", b.ty.u32()), b.Member("vr", b.ty.f32()),
-        b.Member("ug", b.ty.f32()), b.Member("vg", b.ty.f32()),
-        b.Member("ub", b.ty.f32())};
-
-    params_struct_sym = b.Symbols().New("ExternalTextureParams");
-
-    b.Structure(params_struct_sym, member_list);
-  }
-
-  /// Constructs a StatementList containing all the statements making up the
-  /// bodies of the textureSampleExternal and textureLoadExternal functions.
-  /// @param call_type determines which function body to generate
-  /// @returns a statement list that makes of the body of the chosen function
-  ast::StatementList createTexFnExtStatementList(sem::BuiltinType call_type) {
-    using f32 = ProgramBuilder::f32;
-    const ast::CallExpression* single_plane_call = nullptr;
-    const ast::CallExpression* plane_0_call = nullptr;
-    const ast::CallExpression* plane_1_call = nullptr;
-    if (call_type == sem::BuiltinType::kTextureSampleLevel) {
-      // textureSampleLevel(plane0, smp, coord.xy, 0.0);
-      single_plane_call =
-          b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
-      // textureSampleLevel(plane0, smp, coord.xy, 0.0);
-      plane_0_call =
-          b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
-      // textureSampleLevel(plane1, smp, coord.xy, 0.0);
-      plane_1_call =
-          b.Call("textureSampleLevel", "plane1", "smp", "coord", 0.0f);
-    } else if (call_type == sem::BuiltinType::kTextureLoad) {
-      // textureLoad(plane0, coords.xy, 0);
-      single_plane_call = b.Call("textureLoad", "plane0", "coord", 0);
-      // textureLoad(plane0, coords.xy, 0);
-      plane_0_call = b.Call("textureLoad", "plane0", "coord", 0);
-      // textureLoad(plane1, coords.xy, 0);
-      plane_1_call = b.Call("textureLoad", "plane1", "coord", 0);
-    } else {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "unhandled builtin: " << call_type;
-    }
-
-    return {
-        // if (params.numPlanes == 1u) {
-        //    return singlePlaneCall
-        // }
-        b.If(b.create<ast::BinaryExpression>(
-                 ast::BinaryOp::kEqual, b.MemberAccessor("params", "numPlanes"),
-                 b.Expr(1u)),
-             b.Block(b.Return(single_plane_call))),
-        // let y = plane0Call.r - 0.0625;
-        b.Decl(b.Const("y", nullptr,
-                       b.Sub(b.MemberAccessor(plane_0_call, "r"), 0.0625f))),
-        // let uv = plane1Call.rg - 0.5;
-        b.Decl(b.Const("uv", nullptr,
-                       b.Sub(b.MemberAccessor(plane_1_call, "rg"), 0.5f))),
-        // let u = uv.x;
-        b.Decl(b.Const("u", nullptr, b.MemberAccessor("uv", "x"))),
-        // let v = uv.y;
-        b.Decl(b.Const("v", nullptr, b.MemberAccessor("uv", "y"))),
-        // let r = 1.164 * y + params.vr * v;
-        b.Decl(b.Const("r", nullptr,
-                       b.Add(b.Mul(1.164f, "y"),
-                             b.Mul(b.MemberAccessor("params", "vr"), "v")))),
-        // let g = 1.164 * y - params.ug * u - params.vg * v;
-        b.Decl(
-            b.Const("g", nullptr,
-                    b.Sub(b.Sub(b.Mul(1.164f, "y"),
-                                b.Mul(b.MemberAccessor("params", "ug"), "u")),
-                          b.Mul(b.MemberAccessor("params", "vg"), "v")))),
-        // let b = 1.164 * y + params.ub * u;
-        b.Decl(b.Const("b", nullptr,
-                       b.Add(b.Mul(1.164f, "y"),
-                             b.Mul(b.MemberAccessor("params", "ub"), "u")))),
-        // return vec4<f32>(r, g, b, 1.0);
-        b.Return(b.vec4<f32>("r", "g", "b", 1.0f)),
-    };
-  }
-
-  /// Creates the textureSampleExternal function if needed and returns a call
-  /// expression to it.
-  /// @param expr the call expression being transformed
-  /// @param syms the expanded symbols to be used in the new call
-  /// @returns a call expression to textureSampleExternal
-  const ast::CallExpression* createTexSmpExt(const ast::CallExpression* expr,
-                                             NewBindingSymbols syms) {
-    ast::ExpressionList params;
-    const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
-
-    if (expr->args.size() != 3) {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "expected textureSampleLevel call with a "
-             "texture_external to have 3 parameters, found "
-          << expr->args.size() << " parameters";
-    }
-
-    if (!texture_sample_external_sym.IsValid()) {
-      texture_sample_external_sym = b.Symbols().New("textureSampleExternal");
-
-      // Emit the textureSampleExternal function.
-      ast::VariableList varList = {
-          b.Param("plane0",
-                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
-          b.Param("plane1",
-                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
-          b.Param("smp", b.ty.sampler(ast::SamplerKind::kSampler)),
-          b.Param("coord", b.ty.vec2(b.ty.f32())),
-          b.Param("params", b.ty.type_name(params_struct_sym))};
-
-      ast::StatementList statementList =
-          createTexFnExtStatementList(sem::BuiltinType::kTextureSampleLevel);
-
-      b.Func(texture_sample_external_sym, varList, b.ty.vec4(b.ty.f32()),
-             statementList, {});
-    }
-
-    const ast::IdentifierExpression* exp = b.Expr(texture_sample_external_sym);
-    params = {plane_0_binding_param, b.Expr(syms.plane_1),
-              ctx.Clone(expr->args[1]), ctx.Clone(expr->args[2]),
-              b.Expr(syms.params)};
-    return b.Call(exp, params);
-  }
-
-  /// Creates the textureLoadExternal function if needed and returns a call
-  /// expression to it.
-  /// @param expr the call expression being transformed
-  /// @param syms the expanded symbols to be used in the new call
-  /// @returns a call expression to textureLoadExternal
-  const ast::CallExpression* createTexLdExt(const ast::CallExpression* expr,
-                                            NewBindingSymbols syms) {
-    ast::ExpressionList params;
-    const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
-
-    if (expr->args.size() != 2) {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "expected textureLoad call with a texture_external "
-             "to have 2 parameters, found "
-          << expr->args.size() << " parameters";
-    }
-
-    if (!texture_load_external_sym.IsValid()) {
-      texture_load_external_sym = b.Symbols().New("textureLoadExternal");
-
-      // Emit the textureLoadExternal function.
-      ast::VariableList var_list = {
-          b.Param("plane0",
-                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
-          b.Param("plane1",
-                  b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
-          b.Param("coord", b.ty.vec2(b.ty.i32())),
-          b.Param("params", b.ty.type_name(params_struct_sym))};
-
-      ast::StatementList statement_list =
-          createTexFnExtStatementList(sem::BuiltinType::kTextureLoad);
-
-      b.Func(texture_load_external_sym, var_list, b.ty.vec4(b.ty.f32()),
-             statement_list, {});
-    }
-
-    const ast::IdentifierExpression* exp = b.Expr(texture_load_external_sym);
-    params = {plane_0_binding_param, b.Expr(syms.plane_1),
-              ctx.Clone(expr->args[1]), b.Expr(syms.params)};
-    return b.Call(exp, params);
-  }
-};
-
-MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints(
-    BindingsMap inputBindingsMap)
-    : bindings_map(std::move(inputBindingsMap)) {}
-MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default;
-
-MultiplanarExternalTexture::MultiplanarExternalTexture() = default;
-MultiplanarExternalTexture::~MultiplanarExternalTexture() = default;
-
-bool MultiplanarExternalTexture::ShouldRun(const Program* program,
-                                           const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* ty = node->As<ast::Type>()) {
-      if (program->Sem().Get<sem::ExternalTexture>(ty)) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-// Within this transform, an instance of a texture_external binding is unpacked
-// into two texture_2d<f32> bindings representing two possible planes of a
-// single texture and a uniform buffer binding representing a struct of
-// parameters. Calls to textureLoad or textureSampleLevel that contain a
-// texture_external parameter will be transformed into a newly generated version
-// of the function, which can perform the desired operation on a single RGBA
-// plane or on separate Y and UV planes.
-void MultiplanarExternalTexture::Run(CloneContext& ctx,
-                                     const DataMap& inputs,
-                                     DataMap&) const {
-  auto* new_binding_points = inputs.Get<NewBindingPoints>();
-
-  if (!new_binding_points) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing new binding point data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  State state(ctx, new_binding_points);
-
-  state.Process();
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/multiplanar_external_texture.h b/src/transform/multiplanar_external_texture.h
deleted file mode 100644
index caf55e8..0000000
--- a/src/transform/multiplanar_external_texture.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
-#define SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
-
-#include <unordered_map>
-#include <utility>
-
-#include "src/ast/struct_member.h"
-#include "src/sem/binding_point.h"
-#include "src/sem/builtin_type.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// BindingPoint is an alias to sem::BindingPoint
-using BindingPoint = sem::BindingPoint;
-
-/// This struct identifies the binding groups and locations for new bindings to
-/// use when transforming a texture_external instance.
-struct BindingPoints {
-  /// The desired binding location of the texture_2d representing plane #1 when
-  /// a texture_external binding is expanded.
-  BindingPoint plane_1;
-  /// The desired binding location of the ExternalTextureParams uniform when a
-  /// texture_external binding is expanded.
-  BindingPoint params;
-};
-
-/// Within the MultiplanarExternalTexture transform, each instance of a
-/// texture_external binding is unpacked into two texture_2d<f32> bindings
-/// representing two possible planes of a texture and a uniform buffer binding
-/// representing a struct of parameters. Calls to textureLoad or
-/// textureSampleLevel that contain a texture_external parameter will be
-/// transformed into a newly generated version of the function, which can
-/// perform the desired operation on a single RGBA plane or on seperate Y and UV
-/// planes.
-class MultiplanarExternalTexture
-    : public Castable<MultiplanarExternalTexture, Transform> {
- public:
-  /// BindingsMap is a map where the key is the binding location of a
-  /// texture_external and the value is a struct containing the desired
-  /// locations for new bindings expanded from the texture_external instance.
-  using BindingsMap = std::unordered_map<BindingPoint, BindingPoints>;
-
-  /// NewBindingPoints is consumed by the MultiplanarExternalTexture transform.
-  /// Data holds information about location of each texture_external binding and
-  /// which binding slots it should expand into.
-  struct NewBindingPoints : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param bm a map to the new binding slots to use.
-    explicit NewBindingPoints(BindingsMap bm);
-
-    /// Destructor
-    ~NewBindingPoints() override;
-
-    /// A map of new binding points to use.
-    const BindingsMap bindings_map;
-  };
-
-  /// Constructor
-  MultiplanarExternalTexture();
-  /// Destructor
-  ~MultiplanarExternalTexture() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  struct State;
-
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
diff --git a/src/transform/multiplanar_external_texture_test.cc b/src/transform/multiplanar_external_texture_test.cc
deleted file mode 100644
index 8a43308..0000000
--- a/src/transform/multiplanar_external_texture_test.cc
+++ /dev/null
@@ -1,1297 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/multiplanar_external_texture.h"
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using MultiplanarExternalTextureTest = TransformTest;
-
-TEST_F(MultiplanarExternalTextureTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<MultiplanarExternalTexture>(src));
-}
-
-TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureAlias) {
-  auto* src = R"(
-type ET = texture_external;
-)";
-
-  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
-}
-TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureGlobal) {
-  auto* src = R"(
-[[group(0), binding(0)]] var ext_tex : texture_external;
-)";
-
-  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
-}
-
-TEST_F(MultiplanarExternalTextureTest, ShouldRunHasExternalTextureParam) {
-  auto* src = R"(
-fn f(ext_tex : texture_external) {}
-)";
-
-  EXPECT_TRUE(ShouldRun<MultiplanarExternalTexture>(src));
-}
-
-// Running the transform without passing in data for the new bindings should
-// result in an error.
-TEST_F(MultiplanarExternalTextureTest, ErrorNoPassedData) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy);
-}
-)";
-  auto* expect =
-      R"(error: missing new binding point data for tint::transform::MultiplanarExternalTexture)";
-
-  auto got = Run<MultiplanarExternalTexture>(src);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Running the transform with incorrect binding data should result in an error.
-TEST_F(MultiplanarExternalTextureTest, ErrorIncorrectBindingPont) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy);
-}
-)";
-
-  auto* expect =
-      R"(error: missing new binding points for texture_external at binding {0,1})";
-
-  DataMap data;
-  // This bindings map specifies 0,0 as the location of the texture_external,
-  // which is incorrect.
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with a textureDimensions call.
-TEST_F(MultiplanarExternalTextureTest, Dimensions) {
-  auto* src = R"(
-@group(0) @binding(0) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(ext_tex);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(ext_tex);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with a textureDimensions call.
-TEST_F(MultiplanarExternalTextureTest, Dimensions_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(ext_tex);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  var dim : vec2<i32>;
-  dim = textureDimensions(ext_tex);
-  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test that the transform works with a textureSampleLevel call.
-TEST_F(MultiplanarExternalTextureTest, BasicTextureSampleLevel) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var ext_tex : texture_2d<f32>;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params);
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Test that the transform works with a textureSampleLevel call.
-TEST_F(MultiplanarExternalTextureTest, BasicTextureSampleLevel_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy);
-}
-
-@group(0) @binding(1) var ext_tex : texture_external;
-@group(0) @binding(0) var s : sampler;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params);
-}
-
-@group(0) @binding(1) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(0) var s : sampler;
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with a textureLoad call.
-TEST_F(MultiplanarExternalTextureTest, BasicTextureLoad) {
-  auto* src = R"(
-@group(0) @binding(0) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(ext_tex, vec2<i32>(1, 1));
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureLoad(plane0, coord, 0);
-  }
-  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
-  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params);
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with a textureLoad call.
-TEST_F(MultiplanarExternalTextureTest, BasicTextureLoad_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoad(ext_tex, vec2<i32>(1, 1));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(2) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureLoad(plane0, coord, 0);
-  }
-  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
-  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with both a textureSampleLevel and textureLoad
-// call.
-TEST_F(MultiplanarExternalTextureTest, TextureSampleAndTextureLoad) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy) + textureLoad(ext_tex, vec2<i32>(1, 1));
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var ext_tex : texture_2d<f32>;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureLoad(plane0, coord, 0);
-  }
-  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
-  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return (textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params));
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with both a textureSampleLevel and textureLoad
-// call.
-TEST_F(MultiplanarExternalTextureTest, TextureSampleAndTextureLoad_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy) + textureLoad(ext_tex, vec2<i32>(1, 1));
-}
-
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureLoad(plane0, coord, 0);
-  }
-  let y = (textureLoad(plane0, coord, 0).r - 0.0625);
-  let uv = (textureLoad(plane1, coord, 0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return (textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureLoadExternal(ext_tex, ext_tex_plane_1, vec2<i32>(1, 1), ext_tex_params));
-}
-
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var ext_tex : texture_2d<f32>;
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with many instances of texture_external.
-TEST_F(MultiplanarExternalTextureTest, ManyTextureSampleLevel) {
-  auto* src = R"(
-@group(0) @binding(0) var s : sampler;
-@group(0) @binding(1) var ext_tex : texture_external;
-@group(0) @binding(2) var ext_tex_1 : texture_external;
-@group(0) @binding(3) var ext_tex_2 : texture_external;
-@group(1) @binding(0) var ext_tex_3 : texture_external;
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return textureSampleLevel(ext_tex, s, coord.xy) + textureSampleLevel(ext_tex_1, s, coord.xy) + textureSampleLevel(ext_tex_2, s, coord.xy) + textureSampleLevel(ext_tex_3, s, coord.xy);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(4) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(5) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(6) var ext_tex_plane_1_1 : texture_2d<f32>;
-
-@group(0) @binding(7) var<uniform> ext_tex_params_1 : ExternalTextureParams;
-
-@group(0) @binding(8) var ext_tex_plane_1_2 : texture_2d<f32>;
-
-@group(0) @binding(9) var<uniform> ext_tex_params_2 : ExternalTextureParams;
-
-@group(1) @binding(1) var ext_tex_plane_1_3 : texture_2d<f32>;
-
-@group(1) @binding(2) var<uniform> ext_tex_params_3 : ExternalTextureParams;
-
-@group(0) @binding(0) var s : sampler;
-
-@group(0) @binding(1) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(2) var ext_tex_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var ext_tex_2 : texture_2d<f32>;
-
-@group(1) @binding(0) var ext_tex_3 : texture_2d<f32>;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-@stage(fragment)
-fn main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> {
-  return (((textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureSampleExternal(ext_tex_1, ext_tex_plane_1_1, s, coord.xy, ext_tex_params_1)) + textureSampleExternal(ext_tex_2, ext_tex_plane_1_2, s, coord.xy, ext_tex_params_2)) + textureSampleExternal(ext_tex_3, ext_tex_plane_1_3, s, coord.xy, ext_tex_params_3));
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 1}, {{0, 4}, {0, 5}}},
-          {{0, 2}, {{0, 6}, {0, 7}}},
-          {{0, 3}, {{0, 8}, {0, 9}}},
-          {{1, 0}, {{1, 1}, {1, 2}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the texture_external passed as a function parameter produces the
-// correct output.
-TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParam) {
-  auto* src = R"(
-fn f(t : texture_external, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the texture_external passed as a function parameter produces the
-// correct output.
-TEST_F(MultiplanarExternalTextureTest,
-       ExternalTexturePassedAsParam_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp);
-}
-
-fn f(t : texture_external, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the texture_external passed as a parameter not in the first
-// position produces the correct output.
-TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsSecondParam) {
-  auto* src = R"(
-fn f(s : sampler, t : texture_external) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(smp, ext_tex);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(s : sampler, t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(smp, ext_tex, ext_tex_plane_1, ext_tex_params);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that multiple texture_external params passed to a function produces the
-// correct output.
-TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParamMultiple) {
-  auto* src = R"(
-fn f(t : texture_external, s : sampler, t2 : texture_external) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-  textureSampleLevel(t2, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-@group(0) @binding(2) var ext_tex2 : texture_external;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp, ext_tex2);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(4) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(5) var ext_tex_plane_1_1 : texture_2d<f32>;
-
-@group(0) @binding(6) var<uniform> ext_tex_params_1 : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
-  textureSampleExternal(t, ext_tex_plane_1_2, s, vec2<f32>(1.0, 2.0), ext_tex_params_2);
-  textureSampleExternal(t2, ext_tex_plane_1_3, s, vec2<f32>(1.0, 2.0), ext_tex_params_3);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@group(0) @binding(2) var ext_tex2 : texture_2d<f32>;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp, ext_tex2, ext_tex_plane_1_1, ext_tex_params_1);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 3}, {0, 4}}},
-          {{0, 2}, {{0, 5}, {0, 6}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that multiple texture_external params passed to a function produces the
-// correct output.
-TEST_F(MultiplanarExternalTextureTest,
-       ExternalTexturePassedAsParamMultiple_OutOfOrder) {
-  auto* src = R"(
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp, ext_tex2);
-}
-
-fn f(t : texture_external, s : sampler, t2 : texture_external) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-  textureSampleLevel(t2, s, vec2<f32>(1.0, 2.0));
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-@group(0) @binding(2) var ext_tex2 : texture_external;
-
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(4) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@group(0) @binding(5) var ext_tex_plane_1_1 : texture_2d<f32>;
-
-@group(0) @binding(6) var<uniform> ext_tex_params_1 : ExternalTextureParams;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp, ext_tex2, ext_tex_plane_1_1, ext_tex_params_1);
-}
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
-  textureSampleExternal(t, ext_tex_plane_1_2, s, vec2<f32>(1.0, 2.0), ext_tex_params_2);
-  textureSampleExternal(t2, ext_tex_plane_1_3, s, vec2<f32>(1.0, 2.0), ext_tex_params_3);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@group(0) @binding(2) var ext_tex2 : texture_2d<f32>;
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 3}, {0, 4}}},
-          {{0, 2}, {{0, 5}, {0, 6}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the texture_external passed to as a parameter to multiple
-// functions produces the correct output.
-TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParamNested) {
-  auto* src = R"(
-fn nested(t : texture_external, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn f(t : texture_external, s : sampler) {
-  nested(t, s);
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler) {
-  nested(t, ext_tex_plane_1_2, ext_tex_params_2, s);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the texture_external passed to as a parameter to multiple
-// functions produces the correct output.
-TEST_F(MultiplanarExternalTextureTest,
-       ExternalTexturePassedAsParamNested_OutOfOrder) {
-  auto* src = R"(
-fn nested(t : texture_external, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-fn f(t : texture_external, s : sampler) {
-  nested(t, s);
-}
-
-@group(0) @binding(0) var ext_tex : texture_external;
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, smp);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler) {
-  nested(t, ext_tex_plane_1_2, ext_tex_params_2, s);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the transform works with a function using an external texture,
-// even if there's no external texture declared at module scope.
-TEST_F(MultiplanarExternalTextureTest,
-       ExternalTexturePassedAsParamWithoutGlobalDecl) {
-  auto* src = R"(
-fn f(ext_tex : texture_external) -> vec2<i32> {
-  return textureDimensions(ext_tex);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-fn f(ext_tex : texture_2d<f32>, ext_tex_plane_1 : texture_2d<f32>, ext_tex_params : ExternalTextureParams) -> vec2<i32> {
-  return textureDimensions(ext_tex);
-}
-)";
-
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}});
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the the transform handles aliases to external textures
-TEST_F(MultiplanarExternalTextureTest, ExternalTextureAlias) {
-  auto* src = R"(
-type ET = texture_external;
-
-fn f(t : ET, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-[[group(0), binding(0)]] var ext_tex : ET;
-[[group(0), binding(1)]] var smp : sampler;
-
-[[stage(fragment)]]
-fn main() {
-  f(ext_tex, smp);
-}
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-type ET = texture_external;
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-// Tests that the the transform handles aliases to external textures
-TEST_F(MultiplanarExternalTextureTest, ExternalTextureAlias_OutOfOrder) {
-  auto* src = R"(
-[[stage(fragment)]]
-fn main() {
-  f(ext_tex, smp);
-}
-
-fn f(t : ET, s : sampler) {
-  textureSampleLevel(t, s, vec2<f32>(1.0, 2.0));
-}
-
-[[group(0), binding(0)]] var ext_tex : ET;
-[[group(0), binding(1)]] var smp : sampler;
-
-type ET = texture_external;
-)";
-
-  auto* expect = R"(
-struct ExternalTextureParams {
-  numPlanes : u32;
-  vr : f32;
-  ug : f32;
-  vg : f32;
-  ub : f32;
-}
-
-@group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
-
-@group(0) @binding(3) var<uniform> ext_tex_params : ExternalTextureParams;
-
-@stage(fragment)
-fn main() {
-  f(ext_tex, ext_tex_plane_1, ext_tex_params, smp);
-}
-
-fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
-  if ((params.numPlanes == 1u)) {
-    return textureSampleLevel(plane0, smp, coord, 0.0);
-  }
-  let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625);
-  let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5);
-  let u = uv.x;
-  let v = uv.y;
-  let r = ((1.164000034 * y) + (params.vr * v));
-  let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v));
-  let b = ((1.164000034 * y) + (params.ub * u));
-  return vec4<f32>(r, g, b, 1.0);
-}
-
-fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
-  textureSampleExternal(t, ext_tex_plane_1_1, s, vec2<f32>(1.0, 2.0), ext_tex_params_1);
-}
-
-@group(0) @binding(0) var ext_tex : texture_2d<f32>;
-
-@group(0) @binding(1) var smp : sampler;
-
-type ET = texture_external;
-)";
-  DataMap data;
-  data.Add<MultiplanarExternalTexture::NewBindingPoints>(
-      MultiplanarExternalTexture::BindingsMap{
-          {{0, 0}, {{0, 2}, {0, 3}}},
-      });
-  auto got = Run<MultiplanarExternalTexture>(src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/num_workgroups_from_uniform.cc b/src/transform/num_workgroups_from_uniform.cc
deleted file mode 100644
index 180d306..0000000
--- a/src/transform/num_workgroups_from_uniform.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/num_workgroups_from_uniform.h"
-
-#include <memory>
-#include <string>
-#include <unordered_set>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/function.h"
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/utils/hash.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::NumWorkgroupsFromUniform);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::NumWorkgroupsFromUniform::Config);
-
-namespace tint {
-namespace transform {
-namespace {
-/// Accessor describes the identifiers used in a member accessor that is being
-/// used to retrieve the num_workgroups builtin from a parameter.
-struct Accessor {
-  Symbol param;
-  Symbol member;
-
-  /// Equality operator
-  bool operator==(const Accessor& other) const {
-    return param == other.param && member == other.member;
-  }
-  /// Hash function
-  struct Hasher {
-    size_t operator()(const Accessor& a) const {
-      return utils::Hash(a.param, a.member);
-    }
-  };
-};
-}  // namespace
-
-NumWorkgroupsFromUniform::NumWorkgroupsFromUniform() = default;
-NumWorkgroupsFromUniform::~NumWorkgroupsFromUniform() = default;
-
-bool NumWorkgroupsFromUniform::ShouldRun(const Program* program,
-                                         const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* attr = node->As<ast::BuiltinAttribute>()) {
-      if (attr->builtin == ast::Builtin::kNumWorkgroups) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-void NumWorkgroupsFromUniform::Run(CloneContext& ctx,
-                                   const DataMap& inputs,
-                                   DataMap&) const {
-  auto* cfg = inputs.Get<Config>();
-  if (cfg == nullptr) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-    return;
-  }
-
-  const char* kNumWorkgroupsMemberName = "num_workgroups";
-
-  // Find all entry point parameters that declare the num_workgroups builtin.
-  std::unordered_set<Accessor, Accessor::Hasher> to_replace;
-  for (auto* func : ctx.src->AST().Functions()) {
-    // num_workgroups is only valid for compute stages.
-    if (func->PipelineStage() != ast::PipelineStage::kCompute) {
-      continue;
-    }
-
-    for (auto* param : ctx.src->Sem().Get(func)->Parameters()) {
-      // Because the CanonicalizeEntryPointIO transform has been run, builtins
-      // will only appear as struct members.
-      auto* str = param->Type()->As<sem::Struct>();
-      if (!str) {
-        continue;
-      }
-
-      for (auto* member : str->Members()) {
-        auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
-            member->Declaration()->attributes);
-        if (!builtin || builtin->builtin != ast::Builtin::kNumWorkgroups) {
-          continue;
-        }
-
-        // 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});
-
-        // Remove the struct member.
-        // The CanonicalizeEntryPointIO transform will have generated this
-        // struct uniquely for this particular entry point, so we know that
-        // there will be no other uses of this struct in the module and that we
-        // can safely modify it here.
-        ctx.Remove(str->Declaration()->members, member->Declaration());
-
-        // If this is the only member, remove the struct and parameter too.
-        if (str->Members().size() == 1) {
-          ctx.Remove(func->params, param->Declaration());
-          ctx.Remove(ctx.src->AST().GlobalDeclarations(), str->Declaration());
-        }
-      }
-    }
-  }
-
-  // Get (or create, on first call) the uniform buffer that will receive the
-  // number of workgroups.
-  const ast::Variable* num_workgroups_ubo = nullptr;
-  auto get_ubo = [&]() {
-    if (!num_workgroups_ubo) {
-      auto* num_workgroups_struct = ctx.dst->Structure(
-          ctx.dst->Sym(),
-          {ctx.dst->Member(kNumWorkgroupsMemberName,
-                           ctx.dst->ty.vec3(ctx.dst->ty.u32()))});
-      num_workgroups_ubo = ctx.dst->Global(
-          ctx.dst->Sym(), ctx.dst->ty.Of(num_workgroups_struct),
-          ast::StorageClass::kUniform,
-          ast::AttributeList{ctx.dst->GroupAndBinding(
-              cfg->ubo_binding.group, cfg->ubo_binding.binding)});
-    }
-    return num_workgroups_ubo;
-  };
-
-  // Now replace all the places where the builtins are accessed with the value
-  // loaded from the uniform buffer.
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    auto* accessor = node->As<ast::MemberAccessorExpression>();
-    if (!accessor) {
-      continue;
-    }
-    auto* ident = accessor->structure->As<ast::IdentifierExpression>();
-    if (!ident) {
-      continue;
-    }
-
-    if (to_replace.count({ident->symbol, accessor->member->symbol})) {
-      ctx.Replace(accessor, ctx.dst->MemberAccessor(get_ubo()->symbol,
-                                                    kNumWorkgroupsMemberName));
-    }
-  }
-
-  ctx.Clone();
-}
-
-NumWorkgroupsFromUniform::Config::Config(sem::BindingPoint ubo_bp)
-    : ubo_binding(ubo_bp) {}
-NumWorkgroupsFromUniform::Config::Config(const Config&) = default;
-NumWorkgroupsFromUniform::Config::~Config() = default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/num_workgroups_from_uniform.h b/src/transform/num_workgroups_from_uniform.h
deleted file mode 100644
index 1870ea2..0000000
--- a/src/transform/num_workgroups_from_uniform.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
-#define SRC_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
-
-#include "src/sem/binding_point.h"
-#include "src/transform/transform.h"
-
-namespace tint {
-
-// Forward declarations
-class CloneContext;
-
-namespace transform {
-
-/// NumWorkgroupsFromUniform is a transform that implements the `num_workgroups`
-/// builtin by loading it from a uniform buffer.
-///
-/// The generated uniform buffer will have the form:
-/// ```
-/// struct num_workgroups_struct {
-///  num_workgroups : vec3<u32>;
-/// };
-///
-/// @group(0) @binding(0)
-/// var<uniform> num_workgroups_ubo : num_workgroups_struct;
-/// ```
-/// The binding group and number used for this uniform buffer is provided via
-/// the `Config` transform input.
-///
-/// @note Depends on the following transforms to have been run first:
-/// * CanonicalizeEntryPointIO
-class NumWorkgroupsFromUniform
-    : public Castable<NumWorkgroupsFromUniform, Transform> {
- public:
-  /// Constructor
-  NumWorkgroupsFromUniform();
-  /// Destructor
-  ~NumWorkgroupsFromUniform() override;
-
-  /// Configuration options for the NumWorkgroupsFromUniform transform.
-  struct Config : public Castable<Data, transform::Data> {
-    /// Constructor
-    /// @param ubo_bp the binding point to use for the generated uniform buffer.
-    explicit Config(sem::BindingPoint ubo_bp);
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// The binding point to use for the generated uniform buffer.
-    sem::BindingPoint ubo_binding;
-  };
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_NUM_WORKGROUPS_FROM_UNIFORM_H_
diff --git a/src/transform/num_workgroups_from_uniform_test.cc b/src/transform/num_workgroups_from_uniform_test.cc
deleted file mode 100644
index 8471b03..0000000
--- a/src/transform/num_workgroups_from_uniform_test.cc
+++ /dev/null
@@ -1,456 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/num_workgroups_from_uniform.h"
-
-#include <utility>
-
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using NumWorkgroupsFromUniformTest = TransformTest;
-
-TEST_F(NumWorkgroupsFromUniformTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<NumWorkgroupsFromUniform>(src));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, ShouldRunHasNumWorkgroups) {
-  auto* src = R"(
-[[stage(compute), workgroup_size(1)]]
-fn main([[builtin(num_workgroups)]] num_wgs : vec3<u32>) {
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<NumWorkgroupsFromUniform>(src));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, Error_MissingTransformData) {
-  auto* src = R"(
-[[stage(compute), workgroup_size(1)]]
-fn main([[builtin(num_workgroups)]] num_wgs : vec3<u32>) {
-}
-)";
-
-  auto* expect =
-      "error: missing transform data for "
-      "tint::transform::NumWorkgroupsFromUniform";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, Basic) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main(@builtin(num_workgroups) num_wgs : vec3<u32>) {
-  let groups_x = num_wgs.x;
-  let groups_y = num_wgs.y;
-  let groups_z = num_wgs.z;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
-
-fn main_inner(num_wgs : vec3<u32>) {
-  let groups_x = num_wgs.x;
-  let groups_y = num_wgs.y;
-  let groups_z = num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  main_inner(tint_symbol_3.num_workgroups);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, StructOnlyMember) {
-  auto* src = R"(
-struct Builtins {
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-};
-
-@stage(compute) @workgroup_size(1)
-fn main(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
-
-struct Builtins {
-  num_wgs : vec3<u32>;
-}
-
-fn main_inner(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  main_inner(Builtins(tint_symbol_3.num_workgroups));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, StructOnlyMember_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-struct Builtins {
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-};
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
-
-fn main_inner(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  main_inner(Builtins(tint_symbol_3.num_workgroups));
-}
-
-struct Builtins {
-  num_wgs : vec3<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, StructMultipleMembers) {
-  auto* src = R"(
-struct Builtins {
-  @builtin(global_invocation_id) gid : vec3<u32>;
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-  @builtin(workgroup_id) wgid : vec3<u32>;
-};
-
-@stage(compute) @workgroup_size(1)
-fn main(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
-
-struct Builtins {
-  gid : vec3<u32>;
-  num_wgs : vec3<u32>;
-  wgid : vec3<u32>;
-}
-
-struct tint_symbol_1 {
-  @builtin(global_invocation_id)
-  gid : vec3<u32>;
-  @builtin(workgroup_id)
-  wgid : vec3<u32>;
-}
-
-fn main_inner(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(tint_symbol : tint_symbol_1) {
-  main_inner(Builtins(tint_symbol.gid, tint_symbol_3.num_workgroups, tint_symbol.wgid));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, StructMultipleMembers_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-struct Builtins {
-  @builtin(global_invocation_id) gid : vec3<u32>;
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-  @builtin(workgroup_id) wgid : vec3<u32>;
-};
-
-)";
-
-  auto* expect = R"(
-struct tint_symbol_2 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
-
-struct tint_symbol_1 {
-  @builtin(global_invocation_id)
-  gid : vec3<u32>;
-  @builtin(workgroup_id)
-  wgid : vec3<u32>;
-}
-
-fn main_inner(in : Builtins) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(tint_symbol : tint_symbol_1) {
-  main_inner(Builtins(tint_symbol.gid, tint_symbol_3.num_workgroups, tint_symbol.wgid));
-}
-
-struct Builtins {
-  gid : vec3<u32>;
-  num_wgs : vec3<u32>;
-  wgid : vec3<u32>;
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, MultipleEntryPoints) {
-  auto* src = R"(
-struct Builtins1 {
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-};
-
-struct Builtins2 {
-  @builtin(global_invocation_id) gid : vec3<u32>;
-  @builtin(num_workgroups) num_wgs : vec3<u32>;
-  @builtin(workgroup_id) wgid : vec3<u32>;
-};
-
-@stage(compute) @workgroup_size(1)
-fn main1(in : Builtins1) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main2(in : Builtins2) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main3(@builtin(num_workgroups) num_wgs : vec3<u32>) {
-  let groups_x = num_wgs.x;
-  let groups_y = num_wgs.y;
-  let groups_z = num_wgs.z;
-}
-)";
-
-  auto* expect = R"(
-struct tint_symbol_6 {
-  num_workgroups : vec3<u32>;
-}
-
-@group(0) @binding(30) var<uniform> tint_symbol_7 : tint_symbol_6;
-
-struct Builtins1 {
-  num_wgs : vec3<u32>;
-}
-
-struct Builtins2 {
-  gid : vec3<u32>;
-  num_wgs : vec3<u32>;
-  wgid : vec3<u32>;
-}
-
-fn main1_inner(in : Builtins1) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main1() {
-  main1_inner(Builtins1(tint_symbol_7.num_workgroups));
-}
-
-struct tint_symbol_3 {
-  @builtin(global_invocation_id)
-  gid : vec3<u32>;
-  @builtin(workgroup_id)
-  wgid : vec3<u32>;
-}
-
-fn main2_inner(in : Builtins2) {
-  let groups_x = in.num_wgs.x;
-  let groups_y = in.num_wgs.y;
-  let groups_z = in.num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main2(tint_symbol_2 : tint_symbol_3) {
-  main2_inner(Builtins2(tint_symbol_2.gid, tint_symbol_7.num_workgroups, tint_symbol_2.wgid));
-}
-
-fn main3_inner(num_wgs : vec3<u32>) {
-  let groups_x = num_wgs.x;
-  let groups_y = num_wgs.y;
-  let groups_z = num_wgs.z;
-}
-
-@stage(compute) @workgroup_size(1)
-fn main3() {
-  main3_inner(tint_symbol_7.num_workgroups);
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(NumWorkgroupsFromUniformTest, NoUsages) {
-  auto* src = R"(
-struct Builtins {
-  @builtin(global_invocation_id) gid : vec3<u32>;
-  @builtin(workgroup_id) wgid : vec3<u32>;
-};
-
-@stage(compute) @workgroup_size(1)
-fn main(in : Builtins) {
-}
-)";
-
-  auto* expect = R"(
-struct Builtins {
-  gid : vec3<u32>;
-  wgid : vec3<u32>;
-}
-
-struct tint_symbol_1 {
-  @builtin(global_invocation_id)
-  gid : vec3<u32>;
-  @builtin(workgroup_id)
-  wgid : vec3<u32>;
-}
-
-fn main_inner(in : Builtins) {
-}
-
-@stage(compute) @workgroup_size(1)
-fn main(tint_symbol : tint_symbol_1) {
-  main_inner(Builtins(tint_symbol.gid, tint_symbol.wgid));
-}
-)";
-
-  DataMap data;
-  data.Add<CanonicalizeEntryPointIO::Config>(
-      CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<NumWorkgroupsFromUniform::Config>(sem::BindingPoint{0, 30u});
-  auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(
-      src, data);
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/pad_array_elements.cc b/src/transform/pad_array_elements.cc
deleted file mode 100644
index f04976d..0000000
--- a/src/transform/pad_array_elements.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/pad_array_elements.h"
-
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/array.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/type_constructor.h"
-#include "src/utils/map.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::PadArrayElements);
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ArrayBuilder = std::function<const ast::Array*()>;
-
-/// PadArray returns a function that constructs a new array in `ctx.dst` with
-/// the element type padded to account for the explicit stride. PadArray will
-/// recursively pad arrays-of-arrays. The new array element type will be added
-/// to module-scope type declarations of `ctx.dst`.
-/// @param ctx the CloneContext
-/// @param create_ast_type_for Transform::CreateASTTypeFor()
-/// @param padded_arrays a map of src array type to the new array name
-/// @param array the array type
-/// @return the new AST array
-template <typename CREATE_AST_TYPE_FOR>
-ArrayBuilder PadArray(
-    CloneContext& ctx,
-    CREATE_AST_TYPE_FOR&& create_ast_type_for,
-    std::unordered_map<const sem::Array*, ArrayBuilder>& padded_arrays,
-    const sem::Array* array) {
-  if (array->IsStrideImplicit()) {
-    // We don't want to wrap arrays that have an implicit stride
-    return nullptr;
-  }
-
-  return utils::GetOrCreate(padded_arrays, array, [&] {
-    // Generate a unique name for the array element type
-    auto name = ctx.dst->Symbols().New("tint_padded_array_element");
-
-    // Examine the element type. Is it also an array?
-    const ast::Type* el_ty = nullptr;
-    if (auto* el_array = array->ElemType()->As<sem::Array>()) {
-      // Array of array - call PadArray() on the element type
-      if (auto p =
-              PadArray(ctx, create_ast_type_for, padded_arrays, el_array)) {
-        el_ty = p();
-      }
-    }
-
-    // If the element wasn't a padded array, just create the typical AST type
-    // for it
-    if (el_ty == nullptr) {
-      el_ty = create_ast_type_for(ctx, array->ElemType());
-    }
-
-    // Structure() will create and append the ast::Struct to the
-    // global declarations of `ctx.dst`. As we haven't finished building the
-    // current module-scope statement or function, this will be placed
-    // immediately before the usage.
-    ctx.dst->Structure(
-        name,
-        {ctx.dst->Member("el", el_ty, {ctx.dst->MemberSize(array->Stride())})});
-
-    auto* dst = ctx.dst;
-    return [=] {
-      if (array->IsRuntimeSized()) {
-        return dst->ty.array(dst->create<ast::TypeName>(name));
-      } else {
-        return dst->ty.array(dst->create<ast::TypeName>(name), array->Count());
-      }
-    };
-  });
-}
-
-}  // namespace
-
-PadArrayElements::PadArrayElements() = default;
-
-PadArrayElements::~PadArrayElements() = default;
-
-bool PadArrayElements::ShouldRun(const Program* program, const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* var = node->As<ast::Type>()) {
-      if (auto* arr = program->Sem().Get<sem::Array>(var)) {
-        if (!arr->IsStrideImplicit()) {
-          return true;
-        }
-      }
-    }
-  }
-  return false;
-}
-
-void PadArrayElements::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  std::unordered_map<const sem::Array*, ArrayBuilder> padded_arrays;
-  auto pad = [&](const sem::Array* array) {
-    return PadArray(ctx, CreateASTTypeFor, padded_arrays, array);
-  };
-
-  // Replace all array types with their corresponding padded array type
-  ctx.ReplaceAll([&](const ast::Type* ast_type) -> const ast::Type* {
-    auto* type = ctx.src->TypeOf(ast_type);
-    if (auto* array = type->UnwrapRef()->As<sem::Array>()) {
-      if (auto p = pad(array)) {
-        return p();
-      }
-    }
-    return nullptr;
-  });
-
-  // Fix up index accessors so `a[1]` becomes `a[1].el`
-  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* accessor)
-                     -> const ast::Expression* {
-    if (auto* array = tint::As<sem::Array>(
-            sem.Get(accessor->object)->Type()->UnwrapRef())) {
-      if (pad(array)) {
-        // Array element is wrapped in a structure. Emit a member accessor
-        // to get to the actual array element.
-        auto* idx = ctx.CloneWithoutTransform(accessor);
-        return ctx.dst->MemberAccessor(idx, "el");
-      }
-    }
-    return nullptr;
-  });
-
-  // Fix up array constructors so `A(1,2)` becomes
-  // `A(padded(1), padded(2))`
-  ctx.ReplaceAll(
-      [&](const ast::CallExpression* expr) -> const ast::Expression* {
-        auto* call = sem.Get(expr);
-        if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
-          if (auto* array = ctor->ReturnType()->As<sem::Array>()) {
-            if (auto p = pad(array)) {
-              auto* arr_ty = p();
-              auto el_typename = arr_ty->type->As<ast::TypeName>()->name;
-
-              ast::ExpressionList args;
-              args.reserve(call->Arguments().size());
-              for (auto* arg : call->Arguments()) {
-                auto* val = ctx.Clone(arg->Declaration());
-                args.emplace_back(ctx.dst->Construct(
-                    ctx.dst->create<ast::TypeName>(el_typename), val));
-              }
-
-              return ctx.dst->Construct(arr_ty, args);
-            }
-          }
-        }
-        return nullptr;
-      });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/pad_array_elements.h b/src/transform/pad_array_elements.h
deleted file mode 100644
index 5b1df32..0000000
--- a/src/transform/pad_array_elements.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
-#define SRC_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// PadArrayElements is a transform that replaces array types with an explicit
-/// stride that is larger than the implicit stride, with an array of a new
-/// structure type. This structure holds with a single field of the element
-/// type, decorated with a `@size` attribute to pad the structure to the
-/// required array stride. The new array types have no explicit stride,
-/// structure size is equal to the desired stride.
-/// Array index expressions and constructors are also adjusted to deal with this
-/// structure element type.
-/// This transform helps with backends that cannot directly return arrays or use
-/// them as parameters.
-class PadArrayElements : public Castable<PadArrayElements, Transform> {
- public:
-  /// Constructor
-  PadArrayElements();
-
-  /// Destructor
-  ~PadArrayElements() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_PAD_ARRAY_ELEMENTS_H_
diff --git a/src/transform/pad_array_elements_test.cc b/src/transform/pad_array_elements_test.cc
deleted file mode 100644
index f3da053..0000000
--- a/src/transform/pad_array_elements_test.cc
+++ /dev/null
@@ -1,518 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/pad_array_elements.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using PadArrayElementsTest = TransformTest;
-
-TEST_F(PadArrayElementsTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<PadArrayElements>(src));
-}
-
-TEST_F(PadArrayElementsTest, ShouldRunHasImplicitArrayStride) {
-  auto* src = R"(
-var<private> arr : array<i32, 4>;
-)";
-
-  EXPECT_FALSE(ShouldRun<PadArrayElements>(src));
-}
-
-TEST_F(PadArrayElementsTest, ShouldRunHasExplicitArrayStride) {
-  auto* src = R"(
-var<private> arr : [[stride(8)]] array<i32, 4>;
-)";
-
-  EXPECT_TRUE(ShouldRun<PadArrayElements>(src));
-}
-
-TEST_F(PadArrayElementsTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ImplicitArrayStride) {
-  auto* src = R"(
-var<private> arr : array<i32, 4>;
-)";
-  auto* expect = src;
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArrayAsGlobal) {
-  auto* src = R"(
-var<private> arr : @stride(8) array<i32, 4>;
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(8)
-  el : i32;
-}
-
-var<private> arr : array<tint_padded_array_element, 4u>;
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, RuntimeArray) {
-  auto* src = R"(
-struct S {
-  rta : @stride(8) array<i32>;
-};
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(8)
-  el : i32;
-}
-
-struct S {
-  rta : array<tint_padded_array_element>;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArrayFunctionVar) {
-  auto* src = R"(
-fn f() {
-  var arr : @stride(16) array<i32, 4>;
-  arr = @stride(16) array<i32, 4>();
-  arr = @stride(16) array<i32, 4>(1, 2, 3, 4);
-  let x = arr[3];
-}
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(16)
-  el : i32;
-}
-
-fn f() {
-  var arr : array<tint_padded_array_element, 4u>;
-  arr = array<tint_padded_array_element, 4u>();
-  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
-  let x = arr[3].el;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArrayAsParam) {
-  auto* src = R"(
-fn f(a : @stride(12) array<i32, 4>) -> i32 {
-  return a[2];
-}
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(12)
-  el : i32;
-}
-
-fn f(a : array<tint_padded_array_element, 4u>) -> i32 {
-  return a[2].el;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// TODO(crbug.com/tint/781): Cannot parse the stride on the return array type.
-TEST_F(PadArrayElementsTest, DISABLED_ArrayAsReturn) {
-  auto* src = R"(
-fn f() -> @stride(8) array<i32, 4> {
-  return array<i32, 4>(1, 2, 3, 4);
-}
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  el : i32;
-  @size(4)
-  padding : u32;
-};
-
-fn f() -> array<tint_padded_array_element, 4> {
-  return array<tint_padded_array_element, 4>(tint_padded_array_element(1, 0u), tint_padded_array_element(2, 0u), tint_padded_array_element(3, 0u), tint_padded_array_element(4, 0u));
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArrayAlias) {
-  auto* src = R"(
-type Array = @stride(16) array<i32, 4>;
-
-fn f() {
-  var arr : Array;
-  arr = Array();
-  arr = Array(1, 2, 3, 4);
-  let vals : Array = Array(1, 2, 3, 4);
-  arr = vals;
-  let x = arr[3];
-}
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(16)
-  el : i32;
-}
-
-type Array = array<tint_padded_array_element, 4u>;
-
-fn f() {
-  var arr : array<tint_padded_array_element, 4u>;
-  arr = array<tint_padded_array_element, 4u>();
-  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
-  let vals : array<tint_padded_array_element, 4u> = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
-  arr = vals;
-  let x = arr[3].el;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArrayAlias_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var arr : Array;
-  arr = Array();
-  arr = Array(1, 2, 3, 4);
-  let vals : Array = Array(1, 2, 3, 4);
-  arr = vals;
-  let x = arr[3];
-}
-
-type Array = @stride(16) array<i32, 4>;
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(16)
-  el : i32;
-}
-
-fn f() {
-  var arr : array<tint_padded_array_element, 4u>;
-  arr = array<tint_padded_array_element, 4u>();
-  arr = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
-  let vals : array<tint_padded_array_element, 4u> = array<tint_padded_array_element, 4u>(tint_padded_array_element(1), tint_padded_array_element(2), tint_padded_array_element(3), tint_padded_array_element(4));
-  arr = vals;
-  let x = arr[3].el;
-}
-
-type Array = array<tint_padded_array_element, 4u>;
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : @stride(8) array<i32, 4>;
-  b : @stride(8) array<i32, 8>;
-  c : @stride(8) array<i32, 4>;
-  d : @stride(12) array<i32, 8>;
-};
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(8)
-  el : i32;
-}
-
-struct tint_padded_array_element_1 {
-  @size(8)
-  el : i32;
-}
-
-struct tint_padded_array_element_2 {
-  @size(12)
-  el : i32;
-}
-
-struct S {
-  a : array<tint_padded_array_element, 4u>;
-  b : array<tint_padded_array_element_1, 8u>;
-  c : array<tint_padded_array_element, 4u>;
-  d : array<tint_padded_array_element_2, 8u>;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, ArraysOfArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : @stride(512) array<i32, 4>;
-  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
-  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
-};
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(512)
-  el : i32;
-}
-
-struct tint_padded_array_element_2 {
-  @size(32)
-  el : i32;
-}
-
-struct tint_padded_array_element_1 {
-  @size(512)
-  el : array<tint_padded_array_element_2, 4u>;
-}
-
-struct tint_padded_array_element_5 {
-  @size(8)
-  el : i32;
-}
-
-struct tint_padded_array_element_4 {
-  @size(64)
-  el : array<tint_padded_array_element_5, 4u>;
-}
-
-struct tint_padded_array_element_3 {
-  @size(512)
-  el : array<tint_padded_array_element_4, 4u>;
-}
-
-struct S {
-  a : array<tint_padded_array_element, 4u>;
-  b : array<tint_padded_array_element_1, 4u>;
-  c : array<tint_padded_array_element_3, 4u>;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, AccessArraysOfArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : @stride(512) array<i32, 4>;
-  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
-  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
-};
-
-fn f(s : S) -> i32 {
-  return s.a[2] + s.b[1][2] + s.c[3][1][2];
-}
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(512)
-  el : i32;
-}
-
-struct tint_padded_array_element_2 {
-  @size(32)
-  el : i32;
-}
-
-struct tint_padded_array_element_1 {
-  @size(512)
-  el : array<tint_padded_array_element_2, 4u>;
-}
-
-struct tint_padded_array_element_5 {
-  @size(8)
-  el : i32;
-}
-
-struct tint_padded_array_element_4 {
-  @size(64)
-  el : array<tint_padded_array_element_5, 4u>;
-}
-
-struct tint_padded_array_element_3 {
-  @size(512)
-  el : array<tint_padded_array_element_4, 4u>;
-}
-
-struct S {
-  a : array<tint_padded_array_element, 4u>;
-  b : array<tint_padded_array_element_1, 4u>;
-  c : array<tint_padded_array_element_3, 4u>;
-}
-
-fn f(s : S) -> i32 {
-  return ((s.a[2].el + s.b[1].el[2].el) + s.c[3].el[1].el[2].el);
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, AccessArraysOfArraysInStruct_OutOfOrder) {
-  auto* src = R"(
-fn f(s : S) -> i32 {
-  return s.a[2] + s.b[1][2] + s.c[3][1][2];
-}
-
-struct S {
-  a : @stride(512) array<i32, 4>;
-  b : @stride(512) array<@stride(32) array<i32, 4>, 4>;
-  c : @stride(512) array<@stride(64) array<@stride(8) array<i32, 4>, 4>, 4>;
-};
-)";
-  auto* expect = R"(
-struct tint_padded_array_element {
-  @size(512)
-  el : i32;
-}
-
-struct tint_padded_array_element_1 {
-  @size(32)
-  el : i32;
-}
-
-struct tint_padded_array_element_2 {
-  @size(512)
-  el : array<tint_padded_array_element_1, 4u>;
-}
-
-struct tint_padded_array_element_3 {
-  @size(8)
-  el : i32;
-}
-
-struct tint_padded_array_element_4 {
-  @size(64)
-  el : array<tint_padded_array_element_3, 4u>;
-}
-
-struct tint_padded_array_element_5 {
-  @size(512)
-  el : array<tint_padded_array_element_4, 4u>;
-}
-
-fn f(s : S) -> i32 {
-  return ((s.a[2].el + s.b[1].el[2].el) + s.c[3].el[1].el[2].el);
-}
-
-struct S {
-  a : array<tint_padded_array_element, 4u>;
-  b : array<tint_padded_array_element_2, 4u>;
-  c : array<tint_padded_array_element_5, 4u>;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PadArrayElementsTest, DeclarationOrder) {
-  auto* src = R"(
-type T0 = i32;
-
-type T1 = @stride(8) array<i32, 1>;
-
-type T2 = i32;
-
-fn f1(a : @stride(8) array<i32, 2>) {
-}
-
-type T3 = i32;
-
-fn f2() {
-  var v : @stride(8) array<i32, 3>;
-}
-)";
-  auto* expect = R"(
-type T0 = i32;
-
-struct tint_padded_array_element {
-  @size(8)
-  el : i32;
-}
-
-type T1 = array<tint_padded_array_element, 1u>;
-
-type T2 = i32;
-
-struct tint_padded_array_element_1 {
-  @size(8)
-  el : i32;
-}
-
-fn f1(a : array<tint_padded_array_element_1, 2u>) {
-}
-
-type T3 = i32;
-
-struct tint_padded_array_element_2 {
-  @size(8)
-  el : i32;
-}
-
-fn f2() {
-  var v : array<tint_padded_array_element_2, 3u>;
-}
-)";
-
-  auto got = Run<PadArrayElements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/promote_initializers_to_const_var.cc b/src/transform/promote_initializers_to_const_var.cc
deleted file mode 100644
index 0c68118..0000000
--- a/src/transform/promote_initializers_to_const_var.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// 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/transform/promote_initializers_to_const_var.h"
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/statement.h"
-#include "src/sem/type_constructor.h"
-#include "src/transform/utils/hoist_to_decl_before.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteInitializersToConstVar);
-
-namespace tint::transform {
-
-PromoteInitializersToConstVar::PromoteInitializersToConstVar() = default;
-
-PromoteInitializersToConstVar::~PromoteInitializersToConstVar() = default;
-
-void PromoteInitializersToConstVar::Run(CloneContext& ctx,
-                                        const DataMap&,
-                                        DataMap&) const {
-  HoistToDeclBefore hoist_to_decl_before(ctx);
-
-  // Hoists array and structure initializers to a constant variable, declared
-  // just before the statement of usage.
-  auto type_ctor_to_let = [&](const ast::CallExpression* expr) {
-    auto* ctor = ctx.src->Sem().Get(expr);
-    if (!ctor->Target()->Is<sem::TypeConstructor>()) {
-      return true;
-    }
-    auto* sem_stmt = ctor->Stmt();
-    if (!sem_stmt) {
-      // Expression is outside of a statement. This usually means the
-      // expression is part of a global (module-scope) constant declaration.
-      // These must be constexpr, and so cannot contain the type of
-      // expressions that must be sanitized.
-      return true;
-    }
-
-    auto* stmt = sem_stmt->Declaration();
-
-    if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
-      if (src_var_decl->variable->constructor == expr) {
-        // This statement is just a variable declaration with the
-        // initializer as the constructor value. This is what we're
-        // attempting to transform to, and so ignore.
-        return true;
-      }
-    }
-
-    auto* src_ty = ctor->Type();
-    if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
-      // We only care about array and struct initializers
-      return true;
-    }
-
-    return hoist_to_decl_before.Add(ctor, expr, true);
-  };
-
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* call_expr = node->As<ast::CallExpression>()) {
-      if (!type_ctor_to_let(call_expr)) {
-        return;
-      }
-    }
-  }
-
-  hoist_to_decl_before.Apply();
-  ctx.Clone();
-}
-
-}  // namespace tint::transform
diff --git a/src/transform/promote_initializers_to_const_var.h b/src/transform/promote_initializers_to_const_var.h
deleted file mode 100644
index 310f8fc..0000000
--- a/src/transform/promote_initializers_to_const_var.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
-#define SRC_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
-
-#include "src/transform/transform.h"
-
-namespace tint::transform {
-
-/// A transform that hoists the array and structure initializers to a constant
-/// variable, declared just before the statement of usage.
-/// @see crbug.com/tint/406
-class PromoteInitializersToConstVar
-    : public Castable<PromoteInitializersToConstVar, Transform> {
- public:
-  /// Constructor
-  PromoteInitializersToConstVar();
-
-  /// Destructor
-  ~PromoteInitializersToConstVar() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace tint::transform
-
-#endif  // SRC_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
diff --git a/src/transform/promote_initializers_to_const_var_test.cc b/src/transform/promote_initializers_to_const_var_test.cc
deleted file mode 100644
index 109615b..0000000
--- a/src/transform/promote_initializers_to_const_var_test.cc
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/promote_initializers_to_const_var.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using PromoteInitializersToConstVarTest = TransformTest;
-
-TEST_F(PromoteInitializersToConstVarTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicArray) {
-  auto* src = R"(
-fn f() {
-  var f0 = 1.0;
-  var f1 = 2.0;
-  var f2 = 3.0;
-  var f3 = 4.0;
-  var i = array<f32, 4u>(f0, f1, f2, f3)[2];
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var f0 = 1.0;
-  var f1 = 2.0;
-  var f2 = 3.0;
-  var f3 = 4.0;
-  let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
-  var i = tint_symbol[2];
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicStruct) {
-  auto* src = R"(
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-};
-
-fn f() {
-  var x = S(1, 2.0, vec3<f32>()).b;
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-}
-
-fn f() {
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  var x = tint_symbol.b;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicStruct_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var x = S(1, 2.0, vec3<f32>()).b;
-}
-
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-};
-)";
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  var x = tint_symbol.b;
-}
-
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInit) {
-  auto* src = R"(
-fn f() {
-  var insert_after = 1;
-  for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  for(var i = tint_symbol[2]; ; ) {
-    break;
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit) {
-  auto* src = R"(
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-};
-
-fn f() {
-  var insert_after = 1;
-  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-}
-
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  for(var x = tint_symbol.b; ; ) {
-    break;
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var insert_after = 1;
-  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
-    break;
-  }
-}
-
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-};
-)";
-
-  auto* expect = R"(
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  for(var x = tint_symbol.b; ; ) {
-    break;
-  }
-}
-
-struct S {
-  a : i32;
-  b : f32;
-  c : vec3<f32>;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCond) {
-  auto* src = R"(
-fn f() {
-  var f = 1.0;
-  for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
-    var marker = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  loop {
-    let tint_symbol = array<f32, 1u>(f);
-    if (!((f == tint_symbol[0]))) {
-      break;
-    }
-    {
-      var marker = 1;
-    }
-
-    continuing {
-      f = (f + 1.0);
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCont) {
-  auto* src = R"(
-fn f() {
-  var f = 0.0;
-  for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var f = 0.0;
-  loop {
-    if (!((f < 10.0))) {
-      break;
-    }
-    {
-      var marker = 1;
-    }
-
-    continuing {
-      let tint_symbol = array<f32, 1u>(1.0);
-      f = (f + tint_symbol[0]);
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInitCondCont) {
-  auto* src = R"(
-fn f() {
-  for(var f = array<f32, 1u>(0.0)[0];
-      f < array<f32, 1u>(1.0)[0];
-      f = f + array<f32, 1u>(2.0)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = array<f32, 1u>(0.0);
-  {
-    var f = tint_symbol[0];
-    loop {
-      let tint_symbol_1 = array<f32, 1u>(1.0);
-      if (!((f < tint_symbol_1[0]))) {
-        break;
-      }
-      {
-        var marker = 1;
-      }
-
-      continuing {
-        let tint_symbol_2 = array<f32, 1u>(2.0);
-        f = (f + tint_symbol_2[0]);
-      }
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIf) {
-  auto* src = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (f == array<f32, 2u>(f, f)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else {
-    let tint_symbol = array<f32, 2u>(f, f);
-    if ((f == tint_symbol[0])) {
-      var marker = 1;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIfChain) {
-  auto* src = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else if (f == array<f32, 2u>(f, f)[0]) {
-    var marker = 2;
-  } else if (f == array<f32, 2u>(f, f)[1]) {
-    var marker = 3;
-  } else if (true) {
-    var marker = 4;
-  } else {
-    var marker = 5;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else {
-    let tint_symbol = array<f32, 2u>(f, f);
-    if ((f == tint_symbol[0])) {
-      var marker = 2;
-    } else {
-      let tint_symbol_1 = array<f32, 2u>(f, f);
-      if ((f == tint_symbol_1[1])) {
-        var marker = 3;
-      } else if (true) {
-        var marker = 4;
-      } else {
-        var marker = 5;
-      }
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInArrayArray) {
-  auto* src = R"(
-fn f() {
-  var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = array<f32, 2u>(1.0, 2.0);
-  let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
-  let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
-  var i = tint_symbol_2[0][1];
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructNested) {
-  auto* src = R"(
-struct S1 {
-  a : i32;
-};
-
-struct S2 {
-  a : i32;
-  b : S1;
-  c : i32;
-};
-
-struct S3 {
-  a : S2;
-};
-
-fn f() {
-  var x = S3(S2(1, S1(2), 3)).a.b.a;
-}
-)";
-
-  auto* expect = R"(
-struct S1 {
-  a : i32;
-}
-
-struct S2 {
-  a : i32;
-  b : S1;
-  c : i32;
-}
-
-struct S3 {
-  a : S2;
-}
-
-fn f() {
-  let tint_symbol = S1(2);
-  let tint_symbol_1 = S2(1, tint_symbol, 3);
-  let tint_symbol_2 = S3(tint_symbol_1);
-  var x = tint_symbol_2.a.b.a;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, Mixed) {
-  auto* src = R"(
-struct S1 {
-  a : i32;
-};
-
-struct S2 {
-  a : array<S1, 3u>;
-};
-
-fn f() {
-  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
-}
-)";
-
-  auto* expect = R"(
-struct S1 {
-  a : i32;
-}
-
-struct S2 {
-  a : array<S1, 3u>;
-}
-
-fn f() {
-  let tint_symbol = S1(1);
-  let tint_symbol_1 = S1(2);
-  let tint_symbol_2 = S1(3);
-  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
-  let tint_symbol_4 = S2(tint_symbol_3);
-  var x = tint_symbol_4.a[1].a;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, Mixed_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
-}
-
-struct S2 {
-  a : array<S1, 3u>;
-};
-
-struct S1 {
-  a : i32;
-};
-)";
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = S1(1);
-  let tint_symbol_1 = S1(2);
-  let tint_symbol_2 = S1(3);
-  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
-  let tint_symbol_4 = S2(tint_symbol_3);
-  var x = tint_symbol_4.a[1].a;
-}
-
-struct S2 {
-  a : array<S1, 3u>;
-}
-
-struct S1 {
-  a : i32;
-}
-)";
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl) {
-  auto* src = R"(
-struct S {
-  a : i32;
-  b : f32;
-  c : i32;
-}
-
-fn f() {
-  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  var local_str = S(1, 2.0, 3);
-}
-
-let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-
-let module_str : S = S(1, 2.0, 3);
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  var local_str = S(1, 2.0, 3);
-}
-
-let module_str : S = S(1, 2.0, 3);
-
-struct S {
-  a : i32;
-  b : f32;
-  c : i32;
-}
-
-let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<PromoteInitializersToConstVar>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/remove_phonies.cc b/src/transform/remove_phonies.cc
deleted file mode 100644
index 5c600eb..0000000
--- a/src/transform/remove_phonies.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/remove_phonies.h"
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/traverse_expressions.h"
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::RemovePhonies);
-
-namespace tint {
-namespace transform {
-namespace {
-
-struct SinkSignature {
-  std::vector<const sem::Type*> types;
-
-  bool operator==(const SinkSignature& other) const {
-    if (types.size() != other.types.size()) {
-      return false;
-    }
-    for (size_t i = 0; i < types.size(); i++) {
-      if (types[i] != other.types[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  struct Hasher {
-    /// @param sig the CallTargetSignature to hash
-    /// @return the hash value
-    std::size_t operator()(const SinkSignature& sig) const {
-      size_t hash = tint::utils::Hash(sig.types.size());
-      for (auto* ty : sig.types) {
-        tint::utils::HashCombine(&hash, ty);
-      }
-      return hash;
-    }
-  };
-};
-
-}  // namespace
-
-RemovePhonies::RemovePhonies() = default;
-
-RemovePhonies::~RemovePhonies() = default;
-
-bool RemovePhonies::ShouldRun(const Program* program, const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (node->Is<ast::PhonyExpression>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void RemovePhonies::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  std::unordered_map<SinkSignature, Symbol, SinkSignature::Hasher> sinks;
-
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* stmt = node->As<ast::AssignmentStatement>()) {
-      if (stmt->lhs->Is<ast::PhonyExpression>()) {
-        std::vector<const ast::Expression*> side_effects;
-        if (!ast::TraverseExpressions(
-                stmt->rhs, ctx.dst->Diagnostics(),
-                [&](const ast::CallExpression* call) {
-                  // ast::CallExpression may map to a function or builtin call
-                  // (both may have side-effects), or a type constructor or
-                  // type conversion (both do not have side effects).
-                  if (sem.Get(call)
-                          ->Target()
-                          ->IsAnyOf<sem::Function, sem::Builtin>()) {
-                    side_effects.push_back(call);
-                    return ast::TraverseAction::Skip;
-                  }
-                  return ast::TraverseAction::Descend;
-                })) {
-          return;
-        }
-
-        if (side_effects.empty()) {
-          // Phony assignment with no side effects.
-          // Just remove it.
-          RemoveStatement(ctx, stmt);
-          continue;
-        }
-
-        if (side_effects.size() == 1) {
-          if (auto* call = side_effects[0]->As<ast::CallExpression>()) {
-            // Phony assignment with single call side effect.
-            // Replace phony assignment with call.
-            ctx.Replace(
-                stmt, [&, call] { return ctx.dst->CallStmt(ctx.Clone(call)); });
-            continue;
-          }
-        }
-
-        // Phony assignment with multiple side effects.
-        // Generate a call to a placeholder function with the side
-        // effects as arguments.
-        ctx.Replace(stmt, [&, side_effects] {
-          SinkSignature sig;
-          for (auto* arg : side_effects) {
-            sig.types.push_back(sem.Get(arg)->Type()->UnwrapRef());
-          }
-          auto sink = utils::GetOrCreate(sinks, sig, [&] {
-            auto name = ctx.dst->Symbols().New("phony_sink");
-            ast::VariableList params;
-            for (auto* ty : sig.types) {
-              auto* ast_ty = CreateASTTypeFor(ctx, ty);
-              params.push_back(
-                  ctx.dst->Param("p" + std::to_string(params.size()), ast_ty));
-            }
-            ctx.dst->Func(name, params, ctx.dst->ty.void_(), {});
-            return name;
-          });
-          ast::ExpressionList args;
-          for (auto* arg : side_effects) {
-            args.push_back(ctx.Clone(arg));
-          }
-          return ctx.dst->CallStmt(ctx.dst->Call(sink, args));
-        });
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/remove_phonies.h b/src/transform/remove_phonies.h
deleted file mode 100644
index 415ce89..0000000
--- a/src/transform/remove_phonies.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_REMOVE_PHONIES_H_
-#define SRC_TRANSFORM_REMOVE_PHONIES_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// RemovePhonies is a Transform that removes all phony-assignment statements,
-/// while preserving function call expressions in the RHS of the assignment that
-/// may have side-effects.
-class RemovePhonies : public Castable<RemovePhonies, Transform> {
- public:
-  /// Constructor
-  RemovePhonies();
-
-  /// Destructor
-  ~RemovePhonies() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_REMOVE_PHONIES_H_
diff --git a/src/transform/remove_phonies_test.cc b/src/transform/remove_phonies_test.cc
deleted file mode 100644
index a8fd5ea..0000000
--- a/src/transform/remove_phonies_test.cc
+++ /dev/null
@@ -1,431 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/remove_phonies.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using RemovePhoniesTest = TransformTest;
-
-TEST_F(RemovePhoniesTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<RemovePhonies>(src));
-}
-
-TEST_F(RemovePhoniesTest, ShouldRunHasPhony) {
-  auto* src = R"(
-fn f() {
-  _ = 1;
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<RemovePhonies>(src));
-}
-
-TEST_F(RemovePhoniesTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, NoSideEffects) {
-  auto* src = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-fn f() {
-  var v : i32;
-  _ = &v;
-  _ = 1;
-  _ = 1 + 2;
-  _ = t;
-  _ = u32(3.0);
-  _ = f32(i32(4u));
-  _ = vec2<f32>(5.0);
-  _ = vec3<i32>(6, 7, 8);
-  _ = mat2x2<f32>(9.0, 10.0, 11.0, 12.0);
-}
-)";
-
-  auto* expect = R"(
-@group(0) @binding(0) var t : texture_2d<f32>;
-
-fn f() {
-  var v : i32;
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, SingleSideEffects) {
-  auto* src = R"(
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn f() {
-  _ = neg(1);
-  _ = add(2, 3);
-  _ = add(neg(4), neg(5));
-  _ = u32(neg(6));
-  _ = f32(add(7, 8));
-  _ = vec2<f32>(f32(neg(9)));
-  _ = vec3<i32>(1, neg(10), 3);
-  _ = mat2x2<f32>(1.0, f32(add(11, 12)), 3.0, 4.0);
-}
-)";
-
-  auto* expect = R"(
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn f() {
-  neg(1);
-  add(2, 3);
-  add(neg(4), neg(5));
-  neg(6);
-  add(7, 8);
-  neg(9);
-  neg(10);
-  add(11, 12);
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, SingleSideEffects_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  _ = neg(1);
-  _ = add(2, 3);
-  _ = add(neg(4), neg(5));
-  _ = u32(neg(6));
-  _ = f32(add(7, 8));
-  _ = vec2<f32>(f32(neg(9)));
-  _ = vec3<i32>(1, neg(10), 3);
-  _ = mat2x2<f32>(1.0, f32(add(11, 12)), 3.0, 4.0);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  neg(1);
-  add(2, 3);
-  add(neg(4), neg(5));
-  neg(6);
-  add(7, 8);
-  neg(9);
-  neg(10);
-  add(11, 12);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, MultipleSideEffects) {
-  auto* src = R"(
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn xor(a : u32, b : u32) -> u32 {
-  return (a ^ b);
-}
-
-fn f() {
-  _ = (1 + add(2 + add(3, 4), 5)) * add(6, 7) * neg(8);
-  _ = add(9, neg(10)) + neg(11);
-  _ = xor(12u, 13u) + xor(14u, 15u);
-  _ = neg(16) / neg(17) + add(18, 19);
-}
-)";
-
-  auto* expect = R"(
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn xor(a : u32, b : u32) -> u32 {
-  return (a ^ b);
-}
-
-fn phony_sink(p0 : i32, p1 : i32, p2 : i32) {
-}
-
-fn phony_sink_1(p0 : i32, p1 : i32) {
-}
-
-fn phony_sink_2(p0 : u32, p1 : u32) {
-}
-
-fn f() {
-  phony_sink(add((2 + add(3, 4)), 5), add(6, 7), neg(8));
-  phony_sink_1(add(9, neg(10)), neg(11));
-  phony_sink_2(xor(12u, 13u), xor(14u, 15u));
-  phony_sink(neg(16), neg(17), add(18, 19));
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, MultipleSideEffects_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  _ = (1 + add(2 + add(3, 4), 5)) * add(6, 7) * neg(8);
-  _ = add(9, neg(10)) + neg(11);
-  _ = xor(12u, 13u) + xor(14u, 15u);
-  _ = neg(16) / neg(17) + add(18, 19);
-}
-
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn xor(a : u32, b : u32) -> u32 {
-  return (a ^ b);
-}
-)";
-
-  auto* expect = R"(
-fn phony_sink(p0 : i32, p1 : i32, p2 : i32) {
-}
-
-fn phony_sink_1(p0 : i32, p1 : i32) {
-}
-
-fn phony_sink_2(p0 : u32, p1 : u32) {
-}
-
-fn f() {
-  phony_sink(add((2 + add(3, 4)), 5), add(6, 7), neg(8));
-  phony_sink_1(add(9, neg(10)), neg(11));
-  phony_sink_2(xor(12u, 13u), xor(14u, 15u));
-  phony_sink(neg(16), neg(17), add(18, 19));
-}
-
-fn neg(a : i32) -> i32 {
-  return -(a);
-}
-
-fn add(a : i32, b : i32) -> i32 {
-  return (a + b);
-}
-
-fn xor(a : u32, b : u32) -> u32 {
-  return (a ^ b);
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, ForLoop) {
-  auto* src = R"(
-struct S {
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-fn x() -> i32 {
-  return 0;
-}
-
-fn y() -> i32 {
-  return 0;
-}
-
-fn z() -> i32 {
-  return 0;
-}
-
-fn f() {
-  for (_ = &s.arr; ;_ = &s.arr) {
-    break;
-  }
-  for (_ = x(); ;_ = y() + z()) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-
-fn x() -> i32 {
-  return 0;
-}
-
-fn y() -> i32 {
-  return 0;
-}
-
-fn z() -> i32 {
-  return 0;
-}
-
-fn phony_sink(p0 : i32, p1 : i32) {
-}
-
-fn f() {
-  for(; ; ) {
-    break;
-  }
-  for(x(); ; phony_sink(y(), z())) {
-    break;
-  }
-}
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemovePhoniesTest, ForLoop_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  for (_ = &s.arr; ;_ = &s.arr) {
-    break;
-  }
-  for (_ = x(); ;_ = y() + z()) {
-    break;
-  }
-}
-
-fn x() -> i32 {
-  return 0;
-}
-
-fn y() -> i32 {
-  return 0;
-}
-
-fn z() -> i32 {
-  return 0;
-}
-
-struct S {
-  arr : array<i32>;
-};
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-)";
-
-  auto* expect = R"(
-fn phony_sink(p0 : i32, p1 : i32) {
-}
-
-fn f() {
-  for(; ; ) {
-    break;
-  }
-  for(x(); ; phony_sink(y(), z())) {
-    break;
-  }
-}
-
-fn x() -> i32 {
-  return 0;
-}
-
-fn y() -> i32 {
-  return 0;
-}
-
-fn z() -> i32 {
-  return 0;
-}
-
-struct S {
-  arr : array<i32>;
-}
-
-@group(0) @binding(0) var<storage, read_write> s : S;
-)";
-
-  auto got = Run<RemovePhonies>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/remove_unreachable_statements.cc b/src/transform/remove_unreachable_statements.cc
deleted file mode 100644
index 88d4158..0000000
--- a/src/transform/remove_unreachable_statements.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/remove_unreachable_statements.h"
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/traverse_expressions.h"
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::RemoveUnreachableStatements);
-
-namespace tint {
-namespace transform {
-
-RemoveUnreachableStatements::RemoveUnreachableStatements() = default;
-
-RemoveUnreachableStatements::~RemoveUnreachableStatements() = default;
-
-bool RemoveUnreachableStatements::ShouldRun(const Program* program,
-                                            const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* stmt = program->Sem().Get<sem::Statement>(node)) {
-      if (!stmt->IsReachable()) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-void RemoveUnreachableStatements::Run(CloneContext& ctx,
-                                      const DataMap&,
-                                      DataMap&) const {
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* stmt = ctx.src->Sem().Get<sem::Statement>(node)) {
-      if (!stmt->IsReachable()) {
-        RemoveStatement(ctx, stmt->Declaration());
-      }
-    }
-  }
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/remove_unreachable_statements.h b/src/transform/remove_unreachable_statements.h
deleted file mode 100644
index 060a2a6..0000000
--- a/src/transform/remove_unreachable_statements.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
-#define SRC_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// RemoveUnreachableStatements is a Transform that removes all statements
-/// marked as unreachable.
-class RemoveUnreachableStatements
-    : public Castable<RemoveUnreachableStatements, Transform> {
- public:
-  /// Constructor
-  RemoveUnreachableStatements();
-
-  /// Destructor
-  ~RemoveUnreachableStatements() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_REMOVE_UNREACHABLE_STATEMENTS_H_
diff --git a/src/transform/remove_unreachable_statements_test.cc b/src/transform/remove_unreachable_statements_test.cc
deleted file mode 100644
index 33fe5a6..0000000
--- a/src/transform/remove_unreachable_statements_test.cc
+++ /dev/null
@@ -1,571 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/remove_unreachable_statements.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using RemoveUnreachableStatementsTest = TransformTest;
-
-TEST_F(RemoveUnreachableStatementsTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<RemoveUnreachableStatements>(src));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, ShouldRunHasNoUnreachable) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-    var x = 1;
-  }
-}
-)";
-
-  EXPECT_FALSE(ShouldRun<RemoveUnreachableStatements>(src));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, ShouldRunHasUnreachable) {
-  auto* src = R"(
-fn f() {
-  return;
-  if (true) {
-    var x = 1;
-  }
-}
-)";
-
-  EXPECT_TRUE(ShouldRun<RemoveUnreachableStatements>(src));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, Return) {
-  auto* src = R"(
-fn f() {
-  return;
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  return;
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, NestedReturn) {
-  auto* src = R"(
-fn f() {
-  {
-    {
-      return;
-    }
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  {
-    {
-      return;
-    }
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, Discard) {
-  auto* src = R"(
-fn f() {
-  discard;
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  discard;
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, NestedDiscard) {
-  auto* src = R"(
-fn f() {
-  {
-    {
-      discard;
-    }
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  {
-    {
-      discard;
-    }
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, CallToFuncWithDiscard) {
-  auto* src = R"(
-fn DISCARD() {
-  discard;
-}
-
-fn f() {
-  DISCARD();
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn DISCARD() {
-  discard;
-}
-
-fn f() {
-  DISCARD();
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, CallToFuncWithIfDiscard) {
-  auto* src = R"(
-fn DISCARD() {
-  if (true) {
-    discard;
-  }
-}
-
-fn f() {
-  DISCARD();
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfDiscardElseDiscard) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-    discard;
-  } else {
-    discard;
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  if (true) {
-    discard;
-  } else {
-    discard;
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfDiscardElseReturn) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-    discard;
-  } else {
-    return;
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  if (true) {
-    discard;
-  } else {
-    return;
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfDiscard) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-    discard;
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfReturn) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-    return;
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfElseDiscard) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-  } else {
-    discard;
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, IfElseReturn) {
-  auto* src = R"(
-fn f() {
-  if (true) {
-  } else {
-    return;
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, LoopWithDiscard) {
-  auto* src = R"(
-fn f() {
-  loop {
-    var a = 1;
-    discard;
-
-    continuing {
-      var b = 2;
-    }
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  loop {
-    var a = 1;
-    discard;
-
-    continuing {
-      var b = 2;
-    }
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, LoopWithConditionalBreak) {
-  auto* src = R"(
-fn f() {
-  loop {
-    var a = 1;
-    if (true) {
-      break;
-    }
-
-    continuing {
-      var b = 2;
-    }
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, LoopWithConditionalBreakInContinuing) {
-  auto* src = R"(
-fn f() {
-  loop {
-
-    continuing {
-      if (true) {
-        break;
-      }
-    }
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, SwitchDefaultDiscard) {
-  auto* src = R"(
-fn f() {
-  switch(1) {
-    default: {
-      discard;
-    }
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  switch(1) {
-    default: {
-      discard;
-    }
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, SwitchCaseReturnDefaultDiscard) {
-  auto* src = R"(
-fn f() {
-  switch(1) {
-    case 0: {
-      return;
-    }
-    default: {
-      discard;
-    }
-  }
-  var remove_me = 1;
-  if (true) {
-    var remove_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  switch(1) {
-    case 0: {
-      return;
-    }
-    default: {
-      discard;
-    }
-  }
-}
-)";
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, SwitchCaseBreakDefaultDiscard) {
-  auto* src = R"(
-fn f() {
-  switch(1) {
-    case 0: {
-      break;
-    }
-    default: {
-      discard;
-    }
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RemoveUnreachableStatementsTest, SwitchCaseReturnDefaultBreak) {
-  auto* src = R"(
-fn f() {
-  switch(1) {
-    case 0: {
-      return;
-    }
-    default: {
-      break;
-    }
-  }
-  var preserve_me = 1;
-  if (true) {
-    var preserve_me_too = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<RemoveUnreachableStatements>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/renamer.cc b/src/transform/renamer.cc
deleted file mode 100644
index 6863c3d..0000000
--- a/src/transform/renamer.cc
+++ /dev/null
@@ -1,1366 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/renamer.h"
-
-#include <memory>
-#include <unordered_set>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/text/unicode.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer::Data);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer::Config);
-
-namespace tint::transform {
-
-namespace {
-
-// This list is used for a binary search and must be kept in sorted order.
-const char* kReservedKeywordsGLSL[] = {
-    "abs",
-    "acos",
-    "acosh",
-    "active",
-    "all",
-    "any",
-    "asin",
-    "asinh",
-    "asm",
-    "atan",
-    "atanh",
-    "atomicAdd",
-    "atomicAnd",
-    "atomicCompSwap",
-    "atomicCounter",
-    "atomicCounterDecrement",
-    "atomicCounterIncrement",
-    "atomicExchange",
-    "atomicMax",
-    "atomicMin",
-    "atomicOr",
-    "atomicXor",
-    "atomic_uint",
-    "attribute",
-    "barrier",
-    "bitCount",
-    "bitfieldExtract",
-    "bitfieldInsert",
-    "bitfieldReverse",
-    "bool",
-    "break",
-    "buffer",
-    "bvec2",
-    "bvec3",
-    "bvec4",
-    "case",
-    "cast",
-    "ceil",
-    "centroid",
-    "clamp",
-    "class",
-    "coherent",
-    "common",
-    "const",
-    "continue",
-    "cos",
-    "cosh",
-    "cross",
-    "dFdx",
-    "dFdy",
-    "default",
-    "degrees",
-    "determinant",
-    "discard",
-    "distance",
-    "dmat2",
-    "dmat2x2",
-    "dmat2x3",
-    "dmat2x4",
-    "dmat3",
-    "dmat3x2",
-    "dmat3x3",
-    "dmat3x4",
-    "dmat4",
-    "dmat4x2",
-    "dmat4x3",
-    "dmat4x4",
-    "do",
-    "dot",
-    "double",
-    "dvec2",
-    "dvec3",
-    "dvec4",
-    "else",
-    "enum",
-    "equal",
-    "exp",
-    "exp2",
-    "extern",
-    "external",
-    "faceforward",
-    "false",
-    "filter",
-    "findLSB",
-    "findMSB",
-    "fixed",
-    "flat",
-    "float",
-    "floatBitsToInt",
-    "floatBitsToUint",
-    "floor",
-    "for",
-    "fract",
-    "frexp",
-    "fvec2",
-    "fvec3",
-    "fvec4",
-    "fwidth",
-    "gl_BaseInstance",
-    "gl_BaseVertex",
-    "gl_ClipDistance",
-    "gl_DepthRangeParameters",
-    "gl_DrawID",
-    "gl_FragCoord",
-    "gl_FragDepth",
-    "gl_FrontFacing",
-    "gl_GlobalInvocationID",
-    "gl_InstanceID",
-    "gl_LocalInvocationID",
-    "gl_LocalInvocationIndex",
-    "gl_NumSamples",
-    "gl_NumWorkGroups",
-    "gl_PerVertex",
-    "gl_PointCoord",
-    "gl_PointSize",
-    "gl_Position",
-    "gl_PrimitiveID",
-    "gl_SampleID",
-    "gl_SampleMask",
-    "gl_SampleMaskIn",
-    "gl_SamplePosition",
-    "gl_VertexID",
-    "gl_WorkGroupID",
-    "gl_WorkGroupSize",
-    "goto",
-    "greaterThan",
-    "greaterThanEqual",
-    "groupMemoryBarrier",
-    "half",
-    "highp",
-    "hvec2",
-    "hvec3",
-    "hvec4",
-    "if",
-    "iimage1D",
-    "iimage1DArray",
-    "iimage2D",
-    "iimage2DArray",
-    "iimage2DMS",
-    "iimage2DMSArray",
-    "iimage2DRect",
-    "iimage3D",
-    "iimageBuffer",
-    "iimageCube",
-    "iimageCubeArray",
-    "image1D",
-    "image1DArray",
-    "image2D",
-    "image2DArray",
-    "image2DMS",
-    "image2DMSArray",
-    "image2DRect",
-    "image3D",
-    "imageBuffer",
-    "imageCube",
-    "imageCubeArray",
-    "imageLoad",
-    "imageSize",
-    "imageStore",
-    "imulExtended",
-    "in",
-    "inline",
-    "inout",
-    "input",
-    "int",
-    "intBitsToFloat",
-    "interface",
-    "invariant",
-    "inverse",
-    "inversesqrt",
-    "isampler1D",
-    "isampler1DArray",
-    "isampler2D",
-    "isampler2DArray",
-    "isampler2DMS",
-    "isampler2DMSArray",
-    "isampler2DRect",
-    "isampler3D",
-    "isamplerBuffer",
-    "isamplerCube",
-    "isamplerCubeArray",
-    "isinf",
-    "isnan",
-    "ivec2",
-    "ivec3",
-    "ivec4",
-    "layout",
-    "ldexp",
-    "length",
-    "lessThan",
-    "lessThanEqual",
-    "log",
-    "log2",
-    "long",
-    "lowp",
-    "main",
-    "mat2",
-    "mat2x2",
-    "mat2x3",
-    "mat2x4",
-    "mat3",
-    "mat3x2",
-    "mat3x3",
-    "mat3x4",
-    "mat4",
-    "mat4x2",
-    "mat4x3",
-    "mat4x4",
-    "matrixCompMult",
-    "max",
-    "mediump",
-    "memoryBarrier",
-    "memoryBarrierAtomicCounter",
-    "memoryBarrierBuffer",
-    "memoryBarrierImage",
-    "memoryBarrierShared",
-    "min",
-    "mix",
-    "mod",
-    "modf",
-    "namespace",
-    "noinline",
-    "noperspective",
-    "normalize",
-    "not",
-    "notEqual",
-    "out",
-    "outerProduct",
-    "output",
-    "packHalf2x16",
-    "packSnorm2x16",
-    "packSnorm4x8",
-    "packUnorm2x16",
-    "packUnorm4x8",
-    "partition",
-    "patch",
-    "pow",
-    "precise",
-    "precision",
-    "public",
-    "radians",
-    "readonly",
-    "reflect",
-    "refract",
-    "resource",
-    "restrict",
-    "return",
-    "round",
-    "roundEven",
-    "sample",
-    "sampler1D",
-    "sampler1DArray",
-    "sampler1DArrayShadow",
-    "sampler1DShadow",
-    "sampler2D",
-    "sampler2DArray",
-    "sampler2DArrayShadow",
-    "sampler2DMS",
-    "sampler2DMSArray",
-    "sampler2DRect",
-    "sampler2DRectShadow",
-    "sampler2DShadow",
-    "sampler3D",
-    "sampler3DRect",
-    "samplerBuffer",
-    "samplerCube",
-    "samplerCubeArray",
-    "samplerCubeArrayShadow",
-    "samplerCubeShadow",
-    "shared",
-    "short",
-    "sign",
-    "sin",
-    "sinh",
-    "sizeof",
-    "smooth",
-    "smoothstep",
-    "sqrt",
-    "static",
-    "step",
-    "struct",
-    "subroutine",
-    "superp",
-    "switch",
-    "tan",
-    "tanh",
-    "template",
-    "texelFetch",
-    "texelFetchOffset",
-    "texture",
-    "textureGather",
-    "textureGatherOffset",
-    "textureGrad",
-    "textureGradOffset",
-    "textureLod",
-    "textureLodOffset",
-    "textureOffset",
-    "textureProj",
-    "textureProjGrad",
-    "textureProjGradOffset",
-    "textureProjLod",
-    "textureProjLodOffset",
-    "textureProjOffset",
-    "textureSize",
-    "this",
-    "transpose",
-    "true",
-    "trunc",
-    "typedef",
-    "uaddCarry",
-    "uimage1D",
-    "uimage1DArray",
-    "uimage2D",
-    "uimage2DArray",
-    "uimage2DMS",
-    "uimage2DMSArray",
-    "uimage2DRect",
-    "uimage3D",
-    "uimageBuffer",
-    "uimageCube",
-    "uimageCubeArray",
-    "uint",
-    "uintBitsToFloat",
-    "umulExtended",
-    "uniform",
-    "union",
-    "unpackHalf2x16",
-    "unpackSnorm2x16",
-    "unpackSnorm4x8",
-    "unpackUnorm2x16",
-    "unpackUnorm4x8",
-    "unsigned",
-    "usampler1D",
-    "usampler1DArray",
-    "usampler2D",
-    "usampler2DArray",
-    "usampler2DMS",
-    "usampler2DMSArray",
-    "usampler2DRect",
-    "usampler3D",
-    "usamplerBuffer",
-    "usamplerCube",
-    "usamplerCubeArray",
-    "using",
-    "usubBorrow",
-    "uvec2",
-    "uvec3",
-    "uvec4",
-    "varying",
-    "vec2",
-    "vec3",
-    "vec4",
-    "void",
-    "volatile",
-    "while",
-    "writeonly",
-};
-
-// This list is used for a binary search and must be kept in sorted order.
-const char* kReservedKeywordsHLSL[] = {
-    "AddressU",
-    "AddressV",
-    "AddressW",
-    "AllMemoryBarrier",
-    "AllMemoryBarrierWithGroupSync",
-    "AppendStructuredBuffer",
-    "BINORMAL",
-    "BLENDINDICES",
-    "BLENDWEIGHT",
-    "BlendState",
-    "BorderColor",
-    "Buffer",
-    "ByteAddressBuffer",
-    "COLOR",
-    "CheckAccessFullyMapped",
-    "ComparisonFunc",
-    "CompileShader",
-    "ComputeShader",
-    "ConsumeStructuredBuffer",
-    "D3DCOLORtoUBYTE4",
-    "DEPTH",
-    "DepthStencilState",
-    "DepthStencilView",
-    "DeviceMemoryBarrier",
-    "DeviceMemroyBarrierWithGroupSync",
-    "DomainShader",
-    "EvaluateAttributeAtCentroid",
-    "EvaluateAttributeAtSample",
-    "EvaluateAttributeSnapped",
-    "FOG",
-    "Filter",
-    "GeometryShader",
-    "GetRenderTargetSampleCount",
-    "GetRenderTargetSamplePosition",
-    "GroupMemoryBarrier",
-    "GroupMemroyBarrierWithGroupSync",
-    "Hullshader",
-    "InputPatch",
-    "InterlockedAdd",
-    "InterlockedAnd",
-    "InterlockedCompareExchange",
-    "InterlockedCompareStore",
-    "InterlockedExchange",
-    "InterlockedMax",
-    "InterlockedMin",
-    "InterlockedOr",
-    "InterlockedXor",
-    "LineStream",
-    "MaxAnisotropy",
-    "MaxLOD",
-    "MinLOD",
-    "MipLODBias",
-    "NORMAL",
-    "NULL",
-    "Normal",
-    "OutputPatch",
-    "POSITION",
-    "POSITIONT",
-    "PSIZE",
-    "PixelShader",
-    "PointStream",
-    "Process2DQuadTessFactorsAvg",
-    "Process2DQuadTessFactorsMax",
-    "Process2DQuadTessFactorsMin",
-    "ProcessIsolineTessFactors",
-    "ProcessQuadTessFactorsAvg",
-    "ProcessQuadTessFactorsMax",
-    "ProcessQuadTessFactorsMin",
-    "ProcessTriTessFactorsAvg",
-    "ProcessTriTessFactorsMax",
-    "ProcessTriTessFactorsMin",
-    "RWBuffer",
-    "RWByteAddressBuffer",
-    "RWStructuredBuffer",
-    "RWTexture1D",
-    "RWTexture1DArray",
-    "RWTexture2D",
-    "RWTexture2DArray",
-    "RWTexture3D",
-    "RasterizerState",
-    "RenderTargetView",
-    "SV_ClipDistance",
-    "SV_Coverage",
-    "SV_CullDistance",
-    "SV_Depth",
-    "SV_DepthGreaterEqual",
-    "SV_DepthLessEqual",
-    "SV_DispatchThreadID",
-    "SV_DomainLocation",
-    "SV_GSInstanceID",
-    "SV_GroupID",
-    "SV_GroupIndex",
-    "SV_GroupThreadID",
-    "SV_InnerCoverage",
-    "SV_InsideTessFactor",
-    "SV_InstanceID",
-    "SV_IsFrontFace",
-    "SV_OutputControlPointID",
-    "SV_Position",
-    "SV_PrimitiveID",
-    "SV_RenderTargetArrayIndex",
-    "SV_SampleIndex",
-    "SV_StencilRef",
-    "SV_Target",
-    "SV_TessFactor",
-    "SV_VertexArrayIndex",
-    "SV_VertexID",
-    "Sampler",
-    "Sampler1D",
-    "Sampler2D",
-    "Sampler3D",
-    "SamplerCUBE",
-    "SamplerComparisonState",
-    "SamplerState",
-    "StructuredBuffer",
-    "TANGENT",
-    "TESSFACTOR",
-    "TEXCOORD",
-    "Texcoord",
-    "Texture",
-    "Texture1D",
-    "Texture1DArray",
-    "Texture2D",
-    "Texture2DArray",
-    "Texture2DMS",
-    "Texture2DMSArray",
-    "Texture3D",
-    "TextureCube",
-    "TextureCubeArray",
-    "TriangleStream",
-    "VFACE",
-    "VPOS",
-    "VertexShader",
-    "abort",
-    "allow_uav_condition",
-    "asdouble",
-    "asfloat",
-    "asint",
-    "asm",
-    "asm_fragment",
-    "asuint",
-    "auto",
-    "bool",
-    "bool1",
-    "bool1x1",
-    "bool1x2",
-    "bool1x3",
-    "bool1x4",
-    "bool2",
-    "bool2x1",
-    "bool2x2",
-    "bool2x3",
-    "bool2x4",
-    "bool3",
-    "bool3x1",
-    "bool3x2",
-    "bool3x3",
-    "bool3x4",
-    "bool4",
-    "bool4x1",
-    "bool4x2",
-    "bool4x3",
-    "bool4x4",
-    "branch",
-    "break",
-    "call",
-    "case",
-    "catch",
-    "cbuffer",
-    "centroid",
-    "char",
-    "class",
-    "clip",
-    "column_major",
-    "compile",
-    "compile_fragment",
-    "const",
-    "const_cast",
-    "continue",
-    "countbits",
-    "ddx",
-    "ddx_coarse",
-    "ddx_fine",
-    "ddy",
-    "ddy_coarse",
-    "ddy_fine",
-    "default",
-    "degrees",
-    "delete",
-    "discard",
-    "do",
-    "double",
-    "double1",
-    "double1x1",
-    "double1x2",
-    "double1x3",
-    "double1x4",
-    "double2",
-    "double2x1",
-    "double2x2",
-    "double2x3",
-    "double2x4",
-    "double3",
-    "double3x1",
-    "double3x2",
-    "double3x3",
-    "double3x4",
-    "double4",
-    "double4x1",
-    "double4x2",
-    "double4x3",
-    "double4x4",
-    "dst",
-    "dword",
-    "dword1",
-    "dword1x1",
-    "dword1x2",
-    "dword1x3",
-    "dword1x4",
-    "dword2",
-    "dword2x1",
-    "dword2x2",
-    "dword2x3",
-    "dword2x4",
-    "dword3",
-    "dword3x1",
-    "dword3x2",
-    "dword3x3",
-    "dword3x4",
-    "dword4",
-    "dword4x1",
-    "dword4x2",
-    "dword4x3",
-    "dword4x4",
-    "dynamic_cast",
-    "else",
-    "enum",
-    "errorf",
-    "explicit",
-    "export",
-    "extern",
-    "f16to32",
-    "f32tof16",
-    "false",
-    "fastopt",
-    "firstbithigh",
-    "firstbitlow",
-    "flatten",
-    "float",
-    "float1",
-    "float1x1",
-    "float1x2",
-    "float1x3",
-    "float1x4",
-    "float2",
-    "float2x1",
-    "float2x2",
-    "float2x3",
-    "float2x4",
-    "float3",
-    "float3x1",
-    "float3x2",
-    "float3x3",
-    "float3x4",
-    "float4",
-    "float4x1",
-    "float4x2",
-    "float4x3",
-    "float4x4",
-    "fmod",
-    "for",
-    "forcecase",
-    "frac",
-    "friend",
-    "fxgroup",
-    "goto",
-    "groupshared",
-    "half",
-    "half1",
-    "half1x1",
-    "half1x2",
-    "half1x3",
-    "half1x4",
-    "half2",
-    "half2x1",
-    "half2x2",
-    "half2x3",
-    "half2x4",
-    "half3",
-    "half3x1",
-    "half3x2",
-    "half3x3",
-    "half3x4",
-    "half4",
-    "half4x1",
-    "half4x2",
-    "half4x3",
-    "half4x4",
-    "if",
-    "in",
-    "inline",
-    "inout",
-    "int",
-    "int1",
-    "int1x1",
-    "int1x2",
-    "int1x3",
-    "int1x4",
-    "int2",
-    "int2x1",
-    "int2x2",
-    "int2x3",
-    "int2x4",
-    "int3",
-    "int3x1",
-    "int3x2",
-    "int3x3",
-    "int3x4",
-    "int4",
-    "int4x1",
-    "int4x2",
-    "int4x3",
-    "int4x4",
-    "interface",
-    "isfinite",
-    "isinf",
-    "isnan",
-    "lerp",
-    "line",
-    "lineadj",
-    "linear",
-    "lit",
-    "log10",
-    "long",
-    "loop",
-    "mad",
-    "matrix",
-    "min10float",
-    "min10float1",
-    "min10float1x1",
-    "min10float1x2",
-    "min10float1x3",
-    "min10float1x4",
-    "min10float2",
-    "min10float2x1",
-    "min10float2x2",
-    "min10float2x3",
-    "min10float2x4",
-    "min10float3",
-    "min10float3x1",
-    "min10float3x2",
-    "min10float3x3",
-    "min10float3x4",
-    "min10float4",
-    "min10float4x1",
-    "min10float4x2",
-    "min10float4x3",
-    "min10float4x4",
-    "min12int",
-    "min12int1",
-    "min12int1x1",
-    "min12int1x2",
-    "min12int1x3",
-    "min12int1x4",
-    "min12int2",
-    "min12int2x1",
-    "min12int2x2",
-    "min12int2x3",
-    "min12int2x4",
-    "min12int3",
-    "min12int3x1",
-    "min12int3x2",
-    "min12int3x3",
-    "min12int3x4",
-    "min12int4",
-    "min12int4x1",
-    "min12int4x2",
-    "min12int4x3",
-    "min12int4x4",
-    "min16float",
-    "min16float1",
-    "min16float1x1",
-    "min16float1x2",
-    "min16float1x3",
-    "min16float1x4",
-    "min16float2",
-    "min16float2x1",
-    "min16float2x2",
-    "min16float2x3",
-    "min16float2x4",
-    "min16float3",
-    "min16float3x1",
-    "min16float3x2",
-    "min16float3x3",
-    "min16float3x4",
-    "min16float4",
-    "min16float4x1",
-    "min16float4x2",
-    "min16float4x3",
-    "min16float4x4",
-    "min16int",
-    "min16int1",
-    "min16int1x1",
-    "min16int1x2",
-    "min16int1x3",
-    "min16int1x4",
-    "min16int2",
-    "min16int2x1",
-    "min16int2x2",
-    "min16int2x3",
-    "min16int2x4",
-    "min16int3",
-    "min16int3x1",
-    "min16int3x2",
-    "min16int3x3",
-    "min16int3x4",
-    "min16int4",
-    "min16int4x1",
-    "min16int4x2",
-    "min16int4x3",
-    "min16int4x4",
-    "min16uint",
-    "min16uint1",
-    "min16uint1x1",
-    "min16uint1x2",
-    "min16uint1x3",
-    "min16uint1x4",
-    "min16uint2",
-    "min16uint2x1",
-    "min16uint2x2",
-    "min16uint2x3",
-    "min16uint2x4",
-    "min16uint3",
-    "min16uint3x1",
-    "min16uint3x2",
-    "min16uint3x3",
-    "min16uint3x4",
-    "min16uint4",
-    "min16uint4x1",
-    "min16uint4x2",
-    "min16uint4x3",
-    "min16uint4x4",
-    "msad4",
-    "mul",
-    "mutable",
-    "namespace",
-    "new",
-    "nointerpolation",
-    "noise",
-    "noperspective",
-    "numthreads",
-    "operator",
-    "out",
-    "packoffset",
-    "pass",
-    "pixelfragment",
-    "pixelshader",
-    "point",
-    "precise",
-    "printf",
-    "private",
-    "protected",
-    "public",
-    "radians",
-    "rcp",
-    "refract",
-    "register",
-    "reinterpret_cast",
-    "return",
-    "row_major",
-    "rsqrt",
-    "sample",
-    "sampler",
-    "sampler1D",
-    "sampler2D",
-    "sampler3D",
-    "samplerCUBE",
-    "sampler_state",
-    "saturate",
-    "shared",
-    "short",
-    "signed",
-    "sincos",
-    "sizeof",
-    "snorm",
-    "stateblock",
-    "stateblock_state",
-    "static",
-    "static_cast",
-    "string",
-    "struct",
-    "switch",
-    "tbuffer",
-    "technique",
-    "technique10",
-    "technique11",
-    "template",
-    "tex1D",
-    "tex1Dbias",
-    "tex1Dgrad",
-    "tex1Dlod",
-    "tex1Dproj",
-    "tex2D",
-    "tex2Dbias",
-    "tex2Dgrad",
-    "tex2Dlod",
-    "tex2Dproj",
-    "tex3D",
-    "tex3Dbias",
-    "tex3Dgrad",
-    "tex3Dlod",
-    "tex3Dproj",
-    "texCUBE",
-    "texCUBEbias",
-    "texCUBEgrad",
-    "texCUBElod",
-    "texCUBEproj",
-    "texture",
-    "texture1D",
-    "texture1DArray",
-    "texture2D",
-    "texture2DArray",
-    "texture2DMS",
-    "texture2DMSArray",
-    "texture3D",
-    "textureCube",
-    "textureCubeArray",
-    "this",
-    "throw",
-    "transpose",
-    "triangle",
-    "triangleadj",
-    "true",
-    "try",
-    "typedef",
-    "typename",
-    "uint",
-    "uint1",
-    "uint1x1",
-    "uint1x2",
-    "uint1x3",
-    "uint1x4",
-    "uint2",
-    "uint2x1",
-    "uint2x2",
-    "uint2x3",
-    "uint2x4",
-    "uint3",
-    "uint3x1",
-    "uint3x2",
-    "uint3x3",
-    "uint3x4",
-    "uint4",
-    "uint4x1",
-    "uint4x2",
-    "uint4x3",
-    "uint4x4",
-    "uniform",
-    "union",
-    "unorm",
-    "unroll",
-    "unsigned",
-    "using",
-    "vector",
-    "vertexfragment",
-    "vertexshader",
-    "virtual",
-    "void",
-    "volatile",
-    "while",
-};
-
-// This list is used for a binary search and must be kept in sorted order.
-const char* kReservedKeywordsMSL[] = {
-    "HUGE_VALF",
-    "HUGE_VALH",
-    "INFINITY",
-    "MAXFLOAT",
-    "MAXHALF",
-    "M_1_PI_F",
-    "M_1_PI_H",
-    "M_2_PI_F",
-    "M_2_PI_H",
-    "M_2_SQRTPI_F",
-    "M_2_SQRTPI_H",
-    "M_E_F",
-    "M_E_H",
-    "M_LN10_F",
-    "M_LN10_H",
-    "M_LN2_F",
-    "M_LN2_H",
-    "M_LOG10E_F",
-    "M_LOG10E_H",
-    "M_LOG2E_F",
-    "M_LOG2E_H",
-    "M_PI_2_F",
-    "M_PI_2_H",
-    "M_PI_4_F",
-    "M_PI_4_H",
-    "M_PI_F",
-    "M_PI_H",
-    "M_SQRT1_2_F",
-    "M_SQRT1_2_H",
-    "M_SQRT2_F",
-    "M_SQRT2_H",
-    "NAN",
-    "access",
-    "alignas",
-    "alignof",
-    "and",
-    "and_eq",
-    "array",
-    "array_ref",
-    "as_type",
-    "asm",
-    "atomic",
-    "atomic_bool",
-    "atomic_int",
-    "atomic_uint",
-    "auto",
-    "bitand",
-    "bitor",
-    "bool",
-    "bool2",
-    "bool3",
-    "bool4",
-    "break",
-    "buffer",
-    "case",
-    "catch",
-    "char",
-    "char16_t",
-    "char2",
-    "char3",
-    "char32_t",
-    "char4",
-    "class",
-    "compl",
-    "const",
-    "const_cast",
-    "const_reference",
-    "constant",
-    "constexpr",
-    "continue",
-    "decltype",
-    "default",
-    "delete",
-    "depth2d",
-    "depth2d_array",
-    "depth2d_ms",
-    "depth2d_ms_array",
-    "depthcube",
-    "depthcube_array",
-    "device",
-    "discard_fragment",
-    "do",
-    "double",
-    "dynamic_cast",
-    "else",
-    "enum",
-    "explicit",
-    "extern",
-    "false",
-    "final",
-    "float",
-    "float2",
-    "float2x2",
-    "float2x3",
-    "float2x4",
-    "float3",
-    "float3x2",
-    "float3x3",
-    "float3x4",
-    "float4",
-    "float4x2",
-    "float4x3",
-    "float4x4",
-    "for",
-    "fragment",
-    "friend",
-    "goto",
-    "half",
-    "half2",
-    "half2x2",
-    "half2x3",
-    "half2x4",
-    "half3",
-    "half3x2",
-    "half3x3",
-    "half3x4",
-    "half4",
-    "half4x2",
-    "half4x3",
-    "half4x4",
-    "if",
-    "imageblock",
-    "infinity",
-    "inline",
-    "int",
-    "int16_t",
-    "int2",
-    "int3",
-    "int32_t",
-    "int4",
-    "int64_t",
-    "int8_t",
-    "kernel",
-    "long",
-    "long2",
-    "long3",
-    "long4",
-    "main",
-    "matrix",
-    "metal",
-    "mutable",
-    "namespace",
-    "new",
-    "noexcept",
-    "not",
-    "not_eq",
-    "nullptr",
-    "operator",
-    "or",
-    "or_eq",
-    "override",
-    "packed_bool2",
-    "packed_bool3",
-    "packed_bool4",
-    "packed_char2",
-    "packed_char3",
-    "packed_char4",
-    "packed_float2",
-    "packed_float3",
-    "packed_float4",
-    "packed_half2",
-    "packed_half3",
-    "packed_half4",
-    "packed_int2",
-    "packed_int3",
-    "packed_int4",
-    "packed_short2",
-    "packed_short3",
-    "packed_short4",
-    "packed_uchar2",
-    "packed_uchar3",
-    "packed_uchar4",
-    "packed_uint2",
-    "packed_uint3",
-    "packed_uint4",
-    "packed_ushort2",
-    "packed_ushort3",
-    "packed_ushort4",
-    "patch_control_point",
-    "private",
-    "protected",
-    "ptrdiff_t",
-    "public",
-    "r16snorm",
-    "r16unorm",
-    "r8unorm",
-    "reference",
-    "register",
-    "reinterpret_cast",
-    "return",
-    "rg11b10f",
-    "rg16snorm",
-    "rg16unorm",
-    "rg8snorm",
-    "rg8unorm",
-    "rgb10a2",
-    "rgb9e5",
-    "rgba16snorm",
-    "rgba16unorm",
-    "rgba8snorm",
-    "rgba8unorm",
-    "sampler",
-    "short",
-    "short2",
-    "short3",
-    "short4",
-    "signed",
-    "size_t",
-    "sizeof",
-    "srgba8unorm",
-    "static",
-    "static_assert",
-    "static_cast",
-    "struct",
-    "switch",
-    "template",
-    "texture",
-    "texture1d",
-    "texture1d_array",
-    "texture2d",
-    "texture2d_array",
-    "texture2d_ms",
-    "texture2d_ms_array",
-    "texture3d",
-    "texture_buffer",
-    "texturecube",
-    "texturecube_array",
-    "this",
-    "thread",
-    "thread_local",
-    "threadgroup",
-    "threadgroup_imageblock",
-    "throw",
-    "true",
-    "try",
-    "typedef",
-    "typeid",
-    "typename",
-    "uchar",
-    "uchar2",
-    "uchar3",
-    "uchar4",
-    "uint",
-    "uint16_t",
-    "uint2",
-    "uint3",
-    "uint32_t",
-    "uint4",
-    "uint64_t",
-    "uint8_t",
-    "ulong2",
-    "ulong3",
-    "ulong4",
-    "uniform",
-    "union",
-    "unsigned",
-    "ushort",
-    "ushort2",
-    "ushort3",
-    "ushort4",
-    "using",
-    "vec",
-    "vertex",
-    "virtual",
-    "void",
-    "volatile",
-    "wchar_t",
-    "while",
-    "xor",
-    "xor_eq",
-};
-
-}  // namespace
-
-Renamer::Data::Data(Remappings&& r) : remappings(std::move(r)) {}
-Renamer::Data::Data(const Data&) = default;
-Renamer::Data::~Data() = default;
-
-Renamer::Config::Config(Target t, bool pu) : target(t), preserve_unicode(pu) {}
-Renamer::Config::Config(const Config&) = default;
-Renamer::Config::~Config() = default;
-
-Renamer::Renamer() = default;
-Renamer::~Renamer() = default;
-
-Output Renamer::Run(const Program* in, const DataMap& inputs) const {
-  ProgramBuilder out;
-  // Disable auto-cloning of symbols, since we want to rename them.
-  CloneContext ctx(&out, in, false);
-
-  // Swizzles, builtin calls and builtin structure members need to keep their
-  // symbols preserved.
-  std::unordered_set<const ast::IdentifierExpression*> preserve;
-  for (auto* node : in->ASTNodes().Objects()) {
-    if (auto* member = node->As<ast::MemberAccessorExpression>()) {
-      auto* sem = in->Sem().Get(member);
-      if (!sem) {
-        TINT_ICE(Transform, out.Diagnostics())
-            << "MemberAccessorExpression has no semantic info";
-        continue;
-      }
-      if (sem->Is<sem::Swizzle>()) {
-        preserve.emplace(member->member);
-      } else if (auto* str_expr = in->Sem().Get(member->structure)) {
-        if (auto* ty = str_expr->Type()->UnwrapRef()->As<sem::Struct>()) {
-          if (ty->Declaration() == nullptr) {  // Builtin structure
-            preserve.emplace(member->member);
-          }
-        }
-      }
-    } else if (auto* call = node->As<ast::CallExpression>()) {
-      auto* sem = in->Sem().Get(call);
-      if (!sem) {
-        TINT_ICE(Transform, out.Diagnostics())
-            << "CallExpression has no semantic info";
-        continue;
-      }
-      if (sem->Target()->Is<sem::Builtin>()) {
-        preserve.emplace(call->target.name);
-      }
-    }
-  }
-
-  Data::Remappings remappings;
-
-  Target target = Target::kAll;
-  bool preserve_unicode = false;
-
-  if (auto* cfg = inputs.Get<Config>()) {
-    target = cfg->target;
-    preserve_unicode = cfg->preserve_unicode;
-  }
-
-  ctx.ReplaceAll([&](Symbol sym_in) {
-    auto name_in = ctx.src->Symbols().NameFor(sym_in);
-    if (preserve_unicode || text::utf8::IsASCII(name_in)) {
-      switch (target) {
-        case Target::kAll:
-          // Always rename.
-          break;
-        case Target::kGlslKeywords:
-          if (!std::binary_search(
-                  kReservedKeywordsGLSL,
-                  kReservedKeywordsGLSL +
-                      sizeof(kReservedKeywordsGLSL) / sizeof(const char*),
-                  name_in) &&
-              name_in.compare(0, 3, "gl_")) {
-            // No match, just reuse the original name.
-            return ctx.dst->Symbols().New(name_in);
-          }
-          break;
-        case Target::kHlslKeywords:
-          if (!std::binary_search(
-                  kReservedKeywordsHLSL,
-                  kReservedKeywordsHLSL +
-                      sizeof(kReservedKeywordsHLSL) / sizeof(const char*),
-                  name_in)) {
-            // No match, just reuse the original name.
-            return ctx.dst->Symbols().New(name_in);
-          }
-          break;
-        case Target::kMslKeywords:
-          if (!std::binary_search(
-                  kReservedKeywordsMSL,
-                  kReservedKeywordsMSL +
-                      sizeof(kReservedKeywordsMSL) / sizeof(const char*),
-                  name_in)) {
-            // No match, just reuse the original name.
-            return ctx.dst->Symbols().New(name_in);
-          }
-          break;
-      }
-    }
-
-    auto sym_out = ctx.dst->Sym();
-    remappings.emplace(name_in, ctx.dst->Symbols().NameFor(sym_out));
-    return sym_out;
-  });
-
-  ctx.ReplaceAll([&](const ast::IdentifierExpression* ident)
-                     -> const ast::IdentifierExpression* {
-    if (preserve.count(ident)) {
-      auto sym_in = ident->symbol;
-      auto str = in->Symbols().NameFor(sym_in);
-      auto sym_out = out.Symbols().Register(str);
-      return ctx.dst->create<ast::IdentifierExpression>(
-          ctx.Clone(ident->source), sym_out);
-    }
-    return nullptr;  // Clone ident. Uses the symbol remapping above.
-  });
-  ctx.Clone();
-
-  return Output(Program(std::move(out)),
-                std::make_unique<Data>(std::move(remappings)));
-}
-
-}  // namespace tint::transform
diff --git a/src/transform/renamer.h b/src/transform/renamer.h
deleted file mode 100644
index 9b448e8..0000000
--- a/src/transform/renamer.h
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_RENAMER_H_
-#define SRC_TRANSFORM_RENAMER_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/transform/transform.h"
-
-namespace tint::transform {
-
-/// Renamer is a Transform that renames all the symbols in a program.
-class Renamer : public Castable<Renamer, Transform> {
- public:
-  /// Data is outputted by the Renamer transform.
-  /// Data holds information about shader usage and constant buffer offsets.
-  struct Data : public Castable<Data, transform::Data> {
-    /// Remappings is a map of old symbol name to new symbol name
-    using Remappings = std::unordered_map<std::string, std::string>;
-
-    /// Constructor
-    /// @param remappings the symbol remappings
-    explicit Data(Remappings&& remappings);
-
-    /// Copy constructor
-    Data(const Data&);
-
-    /// Destructor
-    ~Data() override;
-
-    /// A map of old symbol name to new symbol name
-    const Remappings remappings;
-  };
-
-  /// Target is an enumerator of rename targets that can be used
-  enum class Target {
-    /// Rename every symbol.
-    kAll,
-    /// Only rename symbols that are reserved keywords in GLSL.
-    kGlslKeywords,
-    /// Only rename symbols that are reserved keywords in HLSL.
-    kHlslKeywords,
-    /// Only rename symbols that are reserved keywords in MSL.
-    kMslKeywords,
-  };
-
-  /// Optional configuration options for the transform.
-  /// If omitted, then the renamer will use Target::kAll.
-  struct Config : public Castable<Config, transform::Data> {
-    /// Constructor
-    /// @param tgt the targets to rename
-    /// @param keep_unicode if false, symbols with non-ascii code-points are
-    /// renamed
-    explicit Config(Target tgt, bool keep_unicode = false);
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// The targets to rename
-    Target const target = Target::kAll;
-
-    /// If false, symbols with non-ascii code-points are renamed.
-    bool preserve_unicode = false;
-  };
-
-  /// Constructor using a the configuration provided in the input Data
-  Renamer();
-
-  /// Destructor
-  ~Renamer() override;
-
-  /// Runs the transform on `program`, returning the transformation result.
-  /// @param program the source program to transform
-  /// @param data optional extra transform-specific input data
-  /// @returns the transformation result
-  Output Run(const Program* program, const DataMap& data = {}) const override;
-};
-
-}  // namespace tint::transform
-
-#endif  // SRC_TRANSFORM_RENAMER_H_
diff --git a/src/transform/renamer_test.cc b/src/transform/renamer_test.cc
deleted file mode 100644
index 56c887c..0000000
--- a/src/transform/renamer_test.cc
+++ /dev/null
@@ -1,1463 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/renamer.h"
-
-#include <memory>
-
-#include "gmock/gmock.h"
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-constexpr const char kUnicodeIdentifier[] =  // "𝖎𝖉𝖊𝖓𝖙𝖎𝖋𝖎𝖊𝖗123"
-    "\xf0\x9d\x96\x8e\xf0\x9d\x96\x89\xf0\x9d\x96\x8a\xf0\x9d\x96\x93"
-    "\xf0\x9d\x96\x99\xf0\x9d\x96\x8e\xf0\x9d\x96\x8b\xf0\x9d\x96\x8e"
-    "\xf0\x9d\x96\x8a\xf0\x9d\x96\x97\x31\x32\x33";
-
-using ::testing::ContainerEq;
-
-using RenamerTest = TransformTest;
-
-TEST_F(RenamerTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_EQ(data->remappings.size(), 0u);
-}
-
-TEST_F(RenamerTest, BasicModuleVertexIndex) {
-  auto* src = R"(
-fn test(vert_idx : u32) -> u32 {
-  return vert_idx;
-}
-
-@stage(vertex)
-fn entry(@builtin(vertex_index) vert_idx : u32
-        ) -> @builtin(position) vec4<f32>  {
-  _ = test(vert_idx);
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-fn tint_symbol(tint_symbol_1 : u32) -> u32 {
-  return tint_symbol_1;
-}
-
-@stage(vertex)
-fn tint_symbol_2(@builtin(vertex_index) tint_symbol_1 : u32) -> @builtin(position) vec4<f32> {
-  _ = tint_symbol(tint_symbol_1);
-  return vec4<f32>();
-}
-)";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_NE(data, nullptr);
-  Renamer::Data::Remappings expected_remappings = {
-      {"vert_idx", "tint_symbol_1"},
-      {"test", "tint_symbol"},
-      {"entry", "tint_symbol_2"},
-  };
-  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
-}
-
-TEST_F(RenamerTest, PreserveSwizzles) {
-  auto* src = R"(
-@stage(vertex)
-fn entry() -> @builtin(position) vec4<f32> {
-  var v : vec4<f32>;
-  var rgba : f32;
-  var xyzw : f32;
-  return v.zyxw + v.rgab;
-}
-)";
-
-  auto* expect = R"(
-@stage(vertex)
-fn tint_symbol() -> @builtin(position) vec4<f32> {
-  var tint_symbol_1 : vec4<f32>;
-  var tint_symbol_2 : f32;
-  var tint_symbol_3 : f32;
-  return (tint_symbol_1.zyxw + tint_symbol_1.rgab);
-}
-)";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_NE(data, nullptr);
-  Renamer::Data::Remappings expected_remappings = {
-      {"entry", "tint_symbol"},
-      {"v", "tint_symbol_1"},
-      {"rgba", "tint_symbol_2"},
-      {"xyzw", "tint_symbol_3"},
-  };
-  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
-}
-
-TEST_F(RenamerTest, PreserveBuiltins) {
-  auto* src = R"(
-@stage(vertex)
-fn entry() -> @builtin(position) vec4<f32> {
-  var blah : vec4<f32>;
-  return abs(blah);
-}
-)";
-
-  auto* expect = R"(
-@stage(vertex)
-fn tint_symbol() -> @builtin(position) vec4<f32> {
-  var tint_symbol_1 : vec4<f32>;
-  return abs(tint_symbol_1);
-}
-)";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_NE(data, nullptr);
-  Renamer::Data::Remappings expected_remappings = {
-      {"entry", "tint_symbol"},
-      {"blah", "tint_symbol_1"},
-  };
-  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
-}
-
-TEST_F(RenamerTest, PreserveBuiltinTypes) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn entry() {
-  var a = modf(1.0).whole;
-  var b = modf(1.0).fract;
-  var c = frexp(1.0).sig;
-  var d = frexp(1.0).exp;
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn tint_symbol() {
-  var tint_symbol_1 = modf(1.0).whole;
-  var tint_symbol_2 = modf(1.0).fract;
-  var tint_symbol_3 = frexp(1.0).sig;
-  var tint_symbol_4 = frexp(1.0).exp;
-}
-)";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_NE(data, nullptr);
-  Renamer::Data::Remappings expected_remappings = {
-      {"entry", "tint_symbol"}, {"a", "tint_symbol_1"}, {"b", "tint_symbol_2"},
-      {"c", "tint_symbol_3"},   {"d", "tint_symbol_4"},
-  };
-  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
-}
-
-TEST_F(RenamerTest, PreserveUnicode) {
-  auto src = R"(
-@stage(fragment)
-fn frag_main() {
-  var )" + std::string(kUnicodeIdentifier) +
-             R"( : i32;
-}
-)";
-
-  auto expect = src;
-
-  DataMap inputs;
-  inputs.Add<Renamer::Config>(Renamer::Target::kMslKeywords,
-                              /* preserve_unicode */ true);
-  auto got = Run<Renamer>(src, inputs);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RenamerTest, AttemptSymbolCollision) {
-  auto* src = R"(
-@stage(vertex)
-fn entry() -> @builtin(position) vec4<f32> {
-  var tint_symbol : vec4<f32>;
-  var tint_symbol_2 : vec4<f32>;
-  var tint_symbol_4 : vec4<f32>;
-  return tint_symbol + tint_symbol_2 + tint_symbol_4;
-}
-)";
-
-  auto* expect = R"(
-@stage(vertex)
-fn tint_symbol() -> @builtin(position) vec4<f32> {
-  var tint_symbol_1 : vec4<f32>;
-  var tint_symbol_2 : vec4<f32>;
-  var tint_symbol_3 : vec4<f32>;
-  return ((tint_symbol_1 + tint_symbol_2) + tint_symbol_3);
-}
-)";
-
-  auto got = Run<Renamer>(src);
-
-  EXPECT_EQ(expect, str(got));
-
-  auto* data = got.data.Get<Renamer::Data>();
-
-  ASSERT_NE(data, nullptr);
-  Renamer::Data::Remappings expected_remappings = {
-      {"entry", "tint_symbol"},
-      {"tint_symbol", "tint_symbol_1"},
-      {"tint_symbol_2", "tint_symbol_2"},
-      {"tint_symbol_4", "tint_symbol_3"},
-  };
-  EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
-}
-
-using RenamerTestGlsl = TransformTestWithParam<std::string>;
-using RenamerTestHlsl = TransformTestWithParam<std::string>;
-using RenamerTestMsl = TransformTestWithParam<std::string>;
-
-TEST_P(RenamerTestGlsl, Keywords) {
-  auto keyword = GetParam();
-
-  auto src = R"(
-@stage(fragment)
-fn frag_main() {
-  var )" + keyword +
-             R"( : i32;
-}
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn frag_main() {
-  var tint_symbol : i32;
-}
-)";
-
-  DataMap inputs;
-  inputs.Add<Renamer::Config>(Renamer::Target::kGlslKeywords,
-                              /* preserve_unicode */ false);
-  auto got = Run<Renamer>(src, inputs);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_P(RenamerTestHlsl, Keywords) {
-  auto keyword = GetParam();
-
-  auto src = R"(
-@stage(fragment)
-fn frag_main() {
-  var )" + keyword +
-             R"( : i32;
-}
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn frag_main() {
-  var tint_symbol : i32;
-}
-)";
-
-  DataMap inputs;
-  inputs.Add<Renamer::Config>(Renamer::Target::kHlslKeywords,
-                              /* preserve_unicode */ false);
-  auto got = Run<Renamer>(src, inputs);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_P(RenamerTestMsl, Keywords) {
-  auto keyword = GetParam();
-
-  auto src = R"(
-@stage(fragment)
-fn frag_main() {
-  var )" + keyword +
-             R"( : i32;
-}
-)";
-
-  auto* expect = R"(
-@stage(fragment)
-fn frag_main() {
-  var tint_symbol : i32;
-}
-)";
-
-  DataMap inputs;
-  inputs.Add<Renamer::Config>(Renamer::Target::kMslKeywords,
-                              /* preserve_unicode */ false);
-  auto got = Run<Renamer>(src, inputs);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-INSTANTIATE_TEST_SUITE_P(RenamerTestGlsl,
-                         RenamerTestGlsl,
-                         testing::Values("active",
-                                         //    "asm",       // WGSL keyword
-                                         "atomic_uint",
-                                         "attribute",
-                                         //    "bool",      // WGSL keyword
-                                         //    "break",     // WGSL keyword
-                                         "buffer",
-                                         "bvec2",
-                                         "bvec3",
-                                         "bvec4",
-                                         //    "case",      // WGSL keyword
-                                         "cast",
-                                         "centroid",
-                                         "class",
-                                         "coherent",
-                                         "common",
-                                         //    "const",     // WGSL keyword
-                                         //    "continue",  // WGSL keyword
-                                         //    "default",   // WGSL keyword
-                                         //    "discard",   // WGSL keyword
-                                         "dmat2",
-                                         "dmat2x2",
-                                         "dmat2x3",
-                                         "dmat2x4",
-                                         "dmat3",
-                                         "dmat3x2",
-                                         "dmat3x3",
-                                         "dmat3x4",
-                                         "dmat4",
-                                         "dmat4x2",
-                                         "dmat4x3",
-                                         "dmat4x4",
-                                         //    "do",         // WGSL keyword
-                                         "double",
-                                         "dvec2",
-                                         "dvec3",
-                                         "dvec4",
-                                         //    "else"        // WGSL keyword
-                                         //    "enum",       // WGSL keyword
-                                         "extern",
-                                         "external",
-                                         //    "false",      // WGSL keyword
-                                         "filter",
-                                         "fixed",
-                                         "flat",
-                                         "float",
-                                         //    "for",        // WGSL keyword
-                                         "fvec2",
-                                         "fvec3",
-                                         "fvec4",
-                                         "gl_BaseInstance",
-                                         "gl_BaseVertex",
-                                         "gl_ClipDistance",
-                                         "gl_DepthRangeParameters",
-                                         "gl_DrawID",
-                                         "gl_FragCoord",
-                                         "gl_FragDepth",
-                                         "gl_FrontFacing",
-                                         "gl_GlobalInvocationID",
-                                         "gl_InstanceID",
-                                         "gl_LocalInvocationID",
-                                         "gl_LocalInvocationIndex",
-                                         "gl_NumSamples",
-                                         "gl_NumWorkGroups",
-                                         "gl_PerVertex",
-                                         "gl_PointCoord",
-                                         "gl_PointSize",
-                                         "gl_Position",
-                                         "gl_PrimitiveID",
-                                         "gl_SampleID",
-                                         "gl_SampleMask",
-                                         "gl_SampleMaskIn",
-                                         "gl_SamplePosition",
-                                         "gl_VertexID",
-                                         "gl_WorkGroupID",
-                                         "gl_WorkGroupSize",
-                                         "goto",
-                                         "half",
-                                         "highp",
-                                         "hvec2",
-                                         "hvec3",
-                                         "hvec4",
-                                         //    "if",         // WGSL keyword
-                                         "iimage1D",
-                                         "iimage1DArray",
-                                         "iimage2D",
-                                         "iimage2DArray",
-                                         "iimage2DMS",
-                                         "iimage2DMSArray",
-                                         "iimage2DRect",
-                                         "iimage3D",
-                                         "iimageBuffer",
-                                         "iimageCube",
-                                         "iimageCubeArray",
-                                         "image1D",
-                                         "image1DArray",
-                                         "image2D",
-                                         "image2DArray",
-                                         "image2DMS",
-                                         "image2DMSArray",
-                                         "image2DRect",
-                                         "image3D",
-                                         "imageBuffer",
-                                         "imageCube",
-                                         "imageCubeArray",
-                                         "in",
-                                         "inline",
-                                         "inout",
-                                         "input",
-                                         "int",
-                                         "interface",
-                                         "invariant",
-                                         "isampler1D",
-                                         "isampler1DArray",
-                                         "isampler2D",
-                                         "isampler2DArray",
-                                         "isampler2DMS",
-                                         "isampler2DMSArray",
-                                         "isampler2DRect",
-                                         "isampler3D",
-                                         "isamplerBuffer",
-                                         "isamplerCube",
-                                         "isamplerCubeArray",
-                                         "ivec2",
-                                         "ivec3",
-                                         "ivec4",
-                                         "layout",
-                                         "long",
-                                         "lowp",
-                                         //    "mat2x2",      // WGSL keyword
-                                         //    "mat2x3",      // WGSL keyword
-                                         //    "mat2x4",      // WGSL keyword
-                                         //    "mat2",
-                                         "mat3",
-                                         //    "mat3x2",      // WGSL keyword
-                                         //    "mat3x3",      // WGSL keyword
-                                         //    "mat3x4",      // WGSL keyword
-                                         "mat4",
-                                         //    "mat4x2",      // WGSL keyword
-                                         //    "mat4x3",      // WGSL keyword
-                                         //    "mat4x4",      // WGSL keyword
-                                         "mediump",
-                                         "namespace",
-                                         "noinline",
-                                         "noperspective",
-                                         "out",
-                                         "output",
-                                         "partition",
-                                         "patch",
-                                         "precise",
-                                         "precision",
-                                         "public",
-                                         "readonly",
-                                         "resource",
-                                         "restrict",
-                                         //    "return",     // WGSL keyword
-                                         "sample",
-                                         "sampler1D",
-                                         "sampler1DArray",
-                                         "sampler1DArrayShadow",
-                                         "sampler1DShadow",
-                                         "sampler2D",
-                                         "sampler2DArray",
-                                         "sampler2DArrayShadow",
-                                         "sampler2DMS",
-                                         "sampler2DMSArray",
-                                         "sampler2DRect",
-                                         "sampler2DRectShadow",
-                                         "sampler2DShadow",
-                                         "sampler3D",
-                                         "sampler3DRect",
-                                         "samplerBuffer",
-                                         "samplerCube",
-                                         "samplerCubeArray",
-                                         "samplerCubeArrayShadow",
-                                         "samplerCubeShadow",
-                                         "shared",
-                                         "short",
-                                         "sizeof",
-                                         "smooth",
-                                         "static",
-                                         //    "struct",     // WGSL keyword
-                                         "subroutine",
-                                         "superp",
-                                         //    "switch",     // WGSL keyword
-                                         "template",
-                                         "this",
-                                         //    "true",       // WGSL keyword
-                                         //    "typedef",    // WGSL keyword
-                                         "uimage1D",
-                                         "uimage1DArray",
-                                         "uimage2D",
-                                         "uimage2DArray",
-                                         "uimage2DMS",
-                                         "uimage2DMSArray",
-                                         "uimage2DRect",
-                                         "uimage3D",
-                                         "uimageBuffer",
-                                         "uimageCube",
-                                         "uimageCubeArray",
-                                         "uint",
-                                         //    "uniform",    // WGSL keyword
-                                         "union",
-                                         "unsigned",
-                                         "usampler1D",
-                                         "usampler1DArray",
-                                         "usampler2D",
-                                         "usampler2DArray",
-                                         "usampler2DMS",
-                                         "usampler2DMSArray",
-                                         "usampler2DRect",
-                                         "usampler3D",
-                                         "usamplerBuffer",
-                                         "usamplerCube",
-                                         "usamplerCubeArray",
-                                         //    "using",      // WGSL keyword
-                                         "uvec2",
-                                         "uvec3",
-                                         "uvec4",
-                                         "varying",
-                                         //    "vec2",       // WGSL keyword
-                                         //    "vec3",       // WGSL keyword
-                                         //    "vec4",       // WGSL keyword
-                                         //    "void",       // WGSL keyword
-                                         "volatile",
-                                         //    "while",      // WGSL keyword
-                                         "writeonly",
-                                         kUnicodeIdentifier));
-
-INSTANTIATE_TEST_SUITE_P(RenamerTestHlsl,
-                         RenamerTestHlsl,
-                         testing::Values("AddressU",
-                                         "AddressV",
-                                         "AddressW",
-                                         "AllMemoryBarrier",
-                                         "AllMemoryBarrierWithGroupSync",
-                                         "AppendStructuredBuffer",
-                                         "BINORMAL",
-                                         "BLENDINDICES",
-                                         "BLENDWEIGHT",
-                                         "BlendState",
-                                         "BorderColor",
-                                         "Buffer",
-                                         "ByteAddressBuffer",
-                                         "COLOR",
-                                         "CheckAccessFullyMapped",
-                                         "ComparisonFunc",
-                                         "CompileShader",
-                                         "ComputeShader",
-                                         "ConsumeStructuredBuffer",
-                                         "D3DCOLORtoUBYTE4",
-                                         "DEPTH",
-                                         "DepthStencilState",
-                                         "DepthStencilView",
-                                         "DeviceMemoryBarrier",
-                                         "DeviceMemroyBarrierWithGroupSync",
-                                         "DomainShader",
-                                         "EvaluateAttributeAtCentroid",
-                                         "EvaluateAttributeAtSample",
-                                         "EvaluateAttributeSnapped",
-                                         "FOG",
-                                         "Filter",
-                                         "GeometryShader",
-                                         "GetRenderTargetSampleCount",
-                                         "GetRenderTargetSamplePosition",
-                                         "GroupMemoryBarrier",
-                                         "GroupMemroyBarrierWithGroupSync",
-                                         "Hullshader",
-                                         "InputPatch",
-                                         "InterlockedAdd",
-                                         "InterlockedAnd",
-                                         "InterlockedCompareExchange",
-                                         "InterlockedCompareStore",
-                                         "InterlockedExchange",
-                                         "InterlockedMax",
-                                         "InterlockedMin",
-                                         "InterlockedOr",
-                                         "InterlockedXor",
-                                         "LineStream",
-                                         "MaxAnisotropy",
-                                         "MaxLOD",
-                                         "MinLOD",
-                                         "MipLODBias",
-                                         "NORMAL",
-                                         "NULL",
-                                         "Normal",
-                                         "OutputPatch",
-                                         "POSITION",
-                                         "POSITIONT",
-                                         "PSIZE",
-                                         "PixelShader",
-                                         "PointStream",
-                                         "Process2DQuadTessFactorsAvg",
-                                         "Process2DQuadTessFactorsMax",
-                                         "Process2DQuadTessFactorsMin",
-                                         "ProcessIsolineTessFactors",
-                                         "ProcessQuadTessFactorsAvg",
-                                         "ProcessQuadTessFactorsMax",
-                                         "ProcessQuadTessFactorsMin",
-                                         "ProcessTriTessFactorsAvg",
-                                         "ProcessTriTessFactorsMax",
-                                         "ProcessTriTessFactorsMin",
-                                         "RWBuffer",
-                                         "RWByteAddressBuffer",
-                                         "RWStructuredBuffer",
-                                         "RWTexture1D",
-                                         "RWTexture1DArray",
-                                         "RWTexture2D",
-                                         "RWTexture2DArray",
-                                         "RWTexture3D",
-                                         "RasterizerState",
-                                         "RenderTargetView",
-                                         "SV_ClipDistance",
-                                         "SV_Coverage",
-                                         "SV_CullDistance",
-                                         "SV_Depth",
-                                         "SV_DepthGreaterEqual",
-                                         "SV_DepthLessEqual",
-                                         "SV_DispatchThreadID",
-                                         "SV_DomainLocation",
-                                         "SV_GSInstanceID",
-                                         "SV_GroupID",
-                                         "SV_GroupIndex",
-                                         "SV_GroupThreadID",
-                                         "SV_InnerCoverage",
-                                         "SV_InsideTessFactor",
-                                         "SV_InstanceID",
-                                         "SV_IsFrontFace",
-                                         "SV_OutputControlPointID",
-                                         "SV_Position",
-                                         "SV_PrimitiveID",
-                                         "SV_RenderTargetArrayIndex",
-                                         "SV_SampleIndex",
-                                         "SV_StencilRef",
-                                         "SV_Target",
-                                         "SV_TessFactor",
-                                         "SV_VertexArrayIndex",
-                                         "SV_VertexID",
-                                         "Sampler",
-                                         "Sampler1D",
-                                         "Sampler2D",
-                                         "Sampler3D",
-                                         "SamplerCUBE",
-                                         "SamplerComparisonState",
-                                         "SamplerState",
-                                         "StructuredBuffer",
-                                         "TANGENT",
-                                         "TESSFACTOR",
-                                         "TEXCOORD",
-                                         "Texcoord",
-                                         "Texture",
-                                         "Texture1D",
-                                         "Texture1DArray",
-                                         "Texture2D",
-                                         "Texture2DArray",
-                                         "Texture2DMS",
-                                         "Texture2DMSArray",
-                                         "Texture3D",
-                                         "TextureCube",
-                                         "TextureCubeArray",
-                                         "TriangleStream",
-                                         "VFACE",
-                                         "VPOS",
-                                         "VertexShader",
-                                         "abort",
-                                         // "abs",  // WGSL builtin
-                                         // "acos",  // WGSL builtin
-                                         // "all",  // WGSL builtin
-                                         "allow_uav_condition",
-                                         // "any",  // WGSL builtin
-                                         "asdouble",
-                                         "asfloat",
-                                         // "asin",  // WGSL builtin
-                                         "asint",
-                                         // "asm",  // WGSL keyword
-                                         "asm_fragment",
-                                         "asuint",
-                                         // "atan",  // WGSL builtin
-                                         // "atan2",  // WGSL builtin
-                                         "auto",
-                                         // "bool",  // WGSL keyword
-                                         "bool1",
-                                         "bool1x1",
-                                         "bool1x2",
-                                         "bool1x3",
-                                         "bool1x4",
-                                         "bool2",
-                                         "bool2x1",
-                                         "bool2x2",
-                                         "bool2x3",
-                                         "bool2x4",
-                                         "bool3",
-                                         "bool3x1",
-                                         "bool3x2",
-                                         "bool3x3",
-                                         "bool3x4",
-                                         "bool4",
-                                         "bool4x1",
-                                         "bool4x2",
-                                         "bool4x3",
-                                         "bool4x4",
-                                         "branch",
-                                         // "break",  // WGSL keyword
-                                         // "call",  // WGSL builtin
-                                         // "case",  // WGSL keyword
-                                         "catch",
-                                         "cbuffer",
-                                         // "ceil",  // WGSL builtin
-                                         "centroid",
-                                         "char",
-                                         // "clamp",  // WGSL builtin
-                                         "class",
-                                         "clip",
-                                         "column_major",
-                                         "compile",
-                                         "compile_fragment",
-                                         // "const",  // WGSL keyword
-                                         "const_cast",
-                                         // "continue",  // WGSL keyword
-                                         // "cos",  // WGSL builtin
-                                         // "cosh",  // WGSL builtin
-                                         "countbits",
-                                         // "cross",  // WGSL builtin
-                                         "ddx",
-                                         "ddx_coarse",
-                                         "ddx_fine",
-                                         "ddy",
-                                         "ddy_coarse",
-                                         "ddy_fine",
-                                         // "default",  // WGSL keyword
-                                         "degrees",
-                                         "delete",
-                                         // "determinant",  // WGSL builtin
-                                         // "discard",  // WGSL keyword
-                                         // "distance",  // WGSL builtin
-                                         // "do",  // WGSL keyword
-                                         // "dot",  // WGSL builtin
-                                         "double",
-                                         "double1",
-                                         "double1x1",
-                                         "double1x2",
-                                         "double1x3",
-                                         "double1x4",
-                                         "double2",
-                                         "double2x1",
-                                         "double2x2",
-                                         "double2x3",
-                                         "double2x4",
-                                         "double3",
-                                         "double3x1",
-                                         "double3x2",
-                                         "double3x3",
-                                         "double3x4",
-                                         "double4",
-                                         "double4x1",
-                                         "double4x2",
-                                         "double4x3",
-                                         "double4x4",
-                                         "dst",
-                                         "dword",
-                                         "dword1",
-                                         "dword1x1",
-                                         "dword1x2",
-                                         "dword1x3",
-                                         "dword1x4",
-                                         "dword2",
-                                         "dword2x1",
-                                         "dword2x2",
-                                         "dword2x3",
-                                         "dword2x4",
-                                         "dword3",
-                                         "dword3x1",
-                                         "dword3x2",
-                                         "dword3x3",
-                                         "dword3x4",
-                                         "dword4",
-                                         "dword4x1",
-                                         "dword4x2",
-                                         "dword4x3",
-                                         "dword4x4",
-                                         "dynamic_cast",
-                                         // "else",  // WGSL keyword
-                                         // "enum",  // WGSL keyword
-                                         "errorf",
-                                         // "exp",  // WGSL builtin
-                                         // "exp2",  // WGSL builtin
-                                         "explicit",
-                                         "export",
-                                         "extern",
-                                         "f16to32",
-                                         "f32tof16",
-                                         // "faceforward",  // WGSL builtin
-                                         // "false",  // WGSL keyword
-                                         "fastopt",
-                                         "firstbithigh",
-                                         "firstbitlow",
-                                         "flatten",
-                                         "float",
-                                         "float1",
-                                         "float1x1",
-                                         "float1x2",
-                                         "float1x3",
-                                         "float1x4",
-                                         "float2",
-                                         "float2x1",
-                                         "float2x2",
-                                         "float2x3",
-                                         "float2x4",
-                                         "float3",
-                                         "float3x1",
-                                         "float3x2",
-                                         "float3x3",
-                                         "float3x4",
-                                         "float4",
-                                         "float4x1",
-                                         "float4x2",
-                                         "float4x3",
-                                         "float4x4",
-                                         // "floor",  // WGSL builtin
-                                         // "fma",  // WGSL builtin
-                                         "fmod",
-                                         // "for",  // WGSL keyword
-                                         "forcecase",
-                                         "frac",
-                                         // "frexp",  // WGSL builtin
-                                         "friend",
-                                         // "fwidth",  // WGSL builtin
-                                         "fxgroup",
-                                         "goto",
-                                         "groupshared",
-                                         "half",
-                                         "half1",
-                                         "half1x1",
-                                         "half1x2",
-                                         "half1x3",
-                                         "half1x4",
-                                         "half2",
-                                         "half2x1",
-                                         "half2x2",
-                                         "half2x3",
-                                         "half2x4",
-                                         "half3",
-                                         "half3x1",
-                                         "half3x2",
-                                         "half3x3",
-                                         "half3x4",
-                                         "half4",
-                                         "half4x1",
-                                         "half4x2",
-                                         "half4x3",
-                                         "half4x4",
-                                         // "if",  // WGSL keyword
-                                         // "in",  // WGSL keyword
-                                         "inline",
-                                         "inout",
-                                         "int",
-                                         "int1",
-                                         "int1x1",
-                                         "int1x2",
-                                         "int1x3",
-                                         "int1x4",
-                                         "int2",
-                                         "int2x1",
-                                         "int2x2",
-                                         "int2x3",
-                                         "int2x4",
-                                         "int3",
-                                         "int3x1",
-                                         "int3x2",
-                                         "int3x3",
-                                         "int3x4",
-                                         "int4",
-                                         "int4x1",
-                                         "int4x2",
-                                         "int4x3",
-                                         "int4x4",
-                                         "interface",
-                                         "isfinite",
-                                         "isinf",
-                                         "isnan",
-                                         // "ldexp",  // WGSL builtin
-                                         // "length",  // WGSL builtin
-                                         "lerp",
-                                         "line",
-                                         "lineadj",
-                                         "linear",
-                                         "lit",
-                                         // "log",  // WGSL builtin
-                                         "log10",
-                                         // "log2",  // WGSL builtin
-                                         "long",
-                                         // "loop",  // WGSL keyword
-                                         "mad",
-                                         "matrix",
-                                         // "max",  // WGSL builtin
-                                         // "min",  // WGSL builtin
-                                         "min10float",
-                                         "min10float1",
-                                         "min10float1x1",
-                                         "min10float1x2",
-                                         "min10float1x3",
-                                         "min10float1x4",
-                                         "min10float2",
-                                         "min10float2x1",
-                                         "min10float2x2",
-                                         "min10float2x3",
-                                         "min10float2x4",
-                                         "min10float3",
-                                         "min10float3x1",
-                                         "min10float3x2",
-                                         "min10float3x3",
-                                         "min10float3x4",
-                                         "min10float4",
-                                         "min10float4x1",
-                                         "min10float4x2",
-                                         "min10float4x3",
-                                         "min10float4x4",
-                                         "min12int",
-                                         "min12int1",
-                                         "min12int1x1",
-                                         "min12int1x2",
-                                         "min12int1x3",
-                                         "min12int1x4",
-                                         "min12int2",
-                                         "min12int2x1",
-                                         "min12int2x2",
-                                         "min12int2x3",
-                                         "min12int2x4",
-                                         "min12int3",
-                                         "min12int3x1",
-                                         "min12int3x2",
-                                         "min12int3x3",
-                                         "min12int3x4",
-                                         "min12int4",
-                                         "min12int4x1",
-                                         "min12int4x2",
-                                         "min12int4x3",
-                                         "min12int4x4",
-                                         "min16float",
-                                         "min16float1",
-                                         "min16float1x1",
-                                         "min16float1x2",
-                                         "min16float1x3",
-                                         "min16float1x4",
-                                         "min16float2",
-                                         "min16float2x1",
-                                         "min16float2x2",
-                                         "min16float2x3",
-                                         "min16float2x4",
-                                         "min16float3",
-                                         "min16float3x1",
-                                         "min16float3x2",
-                                         "min16float3x3",
-                                         "min16float3x4",
-                                         "min16float4",
-                                         "min16float4x1",
-                                         "min16float4x2",
-                                         "min16float4x3",
-                                         "min16float4x4",
-                                         "min16int",
-                                         "min16int1",
-                                         "min16int1x1",
-                                         "min16int1x2",
-                                         "min16int1x3",
-                                         "min16int1x4",
-                                         "min16int2",
-                                         "min16int2x1",
-                                         "min16int2x2",
-                                         "min16int2x3",
-                                         "min16int2x4",
-                                         "min16int3",
-                                         "min16int3x1",
-                                         "min16int3x2",
-                                         "min16int3x3",
-                                         "min16int3x4",
-                                         "min16int4",
-                                         "min16int4x1",
-                                         "min16int4x2",
-                                         "min16int4x3",
-                                         "min16int4x4",
-                                         "min16uint",
-                                         "min16uint1",
-                                         "min16uint1x1",
-                                         "min16uint1x2",
-                                         "min16uint1x3",
-                                         "min16uint1x4",
-                                         "min16uint2",
-                                         "min16uint2x1",
-                                         "min16uint2x2",
-                                         "min16uint2x3",
-                                         "min16uint2x4",
-                                         "min16uint3",
-                                         "min16uint3x1",
-                                         "min16uint3x2",
-                                         "min16uint3x3",
-                                         "min16uint3x4",
-                                         "min16uint4",
-                                         "min16uint4x1",
-                                         "min16uint4x2",
-                                         "min16uint4x3",
-                                         "min16uint4x4",
-                                         // "modf",  // WGSL builtin
-                                         "msad4",
-                                         "mul",
-                                         "mutable",
-                                         "namespace",
-                                         "new",
-                                         "nointerpolation",
-                                         "noise",
-                                         "noperspective",
-                                         // "normalize",  // WGSL builtin
-                                         "numthreads",
-                                         "operator",
-                                         // "out",  // WGSL keyword
-                                         "packoffset",
-                                         "pass",
-                                         "pixelfragment",
-                                         "pixelshader",
-                                         "point",
-                                         // "pow",  // WGSL builtin
-                                         "precise",
-                                         "printf",
-                                         // "private",  // WGSL keyword
-                                         "protected",
-                                         "public",
-                                         "radians",
-                                         "rcp",
-                                         // "reflect",  // WGSL builtin
-                                         "refract",
-                                         "register",
-                                         "reinterpret_cast",
-                                         // "return",  // WGSL keyword
-                                         // "reversebits",  // WGSL builtin
-                                         // "round",  // WGSL builtin
-                                         "row_major",
-                                         "rsqrt",
-                                         "sample",
-                                         "sampler1D",
-                                         "sampler2D",
-                                         "sampler3D",
-                                         "samplerCUBE",
-                                         "sampler_state",
-                                         "saturate",
-                                         "shared",
-                                         "short",
-                                         // "sign",  // WGSL builtin
-                                         "signed",
-                                         // "sin",  // WGSL builtin
-                                         "sincos",
-                                         // "sinh",  // WGSL builtin
-                                         "sizeof",
-                                         // "smoothstep",  // WGSL builtin
-                                         "snorm",
-                                         // "sqrt",  // WGSL builtin
-                                         "stateblock",
-                                         "stateblock_state",
-                                         "static",
-                                         "static_cast",
-                                         // "step",  // WGSL builtin
-                                         "string",
-                                         // "struct",  // WGSL keyword
-                                         // "switch",  // WGSL keyword
-                                         // "tan",  // WGSL builtin
-                                         // "tanh",  // WGSL builtin
-                                         "tbuffer",
-                                         "technique",
-                                         "technique10",
-                                         "technique11",
-                                         "template",
-                                         "tex1D",
-                                         "tex1Dbias",
-                                         "tex1Dgrad",
-                                         "tex1Dlod",
-                                         "tex1Dproj",
-                                         "tex2D",
-                                         "tex2Dbias",
-                                         "tex2Dgrad",
-                                         "tex2Dlod",
-                                         "tex2Dproj",
-                                         "tex3D",
-                                         "tex3Dbias",
-                                         "tex3Dgrad",
-                                         "tex3Dlod",
-                                         "tex3Dproj",
-                                         "texCUBE",
-                                         "texCUBEbias",
-                                         "texCUBEgrad",
-                                         "texCUBElod",
-                                         "texCUBEproj",
-                                         "texture",
-                                         "texture1D",
-                                         "texture1DArray",
-                                         "texture2D",
-                                         "texture2DArray",
-                                         "texture2DMS",
-                                         "texture2DMSArray",
-                                         "texture3D",
-                                         "textureCube",
-                                         "textureCubeArray",
-                                         "this",
-                                         "throw",
-                                         "transpose",
-                                         "triangle",
-                                         "triangleadj",
-                                         // "true",  // WGSL keyword
-                                         // "trunc",  // WGSL builtin
-                                         "try",
-                                         // "typedef",  // WGSL keyword
-                                         "typename",
-                                         "uint",
-                                         "uint1",
-                                         "uint1x1",
-                                         "uint1x2",
-                                         "uint1x3",
-                                         "uint1x4",
-                                         "uint2",
-                                         "uint2x1",
-                                         "uint2x2",
-                                         "uint2x3",
-                                         "uint2x4",
-                                         "uint3",
-                                         "uint3x1",
-                                         "uint3x2",
-                                         "uint3x3",
-                                         "uint3x4",
-                                         "uint4",
-                                         "uint4x1",
-                                         "uint4x2",
-                                         "uint4x3",
-                                         "uint4x4",
-                                         // "uniform",  // WGSL keyword
-                                         "union",
-                                         "unorm",
-                                         "unroll",
-                                         "unsigned",
-                                         // "using",  // WGSL reserved keyword
-                                         "vector",
-                                         "vertexfragment",
-                                         "vertexshader",
-                                         "virtual",
-                                         // "void",  // WGSL keyword
-                                         "volatile",
-                                         // "while"  // WGSL reserved keyword
-                                         kUnicodeIdentifier));
-
-INSTANTIATE_TEST_SUITE_P(
-    RenamerTestMsl,
-    RenamerTestMsl,
-    testing::Values(
-        // c++14 spec
-        "alignas",
-        "alignof",
-        "and",
-        "and_eq",
-        // "asm",  // Also reserved in WGSL
-        "auto",
-        "bitand",
-        "bitor",
-        // "bool",   // Also used in WGSL
-        // "break",  // Also used in WGSL
-        // "case",   // Also used in WGSL
-        "catch",
-        "char",
-        "char16_t",
-        "char32_t",
-        "class",
-        "compl",
-        // "const",     // Also used in WGSL
-        "const_cast",
-        "constexpr",
-        // "continue",  // Also used in WGSL
-        "decltype",
-        // "default",   // Also used in WGSL
-        "delete",
-        // "do",  // Also used in WGSL
-        "double",
-        "dynamic_cast",
-        // "else",  // Also used in WGSL
-        // "enum",  // Also used in WGSL
-        "explicit",
-        "extern",
-        // "false",  // Also used in WGSL
-        "final",
-        "float",
-        // "for",  // Also used in WGSL
-        "friend",
-        "goto",
-        // "if",  // Also used in WGSL
-        "inline",
-        "int",
-        "long",
-        "mutable",
-        "namespace",
-        "new",
-        "noexcept",
-        "not",
-        "not_eq",
-        "nullptr",
-        "operator",
-        "or",
-        "or_eq",
-        // "override", // Also used in WGSL
-        // "private",  // Also used in WGSL
-        "protected",
-        "public",
-        "register",
-        "reinterpret_cast",
-        // "return",  // Also used in WGSL
-        "short",
-        "signed",
-        "sizeof",
-        "static",
-        "static_assert",
-        "static_cast",
-        // "struct",  // Also used in WGSL
-        // "switch",  // Also used in WGSL
-        "template",
-        "this",
-        "thread_local",
-        "throw",
-        // "true",  // Also used in WGSL
-        "try",
-        // "typedef",  // Also used in WGSL
-        "typeid",
-        "typename",
-        "union",
-        "unsigned",
-        // "using",  // WGSL reserved keyword
-        "virtual",
-        // "void",  // Also used in WGSL
-        "volatile",
-        "wchar_t",
-        // "while",  // WGSL reserved keyword
-        "xor",
-        "xor_eq",
-
-        // Metal Spec
-        "access",
-        // "array",  // Also used in WGSL
-        "array_ref",
-        "as_type",
-        // "atomic",  // Also used in WGSL
-        "atomic_bool",
-        "atomic_int",
-        "atomic_uint",
-        "bool2",
-        "bool3",
-        "bool4",
-        "buffer",
-        "char2",
-        "char3",
-        "char4",
-        "const_reference",
-        "constant",
-        "depth2d",
-        "depth2d_array",
-        "depth2d_ms",
-        "depth2d_ms_array",
-        "depthcube",
-        "depthcube_array",
-        "device",
-        "discard_fragment",
-        "float2",
-        "float2x2",
-        "float2x3",
-        "float2x4",
-        "float3",
-        "float3x2",
-        "float3x3",
-        "float3x4",
-        "float4",
-        "float4x2",
-        "float4x3",
-        "float4x4",
-        "fragment",
-        "half",
-        "half2",
-        "half2x2",
-        "half2x3",
-        "half2x4",
-        "half3",
-        "half3x2",
-        "half3x3",
-        "half3x4",
-        "half4",
-        "half4x2",
-        "half4x3",
-        "half4x4",
-        "imageblock",
-        "int16_t",
-        "int2",
-        "int3",
-        "int32_t",
-        "int4",
-        "int64_t",
-        "int8_t",
-        "kernel",
-        "long2",
-        "long3",
-        "long4",
-        "main",  // No functions called main
-        "matrix",
-        "metal",  // The namespace
-        "packed_bool2",
-        "packed_bool3",
-        "packed_bool4",
-        "packed_char2",
-        "packed_char3",
-        "packed_char4",
-        "packed_float2",
-        "packed_float3",
-        "packed_float4",
-        "packed_half2",
-        "packed_half3",
-        "packed_half4",
-        "packed_int2",
-        "packed_int3",
-        "packed_int4",
-        "packed_short2",
-        "packed_short3",
-        "packed_short4",
-        "packed_uchar2",
-        "packed_uchar3",
-        "packed_uchar4",
-        "packed_uint2",
-        "packed_uint3",
-        "packed_uint4",
-        "packed_ushort2",
-        "packed_ushort3",
-        "packed_ushort4",
-        "patch_control_point",
-        "ptrdiff_t",
-        "r16snorm",
-        "r16unorm",
-        "r8unorm",
-        "reference",
-        "rg11b10f",
-        "rg16snorm",
-        "rg16unorm",
-        "rg8snorm",
-        "rg8unorm",
-        "rgb10a2",
-        "rgb9e5",
-        "rgba16snorm",
-        "rgba16unorm",
-        "rgba8snorm",
-        "rgba8unorm",
-        // "sampler",  // Also used in WGSL
-        "short2",
-        "short3",
-        "short4",
-        "size_t",
-        "srgba8unorm",
-        "texture",
-        "texture1d",
-        "texture1d_array",
-        "texture2d",
-        "texture2d_array",
-        "texture2d_ms",
-        "texture2d_ms_array",
-        "texture3d",
-        "texture_buffer",
-        "texturecube",
-        "texturecube_array",
-        "thread",
-        "threadgroup",
-        "threadgroup_imageblock",
-        "uchar",
-        "uchar2",
-        "uchar3",
-        "uchar4",
-        "uint",
-        "uint16_t",
-        "uint2",
-        "uint3",
-        "uint32_t",
-        "uint4",
-        "uint64_t",
-        "uint8_t",
-        "ulong2",
-        "ulong3",
-        "ulong4",
-        // "uniform",  // Also used in WGSL
-        "ushort",
-        "ushort2",
-        "ushort3",
-        "ushort4",
-        // "vec",  // WGSL reserved keyword
-        "vertex",
-
-        // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-        // Table 6.5. Constants for single-precision floating-point math
-        // functions
-        "MAXFLOAT",
-        "HUGE_VALF",
-        "INFINITY",
-        "infinity",
-        "NAN",
-        "M_E_F",
-        "M_LOG2E_F",
-        "M_LOG10E_F",
-        "M_LN2_F",
-        "M_LN10_F",
-        "M_PI_F",
-        "M_PI_2_F",
-        "M_PI_4_F",
-        "M_1_PI_F",
-        "M_2_PI_F",
-        "M_2_SQRTPI_F",
-        "M_SQRT2_F",
-        "M_SQRT1_2_F",
-        "MAXHALF",
-        "HUGE_VALH",
-        "M_E_H",
-        "M_LOG2E_H",
-        "M_LOG10E_H",
-        "M_LN2_H",
-        "M_LN10_H",
-        "M_PI_H",
-        "M_PI_2_H",
-        "M_PI_4_H",
-        "M_1_PI_H",
-        "M_2_PI_H",
-        "M_2_SQRTPI_H",
-        "M_SQRT2_H",
-        "M_SQRT1_2_H",
-        // "while"  // WGSL reserved keyword
-        kUnicodeIdentifier));
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/robustness.cc b/src/transform/robustness.cc
deleted file mode 100644
index 577e88e..0000000
--- a/src/transform/robustness.cc
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright 2020 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/transform/robustness.h"
-
-#include <algorithm>
-#include <limits>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/statement.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness::Config);
-
-namespace tint {
-namespace transform {
-
-/// State holds the current transform state
-struct Robustness::State {
-  /// The clone context
-  CloneContext& ctx;
-
-  /// Set of storage classes to not apply the transform to
-  std::unordered_set<ast::StorageClass> omitted_classes;
-
-  /// Applies the transformation state to `ctx`.
-  void Transform() {
-    ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr) {
-      return Transform(expr);
-    });
-    ctx.ReplaceAll(
-        [&](const ast::CallExpression* expr) { return Transform(expr); });
-  }
-
-  /// Apply bounds clamping to array, vector and matrix indexing
-  /// @param expr the array, vector or matrix index expression
-  /// @return the clamped replacement expression, or nullptr if `expr` should be
-  /// cloned without changes.
-  const ast::IndexAccessorExpression* Transform(
-      const ast::IndexAccessorExpression* expr) {
-    auto* ret_type = ctx.src->Sem().Get(expr->object)->Type();
-
-    auto* ref = ret_type->As<sem::Reference>();
-    if (ref && omitted_classes.count(ref->StorageClass()) != 0) {
-      return nullptr;
-    }
-
-    auto* ret_unwrapped = ret_type->UnwrapRef();
-
-    ProgramBuilder& b = *ctx.dst;
-    using u32 = ProgramBuilder::u32;
-
-    struct Value {
-      const ast::Expression* expr = nullptr;  // If null, then is a constant
-      union {
-        uint32_t u32 = 0;  // use if is_signed == false
-        int32_t i32;       // use if is_signed == true
-      };
-      bool is_signed = false;
-    };
-
-    Value size;              // size of the array, vector or matrix
-    size.is_signed = false;  // size is always unsigned
-    if (auto* vec = ret_unwrapped->As<sem::Vector>()) {
-      size.u32 = vec->Width();
-
-    } else if (auto* arr = ret_unwrapped->As<sem::Array>()) {
-      size.u32 = arr->Count();
-    } else if (auto* mat = ret_unwrapped->As<sem::Matrix>()) {
-      // The row accessor would have been an embedded index accessor and already
-      // handled, so we just need to do columns here.
-      size.u32 = mat->columns();
-    } else {
-      return nullptr;
-    }
-
-    if (size.u32 == 0) {
-      if (!ret_unwrapped->Is<sem::Array>()) {
-        b.Diagnostics().add_error(diag::System::Transform,
-                                  "invalid 0 sized non-array", expr->source);
-        return nullptr;
-      }
-      // Runtime sized array
-      auto* arr = ctx.Clone(expr->object);
-      size.expr = b.Call("arrayLength", b.AddressOf(arr));
-    }
-
-    // Calculate the maximum possible index value (size-1u)
-    // Size must be positive (non-zero), so we can safely subtract 1 here
-    // without underflow.
-    Value limit;
-    limit.is_signed = false;  // Like size, limit is always unsigned.
-    if (size.expr) {
-      // Dynamic size
-      limit.expr = b.Sub(size.expr, 1u);
-    } else {
-      // Constant size
-      limit.u32 = size.u32 - 1u;
-    }
-
-    Value idx;  // index value
-
-    auto* idx_sem = ctx.src->Sem().Get(expr->index);
-    auto* idx_ty = idx_sem->Type()->UnwrapRef();
-    if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
-      TINT_ICE(Transform, b.Diagnostics())
-          << "index must be u32 or i32, got " << idx_sem->Type()->type_name();
-      return nullptr;
-    }
-
-    if (auto idx_constant = idx_sem->ConstantValue()) {
-      // Constant value index
-      if (idx_constant.Type()->Is<sem::I32>()) {
-        idx.i32 = idx_constant.Elements()[0].i32;
-        idx.is_signed = true;
-      } else if (idx_constant.Type()->Is<sem::U32>()) {
-        idx.u32 = idx_constant.Elements()[0].u32;
-        idx.is_signed = false;
-      } else {
-        b.Diagnostics().add_error(diag::System::Transform,
-                                  "unsupported constant value for accessor: " +
-                                      idx_constant.Type()->type_name(),
-                                  expr->source);
-        return nullptr;
-      }
-    } else {
-      // Dynamic value index
-      idx.expr = ctx.Clone(expr->index);
-      idx.is_signed = idx_ty->Is<sem::I32>();
-    }
-
-    // Clamp the index so that it cannot exceed limit.
-    if (idx.expr || limit.expr) {
-      // One of, or both of idx and limit are non-constant.
-
-      // If the index is signed, cast it to a u32 (with clamping if constant).
-      if (idx.is_signed) {
-        if (idx.expr) {
-          // We don't use a max(idx, 0) here, as that incurs a runtime
-          // performance cost, and if the unsigned value will be clamped by
-          // limit, resulting in a value between [0..limit)
-          idx.expr = b.Construct<u32>(idx.expr);
-          idx.is_signed = false;
-        } else {
-          idx.u32 = static_cast<uint32_t>(std::max(idx.i32, 0));
-          idx.is_signed = false;
-        }
-      }
-
-      // Convert idx and limit to expressions, so we can emit `min(idx, limit)`.
-      if (!idx.expr) {
-        idx.expr = b.Expr(idx.u32);
-      }
-      if (!limit.expr) {
-        limit.expr = b.Expr(limit.u32);
-      }
-
-      // Perform the clamp with `min(idx, limit)`
-      idx.expr = b.Call("min", idx.expr, limit.expr);
-    } else {
-      // Both idx and max are constant.
-      if (idx.is_signed) {
-        // The index is signed. Calculate limit as signed.
-        int32_t signed_limit = static_cast<int32_t>(
-            std::min<uint32_t>(limit.u32, std::numeric_limits<int32_t>::max()));
-        idx.i32 = std::max(idx.i32, 0);
-        idx.i32 = std::min(idx.i32, signed_limit);
-      } else {
-        // The index is unsigned.
-        idx.u32 = std::min(idx.u32, limit.u32);
-      }
-    }
-
-    // Convert idx to an expression, so we can emit the new accessor.
-    if (!idx.expr) {
-      idx.expr = idx.is_signed
-                     ? static_cast<const ast::Expression*>(b.Expr(idx.i32))
-                     : static_cast<const ast::Expression*>(b.Expr(idx.u32));
-    }
-
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx.Clone(expr->source);
-    auto* obj = ctx.Clone(expr->object);
-    return b.IndexAccessor(src, obj, idx.expr);
-  }
-
-  /// @param type builtin type
-  /// @returns true if the given builtin is a texture function that requires
-  /// argument clamping,
-  bool TextureBuiltinNeedsClamping(sem::BuiltinType type) {
-    return type == sem::BuiltinType::kTextureLoad ||
-           type == sem::BuiltinType::kTextureStore;
-  }
-
-  /// Apply bounds clamping to the coordinates, array index and level arguments
-  /// of the `textureLoad()` and `textureStore()` builtins.
-  /// @param expr the builtin call expression
-  /// @return the clamped replacement call expression, or nullptr if `expr`
-  /// should be cloned without changes.
-  const ast::CallExpression* Transform(const ast::CallExpression* expr) {
-    auto* call = ctx.src->Sem().Get(expr);
-    auto* call_target = call->Target();
-    auto* builtin = call_target->As<sem::Builtin>();
-    if (!builtin || !TextureBuiltinNeedsClamping(builtin->Type())) {
-      return nullptr;  // No transform, just clone.
-    }
-
-    ProgramBuilder& b = *ctx.dst;
-
-    // Indices of the mandatory texture and coords parameters, and the optional
-    // array and level parameters.
-    auto& signature = builtin->Signature();
-    auto texture_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
-    auto coords_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
-    auto array_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
-    auto level_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
-
-    auto* texture_arg = expr->args[texture_idx];
-    auto* coords_arg = expr->args[coords_idx];
-    auto* coords_ty = builtin->Parameters()[coords_idx]->Type();
-
-    // If the level is provided, then we need to clamp this. As the level is
-    // used by textureDimensions() and the texture[Load|Store]() calls, we need
-    // to clamp both usages.
-    // TODO(bclayton): We probably want to place this into a let so that the
-    // calculation can be reused. This is fiddly to get right.
-    std::function<const ast::Expression*()> level_arg;
-    if (level_idx >= 0) {
-      level_arg = [&] {
-        auto* arg = expr->args[level_idx];
-        auto* num_levels = b.Call("textureNumLevels", ctx.Clone(texture_arg));
-        auto* zero = b.Expr(0);
-        auto* max = ctx.dst->Sub(num_levels, 1);
-        auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
-        return clamped;
-      };
-    }
-
-    // Clamp the coordinates argument
-    {
-      auto* texture_dims =
-          level_arg
-              ? b.Call("textureDimensions", ctx.Clone(texture_arg), level_arg())
-              : b.Call("textureDimensions", ctx.Clone(texture_arg));
-      auto* zero = b.Construct(CreateASTTypeFor(ctx, coords_ty));
-      auto* max = ctx.dst->Sub(
-          texture_dims, b.Construct(CreateASTTypeFor(ctx, coords_ty), 1));
-      auto* clamped_coords = b.Call("clamp", ctx.Clone(coords_arg), zero, max);
-      ctx.Replace(coords_arg, clamped_coords);
-    }
-
-    // Clamp the array_index argument, if provided
-    if (array_idx >= 0) {
-      auto* arg = expr->args[array_idx];
-      auto* num_layers = b.Call("textureNumLayers", ctx.Clone(texture_arg));
-      auto* zero = b.Expr(0);
-      auto* max = ctx.dst->Sub(num_layers, 1);
-      auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
-      ctx.Replace(arg, clamped);
-    }
-
-    // Clamp the level argument, if provided
-    if (level_idx >= 0) {
-      auto* arg = expr->args[level_idx];
-      ctx.Replace(arg, level_arg ? level_arg() : ctx.dst->Expr(0));
-    }
-
-    return nullptr;  // Clone, which will use the argument replacements above.
-  }
-};
-
-Robustness::Config::Config() = default;
-Robustness::Config::Config(const Config&) = default;
-Robustness::Config::~Config() = default;
-Robustness::Config& Robustness::Config::operator=(const Config&) = default;
-
-Robustness::Robustness() = default;
-Robustness::~Robustness() = default;
-
-void Robustness::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) const {
-  Config cfg;
-  if (auto* cfg_data = inputs.Get<Config>()) {
-    cfg = *cfg_data;
-  }
-
-  std::unordered_set<ast::StorageClass> omitted_classes;
-  for (auto sc : cfg.omitted_classes) {
-    switch (sc) {
-      case StorageClass::kUniform:
-        omitted_classes.insert(ast::StorageClass::kUniform);
-        break;
-      case StorageClass::kStorage:
-        omitted_classes.insert(ast::StorageClass::kStorage);
-        break;
-    }
-  }
-
-  State state{ctx, std::move(omitted_classes)};
-
-  state.Transform();
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/robustness.h b/src/transform/robustness.h
deleted file mode 100644
index f4cb0ee..0000000
--- a/src/transform/robustness.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2020 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_TRANSFORM_ROBUSTNESS_H_
-#define SRC_TRANSFORM_ROBUSTNESS_H_
-
-#include <unordered_set>
-
-#include "src/transform/transform.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class IndexAccessorExpression;
-class CallExpression;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace transform {
-
-/// This transform is responsible for clamping all array accesses to be within
-/// the bounds of the array. Any access before the start of the array will clamp
-/// to zero and any access past the end of the array will clamp to
-/// (array length - 1).
-class Robustness : public Castable<Robustness, Transform> {
- public:
-  /// Storage class to be skipped in the transform
-  enum class StorageClass {
-    kUniform,
-    kStorage,
-  };
-
-  /// Configuration options for the transform
-  struct Config : public Castable<Config, Data> {
-    /// Constructor
-    Config();
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// Assignment operator
-    /// @returns this Config
-    Config& operator=(const Config&);
-
-    /// Storage classes to omit from apply the transform to.
-    /// This allows for optimizing on hardware that provide safe accesses.
-    std::unordered_set<StorageClass> omitted_classes;
-  };
-
-  /// Constructor
-  Robustness();
-  /// Destructor
-  ~Robustness() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  struct State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_ROBUSTNESS_H_
diff --git a/src/transform/robustness_test.cc b/src/transform/robustness_test.cc
deleted file mode 100644
index 3b0747b..0000000
--- a/src/transform/robustness_test.cc
+++ /dev/null
@@ -1,1745 +0,0 @@
-// Copyright 2020 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/transform/robustness.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using RobustnessTest = TransformTest;
-
-TEST_F(RobustnessTest, Array_Idx_Clamp) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-let c : u32 = 1u;
-
-fn f() {
-  let b : f32 = a[c];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-let c : u32 = 1u;
-
-fn f() {
-  let b : f32 = a[1u];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Clamp_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  let b : f32 = a[c];
-}
-
-let c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  let b : f32 = a[1u];
-}
-
-let c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Nested_Scalar) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-var<private> b : array<i32, 5>;
-
-var<private> i : u32;
-
-fn f() {
-  var c : f32 = a[ b[i] ];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-var<private> b : array<i32, 5>;
-
-var<private> i : u32;
-
-fn f() {
-  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Nested_Scalar_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var c : f32 = a[ b[i] ];
-}
-
-var<private> i : u32;
-
-var<private> b : array<i32, 5>;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
-}
-
-var<private> i : u32;
-
-var<private> b : array<i32, 5>;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Scalar) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Scalar_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[1];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[1];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Expr) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Expr_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-
-var<private> c : i32;
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Negative) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[-1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[0];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Negative_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[-1];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[0];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_OutOfBounds) {
-  auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[2];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_OutOfBounds_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[3];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// TODO(crbug.com/tint/1177) - Validation currently forbids arrays larger than
-// 0xffffffff. If WGSL supports 64-bit indexing, re-enable this test.
-TEST_F(RobustnessTest, DISABLED_LargeArrays_Idx) {
-  auto* src = R"(
-struct S {
-  a : array<f32, 0x7fffffff>;
-  b : array<f32>;
-};
-@group(0) @binding(0) var<storage, read> s : S;
-
-fn f() {
-  // Signed
-  var i32_a1 : f32 = s.a[ 0x7ffffffe];
-  var i32_a2 : f32 = s.a[ 1];
-  var i32_a3 : f32 = s.a[ 0];
-  var i32_a4 : f32 = s.a[-1];
-  var i32_a5 : f32 = s.a[-0x7fffffff];
-
-  var i32_b1 : f32 = s.b[ 0x7ffffffe];
-  var i32_b2 : f32 = s.b[ 1];
-  var i32_b3 : f32 = s.b[ 0];
-  var i32_b4 : f32 = s.b[-1];
-  var i32_b5 : f32 = s.b[-0x7fffffff];
-
-  // Unsigned
-  var u32_a1 : f32 = s.a[0u];
-  var u32_a2 : f32 = s.a[1u];
-  var u32_a3 : f32 = s.a[0x7ffffffeu];
-  var u32_a4 : f32 = s.a[0x7fffffffu];
-  var u32_a5 : f32 = s.a[0x80000000u];
-  var u32_a6 : f32 = s.a[0xffffffffu];
-
-  var u32_b1 : f32 = s.b[0u];
-  var u32_b2 : f32 = s.b[1u];
-  var u32_b3 : f32 = s.b[0x7ffffffeu];
-  var u32_b4 : f32 = s.b[0x7fffffffu];
-  var u32_b5 : f32 = s.b[0x80000000u];
-  var u32_b6 : f32 = s.b[0xffffffffu];
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : array<f32, 2147483647>;
-  b : array<f32>;
-};
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-fn f() {
-  var i32_a1 : f32 = s.a[2147483646];
-  var i32_a2 : f32 = s.a[1];
-  var i32_a3 : f32 = s.a[0];
-  var i32_a4 : f32 = s.a[0];
-  var i32_a5 : f32 = s.a[0];
-  var i32_b1 : f32 = s.b[min(2147483646u, (arrayLength(&(s.b)) - 1u))];
-  var i32_b2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var i32_b3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_b4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_b5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_a1 : f32 = s.a[0u];
-  var u32_a2 : f32 = s.a[1u];
-  var u32_a3 : f32 = s.a[2147483646u];
-  var u32_a4 : f32 = s.a[2147483646u];
-  var u32_a5 : f32 = s.a[2147483646u];
-  var u32_a6 : f32 = s.a[2147483646u];
-  var u32_b1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_b2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var u32_b3 : f32 = s.b[min(2147483646u, (arrayLength(&(s.b)) - 1u))];
-  var u32_b4 : f32 = s.b[min(2147483647u, (arrayLength(&(s.b)) - 1u))];
-  var u32_b5 : f32 = s.b[min(2147483648u, (arrayLength(&(s.b)) - 1u))];
-  var u32_b6 : f32 = s.b[min(4294967295u, (arrayLength(&(s.b)) - 1u))];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Scalar) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Scalar_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[1];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[1];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Expr) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Expr_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a.xy[2];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a.xy[1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[2];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[1];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[c];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[min(u32(c), 1u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[c];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[min(u32(c), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[c + 2 - 3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Negative) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[-1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[0];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Negative_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[-1];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[0];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_OutOfBounds) {
-  auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[2];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_OutOfBounds_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[3];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Scalar) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Scalar_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[2][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Column) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[c + 2 - 3][1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Column_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3][1];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Row) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[1][c + 2 - 3];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Row_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[1][c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Column) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[-1][1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[0][1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Column_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[-1][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[0][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Row) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][-1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][0];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Row_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[2][-1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2][0];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[5][1];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[5][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row) {
-  auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][5];
-}
-)";
-
-  auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][1];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var b : f32 = a[2][5];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto* expect = R"(
-fn f() {
-  var b : f32 = a[2][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// TODO(dsinclair): Implement when constant_id exists
-TEST_F(RobustnessTest, DISABLED_Vector_Constant_Id_Clamps) {
-  // @id(1300) override idx : i32;
-  // var a : vec3<f32>
-  // var b : f32 = a[idx]
-  //
-  // ->var b : f32 = a[min(u32(idx), 2)]
-}
-
-// TODO(dsinclair): Implement when constant_id exists
-TEST_F(RobustnessTest, DISABLED_Array_Constant_Id_Clamps) {
-  // @id(1300) override idx : i32;
-  // var a : array<f32, 4>
-  // var b : f32 = a[idx]
-  //
-  // -> var b : f32 = a[min(u32(idx), 3)]
-}
-
-// TODO(dsinclair): Implement when constant_id exists
-TEST_F(RobustnessTest, DISABLED_Matrix_Column_Constant_Id_Clamps) {
-  // @id(1300) override idx : i32;
-  // var a : mat3x2<f32>
-  // var b : f32 = a[idx][1]
-  //
-  // -> var b : f32 = a[min(u32(idx), 2)][1]
-}
-
-// TODO(dsinclair): Implement when constant_id exists
-TEST_F(RobustnessTest, DISABLED_Matrix_Row_Constant_Id_Clamps) {
-  // @id(1300) override idx : i32;
-  // var a : mat3x2<f32>
-  // var b : f32 = a[1][idx]
-  //
-  // -> var b : f32 = a[1][min(u32(idx), 0, 1)]
-}
-
-TEST_F(RobustnessTest, RuntimeArray_Clamps) {
-  auto* src = R"(
-struct S {
-  a : f32;
-  b : array<f32>;
-};
-@group(0) @binding(0) var<storage, read> s : S;
-
-fn f() {
-  var d : f32 = s.b[25];
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-fn f() {
-  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, RuntimeArray_Clamps_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var d : f32 = s.b[25];
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct S {
-  a : f32;
-  b : array<f32>;
-};
-)";
-
-  auto* expect = R"(
-fn f() {
-  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct S {
-  a : f32;
-  b : array<f32>;
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Clamp textureLoad() coord, array_index and level values
-TEST_F(RobustnessTest, TextureLoad_Clamp) {
-  auto* src = R"(
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-@group(0) @binding(0) var tex_external : texture_external;
-
-fn f() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-
-  textureLoad(tex_1d, 1, level_idx);
-  textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
-  textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
-  textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
-  textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
-  textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  textureLoad(tex_external, vec2<i32>(1, 2));
-}
-)";
-
-  auto* expect =
-      R"(
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-
-@group(0) @binding(0) var tex_external : texture_external;
-
-fn f() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-  textureLoad(tex_1d, clamp(1, i32(), (textureDimensions(tex_1d, clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1))) - i32(1))), clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1)));
-  textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d, clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1)));
-  textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1)));
-  textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3<i32>(), (textureDimensions(tex_3d, clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1))) - vec3<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1)));
-  textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_ms_2d) - vec2<i32>(1))), sample_idx);
-  textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1)));
-  textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_depth_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1)));
-  textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_external) - vec2<i32>(1))));
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Clamp textureLoad() coord, array_index and level values
-TEST_F(RobustnessTest, TextureLoad_Clamp_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-
-  textureLoad(tex_1d, 1, level_idx);
-  textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
-  textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
-  textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
-  textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
-  textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  textureLoad(tex_external, vec2<i32>(1, 2));
-}
-
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-@group(0) @binding(0) var tex_external : texture_external;
-)";
-
-  auto* expect =
-      R"(
-fn f() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-  textureLoad(tex_1d, clamp(1, i32(), (textureDimensions(tex_1d, clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1))) - i32(1))), clamp(level_idx, 0, (textureNumLevels(tex_1d) - 1)));
-  textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d, clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_2d) - 1)));
-  textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_2d_arr) - 1)));
-  textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3<i32>(), (textureDimensions(tex_3d, clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1))) - vec3<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_3d) - 1)));
-  textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_ms_2d) - vec2<i32>(1))), sample_idx);
-  textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1))) - vec2<i32>(1))), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d) - 1)));
-  textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1))) - vec2<i32>(1))), clamp(array_idx, 0, (textureNumLayers(tex_depth_2d_arr) - 1)), clamp(level_idx, 0, (textureNumLevels(tex_depth_2d_arr) - 1)));
-  textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2<i32>(), (textureDimensions(tex_external) - vec2<i32>(1))));
-}
-
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-
-@group(0) @binding(0) var tex_external : texture_external;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Clamp textureStore() coord, array_index and level values
-TEST_F(RobustnessTest, TextureStore_Clamp) {
-  auto* src = R"(
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
-fn f() {
-  textureStore(tex1d, 10, vec4<i32>());
-  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<i32>(10, 20), 50, vec4<i32>());
-  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
-}
-)";
-
-  auto* expect = R"(
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
-fn f() {
-  textureStore(tex1d, clamp(10, i32(), (textureDimensions(tex1d) - i32(1))), vec4<i32>());
-  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d) - vec2<i32>(1))), vec4<i32>());
-  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d_arr) - vec2<i32>(1))), clamp(50, 0, (textureNumLayers(tex2d_arr) - 1)), vec4<i32>());
-  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3<i32>(), (textureDimensions(tex3d) - vec3<i32>(1))), vec4<i32>());
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// Clamp textureStore() coord, array_index and level values
-TEST_F(RobustnessTest, TextureStore_Clamp_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  textureStore(tex1d, 10, vec4<i32>());
-  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<i32>(10, 20), 50, vec4<i32>());
-  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
-}
-
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
-)";
-
-  auto* expect = R"(
-fn f() {
-  textureStore(tex1d, clamp(10, i32(), (textureDimensions(tex1d) - i32(1))), vec4<i32>());
-  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d) - vec2<i32>(1))), vec4<i32>());
-  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2<i32>(), (textureDimensions(tex2d_arr) - vec2<i32>(1))), clamp(50, 0, (textureNumLayers(tex2d_arr) - 1)), vec4<i32>());
-  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3<i32>(), (textureDimensions(tex3d) - vec3<i32>(1))), vec4<i32>());
-}
-
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// TODO(dsinclair): Test for scoped variables when shadowing is implemented
-TEST_F(RobustnessTest, DISABLED_Shadowed_Variable) {
-  // var a : array<f32, 3>;
-  // var i : u32;
-  // {
-  //    var a : array<f32, 5>;
-  //    var b : f32 = a[i];
-  // }
-  // var c : f32 = a[i];
-  //
-  // -> var b : f32 = a[min(u32(i), 4)];
-  //    var c : f32 = a[min(u32(i), 2)];
-  FAIL();
-}
-
-// Check that existing use of min() and arrayLength() do not get renamed.
-TEST_F(RobustnessTest, DontRenameSymbols) {
-  auto* src = R"(
-struct S {
-  a : f32;
-  b : array<f32>;
-};
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-let c : u32 = 1u;
-
-fn f() {
-  let b : f32 = s.b[c];
-  let x : i32 = min(1, 2);
-  let y : u32 = arrayLength(&s.b);
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  a : f32;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-let c : u32 = 1u;
-
-fn f() {
-  let b : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  let x : i32 = min(1, 2);
-  let y : u32 = arrayLength(&(s.b));
-}
-)";
-
-  auto got = Run<Robustness>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-const char* kOmitSourceShader = R"(
-struct S {
-  a : array<f32, 4>;
-  b : array<f32>;
-};
-@group(0) @binding(0) var<storage, read> s : S;
-
-type UArr = @stride(16) array<f32, 4>;
-struct U {
-  a : UArr;
-};
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  // Signed
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-
-  var i32_ua1 : f32 = u.a[4];
-  var i32_ua2 : f32 = u.a[1];
-  var i32_ua3 : f32 = u.a[0];
-  var i32_ua4 : f32 = u.a[-1];
-  var i32_ua5 : f32 = u.a[-4];
-
-  // Unsigned
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-
-  var u32_ua1 : f32 = u.a[0u];
-  var u32_ua2 : f32 = u.a[1u];
-  var u32_ua3 : f32 = u.a[3u];
-  var u32_ua4 : f32 = u.a[4u];
-  var u32_ua5 : f32 = u.a[10u];
-  var u32_ua6 : f32 = u.a[100u];
-}
-)";
-
-TEST_F(RobustnessTest, OmitNone) {
-  auto* expect = R"(
-struct S {
-  a : array<f32, 4>;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-type UArr = @stride(16) array<f32, 4>;
-
-struct U {
-  a : UArr;
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  var i32_sa1 : f32 = s.a[3];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[0];
-  var i32_sa5 : f32 = s.a[0];
-  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_ua1 : f32 = u.a[3];
-  var i32_ua2 : f32 = u.a[1];
-  var i32_ua3 : f32 = u.a[0];
-  var i32_ua4 : f32 = u.a[0];
-  var i32_ua5 : f32 = u.a[0];
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[3u];
-  var u32_sa5 : f32 = s.a[3u];
-  var u32_sa6 : f32 = s.a[3u];
-  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
-  var u32_ua1 : f32 = u.a[0u];
-  var u32_ua2 : f32 = u.a[1u];
-  var u32_ua3 : f32 = u.a[3u];
-  var u32_ua4 : f32 = u.a[3u];
-  var u32_ua5 : f32 = u.a[3u];
-  var u32_ua6 : f32 = u.a[3u];
-}
-)";
-
-  Robustness::Config cfg;
-  DataMap data;
-  data.Add<Robustness::Config>(cfg);
-
-  auto got = Run<Robustness>(kOmitSourceShader, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, OmitStorage) {
-  auto* expect = R"(
-struct S {
-  a : array<f32, 4>;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-type UArr = @stride(16) array<f32, 4>;
-
-struct U {
-  a : UArr;
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-  var i32_ua1 : f32 = u.a[3];
-  var i32_ua2 : f32 = u.a[1];
-  var i32_ua3 : f32 = u.a[0];
-  var i32_ua4 : f32 = u.a[0];
-  var i32_ua5 : f32 = u.a[0];
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-  var u32_ua1 : f32 = u.a[0u];
-  var u32_ua2 : f32 = u.a[1u];
-  var u32_ua3 : f32 = u.a[3u];
-  var u32_ua4 : f32 = u.a[3u];
-  var u32_ua5 : f32 = u.a[3u];
-  var u32_ua6 : f32 = u.a[3u];
-}
-)";
-
-  Robustness::Config cfg;
-  cfg.omitted_classes.insert(Robustness::StorageClass::kStorage);
-
-  DataMap data;
-  data.Add<Robustness::Config>(cfg);
-
-  auto got = Run<Robustness>(kOmitSourceShader, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, OmitUniform) {
-  auto* expect = R"(
-struct S {
-  a : array<f32, 4>;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-type UArr = @stride(16) array<f32, 4>;
-
-struct U {
-  a : UArr;
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  var i32_sa1 : f32 = s.a[3];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[0];
-  var i32_sa5 : f32 = s.a[0];
-  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_ua1 : f32 = u.a[4];
-  var i32_ua2 : f32 = u.a[1];
-  var i32_ua3 : f32 = u.a[0];
-  var i32_ua4 : f32 = u.a[-1];
-  var i32_ua5 : f32 = u.a[-4];
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[3u];
-  var u32_sa5 : f32 = s.a[3u];
-  var u32_sa6 : f32 = s.a[3u];
-  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
-  var u32_ua1 : f32 = u.a[0u];
-  var u32_ua2 : f32 = u.a[1u];
-  var u32_ua3 : f32 = u.a[3u];
-  var u32_ua4 : f32 = u.a[4u];
-  var u32_ua5 : f32 = u.a[10u];
-  var u32_ua6 : f32 = u.a[100u];
-}
-)";
-
-  Robustness::Config cfg;
-  cfg.omitted_classes.insert(Robustness::StorageClass::kUniform);
-
-  DataMap data;
-  data.Add<Robustness::Config>(cfg);
-
-  auto got = Run<Robustness>(kOmitSourceShader, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, OmitBoth) {
-  auto* expect = R"(
-struct S {
-  a : array<f32, 4>;
-  b : array<f32>;
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-type UArr = @stride(16) array<f32, 4>;
-
-struct U {
-  a : UArr;
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-  var i32_ua1 : f32 = u.a[4];
-  var i32_ua2 : f32 = u.a[1];
-  var i32_ua3 : f32 = u.a[0];
-  var i32_ua4 : f32 = u.a[-1];
-  var i32_ua5 : f32 = u.a[-4];
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-  var u32_ua1 : f32 = u.a[0u];
-  var u32_ua2 : f32 = u.a[1u];
-  var u32_ua3 : f32 = u.a[3u];
-  var u32_ua4 : f32 = u.a[4u];
-  var u32_ua5 : f32 = u.a[10u];
-  var u32_ua6 : f32 = u.a[100u];
-}
-)";
-
-  Robustness::Config cfg;
-  cfg.omitted_classes.insert(Robustness::StorageClass::kStorage);
-  cfg.omitted_classes.insert(Robustness::StorageClass::kUniform);
-
-  DataMap data;
-  data.Add<Robustness::Config>(cfg);
-
-  auto got = Run<Robustness>(kOmitSourceShader, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/simplify_pointers.cc b/src/transform/simplify_pointers.cc
deleted file mode 100644
index cfc06b7..0000000
--- a/src/transform/simplify_pointers.cc
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/simplify_pointers.h"
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-#include "src/transform/unshadow.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::SimplifyPointers);
-
-namespace tint {
-namespace transform {
-
-namespace {
-
-/// PointerOp describes either possible indirection or address-of action on an
-/// expression.
-struct PointerOp {
-  /// Positive: Number of times the `expr` was dereferenced (*expr)
-  /// Negative: Number of times the `expr` was 'addressed-of' (&expr)
-  /// Zero: no pointer op on `expr`
-  int indirections = 0;
-  /// The expression being operated on
-  const ast::Expression* expr = nullptr;
-};
-
-}  // namespace
-
-/// The PIMPL state for the SimplifyPointers transform
-struct SimplifyPointers::State {
-  /// The clone context
-  CloneContext& ctx;
-
-  /// Constructor
-  /// @param context the clone context
-  explicit State(CloneContext& context) : ctx(context) {}
-
-  /// Traverses the expression `expr` looking for non-literal array indexing
-  /// expressions that would affect the computed address of a pointer
-  /// expression. The function-like argument `cb` is called for each found.
-  /// @param expr the expression to traverse
-  /// @param cb a function-like object with the signature
-  /// `void(const ast::Expression*)`, which is called for each array index
-  /// expression
-  template <typename F>
-  static void CollectSavedArrayIndices(const ast::Expression* expr, F&& cb) {
-    if (auto* a = expr->As<ast::IndexAccessorExpression>()) {
-      CollectSavedArrayIndices(a->object, cb);
-      if (!a->index->Is<ast::LiteralExpression>()) {
-        cb(a->index);
-      }
-      return;
-    }
-
-    if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
-      CollectSavedArrayIndices(m->structure, cb);
-      return;
-    }
-
-    if (auto* u = expr->As<ast::UnaryOpExpression>()) {
-      CollectSavedArrayIndices(u->expr, cb);
-      return;
-    }
-
-    // Note: Other ast::Expression types can be safely ignored as they cannot be
-    // used to generate a reference or pointer.
-    // See https://gpuweb.github.io/gpuweb/wgsl/#forming-references-and-pointers
-  }
-
-  /// Reduce walks the expression chain, collapsing all address-of and
-  /// indirection ops into a PointerOp.
-  /// @param in the expression to walk
-  /// @returns the reduced PointerOp
-  PointerOp Reduce(const ast::Expression* in) const {
-    PointerOp op{0, in};
-    while (true) {
-      if (auto* unary = op.expr->As<ast::UnaryOpExpression>()) {
-        switch (unary->op) {
-          case ast::UnaryOp::kIndirection:
-            op.indirections++;
-            op.expr = unary->expr;
-            continue;
-          case ast::UnaryOp::kAddressOf:
-            op.indirections--;
-            op.expr = unary->expr;
-            continue;
-          default:
-            break;
-        }
-      }
-      if (auto* user = ctx.src->Sem().Get<sem::VariableUser>(op.expr)) {
-        auto* var = user->Variable();
-        if (var->Is<sem::LocalVariable>() &&  //
-            var->Declaration()->is_const &&   //
-            var->Type()->Is<sem::Pointer>()) {
-          op.expr = var->Declaration()->constructor;
-          continue;
-        }
-      }
-      return op;
-    }
-  }
-
-  /// Performs the transformation
-  void Run() {
-    // A map of saved expressions to their saved variable name
-    std::unordered_map<const ast::Expression*, Symbol> saved_vars;
-
-    // Register the ast::Expression transform handler.
-    // This performs two different transformations:
-    // * Identifiers that resolve to the pointer-typed `let` declarations are
-    // replaced with the recursively inlined initializer expression for the
-    // `let` declaration.
-    // * Sub-expressions inside the pointer-typed `let` initializer expression
-    // that have been hoisted to a saved variable are replaced with the saved
-    // variable identifier.
-    ctx.ReplaceAll([&](const ast::Expression* expr) -> const ast::Expression* {
-      // Look to see if we need to swap this Expression with a saved variable.
-      auto it = saved_vars.find(expr);
-      if (it != saved_vars.end()) {
-        return ctx.dst->Expr(it->second);
-      }
-
-      // Reduce the expression, folding away chains of address-of / indirections
-      auto op = Reduce(expr);
-
-      // Clone the reduced root expression
-      expr = ctx.CloneWithoutTransform(op.expr);
-
-      // And reapply the minimum number of address-of / indirections
-      for (int i = 0; i < op.indirections; i++) {
-        expr = ctx.dst->Deref(expr);
-      }
-      for (int i = 0; i > op.indirections; i--) {
-        expr = ctx.dst->AddressOf(expr);
-      }
-      return expr;
-    });
-
-    // Find all the pointer-typed `let` declarations.
-    // Note that these must be function-scoped, as module-scoped `let`s are not
-    // permitted.
-    for (auto* node : ctx.src->ASTNodes().Objects()) {
-      if (auto* let = node->As<ast::VariableDeclStatement>()) {
-        if (!let->variable->is_const) {
-          continue;  // Not a `let` declaration. Ignore.
-        }
-
-        auto* var = ctx.src->Sem().Get(let->variable);
-        if (!var->Type()->Is<sem::Pointer>()) {
-          continue;  // Not a pointer type. Ignore.
-        }
-
-        // We're dealing with a pointer-typed `let` declaration.
-
-        // Scan the initializer expression for array index expressions that need
-        // to be hoist to temporary "saved" variables.
-        std::vector<const ast::VariableDeclStatement*> saved;
-        CollectSavedArrayIndices(
-            var->Declaration()->constructor,
-            [&](const ast::Expression* idx_expr) {
-              // We have a sub-expression that needs to be saved.
-              // Create a new variable
-              auto saved_name = ctx.dst->Symbols().New(
-                  ctx.src->Symbols().NameFor(var->Declaration()->symbol) +
-                  "_save");
-              auto* decl = ctx.dst->Decl(
-                  ctx.dst->Const(saved_name, nullptr, ctx.Clone(idx_expr)));
-              saved.emplace_back(decl);
-              // Record the substitution of `idx_expr` to the saved variable
-              // with the symbol `saved_name`. This will be used by the
-              // ReplaceAll() handler above.
-              saved_vars.emplace(idx_expr, saved_name);
-            });
-
-        // Find the place to insert the saved declarations.
-        // Special care needs to be made for lets declared as the initializer
-        // part of for-loops. In this case the block will hold the for-loop
-        // statement, not the let.
-        if (!saved.empty()) {
-          auto* stmt = ctx.src->Sem().Get(let);
-          auto* block = stmt->Block();
-          // Find the statement owned by the block (either the let decl or a
-          // for-loop)
-          while (block != stmt->Parent()) {
-            stmt = stmt->Parent();
-          }
-          // Declare the stored variables just before stmt. Order here is
-          // important as order-of-operations needs to be preserved.
-          // CollectSavedArrayIndices() visits the LHS of an index accessor
-          // before the index expression.
-          for (auto* decl : saved) {
-            // Note that repeated calls to InsertBefore() with the same `before`
-            // argument will result in nodes to inserted in the order the
-            // calls are made (last call is inserted last).
-            ctx.InsertBefore(block->Declaration()->statements,
-                             stmt->Declaration(), decl);
-          }
-        }
-
-        // As the original `let` declaration will be fully inlined, there's no
-        // need for the original declaration to exist. Remove it.
-        RemoveStatement(ctx, let);
-      }
-    }
-    ctx.Clone();
-  }
-};
-
-SimplifyPointers::SimplifyPointers() = default;
-
-SimplifyPointers::~SimplifyPointers() = default;
-
-void SimplifyPointers::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  State(ctx).Run();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/simplify_pointers.h b/src/transform/simplify_pointers.h
deleted file mode 100644
index 2541a36..0000000
--- a/src/transform/simplify_pointers.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_SIMPLIFY_POINTERS_H_
-#define SRC_TRANSFORM_SIMPLIFY_POINTERS_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// SimplifyPointers is a Transform that moves all usage of function-scope
-/// `let` statements of a pointer type into their places of usage, while also
-/// simplifying any chains of address-of or indirections operators.
-///
-/// Parameters of a pointer type are not adjusted.
-///
-/// Note: SimplifyPointers does not operate on module-scope `let`s, as these
-/// cannot be pointers: https://gpuweb.github.io/gpuweb/wgsl/#module-constants
-/// `A module-scope let-declared constant must be of constructible type.`
-///
-/// @note Depends on the following transforms to have been run first:
-/// * Unshadow
-class SimplifyPointers : public Castable<SimplifyPointers, Transform> {
- public:
-  /// Constructor
-  SimplifyPointers();
-
-  /// Destructor
-  ~SimplifyPointers() override;
-
- protected:
-  struct State;
-
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_SIMPLIFY_POINTERS_H_
diff --git a/src/transform/simplify_pointers_test.cc b/src/transform/simplify_pointers_test.cc
deleted file mode 100644
index 584a606..0000000
--- a/src/transform/simplify_pointers_test.cc
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/simplify_pointers.h"
-
-#include "src/transform/test_helper.h"
-#include "src/transform/unshadow.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using SimplifyPointersTest = TransformTest;
-
-TEST_F(SimplifyPointersTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, FoldPointer) {
-  auto* src = R"(
-fn f() {
-  var v : i32;
-  let p : ptr<function, i32> = &v;
-  let x : i32 = *p;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var v : i32;
-  let x : i32 = v;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, AddressOfDeref) {
-  auto* src = R"(
-fn f() {
-  var v : i32;
-  let p : ptr<function, i32> = &(v);
-  let x : ptr<function, i32> = &(*(p));
-  let y : ptr<function, i32> = &(*(&(*(p))));
-  let z : ptr<function, i32> = &(*(&(*(&(*(&(*(p))))))));
-  var a = *x;
-  var b = *y;
-  var c = *z;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var v : i32;
-  var a = v;
-  var b = v;
-  var c = v;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, DerefAddressOf) {
-  auto* src = R"(
-fn f() {
-  var v : i32;
-  let x : i32 = *(&(v));
-  let y : i32 = *(&(*(&(v))));
-  let z : i32 = *(&(*(&(*(&(*(&(v))))))));
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var v : i32;
-  let x : i32 = v;
-  let y : i32 = v;
-  let z : i32 = v;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, ComplexChain) {
-  auto* src = R"(
-fn f() {
-  var a : array<mat4x4<f32>, 4>;
-  let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
-  let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
-  let vp : ptr<function, vec4<f32>> = &(*mp)[2];
-  let v : vec4<f32> = *vp;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var a : array<mat4x4<f32>, 4>;
-  let v : vec4<f32> = a[3][2];
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, SavedVars) {
-  auto* src = R"(
-struct S {
-  i : i32;
-};
-
-fn arr() {
-  var a : array<S, 2>;
-  var i : i32 = 0;
-  var j : i32 = 0;
-  let p : ptr<function, i32> = &a[i + j].i;
-  i = 2;
-  *p = 4;
-}
-
-fn matrix() {
-  var m : mat3x3<f32>;
-  var i : i32 = 0;
-  var j : i32 = 0;
-  let p : ptr<function, vec3<f32>> = &m[i + j];
-  i = 2;
-  *p = vec3<f32>(4.0, 5.0, 6.0);
-}
-)";
-
-  auto* expect = R"(
-struct S {
-  i : i32;
-}
-
-fn arr() {
-  var a : array<S, 2>;
-  var i : i32 = 0;
-  var j : i32 = 0;
-  let p_save = (i + j);
-  i = 2;
-  a[p_save].i = 4;
-}
-
-fn matrix() {
-  var m : mat3x3<f32>;
-  var i : i32 = 0;
-  var j : i32 = 0;
-  let p_save_1 = (i + j);
-  i = 2;
-  m[p_save_1] = vec3<f32>(4.0, 5.0, 6.0);
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, DontSaveLiterals) {
-  auto* src = R"(
-fn f() {
-  var arr : array<i32, 2>;
-  let p1 : ptr<function, i32> = &arr[1];
-  *p1 = 4;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var arr : array<i32, 2>;
-  arr[1] = 4;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, SavedVarsChain) {
-  auto* src = R"(
-fn f() {
-  var arr : array<array<i32, 2>, 2>;
-  let i : i32 = 0;
-  let j : i32 = 1;
-  let p : ptr<function, array<i32, 2>> = &arr[i];
-  let q : ptr<function, i32> = &(*p)[j];
-  *q = 12;
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var arr : array<array<i32, 2>, 2>;
-  let i : i32 = 0;
-  let j : i32 = 1;
-  let p_save = i;
-  let q_save = j;
-  arr[p_save][q_save] = 12;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, ForLoopInit) {
-  auto* src = R"(
-fn foo() -> i32 {
-  return 1;
-}
-
-@stage(fragment)
-fn main() {
-  var arr = array<f32, 4>();
-  for (let a = &arr[foo()]; ;) {
-    let x = *a;
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn foo() -> i32 {
-  return 1;
-}
-
-@stage(fragment)
-fn main() {
-  var arr = array<f32, 4>();
-  let a_save = foo();
-  for(; ; ) {
-    let x = arr[a_save];
-    break;
-  }
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, MultiSavedVarsInSinglePtrLetExpr) {
-  auto* src = R"(
-fn x() -> i32 {
-  return 1;
-}
-
-fn y() -> i32 {
-  return 1;
-}
-
-fn z() -> i32 {
-  return 1;
-}
-
-struct Inner {
-  a : array<i32, 2>;
-};
-
-struct Outer {
-  a : array<Inner, 2>;
-};
-
-fn f() {
-  var arr : array<Outer, 2>;
-  let p : ptr<function, i32> = &arr[x()].a[y()].a[z()];
-  *p = 1;
-  *p = 2;
-}
-)";
-
-  auto* expect = R"(
-fn x() -> i32 {
-  return 1;
-}
-
-fn y() -> i32 {
-  return 1;
-}
-
-fn z() -> i32 {
-  return 1;
-}
-
-struct Inner {
-  a : array<i32, 2>;
-}
-
-struct Outer {
-  a : array<Inner, 2>;
-}
-
-fn f() {
-  var arr : array<Outer, 2>;
-  let p_save = x();
-  let p_save_1 = y();
-  let p_save_2 = z();
-  arr[p_save].a[p_save_1].a[p_save_2] = 1;
-  arr[p_save].a[p_save_1].a[p_save_2] = 2;
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SimplifyPointersTest, ShadowPointer) {
-  auto* src = R"(
-var<private> a : array<i32, 2>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  let x = &a;
-  var a : i32 = (*x)[0];
-  {
-    var a : i32 = (*x)[1];
-  }
-}
-)";
-
-  auto* expect = R"(
-var<private> a : array<i32, 2>;
-
-@stage(compute) @workgroup_size(1)
-fn main() {
-  var a_1 : i32 = a[0];
-  {
-    var a_2 : i32 = a[1];
-  }
-}
-)";
-
-  auto got = Run<Unshadow, SimplifyPointers>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/single_entry_point.cc b/src/transform/single_entry_point.cc
deleted file mode 100644
index 6e25c85..0000000
--- a/src/transform/single_entry_point.cc
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/single_entry_point.h"
-
-#include <unordered_set>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/function.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint::Config);
-
-namespace tint {
-namespace transform {
-
-SingleEntryPoint::SingleEntryPoint() = default;
-
-SingleEntryPoint::~SingleEntryPoint() = default;
-
-void SingleEntryPoint::Run(CloneContext& ctx,
-                           const DataMap& inputs,
-                           DataMap&) const {
-  auto* cfg = inputs.Get<Config>();
-  if (cfg == nullptr) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "missing transform data for " + std::string(TypeInfo().name));
-
-    return;
-  }
-
-  // Find the target entry point.
-  const ast::Function* entry_point = nullptr;
-  for (auto* f : ctx.src->AST().Functions()) {
-    if (!f->IsEntryPoint()) {
-      continue;
-    }
-    if (ctx.src->Symbols().NameFor(f->symbol) == cfg->entry_point_name) {
-      entry_point = f;
-      break;
-    }
-  }
-  if (entry_point == nullptr) {
-    ctx.dst->Diagnostics().add_error(
-        diag::System::Transform,
-        "entry point '" + cfg->entry_point_name + "' not found");
-    return;
-  }
-
-  auto& sem = ctx.src->Sem();
-
-  // Build set of referenced module-scope variables for faster lookups later.
-  std::unordered_set<const ast::Variable*> referenced_vars;
-  for (auto* var : sem.Get(entry_point)->TransitivelyReferencedGlobals()) {
-    referenced_vars.emplace(var->Declaration());
-  }
-
-  // Clone any module-scope variables, types, and functions that are statically
-  // referenced by the target entry point.
-  for (auto* decl : ctx.src->AST().GlobalDeclarations()) {
-    if (auto* ty = decl->As<ast::TypeDecl>()) {
-      // TODO(jrprice): Strip unused types.
-      ctx.dst->AST().AddTypeDecl(ctx.Clone(ty));
-    } else if (auto* var = decl->As<ast::Variable>()) {
-      if (referenced_vars.count(var)) {
-        if (var->is_overridable) {
-          // It is an overridable constant
-          if (!ast::HasAttribute<ast::IdAttribute>(var->attributes)) {
-            // If the constant doesn't already have an @id() attribute, add one
-            // so that its allocated ID so that it won't be affected by other
-            // stripped away constants
-            auto* global = sem.Get(var)->As<sem::GlobalVariable>();
-            const auto* id = ctx.dst->Id(global->ConstantId());
-            ctx.InsertFront(var->attributes, id);
-          }
-        }
-        ctx.dst->AST().AddGlobalVariable(ctx.Clone(var));
-      }
-    } else if (auto* func = decl->As<ast::Function>()) {
-      if (sem.Get(func)->HasAncestorEntryPoint(entry_point->symbol)) {
-        ctx.dst->AST().AddFunction(ctx.Clone(func));
-      }
-    } else {
-      TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-          << "unhandled global declaration: " << decl->TypeInfo().name;
-      return;
-    }
-  }
-
-  // Clone the entry point.
-  ctx.dst->AST().AddFunction(ctx.Clone(entry_point));
-}
-
-SingleEntryPoint::Config::Config(std::string entry_point)
-    : entry_point_name(entry_point) {}
-
-SingleEntryPoint::Config::Config(const Config&) = default;
-SingleEntryPoint::Config::~Config() = default;
-SingleEntryPoint::Config& SingleEntryPoint::Config::operator=(const Config&) =
-    default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/single_entry_point.h b/src/transform/single_entry_point.h
deleted file mode 100644
index 01a70d6..0000000
--- a/src/transform/single_entry_point.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_SINGLE_ENTRY_POINT_H_
-#define SRC_TRANSFORM_SINGLE_ENTRY_POINT_H_
-
-#include <string>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Strip all but one entry point a module.
-///
-/// All module-scope variables, types, and functions that are not used by the
-/// target entry point will also be removed.
-class SingleEntryPoint : public Castable<SingleEntryPoint, Transform> {
- public:
-  /// Configuration options for the transform
-  struct Config : public Castable<Config, Data> {
-    /// Constructor
-    /// @param entry_point the name of the entry point to keep
-    explicit Config(std::string entry_point = "");
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// Assignment operator
-    /// @returns this Config
-    Config& operator=(const Config&);
-
-    /// The name of the entry point to keep.
-    std::string entry_point_name;
-  };
-
-  /// Constructor
-  SingleEntryPoint();
-
-  /// Destructor
-  ~SingleEntryPoint() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_SINGLE_ENTRY_POINT_H_
diff --git a/src/transform/single_entry_point_test.cc b/src/transform/single_entry_point_test.cc
deleted file mode 100644
index 9831ae1..0000000
--- a/src/transform/single_entry_point_test.cc
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/single_entry_point.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using SingleEntryPointTest = TransformTest;
-
-TEST_F(SingleEntryPointTest, Error_MissingTransformData) {
-  auto* src = "";
-
-  auto* expect =
-      "error: missing transform data for tint::transform::SingleEntryPoint";
-
-  auto got = Run<SingleEntryPoint>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, Error_NoEntryPoints) {
-  auto* src = "";
-
-  auto* expect = "error: entry point 'main' not found";
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>("main");
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, Error_InvalidEntryPoint) {
-  auto* src = R"(
-@stage(vertex)
-fn main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = "error: entry point '_' not found";
-
-  SingleEntryPoint::Config cfg("_");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, Error_NotAnEntryPoint) {
-  auto* src = R"(
-fn foo() {}
-
-@stage(fragment)
-fn main() {}
-)";
-
-  auto* expect = "error: entry point 'foo' not found";
-
-  SingleEntryPoint::Config cfg("foo");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, SingleEntryPoint) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn main() {
-}
-)";
-
-  SingleEntryPoint::Config cfg("main");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(src, str(got));
-}
-
-TEST_F(SingleEntryPointTest, MultipleEntryPoints) {
-  auto* src = R"(
-@stage(vertex)
-fn vert_main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-
-@stage(fragment)
-fn frag_main() {
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-}
-)";
-
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-}
-)";
-
-  SingleEntryPoint::Config cfg("comp_main1");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, GlobalVariables) {
-  auto* src = R"(
-var<private> a : f32;
-
-var<private> b : f32;
-
-var<private> c : f32;
-
-var<private> d : f32;
-
-@stage(vertex)
-fn vert_main() -> @builtin(position) vec4<f32> {
-  a = 0.0;
-  return vec4<f32>();
-}
-
-@stage(fragment)
-fn frag_main() {
-  b = 0.0;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  c = 0.0;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-  d = 0.0;
-}
-)";
-
-  auto* expect = R"(
-var<private> c : f32;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  c = 0.0;
-}
-)";
-
-  SingleEntryPoint::Config cfg("comp_main1");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, GlobalConstants) {
-  auto* src = R"(
-let a : f32 = 1.0;
-
-let b : f32 = 1.0;
-
-let c : f32 = 1.0;
-
-let d : f32 = 1.0;
-
-@stage(vertex)
-fn vert_main() -> @builtin(position) vec4<f32> {
-  let local_a : f32 = a;
-  return vec4<f32>();
-}
-
-@stage(fragment)
-fn frag_main() {
-  let local_b : f32 = b;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  let local_c : f32 = c;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-  let local_d : f32 = d;
-}
-)";
-
-  auto* expect = R"(
-let c : f32 = 1.0;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  let local_c : f32 = c;
-}
-)";
-
-  SingleEntryPoint::Config cfg("comp_main1");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, WorkgroupSizeLetPreserved) {
-  auto* src = R"(
-let size : i32 = 1;
-
-@stage(compute) @workgroup_size(size)
-fn main() {
-}
-)";
-
-  auto* expect = src;
-
-  SingleEntryPoint::Config cfg("main");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, OverridableConstants) {
-  auto* src = R"(
-@id(1001) override c1 : u32 = 1u;
-          override c2 : u32 = 1u;
-@id(0)    override c3 : u32 = 1u;
-@id(9999) override c4 : u32 = 1u;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-    let local_d = c1;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-    let local_d = c2;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main3() {
-    let local_d = c3;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main4() {
-    let local_d = c4;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main5() {
-    let local_d = 1u;
-}
-)";
-
-  {
-    SingleEntryPoint::Config cfg("comp_main1");
-    auto* expect = R"(
-@id(1001) override c1 : u32 = 1u;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  let local_d = c1;
-}
-)";
-    DataMap data;
-    data.Add<SingleEntryPoint::Config>(cfg);
-    auto got = Run<SingleEntryPoint>(src, data);
-    EXPECT_EQ(expect, str(got));
-  }
-
-  {
-    SingleEntryPoint::Config cfg("comp_main2");
-    // The decorator is replaced with the one with explicit id
-    // And should not be affected by other constants stripped away
-    auto* expect = R"(
-@id(1) override c2 : u32 = 1u;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-  let local_d = c2;
-}
-)";
-    DataMap data;
-    data.Add<SingleEntryPoint::Config>(cfg);
-    auto got = Run<SingleEntryPoint>(src, data);
-    EXPECT_EQ(expect, str(got));
-  }
-
-  {
-    SingleEntryPoint::Config cfg("comp_main3");
-    auto* expect = R"(
-@id(0) override c3 : u32 = 1u;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main3() {
-  let local_d = c3;
-}
-)";
-    DataMap data;
-    data.Add<SingleEntryPoint::Config>(cfg);
-    auto got = Run<SingleEntryPoint>(src, data);
-    EXPECT_EQ(expect, str(got));
-  }
-
-  {
-    SingleEntryPoint::Config cfg("comp_main4");
-    auto* expect = R"(
-@id(9999) override c4 : u32 = 1u;
-
-@stage(compute) @workgroup_size(1)
-fn comp_main4() {
-  let local_d = c4;
-}
-)";
-    DataMap data;
-    data.Add<SingleEntryPoint::Config>(cfg);
-    auto got = Run<SingleEntryPoint>(src, data);
-    EXPECT_EQ(expect, str(got));
-  }
-
-  {
-    SingleEntryPoint::Config cfg("comp_main5");
-    auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn comp_main5() {
-  let local_d = 1u;
-}
-)";
-    DataMap data;
-    data.Add<SingleEntryPoint::Config>(cfg);
-    auto got = Run<SingleEntryPoint>(src, data);
-    EXPECT_EQ(expect, str(got));
-  }
-}
-
-TEST_F(SingleEntryPointTest, CalledFunctions) {
-  auto* src = R"(
-fn inner1() {
-}
-
-fn inner2() {
-}
-
-fn inner_shared() {
-}
-
-fn outer1() {
-  inner1();
-  inner_shared();
-}
-
-fn outer2() {
-  inner2();
-  inner_shared();
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  outer1();
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-  outer2();
-}
-)";
-
-  auto* expect = R"(
-fn inner1() {
-}
-
-fn inner_shared() {
-}
-
-fn outer1() {
-  inner1();
-  inner_shared();
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  outer1();
-}
-)";
-
-  SingleEntryPoint::Config cfg("comp_main1");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(SingleEntryPointTest, GlobalsReferencedByCalledFunctions) {
-  auto* src = R"(
-var<private> inner1_var : f32;
-
-var<private> inner2_var : f32;
-
-var<private> inner_shared_var : f32;
-
-var<private> outer1_var : f32;
-
-var<private> outer2_var : f32;
-
-fn inner1() {
-  inner1_var = 0.0;
-}
-
-fn inner2() {
-  inner2_var = 0.0;
-}
-
-fn inner_shared() {
-  inner_shared_var = 0.0;
-}
-
-fn outer1() {
-  inner1();
-  inner_shared();
-  outer1_var = 0.0;
-}
-
-fn outer2() {
-  inner2();
-  inner_shared();
-  outer2_var = 0.0;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  outer1();
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main2() {
-  outer2();
-}
-)";
-
-  auto* expect = R"(
-var<private> inner1_var : f32;
-
-var<private> inner_shared_var : f32;
-
-var<private> outer1_var : f32;
-
-fn inner1() {
-  inner1_var = 0.0;
-}
-
-fn inner_shared() {
-  inner_shared_var = 0.0;
-}
-
-fn outer1() {
-  inner1();
-  inner_shared();
-  outer1_var = 0.0;
-}
-
-@stage(compute) @workgroup_size(1)
-fn comp_main1() {
-  outer1();
-}
-)";
-
-  SingleEntryPoint::Config cfg("comp_main1");
-
-  DataMap data;
-  data.Add<SingleEntryPoint::Config>(cfg);
-  auto got = Run<SingleEntryPoint>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/test_helper.h b/src/transform/test_helper.h
deleted file mode 100644
index ae62fce..0000000
--- a/src/transform/test_helper.h
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_TEST_HELPER_H_
-#define SRC_TRANSFORM_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "src/reader/wgsl/parser.h"
-#include "src/transform/manager.h"
-#include "src/writer/wgsl/generator.h"
-
-namespace tint {
-namespace transform {
-
-/// @param program the program to get an output WGSL string from
-/// @returns the output program as a WGSL string, or an error string if the
-/// program is not valid.
-inline std::string str(const Program& program) {
-  diag::Formatter::Style style;
-  style.print_newline_at_end = false;
-
-  if (!program.IsValid()) {
-    return diag::Formatter(style).format(program.Diagnostics());
-  }
-
-  writer::wgsl::Options options;
-  auto result = writer::wgsl::Generate(&program, options);
-  if (!result.success) {
-    return "WGSL writer failed:\n" + result.error;
-  }
-
-  auto res = result.wgsl;
-  if (res.empty()) {
-    return res;
-  }
-  // The WGSL sometimes has two trailing newlines. Strip them
-  while (res.back() == '\n') {
-    res.pop_back();
-  }
-  if (res.empty()) {
-    return res;
-  }
-  return "\n" + res + "\n";
-}
-
-/// Helper class for testing transforms
-template <typename BASE>
-class TransformTestBase : public BASE {
- public:
-  /// Transforms and returns the WGSL source `in`, transformed using
-  /// `transform`.
-  /// @param transform the transform to apply
-  /// @param in the input WGSL source
-  /// @param data the optional DataMap to pass to Transform::Run()
-  /// @return the transformed output
-  Output Run(std::string in,
-             std::unique_ptr<transform::Transform> transform,
-             const DataMap& data = {}) {
-    std::vector<std::unique_ptr<transform::Transform>> transforms;
-    transforms.emplace_back(std::move(transform));
-    return Run(std::move(in), std::move(transforms), data);
-  }
-
-  /// Transforms and returns the WGSL source `in`, transformed using
-  /// a transform of type `TRANSFORM`.
-  /// @param in the input WGSL source
-  /// @param data the optional DataMap to pass to Transform::Run()
-  /// @return the transformed output
-  template <typename... TRANSFORMS>
-  Output Run(std::string in, const DataMap& data = {}) {
-    auto file = std::make_unique<Source::File>("test", in);
-    auto program = reader::wgsl::Parse(file.get());
-
-    // Keep this pointer alive after Transform() returns
-    files_.emplace_back(std::move(file));
-
-    return Run<TRANSFORMS...>(std::move(program), data);
-  }
-
-  /// Transforms and returns program `program`, transformed using a transform of
-  /// type `TRANSFORM`.
-  /// @param program the input Program
-  /// @param data the optional DataMap to pass to Transform::Run()
-  /// @return the transformed output
-  template <typename... TRANSFORMS>
-  Output Run(Program&& program, const DataMap& data = {}) {
-    if (!program.IsValid()) {
-      return Output(std::move(program));
-    }
-
-    Manager manager;
-    for (auto* transform_ptr :
-         std::initializer_list<Transform*>{new TRANSFORMS()...}) {
-      manager.append(std::unique_ptr<Transform>(transform_ptr));
-    }
-    return manager.Run(&program, data);
-  }
-
-  /// @param program the input program
-  /// @param data the optional DataMap to pass to Transform::Run()
-  /// @return true if the transform should be run for the given input.
-  template <typename TRANSFORM>
-  bool ShouldRun(Program&& program, const DataMap& data = {}) {
-    EXPECT_TRUE(program.IsValid()) << program.Diagnostics().str();
-    return TRANSFORM().ShouldRun(&program, data);
-  }
-
-  /// @param in the input WGSL source
-  /// @param data the optional DataMap to pass to Transform::Run()
-  /// @return true if the transform should be run for the given input.
-  template <typename TRANSFORM>
-  bool ShouldRun(std::string in, const DataMap& data = {}) {
-    auto file = std::make_unique<Source::File>("test", in);
-    auto program = reader::wgsl::Parse(file.get());
-    return ShouldRun<TRANSFORM>(std::move(program), data);
-  }
-
-  /// @param output the output of the transform
-  /// @returns the output program as a WGSL string, or an error string if the
-  /// program is not valid.
-  std::string str(const Output& output) {
-    return transform::str(output.program);
-  }
-
- private:
-  std::vector<std::unique_ptr<Source::File>> files_;
-};
-
-using TransformTest = TransformTestBase<testing::Test>;
-
-template <typename T>
-using TransformTestWithParam = TransformTestBase<testing::TestWithParam<T>>;
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_TEST_HELPER_H_
diff --git a/src/transform/transform.cc b/src/transform/transform.cc
deleted file mode 100644
index fb360fd..0000000
--- a/src/transform/transform.cc
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2020 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/transform/transform.h"
-
-#include <algorithm>
-#include <string>
-
-#include "src/program_builder.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampler_type.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Transform);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Data);
-
-namespace tint {
-namespace transform {
-
-Data::Data() = default;
-Data::Data(const Data&) = default;
-Data::~Data() = default;
-Data& Data::operator=(const Data&) = default;
-
-DataMap::DataMap() = default;
-DataMap::DataMap(DataMap&&) = default;
-DataMap::~DataMap() = default;
-DataMap& DataMap::operator=(DataMap&&) = default;
-
-Output::Output() = default;
-Output::Output(Program&& p) : program(std::move(p)) {}
-Transform::Transform() = default;
-Transform::~Transform() = default;
-
-Output Transform::Run(const Program* program,
-                      const DataMap& data /* = {} */) const {
-  ProgramBuilder builder;
-  CloneContext ctx(&builder, program);
-  Output output;
-  Run(ctx, data, output.data);
-  output.program = Program(std::move(builder));
-  return output;
-}
-
-void Transform::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  TINT_UNIMPLEMENTED(Transform, ctx.dst->Diagnostics())
-      << "Transform::Run() unimplemented for " << TypeInfo().name;
-}
-
-bool Transform::ShouldRun(const Program*, const DataMap&) const {
-  return true;
-}
-
-void Transform::RemoveStatement(CloneContext& ctx, const ast::Statement* stmt) {
-  auto* sem = ctx.src->Sem().Get(stmt);
-  if (auto* block = tint::As<sem::BlockStatement>(sem->Parent())) {
-    ctx.Remove(block->Declaration()->statements, stmt);
-    return;
-  }
-  if (tint::Is<sem::ForLoopStatement>(sem->Parent())) {
-    ctx.Replace(stmt, static_cast<ast::Expression*>(nullptr));
-    return;
-  }
-  TINT_ICE(Transform, ctx.dst->Diagnostics())
-      << "unable to remove statement from parent of type "
-      << sem->TypeInfo().name;
-}
-
-const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx,
-                                             const sem::Type* ty) {
-  if (ty->Is<sem::Void>()) {
-    return ctx.dst->create<ast::Void>();
-  }
-  if (ty->Is<sem::I32>()) {
-    return ctx.dst->create<ast::I32>();
-  }
-  if (ty->Is<sem::U32>()) {
-    return ctx.dst->create<ast::U32>();
-  }
-  if (ty->Is<sem::F32>()) {
-    return ctx.dst->create<ast::F32>();
-  }
-  if (ty->Is<sem::Bool>()) {
-    return ctx.dst->create<ast::Bool>();
-  }
-  if (auto* m = ty->As<sem::Matrix>()) {
-    auto* el = CreateASTTypeFor(ctx, m->type());
-    return ctx.dst->create<ast::Matrix>(el, m->rows(), m->columns());
-  }
-  if (auto* v = ty->As<sem::Vector>()) {
-    auto* el = CreateASTTypeFor(ctx, v->type());
-    return ctx.dst->create<ast::Vector>(el, v->Width());
-  }
-  if (auto* a = ty->As<sem::Array>()) {
-    auto* el = CreateASTTypeFor(ctx, a->ElemType());
-    ast::AttributeList attrs;
-    if (!a->IsStrideImplicit()) {
-      attrs.emplace_back(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
-    }
-    if (a->IsRuntimeSized()) {
-      return ctx.dst->ty.array(el, nullptr, std::move(attrs));
-    } else {
-      return ctx.dst->ty.array(el, a->Count(), std::move(attrs));
-    }
-  }
-  if (auto* s = ty->As<sem::Struct>()) {
-    return ctx.dst->create<ast::TypeName>(ctx.Clone(s->Declaration()->name));
-  }
-  if (auto* s = ty->As<sem::Reference>()) {
-    return CreateASTTypeFor(ctx, s->StoreType());
-  }
-  if (auto* a = ty->As<sem::Atomic>()) {
-    return ctx.dst->create<ast::Atomic>(CreateASTTypeFor(ctx, a->Type()));
-  }
-  if (auto* t = ty->As<sem::DepthTexture>()) {
-    return ctx.dst->create<ast::DepthTexture>(t->dim());
-  }
-  if (auto* t = ty->As<sem::DepthMultisampledTexture>()) {
-    return ctx.dst->create<ast::DepthMultisampledTexture>(t->dim());
-  }
-  if (ty->Is<sem::ExternalTexture>()) {
-    return ctx.dst->create<ast::ExternalTexture>();
-  }
-  if (auto* t = ty->As<sem::MultisampledTexture>()) {
-    return ctx.dst->create<ast::MultisampledTexture>(
-        t->dim(), CreateASTTypeFor(ctx, t->type()));
-  }
-  if (auto* t = ty->As<sem::SampledTexture>()) {
-    return ctx.dst->create<ast::SampledTexture>(
-        t->dim(), CreateASTTypeFor(ctx, t->type()));
-  }
-  if (auto* t = ty->As<sem::StorageTexture>()) {
-    return ctx.dst->create<ast::StorageTexture>(
-        t->dim(), t->texel_format(), CreateASTTypeFor(ctx, t->type()),
-        t->access());
-  }
-  if (auto* s = ty->As<sem::Sampler>()) {
-    return ctx.dst->create<ast::Sampler>(s->kind());
-  }
-  TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-      << "Unhandled type: " << ty->TypeInfo().name;
-  return nullptr;
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/transform.h b/src/transform/transform.h
deleted file mode 100644
index 8bc109e..0000000
--- a/src/transform/transform.h
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2020 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_TRANSFORM_TRANSFORM_H_
-#define SRC_TRANSFORM_TRANSFORM_H_
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-
-#include "src/castable.h"
-#include "src/program.h"
-
-namespace tint {
-namespace transform {
-
-/// Data is the base class for transforms that accept extra input or emit extra
-/// output information along with a Program.
-class Data : public Castable<Data> {
- public:
-  /// Constructor
-  Data();
-
-  /// Copy constructor
-  Data(const Data&);
-
-  /// Destructor
-  ~Data() override;
-
-  /// Assignment operator
-  /// @returns this Data
-  Data& operator=(const Data&);
-};
-
-/// DataMap is a map of Data unique pointers keyed by the Data's ClassID.
-class DataMap {
- public:
-  /// Constructor
-  DataMap();
-
-  /// Move constructor
-  DataMap(DataMap&&);
-
-  /// Constructor
-  /// @param data_unique_ptrs a variadic list of additional data unique_ptrs
-  /// produced by the transform
-  template <typename... DATA>
-  explicit DataMap(DATA... data_unique_ptrs) {
-    PutAll(std::forward<DATA>(data_unique_ptrs)...);
-  }
-
-  /// Destructor
-  ~DataMap();
-
-  /// Move assignment operator
-  /// @param rhs the DataMap to move into this DataMap
-  /// @return this DataMap
-  DataMap& operator=(DataMap&& rhs);
-
-  /// Adds the data into DataMap keyed by the ClassID of type T.
-  /// @param data the data to add to the DataMap
-  template <typename T>
-  void Put(std::unique_ptr<T>&& data) {
-    static_assert(std::is_base_of<Data, T>::value,
-                  "T does not derive from Data");
-    map_[&TypeInfo::Of<T>()] = std::move(data);
-  }
-
-  /// Creates the data of type `T` with the provided arguments and adds it into
-  /// DataMap keyed by the ClassID of type T.
-  /// @param args the arguments forwarded to the constructor for type T
-  template <typename T, typename... ARGS>
-  void Add(ARGS&&... args) {
-    Put(std::make_unique<T>(std::forward<ARGS>(args)...));
-  }
-
-  /// @returns a pointer to the Data placed into the DataMap with a call to
-  /// Put()
-  template <typename T>
-  T const* Get() const {
-    auto it = map_.find(&TypeInfo::Of<T>());
-    if (it == map_.end()) {
-      return nullptr;
-    }
-    return static_cast<T*>(it->second.get());
-  }
-
-  /// Add moves all the data from other into this DataMap
-  /// @param other the DataMap to move into this DataMap
-  void Add(DataMap&& other) {
-    for (auto& it : other.map_) {
-      map_.emplace(it.first, std::move(it.second));
-    }
-    other.map_.clear();
-  }
-
- private:
-  template <typename T0>
-  void PutAll(T0&& first) {
-    Put(std::forward<T0>(first));
-  }
-
-  template <typename T0, typename... Tn>
-  void PutAll(T0&& first, Tn&&... remainder) {
-    Put(std::forward<T0>(first));
-    PutAll(std::forward<Tn>(remainder)...);
-  }
-
-  std::unordered_map<const TypeInfo*, std::unique_ptr<Data>> map_;
-};
-
-/// The return type of Run()
-class Output {
- public:
-  /// Constructor
-  Output();
-
-  /// Constructor
-  /// @param program the program to move into this Output
-  explicit Output(Program&& program);
-
-  /// Constructor
-  /// @param program_ the program to move into this Output
-  /// @param data_ a variadic list of additional data unique_ptrs produced by
-  /// the transform
-  template <typename... DATA>
-  Output(Program&& program_, DATA... data_)
-      : program(std::move(program_)), data(std::forward<DATA>(data_)...) {}
-
-  /// The transformed program. May be empty on error.
-  Program program;
-
-  /// Extra output generated by the transforms.
-  DataMap data;
-};
-
-/// Interface for Program transforms
-class Transform : public Castable<Transform> {
- public:
-  /// Constructor
-  Transform();
-  /// Destructor
-  ~Transform() override;
-
-  /// Runs the transform on `program`, returning the transformation result.
-  /// @param program the source program to transform
-  /// @param data optional extra transform-specific input data
-  /// @returns the transformation result
-  virtual Output Run(const Program* program, const DataMap& data = {}) const;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  virtual bool ShouldRun(const Program* program,
-                         const DataMap& data = {}) const;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  virtual void Run(CloneContext& ctx,
-                   const DataMap& inputs,
-                   DataMap& outputs) const;
-
-  /// Removes the statement `stmt` from the transformed program.
-  /// RemoveStatement handles edge cases, like statements in the initializer and
-  /// continuing of for-loops.
-  /// @param ctx the clone context
-  /// @param stmt the statement to remove when the program is cloned
-  static void RemoveStatement(CloneContext& ctx, const ast::Statement* stmt);
-
-  /// CreateASTTypeFor constructs new ast::Type nodes that reconstructs the
-  /// semantic type `ty`.
-  /// @param ctx the clone context
-  /// @param ty the semantic type to reconstruct
-  /// @returns a ast::Type that when resolved, will produce the semantic type
-  /// `ty`.
-  static const ast::Type* CreateASTTypeFor(CloneContext& ctx,
-                                           const sem::Type* ty);
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_TRANSFORM_H_
diff --git a/src/transform/transform_test.cc b/src/transform/transform_test.cc
deleted file mode 100644
index 8098e0c..0000000
--- a/src/transform/transform_test.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/transform.h"
-#include "src/clone_context.h"
-#include "src/program_builder.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-// Inherit from Transform so we have access to protected methods
-struct CreateASTTypeForTest : public testing::Test, public Transform {
-  Output Run(const Program*, const DataMap&) const override { return {}; }
-
-  const ast::Type* create(
-      std::function<sem::Type*(ProgramBuilder&)> create_sem_type) {
-    ProgramBuilder sem_type_builder;
-    auto* sem_type = create_sem_type(sem_type_builder);
-    Program program(std::move(sem_type_builder));
-    CloneContext ctx(&ast_type_builder, &program, false);
-    return CreateASTTypeFor(ctx, sem_type);
-  }
-
-  ProgramBuilder ast_type_builder;
-};
-
-TEST_F(CreateASTTypeForTest, Basic) {
-  EXPECT_TRUE(create([](ProgramBuilder& b) {
-                return b.create<sem::I32>();
-              })->Is<ast::I32>());
-  EXPECT_TRUE(create([](ProgramBuilder& b) {
-                return b.create<sem::U32>();
-              })->Is<ast::U32>());
-  EXPECT_TRUE(create([](ProgramBuilder& b) {
-                return b.create<sem::F32>();
-              })->Is<ast::F32>());
-  EXPECT_TRUE(create([](ProgramBuilder& b) {
-                return b.create<sem::Bool>();
-              })->Is<ast::Bool>());
-  EXPECT_TRUE(create([](ProgramBuilder& b) {
-                return b.create<sem::Void>();
-              })->Is<ast::Void>());
-}
-
-TEST_F(CreateASTTypeForTest, Matrix) {
-  auto* mat = create([](ProgramBuilder& b) {
-    auto* column_type = b.create<sem::Vector>(b.create<sem::F32>(), 2u);
-    return b.create<sem::Matrix>(column_type, 3u);
-  });
-  ASSERT_TRUE(mat->Is<ast::Matrix>());
-  ASSERT_TRUE(mat->As<ast::Matrix>()->type->Is<ast::F32>());
-  ASSERT_EQ(mat->As<ast::Matrix>()->columns, 3u);
-  ASSERT_EQ(mat->As<ast::Matrix>()->rows, 2u);
-}
-
-TEST_F(CreateASTTypeForTest, Vector) {
-  auto* vec = create([](ProgramBuilder& b) {
-    return b.create<sem::Vector>(b.create<sem::F32>(), 2);
-  });
-  ASSERT_TRUE(vec->Is<ast::Vector>());
-  ASSERT_TRUE(vec->As<ast::Vector>()->type->Is<ast::F32>());
-  ASSERT_EQ(vec->As<ast::Vector>()->width, 2u);
-}
-
-TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
-  auto* arr = create([](ProgramBuilder& b) {
-    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 32u, 32u);
-  });
-  ASSERT_TRUE(arr->Is<ast::Array>());
-  ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
-  ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 0u);
-
-  auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
-  ASSERT_NE(size, nullptr);
-  EXPECT_EQ(size->ValueAsI32(), 2);
-}
-
-TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
-  auto* arr = create([](ProgramBuilder& b) {
-    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 64u, 32u);
-  });
-  ASSERT_TRUE(arr->Is<ast::Array>());
-  ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
-  ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 1u);
-  ASSERT_TRUE(arr->As<ast::Array>()->attributes[0]->Is<ast::StrideAttribute>());
-  ASSERT_EQ(
-      arr->As<ast::Array>()->attributes[0]->As<ast::StrideAttribute>()->stride,
-      64u);
-
-  auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
-  ASSERT_NE(size, nullptr);
-  EXPECT_EQ(size->ValueAsI32(), 2);
-}
-
-TEST_F(CreateASTTypeForTest, Struct) {
-  auto* str = create([](ProgramBuilder& b) {
-    auto* decl = b.Structure("S", {}, {});
-    return b.create<sem::Struct>(decl, decl->name, sem::StructMemberList{},
-                                 4 /* align */, 4 /* size */,
-                                 4 /* size_no_padding */);
-  });
-  ASSERT_TRUE(str->Is<ast::TypeName>());
-  EXPECT_EQ(ast_type_builder.Symbols().NameFor(str->As<ast::TypeName>()->name),
-            "S");
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/unshadow.cc b/src/transform/unshadow.cc
deleted file mode 100644
index d71e087..0000000
--- a/src/transform/unshadow.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/unshadow.h"
-
-#include <memory>
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/function.h"
-#include "src/sem/statement.h"
-#include "src/sem/variable.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::Unshadow);
-
-namespace tint {
-namespace transform {
-
-/// The PIMPL state for the Unshadow transform
-struct Unshadow::State {
-  /// The clone context
-  CloneContext& ctx;
-
-  /// Constructor
-  /// @param context the clone context
-  explicit State(CloneContext& context) : ctx(context) {}
-
-  /// Performs the transformation
-  void Run() {
-    auto& sem = ctx.src->Sem();
-
-    // Maps a variable to its new name.
-    std::unordered_map<const sem::Variable*, Symbol> renamed_to;
-
-    auto rename = [&](const sem::Variable* var) -> const ast::Variable* {
-      auto* decl = var->Declaration();
-      auto name = ctx.src->Symbols().NameFor(decl->symbol);
-      auto symbol = ctx.dst->Symbols().New(name);
-      renamed_to.emplace(var, symbol);
-
-      auto source = ctx.Clone(decl->source);
-      auto* type = ctx.Clone(decl->type);
-      auto* constructor = ctx.Clone(decl->constructor);
-      auto attributes = ctx.Clone(decl->attributes);
-      return ctx.dst->create<ast::Variable>(
-          source, symbol, decl->declared_storage_class, decl->declared_access,
-          type, decl->is_const, decl->is_overridable, constructor, attributes);
-    };
-
-    ctx.ReplaceAll([&](const ast::Variable* var) -> const ast::Variable* {
-      if (auto* local = sem.Get<sem::LocalVariable>(var)) {
-        if (local->Shadows()) {
-          return rename(local);
-        }
-      }
-      if (auto* param = sem.Get<sem::Parameter>(var)) {
-        if (param->Shadows()) {
-          return rename(param);
-        }
-      }
-      return nullptr;
-    });
-    ctx.ReplaceAll([&](const ast::IdentifierExpression* ident)
-                       -> const tint::ast::IdentifierExpression* {
-      if (auto* user = sem.Get<sem::VariableUser>(ident)) {
-        auto it = renamed_to.find(user->Variable());
-        if (it != renamed_to.end()) {
-          return ctx.dst->Expr(it->second);
-        }
-      }
-      return nullptr;
-    });
-    ctx.Clone();
-  }
-};
-
-Unshadow::Unshadow() = default;
-
-Unshadow::~Unshadow() = default;
-
-void Unshadow::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-  State(ctx).Run();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/unshadow.h b/src/transform/unshadow.h
deleted file mode 100644
index ca7be17..0000000
--- a/src/transform/unshadow.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_UNSHADOW_H_
-#define SRC_TRANSFORM_UNSHADOW_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Unshadow is a Transform that renames any variables that shadow another
-/// variable.
-class Unshadow : public Castable<Unshadow, Transform> {
- public:
-  /// Constructor
-  Unshadow();
-
-  /// Destructor
-  ~Unshadow() override;
-
- protected:
-  struct State;
-
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_UNSHADOW_H_
diff --git a/src/transform/unshadow_test.cc b/src/transform/unshadow_test.cc
deleted file mode 100644
index f6383ef..0000000
--- a/src/transform/unshadow_test.cc
+++ /dev/null
@@ -1,609 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/unshadow.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using UnshadowTest = TransformTest;
-
-TEST_F(UnshadowTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, Noop) {
-  auto* src = R"(
-var<private> a : i32;
-
-let b : i32 = 1;
-
-fn F(c : i32) {
-  var d : i32;
-  let e : i32 = 1;
-  {
-    var f : i32;
-    let g : i32 = 1;
-  }
-}
-)";
-
-  auto* expect = src;
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsAlias) {
-  auto* src = R"(
-type a = i32;
-
-fn X() {
-  var a = false;
-}
-
-fn Y() {
-  let a = true;
-}
-)";
-
-  auto* expect = R"(
-type a = i32;
-
-fn X() {
-  var a_1 = false;
-}
-
-fn Y() {
-  let a_2 = true;
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsAlias_OutOfOrder) {
-  auto* src = R"(
-fn X() {
-  var a = false;
-}
-
-fn Y() {
-  let a = true;
-}
-
-type a = i32;
-)";
-
-  auto* expect = R"(
-fn X() {
-  var a_1 = false;
-}
-
-fn Y() {
-  let a_2 = true;
-}
-
-type a = i32;
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsStruct) {
-  auto* src = R"(
-struct a {
-  m : i32;
-};
-
-fn X() {
-  var a = true;
-}
-
-fn Y() {
-  let a = false;
-}
-)";
-
-  auto* expect = R"(
-struct a {
-  m : i32;
-}
-
-fn X() {
-  var a_1 = true;
-}
-
-fn Y() {
-  let a_2 = false;
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsStruct_OutOfOrder) {
-  auto* src = R"(
-fn X() {
-  var a = true;
-}
-
-fn Y() {
-  let a = false;
-}
-
-struct a {
-  m : i32;
-};
-
-)";
-
-  auto* expect = R"(
-fn X() {
-  var a_1 = true;
-}
-
-fn Y() {
-  let a_2 = false;
-}
-
-struct a {
-  m : i32;
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsFunction) {
-  auto* src = R"(
-fn a() {
-  var a = true;
-  var b = false;
-}
-
-fn b() {
-  let a = true;
-  let b = false;
-}
-)";
-
-  auto* expect = R"(
-fn a() {
-  var a_1 = true;
-  var b_1 = false;
-}
-
-fn b() {
-  let a_2 = true;
-  let b_2 = false;
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsFunction_OutOfOrder) {
-  auto* src = R"(
-fn b() {
-  let a = true;
-  let b = false;
-}
-
-fn a() {
-  var a = true;
-  var b = false;
-}
-
-)";
-
-  auto* expect = R"(
-fn b() {
-  let a_1 = true;
-  let b_1 = false;
-}
-
-fn a() {
-  var a_2 = true;
-  var b_2 = false;
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsGlobalVar) {
-  auto* src = R"(
-var<private> a : i32;
-
-fn X() {
-  var a = (a == 123);
-}
-
-fn Y() {
-  let a = (a == 321);
-}
-)";
-
-  auto* expect = R"(
-var<private> a : i32;
-
-fn X() {
-  var a_1 = (a == 123);
-}
-
-fn Y() {
-  let a_2 = (a == 321);
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsGlobalVar_OutOfOrder) {
-  auto* src = R"(
-fn X() {
-  var a = (a == 123);
-}
-
-fn Y() {
-  let a = (a == 321);
-}
-
-var<private> a : i32;
-)";
-
-  auto* expect = R"(
-fn X() {
-  var a_1 = (a == 123);
-}
-
-fn Y() {
-  let a_2 = (a == 321);
-}
-
-var<private> a : i32;
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsGlobalLet) {
-  auto* src = R"(
-let a : i32 = 1;
-
-fn X() {
-  var a = (a == 123);
-}
-
-fn Y() {
-  let a = (a == 321);
-}
-)";
-
-  auto* expect = R"(
-let a : i32 = 1;
-
-fn X() {
-  var a_1 = (a == 123);
-}
-
-fn Y() {
-  let a_2 = (a == 321);
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsGlobalLet_OutOfOrder) {
-  auto* src = R"(
-fn X() {
-  var a = (a == 123);
-}
-
-fn Y() {
-  let a = (a == 321);
-}
-
-let a : i32 = 1;
-)";
-
-  auto* expect = R"(
-fn X() {
-  var a_1 = (a == 123);
-}
-
-fn Y() {
-  let a_2 = (a == 321);
-}
-
-let a : i32 = 1;
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsLocalVar) {
-  auto* src = R"(
-fn X() {
-  var a : i32;
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-)";
-
-  auto* expect = R"(
-fn X() {
-  var a : i32;
-  {
-    var a_1 = (a == 123);
-  }
-  {
-    let a_2 = (a == 321);
-  }
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsLocalLet) {
-  auto* src = R"(
-fn X() {
-  let a = 1;
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-)";
-
-  auto* expect = R"(
-fn X() {
-  let a = 1;
-  {
-    var a_1 = (a == 123);
-  }
-  {
-    let a_2 = (a == 321);
-  }
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, LocalShadowsParam) {
-  auto* src = R"(
-fn F(a : i32) {
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-)";
-
-  auto* expect = R"(
-fn F(a : i32) {
-  {
-    var a_1 = (a == 123);
-  }
-  {
-    let a_2 = (a == 321);
-  }
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsFunction) {
-  auto* src = R"(
-fn a(a : i32) {
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-)";
-
-  auto* expect = R"(
-fn a(a_1 : i32) {
-  {
-    var a_2 = (a_1 == 123);
-  }
-  {
-    let a_3 = (a_1 == 321);
-  }
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsGlobalVar) {
-  auto* src = R"(
-var<private> a : i32;
-
-fn F(a : bool) {
-}
-)";
-
-  auto* expect = R"(
-var<private> a : i32;
-
-fn F(a_1 : bool) {
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsGlobalLet) {
-  auto* src = R"(
-let a : i32 = 1;
-
-fn F(a : bool) {
-}
-)";
-
-  auto* expect = R"(
-let a : i32 = 1;
-
-fn F(a_1 : bool) {
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsGlobalLet_OutOfOrder) {
-  auto* src = R"(
-fn F(a : bool) {
-}
-
-let a : i32 = 1;
-)";
-
-  auto* expect = R"(
-fn F(a_1 : bool) {
-}
-
-let a : i32 = 1;
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsAlias) {
-  auto* src = R"(
-type a = i32;
-
-fn F(a : a) {
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-)";
-
-  auto* expect = R"(
-type a = i32;
-
-fn F(a_1 : a) {
-  {
-    var a_2 = (a_1 == 123);
-  }
-  {
-    let a_3 = (a_1 == 321);
-  }
-}
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(UnshadowTest, ParamShadowsAlias_OutOfOrder) {
-  auto* src = R"(
-fn F(a : a) {
-  {
-    var a = (a == 123);
-  }
-  {
-    let a = (a == 321);
-  }
-}
-
-type a = i32;
-)";
-
-  auto* expect = R"(
-fn F(a_1 : a) {
-  {
-    var a_2 = (a_1 == 123);
-  }
-  {
-    let a_3 = (a_1 == 321);
-  }
-}
-
-type a = i32;
-)";
-
-  auto got = Run<Unshadow>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/utils/hoist_to_decl_before.cc b/src/transform/utils/hoist_to_decl_before.cc
deleted file mode 100644
index aeff023..0000000
--- a/src/transform/utils/hoist_to_decl_before.cc
+++ /dev/null
@@ -1,327 +0,0 @@
-// 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/transform/utils/hoist_to_decl_before.h"
-
-#include <unordered_map>
-
-#include "src/ast/variable_decl_statement.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/if_statement.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/variable.h"
-#include "src/utils/reverse.h"
-
-namespace tint::transform {
-
-/// Private implementation of HoistToDeclBefore transform
-class HoistToDeclBefore::State {
-  CloneContext& ctx;
-  ProgramBuilder& b;
-
-  /// Holds information about a for-loop that needs to be decomposed into a
-  /// loop, so that declaration statements can be inserted before the
-  /// condition expression or continuing statement.
-  struct LoopInfo {
-    ast::StatementList cond_decls;
-    ast::StatementList cont_decls;
-  };
-
-  /// Holds information about 'if's with 'else-if' statements that need to be
-  /// decomposed into 'if {else}' so that declaration statements can be
-  /// inserted before the condition expression.
-  struct IfInfo {
-    /// Info for each else-if that needs decomposing
-    struct ElseIfInfo {
-      /// Decls to insert before condition
-      ast::StatementList cond_decls;
-    };
-
-    /// 'else if's that need to be decomposed to 'else { if }'
-    std::unordered_map<const sem::ElseStatement*, ElseIfInfo> else_ifs;
-  };
-
-  /// For-loops that need to be decomposed to loops.
-  std::unordered_map<const sem::ForLoopStatement*, LoopInfo> loops;
-
-  /// If statements with 'else if's that need to be decomposed to 'else { if
-  /// }'
-  std::unordered_map<const sem::IfStatement*, IfInfo> ifs;
-
-  // Inserts `decl` before `sem_expr`, possibly marking a for-loop to be
-  // converted to a loop, or an else-if to an else { if }.
-  bool InsertBefore(const sem::Expression* sem_expr,
-                    const ast::VariableDeclStatement* decl) {
-    auto* sem_stmt = sem_expr->Stmt();
-    auto* stmt = sem_stmt->Declaration();
-
-    if (auto* else_if = sem_stmt->As<sem::ElseStatement>()) {
-      // Expression used in 'else if' condition.
-      // Need to convert 'else if' to 'else { if }'.
-      auto& if_info = ifs[else_if->Parent()->As<sem::IfStatement>()];
-      if_info.else_ifs[else_if].cond_decls.push_back(decl);
-      return true;
-    }
-
-    if (auto* fl = sem_stmt->As<sem::ForLoopStatement>()) {
-      // Expression used in for-loop condition.
-      // For-loop needs to be decomposed to a loop.
-      loops[fl].cond_decls.emplace_back(decl);
-      return true;
-    }
-
-    auto* parent = sem_stmt->Parent();  // The statement's parent
-    if (auto* block = parent->As<sem::BlockStatement>()) {
-      // Expression's statement sits in a block. Simple case.
-      // Insert the decl before the parent statement
-      ctx.InsertBefore(block->Declaration()->statements, stmt, decl);
-      return true;
-    }
-
-    if (auto* fl = parent->As<sem::ForLoopStatement>()) {
-      // Expression is used in a for-loop. These require special care.
-      if (fl->Declaration()->initializer == stmt) {
-        // Expression used in for-loop initializer.
-        // Insert the let above the for-loop.
-        ctx.InsertBefore(fl->Block()->Declaration()->statements,
-                         fl->Declaration(), decl);
-        return true;
-      }
-
-      if (fl->Declaration()->continuing == stmt) {
-        // Expression used in for-loop continuing.
-        // For-loop needs to be decomposed to a loop.
-        loops[fl].cont_decls.emplace_back(decl);
-        return true;
-      }
-
-      TINT_ICE(Transform, b.Diagnostics())
-          << "unhandled use of expression in for-loop";
-      return false;
-    }
-
-    TINT_ICE(Transform, b.Diagnostics())
-        << "unhandled expression parent statement type: "
-        << parent->TypeInfo().name;
-    return false;
-  }
-
-  // Converts any for-loops marked for conversion to loops, inserting
-  // registered declaration statements before the condition or continuing
-  // statement.
-  void ForLoopsToLoops() {
-    if (loops.empty()) {
-      return;
-    }
-
-    // At least one for-loop needs to be transformed into a loop.
-    ctx.ReplaceAll(
-        [&](const ast::ForLoopStatement* stmt) -> const ast::Statement* {
-          auto& sem = ctx.src->Sem();
-
-          if (auto* fl = sem.Get(stmt)) {
-            if (auto it = loops.find(fl); it != loops.end()) {
-              auto& info = it->second;
-              auto* for_loop = fl->Declaration();
-              // For-loop needs to be decomposed to a loop.
-              // Build the loop body's statements.
-              // Start with any let declarations for the conditional
-              // expression.
-              auto body_stmts = info.cond_decls;
-              // If the for-loop has a condition, emit this next as:
-              //   if (!cond) { break; }
-              if (auto* cond = for_loop->condition) {
-                // !condition
-                auto* not_cond = b.create<ast::UnaryOpExpression>(
-                    ast::UnaryOp::kNot, ctx.Clone(cond));
-                // { break; }
-                auto* break_body = b.Block(b.create<ast::BreakStatement>());
-                // if (!condition) { break; }
-                body_stmts.emplace_back(b.If(not_cond, break_body));
-              }
-              // Next emit the for-loop body
-              body_stmts.emplace_back(ctx.Clone(for_loop->body));
-
-              // Finally create the continuing block if there was one.
-              const ast::BlockStatement* continuing = nullptr;
-              if (auto* cont = for_loop->continuing) {
-                // Continuing block starts with any let declarations used by
-                // the continuing.
-                auto cont_stmts = info.cont_decls;
-                cont_stmts.emplace_back(ctx.Clone(cont));
-                continuing = b.Block(cont_stmts);
-              }
-
-              auto* body = b.Block(body_stmts);
-              auto* loop = b.Loop(body, continuing);
-              if (auto* init = for_loop->initializer) {
-                return b.Block(ctx.Clone(init), loop);
-              }
-              return loop;
-            }
-          }
-          return nullptr;
-        });
-  }
-
-  void ElseIfsToElseWithNestedIfs() {
-    if (ifs.empty()) {
-      return;
-    }
-
-    ctx.ReplaceAll([&](const ast::IfStatement* if_stmt)  //
-                   -> const ast::IfStatement* {
-      auto& sem = ctx.src->Sem();
-      auto* sem_if = sem.Get(if_stmt);
-      if (!sem_if) {
-        return nullptr;
-      }
-
-      auto it = ifs.find(sem_if);
-      if (it == ifs.end()) {
-        return nullptr;
-      }
-      auto& if_info = it->second;
-
-      // This if statement has "else if"s that need to be converted to "else
-      // { if }"s
-
-      ast::ElseStatementList next_else_stmts;
-      next_else_stmts.reserve(if_stmt->else_statements.size());
-
-      for (auto* else_stmt : utils::Reverse(if_stmt->else_statements)) {
-        if (else_stmt->condition == nullptr) {
-          // The last 'else', keep as is
-          next_else_stmts.insert(next_else_stmts.begin(), ctx.Clone(else_stmt));
-
-        } else {
-          auto* sem_else_if = sem.Get(else_stmt);
-
-          auto it2 = if_info.else_ifs.find(sem_else_if);
-          if (it2 == if_info.else_ifs.end()) {
-            // 'else if' we don't need to modify (no decls to insert), so
-            // keep as is
-            next_else_stmts.insert(next_else_stmts.begin(),
-                                   ctx.Clone(else_stmt));
-
-          } else {
-            // 'else if' we need to replace with 'else <decls> { if }'
-            auto& else_if_info = it2->second;
-
-            // Build the else body's statements, starting with let decls for
-            // the conditional expression
-            auto& body_stmts = else_if_info.cond_decls;
-
-            // Build nested if
-            auto* cond = ctx.Clone(else_stmt->condition);
-            auto* body = ctx.Clone(else_stmt->body);
-            body_stmts.emplace_back(b.If(cond, body, next_else_stmts));
-
-            // Build else
-            auto* else_with_nested_if = b.Else(b.Block(body_stmts));
-
-            // This will be used in parent if (either another nested if, or
-            // top-level if)
-            next_else_stmts = {else_with_nested_if};
-          }
-        }
-      }
-
-      // Build a new top-level if with new else statements
-      if (next_else_stmts.empty()) {
-        TINT_ICE(Transform, b.Diagnostics())
-            << "Expected else statements to insert into new if";
-      }
-      auto* cond = ctx.Clone(if_stmt->condition);
-      auto* body = ctx.Clone(if_stmt->body);
-      auto* new_if = b.If(cond, body, next_else_stmts);
-      return new_if;
-    });
-  }
-
- public:
-  /// Constructor
-  /// @param ctx_in the clone context
-  explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
-
-  /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
-  /// before `before_expr`.
-  /// @param before_expr expression to insert `expr` before
-  /// @param expr expression to hoist
-  /// @param as_const hoist to `let` if true, otherwise to `var`
-  /// @param decl_name optional name to use for the variable/constant name
-  /// @return true on success
-  bool HoistToDeclBefore(const sem::Expression* before_expr,
-                         const ast::Expression* expr,
-                         bool as_const,
-                         const char* decl_name = "") {
-    auto name = b.Symbols().New(decl_name);
-
-    auto* sem_expr = ctx.src->Sem().Get(expr);
-    bool is_ref =
-        sem_expr &&
-        !sem_expr->Is<sem::VariableUser>()  // Don't need to take a ref to a var
-        && sem_expr->Type()->Is<sem::Reference>();
-
-    auto* expr_clone = ctx.Clone(expr);
-    if (is_ref) {
-      expr_clone = b.AddressOf(expr_clone);
-    }
-
-    // Construct the let/var that holds the hoisted expr
-    auto* v = as_const ? b.Const(name, nullptr, expr_clone)
-                       : b.Var(name, nullptr, expr_clone);
-    auto* decl = b.Decl(v);
-
-    if (!InsertBefore(before_expr, decl)) {
-      return false;
-    }
-
-    // Replace the initializer expression with a reference to the let
-    const ast::Expression* new_expr = b.Expr(name);
-    if (is_ref) {
-      new_expr = b.Deref(new_expr);
-    }
-    ctx.Replace(expr, new_expr);
-    return true;
-  }
-
-  /// Applies any scheduled insertions from previous calls to Add() to
-  /// CloneContext. Call this once before ctx.Clone().
-  /// @return true on success
-  bool Apply() {
-    ForLoopsToLoops();
-    ElseIfsToElseWithNestedIfs();
-    return true;
-  }
-};
-
-HoistToDeclBefore::HoistToDeclBefore(CloneContext& ctx)
-    : state_(std::make_unique<State>(ctx)) {}
-
-HoistToDeclBefore::~HoistToDeclBefore() {}
-
-bool HoistToDeclBefore::Add(const sem::Expression* before_expr,
-                            const ast::Expression* expr,
-                            bool as_const,
-                            const char* decl_name) {
-  return state_->HoistToDeclBefore(before_expr, expr, as_const, decl_name);
-}
-
-bool HoistToDeclBefore::Apply() {
-  return state_->Apply();
-}
-
-}  // namespace tint::transform
diff --git a/src/transform/utils/hoist_to_decl_before.h b/src/transform/utils/hoist_to_decl_before.h
deleted file mode 100644
index 48082a2..0000000
--- a/src/transform/utils/hoist_to_decl_before.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
-#define SRC_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
-
-#include <memory>
-
-#include "src/sem/expression.h"
-#include "src/transform/transform.h"
-
-namespace tint::transform {
-
-/// Utility class that can be used to hoist expressions before other
-/// expressions, possibly converting 'for' loops to 'loop's and 'else if to
-// 'else if'.
-class HoistToDeclBefore {
- public:
-  /// Constructor
-  /// @param ctx the clone context
-  explicit HoistToDeclBefore(CloneContext& ctx);
-
-  /// Destructor
-  ~HoistToDeclBefore();
-
-  /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
-  /// before `before_expr`.
-  /// @param before_expr expression to insert `expr` before
-  /// @param expr expression to hoist
-  /// @param as_const hoist to `let` if true, otherwise to `var`
-  /// @param decl_name optional name to use for the variable/constant name
-  /// @return true on success
-  bool Add(const sem::Expression* before_expr,
-           const ast::Expression* expr,
-           bool as_const,
-           const char* decl_name = "");
-
-  /// Applies any scheduled insertions from previous calls to Add() to
-  /// CloneContext. Call this once before ctx.Clone().
-  /// @return true on success
-  bool Apply();
-
- private:
-  class State;
-  std::unique_ptr<State> state_;
-};
-
-}  // namespace tint::transform
-
-#endif  // SRC_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
diff --git a/src/transform/utils/hoist_to_decl_before_test.cc b/src/transform/utils/hoist_to_decl_before_test.cc
deleted file mode 100644
index 994317c..0000000
--- a/src/transform/utils/hoist_to_decl_before_test.cc
+++ /dev/null
@@ -1,291 +0,0 @@
-// 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 <utility>
-
-#include "gtest/gtest-spi.h"
-#include "src/program_builder.h"
-#include "src/transform/test_helper.h"
-#include "src/transform/utils/hoist_to_decl_before.h"
-
-namespace tint::transform {
-namespace {
-
-using HoistToDeclBeforeTest = ::testing::Test;
-
-TEST_F(HoistToDeclBeforeTest, VarInit) {
-  // fn f() {
-  //     var a = 1;
-  // }
-  ProgramBuilder b;
-  auto* expr = b.Expr(1);
-  auto* var = b.Decl(b.Var("a", nullptr, expr));
-  b.Func("f", {}, b.ty.void_(), {var});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = 1;
-  var a = tint_symbol;
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, ForLoopInit) {
-  // fn f() {
-  //     for(var a = 1; true; ) {
-  //     }
-  // }
-  ProgramBuilder b;
-  auto* expr = b.Expr(1);
-  auto* s =
-      b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), {}, b.Block());
-  b.Func("f", {}, b.ty.void_(), {s});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  let tint_symbol = 1;
-  for(var a = tint_symbol; true; ) {
-  }
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, ForLoopCond) {
-  // fn f() {
-  //     var a : bool;
-  //     for(; a; ) {
-  //     }
-  // }
-  ProgramBuilder b;
-  auto* var = b.Decl(b.Var("a", b.ty.bool_()));
-  auto* expr = b.Expr("a");
-  auto* s = b.For({}, expr, {}, b.Block());
-  b.Func("f", {}, b.ty.void_(), {var, s});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  var a : bool;
-  loop {
-    let tint_symbol = a;
-    if (!(tint_symbol)) {
-      break;
-    }
-    {
-    }
-  }
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, ForLoopCont) {
-  // fn f() {
-  //     for(; true; var a = 1) {
-  //     }
-  // }
-  ProgramBuilder b;
-  auto* expr = b.Expr(1);
-  auto* s =
-      b.For({}, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
-  b.Func("f", {}, b.ty.void_(), {s});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  loop {
-    if (!(true)) {
-      break;
-    }
-    {
-    }
-
-    continuing {
-      let tint_symbol = 1;
-      var a = tint_symbol;
-    }
-  }
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, ElseIf) {
-  // fn f() {
-  //     var a : bool;
-  //     if (true) {
-  //     } else if (a) {
-  //     } else {
-  //     }
-  // }
-  ProgramBuilder b;
-  auto* var = b.Decl(b.Var("a", b.ty.bool_()));
-  auto* expr = b.Expr("a");
-  auto* s = b.If(b.Expr(true), b.Block(),  //
-                 b.Else(expr, b.Block()),  //
-                 b.Else(b.Block()));
-  b.Func("f", {}, b.ty.void_(), {var, s});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  var a : bool;
-  if (true) {
-  } else {
-    let tint_symbol = a;
-    if (tint_symbol) {
-    } else {
-    }
-  }
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, Array1D) {
-  // fn f() {
-  //     var a : array<i32, 10>;
-  //     var b = a[0];
-  // }
-  ProgramBuilder b;
-  auto* var1 = b.Decl(b.Var("a", b.ty.array<ProgramBuilder::i32, 10>()));
-  auto* expr = b.IndexAccessor("a", 0);
-  auto* var2 = b.Decl(b.Var("b", nullptr, expr));
-  b.Func("f", {}, b.ty.void_(), {var1, var2});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  var a : array<i32, 10>;
-  let tint_symbol = &(a[0]);
-  var b = *(tint_symbol);
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-TEST_F(HoistToDeclBeforeTest, Array2D) {
-  // fn f() {
-  //     var a : array<array<i32, 10>, 10>;
-  //     var b = a[0][0];
-  // }
-  ProgramBuilder b;
-
-  auto* var1 =
-      b.Decl(b.Var("a", b.ty.array(b.ty.array<ProgramBuilder::i32, 10>(), 10)));
-  auto* expr = b.IndexAccessor(b.IndexAccessor("a", 0), 0);
-  auto* var2 = b.Decl(b.Var("b", nullptr, expr));
-  b.Func("f", {}, b.ty.void_(), {var1, var2});
-
-  Program original(std::move(b));
-  ProgramBuilder cloned_b;
-  CloneContext ctx(&cloned_b, &original);
-
-  HoistToDeclBefore hoistToDeclBefore(ctx);
-  auto* sem_expr = ctx.src->Sem().Get(expr);
-  hoistToDeclBefore.Add(sem_expr, expr, true);
-  hoistToDeclBefore.Apply();
-
-  ctx.Clone();
-  Program cloned(std::move(cloned_b));
-
-  auto* expect = R"(
-fn f() {
-  var a : array<array<i32, 10>, 10>;
-  let tint_symbol = &(a[0][0]);
-  var b = *(tint_symbol);
-}
-)";
-
-  EXPECT_EQ(expect, str(cloned));
-}
-
-}  // namespace
-}  // namespace tint::transform
diff --git a/src/transform/var_for_dynamic_index.cc b/src/transform/var_for_dynamic_index.cc
deleted file mode 100644
index 44c994d..0000000
--- a/src/transform/var_for_dynamic_index.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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/transform/var_for_dynamic_index.h"
-#include "src/program_builder.h"
-#include "src/transform/utils/hoist_to_decl_before.h"
-
-namespace tint::transform {
-
-VarForDynamicIndex::VarForDynamicIndex() = default;
-
-VarForDynamicIndex::~VarForDynamicIndex() = default;
-
-void VarForDynamicIndex::Run(CloneContext& ctx,
-                             const DataMap&,
-                             DataMap&) const {
-  HoistToDeclBefore hoist_to_decl_before(ctx);
-
-  // Extracts array and matrix values that are dynamically indexed to a
-  // temporary `var` local that is then indexed.
-  auto dynamic_index_to_var =
-      [&](const ast::IndexAccessorExpression* access_expr) {
-        auto* index_expr = access_expr->index;
-        auto* object_expr = access_expr->object;
-        auto& sem = ctx.src->Sem();
-
-        if (sem.Get(index_expr)->ConstantValue()) {
-          // Index expression resolves to a compile time value.
-          // As this isn't a dynamic index, we can ignore this.
-          return true;
-        }
-
-        auto* indexed = sem.Get(object_expr);
-        if (!indexed->Type()->IsAnyOf<sem::Array, sem::Matrix>()) {
-          // We only care about array and matrices.
-          return true;
-        }
-
-        // TODO(bclayton): group multiple accesses in the same object.
-        // e.g. arr[i] + arr[i+1] // Don't create two vars for this
-        return hoist_to_decl_before.Add(indexed, object_expr, false,
-                                        "var_for_index");
-      };
-
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* access_expr = node->As<ast::IndexAccessorExpression>()) {
-      if (!dynamic_index_to_var(access_expr)) {
-        return;
-      }
-    }
-  }
-
-  hoist_to_decl_before.Apply();
-  ctx.Clone();
-}
-
-}  // namespace tint::transform
diff --git a/src/transform/var_for_dynamic_index.h b/src/transform/var_for_dynamic_index.h
deleted file mode 100644
index a04fa06..0000000
--- a/src/transform/var_for_dynamic_index.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
-#define SRC_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
-
-#include "src/transform/transform.h"
-
-namespace tint::transform {
-
-/// A transform that extracts array and matrix values that are dynamically
-/// indexed to a temporary `var` local before performing the index. This
-/// transform is used by the SPIR-V writer as there is no SPIR-V instruction
-/// that can dynamically index a non-pointer composite.
-class VarForDynamicIndex : public Transform {
- public:
-  /// Constructor
-  VarForDynamicIndex();
-
-  /// Destructor
-  ~VarForDynamicIndex() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace tint::transform
-
-#endif  // SRC_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
diff --git a/src/transform/var_for_dynamic_index_test.cc b/src/transform/var_for_dynamic_index_test.cc
deleted file mode 100644
index 91289ef..0000000
--- a/src/transform/var_for_dynamic_index_test.cc
+++ /dev/null
@@ -1,553 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/var_for_dynamic_index.h"
-#include "src/transform/for_loop_to_loop.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using VarForDynamicIndexTest = TransformTest;
-
-TEST_F(VarForDynamicIndexTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = "";
-
-  auto got = Run<ForLoopToLoop, VarForDynamicIndex>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexDynamic) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 4>(1, 2, 3, 4);
-  let x = p[i];
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 4>(1, 2, 3, 4);
-  var var_for_index = p;
-  let x = var_for_index[i];
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexDynamic) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  let x = p[i];
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  var var_for_index = p;
-  let x = var_for_index[i];
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexDynamicChain) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  var j : i32;
-  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
-  let x = p[i][j];
-}
-)";
-
-  // TODO(bclayton): Optimize this case:
-  // This output is not as efficient as it could be.
-  // We only actually need to hoist the inner-most array to a `var`
-  // (`var_for_index`), as later indexing operations will be working with
-  // references, not values.
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  var j : i32;
-  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
-  var var_for_index = p;
-  var var_for_index_1 = var_for_index[i];
-  let x = var_for_index_1[j];
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopInit) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
-  for(let x = p[i]; ; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
-  var var_for_index = p;
-  for(let x = var_for_index[i]; ; ) {
-    break;
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopInit) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  for(let x = p[i]; ; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  var var_for_index = p;
-  for(let x = var_for_index[i]; ; ) {
-    break;
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopCond) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  for(; p[i] < 3; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  loop {
-    var var_for_index = p;
-    if (!((var_for_index[i] < 3))) {
-      break;
-    }
-    {
-      break;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCond) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  for(; p[i].x < 3.0; ) {
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  loop {
-    var var_for_index = p;
-    if (!((var_for_index[i].x < 3.0))) {
-      break;
-    }
-    {
-      break;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCondWithNestedIndex) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  for(; p[i].x < 3.0; ) {
-    if (p[i].x < 1.0) {
-        var marker = 1;
-    }
-    break;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  loop {
-    var var_for_index = p;
-    if (!((var_for_index[i].x < 3.0))) {
-      break;
-    }
-    {
-      var var_for_index_1 = p;
-      if ((var_for_index_1[i].x < 1.0)) {
-        var marker = 1;
-      }
-      break;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIf) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  if (false) {
-    var marker = 0;
-  } else if (p[i] < 3) {
-    var marker = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  if (false) {
-    var marker = 0;
-  } else {
-    var var_for_index = p;
-    if ((var_for_index[i] < 3)) {
-      var marker = 1;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIfChain) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else if (p[i] < 3) {
-    var marker = 2;
-  } else if (p[i] < 4) {
-    var marker = 3;
-  } else if (true) {
-    var marker = 4;
-  } else {
-    var marker = 5;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = array<i32, 2>(1, 2);
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else {
-    var var_for_index = p;
-    if ((var_for_index[i] < 3)) {
-      var marker = 2;
-    } else {
-      var var_for_index_1 = p;
-      if ((var_for_index_1[i] < 4)) {
-        var marker = 3;
-      } else if (true) {
-        var marker = 4;
-      } else {
-        var marker = 5;
-      }
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIf) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  if (false) {
-    var marker_if = 1;
-  } else if (p[i].x < 3.0) {
-    var marker_else_if = 1;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  if (false) {
-    var marker_if = 1;
-  } else {
-    var var_for_index = p;
-    if ((var_for_index[i].x < 3.0)) {
-      var marker_else_if = 1;
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIfChain) {
-  auto* src = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else if (p[i].x < 3.0) {
-    var marker = 2;
-  } else if (p[i].y < 3.0) {
-    var marker = 3;
-  } else if (true) {
-    var marker = 4;
-  } else {
-    var marker = 5;
-  }
-}
-)";
-
-  auto* expect = R"(
-fn f() {
-  var i : i32;
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else {
-    var var_for_index = p;
-    if ((var_for_index[i].x < 3.0)) {
-      var marker = 2;
-    } else {
-      var var_for_index_1 = p;
-      if ((var_for_index_1[i].y < 3.0)) {
-        var marker = 3;
-      } else if (true) {
-        var marker = 4;
-      } else {
-        var marker = 5;
-      }
-    }
-  }
-}
-)";
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexLiteral) {
-  auto* src = R"(
-fn f() {
-  let p = array<i32, 4>(1, 2, 3, 4);
-  let x = p[1];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexLiteral) {
-  auto* src = R"(
-fn f() {
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  let x = p[1];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexConstantLet) {
-  auto* src = R"(
-fn f() {
-  let p = array<i32, 4>(1, 2, 3, 4);
-  let c = 1;
-  let x = p[c];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexConstantLet) {
-  auto* src = R"(
-fn f() {
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  let c = 1;
-  let x = p[c];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, ArrayIndexLiteralChain) {
-  auto* src = R"(
-fn f() {
-  let a = array<i32, 2>(1, 2);
-  let b = array<i32, 2>(3, 4);
-  let p = array<array<i32, 2>, 2>(a, b);
-  let x = p[0][1];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VarForDynamicIndexTest, MatrixIndexLiteralChain) {
-  auto* src = R"(
-fn f() {
-  let p = mat2x2(1.0, 2.0, 3.0, 4.0);
-  let x = p[0][1];
-}
-)";
-
-  auto* expect = src;
-
-  DataMap data;
-  auto got = Run<VarForDynamicIndex>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/vectorize_scalar_matrix_constructors.cc b/src/transform/vectorize_scalar_matrix_constructors.cc
deleted file mode 100644
index 75fcfb1..0000000
--- a/src/transform/vectorize_scalar_matrix_constructors.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/vectorize_scalar_matrix_constructors.h"
-
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/type_constructor.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::VectorizeScalarMatrixConstructors);
-
-namespace tint {
-namespace transform {
-
-VectorizeScalarMatrixConstructors::VectorizeScalarMatrixConstructors() =
-    default;
-
-VectorizeScalarMatrixConstructors::~VectorizeScalarMatrixConstructors() =
-    default;
-
-bool VectorizeScalarMatrixConstructors::ShouldRun(const Program* program,
-                                                  const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (auto* call = program->Sem().Get<sem::Call>(node)) {
-      if (call->Target()->Is<sem::TypeConstructor>() &&
-          call->Type()->Is<sem::Matrix>()) {
-        auto& args = call->Arguments();
-        if (args.size() > 0 && args[0]->Type()->is_scalar()) {
-          return true;
-        }
-      }
-    }
-  }
-  return false;
-}
-
-void VectorizeScalarMatrixConstructors::Run(CloneContext& ctx,
-                                            const DataMap&,
-                                            DataMap&) const {
-  ctx.ReplaceAll(
-      [&](const ast::CallExpression* expr) -> const ast::CallExpression* {
-        auto* call = ctx.src->Sem().Get(expr);
-        auto* ty_ctor = call->Target()->As<sem::TypeConstructor>();
-        if (!ty_ctor) {
-          return nullptr;
-        }
-        // Check if this is a matrix constructor with scalar arguments.
-        auto* mat_type = call->Type()->As<sem::Matrix>();
-        if (!mat_type) {
-          return nullptr;
-        }
-
-        auto& args = call->Arguments();
-        if (args.size() == 0) {
-          return nullptr;
-        }
-        if (!args[0]->Type()->is_scalar()) {
-          return nullptr;
-        }
-
-        // Build a list of vector expressions for each column.
-        ast::ExpressionList columns;
-        for (uint32_t c = 0; c < mat_type->columns(); c++) {
-          // Build a list of scalar expressions for each value in the column.
-          ast::ExpressionList row_values;
-          for (uint32_t r = 0; r < mat_type->rows(); r++) {
-            row_values.push_back(
-                ctx.Clone(args[c * mat_type->rows() + r]->Declaration()));
-          }
-
-          // Construct the column vector.
-          auto* col = ctx.dst->vec(CreateASTTypeFor(ctx, mat_type->type()),
-                                   mat_type->rows(), row_values);
-          columns.push_back(col);
-        }
-        return ctx.dst->Construct(CreateASTTypeFor(ctx, mat_type), columns);
-      });
-
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/vectorize_scalar_matrix_constructors.h b/src/transform/vectorize_scalar_matrix_constructors.h
deleted file mode 100644
index 220d366..0000000
--- a/src/transform/vectorize_scalar_matrix_constructors.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
-#define SRC_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// A transform that converts scalar matrix constructors to the vector form.
-class VectorizeScalarMatrixConstructors
-    : public Castable<VectorizeScalarMatrixConstructors, Transform> {
- public:
-  /// Constructor
-  VectorizeScalarMatrixConstructors();
-
-  /// Destructor
-  ~VectorizeScalarMatrixConstructors() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
diff --git a/src/transform/vectorize_scalar_matrix_constructors_test.cc b/src/transform/vectorize_scalar_matrix_constructors_test.cc
deleted file mode 100644
index 51b252f..0000000
--- a/src/transform/vectorize_scalar_matrix_constructors_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/vectorize_scalar_matrix_constructors.h"
-
-#include <string>
-#include <utility>
-
-#include "src/transform/test_helper.h"
-#include "src/utils/string.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using VectorizeScalarMatrixConstructorsTest =
-    TransformTestWithParam<std::pair<uint32_t, uint32_t>>;
-
-TEST_F(VectorizeScalarMatrixConstructorsTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
-}
-
-TEST_P(VectorizeScalarMatrixConstructorsTest, Basic) {
-  uint32_t cols = GetParam().first;
-  uint32_t rows = GetParam().second;
-  std::string mat_type =
-      "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
-  std::string vec_type = "vec" + std::to_string(rows) + "<f32>";
-  std::string scalar_values;
-  std::string vector_values;
-  for (uint32_t c = 0; c < cols; c++) {
-    if (c > 0) {
-      vector_values += ", ";
-      scalar_values += ", ";
-    }
-    vector_values += vec_type + "(";
-    for (uint32_t r = 0; r < rows; r++) {
-      if (r > 0) {
-        scalar_values += ", ";
-        vector_values += ", ";
-      }
-      auto value = std::to_string(c * rows + r) + ".0";
-      scalar_values += value;
-      vector_values += value;
-    }
-    vector_values += ")";
-  }
-
-  std::string tmpl = R"(
-@stage(fragment)
-fn main() {
-  let m = ${matrix}(${values});
-}
-)";
-  tmpl = utils::ReplaceAll(tmpl, "${matrix}", mat_type);
-  auto src = utils::ReplaceAll(tmpl, "${values}", scalar_values);
-  auto expect = utils::ReplaceAll(tmpl, "${values}", vector_values);
-
-  EXPECT_TRUE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
-
-  auto got = Run<VectorizeScalarMatrixConstructors>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_P(VectorizeScalarMatrixConstructorsTest, NonScalarConstructors) {
-  uint32_t cols = GetParam().first;
-  uint32_t rows = GetParam().second;
-  std::string mat_type =
-      "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
-  std::string vec_type = "vec" + std::to_string(rows) + "<f32>";
-  std::string columns;
-  for (uint32_t c = 0; c < cols; c++) {
-    if (c > 0) {
-      columns += ", ";
-    }
-    columns += vec_type + "()";
-  }
-
-  std::string tmpl = R"(
-@stage(fragment)
-fn main() {
-  let m = ${matrix}(${columns});
-}
-)";
-  tmpl = utils::ReplaceAll(tmpl, "${matrix}", mat_type);
-  auto src = utils::ReplaceAll(tmpl, "${columns}", columns);
-  auto expect = src;
-
-  EXPECT_FALSE(ShouldRun<VectorizeScalarMatrixConstructors>(src));
-
-  auto got = Run<VectorizeScalarMatrixConstructors>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-INSTANTIATE_TEST_SUITE_P(VectorizeScalarMatrixConstructorsTest,
-                         VectorizeScalarMatrixConstructorsTest,
-                         testing::Values(std::make_pair(2, 2),
-                                         std::make_pair(2, 3),
-                                         std::make_pair(2, 4),
-                                         std::make_pair(3, 2),
-                                         std::make_pair(3, 3),
-                                         std::make_pair(3, 4),
-                                         std::make_pair(4, 2),
-                                         std::make_pair(4, 3),
-                                         std::make_pair(4, 4)));
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
deleted file mode 100644
index a284ce5..0000000
--- a/src/transform/vertex_pulling.cc
+++ /dev/null
@@ -1,962 +0,0 @@
-// Copyright 2020 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/transform/vertex_pulling.h"
-
-#include <algorithm>
-#include <utility>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/program_builder.h"
-#include "src/sem/variable.h"
-#include "src/utils/map.h"
-#include "src/utils/math.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config);
-
-namespace tint {
-namespace transform {
-
-namespace {
-
-/// The base type of a component.
-/// The format type is either this type or a vector of this type.
-enum class BaseType {
-  kInvalid,
-  kU32,
-  kI32,
-  kF32,
-};
-
-/// Writes the BaseType to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param format the BaseType to write
-/// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, BaseType format) {
-  switch (format) {
-    case BaseType::kInvalid:
-      return out << "invalid";
-    case BaseType::kU32:
-      return out << "u32";
-    case BaseType::kI32:
-      return out << "i32";
-    case BaseType::kF32:
-      return out << "f32";
-  }
-  return out << "<unknown>";
-}
-
-/// Writes the VertexFormat to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param format the VertexFormat to write
-/// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, VertexFormat format) {
-  switch (format) {
-    case VertexFormat::kUint8x2:
-      return out << "uint8x2";
-    case VertexFormat::kUint8x4:
-      return out << "uint8x4";
-    case VertexFormat::kSint8x2:
-      return out << "sint8x2";
-    case VertexFormat::kSint8x4:
-      return out << "sint8x4";
-    case VertexFormat::kUnorm8x2:
-      return out << "unorm8x2";
-    case VertexFormat::kUnorm8x4:
-      return out << "unorm8x4";
-    case VertexFormat::kSnorm8x2:
-      return out << "snorm8x2";
-    case VertexFormat::kSnorm8x4:
-      return out << "snorm8x4";
-    case VertexFormat::kUint16x2:
-      return out << "uint16x2";
-    case VertexFormat::kUint16x4:
-      return out << "uint16x4";
-    case VertexFormat::kSint16x2:
-      return out << "sint16x2";
-    case VertexFormat::kSint16x4:
-      return out << "sint16x4";
-    case VertexFormat::kUnorm16x2:
-      return out << "unorm16x2";
-    case VertexFormat::kUnorm16x4:
-      return out << "unorm16x4";
-    case VertexFormat::kSnorm16x2:
-      return out << "snorm16x2";
-    case VertexFormat::kSnorm16x4:
-      return out << "snorm16x4";
-    case VertexFormat::kFloat16x2:
-      return out << "float16x2";
-    case VertexFormat::kFloat16x4:
-      return out << "float16x4";
-    case VertexFormat::kFloat32:
-      return out << "float32";
-    case VertexFormat::kFloat32x2:
-      return out << "float32x2";
-    case VertexFormat::kFloat32x3:
-      return out << "float32x3";
-    case VertexFormat::kFloat32x4:
-      return out << "float32x4";
-    case VertexFormat::kUint32:
-      return out << "uint32";
-    case VertexFormat::kUint32x2:
-      return out << "uint32x2";
-    case VertexFormat::kUint32x3:
-      return out << "uint32x3";
-    case VertexFormat::kUint32x4:
-      return out << "uint32x4";
-    case VertexFormat::kSint32:
-      return out << "sint32";
-    case VertexFormat::kSint32x2:
-      return out << "sint32x2";
-    case VertexFormat::kSint32x3:
-      return out << "sint32x3";
-    case VertexFormat::kSint32x4:
-      return out << "sint32x4";
-  }
-  return out << "<unknown>";
-}
-
-/// A vertex attribute data format.
-struct DataType {
-  BaseType base_type;
-  uint32_t width;  // 1 for scalar, 2+ for a vector
-};
-
-DataType DataTypeOf(const sem::Type* ty) {
-  if (ty->Is<sem::I32>()) {
-    return {BaseType::kI32, 1};
-  }
-  if (ty->Is<sem::U32>()) {
-    return {BaseType::kU32, 1};
-  }
-  if (ty->Is<sem::F32>()) {
-    return {BaseType::kF32, 1};
-  }
-  if (auto* vec = ty->As<sem::Vector>()) {
-    return {DataTypeOf(vec->type()).base_type, vec->Width()};
-  }
-  return {BaseType::kInvalid, 0};
-}
-
-DataType DataTypeOf(VertexFormat format) {
-  switch (format) {
-    case VertexFormat::kUint32:
-      return {BaseType::kU32, 1};
-    case VertexFormat::kUint8x2:
-    case VertexFormat::kUint16x2:
-    case VertexFormat::kUint32x2:
-      return {BaseType::kU32, 2};
-    case VertexFormat::kUint32x3:
-      return {BaseType::kU32, 3};
-    case VertexFormat::kUint8x4:
-    case VertexFormat::kUint16x4:
-    case VertexFormat::kUint32x4:
-      return {BaseType::kU32, 4};
-    case VertexFormat::kSint32:
-      return {BaseType::kI32, 1};
-    case VertexFormat::kSint8x2:
-    case VertexFormat::kSint16x2:
-    case VertexFormat::kSint32x2:
-      return {BaseType::kI32, 2};
-    case VertexFormat::kSint32x3:
-      return {BaseType::kI32, 3};
-    case VertexFormat::kSint8x4:
-    case VertexFormat::kSint16x4:
-    case VertexFormat::kSint32x4:
-      return {BaseType::kI32, 4};
-    case VertexFormat::kFloat32:
-      return {BaseType::kF32, 1};
-    case VertexFormat::kUnorm8x2:
-    case VertexFormat::kSnorm8x2:
-    case VertexFormat::kUnorm16x2:
-    case VertexFormat::kSnorm16x2:
-    case VertexFormat::kFloat16x2:
-    case VertexFormat::kFloat32x2:
-      return {BaseType::kF32, 2};
-    case VertexFormat::kFloat32x3:
-      return {BaseType::kF32, 3};
-    case VertexFormat::kUnorm8x4:
-    case VertexFormat::kSnorm8x4:
-    case VertexFormat::kUnorm16x4:
-    case VertexFormat::kSnorm16x4:
-    case VertexFormat::kFloat16x4:
-    case VertexFormat::kFloat32x4:
-      return {BaseType::kF32, 4};
-  }
-  return {BaseType::kInvalid, 0};
-}
-
-struct State {
-  State(CloneContext& context, const VertexPulling::Config& c)
-      : ctx(context), cfg(c) {}
-  State(const State&) = default;
-  ~State() = default;
-
-  /// LocationReplacement describes an ast::Variable replacement for a
-  /// location input.
-  struct LocationReplacement {
-    /// The variable to replace in the source Program
-    ast::Variable* from;
-    /// The replacement to use in the target ProgramBuilder
-    ast::Variable* to;
-  };
-
-  struct LocationInfo {
-    std::function<const ast::Expression*()> expr;
-    const sem::Type* type;
-  };
-
-  CloneContext& ctx;
-  VertexPulling::Config const cfg;
-  std::unordered_map<uint32_t, LocationInfo> location_info;
-  std::function<const ast::Expression*()> vertex_index_expr = nullptr;
-  std::function<const ast::Expression*()> instance_index_expr = nullptr;
-  Symbol pulling_position_name;
-  Symbol struct_buffer_name;
-  std::unordered_map<uint32_t, Symbol> vertex_buffer_names;
-  ast::VariableList new_function_parameters;
-
-  /// Generate the vertex buffer binding name
-  /// @param index index to append to buffer name
-  Symbol GetVertexBufferName(uint32_t index) {
-    return utils::GetOrCreate(vertex_buffer_names, index, [&] {
-      static const char kVertexBufferNamePrefix[] =
-          "tint_pulling_vertex_buffer_";
-      return ctx.dst->Symbols().New(kVertexBufferNamePrefix +
-                                    std::to_string(index));
-    });
-  }
-
-  /// Lazily generates the structure buffer symbol
-  Symbol GetStructBufferName() {
-    if (!struct_buffer_name.IsValid()) {
-      static const char kStructBufferName[] = "tint_vertex_data";
-      struct_buffer_name = ctx.dst->Symbols().New(kStructBufferName);
-    }
-    return struct_buffer_name;
-  }
-
-  /// Adds storage buffer decorated variables for the vertex buffers
-  void AddVertexStorageBuffers() {
-    // Creating the struct type
-    static const char kStructName[] = "TintVertexData";
-    auto* struct_type = ctx.dst->Structure(
-        ctx.dst->Symbols().New(kStructName),
-        {
-            ctx.dst->Member(GetStructBufferName(),
-                            ctx.dst->ty.array<ProgramBuilder::u32>(4)),
-        });
-    for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
-      // The decorated variable with struct type
-      ctx.dst->Global(
-          GetVertexBufferName(i), ctx.dst->ty.Of(struct_type),
-          ast::StorageClass::kStorage, ast::Access::kRead,
-          ast::AttributeList{
-              ctx.dst->create<ast::BindingAttribute>(i),
-              ctx.dst->create<ast::GroupAttribute>(cfg.pulling_group),
-          });
-    }
-  }
-
-  /// Creates and returns the assignment to the variables from the buffers
-  ast::BlockStatement* CreateVertexPullingPreamble() {
-    // Assign by looking at the vertex descriptor to find attributes with
-    // matching location.
-
-    ast::StatementList stmts;
-
-    for (uint32_t buffer_idx = 0; buffer_idx < cfg.vertex_state.size();
-         ++buffer_idx) {
-      const VertexBufferLayoutDescriptor& buffer_layout =
-          cfg.vertex_state[buffer_idx];
-
-      if ((buffer_layout.array_stride & 3) != 0) {
-        ctx.dst->Diagnostics().add_error(
-            diag::System::Transform,
-            "WebGPU requires that vertex stride must be a multiple of 4 bytes, "
-            "but VertexPulling array stride for buffer " +
-                std::to_string(buffer_idx) + " was " +
-                std::to_string(buffer_layout.array_stride) + " bytes");
-        return nullptr;
-      }
-
-      auto* index_expr = buffer_layout.step_mode == VertexStepMode::kVertex
-                             ? vertex_index_expr()
-                             : instance_index_expr();
-
-      // buffer_array_base is the base array offset for all the vertex
-      // attributes. These are units of uint (4 bytes).
-      auto buffer_array_base = ctx.dst->Symbols().New(
-          "buffer_array_base_" + std::to_string(buffer_idx));
-
-      auto* attribute_offset = index_expr;
-      if (buffer_layout.array_stride != 4) {
-        attribute_offset =
-            ctx.dst->Mul(index_expr, buffer_layout.array_stride / 4u);
-      }
-
-      // let pulling_offset_n = <attribute_offset>
-      stmts.emplace_back(ctx.dst->Decl(
-          ctx.dst->Const(buffer_array_base, nullptr, attribute_offset)));
-
-      for (const VertexAttributeDescriptor& attribute_desc :
-           buffer_layout.attributes) {
-        auto it = location_info.find(attribute_desc.shader_location);
-        if (it == location_info.end()) {
-          continue;
-        }
-        auto& var = it->second;
-
-        // Data type of the target WGSL variable
-        auto var_dt = DataTypeOf(var.type);
-        // Data type of the vertex stream attribute
-        auto fmt_dt = DataTypeOf(attribute_desc.format);
-
-        // Base types must match between the vertex stream and the WGSL variable
-        if (var_dt.base_type != fmt_dt.base_type) {
-          std::stringstream err;
-          err << "VertexAttributeDescriptor for location "
-              << std::to_string(attribute_desc.shader_location)
-              << " has format " << attribute_desc.format
-              << " but shader expects "
-              << var.type->FriendlyName(ctx.src->Symbols());
-          ctx.dst->Diagnostics().add_error(diag::System::Transform, err.str());
-          return nullptr;
-        }
-
-        // Load the attribute value
-        auto* fetch = Fetch(buffer_array_base, attribute_desc.offset,
-                            buffer_idx, attribute_desc.format);
-
-        // The attribute value may not be of the desired vector width. If it is
-        // not, we'll need to either reduce the width with a swizzle, or append
-        // 0's and / or a 1.
-        auto* value = fetch;
-        if (var_dt.width < fmt_dt.width) {
-          // WGSL variable vector width is smaller than the loaded vector width
-          switch (var_dt.width) {
-            case 1:
-              value = ctx.dst->MemberAccessor(fetch, "x");
-              break;
-            case 2:
-              value = ctx.dst->MemberAccessor(fetch, "xy");
-              break;
-            case 3:
-              value = ctx.dst->MemberAccessor(fetch, "xyz");
-              break;
-            default:
-              TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-                  << var_dt.width;
-              return nullptr;
-          }
-        } else if (var_dt.width > fmt_dt.width) {
-          // WGSL variable vector width is wider than the loaded vector width
-          const ast::Type* ty = nullptr;
-          ast::ExpressionList values{fetch};
-          switch (var_dt.base_type) {
-            case BaseType::kI32:
-              ty = ctx.dst->ty.i32();
-              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                values.emplace_back(ctx.dst->Expr((i == 3) ? 1 : 0));
-              }
-              break;
-            case BaseType::kU32:
-              ty = ctx.dst->ty.u32();
-              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                values.emplace_back(ctx.dst->Expr((i == 3) ? 1u : 0u));
-              }
-              break;
-            case BaseType::kF32:
-              ty = ctx.dst->ty.f32();
-              for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                values.emplace_back(ctx.dst->Expr((i == 3) ? 1.f : 0.f));
-              }
-              break;
-            default:
-              TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-                  << var_dt.base_type;
-              return nullptr;
-          }
-          value = ctx.dst->Construct(ctx.dst->ty.vec(ty, var_dt.width), values);
-        }
-
-        // Assign the value to the WGSL variable
-        stmts.emplace_back(ctx.dst->Assign(var.expr(), value));
-      }
-    }
-
-    if (stmts.empty()) {
-      return nullptr;
-    }
-
-    return ctx.dst->create<ast::BlockStatement>(stmts);
-  }
-
-  /// Generates an expression reading from a buffer a specific format.
-  /// @param array_base the symbol of the variable holding the base array offset
-  /// of the vertex array (each index is 4-bytes).
-  /// @param offset the byte offset of the data from `buffer_base`
-  /// @param buffer the index of the vertex buffer
-  /// @param format the format to read
-  const ast::Expression* Fetch(Symbol array_base,
-                               uint32_t offset,
-                               uint32_t buffer,
-                               VertexFormat format) {
-    using u32 = ProgramBuilder::u32;
-    using i32 = ProgramBuilder::i32;
-    using f32 = ProgramBuilder::f32;
-
-    // Returns a u32 loaded from buffer_base + offset.
-    auto load_u32 = [&] {
-      return LoadPrimitive(array_base, offset, buffer, VertexFormat::kUint32);
-    };
-
-    // Returns a i32 loaded from buffer_base + offset.
-    auto load_i32 = [&] { return ctx.dst->Bitcast<i32>(load_u32()); };
-
-    // Returns a u32 loaded from buffer_base + offset + 4.
-    auto load_next_u32 = [&] {
-      return LoadPrimitive(array_base, offset + 4, buffer,
-                           VertexFormat::kUint32);
-    };
-
-    // Returns a i32 loaded from buffer_base + offset + 4.
-    auto load_next_i32 = [&] { return ctx.dst->Bitcast<i32>(load_next_u32()); };
-
-    // Returns a u16 loaded from offset, packed in the high 16 bits of a u32.
-    // The low 16 bits are 0.
-    // `min_alignment` must be a power of two.
-    // `offset` must be `min_alignment` bytes aligned.
-    auto load_u16_h = [&] {
-      auto low_u32_offset = offset & ~3u;
-      auto* low_u32 = LoadPrimitive(array_base, low_u32_offset, buffer,
-                                    VertexFormat::kUint32);
-      switch (offset & 3) {
-        case 0:
-          return ctx.dst->Shl(low_u32, 16u);
-        case 1:
-          return ctx.dst->And(ctx.dst->Shl(low_u32, 8u), 0xffff0000u);
-        case 2:
-          return ctx.dst->And(low_u32, 0xffff0000u);
-        default: {  // 3:
-          auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer,
-                                         VertexFormat::kUint32);
-          auto* shr = ctx.dst->Shr(low_u32, 8u);
-          auto* shl = ctx.dst->Shl(high_u32, 24u);
-          return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffff0000u);
-        }
-      }
-    };
-
-    // Returns a u16 loaded from offset, packed in the low 16 bits of a u32.
-    // The high 16 bits are 0.
-    auto load_u16_l = [&] {
-      auto low_u32_offset = offset & ~3u;
-      auto* low_u32 = LoadPrimitive(array_base, low_u32_offset, buffer,
-                                    VertexFormat::kUint32);
-      switch (offset & 3) {
-        case 0:
-          return ctx.dst->And(low_u32, 0xffffu);
-        case 1:
-          return ctx.dst->And(ctx.dst->Shr(low_u32, 8u), 0xffffu);
-        case 2:
-          return ctx.dst->Shr(low_u32, 16u);
-        default: {  // 3:
-          auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer,
-                                         VertexFormat::kUint32);
-          auto* shr = ctx.dst->Shr(low_u32, 24u);
-          auto* shl = ctx.dst->Shl(high_u32, 8u);
-          return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffffu);
-        }
-      }
-    };
-
-    // Returns a i16 loaded from offset, packed in the high 16 bits of a u32.
-    // The low 16 bits are 0.
-    auto load_i16_h = [&] { return ctx.dst->Bitcast<i32>(load_u16_h()); };
-
-    // Assumptions are made that alignment must be at least as large as the size
-    // of a single component.
-    switch (format) {
-      // Basic primitives
-      case VertexFormat::kUint32:
-      case VertexFormat::kSint32:
-      case VertexFormat::kFloat32:
-        return LoadPrimitive(array_base, offset, buffer, format);
-
-        // Vectors of basic primitives
-      case VertexFormat::kUint32x2:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
-                       VertexFormat::kUint32, 2);
-      case VertexFormat::kUint32x3:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
-                       VertexFormat::kUint32, 3);
-      case VertexFormat::kUint32x4:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(),
-                       VertexFormat::kUint32, 4);
-      case VertexFormat::kSint32x2:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
-                       VertexFormat::kSint32, 2);
-      case VertexFormat::kSint32x3:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
-                       VertexFormat::kSint32, 3);
-      case VertexFormat::kSint32x4:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(),
-                       VertexFormat::kSint32, 4);
-      case VertexFormat::kFloat32x2:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
-                       VertexFormat::kFloat32, 2);
-      case VertexFormat::kFloat32x3:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
-                       VertexFormat::kFloat32, 3);
-      case VertexFormat::kFloat32x4:
-        return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(),
-                       VertexFormat::kFloat32, 4);
-
-      case VertexFormat::kUint8x2: {
-        // yyxx0000, yyxx0000
-        auto* u16s = ctx.dst->vec2<u32>(load_u16_h());
-        // xx000000, yyxx0000
-        auto* shl = ctx.dst->Shl(u16s, ctx.dst->vec2<u32>(8u, 0u));
-        // 000000xx, 000000yy
-        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24u));
-      }
-      case VertexFormat::kUint8x4: {
-        // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx
-        auto* u32s = ctx.dst->vec4<u32>(load_u32());
-        // xx000000, yyxx0000, zzyyxx00, wwzzyyxx
-        auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec4<u32>(24u, 16u, 8u, 0u));
-        // 000000xx, 000000yy, 000000zz, 000000ww
-        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24u));
-      }
-      case VertexFormat::kUint16x2: {
-        // yyyyxxxx, yyyyxxxx
-        auto* u32s = ctx.dst->vec2<u32>(load_u32());
-        // xxxx0000, yyyyxxxx
-        auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec2<u32>(16u, 0u));
-        // 0000xxxx, 0000yyyy
-        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16u));
-      }
-      case VertexFormat::kUint16x4: {
-        // yyyyxxxx, wwwwzzzz
-        auto* u32s = ctx.dst->vec2<u32>(load_u32(), load_next_u32());
-        // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz
-        auto* xxyy = ctx.dst->MemberAccessor(u32s, "xxyy");
-        // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz
-        auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16u, 0u, 16u, 0u));
-        // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
-        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16u));
-      }
-      case VertexFormat::kSint8x2: {
-        // yyxx0000, yyxx0000
-        auto* i16s = ctx.dst->vec2<i32>(load_i16_h());
-        // xx000000, yyxx0000
-        auto* shl = ctx.dst->Shl(i16s, ctx.dst->vec2<u32>(8u, 0u));
-        // ssssssxx, ssssssyy
-        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24u));
-      }
-      case VertexFormat::kSint8x4: {
-        // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx
-        auto* i32s = ctx.dst->vec4<i32>(load_i32());
-        // xx000000, yyxx0000, zzyyxx00, wwzzyyxx
-        auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec4<u32>(24u, 16u, 8u, 0u));
-        // ssssssxx, ssssssyy, sssssszz, ssssssww
-        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24u));
-      }
-      case VertexFormat::kSint16x2: {
-        // yyyyxxxx, yyyyxxxx
-        auto* i32s = ctx.dst->vec2<i32>(load_i32());
-        // xxxx0000, yyyyxxxx
-        auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec2<u32>(16u, 0u));
-        // ssssxxxx, ssssyyyy
-        return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16u));
-      }
-      case VertexFormat::kSint16x4: {
-        // yyyyxxxx, wwwwzzzz
-        auto* i32s = ctx.dst->vec2<i32>(load_i32(), load_next_i32());
-        // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz
-        auto* xxyy = ctx.dst->MemberAccessor(i32s, "xxyy");
-        // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz
-        auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16u, 0u, 16u, 0u));
-        // ssssxxxx, ssssyyyy, sssszzzz, sssswwww
-        return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16u));
-      }
-      case VertexFormat::kUnorm8x2:
-        return ctx.dst->MemberAccessor(
-            ctx.dst->Call("unpack4x8unorm", load_u16_l()), "xy");
-      case VertexFormat::kSnorm8x2:
-        return ctx.dst->MemberAccessor(
-            ctx.dst->Call("unpack4x8snorm", load_u16_l()), "xy");
-      case VertexFormat::kUnorm8x4:
-        return ctx.dst->Call("unpack4x8unorm", load_u32());
-      case VertexFormat::kSnorm8x4:
-        return ctx.dst->Call("unpack4x8snorm", load_u32());
-      case VertexFormat::kUnorm16x2:
-        return ctx.dst->Call("unpack2x16unorm", load_u32());
-      case VertexFormat::kSnorm16x2:
-        return ctx.dst->Call("unpack2x16snorm", load_u32());
-      case VertexFormat::kFloat16x2:
-        return ctx.dst->Call("unpack2x16float", load_u32());
-      case VertexFormat::kUnorm16x4:
-        return ctx.dst->vec4<f32>(
-            ctx.dst->Call("unpack2x16unorm", load_u32()),
-            ctx.dst->Call("unpack2x16unorm", load_next_u32()));
-      case VertexFormat::kSnorm16x4:
-        return ctx.dst->vec4<f32>(
-            ctx.dst->Call("unpack2x16snorm", load_u32()),
-            ctx.dst->Call("unpack2x16snorm", load_next_u32()));
-      case VertexFormat::kFloat16x4:
-        return ctx.dst->vec4<f32>(
-            ctx.dst->Call("unpack2x16float", load_u32()),
-            ctx.dst->Call("unpack2x16float", load_next_u32()));
-    }
-
-    TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-        << "format " << static_cast<int>(format);
-    return nullptr;
-  }
-
-  /// Generates an expression reading an aligned basic type (u32, i32, f32) from
-  /// a vertex buffer.
-  /// @param array_base the symbol of the variable holding the base array offset
-  /// of the vertex array (each index is 4-bytes).
-  /// @param offset the byte offset of the data from `buffer_base`
-  /// @param buffer the index of the vertex buffer
-  /// @param format VertexFormat::kUint32, VertexFormat::kSint32 or
-  /// VertexFormat::kFloat32
-  const ast::Expression* LoadPrimitive(Symbol array_base,
-                                       uint32_t offset,
-                                       uint32_t buffer,
-                                       VertexFormat format) {
-    const ast::Expression* u32 = nullptr;
-    if ((offset & 3) == 0) {
-      // Aligned load.
-
-      const ast ::Expression* index = nullptr;
-      if (offset > 0) {
-        index = ctx.dst->Add(array_base, offset / 4);
-      } else {
-        index = ctx.dst->Expr(array_base);
-      }
-      u32 = ctx.dst->IndexAccessor(
-          ctx.dst->MemberAccessor(GetVertexBufferName(buffer),
-                                  GetStructBufferName()),
-          index);
-
-    } else {
-      // Unaligned load
-      uint32_t offset_aligned = offset & ~3u;
-      auto* low = LoadPrimitive(array_base, offset_aligned, buffer,
-                                VertexFormat::kUint32);
-      auto* high = LoadPrimitive(array_base, offset_aligned + 4u, buffer,
-                                 VertexFormat::kUint32);
-
-      uint32_t shift = 8u * (offset & 3u);
-
-      auto* low_shr = ctx.dst->Shr(low, shift);
-      auto* high_shl = ctx.dst->Shl(high, 32u - shift);
-      u32 = ctx.dst->Or(low_shr, high_shl);
-    }
-
-    switch (format) {
-      case VertexFormat::kUint32:
-        return u32;
-      case VertexFormat::kSint32:
-        return ctx.dst->Bitcast(ctx.dst->ty.i32(), u32);
-      case VertexFormat::kFloat32:
-        return ctx.dst->Bitcast(ctx.dst->ty.f32(), u32);
-      default:
-        break;
-    }
-    TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
-        << "invalid format for LoadPrimitive" << static_cast<int>(format);
-    return nullptr;
-  }
-
-  /// Generates an expression reading a vec2/3/4 from a vertex buffer.
-  /// @param array_base the symbol of the variable holding the base array offset
-  /// of the vertex array (each index is 4-bytes).
-  /// @param offset the byte offset of the data from `buffer_base`
-  /// @param buffer the index of the vertex buffer
-  /// @param element_stride stride between elements, in bytes
-  /// @param base_type underlying AST type
-  /// @param base_format underlying vertex format
-  /// @param count how many elements the vector has
-  const ast::Expression* LoadVec(Symbol array_base,
-                                 uint32_t offset,
-                                 uint32_t buffer,
-                                 uint32_t element_stride,
-                                 const ast::Type* base_type,
-                                 VertexFormat base_format,
-                                 uint32_t count) {
-    ast::ExpressionList expr_list;
-    for (uint32_t i = 0; i < count; ++i) {
-      // Offset read position by element_stride for each component
-      uint32_t primitive_offset = offset + element_stride * i;
-      expr_list.push_back(
-          LoadPrimitive(array_base, primitive_offset, buffer, base_format));
-    }
-
-    return ctx.dst->Construct(ctx.dst->create<ast::Vector>(base_type, count),
-                              std::move(expr_list));
-  }
-
-  /// Process a non-struct entry point parameter.
-  /// Generate function-scope variables for location parameters, and record
-  /// vertex_index and instance_index builtins if present.
-  /// @param func the entry point function
-  /// @param param the parameter to process
-  void ProcessNonStructParameter(const ast::Function* func,
-                                 const ast::Variable* param) {
-    if (auto* location =
-            ast::GetAttribute<ast::LocationAttribute>(param->attributes)) {
-      // Create a function-scope variable to replace the parameter.
-      auto func_var_sym = ctx.Clone(param->symbol);
-      auto* func_var_type = ctx.Clone(param->type);
-      auto* func_var = ctx.dst->Var(func_var_sym, func_var_type);
-      ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var));
-      // Capture mapping from location to the new variable.
-      LocationInfo info;
-      info.expr = [this, func_var]() { return ctx.dst->Expr(func_var); };
-      info.type = ctx.src->Sem().Get(param)->Type();
-      location_info[location->value] = info;
-    } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
-                   param->attributes)) {
-      // Check for existing vertex_index and instance_index builtins.
-      if (builtin->builtin == ast::Builtin::kVertexIndex) {
-        vertex_index_expr = [this, param]() {
-          return ctx.dst->Expr(ctx.Clone(param->symbol));
-        };
-      } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
-        instance_index_expr = [this, param]() {
-          return ctx.dst->Expr(ctx.Clone(param->symbol));
-        };
-      }
-      new_function_parameters.push_back(ctx.Clone(param));
-    } else {
-      TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "Invalid entry point parameter";
-    }
-  }
-
-  /// Process a struct entry point parameter.
-  /// If the struct has members with location attributes, push the parameter to
-  /// a function-scope variable and create a new struct parameter without those
-  /// attributes. Record expressions for members that are vertex_index and
-  /// instance_index builtins.
-  /// @param func the entry point function
-  /// @param param the parameter to process
-  /// @param struct_ty the structure type
-  void ProcessStructParameter(const ast::Function* func,
-                              const ast::Variable* param,
-                              const ast::Struct* struct_ty) {
-    auto param_sym = ctx.Clone(param->symbol);
-
-    // Process the struct members.
-    bool has_locations = false;
-    ast::StructMemberList members_to_clone;
-    for (auto* member : struct_ty->members) {
-      auto member_sym = ctx.Clone(member->symbol);
-      std::function<const ast::Expression*()> member_expr = [this, param_sym,
-                                                             member_sym]() {
-        return ctx.dst->MemberAccessor(param_sym, member_sym);
-      };
-
-      if (auto* location =
-              ast::GetAttribute<ast::LocationAttribute>(member->attributes)) {
-        // Capture mapping from location to struct member.
-        LocationInfo info;
-        info.expr = member_expr;
-        info.type = ctx.src->Sem().Get(member)->Type();
-        location_info[location->value] = info;
-        has_locations = true;
-      } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
-                     member->attributes)) {
-        // Check for existing vertex_index and instance_index builtins.
-        if (builtin->builtin == ast::Builtin::kVertexIndex) {
-          vertex_index_expr = member_expr;
-        } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
-          instance_index_expr = member_expr;
-        }
-        members_to_clone.push_back(member);
-      } else {
-        TINT_ICE(Transform, ctx.dst->Diagnostics())
-            << "Invalid entry point parameter";
-      }
-    }
-
-    if (!has_locations) {
-      // Nothing to do.
-      new_function_parameters.push_back(ctx.Clone(param));
-      return;
-    }
-
-    // Create a function-scope variable to replace the parameter.
-    auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->type));
-    ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var));
-
-    if (!members_to_clone.empty()) {
-      // Create a new struct without the location attributes.
-      ast::StructMemberList new_members;
-      for (auto* member : members_to_clone) {
-        auto member_sym = ctx.Clone(member->symbol);
-        auto* member_type = ctx.Clone(member->type);
-        auto member_attrs = ctx.Clone(member->attributes);
-        new_members.push_back(
-            ctx.dst->Member(member_sym, member_type, std::move(member_attrs)));
-      }
-      auto* new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members);
-
-      // Create a new function parameter with this struct.
-      auto* new_param =
-          ctx.dst->Param(ctx.dst->Sym(), ctx.dst->ty.Of(new_struct));
-      new_function_parameters.push_back(new_param);
-
-      // Copy values from the new parameter to the function-scope variable.
-      for (auto* member : members_to_clone) {
-        auto member_name = ctx.Clone(member->symbol);
-        ctx.InsertFront(
-            func->body->statements,
-            ctx.dst->Assign(ctx.dst->MemberAccessor(func_var, member_name),
-                            ctx.dst->MemberAccessor(new_param, member_name)));
-      }
-    }
-  }
-
-  /// Process an entry point function.
-  /// @param func the entry point function
-  void Process(const ast::Function* func) {
-    if (func->body->Empty()) {
-      return;
-    }
-
-    // Process entry point parameters.
-    for (auto* param : func->params) {
-      auto* sem = ctx.src->Sem().Get(param);
-      if (auto* str = sem->Type()->As<sem::Struct>()) {
-        ProcessStructParameter(func, param, str->Declaration());
-      } else {
-        ProcessNonStructParameter(func, param);
-      }
-    }
-
-    // Insert new parameters for vertex_index and instance_index if needed.
-    if (!vertex_index_expr) {
-      for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
-        if (layout.step_mode == VertexStepMode::kVertex) {
-          auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index");
-          new_function_parameters.push_back(
-              ctx.dst->Param(name, ctx.dst->ty.u32(),
-                             {ctx.dst->Builtin(ast::Builtin::kVertexIndex)}));
-          vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); };
-          break;
-        }
-      }
-    }
-    if (!instance_index_expr) {
-      for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
-        if (layout.step_mode == VertexStepMode::kInstance) {
-          auto name = ctx.dst->Symbols().New("tint_pulling_instance_index");
-          new_function_parameters.push_back(
-              ctx.dst->Param(name, ctx.dst->ty.u32(),
-                             {ctx.dst->Builtin(ast::Builtin::kInstanceIndex)}));
-          instance_index_expr = [this, name]() { return ctx.dst->Expr(name); };
-          break;
-        }
-      }
-    }
-
-    // Generate vertex pulling preamble.
-    if (auto* block = CreateVertexPullingPreamble()) {
-      ctx.InsertFront(func->body->statements, block);
-    }
-
-    // Rewrite the function header with the new parameters.
-    auto func_sym = ctx.Clone(func->symbol);
-    auto* ret_type = ctx.Clone(func->return_type);
-    auto* body = ctx.Clone(func->body);
-    auto attrs = ctx.Clone(func->attributes);
-    auto ret_attrs = ctx.Clone(func->return_type_attributes);
-    auto* new_func = ctx.dst->create<ast::Function>(
-        func->source, func_sym, new_function_parameters, ret_type, body,
-        std::move(attrs), std::move(ret_attrs));
-    ctx.Replace(func, new_func);
-  }
-};
-
-}  // namespace
-
-VertexPulling::VertexPulling() = default;
-VertexPulling::~VertexPulling() = default;
-
-void VertexPulling::Run(CloneContext& ctx,
-                        const DataMap& inputs,
-                        DataMap&) const {
-  auto cfg = cfg_;
-  if (auto* cfg_data = inputs.Get<Config>()) {
-    cfg = *cfg_data;
-  }
-
-  // Find entry point
-  auto* func = ctx.src->AST().Functions().Find(
-      ctx.src->Symbols().Get(cfg.entry_point_name),
-      ast::PipelineStage::kVertex);
-  if (func == nullptr) {
-    ctx.dst->Diagnostics().add_error(diag::System::Transform,
-                                     "Vertex stage entry point not found");
-    return;
-  }
-
-  // TODO(idanr): Need to check shader locations in descriptor cover all
-  // attributes
-
-  // TODO(idanr): Make sure we covered all error cases, to guarantee the
-  // following stages will pass
-
-  State state{ctx, cfg};
-  state.AddVertexStorageBuffers();
-  state.Process(func);
-
-  ctx.Clone();
-}
-
-VertexPulling::Config::Config() = default;
-VertexPulling::Config::Config(const Config&) = default;
-VertexPulling::Config::~Config() = default;
-VertexPulling::Config& VertexPulling::Config::operator=(const Config&) =
-    default;
-
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default;
-
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
-    uint32_t in_array_stride,
-    VertexStepMode in_step_mode,
-    std::vector<VertexAttributeDescriptor> in_attributes)
-    : array_stride(in_array_stride),
-      step_mode(in_step_mode),
-      attributes(std::move(in_attributes)) {}
-
-VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
-    const VertexBufferLayoutDescriptor& other) = default;
-
-VertexBufferLayoutDescriptor& VertexBufferLayoutDescriptor::operator=(
-    const VertexBufferLayoutDescriptor& other) = default;
-
-VertexBufferLayoutDescriptor::~VertexBufferLayoutDescriptor() = default;
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/vertex_pulling.h b/src/transform/vertex_pulling.h
deleted file mode 100644
index 2bd45c2..0000000
--- a/src/transform/vertex_pulling.h
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2020 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_TRANSFORM_VERTEX_PULLING_H_
-#define SRC_TRANSFORM_VERTEX_PULLING_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// Describes the format of data in a vertex buffer
-enum class VertexFormat {
-  kUint8x2,    // uint8x2
-  kUint8x4,    // uint8x4
-  kSint8x2,    // sint8x2
-  kSint8x4,    // sint8x4
-  kUnorm8x2,   // unorm8x2
-  kUnorm8x4,   // unorm8x4
-  kSnorm8x2,   // snorm8x2
-  kSnorm8x4,   // snorm8x4
-  kUint16x2,   // uint16x2
-  kUint16x4,   // uint16x4
-  kSint16x2,   // sint16x2
-  kSint16x4,   // sint16x4
-  kUnorm16x2,  // unorm16x2
-  kUnorm16x4,  // unorm16x4
-  kSnorm16x2,  // snorm16x2
-  kSnorm16x4,  // snorm16x4
-  kFloat16x2,  // float16x2
-  kFloat16x4,  // float16x4
-  kFloat32,    // float32
-  kFloat32x2,  // float32x2
-  kFloat32x3,  // float32x3
-  kFloat32x4,  // float32x4
-  kUint32,     // uint32
-  kUint32x2,   // uint32x2
-  kUint32x3,   // uint32x3
-  kUint32x4,   // uint32x4
-  kSint32,     // sint32
-  kSint32x2,   // sint32x2
-  kSint32x3,   // sint32x3
-  kSint32x4,   // sint32x4
-
-  kLastEntry = kSint32x4,
-};
-
-/// Describes if a vertex attributes increments with vertex index or instance
-/// index
-enum class VertexStepMode { kVertex, kInstance, kLastEntry = kInstance };
-
-/// Describes a vertex attribute within a buffer
-struct VertexAttributeDescriptor {
-  /// The format of the attribute
-  VertexFormat format;
-  /// The byte offset of the attribute in the buffer
-  uint32_t offset;
-  /// The shader location used for the attribute
-  uint32_t shader_location;
-};
-
-/// Describes a buffer containing multiple vertex attributes
-struct VertexBufferLayoutDescriptor {
-  /// Constructor
-  VertexBufferLayoutDescriptor();
-  /// Constructor
-  /// @param in_array_stride the array stride of the in buffer
-  /// @param in_step_mode the step mode of the in buffer
-  /// @param in_attributes the in attributes
-  VertexBufferLayoutDescriptor(
-      uint32_t in_array_stride,
-      VertexStepMode in_step_mode,
-      std::vector<VertexAttributeDescriptor> in_attributes);
-  /// Copy constructor
-  /// @param other the struct to copy
-  VertexBufferLayoutDescriptor(const VertexBufferLayoutDescriptor& other);
-
-  /// Assignment operator
-  /// @param other the struct to copy
-  /// @returns this struct
-  VertexBufferLayoutDescriptor& operator=(
-      const VertexBufferLayoutDescriptor& other);
-
-  ~VertexBufferLayoutDescriptor();
-
-  /// The array stride used in the in buffer
-  uint32_t array_stride = 0u;
-  /// The input step mode used
-  VertexStepMode step_mode = VertexStepMode::kVertex;
-  /// The vertex attributes
-  std::vector<VertexAttributeDescriptor> attributes;
-};
-
-/// Describes vertex state, which consists of many buffers containing vertex
-/// attributes
-using VertexStateDescriptor = std::vector<VertexBufferLayoutDescriptor>;
-
-/// Converts a program to use vertex pulling
-///
-/// Variables which accept vertex input are var<in> with a location attribute.
-/// This transform will convert those to be assigned from storage buffers
-/// instead. The intention is to allow vertex input to rely on a storage buffer
-/// clamping pass for out of bounds reads. We bind the storage buffers as arrays
-/// of u32, so any read to byte position `p` will actually need to read position
-/// `p / 4`, since `sizeof(u32) == 4`.
-///
-/// `VertexFormat` represents the input type of the attribute. This isn't
-/// related to the type of the variable in the shader. For example,
-/// `VertexFormat::kVec2F16` tells us that the buffer will contain `f16`
-/// elements, to be read as vec2. In the shader, a user would make a `vec2<f32>`
-/// to be able to use them. The conversion between `f16` and `f32` will need to
-/// be handled by us (using unpack functions).
-///
-/// To be clear, there won't be types such as `f16` or `u8` anywhere in WGSL
-/// code, but these are types that the data may arrive as. We need to convert
-/// these smaller types into the base types such as `f32` and `u32` for the
-/// shader to use.
-class VertexPulling : public Castable<VertexPulling, Transform> {
- public:
-  /// Configuration options for the transform
-  struct Config : public Castable<Config, Data> {
-    /// Constructor
-    Config();
-
-    /// Copy constructor
-    Config(const Config&);
-
-    /// Destructor
-    ~Config() override;
-
-    /// Assignment operator
-    /// @returns this Config
-    Config& operator=(const Config&);
-
-    /// The entry point to add assignments into
-    std::string entry_point_name;
-
-    /// The vertex state descriptor, containing info about attributes
-    VertexStateDescriptor vertex_state;
-
-    /// The "group" we will put all our vertex buffers into (as storage buffers)
-    /// Default to 4 as it is past the limits of user-accessible groups
-    uint32_t pulling_group = 4u;
-  };
-
-  /// Constructor
-  VertexPulling();
-
-  /// Destructor
-  ~VertexPulling() override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  Config cfg_;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_VERTEX_PULLING_H_
diff --git a/src/transform/vertex_pulling_test.cc b/src/transform/vertex_pulling_test.cc
deleted file mode 100644
index 8c551ec..0000000
--- a/src/transform/vertex_pulling_test.cc
+++ /dev/null
@@ -1,1290 +0,0 @@
-// Copyright 2020 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/transform/vertex_pulling.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using VertexPullingTest = TransformTest;
-
-TEST_F(VertexPullingTest, Error_NoEntryPoint) {
-  auto* src = "";
-
-  auto* expect = "error: Vertex stage entry point not found";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>();
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, Error_InvalidEntryPoint) {
-  auto* src = R"(
-@stage(vertex)
-fn main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = "error: Vertex stage entry point not found";
-
-  VertexPulling::Config cfg;
-  cfg.entry_point_name = "_";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, Error_EntryPointWrongStage) {
-  auto* src = R"(
-@stage(fragment)
-fn main() {}
-)";
-
-  auto* expect = "error: Vertex stage entry point not found";
-
-  VertexPulling::Config cfg;
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, Error_BadStride) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect =
-      "error: WebGPU requires that vertex stride must be a multiple of 4 "
-      "bytes, but VertexPulling array stride for buffer 0 was 15 bytes";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{15, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, BasicModule) {
-  auto* src = R"(
-@stage(vertex)
-fn main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@stage(vertex)
-fn main() -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, OneAttribute) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  {
-    let buffer_array_base_0 = tint_pulling_vertex_index;
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-  }
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, OneInstancedAttribute) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(instance_index) tint_pulling_instance_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  {
-    let buffer_array_base_0 = tint_pulling_instance_index;
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-  }
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{4, VertexStepMode::kInstance, {{VertexFormat::kFloat32, 0, 0}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, OneAttributeDifferentOutputSet) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(5) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  {
-    let buffer_array_base_0 = tint_pulling_vertex_index;
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-  }
-  return vec4<f32>(var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
-  cfg.pulling_group = 5;
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, OneAttribute_Struct) {
-  auto* src = R"(
-struct Inputs {
-  @location(0) var_a : f32;
-};
-
-@stage(vertex)
-fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(inputs.var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-struct Inputs {
-  @location(0)
-  var_a : f32;
-}
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var inputs : Inputs;
-  {
-    let buffer_array_base_0 = tint_pulling_vertex_index;
-    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-  }
-  return vec4<f32>(inputs.var_a, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{4, VertexStepMode::kVertex, {{VertexFormat::kFloat32, 0, 0}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-// We expect the transform to use an existing builtin variables if it finds them
-TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32,
-        @location(1) var_b : f32,
-        @builtin(vertex_index) custom_vertex_index : u32,
-        @builtin(instance_index) custom_instance_index : u32
-        ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(var_a, var_b, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) custom_vertex_index : u32, @builtin(instance_index) custom_instance_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  var var_b : f32;
-  {
-    let buffer_array_base_0 = custom_vertex_index;
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    let buffer_array_base_1 = custom_instance_index;
-    var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
-  }
-  return vec4<f32>(var_a, var_b, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {
-          4,
-          VertexStepMode::kVertex,
-          {{VertexFormat::kFloat32, 0, 0}},
-      },
-      {
-          4,
-          VertexStepMode::kInstance,
-          {{VertexFormat::kFloat32, 0, 1}},
-      },
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex_Struct) {
-  auto* src = R"(
-struct Inputs {
-  @location(0) var_a : f32;
-  @location(1) var_b : f32;
-  @builtin(vertex_index) custom_vertex_index : u32;
-  @builtin(instance_index) custom_instance_index : u32;
-};
-
-@stage(vertex)
-fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-struct tint_symbol {
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-
-struct Inputs {
-  @location(0)
-  var_a : f32;
-  @location(1)
-  var_b : f32;
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-
-@stage(vertex)
-fn main(tint_symbol_1 : tint_symbol) -> @builtin(position) vec4<f32> {
-  var inputs : Inputs;
-  inputs.custom_vertex_index = tint_symbol_1.custom_vertex_index;
-  inputs.custom_instance_index = tint_symbol_1.custom_instance_index;
-  {
-    let buffer_array_base_0 = inputs.custom_vertex_index;
-    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    let buffer_array_base_1 = inputs.custom_instance_index;
-    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
-  }
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {
-          4,
-          VertexStepMode::kVertex,
-          {{VertexFormat::kFloat32, 0, 0}},
-      },
-      {
-          4,
-          VertexStepMode::kInstance,
-          {{VertexFormat::kFloat32, 0, 1}},
-      },
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest,
-       ExistingVertexIndexAndInstanceIndex_Struct_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn main(inputs : Inputs) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-
-struct Inputs {
-  @location(0) var_a : f32;
-  @location(1) var_b : f32;
-  @builtin(vertex_index) custom_vertex_index : u32;
-  @builtin(instance_index) custom_instance_index : u32;
-};
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-struct tint_symbol {
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-
-@stage(vertex)
-fn main(tint_symbol_1 : tint_symbol) -> @builtin(position) vec4<f32> {
-  var inputs : Inputs;
-  inputs.custom_vertex_index = tint_symbol_1.custom_vertex_index;
-  inputs.custom_instance_index = tint_symbol_1.custom_instance_index;
-  {
-    let buffer_array_base_0 = inputs.custom_vertex_index;
-    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    let buffer_array_base_1 = inputs.custom_instance_index;
-    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
-  }
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-
-struct Inputs {
-  @location(0)
-  var_a : f32;
-  @location(1)
-  var_b : f32;
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {
-          4,
-          VertexStepMode::kVertex,
-          {{VertexFormat::kFloat32, 0, 0}},
-      },
-      {
-          4,
-          VertexStepMode::kInstance,
-          {{VertexFormat::kFloat32, 0, 1}},
-      },
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, ExistingVertexIndexAndInstanceIndex_SeparateStruct) {
-  auto* src = R"(
-struct Inputs {
-  @location(0) var_a : f32;
-  @location(1) var_b : f32;
-};
-
-struct Indices {
-  @builtin(vertex_index) custom_vertex_index : u32;
-  @builtin(instance_index) custom_instance_index : u32;
-};
-
-@stage(vertex)
-fn main(inputs : Inputs, indices : Indices) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-struct Inputs {
-  @location(0)
-  var_a : f32;
-  @location(1)
-  var_b : f32;
-}
-
-struct Indices {
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-
-@stage(vertex)
-fn main(indices : Indices) -> @builtin(position) vec4<f32> {
-  var inputs : Inputs;
-  {
-    let buffer_array_base_0 = indices.custom_vertex_index;
-    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    let buffer_array_base_1 = indices.custom_instance_index;
-    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
-  }
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {
-          4,
-          VertexStepMode::kVertex,
-          {{VertexFormat::kFloat32, 0, 0}},
-      },
-      {
-          4,
-          VertexStepMode::kInstance,
-          {{VertexFormat::kFloat32, 0, 1}},
-      },
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest,
-       ExistingVertexIndexAndInstanceIndex_SeparateStruct_OutOfOrder) {
-  auto* src = R"(
-@stage(vertex)
-fn main(inputs : Inputs, indices : Indices) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-
-struct Inputs {
-  @location(0) var_a : f32;
-  @location(1) var_b : f32;
-};
-
-struct Indices {
-  @builtin(vertex_index) custom_vertex_index : u32;
-  @builtin(instance_index) custom_instance_index : u32;
-};
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-@stage(vertex)
-fn main(indices : Indices) -> @builtin(position) vec4<f32> {
-  var inputs : Inputs;
-  {
-    let buffer_array_base_0 = indices.custom_vertex_index;
-    inputs.var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    let buffer_array_base_1 = indices.custom_instance_index;
-    inputs.var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]);
-  }
-  return vec4<f32>(inputs.var_a, inputs.var_b, 0.0, 1.0);
-}
-
-struct Inputs {
-  @location(0)
-  var_a : f32;
-  @location(1)
-  var_b : f32;
-}
-
-struct Indices {
-  @builtin(vertex_index)
-  custom_vertex_index : u32;
-  @builtin(instance_index)
-  custom_instance_index : u32;
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {
-          4,
-          VertexStepMode::kVertex,
-          {{VertexFormat::kFloat32, 0, 0}},
-      },
-      {
-          4,
-          VertexStepMode::kInstance,
-          {{VertexFormat::kFloat32, 0, 1}},
-      },
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, TwoAttributesSameBuffer) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32,
-        @location(1) var_b : vec4<f32>) -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  var var_b : vec4<f32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index * 4u);
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]);
-    var_b = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 3u)]));
-  }
-  return vec4<f32>();
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{16,
-        VertexStepMode::kVertex,
-        {{VertexFormat::kFloat32, 0, 0}, {VertexFormat::kFloat32x4, 0, 1}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, FloatVectorAttributes) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : vec2<f32>,
-        @location(1) var_b : vec3<f32>,
-        @location(2) var_c : vec4<f32>
-        ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
-
-@binding(2) @group(4) var<storage, read> tint_pulling_vertex_buffer_2 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var var_a : vec2<f32>;
-  var var_b : vec3<f32>;
-  var var_c : vec4<f32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index * 2u);
-    var_a = vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 1u)]));
-    let buffer_array_base_1 = (tint_pulling_vertex_index * 3u);
-    var_b = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 2u)]));
-    let buffer_array_base_2 = (tint_pulling_vertex_index * 4u);
-    var_c = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[buffer_array_base_2]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 3u)]));
-  }
-  return vec4<f32>();
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{
-      {8, VertexStepMode::kVertex, {{VertexFormat::kFloat32x2, 0, 0}}},
-      {12, VertexStepMode::kVertex, {{VertexFormat::kFloat32x3, 0, 1}}},
-      {16, VertexStepMode::kVertex, {{VertexFormat::kFloat32x4, 0, 2}}},
-  }};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, AttemptSymbolCollision) {
-  auto* src = R"(
-@stage(vertex)
-fn main(@location(0) var_a : f32,
-        @location(1) var_b : vec4<f32>) -> @builtin(position) vec4<f32> {
-  var tint_pulling_vertex_index : i32;
-  var tint_pulling_vertex_buffer_0 : i32;
-  var tint_vertex_data : i32;
-  var tint_pulling_pos : i32;
-  return vec4<f32>();
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data_1 : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0_1 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index_1 : u32) -> @builtin(position) vec4<f32> {
-  var var_a : f32;
-  var var_b : vec4<f32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index_1 * 4u);
-    var_a = bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[buffer_array_base_0]);
-    var_b = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 2u)]), bitcast<f32>(tint_pulling_vertex_buffer_0_1.tint_vertex_data_1[(buffer_array_base_0 + 3u)]));
-  }
-  var tint_pulling_vertex_index : i32;
-  var tint_pulling_vertex_buffer_0 : i32;
-  var tint_vertex_data : i32;
-  var tint_pulling_pos : i32;
-  return vec4<f32>();
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {
-      {{16,
-        VertexStepMode::kVertex,
-        {{VertexFormat::kFloat32, 0, 0}, {VertexFormat::kFloat32x4, 0, 1}}}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, std::move(data));
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, FormatsAligned) {
-  auto* src = R"(
-@stage(vertex)
-fn main(
-    @location(0) uint8x2 : vec2<u32>,
-    @location(1) uint8x4 : vec4<u32>,
-    @location(2) sint8x2 : vec2<i32>,
-    @location(3) sint8x4 : vec4<i32>,
-    @location(4) unorm8x2 : vec2<f32>,
-    @location(5) unorm8x4 : vec4<f32>,
-    @location(6) snorm8x2 : vec2<f32>,
-    @location(7) snorm8x4 : vec4<f32>,
-    @location(8) uint16x2 : vec2<u32>,
-    @location(9) uint16x4 : vec4<u32>,
-    @location(10) sint16x2 : vec2<i32>,
-    @location(11) sint16x4 : vec4<i32>,
-    @location(12) unorm16x2 : vec2<f32>,
-    @location(13) unorm16x4 : vec4<f32>,
-    @location(14) snorm16x2 : vec2<f32>,
-    @location(15) snorm16x4 : vec4<f32>,
-    @location(16) float16x2 : vec2<f32>,
-    @location(17) float16x4 : vec4<f32>,
-    @location(18) float32 : f32,
-    @location(19) float32x2 : vec2<f32>,
-    @location(20) float32x3 : vec3<f32>,
-    @location(21) float32x4 : vec4<f32>,
-    @location(22) uint32 : u32,
-    @location(23) uint32x2 : vec2<u32>,
-    @location(24) uint32x3 : vec3<u32>,
-    @location(25) uint32x4 : vec4<u32>,
-    @location(26) sint32 : i32,
-    @location(27) sint32x2 : vec2<i32>,
-    @location(28) sint32x3 : vec3<i32>,
-    @location(29) sint32x4 : vec4<i32>
-  ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var uint8x2 : vec2<u32>;
-  var uint8x4 : vec4<u32>;
-  var sint8x2 : vec2<i32>;
-  var sint8x4 : vec4<i32>;
-  var unorm8x2 : vec2<f32>;
-  var unorm8x4 : vec4<f32>;
-  var snorm8x2 : vec2<f32>;
-  var snorm8x4 : vec4<f32>;
-  var uint16x2 : vec2<u32>;
-  var uint16x4 : vec4<u32>;
-  var sint16x2 : vec2<i32>;
-  var sint16x4 : vec4<i32>;
-  var unorm16x2 : vec2<f32>;
-  var unorm16x4 : vec4<f32>;
-  var snorm16x2 : vec2<f32>;
-  var snorm16x4 : vec4<f32>;
-  var float16x2 : vec2<f32>;
-  var float16x4 : vec4<f32>;
-  var float32 : f32;
-  var float32x2 : vec2<f32>;
-  var float32x3 : vec3<f32>;
-  var float32x4 : vec4<f32>;
-  var uint32 : u32;
-  var uint32x2 : vec2<u32>;
-  var uint32x3 : vec3<u32>;
-  var uint32x4 : vec4<u32>;
-  var sint32 : i32;
-  var sint32x2 : vec2<i32>;
-  var sint32x3 : vec3<i32>;
-  var sint32x4 : vec4<i32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
-    uint8x2 = ((vec2<u32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
-    uint8x4 = ((vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
-    sint8x2 = ((vec2<i32>(bitcast<i32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
-    sint8x4 = ((vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
-    unorm8x2 = unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy;
-    unorm8x4 = unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    snorm8x2 = unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy;
-    snorm8x4 = unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    uint16x2 = ((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
-    uint16x4 = ((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
-    sint16x2 = ((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
-    sint16x4 = ((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
-    unorm16x2 = unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    unorm16x4 = vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
-    snorm16x2 = unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    snorm16x4 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
-    float16x2 = unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    float16x4 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
-    float32 = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    float32x2 = vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
-    float32x3 = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]));
-    float32x4 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]));
-    uint32 = tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)];
-    uint32x2 = vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]);
-    uint32x3 = vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]);
-    uint32x4 = vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]);
-    sint32 = bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]);
-    sint32x2 = vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]));
-    sint32x3 = vec3<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]));
-    sint32x4 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]));
-  }
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{{256,
-                        VertexStepMode::kVertex,
-                        {
-                            {VertexFormat::kUint8x2, 64, 0},
-                            {VertexFormat::kUint8x4, 64, 1},
-                            {VertexFormat::kSint8x2, 64, 2},
-                            {VertexFormat::kSint8x4, 64, 3},
-                            {VertexFormat::kUnorm8x2, 64, 4},
-                            {VertexFormat::kUnorm8x4, 64, 5},
-                            {VertexFormat::kSnorm8x2, 64, 6},
-                            {VertexFormat::kSnorm8x4, 64, 7},
-                            {VertexFormat::kUint16x2, 64, 8},
-                            {VertexFormat::kUint16x4, 64, 9},
-                            {VertexFormat::kSint16x2, 64, 10},
-                            {VertexFormat::kSint16x4, 64, 11},
-                            {VertexFormat::kUnorm16x2, 64, 12},
-                            {VertexFormat::kUnorm16x4, 64, 13},
-                            {VertexFormat::kSnorm16x2, 64, 14},
-                            {VertexFormat::kSnorm16x4, 64, 15},
-                            {VertexFormat::kFloat16x2, 64, 16},
-                            {VertexFormat::kFloat16x4, 64, 17},
-                            {VertexFormat::kFloat32, 64, 18},
-                            {VertexFormat::kFloat32x2, 64, 19},
-                            {VertexFormat::kFloat32x3, 64, 20},
-                            {VertexFormat::kFloat32x4, 64, 21},
-                            {VertexFormat::kUint32, 64, 22},
-                            {VertexFormat::kUint32x2, 64, 23},
-                            {VertexFormat::kUint32x3, 64, 24},
-                            {VertexFormat::kUint32x4, 64, 25},
-                            {VertexFormat::kSint32, 64, 26},
-                            {VertexFormat::kSint32x2, 64, 27},
-                            {VertexFormat::kSint32x3, 64, 28},
-                            {VertexFormat::kSint32x4, 64, 29},
-                        }}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, FormatsStrideUnaligned) {
-  auto* src = R"(
-@stage(vertex)
-fn main(
-    @location(0) uint8x2 : vec2<u32>,
-    @location(1) uint8x4 : vec4<u32>,
-    @location(2) sint8x2 : vec2<i32>,
-    @location(3) sint8x4 : vec4<i32>,
-    @location(4) unorm8x2 : vec2<f32>,
-    @location(5) unorm8x4 : vec4<f32>,
-    @location(6) snorm8x2 : vec2<f32>,
-    @location(7) snorm8x4 : vec4<f32>,
-    @location(8) uint16x2 : vec2<u32>,
-    @location(9) uint16x4 : vec4<u32>,
-    @location(10) sint16x2 : vec2<i32>,
-    @location(11) sint16x4 : vec4<i32>,
-    @location(12) unorm16x2 : vec2<f32>,
-    @location(13) unorm16x4 : vec4<f32>,
-    @location(14) snorm16x2 : vec2<f32>,
-    @location(15) snorm16x4 : vec4<f32>,
-    @location(16) float16x2 : vec2<f32>,
-    @location(17) float16x4 : vec4<f32>,
-    @location(18) float32 : f32,
-    @location(19) float32x2 : vec2<f32>,
-    @location(20) float32x3 : vec3<f32>,
-    @location(21) float32x4 : vec4<f32>,
-    @location(22) uint32 : u32,
-    @location(23) uint32x2 : vec2<u32>,
-    @location(24) uint32x3 : vec3<u32>,
-    @location(25) uint32x4 : vec4<u32>,
-    @location(26) sint32 : i32,
-    @location(27) sint32x2 : vec2<i32>,
-    @location(28) sint32x3 : vec3<i32>,
-    @location(29) sint32x4 : vec4<i32>
-  ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect =
-      R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var uint8x2 : vec2<u32>;
-  var uint8x4 : vec4<u32>;
-  var sint8x2 : vec2<i32>;
-  var sint8x4 : vec4<i32>;
-  var unorm8x2 : vec2<f32>;
-  var unorm8x4 : vec4<f32>;
-  var snorm8x2 : vec2<f32>;
-  var snorm8x4 : vec4<f32>;
-  var uint16x2 : vec2<u32>;
-  var uint16x4 : vec4<u32>;
-  var sint16x2 : vec2<i32>;
-  var sint16x4 : vec4<i32>;
-  var unorm16x2 : vec2<f32>;
-  var unorm16x4 : vec4<f32>;
-  var snorm16x2 : vec2<f32>;
-  var snorm16x4 : vec4<f32>;
-  var float16x2 : vec2<f32>;
-  var float16x4 : vec4<f32>;
-  var float32 : f32;
-  var float32x2 : vec2<f32>;
-  var float32x3 : vec3<f32>;
-  var float32x4 : vec4<f32>;
-  var uint32 : u32;
-  var uint32x2 : vec2<u32>;
-  var uint32x3 : vec3<u32>;
-  var uint32x4 : vec4<u32>;
-  var sint32 : i32;
-  var sint32x2 : vec2<i32>;
-  var sint32x3 : vec3<i32>;
-  var sint32x4 : vec4<i32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
-    uint8x2 = ((vec2<u32>((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 8u)) & 4294901760u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
-    uint8x4 = ((vec4<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
-    sint8x2 = ((vec2<i32>(bitcast<i32>((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 8u)) & 4294901760u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u));
-    sint8x4 = ((vec4<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)))) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u));
-    unorm8x2 = unpack4x8unorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy;
-    unorm8x4 = unpack4x8unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    snorm8x2 = unpack4x8snorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy;
-    snorm8x4 = unpack4x8snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    uint16x2 = ((vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
-    uint16x4 = ((vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
-    sint16x2 = ((vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)))) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u));
-    sint16x4 = ((vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u));
-    unorm16x2 = unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    unorm16x4 = vec4<f32>(unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
-    snorm16x2 = unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    snorm16x4 = vec4<f32>(unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
-    float16x2 = unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    float16x4 = vec4<f32>(unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
-    float32 = bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    float32x2 = vec2<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
-    float32x3 = vec3<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))));
-    float32x4 = vec4<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u))));
-    uint32 = ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u));
-    uint32x2 = vec2<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)));
-    uint32x3 = vec3<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u)));
-    uint32x4 = vec4<u32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u)), ((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u)));
-    sint32 = bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u)));
-    sint32x2 = vec2<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))));
-    sint32x3 = vec3<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))));
-    sint32x4 = vec4<i32>(bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))), bitcast<i32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u))));
-  }
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{{256,
-                        VertexStepMode::kVertex,
-                        {
-                            {VertexFormat::kUint8x2, 63, 0},
-                            {VertexFormat::kUint8x4, 63, 1},
-                            {VertexFormat::kSint8x2, 63, 2},
-                            {VertexFormat::kSint8x4, 63, 3},
-                            {VertexFormat::kUnorm8x2, 63, 4},
-                            {VertexFormat::kUnorm8x4, 63, 5},
-                            {VertexFormat::kSnorm8x2, 63, 6},
-                            {VertexFormat::kSnorm8x4, 63, 7},
-                            {VertexFormat::kUint16x2, 63, 8},
-                            {VertexFormat::kUint16x4, 63, 9},
-                            {VertexFormat::kSint16x2, 63, 10},
-                            {VertexFormat::kSint16x4, 63, 11},
-                            {VertexFormat::kUnorm16x2, 63, 12},
-                            {VertexFormat::kUnorm16x4, 63, 13},
-                            {VertexFormat::kSnorm16x2, 63, 14},
-                            {VertexFormat::kSnorm16x4, 63, 15},
-                            {VertexFormat::kFloat16x2, 63, 16},
-                            {VertexFormat::kFloat16x4, 63, 17},
-                            {VertexFormat::kFloat32, 63, 18},
-                            {VertexFormat::kFloat32x2, 63, 19},
-                            {VertexFormat::kFloat32x3, 63, 20},
-                            {VertexFormat::kFloat32x4, 63, 21},
-                            {VertexFormat::kUint32, 63, 22},
-                            {VertexFormat::kUint32x2, 63, 23},
-                            {VertexFormat::kUint32x3, 63, 24},
-                            {VertexFormat::kUint32x4, 63, 25},
-                            {VertexFormat::kSint32, 63, 26},
-                            {VertexFormat::kSint32x2, 63, 27},
-                            {VertexFormat::kSint32x3, 63, 28},
-                            {VertexFormat::kSint32x4, 63, 29},
-                        }}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(VertexPullingTest, FormatsWithVectorsResized) {
-  auto* src = R"(
-@stage(vertex)
-fn main(
-    @location(0) uint8x2 : vec3<u32>,
-    @location(1) uint8x4 : vec2<u32>,
-    @location(2) sint8x2 : i32,
-    @location(3) sint8x4 : vec2<i32>,
-    @location(4) unorm8x2 : vec4<f32>,
-    @location(5) unorm8x4 : f32,
-    @location(6) snorm8x2 : vec3<f32>,
-    @location(7) snorm8x4 : f32,
-    @location(8) uint16x2 : vec3<u32>,
-    @location(9) uint16x4 : vec2<u32>,
-    @location(10) sint16x2 : vec4<i32>,
-    @location(11) sint16x4 : i32,
-    @location(12) unorm16x2 : vec3<f32>,
-    @location(13) unorm16x4 : f32,
-    @location(14) snorm16x2 : vec4<f32>,
-    @location(15) snorm16x4 : vec3<f32>,
-    @location(16) float16x2 : vec4<f32>,
-    @location(17) float16x4 : f32,
-    @location(18) float32 : vec4<f32>,
-    @location(19) float32x2 : vec4<f32>,
-    @location(20) float32x3 : vec2<f32>,
-    @location(21) float32x4 : vec3<f32>,
-    @location(22) uint32 : vec3<u32>,
-    @location(23) uint32x2 : vec4<u32>,
-    @location(24) uint32x3 : vec4<u32>,
-    @location(25) uint32x4 : vec2<u32>,
-    @location(26) sint32 : vec4<i32>,
-    @location(27) sint32x2 : vec3<i32>,
-    @location(28) sint32x3 : i32,
-    @location(29) sint32x4 : vec2<i32>
-  ) -> @builtin(position) vec4<f32> {
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  auto* expect = R"(
-struct TintVertexData {
-  tint_vertex_data : @stride(4) array<u32>;
-}
-
-@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
-
-@stage(vertex)
-fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
-  var uint8x2 : vec3<u32>;
-  var uint8x4 : vec2<u32>;
-  var sint8x2 : i32;
-  var sint8x4 : vec2<i32>;
-  var unorm8x2 : vec4<f32>;
-  var unorm8x4 : f32;
-  var snorm8x2 : vec3<f32>;
-  var snorm8x4 : f32;
-  var uint16x2 : vec3<u32>;
-  var uint16x4 : vec2<u32>;
-  var sint16x2 : vec4<i32>;
-  var sint16x4 : i32;
-  var unorm16x2 : vec3<f32>;
-  var unorm16x4 : f32;
-  var snorm16x2 : vec4<f32>;
-  var snorm16x4 : vec3<f32>;
-  var float16x2 : vec4<f32>;
-  var float16x4 : f32;
-  var float32 : vec4<f32>;
-  var float32x2 : vec4<f32>;
-  var float32x3 : vec2<f32>;
-  var float32x4 : vec3<f32>;
-  var uint32 : vec3<u32>;
-  var uint32x2 : vec4<u32>;
-  var uint32x3 : vec4<u32>;
-  var uint32x4 : vec2<u32>;
-  var sint32 : vec4<i32>;
-  var sint32x2 : vec3<i32>;
-  var sint32x3 : i32;
-  var sint32x4 : vec2<i32>;
-  {
-    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
-    uint8x2 = vec3<u32>(((vec2<u32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u)) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u)), 0u);
-    uint8x4 = (((vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
-    sint8x2 = (((vec2<i32>(bitcast<i32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u))).x;
-    sint8x4 = (((vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
-    unorm8x2 = vec4<f32>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0, 1.0);
-    unorm8x4 = unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
-    snorm8x2 = vec3<f32>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0);
-    snorm8x4 = unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
-    uint16x2 = vec3<u32>(((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0u);
-    uint16x4 = (((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).xy;
-    sint16x2 = vec4<i32>(((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0, 1);
-    sint16x4 = (((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).x;
-    unorm16x2 = vec3<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0);
-    unorm16x4 = vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
-    snorm16x2 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
-    snorm16x4 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xyz;
-    float16x2 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
-    float16x4 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
-    float32 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 0.0, 1.0);
-    float32x2 = vec4<f32>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0.0, 1.0);
-    float32x3 = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])).xy;
-    float32x4 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])).xyz;
-    uint32 = vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], 0u, 0u);
-    uint32x2 = vec4<u32>(vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), 0u, 1u);
-    uint32x3 = vec4<u32>(vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), 1u);
-    uint32x4 = vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]).xy;
-    sint32 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0, 0, 1);
-    sint32x2 = vec3<i32>(vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0);
-    sint32x3 = vec3<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])).x;
-    sint32x4 = vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])).xy;
-  }
-  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
-}
-)";
-
-  VertexPulling::Config cfg;
-  cfg.vertex_state = {{{256,
-                        VertexStepMode::kVertex,
-                        {
-                            {VertexFormat::kUint8x2, 64, 0},
-                            {VertexFormat::kUint8x4, 64, 1},
-                            {VertexFormat::kSint8x2, 64, 2},
-                            {VertexFormat::kSint8x4, 64, 3},
-                            {VertexFormat::kUnorm8x2, 64, 4},
-                            {VertexFormat::kUnorm8x4, 64, 5},
-                            {VertexFormat::kSnorm8x2, 64, 6},
-                            {VertexFormat::kSnorm8x4, 64, 7},
-                            {VertexFormat::kUint16x2, 64, 8},
-                            {VertexFormat::kUint16x4, 64, 9},
-                            {VertexFormat::kSint16x2, 64, 10},
-                            {VertexFormat::kSint16x4, 64, 11},
-                            {VertexFormat::kUnorm16x2, 64, 12},
-                            {VertexFormat::kUnorm16x4, 64, 13},
-                            {VertexFormat::kSnorm16x2, 64, 14},
-                            {VertexFormat::kSnorm16x4, 64, 15},
-                            {VertexFormat::kFloat16x2, 64, 16},
-                            {VertexFormat::kFloat16x4, 64, 17},
-                            {VertexFormat::kFloat32, 64, 18},
-                            {VertexFormat::kFloat32x2, 64, 19},
-                            {VertexFormat::kFloat32x3, 64, 20},
-                            {VertexFormat::kFloat32x4, 64, 21},
-                            {VertexFormat::kUint32, 64, 22},
-                            {VertexFormat::kUint32x2, 64, 23},
-                            {VertexFormat::kUint32x3, 64, 24},
-                            {VertexFormat::kUint32x4, 64, 25},
-                            {VertexFormat::kSint32, 64, 26},
-                            {VertexFormat::kSint32x2, 64, 27},
-                            {VertexFormat::kSint32x3, 64, 28},
-                            {VertexFormat::kSint32x4, 64, 29},
-                        }}}};
-  cfg.entry_point_name = "main";
-
-  DataMap data;
-  data.Add<VertexPulling::Config>(cfg);
-  auto got = Run<VertexPulling>(src, data);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/wrap_arrays_in_structs.cc b/src/transform/wrap_arrays_in_structs.cc
deleted file mode 100644
index 938789f..0000000
--- a/src/transform/wrap_arrays_in_structs.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/wrap_arrays_in_structs.h"
-
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/array.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/type_constructor.h"
-#include "src/utils/map.h"
-#include "src/utils/transform.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::WrapArraysInStructs);
-
-namespace tint {
-namespace transform {
-
-WrapArraysInStructs::WrappedArrayInfo::WrappedArrayInfo() = default;
-WrapArraysInStructs::WrappedArrayInfo::WrappedArrayInfo(
-    const WrappedArrayInfo&) = default;
-WrapArraysInStructs::WrappedArrayInfo::~WrappedArrayInfo() = default;
-
-WrapArraysInStructs::WrapArraysInStructs() = default;
-
-WrapArraysInStructs::~WrapArraysInStructs() = default;
-
-bool WrapArraysInStructs::ShouldRun(const Program* program,
-                                    const DataMap&) const {
-  for (auto* node : program->ASTNodes().Objects()) {
-    if (program->Sem().Get<sem::Array>(node->As<ast::Type>())) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void WrapArraysInStructs::Run(CloneContext& ctx,
-                              const DataMap&,
-                              DataMap&) const {
-  auto& sem = ctx.src->Sem();
-
-  std::unordered_map<const sem::Array*, WrappedArrayInfo> wrapped_arrays;
-  auto wrapper = [&](const sem::Array* array) {
-    return WrapArray(ctx, wrapped_arrays, array);
-  };
-  auto wrapper_typename = [&](const sem::Array* arr) -> ast::TypeName* {
-    auto info = wrapper(arr);
-    return info ? ctx.dst->create<ast::TypeName>(info.wrapper_name) : nullptr;
-  };
-
-  // Replace all array types with their corresponding wrapper
-  ctx.ReplaceAll([&](const ast::Type* ast_type) -> const ast::Type* {
-    auto* type = ctx.src->TypeOf(ast_type);
-    if (auto* array = type->UnwrapRef()->As<sem::Array>()) {
-      return wrapper_typename(array);
-    }
-    return nullptr;
-  });
-
-  // Fix up index accessors so `a[1]` becomes `a.arr[1]`
-  ctx.ReplaceAll([&](const ast::IndexAccessorExpression* accessor)
-                     -> const ast::IndexAccessorExpression* {
-    if (auto* array = ::tint::As<sem::Array>(
-            sem.Get(accessor->object)->Type()->UnwrapRef())) {
-      if (wrapper(array)) {
-        // Array is wrapped in a structure. Emit a member accessor to get
-        // to the actual array.
-        auto* arr = ctx.Clone(accessor->object);
-        auto* idx = ctx.Clone(accessor->index);
-        auto* unwrapped = ctx.dst->MemberAccessor(arr, "arr");
-        return ctx.dst->IndexAccessor(accessor->source, unwrapped, idx);
-      }
-    }
-    return nullptr;
-  });
-
-  // Fix up array constructors so `A(1,2)` becomes `tint_array_wrapper(A(1,2))`
-  ctx.ReplaceAll(
-      [&](const ast::CallExpression* expr) -> const ast::Expression* {
-        if (auto* call = sem.Get(expr)) {
-          if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
-            if (auto* array = ctor->ReturnType()->As<sem::Array>()) {
-              if (auto w = wrapper(array)) {
-                // Wrap the array type constructor with another constructor for
-                // the wrapper
-                auto* wrapped_array_ty = ctx.dst->ty.type_name(w.wrapper_name);
-                auto* array_ty = w.array_type(ctx);
-                auto args = utils::Transform(
-                    call->Arguments(), [&](const tint::sem::Expression* s) {
-                      return ctx.Clone(s->Declaration());
-                    });
-                auto* arr_ctor = ctx.dst->Construct(array_ty, args);
-                return ctx.dst->Construct(wrapped_array_ty, arr_ctor);
-              }
-            }
-          }
-        }
-        return nullptr;
-      });
-
-  ctx.Clone();
-}
-
-WrapArraysInStructs::WrappedArrayInfo WrapArraysInStructs::WrapArray(
-    CloneContext& ctx,
-    std::unordered_map<const sem::Array*, WrappedArrayInfo>& wrapped_arrays,
-    const sem::Array* array) const {
-  if (array->IsRuntimeSized()) {
-    return {};  // We don't want to wrap runtime sized arrays
-  }
-
-  return utils::GetOrCreate(wrapped_arrays, array, [&] {
-    WrappedArrayInfo info;
-
-    // Generate a unique name for the array wrapper
-    info.wrapper_name = ctx.dst->Symbols().New("tint_array_wrapper");
-
-    // Examine the element type. Is it also an array?
-    std::function<const ast::Type*(CloneContext&)> el_type;
-    if (auto* el_array = array->ElemType()->As<sem::Array>()) {
-      // Array of array - call WrapArray() on the element type
-      if (auto el = WrapArray(ctx, wrapped_arrays, el_array)) {
-        el_type = [=](CloneContext& c) {
-          return c.dst->create<ast::TypeName>(el.wrapper_name);
-        };
-      }
-    }
-
-    // If the element wasn't an array, just create the typical AST type for it
-    if (!el_type) {
-      el_type = [=](CloneContext& c) {
-        return CreateASTTypeFor(c, array->ElemType());
-      };
-    }
-
-    // Construct the single structure field type
-    info.array_type = [=](CloneContext& c) {
-      ast::AttributeList attrs;
-      if (!array->IsStrideImplicit()) {
-        attrs.emplace_back(
-            c.dst->create<ast::StrideAttribute>(array->Stride()));
-      }
-      return c.dst->ty.array(el_type(c), array->Count(), std::move(attrs));
-    };
-
-    // Structure() will create and append the ast::Struct to the
-    // global declarations of `ctx.dst`. As we haven't finished building the
-    // current module-scope statement or function, this will be placed
-    // immediately before the usage.
-    ctx.dst->Structure(info.wrapper_name,
-                       {ctx.dst->Member("arr", info.array_type(ctx))});
-    return info;
-  });
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/wrap_arrays_in_structs.h b/src/transform/wrap_arrays_in_structs.h
deleted file mode 100644
index dfdc304..0000000
--- a/src/transform/wrap_arrays_in_structs.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
-#define SRC_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "src/transform/transform.h"
-
-// Forward declarations
-namespace tint {
-namespace ast {
-class Type;
-}  // namespace ast
-}  // namespace tint
-
-namespace tint {
-namespace transform {
-
-/// WrapArraysInStructs is a transform that replaces all array types with a
-/// structure holding a single field of that array type.
-/// Array index expressions and constructors are also adjusted to deal with this
-/// wrapping.
-/// This transform helps with backends that cannot directly return arrays or use
-/// them as parameters.
-class WrapArraysInStructs : public Castable<WrapArraysInStructs, Transform> {
- public:
-  /// Constructor
-  WrapArraysInStructs();
-
-  /// Destructor
-  ~WrapArraysInStructs() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  struct WrappedArrayInfo {
-    WrappedArrayInfo();
-    WrappedArrayInfo(const WrappedArrayInfo&);
-    ~WrappedArrayInfo();
-
-    Symbol wrapper_name;
-    std::function<const ast::Type*(CloneContext&)> array_type;
-
-    operator bool() { return wrapper_name.IsValid(); }
-  };
-
-  /// WrapArray wraps the fixed-size array type in a new structure (if it hasn't
-  /// already been wrapped). WrapArray will recursively wrap arrays-of-arrays.
-  /// The new structure will be added to module-scope type declarations of
-  /// `ctx.dst`.
-  /// @param ctx the CloneContext
-  /// @param wrapped_arrays a map of src array type to the wrapped structure
-  /// name
-  /// @param array the array type
-  /// @return the name of the structure that wraps the array, or an invalid
-  /// Symbol if this array should not be wrapped
-  WrappedArrayInfo WrapArray(
-      CloneContext& ctx,
-      std::unordered_map<const sem::Array*, WrappedArrayInfo>& wrapped_arrays,
-      const sem::Array* array) const;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_WRAP_ARRAYS_IN_STRUCTS_H_
diff --git a/src/transform/wrap_arrays_in_structs_test.cc b/src/transform/wrap_arrays_in_structs_test.cc
deleted file mode 100644
index c9daaf8..0000000
--- a/src/transform/wrap_arrays_in_structs_test.cc
+++ /dev/null
@@ -1,424 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/wrap_arrays_in_structs.h"
-
-#include <memory>
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using WrapArraysInStructsTest = TransformTest;
-
-TEST_F(WrapArraysInStructsTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<WrapArraysInStructs>(src));
-}
-
-TEST_F(WrapArraysInStructsTest, ShouldRunHasArray) {
-  auto* src = R"(
-var<private> arr : array<i32, 4>;
-)";
-
-  EXPECT_TRUE(ShouldRun<WrapArraysInStructs>(src));
-}
-
-TEST_F(WrapArraysInStructsTest, EmptyModule) {
-  auto* src = R"()";
-  auto* expect = src;
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAsGlobal) {
-  auto* src = R"(
-var<private> arr : array<i32, 4>;
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-var<private> arr : tint_array_wrapper;
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAsFunctionVar) {
-  auto* src = R"(
-fn f() {
-  var arr : array<i32, 4>;
-  let x = arr[3];
-}
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-fn f() {
-  var arr : tint_array_wrapper;
-  let x = arr.arr[3];
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAsParam) {
-  auto* src = R"(
-fn f(a : array<i32, 4>) -> i32 {
-  return a[2];
-}
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-fn f(a : tint_array_wrapper) -> i32 {
-  return a.arr[2];
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAsReturn) {
-  auto* src = R"(
-fn f() -> array<i32, 4> {
-  return array<i32, 4>(1, 2, 3, 4);
-}
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-fn f() -> tint_array_wrapper {
-  return tint_array_wrapper(array<i32, 4u>(1, 2, 3, 4));
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAlias) {
-  auto* src = R"(
-type Inner = array<i32, 2>;
-type Array = array<Inner, 2>;
-
-fn f() {
-  var arr : Array;
-  arr = Array();
-  arr = Array(Inner(1, 2), Inner(3, 4));
-  let vals : Array = Array(Inner(1, 2), Inner(3, 4));
-  arr = vals;
-  let x = arr[3];
-}
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 2u>;
-}
-
-type Inner = tint_array_wrapper;
-
-struct tint_array_wrapper_1 {
-  arr : array<tint_array_wrapper, 2u>;
-}
-
-type Array = tint_array_wrapper_1;
-
-fn f() {
-  var arr : tint_array_wrapper_1;
-  arr = tint_array_wrapper_1(array<tint_array_wrapper, 2u>());
-  arr = tint_array_wrapper_1(array<tint_array_wrapper, 2u>(tint_array_wrapper(array<i32, 2u>(1, 2)), tint_array_wrapper(array<i32, 2u>(3, 4))));
-  let vals : tint_array_wrapper_1 = tint_array_wrapper_1(array<tint_array_wrapper, 2u>(tint_array_wrapper(array<i32, 2u>(1, 2)), tint_array_wrapper(array<i32, 2u>(3, 4))));
-  arr = vals;
-  let x = arr.arr[3];
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArrayAlias_OutOfOrder) {
-  auto* src = R"(
-fn f() {
-  var arr : Array;
-  arr = Array();
-  arr = Array(Inner(1, 2), Inner(3, 4));
-  let vals : Array = Array(Inner(1, 2), Inner(3, 4));
-  arr = vals;
-  let x = arr[3];
-}
-
-type Array = array<Inner, 2>;
-type Inner = array<i32, 2>;
-)";
-  auto* expect = R"(
-struct tint_array_wrapper_1 {
-  arr : array<i32, 2u>;
-}
-
-struct tint_array_wrapper {
-  arr : array<tint_array_wrapper_1, 2u>;
-}
-
-fn f() {
-  var arr : tint_array_wrapper;
-  arr = tint_array_wrapper(array<tint_array_wrapper_1, 2u>());
-  arr = tint_array_wrapper(array<tint_array_wrapper_1, 2u>(tint_array_wrapper_1(array<i32, 2u>(1, 2)), tint_array_wrapper_1(array<i32, 2u>(3, 4))));
-  let vals : tint_array_wrapper = tint_array_wrapper(array<tint_array_wrapper_1, 2u>(tint_array_wrapper_1(array<i32, 2u>(1, 2)), tint_array_wrapper_1(array<i32, 2u>(3, 4))));
-  arr = vals;
-  let x = arr.arr[3];
-}
-
-type Array = tint_array_wrapper;
-
-type Inner = tint_array_wrapper_1;
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : array<i32, 4>;
-  b : array<i32, 8>;
-  c : array<i32, 4>;
-};
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-struct tint_array_wrapper_1 {
-  arr : array<i32, 8u>;
-}
-
-struct S {
-  a : tint_array_wrapper;
-  b : tint_array_wrapper_1;
-  c : tint_array_wrapper;
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, ArraysOfArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : array<i32, 4>;
-  b : array<array<i32, 4>, 4>;
-  c : array<array<array<i32, 4>, 4>, 4>;
-};
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-struct tint_array_wrapper_1 {
-  arr : array<tint_array_wrapper, 4u>;
-}
-
-struct tint_array_wrapper_2 {
-  arr : array<tint_array_wrapper_1, 4u>;
-}
-
-struct S {
-  a : tint_array_wrapper;
-  b : tint_array_wrapper_1;
-  c : tint_array_wrapper_2;
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, AccessArraysOfArraysInStruct) {
-  auto* src = R"(
-struct S {
-  a : array<i32, 4>;
-  b : array<array<i32, 4>, 4>;
-  c : array<array<array<i32, 4>, 4>, 4>;
-};
-
-fn f(s : S) -> i32 {
-  return s.a[2] + s.b[1][2] + s.c[3][1][2];
-}
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 4u>;
-}
-
-struct tint_array_wrapper_1 {
-  arr : array<tint_array_wrapper, 4u>;
-}
-
-struct tint_array_wrapper_2 {
-  arr : array<tint_array_wrapper_1, 4u>;
-}
-
-struct S {
-  a : tint_array_wrapper;
-  b : tint_array_wrapper_1;
-  c : tint_array_wrapper_2;
-}
-
-fn f(s : S) -> i32 {
-  return ((s.a.arr[2] + s.b.arr[1].arr[2]) + s.c.arr[3].arr[1].arr[2]);
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, DeclarationOrder) {
-  auto* src = R"(
-type T0 = i32;
-
-type T1 = array<i32, 1>;
-
-type T2 = i32;
-
-fn f1(a : array<i32, 2>) {
-}
-
-type T3 = i32;
-
-fn f2() {
-  var v : array<i32, 3>;
-}
-)";
-  auto* expect = R"(
-type T0 = i32;
-
-struct tint_array_wrapper {
-  arr : array<i32, 1u>;
-}
-
-type T1 = tint_array_wrapper;
-
-type T2 = i32;
-
-struct tint_array_wrapper_1 {
-  arr : array<i32, 2u>;
-}
-
-fn f1(a : tint_array_wrapper_1) {
-}
-
-type T3 = i32;
-
-struct tint_array_wrapper_2 {
-  arr : array<i32, 3u>;
-}
-
-fn f2() {
-  var v : tint_array_wrapper_2;
-}
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(WrapArraysInStructsTest, DeclarationOrder_OutOfOrder) {
-  auto* src = R"(
-fn f2() {
-  var v : array<i32, 3>;
-}
-
-type T3 = i32;
-
-fn f1(a : array<i32, 2>) {
-}
-
-type T2 = i32;
-
-type T1 = array<i32, 1>;
-
-type T0 = i32;
-)";
-  auto* expect = R"(
-struct tint_array_wrapper {
-  arr : array<i32, 3u>;
-}
-
-fn f2() {
-  var v : tint_array_wrapper;
-}
-
-type T3 = i32;
-
-struct tint_array_wrapper_1 {
-  arr : array<i32, 2u>;
-}
-
-fn f1(a : tint_array_wrapper_1) {
-}
-
-type T2 = i32;
-
-struct tint_array_wrapper_2 {
-  arr : array<i32, 1u>;
-}
-
-type T1 = tint_array_wrapper_2;
-
-type T0 = i32;
-)";
-
-  auto got = Run<WrapArraysInStructs>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/zero_init_workgroup_memory.cc b/src/transform/zero_init_workgroup_memory.cc
deleted file mode 100644
index 6b20fc0..0000000
--- a/src/transform/zero_init_workgroup_memory.cc
+++ /dev/null
@@ -1,460 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/zero_init_workgroup_memory.h"
-
-#include <algorithm>
-#include <map>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/workgroup_attribute.h"
-#include "src/program_builder.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/function.h"
-#include "src/sem/variable.h"
-#include "src/utils/map.h"
-#include "src/utils/unique_vector.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::ZeroInitWorkgroupMemory);
-
-namespace tint {
-namespace transform {
-
-/// PIMPL state for the ZeroInitWorkgroupMemory transform
-struct ZeroInitWorkgroupMemory::State {
-  /// The clone context
-  CloneContext& ctx;
-
-  /// An alias to *ctx.dst
-  ProgramBuilder& b = *ctx.dst;
-
-  /// The constant size of the workgroup. If 0, then #workgroup_size_expr should
-  /// be used instead.
-  uint32_t workgroup_size_const = 0;
-  /// The size of the workgroup as an expression generator. Use if
-  /// #workgroup_size_const is 0.
-  std::function<const ast::Expression*()> workgroup_size_expr;
-
-  /// ArrayIndex represents a function on the local invocation index, of
-  /// the form: `array_index = (local_invocation_index % modulo) / division`
-  struct ArrayIndex {
-    /// The RHS of the modulus part of the expression
-    uint32_t modulo = 1;
-    /// The RHS of the division part of the expression
-    uint32_t division = 1;
-
-    /// Equality operator
-    /// @param i the ArrayIndex to compare to this ArrayIndex
-    /// @returns true if `i` and this ArrayIndex are equal
-    bool operator==(const ArrayIndex& i) const {
-      return modulo == i.modulo && division == i.division;
-    }
-
-    /// Hash function for the ArrayIndex type
-    struct Hasher {
-      /// @param i the ArrayIndex to calculate a hash for
-      /// @returns the hash value for the ArrayIndex `i`
-      size_t operator()(const ArrayIndex& i) const {
-        return utils::Hash(i.modulo, i.division);
-      }
-    };
-  };
-
-  /// A list of unique ArrayIndex
-  using ArrayIndices = utils::UniqueVector<ArrayIndex, ArrayIndex::Hasher>;
-
-  /// Expression holds information about an expression that is being built for a
-  /// statement will zero workgroup values.
-  struct Expression {
-    /// The AST expression node
-    const ast::Expression* expr = nullptr;
-    /// The number of iterations required to zero the value
-    uint32_t num_iterations = 0;
-    /// All array indices used by this expression
-    ArrayIndices array_indices;
-  };
-
-  /// Statement holds information about a statement that will zero workgroup
-  /// values.
-  struct Statement {
-    /// The AST statement node
-    const ast::Statement* stmt;
-    /// The number of iterations required to zero the value
-    uint32_t num_iterations;
-    /// All array indices used by this statement
-    ArrayIndices array_indices;
-  };
-
-  /// All statements that zero workgroup memory
-  std::vector<Statement> statements;
-
-  /// A map of ArrayIndex to the name reserved for the `let` declaration of that
-  /// index.
-  std::unordered_map<ArrayIndex, Symbol, ArrayIndex::Hasher> array_index_names;
-
-  /// Constructor
-  /// @param c the CloneContext used for the transform
-  explicit State(CloneContext& c) : ctx(c) {}
-
-  /// Run inserts the workgroup memory zero-initialization logic at the top of
-  /// the given function
-  /// @param fn a compute shader entry point function
-  void Run(const ast::Function* fn) {
-    auto& sem = ctx.src->Sem();
-
-    CalculateWorkgroupSize(
-        ast::GetAttribute<ast::WorkgroupAttribute>(fn->attributes));
-
-    // Generate a list of statements to zero initialize each of the
-    // workgroup storage variables used by `fn`. This will populate #statements.
-    auto* func = sem.Get(fn);
-    for (auto* var : func->TransitivelyReferencedGlobals()) {
-      if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
-        BuildZeroingStatements(
-            var->Type()->UnwrapRef(), [&](uint32_t num_values) {
-              auto var_name = ctx.Clone(var->Declaration()->symbol);
-              return Expression{b.Expr(var_name), num_values, ArrayIndices{}};
-            });
-      }
-    }
-
-    if (statements.empty()) {
-      return;  // No workgroup variables to initialize.
-    }
-
-    // Scan the entry point for an existing local_invocation_index builtin
-    // parameter
-    std::function<const ast::Expression*()> local_index;
-    for (auto* param : fn->params) {
-      if (auto* builtin =
-              ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) {
-        if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
-          local_index = [=] { return b.Expr(ctx.Clone(param->symbol)); };
-          break;
-        }
-      }
-
-      if (auto* str = sem.Get(param)->Type()->As<sem::Struct>()) {
-        for (auto* member : str->Members()) {
-          if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
-                  member->Declaration()->attributes)) {
-            if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
-              local_index = [=] {
-                auto* param_expr = b.Expr(ctx.Clone(param->symbol));
-                auto member_name = ctx.Clone(member->Declaration()->symbol);
-                return b.MemberAccessor(param_expr, member_name);
-              };
-              break;
-            }
-          }
-        }
-      }
-    }
-    if (!local_index) {
-      // No existing local index parameter. Append one to the entry point.
-      auto* param =
-          b.Param(b.Symbols().New("local_invocation_index"), b.ty.u32(),
-                  {b.Builtin(ast::Builtin::kLocalInvocationIndex)});
-      ctx.InsertBack(fn->params, param);
-      local_index = [=] { return b.Expr(param->symbol); };
-    }
-
-    // Take the zeroing statements and bin them by the number of iterations
-    // required to zero the workgroup data. We then emit these in blocks,
-    // possibly wrapped in if-statements or for-loops.
-    std::unordered_map<uint32_t, std::vector<Statement>>
-        stmts_by_num_iterations;
-    std::vector<uint32_t> num_sorted_iterations;
-    for (auto& s : statements) {
-      auto& stmts = stmts_by_num_iterations[s.num_iterations];
-      if (stmts.empty()) {
-        num_sorted_iterations.emplace_back(s.num_iterations);
-      }
-      stmts.emplace_back(s);
-    }
-    std::sort(num_sorted_iterations.begin(), num_sorted_iterations.end());
-
-    // Loop over the statements, grouped by num_iterations.
-    for (auto num_iterations : num_sorted_iterations) {
-      auto& stmts = stmts_by_num_iterations[num_iterations];
-
-      // Gather all the array indices used by all the statements in the block.
-      ArrayIndices array_indices;
-      for (auto& s : stmts) {
-        for (auto& idx : s.array_indices) {
-          array_indices.add(idx);
-        }
-      }
-
-      // Determine the block type used to emit these statements.
-
-      if (workgroup_size_const == 0 || num_iterations > workgroup_size_const) {
-        // Either the workgroup size is dynamic, or smaller than num_iterations.
-        // In either case, we need to generate a for loop to ensure we
-        // initialize all the array elements.
-        //
-        //  for (var idx : u32 = local_index;
-        //           idx < num_iterations;
-        //           idx += workgroup_size) {
-        //    ...
-        //  }
-        auto idx = b.Symbols().New("idx");
-        auto* init = b.Decl(b.Var(idx, b.ty.u32(), local_index()));
-        auto* cond = b.create<ast::BinaryExpression>(
-            ast::BinaryOp::kLessThan, b.Expr(idx), b.Expr(num_iterations));
-        auto* cont = b.Assign(
-            idx, b.Add(idx, workgroup_size_const ? b.Expr(workgroup_size_const)
-                                                 : workgroup_size_expr()));
-
-        auto block = DeclareArrayIndices(num_iterations, array_indices,
-                                         [&] { return b.Expr(idx); });
-        for (auto& s : stmts) {
-          block.emplace_back(s.stmt);
-        }
-        auto* for_loop = b.For(init, cond, cont, b.Block(block));
-        ctx.InsertFront(fn->body->statements, for_loop);
-      } else if (num_iterations < workgroup_size_const) {
-        // Workgroup size is a known constant, but is greater than
-        // num_iterations. Emit an if statement:
-        //
-        //  if (local_index < num_iterations) {
-        //    ...
-        //  }
-        auto* cond = b.create<ast::BinaryExpression>(
-            ast::BinaryOp::kLessThan, local_index(), b.Expr(num_iterations));
-        auto block = DeclareArrayIndices(num_iterations, array_indices,
-                                         [&] { return b.Expr(local_index()); });
-        for (auto& s : stmts) {
-          block.emplace_back(s.stmt);
-        }
-        auto* if_stmt = b.If(cond, b.Block(block));
-        ctx.InsertFront(fn->body->statements, if_stmt);
-      } else {
-        // Workgroup size exactly equals num_iterations.
-        // No need for any conditionals. Just emit a basic block:
-        //
-        // {
-        //    ...
-        // }
-        auto block = DeclareArrayIndices(num_iterations, array_indices,
-                                         [&] { return b.Expr(local_index()); });
-        for (auto& s : stmts) {
-          block.emplace_back(s.stmt);
-        }
-        ctx.InsertFront(fn->body->statements, b.Block(block));
-      }
-    }
-
-    // Append a single workgroup barrier after the zero initialization.
-    ctx.InsertFront(fn->body->statements,
-                    b.CallStmt(b.Call("workgroupBarrier")));
-  }
-
-  /// BuildZeroingExpr is a function that builds a sub-expression used to zero
-  /// workgroup values. `num_values` is the number of elements that the
-  /// expression will be used to zero. Returns the expression.
-  using BuildZeroingExpr = std::function<Expression(uint32_t num_values)>;
-
-  /// BuildZeroingStatements() generates the statements required to zero
-  /// initialize the workgroup storage expression of type `ty`.
-  /// @param ty the expression type
-  /// @param get_expr a function that builds the AST nodes for the expression.
-  void BuildZeroingStatements(const sem::Type* ty,
-                              const BuildZeroingExpr& get_expr) {
-    if (CanTriviallyZero(ty)) {
-      auto var = get_expr(1u);
-      auto* zero_init = b.Construct(CreateASTTypeFor(ctx, ty));
-      statements.emplace_back(Statement{b.Assign(var.expr, zero_init),
-                                        var.num_iterations, var.array_indices});
-      return;
-    }
-
-    if (auto* atomic = ty->As<sem::Atomic>()) {
-      auto* zero_init = b.Construct(CreateASTTypeFor(ctx, atomic->Type()));
-      auto expr = get_expr(1u);
-      auto* store = b.Call("atomicStore", b.AddressOf(expr.expr), zero_init);
-      statements.emplace_back(Statement{b.CallStmt(store), expr.num_iterations,
-                                        expr.array_indices});
-      return;
-    }
-
-    if (auto* str = ty->As<sem::Struct>()) {
-      for (auto* member : str->Members()) {
-        auto name = ctx.Clone(member->Declaration()->symbol);
-        BuildZeroingStatements(member->Type(), [&](uint32_t num_values) {
-          auto s = get_expr(num_values);
-          return Expression{b.MemberAccessor(s.expr, name), s.num_iterations,
-                            s.array_indices};
-        });
-      }
-      return;
-    }
-
-    if (auto* arr = ty->As<sem::Array>()) {
-      BuildZeroingStatements(arr->ElemType(), [&](uint32_t num_values) {
-        // num_values is the number of values to zero for the element type.
-        // The number of iterations required to zero the array and its elements
-        // is:
-        //      `num_values * arr->Count()`
-        // The index for this array is:
-        //      `(idx % modulo) / division`
-        auto modulo = num_values * arr->Count();
-        auto division = num_values;
-        auto a = get_expr(modulo);
-        auto array_indices = a.array_indices;
-        array_indices.add(ArrayIndex{modulo, division});
-        auto index =
-            utils::GetOrCreate(array_index_names, ArrayIndex{modulo, division},
-                               [&] { return b.Symbols().New("i"); });
-        return Expression{b.IndexAccessor(a.expr, index), a.num_iterations,
-                          array_indices};
-      });
-      return;
-    }
-
-    TINT_UNREACHABLE(Transform, b.Diagnostics())
-        << "could not zero workgroup type: " << ty->type_name();
-  }
-
-  /// DeclareArrayIndices returns a list of statements that contain the `let`
-  /// declarations for all of the ArrayIndices.
-  /// @param num_iterations the number of iterations for the block
-  /// @param array_indices the list of array indices to generate `let`
-  ///        declarations for
-  /// @param iteration a function that returns the index of the current
-  ///         iteration.
-  /// @returns the list of `let` statements that declare the array indices
-  ast::StatementList DeclareArrayIndices(
-      uint32_t num_iterations,
-      const ArrayIndices& array_indices,
-      const std::function<const ast::Expression*()>& iteration) {
-    ast::StatementList stmts;
-    std::map<Symbol, ArrayIndex> indices_by_name;
-    for (auto index : array_indices) {
-      auto name = array_index_names.at(index);
-      auto* mod =
-          (num_iterations > index.modulo)
-              ? b.create<ast::BinaryExpression>(
-                    ast::BinaryOp::kModulo, iteration(), b.Expr(index.modulo))
-              : iteration();
-      auto* div = (index.division != 1u) ? b.Div(mod, index.division) : mod;
-      auto* decl = b.Decl(b.Const(name, b.ty.u32(), div));
-      stmts.emplace_back(decl);
-    }
-    return stmts;
-  }
-
-  /// CalculateWorkgroupSize initializes the members #workgroup_size_const and
-  /// #workgroup_size_expr with the linear workgroup size.
-  /// @param attr the workgroup attribute applied to the entry point function
-  void CalculateWorkgroupSize(const ast::WorkgroupAttribute* attr) {
-    bool is_signed = false;
-    workgroup_size_const = 1u;
-    workgroup_size_expr = nullptr;
-    for (auto* expr : attr->Values()) {
-      if (!expr) {
-        continue;
-      }
-      auto* sem = ctx.src->Sem().Get(expr);
-      if (auto c = sem->ConstantValue()) {
-        if (c.ElementType()->Is<sem::I32>()) {
-          workgroup_size_const *= static_cast<uint32_t>(c.Elements()[0].i32);
-          continue;
-        } else if (c.ElementType()->Is<sem::U32>()) {
-          workgroup_size_const *= c.Elements()[0].u32;
-          continue;
-        }
-      }
-      // Constant value could not be found. Build expression instead.
-      workgroup_size_expr = [this, expr, size = workgroup_size_expr] {
-        auto* e = ctx.Clone(expr);
-        if (ctx.src->TypeOf(expr)->UnwrapRef()->Is<sem::I32>()) {
-          e = b.Construct<ProgramBuilder::u32>(e);
-        }
-        return size ? b.Mul(size(), e) : e;
-      };
-    }
-    if (workgroup_size_expr) {
-      if (workgroup_size_const != 1) {
-        // Fold workgroup_size_const in to workgroup_size_expr
-        workgroup_size_expr = [this, is_signed,
-                               const_size = workgroup_size_const,
-                               expr_size = workgroup_size_expr] {
-          return is_signed
-                     ? b.Mul(expr_size(), static_cast<int32_t>(const_size))
-                     : b.Mul(expr_size(), const_size);
-        };
-      }
-      // Indicate that workgroup_size_expr should be used instead of the
-      // constant.
-      workgroup_size_const = 0;
-    }
-  }
-
-  /// @returns true if a variable with store type `ty` can be efficiently zeroed
-  /// by assignment of a type constructor without operands. If
-  /// CanTriviallyZero() returns false, then the type needs to be
-  /// initialized by decomposing the initialization into multiple
-  /// sub-initializations.
-  /// @param ty the type to inspect
-  bool CanTriviallyZero(const sem::Type* ty) {
-    if (ty->Is<sem::Atomic>()) {
-      return false;
-    }
-    if (auto* str = ty->As<sem::Struct>()) {
-      for (auto* member : str->Members()) {
-        if (!CanTriviallyZero(member->Type())) {
-          return false;
-        }
-      }
-    }
-    if (ty->Is<sem::Array>()) {
-      return false;
-    }
-    // True for all other storable types
-    return true;
-  }
-};
-
-ZeroInitWorkgroupMemory::ZeroInitWorkgroupMemory() = default;
-
-ZeroInitWorkgroupMemory::~ZeroInitWorkgroupMemory() = default;
-
-bool ZeroInitWorkgroupMemory::ShouldRun(const Program* program,
-                                        const DataMap&) const {
-  for (auto* decl : program->AST().GlobalDeclarations()) {
-    if (auto* var = decl->As<ast::Variable>()) {
-      if (var->declared_storage_class == ast::StorageClass::kWorkgroup) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-void ZeroInitWorkgroupMemory::Run(CloneContext& ctx,
-                                  const DataMap&,
-                                  DataMap&) const {
-  for (auto* fn : ctx.src->AST().Functions()) {
-    if (fn->PipelineStage() == ast::PipelineStage::kCompute) {
-      State{ctx}.Run(fn);
-    }
-  }
-  ctx.Clone();
-}
-
-}  // namespace transform
-}  // namespace tint
diff --git a/src/transform/zero_init_workgroup_memory.h b/src/transform/zero_init_workgroup_memory.h
deleted file mode 100644
index df6c9ae..0000000
--- a/src/transform/zero_init_workgroup_memory.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
-#define SRC_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// ZeroInitWorkgroupMemory is a transform that injects code at the top of entry
-/// points to zero-initialize workgroup memory used by that entry point (and all
-/// transitive functions called by that entry point)
-class ZeroInitWorkgroupMemory
-    : public Castable<ZeroInitWorkgroupMemory, Transform> {
- public:
-  /// Constructor
-  ZeroInitWorkgroupMemory();
-
-  /// Destructor
-  ~ZeroInitWorkgroupMemory() override;
-
-  /// @param program the program to inspect
-  /// @param data optional extra transform-specific input data
-  /// @returns true if this transform should be run for the given program
-  bool ShouldRun(const Program* program,
-                 const DataMap& data = {}) const override;
-
- protected:
-  /// Runs the transform using the CloneContext built for transforming a
-  /// program. Run() is responsible for calling Clone() on the CloneContext.
-  /// @param ctx the CloneContext primed with the input program and
-  /// ProgramBuilder
-  /// @param inputs optional extra transform-specific input data
-  /// @param outputs optional extra transform-specific output data
-  void Run(CloneContext& ctx,
-           const DataMap& inputs,
-           DataMap& outputs) const override;
-
- private:
-  struct State;
-};
-
-}  // namespace transform
-}  // namespace tint
-
-#endif  // SRC_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
diff --git a/src/transform/zero_init_workgroup_memory_test.cc b/src/transform/zero_init_workgroup_memory_test.cc
deleted file mode 100644
index 09baba6..0000000
--- a/src/transform/zero_init_workgroup_memory_test.cc
+++ /dev/null
@@ -1,1381 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/transform/zero_init_workgroup_memory.h"
-
-#include <utility>
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using ZeroInitWorkgroupMemoryTest = TransformTest;
-
-TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunEmptyModule) {
-  auto* src = R"()";
-
-  EXPECT_FALSE(ShouldRun<ZeroInitWorkgroupMemory>(src));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunHasNoWorkgroupVars) {
-  auto* src = R"(
-var<private> v : i32;
-)";
-
-  EXPECT_FALSE(ShouldRun<ZeroInitWorkgroupMemory>(src));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, ShouldRunHasWorkgroupVars) {
-  auto* src = R"(
-var<workgroup> a : i32;
-)";
-
-  EXPECT_TRUE(ShouldRun<ZeroInitWorkgroupMemory>(src));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, EmptyModule) {
-  auto* src = "";
-  auto* expect = src;
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, NoWorkgroupVars) {
-  auto* src = R"(
-var<private> v : i32;
-
-fn f() {
-  v = 1;
-}
-)";
-  auto* expect = src;
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, UnreferencedWorkgroupVars) {
-  auto* src = R"(
-var<workgroup> a : i32;
-
-var<workgroup> b : i32;
-
-var<workgroup> c : i32;
-
-fn unreferenced() {
-  b = c;
-}
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-)";
-  auto* expect = src;
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, UnreferencedWorkgroupVars_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-}
-
-fn unreferenced() {
-  b = c;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : i32;
-
-var<workgroup> c : i32;
-)";
-  auto* expect = src;
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, SingleWorkgroupVar_ExistingLocalIndex) {
-  auto* src = R"(
-var<workgroup> v : i32;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = v; // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-var<workgroup> v : i32;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       SingleWorkgroupVar_ExistingLocalIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = v; // Initialization should be inserted above this statement
-}
-
-var<workgroup> v : i32;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-
-var<workgroup> v : i32;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       SingleWorkgroupVar_ExistingLocalIndexInStruct) {
-  auto* src = R"(
-var<workgroup> v : i32;
-
-struct Params {
-  @builtin(local_invocation_index) local_idx : u32;
-};
-
-@stage(compute) @workgroup_size(1)
-fn f(params : Params) {
-  _ = v; // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-var<workgroup> v : i32;
-
-struct Params {
-  @builtin(local_invocation_index)
-  local_idx : u32;
-}
-
-@stage(compute) @workgroup_size(1)
-fn f(params : Params) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       SingleWorkgroupVar_ExistingLocalIndexInStruct_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f(params : Params) {
-  _ = v; // Initialization should be inserted above this statement
-}
-
-struct Params {
-  @builtin(local_invocation_index) local_idx : u32;
-};
-
-var<workgroup> v : i32;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(params : Params) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-
-struct Params {
-  @builtin(local_invocation_index)
-  local_idx : u32;
-}
-
-var<workgroup> v : i32;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, SingleWorkgroupVar_InjectedLocalIndex) {
-  auto* src = R"(
-var<workgroup> v : i32;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = v; // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-var<workgroup> v : i32;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       SingleWorkgroupVar_InjectedLocalIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = v; // Initialization should be inserted above this statement
-}
-
-var<workgroup> v : i32;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  _ = v;
-}
-
-var<workgroup> v : i32;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_ExistingLocalIndex_Size1) {
-  auto* src = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-)";
-  auto* expect = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    b.y[i] = i32();
-  }
-  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = idx_1;
-    c[i_1].x = i32();
-  }
-  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
-    let i_2 : u32 = (idx_2 / 8u);
-    let i : u32 = (idx_2 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_ExistingLocalIndex_Size1_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    b.y[i] = i32();
-  }
-  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = idx_1;
-    c[i_1].x = i32();
-  }
-  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
-    let i_2 : u32 = (idx_2 / 8u);
-    let i : u32 = (idx_2 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_ExistingLocalIndex_Size_2_3) {
-  auto* src = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(2, 3)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-)";
-  auto* expect = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(2, 3)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  if ((local_idx < 1u)) {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx : u32 = local_idx; (idx < 8u); idx = (idx + 6u)) {
-    let i : u32 = idx;
-    b.y[i] = i32();
-  }
-  for(var idx_1 : u32 = local_idx; (idx_1 < 32u); idx_1 = (idx_1 + 6u)) {
-    let i_1 : u32 = idx_1;
-    c[i_1].x = i32();
-  }
-  for(var idx_2 : u32 = local_idx; (idx_2 < 256u); idx_2 = (idx_2 + 6u)) {
-    let i_2 : u32 = (idx_2 / 8u);
-    let i : u32 = (idx_2 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_ExistingLocalIndex_Size_2_3_X) {
-  auto* src = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@id(1) override X : i32;
-
-@stage(compute) @workgroup_size(2, 3, X)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-)";
-  auto* expect =
-      R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@id(1) override X : i32;
-
-@stage(compute) @workgroup_size(2, 3, X)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  for(var idx : u32 = local_idx; (idx < 1u); idx = (idx + (u32(X) * 6u))) {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx_1 : u32 = local_idx; (idx_1 < 8u); idx_1 = (idx_1 + (u32(X) * 6u))) {
-    let i : u32 = idx_1;
-    b.y[i] = i32();
-  }
-  for(var idx_2 : u32 = local_idx; (idx_2 < 32u); idx_2 = (idx_2 + (u32(X) * 6u))) {
-    let i_1 : u32 = idx_2;
-    c[i_1].x = i32();
-  }
-  for(var idx_3 : u32 = local_idx; (idx_3 < 256u); idx_3 = (idx_3 + (u32(X) * 6u))) {
-    let i_2 : u32 = (idx_3 / 8u);
-    let i : u32 = (idx_3 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_ExistingLocalIndex_Size_5u_X_10u) {
-  auto* src = R"(
-struct S {
-  x : array<array<i32, 8>, 10>;
-  y : array<i32, 8>;
-  z : array<array<array<i32, 8>, 10>, 20>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@id(1) override X : u32;
-
-@stage(compute) @workgroup_size(5u, X, 10u)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-)";
-  auto* expect =
-      R"(
-struct S {
-  x : array<array<i32, 8>, 10>;
-  y : array<i32, 8>;
-  z : array<array<array<i32, 8>, 10>, 20>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@id(1) override X : u32;
-
-@stage(compute) @workgroup_size(5u, X, 10u)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  for(var idx : u32 = local_idx; (idx < 1u); idx = (idx + (X * 50u))) {
-    a = i32();
-  }
-  for(var idx_1 : u32 = local_idx; (idx_1 < 8u); idx_1 = (idx_1 + (X * 50u))) {
-    let i_1 : u32 = idx_1;
-    b.y[i_1] = i32();
-  }
-  for(var idx_2 : u32 = local_idx; (idx_2 < 80u); idx_2 = (idx_2 + (X * 50u))) {
-    let i : u32 = (idx_2 / 8u);
-    let i_1 : u32 = (idx_2 % 8u);
-    b.x[i][i_1] = i32();
-  }
-  for(var idx_3 : u32 = local_idx; (idx_3 < 256u); idx_3 = (idx_3 + (X * 50u))) {
-    let i_4 : u32 = (idx_3 / 8u);
-    let i_1 : u32 = (idx_3 % 8u);
-    c[i_4].y[i_1] = i32();
-  }
-  for(var idx_4 : u32 = local_idx; (idx_4 < 1600u); idx_4 = (idx_4 + (X * 50u))) {
-    let i_2 : u32 = (idx_4 / 80u);
-    let i : u32 = ((idx_4 % 80u) / 8u);
-    let i_1 : u32 = (idx_4 % 8u);
-    b.z[i_2][i][i_1] = i32();
-  }
-  for(var idx_5 : u32 = local_idx; (idx_5 < 2560u); idx_5 = (idx_5 + (X * 50u))) {
-    let i_3 : u32 = (idx_5 / 80u);
-    let i : u32 = ((idx_5 % 80u) / 8u);
-    let i_1 : u32 = (idx_5 % 8u);
-    c[i_3].x[i][i_1] = i32();
-  }
-  for(var idx_6 : u32 = local_idx; (idx_6 < 51200u); idx_6 = (idx_6 + (X * 50u))) {
-    let i_5 : u32 = (idx_6 / 1600u);
-    let i_2 : u32 = ((idx_6 % 1600u) / 80u);
-    let i : u32 = ((idx_6 % 80u) / 8u);
-    let i_1 : u32 = (idx_6 % 8u);
-    c[i_5].z[i_2][i][i_1] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, MultipleWorkgroupVar_InjectedLocalIndex) {
-  auto* src = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-)";
-  auto* expect = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx : u32 = local_invocation_index; (idx < 8u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    b.y[i] = i32();
-  }
-  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = idx_1;
-    c[i_1].x = i32();
-  }
-  for(var idx_2 : u32 = local_invocation_index; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
-    let i_2 : u32 = (idx_2 / 8u);
-    let i : u32 = (idx_2 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_InjectedLocalIndex_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
-  _ = a; // Initialization should be inserted above this statement
-  _ = b;
-  _ = c;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    a = i32();
-    b.x = i32();
-  }
-  for(var idx : u32 = local_invocation_index; (idx < 8u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    b.y[i] = i32();
-  }
-  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 32u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = idx_1;
-    c[i_1].x = i32();
-  }
-  for(var idx_2 : u32 = local_invocation_index; (idx_2 < 256u); idx_2 = (idx_2 + 1u)) {
-    let i_2 : u32 = (idx_2 / 8u);
-    let i : u32 = (idx_2 % 8u);
-    c[i_2].y[i] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = b;
-  _ = c;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, MultipleWorkgroupVar_MultipleEntryPoints) {
-  auto* src = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f1() {
-  _ = a; // Initialization should be inserted above this statement
-  _ = c;
-}
-
-@stage(compute) @workgroup_size(1, 2, 3)
-fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
-  _ = b; // Initialization should be inserted above this statement
-}
-
-@stage(compute) @workgroup_size(4, 5, 6)
-fn f3() {
-  _ = c; // Initialization should be inserted above this statement
-  _ = a;
-}
-)";
-  auto* expect = R"(
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-@stage(compute) @workgroup_size(1)
-fn f1(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    a = i32();
-  }
-  for(var idx : u32 = local_invocation_index; (idx < 32u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    c[i].x = i32();
-  }
-  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 256u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = (idx_1 / 8u);
-    let i_2 : u32 = (idx_1 % 8u);
-    c[i_1].y[i_2] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = c;
-}
-
-@stage(compute) @workgroup_size(1, 2, 3)
-fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index_1 : u32) {
-  if ((local_invocation_index_1 < 1u)) {
-    b.x = i32();
-  }
-  for(var idx_2 : u32 = local_invocation_index_1; (idx_2 < 8u); idx_2 = (idx_2 + 6u)) {
-    let i_3 : u32 = idx_2;
-    b.y[i_3] = i32();
-  }
-  workgroupBarrier();
-  _ = b;
-}
-
-@stage(compute) @workgroup_size(4, 5, 6)
-fn f3(@builtin(local_invocation_index) local_invocation_index_2 : u32) {
-  if ((local_invocation_index_2 < 1u)) {
-    a = i32();
-  }
-  if ((local_invocation_index_2 < 32u)) {
-    let i_4 : u32 = local_invocation_index_2;
-    c[i_4].x = i32();
-  }
-  for(var idx_3 : u32 = local_invocation_index_2; (idx_3 < 256u); idx_3 = (idx_3 + 120u)) {
-    let i_5 : u32 = (idx_3 / 8u);
-    let i_6 : u32 = (idx_3 % 8u);
-    c[i_5].y[i_6] = i32();
-  }
-  workgroupBarrier();
-  _ = c;
-  _ = a;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       MultipleWorkgroupVar_MultipleEntryPoints_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f1() {
-  _ = a; // Initialization should be inserted above this statement
-  _ = c;
-}
-
-@stage(compute) @workgroup_size(1, 2, 3)
-fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>) {
-  _ = b; // Initialization should be inserted above this statement
-}
-
-@stage(compute) @workgroup_size(4, 5, 6)
-fn f3() {
-  _ = c; // Initialization should be inserted above this statement
-  _ = a;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-};
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f1(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    a = i32();
-  }
-  for(var idx : u32 = local_invocation_index; (idx < 32u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    c[i].x = i32();
-  }
-  for(var idx_1 : u32 = local_invocation_index; (idx_1 < 256u); idx_1 = (idx_1 + 1u)) {
-    let i_1 : u32 = (idx_1 / 8u);
-    let i_2 : u32 = (idx_1 % 8u);
-    c[i_1].y[i_2] = i32();
-  }
-  workgroupBarrier();
-  _ = a;
-  _ = c;
-}
-
-@stage(compute) @workgroup_size(1, 2, 3)
-fn f2(@builtin(local_invocation_id) local_invocation_id : vec3<u32>, @builtin(local_invocation_index) local_invocation_index_1 : u32) {
-  if ((local_invocation_index_1 < 1u)) {
-    b.x = i32();
-  }
-  for(var idx_2 : u32 = local_invocation_index_1; (idx_2 < 8u); idx_2 = (idx_2 + 6u)) {
-    let i_3 : u32 = idx_2;
-    b.y[i_3] = i32();
-  }
-  workgroupBarrier();
-  _ = b;
-}
-
-@stage(compute) @workgroup_size(4, 5, 6)
-fn f3(@builtin(local_invocation_index) local_invocation_index_2 : u32) {
-  if ((local_invocation_index_2 < 1u)) {
-    a = i32();
-  }
-  if ((local_invocation_index_2 < 32u)) {
-    let i_4 : u32 = local_invocation_index_2;
-    c[i_4].x = i32();
-  }
-  for(var idx_3 : u32 = local_invocation_index_2; (idx_3 < 256u); idx_3 = (idx_3 + 120u)) {
-    let i_5 : u32 = (idx_3 / 8u);
-    let i_6 : u32 = (idx_3 % 8u);
-    c[i_5].y[i_6] = i32();
-  }
-  workgroupBarrier();
-  _ = c;
-  _ = a;
-}
-
-var<workgroup> a : i32;
-
-var<workgroup> b : S;
-
-var<workgroup> c : array<S, 32>;
-
-struct S {
-  x : i32;
-  y : array<i32, 8>;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, TransitiveUsage) {
-  auto* src = R"(
-var<workgroup> v : i32;
-
-fn use_v() {
-  _ = v;
-}
-
-fn call_use_v() {
-  use_v();
-}
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  call_use_v(); // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-var<workgroup> v : i32;
-
-fn use_v() {
-  _ = v;
-}
-
-fn call_use_v() {
-  use_v();
-}
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  call_use_v();
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, TransitiveUsage_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  call_use_v(); // Initialization should be inserted above this statement
-}
-
-fn call_use_v() {
-  use_v();
-}
-
-fn use_v() {
-  _ = v;
-}
-
-var<workgroup> v : i32;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_idx : u32) {
-  {
-    v = i32();
-  }
-  workgroupBarrier();
-  call_use_v();
-}
-
-fn call_use_v() {
-  use_v();
-}
-
-fn use_v() {
-  _ = v;
-}
-
-var<workgroup> v : i32;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupAtomics) {
-  auto* src = R"(
-var<workgroup> i : atomic<i32>;
-var<workgroup> u : atomic<u32>;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  atomicLoad(&(i)); // Initialization should be inserted above this statement
-  atomicLoad(&(u));
-}
-)";
-  auto* expect = R"(
-var<workgroup> i : atomic<i32>;
-
-var<workgroup> u : atomic<u32>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    atomicStore(&(i), i32());
-    atomicStore(&(u), u32());
-  }
-  workgroupBarrier();
-  atomicLoad(&(i));
-  atomicLoad(&(u));
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-  atomicLoad(&(i)); // Initialization should be inserted above this statement
-  atomicLoad(&(u));
-}
-
-var<workgroup> i : atomic<i32>;
-var<workgroup> u : atomic<u32>;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    atomicStore(&(i), i32());
-    atomicStore(&(u), u32());
-  }
-  workgroupBarrier();
-  atomicLoad(&(i));
-  atomicLoad(&(u));
-}
-
-var<workgroup> i : atomic<i32>;
-
-var<workgroup> u : atomic<u32>;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupStructOfAtomics) {
-  auto* src = R"(
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-};
-
-var<workgroup> w : S;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = w.a; // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-}
-
-var<workgroup> w : S;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    w.a = i32();
-    atomicStore(&(w.i), i32());
-    w.b = f32();
-    atomicStore(&(w.u), u32());
-    w.c = u32();
-  }
-  workgroupBarrier();
-  _ = w.a;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupStructOfAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = w.a; // Initialization should be inserted above this statement
-}
-
-var<workgroup> w : S;
-
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-};
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  {
-    w.a = i32();
-    atomicStore(&(w.i), i32());
-    w.b = f32();
-    atomicStore(&(w.u), u32());
-    w.c = u32();
-  }
-  workgroupBarrier();
-  _ = w.a;
-}
-
-var<workgroup> w : S;
-
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfAtomics) {
-  auto* src = R"(
-var<workgroup> w : array<atomic<u32>, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  atomicLoad(&w[0]); // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-var<workgroup> w : array<atomic<u32>, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    atomicStore(&(w[i]), u32());
-  }
-  workgroupBarrier();
-  atomicLoad(&(w[0]));
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-  atomicLoad(&w[0]); // Initialization should be inserted above this statement
-}
-
-var<workgroup> w : array<atomic<u32>, 4>;
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
-    let i : u32 = idx;
-    atomicStore(&(w[i]), u32());
-  }
-  workgroupBarrier();
-  atomicLoad(&(w[0]));
-}
-
-var<workgroup> w : array<atomic<u32>, 4>;
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest, WorkgroupArrayOfStructOfAtomics) {
-  auto* src = R"(
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-};
-
-var<workgroup> w : array<S, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = w[0].a; // Initialization should be inserted above this statement
-}
-)";
-  auto* expect = R"(
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-}
-
-var<workgroup> w : array<S, 4>;
-
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
-    let i_1 : u32 = idx;
-    w[i_1].a = i32();
-    atomicStore(&(w[i_1].i), i32());
-    w[i_1].b = f32();
-    atomicStore(&(w[i_1].u), u32());
-    w[i_1].c = u32();
-  }
-  workgroupBarrier();
-  _ = w[0].a;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(ZeroInitWorkgroupMemoryTest,
-       WorkgroupArrayOfStructOfAtomics_OutOfOrder) {
-  auto* src = R"(
-@stage(compute) @workgroup_size(1)
-fn f() {
-  _ = w[0].a; // Initialization should be inserted above this statement
-}
-
-var<workgroup> w : array<S, 4>;
-
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-};
-)";
-  auto* expect = R"(
-@stage(compute) @workgroup_size(1)
-fn f(@builtin(local_invocation_index) local_invocation_index : u32) {
-  for(var idx : u32 = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
-    let i_1 : u32 = idx;
-    w[i_1].a = i32();
-    atomicStore(&(w[i_1].i), i32());
-    w[i_1].b = f32();
-    atomicStore(&(w[i_1].u), u32());
-    w[i_1].c = u32();
-  }
-  workgroupBarrier();
-  _ = w[0].a;
-}
-
-var<workgroup> w : array<S, 4>;
-
-struct S {
-  a : i32;
-  i : atomic<i32>;
-  b : f32;
-  u : atomic<u32>;
-  c : u32;
-}
-)";
-
-  auto got = Run<ZeroInitWorkgroupMemory>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace transform
-}  // namespace tint
diff --git a/src/utils/concat.h b/src/utils/concat.h
deleted file mode 100644
index 54a4e75..0000000
--- a/src/utils/concat.h
+++ /dev/null
@@ -1,22 +0,0 @@
-
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_CONCAT_H_
-#define SRC_UTILS_CONCAT_H_
-
-#define TINT_CONCAT_2(a, b) a##b
-#define TINT_CONCAT(a, b) TINT_CONCAT_2(a, b)
-
-#endif  //  SRC_UTILS_CONCAT_H_
diff --git a/src/utils/crc32.h b/src/utils/crc32.h
deleted file mode 100644
index 9c296d0..0000000
--- a/src/utils/crc32.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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_UTILS_CRC32_H_
-#define SRC_UTILS_CRC32_H_
-
-#include <stdint.h>
-
-namespace tint::utils {
-
-/// @returns the CRC32 of the string `s`.
-/// @note this function can be used to calculate the CRC32 of a string literal
-/// at compile time.
-/// @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
-constexpr uint32_t CRC32(const char* s) {
-  constexpr uint32_t kLUT[] = {
-      0,          0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
-      0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
-      0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
-      0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
-      0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
-      0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
-      0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
-      0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
-      0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
-      0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
-      0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
-      0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
-      0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
-      0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
-      0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
-      0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
-      0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
-      0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
-      0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
-      0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
-      0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
-      0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
-      0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
-      0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
-      0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
-      0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
-      0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
-      0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
-      0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
-      0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
-      0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
-      0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
-      0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
-      0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
-      0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
-      0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
-      0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
-      0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
-      0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
-      0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
-      0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
-      0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
-      0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
-
-  uint32_t crc = 0xffffffff;
-  for (auto* p = s; *p != '\0'; ++p) {
-    crc =
-        (crc >> 8) ^ kLUT[static_cast<uint8_t>(crc) ^ static_cast<uint8_t>(*p)];
-  }
-  return crc ^ 0xffffffff;
-}
-
-}  // namespace tint::utils
-
-#endif  // SRC_UTILS_CRC32_H_
diff --git a/src/utils/crc32_test.cc b/src/utils/crc32_test.cc
deleted file mode 100644
index b1b739f..0000000
--- a/src/utils/crc32_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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/utils/crc32.h"
-
-#include "gtest/gtest.h"
-
-namespace tint::utils {
-namespace {
-
-TEST(CRC32Test, Compiletime) {
-  static_assert(CRC32("") == 0x00000000u);
-  static_assert(CRC32("hello world") == 0x0d4a1185u);
-  static_assert(CRC32("123456789") == 0xcbf43926u);
-}
-
-TEST(CRC32Test, Runtime) {
-  EXPECT_EQ(CRC32(""), 0x00000000u);
-  EXPECT_EQ(CRC32("hello world"), 0x0d4a1185u);
-  EXPECT_EQ(CRC32("123456789"), 0xcbf43926u);
-}
-
-}  // namespace
-}  // namespace tint::utils
diff --git a/src/utils/debugger.cc b/src/utils/debugger.cc
deleted file mode 100644
index fe8cc27..0000000
--- a/src/utils/debugger.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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/utils/debugger.h"
-
-#ifdef TINT_ENABLE_BREAK_IN_DEBUGGER
-
-#ifdef _MSC_VER
-#include <Windows.h>
-#elif defined(__linux__)
-#include <signal.h>
-#include <fstream>
-#include <string>
-#endif
-
-#ifdef _MSC_VER
-#define TINT_DEBUGGER_BREAK_DEFINED
-void tint::debugger::Break() {
-  if (::IsDebuggerPresent()) {
-    ::DebugBreak();
-  }
-}
-
-#elif defined(__linux__)
-
-#define TINT_DEBUGGER_BREAK_DEFINED
-void tint::debugger::Break() {
-  // A process is being traced (debugged) if "/proc/self/status" contains a
-  // line with "TracerPid: <non-zero-digit>...".
-  bool is_traced = false;
-  std::ifstream fin("/proc/self/status");
-  std::string line;
-  while (!is_traced && std::getline(fin, line)) {
-    const char kPrefix[] = "TracerPid:\t";
-    static constexpr int kPrefixLen = sizeof(kPrefix) - 1;
-    if (line.length() > kPrefixLen &&
-        line.compare(0, kPrefixLen, kPrefix) == 0) {
-      is_traced = line[kPrefixLen] != '0';
-    }
-  }
-
-  if (is_traced) {
-    raise(SIGTRAP);
-  }
-}
-#endif  // platform
-
-#endif  // TINT_ENABLE_BREAK_IN_DEBUGGER
-
-#ifndef TINT_DEBUGGER_BREAK_DEFINED
-void tint::debugger::Break() {}
-#endif
diff --git a/src/utils/debugger.h b/src/utils/debugger.h
deleted file mode 100644
index 7a4f2bd..0000000
--- a/src/utils/debugger.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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_UTILS_DEBUGGER_H_
-#define SRC_UTILS_DEBUGGER_H_
-
-namespace tint::debugger {
-
-/// If debugger is attached and the `TINT_ENABLE_BREAK_IN_DEBUGGER` preprocessor
-/// macro is defined for `debugger.cc`, calling `Break()` will cause the
-/// debugger to break at the call site.
-void Break();
-
-}  // namespace tint::debugger
-
-#endif  // SRC_UTILS_DEBUGGER_H_
diff --git a/src/utils/defer.h b/src/utils/defer.h
deleted file mode 100644
index 781e5c9..0000000
--- a/src/utils/defer.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_DEFER_H_
-#define SRC_UTILS_DEFER_H_
-
-#include <utility>
-
-#include "src/utils/concat.h"
-
-namespace tint {
-namespace utils {
-
-/// Defer executes a function or function like object when it is destructed.
-template <typename F>
-class Defer {
- public:
-  /// Constructor
-  /// @param f the function to call when the Defer is destructed
-  explicit Defer(F&& f) : f_(std::move(f)) {}
-
-  /// Move constructor
-  Defer(Defer&&) = default;
-
-  /// Destructor
-  /// Calls the deferred function
-  ~Defer() { f_(); }
-
- private:
-  Defer(const Defer&) = delete;
-  Defer& operator=(const Defer&) = delete;
-
-  F f_;
-};
-
-/// Constructor
-/// @param f the function to call when the Defer is destructed
-template <typename F>
-inline Defer<F> MakeDefer(F&& f) {
-  return Defer<F>(std::forward<F>(f));
-}
-
-}  // namespace utils
-}  // namespace tint
-
-/// TINT_DEFER(S) executes the statement(s) `S` when exiting the current lexical
-/// scope.
-#define TINT_DEFER(S)                          \
-  auto TINT_CONCAT(tint_defer_, __COUNTER__) = \
-      ::tint::utils::MakeDefer([&] { S; })
-
-#endif  //  SRC_UTILS_DEFER_H_
diff --git a/src/utils/defer_test.cc b/src/utils/defer_test.cc
deleted file mode 100644
index 4f93b15..0000000
--- a/src/utils/defer_test.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/defer.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(DeferTest, Basic) {
-  bool deferCalled = false;
-  { TINT_DEFER(deferCalled = true); }
-  ASSERT_TRUE(deferCalled);
-}
-
-TEST(DeferTest, DeferOrder) {
-  int counter = 0;
-  int a = 0, b = 0, c = 0;
-  {
-    TINT_DEFER(a = ++counter);
-    TINT_DEFER(b = ++counter);
-    TINT_DEFER(c = ++counter);
-  }
-  ASSERT_EQ(a, 3);
-  ASSERT_EQ(b, 2);
-  ASSERT_EQ(c, 1);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/enum_set.h b/src/utils/enum_set.h
deleted file mode 100644
index d93a232..0000000
--- a/src/utils/enum_set.h
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_ENUM_SET_H_
-#define SRC_UTILS_ENUM_SET_H_
-
-#include <cstdint>
-#include <functional>
-#include <ostream>
-#include <type_traits>
-#include <utility>
-
-namespace tint {
-namespace utils {
-
-/// EnumSet is a set of enum values.
-/// @note As the EnumSet is backed by a single uint64_t value, it can only hold
-/// enum values in the range [0 .. 63].
-template <typename ENUM>
-struct EnumSet {
- public:
-  /// Enum is the enum type this EnumSet wraps
-  using Enum = ENUM;
-
-  /// Constructor. Initializes the EnumSet with zero.
-  constexpr EnumSet() = default;
-
-  /// Copy constructor.
-  /// @param s the set to copy
-  constexpr EnumSet(const EnumSet& s) = default;
-
-  /// Constructor. Initializes the EnumSet with the given values.
-  /// @param values the enumerator values to construct the set with
-  template <typename... VALUES>
-  explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
-
-  /// Copy assignment operator.
-  /// @param set the set to assign to this set
-  /// @return this set so calls can be chained
-  inline EnumSet& operator=(const EnumSet& set) = default;
-
-  /// Copy assignment operator.
-  /// @param e the enum value
-  /// @return this set so calls can be chained
-  inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
-
-  /// Adds all the given values to this set
-  /// @param values the values to add
-  /// @return this set so calls can be chained
-  template <typename... VALUES>
-  inline EnumSet& Add(VALUES... values) {
-    return Add(EnumSet(std::forward<VALUES>(values)...));
-  }
-
-  /// Removes all the given values from this set
-  /// @param values the values to remove
-  /// @return this set so calls can be chained
-  template <typename... VALUES>
-  inline EnumSet& Remove(VALUES... values) {
-    return Remove(EnumSet(std::forward<VALUES>(values)...));
-  }
-
-  /// Adds all of s to this set
-  /// @param s the enum value
-  /// @return this set so calls can be chained
-  inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
-
-  /// Removes all of s from this set
-  /// @param s the enum value
-  /// @return this set so calls can be chained
-  inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
-
-  /// @param e the enum value
-  /// @returns a copy of this set with e added
-  inline EnumSet operator+(Enum e) const {
-    EnumSet out;
-    out.set = set | Bit(e);
-    return out;
-  }
-
-  /// @param e the enum value
-  /// @returns a copy of this set with e removed
-  inline EnumSet operator-(Enum e) const {
-    EnumSet out;
-    out.set = set & ~Bit(e);
-    return out;
-  }
-
-  /// @param s the other set
-  /// @returns the union of this set with s (this ∪ rhs)
-  inline EnumSet operator+(EnumSet s) const {
-    EnumSet out;
-    out.set = set | s.set;
-    return out;
-  }
-
-  /// @param s the other set
-  /// @returns the set of entries found in this but not in s (this \ s)
-  inline EnumSet operator-(EnumSet s) const {
-    EnumSet out;
-    out.set = set & ~s.set;
-    return out;
-  }
-
-  /// @param s the other set
-  /// @returns the intersection of this set with s (this ∩ rhs)
-  inline EnumSet operator&(EnumSet s) const {
-    EnumSet out;
-    out.set = set & s.set;
-    return out;
-  }
-
-  /// @param e the enum value
-  /// @return true if the set contains `e`
-  inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
-
-  /// @return true if the set is empty
-  inline bool Empty() const { return set == 0; }
-
-  /// Equality operator
-  /// @param rhs the other EnumSet to compare this to
-  /// @return true if this EnumSet is equal to rhs
-  inline bool operator==(EnumSet rhs) const { return set == rhs.set; }
-
-  /// Inequality operator
-  /// @param rhs the other EnumSet to compare this to
-  /// @return true if this EnumSet is not equal to rhs
-  inline bool operator!=(EnumSet rhs) const { return set != rhs.set; }
-
-  /// Equality operator
-  /// @param rhs the enum to compare this to
-  /// @return true if this EnumSet only contains `rhs`
-  inline bool operator==(Enum rhs) const { return set == Bit(rhs); }
-
-  /// Inequality operator
-  /// @param rhs the enum to compare this to
-  /// @return false if this EnumSet only contains `rhs`
-  inline bool operator!=(Enum rhs) const { return set != Bit(rhs); }
-
-  /// @return the underlying value for the EnumSet
-  inline uint64_t Value() const { return set; }
-
-  /// Iterator provides read-only, unidirectional iterator over the enums of an
-  /// EnumSet.
-  class Iterator {
-    static constexpr int8_t kEnd = 63;
-
-    Iterator(uint64_t s, int8_t b) : set(s), pos(b) {}
-
-    /// Make the constructor accessible to the EnumSet.
-    friend struct EnumSet;
-
-   public:
-    /// @return the Enum value at this point in the iterator
-    Enum operator*() const { return static_cast<Enum>(pos); }
-
-    /// Increments the iterator
-    /// @returns this iterator
-    Iterator& operator++() {
-      while (pos < kEnd) {
-        pos++;
-        if (set & (static_cast<uint64_t>(1) << static_cast<uint64_t>(pos))) {
-          break;
-        }
-      }
-      return *this;
-    }
-
-    /// Equality operator
-    /// @param rhs the Iterator to compare this to
-    /// @return true if the two iterators are equal
-    bool operator==(const Iterator& rhs) const {
-      return set == rhs.set && pos == rhs.pos;
-    }
-
-    /// Inequality operator
-    /// @param rhs the Iterator to compare this to
-    /// @return true if the two iterators are different
-    bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
-
-   private:
-    const uint64_t set;
-    int8_t pos;
-  };
-
-  /// @returns an read-only iterator to the beginning of the set
-  Iterator begin() {
-    auto it = Iterator{set, -1};
-    ++it;  // Move to first set bit
-    return it;
-  }
-
-  /// @returns an iterator to the beginning of the set
-  Iterator end() { return Iterator{set, Iterator::kEnd}; }
-
- private:
-  static constexpr uint64_t Bit(Enum value) {
-    return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
-  }
-
-  static constexpr uint64_t Union() { return 0; }
-
-  template <typename FIRST, typename... VALUES>
-  static constexpr uint64_t Union(FIRST first, VALUES... values) {
-    return Bit(first) | Union(values...);
-  }
-
-  uint64_t set = 0;
-};
-
-/// Writes the EnumSet to the std::ostream.
-/// @param out the std::ostream to write to
-/// @param set the EnumSet to write
-/// @returns out so calls can be chained
-template <typename ENUM>
-inline std::ostream& operator<<(std::ostream& out, EnumSet<ENUM> set) {
-  out << "{";
-  bool first = true;
-  for (auto e : set) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-    out << e;
-  }
-  return out << "}";
-}
-
-}  // namespace utils
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::utils::EnumSet<T>
-template <typename T>
-class hash<tint::utils::EnumSet<T>> {
- public:
-  /// @param e the EnumSet to create a hash for
-  /// @return the hash value
-  inline std::size_t operator()(const tint::utils::EnumSet<T>& e) const {
-    return std::hash<uint64_t>()(e.Value());
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_UTILS_ENUM_SET_H_
diff --git a/src/utils/enum_set_test.cc b/src/utils/enum_set_test.cc
deleted file mode 100644
index b7f5b72..0000000
--- a/src/utils/enum_set_test.cc
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/enum_set.h"
-
-#include <sstream>
-#include <vector>
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-using ::testing::ElementsAre;
-
-enum class E { A = 0, B = 3, C = 7 };
-
-std::ostream& operator<<(std::ostream& out, E e) {
-  switch (e) {
-    case E::A:
-      return out << "A";
-    case E::B:
-      return out << "B";
-    case E::C:
-      return out << "C";
-  }
-  return out << "E(" << static_cast<uint32_t>(e) << ")";
-}
-
-TEST(EnumSetTest, ConstructEmpty) {
-  EnumSet<E> set;
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-  EXPECT_TRUE(set.Empty());
-}
-
-TEST(EnumSetTest, ConstructWithSingle) {
-  EnumSet<E> set(E::B);
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-  EXPECT_FALSE(set.Empty());
-}
-
-TEST(EnumSetTest, ConstructWithMultiple) {
-  EnumSet<E> set(E::A, E::C);
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-  EXPECT_FALSE(set.Empty());
-}
-
-TEST(EnumSetTest, AssignSet) {
-  EnumSet<E> set;
-  set = EnumSet<E>(E::A, E::C);
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, AssignEnum) {
-  EnumSet<E> set(E::A);
-  set = E::B;
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, AddEnum) {
-  EnumSet<E> set;
-  set.Add(E::B);
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, RemoveEnum) {
-  EnumSet<E> set(E::A, E::B);
-  set.Remove(E::B);
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, AddEnums) {
-  EnumSet<E> set;
-  set.Add(E::B, E::C);
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, RemoveEnums) {
-  EnumSet<E> set(E::A, E::B);
-  set.Remove(E::C, E::B);
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, AddEnumSet) {
-  EnumSet<E> set;
-  set.Add(EnumSet<E>{E::B, E::C});
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, RemoveEnumSet) {
-  EnumSet<E> set(E::A, E::B);
-  set.Remove(EnumSet<E>{E::B, E::C});
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, OperatorPlusEnum) {
-  EnumSet<E> set = EnumSet<E>{E::B} + E::C;
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, OperatorMinusEnum) {
-  EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, OperatorPlusSet) {
-  EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_TRUE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, OperatorMinusSet) {
-  EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
-  EXPECT_TRUE(set.Contains(E::A));
-  EXPECT_FALSE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, OperatorAnd) {
-  EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
-  EXPECT_FALSE(set.Contains(E::A));
-  EXPECT_TRUE(set.Contains(E::B));
-  EXPECT_FALSE(set.Contains(E::C));
-}
-
-TEST(EnumSetTest, EqualitySet) {
-  EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
-  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));
-}
-
-TEST(EnumSetTest, InequalitySet) {
-  EXPECT_FALSE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::B));
-  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::C));
-}
-
-TEST(EnumSetTest, EqualityEnum) {
-  EXPECT_TRUE(EnumSet<E>(E::A) == E::A);
-  EXPECT_FALSE(EnumSet<E>(E::B) == E::A);
-  EXPECT_FALSE(EnumSet<E>(E::B) == E::C);
-  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::A);
-  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::B);
-  EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::C);
-}
-
-TEST(EnumSetTest, InequalityEnum) {
-  EXPECT_FALSE(EnumSet<E>(E::A) != E::A);
-  EXPECT_TRUE(EnumSet<E>(E::B) != E::A);
-  EXPECT_TRUE(EnumSet<E>(E::B) != E::C);
-  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::A);
-  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::B);
-  EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
-}
-
-TEST(EnumSetTest, Hash) {
-  auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
-  EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
-  EXPECT_NE(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::C)));
-}
-
-TEST(EnumSetTest, Value) {
-  EXPECT_EQ(EnumSet<E>().Value(), 0u);
-  EXPECT_EQ(EnumSet<E>(E::A).Value(), 1u);
-  EXPECT_EQ(EnumSet<E>(E::B).Value(), 8u);
-  EXPECT_EQ(EnumSet<E>(E::C).Value(), 128u);
-  EXPECT_EQ(EnumSet<E>(E::A, E::C).Value(), 129u);
-}
-
-TEST(EnumSetTest, Iterator) {
-  auto set = EnumSet<E>(E::C, E::A);
-
-  auto it = set.begin();
-  EXPECT_EQ(*it, E::A);
-  EXPECT_NE(it, set.end());
-  ++it;
-  EXPECT_EQ(*it, E::C);
-  EXPECT_NE(it, set.end());
-  ++it;
-  EXPECT_EQ(it, set.end());
-}
-
-TEST(EnumSetTest, IteratorEmpty) {
-  auto set = EnumSet<E>();
-  EXPECT_EQ(set.begin(), set.end());
-}
-
-TEST(EnumSetTest, Loop) {
-  auto set = EnumSet<E>(E::C, E::A);
-
-  std::vector<E> seen;
-  for (auto e : set) {
-    seen.emplace_back(e);
-  }
-
-  EXPECT_THAT(seen, ElementsAre(E::A, E::C));
-}
-
-TEST(EnumSetTest, Ostream) {
-  std::stringstream ss;
-  ss << EnumSet<E>(E::A, E::C);
-  EXPECT_EQ(ss.str(), "{A, C}");
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/hash.h b/src/utils/hash.h
deleted file mode 100644
index 91792bf..0000000
--- a/src/utils/hash.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_HASH_H_
-#define SRC_UTILS_HASH_H_
-
-#include <stdint.h>
-#include <cstdio>
-#include <functional>
-#include <vector>
-
-namespace tint {
-namespace utils {
-namespace detail {
-
-/// Helper for obtaining a seed bias value for HashCombine with a bit-width
-/// dependent on the size of size_t.
-template <int SIZE_OF_SIZE_T>
-struct HashCombineOffset {};
-
-/// Specialization of HashCombineOffset for size_t == 4.
-template <>
-struct HashCombineOffset<4> {
-  /// @returns the seed bias value for HashCombine()
-  static constexpr inline uint32_t value() { return 0x7f4a7c16; }
-};
-
-/// Specialization of HashCombineOffset for size_t == 8.
-template <>
-struct HashCombineOffset<8> {
-  /// @returns the seed bias value for HashCombine()
-  static constexpr inline uint64_t value() { return 0x9e3779b97f4a7c16; }
-};
-
-}  // namespace detail
-
-/// HashCombine "hashes" together an existing hash and hashable values.
-template <typename T>
-void HashCombine(size_t* hash, const T& value) {
-  constexpr size_t offset = detail::HashCombineOffset<sizeof(size_t)>::value();
-  *hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2);
-}
-
-/// HashCombine "hashes" together an existing hash and hashable values.
-template <typename T>
-void HashCombine(size_t* hash, const std::vector<T>& vector) {
-  HashCombine(hash, vector.size());
-  for (auto& el : vector) {
-    HashCombine(hash, el);
-  }
-}
-
-/// HashCombine "hashes" together an existing hash and hashable values.
-template <typename T, typename... ARGS>
-void HashCombine(size_t* hash, const T& value, const ARGS&... args) {
-  HashCombine(hash, value);
-  HashCombine(hash, args...);
-}
-
-/// @returns a hash of the combined arguments. The returned hash is dependent on
-/// the order of the arguments.
-template <typename... ARGS>
-size_t Hash(const ARGS&... args) {
-  size_t hash = 102931;  // seed with an arbitrary prime
-  HashCombine(&hash, args...);
-  return hash;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  // SRC_UTILS_HASH_H_
diff --git a/src/utils/hash_test.cc b/src/utils/hash_test.cc
deleted file mode 100644
index dd0877d..0000000
--- a/src/utils/hash_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/hash.h"
-
-#include <string>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(HashTests, Basic) {
-  EXPECT_EQ(Hash(123), Hash(123));
-  EXPECT_NE(Hash(123), Hash(321));
-  EXPECT_EQ(Hash(123, 456), Hash(123, 456));
-  EXPECT_NE(Hash(123, 456), Hash(456, 123));
-  EXPECT_NE(Hash(123, 456), Hash(123));
-  EXPECT_EQ(Hash(123, 456, false), Hash(123, 456, false));
-  EXPECT_NE(Hash(123, 456, false), Hash(123, 456));
-  EXPECT_EQ(Hash(std::string("hello")), Hash(std::string("hello")));
-  EXPECT_NE(Hash(std::string("hello")), Hash(std::string("world")));
-}
-
-TEST(HashTests, Vector) {
-  EXPECT_EQ(Hash(std::vector<int>({})), Hash(std::vector<int>({})));
-  EXPECT_EQ(Hash(std::vector<int>({1, 2, 3})),
-            Hash(std::vector<int>({1, 2, 3})));
-  EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
-            Hash(std::vector<int>({1, 2, 4})));
-  EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
-            Hash(std::vector<int>({1, 2, 3, 4})));
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/command.h b/src/utils/io/command.h
deleted file mode 100644
index acedc21..0000000
--- a/src/utils/io/command.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_IO_COMMAND_H_
-#define SRC_UTILS_IO_COMMAND_H_
-
-#include <string>
-#include <utility>
-
-namespace tint {
-namespace utils {
-
-/// Command is a helper used by tests for executing a process with a number of
-/// arguments and an optional stdin string, and then collecting and returning
-/// the process's stdout and stderr output as strings.
-class Command {
- public:
-  /// Output holds the output of the process
-  struct Output {
-    /// stdout from the process
-    std::string out;
-    /// stderr from the process
-    std::string err;
-    /// process error code
-    int error_code = 0;
-  };
-
-  /// Constructor
-  /// @param path path to the executable
-  explicit Command(const std::string& path);
-
-  /// Looks for an executable with the given name in the current working
-  /// directory, and if not found there, in each of the directories in the
-  /// `PATH` environment variable.
-  /// @param executable the executable name
-  /// @returns a Command which will return true for Found() if the executable
-  /// was found.
-  static Command LookPath(const std::string& executable);
-
-  /// @return true if the executable exists at the path provided to the
-  /// constructor
-  bool Found() const;
-
-  /// @returns the path of the command
-  const std::string& Path() const { return path_; }
-
-  /// Invokes the command with the given argument strings, blocking until the
-  /// process has returned.
-  /// @param args the string arguments to pass to the process
-  /// @returns the process output
-  template <typename... ARGS>
-  Output operator()(ARGS... args) const {
-    return Exec({std::forward<ARGS>(args)...});
-  }
-
-  /// Exec invokes the command with the given argument strings, blocking until
-  /// the process has returned.
-  /// @param args the string arguments to pass to the process
-  /// @returns the process output
-  Output Exec(std::initializer_list<std::string> args) const;
-
-  /// @param input the input data to pipe to the process's stdin
-  void SetInput(const std::string& input) { input_ = input; }
-
- private:
-  std::string const path_;
-  std::string input_;
-};
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_IO_COMMAND_H_
diff --git a/src/utils/io/command_other.cc b/src/utils/io/command_other.cc
deleted file mode 100644
index 90d3414..0000000
--- a/src/utils/io/command_other.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/command.h"
-
-namespace tint {
-namespace utils {
-
-Command::Command(const std::string&) {}
-
-Command Command::LookPath(const std::string&) {
-  return Command("");
-}
-
-bool Command::Found() const {
-  return false;
-}
-
-Command::Output Command::Exec(std::initializer_list<std::string>) const {
-  Output out;
-  out.err = "Command not supported by this target";
-  return out;
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/command_posix.cc b/src/utils/io/command_posix.cc
deleted file mode 100644
index b000ecb..0000000
--- a/src/utils/io/command_posix.cc
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/command.h"
-
-#include <sys/poll.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <sstream>
-#include <vector>
-
-namespace tint {
-namespace utils {
-
-namespace {
-
-/// File is a simple wrapper around a POSIX file descriptor
-class File {
-  constexpr static const int kClosed = -1;
-
- public:
-  /// Constructor
-  File() : handle_(kClosed) {}
-
-  /// Constructor
-  explicit File(int handle) : handle_(handle) {}
-
-  /// Destructor
-  ~File() { Close(); }
-
-  /// Move assignment operator
-  File& operator=(File&& rhs) {
-    Close();
-    handle_ = rhs.handle_;
-    rhs.handle_ = kClosed;
-    return *this;
-  }
-
-  /// Closes the file (if it wasn't already closed)
-  void Close() {
-    if (handle_ != kClosed) {
-      close(handle_);
-    }
-    handle_ = kClosed;
-  }
-
-  /// @returns the file handle
-  operator int() { return handle_; }
-
-  /// @returns true if the file is not closed
-  operator bool() { return handle_ != kClosed; }
-
- private:
-  File(const File&) = delete;
-  File& operator=(const File&) = delete;
-
-  int handle_ = kClosed;
-};
-
-/// Pipe is a simple wrapper around a POSIX pipe() function
-class Pipe {
- public:
-  /// Constructs the pipe
-  Pipe() {
-    int pipes[2] = {};
-    if (pipe(pipes) == 0) {
-      read = File(pipes[0]);
-      write = File(pipes[1]);
-    }
-  }
-
-  /// Closes both the read and write files (if they're not already closed)
-  void Close() {
-    read.Close();
-    write.Close();
-  }
-
-  /// @returns true if the pipe has an open read or write file
-  operator bool() { return read || write; }
-
-  /// The reader end of the pipe
-  File read;
-
-  /// The writer end of the pipe
-  File write;
-};
-
-bool ExecutableExists(const std::string& path) {
-  struct stat s {};
-  if (stat(path.c_str(), &s) != 0) {
-    return false;
-  }
-  return s.st_mode & S_IXUSR;
-}
-
-std::string FindExecutable(const std::string& name) {
-  if (ExecutableExists(name)) {
-    return name;
-  }
-  if (name.find("/") == std::string::npos) {
-    auto* path_env = getenv("PATH");
-    if (!path_env) {
-      return "";
-    }
-    std::istringstream path{path_env};
-    std::string dir;
-    while (getline(path, dir, ':')) {
-      auto test = dir + "/" + name;
-      if (ExecutableExists(test)) {
-        return test;
-      }
-    }
-  }
-  return "";
-}
-
-}  // namespace
-
-Command::Command(const std::string& path) : path_(path) {}
-
-Command Command::LookPath(const std::string& executable) {
-  return Command(FindExecutable(executable));
-}
-
-bool Command::Found() const {
-  return ExecutableExists(path_);
-}
-
-Command::Output Command::Exec(
-    std::initializer_list<std::string> arguments) const {
-  if (!Found()) {
-    Output out;
-    out.err = "Executable not found";
-    return out;
-  }
-
-  // Pipes used for piping std[in,out,err] to / from the target process.
-  Pipe stdin_pipe;
-  Pipe stdout_pipe;
-  Pipe stderr_pipe;
-
-  if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
-    Output output;
-    output.err = "Command::Exec(): Failed to create pipes";
-    return output;
-  }
-
-  // execv() and friends replace the current process image with the target
-  // process image. To keep process that called this function going, we need to
-  // fork() this process into a child and parent process.
-  //
-  // The child process is responsible for hooking up the pipes to
-  // std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
-  // run the target command.
-  //
-  // The parent process is responsible for feeding any input to the stdin_pipe
-  // and collectting output from the std[out,err]_pipes.
-
-  int child_id = fork();
-  if (child_id < 0) {
-    Output output;
-    output.err = "Command::Exec(): fork() failed";
-    return output;
-  }
-
-  if (child_id > 0) {
-    // fork() - parent
-
-    // Close the stdout and stderr writer pipes.
-    // This is required for getting poll() POLLHUP events.
-    stdout_pipe.write.Close();
-    stderr_pipe.write.Close();
-
-    // Write the input to the child process
-    if (!input_.empty()) {
-      ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
-      if (n != static_cast<ssize_t>(input_.size())) {
-        Output output;
-        output.err = "Command::Exec(): write() for stdin failed";
-        return output;
-      }
-    }
-    stdin_pipe.write.Close();
-
-    // Accumulate the stdout and stderr output from the child process
-    pollfd poll_fds[2];
-    poll_fds[0].fd = stdout_pipe.read;
-    poll_fds[0].events = POLLIN;
-    poll_fds[1].fd = stderr_pipe.read;
-    poll_fds[1].events = POLLIN;
-
-    Output output;
-    bool stdout_open = true;
-    bool stderr_open = true;
-    while (stdout_open || stderr_open) {
-      if (poll(poll_fds, 2, -1) < 0) {
-        break;
-      }
-      char buf[256];
-      if (poll_fds[0].revents & POLLIN) {
-        auto n = read(stdout_pipe.read, buf, sizeof(buf));
-        if (n > 0) {
-          output.out += std::string(buf, buf + n);
-        }
-      }
-      if (poll_fds[0].revents & POLLHUP) {
-        stdout_open = false;
-      }
-      if (poll_fds[1].revents & POLLIN) {
-        auto n = read(stderr_pipe.read, buf, sizeof(buf));
-        if (n > 0) {
-          output.err += std::string(buf, buf + n);
-        }
-      }
-      if (poll_fds[1].revents & POLLHUP) {
-        stderr_open = false;
-      }
-    }
-
-    // Get the resulting error code
-    waitpid(child_id, &output.error_code, 0);
-
-    return output;
-  } else {
-    // fork() - child
-
-    // Redirect the stdin, stdout, stderr pipes for the execv process
-    if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
-        (dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
-        (dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
-      fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
-      exit(errno);
-    }
-
-    // Close the pipes, once redirected above, we're now done with them.
-    stdin_pipe.Close();
-    stdout_pipe.Close();
-    stderr_pipe.Close();
-
-    // Run target executable
-    std::vector<const char*> args;
-    args.emplace_back(path_.c_str());
-    for (auto& arg : arguments) {
-      args.emplace_back(arg.c_str());
-    }
-    args.emplace_back(nullptr);
-    auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
-    exit(res);
-  }
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/command_test.cc b/src/utils/io/command_test.cc
deleted file mode 100644
index 8127dc2..0000000
--- a/src/utils/io/command_test.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/command.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-#ifdef _WIN32
-
-TEST(CommandTest, Echo) {
-  auto cmd = Command::LookPath("cmd");
-  if (!cmd.Found()) {
-    GTEST_SKIP() << "cmd not found on PATH";
-  }
-
-  auto res = cmd("/C", "echo", "hello world");
-  EXPECT_EQ(res.error_code, 0);
-  EXPECT_EQ(res.out, "hello world\r\n");
-  EXPECT_EQ(res.err, "");
-}
-
-#else
-
-TEST(CommandTest, Echo) {
-  auto cmd = Command::LookPath("echo");
-  if (!cmd.Found()) {
-    GTEST_SKIP() << "echo not found on PATH";
-  }
-
-  auto res = cmd("hello world");
-  EXPECT_EQ(res.error_code, 0);
-  EXPECT_EQ(res.out, "hello world\n");
-  EXPECT_EQ(res.err, "");
-}
-
-TEST(CommandTest, Cat) {
-  auto cmd = Command::LookPath("cat");
-  if (!cmd.Found()) {
-    GTEST_SKIP() << "cat not found on PATH";
-  }
-
-  cmd.SetInput("hello world");
-  auto res = cmd();
-  EXPECT_EQ(res.error_code, 0);
-  EXPECT_EQ(res.out, "hello world");
-  EXPECT_EQ(res.err, "");
-}
-
-TEST(CommandTest, True) {
-  auto cmd = Command::LookPath("true");
-  if (!cmd.Found()) {
-    GTEST_SKIP() << "true not found on PATH";
-  }
-
-  auto res = cmd();
-  EXPECT_EQ(res.error_code, 0);
-  EXPECT_EQ(res.out, "");
-  EXPECT_EQ(res.err, "");
-}
-
-TEST(CommandTest, False) {
-  auto cmd = Command::LookPath("false");
-  if (!cmd.Found()) {
-    GTEST_SKIP() << "false not found on PATH";
-  }
-
-  auto res = cmd();
-  EXPECT_NE(res.error_code, 0);
-  EXPECT_EQ(res.out, "");
-  EXPECT_EQ(res.err, "");
-}
-
-#endif
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/command_windows.cc b/src/utils/io/command_windows.cc
deleted file mode 100644
index 9b1a6bf..0000000
--- a/src/utils/io/command_windows.cc
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/command.h"
-
-#define WIN32_LEAN_AND_MEAN 1
-#include <Windows.h>
-#include <sstream>
-#include <string>
-
-namespace tint {
-namespace utils {
-
-namespace {
-
-/// Handle is a simple wrapper around the Win32 HANDLE
-class Handle {
- public:
-  /// Constructor
-  Handle() : handle_(nullptr) {}
-
-  /// Constructor
-  explicit Handle(HANDLE handle) : handle_(handle) {}
-
-  /// Destructor
-  ~Handle() { Close(); }
-
-  /// Move assignment operator
-  Handle& operator=(Handle&& rhs) {
-    Close();
-    handle_ = rhs.handle_;
-    rhs.handle_ = nullptr;
-    return *this;
-  }
-
-  /// Closes the handle (if it wasn't already closed)
-  void Close() {
-    if (handle_) {
-      CloseHandle(handle_);
-    }
-    handle_ = nullptr;
-  }
-
-  /// @returns the handle
-  operator HANDLE() { return handle_; }
-
-  /// @returns true if the handle is not invalid
-  operator bool() { return handle_ != nullptr; }
-
- private:
-  Handle(const Handle&) = delete;
-  Handle& operator=(const Handle&) = delete;
-
-  HANDLE handle_ = nullptr;
-};
-
-/// Pipe is a simple wrapper around a Win32 CreatePipe() function
-class Pipe {
- public:
-  /// Constructs the pipe
-  explicit Pipe(bool for_read) {
-    SECURITY_ATTRIBUTES sa;
-    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
-    sa.bInheritHandle = TRUE;
-    sa.lpSecurityDescriptor = nullptr;
-
-    HANDLE hread;
-    HANDLE hwrite;
-    if (CreatePipe(&hread, &hwrite, &sa, 0)) {
-      read = Handle(hread);
-      write = Handle(hwrite);
-      // Ensure the read handle to the pipe is not inherited
-      if (!SetHandleInformation(for_read ? read : write, HANDLE_FLAG_INHERIT,
-                                0)) {
-        read.Close();
-        write.Close();
-      }
-    }
-  }
-
-  /// @returns true if the pipe has an open read or write file
-  operator bool() { return read || write; }
-
-  /// The reader end of the pipe
-  Handle read;
-
-  /// The writer end of the pipe
-  Handle write;
-};
-
-bool ExecutableExists(const std::string& path) {
-  DWORD type = 0;
-  return GetBinaryTypeA(path.c_str(), &type);
-}
-
-std::string FindExecutable(const std::string& name) {
-  if (ExecutableExists(name)) {
-    return name;
-  }
-  if (ExecutableExists(name + ".exe")) {
-    return name + ".exe";
-  }
-  if (name.find("/") == std::string::npos &&
-      name.find("\\") == std::string::npos) {
-    char* path_env = nullptr;
-    size_t path_env_len = 0;
-    if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
-      return "";
-    }
-    std::istringstream path{path_env};
-    free(path_env);
-    std::string dir;
-    while (getline(path, dir, ';')) {
-      auto test = dir + "\\" + name;
-      if (ExecutableExists(test)) {
-        return test;
-      }
-      if (ExecutableExists(test + ".exe")) {
-        return test + ".exe";
-      }
-    }
-  }
-  return "";
-}
-
-}  // namespace
-
-Command::Command(const std::string& path) : path_(path) {}
-
-Command Command::LookPath(const std::string& executable) {
-  return Command(FindExecutable(executable));
-}
-
-bool Command::Found() const {
-  return ExecutableExists(path_);
-}
-
-Command::Output Command::Exec(
-    std::initializer_list<std::string> arguments) const {
-  Pipe stdout_pipe(true);
-  Pipe stderr_pipe(true);
-  Pipe stdin_pipe(false);
-  if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
-    Output output;
-    output.err = "Command::Exec(): Failed to create pipes";
-    return output;
-  }
-
-  if (!input_.empty()) {
-    if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr,
-                   nullptr)) {
-      Output output;
-      output.err = "Command::Exec() Failed to write stdin";
-      return output;
-    }
-  }
-  stdin_pipe.write.Close();
-
-  STARTUPINFOA si{};
-  si.cb = sizeof(si);
-  si.dwFlags |= STARTF_USESTDHANDLES;
-  si.hStdOutput = stdout_pipe.write;
-  si.hStdError = stderr_pipe.write;
-  si.hStdInput = stdin_pipe.read;
-
-  std::stringstream args;
-  args << path_;
-  for (auto& arg : arguments) {
-    args << " " << arg;
-  }
-
-  PROCESS_INFORMATION pi{};
-  if (!CreateProcessA(nullptr,  // No module name (use command line)
-                      const_cast<LPSTR>(args.str().c_str()),  // Command line
-                      nullptr,  // Process handle not inheritable
-                      nullptr,  // Thread handle not inheritable
-                      TRUE,     // Handles are inherited
-                      0,        // No creation flags
-                      nullptr,  // Use parent's environment block
-                      nullptr,  // Use parent's starting directory
-                      &si,      // Pointer to STARTUPINFO structure
-                      &pi)) {   // Pointer to PROCESS_INFORMATION structure
-    Output out;
-    out.err = "Command::Exec() CreateProcess() failed";
-    return out;
-  }
-
-  stdin_pipe.read.Close();
-  stdout_pipe.write.Close();
-  stderr_pipe.write.Close();
-
-  struct StreamReadThreadArgs {
-    HANDLE stream;
-    std::string output;
-  };
-
-  auto stream_read_thread = [](LPVOID user) -> DWORD {
-    auto* thread_args = reinterpret_cast<StreamReadThreadArgs*>(user);
-    DWORD n = 0;
-    char buf[256];
-    while (ReadFile(thread_args->stream, buf, sizeof(buf), &n, NULL)) {
-      auto s = std::string(buf, buf + n);
-      thread_args->output += std::string(buf, buf + n);
-    }
-    return 0;
-  };
-
-  StreamReadThreadArgs stdout_read_args{stdout_pipe.read, {}};
-  auto* stdout_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
-                                            &stdout_read_args, 0, nullptr);
-
-  StreamReadThreadArgs stderr_read_args{stderr_pipe.read, {}};
-  auto* stderr_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
-                                            &stderr_read_args, 0, nullptr);
-
-  HANDLE handles[] = {pi.hProcess, stdout_read_thread, stderr_read_thread};
-  constexpr DWORD num_handles = sizeof(handles) / sizeof(handles[0]);
-
-  Output output;
-
-  auto res = WaitForMultipleObjects(num_handles, handles, /* wait_all = */ TRUE,
-                                    INFINITE);
-  if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + num_handles) {
-    output.out = stdout_read_args.output;
-    output.err = stderr_read_args.output;
-    DWORD exit_code = 0;
-    GetExitCodeProcess(pi.hProcess, &exit_code);
-    output.error_code = static_cast<int>(exit_code);
-  } else {
-    output.err = "Command::Exec() WaitForMultipleObjects() returned " +
-                 std::to_string(res);
-  }
-
-  return output;
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/tmpfile.h b/src/utils/io/tmpfile.h
deleted file mode 100644
index 3ff3fb1..0000000
--- a/src/utils/io/tmpfile.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_IO_TMPFILE_H_
-#define SRC_UTILS_IO_TMPFILE_H_
-
-#include <sstream>
-#include <string>
-
-namespace tint {
-namespace utils {
-
-/// TmpFile constructs a temporary file that can be written to, and is
-/// automatically deleted on destruction.
-class TmpFile {
- public:
-  /// Constructor.
-  /// Creates a new temporary file which can be written to.
-  /// The temporary file will be automatically deleted on destruction.
-  /// @param extension optional file extension to use with the file. The file
-  /// have no extension by default.
-  explicit TmpFile(std::string extension = "");
-
-  /// Destructor.
-  /// Deletes the temporary file.
-  ~TmpFile();
-
-  /// @return true if the temporary file was successfully created.
-  operator bool() { return !path_.empty(); }
-
-  /// @return the path to the temporary file
-  std::string Path() const { return path_; }
-
-  /// Opens the temporary file and appends |size| bytes from |data| to the end
-  /// of the temporary file. The temporary file is closed again before
-  /// returning, allowing other processes to open the file on operating systems
-  /// that require exclusive ownership of opened files.
-  /// @param data the data to write to the end of the file
-  /// @param size the number of bytes to write from data
-  /// @returns true on success, otherwise false
-  bool Append(const void* data, size_t size) const;
-
-  /// Appends the argument to the end of the file.
-  /// @param data the data to write to the end of the file
-  /// @return a reference to this TmpFile
-  template <typename T>
-  inline TmpFile& operator<<(T&& data) {
-    std::stringstream ss;
-    ss << data;
-    std::string str = ss.str();
-    Append(str.data(), str.size());
-    return *this;
-  }
-
- private:
-  TmpFile(const TmpFile&) = delete;
-  TmpFile& operator=(const TmpFile&) = delete;
-
-  std::string path_;
-};
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_IO_TMPFILE_H_
diff --git a/src/utils/io/tmpfile_other.cc b/src/utils/io/tmpfile_other.cc
deleted file mode 100644
index dd3080f..0000000
--- a/src/utils/io/tmpfile_other.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/tmpfile.h"
-
-namespace tint {
-namespace utils {
-
-TmpFile::TmpFile(std::string) {}
-
-TmpFile::~TmpFile() = default;
-
-bool TmpFile::Append(const void*, size_t) const {
-  return false;
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/tmpfile_posix.cc b/src/utils/io/tmpfile_posix.cc
deleted file mode 100644
index d75764d..0000000
--- a/src/utils/io/tmpfile_posix.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/tmpfile.h"
-
-#include <unistd.h>
-#include <limits>
-
-#include "src/debug.h"
-
-namespace tint {
-namespace utils {
-
-namespace {
-
-std::string TmpFilePath(std::string ext) {
-  char const* dir = getenv("TMPDIR");
-  if (dir == nullptr) {
-    dir = "/tmp";
-  }
-
-  // mkstemps requires an `int` for the file extension name but STL represents
-  // size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion
-  // (when the source value exceeds the representable range) is implementation
-  // defined. While such a large file extension is unlikely in practice, we
-  // enforce this here at runtime.
-  TINT_ASSERT(Utils, ext.length() <=
-                         static_cast<size_t>(std::numeric_limits<int>::max()));
-  std::string name = std::string(dir) + "/tint_XXXXXX" + ext;
-  int file = mkstemps(&name[0], static_cast<int>(ext.length()));
-  if (file != -1) {
-    close(file);
-    return name;
-  }
-  return "";
-}
-
-}  // namespace
-
-TmpFile::TmpFile(std::string extension)
-    : path_(TmpFilePath(std::move(extension))) {}
-
-TmpFile::~TmpFile() {
-  if (!path_.empty()) {
-    remove(path_.c_str());
-  }
-}
-
-bool TmpFile::Append(const void* data, size_t size) const {
-  if (auto* file = fopen(path_.c_str(), "ab")) {
-    fwrite(data, size, 1, file);
-    fclose(file);
-    return true;
-  }
-  return false;
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/tmpfile_test.cc b/src/utils/io/tmpfile_test.cc
deleted file mode 100644
index b8c04f2..0000000
--- a/src/utils/io/tmpfile_test.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/tmpfile.h"
-
-#include <fstream>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(TmpFileTest, WriteReadAppendDelete) {
-  std::string path;
-  {
-    TmpFile tmp;
-    if (!tmp) {
-      GTEST_SKIP() << "Unable to create a temporary file";
-    }
-
-    path = tmp.Path();
-
-    // Write a string to the temporary file
-    tmp << "hello world\n";
-
-    // Check the content of the file
-    {
-      std::ifstream file(path);
-      ASSERT_TRUE(file);
-      std::string line;
-      EXPECT_TRUE(std::getline(file, line));
-      EXPECT_EQ(line, "hello world");
-      EXPECT_FALSE(std::getline(file, line));
-    }
-
-    // Write some more content to the file
-    tmp << 42;
-
-    // Check the content of the file again
-    {
-      std::ifstream file(path);
-      ASSERT_TRUE(file);
-      std::string line;
-      EXPECT_TRUE(std::getline(file, line));
-      EXPECT_EQ(line, "hello world");
-      EXPECT_TRUE(std::getline(file, line));
-      EXPECT_EQ(line, "42");
-      EXPECT_FALSE(std::getline(file, line));
-    }
-  }
-
-  // Check the file has been deleted when it fell out of scope
-  std::ifstream file(path);
-  ASSERT_FALSE(file);
-}
-
-TEST(TmpFileTest, FileExtension) {
-  const std::string kExt = ".foo";
-  std::string path;
-  {
-    TmpFile tmp(kExt);
-    if (!tmp) {
-      GTEST_SKIP() << "Unable create a temporary file";
-    }
-    path = tmp.Path();
-  }
-
-  ASSERT_GT(path.length(), kExt.length());
-  EXPECT_EQ(kExt, path.substr(path.length() - kExt.length()));
-
-  // Check the file has been deleted when it fell out of scope
-  std::ifstream file(path);
-  ASSERT_FALSE(file);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/io/tmpfile_windows.cc b/src/utils/io/tmpfile_windows.cc
deleted file mode 100644
index a641d5f..0000000
--- a/src/utils/io/tmpfile_windows.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/io/tmpfile.h"
-
-#include <stdio.h>
-#include <cstdio>
-
-namespace tint {
-namespace utils {
-
-namespace {
-
-std::string TmpFilePath(const std::string& ext) {
-  char name[L_tmpnam];
-  // As we're adding an extension, to ensure the file is really unique, try
-  // creating it, failing if it already exists.
-  while (tmpnam_s(name, L_tmpnam - 1) == 0) {
-    std::string name_with_ext = std::string(name) + ext;
-    FILE* f = nullptr;
-    // The "x" arg forces the function to fail if the file already exists.
-    fopen_s(&f, name_with_ext.c_str(), "wbx");
-    if (f) {
-      fclose(f);
-      return name_with_ext;
-    }
-  }
-  return {};
-}
-
-}  // namespace
-
-TmpFile::TmpFile(std::string ext) : path_(TmpFilePath(ext)) {}
-
-TmpFile::~TmpFile() {
-  if (!path_.empty()) {
-    remove(path_.c_str());
-  }
-}
-
-bool TmpFile::Append(const void* data, size_t size) const {
-  FILE* file = nullptr;
-  if (fopen_s(&file, path_.c_str(), "ab") != 0) {
-    return false;
-  }
-  fwrite(data, size, 1, file);
-  fclose(file);
-  return true;
-}
-
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/map.h b/src/utils/map.h
deleted file mode 100644
index a84c9ba..0000000
--- a/src/utils/map.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_MAP_H_
-#define SRC_UTILS_MAP_H_
-
-#include <unordered_map>
-
-namespace tint {
-namespace utils {
-
-/// Lookup is a utility function for fetching a value from an unordered map if
-/// it exists, otherwise returning the `if_missing` argument.
-/// @param map the unordered_map
-/// @param key the map key of the item to query
-/// @param if_missing the value to return if the map does not contain the given
-/// key. Defaults to the zero-initializer for the value type.
-/// @return the map item value, or `if_missing` if the map does not contain the
-/// given key
-template <typename K, typename V, typename H, typename C, typename KV = K>
-V Lookup(const std::unordered_map<K, V, H, C>& map,
-         const KV& key,
-         const V& if_missing = {}) {
-  auto it = map.find(key);
-  return it != map.end() ? it->second : if_missing;
-}
-
-/// GetOrCreate is a utility function for lazily adding to an unordered map.
-/// If the map already contains the key `key` then this is returned, otherwise
-/// `create()` is called and the result is added to the map and is returned.
-/// @param map the unordered_map
-/// @param key the map key of the item to query or add
-/// @param create a callable function-like object with the signature `V()`
-/// @return the value of the item with the given key, or the newly created item
-template <typename K, typename V, typename H, typename C, typename CREATE>
-V GetOrCreate(std::unordered_map<K, V, H, C>& map,
-              const K& key,
-              CREATE&& create) {
-  auto it = map.find(key);
-  if (it != map.end()) {
-    return it->second;
-  }
-  V value = create();
-  map.emplace(key, value);
-  return value;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_MAP_H_
diff --git a/src/utils/map_test.cc b/src/utils/map_test.cc
deleted file mode 100644
index 11f3ba8..0000000
--- a/src/utils/map_test.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/map.h"
-
-#include <unordered_map>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(Lookup, Test) {
-  std::unordered_map<int, int> map;
-  map.emplace(10, 1);
-  EXPECT_EQ(Lookup(map, 10, 0), 1);    // exists, with if_missing
-  EXPECT_EQ(Lookup(map, 10), 1);       // exists, without if_missing
-  EXPECT_EQ(Lookup(map, 20, 50), 50);  // missing, with if_missing
-  EXPECT_EQ(Lookup(map, 20), 0);       // missing, without if_missing
-}
-
-TEST(GetOrCreateTest, NewKey) {
-  std::unordered_map<int, int> map;
-  EXPECT_EQ(GetOrCreate(map, 1, [&] { return 2; }), 2);
-  EXPECT_EQ(map.size(), 1u);
-  EXPECT_EQ(map[1], 2);
-}
-
-TEST(GetOrCreateTest, ExistingKey) {
-  std::unordered_map<int, int> map;
-  map[1] = 2;
-  bool called = false;
-  EXPECT_EQ(GetOrCreate(map, 1,
-                        [&] {
-                          called = true;
-                          return -2;
-                        }),
-            2);
-  EXPECT_EQ(called, false);
-  EXPECT_EQ(map.size(), 1u);
-  EXPECT_EQ(map[1], 2);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/math.h b/src/utils/math.h
deleted file mode 100644
index ce921b1..0000000
--- a/src/utils/math.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_MATH_H_
-#define SRC_UTILS_MATH_H_
-
-#include <sstream>
-#include <string>
-#include <type_traits>
-
-namespace tint {
-namespace utils {
-
-/// @param alignment the next multiple to round `value` to
-/// @param value the value to round to the next multiple of `alignment`
-/// @return `value` rounded to the next multiple of `alignment`
-/// @note `alignment` must be positive. An alignment of zero will cause a DBZ.
-template <typename T>
-inline T RoundUp(T alignment, T value) {
-  return ((value + alignment - 1) / alignment) * alignment;
-}
-
-/// @param value the value to check whether it is a power-of-two
-/// @returns true if `value` is a power-of-two
-/// @note `value` must be positive if `T` is signed
-template <typename T>
-inline bool IsPowerOfTwo(T value) {
-  return (value & (value - 1)) == 0;
-}
-
-/// @param value the input value
-/// @returns the largest power of two that `value` is a multiple of
-template <typename T>
-inline std::enable_if_t<std::is_unsigned<T>::value, T> MaxAlignOf(T value) {
-  T pot = 1;
-  while (value && ((value & 1u) == 0)) {
-    pot <<= 1;
-    value >>= 1;
-  }
-  return pot;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_MATH_H_
diff --git a/src/utils/math_test.cc b/src/utils/math_test.cc
deleted file mode 100644
index 933d594..0000000
--- a/src/utils/math_test.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/math.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(MathTests, RoundUp) {
-  EXPECT_EQ(RoundUp(1, 0), 0);
-  EXPECT_EQ(RoundUp(1, 1), 1);
-  EXPECT_EQ(RoundUp(1, 2), 2);
-
-  EXPECT_EQ(RoundUp(1, 1), 1);
-  EXPECT_EQ(RoundUp(2, 1), 2);
-  EXPECT_EQ(RoundUp(3, 1), 3);
-  EXPECT_EQ(RoundUp(4, 1), 4);
-
-  EXPECT_EQ(RoundUp(1, 2), 2);
-  EXPECT_EQ(RoundUp(2, 2), 2);
-  EXPECT_EQ(RoundUp(3, 2), 3);
-  EXPECT_EQ(RoundUp(4, 2), 4);
-
-  EXPECT_EQ(RoundUp(1, 3), 3);
-  EXPECT_EQ(RoundUp(2, 3), 4);
-  EXPECT_EQ(RoundUp(3, 3), 3);
-  EXPECT_EQ(RoundUp(4, 3), 4);
-
-  EXPECT_EQ(RoundUp(1, 4), 4);
-  EXPECT_EQ(RoundUp(2, 4), 4);
-  EXPECT_EQ(RoundUp(3, 4), 6);
-  EXPECT_EQ(RoundUp(4, 4), 4);
-}
-
-TEST(MathTests, IsPowerOfTwo) {
-  EXPECT_EQ(IsPowerOfTwo(1), true);
-  EXPECT_EQ(IsPowerOfTwo(2), true);
-  EXPECT_EQ(IsPowerOfTwo(3), false);
-  EXPECT_EQ(IsPowerOfTwo(4), true);
-  EXPECT_EQ(IsPowerOfTwo(5), false);
-  EXPECT_EQ(IsPowerOfTwo(6), false);
-  EXPECT_EQ(IsPowerOfTwo(7), false);
-  EXPECT_EQ(IsPowerOfTwo(8), true);
-  EXPECT_EQ(IsPowerOfTwo(9), false);
-}
-
-TEST(MathTests, MaxAlignOf) {
-  EXPECT_EQ(MaxAlignOf(0u), 1u);
-  EXPECT_EQ(MaxAlignOf(1u), 1u);
-  EXPECT_EQ(MaxAlignOf(2u), 2u);
-  EXPECT_EQ(MaxAlignOf(3u), 1u);
-  EXPECT_EQ(MaxAlignOf(4u), 4u);
-  EXPECT_EQ(MaxAlignOf(5u), 1u);
-  EXPECT_EQ(MaxAlignOf(6u), 2u);
-  EXPECT_EQ(MaxAlignOf(7u), 1u);
-  EXPECT_EQ(MaxAlignOf(8u), 8u);
-  EXPECT_EQ(MaxAlignOf(9u), 1u);
-  EXPECT_EQ(MaxAlignOf(10u), 2u);
-  EXPECT_EQ(MaxAlignOf(11u), 1u);
-  EXPECT_EQ(MaxAlignOf(12u), 4u);
-  EXPECT_EQ(MaxAlignOf(13u), 1u);
-  EXPECT_EQ(MaxAlignOf(14u), 2u);
-  EXPECT_EQ(MaxAlignOf(15u), 1u);
-  EXPECT_EQ(MaxAlignOf(16u), 16u);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/reverse.h b/src/utils/reverse.h
deleted file mode 100644
index 0848dc5..0000000
--- a/src/utils/reverse.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_REVERSE_H_
-#define SRC_UTILS_REVERSE_H_
-
-#include <iterator>
-
-namespace tint {
-namespace utils {
-
-namespace detail {
-/// Used by utils::Reverse to hold the underlying iterable.
-/// begin(ReverseIterable<T>) and end(ReverseIterable<T>) are automatically
-/// called for range-for loops, via argument-dependent lookup.
-/// See https://en.cppreference.com/w/cpp/language/range-for
-template <typename T>
-struct ReverseIterable {
-  /// The wrapped iterable object.
-  T& iterable;
-};
-
-template <typename T>
-auto begin(ReverseIterable<T> r_it) {
-  return std::rbegin(r_it.iterable);
-}
-
-template <typename T>
-auto end(ReverseIterable<T> r_it) {
-  return std::rend(r_it.iterable);
-}
-}  // namespace detail
-
-/// Reverse returns an iterable wrapper that when used in range-for loops,
-/// performs a reverse iteration over the object `iterable`.
-/// Example:
-/// ```
-/// /* Equivalent to:
-///  * for (auto it = vec.rbegin(); i != vec.rend(); ++it) {
-///  *   auto v = *it;
-///  */
-/// for (auto v : utils::Reverse(vec)) {
-/// }
-/// ```
-template <typename T>
-detail::ReverseIterable<T> Reverse(T&& iterable) {
-  return {iterable};
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  // SRC_UTILS_REVERSE_H_
diff --git a/src/utils/reverse_test.cc b/src/utils/reverse_test.cc
deleted file mode 100644
index 25be0eb..0000000
--- a/src/utils/reverse_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/reverse.h"
-
-#include <vector>
-
-#include "gmock/gmock.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(ReverseTest, Vector) {
-  std::vector<int> vec{1, 3, 5, 7, 9};
-  std::vector<int> rev;
-  for (auto v : Reverse(vec)) {
-    rev.emplace_back(v);
-  }
-  ASSERT_THAT(rev, testing::ElementsAre(9, 7, 5, 3, 1));
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/scoped_assignment.h b/src/utils/scoped_assignment.h
deleted file mode 100644
index 99f8ba4..0000000
--- a/src/utils/scoped_assignment.h
+++ /dev/null
@@ -1,64 +0,0 @@
-
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_SCOPED_ASSIGNMENT_H_
-#define SRC_UTILS_SCOPED_ASSIGNMENT_H_
-
-#include <type_traits>
-
-#include "src/utils/concat.h"
-
-namespace tint {
-namespace utils {
-
-/// Helper class that temporarily assigns a value to a variable for the lifetime
-/// of the ScopedAssignment object. Once the ScopedAssignment is destructed, the
-/// original value is restored.
-template <typename T>
-class ScopedAssignment {
- public:
-  /// Constructor
-  /// @param var the variable to temporarily assign a new value to
-  /// @param val the value to assign to `ref` for the lifetime of this
-  /// ScopedAssignment.
-  ScopedAssignment(T& var, T val) : ref_(var) {
-    old_value_ = var;
-    var = val;
-  }
-
-  /// Destructor
-  /// Restores the original value of the variable.
-  ~ScopedAssignment() { ref_ = old_value_; }
-
- private:
-  ScopedAssignment(const ScopedAssignment&) = delete;
-  ScopedAssignment& operator=(const ScopedAssignment&) = delete;
-
-  T& ref_;
-  T old_value_;
-};
-
-}  // namespace utils
-}  // namespace tint
-
-/// TINT_SCOPED_ASSIGNMENT(var, val) assigns `val` to `var`, and automatically
-/// restores the original value of `var` when exiting the current lexical scope.
-#define TINT_SCOPED_ASSIGNMENT(var, val)                                  \
-  ::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> \
-  TINT_CONCAT(tint_scoped_assignment_, __COUNTER__) {                     \
-    var, val                                                              \
-  }
-
-#endif  //  SRC_UTILS_SCOPED_ASSIGNMENT_H_
diff --git a/src/utils/scoped_assignment_test.cc b/src/utils/scoped_assignment_test.cc
deleted file mode 100644
index b75bec9..0000000
--- a/src/utils/scoped_assignment_test.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/scoped_assignment.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(ScopedAssignmentTest, Scopes) {
-  int i = 0;
-  EXPECT_EQ(i, 0);
-  {
-    EXPECT_EQ(i, 0);
-    TINT_SCOPED_ASSIGNMENT(i, 1);
-    EXPECT_EQ(i, 1);
-    {
-      EXPECT_EQ(i, 1);
-      TINT_SCOPED_ASSIGNMENT(i, 2);
-      EXPECT_EQ(i, 2);
-    }
-    {
-      EXPECT_EQ(i, 1);
-      TINT_SCOPED_ASSIGNMENT(i, 3);
-      EXPECT_EQ(i, 3);
-    }
-    EXPECT_EQ(i, 1);
-  }
-  EXPECT_EQ(i, 0);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/string.h b/src/utils/string.h
deleted file mode 100644
index 2e93f51..0000000
--- a/src/utils/string.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_STRING_H_
-#define SRC_UTILS_STRING_H_
-
-#include <string>
-
-namespace tint {
-namespace utils {
-
-/// @param str the string to apply replacements to
-/// @param substr the string to search for
-/// @param replacement the replacement string to use instead of `substr`
-/// @returns `str` with all occurrences of `substr` replaced with `replacement`
-inline std::string ReplaceAll(std::string str,
-                              const std::string& substr,
-                              const std::string& replacement) {
-  size_t pos = 0;
-  while ((pos = str.find(substr, pos)) != std::string::npos) {
-    str.replace(pos, substr.length(), replacement);
-    pos += replacement.length();
-  }
-  return str;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_STRING_H_
diff --git a/src/utils/string_test.cc b/src/utils/string_test.cc
deleted file mode 100644
index 5b18470..0000000
--- a/src/utils/string_test.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/string.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(StringTest, ReplaceAll) {
-  ASSERT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
-  ASSERT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
-  ASSERT_EQ("aabbxy", ReplaceAll("aabbcc", "cc", "xy"));
-  ASSERT_EQ("xyxybbcc", ReplaceAll("aabbcc", "a", "xy"));
-  ASSERT_EQ("aaxyxycc", ReplaceAll("aabbcc", "b", "xy"));
-  ASSERT_EQ("aabbxyxy", ReplaceAll("aabbcc", "c", "xy"));
-  // Replacement string includes the searched-for string.
-  // This proves that the algorithm needs to advance 'pos'
-  // past the replacement.
-  ASSERT_EQ("aabxybbxybcc", ReplaceAll("aabbcc", "b", "bxyb"));
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/to_const_ptr_vec.h b/src/utils/to_const_ptr_vec.h
deleted file mode 100644
index 25e9261..0000000
--- a/src/utils/to_const_ptr_vec.h
+++ /dev/null
@@ -1,39 +0,0 @@
-
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_TO_CONST_PTR_VEC_H_
-#define SRC_UTILS_TO_CONST_PTR_VEC_H_
-
-#include <vector>
-
-namespace tint {
-namespace utils {
-
-/// @param in a vector of `T*`
-/// @returns a vector of `const T*` with the content of `in`.
-template <typename T>
-std::vector<const T*> ToConstPtrVec(const std::vector<T*>& in) {
-  std::vector<const T*> out;
-  out.reserve(in.size());
-  for (auto* ptr : in) {
-    out.emplace_back(ptr);
-  }
-  return out;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_TO_CONST_PTR_VEC_H_
diff --git a/src/utils/transform.h b/src/utils/transform.h
deleted file mode 100644
index cd31ec5..0000000
--- a/src/utils/transform.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Tint Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_TRANSFORM_H_
-#define SRC_UTILS_TRANSFORM_H_
-
-#include <algorithm>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-#include "src/traits.h"
-
-namespace tint {
-namespace utils {
-
-/// Transform performs an element-wise transformation of a vector.
-/// @param in the input vector.
-/// @param transform the transformation function with signature: `OUT(IN)`
-/// @returns a new vector with each element of the source vector transformed by
-/// `transform`.
-template <typename IN, typename TRANSFORMER>
-auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
-    -> std::vector<decltype(transform(in[0]))> {
-  std::vector<decltype(transform(in[0]))> result(in.size());
-  for (size_t i = 0; i < result.size(); ++i) {
-    result[i] = transform(in[i]);
-  }
-  return result;
-}
-
-/// Transform performs an element-wise transformation of a vector.
-/// @param in the input vector.
-/// @param transform the transformation function with signature:
-/// `OUT(IN, size_t)`
-/// @returns a new vector with each element of the source vector transformed by
-/// `transform`.
-template <typename IN, typename TRANSFORMER>
-auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
-    -> std::vector<decltype(transform(in[0], 1u))> {
-  std::vector<decltype(transform(in[0], 1u))> result(in.size());
-  for (size_t i = 0; i < result.size(); ++i) {
-    result[i] = transform(in[i], i);
-  }
-  return result;
-}
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  // SRC_UTILS_TRANSFORM_H_
diff --git a/src/utils/transform_test.cc b/src/utils/transform_test.cc
deleted file mode 100644
index 32498be..0000000
--- a/src/utils/transform_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/transform.h"
-
-#include <string>
-#include <type_traits>
-
-#include "gmock/gmock.h"
-
-#define CHECK_ELEMENT_TYPE(vector, expected)                                 \
-  static_assert(std::is_same<decltype(vector)::value_type, expected>::value, \
-                "unexpected result vector element type")
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(TransformTest, Empty) {
-  const std::vector<int> empty{};
-  {
-    auto transformed = Transform(empty, [](int) -> int {
-      [] { FAIL() << "Transform should not be called for empty vector"; }();
-      return 0;
-    });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_EQ(transformed.size(), 0u);
-  }
-  {
-    auto transformed = Transform(empty, [](int, size_t) -> int {
-      [] { FAIL() << "Transform should not be called for empty vector"; }();
-      return 0;
-    });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_EQ(transformed.size(), 0u);
-  }
-}
-
-TEST(TransformTest, Identity) {
-  const std::vector<int> input{1, 2, 3, 4};
-  {
-    auto transformed = Transform(input, [](int i) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-  }
-  {
-    auto transformed = Transform(input, [](int i, size_t) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-  }
-}
-
-TEST(TransformTest, Index) {
-  const std::vector<int> input{10, 20, 30, 40};
-  {
-    auto transformed = Transform(input, [](int, size_t idx) { return idx; });
-    CHECK_ELEMENT_TYPE(transformed, size_t);
-    EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
-  }
-}
-
-TEST(TransformTest, TransformSameType) {
-  const std::vector<int> input{1, 2, 3, 4};
-  {
-    auto transformed = Transform(input, [](int i) { return i * 10; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
-  }
-}
-
-TEST(TransformTest, TransformDifferentType) {
-  const std::vector<int> input{1, 2, 3, 4};
-  {
-    auto transformed =
-        Transform(input, [](int i) { return std::to_string(i); });
-    CHECK_ELEMENT_TYPE(transformed, std::string);
-    EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
-  }
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/utils/unique_vector.h b/src/utils/unique_vector.h
deleted file mode 100644
index e56dd99..0000000
--- a/src/utils/unique_vector.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_UTILS_UNIQUE_VECTOR_H_
-#define SRC_UTILS_UNIQUE_VECTOR_H_
-
-#include <cstddef>
-#include <functional>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-namespace tint {
-namespace utils {
-
-/// UniqueVector is an ordered container that only contains unique items.
-/// Attempting to add a duplicate is a no-op.
-template <typename T,
-          typename HASH = std::hash<T>,
-          typename EQUAL = std::equal_to<T>>
-struct UniqueVector {
-  /// The iterator returned by begin() and end()
-  using ConstIterator = typename std::vector<T>::const_iterator;
-  /// The iterator returned by rbegin() and rend()
-  using ConstReverseIterator = typename std::vector<T>::const_reverse_iterator;
-
-  /// Constructor
-  UniqueVector() = default;
-
-  /// Constructor
-  /// @param v the vector to construct this UniqueVector with. Duplicate
-  /// elements will be removed.
-  explicit UniqueVector(std::vector<T>&& v) {
-    for (auto& el : v) {
-      add(el);
-    }
-  }
-
-  /// add appends the item to the end of the vector, if the vector does not
-  /// already contain the given item.
-  /// @param item the item to append to the end of the vector
-  /// @returns true if the item was added, otherwise false.
-  bool add(const T& item) {
-    if (set.count(item) == 0) {
-      vector.emplace_back(item);
-      set.emplace(item);
-      return true;
-    }
-    return false;
-  }
-
-  /// @returns true if the vector contains `item`
-  /// @param item the item
-  bool contains(const T& item) const { return set.count(item); }
-
-  /// @param i the index of the element to retrieve
-  /// @returns the element at the index `i`
-  T& operator[](size_t i) { return vector[i]; }
-
-  /// @param i the index of the element to retrieve
-  /// @returns the element at the index `i`
-  const T& operator[](size_t i) const { return vector[i]; }
-
-  /// @returns true if the vector is empty
-  bool empty() const { return vector.empty(); }
-
-  /// @returns the number of items in the vector
-  size_t size() const { return vector.size(); }
-
-  /// @returns an iterator to the beginning of the vector
-  ConstIterator begin() const { return vector.begin(); }
-
-  /// @returns an iterator to the end of the vector
-  ConstIterator end() const { return vector.end(); }
-
-  /// @returns an iterator to the beginning of the reversed vector
-  ConstReverseIterator rbegin() const { return vector.rbegin(); }
-
-  /// @returns an iterator to the end of the reversed vector
-  ConstReverseIterator rend() const { return vector.rend(); }
-
-  /// @returns a const reference to the internal vector
-  operator const std::vector<T>&() const { return vector; }
-
-  /// Removes the last element from the vector
-  /// @returns the popped element
-  T pop_back() {
-    auto el = std::move(vector.back());
-    set.erase(el);
-    vector.pop_back();
-    return el;
-  }
-
- private:
-  std::vector<T> vector;
-  std::unordered_set<T, HASH, EQUAL> set;
-};
-
-}  // namespace utils
-}  // namespace tint
-
-#endif  //  SRC_UTILS_UNIQUE_VECTOR_H_
diff --git a/src/utils/unique_vector_test.cc b/src/utils/unique_vector_test.cc
deleted file mode 100644
index 3ec78fa..0000000
--- a/src/utils/unique_vector_test.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/utils/unique_vector.h"
-#include "src/utils/reverse.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace utils {
-namespace {
-
-TEST(UniqueVectorTest, Empty) {
-  UniqueVector<int> unique_vec;
-  EXPECT_EQ(unique_vec.size(), 0u);
-  EXPECT_EQ(unique_vec.empty(), true);
-  EXPECT_EQ(unique_vec.begin(), unique_vec.end());
-}
-
-TEST(UniqueVectorTest, MoveConstructor) {
-  UniqueVector<int> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
-  EXPECT_EQ(unique_vec.size(), 4u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  EXPECT_EQ(unique_vec[0], 0);
-  EXPECT_EQ(unique_vec[1], 3);
-  EXPECT_EQ(unique_vec[2], 2);
-  EXPECT_EQ(unique_vec[3], 1);
-}
-
-TEST(UniqueVectorTest, AddUnique) {
-  UniqueVector<int> unique_vec;
-  unique_vec.add(0);
-  unique_vec.add(1);
-  unique_vec.add(2);
-  EXPECT_EQ(unique_vec.size(), 3u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  int i = 0;
-  for (auto n : unique_vec) {
-    EXPECT_EQ(n, i);
-    i++;
-  }
-  for (auto n : Reverse(unique_vec)) {
-    i--;
-    EXPECT_EQ(n, i);
-  }
-  EXPECT_EQ(unique_vec[0], 0);
-  EXPECT_EQ(unique_vec[1], 1);
-  EXPECT_EQ(unique_vec[2], 2);
-}
-
-TEST(UniqueVectorTest, AddDuplicates) {
-  UniqueVector<int> unique_vec;
-  unique_vec.add(0);
-  unique_vec.add(0);
-  unique_vec.add(0);
-  unique_vec.add(1);
-  unique_vec.add(1);
-  unique_vec.add(2);
-  EXPECT_EQ(unique_vec.size(), 3u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  int i = 0;
-  for (auto n : unique_vec) {
-    EXPECT_EQ(n, i);
-    i++;
-  }
-  for (auto n : Reverse(unique_vec)) {
-    i--;
-    EXPECT_EQ(n, i);
-  }
-  EXPECT_EQ(unique_vec[0], 0);
-  EXPECT_EQ(unique_vec[1], 1);
-  EXPECT_EQ(unique_vec[2], 2);
-}
-
-TEST(UniqueVectorTest, AsVector) {
-  UniqueVector<int> unique_vec;
-  unique_vec.add(0);
-  unique_vec.add(0);
-  unique_vec.add(0);
-  unique_vec.add(1);
-  unique_vec.add(1);
-  unique_vec.add(2);
-
-  const std::vector<int>& vec = unique_vec;
-  EXPECT_EQ(vec.size(), 3u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  int i = 0;
-  for (auto n : vec) {
-    EXPECT_EQ(n, i);
-    i++;
-  }
-  for (auto n : Reverse(unique_vec)) {
-    i--;
-    EXPECT_EQ(n, i);
-  }
-}
-
-TEST(UniqueVectorTest, PopBack) {
-  UniqueVector<int> unique_vec;
-  unique_vec.add(0);
-  unique_vec.add(2);
-  unique_vec.add(1);
-
-  EXPECT_EQ(unique_vec.pop_back(), 1);
-  EXPECT_EQ(unique_vec.size(), 2u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  EXPECT_EQ(unique_vec[0], 0);
-  EXPECT_EQ(unique_vec[1], 2);
-
-  EXPECT_EQ(unique_vec.pop_back(), 2);
-  EXPECT_EQ(unique_vec.size(), 1u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  EXPECT_EQ(unique_vec[0], 0);
-
-  unique_vec.add(1);
-
-  EXPECT_EQ(unique_vec.size(), 2u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  EXPECT_EQ(unique_vec[0], 0);
-  EXPECT_EQ(unique_vec[1], 1);
-
-  EXPECT_EQ(unique_vec.pop_back(), 1);
-  EXPECT_EQ(unique_vec.size(), 1u);
-  EXPECT_EQ(unique_vec.empty(), false);
-  EXPECT_EQ(unique_vec[0], 0);
-
-  EXPECT_EQ(unique_vec.pop_back(), 0);
-  EXPECT_EQ(unique_vec.size(), 0u);
-  EXPECT_EQ(unique_vec.empty(), true);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace tint
diff --git a/src/val/hlsl.cc b/src/val/hlsl.cc
deleted file mode 100644
index 696230b..0000000
--- a/src/val/hlsl.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/val/val.h"
-
-#include "src/utils/io/command.h"
-#include "src/utils/io/tmpfile.h"
-
-#ifdef _WIN32
-#include <Windows.h>
-#include <d3dcommon.h>
-#include <d3dcompiler.h>
-
-#include <wrl.h>
-using Microsoft::WRL::ComPtr;
-#endif  // _WIN32
-
-namespace tint {
-namespace val {
-
-Result HlslUsingDXC(const std::string& dxc_path,
-                    const std::string& source,
-                    const EntryPointList& entry_points) {
-  Result result;
-
-  auto dxc = utils::Command(dxc_path);
-  if (!dxc.Found()) {
-    result.output = "DXC not found at '" + std::string(dxc_path) + "'";
-    result.failed = true;
-    return result;
-  }
-
-  utils::TmpFile file;
-  file << source;
-
-  for (auto ep : entry_points) {
-    const char* profile = "";
-
-    switch (ep.second) {
-      case ast::PipelineStage::kNone:
-        result.output = "Invalid PipelineStage";
-        result.failed = true;
-        return result;
-      case ast::PipelineStage::kVertex:
-        profile = "-T vs_6_0";
-        break;
-      case ast::PipelineStage::kFragment:
-        profile = "-T ps_6_0";
-        break;
-      case ast::PipelineStage::kCompute:
-        profile = "-T cs_6_0";
-        break;
-    }
-
-    // Match Dawn's compile flags
-    // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
-    // and dawn_native\d3d12\ShaderModuleD3D12.cpp (GetDXCArguments)
-    const char* compileFlags =
-        "/Zpr "  // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
-        "/Gis";  // D3DCOMPILE_IEEE_STRICTNESS
-
-    auto res = dxc(profile, "-E " + ep.first, compileFlags, file.Path());
-    if (!res.out.empty()) {
-      if (!result.output.empty()) {
-        result.output += "\n";
-      }
-      result.output += res.out;
-    }
-    if (!res.err.empty()) {
-      if (!result.output.empty()) {
-        result.output += "\n";
-      }
-      result.output += res.err;
-    }
-    result.failed = (res.error_code != 0);
-  }
-
-  if (entry_points.empty()) {
-    result.output = "No entrypoint found";
-    result.failed = true;
-    return result;
-  }
-
-  return result;
-}
-
-#ifdef _WIN32
-Result HlslUsingFXC(const std::string& source,
-                    const EntryPointList& entry_points) {
-  Result result;
-
-  // This library leaks if an error happens in this function, but it is ok
-  // because it is loaded at most once, and the executables using HlslUsingFXC
-  // are short-lived.
-  HMODULE fxcLib = LoadLibraryA("d3dcompiler_47.dll");
-  if (fxcLib == nullptr) {
-    result.output = "Couldn't load FXC";
-    result.failed = true;
-    return result;
-  }
-
-  pD3DCompile d3dCompile =
-      reinterpret_cast<pD3DCompile>(GetProcAddress(fxcLib, "D3DCompile"));
-  if (d3dCompile == nullptr) {
-    result.output = "Couldn't load D3DCompile from FXC";
-    result.failed = true;
-    return result;
-  }
-
-  for (auto ep : entry_points) {
-    const char* profile = "";
-    switch (ep.second) {
-      case ast::PipelineStage::kNone:
-        result.output = "Invalid PipelineStage";
-        result.failed = true;
-        return result;
-      case ast::PipelineStage::kVertex:
-        profile = "vs_5_1";
-        break;
-      case ast::PipelineStage::kFragment:
-        profile = "ps_5_1";
-        break;
-      case ast::PipelineStage::kCompute:
-        profile = "cs_5_1";
-        break;
-    }
-
-    // Match Dawn's compile flags
-    // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
-    UINT compileFlags = D3DCOMPILE_OPTIMIZATION_LEVEL0 |
-                        D3DCOMPILE_PACK_MATRIX_ROW_MAJOR |
-                        D3DCOMPILE_IEEE_STRICTNESS;
-
-    ComPtr<ID3DBlob> compiledShader;
-    ComPtr<ID3DBlob> errors;
-    HRESULT cr = d3dCompile(source.c_str(),    // pSrcData
-                            source.length(),   // SrcDataSize
-                            nullptr,           // pSourceName
-                            nullptr,           // pDefines
-                            nullptr,           // pInclude
-                            ep.first.c_str(),  // pEntrypoint
-                            profile,           // pTarget
-                            compileFlags,      // Flags1
-                            0,                 // Flags2
-                            &compiledShader,   // ppCode
-                            &errors);          // ppErrorMsgs
-    if (FAILED(cr)) {
-      result.output = static_cast<char*>(errors->GetBufferPointer());
-      result.failed = true;
-      return result;
-    }
-  }
-
-  FreeLibrary(fxcLib);
-
-  if (entry_points.empty()) {
-    result.output = "No entrypoint found";
-    result.failed = true;
-    return result;
-  }
-
-  return result;
-}
-#endif  // _WIN32
-
-}  // namespace val
-}  // namespace tint
diff --git a/src/val/msl.cc b/src/val/msl.cc
deleted file mode 100644
index 61ec4a1..0000000
--- a/src/val/msl.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/val/val.h"
-
-#include "src/ast/module.h"
-#include "src/program.h"
-#include "src/utils/io/command.h"
-#include "src/utils/io/tmpfile.h"
-
-namespace tint {
-namespace val {
-
-Result Msl(const std::string& xcrun_path, const std::string& source) {
-  Result result;
-
-  auto xcrun = utils::Command(xcrun_path);
-  if (!xcrun.Found()) {
-    result.output = "xcrun not found at '" + std::string(xcrun_path) + "'";
-    result.failed = true;
-    return result;
-  }
-
-  utils::TmpFile file(".metal");
-  file << source;
-
-#ifdef _WIN32
-  // On Windows, we should actually be running metal.exe from the Metal
-  // Developer Tools for Windows
-  auto res = xcrun("-x", "metal",        //
-                   "-o", "NUL",          //
-                   "-std=osx-metal1.2",  //
-                   "-c", file.Path());
-#else
-  auto res = xcrun("-sdk", "macosx", "metal",  //
-                   "-o", "/dev/null",          //
-                   "-std=osx-metal1.2",        //
-                   "-c", file.Path());
-#endif
-  if (!res.out.empty()) {
-    if (!result.output.empty()) {
-      result.output += "\n";
-    }
-    result.output += res.out;
-  }
-  if (!res.err.empty()) {
-    if (!result.output.empty()) {
-      result.output += "\n";
-    }
-    result.output += res.err;
-  }
-  result.failed = (res.error_code != 0);
-
-  return result;
-}
-
-}  // namespace val
-}  // namespace tint
diff --git a/src/val/msl_metal.mm b/src/val/msl_metal.mm
deleted file mode 100644
index 8869bdb..0000000
--- a/src/val/msl_metal.mm
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
-
-@import Metal;
-
-// Disable: error: treating #include as an import of module 'std.string'
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wauto-import"
-#include "src/val/val.h"
-#pragma clang diagnostic pop
-
-namespace tint {
-namespace val {
-
-Result MslUsingMetalAPI(const std::string& src) {
-  tint::val::Result result;
-
-  NSError* error = nil;
-
-  id<MTLDevice> device = MTLCreateSystemDefaultDevice();
-  if (!device) {
-    result.output = "MTLCreateSystemDefaultDevice returned null";
-    result.failed = true;
-    return result;
-  }
-
-  NSString* source = [NSString stringWithCString:src.c_str()
-                                        encoding:NSUTF8StringEncoding];
-
-  MTLCompileOptions* compileOptions = [MTLCompileOptions new];
-  compileOptions.languageVersion = MTLLanguageVersion1_2;
-
-  id<MTLLibrary> library = [device newLibraryWithSource:source
-                                                options:compileOptions
-                                                  error:&error];
-  if (!library) {
-    NSString* output = [error localizedDescription];
-    result.output = [output UTF8String];
-    result.failed = true;
-  }
-
-  return result;
-}
-
-}  // namespace val
-}  // namespace tint
-
-#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
diff --git a/src/val/val.h b/src/val/val.h
deleted file mode 100644
index 31e83b9..0000000
--- a/src/val/val.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_VAL_VAL_H_
-#define SRC_VAL_VAL_H_
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "src/ast/pipeline_stage.h"
-
-// Forward declarations
-namespace tint {
-class Program;
-}  // namespace tint
-
-namespace tint {
-namespace val {
-
-using EntryPointList = std::vector<std::pair<std::string, ast::PipelineStage>>;
-
-/// The return structure of Validate()
-struct Result {
-  /// True if validation passed
-  bool failed = false;
-  /// Output of DXC.
-  std::string output;
-};
-
-/// Hlsl attempts to compile the shader with DXC, verifying that the shader
-/// compiles successfully.
-/// @param dxc_path path to DXC
-/// @param source the generated HLSL source
-/// @param entry_points the list of entry points to validate
-/// @return the result of the compile
-Result HlslUsingDXC(const std::string& dxc_path,
-                    const std::string& source,
-                    const EntryPointList& entry_points);
-
-#ifdef _WIN32
-/// Hlsl attempts to compile the shader with FXC, verifying that the shader
-/// compiles successfully.
-/// @param source the generated HLSL source
-/// @param entry_points the list of entry points to validate
-/// @return the result of the compile
-Result HlslUsingFXC(const std::string& source,
-                    const EntryPointList& entry_points);
-#endif  // _WIN32
-
-/// Msl attempts to compile the shader with the Metal Shader Compiler,
-/// verifying that the shader compiles successfully.
-/// @param xcrun_path path to xcrun
-/// @param source the generated MSL source
-/// @return the result of the compile
-Result Msl(const std::string& xcrun_path, const std::string& source);
-
-#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
-/// Msl attempts to compile the shader with the runtime Metal Shader Compiler
-/// API, verifying that the shader compiles successfully.
-/// @param source the generated MSL source
-/// @return the result of the compile
-Result MslUsingMetalAPI(const std::string& source);
-#endif  // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
-
-}  // namespace val
-}  // namespace tint
-
-#endif  // SRC_VAL_VAL_H_
diff --git a/src/writer/append_vector.cc b/src/writer/append_vector.cc
deleted file mode 100644
index 4f461d9..0000000
--- a/src/writer/append_vector.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2020 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/writer/append_vector.h"
-
-#include <utility>
-#include <vector>
-
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/utils/transform.h"
-
-namespace tint {
-namespace writer {
-
-namespace {
-
-struct VectorConstructorInfo {
-  const sem::Call* call = nullptr;
-  const sem::TypeConstructor* ctor = nullptr;
-  operator bool() const { return call != nullptr; }
-};
-VectorConstructorInfo AsVectorConstructor(const sem::Expression* expr) {
-  if (auto* call = expr->As<sem::Call>()) {
-    if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
-      if (ctor->ReturnType()->Is<sem::Vector>()) {
-        return {call, ctor};
-      }
-    }
-  }
-  return {};
-}
-
-const sem::Expression* Zero(ProgramBuilder& b,
-                            const sem::Type* ty,
-                            const sem::Statement* stmt) {
-  const ast::Expression* expr = nullptr;
-  if (ty->Is<sem::I32>()) {
-    expr = b.Expr(0);
-  } else if (ty->Is<sem::U32>()) {
-    expr = b.Expr(0u);
-  } else if (ty->Is<sem::F32>()) {
-    expr = b.Expr(0.0f);
-  } else if (ty->Is<sem::Bool>()) {
-    expr = b.Expr(false);
-  } else {
-    TINT_UNREACHABLE(Writer, b.Diagnostics())
-        << "unsupported vector element type: " << ty->TypeInfo().name;
-    return nullptr;
-  }
-  auto* sem = b.create<sem::Expression>(expr, ty, stmt, sem::Constant{},
-                                        /* has_side_effects */ false);
-  b.Sem().Add(expr, sem);
-  return sem;
-}
-
-}  // namespace
-
-const sem::Call* AppendVector(ProgramBuilder* b,
-                              const ast::Expression* vector_ast,
-                              const ast::Expression* scalar_ast) {
-  uint32_t packed_size;
-  const sem::Type* packed_el_sem_ty;
-  auto* vector_sem = b->Sem().Get(vector_ast);
-  auto* scalar_sem = b->Sem().Get(scalar_ast);
-  auto* vector_ty = vector_sem->Type()->UnwrapRef();
-  if (auto* vec = vector_ty->As<sem::Vector>()) {
-    packed_size = vec->Width() + 1;
-    packed_el_sem_ty = vec->type();
-  } else {
-    packed_size = 2;
-    packed_el_sem_ty = vector_ty;
-  }
-
-  const ast::Type* packed_el_ast_ty = nullptr;
-  if (packed_el_sem_ty->Is<sem::I32>()) {
-    packed_el_ast_ty = b->create<ast::I32>();
-  } else if (packed_el_sem_ty->Is<sem::U32>()) {
-    packed_el_ast_ty = b->create<ast::U32>();
-  } else if (packed_el_sem_ty->Is<sem::F32>()) {
-    packed_el_ast_ty = b->create<ast::F32>();
-  } else if (packed_el_sem_ty->Is<sem::Bool>()) {
-    packed_el_ast_ty = b->create<ast::Bool>();
-  } else {
-    TINT_UNREACHABLE(Writer, b->Diagnostics())
-        << "unsupported vector element type: "
-        << packed_el_sem_ty->TypeInfo().name;
-  }
-
-  auto* statement = vector_sem->Stmt();
-
-  auto* packed_ast_ty = b->create<ast::Vector>(packed_el_ast_ty, packed_size);
-  auto* packed_sem_ty = b->create<sem::Vector>(packed_el_sem_ty, packed_size);
-
-  // If the coordinates are already passed in a vector constructor, with only
-  // scalar components supplied, extract the elements into the new vector
-  // instead of nesting a vector-in-vector.
-  // If the coordinates are a zero-constructor of the vector, then expand that
-  // to scalar zeros.
-  // The other cases for a nested vector constructor are when it is used
-  // to convert a vector of a different type, e.g. vec2<i32>(vec2<u32>()).
-  // In that case, preserve the original argument, or you'll get a type error.
-
-  std::vector<const sem::Expression*> packed;
-  if (auto vc = AsVectorConstructor(vector_sem)) {
-    const auto num_supplied = vc.call->Arguments().size();
-    if (num_supplied == 0) {
-      // Zero-value vector constructor. Populate with zeros
-      for (uint32_t i = 0; i < packed_size - 1; i++) {
-        auto* zero = Zero(*b, packed_el_sem_ty, statement);
-        packed.emplace_back(zero);
-      }
-    } else if (num_supplied + 1 == packed_size) {
-      // All vector components were supplied as scalars.  Pass them through.
-      packed = vc.call->Arguments();
-    }
-  }
-  if (packed.empty()) {
-    // The special cases didn't occur. Use the vector argument as-is.
-    packed.emplace_back(vector_sem);
-  }
-
-  if (packed_el_sem_ty != scalar_sem->Type()->UnwrapRef()) {
-    // Cast scalar to the vector element type
-    auto* scalar_cast_ast = b->Construct(packed_el_ast_ty, scalar_ast);
-    auto* scalar_cast_target = b->create<sem::TypeConversion>(
-        packed_el_sem_ty,
-        b->create<sem::Parameter>(nullptr, 0, scalar_sem->Type()->UnwrapRef(),
-                                  ast::StorageClass::kNone,
-                                  ast::Access::kUndefined));
-    auto* scalar_cast_sem = b->create<sem::Call>(
-        scalar_cast_ast, scalar_cast_target,
-        std::vector<const sem::Expression*>{scalar_sem}, statement,
-        sem::Constant{}, /* has_side_effects */ false);
-    b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
-    packed.emplace_back(scalar_cast_sem);
-  } else {
-    packed.emplace_back(scalar_sem);
-  }
-
-  auto* constructor_ast = b->Construct(
-      packed_ast_ty, utils::Transform(packed, [&](const sem::Expression* expr) {
-        return expr->Declaration();
-      }));
-  auto* constructor_target = b->create<sem::TypeConstructor>(
-      packed_sem_ty, utils::Transform(packed,
-                                      [&](const tint::sem::Expression* arg,
-                                          size_t i) -> const sem::Parameter* {
-                                        return b->create<sem::Parameter>(
-                                            nullptr, static_cast<uint32_t>(i),
-                                            arg->Type()->UnwrapRef(),
-                                            ast::StorageClass::kNone,
-                                            ast::Access::kUndefined);
-                                      }));
-  auto* constructor_sem = b->create<sem::Call>(
-      constructor_ast, constructor_target, packed, statement, sem::Constant{},
-      /* has_side_effects */ false);
-  b->Sem().Add(constructor_ast, constructor_sem);
-  return constructor_sem;
-}
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/append_vector.h b/src/writer/append_vector.h
deleted file mode 100644
index 5e28271..0000000
--- a/src/writer/append_vector.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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_WRITER_APPEND_VECTOR_H_
-#define SRC_WRITER_APPEND_VECTOR_H_
-
-#include "src/program_builder.h"
-
-namespace tint {
-
-namespace ast {
-class CallExpression;
-class Expression;
-}  // namespace ast
-
-namespace writer {
-
-/// A helper function used to append a vector with an additional scalar.
-/// If the scalar's type does not match the target vector element type,
-/// then it is value-converted (via TypeConstructor) before being added.
-/// All types must have been assigned to the expressions and their child nodes
-/// before calling.
-/// @param builder the program builder.
-/// @param vector the vector to be appended. May be a scalar, `vec2` or `vec3`.
-/// @param scalar the scalar to append to the vector. Must be a scalar.
-/// @returns a vector expression containing the elements of `vector` followed by
-/// the single element of `scalar` cast to the `vector` element type.
-const sem::Call* AppendVector(ProgramBuilder* builder,
-                              const ast::Expression* vector,
-                              const ast::Expression* scalar);
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_APPEND_VECTOR_H_
diff --git a/src/writer/append_vector_test.cc b/src/writer/append_vector_test.cc
deleted file mode 100644
index e348d8d..0000000
--- a/src/writer/append_vector_test.cc
+++ /dev/null
@@ -1,488 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/append_vector.h"
-#include "src/program_builder.h"
-#include "src/resolver/resolver.h"
-#include "src/sem/type_constructor.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace writer {
-namespace {
-
-class AppendVectorTest : public ::testing::Test, public ProgramBuilder {};
-
-// AppendVector(vec2<i32>(1, 2), 3) -> vec3<i32>(1, 2, 3)
-TEST_F(AppendVectorTest, Vec2i32_i32) {
-  auto* scalar_1 = Expr(1);
-  auto* scalar_2 = Expr(2);
-  auto* scalar_3 = Expr(3);
-  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 3u);
-  EXPECT_EQ(vec_123->args[0], scalar_1);
-  EXPECT_EQ(vec_123->args[1], scalar_2);
-  EXPECT_EQ(vec_123->args[2], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 3u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec2<i32>(1, 2), 3u) -> vec3<i32>(1, 2, i32(3u))
-TEST_F(AppendVectorTest, Vec2i32_u32) {
-  auto* scalar_1 = Expr(1);
-  auto* scalar_2 = Expr(2);
-  auto* scalar_3 = Expr(3u);
-  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 3u);
-  EXPECT_EQ(vec_123->args[0], scalar_1);
-  EXPECT_EQ(vec_123->args[1], scalar_2);
-  auto* u32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
-  ASSERT_NE(u32_to_i32, nullptr);
-  EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
-  ASSERT_EQ(u32_to_i32->args.size(), 1u);
-  EXPECT_EQ(u32_to_i32->args[0], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 3u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(u32_to_i32));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec2<i32>(vec2<u32>(1u, 2u)), 3u) ->
-//    vec3<i32>(vec2<i32>(vec2<u32>(1u, 2u)), i32(3u))
-TEST_F(AppendVectorTest, Vec2i32FromVec2u32_u32) {
-  auto* scalar_1 = Expr(1u);
-  auto* scalar_2 = Expr(2u);
-  auto* scalar_3 = Expr(3u);
-  auto* uvec_12 = vec2<u32>(scalar_1, scalar_2);
-  auto* vec_12 = vec2<i32>(uvec_12);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 2u);
-  auto* v2u32_to_v2i32 = vec_123->args[0]->As<ast::CallExpression>();
-  ASSERT_NE(v2u32_to_v2i32, nullptr);
-  ASSERT_TRUE(v2u32_to_v2i32->target.type->Is<ast::Vector>());
-  EXPECT_EQ(v2u32_to_v2i32->target.type->As<ast::Vector>()->width, 2u);
-  EXPECT_TRUE(
-      v2u32_to_v2i32->target.type->As<ast::Vector>()->type->Is<ast::I32>());
-  EXPECT_EQ(v2u32_to_v2i32->args.size(), 1u);
-  EXPECT_EQ(v2u32_to_v2i32->args[0], uvec_12);
-
-  auto* u32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
-  ASSERT_NE(u32_to_i32, nullptr);
-  EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
-  ASSERT_EQ(u32_to_i32->args.size(), 1u);
-  EXPECT_EQ(u32_to_i32->args[0], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 2u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(u32_to_i32));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec2<i32>(1, 2), 3.0f) -> vec3<i32>(1, 2, i32(3.0f))
-TEST_F(AppendVectorTest, Vec2i32_f32) {
-  auto* scalar_1 = Expr(1);
-  auto* scalar_2 = Expr(2);
-  auto* scalar_3 = Expr(3.0f);
-  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 3u);
-  EXPECT_EQ(vec_123->args[0], scalar_1);
-  EXPECT_EQ(vec_123->args[1], scalar_2);
-  auto* f32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
-  ASSERT_NE(f32_to_i32, nullptr);
-  EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
-  ASSERT_EQ(f32_to_i32->args.size(), 1u);
-  EXPECT_EQ(f32_to_i32->args[0], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 3u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(f32_to_i32));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec3<i32>(1, 2, 3), 4) -> vec4<i32>(1, 2, 3, 4)
-TEST_F(AppendVectorTest, Vec3i32_i32) {
-  auto* scalar_1 = Expr(1);
-  auto* scalar_2 = Expr(2);
-  auto* scalar_3 = Expr(3);
-  auto* scalar_4 = Expr(4);
-  auto* vec_123 = vec3<i32>(scalar_1, scalar_2, scalar_3);
-  WrapInFunction(vec_123, scalar_4);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_123, scalar_4);
-
-  auto* vec_1234 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_1234, nullptr);
-  ASSERT_EQ(vec_1234->args.size(), 4u);
-  EXPECT_EQ(vec_1234->args[0], scalar_1);
-  EXPECT_EQ(vec_1234->args[1], scalar_2);
-  EXPECT_EQ(vec_1234->args[2], scalar_3);
-  EXPECT_EQ(vec_1234->args[3], scalar_4);
-
-  auto* call = Sem().Get(vec_1234);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 4u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
-  EXPECT_EQ(call->Arguments()[3], Sem().Get(scalar_4));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 4u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[3]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec_12, 3) -> vec3<i32>(vec_12, 3)
-TEST_F(AppendVectorTest, Vec2i32Var_i32) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  auto* vec_12 = Expr("vec_12");
-  auto* scalar_3 = Expr(3);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 2u);
-  EXPECT_EQ(vec_123->args[0], vec_12);
-  EXPECT_EQ(vec_123->args[1], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 2u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(1, 2, scalar_3) -> vec3<i32>(1, 2, scalar_3)
-TEST_F(AppendVectorTest, Vec2i32_i32Var) {
-  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
-  auto* scalar_1 = Expr(1);
-  auto* scalar_2 = Expr(2);
-  auto* scalar_3 = Expr("scalar_3");
-  auto* vec_12 = vec2<i32>(scalar_1, scalar_2);
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 3u);
-  EXPECT_EQ(vec_123->args[0], scalar_1);
-  EXPECT_EQ(vec_123->args[1], scalar_2);
-  EXPECT_EQ(vec_123->args[2], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 3u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(scalar_1));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_2));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(scalar_3));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 3u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec_12, scalar_3) -> vec3<i32>(vec_12, scalar_3)
-TEST_F(AppendVectorTest, Vec2i32Var_i32Var) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
-  auto* vec_12 = Expr("vec_12");
-  auto* scalar_3 = Expr("scalar_3");
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 2u);
-  EXPECT_EQ(vec_123->args[0], vec_12);
-  EXPECT_EQ(vec_123->args[1], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 2u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec_12, scalar_3) -> vec3<i32>(vec_12, i32(scalar_3))
-TEST_F(AppendVectorTest, Vec2i32Var_f32Var) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.f32(), ast::StorageClass::kPrivate);
-  auto* vec_12 = Expr("vec_12");
-  auto* scalar_3 = Expr("scalar_3");
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 2u);
-  EXPECT_EQ(vec_123->args[0], vec_12);
-  auto* f32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
-  ASSERT_NE(f32_to_i32, nullptr);
-  EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
-  ASSERT_EQ(f32_to_i32->args.size(), 1u);
-  EXPECT_EQ(f32_to_i32->args[0], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 2u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(f32_to_i32));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-}
-
-// AppendVector(vec_12, scalar_3) -> vec3<bool>(vec_12, scalar_3)
-TEST_F(AppendVectorTest, Vec2boolVar_boolVar) {
-  Global("vec_12", ty.vec2<bool>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* vec_12 = Expr("vec_12");
-  auto* scalar_3 = Expr("scalar_3");
-  WrapInFunction(vec_12, scalar_3);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec_12, scalar_3);
-
-  auto* vec_123 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_123, nullptr);
-  ASSERT_EQ(vec_123->args.size(), 2u);
-  EXPECT_EQ(vec_123->args[0], vec_12);
-  EXPECT_EQ(vec_123->args[1], scalar_3);
-
-  auto* call = Sem().Get(vec_123);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 2u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_12));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(scalar_3));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 3u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::Bool>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 2u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
-}
-
-// AppendVector(vec3<i32>(), 4) -> vec3<bool>(0, 0, 0, 4)
-TEST_F(AppendVectorTest, ZeroVec3i32_i32) {
-  auto* scalar = Expr(4);
-  auto* vec000 = vec3<i32>();
-  WrapInFunction(vec000, scalar);
-
-  resolver::Resolver resolver(this);
-  ASSERT_TRUE(resolver.Resolve()) << resolver.error();
-
-  auto* append = AppendVector(this, vec000, scalar);
-
-  auto* vec_0004 = As<ast::CallExpression>(append->Declaration());
-  ASSERT_NE(vec_0004, nullptr);
-  ASSERT_EQ(vec_0004->args.size(), 4u);
-  for (size_t i = 0; i < 3; i++) {
-    auto* literal = As<ast::SintLiteralExpression>(vec_0004->args[i]);
-    ASSERT_NE(literal, nullptr);
-    EXPECT_EQ(literal->value, 0);
-  }
-  EXPECT_EQ(vec_0004->args[3], scalar);
-
-  auto* call = Sem().Get(vec_0004);
-  ASSERT_NE(call, nullptr);
-  ASSERT_EQ(call->Arguments().size(), 4u);
-  EXPECT_EQ(call->Arguments()[0], Sem().Get(vec_0004->args[0]));
-  EXPECT_EQ(call->Arguments()[1], Sem().Get(vec_0004->args[1]));
-  EXPECT_EQ(call->Arguments()[2], Sem().Get(vec_0004->args[2]));
-  EXPECT_EQ(call->Arguments()[3], Sem().Get(scalar));
-
-  auto* ctor = call->Target()->As<sem::TypeConstructor>();
-  ASSERT_NE(ctor, nullptr);
-  ASSERT_TRUE(ctor->ReturnType()->Is<sem::Vector>());
-  EXPECT_EQ(ctor->ReturnType()->As<sem::Vector>()->Width(), 4u);
-  EXPECT_TRUE(ctor->ReturnType()->As<sem::Vector>()->type()->Is<sem::I32>());
-  EXPECT_EQ(ctor->ReturnType(), call->Type());
-
-  ASSERT_EQ(ctor->Parameters().size(), 4u);
-  EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
-  EXPECT_TRUE(ctor->Parameters()[3]->Type()->Is<sem::I32>());
-}
-
-}  // namespace
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/array_length_from_uniform_options.cc b/src/writer/array_length_from_uniform_options.cc
deleted file mode 100644
index b848a48..0000000
--- a/src/writer/array_length_from_uniform_options.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/array_length_from_uniform_options.h"
-
-namespace tint {
-namespace writer {
-
-ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions() = default;
-ArrayLengthFromUniformOptions::~ArrayLengthFromUniformOptions() = default;
-ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(
-    const ArrayLengthFromUniformOptions&) = default;
-ArrayLengthFromUniformOptions& ArrayLengthFromUniformOptions::operator=(
-    const ArrayLengthFromUniformOptions&) = default;
-ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(
-    ArrayLengthFromUniformOptions&&) = default;
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/array_length_from_uniform_options.h b/src/writer/array_length_from_uniform_options.h
deleted file mode 100644
index 2c06287..0000000
--- a/src/writer/array_length_from_uniform_options.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
-#define SRC_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
-
-#include <unordered_map>
-
-#include "src/sem/binding_point.h"
-
-namespace tint {
-namespace writer {
-
-/// Options used to specify a mapping of binding points to indices into a UBO
-/// from which to load buffer sizes.
-struct ArrayLengthFromUniformOptions {
-  /// Constructor
-  ArrayLengthFromUniformOptions();
-  /// Destructor
-  ~ArrayLengthFromUniformOptions();
-  /// Copy constructor
-  ArrayLengthFromUniformOptions(const ArrayLengthFromUniformOptions&);
-  /// Copy assignment
-  /// @returns this ArrayLengthFromUniformOptions
-  ArrayLengthFromUniformOptions& operator=(
-      const ArrayLengthFromUniformOptions&);
-  /// Move constructor
-  ArrayLengthFromUniformOptions(ArrayLengthFromUniformOptions&&);
-
-  /// The binding point to use to generate a uniform buffer from which to read
-  /// buffer sizes.
-  sem::BindingPoint ubo_binding;
-  /// The mapping from storage buffer binding points to the index into the
-  /// uniform buffer where the length of the buffer is stored.
-  std::unordered_map<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
-
-  // NOTE: Update fuzzers/data_builder.h when adding or changing any struct
-  // members.
-};
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_ARRAY_LENGTH_FROM_UNIFORM_OPTIONS_H_
diff --git a/src/writer/float_to_string.cc b/src/writer/float_to_string.cc
deleted file mode 100644
index d140b34..0000000
--- a/src/writer/float_to_string.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 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/writer/float_to_string.h"
-
-#include <cmath>
-#include <cstring>
-#include <functional>
-#include <iomanip>
-#include <limits>
-#include <sstream>
-
-#include "src/debug.h"
-
-namespace tint {
-namespace writer {
-
-std::string FloatToString(float f) {
-  // Try printing the float in fixed point, with a smallish limit on the
-  // precision
-  std::stringstream fixed;
-  fixed.flags(fixed.flags() | std::ios_base::showpoint | std::ios_base::fixed);
-  fixed.precision(9);
-  fixed << f;
-
-  // If this string can be parsed without loss of information, use it
-  auto float_equal_no_warning = std::equal_to<float>();
-  if (float_equal_no_warning(std::stof(fixed.str()), f)) {
-    auto str = fixed.str();
-    while (str.length() >= 2 && str[str.size() - 1] == '0' &&
-           str[str.size() - 2] != '.') {
-      str.pop_back();
-    }
-
-    return str;
-  }
-
-  // Resort to scientific, with the minimum precision needed to preserve the
-  // whole float
-  std::stringstream sci;
-  sci.precision(std::numeric_limits<float>::max_digits10);
-  sci << f;
-  return sci.str();
-}
-
-std::string FloatToBitPreservingString(float f) {
-  // For the NaN case, avoid handling the number as a floating point value.
-  // Some machines will modify the top bit in the mantissa of a NaN.
-
-  std::stringstream ss;
-
-  uint32_t float_bits = 0u;
-  std::memcpy(&float_bits, &f, sizeof(float_bits));
-
-  // Handle the sign.
-  const uint32_t kSignMask = 1u << 31;
-  if (float_bits & kSignMask) {
-    // If `f` is -0.0 print -0.0.
-    ss << '-';
-    // Strip sign bit.
-    float_bits = float_bits & (~kSignMask);
-  }
-
-  switch (std::fpclassify(f)) {
-    case FP_ZERO:
-    case FP_NORMAL:
-      std::memcpy(&f, &float_bits, sizeof(float_bits));
-      ss << FloatToString(f);
-      break;
-
-    default: {
-      // Infinity, NaN, and Subnormal
-      // TODO(dneto): It's unclear how Infinity and NaN should be handled.
-      // See https://github.com/gpuweb/gpuweb/issues/1769
-
-      // std::hexfloat prints 'nan' and 'inf' instead of an
-      // explicit representation like we want. Split it out
-      // manually.
-      const int kExponentBias = 127;
-      const int kExponentMask = 0x7f800000;
-      const int kMantissaMask = 0x007fffff;
-      const int kMantissaBits = 23;
-
-      int mantissaNibbles = (kMantissaBits + 3) / 4;
-
-      const int biased_exponent =
-          static_cast<int>((float_bits & kExponentMask) >> kMantissaBits);
-      int exponent = biased_exponent - kExponentBias;
-      uint32_t mantissa = float_bits & kMantissaMask;
-
-      ss << "0x";
-
-      if (exponent == 128) {
-        if (mantissa == 0) {
-          //  Infinity case.
-          ss << "1p+128";
-        } else {
-          //  NaN case.
-          //  Emit the mantissa bits as if they are left-justified after the
-          //  binary point.  This is what SPIRV-Tools hex float emitter does,
-          //  and it's a justifiable choice independent of the bit width
-          //  of the mantissa.
-          mantissa <<= (4 - (kMantissaBits % 4));
-          // Remove trailing zeroes, for tidyness.
-          while (0 == (0xf & mantissa)) {
-            mantissa >>= 4;
-            mantissaNibbles--;
-          }
-          ss << "1." << std::hex << std::setfill('0')
-             << std::setw(mantissaNibbles) << mantissa << "p+128";
-        }
-      } else {
-        // Subnormal, and not zero.
-        TINT_ASSERT(Writer, mantissa != 0);
-        const int kTopBit = (1 << kMantissaBits);
-
-        // Shift left until we get 1.x
-        while (0 == (kTopBit & mantissa)) {
-          mantissa <<= 1;
-          exponent--;
-        }
-        // Emit the leading 1, and remove it from the mantissa.
-        ss << "1";
-        mantissa = mantissa ^ kTopBit;
-        mantissa <<= 1;
-        exponent++;
-
-        // Emit the fractional part.
-        if (mantissa) {
-          // Remove trailing zeroes, for tidyness
-          while (0 == (0xf & mantissa)) {
-            mantissa >>= 4;
-            mantissaNibbles--;
-          }
-          ss << "." << std::hex << std::setfill('0')
-             << std::setw(mantissaNibbles) << mantissa;
-        }
-        // Emit the exponent
-        ss << "p" << std::showpos << std::dec << exponent;
-      }
-    }
-  }
-  return ss.str();
-}
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/float_to_string.h b/src/writer/float_to_string.h
deleted file mode 100644
index 9f385db..0000000
--- a/src/writer/float_to_string.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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_WRITER_FLOAT_TO_STRING_H_
-#define SRC_WRITER_FLOAT_TO_STRING_H_
-
-#include <string>
-
-namespace tint {
-namespace writer {
-
-/// Converts the float `f` to a string using fixed-point notation (not
-/// scientific). The float will be printed with the full precision required to
-/// describe the float. All trailing `0`s will be omitted after the last
-/// non-zero fractional number, unless the fractional is zero, in which case the
-/// number will end with `.0`.
-/// @return the float f formatted to a string
-std::string FloatToString(float f);
-
-/// Converts the float `f` to a string, using hex float notation for infinities,
-/// NaNs, or subnormal numbers. Otherwise behaves as FloatToString.
-/// @return the float f formatted to a string
-std::string FloatToBitPreservingString(float f);
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_FLOAT_TO_STRING_H_
diff --git a/src/writer/float_to_string_test.cc b/src/writer/float_to_string_test.cc
deleted file mode 100644
index 2ff278e..0000000
--- a/src/writer/float_to_string_test.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2020 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/writer/float_to_string.h"
-
-#include <cmath>
-#include <cstring>
-#include <limits>
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace writer {
-namespace {
-
-// Makes an IEEE 754 binary32 floating point number with
-// - 0 sign if sign is 0, 1 otherwise
-// - 'exponent_bits' is placed in the exponent space.
-//   So, the exponent bias must already be included.
-float MakeFloat(int sign, int biased_exponent, int mantissa) {
-  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
-  // The binary32 exponent is 8 bits, just below the sign.
-  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
-  // The mantissa is the bottom 23 bits.
-  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
-
-  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
-  float result = 0.0f;
-  static_assert(sizeof(result) == sizeof(bits),
-                "expected float and uint32_t to be the same size");
-  std::memcpy(&result, &bits, sizeof(bits));
-  return result;
-}
-
-TEST(FloatToStringTest, Zero) {
-  EXPECT_EQ(FloatToString(0.0f), "0.0");
-}
-
-TEST(FloatToStringTest, One) {
-  EXPECT_EQ(FloatToString(1.0f), "1.0");
-}
-
-TEST(FloatToStringTest, MinusOne) {
-  EXPECT_EQ(FloatToString(-1.0f), "-1.0");
-}
-
-TEST(FloatToStringTest, Billion) {
-  EXPECT_EQ(FloatToString(1e9f), "1000000000.0");
-}
-
-TEST(FloatToStringTest, Small) {
-  EXPECT_NE(FloatToString(std::numeric_limits<float>::epsilon()), "0.0");
-}
-
-TEST(FloatToStringTest, Highest) {
-  const auto highest = std::numeric_limits<float>::max();
-  const auto expected_highest = 340282346638528859811704183484516925440.0f;
-  if (highest < expected_highest || highest > expected_highest) {
-    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
-                    "this target";
-  }
-  EXPECT_EQ(FloatToString(std::numeric_limits<float>::max()),
-            "340282346638528859811704183484516925440.0");
-}
-
-TEST(FloatToStringTest, Lowest) {
-  // Some compilers complain if you test floating point numbers for equality.
-  // So say it via two inequalities.
-  const auto lowest = std::numeric_limits<float>::lowest();
-  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
-  if (lowest < expected_lowest || lowest > expected_lowest) {
-    GTEST_SKIP()
-        << "std::numeric_limits<float>::lowest() is not as expected for "
-           "this target";
-  }
-  EXPECT_EQ(FloatToString(std::numeric_limits<float>::lowest()),
-            "-340282346638528859811704183484516925440.0");
-}
-
-TEST(FloatToStringTest, Precision) {
-  EXPECT_EQ(FloatToString(1e-8f), "0.00000001");
-  EXPECT_EQ(FloatToString(1e-9f), "0.000000001");
-  EXPECT_EQ(FloatToString(1e-10f), "1.00000001e-10");
-  EXPECT_EQ(FloatToString(1e-20f), "9.99999968e-21");
-}
-
-// FloatToBitPreservingString
-//
-// First replicate the tests for FloatToString
-
-TEST(FloatToBitPreservingStringTest, Zero) {
-  EXPECT_EQ(FloatToBitPreservingString(0.0f), "0.0");
-}
-
-TEST(FloatToBitPreservingStringTest, One) {
-  EXPECT_EQ(FloatToBitPreservingString(1.0f), "1.0");
-}
-
-TEST(FloatToBitPreservingStringTest, MinusOne) {
-  EXPECT_EQ(FloatToBitPreservingString(-1.0f), "-1.0");
-}
-
-TEST(FloatToBitPreservingStringTest, Billion) {
-  EXPECT_EQ(FloatToBitPreservingString(1e9f), "1000000000.0");
-}
-
-TEST(FloatToBitPreservingStringTest, Small) {
-  EXPECT_NE(FloatToBitPreservingString(std::numeric_limits<float>::epsilon()),
-            "0.0");
-}
-
-TEST(FloatToBitPreservingStringTest, Highest) {
-  const auto highest = std::numeric_limits<float>::max();
-  const auto expected_highest = 340282346638528859811704183484516925440.0f;
-  if (highest < expected_highest || highest > expected_highest) {
-    GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
-                    "this target";
-  }
-  EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::max()),
-            "340282346638528859811704183484516925440.0");
-}
-
-TEST(FloatToBitPreservingStringTest, Lowest) {
-  // Some compilers complain if you test floating point numbers for equality.
-  // So say it via two inequalities.
-  const auto lowest = std::numeric_limits<float>::lowest();
-  const auto expected_lowest = -340282346638528859811704183484516925440.0f;
-  if (lowest < expected_lowest || lowest > expected_lowest) {
-    GTEST_SKIP()
-        << "std::numeric_limits<float>::lowest() is not as expected for "
-           "this target";
-  }
-  EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::lowest()),
-            "-340282346638528859811704183484516925440.0");
-}
-
-// Special cases for bit-preserving output.
-
-TEST(FloatToBitPreservingStringTest, NegativeZero) {
-  EXPECT_EQ(FloatToBitPreservingString(std::copysign(0.0f, -5.0f)), "-0.0");
-}
-
-TEST(FloatToBitPreservingStringTest, ZeroAsBits) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0)), "0.0");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0)), "-0.0");
-}
-
-TEST(FloatToBitPreservingStringTest, OneBits) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 127, 0)), "1.0");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 127, 0)), "-1.0");
-}
-
-TEST(FloatToBitPreservingStringTest, SmallestDenormal) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 1)), "0x1p-149");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 1)), "-0x1p-149");
-}
-
-TEST(FloatToBitPreservingStringTest, BiggerDenormal) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 2)), "0x1p-148");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 2)), "-0x1p-148");
-}
-
-TEST(FloatToBitPreservingStringTest, LargestDenormal) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0x7fffff)),
-            "0x1.fffffcp-127");
-}
-
-TEST(FloatToBitPreservingStringTest, Subnormal_cafebe) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0xcafebe)),
-            "0x1.2bfaf8p-127");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0xcafebe)),
-            "-0x1.2bfaf8p-127");
-}
-
-TEST(FloatToBitPreservingStringTest, Subnormal_aaaaa) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 0, 0xaaaaa)),
-            "0x1.55554p-130");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 0, 0xaaaaa)),
-            "-0x1.55554p-130");
-}
-
-TEST(FloatToBitPreservingStringTest, Infinity) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0)), "0x1p+128");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0)), "-0x1p+128");
-}
-
-// TODO(dneto): It's unclear how Infinity and NaN should be handled.
-// https://github.com/gpuweb/gpuweb/issues/1769
-// Windows x86-64 sets the high mantissa bit on NaNs.
-// Disable NaN tests for now.
-
-TEST(FloatToBitPreservingStringTest, DISABLED_NaN_MsbOnly) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x400000)),
-            "0x1.8p+128");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x400000)),
-            "-0x1.8p+128");
-}
-
-TEST(FloatToBitPreservingStringTest, DISABLED_NaN_LsbOnly) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x1)),
-            "0x1.000002p+128");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x1)),
-            "-0x1.000002p+128");
-}
-
-TEST(FloatToBitPreservingStringTest, DISABLED_NaN_NonMsb) {
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(0, 255, 0x20101f)),
-            "0x1.40203ep+128");
-  EXPECT_EQ(FloatToBitPreservingString(MakeFloat(1, 255, 0x20101f)),
-            "-0x1.40203ep+128");
-}
-
-}  // namespace
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator.cc b/src/writer/glsl/generator.cc
deleted file mode 100644
index 3dc8f95..0000000
--- a/src/writer/glsl/generator.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/generator.h"
-
-#include "src/transform/binding_remapper.h"
-#include "src/transform/combine_samplers.h"
-#include "src/transform/glsl.h"
-#include "src/writer/glsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-
-Options::Options() = default;
-Options::~Options() = default;
-Options::Options(const Options&) = default;
-
-Result::Result() = default;
-Result::~Result() = default;
-Result::Result(const Result&) = default;
-
-Result Generate(const Program* program,
-                const Options& options,
-                const std::string& entry_point) {
-  Result result;
-
-  // Run the GLSL sanitizer.
-  transform::DataMap data;
-  data.Add<transform::BindingRemapper::Remappings>(options.binding_points,
-                                                   options.access_controls,
-                                                   options.allow_collisions);
-  data.Add<transform::CombineSamplers::BindingInfo>(
-      options.binding_map, options.placeholder_binding_point);
-  data.Add<transform::Glsl::Config>(entry_point);
-  transform::Glsl sanitizer;
-  auto output = sanitizer.Run(program, data);
-  if (!output.program.IsValid()) {
-    result.success = false;
-    result.error = output.program.Diagnostics().str();
-    return result;
-  }
-
-  // Generate the GLSL code.
-  auto impl = std::make_unique<GeneratorImpl>(&output.program, options.version);
-  result.success = impl->Generate();
-  result.error = impl->error();
-  result.glsl = impl->result();
-
-  // Collect the list of entry points in the sanitized program.
-  for (auto* func : output.program.AST().Functions()) {
-    if (func->IsEntryPoint()) {
-      auto name = output.program.Symbols().NameFor(func->symbol);
-      result.entry_points.push_back({name, func->PipelineStage()});
-    }
-  }
-
-  return result;
-}
-
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator.h b/src/writer/glsl/generator.h
deleted file mode 100644
index 16487e6..0000000
--- a/src/writer/glsl/generator.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_WRITER_GLSL_GENERATOR_H_
-#define SRC_WRITER_GLSL_GENERATOR_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/ast/access.h"
-#include "src/ast/pipeline_stage.h"
-#include "src/sem/binding_point.h"
-#include "src/sem/sampler_texture_pair.h"
-#include "src/writer/glsl/version.h"
-#include "src/writer/text.h"
-
-namespace tint {
-
-// Forward declarations
-class Program;
-
-namespace writer {
-namespace glsl {
-
-// Forward declarations
-class GeneratorImpl;
-
-using BindingMap = std::unordered_map<sem::SamplerTexturePair, std::string>;
-
-/// Configuration options used for generating GLSL.
-struct Options {
-  /// Constructor
-  Options();
-
-  /// Destructor
-  ~Options();
-
-  /// Copy constructor
-  Options(const Options&);
-
-  /// A map of SamplerTexturePair to combined sampler names for the
-  /// CombineSamplers transform
-  BindingMap binding_map;
-
-  /// The binding point to use for placeholder samplers.
-  sem::BindingPoint placeholder_binding_point;
-
-  /// A map of old binding point to new binding point for the BindingRemapper
-  /// transform
-  std::unordered_map<sem::BindingPoint, sem::BindingPoint> binding_points;
-
-  /// A map of old binding point to new access control for the BindingRemapper
-  /// transform
-  std::unordered_map<sem::BindingPoint, ast::Access> access_controls;
-
-  /// If true, then validation will be disabled for binding point collisions
-  /// generated by the BindingRemapper transform
-  bool allow_collisions = false;
-
-  /// The GLSL version to emit
-  Version version;
-};
-
-/// The result produced when generating GLSL.
-struct Result {
-  /// Constructor
-  Result();
-
-  /// Destructor
-  ~Result();
-
-  /// Copy constructor
-  Result(const Result&);
-
-  /// True if generation was successful.
-  bool success = false;
-
-  /// The errors generated during code generation, if any.
-  std::string error;
-
-  /// The generated GLSL.
-  std::string glsl = "";
-
-  /// The list of entry points in the generated GLSL.
-  std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
-};
-
-/// Generate GLSL for a program, according to a set of configuration options.
-/// The result will contain the GLSL, as well as success status and diagnostic
-/// information.
-/// @param program the program to translate to GLSL
-/// @param options the configuration options to use when generating GLSL
-/// @returns the resulting GLSL and supplementary information
-Result Generate(const Program* program,
-                const Options& options,
-                const std::string& entry_point);
-
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_GLSL_GENERATOR_H_
diff --git a/src/writer/glsl/generator_bench.cc b/src/writer/glsl/generator_bench.cc
deleted file mode 100644
index 551bd5c..0000000
--- a/src/writer/glsl/generator_bench.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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 <string>
-
-#include "src/ast/module.h"
-#include "src/bench/benchmark.h"
-
-namespace tint::writer::glsl {
-namespace {
-
-void GenerateGLSL(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadProgram(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& program = std::get<bench::ProgramAndFile>(res).program;
-  std::vector<std::string> entry_points;
-  for (auto& fn : program.AST().Functions()) {
-    if (fn->IsEntryPoint()) {
-      entry_points.emplace_back(program.Symbols().NameFor(fn->symbol));
-    }
-  }
-
-  for (auto _ : state) {
-    for (auto& ep : entry_points) {
-      auto res = Generate(&program, {}, ep);
-      if (!res.error.empty()) {
-        state.SkipWithError(res.error.c_str());
-      }
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(GenerateGLSL);
-
-}  // namespace
-}  // namespace tint::writer::glsl
diff --git a/src/writer/glsl/generator_impl.cc b/src/writer/glsl/generator_impl.cc
deleted file mode 100644
index fb4d0ed..0000000
--- a/src/writer/glsl/generator_impl.cc
+++ /dev/null
@@ -1,2849 +0,0 @@
-/// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/generator_impl.h"
-
-#include <algorithm>
-#include <cmath>
-#include <iomanip>
-#include <set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/call_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/debug.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/variable.h"
-#include "src/transform/glsl.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/writer/append_vector.h"
-#include "src/writer/float_to_string.h"
-
-namespace {
-
-bool IsRelational(tint::ast::BinaryOp op) {
-  return op == tint::ast::BinaryOp::kEqual ||
-         op == tint::ast::BinaryOp::kNotEqual ||
-         op == tint::ast::BinaryOp::kLessThan ||
-         op == tint::ast::BinaryOp::kGreaterThan ||
-         op == tint::ast::BinaryOp::kLessThanEqual ||
-         op == tint::ast::BinaryOp::kGreaterThanEqual;
-}
-
-bool RequiresOESSampleVariables(tint::ast::Builtin builtin) {
-  switch (builtin) {
-    case tint::ast::Builtin::kSampleIndex:
-    case tint::ast::Builtin::kSampleMask:
-      return true;
-    default:
-      return false;
-  }
-}
-
-}  // namespace
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-const char kTempNamePrefix[] = "tint_tmp";
-const char kSpecConstantPrefix[] = "WGSL_SPEC_CONSTANT_";
-
-bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
-  return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
-}
-
-const char* convert_texel_format_to_glsl(const ast::TexelFormat format) {
-  switch (format) {
-    case ast::TexelFormat::kR32Uint:
-      return "r32ui";
-    case ast::TexelFormat::kR32Sint:
-      return "r32i";
-    case ast::TexelFormat::kR32Float:
-      return "r32f";
-    case ast::TexelFormat::kRgba8Unorm:
-      return "rgba8";
-    case ast::TexelFormat::kRgba8Snorm:
-      return "rgba8_snorm";
-    case ast::TexelFormat::kRgba8Uint:
-      return "rgba8ui";
-    case ast::TexelFormat::kRgba8Sint:
-      return "rgba8i";
-    case ast::TexelFormat::kRg32Uint:
-      return "rg32ui";
-    case ast::TexelFormat::kRg32Sint:
-      return "rg32i";
-    case ast::TexelFormat::kRg32Float:
-      return "rg32f";
-    case ast::TexelFormat::kRgba16Uint:
-      return "rgba16ui";
-    case ast::TexelFormat::kRgba16Sint:
-      return "rgba16i";
-    case ast::TexelFormat::kRgba16Float:
-      return "rgba16f";
-    case ast::TexelFormat::kRgba32Uint:
-      return "rgba32ui";
-    case ast::TexelFormat::kRgba32Sint:
-      return "rgba32i";
-    case ast::TexelFormat::kRgba32Float:
-      return "rgba32f";
-    case ast::TexelFormat::kNone:
-      return "unknown";
-  }
-  return "unknown";
-}
-
-}  // namespace
-
-GeneratorImpl::GeneratorImpl(const Program* program, const Version& version)
-    : TextGenerator(program), version_(version) {}
-
-GeneratorImpl::~GeneratorImpl() = default;
-
-bool GeneratorImpl::Generate() {
-  {
-    auto out = line();
-    out << "#version " << version_.major_version << version_.minor_version
-        << "0";
-    if (version_.IsES()) {
-      out << " es";
-    }
-  }
-
-  auto helpers_insertion_point = current_buffer_->lines.size();
-
-  line();
-
-  auto* mod = builder_.Sem().Module();
-  for (auto* decl : mod->DependencyOrderedDeclarations()) {
-    if (decl->Is<ast::Alias>()) {
-      continue;  // Ignore aliases.
-    }
-
-    if (auto* global = decl->As<ast::Variable>()) {
-      if (!EmitGlobalVariable(global)) {
-        return false;
-      }
-    } else if (auto* str = decl->As<ast::Struct>()) {
-      // Skip emission if the struct contains a runtime-sized array, since its
-      // only use will be as the store-type of a buffer and we emit those
-      // elsewhere.
-      // TODO(crbug.com/tint/1339): We could also avoid emitting any other
-      // struct that is only used as a buffer store type.
-      TINT_ASSERT(Writer, str->members.size() > 0);
-      auto* last_member = str->members[str->members.size() - 1];
-      auto* arr = last_member->type->As<ast::Array>();
-      if (!arr || !arr->IsRuntimeArray()) {
-        if (!EmitStructType(current_buffer_, builder_.Sem().Get(str))) {
-          return false;
-        }
-      }
-    } else if (auto* func = decl->As<ast::Function>()) {
-      if (func->IsEntryPoint()) {
-        if (!EmitEntryPointFunction(func)) {
-          return false;
-        }
-      } else {
-        if (!EmitFunction(func)) {
-          return false;
-        }
-      }
-    } else {
-      TINT_ICE(Writer, diagnostics_)
-          << "unhandled module-scope declaration: " << decl->TypeInfo().name;
-      return false;
-    }
-  }
-
-  TextBuffer extensions;
-
-  if (version_.IsES() && requires_oes_sample_variables_) {
-    extensions.Append("#extension GL_OES_sample_variables : require");
-  }
-
-  auto indent = current_buffer_->current_indent;
-
-  if (!extensions.lines.empty()) {
-    current_buffer_->Insert(extensions, helpers_insertion_point, indent);
-    helpers_insertion_point += extensions.lines.size();
-  }
-
-  if (version_.IsES() && requires_default_precision_qualifier_) {
-    current_buffer_->Insert("precision mediump float;",
-                            helpers_insertion_point++, indent);
-  }
-
-  if (!helpers_.lines.empty()) {
-    current_buffer_->Insert("", helpers_insertion_point++, indent);
-    current_buffer_->Insert(helpers_, helpers_insertion_point, indent);
-    helpers_insertion_point += helpers_.lines.size();
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitIndexAccessor(
-    std::ostream& out,
-    const ast::IndexAccessorExpression* expr) {
-  if (!EmitExpression(out, expr->object)) {
-    return false;
-  }
-  out << "[";
-
-  if (!EmitExpression(out, expr->index)) {
-    return false;
-  }
-  out << "]";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBitcast(std::ostream& out,
-                                const ast::BitcastExpression* expr) {
-  auto* src_type = TypeOf(expr->expr)->UnwrapRef();
-  auto* dst_type = TypeOf(expr)->UnwrapRef();
-
-  if (!dst_type->is_integer_scalar_or_vector() &&
-      !dst_type->is_float_scalar_or_vector()) {
-    diagnostics_.add_error(
-        diag::System::Writer,
-        "Unable to do bitcast to type " + dst_type->type_name());
-    return false;
-  }
-
-  if (src_type == dst_type) {
-    return EmitExpression(out, expr->expr);
-  }
-
-  if (src_type->is_float_scalar_or_vector() &&
-      dst_type->is_signed_scalar_or_vector()) {
-    out << "floatBitsToInt";
-  } else if (src_type->is_float_scalar_or_vector() &&
-             dst_type->is_unsigned_scalar_or_vector()) {
-    out << "floatBitsToUint";
-  } else if (src_type->is_signed_scalar_or_vector() &&
-             dst_type->is_float_scalar_or_vector()) {
-    out << "intBitsToFloat";
-  } else if (src_type->is_unsigned_scalar_or_vector() &&
-             dst_type->is_float_scalar_or_vector()) {
-    out << "uintBitsToFloat";
-  } else {
-    if (!EmitType(out, dst_type, ast::StorageClass::kNone,
-                  ast::Access::kReadWrite, "")) {
-      return false;
-    }
-  }
-  out << "(";
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
-  auto out = line();
-  if (!EmitExpression(out, stmt->lhs)) {
-    return false;
-  }
-  out << " = ";
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitVectorRelational(std::ostream& out,
-                                         const ast::BinaryExpression* expr) {
-  switch (expr->op) {
-    case ast::BinaryOp::kEqual:
-      out << "equal";
-      break;
-    case ast::BinaryOp::kNotEqual:
-      out << "notEqual";
-      break;
-    case ast::BinaryOp::kLessThan:
-      out << "lessThan";
-      break;
-    case ast::BinaryOp::kGreaterThan:
-      out << "greaterThan";
-      break;
-    case ast::BinaryOp::kLessThanEqual:
-      out << "lessThanEqual";
-      break;
-    case ast::BinaryOp::kGreaterThanEqual:
-      out << "greaterThanEqual";
-      break;
-    default:
-      break;
-  }
-  out << "(";
-  if (!EmitExpression(out, expr->lhs)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, expr->rhs)) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitBitwiseBoolOp(std::ostream& out,
-                                      const ast::BinaryExpression* expr) {
-  auto* bool_type = TypeOf(expr->lhs)->UnwrapRef();
-  auto* uint_type = BoolTypeToUint(bool_type);
-
-  // Cast result to bool scalar or vector type.
-  if (!EmitType(out, bool_type, ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-  ScopedParen outerCastParen(out);
-  // Cast LHS to uint scalar or vector type.
-  if (!EmitType(out, uint_type, ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-  {
-    ScopedParen innerCastParen(out);
-    // Emit LHS.
-    if (!EmitExpression(out, expr->lhs)) {
-      return false;
-    }
-  }
-  // Emit operator.
-  if (expr->op == ast::BinaryOp::kAnd) {
-    out << " & ";
-  } else if (expr->op == ast::BinaryOp::kOr) {
-    out << " | ";
-  } else {
-    TINT_ICE(Writer, diagnostics_)
-        << "unexpected binary op: " << FriendlyName(expr->op);
-    return false;
-  }
-  // Cast RHS to uint scalar or vector type.
-  if (!EmitType(out, uint_type, ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-  {
-    ScopedParen innerCastParen(out);
-    // Emit RHS.
-    if (!EmitExpression(out, expr->rhs)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitBinary(std::ostream& out,
-                               const ast::BinaryExpression* expr) {
-  if (IsRelational(expr->op) && !TypeOf(expr->lhs)->UnwrapRef()->is_scalar()) {
-    return EmitVectorRelational(out, expr);
-  }
-  if (expr->op == ast::BinaryOp::kLogicalAnd ||
-      expr->op == ast::BinaryOp::kLogicalOr) {
-    auto name = UniqueIdentifier(kTempNamePrefix);
-
-    {
-      auto pre = line();
-      pre << "bool " << name << " = ";
-      if (!EmitExpression(pre, expr->lhs)) {
-        return false;
-      }
-      pre << ";";
-    }
-
-    if (expr->op == ast::BinaryOp::kLogicalOr) {
-      line() << "if (!" << name << ") {";
-    } else {
-      line() << "if (" << name << ") {";
-    }
-
-    {
-      ScopedIndent si(this);
-      auto pre = line();
-      pre << name << " = ";
-      if (!EmitExpression(pre, expr->rhs)) {
-        return false;
-      }
-      pre << ";";
-    }
-
-    line() << "}";
-
-    out << "(" << name << ")";
-    return true;
-  }
-  if ((expr->op == ast::BinaryOp::kAnd || expr->op == ast::BinaryOp::kOr) &&
-      TypeOf(expr->lhs)->UnwrapRef()->is_bool_scalar_or_vector()) {
-    return EmitBitwiseBoolOp(out, expr);
-  }
-
-  out << "(";
-  if (!EmitExpression(out, expr->lhs)) {
-    return false;
-  }
-  out << " ";
-
-  switch (expr->op) {
-    case ast::BinaryOp::kAnd:
-      out << "&";
-      break;
-    case ast::BinaryOp::kOr:
-      out << "|";
-      break;
-    case ast::BinaryOp::kXor:
-      out << "^";
-      break;
-    case ast::BinaryOp::kLogicalAnd:
-    case ast::BinaryOp::kLogicalOr: {
-      // These are both handled above.
-      TINT_UNREACHABLE(Writer, diagnostics_);
-      return false;
-    }
-    case ast::BinaryOp::kEqual:
-      out << "==";
-      break;
-    case ast::BinaryOp::kNotEqual:
-      out << "!=";
-      break;
-    case ast::BinaryOp::kLessThan:
-      out << "<";
-      break;
-    case ast::BinaryOp::kGreaterThan:
-      out << ">";
-      break;
-    case ast::BinaryOp::kLessThanEqual:
-      out << "<=";
-      break;
-    case ast::BinaryOp::kGreaterThanEqual:
-      out << ">=";
-      break;
-    case ast::BinaryOp::kShiftLeft:
-      out << "<<";
-      break;
-    case ast::BinaryOp::kShiftRight:
-      // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
-      // implementation-defined behaviour for negative LHS.  We may have to
-      // generate extra code to implement WGSL-specified behaviour for negative
-      // LHS.
-      out << R"(>>)";
-      break;
-
-    case ast::BinaryOp::kAdd:
-      out << "+";
-      break;
-    case ast::BinaryOp::kSubtract:
-      out << "-";
-      break;
-    case ast::BinaryOp::kMultiply:
-      out << "*";
-      break;
-    case ast::BinaryOp::kDivide:
-      out << "/";
-      break;
-    case ast::BinaryOp::kModulo:
-      out << "%";
-      break;
-    case ast::BinaryOp::kNone:
-      diagnostics_.add_error(diag::System::Writer,
-                             "missing binary operation type");
-      return false;
-  }
-  out << " ";
-
-  if (!EmitExpression(out, expr->rhs)) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
-  for (auto* s : stmts) {
-    if (!EmitStatement(s)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
-  ScopedIndent si(this);
-  return EmitStatements(stmts);
-}
-
-bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
-  line() << "{";
-  if (!EmitStatementsWithIndent(stmt->statements)) {
-    return false;
-  }
-  line() << "}";
-  return true;
-}
-
-bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
-  line() << "break;";
-  return true;
-}
-
-bool GeneratorImpl::EmitCall(std::ostream& out,
-                             const ast::CallExpression* expr) {
-  auto* call = builder_.Sem().Get(expr);
-  auto* target = call->Target();
-
-  if (target->Is<sem::Function>()) {
-    return EmitFunctionCall(out, call);
-  }
-  if (auto* builtin = target->As<sem::Builtin>()) {
-    return EmitBuiltinCall(out, call, builtin);
-  }
-  if (auto* cast = target->As<sem::TypeConversion>()) {
-    return EmitTypeConversion(out, call, cast);
-  }
-  if (auto* ctor = target->As<sem::TypeConstructor>()) {
-    return EmitTypeConstructor(out, call, ctor);
-  }
-  TINT_ICE(Writer, diagnostics_)
-      << "unhandled call target: " << target->TypeInfo().name;
-  return false;
-}
-
-bool GeneratorImpl::EmitFunctionCall(std::ostream& out, const sem::Call* call) {
-  const auto& args = call->Arguments();
-  auto* decl = call->Declaration();
-  auto* ident = decl->target.name;
-
-  auto name = builder_.Symbols().NameFor(ident->symbol);
-  auto caller_sym = ident->symbol;
-
-  out << name << "(";
-
-  bool first = true;
-  for (auto* arg : args) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  auto* expr = call->Declaration();
-  if (builtin->IsTexture()) {
-    return EmitTextureCall(out, call, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kSelect) {
-    return EmitSelectCall(out, expr);
-  }
-  if (builtin->Type() == sem::BuiltinType::kDot) {
-    return EmitDotCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kModf) {
-    return EmitModfCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kFrexp) {
-    return EmitFrexpCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kIsNormal) {
-    return EmitIsNormalCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kDegrees) {
-    return EmitDegreesCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kRadians) {
-    return EmitRadiansCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kArrayLength) {
-    return EmitArrayLength(out, expr);
-  }
-  if (builtin->IsDataPacking()) {
-    return EmitDataPackingCall(out, expr, builtin);
-  }
-  if (builtin->IsDataUnpacking()) {
-    return EmitDataUnpackingCall(out, expr, builtin);
-  }
-  if (builtin->IsBarrier()) {
-    return EmitBarrierCall(out, builtin);
-  }
-  if (builtin->IsAtomic()) {
-    return EmitWorkgroupAtomicCall(out, expr, builtin);
-  }
-  auto name = generate_builtin_name(builtin);
-  if (name.empty()) {
-    return false;
-  }
-
-  out << name << "(";
-
-  bool first = true;
-  for (auto* arg : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
-                                       const sem::Call* call,
-                                       const sem::TypeConversion* conv) {
-  if (!EmitType(out, conv->Target(), ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
-                                        const sem::Call* call,
-                                        const sem::TypeConstructor* ctor) {
-  auto* type = ctor->ReturnType();
-
-  // If the type constructor is empty then we need to construct with the zero
-  // value for all components.
-  if (call->Arguments().empty()) {
-    return EmitZeroValue(out, type);
-  }
-
-  auto it = structure_builders_.find(As<sem::Struct>(type));
-  if (it != structure_builders_.end()) {
-    out << it->second << "(";
-  } else {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
-                  "")) {
-      return false;
-    }
-    out << "(";
-  }
-
-  bool first = true;
-  for (auto* arg : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
-                                            const ast::CallExpression* expr,
-                                            const sem::Builtin* builtin) {
-  auto call = [&](const char* name) {
-    out << name;
-    {
-      ScopedParen sp(out);
-      for (size_t i = 0; i < expr->args.size(); i++) {
-        auto* arg = expr->args[i];
-        if (i > 0) {
-          out << ", ";
-        }
-        if (!EmitExpression(out, arg)) {
-          return false;
-        }
-      }
-    }
-    return true;
-  };
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAtomicLoad: {
-      // GLSL does not have an atomicLoad, so we emulate it with
-      // atomicOr using 0 as the OR value
-      out << "atomicOr";
-      {
-        ScopedParen sp(out);
-        if (!EmitExpression(out, expr->args[0])) {
-          return false;
-        }
-        out << ", 0";
-        if (builtin->ReturnType()->Is<sem::U32>()) {
-          out << "u";
-        }
-      }
-      return true;
-    }
-    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
-      return CallBuiltinHelper(
-          out, expr, builtin,
-          [&](TextBuffer* b, const std::vector<std::string>& params) {
-            {
-              auto pre = line(b);
-              if (!EmitTypeAndName(pre, builtin->ReturnType(),
-                                   ast::StorageClass::kNone,
-                                   ast::Access::kUndefined, "result")) {
-                return false;
-              }
-              pre << ";";
-            }
-            {
-              auto pre = line(b);
-              pre << "result.x = atomicCompSwap";
-              {
-                ScopedParen sp(pre);
-                pre << params[0];
-                pre << ", " << params[1];
-                pre << ", " << params[2];
-              }
-              pre << ";";
-            }
-            {
-              auto pre = line(b);
-              pre << "result.y = result.x == " << params[2] << " ? ";
-              if (TypeOf(expr->args[2])->Is<sem::U32>()) {
-                pre << "1u : 0u;";
-              } else {
-                pre << "1 : 0;";
-              }
-            }
-            line(b) << "return result;";
-            return true;
-          });
-    }
-
-    case sem::BuiltinType::kAtomicAdd:
-    case sem::BuiltinType::kAtomicSub:
-      return call("atomicAdd");
-
-    case sem::BuiltinType::kAtomicMax:
-      return call("atomicMax");
-
-    case sem::BuiltinType::kAtomicMin:
-      return call("atomicMin");
-
-    case sem::BuiltinType::kAtomicAnd:
-      return call("atomicAnd");
-
-    case sem::BuiltinType::kAtomicOr:
-      return call("atomicOr");
-
-    case sem::BuiltinType::kAtomicXor:
-      return call("atomicXor");
-
-    case sem::BuiltinType::kAtomicExchange:
-    case sem::BuiltinType::kAtomicStore:
-      // GLSL does not have an atomicStore, so we emulate it with
-      // atomicExchange.
-      return call("atomicExchange");
-
-    default:
-      break;
-  }
-
-  TINT_UNREACHABLE(Writer, diagnostics_)
-      << "unsupported atomic builtin: " << builtin->Type();
-  return false;
-}
-
-bool GeneratorImpl::EmitArrayLength(std::ostream& out,
-                                    const ast::CallExpression* expr) {
-  out << "uint(";
-  if (!EmitExpression(out, expr->args[0])) {
-    return false;
-  }
-  out << ".length())";
-  return true;
-}
-
-bool GeneratorImpl::EmitSelectCall(std::ostream& out,
-                                   const ast::CallExpression* expr) {
-  auto* expr_false = expr->args[0];
-  auto* expr_true = expr->args[1];
-  auto* expr_cond = expr->args[2];
-  // GLSL does not support ternary expressions with a bool vector conditional,
-  // but it does support mix() with same.
-  if (TypeOf(expr_cond)->UnwrapRef()->is_bool_vector()) {
-    out << "mix(";
-    if (!EmitExpression(out, expr_false)) {
-      return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, expr_true)) {
-      return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, expr_cond)) {
-      return false;
-    }
-    out << ")";
-    return true;
-  }
-  ScopedParen paren(out);
-  if (!EmitExpression(out, expr_cond)) {
-    return false;
-  }
-
-  out << " ? ";
-
-  if (!EmitExpression(out, expr_true)) {
-    return false;
-  }
-
-  out << " : ";
-
-  if (!EmitExpression(out, expr_false)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDotCall(std::ostream& out,
-                                const ast::CallExpression* expr,
-                                const sem::Builtin* builtin) {
-  auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
-  std::string fn = "dot";
-  if (vec_ty->type()->is_integer_scalar()) {
-    // GLSL does not have a builtin for dot() with integer vector types.
-    // Generate the helper function if it hasn't been created already
-    fn = utils::GetOrCreate(int_dot_funcs_, vec_ty, [&]() -> std::string {
-      TextBuffer b;
-      TINT_DEFER(helpers_.Append(b));
-
-      auto fn_name = UniqueIdentifier("tint_int_dot");
-
-      std::string v;
-      {
-        std::stringstream s;
-        if (!EmitType(s, vec_ty->type(), ast::StorageClass::kNone,
-                      ast::Access::kRead, "")) {
-          return "";
-        }
-        v = s.str();
-      }
-      {  // (u)int tint_int_dot([i|u]vecN a, [i|u]vecN b) {
-        auto l = line(&b);
-        if (!EmitType(l, vec_ty->type(), ast::StorageClass::kNone,
-                      ast::Access::kRead, "")) {
-          return "";
-        }
-        l << " " << fn_name << "(";
-        if (!EmitType(l, vec_ty, ast::StorageClass::kNone, ast::Access::kRead,
-                      "")) {
-          return "";
-        }
-        l << " a, ";
-        if (!EmitType(l, vec_ty, ast::StorageClass::kNone, ast::Access::kRead,
-                      "")) {
-          return "";
-        }
-        l << " b) {";
-      }
-      {
-        auto l = line(&b);
-        l << "  return ";
-        for (uint32_t i = 0; i < vec_ty->Width(); i++) {
-          if (i > 0) {
-            l << " + ";
-          }
-          l << "a[" << i << "]*b[" << i << "]";
-        }
-        l << ";";
-      }
-      line(&b) << "}";
-      return fn_name;
-    });
-    if (fn.empty()) {
-      return false;
-    }
-  }
-
-  out << fn << "(";
-  if (!EmitExpression(out, expr->args[0])) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, expr->args[1])) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitModfCall(std::ostream& out,
-                                 const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
-  if (expr->args.size() == 1) {
-    return CallBuiltinHelper(
-        out, expr, builtin,
-        [&](TextBuffer* b, const std::vector<std::string>& params) {
-          // Emit the builtin return type unique to this overload. This does not
-          // exist in the AST, so it will not be generated in Generate().
-          if (!EmitStructType(&helpers_,
-                              builtin->ReturnType()->As<sem::Struct>())) {
-            return false;
-          }
-
-          {
-            auto l = line(b);
-            if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                          ast::Access::kUndefined, "")) {
-              return false;
-            }
-            l << " result;";
-          }
-          line(b) << "result.fract = modf(" << params[0] << ", result.whole);";
-          line(b) << "return result;";
-          return true;
-        });
-  }
-
-  // DEPRECATED
-  out << "modf";
-  ScopedParen sp(out);
-  if (!EmitExpression(out, expr->args[0])) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, expr->args[1])) {
-    return false;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
-                                  const ast::CallExpression* expr,
-                                  const sem::Builtin* builtin) {
-  if (expr->args.size() == 1) {
-    return CallBuiltinHelper(
-        out, expr, builtin,
-        [&](TextBuffer* b, const std::vector<std::string>& params) {
-          // Emit the builtin return type unique to this overload. This does not
-          // exist in the AST, so it will not be generated in Generate().
-          if (!EmitStructType(&helpers_,
-                              builtin->ReturnType()->As<sem::Struct>())) {
-            return false;
-          }
-
-          {
-            auto l = line(b);
-            if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                          ast::Access::kUndefined, "")) {
-              return false;
-            }
-            l << " result;";
-          }
-          line(b) << "result.sig = frexp(" << params[0] << ", result.exp);";
-          line(b) << "return result;";
-          return true;
-        });
-  }
-  // DEPRECATED
-  // Exponent is an integer in WGSL, but HLSL wants a float.
-  // We need to make the call with a temporary float, and then cast.
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* significand_ty = builtin->Parameters()[0]->Type();
-        auto significand = params[0];
-        auto* exponent_ty = builtin->Parameters()[1]->Type();
-        auto exponent = params[1];
-
-        std::string width;
-        if (auto* vec = significand_ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        // Exponent is an integer, which HLSL does not have an overload for.
-        // We need to cast from a float.
-        line(b) << "float" << width << " float_exp;";
-        line(b) << "float" << width << " significand = frexp(" << significand
-                << ", float_exp);";
-        {
-          auto l = line(b);
-          l << exponent << " = ";
-          if (!EmitType(l, exponent_ty->UnwrapPtr(), ast::StorageClass::kNone,
-                        ast::Access::kUndefined, "")) {
-            return false;
-          }
-          l << "(float_exp);";
-        }
-        line(b) << "return significand;";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitIsNormalCall(std::ostream& out,
-                                     const ast::CallExpression* expr,
-                                     const sem::Builtin* builtin) {
-  // GLSL doesn't have a isNormal builtin, we need to emulate
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* input_ty = builtin->Parameters()[0]->Type();
-
-        std::string vec_type;
-        if (auto* vec = input_ty->As<sem::Vector>()) {
-          vec_type = "uvec" + std::to_string(vec->Width());
-        } else {
-          vec_type = "uint";
-        }
-
-        constexpr auto* kExponentMask = "0x7f80000u";
-        constexpr auto* kMinNormalExponent = "0x0080000u";
-        constexpr auto* kMaxNormalExponent = "0x7f00000u";
-
-        line(b) << vec_type << " exponent = floatBitsToUint(" << params[0]
-                << ") & " << kExponentMask << ";";
-        line(b) << vec_type << " clamped = "
-                << "clamp(exponent, " << kMinNormalExponent << ", "
-                << kMaxNormalExponent << ");";
-        if (input_ty->Is<sem::Vector>()) {
-          line(b) << "return equal(clamped, exponent);";
-        } else {
-          line(b) << "return clamped == exponent;";
-        }
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kRadToDeg << ";";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kDegToRad << ";";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDataPackingCall(std::ostream& out,
-                                        const ast::CallExpression* expr,
-                                        const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        uint32_t dims = 2;
-        bool is_signed = false;
-        uint32_t scale = 65535;
-        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kPack4x8unorm) {
-          dims = 4;
-          scale = 255;
-        }
-        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kPack2x16snorm) {
-          is_signed = true;
-          scale = (scale - 1) / 2;
-        }
-        switch (builtin->Type()) {
-          case sem::BuiltinType::kPack4x8snorm:
-          case sem::BuiltinType::kPack4x8unorm:
-          case sem::BuiltinType::kPack2x16snorm:
-          case sem::BuiltinType::kPack2x16unorm: {
-            {
-              auto l = line(b);
-              l << (is_signed ? "" : "u") << "int" << dims
-                << " i = " << (is_signed ? "" : "u") << "int" << dims
-                << "(round(clamp(" << params[0] << ", "
-                << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale
-                << ".0))";
-              if (is_signed) {
-                l << " & " << (dims == 4 ? "0xff" : "0xffff");
-              }
-              l << ";";
-            }
-            {
-              auto l = line(b);
-              l << "return ";
-              if (is_signed) {
-                l << "asuint";
-              }
-              l << "(i.x | i.y << " << (32 / dims);
-              if (dims == 4) {
-                l << " | i.z << 16 | i.w << 24";
-              }
-              l << ");";
-            }
-            break;
-          }
-          case sem::BuiltinType::kPack2x16float: {
-            line(b) << "uint2 i = f32tof16(" << params[0] << ");";
-            line(b) << "return i.x | (i.y << 16);";
-            break;
-          }
-          default:
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "Internal error: unhandled data packing builtin");
-            return false;
-        }
-
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out,
-                                          const ast::CallExpression* expr,
-                                          const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        uint32_t dims = 2;
-        bool is_signed = false;
-        uint32_t scale = 65535;
-        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kUnpack4x8unorm) {
-          dims = 4;
-          scale = 255;
-        }
-        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kUnpack2x16snorm) {
-          is_signed = true;
-          scale = (scale - 1) / 2;
-        }
-        switch (builtin->Type()) {
-          case sem::BuiltinType::kUnpack4x8snorm:
-          case sem::BuiltinType::kUnpack2x16snorm: {
-            line(b) << "int j = int(" << params[0] << ");";
-            {  // Perform sign extension on the converted values.
-              auto l = line(b);
-              l << "int" << dims << " i = int" << dims << "(";
-              if (dims == 2) {
-                l << "j << 16, j) >> 16";
-              } else {
-                l << "j << 24, j << 16, j << 8, j) >> 24";
-              }
-              l << ";";
-            }
-            line(b) << "return clamp(float" << dims << "(i) / " << scale
-                    << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
-            break;
-          }
-          case sem::BuiltinType::kUnpack4x8unorm:
-          case sem::BuiltinType::kUnpack2x16unorm: {
-            line(b) << "uint j = " << params[0] << ";";
-            {
-              auto l = line(b);
-              l << "uint" << dims << " i = uint" << dims << "(";
-              l << "j & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
-              if (dims == 4) {
-                l << "(j >> " << (32 / dims)
-                  << ") & 0xff, (j >> 16) & 0xff, j >> 24";
-              } else {
-                l << "j >> " << (32 / dims);
-              }
-              l << ");";
-            }
-            line(b) << "return float" << dims << "(i) / " << scale << ".0;";
-            break;
-          }
-          case sem::BuiltinType::kUnpack2x16float:
-            line(b) << "uint i = " << params[0] << ";";
-            line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
-            break;
-          default:
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "Internal error: unhandled data packing builtin");
-            return false;
-        }
-
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitBarrierCall(std::ostream& out,
-                                    const sem::Builtin* builtin) {
-  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
-  // instruction.
-  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
-    out << "barrier()";
-  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
-    out << "{ barrier(); memoryBarrierBuffer(); }";
-  } else {
-    TINT_UNREACHABLE(Writer, diagnostics_)
-        << "unexpected barrier builtin type " << sem::str(builtin->Type());
-    return false;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitTextureCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  using Usage = sem::ParameterUsage;
-
-  auto& signature = builtin->Signature();
-  auto* expr = call->Declaration();
-  auto arguments = expr->args;
-
-  // Returns the argument with the given usage
-  auto arg = [&](Usage usage) {
-    int idx = signature.IndexOf(usage);
-    return (idx >= 0) ? arguments[idx] : nullptr;
-  };
-
-  auto* texture = arg(Usage::kTexture);
-  if (!texture) {
-    TINT_ICE(Writer, diagnostics_) << "missing texture argument";
-    return false;
-  }
-
-  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureDimensions: {
-      if (texture_type->Is<sem::StorageTexture>()) {
-        out << "imageSize(";
-      } else {
-        out << "textureSize(";
-      }
-      if (!EmitExpression(out, texture)) {
-        return false;
-      }
-
-      // The LOD parameter is mandatory on textureSize() for non-multisampled
-      // textures.
-      if (!texture_type->Is<sem::StorageTexture>() &&
-          !texture_type->Is<sem::MultisampledTexture>() &&
-          !texture_type->Is<sem::DepthMultisampledTexture>()) {
-        out << ", ";
-        if (auto* level_arg = arg(Usage::kLevel)) {
-          if (!EmitExpression(out, level_arg)) {
-            return false;
-          }
-        } else {
-          out << "0";
-        }
-      }
-      out << ")";
-      // textureSize() on sampler2dArray returns the array size in the
-      // final component, so strip it out.
-      if (texture_type->dim() == ast::TextureDimension::k2dArray) {
-        out << ".xy";
-      }
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumLayers: {
-      if (texture_type->Is<sem::StorageTexture>()) {
-        out << "imageSize(";
-      } else {
-        out << "textureSize(";
-      }
-      // textureSize() on sampler2dArray returns the array size in the
-      // final component, so return it
-      if (!EmitExpression(out, texture)) {
-        return false;
-      }
-      // The LOD parameter is mandatory on textureSize() for non-multisampled
-      // textures.
-      if (!texture_type->Is<sem::StorageTexture>() &&
-          !texture_type->Is<sem::MultisampledTexture>() &&
-          !texture_type->Is<sem::DepthMultisampledTexture>()) {
-        out << ", ";
-        if (auto* level_arg = arg(Usage::kLevel)) {
-          if (!EmitExpression(out, level_arg)) {
-            return false;
-          }
-        } else {
-          out << "0";
-        }
-      }
-      out << ").z";
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumLevels: {
-      out << "textureQueryLevels(";
-      if (!EmitExpression(out, texture)) {
-        return false;
-      }
-      out << ");";
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumSamples: {
-      out << "textureSamples(";
-      if (!EmitExpression(out, texture)) {
-        return false;
-      }
-      out << ");";
-      return true;
-    }
-    default:
-      break;
-  }
-
-  uint32_t glsl_ret_width = 4u;
-  bool is_depth = texture_type->Is<sem::DepthTexture>();
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureSample:
-    case sem::BuiltinType::kTextureSampleBias:
-      out << "texture";
-      if (is_depth) {
-        glsl_ret_width = 1u;
-      }
-      break;
-    case sem::BuiltinType::kTextureSampleLevel:
-      out << "textureLod";
-      break;
-    case sem::BuiltinType::kTextureGather:
-    case sem::BuiltinType::kTextureGatherCompare:
-      out << "textureGather";
-      break;
-    case sem::BuiltinType::kTextureSampleGrad:
-      out << "textureGrad";
-      break;
-    case sem::BuiltinType::kTextureSampleCompare:
-    case sem::BuiltinType::kTextureSampleCompareLevel:
-      out << "texture";
-      glsl_ret_width = 1;
-      break;
-    case sem::BuiltinType::kTextureLoad:
-      out << "texelFetch";
-      break;
-    case sem::BuiltinType::kTextureStore:
-      out << "imageStore";
-      break;
-    default:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Internal compiler error: Unhandled texture builtin '" +
-              std::string(builtin->str()) + "'");
-      return false;
-  }
-
-  if (builtin->Signature().IndexOf(sem::ParameterUsage::kOffset) >= 0) {
-    out << "Offset";
-  }
-
-  out << "(";
-
-  if (!EmitExpression(out, texture))
-    return false;
-
-  out << ", ";
-
-  auto* param_coords = arg(Usage::kCoords);
-  if (!param_coords) {
-    TINT_ICE(Writer, diagnostics_) << "missing coords argument";
-    return false;
-  }
-
-  if (auto* array_index = arg(Usage::kArrayIndex)) {
-    // Array index needs to be appended to the coordinates.
-    param_coords =
-        AppendVector(&builder_, param_coords, array_index)->Declaration();
-  }
-  bool is_cube_array = texture_type->dim() == ast::TextureDimension::kCubeArray;
-
-  // GLSL requires Dref to be appended to the coordinates, *unless* it's
-  // samplerCubeArrayShadow, in which case it will be handled as a separate
-  // parameter [1].
-  if (is_depth && !is_cube_array) {
-    if (auto* depth_ref = arg(Usage::kDepthRef)) {
-      param_coords =
-          AppendVector(&builder_, param_coords, depth_ref)->Declaration();
-    } else if (builtin->Type() == sem::BuiltinType::kTextureSample) {
-      // Sampling a depth texture in GLSL always requires a depth reference, so
-      // append zero here.
-      auto* f32 = builder_.create<sem::F32>();
-      auto* zero = builder_.Expr(0.0f);
-      auto* stmt = builder_.Sem().Get(param_coords)->Stmt();
-      auto* sem_zero = builder_.create<sem::Expression>(
-          zero, f32, stmt, sem::Constant{}, /* has_side_effects */ false);
-      builder_.Sem().Add(zero, sem_zero);
-      param_coords = AppendVector(&builder_, param_coords, zero)->Declaration();
-    }
-  }
-
-  if (!EmitExpression(out, param_coords)) {
-    return false;
-  }
-
-  for (auto usage : {Usage::kLevel, Usage::kDdx, Usage::kDdy,
-                     Usage::kSampleIndex, Usage::kValue}) {
-    if (auto* e = arg(usage)) {
-      out << ", ";
-      if (!EmitExpression(out, e)) {
-        return false;
-      }
-    }
-  }
-
-  // GLSL's textureGather always requires a refZ parameter.
-  if (is_depth && builtin->Type() == sem::BuiltinType::kTextureGather) {
-    out << ", 0.0";
-  }
-
-  for (auto usage : {Usage::kOffset, Usage::kComponent, Usage::kBias}) {
-    if (auto* e = arg(usage)) {
-      out << ", ";
-      if (!EmitExpression(out, e)) {
-        return false;
-      }
-    }
-  }
-
-  // [1] samplerCubeArrayShadow requires a separate depthRef parameter
-  if (is_depth && is_cube_array) {
-    if (auto* e = arg(Usage::kDepthRef)) {
-      out << ", ";
-      if (!EmitExpression(out, e)) {
-        return false;
-      }
-    } else if (builtin->Type() == sem::BuiltinType::kTextureSample) {
-      out << ", 0.0f";
-    }
-  }
-
-  out << ")";
-
-  if (builtin->ReturnType()->Is<sem::Void>()) {
-    return true;
-  }
-  // If the builtin return type does not match the number of elements of the
-  // GLSL builtin, we need to swizzle the expression to generate the correct
-  // number of components.
-  uint32_t wgsl_ret_width = 1;
-  if (auto* vec = builtin->ReturnType()->As<sem::Vector>()) {
-    wgsl_ret_width = vec->Width();
-  }
-  if (wgsl_ret_width < glsl_ret_width) {
-    out << ".";
-    for (uint32_t i = 0; i < wgsl_ret_width; i++) {
-      out << "xyz"[i];
-    }
-  }
-  if (wgsl_ret_width > glsl_ret_width) {
-    TINT_ICE(Writer, diagnostics_)
-        << "WGSL return width (" << wgsl_ret_width
-        << ") is wider than GLSL return width (" << glsl_ret_width << ") for "
-        << builtin->Type();
-    return false;
-  }
-
-  return true;
-}
-
-std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAbs:
-    case sem::BuiltinType::kAcos:
-    case sem::BuiltinType::kAll:
-    case sem::BuiltinType::kAny:
-    case sem::BuiltinType::kAsin:
-    case sem::BuiltinType::kAtan:
-    case sem::BuiltinType::kCeil:
-    case sem::BuiltinType::kClamp:
-    case sem::BuiltinType::kCos:
-    case sem::BuiltinType::kCosh:
-    case sem::BuiltinType::kCross:
-    case sem::BuiltinType::kDeterminant:
-    case sem::BuiltinType::kDistance:
-    case sem::BuiltinType::kDot:
-    case sem::BuiltinType::kExp:
-    case sem::BuiltinType::kExp2:
-    case sem::BuiltinType::kFloor:
-    case sem::BuiltinType::kFrexp:
-    case sem::BuiltinType::kLdexp:
-    case sem::BuiltinType::kLength:
-    case sem::BuiltinType::kLog:
-    case sem::BuiltinType::kLog2:
-    case sem::BuiltinType::kMax:
-    case sem::BuiltinType::kMin:
-    case sem::BuiltinType::kModf:
-    case sem::BuiltinType::kNormalize:
-    case sem::BuiltinType::kPow:
-    case sem::BuiltinType::kReflect:
-    case sem::BuiltinType::kRefract:
-    case sem::BuiltinType::kRound:
-    case sem::BuiltinType::kSign:
-    case sem::BuiltinType::kSin:
-    case sem::BuiltinType::kSinh:
-    case sem::BuiltinType::kSqrt:
-    case sem::BuiltinType::kStep:
-    case sem::BuiltinType::kTan:
-    case sem::BuiltinType::kTanh:
-    case sem::BuiltinType::kTranspose:
-    case sem::BuiltinType::kTrunc:
-      return builtin->str();
-    case sem::BuiltinType::kAtan2:
-      return "atan";
-    case sem::BuiltinType::kCountOneBits:
-      return "countbits";
-    case sem::BuiltinType::kDpdx:
-      return "ddx";
-    case sem::BuiltinType::kDpdxCoarse:
-      return "ddx_coarse";
-    case sem::BuiltinType::kDpdxFine:
-      return "ddx_fine";
-    case sem::BuiltinType::kDpdy:
-      return "ddy";
-    case sem::BuiltinType::kDpdyCoarse:
-      return "ddy_coarse";
-    case sem::BuiltinType::kDpdyFine:
-      return "ddy_fine";
-    case sem::BuiltinType::kFaceForward:
-      return "faceforward";
-    case sem::BuiltinType::kFract:
-      return "frac";
-    case sem::BuiltinType::kFma:
-      return "mad";
-    case sem::BuiltinType::kFwidth:
-    case sem::BuiltinType::kFwidthCoarse:
-    case sem::BuiltinType::kFwidthFine:
-      return "fwidth";
-    case sem::BuiltinType::kInverseSqrt:
-      return "rsqrt";
-    case sem::BuiltinType::kIsFinite:
-      return "isfinite";
-    case sem::BuiltinType::kIsInf:
-      return "isinf";
-    case sem::BuiltinType::kIsNan:
-      return "isnan";
-    case sem::BuiltinType::kMix:
-      return "mix";
-    case sem::BuiltinType::kReverseBits:
-      return "reversebits";
-    case sem::BuiltinType::kSmoothStep:
-      return "smoothstep";
-    default:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Unknown builtin method: " + std::string(builtin->str()));
-  }
-
-  return "";
-}
-
-bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
-  if (stmt->IsDefault()) {
-    line() << "default: {";
-  } else {
-    for (auto* selector : stmt->selectors) {
-      auto out = line();
-      out << "case ";
-      if (!EmitLiteral(out, selector)) {
-        return false;
-      }
-      out << ":";
-      if (selector == stmt->selectors.back()) {
-        out << " {";
-      }
-    }
-  }
-
-  {
-    ScopedIndent si(this);
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-    if (!last_is_break_or_fallthrough(stmt->body)) {
-      line() << "break;";
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
-  if (!emit_continuing_()) {
-    return false;
-  }
-  line() << "continue;";
-  return true;
-}
-
-bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
-  // TODO(dsinclair): Verify this is correct when the discard semantics are
-  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
-  line() << "discard;";
-  return true;
-}
-
-bool GeneratorImpl::EmitExpression(std::ostream& out,
-                                   const ast::Expression* expr) {
-  if (auto* a = expr->As<ast::IndexAccessorExpression>()) {
-    return EmitIndexAccessor(out, a);
-  }
-  if (auto* b = expr->As<ast::BinaryExpression>()) {
-    return EmitBinary(out, b);
-  }
-  if (auto* b = expr->As<ast::BitcastExpression>()) {
-    return EmitBitcast(out, b);
-  }
-  if (auto* c = expr->As<ast::CallExpression>()) {
-    return EmitCall(out, c);
-  }
-  if (auto* i = expr->As<ast::IdentifierExpression>()) {
-    return EmitIdentifier(out, i);
-  }
-  if (auto* l = expr->As<ast::LiteralExpression>()) {
-    return EmitLiteral(out, l);
-  }
-  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
-    return EmitMemberAccessor(out, m);
-  }
-  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
-    return EmitUnaryOp(out, u);
-  }
-
-  diagnostics_.add_error(
-      diag::System::Writer,
-      "unknown expression type: " + std::string(expr->TypeInfo().name));
-  return false;
-}
-
-bool GeneratorImpl::EmitIdentifier(std::ostream& out,
-                                   const ast::IdentifierExpression* expr) {
-  out << builder_.Symbols().NameFor(expr->symbol);
-  return true;
-}
-
-bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
-  {
-    auto out = line();
-    out << "if (";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      line() << "} else {";
-      increment_indent();
-
-      {
-        auto out = line();
-        out << "if (";
-        if (!EmitExpression(out, e->condition)) {
-          return false;
-        }
-        out << ") {";
-      }
-    } else {
-      line() << "} else {";
-    }
-
-    if (!EmitStatementsWithIndent(e->body->statements)) {
-      return false;
-    }
-  }
-
-  line() << "}";
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      decrement_indent();
-      line() << "}";
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-  auto* sem = builder_.Sem().Get(func);
-
-  if (ast::HasAttribute<ast::InternalAttribute>(func->attributes)) {
-    // An internal function. Do not emit.
-    return true;
-  }
-
-  {
-    auto out = line();
-    auto name = builder_.Symbols().NameFor(func->symbol);
-    if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
-                  ast::Access::kReadWrite, "")) {
-      return false;
-    }
-
-    out << " " << name << "(";
-
-    bool first = true;
-
-    for (auto* v : sem->Parameters()) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      auto const* type = v->Type();
-
-      if (auto* ptr = type->As<sem::Pointer>()) {
-        // Transform pointer parameters in to `inout` parameters.
-        // The WGSL spec is highly restrictive in what can be passed in pointer
-        // parameters, which allows for this transformation. See:
-        // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
-        out << "inout ";
-        type = ptr->StoreType();
-      }
-
-      // Note: WGSL only allows for StorageClass::kNone on parameters, however
-      // the sanitizer transforms generates load / store functions for storage
-      // or uniform buffers. These functions have a buffer parameter with
-      // StorageClass::kStorage or StorageClass::kUniform. This is required to
-      // correctly translate the parameter to a [RW]ByteAddressBuffer for
-      // storage buffers and a uint4[N] for uniform buffers.
-      if (!EmitTypeAndName(
-              out, type, v->StorageClass(), v->Access(),
-              builder_.Symbols().NameFor(v->Declaration()->symbol))) {
-        return false;
-      }
-    }
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(func->body->statements)) {
-    return false;
-  }
-
-  line() << "}";
-  line();
-
-  return true;
-}
-
-bool GeneratorImpl::EmitGlobalVariable(const ast::Variable* global) {
-  if (global->is_const) {
-    return EmitProgramConstVariable(global);
-  }
-
-  auto* sem = builder_.Sem().Get(global);
-  switch (sem->StorageClass()) {
-    case ast::StorageClass::kUniform:
-      return EmitUniformVariable(sem);
-    case ast::StorageClass::kStorage:
-      return EmitStorageVariable(sem);
-    case ast::StorageClass::kUniformConstant:
-      return EmitHandleVariable(sem);
-    case ast::StorageClass::kPrivate:
-      return EmitPrivateVariable(sem);
-    case ast::StorageClass::kWorkgroup:
-      return EmitWorkgroupVariable(sem);
-    case ast::StorageClass::kInput:
-    case ast::StorageClass::kOutput:
-      return EmitIOVariable(sem);
-    default:
-      break;
-  }
-
-  TINT_ICE(Writer, diagnostics_)
-      << "unhandled storage class " << sem->StorageClass();
-  return false;
-}
-
-bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto* type = var->Type()->UnwrapRef();
-  auto* str = type->As<sem::Struct>();
-  if (!str) {
-    TINT_ICE(Writer, builder_.Diagnostics())
-        << "storage variable must be of struct type";
-    return false;
-  }
-  ast::VariableBindingPoint bp = decl->BindingPoint();
-  {
-    auto out = line();
-    out << "layout(binding = " << bp.binding->value;
-    if (version_.IsDesktop()) {
-      out << ", std140";
-    }
-    out << ") uniform " << UniqueIdentifier(StructName(str)) << " {";
-  }
-  EmitStructMembers(current_buffer_, str, /* emit_offsets */ true);
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  line() << "} " << name << ";";
-  line();
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto* type = var->Type()->UnwrapRef();
-  auto* str = type->As<sem::Struct>();
-  if (!str) {
-    TINT_ICE(Writer, builder_.Diagnostics())
-        << "storage variable must be of struct type";
-    return false;
-  }
-  ast::VariableBindingPoint bp = decl->BindingPoint();
-  line() << "layout(binding = " << bp.binding->value << ", std430) buffer "
-         << UniqueIdentifier(StructName(str)) << " {";
-  EmitStructMembers(current_buffer_, str, /* emit_offsets */ true);
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  line() << "} " << name << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto out = line();
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (type->Is<sem::Sampler>()) {
-    // GLSL ignores Sampler variables.
-    return true;
-  }
-  if (auto* storage = type->As<sem::StorageTexture>()) {
-    out << "layout(" << convert_texel_format_to_glsl(storage->texel_format())
-        << ") ";
-  }
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto out = line();
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  out << " = ";
-  if (auto* constructor = decl->constructor) {
-    if (!EmitExpression(out, constructor)) {
-      return false;
-    }
-  } else {
-    if (!EmitZeroValue(out, var->Type()->UnwrapRef())) {
-      return false;
-    }
-  }
-
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto out = line();
-
-  out << "shared ";
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  if (auto* constructor = decl->constructor) {
-    out << " = ";
-    if (!EmitExpression(out, constructor)) {
-      return false;
-    }
-  }
-
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitIOVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-
-  if (auto* b = ast::GetAttribute<ast::BuiltinAttribute>(decl->attributes)) {
-    // Use of gl_SampleID requires the GL_OES_sample_variables extension
-    if (RequiresOESSampleVariables(b->builtin)) {
-      requires_oes_sample_variables_ = true;
-    }
-    // Do not emit builtin (gl_) variables.
-    return true;
-  }
-
-  auto out = line();
-  EmitAttributes(out, decl->attributes);
-  EmitInterpolationQualifiers(out, decl->attributes);
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  if (auto* constructor = decl->constructor) {
-    out << " = ";
-    if (!EmitExpression(out, constructor)) {
-      return false;
-    }
-  }
-
-  out << ";";
-  return true;
-}
-
-void GeneratorImpl::EmitInterpolationQualifiers(
-    std::ostream& out,
-    const ast::AttributeList& attributes) {
-  for (auto* attr : attributes) {
-    if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-      switch (interpolate->type) {
-        case ast::InterpolationType::kPerspective:
-        case ast::InterpolationType::kLinear:
-          break;
-        case ast::InterpolationType::kFlat:
-          out << "flat ";
-          break;
-      }
-      switch (interpolate->sampling) {
-        case ast::InterpolationSampling::kCentroid:
-          out << "centroid ";
-          break;
-        case ast::InterpolationSampling::kSample:
-        case ast::InterpolationSampling::kCenter:
-        case ast::InterpolationSampling::kNone:
-          break;
-      }
-    }
-  }
-}
-
-bool GeneratorImpl::EmitAttributes(std::ostream& out,
-                                   const ast::AttributeList& attributes) {
-  if (attributes.empty()) {
-    return true;
-  }
-  bool first = true;
-  for (auto* attr : attributes) {
-    if (auto* location = attr->As<ast::LocationAttribute>()) {
-      out << (first ? "layout(" : ", ");
-      out << "location = " << std::to_string(location->value);
-      first = false;
-    }
-  }
-  if (!first) {
-    out << ") ";
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
-  auto* func_sem = builder_.Sem().Get(func);
-
-  if (func->PipelineStage() == ast::PipelineStage::kFragment) {
-    requires_default_precision_qualifier_ = true;
-  }
-
-  if (func->PipelineStage() == ast::PipelineStage::kCompute) {
-    auto out = line();
-    // Emit the layout(local_size) attributes.
-    auto wgsize = func_sem->WorkgroupSize();
-    out << "layout(";
-    for (int i = 0; i < 3; i++) {
-      if (i > 0) {
-        out << ", ";
-      }
-      out << "local_size_" << (i == 0 ? "x" : i == 1 ? "y" : "z") << " = ";
-
-      if (wgsize[i].overridable_const) {
-        auto* global = builder_.Sem().Get<sem::GlobalVariable>(
-            wgsize[i].overridable_const);
-        if (!global->IsOverridable()) {
-          TINT_ICE(Writer, builder_.Diagnostics())
-              << "expected a pipeline-overridable constant";
-        }
-        out << kSpecConstantPrefix << global->ConstantId();
-      } else {
-        out << std::to_string(wgsize[i].value);
-      }
-    }
-    out << ") in;";
-  }
-
-  // Emit original entry point signature
-  {
-    auto out = line();
-    out << func->return_type->FriendlyName(builder_.Symbols()) << " "
-        << builder_.Symbols().NameFor(func->symbol) << "(";
-
-    bool first = true;
-
-    // Emit entry point parameters.
-    for (auto* var : func->params) {
-      auto* sem = builder_.Sem().Get(var);
-      auto* type = sem->Type();
-      if (!type->Is<sem::Struct>()) {
-        // ICE likely indicates that the CanonicalizeEntryPointIO transform was
-        // not run, or a builtin parameter was added after it was run.
-        TINT_ICE(Writer, diagnostics_)
-            << "Unsupported non-struct entry point parameter";
-      }
-
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                           builder_.Symbols().NameFor(var->symbol))) {
-        return false;
-      }
-    }
-
-    out << ") {";
-  }
-
-  // Emit original entry point function body
-  {
-    ScopedIndent si(this);
-
-    if (!EmitStatements(func->body->statements)) {
-      return false;
-    }
-
-    if (!Is<ast::ReturnStatement>(func->body->Last())) {
-      ast::ReturnStatement ret(ProgramID(), Source{});
-      if (!EmitStatement(&ret)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitLiteral(std::ostream& out,
-                                const ast::LiteralExpression* lit) {
-  if (auto* l = lit->As<ast::BoolLiteralExpression>()) {
-    out << (l->value ? "true" : "false");
-  } else if (auto* fl = lit->As<ast::FloatLiteralExpression>()) {
-    if (std::isinf(fl->value)) {
-      out << (fl->value >= 0 ? "uintBitsToFloat(0x7f800000u)"
-                             : "uintBitsToFloat(0xff800000u)");
-    } else if (std::isnan(fl->value)) {
-      out << "uintBitsToFloat(0x7fc00000u)";
-    } else {
-      out << FloatToString(fl->value) << "f";
-    }
-  } else if (auto* sl = lit->As<ast::SintLiteralExpression>()) {
-    out << sl->value;
-  } else if (auto* ul = lit->As<ast::UintLiteralExpression>()) {
-    out << ul->value << "u";
-  } else {
-    diagnostics_.add_error(diag::System::Writer, "unknown literal type");
-    return false;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
-  if (type->Is<sem::Bool>()) {
-    out << "false";
-  } else if (type->Is<sem::F32>()) {
-    out << "0.0f";
-  } else if (type->Is<sem::I32>()) {
-    out << "0";
-  } else if (type->Is<sem::U32>()) {
-    out << "0u";
-  } else if (auto* vec = type->As<sem::Vector>()) {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
-                  "")) {
-      return false;
-    }
-    ScopedParen sp(out);
-    for (uint32_t i = 0; i < vec->Width(); i++) {
-      if (i != 0) {
-        out << ", ";
-      }
-      if (!EmitZeroValue(out, vec->type())) {
-        return false;
-      }
-    }
-  } else if (auto* mat = type->As<sem::Matrix>()) {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
-                  "")) {
-      return false;
-    }
-    ScopedParen sp(out);
-    for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) {
-      if (i != 0) {
-        out << ", ";
-      }
-      if (!EmitZeroValue(out, mat->type())) {
-        return false;
-      }
-    }
-  } else if (auto* str = type->As<sem::Struct>()) {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined,
-                  "")) {
-      return false;
-    }
-    bool first = true;
-    out << "(";
-    for (auto* member : str->Members()) {
-      if (!first) {
-        out << ", ";
-      } else {
-        first = false;
-      }
-      EmitZeroValue(out, member->Type());
-    }
-    out << ")";
-  } else if (auto* array = type->As<sem::Array>()) {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kUndefined,
-                  "")) {
-      return false;
-    }
-    out << "(";
-    for (uint32_t i = 0; i < array->Count(); i++) {
-      if (i != 0) {
-        out << ", ";
-      }
-      EmitZeroValue(out, array->ElemType());
-    }
-    out << ")";
-  } else {
-    diagnostics_.add_error(
-        diag::System::Writer,
-        "Invalid type for zero emission: " + type->type_name());
-    return false;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
-  auto emit_continuing = [this, stmt]() {
-    if (stmt->continuing && !stmt->continuing->Empty()) {
-      if (!EmitBlock(stmt->continuing)) {
-        return false;
-      }
-    }
-    return true;
-  };
-
-  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-  line() << "while (true) {";
-  {
-    ScopedIndent si(this);
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-    if (!emit_continuing_()) {
-      return false;
-    }
-  }
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
-  // Nest a for loop with a new block. In HLSL the initializer scope is not
-  // nested by the for-loop, so we may get variable redefinitions.
-  line() << "{";
-  increment_indent();
-  TINT_DEFER({
-    decrement_indent();
-    line() << "}";
-  });
-
-  TextBuffer init_buf;
-  if (auto* init = stmt->initializer) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
-    if (!EmitStatement(init)) {
-      return false;
-    }
-  }
-
-  TextBuffer cond_pre;
-  std::stringstream cond_buf;
-  if (auto* cond = stmt->condition) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
-    if (!EmitExpression(cond_buf, cond)) {
-      return false;
-    }
-  }
-
-  TextBuffer cont_buf;
-  if (auto* cont = stmt->continuing) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
-    if (!EmitStatement(cont)) {
-      return false;
-    }
-  }
-
-  // If the for-loop has a multi-statement conditional and / or continuing, then
-  // we cannot emit this as a regular for-loop in HLSL. Instead we need to
-  // generate a `while(true)` loop.
-  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
-
-  // If the for-loop has multi-statement initializer, or is going to be emitted
-  // as a `while(true)` loop, then declare the initializer statement(s) before
-  // the loop.
-  if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) {
-    current_buffer_->Append(init_buf);
-    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
-  }
-
-  if (emit_as_loop) {
-    auto emit_continuing = [&]() {
-      current_buffer_->Append(cont_buf);
-      return true;
-    };
-
-    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-    line() << "while (true) {";
-    increment_indent();
-    TINT_DEFER({
-      decrement_indent();
-      line() << "}";
-    });
-
-    if (stmt->condition) {
-      current_buffer_->Append(cond_pre);
-      line() << "if (!(" << cond_buf.str() << ")) { break; }";
-    }
-
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-
-    if (!emit_continuing_()) {
-      return false;
-    }
-  } else {
-    // For-loop can be generated.
-    {
-      auto out = line();
-      out << "for";
-      {
-        ScopedParen sp(out);
-
-        if (!init_buf.lines.empty()) {
-          out << init_buf.lines[0].content << " ";
-        } else {
-          out << "; ";
-        }
-
-        out << cond_buf.str() << "; ";
-
-        if (!cont_buf.lines.empty()) {
-          out << TrimSuffix(cont_buf.lines[0].content, ";");
-        }
-      }
-      out << " {";
-    }
-    {
-      auto emit_continuing = [] { return true; };
-      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-      if (!EmitStatementsWithIndent(stmt->body->statements)) {
-        return false;
-      }
-    }
-    line() << "}";
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitMemberAccessor(
-    std::ostream& out,
-    const ast::MemberAccessorExpression* expr) {
-  if (!EmitExpression(out, expr->structure)) {
-    return false;
-  }
-  out << ".";
-
-  // Swizzles output the name directly
-  if (builder_.Sem().Get(expr)->Is<sem::Swizzle>()) {
-    out << builder_.Symbols().NameFor(expr->member->symbol);
-  } else if (!EmitExpression(out, expr->member)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
-  if (stmt->value) {
-    auto out = line();
-    out << "return ";
-    if (!EmitExpression(out, stmt->value)) {
-      return false;
-    }
-    out << ";";
-  } else {
-    line() << "return;";
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
-  if (auto* a = stmt->As<ast::AssignmentStatement>()) {
-    return EmitAssign(a);
-  }
-  if (auto* b = stmt->As<ast::BlockStatement>()) {
-    return EmitBlock(b);
-  }
-  if (auto* b = stmt->As<ast::BreakStatement>()) {
-    return EmitBreak(b);
-  }
-  if (auto* c = stmt->As<ast::CallStatement>()) {
-    auto out = line();
-    if (!EmitCall(out, c->expr)) {
-      return false;
-    }
-    out << ";";
-    return true;
-  }
-  if (auto* c = stmt->As<ast::ContinueStatement>()) {
-    return EmitContinue(c);
-  }
-  if (auto* d = stmt->As<ast::DiscardStatement>()) {
-    return EmitDiscard(d);
-  }
-  if (stmt->As<ast::FallthroughStatement>()) {
-    line() << "/* fallthrough */";
-    return true;
-  }
-  if (auto* i = stmt->As<ast::IfStatement>()) {
-    return EmitIf(i);
-  }
-  if (auto* l = stmt->As<ast::LoopStatement>()) {
-    return EmitLoop(l);
-  }
-  if (auto* l = stmt->As<ast::ForLoopStatement>()) {
-    return EmitForLoop(l);
-  }
-  if (auto* r = stmt->As<ast::ReturnStatement>()) {
-    return EmitReturn(r);
-  }
-  if (auto* s = stmt->As<ast::SwitchStatement>()) {
-    return EmitSwitch(s);
-  }
-  if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
-    return EmitVariable(v->variable);
-  }
-
-  diagnostics_.add_error(
-      diag::System::Writer,
-      "unknown statement type: " + std::string(stmt->TypeInfo().name));
-  return false;
-}
-
-bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
-  {  // switch(expr) {
-    auto out = line();
-    out << "switch(";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-    for (auto* s : stmt->body) {
-      if (!EmitCase(s)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
-                             ast::StorageClass storage_class,
-                             ast::Access access,
-                             const std::string& name,
-                             bool* name_printed /* = nullptr */) {
-  if (name_printed) {
-    *name_printed = false;
-  }
-  switch (storage_class) {
-    case ast::StorageClass::kInput: {
-      out << "in ";
-      break;
-    }
-    case ast::StorageClass::kOutput: {
-      out << "out ";
-      break;
-    }
-    case ast::StorageClass::kUniform: {
-      out << "uniform ";
-      break;
-    }
-    default:
-      break;
-  }
-
-  if (auto* ary = type->As<sem::Array>()) {
-    const sem::Type* base_type = ary;
-    std::vector<uint32_t> sizes;
-    while (auto* arr = base_type->As<sem::Array>()) {
-      sizes.push_back(arr->Count());
-      base_type = arr->ElemType();
-    }
-    if (!EmitType(out, base_type, storage_class, access, "")) {
-      return false;
-    }
-    if (!name.empty()) {
-      out << " " << name;
-      if (name_printed) {
-        *name_printed = true;
-      }
-    }
-    for (uint32_t size : sizes) {
-      if (size > 0) {
-        out << "[" << size << "]";
-      } else {
-        out << "[]";
-      }
-    }
-  } else if (type->Is<sem::Bool>()) {
-    out << "bool";
-  } else if (type->Is<sem::F32>()) {
-    out << "float";
-  } else if (type->Is<sem::I32>()) {
-    out << "int";
-  } else if (auto* mat = type->As<sem::Matrix>()) {
-    TINT_ASSERT(Writer, mat->type()->Is<sem::F32>());
-    out << "mat" << mat->columns();
-    if (mat->rows() != mat->columns()) {
-      out << "x" << mat->rows();
-    }
-  } else if (type->Is<sem::Pointer>()) {
-    TINT_ICE(Writer, diagnostics_)
-        << "Attempting to emit pointer type. These should have been removed "
-           "with the InlinePointerLets transform";
-    return false;
-  } else if (type->Is<sem::Sampler>()) {
-    return false;
-  } else if (auto* str = type->As<sem::Struct>()) {
-    out << StructName(str);
-  } else if (auto* tex = type->As<sem::Texture>()) {
-    auto* storage = tex->As<sem::StorageTexture>();
-    auto* ms = tex->As<sem::MultisampledTexture>();
-    auto* depth_ms = tex->As<sem::DepthMultisampledTexture>();
-    auto* sampled = tex->As<sem::SampledTexture>();
-
-    out << "uniform highp ";
-
-    if (storage && storage->access() != ast::Access::kRead) {
-      out << "writeonly ";
-    }
-    auto* subtype = sampled
-                        ? sampled->type()
-                        : storage ? storage->type() : ms ? ms->type() : nullptr;
-    if (!subtype || subtype->Is<sem::F32>()) {
-    } else if (subtype->Is<sem::I32>()) {
-      out << "i";
-    } else if (subtype->Is<sem::U32>()) {
-      out << "u";
-    } else {
-      TINT_ICE(Writer, diagnostics_) << "Unsupported texture type";
-      return false;
-    }
-
-    out << (storage ? "image" : "sampler");
-
-    switch (tex->dim()) {
-      case ast::TextureDimension::k1d:
-        out << "1D";
-        break;
-      case ast::TextureDimension::k2d:
-        out << ((ms || depth_ms) ? "2DMS" : "2D");
-        break;
-      case ast::TextureDimension::k2dArray:
-        out << ((ms || depth_ms) ? "2DMSArray" : "2DArray");
-        break;
-      case ast::TextureDimension::k3d:
-        out << "3D";
-        break;
-      case ast::TextureDimension::kCube:
-        out << "Cube";
-        break;
-      case ast::TextureDimension::kCubeArray:
-        out << "CubeArray";
-        break;
-      default:
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "unexpected TextureDimension " << tex->dim();
-        return false;
-    }
-    if (tex->Is<sem::DepthTexture>()) {
-      out << "Shadow";
-    }
-  } else if (type->Is<sem::U32>()) {
-    out << "uint";
-  } else if (auto* vec = type->As<sem::Vector>()) {
-    auto width = vec->Width();
-    if (vec->type()->Is<sem::F32>() && width >= 1 && width <= 4) {
-      out << "vec" << width;
-    } else if (vec->type()->Is<sem::I32>() && width >= 1 && width <= 4) {
-      out << "ivec" << width;
-    } else if (vec->type()->Is<sem::U32>() && width >= 1 && width <= 4) {
-      out << "uvec" << width;
-    } else if (vec->type()->Is<sem::Bool>() && width >= 1 && width <= 4) {
-      out << "bvec" << width;
-    } else {
-      out << "vector<";
-      if (!EmitType(out, vec->type(), storage_class, access, "")) {
-        return false;
-      }
-      out << ", " << width << ">";
-    }
-  } else if (auto* atomic = type->As<sem::Atomic>()) {
-    if (!EmitType(out, atomic->Type(), storage_class, access, name)) {
-      return false;
-    }
-  } else if (type->Is<sem::Void>()) {
-    out << "void";
-  } else {
-    diagnostics_.add_error(diag::System::Writer, "unknown type in EmitType");
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
-                                    ast::StorageClass storage_class,
-                                    ast::Access access,
-                                    const std::string& name) {
-  bool printed_name = false;
-  if (!EmitType(out, type, storage_class, access, name, &printed_name)) {
-    return false;
-  }
-  if (!name.empty() && !printed_name) {
-    out << " " << name;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
-  auto storage_class_uses = str->StorageClassUsage();
-  line(b) << "struct " << StructName(str) << " {";
-  EmitStructMembers(b, str, false);
-  line(b) << "};";
-  line(b);
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStructMembers(TextBuffer* b,
-                                      const sem::Struct* str,
-                                      bool emit_offsets) {
-  ScopedIndent si(b);
-  for (auto* mem : str->Members()) {
-    auto name = builder_.Symbols().NameFor(mem->Name());
-
-    auto* ty = mem->Type();
-
-    auto out = line(b);
-
-    // Note: offsets are unsupported on GLSL ES.
-    if (emit_offsets && version_.IsDesktop() && mem->Offset() != 0) {
-      out << "layout(offset=" << mem->Offset() << ") ";
-    }
-    if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
-                         ast::Access::kReadWrite, name)) {
-      return false;
-    }
-    out << ";";
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
-                                const ast::UnaryOpExpression* expr) {
-  switch (expr->op) {
-    case ast::UnaryOp::kIndirection:
-    case ast::UnaryOp::kAddressOf:
-      return EmitExpression(out, expr->expr);
-    case ast::UnaryOp::kComplement:
-      out << "~";
-      break;
-    case ast::UnaryOp::kNot:
-      out << "!";
-      break;
-    case ast::UnaryOp::kNegation:
-      out << "-";
-      break;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitVariable(const ast::Variable* var) {
-  auto* sem = builder_.Sem().Get(var);
-  auto* type = sem->Type()->UnwrapRef();
-
-  // TODO(dsinclair): Handle variable attributes
-  if (!var->attributes.empty()) {
-    diagnostics_.add_error(diag::System::Writer,
-                           "Variable attributes are not handled yet");
-    return false;
-  }
-
-  auto out = line();
-  // TODO(senorblanco): handle const
-  if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                       builder_.Symbols().NameFor(var->symbol))) {
-    return false;
-  }
-
-  out << " = ";
-
-  if (var->constructor) {
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-  } else {
-    if (!EmitZeroValue(out, type)) {
-      return false;
-    }
-  }
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
-  for (auto* d : var->attributes) {
-    if (!d->Is<ast::IdAttribute>()) {
-      diagnostics_.add_error(diag::System::Writer,
-                             "Decorated const values not valid");
-      return false;
-    }
-  }
-  if (!var->is_const) {
-    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
-    return false;
-  }
-
-  auto* sem = builder_.Sem().Get(var);
-  auto* type = sem->Type();
-
-  auto* global = sem->As<sem::GlobalVariable>();
-  if (global && global->IsOverridable()) {
-    auto const_id = global->ConstantId();
-
-    line() << "#ifndef " << kSpecConstantPrefix << const_id;
-
-    if (var->constructor != nullptr) {
-      auto out = line();
-      out << "#define " << kSpecConstantPrefix << const_id << " ";
-      if (!EmitExpression(out, var->constructor)) {
-        return false;
-      }
-    } else {
-      line() << "#error spec constant required for constant id " << const_id;
-    }
-    line() << "#endif";
-    {
-      auto out = line();
-      out << "const ";
-      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                           builder_.Symbols().NameFor(var->symbol))) {
-        return false;
-      }
-      out << " = " << kSpecConstantPrefix << const_id << ";";
-    }
-  } else {
-    auto out = line();
-    out << "const ";
-    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                         builder_.Symbols().NameFor(var->symbol))) {
-      return false;
-    }
-    out << " = ";
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-    out << ";";
-  }
-
-  return true;
-}
-
-template <typename F>
-bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
-                                      const ast::CallExpression* call,
-                                      const sem::Builtin* builtin,
-                                      F&& build) {
-  // Generate the helper function if it hasn't been created already
-  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
-    TextBuffer b;
-    TINT_DEFER(helpers_.Append(b));
-
-    auto fn_name =
-        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
-    std::vector<std::string> parameter_names;
-    {
-      auto decl = line(&b);
-      if (!EmitTypeAndName(decl, builtin->ReturnType(),
-                           ast::StorageClass::kNone, ast::Access::kUndefined,
-                           fn_name)) {
-        return "";
-      }
-      {
-        ScopedParen sp(decl);
-        for (auto* param : builtin->Parameters()) {
-          if (!parameter_names.empty()) {
-            decl << ", ";
-          }
-          auto param_name = "param_" + std::to_string(parameter_names.size());
-          const auto* ty = param->Type();
-          if (auto* ptr = ty->As<sem::Pointer>()) {
-            decl << "inout ";
-            ty = ptr->StoreType();
-          }
-          if (!EmitTypeAndName(decl, ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, param_name)) {
-            return "";
-          }
-          parameter_names.emplace_back(std::move(param_name));
-        }
-      }
-      decl << " {";
-    }
-    {
-      ScopedIndent si(&b);
-      if (!build(&b, parameter_names)) {
-        return "";
-      }
-    }
-    line(&b) << "}";
-    line(&b);
-    return fn_name;
-  });
-
-  if (fn.empty()) {
-    return false;
-  }
-
-  // Call the helper
-  out << fn;
-  {
-    ScopedParen sp(out);
-    bool first = true;
-    for (auto* arg : call->args) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-      if (!EmitExpression(out, arg)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-sem::Type* GeneratorImpl::BoolTypeToUint(const sem::Type* type) {
-  auto* u32 = builder_.create<sem::U32>();
-  if (type->Is<sem::Bool>()) {
-    return u32;
-  } else if (auto* vec = type->As<sem::Vector>()) {
-    return builder_.create<sem::Vector>(u32, vec->Width());
-  } else {
-    return nullptr;
-  }
-}
-
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl.h b/src/writer/glsl/generator_impl.h
deleted file mode 100644
index c03d1b7..0000000
--- a/src/writer/glsl/generator_impl.h
+++ /dev/null
@@ -1,500 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_WRITER_GLSL_GENERATOR_IMPL_H_
-#define SRC_WRITER_GLSL_GENERATOR_IMPL_H_
-
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/program_builder.h"
-#include "src/scope_stack.h"
-#include "src/transform/decompose_memory_access.h"
-#include "src/utils/hash.h"
-#include "src/writer/glsl/version.h"
-#include "src/writer/text_generator.h"
-
-namespace tint {
-
-// Forward declarations
-namespace sem {
-class Call;
-class Builtin;
-class TypeConstructor;
-class TypeConversion;
-}  // namespace sem
-
-namespace writer {
-namespace glsl {
-
-/// Implementation class for GLSL generator
-class GeneratorImpl : public TextGenerator {
- public:
-  /// Constructor
-  /// @param program the program to generate
-  /// @param version the GLSL version to use
-  GeneratorImpl(const Program* program, const Version& version);
-  ~GeneratorImpl();
-
-  /// @returns true on successful generation; false otherwise
-  bool Generate();
-
-  /// Handles an index accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the index accessor was emitted
-  bool EmitIndexAccessor(std::ostream& out,
-                         const ast::IndexAccessorExpression* expr);
-  /// Handles an assignment statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitAssign(const ast::AssignmentStatement* stmt);
-  /// Handles emission of bitwise operators (&|) on bool scalars and vectors
-  /// @param out the output of the expression stream
-  /// @param expr the binary expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitBitwiseBoolOp(std::ostream& out, const ast::BinaryExpression* expr);
-  /// Handles generating a binary expression
-  /// @param out the output of the expression stream
-  /// @param expr the binary expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
-  /// Handles generating a bitcast expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the binary expression was emitted
-  bool EmitVectorRelational(std::ostream& out,
-                            const ast::BinaryExpression* expr);
-  /// Handles generating a vector relational expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the vector relational expression was emitted
-  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
-  /// Emits a list of statements
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatements(const ast::StatementList& stmts);
-  /// Emits a list of statements with an indentation
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
-  /// Handles a block statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBlock(const ast::BlockStatement* stmt);
-  /// Handles a break statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBreak(const ast::BreakStatement* stmt);
-  /// Handles generating a call expression
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a function call expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @returns true if the expression is emitted
-  bool EmitFunctionCall(std::ostream& out, const sem::Call* call);
-  /// Handles generating a builtin call expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the builtin being called
-  /// @returns true if the expression is emitted
-  bool EmitBuiltinCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a type conversion expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param conv the type conversion
-  /// @returns true if the expression is emitted
-  bool EmitTypeConversion(std::ostream& out,
-                          const sem::Call* call,
-                          const sem::TypeConversion* conv);
-  /// Handles generating a type constructor expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param ctor the type constructor
-  /// @returns true if the expression is emitted
-  bool EmitTypeConstructor(std::ostream& out,
-                           const sem::Call* call,
-                           const sem::TypeConstructor* ctor);
-  /// Handles generating a barrier builtin call
-  /// @param out the output of the expression stream
-  /// @param builtin the semantic information for the barrier builtin
-  /// @returns true if the call expression is emitted
-  bool EmitBarrierCall(std::ostream& out, const sem::Builtin* builtin);
-  /// Handles generating an atomic intrinsic call for a storage buffer variable
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param intrinsic the atomic intrinsic
-  /// @returns true if the call expression is emitted
-  bool EmitStorageAtomicCall(
-      std::ostream& out,
-      const ast::CallExpression* expr,
-      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
-  /// Handles generating an atomic builtin call for a workgroup variable
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the atomic builtin
-  /// @returns true if the call expression is emitted
-  bool EmitWorkgroupAtomicCall(std::ostream& out,
-                               const ast::CallExpression* expr,
-                               const sem::Builtin* builtin);
-  /// Handles generating an array.length() call
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the array length expression is emitted
-  bool EmitArrayLength(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a call to a texture function (`textureSample`,
-  /// `textureSampleGrad`, etc)
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitTextureCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `select()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitSelectCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a call to the `dot()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDotCall(std::ostream& out,
-                   const ast::CallExpression* expr,
-                   const sem::Builtin* builtin);
-  /// Handles generating a call to the `modf()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitModfCall(std::ostream& out,
-                    const ast::CallExpression* expr,
-                    const sem::Builtin* builtin);
-  /// Handles generating a call to the `frexp()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitFrexpCall(std::ostream& out,
-                     const ast::CallExpression* expr,
-                     const sem::Builtin* builtin);
-  /// Handles generating a call to the `isNormal()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitIsNormalCall(std::ostream& out,
-                        const ast::CallExpression* expr,
-                        const sem::Builtin* builtin);
-  /// Handles generating a call to the `degrees()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDegreesCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `radians()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitRadiansCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to data packing builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDataPackingCall(std::ostream& out,
-                           const ast::CallExpression* expr,
-                           const sem::Builtin* builtin);
-  /// Handles generating a call to data unpacking builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDataUnpackingCall(std::ostream& out,
-                             const ast::CallExpression* expr,
-                             const sem::Builtin* builtin);
-  /// Handles a case statement
-  /// @param stmt the statement
-  /// @returns true if the statement was emitted successfully
-  bool EmitCase(const ast::CaseStatement* stmt);
-  /// Handles generating a discard statement
-  /// @param stmt the discard statement
-  /// @returns true if the statement was successfully emitted
-  bool EmitDiscard(const ast::DiscardStatement* stmt);
-  /// Handles a continue statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitContinue(const ast::ContinueStatement* stmt);
-  /// Handles generate an Expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the expression was emitted
-  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
-  /// Handles generating a function
-  /// @param func the function to generate
-  /// @returns true if the function was emitted
-  bool EmitFunction(const ast::Function* func);
-
-  /// Handles emitting a global variable
-  /// @param global the global variable
-  /// @returns true on success
-  bool EmitGlobalVariable(const ast::Variable* global);
-
-  /// Handles emitting a global variable with the uniform storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitUniformVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the storage storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitStorageVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the handle storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitHandleVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the private storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitPrivateVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the workgroup storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitWorkgroupVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the input or output storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitIOVariable(const sem::Variable* var);
-
-  /// Handles emitting interpolation qualifiers
-  /// @param out the output of the expression stream
-  /// @param attrs the attributes
-  void EmitInterpolationQualifiers(std::ostream& out,
-                                   const ast::AttributeList& attrs);
-  /// Handles emitting attributes
-  /// @param out the output of the expression stream
-  /// @param attrs the attributes
-  /// @returns true if the attributes were emitted
-  bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
-  /// Handles emitting the entry point function
-  /// @param func the entry point
-  /// @returns true if the entry point function was emitted
-  bool EmitEntryPointFunction(const ast::Function* func);
-  /// Handles an if statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitIf(const ast::IfStatement* stmt);
-  /// Handles a literal
-  /// @param out the output stream
-  /// @param lit the literal to emit
-  /// @returns true if the literal was successfully emitted
-  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
-  /// Handles a loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitLoop(const ast::LoopStatement* stmt);
-  /// Handles a for loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitForLoop(const ast::ForLoopStatement* stmt);
-  /// Handles generating an identifier expression
-  /// @param out the output of the expression stream
-  /// @param expr the identifier expression
-  /// @returns true if the identifier was emitted
-  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
-  /// Handles a member accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the member accessor expression
-  /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(std::ostream& out,
-                          const ast::MemberAccessorExpression* expr);
-  /// Handles return statements
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitReturn(const ast::ReturnStatement* stmt);
-  /// Handles statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitStatement(const ast::Statement* stmt);
-  /// Handles generating a switch statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitSwitch(const ast::SwitchStatement* stmt);
-  /// Handles generating type
-  /// @param out the output stream
-  /// @param type the type to generate
-  /// @param storage_class the storage class of the variable
-  /// @param access the access control type of the variable
-  /// @param name the name of the variable, used for array emission.
-  /// @param name_printed (optional) if not nullptr and an array was printed
-  /// then the boolean is set to true.
-  /// @returns true if the type is emitted
-  bool EmitType(std::ostream& out,
-                const sem::Type* type,
-                ast::StorageClass storage_class,
-                ast::Access access,
-                const std::string& name,
-                bool* name_printed = nullptr);
-  /// Handles generating type and name
-  /// @param out the output stream
-  /// @param type the type to generate
-  /// @param storage_class the storage class of the variable
-  /// @param access the access control type of the variable
-  /// @param name the name to emit
-  /// @returns true if the type is emitted
-  bool EmitTypeAndName(std::ostream& out,
-                       const sem::Type* type,
-                       ast::StorageClass storage_class,
-                       ast::Access access,
-                       const std::string& name);
-  /// Handles generating a structure declaration
-  /// @param buffer the text buffer that the type declaration will be written to
-  /// @param ty the struct to generate
-  /// @returns true if the struct is emitted
-  bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty);
-  /// Handles generating the members of a structure
-  /// @param buffer the text buffer that the struct members will be written to
-  /// @param ty the struct to generate
-  /// @param emit_offsets whether offsets should be emitted as offset=
-  /// @returns true if the struct members are emitted
-  bool EmitStructMembers(TextBuffer* buffer,
-                         const sem::Struct* ty,
-                         bool emit_offsets);
-  /// Handles a unary op expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the expression was emitted
-  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
-  /// Emits the zero value for the given type
-  /// @param out the output stream
-  /// @param type the type to emit the value for
-  /// @returns true if the zero value was successfully emitted.
-  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
-  /// Handles generating a variable
-  /// @param var the variable to generate
-  /// @returns true if the variable was emitted
-  bool EmitVariable(const ast::Variable* var);
-  /// Handles generating a program scope constant variable
-  /// @param var the variable to emit
-  /// @returns true if the variable was emitted
-  bool EmitProgramConstVariable(const ast::Variable* var);
-  /// Handles generating a builtin method name
-  /// @param builtin the semantic info for the builtin
-  /// @returns the name or "" if not valid
-  std::string generate_builtin_name(const sem::Builtin* builtin);
-  /// Converts a builtin to a gl_ string
-  /// @param builtin the builtin to convert
-  /// @param stage pipeline stage in which this builtin is used
-  /// @returns the string name of the builtin or blank on error
-  const char* builtin_to_string(ast::Builtin builtin, ast::PipelineStage stage);
-  /// Converts a builtin to a sem::Type appropriate for GLSL.
-  /// @param builtin the builtin to convert
-  /// @returns the appropriate semantic type or null on error.
-  sem::Type* builtin_type(ast::Builtin builtin);
-
- private:
-  enum class VarType { kIn, kOut };
-
-  struct EntryPointData {
-    std::string struct_name;
-    std::string var_name;
-  };
-
-  struct DMAIntrinsic {
-    transform::DecomposeMemoryAccess::Intrinsic::Op op;
-    transform::DecomposeMemoryAccess::Intrinsic::DataType type;
-    bool operator==(const DMAIntrinsic& rhs) const {
-      return op == rhs.op && type == rhs.type;
-    }
-    /// Hasher is a std::hash function for DMAIntrinsic
-    struct Hasher {
-      /// @param i the DMAIntrinsic to hash
-      /// @returns the hash of `i`
-      inline std::size_t operator()(const DMAIntrinsic& i) const {
-        return utils::Hash(i.op, i.type);
-      }
-    };
-  };
-
-  /// CallBuiltinHelper will call the builtin helper function, creating it
-  /// if it hasn't been built already. If the builtin needs to be built then
-  /// CallBuiltinHelper will generate the function signature and will call
-  /// `build` to emit the body of the function.
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @param build a function with the signature:
-  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
-  ///        Where:
-  ///          `buffer` is the body of the generated function
-  ///          `params` is the name of all the generated function parameters
-  /// @returns true if the call expression is emitted
-  template <typename F>
-  bool CallBuiltinHelper(std::ostream& out,
-                         const ast::CallExpression* call,
-                         const sem::Builtin* builtin,
-                         F&& build);
-
-  /// Create a uint type corresponding to the given bool or bool vector type.
-  /// @param type the bool or bool vector type to convert
-  /// @returns the corresponding uint type
-  sem::Type* BoolTypeToUint(const sem::Type* type);
-
-  TextBuffer helpers_;  // Helper functions emitted at the top of the output
-  std::function<bool()> emit_continuing_;
-  std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher>
-      dma_intrinsics_;
-  std::unordered_map<const sem::Builtin*, std::string> builtins_;
-  std::unordered_map<const sem::Struct*, std::string> structure_builders_;
-  std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
-  std::unordered_map<const sem::Vector*, std::string> int_dot_funcs_;
-  bool requires_oes_sample_variables_ = false;
-  bool requires_default_precision_qualifier_ = false;
-  Version version_;
-};
-
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_GLSL_GENERATOR_IMPL_H_
diff --git a/src/writer/glsl/generator_impl_array_accessor_test.cc b/src/writer/glsl/generator_impl_array_accessor_test.cc
deleted file mode 100644
index 7460f76..0000000
--- a/src/writer/glsl/generator_impl_array_accessor_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Expression = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Expression, IndexAccessor) {
-  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
-  auto* expr = IndexAccessor("ary", 5);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "ary[5]");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_assign_test.cc b/src/writer/glsl/generator_impl_assign_test.cc
deleted file mode 100644
index db9e19a..0000000
--- a/src/writer/glsl/generator_impl_assign_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Assign = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Assign, Emit_Assign) {
-  Global("lhs", ty.i32(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.i32(), ast::StorageClass::kPrivate);
-  auto* assign = Assign("lhs", "rhs");
-  WrapInFunction(assign);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
-  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_binary_test.cc b/src/writer/glsl/generator_impl_binary_test.cc
deleted file mode 100644
index 102873f..0000000
--- a/src/writer/glsl/generator_impl_binary_test.cc
+++ /dev/null
@@ -1,515 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/call_statement.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Binary = TestHelper;
-
-struct BinaryData {
-  const char* result;
-  ast::BinaryOp op;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << data.op;
-  return out;
-}
-
-using GlslBinaryTest = TestParamHelper<BinaryData>;
-TEST_P(GlslBinaryTest, Emit_f32) {
-  auto params = GetParam();
-
-  // Skip ops that are illegal for this type
-  if (params.op == ast::BinaryOp::kAnd || params.op == ast::BinaryOp::kOr ||
-      params.op == ast::BinaryOp::kXor ||
-      params.op == ast::BinaryOp::kShiftLeft ||
-      params.op == ast::BinaryOp::kShiftRight) {
-    return;
-  }
-
-  Global("left", ty.f32(), ast::StorageClass::kPrivate);
-  Global("right", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-TEST_P(GlslBinaryTest, Emit_u32) {
-  auto params = GetParam();
-
-  Global("left", ty.u32(), ast::StorageClass::kPrivate);
-  Global("right", ty.u32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-TEST_P(GlslBinaryTest, Emit_i32) {
-  auto params = GetParam();
-
-  // Skip ops that are illegal for this type
-  if (params.op == ast::BinaryOp::kShiftLeft ||
-      params.op == ast::BinaryOp::kShiftRight) {
-    return;
-  }
-
-  Global("left", ty.i32(), ast::StorageClass::kPrivate);
-  Global("right", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest,
-    GlslBinaryTest,
-    testing::Values(
-        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
-        BinaryData{"(left | right)", ast::BinaryOp::kOr},
-        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
-        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
-        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
-        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
-        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
-        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
-        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
-        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
-        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
-        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
-        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
-        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
-        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
-        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorScalar) {
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = Expr(1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            "(vec3(1.0f, 1.0f, 1.0f) * "
-            "1.0f)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarVector) {
-  auto* lhs = Expr(1.f);
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            "(1.0f * vec3(1.0f, 1.0f, "
-            "1.0f))");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixScalar) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr("mat");
-  auto* rhs = Expr(1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(mat * 1.0f)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr(1.f);
-  auto* rhs = Expr("mat");
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(1.0f * mat)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixVector) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr("mat");
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(mat * vec3(1.0f, 1.0f, 1.0f))");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = Expr("mat");
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(vec3(1.0f, 1.0f, 1.0f) * mat)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
-  Global("lhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                             Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(lhs * rhs)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Logical_And) {
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                             Expr("a"), Expr("b"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Logical_Multi) {
-  // (a && b) || (c || d)
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalOr,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
-                                    Expr("b")),
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"),
-                                    Expr("d")));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
-if (tint_tmp_1) {
-  tint_tmp_1 = b;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  bool tint_tmp_2 = c;
-  if (!tint_tmp_2) {
-    tint_tmp_2 = d;
-  }
-  tint_tmp = (tint_tmp_2);
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Logical_Or) {
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                             Expr("a"), Expr("b"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (!tint_tmp) {
-  tint_tmp = b;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, If_WithLogical) {
-  // if (a && b) {
-  //   return 1;
-  // } else if (b || c) {
-  //   return 2;
-  // } else {
-  //   return 3;
-  // }
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                Expr("a"), Expr("b")),
-                  Block(Return(1)),
-                  Else(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                     Expr("b"), Expr("c")),
-                       Block(Return(2))),
-                  Else(Block(Return(3))));
-  Func("func", {}, ty.i32(), {WrapInStatement(expr)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-if ((tint_tmp)) {
-  return 1;
-} else {
-  bool tint_tmp_1 = b;
-  if (!tint_tmp_1) {
-    tint_tmp_1 = c;
-  }
-  if ((tint_tmp_1)) {
-    return 2;
-  } else {
-    return 3;
-  }
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Return_WithLogical) {
-  // return (a && b) || c;
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = Return(create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalOr,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
-                                    Expr("b")),
-      Expr("c")));
-  Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
-if (tint_tmp_1) {
-  tint_tmp_1 = b;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  tint_tmp = c;
-}
-return (tint_tmp);
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Assign_WithLogical) {
-  // a = (b || c) && d;
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = Assign(
-      Expr("a"), create<ast::BinaryExpression>(
-                     ast::BinaryOp::kLogicalAnd,
-                     create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                   Expr("b"), Expr("c")),
-                     Expr("d")));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
-if (!tint_tmp_1) {
-  tint_tmp_1 = c;
-}
-bool tint_tmp = (tint_tmp_1);
-if (tint_tmp) {
-  tint_tmp = d;
-}
-a = (tint_tmp);
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Decl_WithLogical) {
-  // var a : bool = (b && c) || d;
-
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* var = Var("a", ty.bool_(), ast::StorageClass::kNone,
-                  create<ast::BinaryExpression>(
-                      ast::BinaryOp::kLogicalOr,
-                      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                    Expr("b"), Expr("c")),
-                      Expr("d")));
-
-  auto* decl = Decl(var);
-  WrapInFunction(decl);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
-if (tint_tmp_1) {
-  tint_tmp_1 = c;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  tint_tmp = d;
-}
-bool a = (tint_tmp);
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Binary, Call_WithLogical) {
-  // foo(a && b, c || d, (a || c) && (b || d))
-
-  Func("foo",
-       {
-           Param(Sym(), ty.bool_()),
-           Param(Sym(), ty.bool_()),
-           Param(Sym(), ty.bool_()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                 Expr("a"), Expr("b")));
-  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                 Expr("c"), Expr("d")));
-  params.push_back(create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalAnd,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"),
-                                    Expr("c")),
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"),
-                                    Expr("d"))));
-
-  auto* expr = CallStmt(Call("foo", params));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-bool tint_tmp_1 = c;
-if (!tint_tmp_1) {
-  tint_tmp_1 = d;
-}
-bool tint_tmp_3 = a;
-if (!tint_tmp_3) {
-  tint_tmp_3 = c;
-}
-bool tint_tmp_2 = (tint_tmp_3);
-if (tint_tmp_2) {
-  bool tint_tmp_4 = b;
-  if (!tint_tmp_4) {
-    tint_tmp_4 = d;
-  }
-  tint_tmp_2 = (tint_tmp_4);
-}
-foo((tint_tmp), (tint_tmp_1), (tint_tmp_2));
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_bitcast_test.cc b/src/writer/glsl/generator_impl_bitcast_test.cc
deleted file mode 100644
index 140d87e..0000000
--- a/src/writer/glsl/generator_impl_bitcast_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Bitcast = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Float) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "intBitsToFloat(1)");
-}
-
-TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.i32(), Expr(1u));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "int(1u)");
-}
-
-TEST_F(GlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "uint(1)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_block_test.cc b/src/writer/glsl/generator_impl_block_test.cc
deleted file mode 100644
index 191d9e8..0000000
--- a/src/writer/glsl/generator_impl_block_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Block = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Block, Emit_Block) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    discard;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_break_test.cc b/src/writer/glsl/generator_impl_break_test.cc
deleted file mode 100644
index 722e352..0000000
--- a/src/writer/glsl/generator_impl_break_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Break = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Break, Emit_Break) {
-  auto* b = create<ast::BreakStatement>();
-  WrapInFunction(Loop(Block(b)));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), "  break;\n");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_builtin_test.cc b/src/writer/glsl/generator_impl_builtin_test.cc
deleted file mode 100644
index eecff52..0000000
--- a/src/writer/glsl/generator_impl_builtin_test.cc
+++ /dev/null
@@ -1,838 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/sem/call.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using BuiltinType = sem::BuiltinType;
-
-using ::testing::HasSubstr;
-
-using GlslGeneratorImplTest_Builtin = TestHelper;
-
-enum class ParamType {
-  kF32,
-  kU32,
-  kBool,
-};
-
-struct BuiltinData {
-  BuiltinType builtin;
-  ParamType type;
-  const char* glsl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.glsl_name;
-  switch (data.type) {
-    case ParamType::kF32:
-      out << "f32";
-      break;
-    case ParamType::kU32:
-      out << "u32";
-      break;
-    case ParamType::kBool:
-      out << "bool";
-      break;
-  }
-  out << ">";
-  return out;
-}
-
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
-                                        ParamType type,
-                                        ProgramBuilder* builder) {
-  std::string name;
-  std::ostringstream str(name);
-  str << builtin;
-  switch (builtin) {
-    case BuiltinType::kAcos:
-    case BuiltinType::kAsin:
-    case BuiltinType::kAtan:
-    case BuiltinType::kCeil:
-    case BuiltinType::kCos:
-    case BuiltinType::kCosh:
-    case BuiltinType::kDpdx:
-    case BuiltinType::kDpdxCoarse:
-    case BuiltinType::kDpdxFine:
-    case BuiltinType::kDpdy:
-    case BuiltinType::kDpdyCoarse:
-    case BuiltinType::kDpdyFine:
-    case BuiltinType::kExp:
-    case BuiltinType::kExp2:
-    case BuiltinType::kFloor:
-    case BuiltinType::kFract:
-    case BuiltinType::kFwidth:
-    case BuiltinType::kFwidthCoarse:
-    case BuiltinType::kFwidthFine:
-    case BuiltinType::kInverseSqrt:
-    case BuiltinType::kIsFinite:
-    case BuiltinType::kIsInf:
-    case BuiltinType::kIsNan:
-    case BuiltinType::kIsNormal:
-    case BuiltinType::kLength:
-    case BuiltinType::kLog:
-    case BuiltinType::kLog2:
-    case BuiltinType::kNormalize:
-    case BuiltinType::kRound:
-    case BuiltinType::kSin:
-    case BuiltinType::kSinh:
-    case BuiltinType::kSqrt:
-    case BuiltinType::kTan:
-    case BuiltinType::kTanh:
-    case BuiltinType::kTrunc:
-    case BuiltinType::kSign:
-      return builder->Call(str.str(), "f2");
-    case BuiltinType::kLdexp:
-      return builder->Call(str.str(), "f2", "i2");
-    case BuiltinType::kAtan2:
-    case BuiltinType::kDot:
-    case BuiltinType::kDistance:
-    case BuiltinType::kPow:
-    case BuiltinType::kReflect:
-    case BuiltinType::kStep:
-      return builder->Call(str.str(), "f2", "f2");
-    case BuiltinType::kCross:
-      return builder->Call(str.str(), "f3", "f3");
-    case BuiltinType::kFma:
-    case BuiltinType::kMix:
-    case BuiltinType::kFaceForward:
-    case BuiltinType::kSmoothStep:
-      return builder->Call(str.str(), "f2", "f2", "f2");
-    case BuiltinType::kAll:
-    case BuiltinType::kAny:
-      return builder->Call(str.str(), "b2");
-    case BuiltinType::kAbs:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2");
-      } else {
-        return builder->Call(str.str(), "u2");
-      }
-    case BuiltinType::kCountOneBits:
-    case BuiltinType::kReverseBits:
-      return builder->Call(str.str(), "u2");
-    case BuiltinType::kMax:
-    case BuiltinType::kMin:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2");
-      }
-    case BuiltinType::kClamp:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2", "u2");
-      }
-    case BuiltinType::kSelect:
-      return builder->Call(str.str(), "f2", "f2", "b2");
-    case BuiltinType::kDeterminant:
-      return builder->Call(str.str(), "m2x2");
-    case BuiltinType::kTranspose:
-      return builder->Call(str.str(), "m3x2");
-    default:
-      break;
-  }
-  return nullptr;
-}
-using GlslBuiltinTest = TestParamHelper<BuiltinData>;
-TEST_P(GlslBuiltinTest, Emit) {
-  auto param = GetParam();
-
-  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
-  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
-  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
-  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = GenerateCall(param.builtin, param.type, this);
-  ASSERT_NE(nullptr, call) << "Unhandled builtin";
-  Func("func", {}, ty.void_(), {CallStmt(call)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem = program->Sem().Get(call);
-  ASSERT_NE(sem, nullptr);
-  auto* target = sem->Target();
-  ASSERT_NE(target, nullptr);
-  auto* builtin = target->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-
-  EXPECT_EQ(gen.generate_builtin_name(builtin), param.glsl_name);
-}
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest_Builtin,
-    GlslBuiltinTest,
-    testing::Values(
-        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "abs"},
-        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
-        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
-        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
-        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
-        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
-        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
-        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan"},
-        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
-        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
-        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
-        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "countbits"},
-        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
-        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
-        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
-        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
-        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "ddx"},
-        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "ddx_coarse"},
-        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "ddx_fine"},
-        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "ddy"},
-        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "ddy_coarse"},
-        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "ddy_fine"},
-        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
-        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
-        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
-        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
-        BuiltinData{BuiltinType::kFma, ParamType::kF32, "mad"},
-        BuiltinData{BuiltinType::kFract, ParamType::kF32, "frac"},
-        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
-        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
-        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
-        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
-        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
-        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
-        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
-        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
-        BuiltinData{BuiltinType::kMax, ParamType::kF32, "max"},
-        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
-        BuiltinData{BuiltinType::kMin, ParamType::kF32, "min"},
-        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
-        BuiltinData{BuiltinType::kMix, ParamType::kF32, "mix"},
-        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
-        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
-        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
-        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reversebits"},
-        BuiltinData{BuiltinType::kRound, ParamType::kU32, "round"},
-        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
-        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
-        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
-        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
-        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
-        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
-        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
-        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
-        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
-        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"}));
-
-TEST_F(GlslGeneratorImplTest_Builtin, DISABLED_Builtin_IsNormal) {
-  FAIL();
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Builtin_Call) {
-  auto* call = Call("dot", "param1", "param2");
-
-  Global("param1", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  Global("param2", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "dot(param1, param2)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Select_Scalar) {
-  auto* call = Call("select", 1.0f, 2.0f, true);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "(true ? 2.0f : 1.0f)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Select_Vector) {
-  auto* call =
-      Call("select", vec2<i32>(1, 2), vec2<i32>(3, 4), vec2<bool>(true, false));
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "mix(ivec2(1, 2), ivec2(3, 4), bvec2(true, false))");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Modf_Scalar) {
-  auto* call = Call("modf", 1.0f);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct modf_result {
-  float fract;
-  float whole;
-};
-
-modf_result tint_modf(float param_0) {
-  modf_result result;
-  result.fract = modf(param_0, result.whole);
-  return result;
-}
-
-
-void test_function() {
-  tint_modf(1.0f);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Modf_Vector) {
-  auto* call = Call("modf", vec3<f32>());
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct modf_result_vec3 {
-  vec3 fract;
-  vec3 whole;
-};
-
-modf_result_vec3 tint_modf(vec3 param_0) {
-  modf_result_vec3 result;
-  result.fract = modf(param_0, result.whole);
-  return result;
-}
-
-
-void test_function() {
-  tint_modf(vec3(0.0f, 0.0f, 0.0f));
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
-  auto* call = Call("frexp", 1.0f);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(
-  float sig;
-  int exp;
-};
-
-frexp_result tint_frexp(float param_0) {
-  frexp_result result;
-  result.sig = frexp(param_0, result.exp);
-  return result;
-}
-
-
-void test_function() {
-  tint_frexp(1.0f);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
-  auto* call = Call("frexp", vec3<f32>());
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(
-
-struct frexp_result_vec3 {
-  vec3 sig;
-  ivec3 exp;
-};
-
-frexp_result_vec3 tint_frexp(vec3 param_0) {
-  frexp_result_vec3 result;
-  result.sig = frexp(param_0, result.exp);
-  return result;
-}
-
-
-void test_function() {
-  tint_frexp(vec3(0.0f, 0.0f, 0.0f));
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, IsNormal_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("isNormal", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(ion 310 es
-
-bool tint_isNormal(float param_0) {
-  uint exponent = floatBitsToUint(param_0) & 0x7f80000u;
-  uint clamped = clamp(exponent, 0x0080000u, 0x7f00000u);
-  return clamped == exponent;
-}
-
-
-void test_function() {
-  float val = 0.0f;
-  bool tint_symbol = tint_isNormal(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, IsNormal_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("isNormal", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"( 310 es
-
-bvec3 tint_isNormal(vec3 param_0) {
-  uvec3 exponent = floatBitsToUint(param_0) & 0x7f80000u;
-  uvec3 clamped = clamp(exponent, 0x0080000u, 0x7f00000u);
-  return equal(clamped, exponent);
-}
-
-
-void test_function() {
-  vec3 val = vec3(0.0f, 0.0f, 0.0f);
-  bvec3 tint_symbol = tint_isNormal(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Degrees_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-float tint_degrees(float param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-
-void test_function() {
-  float val = 0.0f;
-  float tint_symbol = tint_degrees(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Degrees_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-vec3 tint_degrees(vec3 param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-
-void test_function() {
-  vec3 val = vec3(0.0f, 0.0f, 0.0f);
-  vec3 tint_symbol = tint_degrees(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Radians_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-float tint_radians(float param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-
-void test_function() {
-  float val = 0.0f;
-  float tint_symbol = tint_radians(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Radians_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-vec3 tint_radians(vec3 param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-
-void test_function() {
-  vec3 val = vec3(0.0f, 0.0f, 0.0f);
-  vec3 tint_symbol = tint_radians(val);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-#if 0
-TEST_F(GlslGeneratorImplTest_Builtin, Pack4x8Snorm) {
-  auto* call = Call("pack4x8snorm", "p1");
-  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("ivec4 tint_tmp = ivec4(round(clamp(p1, "
-                                      "-1.0, 1.0) * 127.0)) & 0xff;"));
-  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 8 | "
-                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Pack4x8Unorm) {
-  auto* call = Call("pack4x8unorm", "p1");
-  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uvec4 tint_tmp = uvec4(round(clamp(p1, "
-                                      "0.0, 1.0) * 255.0));"));
-  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 8 | "
-                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Snorm) {
-  auto* call = Call("pack2x16snorm", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int2 tint_tmp = int2(round(clamp(p1, "
-                                      "-1.0, 1.0) * 32767.0)) & 0xffff;"));
-  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 16)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Unorm) {
-  auto* call = Call("pack2x16unorm", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = uint2(round(clamp(p1, "
-                                      "0.0, 1.0) * 65535.0));"));
-  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Pack2x16Float) {
-  auto* call = Call("pack2x16float", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = f32tof16(p1);"));
-  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Unpack4x8Snorm) {
-  auto* call = Call("unpack4x8snorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
-  EXPECT_THAT(gen.result(),
-              HasSubstr("ivec4 tint_tmp = ivec4(tint_tmp_1 << 24, tint_tmp_1 "
-                        "<< 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;"));
-  EXPECT_THAT(out.str(),
-              HasSubstr("clamp(float4(tint_tmp) / 127.0, -1.0, 1.0)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Unpack4x8Unorm) {
-  auto* call = Call("unpack4x8unorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr("uvec4 tint_tmp = uvec4(tint_tmp_1 & 0xff, (tint_tmp_1 >> "
-                "8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);"));
-  EXPECT_THAT(out.str(), HasSubstr("float4(tint_tmp) / 255.0"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Snorm) {
-  auto* call = Call("unpack2x16snorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr("int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;"));
-  EXPECT_THAT(out.str(),
-              HasSubstr("clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Unorm) {
-  auto* call = Call("unpack2x16unorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
-  EXPECT_THAT(gen.result(),
-              HasSubstr("uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, "
-                        "tint_tmp_1 >> 16);"));
-  EXPECT_THAT(out.str(), HasSubstr("float2(tint_tmp) / 65535.0"));
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, Unpack2x16Float) {
-  auto* call = Call("unpack2x16float", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp = p1;"));
-  EXPECT_THAT(out.str(),
-              HasSubstr("f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16))"));
-}
-
-#endif
-
-TEST_F(GlslGeneratorImplTest_Builtin, StorageBarrier) {
-  Func("main", {}, ty.void_(),
-       {CallStmt(Call("storageBarrier"))},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  { barrier(); memoryBarrierBuffer(); };
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
-  Func("main", {}, ty.void_(),
-       {CallStmt(Call("workgroupBarrier"))},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  barrier();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, DotI32) {
-  Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(Call("dot", "v", "v")));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-int tint_int_dot(ivec3 a, ivec3 b) {
-  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
-}
-
-ivec3 v = ivec3(0, 0, 0);
-void test_function() {
-  tint_int_dot(v, v);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Builtin, DotU32) {
-  Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(Call("dot", "v", "v")));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-uint tint_int_dot(uvec3 a, uvec3 b) {
-  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
-}
-
-uvec3 v = uvec3(0u, 0u, 0u);
-void test_function() {
-  tint_int_dot(v, v);
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_builtin_texture_test.cc b/src/writer/glsl/generator_impl_builtin_texture_test.cc
deleted file mode 100644
index 180119c..0000000
--- a/src/writer/glsl/generator_impl_builtin_texture_test.cc
+++ /dev/null
@@ -1,304 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-struct ExpectedResult {
-  ExpectedResult(const char* o) : out(o) {}  // NOLINT
-
-  std::string pre;
-  std::string out;
-};
-
-ExpectedResult expected_texture_overload(
-    ast::builtin::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-    case ValidTextureOverload::kDimensions2d:
-    case ValidTextureOverload::kDimensionsDepth2d:
-    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-    case ValidTextureOverload::kDimensions2dArray:
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-    case ValidTextureOverload::kDimensions3d:
-    case ValidTextureOverload::kDimensionsCube:
-    case ValidTextureOverload::kDimensionsDepthCube:
-    case ValidTextureOverload::kDimensionsCubeArray:
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-    case ValidTextureOverload::kDimensions2dLevel:
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-    case ValidTextureOverload::kDimensions3dLevel:
-    case ValidTextureOverload::kDimensionsCubeLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return {"textureSize"};
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return {"imageSize"};
-    case ValidTextureOverload::kGather2dF32:
-      return R"(textureGather(tint_symbol_sampler, vec2(1.0f, 2.0f), 0))";
-    case ValidTextureOverload::kGather2dOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(3, 4), 0))";
-    case ValidTextureOverload::kGather2dArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0))";
-    case ValidTextureOverload::kGather2dArrayOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5), 0))";
-    case ValidTextureOverload::kGatherCubeF32:
-      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 0))";
-    case ValidTextureOverload::kGatherCubeArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0))";
-    case ValidTextureOverload::kGatherDepth2dF32:
-      return R"(textureGather(tint_symbol_sampler, vec2(1.0f, 2.0f), 0.0))";
-    case ValidTextureOverload::kGatherDepth2dOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 0.0, ivec2(3, 4))";
-    case ValidTextureOverload::kGatherDepth2dArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0.0))";
-    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 0.0, ivec2(4, 5)))";
-    case ValidTextureOverload::kGatherDepthCubeF32:
-      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 0.0))";
-    case ValidTextureOverload::kGatherDepthCubeArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0.0))";
-    case ValidTextureOverload::kGatherCompareDepth2dF32:
-      return R"(textureGather(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f))";
-    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5)))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 4.0f)))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
-      return R"(textureGatherOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 4.0f), ivec2(5, 6)))";
-    case ValidTextureOverload::kGatherCompareDepthCubeF32:
-      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f)))";
-    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
-      return R"(textureGather(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f))";
-    case ValidTextureOverload::kNumLayers2dArray:
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-    case ValidTextureOverload::kNumLayersCubeArray:
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-      return {"textureSize"};
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return {"imageSize"};
-    case ValidTextureOverload::kNumLevels2d:
-    case ValidTextureOverload::kNumLevelsCube:
-    case ValidTextureOverload::kNumLevelsDepth2d:
-    case ValidTextureOverload::kNumLevelsDepthCube:
-    case ValidTextureOverload::kNumLevels2dArray:
-    case ValidTextureOverload::kNumLevels3d:
-    case ValidTextureOverload::kNumLevelsCubeArray:
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return {"textureQueryLevels"};
-    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-      return {"textureSamples"};
-    case ValidTextureOverload::kSample1dF32:
-      return R"(texture(tint_symbol_sampler, 1.0f);)";
-    case ValidTextureOverload::kSample2dF32:
-      return R"(texture(tint_symbol_sampler, vec2(1.0f, 2.0f));)";
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(3, 4));)";
-    case ValidTextureOverload::kSample2dArrayF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)));)";
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(4, 5));)";
-    case ValidTextureOverload::kSample3dF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(4, 5, 6));)";
-    case ValidTextureOverload::kSampleCubeF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)));)";
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 0.0f));)";
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 0.0f), ivec2(3, 4));)";
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 0.0f));)";
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(3), 0.0f), ivec2(4, 5));)";
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f));)";
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 0.0f);)";
-    case ValidTextureOverload::kSampleBias2dF32:
-      return R"(texture(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), ivec2(4, 5), 3.0f);)";
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, float(4)), 3.0f);)";
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), ivec2(5, 6), 4.0f);)";
-    case ValidTextureOverload::kSampleBias3dF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(5, 6, 7), 4.0f);)";
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return R"(textureLod(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return R"(textureLodOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)";
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)";
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)";
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return R"(textureLod(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return R"(textureLod(tint_symbol_sampler, vec2(1.0f, 2.0f), 3).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return R"(textureLodOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3, ivec2(4, 5)).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return R"(textureLodOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), 4, ivec2(5, 6)).x;)";
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return R"(textureLod(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4).x;)";
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return R"(textureLod(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5).x;)";
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return R"(textureGrad(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)";
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return R"(textureGradOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7, 7));)";
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)";
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return R"(textureGradOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f), ivec2(6, 7));)";
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)";
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return R"(textureGradOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f), ivec3(0, 1, 2));)";
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)";
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return R"(textureGrad(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), vec3(5.0f, 6.0f, 7.0f), vec3(8.0f, 9.0f, 10.0f));)";
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f));)";
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4), 3.0f));)";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4), 3.0f), ivec2(5, 6));)";
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)";
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
-      return R"(yyytexture(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
-      return R"(yyytextureOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, float(4)), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
-      return R"(textureOffset(tint_symbol_sampler, vec3(1.0f, 2.0f, float(4)), 3.0f, ivec2(5, 6));)";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
-      return R"(texture(tint_symbol_sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
-      return R"(texture(tint_symbol_sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kLoad1dLevelF32:
-    case ValidTextureOverload::kLoad1dLevelU32:
-    case ValidTextureOverload::kLoad1dLevelI32:
-      return R"(texelFetch(tint_symbol_2, 1, 3);)";
-    case ValidTextureOverload::kLoad2dLevelF32:
-    case ValidTextureOverload::kLoad2dLevelU32:
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-    case ValidTextureOverload::kLoad3dLevelF32:
-    case ValidTextureOverload::kLoad3dLevelU32:
-    case ValidTextureOverload::kLoad3dLevelI32:
-      return R"(texelFetch(tint_symbol_2, ivec3(1, 2, 3), 4);)";
-    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-      return R"(texelFetch(tint_symbol_2, ivec2(1, 2), 3);)";
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return R"(texelFetch(tint_symbol_2, ivec3(1, 2, 3), 4);)";
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-      return R"(imageStore(tint_symbol, 1, vec4(2.0f, 3.0f, 4.0f, 5.0f));)";
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-      return R"(imageStore(tint_symbol, ivec2(1, 2), vec4(3.0f, 4.0f, 5.0f, 6.0f));)";
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return R"(imageStore(tint_symbol, ivec3(1, 2, 3), vec4(4.0f, 5.0f, 6.0f, 7.0f));)";
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return R"(imageStore(tint_symbol, ivec3(1, 2, 3), vec4(4.0f, 5.0f, 6.0f, 7.0f));)";
-  }
-  return "<unmatched texture overload>";
-}  // NOLINT - Ignore the length of this function
-
-class GlslGeneratorBuiltinTextureTest
-    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
-
-TEST_P(GlslGeneratorBuiltinTextureTest, Call) {
-  auto param = GetParam();
-
-  param.BuildTextureVariable(this);
-  param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  auto* stmt = CallStmt(call);
-
-  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto expected = expected_texture_overload(param.overload);
-
-  EXPECT_THAT(gen.result(), HasSubstr(expected.pre));
-  EXPECT_THAT(gen.result(), HasSubstr(expected.out));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorBuiltinTextureTest,
-    GlslGeneratorBuiltinTextureTest,
-    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_call_test.cc b/src/writer/glsl/generator_impl_call_test.cc
deleted file mode 100644
index 41740c2..0000000
--- a/src/writer/glsl/generator_impl_call_test.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/call_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Call = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
-  Func("my_func", {}, ty.f32(), {Return(1.23f)});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func()");
-}
-
-TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.f32(), {Return(1.23f)});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func(param1, param2)");
-}
-
-TEST_F(GlslGeneratorImplTest_Call, EmitStatement_Call) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = CallStmt(Call("my_func", "param1", "param2"));
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(call)) << gen.error();
-  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_case_test.cc b/src/writer/glsl/generator_impl_case_test.cc
deleted file mode 100644
index fbeb873..0000000
--- a/src/writer/glsl/generator_impl_case_test.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/fallthrough_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Case = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) {
-  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::FallthroughStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    /* fallthrough */
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
-  auto* s =
-      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
-             DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5:
-  case 6: {
-    break;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case_Default) {
-  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  default: {
-    break;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_cast_test.cc b/src/writer/glsl/generator_impl_cast_test.cc
deleted file mode 100644
index 207a4c3..0000000
--- a/src/writer/glsl/generator_impl_cast_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Cast = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) {
-  auto* cast = Construct<f32>(1);
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "float(1)");
-}
-
-TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
-  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "vec3(ivec3(1, 2, 3))");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_constructor_test.cc b/src/writer/glsl/generator_impl_constructor_test.cc
deleted file mode 100644
index f6f0cd2..0000000
--- a/src/writer/glsl/generator_impl_constructor_test.cc
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using GlslGeneratorImplTest_Constructor = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Bool) {
-  WrapInFunction(Expr(false));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("false"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
-  WrapInFunction(Expr(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
-  WrapInFunction(Expr(56779u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
-  // Use a number close to 1<<30 but whose decimal representation ends in 0.
-  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
-  WrapInFunction(Construct<f32>(-1.2e-5f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
-  WrapInFunction(Construct<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
-  WrapInFunction(Construct<i32>(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
-  WrapInFunction(Construct<u32>(12345u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
-  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("vec3(1.0f, 2.0f, 3.0f)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
-  WrapInFunction(vec3<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("vec3(0.0f, 0.0f, 0.0f)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Float) {
-  WrapInFunction(vec3<f32>(2.0f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("vec3(2.0f)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Bool) {
-  WrapInFunction(vec3<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bvec3(true)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Int) {
-  WrapInFunction(vec3<i32>(2));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("ivec3(2)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_UInt) {
-  WrapInFunction(vec3<u32>(2u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uvec3(2u)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
-  WrapInFunction(
-      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr("mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(3.0f, 4.0f, 5.0f))"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat_Empty) {
-  WrapInFunction(mat2x3<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  EXPECT_THAT(gen.result(),
-              HasSubstr("mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
-  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3),
-                           vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f),
-                           vec3<f32>(7.f, 8.f, 9.f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("vec3[3](vec3(1.0f, 2.0f, 3.0f), "
-                                      "vec3(4.0f, 5.0f, 6.0f), "
-                                      "vec3(7.0f, 8.0f, 9.0f))"));
-}
-
-// TODO(bclayton): Zero-init arrays
-TEST_F(GlslGeneratorImplTest_Constructor,
-       DISABLED_EmitConstructor_Type_Array_Empty) {
-  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("{vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f),"
-                        " vec3(0.0f, 0.0f, 0.0f)}"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("S(1, 2.0f, ivec3(3, 4, 5))"));
-}
-
-TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str)));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("S(0"));
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_continue_test.cc b/src/writer/glsl/generator_impl_continue_test.cc
deleted file mode 100644
index 68f01b0..0000000
--- a/src/writer/glsl/generator_impl_continue_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Continue = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Continue, Emit_Continue) {
-  auto* loop = Loop(Block(If(false, Block(Break())),  //
-                          Continue()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    if (false) {
-      break;
-    }
-    continue;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_discard_test.cc b/src/writer/glsl/generator_impl_discard_test.cc
deleted file mode 100644
index 551c24f..0000000
--- a/src/writer/glsl/generator_impl_discard_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Discard = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Discard, Emit_Discard) {
-  auto* stmt = create<ast::DiscardStatement>();
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  discard;\n");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_function_test.cc b/src/writer/glsl/generator_impl_function_test.cc
deleted file mode 100644
index 0b539eb..0000000
--- a/src/writer/glsl/generator_impl_function_test.cc
+++ /dev/null
@@ -1,1003 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/writer/glsl/test_helper.h"
-
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Function = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Function) {
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #version 310 es
-
-  void my_func() {
-    return;
-  }
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
-  Func("centroid", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(  void tint_symbol() {
-    return;
-  })"));
-}
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithParams) {
-  Func("my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
-       ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #version 310 es
-
-  void my_func(float a, int b) {
-    return;
-  }
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_NoReturn_Void) {
-  Func("func", ast::VariableList{}, ty.void_(), {/* no explicit return */},
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-void func() {
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function, PtrParameter) {
-  // fn f(foo : ptr<function, f32>) -> f32 {
-  //   return *foo;
-  // }
-  Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))},
-       ty.f32(), {Return(Deref("foo"))});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) {
-  return foo;
-}
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithInOutVars) {
-  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
-  //   return foo;
-  // }
-  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
-  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-layout(location = 0) in float foo_1;
-layout(location = 1) out float value;
-float frag_main(float foo) {
-  return foo;
-}
-
-void main() {
-  float inner_result = frag_main(foo_1);
-  value = inner_result;
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithInOut_Builtins) {
-  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
-  //   return coord.x;
-  // }
-  auto* coord_in =
-      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
-       {Return(MemberAccessor("coord", "x"))},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kFragDepth)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-float frag_main(vec4 coord) {
-  return coord.x;
-}
-
-void main() {
-  float inner_result = frag_main(gl_FragCoord);
-  gl_FragDepth = inner_result;
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
-  // struct Interface {
-  //   @builtin(position) pos : vec4<f32>;
-  //   @location(1) col1 : f32;
-  //   @location(2) col2 : f32;
-  // };
-  // fn vert_main() -> Interface {
-  //   return Interface(vec4<f32>(), 0.4, 0.6);
-  // }
-  // fn frag_main(inputs : Interface) {
-  //   const r = inputs.col1;
-  //   const g = inputs.col2;
-  //   const p = inputs.pos;
-  // }
-  auto* interface_struct = Structure(
-      "Interface",
-      {
-          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-          Member("col1", ty.f32(), {Location(1)}),
-          Member("col2", ty.f32(), {Location(2)}),
-      });
-
-  Func("vert_main", {}, ty.Of(interface_struct),
-       {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
-                         Expr(0.5f), Expr(0.25f)))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
-       {
-           Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))),
-           Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))),
-           Decl(Const("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-layout(location = 1) out float col1_1;
-layout(location = 2) out float col2_1;
-layout(location = 1) in float col1_2;
-layout(location = 2) in float col2_2;
-struct Interface {
-  vec4 pos;
-  float col1;
-  float col2;
-};
-
-Interface vert_main() {
-  Interface tint_symbol = Interface(vec4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f);
-  return tint_symbol;
-}
-
-void main() {
-  Interface inner_result = vert_main();
-  gl_Position = inner_result.pos;
-  col1_1 = inner_result.col1;
-  col2_1 = inner_result.col2;
-  gl_Position.y = -(gl_Position.y);
-  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
-  return;
-}
-void frag_main(Interface inputs) {
-  float r = inputs.col1;
-  float g = inputs.col2;
-  vec4 p = inputs.pos;
-}
-
-void main_1() {
-  Interface tint_symbol_1 = Interface(gl_FragCoord, col1_2, col2_2);
-  frag_main(tint_symbol_1);
-  return;
-}
-)");
-}
-
-#if 0
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
-  // struct VertexOutput {
-  //   @builtin(position) pos : vec4<f32>;
-  // };
-  // fn foo(x : f32) -> VertexOutput {
-  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
-  // }
-  // fn vert_main1() -> VertexOutput {
-  //   return foo(0.5);
-  // }
-  // fn vert_main2() -> VertexOutput {
-  //   return foo(0.25);
-  // }
-  auto* vertex_output_struct = Structure(
-      "VertexOutput",
-      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
-
-  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
-       {Return(Construct(ty.Of(vertex_output_struct),
-                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
-       {});
-
-  Func("vert_main1", {}, ty.Of(vertex_output_struct),
-       {Return(Construct(ty.Of(vertex_output_struct),
-                         Expr(Call("foo", Expr(0.5f)))))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("vert_main2", {}, ty.Of(vertex_output_struct),
-       {Return(Construct(ty.Of(vertex_output_struct),
-                         Expr(Call("foo", Expr(0.25f)))))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct VertexOutput {
-  float4 pos;
-};
-
-VertexOutput foo(float x) {
-  const VertexOutput tint_symbol_4 = {float4(x, x, x, 1.0f)};
-  return tint_symbol_4;
-}
-
-struct tint_symbol {
-  float4 pos : SV_Position;
-};
-
-tint_symbol vert_main1() {
-  const VertexOutput tint_symbol_1 = {foo(0.5f)};
-  const tint_symbol tint_symbol_5 = {tint_symbol_1.pos};
-  return tint_symbol_5;
-}
-
-struct tint_symbol_2 {
-  float4 pos : SV_Position;
-};
-
-tint_symbol_2 vert_main2() {
-  const VertexOutput tint_symbol_3 = {foo(0.25f)};
-  const tint_symbol_2 tint_symbol_6 = {tint_symbol_3.pos};
-  return tint_symbol_6;
-}
-)");
-}
-#endif
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
-  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
-                           {create<ast::StructBlockAttribute>()});
-  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(1),
-                     });
-
-  Func("sub_func",
-       {
-           Param("param", ty.f32()),
-       },
-       ty.f32(),
-       {
-           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", {}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct UBO {
-  vec4 coord;
-};
-
-layout(binding = 0) uniform UBO_1 {
-  vec4 coord;
-} ubo;
-
-float sub_func(float param) {
-  return ubo.coord.x;
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_UniformStruct) {
-  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("uniforms", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct Uniforms {
-  vec4 coord;
-};
-
-layout(binding = 0) uniform Uniforms_1 {
-  vec4 coord;
-} uniforms;
-
-void frag_main() {
-  float v = uniforms.coord.x;
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  int a;
-  float b;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  int a;
-  float b;
-} coord;
-void frag_main() {
-  float v = coord.b;
-  return;
-}
-
-void main() {
-  frag_main();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  int a;
-  float b;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  int a;
-  float b;
-} coord;
-void frag_main() {
-  float v = coord.b;
-  return;
-}
-
-void main() {
-  frag_main();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  int a;
-  float b;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  int a;
-  float b;
-} coord;
-void frag_main() {
-  coord.b = 2.0f;
-  return;
-}
-
-void main() {
-  frag_main();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  int a;
-  float b;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  int a;
-  float b;
-} coord;
-void frag_main() {
-  coord.b = 2.0f;
-  return;
-}
-
-void main() {
-  frag_main();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("coord", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
-       {
-           Return(MemberAccessor("coord", "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-struct S {
-  float x;
-};
-
-layout(binding = 0) uniform S_1 {
-  float x;
-} coord;
-
-float sub_func(float param) {
-  return coord.x;
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
-       {
-           Return(MemberAccessor("coord", "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(#version 310 es
-precision mediump float;
-
-struct S {
-  float x;
-};
-
-layout(binding = 0, std430) buffer S_1 {
-  float x;
-} coord;
-float sub_func(float param) {
-  return coord.x;
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-
-void main() {
-  frag_main();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithNameCollision) {
-  Func("centroid", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
-
-void tint_symbol() {
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(2, 4, 6),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-layout(local_size_x = 2, local_size_y = 4, local_size_z = 6) in;
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) {
-  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
-  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
-  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize("width", "height", "depth"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-const int width = int(2);
-const int height = int(3);
-const int depth = int(4);
-layout(local_size_x = 2, local_size_y = 3, local_size_z = 4) in;
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
-  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
-  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize("width", "height", "depth"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-#ifndef WGSL_SPEC_CONSTANT_7
-#define WGSL_SPEC_CONSTANT_7 int(2)
-#endif
-const int width = WGSL_SPEC_CONSTANT_7;
-#ifndef WGSL_SPEC_CONSTANT_8
-#define WGSL_SPEC_CONSTANT_8 int(3)
-#endif
-const int height = WGSL_SPEC_CONSTANT_8;
-#ifndef WGSL_SPEC_CONSTANT_9
-#define WGSL_SPEC_CONSTANT_9 int(4)
-#endif
-const int depth = WGSL_SPEC_CONSTANT_9;
-layout(local_size_x = WGSL_SPEC_CONSTANT_7, local_size_y = WGSL_SPEC_CONSTANT_8, local_size_z = WGSL_SPEC_CONSTANT_9) in;
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
-  Func("my_func", ast::VariableList{Param("a", ty.array<f32, 5>())}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-void my_func(float a[5]) {
-  return;
-}
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
-  Func("my_func", {}, ty.array<f32, 5>(),
-       {
-           Return(Construct(ty.array<f32, 5>())),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-float[5] my_func() {
-  return float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-}
-
-)");
-}
-
-// https://crbug.com/tint/297
-TEST_F(GlslGeneratorImplTest_Function,
-       Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
-  // [[block]] struct Data {
-  //   d : f32;
-  // };
-  // @binding(0) @group(0) var<storage> data : Data;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn a() {
-  //   var v = data.d;
-  //   return;
-  // }
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn b() {
-  //   var v = data.d;
-  //   return;
-  // }
-
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("a", ast::VariableList{}, ty.void_(),
-         {
-             Decl(var),
-             Return(),
-         },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  }
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("b", ast::VariableList{}, ty.void_(),
-         {
-             Decl(var),
-             Return(),
-         },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  }
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct Data {
-  float d;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  float d;
-} data;
-void a() {
-  float v = data.d;
-  return;
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  a();
-  return;
-}
-void b() {
-  float v = data.d;
-  return;
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main_1() {
-  b();
-  return;
-}
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_identifier_test.cc b/src/writer/glsl/generator_impl_identifier_test.cc
deleted file mode 100644
index e4c0536..0000000
--- a/src/writer/glsl/generator_impl_identifier_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Identifier = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Identifier, EmitIdentifierExpression) {
-  Global("foo", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* i = Expr("foo");
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
-  EXPECT_EQ(out.str(), "foo");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_if_test.cc b/src/writer/glsl/generator_impl_if_test.cc
deleted file mode 100644
index 58b3d48..0000000
--- a/src/writer/glsl/generator_impl_if_test.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_If = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_If, Emit_If) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body);
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElseIf) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_If, Emit_IfWithElse) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    return;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_If, Emit_IfWithMultiple) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-
-  auto* else_body = Block(Return());
-
-  auto* else_body_2 = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body,
-               ast::ElseStatementList{
-                   create<ast::ElseStatement>(else_cond, else_body),
-                   create<ast::ElseStatement>(nullptr, else_body_2),
-               });
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    } else {
-      return;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_import_test.cc b/src/writer/glsl/generator_impl_import_test.cc
deleted file mode 100644
index 3c92396..0000000
--- a/src/writer/glsl/generator_impl_import_test.cc
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Import = TestHelper;
-
-struct GlslImportData {
-  const char* name;
-  const char* glsl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, GlslImportData data) {
-  out << data.name;
-  return out;
-}
-
-using GlslImportData_SingleParamTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_SingleParamTest, FloatScalar) {
-  auto param = GetParam();
-
-  auto* ident = Expr(param.name);
-  auto* expr = Call(ident, 1.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_SingleParamTest,
-                         testing::Values(GlslImportData{"abs", "abs"},
-                                         GlslImportData{"acos", "acos"},
-                                         GlslImportData{"asin", "asin"},
-                                         GlslImportData{"atan", "atan"},
-                                         GlslImportData{"cos", "cos"},
-                                         GlslImportData{"cosh", "cosh"},
-                                         GlslImportData{"ceil", "ceil"},
-                                         GlslImportData{"exp", "exp"},
-                                         GlslImportData{"exp2", "exp2"},
-                                         GlslImportData{"floor", "floor"},
-                                         GlslImportData{"fract", "frac"},
-                                         GlslImportData{"inverseSqrt", "rsqrt"},
-                                         GlslImportData{"length", "length"},
-                                         GlslImportData{"log", "log"},
-                                         GlslImportData{"log2", "log2"},
-                                         GlslImportData{"round", "round"},
-                                         GlslImportData{"sign", "sign"},
-                                         GlslImportData{"sin", "sin"},
-                                         GlslImportData{"sinh", "sinh"},
-                                         GlslImportData{"sqrt", "sqrt"},
-                                         GlslImportData{"tan", "tan"},
-                                         GlslImportData{"tanh", "tanh"},
-                                         GlslImportData{"trunc", "trunc"}));
-
-using GlslImportData_SingleIntParamTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_SingleIntParamTest, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, Expr(1));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_SingleIntParamTest,
-                         testing::Values(GlslImportData{"abs", "abs"}));
-
-using GlslImportData_SingleVectorParamTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_SingleVectorParamTest, FloatVector) {
-  auto param = GetParam();
-
-  auto* ident = Expr(param.name);
-  auto* expr = Call(ident, vec3<f32>(1.f, 2.f, 3.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            std::string(param.glsl_name) + "(vec3(1.0f, 2.0f, 3.0f))");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_SingleVectorParamTest,
-                         testing::Values(GlslImportData{"abs", "abs"},
-                                         GlslImportData{"acos", "acos"},
-                                         GlslImportData{"asin", "asin"},
-                                         GlslImportData{"atan", "atan"},
-                                         GlslImportData{"cos", "cos"},
-                                         GlslImportData{"cosh", "cosh"},
-                                         GlslImportData{"ceil", "ceil"},
-                                         GlslImportData{"exp", "exp"},
-                                         GlslImportData{"exp2", "exp2"},
-                                         GlslImportData{"floor", "floor"},
-                                         GlslImportData{"fract", "frac"},
-                                         GlslImportData{"inverseSqrt", "rsqrt"},
-                                         GlslImportData{"length", "length"},
-                                         GlslImportData{"log", "log"},
-                                         GlslImportData{"log2", "log2"},
-                                         GlslImportData{"normalize",
-                                                        "normalize"},
-                                         GlslImportData{"round", "round"},
-                                         GlslImportData{"sign", "sign"},
-                                         GlslImportData{"sin", "sin"},
-                                         GlslImportData{"sinh", "sinh"},
-                                         GlslImportData{"sqrt", "sqrt"},
-                                         GlslImportData{"tan", "tan"},
-                                         GlslImportData{"tanh", "tanh"},
-                                         GlslImportData{"trunc", "trunc"}));
-
-using GlslImportData_DualParam_ScalarTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_DualParam_ScalarTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1.f, 2.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_DualParam_ScalarTest,
-                         testing::Values(GlslImportData{"atan2", "atan"},
-                                         GlslImportData{"distance", "distance"},
-                                         GlslImportData{"max", "max"},
-                                         GlslImportData{"min", "min"},
-                                         GlslImportData{"pow", "pow"},
-                                         GlslImportData{"step", "step"}));
-
-using GlslImportData_DualParam_VectorTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_DualParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr =
-      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) +
-                           "(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f))");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_DualParam_VectorTest,
-                         testing::Values(GlslImportData{"atan2", "atan"},
-                                         GlslImportData{"cross", "cross"},
-                                         GlslImportData{"distance", "distance"},
-                                         GlslImportData{"max", "max"},
-                                         GlslImportData{"min", "min"},
-                                         GlslImportData{"pow", "pow"},
-                                         GlslImportData{"reflect", "reflect"},
-                                         GlslImportData{"step", "step"}));
-
-using GlslImportData_DualParam_Int_Test = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_DualParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_DualParam_Int_Test,
-                         testing::Values(GlslImportData{"max", "max"},
-                                         GlslImportData{"min", "min"}));
-
-using GlslImportData_TripleParam_ScalarTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_TripleParam_ScalarTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f, 3.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_TripleParam_ScalarTest,
-                         testing::Values(GlslImportData{"fma", "mad"},
-                                         GlslImportData{"mix", "mix"},
-                                         GlslImportData{"clamp", "clamp"},
-                                         GlslImportData{"smoothStep",
-                                                        "smoothstep"}));
-
-using GlslImportData_TripleParam_VectorTest = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_TripleParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
-                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(
-      out.str(),
-      std::string(param.glsl_name) +
-          R"((vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f)))");
-}
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest_Import,
-    GlslImportData_TripleParam_VectorTest,
-    testing::Values(GlslImportData{"faceForward", "faceforward"},
-                    GlslImportData{"fma", "mad"},
-                    GlslImportData{"clamp", "clamp"},
-                    GlslImportData{"smoothStep", "smoothstep"}));
-
-TEST_F(GlslGeneratorImplTest_Import, DISABLED_GlslImportData_FMix) {
-  FAIL();
-}
-
-using GlslImportData_TripleParam_Int_Test = TestParamHelper<GlslImportData>;
-TEST_P(GlslImportData_TripleParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2, 3);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2, 3)");
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Import,
-                         GlslImportData_TripleParam_Int_Test,
-                         testing::Values(GlslImportData{"clamp", "clamp"}));
-
-TEST_F(GlslGeneratorImplTest_Import, GlslImportData_Determinant) {
-  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("determinant", "var");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string("determinant(var)"));
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_loop_test.cc b/src/writer/glsl/generator_impl_loop_test.cc
deleted file mode 100644
index 7f718f0..0000000
--- a/src/writer/glsl/generator_impl_loop_test.cc
+++ /dev/null
@@ -1,390 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Loop = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_Loop) {
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block();
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    discard;
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    discard;
-    {
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* inner = Loop(body, continuing);
-
-  body = Block(inner);
-
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  continuing = Block(Assign(lhs, rhs));
-
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    while (true) {
-      discard;
-      {
-        a_statement();
-      }
-    }
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithVarUsedInContinuing) {
-  // loop {
-  //   var lhs : f32 = 2.4;
-  //   var other : f32;
-  //   break;
-  //   continuing {
-  //     lhs = rhs
-  //   }
-  // }
-
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
-                     Decl(Var("other", ty.f32())),            //
-                     Break());
-  auto* continuing = Block(Assign("lhs", "rhs"));
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    float lhs = 2.400000095f;
-    float other = 0.0f;
-    break;
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoop) {
-  // for(; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, nullptr, nullptr,  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    for(; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) {
-  // for(var i : i32; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr,  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    for(int i = 0; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
-  // for(var b = true && false; ; ) {
-  //   return;
-  // }
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr,
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
-    if (tint_tmp) {
-      tint_tmp = false;
-    }
-    bool b = (tint_tmp);
-    for(; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) {
-  // for(; true; ) {
-  //   return;
-  // }
-
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* f = For(nullptr, true, nullptr, Block(CallStmt(Call("a_statement"))));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    for(; true; ) {
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
-  // for(; true && false; ) {
-  //   return;
-  // }
-
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* f =
-      For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement"))));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    while (true) {
-      bool tint_tmp = true;
-      if (tint_tmp) {
-        tint_tmp = false;
-      }
-      if (!((tint_tmp))) { break; }
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) {
-  // for(; ; i = i + 1) {
-  //   return;
-  // }
-
-  auto* v = Decl(Var("i", ty.i32()));
-  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)),  //
-                Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    for(; ; i = (i + 1)) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
-  // for(; ; i = true && false) {
-  //   return;
-  // }
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* v = Decl(Var("i", ty.bool_()));
-  auto* f = For(nullptr, nullptr, Assign("i", multi_stmt),  //
-                Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    while (true) {
-      return;
-      bool tint_tmp = true;
-      if (tint_tmp) {
-        tint_tmp = false;
-      }
-      i = (tint_tmp);
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) {
-  // for(var i : i32; true; i = i + 1) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    for(int i = 0; true; i = (i + 1)) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
-  // for(var i = true && false; true && false; i = true && false) {
-  //   return;
-  // }
-
-  auto* multi_stmt_a = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-  auto* multi_stmt_b = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-  auto* multi_stmt_c = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-
-  auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b,
-                Assign("i", multi_stmt_c),  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
-    if (tint_tmp) {
-      tint_tmp = false;
-    }
-    bool i = (tint_tmp);
-    while (true) {
-      bool tint_tmp_1 = true;
-      if (tint_tmp_1) {
-        tint_tmp_1 = false;
-      }
-      if (!((tint_tmp_1))) { break; }
-      return;
-      bool tint_tmp_2 = true;
-      if (tint_tmp_2) {
-        tint_tmp_2 = false;
-      }
-      i = (tint_tmp_2);
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_member_accessor_test.cc b/src/writer/glsl/generator_impl_member_accessor_test.cc
deleted file mode 100644
index 5ff298e..0000000
--- a/src/writer/glsl/generator_impl_member_accessor_test.cc
+++ /dev/null
@@ -1,886 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using create_type_func_ptr =
-    const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
-
-inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.i32();
-}
-inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.u32();
-}
-inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.f32();
-}
-template <typename T>
-inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x4<T>();
-}
-
-using i32 = ProgramBuilder::i32;
-using u32 = ProgramBuilder::u32;
-using f32 = ProgramBuilder::f32;
-
-template <typename BASE>
-class GlslGeneratorImplTest_MemberAccessorBase : public BASE {
- public:
-  void SetupStorageBuffer(ast::StructMemberList members) {
-    ProgramBuilder& b = *this;
-
-    auto* s =
-        b.Structure("Data", members, {b.create<ast::StructBlockAttribute>()});
-
-    b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage,
-             ast::Access::kReadWrite,
-             ast::AttributeList{
-                 b.create<ast::BindingAttribute>(0),
-                 b.create<ast::GroupAttribute>(1),
-             });
-  }
-
-  void SetupFunction(ast::StatementList statements) {
-    ProgramBuilder& b = *this;
-    b.Func("main", ast::VariableList{}, b.ty.void_(), statements,
-           ast::AttributeList{
-               b.Stage(ast::PipelineStage::kFragment),
-           });
-  }
-};
-
-using GlslGeneratorImplTest_MemberAccessor =
-    GlslGeneratorImplTest_MemberAccessorBase<TestHelper>;
-
-template <typename T>
-using GlslGeneratorImplTest_MemberAccessorWithParam =
-    GlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
-
-  auto* expr = MemberAccessor("str", "mem");
-  WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct Data {
-  float mem;
-};
-
-Data str = Data(0.0f);
-void test_function() {
-  float expr = str.mem;
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  test_function();
-  return;
-}
-)");
-}
-
-struct TypeCase {
-  create_type_func_ptr member_type;
-  std::string expected;
-};
-inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
-  ProgramBuilder b;
-  auto* ty = c.member_type(b.ty);
-  out << ty->FriendlyName(b.Symbols());
-  return out;
-}
-
-using GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad =
-    GlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
-TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, Test) {
-  // struct Data {
-  //   a : i32;
-  //   b : <type>;
-  // };
-  // var<storage> data : Data;
-  // data.b;
-
-  auto p = GetParam();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", p.member_type(ty)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor("data", "b"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_MemberAccessor,
-                         GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad,
-                         testing::Values(TypeCase{ty_u32, "data.b"},
-                                         TypeCase{ty_f32, "data.b"},
-                                         TypeCase{ty_i32, "data.b"},
-                                         TypeCase{ty_vec2<u32>, "data.b"},
-                                         TypeCase{ty_vec2<f32>, "data.b"},
-                                         TypeCase{ty_vec2<i32>, "data.b"},
-                                         TypeCase{ty_vec3<u32>, "data.b"},
-                                         TypeCase{ty_vec3<f32>, "data.b"},
-                                         TypeCase{ty_vec3<i32>, "data.b"},
-                                         TypeCase{ty_vec4<u32>, "data.b"},
-                                         TypeCase{ty_vec4<f32>, "data.b"},
-                                         TypeCase{ty_vec4<i32>, "data.b"},
-                                         TypeCase{ty_mat2x2<f32>, "data.b"},
-                                         TypeCase{ty_mat2x3<f32>, "data.b"},
-                                         TypeCase{ty_mat2x4<f32>, "data.b"},
-                                         TypeCase{ty_mat3x2<f32>, "data.b"},
-                                         TypeCase{ty_mat3x3<f32>, "data.b"},
-                                         TypeCase{ty_mat3x4<f32>, "data.b"},
-                                         TypeCase{ty_mat4x2<f32>, "data.b"},
-                                         TypeCase{ty_mat4x3<f32>, "data.b"},
-                                         TypeCase{ty_mat4x4<f32>, "data.b"}));
-
-using GlslGeneratorImplTest_MemberAccessor_StorageBufferStore =
-    GlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
-TEST_P(GlslGeneratorImplTest_MemberAccessor_StorageBufferStore, Test) {
-  // struct Data {
-  //   a : i32;
-  //   b : <type>;
-  // };
-  // var<storage> data : Data;
-  // data.b = <type>();
-
-  auto p = GetParam();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", p.member_type(ty)),
-  });
-
-  SetupFunction({
-      Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
-               Construct(p.member_type(ty)))),
-      Assign(MemberAccessor("data", "b"), Expr("value")),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest_MemberAccessor,
-    GlslGeneratorImplTest_MemberAccessor_StorageBufferStore,
-    testing::Values(TypeCase{ty_u32, "data.b = value"},
-                    TypeCase{ty_f32, "data.b = value"},
-                    TypeCase{ty_i32, "data.b = value"},
-                    TypeCase{ty_vec2<u32>, "data.b = value"},
-                    TypeCase{ty_vec2<f32>, "data.b = value"},
-                    TypeCase{ty_vec2<i32>, "data.b = value"},
-                    TypeCase{ty_vec3<u32>, "data.b = value"},
-                    TypeCase{ty_vec3<f32>, "data.b = value"},
-                    TypeCase{ty_vec3<i32>, "data.b = value"},
-                    TypeCase{ty_vec4<u32>, "data.b = value"},
-                    TypeCase{ty_vec4<f32>, "data.b = value"},
-                    TypeCase{ty_vec4<i32>, "data.b = value"},
-                    TypeCase{ty_mat2x2<f32>, "data.b = value"},
-                    TypeCase{ty_mat2x3<f32>, "data.b = value"},
-                    TypeCase{ty_mat2x4<f32>, "data.b = value"},
-                    TypeCase{ty_mat3x2<f32>, "data.b = value"},
-                    TypeCase{ty_mat3x3<f32>, "data.b = value"},
-                    TypeCase{ty_mat3x4<f32>, "data.b = value"},
-                    TypeCase{ty_mat4x2<f32>, "data.b = value"},
-                    TypeCase{ty_mat4x3<f32>, "data.b = value"},
-                    TypeCase{ty_mat4x4<f32>, "data.b = value"}));
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) {
-  // struct Data {
-  //   z : f32;
-  //   a : mat2x3<f32>;
-  // };
-  // var<storage> data : Data;
-  // data.a = mat2x3<f32>();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", ty.mat2x3<f32>()),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor("data", "b"),
-             Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  int a;
-  mat2x3 b;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  int a;
-  mat2x3 b;
-} data;
-void tint_symbol() {
-  data.b = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_Matrix_Single_Element) {
-  // struct Data {
-  //   z : f32;
-  //   a : mat4x3<f32>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2][1];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.mat4x3<f32>()),
-  });
-
-  SetupFunction({
-      Decl(
-          Var("x", nullptr, ast::StorageClass::kNone,
-              IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2), 1))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  float z;
-  mat4x3 a;
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  float z;
-  mat4x3 a;
-} data;
-void tint_symbol() {
-  float x = data.a[2][1];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               IndexAccessor(MemberAccessor("data", "a"), 2))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  float z;
-  int a[5];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  float z;
-  int a[5];
-} data;
-void tint_symbol() {
-  int x = data.a[2];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[(2 + 4) - 3];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               IndexAccessor(MemberAccessor("data", "a"),
-                             Sub(Add(2, Expr(4)), Expr(3))))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  float z;
-  int a[5];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  float z;
-  int a[5];
-} data;
-void tint_symbol() {
-  int x = data.a[((2 + 4) - 3)];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2] = 2;
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Assign(IndexAccessor(MemberAccessor("data", "a"), 2), 2),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Data {
-  float z;
-  int a[5];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  float z;
-  int a[5];
-} data;
-void tint_symbol() {
-  data.a[2] = 2;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var(
-          "x", nullptr, ast::StorageClass::kNone,
-          MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  vec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  vec3 x = data.c[2].b;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Swizzle) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.xy
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor(
-                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                  "b"),
-                   "xy"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  vec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  vec2 x = data.c[2].b.xy;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {  // NOLINT
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.g
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor(
-                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                  "b"),
-                   "g"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  vec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  float x = data.c[2].b.g;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Index) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b[1]
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var(
-          "x", nullptr, ast::StorageClass::kNone,
-          IndexAccessor(MemberAccessor(
-                            IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
-                        1))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  vec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  float x = data.c[2].b[1];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b = vec3<f32>(1.f, 2.f, 3.f);
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
-             vec3<f32>(1.f, 2.f, 3.f)),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  vec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  data.c[2].b = vec3(1.0f, 2.0f, 3.0f);
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Store_Swizzle_SingleLetter) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.y = 1.f;
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<i32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor(
-                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                "b"),
-                 "y"),
-             Expr(1.f)),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(#version 310 es
-precision mediump float;
-
-struct Inner {
-  ivec3 a;
-  vec3 b;
-};
-
-struct Data {
-  Inner c[4];
-};
-
-layout(binding = 0, std430) buffer Data_1 {
-  Inner c[4];
-} data;
-void tint_symbol() {
-  data.c[2].b.y = 1.0f;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
-  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
-                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
-  auto* expr = MemberAccessor("my_vec", "xyz");
-  WrapInFunction(var, expr);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz"));
-}
-
-TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
-  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
-                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
-  auto* expr = MemberAccessor("my_vec", "gbr");
-  WrapInFunction(var, expr);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr"));
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_module_constant_test.cc b/src/writer/glsl/generator_impl_module_constant_test.cc
deleted file mode 100644
index 036f1e5..0000000
--- a/src/writer/glsl/generator_impl_module_constant_test.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/id_attribute.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_ModuleConstant = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) {
-  auto* var = Const("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
-  WrapInFunction(Decl(var));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), "const float pos[3] = float[3](1.0f, 2.0f, 3.0f);\n");
-}
-
-TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) {
-  auto* var = Override("pos", ty.f32(), Expr(3.0f),
-                       ast::AttributeList{
-                           Id(23),
-                       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
-#define WGSL_SPEC_CONSTANT_23 3.0f
-#endif
-const float pos = WGSL_SPEC_CONSTANT_23;
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoConstructor) {
-  auto* var = Override("pos", ty.f32(), nullptr,
-                       ast::AttributeList{
-                           Id(23),
-                       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
-#error spec constant required for constant id 23
-#endif
-const float pos = WGSL_SPEC_CONSTANT_23;
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoId) {
-  auto* a = Override("a", ty.f32(), Expr(3.0f),
-                     ast::AttributeList{
-                         Id(0),
-                     });
-  auto* b = Override("b", ty.f32(), Expr(2.0f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error();
-  ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
-#define WGSL_SPEC_CONSTANT_0 3.0f
-#endif
-const float a = WGSL_SPEC_CONSTANT_0;
-#ifndef WGSL_SPEC_CONSTANT_1
-#define WGSL_SPEC_CONSTANT_1 2.0f
-#endif
-const float b = WGSL_SPEC_CONSTANT_1;
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_return_test.cc b/src/writer/glsl/generator_impl_return_test.cc
deleted file mode 100644
index bcb5bc6..0000000
--- a/src/writer/glsl/generator_impl_return_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Return = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Return, Emit_Return) {
-  auto* r = Return();
-  WrapInFunction(r);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return;\n");
-}
-
-TEST_F(GlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
-  auto* r = Return(123);
-  Func("f", {}, ty.i32(), {r});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return 123;\n");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_sanitizer_test.cc b/src/writer/glsl/generator_impl_sanitizer_test.cc
deleted file mode 100644
index 0329bc0..0000000
--- a/src/writer/glsl/generator_impl_sanitizer_test.cc
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslSanitizerTest = TestHelper;
-
-TEST_F(GlslSanitizerTest, Call_ArrayLength) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-layout(binding = 1, std430) buffer my_struct_1 {
-  float a[];
-} b;
-void a_func() {
-  uint len = uint(b.a.length());
-}
-
-void main() {
-  a_func();
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = Structure("my_struct",
-                      {
-                          Member(0, "z", ty.f32()),
-                          Member(4, "a", ty.array<f32>(4)),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-layout(binding = 1, std430) buffer my_struct_1 {
-  float z;
-  float a[];
-} b;
-void a_func() {
-  uint len = uint(b.a.length());
-}
-
-void main() {
-  a_func();
-  return;
-}
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  auto* p = Const("p", nullptr, AddressOf("b"));
-  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(p),
-           Decl(p2),
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", p2))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-layout(binding = 1, std430) buffer my_struct_1 {
-  float a[];
-} b;
-void a_func() {
-  uint len = uint(b.a.length());
-}
-
-void main() {
-  a_func();
-  return;
-}
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) {
-  auto* array_init = array<i32, 4>(1, 2, 3, 4);
-  auto* array_index = IndexAccessor(array_init, 3);
-  auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index);
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(pos),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-void tint_symbol() {
-  int tint_symbol_1[4] = int[4](1, 2, 3, 4);
-  int pos = tint_symbol_1[3];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.vec3<f32>()),
-                                 Member("c", ty.i32()),
-                             });
-  auto* struct_init = Construct(ty.Of(str), 1, vec3<f32>(2.f, 3.f, 4.f), 4);
-  auto* struct_access = MemberAccessor(struct_init, "b");
-  auto* pos =
-      Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(pos),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-struct S {
-  int a;
-  vec3 b;
-  int c;
-};
-
-void tint_symbol() {
-  S tint_symbol_1 = S(1, vec3(2.0f, 3.0f, 4.0f), 4);
-  vec3 pos = tint_symbol_1.b;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, InlinePtrLetsBasic) {
-  // var v : i32;
-  // let p : ptr<function, i32> = &v;
-  // let x : i32 = *p;
-  auto* v = Var("v", ty.i32());
-  auto* p =
-      Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
-  auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(v),
-           Decl(p),
-           Decl(x),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-void tint_symbol() {
-  int v = 0;
-  int x = v;
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(GlslSanitizerTest, InlinePtrLetsComplexChain) {
-  // var a : array<mat4x4<f32>, 4>;
-  // let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
-  // let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
-  // let vp : ptr<function, vec4<f32>> = &(*mp)[2];
-  // let v : vec4<f32> = *vp;
-  auto* a = Var("a", ty.array(ty.mat4x4<f32>(), 4));
-  auto* ap = Const(
-      "ap",
-      ty.pointer(ty.array(ty.mat4x4<f32>(), 4), ast::StorageClass::kFunction),
-      AddressOf(a));
-  auto* mp =
-      Const("mp", ty.pointer(ty.mat4x4<f32>(), ast::StorageClass::kFunction),
-            AddressOf(IndexAccessor(Deref(ap), 3)));
-  auto* vp =
-      Const("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
-            AddressOf(IndexAccessor(Deref(mp), 2)));
-  auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(ap),
-           Decl(mp),
-           Decl(vp),
-           Decl(v),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#version 310 es
-precision mediump float;
-
-void tint_symbol() {
-  mat4 a[4] = mat4[4](mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-  vec4 v = a[3][2];
-}
-
-void main() {
-  tint_symbol();
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_storage_buffer_test.cc b/src/writer/glsl/generator_impl_storage_buffer_test.cc
deleted file mode 100644
index d6896f4..0000000
--- a/src/writer/glsl/generator_impl_storage_buffer_test.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// 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 "gmock/gmock.h"
-#include "src/writer/glsl/test_helper.h"
-
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_StorageBuffer = TestHelper;
-
-void TestAlign(ProgramBuilder* ctx) {
-  // struct Nephews {
-  //   @align(256) huey  : f32;
-  //   @align(256) dewey : f32;
-  //   @align(256) louie : f32;
-  // };
-  // @group(0) @binding(0) var<storage, read_write> nephews : Nephews;
-  auto* nephews = ctx->Structure(
-      "Nephews",
-      {
-          ctx->Member("huey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-          ctx->Member("dewey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-          ctx->Member("louie", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-      });
-  ctx->Global("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage,
-              ast::AttributeList{
-                  ctx->create<ast::BindingAttribute>(0),
-                  ctx->create<ast::GroupAttribute>(0),
-              });
-}
-
-TEST_F(GlslGeneratorImplTest_StorageBuffer, Align) {
-  TestAlign(this);
-
-  GeneratorImpl& gen = Build();
-
-  // TODO(crbug.com/tint/1421) offsets do not currently work on GLSL ES.
-  // They will likely require manual padding.
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct Nephews {
-  float huey;
-  float dewey;
-  float louie;
-};
-
-layout(binding = 0, std430) buffer Nephews_1 {
-  float huey;
-  float dewey;
-  float louie;
-} nephews;
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_StorageBuffer, Align_Desktop) {
-  TestAlign(this);
-
-  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 440
-
-struct Nephews {
-  float huey;
-  float dewey;
-  float louie;
-};
-
-layout(binding = 0, std430) buffer Nephews_1 {
-  float huey;
-  layout(offset=256) float dewey;
-  layout(offset=512) float louie;
-} nephews;
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_switch_test.cc b/src/writer/glsl/generator_impl_switch_test.cc
deleted file mode 100644
index abcd1ee..0000000
--- a/src/writer/glsl/generator_impl_switch_test.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_Switch = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Switch, Emit_Switch) {
-  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* def_body = Block(create<ast::BreakStatement>());
-  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
-
-  ast::CaseSelectorList case_val;
-  case_val.push_back(Expr(5));
-
-  auto* case_body = Block(create<ast::BreakStatement>());
-
-  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
-
-  ast::CaseStatementList body;
-  body.push_back(case_stmt);
-  body.push_back(def);
-
-  auto* cond = Expr("cond");
-  auto* s = create<ast::SwitchStatement>(cond, body);
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  switch(cond) {
-    case 5: {
-      break;
-    }
-    default: {
-      break;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_test.cc b/src/writer/glsl/generator_impl_test.cc
deleted file mode 100644
index 471407e..0000000
--- a/src/writer/glsl/generator_impl_test.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest = TestHelper;
-
-TEST_F(GlslGeneratorImplTest, Generate) {
-  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::AttributeList{});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-void my_func() {
-}
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest, GenerateDesktop) {
-  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::AttributeList{});
-
-  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 440
-
-void my_func() {
-}
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest, GenerateSampleIndexES) {
-  Global(
-      "gl_SampleID", ty.i32(),
-      ast::AttributeList{Builtin(ast::Builtin::kSampleIndex),
-                         Disable(ast::DisabledValidation::kIgnoreStorageClass)},
-      ast::StorageClass::kInput);
-  Func("my_func", {}, ty.i32(),
-       ast::StatementList{Return(Expr("gl_SampleID"))});
-
-  GeneratorImpl& gen = Build(Version(Version::Standard::kES, 3, 1));
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-#extension GL_OES_sample_variables : require
-
-int my_func() {
-  return gl_SampleID;
-}
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest, GenerateSampleIndexDesktop) {
-  Global(
-      "gl_SampleID", ty.i32(),
-      ast::AttributeList{Builtin(ast::Builtin::kSampleIndex),
-                         Disable(ast::DisabledValidation::kIgnoreStorageClass)},
-      ast::StorageClass::kInput);
-  Func("my_func", {}, ty.i32(),
-       ast::StatementList{Return(Expr("gl_SampleID"))});
-
-  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 440
-
-int my_func() {
-  return gl_SampleID;
-}
-
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_type_test.cc b/src/writer/glsl/generator_impl_type_test.cc
deleted file mode 100644
index 40d414d..0000000
--- a/src/writer/glsl/generator_impl_type_test.cc
+++ /dev/null
@@ -1,575 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using GlslGeneratorImplTest_Type = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Array) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[4]");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
-  auto* arr = ty.array(ty.array<bool, 4>(), 5);
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[5][4]");
-}
-
-// TODO(dsinclair): Is this possible? What order should it output in?
-TEST_F(GlslGeneratorImplTest_Type,
-       DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
-  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 0);
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[5][4][1]");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
-  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool[4]");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Bool) {
-  auto* bool_ = create<sem::Bool>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_F32) {
-  auto* f32 = create<sem::F32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_I32) {
-  auto* i32 = create<sem::I32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "int");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Matrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "mat2x3");
-}
-
-// TODO(dsinclair): How to annotate as workgroup?
-TEST_F(GlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
-  auto* f32 = create<sem::F32>();
-  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
-                                 ast::Access::kReadWrite);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float*");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_StructDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  int a;
-  float b;
-};
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "S");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-  auto* s = Structure("S", {
-                               Member("double", ty.i32()),
-                               Member("float", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(struct S {
-  int tint_symbol;
-  float tint_symbol_1;
-};
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32(), {MemberOffset(0)}),
-                          Member("b", ty.f32(), {MemberOffset(8)}),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  int a;
-  float b;
-};
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_U32) {
-  auto* u32 = create<sem::U32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "uint");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Vector) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "vec3");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitType_Void) {
-  auto* void_ = create<sem::Void>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "void");
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitSampler) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_FALSE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
-                            ast::Access::kReadWrite, ""))
-      << gen.error();
-}
-
-TEST_F(GlslGeneratorImplTest_Type, EmitSamplerComparison) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_FALSE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
-                            ast::Access::kReadWrite, ""))
-      << gen.error();
-}
-
-struct GlslDepthTextureData {
-  ast::TextureDimension dim;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out, GlslDepthTextureData data) {
-  out << data.dim;
-  return out;
-}
-using GlslDepthTexturesTest = TestParamHelper<GlslDepthTextureData>;
-TEST_P(GlslDepthTexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* t = ty.depth_texture(params.dim);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest_Type,
-    GlslDepthTexturesTest,
-    testing::Values(GlslDepthTextureData{ast::TextureDimension::k2d,
-                                         "sampler2DShadow tex;"},
-                    GlslDepthTextureData{ast::TextureDimension::k2dArray,
-                                         "sampler2DArrayShadow tex;"},
-                    GlslDepthTextureData{ast::TextureDimension::kCube,
-                                         "samplerCubeShadow tex;"},
-                    GlslDepthTextureData{ast::TextureDimension::kCubeArray,
-                                         "samplerCubeArrayShadow tex;"}));
-
-using GlslDepthMultisampledTexturesTest = TestHelper;
-TEST_F(GlslDepthMultisampledTexturesTest, Emit) {
-  auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("sampler2DMS tex;"));
-}
-
-enum class TextureDataType { F32, U32, I32 };
-struct GlslSampledTextureData {
-  ast::TextureDimension dim;
-  TextureDataType datatype;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                GlslSampledTextureData data) {
-  out << data.dim;
-  return out;
-}
-using GlslSampledTexturesTest = TestParamHelper<GlslSampledTextureData>;
-TEST_P(GlslSampledTexturesTest, Emit) {
-  auto params = GetParam();
-
-  const ast::Type* datatype = nullptr;
-  switch (params.datatype) {
-    case TextureDataType::F32:
-      datatype = ty.f32();
-      break;
-    case TextureDataType::U32:
-      datatype = ty.u32();
-      break;
-    case TextureDataType::I32:
-      datatype = ty.i32();
-      break;
-  }
-  auto* t = ty.sampled_texture(params.dim, datatype);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(GlslGeneratorImplTest_Type,
-                         GlslSampledTexturesTest,
-                         testing::Values(
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k1d,
-                                 TextureDataType::F32,
-                                 "sampler1D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2d,
-                                 TextureDataType::F32,
-                                 "sampler2D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2dArray,
-                                 TextureDataType::F32,
-                                 "sampler2DArray tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k3d,
-                                 TextureDataType::F32,
-                                 "sampler3D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCube,
-                                 TextureDataType::F32,
-                                 "samplerCube tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCubeArray,
-                                 TextureDataType::F32,
-                                 "samplerCubeArray tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k1d,
-                                 TextureDataType::U32,
-                                 "usampler1D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2d,
-                                 TextureDataType::U32,
-                                 "usampler2D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2dArray,
-                                 TextureDataType::U32,
-                                 "usampler2DArray tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k3d,
-                                 TextureDataType::U32,
-                                 "usampler3D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCube,
-                                 TextureDataType::U32,
-                                 "usamplerCube tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCubeArray,
-                                 TextureDataType::U32,
-                                 "usamplerCubeArray tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k1d,
-                                 TextureDataType::I32,
-                                 "isampler1D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2d,
-                                 TextureDataType::I32,
-                                 "isampler2D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k2dArray,
-                                 TextureDataType::I32,
-                                 "isampler2DArray tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::k3d,
-                                 TextureDataType::I32,
-                                 "isampler3D tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCube,
-                                 TextureDataType::I32,
-                                 "isamplerCube tex;",
-                             },
-                             GlslSampledTextureData{
-                                 ast::TextureDimension::kCubeArray,
-                                 TextureDataType::I32,
-                                 "isamplerCubeArray tex;",
-                             }));
-
-TEST_F(GlslGeneratorImplTest_Type, EmitMultisampledTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "uniform highp sampler2DMS");
-}
-
-struct GlslStorageTextureData {
-  ast::TextureDimension dim;
-  ast::TexelFormat imgfmt;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                GlslStorageTextureData data) {
-  return out << data.dim;
-}
-using GlslStorageTexturesTest = TestParamHelper<GlslStorageTextureData>;
-TEST_P(GlslStorageTexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(
-    GlslGeneratorImplTest_Type,
-    GlslStorageTexturesTest,
-    testing::Values(
-        GlslStorageTextureData{ast::TextureDimension::k1d,
-                               ast::TexelFormat::kRgba8Unorm, "image1D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2d,
-                               ast::TexelFormat::kRgba16Float, "image2D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2dArray,
-                               ast::TexelFormat::kR32Float,
-                               "image2DArray tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k3d,
-                               ast::TexelFormat::kRg32Float, "image3D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k1d,
-                               ast::TexelFormat::kRgba32Float, "image1D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2d,
-                               ast::TexelFormat::kRgba16Uint, "image2D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2dArray,
-                               ast::TexelFormat::kR32Uint, "image2DArray tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k3d,
-                               ast::TexelFormat::kRg32Uint, "image3D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k1d,
-                               ast::TexelFormat::kRgba32Uint, "image1D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2d,
-                               ast::TexelFormat::kRgba16Sint, "image2D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k2dArray,
-                               ast::TexelFormat::kR32Sint, "image2DArray tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k3d,
-                               ast::TexelFormat::kRg32Sint, "image3D tex;"},
-        GlslStorageTextureData{ast::TextureDimension::k1d,
-                               ast::TexelFormat::kRgba32Sint, "image1D tex;"}));
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_unary_op_test.cc b/src/writer/glsl/generator_impl_unary_op_test.cc
deleted file mode 100644
index 0b523e9..0000000
--- a/src/writer/glsl/generator_impl_unary_op_test.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslUnaryOpTest = TestHelper;
-
-TEST_F(GlslUnaryOpTest, AddressOf) {
-  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "expr");
-}
-
-TEST_F(GlslUnaryOpTest, Complement) {
-  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "~(expr)");
-}
-
-TEST_F(GlslUnaryOpTest, Indirection) {
-  Global("G", ty.f32(), ast::StorageClass::kPrivate);
-  auto* p = Const(
-      "expr", nullptr,
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
-  WrapInFunction(p, op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "expr");
-}
-
-TEST_F(GlslUnaryOpTest, Not) {
-  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "!(expr)");
-}
-
-TEST_F(GlslUnaryOpTest, Negation) {
-  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "-(expr)");
-}
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_uniform_buffer_test.cc b/src/writer/glsl/generator_impl_uniform_buffer_test.cc
deleted file mode 100644
index 8dd7fd5..0000000
--- a/src/writer/glsl/generator_impl_uniform_buffer_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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 "gmock/gmock.h"
-#include "src/writer/glsl/test_helper.h"
-
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using GlslGeneratorImplTest_UniformBuffer = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple) {
-  auto* simple = Structure("Simple", {Member("member", ty.f32())});
-  Global("simple", ty.Of(simple), ast::StorageClass::kUniform,
-         GroupAndBinding(0, 0));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 310 es
-
-struct Simple {
-  float member;
-};
-
-layout(binding = 0) uniform Simple_1 {
-  float member;
-} simple;
-
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple_Desktop) {
-  auto* simple = Structure("Simple", {Member("member", ty.f32())});
-  Global("simple", ty.Of(simple), ast::StorageClass::kUniform,
-         GroupAndBinding(0, 0));
-
-  GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#version 440
-
-struct Simple {
-  float member;
-};
-
-layout(binding = 0, std140) uniform Simple_1 {
-  float member;
-} simple;
-
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/writer/glsl/generator_impl_variable_decl_statement_test.cc
deleted file mode 100644
index 8d5cf90..0000000
--- a/src/writer/glsl/generator_impl_variable_decl_statement_test.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using GlslGeneratorImplTest_VariableDecl = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) {
-  auto* var = Var("a", ty.f32());
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
-  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
-  auto* var = Var("a", ty.array<f32, 5>());
-
-  WrapInFunction(var, Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr("  float a[5] = float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);\n"));
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) {
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("  float a = 0.0f;\n"));
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_Private) {
-  Global("initializer", ty.f32(), ast::StorageClass::kPrivate);
-  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer;
-)"));
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_ZeroVec) {
-  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(vec3 a = vec3(0.0f, 0.0f, 0.0f);
-)");
-}
-
-TEST_F(GlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_ZeroMat) {
-  auto* var =
-      Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-)");
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/generator_impl_workgroup_var_test.cc b/src/writer/glsl/generator_impl_workgroup_var_test.cc
deleted file mode 100644
index 41ee6fa..0000000
--- a/src/writer/glsl/generator_impl_workgroup_var_test.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/stage_attribute.h"
-#include "src/writer/glsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-namespace {
-using ::testing::HasSubstr;
-
-using GlslGeneratorImplTest_WorkgroupVar = TestHelper;
-
-TEST_F(GlslGeneratorImplTest_WorkgroupVar, Basic) {
-  Global("wg", ty.f32(), ast::StorageClass::kWorkgroup);
-
-  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("shared float wg;\n"));
-}
-
-TEST_F(GlslGeneratorImplTest_WorkgroupVar, Aliased) {
-  auto* alias = Alias("F32", ty.f32());
-
-  Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
-
-  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("shared float wg;\n"));
-}
-
-}  // namespace
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/glsl/test_helper.h b/src/writer/glsl/test_helper.h
deleted file mode 100644
index 6701160..0000000
--- a/src/writer/glsl/test_helper.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_WRITER_GLSL_TEST_HELPER_H_
-#define SRC_WRITER_GLSL_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/transform/glsl.h"
-#include "src/transform/manager.h"
-#include "src/writer/glsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace glsl {
-
-/// Helper class for testing
-template <typename BODY>
-class TestHelperBase : public BODY, public ProgramBuilder {
- public:
-  TestHelperBase() = default;
-  ~TestHelperBase() override = default;
-
-  /// Builds the program and returns a GeneratorImpl from the program.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @param version the GLSL version
-  /// @return the built generator
-  GeneratorImpl& Build(Version version = Version()) {
-    if (gen_) {
-      return *gen_;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-    gen_ = std::make_unique<GeneratorImpl>(program.get(), version);
-    return *gen_;
-  }
-
-  /// Builds the program, runs the program through the transform::Glsl sanitizer
-  /// and returns a GeneratorImpl from the sanitized program.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @param version the GLSL version
-  /// @return the built generator
-  GeneratorImpl& SanitizeAndBuild(Version version = Version()) {
-    if (gen_) {
-      return *gen_;
-    }
-    diag::Formatter formatter;
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << formatter.format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << formatter.format(program->Diagnostics());
-    }();
-
-    transform::Manager transform_manager;
-    transform::DataMap transform_data;
-    transform_manager.Add<tint::transform::Glsl>();
-    auto result = transform_manager.Run(program.get(), transform_data);
-    [&]() {
-      ASSERT_TRUE(result.program.IsValid())
-          << formatter.format(result.program.Diagnostics());
-    }();
-    *program = std::move(result.program);
-    gen_ = std::make_unique<GeneratorImpl>(program.get(), version);
-    return *gen_;
-  }
-
-  /// The program built with a call to Build()
-  std::unique_ptr<Program> program;
-
- private:
-  std::unique_ptr<GeneratorImpl> gen_;
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace glsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_GLSL_TEST_HELPER_H_
diff --git a/src/writer/glsl/version.h b/src/writer/glsl/version.h
deleted file mode 100644
index 8bc0df9..0000000
--- a/src/writer/glsl/version.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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_WRITER_GLSL_VERSION_H_
-#define SRC_WRITER_GLSL_VERSION_H_
-
-#include <cstdint>
-
-namespace tint::writer::glsl {
-
-/// A structure representing the version of GLSL to be generated.
-struct Version {
-  /// Is this version desktop GLSL, or GLSL ES?
-  enum class Standard {
-    kDesktop,
-    kES,
-  };
-
-  /// Constructor
-  /// @param standard_ Desktop or ES
-  /// @param major_ the major version
-  /// @param minor_ the minor version
-  Version(Standard standard_, uint32_t major_, uint32_t minor_)
-      : standard(standard_), major_version(major_), minor_version(minor_) {}
-
-  /// Default constructor (see default values below)
-  Version() = default;
-
-  /// @returns true if this version is GLSL ES
-  bool IsES() const { return standard == Standard::kES; }
-
-  /// @returns true if this version is Desktop GLSL
-  bool IsDesktop() const { return standard == Standard::kDesktop; }
-
-  /// Desktop or ES
-  Standard standard = Standard::kES;
-
-  /// Major GLSL version
-  uint32_t major_version = 3;
-
-  /// Minor GLSL version
-  uint32_t minor_version = 1;
-};
-
-}  // namespace tint::writer::glsl
-
-#endif  // SRC_WRITER_GLSL_VERSION_H_
diff --git a/src/writer/hlsl/generator.cc b/src/writer/hlsl/generator.cc
deleted file mode 100644
index 727b5d5..0000000
--- a/src/writer/hlsl/generator.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 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/writer/hlsl/generator.h"
-
-#include "src/writer/hlsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-
-Options::Options() = default;
-Options::~Options() = default;
-Options::Options(const Options&) = default;
-Options& Options::operator=(const Options&) = default;
-
-Result::Result() = default;
-Result::~Result() = default;
-Result::Result(const Result&) = default;
-
-Result Generate(const Program* program, const Options& options) {
-  Result result;
-
-  // Sanitize the program.
-  auto sanitized_result = Sanitize(program, options.root_constant_binding_point,
-                                   options.disable_workgroup_init,
-                                   options.array_length_from_uniform);
-  if (!sanitized_result.program.IsValid()) {
-    result.success = false;
-    result.error = sanitized_result.program.Diagnostics().str();
-    return result;
-  }
-
-  // Generate the HLSL code.
-  auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program);
-  result.success = impl->Generate();
-  result.error = impl->error();
-  result.hlsl = impl->result();
-
-  // Collect the list of entry points in the sanitized program.
-  for (auto* func : sanitized_result.program.AST().Functions()) {
-    if (func->IsEntryPoint()) {
-      auto name = sanitized_result.program.Symbols().NameFor(func->symbol);
-      result.entry_points.push_back({name, func->PipelineStage()});
-    }
-  }
-
-  result.used_array_length_from_uniform_indices =
-      std::move(sanitized_result.used_array_length_from_uniform_indices);
-
-  return result;
-}
-
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator.h b/src/writer/hlsl/generator.h
deleted file mode 100644
index 4013cfa..0000000
--- a/src/writer/hlsl/generator.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2020 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_WRITER_HLSL_GENERATOR_H_
-#define SRC_WRITER_HLSL_GENERATOR_H_
-
-#include <memory>
-#include <string>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/pipeline_stage.h"
-#include "src/sem/binding_point.h"
-#include "src/writer/array_length_from_uniform_options.h"
-#include "src/writer/text.h"
-
-namespace tint {
-
-// Forward declarations
-class Program;
-
-namespace writer {
-namespace hlsl {
-
-// Forward declarations
-class GeneratorImpl;
-
-/// Configuration options used for generating HLSL.
-struct Options {
-  /// Constructor
-  Options();
-  /// Destructor
-  ~Options();
-  /// Copy constructor
-  Options(const Options&);
-  /// Copy assignment
-  /// @returns this Options
-  Options& operator=(const Options&);
-
-  /// The binding point to use for information passed via root constants.
-  sem::BindingPoint root_constant_binding_point;
-  /// Set to `true` to disable workgroup memory zero initialization
-  bool disable_workgroup_init = false;
-  /// Options used to specify a mapping of binding points to indices into a UBO
-  /// from which to load buffer sizes.
-  ArrayLengthFromUniformOptions array_length_from_uniform = {};
-
-  // NOTE: Update fuzzers/data_builder.h when adding or changing any struct
-  // members.
-};
-
-/// The result produced when generating HLSL.
-struct Result {
-  /// Constructor
-  Result();
-
-  /// Destructor
-  ~Result();
-
-  /// Copy constructor
-  Result(const Result&);
-
-  /// True if generation was successful.
-  bool success = false;
-
-  /// The errors generated during code generation, if any.
-  std::string error;
-
-  /// The generated HLSL.
-  std::string hlsl = "";
-
-  /// The list of entry points in the generated HLSL.
-  std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
-
-  /// Indices into the array_length_from_uniform binding that are statically
-  /// used.
-  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
-};
-
-/// Generate HLSL for a program, according to a set of configuration options.
-/// The result will contain the HLSL, as well as success status and diagnostic
-/// information.
-/// @param program the program to translate to HLSL
-/// @param options the configuration options to use when generating HLSL
-/// @returns the resulting HLSL and supplementary information
-Result Generate(const Program* program, const Options& options);
-
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_HLSL_GENERATOR_H_
diff --git a/src/writer/hlsl/generator_bench.cc b/src/writer/hlsl/generator_bench.cc
deleted file mode 100644
index 159105d..0000000
--- a/src/writer/hlsl/generator_bench.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 <string>
-
-#include "src/bench/benchmark.h"
-
-namespace tint::writer::hlsl {
-namespace {
-
-void GenerateHLSL(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadProgram(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& program = std::get<bench::ProgramAndFile>(res).program;
-  for (auto _ : state) {
-    auto res = Generate(&program, {});
-    if (!res.error.empty()) {
-      state.SkipWithError(res.error.c_str());
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(GenerateHLSL);
-
-}  // namespace
-}  // namespace tint::writer::hlsl
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
deleted file mode 100644
index ee275b5..0000000
--- a/src/writer/hlsl/generator_impl.cc
+++ /dev/null
@@ -1,4033 +0,0 @@
-/// Copyright 2020 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/writer/hlsl/generator_impl.h"
-
-#include <algorithm>
-#include <cmath>
-#include <functional>
-#include <iomanip>
-#include <set>
-#include <utility>
-#include <vector>
-
-#include "src/ast/call_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/debug.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/variable.h"
-#include "src/transform/add_empty_entry_point.h"
-#include "src/transform/array_length_from_uniform.h"
-#include "src/transform/calculate_array_length.h"
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/transform/decompose_memory_access.h"
-#include "src/transform/external_texture_transform.h"
-#include "src/transform/fold_trivial_single_use_lets.h"
-#include "src/transform/localize_struct_array_assignment.h"
-#include "src/transform/loop_to_for_loop.h"
-#include "src/transform/manager.h"
-#include "src/transform/num_workgroups_from_uniform.h"
-#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_initializers_to_const_var.h"
-#include "src/transform/remove_phonies.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/unshadow.h"
-#include "src/transform/zero_init_workgroup_memory.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/writer/append_vector.h"
-#include "src/writer/float_to_string.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-const char kTempNamePrefix[] = "tint_tmp";
-const char kSpecConstantPrefix[] = "WGSL_SPEC_CONSTANT_";
-
-const char* image_format_to_rwtexture_type(ast::TexelFormat image_format) {
-  switch (image_format) {
-    case ast::TexelFormat::kRgba8Unorm:
-    case ast::TexelFormat::kRgba8Snorm:
-    case ast::TexelFormat::kRgba16Float:
-    case ast::TexelFormat::kR32Float:
-    case ast::TexelFormat::kRg32Float:
-    case ast::TexelFormat::kRgba32Float:
-      return "float4";
-    case ast::TexelFormat::kRgba8Uint:
-    case ast::TexelFormat::kRgba16Uint:
-    case ast::TexelFormat::kR32Uint:
-    case ast::TexelFormat::kRg32Uint:
-    case ast::TexelFormat::kRgba32Uint:
-      return "uint4";
-    case ast::TexelFormat::kRgba8Sint:
-    case ast::TexelFormat::kRgba16Sint:
-    case ast::TexelFormat::kR32Sint:
-    case ast::TexelFormat::kRg32Sint:
-    case ast::TexelFormat::kRgba32Sint:
-      return "int4";
-    default:
-      return nullptr;
-  }
-}
-
-// Helper for writing " : register(RX, spaceY)", where R is the register, X is
-// the binding point binding value, and Y is the binding point group value.
-struct RegisterAndSpace {
-  RegisterAndSpace(char r, ast::VariableBindingPoint bp)
-      : reg(r), binding_point(bp) {}
-
-  const char reg;
-  ast::VariableBindingPoint const binding_point;
-};
-
-std::ostream& operator<<(std::ostream& s, const RegisterAndSpace& rs) {
-  s << " : register(" << rs.reg << rs.binding_point.binding->value << ", space"
-    << rs.binding_point.group->value << ")";
-  return s;
-}
-
-const char* LoopAttribute() {
-  // Force loops not to be unrolled to work around FXC compilation issues when
-  // it attempts and fails to unroll loops when it contains gradient operations.
-  // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-while
-  return "[loop] ";
-}
-
-}  // namespace
-
-SanitizedResult::SanitizedResult() = default;
-SanitizedResult::~SanitizedResult() = default;
-SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
-
-SanitizedResult Sanitize(
-    const Program* in,
-    sem::BindingPoint root_constant_binding_point,
-    bool disable_workgroup_init,
-    const ArrayLengthFromUniformOptions& array_length_from_uniform) {
-  transform::Manager manager;
-  transform::DataMap data;
-
-  // Build the config for the internal ArrayLengthFromUniform transform.
-  transform::ArrayLengthFromUniform::Config array_length_from_uniform_cfg(
-      array_length_from_uniform.ubo_binding);
-  array_length_from_uniform_cfg.bindpoint_to_size_index =
-      array_length_from_uniform.bindpoint_to_size_index;
-
-  manager.Add<transform::Unshadow>();
-
-  // LocalizeStructArrayAssignment must come after:
-  // * SimplifyPointers, because it assumes assignment to arrays in structs are
-  // done directly, not indirectly.
-  // TODO(crbug.com/tint/1340): See if we can get rid of the duplicate
-  // SimplifyPointers transform. Can't do it right now because
-  // LocalizeStructArrayAssignment introduces pointers.
-  manager.Add<transform::SimplifyPointers>();
-  manager.Add<transform::LocalizeStructArrayAssignment>();
-
-  // Attempt to convert `loop`s into for-loops. This is to try and massage the
-  // output into something that will not cause FXC to choke or misbehave.
-  manager.Add<transform::FoldTrivialSingleUseLets>();
-  manager.Add<transform::LoopToForLoop>();
-
-  if (!disable_workgroup_init) {
-    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
-    // ZeroInitWorkgroupMemory may inject new builtin parameters.
-    manager.Add<transform::ZeroInitWorkgroupMemory>();
-  }
-  manager.Add<transform::CanonicalizeEntryPointIO>();
-  // NumWorkgroupsFromUniform must come after CanonicalizeEntryPointIO, as it
-  // assumes that num_workgroups builtins only appear as struct members and are
-  // only accessed directly via member accessors.
-  manager.Add<transform::NumWorkgroupsFromUniform>();
-  manager.Add<transform::SimplifyPointers>();
-  manager.Add<transform::RemovePhonies>();
-  // ArrayLengthFromUniform must come after InlinePointerLets and Simplify, as
-  // it assumes that the form of the array length argument is &var.array.
-  manager.Add<transform::ArrayLengthFromUniform>();
-  data.Add<transform::ArrayLengthFromUniform::Config>(
-      std::move(array_length_from_uniform_cfg));
-  // DecomposeMemoryAccess must come after:
-  // * InlinePointerLets, as we cannot take the address of calls to
-  //   DecomposeMemoryAccess::Intrinsic.
-  // * Simplify, as we need to fold away the address-of and dereferences of
-  // `*(&(intrinsic_load()))` expressions.
-  // * RemovePhonies, as phonies can be assigned a pointer to a
-  //   non-constructible buffer, or dynamic array, which DMA cannot cope with.
-  manager.Add<transform::DecomposeMemoryAccess>();
-  // CalculateArrayLength must come after DecomposeMemoryAccess, as
-  // DecomposeMemoryAccess special-cases the arrayLength() intrinsic, which
-  // will be transformed by CalculateArrayLength
-  manager.Add<transform::CalculateArrayLength>();
-  manager.Add<transform::ExternalTextureTransform>();
-  manager.Add<transform::PromoteInitializersToConstVar>();
-
-  manager.Add<transform::PadArrayElements>();
-  manager.Add<transform::AddEmptyEntryPoint>();
-
-  data.Add<transform::CanonicalizeEntryPointIO::Config>(
-      transform::CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
-  data.Add<transform::NumWorkgroupsFromUniform::Config>(
-      root_constant_binding_point);
-
-  auto out = manager.Run(in, data);
-
-  SanitizedResult result;
-  result.program = std::move(out.program);
-  if (auto* res = out.data.Get<transform::ArrayLengthFromUniform::Result>()) {
-    result.used_array_length_from_uniform_indices =
-        std::move(res->used_size_indices);
-  }
-  return result;
-}
-
-GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
-
-GeneratorImpl::~GeneratorImpl() = default;
-
-bool GeneratorImpl::Generate() {
-  const TypeInfo* last_kind = nullptr;
-  size_t last_padding_line = 0;
-
-  auto* mod = builder_.Sem().Module();
-  for (auto* decl : mod->DependencyOrderedDeclarations()) {
-    if (decl->Is<ast::Alias>()) {
-      continue;  // Ignore aliases.
-    }
-
-    // Emit a new line between declarations if the type of declaration has
-    // changed, or we're about to emit a function
-    auto* kind = &decl->TypeInfo();
-    if (current_buffer_->lines.size() != last_padding_line) {
-      if (last_kind && (last_kind != kind || decl->Is<ast::Function>())) {
-        line();
-        last_padding_line = current_buffer_->lines.size();
-      }
-    }
-    last_kind = kind;
-
-    bool ok = Switch(
-        decl,
-        [&](const ast::Variable* global) {  //
-          return EmitGlobalVariable(global);
-        },
-        [&](const ast::Struct* str) {
-          auto* ty = builder_.Sem().Get(str);
-          auto storage_class_uses = ty->StorageClassUsage();
-          if (storage_class_uses.size() !=
-              (storage_class_uses.count(ast::StorageClass::kStorage) +
-               storage_class_uses.count(ast::StorageClass::kUniform))) {
-            // The structure is used as something other than a storage buffer or
-            // uniform buffer, so it needs to be emitted.
-            // Storage buffer are read and written to via a ByteAddressBuffer
-            // instead of true structure.
-            // Structures used as uniform buffer are read from an array of
-            // vectors instead of true structure.
-            return EmitStructType(current_buffer_, ty);
-          }
-          return true;
-        },
-        [&](const ast::Function* func) {
-          if (func->IsEntryPoint()) {
-            return EmitEntryPointFunction(func);
-          }
-          return EmitFunction(func);
-        },
-        [&](Default) {
-          TINT_ICE(Writer, diagnostics_)
-              << "unhandled module-scope declaration: "
-              << decl->TypeInfo().name;
-          return false;
-        });
-
-    if (!ok) {
-      return false;
-    }
-  }
-
-  if (!helpers_.lines.empty()) {
-    current_buffer_->Insert(helpers_, 0, 0);
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDynamicVectorAssignment(
-    const ast::AssignmentStatement* stmt,
-    const sem::Vector* vec) {
-  auto name =
-      utils::GetOrCreate(dynamic_vector_write_, vec, [&]() -> std::string {
-        std::string fn;
-        {
-          std::ostringstream ss;
-          if (!EmitType(ss, vec, tint::ast::StorageClass::kInvalid,
-                        ast::Access::kUndefined, "")) {
-            return "";
-          }
-          fn = UniqueIdentifier("set_" + ss.str());
-        }
-        {
-          auto out = line(&helpers_);
-          out << "void " << fn << "(inout ";
-          if (!EmitTypeAndName(out, vec, ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "vec")) {
-            return "";
-          }
-          out << ", int idx, ";
-          if (!EmitTypeAndName(out, vec->type(), ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "val")) {
-            return "";
-          }
-          out << ") {";
-        }
-        {
-          ScopedIndent si(&helpers_);
-          auto out = line(&helpers_);
-          switch (vec->Width()) {
-            case 2:
-              out << "vec = (idx.xx == int2(0, 1)) ? val.xx : vec;";
-              break;
-            case 3:
-              out << "vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;";
-              break;
-            case 4:
-              out << "vec = (idx.xxxx == int4(0, 1, 2, 3)) ? val.xxxx : vec;";
-              break;
-            default:
-              TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-                  << "invalid vector size " << vec->Width();
-              break;
-          }
-        }
-        line(&helpers_) << "}";
-        line(&helpers_);
-        return fn;
-      });
-
-  if (name.empty()) {
-    return false;
-  }
-
-  auto* ast_access_expr = stmt->lhs->As<ast::IndexAccessorExpression>();
-
-  auto out = line();
-  out << name << "(";
-  if (!EmitExpression(out, ast_access_expr->object)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, ast_access_expr->index)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-  out << ");";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDynamicMatrixVectorAssignment(
-    const ast::AssignmentStatement* stmt,
-    const sem::Matrix* mat) {
-  auto name = utils::GetOrCreate(
-      dynamic_matrix_vector_write_, mat, [&]() -> std::string {
-        std::string fn;
-        {
-          std::ostringstream ss;
-          if (!EmitType(ss, mat, tint::ast::StorageClass::kInvalid,
-                        ast::Access::kUndefined, "")) {
-            return "";
-          }
-          fn = UniqueIdentifier("set_vector_" + ss.str());
-        }
-        {
-          auto out = line(&helpers_);
-          out << "void " << fn << "(inout ";
-          if (!EmitTypeAndName(out, mat, ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "mat")) {
-            return "";
-          }
-          out << ", int col, ";
-          if (!EmitTypeAndName(out, mat->ColumnType(),
-                               ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "val")) {
-            return "";
-          }
-          out << ") {";
-        }
-        {
-          ScopedIndent si(&helpers_);
-          line(&helpers_) << "switch (col) {";
-          {
-            ScopedIndent si2(&helpers_);
-            for (uint32_t i = 0; i < mat->columns(); ++i) {
-              line(&helpers_)
-                  << "case " << i << ": mat[" << i << "] = val; break;";
-            }
-          }
-          line(&helpers_) << "}";
-        }
-        line(&helpers_) << "}";
-        line(&helpers_);
-        return fn;
-      });
-
-  if (name.empty()) {
-    return false;
-  }
-
-  auto* ast_access_expr = stmt->lhs->As<ast::IndexAccessorExpression>();
-
-  auto out = line();
-  out << name << "(";
-  if (!EmitExpression(out, ast_access_expr->object)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, ast_access_expr->index)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-  out << ");";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDynamicMatrixScalarAssignment(
-    const ast::AssignmentStatement* stmt,
-    const sem::Matrix* mat) {
-  auto* lhs_col_access = stmt->lhs->As<ast::IndexAccessorExpression>();
-  auto* lhs_row_access =
-      lhs_col_access->object->As<ast::IndexAccessorExpression>();
-
-  auto name = utils::GetOrCreate(
-      dynamic_matrix_scalar_write_, mat, [&]() -> std::string {
-        std::string fn;
-        {
-          std::ostringstream ss;
-          if (!EmitType(ss, mat, tint::ast::StorageClass::kInvalid,
-                        ast::Access::kUndefined, "")) {
-            return "";
-          }
-          fn = UniqueIdentifier("set_scalar_" + ss.str());
-        }
-        {
-          auto out = line(&helpers_);
-          out << "void " << fn << "(inout ";
-          if (!EmitTypeAndName(out, mat, ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "mat")) {
-            return "";
-          }
-          out << ", int col, int row, ";
-          if (!EmitTypeAndName(out, mat->type(), ast::StorageClass::kInvalid,
-                               ast::Access::kUndefined, "val")) {
-            return "";
-          }
-          out << ") {";
-        }
-        {
-          ScopedIndent si(&helpers_);
-          line(&helpers_) << "switch (col) {";
-          {
-            ScopedIndent si2(&helpers_);
-            auto* vec =
-                TypeOf(lhs_row_access->object)->UnwrapRef()->As<sem::Vector>();
-            for (uint32_t i = 0; i < mat->columns(); ++i) {
-              line(&helpers_) << "case " << i << ":";
-              {
-                auto vec_name = "mat[" + std::to_string(i) + "]";
-                ScopedIndent si3(&helpers_);
-                {
-                  auto out = line(&helpers_);
-                  switch (mat->rows()) {
-                    case 2:
-                      out << vec_name
-                          << " = (row.xx == int2(0, 1)) ? val.xx : " << vec_name
-                          << ";";
-                      break;
-                    case 3:
-                      out << vec_name
-                          << " = (row.xxx == int3(0, 1, 2)) ? val.xxx : "
-                          << vec_name << ";";
-                      break;
-                    case 4:
-                      out << vec_name
-                          << " = (row.xxxx == int4(0, 1, 2, 3)) ? val.xxxx : "
-                          << vec_name << ";";
-                      break;
-                    default:
-                      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-                          << "invalid vector size " << vec->Width();
-                      break;
-                  }
-                }
-                line(&helpers_) << "break;";
-              }
-            }
-          }
-          line(&helpers_) << "}";
-        }
-        line(&helpers_) << "}";
-        line(&helpers_);
-        return fn;
-      });
-
-  if (name.empty()) {
-    return false;
-  }
-
-  auto out = line();
-  out << name << "(";
-  if (!EmitExpression(out, lhs_row_access->object)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, lhs_col_access->index)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, lhs_row_access->index)) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-  out << ");";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitIndexAccessor(
-    std::ostream& out,
-    const ast::IndexAccessorExpression* expr) {
-  if (!EmitExpression(out, expr->object)) {
-    return false;
-  }
-  out << "[";
-
-  if (!EmitExpression(out, expr->index)) {
-    return false;
-  }
-  out << "]";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBitcast(std::ostream& out,
-                                const ast::BitcastExpression* expr) {
-  auto* type = TypeOf(expr);
-  if (auto* vec = type->UnwrapRef()->As<sem::Vector>()) {
-    type = vec->type();
-  }
-
-  if (!type->is_integer_scalar() && !type->is_float_scalar()) {
-    diagnostics_.add_error(diag::System::Writer,
-                           "Unable to do bitcast to type " + type->type_name());
-    return false;
-  }
-
-  out << "as";
-  if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
-                "")) {
-    return false;
-  }
-  out << "(";
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
-  if (auto* lhs_access = stmt->lhs->As<ast::IndexAccessorExpression>()) {
-    // BUG(crbug.com/tint/1333): work around assignment of scalar to matrices
-    // with at least one dynamic index
-    if (auto* lhs_sub_access =
-            lhs_access->object->As<ast::IndexAccessorExpression>()) {
-      if (auto* mat =
-              TypeOf(lhs_sub_access->object)->UnwrapRef()->As<sem::Matrix>()) {
-        auto* rhs_col_idx_sem = builder_.Sem().Get(lhs_access->index);
-        auto* rhs_row_idx_sem = builder_.Sem().Get(lhs_sub_access->index);
-        if (!rhs_col_idx_sem->ConstantValue().IsValid() ||
-            !rhs_row_idx_sem->ConstantValue().IsValid()) {
-          return EmitDynamicMatrixScalarAssignment(stmt, mat);
-        }
-      }
-    }
-    // BUG(crbug.com/tint/1333): work around assignment of vector to matrices
-    // with dynamic indices
-    const auto* lhs_access_type = TypeOf(lhs_access->object)->UnwrapRef();
-    if (auto* mat = lhs_access_type->As<sem::Matrix>()) {
-      auto* lhs_index_sem = builder_.Sem().Get(lhs_access->index);
-      if (!lhs_index_sem->ConstantValue().IsValid()) {
-        return EmitDynamicMatrixVectorAssignment(stmt, mat);
-      }
-    }
-    // BUG(crbug.com/tint/534): work around assignment to vectors with dynamic
-    // indices
-    if (auto* vec = lhs_access_type->As<sem::Vector>()) {
-      auto* rhs_sem = builder_.Sem().Get(lhs_access->index);
-      if (!rhs_sem->ConstantValue().IsValid()) {
-        return EmitDynamicVectorAssignment(stmt, vec);
-      }
-    }
-  }
-
-  auto out = line();
-  if (!EmitExpression(out, stmt->lhs)) {
-    return false;
-  }
-  out << " = ";
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitExpressionOrOneIfZero(std::ostream& out,
-                                              const ast::Expression* expr) {
-  // For constants, replace literal 0 with 1.
-  sem::Constant::Scalars elems;
-  if (const auto& val = builder_.Sem().Get(expr)->ConstantValue()) {
-    if (!val.AnyZero()) {
-      return EmitExpression(out, expr);
-    }
-
-    if (val.Type()->IsAnyOf<sem::I32, sem::U32>()) {
-      return EmitValue(out, val.Type(), 1);
-    }
-
-    if (auto* vec = val.Type()->As<sem::Vector>()) {
-      auto* elem_ty = vec->type();
-
-      if (!EmitType(out, val.Type(), ast::StorageClass::kNone,
-                    ast::Access::kUndefined, "")) {
-        return false;
-      }
-
-      out << "(";
-      for (size_t i = 0; i < val.Elements().size(); ++i) {
-        if (i != 0) {
-          out << ", ";
-        }
-        if (!val.WithScalarAt(i, [&](auto&& s) -> bool {
-              // Use std::equal_to to work around -Wfloat-equal warnings
-              auto equals_to =
-                  std::equal_to<std::remove_reference_t<decltype(s)>>{};
-
-              bool is_zero = equals_to(s, 0);
-              return EmitValue(out, elem_ty, is_zero ? 1 : static_cast<int>(s));
-            })) {
-          return false;
-        }
-      }
-      out << ")";
-      return true;
-    }
-
-    TINT_ICE(Writer, diagnostics_)
-        << "EmitExpressionOrOneIfZero expects integer scalar or vector";
-    return false;
-  }
-
-  auto* ty = TypeOf(expr)->UnwrapRef();
-
-  // For non-constants, we need to emit runtime code to check if the value is 0,
-  // and return 1 in that case.
-  std::string zero;
-  {
-    std::ostringstream ss;
-    EmitValue(ss, ty, 0);
-    zero = ss.str();
-  }
-  std::string one;
-  {
-    std::ostringstream ss;
-    EmitValue(ss, ty, 1);
-    one = ss.str();
-  }
-
-  // For identifiers, no need for a function call as it's fine to evaluate
-  // `expr` more than once.
-  if (expr->Is<ast::IdentifierExpression>()) {
-    out << "(";
-    if (!EmitExpression(out, expr)) {
-      return false;
-    }
-    out << " == " << zero << " ? " << one << " : ";
-    if (!EmitExpression(out, expr)) {
-      return false;
-    }
-    out << ")";
-    return true;
-  }
-
-  // For non-identifier expressions, call a function to make sure `expr` is only
-  // evaluated once.
-  auto name =
-      utils::GetOrCreate(value_or_one_if_zero_, ty, [&]() -> std::string {
-        // Example:
-        // int4 tint_value_or_one_if_zero_int4(int4 value) {
-        //   return value == 0 ? 0 : value;
-        // }
-        std::string ty_name;
-        {
-          std::ostringstream ss;
-          if (!EmitType(ss, ty, tint::ast::StorageClass::kInvalid,
-                        ast::Access::kUndefined, "")) {
-            return "";
-          }
-          ty_name = ss.str();
-        }
-
-        std::string fn = UniqueIdentifier("value_or_one_if_zero_" + ty_name);
-        line(&helpers_) << ty_name << " " << fn << "(" << ty_name
-                        << " value) {";
-        {
-          ScopedIndent si(&helpers_);
-          line(&helpers_) << "return value == " << zero << " ? " << one
-                          << " : value;";
-        }
-        line(&helpers_) << "}";
-        line(&helpers_);
-        return fn;
-      });
-
-  if (name.empty()) {
-    return false;
-  }
-
-  out << name << "(";
-  if (!EmitExpression(out, expr)) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitBinary(std::ostream& out,
-                               const ast::BinaryExpression* expr) {
-  if (expr->op == ast::BinaryOp::kLogicalAnd ||
-      expr->op == ast::BinaryOp::kLogicalOr) {
-    auto name = UniqueIdentifier(kTempNamePrefix);
-
-    {
-      auto pre = line();
-      pre << "bool " << name << " = ";
-      if (!EmitExpression(pre, expr->lhs)) {
-        return false;
-      }
-      pre << ";";
-    }
-
-    if (expr->op == ast::BinaryOp::kLogicalOr) {
-      line() << "if (!" << name << ") {";
-    } else {
-      line() << "if (" << name << ") {";
-    }
-
-    {
-      ScopedIndent si(this);
-      auto pre = line();
-      pre << name << " = ";
-      if (!EmitExpression(pre, expr->rhs)) {
-        return false;
-      }
-      pre << ";";
-    }
-
-    line() << "}";
-
-    out << "(" << name << ")";
-    return true;
-  }
-
-  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
-  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
-  // Multiplying by a matrix requires the use of `mul` in order to get the
-  // type of multiply we desire.
-  if (expr->op == ast::BinaryOp::kMultiply &&
-      ((lhs_type->Is<sem::Vector>() && rhs_type->Is<sem::Matrix>()) ||
-       (lhs_type->Is<sem::Matrix>() && rhs_type->Is<sem::Vector>()) ||
-       (lhs_type->Is<sem::Matrix>() && rhs_type->Is<sem::Matrix>()))) {
-    // Matrices are transposed, so swap LHS and RHS.
-    out << "mul(";
-    if (!EmitExpression(out, expr->rhs)) {
-      return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, expr->lhs)) {
-      return false;
-    }
-    out << ")";
-
-    return true;
-  }
-
-  out << "(";
-  TINT_DEFER(out << ")");
-
-  if (!EmitExpression(out, expr->lhs)) {
-    return false;
-  }
-  out << " ";
-
-  switch (expr->op) {
-    case ast::BinaryOp::kAnd:
-      out << "&";
-      break;
-    case ast::BinaryOp::kOr:
-      out << "|";
-      break;
-    case ast::BinaryOp::kXor:
-      out << "^";
-      break;
-    case ast::BinaryOp::kLogicalAnd:
-    case ast::BinaryOp::kLogicalOr: {
-      // These are both handled above.
-      TINT_UNREACHABLE(Writer, diagnostics_);
-      return false;
-    }
-    case ast::BinaryOp::kEqual:
-      out << "==";
-      break;
-    case ast::BinaryOp::kNotEqual:
-      out << "!=";
-      break;
-    case ast::BinaryOp::kLessThan:
-      out << "<";
-      break;
-    case ast::BinaryOp::kGreaterThan:
-      out << ">";
-      break;
-    case ast::BinaryOp::kLessThanEqual:
-      out << "<=";
-      break;
-    case ast::BinaryOp::kGreaterThanEqual:
-      out << ">=";
-      break;
-    case ast::BinaryOp::kShiftLeft:
-      out << "<<";
-      break;
-    case ast::BinaryOp::kShiftRight:
-      // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
-      // implementation-defined behaviour for negative LHS.  We may have to
-      // generate extra code to implement WGSL-specified behaviour for negative
-      // LHS.
-      out << R"(>>)";
-      break;
-
-    case ast::BinaryOp::kAdd:
-      out << "+";
-      break;
-    case ast::BinaryOp::kSubtract:
-      out << "-";
-      break;
-    case ast::BinaryOp::kMultiply:
-      out << "*";
-      break;
-    case ast::BinaryOp::kDivide:
-      out << "/";
-      // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
-      // compile error, and undefined behavior in WGSL.
-      if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
-        out << " ";
-        return EmitExpressionOrOneIfZero(out, expr->rhs);
-      }
-      break;
-    case ast::BinaryOp::kModulo:
-      out << "%";
-      // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
-      // compile error, and undefined behavior in WGSL.
-      if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
-        out << " ";
-        return EmitExpressionOrOneIfZero(out, expr->rhs);
-      }
-      break;
-    case ast::BinaryOp::kNone:
-      diagnostics_.add_error(diag::System::Writer,
-                             "missing binary operation type");
-      return false;
-  }
-  out << " ";
-
-  if (!EmitExpression(out, expr->rhs)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
-  for (auto* s : stmts) {
-    if (!EmitStatement(s)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
-  ScopedIndent si(this);
-  return EmitStatements(stmts);
-}
-
-bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
-  line() << "{";
-  if (!EmitStatementsWithIndent(stmt->statements)) {
-    return false;
-  }
-  line() << "}";
-  return true;
-}
-
-bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
-  line() << "break;";
-  return true;
-}
-
-bool GeneratorImpl::EmitCall(std::ostream& out,
-                             const ast::CallExpression* expr) {
-  auto* call = builder_.Sem().Get(expr);
-  auto* target = call->Target();
-  return Switch(
-      target,
-      [&](const sem::Function* func) {
-        return EmitFunctionCall(out, call, func);
-      },
-      [&](const sem::Builtin* builtin) {
-        return EmitBuiltinCall(out, call, builtin);
-      },
-      [&](const sem::TypeConversion* conv) {
-        return EmitTypeConversion(out, call, conv);
-      },
-      [&](const sem::TypeConstructor* ctor) {
-        return EmitTypeConstructor(out, call, ctor);
-      },
-      [&](Default) {
-        TINT_ICE(Writer, diagnostics_)
-            << "unhandled call target: " << target->TypeInfo().name;
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
-                                     const sem::Call* call,
-                                     const sem::Function* func) {
-  auto* expr = call->Declaration();
-
-  if (ast::HasAttribute<transform::CalculateArrayLength::BufferSizeIntrinsic>(
-          func->Declaration()->attributes)) {
-    // Special function generated by the CalculateArrayLength transform for
-    // calling X.GetDimensions(Y)
-    if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
-      return false;
-    }
-    out << ".GetDimensions(";
-    if (!EmitExpression(out, call->Arguments()[1]->Declaration())) {
-      return false;
-    }
-    out << ")";
-    return true;
-  }
-
-  if (auto* intrinsic =
-          ast::GetAttribute<transform::DecomposeMemoryAccess::Intrinsic>(
-              func->Declaration()->attributes)) {
-    switch (intrinsic->storage_class) {
-      case ast::StorageClass::kUniform:
-        return EmitUniformBufferAccess(out, expr, intrinsic);
-      case ast::StorageClass::kStorage:
-        return EmitStorageBufferAccess(out, expr, intrinsic);
-      default:
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "unsupported DecomposeMemoryAccess::Intrinsic storage class:"
-            << intrinsic->storage_class;
-        return false;
-    }
-  }
-
-  out << builder_.Symbols().NameFor(func->Declaration()->symbol) << "(";
-
-  bool first = true;
-  for (auto* arg : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  auto* expr = call->Declaration();
-  if (builtin->IsTexture()) {
-    return EmitTextureCall(out, call, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kSelect) {
-    return EmitSelectCall(out, expr);
-  }
-  if (builtin->Type() == sem::BuiltinType::kModf) {
-    return EmitModfCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kFrexp) {
-    return EmitFrexpCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kIsNormal) {
-    return EmitIsNormalCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kDegrees) {
-    return EmitDegreesCall(out, expr, builtin);
-  }
-  if (builtin->Type() == sem::BuiltinType::kRadians) {
-    return EmitRadiansCall(out, expr, builtin);
-  }
-  if (builtin->IsDataPacking()) {
-    return EmitDataPackingCall(out, expr, builtin);
-  }
-  if (builtin->IsDataUnpacking()) {
-    return EmitDataUnpackingCall(out, expr, builtin);
-  }
-  if (builtin->IsBarrier()) {
-    return EmitBarrierCall(out, builtin);
-  }
-  if (builtin->IsAtomic()) {
-    return EmitWorkgroupAtomicCall(out, expr, builtin);
-  }
-  auto name = generate_builtin_name(builtin);
-  if (name.empty()) {
-    return false;
-  }
-
-  out << name << "(";
-
-  bool first = true;
-  for (auto* arg : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
-                                       const sem::Call* call,
-                                       const sem::TypeConversion* conv) {
-  if (!EmitType(out, conv->Target(), ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
-                                        const sem::Call* call,
-                                        const sem::TypeConstructor* ctor) {
-  auto* type = call->Type();
-
-  // If the type constructor is empty then we need to construct with the zero
-  // value for all components.
-  if (call->Arguments().empty()) {
-    return EmitZeroValue(out, type);
-  }
-
-  bool brackets = type->IsAnyOf<sem::Array, sem::Struct>();
-
-  // For single-value vector initializers, swizzle the scalar to the right
-  // vector dimension using .x
-  const bool is_single_value_vector_init =
-      type->is_scalar_vector() && call->Arguments().size() == 1 &&
-      ctor->Parameters()[0]->Type()->is_scalar();
-
-  auto it = structure_builders_.find(As<sem::Struct>(type));
-  if (it != structure_builders_.end()) {
-    out << it->second << "(";
-    brackets = false;
-  } else if (brackets) {
-    out << "{";
-  } else {
-    if (!EmitType(out, type, ast::StorageClass::kNone, ast::Access::kReadWrite,
-                  "")) {
-      return false;
-    }
-    out << "(";
-  }
-
-  if (is_single_value_vector_init) {
-    out << "(";
-  }
-
-  bool first = true;
-  for (auto* e : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, e->Declaration())) {
-      return false;
-    }
-  }
-
-  if (is_single_value_vector_init) {
-    out << ")." << std::string(type->As<sem::Vector>()->Width(), 'x');
-  }
-
-  out << (brackets ? "}" : ")");
-  return true;
-}
-
-bool GeneratorImpl::EmitUniformBufferAccess(
-    std::ostream& out,
-    const ast::CallExpression* expr,
-    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
-  const auto& args = expr->args;
-  auto* offset_arg = builder_.Sem().Get(args[1]);
-
-  uint32_t scalar_offset_value = 0;
-  std::string scalar_offset_expr;
-
-  // If true, use scalar_offset_value, otherwise use scalar_offset_expr
-  bool scalar_offset_constant = false;
-
-  if (auto val = offset_arg->ConstantValue()) {
-    TINT_ASSERT(Writer, val.Type()->Is<sem::U32>());
-    scalar_offset_value = val.Elements()[0].u32;
-    scalar_offset_value /= 4;  // bytes -> scalar index
-    scalar_offset_constant = true;
-  }
-
-  if (!scalar_offset_constant) {
-    // UBO offset not compile-time known.
-    // Calculate the scalar offset into a temporary.
-    scalar_offset_expr = UniqueIdentifier("scalar_offset");
-    auto pre = line();
-    pre << "const uint " << scalar_offset_expr << " = (";
-    if (!EmitExpression(pre, args[1])) {  // offset
-      return false;
-    }
-    pre << ") / 4;";
-  }
-
-  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
-  using DataType = transform::DecomposeMemoryAccess::Intrinsic::DataType;
-  switch (intrinsic->op) {
-    case Op::kLoad: {
-      auto cast = [&](const char* to, auto&& load) {
-        out << to << "(";
-        auto result = load();
-        out << ")";
-        return result;
-      };
-      auto load_scalar = [&]() {
-        if (!EmitExpression(out, args[0])) {  // buffer
-          return false;
-        }
-        if (scalar_offset_constant) {
-          char swizzle[] = {'x', 'y', 'z', 'w'};
-          out << "[" << (scalar_offset_value / 4) << "]."
-              << swizzle[scalar_offset_value & 3];
-        } else {
-          out << "[" << scalar_offset_expr << " / 4][" << scalar_offset_expr
-              << " % 4]";
-        }
-        return true;
-      };
-      // Has a minimum alignment of 8 bytes, so is either .xy or .zw
-      auto load_vec2 = [&] {
-        if (scalar_offset_constant) {
-          if (!EmitExpression(out, args[0])) {  // buffer
-            return false;
-          }
-          out << "[" << (scalar_offset_value / 4) << "]";
-          out << ((scalar_offset_value & 2) == 0 ? ".xy" : ".zw");
-        } else {
-          std::string ubo_load = UniqueIdentifier("ubo_load");
-          {
-            auto pre = line();
-            pre << "uint4 " << ubo_load << " = ";
-            if (!EmitExpression(pre, args[0])) {  // buffer
-              return false;
-            }
-            pre << "[" << scalar_offset_expr << " / 4];";
-          }
-          out << "((" << scalar_offset_expr << " & 2) ? " << ubo_load
-              << ".zw : " << ubo_load << ".xy)";
-        }
-        return true;
-      };
-      // vec4 has a minimum alignment of 16 bytes, easiest case
-      auto load_vec4 = [&] {
-        if (!EmitExpression(out, args[0])) {  // buffer
-          return false;
-        }
-        if (scalar_offset_constant) {
-          out << "[" << (scalar_offset_value / 4) << "]";
-        } else {
-          out << "[" << scalar_offset_expr << " / 4]";
-        }
-        return true;
-      };
-      // vec3 has a minimum alignment of 16 bytes, so is just a .xyz swizzle
-      auto load_vec3 = [&] {
-        if (!load_vec4()) {
-          return false;
-        }
-        out << ".xyz";
-        return true;
-      };
-      switch (intrinsic->type) {
-        case DataType::kU32:
-          return load_scalar();
-        case DataType::kF32:
-          return cast("asfloat", load_scalar);
-        case DataType::kI32:
-          return cast("asint", load_scalar);
-        case DataType::kVec2U32:
-          return load_vec2();
-        case DataType::kVec2F32:
-          return cast("asfloat", load_vec2);
-        case DataType::kVec2I32:
-          return cast("asint", load_vec2);
-        case DataType::kVec3U32:
-          return load_vec3();
-        case DataType::kVec3F32:
-          return cast("asfloat", load_vec3);
-        case DataType::kVec3I32:
-          return cast("asint", load_vec3);
-        case DataType::kVec4U32:
-          return load_vec4();
-        case DataType::kVec4F32:
-          return cast("asfloat", load_vec4);
-        case DataType::kVec4I32:
-          return cast("asint", load_vec4);
-      }
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
-          << static_cast<int>(intrinsic->type);
-      return false;
-    }
-    default:
-      break;
-  }
-  TINT_UNREACHABLE(Writer, diagnostics_)
-      << "unsupported DecomposeMemoryAccess::Intrinsic::Op: "
-      << static_cast<int>(intrinsic->op);
-  return false;
-}
-
-bool GeneratorImpl::EmitStorageBufferAccess(
-    std::ostream& out,
-    const ast::CallExpression* expr,
-    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
-  const auto& args = expr->args;
-
-  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
-  using DataType = transform::DecomposeMemoryAccess::Intrinsic::DataType;
-  switch (intrinsic->op) {
-    case Op::kLoad: {
-      auto load = [&](const char* cast, int n) {
-        if (cast) {
-          out << cast << "(";
-        }
-        if (!EmitExpression(out, args[0])) {  // buffer
-          return false;
-        }
-        out << ".Load";
-        if (n > 1) {
-          out << n;
-        }
-        ScopedParen sp(out);
-        if (!EmitExpression(out, args[1])) {  // offset
-          return false;
-        }
-        if (cast) {
-          out << ")";
-        }
-        return true;
-      };
-      switch (intrinsic->type) {
-        case DataType::kU32:
-          return load(nullptr, 1);
-        case DataType::kF32:
-          return load("asfloat", 1);
-        case DataType::kI32:
-          return load("asint", 1);
-        case DataType::kVec2U32:
-          return load(nullptr, 2);
-        case DataType::kVec2F32:
-          return load("asfloat", 2);
-        case DataType::kVec2I32:
-          return load("asint", 2);
-        case DataType::kVec3U32:
-          return load(nullptr, 3);
-        case DataType::kVec3F32:
-          return load("asfloat", 3);
-        case DataType::kVec3I32:
-          return load("asint", 3);
-        case DataType::kVec4U32:
-          return load(nullptr, 4);
-        case DataType::kVec4F32:
-          return load("asfloat", 4);
-        case DataType::kVec4I32:
-          return load("asint", 4);
-      }
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
-          << static_cast<int>(intrinsic->type);
-      return false;
-    }
-
-    case Op::kStore: {
-      auto store = [&](int n) {
-        if (!EmitExpression(out, args[0])) {  // buffer
-          return false;
-        }
-        out << ".Store";
-        if (n > 1) {
-          out << n;
-        }
-        ScopedParen sp1(out);
-        if (!EmitExpression(out, args[1])) {  // offset
-          return false;
-        }
-        out << ", asuint";
-        ScopedParen sp2(out);
-        if (!EmitExpression(out, args[2])) {  // value
-          return false;
-        }
-        return true;
-      };
-      switch (intrinsic->type) {
-        case DataType::kU32:
-          return store(1);
-        case DataType::kF32:
-          return store(1);
-        case DataType::kI32:
-          return store(1);
-        case DataType::kVec2U32:
-          return store(2);
-        case DataType::kVec2F32:
-          return store(2);
-        case DataType::kVec2I32:
-          return store(2);
-        case DataType::kVec3U32:
-          return store(3);
-        case DataType::kVec3F32:
-          return store(3);
-        case DataType::kVec3I32:
-          return store(3);
-        case DataType::kVec4U32:
-          return store(4);
-        case DataType::kVec4F32:
-          return store(4);
-        case DataType::kVec4I32:
-          return store(4);
-      }
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "unsupported DecomposeMemoryAccess::Intrinsic::DataType: "
-          << static_cast<int>(intrinsic->type);
-      return false;
-    }
-
-    case Op::kAtomicLoad:
-    case Op::kAtomicStore:
-    case Op::kAtomicAdd:
-    case Op::kAtomicSub:
-    case Op::kAtomicMax:
-    case Op::kAtomicMin:
-    case Op::kAtomicAnd:
-    case Op::kAtomicOr:
-    case Op::kAtomicXor:
-    case Op::kAtomicExchange:
-    case Op::kAtomicCompareExchangeWeak:
-      return EmitStorageAtomicCall(out, expr, intrinsic);
-  }
-
-  TINT_UNREACHABLE(Writer, diagnostics_)
-      << "unsupported DecomposeMemoryAccess::Intrinsic::Op: "
-      << static_cast<int>(intrinsic->op);
-  return false;
-}
-
-bool GeneratorImpl::EmitStorageAtomicCall(
-    std::ostream& out,
-    const ast::CallExpression* expr,
-    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
-  using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
-
-  auto* result_ty = TypeOf(expr);
-
-  auto& buf = helpers_;
-
-  // generate_helper() generates a helper function that translates the
-  // DecomposeMemoryAccess::Intrinsic call into the corresponding HLSL
-  // atomic intrinsic function.
-  auto generate_helper = [&]() -> std::string {
-    auto rmw = [&](const char* wgsl, const char* hlsl) -> std::string {
-      auto name = UniqueIdentifier(wgsl);
-      {
-        auto fn = line(&buf);
-        if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                             ast::Access::kUndefined, name)) {
-          return "";
-        }
-        fn << "(RWByteAddressBuffer buffer, uint offset, ";
-        if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                             ast::Access::kUndefined, "value")) {
-          return "";
-        }
-        fn << ") {";
-      }
-
-      buf.IncrementIndent();
-      TINT_DEFER({
-        buf.DecrementIndent();
-        line(&buf) << "}";
-        line(&buf);
-      });
-
-      {
-        auto l = line(&buf);
-        if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                             ast::Access::kUndefined, "original_value")) {
-          return "";
-        }
-        l << " = 0;";
-      }
-      {
-        auto l = line(&buf);
-        l << "buffer." << hlsl << "(offset, ";
-        if (intrinsic->op == Op::kAtomicSub) {
-          l << "-";
-        }
-        l << "value, original_value);";
-      }
-      line(&buf) << "return original_value;";
-      return name;
-    };
-
-    switch (intrinsic->op) {
-      case Op::kAtomicAdd:
-        return rmw("atomicAdd", "InterlockedAdd");
-
-      case Op::kAtomicSub:
-        // Use add with the operand negated.
-        return rmw("atomicSub", "InterlockedAdd");
-
-      case Op::kAtomicMax:
-        return rmw("atomicMax", "InterlockedMax");
-
-      case Op::kAtomicMin:
-        return rmw("atomicMin", "InterlockedMin");
-
-      case Op::kAtomicAnd:
-        return rmw("atomicAnd", "InterlockedAnd");
-
-      case Op::kAtomicOr:
-        return rmw("atomicOr", "InterlockedOr");
-
-      case Op::kAtomicXor:
-        return rmw("atomicXor", "InterlockedXor");
-
-      case Op::kAtomicExchange:
-        return rmw("atomicExchange", "InterlockedExchange");
-
-      case Op::kAtomicLoad: {
-        // HLSL does not have an InterlockedLoad, so we emulate it with
-        // InterlockedOr using 0 as the OR value
-        auto name = UniqueIdentifier("atomicLoad");
-        {
-          auto fn = line(&buf);
-          if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, name)) {
-            return "";
-          }
-          fn << "(RWByteAddressBuffer buffer, uint offset) {";
-        }
-
-        buf.IncrementIndent();
-        TINT_DEFER({
-          buf.DecrementIndent();
-          line(&buf) << "}";
-          line(&buf);
-        });
-
-        {
-          auto l = line(&buf);
-          if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "value")) {
-            return "";
-          }
-          l << " = 0;";
-        }
-
-        line(&buf) << "buffer.InterlockedOr(offset, 0, value);";
-        line(&buf) << "return value;";
-        return name;
-      }
-      case Op::kAtomicStore: {
-        // HLSL does not have an InterlockedStore, so we emulate it with
-        // InterlockedExchange and discard the returned value
-        auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
-        auto name = UniqueIdentifier("atomicStore");
-        {
-          auto fn = line(&buf);
-          fn << "void " << name << "(RWByteAddressBuffer buffer, uint offset, ";
-          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "value")) {
-            return "";
-          }
-          fn << ") {";
-        }
-
-        buf.IncrementIndent();
-        TINT_DEFER({
-          buf.DecrementIndent();
-          line(&buf) << "}";
-          line(&buf);
-        });
-
-        {
-          auto l = line(&buf);
-          if (!EmitTypeAndName(l, value_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "ignored")) {
-            return "";
-          }
-          l << ";";
-        }
-        line(&buf) << "buffer.InterlockedExchange(offset, value, ignored);";
-        return name;
-      }
-      case Op::kAtomicCompareExchangeWeak: {
-        auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
-
-        auto name = UniqueIdentifier("atomicCompareExchangeWeak");
-        {
-          auto fn = line(&buf);
-          if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, name)) {
-            return "";
-          }
-          fn << "(RWByteAddressBuffer buffer, uint offset, ";
-          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "compare")) {
-            return "";
-          }
-          fn << ", ";
-          if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "value")) {
-            return "";
-          }
-          fn << ") {";
-        }
-
-        buf.IncrementIndent();
-        TINT_DEFER({
-          buf.DecrementIndent();
-          line(&buf) << "}";
-          line(&buf);
-        });
-
-        {  // T result = {0, 0};
-          auto l = line(&buf);
-          if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, "result")) {
-            return "";
-          }
-          l << " = {0, 0};";
-        }
-        line(&buf) << "buffer.InterlockedCompareExchange(offset, compare, "
-                      "value, result.x);";
-        line(&buf) << "result.y = result.x == compare;";
-        line(&buf) << "return result;";
-        return name;
-      }
-      default:
-        break;
-    }
-    TINT_UNREACHABLE(Writer, diagnostics_)
-        << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
-        << static_cast<int>(intrinsic->op);
-    return "";
-  };
-
-  auto func = utils::GetOrCreate(dma_intrinsics_,
-                                 DMAIntrinsic{intrinsic->op, intrinsic->type},
-                                 generate_helper);
-  if (func.empty()) {
-    return false;
-  }
-
-  out << func;
-  {
-    ScopedParen sp(out);
-    bool first = true;
-    for (auto* arg : expr->args) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-      if (!EmitExpression(out, arg)) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
-                                            const ast::CallExpression* expr,
-                                            const sem::Builtin* builtin) {
-  std::string result = UniqueIdentifier("atomic_result");
-
-  if (!builtin->ReturnType()->Is<sem::Void>()) {
-    auto pre = line();
-    if (!EmitTypeAndName(pre, builtin->ReturnType(), ast::StorageClass::kNone,
-                         ast::Access::kUndefined, result)) {
-      return false;
-    }
-    pre << " = ";
-    if (!EmitZeroValue(pre, builtin->ReturnType())) {
-      return false;
-    }
-    pre << ";";
-  }
-
-  auto call = [&](const char* name) {
-    auto pre = line();
-    pre << name;
-
-    {
-      ScopedParen sp(pre);
-      for (size_t i = 0; i < expr->args.size(); i++) {
-        auto* arg = expr->args[i];
-        if (i > 0) {
-          pre << ", ";
-        }
-        if (i == 1 && builtin->Type() == sem::BuiltinType::kAtomicSub) {
-          // Sub uses InterlockedAdd with the operand negated.
-          pre << "-";
-        }
-        if (!EmitExpression(pre, arg)) {
-          return false;
-        }
-      }
-
-      pre << ", " << result;
-    }
-
-    pre << ";";
-
-    out << result;
-    return true;
-  };
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAtomicLoad: {
-      // HLSL does not have an InterlockedLoad, so we emulate it with
-      // InterlockedOr using 0 as the OR value
-      auto pre = line();
-      pre << "InterlockedOr";
-      {
-        ScopedParen sp(pre);
-        if (!EmitExpression(pre, expr->args[0])) {
-          return false;
-        }
-        pre << ", 0, " << result;
-      }
-      pre << ";";
-
-      out << result;
-      return true;
-    }
-    case sem::BuiltinType::kAtomicStore: {
-      // HLSL does not have an InterlockedStore, so we emulate it with
-      // InterlockedExchange and discard the returned value
-      {  // T result = 0;
-        auto pre = line();
-        auto* value_ty = builtin->Parameters()[1]->Type()->UnwrapRef();
-        if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone,
-                             ast::Access::kUndefined, result)) {
-          return false;
-        }
-        pre << " = ";
-        if (!EmitZeroValue(pre, value_ty)) {
-          return false;
-        }
-        pre << ";";
-      }
-
-      out << "InterlockedExchange";
-      {
-        ScopedParen sp(out);
-        if (!EmitExpression(out, expr->args[0])) {
-          return false;
-        }
-        out << ", ";
-        if (!EmitExpression(out, expr->args[1])) {
-          return false;
-        }
-        out << ", " << result;
-      }
-      return true;
-    }
-    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
-      auto* dest = expr->args[0];
-      auto* compare_value = expr->args[1];
-      auto* value = expr->args[2];
-
-      std::string compare = UniqueIdentifier("atomic_compare_value");
-
-      {  // T compare_value = <compare_value>;
-        auto pre = line();
-        if (!EmitTypeAndName(pre, TypeOf(compare_value),
-                             ast::StorageClass::kNone, ast::Access::kUndefined,
-                             compare)) {
-          return false;
-        }
-        pre << " = ";
-        if (!EmitExpression(pre, compare_value)) {
-          return false;
-        }
-        pre << ";";
-      }
-
-      {  // InterlockedCompareExchange(dst, compare, value, result.x);
-        auto pre = line();
-        pre << "InterlockedCompareExchange";
-        {
-          ScopedParen sp(pre);
-          if (!EmitExpression(pre, dest)) {
-            return false;
-          }
-          pre << ", " << compare << ", ";
-          if (!EmitExpression(pre, value)) {
-            return false;
-          }
-          pre << ", " << result << ".x";
-        }
-        pre << ";";
-      }
-
-      {  // result.y = result.x == compare;
-        line() << result << ".y = " << result << ".x == " << compare << ";";
-      }
-
-      out << result;
-      return true;
-    }
-
-    case sem::BuiltinType::kAtomicAdd:
-    case sem::BuiltinType::kAtomicSub:
-      return call("InterlockedAdd");
-
-    case sem::BuiltinType::kAtomicMax:
-      return call("InterlockedMax");
-
-    case sem::BuiltinType::kAtomicMin:
-      return call("InterlockedMin");
-
-    case sem::BuiltinType::kAtomicAnd:
-      return call("InterlockedAnd");
-
-    case sem::BuiltinType::kAtomicOr:
-      return call("InterlockedOr");
-
-    case sem::BuiltinType::kAtomicXor:
-      return call("InterlockedXor");
-
-    case sem::BuiltinType::kAtomicExchange:
-      return call("InterlockedExchange");
-
-    default:
-      break;
-  }
-
-  TINT_UNREACHABLE(Writer, diagnostics_)
-      << "unsupported atomic builtin: " << builtin->Type();
-  return false;
-}
-
-bool GeneratorImpl::EmitSelectCall(std::ostream& out,
-                                   const ast::CallExpression* expr) {
-  auto* expr_false = expr->args[0];
-  auto* expr_true = expr->args[1];
-  auto* expr_cond = expr->args[2];
-  ScopedParen paren(out);
-  if (!EmitExpression(out, expr_cond)) {
-    return false;
-  }
-
-  out << " ? ";
-
-  if (!EmitExpression(out, expr_true)) {
-    return false;
-  }
-
-  out << " : ";
-
-  if (!EmitExpression(out, expr_false)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitModfCall(std::ostream& out,
-                                 const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* ty = builtin->Parameters()[0]->Type();
-        auto in = params[0];
-
-        std::string width;
-        if (auto* vec = ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        // Emit the builtin return type unique to this overload. This does not
-        // exist in the AST, so it will not be generated in Generate().
-        if (!EmitStructType(&helpers_,
-                            builtin->ReturnType()->As<sem::Struct>())) {
-          return false;
-        }
-
-        line(b) << "float" << width << " whole;";
-        line(b) << "float" << width << " fract = modf(" << in << ", whole);";
-        {
-          auto l = line(b);
-          if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                        ast::Access::kUndefined, "")) {
-            return false;
-          }
-          l << " result = {fract, whole};";
-        }
-        line(b) << "return result;";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
-                                  const ast::CallExpression* expr,
-                                  const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* ty = builtin->Parameters()[0]->Type();
-        auto in = params[0];
-
-        std::string width;
-        if (auto* vec = ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        // Emit the builtin return type unique to this overload. This does not
-        // exist in the AST, so it will not be generated in Generate().
-        if (!EmitStructType(&helpers_,
-                            builtin->ReturnType()->As<sem::Struct>())) {
-          return false;
-        }
-
-        line(b) << "float" << width << " exp;";
-        line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
-        {
-          auto l = line(b);
-          if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                        ast::Access::kUndefined, "")) {
-            return false;
-          }
-          l << " result = {sig, int" << width << "(exp)};";
-        }
-        line(b) << "return result;";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitIsNormalCall(std::ostream& out,
-                                     const ast::CallExpression* expr,
-                                     const sem::Builtin* builtin) {
-  // HLSL doesn't have a isNormal builtin, we need to emulate
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* input_ty = builtin->Parameters()[0]->Type();
-
-        std::string width;
-        if (auto* vec = input_ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        constexpr auto* kExponentMask = "0x7f80000";
-        constexpr auto* kMinNormalExponent = "0x0080000";
-        constexpr auto* kMaxNormalExponent = "0x7f00000";
-
-        line(b) << "uint" << width << " exponent = asuint(" << params[0]
-                << ") & " << kExponentMask << ";";
-        line(b) << "uint" << width << " clamped = "
-                << "clamp(exponent, " << kMinNormalExponent << ", "
-                << kMaxNormalExponent << ");";
-        line(b) << "return clamped == exponent;";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kRadToDeg << ";";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kDegToRad << ";";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDataPackingCall(std::ostream& out,
-                                        const ast::CallExpression* expr,
-                                        const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        uint32_t dims = 2;
-        bool is_signed = false;
-        uint32_t scale = 65535;
-        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kPack4x8unorm) {
-          dims = 4;
-          scale = 255;
-        }
-        if (builtin->Type() == sem::BuiltinType::kPack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kPack2x16snorm) {
-          is_signed = true;
-          scale = (scale - 1) / 2;
-        }
-        switch (builtin->Type()) {
-          case sem::BuiltinType::kPack4x8snorm:
-          case sem::BuiltinType::kPack4x8unorm:
-          case sem::BuiltinType::kPack2x16snorm:
-          case sem::BuiltinType::kPack2x16unorm: {
-            {
-              auto l = line(b);
-              l << (is_signed ? "" : "u") << "int" << dims
-                << " i = " << (is_signed ? "" : "u") << "int" << dims
-                << "(round(clamp(" << params[0] << ", "
-                << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale
-                << ".0))";
-              if (is_signed) {
-                l << " & " << (dims == 4 ? "0xff" : "0xffff");
-              }
-              l << ";";
-            }
-            {
-              auto l = line(b);
-              l << "return ";
-              if (is_signed) {
-                l << "asuint";
-              }
-              l << "(i.x | i.y << " << (32 / dims);
-              if (dims == 4) {
-                l << " | i.z << 16 | i.w << 24";
-              }
-              l << ");";
-            }
-            break;
-          }
-          case sem::BuiltinType::kPack2x16float: {
-            line(b) << "uint2 i = f32tof16(" << params[0] << ");";
-            line(b) << "return i.x | (i.y << 16);";
-            break;
-          }
-          default:
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "Internal error: unhandled data packing builtin");
-            return false;
-        }
-
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out,
-                                          const ast::CallExpression* expr,
-                                          const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        uint32_t dims = 2;
-        bool is_signed = false;
-        uint32_t scale = 65535;
-        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kUnpack4x8unorm) {
-          dims = 4;
-          scale = 255;
-        }
-        if (builtin->Type() == sem::BuiltinType::kUnpack4x8snorm ||
-            builtin->Type() == sem::BuiltinType::kUnpack2x16snorm) {
-          is_signed = true;
-          scale = (scale - 1) / 2;
-        }
-        switch (builtin->Type()) {
-          case sem::BuiltinType::kUnpack4x8snorm:
-          case sem::BuiltinType::kUnpack2x16snorm: {
-            line(b) << "int j = int(" << params[0] << ");";
-            {  // Perform sign extension on the converted values.
-              auto l = line(b);
-              l << "int" << dims << " i = int" << dims << "(";
-              if (dims == 2) {
-                l << "j << 16, j) >> 16";
-              } else {
-                l << "j << 24, j << 16, j << 8, j) >> 24";
-              }
-              l << ";";
-            }
-            line(b) << "return clamp(float" << dims << "(i) / " << scale
-                    << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
-            break;
-          }
-          case sem::BuiltinType::kUnpack4x8unorm:
-          case sem::BuiltinType::kUnpack2x16unorm: {
-            line(b) << "uint j = " << params[0] << ";";
-            {
-              auto l = line(b);
-              l << "uint" << dims << " i = uint" << dims << "(";
-              l << "j & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
-              if (dims == 4) {
-                l << "(j >> " << (32 / dims)
-                  << ") & 0xff, (j >> 16) & 0xff, j >> 24";
-              } else {
-                l << "j >> " << (32 / dims);
-              }
-              l << ");";
-            }
-            line(b) << "return float" << dims << "(i) / " << scale << ".0;";
-            break;
-          }
-          case sem::BuiltinType::kUnpack2x16float:
-            line(b) << "uint i = " << params[0] << ";";
-            line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
-            break;
-          default:
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "Internal error: unhandled data packing builtin");
-            return false;
-        }
-
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitBarrierCall(std::ostream& out,
-                                    const sem::Builtin* builtin) {
-  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
-  // instruction.
-  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
-    out << "GroupMemoryBarrierWithGroupSync()";
-  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
-    out << "DeviceMemoryBarrierWithGroupSync()";
-  } else {
-    TINT_UNREACHABLE(Writer, diagnostics_)
-        << "unexpected barrier builtin type " << sem::str(builtin->Type());
-    return false;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitTextureCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  using Usage = sem::ParameterUsage;
-
-  auto& signature = builtin->Signature();
-  auto* expr = call->Declaration();
-  auto arguments = expr->args;
-
-  // Returns the argument with the given usage
-  auto arg = [&](Usage usage) {
-    int idx = signature.IndexOf(usage);
-    return (idx >= 0) ? arguments[idx] : nullptr;
-  };
-
-  auto* texture = arg(Usage::kTexture);
-  if (!texture) {
-    TINT_ICE(Writer, diagnostics_) << "missing texture argument";
-    return false;
-  }
-
-  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureDimensions:
-    case sem::BuiltinType::kTextureNumLayers:
-    case sem::BuiltinType::kTextureNumLevels:
-    case sem::BuiltinType::kTextureNumSamples: {
-      // All of these builtins use the GetDimensions() method on the texture
-      bool is_ms = texture_type->IsAnyOf<sem::MultisampledTexture,
-                                         sem::DepthMultisampledTexture>();
-      int num_dimensions = 0;
-      std::string swizzle;
-
-      switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureDimensions:
-          switch (texture_type->dim()) {
-            case ast::TextureDimension::kNone:
-              TINT_ICE(Writer, diagnostics_) << "texture dimension is kNone";
-              return false;
-            case ast::TextureDimension::k1d:
-              num_dimensions = 1;
-              break;
-            case ast::TextureDimension::k2d:
-              num_dimensions = is_ms ? 3 : 2;
-              swizzle = is_ms ? ".xy" : "";
-              break;
-            case ast::TextureDimension::k2dArray:
-              num_dimensions = is_ms ? 4 : 3;
-              swizzle = ".xy";
-              break;
-            case ast::TextureDimension::k3d:
-              num_dimensions = 3;
-              break;
-            case ast::TextureDimension::kCube:
-              num_dimensions = 2;
-              break;
-            case ast::TextureDimension::kCubeArray:
-              num_dimensions = 3;
-              swizzle = ".xy";
-              break;
-          }
-          break;
-        case sem::BuiltinType::kTextureNumLayers:
-          switch (texture_type->dim()) {
-            default:
-              TINT_ICE(Writer, diagnostics_)
-                  << "texture dimension is not arrayed";
-              return false;
-            case ast::TextureDimension::k2dArray:
-              num_dimensions = is_ms ? 4 : 3;
-              swizzle = ".z";
-              break;
-            case ast::TextureDimension::kCubeArray:
-              num_dimensions = 3;
-              swizzle = ".z";
-              break;
-          }
-          break;
-        case sem::BuiltinType::kTextureNumLevels:
-          switch (texture_type->dim()) {
-            default:
-              TINT_ICE(Writer, diagnostics_)
-                  << "texture dimension does not support mips";
-              return false;
-            case ast::TextureDimension::k1d:
-              num_dimensions = 2;
-              swizzle = ".y";
-              break;
-            case ast::TextureDimension::k2d:
-            case ast::TextureDimension::kCube:
-              num_dimensions = 3;
-              swizzle = ".z";
-              break;
-            case ast::TextureDimension::k2dArray:
-            case ast::TextureDimension::k3d:
-            case ast::TextureDimension::kCubeArray:
-              num_dimensions = 4;
-              swizzle = ".w";
-              break;
-          }
-          break;
-        case sem::BuiltinType::kTextureNumSamples:
-          switch (texture_type->dim()) {
-            default:
-              TINT_ICE(Writer, diagnostics_)
-                  << "texture dimension does not support multisampling";
-              return false;
-            case ast::TextureDimension::k2d:
-              num_dimensions = 3;
-              swizzle = ".z";
-              break;
-            case ast::TextureDimension::k2dArray:
-              num_dimensions = 4;
-              swizzle = ".w";
-              break;
-          }
-          break;
-        default:
-          TINT_ICE(Writer, diagnostics_) << "unexpected builtin";
-          return false;
-      }
-
-      auto* level_arg = arg(Usage::kLevel);
-
-      if (level_arg) {
-        // `NumberOfLevels` is a non-optional argument if `MipLevel` was passed.
-        // Increment the number of dimensions for the temporary vector to
-        // accommodate this.
-        num_dimensions++;
-
-        // If the swizzle was empty, the expression will evaluate to the whole
-        // vector. As we've grown the vector by one element, we now need to
-        // swizzle to keep the result expression equivalent.
-        if (swizzle.empty()) {
-          static constexpr const char* swizzles[] = {"", ".x", ".xy", ".xyz"};
-          swizzle = swizzles[num_dimensions - 1];
-        }
-      }
-
-      if (num_dimensions > 4) {
-        TINT_ICE(Writer, diagnostics_)
-            << "Texture query builtin temporary vector has " << num_dimensions
-            << " dimensions";
-        return false;
-      }
-
-      // Declare a variable to hold the queried texture info
-      auto dims = UniqueIdentifier(kTempNamePrefix);
-      if (num_dimensions == 1) {
-        line() << "int " << dims << ";";
-      } else {
-        line() << "int" << num_dimensions << " " << dims << ";";
-      }
-
-      {  // texture.GetDimensions(...)
-        auto pre = line();
-        if (!EmitExpression(pre, texture)) {
-          return false;
-        }
-        pre << ".GetDimensions(";
-
-        if (level_arg) {
-          if (!EmitExpression(pre, level_arg)) {
-            return false;
-          }
-          pre << ", ";
-        } else if (builtin->Type() == sem::BuiltinType::kTextureNumLevels) {
-          pre << "0, ";
-        }
-
-        if (num_dimensions == 1) {
-          pre << dims;
-        } else {
-          static constexpr char xyzw[] = {'x', 'y', 'z', 'w'};
-          if (num_dimensions < 0 || num_dimensions > 4) {
-            TINT_ICE(Writer, diagnostics_)
-                << "vector dimensions are " << num_dimensions;
-            return false;
-          }
-          for (int i = 0; i < num_dimensions; i++) {
-            if (i > 0) {
-              pre << ", ";
-            }
-            pre << dims << "." << xyzw[i];
-          }
-        }
-
-        pre << ");";
-      }
-
-      // The out parameters of the GetDimensions() call is now in temporary
-      // `dims` variable. This may be packed with other data, so the final
-      // expression may require a swizzle.
-      out << dims << swizzle;
-      return true;
-    }
-    default:
-      break;
-  }
-
-  if (!EmitExpression(out, texture))
-    return false;
-
-  // If pack_level_in_coords is true, then the mip level will be appended as the
-  // last value of the coordinates argument. If the WGSL builtin overload does
-  // not have a level parameter and pack_level_in_coords is true, then a zero
-  // mip level will be inserted.
-  bool pack_level_in_coords = false;
-
-  uint32_t hlsl_ret_width = 4u;
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureSample:
-      out << ".Sample(";
-      break;
-    case sem::BuiltinType::kTextureSampleBias:
-      out << ".SampleBias(";
-      break;
-    case sem::BuiltinType::kTextureSampleLevel:
-      out << ".SampleLevel(";
-      break;
-    case sem::BuiltinType::kTextureSampleGrad:
-      out << ".SampleGrad(";
-      break;
-    case sem::BuiltinType::kTextureSampleCompare:
-      out << ".SampleCmp(";
-      hlsl_ret_width = 1;
-      break;
-    case sem::BuiltinType::kTextureSampleCompareLevel:
-      out << ".SampleCmpLevelZero(";
-      hlsl_ret_width = 1;
-      break;
-    case sem::BuiltinType::kTextureLoad:
-      out << ".Load(";
-      // Multisampled textures do not support mip-levels.
-      if (!texture_type->Is<sem::MultisampledTexture>()) {
-        pack_level_in_coords = true;
-      }
-      break;
-    case sem::BuiltinType::kTextureGather:
-      out << ".Gather";
-      if (builtin->Parameters()[0]->Usage() ==
-          sem::ParameterUsage::kComponent) {
-        switch (call->Arguments()[0]->ConstantValue().Elements()[0].i32) {
-          case 0:
-            out << "Red";
-            break;
-          case 1:
-            out << "Green";
-            break;
-          case 2:
-            out << "Blue";
-            break;
-          case 3:
-            out << "Alpha";
-            break;
-        }
-      }
-      out << "(";
-      break;
-    case sem::BuiltinType::kTextureGatherCompare:
-      out << ".GatherCmp(";
-      break;
-    case sem::BuiltinType::kTextureStore:
-      out << "[";
-      break;
-    default:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Internal compiler error: Unhandled texture builtin '" +
-              std::string(builtin->str()) + "'");
-      return false;
-  }
-
-  if (auto* sampler = arg(Usage::kSampler)) {
-    if (!EmitExpression(out, sampler))
-      return false;
-    out << ", ";
-  }
-
-  auto* param_coords = arg(Usage::kCoords);
-  if (!param_coords) {
-    TINT_ICE(Writer, diagnostics_) << "missing coords argument";
-    return false;
-  }
-
-  auto emit_vector_appended_with_i32_zero = [&](const ast::Expression* vector) {
-    auto* i32 = builder_.create<sem::I32>();
-    auto* zero = builder_.Expr(0);
-    auto* stmt = builder_.Sem().Get(vector)->Stmt();
-    builder_.Sem().Add(
-        zero, builder_.create<sem::Expression>(zero, i32, stmt, sem::Constant{},
-                                               /* has_side_effects */ false));
-    auto* packed = AppendVector(&builder_, vector, zero);
-    return EmitExpression(out, packed->Declaration());
-  };
-
-  auto emit_vector_appended_with_level = [&](const ast::Expression* vector) {
-    if (auto* level = arg(Usage::kLevel)) {
-      auto* packed = AppendVector(&builder_, vector, level);
-      return EmitExpression(out, packed->Declaration());
-    }
-    return emit_vector_appended_with_i32_zero(vector);
-  };
-
-  if (auto* array_index = arg(Usage::kArrayIndex)) {
-    // Array index needs to be appended to the coordinates.
-    auto* packed = AppendVector(&builder_, param_coords, array_index);
-    if (pack_level_in_coords) {
-      // Then mip level needs to be appended to the coordinates.
-      if (!emit_vector_appended_with_level(packed->Declaration())) {
-        return false;
-      }
-    } else {
-      if (!EmitExpression(out, packed->Declaration())) {
-        return false;
-      }
-    }
-  } else if (pack_level_in_coords) {
-    // Mip level needs to be appended to the coordinates.
-    if (!emit_vector_appended_with_level(param_coords)) {
-      return false;
-    }
-  } else {
-    if (!EmitExpression(out, param_coords)) {
-      return false;
-    }
-  }
-
-  for (auto usage : {Usage::kDepthRef, Usage::kBias, Usage::kLevel, Usage::kDdx,
-                     Usage::kDdy, Usage::kSampleIndex, Usage::kOffset}) {
-    if (usage == Usage::kLevel && pack_level_in_coords) {
-      continue;  // mip level already packed in coordinates.
-    }
-    if (auto* e = arg(usage)) {
-      out << ", ";
-      if (!EmitExpression(out, e)) {
-        return false;
-      }
-    }
-  }
-
-  if (builtin->Type() == sem::BuiltinType::kTextureStore) {
-    out << "] = ";
-    if (!EmitExpression(out, arg(Usage::kValue))) {
-      return false;
-    }
-  } else {
-    out << ")";
-
-    // If the builtin return type does not match the number of elements of the
-    // HLSL builtin, we need to swizzle the expression to generate the correct
-    // number of components.
-    uint32_t wgsl_ret_width = 1;
-    if (auto* vec = builtin->ReturnType()->As<sem::Vector>()) {
-      wgsl_ret_width = vec->Width();
-    }
-    if (wgsl_ret_width < hlsl_ret_width) {
-      out << ".";
-      for (uint32_t i = 0; i < wgsl_ret_width; i++) {
-        out << "xyz"[i];
-      }
-    }
-    if (wgsl_ret_width > hlsl_ret_width) {
-      TINT_ICE(Writer, diagnostics_)
-          << "WGSL return width (" << wgsl_ret_width
-          << ") is wider than HLSL return width (" << hlsl_ret_width << ") for "
-          << builtin->Type();
-      return false;
-    }
-  }
-
-  return true;
-}
-
-std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAbs:
-    case sem::BuiltinType::kAcos:
-    case sem::BuiltinType::kAll:
-    case sem::BuiltinType::kAny:
-    case sem::BuiltinType::kAsin:
-    case sem::BuiltinType::kAtan:
-    case sem::BuiltinType::kAtan2:
-    case sem::BuiltinType::kCeil:
-    case sem::BuiltinType::kClamp:
-    case sem::BuiltinType::kCos:
-    case sem::BuiltinType::kCosh:
-    case sem::BuiltinType::kCross:
-    case sem::BuiltinType::kDeterminant:
-    case sem::BuiltinType::kDistance:
-    case sem::BuiltinType::kDot:
-    case sem::BuiltinType::kExp:
-    case sem::BuiltinType::kExp2:
-    case sem::BuiltinType::kFloor:
-    case sem::BuiltinType::kFrexp:
-    case sem::BuiltinType::kLdexp:
-    case sem::BuiltinType::kLength:
-    case sem::BuiltinType::kLog:
-    case sem::BuiltinType::kLog2:
-    case sem::BuiltinType::kMax:
-    case sem::BuiltinType::kMin:
-    case sem::BuiltinType::kModf:
-    case sem::BuiltinType::kNormalize:
-    case sem::BuiltinType::kPow:
-    case sem::BuiltinType::kReflect:
-    case sem::BuiltinType::kRefract:
-    case sem::BuiltinType::kRound:
-    case sem::BuiltinType::kSign:
-    case sem::BuiltinType::kSin:
-    case sem::BuiltinType::kSinh:
-    case sem::BuiltinType::kSqrt:
-    case sem::BuiltinType::kStep:
-    case sem::BuiltinType::kTan:
-    case sem::BuiltinType::kTanh:
-    case sem::BuiltinType::kTranspose:
-    case sem::BuiltinType::kTrunc:
-      return builtin->str();
-    case sem::BuiltinType::kCountOneBits:
-      return "countbits";
-    case sem::BuiltinType::kDpdx:
-      return "ddx";
-    case sem::BuiltinType::kDpdxCoarse:
-      return "ddx_coarse";
-    case sem::BuiltinType::kDpdxFine:
-      return "ddx_fine";
-    case sem::BuiltinType::kDpdy:
-      return "ddy";
-    case sem::BuiltinType::kDpdyCoarse:
-      return "ddy_coarse";
-    case sem::BuiltinType::kDpdyFine:
-      return "ddy_fine";
-    case sem::BuiltinType::kFaceForward:
-      return "faceforward";
-    case sem::BuiltinType::kFract:
-      return "frac";
-    case sem::BuiltinType::kFma:
-      return "mad";
-    case sem::BuiltinType::kFwidth:
-    case sem::BuiltinType::kFwidthCoarse:
-    case sem::BuiltinType::kFwidthFine:
-      return "fwidth";
-    case sem::BuiltinType::kInverseSqrt:
-      return "rsqrt";
-    case sem::BuiltinType::kIsFinite:
-      return "isfinite";
-    case sem::BuiltinType::kIsInf:
-      return "isinf";
-    case sem::BuiltinType::kIsNan:
-      return "isnan";
-    case sem::BuiltinType::kMix:
-      return "lerp";
-    case sem::BuiltinType::kReverseBits:
-      return "reversebits";
-    case sem::BuiltinType::kSmoothStep:
-      return "smoothstep";
-    default:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Unknown builtin method: " + std::string(builtin->str()));
-  }
-
-  return "";
-}
-
-bool GeneratorImpl::EmitCase(const ast::SwitchStatement* s, size_t case_idx) {
-  auto* stmt = s->body[case_idx];
-  if (stmt->IsDefault()) {
-    line() << "default: {";
-  } else {
-    for (auto* selector : stmt->selectors) {
-      auto out = line();
-      out << "case ";
-      if (!EmitLiteral(out, selector)) {
-        return false;
-      }
-      out << ":";
-      if (selector == stmt->selectors.back()) {
-        out << " {";
-      }
-    }
-  }
-
-  increment_indent();
-  TINT_DEFER({
-    decrement_indent();
-    line() << "}";
-  });
-
-  // Emit the case statement
-  if (!EmitStatements(stmt->body->statements)) {
-    return false;
-  }
-
-  // Inline all fallthrough case statements. FXC cannot handle fallthroughs.
-  while (tint::Is<ast::FallthroughStatement>(stmt->body->Last())) {
-    case_idx++;
-    stmt = s->body[case_idx];
-    // Generate each fallthrough case statement in a new block. This is done to
-    // prevent symbol collision of variables declared in these cases statements.
-    if (!EmitBlock(stmt->body)) {
-      return false;
-    }
-  }
-
-  if (!tint::IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(
-          stmt->body->Last())) {
-    line() << "break;";
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
-  if (!emit_continuing_()) {
-    return false;
-  }
-  line() << "continue;";
-  return true;
-}
-
-bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
-  // TODO(dsinclair): Verify this is correct when the discard semantics are
-  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
-  line() << "discard;";
-  return true;
-}
-
-bool GeneratorImpl::EmitExpression(std::ostream& out,
-                                   const ast::Expression* expr) {
-  return Switch(
-      expr,
-      [&](const ast::IndexAccessorExpression* a) {  //
-        return EmitIndexAccessor(out, a);
-      },
-      [&](const ast::BinaryExpression* b) {  //
-        return EmitBinary(out, b);
-      },
-      [&](const ast::BitcastExpression* b) {  //
-        return EmitBitcast(out, b);
-      },
-      [&](const ast::CallExpression* c) {  //
-        return EmitCall(out, c);
-      },
-      [&](const ast::IdentifierExpression* i) {  //
-        return EmitIdentifier(out, i);
-      },
-      [&](const ast::LiteralExpression* l) {  //
-        return EmitLiteral(out, l);
-      },
-      [&](const ast::MemberAccessorExpression* m) {  //
-        return EmitMemberAccessor(out, m);
-      },
-      [&](const ast::UnaryOpExpression* u) {  //
-        return EmitUnaryOp(out, u);
-      },
-      [&](Default) {  //
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown expression type: " + std::string(expr->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitIdentifier(std::ostream& out,
-                                   const ast::IdentifierExpression* expr) {
-  out << builder_.Symbols().NameFor(expr->symbol);
-  return true;
-}
-
-bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
-  {
-    auto out = line();
-    out << "if (";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      line() << "} else {";
-      increment_indent();
-
-      {
-        auto out = line();
-        out << "if (";
-        if (!EmitExpression(out, e->condition)) {
-          return false;
-        }
-        out << ") {";
-      }
-    } else {
-      line() << "} else {";
-    }
-
-    if (!EmitStatementsWithIndent(e->body->statements)) {
-      return false;
-    }
-  }
-
-  line() << "}";
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      decrement_indent();
-      line() << "}";
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-  auto* sem = builder_.Sem().Get(func);
-
-  if (ast::HasAttribute<ast::InternalAttribute>(func->attributes)) {
-    // An internal function. Do not emit.
-    return true;
-  }
-
-  {
-    auto out = line();
-    auto name = builder_.Symbols().NameFor(func->symbol);
-    // If the function returns an array, then we need to declare a typedef for
-    // this.
-    if (sem->ReturnType()->Is<sem::Array>()) {
-      auto typedef_name = UniqueIdentifier(name + "_ret");
-      auto pre = line();
-      pre << "typedef ";
-      if (!EmitTypeAndName(pre, sem->ReturnType(), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, typedef_name)) {
-        return false;
-      }
-      pre << ";";
-      out << typedef_name;
-    } else {
-      if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
-                    ast::Access::kReadWrite, "")) {
-        return false;
-      }
-    }
-
-    out << " " << name << "(";
-
-    bool first = true;
-
-    for (auto* v : sem->Parameters()) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      auto const* type = v->Type();
-
-      if (auto* ptr = type->As<sem::Pointer>()) {
-        // Transform pointer parameters in to `inout` parameters.
-        // The WGSL spec is highly restrictive in what can be passed in pointer
-        // parameters, which allows for this transformation. See:
-        // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
-        out << "inout ";
-        type = ptr->StoreType();
-      }
-
-      // Note: WGSL only allows for StorageClass::kNone on parameters, however
-      // the sanitizer transforms generates load / store functions for storage
-      // or uniform buffers. These functions have a buffer parameter with
-      // StorageClass::kStorage or StorageClass::kUniform. This is required to
-      // correctly translate the parameter to a [RW]ByteAddressBuffer for
-      // storage buffers and a uint4[N] for uniform buffers.
-      if (!EmitTypeAndName(
-              out, type, v->StorageClass(), v->Access(),
-              builder_.Symbols().NameFor(v->Declaration()->symbol))) {
-        return false;
-      }
-    }
-    out << ") {";
-  }
-
-  if (sem->HasDiscard() && !sem->ReturnType()->Is<sem::Void>()) {
-    // BUG(crbug.com/tint/1081): work around non-void functions with discard
-    // failing compilation sometimes
-    if (!EmitFunctionBodyWithDiscard(func)) {
-      return false;
-    }
-  } else {
-    if (!EmitStatementsWithIndent(func->body->statements)) {
-      return false;
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitFunctionBodyWithDiscard(const ast::Function* func) {
-  // FXC sometimes fails to compile functions that discard with 'Not all control
-  // paths return a value'. We work around this by wrapping the function body
-  // within an "if (true) { <body> } return <default return type obj>;" so that
-  // there is always an (unused) return statement.
-
-  auto* sem = builder_.Sem().Get(func);
-  TINT_ASSERT(Writer, sem->HasDiscard() && !sem->ReturnType()->Is<sem::Void>());
-
-  ScopedIndent si(this);
-  line() << "if (true) {";
-
-  if (!EmitStatementsWithIndent(func->body->statements)) {
-    return false;
-  }
-
-  line() << "}";
-
-  // Return an unused result that matches the type of the return value
-  auto name = builder_.Symbols().NameFor(builder_.Symbols().New("unused"));
-  {
-    auto out = line();
-    if (!EmitTypeAndName(out, sem->ReturnType(), ast::StorageClass::kNone,
-                         ast::Access::kReadWrite, name)) {
-      return false;
-    }
-    out << ";";
-  }
-  line() << "return " << name << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitGlobalVariable(const ast::Variable* global) {
-  if (global->is_const) {
-    return EmitProgramConstVariable(global);
-  }
-
-  auto* sem = builder_.Sem().Get(global);
-  switch (sem->StorageClass()) {
-    case ast::StorageClass::kUniform:
-      return EmitUniformVariable(sem);
-    case ast::StorageClass::kStorage:
-      return EmitStorageVariable(sem);
-    case ast::StorageClass::kUniformConstant:
-      return EmitHandleVariable(sem);
-    case ast::StorageClass::kPrivate:
-      return EmitPrivateVariable(sem);
-    case ast::StorageClass::kWorkgroup:
-      return EmitWorkgroupVariable(sem);
-    default:
-      break;
-  }
-
-  TINT_ICE(Writer, diagnostics_)
-      << "unhandled storage class " << sem->StorageClass();
-  return false;
-}
-
-bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto binding_point = decl->BindingPoint();
-  auto* type = var->Type()->UnwrapRef();
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  line() << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point)
-         << " {";
-
-  {
-    ScopedIndent si(this);
-    auto out = line();
-    if (!EmitTypeAndName(out, type, ast::StorageClass::kUniform, var->Access(),
-                         name)) {
-      return false;
-    }
-    out << ";";
-  }
-
-  line() << "};";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto* type = var->Type()->UnwrapRef();
-  auto out = line();
-  if (!EmitTypeAndName(out, type, ast::StorageClass::kStorage, var->Access(),
-                       builder_.Symbols().NameFor(decl->symbol))) {
-    return false;
-  }
-
-  out << RegisterAndSpace(var->Access() == ast::Access::kRead ? 't' : 'u',
-                          decl->BindingPoint())
-      << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto* unwrapped_type = var->Type()->UnwrapRef();
-  auto out = line();
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  const char* register_space = nullptr;
-
-  if (unwrapped_type->Is<sem::Texture>()) {
-    register_space = "t";
-    if (unwrapped_type->Is<sem::StorageTexture>()) {
-      register_space = "u";
-    }
-  } else if (unwrapped_type->Is<sem::Sampler>()) {
-    register_space = "s";
-  }
-
-  if (register_space) {
-    auto bp = decl->BindingPoint();
-    out << " : register(" << register_space << bp.binding->value << ", space"
-        << bp.group->value << ")";
-  }
-
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto out = line();
-
-  out << "static ";
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  out << " = ";
-  if (auto* constructor = decl->constructor) {
-    if (!EmitExpression(out, constructor)) {
-      return false;
-    }
-  } else {
-    if (!EmitZeroValue(out, var->Type()->UnwrapRef())) {
-      return false;
-    }
-  }
-
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-  auto out = line();
-
-  out << "groupshared ";
-
-  auto name = builder_.Symbols().NameFor(decl->symbol);
-  auto* type = var->Type()->UnwrapRef();
-  if (!EmitTypeAndName(out, type, var->StorageClass(), var->Access(), name)) {
-    return false;
-  }
-
-  if (auto* constructor = decl->constructor) {
-    out << " = ";
-    if (!EmitExpression(out, constructor)) {
-      return false;
-    }
-  }
-
-  out << ";";
-  return true;
-}
-
-std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
-  switch (builtin) {
-    case ast::Builtin::kPosition:
-      return "SV_Position";
-    case ast::Builtin::kVertexIndex:
-      return "SV_VertexID";
-    case ast::Builtin::kInstanceIndex:
-      return "SV_InstanceID";
-    case ast::Builtin::kFrontFacing:
-      return "SV_IsFrontFace";
-    case ast::Builtin::kFragDepth:
-      return "SV_Depth";
-    case ast::Builtin::kLocalInvocationId:
-      return "SV_GroupThreadID";
-    case ast::Builtin::kLocalInvocationIndex:
-      return "SV_GroupIndex";
-    case ast::Builtin::kGlobalInvocationId:
-      return "SV_DispatchThreadID";
-    case ast::Builtin::kWorkgroupId:
-      return "SV_GroupID";
-    case ast::Builtin::kSampleIndex:
-      return "SV_SampleIndex";
-    case ast::Builtin::kSampleMask:
-      return "SV_Coverage";
-    default:
-      break;
-  }
-  return "";
-}
-
-std::string GeneratorImpl::interpolation_to_modifiers(
-    ast::InterpolationType type,
-    ast::InterpolationSampling sampling) const {
-  std::string modifiers;
-  switch (type) {
-    case ast::InterpolationType::kPerspective:
-      modifiers += "linear ";
-      break;
-    case ast::InterpolationType::kLinear:
-      modifiers += "noperspective ";
-      break;
-    case ast::InterpolationType::kFlat:
-      modifiers += "nointerpolation ";
-      break;
-  }
-  switch (sampling) {
-    case ast::InterpolationSampling::kCentroid:
-      modifiers += "centroid ";
-      break;
-    case ast::InterpolationSampling::kSample:
-      modifiers += "sample ";
-      break;
-    case ast::InterpolationSampling::kCenter:
-    case ast::InterpolationSampling::kNone:
-      break;
-  }
-  return modifiers;
-}
-
-bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
-  auto* func_sem = builder_.Sem().Get(func);
-
-  {
-    auto out = line();
-    if (func->PipelineStage() == ast::PipelineStage::kCompute) {
-      // Emit the workgroup_size attribute.
-      auto wgsize = func_sem->WorkgroupSize();
-      out << "[numthreads(";
-      for (int i = 0; i < 3; i++) {
-        if (i > 0) {
-          out << ", ";
-        }
-
-        if (wgsize[i].overridable_const) {
-          auto* global = builder_.Sem().Get<sem::GlobalVariable>(
-              wgsize[i].overridable_const);
-          if (!global->IsOverridable()) {
-            TINT_ICE(Writer, builder_.Diagnostics())
-                << "expected a pipeline-overridable constant";
-          }
-          out << kSpecConstantPrefix << global->ConstantId();
-        } else {
-          out << std::to_string(wgsize[i].value);
-        }
-      }
-      out << ")]" << std::endl;
-    }
-
-    out << func->return_type->FriendlyName(builder_.Symbols());
-
-    out << " " << builder_.Symbols().NameFor(func->symbol) << "(";
-
-    bool first = true;
-
-    // Emit entry point parameters.
-    for (auto* var : func->params) {
-      auto* sem = builder_.Sem().Get(var);
-      auto* type = sem->Type();
-      if (!type->Is<sem::Struct>()) {
-        // ICE likely indicates that the CanonicalizeEntryPointIO transform was
-        // not run, or a builtin parameter was added after it was run.
-        TINT_ICE(Writer, diagnostics_)
-            << "Unsupported non-struct entry point parameter";
-      }
-
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                           builder_.Symbols().NameFor(var->symbol))) {
-        return false;
-      }
-    }
-
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-
-    if (!EmitStatements(func->body->statements)) {
-      return false;
-    }
-
-    if (!Is<ast::ReturnStatement>(func->body->Last())) {
-      ast::ReturnStatement ret(ProgramID(), Source{});
-      if (!EmitStatement(&ret)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitLiteral(std::ostream& out,
-                                const ast::LiteralExpression* lit) {
-  return Switch(
-      lit,
-      [&](const ast::BoolLiteralExpression* l) {
-        out << (l->value ? "true" : "false");
-        return true;
-      },
-      [&](const ast::FloatLiteralExpression* fl) {
-        if (std::isinf(fl->value)) {
-          out << (fl->value >= 0 ? "asfloat(0x7f800000u)"
-                                 : "asfloat(0xff800000u)");
-        } else if (std::isnan(fl->value)) {
-          out << "asfloat(0x7fc00000u)";
-        } else {
-          out << FloatToString(fl->value) << "f";
-        }
-        return true;
-      },
-      [&](const ast::SintLiteralExpression* sl) {
-        out << sl->value;
-        return true;
-      },
-      [&](const ast::UintLiteralExpression* ul) {
-        out << ul->value << "u";
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitValue(std::ostream& out,
-                              const sem::Type* type,
-                              int value) {
-  return Switch(
-      type,
-      [&](const sem::Bool*) {
-        out << (value == 0 ? "false" : "true");
-        return true;
-      },
-      [&](const sem::F32*) {
-        out << value << ".0f";
-        return true;
-      },
-      [&](const sem::I32*) {
-        out << value;
-        return true;
-      },
-      [&](const sem::U32*) {
-        out << value << "u";
-        return true;
-      },
-      [&](const sem::Vector* vec) {
-        if (!EmitType(out, type, ast::StorageClass::kNone,
-                      ast::Access::kReadWrite, "")) {
-          return false;
-        }
-        ScopedParen sp(out);
-        for (uint32_t i = 0; i < vec->Width(); i++) {
-          if (i != 0) {
-            out << ", ";
-          }
-          if (!EmitValue(out, vec->type(), value)) {
-            return false;
-          }
-        }
-        return true;
-      },
-      [&](const sem::Matrix* mat) {
-        if (!EmitType(out, type, ast::StorageClass::kNone,
-                      ast::Access::kReadWrite, "")) {
-          return false;
-        }
-        ScopedParen sp(out);
-        for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) {
-          if (i != 0) {
-            out << ", ";
-          }
-          if (!EmitValue(out, mat->type(), value)) {
-            return false;
-          }
-        }
-        return true;
-      },
-      [&](const sem::Struct*) {
-        out << "(";
-        TINT_DEFER(out << ")" << value);
-        return EmitType(out, type, ast::StorageClass::kNone,
-                        ast::Access::kUndefined, "");
-      },
-      [&](const sem::Array*) {
-        out << "(";
-        TINT_DEFER(out << ")" << value);
-        return EmitType(out, type, ast::StorageClass::kNone,
-                        ast::Access::kUndefined, "");
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "Invalid type for value emission: " + type->type_name());
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
-  return EmitValue(out, type, 0);
-}
-
-bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
-  auto emit_continuing = [this, stmt]() {
-    if (stmt->continuing && !stmt->continuing->Empty()) {
-      if (!EmitBlock(stmt->continuing)) {
-        return false;
-      }
-    }
-    return true;
-  };
-
-  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-  line() << LoopAttribute() << "while (true) {";
-  {
-    ScopedIndent si(this);
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-    if (!emit_continuing_()) {
-      return false;
-    }
-  }
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
-  // Nest a for loop with a new block. In HLSL the initializer scope is not
-  // nested by the for-loop, so we may get variable redefinitions.
-  line() << "{";
-  increment_indent();
-  TINT_DEFER({
-    decrement_indent();
-    line() << "}";
-  });
-
-  TextBuffer init_buf;
-  if (auto* init = stmt->initializer) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
-    if (!EmitStatement(init)) {
-      return false;
-    }
-  }
-
-  TextBuffer cond_pre;
-  std::stringstream cond_buf;
-  if (auto* cond = stmt->condition) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
-    if (!EmitExpression(cond_buf, cond)) {
-      return false;
-    }
-  }
-
-  TextBuffer cont_buf;
-  if (auto* cont = stmt->continuing) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
-    if (!EmitStatement(cont)) {
-      return false;
-    }
-  }
-
-  // If the for-loop has a multi-statement conditional and / or continuing, then
-  // we cannot emit this as a regular for-loop in HLSL. Instead we need to
-  // generate a `while(true)` loop.
-  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
-
-  // If the for-loop has multi-statement initializer, or is going to be emitted
-  // as a `while(true)` loop, then declare the initializer statement(s) before
-  // the loop.
-  if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) {
-    current_buffer_->Append(init_buf);
-    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
-  }
-
-  if (emit_as_loop) {
-    auto emit_continuing = [&]() {
-      current_buffer_->Append(cont_buf);
-      return true;
-    };
-
-    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-    line() << LoopAttribute() << "while (true) {";
-    increment_indent();
-    TINT_DEFER({
-      decrement_indent();
-      line() << "}";
-    });
-
-    if (stmt->condition) {
-      current_buffer_->Append(cond_pre);
-      line() << "if (!(" << cond_buf.str() << ")) { break; }";
-    }
-
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-
-    if (!emit_continuing_()) {
-      return false;
-    }
-  } else {
-    // For-loop can be generated.
-    {
-      auto out = line();
-      out << LoopAttribute() << "for";
-      {
-        ScopedParen sp(out);
-
-        if (!init_buf.lines.empty()) {
-          out << init_buf.lines[0].content << " ";
-        } else {
-          out << "; ";
-        }
-
-        out << cond_buf.str() << "; ";
-
-        if (!cont_buf.lines.empty()) {
-          out << TrimSuffix(cont_buf.lines[0].content, ";");
-        }
-      }
-      out << " {";
-    }
-    {
-      auto emit_continuing = [] { return true; };
-      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-      if (!EmitStatementsWithIndent(stmt->body->statements)) {
-        return false;
-      }
-    }
-    line() << "}";
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitMemberAccessor(
-    std::ostream& out,
-    const ast::MemberAccessorExpression* expr) {
-  if (!EmitExpression(out, expr->structure)) {
-    return false;
-  }
-  out << ".";
-
-  // Swizzles output the name directly
-  if (builder_.Sem().Get(expr)->Is<sem::Swizzle>()) {
-    out << builder_.Symbols().NameFor(expr->member->symbol);
-  } else if (!EmitExpression(out, expr->member)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
-  if (stmt->value) {
-    auto out = line();
-    out << "return ";
-    if (!EmitExpression(out, stmt->value)) {
-      return false;
-    }
-    out << ";";
-  } else {
-    line() << "return;";
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
-  return Switch(
-      stmt,
-      [&](const ast::AssignmentStatement* a) {  //
-        return EmitAssign(a);
-      },
-      [&](const ast::BlockStatement* b) {  //
-        return EmitBlock(b);
-      },
-      [&](const ast::BreakStatement* b) {  //
-        return EmitBreak(b);
-      },
-      [&](const ast::CallStatement* c) {  //
-        auto out = line();
-        if (!EmitCall(out, c->expr)) {
-          return false;
-        }
-        out << ";";
-        return true;
-      },
-      [&](const ast::ContinueStatement* c) {  //
-        return EmitContinue(c);
-      },
-      [&](const ast::DiscardStatement* d) {  //
-        return EmitDiscard(d);
-      },
-      [&](const ast::FallthroughStatement*) {  //
-        line() << "/* fallthrough */";
-        return true;
-      },
-      [&](const ast::IfStatement* i) {  //
-        return EmitIf(i);
-      },
-      [&](const ast::LoopStatement* l) {  //
-        return EmitLoop(l);
-      },
-      [&](const ast::ForLoopStatement* l) {  //
-        return EmitForLoop(l);
-      },
-      [&](const ast::ReturnStatement* r) {  //
-        return EmitReturn(r);
-      },
-      [&](const ast::SwitchStatement* s) {  //
-        return EmitSwitch(s);
-      },
-      [&](const ast::VariableDeclStatement* v) {  //
-        return EmitVariable(v->variable);
-      },
-      [&](Default) {  //
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown statement type: " + std::string(stmt->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt) {
-  TINT_ASSERT(Writer, stmt->body.size() == 1 && stmt->body[0]->IsDefault());
-
-  // FXC fails to compile a switch with just a default case, ignoring the
-  // default case body. We work around this here by emitting the default case
-  // without the switch.
-
-  // Emit the switch condition as-is in case it has side-effects (e.g.
-  // function call). Note that's it's fine not to assign the result of the
-  // expression.
-  {
-    auto out = line();
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ";";
-  }
-
-  // Emit "do { <default case body> } while(false);". We use a 'do' loop so
-  // that break statements work as expected, and make it 'while (false)' in
-  // case there isn't a break statement.
-  line() << "do {";
-  {
-    ScopedIndent si(this);
-    if (!EmitStatements(stmt->body[0]->body->statements)) {
-      return false;
-    }
-  }
-  line() << "} while (false);";
-  return true;
-}
-
-bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
-  // BUG(crbug.com/tint/1188): work around default-only switches
-  if (stmt->body.size() == 1 && stmt->body[0]->IsDefault()) {
-    return EmitDefaultOnlySwitch(stmt);
-  }
-
-  {  // switch(expr) {
-    auto out = line();
-    out << "switch(";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-    for (size_t i = 0; i < stmt->body.size(); i++) {
-      if (!EmitCase(stmt, i)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
-                             ast::StorageClass storage_class,
-                             ast::Access access,
-                             const std::string& name,
-                             bool* name_printed /* = nullptr */) {
-  if (name_printed) {
-    *name_printed = false;
-  }
-  switch (storage_class) {
-    case ast::StorageClass::kStorage:
-      if (access != ast::Access::kRead) {
-        out << "RW";
-      }
-      out << "ByteAddressBuffer";
-      return true;
-    case ast::StorageClass::kUniform: {
-      auto array_length = (type->Size() + 15) / 16;
-      out << "uint4 " << name << "[" << array_length << "]";
-      if (name_printed) {
-        *name_printed = true;
-      }
-      return true;
-    }
-    default:
-      break;
-  }
-
-  return Switch(
-      type,
-      [&](const sem::Array* ary) {
-        const sem::Type* base_type = ary;
-        std::vector<uint32_t> sizes;
-        while (auto* arr = base_type->As<sem::Array>()) {
-          if (arr->IsRuntimeSized()) {
-            TINT_ICE(Writer, diagnostics_)
-                << "Runtime arrays may only exist in storage buffers, which "
-                   "should "
-                   "have been transformed into a ByteAddressBuffer";
-            return false;
-          }
-          sizes.push_back(arr->Count());
-          base_type = arr->ElemType();
-        }
-        if (!EmitType(out, base_type, storage_class, access, "")) {
-          return false;
-        }
-        if (!name.empty()) {
-          out << " " << name;
-          if (name_printed) {
-            *name_printed = true;
-          }
-        }
-        for (uint32_t size : sizes) {
-          out << "[" << size << "]";
-        }
-        return true;
-      },
-      [&](const sem::Bool*) {
-        out << "bool";
-        return true;
-      },
-      [&](const sem::F32*) {
-        out << "float";
-        return true;
-      },
-      [&](const sem::I32*) {
-        out << "int";
-        return true;
-      },
-      [&](const sem::Matrix* mat) {
-        if (!EmitType(out, mat->type(), storage_class, access, "")) {
-          return false;
-        }
-        // Note: HLSL's matrices are declared as <type>NxM, where N is the
-        // number of rows and M is the number of columns. Despite HLSL's
-        // matrices being column-major by default, the index operator and
-        // constructors actually operate on row-vectors, where as WGSL operates
-        // on column vectors. To simplify everything we use the transpose of the
-        // matrices. See:
-        // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-per-component-math#matrix-ordering
-        out << mat->columns() << "x" << mat->rows();
-        return true;
-      },
-      [&](const sem::Pointer*) {
-        TINT_ICE(Writer, diagnostics_)
-            << "Attempting to emit pointer type. These should have been "
-               "removed with the InlinePointerLets transform";
-        return false;
-      },
-      [&](const sem::Sampler* sampler) {
-        out << "Sampler";
-        if (sampler->IsComparison()) {
-          out << "Comparison";
-        }
-        out << "State";
-        return true;
-      },
-      [&](const sem::Struct* str) {
-        out << StructName(str);
-        return true;
-      },
-      [&](const sem::Texture* tex) {
-        auto* storage = tex->As<sem::StorageTexture>();
-        auto* ms = tex->As<sem::MultisampledTexture>();
-        auto* depth_ms = tex->As<sem::DepthMultisampledTexture>();
-        auto* sampled = tex->As<sem::SampledTexture>();
-
-        if (storage && storage->access() != ast::Access::kRead) {
-          out << "RW";
-        }
-        out << "Texture";
-
-        switch (tex->dim()) {
-          case ast::TextureDimension::k1d:
-            out << "1D";
-            break;
-          case ast::TextureDimension::k2d:
-            out << ((ms || depth_ms) ? "2DMS" : "2D");
-            break;
-          case ast::TextureDimension::k2dArray:
-            out << ((ms || depth_ms) ? "2DMSArray" : "2DArray");
-            break;
-          case ast::TextureDimension::k3d:
-            out << "3D";
-            break;
-          case ast::TextureDimension::kCube:
-            out << "Cube";
-            break;
-          case ast::TextureDimension::kCubeArray:
-            out << "CubeArray";
-            break;
-          default:
-            TINT_UNREACHABLE(Writer, diagnostics_)
-                << "unexpected TextureDimension " << tex->dim();
-            return false;
-        }
-
-        if (storage) {
-          auto* component =
-              image_format_to_rwtexture_type(storage->texel_format());
-          if (component == nullptr) {
-            TINT_ICE(Writer, diagnostics_)
-                << "Unsupported StorageTexture TexelFormat: "
-                << static_cast<int>(storage->texel_format());
-            return false;
-          }
-          out << "<" << component << ">";
-        } else if (depth_ms) {
-          out << "<float4>";
-        } else if (sampled || ms) {
-          auto* subtype = sampled ? sampled->type() : ms->type();
-          out << "<";
-          if (subtype->Is<sem::F32>()) {
-            out << "float4";
-          } else if (subtype->Is<sem::I32>()) {
-            out << "int4";
-          } else if (subtype->Is<sem::U32>()) {
-            out << "uint4";
-          } else {
-            TINT_ICE(Writer, diagnostics_)
-                << "Unsupported multisampled texture type";
-            return false;
-          }
-          out << ">";
-        }
-        return true;
-      },
-      [&](const sem::U32*) {
-        out << "uint";
-        return true;
-      },
-      [&](const sem::Vector* vec) {
-        auto width = vec->Width();
-        if (vec->type()->Is<sem::F32>() && width >= 1 && width <= 4) {
-          out << "float" << width;
-        } else if (vec->type()->Is<sem::I32>() && width >= 1 && width <= 4) {
-          out << "int" << width;
-        } else if (vec->type()->Is<sem::U32>() && width >= 1 && width <= 4) {
-          out << "uint" << width;
-        } else if (vec->type()->Is<sem::Bool>() && width >= 1 && width <= 4) {
-          out << "bool" << width;
-        } else {
-          out << "vector<";
-          if (!EmitType(out, vec->type(), storage_class, access, "")) {
-            return false;
-          }
-          out << ", " << width << ">";
-        }
-        return true;
-      },
-      [&](const sem::Atomic* atomic) {
-        return EmitType(out, atomic->Type(), storage_class, access, name);
-      },
-      [&](const sem::Void*) {
-        out << "void";
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(diag::System::Writer,
-                               "unknown type in EmitType");
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
-                                    ast::StorageClass storage_class,
-                                    ast::Access access,
-                                    const std::string& name) {
-  bool name_printed = false;
-  if (!EmitType(out, type, storage_class, access, name, &name_printed)) {
-    return false;
-  }
-  if (!name.empty() && !name_printed) {
-    out << " " << name;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
-  line(b) << "struct " << StructName(str) << " {";
-  {
-    ScopedIndent si(b);
-    for (auto* mem : str->Members()) {
-      auto mem_name = builder_.Symbols().NameFor(mem->Name());
-
-      auto* ty = mem->Type();
-
-      auto out = line(b);
-
-      std::string pre, post;
-
-      if (auto* decl = mem->Declaration()) {
-        for (auto* attr : decl->attributes) {
-          if (auto* location = attr->As<ast::LocationAttribute>()) {
-            auto& pipeline_stage_uses = str->PipelineStageUses();
-            if (pipeline_stage_uses.size() != 1) {
-              TINT_ICE(Writer, diagnostics_)
-                  << "invalid entry point IO struct uses";
-            }
-
-            if (pipeline_stage_uses.count(
-                    sem::PipelineStageUsage::kVertexInput)) {
-              post += " : TEXCOORD" + std::to_string(location->value);
-            } else if (pipeline_stage_uses.count(
-                           sem::PipelineStageUsage::kVertexOutput)) {
-              post += " : TEXCOORD" + std::to_string(location->value);
-            } else if (pipeline_stage_uses.count(
-                           sem::PipelineStageUsage::kFragmentInput)) {
-              post += " : TEXCOORD" + std::to_string(location->value);
-            } else if (pipeline_stage_uses.count(
-                           sem::PipelineStageUsage::kFragmentOutput)) {
-              post += " : SV_Target" + std::to_string(location->value);
-            } else {
-              TINT_ICE(Writer, diagnostics_)
-                  << "invalid use of location attribute";
-            }
-          } else if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-            auto name = builtin_to_attribute(builtin->builtin);
-            if (name.empty()) {
-              diagnostics_.add_error(diag::System::Writer,
-                                     "unsupported builtin");
-              return false;
-            }
-            post += " : " + name;
-          } else if (auto* interpolate =
-                         attr->As<ast::InterpolateAttribute>()) {
-            auto mod = interpolation_to_modifiers(interpolate->type,
-                                                  interpolate->sampling);
-            if (mod.empty()) {
-              diagnostics_.add_error(diag::System::Writer,
-                                     "unsupported interpolation");
-              return false;
-            }
-            pre += mod;
-
-          } else if (attr->Is<ast::InvariantAttribute>()) {
-            // Note: `precise` is not exactly the same as `invariant`, but is
-            // stricter and therefore provides the necessary guarantees.
-            // See discussion here: https://github.com/gpuweb/gpuweb/issues/893
-            pre += "precise ";
-          } else if (!attr->IsAnyOf<ast::StructMemberAlignAttribute,
-                                    ast::StructMemberOffsetAttribute,
-                                    ast::StructMemberSizeAttribute>()) {
-            TINT_ICE(Writer, diagnostics_)
-                << "unhandled struct member attribute: " << attr->Name();
-            return false;
-          }
-        }
-      }
-
-      out << pre;
-      if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, mem_name)) {
-        return false;
-      }
-      out << post << ";";
-    }
-  }
-
-  line(b) << "};";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
-                                const ast::UnaryOpExpression* expr) {
-  switch (expr->op) {
-    case ast::UnaryOp::kIndirection:
-    case ast::UnaryOp::kAddressOf:
-      return EmitExpression(out, expr->expr);
-    case ast::UnaryOp::kComplement:
-      out << "~";
-      break;
-    case ast::UnaryOp::kNot:
-      out << "!";
-      break;
-    case ast::UnaryOp::kNegation:
-      out << "-";
-      break;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitVariable(const ast::Variable* var) {
-  auto* sem = builder_.Sem().Get(var);
-  auto* type = sem->Type()->UnwrapRef();
-
-  // TODO(dsinclair): Handle variable attributes
-  if (!var->attributes.empty()) {
-    diagnostics_.add_error(diag::System::Writer,
-                           "Variable attributes are not handled yet");
-    return false;
-  }
-
-  auto out = line();
-  if (var->is_const) {
-    out << "const ";
-  }
-  if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                       builder_.Symbols().NameFor(var->symbol))) {
-    return false;
-  }
-
-  out << " = ";
-
-  if (var->constructor) {
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-  } else {
-    if (!EmitZeroValue(out, type)) {
-      return false;
-    }
-  }
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
-  for (auto* d : var->attributes) {
-    if (!d->Is<ast::IdAttribute>()) {
-      diagnostics_.add_error(diag::System::Writer,
-                             "Decorated const values not valid");
-      return false;
-    }
-  }
-  if (!var->is_const) {
-    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
-    return false;
-  }
-
-  auto* sem = builder_.Sem().Get(var);
-  auto* type = sem->Type();
-
-  auto* global = sem->As<sem::GlobalVariable>();
-  if (global && global->IsOverridable()) {
-    auto const_id = global->ConstantId();
-
-    line() << "#ifndef " << kSpecConstantPrefix << const_id;
-
-    if (var->constructor != nullptr) {
-      auto out = line();
-      out << "#define " << kSpecConstantPrefix << const_id << " ";
-      if (!EmitExpression(out, var->constructor)) {
-        return false;
-      }
-    } else {
-      line() << "#error spec constant required for constant id " << const_id;
-    }
-    line() << "#endif";
-    {
-      auto out = line();
-      out << "static const ";
-      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                           builder_.Symbols().NameFor(var->symbol))) {
-        return false;
-      }
-      out << " = " << kSpecConstantPrefix << const_id << ";";
-    }
-  } else {
-    auto out = line();
-    out << "static const ";
-    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                         builder_.Symbols().NameFor(var->symbol))) {
-      return false;
-    }
-    out << " = ";
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-    out << ";";
-  }
-
-  return true;
-}
-
-template <typename F>
-bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
-                                      const ast::CallExpression* call,
-                                      const sem::Builtin* builtin,
-                                      F&& build) {
-  // Generate the helper function if it hasn't been created already
-  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
-    TextBuffer b;
-    TINT_DEFER(helpers_.Append(b));
-
-    auto fn_name =
-        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
-    std::vector<std::string> parameter_names;
-    {
-      auto decl = line(&b);
-      if (!EmitTypeAndName(decl, builtin->ReturnType(),
-                           ast::StorageClass::kNone, ast::Access::kUndefined,
-                           fn_name)) {
-        return "";
-      }
-      {
-        ScopedParen sp(decl);
-        for (auto* param : builtin->Parameters()) {
-          if (!parameter_names.empty()) {
-            decl << ", ";
-          }
-          auto param_name = "param_" + std::to_string(parameter_names.size());
-          const auto* ty = param->Type();
-          if (auto* ptr = ty->As<sem::Pointer>()) {
-            decl << "inout ";
-            ty = ptr->StoreType();
-          }
-          if (!EmitTypeAndName(decl, ty, ast::StorageClass::kNone,
-                               ast::Access::kUndefined, param_name)) {
-            return "";
-          }
-          parameter_names.emplace_back(std::move(param_name));
-        }
-      }
-      decl << " {";
-    }
-    {
-      ScopedIndent si(&b);
-      if (!build(&b, parameter_names)) {
-        return "";
-      }
-    }
-    line(&b) << "}";
-    line(&b);
-    return fn_name;
-  });
-
-  if (fn.empty()) {
-    return false;
-  }
-
-  // Call the helper
-  out << fn;
-  {
-    ScopedParen sp(out);
-    bool first = true;
-    for (auto* arg : call->args) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-      if (!EmitExpression(out, arg)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
deleted file mode 100644
index 8685d3f..0000000
--- a/src/writer/hlsl/generator_impl.h
+++ /dev/null
@@ -1,553 +0,0 @@
-// Copyright 2020 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_WRITER_HLSL_GENERATOR_IMPL_H_
-#define SRC_WRITER_HLSL_GENERATOR_IMPL_H_
-
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/program_builder.h"
-#include "src/scope_stack.h"
-#include "src/sem/binding_point.h"
-#include "src/transform/decompose_memory_access.h"
-#include "src/utils/hash.h"
-#include "src/writer/array_length_from_uniform_options.h"
-#include "src/writer/text_generator.h"
-
-namespace tint {
-
-// Forward declarations
-namespace sem {
-class Call;
-class Builtin;
-class TypeConstructor;
-class TypeConversion;
-}  // namespace sem
-
-namespace writer {
-namespace hlsl {
-
-/// The result of sanitizing a program for generation.
-struct SanitizedResult {
-  /// Constructor
-  SanitizedResult();
-  /// Destructor
-  ~SanitizedResult();
-  /// Move constructor
-  SanitizedResult(SanitizedResult&&);
-
-  /// The sanitized program.
-  Program program;
-  /// Indices into the array_length_from_uniform binding that are statically
-  /// used.
-  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
-};
-
-/// Sanitize a program in preparation for generating HLSL.
-/// @param root_constant_binding_point the binding point to use for information
-/// that will be passed via root constants
-/// @param disable_workgroup_init `true` to disable workgroup memory zero
-/// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(
-    const Program* program,
-    sem::BindingPoint root_constant_binding_point = {},
-    bool disable_workgroup_init = false,
-    const ArrayLengthFromUniformOptions& array_length_from_uniform = {});
-
-/// Implementation class for HLSL generator
-class GeneratorImpl : public TextGenerator {
- public:
-  /// Constructor
-  /// @param program the program to generate
-  explicit GeneratorImpl(const Program* program);
-  ~GeneratorImpl();
-
-  /// @returns true on successful generation; false otherwise
-  bool Generate();
-
-  /// Handles an index accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the index accessor was emitted
-  bool EmitIndexAccessor(std::ostream& out,
-                         const ast::IndexAccessorExpression* expr);
-  /// Handles an assignment statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitAssign(const ast::AssignmentStatement* stmt);
-  /// Emits code such that if `expr` is zero, it emits one, else `expr`
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitExpressionOrOneIfZero(std::ostream& out,
-                                 const ast::Expression* expr);
-  /// Handles generating a binary expression
-  /// @param out the output of the expression stream
-  /// @param expr the binary expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
-  /// Handles generating a bitcast expression
-  /// @param out the output of the expression stream
-  /// @param expr the as expression
-  /// @returns true if the bitcast was emitted
-  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
-  /// Emits a list of statements
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatements(const ast::StatementList& stmts);
-  /// Emits a list of statements with an indentation
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
-  /// Handles a block statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBlock(const ast::BlockStatement* stmt);
-  /// Handles a break statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBreak(const ast::BreakStatement* stmt);
-  /// Handles generating a call expression
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a function call expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param function the function being called
-  /// @returns true if the expression is emitted
-  bool EmitFunctionCall(std::ostream& out,
-                        const sem::Call* call,
-                        const sem::Function* function);
-  /// Handles generating a builtin call expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the builtin being called
-  /// @returns true if the expression is emitted
-  bool EmitBuiltinCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a type conversion expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param conv the type conversion
-  /// @returns true if the expression is emitted
-  bool EmitTypeConversion(std::ostream& out,
-                          const sem::Call* call,
-                          const sem::TypeConversion* conv);
-  /// Handles generating a type constructor expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param ctor the type constructor
-  /// @returns true if the expression is emitted
-  bool EmitTypeConstructor(std::ostream& out,
-                           const sem::Call* call,
-                           const sem::TypeConstructor* ctor);
-  /// Handles generating a call expression to a
-  /// transform::DecomposeMemoryAccess::Intrinsic for a uniform buffer
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
-  /// @returns true if the call expression is emitted
-  bool EmitUniformBufferAccess(
-      std::ostream& out,
-      const ast::CallExpression* expr,
-      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
-  /// Handles generating a call expression to a
-  /// transform::DecomposeMemoryAccess::Intrinsic for a storage buffer
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
-  /// @returns true if the call expression is emitted
-  bool EmitStorageBufferAccess(
-      std::ostream& out,
-      const ast::CallExpression* expr,
-      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
-  /// Handles generating a barrier intrinsic call
-  /// @param out the output of the expression stream
-  /// @param builtin the semantic information for the barrier builtin
-  /// @returns true if the call expression is emitted
-  bool EmitBarrierCall(std::ostream& out, const sem::Builtin* builtin);
-  /// Handles generating an atomic intrinsic call for a storage buffer variable
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param intrinsic the atomic intrinsic
-  /// @returns true if the call expression is emitted
-  bool EmitStorageAtomicCall(
-      std::ostream& out,
-      const ast::CallExpression* expr,
-      const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
-  /// Handles generating an atomic intrinsic call for a workgroup variable
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the atomic builtin
-  /// @returns true if the call expression is emitted
-  bool EmitWorkgroupAtomicCall(std::ostream& out,
-                               const ast::CallExpression* expr,
-                               const sem::Builtin* builtin);
-  /// Handles generating a call to a texture function (`textureSample`,
-  /// `textureSampleGrad`, etc)
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitTextureCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `select()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitSelectCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a call to the `modf()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitModfCall(std::ostream& out,
-                    const ast::CallExpression* expr,
-                    const sem::Builtin* builtin);
-  /// Handles generating a call to the `frexp()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitFrexpCall(std::ostream& out,
-                     const ast::CallExpression* expr,
-                     const sem::Builtin* builtin);
-  /// Handles generating a call to the `isNormal()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitIsNormalCall(std::ostream& out,
-                        const ast::CallExpression* expr,
-                        const sem::Builtin* builtin);
-  /// Handles generating a call to the `degrees()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDegreesCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `radians()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitRadiansCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to data packing builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDataPackingCall(std::ostream& out,
-                           const ast::CallExpression* expr,
-                           const sem::Builtin* builtin);
-  /// Handles generating a call to data unpacking builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDataUnpackingCall(std::ostream& out,
-                             const ast::CallExpression* expr,
-                             const sem::Builtin* builtin);
-  /// Handles a case statement
-  /// @param s the switch statement
-  /// @param case_idx the index of the switch case in the switch statement
-  /// @returns true if the statement was emitted successfully
-  bool EmitCase(const ast::SwitchStatement* s, size_t case_idx);
-  /// Handles generating a discard statement
-  /// @param stmt the discard statement
-  /// @returns true if the statement was successfully emitted
-  bool EmitDiscard(const ast::DiscardStatement* stmt);
-  /// Handles a continue statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitContinue(const ast::ContinueStatement* stmt);
-  /// Handles generate an Expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the expression was emitted
-  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
-  /// Handles generating a function
-  /// @param func the function to generate
-  /// @returns true if the function was emitted
-  bool EmitFunction(const ast::Function* func);
-  /// Handles emitting the function body if it discards to work around a FXC
-  /// compilation bug.
-  /// @param func the function with the body to emit
-  /// @returns true if the function was emitted
-  bool EmitFunctionBodyWithDiscard(const ast::Function* func);
-  /// Handles emitting a global variable
-  /// @param global the global variable
-  /// @returns true on success
-  bool EmitGlobalVariable(const ast::Variable* global);
-
-  /// Handles emitting a global variable with the uniform storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitUniformVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the storage storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitStorageVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the handle storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitHandleVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the private storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitPrivateVariable(const sem::Variable* var);
-
-  /// Handles emitting a global variable with the workgroup storage class
-  /// @param var the global variable
-  /// @returns true on success
-  bool EmitWorkgroupVariable(const sem::Variable* var);
-
-  /// Handles emitting the entry point function
-  /// @param func the entry point
-  /// @returns true if the entry point function was emitted
-  bool EmitEntryPointFunction(const ast::Function* func);
-  /// Handles an if statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitIf(const ast::IfStatement* stmt);
-  /// Handles a literal
-  /// @param out the output stream
-  /// @param lit the literal to emit
-  /// @returns true if the literal was successfully emitted
-  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
-  /// Handles a loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitLoop(const ast::LoopStatement* stmt);
-  /// Handles a for loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitForLoop(const ast::ForLoopStatement* stmt);
-  /// Handles generating an identifier expression
-  /// @param out the output of the expression stream
-  /// @param expr the identifier expression
-  /// @returns true if the identifeir was emitted
-  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
-  /// Handles a member accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the member accessor expression
-  /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(std::ostream& out,
-                          const ast::MemberAccessorExpression* expr);
-  /// Handles return statements
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitReturn(const ast::ReturnStatement* stmt);
-  /// Handles statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitStatement(const ast::Statement* stmt);
-  /// Handles generating a switch statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitSwitch(const ast::SwitchStatement* stmt);
-  // Handles generating a switch statement with only a default case
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt);
-  /// Handles generating type
-  /// @param out the output stream
-  /// @param type the type to generate
-  /// @param storage_class the storage class of the variable
-  /// @param access the access control type of the variable
-  /// @param name the name of the variable, used for array emission.
-  /// @param name_printed (optional) if not nullptr and an array was printed
-  /// then the boolean is set to true.
-  /// @returns true if the type is emitted
-  bool EmitType(std::ostream& out,
-                const sem::Type* type,
-                ast::StorageClass storage_class,
-                ast::Access access,
-                const std::string& name,
-                bool* name_printed = nullptr);
-  /// Handles generating type and name
-  /// @param out the output stream
-  /// @param type the type to generate
-  /// @param storage_class the storage class of the variable
-  /// @param access the access control type of the variable
-  /// @param name the name to emit
-  /// @returns true if the type is emitted
-  bool EmitTypeAndName(std::ostream& out,
-                       const sem::Type* type,
-                       ast::StorageClass storage_class,
-                       ast::Access access,
-                       const std::string& name);
-  /// Handles generating a structure declaration
-  /// @param buffer the text buffer that the type declaration will be written to
-  /// @param ty the struct to generate
-  /// @returns true if the struct is emitted
-  bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty);
-  /// Handles a unary op expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the expression was emitted
-  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
-  /// Emits `value` for the given type
-  /// @param out the output stream
-  /// @param type the type to emit the value for
-  /// @param value the value to emit
-  /// @returns true if the value was successfully emitted.
-  bool EmitValue(std::ostream& out, const sem::Type* type, int value);
-  /// Emits the zero value for the given type
-  /// @param out the output stream
-  /// @param type the type to emit the value for
-  /// @returns true if the zero value was successfully emitted.
-  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
-  /// Handles generating a variable
-  /// @param var the variable to generate
-  /// @returns true if the variable was emitted
-  bool EmitVariable(const ast::Variable* var);
-  /// Handles generating a program scope constant variable
-  /// @param var the variable to emit
-  /// @returns true if the variable was emitted
-  bool EmitProgramConstVariable(const ast::Variable* var);
-  /// Emits call to a helper vector assignment function for the input assignment
-  /// statement and vector type. This is used to work around FXC issues where
-  /// assignments to vectors with dynamic indices cause compilation failures.
-  /// @param stmt assignment statement that corresponds to a vector assignment
-  /// via an accessor expression
-  /// @param vec the vector type being assigned to
-  /// @returns true on success
-  bool EmitDynamicVectorAssignment(const ast::AssignmentStatement* stmt,
-                                   const sem::Vector* vec);
-  /// Emits call to a helper matrix assignment function for the input assignment
-  /// statement and matrix type. This is used to work around FXC issues where
-  /// assignment of a vector to a matrix with a dynamic index causes compilation
-  /// failures.
-  /// @param stmt assignment statement that corresponds to a matrix assignment
-  /// via an accessor expression
-  /// @param mat the matrix type being assigned to
-  /// @returns true on success
-  bool EmitDynamicMatrixVectorAssignment(const ast::AssignmentStatement* stmt,
-                                         const sem::Matrix* mat);
-  /// Emits call to a helper matrix assignment function for the input assignment
-  /// statement and matrix type. This is used to work around FXC issues where
-  /// assignment of a scalar to a matrix with at least one dynamic index causes
-  /// compilation failures.
-  /// @param stmt assignment statement that corresponds to a matrix assignment
-  /// via an accessor expression
-  /// @param mat the matrix type being assigned to
-  /// @returns true on success
-  bool EmitDynamicMatrixScalarAssignment(const ast::AssignmentStatement* stmt,
-                                         const sem::Matrix* mat);
-
-  /// Handles generating a builtin method name
-  /// @param builtin the semantic info for the builtin
-  /// @returns the name or "" if not valid
-  std::string generate_builtin_name(const sem::Builtin* builtin);
-  /// Converts a builtin to an attribute name
-  /// @param builtin the builtin to convert
-  /// @returns the string name of the builtin or blank on error
-  std::string builtin_to_attribute(ast::Builtin builtin) const;
-
-  /// Converts interpolation attributes to a HLSL modifiers
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  /// @returns the string name of the attribute or blank on error
-  std::string interpolation_to_modifiers(
-      ast::InterpolationType type,
-      ast::InterpolationSampling sampling) const;
-
- private:
-  enum class VarType { kIn, kOut };
-
-  struct EntryPointData {
-    std::string struct_name;
-    std::string var_name;
-  };
-
-  struct DMAIntrinsic {
-    transform::DecomposeMemoryAccess::Intrinsic::Op op;
-    transform::DecomposeMemoryAccess::Intrinsic::DataType type;
-    bool operator==(const DMAIntrinsic& rhs) const {
-      return op == rhs.op && type == rhs.type;
-    }
-    /// Hasher is a std::hash function for DMAIntrinsic
-    struct Hasher {
-      /// @param i the DMAIntrinsic to hash
-      /// @returns the hash of `i`
-      inline std::size_t operator()(const DMAIntrinsic& i) const {
-        return utils::Hash(i.op, i.type);
-      }
-    };
-  };
-
-  /// CallBuiltinHelper will call the builtin helper function, creating it
-  /// if it hasn't been built already. If the builtin needs to be built then
-  /// CallBuiltinHelper will generate the function signature and will call
-  /// `build` to emit the body of the function.
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @param build a function with the signature:
-  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
-  ///        Where:
-  ///          `buffer` is the body of the generated function
-  ///          `params` is the name of all the generated function parameters
-  /// @returns true if the call expression is emitted
-  template <typename F>
-  bool CallBuiltinHelper(std::ostream& out,
-                         const ast::CallExpression* call,
-                         const sem::Builtin* builtin,
-                         F&& build);
-
-  TextBuffer helpers_;  // Helper functions emitted at the top of the output
-  std::function<bool()> emit_continuing_;
-  std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher>
-      dma_intrinsics_;
-  std::unordered_map<const sem::Builtin*, std::string> builtins_;
-  std::unordered_map<const sem::Struct*, std::string> structure_builders_;
-  std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
-  std::unordered_map<const sem::Matrix*, std::string>
-      dynamic_matrix_vector_write_;
-  std::unordered_map<const sem::Matrix*, std::string>
-      dynamic_matrix_scalar_write_;
-  std::unordered_map<const sem::Type*, std::string> value_or_one_if_zero_;
-};
-
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_HLSL_GENERATOR_IMPL_H_
diff --git a/src/writer/hlsl/generator_impl_array_accessor_test.cc b/src/writer/hlsl/generator_impl_array_accessor_test.cc
deleted file mode 100644
index 00c75e4..0000000
--- a/src/writer/hlsl/generator_impl_array_accessor_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Expression = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Expression, IndexAccessor) {
-  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
-  auto* expr = IndexAccessor("ary", 5);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "ary[5]");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_assign_test.cc b/src/writer/hlsl/generator_impl_assign_test.cc
deleted file mode 100644
index 98d9eb6..0000000
--- a/src/writer/hlsl/generator_impl_assign_test.cc
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Assign = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Assign) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.i32())),
-           Decl(Var("rhs", ty.i32())),
-           Assign("lhs", "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(),
-            R"(void fn() {
-  int lhs = 0;
-  int rhs = 0;
-  lhs = rhs;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_ConstantIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.vec3<f32>())),
-           Decl(Var("rhs", ty.f32())),
-           Decl(Const("index", ty.u32(), Expr(0u))),
-           Assign(IndexAccessor("lhs", "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(),
-            R"(void fn() {
-  float3 lhs = float3(0.0f, 0.0f, 0.0f);
-  float rhs = 0.0f;
-  const uint index = 0u;
-  lhs[index] = rhs;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_DynamicIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.vec3<f32>())),
-           Decl(Var("rhs", ty.f32())),
-           Decl(Var("index", ty.u32())),
-           Assign(IndexAccessor("lhs", "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(),
-            R"(void set_float3(inout float3 vec, int idx, float val) {
-  vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;
-}
-
-void fn() {
-  float3 lhs = float3(0.0f, 0.0f, 0.0f);
-  float rhs = 0.0f;
-  uint index = 0u;
-  set_float3(lhs, index, rhs);
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_ConstantIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.mat4x2<f32>())),
-           Decl(Var("rhs", ty.vec2<f32>())),
-           Decl(Const("index", ty.u32(), Expr(0u))),
-           Assign(IndexAccessor("lhs", "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(),
-            R"(void fn() {
-  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-  float2 rhs = float2(0.0f, 0.0f);
-  const uint index = 0u;
-  lhs[index] = rhs;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_DynamicIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.mat4x2<f32>())),
-           Decl(Var("rhs", ty.vec2<f32>())),
-           Decl(Var("index", ty.u32())),
-           Assign(IndexAccessor("lhs", "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(
-      gen.result(),
-      R"(void set_vector_float4x2(inout float4x2 mat, int col, float2 val) {
-  switch (col) {
-    case 0: mat[0] = val; break;
-    case 1: mat[1] = val; break;
-    case 2: mat[2] = val; break;
-    case 3: mat[3] = val; break;
-  }
-}
-
-void fn() {
-  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-  float2 rhs = float2(0.0f, 0.0f);
-  uint index = 0u;
-  set_vector_float4x2(lhs, index, rhs);
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_ConstantIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.mat4x2<f32>())),
-           Decl(Var("rhs", ty.f32())),
-           Decl(Const("index", ty.u32(), Expr(0u))),
-           Assign(IndexAccessor(IndexAccessor("lhs", "index"), "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(),
-            R"(void fn() {
-  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-  float rhs = 0.0f;
-  const uint index = 0u;
-  lhs[index][index] = rhs;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_DynamicIndex) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("lhs", ty.mat4x2<f32>())),
-           Decl(Var("rhs", ty.f32())),
-           Decl(Var("index", ty.u32())),
-           Assign(IndexAccessor(IndexAccessor("lhs", "index"), "index"), "rhs"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(
-      gen.result(),
-      R"(void set_scalar_float4x2(inout float4x2 mat, int col, int row, float val) {
-  switch (col) {
-    case 0:
-      mat[0] = (row.xx == int2(0, 1)) ? val.xx : mat[0];
-      break;
-    case 1:
-      mat[1] = (row.xx == int2(0, 1)) ? val.xx : mat[1];
-      break;
-    case 2:
-      mat[2] = (row.xx == int2(0, 1)) ? val.xx : mat[2];
-      break;
-    case 3:
-      mat[3] = (row.xx == int2(0, 1)) ? val.xx : mat[3];
-      break;
-  }
-}
-
-void fn() {
-  float4x2 lhs = float4x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-  float rhs = 0.0f;
-  uint index = 0u;
-  set_scalar_float4x2(lhs, index, index, rhs);
-}
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_binary_test.cc b/src/writer/hlsl/generator_impl_binary_test.cc
deleted file mode 100644
index 7714660..0000000
--- a/src/writer/hlsl/generator_impl_binary_test.cc
+++ /dev/null
@@ -1,828 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Binary = TestHelper;
-
-struct BinaryData {
-  const char* result;
-  ast::BinaryOp op;
-
-  enum Types { All = 0b11, Integer = 0b10, Float = 0b01 };
-  Types valid_for = Types::All;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << data.op;
-  return out;
-}
-
-using HlslBinaryTest = TestParamHelper<BinaryData>;
-TEST_P(HlslBinaryTest, Emit_f32) {
-  auto params = GetParam();
-
-  if ((params.valid_for & BinaryData::Types::Float) == 0) {
-    return;
-  }
-
-  // Skip ops that are illegal for this type
-  if (params.op == ast::BinaryOp::kAnd || params.op == ast::BinaryOp::kOr ||
-      params.op == ast::BinaryOp::kXor ||
-      params.op == ast::BinaryOp::kShiftLeft ||
-      params.op == ast::BinaryOp::kShiftRight) {
-    return;
-  }
-
-  Global("left", ty.f32(), ast::StorageClass::kPrivate);
-  Global("right", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-TEST_P(HlslBinaryTest, Emit_u32) {
-  auto params = GetParam();
-
-  if ((params.valid_for & BinaryData::Types::Integer) == 0) {
-    return;
-  }
-
-  Global("left", ty.u32(), ast::StorageClass::kPrivate);
-  Global("right", ty.u32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-TEST_P(HlslBinaryTest, Emit_i32) {
-  auto params = GetParam();
-
-  if ((params.valid_for & BinaryData::Types::Integer) == 0) {
-    return;
-  }
-
-  // Skip ops that are illegal for this type
-  if (params.op == ast::BinaryOp::kShiftLeft ||
-      params.op == ast::BinaryOp::kShiftRight) {
-    return;
-  }
-
-  Global("left", ty.i32(), ast::StorageClass::kPrivate);
-  Global("right", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest,
-    HlslBinaryTest,
-    testing::Values(
-        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
-        BinaryData{"(left | right)", ast::BinaryOp::kOr},
-        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
-        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
-        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
-        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
-        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
-        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
-        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
-        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
-        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
-        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
-        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
-        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
-        // NOTE: Integer divide covered by DivOrModBy* tests below
-        BinaryData{"(left / right)", ast::BinaryOp::kDivide,
-                   BinaryData::Types::Float},
-        // NOTE: Integer modulo covered by DivOrModBy* tests below
-        BinaryData{"(left % right)", ast::BinaryOp::kModulo,
-                   BinaryData::Types::Float}));
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_VectorScalar) {
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = Expr(1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            "(float3(1.0f, 1.0f, 1.0f) * "
-            "1.0f)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_ScalarVector) {
-  auto* lhs = Expr(1.f);
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            "(1.0f * float3(1.0f, 1.0f, "
-            "1.0f))");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixScalar) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr("mat");
-  auto* rhs = Expr(1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(mat * 1.0f)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr(1.f);
-  auto* rhs = Expr("mat");
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(1.0f * mat)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixVector) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = Expr("mat");
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "mul(float3(1.0f, 1.0f, 1.0f), mat)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
-  Global("mat", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = Expr("mat");
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "mul(mat, float3(1.0f, 1.0f, 1.0f))");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
-  Global("lhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                             Expr("lhs"), Expr("rhs"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "mul(rhs, lhs)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Logical_And) {
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                             Expr("a"), Expr("b"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Logical_Multi) {
-  // (a && b) || (c || d)
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalOr,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
-                                    Expr("b")),
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"),
-                                    Expr("d")));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
-if (tint_tmp_1) {
-  tint_tmp_1 = b;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  bool tint_tmp_2 = c;
-  if (!tint_tmp_2) {
-    tint_tmp_2 = d;
-  }
-  tint_tmp = (tint_tmp_2);
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Logical_Or) {
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                             Expr("a"), Expr("b"));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(tint_tmp)");
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (!tint_tmp) {
-  tint_tmp = b;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, If_WithLogical) {
-  // if (a && b) {
-  //   return 1;
-  // } else if (b || c) {
-  //   return 2;
-  // } else {
-  //   return 3;
-  // }
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                Expr("a"), Expr("b")),
-                  Block(Return(1)),
-                  Else(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                     Expr("b"), Expr("c")),
-                       Block(Return(2))),
-                  Else(Block(Return(3))));
-  Func("func", {}, ty.i32(), {WrapInStatement(expr)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-if ((tint_tmp)) {
-  return 1;
-} else {
-  bool tint_tmp_1 = b;
-  if (!tint_tmp_1) {
-    tint_tmp_1 = c;
-  }
-  if ((tint_tmp_1)) {
-    return 2;
-  } else {
-    return 3;
-  }
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Return_WithLogical) {
-  // return (a && b) || c;
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = Return(create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalOr,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"),
-                                    Expr("b")),
-      Expr("c")));
-  Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
-if (tint_tmp_1) {
-  tint_tmp_1 = b;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  tint_tmp = c;
-}
-return (tint_tmp);
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Assign_WithLogical) {
-  // a = (b || c) && d;
-
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* expr = Assign(
-      Expr("a"), create<ast::BinaryExpression>(
-                     ast::BinaryOp::kLogicalAnd,
-                     create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                   Expr("b"), Expr("c")),
-                     Expr("d")));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
-if (!tint_tmp_1) {
-  tint_tmp_1 = c;
-}
-bool tint_tmp = (tint_tmp_1);
-if (tint_tmp) {
-  tint_tmp = d;
-}
-a = (tint_tmp);
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Decl_WithLogical) {
-  // var a : bool = (b && c) || d;
-
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* var = Var("a", ty.bool_(), ast::StorageClass::kNone,
-                  create<ast::BinaryExpression>(
-                      ast::BinaryOp::kLogicalOr,
-                      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                    Expr("b"), Expr("c")),
-                      Expr("d")));
-
-  auto* decl = Decl(var);
-  WrapInFunction(decl);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
-if (tint_tmp_1) {
-  tint_tmp_1 = c;
-}
-bool tint_tmp = (tint_tmp_1);
-if (!tint_tmp) {
-  tint_tmp = d;
-}
-bool a = (tint_tmp);
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Binary, Call_WithLogical) {
-  // foo(a && b, c || d, (a || c) && (b || d))
-
-  Func("foo",
-       {
-           Param(Sym(), ty.bool_()),
-           Param(Sym(), ty.bool_()),
-           Param(Sym(), ty.bool_()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("b", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("c", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("d", ty.bool_(), ast::StorageClass::kPrivate);
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                 Expr("a"), Expr("b")));
-  params.push_back(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                                 Expr("c"), Expr("d")));
-  params.push_back(create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalAnd,
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"),
-                                    Expr("c")),
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"),
-                                    Expr("d"))));
-
-  auto* expr = CallStmt(Call("foo", params));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
-}
-bool tint_tmp_1 = c;
-if (!tint_tmp_1) {
-  tint_tmp_1 = d;
-}
-bool tint_tmp_3 = a;
-if (!tint_tmp_3) {
-  tint_tmp_3 = c;
-}
-bool tint_tmp_2 = (tint_tmp_3);
-if (tint_tmp_2) {
-  bool tint_tmp_4 = b;
-  if (!tint_tmp_4) {
-    tint_tmp_4 = d;
-  }
-  tint_tmp_2 = (tint_tmp_4);
-}
-foo((tint_tmp), (tint_tmp_1), (tint_tmp_2));
-)");
-}
-
-namespace HlslGeneratorDivMod {
-
-struct Params {
-  enum class Type { Div, Mod };
-  Type type;
-};
-
-struct HlslGeneratorDivModTest : TestParamHelper<Params> {
-  std::string Token() {
-    return GetParam().type == Params::Type::Div ? "/" : "%";
-  }
-
-  template <typename... Args>
-  auto Op(Args... args) {
-    return GetParam().type == Params::Type::Div
-               ? Div(std::forward<Args>(args)...)
-               : Mod(std::forward<Args>(args)...);
-  }
-};
-
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
-                         HlslGeneratorDivModTest,
-                         testing::Values(Params{Params::Type::Div},
-                                         Params{Params::Type::Mod}));
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_i32) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.i32())),
-           Decl(Const("r", nullptr, Op("a", 0))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn() {
-  int a = 0;
-  const int r = (a )" + Token() +
-                              R"( 1);
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_u32) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.u32())),
-           Decl(Const("r", nullptr, Op("a", 0u))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn() {
-  uint a = 0u;
-  const uint r = (a )" + Token() +
-                              R"( 1u);
-}
-)");
-}  // namespace HlslGeneratorDivMod
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_vec_i32) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", nullptr, vec4<i32>(100, 100, 100, 100))),
-           Decl(Const("r", nullptr, Op("a", vec4<i32>(50, 0, 25, 0)))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn() {
-  int4 a = int4(100, 100, 100, 100);
-  const int4 r = (a )" + Token() +
-                              R"( int4(50, 1, 25, 1));
-}
-)");
-}  // namespace
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_scalar_i32) {
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", nullptr, vec4<i32>(100, 100, 100, 100))),
-           Decl(Const("r", nullptr, Op("a", 0))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn() {
-  int4 a = int4(100, 100, 100, 100);
-  const int4 r = (a )" + Token() +
-                              R"( 1);
-}
-)");
-}  // namespace hlsl
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_i32) {
-  Func("fn", {Param("b", ty.i32())}, ty.void_(),
-       {
-           Decl(Var("a", ty.i32())),
-           Decl(Const("r", nullptr, Op("a", "b"))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn(int b) {
-  int a = 0;
-  const int r = (a )" + Token() +
-                              R"( (b == 0 ? 1 : b));
-}
-)");
-}  // namespace writer
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_u32) {
-  Func("fn", {Param("b", ty.u32())}, ty.void_(),
-       {
-           Decl(Var("a", ty.u32())),
-           Decl(Const("r", nullptr, Op("a", "b"))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn(uint b) {
-  uint a = 0u;
-  const uint r = (a )" + Token() +
-                              R"( (b == 0u ? 1u : b));
-}
-)");
-}  // namespace tint
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_vec_i32) {
-  Func("fn", {Param("b", ty.vec3<i32>())}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec3<i32>())),
-           Decl(Const("r", nullptr, Op("a", "b"))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn(int3 b) {
-  int3 a = int3(0, 0, 0);
-  const int3 r = (a )" + Token() +
-                              R"( (b == int3(0, 0, 0) ? int3(1, 1, 1) : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_scalar_i32) {
-  Func("fn", {Param("b", ty.i32())}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec3<i32>())),
-           Decl(Const("r", nullptr, Op("a", "b"))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(void fn(int b) {
-  int3 a = int3(0, 0, 0);
-  const int3 r = (a )" + Token() +
-                              R"( (b == 0 ? 1 : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_i32) {
-  Func("zero", {}, ty.i32(),
-       {
-           Return(Expr(0)),
-       });
-
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.i32())),
-           Decl(Const("r", nullptr, Op("a", Call("zero")))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
-  return value == 0 ? 1 : value;
-}
-
-int zero() {
-  return 0;
-}
-
-void fn() {
-  int a = 0;
-  const int r = (a )" + Token() +
-                              R"( value_or_one_if_zero_int(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_u32) {
-  Func("zero", {}, ty.u32(),
-       {
-           Return(Expr(0u)),
-       });
-
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.u32())),
-           Decl(Const("r", nullptr, Op("a", Call("zero")))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(uint value_or_one_if_zero_uint(uint value) {
-  return value == 0u ? 1u : value;
-}
-
-uint zero() {
-  return 0u;
-}
-
-void fn() {
-  uint a = 0u;
-  const uint r = (a )" + Token() +
-                              R"( value_or_one_if_zero_uint(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_vec_i32) {
-  Func("zero", {}, ty.vec3<i32>(),
-       {
-           Return(vec3<i32>(0, 0, 0)),
-       });
-
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec3<i32>())),
-           Decl(Const("r", nullptr, Op("a", Call("zero")))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(int3 value_or_one_if_zero_int3(int3 value) {
-  return value == int3(0, 0, 0) ? int3(1, 1, 1) : value;
-}
-
-int3 zero() {
-  return int3(0, 0, 0);
-}
-
-void fn() {
-  int3 a = int3(0, 0, 0);
-  const int3 r = (a )" + Token() +
-                              R"( value_or_one_if_zero_int3(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_scalar_i32) {
-  Func("zero", {}, ty.i32(),
-       {
-           Return(0),
-       });
-
-  Func("fn", {}, ty.void_(),
-       {
-           Decl(Var("a", ty.vec3<i32>())),
-           Decl(Const("r", nullptr, Op("a", Call("zero")))),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate());
-  EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
-  return value == 0 ? 1 : value;
-}
-
-int zero() {
-  return 0;
-}
-
-void fn() {
-  int3 a = int3(0, 0, 0);
-  const int3 r = (a )" + Token() +
-                              R"( value_or_one_if_zero_int(zero()));
-}
-)");
-}
-}  // namespace HlslGeneratorDivMod
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_bitcast_test.cc b/src/writer/hlsl/generator_impl_bitcast_test.cc
deleted file mode 100644
index 9cb97d8..0000000
--- a/src/writer/hlsl/generator_impl_bitcast_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Bitcast = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Float) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "asfloat(1)");
-}
-
-TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.i32(), Expr(1u));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "asint(1u)");
-}
-
-TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "asuint(1)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_block_test.cc b/src/writer/hlsl/generator_impl_block_test.cc
deleted file mode 100644
index 4aec6a3..0000000
--- a/src/writer/hlsl/generator_impl_block_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Block = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Block, Emit_Block) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    discard;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_break_test.cc b/src/writer/hlsl/generator_impl_break_test.cc
deleted file mode 100644
index b2f532f..0000000
--- a/src/writer/hlsl/generator_impl_break_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Break = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Break, Emit_Break) {
-  auto* b = create<ast::BreakStatement>();
-  WrapInFunction(Loop(Block(b)));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), "  break;\n");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_builtin_test.cc b/src/writer/hlsl/generator_impl_builtin_test.cc
deleted file mode 100644
index 5b38b36..0000000
--- a/src/writer/hlsl/generator_impl_builtin_test.cc
+++ /dev/null
@@ -1,790 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/sem/call.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using BuiltinType = sem::BuiltinType;
-
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_Builtin = TestHelper;
-
-enum class ParamType {
-  kF32,
-  kU32,
-  kBool,
-};
-
-struct BuiltinData {
-  BuiltinType builtin;
-  ParamType type;
-  const char* hlsl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.hlsl_name;
-  switch (data.type) {
-    case ParamType::kF32:
-      out << "f32";
-      break;
-    case ParamType::kU32:
-      out << "u32";
-      break;
-    case ParamType::kBool:
-      out << "bool";
-      break;
-  }
-  out << ">";
-  return out;
-}
-
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
-                                        ParamType type,
-                                        ProgramBuilder* builder) {
-  std::string name;
-  std::ostringstream str(name);
-  str << builtin;
-  switch (builtin) {
-    case BuiltinType::kAcos:
-    case BuiltinType::kAsin:
-    case BuiltinType::kAtan:
-    case BuiltinType::kCeil:
-    case BuiltinType::kCos:
-    case BuiltinType::kCosh:
-    case BuiltinType::kDpdx:
-    case BuiltinType::kDpdxCoarse:
-    case BuiltinType::kDpdxFine:
-    case BuiltinType::kDpdy:
-    case BuiltinType::kDpdyCoarse:
-    case BuiltinType::kDpdyFine:
-    case BuiltinType::kExp:
-    case BuiltinType::kExp2:
-    case BuiltinType::kFloor:
-    case BuiltinType::kFract:
-    case BuiltinType::kFwidth:
-    case BuiltinType::kFwidthCoarse:
-    case BuiltinType::kFwidthFine:
-    case BuiltinType::kInverseSqrt:
-    case BuiltinType::kIsFinite:
-    case BuiltinType::kIsInf:
-    case BuiltinType::kIsNan:
-    case BuiltinType::kIsNormal:
-    case BuiltinType::kLength:
-    case BuiltinType::kLog:
-    case BuiltinType::kLog2:
-    case BuiltinType::kNormalize:
-    case BuiltinType::kRound:
-    case BuiltinType::kSin:
-    case BuiltinType::kSinh:
-    case BuiltinType::kSqrt:
-    case BuiltinType::kTan:
-    case BuiltinType::kTanh:
-    case BuiltinType::kTrunc:
-    case BuiltinType::kSign:
-      return builder->Call(str.str(), "f2");
-    case BuiltinType::kLdexp:
-      return builder->Call(str.str(), "f2", "i2");
-    case BuiltinType::kAtan2:
-    case BuiltinType::kDot:
-    case BuiltinType::kDistance:
-    case BuiltinType::kPow:
-    case BuiltinType::kReflect:
-    case BuiltinType::kStep:
-      return builder->Call(str.str(), "f2", "f2");
-    case BuiltinType::kCross:
-      return builder->Call(str.str(), "f3", "f3");
-    case BuiltinType::kFma:
-    case BuiltinType::kMix:
-    case BuiltinType::kFaceForward:
-    case BuiltinType::kSmoothStep:
-      return builder->Call(str.str(), "f2", "f2", "f2");
-    case BuiltinType::kAll:
-    case BuiltinType::kAny:
-      return builder->Call(str.str(), "b2");
-    case BuiltinType::kAbs:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2");
-      } else {
-        return builder->Call(str.str(), "u2");
-      }
-    case BuiltinType::kCountOneBits:
-    case BuiltinType::kReverseBits:
-      return builder->Call(str.str(), "u2");
-    case BuiltinType::kMax:
-    case BuiltinType::kMin:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2");
-      }
-    case BuiltinType::kClamp:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2", "u2");
-      }
-    case BuiltinType::kSelect:
-      return builder->Call(str.str(), "f2", "f2", "b2");
-    case BuiltinType::kDeterminant:
-      return builder->Call(str.str(), "m2x2");
-    case BuiltinType::kTranspose:
-      return builder->Call(str.str(), "m3x2");
-    default:
-      break;
-  }
-  return nullptr;
-}
-using HlslBuiltinTest = TestParamHelper<BuiltinData>;
-TEST_P(HlslBuiltinTest, Emit) {
-  auto param = GetParam();
-
-  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
-  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
-  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
-  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = GenerateCall(param.builtin, param.type, this);
-  ASSERT_NE(nullptr, call) << "Unhandled builtin";
-  Func("func", {}, ty.void_(), {CallStmt(call)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem = program->Sem().Get(call);
-  ASSERT_NE(sem, nullptr);
-  auto* target = sem->Target();
-  ASSERT_NE(target, nullptr);
-  auto* builtin = target->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-
-  EXPECT_EQ(gen.generate_builtin_name(builtin), param.hlsl_name);
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_Builtin,
-    HlslBuiltinTest,
-    testing::Values(
-        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "abs"},
-        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
-        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
-        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
-        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
-        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
-        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
-        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan2"},
-        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
-        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
-        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
-        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "countbits"},
-        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
-        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
-        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
-        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
-        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "ddx"},
-        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "ddx_coarse"},
-        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "ddx_fine"},
-        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "ddy"},
-        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "ddy_coarse"},
-        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "ddy_fine"},
-        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
-        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
-        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
-        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
-        BuiltinData{BuiltinType::kFma, ParamType::kF32, "mad"},
-        BuiltinData{BuiltinType::kFract, ParamType::kF32, "frac"},
-        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
-        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
-        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
-        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
-        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
-        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
-        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
-        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
-        BuiltinData{BuiltinType::kMax, ParamType::kF32, "max"},
-        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
-        BuiltinData{BuiltinType::kMin, ParamType::kF32, "min"},
-        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
-        BuiltinData{BuiltinType::kMix, ParamType::kF32, "lerp"},
-        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
-        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
-        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
-        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reversebits"},
-        BuiltinData{BuiltinType::kRound, ParamType::kU32, "round"},
-        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
-        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
-        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
-        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
-        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
-        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
-        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
-        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
-        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
-        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"}));
-
-TEST_F(HlslGeneratorImplTest_Builtin, DISABLED_Builtin_IsNormal) {
-  FAIL();
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Builtin_Call) {
-  auto* call = Call("dot", "param1", "param2");
-
-  Global("param1", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  Global("param2", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "dot(param1, param2)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Select_Scalar) {
-  auto* call = Call("select", 1.0f, 2.0f, true);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "(true ? 2.0f : 1.0f)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Select_Vector) {
-  auto* call =
-      Call("select", vec2<i32>(1, 2), vec2<i32>(3, 4), vec2<bool>(true, false));
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "(bool2(true, false) ? int2(3, 4) : int2(1, 2))");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Modf_Scalar) {
-  auto* call = Call("modf", 1.0f);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct modf_result {
-  float fract;
-  float whole;
-};
-modf_result tint_modf(float param_0) {
-  float whole;
-  float fract = modf(param_0, whole);
-  modf_result result = {fract, whole};
-  return result;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_modf(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Modf_Vector) {
-  auto* call = Call("modf", vec3<f32>());
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct modf_result_vec3 {
-  float3 fract;
-  float3 whole;
-};
-modf_result_vec3 tint_modf(float3 param_0) {
-  float3 whole;
-  float3 fract = modf(param_0, whole);
-  modf_result_vec3 result = {fract, whole};
-  return result;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_modf(float3(0.0f, 0.0f, 0.0f));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
-  auto* call = Call("frexp", 1.0f);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct frexp_result {
-  float sig;
-  int exp;
-};
-frexp_result tint_frexp(float param_0) {
-  float exp;
-  float sig = frexp(param_0, exp);
-  frexp_result result = {sig, int(exp)};
-  return result;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_frexp(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
-  auto* call = Call("frexp", vec3<f32>());
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct frexp_result_vec3 {
-  float3 sig;
-  int3 exp;
-};
-frexp_result_vec3 tint_frexp(float3 param_0) {
-  float3 exp;
-  float3 sig = frexp(param_0, exp);
-  frexp_result_vec3 result = {sig, int3(exp)};
-  return result;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_frexp(float3(0.0f, 0.0f, 0.0f));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, IsNormal_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("isNormal", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool tint_isNormal(float param_0) {
-  uint exponent = asuint(param_0) & 0x7f80000;
-  uint clamped = clamp(exponent, 0x0080000, 0x7f00000);
-  return clamped == exponent;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float val = 0.0f;
-  const bool tint_symbol = tint_isNormal(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, IsNormal_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("isNormal", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(bool3 tint_isNormal(float3 param_0) {
-  uint3 exponent = asuint(param_0) & 0x7f80000;
-  uint3 clamped = clamp(exponent, 0x0080000, 0x7f00000);
-  return clamped == exponent;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float3 val = float3(0.0f, 0.0f, 0.0f);
-  const bool3 tint_symbol = tint_isNormal(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Degrees_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float tint_degrees(float param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float val = 0.0f;
-  const float tint_symbol = tint_degrees(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Degrees_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float3 tint_degrees(float3 param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float3 val = float3(0.0f, 0.0f, 0.0f);
-  const float3 tint_symbol = tint_degrees(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Radians_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float tint_radians(float param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float val = 0.0f;
-  const float tint_symbol = tint_radians(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Radians_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float3 tint_radians(float3 param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float3 val = float3(0.0f, 0.0f, 0.0f);
-  const float3 tint_symbol = tint_radians(val);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Pack4x8Snorm) {
-  auto* call = Call("pack4x8snorm", "p1");
-  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(uint tint_pack4x8snorm(float4 param_0) {
-  int4 i = int4(round(clamp(param_0, -1.0, 1.0) * 127.0)) & 0xff;
-  return asuint(i.x | i.y << 8 | i.z << 16 | i.w << 24);
-}
-
-static float4 p1 = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_pack4x8snorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Pack4x8Unorm) {
-  auto* call = Call("pack4x8unorm", "p1");
-  Global("p1", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(uint tint_pack4x8unorm(float4 param_0) {
-  uint4 i = uint4(round(clamp(param_0, 0.0, 1.0) * 255.0));
-  return (i.x | i.y << 8 | i.z << 16 | i.w << 24);
-}
-
-static float4 p1 = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_pack4x8unorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Snorm) {
-  auto* call = Call("pack2x16snorm", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16snorm(float2 param_0) {
-  int2 i = int2(round(clamp(param_0, -1.0, 1.0) * 32767.0)) & 0xffff;
-  return asuint(i.x | i.y << 16);
-}
-
-static float2 p1 = float2(0.0f, 0.0f);
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_pack2x16snorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Unorm) {
-  auto* call = Call("pack2x16unorm", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16unorm(float2 param_0) {
-  uint2 i = uint2(round(clamp(param_0, 0.0, 1.0) * 65535.0));
-  return (i.x | i.y << 16);
-}
-
-static float2 p1 = float2(0.0f, 0.0f);
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_pack2x16unorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Pack2x16Float) {
-  auto* call = Call("pack2x16float", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(uint tint_pack2x16float(float2 param_0) {
-  uint2 i = f32tof16(param_0);
-  return i.x | (i.y << 16);
-}
-
-static float2 p1 = float2(0.0f, 0.0f);
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_pack2x16float(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Unpack4x8Snorm) {
-  auto* call = Call("unpack4x8snorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float4 tint_unpack4x8snorm(uint param_0) {
-  int j = int(param_0);
-  int4 i = int4(j << 24, j << 16, j << 8, j) >> 24;
-  return clamp(float4(i) / 127.0, -1.0, 1.0);
-}
-
-static uint p1 = 0u;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_unpack4x8snorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Unpack4x8Unorm) {
-  auto* call = Call("unpack4x8unorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float4 tint_unpack4x8unorm(uint param_0) {
-  uint j = param_0;
-  uint4 i = uint4(j & 0xff, (j >> 8) & 0xff, (j >> 16) & 0xff, j >> 24);
-  return float4(i) / 255.0;
-}
-
-static uint p1 = 0u;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_unpack4x8unorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Snorm) {
-  auto* call = Call("unpack2x16snorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16snorm(uint param_0) {
-  int j = int(param_0);
-  int2 i = int2(j << 16, j) >> 16;
-  return clamp(float2(i) / 32767.0, -1.0, 1.0);
-}
-
-static uint p1 = 0u;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_unpack2x16snorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Unorm) {
-  auto* call = Call("unpack2x16unorm", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16unorm(uint param_0) {
-  uint j = param_0;
-  uint2 i = uint2(j & 0xffff, j >> 16);
-  return float2(i) / 65535.0;
-}
-
-static uint p1 = 0u;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_unpack2x16unorm(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, Unpack2x16Float) {
-  auto* call = Call("unpack2x16float", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float2 tint_unpack2x16float(uint param_0) {
-  uint i = param_0;
-  return f16tof32(uint2(i & 0xffff, i >> 16));
-}
-
-static uint p1 = 0u;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  tint_unpack2x16float(p1);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, StorageBarrier) {
-  Func("main", {}, ty.void_(), {CallStmt(Call("storageBarrier"))},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
-void main() {
-  DeviceMemoryBarrierWithGroupSync();
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
-  Func("main", {}, ty.void_(), {CallStmt(Call("workgroupBarrier"))},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
-void main() {
-  GroupMemoryBarrierWithGroupSync();
-  return;
-}
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_builtin_texture_test.cc b/src/writer/hlsl/generator_impl_builtin_texture_test.cc
deleted file mode 100644
index 332b4ad..0000000
--- a/src/writer/hlsl/generator_impl_builtin_texture_test.cc
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-struct ExpectedResult {
-  ExpectedResult(const char* o) : out(o) {}  // NOLINT
-  ExpectedResult(const char* p, const char* o) : pre(p), out(o) {}
-
-  std::string pre;
-  std::string out;
-};
-
-ExpectedResult expected_texture_overload(
-    ast::builtin::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-      return {
-          R"(int tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp);
-)",
-          "tint_tmp;",
-      };
-    case ValidTextureOverload::kDimensions2d:
-    case ValidTextureOverload::kDimensionsDepth2d:
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-      return {
-          R"(int2 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y);
-)",
-          "tint_tmp;",
-      };
-    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.xy;",
-      };
-
-    case ValidTextureOverload::kDimensions2dArray:
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kDimensions3d:
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp;",
-      };
-    case ValidTextureOverload::kDimensionsCube:
-    case ValidTextureOverload::kDimensionsDepthCube:
-      return {
-          R"(int2 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y);
-)",
-          "tint_tmp;",
-      };
-    case ValidTextureOverload::kDimensionsCubeArray:
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kDimensions2dLevel:
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-      return {
-          R"(int4 tint_tmp;
-  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kDimensions3dLevel:
-      return {
-          R"(int4 tint_tmp;
-  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
-)",
-          "tint_tmp.xyz;",
-      };
-    case ValidTextureOverload::kDimensionsCubeLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return {
-          R"(int4 tint_tmp;
-  tint_symbol.GetDimensions(1, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
-)",
-          "tint_tmp.xy;",
-      };
-    case ValidTextureOverload::kGather2dF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float2(1.0f, 2.0f)))";
-    case ValidTextureOverload::kGather2dOffsetF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)))";
-    case ValidTextureOverload::kGather2dArrayF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, float(3))))";
-    case ValidTextureOverload::kGather2dArrayOffsetF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)))";
-    case ValidTextureOverload::kGatherCubeF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kGatherCubeArrayF32:
-      return R"(tint_symbol.GatherRed(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))))";
-    case ValidTextureOverload::kGatherDepth2dF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float2(1.0f, 2.0f)))";
-    case ValidTextureOverload::kGatherDepth2dOffsetF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)))";
-    case ValidTextureOverload::kGatherDepth2dArrayF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, float(3))))";
-    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)))";
-    case ValidTextureOverload::kGatherDepthCubeF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kGatherDepthCubeArrayF32:
-      return R"(tint_symbol.Gather(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))))";
-    case ValidTextureOverload::kGatherCompareDepth2dF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f))";
-    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6)))";
-    case ValidTextureOverload::kGatherCompareDepthCubeF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f))";
-    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
-      return R"(tint_symbol.GatherCmp(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f))";
-    case ValidTextureOverload::kNumLayers2dArray:
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-    case ValidTextureOverload::kNumLayersCubeArray:
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.z;",
-      };
-    case ValidTextureOverload::kNumLevels2d:
-    case ValidTextureOverload::kNumLevelsCube:
-    case ValidTextureOverload::kNumLevelsDepth2d:
-    case ValidTextureOverload::kNumLevelsDepthCube:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.z;",
-      };
-    case ValidTextureOverload::kNumLevels2dArray:
-    case ValidTextureOverload::kNumLevels3d:
-    case ValidTextureOverload::kNumLevelsCubeArray:
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return {
-          R"(int4 tint_tmp;
-  tint_symbol.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z, tint_tmp.w);
-)",
-          "tint_tmp.w;",
-      };
-    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-      return {
-          R"(int3 tint_tmp;
-  tint_symbol.GetDimensions(tint_tmp.x, tint_tmp.y, tint_tmp.z);
-)",
-          "tint_tmp.z;",
-      };
-    case ValidTextureOverload::kSample1dF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, 1.0f);)";
-    case ValidTextureOverload::kSample2dF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f));)";
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4));)";
-    case ValidTextureOverload::kSample2dArrayF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)));)";
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5));)";
-    case ValidTextureOverload::kSample3dF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f));)";
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), int3(4, 5, 6));)";
-    case ValidTextureOverload::kSampleCubeF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f));)";
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)));)";
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f)).x;)";
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float2(1.0f, 2.0f), int2(3, 4)).x;)";
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3))).x;)";
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, float(3)), int2(4, 5)).x;)";
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float3(1.0f, 2.0f, 3.0f)).x;)";
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return R"(tint_symbol.Sample(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4))).x;)";
-    case ValidTextureOverload::kSampleBias2dF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6));)";
-    case ValidTextureOverload::kSampleBias3dF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7));)";
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return R"(tint_symbol.SampleBias(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4.0f, int2(5, 6));)";
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7));)";
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float2(1.0f, 2.0f), 3, int2(4, 5)).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4).x;)";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, float(3)), 4, int2(5, 6)).x;)";
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4).x;)";
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return R"(tint_symbol.SampleLevel(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5).x;)";
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f));)";
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f), int2(7, 7));)";
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, float(3)), float2(4.0f, 5.0f), float2(6.0f, 7.0f));)";
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, float(3)), float2(4.0f, 5.0f), float2(6.0f, 7.0f), int2(6, 7));)";
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));)";
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f), int3(0, 1, 2));)";
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));)";
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return R"(tint_symbol.SampleGrad(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), float3(5.0f, 6.0f, 7.0f), float3(8.0f, 9.0f, 10.0f));)";
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f, int2(5, 6));)";
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return R"(tint_symbol.SampleCmp(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float2(1.0f, 2.0f), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float2(1.0f, 2.0f), 3.0f, int2(4, 5));)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, float(4)), 3.0f, int2(5, 6));)";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float3(1.0f, 2.0f, 3.0f), 4.0f);)";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
-      return R"(tint_symbol.SampleCmpLevelZero(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)";
-    case ValidTextureOverload::kLoad1dLevelF32:
-    case ValidTextureOverload::kLoad1dLevelU32:
-    case ValidTextureOverload::kLoad1dLevelI32:
-      return R"(tint_symbol.Load(int2(1, 3));)";
-    case ValidTextureOverload::kLoad2dLevelF32:
-    case ValidTextureOverload::kLoad2dLevelU32:
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return R"(tint_symbol.Load(int3(1, 2, 3));)";
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-    case ValidTextureOverload::kLoad3dLevelF32:
-    case ValidTextureOverload::kLoad3dLevelU32:
-    case ValidTextureOverload::kLoad3dLevelI32:
-      return R"(tint_symbol.Load(int4(1, 2, 3, 4));)";
-    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return R"(tint_symbol.Load(int2(1, 2), 3);)";
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-      return R"(tint_symbol.Load(int3(1, 2, 3)).x;)";
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return R"(tint_symbol.Load(int4(1, 2, 3, 4)).x;)";
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-      return R"(tint_symbol[1] = float4(2.0f, 3.0f, 4.0f, 5.0f);)";
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-      return R"(tint_symbol[int2(1, 2)] = float4(3.0f, 4.0f, 5.0f, 6.0f);)";
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return R"(tint_symbol[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f);)";
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return R"(tint_symbol[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f);)";
-  }
-  return "<unmatched texture overload>";
-}  // NOLINT - Ignore the length of this function
-
-class HlslGeneratorBuiltinTextureTest
-    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
-
-TEST_P(HlslGeneratorBuiltinTextureTest, Call) {
-  auto param = GetParam();
-
-  param.BuildTextureVariable(this);
-  param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  auto* stmt = CallStmt(call);
-
-  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto expected = expected_texture_overload(param.overload);
-
-  EXPECT_THAT(gen.result(), HasSubstr(expected.pre));
-  EXPECT_THAT(gen.result(), HasSubstr(expected.out));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorBuiltinTextureTest,
-    HlslGeneratorBuiltinTextureTest,
-    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_call_test.cc b/src/writer/hlsl/generator_impl_call_test.cc
deleted file mode 100644
index 065b347..0000000
--- a/src/writer/hlsl/generator_impl_call_test.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Call = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
-  Func("my_func", {}, ty.f32(), {Return(1.23f)});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func()");
-}
-
-TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.f32(), {Return(1.23f)});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func(param1, param2)");
-}
-
-TEST_F(HlslGeneratorImplTest_Call, EmitStatement_Call) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = CallStmt(Call("my_func", "param1", "param2"));
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(call)) << gen.error();
-  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_case_test.cc b/src/writer/hlsl/generator_impl_case_test.cc
deleted file mode 100644
index 1d29dc5..0000000
--- a/src/writer/hlsl/generator_impl_case_test.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Case = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) {
-  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
-  auto* s =
-      Switch(1,                                                          //
-             Case(Expr(4), Block(create<ast::FallthroughStatement>())),  //
-             Case(Expr(5), Block(create<ast::ReturnStatement>())),       //
-             DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 4: {
-    /* fallthrough */
-    {
-      return;
-    }
-    break;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
-  auto* s =
-      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
-             DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5:
-  case 6: {
-    break;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case_Default) {
-  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  default: {
-    break;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_cast_test.cc b/src/writer/hlsl/generator_impl_cast_test.cc
deleted file mode 100644
index b281f4b..0000000
--- a/src/writer/hlsl/generator_impl_cast_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Cast = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) {
-  auto* cast = Construct<f32>(1);
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "float(1)");
-}
-
-TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
-  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "float3(int3(1, 2, 3))");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
deleted file mode 100644
index 9c253bc..0000000
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_Constructor = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Bool) {
-  WrapInFunction(Expr(false));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("false"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
-  WrapInFunction(Expr(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
-  WrapInFunction(Expr(56779u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
-  // Use a number close to 1<<30 but whose decimal representation ends in 0.
-  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
-  WrapInFunction(Construct<f32>(-1.2e-5f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
-  WrapInFunction(Construct<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
-  WrapInFunction(Construct<i32>(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
-  WrapInFunction(Construct<u32>(12345u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
-  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
-  WrapInFunction(vec3<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float3(0.0f, 0.0f, 0.0f)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Float_Literal) {
-  WrapInFunction(vec3<f32>(2.0f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float3((2.0f).xxx)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Float_Var) {
-  auto* var = Var("v", nullptr, Expr(2.0f));
-  auto* cast = vec3<f32>(var);
-  WrapInFunction(var, cast);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(float v = 2.0f;
-  const float3 tint_symbol = float3((v).xxx);)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Bool_Literal) {
-  WrapInFunction(vec3<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bool3((true).xxx)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Bool_Var) {
-  auto* var = Var("v", nullptr, Expr(true));
-  auto* cast = vec3<bool>(var);
-  WrapInFunction(var, cast);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(bool v = true;
-  const bool3 tint_symbol = bool3((v).xxx);)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_Int) {
-  WrapInFunction(vec3<i32>(2));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int3((2).xxx)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor,
-       EmitConstructor_Type_Vec_SingleScalar_UInt) {
-  WrapInFunction(vec3<u32>(2u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint3((2u).xxx)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
-  WrapInFunction(
-      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr(
-          "float2x3(float3(1.0f, 2.0f, 3.0f), float3(3.0f, 4.0f, 5.0f))"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat_Empty) {
-  WrapInFunction(mat2x3<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  EXPECT_THAT(gen.result(),
-              HasSubstr("float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
-  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3),
-                           vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f),
-                           vec3<f32>(7.f, 8.f, 9.f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("{float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f),"
-                        " float3(7.0f, 8.0f, 9.0f)}"));
-}
-
-// TODO(bclayton): Zero-init arrays
-TEST_F(HlslGeneratorImplTest_Constructor,
-       DISABLED_EmitConstructor_Type_Array_Empty) {
-  WrapInFunction(Construct(ty.array(ty.vec3<f32>(), 3)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("{float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f),"
-                        " float3(0.0f, 0.0f, 0.0f)}"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("{1, 2.0f, int3(3, 4, 5)}"));
-}
-
-TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str)));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("(S)0"));
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_continue_test.cc b/src/writer/hlsl/generator_impl_continue_test.cc
deleted file mode 100644
index 277e6a4..0000000
--- a/src/writer/hlsl/generator_impl_continue_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Continue = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Continue, Emit_Continue) {
-  auto* loop = Loop(Block(If(false, Block(Break())),  //
-                          Continue()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
-    if (false) {
-      break;
-    }
-    continue;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_discard_test.cc b/src/writer/hlsl/generator_impl_discard_test.cc
deleted file mode 100644
index e7a5cca..0000000
--- a/src/writer/hlsl/generator_impl_discard_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Discard = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Discard, Emit_Discard) {
-  auto* stmt = create<ast::DiscardStatement>();
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  discard;\n");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
deleted file mode 100644
index d17620c..0000000
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ /dev/null
@@ -1,931 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/writer/hlsl/test_helper.h"
-
-using ::testing::HasSubstr;
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Function = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function) {
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  void my_func() {
-    return;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
-  Func("GeometryShader", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(  void tint_symbol() {
-    return;
-  })"));
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithParams) {
-  Func("my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
-       ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  void my_func(float a, int b) {
-    return;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_NoReturn_Void) {
-  Func("main", ast::VariableList{}, ty.void_(), {/* no explicit return */},
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(void main() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, PtrParameter) {
-  // fn f(foo : ptr<function, f32>) -> f32 {
-  //   return *foo;
-  // }
-  Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))},
-       ty.f32(), {Return(Deref("foo"))});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) {
-  return foo;
-}
-)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithInOutVars) {
-  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
-  //   return foo;
-  // }
-  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
-  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
-  float foo : TEXCOORD0;
-};
-struct tint_symbol_2 {
-  float value : SV_Target1;
-};
-
-float frag_main_inner(float foo) {
-  return foo;
-}
-
-tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) {
-  const float inner_result = frag_main_inner(tint_symbol.foo);
-  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithInOut_Builtins) {
-  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
-  //   return coord.x;
-  // }
-  auto* coord_in =
-      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
-       {Return(MemberAccessor("coord", "x"))},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kFragDepth)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
-  float4 coord : SV_Position;
-};
-struct tint_symbol_2 {
-  float value : SV_Depth;
-};
-
-float frag_main_inner(float4 coord) {
-  return coord.x;
-}
-
-tint_symbol_2 frag_main(tint_symbol_1 tint_symbol) {
-  const float inner_result = frag_main_inner(tint_symbol.coord);
-  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
-  // struct Interface {
-  //   @builtin(position) pos : vec4<f32>;
-  //   @location(1) col1 : f32;
-  //   @location(2) col2 : f32;
-  // };
-  // fn vert_main() -> Interface {
-  //   return Interface(vec4<f32>(), 0.4, 0.6);
-  // }
-  // fn frag_main(inputs : Interface) {
-  //   const r = inputs.col1;
-  //   const g = inputs.col2;
-  //   const p = inputs.pos;
-  // }
-  auto* interface_struct = Structure(
-      "Interface",
-      {
-          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-          Member("col1", ty.f32(), {Location(1)}),
-          Member("col2", ty.f32(), {Location(2)}),
-      });
-
-  Func("vert_main", {}, ty.Of(interface_struct),
-       {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
-                         Expr(0.5f), Expr(0.25f)))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
-       {
-           Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))),
-           Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))),
-           Decl(Const("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct Interface {
-  float4 pos;
-  float col1;
-  float col2;
-};
-struct tint_symbol {
-  float col1 : TEXCOORD1;
-  float col2 : TEXCOORD2;
-  float4 pos : SV_Position;
-};
-
-Interface vert_main_inner() {
-  const Interface tint_symbol_3 = {float4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f};
-  return tint_symbol_3;
-}
-
-tint_symbol vert_main() {
-  const Interface inner_result = vert_main_inner();
-  tint_symbol wrapper_result = (tint_symbol)0;
-  wrapper_result.pos = inner_result.pos;
-  wrapper_result.col1 = inner_result.col1;
-  wrapper_result.col2 = inner_result.col2;
-  return wrapper_result;
-}
-
-struct tint_symbol_2 {
-  float col1 : TEXCOORD1;
-  float col2 : TEXCOORD2;
-  float4 pos : SV_Position;
-};
-
-void frag_main_inner(Interface inputs) {
-  const float r = inputs.col1;
-  const float g = inputs.col2;
-  const float4 p = inputs.pos;
-}
-
-void frag_main(tint_symbol_2 tint_symbol_1) {
-  const Interface tint_symbol_4 = {tint_symbol_1.pos, tint_symbol_1.col1, tint_symbol_1.col2};
-  frag_main_inner(tint_symbol_4);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
-  // struct VertexOutput {
-  //   @builtin(position) pos : vec4<f32>;
-  // };
-  // fn foo(x : f32) -> VertexOutput {
-  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
-  // }
-  // fn vert_main1() -> VertexOutput {
-  //   return foo(0.5);
-  // }
-  // fn vert_main2() -> VertexOutput {
-  //   return foo(0.25);
-  // }
-  auto* vertex_output_struct = Structure(
-      "VertexOutput",
-      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
-
-  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
-       {Return(Construct(ty.Of(vertex_output_struct),
-                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
-       {});
-
-  Func("vert_main1", {}, ty.Of(vertex_output_struct),
-       {Return(Call("foo", Expr(0.5f)))}, {Stage(ast::PipelineStage::kVertex)});
-
-  Func("vert_main2", {}, ty.Of(vertex_output_struct),
-       {Return(Call("foo", Expr(0.25f)))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct VertexOutput {
-  float4 pos;
-};
-
-VertexOutput foo(float x) {
-  const VertexOutput tint_symbol_2 = {float4(x, x, x, 1.0f)};
-  return tint_symbol_2;
-}
-
-struct tint_symbol {
-  float4 pos : SV_Position;
-};
-
-VertexOutput vert_main1_inner() {
-  return foo(0.5f);
-}
-
-tint_symbol vert_main1() {
-  const VertexOutput inner_result = vert_main1_inner();
-  tint_symbol wrapper_result = (tint_symbol)0;
-  wrapper_result.pos = inner_result.pos;
-  return wrapper_result;
-}
-
-struct tint_symbol_1 {
-  float4 pos : SV_Position;
-};
-
-VertexOutput vert_main2_inner() {
-  return foo(0.25f);
-}
-
-tint_symbol_1 vert_main2() {
-  const VertexOutput inner_result_1 = vert_main2_inner();
-  tint_symbol_1 wrapper_result_1 = (tint_symbol_1)0;
-  wrapper_result_1.pos = inner_result_1.pos;
-  return wrapper_result_1;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
-  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
-                           {create<ast::StructBlockAttribute>()});
-  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(1),
-                     });
-
-  Func("sub_func",
-       {
-           Param("param", ty.f32()),
-       },
-       ty.f32(),
-       {
-           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", {}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_ubo : register(b0, space1) {
-  uint4 ubo[1];
-};
-
-float sub_func(float param) {
-  return asfloat(ubo[0].x);
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_UniformStruct) {
-  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("uniforms", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_uniforms : register(b0, space1) {
-  uint4 uniforms[1];
-};
-
-void frag_main() {
-  float v = uniforms.coord.x;
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(RWByteAddressBuffer coord : register(u0, space1);
-
-void frag_main() {
-  float v = asfloat(coord.Load(4u));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(ByteAddressBuffer coord : register(t0, space1);
-
-void frag_main() {
-  float v = asfloat(coord.Load(4u));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(RWByteAddressBuffer coord : register(u0, space1);
-
-void frag_main() {
-  coord.Store(4u, asuint(2.0f));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Assign(MemberAccessor("coord", "b"), Expr(2.0f)),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(RWByteAddressBuffer coord : register(u0, space1);
-
-void frag_main() {
-  coord.Store(4u, asuint(2.0f));
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("coord", ty.Of(s), ast::StorageClass::kUniform,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
-       {
-           Return(MemberAccessor("coord", "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_coord : register(b0, space1) {
-  uint4 coord[1];
-};
-
-float sub_func(float param) {
-  return coord.x;
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(1),
-         });
-
-  Func("sub_func", ast::VariableList{Param("param", ty.f32())}, ty.f32(),
-       {
-           Return(MemberAccessor("coord", "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(RWByteAddressBuffer coord : register(u0, space1);
-
-float sub_func(float param) {
-  return asfloat(coord.Load(0u));
-}
-
-void frag_main() {
-  float v = sub_func(1.0f);
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_WithNameCollision) {
-  Func("GeometryShader", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(void tint_symbol() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Return(),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(2, 4, 6),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"([numthreads(2, 4, 6)]
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) {
-  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
-  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
-  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize("width", "height", "depth"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(static const int width = int(2);
-static const int height = int(3);
-static const int depth = int(4);
-
-[numthreads(2, 3, 4)]
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
-  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
-  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
-  Func("main", ast::VariableList{}, ty.void_(), {},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize("width", "height", "depth"),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_7
-#define WGSL_SPEC_CONSTANT_7 int(2)
-#endif
-static const int width = WGSL_SPEC_CONSTANT_7;
-#ifndef WGSL_SPEC_CONSTANT_8
-#define WGSL_SPEC_CONSTANT_8 int(3)
-#endif
-static const int height = WGSL_SPEC_CONSTANT_8;
-#ifndef WGSL_SPEC_CONSTANT_9
-#define WGSL_SPEC_CONSTANT_9 int(4)
-#endif
-static const int depth = WGSL_SPEC_CONSTANT_9;
-
-[numthreads(WGSL_SPEC_CONSTANT_7, WGSL_SPEC_CONSTANT_8, WGSL_SPEC_CONSTANT_9)]
-void main() {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
-  Func("my_func", ast::VariableList{Param("a", ty.array<f32, 5>())}, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(void my_func(float a[5]) {
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
-  Func("my_func", {}, ty.array<f32, 5>(),
-       {
-           Return(Construct(ty.array<f32, 5>())),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(typedef float my_func_ret[5];
-my_func_ret my_func() {
-  return (float[5])0;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithDiscardAndVoidReturn) {
-  Func("my_func", {Param("a", ty.i32())}, ty.void_(),
-       {
-           If(Equal("a", 0),  //
-              Block(create<ast::DiscardStatement>())),
-           Return(),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(void my_func(int a) {
-  if ((a == 0)) {
-    discard;
-  }
-  return;
-}
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Function_WithDiscardAndNonVoidReturn) {
-  Func("my_func", {Param("a", ty.i32())}, ty.i32(),
-       {
-           If(Equal("a", 0),  //
-              Block(create<ast::DiscardStatement>())),
-           Return(42),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(int my_func(int a) {
-  if (true) {
-    if ((a == 0)) {
-      discard;
-    }
-    return 42;
-  }
-  int unused;
-  return unused;
-}
-)");
-}
-
-// https://crbug.com/tint/297
-TEST_F(HlslGeneratorImplTest_Function,
-       Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
-  // [[block]] struct Data {
-  //   d : f32;
-  // };
-  // @binding(0) @group(0) var<storage> data : Data;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn a() {
-  //   var v = data.d;
-  //   return;
-  // }
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn b() {
-  //   var v = data.d;
-  //   return;
-  // }
-
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("a", ast::VariableList{}, ty.void_(),
-         {
-             Decl(var),
-             Return(),
-         },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  }
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("b", ast::VariableList{}, ty.void_(),
-         {
-             Decl(var),
-             Return(),
-         },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  }
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(RWByteAddressBuffer data : register(u0, space0);
-
-[numthreads(1, 1, 1)]
-void a() {
-  float v = asfloat(data.Load(0u));
-  return;
-}
-
-[numthreads(1, 1, 1)]
-void b() {
-  float v = asfloat(data.Load(0u));
-  return;
-}
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_identifier_test.cc b/src/writer/hlsl/generator_impl_identifier_test.cc
deleted file mode 100644
index 4929bc9..0000000
--- a/src/writer/hlsl/generator_impl_identifier_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Identifier = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Identifier, EmitIdentifierExpression) {
-  Global("foo", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* i = Expr("foo");
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
-  EXPECT_EQ(out.str(), "foo");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_if_test.cc b/src/writer/hlsl/generator_impl_if_test.cc
deleted file mode 100644
index e40f842..0000000
--- a/src/writer/hlsl/generator_impl_if_test.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_If = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_If, Emit_If) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body);
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElseIf) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElse) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    return;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_If, Emit_IfWithMultiple) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-
-  auto* else_body = Block(Return());
-
-  auto* else_body_2 = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body,
-               ast::ElseStatementList{
-                   create<ast::ElseStatement>(else_cond, else_body),
-                   create<ast::ElseStatement>(nullptr, else_body_2),
-               });
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    } else {
-      return;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_import_test.cc b/src/writer/hlsl/generator_impl_import_test.cc
deleted file mode 100644
index f12c7fa..0000000
--- a/src/writer/hlsl/generator_impl_import_test.cc
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Import = TestHelper;
-
-struct HlslImportData {
-  const char* name;
-  const char* hlsl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, HlslImportData data) {
-  out << data.name;
-  return out;
-}
-
-using HlslImportData_SingleParamTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_SingleParamTest, FloatScalar) {
-  auto param = GetParam();
-
-  auto* ident = Expr(param.name);
-  auto* expr = Call(ident, 1.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_SingleParamTest,
-                         testing::Values(HlslImportData{"abs", "abs"},
-                                         HlslImportData{"acos", "acos"},
-                                         HlslImportData{"asin", "asin"},
-                                         HlslImportData{"atan", "atan"},
-                                         HlslImportData{"cos", "cos"},
-                                         HlslImportData{"cosh", "cosh"},
-                                         HlslImportData{"ceil", "ceil"},
-                                         HlslImportData{"exp", "exp"},
-                                         HlslImportData{"exp2", "exp2"},
-                                         HlslImportData{"floor", "floor"},
-                                         HlslImportData{"fract", "frac"},
-                                         HlslImportData{"inverseSqrt", "rsqrt"},
-                                         HlslImportData{"length", "length"},
-                                         HlslImportData{"log", "log"},
-                                         HlslImportData{"log2", "log2"},
-                                         HlslImportData{"round", "round"},
-                                         HlslImportData{"sign", "sign"},
-                                         HlslImportData{"sin", "sin"},
-                                         HlslImportData{"sinh", "sinh"},
-                                         HlslImportData{"sqrt", "sqrt"},
-                                         HlslImportData{"tan", "tan"},
-                                         HlslImportData{"tanh", "tanh"},
-                                         HlslImportData{"trunc", "trunc"}));
-
-using HlslImportData_SingleIntParamTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_SingleIntParamTest, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, Expr(1));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_SingleIntParamTest,
-                         testing::Values(HlslImportData{"abs", "abs"}));
-
-using HlslImportData_SingleVectorParamTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_SingleVectorParamTest, FloatVector) {
-  auto param = GetParam();
-
-  auto* ident = Expr(param.name);
-  auto* expr = Call(ident, vec3<f32>(1.f, 2.f, 3.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            std::string(param.hlsl_name) + "(float3(1.0f, 2.0f, 3.0f))");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_SingleVectorParamTest,
-                         testing::Values(HlslImportData{"abs", "abs"},
-                                         HlslImportData{"acos", "acos"},
-                                         HlslImportData{"asin", "asin"},
-                                         HlslImportData{"atan", "atan"},
-                                         HlslImportData{"cos", "cos"},
-                                         HlslImportData{"cosh", "cosh"},
-                                         HlslImportData{"ceil", "ceil"},
-                                         HlslImportData{"exp", "exp"},
-                                         HlslImportData{"exp2", "exp2"},
-                                         HlslImportData{"floor", "floor"},
-                                         HlslImportData{"fract", "frac"},
-                                         HlslImportData{"inverseSqrt", "rsqrt"},
-                                         HlslImportData{"length", "length"},
-                                         HlslImportData{"log", "log"},
-                                         HlslImportData{"log2", "log2"},
-                                         HlslImportData{"normalize",
-                                                        "normalize"},
-                                         HlslImportData{"round", "round"},
-                                         HlslImportData{"sign", "sign"},
-                                         HlslImportData{"sin", "sin"},
-                                         HlslImportData{"sinh", "sinh"},
-                                         HlslImportData{"sqrt", "sqrt"},
-                                         HlslImportData{"tan", "tan"},
-                                         HlslImportData{"tanh", "tanh"},
-                                         HlslImportData{"trunc", "trunc"}));
-
-using HlslImportData_DualParam_ScalarTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_DualParam_ScalarTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1.f, 2.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_DualParam_ScalarTest,
-                         testing::Values(HlslImportData{"atan2", "atan2"},
-                                         HlslImportData{"distance", "distance"},
-                                         HlslImportData{"max", "max"},
-                                         HlslImportData{"min", "min"},
-                                         HlslImportData{"pow", "pow"},
-                                         HlslImportData{"step", "step"}));
-
-using HlslImportData_DualParam_VectorTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_DualParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr =
-      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            std::string(param.hlsl_name) +
-                "(float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f))");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_DualParam_VectorTest,
-                         testing::Values(HlslImportData{"atan2", "atan2"},
-                                         HlslImportData{"cross", "cross"},
-                                         HlslImportData{"distance", "distance"},
-                                         HlslImportData{"max", "max"},
-                                         HlslImportData{"min", "min"},
-                                         HlslImportData{"pow", "pow"},
-                                         HlslImportData{"reflect", "reflect"},
-                                         HlslImportData{"step", "step"}));
-
-using HlslImportData_DualParam_Int_Test = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_DualParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_DualParam_Int_Test,
-                         testing::Values(HlslImportData{"max", "max"},
-                                         HlslImportData{"min", "min"}));
-
-using HlslImportData_TripleParam_ScalarTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_TripleParam_ScalarTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f, 3.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_TripleParam_ScalarTest,
-                         testing::Values(HlslImportData{"fma", "mad"},
-                                         HlslImportData{"mix", "lerp"},
-                                         HlslImportData{"clamp", "clamp"},
-                                         HlslImportData{"smoothStep",
-                                                        "smoothstep"}));
-
-using HlslImportData_TripleParam_VectorTest = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_TripleParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
-                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(
-      out.str(),
-      std::string(param.hlsl_name) +
-          R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))");
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_Import,
-    HlslImportData_TripleParam_VectorTest,
-    testing::Values(HlslImportData{"faceForward", "faceforward"},
-                    HlslImportData{"fma", "mad"},
-                    HlslImportData{"clamp", "clamp"},
-                    HlslImportData{"smoothStep", "smoothstep"}));
-
-TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_FMix) {
-  FAIL();
-}
-
-using HlslImportData_TripleParam_Int_Test = TestParamHelper<HlslImportData>;
-TEST_P(HlslImportData_TripleParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2, 3);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2, 3)");
-}
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
-                         HlslImportData_TripleParam_Int_Test,
-                         testing::Values(HlslImportData{"clamp", "clamp"}));
-
-TEST_F(HlslGeneratorImplTest_Import, HlslImportData_Determinant) {
-  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("determinant", "var");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string("determinant(var)"));
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_loop_test.cc b/src/writer/hlsl/generator_impl_loop_test.cc
deleted file mode 100644
index 2962b28..0000000
--- a/src/writer/hlsl/generator_impl_loop_test.cc
+++ /dev/null
@@ -1,381 +0,0 @@
-// Copyright 2020 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/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Loop = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_Loop) {
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block();
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
-    discard;
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
-    discard;
-    {
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* inner = Loop(body, continuing);
-
-  body = Block(inner);
-
-  auto* lhs = Expr("lhs");
-  auto* rhs = Expr("rhs");
-
-  continuing = Block(Assign(lhs, rhs));
-
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
-    [loop] while (true) {
-      discard;
-      {
-        a_statement();
-      }
-    }
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithVarUsedInContinuing) {
-  // loop {
-  //   var lhs : f32 = 2.4;
-  //   var other : f32;
-  //   break;
-  //   continuing {
-  //     lhs = rhs
-  //   }
-  // }
-
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
-                     Decl(Var("other", ty.f32())),            //
-                     Break());
-
-  auto* continuing = Block(Assign("lhs", "rhs"));
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  [loop] while (true) {
-    float lhs = 2.400000095f;
-    float other = 0.0f;
-    break;
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoop) {
-  // for(; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, nullptr, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] for(; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) {
-  // for(var i : i32; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] for(int i = 0; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
-  // for(var b = true && false; ; ) {
-  //   return;
-  // }
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr,
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
-    if (tint_tmp) {
-      tint_tmp = false;
-    }
-    bool b = (tint_tmp);
-    [loop] for(; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) {
-  // for(; true; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, true, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] for(; true; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
-  // for(; true && false; ) {
-  //   return;
-  // }
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* f = For(nullptr, multi_stmt, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] while (true) {
-      bool tint_tmp = true;
-      if (tint_tmp) {
-        tint_tmp = false;
-      }
-      if (!((tint_tmp))) { break; }
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) {
-  // for(; ; i = i + 1) {
-  //   return;
-  // }
-
-  auto* v = Decl(Var("i", ty.i32()));
-  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] for(; ; i = (i + 1)) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
-  // for(; ; i = true && false) {
-  //   return;
-  // }
-
-  auto* multi_stmt = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                   Expr(true), Expr(false));
-  auto* v = Decl(Var("i", ty.bool_()));
-  auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] while (true) {
-      return;
-      bool tint_tmp = true;
-      if (tint_tmp) {
-        tint_tmp = false;
-      }
-      i = (tint_tmp);
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) {
-  // for(var i : i32; true; i = i + 1) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    [loop] for(int i = 0; true; i = (i + 1)) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
-  // for(var i = true && false; true && false; i = true && false) {
-  //   return;
-  // }
-
-  auto* multi_stmt_a = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-  auto* multi_stmt_b = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-  auto* multi_stmt_c = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                                     Expr(true), Expr(false));
-
-  auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b,
-                Assign("i", multi_stmt_c), Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
-    if (tint_tmp) {
-      tint_tmp = false;
-    }
-    bool i = (tint_tmp);
-    [loop] while (true) {
-      bool tint_tmp_1 = true;
-      if (tint_tmp_1) {
-        tint_tmp_1 = false;
-      }
-      if (!((tint_tmp_1))) { break; }
-      return;
-      bool tint_tmp_2 = true;
-      if (tint_tmp_2) {
-        tint_tmp_2 = false;
-      }
-      i = (tint_tmp_2);
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
deleted file mode 100644
index df9a6bf..0000000
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ /dev/null
@@ -1,770 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using create_type_func_ptr =
-    const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
-
-inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.i32();
-}
-inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.u32();
-}
-inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.f32();
-}
-template <typename T>
-inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.vec4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat2x4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat3x4<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x2<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x3<T>();
-}
-template <typename T>
-inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
-  return ty.mat4x4<T>();
-}
-
-using i32 = ProgramBuilder::i32;
-using u32 = ProgramBuilder::u32;
-using f32 = ProgramBuilder::f32;
-
-template <typename BASE>
-class HlslGeneratorImplTest_MemberAccessorBase : public BASE {
- public:
-  void SetupStorageBuffer(ast::StructMemberList members) {
-    ProgramBuilder& b = *this;
-
-    auto* s =
-        b.Structure("Data", members, {b.create<ast::StructBlockAttribute>()});
-
-    b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage,
-             ast::Access::kReadWrite,
-             ast::AttributeList{
-                 b.create<ast::BindingAttribute>(0),
-                 b.create<ast::GroupAttribute>(1),
-             });
-  }
-
-  void SetupFunction(ast::StatementList statements) {
-    ProgramBuilder& b = *this;
-    b.Func("main", ast::VariableList{}, b.ty.void_(), statements,
-           ast::AttributeList{
-               b.Stage(ast::PipelineStage::kFragment),
-           });
-  }
-};
-
-using HlslGeneratorImplTest_MemberAccessor =
-    HlslGeneratorImplTest_MemberAccessorBase<TestHelper>;
-
-template <typename T>
-using HlslGeneratorImplTest_MemberAccessorWithParam =
-    HlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
-
-  auto* expr = MemberAccessor("str", "mem");
-  WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct Data {
-  float mem;
-};
-
-static Data str = (Data)0;
-
-[numthreads(1, 1, 1)]
-void test_function() {
-  float expr = str.mem;
-  return;
-}
-)");
-}
-
-struct TypeCase {
-  create_type_func_ptr member_type;
-  std::string expected;
-};
-inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
-  ProgramBuilder b;
-  auto* ty = c.member_type(b.ty);
-  out << ty->FriendlyName(b.Symbols());
-  return out;
-}
-
-using HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad =
-    HlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
-TEST_P(HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad, Test) {
-  // struct Data {
-  //   a : i32;
-  //   b : <type>;
-  // };
-  // var<storage> data : Data;
-  // data.b;
-
-  auto p = GetParam();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", p.member_type(ty)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor("data", "b"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_MemberAccessor,
-    HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad,
-    testing::Values(
-        TypeCase{ty_u32, "data.Load(4u)"},
-        TypeCase{ty_f32, "asfloat(data.Load(4u))"},
-        TypeCase{ty_i32, "asint(data.Load(4u))"},
-        TypeCase{ty_vec2<u32>, "data.Load2(8u)"},
-        TypeCase{ty_vec2<f32>, "asfloat(data.Load2(8u))"},
-        TypeCase{ty_vec2<i32>, "asint(data.Load2(8u))"},
-        TypeCase{ty_vec3<u32>, "data.Load3(16u)"},
-        TypeCase{ty_vec3<f32>, "asfloat(data.Load3(16u))"},
-        TypeCase{ty_vec3<i32>, "asint(data.Load3(16u))"},
-        TypeCase{ty_vec4<u32>, "data.Load4(16u)"},
-        TypeCase{ty_vec4<f32>, "asfloat(data.Load4(16u))"},
-        TypeCase{ty_vec4<i32>, "asint(data.Load4(16u))"},
-        TypeCase{
-            ty_mat2x2<f32>,
-            R"(return float2x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))));)"},
-        TypeCase{
-            ty_mat2x3<f32>,
-            R"(return float2x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))));)"},
-        TypeCase{
-            ty_mat2x4<f32>,
-            R"(return float2x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))));)"},
-        TypeCase{
-            ty_mat3x2<f32>,
-            R"(return float3x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))), asfloat(buffer.Load2((offset + 16u))));)"},
-        TypeCase{
-            ty_mat3x3<f32>,
-            R"(return float3x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))), asfloat(buffer.Load3((offset + 32u))));)"},
-        TypeCase{
-            ty_mat3x4<f32>,
-            R"(return float3x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))));)"},
-        TypeCase{
-            ty_mat4x2<f32>,
-            R"(return float4x2(asfloat(buffer.Load2((offset + 0u))), asfloat(buffer.Load2((offset + 8u))), asfloat(buffer.Load2((offset + 16u))), asfloat(buffer.Load2((offset + 24u))));)"},
-        TypeCase{
-            ty_mat4x3<f32>,
-            R"(return float4x3(asfloat(buffer.Load3((offset + 0u))), asfloat(buffer.Load3((offset + 16u))), asfloat(buffer.Load3((offset + 32u))), asfloat(buffer.Load3((offset + 48u))));)"},
-        TypeCase{
-            ty_mat4x4<f32>,
-            R"(return float4x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))), asfloat(buffer.Load4((offset + 48u))));)"}));
-
-using HlslGeneratorImplTest_MemberAccessor_StorageBufferStore =
-    HlslGeneratorImplTest_MemberAccessorWithParam<TypeCase>;
-TEST_P(HlslGeneratorImplTest_MemberAccessor_StorageBufferStore, Test) {
-  // struct Data {
-  //   a : i32;
-  //   b : <type>;
-  // };
-  // var<storage> data : Data;
-  // data.b = <type>();
-
-  auto p = GetParam();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", p.member_type(ty)),
-  });
-
-  SetupFunction({
-      Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
-               Construct(p.member_type(ty)))),
-      Assign(MemberAccessor("data", "b"), Expr("value")),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_MemberAccessor,
-    HlslGeneratorImplTest_MemberAccessor_StorageBufferStore,
-    testing::Values(TypeCase{ty_u32, "data.Store(4u, asuint(value))"},
-                    TypeCase{ty_f32, "data.Store(4u, asuint(value))"},
-                    TypeCase{ty_i32, "data.Store(4u, asuint(value))"},
-                    TypeCase{ty_vec2<u32>, "data.Store2(8u, asuint(value))"},
-                    TypeCase{ty_vec2<f32>, "data.Store2(8u, asuint(value))"},
-                    TypeCase{ty_vec2<i32>, "data.Store2(8u, asuint(value))"},
-                    TypeCase{ty_vec3<u32>, "data.Store3(16u, asuint(value))"},
-                    TypeCase{ty_vec3<f32>, "data.Store3(16u, asuint(value))"},
-                    TypeCase{ty_vec3<i32>, "data.Store3(16u, asuint(value))"},
-                    TypeCase{ty_vec4<u32>, "data.Store4(16u, asuint(value))"},
-                    TypeCase{ty_vec4<f32>, "data.Store4(16u, asuint(value))"},
-                    TypeCase{ty_vec4<i32>, "data.Store4(16u, asuint(value))"},
-                    TypeCase{ty_mat2x2<f32>, R"({
-  buffer.Store2((offset + 0u), asuint(value[0u]));
-  buffer.Store2((offset + 8u), asuint(value[1u]));
-})"},
-                    TypeCase{ty_mat2x3<f32>, R"({
-  buffer.Store3((offset + 0u), asuint(value[0u]));
-  buffer.Store3((offset + 16u), asuint(value[1u]));
-})"},
-                    TypeCase{ty_mat2x4<f32>, R"({
-  buffer.Store4((offset + 0u), asuint(value[0u]));
-  buffer.Store4((offset + 16u), asuint(value[1u]));
-})"},
-                    TypeCase{ty_mat3x2<f32>, R"({
-  buffer.Store2((offset + 0u), asuint(value[0u]));
-  buffer.Store2((offset + 8u), asuint(value[1u]));
-  buffer.Store2((offset + 16u), asuint(value[2u]));
-})"},
-                    TypeCase{ty_mat3x3<f32>, R"({
-  buffer.Store3((offset + 0u), asuint(value[0u]));
-  buffer.Store3((offset + 16u), asuint(value[1u]));
-  buffer.Store3((offset + 32u), asuint(value[2u]));
-})"},
-                    TypeCase{ty_mat3x4<f32>, R"({
-  buffer.Store4((offset + 0u), asuint(value[0u]));
-  buffer.Store4((offset + 16u), asuint(value[1u]));
-  buffer.Store4((offset + 32u), asuint(value[2u]));
-})"},
-                    TypeCase{ty_mat4x2<f32>, R"({
-  buffer.Store2((offset + 0u), asuint(value[0u]));
-  buffer.Store2((offset + 8u), asuint(value[1u]));
-  buffer.Store2((offset + 16u), asuint(value[2u]));
-  buffer.Store2((offset + 24u), asuint(value[3u]));
-})"},
-                    TypeCase{ty_mat4x3<f32>, R"({
-  buffer.Store3((offset + 0u), asuint(value[0u]));
-  buffer.Store3((offset + 16u), asuint(value[1u]));
-  buffer.Store3((offset + 32u), asuint(value[2u]));
-  buffer.Store3((offset + 48u), asuint(value[3u]));
-})"},
-                    TypeCase{ty_mat4x4<f32>, R"({
-  buffer.Store4((offset + 0u), asuint(value[0u]));
-  buffer.Store4((offset + 16u), asuint(value[1u]));
-  buffer.Store4((offset + 32u), asuint(value[2u]));
-  buffer.Store4((offset + 48u), asuint(value[3u]));
-})"}));
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) {
-  // struct Data {
-  //   z : f32;
-  //   a : mat2x3<f32>;
-  // };
-  // var<storage> data : Data;
-  // data.a = mat2x3<f32>();
-
-  SetupStorageBuffer({
-      Member("a", ty.i32()),
-      Member("b", ty.mat2x3<f32>()),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor("data", "b"),
-             Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x3 value) {
-  buffer.Store3((offset + 0u), asuint(value[0u]));
-  buffer.Store3((offset + 16u), asuint(value[1u]));
-}
-
-void main() {
-  tint_symbol(data, 16u, float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_Matrix_Single_Element) {
-  // struct Data {
-  //   z : f32;
-  //   a : mat4x3<f32>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2][1];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.mat4x3<f32>()),
-  });
-
-  SetupFunction({
-      Decl(
-          Var("x", nullptr, ast::StorageClass::kNone,
-              IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2), 1))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  float x = asfloat(data.Load(52u));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               IndexAccessor(MemberAccessor("data", "a"), 2))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  int x = asint(data.Load(12u));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[(2 + 4) - 3];
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               IndexAccessor(MemberAccessor("data", "a"),
-                             Sub(Add(2, Expr(4)), Expr(3))))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  int x = asint(data.Load((4u + (4u * uint(((2 + 4) - 3))))));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) {
-  // struct Data {
-  //   a : @stride(4) array<i32, 5>;
-  // };
-  // var<storage> data : Data;
-  // data.a[2] = 2;
-
-  SetupStorageBuffer({
-      Member("z", ty.f32()),
-      Member("a", ty.array<i32, 5>(4)),
-  });
-
-  SetupFunction({
-      Assign(IndexAccessor(MemberAccessor("data", "a"), 2), 2),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  data.Store(12u, asuint(2));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var(
-          "x", nullptr, ast::StorageClass::kNone,
-          MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  float3 x = asfloat(data.Load3(80u));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Swizzle) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.xy
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor(
-                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                  "b"),
-                   "xy"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  float2 x = asfloat(data.Load3(80u)).xy;
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {  // NOLINT
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.g
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var("x", nullptr, ast::StorageClass::kNone,
-               MemberAccessor(
-                   MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                  "b"),
-                   "g"))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  float x = asfloat(data.Load(84u));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Load_MultiLevel_Index) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b[1]
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Decl(Var(
-          "x", nullptr, ast::StorageClass::kNone,
-          IndexAccessor(MemberAccessor(
-                            IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
-                        1))),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  float x = asfloat(data.Load(84u));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b = vec3<f32>(1.f, 2.f, 3.f);
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2), "b"),
-             vec3<f32>(1.f, 2.f, 3.f)),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  data.Store3(80u, asuint(float3(1.0f, 2.0f, 3.0f)));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor,
-       StorageBuffer_Store_Swizzle_SingleLetter) {
-  // struct Inner {
-  //   a : vec3<i32>;
-  //   b : vec3<f32>;
-  // };
-  // struct Data {
-  //   var c : @stride(32) array<Inner, 4>;
-  // };
-  //
-  // var<storage> data : Pre;
-  // data.c[2].b.y = 1.f;
-
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<i32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  SetupStorageBuffer({
-      Member("c", ty.array(ty.Of(inner), 4, 32)),
-  });
-
-  SetupFunction({
-      Assign(MemberAccessor(
-                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2),
-                                "b"),
-                 "y"),
-             Expr(1.f)),
-  });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  auto* expected =
-      R"(RWByteAddressBuffer data : register(u0, space1);
-
-void main() {
-  data.Store(84u, asuint(1.0f));
-  return;
-}
-)";
-  EXPECT_EQ(gen.result(), expected);
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
-  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
-                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
-  auto* expr = MemberAccessor("my_vec", "xyz");
-  WrapInFunction(var, expr);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz"));
-}
-
-TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
-  auto* var = Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone,
-                  vec4<f32>(1.f, 2.f, 3.f, 4.f));
-  auto* expr = MemberAccessor("my_vec", "gbr");
-  WrapInFunction(var, expr);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr"));
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_module_constant_test.cc b/src/writer/hlsl/generator_impl_module_constant_test.cc
deleted file mode 100644
index 7502288..0000000
--- a/src/writer/hlsl/generator_impl_module_constant_test.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2020 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/ast/id_attribute.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_ModuleConstant = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) {
-  auto* var = Const("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
-  WrapInFunction(Decl(var));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), "static const float pos[3] = {1.0f, 2.0f, 3.0f};\n");
-}
-
-TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) {
-  auto* var = Override("pos", ty.f32(), Expr(3.0f),
-                       ast::AttributeList{
-                           Id(23),
-                       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
-#define WGSL_SPEC_CONSTANT_23 3.0f
-#endif
-static const float pos = WGSL_SPEC_CONSTANT_23;
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoConstructor) {
-  auto* var = Override("pos", ty.f32(), nullptr,
-                       ast::AttributeList{
-                           Id(23),
-                       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
-#error spec constant required for constant id 23
-#endif
-static const float pos = WGSL_SPEC_CONSTANT_23;
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant_NoId) {
-  auto* a = Override("a", ty.f32(), Expr(3.0f),
-                     ast::AttributeList{
-                         Id(0),
-                     });
-  auto* b = Override("b", ty.f32(), Expr(2.0f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error();
-  ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
-#define WGSL_SPEC_CONSTANT_0 3.0f
-#endif
-static const float a = WGSL_SPEC_CONSTANT_0;
-#ifndef WGSL_SPEC_CONSTANT_1
-#define WGSL_SPEC_CONSTANT_1 2.0f
-#endif
-static const float b = WGSL_SPEC_CONSTANT_1;
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_return_test.cc b/src/writer/hlsl/generator_impl_return_test.cc
deleted file mode 100644
index 177ac25..0000000
--- a/src/writer/hlsl/generator_impl_return_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Return = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Return, Emit_Return) {
-  auto* r = Return();
-  WrapInFunction(r);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return;\n");
-}
-
-TEST_F(HlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
-  auto* r = Return(123);
-  Func("f", {}, ty.i32(), {r});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return 123;\n");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_sanitizer_test.cc b/src/writer/hlsl/generator_impl_sanitizer_test.cc
deleted file mode 100644
index 41f5840..0000000
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslSanitizerTest = TestHelper;
-
-TEST_F(HlslSanitizerTest, Call_ArrayLength) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
-
-void a_func() {
-  uint tint_symbol_1 = 0u;
-  b.GetDimensions(tint_symbol_1);
-  const uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u);
-  uint len = tint_symbol_2;
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = Structure("my_struct",
-                      {
-                          Member(0, "z", ty.f32()),
-                          Member(4, "a", ty.array<f32>(4)),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
-
-void a_func() {
-  uint tint_symbol_1 = 0u;
-  b.GetDimensions(tint_symbol_1);
-  const uint tint_symbol_2 = ((tint_symbol_1 - 4u) / 4u);
-  uint len = tint_symbol_2;
-  return;
-}
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, Call_ArrayLength_ViaLets) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  auto* p = Const("p", nullptr, AddressOf("b"));
-  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(p),
-           Decl(p2),
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", p2))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
-
-void a_func() {
-  uint tint_symbol_1 = 0u;
-  b.GetDimensions(tint_symbol_1);
-  const uint tint_symbol_2 = ((tint_symbol_1 - 0u) / 4u);
-  uint len = tint_symbol_2;
-  return;
-}
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(2),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var(
-               "len", ty.u32(), ast::StorageClass::kNone,
-               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
-                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Options options;
-  options.array_length_from_uniform.ubo_binding = {3, 4};
-  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
-      sem::BindingPoint{2, 2}, 7u);
-  GeneratorImpl& gen = SanitizeAndBuild(options);
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(cbuffer cbuffer_tint_symbol_1 : register(b4, space3) {
-  uint4 tint_symbol_1[2];
-};
-ByteAddressBuffer b : register(t1, space2);
-ByteAddressBuffer c : register(t2, space2);
-
-void a_func() {
-  uint tint_symbol_4 = 0u;
-  b.GetDimensions(tint_symbol_4);
-  const uint tint_symbol_5 = ((tint_symbol_4 - 0u) / 4u);
-  uint len = (tint_symbol_5 + ((tint_symbol_1[1].w - 0u) / 4u));
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, PromoteArrayInitializerToConstVar) {
-  auto* array_init = array<i32, 4>(1, 2, 3, 4);
-  auto* array_index = IndexAccessor(array_init, 3);
-  auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index);
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(pos),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(void main() {
-  const int tint_symbol[4] = {1, 2, 3, 4};
-  int pos = tint_symbol[3];
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, PromoteStructInitializerToConstVar) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.vec3<f32>()),
-                                 Member("c", ty.i32()),
-                             });
-  auto* struct_init = Construct(ty.Of(str), 1, vec3<f32>(2.f, 3.f, 4.f), 4);
-  auto* struct_access = MemberAccessor(struct_init, "b");
-  auto* pos =
-      Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(pos),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(struct S {
-  int a;
-  float3 b;
-  int c;
-};
-
-void main() {
-  const S tint_symbol = {1, float3(2.0f, 3.0f, 4.0f), 4};
-  float3 pos = tint_symbol.b;
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, InlinePtrLetsBasic) {
-  // var v : i32;
-  // let p : ptr<function, i32> = &v;
-  // let x : i32 = *p;
-  auto* v = Var("v", ty.i32());
-  auto* p =
-      Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
-  auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(v),
-           Decl(p),
-           Decl(x),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(void main() {
-  int v = 0;
-  int x = v;
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(HlslSanitizerTest, InlinePtrLetsComplexChain) {
-  // var a : array<mat4x4<f32>, 4>;
-  // let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
-  // let mp : ptr<function, mat4x4<f32>> = &(*ap)[3];
-  // let vp : ptr<function, vec4<f32>> = &(*mp)[2];
-  // let v : vec4<f32> = *vp;
-  auto* a = Var("a", ty.array(ty.mat4x4<f32>(), 4));
-  auto* ap = Const(
-      "ap",
-      ty.pointer(ty.array(ty.mat4x4<f32>(), 4), ast::StorageClass::kFunction),
-      AddressOf(a));
-  auto* mp =
-      Const("mp", ty.pointer(ty.mat4x4<f32>(), ast::StorageClass::kFunction),
-            AddressOf(IndexAccessor(Deref(ap), 3)));
-  auto* vp =
-      Const("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
-            AddressOf(IndexAccessor(Deref(mp), 2)));
-  auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       {
-           Decl(a),
-           Decl(ap),
-           Decl(mp),
-           Decl(vp),
-           Decl(v),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(void main() {
-  float4x4 a[4] = (float4x4[4])0;
-  float4 v = a[3][2];
-  return;
-}
-)";
-  EXPECT_EQ(expect, got);
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_switch_test.cc b/src/writer/hlsl/generator_impl_switch_test.cc
deleted file mode 100644
index 1f65348..0000000
--- a/src/writer/hlsl/generator_impl_switch_test.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest_Switch = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Switch, Emit_Switch) {
-  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
-  auto* s = Switch(                   //
-      Expr("cond"),                   //
-      Case(Expr(5), Block(Break())),  //
-      DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  switch(cond) {
-    case 5: {
-      break;
-    }
-    default: {
-      break;
-    }
-  }
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Switch, Emit_Switch_OnlyDefaultCase) {
-  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
-  Global("a", ty.i32(), ast::StorageClass::kPrivate);
-  auto* s = Switch(  //
-      Expr("cond"),  //
-      DefaultCase(Block(Assign(Expr("a"), Expr(42)))));
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  cond;
-  do {
-    a = 42;
-  } while (false);
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_test.cc b/src/writer/hlsl/generator_impl_test.cc
deleted file mode 100644
index 1eb4087..0000000
--- a/src/writer/hlsl/generator_impl_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslGeneratorImplTest = TestHelper;
-
-TEST_F(HlslGeneratorImplTest, Generate) {
-  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::AttributeList{});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(void my_func() {
-}
-)");
-}
-
-struct HlslBuiltinData {
-  ast::Builtin builtin;
-  const char* attribute_name;
-};
-inline std::ostream& operator<<(std::ostream& out, HlslBuiltinData data) {
-  out << data.builtin;
-  return out;
-}
-using HlslBuiltinConversionTest = TestParamHelper<HlslBuiltinData>;
-TEST_P(HlslBuiltinConversionTest, Emit) {
-  auto params = GetParam();
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(gen.builtin_to_attribute(params.builtin),
-            std::string(params.attribute_name));
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest,
-    HlslBuiltinConversionTest,
-    testing::Values(
-        HlslBuiltinData{ast::Builtin::kPosition, "SV_Position"},
-        HlslBuiltinData{ast::Builtin::kVertexIndex, "SV_VertexID"},
-        HlslBuiltinData{ast::Builtin::kInstanceIndex, "SV_InstanceID"},
-        HlslBuiltinData{ast::Builtin::kFrontFacing, "SV_IsFrontFace"},
-        HlslBuiltinData{ast::Builtin::kFragDepth, "SV_Depth"},
-        HlslBuiltinData{ast::Builtin::kLocalInvocationId, "SV_GroupThreadID"},
-        HlslBuiltinData{ast::Builtin::kLocalInvocationIndex, "SV_GroupIndex"},
-        HlslBuiltinData{ast::Builtin::kGlobalInvocationId,
-                        "SV_DispatchThreadID"},
-        HlslBuiltinData{ast::Builtin::kWorkgroupId, "SV_GroupID"},
-        HlslBuiltinData{ast::Builtin::kSampleIndex, "SV_SampleIndex"},
-        HlslBuiltinData{ast::Builtin::kSampleMask, "SV_Coverage"}));
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
deleted file mode 100644
index dd26e59..0000000
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ /dev/null
@@ -1,606 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_Type = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[4]");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
-  auto* arr = ty.array(ty.array<bool, 4>(), 5);
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[5][4]");
-}
-
-// TODO(dsinclair): Is this possible? What order should it output in?
-TEST_F(HlslGeneratorImplTest_Type,
-       DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
-  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5));
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[5][4][1]");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
-  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, "ary"))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool[4]");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Bool) {
-  auto* bool_ = create<sem::Bool>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "bool");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_F32) {
-  auto* f32 = create<sem::F32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_I32) {
-  auto* i32 = create<sem::I32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "int");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float2x3");
-}
-
-// TODO(dsinclair): How to annotate as workgroup?
-TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
-  auto* f32 = create<sem::F32>();
-  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
-                                 ast::Access::kReadWrite);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float*");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  int a;
-  float b;
-};
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), "RWByteAddressBuffer g : register(u0, space0);\n");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "S");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-  auto* s = Structure("S", {
-                               Member("double", ty.i32()),
-                               Member("float", ty.f32()),
-                           });
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(struct S {
-  int tint_symbol;
-  float tint_symbol_1;
-};
-)"));
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32(), {MemberOffset(0)}),
-                          Member("b", ty.f32(), {MemberOffset(8)}),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  int a;
-  float b;
-};
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_U32) {
-  auto* u32 = create<sem::U32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "uint");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Vector) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "float3");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitType_Void) {
-  auto* void_ = create<sem::Void>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "void");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitSampler) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "SamplerState");
-}
-
-TEST_F(HlslGeneratorImplTest_Type, EmitSamplerComparison) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "SamplerComparisonState");
-}
-
-struct HlslDepthTextureData {
-  ast::TextureDimension dim;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out, HlslDepthTextureData data) {
-  out << data.dim;
-  return out;
-}
-using HlslDepthTexturesTest = TestParamHelper<HlslDepthTextureData>;
-TEST_P(HlslDepthTexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* t = ty.depth_texture(params.dim);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_Type,
-    HlslDepthTexturesTest,
-    testing::Values(
-        HlslDepthTextureData{ast::TextureDimension::k2d,
-                             "Texture2D tex : register(t1, space2);"},
-        HlslDepthTextureData{ast::TextureDimension::k2dArray,
-                             "Texture2DArray tex : register(t1, space2);"},
-        HlslDepthTextureData{ast::TextureDimension::kCube,
-                             "TextureCube tex : register(t1, space2);"},
-        HlslDepthTextureData{ast::TextureDimension::kCubeArray,
-                             "TextureCubeArray tex : register(t1, space2);"}));
-
-using HlslDepthMultisampledTexturesTest = TestHelper;
-TEST_F(HlslDepthMultisampledTexturesTest, Emit) {
-  auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("Texture2DMS<float4> tex : register(t1, space2);"));
-}
-
-enum class TextureDataType { F32, U32, I32 };
-struct HlslSampledTextureData {
-  ast::TextureDimension dim;
-  TextureDataType datatype;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                HlslSampledTextureData data) {
-  out << data.dim;
-  return out;
-}
-using HlslSampledTexturesTest = TestParamHelper<HlslSampledTextureData>;
-TEST_P(HlslSampledTexturesTest, Emit) {
-  auto params = GetParam();
-
-  const ast::Type* datatype = nullptr;
-  switch (params.datatype) {
-    case TextureDataType::F32:
-      datatype = ty.f32();
-      break;
-    case TextureDataType::U32:
-      datatype = ty.u32();
-      break;
-    case TextureDataType::I32:
-      datatype = ty.i32();
-      break;
-  }
-  auto* t = ty.sampled_texture(params.dim, datatype);
-
-  Global("tex", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_Type,
-    HlslSampledTexturesTest,
-    testing::Values(
-        HlslSampledTextureData{
-            ast::TextureDimension::k1d,
-            TextureDataType::F32,
-            "Texture1D<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2d,
-            TextureDataType::F32,
-            "Texture2D<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2dArray,
-            TextureDataType::F32,
-            "Texture2DArray<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k3d,
-            TextureDataType::F32,
-            "Texture3D<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCube,
-            TextureDataType::F32,
-            "TextureCube<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCubeArray,
-            TextureDataType::F32,
-            "TextureCubeArray<float4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k1d,
-            TextureDataType::U32,
-            "Texture1D<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2d,
-            TextureDataType::U32,
-            "Texture2D<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2dArray,
-            TextureDataType::U32,
-            "Texture2DArray<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k3d,
-            TextureDataType::U32,
-            "Texture3D<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCube,
-            TextureDataType::U32,
-            "TextureCube<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCubeArray,
-            TextureDataType::U32,
-            "TextureCubeArray<uint4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k1d,
-            TextureDataType::I32,
-            "Texture1D<int4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2d,
-            TextureDataType::I32,
-            "Texture2D<int4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k2dArray,
-            TextureDataType::I32,
-            "Texture2DArray<int4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::k3d,
-            TextureDataType::I32,
-            "Texture3D<int4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCube,
-            TextureDataType::I32,
-            "TextureCube<int4> tex : register(t1, space2);",
-        },
-        HlslSampledTextureData{
-            ast::TextureDimension::kCubeArray,
-            TextureDataType::I32,
-            "TextureCubeArray<int4> tex : register(t1, space2);",
-        }));
-
-TEST_F(HlslGeneratorImplTest_Type, EmitMultisampledTexture) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone,
-                           ast::Access::kReadWrite, ""))
-      << gen.error();
-  EXPECT_EQ(out.str(), "Texture2DMS<float4>");
-}
-
-struct HlslStorageTextureData {
-  ast::TextureDimension dim;
-  ast::TexelFormat imgfmt;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out,
-                                HlslStorageTextureData data) {
-  out << data.dim;
-  return out;
-}
-using HlslStorageTexturesTest = TestParamHelper<HlslStorageTextureData>;
-TEST_P(HlslStorageTexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
-
-  Global("tex", t, ast::AttributeList{GroupAndBinding(2, 1)});
-
-  Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(params.result));
-}
-INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest_Type,
-    HlslStorageTexturesTest,
-    testing::Values(
-        HlslStorageTextureData{
-            ast::TextureDimension::k1d, ast::TexelFormat::kRgba8Unorm,
-            "RWTexture1D<float4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k2d, ast::TexelFormat::kRgba16Float,
-            "RWTexture2D<float4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Float,
-            "RWTexture2DArray<float4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k3d, ast::TexelFormat::kRg32Float,
-            "RWTexture3D<float4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Float,
-            "RWTexture1D<float4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k2d, ast::TexelFormat::kRgba16Uint,
-            "RWTexture2D<uint4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Uint,
-            "RWTexture2DArray<uint4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k3d, ast::TexelFormat::kRg32Uint,
-            "RWTexture3D<uint4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Uint,
-            "RWTexture1D<uint4> tex : register(u1, space2);"},
-        HlslStorageTextureData{ast::TextureDimension::k2d,
-                               ast::TexelFormat::kRgba16Sint,
-                               "RWTexture2D<int4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Sint,
-            "RWTexture2DArray<int4> tex : register(u1, space2);"},
-        HlslStorageTextureData{ast::TextureDimension::k3d,
-                               ast::TexelFormat::kRg32Sint,
-                               "RWTexture3D<int4> tex : register(u1, space2);"},
-        HlslStorageTextureData{
-            ast::TextureDimension::k1d, ast::TexelFormat::kRgba32Sint,
-            "RWTexture1D<int4> tex : register(u1, space2);"}));
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_unary_op_test.cc b/src/writer/hlsl/generator_impl_unary_op_test.cc
deleted file mode 100644
index 5fb7cd6..0000000
--- a/src/writer/hlsl/generator_impl_unary_op_test.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2020 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/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using HlslUnaryOpTest = TestHelper;
-
-TEST_F(HlslUnaryOpTest, AddressOf) {
-  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "expr");
-}
-
-TEST_F(HlslUnaryOpTest, Complement) {
-  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "~(expr)");
-}
-
-TEST_F(HlslUnaryOpTest, Indirection) {
-  Global("G", ty.f32(), ast::StorageClass::kPrivate);
-  auto* p = Const(
-      "expr", nullptr,
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
-  WrapInFunction(p, op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "expr");
-}
-
-TEST_F(HlslUnaryOpTest, Not) {
-  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "!(expr)");
-}
-
-TEST_F(HlslUnaryOpTest, Negation) {
-  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "-(expr)");
-}
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc b/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
deleted file mode 100644
index e5bec64..0000000
--- a/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_VariableDecl = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) {
-  auto* var = Var("a", ty.f32());
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
-  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  const float a = 0.0f;\n");
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
-  auto* var = Var("a", ty.array<f32, 5>());
-
-  WrapInFunction(var, Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("  float a[5] = (float[5])0;\n"));
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) {
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("  static float a = 0.0f;\n"));
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_Private) {
-  Global("initializer", ty.f32(), ast::StorageClass::kPrivate);
-  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer;
-)"));
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_ZeroVec) {
-  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float3 a = float3(0.0f, 0.0f, 0.0f);
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_VariableDecl,
-       Emit_VariableDeclStatement_Initializer_ZeroMat) {
-  auto* var =
-      Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(),
-            R"(float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
deleted file mode 100644
index 4da3a81..0000000
--- a/src/writer/hlsl/generator_impl_workgroup_var_test.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/stage_attribute.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_WorkgroupVar = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_WorkgroupVar, Basic) {
-  Global("wg", ty.f32(), ast::StorageClass::kWorkgroup);
-
-  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
-}
-
-TEST_F(HlslGeneratorImplTest_WorkgroupVar, Aliased) {
-  auto* alias = Alias("F32", ty.f32());
-
-  Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
-
-  Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/test_helper.h b/src/writer/hlsl/test_helper.h
deleted file mode 100644
index 337a1ac..0000000
--- a/src/writer/hlsl/test_helper.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2020 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_WRITER_HLSL_TEST_HELPER_H_
-#define SRC_WRITER_HLSL_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/transform/manager.h"
-#include "src/transform/renamer.h"
-#include "src/writer/hlsl/generator.h"
-#include "src/writer/hlsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-
-/// Helper class for testing
-template <typename BODY>
-class TestHelperBase : public BODY, public ProgramBuilder {
- public:
-  TestHelperBase() = default;
-  ~TestHelperBase() override = default;
-
-  /// Builds the program and returns a GeneratorImpl from the program.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @return the built generator
-  GeneratorImpl& Build() {
-    if (gen_) {
-      return *gen_;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-    gen_ = std::make_unique<GeneratorImpl>(program.get());
-    return *gen_;
-  }
-
-  /// Builds the program, runs the program through the HLSL sanitizer
-  /// and returns a GeneratorImpl from the sanitized program.
-  /// @param options The HLSL generator options.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @return the built generator
-  GeneratorImpl& SanitizeAndBuild(const Options& options = {}) {
-    if (gen_) {
-      return *gen_;
-    }
-    diag::Formatter formatter;
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << formatter.format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << formatter.format(program->Diagnostics());
-    }();
-
-    auto sanitized_result = Sanitize(
-        program.get(), options.root_constant_binding_point,
-        options.disable_workgroup_init, options.array_length_from_uniform);
-    [&]() {
-      ASSERT_TRUE(sanitized_result.program.IsValid())
-          << formatter.format(sanitized_result.program.Diagnostics());
-    }();
-
-    transform::Manager transform_manager;
-    transform::DataMap transform_data;
-    transform_data.Add<transform::Renamer::Config>(
-        transform::Renamer::Target::kHlslKeywords,
-        /* preserve_unicode */ true);
-    transform_manager.Add<tint::transform::Renamer>();
-    auto result =
-        transform_manager.Run(&sanitized_result.program, transform_data);
-    [&]() {
-      ASSERT_TRUE(result.program.IsValid())
-          << formatter.format(result.program.Diagnostics());
-    }();
-    *program = std::move(result.program);
-    gen_ = std::make_unique<GeneratorImpl>(program.get());
-    return *gen_;
-  }
-
-  /// The program built with a call to Build()
-  std::unique_ptr<Program> program;
-
- private:
-  std::unique_ptr<GeneratorImpl> gen_;
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_HLSL_TEST_HELPER_H_
diff --git a/src/writer/msl/generator.cc b/src/writer/msl/generator.cc
deleted file mode 100644
index 92741e8..0000000
--- a/src/writer/msl/generator.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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/writer/msl/generator.h"
-
-#include <utility>
-
-#include "src/writer/msl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-
-Options::Options() = default;
-Options::~Options() = default;
-Options::Options(const Options&) = default;
-Options& Options::operator=(const Options&) = default;
-
-Result::Result() = default;
-Result::~Result() = default;
-Result::Result(const Result&) = default;
-
-Result Generate(const Program* program, const Options& options) {
-  Result result;
-
-  // Sanitize the program.
-  auto sanitized_result = Sanitize(
-      program, options.buffer_size_ubo_index, options.fixed_sample_mask,
-      options.emit_vertex_point_size, options.disable_workgroup_init,
-      options.array_length_from_uniform);
-  if (!sanitized_result.program.IsValid()) {
-    result.success = false;
-    result.error = sanitized_result.program.Diagnostics().str();
-    return result;
-  }
-  result.needs_storage_buffer_sizes =
-      sanitized_result.needs_storage_buffer_sizes;
-  result.used_array_length_from_uniform_indices =
-      std::move(sanitized_result.used_array_length_from_uniform_indices);
-
-  // Generate the MSL code.
-  auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program);
-  result.success = impl->Generate();
-  result.error = impl->error();
-  result.msl = impl->result();
-  result.has_invariant_attribute = impl->HasInvariant();
-  result.workgroup_allocations = impl->DynamicWorkgroupAllocations();
-
-  return result;
-}
-
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator.h b/src/writer/msl/generator.h
deleted file mode 100644
index 7f8f92f..0000000
--- a/src/writer/msl/generator.h
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2020 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_WRITER_MSL_GENERATOR_H_
-#define SRC_WRITER_MSL_GENERATOR_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
-#include "src/writer/array_length_from_uniform_options.h"
-#include "src/writer/text.h"
-
-namespace tint {
-
-// Forward declarations
-class Program;
-
-namespace writer {
-namespace msl {
-
-class GeneratorImpl;
-
-/// Configuration options used for generating MSL.
-struct Options {
-  /// Constructor
-  Options();
-  /// Destructor
-  ~Options();
-  /// Copy constructor
-  Options(const Options&);
-  /// Copy assignment
-  /// @returns this Options
-  Options& operator=(const Options&);
-
-  /// The index to use when generating a UBO to receive storage buffer sizes.
-  /// Defaults to 30, which is the last valid buffer slot.
-  uint32_t buffer_size_ubo_index = 30;
-
-  /// The fixed sample mask to combine with fragment shader outputs.
-  /// Defaults to 0xFFFFFFFF.
-  uint32_t fixed_sample_mask = 0xFFFFFFFF;
-
-  /// Set to `true` to generate a [[point_size]] attribute which is set to 1.0
-  /// for all vertex shaders in the module.
-  bool emit_vertex_point_size = false;
-
-  /// Set to `true` to disable workgroup memory zero initialization
-  bool disable_workgroup_init = false;
-
-  /// Options used to specify a mapping of binding points to indices into a UBO
-  /// from which to load buffer sizes.
-  ArrayLengthFromUniformOptions array_length_from_uniform = {};
-
-  // NOTE: Update fuzzers/data_builder.h when adding or changing any struct
-  // members.
-};
-
-/// The result produced when generating MSL.
-struct Result {
-  /// Constructor
-  Result();
-
-  /// Destructor
-  ~Result();
-
-  /// Copy constructor
-  Result(const Result&);
-
-  /// True if generation was successful.
-  bool success = false;
-
-  /// The errors generated during code generation, if any.
-  std::string error;
-
-  /// The generated MSL.
-  std::string msl = "";
-
-  /// True if the shader needs a UBO of buffer sizes.
-  bool needs_storage_buffer_sizes = false;
-
-  /// True if the generated shader uses the invariant attribute.
-  bool has_invariant_attribute = false;
-
-  /// A map from entry point name to a list of dynamic workgroup allocations.
-  /// Each entry in the vector is the size of the workgroup allocation that
-  /// should be created for that index.
-  std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations;
-
-  /// Indices into the array_length_from_uniform binding that are statically
-  /// used.
-  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
-};
-
-/// Generate MSL for a program, according to a set of configuration options. The
-/// result will contain the MSL, as well as success status and diagnostic
-/// information.
-/// @param program the program to translate to MSL
-/// @param options the configuration options to use when generating MSL
-/// @returns the resulting MSL and supplementary information
-Result Generate(const Program* program, const Options& options);
-
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_MSL_GENERATOR_H_
diff --git a/src/writer/msl/generator_bench.cc b/src/writer/msl/generator_bench.cc
deleted file mode 100644
index fb9fee6..0000000
--- a/src/writer/msl/generator_bench.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 <string>
-
-#include "src/bench/benchmark.h"
-
-namespace tint::writer::msl {
-namespace {
-
-void GenerateMSL(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadProgram(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& program = std::get<bench::ProgramAndFile>(res).program;
-  for (auto _ : state) {
-    auto res = Generate(&program, {});
-    if (!res.error.empty()) {
-      state.SkipWithError(res.error.c_str());
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(GenerateMSL);
-
-}  // namespace
-}  // namespace tint::writer::msl
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
deleted file mode 100644
index 2930fcf..0000000
--- a/src/writer/msl/generator_impl.cc
+++ /dev/null
@@ -1,3023 +0,0 @@
-// Copyright 2020 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/writer/msl/generator_impl.h"
-
-#include <algorithm>
-#include <cmath>
-#include <iomanip>
-#include <limits>
-#include <utility>
-#include <vector>
-
-#include "src/ast/alias.h"
-#include "src/ast/bool_literal_expression.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_attribute.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/float_literal_expression.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/module.h"
-#include "src/ast/sint_literal_expression.h"
-#include "src/ast/uint_literal_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/void.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/bool_type.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/function.h"
-#include "src/sem/i32_type.h"
-#include "src/sem/matrix_type.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/pointer_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/u32_type.h"
-#include "src/sem/variable.h"
-#include "src/sem/vector_type.h"
-#include "src/sem/void_type.h"
-#include "src/transform/array_length_from_uniform.h"
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/transform/external_texture_transform.h"
-#include "src/transform/manager.h"
-#include "src/transform/module_scope_var_to_entry_point_param.h"
-#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_initializers_to_const_var.h"
-#include "src/transform/remove_phonies.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/unshadow.h"
-#include "src/transform/vectorize_scalar_matrix_constructors.h"
-#include "src/transform/wrap_arrays_in_structs.h"
-#include "src/transform/zero_init_workgroup_memory.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/writer/float_to_string.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
-  return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
-}
-
-class ScopedBitCast {
- public:
-  ScopedBitCast(GeneratorImpl* generator,
-                std::ostream& stream,
-                const sem::Type* curr_type,
-                const sem::Type* target_type)
-      : s(stream) {
-    auto* target_vec_type = target_type->As<sem::Vector>();
-
-    // If we need to promote from scalar to vector, bitcast the scalar to the
-    // vector element type.
-    if (curr_type->is_scalar() && target_vec_type) {
-      target_type = target_vec_type->type();
-    }
-
-    // Bit cast
-    s << "as_type<";
-    generator->EmitType(s, target_type, "");
-    s << ">(";
-  }
-
-  ~ScopedBitCast() { s << ")"; }
-
- private:
-  std::ostream& s;
-};
-}  // namespace
-
-SanitizedResult::SanitizedResult() = default;
-SanitizedResult::~SanitizedResult() = default;
-SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
-
-SanitizedResult Sanitize(
-    const Program* in,
-    uint32_t buffer_size_ubo_index,
-    uint32_t fixed_sample_mask,
-    bool emit_vertex_point_size,
-    bool disable_workgroup_init,
-    const ArrayLengthFromUniformOptions& array_length_from_uniform) {
-  transform::Manager manager;
-  transform::DataMap data;
-
-  // Build the config for the internal ArrayLengthFromUniform transform.
-  transform::ArrayLengthFromUniform::Config array_length_from_uniform_cfg(
-      array_length_from_uniform.ubo_binding);
-  if (!array_length_from_uniform.bindpoint_to_size_index.empty()) {
-    // If |array_length_from_uniform| bindings are provided, use that config.
-    array_length_from_uniform_cfg.bindpoint_to_size_index =
-        array_length_from_uniform.bindpoint_to_size_index;
-  } else {
-    // If the binding map is empty, use the deprecated |buffer_size_ubo_index|
-    // and automatically choose indices using the binding numbers.
-    array_length_from_uniform_cfg = transform::ArrayLengthFromUniform::Config(
-        sem::BindingPoint{0, buffer_size_ubo_index});
-    // Use the SSBO binding numbers as the indices for the buffer size lookups.
-    for (auto* var : in->AST().GlobalVariables()) {
-      auto* global = in->Sem().Get<sem::GlobalVariable>(var);
-      if (global && global->StorageClass() == ast::StorageClass::kStorage) {
-        array_length_from_uniform_cfg.bindpoint_to_size_index.emplace(
-            global->BindingPoint(), global->BindingPoint().binding);
-      }
-    }
-  }
-
-  // Build the configs for the internal CanonicalizeEntryPointIO transform.
-  auto entry_point_io_cfg = transform::CanonicalizeEntryPointIO::Config(
-      transform::CanonicalizeEntryPointIO::ShaderStyle::kMsl, fixed_sample_mask,
-      emit_vertex_point_size);
-
-  manager.Add<transform::Unshadow>();
-
-  if (!disable_workgroup_init) {
-    // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
-    // ZeroInitWorkgroupMemory may inject new builtin parameters.
-    manager.Add<transform::ZeroInitWorkgroupMemory>();
-  }
-  manager.Add<transform::CanonicalizeEntryPointIO>();
-  manager.Add<transform::ExternalTextureTransform>();
-  manager.Add<transform::PromoteInitializersToConstVar>();
-
-  manager.Add<transform::VectorizeScalarMatrixConstructors>();
-  manager.Add<transform::WrapArraysInStructs>();
-  manager.Add<transform::PadArrayElements>();
-  manager.Add<transform::RemovePhonies>();
-  manager.Add<transform::SimplifyPointers>();
-  // ArrayLengthFromUniform must come after SimplifyPointers, as
-  // it assumes that the form of the array length argument is &var.array.
-  manager.Add<transform::ArrayLengthFromUniform>();
-  manager.Add<transform::ModuleScopeVarToEntryPointParam>();
-  data.Add<transform::ArrayLengthFromUniform::Config>(
-      std::move(array_length_from_uniform_cfg));
-  data.Add<transform::CanonicalizeEntryPointIO::Config>(
-      std::move(entry_point_io_cfg));
-  auto out = manager.Run(in, data);
-
-  SanitizedResult result;
-  result.program = std::move(out.program);
-  if (!result.program.IsValid()) {
-    return result;
-  }
-  if (auto* res = out.data.Get<transform::ArrayLengthFromUniform::Result>()) {
-    result.used_array_length_from_uniform_indices =
-        std::move(res->used_size_indices);
-  }
-  result.needs_storage_buffer_sizes =
-      !result.used_array_length_from_uniform_indices.empty();
-  return result;
-}
-
-GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
-
-GeneratorImpl::~GeneratorImpl() = default;
-
-bool GeneratorImpl::Generate() {
-  line() << "#include <metal_stdlib>";
-  line();
-  line() << "using namespace metal;";
-
-  auto helpers_insertion_point = current_buffer_->lines.size();
-
-  auto* mod = builder_.Sem().Module();
-  for (auto* decl : mod->DependencyOrderedDeclarations()) {
-    bool ok = Switch(
-        decl,  //
-        [&](const ast::Struct* str) {
-          TINT_DEFER(line());
-          return EmitTypeDecl(TypeOf(str));
-        },
-        [&](const ast::Alias*) {
-          return true;  // folded away by the writer
-        },
-        [&](const ast::Variable* var) {
-          if (var->is_const) {
-            TINT_DEFER(line());
-            return EmitProgramConstVariable(var);
-          }
-          // These are pushed into the entry point by sanitizer transforms.
-          TINT_ICE(Writer, diagnostics_)
-              << "module-scope variables should have been handled by the MSL "
-                 "sanitizer";
-          return false;
-        },
-        [&](const ast::Function* func) {
-          TINT_DEFER(line());
-          if (func->IsEntryPoint()) {
-            return EmitEntryPointFunction(func);
-          }
-          return EmitFunction(func);
-        },
-        [&](Default) {
-          // These are pushed into the entry point by sanitizer transforms.
-          TINT_ICE(Writer, diagnostics_)
-              << "unhandled type: " << decl->TypeInfo().name;
-          return false;
-        });
-    if (!ok) {
-      return false;
-    }
-  }
-
-  if (!invariant_define_name_.empty()) {
-    // 'invariant' attribute requires MSL 2.1 or higher.
-    // WGSL can ignore the invariant attribute on pre MSL 2.1 devices.
-    // See: https://github.com/gpuweb/gpuweb/issues/893#issuecomment-745537465
-    line(&helpers_) << "#if __METAL_VERSION__ >= 210";
-    line(&helpers_) << "#define " << invariant_define_name_ << " [[invariant]]";
-    line(&helpers_) << "#else";
-    line(&helpers_) << "#define " << invariant_define_name_;
-    line(&helpers_) << "#endif";
-    line(&helpers_);
-  }
-
-  if (!helpers_.lines.empty()) {
-    current_buffer_->Insert("", helpers_insertion_point++, 0);
-    current_buffer_->Insert(helpers_, helpers_insertion_point++, 0);
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeDecl(const sem::Type* ty) {
-  if (auto* str = ty->As<sem::Struct>()) {
-    if (!EmitStructType(current_buffer_, str)) {
-      return false;
-    }
-  } else {
-    diagnostics_.add_error(diag::System::Writer,
-                           "unknown alias type: " + ty->type_name());
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitIndexAccessor(
-    std::ostream& out,
-    const ast::IndexAccessorExpression* expr) {
-  bool paren_lhs =
-      !expr->object->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
-                             ast::IdentifierExpression,
-                             ast::MemberAccessorExpression>();
-
-  if (paren_lhs) {
-    out << "(";
-  }
-  if (!EmitExpression(out, expr->object)) {
-    return false;
-  }
-  if (paren_lhs) {
-    out << ")";
-  }
-
-  out << "[";
-
-  if (!EmitExpression(out, expr->index)) {
-    return false;
-  }
-  out << "]";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBitcast(std::ostream& out,
-                                const ast::BitcastExpression* expr) {
-  out << "as_type<";
-  if (!EmitType(out, TypeOf(expr)->UnwrapRef(), "")) {
-    return false;
-  }
-
-  out << ">(";
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
-  auto out = line();
-
-  if (!EmitExpression(out, stmt->lhs)) {
-    return false;
-  }
-
-  out << " = ";
-
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBinary(std::ostream& out,
-                               const ast::BinaryExpression* expr) {
-  auto emit_op = [&] {
-    out << " ";
-
-    switch (expr->op) {
-      case ast::BinaryOp::kAnd:
-        out << "&";
-        break;
-      case ast::BinaryOp::kOr:
-        out << "|";
-        break;
-      case ast::BinaryOp::kXor:
-        out << "^";
-        break;
-      case ast::BinaryOp::kLogicalAnd:
-        out << "&&";
-        break;
-      case ast::BinaryOp::kLogicalOr:
-        out << "||";
-        break;
-      case ast::BinaryOp::kEqual:
-        out << "==";
-        break;
-      case ast::BinaryOp::kNotEqual:
-        out << "!=";
-        break;
-      case ast::BinaryOp::kLessThan:
-        out << "<";
-        break;
-      case ast::BinaryOp::kGreaterThan:
-        out << ">";
-        break;
-      case ast::BinaryOp::kLessThanEqual:
-        out << "<=";
-        break;
-      case ast::BinaryOp::kGreaterThanEqual:
-        out << ">=";
-        break;
-      case ast::BinaryOp::kShiftLeft:
-        out << "<<";
-        break;
-      case ast::BinaryOp::kShiftRight:
-        // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
-        // implementation-defined behaviour for negative LHS.  We may have to
-        // generate extra code to implement WGSL-specified behaviour for
-        // negative LHS.
-        out << R"(>>)";
-        break;
-
-      case ast::BinaryOp::kAdd:
-        out << "+";
-        break;
-      case ast::BinaryOp::kSubtract:
-        out << "-";
-        break;
-      case ast::BinaryOp::kMultiply:
-        out << "*";
-        break;
-      case ast::BinaryOp::kDivide:
-        out << "/";
-        break;
-      case ast::BinaryOp::kModulo:
-        out << "%";
-        break;
-      case ast::BinaryOp::kNone:
-        diagnostics_.add_error(diag::System::Writer,
-                               "missing binary operation type");
-        return false;
-    }
-    out << " ";
-    return true;
-  };
-
-  auto signed_type_of = [&](const sem::Type* ty) -> const sem::Type* {
-    if (ty->is_integer_scalar()) {
-      return builder_.create<sem::I32>();
-    } else if (auto* v = ty->As<sem::Vector>()) {
-      return builder_.create<sem::Vector>(builder_.create<sem::I32>(),
-                                          v->Width());
-    }
-    return {};
-  };
-
-  auto unsigned_type_of = [&](const sem::Type* ty) -> const sem::Type* {
-    if (ty->is_integer_scalar()) {
-      return builder_.create<sem::U32>();
-    } else if (auto* v = ty->As<sem::Vector>()) {
-      return builder_.create<sem::Vector>(builder_.create<sem::U32>(),
-                                          v->Width());
-    }
-    return {};
-  };
-
-  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
-  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
-
-  // Handle fmod
-  if (expr->op == ast::BinaryOp::kModulo &&
-      lhs_type->is_float_scalar_or_vector()) {
-    out << "fmod";
-    ScopedParen sp(out);
-    if (!EmitExpression(out, expr->lhs)) {
-      return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, expr->rhs)) {
-      return false;
-    }
-    return true;
-  }
-
-  // Handle +/-/* of signed values
-  if ((expr->IsAdd() || expr->IsSubtract() || expr->IsMultiply()) &&
-      lhs_type->is_signed_scalar_or_vector() &&
-      rhs_type->is_signed_scalar_or_vector()) {
-    // If lhs or rhs is a vector, use that type (support implicit scalar to
-    // vector promotion)
-    auto* target_type =
-        lhs_type->Is<sem::Vector>()
-            ? lhs_type
-            : (rhs_type->Is<sem::Vector>() ? rhs_type : lhs_type);
-
-    // WGSL defines behaviour for signed overflow, MSL does not. For these
-    // cases, bitcast operands to unsigned, then cast result to signed.
-    ScopedBitCast outer_int_cast(this, out, target_type,
-                                 signed_type_of(target_type));
-    ScopedParen sp(out);
-    {
-      ScopedBitCast lhs_uint_cast(this, out, lhs_type,
-                                  unsigned_type_of(target_type));
-      if (!EmitExpression(out, expr->lhs)) {
-        return false;
-      }
-    }
-    if (!emit_op()) {
-      return false;
-    }
-    {
-      ScopedBitCast rhs_uint_cast(this, out, rhs_type,
-                                  unsigned_type_of(target_type));
-      if (!EmitExpression(out, expr->rhs)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // Handle left bit shifting a signed value
-  // TODO(crbug.com/tint/1077): This may not be necessary. The MSL spec
-  // seems to imply that left shifting a signed value is treated the same as
-  // left shifting an unsigned value, but we need to make sure.
-  if (expr->IsShiftLeft() && lhs_type->is_signed_scalar_or_vector()) {
-    // Shift left: discards top bits, so convert first operand to unsigned
-    // first, then convert result back to signed
-    ScopedBitCast outer_int_cast(this, out, lhs_type, signed_type_of(lhs_type));
-    ScopedParen sp(out);
-    {
-      ScopedBitCast lhs_uint_cast(this, out, lhs_type,
-                                  unsigned_type_of(lhs_type));
-      if (!EmitExpression(out, expr->lhs)) {
-        return false;
-      }
-    }
-    if (!emit_op()) {
-      return false;
-    }
-    if (!EmitExpression(out, expr->rhs)) {
-      return false;
-    }
-    return true;
-  }
-
-  // Emit as usual
-  ScopedParen sp(out);
-  if (!EmitExpression(out, expr->lhs)) {
-    return false;
-  }
-  if (!emit_op()) {
-    return false;
-  }
-  if (!EmitExpression(out, expr->rhs)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
-  line() << "break;";
-  return true;
-}
-
-bool GeneratorImpl::EmitCall(std::ostream& out,
-                             const ast::CallExpression* expr) {
-  auto* call = program_->Sem().Get(expr);
-  auto* target = call->Target();
-  return Switch(
-      target,
-      [&](const sem::Function* func) {
-        return EmitFunctionCall(out, call, func);
-      },
-      [&](const sem::Builtin* builtin) {
-        return EmitBuiltinCall(out, call, builtin);
-      },
-      [&](const sem::TypeConversion* conv) {
-        return EmitTypeConversion(out, call, conv);
-      },
-      [&](const sem::TypeConstructor* ctor) {
-        return EmitTypeConstructor(out, call, ctor);
-      },
-      [&](Default) {
-        TINT_ICE(Writer, diagnostics_)
-            << "unhandled call target: " << target->TypeInfo().name;
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
-                                     const sem::Call* call,
-                                     const sem::Function*) {
-  auto* ident = call->Declaration()->target.name;
-  out << program_->Symbols().NameFor(ident->symbol) << "(";
-
-  bool first = true;
-  for (auto* arg : call->Arguments()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  auto* expr = call->Declaration();
-  if (builtin->IsAtomic()) {
-    return EmitAtomicCall(out, expr, builtin);
-  }
-  if (builtin->IsTexture()) {
-    return EmitTextureCall(out, call, builtin);
-  }
-
-  auto name = generate_builtin_name(builtin);
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kDot:
-      return EmitDotCall(out, expr, builtin);
-    case sem::BuiltinType::kModf:
-      return EmitModfCall(out, expr, builtin);
-    case sem::BuiltinType::kFrexp:
-      return EmitFrexpCall(out, expr, builtin);
-    case sem::BuiltinType::kDegrees:
-      return EmitDegreesCall(out, expr, builtin);
-    case sem::BuiltinType::kRadians:
-      return EmitRadiansCall(out, expr, builtin);
-
-    case sem::BuiltinType::kPack2x16float:
-    case sem::BuiltinType::kUnpack2x16float: {
-      if (builtin->Type() == sem::BuiltinType::kPack2x16float) {
-        out << "as_type<uint>(half2(";
-      } else {
-        out << "float2(as_type<half2>(";
-      }
-      if (!EmitExpression(out, expr->args[0])) {
-        return false;
-      }
-      out << "))";
-      return true;
-    }
-    // TODO(crbug.com/tint/661): Combine sequential barriers to a single
-    // instruction.
-    case sem::BuiltinType::kStorageBarrier: {
-      out << "threadgroup_barrier(mem_flags::mem_device)";
-      return true;
-    }
-    case sem::BuiltinType::kWorkgroupBarrier: {
-      out << "threadgroup_barrier(mem_flags::mem_threadgroup)";
-      return true;
-    }
-
-    case sem::BuiltinType::kLength: {
-      auto* sem = builder_.Sem().Get(expr->args[0]);
-      if (sem->Type()->UnwrapRef()->is_scalar()) {
-        // Emulate scalar overload using fabs(x).
-        name = "fabs";
-      }
-      break;
-    }
-
-    case sem::BuiltinType::kDistance: {
-      auto* sem = builder_.Sem().Get(expr->args[0]);
-      if (sem->Type()->UnwrapRef()->is_scalar()) {
-        // Emulate scalar overload using fabs(x - y);
-        out << "fabs";
-        ScopedParen sp(out);
-        if (!EmitExpression(out, expr->args[0])) {
-          return false;
-        }
-        out << " - ";
-        if (!EmitExpression(out, expr->args[1])) {
-          return false;
-        }
-        return true;
-      }
-      break;
-    }
-
-    default:
-      break;
-  }
-
-  if (name.empty()) {
-    return false;
-  }
-
-  out << name << "(";
-
-  bool first = true;
-  for (auto* arg : expr->args) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg)) {
-      return false;
-    }
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConversion(std::ostream& out,
-                                       const sem::Call* call,
-                                       const sem::TypeConversion* conv) {
-  if (!EmitType(out, conv->Target(), "")) {
-    return false;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, call->Arguments()[0]->Declaration())) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
-                                        const sem::Call* call,
-                                        const sem::TypeConstructor* ctor) {
-  auto* type = ctor->ReturnType();
-
-  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
-    out << "{";
-  } else {
-    if (!EmitType(out, type, "")) {
-      return false;
-    }
-    out << "(";
-  }
-
-  int i = 0;
-  for (auto* arg : call->Arguments()) {
-    if (i > 0) {
-      out << ", ";
-    }
-
-    if (auto* struct_ty = type->As<sem::Struct>()) {
-      // Emit field designators for structures to account for padding members.
-      auto* member = struct_ty->Members()[i]->Declaration();
-      auto name = program_->Symbols().NameFor(member->symbol);
-      out << "." << name << "=";
-    }
-
-    if (!EmitExpression(out, arg->Declaration())) {
-      return false;
-    }
-
-    i++;
-  }
-
-  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
-    out << "}";
-  } else {
-    out << ")";
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitAtomicCall(std::ostream& out,
-                                   const ast::CallExpression* expr,
-                                   const sem::Builtin* builtin) {
-  auto call = [&](const std::string& name, bool append_memory_order_relaxed) {
-    out << name;
-    {
-      ScopedParen sp(out);
-      for (size_t i = 0; i < expr->args.size(); i++) {
-        auto* arg = expr->args[i];
-        if (i > 0) {
-          out << ", ";
-        }
-        if (!EmitExpression(out, arg)) {
-          return false;
-        }
-      }
-      if (append_memory_order_relaxed) {
-        out << ", memory_order_relaxed";
-      }
-    }
-    return true;
-  };
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAtomicLoad:
-      return call("atomic_load_explicit", true);
-
-    case sem::BuiltinType::kAtomicStore:
-      return call("atomic_store_explicit", true);
-
-    case sem::BuiltinType::kAtomicAdd:
-      return call("atomic_fetch_add_explicit", true);
-
-    case sem::BuiltinType::kAtomicSub:
-      return call("atomic_fetch_sub_explicit", true);
-
-    case sem::BuiltinType::kAtomicMax:
-      return call("atomic_fetch_max_explicit", true);
-
-    case sem::BuiltinType::kAtomicMin:
-      return call("atomic_fetch_min_explicit", true);
-
-    case sem::BuiltinType::kAtomicAnd:
-      return call("atomic_fetch_and_explicit", true);
-
-    case sem::BuiltinType::kAtomicOr:
-      return call("atomic_fetch_or_explicit", true);
-
-    case sem::BuiltinType::kAtomicXor:
-      return call("atomic_fetch_xor_explicit", true);
-
-    case sem::BuiltinType::kAtomicExchange:
-      return call("atomic_exchange_explicit", true);
-
-    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
-      auto* ptr_ty = TypeOf(expr->args[0])->UnwrapRef()->As<sem::Pointer>();
-      auto sc = ptr_ty->StorageClass();
-
-      auto func = utils::GetOrCreate(
-          atomicCompareExchangeWeak_, sc, [&]() -> std::string {
-            auto name = UniqueIdentifier("atomicCompareExchangeWeak");
-            auto& buf = helpers_;
-
-            line(&buf) << "template <typename A, typename T>";
-            {
-              auto f = line(&buf);
-              f << "vec<T, 2> " << name << "(";
-              if (!EmitStorageClass(f, sc)) {
-                return "";
-              }
-              f << " A* atomic, T compare, T value) {";
-            }
-
-            buf.IncrementIndent();
-            TINT_DEFER({
-              buf.DecrementIndent();
-              line(&buf) << "}";
-              line(&buf);
-            });
-
-            line(&buf) << "T prev_value = compare;";
-            line(&buf) << "bool matched = "
-                          "atomic_compare_exchange_weak_explicit(atomic, "
-                          "&prev_value, value, memory_order_relaxed, "
-                          "memory_order_relaxed);";
-            line(&buf) << "return {prev_value, matched};";
-            return name;
-          });
-
-      return call(func, false);
-    }
-
-    default:
-      break;
-  }
-
-  TINT_UNREACHABLE(Writer, diagnostics_)
-      << "unsupported atomic builtin: " << builtin->Type();
-  return false;
-}
-
-bool GeneratorImpl::EmitTextureCall(std::ostream& out,
-                                    const sem::Call* call,
-                                    const sem::Builtin* builtin) {
-  using Usage = sem::ParameterUsage;
-
-  auto& signature = builtin->Signature();
-  auto* expr = call->Declaration();
-  auto& arguments = call->Arguments();
-
-  // Returns the argument with the given usage
-  auto arg = [&](Usage usage) {
-    int idx = signature.IndexOf(usage);
-    return (idx >= 0) ? arguments[idx] : nullptr;
-  };
-
-  auto* texture = arg(Usage::kTexture)->Declaration();
-  if (!texture) {
-    TINT_ICE(Writer, diagnostics_) << "missing texture arg";
-    return false;
-  }
-
-  auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
-
-  // Helper to emit the texture expression, wrapped in parentheses if the
-  // expression includes an operator with lower precedence than the member
-  // accessor used for the function calls.
-  auto texture_expr = [&]() {
-    bool paren_lhs =
-        !texture->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
-                          ast::IdentifierExpression,
-                          ast::MemberAccessorExpression>();
-    if (paren_lhs) {
-      out << "(";
-    }
-    if (!EmitExpression(out, texture)) {
-      return false;
-    }
-    if (paren_lhs) {
-      out << ")";
-    }
-    return true;
-  };
-
-  // MSL requires that `lod` is a constant 0 for 1D textures.
-  bool level_is_constant_zero =
-      texture_type->dim() == ast::TextureDimension::k1d;
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureDimensions: {
-      std::vector<const char*> dims;
-      switch (texture_type->dim()) {
-        case ast::TextureDimension::kNone:
-          diagnostics_.add_error(diag::System::Writer,
-                                 "texture dimension is kNone");
-          return false;
-        case ast::TextureDimension::k1d:
-          dims = {"width"};
-          break;
-        case ast::TextureDimension::k2d:
-        case ast::TextureDimension::k2dArray:
-        case ast::TextureDimension::kCube:
-        case ast::TextureDimension::kCubeArray:
-          dims = {"width", "height"};
-          break;
-        case ast::TextureDimension::k3d:
-          dims = {"width", "height", "depth"};
-          break;
-      }
-
-      auto get_dim = [&](const char* name) {
-        if (!texture_expr()) {
-          return false;
-        }
-        out << ".get_" << name << "(";
-        if (level_is_constant_zero) {
-          out << "0";
-        } else {
-          if (auto* level = arg(Usage::kLevel)) {
-            if (!EmitExpression(out, level->Declaration())) {
-              return false;
-            }
-          }
-        }
-        out << ")";
-        return true;
-      };
-
-      if (dims.size() == 1) {
-        out << "int(";
-        get_dim(dims[0]);
-        out << ")";
-      } else {
-        EmitType(out, TypeOf(expr)->UnwrapRef(), "");
-        out << "(";
-        for (size_t i = 0; i < dims.size(); i++) {
-          if (i > 0) {
-            out << ", ";
-          }
-          get_dim(dims[i]);
-        }
-        out << ")";
-      }
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumLayers: {
-      out << "int(";
-      if (!texture_expr()) {
-        return false;
-      }
-      out << ".get_array_size())";
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumLevels: {
-      out << "int(";
-      if (!texture_expr()) {
-        return false;
-      }
-      out << ".get_num_mip_levels())";
-      return true;
-    }
-    case sem::BuiltinType::kTextureNumSamples: {
-      out << "int(";
-      if (!texture_expr()) {
-        return false;
-      }
-      out << ".get_num_samples())";
-      return true;
-    }
-    default:
-      break;
-  }
-
-  if (!texture_expr()) {
-    return false;
-  }
-
-  bool lod_param_is_named = true;
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kTextureSample:
-    case sem::BuiltinType::kTextureSampleBias:
-    case sem::BuiltinType::kTextureSampleLevel:
-    case sem::BuiltinType::kTextureSampleGrad:
-      out << ".sample(";
-      break;
-    case sem::BuiltinType::kTextureSampleCompare:
-    case sem::BuiltinType::kTextureSampleCompareLevel:
-      out << ".sample_compare(";
-      break;
-    case sem::BuiltinType::kTextureGather:
-      out << ".gather(";
-      break;
-    case sem::BuiltinType::kTextureGatherCompare:
-      out << ".gather_compare(";
-      break;
-    case sem::BuiltinType::kTextureLoad:
-      out << ".read(";
-      lod_param_is_named = false;
-      break;
-    case sem::BuiltinType::kTextureStore:
-      out << ".write(";
-      break;
-    default:
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "Unhandled texture builtin '" << builtin->str() << "'";
-      return false;
-  }
-
-  bool first_arg = true;
-  auto maybe_write_comma = [&] {
-    if (!first_arg) {
-      out << ", ";
-    }
-    first_arg = false;
-  };
-
-  for (auto usage :
-       {Usage::kValue, Usage::kSampler, Usage::kCoords, Usage::kArrayIndex,
-        Usage::kDepthRef, Usage::kSampleIndex}) {
-    if (auto* e = arg(usage)) {
-      maybe_write_comma();
-
-      // Cast the coordinates to unsigned integers if necessary.
-      bool casted = false;
-      if (usage == Usage::kCoords &&
-          e->Type()->UnwrapRef()->is_integer_scalar_or_vector()) {
-        casted = true;
-        switch (texture_type->dim()) {
-          case ast::TextureDimension::k1d:
-            out << "uint(";
-            break;
-          case ast::TextureDimension::k2d:
-          case ast::TextureDimension::k2dArray:
-            out << "uint2(";
-            break;
-          case ast::TextureDimension::k3d:
-            out << "uint3(";
-            break;
-          default:
-            TINT_ICE(Writer, diagnostics_)
-                << "unhandled texture dimensionality";
-            break;
-        }
-      }
-
-      if (!EmitExpression(out, e->Declaration()))
-        return false;
-
-      if (casted) {
-        out << ")";
-      }
-    }
-  }
-
-  if (auto* bias = arg(Usage::kBias)) {
-    maybe_write_comma();
-    out << "bias(";
-    if (!EmitExpression(out, bias->Declaration())) {
-      return false;
-    }
-    out << ")";
-  }
-  if (auto* level = arg(Usage::kLevel)) {
-    maybe_write_comma();
-    if (lod_param_is_named) {
-      out << "level(";
-    }
-    if (level_is_constant_zero) {
-      out << "0";
-    } else {
-      if (!EmitExpression(out, level->Declaration())) {
-        return false;
-      }
-    }
-    if (lod_param_is_named) {
-      out << ")";
-    }
-  }
-  if (builtin->Type() == sem::BuiltinType::kTextureSampleCompareLevel) {
-    maybe_write_comma();
-    out << "level(0)";
-  }
-  if (auto* ddx = arg(Usage::kDdx)) {
-    auto dim = texture_type->dim();
-    switch (dim) {
-      case ast::TextureDimension::k2d:
-      case ast::TextureDimension::k2dArray:
-        maybe_write_comma();
-        out << "gradient2d(";
-        break;
-      case ast::TextureDimension::k3d:
-        maybe_write_comma();
-        out << "gradient3d(";
-        break;
-      case ast::TextureDimension::kCube:
-      case ast::TextureDimension::kCubeArray:
-        maybe_write_comma();
-        out << "gradientcube(";
-        break;
-      default: {
-        std::stringstream err;
-        err << "MSL does not support gradients for " << dim << " textures";
-        diagnostics_.add_error(diag::System::Writer, err.str());
-        return false;
-      }
-    }
-    if (!EmitExpression(out, ddx->Declaration())) {
-      return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, arg(Usage::kDdy)->Declaration())) {
-      return false;
-    }
-    out << ")";
-  }
-
-  bool has_offset = false;
-  if (auto* offset = arg(Usage::kOffset)) {
-    has_offset = true;
-    maybe_write_comma();
-    if (!EmitExpression(out, offset->Declaration())) {
-      return false;
-    }
-  }
-
-  if (auto* component = arg(Usage::kComponent)) {
-    maybe_write_comma();
-    if (!has_offset) {
-      // offset argument may need to be provided if we have a component.
-      switch (texture_type->dim()) {
-        case ast::TextureDimension::k2d:
-        case ast::TextureDimension::k2dArray:
-          out << "int2(0), ";
-          break;
-        default:
-          break;  // Other texture dimensions don't have an offset
-      }
-    }
-    auto c = component->ConstantValue().Elements()[0].i32;
-    switch (c) {
-      case 0:
-        out << "component::x";
-        break;
-      case 1:
-        out << "component::y";
-        break;
-      case 2:
-        out << "component::z";
-        break;
-      case 3:
-        out << "component::w";
-        break;
-      default:
-        TINT_ICE(Writer, diagnostics_)
-            << "invalid textureGather component: " << c;
-        break;
-    }
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDotCall(std::ostream& out,
-                                const ast::CallExpression* expr,
-                                const sem::Builtin* builtin) {
-  auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
-  std::string fn = "dot";
-  if (vec_ty->type()->is_integer_scalar()) {
-    // MSL does not have a builtin for dot() with integer vector types.
-    // Generate the helper function if it hasn't been created already
-    fn = utils::GetOrCreate(
-        int_dot_funcs_, vec_ty->Width(), [&]() -> std::string {
-          TextBuffer b;
-          TINT_DEFER(helpers_.Append(b));
-
-          auto fn_name =
-              UniqueIdentifier("tint_dot" + std::to_string(vec_ty->Width()));
-          auto v = "vec<T," + std::to_string(vec_ty->Width()) + ">";
-
-          line(&b) << "template<typename T>";
-          line(&b) << "T " << fn_name << "(" << v << " a, " << v << " b) {";
-          {
-            auto l = line(&b);
-            l << "  return ";
-            for (uint32_t i = 0; i < vec_ty->Width(); i++) {
-              if (i > 0) {
-                l << " + ";
-              }
-              l << "a[" << i << "]*b[" << i << "]";
-            }
-            l << ";";
-          }
-          line(&b) << "}";
-          return fn_name;
-        });
-  }
-
-  out << fn << "(";
-  if (!EmitExpression(out, expr->args[0])) {
-    return false;
-  }
-  out << ", ";
-  if (!EmitExpression(out, expr->args[1])) {
-    return false;
-  }
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitModfCall(std::ostream& out,
-                                 const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* ty = builtin->Parameters()[0]->Type();
-        auto in = params[0];
-
-        std::string width;
-        if (auto* vec = ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        // Emit the builtin return type unique to this overload. This does not
-        // exist in the AST, so it will not be generated in Generate().
-        if (!EmitStructType(&helpers_,
-                            builtin->ReturnType()->As<sem::Struct>())) {
-          return false;
-        }
-
-        line(b) << "float" << width << " whole;";
-        line(b) << "float" << width << " fract = modf(" << in << ", whole);";
-        line(b) << "return {fract, whole};";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
-                                  const ast::CallExpression* expr,
-                                  const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        auto* ty = builtin->Parameters()[0]->Type();
-        auto in = params[0];
-
-        std::string width;
-        if (auto* vec = ty->As<sem::Vector>()) {
-          width = std::to_string(vec->Width());
-        }
-
-        // Emit the builtin return type unique to this overload. This does not
-        // exist in the AST, so it will not be generated in Generate().
-        if (!EmitStructType(&helpers_,
-                            builtin->ReturnType()->As<sem::Struct>())) {
-          return false;
-        }
-
-        line(b) << "int" << width << " exp;";
-        line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
-        line(b) << "return {sig, exp};";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kRadToDeg << ";";
-        return true;
-      });
-}
-
-bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
-                                    const ast::CallExpression* expr,
-                                    const sem::Builtin* builtin) {
-  return CallBuiltinHelper(
-      out, expr, builtin,
-      [&](TextBuffer* b, const std::vector<std::string>& params) {
-        line(b) << "return " << params[0] << " * " << std::setprecision(20)
-                << sem::kDegToRad << ";";
-        return true;
-      });
-}
-
-std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
-  std::string out = "";
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAcos:
-    case sem::BuiltinType::kAll:
-    case sem::BuiltinType::kAny:
-    case sem::BuiltinType::kAsin:
-    case sem::BuiltinType::kAtan:
-    case sem::BuiltinType::kAtan2:
-    case sem::BuiltinType::kCeil:
-    case sem::BuiltinType::kCos:
-    case sem::BuiltinType::kCosh:
-    case sem::BuiltinType::kCross:
-    case sem::BuiltinType::kDeterminant:
-    case sem::BuiltinType::kDistance:
-    case sem::BuiltinType::kDot:
-    case sem::BuiltinType::kExp:
-    case sem::BuiltinType::kExp2:
-    case sem::BuiltinType::kFloor:
-    case sem::BuiltinType::kFma:
-    case sem::BuiltinType::kFract:
-    case sem::BuiltinType::kFrexp:
-    case sem::BuiltinType::kLength:
-    case sem::BuiltinType::kLdexp:
-    case sem::BuiltinType::kLog:
-    case sem::BuiltinType::kLog2:
-    case sem::BuiltinType::kMix:
-    case sem::BuiltinType::kModf:
-    case sem::BuiltinType::kNormalize:
-    case sem::BuiltinType::kPow:
-    case sem::BuiltinType::kReflect:
-    case sem::BuiltinType::kRefract:
-    case sem::BuiltinType::kSelect:
-    case sem::BuiltinType::kSin:
-    case sem::BuiltinType::kSinh:
-    case sem::BuiltinType::kSqrt:
-    case sem::BuiltinType::kStep:
-    case sem::BuiltinType::kTan:
-    case sem::BuiltinType::kTanh:
-    case sem::BuiltinType::kTranspose:
-    case sem::BuiltinType::kTrunc:
-    case sem::BuiltinType::kSign:
-    case sem::BuiltinType::kClamp:
-      out += builtin->str();
-      break;
-    case sem::BuiltinType::kAbs:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        out += "fabs";
-      } else {
-        out += "abs";
-      }
-      break;
-    case sem::BuiltinType::kCountOneBits:
-      out += "popcount";
-      break;
-    case sem::BuiltinType::kDpdx:
-    case sem::BuiltinType::kDpdxCoarse:
-    case sem::BuiltinType::kDpdxFine:
-      out += "dfdx";
-      break;
-    case sem::BuiltinType::kDpdy:
-    case sem::BuiltinType::kDpdyCoarse:
-    case sem::BuiltinType::kDpdyFine:
-      out += "dfdy";
-      break;
-    case sem::BuiltinType::kFwidth:
-    case sem::BuiltinType::kFwidthCoarse:
-    case sem::BuiltinType::kFwidthFine:
-      out += "fwidth";
-      break;
-    case sem::BuiltinType::kIsFinite:
-      out += "isfinite";
-      break;
-    case sem::BuiltinType::kIsInf:
-      out += "isinf";
-      break;
-    case sem::BuiltinType::kIsNan:
-      out += "isnan";
-      break;
-    case sem::BuiltinType::kIsNormal:
-      out += "isnormal";
-      break;
-    case sem::BuiltinType::kMax:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        out += "fmax";
-      } else {
-        out += "max";
-      }
-      break;
-    case sem::BuiltinType::kMin:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        out += "fmin";
-      } else {
-        out += "min";
-      }
-      break;
-    case sem::BuiltinType::kFaceForward:
-      out += "faceforward";
-      break;
-    case sem::BuiltinType::kPack4x8snorm:
-      out += "pack_float_to_snorm4x8";
-      break;
-    case sem::BuiltinType::kPack4x8unorm:
-      out += "pack_float_to_unorm4x8";
-      break;
-    case sem::BuiltinType::kPack2x16snorm:
-      out += "pack_float_to_snorm2x16";
-      break;
-    case sem::BuiltinType::kPack2x16unorm:
-      out += "pack_float_to_unorm2x16";
-      break;
-    case sem::BuiltinType::kReverseBits:
-      out += "reverse_bits";
-      break;
-    case sem::BuiltinType::kRound:
-      out += "rint";
-      break;
-    case sem::BuiltinType::kSmoothStep:
-      out += "smoothstep";
-      break;
-    case sem::BuiltinType::kInverseSqrt:
-      out += "rsqrt";
-      break;
-    case sem::BuiltinType::kUnpack4x8snorm:
-      out += "unpack_snorm4x8_to_float";
-      break;
-    case sem::BuiltinType::kUnpack4x8unorm:
-      out += "unpack_unorm4x8_to_float";
-      break;
-    case sem::BuiltinType::kUnpack2x16snorm:
-      out += "unpack_snorm2x16_to_float";
-      break;
-    case sem::BuiltinType::kUnpack2x16unorm:
-      out += "unpack_unorm2x16_to_float";
-      break;
-    case sem::BuiltinType::kArrayLength:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Unable to translate builtin: " + std::string(builtin->str()) +
-              "\nDid you forget to pass array_length_from_uniform generator "
-              "options?");
-      return "";
-    default:
-      diagnostics_.add_error(
-          diag::System::Writer,
-          "Unknown import method: " + std::string(builtin->str()));
-      return "";
-  }
-  return out;
-}
-
-bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
-  if (stmt->IsDefault()) {
-    line() << "default: {";
-  } else {
-    for (auto* selector : stmt->selectors) {
-      auto out = line();
-      out << "case ";
-      if (!EmitLiteral(out, selector)) {
-        return false;
-      }
-      out << ":";
-      if (selector == stmt->selectors.back()) {
-        out << " {";
-      }
-    }
-  }
-
-  {
-    ScopedIndent si(this);
-
-    for (auto* s : stmt->body->statements) {
-      if (!EmitStatement(s)) {
-        return false;
-      }
-    }
-
-    if (!last_is_break_or_fallthrough(stmt->body)) {
-      line() << "break;";
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
-  if (!emit_continuing_()) {
-    return false;
-  }
-
-  line() << "continue;";
-  return true;
-}
-
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
-  return Switch(
-      type,
-      [&](const sem::Bool*) {
-        out << "false";
-        return true;
-      },
-      [&](const sem::F32*) {
-        out << "0.0f";
-        return true;
-      },
-      [&](const sem::I32*) {
-        out << "0";
-        return true;
-      },
-      [&](const sem::U32*) {
-        out << "0u";
-        return true;
-      },
-      [&](const sem::Vector* vec) {  //
-        return EmitZeroValue(out, vec->type());
-      },
-      [&](const sem::Matrix* mat) {
-        if (!EmitType(out, mat, "")) {
-          return false;
-        }
-        out << "(";
-        TINT_DEFER(out << ")");
-        return EmitZeroValue(out, mat->type());
-      },
-      [&](const sem::Array* arr) {
-        out << "{";
-        TINT_DEFER(out << "}");
-        return EmitZeroValue(out, arr->ElemType());
-      },
-      [&](const sem::Struct*) {
-        out << "{}";
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "Invalid type for zero emission: " + type->type_name());
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitLiteral(std::ostream& out,
-                                const ast::LiteralExpression* lit) {
-  return Switch(
-      lit,
-      [&](const ast::BoolLiteralExpression* l) {
-        out << (l->value ? "true" : "false");
-        return true;
-      },
-      [&](const ast::FloatLiteralExpression* l) {
-        if (std::isinf(l->value)) {
-          out << (l->value >= 0 ? "INFINITY" : "-INFINITY");
-        } else if (std::isnan(l->value)) {
-          out << "NAN";
-        } else {
-          out << FloatToString(l->value) << "f";
-        }
-        return true;
-      },
-      [&](const ast::SintLiteralExpression* l) {
-        // MSL (and C++) parse `-2147483648` as a `long` because it parses
-        // unary minus and `2147483648` as separate tokens, and the latter
-        // doesn't fit into an (32-bit) `int`. WGSL, OTOH, parses this as an
-        // `i32`. To avoid issues with `long` to `int` casts, emit
-        // `(2147483647 - 1)` instead, which ensures the expression type is
-        // `int`.
-        const auto int_min = std::numeric_limits<int32_t>::min();
-        if (l->ValueAsI32() == int_min) {
-          out << "(" << int_min + 1 << " - 1)";
-        } else {
-          out << l->value;
-        }
-        return true;
-      },
-      [&](const ast::UintLiteralExpression* l) {
-        out << l->value << "u";
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitExpression(std::ostream& out,
-                                   const ast::Expression* expr) {
-  return Switch(
-      expr,
-      [&](const ast::IndexAccessorExpression* a) {  //
-        return EmitIndexAccessor(out, a);
-      },
-      [&](const ast::BinaryExpression* b) {  //
-        return EmitBinary(out, b);
-      },
-      [&](const ast::BitcastExpression* b) {  //
-        return EmitBitcast(out, b);
-      },
-      [&](const ast::CallExpression* c) {  //
-        return EmitCall(out, c);
-      },
-      [&](const ast::IdentifierExpression* i) {  //
-        return EmitIdentifier(out, i);
-      },
-      [&](const ast::LiteralExpression* l) {  //
-        return EmitLiteral(out, l);
-      },
-      [&](const ast::MemberAccessorExpression* m) {  //
-        return EmitMemberAccessor(out, m);
-      },
-      [&](const ast::UnaryOpExpression* u) {  //
-        return EmitUnaryOp(out, u);
-      },
-      [&](Default) {  //
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown expression type: " + std::string(expr->TypeInfo().name));
-        return false;
-      });
-}
-
-void GeneratorImpl::EmitStage(std::ostream& out, ast::PipelineStage stage) {
-  switch (stage) {
-    case ast::PipelineStage::kFragment:
-      out << "fragment";
-      break;
-    case ast::PipelineStage::kVertex:
-      out << "vertex";
-      break;
-    case ast::PipelineStage::kCompute:
-      out << "kernel";
-      break;
-    case ast::PipelineStage::kNone:
-      break;
-  }
-  return;
-}
-
-bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-  auto* func_sem = program_->Sem().Get(func);
-
-  {
-    auto out = line();
-    if (!EmitType(out, func_sem->ReturnType(), "")) {
-      return false;
-    }
-    out << " " << program_->Symbols().NameFor(func->symbol) << "(";
-
-    bool first = true;
-    for (auto* v : func->params) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      auto* type = program_->Sem().Get(v)->Type();
-
-      std::string param_name =
-          "const " + program_->Symbols().NameFor(v->symbol);
-      if (!EmitType(out, type, param_name)) {
-        return false;
-      }
-      // Parameter name is output as part of the type for arrays and pointers.
-      if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
-        out << " " << program_->Symbols().NameFor(v->symbol);
-      }
-    }
-
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(func->body->statements)) {
-    return false;
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
-  switch (builtin) {
-    case ast::Builtin::kPosition:
-      return "position";
-    case ast::Builtin::kVertexIndex:
-      return "vertex_id";
-    case ast::Builtin::kInstanceIndex:
-      return "instance_id";
-    case ast::Builtin::kFrontFacing:
-      return "front_facing";
-    case ast::Builtin::kFragDepth:
-      return "depth(any)";
-    case ast::Builtin::kLocalInvocationId:
-      return "thread_position_in_threadgroup";
-    case ast::Builtin::kLocalInvocationIndex:
-      return "thread_index_in_threadgroup";
-    case ast::Builtin::kGlobalInvocationId:
-      return "thread_position_in_grid";
-    case ast::Builtin::kWorkgroupId:
-      return "threadgroup_position_in_grid";
-    case ast::Builtin::kNumWorkgroups:
-      return "threadgroups_per_grid";
-    case ast::Builtin::kSampleIndex:
-      return "sample_id";
-    case ast::Builtin::kSampleMask:
-      return "sample_mask";
-    case ast::Builtin::kPointSize:
-      return "point_size";
-    default:
-      break;
-  }
-  return "";
-}
-
-std::string GeneratorImpl::interpolation_to_attribute(
-    ast::InterpolationType type,
-    ast::InterpolationSampling sampling) const {
-  std::string attr;
-  switch (sampling) {
-    case ast::InterpolationSampling::kCenter:
-      attr = "center_";
-      break;
-    case ast::InterpolationSampling::kCentroid:
-      attr = "centroid_";
-      break;
-    case ast::InterpolationSampling::kSample:
-      attr = "sample_";
-      break;
-    case ast::InterpolationSampling::kNone:
-      break;
-  }
-  switch (type) {
-    case ast::InterpolationType::kPerspective:
-      attr += "perspective";
-      break;
-    case ast::InterpolationType::kLinear:
-      attr += "no_perspective";
-      break;
-    case ast::InterpolationType::kFlat:
-      attr += "flat";
-      break;
-  }
-  return attr;
-}
-
-bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
-  auto func_name = program_->Symbols().NameFor(func->symbol);
-
-  // Returns the binding index of a variable, requiring that the group
-  // attribute have a value of zero.
-  const uint32_t kInvalidBindingIndex = std::numeric_limits<uint32_t>::max();
-  auto get_binding_index = [&](const ast::Variable* var) -> uint32_t {
-    auto bp = var->BindingPoint();
-    if (bp.group == nullptr || bp.binding == nullptr) {
-      TINT_ICE(Writer, diagnostics_)
-          << "missing binding attributes for entry point parameter";
-      return kInvalidBindingIndex;
-    }
-    if (bp.group->value != 0) {
-      TINT_ICE(Writer, diagnostics_)
-          << "encountered non-zero resource group index (use "
-             "BindingRemapper to fix)";
-      return kInvalidBindingIndex;
-    }
-    return bp.binding->value;
-  };
-
-  {
-    auto out = line();
-
-    EmitStage(out, func->PipelineStage());
-    out << " " << func->return_type->FriendlyName(program_->Symbols());
-    out << " " << func_name << "(";
-
-    // Emit entry point parameters.
-    bool first = true;
-    for (auto* var : func->params) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      auto* type = program_->Sem().Get(var)->Type()->UnwrapRef();
-
-      auto param_name = program_->Symbols().NameFor(var->symbol);
-      if (!EmitType(out, type, param_name)) {
-        return false;
-      }
-      // Parameter name is output as part of the type for arrays and pointers.
-      if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
-        out << " " << param_name;
-      }
-
-      if (type->Is<sem::Struct>()) {
-        out << " [[stage_in]]";
-      } else if (type->is_handle()) {
-        uint32_t binding = get_binding_index(var);
-        if (binding == kInvalidBindingIndex) {
-          return false;
-        }
-        if (var->type->Is<ast::Sampler>()) {
-          out << " [[sampler(" << binding << ")]]";
-        } else if (var->type->Is<ast::Texture>()) {
-          out << " [[texture(" << binding << ")]]";
-        } else {
-          TINT_ICE(Writer, diagnostics_)
-              << "invalid handle type entry point parameter";
-          return false;
-        }
-      } else if (auto* ptr = var->type->As<ast::Pointer>()) {
-        auto sc = ptr->storage_class;
-        if (sc == ast::StorageClass::kWorkgroup) {
-          auto& allocations = workgroup_allocations_[func_name];
-          out << " [[threadgroup(" << allocations.size() << ")]]";
-          allocations.push_back(program_->Sem().Get(ptr->type)->Size());
-        } else if (sc == ast::StorageClass::kStorage ||
-                   sc == ast::StorageClass::kUniform) {
-          uint32_t binding = get_binding_index(var);
-          if (binding == kInvalidBindingIndex) {
-            return false;
-          }
-          out << " [[buffer(" << binding << ")]]";
-        } else {
-          TINT_ICE(Writer, diagnostics_)
-              << "invalid pointer storage class for entry point parameter";
-          return false;
-        }
-      } else {
-        auto& attrs = var->attributes;
-        bool builtin_found = false;
-        for (auto* attr : attrs) {
-          auto* builtin = attr->As<ast::BuiltinAttribute>();
-          if (!builtin) {
-            continue;
-          }
-
-          builtin_found = true;
-
-          auto name = builtin_to_attribute(builtin->builtin);
-          if (name.empty()) {
-            diagnostics_.add_error(diag::System::Writer, "unknown builtin");
-            return false;
-          }
-          out << " [[" << name << "]]";
-        }
-        if (!builtin_found) {
-          TINT_ICE(Writer, diagnostics_) << "Unsupported entry point parameter";
-        }
-      }
-    }
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-
-    if (!EmitStatements(func->body->statements)) {
-      return false;
-    }
-
-    if (!Is<ast::ReturnStatement>(func->body->Last())) {
-      ast::ReturnStatement ret(ProgramID{}, Source{});
-      if (!EmitStatement(&ret)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-  return true;
-}
-
-bool GeneratorImpl::EmitIdentifier(std::ostream& out,
-                                   const ast::IdentifierExpression* expr) {
-  out << program_->Symbols().NameFor(expr->symbol);
-  return true;
-}
-
-bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
-  auto emit_continuing = [this, stmt]() {
-    if (stmt->continuing && !stmt->continuing->Empty()) {
-      if (!EmitBlock(stmt->continuing)) {
-        return false;
-      }
-    }
-    return true;
-  };
-
-  TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-  line() << "while (true) {";
-  {
-    ScopedIndent si(this);
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-    if (!emit_continuing_()) {
-      return false;
-    }
-  }
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
-  TextBuffer init_buf;
-  if (auto* init = stmt->initializer) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
-    if (!EmitStatement(init)) {
-      return false;
-    }
-  }
-
-  TextBuffer cond_pre;
-  std::stringstream cond_buf;
-  if (auto* cond = stmt->condition) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
-    if (!EmitExpression(cond_buf, cond)) {
-      return false;
-    }
-  }
-
-  TextBuffer cont_buf;
-  if (auto* cont = stmt->continuing) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
-    if (!EmitStatement(cont)) {
-      return false;
-    }
-  }
-
-  // If the for-loop has a multi-statement conditional and / or continuing,
-  // then we cannot emit this as a regular for-loop in MSL. Instead we need to
-  // generate a `while(true)` loop.
-  bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
-
-  // If the for-loop has multi-statement initializer, or is going to be
-  // emitted as a `while(true)` loop, then declare the initializer
-  // statement(s) before the loop in a new block.
-  bool nest_in_block =
-      init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop);
-  if (nest_in_block) {
-    line() << "{";
-    increment_indent();
-    current_buffer_->Append(init_buf);
-    init_buf.lines.clear();  // Don't emit the initializer again in the 'for'
-  }
-  TINT_DEFER({
-    if (nest_in_block) {
-      decrement_indent();
-      line() << "}";
-    }
-  });
-
-  if (emit_as_loop) {
-    auto emit_continuing = [&]() {
-      current_buffer_->Append(cont_buf);
-      return true;
-    };
-
-    TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-    line() << "while (true) {";
-    increment_indent();
-    TINT_DEFER({
-      decrement_indent();
-      line() << "}";
-    });
-
-    if (stmt->condition) {
-      current_buffer_->Append(cond_pre);
-      line() << "if (!(" << cond_buf.str() << ")) { break; }";
-    }
-
-    if (!EmitStatements(stmt->body->statements)) {
-      return false;
-    }
-
-    if (!emit_continuing_()) {
-      return false;
-    }
-  } else {
-    // For-loop can be generated.
-    {
-      auto out = line();
-      out << "for";
-      {
-        ScopedParen sp(out);
-
-        if (!init_buf.lines.empty()) {
-          out << init_buf.lines[0].content << " ";
-        } else {
-          out << "; ";
-        }
-
-        out << cond_buf.str() << "; ";
-
-        if (!cont_buf.lines.empty()) {
-          out << TrimSuffix(cont_buf.lines[0].content, ";");
-        }
-      }
-      out << " {";
-    }
-    {
-      auto emit_continuing = [] { return true; };
-      TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-      if (!EmitStatementsWithIndent(stmt->body->statements)) {
-        return false;
-      }
-    }
-    line() << "}";
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
-  // TODO(dsinclair): Verify this is correct when the discard semantics are
-  // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
-  line() << "discard_fragment();";
-  return true;
-}
-
-bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
-  {
-    auto out = line();
-    out << "if (";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      line() << "} else {";
-      increment_indent();
-
-      {
-        auto out = line();
-        out << "if (";
-        if (!EmitExpression(out, e->condition)) {
-          return false;
-        }
-        out << ") {";
-      }
-    } else {
-      line() << "} else {";
-    }
-
-    if (!EmitStatementsWithIndent(e->body->statements)) {
-      return false;
-    }
-  }
-
-  line() << "}";
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      decrement_indent();
-      line() << "}";
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitMemberAccessor(
-    std::ostream& out,
-    const ast::MemberAccessorExpression* expr) {
-  auto write_lhs = [&] {
-    bool paren_lhs = !expr->structure->IsAnyOf<
-        ast::IndexAccessorExpression, ast::CallExpression,
-        ast::IdentifierExpression, ast::MemberAccessorExpression>();
-    if (paren_lhs) {
-      out << "(";
-    }
-    if (!EmitExpression(out, expr->structure)) {
-      return false;
-    }
-    if (paren_lhs) {
-      out << ")";
-    }
-    return true;
-  };
-
-  auto& sem = program_->Sem();
-
-  if (auto* swizzle = sem.Get(expr)->As<sem::Swizzle>()) {
-    // Metal 1.x does not support swizzling of packed vector types.
-    // For single element swizzles, we can use the index operator.
-    // For multi-element swizzles, we need to cast to a regular vector type
-    // first. Note that we do not currently allow assignments to swizzles, so
-    // the casting which will convert the l-value to r-value is fine.
-    if (swizzle->Indices().size() == 1) {
-      if (!write_lhs()) {
-        return false;
-      }
-      out << "[" << swizzle->Indices()[0] << "]";
-    } else {
-      if (!EmitType(out, sem.Get(expr->structure)->Type()->UnwrapRef(), "")) {
-        return false;
-      }
-      out << "(";
-      if (!write_lhs()) {
-        return false;
-      }
-      out << ")." << program_->Symbols().NameFor(expr->member->symbol);
-    }
-  } else {
-    if (!write_lhs()) {
-      return false;
-    }
-    out << ".";
-    if (!EmitExpression(out, expr->member)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
-  auto out = line();
-  out << "return";
-  if (stmt->value) {
-    out << " ";
-    if (!EmitExpression(out, stmt->value)) {
-      return false;
-    }
-  }
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
-  line() << "{";
-
-  if (!EmitStatementsWithIndent(stmt->statements)) {
-    return false;
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
-  return Switch(
-      stmt,
-      [&](const ast::AssignmentStatement* a) {  //
-        return EmitAssign(a);
-      },
-      [&](const ast::BlockStatement* b) {  //
-        return EmitBlock(b);
-      },
-      [&](const ast::BreakStatement* b) {  //
-        return EmitBreak(b);
-      },
-      [&](const ast::CallStatement* c) {  //
-        auto out = line();
-        if (!EmitCall(out, c->expr)) {  //
-          return false;
-        }
-        out << ";";
-        return true;
-      },
-      [&](const ast::ContinueStatement* c) {  //
-        return EmitContinue(c);
-      },
-      [&](const ast::DiscardStatement* d) {  //
-        return EmitDiscard(d);
-      },
-      [&](const ast::FallthroughStatement*) {  //
-        line() << "/* fallthrough */";
-        return true;
-      },
-      [&](const ast::IfStatement* i) {  //
-        return EmitIf(i);
-      },
-      [&](const ast::LoopStatement* l) {  //
-        return EmitLoop(l);
-      },
-      [&](const ast::ForLoopStatement* l) {  //
-        return EmitForLoop(l);
-      },
-      [&](const ast::ReturnStatement* r) {  //
-        return EmitReturn(r);
-      },
-      [&](const ast::SwitchStatement* s) {  //
-        return EmitSwitch(s);
-      },
-      [&](const ast::VariableDeclStatement* v) {  //
-        auto* var = program_->Sem().Get(v->variable);
-        return EmitVariable(var);
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown statement type: " + std::string(stmt->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
-  for (auto* s : stmts) {
-    if (!EmitStatement(s)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
-  ScopedIndent si(this);
-  return EmitStatements(stmts);
-}
-
-bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
-  {
-    auto out = line();
-    out << "switch(";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-    for (auto* s : stmt->body) {
-      if (!EmitCase(s)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
-                             const std::string& name,
-                             bool* name_printed /* = nullptr */) {
-  if (name_printed) {
-    *name_printed = false;
-  }
-
-  return Switch(
-      type,
-      [&](const sem::Atomic* atomic) {
-        if (atomic->Type()->Is<sem::I32>()) {
-          out << "atomic_int";
-          return true;
-        }
-        if (atomic->Type()->Is<sem::U32>()) {
-          out << "atomic_uint";
-          return true;
-        }
-        TINT_ICE(Writer, diagnostics_)
-            << "unhandled atomic type " << atomic->Type()->type_name();
-        return false;
-      },
-      [&](const sem::Array* ary) {
-        const sem::Type* base_type = ary;
-        std::vector<uint32_t> sizes;
-        while (auto* arr = base_type->As<sem::Array>()) {
-          if (arr->IsRuntimeSized()) {
-            sizes.push_back(1);
-          } else {
-            sizes.push_back(arr->Count());
-          }
-          base_type = arr->ElemType();
-        }
-        if (!EmitType(out, base_type, "")) {
-          return false;
-        }
-        if (!name.empty()) {
-          out << " " << name;
-          if (name_printed) {
-            *name_printed = true;
-          }
-        }
-        for (uint32_t size : sizes) {
-          out << "[" << size << "]";
-        }
-        return true;
-      },
-      [&](const sem::Bool*) {
-        out << "bool";
-        return true;
-      },
-      [&](const sem::F32*) {
-        out << "float";
-        return true;
-      },
-      [&](const sem::I32*) {
-        out << "int";
-        return true;
-      },
-      [&](const sem::Matrix* mat) {
-        if (!EmitType(out, mat->type(), "")) {
-          return false;
-        }
-        out << mat->columns() << "x" << mat->rows();
-        return true;
-      },
-      [&](const sem::Pointer* ptr) {
-        if (ptr->Access() == ast::Access::kRead) {
-          out << "const ";
-        }
-        if (!EmitStorageClass(out, ptr->StorageClass())) {
-          return false;
-        }
-        out << " ";
-        if (ptr->StoreType()->Is<sem::Array>()) {
-          std::string inner = "(*" + name + ")";
-          if (!EmitType(out, ptr->StoreType(), inner)) {
-            return false;
-          }
-          if (name_printed) {
-            *name_printed = true;
-          }
-        } else {
-          if (!EmitType(out, ptr->StoreType(), "")) {
-            return false;
-          }
-          out << "* " << name;
-          if (name_printed) {
-            *name_printed = true;
-          }
-        }
-        return true;
-      },
-      [&](const sem::Sampler*) {
-        out << "sampler";
-        return true;
-      },
-      [&](const sem::Struct* str) {
-        // The struct type emits as just the name. The declaration would be
-        // emitted as part of emitting the declared types.
-        out << StructName(str);
-        return true;
-      },
-      [&](const sem::Texture* tex) {
-        if (tex->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
-          out << "depth";
-        } else {
-          out << "texture";
-        }
-
-        switch (tex->dim()) {
-          case ast::TextureDimension::k1d:
-            out << "1d";
-            break;
-          case ast::TextureDimension::k2d:
-            out << "2d";
-            break;
-          case ast::TextureDimension::k2dArray:
-            out << "2d_array";
-            break;
-          case ast::TextureDimension::k3d:
-            out << "3d";
-            break;
-          case ast::TextureDimension::kCube:
-            out << "cube";
-            break;
-          case ast::TextureDimension::kCubeArray:
-            out << "cube_array";
-            break;
-          default:
-            diagnostics_.add_error(diag::System::Writer,
-                                   "Invalid texture dimensions");
-            return false;
-        }
-        if (tex->IsAnyOf<sem::MultisampledTexture,
-                         sem::DepthMultisampledTexture>()) {
-          out << "_ms";
-        }
-        out << "<";
-        TINT_DEFER(out << ">");
-
-        return Switch(
-            tex,
-            [&](const sem::DepthTexture*) {
-              out << "float, access::sample";
-              return true;
-            },
-            [&](const sem::DepthMultisampledTexture*) {
-              out << "float, access::read";
-              return true;
-            },
-            [&](const sem::StorageTexture* storage) {
-              if (!EmitType(out, storage->type(), "")) {
-                return false;
-              }
-
-              std::string access_str;
-              if (storage->access() == ast::Access::kRead) {
-                out << ", access::read";
-              } else if (storage->access() == ast::Access::kWrite) {
-                out << ", access::write";
-              } else {
-                diagnostics_.add_error(
-                    diag::System::Writer,
-                    "Invalid access control for storage texture");
-                return false;
-              }
-              return true;
-            },
-            [&](const sem::MultisampledTexture* ms) {
-              if (!EmitType(out, ms->type(), "")) {
-                return false;
-              }
-              out << ", access::read";
-              return true;
-            },
-            [&](const sem::SampledTexture* sampled) {
-              if (!EmitType(out, sampled->type(), "")) {
-                return false;
-              }
-              out << ", access::sample";
-              return true;
-            },
-            [&](Default) {
-              diagnostics_.add_error(diag::System::Writer,
-                                     "invalid texture type");
-              return false;
-            });
-      },
-      [&](const sem::U32*) {
-        out << "uint";
-        return true;
-      },
-      [&](const sem::Vector* vec) {
-        if (!EmitType(out, vec->type(), "")) {
-          return false;
-        }
-        out << vec->Width();
-        return true;
-      },
-      [&](const sem::Void*) {
-        out << "void";
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown type in EmitType: " + type->type_name());
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
-                                    const std::string& name) {
-  bool name_printed = false;
-  if (!EmitType(out, type, name, &name_printed)) {
-    return false;
-  }
-  if (!name_printed) {
-    out << " " << name;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStorageClass(std::ostream& out, ast::StorageClass sc) {
-  switch (sc) {
-    case ast::StorageClass::kFunction:
-    case ast::StorageClass::kPrivate:
-    case ast::StorageClass::kUniformConstant:
-      out << "thread";
-      return true;
-    case ast::StorageClass::kWorkgroup:
-      out << "threadgroup";
-      return true;
-    case ast::StorageClass::kStorage:
-      out << "device";
-      return true;
-    case ast::StorageClass::kUniform:
-      out << "constant";
-      return true;
-    default:
-      break;
-  }
-  TINT_ICE(Writer, diagnostics_) << "unhandled storage class: " << sc;
-  return false;
-}
-
-bool GeneratorImpl::EmitPackedType(std::ostream& out,
-                                   const sem::Type* type,
-                                   const std::string& name) {
-  auto* vec = type->As<sem::Vector>();
-  if (vec && vec->Width() == 3) {
-    out << "packed_";
-    if (!EmitType(out, vec, "")) {
-      return false;
-    }
-
-    if (vec->is_float_vector() && !matrix_packed_vector_overloads_) {
-      // Overload operators for matrix-vector arithmetic where the vector
-      // operand is packed, as these overloads to not exist in the metal
-      // namespace.
-      TextBuffer b;
-      TINT_DEFER(helpers_.Append(b));
-      line(&b) << R"(template<typename T, int N, int M>
-inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
-  return lhs * vec<T, N>(rhs);
-}
-
-template<typename T, int N, int M>
-inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
-  return vec<T, M>(lhs) * rhs;
-}
-)";
-      matrix_packed_vector_overloads_ = true;
-    }
-
-    return true;
-  }
-
-  return EmitType(out, type, name);
-}
-
-bool GeneratorImpl::EmitStructType(TextBuffer* b, const sem::Struct* str) {
-  line(b) << "struct " << StructName(str) << " {";
-
-  bool is_host_shareable = str->IsHostShareable();
-
-  // Emits a `/* 0xnnnn */` byte offset comment for a struct member.
-  auto add_byte_offset_comment = [&](std::ostream& out, uint32_t offset) {
-    std::ios_base::fmtflags saved_flag_state(out.flags());
-    out << "/* 0x" << std::hex << std::setfill('0') << std::setw(4) << offset
-        << " */ ";
-    out.flags(saved_flag_state);
-  };
-
-  auto add_padding = [&](uint32_t size, uint32_t msl_offset) {
-    std::string name;
-    do {
-      name = UniqueIdentifier("tint_pad");
-    } while (str->FindMember(program_->Symbols().Get(name)));
-
-    auto out = line(b);
-    add_byte_offset_comment(out, msl_offset);
-    out << "int8_t " << name << "[" << size << "];";
-  };
-
-  b->IncrementIndent();
-
-  uint32_t msl_offset = 0;
-  for (auto* mem : str->Members()) {
-    auto out = line(b);
-    auto mem_name = program_->Symbols().NameFor(mem->Name());
-    auto wgsl_offset = mem->Offset();
-
-    if (is_host_shareable) {
-      if (wgsl_offset < msl_offset) {
-        // Unimplementable layout
-        TINT_ICE(Writer, diagnostics_)
-            << "Structure member WGSL offset (" << wgsl_offset
-            << ") is behind MSL offset (" << msl_offset << ")";
-        return false;
-      }
-
-      // Generate padding if required
-      if (auto padding = wgsl_offset - msl_offset) {
-        add_padding(padding, msl_offset);
-        msl_offset += padding;
-      }
-
-      add_byte_offset_comment(out, msl_offset);
-
-      if (!EmitPackedType(out, mem->Type(), mem_name)) {
-        return false;
-      }
-    } else {
-      if (!EmitType(out, mem->Type(), mem_name)) {
-        return false;
-      }
-    }
-
-    auto* ty = mem->Type();
-
-    // Array member name will be output with the type
-    if (!ty->Is<sem::Array>()) {
-      out << " " << mem_name;
-    }
-
-    // Emit attributes
-    if (auto* decl = mem->Declaration()) {
-      for (auto* attr : decl->attributes) {
-        bool ok = Switch(
-            attr,
-            [&](const ast::BuiltinAttribute* builtin) {
-              auto name = builtin_to_attribute(builtin->builtin);
-              if (name.empty()) {
-                diagnostics_.add_error(diag::System::Writer, "unknown builtin");
-                return false;
-              }
-              out << " [[" << name << "]]";
-              return true;
-            },
-            [&](const ast::LocationAttribute* loc) {
-              auto& pipeline_stage_uses = str->PipelineStageUses();
-              if (pipeline_stage_uses.size() != 1) {
-                TINT_ICE(Writer, diagnostics_)
-                    << "invalid entry point IO struct uses";
-                return false;
-              }
-
-              if (pipeline_stage_uses.count(
-                      sem::PipelineStageUsage::kVertexInput)) {
-                out << " [[attribute(" + std::to_string(loc->value) + ")]]";
-              } else if (pipeline_stage_uses.count(
-                             sem::PipelineStageUsage::kVertexOutput)) {
-                out << " [[user(locn" + std::to_string(loc->value) + ")]]";
-              } else if (pipeline_stage_uses.count(
-                             sem::PipelineStageUsage::kFragmentInput)) {
-                out << " [[user(locn" + std::to_string(loc->value) + ")]]";
-              } else if (pipeline_stage_uses.count(
-                             sem::PipelineStageUsage::kFragmentOutput)) {
-                out << " [[color(" + std::to_string(loc->value) + ")]]";
-              } else {
-                TINT_ICE(Writer, diagnostics_)
-                    << "invalid use of location decoration";
-                return false;
-              }
-              return true;
-            },
-            [&](const ast::InterpolateAttribute* interpolate) {
-              auto name = interpolation_to_attribute(interpolate->type,
-                                                     interpolate->sampling);
-              if (name.empty()) {
-                diagnostics_.add_error(diag::System::Writer,
-                                       "unknown interpolation attribute");
-                return false;
-              }
-              out << " [[" << name << "]]";
-              return true;
-            },
-            [&](const ast::InvariantAttribute*) {
-              if (invariant_define_name_.empty()) {
-                invariant_define_name_ = UniqueIdentifier("TINT_INVARIANT");
-              }
-              out << " " << invariant_define_name_;
-              return true;
-            },
-            [&](const ast::StructMemberOffsetAttribute*) { return true; },
-            [&](const ast::StructMemberAlignAttribute*) { return true; },
-            [&](const ast::StructMemberSizeAttribute*) { return true; },
-            [&](Default) {
-              TINT_ICE(Writer, diagnostics_)
-                  << "unhandled struct member attribute: " << attr->Name();
-              return false;
-            });
-        if (!ok) {
-          return false;
-        }
-      }
-    }
-
-    out << ";";
-
-    if (is_host_shareable) {
-      // Calculate new MSL offset
-      auto size_align = MslPackedTypeSizeAndAlign(ty);
-      if (msl_offset % size_align.align) {
-        TINT_ICE(Writer, diagnostics_)
-            << "Misaligned MSL structure member "
-            << ty->FriendlyName(program_->Symbols()) << " " << mem_name;
-        return false;
-      }
-      msl_offset += size_align.size;
-    }
-  }
-
-  if (is_host_shareable && str->Size() != msl_offset) {
-    add_padding(str->Size() - msl_offset, msl_offset);
-  }
-
-  b->DecrementIndent();
-
-  line(b) << "};";
-  return true;
-}
-
-bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
-                                const ast::UnaryOpExpression* expr) {
-  // Handle `-e` when `e` is signed, so that we ensure that if `e` is the
-  // largest negative value, it returns `e`.
-  auto* expr_type = TypeOf(expr->expr)->UnwrapRef();
-  if (expr->op == ast::UnaryOp::kNegation &&
-      expr_type->is_signed_scalar_or_vector()) {
-    auto fn =
-        utils::GetOrCreate(unary_minus_funcs_, expr_type, [&]() -> std::string {
-          // e.g.:
-          // int tint_unary_minus(const int v) {
-          //     return (v == -2147483648) ? v : -v;
-          // }
-          TextBuffer b;
-          TINT_DEFER(helpers_.Append(b));
-
-          auto fn_name = UniqueIdentifier("tint_unary_minus");
-          {
-            auto decl = line(&b);
-            if (!EmitTypeAndName(decl, expr_type, fn_name)) {
-              return "";
-            }
-            decl << "(const ";
-            if (!EmitType(decl, expr_type, "")) {
-              return "";
-            }
-            decl << " v) {";
-          }
-
-          {
-            ScopedIndent si(&b);
-            const auto largest_negative_value =
-                std::to_string(std::numeric_limits<int32_t>::min());
-            line(&b) << "return select(-v, v, v == " << largest_negative_value
-                     << ");";
-          }
-          line(&b) << "}";
-          line(&b);
-          return fn_name;
-        });
-
-    out << fn << "(";
-    if (!EmitExpression(out, expr->expr)) {
-      return false;
-    }
-    out << ")";
-    return true;
-  }
-
-  switch (expr->op) {
-    case ast::UnaryOp::kAddressOf:
-      out << "&";
-      break;
-    case ast::UnaryOp::kComplement:
-      out << "~";
-      break;
-    case ast::UnaryOp::kIndirection:
-      out << "*";
-      break;
-    case ast::UnaryOp::kNot:
-      out << "!";
-      break;
-    case ast::UnaryOp::kNegation:
-      out << "-";
-      break;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitVariable(const sem::Variable* var) {
-  auto* decl = var->Declaration();
-
-  for (auto* attr : decl->attributes) {
-    if (!attr->Is<ast::InternalAttribute>()) {
-      TINT_ICE(Writer, diagnostics_) << "unexpected variable attribute";
-      return false;
-    }
-  }
-
-  auto out = line();
-
-  switch (var->StorageClass()) {
-    case ast::StorageClass::kFunction:
-    case ast::StorageClass::kUniformConstant:
-    case ast::StorageClass::kNone:
-      break;
-    case ast::StorageClass::kPrivate:
-      out << "thread ";
-      break;
-    case ast::StorageClass::kWorkgroup:
-      out << "threadgroup ";
-      break;
-    default:
-      TINT_ICE(Writer, diagnostics_) << "unhandled variable storage class";
-      return false;
-  }
-
-  auto* type = var->Type()->UnwrapRef();
-
-  std::string name = program_->Symbols().NameFor(decl->symbol);
-  if (decl->is_const) {
-    name = "const " + name;
-  }
-  if (!EmitType(out, type, name)) {
-    return false;
-  }
-  // Variable name is output as part of the type for arrays and pointers.
-  if (!type->Is<sem::Array>() && !type->Is<sem::Pointer>()) {
-    out << " " << name;
-  }
-
-  if (decl->constructor != nullptr) {
-    out << " = ";
-    if (!EmitExpression(out, decl->constructor)) {
-      return false;
-    }
-  } else if (var->StorageClass() == ast::StorageClass::kPrivate ||
-             var->StorageClass() == ast::StorageClass::kFunction ||
-             var->StorageClass() == ast::StorageClass::kNone) {
-    out << " = ";
-    if (!EmitZeroValue(out, type)) {
-      return false;
-    }
-  }
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
-  for (auto* d : var->attributes) {
-    if (!d->Is<ast::IdAttribute>()) {
-      diagnostics_.add_error(diag::System::Writer,
-                             "Decorated const values not valid");
-      return false;
-    }
-  }
-  if (!var->is_const) {
-    diagnostics_.add_error(diag::System::Writer, "Expected a const value");
-    return false;
-  }
-
-  auto out = line();
-  out << "constant ";
-  auto* type = program_->Sem().Get(var)->Type()->UnwrapRef();
-  if (!EmitType(out, type, program_->Symbols().NameFor(var->symbol))) {
-    return false;
-  }
-  if (!type->Is<sem::Array>()) {
-    out << " " << program_->Symbols().NameFor(var->symbol);
-  }
-
-  auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
-  if (global && global->IsOverridable()) {
-    out << " [[function_constant(" << global->ConstantId() << ")]]";
-  } else if (var->constructor != nullptr) {
-    out << " = ";
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-  }
-  out << ";";
-
-  return true;
-}
-
-GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(
-    const sem::Type* ty) {
-  return Switch(
-      ty,
-
-      // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-      // 2.1 Scalar Data Types
-      [&](const sem::U32*) {
-        return SizeAndAlign{4, 4};
-      },
-      [&](const sem::I32*) {
-        return SizeAndAlign{4, 4};
-      },
-      [&](const sem::F32*) {
-        return SizeAndAlign{4, 4};
-      },
-
-      [&](const sem::Vector* vec) {
-        auto num_els = vec->Width();
-        auto* el_ty = vec->type();
-        if (el_ty->IsAnyOf<sem::U32, sem::I32, sem::F32>()) {
-          // Use a packed_vec type for 3-element vectors only.
-          if (num_els == 3) {
-            // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-            // 2.2.3 Packed Vector Types
-            return SizeAndAlign{num_els * 4, 4};
-          } else {
-            // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-            // 2.2 Vector Data Types
-            return SizeAndAlign{num_els * 4, num_els * 4};
-          }
-        }
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "Unhandled vector element type " << el_ty->TypeInfo().name;
-        return SizeAndAlign{};
-      },
-
-      [&](const sem::Matrix* mat) {
-        // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-        // 2.3 Matrix Data Types
-        auto cols = mat->columns();
-        auto rows = mat->rows();
-        auto* el_ty = mat->type();
-        if (el_ty->IsAnyOf<sem::U32, sem::I32, sem::F32>()) {
-          static constexpr SizeAndAlign table[] = {
-              /* float2x2 */ {16, 8},
-              /* float2x3 */ {32, 16},
-              /* float2x4 */ {32, 16},
-              /* float3x2 */ {24, 8},
-              /* float3x3 */ {48, 16},
-              /* float3x4 */ {48, 16},
-              /* float4x2 */ {32, 8},
-              /* float4x3 */ {64, 16},
-              /* float4x4 */ {64, 16},
-          };
-          if (cols >= 2 && cols <= 4 && rows >= 2 && rows <= 4) {
-            return table[(3 * (cols - 2)) + (rows - 2)];
-          }
-        }
-
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "Unhandled matrix element type " << el_ty->TypeInfo().name;
-        return SizeAndAlign{};
-      },
-
-      [&](const sem::Array* arr) {
-        if (!arr->IsStrideImplicit()) {
-          TINT_ICE(Writer, diagnostics_)
-              << "arrays with explicit strides should have "
-                 "removed with the PadArrayElements transform";
-          return SizeAndAlign{};
-        }
-        auto num_els = std::max<uint32_t>(arr->Count(), 1);
-        return SizeAndAlign{arr->Stride() * num_els, arr->Align()};
-      },
-
-      [&](const sem::Struct* str) {
-        // TODO(crbug.com/tint/650): There's an assumption here that MSL's
-        // default structure size and alignment matches WGSL's. We need to
-        // confirm this.
-        return SizeAndAlign{str->Size(), str->Align()};
-      },
-
-      [&](const sem::Atomic* atomic) {
-        return MslPackedTypeSizeAndAlign(atomic->Type());
-      },
-
-      [&](Default) {
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "Unhandled type " << ty->TypeInfo().name;
-        return SizeAndAlign{};
-      });
-}
-
-template <typename F>
-bool GeneratorImpl::CallBuiltinHelper(std::ostream& out,
-                                      const ast::CallExpression* call,
-                                      const sem::Builtin* builtin,
-                                      F&& build) {
-  // Generate the helper function if it hasn't been created already
-  auto fn = utils::GetOrCreate(builtins_, builtin, [&]() -> std::string {
-    TextBuffer b;
-    TINT_DEFER(helpers_.Append(b));
-
-    auto fn_name =
-        UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
-    std::vector<std::string> parameter_names;
-    {
-      auto decl = line(&b);
-      if (!EmitTypeAndName(decl, builtin->ReturnType(), fn_name)) {
-        return "";
-      }
-      {
-        ScopedParen sp(decl);
-        for (auto* param : builtin->Parameters()) {
-          if (!parameter_names.empty()) {
-            decl << ", ";
-          }
-          auto param_name = "param_" + std::to_string(parameter_names.size());
-          if (!EmitTypeAndName(decl, param->Type(), param_name)) {
-            return "";
-          }
-          parameter_names.emplace_back(std::move(param_name));
-        }
-      }
-      decl << " {";
-    }
-    {
-      ScopedIndent si(&b);
-      if (!build(&b, parameter_names)) {
-        return "";
-      }
-    }
-    line(&b) << "}";
-    line(&b);
-    return fn_name;
-  });
-
-  if (fn.empty()) {
-    return false;
-  }
-
-  // Call the helper
-  out << fn;
-  {
-    ScopedParen sp(out);
-    bool first = true;
-    for (auto* arg : call->args) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-      if (!EmitExpression(out, arg)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
deleted file mode 100644
index 2d0a2bb..0000000
--- a/src/writer/msl/generator_impl.h
+++ /dev/null
@@ -1,450 +0,0 @@
-// Copyright 2020 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_WRITER_MSL_GENERATOR_IMPL_H_
-#define SRC_WRITER_MSL_GENERATOR_IMPL_H_
-
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/expression.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/index_accessor_expression.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/program.h"
-#include "src/scope_stack.h"
-#include "src/sem/struct.h"
-#include "src/writer/array_length_from_uniform_options.h"
-#include "src/writer/text_generator.h"
-
-namespace tint {
-
-// Forward declarations
-namespace sem {
-class Call;
-class Builtin;
-class TypeConstructor;
-class TypeConversion;
-}  // namespace sem
-
-namespace writer {
-namespace msl {
-
-/// The result of sanitizing a program for generation.
-struct SanitizedResult {
-  /// Constructor
-  SanitizedResult();
-  /// Destructor
-  ~SanitizedResult();
-  /// Move constructor
-  SanitizedResult(SanitizedResult&&);
-
-  /// The sanitized program.
-  Program program;
-  /// True if the shader needs a UBO of buffer sizes.
-  bool needs_storage_buffer_sizes = false;
-  /// Indices into the array_length_from_uniform binding that are statically
-  /// used.
-  std::unordered_set<uint32_t> used_array_length_from_uniform_indices;
-};
-
-/// Sanitize a program in preparation for generating MSL.
-/// @param buffer_size_ubo_index the index to use for the buffer size UBO
-/// @param fixed_sample_mask the fixed sample mask to use for fragment shaders
-/// @param emit_vertex_point_size `true` to emit a vertex point size builtin
-/// @param disable_workgroup_init `true` to disable workgroup memory zero
-/// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(
-    const Program* program,
-    uint32_t buffer_size_ubo_index,
-    uint32_t fixed_sample_mask = 0xFFFFFFFF,
-    bool emit_vertex_point_size = false,
-    bool disable_workgroup_init = false,
-    const ArrayLengthFromUniformOptions& array_length_from_uniform = {});
-
-/// Implementation class for MSL generator
-class GeneratorImpl : public TextGenerator {
- public:
-  /// Constructor
-  /// @param program the program to generate
-  explicit GeneratorImpl(const Program* program);
-  ~GeneratorImpl();
-
-  /// @returns true on successful generation; false otherwise
-  bool Generate();
-
-  /// @returns true if an invariant attribute was generated
-  bool HasInvariant() { return !invariant_define_name_.empty(); }
-
-  /// @returns a map from entry point to list of required workgroup allocations
-  const std::unordered_map<std::string, std::vector<uint32_t>>&
-  DynamicWorkgroupAllocations() const {
-    return workgroup_allocations_;
-  }
-
-  /// Handles generating a declared type
-  /// @param ty the declared type to generate
-  /// @returns true if the declared type was emitted
-  bool EmitTypeDecl(const sem::Type* ty);
-  /// Handles an index accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the index accessor was emitted
-  bool EmitIndexAccessor(std::ostream& out,
-                         const ast::IndexAccessorExpression* expr);
-  /// Handles an assignment statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitAssign(const ast::AssignmentStatement* stmt);
-  /// Handles generating a binary expression
-  /// @param out the output of the expression stream
-  /// @param expr the binary expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
-  /// Handles generating a bitcast expression
-  /// @param out the output of the expression stream
-  /// @param expr the bitcast expression
-  /// @returns true if the bitcast was emitted
-  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
-  /// Handles a block statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBlock(const ast::BlockStatement* stmt);
-  /// Handles a break statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBreak(const ast::BreakStatement* stmt);
-  /// Handles generating a call expression
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles generating a builtin call expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the builtin being called
-  /// @returns true if the call expression is emitted
-  bool EmitBuiltinCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a type conversion expression
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param conv the type conversion
-  /// @returns true if the expression is emitted
-  bool EmitTypeConversion(std::ostream& out,
-                          const sem::Call* call,
-                          const sem::TypeConversion* conv);
-  /// Handles generating a type constructor
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param ctor the type constructor
-  /// @returns true if the constructor is emitted
-  bool EmitTypeConstructor(std::ostream& out,
-                           const sem::Call* call,
-                           const sem::TypeConstructor* ctor);
-  /// Handles generating a function call
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param func the target function
-  /// @returns true if the call is emitted
-  bool EmitFunctionCall(std::ostream& out,
-                        const sem::Call* call,
-                        const sem::Function* func);
-  /// Handles generating a call to an atomic function (`atomicAdd`,
-  /// `atomicMax`, etc)
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the atomic builtin
-  /// @returns true if the call expression is emitted
-  bool EmitAtomicCall(std::ostream& out,
-                      const ast::CallExpression* expr,
-                      const sem::Builtin* builtin);
-  /// Handles generating a call to a texture function (`textureSample`,
-  /// `textureSampleGrad`, etc)
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @returns true if the call expression is emitted
-  bool EmitTextureCall(std::ostream& out,
-                       const sem::Call* call,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `dot()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDotCall(std::ostream& out,
-                   const ast::CallExpression* expr,
-                   const sem::Builtin* builtin);
-  /// Handles generating a call to the `modf()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitModfCall(std::ostream& out,
-                    const ast::CallExpression* expr,
-                    const sem::Builtin* builtin);
-  /// Handles generating a call to the `frexp()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitFrexpCall(std::ostream& out,
-                     const ast::CallExpression* expr,
-                     const sem::Builtin* builtin);
-  /// Handles generating a call to the `degrees()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitDegreesCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles generating a call to the `radians()` builtin
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @returns true if the call expression is emitted
-  bool EmitRadiansCall(std::ostream& out,
-                       const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
-  /// Handles a case statement
-  /// @param stmt the statement
-  /// @returns true if the statement was emitted successfully
-  bool EmitCase(const ast::CaseStatement* stmt);
-  /// Handles a continue statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitContinue(const ast::ContinueStatement* stmt);
-  /// Handles generating a discard statement
-  /// @param stmt the discard statement
-  /// @returns true if the statement was successfully emitted
-  bool EmitDiscard(const ast::DiscardStatement* stmt);
-  /// Handles emitting the entry point function
-  /// @param func the entry point function
-  /// @returns true if the entry point function was emitted
-  bool EmitEntryPointFunction(const ast::Function* func);
-  /// Handles generate an Expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the expression was emitted
-  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
-  /// Handles generating a function
-  /// @param func the function to generate
-  /// @returns true if the function was emitted
-  bool EmitFunction(const ast::Function* func);
-  /// Handles generating an identifier expression
-  /// @param out the output of the expression stream
-  /// @param expr the identifier expression
-  /// @returns true if the identifier was emitted
-  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
-  /// Handles an if statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitIf(const ast::IfStatement* stmt);
-  /// Handles a literal
-  /// @param out the output of the expression stream
-  /// @param lit the literal to emit
-  /// @returns true if the literal was successfully emitted
-  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit);
-  /// Handles a loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitLoop(const ast::LoopStatement* stmt);
-  /// Handles a for loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitForLoop(const ast::ForLoopStatement* stmt);
-  /// Handles a member accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the member accessor expression
-  /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(std::ostream& out,
-                          const ast::MemberAccessorExpression* expr);
-  /// Handles return statements
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitReturn(const ast::ReturnStatement* stmt);
-  /// Handles emitting a pipeline stage name
-  /// @param out the output of the expression stream
-  /// @param stage the stage to emit
-  void EmitStage(std::ostream& out, ast::PipelineStage stage);
-  /// Handles statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitStatement(const ast::Statement* stmt);
-  /// Emits a list of statements
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatements(const ast::StatementList& stmts);
-  /// Emits a list of statements with an indentation
-  /// @param stmts the statement list
-  /// @returns true if the statements were emitted successfully
-  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
-  /// Handles generating a switch statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitSwitch(const ast::SwitchStatement* stmt);
-  /// Handles generating a type
-  /// @param out the output of the type stream
-  /// @param type the type to generate
-  /// @param name the name of the variable, only used for array emission
-  /// @param name_printed (optional) if not nullptr and an array was printed
-  /// @returns true if the type is emitted
-  bool EmitType(std::ostream& out,
-                const sem::Type* type,
-                const std::string& name,
-                bool* name_printed = nullptr);
-  /// Handles generating type and name
-  /// @param out the output stream
-  /// @param type the type to generate
-  /// @param name the name to emit
-  /// @returns true if the type is emitted
-  bool EmitTypeAndName(std::ostream& out,
-                       const sem::Type* type,
-                       const std::string& name);
-  /// Handles generating a storage class
-  /// @param out the output of the type stream
-  /// @param sc the storage class to generate
-  /// @returns true if the storage class is emitted
-  bool EmitStorageClass(std::ostream& out, ast::StorageClass sc);
-  /// Handles generating an MSL-packed storage type.
-  /// If the type does not have a packed form, the standard non-packed form is
-  /// emitted.
-  /// @param out the output of the type stream
-  /// @param type the type to generate
-  /// @param name the name of the variable, only used for array emission
-  /// @returns true if the type is emitted
-  bool EmitPackedType(std::ostream& out,
-                      const sem::Type* type,
-                      const std::string& name);
-  /// Handles generating a struct declaration
-  /// @param buffer the text buffer that the type declaration will be written to
-  /// @param str the struct to generate
-  /// @returns true if the struct is emitted
-  bool EmitStructType(TextBuffer* buffer, const sem::Struct* str);
-  /// Handles a unary op expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the expression was emitted
-  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
-  /// Handles generating a variable
-  /// @param var the variable to generate
-  /// @returns true if the variable was emitted
-  bool EmitVariable(const sem::Variable* var);
-  /// Handles generating a program scope constant variable
-  /// @param var the variable to emit
-  /// @returns true if the variable was emitted
-  bool EmitProgramConstVariable(const ast::Variable* var);
-  /// Emits the zero value for the given type
-  /// @param out the output of the expression stream
-  /// @param type the type to emit the value for
-  /// @returns true if the zero value was successfully emitted.
-  bool EmitZeroValue(std::ostream& out, const sem::Type* type);
-
-  /// Handles generating a builtin name
-  /// @param builtin the semantic info for the builtin
-  /// @returns the name or "" if not valid
-  std::string generate_builtin_name(const sem::Builtin* builtin);
-
-  /// Converts a builtin to an attribute name
-  /// @param builtin the builtin to convert
-  /// @returns the string name of the builtin or blank on error
-  std::string builtin_to_attribute(ast::Builtin builtin) const;
-
-  /// Converts interpolation attributes to an MSL attribute
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  /// @returns the string name of the attribute or blank on error
-  std::string interpolation_to_attribute(
-      ast::InterpolationType type,
-      ast::InterpolationSampling sampling) const;
-
- private:
-  // A pair of byte size and alignment `uint32_t`s.
-  struct SizeAndAlign {
-    uint32_t size;
-    uint32_t align;
-  };
-
-  /// CallBuiltinHelper will call the builtin helper function, creating it
-  /// if it hasn't been built already. If the builtin needs to be built then
-  /// CallBuiltinHelper will generate the function signature and will call
-  /// `build` to emit the body of the function.
-  /// @param out the output of the expression stream
-  /// @param call the call expression
-  /// @param builtin the semantic information for the builtin
-  /// @param build a function with the signature:
-  ///        `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
-  ///        Where:
-  ///          `buffer` is the body of the generated function
-  ///          `params` is the name of all the generated function parameters
-  /// @returns true if the call expression is emitted
-  template <typename F>
-  bool CallBuiltinHelper(std::ostream& out,
-                         const ast::CallExpression* call,
-                         const sem::Builtin* builtin,
-                         F&& build);
-
-  TextBuffer helpers_;  // Helper functions emitted at the top of the output
-
-  /// @returns the MSL packed type size and alignment in bytes for the given
-  /// type.
-  SizeAndAlign MslPackedTypeSizeAndAlign(const sem::Type* ty);
-
-  using StorageClassToString =
-      std::unordered_map<ast::StorageClass, std::string>;
-
-  std::function<bool()> emit_continuing_;
-
-  /// Name of atomicCompareExchangeWeak() helper for the given pointer storage
-  /// class.
-  StorageClassToString atomicCompareExchangeWeak_;
-
-  /// Unique name of the 'TINT_INVARIANT' preprocessor define. Non-empty only if
-  /// an invariant attribute has been generated.
-  std::string invariant_define_name_;
-
-  /// True if matrix-packed_vector operator overloads have been generated.
-  bool matrix_packed_vector_overloads_ = false;
-
-  /// A map from entry point name to a list of dynamic workgroup allocations.
-  /// Each entry in the vector is the size of the workgroup allocation that
-  /// should be created for that index.
-  std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations_;
-
-  std::unordered_map<const sem::Builtin*, std::string> builtins_;
-  std::unordered_map<const sem::Type*, std::string> unary_minus_funcs_;
-  std::unordered_map<uint32_t, std::string> int_dot_funcs_;
-};
-
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_MSL_GENERATOR_IMPL_H_
diff --git a/src/writer/msl/generator_impl_array_accessor_test.cc b/src/writer/msl/generator_impl_array_accessor_test.cc
deleted file mode 100644
index 3dd881f..0000000
--- a/src/writer/msl/generator_impl_array_accessor_test.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, IndexAccessor) {
-  auto* ary = Var("ary", ty.array<i32, 10>());
-  auto* expr = IndexAccessor("ary", 5);
-  WrapInFunction(ary, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "ary[5]");
-}
-
-TEST_F(MslGeneratorImplTest, IndexAccessor_OfDref) {
-  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
-
-  auto* p = Const("p", nullptr, AddressOf("ary"));
-  auto* expr = IndexAccessor(Deref("p"), 5);
-  WrapInFunction(p, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(*(p))[5]");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_assign_test.cc b/src/writer/msl/generator_impl_assign_test.cc
deleted file mode 100644
index 3e635fa..0000000
--- a/src/writer/msl/generator_impl_assign_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Assign) {
-  auto* lhs = Var("lhs", ty.i32());
-  auto* rhs = Var("rhs", ty.i32());
-  auto* assign = Assign(lhs, rhs);
-  WrapInFunction(lhs, rhs, assign);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
-  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_binary_test.cc b/src/writer/msl/generator_impl_binary_test.cc
deleted file mode 100644
index 330d641..0000000
--- a/src/writer/msl/generator_impl_binary_test.cc
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-struct BinaryData {
-  const char* result;
-  ast::BinaryOp op;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << data.op;
-  return out;
-}
-using MslBinaryTest = TestParamHelper<BinaryData>;
-TEST_P(MslBinaryTest, Emit) {
-  auto params = GetParam();
-
-  auto type = [&] {
-    return ((params.op == ast::BinaryOp::kLogicalAnd) ||
-            (params.op == ast::BinaryOp::kLogicalOr))
-               ? static_cast<const ast::Type*>(ty.bool_())
-               : static_cast<const ast::Type*>(ty.u32());
-  };
-
-  auto* left = Var("left", type());
-  auto* right = Var("right", type());
-
-  auto* expr =
-      create<ast::BinaryExpression>(params.op, Expr(left), Expr(right));
-  WrapInFunction(left, right, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslBinaryTest,
-    testing::Values(
-        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
-        BinaryData{"(left | right)", ast::BinaryOp::kOr},
-        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
-        BinaryData{"(left && right)", ast::BinaryOp::kLogicalAnd},
-        BinaryData{"(left || right)", ast::BinaryOp::kLogicalOr},
-        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
-        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
-        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
-        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
-        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
-        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
-        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
-        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
-        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
-        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
-        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
-        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
-        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
-
-using MslBinaryTest_SignedOverflowDefinedBehaviour =
-    TestParamHelper<BinaryData>;
-TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour, Emit) {
-  auto params = GetParam();
-
-  auto* a_type = ty.i32();
-  auto* b_type = (params.op == ast::BinaryOp::kShiftLeft ||
-                  params.op == ast::BinaryOp::kShiftRight)
-                     ? static_cast<const ast::Type*>(ty.u32())
-                     : ty.i32();
-
-  auto* a = Var("a", a_type);
-  auto* b = Var("b", b_type);
-
-  auto* expr = create<ast::BinaryExpression>(params.op, Expr(a), Expr(b));
-  WrapInFunction(a, b, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-using Op = ast::BinaryOp;
-constexpr BinaryData signed_overflow_defined_behaviour_cases[] = {
-    {"as_type<int>((as_type<uint>(a) << b))", Op::kShiftLeft},
-    {"(a >> b)", Op::kShiftRight},
-    {"as_type<int>((as_type<uint>(a) + as_type<uint>(b)))", Op::kAdd},
-    {"as_type<int>((as_type<uint>(a) - as_type<uint>(b)))", Op::kSubtract},
-    {"as_type<int>((as_type<uint>(a) * as_type<uint>(b)))", Op::kMultiply}};
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslBinaryTest_SignedOverflowDefinedBehaviour,
-    testing::ValuesIn(signed_overflow_defined_behaviour_cases));
-
-using MslBinaryTest_SignedOverflowDefinedBehaviour_Chained =
-    TestParamHelper<BinaryData>;
-TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour_Chained, Emit) {
-  auto params = GetParam();
-
-  auto* a_type = ty.i32();
-  auto* b_type = (params.op == ast::BinaryOp::kShiftLeft ||
-                  params.op == ast::BinaryOp::kShiftRight)
-                     ? static_cast<const ast::Type*>(ty.u32())
-                     : ty.i32();
-
-  auto* a = Var("a", a_type);
-  auto* b = Var("b", b_type);
-
-  auto* expr1 = create<ast::BinaryExpression>(params.op, Expr(a), Expr(b));
-  auto* expr2 = create<ast::BinaryExpression>(params.op, expr1, Expr(b));
-  WrapInFunction(a, b, expr2);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr2)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-using Op = ast::BinaryOp;
-constexpr BinaryData signed_overflow_defined_behaviour_chained_cases[] = {
-    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) << b))) << "
-     "b))",
-     Op::kShiftLeft},
-    {"((a >> b) >> b)", Op::kShiftRight},
-    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) + "
-     "as_type<uint>(b)))) + as_type<uint>(b)))",
-     Op::kAdd},
-    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) - "
-     "as_type<uint>(b)))) - as_type<uint>(b)))",
-     Op::kSubtract},
-    {"as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) * "
-     "as_type<uint>(b)))) * as_type<uint>(b)))",
-     Op::kMultiply}};
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslBinaryTest_SignedOverflowDefinedBehaviour_Chained,
-    testing::ValuesIn(signed_overflow_defined_behaviour_chained_cases));
-
-TEST_F(MslBinaryTest, ModF32) {
-  auto* left = Var("left", ty.f32());
-  auto* right = Var("right", ty.f32());
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kModulo, Expr(left),
-                                             Expr(right));
-  WrapInFunction(left, right, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "fmod(left, right)");
-}
-
-TEST_F(MslBinaryTest, ModVec3F32) {
-  auto* left = Var("left", ty.vec3<f32>());
-  auto* right = Var("right", ty.vec3<f32>());
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kModulo, Expr(left),
-                                             Expr(right));
-  WrapInFunction(left, right, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "fmod(left, right)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_bitcast_test.cc b/src/writer/msl/generator_impl_bitcast_test.cc
deleted file mode 100644
index aee3497..0000000
--- a/src/writer/msl/generator_impl_bitcast_test.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Bitcast) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "as_type<float>(1)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_block_test.cc b/src/writer/msl/generator_impl_block_test.cc
deleted file mode 100644
index 952302d..0000000
--- a/src/writer/msl/generator_impl_block_test.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Block) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    discard_fragment();
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Block_WithoutNewline) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitBlock(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    discard_fragment();
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_break_test.cc b/src/writer/msl/generator_impl_break_test.cc
deleted file mode 100644
index 8ef4445..0000000
--- a/src/writer/msl/generator_impl_break_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Break) {
-  auto* b = create<ast::BreakStatement>();
-  WrapInFunction(Loop(Block(b)));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), "  break;\n");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_builtin_test.cc b/src/writer/msl/generator_impl_builtin_test.cc
deleted file mode 100644
index fba4296..0000000
--- a/src/writer/msl/generator_impl_builtin_test.cc
+++ /dev/null
@@ -1,496 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/sem/call.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using BuiltinType = sem::BuiltinType;
-
-using MslGeneratorImplTest = TestHelper;
-
-enum class ParamType {
-  kF32,
-  kU32,
-  kBool,
-};
-
-struct BuiltinData {
-  BuiltinType builtin;
-  ParamType type;
-  const char* msl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.msl_name << "<";
-  switch (data.type) {
-    case ParamType::kF32:
-      out << "f32";
-      break;
-    case ParamType::kU32:
-      out << "u32";
-      break;
-    case ParamType::kBool:
-      out << "bool";
-      break;
-  }
-  out << ">";
-  return out;
-}
-
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
-                                        ParamType type,
-                                        ProgramBuilder* builder) {
-  std::string name;
-  std::ostringstream str(name);
-  str << builtin;
-  switch (builtin) {
-    case BuiltinType::kAcos:
-    case BuiltinType::kAsin:
-    case BuiltinType::kAtan:
-    case BuiltinType::kCeil:
-    case BuiltinType::kCos:
-    case BuiltinType::kCosh:
-    case BuiltinType::kDpdx:
-    case BuiltinType::kDpdxCoarse:
-    case BuiltinType::kDpdxFine:
-    case BuiltinType::kDpdy:
-    case BuiltinType::kDpdyCoarse:
-    case BuiltinType::kDpdyFine:
-    case BuiltinType::kExp:
-    case BuiltinType::kExp2:
-    case BuiltinType::kFloor:
-    case BuiltinType::kFract:
-    case BuiltinType::kFwidth:
-    case BuiltinType::kFwidthCoarse:
-    case BuiltinType::kFwidthFine:
-    case BuiltinType::kInverseSqrt:
-    case BuiltinType::kIsFinite:
-    case BuiltinType::kIsInf:
-    case BuiltinType::kIsNan:
-    case BuiltinType::kIsNormal:
-    case BuiltinType::kLength:
-    case BuiltinType::kLog:
-    case BuiltinType::kLog2:
-    case BuiltinType::kNormalize:
-    case BuiltinType::kRound:
-    case BuiltinType::kSin:
-    case BuiltinType::kSinh:
-    case BuiltinType::kSqrt:
-    case BuiltinType::kTan:
-    case BuiltinType::kTanh:
-    case BuiltinType::kTrunc:
-    case BuiltinType::kSign:
-      return builder->Call(str.str(), "f2");
-    case BuiltinType::kLdexp:
-      return builder->Call(str.str(), "f2", "i2");
-    case BuiltinType::kAtan2:
-    case BuiltinType::kDot:
-    case BuiltinType::kDistance:
-    case BuiltinType::kPow:
-    case BuiltinType::kReflect:
-    case BuiltinType::kStep:
-      return builder->Call(str.str(), "f2", "f2");
-    case BuiltinType::kStorageBarrier:
-      return builder->Call(str.str());
-    case BuiltinType::kCross:
-      return builder->Call(str.str(), "f3", "f3");
-    case BuiltinType::kFma:
-    case BuiltinType::kMix:
-    case BuiltinType::kFaceForward:
-    case BuiltinType::kSmoothStep:
-      return builder->Call(str.str(), "f2", "f2", "f2");
-    case BuiltinType::kAll:
-    case BuiltinType::kAny:
-      return builder->Call(str.str(), "b2");
-    case BuiltinType::kAbs:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2");
-      } else {
-        return builder->Call(str.str(), "u2");
-      }
-    case BuiltinType::kCountOneBits:
-    case BuiltinType::kReverseBits:
-      return builder->Call(str.str(), "u2");
-    case BuiltinType::kMax:
-    case BuiltinType::kMin:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2");
-      }
-    case BuiltinType::kClamp:
-      if (type == ParamType::kF32) {
-        return builder->Call(str.str(), "f2", "f2", "f2");
-      } else {
-        return builder->Call(str.str(), "u2", "u2", "u2");
-      }
-    case BuiltinType::kSelect:
-      return builder->Call(str.str(), "f2", "f2", "b2");
-    case BuiltinType::kDeterminant:
-      return builder->Call(str.str(), "m2x2");
-    case BuiltinType::kPack2x16snorm:
-    case BuiltinType::kPack2x16unorm:
-      return builder->Call(str.str(), "f2");
-    case BuiltinType::kPack4x8snorm:
-    case BuiltinType::kPack4x8unorm:
-      return builder->Call(str.str(), "f4");
-    case BuiltinType::kUnpack4x8snorm:
-    case BuiltinType::kUnpack4x8unorm:
-    case BuiltinType::kUnpack2x16snorm:
-    case BuiltinType::kUnpack2x16unorm:
-      return builder->Call(str.str(), "u1");
-    case BuiltinType::kWorkgroupBarrier:
-      return builder->Call(str.str());
-    case BuiltinType::kTranspose:
-      return builder->Call(str.str(), "m3x2");
-    default:
-      break;
-  }
-  return nullptr;
-}
-
-using MslBuiltinTest = TestParamHelper<BuiltinData>;
-TEST_P(MslBuiltinTest, Emit) {
-  auto param = GetParam();
-
-  Global("f2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  Global("f3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  Global("f4", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-  Global("u1", ty.u32(), ast::StorageClass::kPrivate);
-  Global("u2", ty.vec2<u32>(), ast::StorageClass::kPrivate);
-  Global("i2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("b2", ty.vec2<bool>(), ast::StorageClass::kPrivate);
-  Global("m2x2", ty.mat2x2<f32>(), ast::StorageClass::kPrivate);
-  Global("m3x2", ty.mat3x2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = GenerateCall(param.builtin, param.type, this);
-  ASSERT_NE(nullptr, call) << "Unhandled builtin";
-  Func("func", {}, ty.void_(), {Ignore(call)},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem = program->Sem().Get(call);
-  ASSERT_NE(sem, nullptr);
-  auto* target = sem->Target();
-  ASSERT_NE(target, nullptr);
-  auto* builtin = target->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-
-  EXPECT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslBuiltinTest,
-    testing::Values(
-        BuiltinData{BuiltinType::kAbs, ParamType::kF32, "fabs"},
-        BuiltinData{BuiltinType::kAbs, ParamType::kU32, "abs"},
-        BuiltinData{BuiltinType::kAcos, ParamType::kF32, "acos"},
-        BuiltinData{BuiltinType::kAll, ParamType::kBool, "all"},
-        BuiltinData{BuiltinType::kAny, ParamType::kBool, "any"},
-        BuiltinData{BuiltinType::kAsin, ParamType::kF32, "asin"},
-        BuiltinData{BuiltinType::kAtan, ParamType::kF32, "atan"},
-        BuiltinData{BuiltinType::kAtan2, ParamType::kF32, "atan2"},
-        BuiltinData{BuiltinType::kCeil, ParamType::kF32, "ceil"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kF32, "clamp"},
-        BuiltinData{BuiltinType::kClamp, ParamType::kU32, "clamp"},
-        BuiltinData{BuiltinType::kCos, ParamType::kF32, "cos"},
-        BuiltinData{BuiltinType::kCosh, ParamType::kF32, "cosh"},
-        BuiltinData{BuiltinType::kCountOneBits, ParamType::kU32, "popcount"},
-        BuiltinData{BuiltinType::kCross, ParamType::kF32, "cross"},
-        BuiltinData{BuiltinType::kDeterminant, ParamType::kF32, "determinant"},
-        BuiltinData{BuiltinType::kDistance, ParamType::kF32, "distance"},
-        BuiltinData{BuiltinType::kDot, ParamType::kF32, "dot"},
-        BuiltinData{BuiltinType::kDpdx, ParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdxCoarse, ParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdxFine, ParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdy, ParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kDpdyCoarse, ParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kDpdyFine, ParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kExp, ParamType::kF32, "exp"},
-        BuiltinData{BuiltinType::kExp2, ParamType::kF32, "exp2"},
-        BuiltinData{BuiltinType::kFaceForward, ParamType::kF32, "faceforward"},
-        BuiltinData{BuiltinType::kFloor, ParamType::kF32, "floor"},
-        BuiltinData{BuiltinType::kFma, ParamType::kF32, "fma"},
-        BuiltinData{BuiltinType::kFract, ParamType::kF32, "fract"},
-        BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
-        BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
-        BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
-        BuiltinData{BuiltinType::kIsNan, ParamType::kF32, "isnan"},
-        BuiltinData{BuiltinType::kIsNormal, ParamType::kF32, "isnormal"},
-        BuiltinData{BuiltinType::kLdexp, ParamType::kF32, "ldexp"},
-        BuiltinData{BuiltinType::kLength, ParamType::kF32, "length"},
-        BuiltinData{BuiltinType::kLog, ParamType::kF32, "log"},
-        BuiltinData{BuiltinType::kLog2, ParamType::kF32, "log2"},
-        BuiltinData{BuiltinType::kMax, ParamType::kF32, "fmax"},
-        BuiltinData{BuiltinType::kMax, ParamType::kU32, "max"},
-        BuiltinData{BuiltinType::kMin, ParamType::kF32, "fmin"},
-        BuiltinData{BuiltinType::kMin, ParamType::kU32, "min"},
-        BuiltinData{BuiltinType::kNormalize, ParamType::kF32, "normalize"},
-        BuiltinData{BuiltinType::kPack4x8snorm, ParamType::kF32,
-                    "pack_float_to_snorm4x8"},
-        BuiltinData{BuiltinType::kPack4x8unorm, ParamType::kF32,
-                    "pack_float_to_unorm4x8"},
-        BuiltinData{BuiltinType::kPack2x16snorm, ParamType::kF32,
-                    "pack_float_to_snorm2x16"},
-        BuiltinData{BuiltinType::kPack2x16unorm, ParamType::kF32,
-                    "pack_float_to_unorm2x16"},
-        BuiltinData{BuiltinType::kPow, ParamType::kF32, "pow"},
-        BuiltinData{BuiltinType::kReflect, ParamType::kF32, "reflect"},
-        BuiltinData{BuiltinType::kReverseBits, ParamType::kU32, "reverse_bits"},
-        BuiltinData{BuiltinType::kRound, ParamType::kU32, "rint"},
-        BuiltinData{BuiltinType::kSelect, ParamType::kF32, "select"},
-        BuiltinData{BuiltinType::kSign, ParamType::kF32, "sign"},
-        BuiltinData{BuiltinType::kSin, ParamType::kF32, "sin"},
-        BuiltinData{BuiltinType::kSinh, ParamType::kF32, "sinh"},
-        BuiltinData{BuiltinType::kSmoothStep, ParamType::kF32, "smoothstep"},
-        BuiltinData{BuiltinType::kSqrt, ParamType::kF32, "sqrt"},
-        BuiltinData{BuiltinType::kStep, ParamType::kF32, "step"},
-        BuiltinData{BuiltinType::kTan, ParamType::kF32, "tan"},
-        BuiltinData{BuiltinType::kTanh, ParamType::kF32, "tanh"},
-        BuiltinData{BuiltinType::kTranspose, ParamType::kF32, "transpose"},
-        BuiltinData{BuiltinType::kTrunc, ParamType::kF32, "trunc"},
-        BuiltinData{BuiltinType::kUnpack4x8snorm, ParamType::kU32,
-                    "unpack_snorm4x8_to_float"},
-        BuiltinData{BuiltinType::kUnpack4x8unorm, ParamType::kU32,
-                    "unpack_unorm4x8_to_float"},
-        BuiltinData{BuiltinType::kUnpack2x16snorm, ParamType::kU32,
-                    "unpack_snorm2x16_to_float"},
-        BuiltinData{BuiltinType::kUnpack2x16unorm, ParamType::kU32,
-                    "unpack_unorm2x16_to_float"}));
-
-TEST_F(MslGeneratorImplTest, Builtin_Call) {
-  Global("param1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  Global("param2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("dot", "param1", "param2");
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "dot(param1, param2)");
-}
-
-TEST_F(MslGeneratorImplTest, StorageBarrier) {
-  auto* call = Call("storageBarrier");
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "threadgroup_barrier(mem_flags::mem_device)");
-}
-
-TEST_F(MslGeneratorImplTest, WorkgroupBarrier) {
-  auto* call = Call("workgroupBarrier");
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "threadgroup_barrier(mem_flags::mem_threadgroup)");
-}
-
-TEST_F(MslGeneratorImplTest, Degrees_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-float tint_degrees(float param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-kernel void test_function() {
-  float val = 0.0f;
-  float const tint_symbol = tint_degrees(val);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Degrees_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("degrees", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-float3 tint_degrees(float3 param_0) {
-  return param_0 * 57.295779513082322865;
-}
-
-kernel void test_function() {
-  float3 val = 0.0f;
-  float3 const tint_symbol = tint_degrees(val);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Radians_Scalar) {
-  auto* val = Var("val", ty.f32());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-float tint_radians(float param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-kernel void test_function() {
-  float val = 0.0f;
-  float const tint_symbol = tint_radians(val);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Radians_Vector) {
-  auto* val = Var("val", ty.vec3<f32>());
-  auto* call = Call("radians", val);
-  WrapInFunction(val, call);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-float3 tint_radians(float3 param_0) {
-  return param_0 * 0.017453292519943295474;
-}
-
-kernel void test_function() {
-  float3 val = 0.0f;
-  float3 const tint_symbol = tint_radians(val);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Pack2x16Float) {
-  auto* call = Call("pack2x16float", "p1");
-  Global("p1", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "as_type<uint>(half2(p1))");
-}
-
-TEST_F(MslGeneratorImplTest, Unpack2x16Float) {
-  auto* call = Call("unpack2x16float", "p1");
-  Global("p1", ty.u32(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(call));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "float2(as_type<half2>(p1))");
-}
-
-TEST_F(MslGeneratorImplTest, DotI32) {
-  Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-  WrapInFunction(CallStmt(Call("dot", "v", "v")));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-template<typename T>
-T tint_dot3(vec<T,3> a, vec<T,3> b) {
-  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
-}
-kernel void test_function() {
-  thread int3 tint_symbol = 0;
-  tint_dot3(tint_symbol, tint_symbol);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Ignore) {
-  Func("f", {Param("a", ty.i32()), Param("b", ty.i32()), Param("c", ty.i32())},
-       ty.i32(), {Return(Mul(Add("a", "b"), "c"))});
-
-  Func("func", {}, ty.void_(), {CallStmt(Call("f", 1, 2, 3))},
-       {
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-int f(int a, int b, int c) {
-  return as_type<int>((as_type<uint>(as_type<int>((as_type<uint>(a) + as_type<uint>(b)))) * as_type<uint>(c)));
-}
-
-kernel void func() {
-  f(1, 2, 3);
-  return;
-}
-
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_builtin_texture_test.cc b/src/writer/msl/generator_impl_builtin_texture_test.cc
deleted file mode 100644
index 46fe89f..0000000
--- a/src/writer/msl/generator_impl_builtin_texture_test.cc
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2020 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/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-std::string expected_texture_overload(
-    ast::builtin::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-      return R"(int(texture.get_width(0)))";
-    case ValidTextureOverload::kDimensions2d:
-    case ValidTextureOverload::kDimensions2dArray:
-    case ValidTextureOverload::kDimensionsCube:
-    case ValidTextureOverload::kDimensionsCubeArray:
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-    case ValidTextureOverload::kDimensionsDepth2d:
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-    case ValidTextureOverload::kDimensionsDepthCube:
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-      return R"(int2(texture.get_width(), texture.get_height()))";
-    case ValidTextureOverload::kDimensions3d:
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return R"(int3(texture.get_width(), texture.get_height(), texture.get_depth()))";
-    case ValidTextureOverload::kDimensions2dLevel:
-    case ValidTextureOverload::kDimensionsCubeLevel:
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return R"(int2(texture.get_width(1), texture.get_height(1)))";
-    case ValidTextureOverload::kDimensions3dLevel:
-      return R"(int3(texture.get_width(1), texture.get_height(1), texture.get_depth(1)))";
-    case ValidTextureOverload::kGather2dF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(0), component::x))";
-    case ValidTextureOverload::kGather2dOffsetF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(3, 4), component::x))";
-    case ValidTextureOverload::kGather2dArrayF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(0), component::x))";
-    case ValidTextureOverload::kGather2dArrayOffsetF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(4, 5), component::x))";
-    case ValidTextureOverload::kGatherCubeF32:
-      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), component::x))";
-    case ValidTextureOverload::kGatherCubeArrayF32:
-      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), 4, component::x))";
-    case ValidTextureOverload::kGatherDepth2dF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f)))";
-    case ValidTextureOverload::kGatherDepth2dOffsetF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
-    case ValidTextureOverload::kGatherDepth2dArrayF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3))";
-    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
-      return R"(texture.gather(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
-    case ValidTextureOverload::kGatherDepthCubeF32:
-      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kGatherDepthCubeArrayF32:
-      return R"(texture.gather(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
-    case ValidTextureOverload::kGatherCompareDepth2dF32:
-      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
-    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
-      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
-      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3, 4.0f))";
-    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
-      return R"(texture.gather_compare(sampler, float2(1.0f, 2.0f), 3, 4.0f, int2(5, 6)))";
-    case ValidTextureOverload::kGatherCompareDepthCubeF32:
-      return R"(texture.gather_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
-    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
-      return R"(texture.gather_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
-    case ValidTextureOverload::kNumLayers2dArray:
-    case ValidTextureOverload::kNumLayersCubeArray:
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return R"(int(texture.get_array_size()))";
-    case ValidTextureOverload::kNumLevels2d:
-    case ValidTextureOverload::kNumLevels2dArray:
-    case ValidTextureOverload::kNumLevels3d:
-    case ValidTextureOverload::kNumLevelsCube:
-    case ValidTextureOverload::kNumLevelsCubeArray:
-    case ValidTextureOverload::kNumLevelsDepth2d:
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-    case ValidTextureOverload::kNumLevelsDepthCube:
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return R"(int(texture.get_num_mip_levels()))";
-    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-      return R"(int(texture.get_num_samples()))";
-    case ValidTextureOverload::kSample1dF32:
-      return R"(texture.sample(sampler, 1.0f))";
-    case ValidTextureOverload::kSample2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f)))";
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
-    case ValidTextureOverload::kSample2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3))";
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
-    case ValidTextureOverload::kSample3dF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), int3(4, 5, 6)))";
-    case ValidTextureOverload::kSampleCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f)))";
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), int2(3, 4)))";
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3))";
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, int2(4, 5)))";
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f)))";
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4))";
-    case ValidTextureOverload::kSampleBias2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), bias(3.0f)))";
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), bias(3.0f), int2(4, 5)))";
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 4, bias(3.0f)))";
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, bias(4.0f), int2(5, 6)))";
-    case ValidTextureOverload::kSampleBias3dF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f)))";
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f), int3(5, 6, 7)))";
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), bias(4.0f)))";
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 3, bias(4.0f)))";
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3.0f)))";
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3.0f), int2(4, 5)))";
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4.0f)))";
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4.0f), int2(5, 6)))";
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f)))";
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f), int3(5, 6, 7)))";
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4.0f)))";
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, level(5.0f)))";
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3)))";
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), level(3), int2(4, 5)))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4)))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, level(4), int2(5, 6)))";
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), level(4)))";
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, level(5)))";
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), gradient2d(float2(3.0f, 4.0f), float2(5.0f, 6.0f))))";
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), gradient2d(float2(3.0f, 4.0f), float2(5.0f, 6.0f)), int2(7, 7)))";
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, gradient2d(float2(4.0f, 5.0f), float2(6.0f, 7.0f))))";
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return R"(texture.sample(sampler, float2(1.0f, 2.0f), 3, gradient2d(float2(4.0f, 5.0f), float2(6.0f, 7.0f)), int2(6, 7)))";
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradient3d(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f))))";
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradient3d(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)), int3(0, 1, 2)))";
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), gradientcube(float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f))))";
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return R"(texture.sample(sampler, float3(1.0f, 2.0f, 3.0f), 4, gradientcube(float3(5.0f, 6.0f, 7.0f), float3(8.0f, 9.0f, 10.0f))))";
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f, int2(5, 6)))";
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f))";
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
-      return R"(texture.sample_compare(sampler, float2(1.0f, 2.0f), 4, 3.0f, int2(5, 6)))";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
-      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4.0f))";
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
-      return R"(texture.sample_compare(sampler, float3(1.0f, 2.0f, 3.0f), 4, 5.0f))";
-    case ValidTextureOverload::kLoad1dLevelF32:
-      return R"(texture.read(uint(1), 0))";
-    case ValidTextureOverload::kLoad1dLevelU32:
-      return R"(texture.read(uint(1), 0))";
-    case ValidTextureOverload::kLoad1dLevelI32:
-      return R"(texture.read(uint(1), 0))";
-    case ValidTextureOverload::kLoad2dLevelF32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoad2dLevelU32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
-    case ValidTextureOverload::kLoad3dLevelF32:
-      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
-    case ValidTextureOverload::kLoad3dLevelU32:
-      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
-    case ValidTextureOverload::kLoad3dLevelI32:
-      return R"(texture.read(uint3(int3(1, 2, 3)), 4))";
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
-      return R"(texture.read(uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return R"(texture.read(uint2(int2(1, 2)), 3, 4))";
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-      return R"(texture.write(float4(2.0f, 3.0f, 4.0f, 5.0f), uint(1)))";
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-      return R"(texture.write(float4(3.0f, 4.0f, 5.0f, 6.0f), uint2(int2(1, 2))))";
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return R"(texture.write(float4(4.0f, 5.0f, 6.0f, 7.0f), uint2(int2(1, 2)), 3))";
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return R"(texture.write(float4(4.0f, 5.0f, 6.0f, 7.0f), uint3(int3(1, 2, 3))))";
-  }
-  return "<unmatched texture overload>";
-}  // NOLINT - Ignore the length of this function
-
-class MslGeneratorBuiltinTextureTest
-    : public TestParamHelper<ast::builtin::test::TextureOverloadCase> {};
-
-TEST_P(MslGeneratorBuiltinTextureTest, Call) {
-  auto param = GetParam();
-
-  param.BuildTextureVariable(this);
-  param.BuildSamplerVariable(this);
-
-  auto* call = Call(Expr(param.function), param.args(this));
-  auto* stmt = CallStmt(call);
-
-  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-
-  auto expected = expected_texture_overload(param.overload);
-  EXPECT_EQ(expected, out.str());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorBuiltinTextureTest,
-    MslGeneratorBuiltinTextureTest,
-    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_call_test.cc b/src/writer/msl/generator_impl_call_test.cc
deleted file mode 100644
index a06519f..0000000
--- a/src/writer/msl/generator_impl_call_test.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
-  Func("my_func", {}, ty.f32(), {Return(1.23f)});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func()");
-}
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithParams) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.f32(), {Return(1.23f)});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func(param1, param2)");
-}
-
-TEST_F(MslGeneratorImplTest, EmitStatement_Call) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  auto* stmt = CallStmt(call);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_case_test.cc b/src/writer/msl/generator_impl_case_test.cc
deleted file mode 100644
index ece2370..0000000
--- a/src/writer/msl/generator_impl_case_test.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Case) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Case_BreaksByDefault) {
-  auto* s = Switch(1, Case(Expr(5), Block()), DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Case_WithFallthrough) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::FallthroughStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    /* fallthrough */
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Case_MultipleSelectors) {
-  auto* s =
-      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
-             DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5:
-  case 6: {
-    break;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Case_Default) {
-  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  default: {
-    break;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_cast_test.cc b/src/writer/msl/generator_impl_cast_test.cc
deleted file mode 100644
index c871382..0000000
--- a/src/writer/msl/generator_impl_cast_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Scalar) {
-  auto* cast = Construct<f32>(1);
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "float(1)");
-}
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Vector) {
-  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "float3(int3(1, 2, 3))");
-}
-
-TEST_F(MslGeneratorImplTest, EmitExpression_Cast_IntMin) {
-  auto* cast = Construct<u32>(std::numeric_limits<int32_t>::min());
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "uint((-2147483647 - 1))");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_constructor_test.cc b/src/writer/msl/generator_impl_constructor_test.cc
deleted file mode 100644
index bf4646f..0000000
--- a/src/writer/msl/generator_impl_constructor_test.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Bool) {
-  WrapInFunction(Expr(false));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("false"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Int) {
-  WrapInFunction(Expr(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_UInt) {
-  WrapInFunction(Expr(56779u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Float) {
-  // Use a number close to 1<<30 but whose decimal representation ends in 0.
-  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Float) {
-  WrapInFunction(Construct<f32>(-1.2e-5f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Bool) {
-  WrapInFunction(Construct<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Int) {
-  WrapInFunction(Construct<i32>(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Uint) {
-  WrapInFunction(Construct<u32>(12345u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec) {
-  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec_Empty) {
-  WrapInFunction(vec3<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float3()"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Mat) {
-  WrapInFunction(Construct(ty.mat2x3<f32>(), vec3<f32>(1.0f, 2.0f, 3.0f),
-                           vec3<f32>(3.0f, 4.0f, 5.0f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  // A matrix of type T with n columns and m rows can also be constructed from
-  // n vectors of type T with m components.
-  EXPECT_THAT(
-      gen.result(),
-      HasSubstr(
-          "float2x3(float3(1.0f, 2.0f, 3.0f), float3(3.0f, 4.0f, 5.0f))"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Mat_Empty) {
-  WrapInFunction(mat4x4<f32>());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("float4x4()"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Array) {
-  WrapInFunction(
-      Construct(ty.array(ty.vec3<f32>(), 3), vec3<f32>(1.0f, 2.0f, 3.0f),
-                vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("{float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), "
-                        "float3(7.0f, 8.0f, 9.0f)}"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("{.a=1, .b=2.0f, .c=int3(3, 4, 5)}"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct_Empty) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
-
-  WrapInFunction(Construct(ty.Of(str)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("{}"));
-  EXPECT_THAT(gen.result(), testing::Not(HasSubstr("{{}}")));
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_continue_test.cc b/src/writer/msl/generator_impl_continue_test.cc
deleted file mode 100644
index cb3b932..0000000
--- a/src/writer/msl/generator_impl_continue_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Continue) {
-  auto* loop = Loop(Block(If(false, Block(Break())),  //
-                          Continue()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    if (false) {
-      break;
-    }
-    continue;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_discard_test.cc b/src/writer/msl/generator_impl_discard_test.cc
deleted file mode 100644
index 6cdbba9..0000000
--- a/src/writer/msl/generator_impl_discard_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Discard) {
-  auto* stmt = create<ast::DiscardStatement>();
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  discard_fragment();\n");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
deleted file mode 100644
index ccfd3ec..0000000
--- a/src/writer/msl/generator_impl_function_test.cc
+++ /dev/null
@@ -1,724 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Function) {
-  Func("my_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       {});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
-
-  using namespace metal;
-  void my_func() {
-    return;
-  }
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Function_WithParams) {
-  ast::VariableList params;
-  params.push_back(Param("a", ty.f32()));
-  params.push_back(Param("b", ty.i32()));
-
-  Func("my_func", params, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       {});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
-
-  using namespace metal;
-  void my_func(float a, int b) {
-    return;
-  }
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_NoReturn_Void) {
-  Func("main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{/* no explicit return */},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-fragment void main() {
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_WithInOutVars) {
-  // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
-  //   return foo;
-  // }
-  auto* foo_in = Param("foo", ty.f32(), {Location(0)});
-  Func("frag_main", ast::VariableList{foo_in}, ty.f32(), {Return("foo")},
-       {Stage(ast::PipelineStage::kFragment)}, {Location(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol_1 {
-  float foo [[user(locn0)]];
-};
-
-struct tint_symbol_2 {
-  float value [[color(1)]];
-};
-
-float frag_main_inner(float foo) {
-  return foo;
-}
-
-fragment tint_symbol_2 frag_main(tint_symbol_1 tint_symbol [[stage_in]]) {
-  float const inner_result = frag_main_inner(tint_symbol.foo);
-  tint_symbol_2 wrapper_result = {};
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_WithInOut_Builtins) {
-  // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
-  //   return coord.x;
-  // }
-  auto* coord_in =
-      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-  Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
-       {Return(MemberAccessor("coord", "x"))},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kFragDepth)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol {
-  float value [[depth(any)]];
-};
-
-float frag_main_inner(float4 coord) {
-  return coord[0];
-}
-
-fragment tint_symbol frag_main(float4 coord [[position]]) {
-  float const inner_result = frag_main_inner(coord);
-  tint_symbol wrapper_result = {};
-  wrapper_result.value = inner_result;
-  return wrapper_result;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) {
-  // struct Interface {
-  //   @location(1) col1 : f32;
-  //   @location(2) col2 : f32;
-  //   @builtin(position) pos : vec4<f32>;
-  // };
-  // fn vert_main() -> Interface {
-  //   return Interface(0.4, 0.6, vec4<f32>());
-  // }
-  // fn frag_main(colors : Interface) {
-  //   const r = colors.col1;
-  //   const g = colors.col2;
-  // }
-  auto* interface_struct = Structure(
-      "Interface",
-      {
-          Member("col1", ty.f32(), {Location(1)}),
-          Member("col2", ty.f32(), {Location(2)}),
-          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-      });
-
-  Func("vert_main", {}, ty.Of(interface_struct),
-       {Return(Construct(ty.Of(interface_struct), Expr(0.5f), Expr(0.25f),
-                         Construct(ty.vec4<f32>())))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("frag_main", {Param("colors", ty.Of(interface_struct))}, ty.void_(),
-       {
-           WrapInStatement(
-               Const("r", ty.f32(), MemberAccessor("colors", "col1"))),
-           WrapInStatement(
-               Const("g", ty.f32(), MemberAccessor("colors", "col2"))),
-       },
-       {Stage(ast::PipelineStage::kFragment)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Interface {
-  float col1;
-  float col2;
-  float4 pos;
-};
-
-struct tint_symbol {
-  float col1 [[user(locn1)]];
-  float col2 [[user(locn2)]];
-  float4 pos [[position]];
-};
-
-Interface vert_main_inner() {
-  Interface const tint_symbol_3 = {.col1=0.5f, .col2=0.25f, .pos=float4()};
-  return tint_symbol_3;
-}
-
-vertex tint_symbol vert_main() {
-  Interface const inner_result = vert_main_inner();
-  tint_symbol wrapper_result = {};
-  wrapper_result.col1 = inner_result.col1;
-  wrapper_result.col2 = inner_result.col2;
-  wrapper_result.pos = inner_result.pos;
-  return wrapper_result;
-}
-
-struct tint_symbol_2 {
-  float col1 [[user(locn1)]];
-  float col2 [[user(locn2)]];
-};
-
-void frag_main_inner(Interface colors) {
-  float const r = colors.col1;
-  float const g = colors.col2;
-}
-
-fragment void frag_main(float4 pos [[position]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
-  Interface const tint_symbol_4 = {.col1=tint_symbol_1.col1, .col2=tint_symbol_1.col2, .pos=pos};
-  frag_main_inner(tint_symbol_4);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) {
-  // struct VertexOutput {
-  //   @builtin(position) pos : vec4<f32>;
-  // };
-  // fn foo(x : f32) -> VertexOutput {
-  //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
-  // }
-  // fn vert_main1() -> VertexOutput {
-  //   return foo(0.5);
-  // }
-  // fn vert_main2() -> VertexOutput {
-  //   return foo(0.25);
-  // }
-  auto* vertex_output_struct = Structure(
-      "VertexOutput",
-      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
-
-  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
-       {Return(Construct(ty.Of(vertex_output_struct),
-                         Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
-       {});
-
-  Func("vert_main1", {}, ty.Of(vertex_output_struct),
-       {Return(Expr(Call("foo", Expr(0.5f))))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  Func("vert_main2", {}, ty.Of(vertex_output_struct),
-       {Return(Expr(Call("foo", Expr(0.25f))))},
-       {Stage(ast::PipelineStage::kVertex)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct VertexOutput {
-  float4 pos;
-};
-
-VertexOutput foo(float x) {
-  VertexOutput const tint_symbol_2 = {.pos=float4(x, x, x, 1.0f)};
-  return tint_symbol_2;
-}
-
-struct tint_symbol {
-  float4 pos [[position]];
-};
-
-VertexOutput vert_main1_inner() {
-  return foo(0.5f);
-}
-
-vertex tint_symbol vert_main1() {
-  VertexOutput const inner_result = vert_main1_inner();
-  tint_symbol wrapper_result = {};
-  wrapper_result.pos = inner_result.pos;
-  return wrapper_result;
-}
-
-struct tint_symbol_1 {
-  float4 pos [[position]];
-};
-
-VertexOutput vert_main2_inner() {
-  return foo(0.25f);
-}
-
-vertex tint_symbol_1 vert_main2() {
-  VertexOutput const inner_result_1 = vert_main2_inner();
-  tint_symbol_1 wrapper_result_1 = {};
-  wrapper_result_1.pos = inner_result_1.pos;
-  return wrapper_result_1;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_FunctionAttribute_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Data {
-  /* 0x0000 */ int a;
-  /* 0x0004 */ float b;
-};
-
-fragment void frag_main(device Data* tint_symbol [[buffer(0)]]) {
-  float v = (*(tint_symbol)).b;
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_FunctionAttribute_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                  MemberAccessor("coord", "b"));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Data {
-  /* 0x0000 */ int a;
-  /* 0x0004 */ float b;
-};
-
-fragment void frag_main(const device Data* tint_symbol [[buffer(0)]]) {
-  float v = (*(tint_symbol)).b;
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
-                           {create<ast::StructBlockAttribute>()});
-  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(0),
-                     });
-
-  Func("sub_func",
-       {
-           Param("param", ty.f32()),
-       },
-       ty.f32(),
-       {
-           Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
-       });
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", {}, ty.void_(),
-       {
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct UBO {
-  /* 0x0000 */ float4 coord;
-};
-
-float sub_func(float param, const constant UBO* const tint_symbol) {
-  return (*(tint_symbol)).coord[0];
-}
-
-fragment void frag_main(const constant UBO* tint_symbol_1 [[buffer(0)]]) {
-  float v = sub_func(1.0f, tint_symbol_1);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_FunctionAttribute_Called_By_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ast::VariableList params;
-  params.push_back(Param("param", ty.f32()));
-
-  auto body = ast::StatementList{Return(MemberAccessor("coord", "b"))};
-
-  Func("sub_func", params, ty.f32(), body, {});
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Data {
-  /* 0x0000 */ int a;
-  /* 0x0004 */ float b;
-};
-
-float sub_func(float param, device Data* const tint_symbol) {
-  return (*(tint_symbol)).b;
-}
-
-fragment void frag_main(device Data* tint_symbol_1 [[buffer(0)]]) {
-  float v = sub_func(1.0f, tint_symbol_1);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest,
-       Emit_FunctionAttribute_Called_By_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  ast::VariableList params;
-  params.push_back(Param("param", ty.f32()));
-
-  auto body = ast::StatementList{Return(MemberAccessor("coord", "b"))};
-
-  Func("sub_func", params, ty.f32(), body, {});
-
-  auto* var =
-      Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1.0f));
-
-  Func("frag_main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(var),
-           Return(),
-       },
-       {
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Data {
-  /* 0x0000 */ int a;
-  /* 0x0004 */ float b;
-};
-
-float sub_func(float param, const device Data* const tint_symbol) {
-  return (*(tint_symbol)).b;
-}
-
-fragment void frag_main(const device Data* tint_symbol_1 [[buffer(0)]]) {
-  float v = sub_func(1.0f, tint_symbol_1);
-  return;
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayParams) {
-  ast::VariableList params;
-  params.push_back(Param("a", ty.array<f32, 5>()));
-
-  Func("my_func", params, ty.void_(),
-       {
-           Return(),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
-
-  using namespace metal;
-  struct tint_array_wrapper {
-    float arr[5];
-  };
-
-  void my_func(tint_array_wrapper a) {
-    return;
-  }
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayReturn) {
-  Func("my_func", {}, ty.array<f32, 5>(),
-       {
-           Return(Construct(ty.array<f32, 5>())),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  #include <metal_stdlib>
-
-  using namespace metal;
-  struct tint_array_wrapper {
-    float arr[5];
-  };
-
-  tint_array_wrapper my_func() {
-    tint_array_wrapper const tint_symbol = {.arr={}};
-    return tint_symbol;
-  }
-
-)");
-}
-
-// https://crbug.com/tint/297
-TEST_F(MslGeneratorImplTest,
-       Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
-  // [[block]] struct Data {
-  //   d : f32;
-  // };
-  // @binding(0) @group(0) var<storage> data : Data;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn a() {
-  //   return;
-  // }
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn b() {
-  //   return;
-  // }
-
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("a", ast::VariableList{}, ty.void_(),
-         ast::StatementList{
-             Decl(var),
-             Return(),
-         },
-         {
-             Stage(ast::PipelineStage::kCompute),
-             WorkgroupSize(1),
-         });
-  }
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("b", ast::VariableList{}, ty.void_(),
-         ast::StatementList{Decl(var), Return()},
-         {
-             Stage(ast::PipelineStage::kCompute),
-             WorkgroupSize(1),
-         });
-  }
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Data {
-  /* 0x0000 */ float d;
-};
-
-kernel void a(device Data* tint_symbol [[buffer(0)]]) {
-  float v = (*(tint_symbol)).d;
-  return;
-}
-
-kernel void b(device Data* tint_symbol_1 [[buffer(0)]]) {
-  float v = (*(tint_symbol_1)).d;
-  return;
-}
-
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_identifier_test.cc b/src/writer/msl/generator_impl_identifier_test.cc
deleted file mode 100644
index 0275404..0000000
--- a/src/writer/msl/generator_impl_identifier_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitIdentifierExpression) {
-  auto* foo = Var("foo", ty.i32());
-
-  auto* i = Expr("foo");
-  WrapInFunction(foo, i);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
-  EXPECT_EQ(out.str(), "foo");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_if_test.cc b/src/writer/msl/generator_impl_if_test.cc
deleted file mode 100644
index 4bbf950..0000000
--- a/src/writer/msl/generator_impl_if_test.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_If) {
-  auto* cond = Var("cond", ty.bool_());
-  auto* i = If(cond, Block(Return()));
-  WrapInFunction(cond, i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_IfWithElseIf) {
-  auto* cond = Var("cond", ty.bool_());
-  auto* else_cond = Var("else_cond", ty.bool_());
-  auto* i = If(cond, Block(Return()), Else(else_cond, Block(Return())));
-  WrapInFunction(cond, else_cond, i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_IfWithElse) {
-  auto* cond = Var("cond", ty.bool_());
-  auto* i = If(cond, Block(Return()), Else(nullptr, Block(Return())));
-  WrapInFunction(cond, i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_IfWithMultiple) {
-  auto* cond = Var("cond", ty.bool_());
-  auto* else_cond = Var("else_cond", ty.bool_());
-  auto* i = If(cond, Block(Return()), Else(else_cond, Block(Return())),
-               Else(nullptr, Block(Return())));
-  WrapInFunction(cond, else_cond, i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    if (else_cond) {
-      return;
-    } else {
-      return;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_import_test.cc b/src/writer/msl/generator_impl_import_test.cc
deleted file mode 100644
index 2624195..0000000
--- a/src/writer/msl/generator_impl_import_test.cc
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/call.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-struct MslImportData {
-  const char* name;
-  const char* msl_name;
-};
-inline std::ostream& operator<<(std::ostream& out, MslImportData data) {
-  out << data.name;
-  return out;
-}
-using MslImportData_SingleParamTest = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_SingleParamTest, FloatScalar) {
-  auto param = GetParam();
-  auto* call = Call(param.name, 1.f);
-
-  // The resolver will set the builtin data for the ident
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  auto* sem = program->Sem().Get(call);
-  ASSERT_NE(sem, nullptr);
-  auto* target = sem->Target();
-  ASSERT_NE(target, nullptr);
-  auto* builtin = target->As<sem::Builtin>();
-  ASSERT_NE(builtin, nullptr);
-
-  ASSERT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_SingleParamTest,
-                         testing::Values(MslImportData{"abs", "fabs"},
-                                         MslImportData{"acos", "acos"},
-                                         MslImportData{"asin", "asin"},
-                                         MslImportData{"atan", "atan"},
-                                         MslImportData{"ceil", "ceil"},
-                                         MslImportData{"cos", "cos"},
-                                         MslImportData{"cosh", "cosh"},
-                                         MslImportData{"exp", "exp"},
-                                         MslImportData{"exp2", "exp2"},
-                                         MslImportData{"floor", "floor"},
-                                         MslImportData{"fract", "fract"},
-                                         MslImportData{"inverseSqrt", "rsqrt"},
-                                         MslImportData{"length", "length"},
-                                         MslImportData{"log", "log"},
-                                         MslImportData{"log2", "log2"},
-                                         MslImportData{"round", "rint"},
-                                         MslImportData{"sign", "sign"},
-                                         MslImportData{"sin", "sin"},
-                                         MslImportData{"sinh", "sinh"},
-                                         MslImportData{"sqrt", "sqrt"},
-                                         MslImportData{"tan", "tan"},
-                                         MslImportData{"tanh", "tanh"},
-                                         MslImportData{"trunc", "trunc"}));
-
-TEST_F(MslGeneratorImplTest, MslImportData_SingleParamTest_IntScalar) {
-  auto* expr = Call("abs", 1);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), R"(abs(1))");
-}
-
-TEST_F(MslGeneratorImplTest, MslImportData_SingleParamTest_ScalarLength) {
-  auto* expr = Call("length", 2.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), R"(fabs(2.0f))");
-}
-
-using MslImportData_DualParam_ScalarTest = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_DualParam_ScalarTest, Float) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1.0f, 2.0f);
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1.0f, 2.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_DualParam_ScalarTest,
-                         testing::Values(MslImportData{"atan2", "atan2"},
-                                         MslImportData{"max", "fmax"},
-                                         MslImportData{"min", "fmin"},
-                                         MslImportData{"pow", "pow"},
-                                         MslImportData{"step", "step"}));
-
-TEST_F(MslGeneratorImplTest, MslImportData_DualParam_ScalarDistance) {
-  auto* expr = Call("distance", 2.f, 3.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), R"(fabs(2.0f - 3.0f))");
-}
-
-using MslImportData_DualParam_VectorTest = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_DualParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr =
-      Call(param.name, vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(4.f, 5.f, 6.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(),
-            std::string(param.msl_name) +
-                R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f)))");
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_DualParam_VectorTest,
-                         testing::Values(MslImportData{"atan2", "atan2"},
-                                         MslImportData{"cross", "cross"},
-                                         MslImportData{"distance", "distance"},
-                                         MslImportData{"max", "fmax"},
-                                         MslImportData{"min", "fmin"},
-                                         MslImportData{"pow", "pow"},
-                                         MslImportData{"reflect", "reflect"},
-                                         MslImportData{"step", "step"}));
-
-using MslImportData_DualParam_Int_Test = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_DualParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1, 2)");
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_DualParam_Int_Test,
-                         testing::Values(MslImportData{"max", "max"},
-                                         MslImportData{"min", "min"}));
-
-using MslImportData_TripleParam_ScalarTest = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_TripleParam_ScalarTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1.0f, 2.0f, 3.0f)");
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_TripleParam_ScalarTest,
-                         testing::Values(MslImportData{"fma", "fma"},
-                                         MslImportData{"mix", "mix"},
-                                         MslImportData{"clamp", "clamp"},
-                                         MslImportData{"smoothStep",
-                                                       "smoothstep"}));
-
-using MslImportData_TripleParam_VectorTest = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_TripleParam_VectorTest, Float) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, vec3<f32>(1.f, 2.f, 3.f),
-                    vec3<f32>(4.f, 5.f, 6.f), vec3<f32>(7.f, 8.f, 9.f));
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(
-      out.str(),
-      std::string(param.msl_name) +
-          R"((float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))");
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslImportData_TripleParam_VectorTest,
-    testing::Values(MslImportData{"faceForward", "faceforward"},
-                    MslImportData{"fma", "fma"},
-                    MslImportData{"clamp", "clamp"},
-                    MslImportData{"smoothStep", "smoothstep"}));
-
-using MslImportData_TripleParam_Int_Test = TestParamHelper<MslImportData>;
-TEST_P(MslImportData_TripleParam_Int_Test, IntScalar) {
-  auto param = GetParam();
-
-  auto* expr = Call(param.name, 1, 2, 3);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.msl_name) + "(1, 2, 3)");
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslImportData_TripleParam_Int_Test,
-                         testing::Values(MslImportData{"clamp", "clamp"},
-                                         MslImportData{"clamp", "clamp"}));
-
-TEST_F(MslGeneratorImplTest, MslImportData_Determinant) {
-  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = Call("determinant", "var");
-
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), std::string("determinant(var)"));
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_loop_test.cc b/src/writer/msl/generator_impl_loop_test.cc
deleted file mode 100644
index de0603f..0000000
--- a/src/writer/msl/generator_impl_loop_test.cc
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2020 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/ast/variable_decl_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Loop) {
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block();
-  auto* l = Loop(body, continuing);
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    discard_fragment();
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* l = Loop(body, continuing);
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    discard_fragment();
-    {
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  Global("lhs", ty.f32(), ast::StorageClass::kPrivate);
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* inner = Loop(body, continuing);
-
-  body = Block(inner);
-
-  continuing = Block(Assign("lhs", "rhs"));
-
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    while (true) {
-      discard_fragment();
-      {
-        a_statement();
-      }
-    }
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_LoopWithVarUsedInContinuing) {
-  // loop {
-  //   var lhs : f32 = 2.4;
-  //   var other : f32;
-  //   continuing {
-  //     lhs = rhs
-  //   }
-  // }
-  //
-
-  Global("rhs", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.4f))),  //
-                     Decl(Var("other", ty.f32())),            //
-                     Break());
-
-  auto* continuing = Block(Assign("lhs", "rhs"));
-  auto* outer = Loop(body, continuing);
-  WrapInFunction(outer);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    float lhs = 2.400000095f;
-    float other = 0.0f;
-    break;
-    {
-      lhs = rhs;
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoop) {
-  // for(; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, nullptr, nullptr,  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(; ; ) {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInit) {
-  // for(var i : i32; ; ) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr,  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(int i = 0; ; ) {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
-  // fn f(i : i32) {}
-  //
-  // var<workgroup> a : atomic<i32>;
-  // for({f(1); f(2);}; ; ) {
-  //   return;
-  // }
-
-  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
-  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
-
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt = Block(f(1), f(2));
-  auto* loop = For(multi_stmt, nullptr, nullptr,  //
-                   Block(Return()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    {
-      f(1);
-      f(2);
-    }
-    for(; ; ) {
-      return;
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCond) {
-  // for(; true; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, true, nullptr,  //
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(; true; ) {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCont) {
-  // for(; ; i = i + 1) {
-  //   return;
-  // }
-
-  auto* v = Decl(Var("i", ty.i32()));
-  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)),  //
-                Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(
-      gen.result(),
-      R"(  for(; ; i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)))) {
-    return;
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtCont) {
-  // fn f(i : i32) {}
-  //
-  // var<workgroup> a : atomic<i32>;
-  // for(; ; { f(1); f(2); }) {
-  //   return;
-  // }
-
-  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
-  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
-
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt = Block(f(1), f(2));
-  auto* loop = For(nullptr, nullptr, multi_stmt,  //
-                   Block(Return()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  while (true) {
-    return;
-    {
-      f(1);
-      f(2);
-    }
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInitCondCont) {
-  // for(var i : i32; true; i = i + 1) {
-  //   return;
-  // }
-
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
-                Block(CallStmt(Call("a_statement"))));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(
-      gen.result(),
-      R"(  for(int i = 0; true; i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)))) {
-    a_statement();
-  }
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInitCondCont) {
-  // fn f(i : i32) {}
-  //
-  // var<workgroup> a : atomic<i32>;
-  // for({ f(1); f(2); }; true; { f(3); f(4); }) {
-  //   return;
-  // }
-
-  Func("f", {Param("i", ty.i32())}, ty.void_(), {});
-  auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
-
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt_a = Block(f(1), f(2));
-  auto* multi_stmt_b = Block(f(3), f(4));
-  auto* loop = For(multi_stmt_a, Expr(true), multi_stmt_b,  //
-                   Block(Return()));
-  WrapInFunction(loop);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    {
-      f(1);
-      f(2);
-    }
-    while (true) {
-      if (!(true)) { break; }
-      return;
-      {
-        f(3);
-        f(4);
-      }
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_member_accessor_test.cc b/src/writer/msl/generator_impl_member_accessor_test.cc
deleted file mode 100644
index b511cab..0000000
--- a/src/writer/msl/generator_impl_member_accessor_test.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor) {
-  Global("str", ty.Of(Structure("my_str", {Member("mem", ty.f32())})),
-         ast::StorageClass::kPrivate);
-  auto* expr = MemberAccessor("str", "mem");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "str.mem");
-}
-
-TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor_Swizzle_xyz) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = MemberAccessor("my_vec", "xyz");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "float4(my_vec).xyz");
-}
-
-TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor_Swizzle_gbr) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-
-  auto* expr = MemberAccessor("my_vec", "gbr");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "float4(my_vec).gbr");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_module_constant_test.cc b/src/writer/msl/generator_impl_module_constant_test.cc
deleted file mode 100644
index 0d2a7eb..0000000
--- a/src/writer/msl/generator_impl_module_constant_test.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2020 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/ast/id_attribute.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_ModuleConstant) {
-  auto* var =
-      GlobalConst("pos", ty.array<f32, 3>(), array<f32, 3>(1.f, 2.f, 3.f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), "constant float pos[3] = {1.0f, 2.0f, 3.0f};\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_SpecConstant) {
-  auto* var = Override("pos", ty.f32(), Expr(3.f),
-                       ast::AttributeList{
-                           Id(23),
-                       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
-  EXPECT_EQ(gen.result(), "constant float pos [[function_constant(23)]];\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_SpecConstant_NoId) {
-  auto* var_a = Override("a", ty.f32(), nullptr,
-                         ast::AttributeList{
-                             Id(0),
-                         });
-  auto* var_b = Override("b", ty.f32(), nullptr);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var_a)) << gen.error();
-  ASSERT_TRUE(gen.EmitProgramConstVariable(var_b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(constant float a [[function_constant(0)]];
-constant float b [[function_constant(1)]];
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_return_test.cc b/src/writer/msl/generator_impl_return_test.cc
deleted file mode 100644
index 50a53e6..0000000
--- a/src/writer/msl/generator_impl_return_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Return) {
-  auto* r = Return();
-  WrapInFunction(r);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return;\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_ReturnWithValue) {
-  auto* r = Return(123);
-  Func("f", {}, ty.i32(), {r});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return 123;\n");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_sanitizer_test.cc b/src/writer/msl/generator_impl_sanitizer_test.cc
deleted file mode 100644
index ea11591..0000000
--- a/src/writer/msl/generator_impl_sanitizer_test.cc
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using MslSanitizerTest = TestHelper;
-
-TEST_F(MslSanitizerTest, Call_ArrayLength) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol {
-  /* 0x0000 */ uint4 buffer_size[1];
-};
-
-struct my_struct {
-  float a[1];
-};
-
-fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
-  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 0u) / 4u);
-  return;
-}
-
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(MslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = Structure("my_struct",
-                      {
-                          Member(0, "z", ty.f32()),
-                          Member(4, "a", ty.array<f32>(4)),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol {
-  /* 0x0000 */ uint4 buffer_size[1];
-};
-
-struct my_struct {
-  float z;
-  float a[1];
-};
-
-fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
-  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 4u) / 4u);
-  return;
-}
-
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(MslSanitizerTest, Call_ArrayLength_ViaLets) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  auto* p = Const("p", nullptr, AddressOf("b"));
-  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(p),
-           Decl(p2),
-           Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                    Call("arrayLength", p2))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol {
-  /* 0x0000 */ uint4 buffer_size[1];
-};
-
-struct my_struct {
-  float a[1];
-};
-
-fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(30)]]) {
-  uint len = (((*(tint_symbol_2)).buffer_size[0u][1u] - 0u) / 4u);
-  return;
-}
-
-)";
-
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(0),
-         });
-  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(2),
-             create<ast::GroupAttribute>(0),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var(
-               "len", ty.u32(), ast::StorageClass::kNone,
-               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
-                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Options options;
-  options.array_length_from_uniform.ubo_binding = {0, 29};
-  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
-      sem::BindingPoint{0, 1}, 7u);
-  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
-      sem::BindingPoint{0, 2}, 2u);
-  GeneratorImpl& gen = SanitizeAndBuild(options);
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-
-  auto got = gen.result();
-  auto* expect = R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol {
-  /* 0x0000 */ uint4 buffer_size[2];
-};
-
-struct my_struct {
-  float a[1];
-};
-
-fragment void a_func(const constant tint_symbol* tint_symbol_2 [[buffer(29)]]) {
-  uint len = ((((*(tint_symbol_2)).buffer_size[1u][3u] - 0u) / 4u) + (((*(tint_symbol_2)).buffer_size[0u][2u] - 0u) / 4u));
-  return;
-}
-
-)";
-  EXPECT_EQ(expect, got);
-}
-
-TEST_F(MslSanitizerTest,
-       Call_ArrayLength_ArrayLengthFromUniformMissingBinding) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(0),
-         });
-  Global("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(2),
-             create<ast::GroupAttribute>(0),
-         });
-
-  Func("a_func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var(
-               "len", ty.u32(), ast::StorageClass::kNone,
-               Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
-                   Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  Options options;
-  options.array_length_from_uniform.ubo_binding = {0, 29};
-  options.array_length_from_uniform.bindpoint_to_size_index.emplace(
-      sem::BindingPoint{0, 2}, 2u);
-  GeneratorImpl& gen = SanitizeAndBuild(options);
-
-  ASSERT_FALSE(gen.Generate());
-  EXPECT_THAT(gen.error(),
-              HasSubstr("Unable to translate builtin: arrayLength"));
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_switch_test.cc b/src/writer/msl/generator_impl_switch_test.cc
deleted file mode 100644
index c6f5672..0000000
--- a/src/writer/msl/generator_impl_switch_test.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_Switch) {
-  auto* cond = Var("cond", ty.i32());
-
-  auto* def_body = Block(create<ast::BreakStatement>());
-  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
-
-  ast::CaseSelectorList case_val;
-  case_val.push_back(Expr(5));
-
-  auto* case_body = Block(create<ast::BreakStatement>());
-
-  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
-
-  ast::CaseStatementList body;
-  body.push_back(case_stmt);
-  body.push_back(def);
-
-  auto* s = create<ast::SwitchStatement>(Expr(cond), body);
-  WrapInFunction(cond, s);
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  switch(cond) {
-    case 5: {
-      break;
-    }
-    default: {
-      break;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
deleted file mode 100644
index 6e066bb..0000000
--- a/src/writer/msl/generator_impl_test.cc
+++ /dev/null
@@ -1,417 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Generate) {
-  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-kernel void my_func() {
-  return;
-}
-
-)");
-}
-
-struct MslBuiltinData {
-  ast::Builtin builtin;
-  const char* attribute_name;
-};
-inline std::ostream& operator<<(std::ostream& out, MslBuiltinData data) {
-  out << data.builtin;
-  return out;
-}
-using MslBuiltinConversionTest = TestParamHelper<MslBuiltinData>;
-TEST_P(MslBuiltinConversionTest, Emit) {
-  auto params = GetParam();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(gen.builtin_to_attribute(params.builtin),
-            std::string(params.attribute_name));
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslBuiltinConversionTest,
-    testing::Values(MslBuiltinData{ast::Builtin::kPosition, "position"},
-                    MslBuiltinData{ast::Builtin::kVertexIndex, "vertex_id"},
-                    MslBuiltinData{ast::Builtin::kInstanceIndex, "instance_id"},
-                    MslBuiltinData{ast::Builtin::kFrontFacing, "front_facing"},
-                    MslBuiltinData{ast::Builtin::kFragDepth, "depth(any)"},
-                    MslBuiltinData{ast::Builtin::kLocalInvocationId,
-                                   "thread_position_in_threadgroup"},
-                    MslBuiltinData{ast::Builtin::kLocalInvocationIndex,
-                                   "thread_index_in_threadgroup"},
-                    MslBuiltinData{ast::Builtin::kGlobalInvocationId,
-                                   "thread_position_in_grid"},
-                    MslBuiltinData{ast::Builtin::kWorkgroupId,
-                                   "threadgroup_position_in_grid"},
-                    MslBuiltinData{ast::Builtin::kNumWorkgroups,
-                                   "threadgroups_per_grid"},
-                    MslBuiltinData{ast::Builtin::kSampleIndex, "sample_id"},
-                    MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"},
-                    MslBuiltinData{ast::Builtin::kPointSize, "point_size"}));
-
-TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
-  auto* out = Structure(
-      "Out", {Member("pos", ty.vec4<f32>(),
-                     {Builtin(ast::Builtin::kPosition), Invariant()})});
-  Func("vert_main", ast::VariableList{}, ty.Of(out),
-       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_TRUE(gen.HasInvariant());
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-
-#if __METAL_VERSION__ >= 210
-#define TINT_INVARIANT [[invariant]]
-#else
-#define TINT_INVARIANT
-#endif
-
-struct Out {
-  float4 pos [[position]] TINT_INVARIANT;
-};
-
-vertex Out vert_main() {
-  return {};
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, HasInvariantAttribute_False) {
-  auto* out = Structure("Out", {Member("pos", ty.vec4<f32>(),
-                                       {Builtin(ast::Builtin::kPosition)})});
-  Func("vert_main", ast::VariableList{}, ty.Of(out),
-       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_FALSE(gen.HasInvariant());
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct Out {
-  float4 pos [[position]];
-};
-
-vertex Out vert_main() {
-  return {};
-}
-
-)");
-}
-
-TEST_F(MslGeneratorImplTest, WorkgroupMatrix) {
-  Global("m", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
-  Func("comp_main", ast::VariableList{}, ty.void_(),
-       {Decl(Const("x", nullptr, Expr("m")))},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol_3 {
-  float2x2 m;
-};
-
-void comp_main_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol) {
-  {
-    *(tint_symbol) = float2x2();
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  float2x2 const x = *(tint_symbol);
-}
-
-kernel void comp_main(threadgroup tint_symbol_3* tint_symbol_2 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
-  threadgroup float2x2* const tint_symbol_1 = &((*(tint_symbol_2)).m);
-  comp_main_inner(local_invocation_index, tint_symbol_1);
-  return;
-}
-
-)");
-
-  auto allocations = gen.DynamicWorkgroupAllocations();
-  ASSERT_TRUE(allocations.count("comp_main"));
-  ASSERT_EQ(allocations["comp_main"].size(), 1u);
-  EXPECT_EQ(allocations["comp_main"][0], 2u * 2u * sizeof(float));
-}
-
-TEST_F(MslGeneratorImplTest, WorkgroupMatrixInArray) {
-  Global("m", ty.array(ty.mat2x2<f32>(), 4), ast::StorageClass::kWorkgroup);
-  Func("comp_main", ast::VariableList{}, ty.void_(),
-       {Decl(Const("x", nullptr, Expr("m")))},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_array_wrapper {
-  float2x2 arr[4];
-};
-
-struct tint_symbol_3 {
-  tint_array_wrapper m;
-};
-
-void comp_main_inner(uint local_invocation_index, threadgroup tint_array_wrapper* const tint_symbol) {
-  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
-    uint const i = idx;
-    (*(tint_symbol)).arr[i] = float2x2();
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  tint_array_wrapper const x = *(tint_symbol);
-}
-
-kernel void comp_main(threadgroup tint_symbol_3* tint_symbol_2 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
-  threadgroup tint_array_wrapper* const tint_symbol_1 = &((*(tint_symbol_2)).m);
-  comp_main_inner(local_invocation_index, tint_symbol_1);
-  return;
-}
-
-)");
-
-  auto allocations = gen.DynamicWorkgroupAllocations();
-  ASSERT_TRUE(allocations.count("comp_main"));
-  ASSERT_EQ(allocations["comp_main"].size(), 1u);
-  EXPECT_EQ(allocations["comp_main"][0], 4u * 2u * 2u * sizeof(float));
-}
-
-TEST_F(MslGeneratorImplTest, WorkgroupMatrixInStruct) {
-  Structure("S1", {
-                      Member("m1", ty.mat2x2<f32>()),
-                      Member("m2", ty.mat4x4<f32>()),
-                  });
-  Structure("S2", {
-                      Member("s", ty.type_name("S1")),
-                  });
-  Global("s", ty.type_name("S2"), ast::StorageClass::kWorkgroup);
-  Func("comp_main", ast::VariableList{}, ty.void_(),
-       {Decl(Const("x", nullptr, Expr("s")))},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct S1 {
-  float2x2 m1;
-  float4x4 m2;
-};
-
-struct S2 {
-  S1 s;
-};
-
-struct tint_symbol_4 {
-  S2 s;
-};
-
-void comp_main_inner(uint local_invocation_index, threadgroup S2* const tint_symbol_1) {
-  {
-    S2 const tint_symbol = {};
-    *(tint_symbol_1) = tint_symbol;
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  S2 const x = *(tint_symbol_1);
-}
-
-kernel void comp_main(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
-  threadgroup S2* const tint_symbol_2 = &((*(tint_symbol_3)).s);
-  comp_main_inner(local_invocation_index, tint_symbol_2);
-  return;
-}
-
-)");
-
-  auto allocations = gen.DynamicWorkgroupAllocations();
-  ASSERT_TRUE(allocations.count("comp_main"));
-  ASSERT_EQ(allocations["comp_main"].size(), 1u);
-  EXPECT_EQ(allocations["comp_main"][0],
-            (2 * 2 * sizeof(float)) + (4u * 4u * sizeof(float)));
-}
-
-TEST_F(MslGeneratorImplTest, WorkgroupMatrix_Multiples) {
-  Global("m1", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m2", ty.mat2x3<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m3", ty.mat2x4<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m4", ty.mat3x2<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m5", ty.mat3x3<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m6", ty.mat3x4<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m7", ty.mat4x2<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m8", ty.mat4x3<f32>(), ast::StorageClass::kWorkgroup);
-  Global("m9", ty.mat4x4<f32>(), ast::StorageClass::kWorkgroup);
-  Func("main1", ast::VariableList{}, ty.void_(),
-       {
-           Decl(Const("a1", nullptr, Expr("m1"))),
-           Decl(Const("a2", nullptr, Expr("m2"))),
-           Decl(Const("a3", nullptr, Expr("m3"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  Func("main2", ast::VariableList{}, ty.void_(),
-       {
-           Decl(Const("a1", nullptr, Expr("m4"))),
-           Decl(Const("a2", nullptr, Expr("m5"))),
-           Decl(Const("a3", nullptr, Expr("m6"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  Func("main3", ast::VariableList{}, ty.void_(),
-       {
-           Decl(Const("a1", nullptr, Expr("m7"))),
-           Decl(Const("a2", nullptr, Expr("m8"))),
-           Decl(Const("a3", nullptr, Expr("m9"))),
-       },
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-  Func("main4_no_usages", ast::VariableList{}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
-
-using namespace metal;
-struct tint_symbol_7 {
-  float2x2 m1;
-  float2x3 m2;
-  float2x4 m3;
-};
-
-struct tint_symbol_15 {
-  float3x2 m4;
-  float3x3 m5;
-  float3x4 m6;
-};
-
-struct tint_symbol_23 {
-  float4x2 m7;
-  float4x3 m8;
-  float4x4 m9;
-};
-
-void main1_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol, threadgroup float2x3* const tint_symbol_1, threadgroup float2x4* const tint_symbol_2) {
-  {
-    *(tint_symbol) = float2x2();
-    *(tint_symbol_1) = float2x3();
-    *(tint_symbol_2) = float2x4();
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  float2x2 const a1 = *(tint_symbol);
-  float2x3 const a2 = *(tint_symbol_1);
-  float2x4 const a3 = *(tint_symbol_2);
-}
-
-kernel void main1(threadgroup tint_symbol_7* tint_symbol_4 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
-  threadgroup float2x2* const tint_symbol_3 = &((*(tint_symbol_4)).m1);
-  threadgroup float2x3* const tint_symbol_5 = &((*(tint_symbol_4)).m2);
-  threadgroup float2x4* const tint_symbol_6 = &((*(tint_symbol_4)).m3);
-  main1_inner(local_invocation_index, tint_symbol_3, tint_symbol_5, tint_symbol_6);
-  return;
-}
-
-void main2_inner(uint local_invocation_index_1, threadgroup float3x2* const tint_symbol_8, threadgroup float3x3* const tint_symbol_9, threadgroup float3x4* const tint_symbol_10) {
-  {
-    *(tint_symbol_8) = float3x2();
-    *(tint_symbol_9) = float3x3();
-    *(tint_symbol_10) = float3x4();
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  float3x2 const a1 = *(tint_symbol_8);
-  float3x3 const a2 = *(tint_symbol_9);
-  float3x4 const a3 = *(tint_symbol_10);
-}
-
-kernel void main2(threadgroup tint_symbol_15* tint_symbol_12 [[threadgroup(0)]], uint local_invocation_index_1 [[thread_index_in_threadgroup]]) {
-  threadgroup float3x2* const tint_symbol_11 = &((*(tint_symbol_12)).m4);
-  threadgroup float3x3* const tint_symbol_13 = &((*(tint_symbol_12)).m5);
-  threadgroup float3x4* const tint_symbol_14 = &((*(tint_symbol_12)).m6);
-  main2_inner(local_invocation_index_1, tint_symbol_11, tint_symbol_13, tint_symbol_14);
-  return;
-}
-
-void main3_inner(uint local_invocation_index_2, threadgroup float4x2* const tint_symbol_16, threadgroup float4x3* const tint_symbol_17, threadgroup float4x4* const tint_symbol_18) {
-  {
-    *(tint_symbol_16) = float4x2();
-    *(tint_symbol_17) = float4x3();
-    *(tint_symbol_18) = float4x4();
-  }
-  threadgroup_barrier(mem_flags::mem_threadgroup);
-  float4x2 const a1 = *(tint_symbol_16);
-  float4x3 const a2 = *(tint_symbol_17);
-  float4x4 const a3 = *(tint_symbol_18);
-}
-
-kernel void main3(threadgroup tint_symbol_23* tint_symbol_20 [[threadgroup(0)]], uint local_invocation_index_2 [[thread_index_in_threadgroup]]) {
-  threadgroup float4x2* const tint_symbol_19 = &((*(tint_symbol_20)).m7);
-  threadgroup float4x3* const tint_symbol_21 = &((*(tint_symbol_20)).m8);
-  threadgroup float4x4* const tint_symbol_22 = &((*(tint_symbol_20)).m9);
-  main3_inner(local_invocation_index_2, tint_symbol_19, tint_symbol_21, tint_symbol_22);
-  return;
-}
-
-kernel void main4_no_usages() {
-  return;
-}
-
-)");
-
-  auto allocations = gen.DynamicWorkgroupAllocations();
-  ASSERT_TRUE(allocations.count("main1"));
-  ASSERT_TRUE(allocations.count("main2"));
-  ASSERT_TRUE(allocations.count("main3"));
-  EXPECT_EQ(allocations.count("main4_no_usages"), 0u);
-  ASSERT_EQ(allocations["main1"].size(), 1u);
-  EXPECT_EQ(allocations["main1"][0], 20u * sizeof(float));
-  ASSERT_EQ(allocations["main2"].size(), 1u);
-  EXPECT_EQ(allocations["main2"][0], 32u * sizeof(float));
-  ASSERT_EQ(allocations["main3"].size(), 1u);
-  EXPECT_EQ(allocations["main3"][0], 40u * sizeof(float));
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
deleted file mode 100644
index f9a56f0..0000000
--- a/src/writer/msl/generator_impl_type_test.cc
+++ /dev/null
@@ -1,883 +0,0 @@
-// Copyright 2020 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 <array>
-
-#include "gmock/gmock.h"
-
-#include "src/ast/struct_block_attribute.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/sampler_type.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using ::testing::HasSubstr;
-
-#define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN)    \
-  static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \
-  static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")
-
-// Declare C++ types that match the size and alignment of the types of the same
-// name in MSL.
-#define DECLARE_TYPE(NAME, SIZE, ALIGN) \
-  struct alignas(ALIGN) NAME {          \
-    uint8_t _[SIZE];                    \
-  };                                    \
-  CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)
-
-// Size and alignments taken from the MSL spec:
-// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-DECLARE_TYPE(float2, 8, 8);
-DECLARE_TYPE(packed_float3, 12, 4);
-DECLARE_TYPE(float4, 16, 16);
-DECLARE_TYPE(float2x2, 16, 8);
-DECLARE_TYPE(float2x3, 32, 16);
-DECLARE_TYPE(float2x4, 32, 16);
-DECLARE_TYPE(float3x2, 24, 8);
-DECLARE_TYPE(float3x3, 48, 16);
-DECLARE_TYPE(float3x4, 48, 16);
-DECLARE_TYPE(float4x2, 32, 8);
-DECLARE_TYPE(float4x3, 64, 16);
-DECLARE_TYPE(float4x4, 64, 16);
-using uint = unsigned int;
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitType_Array) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[4]");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArray) {
-  auto* a = ty.array<bool, 4>();
-  auto* b = ty.array(a, 5);
-  Global("G", b, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(b), "ary")) << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[5][4]");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
-  auto* a = ty.array<bool, 4>();
-  auto* b = ty.array(a, 5);
-  auto* c = ty.array(b, 6);
-  Global("G", c, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(c), "ary")) << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
-  auto* arr = ty.array<bool, 4>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "")) << gen.error();
-  EXPECT_EQ(out.str(), "bool[4]");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
-  auto* arr = ty.array<bool, 1>();
-  Global("G", arr, ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
-  EXPECT_EQ(out.str(), "bool ary[1]");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_ArrayWithStride) {
-  auto* s = Structure("s", {Member("arr", ty.array<f32, 4>(64))},
-                      {create<ast::StructBlockAttribute>()});
-  auto* ubo = Global("ubo", ty.Of(s), ast::StorageClass::kUniform,
-                     ast::AttributeList{
-                         create<ast::GroupAttribute>(0),
-                         create<ast::BindingAttribute>(1),
-                     });
-  WrapInFunction(MemberAccessor(ubo, "arr"));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr(R"(struct tint_padded_array_element {
-  /* 0x0000 */ float el;
-  /* 0x0004 */ int8_t tint_pad[60];
-};)"));
-  EXPECT_THAT(gen.result(), HasSubstr(R"(struct tint_array_wrapper {
-  /* 0x0000 */ tint_padded_array_element arr[4];
-};)"));
-  EXPECT_THAT(gen.result(), HasSubstr(R"(struct s {
-  /* 0x0000 */ tint_array_wrapper arr;
-};)"));
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Bool) {
-  auto* bool_ = create<sem::Bool>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, bool_, "")) << gen.error();
-  EXPECT_EQ(out.str(), "bool");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_F32) {
-  auto* f32 = create<sem::F32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, f32, "")) << gen.error();
-  EXPECT_EQ(out.str(), "float");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_I32) {
-  auto* i32 = create<sem::I32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, i32, "")) << gen.error();
-  EXPECT_EQ(out.str(), "int");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Matrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, mat2x3, "")) << gen.error();
-  EXPECT_EQ(out.str(), "float2x3");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Pointer) {
-  auto* f32 = create<sem::F32>();
-  auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
-                                 ast::Access::kReadWrite);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, p, "")) << gen.error();
-  EXPECT_EQ(out.str(), "threadgroup float* ");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
-  EXPECT_EQ(out.str(), "S");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  int a;
-  float b;
-};
-)");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-  auto* s =
-      Structure("S",
-                {
-                    Member("a", ty.i32(), {MemberSize(32)}),
-                    Member("b", ty.f32(), {MemberAlign(128), MemberSize(128)}),
-                    Member("c", ty.vec2<f32>()),
-                    Member("d", ty.u32()),
-                    Member("e", ty.vec3<f32>()),
-                    Member("f", ty.u32()),
-                    Member("g", ty.vec4<f32>()),
-                    Member("h", ty.u32()),
-                    Member("i", ty.mat2x2<f32>()),
-                    Member("j", ty.u32()),
-                    Member("k", ty.mat2x3<f32>()),
-                    Member("l", ty.u32()),
-                    Member("m", ty.mat2x4<f32>()),
-                    Member("n", ty.u32()),
-                    Member("o", ty.mat3x2<f32>()),
-                    Member("p", ty.u32()),
-                    Member("q", ty.mat3x3<f32>()),
-                    Member("r", ty.u32()),
-                    Member("s", ty.mat3x4<f32>()),
-                    Member("t", ty.u32()),
-                    Member("u", ty.mat4x2<f32>()),
-                    Member("v", ty.u32()),
-                    Member("w", ty.mat4x3<f32>()),
-                    Member("x", ty.u32()),
-                    Member("y", ty.mat4x4<f32>()),
-                    Member("z", ty.f32()),
-                },
-                {create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-
-  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
-  // for each field of the structure s.
-#define ALL_FIELDS()                             \
-  FIELD(0x0000, int, a, /*NO SUFFIX*/)           \
-  FIELD(0x0004, int8_t, tint_pad, [124])         \
-  FIELD(0x0080, float, b, /*NO SUFFIX*/)         \
-  FIELD(0x0084, int8_t, tint_pad_1, [124])       \
-  FIELD(0x0100, float2, c, /*NO SUFFIX*/)        \
-  FIELD(0x0108, uint, d, /*NO SUFFIX*/)          \
-  FIELD(0x010c, int8_t, tint_pad_2, [4])         \
-  FIELD(0x0110, packed_float3, e, /*NO SUFFIX*/) \
-  FIELD(0x011c, uint, f, /*NO SUFFIX*/)          \
-  FIELD(0x0120, float4, g, /*NO SUFFIX*/)        \
-  FIELD(0x0130, uint, h, /*NO SUFFIX*/)          \
-  FIELD(0x0134, int8_t, tint_pad_3, [4])         \
-  FIELD(0x0138, float2x2, i, /*NO SUFFIX*/)      \
-  FIELD(0x0148, uint, j, /*NO SUFFIX*/)          \
-  FIELD(0x014c, int8_t, tint_pad_4, [4])         \
-  FIELD(0x0150, float2x3, k, /*NO SUFFIX*/)      \
-  FIELD(0x0170, uint, l, /*NO SUFFIX*/)          \
-  FIELD(0x0174, int8_t, tint_pad_5, [12])        \
-  FIELD(0x0180, float2x4, m, /*NO SUFFIX*/)      \
-  FIELD(0x01a0, uint, n, /*NO SUFFIX*/)          \
-  FIELD(0x01a4, int8_t, tint_pad_6, [4])         \
-  FIELD(0x01a8, float3x2, o, /*NO SUFFIX*/)      \
-  FIELD(0x01c0, uint, p, /*NO SUFFIX*/)          \
-  FIELD(0x01c4, int8_t, tint_pad_7, [12])        \
-  FIELD(0x01d0, float3x3, q, /*NO SUFFIX*/)      \
-  FIELD(0x0200, uint, r, /*NO SUFFIX*/)          \
-  FIELD(0x0204, int8_t, tint_pad_8, [12])        \
-  FIELD(0x0210, float3x4, s, /*NO SUFFIX*/)      \
-  FIELD(0x0240, uint, t, /*NO SUFFIX*/)          \
-  FIELD(0x0244, int8_t, tint_pad_9, [4])         \
-  FIELD(0x0248, float4x2, u, /*NO SUFFIX*/)      \
-  FIELD(0x0268, uint, v, /*NO SUFFIX*/)          \
-  FIELD(0x026c, int8_t, tint_pad_10, [4])        \
-  FIELD(0x0270, float4x3, w, /*NO SUFFIX*/)      \
-  FIELD(0x02b0, uint, x, /*NO SUFFIX*/)          \
-  FIELD(0x02b4, int8_t, tint_pad_11, [12])       \
-  FIELD(0x02c0, float4x4, y, /*NO SUFFIX*/)      \
-  FIELD(0x0300, float, z, /*NO SUFFIX*/)         \
-  FIELD(0x0304, int8_t, tint_pad_12, [124])
-
-  // Check that the generated string is as expected.
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
-  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
-#undef FIELD
-  EXPECT_EQ(buf.String(), expect);
-
-  // 1.4 Metal and C++14
-  // The Metal programming language is a C++14-based Specification with
-  // extensions and restrictions. Refer to the C++14 Specification (also known
-  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-  // description of the language grammar.
-  //
-  // Tint is written in C++14, so use the compiler to verify the generated
-  // layout is as expected for C++14 / MSL.
-  {
-    struct S {
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
-      ALL_FIELDS()
-#undef FIELD
-    };
-
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-    ALL_FIELDS()
-#undef FIELD
-  }
-#undef ALL_FIELDS
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
-  // inner_x: size(1024), align(512)
-  auto* inner_x =
-      Structure("inner_x", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32(), {MemberAlign(512)}),
-                           });
-
-  // inner_y: size(516), align(4)
-  auto* inner_y =
-      Structure("inner_y", {
-                               Member("a", ty.i32(), {MemberSize(512)}),
-                               Member("b", ty.f32()),
-                           });
-
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.Of(inner_x)),
-                          Member("c", ty.f32()),
-                          Member("d", ty.Of(inner_y)),
-                          Member("e", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-
-  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
-  // for each field of the structure s.
-#define ALL_FIELDS()                       \
-  FIELD(0x0000, int, a, /*NO SUFFIX*/)     \
-  FIELD(0x0004, int8_t, tint_pad, [508])   \
-  FIELD(0x0200, inner_x, b, /*NO SUFFIX*/) \
-  FIELD(0x0600, float, c, /*NO SUFFIX*/)   \
-  FIELD(0x0604, inner_y, d, /*NO SUFFIX*/) \
-  FIELD(0x0808, float, e, /*NO SUFFIX*/)   \
-  FIELD(0x080c, int8_t, tint_pad_1, [500])
-
-  // Check that the generated string is as expected.
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
-  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
-#undef FIELD
-  EXPECT_EQ(buf.String(), expect);
-
-  // 1.4 Metal and C++14
-  // The Metal programming language is a C++14-based Specification with
-  // extensions and restrictions. Refer to the C++14 Specification (also known
-  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-  // description of the language grammar.
-  //
-  // Tint is written in C++14, so use the compiler to verify the generated
-  // layout is as expected for C++14 / MSL.
-  {
-    struct inner_x {
-      uint32_t a;
-      alignas(512) float b;
-    };
-    CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);
-
-    struct inner_y {
-      uint32_t a[128];
-      float b;
-    };
-    CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);
-
-    struct S {
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
-      ALL_FIELDS()
-#undef FIELD
-    };
-
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-    ALL_FIELDS()
-#undef FIELD
-  }
-
-#undef ALL_FIELDS
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
-  // inner: size(1024), align(512)
-  auto* inner =
-      Structure("inner", {
-                             Member("a", ty.i32()),
-                             Member("b", ty.f32(), {MemberAlign(512)}),
-                         });
-
-  // array_x: size(28), align(4)
-  auto* array_x = ty.array<f32, 7>();
-
-  // array_y: size(4096), align(512)
-  auto* array_y = ty.array(ty.Of(inner), 4);
-
-  // array_z: size(4), align(4)
-  auto* array_z = ty.array<f32>();
-
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", array_x),
-                          Member("c", ty.f32()),
-                          Member("d", array_y),
-                          Member("e", ty.f32()),
-                          Member("f", array_z),
-                      },
-                      ast::AttributeList{create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-
-  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
-  // for each field of the structure s.
-#define ALL_FIELDS()                     \
-  FIELD(0x0000, int, a, /*NO SUFFIX*/)   \
-  FIELD(0x0004, float, b, [7])           \
-  FIELD(0x0020, float, c, /*NO SUFFIX*/) \
-  FIELD(0x0024, int8_t, tint_pad, [476]) \
-  FIELD(0x0200, inner, d, [4])           \
-  FIELD(0x1200, float, e, /*NO SUFFIX*/) \
-  FIELD(0x1204, float, f, [1])           \
-  FIELD(0x1208, int8_t, tint_pad_1, [504])
-
-  // Check that the generated string is as expected.
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
-  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
-#undef FIELD
-  EXPECT_EQ(buf.String(), expect);
-
-  // 1.4 Metal and C++14
-  // The Metal programming language is a C++14-based Specification with
-  // extensions and restrictions. Refer to the C++14 Specification (also known
-  // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-  // description of the language grammar.
-  //
-  // Tint is written in C++14, so use the compiler to verify the generated
-  // layout is as expected for C++14 / MSL.
-  {
-    struct inner {
-      uint32_t a;
-      alignas(512) float b;
-    };
-    CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
-
-    // array_x: size(28), align(4)
-    using array_x = std::array<float, 7>;
-    CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
-
-    // array_y: size(4096), align(512)
-    using array_y = std::array<inner, 4>;
-    CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
-
-    // array_z: size(4), align(4)
-    using array_z = std::array<float, 1>;
-    CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);
-
-    struct S {
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) TYPE NAME SUFFIX;
-      ALL_FIELDS()
-#undef FIELD
-    };
-
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-    ALL_FIELDS()
-#undef FIELD
-  }
-
-#undef ALL_FIELDS
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
-  // array: size(64), align(16)
-  auto* array = ty.array(ty.vec3<f32>(), 4);
-
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", array),
-                          Member("c", ty.i32()),
-                      },
-                      ast::AttributeList{create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-
-  // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
-  // for each field of the structure s.
-#define ALL_FIELDS()                    \
-  FIELD(0x0000, int, a, /*NO SUFFIX*/)  \
-  FIELD(0x0004, int8_t, tint_pad, [12]) \
-  FIELD(0x0010, float3, b, [4])         \
-  FIELD(0x0050, int, c, /*NO SUFFIX*/)  \
-  FIELD(0x0054, int8_t, tint_pad_1, [12])
-
-  // Check that the generated string is as expected.
-#define FIELD(ADDR, TYPE, NAME, SUFFIX) \
-  "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
-  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
-#undef FIELD
-  EXPECT_EQ(buf.String(), expect);
-}
-
-TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
-  auto* s = Structure(
-      "S",
-      {
-          // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
-          Member("tint_pad_2", ty.i32(), {MemberSize(32)}),
-          Member("tint_pad_20", ty.f32(), {MemberAlign(128), MemberSize(128)}),
-          Member("tint_pad_33", ty.vec2<f32>()),
-          Member("tint_pad_1", ty.u32()),
-          Member("tint_pad_3", ty.vec3<f32>()),
-          Member("tint_pad_7", ty.u32()),
-          Member("tint_pad_25", ty.vec4<f32>()),
-          Member("tint_pad_5", ty.u32()),
-          Member("tint_pad_27", ty.mat2x2<f32>()),
-          Member("tint_pad_24", ty.u32()),
-          Member("tint_pad_23", ty.mat2x3<f32>()),
-          Member("tint_pad", ty.u32()),
-          Member("tint_pad_8", ty.mat2x4<f32>()),
-          Member("tint_pad_26", ty.u32()),
-          Member("tint_pad_29", ty.mat3x2<f32>()),
-          Member("tint_pad_6", ty.u32()),
-          Member("tint_pad_22", ty.mat3x3<f32>()),
-          Member("tint_pad_32", ty.u32()),
-          Member("tint_pad_34", ty.mat3x4<f32>()),
-          Member("tint_pad_35", ty.u32()),
-          Member("tint_pad_30", ty.mat4x2<f32>()),
-          Member("tint_pad_9", ty.u32()),
-          Member("tint_pad_31", ty.mat4x3<f32>()),
-          Member("tint_pad_28", ty.u32()),
-          Member("tint_pad_4", ty.mat4x4<f32>()),
-          Member("tint_pad_21", ty.f32()),
-      },
-      {create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  TextGenerator::TextBuffer buf;
-  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
-  EXPECT_EQ(buf.String(), R"(struct S {
-  /* 0x0000 */ int tint_pad_2;
-  /* 0x0004 */ int8_t tint_pad_10[124];
-  /* 0x0080 */ float tint_pad_20;
-  /* 0x0084 */ int8_t tint_pad_11[124];
-  /* 0x0100 */ float2 tint_pad_33;
-  /* 0x0108 */ uint tint_pad_1;
-  /* 0x010c */ int8_t tint_pad_12[4];
-  /* 0x0110 */ packed_float3 tint_pad_3;
-  /* 0x011c */ uint tint_pad_7;
-  /* 0x0120 */ float4 tint_pad_25;
-  /* 0x0130 */ uint tint_pad_5;
-  /* 0x0134 */ int8_t tint_pad_13[4];
-  /* 0x0138 */ float2x2 tint_pad_27;
-  /* 0x0148 */ uint tint_pad_24;
-  /* 0x014c */ int8_t tint_pad_14[4];
-  /* 0x0150 */ float2x3 tint_pad_23;
-  /* 0x0170 */ uint tint_pad;
-  /* 0x0174 */ int8_t tint_pad_15[12];
-  /* 0x0180 */ float2x4 tint_pad_8;
-  /* 0x01a0 */ uint tint_pad_26;
-  /* 0x01a4 */ int8_t tint_pad_16[4];
-  /* 0x01a8 */ float3x2 tint_pad_29;
-  /* 0x01c0 */ uint tint_pad_6;
-  /* 0x01c4 */ int8_t tint_pad_17[12];
-  /* 0x01d0 */ float3x3 tint_pad_22;
-  /* 0x0200 */ uint tint_pad_32;
-  /* 0x0204 */ int8_t tint_pad_18[12];
-  /* 0x0210 */ float3x4 tint_pad_34;
-  /* 0x0240 */ uint tint_pad_35;
-  /* 0x0244 */ int8_t tint_pad_19[4];
-  /* 0x0248 */ float4x2 tint_pad_30;
-  /* 0x0268 */ uint tint_pad_9;
-  /* 0x026c */ int8_t tint_pad_36[4];
-  /* 0x0270 */ float4x3 tint_pad_31;
-  /* 0x02b0 */ uint tint_pad_28;
-  /* 0x02b4 */ int8_t tint_pad_37[12];
-  /* 0x02c0 */ float4x4 tint_pad_4;
-  /* 0x0300 */ float tint_pad_21;
-  /* 0x0304 */ int8_t tint_pad_38[124];
-};
-)");
-}
-
-// TODO(dsinclair): How to translate @block
-TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithAttribute) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
-  EXPECT_EQ(out.str(), R"(struct {
-  /* 0x0000 */ int a;
-  /* 0x0004 */ float b;
-})");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_U32) {
-  auto* u32 = create<sem::U32>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, u32, "")) << gen.error();
-  EXPECT_EQ(out.str(), "uint");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Vector) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, vec3, "")) << gen.error();
-  EXPECT_EQ(out.str(), "float3");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Void) {
-  auto* void_ = create<sem::Void>();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, void_, "")) << gen.error();
-  EXPECT_EQ(out.str(), "void");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_Sampler) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.error();
-  EXPECT_EQ(out.str(), "sampler");
-}
-
-TEST_F(MslGeneratorImplTest, EmitType_SamplerComparison) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.error();
-  EXPECT_EQ(out.str(), "sampler");
-}
-
-struct MslDepthTextureData {
-  ast::TextureDimension dim;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out, MslDepthTextureData data) {
-  out << data.dim;
-  return out;
-}
-using MslDepthTexturesTest = TestParamHelper<MslDepthTextureData>;
-TEST_P(MslDepthTexturesTest, Emit) {
-  auto params = GetParam();
-
-  sem::DepthTexture s(params.dim);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslDepthTexturesTest,
-    testing::Values(MslDepthTextureData{ast::TextureDimension::k2d,
-                                        "depth2d<float, access::sample>"},
-                    MslDepthTextureData{ast::TextureDimension::k2dArray,
-                                        "depth2d_array<float, access::sample>"},
-                    MslDepthTextureData{ast::TextureDimension::kCube,
-                                        "depthcube<float, access::sample>"},
-                    MslDepthTextureData{
-                        ast::TextureDimension::kCubeArray,
-                        "depthcube_array<float, access::sample>"}));
-
-using MslDepthMultisampledTexturesTest = TestHelper;
-TEST_F(MslDepthMultisampledTexturesTest, Emit) {
-  sem::DepthMultisampledTexture s(ast::TextureDimension::k2d);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
-  EXPECT_EQ(out.str(), "depth2d_ms<float, access::read>");
-}
-
-struct MslTextureData {
-  ast::TextureDimension dim;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out, MslTextureData data) {
-  out << data.dim;
-  return out;
-}
-using MslSampledtexturesTest = TestParamHelper<MslTextureData>;
-TEST_P(MslSampledtexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(params.dim, f32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslSampledtexturesTest,
-    testing::Values(MslTextureData{ast::TextureDimension::k1d,
-                                   "texture1d<float, access::sample>"},
-                    MslTextureData{ast::TextureDimension::k2d,
-                                   "texture2d<float, access::sample>"},
-                    MslTextureData{ast::TextureDimension::k2dArray,
-                                   "texture2d_array<float, access::sample>"},
-                    MslTextureData{ast::TextureDimension::k3d,
-                                   "texture3d<float, access::sample>"},
-                    MslTextureData{ast::TextureDimension::kCube,
-                                   "texturecube<float, access::sample>"},
-                    MslTextureData{
-                        ast::TextureDimension::kCubeArray,
-                        "texturecube_array<float, access::sample>"}));
-
-TEST_F(MslGeneratorImplTest, Emit_TypeMultisampledTexture) {
-  auto* u32 = create<sem::U32>();
-  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, u32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, ms, "")) << gen.error();
-  EXPECT_EQ(out.str(), "texture2d_ms<uint, access::read>");
-}
-
-struct MslStorageTextureData {
-  ast::TextureDimension dim;
-  std::string result;
-};
-inline std::ostream& operator<<(std::ostream& out, MslStorageTextureData data) {
-  return out << data.dim;
-}
-using MslStorageTexturesTest = TestParamHelper<MslStorageTextureData>;
-TEST_P(MslStorageTexturesTest, Emit) {
-  auto params = GetParam();
-
-  auto* s = ty.storage_texture(params.dim, ast::TexelFormat::kR32Float,
-                               ast::Access::kWrite);
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    MslGeneratorImplTest,
-    MslStorageTexturesTest,
-    testing::Values(MslStorageTextureData{ast::TextureDimension::k1d,
-                                          "texture1d<float, access::write>"},
-                    MslStorageTextureData{ast::TextureDimension::k2d,
-                                          "texture2d<float, access::write>"},
-                    MslStorageTextureData{
-                        ast::TextureDimension::k2dArray,
-                        "texture2d_array<float, access::write>"},
-                    MslStorageTextureData{ast::TextureDimension::k3d,
-                                          "texture3d<float, access::write>"}));
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_unary_op_test.cc b/src/writer/msl/generator_impl_unary_op_test.cc
deleted file mode 100644
index 799efb6..0000000
--- a/src/writer/msl/generator_impl_unary_op_test.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2020 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/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslUnaryOpTest = TestHelper;
-
-TEST_F(MslUnaryOpTest, AddressOf) {
-  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "&(expr)");
-}
-
-TEST_F(MslUnaryOpTest, Complement) {
-  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "~(expr)");
-}
-
-TEST_F(MslUnaryOpTest, Indirection) {
-  Global("G", ty.f32(), ast::StorageClass::kPrivate);
-  auto* p = Const(
-      "expr", nullptr,
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
-  WrapInFunction(p, op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "*(expr)");
-}
-
-TEST_F(MslUnaryOpTest, Not) {
-  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "!(expr)");
-}
-
-TEST_F(MslUnaryOpTest, Negation) {
-  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "tint_unary_minus(expr)");
-}
-
-TEST_F(MslUnaryOpTest, NegationOfIntMin) {
-  auto* op = create<ast::UnaryOpExpression>(
-      ast::UnaryOp::kNegation, Expr(std::numeric_limits<int32_t>::min()));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "tint_unary_minus((-2147483647 - 1))");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
deleted file mode 100644
index 61c8b66..0000000
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement) {
-  auto* var = Var("a", ty.f32(), ast::StorageClass::kNone);
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const) {
-  auto* var = Const("a", ty.f32(), Construct(ty.f32()));
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float const a = float();\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Array) {
-  auto* var = Var("a", ty.array<f32, 5>(), ast::StorageClass::kNone);
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float a[5] = {0.0f};\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.f32()),
-                           });
-
-  auto* var = Var("a", ty.Of(s), ast::StorageClass::kNone);
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  S a = {};
-)");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Vector) {
-  auto* var = Var("a", ty.vec2<f32>());
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float2 a = 0.0f;\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Matrix) {
-  auto* var = Var("a", ty.mat3x2<f32>());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  float3x2 a = float3x2(0.0f);\n");
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Private) {
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("thread float tint_symbol_1 = 0.0f;\n"));
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_Private) {
-  GlobalConst("initializer", ty.f32(), Expr(0.f));
-  Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr("initializer"));
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("thread float tint_symbol_1 = initializer;\n"));
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Workgroup) {
-  Global("a", ty.f32(), ast::StorageClass::kWorkgroup);
-
-  WrapInFunction(Expr("a"));
-
-  GeneratorImpl& gen = SanitizeAndBuild();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("threadgroup float tint_symbol_2;\n"));
-}
-
-TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec) {
-  auto* zero_vec = vec3<f32>();
-
-  auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, zero_vec);
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(float3 a = float3();
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/test_helper.h b/src/writer/msl/test_helper.h
deleted file mode 100644
index 8b4cf3b..0000000
--- a/src/writer/msl/test_helper.h
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2020 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_WRITER_MSL_TEST_HELPER_H_
-#define SRC_WRITER_MSL_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-#include "src/writer/msl/generator.h"
-#include "src/writer/msl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-
-/// Helper class for testing
-template <typename BASE>
-class TestHelperBase : public BASE, public ProgramBuilder {
- public:
-  TestHelperBase() = default;
-  ~TestHelperBase() override = default;
-
-  /// Builds and returns a GeneratorImpl from the program.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @return the built generator
-  GeneratorImpl& Build() {
-    if (gen_) {
-      return *gen_;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-    gen_ = std::make_unique<GeneratorImpl>(program.get());
-    return *gen_;
-  }
-
-  /// Builds the program, runs the program through the transform::Msl sanitizer
-  /// and returns a GeneratorImpl from the sanitized program.
-  /// @param options The MSL generator options.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @return the built generator
-  GeneratorImpl& SanitizeAndBuild(const Options& options = {}) {
-    if (gen_) {
-      return *gen_;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-
-    auto result = Sanitize(
-        program.get(), options.buffer_size_ubo_index, options.fixed_sample_mask,
-        options.emit_vertex_point_size, options.disable_workgroup_init,
-        options.array_length_from_uniform);
-    [&]() {
-      ASSERT_TRUE(result.program.IsValid())
-          << diag::Formatter().format(result.program.Diagnostics());
-    }();
-    *program = std::move(result.program);
-    gen_ = std::make_unique<GeneratorImpl>(program.get());
-    return *gen_;
-  }
-
-  /// The program built with a call to Build()
-  std::unique_ptr<Program> program;
-
- private:
-  std::unique_ptr<GeneratorImpl> gen_;
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_MSL_TEST_HELPER_H_
diff --git a/src/writer/spirv/binary_writer.cc b/src/writer/spirv/binary_writer.cc
deleted file mode 100644
index 9f8bab5..0000000
--- a/src/writer/spirv/binary_writer.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 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/writer/spirv/binary_writer.h"
-
-#include <cstring>
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-const uint32_t kGeneratorId = 23u << 16;
-
-}  // namespace
-
-BinaryWriter::BinaryWriter() = default;
-
-BinaryWriter::~BinaryWriter() = default;
-
-void BinaryWriter::WriteBuilder(Builder* builder) {
-  out_.reserve(builder->total_size());
-  builder->iterate(
-      [this](const Instruction& inst) { this->process_instruction(inst); });
-}
-
-void BinaryWriter::WriteInstruction(const Instruction& inst) {
-  process_instruction(inst);
-}
-
-void BinaryWriter::WriteHeader(uint32_t bound) {
-  out_.push_back(spv::MagicNumber);
-  out_.push_back(0x00010300);  // Version 1.3
-  out_.push_back(kGeneratorId);
-  out_.push_back(bound);
-  out_.push_back(0);
-}
-
-void BinaryWriter::process_instruction(const Instruction& inst) {
-  out_.push_back(inst.word_length() << 16 |
-                 static_cast<uint32_t>(inst.opcode()));
-  for (const auto& op : inst.operands()) {
-    process_op(op);
-  }
-}
-
-void BinaryWriter::process_op(const Operand& op) {
-  if (op.IsFloat()) {
-    // Allocate space for the float
-    out_.push_back(0);
-    auto f = op.to_f();
-    uint8_t* ptr = reinterpret_cast<uint8_t*>(out_.data() + (out_.size() - 1));
-    memcpy(ptr, &f, 4);
-  } else if (op.IsInt()) {
-    out_.push_back(op.to_i());
-  } else {
-    auto idx = out_.size();
-    const auto& str = op.to_s();
-    out_.resize(out_.size() + op.length(), 0);
-    memcpy(out_.data() + idx, str.c_str(), str.size() + 1);
-  }
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/binary_writer.h b/src/writer/spirv/binary_writer.h
deleted file mode 100644
index 78bac9c..0000000
--- a/src/writer/spirv/binary_writer.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_BINARY_WRITER_H_
-#define SRC_WRITER_SPIRV_BINARY_WRITER_H_
-
-#include <vector>
-
-#include "src/writer/spirv/builder.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// Writer to convert from builder to SPIR-V binary
-class BinaryWriter {
- public:
-  /// Constructor
-  BinaryWriter();
-  ~BinaryWriter();
-
-  /// Writes the SPIR-V header.
-  /// @param bound the bound to output
-  void WriteHeader(uint32_t bound);
-
-  /// Writes the given builder data into a binary. Note, this does not emit
-  /// the SPIR-V header. You **must** call WriteHeader() before WriteBuilder()
-  /// if you want the SPIR-V to be emitted.
-  /// @param builder the builder to assemble from
-  void WriteBuilder(Builder* builder);
-
-  /// Writes the given instruction into the binary.
-  /// @param inst the instruction to assemble
-  void WriteInstruction(const Instruction& inst);
-
-  /// @returns the assembled SPIR-V
-  const std::vector<uint32_t>& result() const { return out_; }
-
- private:
-  void process_instruction(const Instruction& inst);
-  void process_op(const Operand& op);
-
-  std::vector<uint32_t> out_;
-};
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_BINARY_WRITER_H_
diff --git a/src/writer/spirv/binary_writer_test.cc b/src/writer/spirv/binary_writer_test.cc
deleted file mode 100644
index 392fae7..0000000
--- a/src/writer/spirv/binary_writer_test.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2020 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/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BinaryWriterTest = TestHelper;
-
-TEST_F(BinaryWriterTest, Preamble) {
-  BinaryWriter bw;
-  bw.WriteHeader(5);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 5u);
-  EXPECT_EQ(res[0], spv::MagicNumber);
-  EXPECT_EQ(res[1], 0x00010300u);  // SPIR-V 1.3
-  EXPECT_EQ(res[2], 23u << 16);    // Generator ID
-  EXPECT_EQ(res[3], 5u);           // ID Bound
-  EXPECT_EQ(res[4], 0u);           // Reserved
-}
-
-TEST_F(BinaryWriterTest, Float) {
-  spirv::Builder& b = Build();
-
-  b.push_annot(spv::Op::OpKill, {Operand::Float(2.4f)});
-  BinaryWriter bw;
-  bw.WriteBuilder(&b);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 2u);
-  float f;
-  memcpy(&f, res.data() + 1, 4);
-  EXPECT_EQ(f, 2.4f);
-}
-
-TEST_F(BinaryWriterTest, Int) {
-  spirv::Builder& b = Build();
-
-  b.push_annot(spv::Op::OpKill, {Operand::Int(2)});
-  BinaryWriter bw;
-  bw.WriteBuilder(&b);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 2u);
-  EXPECT_EQ(res[1], 2u);
-}
-
-TEST_F(BinaryWriterTest, String) {
-  spirv::Builder& b = Build();
-
-  b.push_annot(spv::Op::OpKill, {Operand::String("my_string")});
-  BinaryWriter bw;
-  bw.WriteBuilder(&b);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 4u);
-
-  uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
-  EXPECT_EQ(v[0], 'm');
-  EXPECT_EQ(v[1], 'y');
-  EXPECT_EQ(v[2], '_');
-  EXPECT_EQ(v[3], 's');
-  EXPECT_EQ(v[4], 't');
-  EXPECT_EQ(v[5], 'r');
-  EXPECT_EQ(v[6], 'i');
-  EXPECT_EQ(v[7], 'n');
-  EXPECT_EQ(v[8], 'g');
-  EXPECT_EQ(v[9], '\0');
-  EXPECT_EQ(v[10], '\0');
-  EXPECT_EQ(v[11], '\0');
-}
-
-TEST_F(BinaryWriterTest, String_Multiple4Length) {
-  spirv::Builder& b = Build();
-
-  b.push_annot(spv::Op::OpKill, {Operand::String("mystring")});
-  BinaryWriter bw;
-  bw.WriteBuilder(&b);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 4u);
-
-  uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
-  EXPECT_EQ(v[0], 'm');
-  EXPECT_EQ(v[1], 'y');
-  EXPECT_EQ(v[2], 's');
-  EXPECT_EQ(v[3], 't');
-  EXPECT_EQ(v[4], 'r');
-  EXPECT_EQ(v[5], 'i');
-  EXPECT_EQ(v[6], 'n');
-  EXPECT_EQ(v[7], 'g');
-  EXPECT_EQ(v[8], '\0');
-  EXPECT_EQ(v[9], '\0');
-  EXPECT_EQ(v[10], '\0');
-  EXPECT_EQ(v[11], '\0');
-}
-
-TEST_F(BinaryWriterTest, TestInstructionWriter) {
-  Instruction i1{spv::Op::OpKill, {Operand::Int(2)}};
-  Instruction i2{spv::Op::OpKill, {Operand::Int(4)}};
-
-  BinaryWriter bw;
-  bw.WriteInstruction(i1);
-  bw.WriteInstruction(i2);
-
-  auto res = bw.result();
-  ASSERT_EQ(res.size(), 4u);
-  EXPECT_EQ(res[1], 2u);
-  EXPECT_EQ(res[3], 4u);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
deleted file mode 100644
index 84bd633..0000000
--- a/src/writer/spirv/builder.cc
+++ /dev/null
@@ -1,4468 +0,0 @@
-// Copyright 2020 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/writer/spirv/builder.h"
-
-#include <algorithm>
-#include <limits>
-#include <utility>
-
-#include "spirv/unified1/GLSL.std.450.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/traverse_expressions.h"
-#include "src/sem/array.h"
-#include "src/sem/atomic_type.h"
-#include "src/sem/builtin.h"
-#include "src/sem/call.h"
-#include "src/sem/depth_multisampled_texture_type.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/function.h"
-#include "src/sem/member_accessor_expression.h"
-#include "src/sem/module.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/reference_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/sem/statement.h"
-#include "src/sem/struct.h"
-#include "src/sem/type_constructor.h"
-#include "src/sem/type_conversion.h"
-#include "src/sem/variable.h"
-#include "src/sem/vector_type.h"
-#include "src/transform/add_empty_entry_point.h"
-#include "src/transform/add_spirv_block_attribute.h"
-#include "src/transform/canonicalize_entry_point_io.h"
-#include "src/transform/external_texture_transform.h"
-#include "src/transform/fold_constants.h"
-#include "src/transform/for_loop_to_loop.h"
-#include "src/transform/manager.h"
-#include "src/transform/remove_unreachable_statements.h"
-#include "src/transform/simplify_pointers.h"
-#include "src/transform/unshadow.h"
-#include "src/transform/var_for_dynamic_index.h"
-#include "src/transform/vectorize_scalar_matrix_constructors.h"
-#include "src/transform/zero_init_workgroup_memory.h"
-#include "src/utils/defer.h"
-#include "src/utils/map.h"
-#include "src/writer/append_vector.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuiltinType = sem::BuiltinType;
-
-const char kGLSLstd450[] = "GLSL.std.450";
-
-uint32_t size_of(const InstructionList& instructions) {
-  uint32_t size = 0;
-  for (const auto& inst : instructions)
-    size += inst.word_length();
-
-  return size;
-}
-
-uint32_t pipeline_stage_to_execution_model(ast::PipelineStage stage) {
-  SpvExecutionModel model = SpvExecutionModelVertex;
-
-  switch (stage) {
-    case ast::PipelineStage::kFragment:
-      model = SpvExecutionModelFragment;
-      break;
-    case ast::PipelineStage::kVertex:
-      model = SpvExecutionModelVertex;
-      break;
-    case ast::PipelineStage::kCompute:
-      model = SpvExecutionModelGLCompute;
-      break;
-    case ast::PipelineStage::kNone:
-      model = SpvExecutionModelMax;
-      break;
-  }
-  return model;
-}
-
-bool LastIsFallthrough(const ast::BlockStatement* stmts) {
-  return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
-}
-
-/// Returns the matrix type that is `type` or that is wrapped by
-/// one or more levels of an arrays inside of `type`.
-/// @param type the given type, which must not be null
-/// @returns the nested matrix type, or nullptr if none
-const sem::Matrix* GetNestedMatrixType(const sem::Type* type) {
-  while (auto* arr = type->As<sem::Array>()) {
-    type = arr->ElemType();
-  }
-  return type->As<sem::Matrix>();
-}
-
-uint32_t builtin_to_glsl_method(const sem::Builtin* builtin) {
-  switch (builtin->Type()) {
-    case BuiltinType::kAcos:
-      return GLSLstd450Acos;
-    case BuiltinType::kAsin:
-      return GLSLstd450Asin;
-    case BuiltinType::kAtan:
-      return GLSLstd450Atan;
-    case BuiltinType::kAtan2:
-      return GLSLstd450Atan2;
-    case BuiltinType::kCeil:
-      return GLSLstd450Ceil;
-    case BuiltinType::kClamp:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        return GLSLstd450NClamp;
-      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
-        return GLSLstd450UClamp;
-      } else {
-        return GLSLstd450SClamp;
-      }
-    case BuiltinType::kCos:
-      return GLSLstd450Cos;
-    case BuiltinType::kCosh:
-      return GLSLstd450Cosh;
-    case BuiltinType::kCross:
-      return GLSLstd450Cross;
-    case BuiltinType::kDegrees:
-      return GLSLstd450Degrees;
-    case BuiltinType::kDeterminant:
-      return GLSLstd450Determinant;
-    case BuiltinType::kDistance:
-      return GLSLstd450Distance;
-    case BuiltinType::kExp:
-      return GLSLstd450Exp;
-    case BuiltinType::kExp2:
-      return GLSLstd450Exp2;
-    case BuiltinType::kFaceForward:
-      return GLSLstd450FaceForward;
-    case BuiltinType::kFloor:
-      return GLSLstd450Floor;
-    case BuiltinType::kFma:
-      return GLSLstd450Fma;
-    case BuiltinType::kFract:
-      return GLSLstd450Fract;
-    case BuiltinType::kFrexp:
-      return GLSLstd450FrexpStruct;
-    case BuiltinType::kInverseSqrt:
-      return GLSLstd450InverseSqrt;
-    case BuiltinType::kLdexp:
-      return GLSLstd450Ldexp;
-    case BuiltinType::kLength:
-      return GLSLstd450Length;
-    case BuiltinType::kLog:
-      return GLSLstd450Log;
-    case BuiltinType::kLog2:
-      return GLSLstd450Log2;
-    case BuiltinType::kMax:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        return GLSLstd450NMax;
-      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
-        return GLSLstd450UMax;
-      } else {
-        return GLSLstd450SMax;
-      }
-    case BuiltinType::kMin:
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        return GLSLstd450NMin;
-      } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
-        return GLSLstd450UMin;
-      } else {
-        return GLSLstd450SMin;
-      }
-    case BuiltinType::kMix:
-      return GLSLstd450FMix;
-    case BuiltinType::kModf:
-      return GLSLstd450ModfStruct;
-    case BuiltinType::kNormalize:
-      return GLSLstd450Normalize;
-    case BuiltinType::kPack4x8snorm:
-      return GLSLstd450PackSnorm4x8;
-    case BuiltinType::kPack4x8unorm:
-      return GLSLstd450PackUnorm4x8;
-    case BuiltinType::kPack2x16snorm:
-      return GLSLstd450PackSnorm2x16;
-    case BuiltinType::kPack2x16unorm:
-      return GLSLstd450PackUnorm2x16;
-    case BuiltinType::kPack2x16float:
-      return GLSLstd450PackHalf2x16;
-    case BuiltinType::kPow:
-      return GLSLstd450Pow;
-    case BuiltinType::kRadians:
-      return GLSLstd450Radians;
-    case BuiltinType::kReflect:
-      return GLSLstd450Reflect;
-    case BuiltinType::kRefract:
-      return GLSLstd450Refract;
-    case BuiltinType::kRound:
-      return GLSLstd450RoundEven;
-    case BuiltinType::kSign:
-      return GLSLstd450FSign;
-    case BuiltinType::kSin:
-      return GLSLstd450Sin;
-    case BuiltinType::kSinh:
-      return GLSLstd450Sinh;
-    case BuiltinType::kSmoothStep:
-      return GLSLstd450SmoothStep;
-    case BuiltinType::kSqrt:
-      return GLSLstd450Sqrt;
-    case BuiltinType::kStep:
-      return GLSLstd450Step;
-    case BuiltinType::kTan:
-      return GLSLstd450Tan;
-    case BuiltinType::kTanh:
-      return GLSLstd450Tanh;
-    case BuiltinType::kTrunc:
-      return GLSLstd450Trunc;
-    case BuiltinType::kUnpack4x8snorm:
-      return GLSLstd450UnpackSnorm4x8;
-    case BuiltinType::kUnpack4x8unorm:
-      return GLSLstd450UnpackUnorm4x8;
-    case BuiltinType::kUnpack2x16snorm:
-      return GLSLstd450UnpackSnorm2x16;
-    case BuiltinType::kUnpack2x16unorm:
-      return GLSLstd450UnpackUnorm2x16;
-    case BuiltinType::kUnpack2x16float:
-      return GLSLstd450UnpackHalf2x16;
-    default:
-      break;
-  }
-  return 0;
-}
-
-/// @return the vector element type if ty is a vector, otherwise return ty.
-const sem::Type* ElementTypeOf(const sem::Type* ty) {
-  if (auto* v = ty->As<sem::Vector>()) {
-    return v->type();
-  }
-  return ty;
-}
-
-}  // namespace
-
-SanitizedResult Sanitize(const Program* in,
-                         bool emit_vertex_point_size,
-                         bool disable_workgroup_init) {
-  transform::Manager manager;
-  transform::DataMap data;
-
-  manager.Add<transform::Unshadow>();
-  if (!disable_workgroup_init) {
-    manager.Add<transform::ZeroInitWorkgroupMemory>();
-  }
-  manager.Add<transform::RemoveUnreachableStatements>();
-  manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
-  manager.Add<transform::FoldConstants>();
-  manager.Add<transform::ExternalTextureTransform>();
-  manager.Add<transform::VectorizeScalarMatrixConstructors>();
-  manager.Add<transform::ForLoopToLoop>();  // Must come after
-                                            // ZeroInitWorkgroupMemory
-  manager.Add<transform::CanonicalizeEntryPointIO>();
-  manager.Add<transform::AddEmptyEntryPoint>();
-  manager.Add<transform::AddSpirvBlockAttribute>();
-  manager.Add<transform::VarForDynamicIndex>();
-
-  data.Add<transform::CanonicalizeEntryPointIO::Config>(
-      transform::CanonicalizeEntryPointIO::Config(
-          transform::CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF,
-          emit_vertex_point_size));
-
-  SanitizedResult result;
-  result.program = std::move(manager.Run(in, data).program);
-  return result;
-}
-
-Builder::AccessorInfo::AccessorInfo() : source_id(0), source_type(nullptr) {}
-
-Builder::AccessorInfo::~AccessorInfo() {}
-
-Builder::Builder(const Program* program)
-    : builder_(ProgramBuilder::Wrap(program)), scope_stack_({}) {}
-
-Builder::~Builder() = default;
-
-bool Builder::Build() {
-  push_capability(SpvCapabilityShader);
-
-  push_memory_model(spv::Op::OpMemoryModel,
-                    {Operand::Int(SpvAddressingModelLogical),
-                     Operand::Int(SpvMemoryModelGLSL450)});
-
-  for (auto* var : builder_.AST().GlobalVariables()) {
-    if (!GenerateGlobalVariable(var)) {
-      return false;
-    }
-  }
-
-  auto* mod = builder_.Sem().Module();
-  for (auto* decl : mod->DependencyOrderedDeclarations()) {
-    if (auto* func = decl->As<ast::Function>()) {
-      if (!GenerateFunction(func)) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-Operand Builder::result_op() {
-  return Operand::Int(next_id());
-}
-
-uint32_t Builder::total_size() const {
-  // The 5 covers the magic, version, generator, id bound and reserved.
-  uint32_t size = 5;
-
-  size += size_of(capabilities_);
-  size += size_of(extensions_);
-  size += size_of(ext_imports_);
-  size += size_of(memory_model_);
-  size += size_of(entry_points_);
-  size += size_of(execution_modes_);
-  size += size_of(debug_);
-  size += size_of(annotations_);
-  size += size_of(types_);
-  for (const auto& func : functions_) {
-    size += func.word_length();
-  }
-
-  return size;
-}
-
-void Builder::iterate(std::function<void(const Instruction&)> cb) const {
-  for (const auto& inst : capabilities_) {
-    cb(inst);
-  }
-  for (const auto& inst : extensions_) {
-    cb(inst);
-  }
-  for (const auto& inst : ext_imports_) {
-    cb(inst);
-  }
-  for (const auto& inst : memory_model_) {
-    cb(inst);
-  }
-  for (const auto& inst : entry_points_) {
-    cb(inst);
-  }
-  for (const auto& inst : execution_modes_) {
-    cb(inst);
-  }
-  for (const auto& inst : debug_) {
-    cb(inst);
-  }
-  for (const auto& inst : annotations_) {
-    cb(inst);
-  }
-  for (const auto& inst : types_) {
-    cb(inst);
-  }
-  for (const auto& func : functions_) {
-    func.iterate(cb);
-  }
-}
-
-void Builder::push_capability(uint32_t cap) {
-  if (capability_set_.count(cap) == 0) {
-    capability_set_.insert(cap);
-    capabilities_.push_back(
-        Instruction{spv::Op::OpCapability, {Operand::Int(cap)}});
-  }
-}
-
-bool Builder::GenerateLabel(uint32_t id) {
-  if (!push_function_inst(spv::Op::OpLabel, {Operand::Int(id)})) {
-    return false;
-  }
-  current_label_id_ = id;
-  return true;
-}
-
-bool Builder::GenerateAssignStatement(const ast::AssignmentStatement* assign) {
-  if (assign->lhs->Is<ast::PhonyExpression>()) {
-    auto rhs_id = GenerateExpression(assign->rhs);
-    if (rhs_id == 0) {
-      return false;
-    }
-    return true;
-  } else {
-    auto lhs_id = GenerateExpression(assign->lhs);
-    if (lhs_id == 0) {
-      return false;
-    }
-    auto rhs_id = GenerateExpressionWithLoadIfNeeded(assign->rhs);
-    if (rhs_id == 0) {
-      return false;
-    }
-    return GenerateStore(lhs_id, rhs_id);
-  }
-}
-
-bool Builder::GenerateBreakStatement(const ast::BreakStatement*) {
-  if (merge_stack_.empty()) {
-    error_ = "Attempted to break without a merge block";
-    return false;
-  }
-  if (!push_function_inst(spv::Op::OpBranch,
-                          {Operand::Int(merge_stack_.back())})) {
-    return false;
-  }
-  return true;
-}
-
-bool Builder::GenerateContinueStatement(const ast::ContinueStatement*) {
-  if (continue_stack_.empty()) {
-    error_ = "Attempted to continue without a continue block";
-    return false;
-  }
-  if (!push_function_inst(spv::Op::OpBranch,
-                          {Operand::Int(continue_stack_.back())})) {
-    return false;
-  }
-  return true;
-}
-
-// TODO(dsinclair): This is generating an OpKill but the semantics of kill
-// haven't been defined for WGSL yet. So, this may need to change.
-// https://github.com/gpuweb/gpuweb/issues/676
-bool Builder::GenerateDiscardStatement(const ast::DiscardStatement*) {
-  if (!push_function_inst(spv::Op::OpKill, {})) {
-    return false;
-  }
-  return true;
-}
-
-bool Builder::GenerateEntryPoint(const ast::Function* func, uint32_t id) {
-  auto stage = pipeline_stage_to_execution_model(func->PipelineStage());
-  if (stage == SpvExecutionModelMax) {
-    error_ = "Unknown pipeline stage provided";
-    return false;
-  }
-
-  OperandList operands = {
-      Operand::Int(stage), Operand::Int(id),
-      Operand::String(builder_.Symbols().NameFor(func->symbol))};
-
-  auto* func_sem = builder_.Sem().Get(func);
-  for (const auto* var : func_sem->TransitivelyReferencedGlobals()) {
-    // For SPIR-V 1.3 we only output Input/output variables. If we update to
-    // SPIR-V 1.4 or later this should be all variables.
-    if (var->StorageClass() != ast::StorageClass::kInput &&
-        var->StorageClass() != ast::StorageClass::kOutput) {
-      continue;
-    }
-
-    uint32_t var_id = scope_stack_.Get(var->Declaration()->symbol);
-    if (var_id == 0) {
-      error_ = "unable to find ID for global variable: " +
-               builder_.Symbols().NameFor(var->Declaration()->symbol);
-      return false;
-    }
-
-    operands.push_back(Operand::Int(var_id));
-  }
-  push_entry_point(spv::Op::OpEntryPoint, operands);
-
-  return true;
-}
-
-bool Builder::GenerateExecutionModes(const ast::Function* func, uint32_t id) {
-  auto* func_sem = builder_.Sem().Get(func);
-
-  // WGSL fragment shader origin is upper left
-  if (func->PipelineStage() == ast::PipelineStage::kFragment) {
-    push_execution_mode(
-        spv::Op::OpExecutionMode,
-        {Operand::Int(id), Operand::Int(SpvExecutionModeOriginUpperLeft)});
-  } else if (func->PipelineStage() == ast::PipelineStage::kCompute) {
-    auto& wgsize = func_sem->WorkgroupSize();
-
-    // Check if the workgroup_size uses pipeline-overridable constants.
-    if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
-        wgsize[2].overridable_const) {
-      if (has_overridable_workgroup_size_) {
-        // Only one stage can have a pipeline-overridable workgroup size.
-        // TODO(crbug.com/tint/810): Use LocalSizeId to handle this scenario.
-        TINT_ICE(Writer, builder_.Diagnostics())
-            << "multiple stages using pipeline-overridable workgroup sizes";
-      }
-      has_overridable_workgroup_size_ = true;
-
-      auto* vec3_u32 =
-          builder_.create<sem::Vector>(builder_.create<sem::U32>(), 3);
-      uint32_t vec3_u32_type_id = GenerateTypeIfNeeded(vec3_u32);
-      if (vec3_u32_type_id == 0) {
-        return 0;
-      }
-
-      OperandList wgsize_ops;
-      auto wgsize_result = result_op();
-      wgsize_ops.push_back(Operand::Int(vec3_u32_type_id));
-      wgsize_ops.push_back(wgsize_result);
-
-      // Generate OpConstant instructions for each dimension.
-      for (int i = 0; i < 3; i++) {
-        auto constant = ScalarConstant::U32(wgsize[i].value);
-        if (wgsize[i].overridable_const) {
-          // Make the constant specializable.
-          auto* sem_const = builder_.Sem().Get<sem::GlobalVariable>(
-              wgsize[i].overridable_const);
-          if (!sem_const->IsOverridable()) {
-            TINT_ICE(Writer, builder_.Diagnostics())
-                << "expected a pipeline-overridable constant";
-          }
-          constant.is_spec_op = true;
-          constant.constant_id = sem_const->ConstantId();
-        }
-
-        auto result = GenerateConstantIfNeeded(constant);
-        wgsize_ops.push_back(Operand::Int(result));
-      }
-
-      // Generate the WorkgroupSize builtin.
-      push_type(spv::Op::OpSpecConstantComposite, wgsize_ops);
-      push_annot(spv::Op::OpDecorate,
-                 {wgsize_result, Operand::Int(SpvDecorationBuiltIn),
-                  Operand::Int(SpvBuiltInWorkgroupSize)});
-    } else {
-      // Not overridable, so just use OpExecutionMode LocalSize.
-      uint32_t x = wgsize[0].value;
-      uint32_t y = wgsize[1].value;
-      uint32_t z = wgsize[2].value;
-      push_execution_mode(
-          spv::Op::OpExecutionMode,
-          {Operand::Int(id), Operand::Int(SpvExecutionModeLocalSize),
-           Operand::Int(x), Operand::Int(y), Operand::Int(z)});
-    }
-  }
-
-  for (auto builtin : func_sem->TransitivelyReferencedBuiltinVariables()) {
-    if (builtin.second->builtin == ast::Builtin::kFragDepth) {
-      push_execution_mode(
-          spv::Op::OpExecutionMode,
-          {Operand::Int(id), Operand::Int(SpvExecutionModeDepthReplacing)});
-    }
-  }
-
-  return true;
-}
-
-uint32_t Builder::GenerateExpression(const ast::Expression* expr) {
-  return Switch(
-      expr,
-      [&](const ast::IndexAccessorExpression* a) {  //
-        return GenerateAccessorExpression(a);
-      },
-      [&](const ast::BinaryExpression* b) {  //
-        return GenerateBinaryExpression(b);
-      },
-      [&](const ast::BitcastExpression* b) {  //
-        return GenerateBitcastExpression(b);
-      },
-      [&](const ast::CallExpression* c) {  //
-        return GenerateCallExpression(c);
-      },
-      [&](const ast::IdentifierExpression* i) {  //
-        return GenerateIdentifierExpression(i);
-      },
-      [&](const ast::LiteralExpression* l) {  //
-        return GenerateLiteralIfNeeded(nullptr, l);
-      },
-      [&](const ast::MemberAccessorExpression* m) {  //
-        return GenerateAccessorExpression(m);
-      },
-      [&](const ast::UnaryOpExpression* u) {  //
-        return GenerateUnaryOpExpression(u);
-      },
-      [&](Default) -> uint32_t {
-        error_ =
-            "unknown expression type: " + std::string(expr->TypeInfo().name);
-        return 0;
-      });
-}
-
-bool Builder::GenerateFunction(const ast::Function* func_ast) {
-  auto* func = builder_.Sem().Get(func_ast);
-
-  uint32_t func_type_id = GenerateFunctionTypeIfNeeded(func);
-  if (func_type_id == 0) {
-    return false;
-  }
-
-  auto func_op = result_op();
-  auto func_id = func_op.to_i();
-
-  push_debug(spv::Op::OpName,
-             {Operand::Int(func_id),
-              Operand::String(builder_.Symbols().NameFor(func_ast->symbol))});
-
-  auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
-  if (ret_id == 0) {
-    return false;
-  }
-
-  scope_stack_.Push();
-  TINT_DEFER(scope_stack_.Pop());
-
-  auto definition_inst = Instruction{
-      spv::Op::OpFunction,
-      {Operand::Int(ret_id), func_op, Operand::Int(SpvFunctionControlMaskNone),
-       Operand::Int(func_type_id)}};
-
-  InstructionList params;
-  for (auto* param : func->Parameters()) {
-    auto param_op = result_op();
-    auto param_id = param_op.to_i();
-
-    auto param_type_id = GenerateTypeIfNeeded(param->Type());
-    if (param_type_id == 0) {
-      return false;
-    }
-
-    push_debug(spv::Op::OpName, {Operand::Int(param_id),
-                                 Operand::String(builder_.Symbols().NameFor(
-                                     param->Declaration()->symbol))});
-    params.push_back(Instruction{spv::Op::OpFunctionParameter,
-                                 {Operand::Int(param_type_id), param_op}});
-
-    scope_stack_.Set(param->Declaration()->symbol, param_id);
-  }
-
-  push_function(Function{definition_inst, result_op(), std::move(params)});
-
-  for (auto* stmt : func_ast->body->statements) {
-    if (!GenerateStatement(stmt)) {
-      return false;
-    }
-  }
-
-  if (InsideBasicBlock()) {
-    if (func->ReturnType()->Is<sem::Void>()) {
-      push_function_inst(spv::Op::OpReturn, {});
-    } else {
-      auto zero = GenerateConstantNullIfNeeded(func->ReturnType());
-      push_function_inst(spv::Op::OpReturnValue, {Operand::Int(zero)});
-    }
-  }
-
-  if (func_ast->IsEntryPoint()) {
-    if (!GenerateEntryPoint(func_ast, func_id)) {
-      return false;
-    }
-    if (!GenerateExecutionModes(func_ast, func_id)) {
-      return false;
-    }
-  }
-
-  func_symbol_to_id_[func_ast->symbol] = func_id;
-
-  return true;
-}
-
-uint32_t Builder::GenerateFunctionTypeIfNeeded(const sem::Function* func) {
-  return utils::GetOrCreate(
-      func_sig_to_id_, func->Signature(), [&]() -> uint32_t {
-        auto func_op = result_op();
-        auto func_type_id = func_op.to_i();
-
-        auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
-        if (ret_id == 0) {
-          return 0;
-        }
-
-        OperandList ops = {func_op, Operand::Int(ret_id)};
-        for (auto* param : func->Parameters()) {
-          auto param_type_id = GenerateTypeIfNeeded(param->Type());
-          if (param_type_id == 0) {
-            return 0;
-          }
-          ops.push_back(Operand::Int(param_type_id));
-        }
-
-        push_type(spv::Op::OpTypeFunction, std::move(ops));
-        return func_type_id;
-      });
-}
-
-bool Builder::GenerateFunctionVariable(const ast::Variable* var) {
-  uint32_t init_id = 0;
-  if (var->constructor) {
-    init_id = GenerateExpressionWithLoadIfNeeded(var->constructor);
-    if (init_id == 0) {
-      return false;
-    }
-  }
-
-  if (var->is_const) {
-    if (!var->constructor) {
-      error_ = "missing constructor for constant";
-      return false;
-    }
-    scope_stack_.Set(var->symbol, init_id);
-    spirv_id_to_variable_[init_id] = var;
-    return true;
-  }
-
-  auto result = result_op();
-  auto var_id = result.to_i();
-  auto sc = ast::StorageClass::kFunction;
-  auto* type = builder_.Sem().Get(var)->Type();
-  auto type_id = GenerateTypeIfNeeded(type);
-  if (type_id == 0) {
-    return false;
-  }
-
-  push_debug(spv::Op::OpName,
-             {Operand::Int(var_id),
-              Operand::String(builder_.Symbols().NameFor(var->symbol))});
-
-  // TODO(dsinclair) We could detect if the constructor is fully const and emit
-  // an initializer value for the variable instead of doing the OpLoad.
-  auto null_id = GenerateConstantNullIfNeeded(type->UnwrapRef());
-  if (null_id == 0) {
-    return 0;
-  }
-  push_function_var({Operand::Int(type_id), result,
-                     Operand::Int(ConvertStorageClass(sc)),
-                     Operand::Int(null_id)});
-
-  if (var->constructor) {
-    if (!GenerateStore(var_id, init_id)) {
-      return false;
-    }
-  }
-
-  scope_stack_.Set(var->symbol, var_id);
-  spirv_id_to_variable_[var_id] = var;
-
-  return true;
-}
-
-bool Builder::GenerateStore(uint32_t to, uint32_t from) {
-  return push_function_inst(spv::Op::OpStore,
-                            {Operand::Int(to), Operand::Int(from)});
-}
-
-bool Builder::GenerateGlobalVariable(const ast::Variable* var) {
-  auto* sem = builder_.Sem().Get(var);
-  auto* type = sem->Type()->UnwrapRef();
-
-  uint32_t init_id = 0;
-  if (var->constructor) {
-    init_id = GenerateConstructorExpression(var, var->constructor);
-    if (init_id == 0) {
-      return false;
-    }
-  }
-
-  if (var->is_const) {
-    if (!var->constructor) {
-      // Constants must have an initializer unless they are overridable.
-      if (!var->is_overridable) {
-        error_ = "missing constructor for constant";
-        return false;
-      }
-
-      // SPIR-V requires specialization constants to have initializers.
-      if (type->Is<sem::F32>()) {
-        ast::FloatLiteralExpression l(ProgramID(), Source{}, 0.0f);
-        init_id = GenerateLiteralIfNeeded(var, &l);
-      } else if (type->Is<sem::U32>()) {
-        ast::UintLiteralExpression l(ProgramID(), Source{}, 0);
-        init_id = GenerateLiteralIfNeeded(var, &l);
-      } else if (type->Is<sem::I32>()) {
-        ast::SintLiteralExpression l(ProgramID(), Source{}, 0);
-        init_id = GenerateLiteralIfNeeded(var, &l);
-      } else if (type->Is<sem::Bool>()) {
-        ast::BoolLiteralExpression l(ProgramID(), Source{}, false);
-        init_id = GenerateLiteralIfNeeded(var, &l);
-      } else {
-        error_ = "invalid type for pipeline constant ID, must be scalar";
-        return false;
-      }
-      if (init_id == 0) {
-        return 0;
-      }
-    }
-    push_debug(spv::Op::OpName,
-               {Operand::Int(init_id),
-                Operand::String(builder_.Symbols().NameFor(var->symbol))});
-
-    scope_stack_.Set(var->symbol, init_id);
-    spirv_id_to_variable_[init_id] = var;
-    return true;
-  }
-
-  auto result = result_op();
-  auto var_id = result.to_i();
-
-  auto sc = sem->StorageClass() == ast::StorageClass::kNone
-                ? ast::StorageClass::kPrivate
-                : sem->StorageClass();
-
-  auto type_id = GenerateTypeIfNeeded(sem->Type());
-  if (type_id == 0) {
-    return false;
-  }
-
-  push_debug(spv::Op::OpName,
-             {Operand::Int(var_id),
-              Operand::String(builder_.Symbols().NameFor(var->symbol))});
-
-  OperandList ops = {Operand::Int(type_id), result,
-                     Operand::Int(ConvertStorageClass(sc))};
-
-  if (var->constructor) {
-    ops.push_back(Operand::Int(init_id));
-  } else {
-    auto* st = type->As<sem::StorageTexture>();
-    if (st || type->Is<sem::Struct>()) {
-      // type is a sem::Struct or a sem::StorageTexture
-      auto access = st ? st->access() : sem->Access();
-      switch (access) {
-        case ast::Access::kWrite:
-          push_annot(
-              spv::Op::OpDecorate,
-              {Operand::Int(var_id), Operand::Int(SpvDecorationNonReadable)});
-          break;
-        case ast::Access::kRead:
-          push_annot(
-              spv::Op::OpDecorate,
-              {Operand::Int(var_id), Operand::Int(SpvDecorationNonWritable)});
-          break;
-        case ast::Access::kUndefined:
-        case ast::Access::kReadWrite:
-          break;
-      }
-    }
-    if (!type->Is<sem::Sampler>()) {
-      // If we don't have a constructor and we're an Output or Private
-      // variable, then WGSL requires that we zero-initialize.
-      if (sem->StorageClass() == ast::StorageClass::kPrivate ||
-          sem->StorageClass() == ast::StorageClass::kOutput) {
-        init_id = GenerateConstantNullIfNeeded(type);
-        if (init_id == 0) {
-          return 0;
-        }
-        ops.push_back(Operand::Int(init_id));
-      }
-    }
-  }
-
-  push_type(spv::Op::OpVariable, std::move(ops));
-
-  for (auto* attr : var->attributes) {
-    bool ok = Switch(
-        attr,
-        [&](const ast::BuiltinAttribute* builtin) {
-          push_annot(spv::Op::OpDecorate,
-                     {Operand::Int(var_id), Operand::Int(SpvDecorationBuiltIn),
-                      Operand::Int(ConvertBuiltin(builtin->builtin,
-                                                  sem->StorageClass()))});
-          return true;
-        },
-        [&](const ast::LocationAttribute* location) {
-          push_annot(spv::Op::OpDecorate,
-                     {Operand::Int(var_id), Operand::Int(SpvDecorationLocation),
-                      Operand::Int(location->value)});
-          return true;
-        },
-        [&](const ast::InterpolateAttribute* interpolate) {
-          AddInterpolationDecorations(var_id, interpolate->type,
-                                      interpolate->sampling);
-          return true;
-        },
-        [&](const ast::InvariantAttribute*) {
-          push_annot(
-              spv::Op::OpDecorate,
-              {Operand::Int(var_id), Operand::Int(SpvDecorationInvariant)});
-          return true;
-        },
-        [&](const ast::BindingAttribute* binding) {
-          push_annot(spv::Op::OpDecorate,
-                     {Operand::Int(var_id), Operand::Int(SpvDecorationBinding),
-                      Operand::Int(binding->value)});
-          return true;
-        },
-        [&](const ast::GroupAttribute* group) {
-          push_annot(
-              spv::Op::OpDecorate,
-              {Operand::Int(var_id), Operand::Int(SpvDecorationDescriptorSet),
-               Operand::Int(group->value)});
-          return true;
-        },
-        [&](const ast::IdAttribute*) {
-          return true;  // Spec constants are handled elsewhere
-        },
-        [&](const ast::InternalAttribute*) {
-          return true;  // ignored
-        },
-        [&](Default) {
-          error_ = "unknown attribute";
-          return false;
-        });
-    if (!ok) {
-      return false;
-    }
-  }
-
-  scope_stack_.Set(var->symbol, var_id);
-  spirv_id_to_variable_[var_id] = var;
-  return true;
-}
-
-bool Builder::GenerateIndexAccessor(const ast::IndexAccessorExpression* expr,
-                                    AccessorInfo* info) {
-  auto idx_id = GenerateExpressionWithLoadIfNeeded(expr->index);
-  if (idx_id == 0) {
-    return 0;
-  }
-
-  // If the source is a reference, we access chain into it.
-  // In the future, pointers may support access-chaining.
-  // See https://github.com/gpuweb/gpuweb/pull/1580
-  if (info->source_type->Is<sem::Reference>()) {
-    info->access_chain_indices.push_back(idx_id);
-    info->source_type = TypeOf(expr);
-    return true;
-  }
-
-  auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
-  if (result_type_id == 0) {
-    return false;
-  }
-
-  // We don't have a pointer, so we can just directly extract the value.
-  auto extract = result_op();
-  auto extract_id = extract.to_i();
-
-  // If the index is compile-time constant, we use OpCompositeExtract.
-  auto* idx = builder_.Sem().Get(expr->index);
-  if (auto idx_constval = idx->ConstantValue()) {
-    if (!push_function_inst(
-            spv::Op::OpCompositeExtract,
-            {
-                Operand::Int(result_type_id),
-                extract,
-                Operand::Int(info->source_id),
-                Operand::Int(idx_constval.ElementAs<uint32_t>(0)),
-            })) {
-      return false;
-    }
-
-    info->source_id = extract_id;
-    info->source_type = TypeOf(expr);
-
-    return true;
-  }
-
-  // If the source is a vector, we use OpVectorExtractDynamic.
-  if (info->source_type->Is<sem::Vector>()) {
-    if (!push_function_inst(
-            spv::Op::OpVectorExtractDynamic,
-            {Operand::Int(result_type_id), extract,
-             Operand::Int(info->source_id), Operand::Int(idx_id)})) {
-      return false;
-    }
-
-    info->source_id = extract_id;
-    info->source_type = TypeOf(expr);
-
-    return true;
-  }
-
-  TINT_ICE(Writer, builder_.Diagnostics())
-      << "unsupported index accessor expression";
-  return false;
-}
-
-bool Builder::GenerateMemberAccessor(const ast::MemberAccessorExpression* expr,
-                                     AccessorInfo* info) {
-  auto* expr_sem = builder_.Sem().Get(expr);
-  auto* expr_type = expr_sem->Type();
-
-  if (auto* access = expr_sem->As<sem::StructMemberAccess>()) {
-    uint32_t idx = access->Member()->Index();
-
-    if (info->source_type->Is<sem::Reference>()) {
-      auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(idx));
-      if (idx_id == 0) {
-        return 0;
-      }
-      info->access_chain_indices.push_back(idx_id);
-      info->source_type = expr_type;
-    } else {
-      auto result_type_id = GenerateTypeIfNeeded(expr_type);
-      if (result_type_id == 0) {
-        return false;
-      }
-
-      auto extract = result_op();
-      auto extract_id = extract.to_i();
-      if (!push_function_inst(
-              spv::Op::OpCompositeExtract,
-              {Operand::Int(result_type_id), extract,
-               Operand::Int(info->source_id), Operand::Int(idx)})) {
-        return false;
-      }
-
-      info->source_id = extract_id;
-      info->source_type = expr_type;
-    }
-
-    return true;
-  }
-
-  if (auto* swizzle = expr_sem->As<sem::Swizzle>()) {
-    // Single element swizzle is either an access chain or a composite extract
-    auto& indices = swizzle->Indices();
-    if (indices.size() == 1) {
-      if (info->source_type->Is<sem::Reference>()) {
-        auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(indices[0]));
-        if (idx_id == 0) {
-          return 0;
-        }
-        info->access_chain_indices.push_back(idx_id);
-      } else {
-        auto result_type_id = GenerateTypeIfNeeded(expr_type);
-        if (result_type_id == 0) {
-          return 0;
-        }
-
-        auto extract = result_op();
-        auto extract_id = extract.to_i();
-        if (!push_function_inst(
-                spv::Op::OpCompositeExtract,
-                {Operand::Int(result_type_id), extract,
-                 Operand::Int(info->source_id), Operand::Int(indices[0])})) {
-          return false;
-        }
-
-        info->source_id = extract_id;
-        info->source_type = expr_type;
-      }
-      return true;
-    }
-
-    // Store the type away as it may change if we run the access chain
-    auto* incoming_type = info->source_type;
-
-    // Multi-item extract is a VectorShuffle. We have to emit any existing
-    // access chain data, then load the access chain and shuffle that.
-    if (!info->access_chain_indices.empty()) {
-      auto result_type_id = GenerateTypeIfNeeded(info->source_type);
-      if (result_type_id == 0) {
-        return 0;
-      }
-      auto extract = result_op();
-      auto extract_id = extract.to_i();
-
-      OperandList ops = {Operand::Int(result_type_id), extract,
-                         Operand::Int(info->source_id)};
-      for (auto id : info->access_chain_indices) {
-        ops.push_back(Operand::Int(id));
-      }
-
-      if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
-        return false;
-      }
-
-      info->source_id = GenerateLoadIfNeeded(expr_type, extract_id);
-      info->source_type = expr_type->UnwrapRef();
-      info->access_chain_indices.clear();
-    }
-
-    auto result_type_id = GenerateTypeIfNeeded(expr_type);
-    if (result_type_id == 0) {
-      return false;
-    }
-
-    auto vec_id = GenerateLoadIfNeeded(incoming_type, info->source_id);
-
-    auto result = result_op();
-    auto result_id = result.to_i();
-
-    OperandList ops = {Operand::Int(result_type_id), result,
-                       Operand::Int(vec_id), Operand::Int(vec_id)};
-
-    for (auto idx : indices) {
-      ops.push_back(Operand::Int(idx));
-    }
-
-    if (!push_function_inst(spv::Op::OpVectorShuffle, ops)) {
-      return false;
-    }
-    info->source_id = result_id;
-    info->source_type = expr_type;
-    return true;
-  }
-
-  TINT_ICE(Writer, builder_.Diagnostics())
-      << "unhandled member index type: " << expr_sem->TypeInfo().name;
-  return false;
-}
-
-uint32_t Builder::GenerateAccessorExpression(const ast::Expression* expr) {
-  if (!expr->IsAnyOf<ast::IndexAccessorExpression,
-                     ast::MemberAccessorExpression>()) {
-    TINT_ICE(Writer, builder_.Diagnostics()) << "expression is not an accessor";
-    return 0;
-  }
-
-  // Gather a list of all the member and index accessors that are in this chain.
-  // The list is built in reverse order as that's the order we need to access
-  // the chain.
-  std::vector<const ast::Expression*> accessors;
-  const ast::Expression* source = expr;
-  while (true) {
-    if (auto* array = source->As<ast::IndexAccessorExpression>()) {
-      accessors.insert(accessors.begin(), source);
-      source = array->object;
-    } else if (auto* member = source->As<ast::MemberAccessorExpression>()) {
-      accessors.insert(accessors.begin(), source);
-      source = member->structure;
-    } else {
-      break;
-    }
-  }
-
-  AccessorInfo info;
-  info.source_id = GenerateExpression(source);
-  if (info.source_id == 0) {
-    return 0;
-  }
-  info.source_type = TypeOf(source);
-
-  // Note: Dynamic index on array and matrix values (lets) should have been
-  // promoted to storage with the VarForDynamicIndex transform.
-
-  for (auto* accessor : accessors) {
-    bool ok = Switch(
-        accessor,
-        [&](const ast::IndexAccessorExpression* array) {
-          return GenerateIndexAccessor(array, &info);
-        },
-        [&](const ast::MemberAccessorExpression* member) {
-          return GenerateMemberAccessor(member, &info);
-        },
-        [&](Default) {
-          error_ = "invalid accessor in list: " +
-                   std::string(accessor->TypeInfo().name);
-          return false;
-        });
-    if (!ok) {
-      return false;
-    }
-  }
-
-  if (!info.access_chain_indices.empty()) {
-    auto* type = TypeOf(expr);
-    auto result_type_id = GenerateTypeIfNeeded(type);
-    if (result_type_id == 0) {
-      return 0;
-    }
-
-    auto result = result_op();
-    auto result_id = result.to_i();
-
-    OperandList ops = {Operand::Int(result_type_id), result,
-                       Operand::Int(info.source_id)};
-    for (auto id : info.access_chain_indices) {
-      ops.push_back(Operand::Int(id));
-    }
-
-    if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
-      return false;
-    }
-    info.source_id = result_id;
-  }
-
-  return info.source_id;
-}
-
-uint32_t Builder::GenerateIdentifierExpression(
-    const ast::IdentifierExpression* expr) {
-  uint32_t val = scope_stack_.Get(expr->symbol);
-  if (val == 0) {
-    error_ = "unable to find variable with identifier: " +
-             builder_.Symbols().NameFor(expr->symbol);
-  }
-  return val;
-}
-
-uint32_t Builder::GenerateExpressionWithLoadIfNeeded(
-    const sem::Expression* expr) {
-  // The semantic node directly knows both the AST node and the resolved type.
-  if (const auto id = GenerateExpression(expr->Declaration())) {
-    return GenerateLoadIfNeeded(expr->Type(), id);
-  }
-  return 0;
-}
-
-uint32_t Builder::GenerateExpressionWithLoadIfNeeded(
-    const ast::Expression* expr) {
-  if (const auto id = GenerateExpression(expr)) {
-    // Perform a lookup to get the resolved type.
-    return GenerateLoadIfNeeded(TypeOf(expr), id);
-  }
-  return 0;
-}
-
-uint32_t Builder::GenerateLoadIfNeeded(const sem::Type* type, uint32_t id) {
-  if (auto* ref = type->As<sem::Reference>()) {
-    type = ref->StoreType();
-  } else {
-    return id;
-  }
-
-  auto type_id = GenerateTypeIfNeeded(type);
-  auto result = result_op();
-  auto result_id = result.to_i();
-  if (!push_function_inst(spv::Op::OpLoad,
-                          {Operand::Int(type_id), result, Operand::Int(id)})) {
-    return 0;
-  }
-  return result_id;
-}
-
-uint32_t Builder::GenerateUnaryOpExpression(
-    const ast::UnaryOpExpression* expr) {
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  spv::Op op = spv::Op::OpNop;
-  switch (expr->op) {
-    case ast::UnaryOp::kComplement:
-      op = spv::Op::OpNot;
-      break;
-    case ast::UnaryOp::kNegation:
-      if (TypeOf(expr)->is_float_scalar_or_vector()) {
-        op = spv::Op::OpFNegate;
-      } else {
-        op = spv::Op::OpSNegate;
-      }
-      break;
-    case ast::UnaryOp::kNot:
-      op = spv::Op::OpLogicalNot;
-      break;
-    case ast::UnaryOp::kAddressOf:
-    case ast::UnaryOp::kIndirection:
-      // Address-of converts a reference to a pointer, and dereference converts
-      // a pointer to a reference. These are the same thing in SPIR-V, so this
-      // is a no-op.
-      return GenerateExpression(expr->expr);
-  }
-
-  auto val_id = GenerateExpressionWithLoadIfNeeded(expr->expr);
-  if (val_id == 0) {
-    return 0;
-  }
-
-  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
-  if (type_id == 0) {
-    return 0;
-  }
-
-  if (!push_function_inst(
-          op, {Operand::Int(type_id), result, Operand::Int(val_id)})) {
-    return false;
-  }
-
-  return result_id;
-}
-
-uint32_t Builder::GetGLSLstd450Import() {
-  auto where = import_name_to_id_.find(kGLSLstd450);
-  if (where != import_name_to_id_.end()) {
-    return where->second;
-  }
-
-  // It doesn't exist yet. Generate it.
-  auto result = result_op();
-  auto id = result.to_i();
-
-  push_ext_import(spv::Op::OpExtInstImport,
-                  {result, Operand::String(kGLSLstd450)});
-
-  // Remember it for later.
-  import_name_to_id_[kGLSLstd450] = id;
-  return id;
-}
-
-uint32_t Builder::GenerateConstructorExpression(const ast::Variable* var,
-                                                const ast::Expression* expr) {
-  if (auto* literal = expr->As<ast::LiteralExpression>()) {
-    return GenerateLiteralIfNeeded(var, literal);
-  }
-  if (auto* call = builder_.Sem().Get<sem::Call>(expr)) {
-    if (call->Target()->IsAnyOf<sem::TypeConstructor, sem::TypeConversion>()) {
-      return GenerateTypeConstructorOrConversion(call, var);
-    }
-  }
-  error_ = "unknown constructor expression";
-  return 0;
-}
-
-bool Builder::IsConstructorConst(const ast::Expression* expr) {
-  bool is_const = true;
-  ast::TraverseExpressions(expr, builder_.Diagnostics(),
-                           [&](const ast::Expression* e) {
-                             if (e->Is<ast::LiteralExpression>()) {
-                               return ast::TraverseAction::Descend;
-                             }
-                             if (auto* ce = e->As<ast::CallExpression>()) {
-                               auto* call = builder_.Sem().Get(ce);
-                               if (call->Target()->Is<sem::TypeConstructor>()) {
-                                 return ast::TraverseAction::Descend;
-                               }
-                             }
-
-                             is_const = false;
-                             return ast::TraverseAction::Stop;
-                           });
-  return is_const;
-}
-
-uint32_t Builder::GenerateTypeConstructorOrConversion(
-    const sem::Call* call,
-    const ast::Variable* var) {
-  auto& args = call->Arguments();
-  auto* global_var = builder_.Sem().Get<sem::GlobalVariable>(var);
-  auto* result_type = call->Type();
-
-  // Generate the zero initializer if there are no values provided.
-  if (args.empty()) {
-    if (global_var && global_var->IsOverridable()) {
-      auto constant_id = global_var->ConstantId();
-      if (result_type->Is<sem::I32>()) {
-        return GenerateConstantIfNeeded(
-            ScalarConstant::I32(0).AsSpecOp(constant_id));
-      }
-      if (result_type->Is<sem::U32>()) {
-        return GenerateConstantIfNeeded(
-            ScalarConstant::U32(0).AsSpecOp(constant_id));
-      }
-      if (result_type->Is<sem::F32>()) {
-        return GenerateConstantIfNeeded(
-            ScalarConstant::F32(0).AsSpecOp(constant_id));
-      }
-      if (result_type->Is<sem::Bool>()) {
-        return GenerateConstantIfNeeded(
-            ScalarConstant::Bool(false).AsSpecOp(constant_id));
-      }
-    }
-    return GenerateConstantNullIfNeeded(result_type->UnwrapRef());
-  }
-
-  std::ostringstream out;
-  out << "__const_" << result_type->FriendlyName(builder_.Symbols()) << "_";
-
-  result_type = result_type->UnwrapRef();
-  bool constructor_is_const = IsConstructorConst(call->Declaration());
-  if (has_error()) {
-    return 0;
-  }
-
-  bool can_cast_or_copy = result_type->is_scalar();
-
-  if (auto* res_vec = result_type->As<sem::Vector>()) {
-    if (res_vec->type()->is_scalar()) {
-      auto* value_type = args[0]->Type()->UnwrapRef();
-      if (auto* val_vec = value_type->As<sem::Vector>()) {
-        if (val_vec->type()->is_scalar()) {
-          can_cast_or_copy = res_vec->Width() == val_vec->Width();
-        }
-      }
-    }
-  }
-
-  if (can_cast_or_copy) {
-    return GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(),
-                                           global_var);
-  }
-
-  auto type_id = GenerateTypeIfNeeded(result_type);
-  if (type_id == 0) {
-    return 0;
-  }
-
-  bool result_is_constant_composite = constructor_is_const;
-  bool result_is_spec_composite = false;
-
-  if (auto* vec = result_type->As<sem::Vector>()) {
-    result_type = vec->type();
-  }
-
-  OperandList ops;
-  for (auto* e : args) {
-    uint32_t id = 0;
-    id = GenerateExpressionWithLoadIfNeeded(e);
-    if (id == 0) {
-      return 0;
-    }
-
-    auto* value_type = e->Type()->UnwrapRef();
-    // If the result and value types are the same we can just use the object.
-    // If the result is not a vector then we should have validated that the
-    // value type is a correctly sized vector so we can just use it directly.
-    if (result_type == value_type || result_type->Is<sem::Matrix>() ||
-        result_type->Is<sem::Array>() || result_type->Is<sem::Struct>()) {
-      out << "_" << id;
-
-      ops.push_back(Operand::Int(id));
-      continue;
-    }
-
-    // Both scalars, but not the same type so we need to generate a conversion
-    // of the value.
-    if (value_type->is_scalar() && result_type->is_scalar()) {
-      id = GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(),
-                                           global_var);
-      out << "_" << id;
-      ops.push_back(Operand::Int(id));
-      continue;
-    }
-
-    // When handling vectors as the values there a few cases to take into
-    // consideration:
-    //  1. Module scoped vec3<f32>(vec2<f32>(1, 2), 3)  -> OpSpecConstantOp
-    //  2. Function scoped vec3<f32>(vec2<f32>(1, 2), 3) ->  OpCompositeExtract
-    //  3. Either array<vec3<f32>, 1>(vec3<f32>(1, 2, 3))  -> use the ID.
-    //       -> handled above
-    //
-    // For cases 1 and 2, if the type is different we also may need to insert
-    // a type cast.
-    if (auto* vec = value_type->As<sem::Vector>()) {
-      auto* vec_type = vec->type();
-
-      auto value_type_id = GenerateTypeIfNeeded(vec_type);
-      if (value_type_id == 0) {
-        return 0;
-      }
-
-      for (uint32_t i = 0; i < vec->Width(); ++i) {
-        auto extract = result_op();
-        auto extract_id = extract.to_i();
-
-        if (!global_var) {
-          // A non-global initializer. Case 2.
-          if (!push_function_inst(spv::Op::OpCompositeExtract,
-                                  {Operand::Int(value_type_id), extract,
-                                   Operand::Int(id), Operand::Int(i)})) {
-            return false;
-          }
-
-          // We no longer have a constant composite, but have to do a
-          // composite construction as these calls are inside a function.
-          result_is_constant_composite = false;
-        } else {
-          // A global initializer, must use OpSpecConstantOp. Case 1.
-          auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(i));
-          if (idx_id == 0) {
-            return 0;
-          }
-          push_type(spv::Op::OpSpecConstantOp,
-                    {Operand::Int(value_type_id), extract,
-                     Operand::Int(SpvOpCompositeExtract), Operand::Int(id),
-                     Operand::Int(idx_id)});
-
-          result_is_spec_composite = true;
-        }
-
-        out << "_" << extract_id;
-        ops.push_back(Operand::Int(extract_id));
-      }
-    } else {
-      error_ = "Unhandled type cast value type";
-      return 0;
-    }
-  }
-
-  // For a single-value vector initializer, splat the initializer value.
-  auto* const init_result_type = call->Type()->UnwrapRef();
-  if (args.size() == 1 && init_result_type->is_scalar_vector() &&
-      args[0]->Type()->UnwrapRef()->is_scalar()) {
-    size_t vec_size = init_result_type->As<sem::Vector>()->Width();
-    for (size_t i = 0; i < (vec_size - 1); ++i) {
-      ops.push_back(ops[0]);
-    }
-  }
-
-  auto str = out.str();
-  auto val = type_constructor_to_id_.find(str);
-  if (val != type_constructor_to_id_.end()) {
-    return val->second;
-  }
-
-  auto result = result_op();
-  ops.insert(ops.begin(), result);
-  ops.insert(ops.begin(), Operand::Int(type_id));
-
-  type_constructor_to_id_[str] = result.to_i();
-
-  if (result_is_spec_composite) {
-    push_type(spv::Op::OpSpecConstantComposite, ops);
-  } else if (result_is_constant_composite) {
-    push_type(spv::Op::OpConstantComposite, ops);
-  } else {
-    if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
-      return 0;
-    }
-  }
-
-  return result.to_i();
-}
-
-uint32_t Builder::GenerateCastOrCopyOrPassthrough(
-    const sem::Type* to_type,
-    const ast::Expression* from_expr,
-    bool is_global_init) {
-  // This should not happen as we rely on constant folding to obviate
-  // casts/conversions for module-scope variables
-  if (is_global_init) {
-    TINT_ICE(Writer, builder_.Diagnostics())
-        << "Module-level conversions are not supported. Conversions should "
-           "have already been constant-folded by the FoldConstants transform.";
-    return 0;
-  }
-
-  auto elem_type_of = [](const sem::Type* t) -> const sem::Type* {
-    if (t->is_scalar()) {
-      return t;
-    }
-    if (auto* v = t->As<sem::Vector>()) {
-      return v->type();
-    }
-    return nullptr;
-  };
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  auto result_type_id = GenerateTypeIfNeeded(to_type);
-  if (result_type_id == 0) {
-    return 0;
-  }
-
-  auto val_id = GenerateExpressionWithLoadIfNeeded(from_expr);
-  if (val_id == 0) {
-    return 0;
-  }
-
-  auto* from_type = TypeOf(from_expr)->UnwrapRef();
-
-  spv::Op op = spv::Op::OpNop;
-  if ((from_type->Is<sem::I32>() && to_type->Is<sem::F32>()) ||
-      (from_type->is_signed_integer_vector() && to_type->is_float_vector())) {
-    op = spv::Op::OpConvertSToF;
-  } else if ((from_type->Is<sem::U32>() && to_type->Is<sem::F32>()) ||
-             (from_type->is_unsigned_integer_vector() &&
-              to_type->is_float_vector())) {
-    op = spv::Op::OpConvertUToF;
-  } else if ((from_type->Is<sem::F32>() && to_type->Is<sem::I32>()) ||
-             (from_type->is_float_vector() &&
-              to_type->is_signed_integer_vector())) {
-    op = spv::Op::OpConvertFToS;
-  } else if ((from_type->Is<sem::F32>() && to_type->Is<sem::U32>()) ||
-             (from_type->is_float_vector() &&
-              to_type->is_unsigned_integer_vector())) {
-    op = spv::Op::OpConvertFToU;
-  } else if ((from_type->Is<sem::Bool>() && to_type->Is<sem::Bool>()) ||
-             (from_type->Is<sem::U32>() && to_type->Is<sem::U32>()) ||
-             (from_type->Is<sem::I32>() && to_type->Is<sem::I32>()) ||
-             (from_type->Is<sem::F32>() && to_type->Is<sem::F32>()) ||
-             (from_type->Is<sem::Vector>() && (from_type == to_type))) {
-    return val_id;
-  } else if ((from_type->Is<sem::I32>() && to_type->Is<sem::U32>()) ||
-             (from_type->Is<sem::U32>() && to_type->Is<sem::I32>()) ||
-             (from_type->is_signed_integer_vector() &&
-              to_type->is_unsigned_integer_vector()) ||
-             (from_type->is_unsigned_integer_vector() &&
-              to_type->is_integer_scalar_or_vector())) {
-    op = spv::Op::OpBitcast;
-  } else if ((from_type->is_numeric_scalar() && to_type->Is<sem::Bool>()) ||
-             (from_type->is_numeric_vector() && to_type->is_bool_vector())) {
-    // Convert scalar (vector) to bool (vector)
-
-    // Return the result of comparing from_expr with zero
-    uint32_t zero = GenerateConstantNullIfNeeded(from_type);
-    const auto* from_elem_type = elem_type_of(from_type);
-    op = from_elem_type->is_integer_scalar() ? spv::Op::OpINotEqual
-                                             : spv::Op::OpFUnordNotEqual;
-    if (!push_function_inst(
-            op, {Operand::Int(result_type_id), Operand::Int(result_id),
-                 Operand::Int(val_id), Operand::Int(zero)})) {
-      return 0;
-    }
-
-    return result_id;
-  } else if (from_type->is_bool_scalar_or_vector() &&
-             to_type->is_numeric_scalar_or_vector()) {
-    // Convert bool scalar/vector to numeric scalar/vector.
-    // Use the bool to select between 1 (if true) and 0 (if false).
-
-    const auto* to_elem_type = elem_type_of(to_type);
-    uint32_t one_id;
-    uint32_t zero_id;
-    if (to_elem_type->Is<sem::F32>()) {
-      ast::FloatLiteralExpression one(ProgramID(), Source{}, 1.0f);
-      ast::FloatLiteralExpression zero(ProgramID(), Source{}, 0.0f);
-      one_id = GenerateLiteralIfNeeded(nullptr, &one);
-      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
-    } else if (to_elem_type->Is<sem::U32>()) {
-      ast::UintLiteralExpression one(ProgramID(), Source{}, 1);
-      ast::UintLiteralExpression zero(ProgramID(), Source{}, 0);
-      one_id = GenerateLiteralIfNeeded(nullptr, &one);
-      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
-    } else if (to_elem_type->Is<sem::I32>()) {
-      ast::SintLiteralExpression one(ProgramID(), Source{}, 1);
-      ast::SintLiteralExpression zero(ProgramID(), Source{}, 0);
-      one_id = GenerateLiteralIfNeeded(nullptr, &one);
-      zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
-    } else {
-      error_ = "invalid destination type for bool conversion";
-      return false;
-    }
-    if (auto* to_vec = to_type->As<sem::Vector>()) {
-      // Splat the scalars into vectors.
-      one_id = GenerateConstantVectorSplatIfNeeded(to_vec, one_id);
-      zero_id = GenerateConstantVectorSplatIfNeeded(to_vec, zero_id);
-    }
-    if (!one_id || !zero_id) {
-      return false;
-    }
-
-    op = spv::Op::OpSelect;
-    if (!push_function_inst(
-            op, {Operand::Int(result_type_id), Operand::Int(result_id),
-                 Operand::Int(val_id), Operand::Int(one_id),
-                 Operand::Int(zero_id)})) {
-      return 0;
-    }
-
-    return result_id;
-  } else {
-    TINT_ICE(Writer, builder_.Diagnostics()) << "Invalid from_type";
-  }
-
-  if (op == spv::Op::OpNop) {
-    error_ = "unable to determine conversion type for cast, from: " +
-             from_type->type_name() + " to: " + to_type->type_name();
-    return 0;
-  }
-
-  if (!push_function_inst(
-          op, {Operand::Int(result_type_id), result, Operand::Int(val_id)})) {
-    return 0;
-  }
-
-  return result_id;
-}
-
-uint32_t Builder::GenerateLiteralIfNeeded(const ast::Variable* var,
-                                          const ast::LiteralExpression* lit) {
-  ScalarConstant constant;
-
-  auto* global = builder_.Sem().Get<sem::GlobalVariable>(var);
-  if (global && global->IsOverridable()) {
-    constant.is_spec_op = true;
-    constant.constant_id = global->ConstantId();
-  }
-
-  Switch(
-      lit,
-      [&](const ast::BoolLiteralExpression* l) {
-        constant.kind = ScalarConstant::Kind::kBool;
-        constant.value.b = l->value;
-      },
-      [&](const ast::SintLiteralExpression* sl) {
-        constant.kind = ScalarConstant::Kind::kI32;
-        constant.value.i32 = sl->value;
-      },
-      [&](const ast::UintLiteralExpression* ul) {
-        constant.kind = ScalarConstant::Kind::kU32;
-        constant.value.u32 = ul->value;
-      },
-      [&](const ast::FloatLiteralExpression* fl) {
-        constant.kind = ScalarConstant::Kind::kF32;
-        constant.value.f32 = fl->value;
-      },
-      [&](Default) { error_ = "unknown literal type"; });
-
-  if (!error_.empty()) {
-    return false;
-  }
-
-  return GenerateConstantIfNeeded(constant);
-}
-
-uint32_t Builder::GenerateConstantIfNeeded(const ScalarConstant& constant) {
-  auto it = const_to_id_.find(constant);
-  if (it != const_to_id_.end()) {
-    return it->second;
-  }
-
-  uint32_t type_id = 0;
-
-  switch (constant.kind) {
-    case ScalarConstant::Kind::kU32: {
-      type_id = GenerateTypeIfNeeded(builder_.create<sem::U32>());
-      break;
-    }
-    case ScalarConstant::Kind::kI32: {
-      type_id = GenerateTypeIfNeeded(builder_.create<sem::I32>());
-      break;
-    }
-    case ScalarConstant::Kind::kF32: {
-      type_id = GenerateTypeIfNeeded(builder_.create<sem::F32>());
-      break;
-    }
-    case ScalarConstant::Kind::kBool: {
-      type_id = GenerateTypeIfNeeded(builder_.create<sem::Bool>());
-      break;
-    }
-  }
-
-  if (type_id == 0) {
-    return 0;
-  }
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  if (constant.is_spec_op) {
-    push_annot(spv::Op::OpDecorate,
-               {Operand::Int(result_id), Operand::Int(SpvDecorationSpecId),
-                Operand::Int(constant.constant_id)});
-  }
-
-  switch (constant.kind) {
-    case ScalarConstant::Kind::kU32: {
-      push_type(
-          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
-          {Operand::Int(type_id), result, Operand::Int(constant.value.u32)});
-      break;
-    }
-    case ScalarConstant::Kind::kI32: {
-      push_type(
-          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
-          {Operand::Int(type_id), result, Operand::Int(constant.value.i32)});
-      break;
-    }
-    case ScalarConstant::Kind::kF32: {
-      push_type(
-          constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
-          {Operand::Int(type_id), result, Operand::Float(constant.value.f32)});
-      break;
-    }
-    case ScalarConstant::Kind::kBool: {
-      if (constant.value.b) {
-        push_type(constant.is_spec_op ? spv::Op::OpSpecConstantTrue
-                                      : spv::Op::OpConstantTrue,
-                  {Operand::Int(type_id), result});
-      } else {
-        push_type(constant.is_spec_op ? spv::Op::OpSpecConstantFalse
-                                      : spv::Op::OpConstantFalse,
-                  {Operand::Int(type_id), result});
-      }
-      break;
-    }
-  }
-
-  const_to_id_[constant] = result_id;
-  return result_id;
-}
-
-uint32_t Builder::GenerateConstantNullIfNeeded(const sem::Type* type) {
-  auto type_id = GenerateTypeIfNeeded(type);
-  if (type_id == 0) {
-    return 0;
-  }
-
-  auto name = type->type_name();
-
-  auto it = const_null_to_id_.find(name);
-  if (it != const_null_to_id_.end()) {
-    return it->second;
-  }
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  push_type(spv::Op::OpConstantNull, {Operand::Int(type_id), result});
-
-  const_null_to_id_[name] = result_id;
-  return result_id;
-}
-
-uint32_t Builder::GenerateConstantVectorSplatIfNeeded(const sem::Vector* type,
-                                                      uint32_t value_id) {
-  auto type_id = GenerateTypeIfNeeded(type);
-  if (type_id == 0 || value_id == 0) {
-    return 0;
-  }
-
-  uint64_t key = (static_cast<uint64_t>(type->Width()) << 32) + value_id;
-  return utils::GetOrCreate(const_splat_to_id_, key, [&] {
-    auto result = result_op();
-    auto result_id = result.to_i();
-
-    OperandList ops;
-    ops.push_back(Operand::Int(type_id));
-    ops.push_back(result);
-    for (uint32_t i = 0; i < type->Width(); i++) {
-      ops.push_back(Operand::Int(value_id));
-    }
-    push_type(spv::Op::OpConstantComposite, ops);
-
-    const_splat_to_id_[key] = result_id;
-    return result_id;
-  });
-}
-
-uint32_t Builder::GenerateShortCircuitBinaryExpression(
-    const ast::BinaryExpression* expr) {
-  auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
-  if (lhs_id == 0) {
-    return false;
-  }
-
-  // Get the ID of the basic block where control flow will diverge. It's the
-  // last basic block generated for the left-hand-side of the operator.
-  auto original_label_id = current_label_id_;
-
-  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
-  if (type_id == 0) {
-    return 0;
-  }
-
-  auto merge_block = result_op();
-  auto merge_block_id = merge_block.to_i();
-
-  auto block = result_op();
-  auto block_id = block.to_i();
-
-  auto true_block_id = block_id;
-  auto false_block_id = merge_block_id;
-
-  // For a logical or we want to only check the RHS if the LHS is failed.
-  if (expr->IsLogicalOr()) {
-    std::swap(true_block_id, false_block_id);
-  }
-
-  if (!push_function_inst(spv::Op::OpSelectionMerge,
-                          {Operand::Int(merge_block_id),
-                           Operand::Int(SpvSelectionControlMaskNone)})) {
-    return 0;
-  }
-  if (!push_function_inst(spv::Op::OpBranchConditional,
-                          {Operand::Int(lhs_id), Operand::Int(true_block_id),
-                           Operand::Int(false_block_id)})) {
-    return 0;
-  }
-
-  // Output block to check the RHS
-  if (!GenerateLabel(block_id)) {
-    return 0;
-  }
-  auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
-  if (rhs_id == 0) {
-    return 0;
-  }
-
-  // Get the block ID of the last basic block generated for the right-hand-side
-  // expression. That block will be an immediate predecessor to the merge block.
-  auto rhs_block_id = current_label_id_;
-  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(merge_block_id)})) {
-    return 0;
-  }
-
-  // Output the merge block
-  if (!GenerateLabel(merge_block_id)) {
-    return 0;
-  }
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  if (!push_function_inst(spv::Op::OpPhi,
-                          {Operand::Int(type_id), result, Operand::Int(lhs_id),
-                           Operand::Int(original_label_id),
-                           Operand::Int(rhs_id), Operand::Int(rhs_block_id)})) {
-    return 0;
-  }
-
-  return result_id;
-}
-
-uint32_t Builder::GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type) {
-  // Create a new vector to splat scalar into
-  auto splat_vector = result_op();
-  auto* splat_vector_type = builder_.create<sem::Pointer>(
-      vec_type, ast::StorageClass::kFunction, ast::Access::kReadWrite);
-  push_function_var(
-      {Operand::Int(GenerateTypeIfNeeded(splat_vector_type)), splat_vector,
-       Operand::Int(ConvertStorageClass(ast::StorageClass::kFunction)),
-       Operand::Int(GenerateConstantNullIfNeeded(vec_type))});
-
-  // Splat scalar into vector
-  auto splat_result = result_op();
-  OperandList ops;
-  ops.push_back(Operand::Int(GenerateTypeIfNeeded(vec_type)));
-  ops.push_back(splat_result);
-  for (size_t i = 0; i < vec_type->As<sem::Vector>()->Width(); ++i) {
-    ops.push_back(Operand::Int(scalar_id));
-  }
-  if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
-    return 0;
-  }
-
-  return splat_result.to_i();
-}
-
-uint32_t Builder::GenerateMatrixAddOrSub(uint32_t lhs_id,
-                                         uint32_t rhs_id,
-                                         const sem::Matrix* type,
-                                         spv::Op op) {
-  // Example addition of two matrices:
-  // %31 = OpLoad %mat3v4float %m34
-  // %32 = OpLoad %mat3v4float %m34
-  // %33 = OpCompositeExtract %v4float %31 0
-  // %34 = OpCompositeExtract %v4float %32 0
-  // %35 = OpFAdd %v4float %33 %34
-  // %36 = OpCompositeExtract %v4float %31 1
-  // %37 = OpCompositeExtract %v4float %32 1
-  // %38 = OpFAdd %v4float %36 %37
-  // %39 = OpCompositeExtract %v4float %31 2
-  // %40 = OpCompositeExtract %v4float %32 2
-  // %41 = OpFAdd %v4float %39 %40
-  // %42 = OpCompositeConstruct %mat3v4float %35 %38 %41
-
-  auto* column_type = builder_.create<sem::Vector>(type->type(), type->rows());
-  auto column_type_id = GenerateTypeIfNeeded(column_type);
-
-  OperandList ops;
-
-  for (uint32_t i = 0; i < type->columns(); ++i) {
-    // Extract column `i` from lhs mat
-    auto lhs_column_id = result_op();
-    if (!push_function_inst(spv::Op::OpCompositeExtract,
-                            {Operand::Int(column_type_id), lhs_column_id,
-                             Operand::Int(lhs_id), Operand::Int(i)})) {
-      return 0;
-    }
-
-    // Extract column `i` from rhs mat
-    auto rhs_column_id = result_op();
-    if (!push_function_inst(spv::Op::OpCompositeExtract,
-                            {Operand::Int(column_type_id), rhs_column_id,
-                             Operand::Int(rhs_id), Operand::Int(i)})) {
-      return 0;
-    }
-
-    // Add or subtract the two columns
-    auto result = result_op();
-    if (!push_function_inst(op, {Operand::Int(column_type_id), result,
-                                 lhs_column_id, rhs_column_id})) {
-      return 0;
-    }
-
-    ops.push_back(result);
-  }
-
-  // Create the result matrix from the added/subtracted column vectors
-  auto result_mat_id = result_op();
-  ops.insert(ops.begin(), result_mat_id);
-  ops.insert(ops.begin(), Operand::Int(GenerateTypeIfNeeded(type)));
-  if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
-    return 0;
-  }
-
-  return result_mat_id.to_i();
-}
-
-uint32_t Builder::GenerateBinaryExpression(const ast::BinaryExpression* expr) {
-  // There is special logic for short circuiting operators.
-  if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
-    return GenerateShortCircuitBinaryExpression(expr);
-  }
-
-  auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
-  if (lhs_id == 0) {
-    return 0;
-  }
-
-  auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
-  if (rhs_id == 0) {
-    return 0;
-  }
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
-  if (type_id == 0) {
-    return 0;
-  }
-
-  // Handle int and float and the vectors of those types. Other types
-  // should have been rejected by validation.
-  auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
-  auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
-
-  // Handle matrix-matrix addition and subtraction
-  if ((expr->IsAdd() || expr->IsSubtract()) && lhs_type->is_float_matrix() &&
-      rhs_type->is_float_matrix()) {
-    auto* lhs_mat = lhs_type->As<sem::Matrix>();
-    auto* rhs_mat = rhs_type->As<sem::Matrix>();
-
-    // This should already have been validated by resolver
-    if (lhs_mat->rows() != rhs_mat->rows() ||
-        lhs_mat->columns() != rhs_mat->columns()) {
-      error_ = "matrices must have same dimensionality for add or subtract";
-      return 0;
-    }
-
-    return GenerateMatrixAddOrSub(
-        lhs_id, rhs_id, lhs_mat,
-        expr->IsAdd() ? spv::Op::OpFAdd : spv::Op::OpFSub);
-  }
-
-  // For vector-scalar arithmetic operations, splat scalar into a vector. We
-  // skip this for multiply as we can use OpVectorTimesScalar.
-  const bool is_float_scalar_vector_multiply =
-      expr->IsMultiply() &&
-      ((lhs_type->is_float_scalar() && rhs_type->is_float_vector()) ||
-       (lhs_type->is_float_vector() && rhs_type->is_float_scalar()));
-
-  if (expr->IsArithmetic() && !is_float_scalar_vector_multiply) {
-    if (lhs_type->Is<sem::Vector>() && rhs_type->is_numeric_scalar()) {
-      uint32_t splat_vector_id = GenerateSplat(rhs_id, lhs_type);
-      if (splat_vector_id == 0) {
-        return 0;
-      }
-      rhs_id = splat_vector_id;
-      rhs_type = lhs_type;
-
-    } else if (lhs_type->is_numeric_scalar() && rhs_type->Is<sem::Vector>()) {
-      uint32_t splat_vector_id = GenerateSplat(lhs_id, rhs_type);
-      if (splat_vector_id == 0) {
-        return 0;
-      }
-      lhs_id = splat_vector_id;
-      lhs_type = rhs_type;
-    }
-  }
-
-  bool lhs_is_float_or_vec = lhs_type->is_float_scalar_or_vector();
-  bool lhs_is_bool_or_vec = lhs_type->is_bool_scalar_or_vector();
-  bool lhs_is_integer_or_vec = lhs_type->is_integer_scalar_or_vector();
-  bool lhs_is_unsigned = lhs_type->is_unsigned_scalar_or_vector();
-
-  spv::Op op = spv::Op::OpNop;
-  if (expr->IsAnd()) {
-    if (lhs_is_integer_or_vec) {
-      op = spv::Op::OpBitwiseAnd;
-    } else if (lhs_is_bool_or_vec) {
-      op = spv::Op::OpLogicalAnd;
-    } else {
-      error_ = "invalid and expression";
-      return 0;
-    }
-  } else if (expr->IsAdd()) {
-    op = lhs_is_float_or_vec ? spv::Op::OpFAdd : spv::Op::OpIAdd;
-  } else if (expr->IsDivide()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFDiv;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpUDiv;
-    } else {
-      op = spv::Op::OpSDiv;
-    }
-  } else if (expr->IsEqual()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdEqual;
-    } else if (lhs_is_bool_or_vec) {
-      op = spv::Op::OpLogicalEqual;
-    } else if (lhs_is_integer_or_vec) {
-      op = spv::Op::OpIEqual;
-    } else {
-      error_ = "invalid equal expression";
-      return 0;
-    }
-  } else if (expr->IsGreaterThan()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdGreaterThan;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpUGreaterThan;
-    } else {
-      op = spv::Op::OpSGreaterThan;
-    }
-  } else if (expr->IsGreaterThanEqual()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdGreaterThanEqual;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpUGreaterThanEqual;
-    } else {
-      op = spv::Op::OpSGreaterThanEqual;
-    }
-  } else if (expr->IsLessThan()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdLessThan;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpULessThan;
-    } else {
-      op = spv::Op::OpSLessThan;
-    }
-  } else if (expr->IsLessThanEqual()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdLessThanEqual;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpULessThanEqual;
-    } else {
-      op = spv::Op::OpSLessThanEqual;
-    }
-  } else if (expr->IsModulo()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFRem;
-    } else if (lhs_is_unsigned) {
-      op = spv::Op::OpUMod;
-    } else {
-      op = spv::Op::OpSMod;
-    }
-  } else if (expr->IsMultiply()) {
-    if (lhs_type->is_integer_scalar_or_vector()) {
-      // If the left hand side is an integer then this _has_ to be OpIMul as
-      // there there is no other integer multiplication.
-      op = spv::Op::OpIMul;
-    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_scalar()) {
-      // Float scalars multiply with OpFMul
-      op = spv::Op::OpFMul;
-    } else if (lhs_type->is_float_vector() && rhs_type->is_float_vector()) {
-      // Float vectors must be validated to be the same size and then use OpFMul
-      op = spv::Op::OpFMul;
-    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_vector()) {
-      // Scalar * Vector we need to flip lhs and rhs types
-      // because OpVectorTimesScalar expects <vector>, <scalar>
-      std::swap(lhs_id, rhs_id);
-      op = spv::Op::OpVectorTimesScalar;
-    } else if (lhs_type->is_float_vector() && rhs_type->is_float_scalar()) {
-      // float vector * scalar
-      op = spv::Op::OpVectorTimesScalar;
-    } else if (lhs_type->is_float_scalar() && rhs_type->is_float_matrix()) {
-      // Scalar * Matrix we need to flip lhs and rhs types because
-      // OpMatrixTimesScalar expects <matrix>, <scalar>
-      std::swap(lhs_id, rhs_id);
-      op = spv::Op::OpMatrixTimesScalar;
-    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_scalar()) {
-      // float matrix * scalar
-      op = spv::Op::OpMatrixTimesScalar;
-    } else if (lhs_type->is_float_vector() && rhs_type->is_float_matrix()) {
-      // float vector * matrix
-      op = spv::Op::OpVectorTimesMatrix;
-    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_vector()) {
-      // float matrix * vector
-      op = spv::Op::OpMatrixTimesVector;
-    } else if (lhs_type->is_float_matrix() && rhs_type->is_float_matrix()) {
-      // float matrix * matrix
-      op = spv::Op::OpMatrixTimesMatrix;
-    } else {
-      error_ = "invalid multiply expression";
-      return 0;
-    }
-  } else if (expr->IsNotEqual()) {
-    if (lhs_is_float_or_vec) {
-      op = spv::Op::OpFOrdNotEqual;
-    } else if (lhs_is_bool_or_vec) {
-      op = spv::Op::OpLogicalNotEqual;
-    } else if (lhs_is_integer_or_vec) {
-      op = spv::Op::OpINotEqual;
-    } else {
-      error_ = "invalid not-equal expression";
-      return 0;
-    }
-  } else if (expr->IsOr()) {
-    if (lhs_is_integer_or_vec) {
-      op = spv::Op::OpBitwiseOr;
-    } else if (lhs_is_bool_or_vec) {
-      op = spv::Op::OpLogicalOr;
-    } else {
-      error_ = "invalid and expression";
-      return 0;
-    }
-  } else if (expr->IsShiftLeft()) {
-    op = spv::Op::OpShiftLeftLogical;
-  } else if (expr->IsShiftRight() && lhs_type->is_signed_scalar_or_vector()) {
-    // A shift right with a signed LHS is an arithmetic shift.
-    op = spv::Op::OpShiftRightArithmetic;
-  } else if (expr->IsShiftRight()) {
-    op = spv::Op::OpShiftRightLogical;
-  } else if (expr->IsSubtract()) {
-    op = lhs_is_float_or_vec ? spv::Op::OpFSub : spv::Op::OpISub;
-  } else if (expr->IsXor()) {
-    op = spv::Op::OpBitwiseXor;
-  } else {
-    error_ = "unknown binary expression";
-    return 0;
-  }
-
-  if (!push_function_inst(op, {Operand::Int(type_id), result,
-                               Operand::Int(lhs_id), Operand::Int(rhs_id)})) {
-    return 0;
-  }
-  return result_id;
-}
-
-bool Builder::GenerateBlockStatement(const ast::BlockStatement* stmt) {
-  scope_stack_.Push();
-  TINT_DEFER(scope_stack_.Pop());
-  return GenerateBlockStatementWithoutScoping(stmt);
-}
-
-bool Builder::GenerateBlockStatementWithoutScoping(
-    const ast::BlockStatement* stmt) {
-  for (auto* block_stmt : stmt->statements) {
-    if (!GenerateStatement(block_stmt)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-uint32_t Builder::GenerateCallExpression(const ast::CallExpression* expr) {
-  auto* call = builder_.Sem().Get(expr);
-  auto* target = call->Target();
-  return Switch(
-      target,
-      [&](const sem::Function* func) {
-        return GenerateFunctionCall(call, func);
-      },
-      [&](const sem::Builtin* builtin) {
-        return GenerateBuiltinCall(call, builtin);
-      },
-      [&](const sem::TypeConversion*) {
-        return GenerateTypeConstructorOrConversion(call, nullptr);
-      },
-      [&](const sem::TypeConstructor*) {
-        return GenerateTypeConstructorOrConversion(call, nullptr);
-      },
-      [&](Default) -> uint32_t {
-        TINT_ICE(Writer, builder_.Diagnostics())
-            << "unhandled call target: " << target->TypeInfo().name;
-        return 0;
-      });
-}
-
-uint32_t Builder::GenerateFunctionCall(const sem::Call* call,
-                                       const sem::Function*) {
-  auto* expr = call->Declaration();
-  auto* ident = expr->target.name;
-
-  auto type_id = GenerateTypeIfNeeded(call->Type());
-  if (type_id == 0) {
-    return 0;
-  }
-
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  OperandList ops = {Operand::Int(type_id), result};
-
-  auto func_id = func_symbol_to_id_[ident->symbol];
-  if (func_id == 0) {
-    error_ = "unable to find called function: " +
-             builder_.Symbols().NameFor(ident->symbol);
-    return 0;
-  }
-  ops.push_back(Operand::Int(func_id));
-
-  size_t arg_idx = 0;
-  for (auto* arg : expr->args) {
-    auto id = GenerateExpressionWithLoadIfNeeded(arg);
-    if (id == 0) {
-      return 0;
-    }
-    ops.push_back(Operand::Int(id));
-    arg_idx++;
-  }
-
-  if (!push_function_inst(spv::Op::OpFunctionCall, std::move(ops))) {
-    return 0;
-  }
-
-  return result_id;
-}
-
-uint32_t Builder::GenerateBuiltinCall(const sem::Call* call,
-                                      const sem::Builtin* builtin) {
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  auto result_type_id = GenerateTypeIfNeeded(builtin->ReturnType());
-  if (result_type_id == 0) {
-    return 0;
-  }
-
-  if (builtin->IsFineDerivative() || builtin->IsCoarseDerivative()) {
-    push_capability(SpvCapabilityDerivativeControl);
-  }
-
-  if (builtin->IsImageQuery()) {
-    push_capability(SpvCapabilityImageQuery);
-  }
-
-  if (builtin->IsTexture()) {
-    if (!GenerateTextureBuiltin(call, builtin, Operand::Int(result_type_id),
-                                result)) {
-      return 0;
-    }
-    return result_id;
-  }
-
-  if (builtin->IsBarrier()) {
-    if (!GenerateControlBarrierBuiltin(builtin)) {
-      return 0;
-    }
-    return result_id;
-  }
-
-  if (builtin->IsAtomic()) {
-    if (!GenerateAtomicBuiltin(call, builtin, Operand::Int(result_type_id),
-                               result)) {
-      return 0;
-    }
-    return result_id;
-  }
-
-  // Generates the SPIR-V ID for the expression for the indexed call argument,
-  // and loads it if necessary. Returns 0 on error.
-  auto get_arg_as_value_id = [&](size_t i,
-                                 bool generate_load = true) -> uint32_t {
-    auto* arg = call->Arguments()[i];
-    auto* param = builtin->Parameters()[i];
-    auto val_id = GenerateExpression(arg->Declaration());
-    if (val_id == 0) {
-      return 0;
-    }
-
-    if (generate_load && !param->Type()->Is<sem::Pointer>()) {
-      val_id = GenerateLoadIfNeeded(arg->Type(), val_id);
-    }
-    return val_id;
-  };
-
-  OperandList params = {Operand::Int(result_type_id), result};
-  spv::Op op = spv::Op::OpNop;
-
-  // Pushes the arguments for a GlslStd450 extended instruction, and sets op
-  // to OpExtInst.
-  auto glsl_std450 = [&](uint32_t inst_id) {
-    auto set_id = GetGLSLstd450Import();
-    params.push_back(Operand::Int(set_id));
-    params.push_back(Operand::Int(inst_id));
-    op = spv::Op::OpExtInst;
-  };
-
-  switch (builtin->Type()) {
-    case BuiltinType::kAny:
-      if (builtin->Parameters()[0]->Type()->Is<sem::Bool>()) {
-        // any(v: bool) just resolves to v.
-        return get_arg_as_value_id(0);
-      }
-      op = spv::Op::OpAny;
-      break;
-    case BuiltinType::kAll:
-      if (builtin->Parameters()[0]->Type()->Is<sem::Bool>()) {
-        // all(v: bool) just resolves to v.
-        return get_arg_as_value_id(0);
-      }
-      op = spv::Op::OpAll;
-      break;
-    case BuiltinType::kArrayLength: {
-      auto* address_of =
-          call->Arguments()[0]->Declaration()->As<ast::UnaryOpExpression>();
-      if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
-        error_ = "arrayLength() expected pointer to member access, got " +
-                 std::string(address_of->TypeInfo().name);
-        return 0;
-      }
-      auto* array_expr = address_of->expr;
-
-      auto* accessor = array_expr->As<ast::MemberAccessorExpression>();
-      if (!accessor) {
-        error_ =
-            "arrayLength() expected pointer to member access, got pointer to " +
-            std::string(array_expr->TypeInfo().name);
-        return 0;
-      }
-
-      auto struct_id = GenerateExpression(accessor->structure);
-      if (struct_id == 0) {
-        return 0;
-      }
-      params.push_back(Operand::Int(struct_id));
-
-      auto* type = TypeOf(accessor->structure)->UnwrapRef();
-      if (!type->Is<sem::Struct>()) {
-        error_ =
-            "invalid type (" + type->type_name() + ") for runtime array length";
-        return 0;
-      }
-      // Runtime array must be the last member in the structure
-      params.push_back(Operand::Int(uint32_t(
-          type->As<sem::Struct>()->Declaration()->members.size() - 1)));
-
-      if (!push_function_inst(spv::Op::OpArrayLength, params)) {
-        return 0;
-      }
-      return result_id;
-    }
-    case BuiltinType::kCountOneBits:
-      op = spv::Op::OpBitCount;
-      break;
-    case BuiltinType::kDot: {
-      op = spv::Op::OpDot;
-      auto* vec_ty = builtin->Parameters()[0]->Type()->As<sem::Vector>();
-      if (vec_ty->type()->is_integer_scalar()) {
-        // TODO(crbug.com/tint/1267): OpDot requires floating-point types, but
-        // WGSL also supports integer types. SPV_KHR_integer_dot_product adds
-        // support for integer vectors. Use it if it is available.
-        auto el_ty = Operand::Int(GenerateTypeIfNeeded(vec_ty->type()));
-        auto vec_a = Operand::Int(get_arg_as_value_id(0));
-        auto vec_b = Operand::Int(get_arg_as_value_id(1));
-        if (vec_a.to_i() == 0 || vec_b.to_i() == 0) {
-          return 0;
-        }
-
-        auto sum = Operand::Int(0);
-        for (uint32_t i = 0; i < vec_ty->Width(); i++) {
-          auto a = result_op();
-          auto b = result_op();
-          auto mul = result_op();
-          if (!push_function_inst(spv::Op::OpCompositeExtract,
-                                  {el_ty, a, vec_a, Operand::Int(i)}) ||
-              !push_function_inst(spv::Op::OpCompositeExtract,
-                                  {el_ty, b, vec_b, Operand::Int(i)}) ||
-              !push_function_inst(spv::Op::OpIMul, {el_ty, mul, a, b})) {
-            return 0;
-          }
-          if (i == 0) {
-            sum = mul;
-          } else {
-            auto prev_sum = sum;
-            auto is_last_el = i == (vec_ty->Width() - 1);
-            sum = is_last_el ? Operand::Int(result_id) : result_op();
-            if (!push_function_inst(spv::Op::OpIAdd,
-                                    {el_ty, sum, prev_sum, mul})) {
-              return 0;
-            }
-          }
-        }
-        return result_id;
-      }
-      break;
-    }
-    case BuiltinType::kDpdx:
-      op = spv::Op::OpDPdx;
-      break;
-    case BuiltinType::kDpdxCoarse:
-      op = spv::Op::OpDPdxCoarse;
-      break;
-    case BuiltinType::kDpdxFine:
-      op = spv::Op::OpDPdxFine;
-      break;
-    case BuiltinType::kDpdy:
-      op = spv::Op::OpDPdy;
-      break;
-    case BuiltinType::kDpdyCoarse:
-      op = spv::Op::OpDPdyCoarse;
-      break;
-    case BuiltinType::kDpdyFine:
-      op = spv::Op::OpDPdyFine;
-      break;
-    case BuiltinType::kFwidth:
-      op = spv::Op::OpFwidth;
-      break;
-    case BuiltinType::kFwidthCoarse:
-      op = spv::Op::OpFwidthCoarse;
-      break;
-    case BuiltinType::kFwidthFine:
-      op = spv::Op::OpFwidthFine;
-      break;
-    case BuiltinType::kIsInf:
-      op = spv::Op::OpIsInf;
-      break;
-    case BuiltinType::kIsNan:
-      op = spv::Op::OpIsNan;
-      break;
-    case BuiltinType::kIsFinite: {
-      // Implemented as:   not(IsInf or IsNan)
-      auto val_id = get_arg_as_value_id(0);
-      if (!val_id) {
-        return 0;
-      }
-      auto inf_result = result_op();
-      auto nan_result = result_op();
-      auto or_result = result_op();
-      if (push_function_inst(spv::Op::OpIsInf,
-                             {Operand::Int(result_type_id), inf_result,
-                              Operand::Int(val_id)}) &&
-          push_function_inst(spv::Op::OpIsNan,
-                             {Operand::Int(result_type_id), nan_result,
-                              Operand::Int(val_id)}) &&
-          push_function_inst(spv::Op::OpLogicalOr,
-                             {Operand::Int(result_type_id), or_result,
-                              Operand::Int(inf_result.to_i()),
-                              Operand::Int(nan_result.to_i())}) &&
-          push_function_inst(spv::Op::OpLogicalNot,
-                             {Operand::Int(result_type_id), result,
-                              Operand::Int(or_result.to_i())})) {
-        return result_id;
-      }
-      return 0;
-    }
-    case BuiltinType::kIsNormal: {
-      // A normal number is finite, non-zero, and not subnormal.
-      // Its exponent is neither of the extreme possible values.
-      // Implemented as:
-      //   exponent_bits = bitcast<u32>(f);
-      //   clamped = uclamp(1,254,exponent_bits);
-      //   result = (clamped == exponent_bits);
-      //
-      auto val_id = get_arg_as_value_id(0);
-      if (!val_id) {
-        return 0;
-      }
-
-      // These parameters are valid for IEEE 754 binary32
-      const uint32_t kExponentMask = 0x7f80000;
-      const uint32_t kMinNormalExponent = 0x0080000;
-      const uint32_t kMaxNormalExponent = 0x7f00000;
-
-      auto set_id = GetGLSLstd450Import();
-      auto* u32 = builder_.create<sem::U32>();
-
-      auto unsigned_id = GenerateTypeIfNeeded(u32);
-      auto exponent_mask_id =
-          GenerateConstantIfNeeded(ScalarConstant::U32(kExponentMask));
-      auto min_exponent_id =
-          GenerateConstantIfNeeded(ScalarConstant::U32(kMinNormalExponent));
-      auto max_exponent_id =
-          GenerateConstantIfNeeded(ScalarConstant::U32(kMaxNormalExponent));
-      if (auto* fvec_ty = builtin->ReturnType()->As<sem::Vector>()) {
-        // In the vector case, update the unsigned type to a vector type of the
-        // same size, and create vector constants by replicating the scalars.
-        // I expect backend compilers to fold these into unique constants, so
-        // there is no loss of efficiency.
-        auto* uvec_ty = builder_.create<sem::Vector>(u32, fvec_ty->Width());
-        unsigned_id = GenerateTypeIfNeeded(uvec_ty);
-        auto splat = [&](uint32_t scalar_id) -> uint32_t {
-          auto splat_result = result_op();
-          OperandList splat_params{Operand::Int(unsigned_id), splat_result};
-          for (size_t i = 0; i < fvec_ty->Width(); i++) {
-            splat_params.emplace_back(Operand::Int(scalar_id));
-          }
-          if (!push_function_inst(spv::Op::OpCompositeConstruct,
-                                  std::move(splat_params))) {
-            return 0;
-          }
-          return splat_result.to_i();
-        };
-        exponent_mask_id = splat(exponent_mask_id);
-        min_exponent_id = splat(min_exponent_id);
-        max_exponent_id = splat(max_exponent_id);
-      }
-      auto cast_result = result_op();
-      auto exponent_bits_result = result_op();
-      auto clamp_result = result_op();
-
-      if (set_id && unsigned_id && exponent_mask_id && min_exponent_id &&
-          max_exponent_id &&
-          push_function_inst(
-              spv::Op::OpBitcast,
-              {Operand::Int(unsigned_id), cast_result, Operand::Int(val_id)}) &&
-          push_function_inst(spv::Op::OpBitwiseAnd,
-                             {Operand::Int(unsigned_id), exponent_bits_result,
-                              Operand::Int(cast_result.to_i()),
-                              Operand::Int(exponent_mask_id)}) &&
-          push_function_inst(
-              spv::Op::OpExtInst,
-              {Operand::Int(unsigned_id), clamp_result, Operand::Int(set_id),
-               Operand::Int(GLSLstd450UClamp),
-               Operand::Int(exponent_bits_result.to_i()),
-               Operand::Int(min_exponent_id), Operand::Int(max_exponent_id)}) &&
-          push_function_inst(spv::Op::OpIEqual,
-                             {Operand::Int(result_type_id), result,
-                              Operand::Int(exponent_bits_result.to_i()),
-                              Operand::Int(clamp_result.to_i())})) {
-        return result_id;
-      }
-      return 0;
-    }
-    case BuiltinType::kMix: {
-      auto std450 = Operand::Int(GetGLSLstd450Import());
-
-      auto a_id = get_arg_as_value_id(0);
-      auto b_id = get_arg_as_value_id(1);
-      auto f_id = get_arg_as_value_id(2);
-      if (!a_id || !b_id || !f_id) {
-        return 0;
-      }
-
-      // If the interpolant is scalar but the objects are vectors, we need to
-      // splat the interpolant into a vector of the same size.
-      auto* result_vector_type = builtin->ReturnType()->As<sem::Vector>();
-      if (result_vector_type && builtin->Parameters()[2]->Type()->is_scalar()) {
-        f_id = GenerateSplat(f_id, builtin->Parameters()[0]->Type());
-        if (f_id == 0) {
-          return 0;
-        }
-      }
-
-      if (!push_function_inst(spv::Op::OpExtInst,
-                              {Operand::Int(result_type_id), result, std450,
-                               Operand::Int(GLSLstd450FMix), Operand::Int(a_id),
-                               Operand::Int(b_id), Operand::Int(f_id)})) {
-        return 0;
-      }
-      return result_id;
-    }
-    case BuiltinType::kReverseBits:
-      op = spv::Op::OpBitReverse;
-      break;
-    case BuiltinType::kSelect: {
-      // Note: Argument order is different in WGSL and SPIR-V
-      auto cond_id = get_arg_as_value_id(2);
-      auto true_id = get_arg_as_value_id(1);
-      auto false_id = get_arg_as_value_id(0);
-      if (!cond_id || !true_id || !false_id) {
-        return 0;
-      }
-
-      // If the condition is scalar but the objects are vectors, we need to
-      // splat the condition into a vector of the same size.
-      // TODO(jrprice): If we're targeting SPIR-V 1.4, we don't need to do this.
-      auto* result_vector_type = builtin->ReturnType()->As<sem::Vector>();
-      if (result_vector_type && builtin->Parameters()[2]->Type()->is_scalar()) {
-        auto* bool_vec_ty = builder_.create<sem::Vector>(
-            builder_.create<sem::Bool>(), result_vector_type->Width());
-        if (!GenerateTypeIfNeeded(bool_vec_ty)) {
-          return 0;
-        }
-        cond_id = GenerateSplat(cond_id, bool_vec_ty);
-        if (cond_id == 0) {
-          return 0;
-        }
-      }
-
-      if (!push_function_inst(
-              spv::Op::OpSelect,
-              {Operand::Int(result_type_id), result, Operand::Int(cond_id),
-               Operand::Int(true_id), Operand::Int(false_id)})) {
-        return 0;
-      }
-      return result_id;
-    }
-    case BuiltinType::kTranspose:
-      op = spv::Op::OpTranspose;
-      break;
-    case BuiltinType::kAbs:
-      if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
-        // abs() only operates on *signed* integers.
-        // This is a no-op for unsigned integers.
-        return get_arg_as_value_id(0);
-      }
-      if (builtin->ReturnType()->is_float_scalar_or_vector()) {
-        glsl_std450(GLSLstd450FAbs);
-      } else {
-        glsl_std450(GLSLstd450SAbs);
-      }
-      break;
-    default: {
-      auto inst_id = builtin_to_glsl_method(builtin);
-      if (inst_id == 0) {
-        error_ = "unknown method " + std::string(builtin->str());
-        return 0;
-      }
-      glsl_std450(inst_id);
-      break;
-    }
-  }
-
-  if (op == spv::Op::OpNop) {
-    error_ = "unable to determine operator for: " + std::string(builtin->str());
-    return 0;
-  }
-
-  for (size_t i = 0; i < call->Arguments().size(); i++) {
-    if (auto val_id = get_arg_as_value_id(i)) {
-      params.emplace_back(Operand::Int(val_id));
-    } else {
-      return 0;
-    }
-  }
-
-  if (!push_function_inst(op, params)) {
-    return 0;
-  }
-
-  return result_id;
-}
-
-bool Builder::GenerateTextureBuiltin(const sem::Call* call,
-                                     const sem::Builtin* builtin,
-                                     Operand result_type,
-                                     Operand result_id) {
-  using Usage = sem::ParameterUsage;
-
-  auto& signature = builtin->Signature();
-  auto& arguments = call->Arguments();
-
-  // Generates the given expression, returning the operand ID
-  auto gen = [&](const sem::Expression* expr) {
-    const auto val_id = GenerateExpressionWithLoadIfNeeded(expr);
-    return Operand::Int(val_id);
-  };
-
-  // Returns the argument with the given usage
-  auto arg = [&](Usage usage) {
-    int idx = signature.IndexOf(usage);
-    return (idx >= 0) ? arguments[idx] : nullptr;
-  };
-
-  // Generates the argument with the given usage, returning the operand ID
-  auto gen_arg = [&](Usage usage) {
-    auto* argument = arg(usage);
-    if (!argument) {
-      TINT_ICE(Writer, builder_.Diagnostics())
-          << "missing argument " << static_cast<int>(usage);
-    }
-    return gen(argument);
-  };
-
-  auto* texture = arg(Usage::kTexture);
-  if (!texture) {
-    TINT_ICE(Writer, builder_.Diagnostics()) << "missing texture argument";
-  }
-
-  auto* texture_type = texture->Type()->UnwrapRef()->As<sem::Texture>();
-
-  auto op = spv::Op::OpNop;
-
-  // Custom function to call after the texture-builtin op has been generated.
-  std::function<bool()> post_emission = [] { return true; };
-
-  // Populate the spirv_params with common parameters
-  OperandList spirv_params;
-  spirv_params.reserve(8);  // Enough to fit most parameter lists
-
-  // Extra image operands, appended to spirv_params.
-  struct ImageOperand {
-    SpvImageOperandsMask mask;
-    Operand operand;
-  };
-  std::vector<ImageOperand> image_operands;
-  image_operands.reserve(4);  // Enough to fit most parameter lists
-
-  // Appends `result_type` and `result_id` to `spirv_params`
-  auto append_result_type_and_id_to_spirv_params = [&]() {
-    spirv_params.emplace_back(std::move(result_type));
-    spirv_params.emplace_back(std::move(result_id));
-  };
-
-  // Appends a result type and id to `spirv_params`, possibly adding a
-  // post_emission step.
-  //
-  // If the texture is a depth texture, then this function wraps the result of
-  // the op with a OpCompositeExtract to evaluate to the first element of the
-  // returned vector. This is done as the WGSL texture reading functions for
-  // depths return a single float scalar instead of a vector.
-  //
-  // If the texture is not a depth texture, then this function simply delegates
-  // to calling append_result_type_and_id_to_spirv_params().
-  auto append_result_type_and_id_to_spirv_params_for_read = [&]() {
-    if (texture_type
-            ->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
-      auto* f32 = builder_.create<sem::F32>();
-      auto* spirv_result_type = builder_.create<sem::Vector>(f32, 4);
-      auto spirv_result = result_op();
-      post_emission = [=] {
-        return push_function_inst(
-            spv::Op::OpCompositeExtract,
-            {result_type, result_id, spirv_result, Operand::Int(0)});
-      };
-      auto spirv_result_type_id = GenerateTypeIfNeeded(spirv_result_type);
-      if (spirv_result_type_id == 0) {
-        return false;
-      }
-      spirv_params.emplace_back(Operand::Int(spirv_result_type_id));
-      spirv_params.emplace_back(spirv_result);
-      return true;
-    }
-
-    append_result_type_and_id_to_spirv_params();
-    return true;
-  };
-
-  // Appends a result type and id to `spirv_params`, by first swizzling the
-  // result of the op with `swizzle`.
-  auto append_result_type_and_id_to_spirv_params_swizzled =
-      [&](uint32_t spirv_result_width, std::vector<uint32_t> swizzle) {
-        if (swizzle.empty()) {
-          append_result_type_and_id_to_spirv_params();
-        } else {
-          // Assign post_emission to swizzle the result of the call to
-          // OpImageQuerySize[Lod].
-          auto* element_type = ElementTypeOf(call->Type());
-          auto spirv_result = result_op();
-          auto* spirv_result_type =
-              builder_.create<sem::Vector>(element_type, spirv_result_width);
-          if (swizzle.size() > 1) {
-            post_emission = [=] {
-              OperandList operands{
-                  result_type,
-                  result_id,
-                  spirv_result,
-                  spirv_result,
-              };
-              for (auto idx : swizzle) {
-                operands.emplace_back(Operand::Int(idx));
-              }
-              return push_function_inst(spv::Op::OpVectorShuffle, operands);
-            };
-          } else {
-            post_emission = [=] {
-              return push_function_inst(spv::Op::OpCompositeExtract,
-                                        {result_type, result_id, spirv_result,
-                                         Operand::Int(swizzle[0])});
-            };
-          }
-          auto spirv_result_type_id = GenerateTypeIfNeeded(spirv_result_type);
-          if (spirv_result_type_id == 0) {
-            return false;
-          }
-          spirv_params.emplace_back(Operand::Int(spirv_result_type_id));
-          spirv_params.emplace_back(spirv_result);
-        }
-        return true;
-      };
-
-  auto append_coords_to_spirv_params = [&]() -> bool {
-    if (auto* array_index = arg(Usage::kArrayIndex)) {
-      // Array index needs to be appended to the coordinates.
-      auto* packed = AppendVector(&builder_, arg(Usage::kCoords)->Declaration(),
-                                  array_index->Declaration());
-      auto param = GenerateExpression(packed->Declaration());
-      if (param == 0) {
-        return false;
-      }
-      spirv_params.emplace_back(Operand::Int(param));
-    } else {
-      spirv_params.emplace_back(gen_arg(Usage::kCoords));  // coordinates
-    }
-    return true;
-  };
-
-  auto append_image_and_coords_to_spirv_params = [&]() -> bool {
-    auto sampler_param = gen_arg(Usage::kSampler);
-    auto texture_param = gen_arg(Usage::kTexture);
-    auto sampled_image =
-        GenerateSampledImage(texture_type, texture_param, sampler_param);
-
-    // Populate the spirv_params with the common parameters
-    spirv_params.emplace_back(Operand::Int(sampled_image));  // sampled image
-    return append_coords_to_spirv_params();
-  };
-
-  switch (builtin->Type()) {
-    case BuiltinType::kTextureDimensions: {
-      // Number of returned elements from OpImageQuerySize[Lod] may not match
-      // those of textureDimensions().
-      // This might be due to an extra vector scalar describing the number of
-      // array elements or textureDimensions() returning a vec3 for cubes
-      // when only width / height is returned by OpImageQuerySize[Lod]
-      // (see https://github.com/gpuweb/gpuweb/issues/1345).
-      // Handle these mismatches by swizzling the returned vector.
-      std::vector<uint32_t> swizzle;
-      uint32_t spirv_dims = 0;
-      switch (texture_type->dim()) {
-        case ast::TextureDimension::kNone:
-          error_ = "texture dimension is kNone";
-          return false;
-        case ast::TextureDimension::k1d:
-        case ast::TextureDimension::k2d:
-        case ast::TextureDimension::k3d:
-        case ast::TextureDimension::kCube:
-          break;  // No swizzle needed
-        case ast::TextureDimension::kCubeArray:
-        case ast::TextureDimension::k2dArray:
-          swizzle = {0, 1};  // Strip array index
-          spirv_dims = 3;    // [width, height, array_count]
-          break;
-      }
-
-      if (!append_result_type_and_id_to_spirv_params_swizzled(spirv_dims,
-                                                              swizzle)) {
-        return false;
-      }
-
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-      if (texture_type->IsAnyOf<sem::MultisampledTexture,       //
-                                sem::DepthMultisampledTexture,  //
-                                sem::StorageTexture>()) {
-        op = spv::Op::OpImageQuerySize;
-      } else if (auto* level = arg(Usage::kLevel)) {
-        op = spv::Op::OpImageQuerySizeLod;
-        spirv_params.emplace_back(gen(level));
-      } else {
-        ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
-        op = spv::Op::OpImageQuerySizeLod;
-        spirv_params.emplace_back(
-            Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
-      }
-      break;
-    }
-    case BuiltinType::kTextureNumLayers: {
-      uint32_t spirv_dims = 0;
-      switch (texture_type->dim()) {
-        default:
-          error_ = "texture is not arrayed";
-          return false;
-        case ast::TextureDimension::k2dArray:
-        case ast::TextureDimension::kCubeArray:
-          spirv_dims = 3;
-          break;
-      }
-
-      // OpImageQuerySize[Lod] packs the array count as the last element of the
-      // returned vector. Extract this.
-      if (!append_result_type_and_id_to_spirv_params_swizzled(
-              spirv_dims, {spirv_dims - 1})) {
-        return false;
-      }
-
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-
-      if (texture_type->Is<sem::MultisampledTexture>() ||
-          texture_type->Is<sem::StorageTexture>()) {
-        op = spv::Op::OpImageQuerySize;
-      } else {
-        ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
-        op = spv::Op::OpImageQuerySizeLod;
-        spirv_params.emplace_back(
-            Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
-      }
-      break;
-    }
-    case BuiltinType::kTextureNumLevels: {
-      op = spv::Op::OpImageQueryLevels;
-      append_result_type_and_id_to_spirv_params();
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-      break;
-    }
-    case BuiltinType::kTextureNumSamples: {
-      op = spv::Op::OpImageQuerySamples;
-      append_result_type_and_id_to_spirv_params();
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-      break;
-    }
-    case BuiltinType::kTextureLoad: {
-      op = texture_type->Is<sem::StorageTexture>() ? spv::Op::OpImageRead
-                                                   : spv::Op::OpImageFetch;
-      append_result_type_and_id_to_spirv_params_for_read();
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-      if (!append_coords_to_spirv_params()) {
-        return false;
-      }
-
-      if (auto* level = arg(Usage::kLevel)) {
-        image_operands.emplace_back(
-            ImageOperand{SpvImageOperandsLodMask, gen(level)});
-      }
-
-      if (auto* sample_index = arg(Usage::kSampleIndex)) {
-        image_operands.emplace_back(
-            ImageOperand{SpvImageOperandsSampleMask, gen(sample_index)});
-      }
-
-      break;
-    }
-    case BuiltinType::kTextureStore: {
-      op = spv::Op::OpImageWrite;
-      spirv_params.emplace_back(gen_arg(Usage::kTexture));
-      if (!append_coords_to_spirv_params()) {
-        return false;
-      }
-      spirv_params.emplace_back(gen_arg(Usage::kValue));
-      break;
-    }
-    case BuiltinType::kTextureGather: {
-      op = spv::Op::OpImageGather;
-      append_result_type_and_id_to_spirv_params();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      if (signature.IndexOf(Usage::kComponent) < 0) {
-        spirv_params.emplace_back(
-            Operand::Int(GenerateConstantIfNeeded(ScalarConstant::I32(0))));
-      } else {
-        spirv_params.emplace_back(gen_arg(Usage::kComponent));
-      }
-      break;
-    }
-    case BuiltinType::kTextureGatherCompare: {
-      op = spv::Op::OpImageDrefGather;
-      append_result_type_and_id_to_spirv_params();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
-      break;
-    }
-    case BuiltinType::kTextureSample: {
-      op = spv::Op::OpImageSampleImplicitLod;
-      append_result_type_and_id_to_spirv_params_for_read();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      break;
-    }
-    case BuiltinType::kTextureSampleBias: {
-      op = spv::Op::OpImageSampleImplicitLod;
-      append_result_type_and_id_to_spirv_params_for_read();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      image_operands.emplace_back(
-          ImageOperand{SpvImageOperandsBiasMask, gen_arg(Usage::kBias)});
-      break;
-    }
-    case BuiltinType::kTextureSampleLevel: {
-      op = spv::Op::OpImageSampleExplicitLod;
-      append_result_type_and_id_to_spirv_params_for_read();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      auto level = Operand::Int(0);
-      if (arg(Usage::kLevel)->Type()->UnwrapRef()->Is<sem::I32>()) {
-        // Depth textures have i32 parameters for the level, but SPIR-V expects
-        // F32. Cast.
-        auto f32_type_id = GenerateTypeIfNeeded(builder_.create<sem::F32>());
-        if (f32_type_id == 0) {
-          return 0;
-        }
-        level = result_op();
-        if (!push_function_inst(
-                spv::Op::OpConvertSToF,
-                {Operand::Int(f32_type_id), level, gen_arg(Usage::kLevel)})) {
-          return 0;
-        }
-      } else {
-        level = gen_arg(Usage::kLevel);
-      }
-      image_operands.emplace_back(ImageOperand{SpvImageOperandsLodMask, level});
-      break;
-    }
-    case BuiltinType::kTextureSampleGrad: {
-      op = spv::Op::OpImageSampleExplicitLod;
-      append_result_type_and_id_to_spirv_params_for_read();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      image_operands.emplace_back(
-          ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdx)});
-      image_operands.emplace_back(
-          ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdy)});
-      break;
-    }
-    case BuiltinType::kTextureSampleCompare: {
-      op = spv::Op::OpImageSampleDrefImplicitLod;
-      append_result_type_and_id_to_spirv_params();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
-      break;
-    }
-    case BuiltinType::kTextureSampleCompareLevel: {
-      op = spv::Op::OpImageSampleDrefExplicitLod;
-      append_result_type_and_id_to_spirv_params();
-      if (!append_image_and_coords_to_spirv_params()) {
-        return false;
-      }
-      spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
-
-      ast::FloatLiteralExpression float_0(ProgramID(), Source{}, 0.0);
-      image_operands.emplace_back(ImageOperand{
-          SpvImageOperandsLodMask,
-          Operand::Int(GenerateLiteralIfNeeded(nullptr, &float_0))});
-      break;
-    }
-    default:
-      TINT_UNREACHABLE(Writer, builder_.Diagnostics());
-      return false;
-  }
-
-  if (auto* offset = arg(Usage::kOffset)) {
-    image_operands.emplace_back(
-        ImageOperand{SpvImageOperandsConstOffsetMask, gen(offset)});
-  }
-
-  if (!image_operands.empty()) {
-    std::sort(image_operands.begin(), image_operands.end(),
-              [](auto& a, auto& b) { return a.mask < b.mask; });
-    uint32_t mask = 0;
-    for (auto& image_operand : image_operands) {
-      mask |= image_operand.mask;
-    }
-    spirv_params.emplace_back(Operand::Int(mask));
-    for (auto& image_operand : image_operands) {
-      spirv_params.emplace_back(image_operand.operand);
-    }
-  }
-
-  if (op == spv::Op::OpNop) {
-    error_ = "unable to determine operator for: " + std::string(builtin->str());
-    return false;
-  }
-
-  if (!push_function_inst(op, spirv_params)) {
-    return false;
-  }
-
-  return post_emission();
-}
-
-bool Builder::GenerateControlBarrierBuiltin(const sem::Builtin* builtin) {
-  auto const op = spv::Op::OpControlBarrier;
-  uint32_t execution = 0;
-  uint32_t memory = 0;
-  uint32_t semantics = 0;
-
-  // TODO(crbug.com/tint/661): Combine sequential barriers to a single
-  // instruction.
-  if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
-    execution = static_cast<uint32_t>(spv::Scope::Workgroup);
-    memory = static_cast<uint32_t>(spv::Scope::Workgroup);
-    semantics =
-        static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
-        static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
-  } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
-    execution = static_cast<uint32_t>(spv::Scope::Workgroup);
-    memory = static_cast<uint32_t>(spv::Scope::Workgroup);
-    semantics =
-        static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
-        static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
-  } else {
-    error_ = "unexpected barrier builtin type ";
-    error_ += sem::str(builtin->Type());
-    return false;
-  }
-
-  auto execution_id = GenerateConstantIfNeeded(ScalarConstant::U32(execution));
-  auto memory_id = GenerateConstantIfNeeded(ScalarConstant::U32(memory));
-  auto semantics_id = GenerateConstantIfNeeded(ScalarConstant::U32(semantics));
-  if (execution_id == 0 || memory_id == 0 || semantics_id == 0) {
-    return false;
-  }
-
-  return push_function_inst(op, {
-                                    Operand::Int(execution_id),
-                                    Operand::Int(memory_id),
-                                    Operand::Int(semantics_id),
-                                });
-}
-
-bool Builder::GenerateAtomicBuiltin(const sem::Call* call,
-                                    const sem::Builtin* builtin,
-                                    Operand result_type,
-                                    Operand result_id) {
-  auto is_value_signed = [&] {
-    return builtin->Parameters()[1]->Type()->Is<sem::I32>();
-  };
-
-  auto storage_class =
-      builtin->Parameters()[0]->Type()->As<sem::Pointer>()->StorageClass();
-
-  uint32_t memory_id = 0;
-  switch (
-      builtin->Parameters()[0]->Type()->As<sem::Pointer>()->StorageClass()) {
-    case ast::StorageClass::kWorkgroup:
-      memory_id = GenerateConstantIfNeeded(
-          ScalarConstant::U32(static_cast<uint32_t>(spv::Scope::Workgroup)));
-      break;
-    case ast::StorageClass::kStorage:
-      memory_id = GenerateConstantIfNeeded(
-          ScalarConstant::U32(static_cast<uint32_t>(spv::Scope::Device)));
-      break;
-    default:
-      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-          << "unhandled atomic storage class " << storage_class;
-      return false;
-  }
-  if (memory_id == 0) {
-    return false;
-  }
-
-  uint32_t semantics_id = GenerateConstantIfNeeded(ScalarConstant::U32(
-      static_cast<uint32_t>(spv::MemorySemanticsMask::MaskNone)));
-  if (semantics_id == 0) {
-    return false;
-  }
-
-  uint32_t pointer_id = GenerateExpression(call->Arguments()[0]->Declaration());
-  if (pointer_id == 0) {
-    return false;
-  }
-
-  uint32_t value_id = 0;
-  if (call->Arguments().size() > 1) {
-    value_id = GenerateExpressionWithLoadIfNeeded(call->Arguments().back());
-    if (value_id == 0) {
-      return false;
-    }
-  }
-
-  Operand pointer = Operand::Int(pointer_id);
-  Operand value = Operand::Int(value_id);
-  Operand memory = Operand::Int(memory_id);
-  Operand semantics = Operand::Int(semantics_id);
-
-  switch (builtin->Type()) {
-    case sem::BuiltinType::kAtomicLoad:
-      return push_function_inst(spv::Op::OpAtomicLoad, {
-                                                           result_type,
-                                                           result_id,
-                                                           pointer,
-                                                           memory,
-                                                           semantics,
-                                                       });
-    case sem::BuiltinType::kAtomicStore:
-      return push_function_inst(spv::Op::OpAtomicStore, {
-                                                            pointer,
-                                                            memory,
-                                                            semantics,
-                                                            value,
-                                                        });
-    case sem::BuiltinType::kAtomicAdd:
-      return push_function_inst(spv::Op::OpAtomicIAdd, {
-                                                           result_type,
-                                                           result_id,
-                                                           pointer,
-                                                           memory,
-                                                           semantics,
-                                                           value,
-                                                       });
-    case sem::BuiltinType::kAtomicSub:
-      return push_function_inst(spv::Op::OpAtomicISub, {
-                                                           result_type,
-                                                           result_id,
-                                                           pointer,
-                                                           memory,
-                                                           semantics,
-                                                           value,
-                                                       });
-    case sem::BuiltinType::kAtomicMax:
-      return push_function_inst(
-          is_value_signed() ? spv::Op::OpAtomicSMax : spv::Op::OpAtomicUMax,
-          {
-              result_type,
-              result_id,
-              pointer,
-              memory,
-              semantics,
-              value,
-          });
-    case sem::BuiltinType::kAtomicMin:
-      return push_function_inst(
-          is_value_signed() ? spv::Op::OpAtomicSMin : spv::Op::OpAtomicUMin,
-          {
-              result_type,
-              result_id,
-              pointer,
-              memory,
-              semantics,
-              value,
-          });
-    case sem::BuiltinType::kAtomicAnd:
-      return push_function_inst(spv::Op::OpAtomicAnd, {
-                                                          result_type,
-                                                          result_id,
-                                                          pointer,
-                                                          memory,
-                                                          semantics,
-                                                          value,
-                                                      });
-    case sem::BuiltinType::kAtomicOr:
-      return push_function_inst(spv::Op::OpAtomicOr, {
-                                                         result_type,
-                                                         result_id,
-                                                         pointer,
-                                                         memory,
-                                                         semantics,
-                                                         value,
-                                                     });
-    case sem::BuiltinType::kAtomicXor:
-      return push_function_inst(spv::Op::OpAtomicXor, {
-                                                          result_type,
-                                                          result_id,
-                                                          pointer,
-                                                          memory,
-                                                          semantics,
-                                                          value,
-                                                      });
-    case sem::BuiltinType::kAtomicExchange:
-      return push_function_inst(spv::Op::OpAtomicExchange, {
-                                                               result_type,
-                                                               result_id,
-                                                               pointer,
-                                                               memory,
-                                                               semantics,
-                                                               value,
-                                                           });
-    case sem::BuiltinType::kAtomicCompareExchangeWeak: {
-      auto comparator = GenerateExpression(call->Arguments()[1]->Declaration());
-      if (comparator == 0) {
-        return false;
-      }
-
-      auto* value_sem_type = TypeOf(call->Arguments()[2]->Declaration());
-
-      auto value_type = GenerateTypeIfNeeded(value_sem_type);
-      if (value_type == 0) {
-        return false;
-      }
-
-      auto* bool_sem_ty = builder_.create<sem::Bool>();
-      auto bool_type = GenerateTypeIfNeeded(bool_sem_ty);
-      if (bool_type == 0) {
-        return false;
-      }
-
-      // original_value := OpAtomicCompareExchange(pointer, memory, semantics,
-      //                                           semantics, value, comparator)
-      auto original_value = result_op();
-      if (!push_function_inst(spv::Op::OpAtomicCompareExchange,
-                              {
-                                  Operand::Int(value_type),
-                                  original_value,
-                                  pointer,
-                                  memory,
-                                  semantics,
-                                  semantics,
-                                  value,
-                                  Operand::Int(comparator),
-                              })) {
-        return false;
-      }
-
-      // values_equal := original_value == value
-      auto values_equal = result_op();
-      if (!push_function_inst(spv::Op::OpIEqual, {
-                                                     Operand::Int(bool_type),
-                                                     values_equal,
-                                                     original_value,
-                                                     value,
-                                                 })) {
-        return false;
-      }
-
-      // zero := T(0)
-      // one := T(1)
-      uint32_t zero = 0;
-      uint32_t one = 0;
-      if (value_sem_type->Is<sem::I32>()) {
-        zero = GenerateConstantIfNeeded(ScalarConstant::I32(0u));
-        one = GenerateConstantIfNeeded(ScalarConstant::I32(1u));
-      } else if (value_sem_type->Is<sem::U32>()) {
-        zero = GenerateConstantIfNeeded(ScalarConstant::U32(0u));
-        one = GenerateConstantIfNeeded(ScalarConstant::U32(1u));
-      } else {
-        TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-            << "unsupported atomic type " << value_sem_type->TypeInfo().name;
-      }
-      if (zero == 0 || one == 0) {
-        return false;
-      }
-
-      // xchg_success := values_equal ? one : zero
-      auto xchg_success = result_op();
-      if (!push_function_inst(spv::Op::OpSelect, {
-                                                     Operand::Int(value_type),
-                                                     xchg_success,
-                                                     values_equal,
-                                                     Operand::Int(one),
-                                                     Operand::Int(zero),
-                                                 })) {
-        return false;
-      }
-
-      // result := vec2<T>(original_value, xchg_success)
-      return push_function_inst(spv::Op::OpCompositeConstruct,
-                                {
-                                    result_type,
-                                    result_id,
-                                    original_value,
-                                    xchg_success,
-                                });
-    }
-    default:
-      TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-          << "unhandled atomic builtin " << builtin->Type();
-      return false;
-  }
-}
-
-uint32_t Builder::GenerateSampledImage(const sem::Type* texture_type,
-                                       Operand texture_operand,
-                                       Operand sampler_operand) {
-  uint32_t sampled_image_type_id = 0;
-  auto val = texture_type_name_to_sampled_image_type_id_.find(
-      texture_type->type_name());
-  if (val != texture_type_name_to_sampled_image_type_id_.end()) {
-    // The sampled image type is already created.
-    sampled_image_type_id = val->second;
-  } else {
-    // We need to create the sampled image type and cache the result.
-    auto sampled_image_type = result_op();
-    sampled_image_type_id = sampled_image_type.to_i();
-    auto texture_type_id = GenerateTypeIfNeeded(texture_type);
-    push_type(spv::Op::OpTypeSampledImage,
-              {sampled_image_type, Operand::Int(texture_type_id)});
-    texture_type_name_to_sampled_image_type_id_[texture_type->type_name()] =
-        sampled_image_type_id;
-  }
-
-  auto sampled_image = result_op();
-  if (!push_function_inst(spv::Op::OpSampledImage,
-                          {Operand::Int(sampled_image_type_id), sampled_image,
-                           texture_operand, sampler_operand})) {
-    return 0;
-  }
-
-  return sampled_image.to_i();
-}
-
-uint32_t Builder::GenerateBitcastExpression(
-    const ast::BitcastExpression* expr) {
-  auto result = result_op();
-  auto result_id = result.to_i();
-
-  auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
-  if (result_type_id == 0) {
-    return 0;
-  }
-
-  auto val_id = GenerateExpressionWithLoadIfNeeded(expr->expr);
-  if (val_id == 0) {
-    return 0;
-  }
-
-  // Bitcast does not allow same types, just emit a CopyObject
-  auto* to_type = TypeOf(expr)->UnwrapRef();
-  auto* from_type = TypeOf(expr->expr)->UnwrapRef();
-  if (to_type->type_name() == from_type->type_name()) {
-    if (!push_function_inst(
-            spv::Op::OpCopyObject,
-            {Operand::Int(result_type_id), result, Operand::Int(val_id)})) {
-      return 0;
-    }
-    return result_id;
-  }
-
-  if (!push_function_inst(spv::Op::OpBitcast, {Operand::Int(result_type_id),
-                                               result, Operand::Int(val_id)})) {
-    return 0;
-  }
-
-  return result_id;
-}
-
-bool Builder::GenerateConditionalBlock(
-    const ast::Expression* cond,
-    const ast::BlockStatement* true_body,
-    size_t cur_else_idx,
-    const ast::ElseStatementList& else_stmts) {
-  auto cond_id = GenerateExpressionWithLoadIfNeeded(cond);
-  if (cond_id == 0) {
-    return false;
-  }
-
-  auto merge_block = result_op();
-  auto merge_block_id = merge_block.to_i();
-
-  if (!push_function_inst(spv::Op::OpSelectionMerge,
-                          {Operand::Int(merge_block_id),
-                           Operand::Int(SpvSelectionControlMaskNone)})) {
-    return false;
-  }
-
-  auto true_block = result_op();
-  auto true_block_id = true_block.to_i();
-
-  // if there are no more else statements we branch on false to the merge
-  // block otherwise we branch to the false block
-  auto false_block_id =
-      cur_else_idx < else_stmts.size() ? next_id() : merge_block_id;
-
-  if (!push_function_inst(spv::Op::OpBranchConditional,
-                          {Operand::Int(cond_id), Operand::Int(true_block_id),
-                           Operand::Int(false_block_id)})) {
-    return false;
-  }
-
-  // Output true block
-  if (!GenerateLabel(true_block_id)) {
-    return false;
-  }
-  if (!GenerateBlockStatement(true_body)) {
-    return false;
-  }
-  // We only branch if the last element of the body didn't already branch.
-  if (InsideBasicBlock()) {
-    if (!push_function_inst(spv::Op::OpBranch,
-                            {Operand::Int(merge_block_id)})) {
-      return false;
-    }
-  }
-
-  // Start the false block if needed
-  if (false_block_id != merge_block_id) {
-    if (!GenerateLabel(false_block_id)) {
-      return false;
-    }
-
-    auto* else_stmt = else_stmts[cur_else_idx];
-    // Handle the else case by just outputting the statements.
-    if (!else_stmt->condition) {
-      if (!GenerateBlockStatement(else_stmt->body)) {
-        return false;
-      }
-    } else {
-      if (!GenerateConditionalBlock(else_stmt->condition, else_stmt->body,
-                                    cur_else_idx + 1, else_stmts)) {
-        return false;
-      }
-    }
-    if (InsideBasicBlock()) {
-      if (!push_function_inst(spv::Op::OpBranch,
-                              {Operand::Int(merge_block_id)})) {
-        return false;
-      }
-    }
-  }
-
-  // Output the merge block
-  return GenerateLabel(merge_block_id);
-}
-
-bool Builder::GenerateIfStatement(const ast::IfStatement* stmt) {
-  if (!continuing_stack_.empty() &&
-      stmt == continuing_stack_.back().last_statement->As<ast::IfStatement>()) {
-    const ContinuingInfo& ci = continuing_stack_.back();
-    // Match one of two patterns: the break-if and break-unless patterns.
-    //
-    // The break-if pattern:
-    //  continuing { ...
-    //    if (cond) { break; }
-    //  }
-    //
-    // The break-unless pattern:
-    //  continuing { ...
-    //    if (cond) {} else {break;}
-    //  }
-    auto is_just_a_break = [](const ast::BlockStatement* block) {
-      return block && (block->statements.size() == 1) &&
-             block->Last()->Is<ast::BreakStatement>();
-    };
-    if (is_just_a_break(stmt->body) && stmt->else_statements.empty()) {
-      // It's a break-if.
-      TINT_ASSERT(Writer, !backedge_stack_.empty());
-      const auto cond_id = GenerateExpressionWithLoadIfNeeded(stmt->condition);
-      if (!cond_id) {
-        return false;
-      }
-      backedge_stack_.back() =
-          Backedge(spv::Op::OpBranchConditional,
-                   {Operand::Int(cond_id), Operand::Int(ci.break_target_id),
-                    Operand::Int(ci.loop_header_id)});
-      return true;
-    } else if (stmt->body->Empty()) {
-      const auto& es = stmt->else_statements;
-      if (es.size() == 1 && !es.back()->condition &&
-          is_just_a_break(es.back()->body)) {
-        // It's a break-unless.
-        TINT_ASSERT(Writer, !backedge_stack_.empty());
-        const auto cond_id =
-            GenerateExpressionWithLoadIfNeeded(stmt->condition);
-        if (!cond_id) {
-          return false;
-        }
-        backedge_stack_.back() =
-            Backedge(spv::Op::OpBranchConditional,
-                     {Operand::Int(cond_id), Operand::Int(ci.loop_header_id),
-                      Operand::Int(ci.break_target_id)});
-        return true;
-      }
-    }
-  }
-
-  if (!GenerateConditionalBlock(stmt->condition, stmt->body, 0,
-                                stmt->else_statements)) {
-    return false;
-  }
-  return true;
-}
-
-bool Builder::GenerateSwitchStatement(const ast::SwitchStatement* stmt) {
-  auto merge_block = result_op();
-  auto merge_block_id = merge_block.to_i();
-
-  merge_stack_.push_back(merge_block_id);
-
-  auto cond_id = GenerateExpressionWithLoadIfNeeded(stmt->condition);
-  if (cond_id == 0) {
-    return false;
-  }
-
-  auto default_block = result_op();
-  auto default_block_id = default_block.to_i();
-
-  OperandList params = {Operand::Int(cond_id), Operand::Int(default_block_id)};
-
-  std::vector<uint32_t> case_ids;
-  for (const auto* item : stmt->body) {
-    if (item->IsDefault()) {
-      case_ids.push_back(default_block_id);
-      continue;
-    }
-
-    auto block = result_op();
-    auto block_id = block.to_i();
-
-    case_ids.push_back(block_id);
-    for (auto* selector : item->selectors) {
-      auto* int_literal = selector->As<ast::IntLiteralExpression>();
-      if (!int_literal) {
-        error_ = "expected integer literal for switch case label";
-        return false;
-      }
-
-      params.push_back(Operand::Int(int_literal->ValueAsU32()));
-      params.push_back(Operand::Int(block_id));
-    }
-  }
-
-  if (!push_function_inst(spv::Op::OpSelectionMerge,
-                          {Operand::Int(merge_block_id),
-                           Operand::Int(SpvSelectionControlMaskNone)})) {
-    return false;
-  }
-  if (!push_function_inst(spv::Op::OpSwitch, params)) {
-    return false;
-  }
-
-  bool generated_default = false;
-  auto& body = stmt->body;
-  // We output the case statements in order they were entered in the original
-  // source. Each fallthrough goes to the next case entry, so is a forward
-  // branch, otherwise the branch is to the merge block which comes after
-  // the switch statement.
-  for (uint32_t i = 0; i < body.size(); i++) {
-    auto* item = body[i];
-
-    if (item->IsDefault()) {
-      generated_default = true;
-    }
-
-    if (!GenerateLabel(case_ids[i])) {
-      return false;
-    }
-    if (!GenerateBlockStatement(item->body)) {
-      return false;
-    }
-
-    if (LastIsFallthrough(item->body)) {
-      if (i == (body.size() - 1)) {
-        // This case is caught by Resolver validation
-        TINT_UNREACHABLE(Writer, builder_.Diagnostics());
-        return false;
-      }
-      if (!push_function_inst(spv::Op::OpBranch,
-                              {Operand::Int(case_ids[i + 1])})) {
-        return false;
-      }
-    } else if (InsideBasicBlock()) {
-      if (!push_function_inst(spv::Op::OpBranch,
-                              {Operand::Int(merge_block_id)})) {
-        return false;
-      }
-    }
-  }
-
-  if (!generated_default) {
-    if (!GenerateLabel(default_block_id)) {
-      return false;
-    }
-    if (!push_function_inst(spv::Op::OpBranch,
-                            {Operand::Int(merge_block_id)})) {
-      return false;
-    }
-  }
-
-  merge_stack_.pop_back();
-
-  return GenerateLabel(merge_block_id);
-}
-
-bool Builder::GenerateReturnStatement(const ast::ReturnStatement* stmt) {
-  if (stmt->value) {
-    auto val_id = GenerateExpressionWithLoadIfNeeded(stmt->value);
-    if (val_id == 0) {
-      return false;
-    }
-    if (!push_function_inst(spv::Op::OpReturnValue, {Operand::Int(val_id)})) {
-      return false;
-    }
-  } else {
-    if (!push_function_inst(spv::Op::OpReturn, {})) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool Builder::GenerateLoopStatement(const ast::LoopStatement* stmt) {
-  auto loop_header = result_op();
-  auto loop_header_id = loop_header.to_i();
-  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(loop_header_id)})) {
-    return false;
-  }
-  if (!GenerateLabel(loop_header_id)) {
-    return false;
-  }
-
-  auto merge_block = result_op();
-  auto merge_block_id = merge_block.to_i();
-  auto continue_block = result_op();
-  auto continue_block_id = continue_block.to_i();
-
-  auto body_block = result_op();
-  auto body_block_id = body_block.to_i();
-
-  if (!push_function_inst(
-          spv::Op::OpLoopMerge,
-          {Operand::Int(merge_block_id), Operand::Int(continue_block_id),
-           Operand::Int(SpvLoopControlMaskNone)})) {
-    return false;
-  }
-
-  continue_stack_.push_back(continue_block_id);
-  merge_stack_.push_back(merge_block_id);
-
-  // Usually, the backedge is a simple branch.  This will be modified if the
-  // backedge block in the continuing construct has an exiting edge.
-  backedge_stack_.emplace_back(spv::Op::OpBranch,
-                               OperandList{Operand::Int(loop_header_id)});
-
-  if (!push_function_inst(spv::Op::OpBranch, {Operand::Int(body_block_id)})) {
-    return false;
-  }
-  if (!GenerateLabel(body_block_id)) {
-    return false;
-  }
-
-  // We need variables from the body to be visible in the continuing block, so
-  // manage scope outside of GenerateBlockStatement.
-  {
-    scope_stack_.Push();
-    TINT_DEFER(scope_stack_.Pop());
-
-    if (!GenerateBlockStatementWithoutScoping(stmt->body)) {
-      return false;
-    }
-
-    // We only branch if the last element of the body didn't already branch.
-    if (InsideBasicBlock()) {
-      if (!push_function_inst(spv::Op::OpBranch,
-                              {Operand::Int(continue_block_id)})) {
-        return false;
-      }
-    }
-
-    if (!GenerateLabel(continue_block_id)) {
-      return false;
-    }
-    if (stmt->continuing && !stmt->continuing->Empty()) {
-      continuing_stack_.emplace_back(stmt->continuing->Last(), loop_header_id,
-                                     merge_block_id);
-      if (!GenerateBlockStatementWithoutScoping(stmt->continuing)) {
-        return false;
-      }
-      continuing_stack_.pop_back();
-    }
-  }
-
-  // Generate the backedge.
-  TINT_ASSERT(Writer, !backedge_stack_.empty());
-  const Backedge& backedge = backedge_stack_.back();
-  if (!push_function_inst(backedge.opcode, backedge.operands)) {
-    return false;
-  }
-  backedge_stack_.pop_back();
-
-  merge_stack_.pop_back();
-  continue_stack_.pop_back();
-
-  return GenerateLabel(merge_block_id);
-}
-
-bool Builder::GenerateStatement(const ast::Statement* stmt) {
-  return Switch(
-      stmt,
-      [&](const ast::AssignmentStatement* a) {
-        return GenerateAssignStatement(a);
-      },
-      [&](const ast::BlockStatement* b) {  //
-        return GenerateBlockStatement(b);
-      },
-      [&](const ast::BreakStatement* b) {  //
-        return GenerateBreakStatement(b);
-      },
-      [&](const ast::CallStatement* c) {
-        return GenerateCallExpression(c->expr) != 0;
-      },
-      [&](const ast::ContinueStatement* c) {
-        return GenerateContinueStatement(c);
-      },
-      [&](const ast::DiscardStatement* d) {
-        return GenerateDiscardStatement(d);
-      },
-      [&](const ast::FallthroughStatement*) {
-        // Do nothing here, the fallthrough gets handled by the switch code.
-        return true;
-      },
-      [&](const ast::IfStatement* i) {  //
-        return GenerateIfStatement(i);
-      },
-      [&](const ast::LoopStatement* l) {  //
-        return GenerateLoopStatement(l);
-      },
-      [&](const ast::ReturnStatement* r) {  //
-        return GenerateReturnStatement(r);
-      },
-      [&](const ast::SwitchStatement* s) {  //
-        return GenerateSwitchStatement(s);
-      },
-      [&](const ast::VariableDeclStatement* v) {
-        return GenerateVariableDeclStatement(v);
-      },
-      [&](Default) {
-        error_ = "Unknown statement: " + std::string(stmt->TypeInfo().name);
-        return false;
-      });
-}
-
-bool Builder::GenerateVariableDeclStatement(
-    const ast::VariableDeclStatement* stmt) {
-  return GenerateFunctionVariable(stmt->variable);
-}
-
-uint32_t Builder::GenerateTypeIfNeeded(const sem::Type* type) {
-  if (type == nullptr) {
-    error_ = "attempting to generate type from null type";
-    return 0;
-  }
-
-  // Atomics are a type in WGSL, but aren't a distinct type in SPIR-V.
-  // Just emit the type inside the atomic.
-  if (auto* atomic = type->As<sem::Atomic>()) {
-    return GenerateTypeIfNeeded(atomic->Type());
-  }
-
-  // Pointers and references with differing accesses should not result in a
-  // different SPIR-V types, so we explicitly ignore the access.
-  // Pointers and References both map to a SPIR-V pointer type.
-  // Transform a Reference to a Pointer to prevent these having duplicated
-  // definitions in the generated SPIR-V. Note that nested pointers and
-  // references are not legal in WGSL, so only considering the top-level type is
-  // fine.
-  std::string type_name;
-  if (auto* ptr = type->As<sem::Pointer>()) {
-    type_name =
-        sem::Pointer(ptr->StoreType(), ptr->StorageClass(), ast::kReadWrite)
-            .type_name();
-  } else if (auto* ref = type->As<sem::Reference>()) {
-    type_name =
-        sem::Pointer(ref->StoreType(), ref->StorageClass(), ast::kReadWrite)
-            .type_name();
-  } else {
-    type_name = type->type_name();
-  }
-
-  return utils::GetOrCreate(type_name_to_id_, type_name, [&]() -> uint32_t {
-    auto result = result_op();
-    auto id = result.to_i();
-    bool ok = Switch(
-        type,
-        [&](const sem::Array* arr) {  //
-          return GenerateArrayType(arr, result);
-        },
-        [&](const sem::Bool*) {
-          push_type(spv::Op::OpTypeBool, {result});
-          return true;
-        },
-        [&](const sem::F32*) {
-          push_type(spv::Op::OpTypeFloat, {result, Operand::Int(32)});
-          return true;
-        },
-        [&](const sem::I32*) {
-          push_type(spv::Op::OpTypeInt,
-                    {result, Operand::Int(32), Operand::Int(1)});
-          return true;
-        },
-        [&](const sem::Matrix* mat) {  //
-          return GenerateMatrixType(mat, result);
-        },
-        [&](const sem::Pointer* ptr) {  //
-          return GeneratePointerType(ptr, result);
-        },
-        [&](const sem::Reference* ref) {  //
-          return GenerateReferenceType(ref, result);
-        },
-        [&](const sem::Struct* str) {  //
-          return GenerateStructType(str, result);
-        },
-        [&](const sem::U32*) {
-          push_type(spv::Op::OpTypeInt,
-                    {result, Operand::Int(32), Operand::Int(0)});
-          return true;
-        },
-        [&](const sem::Vector* vec) {  //
-          return GenerateVectorType(vec, result);
-        },
-        [&](const sem::Void*) {
-          push_type(spv::Op::OpTypeVoid, {result});
-          return true;
-        },
-        [&](const sem::StorageTexture* tex) {
-          if (!GenerateTextureType(tex, result)) {
-            return false;
-          }
-
-          // Register all three access types of StorageTexture names. In
-          // SPIR-V, we must output a single type, while the variable is
-          // annotated with the access type. Doing this ensures we de-dupe.
-          type_name_to_id_[builder_
-                               .create<sem::StorageTexture>(
-                                   tex->dim(), tex->texel_format(),
-                                   ast::Access::kRead, tex->type())
-                               ->type_name()] = id;
-          type_name_to_id_[builder_
-                               .create<sem::StorageTexture>(
-                                   tex->dim(), tex->texel_format(),
-                                   ast::Access::kWrite, tex->type())
-                               ->type_name()] = id;
-          type_name_to_id_[builder_
-                               .create<sem::StorageTexture>(
-                                   tex->dim(), tex->texel_format(),
-                                   ast::Access::kReadWrite, tex->type())
-                               ->type_name()] = id;
-          return true;
-        },
-        [&](const sem::Texture* tex) {
-          return GenerateTextureType(tex, result);
-        },
-        [&](const sem::Sampler*) {
-          push_type(spv::Op::OpTypeSampler, {result});
-
-          // Register both of the sampler type names. In SPIR-V they're the same
-          // sampler type, so we need to match that when we do the dedup check.
-          type_name_to_id_["__sampler_sampler"] = id;
-          type_name_to_id_["__sampler_comparison"] = id;
-          return true;
-        },
-        [&](Default) {
-          error_ = "unable to convert type: " + type->type_name();
-          return false;
-        });
-
-    if (!ok) {
-      return 0;
-    }
-
-    return id;
-  });
-}
-
-bool Builder::GenerateTextureType(const sem::Texture* texture,
-                                  const Operand& result) {
-  uint32_t array_literal = 0u;
-  const auto dim = texture->dim();
-  if (dim == ast::TextureDimension::k2dArray ||
-      dim == ast::TextureDimension::kCubeArray) {
-    array_literal = 1u;
-  }
-
-  uint32_t dim_literal = SpvDim2D;
-  if (dim == ast::TextureDimension::k1d) {
-    dim_literal = SpvDim1D;
-    if (texture->Is<sem::SampledTexture>()) {
-      push_capability(SpvCapabilitySampled1D);
-    } else if (texture->Is<sem::StorageTexture>()) {
-      push_capability(SpvCapabilityImage1D);
-    }
-  }
-  if (dim == ast::TextureDimension::k3d) {
-    dim_literal = SpvDim3D;
-  }
-  if (dim == ast::TextureDimension::kCube ||
-      dim == ast::TextureDimension::kCubeArray) {
-    dim_literal = SpvDimCube;
-  }
-
-  uint32_t ms_literal = 0u;
-  if (texture->IsAnyOf<sem::MultisampledTexture,
-                       sem::DepthMultisampledTexture>()) {
-    ms_literal = 1u;
-  }
-
-  uint32_t depth_literal = 0u;
-  if (texture->IsAnyOf<sem::DepthTexture, sem::DepthMultisampledTexture>()) {
-    depth_literal = 1u;
-  }
-
-  uint32_t sampled_literal = 2u;
-  if (texture->IsAnyOf<sem::MultisampledTexture, sem::SampledTexture,
-                       sem::DepthTexture, sem::DepthMultisampledTexture>()) {
-    sampled_literal = 1u;
-  }
-
-  if (dim == ast::TextureDimension::kCubeArray) {
-    if (texture->IsAnyOf<sem::SampledTexture, sem::DepthTexture>()) {
-      push_capability(SpvCapabilitySampledCubeArray);
-    }
-  }
-
-  uint32_t type_id = Switch(
-      texture,
-      [&](const sem::DepthTexture*) {
-        return GenerateTypeIfNeeded(builder_.create<sem::F32>());
-      },
-      [&](const sem::DepthMultisampledTexture*) {
-        return GenerateTypeIfNeeded(builder_.create<sem::F32>());
-      },
-      [&](const sem::SampledTexture* t) {
-        return GenerateTypeIfNeeded(t->type());
-      },
-      [&](const sem::MultisampledTexture* t) {
-        return GenerateTypeIfNeeded(t->type());
-      },
-      [&](const sem::StorageTexture* t) {
-        return GenerateTypeIfNeeded(t->type());
-      },
-      [&](Default) -> uint32_t {  //
-        return 0u;
-      });
-  if (type_id == 0u) {
-    return false;
-  }
-
-  uint32_t format_literal = SpvImageFormat_::SpvImageFormatUnknown;
-  if (auto* t = texture->As<sem::StorageTexture>()) {
-    format_literal = convert_texel_format_to_spv(t->texel_format());
-  }
-
-  push_type(spv::Op::OpTypeImage,
-            {result, Operand::Int(type_id), Operand::Int(dim_literal),
-             Operand::Int(depth_literal), Operand::Int(array_literal),
-             Operand::Int(ms_literal), Operand::Int(sampled_literal),
-             Operand::Int(format_literal)});
-
-  return true;
-}
-
-bool Builder::GenerateArrayType(const sem::Array* ary, const Operand& result) {
-  auto elem_type = GenerateTypeIfNeeded(ary->ElemType());
-  if (elem_type == 0) {
-    return false;
-  }
-
-  auto result_id = result.to_i();
-  if (ary->IsRuntimeSized()) {
-    push_type(spv::Op::OpTypeRuntimeArray, {result, Operand::Int(elem_type)});
-  } else {
-    auto len_id = GenerateConstantIfNeeded(ScalarConstant::U32(ary->Count()));
-    if (len_id == 0) {
-      return false;
-    }
-
-    push_type(spv::Op::OpTypeArray,
-              {result, Operand::Int(elem_type), Operand::Int(len_id)});
-  }
-
-  push_annot(spv::Op::OpDecorate,
-             {Operand::Int(result_id), Operand::Int(SpvDecorationArrayStride),
-              Operand::Int(ary->Stride())});
-  return true;
-}
-
-bool Builder::GenerateMatrixType(const sem::Matrix* mat,
-                                 const Operand& result) {
-  auto* col_type = builder_.create<sem::Vector>(mat->type(), mat->rows());
-  auto col_type_id = GenerateTypeIfNeeded(col_type);
-  if (has_error()) {
-    return false;
-  }
-
-  push_type(spv::Op::OpTypeMatrix,
-            {result, Operand::Int(col_type_id), Operand::Int(mat->columns())});
-  return true;
-}
-
-bool Builder::GeneratePointerType(const sem::Pointer* ptr,
-                                  const Operand& result) {
-  auto subtype_id = GenerateTypeIfNeeded(ptr->StoreType());
-  if (subtype_id == 0) {
-    return false;
-  }
-
-  auto stg_class = ConvertStorageClass(ptr->StorageClass());
-  if (stg_class == SpvStorageClassMax) {
-    error_ = "invalid storage class for pointer";
-    return false;
-  }
-
-  push_type(spv::Op::OpTypePointer,
-            {result, Operand::Int(stg_class), Operand::Int(subtype_id)});
-
-  return true;
-}
-
-bool Builder::GenerateReferenceType(const sem::Reference* ref,
-                                    const Operand& result) {
-  auto subtype_id = GenerateTypeIfNeeded(ref->StoreType());
-  if (subtype_id == 0) {
-    return false;
-  }
-
-  auto stg_class = ConvertStorageClass(ref->StorageClass());
-  if (stg_class == SpvStorageClassMax) {
-    error_ = "invalid storage class for reference";
-    return false;
-  }
-
-  push_type(spv::Op::OpTypePointer,
-            {result, Operand::Int(stg_class), Operand::Int(subtype_id)});
-
-  return true;
-}
-
-bool Builder::GenerateStructType(const sem::Struct* struct_type,
-                                 const Operand& result) {
-  auto struct_id = result.to_i();
-
-  if (struct_type->Name().IsValid()) {
-    push_debug(
-        spv::Op::OpName,
-        {Operand::Int(struct_id),
-         Operand::String(builder_.Symbols().NameFor(struct_type->Name()))});
-  }
-
-  OperandList ops;
-  ops.push_back(result);
-
-  auto* decl = struct_type->Declaration();
-  if (decl &&
-      ast::HasAttribute<transform::AddSpirvBlockAttribute::SpirvBlockAttribute>(
-          decl->attributes)) {
-    push_annot(spv::Op::OpDecorate,
-               {Operand::Int(struct_id), Operand::Int(SpvDecorationBlock)});
-  }
-
-  for (uint32_t i = 0; i < struct_type->Members().size(); ++i) {
-    auto mem_id = GenerateStructMember(struct_id, i, struct_type->Members()[i]);
-    if (mem_id == 0) {
-      return false;
-    }
-
-    ops.push_back(Operand::Int(mem_id));
-  }
-
-  push_type(spv::Op::OpTypeStruct, std::move(ops));
-  return true;
-}
-
-uint32_t Builder::GenerateStructMember(uint32_t struct_id,
-                                       uint32_t idx,
-                                       const sem::StructMember* member) {
-  push_debug(spv::Op::OpMemberName,
-             {Operand::Int(struct_id), Operand::Int(idx),
-              Operand::String(builder_.Symbols().NameFor(member->Name()))});
-
-  // Note: This will generate layout annotations for *all* structs, whether or
-  // not they are used in host-shareable variables. This is officially ok in
-  // SPIR-V 1.0 through 1.3. If / when we migrate to using SPIR-V 1.4 we'll have
-  // to only generate the layout info for structs used for certain storage
-  // classes.
-
-  push_annot(
-      spv::Op::OpMemberDecorate,
-      {Operand::Int(struct_id), Operand::Int(idx),
-       Operand::Int(SpvDecorationOffset), Operand::Int(member->Offset())});
-
-  // Infer and emit matrix layout.
-  auto* matrix_type = GetNestedMatrixType(member->Type());
-  if (matrix_type) {
-    push_annot(spv::Op::OpMemberDecorate,
-               {Operand::Int(struct_id), Operand::Int(idx),
-                Operand::Int(SpvDecorationColMajor)});
-    if (!matrix_type->type()->Is<sem::F32>()) {
-      error_ = "matrix scalar element type must be f32";
-      return 0;
-    }
-    const auto scalar_elem_size = 4;
-    const auto effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
-    push_annot(spv::Op::OpMemberDecorate,
-               {Operand::Int(struct_id), Operand::Int(idx),
-                Operand::Int(SpvDecorationMatrixStride),
-                Operand::Int(effective_row_count * scalar_elem_size)});
-  }
-
-  return GenerateTypeIfNeeded(member->Type());
-}
-
-bool Builder::GenerateVectorType(const sem::Vector* vec,
-                                 const Operand& result) {
-  auto type_id = GenerateTypeIfNeeded(vec->type());
-  if (has_error()) {
-    return false;
-  }
-
-  push_type(spv::Op::OpTypeVector,
-            {result, Operand::Int(type_id), Operand::Int(vec->Width())});
-  return true;
-}
-
-SpvStorageClass Builder::ConvertStorageClass(ast::StorageClass klass) const {
-  switch (klass) {
-    case ast::StorageClass::kInvalid:
-      return SpvStorageClassMax;
-    case ast::StorageClass::kInput:
-      return SpvStorageClassInput;
-    case ast::StorageClass::kOutput:
-      return SpvStorageClassOutput;
-    case ast::StorageClass::kUniform:
-      return SpvStorageClassUniform;
-    case ast::StorageClass::kWorkgroup:
-      return SpvStorageClassWorkgroup;
-    case ast::StorageClass::kUniformConstant:
-      return SpvStorageClassUniformConstant;
-    case ast::StorageClass::kStorage:
-      return SpvStorageClassStorageBuffer;
-    case ast::StorageClass::kPrivate:
-      return SpvStorageClassPrivate;
-    case ast::StorageClass::kFunction:
-      return SpvStorageClassFunction;
-    case ast::StorageClass::kNone:
-      break;
-  }
-  return SpvStorageClassMax;
-}
-
-SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin,
-                                   ast::StorageClass storage) {
-  switch (builtin) {
-    case ast::Builtin::kPosition:
-      if (storage == ast::StorageClass::kInput) {
-        return SpvBuiltInFragCoord;
-      } else if (storage == ast::StorageClass::kOutput) {
-        return SpvBuiltInPosition;
-      } else {
-        TINT_ICE(Writer, builder_.Diagnostics())
-            << "invalid storage class for builtin";
-        break;
-      }
-    case ast::Builtin::kVertexIndex:
-      return SpvBuiltInVertexIndex;
-    case ast::Builtin::kInstanceIndex:
-      return SpvBuiltInInstanceIndex;
-    case ast::Builtin::kFrontFacing:
-      return SpvBuiltInFrontFacing;
-    case ast::Builtin::kFragDepth:
-      return SpvBuiltInFragDepth;
-    case ast::Builtin::kLocalInvocationId:
-      return SpvBuiltInLocalInvocationId;
-    case ast::Builtin::kLocalInvocationIndex:
-      return SpvBuiltInLocalInvocationIndex;
-    case ast::Builtin::kGlobalInvocationId:
-      return SpvBuiltInGlobalInvocationId;
-    case ast::Builtin::kPointSize:
-      return SpvBuiltInPointSize;
-    case ast::Builtin::kWorkgroupId:
-      return SpvBuiltInWorkgroupId;
-    case ast::Builtin::kNumWorkgroups:
-      return SpvBuiltInNumWorkgroups;
-    case ast::Builtin::kSampleIndex:
-      push_capability(SpvCapabilitySampleRateShading);
-      return SpvBuiltInSampleId;
-    case ast::Builtin::kSampleMask:
-      return SpvBuiltInSampleMask;
-    case ast::Builtin::kNone:
-      break;
-  }
-  return SpvBuiltInMax;
-}
-
-void Builder::AddInterpolationDecorations(uint32_t id,
-                                          ast::InterpolationType type,
-                                          ast::InterpolationSampling sampling) {
-  switch (type) {
-    case ast::InterpolationType::kLinear:
-      push_annot(spv::Op::OpDecorate,
-                 {Operand::Int(id), Operand::Int(SpvDecorationNoPerspective)});
-      break;
-    case ast::InterpolationType::kFlat:
-      push_annot(spv::Op::OpDecorate,
-                 {Operand::Int(id), Operand::Int(SpvDecorationFlat)});
-      break;
-    case ast::InterpolationType::kPerspective:
-      break;
-  }
-  switch (sampling) {
-    case ast::InterpolationSampling::kCentroid:
-      push_annot(spv::Op::OpDecorate,
-                 {Operand::Int(id), Operand::Int(SpvDecorationCentroid)});
-      break;
-    case ast::InterpolationSampling::kSample:
-      push_capability(SpvCapabilitySampleRateShading);
-      push_annot(spv::Op::OpDecorate,
-                 {Operand::Int(id), Operand::Int(SpvDecorationSample)});
-      break;
-    case ast::InterpolationSampling::kCenter:
-    case ast::InterpolationSampling::kNone:
-      break;
-  }
-}
-
-SpvImageFormat Builder::convert_texel_format_to_spv(
-    const ast::TexelFormat format) {
-  switch (format) {
-    case ast::TexelFormat::kR32Uint:
-      return SpvImageFormatR32ui;
-    case ast::TexelFormat::kR32Sint:
-      return SpvImageFormatR32i;
-    case ast::TexelFormat::kR32Float:
-      return SpvImageFormatR32f;
-    case ast::TexelFormat::kRgba8Unorm:
-      return SpvImageFormatRgba8;
-    case ast::TexelFormat::kRgba8Snorm:
-      return SpvImageFormatRgba8Snorm;
-    case ast::TexelFormat::kRgba8Uint:
-      return SpvImageFormatRgba8ui;
-    case ast::TexelFormat::kRgba8Sint:
-      return SpvImageFormatRgba8i;
-    case ast::TexelFormat::kRg32Uint:
-      push_capability(SpvCapabilityStorageImageExtendedFormats);
-      return SpvImageFormatRg32ui;
-    case ast::TexelFormat::kRg32Sint:
-      push_capability(SpvCapabilityStorageImageExtendedFormats);
-      return SpvImageFormatRg32i;
-    case ast::TexelFormat::kRg32Float:
-      push_capability(SpvCapabilityStorageImageExtendedFormats);
-      return SpvImageFormatRg32f;
-    case ast::TexelFormat::kRgba16Uint:
-      return SpvImageFormatRgba16ui;
-    case ast::TexelFormat::kRgba16Sint:
-      return SpvImageFormatRgba16i;
-    case ast::TexelFormat::kRgba16Float:
-      return SpvImageFormatRgba16f;
-    case ast::TexelFormat::kRgba32Uint:
-      return SpvImageFormatRgba32ui;
-    case ast::TexelFormat::kRgba32Sint:
-      return SpvImageFormatRgba32i;
-    case ast::TexelFormat::kRgba32Float:
-      return SpvImageFormatRgba32f;
-    case ast::TexelFormat::kNone:
-      return SpvImageFormatUnknown;
-  }
-  return SpvImageFormatUnknown;
-}
-
-bool Builder::push_function_inst(spv::Op op, const OperandList& operands) {
-  if (functions_.empty()) {
-    std::ostringstream ss;
-    ss << "Internal error: trying to add SPIR-V instruction " << int(op)
-       << " outside a function";
-    error_ = ss.str();
-    return false;
-  }
-  functions_.back().push_inst(op, operands);
-  return true;
-}
-
-bool Builder::InsideBasicBlock() const {
-  if (functions_.empty()) {
-    return false;
-  }
-  const auto& instructions = functions_.back().instructions();
-  if (instructions.empty()) {
-    // The Function object does not explicitly represent its entry block
-    // label.  So return *true* because an empty list means the only
-    // thing in the function is that entry block label.
-    return true;
-  }
-  const auto& inst = instructions.back();
-  switch (inst.opcode()) {
-    case spv::Op::OpBranch:
-    case spv::Op::OpBranchConditional:
-    case spv::Op::OpSwitch:
-    case spv::Op::OpReturn:
-    case spv::Op::OpReturnValue:
-    case spv::Op::OpUnreachable:
-    case spv::Op::OpKill:
-    case spv::Op::OpTerminateInvocation:
-      return false;
-    default:
-      break;
-  }
-  return true;
-}
-
-Builder::ContinuingInfo::ContinuingInfo(
-    const ast::Statement* the_last_statement,
-    uint32_t loop_id,
-    uint32_t break_id)
-    : last_statement(the_last_statement),
-      loop_header_id(loop_id),
-      break_target_id(break_id) {
-  TINT_ASSERT(Writer, last_statement != nullptr);
-  TINT_ASSERT(Writer, loop_header_id != 0u);
-  TINT_ASSERT(Writer, break_target_id != 0u);
-}
-
-Builder::Backedge::Backedge(spv::Op the_opcode, OperandList the_operands)
-    : opcode(the_opcode), operands(the_operands) {}
-
-Builder::Backedge::Backedge(const Builder::Backedge& other) = default;
-Builder::Backedge& Builder::Backedge::operator=(
-    const Builder::Backedge& other) = default;
-Builder::Backedge::~Backedge() = default;
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
deleted file mode 100644
index 3b194f4..0000000
--- a/src/writer/spirv/builder.h
+++ /dev/null
@@ -1,660 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_BUILDER_H_
-#define SRC_WRITER_SPIRV_BUILDER_H_
-
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
-#include "spirv/unified1/spirv.h"
-#include "src/ast/assignment_statement.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/program_builder.h"
-#include "src/scope_stack.h"
-#include "src/sem/builtin.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/writer/spirv/function.h"
-#include "src/writer/spirv/scalar_constant.h"
-
-namespace tint {
-
-// Forward declarations
-namespace sem {
-class Call;
-class Reference;
-class TypeConstructor;
-class TypeConversion;
-}  // namespace sem
-
-namespace writer {
-namespace spirv {
-
-/// The result of sanitizing a program for generation.
-struct SanitizedResult {
-  /// The sanitized program.
-  Program program;
-};
-
-/// Sanitize a program in preparation for generating SPIR-V.
-/// @param emit_vertex_point_size `true` to emit a vertex point size builtin
-/// @param disable_workgroup_init `true` to disable workgroup memory zero
-/// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(const Program* program,
-                         bool emit_vertex_point_size = false,
-                         bool disable_workgroup_init = false);
-
-/// Builder class to create SPIR-V instructions from a module.
-class Builder {
- public:
-  /// Contains information for generating accessor chains
-  struct AccessorInfo {
-    AccessorInfo();
-    ~AccessorInfo();
-
-    /// The ID of the current chain source. The chain source may change as we
-    /// evaluate the access chain. The chain source always points to the ID
-    /// which we will use to evaluate the current set of accessors. This maybe
-    /// the original variable, or maybe an intermediary if we had to evaulate
-    /// the access chain early (in the case of a swizzle of an access chain).
-    uint32_t source_id;
-    /// The type of the current chain source. This type matches the deduced
-    /// result_type of the current source defined above.
-    const sem::Type* source_type;
-    /// A list of access chain indices to emit. Note, we _only_ have access
-    /// chain indices if the source is reference.
-    std::vector<uint32_t> access_chain_indices;
-  };
-
-  /// Constructor
-  /// @param program the program
-  explicit Builder(const Program* program);
-  ~Builder();
-
-  /// Generates the SPIR-V instructions for the given program
-  /// @returns true if the SPIR-V was successfully built
-  bool Build();
-
-  /// @returns the error string or blank if no error was reported.
-  const std::string& error() const { return error_; }
-  /// @returns true if the builder encountered an error
-  bool has_error() const { return !error_.empty(); }
-
-  /// @returns the number of uint32_t's needed to make up the results
-  uint32_t total_size() const;
-
-  /// @returns the id bound for this program
-  uint32_t id_bound() const { return next_id_; }
-
-  /// @returns the next id to be used
-  uint32_t next_id() {
-    auto id = next_id_;
-    next_id_ += 1;
-    return id;
-  }
-
-  /// Iterates over all the instructions in the correct order and calls the
-  /// given callback
-  /// @param cb the callback to execute
-  void iterate(std::function<void(const Instruction&)> cb) const;
-
-  /// Adds an instruction to the list of capabilities, if the capability
-  /// hasn't already been added.
-  /// @param cap the capability to set
-  void push_capability(uint32_t cap);
-  /// @returns the capabilities
-  const InstructionList& capabilities() const { return capabilities_; }
-  /// Adds an instruction to the extensions
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_extension(spv::Op op, const OperandList& operands) {
-    extensions_.push_back(Instruction{op, operands});
-  }
-  /// @returns the extensions
-  const InstructionList& extensions() const { return extensions_; }
-  /// Adds an instruction to the ext import
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_ext_import(spv::Op op, const OperandList& operands) {
-    ext_imports_.push_back(Instruction{op, operands});
-  }
-  /// @returns the ext imports
-  const InstructionList& ext_imports() const { return ext_imports_; }
-  /// Adds an instruction to the memory model
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_memory_model(spv::Op op, const OperandList& operands) {
-    memory_model_.push_back(Instruction{op, operands});
-  }
-  /// @returns the memory model
-  const InstructionList& memory_model() const { return memory_model_; }
-  /// Adds an instruction to the entry points
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_entry_point(spv::Op op, const OperandList& operands) {
-    entry_points_.push_back(Instruction{op, operands});
-  }
-  /// @returns the entry points
-  const InstructionList& entry_points() const { return entry_points_; }
-  /// Adds an instruction to the execution modes
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_execution_mode(spv::Op op, const OperandList& operands) {
-    execution_modes_.push_back(Instruction{op, operands});
-  }
-  /// @returns the execution modes
-  const InstructionList& execution_modes() const { return execution_modes_; }
-  /// Adds an instruction to the debug
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_debug(spv::Op op, const OperandList& operands) {
-    debug_.push_back(Instruction{op, operands});
-  }
-  /// @returns the debug instructions
-  const InstructionList& debug() const { return debug_; }
-  /// Adds an instruction to the types
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_type(spv::Op op, const OperandList& operands) {
-    types_.push_back(Instruction{op, operands});
-  }
-  /// @returns the type instructions
-  const InstructionList& types() const { return types_; }
-  /// Adds an instruction to the annotations
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_annot(spv::Op op, const OperandList& operands) {
-    annotations_.push_back(Instruction{op, operands});
-  }
-  /// @returns the annotations
-  const InstructionList& annots() const { return annotations_; }
-
-  /// Adds a function to the builder
-  /// @param func the function to add
-  void push_function(const Function& func) {
-    functions_.push_back(func);
-    current_label_id_ = func.label_id();
-  }
-  /// @returns the functions
-  const std::vector<Function>& functions() const { return functions_; }
-  /// Pushes an instruction to the current function. If we're outside
-  /// a function then issue an internal error and return false.
-  /// @param op the operation
-  /// @param operands the operands
-  /// @returns true if we succeeded
-  bool push_function_inst(spv::Op op, const OperandList& operands);
-  /// Pushes a variable to the current function
-  /// @param operands the variable operands
-  void push_function_var(const OperandList& operands) {
-    if (functions_.empty()) {
-      TINT_ICE(Writer, builder_.Diagnostics())
-          << "push_function_var() called without a function";
-    }
-    functions_.back().push_var(operands);
-  }
-
-  /// @returns true if the current instruction insertion point is
-  /// inside a basic block.
-  bool InsideBasicBlock() const;
-
-  /// Converts a storage class to a SPIR-V storage class.
-  /// @param klass the storage class to convert
-  /// @returns the SPIR-V storage class or SpvStorageClassMax on error.
-  SpvStorageClass ConvertStorageClass(ast::StorageClass klass) const;
-  /// Converts a builtin to a SPIR-V builtin and pushes a capability if needed.
-  /// @param builtin the builtin to convert
-  /// @param storage the storage class that this builtin is being used with
-  /// @returns the SPIR-V builtin or SpvBuiltInMax on error.
-  SpvBuiltIn ConvertBuiltin(ast::Builtin builtin, ast::StorageClass storage);
-
-  /// Converts an interpolate attribute to SPIR-V decorations and pushes a
-  /// capability if needed.
-  /// @param id the id to decorate
-  /// @param type the interpolation type
-  /// @param sampling the interpolation sampling
-  void AddInterpolationDecorations(uint32_t id,
-                                   ast::InterpolationType type,
-                                   ast::InterpolationSampling sampling);
-
-  /// Generates a label for the given id. Emits an error and returns false if
-  /// we're currently outside a function.
-  /// @param id the id to use for the label
-  /// @returns true on success.
-  bool GenerateLabel(uint32_t id);
-  /// Generates an assignment statement
-  /// @param assign the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateAssignStatement(const ast::AssignmentStatement* assign);
-  /// Generates a block statement, wrapped in a push/pop scope
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateBlockStatement(const ast::BlockStatement* stmt);
-  /// Generates a block statement
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateBlockStatementWithoutScoping(const ast::BlockStatement* stmt);
-  /// Generates a break statement
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateBreakStatement(const ast::BreakStatement* stmt);
-  /// Generates a continue statement
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateContinueStatement(const ast::ContinueStatement* stmt);
-  /// Generates a discard statement
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was successfully generated
-  bool GenerateDiscardStatement(const ast::DiscardStatement* stmt);
-  /// Generates an entry point instruction
-  /// @param func the function
-  /// @param id the id of the function
-  /// @returns true if the instruction was generated, false otherwise
-  bool GenerateEntryPoint(const ast::Function* func, uint32_t id);
-  /// Generates execution modes for an entry point
-  /// @param func the function
-  /// @param id the id of the function
-  /// @returns false on failure
-  bool GenerateExecutionModes(const ast::Function* func, uint32_t id);
-  /// Generates an expression
-  /// @param expr the expression to generate
-  /// @returns the resulting ID of the expression or 0 on error
-  uint32_t GenerateExpression(const ast::Expression* expr);
-  /// Generates the instructions for a function
-  /// @param func the function to generate
-  /// @returns true if the instructions were generated
-  bool GenerateFunction(const ast::Function* func);
-  /// Generates a function type if not already created
-  /// @param func the function to generate for
-  /// @returns the ID to use for the function type. Returns 0 on failure.
-  uint32_t GenerateFunctionTypeIfNeeded(const sem::Function* func);
-  /// Generates access control annotations if needed
-  /// @param type the type to generate for
-  /// @param struct_id the struct id
-  /// @param member_idx the member index
-  void GenerateMemberAccessIfNeeded(const sem::Type* type,
-                                    uint32_t struct_id,
-                                    uint32_t member_idx);
-  /// Generates a function variable
-  /// @param var the variable
-  /// @returns true if the variable was generated
-  bool GenerateFunctionVariable(const ast::Variable* var);
-  /// Generates a global variable
-  /// @param var the variable to generate
-  /// @returns true if the variable is emited.
-  bool GenerateGlobalVariable(const ast::Variable* var);
-  /// Generates an index accessor expression.
-  ///
-  /// For more information on accessors see the "Pointer evaluation" section of
-  /// the WGSL specification.
-  ///
-  /// @param expr the expresssion to generate
-  /// @returns the id of the expression or 0 on failure
-  uint32_t GenerateAccessorExpression(const ast::Expression* expr);
-  /// Generates an index accessor
-  /// @param expr the accessor to generate
-  /// @param info the current accessor information
-  /// @returns true if the accessor was generated successfully
-  bool GenerateIndexAccessor(const ast::IndexAccessorExpression* expr,
-                             AccessorInfo* info);
-  /// Generates a member accessor
-  /// @param expr the accessor to generate
-  /// @param info the current accessor information
-  /// @returns true if the accessor was generated successfully
-  bool GenerateMemberAccessor(const ast::MemberAccessorExpression* expr,
-                              AccessorInfo* info);
-  /// Generates an identifier expression
-  /// @param expr the expresssion to generate
-  /// @returns the id of the expression or 0 on failure
-  uint32_t GenerateIdentifierExpression(const ast::IdentifierExpression* expr);
-  /// Generates a unary op expression
-  /// @param expr the expression to generate
-  /// @returns the id of the expression or 0 on failure
-  uint32_t GenerateUnaryOpExpression(const ast::UnaryOpExpression* expr);
-  /// Generates an if statement
-  /// @param stmt the statement to generate
-  /// @returns true on success
-  bool GenerateIfStatement(const ast::IfStatement* stmt);
-  /// Generates an import instruction for the "GLSL.std.450" extended
-  /// instruction set, if one doesn't exist yet, and returns the import ID.
-  /// @returns the import ID, or 0 on error.
-  uint32_t GetGLSLstd450Import();
-  /// Generates a constructor expression
-  /// @param var the variable generated for, nullptr if no variable associated.
-  /// @param expr the expression to generate
-  /// @returns the ID of the expression or 0 on failure.
-  uint32_t GenerateConstructorExpression(const ast::Variable* var,
-                                         const ast::Expression* expr);
-  /// Generates a literal constant if needed
-  /// @param var the variable generated for, nullptr if no variable associated.
-  /// @param lit the literal to generate
-  /// @returns the ID on success or 0 on failure
-  uint32_t GenerateLiteralIfNeeded(const ast::Variable* var,
-                                   const ast::LiteralExpression* lit);
-  /// Generates a binary expression
-  /// @param expr the expression to generate
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateBinaryExpression(const ast::BinaryExpression* expr);
-  /// Generates a bitcast expression
-  /// @param expr the expression to generate
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateBitcastExpression(const ast::BitcastExpression* expr);
-  /// Generates a short circuting binary expression
-  /// @param expr the expression to generate
-  /// @returns teh expression ID on success or 0 otherwise
-  uint32_t GenerateShortCircuitBinaryExpression(
-      const ast::BinaryExpression* expr);
-  /// Generates a call expression
-  /// @param expr the expression to generate
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateCallExpression(const ast::CallExpression* expr);
-  /// Handles generating a function call expression
-  /// @param call the call expression
-  /// @param function the function being called
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateFunctionCall(const sem::Call* call,
-                                const sem::Function* function);
-  /// Handles generating a builtin call expression
-  /// @param call the call expression
-  /// @param builtin the builtin being called
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateBuiltinCall(const sem::Call* call,
-                               const sem::Builtin* builtin);
-  /// Handles generating a type constructor or type conversion expression
-  /// @param call the call expression
-  /// @param var the variable that is being initialized. May be null.
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateTypeConstructorOrConversion(const sem::Call* call,
-                                               const ast::Variable* var);
-  /// Generates a texture builtin call. Emits an error and returns false if
-  /// we're currently outside a function.
-  /// @param call the call expression
-  /// @param builtin the semantic information for the texture builtin
-  /// @param result_type result type operand of the texture instruction
-  /// @param result_id result identifier operand of the texture instruction
-  /// parameters
-  /// @returns true on success
-  bool GenerateTextureBuiltin(const sem::Call* call,
-                              const sem::Builtin* builtin,
-                              spirv::Operand result_type,
-                              spirv::Operand result_id);
-  /// Generates a control barrier statement.
-  /// @param builtin the semantic information for the barrier builtin call
-  /// @returns true on success
-  bool GenerateControlBarrierBuiltin(const sem::Builtin* builtin);
-  /// Generates an atomic builtin call.
-  /// @param call the call expression
-  /// @param builtin the semantic information for the atomic builtin call
-  /// @param result_type result type operand of the texture instruction
-  /// @param result_id result identifier operand of the texture instruction
-  /// @returns true on success
-  bool GenerateAtomicBuiltin(const sem::Call* call,
-                             const sem::Builtin* builtin,
-                             Operand result_type,
-                             Operand result_id);
-  /// Generates a sampled image
-  /// @param texture_type the texture type
-  /// @param texture_operand the texture operand
-  /// @param sampler_operand the sampler operand
-  /// @returns the expression ID
-  uint32_t GenerateSampledImage(const sem::Type* texture_type,
-                                Operand texture_operand,
-                                Operand sampler_operand);
-  /// Generates a cast or object copy for the expression result,
-  /// or return the ID generated the expression if it is already
-  /// of the right type.
-  /// @param to_type the type we're casting too
-  /// @param from_expr the expression to cast
-  /// @param is_global_init if this is a global initializer
-  /// @returns the expression ID on success or 0 otherwise
-  uint32_t GenerateCastOrCopyOrPassthrough(const sem::Type* to_type,
-                                           const ast::Expression* from_expr,
-                                           bool is_global_init);
-  /// Generates a loop statement
-  /// @param stmt the statement to generate
-  /// @returns true on successful generation
-  bool GenerateLoopStatement(const ast::LoopStatement* stmt);
-  /// Generates a return statement
-  /// @param stmt the statement to generate
-  /// @returns true on success, false otherwise
-  bool GenerateReturnStatement(const ast::ReturnStatement* stmt);
-  /// Generates a switch statement
-  /// @param stmt the statement to generate
-  /// @returns ture on success, false otherwise
-  bool GenerateSwitchStatement(const ast::SwitchStatement* stmt);
-  /// Generates a conditional section merge block
-  /// @param cond the condition
-  /// @param true_body the statements making up the true block
-  /// @param cur_else_idx the index of the current else statement to process
-  /// @param else_stmts the list of all else statements
-  /// @returns true on success, false on failure
-  bool GenerateConditionalBlock(const ast::Expression* cond,
-                                const ast::BlockStatement* true_body,
-                                size_t cur_else_idx,
-                                const ast::ElseStatementList& else_stmts);
-  /// Generates a statement
-  /// @param stmt the statement to generate
-  /// @returns true if the statement was generated
-  bool GenerateStatement(const ast::Statement* stmt);
-  /// Generates an expression. If the WGSL expression does not have reference
-  /// type, then return the SPIR-V ID for the expression. Otherwise implement
-  /// the WGSL Load Rule: generate an OpLoad and return the ID of the result.
-  /// Returns 0 if the expression could not be generated.
-  /// @param expr the semantic expression node to be generated
-  /// @returns the the ID of the expression, or loaded expression
-  uint32_t GenerateExpressionWithLoadIfNeeded(const sem::Expression* expr);
-  /// Generates an expression. If the WGSL expression does not have reference
-  /// type, then return the SPIR-V ID for the expression. Otherwise implement
-  /// the WGSL Load Rule: generate an OpLoad and return the ID of the result.
-  /// Returns 0 if the expression could not be generated.
-  /// @param expr the AST expression to be generated
-  /// @returns the the ID of the expression, or loaded expression
-  uint32_t GenerateExpressionWithLoadIfNeeded(const ast::Expression* expr);
-  /// Generates an OpLoad on the given ID if it has reference type in WGSL,
-  /// othewrise return the ID itself.
-  /// @param type the type of the expression
-  /// @param id the SPIR-V id of the experssion
-  /// @returns the ID of the loaded value or `id` if type is not a reference
-  uint32_t GenerateLoadIfNeeded(const sem::Type* type, uint32_t id);
-  /// Generates an OpStore. Emits an error and returns false if we're
-  /// currently outside a function.
-  /// @param to the ID to store too
-  /// @param from the ID to store from
-  /// @returns true on success
-  bool GenerateStore(uint32_t to, uint32_t from);
-  /// Generates a type if not already created
-  /// @param type the type to create
-  /// @returns the ID to use for the given type. Returns 0 on unknown type.
-  uint32_t GenerateTypeIfNeeded(const sem::Type* type);
-  /// Generates a texture type declaration
-  /// @param texture the texture to generate
-  /// @param result the result operand
-  /// @returns true if the texture was successfully generated
-  bool GenerateTextureType(const sem::Texture* texture, const Operand& result);
-  /// Generates an array type declaration
-  /// @param ary the array to generate
-  /// @param result the result operand
-  /// @returns true if the array was successfully generated
-  bool GenerateArrayType(const sem::Array* ary, const Operand& result);
-  /// Generates a matrix type declaration
-  /// @param mat the matrix to generate
-  /// @param result the result operand
-  /// @returns true if the matrix was successfully generated
-  bool GenerateMatrixType(const sem::Matrix* mat, const Operand& result);
-  /// Generates a pointer type declaration
-  /// @param ptr the pointer type to generate
-  /// @param result the result operand
-  /// @returns true if the pointer was successfully generated
-  bool GeneratePointerType(const sem::Pointer* ptr, const Operand& result);
-  /// Generates a reference type declaration
-  /// @param ref the reference type to generate
-  /// @param result the result operand
-  /// @returns true if the reference was successfully generated
-  bool GenerateReferenceType(const sem::Reference* ref, const Operand& result);
-  /// Generates a vector type declaration
-  /// @param struct_type the vector to generate
-  /// @param result the result operand
-  /// @returns true if the vector was successfully generated
-  bool GenerateStructType(const sem::Struct* struct_type,
-                          const Operand& result);
-  /// Generates a struct member
-  /// @param struct_id the id of the parent structure
-  /// @param idx the index of the member
-  /// @param member the member to generate
-  /// @returns the id of the struct member or 0 on error.
-  uint32_t GenerateStructMember(uint32_t struct_id,
-                                uint32_t idx,
-                                const sem::StructMember* member);
-  /// Generates a variable declaration statement
-  /// @param stmt the statement to generate
-  /// @returns true on successfull generation
-  bool GenerateVariableDeclStatement(const ast::VariableDeclStatement* stmt);
-  /// Generates a vector type declaration
-  /// @param vec the vector to generate
-  /// @param result the result operand
-  /// @returns true if the vector was successfully generated
-  bool GenerateVectorType(const sem::Vector* vec, const Operand& result);
-
-  /// Generates instructions to splat `scalar_id` into a vector of type
-  /// `vec_type`
-  /// @param scalar_id scalar to splat
-  /// @param vec_type type of vector
-  /// @returns id of the new vector
-  uint32_t GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type);
-
-  /// Generates instructions to add or subtract two matrices
-  /// @param lhs_id id of multiplicand
-  /// @param rhs_id id of multiplier
-  /// @param type type of both matrices and of result
-  /// @param op one of `spv::Op::OpFAdd` or `spv::Op::OpFSub`
-  /// @returns id of the result matrix
-  uint32_t GenerateMatrixAddOrSub(uint32_t lhs_id,
-                                  uint32_t rhs_id,
-                                  const sem::Matrix* type,
-                                  spv::Op op);
-
-  /// Converts TexelFormat to SPIR-V and pushes an appropriate capability.
-  /// @param format AST image format type
-  /// @returns SPIR-V image format type
-  SpvImageFormat convert_texel_format_to_spv(const ast::TexelFormat format);
-
-  /// Determines if the given type constructor is created from constant values
-  /// @param expr the expression to check
-  /// @returns true if the constructor is constant
-  bool IsConstructorConst(const ast::Expression* expr);
-
- private:
-  /// @returns an Operand with a new result ID in it. Increments the next_id_
-  /// automatically.
-  Operand result_op();
-
-  /// @returns the resolved type of the ast::Expression `expr`
-  /// @param expr the expression
-  const sem::Type* TypeOf(const ast::Expression* expr) const {
-    return builder_.TypeOf(expr);
-  }
-
-  /// Generates a scalar constant if needed
-  /// @param constant the constant to generate.
-  /// @returns the ID on success or 0 on failure
-  uint32_t GenerateConstantIfNeeded(const ScalarConstant& constant);
-
-  /// Generates a constant-null of the given type, if needed
-  /// @param type the type of the constant null to generate.
-  /// @returns the ID on success or 0 on failure
-  uint32_t GenerateConstantNullIfNeeded(const sem::Type* type);
-
-  /// Generates a vector constant splat if needed
-  /// @param type the type of the vector to generate
-  /// @param value_id the ID of the scalar value to splat
-  /// @returns the ID on success or 0 on failure
-  uint32_t GenerateConstantVectorSplatIfNeeded(const sem::Vector* type,
-                                               uint32_t value_id);
-
-  ProgramBuilder builder_;
-  std::string error_;
-  uint32_t next_id_ = 1;
-  uint32_t current_label_id_ = 0;
-  InstructionList capabilities_;
-  InstructionList extensions_;
-  InstructionList ext_imports_;
-  InstructionList memory_model_;
-  InstructionList entry_points_;
-  InstructionList execution_modes_;
-  InstructionList debug_;
-  InstructionList types_;
-  InstructionList annotations_;
-  std::vector<Function> functions_;
-
-  std::unordered_map<std::string, uint32_t> import_name_to_id_;
-  std::unordered_map<Symbol, uint32_t> func_symbol_to_id_;
-  std::unordered_map<sem::CallTargetSignature, uint32_t> func_sig_to_id_;
-  std::unordered_map<std::string, uint32_t> type_name_to_id_;
-  std::unordered_map<ScalarConstant, uint32_t> const_to_id_;
-  std::unordered_map<std::string, uint32_t> type_constructor_to_id_;
-  std::unordered_map<std::string, uint32_t> const_null_to_id_;
-  std::unordered_map<uint64_t, uint32_t> const_splat_to_id_;
-  std::unordered_map<std::string, uint32_t>
-      texture_type_name_to_sampled_image_type_id_;
-  ScopeStack<uint32_t> scope_stack_;
-  std::unordered_map<uint32_t, const ast::Variable*> spirv_id_to_variable_;
-  std::vector<uint32_t> merge_stack_;
-  std::vector<uint32_t> continue_stack_;
-  std::unordered_set<uint32_t> capability_set_;
-  bool has_overridable_workgroup_size_ = false;
-
-  struct ContinuingInfo {
-    ContinuingInfo(const ast::Statement* last_statement,
-                   uint32_t loop_header_id,
-                   uint32_t break_target_id);
-    // The last statement in the continiung block.
-    const ast::Statement* const last_statement = nullptr;
-    // The ID of the loop header
-    const uint32_t loop_header_id = 0u;
-    // The ID of the merge block for the loop.
-    const uint32_t break_target_id = 0u;
-  };
-  // Stack of nodes, where each is the last statement in a surrounding
-  // continuing block.
-  std::vector<ContinuingInfo> continuing_stack_;
-
-  // The instruction to emit as the backedge of a loop.
-  struct Backedge {
-    Backedge(spv::Op, OperandList);
-    Backedge(const Backedge&);
-    Backedge& operator=(const Backedge&);
-    ~Backedge();
-
-    spv::Op opcode;
-    OperandList operands;
-  };
-  std::vector<Backedge> backedge_stack_;
-};
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_BUILDER_H_
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
deleted file mode 100644
index ff00575..0000000
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ /dev/null
@@ -1,1047 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, IndexAccessor_VectorRef_Literal) {
-  // var ary : vec3<f32>;
-  // ary[1]  -> ref<f32>
-
-  auto* var = Var("ary", ty.vec3<f32>());
-
-  auto* ary = Expr("ary");
-  auto* idx_expr = Expr(1);
-
-  auto* expr = IndexAccessor(ary, idx_expr);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%6 = OpTypeInt 32 1
-%7 = OpConstant %6 1
-%8 = OpTypePointer Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpAccessChain %8 %1 %7
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_VectorRef_Dynamic) {
-  // var ary : vec3<f32>;
-  // var idx : i32;
-  // ary[idx]  -> ref<f32>
-
-  auto* var = Var("ary", ty.vec3<f32>());
-  auto* idx = Var("idx", ty.i32());
-
-  auto* ary = Expr("ary");
-  auto* idx_expr = Expr("idx");
-
-  auto* expr = IndexAccessor(ary, idx_expr);
-  WrapInFunction(var, idx, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunctionVariable(idx)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 12u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%8 = OpTypeInt 32 1
-%7 = OpTypePointer Function %8
-%9 = OpConstantNull %8
-%11 = OpTypePointer Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-%6 = OpVariable %7 Function %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%10 = OpLoad %8 %6
-%12 = OpAccessChain %11 %1 %10
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_VectorRef_Dynamic2) {
-  // var ary : vec3<f32>;
-  // ary[1 + 2]  -> ref<f32>
-
-  auto* var = Var("ary", ty.vec3<f32>());
-
-  auto* ary = Expr("ary");
-
-  auto* expr = IndexAccessor(ary, Add(1, 2));
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%6 = OpTypeInt 32 1
-%7 = OpConstant %6 1
-%8 = OpConstant %6 2
-%10 = OpTypePointer Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpIAdd %6 %7 %8
-%11 = OpAccessChain %10 %1 %9
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_ArrayRef_MultiLevel) {
-  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
-
-  // var ary : array<vec3<f32>, 4>
-  // ary[3][2];
-
-  auto* var = Var("ary", ary4);
-
-  auto* expr = IndexAccessor(IndexAccessor("ary", 3), 2);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 13u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 4
-%3 = OpTypeArray %4 %7
-%2 = OpTypePointer Function %3
-%8 = OpConstantNull %3
-%9 = OpTypeInt 32 1
-%10 = OpConstant %9 3
-%11 = OpConstant %9 2
-%12 = OpTypePointer Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %8
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%13 = OpAccessChain %12 %1 %10 %11
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_ArrayRef_ArrayWithSwizzle) {
-  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
-
-  // var a : array<vec3<f32>, 4>;
-  // a[2].xy;
-
-  auto* var = Var("ary", ary4);
-
-  auto* expr = MemberAccessor(IndexAccessor("ary", 2), "xy");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 15u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 4
-%3 = OpTypeArray %4 %7
-%2 = OpTypePointer Function %3
-%8 = OpConstantNull %3
-%9 = OpTypeInt 32 1
-%10 = OpConstant %9 2
-%11 = OpTypePointer Function %4
-%13 = OpTypeVector %5 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %8
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%12 = OpAccessChain %11 %1 %10
-%14 = OpLoad %4 %12
-%15 = OpVectorShuffle %13 %14 %14 0 1
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor) {
-  // my_struct {
-  //   a : f32
-  //   b : f32
-  // }
-  // var ident : my_struct
-  // ident.b
-
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
-
-  auto* var = Var("ident", ty.Of(s));
-
-  auto* expr = MemberAccessor("ident", "b");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeStruct %4 %4
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-%8 = OpTypePointer Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpAccessChain %8 %1 %7
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Nested) {
-  // inner_struct {
-  //   a : f32
-  //   b : f32
-  // }
-  // my_struct {
-  //   inner : inner_struct
-  // }
-  //
-  // var ident : my_struct
-  // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
-
-  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
-
-  auto* var = Var("ident", ty.Of(s_type));
-  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeStruct %5 %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Function %3
-%6 = OpConstantNull %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%9 = OpConstant %7 1
-%10 = OpTypePointer Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%11 = OpAccessChain %10 %1 %8 %9
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_NonPointer) {
-  // my_struct {
-  //   a : f32
-  //   b : f32
-  // }
-  // let ident : my_struct = my_struct();
-  // ident.b
-
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
-
-  auto* var = Const("ident", ty.Of(s), Construct(ty.Of(s), 0.f, 0.f));
-
-  auto* expr = MemberAccessor("ident", "b");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 5u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeStruct %2 %2
-%3 = OpConstant %2 0
-%4 = OpConstantComposite %1 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpCompositeExtract %2 %4 1
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Nested_NonPointer) {
-  // inner_struct {
-  //   a : f32
-  //   b : f32
-  // }
-  // my_struct {
-  //   inner : inner_struct
-  // }
-  //
-  // let ident : my_struct = my_struct();
-  // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
-
-  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
-
-  auto* var =
-      Const("ident", ty.Of(s_type),
-            Construct(ty.Of(s_type), Construct(ty.Of(inner_struct), 0.f, 0.f)));
-  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeStruct %3 %3
-%1 = OpTypeStruct %2
-%4 = OpConstant %3 0
-%5 = OpConstantComposite %2 %4 %4
-%6 = OpConstantComposite %1 %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%7 = OpCompositeExtract %2 %6 0
-%8 = OpCompositeExtract %3 %7 1
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Nested_WithAlias) {
-  // struct Inner {
-  //   a : f32
-  //   b : f32
-  // };
-  // type Alias = Inner;
-  // my_struct {
-  //   inner : Inner
-  // }
-  //
-  // var ident : my_struct
-  // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
-
-  auto* alias = Alias("Alias", ty.Of(inner_struct));
-  auto* s_type = Structure("Outer", {Member("inner", ty.Of(alias))});
-
-  auto* var = Var("ident", ty.Of(s_type));
-  auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 10u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeStruct %5 %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Function %3
-%6 = OpConstantNull %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%9 = OpTypePointer Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%10 = OpAccessChain %9 %1 %8 %8
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Nested_Assignment_LHS) {
-  // inner_struct {
-  //   a : f32
-  // }
-  // my_struct {
-  //   inner : inner_struct
-  // }
-  //
-  // var ident : my_struct
-  // ident.inner.a = 2.0f;
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
-
-  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
-
-  auto* var = Var("ident", ty.Of(s_type));
-  auto* expr =
-      Assign(MemberAccessor(MemberAccessor("ident", "inner"), "a"), Expr(2.0f));
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(expr)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeStruct %5 %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Function %3
-%6 = OpConstantNull %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%9 = OpTypePointer Function %5
-%11 = OpConstant %5 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%10 = OpAccessChain %9 %1 %8 %8
-OpStore %10 %11
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Nested_Assignment_RHS) {
-  // inner_struct {
-  //   a : f32
-  // }
-  // my_struct {
-  //   inner : inner_struct
-  // }
-  //
-  // var ident : my_struct
-  // var store : f32 = ident.inner.a
-
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
-
-  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
-
-  auto* var = Var("ident", ty.Of(s_type));
-  auto* store = Var("store", ty.f32());
-
-  auto* rhs = MemberAccessor(MemberAccessor("ident", "inner"), "a");
-  auto* expr = Assign("store", rhs);
-  WrapInFunction(var, store, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunctionVariable(store)) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(expr)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeStruct %5 %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Function %3
-%6 = OpConstantNull %3
-%8 = OpTypePointer Function %5
-%9 = OpConstantNull %5
-%10 = OpTypeInt 32 0
-%11 = OpConstant %10 0
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %6
-%7 = OpVariable %8 Function %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%12 = OpAccessChain %8 %1 %11 %11
-%13 = OpLoad %5 %12
-OpStore %7 %13
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Swizzle_Single) {
-  // ident.y
-
-  auto* var = Var("ident", ty.vec3<f32>());
-
-  auto* expr = MemberAccessor("ident", "y");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-%8 = OpTypePointer Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpAccessChain %8 %1 %7
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Swizzle_MultipleNames) {
-  // ident.yx
-
-  auto* var = Var("ident", ty.vec3<f32>());
-
-  auto* expr = MemberAccessor("ident", "yx");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%6 = OpTypeVector %4 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%7 = OpLoad %3 %1
-%8 = OpVectorShuffle %6 %7 %7 1 0
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Swizzle_of_Swizzle) {
-  // ident.yxz.xz
-
-  auto* var = Var("ident", ty.vec3<f32>());
-
-  auto* expr = MemberAccessor(MemberAccessor("ident", "yxz"), "xz");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%8 = OpTypeVector %4 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%7 = OpVectorShuffle %3 %6 %6 1 0 2
-%9 = OpVectorShuffle %8 %7 %7 0 2
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Member_of_Swizzle) {
-  // ident.yxz.x
-
-  auto* var = Var("ident", ty.vec3<f32>());
-
-  auto* expr = MemberAccessor(MemberAccessor("ident", "yxz"), "x");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%7 = OpVectorShuffle %3 %6 %6 1 0 2
-%8 = OpCompositeExtract %4 %7 0
-)");
-}
-
-TEST_F(BuilderTest, MemberAccessor_Array_of_Swizzle) {
-  // index.yxz[1]
-
-  auto* var = Var("ident", ty.vec3<f32>());
-
-  auto* expr = IndexAccessor(MemberAccessor("ident", "yxz"), 1);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 10u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-%8 = OpTypeInt 32 1
-%9 = OpConstant %8 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%7 = OpVectorShuffle %3 %6 %6 1 0 2
-%10 = OpCompositeExtract %4 %7 1
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_Mixed_ArrayAndMember) {
-  // type C = struct {
-  //   baz : vec3<f32>
-  // }
-  // type B = struct {
-  //  bar : C;
-  // }
-  // type A = struct {
-  //   foo : array<B, 3>
-  // }
-  // var index : array<A, 2>
-  // index[0].foo[2].bar.baz.yx
-
-  auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
-
-  auto* b_type = Structure("B", {Member("bar", ty.Of(c_type))});
-  auto* b_ary_type = ty.array(ty.Of(b_type), 3);
-  auto* a_type = Structure("A", {Member("foo", b_ary_type)});
-
-  auto* a_ary_type = ty.array(ty.Of(a_type), 2);
-  auto* var = Var("index", a_ary_type);
-  auto* expr = MemberAccessor(
-      MemberAccessor(
-          MemberAccessor(
-              IndexAccessor(MemberAccessor(IndexAccessor("index", 0), "foo"),
-                            2),
-              "bar"),
-          "baz"),
-      "yx");
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 22u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeFloat 32
-%8 = OpTypeVector %9 3
-%7 = OpTypeStruct %8
-%6 = OpTypeStruct %7
-%10 = OpTypeInt 32 0
-%11 = OpConstant %10 3
-%5 = OpTypeArray %6 %11
-%4 = OpTypeStruct %5
-%12 = OpConstant %10 2
-%3 = OpTypeArray %4 %12
-%2 = OpTypePointer Function %3
-%13 = OpConstantNull %3
-%14 = OpTypeInt 32 1
-%15 = OpConstant %14 0
-%16 = OpConstant %10 0
-%17 = OpConstant %14 2
-%18 = OpTypePointer Function %8
-%20 = OpTypeVector %9 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %13
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%19 = OpAccessChain %18 %1 %15 %16 %17 %16 %16
-%21 = OpLoad %8 %19
-%22 = OpVectorShuffle %20 %21 %21 1 0
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_Of_Vec) {
-  // let pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
-  //   vec2<f32>(0.0, 0.5),
-  //   vec2<f32>(-0.5, -0.5),
-  //   vec2<f32>(0.5, -0.5));
-  // pos[1]
-
-  auto* var =
-      Const("pos", ty.array(ty.vec2<f32>(), 3),
-            Construct(ty.array(ty.vec2<f32>(), 3), vec2<f32>(0.0f, 0.5f),
-                      vec2<f32>(-0.5f, -0.5f), vec2<f32>(0.5f, -0.5f)));
-
-  auto* expr = IndexAccessor("pos", 1u);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%8 = OpTypeInt 32 0
-%9 = OpConstant %8 3
-%5 = OpTypeArray %6 %9
-%10 = OpConstant %7 0
-%11 = OpConstant %7 0.5
-%12 = OpConstantComposite %6 %10 %11
-%13 = OpConstant %7 -0.5
-%14 = OpConstantComposite %6 %13 %13
-%15 = OpConstantComposite %6 %11 %13
-%16 = OpConstantComposite %5 %12 %14 %15
-%17 = OpConstant %8 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%18 = OpCompositeExtract %6 %16 1
-OpReturn
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, IndexAccessor_Of_Array_Of_f32) {
-  // let pos : array<array<f32, 2>, 3> = array<vec2<f32, 2>, 3>(
-  //   array<f32, 2>(0.0, 0.5),
-  //   array<f32, 2>(-0.5, -0.5),
-  //   array<f32, 2>(0.5, -0.5));
-  // pos[2][1]
-
-  auto* var =
-      Const("pos", ty.array(ty.vec2<f32>(), 3),
-            Construct(ty.array(ty.vec2<f32>(), 3), vec2<f32>(0.0f, 0.5f),
-                      vec2<f32>(-0.5f, -0.5f), vec2<f32>(0.5f, -0.5f)));
-
-  auto* expr = IndexAccessor(IndexAccessor("pos", 2u), 1u);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%8 = OpTypeInt 32 0
-%9 = OpConstant %8 3
-%5 = OpTypeArray %6 %9
-%10 = OpConstant %7 0
-%11 = OpConstant %7 0.5
-%12 = OpConstantComposite %6 %10 %11
-%13 = OpConstant %7 -0.5
-%14 = OpConstantComposite %6 %13 %13
-%15 = OpConstantComposite %6 %11 %13
-%16 = OpConstantComposite %5 %12 %14 %15
-%17 = OpConstant %8 2
-%19 = OpConstant %8 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%18 = OpCompositeExtract %6 %16 2
-%20 = OpCompositeExtract %7 %18 1
-OpReturn
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, IndexAccessor_Vec_Literal) {
-  // let pos : vec2<f32> = vec2<f32>(0.0, 0.5);
-  // pos[1]
-
-  auto* var = Const("pos", ty.vec2<f32>(), vec2<f32>(0.0f, 0.5f));
-
-  auto* expr = IndexAccessor("pos", 1u);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 8u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 0
-%4 = OpConstant %2 0.5
-%5 = OpConstantComposite %1 %3 %4
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), "");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%8 = OpCompositeExtract %2 %5 1
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_Vec_Dynamic) {
-  // let pos : vec2<f32> = vec2<f32>(0.0, 0.5);
-  // idx : i32
-  // pos[idx]
-
-  auto* var = Const("pos", ty.vec2<f32>(), vec2<f32>(0.0f, 0.5f));
-  auto* idx = Var("idx", ty.i32());
-  auto* expr = IndexAccessor("pos", idx);
-
-  WrapInFunction(var, idx, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunctionVariable(idx)) << b.error();
-  EXPECT_EQ(b.GenerateAccessorExpression(expr), 11u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 0
-%4 = OpConstant %2 0.5
-%5 = OpConstantComposite %1 %3 %4
-%8 = OpTypeInt 32 1
-%7 = OpTypePointer Function %8
-%9 = OpConstantNull %8
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%6 = OpVariable %7 Function %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%10 = OpLoad %8 %6
-%11 = OpVectorExtractDynamic %2 %5 %10
-)");
-}
-
-TEST_F(BuilderTest, IndexAccessor_Array_Literal) {
-  // let a : array<f32, 3>;
-  // a[2]
-
-  auto* var = Const("a", ty.array<f32, 3>(),
-                    Construct(ty.array<f32, 3>(), 0.0f, 0.5f, 1.0f));
-  auto* expr = IndexAccessor("a", 2);
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 3
-%5 = OpTypeArray %6 %8
-%9 = OpConstant %6 0
-%10 = OpConstant %6 0.5
-%11 = OpConstant %6 1
-%12 = OpConstantComposite %5 %9 %10 %11
-%13 = OpTypeInt 32 1
-%14 = OpConstant %13 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), "");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%15 = OpCompositeExtract %6 %12 2
-OpReturn
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, IndexAccessor_Array_Dynamic) {
-  // let a : array<f32, 3>;
-  // idx : i32
-  // a[idx]
-
-  auto* var = Const("a", ty.array<f32, 3>(),
-                    Construct(ty.array<f32, 3>(), 0.0f, 0.5f, 1.0f));
-
-  auto* idx = Var("idx", ty.i32());
-  auto* expr = IndexAccessor("a", idx);
-
-  WrapInFunction(var, idx, expr);
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 3
-%5 = OpTypeArray %6 %8
-%9 = OpConstant %6 0
-%10 = OpConstant %6 0.5
-%11 = OpConstant %6 1
-%12 = OpConstantComposite %5 %9 %10 %11
-%15 = OpTypeInt 32 1
-%14 = OpTypePointer Function %15
-%16 = OpConstantNull %15
-%18 = OpTypePointer Function %5
-%19 = OpConstantNull %5
-%21 = OpTypePointer Function %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%13 = OpVariable %14 Function %16
-%17 = OpVariable %18 Function %19
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %17 %12
-%20 = OpLoad %15 %13
-%22 = OpAccessChain %21 %17 %20
-%23 = OpLoad %6 %22
-OpReturn
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, IndexAccessor_Matrix_Dynamic) {
-  // let a : mat2x2<f32>(vec2<f32>(1., 2.), vec2<f32>(3., 4.));
-  // idx : i32
-  // a[idx]
-
-  auto* var =
-      Const("a", ty.mat2x2<f32>(),
-            Construct(ty.mat2x2<f32>(), Construct(ty.vec2<f32>(), 1.f, 2.f),
-                      Construct(ty.vec2<f32>(), 3.f, 4.f)));
-
-  auto* idx = Var("idx", ty.i32());
-  auto* expr = IndexAccessor("a", idx);
-
-  WrapInFunction(var, idx, expr);
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%5 = OpTypeMatrix %6 2
-%8 = OpConstant %7 1
-%9 = OpConstant %7 2
-%10 = OpConstantComposite %6 %8 %9
-%11 = OpConstant %7 3
-%12 = OpConstant %7 4
-%13 = OpConstantComposite %6 %11 %12
-%14 = OpConstantComposite %5 %10 %13
-%17 = OpTypeInt 32 1
-%16 = OpTypePointer Function %17
-%18 = OpConstantNull %17
-%20 = OpTypePointer Function %5
-%21 = OpConstantNull %5
-%23 = OpTypePointer Function %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%15 = OpVariable %16 Function %18
-%19 = OpVariable %20 Function %21
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %19 %14
-%22 = OpLoad %17 %15
-%24 = OpAccessChain %23 %19 %22
-%25 = OpLoad %6 %24
-OpReturn
-)");
-
-  Validate(b);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
deleted file mode 100644
index f611143..0000000
--- a/src/writer/spirv/builder_assign_test.cc
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Assign_Var) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign("var", 1.f);
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpConstant %3 1
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %1 %5
-)");
-}
-
-TEST_F(BuilderTest, Assign_Var_OutsideFunction_IsError) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign("var", Expr(1.f));
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_FALSE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_TRUE(b.has_error());
-  EXPECT_EQ(b.error(),
-            "Internal error: trying to add SPIR-V instruction 62 outside a "
-            "function");
-}
-
-TEST_F(BuilderTest, Assign_Var_ZeroConstructor) {
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* val = vec3<f32>();
-  auto* assign = Assign("var", val);
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %1 %5
-)");
-}
-
-TEST_F(BuilderTest, Assign_Var_Complex_ConstructorWithExtract) {
-  auto* init = vec3<f32>(vec2<f32>(1.f, 2.f), 3.f);
-
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign("var", init);
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%6 = OpTypeVector %4 2
-%7 = OpConstant %4 1
-%8 = OpConstant %4 2
-%9 = OpConstantComposite %6 %7 %8
-%12 = OpConstant %4 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%10 = OpCompositeExtract %4 %9 0
-%11 = OpCompositeExtract %4 %9 1
-%13 = OpCompositeConstruct %3 %10 %11 %12
-OpStore %1 %13
-)");
-}
-
-TEST_F(BuilderTest, Assign_Var_Complex_Constructor) {
-  auto* init = vec3<f32>(1.f, 2.f, 3.f);
-
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign("var", init);
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%6 = OpConstant %4 1
-%7 = OpConstant %4 2
-%8 = OpConstant %4 3
-%9 = OpConstantComposite %3 %6 %7 %8
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %1 %9
-)");
-}
-
-TEST_F(BuilderTest, Assign_StructMember) {
-  // my_struct {
-  //   a : f32
-  //   b : f32
-  // }
-  // var ident : my_struct
-  // ident.b = 4.0;
-
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
-
-  auto* v = Var("ident", ty.Of(s));
-
-  auto* assign = Assign(MemberAccessor("ident", "b"), Expr(4.f));
-
-  WrapInFunction(v, assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeStruct %4 %4
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%5 = OpTypeInt 32 0
-%6 = OpConstant %5 1
-%7 = OpTypePointer Function %4
-%9 = OpConstant %4 4
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%8 = OpAccessChain %7 %1 %6
-OpStore %8 %9
-)");
-}
-
-TEST_F(BuilderTest, Assign_Vector) {
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* val = vec3<f32>(1.f, 1.f, 3.f);
-  auto* assign = Assign("var", val);
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%6 = OpConstant %4 1
-%7 = OpConstant %4 3
-%8 = OpConstantComposite %3 %6 %6 %7
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %1 %8
-)");
-}
-
-TEST_F(BuilderTest, Assign_Vector_MemberByName) {
-  // var.y = 1
-
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign(MemberAccessor("var", "y"), Expr(1.f));
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-%8 = OpTypePointer Private %4
-%10 = OpConstant %4 1
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpAccessChain %8 %1 %7
-OpStore %9 %10
-)");
-}
-
-TEST_F(BuilderTest, Assign_Vector_MemberByIndex) {
-  // var[1] = 1
-
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* assign = Assign(IndexAccessor("var", 1), Expr(1.f));
-
-  WrapInFunction(assign);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%6 = OpTypeInt 32 1
-%7 = OpConstant %6 1
-%8 = OpTypePointer Private %4
-%10 = OpConstant %4 1
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpAccessChain %8 %1 %7
-OpStore %9 %10
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_binary_expression_test.cc b/src/writer/spirv/builder_binary_expression_test.cc
deleted file mode 100644
index a238845..0000000
--- a/src/writer/spirv/builder_binary_expression_test.cc
+++ /dev/null
@@ -1,1249 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-struct BinaryData {
-  ast::BinaryOp op;
-  std::string name;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << data.op;
-  return out;
-}
-
-using BinaryArithSignedIntegerTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryArithSignedIntegerTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3);
-  auto* rhs = Expr(4);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstant %1 3
-%3 = OpConstant %1 4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %1 %2 %3\n");
-}
-
-TEST_P(BinaryArithSignedIntegerTest, Vector) {
-  auto param = GetParam();
-
-  // Skip ops that are illegal for this type
-  if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
-      param.op == ast::BinaryOp::kXor) {
-    return;
-  }
-
-  auto* lhs = vec3<i32>(1, 1, 1);
-  auto* rhs = vec3<i32>(1, 1, 1);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %1 %4 %4\n");
-}
-TEST_P(BinaryArithSignedIntegerTest, Scalar_Loads) {
-  auto param = GetParam();
-
-  auto* var = Var("param", ty.i32());
-  auto* expr =
-      create<ast::BinaryExpression>(param.op, Expr("param"), Expr("param"));
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpLoad %3 %1
-%6 = OpLoad %3 %1
-%7 = )" + param.name +
-                R"( %3 %5 %6
-)");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryArithSignedIntegerTest,
-    // NOTE: No left and right shift as they require u32 for rhs operand
-    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
-                    BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
-                    BinaryData{ast::BinaryOp::kDivide, "OpSDiv"},
-                    BinaryData{ast::BinaryOp::kModulo, "OpSMod"},
-                    BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
-                    BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
-                    BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
-                    BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
-
-using BinaryArithUnsignedIntegerTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryArithUnsignedIntegerTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3u);
-  auto* rhs = Expr(4u);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstant %1 3
-%3 = OpConstant %1 4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %1 %2 %3\n");
-}
-TEST_P(BinaryArithUnsignedIntegerTest, Vector) {
-  auto param = GetParam();
-
-  // Skip ops that are illegal for this type
-  if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
-      param.op == ast::BinaryOp::kXor) {
-    return;
-  }
-
-  auto* lhs = vec3<u32>(1u, 1u, 1u);
-  auto* rhs = vec3<u32>(1u, 1u, 1u);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %1 %4 %4\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryArithUnsignedIntegerTest,
-    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
-                    BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
-                    BinaryData{ast::BinaryOp::kDivide, "OpUDiv"},
-                    BinaryData{ast::BinaryOp::kModulo, "OpUMod"},
-                    BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
-                    BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
-                    BinaryData{ast::BinaryOp::kShiftLeft, "OpShiftLeftLogical"},
-                    BinaryData{ast::BinaryOp::kShiftRight,
-                               "OpShiftRightLogical"},
-                    BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
-                    BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
-
-using BinaryArithFloatTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryArithFloatTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3.2f);
-  auto* rhs = Expr(4.5f);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 3.20000005
-%3 = OpConstant %1 4.5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %1 %2 %3\n");
-}
-
-TEST_P(BinaryArithFloatTest, Vector) {
-  auto param = GetParam();
-
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %1 %4 %4\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryArithFloatTest,
-    testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpFAdd"},
-                    BinaryData{ast::BinaryOp::kDivide, "OpFDiv"},
-                    BinaryData{ast::BinaryOp::kModulo, "OpFRem"},
-                    BinaryData{ast::BinaryOp::kMultiply, "OpFMul"},
-                    BinaryData{ast::BinaryOp::kSubtract, "OpFSub"}));
-
-using BinaryOperatorBoolTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryOperatorBoolTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(true);
-  auto* rhs = Expr(false);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantTrue %1
-%3 = OpConstantFalse %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %1 %2 %3\n");
-}
-
-TEST_P(BinaryOperatorBoolTest, Vector) {
-  auto param = GetParam();
-
-  auto* lhs = vec3<bool>(false, true, false);
-  auto* rhs = vec3<bool>(true, false, true);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeVector %2 3
-%3 = OpConstantFalse %2
-%4 = OpConstantTrue %2
-%5 = OpConstantComposite %1 %3 %4 %3
-%6 = OpConstantComposite %1 %4 %3 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%7 = " + param.name + " %1 %5 %6\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryOperatorBoolTest,
-    testing::Values(BinaryData{ast::BinaryOp::kEqual, "OpLogicalEqual"},
-                    BinaryData{ast::BinaryOp::kNotEqual, "OpLogicalNotEqual"},
-                    BinaryData{ast::BinaryOp::kAnd, "OpLogicalAnd"},
-                    BinaryData{ast::BinaryOp::kOr, "OpLogicalOr"}));
-
-using BinaryCompareUnsignedIntegerTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryCompareUnsignedIntegerTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3u);
-  auto* rhs = Expr(4u);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstant %1 3
-%3 = OpConstant %1 4
-%5 = OpTypeBool
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %5 %2 %3\n");
-}
-
-TEST_P(BinaryCompareUnsignedIntegerTest, Vector) {
-  auto param = GetParam();
-
-  auto* lhs = vec3<u32>(1u, 1u, 1u);
-  auto* rhs = vec3<u32>(1u, 1u, 1u);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-%7 = OpTypeBool
-%6 = OpTypeVector %7 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %6 %4 %4\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryCompareUnsignedIntegerTest,
-    testing::Values(
-        BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
-        BinaryData{ast::BinaryOp::kGreaterThan, "OpUGreaterThan"},
-        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpUGreaterThanEqual"},
-        BinaryData{ast::BinaryOp::kLessThan, "OpULessThan"},
-        BinaryData{ast::BinaryOp::kLessThanEqual, "OpULessThanEqual"},
-        BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
-
-using BinaryCompareSignedIntegerTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryCompareSignedIntegerTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3);
-  auto* rhs = Expr(4);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstant %1 3
-%3 = OpConstant %1 4
-%5 = OpTypeBool
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %5 %2 %3\n");
-}
-
-TEST_P(BinaryCompareSignedIntegerTest, Vector) {
-  auto param = GetParam();
-
-  auto* lhs = vec3<i32>(1, 1, 1);
-  auto* rhs = vec3<i32>(1, 1, 1);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-%7 = OpTypeBool
-%6 = OpTypeVector %7 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %6 %4 %4\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryCompareSignedIntegerTest,
-    testing::Values(
-        BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
-        BinaryData{ast::BinaryOp::kGreaterThan, "OpSGreaterThan"},
-        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpSGreaterThanEqual"},
-        BinaryData{ast::BinaryOp::kLessThan, "OpSLessThan"},
-        BinaryData{ast::BinaryOp::kLessThanEqual, "OpSLessThanEqual"},
-        BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
-
-using BinaryCompareFloatTest = TestParamHelper<BinaryData>;
-TEST_P(BinaryCompareFloatTest, Scalar) {
-  auto param = GetParam();
-
-  auto* lhs = Expr(3.2f);
-  auto* rhs = Expr(4.5f);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 3.20000005
-%3 = OpConstant %1 4.5
-%5 = OpTypeBool
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%4 = " + param.name + " %5 %2 %3\n");
-}
-
-TEST_P(BinaryCompareFloatTest, Vector) {
-  auto param = GetParam();
-
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-%7 = OpTypeBool
-%6 = OpTypeVector %7 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = " + param.name + " %6 %4 %4\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryCompareFloatTest,
-    testing::Values(
-        BinaryData{ast::BinaryOp::kEqual, "OpFOrdEqual"},
-        BinaryData{ast::BinaryOp::kGreaterThan, "OpFOrdGreaterThan"},
-        BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpFOrdGreaterThanEqual"},
-        BinaryData{ast::BinaryOp::kLessThan, "OpFOrdLessThan"},
-        BinaryData{ast::BinaryOp::kLessThanEqual, "OpFOrdLessThanEqual"},
-        BinaryData{ast::BinaryOp::kNotEqual, "OpFOrdNotEqual"}));
-
-TEST_F(BuilderTest, Binary_Multiply_VectorScalar) {
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-  auto* rhs = Expr(1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = OpVectorTimesScalar %1 %4 %3\n");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_ScalarVector) {
-  auto* lhs = Expr(1.f);
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 1
-%3 = OpTypeVector %1 3
-%4 = OpConstantComposite %3 %2 %2 %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%5 = OpVectorTimesScalar %3 %4 %2\n");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_MatrixScalar) {
-  auto* var = Var("mat", ty.mat3x3<f32>());
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                             Expr("mat"), Expr(1.f));
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %6 %7
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix) {
-  auto* var = Var("mat", ty.mat3x3<f32>());
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                             Expr(1.f), Expr("mat"));
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%7 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %7 %6
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_MatrixVector) {
-  auto* var = Var("mat", ty.mat3x3<f32>());
-  auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 1
-%8 = OpConstantComposite %4 %7 %7 %7
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%9 = OpMatrixTimesVector %4 %6 %8
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_VectorMatrix) {
-  auto* var = Var("mat", ty.mat3x3<f32>());
-  auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, Expr("mat"));
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 1
-%7 = OpConstantComposite %4 %6 %6 %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%8 = OpLoad %3 %1
-%9 = OpVectorTimesMatrix %4 %7 %8
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_MatrixMatrix) {
-  auto* var = Var("mat", ty.mat3x3<f32>());
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
-                                             Expr("mat"), Expr("mat"));
-
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpLoad %3 %1
-%7 = OpLoad %3 %1
-%8 = OpMatrixTimesMatrix %3 %6 %7
-)");
-}
-
-TEST_F(BuilderTest, Binary_LogicalAnd) {
-  auto* lhs =
-      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
-
-  auto* rhs =
-      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 1
-%4 = OpConstant %2 2
-%6 = OpTypeBool
-%9 = OpConstant %2 3
-%10 = OpConstant %2 4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-%5 = OpIEqual %6 %3 %4
-OpSelectionMerge %7 None
-OpBranchConditional %5 %8 %7
-%8 = OpLabel
-%11 = OpIEqual %6 %9 %10
-OpBranch %7
-%7 = OpLabel
-%12 = OpPhi %6 %5 %1 %11 %8
-)");
-}
-
-TEST_F(BuilderTest, Binary_LogicalAnd_WithLoads) {
-  auto* a_var =
-      Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
-  auto* b_var =
-      Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                             Expr("a"), Expr("b"));
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%5 = OpTypePointer Private %2
-%4 = OpVariable %5 Private %3
-%6 = OpConstantFalse %2
-%7 = OpVariable %5 Private %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-%8 = OpLoad %2 %4
-OpSelectionMerge %9 None
-OpBranchConditional %8 %10 %9
-%10 = OpLabel
-%11 = OpLoad %2 %7
-OpBranch %9
-%9 = OpLabel
-%12 = OpPhi %2 %8 %1 %11 %10
-)");
-}
-
-TEST_F(BuilderTest, Binary_logicalOr_Nested_LogicalAnd) {
-  // Test an expression like
-  //    a || (b && c)
-  // From: crbug.com/tint/355
-
-  auto* logical_and_expr = create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                             Expr(true), logical_and_expr);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%8 = OpConstantFalse %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-OpSelectionMerge %4 None
-OpBranchConditional %3 %4 %5
-%5 = OpLabel
-OpSelectionMerge %6 None
-OpBranchConditional %3 %7 %6
-%7 = OpLabel
-OpBranch %6
-%6 = OpLabel
-%9 = OpPhi %2 %3 %5 %8 %7
-OpBranch %4
-%4 = OpLabel
-%10 = OpPhi %2 %3 %1 %9 %6
-)");
-}
-
-TEST_F(BuilderTest, Binary_logicalAnd_Nested_LogicalOr) {
-  // Test an expression like
-  //    a && (b || c)
-  // From: crbug.com/tint/355
-
-  auto* logical_or_expr = create<ast::BinaryExpression>(
-      ast::BinaryOp::kLogicalOr, Expr(true), Expr(false));
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
-                                             Expr(true), logical_or_expr);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%8 = OpConstantFalse %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-OpSelectionMerge %4 None
-OpBranchConditional %3 %5 %4
-%5 = OpLabel
-OpSelectionMerge %6 None
-OpBranchConditional %3 %6 %7
-%7 = OpLabel
-OpBranch %6
-%6 = OpLabel
-%9 = OpPhi %2 %3 %5 %8 %7
-OpBranch %4
-%4 = OpLabel
-%10 = OpPhi %2 %3 %1 %9 %6
-)");
-}
-
-TEST_F(BuilderTest, Binary_LogicalOr) {
-  auto* lhs =
-      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
-
-  auto* rhs =
-      create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
-
-  auto* expr =
-      create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 1
-%4 = OpConstant %2 2
-%6 = OpTypeBool
-%9 = OpConstant %2 3
-%10 = OpConstant %2 4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-%5 = OpIEqual %6 %3 %4
-OpSelectionMerge %7 None
-OpBranchConditional %5 %7 %8
-%8 = OpLabel
-%11 = OpIEqual %6 %9 %10
-OpBranch %7
-%7 = OpLabel
-%12 = OpPhi %6 %5 %1 %11 %8
-)");
-}
-
-TEST_F(BuilderTest, Binary_LogicalOr_WithLoads) {
-  auto* a_var =
-      Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
-  auto* b_var =
-      Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
-
-  auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
-                                             Expr("a"), Expr("b"));
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  b.GenerateLabel(b.next_id());
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%5 = OpTypePointer Private %2
-%4 = OpVariable %5 Private %3
-%6 = OpConstantFalse %2
-%7 = OpVariable %5 Private %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLabel
-%8 = OpLoad %2 %4
-OpSelectionMerge %9 None
-OpBranchConditional %8 %9 %10
-%10 = OpLabel
-%11 = OpLoad %2 %7
-OpBranch %9
-%9 = OpLabel
-%12 = OpPhi %2 %8 %1 %11 %10
-)");
-}
-
-namespace BinaryArithVectorScalar {
-
-enum class Type { f32, i32, u32 };
-static const ast::Expression* MakeVectorExpr(ProgramBuilder* builder,
-                                             Type type) {
-  switch (type) {
-    case Type::f32:
-      return builder->vec3<ProgramBuilder::f32>(1.f, 1.f, 1.f);
-    case Type::i32:
-      return builder->vec3<ProgramBuilder::i32>(1, 1, 1);
-    case Type::u32:
-      return builder->vec3<ProgramBuilder::u32>(1u, 1u, 1u);
-  }
-  return nullptr;
-}
-static const ast::Expression* MakeScalarExpr(ProgramBuilder* builder,
-                                             Type type) {
-  switch (type) {
-    case Type::f32:
-      return builder->Expr(1.f);
-    case Type::i32:
-      return builder->Expr(1);
-    case Type::u32:
-      return builder->Expr(1u);
-  }
-  return nullptr;
-}
-static std::string OpTypeDecl(Type type) {
-  switch (type) {
-    case Type::f32:
-      return "OpTypeFloat 32";
-    case Type::i32:
-      return "OpTypeInt 32 1";
-    case Type::u32:
-      return "OpTypeInt 32 0";
-  }
-  return {};
-}
-
-struct Param {
-  Type type;
-  ast::BinaryOp op;
-  std::string name;
-};
-
-using BinaryArithVectorScalarTest = TestParamHelper<Param>;
-TEST_P(BinaryArithVectorScalarTest, VectorScalar) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = MakeVectorExpr(this, param.type);
-  const ast::Expression* rhs = MakeScalarExpr(this, param.type);
-  std::string op_type_decl = OpTypeDecl(param.type);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = )" + op_type_decl + R"(
-%5 = OpTypeVector %6 3
-%7 = OpConstant %6 1
-%8 = OpConstantComposite %5 %7 %7 %7
-%11 = OpTypePointer Function %5
-%12 = OpConstantNull %5
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpVariable %11 Function %12
-%13 = OpCompositeConstruct %5 %7 %7 %7
-%9 = )" + param.name + R"( %5 %8 %13
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-TEST_P(BinaryArithVectorScalarTest, ScalarVector) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = MakeScalarExpr(this, param.type);
-  const ast::Expression* rhs = MakeVectorExpr(this, param.type);
-  std::string op_type_decl = OpTypeDecl(param.type);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = )" + op_type_decl + R"(
-%6 = OpConstant %5 1
-%7 = OpTypeVector %5 3
-%8 = OpConstantComposite %7 %6 %6 %6
-%11 = OpTypePointer Function %7
-%12 = OpConstantNull %7
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpVariable %11 Function %12
-%13 = OpCompositeConstruct %7 %6 %6 %6
-%9 = )" + param.name + R"( %7 %13 %8
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    BinaryArithVectorScalarTest,
-    testing::Values(Param{Type::f32, ast::BinaryOp::kAdd, "OpFAdd"},
-                    Param{Type::f32, ast::BinaryOp::kDivide, "OpFDiv"},
-                    // NOTE: Modulo not allowed on mixed float scalar-vector
-                    // Param{Type::f32, ast::BinaryOp::kModulo, "OpFMod"},
-                    // NOTE: We test f32 multiplies separately as we emit
-                    // OpVectorTimesScalar for this case
-                    // Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
-                    Param{Type::f32, ast::BinaryOp::kSubtract, "OpFSub"},
-
-                    Param{Type::i32, ast::BinaryOp::kAdd, "OpIAdd"},
-                    Param{Type::i32, ast::BinaryOp::kDivide, "OpSDiv"},
-                    Param{Type::i32, ast::BinaryOp::kModulo, "OpSMod"},
-                    Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
-                    Param{Type::i32, ast::BinaryOp::kSubtract, "OpISub"},
-
-                    Param{Type::u32, ast::BinaryOp::kAdd, "OpIAdd"},
-                    Param{Type::u32, ast::BinaryOp::kDivide, "OpUDiv"},
-                    Param{Type::u32, ast::BinaryOp::kModulo, "OpUMod"},
-                    Param{Type::u32, ast::BinaryOp::kMultiply, "OpIMul"},
-                    Param{Type::u32, ast::BinaryOp::kSubtract, "OpISub"}));
-
-using BinaryArithVectorScalarMultiplyTest = TestParamHelper<Param>;
-TEST_P(BinaryArithVectorScalarMultiplyTest, VectorScalar) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = MakeVectorExpr(this, param.type);
-  const ast::Expression* rhs = MakeScalarExpr(this, param.type);
-  std::string op_type_decl = OpTypeDecl(param.type);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = )" + op_type_decl + R"(
-%5 = OpTypeVector %6 3
-%7 = OpConstant %6 1
-%8 = OpConstantComposite %5 %7 %7 %7
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%9 = OpVectorTimesScalar %5 %8 %7
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-TEST_P(BinaryArithVectorScalarMultiplyTest, ScalarVector) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = MakeScalarExpr(this, param.type);
-  const ast::Expression* rhs = MakeVectorExpr(this, param.type);
-  std::string op_type_decl = OpTypeDecl(param.type);
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = )" + op_type_decl + R"(
-%6 = OpConstant %5 1
-%7 = OpTypeVector %5 3
-%8 = OpConstantComposite %7 %6 %6 %6
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%9 = OpVectorTimesScalar %7 %8 %6
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(BuilderTest,
-                         BinaryArithVectorScalarMultiplyTest,
-                         testing::Values(Param{
-                             Type::f32, ast::BinaryOp::kMultiply, "OpFMul"}));
-
-}  // namespace BinaryArithVectorScalar
-
-namespace BinaryArithMatrixMatrix {
-
-struct Param {
-  ast::BinaryOp op;
-  std::string name;
-};
-
-using BinaryArithMatrixMatrix = TestParamHelper<Param>;
-TEST_P(BinaryArithMatrixMatrix, AddOrSubtract) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = mat3x4<f32>();
-  const ast::Expression* rhs = mat3x4<f32>();
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 4
-%5 = OpTypeMatrix %6 3
-%8 = OpConstantNull %5
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpCompositeExtract %6 %8 0
-%11 = OpCompositeExtract %6 %8 0
-%12 = )" + param.name + R"( %6 %10 %11
-%13 = OpCompositeExtract %6 %8 1
-%14 = OpCompositeExtract %6 %8 1
-%15 = )" + param.name + R"( %6 %13 %14
-%16 = OpCompositeExtract %6 %8 2
-%17 = OpCompositeExtract %6 %8 2
-%18 = )" + param.name + R"( %6 %16 %17
-%19 = OpCompositeConstruct %5 %12 %15 %18
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(  //
-    BuilderTest,
-    BinaryArithMatrixMatrix,
-    testing::Values(Param{ast::BinaryOp::kAdd, "OpFAdd"},
-                    Param{ast::BinaryOp::kSubtract, "OpFSub"}));
-
-using BinaryArithMatrixMatrixMultiply = TestParamHelper<Param>;
-TEST_P(BinaryArithMatrixMatrixMultiply, Multiply) {
-  auto& param = GetParam();
-
-  const ast::Expression* lhs = mat3x4<f32>();
-  const ast::Expression* rhs = mat4x3<f32>();
-
-  auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
-
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 4
-%5 = OpTypeMatrix %6 3
-%8 = OpConstantNull %5
-%10 = OpTypeVector %7 3
-%9 = OpTypeMatrix %10 4
-%11 = OpConstantNull %9
-%13 = OpTypeMatrix %6 4
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%12 = OpMatrixTimesMatrix %13 %8 %11
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(  //
-    BuilderTest,
-    BinaryArithMatrixMatrixMultiply,
-    testing::Values(Param{ast::BinaryOp::kMultiply, "OpFMul"}));
-
-}  // namespace BinaryArithMatrixMatrix
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_bitcast_expression_test.cc b/src/writer/spirv/builder_bitcast_expression_test.cc
deleted file mode 100644
index a692932..0000000
--- a/src/writer/spirv/builder_bitcast_expression_test.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Bitcast) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.u32(), Expr(2.4f));
-
-  WrapInFunction(bitcast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%3 = OpTypeFloat 32
-%4 = OpConstant %3 2.4000001
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpBitcast %2 %4
-)");
-}
-
-TEST_F(BuilderTest, Bitcast_DuplicateType) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(2.4f));
-
-  WrapInFunction(bitcast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpConstant %2 2.4000001
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpCopyObject %2 %3
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_block_test.cc b/src/writer/spirv/builder_block_test.cc
deleted file mode 100644
index e0cad3c..0000000
--- a/src/writer/spirv/builder_block_test.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-// TODO(amaiorano): Disabled because Resolver now emits "redeclared identifier
-// 'var'".
-TEST_F(BuilderTest, DISABLED_Block) {
-  // Note, this test uses shadow variables which aren't allowed in WGSL but
-  // serves to prove the block code is pushing new scopes as needed.
-  auto* inner = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)),
-                      Assign("var", 2.f));
-  auto* outer = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)),
-                      Assign("var", 1.f), inner, Assign("var", 3.f));
-
-  WrapInFunction(outer);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_TRUE(b.GenerateStatement(outer)) << b.error();
-  EXPECT_FALSE(b.has_error());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-%5 = OpConstant %3 1
-%7 = OpConstant %3 2
-%8 = OpConstant %3 3
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %4
-%6 = OpVariable %2 Function %4
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %1 %5
-OpStore %6 %7
-OpStore %1 %8
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_builtin_test.cc b/src/writer/spirv/builder_builtin_test.cc
deleted file mode 100644
index 3786265..0000000
--- a/src/writer/spirv/builder_builtin_test.cc
+++ /dev/null
@@ -1,2560 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/utils/string.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuiltinBuilderTest = TestHelper;
-
-template <typename T>
-using BuiltinBuilderTestWithParam = TestParamHelper<T>;
-
-struct BuiltinData {
-  std::string name;
-  std::string op;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.name;
-  return out;
-}
-
-using BuiltinBoolTest = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(BuiltinBoolTest, Call_Bool_Scalar) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  // both any and all are 'passthrough' for scalar booleans
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            "%10 = OpLoad %3 %1\nOpReturn\n");
-}
-
-TEST_P(BuiltinBoolTest, Call_Bool_Vector) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.vec3<bool>(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeBool
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-
-  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
-%10 = ${op} %4 %11
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         BuiltinBoolTest,
-                         testing::Values(BuiltinData{"any", "OpAny"},
-                                         BuiltinData{"all", "OpAll"}));
-
-using BuiltinFloatTest = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(BuiltinFloatTest, Call_Float_Scalar) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%10 = OpTypeBool
-)");
-
-  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
-%9 = ${op} %10 %11
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-
-TEST_P(BuiltinFloatTest, Call_Float_Vector) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%12 = OpTypeBool
-%11 = OpTypeVector %12 3
-)");
-
-  auto expected = utils::ReplaceAll(R"(%13 = OpLoad %3 %1
-%10 = ${op} %11 %13
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         BuiltinFloatTest,
-                         testing::Values(BuiltinData{"isNan", "OpIsNan"},
-                                         BuiltinData{"isInf", "OpIsInf"}));
-
-TEST_F(BuiltinBuilderTest, IsFinite_Scalar) {
-  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call("isFinite", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%10 = OpTypeBool
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%11 = OpLoad %3 %1
-%12 = OpIsInf %10 %11
-%13 = OpIsNan %10 %11
-%14 = OpLogicalOr %10 %12 %13
-%9 = OpLogicalNot %10 %14
-OpReturn
-)");
-}
-
-TEST_F(BuiltinBuilderTest, IsFinite_Vector) {
-  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("isFinite", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%12 = OpTypeBool
-%11 = OpTypeVector %12 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%13 = OpLoad %3 %1
-%14 = OpIsInf %11 %13
-%15 = OpIsNan %11 %13
-%16 = OpLogicalOr %11 %14 %15
-%10 = OpLogicalNot %11 %16
-OpReturn
-)");
-}
-
-TEST_F(BuiltinBuilderTest, IsNormal_Scalar) {
-  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call("isNormal", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  auto got = DumpBuilder(b);
-  EXPECT_EQ(got, R"(%12 = OpExtInstImport "GLSL.std.450"
-OpName %1 "v"
-OpName %7 "a_func"
-%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%10 = OpTypeBool
-%13 = OpTypeInt 32 0
-%14 = OpConstant %13 133693440
-%15 = OpConstant %13 524288
-%16 = OpConstant %13 133169152
-%7 = OpFunction %6 None %5
-%8 = OpLabel
-%11 = OpLoad %3 %1
-%17 = OpBitcast %13 %11
-%18 = OpBitwiseAnd %13 %17 %14
-%19 = OpExtInst %13 %12 UClamp %18 %15 %16
-%9 = OpIEqual %10 %18 %19
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, IsNormal_Vector) {
-  auto* var = Global("v", ty.vec2<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("isNormal", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  auto got = DumpBuilder(b);
-  EXPECT_EQ(got, R"(%14 = OpExtInstImport "GLSL.std.450"
-OpName %1 "v"
-OpName %8 "a_func"
-%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 2
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%12 = OpTypeBool
-%11 = OpTypeVector %12 2
-%15 = OpTypeInt 32 0
-%16 = OpConstant %15 133693440
-%17 = OpConstant %15 524288
-%18 = OpConstant %15 133169152
-%19 = OpTypeVector %15 2
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%13 = OpLoad %3 %1
-%20 = OpCompositeConstruct %19 %16 %16
-%21 = OpCompositeConstruct %19 %17 %17
-%22 = OpCompositeConstruct %19 %18 %18
-%23 = OpBitcast %19 %13
-%24 = OpBitwiseAnd %19 %23 %20
-%25 = OpExtInst %19 %14 UClamp %24 %21 %22
-%10 = OpIEqual %11 %24 %25
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using BuiltinIntTest = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(BuiltinIntTest, Call_SInt_Scalar) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
-%9 = ${op} %3 %10
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-
-TEST_P(BuiltinIntTest, Call_SInt_Vector) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-
-  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
-%10 = ${op} %3 %11
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-
-TEST_P(BuiltinIntTest, Call_UInt_Scalar) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.u32(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 0
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
-%9 = ${op} %3 %10
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-
-TEST_P(BuiltinIntTest, Call_UInt_Vector) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-
-  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
-%10 = ${op} %3 %11
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    BuiltinIntTest,
-    testing::Values(BuiltinData{"countOneBits", "OpBitCount"},
-                    BuiltinData{"reverseBits", "OpBitReverse"}));
-
-TEST_F(BuiltinBuilderTest, Call_Dot_F32) {
-  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("dot", "v", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%11 = OpLoad %3 %1
-%12 = OpLoad %3 %1
-%10 = OpDot %4 %11 %12
-OpReturn
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Dot_U32) {
-  auto* var = Global("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("dot", "v", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%11 = OpLoad %3 %1
-%12 = OpLoad %3 %1
-%13 = OpCompositeExtract %4 %11 0
-%14 = OpCompositeExtract %4 %12 0
-%15 = OpIMul %4 %13 %14
-%16 = OpCompositeExtract %4 %11 1
-%17 = OpCompositeExtract %4 %12 1
-%18 = OpIMul %4 %16 %17
-%19 = OpIAdd %4 %15 %18
-%20 = OpCompositeExtract %4 %11 2
-%21 = OpCompositeExtract %4 %12 2
-%22 = OpIMul %4 %20 %21
-%10 = OpIAdd %4 %19 %22
-OpReturn
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Dot_I32) {
-  auto* var = Global("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("dot", "v", "v");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%11 = OpLoad %3 %1
-%12 = OpLoad %3 %1
-%13 = OpCompositeExtract %4 %11 0
-%14 = OpCompositeExtract %4 %12 0
-%15 = OpIMul %4 %13 %14
-%16 = OpCompositeExtract %4 %11 1
-%17 = OpCompositeExtract %4 %12 1
-%18 = OpIMul %4 %16 %17
-%19 = OpIAdd %4 %15 %18
-%20 = OpCompositeExtract %4 %11 2
-%21 = OpCompositeExtract %4 %12 2
-%22 = OpIMul %4 %20 %21
-%10 = OpIAdd %4 %19 %22
-OpReturn
-)");
-}
-
-using BuiltinDeriveTest = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(BuiltinDeriveTest, Call_Derivative_Scalar) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("func", {}, ty.void_(), {CallStmt(expr)},
-                    {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  auto expected = utils::ReplaceAll(R"(%10 = OpLoad %3 %1
-%9 = ${op} %3 %10
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-
-TEST_P(BuiltinDeriveTest, Call_Derivative_Vector) {
-  auto param = GetParam();
-  auto* var = Global("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call(param.name, "v");
-  auto* func = Func("func", {}, ty.void_(), {CallStmt(expr)},
-                    {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  if (param.name != "dpdx" && param.name != "dpdy" && param.name != "fwidth") {
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
-              R"(OpCapability DerivativeControl
-)");
-  }
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-
-  auto expected = utils::ReplaceAll(R"(%11 = OpLoad %3 %1
-%10 = ${op} %3 %11
-OpReturn
-)",
-                                    "${op}", param.op);
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    BuiltinDeriveTest,
-    testing::Values(BuiltinData{"dpdx", "OpDPdx"},
-                    BuiltinData{"dpdxFine", "OpDPdxFine"},
-                    BuiltinData{"dpdxCoarse", "OpDPdxCoarse"},
-                    BuiltinData{"dpdy", "OpDPdy"},
-                    BuiltinData{"dpdyFine", "OpDPdyFine"},
-                    BuiltinData{"dpdyCoarse", "OpDPdyCoarse"},
-                    BuiltinData{"fwidth", "OpFwidth"},
-                    BuiltinData{"fwidthFine", "OpFwidthFine"},
-                    BuiltinData{"fwidthCoarse", "OpFwidthCoarse"}));
-
-TEST_F(BuiltinBuilderTest, Call_Select) {
-  auto* v3 = Global("v3", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* bool_v3 =
-      Global("bool_v3", ty.vec3<bool>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("select", "v3", "v3", "bool_v3");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v3)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(bool_v3)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%9 = OpTypeBool
-%8 = OpTypeVector %9 3
-%7 = OpTypePointer Private %8
-%10 = OpConstantNull %8
-%6 = OpVariable %7 Private %10
-%12 = OpTypeVoid
-%11 = OpTypeFunction %12
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%16 = OpLoad %8 %6
-%17 = OpLoad %3 %1
-%18 = OpLoad %3 %1
-%15 = OpSelect %3 %16 %17 %18
-OpReturn
-)");
-}
-
-// This tests that we do not push OpTypeSampledImage and float_0 type twice.
-TEST_F(BuiltinBuilderTest, Call_TextureSampleCompare_Twice) {
-  auto* s = ty.sampler(ast::SamplerKind::kComparisonSampler);
-  auto* t = ty.depth_texture(ast::TextureDimension::k2d);
-
-  auto* tex = Global("texture", t,
-                     ast::AttributeList{
-                         create<ast::BindingAttribute>(0),
-                         create<ast::GroupAttribute>(0),
-                     });
-
-  auto* sampler = Global("sampler", s,
-                         ast::AttributeList{
-                             create<ast::BindingAttribute>(1),
-                             create<ast::GroupAttribute>(0),
-                         });
-
-  auto* expr1 = Call("textureSampleCompare", "texture", "sampler",
-                     vec2<f32>(1.0f, 2.0f), 2.0f);
-  auto* expr2 = Call("textureSampleCompare", "texture", "sampler",
-                     vec2<f32>(1.0f, 2.0f), 2.0f);
-
-  Func("f1", {}, ty.void_(), {CallStmt(expr1)}, {});
-  Func("f2", {}, ty.void_(), {CallStmt(expr2)}, {});
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(tex)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
-
-  EXPECT_EQ(b.GenerateExpression(expr1), 8u) << b.error();
-  EXPECT_EQ(b.GenerateExpression(expr2), 17u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 2
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstantComposite %13 %14 %15
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %15
-%18 = OpLoad %7 %5
-%19 = OpLoad %3 %1
-%20 = OpSampledImage %11 %19 %18
-%17 = OpImageSampleDrefImplicitLod %4 %20 %16 %15
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_GLSLMethod_WithLoad) {
-  auto* var = Global("ident", ty.f32(), ast::StorageClass::kPrivate);
-  auto* expr = Call("round", "ident");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%10 = OpExtInstImport "GLSL.std.450"
-OpName %1 "ident"
-OpName %7 "a_func"
-%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%7 = OpFunction %6 None %5
-%8 = OpLabel
-%11 = OpLoad %3 %1
-%9 = OpExtInst %3 %10 RoundEven %11
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using Builtin_Builtin_SingleParam_Float_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_SingleParam_Float_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1.0f);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_SingleParam_Float_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_SingleParam_Float_Test,
-                         testing::Values(BuiltinData{"abs", "FAbs"},
-                                         BuiltinData{"acos", "Acos"},
-                                         BuiltinData{"asin", "Asin"},
-                                         BuiltinData{"atan", "Atan"},
-                                         BuiltinData{"ceil", "Ceil"},
-                                         BuiltinData{"cos", "Cos"},
-                                         BuiltinData{"cosh", "Cosh"},
-                                         BuiltinData{"degrees", "Degrees"},
-                                         BuiltinData{"exp", "Exp"},
-                                         BuiltinData{"exp2", "Exp2"},
-                                         BuiltinData{"floor", "Floor"},
-                                         BuiltinData{"fract", "Fract"},
-                                         BuiltinData{"inverseSqrt",
-                                                     "InverseSqrt"},
-                                         BuiltinData{"log", "Log"},
-                                         BuiltinData{"log2", "Log2"},
-                                         BuiltinData{"radians", "Radians"},
-                                         BuiltinData{"round", "RoundEven"},
-                                         BuiltinData{"sign", "FSign"},
-                                         BuiltinData{"sin", "Sin"},
-                                         BuiltinData{"sinh", "Sinh"},
-                                         BuiltinData{"sqrt", "Sqrt"},
-                                         BuiltinData{"tan", "Tan"},
-                                         BuiltinData{"tanh", "Tanh"},
-                                         BuiltinData{"trunc", "Trunc"}));
-
-TEST_F(BuiltinBuilderTest, Call_Length_Scalar) {
-  auto* expr = Call("length", 1.0f);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 Length %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Length_Vector) {
-  auto* expr = Call("length", vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpTypeVector %6 2
-%9 = OpConstant %6 1
-%10 = OpConstantComposite %8 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 Length %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Normalize) {
-  auto* expr = Call("normalize", vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 Normalize %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using Builtin_Builtin_DualParam_Float_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_DualParam_Float_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1.0f, 1.0f);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_DualParam_Float_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_DualParam_Float_Test,
-                         testing::Values(BuiltinData{"atan2", "Atan2"},
-                                         BuiltinData{"max", "NMax"},
-                                         BuiltinData{"min", "NMin"},
-                                         BuiltinData{"pow", "Pow"},
-                                         BuiltinData{"step", "Step"}));
-
-TEST_F(BuiltinBuilderTest, Call_Reflect_Vector) {
-  auto* expr = Call("reflect", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 Reflect %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Distance_Scalar) {
-  auto* expr = Call("distance", 1.0f, 1.0f);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 Distance %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Distance_Vector) {
-  auto* expr = Call("distance", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpTypeVector %6 2
-%9 = OpConstant %6 1
-%10 = OpConstantComposite %8 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 Distance %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Cross) {
-  auto* expr =
-      Call("cross", vec3<f32>(1.0f, 1.0f, 1.0f), vec3<f32>(1.0f, 1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 3
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 Cross %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using Builtin_Builtin_ThreeParam_Float_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1.0f, 1.0f, 1.0f);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeFloat 32
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
-                    vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_ThreeParam_Float_Test,
-                         testing::Values(BuiltinData{"clamp", "NClamp"},
-                                         BuiltinData{"fma", "Fma"},
-                                         BuiltinData{"mix", "FMix"},
-
-                                         BuiltinData{"smoothStep",
-                                                     "SmoothStep"}));
-
-TEST_F(BuiltinBuilderTest, Call_FaceForward_Vector) {
-  auto* expr = Call("faceForward", vec2<f32>(1.0f, 1.0f), vec2<f32>(1.0f, 1.0f),
-                    vec2<f32>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 FaceForward %10 %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using Builtin_Builtin_SingleParam_Sint_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_SingleParam_Sint_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 1
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_SingleParam_Sint_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<i32>(1, 1));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_SingleParam_Sint_Test,
-                         testing::Values(BuiltinData{"abs", "SAbs"}));
-
-// Calling abs() on an unsigned integer scalar / vector is a no-op.
-using Builtin_Builtin_Abs_Uint_Test = BuiltinBuilderTest;
-TEST_F(Builtin_Builtin_Abs_Uint_Test, Call_Scalar) {
-  auto* expr = Call("abs", 1u);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(Builtin_Builtin_Abs_Uint_Test, Call_Vector) {
-  auto* expr = Call("abs", vec2<u32>(1u, 1u));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 0
-%6 = OpTypeVector %7 2
-%8 = OpConstant %7 1
-%9 = OpConstantComposite %6 %8 %8
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-using Builtin_Builtin_DualParam_SInt_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_DualParam_SInt_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1, 1);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 1
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_DualParam_SInt_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<i32>(1, 1), vec2<i32>(1, 1));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_DualParam_SInt_Test,
-                         testing::Values(BuiltinData{"max", "SMax"},
-                                         BuiltinData{"min", "SMin"}));
-
-using Builtin_Builtin_DualParam_UInt_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_DualParam_UInt_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1u, 1u);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_DualParam_UInt_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, vec2<u32>(1u, 1u), vec2<u32>(1u, 1u));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 0
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_DualParam_UInt_Test,
-                         testing::Values(BuiltinData{"max", "UMax"},
-                                         BuiltinData{"min", "UMin"}));
-
-using Builtin_Builtin_ThreeParam_Sint_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1, 1, 1);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 1
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr =
-      Call(param.name, vec2<i32>(1, 1), vec2<i32>(1, 1), vec2<i32>(1, 1));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_ThreeParam_Sint_Test,
-                         testing::Values(BuiltinData{"clamp", "SClamp"}));
-
-using Builtin_Builtin_ThreeParam_Uint_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Scalar) {
-  auto param = GetParam();
-  auto* expr = Call(param.name, 1u, 1u, 1u);
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%8 = OpConstant %6 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                R"( %8 %8 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Vector) {
-  auto param = GetParam();
-  auto* expr =
-      Call(param.name, vec2<u32>(1u, 1u), vec2<u32>(1u, 1u), vec2<u32>(1u, 1u));
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeInt 32 0
-%6 = OpTypeVector %7 2
-%9 = OpConstant %7 1
-%10 = OpConstantComposite %6 %9 %9
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                R"( %10 %10 %10
-OpReturn
-OpFunctionEnd
-)");
-}
-INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
-                         Builtin_Builtin_ThreeParam_Uint_Test,
-                         testing::Values(BuiltinData{"clamp", "UClamp"}));
-
-TEST_F(BuiltinBuilderTest, Call_Modf) {
-  auto* expr = Call("modf", vec2<f32>(1.0f, 2.0f));
-  Func("a_func", {}, ty.void_(), {CallStmt(expr)},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-  auto got = DumpBuilder(b);
-  auto* expect = R"(OpCapability Shader
-%9 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %3 "a_func"
-OpExecutionMode %3 OriginUpperLeft
-OpName %3 "a_func"
-OpName %6 "__modf_result_vec2"
-OpMemberName %6 0 "fract"
-OpMemberName %6 1 "whole"
-OpMemberDecorate %6 0 Offset 0
-OpMemberDecorate %6 1 Offset 8
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%8 = OpTypeFloat 32
-%7 = OpTypeVector %8 2
-%6 = OpTypeStruct %7 %7
-%10 = OpConstant %8 1
-%11 = OpConstant %8 2
-%12 = OpConstantComposite %7 %10 %11
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %9 ModfStruct %12
-OpReturn
-OpFunctionEnd
-)";
-  EXPECT_EQ(expect, got);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_Frexp) {
-  auto* expr = Call("frexp", vec2<f32>(1.0f, 2.0f));
-  Func("a_func", {}, ty.void_(), {CallStmt(expr)},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-  auto got = DumpBuilder(b);
-  auto* expect = R"(OpCapability Shader
-%11 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %3 "a_func"
-OpExecutionMode %3 OriginUpperLeft
-OpName %3 "a_func"
-OpName %6 "__frexp_result_vec2"
-OpMemberName %6 0 "sig"
-OpMemberName %6 1 "exp"
-OpMemberDecorate %6 0 Offset 0
-OpMemberDecorate %6 1 Offset 8
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%8 = OpTypeFloat 32
-%7 = OpTypeVector %8 2
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%6 = OpTypeStruct %7 %9
-%12 = OpConstant %8 1
-%13 = OpConstant %8 2
-%14 = OpConstantComposite %7 %12 %13
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %11 FrexpStruct %14
-OpReturn
-OpFunctionEnd
-)";
-  EXPECT_EQ(expect, got);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_Determinant) {
-  auto* var = Global("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("determinant", "var");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(%12 = OpExtInstImport "GLSL.std.450"
-OpName %1 "var"
-OpName %9 "a_func"
-%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Private %3
-%6 = OpConstantNull %3
-%1 = OpVariable %2 Private %6
-%8 = OpTypeVoid
-%7 = OpTypeFunction %8
-%9 = OpFunction %8 None %7
-%10 = OpLabel
-%13 = OpLoad %3 %1
-%11 = OpExtInst %5 %12 Determinant %13
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_Transpose) {
-  auto* var = Global("var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
-  auto* expr = Call("transpose", "var");
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Assign(Phony(), expr),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "var"
-OpName %9 "a_func"
-%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 2
-%2 = OpTypePointer Private %3
-%6 = OpConstantNull %3
-%1 = OpVariable %2 Private %6
-%8 = OpTypeVoid
-%7 = OpTypeFunction %8
-%13 = OpTypeVector %5 2
-%12 = OpTypeMatrix %13 3
-%9 = OpFunction %8 None %7
-%10 = OpLabel
-%14 = OpLoad %3 %1
-%11 = OpTranspose %12 %14
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuiltinBuilderTest, Call_ArrayLength) {
-  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-  auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           CallStmt(expr),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%5 = OpTypeFloat 32
-%4 = OpTypeRuntimeArray %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpTypeInt 32 0
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = Structure("my_struct",
-                      {
-                          Member("z", ty.f32()),
-                          Member(4, "a", ty.array<f32>(4)),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-  auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           CallStmt(expr),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%4 = OpTypeFloat 32
-%5 = OpTypeRuntimeArray %4
-%3 = OpTypeStruct %4 %5
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpTypeInt 32 0
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 1
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_ArrayLength_ViaLets) {
-  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  auto* p = Const("p", nullptr, AddressOf("b"));
-  auto* p2 = Const("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
-  auto* expr = Call("arrayLength", p2);
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(p),
-           Decl(p2),
-           CallStmt(expr),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%5 = OpTypeFloat 32
-%4 = OpTypeRuntimeArray %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpTypeInt 32 0
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_ArrayLength_ViaLets_WithPtrNoise) {
-  // [[block]] struct my_struct {
-  //   a : @stride(4) array<f32>;
-  // };
-  // @binding(1) @group(2) var<storage, read> b : my_struct;
-  //
-  // fn a_func() {
-  //   let p = &*&b;
-  //   let p2 = &*p;
-  //   let p3 = &((*p).a);
-  //   arrayLength(&*p3);
-  // }
-  auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  auto* p = Const("p", nullptr, AddressOf(Deref(AddressOf("b"))));
-  auto* p2 = Const("p2", nullptr, AddressOf(Deref(p)));
-  auto* p3 = Const("p3", nullptr, AddressOf(MemberAccessor(Deref(p2), "a")));
-  auto* expr = Call("arrayLength", AddressOf(Deref(p3)));
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(p),
-           Decl(p2),
-           Decl(p3),
-           CallStmt(expr),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%5 = OpTypeFloat 32
-%4 = OpTypeRuntimeArray %5
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpTypeInt 32 0
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_AtomicLoad) {
-  // [[block]] struct S {
-  //   u : atomic<u32>;
-  //   i : atomic<i32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   let u : u32 = atomicLoad(&b.u);
-  //   let i : i32 = atomicLoad(&b.i);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("u", ty.atomic<u32>()),
-                          Member("i", ty.atomic<i32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Const("u", ty.u32(),
-                      Call("atomicLoad", AddressOf(MemberAccessor("b", "u"))))),
-           Decl(Const("i", ty.i32(),
-                      Call("atomicLoad", AddressOf(MemberAccessor("b", "i"))))),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%4 = OpTypeInt 32 0
-%5 = OpTypeInt 32 1
-%3 = OpTypeStruct %4 %5
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpConstant %4 1
-%12 = OpConstant %4 0
-%14 = OpTypePointer StorageBuffer %4
-%18 = OpTypePointer StorageBuffer %5
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%15 = OpAccessChain %14 %1 %12
-%10 = OpAtomicLoad %4 %15 %11 %12
-%19 = OpAccessChain %18 %1 %11
-%16 = OpAtomicLoad %5 %19 %11 %12
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_AtomicStore) {
-  // [[block]] struct S {
-  //   u : atomic<u32>;
-  //   i : atomic<i32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   var u = 1u;
-  //   var i = 2;
-  //   atomicStore(&b.u, u);
-  //   atomicStore(&b.i, i);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("u", ty.atomic<u32>()),
-                          Member("i", ty.atomic<i32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("u", nullptr, Expr(1u))),
-           Decl(Var("i", nullptr, Expr(2))),
-           CallStmt(
-               Call("atomicStore", AddressOf(MemberAccessor("b", "u")), "u")),
-           CallStmt(
-               Call("atomicStore", AddressOf(MemberAccessor("b", "i")), "i")),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%4 = OpTypeInt 32 0
-%5 = OpTypeInt 32 1
-%3 = OpTypeStruct %4 %5
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%10 = OpConstant %4 1
-%12 = OpTypePointer Function %4
-%13 = OpConstantNull %4
-%14 = OpConstant %5 2
-%16 = OpTypePointer Function %5
-%17 = OpConstantNull %5
-%19 = OpConstant %4 0
-%21 = OpTypePointer StorageBuffer %4
-%26 = OpTypePointer StorageBuffer %5
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(OpStore %11 %10
-OpStore %15 %14
-%22 = OpAccessChain %21 %1 %19
-%23 = OpLoad %4 %11
-OpAtomicStore %22 %10 %19 %23
-%27 = OpAccessChain %26 %1 %10
-%28 = OpLoad %5 %15
-OpAtomicStore %27 %10 %19 %28
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-using Builtin_Builtin_AtomicRMW_i32 = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_AtomicRMW_i32, Test) {
-  // [[block]] struct S {
-  //   v : atomic<i32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   var v = 10;
-  //   let x : i32 = atomicOP(&b.v, v);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("v", ty.atomic<i32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("v", nullptr, Expr(10))),
-           Decl(Const("x", ty.i32(),
-                      Call(GetParam().name, AddressOf(MemberAccessor("b", "v")),
-                           "v"))),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  std::string expected_types = R"(%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%9 = OpConstant %4 10
-%11 = OpTypePointer Function %4
-%12 = OpConstantNull %4
-%14 = OpTypeInt 32 0
-%15 = OpConstant %14 1
-%16 = OpConstant %14 0
-%18 = OpTypePointer StorageBuffer %4
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  std::string expected_instructions = R"(OpStore %10 %9
-%19 = OpAccessChain %18 %1 %16
-%20 = OpLoad %4 %10
-)";
-  expected_instructions += "%13 = " + GetParam().op + " %4 %19 %15 %16 %20\n";
-  expected_instructions += "OpReturn\n";
-
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    Builtin_Builtin_AtomicRMW_i32,
-    testing::Values(BuiltinData{"atomicAdd", "OpAtomicIAdd"},
-                    BuiltinData{"atomicMax", "OpAtomicSMax"},
-                    BuiltinData{"atomicMin", "OpAtomicSMin"},
-                    BuiltinData{"atomicAnd", "OpAtomicAnd"},
-                    BuiltinData{"atomicOr", "OpAtomicOr"},
-                    BuiltinData{"atomicXor", "OpAtomicXor"}));
-
-using Builtin_Builtin_AtomicRMW_u32 = BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_AtomicRMW_u32, Test) {
-  // [[block]] struct S {
-  //   v : atomic<u32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   var v = 10u;
-  //   let x : u32 = atomicOP(&b.v, v);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("v", ty.atomic<u32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("v", nullptr, Expr(10u))),
-           Decl(Const("x", ty.u32(),
-                      Call(GetParam().name, AddressOf(MemberAccessor("b", "v")),
-                           "v"))),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  std::string expected_types = R"(%4 = OpTypeInt 32 0
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%9 = OpConstant %4 10
-%11 = OpTypePointer Function %4
-%12 = OpConstantNull %4
-%14 = OpConstant %4 1
-%15 = OpConstant %4 0
-%17 = OpTypePointer StorageBuffer %4
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  std::string expected_instructions = R"(OpStore %10 %9
-%18 = OpAccessChain %17 %1 %15
-%19 = OpLoad %4 %10
-)";
-  expected_instructions += "%13 = " + GetParam().op + " %4 %18 %14 %15 %19\n";
-  expected_instructions += "OpReturn\n";
-
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    Builtin_Builtin_AtomicRMW_u32,
-    testing::Values(BuiltinData{"atomicAdd", "OpAtomicIAdd"},
-                    BuiltinData{"atomicMax", "OpAtomicUMax"},
-                    BuiltinData{"atomicMin", "OpAtomicUMin"},
-                    BuiltinData{"atomicAnd", "OpAtomicAnd"},
-                    BuiltinData{"atomicOr", "OpAtomicOr"},
-                    BuiltinData{"atomicXor", "OpAtomicXor"}));
-
-TEST_F(BuiltinBuilderTest, Call_AtomicExchange) {
-  // [[block]] struct S {
-  //   u : atomic<u32>;
-  //   i : atomic<i32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   var u = 10u;
-  //   var i = 10;
-  //   let r : u32 = atomicExchange(&b.u, u);
-  //   let s : i32 = atomicExchange(&b.i, i);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("u", ty.atomic<u32>()),
-                          Member("i", ty.atomic<i32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("u", nullptr, Expr(10u))),
-           Decl(Var("i", nullptr, Expr(10))),
-           Decl(Const("r", ty.u32(),
-                      Call("atomicExchange",
-                           AddressOf(MemberAccessor("b", "u")), "u"))),
-           Decl(Const("s", ty.i32(),
-                      Call("atomicExchange",
-                           AddressOf(MemberAccessor("b", "i")), "i"))),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%4 = OpTypeInt 32 0
-%5 = OpTypeInt 32 1
-%3 = OpTypeStruct %4 %5
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%10 = OpConstant %4 10
-%12 = OpTypePointer Function %4
-%13 = OpConstantNull %4
-%14 = OpConstant %5 10
-%16 = OpTypePointer Function %5
-%17 = OpConstantNull %5
-%19 = OpConstant %4 1
-%20 = OpConstant %4 0
-%22 = OpTypePointer StorageBuffer %4
-%27 = OpTypePointer StorageBuffer %5
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(OpStore %11 %10
-OpStore %15 %14
-%23 = OpAccessChain %22 %1 %20
-%24 = OpLoad %4 %11
-%18 = OpAtomicExchange %4 %23 %19 %20 %24
-%28 = OpAccessChain %27 %1 %19
-%29 = OpLoad %5 %15
-%25 = OpAtomicExchange %5 %28 %19 %20 %29
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_AtomicCompareExchangeWeak) {
-  // [[block]] struct S {
-  //   u : atomic<u32>;
-  //   i : atomic<i32>;
-  // }
-  //
-  // @binding(1) @group(2) var<storage, read_write> b : S;
-  //
-  // fn a_func() {
-  //   let u : vec2<u32> = atomicCompareExchangeWeak(&b.u, 10u);
-  //   let i : vec2<i32> = atomicCompareExchangeWeak(&b.i, 10);
-  // }
-  auto* s = Structure("S",
-                      {
-                          Member("u", ty.atomic<u32>()),
-                          Member("i", ty.atomic<i32>()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Decl(Const("u", ty.vec2<u32>(),
-                      Call("atomicCompareExchangeWeak",
-                           AddressOf(MemberAccessor("b", "u")), 10u, 20u))),
-           Decl(Const("i", ty.vec2<i32>(),
-                      Call("atomicCompareExchangeWeak",
-                           AddressOf(MemberAccessor("b", "i")), 10, 20))),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%4 = OpTypeInt 32 0
-%5 = OpTypeInt 32 1
-%3 = OpTypeStruct %4 %5
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%11 = OpTypeVector %4 2
-%12 = OpConstant %4 1
-%13 = OpConstant %4 0
-%15 = OpTypePointer StorageBuffer %4
-%17 = OpConstant %4 20
-%18 = OpConstant %4 10
-%19 = OpTypeBool
-%24 = OpTypeVector %5 2
-%26 = OpTypePointer StorageBuffer %5
-%28 = OpConstant %5 20
-%29 = OpConstant %5 10
-%32 = OpConstant %5 0
-%33 = OpConstant %5 1
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(%16 = OpAccessChain %15 %1 %13
-%20 = OpAtomicCompareExchange %4 %16 %12 %13 %13 %17 %18
-%21 = OpIEqual %19 %20 %17
-%22 = OpSelect %4 %21 %12 %13
-%10 = OpCompositeConstruct %11 %20 %22
-%27 = OpAccessChain %26 %1 %12
-%30 = OpAtomicCompareExchange %5 %27 %12 %13 %13 %28 %29
-%31 = OpIEqual %19 %30 %28
-%34 = OpSelect %5 %31 %33 %32
-%23 = OpCompositeConstruct %24 %30 %34
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-using Builtin_Builtin_DataPacking_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_DataPacking_Test, Binary) {
-  auto param = GetParam();
-
-  bool pack4 = param.name == "pack4x8snorm" || param.name == "pack4x8unorm";
-  auto* call = pack4 ? Call(param.name, vec4<float>(1.0f, 1.0f, 1.0f, 1.0f))
-                     : Call(param.name, vec2<float>(1.0f, 1.0f));
-  auto* func = Func("a_func", {}, ty.void_(), {CallStmt(call)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  if (pack4) {
-    EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%9 = OpTypeFloat 32
-%8 = OpTypeVector %9 4
-%10 = OpConstant %9 1
-%11 = OpConstantComposite %8 %10 %10 %10 %10
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                  R"( %11
-OpReturn
-OpFunctionEnd
-)");
-  } else {
-    EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%9 = OpTypeFloat 32
-%8 = OpTypeVector %9 2
-%10 = OpConstant %9 1
-%11 = OpConstantComposite %8 %10 %10
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %7 )" + param.op +
-                                  R"( %11
-OpReturn
-OpFunctionEnd
-)");
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    Builtin_Builtin_DataPacking_Test,
-    testing::Values(BuiltinData{"pack4x8snorm", "PackSnorm4x8"},
-                    BuiltinData{"pack4x8unorm", "PackUnorm4x8"},
-                    BuiltinData{"pack2x16snorm", "PackSnorm2x16"},
-                    BuiltinData{"pack2x16unorm", "PackUnorm2x16"},
-                    BuiltinData{"pack2x16float", "PackHalf2x16"}));
-
-using Builtin_Builtin_DataUnpacking_Test =
-    BuiltinBuilderTestWithParam<BuiltinData>;
-TEST_P(Builtin_Builtin_DataUnpacking_Test, Binary) {
-  auto param = GetParam();
-
-  bool pack4 = param.name == "unpack4x8snorm" || param.name == "unpack4x8unorm";
-  auto* func = Func("a_func", {}, ty.void_(), {CallStmt(Call(param.name, 1u))});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  if (pack4) {
-    EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 4
-%9 = OpTypeInt 32 0
-%10 = OpConstant %9 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                  R"( %10
-OpReturn
-OpFunctionEnd
-)");
-  } else {
-    EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
-OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = OpTypeFloat 32
-%6 = OpTypeVector %7 2
-%9 = OpTypeInt 32 0
-%10 = OpConstant %9 1
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%5 = OpExtInst %6 %8 )" + param.op +
-                                  R"( %10
-OpReturn
-OpFunctionEnd
-)");
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinBuilderTest,
-    Builtin_Builtin_DataUnpacking_Test,
-    testing::Values(BuiltinData{"unpack4x8snorm", "UnpackSnorm4x8"},
-                    BuiltinData{"unpack4x8unorm", "UnpackUnorm4x8"},
-                    BuiltinData{"unpack2x16snorm", "UnpackSnorm2x16"},
-                    BuiltinData{"unpack2x16unorm", "UnpackUnorm2x16"},
-                    BuiltinData{"unpack2x16float", "UnpackHalf2x16"}));
-
-TEST_F(BuiltinBuilderTest, Call_WorkgroupBarrier) {
-  Func("f", {}, ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("workgroupBarrier")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 2
-%8 = OpConstant %6 264
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-TEST_F(BuiltinBuilderTest, Call_StorageBarrier) {
-  Func("f", {}, ty.void_(),
-       ast::StatementList{
-           CallStmt(Call("storageBarrier")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  ASSERT_EQ(b.functions().size(), 1u);
-
-  auto* expected_types = R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 2
-%8 = OpConstant %6 72
-)";
-  auto got_types = DumpInstructions(b.types());
-  EXPECT_EQ(expected_types, got_types);
-
-  auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
-OpReturn
-)";
-  auto got_instructions = DumpInstructions(b.functions()[0].instructions());
-  EXPECT_EQ(expected_instructions, got_instructions);
-
-  Validate(b);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_builtin_texture_test.cc b/src/writer/spirv/builder_builtin_texture_test.cc
deleted file mode 100644
index d26f288..0000000
--- a/src/writer/spirv/builder_builtin_texture_test.cc
+++ /dev/null
@@ -1,3755 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/ast/builtin_texture_helper_test.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-struct expected_texture_overload_spirv {
-  std::string types;
-  std::string instructions;
-  std::string capabilities;
-};
-
-expected_texture_overload_spirv expected_texture_overload(
-    ast::builtin::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpConstant %9 0
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %10 %11
-)",
-          R"(
-OpCapability Sampled1D
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions2d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 0
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions2dLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 1
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions2dArray:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 0
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 1
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions3d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 3
-%12 = OpConstant %10 0
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensions3dLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 3
-%12 = OpConstant %10 1
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsCube:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 0
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsCubeLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 1
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsCubeArray:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 0
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 1
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySize %9 %11
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepth2d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 0
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 1
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 0
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 1
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepthCube:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 0
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpConstant %10 1
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySizeLod %9 %11 %12
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 0
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-%14 = OpConstant %10 1
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySizeLod %12 %13 %14
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsDepthMultisampled2d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySize %9 %11
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 1D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQuerySize %9 %10
-)",
-          R"(
-OpCapability Image1D
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySize %9 %11
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 2
-%12 = OpTypeVector %10 3
-)",
-          R"(
-%13 = OpLoad %3 %1
-%11 = OpImageQuerySize %12 %13
-%8 = OpVectorShuffle %9 %11 %11 0 1
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeInt 32 1
-%9 = OpTypeVector %10 3
-)",
-          R"(
-%11 = OpLoad %3 %1
-%8 = OpImageQuerySize %9 %11
-)",
-          R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kGather2dF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %17 %19
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGather2dOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 0
-%20 = OpTypeVector %18 2
-%21 = OpConstant %18 3
-%22 = OpConstant %18 4
-%23 = OpConstantComposite %20 %21 %22
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %17 %19 ConstOffset %23
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGather2dArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGather2dArrayOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %18 0
-%22 = OpTypeVector %18 2
-%23 = OpConstant %18 4
-%24 = OpConstant %18 5
-%25 = OpConstantComposite %22 %23 %24
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21 ConstOffset %25
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCubeF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %18 %20
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCubeArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21
-)",
-              R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kGatherDepth2dF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %17 %19
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherDepth2dOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 0
-%20 = OpTypeVector %18 2
-%21 = OpConstant %18 3
-%22 = OpConstant %18 4
-%23 = OpConstantComposite %20 %21 %22
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %17 %19 ConstOffset %23
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherDepth2dArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherDepth2dArrayOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %18 0
-%22 = OpTypeVector %18 2
-%23 = OpConstant %18 4
-%24 = OpConstant %18 5
-%25 = OpConstantComposite %22 %23 %24
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21 ConstOffset %25
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherDepthCubeF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageGather %9 %13 %18 %20
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherDepthCubeArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %18 0
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageGather %9 %13 %20 %21
-)",
-              R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kGatherCompareDepth2dF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageDrefGather %9 %13 %17 %18
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCompareDepth2dOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 2
-%21 = OpConstant %20 4
-%22 = OpConstant %20 5
-%23 = OpConstantComposite %19 %21 %22
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageDrefGather %9 %13 %17 %18 ConstOffset %23
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCompareDepth2dArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageDrefGather %9 %13 %20 %21
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-%22 = OpTypeVector %18 2
-%23 = OpConstant %18 5
-%24 = OpConstant %18 6
-%25 = OpConstantComposite %22 %23 %24
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageDrefGather %9 %13 %20 %21 ConstOffset %25
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCompareDepthCubeF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageDrefGather %9 %13 %18 %19
-)",
-              R"(
-)"};
-    case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %4 5
-)",
-              R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageDrefGather %9 %13 %20 %21
-)",
-              R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kNumLayers2dArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpTypeVector %9 3
-%13 = OpConstant %9 0
-)",
-              R"(
-%12 = OpLoad %3 %1
-%10 = OpImageQuerySizeLod %11 %12 %13
-%8 = OpCompositeExtract %9 %10 2
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLayersCubeArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpTypeVector %9 3
-%13 = OpConstant %9 0
-)",
-              R"(
-%12 = OpLoad %3 %1
-%10 = OpImageQuerySizeLod %11 %12 %13
-%8 = OpCompositeExtract %9 %10 2
-)",
-              R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpTypeVector %9 3
-%13 = OpConstant %9 0
-)",
-              R"(
-%12 = OpLoad %3 %1
-%10 = OpImageQuerySizeLod %11 %12 %13
-%8 = OpCompositeExtract %9 %10 2
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpTypeVector %9 3
-%13 = OpConstant %9 0
-)",
-              R"(
-%12 = OpLoad %3 %1
-%10 = OpImageQuerySizeLod %11 %12 %13
-%8 = OpCompositeExtract %9 %10 2
-)",
-              R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-%11 = OpTypeVector %9 3
-)",
-              R"(
-%12 = OpLoad %3 %1
-%10 = OpImageQuerySize %11 %12
-%8 = OpCompositeExtract %9 %10 2
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevels2d:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevels2dArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevels3d:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsCube:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsCubeArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsDepth2d:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsDepthCube:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQueryLevels %9 %10
-)",
-              R"(
-OpCapability SampledCubeArray
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQuerySamples %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kNumSamplesDepthMultisampled2d:
-      return {R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeInt 32 1
-)",
-              R"(
-%10 = OpLoad %3 %1
-%8 = OpImageQuerySamples %9 %10
-)",
-              R"(
-OpCapability ImageQuery
-)"};
-    case ValidTextureOverload::kSample1dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %14
-)",
-          R"(
-OpCapability Sampled1D
-)"};
-    case ValidTextureOverload::kSample2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%19 = OpTypeInt 32 1
-%18 = OpTypeVector %19 2
-%20 = OpConstant %19 3
-%21 = OpConstant %19 4
-%22 = OpConstantComposite %18 %20 %21
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %17 ConstOffset %22
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSample2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpTypeVector %18 2
-%22 = OpConstant %18 4
-%23 = OpConstant %18 5
-%24 = OpConstantComposite %21 %22 %23
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20 ConstOffset %24
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSample3dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 3
-%21 = OpConstant %20 4
-%22 = OpConstant %20 5
-%23 = OpConstant %20 6
-%24 = OpConstantComposite %19 %21 %22 %23
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18 ConstOffset %24
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 2
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstantComposite %15 %16 %17
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%9 = OpImageSampleImplicitLod %10 %14 %18
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 2
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstantComposite %15 %16 %17
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 2
-%21 = OpConstant %20 3
-%22 = OpConstant %20 4
-%23 = OpConstantComposite %19 %21 %22
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%9 = OpImageSampleImplicitLod %10 %14 %18 ConstOffset %23
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 3
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %15 %16 %17 %18
-%9 = OpImageSampleImplicitLod %10 %14 %21
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 3
-%22 = OpTypeVector %19 2
-%23 = OpConstant %19 4
-%24 = OpConstant %19 5
-%25 = OpConstantComposite %22 %23 %24
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %15 %16 %17 %18
-%9 = OpImageSampleImplicitLod %10 %14 %21 ConstOffset %25
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstant %4 3
-%19 = OpConstantComposite %15 %16 %17 %18
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%9 = OpImageSampleImplicitLod %10 %14 %19
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 4
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %10 %15 %16 %17 %18
-%9 = OpImageSampleImplicitLod %10 %14 %21
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleBias2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %17 Bias %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 2
-%21 = OpConstant %20 4
-%22 = OpConstant %20 5
-%23 = OpConstantComposite %19 %21 %22
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %17 Bias|ConstOffset %18 %23
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20 Bias %21
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-%22 = OpTypeVector %18 2
-%23 = OpConstant %18 5
-%24 = OpConstant %18 6
-%25 = OpConstantComposite %22 %23 %24
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20 Bias|ConstOffset %21 %25
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBias3dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18 Bias %19
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-%21 = OpTypeInt 32 1
-%20 = OpTypeVector %21 3
-%22 = OpConstant %21 5
-%23 = OpConstant %21 6
-%24 = OpConstant %21 7
-%25 = OpConstantComposite %20 %22 %23 %24
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18 Bias|ConstOffset %19 %25
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleImplicitLod %9 %13 %18 Bias %19
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageSampleImplicitLod %9 %13 %20 Bias %21
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %17 Lod %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 2
-%21 = OpConstant %20 4
-%22 = OpConstant %20 5
-%23 = OpConstantComposite %19 %21 %22
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %17 Lod|ConstOffset %18 %23
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Lod %21
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpConstant %4 4
-%22 = OpTypeVector %18 2
-%23 = OpConstant %18 5
-%24 = OpConstant %18 6
-%25 = OpConstantComposite %22 %23 %24
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Lod|ConstOffset %21 %25
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Lod %19
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-%21 = OpTypeInt 32 1
-%20 = OpTypeVector %21 3
-%22 = OpConstant %21 5
-%23 = OpConstant %21 6
-%24 = OpConstant %21 7
-%25 = OpConstantComposite %20 %22 %23 %24
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Lod|ConstOffset %19 %25
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Lod %19
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %4 5
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Lod %21
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 2
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstantComposite %15 %16 %17
-%20 = OpTypeInt 32 1
-%21 = OpConstant %20 3
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%19 = OpConvertSToF %4 %21
-%9 = OpImageSampleExplicitLod %10 %14 %18 Lod %19
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 2
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstantComposite %15 %16 %17
-%20 = OpTypeInt 32 1
-%21 = OpConstant %20 3
-%22 = OpTypeVector %20 2
-%23 = OpConstant %20 4
-%24 = OpConstant %20 5
-%25 = OpConstantComposite %22 %23 %24
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%19 = OpConvertSToF %4 %21
-%9 = OpImageSampleExplicitLod %10 %14 %18 Lod|ConstOffset %19 %25
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 3
-%23 = OpConstant %19 4
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %15 %16 %17 %18
-%22 = OpConvertSToF %4 %23
-%9 = OpImageSampleExplicitLod %10 %14 %21 Lod %22
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 3
-%23 = OpConstant %19 4
-%24 = OpTypeVector %19 2
-%25 = OpConstant %19 5
-%26 = OpConstant %19 6
-%27 = OpConstantComposite %24 %25 %26
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %15 %16 %17 %18
-%22 = OpConvertSToF %4 %23
-%9 = OpImageSampleExplicitLod %10 %14 %21 Lod|ConstOffset %22 %27
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpTypeVector %4 3
-%16 = OpConstant %4 1
-%17 = OpConstant %4 2
-%18 = OpConstant %4 3
-%19 = OpConstantComposite %15 %16 %17 %18
-%21 = OpTypeInt 32 1
-%22 = OpConstant %21 4
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%20 = OpConvertSToF %4 %22
-%9 = OpImageSampleExplicitLod %10 %14 %19 Lod %20
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeSampledImage %3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%19 = OpTypeInt 32 1
-%20 = OpConstant %19 4
-%23 = OpConstant %19 5
-)",
-          R"(
-%11 = OpLoad %7 %5
-%12 = OpLoad %3 %1
-%14 = OpSampledImage %13 %12 %11
-%18 = OpConvertSToF %4 %20
-%21 = OpCompositeConstruct %10 %15 %16 %17 %18
-%22 = OpConvertSToF %4 %23
-%9 = OpImageSampleExplicitLod %10 %14 %21 Lod %22
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-%19 = OpConstant %4 4
-%20 = OpConstantComposite %14 %18 %19
-%21 = OpConstant %4 5
-%22 = OpConstant %4 6
-%23 = OpConstantComposite %14 %21 %22
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %17 Grad %20 %23
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 2
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstantComposite %14 %15 %16
-%18 = OpConstant %4 3
-%19 = OpConstant %4 4
-%20 = OpConstantComposite %14 %18 %19
-%21 = OpConstant %4 5
-%22 = OpConstant %4 6
-%23 = OpConstantComposite %14 %21 %22
-%25 = OpTypeInt 32 1
-%24 = OpTypeVector %25 2
-%26 = OpConstant %25 7
-%27 = OpConstantComposite %24 %26 %26
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %17 Grad|ConstOffset %20 %23 %27
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpTypeVector %4 2
-%22 = OpConstant %4 4
-%23 = OpConstant %4 5
-%24 = OpConstantComposite %21 %22 %23
-%25 = OpConstant %4 6
-%26 = OpConstant %4 7
-%27 = OpConstantComposite %21 %25 %26
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Grad %24 %27
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 3
-%21 = OpTypeVector %4 2
-%22 = OpConstant %4 4
-%23 = OpConstant %4 5
-%24 = OpConstantComposite %21 %22 %23
-%25 = OpConstant %4 6
-%26 = OpConstant %4 7
-%27 = OpConstantComposite %21 %25 %26
-%28 = OpTypeVector %18 2
-%29 = OpConstant %18 6
-%30 = OpConstant %18 7
-%31 = OpConstantComposite %28 %29 %30
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Grad|ConstOffset %24 %27 %31
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-%20 = OpConstant %4 5
-%21 = OpConstant %4 6
-%22 = OpConstantComposite %14 %19 %20 %21
-%23 = OpConstant %4 7
-%24 = OpConstant %4 8
-%25 = OpConstant %4 9
-%26 = OpConstantComposite %14 %23 %24 %25
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Grad %22 %26
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-%20 = OpConstant %4 5
-%21 = OpConstant %4 6
-%22 = OpConstantComposite %14 %19 %20 %21
-%23 = OpConstant %4 7
-%24 = OpConstant %4 8
-%25 = OpConstant %4 9
-%26 = OpConstantComposite %14 %23 %24 %25
-%28 = OpTypeInt 32 1
-%27 = OpTypeVector %28 3
-%29 = OpConstant %28 0
-%30 = OpConstant %28 1
-%31 = OpConstant %28 2
-%32 = OpConstantComposite %27 %29 %30 %31
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Grad|ConstOffset %22 %26 %32
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpTypeVector %4 3
-%15 = OpConstant %4 1
-%16 = OpConstant %4 2
-%17 = OpConstant %4 3
-%18 = OpConstantComposite %14 %15 %16 %17
-%19 = OpConstant %4 4
-%20 = OpConstant %4 5
-%21 = OpConstant %4 6
-%22 = OpConstantComposite %14 %19 %20 %21
-%23 = OpConstant %4 7
-%24 = OpConstant %4 8
-%25 = OpConstant %4 9
-%26 = OpConstantComposite %14 %23 %24 %25
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%8 = OpImageSampleExplicitLod %9 %13 %18 Grad %22 %26
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeSampledImage %3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpTypeVector %4 3
-%22 = OpConstant %4 5
-%23 = OpConstant %4 6
-%24 = OpConstant %4 7
-%25 = OpConstantComposite %21 %22 %23 %24
-%26 = OpConstant %4 8
-%27 = OpConstant %4 9
-%28 = OpConstant %4 10
-%29 = OpConstantComposite %21 %26 %27 %28
-)",
-          R"(
-%10 = OpLoad %7 %5
-%11 = OpLoad %3 %1
-%13 = OpSampledImage %12 %11 %10
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %9 %14 %15 %16 %17
-%8 = OpImageSampleExplicitLod %9 %13 %20 Grad %25 %29
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 2
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstantComposite %13 %14 %15
-%17 = OpConstant %4 3
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 2
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstantComposite %13 %14 %15
-%17 = OpConstant %4 3
-%19 = OpTypeInt 32 1
-%18 = OpTypeVector %19 2
-%20 = OpConstant %19 4
-%21 = OpConstant %19 5
-%22 = OpConstantComposite %18 %20 %21
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefImplicitLod %4 %12 %16 %17 ConstOffset %22
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%17 = OpTypeInt 32 1
-%18 = OpConstant %17 4
-%20 = OpConstant %4 3
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%16 = OpConvertSToF %4 %18
-%19 = OpCompositeConstruct %13 %14 %15 %16
-%8 = OpImageSampleDrefImplicitLod %4 %12 %19 %20
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%17 = OpTypeInt 32 1
-%18 = OpConstant %17 4
-%20 = OpConstant %4 3
-%21 = OpTypeVector %17 2
-%22 = OpConstant %17 5
-%23 = OpConstant %17 6
-%24 = OpConstantComposite %21 %22 %23
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%16 = OpConvertSToF %4 %18
-%19 = OpCompositeConstruct %13 %14 %15 %16
-%8 = OpImageSampleDrefImplicitLod %4 %12 %19 %20 ConstOffset %24
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%17 = OpConstantComposite %13 %14 %15 %16
-%18 = OpConstant %4 4
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefImplicitLod %4 %12 %17 %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 4
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %4 5
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %13 %14 %15 %16 %17
-%8 = OpImageSampleDrefImplicitLod %4 %12 %20 %21
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepth2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 2
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstantComposite %13 %14 %15
-%17 = OpConstant %4 3
-%18 = OpConstant %4 0
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefExplicitLod %4 %12 %16 %17 Lod %18
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 2
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstantComposite %13 %14 %15
-%17 = OpConstant %4 3
-%18 = OpConstant %4 0
-%20 = OpTypeInt 32 1
-%19 = OpTypeVector %20 2
-%21 = OpConstant %20 4
-%22 = OpConstant %20 5
-%23 = OpConstantComposite %19 %21 %22
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefExplicitLod %4 %12 %16 %17 Lod|ConstOffset %18 %23
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%17 = OpTypeInt 32 1
-%18 = OpConstant %17 4
-%20 = OpConstant %4 3
-%21 = OpConstant %4 0
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%16 = OpConvertSToF %4 %18
-%19 = OpCompositeConstruct %13 %14 %15 %16
-%8 = OpImageSampleDrefExplicitLod %4 %12 %19 %20 Lod %21
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%17 = OpTypeInt 32 1
-%18 = OpConstant %17 4
-%20 = OpConstant %4 3
-%21 = OpConstant %4 0
-%22 = OpTypeVector %17 2
-%23 = OpConstant %17 5
-%24 = OpConstant %17 6
-%25 = OpConstantComposite %22 %23 %24
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%16 = OpConvertSToF %4 %18
-%19 = OpCompositeConstruct %13 %14 %15 %16
-%8 = OpImageSampleDrefExplicitLod %4 %12 %19 %20 Lod|ConstOffset %21 %25
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 3
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%17 = OpConstantComposite %13 %14 %15 %16
-%18 = OpConstant %4 4
-%19 = OpConstant %4 0
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%8 = OpImageSampleDrefExplicitLod %4 %12 %17 %18 Lod %19
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 Cube 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%11 = OpTypeSampledImage %3
-%13 = OpTypeVector %4 4
-%14 = OpConstant %4 1
-%15 = OpConstant %4 2
-%16 = OpConstant %4 3
-%18 = OpTypeInt 32 1
-%19 = OpConstant %18 4
-%21 = OpConstant %4 5
-%22 = OpConstant %4 0
-)",
-          R"(
-%9 = OpLoad %7 %5
-%10 = OpLoad %3 %1
-%12 = OpSampledImage %11 %10 %9
-%17 = OpConvertSToF %4 %19
-%20 = OpCompositeConstruct %13 %14 %15 %16 %17
-%8 = OpImageSampleDrefExplicitLod %4 %12 %20 %21 Lod %22
-)",
-          R"(
-OpCapability SampledCubeArray
-)"};
-    case ValidTextureOverload::kLoad1dLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeInt 32 1
-%12 = OpConstant %11 1
-%13 = OpConstant %11 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %12 Lod %13
-)",
-          R"(
-OpCapability Sampled1D
-)"};
-    case ValidTextureOverload::kLoad1dLevelU32:
-      return {
-          R"(
-%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeInt 32 1
-%12 = OpConstant %11 1
-%13 = OpConstant %11 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %12 Lod %13
-)",
-          R"(
-OpCapability Sampled1D
-)"};
-    case ValidTextureOverload::kLoad1dLevelI32:
-      return {
-          R"(
-%4 = OpTypeInt 32 1
-%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpConstant %4 1
-%12 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %11 Lod %12
-)",
-          R"(
-OpCapability Sampled1D
-)"};
-    case ValidTextureOverload::kLoad2dLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 2
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstantComposite %11 %13 %14
-%16 = OpConstant %12 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Lod %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad2dLevelU32:
-      return {
-          R"(
-%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 2
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstantComposite %11 %13 %14
-%16 = OpConstant %12 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Lod %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return {
-          R"(
-%4 = OpTypeInt 32 1
-%3 = OpTypeImage %4 2D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeVector %4 2
-%12 = OpConstant %4 1
-%13 = OpConstant %4 2
-%14 = OpConstantComposite %11 %12 %13
-%15 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %14 Lod %15
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpConstant %12 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %16 Lod %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-      return {
-          R"(
-%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpConstant %12 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %16 Lod %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-      return {
-          R"(
-%4 = OpTypeInt 32 1
-%3 = OpTypeImage %4 2D 0 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeVector %4 3
-%12 = OpConstant %4 1
-%13 = OpConstant %4 2
-%14 = OpConstant %4 3
-%15 = OpConstantComposite %11 %12 %13 %14
-%16 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Lod %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad3dLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpConstant %12 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %16 Lod %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad3dLevelU32:
-      return {
-          R"(
-%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpConstant %12 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %16 Lod %17
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoad3dLevelI32:
-      return {
-          R"(
-%4 = OpTypeInt 32 1
-%3 = OpTypeImage %4 3D 0 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeVector %4 3
-%12 = OpConstant %4 1
-%13 = OpConstant %4 2
-%14 = OpConstant %4 3
-%15 = OpConstantComposite %11 %12 %13 %14
-%16 = OpConstant %4 4
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Lod %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 2
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstantComposite %11 %13 %14
-%16 = OpConstant %12 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Sample %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-      return {
-          R"(
-%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 2
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstantComposite %11 %13 %14
-%16 = OpConstant %12 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %15 Sample %16
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return {
-          R"(
-%4 = OpTypeInt 32 1
-%3 = OpTypeImage %4 2D 0 0 1 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVector %4 4
-%11 = OpTypeVector %4 2
-%12 = OpConstant %4 1
-%13 = OpConstant %4 2
-%14 = OpConstantComposite %11 %12 %13
-%15 = OpConstant %4 3
-)",
-          R"(
-%10 = OpLoad %3 %1
-%8 = OpImageFetch %9 %10 %14 Sample %15
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 0 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeInt 32 1
-%12 = OpTypeVector %13 2
-%14 = OpConstant %13 1
-%15 = OpConstant %13 2
-%16 = OpConstantComposite %12 %14 %15
-%17 = OpConstant %13 3
-)",
-          R"(
-%11 = OpLoad %3 %1
-%9 = OpImageFetch %10 %11 %16 Lod %17
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeInt 32 1
-%12 = OpTypeVector %13 3
-%14 = OpConstant %13 1
-%15 = OpConstant %13 2
-%16 = OpConstant %13 3
-%17 = OpConstantComposite %12 %14 %15 %16
-%18 = OpConstant %13 4
-)",
-          R"(
-%11 = OpLoad %3 %1
-%9 = OpImageFetch %10 %11 %17 Lod %18
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kLoadDepthMultisampled2dF32:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 1 1 0 1 Unknown
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%10 = OpTypeVector %4 4
-%13 = OpTypeInt 32 1
-%12 = OpTypeVector %13 3
-%14 = OpConstant %13 1
-%15 = OpConstant %13 2
-%16 = OpConstant %13 3
-%17 = OpConstantComposite %12 %14 %15 %16
-%18 = OpConstant %13 4
-)",
-          R"(
-%11 = OpLoad %3 %1
-%9 = OpImageFetch %10 %11 %17 Sample %18
-%8 = OpCompositeExtract %4 %9 0
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 1D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVoid
-%11 = OpTypeInt 32 1
-%12 = OpConstant %11 1
-%13 = OpTypeVector %4 4
-%14 = OpConstant %4 2
-%15 = OpConstant %4 3
-%16 = OpConstant %4 4
-%17 = OpConstant %4 5
-%18 = OpConstantComposite %13 %14 %15 %16 %17
-)",
-          R"(
-%10 = OpLoad %3 %1
-OpImageWrite %10 %12 %18
-)",
-          R"(
-OpCapability Image1D
-)"};
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVoid
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 2
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstantComposite %11 %13 %14
-%16 = OpTypeVector %4 4
-%17 = OpConstant %4 3
-%18 = OpConstant %4 4
-%19 = OpConstant %4 5
-%20 = OpConstant %4 6
-%21 = OpConstantComposite %16 %17 %18 %19 %20
-)",
-          R"(
-%10 = OpLoad %3 %1
-OpImageWrite %10 %15 %21
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVoid
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpTypeVector %4 4
-%18 = OpConstant %4 4
-%19 = OpConstant %4 5
-%20 = OpConstant %4 6
-%21 = OpConstant %4 7
-%22 = OpConstantComposite %17 %18 %19 %20 %21
-)",
-          R"(
-%10 = OpLoad %3 %1
-OpImageWrite %10 %16 %22
-)",
-          R"(
-)"};
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return {
-          R"(
-%4 = OpTypeFloat 32
-%3 = OpTypeImage %4 3D 0 0 0 2 Rgba32f
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%7 = OpTypeSampler
-%6 = OpTypePointer UniformConstant %7
-%5 = OpVariable %6 UniformConstant
-%9 = OpTypeVoid
-%12 = OpTypeInt 32 1
-%11 = OpTypeVector %12 3
-%13 = OpConstant %12 1
-%14 = OpConstant %12 2
-%15 = OpConstant %12 3
-%16 = OpConstantComposite %11 %13 %14 %15
-%17 = OpTypeVector %4 4
-%18 = OpConstant %4 4
-%19 = OpConstant %4 5
-%20 = OpConstant %4 6
-%21 = OpConstant %4 7
-%22 = OpConstantComposite %17 %18 %19 %20 %21
-)",
-          R"(
-%10 = OpLoad %3 %1
-OpImageWrite %10 %16 %22
-)",
-          R"(
-)"};
-  }
-
-  return {"<unmatched texture overload>", "<unmatched texture overload>",
-          "<unmatched texture overload>"};
-}  // NOLINT - Ignore the length of this function
-
-using BuiltinTextureTest =
-    TestParamHelper<ast::builtin::test::TextureOverloadCase>;
-
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinTextureTest,
-    BuiltinTextureTest,
-    testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
-
-TEST_P(BuiltinTextureTest, Call) {
-  auto param = GetParam();
-
-  auto* texture = param.BuildTextureVariable(this);
-  auto* sampler = param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  auto* stmt = CallStmt(call);
-  Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
-
-  EXPECT_EQ(b.GenerateExpression(call), 8u) << b.error();
-
-  auto expected = expected_texture_overload(param.overload);
-  EXPECT_EQ(expected.types, "\n" + DumpInstructions(b.types()));
-  EXPECT_EQ(expected.instructions,
-            "\n" + DumpInstructions(b.functions()[0].instructions()));
-  EXPECT_EQ(expected.capabilities, "\n" + DumpInstructions(b.capabilities()));
-}
-
-// Check the SPIRV generated passes validation
-TEST_P(BuiltinTextureTest, ValidateSPIRV) {
-  auto param = GetParam();
-
-  param.BuildTextureVariable(this);
-  param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-
-  auto* stmt = CallStmt(call);
-  Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  Validate(b);
-}
-
-TEST_P(BuiltinTextureTest, OutsideFunction_IsError) {
-  auto param = GetParam();
-
-  // The point of this test is to try to generate the texture
-  // builtin call outside a function.
-
-  auto* texture = param.BuildTextureVariable(this);
-  auto* sampler = param.BuildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  auto* stmt = CallStmt(call);
-  Func("func", {}, ty.void_(), {stmt},
-       {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(call), 0u);
-  EXPECT_THAT(b.error(),
-              ::testing::StartsWith(
-                  "Internal error: trying to add SPIR-V instruction "));
-  EXPECT_THAT(b.error(), ::testing::EndsWith(" outside a function"));
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_call_test.cc b/src/writer/spirv/builder_call_test.cc
deleted file mode 100644
index ba9d852..0000000
--- a/src/writer/spirv/builder_call_test.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Expression_Call) {
-  ast::VariableList func_params;
-  func_params.push_back(Param("a", ty.f32()));
-  func_params.push_back(Param("b", ty.f32()));
-
-  auto* a_func = Func("a_func", func_params, ty.f32(), {Return(Add("a", "b"))});
-  auto* func =
-      Func("main", {}, ty.void_(), {Assign(Phony(), Call("a_func", 1.f, 1.f))});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-OpName %4 "a"
-OpName %5 "b"
-OpName %10 "main"
-%2 = OpTypeFloat 32
-%1 = OpTypeFunction %2 %2 %2
-%9 = OpTypeVoid
-%8 = OpTypeFunction %9
-%13 = OpConstant %2 1
-%3 = OpFunction %2 None %1
-%4 = OpFunctionParameter %2
-%5 = OpFunctionParameter %2
-%6 = OpLabel
-%7 = OpFAdd %2 %4 %5
-OpReturnValue %7
-OpFunctionEnd
-%10 = OpFunction %9 None %8
-%11 = OpLabel
-%12 = OpFunctionCall %2 %3 %13 %13
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Statement_Call) {
-  ast::VariableList func_params;
-  func_params.push_back(Param("a", ty.f32()));
-  func_params.push_back(Param("b", ty.f32()));
-
-  auto* a_func = Func("a_func", func_params, ty.f32(), {Return(Add("a", "b"))});
-
-  auto* func =
-      Func("main", {}, ty.void_(), {CallStmt(Call("a_func", 1.f, 1.f))});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-OpName %4 "a"
-OpName %5 "b"
-OpName %10 "main"
-%2 = OpTypeFloat 32
-%1 = OpTypeFunction %2 %2 %2
-%9 = OpTypeVoid
-%8 = OpTypeFunction %9
-%13 = OpConstant %2 1
-%3 = OpFunction %2 None %1
-%4 = OpFunctionParameter %2
-%5 = OpFunctionParameter %2
-%6 = OpLabel
-%7 = OpFAdd %2 %4 %5
-OpReturnValue %7
-OpFunctionEnd
-%10 = OpFunction %9 None %8
-%11 = OpLabel
-%12 = OpFunctionCall %2 %3 %13 %13
-OpReturn
-OpFunctionEnd
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
deleted file mode 100644
index 709cea4..0000000
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ /dev/null
@@ -1,1858 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using SpvBuilderConstructorTest = TestHelper;
-
-TEST_F(SpvBuilderConstructorTest, Const) {
-  auto* c = Expr(42.2f);
-  auto* g = Global("g", ty.f32(), c, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateConstructorExpression(g, c), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 42.2000008
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_WithCasts_OutsideFunction_IsError) {
-  auto* t = Construct<f32>(Construct<u32>(1));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateExpression(t), 0u);
-  EXPECT_TRUE(b.has_error()) << b.error();
-  EXPECT_EQ(b.error(),
-            "Internal error: trying to add SPIR-V instruction 124 outside a "
-            "function");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type) {
-  auto* t = vec3<f32>(1.0f, 1.0f, 3.0f);
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateConstructorExpression(nullptr, t), 5u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_WithCasts) {
-  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(1));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 7u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%4 = OpTypeInt 32 1
-%5 = OpConstant %4 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%3 = OpConvertSToF %2 %5
-%6 = OpConvertSToF %2 %5
-%7 = OpCompositeConstruct %1 %3 %6
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_WithAlias) {
-  // type Int = i32
-  // cast<Int>(2.3f)
-
-  auto* alias = Alias("Int", ty.i32());
-  auto* cast = Construct(ty.Of(alias), 2.3f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeFloat 32
-%4 = OpConstant %3 2.29999995
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpConvertFToS %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_IdentifierExpression_Param) {
-  auto* var = Var("ident", ty.f32());
-
-  auto* t = vec2<f32>(1.0f, "ident");
-  WrapInFunction(var, t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateExpression(t), 8u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-%5 = OpTypeVector %3 2
-%6 = OpConstant %3 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %4
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%7 = OpLoad %3 %1
-%8 = OpCompositeConstruct %5 %6 %7
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Vector_Bitcast_Params) {
-  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 7u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%1 = OpTypeVector %2 2
-%4 = OpTypeInt 32 1
-%5 = OpConstant %4 1
-)");
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%3 = OpBitcast %2 %5
-%6 = OpBitcast %2 %5
-%7 = OpCompositeConstruct %1 %3 %6
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Bool_With_Bool) {
-  auto* cast = Construct<bool>(true);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(cast), 3u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_I32_With_I32) {
-  auto* cast = Construct<i32>(2);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 3u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_U32_With_U32) {
-  auto* cast = Construct<u32>(2u);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 3u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%3 = OpConstant %2 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_F32_With_F32) {
-  auto* cast = Construct<f32>(2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 3u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpConstant %2 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Literal) {
-  auto* cast = vec2<bool>(true);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeVector %2 2
-%3 = OpConstantTrue %2
-%4 = OpConstantComposite %1 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Var) {
-  auto* var = Var("v", nullptr, Expr(true));
-  auto* cast = vec2<bool>(var);
-  WrapInFunction(var, cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantTrue %1
-%4 = OpTypePointer Function %1
-%5 = OpConstantNull %1
-%6 = OpTypeVector %1 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %3 %2
-%7 = OpLoad %1 %3
-%8 = OpCompositeConstruct %6 %7 %7
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_Literal) {
-  auto* cast = vec2<f32>(2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_Var) {
-  auto* var = Var("v", nullptr, Expr(2.0f));
-  auto* cast = vec2<f32>(var);
-  WrapInFunction(var, cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 2
-%4 = OpTypePointer Function %1
-%5 = OpConstantNull %1
-%6 = OpTypeVector %1 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %3 %2
-%7 = OpLoad %1 %3
-%8 = OpCompositeConstruct %6 %7 %7
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_F32) {
-  auto* cast = vec2<f32>(2.0f, 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Vec2) {
-  auto* value = vec2<f32>(2.0f, 2.0f);
-  auto* cast = vec2<f32>(value);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 5u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 2
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32) {
-  auto* cast = vec3<f32>(2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Bool) {
-  auto* cast = vec3<bool>(true);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeVector %2 3
-%3 = OpConstantTrue %2
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_F32_F32) {
-  auto* cast = vec3<f32>(2.0f, 2.0f, 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_Vec2) {
-  auto* cast = vec3<f32>(2.0f, vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeConstruct %1 %3 %6 %7
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec2_F32) {
-  auto* cast = vec3<f32>(vec2<f32>(2.0f, 2.0f), 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeConstruct %1 %6 %7 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec3) {
-  auto* value = vec3<f32>(2.0f, 2.0f, 2.0f);
-  auto* cast = vec3<f32>(value);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 5u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Bool) {
-  auto* cast = vec4<bool>(true);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeVector %2 4
-%3 = OpConstantTrue %2
-%4 = OpConstantComposite %1 %3 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32) {
-  auto* cast = vec4<f32>(2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_F32_F32) {
-  auto* cast = vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_Vec2) {
-  auto* cast = vec4<f32>(2.0f, 2.0f, vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeConstruct %1 %3 %3 %6 %7
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec2_F32) {
-  auto* cast = vec4<f32>(2.0f, vec2<f32>(2.0f, 2.0f), 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeConstruct %1 %3 %6 %7 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_F32_F32) {
-  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), 2.0f, 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 8u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeConstruct %1 %6 %7 %4 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_Vec2) {
-  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 10u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeExtract %2 %5 0
-%9 = OpCompositeExtract %2 %5 1
-%10 = OpCompositeConstruct %1 %6 %7 %8 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec3) {
-  auto* cast = vec4<f32>(2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 3
-%5 = OpConstantComposite %4 %3 %3 %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeExtract %2 %5 2
-%9 = OpCompositeConstruct %1 %3 %6 %7 %8
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec3_F32) {
-  auto* cast = vec4<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 3
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%6 = OpCompositeExtract %2 %5 0
-%7 = OpCompositeExtract %2 %5 1
-%8 = OpCompositeExtract %2 %5 2
-%9 = OpCompositeConstruct %1 %6 %7 %8 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec4) {
-  auto* value = vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f);
-  auto* cast = vec4<f32>(value);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 5u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 4
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_F32_With_F32) {
-  auto* ctor = Construct<f32>(2.0f);
-  GlobalConst("g", ty.f32(), ctor);
-
-  spirv::Builder& b = SanitizeAndBuild();
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 2
-%4 = OpTypeVoid
-%3 = OpTypeFunction %4
-)");
-  Validate(b);
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_U32_With_F32) {
-  auto* ctor = Construct<u32>(1.5f);
-  GlobalConst("g", ty.u32(), ctor);
-
-  spirv::Builder& b = SanitizeAndBuild();
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstant %1 1
-%4 = OpTypeVoid
-%3 = OpTypeFunction %4
-)");
-  Validate(b);
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_F32) {
-  auto* cast = vec2<f32>(2.0f);
-  auto* g = Global("g", ty.vec2<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_Vec2) {
-  auto* cast = vec2<f32>(vec2<f32>(2.0f, 2.0f));
-  GlobalConst("a", ty.vec2<f32>(), cast);
-
-  spirv::Builder& b = SanitizeAndBuild();
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  Validate(b);
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_Vec3) {
-  auto* cast = vec3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f));
-  GlobalConst("a", ty.vec3<f32>(), cast);
-
-  spirv::Builder& b = SanitizeAndBuild();
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  Validate(b);
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec4) {
-  auto* cast = vec4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
-  GlobalConst("a", ty.vec4<f32>(), cast);
-
-  spirv::Builder& b = SanitizeAndBuild();
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3 %3
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-
-  Validate(b);
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32) {
-  auto* cast = vec3<f32>(2.0f);
-  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32_Vec2) {
-  auto* cast = vec3<f32>(2.0f, vec2<f32>(2.0f, 2.0f));
-  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantComposite %1 %3 %6 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_Vec2_F32) {
-  auto* cast = vec3<f32>(vec2<f32>(2.0f, 2.0f), 2.0f);
-  auto* g = Global("g", ty.vec3<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantComposite %1 %6 %9 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32) {
-  auto* cast = vec4<f32>(2.0f);
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_F32_Vec2) {
-  auto* cast = vec4<f32>(2.0f, 2.0f, vec2<f32>(2.0f, 2.0f));
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantComposite %1 %3 %3 %6 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_Vec2_F32) {
-  auto* cast = vec4<f32>(2.0f, vec2<f32>(2.0f, 2.0f), 2.0f);
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 2
-%5 = OpConstantComposite %4 %3 %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantComposite %1 %3 %6 %9 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec2_F32_F32) {
-  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), 2.0f, 2.0f);
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 11u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantComposite %1 %6 %9 %4 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec2_Vec2) {
-  auto* cast = vec4<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%11 = OpSpecConstantOp %2 CompositeExtract %5 8
-%12 = OpSpecConstantOp %2 CompositeExtract %5 10
-%13 = OpSpecConstantComposite %1 %6 %9 %11 %12
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_Vec3) {
-  auto* cast = vec4<f32>(2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpConstant %2 2
-%4 = OpTypeVector %2 3
-%5 = OpConstantComposite %4 %3 %3 %3
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%12 = OpConstant %7 2
-%11 = OpSpecConstantOp %2 CompositeExtract %5 12
-%13 = OpSpecConstantComposite %1 %3 %6 %9 %11
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_Vec3_F32) {
-  auto* cast = vec4<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), 2.0f);
-  auto* g = Global("g", ty.vec4<f32>(), cast, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 13u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 4
-%3 = OpTypeVector %2 3
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4 %4
-%7 = OpTypeInt 32 0
-%8 = OpConstant %7 0
-%6 = OpSpecConstantOp %2 CompositeExtract %5 8
-%10 = OpConstant %7 1
-%9 = OpSpecConstantOp %2 CompositeExtract %5 10
-%12 = OpConstant %7 2
-%11 = OpSpecConstantOp %2 CompositeExtract %5 12
-%13 = OpSpecConstantComposite %1 %6 %9 %11 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat2x2_With_Vec2_Vec2) {
-  auto* cast = mat2x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 2
-%1 = OpTypeMatrix %2 2
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4
-%6 = OpConstantComposite %1 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat3x2_With_Vec2_Vec2_Vec2) {
-  auto* cast = mat3x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f),
-                           vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 2
-%1 = OpTypeMatrix %2 3
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat4x2_With_Vec2_Vec2_Vec2_Vec2) {
-  auto* cast = mat4x2<f32>(vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f),
-                           vec2<f32>(2.0f, 2.0f), vec2<f32>(2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 2
-%1 = OpTypeMatrix %2 4
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat2x3_With_Vec3_Vec3) {
-  auto* cast =
-      mat2x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%1 = OpTypeMatrix %2 2
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat3x3_With_Vec3_Vec3_Vec3) {
-  auto* cast =
-      mat3x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f),
-                  vec3<f32>(2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%1 = OpTypeMatrix %2 3
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat4x3_With_Vec3_Vec3_Vec3_Vec3) {
-  auto* cast =
-      mat4x3<f32>(vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f),
-                  vec3<f32>(2.0f, 2.0f, 2.0f), vec3<f32>(2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%1 = OpTypeMatrix %2 4
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat2x4_With_Vec4_Vec4) {
-  auto* cast = mat2x4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
-                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 4
-%1 = OpTypeMatrix %2 2
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat3x4_With_Vec4_Vec4_Vec4) {
-  auto* cast = mat3x4<f32>(vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
-                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
-                           vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 4
-%1 = OpTypeMatrix %2 3
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Mat4x4_With_Vec4_Vec4_Vec4_Vec4) {
-  auto* cast = mat4x4<f32>(
-      vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f), vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f),
-      vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f), vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f));
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 4
-%1 = OpTypeMatrix %2 4
-%4 = OpConstant %3 2
-%5 = OpConstantComposite %2 %4 %4 %4 %4
-%6 = OpConstantComposite %1 %5 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Array_5_F32) {
-  auto* cast = array<f32, 5>(2.0f, 2.0f, 2.0f, 2.0f, 2.0f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 5
-%1 = OpTypeArray %2 %4
-%5 = OpConstant %2 2
-%6 = OpConstantComposite %1 %5 %5 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Array_2_Vec3) {
-  auto* first = vec3<f32>(1.f, 2.f, 3.f);
-  auto* second = vec3<f32>(1.f, 2.f, 3.f);
-  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), first, second);
-  WrapInFunction(t);
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(t), 10u);
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%4 = OpTypeInt 32 0
-%5 = OpConstant %4 2
-%1 = OpTypeArray %2 %5
-%6 = OpConstant %3 1
-%7 = OpConstant %3 2
-%8 = OpConstant %3 3
-%9 = OpConstantComposite %2 %6 %7 %8
-%10 = OpConstantComposite %1 %9 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoVectors) {
-  auto* v1 = vec3<f32>(2.0f, 2.0f, 2.0f);
-  auto* v2 = vec3<f32>(2.0f, 2.0f, 2.0f);
-  ast::StatementList stmts = {
-      WrapInStatement(v1),
-      WrapInStatement(v2),
-  };
-  WrapInFunction(stmts);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(v1), 4u);
-  EXPECT_EQ(b.GenerateExpression(v2), 4u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 2
-%4 = OpConstantComposite %1 %3 %3 %3
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoArrays) {
-  auto* a1 = array<f32, 3>(2.0f, 2.0f, 2.0f);
-  auto* a2 = array<f32, 3>(2.0f, 2.0f, 2.0f);
-  ast::StatementList stmts = {
-      WrapInStatement(a1),
-      WrapInStatement(a2),
-  };
-  WrapInFunction(stmts);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(a1), 6u);
-  EXPECT_EQ(b.GenerateExpression(a2), 6u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 3
-%1 = OpTypeArray %2 %4
-%5 = OpConstant %2 2
-%6 = OpConstantComposite %1 %5 %5 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, CommonInitializer_Array_VecArray) {
-  // Test that initializers of different types with the same values produce
-  // different OpConstantComposite instructions.
-  // crbug.com/tint/777
-  auto* a1 = array<f32, 2>(1.0f, 2.0f);
-  auto* a2 = vec2<f32>(1.0f, 2.0f);
-  ast::StatementList stmts = {
-      WrapInStatement(a1),
-      WrapInStatement(a2),
-  };
-  WrapInFunction(stmts);
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(a1), 7u);
-  EXPECT_EQ(b.GenerateExpression(a2), 9u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 2
-%1 = OpTypeArray %2 %4
-%5 = OpConstant %2 1
-%6 = OpConstant %2 2
-%7 = OpConstantComposite %1 %5 %6
-%8 = OpTypeVector %2 2
-%9 = OpConstantComposite %8 %5 %6
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Struct) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  auto* t = Construct(ty.Of(s), 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 6u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeVector %2 3
-%1 = OpTypeStruct %2 %3
-%4 = OpConstant %2 2
-%5 = OpConstantComposite %3 %4 %4 %4
-%6 = OpConstantComposite %1 %4 %5
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_F32) {
-  auto* t = Construct<f32>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_I32) {
-  auto* t = Construct<i32>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_U32) {
-  auto* t = Construct<u32>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Bool) {
-  auto* t = Construct<bool>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Vector) {
-  auto* t = vec2<i32>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 3u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeVector %2 2
-%3 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Matrix) {
-  auto* t = mat4x2<f32>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 4u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 2
-%1 = OpTypeMatrix %2 4
-%4 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Array) {
-  auto* t = array<i32, 2>();
-
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 5u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 2
-%1 = OpTypeArray %2 %4
-%5 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
-  auto* s = Structure("my_struct", {Member("a", ty.f32())});
-  auto* t = Construct(ty.Of(s));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_EQ(b.GenerateExpression(t), 3u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeStruct %2
-%3 = OpConstantNull %1
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_U32_To_I32) {
-  auto* cast = Construct<i32>(2u);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpBitcast %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_I32_To_U32) {
-  auto* cast = Construct<u32>(2);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%3 = OpTypeInt 32 1
-%4 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpBitcast %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_F32_To_I32) {
-  auto* cast = Construct<i32>(2.4f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeFloat 32
-%4 = OpConstant %3 2.4000001
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpConvertFToS %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_F32_To_U32) {
-  auto* cast = Construct<u32>(2.4f);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%3 = OpTypeFloat 32
-%4 = OpConstant %3 2.4000001
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpConvertFToU %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_I32_To_F32) {
-  auto* cast = Construct<f32>(2);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 1
-%4 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpConvertSToF %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_U32_To_F32) {
-  auto* cast = Construct<f32>(2u);
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateExpression(cast), 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpConvertUToF %2 %4
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_U32_to_I32) {
-  auto* var = Global("i", ty.vec3<u32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<i32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeInt 32 1
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpBitcast %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_F32_to_I32) {
-  auto* var = Global("i", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<i32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeInt 32 1
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpConvertFToS %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_I32_to_U32) {
-  auto* var = Global("i", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<u32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeInt 32 0
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpBitcast %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_F32_to_U32) {
-  auto* var = Global("i", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<u32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeInt 32 0
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpConvertFToU %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_I32_to_F32) {
-  auto* var = Global("i", ty.vec3<i32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<f32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeFloat 32
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpConvertSToF %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest, Type_Convert_Vectors_U32_to_F32) {
-  auto* var = Global("i", ty.vec3<u32>(), ast::StorageClass::kPrivate);
-
-  auto* cast = vec3<f32>("i");
-  WrapInFunction(cast);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
-%5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
-%8 = OpTypeFloat 32
-%7 = OpTypeVector %8 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-%6 = OpConvertUToF %7 %9
-)");
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_GlobalVectorWithAllConstConstructors) {
-  // vec3<f32>(1.0, 2.0, 3.0)  -> true
-  auto* t = vec3<f32>(1.f, 2.f, 3.f);
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_GlobalArrayWithAllConstConstructors) {
-  // array<vec3<f32>, 2>(vec3<f32>(1.0, 2.0, 3.0), vec3<f32>(1.0, 2.0, 3.0))
-  //   -> true
-  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), vec3<f32>(1.f, 2.f, 3.f),
-                      vec3<f32>(1.f, 2.f, 3.f));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_GlobalVectorWithMatchingTypeConstructors) {
-  // vec2<f32>(f32(1.0), f32(2.0))  -> false
-
-  auto* t = vec2<f32>(Construct<f32>(1.f), Construct<f32>(2.f));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_GlobalWithTypeConversionConstructor) {
-  // vec2<f32>(f32(1), f32(2)) -> false
-
-  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_VectorWithAllConstConstructors) {
-  // vec3<f32>(1.0, 2.0, 3.0)  -> true
-
-  auto* t = vec3<f32>(1.f, 2.f, 3.f);
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Vector_WithIdent) {
-  // vec3<f32>(a, b, c)  -> false
-
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-  Global("b", ty.f32(), ast::StorageClass::kPrivate);
-  Global("c", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* t = vec3<f32>("a", "b", "c");
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_ArrayWithAllConstConstructors) {
-  // array<vec3<f32>, 2>(vec3<f32>(1.0, 2.0, 3.0), vec3<f32>(1.0, 2.0, 3.0))
-  //   -> true
-
-  auto* first = vec3<f32>(1.f, 2.f, 3.f);
-  auto* second = vec3<f32>(1.f, 2.f, 3.f);
-
-  auto* t = Construct(ty.array(ty.vec3<f32>(), 2), first, second);
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_VectorWithTypeConversionConstConstructors) {
-  // vec2<f32>(f32(1), f32(2))  -> false
-
-  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest, IsConstructorConst_BitCastScalars) {
-  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  auto* t = Construct(ty.Of(s), 2.f, vec3<f32>(2.f, 2.f, 2.f));
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_Struct_WithIdentSubExpression) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
-
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-  Global("b", ty.vec3<f32>(), ast::StorageClass::kPrivate);
-
-  auto* t = Construct(ty.Of(s), "a", "b");
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.IsConstructorConst(t));
-  EXPECT_FALSE(b.has_error());
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_discard_test.cc b/src/writer/spirv/builder_discard_test.cc
deleted file mode 100644
index 106f87b..0000000
--- a/src/writer/spirv/builder_discard_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Discard) {
-  auto* expr = create<ast::DiscardStatement>();
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpKill
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_entry_point_test.cc b/src/writer/spirv/builder_entry_point_test.cc
deleted file mode 100644
index d22e4e4..0000000
--- a/src/writer/spirv/builder_entry_point_test.cc
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <memory>
-
-#include "gtest/gtest.h"
-#include "src/ast/builtin.h"
-#include "src/ast/builtin_attribute.h"
-#include "src/ast/location_attribute.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/storage_class.h"
-#include "src/ast/variable.h"
-#include "src/program.h"
-#include "src/sem/f32_type.h"
-#include "src/sem/vector_type.h"
-#include "src/writer/spirv/builder.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, EntryPoint_Parameters) {
-  // @stage(fragment)
-  // fn frag_main(@builtin(position) coord : vec4<f32>,
-  //              @location(1) loc1 : f32) {
-  //   var col : f32 = (coord.x * loc1);
-  // }
-  auto* coord =
-      Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-  auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
-  auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
-  auto* col = Var("col", ty.f32(), ast::StorageClass::kNone, mul);
-  Func("frag_main", ast::VariableList{coord, loc1}, ty.void_(),
-       ast::StatementList{WrapInStatement(col)},
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  // Test that "coord" and "loc1" get hoisted out to global variables with the
-  // Input storage class, retaining their decorations.
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %19 "frag_main" %1 %5
-OpExecutionMode %19 OriginUpperLeft
-OpName %1 "coord_1"
-OpName %5 "loc1_1"
-OpName %9 "frag_main_inner"
-OpName %10 "coord"
-OpName %11 "loc1"
-OpName %15 "col"
-OpName %19 "frag_main"
-OpDecorate %1 BuiltIn FragCoord
-OpDecorate %5 Location 1
-%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 4
-%2 = OpTypePointer Input %3
-%1 = OpVariable %2 Input
-%6 = OpTypePointer Input %4
-%5 = OpVariable %6 Input
-%8 = OpTypeVoid
-%7 = OpTypeFunction %8 %3 %4
-%16 = OpTypePointer Function %4
-%17 = OpConstantNull %4
-%18 = OpTypeFunction %8
-%9 = OpFunction %8 None %7
-%10 = OpFunctionParameter %3
-%11 = OpFunctionParameter %4
-%12 = OpLabel
-%15 = OpVariable %16 Function %17
-%13 = OpCompositeExtract %4 %10 0
-%14 = OpFMul %4 %13 %11
-OpStore %15 %14
-OpReturn
-OpFunctionEnd
-%19 = OpFunction %8 None %18
-%20 = OpLabel
-%22 = OpLoad %3 %1
-%23 = OpLoad %4 %5
-%21 = OpFunctionCall %8 %9 %22 %23
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, EntryPoint_ReturnValue) {
-  // @stage(fragment)
-  // fn frag_main(@location(0) @interpolate(flat) loc_in : u32)
-  //     -> @location(0) f32 {
-  //   if (loc_in > 10) {
-  //     return 0.5;
-  //   }
-  //   return 1.0;
-  // }
-  auto* loc_in = Param("loc_in", ty.u32(), {Location(0), Flat()});
-  auto* cond = create<ast::BinaryExpression>(ast::BinaryOp::kGreaterThan,
-                                             Expr("loc_in"), Expr(10u));
-  Func("frag_main", ast::VariableList{loc_in}, ty.f32(),
-       ast::StatementList{
-           If(cond, Block(Return(0.5f))),
-           Return(1.0f),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kFragment),
-       },
-       ast::AttributeList{Location(0)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  // Test that the return value gets hoisted out to a global variable with the
-  // Output storage class, and the return statements are replaced with stores.
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %21 "frag_main" %1 %4
-OpExecutionMode %21 OriginUpperLeft
-OpName %1 "loc_in_1"
-OpName %4 "value"
-OpName %9 "frag_main_inner"
-OpName %10 "loc_in"
-OpName %21 "frag_main"
-OpDecorate %1 Location 0
-OpDecorate %1 Flat
-OpDecorate %4 Location 0
-%3 = OpTypeInt 32 0
-%2 = OpTypePointer Input %3
-%1 = OpVariable %2 Input
-%6 = OpTypeFloat 32
-%5 = OpTypePointer Output %6
-%7 = OpConstantNull %6
-%4 = OpVariable %5 Output %7
-%8 = OpTypeFunction %6 %3
-%12 = OpConstant %3 10
-%14 = OpTypeBool
-%17 = OpConstant %6 0.5
-%18 = OpConstant %6 1
-%20 = OpTypeVoid
-%19 = OpTypeFunction %20
-%9 = OpFunction %6 None %8
-%10 = OpFunctionParameter %3
-%11 = OpLabel
-%13 = OpUGreaterThan %14 %10 %12
-OpSelectionMerge %15 None
-OpBranchConditional %13 %16 %15
-%16 = OpLabel
-OpReturnValue %17
-%15 = OpLabel
-OpReturnValue %18
-OpFunctionEnd
-%21 = OpFunction %20 None %19
-%22 = OpLabel
-%24 = OpLoad %3 %1
-%23 = OpFunctionCall %6 %9 %24
-OpStore %4 %23
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, EntryPoint_SharedStruct) {
-  // struct Interface {
-  //   @location(1) value : f32;
-  //   @builtin(position) pos : vec4<f32>;
-  // };
-  //
-  // @stage(vertex)
-  // fn vert_main() -> Interface {
-  //   return Interface(42.0, vec4<f32>());
-  // }
-  //
-  // @stage(fragment)
-  // fn frag_main(inputs : Interface) -> @builtin(frag_depth) f32 {
-  //   return inputs.value;
-  // }
-
-  auto* interface = Structure(
-      "Interface",
-      {
-          Member("value", ty.f32(), ast::AttributeList{Location(1u)}),
-          Member("pos", ty.vec4<f32>(),
-                 ast::AttributeList{Builtin(ast::Builtin::kPosition)}),
-      });
-
-  auto* vert_retval =
-      Construct(ty.Of(interface), 42.f, Construct(ty.vec4<f32>()));
-  Func("vert_main", ast::VariableList{}, ty.Of(interface),
-       {Return(vert_retval)}, {Stage(ast::PipelineStage::kVertex)});
-
-  auto* frag_inputs = Param("inputs", ty.Of(interface));
-  Func("frag_main", ast::VariableList{frag_inputs}, ty.f32(),
-       {Return(MemberAccessor(Expr("inputs"), "value"))},
-       {Stage(ast::PipelineStage::kFragment)},
-       {Builtin(ast::Builtin::kFragDepth)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %22 "vert_main" %1 %5
-OpEntryPoint Fragment %32 "frag_main" %9 %11 %13
-OpExecutionMode %32 OriginUpperLeft
-OpExecutionMode %32 DepthReplacing
-OpName %1 "value_1"
-OpName %5 "pos_1"
-OpName %9 "value_2"
-OpName %11 "pos_2"
-OpName %13 "value_3"
-OpName %15 "Interface"
-OpMemberName %15 0 "value"
-OpMemberName %15 1 "pos"
-OpName %16 "vert_main_inner"
-OpName %22 "vert_main"
-OpName %28 "frag_main_inner"
-OpName %29 "inputs"
-OpName %32 "frag_main"
-OpDecorate %1 Location 1
-OpDecorate %5 BuiltIn Position
-OpDecorate %9 Location 1
-OpDecorate %11 BuiltIn FragCoord
-OpDecorate %13 BuiltIn FragDepth
-OpMemberDecorate %15 0 Offset 0
-OpMemberDecorate %15 1 Offset 16
-%3 = OpTypeFloat 32
-%2 = OpTypePointer Output %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Output %4
-%7 = OpTypeVector %3 4
-%6 = OpTypePointer Output %7
-%8 = OpConstantNull %7
-%5 = OpVariable %6 Output %8
-%10 = OpTypePointer Input %3
-%9 = OpVariable %10 Input
-%12 = OpTypePointer Input %7
-%11 = OpVariable %12 Input
-%13 = OpVariable %2 Output %4
-%15 = OpTypeStruct %3 %7
-%14 = OpTypeFunction %15
-%18 = OpConstant %3 42
-%19 = OpConstantComposite %15 %18 %8
-%21 = OpTypeVoid
-%20 = OpTypeFunction %21
-%27 = OpTypeFunction %3 %15
-%16 = OpFunction %15 None %14
-%17 = OpLabel
-OpReturnValue %19
-OpFunctionEnd
-%22 = OpFunction %21 None %20
-%23 = OpLabel
-%24 = OpFunctionCall %15 %16
-%25 = OpCompositeExtract %3 %24 0
-OpStore %1 %25
-%26 = OpCompositeExtract %7 %24 1
-OpStore %5 %26
-OpReturn
-OpFunctionEnd
-%28 = OpFunction %3 None %27
-%29 = OpFunctionParameter %15
-%30 = OpLabel
-%31 = OpCompositeExtract %3 %29 0
-OpReturnValue %31
-OpFunctionEnd
-%32 = OpFunction %21 None %20
-%33 = OpLabel
-%35 = OpLoad %3 %9
-%36 = OpLoad %7 %11
-%37 = OpCompositeConstruct %15 %35 %36
-%34 = OpFunctionCall %3 %28 %37
-OpStore %13 %34
-OpReturn
-OpFunctionEnd
-)");
-
-  Validate(b);
-}
-
-TEST_F(BuilderTest, SampleIndex_SampleRateShadingCapability) {
-  Func("main",
-       {Param("sample_index", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})},
-       ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build()) << b.error();
-
-  // Make sure we generate the SampleRateShading capability.
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            "OpCapability Shader\n"
-            "OpCapability SampleRateShading\n");
-  EXPECT_EQ(DumpInstructions(b.annots()), "OpDecorate %1 BuiltIn SampleId\n");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_format_conversion_test.cc b/src/writer/spirv/builder_format_conversion_test.cc
deleted file mode 100644
index 0c4b67b..0000000
--- a/src/writer/spirv/builder_format_conversion_test.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-struct TestData {
-  ast::TexelFormat ast_format;
-  SpvImageFormat_ spv_format;
-  bool extended_format = false;
-};
-inline std::ostream& operator<<(std::ostream& out, TestData data) {
-  out << data.ast_format;
-  return out;
-}
-using ImageFormatConversionTest = TestParamHelper<TestData>;
-
-TEST_P(ImageFormatConversionTest, ImageFormatConversion) {
-  auto param = GetParam();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.convert_texel_format_to_spv(param.ast_format), param.spv_format);
-
-  if (param.extended_format) {
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
-              R"(OpCapability StorageImageExtendedFormats
-)");
-  } else {
-    EXPECT_EQ(DumpInstructions(b.capabilities()), "");
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    ImageFormatConversionTest,
-    testing::Values(
-        /* WGSL unsupported formats
-  TestData{ast::TexelFormat::kR8Unorm, SpvImageFormatR8, true},
-  TestData{ast::TexelFormat::kR8Snorm, SpvImageFormatR8Snorm, true},
-  TestData{ast::TexelFormat::kR8Uint, SpvImageFormatR8ui, true},
-  TestData{ast::TexelFormat::kR8Sint, SpvImageFormatR8i, true},
-  TestData{ast::TexelFormat::kR16Uint, SpvImageFormatR16ui, true},
-  TestData{ast::TexelFormat::kR16Sint, SpvImageFormatR16i, true},
-  TestData{ast::TexelFormat::kR16Float, SpvImageFormatR16f, true},
-  TestData{ast::TexelFormat::kRg8Unorm, SpvImageFormatRg8, true},
-  TestData{ast::TexelFormat::kRg8Snorm, SpvImageFormatRg8Snorm, true},
-  TestData{ast::TexelFormat::kRg8Uint, SpvImageFormatRg8ui, true},
-  TestData{ast::TexelFormat::kRg8Sint, SpvImageFormatRg8i, true},
-  TestData{ast::TexelFormat::kRg16Uint, SpvImageFormatRg16ui, true},
-  TestData{ast::TexelFormat::kRg16Sint, SpvImageFormatRg16i, true},
-  TestData{ast::TexelFormat::kRg16Float, SpvImageFormatRg16f, true},
-  TestData{ast::TexelFormat::kRgba8UnormSrgb, SpvImageFormatUnknown},
-  TestData{ast::TexelFormat::kBgra8Unorm, SpvImageFormatUnknown},
-  TestData{ast::TexelFormat::kBgra8UnormSrgb, SpvImageFormatUnknown},
-  TestData{ast::TexelFormat::kRgb10A2Unorm, SpvImageFormatRgb10A2, true},
-  TestData{ast::TexelFormat::kRg11B10Float, SpvImageFormatR11fG11fB10f, true},
-*/
-        TestData{ast::TexelFormat::kR32Uint, SpvImageFormatR32ui},
-        TestData{ast::TexelFormat::kR32Sint, SpvImageFormatR32i},
-        TestData{ast::TexelFormat::kR32Float, SpvImageFormatR32f},
-        TestData{ast::TexelFormat::kRgba8Unorm, SpvImageFormatRgba8},
-        TestData{ast::TexelFormat::kRgba8Snorm, SpvImageFormatRgba8Snorm},
-        TestData{ast::TexelFormat::kRgba8Uint, SpvImageFormatRgba8ui},
-        TestData{ast::TexelFormat::kRgba8Sint, SpvImageFormatRgba8i},
-        TestData{ast::TexelFormat::kRg32Uint, SpvImageFormatRg32ui, true},
-        TestData{ast::TexelFormat::kRg32Sint, SpvImageFormatRg32i, true},
-        TestData{ast::TexelFormat::kRg32Float, SpvImageFormatRg32f, true},
-        TestData{ast::TexelFormat::kRgba16Uint, SpvImageFormatRgba16ui},
-        TestData{ast::TexelFormat::kRgba16Sint, SpvImageFormatRgba16i},
-        TestData{ast::TexelFormat::kRgba16Float, SpvImageFormatRgba16f},
-        TestData{ast::TexelFormat::kRgba32Uint, SpvImageFormatRgba32ui},
-        TestData{ast::TexelFormat::kRgba32Sint, SpvImageFormatRgba32i},
-        TestData{ast::TexelFormat::kRgba32Float, SpvImageFormatRgba32f}));
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_function_attribute_test.cc b/src/writer/spirv/builder_function_attribute_test.cc
deleted file mode 100644
index a5f8a5c..0000000
--- a/src/writer/spirv/builder_function_attribute_test.cc
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Attribute_Stage) {
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kFragment),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.entry_points()),
-            R"(OpEntryPoint Fragment %3 "main"
-)");
-}
-
-struct FunctionStageData {
-  ast::PipelineStage stage;
-  SpvExecutionModel model;
-};
-inline std::ostream& operator<<(std::ostream& out, FunctionStageData data) {
-  out << data.stage;
-  return out;
-}
-using Attribute_StageTest = TestParamHelper<FunctionStageData>;
-TEST_P(Attribute_StageTest, Emit) {
-  auto params = GetParam();
-
-  const ast::Variable* var = nullptr;
-  const ast::Type* ret_type = nullptr;
-  ast::AttributeList ret_type_attrs;
-  ast::StatementList body;
-  if (params.stage == ast::PipelineStage::kVertex) {
-    ret_type = ty.vec4<f32>();
-    ret_type_attrs.push_back(Builtin(ast::Builtin::kPosition));
-    body.push_back(Return(Construct(ty.vec4<f32>())));
-  } else {
-    ret_type = ty.void_();
-  }
-
-  auto deco_list = ast::AttributeList{Stage(params.stage)};
-  if (params.stage == ast::PipelineStage::kCompute) {
-    deco_list.push_back(WorkgroupSize(1));
-  }
-
-  auto* func = Func("main", {}, ret_type, body, deco_list, ret_type_attrs);
-
-  spirv::Builder& b = Build();
-
-  if (var) {
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  }
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  auto preamble = b.entry_points();
-  ASSERT_GE(preamble.size(), 1u);
-  EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint);
-
-  ASSERT_GE(preamble[0].operands().size(), 3u);
-  EXPECT_EQ(preamble[0].operands()[0].to_i(),
-            static_cast<uint32_t>(params.model));
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest,
-    Attribute_StageTest,
-    testing::Values(FunctionStageData{ast::PipelineStage::kVertex,
-                                      SpvExecutionModelVertex},
-                    FunctionStageData{ast::PipelineStage::kFragment,
-                                      SpvExecutionModelFragment},
-                    FunctionStageData{ast::PipelineStage::kCompute,
-                                      SpvExecutionModelGLCompute}));
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_Fragment_OriginUpperLeft) {
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kFragment),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %3 OriginUpperLeft
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Default) {
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                                       WorkgroupSize(1)});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %3 LocalSize 1 1 1
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Literals) {
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        WorkgroupSize(2, 4, 6),
-                        Stage(ast::PipelineStage::kCompute),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %3 LocalSize 2 4 6
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Const) {
-  GlobalConst("width", ty.i32(), Construct(ty.i32(), 2));
-  GlobalConst("height", ty.i32(), Construct(ty.i32(), 3));
-  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4));
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        WorkgroupSize("width", "height", "depth"),
-                        Stage(ast::PipelineStage::kCompute),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %3 LocalSize 2 3 4
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_OverridableConst) {
-  Override("width", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
-  Override("height", ty.i32(), Construct(ty.i32(), 3), {Id(8u)});
-  Override("depth", ty.i32(), Construct(ty.i32(), 4), {Id(9u)});
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        WorkgroupSize("width", "height", "depth"),
-                        Stage(ast::PipelineStage::kCompute),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()), "");
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 0
-%1 = OpTypeVector %2 3
-%4 = OpSpecConstant %2 2
-%5 = OpSpecConstant %2 3
-%6 = OpSpecConstant %2 4
-%3 = OpSpecConstantComposite %1 %4 %5 %6
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()),
-            R"(OpDecorate %4 SpecId 7
-OpDecorate %5 SpecId 8
-OpDecorate %6 SpecId 9
-OpDecorate %3 BuiltIn WorkgroupSize
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) {
-  Override("height", ty.i32(), Construct(ty.i32(), 2), {Id(7u)});
-  GlobalConst("depth", ty.i32(), Construct(ty.i32(), 3));
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
-                    ast::AttributeList{
-                        WorkgroupSize(4, "height", "depth"),
-                        Stage(ast::PipelineStage::kCompute),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.execution_modes()), "");
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 0
-%1 = OpTypeVector %2 3
-%4 = OpConstant %2 4
-%5 = OpSpecConstant %2 2
-%6 = OpConstant %2 3
-%3 = OpSpecConstantComposite %1 %4 %5 %6
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()),
-            R"(OpDecorate %5 SpecId 7
-OpDecorate %3 BuiltIn WorkgroupSize
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_MultipleFragment) {
-  auto* func1 = Func("main1", {}, ty.void_(), ast::StatementList{},
-                     ast::AttributeList{
-                         Stage(ast::PipelineStage::kFragment),
-                     });
-
-  auto* func2 = Func("main2", {}, ty.void_(), ast::StatementList{},
-                     ast::AttributeList{
-                         Stage(ast::PipelineStage::kFragment),
-                     });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func1)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func2)) << b.error();
-  EXPECT_EQ(DumpBuilder(b),
-            R"(OpEntryPoint Fragment %3 "main1"
-OpEntryPoint Fragment %5 "main2"
-OpExecutionMode %3 OriginUpperLeft
-OpExecutionMode %5 OriginUpperLeft
-OpName %3 "main1"
-OpName %5 "main2"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-%5 = OpFunction %2 None %1
-%6 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Decoration_ExecutionMode_FragDepth) {
-  Func("main", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return(Expr(1.f)),
-       },
-       ast::AttributeList{Stage(ast::PipelineStage::kFragment)},
-       ast::AttributeList{
-           Builtin(ast::Builtin::kFragDepth),
-       });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %11 OriginUpperLeft
-OpExecutionMode %11 DepthReplacing
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_function_test.cc b/src/writer/spirv/builder_function_test.cc
deleted file mode 100644
index d1d19c1..0000000
--- a/src/writer/spirv/builder_function_test.cc
+++ /dev/null
@@ -1,292 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Function_Empty) {
-  Func("a_func", {}, ty.void_(), ast::StatementList{}, ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Function_Terminator_Return) {
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Function_Terminator_ReturnValue) {
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  Func("a_func", {}, ty.f32(), ast::StatementList{Return("a")},
-       ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* var_a = program->AST().GlobalVariables()[0];
-  auto* func = program->AST().Functions()[0];
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "a"
-OpName %6 "a_func"
-%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpTypeFunction %3
-%6 = OpFunction %3 None %5
-%7 = OpLabel
-%8 = OpLoad %3 %1
-OpReturnValue %8
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Function_Terminator_Discard) {
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           create<ast::DiscardStatement>(),
-       },
-       ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpKill
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Function_WithParams) {
-  ast::VariableList params = {Param("a", ty.f32()), Param("b", ty.i32())};
-
-  Func("a_func", params, ty.f32(), ast::StatementList{Return("a")},
-       ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %4 "a_func"
-OpName %5 "a"
-OpName %6 "b"
-%2 = OpTypeFloat 32
-%3 = OpTypeInt 32 1
-%1 = OpTypeFunction %2 %2 %3
-%4 = OpFunction %2 None %1
-%5 = OpFunctionParameter %2
-%6 = OpFunctionParameter %3
-%7 = OpLabel
-OpReturnValue %5
-OpFunctionEnd
-)") << DumpBuilder(b);
-}
-
-TEST_F(BuilderTest, Function_WithBody) {
-  Func("a_func", {}, ty.void_(),
-       ast::StatementList{
-           Return(),
-       },
-       ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, FunctionType) {
-  Func("a_func", {}, ty.void_(), ast::StatementList{}, ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  auto* func = program->AST().Functions()[0];
-  ASSERT_TRUE(b.GenerateFunction(func));
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-)");
-}
-
-TEST_F(BuilderTest, FunctionType_DeDuplicate) {
-  auto* func1 = Func("a_func", {}, ty.void_(), ast::StatementList{},
-                     ast::AttributeList{});
-  auto* func2 = Func("b_func", {}, ty.void_(), ast::StatementList{},
-                     ast::AttributeList{});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateFunction(func1));
-  ASSERT_TRUE(b.GenerateFunction(func2));
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-)");
-}
-
-// https://crbug.com/tint/297
-TEST_F(BuilderTest, Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
-  // [[block]] struct Data {
-  //   d : f32;
-  // };
-  // @binding(0) @group(0) var<storage> data : Data;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn a() {
-  //   return;
-  // }
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn b() {
-  //   return;
-  // }
-
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("a", ast::VariableList{}, ty.void_(),
-         ast::StatementList{
-             Decl(var),
-             Return(),
-         },
-         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                            WorkgroupSize(1)});
-  }
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("b", ast::VariableList{}, ty.void_(),
-         ast::StatementList{
-             Decl(var),
-             Return(),
-         },
-         ast::AttributeList{Stage(ast::PipelineStage::kCompute),
-                            WorkgroupSize(1)});
-  }
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %7 "a"
-OpEntryPoint GLCompute %17 "b"
-OpExecutionMode %7 LocalSize 1 1 1
-OpExecutionMode %17 LocalSize 1 1 1
-OpName %3 "Data"
-OpMemberName %3 0 "d"
-OpName %1 "data"
-OpName %7 "a"
-OpName %14 "v"
-OpName %17 "b"
-OpName %21 "v"
-OpDecorate %3 Block
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-%4 = OpTypeFloat 32
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-%9 = OpTypeInt 32 0
-%10 = OpConstant %9 0
-%11 = OpTypePointer StorageBuffer %4
-%15 = OpTypePointer Function %4
-%16 = OpConstantNull %4
-%7 = OpFunction %6 None %5
-%8 = OpLabel
-%14 = OpVariable %15 Function %16
-%12 = OpAccessChain %11 %1 %10
-%13 = OpLoad %4 %12
-OpStore %14 %13
-OpReturn
-OpFunctionEnd
-%17 = OpFunction %6 None %5
-%18 = OpLabel
-%21 = OpVariable %15 Function %16
-%19 = OpAccessChain %11 %1 %10
-%20 = OpLoad %4 %19
-OpStore %21 %20
-OpReturn
-OpFunctionEnd
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_function_variable_test.cc b/src/writer/spirv/builder_function_variable_test.cc
deleted file mode 100644
index 1ef5deb..0000000
--- a/src/writer/spirv/builder_function_variable_test.cc
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, FunctionVar_NoStorageClass) {
-  auto* v = Var("var", ty.f32(), ast::StorageClass::kFunction);
-  WrapInFunction(v);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-)");
-
-  const auto& func = b.functions()[0];
-  EXPECT_EQ(DumpInstructions(func.variables()),
-            R"(%1 = OpVariable %2 Function %4
-)");
-}
-
-TEST_F(BuilderTest, FunctionVar_WithConstantConstructor) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-  auto* v = Var("var", ty.vec3<f32>(), ast::StorageClass::kFunction, init);
-  WrapInFunction(v);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-%7 = OpTypePointer Function %1
-%8 = OpConstantNull %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%6 = OpVariable %7 Function %8
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %6 %5
-)");
-}
-
-TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructor) {
-  auto* init = vec2<f32>(1.f, Add(3.f, 3.f));
-
-  auto* v = Var("var", ty.vec2<f32>(), ast::StorageClass::kNone, init);
-  WrapInFunction(v);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %7 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%8 = OpTypePointer Function %1
-%9 = OpConstantNull %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%7 = OpVariable %8 Function %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpFAdd %2 %4 %4
-%6 = OpCompositeConstruct %1 %3 %5
-OpStore %7 %6
-)");
-}
-
-TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructorLoadedFromVar) {
-  // var v : f32 = 1.0;
-  // var v2 : f32 = v; // Should generate the load and store automatically.
-
-  auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1.f));
-
-  auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
-  WrapInFunction(v, v2);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
-OpName %7 "v2"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 1
-%4 = OpTypePointer Function %1
-%5 = OpConstantNull %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%3 = OpVariable %4 Function %5
-%7 = OpVariable %4 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %3 %2
-%6 = OpLoad %1 %3
-OpStore %7 %6
-)");
-}
-
-TEST_F(BuilderTest, FunctionVar_ConstWithVarInitializer) {
-  // var v : f32 = 1.0;
-  // let v2 : f32 = v; // Should generate the load
-
-  auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1.f));
-
-  auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
-  WrapInFunction(v, v2);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
-OpName %7 "v2"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 1
-%4 = OpTypePointer Function %1
-%5 = OpConstantNull %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%3 = OpVariable %4 Function %5
-%7 = OpVariable %4 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpStore %3 %2
-%6 = OpLoad %1 %3
-OpStore %7 %6
-)");
-}
-
-TEST_F(BuilderTest, FunctionVar_Const) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* v = Const("var", ty.vec3<f32>(), init);
-
-  WrapInFunction(v);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
deleted file mode 100644
index 9181fa7..0000000
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ /dev/null
@@ -1,628 +0,0 @@
-// Copyright 2020 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/ast/id_attribute.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, GlobalVar_WithStorageClass) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_WithConstructor) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate, init);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-%7 = OpTypePointer Private %1
-%6 = OpVariable %7 Private %5
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Const) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %5 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Complex_Constructor) {
-  auto* init = vec3<f32>(ast::ExpressionList{Expr(1.f), Expr(2.f), Expr(3.f)});
-
-  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 2
-%5 = OpConstant %2 3
-%6 = OpConstantComposite %1 %3 %4 %5
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Complex_ConstructorWithExtract) {
-  auto* init = vec3<f32>(vec2<f32>(1.f, 2.f), 3.f);
-
-  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 1
-%5 = OpConstant %2 2
-%6 = OpConstantComposite %3 %4 %5
-%8 = OpTypeInt 32 0
-%9 = OpConstant %8 0
-%7 = OpSpecConstantOp %2 CompositeExtract %6 9
-%11 = OpConstant %8 1
-%10 = OpSpecConstantOp %2 CompositeExtract %6 11
-%12 = OpConstant %2 3
-%13 = OpSpecConstantComposite %1 %7 %10 %12
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
-  auto* v = Global("var", ty.sampler(ast::SamplerKind::kSampler),
-                   ast::StorageClass::kNone, nullptr,
-                   ast::AttributeList{
-                       create<ast::BindingAttribute>(2),
-                       create<ast::GroupAttribute>(3),
-                   });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2
-OpDecorate %1 DescriptorSet 3
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeSampler
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Bool) {
-  auto* v = Override("var", ty.bool_(), Expr(true),
-                     ast::AttributeList{
-                         Id(1200),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpSpecConstantTrue %1
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Bool_ZeroValue) {
-  auto* v = Override("var", ty.bool_(), Construct<bool>(),
-                     ast::AttributeList{
-                         Id(1200),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpSpecConstantFalse %1
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Bool_NoConstructor) {
-  auto* v = Override("var", ty.bool_(), nullptr,
-                     ast::AttributeList{
-                         Id(1200),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 1200
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpSpecConstantFalse %1
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Scalar) {
-  auto* v = Override("var", ty.f32(), Expr(2.f),
-                     ast::AttributeList{
-                         Id(0),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpSpecConstant %1 2
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Scalar_ZeroValue) {
-  auto* v = Override("var", ty.f32(), Construct<f32>(),
-                     ast::AttributeList{
-                         Id(0),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpSpecConstant %1 0
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Scalar_F32_NoConstructor) {
-  auto* v = Override("var", ty.f32(), nullptr,
-                     ast::AttributeList{
-                         Id(0),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpSpecConstant %1 0
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Scalar_I32_NoConstructor) {
-  auto* v = Override("var", ty.i32(), nullptr,
-                     ast::AttributeList{
-                         Id(0),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpSpecConstant %1 0
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_Scalar_U32_NoConstructor) {
-  auto* v = Override("var", ty.u32(), nullptr,
-                     ast::AttributeList{
-                         Id(0),
-                     });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpSpecConstant %1 0
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_Override_NoId) {
-  auto* var_a = Override("a", ty.bool_(), Expr(true),
-                         ast::AttributeList{
-                             Id(0),
-                         });
-  auto* var_b = Override("b", ty.bool_(), Expr(false));
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-  EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %2 "a"
-OpName %3 "b"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %2 SpecId 0
-OpDecorate %3 SpecId 1
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpSpecConstantTrue %1
-%3 = OpSpecConstantFalse %1
-)");
-}
-
-struct BuiltinData {
-  ast::Builtin builtin;
-  ast::StorageClass storage;
-  SpvBuiltIn result;
-};
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-  out << data.builtin;
-  return out;
-}
-using BuiltinDataTest = TestParamHelper<BuiltinData>;
-TEST_P(BuiltinDataTest, Convert) {
-  auto params = GetParam();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.ConvertBuiltin(params.builtin, params.storage), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest_Type,
-    BuiltinDataTest,
-    testing::Values(
-        BuiltinData{ast::Builtin::kNone, ast::StorageClass::kNone,
-                    SpvBuiltInMax},
-        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kInput,
-                    SpvBuiltInFragCoord},
-        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kOutput,
-                    SpvBuiltInPosition},
-        BuiltinData{
-            ast::Builtin::kVertexIndex,
-            ast::StorageClass::kInput,
-            SpvBuiltInVertexIndex,
-        },
-        BuiltinData{ast::Builtin::kInstanceIndex, ast::StorageClass::kInput,
-                    SpvBuiltInInstanceIndex},
-        BuiltinData{ast::Builtin::kFrontFacing, ast::StorageClass::kInput,
-                    SpvBuiltInFrontFacing},
-        BuiltinData{ast::Builtin::kFragDepth, ast::StorageClass::kOutput,
-                    SpvBuiltInFragDepth},
-        BuiltinData{ast::Builtin::kLocalInvocationId, ast::StorageClass::kInput,
-                    SpvBuiltInLocalInvocationId},
-        BuiltinData{ast::Builtin::kLocalInvocationIndex,
-                    ast::StorageClass::kInput, SpvBuiltInLocalInvocationIndex},
-        BuiltinData{ast::Builtin::kGlobalInvocationId,
-                    ast::StorageClass::kInput, SpvBuiltInGlobalInvocationId},
-        BuiltinData{ast::Builtin::kWorkgroupId, ast::StorageClass::kInput,
-                    SpvBuiltInWorkgroupId},
-        BuiltinData{ast::Builtin::kNumWorkgroups, ast::StorageClass::kInput,
-                    SpvBuiltInNumWorkgroups},
-        BuiltinData{ast::Builtin::kSampleIndex, ast::StorageClass::kInput,
-                    SpvBuiltInSampleId},
-        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kInput,
-                    SpvBuiltInSampleMask},
-        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kOutput,
-                    SpvBuiltInSampleMask}));
-
-TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
-  // struct A {
-  //   a : i32;
-  // };
-  // var b<storage, read> : A
-
-  auto* A = Structure("A",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.i32()),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
-OpMemberDecorate %3 0 Offset 0
-OpMemberDecorate %3 1 Offset 4
-OpDecorate %1 NonWritable
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
-OpMemberName %3 0 "a"
-OpMemberName %3 1 "b"
-OpName %1 "b"
-OpName %7 "unused_entry_point"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4 %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) {
-  // struct A {
-  //   a : i32;
-  // };
-  // type B = A;
-  // var b<storage, read> : B
-
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* B = Alias("B", ty.Of(A));
-  Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %1 NonWritable
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
-OpMemberName %3 0 "a"
-OpName %1 "b"
-OpName %7 "unused_entry_point"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) {
-  // struct A {
-  //   a : i32;
-  // };
-  // type B = A;
-  // var<storage, read> b : B
-
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* B = Alias("B", ty.Of(A));
-  Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %1 NonWritable
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
-OpMemberName %3 0 "a"
-OpName %1 "b"
-OpName %7 "unused_entry_point"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) {
-  // struct A {
-  //   a : i32;
-  // };
-  // var<storage, read> b : A
-  // var<storage, read_write> c : A
-
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::GroupAttribute>(0),
-             create<ast::BindingAttribute>(0),
-         });
-  Global("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::GroupAttribute>(1),
-             create<ast::BindingAttribute>(0),
-         });
-
-  spirv::Builder& b = SanitizeAndBuild();
-
-  ASSERT_TRUE(b.Build());
-
-  EXPECT_EQ(DumpInstructions(b.annots()),
-            R"(OpDecorate %3 Block
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %1 NonWritable
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-OpDecorate %5 DescriptorSet 1
-OpDecorate %5 Binding 0
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
-OpMemberName %3 0 "a"
-OpName %1 "b"
-OpName %5 "c"
-OpName %8 "unused_entry_point"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%5 = OpVariable %2 StorageBuffer
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-)");
-}
-
-TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
-  // var<uniform_constant> a : texture_storage_2d<r32uint, write>;
-
-  auto* type =
-      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
-                         ast::Access::kWrite);
-
-  auto* var_a = Global("a", type,
-                       ast::AttributeList{
-                           create<ast::BindingAttribute>(0),
-                           create<ast::GroupAttribute>(0),
-                       });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 2D 0 0 0 2 R32ui
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-)");
-}
-
-// Check that multiple texture_storage types with different access modifiers
-// only produces a single OpTypeImage.
-// Test disabled as storage textures currently only support 'write' access. In
-// the future we'll likely support read_write.
-TEST_F(BuilderTest, DISABLED_GlobalVar_TextureStorageWithDifferentAccess) {
-  // var<uniform_constant> a : texture_storage_2d<r32uint, read_write>;
-  // var<uniform_constant> b : texture_storage_2d<r32uint, write>;
-
-  auto* type_a =
-      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
-                         ast::Access::kReadWrite);
-  auto* var_a = Global("a", type_a, ast::StorageClass::kNone,
-                       ast::AttributeList{
-                           create<ast::BindingAttribute>(0),
-                           create<ast::GroupAttribute>(0),
-                       });
-
-  auto* type_b =
-      ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
-                         ast::Access::kWrite);
-  auto* var_b = Global("b", type_b, ast::StorageClass::kNone,
-                       ast::AttributeList{
-                           create<ast::BindingAttribute>(1),
-                           create<ast::GroupAttribute>(0),
-                       });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-  EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable
-OpDecorate %1 Binding 0
-OpDecorate %1 DescriptorSet 0
-OpDecorate %5 NonReadable
-OpDecorate %5 Binding 1
-OpDecorate %5 DescriptorSet 0
-)");
-  // There must only be one OpTypeImage declaration with the same
-  // arguments
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
-%3 = OpTypeImage %4 2D 0 0 0 2 R32ui
-%2 = OpTypePointer UniformConstant %3
-%1 = OpVariable %2 UniformConstant
-%6 = OpTypePointer UniformConstant %3
-%5 = OpVariable %6 UniformConstant
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_ident_expression_test.cc b/src/writer/spirv/builder_ident_expression_test.cc
deleted file mode 100644
index 2b8299e..0000000
--- a/src/writer/spirv/builder_ident_expression_test.cc
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, IdentifierExpression_GlobalConst) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* v = GlobalConst("var", ty.vec3<f32>(), init);
-
-  auto* expr = Expr("var");
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-
-  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
-}
-
-TEST_F(BuilderTest, IdentifierExpression_GlobalVar) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Expr("var");
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-)");
-
-  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
-}
-
-TEST_F(BuilderTest, IdentifierExpression_FunctionConst) {
-  auto* init = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* v = Const("var", ty.vec3<f32>(), init);
-
-  auto* expr = Expr("var");
-  WrapInFunction(v, expr);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-
-  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
-}
-
-TEST_F(BuilderTest, IdentifierExpression_FunctionVar) {
-  auto* v = Var("var", ty.f32(), ast::StorageClass::kFunction);
-  auto* expr = Expr("var");
-  WrapInFunction(v, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
-)");
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-)");
-
-  const auto& func = b.functions()[0];
-  EXPECT_EQ(DumpInstructions(func.variables()),
-            R"(%1 = OpVariable %2 Function %4
-)");
-
-  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
-}
-
-TEST_F(BuilderTest, IdentifierExpression_Load) {
-  auto* var = Global("var", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* expr = Add("var", "var");
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 7u)
-      << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpLoad %3 %1
-%6 = OpLoad %3 %1
-%7 = OpIAdd %3 %5 %6
-)");
-}
-
-TEST_F(BuilderTest, IdentifierExpression_NoLoadConst) {
-  auto* var = GlobalConst("var", ty.i32(), Expr(2));
-
-  auto* expr = Add("var", "var");
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 3u)
-      << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstant %1 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%3 = OpIAdd %1 %2 %2
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_if_test.cc b/src/writer/spirv/builder_if_test.cc
deleted file mode 100644
index f03d373..0000000
--- a/src/writer/spirv/builder_if_test.cc
+++ /dev/null
@@ -1,673 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, If_Empty) {
-  // if (true) {
-  // }
-  auto* expr = If(true, Block());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantTrue %1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %3 None
-OpBranchConditional %2 %4 %3
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_Empty_OutsideFunction_IsError) {
-  // Outside a function.
-  // if (true) {
-  // }
-
-  auto* block = Block();
-  auto* expr = If(true, block);
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_TRUE(b.has_error());
-  EXPECT_EQ(b.error(),
-            "Internal error: trying to add SPIR-V instruction 247 outside a "
-            "function");
-}
-
-TEST_F(BuilderTest, If_WithStatements) {
-  // if (true) {
-  //   v = 2;
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2));
-  auto* expr = If(true, body);
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%9 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %7
-%8 = OpLabel
-OpStore %1 %9
-OpBranch %7
-%7 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithElse) {
-  // if (true) {
-  //   v = 2;
-  // } else {
-  //   v = 3;
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2));
-  auto* else_body = Block(Assign("v", 3));
-
-  auto* expr = If(true, body, Else(else_body));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%10 = OpConstant %3 2
-%11 = OpConstant %3 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpStore %1 %10
-OpBranch %7
-%9 = OpLabel
-OpStore %1 %11
-OpBranch %7
-%7 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithElseIf) {
-  // if (true) {
-  //   v = 2;
-  // } else if (true) {
-  //   v = 3;
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2));
-  auto* else_body = Block(Assign("v", 3));
-
-  auto* expr = If(true, body, Else(true, else_body));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%10 = OpConstant %3 2
-%13 = OpConstant %3 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpStore %1 %10
-OpBranch %7
-%9 = OpLabel
-OpSelectionMerge %11 None
-OpBranchConditional %6 %12 %11
-%12 = OpLabel
-OpStore %1 %13
-OpBranch %11
-%11 = OpLabel
-OpBranch %7
-%7 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithMultiple) {
-  // if (true) {
-  //   v = 2;
-  // } else if (true) {
-  //   v = 3;
-  // } else if (false) {
-  //   v = 4;
-  // } else {
-  //   v = 5;
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2));
-  auto* elseif_1_body = Block(Assign("v", 3));
-  auto* elseif_2_body = Block(Assign("v", 4));
-  auto* else_body = Block(Assign("v", 5));
-
-  auto* expr = If(true, body,                  //
-                  Else(true, elseif_1_body),   //
-                  Else(false, elseif_2_body),  //
-                  Else(else_body));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%10 = OpConstant %3 2
-%14 = OpConstant %3 3
-%15 = OpConstantFalse %5
-%19 = OpConstant %3 4
-%20 = OpConstant %3 5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpStore %1 %10
-OpBranch %7
-%9 = OpLabel
-OpSelectionMerge %11 None
-OpBranchConditional %6 %12 %13
-%12 = OpLabel
-OpStore %1 %14
-OpBranch %11
-%13 = OpLabel
-OpSelectionMerge %16 None
-OpBranchConditional %15 %17 %18
-%17 = OpLabel
-OpStore %1 %19
-OpBranch %16
-%18 = OpLabel
-OpStore %1 %20
-OpBranch %16
-%16 = OpLabel
-OpBranch %11
-%11 = OpLabel
-OpBranch %7
-%7 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithBreak) {
-  // loop {
-  //   if (true) {
-  //     break;
-  //   }
-  // }
-
-  auto* if_body = Block(Break());
-
-  auto* if_stmt = If(true, if_body);
-
-  auto* loop_body = Block(if_stmt);
-
-  auto* expr = Loop(loop_body, Block());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %7
-%8 = OpLabel
-OpBranch %2
-%7 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithElseBreak) {
-  // loop {
-  //   if (true) {
-  //   } else {
-  //     break;
-  //   }
-  // }
-  auto* else_body = Block(Break());
-
-  auto* if_stmt = If(true, Block(), Else(else_body));
-
-  auto* loop_body = Block(if_stmt);
-
-  auto* expr = Loop(loop_body, Block());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpBranch %7
-%9 = OpLabel
-OpBranch %2
-%7 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithContinueAndBreak) {
-  // loop {
-  //   if (true) {
-  //     continue;
-  //   } else {
-  //     break;
-  //   }
-  // }
-
-  auto* if_stmt = If(true, Block(Continue()), Else(Block(Break())));
-
-  auto* expr = Loop(Block(if_stmt), Block());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpBranch %3
-%9 = OpLabel
-OpBranch %2
-%7 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithElseContinue) {
-  // loop {
-  //   if (true) {
-  //   } else {
-  //     continue;
-  //   }
-  //   break;
-  // }
-  auto* else_body = Block(create<ast::ContinueStatement>());
-
-  auto* if_stmt = If(true, Block(), Else(else_body));
-
-  auto* loop_body = Block(if_stmt, Break());
-
-  auto* expr = Loop(loop_body, Block());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpBranch %7
-%9 = OpLabel
-OpBranch %3
-%7 = OpLabel
-OpBranch %2
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, If_WithReturn) {
-  // if (true) {
-  //   return;
-  // }
-
-  auto* fn = Func("f", {}, ty.void_(), {If(true, Block(Return()))});
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %7
-%8 = OpLabel
-OpReturn
-%7 = OpLabel
-OpReturn
-)");
-}
-
-TEST_F(BuilderTest, If_WithReturnValue) {
-  // if (true) {
-  //   return false;
-  // }
-  // return true;
-
-  auto* fn = Func("f", {}, ty.bool_(),
-                  {
-                      If(true, Block(Return(false))),
-                      Return(true),
-                  });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeFunction %2
-%5 = OpConstantTrue %2
-%8 = OpConstantFalse %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %6 None
-OpBranchConditional %5 %7 %6
-%7 = OpLabel
-OpReturnValue %8
-%6 = OpLabel
-OpReturnValue %5
-)");
-}
-
-TEST_F(BuilderTest, IfElse_BothReturn) {
-  // if (true) {
-  //   return true;
-  // } else {
-  //   return true;
-  // }
-
-  auto* fn = Func("f", {}, ty.bool_(),
-                  {
-                      If(true,                 //
-                         Block(Return(true)),  //
-                         Else(Block(Return(true)))),
-                  });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeFunction %2
-%5 = OpConstantTrue %2
-%9 = OpConstantNull %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %6 None
-OpBranchConditional %5 %7 %8
-%7 = OpLabel
-OpReturnValue %5
-%8 = OpLabel
-OpReturnValue %5
-%6 = OpLabel
-OpReturnValue %9
-)");
-}
-
-TEST_F(BuilderTest, If_WithNestedBlockReturnValue) {
-  // if (true) {
-  //  {
-  //    {
-  //      {
-  //        return false;
-  //      }
-  //    }
-  //  }
-  // }
-  // return true;
-
-  auto* fn = Func("f", {}, ty.bool_(),
-                  {
-                      If(true, Block(Block(Block(Block(Return(false)))))),
-                      Return(true),
-                  });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%1 = OpTypeFunction %2
-%5 = OpConstantTrue %2
-%8 = OpConstantFalse %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %6 None
-OpBranchConditional %5 %7 %6
-%7 = OpLabel
-OpReturnValue %8
-%6 = OpLabel
-OpReturnValue %5
-)");
-}
-
-TEST_F(BuilderTest, If_WithLoad_Bug327) {
-  // var a : bool;
-  // if (a) {
-  // }
-
-  auto* var = Global("a", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* fn = Func("f", {}, ty.void_(), {If("a", Block())});
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%5 = OpTypeFunction %6
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%9 = OpLoad %3 %1
-OpSelectionMerge %10 None
-OpBranchConditional %9 %11 %10
-%11 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpReturn
-)");
-}
-
-TEST_F(BuilderTest, If_ElseIf_WithReturn) {
-  // crbug.com/tint/1315
-  // if (false) {
-  // } else if (true) {
-  //   return;
-  // }
-
-  auto* if_stmt = If(false, Block(), Else(true, Block(Return())));
-  auto* fn = Func("f", {}, ty.void_(), {if_stmt});
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = OpTypeBool
-%6 = OpConstantFalse %5
-%10 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %9
-%8 = OpLabel
-OpBranch %7
-%9 = OpLabel
-OpSelectionMerge %11 None
-OpBranchConditional %10 %12 %11
-%12 = OpLabel
-OpReturn
-%11 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpReturn
-)");
-}
-
-TEST_F(BuilderTest, Loop_If_ElseIf_WithBreak) {
-  // crbug.com/tint/1315
-  // loop {
-  //   if (false) {
-  //   } else if (true) {
-  //     break;
-  //   }
-  // }
-
-  auto* if_stmt = If(false, Block(), Else(true, Block(Break())));
-  auto* fn = Func("f", {}, ty.void_(), {Loop(Block(if_stmt))});
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%9 = OpTypeBool
-%10 = OpConstantFalse %9
-%14 = OpConstantTrue %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %5
-%5 = OpLabel
-OpLoopMerge %6 %7 None
-OpBranch %8
-%8 = OpLabel
-OpSelectionMerge %11 None
-OpBranchConditional %10 %12 %13
-%12 = OpLabel
-OpBranch %11
-%13 = OpLabel
-OpSelectionMerge %15 None
-OpBranchConditional %14 %16 %15
-%16 = OpLabel
-OpBranch %6
-%15 = OpLabel
-OpBranch %11
-%11 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpBranch %5
-%6 = OpLabel
-OpReturn
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_literal_test.cc b/src/writer/spirv/builder_literal_test.cc
deleted file mode 100644
index ee72713..0000000
--- a/src/writer/spirv/builder_literal_test.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Literal_Bool_True) {
-  auto* b_true = create<ast::BoolLiteralExpression>(true);
-  WrapInFunction(b_true);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateLiteralIfNeeded(nullptr, b_true);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(2u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantTrue %1
-)");
-}
-
-TEST_F(BuilderTest, Literal_Bool_False) {
-  auto* b_false = create<ast::BoolLiteralExpression>(false);
-  WrapInFunction(b_false);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateLiteralIfNeeded(nullptr, b_false);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(2u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantFalse %1
-)");
-}
-
-TEST_F(BuilderTest, Literal_Bool_Dedup) {
-  auto* b_true = create<ast::BoolLiteralExpression>(true);
-  auto* b_false = create<ast::BoolLiteralExpression>(false);
-  WrapInFunction(b_true, b_false);
-
-  spirv::Builder& b = Build();
-
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_true), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_false), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, b_true), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
-%2 = OpConstantTrue %1
-%3 = OpConstantFalse %1
-)");
-}
-
-TEST_F(BuilderTest, Literal_I32) {
-  auto* i = create<ast::SintLiteralExpression>(-23);
-  WrapInFunction(i);
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(2u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstant %1 -23
-)");
-}
-
-TEST_F(BuilderTest, Literal_I32_Dedup) {
-  auto* i1 = create<ast::SintLiteralExpression>(-23);
-  auto* i2 = create<ast::SintLiteralExpression>(-23);
-  WrapInFunction(i1, i2);
-
-  spirv::Builder& b = Build();
-
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
-%2 = OpConstant %1 -23
-)");
-}
-
-TEST_F(BuilderTest, Literal_U32) {
-  auto* i = create<ast::UintLiteralExpression>(23);
-  WrapInFunction(i);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(2u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstant %1 23
-)");
-}
-
-TEST_F(BuilderTest, Literal_U32_Dedup) {
-  auto* i1 = create<ast::UintLiteralExpression>(23);
-  auto* i2 = create<ast::UintLiteralExpression>(23);
-  WrapInFunction(i1, i2);
-
-  spirv::Builder& b = Build();
-
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
-%2 = OpConstant %1 23
-)");
-}
-
-TEST_F(BuilderTest, Literal_F32) {
-  auto* i = create<ast::FloatLiteralExpression>(23.245f);
-  WrapInFunction(i);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateLiteralIfNeeded(nullptr, i);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(2u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 23.2450008
-)");
-}
-
-TEST_F(BuilderTest, Literal_F32_Dedup) {
-  auto* i1 = create<ast::FloatLiteralExpression>(23.245f);
-  auto* i2 = create<ast::FloatLiteralExpression>(23.245f);
-  WrapInFunction(i1, i2);
-
-  spirv::Builder& b = Build();
-
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i1), 0u);
-  ASSERT_NE(b.GenerateLiteralIfNeeded(nullptr, i2), 0u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
-%2 = OpConstant %1 23.2450008
-)");
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_loop_test.cc b/src/writer/spirv/builder_loop_test.cc
deleted file mode 100644
index 93b1a02..0000000
--- a/src/writer/spirv/builder_loop_test.cc
+++ /dev/null
@@ -1,495 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Loop_Empty) {
-  // loop {
-  //   break;
-  // }
-
-  auto* loop = Loop(Block(Break()), Block());
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %2
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithoutContinuing) {
-  // loop {
-  //   v = 2;
-  //   break;
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2),  //
-                     Break());
-
-  auto* loop = Loop(body, Block());
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%9 = OpConstant %3 2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %5
-%5 = OpLabel
-OpLoopMerge %6 %7 None
-OpBranch %8
-%8 = OpLabel
-OpStore %1 %9
-OpBranch %6
-%7 = OpLabel
-OpBranch %5
-%6 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing) {
-  // loop {
-  //   a = 2;
-  //   break;
-  //   continuing {
-  //     a = 3;
-  //   }
-  // }
-
-  auto* var = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* body = Block(Assign("v", 2),  //
-                     Break());
-  auto* continuing = Block(Assign("v", 3));
-
-  auto* loop = Loop(body, continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%9 = OpConstant %3 2
-%10 = OpConstant %3 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %5
-%5 = OpLabel
-OpLoopMerge %6 %7 None
-OpBranch %8
-%8 = OpLabel
-OpStore %1 %9
-OpBranch %6
-%7 = OpLabel
-OpStore %1 %10
-OpBranch %5
-%6 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithBodyVariableAccessInContinuing) {
-  // loop {
-  //   var a : i32;
-  //   break;
-  //   continuing {
-  //     a = 3;
-  //   }
-  // }
-
-  auto* body = Block(Decl(Var("a", ty.i32())),  //
-                     Break());
-  auto* continuing = Block(Assign("a", 3));
-
-  auto* loop = Loop(body, continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%8 = OpConstantNull %7
-%9 = OpConstant %7 3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %2
-%3 = OpLabel
-OpStore %5 %9
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinue) {
-  // loop {
-  //   if (false) { break; }
-  //   continue;
-  // }
-  auto* body = Block(If(false, Block(Break())),  //
-                     Continue());
-  auto* loop = Loop(body, Block());
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpSelectionMerge %7 None
-OpBranchConditional %6 %8 %7
-%8 = OpLabel
-OpBranch %2
-%7 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithBreak) {
-  // loop {
-  //   break;
-  // }
-  auto* body = Block(create<ast::BreakStatement>());
-  auto* loop = Loop(body, Block());
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %2
-%3 = OpLabel
-OpBranch %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakIf) {
-  // loop {
-  //   continuing {
-  //     if (true) { break; }
-  //   }
-  // }
-
-  auto* if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
-                                           ast::ElseStatementList{});
-  auto* continuing = Block(if_stmt);
-  auto* loop = Loop(Block(), continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranchConditional %6 %2 %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless) {
-  // loop {
-  //   continuing {
-  //     if (true) {} else { break; }
-  //   }
-  // }
-  auto* if_stmt = create<ast::IfStatement>(
-      Expr(true), Block(),
-      ast::ElseStatementList{Else(nullptr, Block(Break()))});
-  auto* continuing = Block(if_stmt);
-  auto* loop = Loop(Block(), continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranchConditional %6 %1 %2
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_ConditionIsVar) {
-  // loop {
-  //   continuing {
-  //     var cond = true;
-  //     if (cond) { break; }
-  //   }
-  // }
-
-  auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
-  auto* if_stmt = If(Expr("cond"), Block(Break()), ast::ElseStatementList{});
-  auto* continuing = Block(cond_var, if_stmt);
-  auto* loop = Loop(Block(), continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%8 = OpTypePointer Function %5
-%9 = OpConstantNull %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpStore %7 %6
-%10 = OpLoad %5 %7
-OpBranchConditional %10 %2 %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless_ConditionIsVar) {
-  // loop {
-  //   continuing {
-  //     var cond = true;
-  //     if (cond) {} else { break; }
-  //   }
-  // }
-  auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
-  auto* if_stmt = If(Expr("cond"), Block(),
-                     ast::ElseStatementList{Else(nullptr, Block(Break()))});
-  auto* continuing = Block(cond_var, if_stmt);
-  auto* loop = Loop(Block(), continuing);
-  WrapInFunction(loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
-%8 = OpTypePointer Function %5
-%9 = OpConstantNull %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpStore %7 %6
-%10 = OpLoad %5 %7
-OpBranchConditional %10 %1 %2
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_Nested) {
-  // Make sure the right backedge and break target are used.
-  // loop {
-  //   continuing {
-  //     loop {
-  //       continuing {
-  //         if (true) { break; }
-  //       }
-  //     }
-  //     if (true) { break; }
-  //   }
-  // }
-
-  auto* inner_if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
-                                                 ast::ElseStatementList{});
-  auto* inner_continuing = Block(inner_if_stmt);
-  auto* inner_loop = Loop(Block(), inner_continuing);
-
-  auto* outer_if_stmt = create<ast::IfStatement>(Expr(true), Block(Break()),
-                                                 ast::ElseStatementList{});
-  auto* outer_continuing = Block(inner_loop, outer_if_stmt);
-  auto* outer_loop = Loop(Block(), outer_continuing);
-
-  WrapInFunction(outer_loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeBool
-%10 = OpConstantTrue %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %5
-%5 = OpLabel
-OpLoopMerge %6 %7 None
-OpBranch %8
-%8 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpBranchConditional %10 %6 %5
-%6 = OpLabel
-OpBranchConditional %10 %2 %1
-%2 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless_Nested) {
-  // Make sure the right backedge and break target are used.
-  // loop {
-  //   continuing {
-  //     loop {
-  //       continuing {
-  //         if (true) {} else { break; }
-  //       }
-  //     }
-  //     if (true) {} else { break; }
-  //   }
-  // }
-
-  auto* inner_if_stmt = create<ast::IfStatement>(
-      Expr(true), Block(),
-      ast::ElseStatementList{Else(nullptr, Block(Break()))});
-  auto* inner_continuing = Block(inner_if_stmt);
-  auto* inner_loop = Loop(Block(), inner_continuing);
-
-  auto* outer_if_stmt = create<ast::IfStatement>(
-      Expr(true), Block(),
-      ast::ElseStatementList{Else(nullptr, Block(Break()))});
-  auto* outer_continuing = Block(inner_loop, outer_if_stmt);
-  auto* outer_loop = Loop(Block(), outer_continuing);
-
-  WrapInFunction(outer_loop);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeBool
-%10 = OpConstantTrue %9
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpBranch %1
-%1 = OpLabel
-OpLoopMerge %2 %3 None
-OpBranch %4
-%4 = OpLabel
-OpBranch %3
-%3 = OpLabel
-OpBranch %5
-%5 = OpLabel
-OpLoopMerge %6 %7 None
-OpBranch %8
-%8 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpBranchConditional %10 %5 %6
-%6 = OpLabel
-OpBranchConditional %10 %1 %2
-%2 = OpLabel
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_return_test.cc b/src/writer/spirv/builder_return_test.cc
deleted file mode 100644
index a89a59a..0000000
--- a/src/writer/spirv/builder_return_test.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Return) {
-  auto* ret = Return();
-  WrapInFunction(ret);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateReturnStatement(ret));
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
-)");
-}
-
-TEST_F(BuilderTest, Return_WithValue) {
-  auto* val = vec3<f32>(1.f, 1.f, 3.f);
-
-  auto* ret = Return(val);
-  Func("test", {}, ty.vec3<f32>(), {ret}, {});
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateReturnStatement(ret));
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%5 = OpConstantComposite %1 %3 %3 %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpReturnValue %5
-)");
-}
-
-TEST_F(BuilderTest, Return_WithValue_GeneratesLoad) {
-  auto* var = Var("param", ty.f32());
-
-  auto* ret = Return(var);
-  Func("test", {}, ty.f32(), {Decl(var), ret}, {});
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  EXPECT_TRUE(b.GenerateReturnStatement(ret)) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Function %3
-%4 = OpConstantNull %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %4
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpLoad %3 %1
-OpReturnValue %5
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_switch_test.cc b/src/writer/spirv/builder_switch_test.cc
deleted file mode 100644
index 93e97a2..0000000
--- a/src/writer/spirv/builder_switch_test.cc
+++ /dev/null
@@ -1,452 +0,0 @@
-// Copyright 2020 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/ast/fallthrough_statement.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, Switch_Empty) {
-  // switch (1) {
-  //   default: {}
-  // }
-
-  auto* expr = Switch(1, DefaultCase());
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-
-  EXPECT_TRUE(b.GenerateSwitchStatement(expr)) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(OpSelectionMerge %1 None
-OpSwitch %3 %4
-%4 = OpLabel
-OpBranch %1
-%1 = OpLabel
-)");
-}
-
-TEST_F(BuilderTest, Switch_WithCase) {
-  // switch(a) {
-  //   case 1:
-  //     v = 1;
-  //   case 2:
-  //     v = 2;
-  //   default: {}
-  // }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Switch("a",                                   //
-                               Case(Expr(1), Block(Assign("v", 1))),  //
-                               Case(Expr(2), Block(Assign("v", 2))),  //
-                               DefaultCase()),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%15 = OpConstant %3 1
-%16 = OpConstant %3 2
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12 1 %13 2 %14
-%13 = OpLabel
-OpStore %1 %15
-OpBranch %10
-%14 = OpLabel
-OpStore %1 %16
-OpBranch %10
-%12 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_WithCase_Unsigned) {
-  // switch(a) {
-  //   case 1u:
-  //     v = 1;
-  //   case 2u:
-  //     v = 2;
-  //   default: {}
-  // }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.u32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Switch("a",                                    //
-                               Case(Expr(1u), Block(Assign("v", 1))),  //
-                               Case(Expr(2u), Block(Assign("v", 2))),  //
-                               DefaultCase()),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %11 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%7 = OpTypeInt 32 0
-%6 = OpTypePointer Private %7
-%8 = OpConstantNull %7
-%5 = OpVariable %6 Private %8
-%10 = OpTypeVoid
-%9 = OpTypeFunction %10
-%18 = OpConstant %3 1
-%19 = OpConstant %3 2
-%11 = OpFunction %10 None %9
-%12 = OpLabel
-%14 = OpLoad %7 %5
-OpSelectionMerge %13 None
-OpSwitch %14 %15 1 %16 2 %17
-%16 = OpLabel
-OpStore %1 %18
-OpBranch %13
-%17 = OpLabel
-OpStore %1 %19
-OpBranch %13
-%15 = OpLabel
-OpBranch %13
-%13 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_WithDefault) {
-  // switch(true) {
-  //   default: {}
-  //     v = 1;
-  //  }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Switch("a",                                  //
-                               DefaultCase(Block(Assign("v", 1)))),  //
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%13 = OpConstant %3 1
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12
-%12 = OpLabel
-OpStore %1 %13
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_WithCaseAndDefault) {
-  // switch(a) {
-  //   case 1:
-  //      v = 1;
-  //   case 2, 3:
-  //      v = 2;
-  //   default: {}
-  //      v = 3;
-  //  }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Switch(Expr("a"),                    //
-                               Case(Expr(1),                 //
-                                    Block(Assign("v", 1))),  //
-                               Case({Expr(2), Expr(3)},      //
-                                    Block(Assign("v", 2))),  //
-                               DefaultCase(Block(Assign("v", 3)))),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%15 = OpConstant %3 1
-%16 = OpConstant %3 2
-%17 = OpConstant %3 3
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12 1 %13 2 %14 3 %14
-%13 = OpLabel
-OpStore %1 %15
-OpBranch %10
-%14 = OpLabel
-OpStore %1 %16
-OpBranch %10
-%12 = OpLabel
-OpStore %1 %17
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
-  // switch(a) {
-  //   case 1:
-  //      v = 1;
-  //      fallthrough;
-  //   case 2:
-  //      v = 2;
-  //   default: {}
-  //      v = 3;
-  //  }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func("a_func", {}, ty.void_(),
-                    {
-                        Switch(Expr("a"),                                   //
-                               Case(Expr(1),                                //
-                                    Block(Assign("v", 1), Fallthrough())),  //
-                               Case(Expr(2),                                //
-                                    Block(Assign("v", 2))),                 //
-                               DefaultCase(Block(Assign("v", 3)))),
-                    });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%15 = OpConstant %3 1
-%16 = OpConstant %3 2
-%17 = OpConstant %3 3
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12 1 %13 2 %14
-%13 = OpLabel
-OpStore %1 %15
-OpBranch %14
-%14 = OpLabel
-OpStore %1 %16
-OpBranch %10
-%12 = OpLabel
-OpStore %1 %17
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_WithNestedBreak) {
-  // switch (a) {
-  //   case 1:
-  //     if (true) {
-  //       break;
-  //     }
-  //     v = 1;
-  //   default: {}
-  // }
-
-  auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
-  auto* a = Global("a", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* func = Func(
-      "a_func", {}, ty.void_(),
-      {
-          Switch("a",           //
-                 Case(Expr(1),  //
-                      Block(    //
-                          If(Expr(true), Block(create<ast::BreakStatement>())),
-                          Assign("v", 1))),
-                 DefaultCase()),
-      });
-
-  spirv::Builder& b = Build();
-
-  ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-  ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%14 = OpTypeBool
-%15 = OpConstantTrue %14
-%18 = OpConstant %3 1
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12 1 %13
-%13 = OpLabel
-OpSelectionMerge %16 None
-OpBranchConditional %15 %17 %16
-%17 = OpLabel
-OpBranch %10
-%16 = OpLabel
-OpStore %1 %18
-OpBranch %10
-%12 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(BuilderTest, Switch_AllReturn) {
-  // switch (1) {
-  //   case 1: {
-  //     return 1;
-  //   }
-  //   case 2: {
-  //     fallthrough;
-  //   }
-  //   default: {
-  //     return 3;
-  //   }
-  // }
-
-  auto* fn = Func("f", {}, ty.i32(),
-                  {
-                      Switch(1,                                    //
-                             Case(Expr(1), Block(Return(1))),      //
-                             Case(Expr(2), Block(Fallthrough())),  //
-                             DefaultCase(Block(Return(3)))),
-                  });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-  EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "f"
-%2 = OpTypeInt 32 1
-%1 = OpTypeFunction %2
-%6 = OpConstant %2 1
-%10 = OpConstant %2 3
-%11 = OpConstantNull %2
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpSwitch %6 %7 1 %8 2 %9
-%8 = OpLabel
-OpReturnValue %6
-%9 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpReturnValue %10
-%5 = OpLabel
-OpReturnValue %11
-OpFunctionEnd
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_test.cc b/src/writer/spirv/builder_test.cc
deleted file mode 100644
index 977cdf3..0000000
--- a/src/writer/spirv/builder_test.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, TracksIdBounds) {
-  spirv::Builder& b = Build();
-
-  for (size_t i = 0; i < 5; i++) {
-    EXPECT_EQ(b.next_id(), i + 1);
-  }
-
-  EXPECT_EQ(6u, b.id_bound());
-}
-
-TEST_F(BuilderTest, Capabilities_Dedup) {
-  spirv::Builder& b = Build();
-
-  b.push_capability(SpvCapabilityShader);
-  b.push_capability(SpvCapabilityShader);
-  b.push_capability(SpvCapabilityShader);
-
-  EXPECT_EQ(DumpInstructions(b.capabilities()), "OpCapability Shader\n");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
deleted file mode 100644
index 19c5fa5..0000000
--- a/src/writer/spirv/builder_type_test.cc
+++ /dev/null
@@ -1,968 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest_Type = TestHelper;
-
-TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
-  auto* ary = ty.array(ty.i32());
-  auto* str =
-      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
-  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeRuntimeArray %2
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
-  auto* ary = ty.array(ty.i32());
-  auto* str =
-      Structure("S", {Member("x", ary)}, {create<ast::StructBlockAttribute>()});
-  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeRuntimeArray %2
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateArray) {
-  auto* ary = ty.array(ty.i32(), 4);
-  Global("a", ary, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 4
-%1 = OpTypeArray %2 %4
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
-  auto* ary = ty.array(ty.i32(), 4, 16u);
-  Global("a", ary, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id);
-
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 ArrayStride 16
-)");
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 4
-%1 = OpTypeArray %2 %4
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
-  auto* ary = ty.array(ty.i32(), 4);
-  Global("a", ary, ast::StorageClass::kPrivate);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpTypeInt 32 0
-%4 = OpConstant %3 4
-%1 = OpTypeArray %2 %4
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateBool) {
-  auto* bool_ = create<sem::Bool>();
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(bool_);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  ASSERT_EQ(b.types().size(), 1u);
-  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeBool
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedBool) {
-  auto* bool_ = create<sem::Bool>();
-  auto* i32 = create<sem::I32>();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GenerateF32) {
-  auto* f32 = create<sem::F32>();
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(f32);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  ASSERT_EQ(b.types().size(), 1u);
-  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeFloat 32
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedF32) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GenerateI32) {
-  auto* i32 = create<sem::I32>();
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(i32);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  ASSERT_EQ(b.types().size(), 1u);
-  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 1
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedI32) {
-  auto* f32 = create<sem::F32>();
-  auto* i32 = create<sem::I32>();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GenerateMatrix) {
-  auto* f32 = create<sem::F32>();
-  auto* vec3 = create<sem::Vector>(f32, 3);
-  auto* mat2x3 = create<sem::Matrix>(vec3, 2);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(mat2x3);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(b.types().size(), 3u);
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 3
-%1 = OpTypeMatrix %2 2
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
-  auto* i32 = create<sem::I32>();
-  auto* col = create<sem::Vector>(i32, 4);
-  auto* mat = create<sem::Matrix>(col, 3);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 3u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GeneratePtr) {
-  auto* i32 = create<sem::I32>();
-  auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
-                                   ast::Access::kReadWrite);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(ptr);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypePointer Output %2
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedPtr) {
-  auto* i32 = create<sem::I32>();
-  auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
-                                   ast::Access::kReadWrite);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ptr), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ptr), 1u);
-}
-
-TEST_F(BuilderTest_Type, GenerateStruct) {
-  auto* s = Structure("my_struct", {Member("a", ty.f32())});
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeStruct %2
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_struct"
-OpMemberName %1 0 "a"
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.f32(), {MemberAlign(8)}),
-                           });
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeStruct %2 %2
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
-OpMemberName %1 0 "a"
-OpMemberName %1 1 "b"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 1 Offset 8
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateStruct_NonLayout_Matrix) {
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<f32>()),
-                               Member("b", ty.mat2x3<f32>()),
-                               Member("c", ty.mat4x4<f32>()),
-                           });
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 2
-%2 = OpTypeMatrix %3 2
-%6 = OpTypeVector %4 3
-%5 = OpTypeMatrix %6 2
-%8 = OpTypeVector %4 4
-%7 = OpTypeMatrix %8 4
-%1 = OpTypeStruct %2 %5 %7
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
-OpMemberName %1 0 "a"
-OpMemberName %1 1 "b"
-OpMemberName %1 2 "c"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 0 ColMajor
-OpMemberDecorate %1 0 MatrixStride 8
-OpMemberDecorate %1 1 Offset 16
-OpMemberDecorate %1 1 ColMajor
-OpMemberDecorate %1 1 MatrixStride 16
-OpMemberDecorate %1 2 Offset 48
-OpMemberDecorate %1 2 ColMajor
-OpMemberDecorate %1 2 MatrixStride 16
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutMatrix) {
-  // We have to infer layout for matrix when it also has an offset.
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<f32>()),
-                               Member("b", ty.mat2x3<f32>()),
-                               Member("c", ty.mat4x4<f32>()),
-                           });
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 2
-%2 = OpTypeMatrix %3 2
-%6 = OpTypeVector %4 3
-%5 = OpTypeMatrix %6 2
-%8 = OpTypeVector %4 4
-%7 = OpTypeMatrix %8 4
-%1 = OpTypeStruct %2 %5 %7
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
-OpMemberName %1 0 "a"
-OpMemberName %1 1 "b"
-OpMemberName %1 2 "c"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 0 ColMajor
-OpMemberDecorate %1 0 MatrixStride 8
-OpMemberDecorate %1 1 Offset 16
-OpMemberDecorate %1 1 ColMajor
-OpMemberDecorate %1 1 MatrixStride 16
-OpMemberDecorate %1 2 Offset 48
-OpMemberDecorate %1 2 ColMajor
-OpMemberDecorate %1 2 MatrixStride 16
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutArraysOfMatrix) {
-  // We have to infer layout for matrix when it also has an offset.
-  // The decoration goes on the struct member, even if the matrix is buried
-  // in levels of arrays.
-  auto* arr_mat2x2 = ty.array(ty.mat2x2<f32>(), 1);      // Singly nested array
-  auto* arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
-  auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>());       // Runtime array
-
-  auto* s = Structure("S", {
-                               Member("a", arr_mat2x2),
-                               Member("b", arr_arr_mat2x3),
-                               Member("c", rtarr_mat4x4),
-                           });
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 2
-%3 = OpTypeMatrix %4 2
-%6 = OpTypeInt 32 0
-%7 = OpConstant %6 1
-%2 = OpTypeArray %3 %7
-%10 = OpTypeVector %5 3
-%9 = OpTypeMatrix %10 2
-%8 = OpTypeArray %9 %7
-%13 = OpTypeVector %5 4
-%12 = OpTypeMatrix %13 4
-%11 = OpTypeRuntimeArray %12
-%1 = OpTypeStruct %2 %8 %11
-)");
-  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
-OpMemberName %1 0 "a"
-OpMemberName %1 1 "b"
-OpMemberName %1 2 "c"
-)");
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 0 ColMajor
-OpMemberDecorate %1 0 MatrixStride 8
-OpDecorate %2 ArrayStride 16
-OpMemberDecorate %1 1 Offset 16
-OpMemberDecorate %1 1 ColMajor
-OpMemberDecorate %1 1 MatrixStride 16
-OpDecorate %8 ArrayStride 32
-OpMemberDecorate %1 2 Offset 48
-OpMemberDecorate %1 2 ColMajor
-OpMemberDecorate %1 2 MatrixStride 16
-OpDecorate %11 ArrayStride 64
-)");
-}
-
-TEST_F(BuilderTest_Type, GenerateU32) {
-  auto* u32 = create<sem::U32>();
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(u32);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  ASSERT_EQ(b.types().size(), 1u);
-  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 0
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedU32) {
-  auto* u32 = create<sem::U32>();
-  auto* f32 = create<sem::F32>();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GenerateVector) {
-  auto* vec = create<sem::Vector>(create<sem::F32>(), 3);
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(vec);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  EXPECT_EQ(b.types().size(), 2u);
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 3
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedVector) {
-  auto* i32 = create<sem::I32>();
-  auto* vec = create<sem::Vector>(i32, 3);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-TEST_F(BuilderTest_Type, GenerateVoid) {
-  auto* void_ = create<sem::Void>();
-
-  spirv::Builder& b = Build();
-
-  auto id = b.GenerateTypeIfNeeded(void_);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(id, 1u);
-
-  ASSERT_EQ(b.types().size(), 1u);
-  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeVoid
-)");
-}
-
-TEST_F(BuilderTest_Type, ReturnsGeneratedVoid) {
-  auto* void_ = create<sem::Void>();
-  auto* i32 = create<sem::I32>();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-}
-
-struct PtrData {
-  ast::StorageClass ast_class;
-  SpvStorageClass result;
-};
-inline std::ostream& operator<<(std::ostream& out, PtrData data) {
-  out << data.ast_class;
-  return out;
-}
-using PtrDataTest = TestParamHelper<PtrData>;
-TEST_P(PtrDataTest, ConvertStorageClass) {
-  auto params = GetParam();
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.ConvertStorageClass(params.ast_class), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    BuilderTest_Type,
-    PtrDataTest,
-    testing::Values(
-        PtrData{ast::StorageClass::kNone, SpvStorageClassMax},
-        PtrData{ast::StorageClass::kInput, SpvStorageClassInput},
-        PtrData{ast::StorageClass::kOutput, SpvStorageClassOutput},
-        PtrData{ast::StorageClass::kUniform, SpvStorageClassUniform},
-        PtrData{ast::StorageClass::kWorkgroup, SpvStorageClassWorkgroup},
-        PtrData{ast::StorageClass::kUniformConstant,
-                SpvStorageClassUniformConstant},
-        PtrData{ast::StorageClass::kStorage, SpvStorageClassStorageBuffer},
-        PtrData{ast::StorageClass::kPrivate, SpvStorageClassPrivate},
-        PtrData{ast::StorageClass::kFunction, SpvStorageClassFunction}));
-
-TEST_F(BuilderTest_Type, DepthTexture_Generate_2d) {
-  auto* two_d = create<sem::DepthTexture>(ast::TextureDimension::k2d);
-
-  spirv::Builder& b = Build();
-
-  auto id_two_d = b.GenerateTypeIfNeeded(two_d);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id_two_d);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 1 0 0 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, DepthTexture_Generate_2dArray) {
-  auto* two_d_array =
-      create<sem::DepthTexture>(ast::TextureDimension::k2dArray);
-
-  spirv::Builder& b = Build();
-
-  auto id_two_d_array = b.GenerateTypeIfNeeded(two_d_array);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id_two_d_array);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 1 1 0 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, DepthTexture_Generate_Cube) {
-  auto* cube = create<sem::DepthTexture>(ast::TextureDimension::kCube);
-
-  spirv::Builder& b = Build();
-
-  auto id_cube = b.GenerateTypeIfNeeded(cube);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id_cube);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 Cube 1 0 0 1 Unknown
-)");
-  EXPECT_EQ(DumpInstructions(b.capabilities()), "");
-}
-
-TEST_F(BuilderTest_Type, DepthTexture_Generate_CubeArray) {
-  auto* cube_array =
-      create<sem::DepthTexture>(ast::TextureDimension::kCubeArray);
-
-  spirv::Builder& b = Build();
-
-  auto id_cube_array = b.GenerateTypeIfNeeded(cube_array);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(1u, id_cube_array);
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 Cube 1 1 0 1 Unknown
-)");
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            R"(OpCapability SampledCubeArray
-)");
-}
-
-TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_i32) {
-  auto* i32 = create<sem::I32>();
-  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, i32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(1u, b.GenerateTypeIfNeeded(ms));
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_u32) {
-  auto* u32 = create<sem::U32>();
-  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, u32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 0
-%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_f32) {
-  auto* f32 = create<sem::F32>();
-  auto* ms = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 0 1 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_i32) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d,
-                                        create<sem::I32>());
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 1
-%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
-)");
-
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            R"(OpCapability Sampled1D
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_u32) {
-  auto* u32 = create<sem::U32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, u32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeInt 32 0
-%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
-)");
-
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            R"(OpCapability Sampled1D
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_f32) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 1D 0 0 0 1 Unknown
-)");
-
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            R"(OpCapability Sampled1D
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_2d) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 0 0 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_2d_array) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k2dArray, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 1 0 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_3d) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k3d, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 3D 0 0 0 1 Unknown
-)");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_Cube) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::kCube, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 Cube 0 0 0 1 Unknown
-)");
-  EXPECT_EQ(DumpInstructions(b.capabilities()), "");
-}
-
-TEST_F(BuilderTest_Type, SampledTexture_Generate_CubeArray) {
-  auto* f32 = create<sem::F32>();
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::kCubeArray, f32);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()),
-            R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 Cube 0 1 0 1 Unknown
-)");
-  EXPECT_EQ(DumpInstructions(b.capabilities()),
-            R"(OpCapability SampledCubeArray
-)");
-}
-
-TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
-  auto* s =
-      ty.storage_texture(ast::TextureDimension::k1d,
-                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 1D 0 0 0 2 R32f
-)");
-}
-
-TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
-  auto* s =
-      ty.storage_texture(ast::TextureDimension::k2d,
-                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 0 0 2 R32f
-)");
-}
-
-TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
-  auto* s =
-      ty.storage_texture(ast::TextureDimension::k2dArray,
-                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 1 0 2 R32f
-)");
-}
-
-TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
-  auto* s =
-      ty.storage_texture(ast::TextureDimension::k3d,
-                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 3D 0 0 0 2 R32f
-)");
-}
-
-TEST_F(BuilderTest_Type,
-       StorageTexture_Generate_SampledTypeFloat_Format_r32float) {
-  auto* s =
-      ty.storage_texture(ast::TextureDimension::k2d,
-                         ast::TexelFormat::kR32Float, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeImage %2 2D 0 0 0 2 R32f
-)");
-}
-
-TEST_F(BuilderTest_Type,
-       StorageTexture_Generate_SampledTypeSint_Format_r32sint) {
-  auto* s = ty.storage_texture(ast::TextureDimension::k2d,
-                               ast::TexelFormat::kR32Sint, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%1 = OpTypeImage %2 2D 0 0 0 2 R32i
-)");
-}
-
-TEST_F(BuilderTest_Type,
-       StorageTexture_Generate_SampledTypeUint_Format_r32uint) {
-  auto* s = ty.storage_texture(ast::TextureDimension::k2d,
-                               ast::TexelFormat::kR32Uint, ast::Access::kWrite);
-
-  Global("test_var", s,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
-%1 = OpTypeImage %2 2D 0 0 0 2 R32ui
-)");
-}
-
-TEST_F(BuilderTest_Type, Sampler) {
-  sem::Sampler sampler(ast::SamplerKind::kSampler);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
-}
-
-TEST_F(BuilderTest_Type, ComparisonSampler) {
-  sem::Sampler sampler(ast::SamplerKind::kComparisonSampler);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
-}
-
-TEST_F(BuilderTest_Type, Dedup_Sampler_And_ComparisonSampler) {
-  sem::Sampler comp_sampler(ast::SamplerKind::kComparisonSampler);
-  sem::Sampler sampler(ast::SamplerKind::kSampler);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&comp_sampler), 1u);
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&sampler), 1u);
-
-  ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/builder_unary_op_expression_test.cc b/src/writer/spirv/builder_unary_op_expression_test.cc
deleted file mode 100644
index 4cc1c3d..0000000
--- a/src/writer/spirv/builder_unary_op_expression_test.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using BuilderTest = TestHelper;
-
-TEST_F(BuilderTest, UnaryOp_Negation_Integer) {
-  auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(1));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpSNegate %2 %3
-)");
-}
-
-TEST_F(BuilderTest, UnaryOp_Negation_Float) {
-  auto* expr =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(1.f));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%3 = OpConstant %2 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpFNegate %2 %3
-)");
-}
-
-TEST_F(BuilderTest, UnaryOp_Complement) {
-  auto* expr =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr(1));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
-%3 = OpConstant %2 1
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpNot %2 %3
-)");
-}
-
-TEST_F(BuilderTest, UnaryOp_Not) {
-  auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr(false));
-  WrapInFunction(expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantFalse %2
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%1 = OpLogicalNot %2 %3
-)");
-}
-
-TEST_F(BuilderTest, UnaryOp_LoadRequired) {
-  auto* var = Var("param", ty.vec3<f32>());
-
-  auto* expr =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("param"));
-  WrapInFunction(var, expr);
-
-  spirv::Builder& b = Build();
-
-  b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-  EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 6u) << b.error();
-  ASSERT_FALSE(b.has_error()) << b.error();
-
-  EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypePointer Function %3
-%5 = OpConstantNull %3
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
-            R"(%1 = OpVariable %2 Function %5
-)");
-  EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%7 = OpLoad %3 %1
-%6 = OpFNegate %3 %7
-)");
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/function.cc b/src/writer/spirv/function.cc
deleted file mode 100644
index 01da96c..0000000
--- a/src/writer/spirv/function.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 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/writer/spirv/function.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-Function::Function()
-    : declaration_(Instruction{spv::Op::OpNop, {}}),
-      label_op_(Operand::Int(0)) {}
-
-Function::Function(const Instruction& declaration,
-                   const Operand& label_op,
-                   const InstructionList& params)
-    : declaration_(declaration), label_op_(label_op), params_(params) {}
-
-Function::Function(const Function& other) = default;
-
-Function::~Function() = default;
-
-void Function::iterate(std::function<void(const Instruction&)> cb) const {
-  cb(declaration_);
-
-  for (const auto& param : params_) {
-    cb(param);
-  }
-
-  cb(Instruction{spv::Op::OpLabel, {label_op_}});
-
-  for (const auto& var : vars_) {
-    cb(var);
-  }
-  for (const auto& inst : instructions_) {
-    cb(inst);
-  }
-
-  cb(Instruction{spv::Op::OpFunctionEnd, {}});
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/function.h b/src/writer/spirv/function.h
deleted file mode 100644
index df747ec..0000000
--- a/src/writer/spirv/function.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_FUNCTION_H_
-#define SRC_WRITER_SPIRV_FUNCTION_H_
-
-#include <functional>
-
-#include "src/writer/spirv/instruction.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// A SPIR-V function
-class Function {
- public:
-  /// Constructor for testing purposes
-  /// This creates a bad declaration, so won't generate correct SPIR-V
-  Function();
-
-  /// Constructor
-  /// @param declaration the function declaration
-  /// @param label_op the operand for function's entry block label
-  /// @param params the function parameters
-  Function(const Instruction& declaration,
-           const Operand& label_op,
-           const InstructionList& params);
-  /// Copy constructor
-  /// @param other the function to copy
-  Function(const Function& other);
-  ~Function();
-
-  /// Iterates over the function call the cb on each instruction
-  /// @param cb the callback to call
-  void iterate(std::function<void(const Instruction&)> cb) const;
-
-  /// @returns the declaration
-  const Instruction& declaration() const { return declaration_; }
-
-  /// @returns the label ID for the function entry block
-  uint32_t label_id() const { return label_op_.to_i(); }
-
-  /// Adds an instruction to the instruction list
-  /// @param op the op to set
-  /// @param operands the operands for the instruction
-  void push_inst(spv::Op op, const OperandList& operands) {
-    instructions_.push_back(Instruction{op, operands});
-  }
-  /// @returns the instruction list
-  const InstructionList& instructions() const { return instructions_; }
-
-  /// Adds a variable to the variable list
-  /// @param operands the operands for the variable
-  void push_var(const OperandList& operands) {
-    vars_.push_back(Instruction{spv::Op::OpVariable, operands});
-  }
-  /// @returns the variable list
-  const InstructionList& variables() const { return vars_; }
-
-  /// @returns the word length of the function
-  uint32_t word_length() const {
-    // 1 for the Label and 1 for the FunctionEnd
-    uint32_t size = 2 + declaration_.word_length();
-
-    for (const auto& param : params_) {
-      size += param.word_length();
-    }
-    for (const auto& var : vars_) {
-      size += var.word_length();
-    }
-    for (const auto& inst : instructions_) {
-      size += inst.word_length();
-    }
-    return size;
-  }
-
- private:
-  Instruction declaration_;
-  Operand label_op_;
-  InstructionList params_;
-  InstructionList vars_;
-  InstructionList instructions_;
-};
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_FUNCTION_H_
diff --git a/src/writer/spirv/generator.cc b/src/writer/spirv/generator.cc
deleted file mode 100644
index ee477f0..0000000
--- a/src/writer/spirv/generator.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 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/writer/spirv/generator.h"
-
-#include "src/writer/spirv/binary_writer.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-Result::Result() = default;
-Result::~Result() = default;
-Result::Result(const Result&) = default;
-
-Result Generate(const Program* program, const Options& options) {
-  Result result;
-
-  // Sanitize the program.
-  auto sanitized_result = Sanitize(program, options.emit_vertex_point_size,
-                                   options.disable_workgroup_init);
-  if (!sanitized_result.program.IsValid()) {
-    result.success = false;
-    result.error = sanitized_result.program.Diagnostics().str();
-    return result;
-  }
-
-  // Generate the SPIR-V code.
-  auto builder = std::make_unique<Builder>(&sanitized_result.program);
-  auto writer = std::make_unique<BinaryWriter>();
-  if (!builder->Build()) {
-    result.success = false;
-    result.error = builder->error();
-    return result;
-  }
-
-  writer->WriteHeader(builder->id_bound());
-  writer->WriteBuilder(builder.get());
-
-  result.success = true;
-  result.spirv = writer->result();
-
-  return result;
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/generator.h b/src/writer/spirv/generator.h
deleted file mode 100644
index aaacd94..0000000
--- a/src/writer/spirv/generator.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_GENERATOR_H_
-#define SRC_WRITER_SPIRV_GENERATOR_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "src/writer/writer.h"
-
-namespace tint {
-
-// Forward declarations
-class Program;
-
-namespace writer {
-namespace spirv {
-
-/// Forward declarations
-class Builder;
-class BinaryWriter;
-
-/// Configuration options used for generating SPIR-V.
-struct Options {
-  /// Set to `true` to generate a PointSize builtin and have it set to 1.0
-  /// from all vertex shaders in the module.
-  bool emit_vertex_point_size = true;
-
-  /// Set to `true` to disable workgroup memory zero initialization
-  bool disable_workgroup_init = false;
-};
-
-/// The result produced when generating SPIR-V.
-struct Result {
-  /// Constructor
-  Result();
-
-  /// Destructor
-  ~Result();
-
-  /// Copy constructor
-  Result(const Result&);
-
-  /// True if generation was successful.
-  bool success = false;
-
-  /// The errors generated during code generation, if any.
-  std::string error;
-
-  /// The generated SPIR-V.
-  std::vector<uint32_t> spirv;
-};
-
-/// Generate SPIR-V for a program, according to a set of configuration options.
-/// The result will contain the SPIR-V, as well as success status and diagnostic
-/// information.
-/// @param program the program to translate to SPIR-V
-/// @param options the configuration options to use when generating SPIR-V
-/// @returns the resulting SPIR-V and supplementary information
-Result Generate(const Program* program, const Options& options);
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_GENERATOR_H_
diff --git a/src/writer/spirv/generator_bench.cc b/src/writer/spirv/generator_bench.cc
deleted file mode 100644
index 1f9ebde..0000000
--- a/src/writer/spirv/generator_bench.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 <string>
-
-#include "src/bench/benchmark.h"
-
-namespace tint::writer::spirv {
-namespace {
-
-void GenerateSPIRV(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadProgram(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& program = std::get<bench::ProgramAndFile>(res).program;
-  for (auto _ : state) {
-    auto res = Generate(&program, {});
-    if (!res.error.empty()) {
-      state.SkipWithError(res.error.c_str());
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(GenerateSPIRV);
-
-}  // namespace
-}  // namespace tint::writer::spirv
diff --git a/src/writer/spirv/instruction.cc b/src/writer/spirv/instruction.cc
deleted file mode 100644
index 9a1c174..0000000
--- a/src/writer/spirv/instruction.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/writer/spirv/instruction.h"
-
-#include <utility>
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-Instruction::Instruction(spv::Op op, OperandList operands)
-    : op_(op), operands_(std::move(operands)) {}
-
-Instruction::Instruction(const Instruction&) = default;
-
-Instruction::~Instruction() = default;
-
-uint32_t Instruction::word_length() const {
-  uint32_t size = 1;  // Initial 1 for the op and size
-  for (const auto& op : operands_) {
-    size += op.length();
-  }
-  return size;
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/instruction.h b/src/writer/spirv/instruction.h
deleted file mode 100644
index 649746c..0000000
--- a/src/writer/spirv/instruction.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_INSTRUCTION_H_
-#define SRC_WRITER_SPIRV_INSTRUCTION_H_
-
-#include <vector>
-
-#include "spirv/unified1/spirv.hpp11"
-#include "src/writer/spirv/operand.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// A single SPIR-V instruction
-class Instruction {
- public:
-  /// Constructor
-  /// @param op the op to generate
-  /// @param operands the operand values for the instruction
-  Instruction(spv::Op op, OperandList operands);
-  /// Copy Constructor
-  Instruction(const Instruction&);
-  ~Instruction();
-
-  /// @returns the instructions op
-  spv::Op opcode() const { return op_; }
-
-  /// @returns the instructions operands
-  const OperandList& operands() const { return operands_; }
-
-  /// @returns the number of uint32_t's needed to hold the instruction
-  uint32_t word_length() const;
-
- private:
-  spv::Op op_ = spv::Op::OpNop;
-  OperandList operands_;
-};
-
-/// A list of instructions
-using InstructionList = std::vector<Instruction>;
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_INSTRUCTION_H_
diff --git a/src/writer/spirv/instruction_test.cc b/src/writer/spirv/instruction_test.cc
deleted file mode 100644
index d9d9946..0000000
--- a/src/writer/spirv/instruction_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 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/writer/spirv/instruction.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using InstructionTest = testing::Test;
-
-TEST_F(InstructionTest, Create) {
-  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
-                                        Operand::String("my_str")});
-  EXPECT_EQ(i.opcode(), spv::Op::OpEntryPoint);
-  ASSERT_EQ(i.operands().size(), 3u);
-
-  const auto& ops = i.operands();
-  EXPECT_TRUE(ops[0].IsFloat());
-  EXPECT_FLOAT_EQ(ops[0].to_f(), 1.2f);
-
-  EXPECT_TRUE(ops[1].IsInt());
-  EXPECT_EQ(ops[1].to_i(), 1u);
-
-  EXPECT_TRUE(ops[2].IsString());
-  EXPECT_EQ(ops[2].to_s(), "my_str");
-}
-
-TEST_F(InstructionTest, Length) {
-  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
-                                        Operand::String("my_str")});
-  EXPECT_EQ(i.word_length(), 5u);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/operand.cc b/src/writer/spirv/operand.cc
deleted file mode 100644
index 36f4c23..0000000
--- a/src/writer/spirv/operand.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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/writer/spirv/operand.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-// static
-Operand Operand::Float(float val) {
-  Operand o(Kind::kFloat);
-  o.set_float(val);
-  return o;
-}
-
-// static
-Operand Operand::Int(uint32_t val) {
-  Operand o(Kind::kInt);
-  o.set_int(val);
-  return o;
-}
-
-// static
-Operand Operand::String(const std::string& val) {
-  Operand o(Kind::kString);
-  o.set_string(val);
-  return o;
-}
-
-Operand::Operand(Kind kind) : kind_(kind) {}
-
-Operand::~Operand() = default;
-
-uint32_t Operand::length() const {
-  uint32_t val = 0;
-  switch (kind_) {
-    case Kind::kFloat:
-    case Kind::kInt:
-      val = 1;
-      break;
-    case Kind::kString:
-      // SPIR-V always nul-terminates strings. The length is rounded up to a
-      // multiple of 4 bytes with 0 bytes padding the end. Accounting for the
-      // nul terminator is why '+ 4u' is used here instead of '+ 3u'.
-      val = static_cast<uint32_t>((str_val_.length() + 4u) >> 2);
-      break;
-  }
-  return val;
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/operand.h b/src/writer/spirv/operand.h
deleted file mode 100644
index a2eafce..0000000
--- a/src/writer/spirv/operand.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_OPERAND_H_
-#define SRC_WRITER_SPIRV_OPERAND_H_
-
-#include <string>
-#include <vector>
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// A single SPIR-V instruction operand
-class Operand {
- public:
-  /// The kind of the operand
-  // Note, the `kInt` will cover most cases as things like IDs in SPIR-V are
-  // just ints for the purpose of converting to binary.
-  enum class Kind { kInt = 0, kFloat, kString };
-
-  /// Creates a float operand
-  /// @param val the float value
-  /// @returns the operand
-  static Operand Float(float val);
-  /// Creates an int operand
-  /// @param val the int value
-  /// @returns the operand
-  static Operand Int(uint32_t val);
-  /// Creates a string operand
-  /// @param val the string value
-  /// @returns the operand
-  static Operand String(const std::string& val);
-
-  /// Constructor
-  /// @param kind the type of operand
-  explicit Operand(Kind kind);
-  /// Copy Constructor
-  Operand(const Operand&) = default;
-  ~Operand();
-
-  /// Copy assignment
-  /// @param b the operand to copy
-  /// @returns a copy of this operand
-  Operand& operator=(const Operand& b) = default;
-
-  /// Sets the float value
-  /// @param val the value to set
-  void set_float(float val) { float_val_ = val; }
-  /// Sets the int value
-  /// @param val the value to set
-  void set_int(uint32_t val) { int_val_ = val; }
-  /// Sets the string value
-  /// @param val the value to set
-  void set_string(const std::string& val) { str_val_ = val; }
-
-  /// @returns true if this is a float operand
-  bool IsFloat() const { return kind_ == Kind::kFloat; }
-  /// @returns true if this is an integer operand
-  bool IsInt() const { return kind_ == Kind::kInt; }
-  /// @returns true if this is a string operand
-  bool IsString() const { return kind_ == Kind::kString; }
-
-  /// @returns the number of uint32_t's needed for this operand
-  uint32_t length() const;
-
-  /// @returns the float value
-  float to_f() const { return float_val_; }
-  /// @returns the int value
-  uint32_t to_i() const { return int_val_; }
-  /// @returns the string value
-  const std::string& to_s() const { return str_val_; }
-
- private:
-  Kind kind_ = Kind::kInt;
-  float float_val_ = 0.0;
-  uint32_t int_val_ = 0;
-  std::string str_val_;
-};
-
-/// A list of operands
-using OperandList = std::vector<Operand>;
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_OPERAND_H_
diff --git a/src/writer/spirv/operand_test.cc b/src/writer/spirv/operand_test.cc
deleted file mode 100644
index 405028e..0000000
--- a/src/writer/spirv/operand_test.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 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/writer/spirv/operand.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using OperandTest = testing::Test;
-
-TEST_F(OperandTest, CreateFloat) {
-  auto o = Operand::Float(1.2f);
-  EXPECT_TRUE(o.IsFloat());
-  EXPECT_FLOAT_EQ(o.to_f(), 1.2f);
-}
-
-TEST_F(OperandTest, CreateInt) {
-  auto o = Operand::Int(1);
-  EXPECT_TRUE(o.IsInt());
-  EXPECT_EQ(o.to_i(), 1u);
-}
-
-TEST_F(OperandTest, CreateString) {
-  auto o = Operand::String("my string");
-  EXPECT_TRUE(o.IsString());
-  EXPECT_EQ(o.to_s(), "my string");
-}
-
-TEST_F(OperandTest, Length_Float) {
-  auto o = Operand::Float(1.2f);
-  EXPECT_EQ(o.length(), 1u);
-}
-
-TEST_F(OperandTest, Length_Int) {
-  auto o = Operand::Int(1);
-  EXPECT_EQ(o.length(), 1u);
-}
-
-TEST_F(OperandTest, Length_String) {
-  auto o = Operand::String("my string");
-  EXPECT_EQ(o.length(), 3u);
-}
-
-TEST_F(OperandTest, Length_String_Empty) {
-  auto o = Operand::String("");
-  EXPECT_EQ(o.length(), 1u);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/scalar_constant.h b/src/writer/spirv/scalar_constant.h
deleted file mode 100644
index d8deece..0000000
--- a/src/writer/spirv/scalar_constant.h
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_WRITER_SPIRV_SCALAR_CONSTANT_H_
-#define SRC_WRITER_SPIRV_SCALAR_CONSTANT_H_
-
-#include <stdint.h>
-
-#include <cstring>
-#include <functional>
-
-#include "src/utils/hash.h"
-
-namespace tint {
-
-// Forward declarations
-namespace sem {
-class Call;
-}  // namespace sem
-
-namespace writer {
-namespace spirv {
-
-/// ScalarConstant represents a scalar constant value
-struct ScalarConstant {
-  /// The constant value
-  union Value {
-    /// The value as a bool
-    bool b;
-    /// The value as a uint32_t
-    uint32_t u32;
-    /// The value as a int32_t
-    int32_t i32;
-    /// The value as a float
-    float f32;
-
-    /// The value that is wide enough to encompass all other types (including
-    /// future 64-bit data types).
-    uint64_t u64;
-  };
-
-  /// The kind of constant
-  enum class Kind { kBool, kU32, kI32, kF32 };
-
-  /// Constructor
-  inline ScalarConstant() { value.u64 = 0; }
-
-  /// @param value the value of the constant
-  /// @returns a new ScalarConstant with the provided value and kind Kind::kU32
-  static inline ScalarConstant U32(uint32_t value) {
-    ScalarConstant c;
-    c.value.u32 = value;
-    c.kind = Kind::kU32;
-    return c;
-  }
-
-  /// @param value the value of the constant
-  /// @returns a new ScalarConstant with the provided value and kind Kind::kI32
-  static inline ScalarConstant I32(int32_t value) {
-    ScalarConstant c;
-    c.value.i32 = value;
-    c.kind = Kind::kI32;
-    return c;
-  }
-
-  /// @param value the value of the constant
-  /// @returns a new ScalarConstant with the provided value and kind Kind::kI32
-  static inline ScalarConstant F32(float value) {
-    ScalarConstant c;
-    c.value.f32 = value;
-    c.kind = Kind::kF32;
-    return c;
-  }
-
-  /// @param value the value of the constant
-  /// @returns a new ScalarConstant with the provided value and kind Kind::kBool
-  static inline ScalarConstant Bool(bool value) {
-    ScalarConstant c;
-    c.value.b = value;
-    c.kind = Kind::kBool;
-    return c;
-  }
-
-  /// Equality operator
-  /// @param rhs the ScalarConstant to compare against
-  /// @returns true if this ScalarConstant is equal to `rhs`
-  inline bool operator==(const ScalarConstant& rhs) const {
-    return value.u64 == rhs.value.u64 && kind == rhs.kind &&
-           is_spec_op == rhs.is_spec_op && constant_id == rhs.constant_id;
-  }
-
-  /// Inequality operator
-  /// @param rhs the ScalarConstant to compare against
-  /// @returns true if this ScalarConstant is not equal to `rhs`
-  inline bool operator!=(const ScalarConstant& rhs) const {
-    return !(*this == rhs);
-  }
-
-  /// @returns this ScalarConstant as a specialization op with the given
-  /// specialization constant identifier
-  /// @param id the constant identifier
-  ScalarConstant AsSpecOp(uint32_t id) const {
-    auto ret = *this;
-    ret.is_spec_op = true;
-    ret.constant_id = id;
-    return ret;
-  }
-
-  /// The constant value
-  Value value;
-  /// The constant value kind
-  Kind kind = Kind::kBool;
-  /// True if the constant is a specialization op
-  bool is_spec_op = false;
-  /// The identifier if a specialization op
-  uint32_t constant_id = 0;
-};
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-namespace std {
-
-/// Custom std::hash specialization for tint::Symbol so symbols can be used as
-/// keys for std::unordered_map and std::unordered_set.
-template <>
-class hash<tint::writer::spirv::ScalarConstant> {
- public:
-  /// @param c the ScalarConstant
-  /// @return the Symbol internal value
-  inline std::size_t operator()(
-      const tint::writer::spirv::ScalarConstant& c) const {
-    uint32_t value = 0;
-    std::memcpy(&value, &c.value, sizeof(value));
-    return tint::utils::Hash(value, c.kind);
-  }
-};
-
-}  // namespace std
-
-#endif  // SRC_WRITER_SPIRV_SCALAR_CONSTANT_H_
diff --git a/src/writer/spirv/scalar_constant_test.cc b/src/writer/spirv/scalar_constant_test.cc
deleted file mode 100644
index b514146..0000000
--- a/src/writer/spirv/scalar_constant_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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/writer/spirv/scalar_constant.h"
-#include "src/writer/spirv/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-using SpirvScalarConstantTest = TestHelper;
-
-TEST_F(SpirvScalarConstantTest, Equality) {
-  ScalarConstant a{};
-  ScalarConstant b{};
-  EXPECT_EQ(a, b);
-
-  a.kind = ScalarConstant::Kind::kU32;
-  EXPECT_NE(a, b);
-  b.kind = ScalarConstant::Kind::kU32;
-  EXPECT_EQ(a, b);
-
-  a.value.b = true;
-  EXPECT_NE(a, b);
-  b.value.b = true;
-  EXPECT_EQ(a, b);
-
-  a.is_spec_op = true;
-  EXPECT_NE(a, b);
-  b.is_spec_op = true;
-  EXPECT_EQ(a, b);
-
-  a.constant_id = 3;
-  EXPECT_NE(a, b);
-  b.constant_id = 3;
-  EXPECT_EQ(a, b);
-}
-
-TEST_F(SpirvScalarConstantTest, U32) {
-  auto c = ScalarConstant::U32(123);
-  EXPECT_EQ(c.value.u32, 123u);
-  EXPECT_EQ(c.kind, ScalarConstant::Kind::kU32);
-}
-
-}  // namespace
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/spv_dump.cc b/src/writer/spirv/spv_dump.cc
deleted file mode 100644
index 1f343bb..0000000
--- a/src/writer/spirv/spv_dump.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 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/writer/spirv/spv_dump.h"
-
-#include "spirv-tools/libspirv.hpp"
-#include "src/writer/spirv/binary_writer.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-namespace {
-
-std::string Disassemble(const std::vector<uint32_t>& data) {
-  std::string spv_errors;
-  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
-
-  auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
-                                    const spv_position_t& position,
-                                    const char* message) {
-    switch (level) {
-      case SPV_MSG_FATAL:
-      case SPV_MSG_INTERNAL_ERROR:
-      case SPV_MSG_ERROR:
-        spv_errors += "error: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_WARNING:
-        spv_errors += "warning: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_INFO:
-        spv_errors += "info: line " + std::to_string(position.index) + ": " +
-                      message + "\n";
-        break;
-      case SPV_MSG_DEBUG:
-        break;
-    }
-  };
-
-  spvtools::SpirvTools tools(target_env);
-  tools.SetMessageConsumer(msg_consumer);
-
-  std::string result;
-  if (!tools.Disassemble(data, &result, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) {
-    return "*** Invalid SPIR-V ***\n" + spv_errors;
-  }
-  return result;
-}
-
-}  // namespace
-
-std::string DumpBuilder(Builder& builder) {
-  BinaryWriter writer;
-  writer.WriteHeader(builder.id_bound());
-  writer.WriteBuilder(&builder);
-  return Disassemble(writer.result());
-}
-
-std::string DumpInstruction(const Instruction& inst) {
-  BinaryWriter writer;
-  writer.WriteHeader(kDefaultMaxIdBound);
-  writer.WriteInstruction(inst);
-  return Disassemble(writer.result());
-}
-
-std::string DumpInstructions(const InstructionList& insts) {
-  BinaryWriter writer;
-  writer.WriteHeader(kDefaultMaxIdBound);
-  for (const auto& inst : insts) {
-    writer.WriteInstruction(inst);
-  }
-  return Disassemble(writer.result());
-}
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/spirv/spv_dump.h b/src/writer/spirv/spv_dump.h
deleted file mode 100644
index 1187d1a..0000000
--- a/src/writer/spirv/spv_dump.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_SPV_DUMP_H_
-#define SRC_WRITER_SPIRV_SPV_DUMP_H_
-
-#include <string>
-#include <vector>
-
-#include "src/writer/spirv/builder.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// Dumps the given builder to a SPIR-V disassembly string
-/// @param builder the builder to convert
-/// @returns the builder as a SPIR-V disassembly string
-std::string DumpBuilder(Builder& builder);
-
-/// Dumps the given instruction to a SPIR-V disassembly string
-/// @param inst the instruction to dump
-/// @returns the instruction as a SPIR-V disassembly string
-std::string DumpInstruction(const Instruction& inst);
-
-/// Dumps the given instructions to a SPIR-V disassembly string
-/// @param insts the instructions to dump
-/// @returns the instruction as a SPIR-V disassembly string
-std::string DumpInstructions(const InstructionList& insts);
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_SPV_DUMP_H_
diff --git a/src/writer/spirv/test_helper.h b/src/writer/spirv/test_helper.h
deleted file mode 100644
index 90fa2cb..0000000
--- a/src/writer/spirv/test_helper.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2020 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_WRITER_SPIRV_TEST_HELPER_H_
-#define SRC_WRITER_SPIRV_TEST_HELPER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "spirv-tools/libspirv.hpp"
-#include "src/writer/spirv/binary_writer.h"
-
-namespace tint {
-namespace writer {
-namespace spirv {
-
-/// Helper class for testing
-template <typename BASE>
-class TestHelperBase : public ProgramBuilder, public BASE {
- public:
-  TestHelperBase() = default;
-  ~TestHelperBase() override = default;
-
-  /// Builds and returns a spirv::Builder from the program.
-  /// @note The spirv::Builder is only built once. Multiple calls to Build()
-  /// will return the same spirv::Builder without rebuilding.
-  /// @return the built spirv::Builder
-  spirv::Builder& Build() {
-    if (spirv_builder) {
-      return *spirv_builder;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-    spirv_builder = std::make_unique<spirv::Builder>(program.get());
-    return *spirv_builder;
-  }
-
-  /// Builds the program, runs the program through the transform::Spirv
-  /// sanitizer and returns a spirv::Builder from the sanitized program.
-  /// @note The spirv::Builder is only built once. Multiple calls to Build()
-  /// will return the same spirv::Builder without rebuilding.
-  /// @return the built spirv::Builder
-  spirv::Builder& SanitizeAndBuild() {
-    if (spirv_builder) {
-      return *spirv_builder;
-    }
-    [&]() {
-      ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
-                             << diag::Formatter().format(Diagnostics());
-    }();
-    program = std::make_unique<Program>(std::move(*this));
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << diag::Formatter().format(program->Diagnostics());
-    }();
-    auto result = Sanitize(program.get());
-    [&]() {
-      ASSERT_TRUE(result.program.IsValid())
-          << diag::Formatter().format(result.program.Diagnostics());
-    }();
-    *program = std::move(result.program);
-    spirv_builder = std::make_unique<spirv::Builder>(program.get());
-    return *spirv_builder;
-  }
-
-  /// Validate passes the generated SPIR-V of the builder `b` to the SPIR-V
-  /// Tools Validator. If the validator finds problems the test will fail.
-  /// @param b the spirv::Builder containing the built SPIR-V module
-  void Validate(spirv::Builder& b) {
-    BinaryWriter writer;
-    writer.WriteHeader(b.id_bound());
-    writer.WriteBuilder(&b);
-    auto binary = writer.result();
-
-    std::string spv_errors;
-    auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
-                                      const spv_position_t& position,
-                                      const char* message) {
-      switch (level) {
-        case SPV_MSG_FATAL:
-        case SPV_MSG_INTERNAL_ERROR:
-        case SPV_MSG_ERROR:
-          spv_errors += "error: line " + std::to_string(position.index) + ": " +
-                        message + "\n";
-          break;
-        case SPV_MSG_WARNING:
-          spv_errors += "warning: line " + std::to_string(position.index) +
-                        ": " + message + "\n";
-          break;
-        case SPV_MSG_INFO:
-          spv_errors += "info: line " + std::to_string(position.index) + ": " +
-                        message + "\n";
-          break;
-        case SPV_MSG_DEBUG:
-          break;
-      }
-    };
-
-    spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
-    tools.SetMessageConsumer(msg_consumer);
-    ASSERT_TRUE(tools.Validate(binary)) << spv_errors;
-  }
-
-  /// The program built with a call to Build()
-  std::unique_ptr<Program> program;
-
- private:
-  std::unique_ptr<spirv::Builder> spirv_builder;
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace spirv
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_SPIRV_TEST_HELPER_H_
diff --git a/src/writer/text.cc b/src/writer/text.cc
deleted file mode 100644
index 3853e27..0000000
--- a/src/writer/text.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 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/writer/text.h"
-
-namespace tint {
-namespace writer {
-
-Text::~Text() = default;
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/text.h b/src/writer/text.h
deleted file mode 100644
index 625e8e0..0000000
--- a/src/writer/text.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 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_WRITER_TEXT_H_
-#define SRC_WRITER_TEXT_H_
-
-#include <string>
-
-#include "src/writer/writer.h"
-
-namespace tint {
-namespace writer {
-
-/// Class to generate text source
-class Text : public Writer {
- public:
-  ~Text() override;
-
-  /// @returns the result data
-  virtual std::string result() const = 0;
-};
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_TEXT_H_
diff --git a/src/writer/text_generator.cc b/src/writer/text_generator.cc
deleted file mode 100644
index 405407b..0000000
--- a/src/writer/text_generator.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2020 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/writer/text_generator.h"
-
-#include <algorithm>
-#include <limits>
-
-#include "src/utils/map.h"
-
-namespace tint {
-namespace writer {
-
-TextGenerator::TextGenerator(const Program* program)
-    : program_(program), builder_(ProgramBuilder::Wrap(program)) {}
-
-TextGenerator::~TextGenerator() = default;
-
-std::string TextGenerator::UniqueIdentifier(const std::string& prefix) {
-  return builder_.Symbols().NameFor(builder_.Symbols().New(prefix));
-}
-
-std::string TextGenerator::StructName(const sem::Struct* s) {
-  auto name = builder_.Symbols().NameFor(s->Name());
-  if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
-    name = utils::GetOrCreate(builtin_struct_names_, s,
-                              [&] { return UniqueIdentifier(name.substr(2)); });
-  }
-  return name;
-}
-
-std::string TextGenerator::TrimSuffix(std::string str,
-                                      const std::string& suffix) {
-  if (str.size() >= suffix.size()) {
-    if (str.substr(str.size() - suffix.size(), suffix.size()) == suffix) {
-      return str.substr(0, str.size() - suffix.size());
-    }
-  }
-  return str;
-}
-
-TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
-
-TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
-  buffer = other.buffer;
-  other.buffer = nullptr;
-}
-
-TextGenerator::LineWriter::~LineWriter() {
-  if (buffer) {
-    buffer->Append(os.str());
-  }
-}
-
-TextGenerator::TextBuffer::TextBuffer() = default;
-TextGenerator::TextBuffer::~TextBuffer() = default;
-
-void TextGenerator::TextBuffer::IncrementIndent() {
-  current_indent += 2;
-}
-
-void TextGenerator::TextBuffer::DecrementIndent() {
-  current_indent = std::max(2u, current_indent) - 2u;
-}
-
-void TextGenerator::TextBuffer::Append(const std::string& line) {
-  lines.emplace_back(Line{current_indent, line});
-}
-
-void TextGenerator::TextBuffer::Insert(const std::string& line,
-                                       size_t before,
-                                       uint32_t indent) {
-  if (before >= lines.size()) {
-    diag::List d;
-    TINT_ICE(Writer, d)
-        << "TextBuffer::Insert() called with before >= lines.size()\n"
-        << "  before:" << before << "\n"
-        << "  lines.size(): " << lines.size();
-    return;
-  }
-  lines.insert(lines.begin() + before, Line{indent, line});
-}
-
-void TextGenerator::TextBuffer::Append(const TextBuffer& tb) {
-  for (auto& line : tb.lines) {
-    // TODO(bclayton): inefficent, consider optimizing
-    lines.emplace_back(Line{current_indent + line.indent, line.content});
-  }
-}
-
-void TextGenerator::TextBuffer::Insert(const TextBuffer& tb,
-                                       size_t before,
-                                       uint32_t indent) {
-  if (before >= lines.size()) {
-    diag::List d;
-    TINT_ICE(Writer, d)
-        << "TextBuffer::Insert() called with before >= lines.size()\n"
-        << "  before:" << before << "\n"
-        << "  lines.size(): " << lines.size();
-    return;
-  }
-  size_t idx = 0;
-  for (auto& line : tb.lines) {
-    // TODO(bclayton): inefficent, consider optimizing
-    lines.insert(lines.begin() + before + idx,
-                 Line{indent + line.indent, line.content});
-    idx++;
-  }
-}
-
-std::string TextGenerator::TextBuffer::String(uint32_t indent /* = 0 */) const {
-  std::stringstream ss;
-  for (auto& line : lines) {
-    if (!line.content.empty()) {
-      for (uint32_t i = 0; i < indent + line.indent; i++) {
-        ss << " ";
-      }
-      ss << line.content;
-    }
-    ss << std::endl;
-  }
-  return ss.str();
-}
-
-TextGenerator::ScopedParen::ScopedParen(std::ostream& stream) : s(stream) {
-  s << "(";
-}
-TextGenerator::ScopedParen::~ScopedParen() {
-  s << ")";
-}
-
-TextGenerator::ScopedIndent::ScopedIndent(TextGenerator* generator)
-    : ScopedIndent(generator->current_buffer_) {}
-
-TextGenerator::ScopedIndent::ScopedIndent(TextBuffer* buffer)
-    : buffer_(buffer) {
-  buffer_->IncrementIndent();
-}
-TextGenerator::ScopedIndent::~ScopedIndent() {
-  buffer_->DecrementIndent();
-}
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/text_generator.h b/src/writer/text_generator.h
deleted file mode 100644
index dd6698c..0000000
--- a/src/writer/text_generator.h
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2020 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_WRITER_TEXT_GENERATOR_H_
-#define SRC_WRITER_TEXT_GENERATOR_H_
-
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/diagnostic/diagnostic.h"
-#include "src/program_builder.h"
-
-namespace tint {
-namespace writer {
-
-/// Helper methods for generators which are creating text output
-class TextGenerator {
- public:
-  /// Line holds a single line of text
-  struct Line {
-    /// The indentation of the line in whitespaces
-    uint32_t indent = 0;
-    /// The content of the line, without a trailing newline character
-    std::string content;
-  };
-
-  /// TextBuffer holds a list of lines of text.
-  struct TextBuffer {
-    // Constructor
-    TextBuffer();
-
-    // Destructor
-    ~TextBuffer();
-
-    /// IncrementIndent increases the indentation of lines that will be written
-    /// to the TextBuffer
-    void IncrementIndent();
-
-    /// DecrementIndent decreases the indentation of lines that will be written
-    /// to the TextBuffer
-    void DecrementIndent();
-
-    /// Appends the line to the end of the TextBuffer
-    /// @param line the line to append to the TextBuffer
-    void Append(const std::string& line);
-
-    /// Inserts the line to the TextBuffer before the line with index `before`
-    /// @param line the line to append to the TextBuffer
-    /// @param before the zero-based index of the line to insert the text before
-    /// @param indent the indentation to apply to the inserted lines
-    void Insert(const std::string& line, size_t before, uint32_t indent);
-
-    /// Appends the lines of `tb` to the end of this TextBuffer
-    /// @param tb the TextBuffer to append to the end of this TextBuffer
-    void Append(const TextBuffer& tb);
-
-    /// Inserts the lines of `tb` to the TextBuffer before the line with index
-    /// `before`
-    /// @param tb the TextBuffer to insert into this TextBuffer
-    /// @param before the zero-based index of the line to insert the text before
-    /// @param indent the indentation to apply to the inserted lines
-    void Insert(const TextBuffer& tb, size_t before, uint32_t indent);
-
-    /// @returns the buffer's content as a single string
-    /// @param indent additional indentation to apply to each line
-    std::string String(uint32_t indent = 0) const;
-
-    /// The current indentation of the TextBuffer. Lines appended to the
-    /// TextBuffer will use this indentation.
-    uint32_t current_indent = 0;
-
-    /// The lines
-    std::vector<Line> lines;
-  };
-
-  /// Constructor
-  /// @param program the program used by the generator
-  explicit TextGenerator(const Program* program);
-  ~TextGenerator();
-
-  /// Increment the emitter indent level
-  void increment_indent() { current_buffer_->IncrementIndent(); }
-  /// Decrement the emitter indent level
-  void decrement_indent() { current_buffer_->DecrementIndent(); }
-
-  /// @returns the result data
-  std::string result() const { return main_buffer_.String(); }
-
-  /// @returns the list of diagnostics raised by the generator.
-  const diag::List& Diagnostics() const { return diagnostics_; }
-
-  /// @returns the error
-  std::string error() const { return diagnostics_.str(); }
-
-  /// @return a new, unique identifier with the given prefix.
-  /// @param prefix optional prefix to apply to the generated identifier. If
-  /// empty "tint_symbol" will be used.
-  std::string UniqueIdentifier(const std::string& prefix = "");
-
-  /// @param s the semantic structure
-  /// @returns the name of the structure, taking special care of builtin
-  /// structures that start with double underscores. If the structure is a
-  /// builtin, then the returned name will be a unique name without the leading
-  /// underscores.
-  std::string StructName(const sem::Struct* s);
-
-  /// @param str the string
-  /// @param suffix the suffix to remove
-  /// @return returns str without the provided trailing suffix string. If str
-  /// doesn't end with suffix, str is returned unchanged.
-  std::string TrimSuffix(std::string str, const std::string& suffix);
-
- protected:
-  /// LineWriter is a helper that acts as a string buffer, who's content is
-  /// emitted to the TextBuffer as a single line on destruction.
-  struct LineWriter {
-   public:
-    /// Constructor
-    /// @param buffer the TextBuffer that the LineWriter will append its
-    /// content to on destruction, at the end of the buffer.
-    explicit LineWriter(TextBuffer* buffer);
-
-    /// Move constructor
-    /// @param rhs the LineWriter to move
-    LineWriter(LineWriter&& rhs);
-    /// Destructor
-    ~LineWriter();
-
-    /// @returns the ostringstream
-    operator std::ostream&() { return os; }
-
-    /// @param rhs the value to write to the line
-    /// @returns the ostream so calls can be chained
-    template <typename T>
-    std::ostream& operator<<(T&& rhs) {
-      return os << std::forward<T>(rhs);
-    }
-
-   private:
-    LineWriter(const LineWriter&) = delete;
-    LineWriter& operator=(const LineWriter&) = delete;
-
-    std::ostringstream os;
-    TextBuffer* buffer;
-  };
-
-  /// Helper for writing a '(' on construction and a ')' destruction.
-  struct ScopedParen {
-    /// Constructor
-    /// @param stream the std::ostream that will be written to
-    explicit ScopedParen(std::ostream& stream);
-    /// Destructor
-    ~ScopedParen();
-
-   private:
-    ScopedParen(ScopedParen&& rhs) = delete;
-    ScopedParen(const ScopedParen&) = delete;
-    ScopedParen& operator=(const ScopedParen&) = delete;
-    std::ostream& s;
-  };
-
-  /// Helper for incrementing indentation on construction and decrementing
-  /// indentation on destruction.
-  struct ScopedIndent {
-    /// Constructor
-    /// @param buffer the TextBuffer that the ScopedIndent will indent
-    explicit ScopedIndent(TextBuffer* buffer);
-    /// Constructor
-    /// @param generator ScopedIndent will indent the generator's
-    /// `current_buffer_`
-    explicit ScopedIndent(TextGenerator* generator);
-    /// Destructor
-    ~ScopedIndent();
-
-   private:
-    ScopedIndent(ScopedIndent&& rhs) = delete;
-    ScopedIndent(const ScopedIndent&) = delete;
-    ScopedIndent& operator=(const ScopedIndent&) = delete;
-    TextBuffer* buffer_;
-  };
-
-  /// @returns the resolved type of the ast::Expression `expr`
-  /// @param expr the expression
-  const sem::Type* TypeOf(const ast::Expression* expr) const {
-    return builder_.TypeOf(expr);
-  }
-
-  /// @returns the resolved type of the ast::Type `type`
-  /// @param type the type
-  const sem::Type* TypeOf(const ast::Type* type) const {
-    return builder_.TypeOf(type);
-  }
-
-  /// @returns the resolved type of the ast::TypeDecl `type_decl`
-  /// @param type_decl the type
-  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const {
-    return builder_.TypeOf(type_decl);
-  }
-
-  /// @returns a new LineWriter, used for buffering and writing a line to
-  /// the end of #current_buffer_.
-  LineWriter line() { return LineWriter(current_buffer_); }
-
-  /// @param buffer the TextBuffer to write the line to
-  /// @returns a new LineWriter, used for buffering and writing a line to
-  /// the end of `buffer`.
-  static LineWriter line(TextBuffer* buffer) { return LineWriter(buffer); }
-
-  /// The program
-  Program const* const program_;
-  /// A ProgramBuilder that thinly wraps program_
-  ProgramBuilder builder_;
-  /// Diagnostics generated by the generator
-  diag::List diagnostics_;
-  /// The buffer the TextGenerator is currently appending lines to
-  TextBuffer* current_buffer_ = &main_buffer_;
-
- private:
-  /// The primary text buffer that the generator will emit
-  TextBuffer main_buffer_;
-  /// Map of builtin structure to unique generated name
-  std::unordered_map<const sem::Struct*, std::string> builtin_struct_names_;
-};
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_TEXT_GENERATOR_H_
diff --git a/src/writer/text_generator_test.cc b/src/writer/text_generator_test.cc
deleted file mode 100644
index 10c7086..0000000
--- a/src/writer/text_generator_test.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/text_generator.h"
-
-#include "gtest/gtest.h"
-
-namespace tint {
-namespace writer {
-namespace {
-
-TEST(TextGeneratorTest, UniqueIdentifier) {
-  Program program(ProgramBuilder{});
-
-  TextGenerator gen(&program);
-
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_1");
-}
-
-TEST(TextGeneratorTest, UniqueIdentifier_ConflictWithExisting) {
-  ProgramBuilder builder;
-  builder.Symbols().Register("ident_1");
-  builder.Symbols().Register("ident_2");
-  Program program(std::move(builder));
-
-  TextGenerator gen(&program);
-
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_3");
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_4");
-  ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_5");
-}
-
-}  // namespace
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator.cc b/src/writer/wgsl/generator.cc
deleted file mode 100644
index b7c3566..0000000
--- a/src/writer/wgsl/generator.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 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/writer/wgsl/generator.h"
-#include "src/writer/wgsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-
-Result::Result() = default;
-Result::~Result() = default;
-Result::Result(const Result&) = default;
-
-Result Generate(const Program* program, const Options&) {
-  Result result;
-
-  // Generate the WGSL code.
-  auto impl = std::make_unique<GeneratorImpl>(program);
-  result.success = impl->Generate();
-  result.error = impl->error();
-  result.wgsl = impl->result();
-
-  return result;
-}
-
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator.h b/src/writer/wgsl/generator.h
deleted file mode 100644
index 9e93c76..0000000
--- a/src/writer/wgsl/generator.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 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_WRITER_WGSL_GENERATOR_H_
-#define SRC_WRITER_WGSL_GENERATOR_H_
-
-#include <memory>
-#include <string>
-
-#include "src/writer/text.h"
-
-namespace tint {
-
-// Forward declarations
-class Program;
-
-namespace writer {
-namespace wgsl {
-
-class GeneratorImpl;
-
-/// Configuration options used for generating WGSL.
-struct Options {};
-
-/// The result produced when generating WGSL.
-struct Result {
-  /// Constructor
-  Result();
-
-  /// Destructor
-  ~Result();
-
-  /// Copy constructor
-  Result(const Result&);
-
-  /// True if generation was successful.
-  bool success = false;
-
-  /// The errors generated during code generation, if any.
-  std::string error;
-
-  /// The generated WGSL.
-  std::string wgsl = "";
-};
-
-/// Generate WGSL for a program, according to a set of configuration options.
-/// The result will contain the WGSL, as well as success status and diagnostic
-/// information.
-/// @param program the program to translate to WGSL
-/// @param options the configuration options to use when generating WGSL
-/// @returns the resulting WGSL and supplementary information
-Result Generate(const Program* program, const Options& options);
-
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_WGSL_GENERATOR_H_
diff --git a/src/writer/wgsl/generator_bench.cc b/src/writer/wgsl/generator_bench.cc
deleted file mode 100644
index b070ecc..0000000
--- a/src/writer/wgsl/generator_bench.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 <string>
-
-#include "src/bench/benchmark.h"
-
-namespace tint::writer::wgsl {
-namespace {
-
-void GenerateWGSL(benchmark::State& state, std::string input_name) {
-  auto res = bench::LoadProgram(input_name);
-  if (auto err = std::get_if<bench::Error>(&res)) {
-    state.SkipWithError(err->msg.c_str());
-    return;
-  }
-  auto& program = std::get<bench::ProgramAndFile>(res).program;
-  for (auto _ : state) {
-    auto res = Generate(&program, {});
-    if (!res.error.empty()) {
-      state.SkipWithError(res.error.c_str());
-    }
-  }
-}
-
-TINT_BENCHMARK_WGSL_PROGRAMS(GenerateWGSL);
-
-}  // namespace
-}  // namespace tint::writer::wgsl
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
deleted file mode 100644
index 8926839..0000000
--- a/src/writer/wgsl/generator_impl.cc
+++ /dev/null
@@ -1,1190 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/wgsl/generator_impl.h"
-
-#include <algorithm>
-
-#include "src/ast/access.h"
-#include "src/ast/alias.h"
-#include "src/ast/array.h"
-#include "src/ast/atomic.h"
-#include "src/ast/bool.h"
-#include "src/ast/bool_literal_expression.h"
-#include "src/ast/call_statement.h"
-#include "src/ast/depth_texture.h"
-#include "src/ast/external_texture.h"
-#include "src/ast/f32.h"
-#include "src/ast/float_literal_expression.h"
-#include "src/ast/i32.h"
-#include "src/ast/id_attribute.h"
-#include "src/ast/internal_attribute.h"
-#include "src/ast/interpolate_attribute.h"
-#include "src/ast/invariant_attribute.h"
-#include "src/ast/matrix.h"
-#include "src/ast/module.h"
-#include "src/ast/multisampled_texture.h"
-#include "src/ast/pointer.h"
-#include "src/ast/sampled_texture.h"
-#include "src/ast/sint_literal_expression.h"
-#include "src/ast/stage_attribute.h"
-#include "src/ast/storage_texture.h"
-#include "src/ast/stride_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/struct_member_align_attribute.h"
-#include "src/ast/struct_member_offset_attribute.h"
-#include "src/ast/struct_member_size_attribute.h"
-#include "src/ast/type_name.h"
-#include "src/ast/u32.h"
-#include "src/ast/uint_literal_expression.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/vector.h"
-#include "src/ast/void.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/sem/struct.h"
-#include "src/utils/math.h"
-#include "src/utils/scoped_assignment.h"
-#include "src/writer/float_to_string.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-
-GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
-
-GeneratorImpl::~GeneratorImpl() = default;
-
-bool GeneratorImpl::Generate() {
-  // Generate global declarations in the order they appear in the module.
-  for (auto* decl : program_->AST().GlobalDeclarations()) {
-    if (!Switch(
-            decl,  //
-            [&](const ast::TypeDecl* td) { return EmitTypeDecl(td); },
-            [&](const ast::Function* func) { return EmitFunction(func); },
-            [&](const ast::Variable* var) { return EmitVariable(line(), var); },
-            [&](Default) {
-              TINT_UNREACHABLE(Writer, diagnostics_);
-              return false;
-            })) {
-      return false;
-    }
-    if (decl != program_->AST().GlobalDeclarations().back()) {
-      line();
-    }
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitTypeDecl(const ast::TypeDecl* ty) {
-  return Switch(
-      ty,
-      [&](const ast::Alias* alias) {  //
-        auto out = line();
-        out << "type " << program_->Symbols().NameFor(alias->name) << " = ";
-        if (!EmitType(out, alias->type)) {
-          return false;
-        }
-        out << ";";
-        return true;
-      },
-      [&](const ast::Struct* str) {  //
-        return EmitStructType(str);
-      },
-      [&](Default) {  //
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown declared type: " + std::string(ty->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitExpression(std::ostream& out,
-                                   const ast::Expression* expr) {
-  return Switch(
-      expr,
-      [&](const ast::IndexAccessorExpression* a) {  //
-        return EmitIndexAccessor(out, a);
-      },
-      [&](const ast::BinaryExpression* b) {  //
-        return EmitBinary(out, b);
-      },
-      [&](const ast::BitcastExpression* b) {  //
-        return EmitBitcast(out, b);
-      },
-      [&](const ast::CallExpression* c) {  //
-        return EmitCall(out, c);
-      },
-      [&](const ast::IdentifierExpression* i) {  //
-        return EmitIdentifier(out, i);
-      },
-      [&](const ast::LiteralExpression* l) {  //
-        return EmitLiteral(out, l);
-      },
-      [&](const ast::MemberAccessorExpression* m) {  //
-        return EmitMemberAccessor(out, m);
-      },
-      [&](const ast::PhonyExpression*) {  //
-        out << "_";
-        return true;
-      },
-      [&](const ast::UnaryOpExpression* u) {  //
-        return EmitUnaryOp(out, u);
-      },
-      [&](Default) {
-        diagnostics_.add_error(diag::System::Writer, "unknown expression type");
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitIndexAccessor(
-    std::ostream& out,
-    const ast::IndexAccessorExpression* expr) {
-  bool paren_lhs =
-      !expr->object->IsAnyOf<ast::IndexAccessorExpression, ast::CallExpression,
-                             ast::IdentifierExpression,
-                             ast::MemberAccessorExpression>();
-  if (paren_lhs) {
-    out << "(";
-  }
-  if (!EmitExpression(out, expr->object)) {
-    return false;
-  }
-  if (paren_lhs) {
-    out << ")";
-  }
-  out << "[";
-
-  if (!EmitExpression(out, expr->index)) {
-    return false;
-  }
-  out << "]";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitMemberAccessor(
-    std::ostream& out,
-    const ast::MemberAccessorExpression* expr) {
-  bool paren_lhs =
-      !expr->structure->IsAnyOf<ast::IndexAccessorExpression,
-                                ast::CallExpression, ast::IdentifierExpression,
-                                ast::MemberAccessorExpression>();
-  if (paren_lhs) {
-    out << "(";
-  }
-  if (!EmitExpression(out, expr->structure)) {
-    return false;
-  }
-  if (paren_lhs) {
-    out << ")";
-  }
-
-  out << ".";
-
-  return EmitExpression(out, expr->member);
-}
-
-bool GeneratorImpl::EmitBitcast(std::ostream& out,
-                                const ast::BitcastExpression* expr) {
-  out << "bitcast<";
-  if (!EmitType(out, expr->type)) {
-    return false;
-  }
-
-  out << ">(";
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitCall(std::ostream& out,
-                             const ast::CallExpression* expr) {
-  if (expr->target.name) {
-    if (!EmitExpression(out, expr->target.name)) {
-      return false;
-    }
-  } else if (expr->target.type) {
-    if (!EmitType(out, expr->target.type)) {
-      return false;
-    }
-  } else {
-    TINT_ICE(Writer, diagnostics_)
-        << "CallExpression target had neither a name or type";
-    return false;
-  }
-  out << "(";
-
-  bool first = true;
-  const auto& args = expr->args;
-  for (auto* arg : args) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitExpression(out, arg)) {
-      return false;
-    }
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitLiteral(std::ostream& out,
-                                const ast::LiteralExpression* lit) {
-  return Switch(
-      lit,
-      [&](const ast::BoolLiteralExpression* bl) {  //
-        out << (bl->value ? "true" : "false");
-        return true;
-      },
-      [&](const ast::FloatLiteralExpression* fl) {  //
-        out << FloatToBitPreservingString(fl->value);
-        return true;
-      },
-      [&](const ast::SintLiteralExpression* sl) {  //
-        out << sl->value;
-        return true;
-      },
-      [&](const ast::UintLiteralExpression* ul) {  //
-        out << ul->value << "u";
-        return true;
-      },
-      [&](Default) {  //
-        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitIdentifier(std::ostream& out,
-                                   const ast::IdentifierExpression* expr) {
-  out << program_->Symbols().NameFor(expr->symbol);
-  return true;
-}
-
-bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-  if (func->attributes.size()) {
-    if (!EmitAttributes(line(), func->attributes)) {
-      return false;
-    }
-  }
-  {
-    auto out = line();
-    out << "fn " << program_->Symbols().NameFor(func->symbol) << "(";
-
-    bool first = true;
-    for (auto* v : func->params) {
-      if (!first) {
-        out << ", ";
-      }
-      first = false;
-
-      if (!v->attributes.empty()) {
-        if (!EmitAttributes(out, v->attributes)) {
-          return false;
-        }
-        out << " ";
-      }
-
-      out << program_->Symbols().NameFor(v->symbol) << " : ";
-
-      if (!EmitType(out, v->type)) {
-        return false;
-      }
-    }
-
-    out << ")";
-
-    if (!func->return_type->Is<ast::Void>() ||
-        !func->return_type_attributes.empty()) {
-      out << " -> ";
-
-      if (!func->return_type_attributes.empty()) {
-        if (!EmitAttributes(out, func->return_type_attributes)) {
-          return false;
-        }
-        out << " ";
-      }
-
-      if (!EmitType(out, func->return_type)) {
-        return false;
-      }
-    }
-
-    if (func->body) {
-      out << " {";
-    }
-  }
-
-  if (func->body) {
-    if (!EmitStatementsWithIndent(func->body->statements)) {
-      return false;
-    }
-    line() << "}";
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitImageFormat(std::ostream& out,
-                                    const ast::TexelFormat fmt) {
-  switch (fmt) {
-    case ast::TexelFormat::kNone:
-      diagnostics_.add_error(diag::System::Writer, "unknown image format");
-      return false;
-    default:
-      out << fmt;
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitAccess(std::ostream& out, const ast::Access access) {
-  switch (access) {
-    case ast::Access::kRead:
-      out << "read";
-      return true;
-    case ast::Access::kWrite:
-      out << "write";
-      return true;
-    case ast::Access::kReadWrite:
-      out << "read_write";
-      return true;
-    default:
-      break;
-  }
-  diagnostics_.add_error(diag::System::Writer, "unknown access");
-  return false;
-}
-
-bool GeneratorImpl::EmitType(std::ostream& out, const ast::Type* ty) {
-  return Switch(
-      ty,
-      [&](const ast::Array* ary) {
-        for (auto* attr : ary->attributes) {
-          if (auto* stride = attr->As<ast::StrideAttribute>()) {
-            out << "@stride(" << stride->stride << ") ";
-          }
-        }
-
-        out << "array<";
-        if (!EmitType(out, ary->type)) {
-          return false;
-        }
-
-        if (!ary->IsRuntimeArray()) {
-          out << ", ";
-          if (!EmitExpression(out, ary->count)) {
-            return false;
-          }
-        }
-
-        out << ">";
-        return true;
-      },
-      [&](const ast::Bool*) {
-        out << "bool";
-        return true;
-      },
-      [&](const ast::F32*) {
-        out << "f32";
-        return true;
-      },
-      [&](const ast::I32*) {
-        out << "i32";
-        return true;
-      },
-      [&](const ast::Matrix* mat) {
-        out << "mat" << mat->columns << "x" << mat->rows;
-        if (auto* el_ty = mat->type) {
-          out << "<";
-          if (!EmitType(out, el_ty)) {
-            return false;
-          }
-          out << ">";
-        }
-        return true;
-      },
-      [&](const ast::Pointer* ptr) {
-        out << "ptr<" << ptr->storage_class << ", ";
-        if (!EmitType(out, ptr->type)) {
-          return false;
-        }
-        if (ptr->access != ast::Access::kUndefined) {
-          out << ", ";
-          if (!EmitAccess(out, ptr->access)) {
-            return false;
-          }
-        }
-        out << ">";
-        return true;
-      },
-      [&](const ast::Atomic* atomic) {
-        out << "atomic<";
-        if (!EmitType(out, atomic->type)) {
-          return false;
-        }
-        out << ">";
-        return true;
-      },
-      [&](const ast::Sampler* sampler) {
-        out << "sampler";
-
-        if (sampler->IsComparison()) {
-          out << "_comparison";
-        }
-        return true;
-      },
-      [&](const ast::ExternalTexture*) {
-        out << "texture_external";
-        return true;
-      },
-      [&](const ast::Texture* texture) {
-        out << "texture_";
-        bool ok = Switch(
-            texture,
-            [&](const ast::DepthTexture*) {  //
-              out << "depth_";
-              return true;
-            },
-            [&](const ast::DepthMultisampledTexture*) {  //
-              out << "depth_multisampled_";
-              return true;
-            },
-            [&](const ast::SampledTexture*) {  //
-              /* nothing to emit */
-              return true;
-            },
-            [&](const ast::MultisampledTexture*) {  //
-              out << "multisampled_";
-              return true;
-            },
-            [&](const ast::StorageTexture*) {  //
-              out << "storage_";
-              return true;
-            },
-            [&](Default) {  //
-              diagnostics_.add_error(diag::System::Writer,
-                                     "unknown texture type");
-              return false;
-            });
-        if (!ok) {
-          return false;
-        }
-
-        switch (texture->dim) {
-          case ast::TextureDimension::k1d:
-            out << "1d";
-            break;
-          case ast::TextureDimension::k2d:
-            out << "2d";
-            break;
-          case ast::TextureDimension::k2dArray:
-            out << "2d_array";
-            break;
-          case ast::TextureDimension::k3d:
-            out << "3d";
-            break;
-          case ast::TextureDimension::kCube:
-            out << "cube";
-            break;
-          case ast::TextureDimension::kCubeArray:
-            out << "cube_array";
-            break;
-          default:
-            diagnostics_.add_error(diag::System::Writer,
-                                   "unknown texture dimension");
-            return false;
-        }
-
-        return Switch(
-            texture,
-            [&](const ast::SampledTexture* sampled) {  //
-              out << "<";
-              if (!EmitType(out, sampled->type)) {
-                return false;
-              }
-              out << ">";
-              return true;
-            },
-            [&](const ast::MultisampledTexture* ms) {  //
-              out << "<";
-              if (!EmitType(out, ms->type)) {
-                return false;
-              }
-              out << ">";
-              return true;
-            },
-            [&](const ast::StorageTexture* storage) {  //
-              out << "<";
-              if (!EmitImageFormat(out, storage->format)) {
-                return false;
-              }
-              out << ", ";
-              if (!EmitAccess(out, storage->access)) {
-                return false;
-              }
-              out << ">";
-              return true;
-            },
-            [&](Default) {  //
-              return true;
-            });
-      },
-      [&](const ast::U32*) {
-        out << "u32";
-        return true;
-      },
-      [&](const ast::Vector* vec) {
-        out << "vec" << vec->width;
-        if (auto* el_ty = vec->type) {
-          out << "<";
-          if (!EmitType(out, el_ty)) {
-            return false;
-          }
-          out << ">";
-        }
-        return true;
-      },
-      [&](const ast::Void*) {
-        out << "void";
-        return true;
-      },
-      [&](const ast::TypeName* tn) {
-        out << program_->Symbols().NameFor(tn->name);
-        return true;
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown type in EmitType: " + std::string(ty->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitStructType(const ast::Struct* str) {
-  if (str->attributes.size()) {
-    if (!EmitAttributes(line(), str->attributes)) {
-      return false;
-    }
-  }
-  line() << "struct " << program_->Symbols().NameFor(str->name) << " {";
-
-  auto add_padding = [&](uint32_t size) {
-    line() << "@size(" << size << ")";
-
-    // Note: u32 is the smallest primitive we currently support. When WGSL
-    // supports smaller types, this will need to be updated.
-    line() << UniqueIdentifier("padding") << " : u32;";
-  };
-
-  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.
-    if (auto* mem_sem = program_->Sem().Get(mem)) {
-      offset = utils::RoundUp(mem_sem->Align(), offset);
-      if (uint32_t padding = mem_sem->Offset() - offset) {
-        add_padding(padding);
-        offset += padding;
-      }
-      offset += mem_sem->Size();
-    }
-
-    // Offset attributes no longer exist in the WGSL spec, but are emitted
-    // by the SPIR-V reader and are consumed by the Resolver(). These should not
-    // be emitted, but instead struct padding fields should be emitted.
-    ast::AttributeList attributes_sanitized;
-    attributes_sanitized.reserve(mem->attributes.size());
-    for (auto* attr : mem->attributes) {
-      if (!attr->Is<ast::StructMemberOffsetAttribute>()) {
-        attributes_sanitized.emplace_back(attr);
-      }
-    }
-
-    if (!attributes_sanitized.empty()) {
-      if (!EmitAttributes(line(), attributes_sanitized)) {
-        return false;
-      }
-    }
-
-    auto out = line();
-    out << program_->Symbols().NameFor(mem->symbol) << " : ";
-    if (!EmitType(out, mem->type)) {
-      return false;
-    }
-    out << ";";
-  }
-  decrement_indent();
-
-  line() << "}";
-  return true;
-}
-
-bool GeneratorImpl::EmitVariable(std::ostream& out, const ast::Variable* var) {
-  if (!var->attributes.empty()) {
-    if (!EmitAttributes(out, var->attributes)) {
-      return false;
-    }
-    out << " ";
-  }
-
-  if (var->is_overridable) {
-    out << "override";
-  } else if (var->is_const) {
-    out << "let";
-  } else {
-    out << "var";
-    auto sc = var->declared_storage_class;
-    auto ac = var->declared_access;
-    if (sc != ast::StorageClass::kNone || ac != ast::Access::kUndefined) {
-      out << "<" << sc;
-      if (ac != ast::Access::kUndefined) {
-        out << ", ";
-        if (!EmitAccess(out, ac)) {
-          return false;
-        }
-      }
-      out << ">";
-    }
-  }
-
-  out << " " << program_->Symbols().NameFor(var->symbol);
-
-  if (auto* ty = var->type) {
-    out << " : ";
-    if (!EmitType(out, ty)) {
-      return false;
-    }
-  }
-
-  if (var->constructor != nullptr) {
-    out << " = ";
-    if (!EmitExpression(out, var->constructor)) {
-      return false;
-    }
-  }
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitAttributes(std::ostream& out,
-                                   const ast::AttributeList& attrs) {
-  bool first = true;
-  for (auto* attr : attrs) {
-    if (!first) {
-      out << " ";
-    }
-    first = false;
-    out << "@";
-    bool ok = Switch(
-        attr,
-        [&](const ast::WorkgroupAttribute* workgroup) {
-          auto values = workgroup->Values();
-          out << "workgroup_size(";
-          for (int i = 0; i < 3; i++) {
-            if (values[i]) {
-              if (i > 0) {
-                out << ", ";
-              }
-              if (!EmitExpression(out, values[i])) {
-                return false;
-              }
-            }
-          }
-          out << ")";
-          return true;
-        },
-        [&](const ast::StructBlockAttribute*) {  //
-          out << "block";
-          return true;
-        },
-        [&](const ast::StageAttribute* stage) {
-          out << "stage(" << stage->stage << ")";
-          return true;
-        },
-        [&](const ast::BindingAttribute* binding) {
-          out << "binding(" << binding->value << ")";
-          return true;
-        },
-        [&](const ast::GroupAttribute* group) {
-          out << "group(" << group->value << ")";
-          return true;
-        },
-        [&](const ast::LocationAttribute* location) {
-          out << "location(" << location->value << ")";
-          return true;
-        },
-        [&](const ast::BuiltinAttribute* builtin) {
-          out << "builtin(" << builtin->builtin << ")";
-          return true;
-        },
-        [&](const ast::InterpolateAttribute* interpolate) {
-          out << "interpolate(" << interpolate->type;
-          if (interpolate->sampling != ast::InterpolationSampling::kNone) {
-            out << ", " << interpolate->sampling;
-          }
-          out << ")";
-          return true;
-        },
-        [&](const ast::InvariantAttribute*) {
-          out << "invariant";
-          return true;
-        },
-        [&](const ast::IdAttribute* override_deco) {
-          out << "id(" << override_deco->value << ")";
-          return true;
-        },
-        [&](const ast::StructMemberSizeAttribute* size) {
-          out << "size(" << size->size << ")";
-          return true;
-        },
-        [&](const ast::StructMemberAlignAttribute* align) {
-          out << "align(" << align->align << ")";
-          return true;
-        },
-        [&](const ast::StrideAttribute* stride) {
-          out << "stride(" << stride->stride << ")";
-          return true;
-        },
-        [&](const ast::InternalAttribute* internal) {
-          out << "internal(" << internal->InternalName() << ")";
-          return true;
-        },
-        [&](Default) {
-          TINT_ICE(Writer, diagnostics_)
-              << "Unsupported attribute '" << attr->TypeInfo().name << "'";
-          return false;
-        });
-
-    if (!ok) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBinary(std::ostream& out,
-                               const ast::BinaryExpression* expr) {
-  out << "(";
-
-  if (!EmitExpression(out, expr->lhs)) {
-    return false;
-  }
-  out << " ";
-
-  switch (expr->op) {
-    case ast::BinaryOp::kAnd:
-      out << "&";
-      break;
-    case ast::BinaryOp::kOr:
-      out << "|";
-      break;
-    case ast::BinaryOp::kXor:
-      out << "^";
-      break;
-    case ast::BinaryOp::kLogicalAnd:
-      out << "&&";
-      break;
-    case ast::BinaryOp::kLogicalOr:
-      out << "||";
-      break;
-    case ast::BinaryOp::kEqual:
-      out << "==";
-      break;
-    case ast::BinaryOp::kNotEqual:
-      out << "!=";
-      break;
-    case ast::BinaryOp::kLessThan:
-      out << "<";
-      break;
-    case ast::BinaryOp::kGreaterThan:
-      out << ">";
-      break;
-    case ast::BinaryOp::kLessThanEqual:
-      out << "<=";
-      break;
-    case ast::BinaryOp::kGreaterThanEqual:
-      out << ">=";
-      break;
-    case ast::BinaryOp::kShiftLeft:
-      out << "<<";
-      break;
-    case ast::BinaryOp::kShiftRight:
-      out << ">>";
-      break;
-    case ast::BinaryOp::kAdd:
-      out << "+";
-      break;
-    case ast::BinaryOp::kSubtract:
-      out << "-";
-      break;
-    case ast::BinaryOp::kMultiply:
-      out << "*";
-      break;
-    case ast::BinaryOp::kDivide:
-      out << "/";
-      break;
-    case ast::BinaryOp::kModulo:
-      out << "%";
-      break;
-    case ast::BinaryOp::kNone:
-      diagnostics_.add_error(diag::System::Writer,
-                             "missing binary operation type");
-      return false;
-  }
-  out << " ";
-
-  if (!EmitExpression(out, expr->rhs)) {
-    return false;
-  }
-
-  out << ")";
-  return true;
-}
-
-bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
-                                const ast::UnaryOpExpression* expr) {
-  switch (expr->op) {
-    case ast::UnaryOp::kAddressOf:
-      out << "&";
-      break;
-    case ast::UnaryOp::kComplement:
-      out << "~";
-      break;
-    case ast::UnaryOp::kIndirection:
-      out << "*";
-      break;
-    case ast::UnaryOp::kNot:
-      out << "!";
-      break;
-    case ast::UnaryOp::kNegation:
-      out << "-";
-      break;
-  }
-  out << "(";
-
-  if (!EmitExpression(out, expr->expr)) {
-    return false;
-  }
-
-  out << ")";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
-  line() << "{";
-  if (!EmitStatementsWithIndent(stmt->statements)) {
-    return false;
-  }
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
-  return Switch(
-      stmt,  //
-      [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
-      [&](const ast::BlockStatement* b) { return EmitBlock(b); },
-      [&](const ast::BreakStatement* b) { return EmitBreak(b); },
-      [&](const ast::CallStatement* c) {
-        auto out = line();
-        if (!EmitCall(out, c->expr)) {
-          return false;
-        }
-        out << ";";
-        return true;
-      },
-      [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
-      [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
-      [&](const ast::FallthroughStatement* f) { return EmitFallthrough(f); },
-      [&](const ast::IfStatement* i) { return EmitIf(i); },
-      [&](const ast::LoopStatement* l) { return EmitLoop(l); },
-      [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
-      [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
-      [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
-      [&](const ast::VariableDeclStatement* v) {
-        return EmitVariable(line(), v->variable);
-      },
-      [&](Default) {
-        diagnostics_.add_error(
-            diag::System::Writer,
-            "unknown statement type: " + std::string(stmt->TypeInfo().name));
-        return false;
-      });
-}
-
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
-  for (auto* s : stmts) {
-    if (!EmitStatement(s)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
-  ScopedIndent si(this);
-  return EmitStatements(stmts);
-}
-
-bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
-  auto out = line();
-
-  if (!EmitExpression(out, stmt->lhs)) {
-    return false;
-  }
-
-  out << " = ";
-
-  if (!EmitExpression(out, stmt->rhs)) {
-    return false;
-  }
-
-  out << ";";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
-  line() << "break;";
-  return true;
-}
-
-bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
-  if (stmt->IsDefault()) {
-    line() << "default: {";
-  } else {
-    auto out = line();
-    out << "case ";
-
-    bool first = true;
-    for (auto* selector : stmt->selectors) {
-      if (!first) {
-        out << ", ";
-      }
-
-      first = false;
-      if (!EmitLiteral(out, selector)) {
-        return false;
-      }
-    }
-    out << ": {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  line() << "}";
-  return true;
-}
-
-bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
-  line() << "continue;";
-  return true;
-}
-
-bool GeneratorImpl::EmitFallthrough(const ast::FallthroughStatement*) {
-  line() << "fallthrough;";
-  return true;
-}
-
-bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
-  {
-    auto out = line();
-    out << "if (";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  for (auto* e : stmt->else_statements) {
-    if (e->condition) {
-      auto out = line();
-      out << "} else if (";
-      if (!EmitExpression(out, e->condition)) {
-        return false;
-      }
-      out << ") {";
-    } else {
-      line() << "} else {";
-    }
-
-    if (!EmitStatementsWithIndent(e->body->statements)) {
-      return false;
-    }
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
-  line() << "discard;";
-  return true;
-}
-
-bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
-  line() << "loop {";
-  increment_indent();
-
-  if (!EmitStatements(stmt->body->statements)) {
-    return false;
-  }
-
-  if (stmt->continuing && !stmt->continuing->Empty()) {
-    line();
-    line() << "continuing {";
-    if (!EmitStatementsWithIndent(stmt->continuing->statements)) {
-      return false;
-    }
-    line() << "}";
-  }
-
-  decrement_indent();
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
-  TextBuffer init_buf;
-  if (auto* init = stmt->initializer) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
-    if (!EmitStatement(init)) {
-      return false;
-    }
-  }
-
-  TextBuffer cont_buf;
-  if (auto* cont = stmt->continuing) {
-    TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
-    if (!EmitStatement(cont)) {
-      return false;
-    }
-  }
-
-  {
-    auto out = line();
-    out << "for";
-    {
-      ScopedParen sp(out);
-      switch (init_buf.lines.size()) {
-        case 0:  // No initializer
-          break;
-        case 1:  // Single line initializer statement
-          out << TrimSuffix(init_buf.lines[0].content, ";");
-          break;
-        default:  // Block initializer statement
-          for (size_t i = 1; i < init_buf.lines.size(); i++) {
-            // Indent all by the first line
-            init_buf.lines[i].indent += current_buffer_->current_indent;
-          }
-          out << TrimSuffix(init_buf.String(), "\n");
-          break;
-      }
-
-      out << "; ";
-
-      if (auto* cond = stmt->condition) {
-        if (!EmitExpression(out, cond)) {
-          return false;
-        }
-      }
-
-      out << "; ";
-
-      switch (cont_buf.lines.size()) {
-        case 0:  // No continuing
-          break;
-        case 1:  // Single line continuing statement
-          out << TrimSuffix(cont_buf.lines[0].content, ";");
-          break;
-        default:  // Block continuing statement
-          for (size_t i = 1; i < cont_buf.lines.size(); i++) {
-            // Indent all by the first line
-            cont_buf.lines[i].indent += current_buffer_->current_indent;
-          }
-          out << TrimSuffix(cont_buf.String(), "\n");
-          break;
-      }
-    }
-    out << " {";
-  }
-
-  if (!EmitStatementsWithIndent(stmt->body->statements)) {
-    return false;
-  }
-
-  line() << "}";
-
-  return true;
-}
-
-bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
-  auto out = line();
-  out << "return";
-  if (stmt->value) {
-    out << " ";
-    if (!EmitExpression(out, stmt->value)) {
-      return false;
-    }
-  }
-  out << ";";
-  return true;
-}
-
-bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
-  {
-    auto out = line();
-    out << "switch(";
-    if (!EmitExpression(out, stmt->condition)) {
-      return false;
-    }
-    out << ") {";
-  }
-
-  {
-    ScopedIndent si(this);
-    for (auto* s : stmt->body) {
-      if (!EmitCase(s)) {
-        return false;
-      }
-    }
-  }
-
-  line() << "}";
-  return true;
-}
-
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl.h b/src/writer/wgsl/generator_impl.h
deleted file mode 100644
index 48aeccd..0000000
--- a/src/writer/wgsl/generator_impl.h
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2020 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_WRITER_WGSL_GENERATOR_IMPL_H_
-#define SRC_WRITER_WGSL_GENERATOR_IMPL_H_
-
-#include <string>
-
-#include "src/ast/assignment_statement.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/break_statement.h"
-#include "src/ast/continue_statement.h"
-#include "src/ast/discard_statement.h"
-#include "src/ast/fallthrough_statement.h"
-#include "src/ast/for_loop_statement.h"
-#include "src/ast/if_statement.h"
-#include "src/ast/index_accessor_expression.h"
-#include "src/ast/loop_statement.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/switch_statement.h"
-#include "src/ast/unary_op_expression.h"
-#include "src/program.h"
-#include "src/sem/storage_texture_type.h"
-#include "src/sem/struct.h"
-#include "src/writer/text_generator.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-
-/// Implementation class for WGSL generator
-class GeneratorImpl : public TextGenerator {
- public:
-  /// Constructor
-  /// @param program the program
-  explicit GeneratorImpl(const Program* program);
-  ~GeneratorImpl();
-
-  /// Generates the result data
-  /// @returns true on successful generation; false otherwise
-  bool Generate();
-
-  /// Handles generating a declared type
-  /// @param ty the declared type to generate
-  /// @returns true if the declared type was emitted
-  bool EmitTypeDecl(const ast::TypeDecl* ty);
-  /// Handles an index accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the index accessor was emitted
-  bool EmitIndexAccessor(std::ostream& out,
-                         const ast::IndexAccessorExpression* expr);
-  /// Handles an assignment statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitAssign(const ast::AssignmentStatement* stmt);
-  /// Handles generating a binary expression
-  /// @param out the output of the expression stream
-  /// @param expr the binary expression
-  /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
-  /// Handles generating a bitcast expression
-  /// @param out the output of the expression stream
-  /// @param expr the bitcast expression
-  /// @returns true if the bitcast was emitted
-  bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
-  /// Handles a block statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBlock(const ast::BlockStatement* stmt);
-  /// Handles a break statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitBreak(const ast::BreakStatement* stmt);
-  /// Handles generating a call expression
-  /// @param out the output of the expression stream
-  /// @param expr the call expression
-  /// @returns true if the call expression is emitted
-  bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
-  /// Handles a case statement
-  /// @param stmt the statement
-  /// @returns true if the statment was emitted successfully
-  bool EmitCase(const ast::CaseStatement* stmt);
-  /// Handles generating a literal expression
-  /// @param out the output of the expression stream
-  /// @param expr the literal expression expression
-  /// @returns true if the literal expression is emitted
-  bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* expr);
-  /// Handles a continue statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted successfully
-  bool EmitContinue(const ast::ContinueStatement* stmt);
-  /// Handles generate an Expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression
-  /// @returns true if the expression was emitted
-  bool EmitExpression(std::ostream& out, const ast::Expression* expr);
-  /// Handles generating a fallthrough statement
-  /// @param stmt the fallthrough statement
-  /// @returns true if the statement was successfully emitted
-  bool EmitFallthrough(const ast::FallthroughStatement* stmt);
-  /// Handles generating a function
-  /// @param func the function to generate
-  /// @returns true if the function was emitted
-  bool EmitFunction(const ast::Function* func);
-  /// Handles generating an identifier expression
-  /// @param out the output of the expression stream
-  /// @param expr the identifier expression
-  /// @returns true if the identifier was emitted
-  bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
-  /// Handles an if statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitIf(const ast::IfStatement* stmt);
-  /// Handles generating a discard statement
-  /// @param stmt the discard statement
-  /// @returns true if the statement was successfully emitted
-  bool EmitDiscard(const ast::DiscardStatement* stmt);
-  /// Handles a loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emtited
-  bool EmitLoop(const ast::LoopStatement* stmt);
-  /// Handles a for-loop statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emtited
-  bool EmitForLoop(const ast::ForLoopStatement* stmt);
-  /// Handles a member accessor expression
-  /// @param out the output of the expression stream
-  /// @param expr the member accessor expression
-  /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(std::ostream& out,
-                          const ast::MemberAccessorExpression* expr);
-  /// Handles return statements
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was successfully emitted
-  bool EmitReturn(const ast::ReturnStatement* stmt);
-  /// Handles statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitStatement(const ast::Statement* stmt);
-  /// Handles a statement list
-  /// @param stmts the statements to emit
-  /// @returns true if the statements were emitted
-  bool EmitStatements(const ast::StatementList& stmts);
-  /// Handles a statement list with an increased indentation
-  /// @param stmts the statements to emit
-  /// @returns true if the statements were emitted
-  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
-  /// Handles generating a switch statement
-  /// @param stmt the statement to emit
-  /// @returns true if the statement was emitted
-  bool EmitSwitch(const ast::SwitchStatement* stmt);
-  /// Handles generating type
-  /// @param out the output of the expression stream
-  /// @param type the type to generate
-  /// @returns true if the type is emitted
-  bool EmitType(std::ostream& out, const ast::Type* type);
-  /// Handles generating a struct declaration
-  /// @param str the struct
-  /// @returns true if the struct is emitted
-  bool EmitStructType(const ast::Struct* str);
-  /// Handles emitting an image format
-  /// @param out the output of the expression stream
-  /// @param fmt the format to generate
-  /// @returns true if the format is emitted
-  bool EmitImageFormat(std::ostream& out, const ast::TexelFormat fmt);
-  /// Handles emitting an access control
-  /// @param out the output of the expression stream
-  /// @param access the access to generate
-  /// @returns true if the access is emitted
-  bool EmitAccess(std::ostream& out, const ast::Access access);
-  /// Handles a unary op expression
-  /// @param out the output of the expression stream
-  /// @param expr the expression to emit
-  /// @returns true if the expression was emitted
-  bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
-  /// Handles generating a variable
-  /// @param out the output of the expression stream
-  /// @param var the variable to generate
-  /// @returns true if the variable was emitted
-  bool EmitVariable(std::ostream& out, const ast::Variable* var);
-  /// Handles generating a attribute list
-  /// @param out the output of the expression stream
-  /// @param attrs the attribute list
-  /// @returns true if the attributes were emitted
-  bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
-};
-
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_WGSL_GENERATOR_IMPL_H_
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
deleted file mode 100644
index f1a2f47..0000000
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitAlias_F32) {
-  auto* alias = Alias("a", ty.f32());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(type a = f32;
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitTypeDecl_Struct) {
-  auto* s = Structure("A", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
-
-  auto* alias = Alias("B", ty.Of(s));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitTypeDecl(s)) << gen.error();
-  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct A {
-  a : f32;
-  b : i32;
-}
-type B = A;
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitAlias_ToStruct) {
-  auto* s = Structure("A", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
-
-  auto* alias = Alias("B", ty.Of(s));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitTypeDecl(alias)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(type B = A;
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_array_accessor_test.cc b/src/writer/wgsl/generator_impl_array_accessor_test.cc
deleted file mode 100644
index e6d5af3..0000000
--- a/src/writer/wgsl/generator_impl_array_accessor_test.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, IndexAccessor) {
-  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
-  auto* expr = IndexAccessor("ary", 5);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "ary[5]");
-}
-
-TEST_F(WgslGeneratorImplTest, IndexAccessor_OfDref) {
-  Global("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
-
-  auto* p = Const("p", nullptr, AddressOf("ary"));
-  auto* expr = IndexAccessor(Deref("p"), 5);
-  WrapInFunction(p, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(*(p))[5]");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_assign_test.cc b/src/writer/wgsl/generator_impl_assign_test.cc
deleted file mode 100644
index 4b90f6a..0000000
--- a/src/writer/wgsl/generator_impl_assign_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Assign) {
-  auto* lhs = Global("lhs", ty.i32(), ast::StorageClass::kPrivate);
-  auto* rhs = Global("rhs", ty.i32(), ast::StorageClass::kPrivate);
-  auto* assign = Assign(lhs, rhs);
-  WrapInFunction(assign);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
-  EXPECT_EQ(gen.result(), "  lhs = rhs;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_binary_test.cc b/src/writer/wgsl/generator_impl_binary_test.cc
deleted file mode 100644
index aeaf894..0000000
--- a/src/writer/wgsl/generator_impl_binary_test.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-struct BinaryData {
-  const char* result;
-  ast::BinaryOp op;
-};
-inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
-  out << data.op;
-  return out;
-}
-using WgslBinaryTest = TestParamHelper<BinaryData>;
-TEST_P(WgslBinaryTest, Emit) {
-  auto params = GetParam();
-
-  auto op_ty = [&]() -> const ast::Type* {
-    if (params.op == ast::BinaryOp::kLogicalAnd ||
-        params.op == ast::BinaryOp::kLogicalOr) {
-      return ty.bool_();
-    } else {
-      return ty.u32();
-    }
-  };
-
-  Global("left", op_ty(), ast::StorageClass::kPrivate);
-  Global("right", op_ty(), ast::StorageClass::kPrivate);
-  auto* left = Expr("left");
-  auto* right = Expr("right");
-
-  auto* expr = create<ast::BinaryExpression>(params.op, left, right);
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), params.result);
-}
-INSTANTIATE_TEST_SUITE_P(
-    WgslGeneratorImplTest,
-    WgslBinaryTest,
-    testing::Values(
-        BinaryData{"(left & right)", ast::BinaryOp::kAnd},
-        BinaryData{"(left | right)", ast::BinaryOp::kOr},
-        BinaryData{"(left ^ right)", ast::BinaryOp::kXor},
-        BinaryData{"(left && right)", ast::BinaryOp::kLogicalAnd},
-        BinaryData{"(left || right)", ast::BinaryOp::kLogicalOr},
-        BinaryData{"(left == right)", ast::BinaryOp::kEqual},
-        BinaryData{"(left != right)", ast::BinaryOp::kNotEqual},
-        BinaryData{"(left < right)", ast::BinaryOp::kLessThan},
-        BinaryData{"(left > right)", ast::BinaryOp::kGreaterThan},
-        BinaryData{"(left <= right)", ast::BinaryOp::kLessThanEqual},
-        BinaryData{"(left >= right)", ast::BinaryOp::kGreaterThanEqual},
-        BinaryData{"(left << right)", ast::BinaryOp::kShiftLeft},
-        BinaryData{"(left >> right)", ast::BinaryOp::kShiftRight},
-        BinaryData{"(left + right)", ast::BinaryOp::kAdd},
-        BinaryData{"(left - right)", ast::BinaryOp::kSubtract},
-        BinaryData{"(left * right)", ast::BinaryOp::kMultiply},
-        BinaryData{"(left / right)", ast::BinaryOp::kDivide},
-        BinaryData{"(left % right)", ast::BinaryOp::kModulo}));
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_bitcast_test.cc b/src/writer/wgsl/generator_impl_bitcast_test.cc
deleted file mode 100644
index 31d0f6a..0000000
--- a/src/writer/wgsl/generator_impl_bitcast_test.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_Bitcast) {
-  auto* bitcast = create<ast::BitcastExpression>(ty.f32(), Expr(1));
-  WrapInFunction(bitcast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
-  EXPECT_EQ(out.str(), "bitcast<f32>(1)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_block_test.cc b/src/writer/wgsl/generator_impl_block_test.cc
deleted file mode 100644
index c1bc7b5..0000000
--- a/src/writer/wgsl/generator_impl_block_test.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Block) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  {
-    discard;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_break_test.cc b/src/writer/wgsl/generator_impl_break_test.cc
deleted file mode 100644
index 8585323..0000000
--- a/src/writer/wgsl/generator_impl_break_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Break) {
-  auto* b = create<ast::BreakStatement>();
-  WrapInFunction(Loop(Block(b)));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
-  EXPECT_EQ(gen.result(), "  break;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_call_test.cc b/src/writer/wgsl/generator_impl_call_test.cc
deleted file mode 100644
index 7a3b5a3..0000000
--- a/src/writer/wgsl/generator_impl_call_test.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2020 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/ast/call_statement.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
-  Func("my_func", {}, ty.f32(), {Return(1.23f)});
-
-  auto* call = Call("my_func");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func()");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithParams) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.f32(), {Return(1.23f)});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  WrapInFunction(call);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-  EXPECT_EQ(out.str(), "my_func(param1, param2)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitStatement_Call) {
-  Func("my_func",
-       {
-           Param(Sym(), ty.f32()),
-           Param(Sym(), ty.f32()),
-       },
-       ty.void_(), ast::StatementList{}, ast::AttributeList{});
-  Global("param1", ty.f32(), ast::StorageClass::kPrivate);
-  Global("param2", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* call = Call("my_func", "param1", "param2");
-  auto* stmt = CallStmt(call);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_case_test.cc b/src/writer/wgsl/generator_impl_case_test.cc
deleted file mode 100644
index 95851e0..0000000
--- a/src/writer/wgsl/generator_impl_case_test.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Case) {
-  auto* s = Switch(1, Case(Expr(5), Block(create<ast::BreakStatement>())),
-                   DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5: {
-    break;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Case_MultipleSelectors) {
-  auto* s =
-      Switch(1, Case({Expr(5), Expr(6)}, Block(create<ast::BreakStatement>())),
-             DefaultCase());
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  case 5, 6: {
-    break;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Case_Default) {
-  auto* s = Switch(1, DefaultCase(Block(create<ast::BreakStatement>())));
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  default: {
-    break;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_cast_test.cc b/src/writer/wgsl/generator_impl_cast_test.cc
deleted file mode 100644
index 1af5088..0000000
--- a/src/writer/wgsl/generator_impl_cast_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_Cast_Scalar) {
-  auto* cast = Construct<f32>(1);
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "f32(1)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_Cast_Vector) {
-  auto* cast = vec3<f32>(vec3<i32>(1, 2, 3));
-  WrapInFunction(cast);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-  EXPECT_EQ(out.str(), "vec3<f32>(vec3<i32>(1, 2, 3))");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_constructor_test.cc b/src/writer/wgsl/generator_impl_constructor_test.cc
deleted file mode 100644
index aaf382c..0000000
--- a/src/writer/wgsl/generator_impl_constructor_test.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2020 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 "gmock/gmock.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Bool) {
-  WrapInFunction(Expr(false));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("false"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Int) {
-  WrapInFunction(Expr(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_UInt) {
-  WrapInFunction(Expr(56779u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Float) {
-  // Use a number close to 1<<30 but whose decimal representation ends in 0.
-  WrapInFunction(Expr(static_cast<float>((1 << 30) - 4)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Float) {
-  WrapInFunction(Construct<f32>(Expr(-1.2e-5f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("f32(-0.000012)"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Bool) {
-  WrapInFunction(Construct<bool>(true));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Int) {
-  WrapInFunction(Construct<i32>(-12345));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("i32(-12345)"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Uint) {
-  WrapInFunction(Construct<u32>(12345u));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("u32(12345u)"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Vec) {
-  WrapInFunction(vec3<f32>(1.f, 2.f, 3.f));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("vec3<f32>(1.0, 2.0, 3.0)"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Mat) {
-  WrapInFunction(
-      mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(), HasSubstr("mat2x3<f32>(vec3<f32>(1.0, 2.0, 3.0), "
-                                      "vec3<f32>(3.0, 4.0, 5.0))"));
-}
-
-TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Array) {
-  WrapInFunction(
-      Construct(ty.array(ty.vec3<f32>(), 3), vec3<f32>(1.0f, 2.0f, 3.0f),
-                vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f)));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_THAT(gen.result(),
-              HasSubstr("array<vec3<f32>, 3>(vec3<f32>(1.0, 2.0, 3.0), "
-                        "vec3<f32>(4.0, 5.0, 6.0), vec3<f32>(7.0, 8.0, 9.0))"));
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_continue_test.cc b/src/writer/wgsl/generator_impl_continue_test.cc
deleted file mode 100644
index d73c3d5..0000000
--- a/src/writer/wgsl/generator_impl_continue_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Continue) {
-  auto* c = Continue();
-
-  WrapInFunction(Loop(Block(If(false, Block(Break())),  //
-                            c)));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(c)) << gen.error();
-  EXPECT_EQ(gen.result(), "  continue;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_discard_test.cc b/src/writer/wgsl/generator_impl_discard_test.cc
deleted file mode 100644
index bb4ebf0..0000000
--- a/src/writer/wgsl/generator_impl_discard_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Discard) {
-  auto* stmt = create<ast::DiscardStatement>();
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  discard;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_fallthrough_test.cc b/src/writer/wgsl/generator_impl_fallthrough_test.cc
deleted file mode 100644
index aa1d037..0000000
--- a/src/writer/wgsl/generator_impl_fallthrough_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Fallthrough) {
-  auto* f = create<ast::FallthroughStatement>();
-  WrapInFunction(Switch(1,                        //
-                        Case(Expr(1), Block(f)),  //
-                        DefaultCase()));
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), "  fallthrough;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
deleted file mode 100644
index 54bb8b0..0000000
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2020 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/ast/stage_attribute.h"
-#include "src/ast/struct_block_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/ast/workgroup_attribute.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Function) {
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    ast::StatementList{
-                        Return(),
-                    },
-                    ast::AttributeList{});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  fn my_func() {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Function_WithParams) {
-  auto* func = Func(
-      "my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
-      ty.void_(),
-      ast::StatementList{
-          Return(),
-      },
-      ast::AttributeList{});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  fn my_func(a : f32, b : i32) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Function_WithAttribute_WorkgroupSize) {
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    ast::StatementList{Return()},
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kCompute),
-                        WorkgroupSize(2, 4, 6),
-                    });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(2, 4, 6)
-  fn my_func() {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest,
-       Emit_Function_WithAttribute_WorkgroupSize_WithIdent) {
-  GlobalConst("height", ty.i32(), Expr(2));
-  auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
-                    ast::StatementList{Return()},
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kCompute),
-                        WorkgroupSize(2, "height"),
-                    });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(2, height)
-  fn my_func() {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
-  auto* vec4 = ty.vec4<f32>();
-  auto* coord = Param("coord", vec4, {Builtin(ast::Builtin::kPosition)});
-  auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
-  auto* func = Func("frag_main", ast::VariableList{coord, loc1}, ty.void_(),
-                    ast::StatementList{},
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kFragment),
-                    });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  @stage(fragment)
-  fn frag_main(@builtin(position) coord : vec4<f32>, @location(1) loc1 : f32) {
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_ReturnValue) {
-  auto* func = Func("frag_main", ast::VariableList{}, ty.f32(),
-                    ast::StatementList{
-                        Return(1.f),
-                    },
-                    ast::AttributeList{
-                        Stage(ast::PipelineStage::kFragment),
-                    },
-                    ast::AttributeList{
-                        Location(1u),
-                    });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitFunction(func));
-  EXPECT_EQ(gen.result(), R"(  @stage(fragment)
-  fn frag_main() -> @location(1) f32 {
-    return 1.0;
-  }
-)");
-}
-
-// https://crbug.com/tint/297
-TEST_F(WgslGeneratorImplTest,
-       Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
-  // [[block]] struct Data {
-  //   d : f32;
-  // };
-  // @binding(0) @group(0) var<storage> data : Data;
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn a() {
-  //   return;
-  // }
-  //
-  // @stage(compute) @workgroup_size(1)
-  // fn b() {
-  //   return;
-  // }
-
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockAttribute>()});
-
-  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(0),
-             create<ast::GroupAttribute>(0),
-         });
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("a", ast::VariableList{}, ty.void_(),
-         ast::StatementList{
-             Decl(var),
-             Return(),
-         },
-         ast::AttributeList{
-             Stage(ast::PipelineStage::kCompute),
-             WorkgroupSize(1),
-         });
-  }
-
-  {
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor("data", "d"));
-
-    Func("b", ast::VariableList{}, ty.void_(),
-         ast::StatementList{
-             Decl(var),
-             Return(),
-         },
-         ast::AttributeList{
-             Stage(ast::PipelineStage::kCompute),
-             WorkgroupSize(1),
-         });
-  }
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(@block
-struct Data {
-  d : f32;
-}
-
-@binding(0) @group(0) var<storage, read_write> data : Data;
-
-@stage(compute) @workgroup_size(1)
-fn a() {
-  var v : f32 = data.d;
-  return;
-}
-
-@stage(compute) @workgroup_size(1)
-fn b() {
-  var v : f32 = data.d;
-  return;
-}
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_global_decl_test.cc b/src/writer/wgsl/generator_impl_global_decl_test.cc
deleted file mode 100644
index 87493c6..0000000
--- a/src/writer/wgsl/generator_impl_global_decl_test.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/stage_attribute.h"
-#include "src/ast/variable_decl_statement.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_GlobalDeclAfterFunction) {
-  auto* func_var = Var("a", ty.f32());
-  WrapInFunction(func_var);
-
-  Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  @stage(compute) @workgroup_size(1, 1, 1)
-  fn test_function() {
-    var a : f32;
-  }
-
-  var<private> a : f32;
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_GlobalsInterleaved) {
-  Global("a0", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* s0 = Structure("S0", {Member("a", ty.i32())});
-
-  Func("func", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return("a0"),
-       },
-       ast::AttributeList{});
-
-  Global("a1", ty.f32(), ast::StorageClass::kPrivate);
-
-  auto* s1 = Structure("S1", {Member("a", ty.i32())});
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           Decl(Var("s0", ty.Of(s0))),
-           Decl(Var("s1", ty.Of(s1))),
-           Assign("a1", Call("func")),
-       },
-       ast::AttributeList{
-           Stage(ast::PipelineStage::kCompute),
-           WorkgroupSize(1),
-       });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  var<private> a0 : f32;
-
-  struct S0 {
-    a : i32;
-  }
-
-  fn func() -> f32 {
-    return a0;
-  }
-
-  var<private> a1 : f32;
-
-  struct S1 {
-    a : i32;
-  }
-
-  @stage(compute) @workgroup_size(1)
-  fn main() {
-    var s0 : S0;
-    var s1 : S1;
-    a1 = func();
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
-  Global("s", ty.sampler(ast::SamplerKind::kSampler),
-         ast::AttributeList{
-             create<ast::GroupAttribute>(0),
-             create<ast::BindingAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), "  @group(0) @binding(0) var s : sampler;\n");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
-  auto* st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  Global("t", st,
-         ast::AttributeList{
-             create<ast::GroupAttribute>(0),
-             create<ast::BindingAttribute>(0),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), "  @group(0) @binding(0) var t : texture_1d<f32>;\n");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_OverridableConstants) {
-  Override("a", ty.f32(), nullptr);
-  Override("b", ty.f32(), nullptr, {Id(7u)});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  override a : f32;
-
-  @id(7) override b : f32;
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_identifier_test.cc b/src/writer/wgsl/generator_impl_identifier_test.cc
deleted file mode 100644
index 58dbb27..0000000
--- a/src/writer/wgsl/generator_impl_identifier_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitIdentifierExpression_Single) {
-  Global("glsl", ty.f32(), ast::StorageClass::kPrivate);
-  auto* i = Expr("glsl");
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
-  EXPECT_EQ(out.str(), "glsl");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_if_test.cc b/src/writer/wgsl/generator_impl_if_test.cc
deleted file mode 100644
index 36fe5d2..0000000
--- a/src/writer/wgsl/generator_impl_if_test.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_If) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body);
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_IfWithElseIf) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else if (else_cond) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_IfWithElse) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_body = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(nullptr, else_body)});
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_IfWithMultiple) {
-  Global("cond", ty.bool_(), ast::StorageClass::kPrivate);
-  Global("else_cond", ty.bool_(), ast::StorageClass::kPrivate);
-
-  auto* else_cond = Expr("else_cond");
-
-  auto* else_body = Block(Return());
-
-  auto* else_body_2 = Block(Return());
-
-  auto* cond = Expr("cond");
-  auto* body = Block(Return());
-  auto* i = If(cond, body,
-               ast::ElseStatementList{
-                   create<ast::ElseStatement>(else_cond, else_body),
-                   create<ast::ElseStatement>(nullptr, else_body_2),
-               });
-  WrapInFunction(i);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  if (cond) {
-    return;
-  } else if (else_cond) {
-    return;
-  } else {
-    return;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_literal_test.cc b/src/writer/wgsl/generator_impl_literal_test.cc
deleted file mode 100644
index 55fc678..0000000
--- a/src/writer/wgsl/generator_impl_literal_test.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2020 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 <cstring>
-
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-// Makes an IEEE 754 binary32 floating point number with
-// - 0 sign if sign is 0, 1 otherwise
-// - 'exponent_bits' is placed in the exponent space.
-//   So, the exponent bias must already be included.
-float MakeFloat(int sign, int biased_exponent, int mantissa) {
-  const uint32_t sign_bit = sign ? 0x80000000u : 0u;
-  // The binary32 exponent is 8 bits, just below the sign.
-  const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23;
-  // The mantissa is the bottom 23 bits.
-  const uint32_t mantissa_bits = (mantissa & 0x7fffffu);
-
-  uint32_t bits = sign_bit | exponent_bits | mantissa_bits;
-  float result = 0.0f;
-  static_assert(sizeof(result) == sizeof(bits),
-                "expected float and uint32_t to be the same size");
-  std::memcpy(&result, &bits, sizeof(bits));
-  return result;
-}
-
-struct FloatData {
-  float value;
-  std::string expected;
-};
-inline std::ostream& operator<<(std::ostream& out, FloatData data) {
-  out << "{" << data.value << "," << data.expected << "}";
-  return out;
-}
-
-using WgslGenerator_FloatLiteralTest = TestParamHelper<FloatData>;
-
-TEST_P(WgslGenerator_FloatLiteralTest, Emit) {
-  auto* v = Expr(GetParam().value);
-
-  SetResolveOnBuild(false);
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitLiteral(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), GetParam().expected);
-}
-
-INSTANTIATE_TEST_SUITE_P(Zero,
-                         WgslGenerator_FloatLiteralTest,
-                         ::testing::ValuesIn(std::vector<FloatData>{
-                             {0.0f, "0.0"},
-                             {MakeFloat(0, 0, 0), "0.0"},
-                             {MakeFloat(1, 0, 0), "-0.0"}}));
-
-INSTANTIATE_TEST_SUITE_P(Normal,
-                         WgslGenerator_FloatLiteralTest,
-                         ::testing::ValuesIn(std::vector<FloatData>{
-                             {1.0f, "1.0"},
-                             {-1.0f, "-1.0"},
-                             {101.375, "101.375"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    Subnormal,
-    WgslGenerator_FloatLiteralTest,
-    ::testing::ValuesIn(std::vector<FloatData>{
-        {MakeFloat(0, 0, 1), "0x1p-149"},  // Smallest
-        {MakeFloat(1, 0, 1), "-0x1p-149"},
-        {MakeFloat(0, 0, 2), "0x1p-148"},
-        {MakeFloat(1, 0, 2), "-0x1p-148"},
-        {MakeFloat(0, 0, 0x7fffff), "0x1.fffffcp-127"},   // Largest
-        {MakeFloat(1, 0, 0x7fffff), "-0x1.fffffcp-127"},  // Largest
-        {MakeFloat(0, 0, 0xcafebe), "0x1.2bfaf8p-127"},   // Scattered bits
-        {MakeFloat(1, 0, 0xcafebe), "-0x1.2bfaf8p-127"},  // Scattered bits
-        {MakeFloat(0, 0, 0xaaaaa), "0x1.55554p-130"},     // Scattered bits
-        {MakeFloat(1, 0, 0xaaaaa), "-0x1.55554p-130"},    // Scattered bits
-    }));
-
-INSTANTIATE_TEST_SUITE_P(Infinity,
-                         WgslGenerator_FloatLiteralTest,
-                         ::testing::ValuesIn(std::vector<FloatData>{
-                             {MakeFloat(0, 255, 0), "0x1p+128"},
-                             {MakeFloat(1, 255, 0), "-0x1p+128"}}));
-
-INSTANTIATE_TEST_SUITE_P(
-    // TODO(dneto): It's unclear how Infinity and NaN should be handled.
-    // https://github.com/gpuweb/gpuweb/issues/1769
-    // This test fails on Windows x86-64 because the machine sets the high
-    // mantissa bit on NaNs.
-    DISABLED_NaN,
-    // In the NaN case, the top bit in the mantissa is often used to encode
-    // whether the NaN is signalling or quiet, but no agreement between
-    // different machine architectures on whether 1 means signalling or
-    // if 1 means quiet.
-    WgslGenerator_FloatLiteralTest,
-    ::testing::ValuesIn(std::vector<FloatData>{
-        // LSB only.  Smallest mantissa.
-        {MakeFloat(0, 255, 1), "0x1.000002p+128"},  // Smallest mantissa
-        {MakeFloat(1, 255, 1), "-0x1.000002p+128"},
-        // MSB only.
-        {MakeFloat(0, 255, 0x400000), "0x1.8p+128"},
-        {MakeFloat(1, 255, 0x400000), "-0x1.8p+128"},
-        // All 1s in the mantissa.
-        {MakeFloat(0, 255, 0x7fffff), "0x1.fffffep+128"},
-        {MakeFloat(1, 255, 0x7fffff), "-0x1.fffffep+128"},
-        // Scattered bits, with 0 in top mantissa bit.
-        {MakeFloat(0, 255, 0x20101f), "0x1.40203ep+128"},
-        {MakeFloat(1, 255, 0x20101f), "-0x1.40203ep+128"},
-        // Scattered bits, with 1 in top mantissa bit.
-        {MakeFloat(0, 255, 0x40101f), "0x1.80203ep+128"},
-        {MakeFloat(1, 255, 0x40101f), "-0x1.80203ep+128"}}));
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_loop_test.cc b/src/writer/wgsl/generator_impl_loop_test.cc
deleted file mode 100644
index d0b2e75..0000000
--- a/src/writer/wgsl/generator_impl_loop_test.cc
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Loop) {
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block();
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  loop {
-    discard;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_LoopWithContinuing) {
-  Func("a_statement", {}, ty.void_(), {});
-
-  auto* body = Block(create<ast::DiscardStatement>());
-  auto* continuing = Block(CallStmt(Call("a_statement")));
-  auto* l = Loop(body, continuing);
-
-  WrapInFunction(l);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  loop {
-    discard;
-
-    continuing {
-      a_statement();
-    }
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
-  // var<workgroup> a : atomic<i32>;
-  // for({ignore(1); ignore(2);}; ; ) {
-  //   return;
-  // }
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt = Block(Ignore(1), Ignore(2));
-  auto* f = For(multi_stmt, nullptr, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for({
-    _ = 1;
-    _ = 2;
-  }; ; ) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleCond) {
-  // for(; true; ) {
-  //   return;
-  // }
-
-  auto* f = For(nullptr, true, nullptr, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(; true; ) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleCont) {
-  // for(; ; i = i + 1) {
-  //   return;
-  // }
-
-  auto* v = Decl(Var("i", ty.i32()));
-  auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), Block(Return()));
-  WrapInFunction(v, f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(; ; i = (i + 1)) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtCont) {
-  // var<workgroup> a : atomic<i32>;
-  // for(; ; { ignore(1); ignore(2); }) {
-  //   return;
-  // }
-
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt = Block(Ignore(1), Ignore(2));
-  auto* f = For(nullptr, nullptr, multi_stmt, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(; ; {
-    _ = 1;
-    _ = 2;
-  }) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithSimpleInitCondCont) {
-  // for(var i : i32; true; i = i + 1) {
-  //   return;
-  // }
-
-  auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
-                Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for(var i : i32; true; i = (i + 1)) {
-    return;
-  }
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtInitCondCont) {
-  // var<workgroup> a : atomic<i32>;
-  // for({ ignore(1); ignore(2); }; true; { ignore(3); ignore(4); }) {
-  //   return;
-  // }
-  Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
-  auto* multi_stmt_a = Block(Ignore(1), Ignore(2));
-  auto* multi_stmt_b = Block(Ignore(3), Ignore(4));
-  auto* f = For(multi_stmt_a, Expr(true), multi_stmt_b, Block(Return()));
-  WrapInFunction(f);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  for({
-    _ = 1;
-    _ = 2;
-  }; true; {
-    _ = 3;
-    _ = 4;
-  }) {
-    return;
-  }
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_member_accessor_test.cc b/src/writer/wgsl/generator_impl_member_accessor_test.cc
deleted file mode 100644
index 787cd94..0000000
--- a/src/writer/wgsl/generator_impl_member_accessor_test.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
-
-  auto* expr = MemberAccessor("str", "mem");
-  WrapInFunction(expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "str.mem");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor_OfDref) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
-
-  auto* p = Const("p", nullptr, AddressOf("str"));
-  auto* expr = MemberAccessor(Deref("p"), "mem");
-  WrapInFunction(p, expr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-  EXPECT_EQ(out.str(), "(*(p)).mem");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_return_test.cc b/src/writer/wgsl/generator_impl_return_test.cc
deleted file mode 100644
index f261d8e..0000000
--- a/src/writer/wgsl/generator_impl_return_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Return) {
-  auto* r = Return();
-  WrapInFunction(r);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return;\n");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_ReturnWithValue) {
-  auto* r = Return(123);
-  Func("f", {}, ty.i32(), {r});
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
-  EXPECT_EQ(gen.result(), "  return 123;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_switch_test.cc b/src/writer/wgsl/generator_impl_switch_test.cc
deleted file mode 100644
index c39dbb5..0000000
--- a/src/writer/wgsl/generator_impl_switch_test.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Switch) {
-  Global("cond", ty.i32(), ast::StorageClass::kPrivate);
-
-  auto* def_body = Block(create<ast::BreakStatement>());
-  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
-
-  ast::CaseSelectorList case_val;
-  case_val.push_back(Expr(5));
-
-  auto* case_body = Block(create<ast::BreakStatement>());
-
-  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
-
-  ast::CaseStatementList body;
-  body.push_back(case_stmt);
-  body.push_back(def);
-
-  auto* cond = Expr("cond");
-  auto* s = create<ast::SwitchStatement>(cond, body);
-  WrapInFunction(s);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(  switch(cond) {
-    case 5: {
-      break;
-    }
-    default: {
-      break;
-    }
-  }
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_test.cc b/src/writer/wgsl/generator_impl_test.cc
deleted file mode 100644
index 657de32..0000000
--- a/src/writer/wgsl/generator_impl_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/variable.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Generate) {
-  Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::AttributeList{});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.Generate()) << gen.error();
-  EXPECT_EQ(gen.result(), R"(fn my_func() {
-}
-)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
deleted file mode 100644
index b121485..0000000
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ /dev/null
@@ -1,534 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/sem/depth_texture_type.h"
-#include "src/sem/multisampled_texture_type.h"
-#include "src/sem/sampled_texture_type.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
-  auto* alias = Alias("alias", ty.f32());
-  auto* alias_ty = ty.Of(alias);
-  WrapInFunction(Var("make_reachable", alias_ty));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, alias_ty)) << gen.error();
-  EXPECT_EQ(out.str(), "alias");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Array) {
-  auto* arr = ty.array<bool, 4>();
-  Alias("make_type_reachable", arr);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, arr)) << gen.error();
-  EXPECT_EQ(out.str(), "array<bool, 4>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Array_Attribute) {
-  auto* a = ty.array(ty.bool_(), 4, 16u);
-  Alias("make_type_reachable", a);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
-  EXPECT_EQ(out.str(), "@stride(16) array<bool, 4>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_RuntimeArray) {
-  auto* a = ty.array(ty.bool_());
-  Alias("make_type_reachable", a);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
-  EXPECT_EQ(out.str(), "array<bool>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Bool) {
-  auto* bool_ = ty.bool_();
-  Alias("make_type_reachable", bool_);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, bool_)) << gen.error();
-  EXPECT_EQ(out.str(), "bool");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_F32) {
-  auto* f32 = ty.f32();
-  Alias("make_type_reachable", f32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, f32)) << gen.error();
-  EXPECT_EQ(out.str(), "f32");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_I32) {
-  auto* i32 = ty.i32();
-  Alias("make_type_reachable", i32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, i32)) << gen.error();
-  EXPECT_EQ(out.str(), "i32");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Matrix) {
-  auto* mat2x3 = ty.mat2x3<f32>();
-  Alias("make_type_reachable", mat2x3);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.error();
-  EXPECT_EQ(out.str(), "mat2x3<f32>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Pointer) {
-  auto* p = ty.pointer<f32>(ast::StorageClass::kWorkgroup);
-  Alias("make_type_reachable", p);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
-  EXPECT_EQ(out.str(), "ptr<workgroup, f32>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_PointerAccessMode) {
-  auto* p =
-      ty.pointer<f32>(ast::StorageClass::kStorage, ast::Access::kReadWrite);
-  Alias("make_type_reachable", p);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
-  EXPECT_EQ(out.str(), "ptr<storage, f32, read_write>");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
-  auto* s_ty = ty.Of(s);
-  WrapInFunction(Var("make_reachable", s_ty));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, s_ty)) << gen.error();
-  EXPECT_EQ(out.str(), "S");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberOffset(8)}),
-                               Member("b", ty.f32(), {MemberOffset(16)}),
-                           });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct S {
-  @size(8)
-  padding : u32;
-  a : i32;
-  @size(4)
-  padding_1 : u32;
-  b : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl_WithSymbolCollisions) {
-  auto* s =
-      Structure("S", {
-                         Member("tint_0_padding", ty.i32(), {MemberOffset(8)}),
-                         Member("tint_2_padding", ty.f32(), {MemberOffset(16)}),
-                     });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct S {
-  @size(8)
-  padding : u32;
-  tint_0_padding : i32;
-  @size(4)
-  padding_1 : u32;
-  tint_2_padding : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_StructAlignDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberAlign(8)}),
-                               Member("b", ty.f32(), {MemberAlign(16)}),
-                           });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct S {
-  @align(8)
-  a : i32;
-  @align(16)
-  b : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_StructSizeDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberSize(16)}),
-                               Member("b", ty.f32(), {MemberSize(32)}),
-                           });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct S {
-  @size(16)
-  a : i32;
-  @size(32)
-  b : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithAttribute) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32(), {MemberAlign(8)}),
-                      },
-                      {create<ast::StructBlockAttribute>()});
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(@block
-struct S {
-  a : i32;
-  @align(8)
-  b : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithEntryPointAttributes) {
-  ast::AttributeList attrs;
-  attrs.push_back(create<ast::StructBlockAttribute>());
-
-  auto* s = Structure(
-      "S",
-      ast::StructMemberList{
-          Member("a", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)}),
-          Member("b", ty.f32(), {Location(2u)})},
-      attrs);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(@block
-struct S {
-  @builtin(vertex_index)
-  a : u32;
-  @location(2)
-  b : f32;
-}
-)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_U32) {
-  auto* u32 = ty.u32();
-  Alias("make_type_reachable", u32);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, u32)) << gen.error();
-  EXPECT_EQ(out.str(), "u32");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_Vector) {
-  auto* vec3 = ty.vec3<f32>();
-  Alias("make_type_reachable", vec3);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, vec3)) << gen.error();
-  EXPECT_EQ(out.str(), "vec3<f32>");
-}
-
-struct TextureData {
-  ast::TextureDimension dim;
-  const char* name;
-};
-inline std::ostream& operator<<(std::ostream& out, TextureData data) {
-  out << data.name;
-  return out;
-}
-using WgslGenerator_DepthTextureTest = TestParamHelper<TextureData>;
-
-TEST_P(WgslGenerator_DepthTextureTest, EmitType_DepthTexture) {
-  auto param = GetParam();
-
-  auto* d = ty.depth_texture(param.dim);
-  Alias("make_type_reachable", d);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, d)) << gen.error();
-  EXPECT_EQ(out.str(), param.name);
-}
-INSTANTIATE_TEST_SUITE_P(
-    WgslGeneratorImplTest,
-    WgslGenerator_DepthTextureTest,
-    testing::Values(
-        TextureData{ast::TextureDimension::k2d, "texture_depth_2d"},
-        TextureData{ast::TextureDimension::k2dArray, "texture_depth_2d_array"},
-        TextureData{ast::TextureDimension::kCube, "texture_depth_cube"},
-        TextureData{ast::TextureDimension::kCubeArray,
-                    "texture_depth_cube_array"}));
-
-using WgslGenerator_SampledTextureTest = TestParamHelper<TextureData>;
-TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_F32) {
-  auto param = GetParam();
-
-  auto* t = ty.sampled_texture(param.dim, ty.f32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
-}
-
-TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_I32) {
-  auto param = GetParam();
-
-  auto* t = ty.sampled_texture(param.dim, ty.i32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
-}
-
-TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_U32) {
-  auto param = GetParam();
-
-  auto* t = ty.sampled_texture(param.dim, ty.u32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
-}
-INSTANTIATE_TEST_SUITE_P(
-    WgslGeneratorImplTest,
-    WgslGenerator_SampledTextureTest,
-    testing::Values(
-        TextureData{ast::TextureDimension::k1d, "texture_1d"},
-        TextureData{ast::TextureDimension::k2d, "texture_2d"},
-        TextureData{ast::TextureDimension::k2dArray, "texture_2d_array"},
-        TextureData{ast::TextureDimension::k3d, "texture_3d"},
-        TextureData{ast::TextureDimension::kCube, "texture_cube"},
-        TextureData{ast::TextureDimension::kCubeArray, "texture_cube_array"}));
-
-using WgslGenerator_MultiampledTextureTest = TestParamHelper<TextureData>;
-TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_F32) {
-  auto param = GetParam();
-
-  auto* t = ty.multisampled_texture(param.dim, ty.f32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
-}
-
-TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_I32) {
-  auto param = GetParam();
-
-  auto* t = ty.multisampled_texture(param.dim, ty.i32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
-}
-
-TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_U32) {
-  auto param = GetParam();
-
-  auto* t = ty.multisampled_texture(param.dim, ty.u32());
-  Alias("make_type_reachable", t);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
-}
-INSTANTIATE_TEST_SUITE_P(WgslGeneratorImplTest,
-                         WgslGenerator_MultiampledTextureTest,
-                         testing::Values(TextureData{
-                             ast::TextureDimension::k2d,
-                             "texture_multisampled_2d"}));
-
-struct StorageTextureData {
-  ast::TexelFormat fmt;
-  ast::TextureDimension dim;
-  ast::Access access;
-  const char* name;
-};
-inline std::ostream& operator<<(std::ostream& out, StorageTextureData data) {
-  out << data.name;
-  return out;
-}
-using WgslGenerator_StorageTextureTest = TestParamHelper<StorageTextureData>;
-TEST_P(WgslGenerator_StorageTextureTest, EmitType_StorageTexture) {
-  auto param = GetParam();
-
-  auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
-  Global("g", t,
-         ast::AttributeList{
-             create<ast::BindingAttribute>(1),
-             create<ast::GroupAttribute>(2),
-         });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
-  EXPECT_EQ(out.str(), param.name);
-}
-INSTANTIATE_TEST_SUITE_P(
-    WgslGeneratorImplTest,
-    WgslGenerator_StorageTextureTest,
-    testing::Values(
-        StorageTextureData{ast::TexelFormat::kRgba8Sint,
-                           ast::TextureDimension::k1d, ast::Access::kWrite,
-                           "texture_storage_1d<rgba8sint, write>"},
-        StorageTextureData{ast::TexelFormat::kRgba8Sint,
-                           ast::TextureDimension::k2d, ast::Access::kWrite,
-                           "texture_storage_2d<rgba8sint, write>"},
-        StorageTextureData{ast::TexelFormat::kRgba8Sint,
-                           ast::TextureDimension::k2dArray, ast::Access::kWrite,
-                           "texture_storage_2d_array<rgba8sint, write>"},
-        StorageTextureData{ast::TexelFormat::kRgba8Sint,
-                           ast::TextureDimension::k3d, ast::Access::kWrite,
-                           "texture_storage_3d<rgba8sint, write>"}));
-
-struct ImageFormatData {
-  ast::TexelFormat fmt;
-  const char* name;
-};
-inline std::ostream& operator<<(std::ostream& out, ImageFormatData data) {
-  out << data.name;
-  return out;
-}
-using WgslGenerator_ImageFormatTest = TestParamHelper<ImageFormatData>;
-TEST_P(WgslGenerator_ImageFormatTest, EmitType_StorageTexture_ImageFormat) {
-  auto param = GetParam();
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitImageFormat(out, param.fmt)) << gen.error();
-  EXPECT_EQ(out.str(), param.name);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    WgslGeneratorImplTest,
-    WgslGenerator_ImageFormatTest,
-    testing::Values(
-        ImageFormatData{ast::TexelFormat::kR32Uint, "r32uint"},
-        ImageFormatData{ast::TexelFormat::kR32Sint, "r32sint"},
-        ImageFormatData{ast::TexelFormat::kR32Float, "r32float"},
-        ImageFormatData{ast::TexelFormat::kRgba8Unorm, "rgba8unorm"},
-        ImageFormatData{ast::TexelFormat::kRgba8Snorm, "rgba8snorm"},
-        ImageFormatData{ast::TexelFormat::kRgba8Uint, "rgba8uint"},
-        ImageFormatData{ast::TexelFormat::kRgba8Sint, "rgba8sint"},
-        ImageFormatData{ast::TexelFormat::kRg32Uint, "rg32uint"},
-        ImageFormatData{ast::TexelFormat::kRg32Sint, "rg32sint"},
-        ImageFormatData{ast::TexelFormat::kRg32Float, "rg32float"},
-        ImageFormatData{ast::TexelFormat::kRgba16Uint, "rgba16uint"},
-        ImageFormatData{ast::TexelFormat::kRgba16Sint, "rgba16sint"},
-        ImageFormatData{ast::TexelFormat::kRgba16Float, "rgba16float"},
-        ImageFormatData{ast::TexelFormat::kRgba32Uint, "rgba32uint"},
-        ImageFormatData{ast::TexelFormat::kRgba32Sint, "rgba32sint"},
-        ImageFormatData{ast::TexelFormat::kRgba32Float, "rgba32float"}));
-
-TEST_F(WgslGeneratorImplTest, EmitType_Sampler) {
-  auto* sampler = ty.sampler(ast::SamplerKind::kSampler);
-  Alias("make_type_reachable", sampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
-  EXPECT_EQ(out.str(), "sampler");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitType_SamplerComparison) {
-  auto* sampler = ty.sampler(ast::SamplerKind::kComparisonSampler);
-  Alias("make_type_reachable", sampler);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
-  EXPECT_EQ(out.str(), "sampler_comparison");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_unary_op_test.cc b/src/writer/wgsl/generator_impl_unary_op_test.cc
deleted file mode 100644
index f771359..0000000
--- a/src/writer/wgsl/generator_impl_unary_op_test.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 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/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslUnaryOpTest = TestHelper;
-
-TEST_F(WgslUnaryOpTest, AddressOf) {
-  Global("expr", ty.f32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "&(expr)");
-}
-
-TEST_F(WgslUnaryOpTest, Complement) {
-  Global("expr", ty.u32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "~(expr)");
-}
-
-TEST_F(WgslUnaryOpTest, Indirection) {
-  Global("G", ty.f32(), ast::StorageClass::kPrivate);
-  auto* p = Const(
-      "expr", nullptr,
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
-  WrapInFunction(p, op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "*(expr)");
-}
-
-TEST_F(WgslUnaryOpTest, Not) {
-  Global("expr", ty.bool_(), ast::StorageClass::kPrivate);
-  auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "!(expr)");
-}
-
-TEST_F(WgslUnaryOpTest, Negation) {
-  Global("expr", ty.i32(), ast::StorageClass::kPrivate);
-  auto* op =
-      create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr("expr"));
-  WrapInFunction(op);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
-  EXPECT_EQ(out.str(), "-(expr)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_variable_decl_statement_test.cc b/src/writer/wgsl/generator_impl_variable_decl_statement_test.cc
deleted file mode 100644
index e7a3f30..0000000
--- a/src/writer/wgsl/generator_impl_variable_decl_statement_test.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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/ast/variable_decl_statement.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement) {
-  auto* var = Var("a", ty.f32());
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  var a : f32;\n");
-}
-
-TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_InferredType) {
-  auto* var = Var("a", nullptr, ast::StorageClass::kNone, Expr(123));
-
-  auto* stmt = Decl(var);
-  WrapInFunction(stmt);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-  EXPECT_EQ(gen.result(), "  var a = 123;\n");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/generator_impl_variable_test.cc b/src/writer/wgsl/generator_impl_variable_test.cc
deleted file mode 100644
index fc1654c..0000000
--- a/src/writer/wgsl/generator_impl_variable_test.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2020 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/ast/struct_block_attribute.h"
-#include "src/writer/wgsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, EmitVariable) {
-  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(var<private> a : f32;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_StorageClass) {
-  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(var<private> a : f32;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Read) {
-  auto* s = Structure("S", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* v =
-      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-             ast::AttributeList{
-                 create<ast::BindingAttribute>(0),
-                 create<ast::GroupAttribute>(0),
-             });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(@binding(0) @group(0) var<storage, read> a : S;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Write) {
-  auto* s = Structure("S", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* v =
-      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
-             ast::AttributeList{
-                 create<ast::BindingAttribute>(0),
-                 create<ast::GroupAttribute>(0),
-             });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(@binding(0) @group(0) var<storage, write> a : S;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Access_ReadWrite) {
-  auto* s = Structure("S", {Member("a", ty.i32())},
-                      {create<ast::StructBlockAttribute>()});
-  auto* v = Global("a", ty.Of(s), ast::StorageClass::kStorage,
-                   ast::Access::kReadWrite,
-                   ast::AttributeList{
-                       create<ast::BindingAttribute>(0),
-                       create<ast::GroupAttribute>(0),
-                   });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(),
-            R"(@binding(0) @group(0) var<storage, read_write> a : S;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Decorated) {
-  auto* v = Global("a", ty.sampler(ast::SamplerKind::kSampler),
-                   ast::StorageClass::kNone, nullptr,
-                   ast::AttributeList{
-                       create<ast::GroupAttribute>(1),
-                       create<ast::BindingAttribute>(2),
-                   });
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(@group(1) @binding(2) var a : sampler;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Constructor) {
-  auto* v = Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(1.0f));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(var<private> a : f32 = 1.0;)");
-}
-
-TEST_F(WgslGeneratorImplTest, EmitVariable_Const) {
-  auto* v = Const("a", ty.f32(), Expr(1.0f));
-  WrapInFunction(Decl(v));
-
-  GeneratorImpl& gen = Build();
-
-  std::stringstream out;
-  ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-  EXPECT_EQ(out.str(), R"(let a : f32 = 1.0;)");
-}
-
-}  // namespace
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/wgsl/test_helper.h b/src/writer/wgsl/test_helper.h
deleted file mode 100644
index b783938..0000000
--- a/src/writer/wgsl/test_helper.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 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_WRITER_WGSL_TEST_HELPER_H_
-#define SRC_WRITER_WGSL_TEST_HELPER_H_
-
-#include <memory>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "src/program_builder.h"
-#include "src/writer/wgsl/generator_impl.h"
-
-namespace tint {
-namespace writer {
-namespace wgsl {
-
-/// Helper class for testing
-template <typename BASE>
-class TestHelperBase : public BASE, public ProgramBuilder {
- public:
-  TestHelperBase() = default;
-
-  ~TestHelperBase() override = default;
-
-  /// Builds and returns a GeneratorImpl from the program.
-  /// @note The generator is only built once. Multiple calls to Build() will
-  /// return the same GeneratorImpl without rebuilding.
-  /// @return the built generator
-  GeneratorImpl& Build() {
-    if (gen_) {
-      return *gen_;
-    }
-    program = std::make_unique<Program>(std::move(*this));
-    diag::Formatter formatter;
-    [&]() {
-      ASSERT_TRUE(program->IsValid())
-          << formatter.format(program->Diagnostics());
-    }();
-    gen_ = std::make_unique<GeneratorImpl>(program.get());
-    return *gen_;
-  }
-
-  /// The program built with a call to Build()
-  std::unique_ptr<Program> program;
-
- private:
-  std::unique_ptr<GeneratorImpl> gen_;
-};
-using TestHelper = TestHelperBase<testing::Test>;
-
-template <typename T>
-using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
-
-}  // namespace wgsl
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_WGSL_TEST_HELPER_H_
diff --git a/src/writer/writer.cc b/src/writer/writer.cc
deleted file mode 100644
index f3b765e..0000000
--- a/src/writer/writer.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 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/writer/writer.h"
-
-namespace tint {
-namespace writer {
-
-Writer::~Writer() = default;
-
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/writer.h b/src/writer/writer.h
deleted file mode 100644
index fdbb569..0000000
--- a/src/writer/writer.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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_WRITER_WRITER_H_
-#define SRC_WRITER_WRITER_H_
-
-#include <string>
-
-namespace tint {
-namespace writer {
-
-/// Base class for the output writers
-class Writer {
- public:
-  virtual ~Writer();
-
-  /// @returns the writer error string
-  const std::string& error() const { return error_; }
-
-  /// Converts the module into the desired format
-  /// @returns true on success; false on failure
-  virtual bool Generate() = 0;
-
- protected:
-  /// Sets the error string
-  /// @param msg the error message
-  void set_error(const std::string& msg) { error_ = msg; }
-
-  /// An error message, if an error was encountered
-  std::string error_;
-};
-
-}  // namespace writer
-}  // namespace tint
-
-#endif  // SRC_WRITER_WRITER_H_
diff --git a/test/BUILD.gn b/test/BUILD.gn
deleted file mode 100644
index 638a767..0000000
--- a/test/BUILD.gn
+++ /dev/null
@@ -1,812 +0,0 @@
-# Copyright 2021 The Tint Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//build_overrides/build.gni")
-import("//testing/test.gni")
-import("../tint_overrides_with_defaults.gni")
-
-###############################################################################
-# Gtest Gmock - Handle building inside and outside of Chromium.
-###############################################################################
-# When building outside of Chromium we need to define our own targets for GTest
-# and GMock. However when compiling inside of Chromium we need to reuse the
-# existing targets, both because Chromium has a special harness for swarming
-# and because otherwise the "gn check" fails.
-
-if (!build_with_chromium) {
-  # When we aren't in Chromium we define out own targets based on the location
-  # of the googletest repo.
-  config("gtest_config") {
-    include_dirs = [
-      "${tint_googletest_dir}/googletest",
-      "${tint_googletest_dir}/googletest/include",
-    ]
-  }
-  static_library("gtest") {
-    testonly = true
-    sources = [ "${tint_googletest_dir}/googletest/src/gtest-all.cc" ]
-    public_configs = [ ":gtest_config" ]
-  }
-
-  config("gmock_config") {
-    include_dirs = [
-      "${tint_googletest_dir}/googlemock",
-      "${tint_googletest_dir}/googlemock/include",
-      "${tint_googletest_dir}/googletest/include",
-    ]
-  }
-
-  static_library("gmock") {
-    testonly = true
-    sources = [ "${tint_googletest_dir}/googlemock/src/gmock-all.cc" ]
-    public_configs = [ ":gmock_config" ]
-  }
-
-  group("gmock_and_gtest") {
-    testonly = true
-    public_deps = [
-      ":gmock",
-      ":gtest",
-    ]
-  }
-} else {
-  # When we are in Chromium we reuse its targets, and also add some deps that
-  # are needed to launch the test in swarming mode.
-  group("gmock_and_gtest") {
-    testonly = true
-    public_deps = [
-      "//base",
-      "//base/test:test_support",
-      "//testing/gmock",
-      "//testing/gtest",
-      "//third_party/googletest:gmock",
-    ]
-  }
-}
-
-###############################################################################
-# Wrapping of Chromium targets
-###############################################################################
-# These targets are separated because they are Chromium sources files that
-# can't use the tint_internal config, otherwise Tint's warning flags get
-# applied while compiling a bunch of Chromium's //base (via header inclusion)
-source_set("tint_unittests_main") {
-  testonly = true
-  deps = [ ":gmock_and_gtest" ]
-  if (build_with_chromium) {
-    sources = [ "//gpu/tint_unittests_main.cc" ]
-  } else {
-    sources = [ "../src/test_main.cc" ]
-    configs += [ ":tint_unittests_config" ]
-    deps += [
-      ":tint_unittests_hlsl_writer_src",
-      ":tint_unittests_msl_writer_src",
-      ":tint_unittests_spv_reader_src",
-      "${tint_root_dir}/src:libtint",
-    ]
-  }
-}
-
-###############################################################################
-# Tests - For libtint core and optional modules
-###############################################################################
-config("tint_unittests_config") {
-  include_dirs = [
-    "${tint_googletest_dir}/googlemock/include",
-    "${tint_googletest_dir}/googletest/include",
-  ]
-
-  configs = [
-    "${tint_root_dir}/src:tint_common_config",
-    "${tint_root_dir}/src:tint_public_config",
-  ]
-}
-
-template("tint_unittests_source_set") {
-  source_set(target_name) {
-    forward_variables_from(invoker, "*", [ "configs" ])
-
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-    configs += [ ":tint_unittests_config" ]
-    if (build_with_chromium) {
-      configs -= [ "//build/config/compiler:chromium_code" ]
-      configs += [ "//build/config/compiler:no_chromium_code" ]
-    }
-
-    testonly = true
-
-    if (!defined(invoker.deps)) {
-      deps = []
-    }
-    deps += [
-      ":gmock_and_gtest",
-      "${tint_root_dir}/src:libtint",
-      "${tint_root_dir}/src:tint_utils_io",
-    ]
-  }
-}
-
-tint_unittests_source_set("tint_unittests_ast_src") {
-  sources = [
-    "../src/ast/alias_test.cc",
-    "../src/ast/array_test.cc",
-    "../src/ast/assignment_statement_test.cc",
-    "../src/ast/atomic_test.cc",
-    "../src/ast/binary_expression_test.cc",
-    "../src/ast/binding_attribute_test.cc",
-    "../src/ast/bitcast_expression_test.cc",
-    "../src/ast/block_statement_test.cc",
-    "../src/ast/bool_literal_expression_test.cc",
-    "../src/ast/bool_test.cc",
-    "../src/ast/break_statement_test.cc",
-    "../src/ast/builtin_attribute_test.cc",
-    "../src/ast/builtin_texture_helper_test.cc",
-    "../src/ast/builtin_texture_helper_test.h",
-    "../src/ast/call_expression_test.cc",
-    "../src/ast/call_statement_test.cc",
-    "../src/ast/case_statement_test.cc",
-    "../src/ast/continue_statement_test.cc",
-    "../src/ast/depth_multisampled_texture_test.cc",
-    "../src/ast/depth_texture_test.cc",
-    "../src/ast/discard_statement_test.cc",
-    "../src/ast/else_statement_test.cc",
-    "../src/ast/external_texture_test.cc",
-    "../src/ast/f32_test.cc",
-    "../src/ast/fallthrough_statement_test.cc",
-    "../src/ast/float_literal_expression_test.cc",
-    "../src/ast/for_loop_statement_test.cc",
-    "../src/ast/function_test.cc",
-    "../src/ast/group_attribute_test.cc",
-    "../src/ast/i32_test.cc",
-    "../src/ast/id_attribute_test.cc",
-    "../src/ast/identifier_expression_test.cc",
-    "../src/ast/if_statement_test.cc",
-    "../src/ast/index_accessor_expression_test.cc",
-    "../src/ast/int_literal_expression_test.cc",
-    "../src/ast/interpolate_attribute_test.cc",
-    "../src/ast/invariant_attribute_test.cc",
-    "../src/ast/location_attribute_test.cc",
-    "../src/ast/loop_statement_test.cc",
-    "../src/ast/matrix_test.cc",
-    "../src/ast/member_accessor_expression_test.cc",
-    "../src/ast/module_clone_test.cc",
-    "../src/ast/module_test.cc",
-    "../src/ast/multisampled_texture_test.cc",
-    "../src/ast/phony_expression_test.cc",
-    "../src/ast/pointer_test.cc",
-    "../src/ast/return_statement_test.cc",
-    "../src/ast/sampled_texture_test.cc",
-    "../src/ast/sampler_test.cc",
-    "../src/ast/sint_literal_expression_test.cc",
-    "../src/ast/stage_attribute_test.cc",
-    "../src/ast/storage_texture_test.cc",
-    "../src/ast/stride_attribute_test.cc",
-    "../src/ast/struct_member_align_attribute_test.cc",
-    "../src/ast/struct_member_offset_attribute_test.cc",
-    "../src/ast/struct_member_size_attribute_test.cc",
-    "../src/ast/struct_member_test.cc",
-    "../src/ast/struct_test.cc",
-    "../src/ast/switch_statement_test.cc",
-    "../src/ast/test_helper.h",
-    "../src/ast/texture_test.cc",
-    "../src/ast/traverse_expressions_test.cc",
-    "../src/ast/u32_test.cc",
-    "../src/ast/uint_literal_expression_test.cc",
-    "../src/ast/unary_op_expression_test.cc",
-    "../src/ast/variable_decl_statement_test.cc",
-    "../src/ast/variable_test.cc",
-    "../src/ast/vector_test.cc",
-    "../src/ast/workgroup_attribute_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_diagnostic_src") {
-  sources = [
-    "../src/diagnostic/diagnostic_test.cc",
-    "../src/diagnostic/formatter_test.cc",
-    "../src/diagnostic/printer_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_inspector_src") {
-  sources = [
-    "../src/inspector/inspector_test.cc",
-    "../src/inspector/test_inspector_builder.cc",
-    "../src/inspector/test_inspector_builder.h",
-    "../src/inspector/test_inspector_runner.cc",
-    "../src/inspector/test_inspector_runner.h",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_resolver_src") {
-  sources = [
-    "../src/resolver/array_accessor_test.cc",
-    "../src/resolver/assignment_validation_test.cc",
-    "../src/resolver/atomics_test.cc",
-    "../src/resolver/atomics_validation_test.cc",
-    "../src/resolver/attribute_validation_test.cc",
-    "../src/resolver/bitcast_validation_test.cc",
-    "../src/resolver/builtin_test.cc",
-    "../src/resolver/builtin_validation_test.cc",
-    "../src/resolver/builtins_validation_test.cc",
-    "../src/resolver/call_test.cc",
-    "../src/resolver/call_validation_test.cc",
-    "../src/resolver/compound_statement_test.cc",
-    "../src/resolver/control_block_validation_test.cc",
-    "../src/resolver/dependency_graph_test.cc",
-    "../src/resolver/entry_point_validation_test.cc",
-    "../src/resolver/function_validation_test.cc",
-    "../src/resolver/host_shareable_validation_test.cc",
-    "../src/resolver/is_host_shareable_test.cc",
-    "../src/resolver/is_storeable_test.cc",
-    "../src/resolver/pipeline_overridable_constant_test.cc",
-    "../src/resolver/ptr_ref_test.cc",
-    "../src/resolver/ptr_ref_validation_test.cc",
-    "../src/resolver/resolver_behavior_test.cc",
-    "../src/resolver/resolver_constants_test.cc",
-    "../src/resolver/resolver_test.cc",
-    "../src/resolver/resolver_test_helper.cc",
-    "../src/resolver/resolver_test_helper.h",
-    "../src/resolver/side_effects_test.cc",
-    "../src/resolver/storage_class_layout_validation_test.cc",
-    "../src/resolver/storage_class_validation_test.cc",
-    "../src/resolver/struct_layout_test.cc",
-    "../src/resolver/struct_pipeline_stage_use_test.cc",
-    "../src/resolver/struct_storage_class_use_test.cc",
-    "../src/resolver/type_constructor_validation_test.cc",
-    "../src/resolver/type_validation_test.cc",
-    "../src/resolver/validation_test.cc",
-    "../src/resolver/var_let_test.cc",
-    "../src/resolver/var_let_validation_test.cc",
-  ]
-  deps = [ ":tint_unittests_ast_src" ]
-}
-
-tint_unittests_source_set("tint_unittests_sem_src") {
-  sources = [
-    "../src/sem/atomic_type_test.cc",
-    "../src/sem/bool_type_test.cc",
-    "../src/sem/builtin_test.cc",
-    "../src/sem/depth_multisampled_texture_type_test.cc",
-    "../src/sem/depth_texture_type_test.cc",
-    "../src/sem/external_texture_type_test.cc",
-    "../src/sem/f32_type_test.cc",
-    "../src/sem/i32_type_test.cc",
-    "../src/sem/matrix_type_test.cc",
-    "../src/sem/multisampled_texture_type_test.cc",
-    "../src/sem/pointer_type_test.cc",
-    "../src/sem/reference_type_test.cc",
-    "../src/sem/sampled_texture_type_test.cc",
-    "../src/sem/sampler_type_test.cc",
-    "../src/sem/sem_array_test.cc",
-    "../src/sem/sem_struct_test.cc",
-    "../src/sem/storage_texture_type_test.cc",
-    "../src/sem/texture_type_test.cc",
-    "../src/sem/type_manager_test.cc",
-    "../src/sem/u32_type_test.cc",
-    "../src/sem/vector_type_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_text_src") {
-  sources = [
-    "../src/text/unicode_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_transform_src") {
-  sources = [
-    "../src/transform/add_empty_entry_point_test.cc",
-    "../src/transform/add_spirv_block_attribute_test.cc",
-    "../src/transform/array_length_from_uniform_test.cc",
-    "../src/transform/binding_remapper_test.cc",
-    "../src/transform/calculate_array_length_test.cc",
-    "../src/transform/canonicalize_entry_point_io_test.cc",
-    "../src/transform/combine_samplers_test.cc",
-    "../src/transform/decompose_memory_access_test.cc",
-    "../src/transform/decompose_strided_array_test.cc",
-    "../src/transform/decompose_strided_matrix_test.cc",
-    "../src/transform/external_texture_transform_test.cc",
-    "../src/transform/first_index_offset_test.cc",
-    "../src/transform/fold_constants_test.cc",
-    "../src/transform/fold_trivial_single_use_lets_test.cc",
-    "../src/transform/for_loop_to_loop_test.cc",
-    "../src/transform/localize_struct_array_assignment_test.cc",
-    "../src/transform/loop_to_for_loop_test.cc",
-    "../src/transform/module_scope_var_to_entry_point_param_test.cc",
-    "../src/transform/multiplanar_external_texture_test.cc",
-    "../src/transform/num_workgroups_from_uniform_test.cc",
-    "../src/transform/pad_array_elements_test.cc",
-    "../src/transform/promote_initializers_to_const_var_test.cc",
-    "../src/transform/remove_phonies_test.cc",
-    "../src/transform/remove_unreachable_statements_test.cc",
-    "../src/transform/renamer_test.cc",
-    "../src/transform/robustness_test.cc",
-    "../src/transform/simplify_pointers_test.cc",
-    "../src/transform/single_entry_point_test.cc",
-    "../src/transform/test_helper.h",
-    "../src/transform/transform_test.cc",
-    "../src/transform/unshadow_test.cc",
-    "../src/transform/utils/hoist_to_decl_before_test.cc",
-    "../src/transform/var_for_dynamic_index_test.cc",
-    "../src/transform/vectorize_scalar_matrix_constructors_test.cc",
-    "../src/transform/vertex_pulling_test.cc",
-    "../src/transform/wrap_arrays_in_structs_test.cc",
-    "../src/transform/zero_init_workgroup_memory_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_utils_src") {
-  sources = [
-    "../src/utils/crc32_test.cc",
-    "../src/utils/defer_test.cc",
-    "../src/utils/enum_set_test.cc",
-    "../src/utils/hash_test.cc",
-    "../src/utils/io/command_test.cc",
-    "../src/utils/io/tmpfile_test.cc",
-    "../src/utils/map_test.cc",
-    "../src/utils/math_test.cc",
-    "../src/utils/reverse_test.cc",
-    "../src/utils/scoped_assignment_test.cc",
-    "../src/utils/string_test.cc",
-    "../src/utils/transform_test.cc",
-    "../src/utils/unique_vector_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_writer_src") {
-  sources = [
-    "../src/writer/append_vector_test.cc",
-    "../src/writer/float_to_string_test.cc",
-    "../src/writer/text_generator_test.cc",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_spv_reader_src") {
-  sources = [
-    "../src/reader/spirv/enum_converter_test.cc",
-    "../src/reader/spirv/fail_stream_test.cc",
-    "../src/reader/spirv/function_arithmetic_test.cc",
-    "../src/reader/spirv/function_bit_test.cc",
-    "../src/reader/spirv/function_call_test.cc",
-    "../src/reader/spirv/function_cfg_test.cc",
-    "../src/reader/spirv/function_composite_test.cc",
-    "../src/reader/spirv/function_conversion_test.cc",
-    "../src/reader/spirv/function_decl_test.cc",
-    "../src/reader/spirv/function_glsl_std_450_test.cc",
-    "../src/reader/spirv/function_logical_test.cc",
-    "../src/reader/spirv/function_memory_test.cc",
-    "../src/reader/spirv/function_misc_test.cc",
-    "../src/reader/spirv/function_var_test.cc",
-    "../src/reader/spirv/namer_test.cc",
-    "../src/reader/spirv/parser_impl_barrier_test.cc",
-    "../src/reader/spirv/parser_impl_convert_member_decoration_test.cc",
-    "../src/reader/spirv/parser_impl_convert_type_test.cc",
-    "../src/reader/spirv/parser_impl_function_decl_test.cc",
-    "../src/reader/spirv/parser_impl_get_decorations_test.cc",
-    "../src/reader/spirv/parser_impl_handle_test.cc",
-    "../src/reader/spirv/parser_impl_import_test.cc",
-    "../src/reader/spirv/parser_impl_module_var_test.cc",
-    "../src/reader/spirv/parser_impl_named_types_test.cc",
-    "../src/reader/spirv/parser_impl_test.cc",
-    "../src/reader/spirv/parser_impl_test_helper.cc",
-    "../src/reader/spirv/parser_impl_test_helper.h",
-    "../src/reader/spirv/parser_impl_user_name_test.cc",
-    "../src/reader/spirv/parser_test.cc",
-    "../src/reader/spirv/parser_type_test.cc",
-    "../src/reader/spirv/spirv_tools_helpers_test.cc",
-    "../src/reader/spirv/spirv_tools_helpers_test.h",
-    "../src/reader/spirv/usage_test.cc",
-  ]
-
-  deps = [ "${tint_root_dir}/src:libtint_spv_reader_src" ]
-}
-
-tint_unittests_source_set("tint_unittests_spv_writer_src") {
-  sources = [
-    "../src/writer/spirv/binary_writer_test.cc",
-    "../src/writer/spirv/builder_accessor_expression_test.cc",
-    "../src/writer/spirv/builder_assign_test.cc",
-    "../src/writer/spirv/builder_binary_expression_test.cc",
-    "../src/writer/spirv/builder_bitcast_expression_test.cc",
-    "../src/writer/spirv/builder_block_test.cc",
-    "../src/writer/spirv/builder_builtin_test.cc",
-    "../src/writer/spirv/builder_builtin_texture_test.cc",
-    "../src/writer/spirv/builder_call_test.cc",
-    "../src/writer/spirv/builder_constructor_expression_test.cc",
-    "../src/writer/spirv/builder_discard_test.cc",
-    "../src/writer/spirv/builder_entry_point_test.cc",
-    "../src/writer/spirv/builder_format_conversion_test.cc",
-    "../src/writer/spirv/builder_function_attribute_test.cc",
-    "../src/writer/spirv/builder_function_test.cc",
-    "../src/writer/spirv/builder_function_variable_test.cc",
-    "../src/writer/spirv/builder_global_variable_test.cc",
-    "../src/writer/spirv/builder_ident_expression_test.cc",
-    "../src/writer/spirv/builder_if_test.cc",
-    "../src/writer/spirv/builder_literal_test.cc",
-    "../src/writer/spirv/builder_loop_test.cc",
-    "../src/writer/spirv/builder_return_test.cc",
-    "../src/writer/spirv/builder_switch_test.cc",
-    "../src/writer/spirv/builder_test.cc",
-    "../src/writer/spirv/builder_type_test.cc",
-    "../src/writer/spirv/builder_unary_op_expression_test.cc",
-    "../src/writer/spirv/instruction_test.cc",
-    "../src/writer/spirv/operand_test.cc",
-    "../src/writer/spirv/scalar_constant_test.cc",
-    "../src/writer/spirv/spv_dump.cc",
-    "../src/writer/spirv/spv_dump.h",
-    "../src/writer/spirv/test_helper.h",
-  ]
-
-  deps = [
-    ":tint_unittests_ast_src",
-    "${tint_root_dir}/src:libtint_spv_writer_src",
-    "${tint_spirv_tools_dir}/:spvtools",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_wgsl_reader_src") {
-  sources = [
-    "../src/reader/wgsl/lexer_test.cc",
-    "../src/reader/wgsl/parser_impl_additive_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_and_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_argument_expression_list_test.cc",
-    "../src/reader/wgsl/parser_impl_assignment_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_body_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_break_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_bug_cases_test.cc",
-    "../src/reader/wgsl/parser_impl_call_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_case_body_test.cc",
-    "../src/reader/wgsl/parser_impl_const_expr_test.cc",
-    "../src/reader/wgsl/parser_impl_const_literal_test.cc",
-    "../src/reader/wgsl/parser_impl_continue_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_continuing_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_depth_texture_type_test.cc",
-    "../src/reader/wgsl/parser_impl_elseif_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_equality_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_error_msg_test.cc",
-    "../src/reader/wgsl/parser_impl_error_resync_test.cc",
-    "../src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_external_texture_type_test.cc",
-    "../src/reader/wgsl/parser_impl_for_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_function_attribute_list_test.cc",
-    "../src/reader/wgsl/parser_impl_function_attribute_test.cc",
-    "../src/reader/wgsl/parser_impl_function_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_function_header_test.cc",
-    "../src/reader/wgsl/parser_impl_global_constant_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_global_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_global_variable_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_if_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_logical_and_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_logical_or_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_loop_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_multiplicative_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_param_list_test.cc",
-    "../src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_pipeline_stage_test.cc",
-    "../src/reader/wgsl/parser_impl_primary_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_relational_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_reserved_keyword_test.cc",
-    "../src/reader/wgsl/parser_impl_sampled_texture_type_test.cc",
-    "../src/reader/wgsl/parser_impl_sampler_type_test.cc",
-    "../src/reader/wgsl/parser_impl_shift_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_singular_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_statement_test.cc",
-    "../src/reader/wgsl/parser_impl_statements_test.cc",
-    "../src/reader/wgsl/parser_impl_storage_class_test.cc",
-    "../src/reader/wgsl/parser_impl_storage_texture_type_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_attribute_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_attribute_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_body_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_member_attribute_test.cc",
-    "../src/reader/wgsl/parser_impl_struct_member_test.cc",
-    "../src/reader/wgsl/parser_impl_switch_body_test.cc",
-    "../src/reader/wgsl/parser_impl_switch_stmt_test.cc",
-    "../src/reader/wgsl/parser_impl_test.cc",
-    "../src/reader/wgsl/parser_impl_test_helper.cc",
-    "../src/reader/wgsl/parser_impl_test_helper.h",
-    "../src/reader/wgsl/parser_impl_texel_format_test.cc",
-    "../src/reader/wgsl/parser_impl_texture_sampler_types_test.cc",
-    "../src/reader/wgsl/parser_impl_type_alias_test.cc",
-    "../src/reader/wgsl/parser_impl_type_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_unary_expression_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_attribute_list_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_attribute_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_ident_decl_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_qualifier_test.cc",
-    "../src/reader/wgsl/parser_impl_variable_stmt_test.cc",
-    "../src/reader/wgsl/parser_test.cc",
-    "../src/reader/wgsl/token_test.cc",
-  ]
-
-  deps = [ "${tint_root_dir}/src:libtint_wgsl_reader_src" ]
-}
-
-tint_unittests_source_set("tint_unittests_wgsl_writer_src") {
-  sources = [
-    "../src/writer/wgsl/generator_impl_alias_type_test.cc",
-    "../src/writer/wgsl/generator_impl_array_accessor_test.cc",
-    "../src/writer/wgsl/generator_impl_assign_test.cc",
-    "../src/writer/wgsl/generator_impl_binary_test.cc",
-    "../src/writer/wgsl/generator_impl_bitcast_test.cc",
-    "../src/writer/wgsl/generator_impl_block_test.cc",
-    "../src/writer/wgsl/generator_impl_break_test.cc",
-    "../src/writer/wgsl/generator_impl_call_test.cc",
-    "../src/writer/wgsl/generator_impl_case_test.cc",
-    "../src/writer/wgsl/generator_impl_cast_test.cc",
-    "../src/writer/wgsl/generator_impl_constructor_test.cc",
-    "../src/writer/wgsl/generator_impl_continue_test.cc",
-    "../src/writer/wgsl/generator_impl_discard_test.cc",
-    "../src/writer/wgsl/generator_impl_fallthrough_test.cc",
-    "../src/writer/wgsl/generator_impl_function_test.cc",
-    "../src/writer/wgsl/generator_impl_global_decl_test.cc",
-    "../src/writer/wgsl/generator_impl_identifier_test.cc",
-    "../src/writer/wgsl/generator_impl_if_test.cc",
-    "../src/writer/wgsl/generator_impl_literal_test.cc",
-    "../src/writer/wgsl/generator_impl_loop_test.cc",
-    "../src/writer/wgsl/generator_impl_member_accessor_test.cc",
-    "../src/writer/wgsl/generator_impl_return_test.cc",
-    "../src/writer/wgsl/generator_impl_switch_test.cc",
-    "../src/writer/wgsl/generator_impl_test.cc",
-    "../src/writer/wgsl/generator_impl_type_test.cc",
-    "../src/writer/wgsl/generator_impl_unary_op_test.cc",
-    "../src/writer/wgsl/generator_impl_variable_decl_statement_test.cc",
-    "../src/writer/wgsl/generator_impl_variable_test.cc",
-    "../src/writer/wgsl/test_helper.h",
-  ]
-
-  deps = [
-    ":tint_unittests_ast_src",
-    "${tint_root_dir}/src:libtint_wgsl_writer_src",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_msl_writer_src") {
-  sources = [
-    "../src/writer/msl/generator_impl_array_accessor_test.cc",
-    "../src/writer/msl/generator_impl_assign_test.cc",
-    "../src/writer/msl/generator_impl_binary_test.cc",
-    "../src/writer/msl/generator_impl_bitcast_test.cc",
-    "../src/writer/msl/generator_impl_block_test.cc",
-    "../src/writer/msl/generator_impl_break_test.cc",
-    "../src/writer/msl/generator_impl_builtin_test.cc",
-    "../src/writer/msl/generator_impl_builtin_texture_test.cc",
-    "../src/writer/msl/generator_impl_call_test.cc",
-    "../src/writer/msl/generator_impl_case_test.cc",
-    "../src/writer/msl/generator_impl_cast_test.cc",
-    "../src/writer/msl/generator_impl_constructor_test.cc",
-    "../src/writer/msl/generator_impl_continue_test.cc",
-    "../src/writer/msl/generator_impl_discard_test.cc",
-    "../src/writer/msl/generator_impl_function_test.cc",
-    "../src/writer/msl/generator_impl_identifier_test.cc",
-    "../src/writer/msl/generator_impl_if_test.cc",
-    "../src/writer/msl/generator_impl_import_test.cc",
-    "../src/writer/msl/generator_impl_loop_test.cc",
-    "../src/writer/msl/generator_impl_member_accessor_test.cc",
-    "../src/writer/msl/generator_impl_module_constant_test.cc",
-    "../src/writer/msl/generator_impl_return_test.cc",
-    "../src/writer/msl/generator_impl_sanitizer_test.cc",
-    "../src/writer/msl/generator_impl_switch_test.cc",
-    "../src/writer/msl/generator_impl_test.cc",
-    "../src/writer/msl/generator_impl_type_test.cc",
-    "../src/writer/msl/generator_impl_unary_op_test.cc",
-    "../src/writer/msl/generator_impl_variable_decl_statement_test.cc",
-    "../src/writer/msl/test_helper.h",
-  ]
-
-  deps = [
-    ":tint_unittests_ast_src",
-    "${tint_root_dir}/src:libtint_msl_writer_src",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_hlsl_writer_src") {
-  sources = [
-    "../src/writer/hlsl/generator_impl_array_accessor_test.cc",
-    "../src/writer/hlsl/generator_impl_assign_test.cc",
-    "../src/writer/hlsl/generator_impl_binary_test.cc",
-    "../src/writer/hlsl/generator_impl_bitcast_test.cc",
-    "../src/writer/hlsl/generator_impl_block_test.cc",
-    "../src/writer/hlsl/generator_impl_break_test.cc",
-    "../src/writer/hlsl/generator_impl_builtin_test.cc",
-    "../src/writer/hlsl/generator_impl_builtin_texture_test.cc",
-    "../src/writer/hlsl/generator_impl_call_test.cc",
-    "../src/writer/hlsl/generator_impl_case_test.cc",
-    "../src/writer/hlsl/generator_impl_cast_test.cc",
-    "../src/writer/hlsl/generator_impl_constructor_test.cc",
-    "../src/writer/hlsl/generator_impl_continue_test.cc",
-    "../src/writer/hlsl/generator_impl_discard_test.cc",
-    "../src/writer/hlsl/generator_impl_function_test.cc",
-    "../src/writer/hlsl/generator_impl_identifier_test.cc",
-    "../src/writer/hlsl/generator_impl_if_test.cc",
-    "../src/writer/hlsl/generator_impl_import_test.cc",
-    "../src/writer/hlsl/generator_impl_loop_test.cc",
-    "../src/writer/hlsl/generator_impl_member_accessor_test.cc",
-    "../src/writer/hlsl/generator_impl_module_constant_test.cc",
-    "../src/writer/hlsl/generator_impl_return_test.cc",
-    "../src/writer/hlsl/generator_impl_sanitizer_test.cc",
-    "../src/writer/hlsl/generator_impl_switch_test.cc",
-    "../src/writer/hlsl/generator_impl_test.cc",
-    "../src/writer/hlsl/generator_impl_type_test.cc",
-    "../src/writer/hlsl/generator_impl_unary_op_test.cc",
-    "../src/writer/hlsl/generator_impl_variable_decl_statement_test.cc",
-    "../src/writer/hlsl/generator_impl_workgroup_var_test.cc",
-    "../src/writer/hlsl/test_helper.h",
-  ]
-
-  deps = [
-    ":tint_unittests_ast_src",
-    "${tint_root_dir}/src:libtint_hlsl_writer_src",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_glsl_writer_src") {
-  sources = [
-    "../src/transform/glsl_test.cc",
-    "../src/writer/glsl/generator_impl_array_accessor_test.cc",
-    "../src/writer/glsl/generator_impl_assign_test.cc",
-    "../src/writer/glsl/generator_impl_binary_test.cc",
-    "../src/writer/glsl/generator_impl_bitcast_test.cc",
-    "../src/writer/glsl/generator_impl_block_test.cc",
-    "../src/writer/glsl/generator_impl_break_test.cc",
-    "../src/writer/glsl/generator_impl_builtin_test.cc",
-    "../src/writer/glsl/generator_impl_builtin_texture_test.cc",
-    "../src/writer/glsl/generator_impl_call_test.cc",
-    "../src/writer/glsl/generator_impl_case_test.cc",
-    "../src/writer/glsl/generator_impl_cast_test.cc",
-    "../src/writer/glsl/generator_impl_constructor_test.cc",
-    "../src/writer/glsl/generator_impl_continue_test.cc",
-    "../src/writer/glsl/generator_impl_discard_test.cc",
-    "../src/writer/glsl/generator_impl_function_test.cc",
-    "../src/writer/glsl/generator_impl_identifier_test.cc",
-    "../src/writer/glsl/generator_impl_if_test.cc",
-    "../src/writer/glsl/generator_impl_import_test.cc",
-    "../src/writer/glsl/generator_impl_loop_test.cc",
-    "../src/writer/glsl/generator_impl_member_accessor_test.cc",
-    "../src/writer/glsl/generator_impl_module_constant_test.cc",
-    "../src/writer/glsl/generator_impl_return_test.cc",
-    "../src/writer/glsl/generator_impl_sanitizer_test.cc",
-    "../src/writer/glsl/generator_impl_storage_buffer_test.cc",
-    "../src/writer/glsl/generator_impl_switch_test.cc",
-    "../src/writer/glsl/generator_impl_test.cc",
-    "../src/writer/glsl/generator_impl_type_test.cc",
-    "../src/writer/glsl/generator_impl_unary_op_test.cc",
-    "../src/writer/glsl/generator_impl_uniform_buffer_test.cc",
-    "../src/writer/glsl/generator_impl_variable_decl_statement_test.cc",
-    "../src/writer/glsl/generator_impl_workgroup_var_test.cc",
-    "../src/writer/glsl/test_helper.h",
-  ]
-
-  deps = [
-    ":tint_unittests_ast_src",
-    ":tint_unittests_transform_src",
-    "${tint_root_dir}/src:libtint_glsl_writer_src",
-  ]
-}
-
-tint_unittests_source_set("tint_unittests_core_src") {
-  sources = [
-    "../src/block_allocator_test.cc",
-    "../src/builtin_table_test.cc",
-    "../src/castable_test.cc",
-    "../src/clone_context_test.cc",
-    "../src/debug_test.cc",
-    "../src/demangler_test.cc",
-    "../src/program_builder_test.cc",
-    "../src/program_test.cc",
-    "../src/scope_stack_test.cc",
-    "../src/source_test.cc",
-    "../src/symbol_table_test.cc",
-    "../src/symbol_test.cc",
-    "../src/traits_test.cc",
-  ]
-
-  deps = [ ":tint_unittests_ast_src" ]
-}
-
-if (build_with_chromium) {
-  tint_unittests_source_set("tint_unittests_fuzzer_src") {
-    sources = [ "../fuzzers/random_generator_test.cc" ]
-
-    deps = [
-      ":tint_unittests_core_src",
-      "${tint_root_dir}/fuzzers:tint_fuzzer_common_src",
-    ]
-  }
-}
-
-source_set("tint_unittests_src") {
-  testonly = true
-
-  deps = [
-    ":tint_unittests_ast_src",
-    ":tint_unittests_core_src",
-    ":tint_unittests_diagnostic_src",
-    ":tint_unittests_inspector_src",
-    ":tint_unittests_resolver_src",
-    ":tint_unittests_sem_src",
-    ":tint_unittests_text_src",
-    ":tint_unittests_transform_src",
-    ":tint_unittests_utils_src",
-    ":tint_unittests_writer_src",
-    "${tint_root_dir}/src:libtint_wgsl_reader_src",
-    "${tint_root_dir}/src:libtint_wgsl_writer_src",
-  ]
-
-  if (tint_build_spv_reader) {
-    deps += [ ":tint_unittests_spv_reader_src" ]
-  }
-
-  if (tint_build_spv_writer) {
-    deps += [ ":tint_unittests_spv_writer_src" ]
-  }
-
-  if (tint_build_wgsl_reader) {
-    deps += [ ":tint_unittests_wgsl_reader_src" ]
-  }
-
-  if (tint_build_wgsl_writer) {
-    deps += [ ":tint_unittests_wgsl_writer_src" ]
-  }
-
-  if (tint_build_msl_writer) {
-    deps += [ ":tint_unittests_msl_writer_src" ]
-  }
-
-  if (tint_build_hlsl_writer) {
-    deps += [ ":tint_unittests_hlsl_writer_src" ]
-  }
-
-  if (tint_build_glsl_writer) {
-    deps += [ ":tint_unittests_glsl_writer_src" ]
-  }
-
-  if (build_with_chromium) {
-    deps += [ ":tint_unittests_fuzzer_src" ]
-  }
-
-  configs += [ ":tint_unittests_config" ]
-
-  if (build_with_chromium) {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-  }
-}
-
-test("tint_unittests") {
-  deps = [
-    ":gmock_and_gtest",
-    ":tint_unittests_src",
-    "${tint_spirv_tools_dir}/:spvtools",
-    "${tint_spirv_tools_dir}/:spvtools_opt",
-    "${tint_spirv_tools_dir}/:spvtools_val",
-  ]
-
-  deps += [ ":tint_unittests_main" ]
-
-  configs += [ ":tint_unittests_config" ]
-
-  if (build_with_chromium) {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-  }
-
-  testonly = true
-}
diff --git a/test/extract-spvasm.py b/test/extract-spvasm.py
deleted file mode 100755
index acc5635..0000000
--- a/test/extract-spvasm.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python3
-
-# 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.
-
-# Extract SPIR-V assembly dumps from the output of
-#    tint_unittests --dump-spirv
-# Writes each module to a distinct filename, which is a sanitized
-# form of the test name, and with a ".spvasm" suffix.
-#
-# Usage:
-#    tint_unittests --dump-spirv | python3 extract-spvasm.py
-
-
-import sys
-import re
-
-def extract():
-    test_name = ''
-    in_spirv = False
-    parts = []
-    for line in sys.stdin:
-        run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
-        if run_match:
-            test_name = run_match.group(1)
-            test_name = re.sub('[^0-9a-zA-Z]', '_', test_name) + '.spvasm'
-        elif re.match('BEGIN ConvertedOk', line):
-            parts = []
-            in_spirv = True
-        elif re.match('END ConvertedOk', line):
-            with open(test_name, 'w') as f:
-                f.write('; Test: ' + test_name + '\n')
-                for l in parts:
-                    f.write(l)
-                f.close()
-        elif in_spirv:
-            parts.append(line)
-
-def main(argv):
-    if '--help' in argv or '-h' in argv:
-        print('Extract SPIR-V from the output of tint_unittests --dump-spirv\n')
-        print('Usage:\n    tint_unittests --dump-spirv | python3 extract-spvasm.py\n')
-        print('Writes each module to a distinct filename, which is a sanitized')
-        print('form of the test name, and with a ".spvasm" suffix.')
-        return 1
-    else:
-        extract()
-        return 0
-
-if __name__ == '__main__':
-    exit(main(sys.argv[1:]))
diff --git a/test/test-all.sh b/test/test-all.sh
deleted file mode 100755
index 5609fd3..0000000
--- a/test/test-all.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-set -e # Fail on any error.
-
-SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
-
-function usage() {
-    echo "test-all.sh is a simple wrapper around <tint>/tools/test-runner that"
-    echo "injects the <tint>/test directory as the second command line argument"
-    echo
-    echo "Usage of <tint>/tools/test-runner:"
-    "${SCRIPT_DIR}/../tools/test-runner" --help
-}
-
-TINT="$1"
-
-if [ -z "$TINT" ]; then
-    echo "error: missing argument: location of the 'tint' executable"
-    echo
-    usage
-    exit 1
-fi
-if [ ! -x "$TINT" ]; then
-    echo "error: invalid argument: location of the 'tint' executable"
-    echo
-    usage
-    exit 1
-fi
-
-"${SCRIPT_DIR}/../tools/test-runner" "${@:2}" "${TINT}" "${SCRIPT_DIR}"
diff --git a/test/tint/BUILD.gn b/test/tint/BUILD.gn
new file mode 100644
index 0000000..1b71428
--- /dev/null
+++ b/test/tint/BUILD.gn
@@ -0,0 +1,810 @@
+# 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.
+
+import("//build_overrides/build.gni")
+import("//testing/test.gni")
+import("../../tint_overrides_with_defaults.gni")
+
+###############################################################################
+# Gtest Gmock - Handle building inside and outside of Chromium.
+###############################################################################
+# When building outside of Chromium we need to define our own targets for GTest
+# and GMock. However when compiling inside of Chromium we need to reuse the
+# existing targets, both because Chromium has a special harness for swarming
+# and because otherwise the "gn check" fails.
+
+if (!build_with_chromium) {
+  # When we aren't in Chromium we define out own targets based on the location
+  # of the googletest repo.
+  config("gtest_config") {
+    include_dirs = [
+      "${tint_googletest_dir}/googletest",
+      "${tint_googletest_dir}/googletest/include",
+    ]
+  }
+  static_library("gtest") {
+    testonly = true
+    sources = [ "${tint_googletest_dir}/googletest/src/gtest-all.cc" ]
+    public_configs = [ ":gtest_config" ]
+  }
+
+  config("gmock_config") {
+    include_dirs = [
+      "${tint_googletest_dir}/googlemock",
+      "${tint_googletest_dir}/googlemock/include",
+      "${tint_googletest_dir}/googletest/include",
+    ]
+  }
+
+  static_library("gmock") {
+    testonly = true
+    sources = [ "${tint_googletest_dir}/googlemock/src/gmock-all.cc" ]
+    public_configs = [ ":gmock_config" ]
+  }
+
+  group("gmock_and_gtest") {
+    testonly = true
+    public_deps = [
+      ":gmock",
+      ":gtest",
+    ]
+  }
+} else {
+  # When we are in Chromium we reuse its targets, and also add some deps that
+  # are needed to launch the test in swarming mode.
+  group("gmock_and_gtest") {
+    testonly = true
+    public_deps = [
+      "//base",
+      "//base/test:test_support",
+      "//testing/gmock",
+      "//testing/gtest",
+      "//third_party/googletest:gmock",
+    ]
+  }
+}
+
+###############################################################################
+# Wrapping of Chromium targets
+###############################################################################
+# These targets are separated because they are Chromium sources files that
+# can't use the tint_internal config, otherwise Tint's warning flags get
+# applied while compiling a bunch of Chromium's //base (via header inclusion)
+source_set("tint_unittests_main") {
+  testonly = true
+  deps = [ ":gmock_and_gtest" ]
+  if (build_with_chromium) {
+    sources = [ "//gpu/tint_unittests_main.cc" ]
+  } else {
+    sources = [ "../../src/tint/test_main.cc" ]
+    configs += [ ":tint_unittests_config" ]
+    deps += [
+      ":tint_unittests_hlsl_writer_src",
+      ":tint_unittests_msl_writer_src",
+      ":tint_unittests_spv_reader_src",
+      "${tint_root_dir}/src/tint:libtint",
+    ]
+  }
+}
+
+###############################################################################
+# Tests - For libtint core and optional modules
+###############################################################################
+config("tint_unittests_config") {
+  include_dirs = [
+    "${tint_googletest_dir}/googlemock/include",
+    "${tint_googletest_dir}/googletest/include",
+  ]
+
+  configs = [
+    "${tint_root_dir}/src/tint:tint_common_config",
+    "${tint_root_dir}/src/tint:tint_public_config",
+  ]
+}
+
+template("tint_unittests_source_set") {
+  source_set(target_name) {
+    forward_variables_from(invoker, "*", [ "configs" ])
+
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+    configs += [ ":tint_unittests_config" ]
+    if (build_with_chromium) {
+      configs -= [ "//build/config/compiler:chromium_code" ]
+      configs += [ "//build/config/compiler:no_chromium_code" ]
+    }
+
+    testonly = true
+
+    if (!defined(invoker.deps)) {
+      deps = []
+    }
+    deps += [
+      ":gmock_and_gtest",
+      "${tint_root_dir}/src/tint:libtint",
+      "${tint_root_dir}/src/tint:tint_utils_io",
+    ]
+  }
+}
+
+tint_unittests_source_set("tint_unittests_ast_src") {
+  sources = [
+    "../../src/tint/ast/alias_test.cc",
+    "../../src/tint/ast/array_test.cc",
+    "../../src/tint/ast/assignment_statement_test.cc",
+    "../../src/tint/ast/atomic_test.cc",
+    "../../src/tint/ast/binary_expression_test.cc",
+    "../../src/tint/ast/binding_attribute_test.cc",
+    "../../src/tint/ast/bitcast_expression_test.cc",
+    "../../src/tint/ast/block_statement_test.cc",
+    "../../src/tint/ast/bool_literal_expression_test.cc",
+    "../../src/tint/ast/bool_test.cc",
+    "../../src/tint/ast/break_statement_test.cc",
+    "../../src/tint/ast/builtin_attribute_test.cc",
+    "../../src/tint/ast/builtin_texture_helper_test.cc",
+    "../../src/tint/ast/builtin_texture_helper_test.h",
+    "../../src/tint/ast/call_expression_test.cc",
+    "../../src/tint/ast/call_statement_test.cc",
+    "../../src/tint/ast/case_statement_test.cc",
+    "../../src/tint/ast/continue_statement_test.cc",
+    "../../src/tint/ast/depth_multisampled_texture_test.cc",
+    "../../src/tint/ast/depth_texture_test.cc",
+    "../../src/tint/ast/discard_statement_test.cc",
+    "../../src/tint/ast/else_statement_test.cc",
+    "../../src/tint/ast/external_texture_test.cc",
+    "../../src/tint/ast/f32_test.cc",
+    "../../src/tint/ast/fallthrough_statement_test.cc",
+    "../../src/tint/ast/float_literal_expression_test.cc",
+    "../../src/tint/ast/for_loop_statement_test.cc",
+    "../../src/tint/ast/function_test.cc",
+    "../../src/tint/ast/group_attribute_test.cc",
+    "../../src/tint/ast/i32_test.cc",
+    "../../src/tint/ast/id_attribute_test.cc",
+    "../../src/tint/ast/identifier_expression_test.cc",
+    "../../src/tint/ast/if_statement_test.cc",
+    "../../src/tint/ast/index_accessor_expression_test.cc",
+    "../../src/tint/ast/int_literal_expression_test.cc",
+    "../../src/tint/ast/interpolate_attribute_test.cc",
+    "../../src/tint/ast/invariant_attribute_test.cc",
+    "../../src/tint/ast/location_attribute_test.cc",
+    "../../src/tint/ast/loop_statement_test.cc",
+    "../../src/tint/ast/matrix_test.cc",
+    "../../src/tint/ast/member_accessor_expression_test.cc",
+    "../../src/tint/ast/module_clone_test.cc",
+    "../../src/tint/ast/module_test.cc",
+    "../../src/tint/ast/multisampled_texture_test.cc",
+    "../../src/tint/ast/phony_expression_test.cc",
+    "../../src/tint/ast/pointer_test.cc",
+    "../../src/tint/ast/return_statement_test.cc",
+    "../../src/tint/ast/sampled_texture_test.cc",
+    "../../src/tint/ast/sampler_test.cc",
+    "../../src/tint/ast/sint_literal_expression_test.cc",
+    "../../src/tint/ast/stage_attribute_test.cc",
+    "../../src/tint/ast/storage_texture_test.cc",
+    "../../src/tint/ast/stride_attribute_test.cc",
+    "../../src/tint/ast/struct_member_align_attribute_test.cc",
+    "../../src/tint/ast/struct_member_offset_attribute_test.cc",
+    "../../src/tint/ast/struct_member_size_attribute_test.cc",
+    "../../src/tint/ast/struct_member_test.cc",
+    "../../src/tint/ast/struct_test.cc",
+    "../../src/tint/ast/switch_statement_test.cc",
+    "../../src/tint/ast/test_helper.h",
+    "../../src/tint/ast/texture_test.cc",
+    "../../src/tint/ast/traverse_expressions_test.cc",
+    "../../src/tint/ast/u32_test.cc",
+    "../../src/tint/ast/uint_literal_expression_test.cc",
+    "../../src/tint/ast/unary_op_expression_test.cc",
+    "../../src/tint/ast/variable_decl_statement_test.cc",
+    "../../src/tint/ast/variable_test.cc",
+    "../../src/tint/ast/vector_test.cc",
+    "../../src/tint/ast/workgroup_attribute_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_diagnostic_src") {
+  sources = [
+    "../../src/tint/diagnostic/diagnostic_test.cc",
+    "../../src/tint/diagnostic/formatter_test.cc",
+    "../../src/tint/diagnostic/printer_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_inspector_src") {
+  sources = [
+    "../../src/tint/inspector/inspector_test.cc",
+    "../../src/tint/inspector/test_inspector_builder.cc",
+    "../../src/tint/inspector/test_inspector_builder.h",
+    "../../src/tint/inspector/test_inspector_runner.cc",
+    "../../src/tint/inspector/test_inspector_runner.h",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_resolver_src") {
+  sources = [
+    "../../src/tint/resolver/array_accessor_test.cc",
+    "../../src/tint/resolver/assignment_validation_test.cc",
+    "../../src/tint/resolver/atomics_test.cc",
+    "../../src/tint/resolver/atomics_validation_test.cc",
+    "../../src/tint/resolver/attribute_validation_test.cc",
+    "../../src/tint/resolver/bitcast_validation_test.cc",
+    "../../src/tint/resolver/builtin_test.cc",
+    "../../src/tint/resolver/builtin_validation_test.cc",
+    "../../src/tint/resolver/builtins_validation_test.cc",
+    "../../src/tint/resolver/call_test.cc",
+    "../../src/tint/resolver/call_validation_test.cc",
+    "../../src/tint/resolver/compound_statement_test.cc",
+    "../../src/tint/resolver/control_block_validation_test.cc",
+    "../../src/tint/resolver/dependency_graph_test.cc",
+    "../../src/tint/resolver/entry_point_validation_test.cc",
+    "../../src/tint/resolver/function_validation_test.cc",
+    "../../src/tint/resolver/host_shareable_validation_test.cc",
+    "../../src/tint/resolver/is_host_shareable_test.cc",
+    "../../src/tint/resolver/is_storeable_test.cc",
+    "../../src/tint/resolver/pipeline_overridable_constant_test.cc",
+    "../../src/tint/resolver/ptr_ref_test.cc",
+    "../../src/tint/resolver/ptr_ref_validation_test.cc",
+    "../../src/tint/resolver/resolver_behavior_test.cc",
+    "../../src/tint/resolver/resolver_constants_test.cc",
+    "../../src/tint/resolver/resolver_test.cc",
+    "../../src/tint/resolver/resolver_test_helper.cc",
+    "../../src/tint/resolver/resolver_test_helper.h",
+    "../../src/tint/resolver/side_effects_test.cc",
+    "../../src/tint/resolver/storage_class_layout_validation_test.cc",
+    "../../src/tint/resolver/storage_class_validation_test.cc",
+    "../../src/tint/resolver/struct_layout_test.cc",
+    "../../src/tint/resolver/struct_pipeline_stage_use_test.cc",
+    "../../src/tint/resolver/struct_storage_class_use_test.cc",
+    "../../src/tint/resolver/type_constructor_validation_test.cc",
+    "../../src/tint/resolver/type_validation_test.cc",
+    "../../src/tint/resolver/validation_test.cc",
+    "../../src/tint/resolver/var_let_test.cc",
+    "../../src/tint/resolver/var_let_validation_test.cc",
+  ]
+  deps = [ ":tint_unittests_ast_src" ]
+}
+
+tint_unittests_source_set("tint_unittests_sem_src") {
+  sources = [
+    "../../src/tint/sem/atomic_type_test.cc",
+    "../../src/tint/sem/bool_type_test.cc",
+    "../../src/tint/sem/builtin_test.cc",
+    "../../src/tint/sem/depth_multisampled_texture_type_test.cc",
+    "../../src/tint/sem/depth_texture_type_test.cc",
+    "../../src/tint/sem/external_texture_type_test.cc",
+    "../../src/tint/sem/f32_type_test.cc",
+    "../../src/tint/sem/i32_type_test.cc",
+    "../../src/tint/sem/matrix_type_test.cc",
+    "../../src/tint/sem/multisampled_texture_type_test.cc",
+    "../../src/tint/sem/pointer_type_test.cc",
+    "../../src/tint/sem/reference_type_test.cc",
+    "../../src/tint/sem/sampled_texture_type_test.cc",
+    "../../src/tint/sem/sampler_type_test.cc",
+    "../../src/tint/sem/sem_array_test.cc",
+    "../../src/tint/sem/sem_struct_test.cc",
+    "../../src/tint/sem/storage_texture_type_test.cc",
+    "../../src/tint/sem/texture_type_test.cc",
+    "../../src/tint/sem/type_manager_test.cc",
+    "../../src/tint/sem/u32_type_test.cc",
+    "../../src/tint/sem/vector_type_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_text_src") {
+  sources = [ "../../src/tint/text/unicode_test.cc" ]
+}
+
+tint_unittests_source_set("tint_unittests_transform_src") {
+  sources = [
+    "../../src/tint/transform/add_empty_entry_point_test.cc",
+    "../../src/tint/transform/add_spirv_block_attribute_test.cc",
+    "../../src/tint/transform/array_length_from_uniform_test.cc",
+    "../../src/tint/transform/binding_remapper_test.cc",
+    "../../src/tint/transform/calculate_array_length_test.cc",
+    "../../src/tint/transform/canonicalize_entry_point_io_test.cc",
+    "../../src/tint/transform/combine_samplers_test.cc",
+    "../../src/tint/transform/decompose_memory_access_test.cc",
+    "../../src/tint/transform/decompose_strided_array_test.cc",
+    "../../src/tint/transform/decompose_strided_matrix_test.cc",
+    "../../src/tint/transform/external_texture_transform_test.cc",
+    "../../src/tint/transform/first_index_offset_test.cc",
+    "../../src/tint/transform/fold_constants_test.cc",
+    "../../src/tint/transform/fold_trivial_single_use_lets_test.cc",
+    "../../src/tint/transform/for_loop_to_loop_test.cc",
+    "../../src/tint/transform/localize_struct_array_assignment_test.cc",
+    "../../src/tint/transform/loop_to_for_loop_test.cc",
+    "../../src/tint/transform/module_scope_var_to_entry_point_param_test.cc",
+    "../../src/tint/transform/multiplanar_external_texture_test.cc",
+    "../../src/tint/transform/num_workgroups_from_uniform_test.cc",
+    "../../src/tint/transform/pad_array_elements_test.cc",
+    "../../src/tint/transform/promote_initializers_to_const_var_test.cc",
+    "../../src/tint/transform/remove_phonies_test.cc",
+    "../../src/tint/transform/remove_unreachable_statements_test.cc",
+    "../../src/tint/transform/renamer_test.cc",
+    "../../src/tint/transform/robustness_test.cc",
+    "../../src/tint/transform/simplify_pointers_test.cc",
+    "../../src/tint/transform/single_entry_point_test.cc",
+    "../../src/tint/transform/test_helper.h",
+    "../../src/tint/transform/transform_test.cc",
+    "../../src/tint/transform/unshadow_test.cc",
+    "../../src/tint/transform/utils/hoist_to_decl_before_test.cc",
+    "../../src/tint/transform/var_for_dynamic_index_test.cc",
+    "../../src/tint/transform/vectorize_scalar_matrix_constructors_test.cc",
+    "../../src/tint/transform/vertex_pulling_test.cc",
+    "../../src/tint/transform/wrap_arrays_in_structs_test.cc",
+    "../../src/tint/transform/zero_init_workgroup_memory_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_utils_src") {
+  sources = [
+    "../../src/tint/utils/crc32_test.cc",
+    "../../src/tint/utils/defer_test.cc",
+    "../../src/tint/utils/enum_set_test.cc",
+    "../../src/tint/utils/hash_test.cc",
+    "../../src/tint/utils/io/command_test.cc",
+    "../../src/tint/utils/io/tmpfile_test.cc",
+    "../../src/tint/utils/map_test.cc",
+    "../../src/tint/utils/math_test.cc",
+    "../../src/tint/utils/reverse_test.cc",
+    "../../src/tint/utils/scoped_assignment_test.cc",
+    "../../src/tint/utils/string_test.cc",
+    "../../src/tint/utils/transform_test.cc",
+    "../../src/tint/utils/unique_vector_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_writer_src") {
+  sources = [
+    "../../src/tint/writer/append_vector_test.cc",
+    "../../src/tint/writer/float_to_string_test.cc",
+    "../../src/tint/writer/text_generator_test.cc",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_spv_reader_src") {
+  sources = [
+    "../../src/tint/reader/spirv/enum_converter_test.cc",
+    "../../src/tint/reader/spirv/fail_stream_test.cc",
+    "../../src/tint/reader/spirv/function_arithmetic_test.cc",
+    "../../src/tint/reader/spirv/function_bit_test.cc",
+    "../../src/tint/reader/spirv/function_call_test.cc",
+    "../../src/tint/reader/spirv/function_cfg_test.cc",
+    "../../src/tint/reader/spirv/function_composite_test.cc",
+    "../../src/tint/reader/spirv/function_conversion_test.cc",
+    "../../src/tint/reader/spirv/function_decl_test.cc",
+    "../../src/tint/reader/spirv/function_glsl_std_450_test.cc",
+    "../../src/tint/reader/spirv/function_logical_test.cc",
+    "../../src/tint/reader/spirv/function_memory_test.cc",
+    "../../src/tint/reader/spirv/function_misc_test.cc",
+    "../../src/tint/reader/spirv/function_var_test.cc",
+    "../../src/tint/reader/spirv/namer_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_barrier_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_convert_type_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_function_decl_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_get_decorations_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_handle_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_import_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_module_var_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_named_types_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_test.cc",
+    "../../src/tint/reader/spirv/parser_impl_test_helper.cc",
+    "../../src/tint/reader/spirv/parser_impl_test_helper.h",
+    "../../src/tint/reader/spirv/parser_impl_user_name_test.cc",
+    "../../src/tint/reader/spirv/parser_test.cc",
+    "../../src/tint/reader/spirv/parser_type_test.cc",
+    "../../src/tint/reader/spirv/spirv_tools_helpers_test.cc",
+    "../../src/tint/reader/spirv/spirv_tools_helpers_test.h",
+    "../../src/tint/reader/spirv/usage_test.cc",
+  ]
+
+  deps = [ "${tint_root_dir}/src/tint:libtint_spv_reader_src" ]
+}
+
+tint_unittests_source_set("tint_unittests_spv_writer_src") {
+  sources = [
+    "../../src/tint/writer/spirv/binary_writer_test.cc",
+    "../../src/tint/writer/spirv/builder_accessor_expression_test.cc",
+    "../../src/tint/writer/spirv/builder_assign_test.cc",
+    "../../src/tint/writer/spirv/builder_binary_expression_test.cc",
+    "../../src/tint/writer/spirv/builder_bitcast_expression_test.cc",
+    "../../src/tint/writer/spirv/builder_block_test.cc",
+    "../../src/tint/writer/spirv/builder_builtin_test.cc",
+    "../../src/tint/writer/spirv/builder_builtin_texture_test.cc",
+    "../../src/tint/writer/spirv/builder_call_test.cc",
+    "../../src/tint/writer/spirv/builder_constructor_expression_test.cc",
+    "../../src/tint/writer/spirv/builder_discard_test.cc",
+    "../../src/tint/writer/spirv/builder_entry_point_test.cc",
+    "../../src/tint/writer/spirv/builder_format_conversion_test.cc",
+    "../../src/tint/writer/spirv/builder_function_attribute_test.cc",
+    "../../src/tint/writer/spirv/builder_function_test.cc",
+    "../../src/tint/writer/spirv/builder_function_variable_test.cc",
+    "../../src/tint/writer/spirv/builder_global_variable_test.cc",
+    "../../src/tint/writer/spirv/builder_ident_expression_test.cc",
+    "../../src/tint/writer/spirv/builder_if_test.cc",
+    "../../src/tint/writer/spirv/builder_literal_test.cc",
+    "../../src/tint/writer/spirv/builder_loop_test.cc",
+    "../../src/tint/writer/spirv/builder_return_test.cc",
+    "../../src/tint/writer/spirv/builder_switch_test.cc",
+    "../../src/tint/writer/spirv/builder_test.cc",
+    "../../src/tint/writer/spirv/builder_type_test.cc",
+    "../../src/tint/writer/spirv/builder_unary_op_expression_test.cc",
+    "../../src/tint/writer/spirv/instruction_test.cc",
+    "../../src/tint/writer/spirv/operand_test.cc",
+    "../../src/tint/writer/spirv/scalar_constant_test.cc",
+    "../../src/tint/writer/spirv/spv_dump.cc",
+    "../../src/tint/writer/spirv/spv_dump.h",
+    "../../src/tint/writer/spirv/test_helper.h",
+  ]
+
+  deps = [
+    ":tint_unittests_ast_src",
+    "${tint_root_dir}/src/tint:libtint_spv_writer_src",
+    "${tint_spirv_tools_dir}/:spvtools",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_wgsl_reader_src") {
+  sources = [
+    "../../src/tint/reader/wgsl/lexer_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_additive_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_and_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_body_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_break_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_bug_cases_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_call_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_case_body_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_const_expr_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_const_literal_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_depth_texture_type_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_elseif_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_equality_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_error_msg_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_error_resync_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_exclusive_or_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_external_texture_type_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_for_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_function_attribute_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_function_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_function_header_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_global_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_if_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_inclusive_or_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_logical_and_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_logical_or_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_param_list_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_pipeline_stage_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_primary_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_relational_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_sampled_texture_type_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_sampler_type_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_shift_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_singular_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_statement_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_statements_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_storage_class_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_storage_texture_type_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_attribute_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_member_attribute_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_struct_member_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_switch_body_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_test_helper.cc",
+    "../../src/tint/reader/wgsl/parser_impl_test_helper.h",
+    "../../src/tint/reader/wgsl/parser_impl_texel_format_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_texture_sampler_types_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_type_alias_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_type_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_unary_expression_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc",
+    "../../src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc",
+    "../../src/tint/reader/wgsl/parser_test.cc",
+    "../../src/tint/reader/wgsl/token_test.cc",
+  ]
+
+  deps = [ "${tint_root_dir}/src/tint:libtint_wgsl_reader_src" ]
+}
+
+tint_unittests_source_set("tint_unittests_wgsl_writer_src") {
+  sources = [
+    "../../src/tint/writer/wgsl/generator_impl_alias_type_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_array_accessor_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_assign_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_binary_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_bitcast_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_block_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_break_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_call_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_case_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_cast_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_constructor_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_continue_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_discard_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_fallthrough_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_function_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_global_decl_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_identifier_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_if_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_literal_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_loop_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_member_accessor_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_return_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_switch_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_type_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_unary_op_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc",
+    "../../src/tint/writer/wgsl/generator_impl_variable_test.cc",
+    "../../src/tint/writer/wgsl/test_helper.h",
+  ]
+
+  deps = [
+    ":tint_unittests_ast_src",
+    "${tint_root_dir}/src/tint:libtint_wgsl_writer_src",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_msl_writer_src") {
+  sources = [
+    "../../src/tint/writer/msl/generator_impl_array_accessor_test.cc",
+    "../../src/tint/writer/msl/generator_impl_assign_test.cc",
+    "../../src/tint/writer/msl/generator_impl_binary_test.cc",
+    "../../src/tint/writer/msl/generator_impl_bitcast_test.cc",
+    "../../src/tint/writer/msl/generator_impl_block_test.cc",
+    "../../src/tint/writer/msl/generator_impl_break_test.cc",
+    "../../src/tint/writer/msl/generator_impl_builtin_test.cc",
+    "../../src/tint/writer/msl/generator_impl_builtin_texture_test.cc",
+    "../../src/tint/writer/msl/generator_impl_call_test.cc",
+    "../../src/tint/writer/msl/generator_impl_case_test.cc",
+    "../../src/tint/writer/msl/generator_impl_cast_test.cc",
+    "../../src/tint/writer/msl/generator_impl_constructor_test.cc",
+    "../../src/tint/writer/msl/generator_impl_continue_test.cc",
+    "../../src/tint/writer/msl/generator_impl_discard_test.cc",
+    "../../src/tint/writer/msl/generator_impl_function_test.cc",
+    "../../src/tint/writer/msl/generator_impl_identifier_test.cc",
+    "../../src/tint/writer/msl/generator_impl_if_test.cc",
+    "../../src/tint/writer/msl/generator_impl_import_test.cc",
+    "../../src/tint/writer/msl/generator_impl_loop_test.cc",
+    "../../src/tint/writer/msl/generator_impl_member_accessor_test.cc",
+    "../../src/tint/writer/msl/generator_impl_module_constant_test.cc",
+    "../../src/tint/writer/msl/generator_impl_return_test.cc",
+    "../../src/tint/writer/msl/generator_impl_sanitizer_test.cc",
+    "../../src/tint/writer/msl/generator_impl_switch_test.cc",
+    "../../src/tint/writer/msl/generator_impl_test.cc",
+    "../../src/tint/writer/msl/generator_impl_type_test.cc",
+    "../../src/tint/writer/msl/generator_impl_unary_op_test.cc",
+    "../../src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc",
+    "../../src/tint/writer/msl/test_helper.h",
+  ]
+
+  deps = [
+    ":tint_unittests_ast_src",
+    "${tint_root_dir}/src/tint:libtint_msl_writer_src",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_hlsl_writer_src") {
+  sources = [
+    "../../src/tint/writer/hlsl/generator_impl_array_accessor_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_assign_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_binary_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_bitcast_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_block_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_break_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_builtin_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_call_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_case_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_cast_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_constructor_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_continue_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_discard_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_function_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_identifier_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_if_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_import_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_loop_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_member_accessor_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_module_constant_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_return_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_sanitizer_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_switch_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_type_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_unary_op_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc",
+    "../../src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc",
+    "../../src/tint/writer/hlsl/test_helper.h",
+  ]
+
+  deps = [
+    ":tint_unittests_ast_src",
+    "${tint_root_dir}/src/tint:libtint_hlsl_writer_src",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_glsl_writer_src") {
+  sources = [
+    "../../src/tint/transform/glsl_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_array_accessor_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_assign_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_binary_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_bitcast_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_block_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_break_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_builtin_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_builtin_texture_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_call_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_case_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_cast_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_constructor_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_continue_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_discard_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_function_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_identifier_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_if_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_import_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_loop_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_member_accessor_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_module_constant_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_return_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_sanitizer_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_storage_buffer_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_switch_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_type_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_unary_op_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc",
+    "../../src/tint/writer/glsl/generator_impl_workgroup_var_test.cc",
+    "../../src/tint/writer/glsl/test_helper.h",
+  ]
+
+  deps = [
+    ":tint_unittests_ast_src",
+    ":tint_unittests_transform_src",
+    "${tint_root_dir}/src/tint:libtint_glsl_writer_src",
+  ]
+}
+
+tint_unittests_source_set("tint_unittests_core_src") {
+  sources = [
+    "../../src/tint/block_allocator_test.cc",
+    "../../src/tint/builtin_table_test.cc",
+    "../../src/tint/castable_test.cc",
+    "../../src/tint/clone_context_test.cc",
+    "../../src/tint/debug_test.cc",
+    "../../src/tint/demangler_test.cc",
+    "../../src/tint/program_builder_test.cc",
+    "../../src/tint/program_test.cc",
+    "../../src/tint/scope_stack_test.cc",
+    "../../src/tint/source_test.cc",
+    "../../src/tint/symbol_table_test.cc",
+    "../../src/tint/symbol_test.cc",
+    "../../src/tint/traits_test.cc",
+  ]
+
+  deps = [ ":tint_unittests_ast_src" ]
+}
+
+if (build_with_chromium) {
+  tint_unittests_source_set("tint_unittests_fuzzer_src") {
+    sources = [ "../../src/tint/fuzzers/random_generator_test.cc" ]
+
+    deps = [
+      ":tint_unittests_core_src",
+      "${tint_root_dir}/src/tint/fuzzers:tint_fuzzer_common_src",
+    ]
+  }
+}
+
+source_set("tint_unittests_src") {
+  testonly = true
+
+  deps = [
+    ":tint_unittests_ast_src",
+    ":tint_unittests_core_src",
+    ":tint_unittests_diagnostic_src",
+    ":tint_unittests_inspector_src",
+    ":tint_unittests_resolver_src",
+    ":tint_unittests_sem_src",
+    ":tint_unittests_text_src",
+    ":tint_unittests_transform_src",
+    ":tint_unittests_utils_src",
+    ":tint_unittests_writer_src",
+    "${tint_root_dir}/src/tint:libtint_wgsl_reader_src",
+    "${tint_root_dir}/src/tint:libtint_wgsl_writer_src",
+  ]
+
+  if (tint_build_spv_reader) {
+    deps += [ ":tint_unittests_spv_reader_src" ]
+  }
+
+  if (tint_build_spv_writer) {
+    deps += [ ":tint_unittests_spv_writer_src" ]
+  }
+
+  if (tint_build_wgsl_reader) {
+    deps += [ ":tint_unittests_wgsl_reader_src" ]
+  }
+
+  if (tint_build_wgsl_writer) {
+    deps += [ ":tint_unittests_wgsl_writer_src" ]
+  }
+
+  if (tint_build_msl_writer) {
+    deps += [ ":tint_unittests_msl_writer_src" ]
+  }
+
+  if (tint_build_hlsl_writer) {
+    deps += [ ":tint_unittests_hlsl_writer_src" ]
+  }
+
+  if (tint_build_glsl_writer) {
+    deps += [ ":tint_unittests_glsl_writer_src" ]
+  }
+
+  if (build_with_chromium) {
+    deps += [ ":tint_unittests_fuzzer_src" ]
+  }
+
+  configs += [ ":tint_unittests_config" ]
+
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+}
+
+test("tint_unittests") {
+  deps = [
+    ":gmock_and_gtest",
+    ":tint_unittests_src",
+    "${tint_spirv_tools_dir}/:spvtools",
+    "${tint_spirv_tools_dir}/:spvtools_opt",
+    "${tint_spirv_tools_dir}/:spvtools_val",
+  ]
+
+  deps += [ ":tint_unittests_main" ]
+
+  configs += [ ":tint_unittests_config" ]
+
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+
+  testonly = true
+}
diff --git a/test/access/let/matrix.spvasm b/test/tint/access/let/matrix.spvasm
similarity index 100%
rename from test/access/let/matrix.spvasm
rename to test/tint/access/let/matrix.spvasm
diff --git a/test/access/let/matrix.spvasm.expected.glsl b/test/tint/access/let/matrix.spvasm.expected.glsl
similarity index 100%
rename from test/access/let/matrix.spvasm.expected.glsl
rename to test/tint/access/let/matrix.spvasm.expected.glsl
diff --git a/test/access/let/matrix.spvasm.expected.hlsl b/test/tint/access/let/matrix.spvasm.expected.hlsl
similarity index 100%
rename from test/access/let/matrix.spvasm.expected.hlsl
rename to test/tint/access/let/matrix.spvasm.expected.hlsl
diff --git a/test/access/let/matrix.spvasm.expected.msl b/test/tint/access/let/matrix.spvasm.expected.msl
similarity index 100%
rename from test/access/let/matrix.spvasm.expected.msl
rename to test/tint/access/let/matrix.spvasm.expected.msl
diff --git a/test/access/let/matrix.spvasm.expected.spvasm b/test/tint/access/let/matrix.spvasm.expected.spvasm
similarity index 100%
rename from test/access/let/matrix.spvasm.expected.spvasm
rename to test/tint/access/let/matrix.spvasm.expected.spvasm
diff --git a/test/access/let/matrix.spvasm.expected.wgsl b/test/tint/access/let/matrix.spvasm.expected.wgsl
similarity index 100%
rename from test/access/let/matrix.spvasm.expected.wgsl
rename to test/tint/access/let/matrix.spvasm.expected.wgsl
diff --git a/test/access/let/matrix.wgsl b/test/tint/access/let/matrix.wgsl
similarity index 100%
rename from test/access/let/matrix.wgsl
rename to test/tint/access/let/matrix.wgsl
diff --git a/test/access/let/matrix.wgsl.expected.glsl b/test/tint/access/let/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/access/let/matrix.wgsl.expected.glsl
rename to test/tint/access/let/matrix.wgsl.expected.glsl
diff --git a/test/access/let/matrix.wgsl.expected.hlsl b/test/tint/access/let/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/access/let/matrix.wgsl.expected.hlsl
rename to test/tint/access/let/matrix.wgsl.expected.hlsl
diff --git a/test/access/let/matrix.wgsl.expected.msl b/test/tint/access/let/matrix.wgsl.expected.msl
similarity index 100%
rename from test/access/let/matrix.wgsl.expected.msl
rename to test/tint/access/let/matrix.wgsl.expected.msl
diff --git a/test/access/let/matrix.wgsl.expected.spvasm b/test/tint/access/let/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/access/let/matrix.wgsl.expected.spvasm
rename to test/tint/access/let/matrix.wgsl.expected.spvasm
diff --git a/test/access/let/matrix.wgsl.expected.wgsl b/test/tint/access/let/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/access/let/matrix.wgsl.expected.wgsl
rename to test/tint/access/let/matrix.wgsl.expected.wgsl
diff --git a/test/access/let/vector.spvasm b/test/tint/access/let/vector.spvasm
similarity index 100%
rename from test/access/let/vector.spvasm
rename to test/tint/access/let/vector.spvasm
diff --git a/test/access/let/vector.spvasm.expected.glsl b/test/tint/access/let/vector.spvasm.expected.glsl
similarity index 100%
rename from test/access/let/vector.spvasm.expected.glsl
rename to test/tint/access/let/vector.spvasm.expected.glsl
diff --git a/test/access/let/vector.spvasm.expected.hlsl b/test/tint/access/let/vector.spvasm.expected.hlsl
similarity index 100%
rename from test/access/let/vector.spvasm.expected.hlsl
rename to test/tint/access/let/vector.spvasm.expected.hlsl
diff --git a/test/access/let/vector.spvasm.expected.msl b/test/tint/access/let/vector.spvasm.expected.msl
similarity index 100%
rename from test/access/let/vector.spvasm.expected.msl
rename to test/tint/access/let/vector.spvasm.expected.msl
diff --git a/test/access/let/vector.spvasm.expected.spvasm b/test/tint/access/let/vector.spvasm.expected.spvasm
similarity index 100%
rename from test/access/let/vector.spvasm.expected.spvasm
rename to test/tint/access/let/vector.spvasm.expected.spvasm
diff --git a/test/access/let/vector.spvasm.expected.wgsl b/test/tint/access/let/vector.spvasm.expected.wgsl
similarity index 100%
rename from test/access/let/vector.spvasm.expected.wgsl
rename to test/tint/access/let/vector.spvasm.expected.wgsl
diff --git a/test/access/let/vector.wgsl b/test/tint/access/let/vector.wgsl
similarity index 100%
rename from test/access/let/vector.wgsl
rename to test/tint/access/let/vector.wgsl
diff --git a/test/access/let/vector.wgsl.expected.glsl b/test/tint/access/let/vector.wgsl.expected.glsl
similarity index 100%
rename from test/access/let/vector.wgsl.expected.glsl
rename to test/tint/access/let/vector.wgsl.expected.glsl
diff --git a/test/access/let/vector.wgsl.expected.hlsl b/test/tint/access/let/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/access/let/vector.wgsl.expected.hlsl
rename to test/tint/access/let/vector.wgsl.expected.hlsl
diff --git a/test/access/let/vector.wgsl.expected.msl b/test/tint/access/let/vector.wgsl.expected.msl
similarity index 100%
rename from test/access/let/vector.wgsl.expected.msl
rename to test/tint/access/let/vector.wgsl.expected.msl
diff --git a/test/access/let/vector.wgsl.expected.spvasm b/test/tint/access/let/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/access/let/vector.wgsl.expected.spvasm
rename to test/tint/access/let/vector.wgsl.expected.spvasm
diff --git a/test/access/let/vector.wgsl.expected.wgsl b/test/tint/access/let/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/access/let/vector.wgsl.expected.wgsl
rename to test/tint/access/let/vector.wgsl.expected.wgsl
diff --git a/test/access/var/matrix.spvasm b/test/tint/access/var/matrix.spvasm
similarity index 100%
rename from test/access/var/matrix.spvasm
rename to test/tint/access/var/matrix.spvasm
diff --git a/test/access/var/matrix.spvasm.expected.glsl b/test/tint/access/var/matrix.spvasm.expected.glsl
similarity index 100%
rename from test/access/var/matrix.spvasm.expected.glsl
rename to test/tint/access/var/matrix.spvasm.expected.glsl
diff --git a/test/access/var/matrix.spvasm.expected.hlsl b/test/tint/access/var/matrix.spvasm.expected.hlsl
similarity index 100%
rename from test/access/var/matrix.spvasm.expected.hlsl
rename to test/tint/access/var/matrix.spvasm.expected.hlsl
diff --git a/test/access/var/matrix.spvasm.expected.msl b/test/tint/access/var/matrix.spvasm.expected.msl
similarity index 100%
rename from test/access/var/matrix.spvasm.expected.msl
rename to test/tint/access/var/matrix.spvasm.expected.msl
diff --git a/test/access/var/matrix.spvasm.expected.spvasm b/test/tint/access/var/matrix.spvasm.expected.spvasm
similarity index 100%
rename from test/access/var/matrix.spvasm.expected.spvasm
rename to test/tint/access/var/matrix.spvasm.expected.spvasm
diff --git a/test/access/var/matrix.spvasm.expected.wgsl b/test/tint/access/var/matrix.spvasm.expected.wgsl
similarity index 100%
rename from test/access/var/matrix.spvasm.expected.wgsl
rename to test/tint/access/var/matrix.spvasm.expected.wgsl
diff --git a/test/access/var/matrix.wgsl b/test/tint/access/var/matrix.wgsl
similarity index 100%
rename from test/access/var/matrix.wgsl
rename to test/tint/access/var/matrix.wgsl
diff --git a/test/access/var/matrix.wgsl.expected.glsl b/test/tint/access/var/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/access/var/matrix.wgsl.expected.glsl
rename to test/tint/access/var/matrix.wgsl.expected.glsl
diff --git a/test/access/var/matrix.wgsl.expected.hlsl b/test/tint/access/var/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/access/var/matrix.wgsl.expected.hlsl
rename to test/tint/access/var/matrix.wgsl.expected.hlsl
diff --git a/test/access/var/matrix.wgsl.expected.msl b/test/tint/access/var/matrix.wgsl.expected.msl
similarity index 100%
rename from test/access/var/matrix.wgsl.expected.msl
rename to test/tint/access/var/matrix.wgsl.expected.msl
diff --git a/test/access/var/matrix.wgsl.expected.spvasm b/test/tint/access/var/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/access/var/matrix.wgsl.expected.spvasm
rename to test/tint/access/var/matrix.wgsl.expected.spvasm
diff --git a/test/access/var/matrix.wgsl.expected.wgsl b/test/tint/access/var/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/access/var/matrix.wgsl.expected.wgsl
rename to test/tint/access/var/matrix.wgsl.expected.wgsl
diff --git a/test/access/var/vector.spvasm b/test/tint/access/var/vector.spvasm
similarity index 100%
rename from test/access/var/vector.spvasm
rename to test/tint/access/var/vector.spvasm
diff --git a/test/access/var/vector.spvasm.expected.glsl b/test/tint/access/var/vector.spvasm.expected.glsl
similarity index 100%
rename from test/access/var/vector.spvasm.expected.glsl
rename to test/tint/access/var/vector.spvasm.expected.glsl
diff --git a/test/access/var/vector.spvasm.expected.hlsl b/test/tint/access/var/vector.spvasm.expected.hlsl
similarity index 100%
rename from test/access/var/vector.spvasm.expected.hlsl
rename to test/tint/access/var/vector.spvasm.expected.hlsl
diff --git a/test/access/var/vector.spvasm.expected.msl b/test/tint/access/var/vector.spvasm.expected.msl
similarity index 100%
rename from test/access/var/vector.spvasm.expected.msl
rename to test/tint/access/var/vector.spvasm.expected.msl
diff --git a/test/access/var/vector.spvasm.expected.spvasm b/test/tint/access/var/vector.spvasm.expected.spvasm
similarity index 100%
rename from test/access/var/vector.spvasm.expected.spvasm
rename to test/tint/access/var/vector.spvasm.expected.spvasm
diff --git a/test/access/var/vector.spvasm.expected.wgsl b/test/tint/access/var/vector.spvasm.expected.wgsl
similarity index 100%
rename from test/access/var/vector.spvasm.expected.wgsl
rename to test/tint/access/var/vector.spvasm.expected.wgsl
diff --git a/test/access/var/vector.wgsl b/test/tint/access/var/vector.wgsl
similarity index 100%
rename from test/access/var/vector.wgsl
rename to test/tint/access/var/vector.wgsl
diff --git a/test/access/var/vector.wgsl.expected.glsl b/test/tint/access/var/vector.wgsl.expected.glsl
similarity index 100%
rename from test/access/var/vector.wgsl.expected.glsl
rename to test/tint/access/var/vector.wgsl.expected.glsl
diff --git a/test/access/var/vector.wgsl.expected.hlsl b/test/tint/access/var/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/access/var/vector.wgsl.expected.hlsl
rename to test/tint/access/var/vector.wgsl.expected.hlsl
diff --git a/test/access/var/vector.wgsl.expected.msl b/test/tint/access/var/vector.wgsl.expected.msl
similarity index 100%
rename from test/access/var/vector.wgsl.expected.msl
rename to test/tint/access/var/vector.wgsl.expected.msl
diff --git a/test/access/var/vector.wgsl.expected.spvasm b/test/tint/access/var/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/access/var/vector.wgsl.expected.spvasm
rename to test/tint/access/var/vector.wgsl.expected.spvasm
diff --git a/test/access/var/vector.wgsl.expected.wgsl b/test/tint/access/var/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/access/var/vector.wgsl.expected.wgsl
rename to test/tint/access/var/vector.wgsl.expected.wgsl
diff --git a/test/array/assign_to_function_var.wgsl b/test/tint/array/assign_to_function_var.wgsl
similarity index 100%
rename from test/array/assign_to_function_var.wgsl
rename to test/tint/array/assign_to_function_var.wgsl
diff --git a/test/array/assign_to_function_var.wgsl.expected.glsl b/test/tint/array/assign_to_function_var.wgsl.expected.glsl
similarity index 100%
rename from test/array/assign_to_function_var.wgsl.expected.glsl
rename to test/tint/array/assign_to_function_var.wgsl.expected.glsl
diff --git a/test/array/assign_to_function_var.wgsl.expected.hlsl b/test/tint/array/assign_to_function_var.wgsl.expected.hlsl
similarity index 100%
rename from test/array/assign_to_function_var.wgsl.expected.hlsl
rename to test/tint/array/assign_to_function_var.wgsl.expected.hlsl
diff --git a/test/array/assign_to_function_var.wgsl.expected.msl b/test/tint/array/assign_to_function_var.wgsl.expected.msl
similarity index 100%
rename from test/array/assign_to_function_var.wgsl.expected.msl
rename to test/tint/array/assign_to_function_var.wgsl.expected.msl
diff --git a/test/array/assign_to_function_var.wgsl.expected.spvasm b/test/tint/array/assign_to_function_var.wgsl.expected.spvasm
similarity index 100%
rename from test/array/assign_to_function_var.wgsl.expected.spvasm
rename to test/tint/array/assign_to_function_var.wgsl.expected.spvasm
diff --git a/test/array/assign_to_function_var.wgsl.expected.wgsl b/test/tint/array/assign_to_function_var.wgsl.expected.wgsl
similarity index 100%
rename from test/array/assign_to_function_var.wgsl.expected.wgsl
rename to test/tint/array/assign_to_function_var.wgsl.expected.wgsl
diff --git a/test/array/assign_to_private_var.wgsl b/test/tint/array/assign_to_private_var.wgsl
similarity index 100%
rename from test/array/assign_to_private_var.wgsl
rename to test/tint/array/assign_to_private_var.wgsl
diff --git a/test/array/assign_to_private_var.wgsl.expected.glsl b/test/tint/array/assign_to_private_var.wgsl.expected.glsl
similarity index 100%
rename from test/array/assign_to_private_var.wgsl.expected.glsl
rename to test/tint/array/assign_to_private_var.wgsl.expected.glsl
diff --git a/test/array/assign_to_private_var.wgsl.expected.hlsl b/test/tint/array/assign_to_private_var.wgsl.expected.hlsl
similarity index 100%
rename from test/array/assign_to_private_var.wgsl.expected.hlsl
rename to test/tint/array/assign_to_private_var.wgsl.expected.hlsl
diff --git a/test/array/assign_to_private_var.wgsl.expected.msl b/test/tint/array/assign_to_private_var.wgsl.expected.msl
similarity index 100%
rename from test/array/assign_to_private_var.wgsl.expected.msl
rename to test/tint/array/assign_to_private_var.wgsl.expected.msl
diff --git a/test/array/assign_to_private_var.wgsl.expected.spvasm b/test/tint/array/assign_to_private_var.wgsl.expected.spvasm
similarity index 100%
rename from test/array/assign_to_private_var.wgsl.expected.spvasm
rename to test/tint/array/assign_to_private_var.wgsl.expected.spvasm
diff --git a/test/array/assign_to_private_var.wgsl.expected.wgsl b/test/tint/array/assign_to_private_var.wgsl.expected.wgsl
similarity index 100%
rename from test/array/assign_to_private_var.wgsl.expected.wgsl
rename to test/tint/array/assign_to_private_var.wgsl.expected.wgsl
diff --git a/test/array/assign_to_storage_var.wgsl b/test/tint/array/assign_to_storage_var.wgsl
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl
rename to test/tint/array/assign_to_storage_var.wgsl
diff --git a/test/array/assign_to_storage_var.wgsl.expected.glsl b/test/tint/array/assign_to_storage_var.wgsl.expected.glsl
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl.expected.glsl
rename to test/tint/array/assign_to_storage_var.wgsl.expected.glsl
diff --git a/test/array/assign_to_storage_var.wgsl.expected.hlsl b/test/tint/array/assign_to_storage_var.wgsl.expected.hlsl
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl.expected.hlsl
rename to test/tint/array/assign_to_storage_var.wgsl.expected.hlsl
diff --git a/test/array/assign_to_storage_var.wgsl.expected.msl b/test/tint/array/assign_to_storage_var.wgsl.expected.msl
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl.expected.msl
rename to test/tint/array/assign_to_storage_var.wgsl.expected.msl
diff --git a/test/array/assign_to_storage_var.wgsl.expected.spvasm b/test/tint/array/assign_to_storage_var.wgsl.expected.spvasm
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl.expected.spvasm
rename to test/tint/array/assign_to_storage_var.wgsl.expected.spvasm
diff --git a/test/array/assign_to_storage_var.wgsl.expected.wgsl b/test/tint/array/assign_to_storage_var.wgsl.expected.wgsl
similarity index 100%
rename from test/array/assign_to_storage_var.wgsl.expected.wgsl
rename to test/tint/array/assign_to_storage_var.wgsl.expected.wgsl
diff --git a/test/array/assign_to_subexpr.wgsl b/test/tint/array/assign_to_subexpr.wgsl
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl
rename to test/tint/array/assign_to_subexpr.wgsl
diff --git a/test/array/assign_to_subexpr.wgsl.expected.glsl b/test/tint/array/assign_to_subexpr.wgsl.expected.glsl
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl.expected.glsl
rename to test/tint/array/assign_to_subexpr.wgsl.expected.glsl
diff --git a/test/array/assign_to_subexpr.wgsl.expected.hlsl b/test/tint/array/assign_to_subexpr.wgsl.expected.hlsl
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl.expected.hlsl
rename to test/tint/array/assign_to_subexpr.wgsl.expected.hlsl
diff --git a/test/array/assign_to_subexpr.wgsl.expected.msl b/test/tint/array/assign_to_subexpr.wgsl.expected.msl
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl.expected.msl
rename to test/tint/array/assign_to_subexpr.wgsl.expected.msl
diff --git a/test/array/assign_to_subexpr.wgsl.expected.spvasm b/test/tint/array/assign_to_subexpr.wgsl.expected.spvasm
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl.expected.spvasm
rename to test/tint/array/assign_to_subexpr.wgsl.expected.spvasm
diff --git a/test/array/assign_to_subexpr.wgsl.expected.wgsl b/test/tint/array/assign_to_subexpr.wgsl.expected.wgsl
similarity index 100%
rename from test/array/assign_to_subexpr.wgsl.expected.wgsl
rename to test/tint/array/assign_to_subexpr.wgsl.expected.wgsl
diff --git a/test/array/assign_to_workgroup_var.wgsl b/test/tint/array/assign_to_workgroup_var.wgsl
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl
rename to test/tint/array/assign_to_workgroup_var.wgsl
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.glsl b/test/tint/array/assign_to_workgroup_var.wgsl.expected.glsl
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl.expected.glsl
rename to test/tint/array/assign_to_workgroup_var.wgsl.expected.glsl
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.hlsl b/test/tint/array/assign_to_workgroup_var.wgsl.expected.hlsl
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl.expected.hlsl
rename to test/tint/array/assign_to_workgroup_var.wgsl.expected.hlsl
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.msl b/test/tint/array/assign_to_workgroup_var.wgsl.expected.msl
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl.expected.msl
rename to test/tint/array/assign_to_workgroup_var.wgsl.expected.msl
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.spvasm b/test/tint/array/assign_to_workgroup_var.wgsl.expected.spvasm
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl.expected.spvasm
rename to test/tint/array/assign_to_workgroup_var.wgsl.expected.spvasm
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.wgsl b/test/tint/array/assign_to_workgroup_var.wgsl.expected.wgsl
similarity index 100%
rename from test/array/assign_to_workgroup_var.wgsl.expected.wgsl
rename to test/tint/array/assign_to_workgroup_var.wgsl.expected.wgsl
diff --git a/test/array/function_parameter.wgsl b/test/tint/array/function_parameter.wgsl
similarity index 100%
rename from test/array/function_parameter.wgsl
rename to test/tint/array/function_parameter.wgsl
diff --git a/test/array/function_parameter.wgsl.expected.glsl b/test/tint/array/function_parameter.wgsl.expected.glsl
similarity index 100%
rename from test/array/function_parameter.wgsl.expected.glsl
rename to test/tint/array/function_parameter.wgsl.expected.glsl
diff --git a/test/array/function_parameter.wgsl.expected.hlsl b/test/tint/array/function_parameter.wgsl.expected.hlsl
similarity index 100%
rename from test/array/function_parameter.wgsl.expected.hlsl
rename to test/tint/array/function_parameter.wgsl.expected.hlsl
diff --git a/test/array/function_parameter.wgsl.expected.msl b/test/tint/array/function_parameter.wgsl.expected.msl
similarity index 100%
rename from test/array/function_parameter.wgsl.expected.msl
rename to test/tint/array/function_parameter.wgsl.expected.msl
diff --git a/test/array/function_parameter.wgsl.expected.spvasm b/test/tint/array/function_parameter.wgsl.expected.spvasm
similarity index 100%
rename from test/array/function_parameter.wgsl.expected.spvasm
rename to test/tint/array/function_parameter.wgsl.expected.spvasm
diff --git a/test/array/function_parameter.wgsl.expected.wgsl b/test/tint/array/function_parameter.wgsl.expected.wgsl
similarity index 100%
rename from test/array/function_parameter.wgsl.expected.wgsl
rename to test/tint/array/function_parameter.wgsl.expected.wgsl
diff --git a/test/array/function_return_type.wgsl b/test/tint/array/function_return_type.wgsl
similarity index 100%
rename from test/array/function_return_type.wgsl
rename to test/tint/array/function_return_type.wgsl
diff --git a/test/array/function_return_type.wgsl.expected.glsl b/test/tint/array/function_return_type.wgsl.expected.glsl
similarity index 100%
rename from test/array/function_return_type.wgsl.expected.glsl
rename to test/tint/array/function_return_type.wgsl.expected.glsl
diff --git a/test/array/function_return_type.wgsl.expected.hlsl b/test/tint/array/function_return_type.wgsl.expected.hlsl
similarity index 100%
rename from test/array/function_return_type.wgsl.expected.hlsl
rename to test/tint/array/function_return_type.wgsl.expected.hlsl
diff --git a/test/array/function_return_type.wgsl.expected.msl b/test/tint/array/function_return_type.wgsl.expected.msl
similarity index 100%
rename from test/array/function_return_type.wgsl.expected.msl
rename to test/tint/array/function_return_type.wgsl.expected.msl
diff --git a/test/array/function_return_type.wgsl.expected.spvasm b/test/tint/array/function_return_type.wgsl.expected.spvasm
similarity index 100%
rename from test/array/function_return_type.wgsl.expected.spvasm
rename to test/tint/array/function_return_type.wgsl.expected.spvasm
diff --git a/test/array/function_return_type.wgsl.expected.wgsl b/test/tint/array/function_return_type.wgsl.expected.wgsl
similarity index 100%
rename from test/array/function_return_type.wgsl.expected.wgsl
rename to test/tint/array/function_return_type.wgsl.expected.wgsl
diff --git a/test/array/size.wgsl b/test/tint/array/size.wgsl
similarity index 100%
rename from test/array/size.wgsl
rename to test/tint/array/size.wgsl
diff --git a/test/array/size.wgsl.expected.glsl b/test/tint/array/size.wgsl.expected.glsl
similarity index 100%
rename from test/array/size.wgsl.expected.glsl
rename to test/tint/array/size.wgsl.expected.glsl
diff --git a/test/array/size.wgsl.expected.hlsl b/test/tint/array/size.wgsl.expected.hlsl
similarity index 100%
rename from test/array/size.wgsl.expected.hlsl
rename to test/tint/array/size.wgsl.expected.hlsl
diff --git a/test/array/size.wgsl.expected.msl b/test/tint/array/size.wgsl.expected.msl
similarity index 100%
rename from test/array/size.wgsl.expected.msl
rename to test/tint/array/size.wgsl.expected.msl
diff --git a/test/array/size.wgsl.expected.spvasm b/test/tint/array/size.wgsl.expected.spvasm
similarity index 100%
rename from test/array/size.wgsl.expected.spvasm
rename to test/tint/array/size.wgsl.expected.spvasm
diff --git a/test/array/size.wgsl.expected.wgsl b/test/tint/array/size.wgsl.expected.wgsl
similarity index 100%
rename from test/array/size.wgsl.expected.wgsl
rename to test/tint/array/size.wgsl.expected.wgsl
diff --git a/test/array/strides.spvasm b/test/tint/array/strides.spvasm
similarity index 100%
rename from test/array/strides.spvasm
rename to test/tint/array/strides.spvasm
diff --git a/test/array/strides.spvasm.expected.glsl b/test/tint/array/strides.spvasm.expected.glsl
similarity index 100%
rename from test/array/strides.spvasm.expected.glsl
rename to test/tint/array/strides.spvasm.expected.glsl
diff --git a/test/array/strides.spvasm.expected.hlsl b/test/tint/array/strides.spvasm.expected.hlsl
similarity index 100%
rename from test/array/strides.spvasm.expected.hlsl
rename to test/tint/array/strides.spvasm.expected.hlsl
diff --git a/test/array/strides.spvasm.expected.msl b/test/tint/array/strides.spvasm.expected.msl
similarity index 100%
rename from test/array/strides.spvasm.expected.msl
rename to test/tint/array/strides.spvasm.expected.msl
diff --git a/test/array/strides.spvasm.expected.spvasm b/test/tint/array/strides.spvasm.expected.spvasm
similarity index 100%
rename from test/array/strides.spvasm.expected.spvasm
rename to test/tint/array/strides.spvasm.expected.spvasm
diff --git a/test/array/strides.spvasm.expected.wgsl b/test/tint/array/strides.spvasm.expected.wgsl
similarity index 100%
rename from test/array/strides.spvasm.expected.wgsl
rename to test/tint/array/strides.spvasm.expected.wgsl
diff --git a/test/array/type_constructor.wgsl b/test/tint/array/type_constructor.wgsl
similarity index 100%
rename from test/array/type_constructor.wgsl
rename to test/tint/array/type_constructor.wgsl
diff --git a/test/array/type_constructor.wgsl.expected.glsl b/test/tint/array/type_constructor.wgsl.expected.glsl
similarity index 100%
rename from test/array/type_constructor.wgsl.expected.glsl
rename to test/tint/array/type_constructor.wgsl.expected.glsl
diff --git a/test/array/type_constructor.wgsl.expected.hlsl b/test/tint/array/type_constructor.wgsl.expected.hlsl
similarity index 100%
rename from test/array/type_constructor.wgsl.expected.hlsl
rename to test/tint/array/type_constructor.wgsl.expected.hlsl
diff --git a/test/array/type_constructor.wgsl.expected.msl b/test/tint/array/type_constructor.wgsl.expected.msl
similarity index 100%
rename from test/array/type_constructor.wgsl.expected.msl
rename to test/tint/array/type_constructor.wgsl.expected.msl
diff --git a/test/array/type_constructor.wgsl.expected.spvasm b/test/tint/array/type_constructor.wgsl.expected.spvasm
similarity index 100%
rename from test/array/type_constructor.wgsl.expected.spvasm
rename to test/tint/array/type_constructor.wgsl.expected.spvasm
diff --git a/test/array/type_constructor.wgsl.expected.wgsl b/test/tint/array/type_constructor.wgsl.expected.wgsl
similarity index 100%
rename from test/array/type_constructor.wgsl.expected.wgsl
rename to test/tint/array/type_constructor.wgsl.expected.wgsl
diff --git a/test/benchmark/animometer.wgsl b/test/tint/benchmark/animometer.wgsl
similarity index 100%
rename from test/benchmark/animometer.wgsl
rename to test/tint/benchmark/animometer.wgsl
diff --git a/test/benchmark/animometer.wgsl.expected.glsl b/test/tint/benchmark/animometer.wgsl.expected.glsl
similarity index 100%
rename from test/benchmark/animometer.wgsl.expected.glsl
rename to test/tint/benchmark/animometer.wgsl.expected.glsl
diff --git a/test/benchmark/bloom-vertical-blur.wgsl b/test/tint/benchmark/bloom-vertical-blur.wgsl
similarity index 100%
rename from test/benchmark/bloom-vertical-blur.wgsl
rename to test/tint/benchmark/bloom-vertical-blur.wgsl
diff --git a/test/benchmark/cluster-lights.wgsl b/test/tint/benchmark/cluster-lights.wgsl
similarity index 100%
rename from test/benchmark/cluster-lights.wgsl
rename to test/tint/benchmark/cluster-lights.wgsl
diff --git a/test/benchmark/empty.wgsl b/test/tint/benchmark/empty.wgsl
similarity index 100%
rename from test/benchmark/empty.wgsl
rename to test/tint/benchmark/empty.wgsl
diff --git a/test/benchmark/metaball-isosurface.wgsl b/test/tint/benchmark/metaball-isosurface.wgsl
similarity index 100%
rename from test/benchmark/metaball-isosurface.wgsl
rename to test/tint/benchmark/metaball-isosurface.wgsl
diff --git a/test/benchmark/particles.wgsl b/test/tint/benchmark/particles.wgsl
similarity index 100%
rename from test/benchmark/particles.wgsl
rename to test/tint/benchmark/particles.wgsl
diff --git a/test/benchmark/particles.wgsl.expected.glsl b/test/tint/benchmark/particles.wgsl.expected.glsl
similarity index 100%
rename from test/benchmark/particles.wgsl.expected.glsl
rename to test/tint/benchmark/particles.wgsl.expected.glsl
diff --git a/test/benchmark/shadow-fragment.wgsl b/test/tint/benchmark/shadow-fragment.wgsl
similarity index 100%
rename from test/benchmark/shadow-fragment.wgsl
rename to test/tint/benchmark/shadow-fragment.wgsl
diff --git a/test/benchmark/simple-compute.wgsl b/test/tint/benchmark/simple-compute.wgsl
similarity index 100%
rename from test/benchmark/simple-compute.wgsl
rename to test/tint/benchmark/simple-compute.wgsl
diff --git a/test/benchmark/simple-fragment.wgsl b/test/tint/benchmark/simple-fragment.wgsl
similarity index 100%
rename from test/benchmark/simple-fragment.wgsl
rename to test/tint/benchmark/simple-fragment.wgsl
diff --git a/test/benchmark/simple-vertex.wgsl b/test/tint/benchmark/simple-vertex.wgsl
similarity index 100%
rename from test/benchmark/simple-vertex.wgsl
rename to test/tint/benchmark/simple-vertex.wgsl
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl b/test/tint/benchmark/skinned-shadowed-pbr-fragment.wgsl
similarity index 100%
rename from test/benchmark/skinned-shadowed-pbr-fragment.wgsl
rename to test/tint/benchmark/skinned-shadowed-pbr-fragment.wgsl
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl b/test/tint/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl
similarity index 100%
rename from test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl
rename to test/tint/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl b/test/tint/benchmark/skinned-shadowed-pbr-vertex.wgsl
similarity index 100%
rename from test/benchmark/skinned-shadowed-pbr-vertex.wgsl
rename to test/tint/benchmark/skinned-shadowed-pbr-vertex.wgsl
diff --git a/test/buffer/storage/dynamic_index/read.wgsl b/test/tint/buffer/storage/dynamic_index/read.wgsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl
rename to test/tint/buffer/storage/dynamic_index/read.wgsl
diff --git a/test/buffer/storage/dynamic_index/read.wgsl.expected.glsl b/test/tint/buffer/storage/dynamic_index/read.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl.expected.glsl
rename to test/tint/buffer/storage/dynamic_index/read.wgsl.expected.glsl
diff --git a/test/buffer/storage/dynamic_index/read.wgsl.expected.hlsl b/test/tint/buffer/storage/dynamic_index/read.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl.expected.hlsl
rename to test/tint/buffer/storage/dynamic_index/read.wgsl.expected.hlsl
diff --git a/test/buffer/storage/dynamic_index/read.wgsl.expected.msl b/test/tint/buffer/storage/dynamic_index/read.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl.expected.msl
rename to test/tint/buffer/storage/dynamic_index/read.wgsl.expected.msl
diff --git a/test/buffer/storage/dynamic_index/read.wgsl.expected.spvasm b/test/tint/buffer/storage/dynamic_index/read.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl.expected.spvasm
rename to test/tint/buffer/storage/dynamic_index/read.wgsl.expected.spvasm
diff --git a/test/buffer/storage/dynamic_index/read.wgsl.expected.wgsl b/test/tint/buffer/storage/dynamic_index/read.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/read.wgsl.expected.wgsl
rename to test/tint/buffer/storage/dynamic_index/read.wgsl.expected.wgsl
diff --git a/test/buffer/storage/dynamic_index/write.wgsl b/test/tint/buffer/storage/dynamic_index/write.wgsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl
rename to test/tint/buffer/storage/dynamic_index/write.wgsl
diff --git a/test/buffer/storage/dynamic_index/write.wgsl.expected.glsl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl.expected.glsl
rename to test/tint/buffer/storage/dynamic_index/write.wgsl.expected.glsl
diff --git a/test/buffer/storage/dynamic_index/write.wgsl.expected.hlsl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl.expected.hlsl
rename to test/tint/buffer/storage/dynamic_index/write.wgsl.expected.hlsl
diff --git a/test/buffer/storage/dynamic_index/write.wgsl.expected.msl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl.expected.msl
rename to test/tint/buffer/storage/dynamic_index/write.wgsl.expected.msl
diff --git a/test/buffer/storage/dynamic_index/write.wgsl.expected.spvasm b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl.expected.spvasm
rename to test/tint/buffer/storage/dynamic_index/write.wgsl.expected.spvasm
diff --git a/test/buffer/storage/dynamic_index/write.wgsl.expected.wgsl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/dynamic_index/write.wgsl.expected.wgsl
rename to test/tint/buffer/storage/dynamic_index/write.wgsl.expected.wgsl
diff --git a/test/buffer/storage/static_index/read.wgsl b/test/tint/buffer/storage/static_index/read.wgsl
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl
rename to test/tint/buffer/storage/static_index/read.wgsl
diff --git a/test/buffer/storage/static_index/read.wgsl.expected.glsl b/test/tint/buffer/storage/static_index/read.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl.expected.glsl
rename to test/tint/buffer/storage/static_index/read.wgsl.expected.glsl
diff --git a/test/buffer/storage/static_index/read.wgsl.expected.hlsl b/test/tint/buffer/storage/static_index/read.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl.expected.hlsl
rename to test/tint/buffer/storage/static_index/read.wgsl.expected.hlsl
diff --git a/test/buffer/storage/static_index/read.wgsl.expected.msl b/test/tint/buffer/storage/static_index/read.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl.expected.msl
rename to test/tint/buffer/storage/static_index/read.wgsl.expected.msl
diff --git a/test/buffer/storage/static_index/read.wgsl.expected.spvasm b/test/tint/buffer/storage/static_index/read.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl.expected.spvasm
rename to test/tint/buffer/storage/static_index/read.wgsl.expected.spvasm
diff --git a/test/buffer/storage/static_index/read.wgsl.expected.wgsl b/test/tint/buffer/storage/static_index/read.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/static_index/read.wgsl.expected.wgsl
rename to test/tint/buffer/storage/static_index/read.wgsl.expected.wgsl
diff --git a/test/buffer/storage/static_index/write.wgsl b/test/tint/buffer/storage/static_index/write.wgsl
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl
rename to test/tint/buffer/storage/static_index/write.wgsl
diff --git a/test/buffer/storage/static_index/write.wgsl.expected.glsl b/test/tint/buffer/storage/static_index/write.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl.expected.glsl
rename to test/tint/buffer/storage/static_index/write.wgsl.expected.glsl
diff --git a/test/buffer/storage/static_index/write.wgsl.expected.hlsl b/test/tint/buffer/storage/static_index/write.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl.expected.hlsl
rename to test/tint/buffer/storage/static_index/write.wgsl.expected.hlsl
diff --git a/test/buffer/storage/static_index/write.wgsl.expected.msl b/test/tint/buffer/storage/static_index/write.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl.expected.msl
rename to test/tint/buffer/storage/static_index/write.wgsl.expected.msl
diff --git a/test/buffer/storage/static_index/write.wgsl.expected.spvasm b/test/tint/buffer/storage/static_index/write.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl.expected.spvasm
rename to test/tint/buffer/storage/static_index/write.wgsl.expected.spvasm
diff --git a/test/buffer/storage/static_index/write.wgsl.expected.wgsl b/test/tint/buffer/storage/static_index/write.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/static_index/write.wgsl.expected.wgsl
rename to test/tint/buffer/storage/static_index/write.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/array.wgsl b/test/tint/buffer/storage/types/array.wgsl
similarity index 100%
rename from test/buffer/storage/types/array.wgsl
rename to test/tint/buffer/storage/types/array.wgsl
diff --git a/test/buffer/storage/types/array.wgsl.expected.glsl b/test/tint/buffer/storage/types/array.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/array.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/array.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/array.wgsl.expected.hlsl b/test/tint/buffer/storage/types/array.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/array.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/array.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/array.wgsl.expected.msl b/test/tint/buffer/storage/types/array.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/array.wgsl.expected.msl
rename to test/tint/buffer/storage/types/array.wgsl.expected.msl
diff --git a/test/buffer/storage/types/array.wgsl.expected.spvasm b/test/tint/buffer/storage/types/array.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/array.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/array.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/array.wgsl.expected.wgsl b/test/tint/buffer/storage/types/array.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/array.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/array.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/f32.wgsl b/test/tint/buffer/storage/types/f32.wgsl
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl
rename to test/tint/buffer/storage/types/f32.wgsl
diff --git a/test/buffer/storage/types/f32.wgsl.expected.glsl b/test/tint/buffer/storage/types/f32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/f32.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/f32.wgsl.expected.hlsl b/test/tint/buffer/storage/types/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/f32.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/f32.wgsl.expected.msl b/test/tint/buffer/storage/types/f32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl.expected.msl
rename to test/tint/buffer/storage/types/f32.wgsl.expected.msl
diff --git a/test/buffer/storage/types/f32.wgsl.expected.spvasm b/test/tint/buffer/storage/types/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/f32.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/f32.wgsl.expected.wgsl b/test/tint/buffer/storage/types/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/f32.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/f32.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/i32.wgsl b/test/tint/buffer/storage/types/i32.wgsl
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl
rename to test/tint/buffer/storage/types/i32.wgsl
diff --git a/test/buffer/storage/types/i32.wgsl.expected.glsl b/test/tint/buffer/storage/types/i32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/i32.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/i32.wgsl.expected.hlsl b/test/tint/buffer/storage/types/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/i32.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/i32.wgsl.expected.msl b/test/tint/buffer/storage/types/i32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl.expected.msl
rename to test/tint/buffer/storage/types/i32.wgsl.expected.msl
diff --git a/test/buffer/storage/types/i32.wgsl.expected.spvasm b/test/tint/buffer/storage/types/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/i32.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/i32.wgsl.expected.wgsl b/test/tint/buffer/storage/types/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/i32.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/i32.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/mat2x2.wgsl b/test/tint/buffer/storage/types/mat2x2.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl
rename to test/tint/buffer/storage/types/mat2x2.wgsl
diff --git a/test/buffer/storage/types/mat2x2.wgsl.expected.glsl b/test/tint/buffer/storage/types/mat2x2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/mat2x2.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/mat2x2.wgsl.expected.hlsl b/test/tint/buffer/storage/types/mat2x2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/mat2x2.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/mat2x2.wgsl.expected.msl b/test/tint/buffer/storage/types/mat2x2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl.expected.msl
rename to test/tint/buffer/storage/types/mat2x2.wgsl.expected.msl
diff --git a/test/buffer/storage/types/mat2x2.wgsl.expected.spvasm b/test/tint/buffer/storage/types/mat2x2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/mat2x2.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/mat2x2.wgsl.expected.wgsl b/test/tint/buffer/storage/types/mat2x2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat2x2.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/mat2x2.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/mat2x3.wgsl b/test/tint/buffer/storage/types/mat2x3.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl
rename to test/tint/buffer/storage/types/mat2x3.wgsl
diff --git a/test/buffer/storage/types/mat2x3.wgsl.expected.glsl b/test/tint/buffer/storage/types/mat2x3.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/mat2x3.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/mat2x3.wgsl.expected.hlsl b/test/tint/buffer/storage/types/mat2x3.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/mat2x3.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/mat2x3.wgsl.expected.msl b/test/tint/buffer/storage/types/mat2x3.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl.expected.msl
rename to test/tint/buffer/storage/types/mat2x3.wgsl.expected.msl
diff --git a/test/buffer/storage/types/mat2x3.wgsl.expected.spvasm b/test/tint/buffer/storage/types/mat2x3.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/mat2x3.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/mat2x3.wgsl.expected.wgsl b/test/tint/buffer/storage/types/mat2x3.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat2x3.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/mat2x3.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/mat3x2.wgsl b/test/tint/buffer/storage/types/mat3x2.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl
rename to test/tint/buffer/storage/types/mat3x2.wgsl
diff --git a/test/buffer/storage/types/mat3x2.wgsl.expected.glsl b/test/tint/buffer/storage/types/mat3x2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/mat3x2.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/mat3x2.wgsl.expected.hlsl b/test/tint/buffer/storage/types/mat3x2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/mat3x2.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/mat3x2.wgsl.expected.msl b/test/tint/buffer/storage/types/mat3x2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl.expected.msl
rename to test/tint/buffer/storage/types/mat3x2.wgsl.expected.msl
diff --git a/test/buffer/storage/types/mat3x2.wgsl.expected.spvasm b/test/tint/buffer/storage/types/mat3x2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/mat3x2.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/mat3x2.wgsl.expected.wgsl b/test/tint/buffer/storage/types/mat3x2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat3x2.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/mat3x2.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/mat4x4.wgsl b/test/tint/buffer/storage/types/mat4x4.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl
rename to test/tint/buffer/storage/types/mat4x4.wgsl
diff --git a/test/buffer/storage/types/mat4x4.wgsl.expected.glsl b/test/tint/buffer/storage/types/mat4x4.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/mat4x4.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/mat4x4.wgsl.expected.hlsl b/test/tint/buffer/storage/types/mat4x4.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/mat4x4.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/mat4x4.wgsl.expected.msl b/test/tint/buffer/storage/types/mat4x4.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl.expected.msl
rename to test/tint/buffer/storage/types/mat4x4.wgsl.expected.msl
diff --git a/test/buffer/storage/types/mat4x4.wgsl.expected.spvasm b/test/tint/buffer/storage/types/mat4x4.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/mat4x4.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/mat4x4.wgsl.expected.wgsl b/test/tint/buffer/storage/types/mat4x4.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/mat4x4.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/mat4x4.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/runtime_array.wgsl b/test/tint/buffer/storage/types/runtime_array.wgsl
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl
rename to test/tint/buffer/storage/types/runtime_array.wgsl
diff --git a/test/buffer/storage/types/runtime_array.wgsl.expected.glsl b/test/tint/buffer/storage/types/runtime_array.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/runtime_array.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/runtime_array.wgsl.expected.hlsl b/test/tint/buffer/storage/types/runtime_array.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/runtime_array.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/runtime_array.wgsl.expected.msl b/test/tint/buffer/storage/types/runtime_array.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl.expected.msl
rename to test/tint/buffer/storage/types/runtime_array.wgsl.expected.msl
diff --git a/test/buffer/storage/types/runtime_array.wgsl.expected.spvasm b/test/tint/buffer/storage/types/runtime_array.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/runtime_array.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/runtime_array.wgsl.expected.wgsl b/test/tint/buffer/storage/types/runtime_array.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/runtime_array.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/runtime_array.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/struct.wgsl b/test/tint/buffer/storage/types/struct.wgsl
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl
rename to test/tint/buffer/storage/types/struct.wgsl
diff --git a/test/buffer/storage/types/struct.wgsl.expected.glsl b/test/tint/buffer/storage/types/struct.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/struct.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/struct.wgsl.expected.hlsl b/test/tint/buffer/storage/types/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/struct.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/struct.wgsl.expected.msl b/test/tint/buffer/storage/types/struct.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl.expected.msl
rename to test/tint/buffer/storage/types/struct.wgsl.expected.msl
diff --git a/test/buffer/storage/types/struct.wgsl.expected.spvasm b/test/tint/buffer/storage/types/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/struct.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/struct.wgsl.expected.wgsl b/test/tint/buffer/storage/types/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/struct.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/struct.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/u32.wgsl b/test/tint/buffer/storage/types/u32.wgsl
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl
rename to test/tint/buffer/storage/types/u32.wgsl
diff --git a/test/buffer/storage/types/u32.wgsl.expected.glsl b/test/tint/buffer/storage/types/u32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/u32.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/u32.wgsl.expected.hlsl b/test/tint/buffer/storage/types/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/u32.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/u32.wgsl.expected.msl b/test/tint/buffer/storage/types/u32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl.expected.msl
rename to test/tint/buffer/storage/types/u32.wgsl.expected.msl
diff --git a/test/buffer/storage/types/u32.wgsl.expected.spvasm b/test/tint/buffer/storage/types/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/u32.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/u32.wgsl.expected.wgsl b/test/tint/buffer/storage/types/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/u32.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/u32.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/vec2.wgsl b/test/tint/buffer/storage/types/vec2.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl
rename to test/tint/buffer/storage/types/vec2.wgsl
diff --git a/test/buffer/storage/types/vec2.wgsl.expected.glsl b/test/tint/buffer/storage/types/vec2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/vec2.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/vec2.wgsl.expected.hlsl b/test/tint/buffer/storage/types/vec2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/vec2.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/vec2.wgsl.expected.msl b/test/tint/buffer/storage/types/vec2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl.expected.msl
rename to test/tint/buffer/storage/types/vec2.wgsl.expected.msl
diff --git a/test/buffer/storage/types/vec2.wgsl.expected.spvasm b/test/tint/buffer/storage/types/vec2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/vec2.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/vec2.wgsl.expected.wgsl b/test/tint/buffer/storage/types/vec2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec2.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/vec2.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/vec3.wgsl b/test/tint/buffer/storage/types/vec3.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl
rename to test/tint/buffer/storage/types/vec3.wgsl
diff --git a/test/buffer/storage/types/vec3.wgsl.expected.glsl b/test/tint/buffer/storage/types/vec3.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/vec3.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/vec3.wgsl.expected.hlsl b/test/tint/buffer/storage/types/vec3.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/vec3.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/vec3.wgsl.expected.msl b/test/tint/buffer/storage/types/vec3.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl.expected.msl
rename to test/tint/buffer/storage/types/vec3.wgsl.expected.msl
diff --git a/test/buffer/storage/types/vec3.wgsl.expected.spvasm b/test/tint/buffer/storage/types/vec3.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/vec3.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/vec3.wgsl.expected.wgsl b/test/tint/buffer/storage/types/vec3.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec3.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/vec3.wgsl.expected.wgsl
diff --git a/test/buffer/storage/types/vec4.wgsl b/test/tint/buffer/storage/types/vec4.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl
rename to test/tint/buffer/storage/types/vec4.wgsl
diff --git a/test/buffer/storage/types/vec4.wgsl.expected.glsl b/test/tint/buffer/storage/types/vec4.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl.expected.glsl
rename to test/tint/buffer/storage/types/vec4.wgsl.expected.glsl
diff --git a/test/buffer/storage/types/vec4.wgsl.expected.hlsl b/test/tint/buffer/storage/types/vec4.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl.expected.hlsl
rename to test/tint/buffer/storage/types/vec4.wgsl.expected.hlsl
diff --git a/test/buffer/storage/types/vec4.wgsl.expected.msl b/test/tint/buffer/storage/types/vec4.wgsl.expected.msl
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl.expected.msl
rename to test/tint/buffer/storage/types/vec4.wgsl.expected.msl
diff --git a/test/buffer/storage/types/vec4.wgsl.expected.spvasm b/test/tint/buffer/storage/types/vec4.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl.expected.spvasm
rename to test/tint/buffer/storage/types/vec4.wgsl.expected.spvasm
diff --git a/test/buffer/storage/types/vec4.wgsl.expected.wgsl b/test/tint/buffer/storage/types/vec4.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/storage/types/vec4.wgsl.expected.wgsl
rename to test/tint/buffer/storage/types/vec4.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl b/test/tint/buffer/uniform/dynamic_index/read.wgsl
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl.expected.glsl b/test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl.expected.glsl
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.glsl
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl.expected.hlsl b/test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl.expected.msl b/test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl.expected.msl
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.msl
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl.expected.spvasm b/test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/dynamic_index/read.wgsl.expected.wgsl b/test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/dynamic_index/read.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/dynamic_index/read.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/static_index/read.wgsl b/test/tint/buffer/uniform/static_index/read.wgsl
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl
rename to test/tint/buffer/uniform/static_index/read.wgsl
diff --git a/test/buffer/uniform/static_index/read.wgsl.expected.glsl b/test/tint/buffer/uniform/static_index/read.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl.expected.glsl
rename to test/tint/buffer/uniform/static_index/read.wgsl.expected.glsl
diff --git a/test/buffer/uniform/static_index/read.wgsl.expected.hlsl b/test/tint/buffer/uniform/static_index/read.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/static_index/read.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/static_index/read.wgsl.expected.msl b/test/tint/buffer/uniform/static_index/read.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl.expected.msl
rename to test/tint/buffer/uniform/static_index/read.wgsl.expected.msl
diff --git a/test/buffer/uniform/static_index/read.wgsl.expected.spvasm b/test/tint/buffer/uniform/static_index/read.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/static_index/read.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/static_index/read.wgsl.expected.wgsl b/test/tint/buffer/uniform/static_index/read.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/static_index/read.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/static_index/read.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/array.wgsl b/test/tint/buffer/uniform/types/array.wgsl
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl
rename to test/tint/buffer/uniform/types/array.wgsl
diff --git a/test/buffer/uniform/types/array.wgsl.expected.glsl b/test/tint/buffer/uniform/types/array.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/array.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/array.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/array.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/array.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/array.wgsl.expected.msl b/test/tint/buffer/uniform/types/array.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/array.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/array.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/array.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/array.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/array.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/array.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/array.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/array.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/f32.wgsl b/test/tint/buffer/uniform/types/f32.wgsl
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl
rename to test/tint/buffer/uniform/types/f32.wgsl
diff --git a/test/buffer/uniform/types/f32.wgsl.expected.glsl b/test/tint/buffer/uniform/types/f32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/f32.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/f32.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/f32.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/f32.wgsl.expected.msl b/test/tint/buffer/uniform/types/f32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/f32.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/f32.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/f32.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/f32.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/f32.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/f32.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/i32.wgsl b/test/tint/buffer/uniform/types/i32.wgsl
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl
rename to test/tint/buffer/uniform/types/i32.wgsl
diff --git a/test/buffer/uniform/types/i32.wgsl.expected.glsl b/test/tint/buffer/uniform/types/i32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/i32.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/i32.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/i32.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/i32.wgsl.expected.msl b/test/tint/buffer/uniform/types/i32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/i32.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/i32.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/i32.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/i32.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/i32.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/i32.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/mat2x2.wgsl b/test/tint/buffer/uniform/types/mat2x2.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl
rename to test/tint/buffer/uniform/types/mat2x2.wgsl
diff --git a/test/buffer/uniform/types/mat2x2.wgsl.expected.glsl b/test/tint/buffer/uniform/types/mat2x2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/mat2x2.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/mat2x2.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/mat2x2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/mat2x2.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/mat2x2.wgsl.expected.msl b/test/tint/buffer/uniform/types/mat2x2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/mat2x2.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/mat2x2.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/mat2x2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/mat2x2.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/mat2x2.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/mat2x2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x2.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/mat2x2.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/mat2x3.wgsl b/test/tint/buffer/uniform/types/mat2x3.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl
rename to test/tint/buffer/uniform/types/mat2x3.wgsl
diff --git a/test/buffer/uniform/types/mat2x3.wgsl.expected.glsl b/test/tint/buffer/uniform/types/mat2x3.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/mat2x3.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/mat2x3.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/mat2x3.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/mat2x3.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/mat2x3.wgsl.expected.msl b/test/tint/buffer/uniform/types/mat2x3.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/mat2x3.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/mat2x3.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/mat2x3.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/mat2x3.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/mat2x3.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/mat2x3.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat2x3.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/mat2x3.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/mat3x2.wgsl b/test/tint/buffer/uniform/types/mat3x2.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl
rename to test/tint/buffer/uniform/types/mat3x2.wgsl
diff --git a/test/buffer/uniform/types/mat3x2.wgsl.expected.glsl b/test/tint/buffer/uniform/types/mat3x2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/mat3x2.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/mat3x2.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/mat3x2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/mat3x2.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/mat3x2.wgsl.expected.msl b/test/tint/buffer/uniform/types/mat3x2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/mat3x2.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/mat3x2.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/mat3x2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/mat3x2.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/mat3x2.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/mat3x2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat3x2.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/mat3x2.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/mat4x4.wgsl b/test/tint/buffer/uniform/types/mat4x4.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl
rename to test/tint/buffer/uniform/types/mat4x4.wgsl
diff --git a/test/buffer/uniform/types/mat4x4.wgsl.expected.glsl b/test/tint/buffer/uniform/types/mat4x4.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/mat4x4.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/mat4x4.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/mat4x4.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/mat4x4.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/mat4x4.wgsl.expected.msl b/test/tint/buffer/uniform/types/mat4x4.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/mat4x4.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/mat4x4.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/mat4x4.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/mat4x4.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/mat4x4.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/mat4x4.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/mat4x4.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/mat4x4.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/struct.wgsl b/test/tint/buffer/uniform/types/struct.wgsl
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl
rename to test/tint/buffer/uniform/types/struct.wgsl
diff --git a/test/buffer/uniform/types/struct.wgsl.expected.glsl b/test/tint/buffer/uniform/types/struct.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/struct.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/struct.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/struct.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/struct.wgsl.expected.msl b/test/tint/buffer/uniform/types/struct.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/struct.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/struct.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/struct.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/struct.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/struct.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/struct.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/u32.wgsl b/test/tint/buffer/uniform/types/u32.wgsl
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl
rename to test/tint/buffer/uniform/types/u32.wgsl
diff --git a/test/buffer/uniform/types/u32.wgsl.expected.glsl b/test/tint/buffer/uniform/types/u32.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/u32.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/u32.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/u32.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/u32.wgsl.expected.msl b/test/tint/buffer/uniform/types/u32.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/u32.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/u32.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/u32.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/u32.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/u32.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/u32.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/vec2.wgsl b/test/tint/buffer/uniform/types/vec2.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl
rename to test/tint/buffer/uniform/types/vec2.wgsl
diff --git a/test/buffer/uniform/types/vec2.wgsl.expected.glsl b/test/tint/buffer/uniform/types/vec2.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/vec2.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/vec2.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/vec2.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/vec2.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/vec2.wgsl.expected.msl b/test/tint/buffer/uniform/types/vec2.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/vec2.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/vec2.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/vec2.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/vec2.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/vec2.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/vec2.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec2.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/vec2.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/vec3.wgsl b/test/tint/buffer/uniform/types/vec3.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl
rename to test/tint/buffer/uniform/types/vec3.wgsl
diff --git a/test/buffer/uniform/types/vec3.wgsl.expected.glsl b/test/tint/buffer/uniform/types/vec3.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/vec3.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/vec3.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/vec3.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/vec3.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/vec3.wgsl.expected.msl b/test/tint/buffer/uniform/types/vec3.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/vec3.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/vec3.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/vec3.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/vec3.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/vec3.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/vec3.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec3.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/vec3.wgsl.expected.wgsl
diff --git a/test/buffer/uniform/types/vec4.wgsl b/test/tint/buffer/uniform/types/vec4.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl
rename to test/tint/buffer/uniform/types/vec4.wgsl
diff --git a/test/buffer/uniform/types/vec4.wgsl.expected.glsl b/test/tint/buffer/uniform/types/vec4.wgsl.expected.glsl
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl.expected.glsl
rename to test/tint/buffer/uniform/types/vec4.wgsl.expected.glsl
diff --git a/test/buffer/uniform/types/vec4.wgsl.expected.hlsl b/test/tint/buffer/uniform/types/vec4.wgsl.expected.hlsl
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl.expected.hlsl
rename to test/tint/buffer/uniform/types/vec4.wgsl.expected.hlsl
diff --git a/test/buffer/uniform/types/vec4.wgsl.expected.msl b/test/tint/buffer/uniform/types/vec4.wgsl.expected.msl
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl.expected.msl
rename to test/tint/buffer/uniform/types/vec4.wgsl.expected.msl
diff --git a/test/buffer/uniform/types/vec4.wgsl.expected.spvasm b/test/tint/buffer/uniform/types/vec4.wgsl.expected.spvasm
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl.expected.spvasm
rename to test/tint/buffer/uniform/types/vec4.wgsl.expected.spvasm
diff --git a/test/buffer/uniform/types/vec4.wgsl.expected.wgsl b/test/tint/buffer/uniform/types/vec4.wgsl.expected.wgsl
similarity index 100%
rename from test/buffer/uniform/types/vec4.wgsl.expected.wgsl
rename to test/tint/buffer/uniform/types/vec4.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1221120.wgsl b/test/tint/bug/chromium/1221120.wgsl
similarity index 100%
rename from test/bug/chromium/1221120.wgsl
rename to test/tint/bug/chromium/1221120.wgsl
diff --git a/test/bug/chromium/1221120.wgsl.expected.glsl b/test/tint/bug/chromium/1221120.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1221120.wgsl.expected.glsl
rename to test/tint/bug/chromium/1221120.wgsl.expected.glsl
diff --git a/test/bug/chromium/1221120.wgsl.expected.hlsl b/test/tint/bug/chromium/1221120.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1221120.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1221120.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1221120.wgsl.expected.msl b/test/tint/bug/chromium/1221120.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1221120.wgsl.expected.msl
rename to test/tint/bug/chromium/1221120.wgsl.expected.msl
diff --git a/test/bug/chromium/1221120.wgsl.expected.spvasm b/test/tint/bug/chromium/1221120.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1221120.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1221120.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1221120.wgsl.expected.wgsl b/test/tint/bug/chromium/1221120.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1221120.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1221120.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1236161.wgsl b/test/tint/bug/chromium/1236161.wgsl
similarity index 100%
rename from test/bug/chromium/1236161.wgsl
rename to test/tint/bug/chromium/1236161.wgsl
diff --git a/test/bug/chromium/1236161.wgsl.expected.glsl b/test/tint/bug/chromium/1236161.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1236161.wgsl.expected.glsl
rename to test/tint/bug/chromium/1236161.wgsl.expected.glsl
diff --git a/test/bug/chromium/1236161.wgsl.expected.hlsl b/test/tint/bug/chromium/1236161.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1236161.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1236161.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1236161.wgsl.expected.msl b/test/tint/bug/chromium/1236161.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1236161.wgsl.expected.msl
rename to test/tint/bug/chromium/1236161.wgsl.expected.msl
diff --git a/test/bug/chromium/1236161.wgsl.expected.spvasm b/test/tint/bug/chromium/1236161.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1236161.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1236161.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1236161.wgsl.expected.wgsl b/test/tint/bug/chromium/1236161.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1236161.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1236161.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1251009.wgsl b/test/tint/bug/chromium/1251009.wgsl
similarity index 100%
rename from test/bug/chromium/1251009.wgsl
rename to test/tint/bug/chromium/1251009.wgsl
diff --git a/test/bug/chromium/1251009.wgsl.expected.glsl b/test/tint/bug/chromium/1251009.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1251009.wgsl.expected.glsl
rename to test/tint/bug/chromium/1251009.wgsl.expected.glsl
diff --git a/test/bug/chromium/1251009.wgsl.expected.hlsl b/test/tint/bug/chromium/1251009.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1251009.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1251009.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1251009.wgsl.expected.msl b/test/tint/bug/chromium/1251009.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1251009.wgsl.expected.msl
rename to test/tint/bug/chromium/1251009.wgsl.expected.msl
diff --git a/test/bug/chromium/1251009.wgsl.expected.spvasm b/test/tint/bug/chromium/1251009.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1251009.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1251009.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1251009.wgsl.expected.wgsl b/test/tint/bug/chromium/1251009.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1251009.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1251009.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1273230.wgsl b/test/tint/bug/chromium/1273230.wgsl
similarity index 100%
rename from test/bug/chromium/1273230.wgsl
rename to test/tint/bug/chromium/1273230.wgsl
diff --git a/test/bug/chromium/1273230.wgsl.expected.glsl b/test/tint/bug/chromium/1273230.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1273230.wgsl.expected.glsl
rename to test/tint/bug/chromium/1273230.wgsl.expected.glsl
diff --git a/test/bug/chromium/1273230.wgsl.expected.hlsl b/test/tint/bug/chromium/1273230.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1273230.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1273230.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1273230.wgsl.expected.msl b/test/tint/bug/chromium/1273230.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1273230.wgsl.expected.msl
rename to test/tint/bug/chromium/1273230.wgsl.expected.msl
diff --git a/test/bug/chromium/1273230.wgsl.expected.spvasm b/test/tint/bug/chromium/1273230.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1273230.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1273230.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1273230.wgsl.expected.wgsl b/test/tint/bug/chromium/1273230.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1273230.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1273230.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1273451.wgsl b/test/tint/bug/chromium/1273451.wgsl
similarity index 100%
rename from test/bug/chromium/1273451.wgsl
rename to test/tint/bug/chromium/1273451.wgsl
diff --git a/test/bug/chromium/1273451.wgsl.expected.glsl b/test/tint/bug/chromium/1273451.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1273451.wgsl.expected.glsl
rename to test/tint/bug/chromium/1273451.wgsl.expected.glsl
diff --git a/test/bug/chromium/1273451.wgsl.expected.hlsl b/test/tint/bug/chromium/1273451.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1273451.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1273451.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1273451.wgsl.expected.msl b/test/tint/bug/chromium/1273451.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1273451.wgsl.expected.msl
rename to test/tint/bug/chromium/1273451.wgsl.expected.msl
diff --git a/test/bug/chromium/1273451.wgsl.expected.spvasm b/test/tint/bug/chromium/1273451.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1273451.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1273451.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1273451.wgsl.expected.wgsl b/test/tint/bug/chromium/1273451.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1273451.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1273451.wgsl.expected.wgsl
diff --git a/test/bug/chromium/1290107.wgsl b/test/tint/bug/chromium/1290107.wgsl
similarity index 100%
rename from test/bug/chromium/1290107.wgsl
rename to test/tint/bug/chromium/1290107.wgsl
diff --git a/test/bug/chromium/1290107.wgsl.expected.glsl b/test/tint/bug/chromium/1290107.wgsl.expected.glsl
similarity index 100%
rename from test/bug/chromium/1290107.wgsl.expected.glsl
rename to test/tint/bug/chromium/1290107.wgsl.expected.glsl
diff --git a/test/bug/chromium/1290107.wgsl.expected.hlsl b/test/tint/bug/chromium/1290107.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/chromium/1290107.wgsl.expected.hlsl
rename to test/tint/bug/chromium/1290107.wgsl.expected.hlsl
diff --git a/test/bug/chromium/1290107.wgsl.expected.msl b/test/tint/bug/chromium/1290107.wgsl.expected.msl
similarity index 100%
rename from test/bug/chromium/1290107.wgsl.expected.msl
rename to test/tint/bug/chromium/1290107.wgsl.expected.msl
diff --git a/test/bug/chromium/1290107.wgsl.expected.spvasm b/test/tint/bug/chromium/1290107.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/chromium/1290107.wgsl.expected.spvasm
rename to test/tint/bug/chromium/1290107.wgsl.expected.spvasm
diff --git a/test/bug/chromium/1290107.wgsl.expected.wgsl b/test/tint/bug/chromium/1290107.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/chromium/1290107.wgsl.expected.wgsl
rename to test/tint/bug/chromium/1290107.wgsl.expected.wgsl
diff --git a/test/bug/dawn/947.wgsl b/test/tint/bug/dawn/947.wgsl
similarity index 100%
rename from test/bug/dawn/947.wgsl
rename to test/tint/bug/dawn/947.wgsl
diff --git a/test/bug/dawn/947.wgsl.expected.glsl b/test/tint/bug/dawn/947.wgsl.expected.glsl
similarity index 100%
rename from test/bug/dawn/947.wgsl.expected.glsl
rename to test/tint/bug/dawn/947.wgsl.expected.glsl
diff --git a/test/bug/dawn/947.wgsl.expected.hlsl b/test/tint/bug/dawn/947.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/dawn/947.wgsl.expected.hlsl
rename to test/tint/bug/dawn/947.wgsl.expected.hlsl
diff --git a/test/bug/dawn/947.wgsl.expected.msl b/test/tint/bug/dawn/947.wgsl.expected.msl
similarity index 100%
rename from test/bug/dawn/947.wgsl.expected.msl
rename to test/tint/bug/dawn/947.wgsl.expected.msl
diff --git a/test/bug/dawn/947.wgsl.expected.spvasm b/test/tint/bug/dawn/947.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/dawn/947.wgsl.expected.spvasm
rename to test/tint/bug/dawn/947.wgsl.expected.spvasm
diff --git a/test/bug/dawn/947.wgsl.expected.wgsl b/test/tint/bug/dawn/947.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/dawn/947.wgsl.expected.wgsl
rename to test/tint/bug/dawn/947.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/function.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/function.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/private.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/private.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/storage.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/uniform.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/read/workgroup.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/function_via_param.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/private_via_param.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/storage.wgsl.expected.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.glsl b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.glsl
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.glsl
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.hlsl b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.hlsl
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.hlsl
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.msl b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.msl
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.msl
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.spvasm b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.spvasm
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.spvasm
diff --git a/test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.wgsl b/test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.wgsl
rename to test/tint/bug/fxc/dyn_array_idx/write/workgroup.wgsl.expected.wgsl
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
diff --git a/test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
rename to test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.glsl b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.glsl
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.glsl
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.hlsl b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.hlsl
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.hlsl
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.msl b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.msl
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.msl
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.spvasm b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.spvasm
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.spvasm
diff --git a/test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.wgsl b/test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.wgsl
rename to test/tint/bug/fxc/indexed_assign_to_array_in_struct/1206.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_x.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_xy.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_scalar_y.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/local_assign_vector.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_x.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_xy.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_scalar_y.wgsl.expected.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.glsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.glsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.glsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.hlsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.hlsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.hlsl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.msl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.msl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.msl
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.spvasm b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.spvasm
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.spvasm
diff --git a/test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.wgsl b/test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.wgsl
rename to test/tint/bug/fxc/matrix_assignment_dynamic_index/module_assign_vector.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_loop.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_call_with_no_loop.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_all.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_repeated.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/loop_types_some.wgsl.expected.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.glsl b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.glsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.glsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.glsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.hlsl b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.hlsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.hlsl
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.msl b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.msl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.msl
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.msl
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.spvasm b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.spvasm
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.spvasm
diff --git a/test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.wgsl b/test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.wgsl
rename to test/tint/bug/fxc/vector_assignment_in_loop/no_loop.wgsl.expected.wgsl
diff --git a/test/bug/tint/1046.wgsl b/test/tint/bug/tint/1046.wgsl
similarity index 100%
rename from test/bug/tint/1046.wgsl
rename to test/tint/bug/tint/1046.wgsl
diff --git a/test/bug/tint/1046.wgsl.expected.glsl b/test/tint/bug/tint/1046.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1046.wgsl.expected.glsl
rename to test/tint/bug/tint/1046.wgsl.expected.glsl
diff --git a/test/bug/tint/1046.wgsl.expected.hlsl b/test/tint/bug/tint/1046.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1046.wgsl.expected.hlsl
rename to test/tint/bug/tint/1046.wgsl.expected.hlsl
diff --git a/test/bug/tint/1046.wgsl.expected.msl b/test/tint/bug/tint/1046.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1046.wgsl.expected.msl
rename to test/tint/bug/tint/1046.wgsl.expected.msl
diff --git a/test/bug/tint/1046.wgsl.expected.spvasm b/test/tint/bug/tint/1046.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1046.wgsl.expected.spvasm
rename to test/tint/bug/tint/1046.wgsl.expected.spvasm
diff --git a/test/bug/tint/1046.wgsl.expected.wgsl b/test/tint/bug/tint/1046.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1046.wgsl.expected.wgsl
rename to test/tint/bug/tint/1046.wgsl.expected.wgsl
diff --git a/test/bug/tint/1064.wgsl b/test/tint/bug/tint/1064.wgsl
similarity index 100%
rename from test/bug/tint/1064.wgsl
rename to test/tint/bug/tint/1064.wgsl
diff --git a/test/bug/tint/1064.wgsl.expected.glsl b/test/tint/bug/tint/1064.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1064.wgsl.expected.glsl
rename to test/tint/bug/tint/1064.wgsl.expected.glsl
diff --git a/test/bug/tint/1064.wgsl.expected.hlsl b/test/tint/bug/tint/1064.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1064.wgsl.expected.hlsl
rename to test/tint/bug/tint/1064.wgsl.expected.hlsl
diff --git a/test/bug/tint/1064.wgsl.expected.msl b/test/tint/bug/tint/1064.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1064.wgsl.expected.msl
rename to test/tint/bug/tint/1064.wgsl.expected.msl
diff --git a/test/bug/tint/1064.wgsl.expected.spvasm b/test/tint/bug/tint/1064.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1064.wgsl.expected.spvasm
rename to test/tint/bug/tint/1064.wgsl.expected.spvasm
diff --git a/test/bug/tint/1064.wgsl.expected.wgsl b/test/tint/bug/tint/1064.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1064.wgsl.expected.wgsl
rename to test/tint/bug/tint/1064.wgsl.expected.wgsl
diff --git a/test/bug/tint/1076.wgsl b/test/tint/bug/tint/1076.wgsl
similarity index 100%
rename from test/bug/tint/1076.wgsl
rename to test/tint/bug/tint/1076.wgsl
diff --git a/test/bug/tint/1076.wgsl.expected.glsl b/test/tint/bug/tint/1076.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1076.wgsl.expected.glsl
rename to test/tint/bug/tint/1076.wgsl.expected.glsl
diff --git a/test/bug/tint/1076.wgsl.expected.hlsl b/test/tint/bug/tint/1076.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1076.wgsl.expected.hlsl
rename to test/tint/bug/tint/1076.wgsl.expected.hlsl
diff --git a/test/bug/tint/1076.wgsl.expected.msl b/test/tint/bug/tint/1076.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1076.wgsl.expected.msl
rename to test/tint/bug/tint/1076.wgsl.expected.msl
diff --git a/test/bug/tint/1076.wgsl.expected.spvasm b/test/tint/bug/tint/1076.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1076.wgsl.expected.spvasm
rename to test/tint/bug/tint/1076.wgsl.expected.spvasm
diff --git a/test/bug/tint/1076.wgsl.expected.wgsl b/test/tint/bug/tint/1076.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1076.wgsl.expected.wgsl
rename to test/tint/bug/tint/1076.wgsl.expected.wgsl
diff --git a/test/bug/tint/1081.wgsl b/test/tint/bug/tint/1081.wgsl
similarity index 100%
rename from test/bug/tint/1081.wgsl
rename to test/tint/bug/tint/1081.wgsl
diff --git a/test/bug/tint/1081.wgsl.expected.glsl b/test/tint/bug/tint/1081.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1081.wgsl.expected.glsl
rename to test/tint/bug/tint/1081.wgsl.expected.glsl
diff --git a/test/bug/tint/1081.wgsl.expected.hlsl b/test/tint/bug/tint/1081.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1081.wgsl.expected.hlsl
rename to test/tint/bug/tint/1081.wgsl.expected.hlsl
diff --git a/test/bug/tint/1081.wgsl.expected.msl b/test/tint/bug/tint/1081.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1081.wgsl.expected.msl
rename to test/tint/bug/tint/1081.wgsl.expected.msl
diff --git a/test/bug/tint/1081.wgsl.expected.spvasm b/test/tint/bug/tint/1081.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1081.wgsl.expected.spvasm
rename to test/tint/bug/tint/1081.wgsl.expected.spvasm
diff --git a/test/bug/tint/1081.wgsl.expected.wgsl b/test/tint/bug/tint/1081.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1081.wgsl.expected.wgsl
rename to test/tint/bug/tint/1081.wgsl.expected.wgsl
diff --git a/test/bug/tint/1083.wgsl b/test/tint/bug/tint/1083.wgsl
similarity index 100%
rename from test/bug/tint/1083.wgsl
rename to test/tint/bug/tint/1083.wgsl
diff --git a/test/bug/tint/1083.wgsl.expected.glsl b/test/tint/bug/tint/1083.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1083.wgsl.expected.glsl
rename to test/tint/bug/tint/1083.wgsl.expected.glsl
diff --git a/test/bug/tint/1083.wgsl.expected.hlsl b/test/tint/bug/tint/1083.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1083.wgsl.expected.hlsl
rename to test/tint/bug/tint/1083.wgsl.expected.hlsl
diff --git a/test/bug/tint/1083.wgsl.expected.msl b/test/tint/bug/tint/1083.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1083.wgsl.expected.msl
rename to test/tint/bug/tint/1083.wgsl.expected.msl
diff --git a/test/bug/tint/1083.wgsl.expected.spvasm b/test/tint/bug/tint/1083.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1083.wgsl.expected.spvasm
rename to test/tint/bug/tint/1083.wgsl.expected.spvasm
diff --git a/test/bug/tint/1083.wgsl.expected.wgsl b/test/tint/bug/tint/1083.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1083.wgsl.expected.wgsl
rename to test/tint/bug/tint/1083.wgsl.expected.wgsl
diff --git a/test/bug/tint/1086.wgsl b/test/tint/bug/tint/1086.wgsl
similarity index 100%
rename from test/bug/tint/1086.wgsl
rename to test/tint/bug/tint/1086.wgsl
diff --git a/test/bug/tint/1086.wgsl.expected.glsl b/test/tint/bug/tint/1086.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1086.wgsl.expected.glsl
rename to test/tint/bug/tint/1086.wgsl.expected.glsl
diff --git a/test/bug/tint/1086.wgsl.expected.hlsl b/test/tint/bug/tint/1086.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1086.wgsl.expected.hlsl
rename to test/tint/bug/tint/1086.wgsl.expected.hlsl
diff --git a/test/bug/tint/1086.wgsl.expected.msl b/test/tint/bug/tint/1086.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1086.wgsl.expected.msl
rename to test/tint/bug/tint/1086.wgsl.expected.msl
diff --git a/test/bug/tint/1086.wgsl.expected.spvasm b/test/tint/bug/tint/1086.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1086.wgsl.expected.spvasm
rename to test/tint/bug/tint/1086.wgsl.expected.spvasm
diff --git a/test/bug/tint/1086.wgsl.expected.wgsl b/test/tint/bug/tint/1086.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1086.wgsl.expected.wgsl
rename to test/tint/bug/tint/1086.wgsl.expected.wgsl
diff --git a/test/bug/tint/1088.spvasm b/test/tint/bug/tint/1088.spvasm
similarity index 100%
rename from test/bug/tint/1088.spvasm
rename to test/tint/bug/tint/1088.spvasm
diff --git a/test/bug/tint/1088.spvasm.expected.glsl b/test/tint/bug/tint/1088.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/1088.spvasm.expected.glsl
rename to test/tint/bug/tint/1088.spvasm.expected.glsl
diff --git a/test/bug/tint/1088.spvasm.expected.hlsl b/test/tint/bug/tint/1088.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/1088.spvasm.expected.hlsl
rename to test/tint/bug/tint/1088.spvasm.expected.hlsl
diff --git a/test/bug/tint/1088.spvasm.expected.msl b/test/tint/bug/tint/1088.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/1088.spvasm.expected.msl
rename to test/tint/bug/tint/1088.spvasm.expected.msl
diff --git a/test/bug/tint/1088.spvasm.expected.spvasm b/test/tint/bug/tint/1088.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/1088.spvasm.expected.spvasm
rename to test/tint/bug/tint/1088.spvasm.expected.spvasm
diff --git a/test/bug/tint/1088.spvasm.expected.wgsl b/test/tint/bug/tint/1088.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/1088.spvasm.expected.wgsl
rename to test/tint/bug/tint/1088.spvasm.expected.wgsl
diff --git a/test/bug/tint/1113.wgsl b/test/tint/bug/tint/1113.wgsl
similarity index 100%
rename from test/bug/tint/1113.wgsl
rename to test/tint/bug/tint/1113.wgsl
diff --git a/test/bug/tint/1113.wgsl.expected.glsl b/test/tint/bug/tint/1113.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1113.wgsl.expected.glsl
rename to test/tint/bug/tint/1113.wgsl.expected.glsl
diff --git a/test/bug/tint/1113.wgsl.expected.hlsl b/test/tint/bug/tint/1113.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1113.wgsl.expected.hlsl
rename to test/tint/bug/tint/1113.wgsl.expected.hlsl
diff --git a/test/bug/tint/1113.wgsl.expected.msl b/test/tint/bug/tint/1113.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1113.wgsl.expected.msl
rename to test/tint/bug/tint/1113.wgsl.expected.msl
diff --git a/test/bug/tint/1113.wgsl.expected.spvasm b/test/tint/bug/tint/1113.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1113.wgsl.expected.spvasm
rename to test/tint/bug/tint/1113.wgsl.expected.spvasm
diff --git a/test/bug/tint/1113.wgsl.expected.wgsl b/test/tint/bug/tint/1113.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1113.wgsl.expected.wgsl
rename to test/tint/bug/tint/1113.wgsl.expected.wgsl
diff --git a/test/bug/tint/1121.wgsl b/test/tint/bug/tint/1121.wgsl
similarity index 100%
rename from test/bug/tint/1121.wgsl
rename to test/tint/bug/tint/1121.wgsl
diff --git a/test/bug/tint/1121.wgsl.expected.glsl b/test/tint/bug/tint/1121.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1121.wgsl.expected.glsl
rename to test/tint/bug/tint/1121.wgsl.expected.glsl
diff --git a/test/bug/tint/1121.wgsl.expected.hlsl b/test/tint/bug/tint/1121.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1121.wgsl.expected.hlsl
rename to test/tint/bug/tint/1121.wgsl.expected.hlsl
diff --git a/test/bug/tint/1121.wgsl.expected.msl b/test/tint/bug/tint/1121.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1121.wgsl.expected.msl
rename to test/tint/bug/tint/1121.wgsl.expected.msl
diff --git a/test/bug/tint/1121.wgsl.expected.spvasm b/test/tint/bug/tint/1121.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1121.wgsl.expected.spvasm
rename to test/tint/bug/tint/1121.wgsl.expected.spvasm
diff --git a/test/bug/tint/1121.wgsl.expected.wgsl b/test/tint/bug/tint/1121.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1121.wgsl.expected.wgsl
rename to test/tint/bug/tint/1121.wgsl.expected.wgsl
diff --git a/test/bug/tint/1136.wgsl b/test/tint/bug/tint/1136.wgsl
similarity index 100%
rename from test/bug/tint/1136.wgsl
rename to test/tint/bug/tint/1136.wgsl
diff --git a/test/bug/tint/1136.wgsl.expected.glsl b/test/tint/bug/tint/1136.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1136.wgsl.expected.glsl
rename to test/tint/bug/tint/1136.wgsl.expected.glsl
diff --git a/test/bug/tint/1136.wgsl.expected.hlsl b/test/tint/bug/tint/1136.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1136.wgsl.expected.hlsl
rename to test/tint/bug/tint/1136.wgsl.expected.hlsl
diff --git a/test/bug/tint/1136.wgsl.expected.msl b/test/tint/bug/tint/1136.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1136.wgsl.expected.msl
rename to test/tint/bug/tint/1136.wgsl.expected.msl
diff --git a/test/bug/tint/1136.wgsl.expected.spvasm b/test/tint/bug/tint/1136.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1136.wgsl.expected.spvasm
rename to test/tint/bug/tint/1136.wgsl.expected.spvasm
diff --git a/test/bug/tint/1136.wgsl.expected.wgsl b/test/tint/bug/tint/1136.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1136.wgsl.expected.wgsl
rename to test/tint/bug/tint/1136.wgsl.expected.wgsl
diff --git a/test/bug/tint/1321.wgsl b/test/tint/bug/tint/1321.wgsl
similarity index 100%
rename from test/bug/tint/1321.wgsl
rename to test/tint/bug/tint/1321.wgsl
diff --git a/test/bug/tint/1321.wgsl.expected.glsl b/test/tint/bug/tint/1321.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1321.wgsl.expected.glsl
rename to test/tint/bug/tint/1321.wgsl.expected.glsl
diff --git a/test/bug/tint/1321.wgsl.expected.hlsl b/test/tint/bug/tint/1321.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1321.wgsl.expected.hlsl
rename to test/tint/bug/tint/1321.wgsl.expected.hlsl
diff --git a/test/bug/tint/1321.wgsl.expected.msl b/test/tint/bug/tint/1321.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1321.wgsl.expected.msl
rename to test/tint/bug/tint/1321.wgsl.expected.msl
diff --git a/test/bug/tint/1321.wgsl.expected.spvasm b/test/tint/bug/tint/1321.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1321.wgsl.expected.spvasm
rename to test/tint/bug/tint/1321.wgsl.expected.spvasm
diff --git a/test/bug/tint/1321.wgsl.expected.wgsl b/test/tint/bug/tint/1321.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1321.wgsl.expected.wgsl
rename to test/tint/bug/tint/1321.wgsl.expected.wgsl
diff --git a/test/bug/tint/1369.wgsl b/test/tint/bug/tint/1369.wgsl
similarity index 100%
rename from test/bug/tint/1369.wgsl
rename to test/tint/bug/tint/1369.wgsl
diff --git a/test/bug/tint/1369.wgsl.expected.glsl b/test/tint/bug/tint/1369.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1369.wgsl.expected.glsl
rename to test/tint/bug/tint/1369.wgsl.expected.glsl
diff --git a/test/bug/tint/1369.wgsl.expected.hlsl b/test/tint/bug/tint/1369.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1369.wgsl.expected.hlsl
rename to test/tint/bug/tint/1369.wgsl.expected.hlsl
diff --git a/test/bug/tint/1369.wgsl.expected.msl b/test/tint/bug/tint/1369.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1369.wgsl.expected.msl
rename to test/tint/bug/tint/1369.wgsl.expected.msl
diff --git a/test/bug/tint/1369.wgsl.expected.spvasm b/test/tint/bug/tint/1369.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1369.wgsl.expected.spvasm
rename to test/tint/bug/tint/1369.wgsl.expected.spvasm
diff --git a/test/bug/tint/1369.wgsl.expected.wgsl b/test/tint/bug/tint/1369.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1369.wgsl.expected.wgsl
rename to test/tint/bug/tint/1369.wgsl.expected.wgsl
diff --git a/test/bug/tint/1385.wgsl b/test/tint/bug/tint/1385.wgsl
similarity index 100%
rename from test/bug/tint/1385.wgsl
rename to test/tint/bug/tint/1385.wgsl
diff --git a/test/bug/tint/1385.wgsl.expected.glsl b/test/tint/bug/tint/1385.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/1385.wgsl.expected.glsl
rename to test/tint/bug/tint/1385.wgsl.expected.glsl
diff --git a/test/bug/tint/1385.wgsl.expected.hlsl b/test/tint/bug/tint/1385.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/1385.wgsl.expected.hlsl
rename to test/tint/bug/tint/1385.wgsl.expected.hlsl
diff --git a/test/bug/tint/1385.wgsl.expected.msl b/test/tint/bug/tint/1385.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/1385.wgsl.expected.msl
rename to test/tint/bug/tint/1385.wgsl.expected.msl
diff --git a/test/bug/tint/1385.wgsl.expected.spvasm b/test/tint/bug/tint/1385.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/1385.wgsl.expected.spvasm
rename to test/tint/bug/tint/1385.wgsl.expected.spvasm
diff --git a/test/bug/tint/1385.wgsl.expected.wgsl b/test/tint/bug/tint/1385.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/1385.wgsl.expected.wgsl
rename to test/tint/bug/tint/1385.wgsl.expected.wgsl
diff --git a/test/bug/tint/219.spvasm b/test/tint/bug/tint/219.spvasm
similarity index 100%
rename from test/bug/tint/219.spvasm
rename to test/tint/bug/tint/219.spvasm
diff --git a/test/bug/tint/219.spvasm.expected.glsl b/test/tint/bug/tint/219.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/219.spvasm.expected.glsl
rename to test/tint/bug/tint/219.spvasm.expected.glsl
diff --git a/test/bug/tint/219.spvasm.expected.hlsl b/test/tint/bug/tint/219.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/219.spvasm.expected.hlsl
rename to test/tint/bug/tint/219.spvasm.expected.hlsl
diff --git a/test/bug/tint/219.spvasm.expected.msl b/test/tint/bug/tint/219.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/219.spvasm.expected.msl
rename to test/tint/bug/tint/219.spvasm.expected.msl
diff --git a/test/bug/tint/219.spvasm.expected.spvasm b/test/tint/bug/tint/219.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/219.spvasm.expected.spvasm
rename to test/tint/bug/tint/219.spvasm.expected.spvasm
diff --git a/test/bug/tint/219.spvasm.expected.wgsl b/test/tint/bug/tint/219.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/219.spvasm.expected.wgsl
rename to test/tint/bug/tint/219.spvasm.expected.wgsl
diff --git a/test/bug/tint/221.wgsl b/test/tint/bug/tint/221.wgsl
similarity index 100%
rename from test/bug/tint/221.wgsl
rename to test/tint/bug/tint/221.wgsl
diff --git a/test/bug/tint/221.wgsl.expected.glsl b/test/tint/bug/tint/221.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/221.wgsl.expected.glsl
rename to test/tint/bug/tint/221.wgsl.expected.glsl
diff --git a/test/bug/tint/221.wgsl.expected.hlsl b/test/tint/bug/tint/221.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/221.wgsl.expected.hlsl
rename to test/tint/bug/tint/221.wgsl.expected.hlsl
diff --git a/test/bug/tint/221.wgsl.expected.msl b/test/tint/bug/tint/221.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/221.wgsl.expected.msl
rename to test/tint/bug/tint/221.wgsl.expected.msl
diff --git a/test/bug/tint/221.wgsl.expected.spvasm b/test/tint/bug/tint/221.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/221.wgsl.expected.spvasm
rename to test/tint/bug/tint/221.wgsl.expected.spvasm
diff --git a/test/bug/tint/221.wgsl.expected.wgsl b/test/tint/bug/tint/221.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/221.wgsl.expected.wgsl
rename to test/tint/bug/tint/221.wgsl.expected.wgsl
diff --git a/test/bug/tint/292.wgsl b/test/tint/bug/tint/292.wgsl
similarity index 100%
rename from test/bug/tint/292.wgsl
rename to test/tint/bug/tint/292.wgsl
diff --git a/test/bug/tint/292.wgsl.expected.glsl b/test/tint/bug/tint/292.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/292.wgsl.expected.glsl
rename to test/tint/bug/tint/292.wgsl.expected.glsl
diff --git a/test/bug/tint/292.wgsl.expected.hlsl b/test/tint/bug/tint/292.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/292.wgsl.expected.hlsl
rename to test/tint/bug/tint/292.wgsl.expected.hlsl
diff --git a/test/bug/tint/292.wgsl.expected.msl b/test/tint/bug/tint/292.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/292.wgsl.expected.msl
rename to test/tint/bug/tint/292.wgsl.expected.msl
diff --git a/test/bug/tint/292.wgsl.expected.spvasm b/test/tint/bug/tint/292.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/292.wgsl.expected.spvasm
rename to test/tint/bug/tint/292.wgsl.expected.spvasm
diff --git a/test/bug/tint/292.wgsl.expected.wgsl b/test/tint/bug/tint/292.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/292.wgsl.expected.wgsl
rename to test/tint/bug/tint/292.wgsl.expected.wgsl
diff --git a/test/bug/tint/294.wgsl b/test/tint/bug/tint/294.wgsl
similarity index 100%
rename from test/bug/tint/294.wgsl
rename to test/tint/bug/tint/294.wgsl
diff --git a/test/bug/tint/294.wgsl.expected.glsl b/test/tint/bug/tint/294.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/294.wgsl.expected.glsl
rename to test/tint/bug/tint/294.wgsl.expected.glsl
diff --git a/test/bug/tint/294.wgsl.expected.hlsl b/test/tint/bug/tint/294.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/294.wgsl.expected.hlsl
rename to test/tint/bug/tint/294.wgsl.expected.hlsl
diff --git a/test/bug/tint/294.wgsl.expected.msl b/test/tint/bug/tint/294.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/294.wgsl.expected.msl
rename to test/tint/bug/tint/294.wgsl.expected.msl
diff --git a/test/bug/tint/294.wgsl.expected.spvasm b/test/tint/bug/tint/294.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/294.wgsl.expected.spvasm
rename to test/tint/bug/tint/294.wgsl.expected.spvasm
diff --git a/test/bug/tint/294.wgsl.expected.wgsl b/test/tint/bug/tint/294.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/294.wgsl.expected.wgsl
rename to test/tint/bug/tint/294.wgsl.expected.wgsl
diff --git a/test/bug/tint/369.wgsl b/test/tint/bug/tint/369.wgsl
similarity index 100%
rename from test/bug/tint/369.wgsl
rename to test/tint/bug/tint/369.wgsl
diff --git a/test/bug/tint/369.wgsl.expected.glsl b/test/tint/bug/tint/369.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/369.wgsl.expected.glsl
rename to test/tint/bug/tint/369.wgsl.expected.glsl
diff --git a/test/bug/tint/369.wgsl.expected.hlsl b/test/tint/bug/tint/369.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/369.wgsl.expected.hlsl
rename to test/tint/bug/tint/369.wgsl.expected.hlsl
diff --git a/test/bug/tint/369.wgsl.expected.msl b/test/tint/bug/tint/369.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/369.wgsl.expected.msl
rename to test/tint/bug/tint/369.wgsl.expected.msl
diff --git a/test/bug/tint/369.wgsl.expected.spvasm b/test/tint/bug/tint/369.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/369.wgsl.expected.spvasm
rename to test/tint/bug/tint/369.wgsl.expected.spvasm
diff --git a/test/bug/tint/369.wgsl.expected.wgsl b/test/tint/bug/tint/369.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/369.wgsl.expected.wgsl
rename to test/tint/bug/tint/369.wgsl.expected.wgsl
diff --git a/test/bug/tint/403.wgsl b/test/tint/bug/tint/403.wgsl
similarity index 100%
rename from test/bug/tint/403.wgsl
rename to test/tint/bug/tint/403.wgsl
diff --git a/test/bug/tint/403.wgsl.expected.glsl b/test/tint/bug/tint/403.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/403.wgsl.expected.glsl
rename to test/tint/bug/tint/403.wgsl.expected.glsl
diff --git a/test/bug/tint/403.wgsl.expected.hlsl b/test/tint/bug/tint/403.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/403.wgsl.expected.hlsl
rename to test/tint/bug/tint/403.wgsl.expected.hlsl
diff --git a/test/bug/tint/403.wgsl.expected.msl b/test/tint/bug/tint/403.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/403.wgsl.expected.msl
rename to test/tint/bug/tint/403.wgsl.expected.msl
diff --git a/test/bug/tint/403.wgsl.expected.spvasm b/test/tint/bug/tint/403.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/403.wgsl.expected.spvasm
rename to test/tint/bug/tint/403.wgsl.expected.spvasm
diff --git a/test/bug/tint/403.wgsl.expected.wgsl b/test/tint/bug/tint/403.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/403.wgsl.expected.wgsl
rename to test/tint/bug/tint/403.wgsl.expected.wgsl
diff --git a/test/bug/tint/413.spvasm b/test/tint/bug/tint/413.spvasm
similarity index 100%
rename from test/bug/tint/413.spvasm
rename to test/tint/bug/tint/413.spvasm
diff --git a/test/bug/tint/413.spvasm.expected.glsl b/test/tint/bug/tint/413.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/413.spvasm.expected.glsl
rename to test/tint/bug/tint/413.spvasm.expected.glsl
diff --git a/test/bug/tint/413.spvasm.expected.hlsl b/test/tint/bug/tint/413.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/413.spvasm.expected.hlsl
rename to test/tint/bug/tint/413.spvasm.expected.hlsl
diff --git a/test/bug/tint/413.spvasm.expected.msl b/test/tint/bug/tint/413.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/413.spvasm.expected.msl
rename to test/tint/bug/tint/413.spvasm.expected.msl
diff --git a/test/bug/tint/413.spvasm.expected.spvasm b/test/tint/bug/tint/413.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/413.spvasm.expected.spvasm
rename to test/tint/bug/tint/413.spvasm.expected.spvasm
diff --git a/test/bug/tint/413.spvasm.expected.wgsl b/test/tint/bug/tint/413.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/413.spvasm.expected.wgsl
rename to test/tint/bug/tint/413.spvasm.expected.wgsl
diff --git a/test/bug/tint/453.wgsl b/test/tint/bug/tint/453.wgsl
similarity index 100%
rename from test/bug/tint/453.wgsl
rename to test/tint/bug/tint/453.wgsl
diff --git a/test/bug/tint/453.wgsl.expected.glsl b/test/tint/bug/tint/453.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/453.wgsl.expected.glsl
rename to test/tint/bug/tint/453.wgsl.expected.glsl
diff --git a/test/bug/tint/453.wgsl.expected.hlsl b/test/tint/bug/tint/453.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/453.wgsl.expected.hlsl
rename to test/tint/bug/tint/453.wgsl.expected.hlsl
diff --git a/test/bug/tint/453.wgsl.expected.msl b/test/tint/bug/tint/453.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/453.wgsl.expected.msl
rename to test/tint/bug/tint/453.wgsl.expected.msl
diff --git a/test/bug/tint/453.wgsl.expected.spvasm b/test/tint/bug/tint/453.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/453.wgsl.expected.spvasm
rename to test/tint/bug/tint/453.wgsl.expected.spvasm
diff --git a/test/bug/tint/453.wgsl.expected.wgsl b/test/tint/bug/tint/453.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/453.wgsl.expected.wgsl
rename to test/tint/bug/tint/453.wgsl.expected.wgsl
diff --git a/test/bug/tint/492.wgsl b/test/tint/bug/tint/492.wgsl
similarity index 100%
rename from test/bug/tint/492.wgsl
rename to test/tint/bug/tint/492.wgsl
diff --git a/test/bug/tint/492.wgsl.expected.glsl b/test/tint/bug/tint/492.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/492.wgsl.expected.glsl
rename to test/tint/bug/tint/492.wgsl.expected.glsl
diff --git a/test/bug/tint/492.wgsl.expected.hlsl b/test/tint/bug/tint/492.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/492.wgsl.expected.hlsl
rename to test/tint/bug/tint/492.wgsl.expected.hlsl
diff --git a/test/bug/tint/492.wgsl.expected.msl b/test/tint/bug/tint/492.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/492.wgsl.expected.msl
rename to test/tint/bug/tint/492.wgsl.expected.msl
diff --git a/test/bug/tint/492.wgsl.expected.spvasm b/test/tint/bug/tint/492.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/492.wgsl.expected.spvasm
rename to test/tint/bug/tint/492.wgsl.expected.spvasm
diff --git a/test/bug/tint/492.wgsl.expected.wgsl b/test/tint/bug/tint/492.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/492.wgsl.expected.wgsl
rename to test/tint/bug/tint/492.wgsl.expected.wgsl
diff --git a/test/bug/tint/534.wgsl b/test/tint/bug/tint/534.wgsl
similarity index 100%
rename from test/bug/tint/534.wgsl
rename to test/tint/bug/tint/534.wgsl
diff --git a/test/bug/tint/534.wgsl.expected.glsl b/test/tint/bug/tint/534.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/534.wgsl.expected.glsl
rename to test/tint/bug/tint/534.wgsl.expected.glsl
diff --git a/test/bug/tint/534.wgsl.expected.hlsl b/test/tint/bug/tint/534.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/534.wgsl.expected.hlsl
rename to test/tint/bug/tint/534.wgsl.expected.hlsl
diff --git a/test/bug/tint/534.wgsl.expected.msl b/test/tint/bug/tint/534.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/534.wgsl.expected.msl
rename to test/tint/bug/tint/534.wgsl.expected.msl
diff --git a/test/bug/tint/534.wgsl.expected.spvasm b/test/tint/bug/tint/534.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/534.wgsl.expected.spvasm
rename to test/tint/bug/tint/534.wgsl.expected.spvasm
diff --git a/test/bug/tint/534.wgsl.expected.wgsl b/test/tint/bug/tint/534.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/534.wgsl.expected.wgsl
rename to test/tint/bug/tint/534.wgsl.expected.wgsl
diff --git a/test/bug/tint/744.wgsl b/test/tint/bug/tint/744.wgsl
similarity index 100%
rename from test/bug/tint/744.wgsl
rename to test/tint/bug/tint/744.wgsl
diff --git a/test/bug/tint/744.wgsl.expected.glsl b/test/tint/bug/tint/744.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/744.wgsl.expected.glsl
rename to test/tint/bug/tint/744.wgsl.expected.glsl
diff --git a/test/bug/tint/744.wgsl.expected.hlsl b/test/tint/bug/tint/744.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/744.wgsl.expected.hlsl
rename to test/tint/bug/tint/744.wgsl.expected.hlsl
diff --git a/test/bug/tint/744.wgsl.expected.msl b/test/tint/bug/tint/744.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/744.wgsl.expected.msl
rename to test/tint/bug/tint/744.wgsl.expected.msl
diff --git a/test/bug/tint/744.wgsl.expected.spvasm b/test/tint/bug/tint/744.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/744.wgsl.expected.spvasm
rename to test/tint/bug/tint/744.wgsl.expected.spvasm
diff --git a/test/bug/tint/744.wgsl.expected.wgsl b/test/tint/bug/tint/744.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/744.wgsl.expected.wgsl
rename to test/tint/bug/tint/744.wgsl.expected.wgsl
diff --git a/test/bug/tint/749.spvasm b/test/tint/bug/tint/749.spvasm
similarity index 100%
rename from test/bug/tint/749.spvasm
rename to test/tint/bug/tint/749.spvasm
diff --git a/test/bug/tint/749.spvasm.expected.glsl b/test/tint/bug/tint/749.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/749.spvasm.expected.glsl
rename to test/tint/bug/tint/749.spvasm.expected.glsl
diff --git a/test/bug/tint/749.spvasm.expected.hlsl b/test/tint/bug/tint/749.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/749.spvasm.expected.hlsl
rename to test/tint/bug/tint/749.spvasm.expected.hlsl
diff --git a/test/bug/tint/749.spvasm.expected.msl b/test/tint/bug/tint/749.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/749.spvasm.expected.msl
rename to test/tint/bug/tint/749.spvasm.expected.msl
diff --git a/test/bug/tint/749.spvasm.expected.spvasm b/test/tint/bug/tint/749.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/749.spvasm.expected.spvasm
rename to test/tint/bug/tint/749.spvasm.expected.spvasm
diff --git a/test/bug/tint/749.spvasm.expected.wgsl b/test/tint/bug/tint/749.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/749.spvasm.expected.wgsl
rename to test/tint/bug/tint/749.spvasm.expected.wgsl
diff --git a/test/bug/tint/757.wgsl b/test/tint/bug/tint/757.wgsl
similarity index 100%
rename from test/bug/tint/757.wgsl
rename to test/tint/bug/tint/757.wgsl
diff --git a/test/bug/tint/757.wgsl.expected.glsl b/test/tint/bug/tint/757.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/757.wgsl.expected.glsl
rename to test/tint/bug/tint/757.wgsl.expected.glsl
diff --git a/test/bug/tint/757.wgsl.expected.hlsl b/test/tint/bug/tint/757.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/757.wgsl.expected.hlsl
rename to test/tint/bug/tint/757.wgsl.expected.hlsl
diff --git a/test/bug/tint/757.wgsl.expected.msl b/test/tint/bug/tint/757.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/757.wgsl.expected.msl
rename to test/tint/bug/tint/757.wgsl.expected.msl
diff --git a/test/bug/tint/757.wgsl.expected.spvasm b/test/tint/bug/tint/757.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/757.wgsl.expected.spvasm
rename to test/tint/bug/tint/757.wgsl.expected.spvasm
diff --git a/test/bug/tint/757.wgsl.expected.wgsl b/test/tint/bug/tint/757.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/757.wgsl.expected.wgsl
rename to test/tint/bug/tint/757.wgsl.expected.wgsl
diff --git a/test/bug/tint/764.wgsl b/test/tint/bug/tint/764.wgsl
similarity index 100%
rename from test/bug/tint/764.wgsl
rename to test/tint/bug/tint/764.wgsl
diff --git a/test/bug/tint/764.wgsl.expected.glsl b/test/tint/bug/tint/764.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/764.wgsl.expected.glsl
rename to test/tint/bug/tint/764.wgsl.expected.glsl
diff --git a/test/bug/tint/764.wgsl.expected.hlsl b/test/tint/bug/tint/764.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/764.wgsl.expected.hlsl
rename to test/tint/bug/tint/764.wgsl.expected.hlsl
diff --git a/test/bug/tint/764.wgsl.expected.msl b/test/tint/bug/tint/764.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/764.wgsl.expected.msl
rename to test/tint/bug/tint/764.wgsl.expected.msl
diff --git a/test/bug/tint/764.wgsl.expected.spvasm b/test/tint/bug/tint/764.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/764.wgsl.expected.spvasm
rename to test/tint/bug/tint/764.wgsl.expected.spvasm
diff --git a/test/bug/tint/764.wgsl.expected.wgsl b/test/tint/bug/tint/764.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/764.wgsl.expected.wgsl
rename to test/tint/bug/tint/764.wgsl.expected.wgsl
diff --git a/test/bug/tint/782.wgsl b/test/tint/bug/tint/782.wgsl
similarity index 100%
rename from test/bug/tint/782.wgsl
rename to test/tint/bug/tint/782.wgsl
diff --git a/test/bug/tint/782.wgsl.expected.glsl b/test/tint/bug/tint/782.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/782.wgsl.expected.glsl
rename to test/tint/bug/tint/782.wgsl.expected.glsl
diff --git a/test/bug/tint/782.wgsl.expected.hlsl b/test/tint/bug/tint/782.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/782.wgsl.expected.hlsl
rename to test/tint/bug/tint/782.wgsl.expected.hlsl
diff --git a/test/bug/tint/782.wgsl.expected.msl b/test/tint/bug/tint/782.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/782.wgsl.expected.msl
rename to test/tint/bug/tint/782.wgsl.expected.msl
diff --git a/test/bug/tint/782.wgsl.expected.spvasm b/test/tint/bug/tint/782.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/782.wgsl.expected.spvasm
rename to test/tint/bug/tint/782.wgsl.expected.spvasm
diff --git a/test/bug/tint/782.wgsl.expected.wgsl b/test/tint/bug/tint/782.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/782.wgsl.expected.wgsl
rename to test/tint/bug/tint/782.wgsl.expected.wgsl
diff --git a/test/bug/tint/804.spv b/test/tint/bug/tint/804.spv
similarity index 100%
rename from test/bug/tint/804.spv
rename to test/tint/bug/tint/804.spv
Binary files differ
diff --git a/test/bug/tint/804.spv.expected.glsl b/test/tint/bug/tint/804.spv.expected.glsl
similarity index 100%
rename from test/bug/tint/804.spv.expected.glsl
rename to test/tint/bug/tint/804.spv.expected.glsl
diff --git a/test/bug/tint/804.spv.expected.hlsl b/test/tint/bug/tint/804.spv.expected.hlsl
similarity index 100%
rename from test/bug/tint/804.spv.expected.hlsl
rename to test/tint/bug/tint/804.spv.expected.hlsl
diff --git a/test/bug/tint/804.spv.expected.msl b/test/tint/bug/tint/804.spv.expected.msl
similarity index 100%
rename from test/bug/tint/804.spv.expected.msl
rename to test/tint/bug/tint/804.spv.expected.msl
diff --git a/test/bug/tint/804.spv.expected.spvasm b/test/tint/bug/tint/804.spv.expected.spvasm
similarity index 100%
rename from test/bug/tint/804.spv.expected.spvasm
rename to test/tint/bug/tint/804.spv.expected.spvasm
diff --git a/test/bug/tint/804.spv.expected.wgsl b/test/tint/bug/tint/804.spv.expected.wgsl
similarity index 100%
rename from test/bug/tint/804.spv.expected.wgsl
rename to test/tint/bug/tint/804.spv.expected.wgsl
diff --git a/test/bug/tint/807.spv b/test/tint/bug/tint/807.spv
similarity index 100%
rename from test/bug/tint/807.spv
rename to test/tint/bug/tint/807.spv
Binary files differ
diff --git a/test/bug/tint/807.spv.expected.glsl b/test/tint/bug/tint/807.spv.expected.glsl
similarity index 100%
rename from test/bug/tint/807.spv.expected.glsl
rename to test/tint/bug/tint/807.spv.expected.glsl
diff --git a/test/bug/tint/807.spv.expected.hlsl b/test/tint/bug/tint/807.spv.expected.hlsl
similarity index 100%
rename from test/bug/tint/807.spv.expected.hlsl
rename to test/tint/bug/tint/807.spv.expected.hlsl
diff --git a/test/bug/tint/807.spv.expected.msl b/test/tint/bug/tint/807.spv.expected.msl
similarity index 100%
rename from test/bug/tint/807.spv.expected.msl
rename to test/tint/bug/tint/807.spv.expected.msl
diff --git a/test/bug/tint/807.spv.expected.spvasm b/test/tint/bug/tint/807.spv.expected.spvasm
similarity index 100%
rename from test/bug/tint/807.spv.expected.spvasm
rename to test/tint/bug/tint/807.spv.expected.spvasm
diff --git a/test/bug/tint/807.spv.expected.wgsl b/test/tint/bug/tint/807.spv.expected.wgsl
similarity index 100%
rename from test/bug/tint/807.spv.expected.wgsl
rename to test/tint/bug/tint/807.spv.expected.wgsl
diff --git a/test/bug/tint/824.wgsl b/test/tint/bug/tint/824.wgsl
similarity index 100%
rename from test/bug/tint/824.wgsl
rename to test/tint/bug/tint/824.wgsl
diff --git a/test/bug/tint/824.wgsl.expected.glsl b/test/tint/bug/tint/824.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/824.wgsl.expected.glsl
rename to test/tint/bug/tint/824.wgsl.expected.glsl
diff --git a/test/bug/tint/824.wgsl.expected.hlsl b/test/tint/bug/tint/824.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/824.wgsl.expected.hlsl
rename to test/tint/bug/tint/824.wgsl.expected.hlsl
diff --git a/test/bug/tint/824.wgsl.expected.msl b/test/tint/bug/tint/824.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/824.wgsl.expected.msl
rename to test/tint/bug/tint/824.wgsl.expected.msl
diff --git a/test/bug/tint/824.wgsl.expected.spvasm b/test/tint/bug/tint/824.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/824.wgsl.expected.spvasm
rename to test/tint/bug/tint/824.wgsl.expected.spvasm
diff --git a/test/bug/tint/824.wgsl.expected.wgsl b/test/tint/bug/tint/824.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/824.wgsl.expected.wgsl
rename to test/tint/bug/tint/824.wgsl.expected.wgsl
diff --git a/test/bug/tint/825.wgsl b/test/tint/bug/tint/825.wgsl
similarity index 100%
rename from test/bug/tint/825.wgsl
rename to test/tint/bug/tint/825.wgsl
diff --git a/test/bug/tint/825.wgsl.expected.glsl b/test/tint/bug/tint/825.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/825.wgsl.expected.glsl
rename to test/tint/bug/tint/825.wgsl.expected.glsl
diff --git a/test/bug/tint/825.wgsl.expected.hlsl b/test/tint/bug/tint/825.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/825.wgsl.expected.hlsl
rename to test/tint/bug/tint/825.wgsl.expected.hlsl
diff --git a/test/bug/tint/825.wgsl.expected.msl b/test/tint/bug/tint/825.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/825.wgsl.expected.msl
rename to test/tint/bug/tint/825.wgsl.expected.msl
diff --git a/test/bug/tint/825.wgsl.expected.spvasm b/test/tint/bug/tint/825.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/825.wgsl.expected.spvasm
rename to test/tint/bug/tint/825.wgsl.expected.spvasm
diff --git a/test/bug/tint/825.wgsl.expected.wgsl b/test/tint/bug/tint/825.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/825.wgsl.expected.wgsl
rename to test/tint/bug/tint/825.wgsl.expected.wgsl
diff --git a/test/bug/tint/827.wgsl b/test/tint/bug/tint/827.wgsl
similarity index 100%
rename from test/bug/tint/827.wgsl
rename to test/tint/bug/tint/827.wgsl
diff --git a/test/bug/tint/827.wgsl.expected.glsl b/test/tint/bug/tint/827.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/827.wgsl.expected.glsl
rename to test/tint/bug/tint/827.wgsl.expected.glsl
diff --git a/test/bug/tint/827.wgsl.expected.hlsl b/test/tint/bug/tint/827.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/827.wgsl.expected.hlsl
rename to test/tint/bug/tint/827.wgsl.expected.hlsl
diff --git a/test/bug/tint/827.wgsl.expected.msl b/test/tint/bug/tint/827.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/827.wgsl.expected.msl
rename to test/tint/bug/tint/827.wgsl.expected.msl
diff --git a/test/bug/tint/827.wgsl.expected.spvasm b/test/tint/bug/tint/827.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/827.wgsl.expected.spvasm
rename to test/tint/bug/tint/827.wgsl.expected.spvasm
diff --git a/test/bug/tint/827.wgsl.expected.wgsl b/test/tint/bug/tint/827.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/827.wgsl.expected.wgsl
rename to test/tint/bug/tint/827.wgsl.expected.wgsl
diff --git a/test/bug/tint/870.spvasm b/test/tint/bug/tint/870.spvasm
similarity index 100%
rename from test/bug/tint/870.spvasm
rename to test/tint/bug/tint/870.spvasm
diff --git a/test/bug/tint/870.spvasm.expected.glsl b/test/tint/bug/tint/870.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/870.spvasm.expected.glsl
rename to test/tint/bug/tint/870.spvasm.expected.glsl
diff --git a/test/bug/tint/870.spvasm.expected.hlsl b/test/tint/bug/tint/870.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/870.spvasm.expected.hlsl
rename to test/tint/bug/tint/870.spvasm.expected.hlsl
diff --git a/test/bug/tint/870.spvasm.expected.msl b/test/tint/bug/tint/870.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/870.spvasm.expected.msl
rename to test/tint/bug/tint/870.spvasm.expected.msl
diff --git a/test/bug/tint/870.spvasm.expected.spvasm b/test/tint/bug/tint/870.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/870.spvasm.expected.spvasm
rename to test/tint/bug/tint/870.spvasm.expected.spvasm
diff --git a/test/bug/tint/870.spvasm.expected.wgsl b/test/tint/bug/tint/870.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/870.spvasm.expected.wgsl
rename to test/tint/bug/tint/870.spvasm.expected.wgsl
diff --git a/test/bug/tint/913.wgsl b/test/tint/bug/tint/913.wgsl
similarity index 100%
rename from test/bug/tint/913.wgsl
rename to test/tint/bug/tint/913.wgsl
diff --git a/test/bug/tint/913.wgsl.expected.glsl b/test/tint/bug/tint/913.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/913.wgsl.expected.glsl
rename to test/tint/bug/tint/913.wgsl.expected.glsl
diff --git a/test/bug/tint/913.wgsl.expected.hlsl b/test/tint/bug/tint/913.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/913.wgsl.expected.hlsl
rename to test/tint/bug/tint/913.wgsl.expected.hlsl
diff --git a/test/bug/tint/913.wgsl.expected.msl b/test/tint/bug/tint/913.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/913.wgsl.expected.msl
rename to test/tint/bug/tint/913.wgsl.expected.msl
diff --git a/test/bug/tint/913.wgsl.expected.spvasm b/test/tint/bug/tint/913.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/913.wgsl.expected.spvasm
rename to test/tint/bug/tint/913.wgsl.expected.spvasm
diff --git a/test/bug/tint/913.wgsl.expected.wgsl b/test/tint/bug/tint/913.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/913.wgsl.expected.wgsl
rename to test/tint/bug/tint/913.wgsl.expected.wgsl
diff --git a/test/bug/tint/914.wgsl b/test/tint/bug/tint/914.wgsl
similarity index 100%
rename from test/bug/tint/914.wgsl
rename to test/tint/bug/tint/914.wgsl
diff --git a/test/bug/tint/914.wgsl.expected.glsl b/test/tint/bug/tint/914.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/914.wgsl.expected.glsl
rename to test/tint/bug/tint/914.wgsl.expected.glsl
diff --git a/test/bug/tint/914.wgsl.expected.hlsl b/test/tint/bug/tint/914.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/914.wgsl.expected.hlsl
rename to test/tint/bug/tint/914.wgsl.expected.hlsl
diff --git a/test/bug/tint/914.wgsl.expected.msl b/test/tint/bug/tint/914.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/914.wgsl.expected.msl
rename to test/tint/bug/tint/914.wgsl.expected.msl
diff --git a/test/bug/tint/914.wgsl.expected.spvasm b/test/tint/bug/tint/914.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/914.wgsl.expected.spvasm
rename to test/tint/bug/tint/914.wgsl.expected.spvasm
diff --git a/test/bug/tint/914.wgsl.expected.wgsl b/test/tint/bug/tint/914.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/914.wgsl.expected.wgsl
rename to test/tint/bug/tint/914.wgsl.expected.wgsl
diff --git a/test/bug/tint/922.wgsl b/test/tint/bug/tint/922.wgsl
similarity index 100%
rename from test/bug/tint/922.wgsl
rename to test/tint/bug/tint/922.wgsl
diff --git a/test/bug/tint/922.wgsl.expected.glsl b/test/tint/bug/tint/922.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/922.wgsl.expected.glsl
rename to test/tint/bug/tint/922.wgsl.expected.glsl
diff --git a/test/bug/tint/922.wgsl.expected.hlsl b/test/tint/bug/tint/922.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/922.wgsl.expected.hlsl
rename to test/tint/bug/tint/922.wgsl.expected.hlsl
diff --git a/test/bug/tint/922.wgsl.expected.msl b/test/tint/bug/tint/922.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/922.wgsl.expected.msl
rename to test/tint/bug/tint/922.wgsl.expected.msl
diff --git a/test/bug/tint/922.wgsl.expected.spvasm b/test/tint/bug/tint/922.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/922.wgsl.expected.spvasm
rename to test/tint/bug/tint/922.wgsl.expected.spvasm
diff --git a/test/bug/tint/922.wgsl.expected.wgsl b/test/tint/bug/tint/922.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/922.wgsl.expected.wgsl
rename to test/tint/bug/tint/922.wgsl.expected.wgsl
diff --git a/test/bug/tint/926.wgsl b/test/tint/bug/tint/926.wgsl
similarity index 100%
rename from test/bug/tint/926.wgsl
rename to test/tint/bug/tint/926.wgsl
diff --git a/test/bug/tint/926.wgsl.expected.glsl b/test/tint/bug/tint/926.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/926.wgsl.expected.glsl
rename to test/tint/bug/tint/926.wgsl.expected.glsl
diff --git a/test/bug/tint/926.wgsl.expected.hlsl b/test/tint/bug/tint/926.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/926.wgsl.expected.hlsl
rename to test/tint/bug/tint/926.wgsl.expected.hlsl
diff --git a/test/bug/tint/926.wgsl.expected.msl b/test/tint/bug/tint/926.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/926.wgsl.expected.msl
rename to test/tint/bug/tint/926.wgsl.expected.msl
diff --git a/test/bug/tint/926.wgsl.expected.spvasm b/test/tint/bug/tint/926.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/926.wgsl.expected.spvasm
rename to test/tint/bug/tint/926.wgsl.expected.spvasm
diff --git a/test/bug/tint/926.wgsl.expected.wgsl b/test/tint/bug/tint/926.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/926.wgsl.expected.wgsl
rename to test/tint/bug/tint/926.wgsl.expected.wgsl
diff --git a/test/bug/tint/942.wgsl b/test/tint/bug/tint/942.wgsl
similarity index 100%
rename from test/bug/tint/942.wgsl
rename to test/tint/bug/tint/942.wgsl
diff --git a/test/bug/tint/942.wgsl.expected.glsl b/test/tint/bug/tint/942.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/942.wgsl.expected.glsl
rename to test/tint/bug/tint/942.wgsl.expected.glsl
diff --git a/test/bug/tint/942.wgsl.expected.hlsl b/test/tint/bug/tint/942.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/942.wgsl.expected.hlsl
rename to test/tint/bug/tint/942.wgsl.expected.hlsl
diff --git a/test/bug/tint/942.wgsl.expected.msl b/test/tint/bug/tint/942.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/942.wgsl.expected.msl
rename to test/tint/bug/tint/942.wgsl.expected.msl
diff --git a/test/bug/tint/942.wgsl.expected.spvasm b/test/tint/bug/tint/942.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/942.wgsl.expected.spvasm
rename to test/tint/bug/tint/942.wgsl.expected.spvasm
diff --git a/test/bug/tint/942.wgsl.expected.wgsl b/test/tint/bug/tint/942.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/942.wgsl.expected.wgsl
rename to test/tint/bug/tint/942.wgsl.expected.wgsl
diff --git a/test/bug/tint/943.spvasm b/test/tint/bug/tint/943.spvasm
similarity index 100%
rename from test/bug/tint/943.spvasm
rename to test/tint/bug/tint/943.spvasm
diff --git a/test/bug/tint/943.spvasm.expected.glsl b/test/tint/bug/tint/943.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/943.spvasm.expected.glsl
rename to test/tint/bug/tint/943.spvasm.expected.glsl
diff --git a/test/bug/tint/943.spvasm.expected.hlsl b/test/tint/bug/tint/943.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/943.spvasm.expected.hlsl
rename to test/tint/bug/tint/943.spvasm.expected.hlsl
diff --git a/test/bug/tint/943.spvasm.expected.msl b/test/tint/bug/tint/943.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/943.spvasm.expected.msl
rename to test/tint/bug/tint/943.spvasm.expected.msl
diff --git a/test/bug/tint/943.spvasm.expected.spvasm b/test/tint/bug/tint/943.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/943.spvasm.expected.spvasm
rename to test/tint/bug/tint/943.spvasm.expected.spvasm
diff --git a/test/bug/tint/943.spvasm.expected.wgsl b/test/tint/bug/tint/943.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/943.spvasm.expected.wgsl
rename to test/tint/bug/tint/943.spvasm.expected.wgsl
diff --git a/test/bug/tint/948.wgsl b/test/tint/bug/tint/948.wgsl
similarity index 100%
rename from test/bug/tint/948.wgsl
rename to test/tint/bug/tint/948.wgsl
diff --git a/test/bug/tint/948.wgsl.expected.glsl b/test/tint/bug/tint/948.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/948.wgsl.expected.glsl
rename to test/tint/bug/tint/948.wgsl.expected.glsl
diff --git a/test/bug/tint/948.wgsl.expected.hlsl b/test/tint/bug/tint/948.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/948.wgsl.expected.hlsl
rename to test/tint/bug/tint/948.wgsl.expected.hlsl
diff --git a/test/bug/tint/948.wgsl.expected.msl b/test/tint/bug/tint/948.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/948.wgsl.expected.msl
rename to test/tint/bug/tint/948.wgsl.expected.msl
diff --git a/test/bug/tint/948.wgsl.expected.spvasm b/test/tint/bug/tint/948.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/948.wgsl.expected.spvasm
rename to test/tint/bug/tint/948.wgsl.expected.spvasm
diff --git a/test/bug/tint/948.wgsl.expected.wgsl b/test/tint/bug/tint/948.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/948.wgsl.expected.wgsl
rename to test/tint/bug/tint/948.wgsl.expected.wgsl
diff --git a/test/bug/tint/949.wgsl b/test/tint/bug/tint/949.wgsl
similarity index 100%
rename from test/bug/tint/949.wgsl
rename to test/tint/bug/tint/949.wgsl
diff --git a/test/bug/tint/949.wgsl.expected.glsl b/test/tint/bug/tint/949.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/949.wgsl.expected.glsl
rename to test/tint/bug/tint/949.wgsl.expected.glsl
diff --git a/test/bug/tint/949.wgsl.expected.hlsl b/test/tint/bug/tint/949.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/949.wgsl.expected.hlsl
rename to test/tint/bug/tint/949.wgsl.expected.hlsl
diff --git a/test/bug/tint/949.wgsl.expected.msl b/test/tint/bug/tint/949.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/949.wgsl.expected.msl
rename to test/tint/bug/tint/949.wgsl.expected.msl
diff --git a/test/bug/tint/949.wgsl.expected.spvasm b/test/tint/bug/tint/949.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/949.wgsl.expected.spvasm
rename to test/tint/bug/tint/949.wgsl.expected.spvasm
diff --git a/test/bug/tint/949.wgsl.expected.wgsl b/test/tint/bug/tint/949.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/949.wgsl.expected.wgsl
rename to test/tint/bug/tint/949.wgsl.expected.wgsl
diff --git a/test/bug/tint/951.spvasm b/test/tint/bug/tint/951.spvasm
similarity index 100%
rename from test/bug/tint/951.spvasm
rename to test/tint/bug/tint/951.spvasm
diff --git a/test/bug/tint/951.spvasm.expected.glsl b/test/tint/bug/tint/951.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/951.spvasm.expected.glsl
rename to test/tint/bug/tint/951.spvasm.expected.glsl
diff --git a/test/bug/tint/951.spvasm.expected.hlsl b/test/tint/bug/tint/951.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/951.spvasm.expected.hlsl
rename to test/tint/bug/tint/951.spvasm.expected.hlsl
diff --git a/test/bug/tint/951.spvasm.expected.msl b/test/tint/bug/tint/951.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/951.spvasm.expected.msl
rename to test/tint/bug/tint/951.spvasm.expected.msl
diff --git a/test/bug/tint/951.spvasm.expected.spvasm b/test/tint/bug/tint/951.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/951.spvasm.expected.spvasm
rename to test/tint/bug/tint/951.spvasm.expected.spvasm
diff --git a/test/bug/tint/951.spvasm.expected.wgsl b/test/tint/bug/tint/951.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/951.spvasm.expected.wgsl
rename to test/tint/bug/tint/951.spvasm.expected.wgsl
diff --git a/test/bug/tint/959.wgsl b/test/tint/bug/tint/959.wgsl
similarity index 100%
rename from test/bug/tint/959.wgsl
rename to test/tint/bug/tint/959.wgsl
diff --git a/test/bug/tint/959.wgsl.expected.glsl b/test/tint/bug/tint/959.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/959.wgsl.expected.glsl
rename to test/tint/bug/tint/959.wgsl.expected.glsl
diff --git a/test/bug/tint/959.wgsl.expected.hlsl b/test/tint/bug/tint/959.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/959.wgsl.expected.hlsl
rename to test/tint/bug/tint/959.wgsl.expected.hlsl
diff --git a/test/bug/tint/959.wgsl.expected.msl b/test/tint/bug/tint/959.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/959.wgsl.expected.msl
rename to test/tint/bug/tint/959.wgsl.expected.msl
diff --git a/test/bug/tint/959.wgsl.expected.spvasm b/test/tint/bug/tint/959.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/959.wgsl.expected.spvasm
rename to test/tint/bug/tint/959.wgsl.expected.spvasm
diff --git a/test/bug/tint/959.wgsl.expected.wgsl b/test/tint/bug/tint/959.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/959.wgsl.expected.wgsl
rename to test/tint/bug/tint/959.wgsl.expected.wgsl
diff --git a/test/bug/tint/977.spvasm b/test/tint/bug/tint/977.spvasm
similarity index 100%
rename from test/bug/tint/977.spvasm
rename to test/tint/bug/tint/977.spvasm
diff --git a/test/bug/tint/977.spvasm.expected.glsl b/test/tint/bug/tint/977.spvasm.expected.glsl
similarity index 100%
rename from test/bug/tint/977.spvasm.expected.glsl
rename to test/tint/bug/tint/977.spvasm.expected.glsl
diff --git a/test/bug/tint/977.spvasm.expected.hlsl b/test/tint/bug/tint/977.spvasm.expected.hlsl
similarity index 100%
rename from test/bug/tint/977.spvasm.expected.hlsl
rename to test/tint/bug/tint/977.spvasm.expected.hlsl
diff --git a/test/bug/tint/977.spvasm.expected.msl b/test/tint/bug/tint/977.spvasm.expected.msl
similarity index 100%
rename from test/bug/tint/977.spvasm.expected.msl
rename to test/tint/bug/tint/977.spvasm.expected.msl
diff --git a/test/bug/tint/977.spvasm.expected.spvasm b/test/tint/bug/tint/977.spvasm.expected.spvasm
similarity index 100%
rename from test/bug/tint/977.spvasm.expected.spvasm
rename to test/tint/bug/tint/977.spvasm.expected.spvasm
diff --git a/test/bug/tint/977.spvasm.expected.wgsl b/test/tint/bug/tint/977.spvasm.expected.wgsl
similarity index 100%
rename from test/bug/tint/977.spvasm.expected.wgsl
rename to test/tint/bug/tint/977.spvasm.expected.wgsl
diff --git a/test/bug/tint/978.wgsl b/test/tint/bug/tint/978.wgsl
similarity index 100%
rename from test/bug/tint/978.wgsl
rename to test/tint/bug/tint/978.wgsl
diff --git a/test/bug/tint/978.wgsl.expected.glsl b/test/tint/bug/tint/978.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/978.wgsl.expected.glsl
rename to test/tint/bug/tint/978.wgsl.expected.glsl
diff --git a/test/bug/tint/978.wgsl.expected.hlsl b/test/tint/bug/tint/978.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/978.wgsl.expected.hlsl
rename to test/tint/bug/tint/978.wgsl.expected.hlsl
diff --git a/test/bug/tint/978.wgsl.expected.msl b/test/tint/bug/tint/978.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/978.wgsl.expected.msl
rename to test/tint/bug/tint/978.wgsl.expected.msl
diff --git a/test/bug/tint/978.wgsl.expected.spvasm b/test/tint/bug/tint/978.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/978.wgsl.expected.spvasm
rename to test/tint/bug/tint/978.wgsl.expected.spvasm
diff --git a/test/bug/tint/978.wgsl.expected.wgsl b/test/tint/bug/tint/978.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/978.wgsl.expected.wgsl
rename to test/tint/bug/tint/978.wgsl.expected.wgsl
diff --git a/test/bug/tint/980.wgsl b/test/tint/bug/tint/980.wgsl
similarity index 100%
rename from test/bug/tint/980.wgsl
rename to test/tint/bug/tint/980.wgsl
diff --git a/test/bug/tint/980.wgsl.expected.glsl b/test/tint/bug/tint/980.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/980.wgsl.expected.glsl
rename to test/tint/bug/tint/980.wgsl.expected.glsl
diff --git a/test/bug/tint/980.wgsl.expected.hlsl b/test/tint/bug/tint/980.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/980.wgsl.expected.hlsl
rename to test/tint/bug/tint/980.wgsl.expected.hlsl
diff --git a/test/bug/tint/980.wgsl.expected.msl b/test/tint/bug/tint/980.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/980.wgsl.expected.msl
rename to test/tint/bug/tint/980.wgsl.expected.msl
diff --git a/test/bug/tint/980.wgsl.expected.spvasm b/test/tint/bug/tint/980.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/980.wgsl.expected.spvasm
rename to test/tint/bug/tint/980.wgsl.expected.spvasm
diff --git a/test/bug/tint/980.wgsl.expected.wgsl b/test/tint/bug/tint/980.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/980.wgsl.expected.wgsl
rename to test/tint/bug/tint/980.wgsl.expected.wgsl
diff --git a/test/bug/tint/990.wgsl b/test/tint/bug/tint/990.wgsl
similarity index 100%
rename from test/bug/tint/990.wgsl
rename to test/tint/bug/tint/990.wgsl
diff --git a/test/bug/tint/990.wgsl.expected.glsl b/test/tint/bug/tint/990.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/990.wgsl.expected.glsl
rename to test/tint/bug/tint/990.wgsl.expected.glsl
diff --git a/test/bug/tint/990.wgsl.expected.hlsl b/test/tint/bug/tint/990.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/990.wgsl.expected.hlsl
rename to test/tint/bug/tint/990.wgsl.expected.hlsl
diff --git a/test/bug/tint/990.wgsl.expected.msl b/test/tint/bug/tint/990.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/990.wgsl.expected.msl
rename to test/tint/bug/tint/990.wgsl.expected.msl
diff --git a/test/bug/tint/990.wgsl.expected.spvasm b/test/tint/bug/tint/990.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/990.wgsl.expected.spvasm
rename to test/tint/bug/tint/990.wgsl.expected.spvasm
diff --git a/test/bug/tint/990.wgsl.expected.wgsl b/test/tint/bug/tint/990.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/990.wgsl.expected.wgsl
rename to test/tint/bug/tint/990.wgsl.expected.wgsl
diff --git a/test/bug/tint/992.wgsl b/test/tint/bug/tint/992.wgsl
similarity index 100%
rename from test/bug/tint/992.wgsl
rename to test/tint/bug/tint/992.wgsl
diff --git a/test/bug/tint/992.wgsl.expected.glsl b/test/tint/bug/tint/992.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/992.wgsl.expected.glsl
rename to test/tint/bug/tint/992.wgsl.expected.glsl
diff --git a/test/bug/tint/992.wgsl.expected.hlsl b/test/tint/bug/tint/992.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/992.wgsl.expected.hlsl
rename to test/tint/bug/tint/992.wgsl.expected.hlsl
diff --git a/test/bug/tint/992.wgsl.expected.msl b/test/tint/bug/tint/992.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/992.wgsl.expected.msl
rename to test/tint/bug/tint/992.wgsl.expected.msl
diff --git a/test/bug/tint/992.wgsl.expected.spvasm b/test/tint/bug/tint/992.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/992.wgsl.expected.spvasm
rename to test/tint/bug/tint/992.wgsl.expected.spvasm
diff --git a/test/bug/tint/992.wgsl.expected.wgsl b/test/tint/bug/tint/992.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/992.wgsl.expected.wgsl
rename to test/tint/bug/tint/992.wgsl.expected.wgsl
diff --git a/test/bug/tint/993.wgsl b/test/tint/bug/tint/993.wgsl
similarity index 100%
rename from test/bug/tint/993.wgsl
rename to test/tint/bug/tint/993.wgsl
diff --git a/test/bug/tint/993.wgsl.expected.glsl b/test/tint/bug/tint/993.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/993.wgsl.expected.glsl
rename to test/tint/bug/tint/993.wgsl.expected.glsl
diff --git a/test/bug/tint/993.wgsl.expected.hlsl b/test/tint/bug/tint/993.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/993.wgsl.expected.hlsl
rename to test/tint/bug/tint/993.wgsl.expected.hlsl
diff --git a/test/bug/tint/993.wgsl.expected.msl b/test/tint/bug/tint/993.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/993.wgsl.expected.msl
rename to test/tint/bug/tint/993.wgsl.expected.msl
diff --git a/test/bug/tint/993.wgsl.expected.spvasm b/test/tint/bug/tint/993.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/993.wgsl.expected.spvasm
rename to test/tint/bug/tint/993.wgsl.expected.spvasm
diff --git a/test/bug/tint/993.wgsl.expected.wgsl b/test/tint/bug/tint/993.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/993.wgsl.expected.wgsl
rename to test/tint/bug/tint/993.wgsl.expected.wgsl
diff --git a/test/bug/tint/998.wgsl b/test/tint/bug/tint/998.wgsl
similarity index 100%
rename from test/bug/tint/998.wgsl
rename to test/tint/bug/tint/998.wgsl
diff --git a/test/bug/tint/998.wgsl.expected.glsl b/test/tint/bug/tint/998.wgsl.expected.glsl
similarity index 100%
rename from test/bug/tint/998.wgsl.expected.glsl
rename to test/tint/bug/tint/998.wgsl.expected.glsl
diff --git a/test/bug/tint/998.wgsl.expected.hlsl b/test/tint/bug/tint/998.wgsl.expected.hlsl
similarity index 100%
rename from test/bug/tint/998.wgsl.expected.hlsl
rename to test/tint/bug/tint/998.wgsl.expected.hlsl
diff --git a/test/bug/tint/998.wgsl.expected.msl b/test/tint/bug/tint/998.wgsl.expected.msl
similarity index 100%
rename from test/bug/tint/998.wgsl.expected.msl
rename to test/tint/bug/tint/998.wgsl.expected.msl
diff --git a/test/bug/tint/998.wgsl.expected.spvasm b/test/tint/bug/tint/998.wgsl.expected.spvasm
similarity index 100%
rename from test/bug/tint/998.wgsl.expected.spvasm
rename to test/tint/bug/tint/998.wgsl.expected.spvasm
diff --git a/test/bug/tint/998.wgsl.expected.wgsl b/test/tint/bug/tint/998.wgsl.expected.wgsl
similarity index 100%
rename from test/bug/tint/998.wgsl.expected.wgsl
rename to test/tint/bug/tint/998.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl b/test/tint/builtins/arrayLength/complex_via_let.wgsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl.expected.glsl b/test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl.expected.msl b/test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/complex_via_let.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/complex_via_let.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.glsl b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.msl b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/deprecated.wgsl b/test/tint/builtins/arrayLength/deprecated.wgsl
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl
rename to test/tint/builtins/arrayLength/deprecated.wgsl
diff --git a/test/builtins/arrayLength/deprecated.wgsl.expected.glsl b/test/tint/builtins/arrayLength/deprecated.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/deprecated.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/deprecated.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/deprecated.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/deprecated.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/deprecated.wgsl.expected.msl b/test/tint/builtins/arrayLength/deprecated.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/deprecated.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/deprecated.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/deprecated.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/deprecated.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/deprecated.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/deprecated.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/deprecated.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/deprecated.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/simple.wgsl b/test/tint/builtins/arrayLength/simple.wgsl
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl
rename to test/tint/builtins/arrayLength/simple.wgsl
diff --git a/test/builtins/arrayLength/simple.wgsl.expected.glsl b/test/tint/builtins/arrayLength/simple.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/simple.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/simple.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/simple.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/simple.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/simple.wgsl.expected.msl b/test/tint/builtins/arrayLength/simple.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/simple.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/simple.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/simple.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/simple.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/simple.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/simple.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/simple.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/simple.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl b/test/tint/builtins/arrayLength/simple_no_struct.wgsl
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl.expected.glsl b/test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl.expected.msl b/test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/simple_no_struct.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/simple_no_struct.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/simple_no_struct.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/via_let.wgsl b/test/tint/builtins/arrayLength/via_let.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl
rename to test/tint/builtins/arrayLength/via_let.wgsl
diff --git a/test/builtins/arrayLength/via_let.wgsl.expected.glsl b/test/tint/builtins/arrayLength/via_let.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/via_let.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/via_let.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/via_let.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/via_let.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/via_let.wgsl.expected.msl b/test/tint/builtins/arrayLength/via_let.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/via_let.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/via_let.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/via_let.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/via_let.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/via_let.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/via_let.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/via_let.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl b/test/tint/builtins/arrayLength/via_let_complex.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl.expected.glsl b/test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl.expected.msl b/test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/via_let_complex.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/via_let_complex.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.glsl b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.msl b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl.expected.glsl b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl.expected.glsl
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.glsl
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl.expected.hlsl b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl.expected.hlsl
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.hlsl
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl.expected.msl b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.msl
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl.expected.msl
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.msl
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl.expected.spvasm b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl.expected.spvasm
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.spvasm
diff --git a/test/builtins/arrayLength/via_let_no_struct.wgsl.expected.wgsl b/test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/arrayLength/via_let_no_struct.wgsl.expected.wgsl
rename to test/tint/builtins/arrayLength/via_let_no_struct.wgsl.expected.wgsl
diff --git a/test/builtins/builtins.wgsl.tmpl b/test/tint/builtins/builtins.wgsl.tmpl
similarity index 100%
rename from test/builtins/builtins.wgsl.tmpl
rename to test/tint/builtins/builtins.wgsl.tmpl
diff --git a/test/builtins/degrees.spvasm b/test/tint/builtins/degrees.spvasm
similarity index 100%
rename from test/builtins/degrees.spvasm
rename to test/tint/builtins/degrees.spvasm
diff --git a/test/builtins/degrees.spvasm.expected.glsl b/test/tint/builtins/degrees.spvasm.expected.glsl
similarity index 100%
rename from test/builtins/degrees.spvasm.expected.glsl
rename to test/tint/builtins/degrees.spvasm.expected.glsl
diff --git a/test/builtins/degrees.spvasm.expected.hlsl b/test/tint/builtins/degrees.spvasm.expected.hlsl
similarity index 100%
rename from test/builtins/degrees.spvasm.expected.hlsl
rename to test/tint/builtins/degrees.spvasm.expected.hlsl
diff --git a/test/builtins/degrees.spvasm.expected.msl b/test/tint/builtins/degrees.spvasm.expected.msl
similarity index 100%
rename from test/builtins/degrees.spvasm.expected.msl
rename to test/tint/builtins/degrees.spvasm.expected.msl
diff --git a/test/builtins/degrees.spvasm.expected.spvasm b/test/tint/builtins/degrees.spvasm.expected.spvasm
similarity index 100%
rename from test/builtins/degrees.spvasm.expected.spvasm
rename to test/tint/builtins/degrees.spvasm.expected.spvasm
diff --git a/test/builtins/degrees.spvasm.expected.wgsl b/test/tint/builtins/degrees.spvasm.expected.wgsl
similarity index 100%
rename from test/builtins/degrees.spvasm.expected.wgsl
rename to test/tint/builtins/degrees.spvasm.expected.wgsl
diff --git a/test/builtins/frexp.wgsl b/test/tint/builtins/frexp.wgsl
similarity index 100%
rename from test/builtins/frexp.wgsl
rename to test/tint/builtins/frexp.wgsl
diff --git a/test/builtins/frexp.wgsl.expected.glsl b/test/tint/builtins/frexp.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/frexp.wgsl.expected.glsl
rename to test/tint/builtins/frexp.wgsl.expected.glsl
diff --git a/test/builtins/frexp.wgsl.expected.hlsl b/test/tint/builtins/frexp.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/frexp.wgsl.expected.hlsl
rename to test/tint/builtins/frexp.wgsl.expected.hlsl
diff --git a/test/builtins/frexp.wgsl.expected.msl b/test/tint/builtins/frexp.wgsl.expected.msl
similarity index 100%
rename from test/builtins/frexp.wgsl.expected.msl
rename to test/tint/builtins/frexp.wgsl.expected.msl
diff --git a/test/builtins/frexp.wgsl.expected.spvasm b/test/tint/builtins/frexp.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/frexp.wgsl.expected.spvasm
rename to test/tint/builtins/frexp.wgsl.expected.spvasm
diff --git a/test/builtins/frexp.wgsl.expected.wgsl b/test/tint/builtins/frexp.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/frexp.wgsl.expected.wgsl
rename to test/tint/builtins/frexp.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/002533.wgsl b/test/tint/builtins/gen/abs/002533.wgsl
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl
rename to test/tint/builtins/gen/abs/002533.wgsl
diff --git a/test/builtins/gen/abs/002533.wgsl.expected.glsl b/test/tint/builtins/gen/abs/002533.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/002533.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/002533.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/002533.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/002533.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/002533.wgsl.expected.msl b/test/tint/builtins/gen/abs/002533.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/002533.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/002533.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/002533.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/002533.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/002533.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/002533.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/002533.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/002533.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/005174.wgsl b/test/tint/builtins/gen/abs/005174.wgsl
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl
rename to test/tint/builtins/gen/abs/005174.wgsl
diff --git a/test/builtins/gen/abs/005174.wgsl.expected.glsl b/test/tint/builtins/gen/abs/005174.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/005174.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/005174.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/005174.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/005174.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/005174.wgsl.expected.msl b/test/tint/builtins/gen/abs/005174.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/005174.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/005174.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/005174.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/005174.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/005174.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/005174.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/005174.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/005174.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/1ce782.wgsl b/test/tint/builtins/gen/abs/1ce782.wgsl
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl
rename to test/tint/builtins/gen/abs/1ce782.wgsl
diff --git a/test/builtins/gen/abs/1ce782.wgsl.expected.glsl b/test/tint/builtins/gen/abs/1ce782.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/1ce782.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/1ce782.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/1ce782.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/1ce782.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/1ce782.wgsl.expected.msl b/test/tint/builtins/gen/abs/1ce782.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/1ce782.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/1ce782.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/1ce782.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/1ce782.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/1ce782.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/1ce782.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/1ce782.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/1ce782.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/1e9d53.wgsl b/test/tint/builtins/gen/abs/1e9d53.wgsl
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl
rename to test/tint/builtins/gen/abs/1e9d53.wgsl
diff --git a/test/builtins/gen/abs/1e9d53.wgsl.expected.glsl b/test/tint/builtins/gen/abs/1e9d53.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/1e9d53.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/1e9d53.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/1e9d53.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/1e9d53.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/1e9d53.wgsl.expected.msl b/test/tint/builtins/gen/abs/1e9d53.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/1e9d53.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/1e9d53.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/1e9d53.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/1e9d53.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/1e9d53.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/1e9d53.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/1e9d53.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/1e9d53.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/467cd1.wgsl b/test/tint/builtins/gen/abs/467cd1.wgsl
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl
rename to test/tint/builtins/gen/abs/467cd1.wgsl
diff --git a/test/builtins/gen/abs/467cd1.wgsl.expected.glsl b/test/tint/builtins/gen/abs/467cd1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/467cd1.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/467cd1.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/467cd1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/467cd1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/467cd1.wgsl.expected.msl b/test/tint/builtins/gen/abs/467cd1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/467cd1.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/467cd1.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/467cd1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/467cd1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/467cd1.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/467cd1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/467cd1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/467cd1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/4ad288.wgsl b/test/tint/builtins/gen/abs/4ad288.wgsl
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl
rename to test/tint/builtins/gen/abs/4ad288.wgsl
diff --git a/test/builtins/gen/abs/4ad288.wgsl.expected.glsl b/test/tint/builtins/gen/abs/4ad288.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/4ad288.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/4ad288.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/4ad288.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/4ad288.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/4ad288.wgsl.expected.msl b/test/tint/builtins/gen/abs/4ad288.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/4ad288.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/4ad288.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/4ad288.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/4ad288.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/4ad288.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/4ad288.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/4ad288.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/4ad288.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/5ad50a.wgsl b/test/tint/builtins/gen/abs/5ad50a.wgsl
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl
rename to test/tint/builtins/gen/abs/5ad50a.wgsl
diff --git a/test/builtins/gen/abs/5ad50a.wgsl.expected.glsl b/test/tint/builtins/gen/abs/5ad50a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/5ad50a.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/5ad50a.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/5ad50a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/5ad50a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/5ad50a.wgsl.expected.msl b/test/tint/builtins/gen/abs/5ad50a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/5ad50a.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/5ad50a.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/5ad50a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/5ad50a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/5ad50a.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/5ad50a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/5ad50a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/5ad50a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/7326de.wgsl b/test/tint/builtins/gen/abs/7326de.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl
rename to test/tint/builtins/gen/abs/7326de.wgsl
diff --git a/test/builtins/gen/abs/7326de.wgsl.expected.glsl b/test/tint/builtins/gen/abs/7326de.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/7326de.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/7326de.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/7326de.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/7326de.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/7326de.wgsl.expected.msl b/test/tint/builtins/gen/abs/7326de.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/7326de.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/7326de.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/7326de.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/7326de.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/7326de.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/7326de.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7326de.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/7326de.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/7f28e6.wgsl b/test/tint/builtins/gen/abs/7f28e6.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl
rename to test/tint/builtins/gen/abs/7f28e6.wgsl
diff --git a/test/builtins/gen/abs/7f28e6.wgsl.expected.glsl b/test/tint/builtins/gen/abs/7f28e6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/7f28e6.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/7f28e6.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/7f28e6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/7f28e6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/7f28e6.wgsl.expected.msl b/test/tint/builtins/gen/abs/7f28e6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/7f28e6.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/7f28e6.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/7f28e6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/7f28e6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/7f28e6.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/7f28e6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7f28e6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/7f28e6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/7faa9e.wgsl b/test/tint/builtins/gen/abs/7faa9e.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl
rename to test/tint/builtins/gen/abs/7faa9e.wgsl
diff --git a/test/builtins/gen/abs/7faa9e.wgsl.expected.glsl b/test/tint/builtins/gen/abs/7faa9e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/7faa9e.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/7faa9e.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/7faa9e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/7faa9e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/7faa9e.wgsl.expected.msl b/test/tint/builtins/gen/abs/7faa9e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/7faa9e.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/7faa9e.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/7faa9e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/7faa9e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/7faa9e.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/7faa9e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/7faa9e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/7faa9e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/9c80a6.wgsl b/test/tint/builtins/gen/abs/9c80a6.wgsl
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl
rename to test/tint/builtins/gen/abs/9c80a6.wgsl
diff --git a/test/builtins/gen/abs/9c80a6.wgsl.expected.glsl b/test/tint/builtins/gen/abs/9c80a6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/9c80a6.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/9c80a6.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/9c80a6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/9c80a6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/9c80a6.wgsl.expected.msl b/test/tint/builtins/gen/abs/9c80a6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/9c80a6.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/9c80a6.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/9c80a6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/9c80a6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/9c80a6.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/9c80a6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/9c80a6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/9c80a6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/abs/b96037.wgsl b/test/tint/builtins/gen/abs/b96037.wgsl
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl
rename to test/tint/builtins/gen/abs/b96037.wgsl
diff --git a/test/builtins/gen/abs/b96037.wgsl.expected.glsl b/test/tint/builtins/gen/abs/b96037.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl.expected.glsl
rename to test/tint/builtins/gen/abs/b96037.wgsl.expected.glsl
diff --git a/test/builtins/gen/abs/b96037.wgsl.expected.hlsl b/test/tint/builtins/gen/abs/b96037.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl.expected.hlsl
rename to test/tint/builtins/gen/abs/b96037.wgsl.expected.hlsl
diff --git a/test/builtins/gen/abs/b96037.wgsl.expected.msl b/test/tint/builtins/gen/abs/b96037.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl.expected.msl
rename to test/tint/builtins/gen/abs/b96037.wgsl.expected.msl
diff --git a/test/builtins/gen/abs/b96037.wgsl.expected.spvasm b/test/tint/builtins/gen/abs/b96037.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl.expected.spvasm
rename to test/tint/builtins/gen/abs/b96037.wgsl.expected.spvasm
diff --git a/test/builtins/gen/abs/b96037.wgsl.expected.wgsl b/test/tint/builtins/gen/abs/b96037.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/abs/b96037.wgsl.expected.wgsl
rename to test/tint/builtins/gen/abs/b96037.wgsl.expected.wgsl
diff --git a/test/builtins/gen/acos/489247.wgsl b/test/tint/builtins/gen/acos/489247.wgsl
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl
rename to test/tint/builtins/gen/acos/489247.wgsl
diff --git a/test/builtins/gen/acos/489247.wgsl.expected.glsl b/test/tint/builtins/gen/acos/489247.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl.expected.glsl
rename to test/tint/builtins/gen/acos/489247.wgsl.expected.glsl
diff --git a/test/builtins/gen/acos/489247.wgsl.expected.hlsl b/test/tint/builtins/gen/acos/489247.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl.expected.hlsl
rename to test/tint/builtins/gen/acos/489247.wgsl.expected.hlsl
diff --git a/test/builtins/gen/acos/489247.wgsl.expected.msl b/test/tint/builtins/gen/acos/489247.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl.expected.msl
rename to test/tint/builtins/gen/acos/489247.wgsl.expected.msl
diff --git a/test/builtins/gen/acos/489247.wgsl.expected.spvasm b/test/tint/builtins/gen/acos/489247.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl.expected.spvasm
rename to test/tint/builtins/gen/acos/489247.wgsl.expected.spvasm
diff --git a/test/builtins/gen/acos/489247.wgsl.expected.wgsl b/test/tint/builtins/gen/acos/489247.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/acos/489247.wgsl.expected.wgsl
rename to test/tint/builtins/gen/acos/489247.wgsl.expected.wgsl
diff --git a/test/builtins/gen/acos/8e2acf.wgsl b/test/tint/builtins/gen/acos/8e2acf.wgsl
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl
rename to test/tint/builtins/gen/acos/8e2acf.wgsl
diff --git a/test/builtins/gen/acos/8e2acf.wgsl.expected.glsl b/test/tint/builtins/gen/acos/8e2acf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl.expected.glsl
rename to test/tint/builtins/gen/acos/8e2acf.wgsl.expected.glsl
diff --git a/test/builtins/gen/acos/8e2acf.wgsl.expected.hlsl b/test/tint/builtins/gen/acos/8e2acf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/acos/8e2acf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/acos/8e2acf.wgsl.expected.msl b/test/tint/builtins/gen/acos/8e2acf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl.expected.msl
rename to test/tint/builtins/gen/acos/8e2acf.wgsl.expected.msl
diff --git a/test/builtins/gen/acos/8e2acf.wgsl.expected.spvasm b/test/tint/builtins/gen/acos/8e2acf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/acos/8e2acf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/acos/8e2acf.wgsl.expected.wgsl b/test/tint/builtins/gen/acos/8e2acf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/acos/8e2acf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/acos/8e2acf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/acos/a610c4.wgsl b/test/tint/builtins/gen/acos/a610c4.wgsl
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl
rename to test/tint/builtins/gen/acos/a610c4.wgsl
diff --git a/test/builtins/gen/acos/a610c4.wgsl.expected.glsl b/test/tint/builtins/gen/acos/a610c4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl.expected.glsl
rename to test/tint/builtins/gen/acos/a610c4.wgsl.expected.glsl
diff --git a/test/builtins/gen/acos/a610c4.wgsl.expected.hlsl b/test/tint/builtins/gen/acos/a610c4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/acos/a610c4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/acos/a610c4.wgsl.expected.msl b/test/tint/builtins/gen/acos/a610c4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl.expected.msl
rename to test/tint/builtins/gen/acos/a610c4.wgsl.expected.msl
diff --git a/test/builtins/gen/acos/a610c4.wgsl.expected.spvasm b/test/tint/builtins/gen/acos/a610c4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/acos/a610c4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/acos/a610c4.wgsl.expected.wgsl b/test/tint/builtins/gen/acos/a610c4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/acos/a610c4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/acos/a610c4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/acos/dfc915.wgsl b/test/tint/builtins/gen/acos/dfc915.wgsl
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl
rename to test/tint/builtins/gen/acos/dfc915.wgsl
diff --git a/test/builtins/gen/acos/dfc915.wgsl.expected.glsl b/test/tint/builtins/gen/acos/dfc915.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl.expected.glsl
rename to test/tint/builtins/gen/acos/dfc915.wgsl.expected.glsl
diff --git a/test/builtins/gen/acos/dfc915.wgsl.expected.hlsl b/test/tint/builtins/gen/acos/dfc915.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl.expected.hlsl
rename to test/tint/builtins/gen/acos/dfc915.wgsl.expected.hlsl
diff --git a/test/builtins/gen/acos/dfc915.wgsl.expected.msl b/test/tint/builtins/gen/acos/dfc915.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl.expected.msl
rename to test/tint/builtins/gen/acos/dfc915.wgsl.expected.msl
diff --git a/test/builtins/gen/acos/dfc915.wgsl.expected.spvasm b/test/tint/builtins/gen/acos/dfc915.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl.expected.spvasm
rename to test/tint/builtins/gen/acos/dfc915.wgsl.expected.spvasm
diff --git a/test/builtins/gen/acos/dfc915.wgsl.expected.wgsl b/test/tint/builtins/gen/acos/dfc915.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/acos/dfc915.wgsl.expected.wgsl
rename to test/tint/builtins/gen/acos/dfc915.wgsl.expected.wgsl
diff --git a/test/builtins/gen/all/353d6a.wgsl b/test/tint/builtins/gen/all/353d6a.wgsl
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl
rename to test/tint/builtins/gen/all/353d6a.wgsl
diff --git a/test/builtins/gen/all/353d6a.wgsl.expected.glsl b/test/tint/builtins/gen/all/353d6a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl.expected.glsl
rename to test/tint/builtins/gen/all/353d6a.wgsl.expected.glsl
diff --git a/test/builtins/gen/all/353d6a.wgsl.expected.hlsl b/test/tint/builtins/gen/all/353d6a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/all/353d6a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/all/353d6a.wgsl.expected.msl b/test/tint/builtins/gen/all/353d6a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl.expected.msl
rename to test/tint/builtins/gen/all/353d6a.wgsl.expected.msl
diff --git a/test/builtins/gen/all/353d6a.wgsl.expected.spvasm b/test/tint/builtins/gen/all/353d6a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/all/353d6a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/all/353d6a.wgsl.expected.wgsl b/test/tint/builtins/gen/all/353d6a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/all/353d6a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/all/353d6a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/all/986c7b.wgsl b/test/tint/builtins/gen/all/986c7b.wgsl
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl
rename to test/tint/builtins/gen/all/986c7b.wgsl
diff --git a/test/builtins/gen/all/986c7b.wgsl.expected.glsl b/test/tint/builtins/gen/all/986c7b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl.expected.glsl
rename to test/tint/builtins/gen/all/986c7b.wgsl.expected.glsl
diff --git a/test/builtins/gen/all/986c7b.wgsl.expected.hlsl b/test/tint/builtins/gen/all/986c7b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/all/986c7b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/all/986c7b.wgsl.expected.msl b/test/tint/builtins/gen/all/986c7b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl.expected.msl
rename to test/tint/builtins/gen/all/986c7b.wgsl.expected.msl
diff --git a/test/builtins/gen/all/986c7b.wgsl.expected.spvasm b/test/tint/builtins/gen/all/986c7b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/all/986c7b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/all/986c7b.wgsl.expected.wgsl b/test/tint/builtins/gen/all/986c7b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/all/986c7b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/all/986c7b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/all/bd2dba.wgsl b/test/tint/builtins/gen/all/bd2dba.wgsl
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl
rename to test/tint/builtins/gen/all/bd2dba.wgsl
diff --git a/test/builtins/gen/all/bd2dba.wgsl.expected.glsl b/test/tint/builtins/gen/all/bd2dba.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl.expected.glsl
rename to test/tint/builtins/gen/all/bd2dba.wgsl.expected.glsl
diff --git a/test/builtins/gen/all/bd2dba.wgsl.expected.hlsl b/test/tint/builtins/gen/all/bd2dba.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl.expected.hlsl
rename to test/tint/builtins/gen/all/bd2dba.wgsl.expected.hlsl
diff --git a/test/builtins/gen/all/bd2dba.wgsl.expected.msl b/test/tint/builtins/gen/all/bd2dba.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl.expected.msl
rename to test/tint/builtins/gen/all/bd2dba.wgsl.expected.msl
diff --git a/test/builtins/gen/all/bd2dba.wgsl.expected.spvasm b/test/tint/builtins/gen/all/bd2dba.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl.expected.spvasm
rename to test/tint/builtins/gen/all/bd2dba.wgsl.expected.spvasm
diff --git a/test/builtins/gen/all/bd2dba.wgsl.expected.wgsl b/test/tint/builtins/gen/all/bd2dba.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/all/bd2dba.wgsl.expected.wgsl
rename to test/tint/builtins/gen/all/bd2dba.wgsl.expected.wgsl
diff --git a/test/builtins/gen/all/f46790.wgsl b/test/tint/builtins/gen/all/f46790.wgsl
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl
rename to test/tint/builtins/gen/all/f46790.wgsl
diff --git a/test/builtins/gen/all/f46790.wgsl.expected.glsl b/test/tint/builtins/gen/all/f46790.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl.expected.glsl
rename to test/tint/builtins/gen/all/f46790.wgsl.expected.glsl
diff --git a/test/builtins/gen/all/f46790.wgsl.expected.hlsl b/test/tint/builtins/gen/all/f46790.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl.expected.hlsl
rename to test/tint/builtins/gen/all/f46790.wgsl.expected.hlsl
diff --git a/test/builtins/gen/all/f46790.wgsl.expected.msl b/test/tint/builtins/gen/all/f46790.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl.expected.msl
rename to test/tint/builtins/gen/all/f46790.wgsl.expected.msl
diff --git a/test/builtins/gen/all/f46790.wgsl.expected.spvasm b/test/tint/builtins/gen/all/f46790.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl.expected.spvasm
rename to test/tint/builtins/gen/all/f46790.wgsl.expected.spvasm
diff --git a/test/builtins/gen/all/f46790.wgsl.expected.wgsl b/test/tint/builtins/gen/all/f46790.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/all/f46790.wgsl.expected.wgsl
rename to test/tint/builtins/gen/all/f46790.wgsl.expected.wgsl
diff --git a/test/builtins/gen/any/083428.wgsl b/test/tint/builtins/gen/any/083428.wgsl
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl
rename to test/tint/builtins/gen/any/083428.wgsl
diff --git a/test/builtins/gen/any/083428.wgsl.expected.glsl b/test/tint/builtins/gen/any/083428.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl.expected.glsl
rename to test/tint/builtins/gen/any/083428.wgsl.expected.glsl
diff --git a/test/builtins/gen/any/083428.wgsl.expected.hlsl b/test/tint/builtins/gen/any/083428.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl.expected.hlsl
rename to test/tint/builtins/gen/any/083428.wgsl.expected.hlsl
diff --git a/test/builtins/gen/any/083428.wgsl.expected.msl b/test/tint/builtins/gen/any/083428.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl.expected.msl
rename to test/tint/builtins/gen/any/083428.wgsl.expected.msl
diff --git a/test/builtins/gen/any/083428.wgsl.expected.spvasm b/test/tint/builtins/gen/any/083428.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl.expected.spvasm
rename to test/tint/builtins/gen/any/083428.wgsl.expected.spvasm
diff --git a/test/builtins/gen/any/083428.wgsl.expected.wgsl b/test/tint/builtins/gen/any/083428.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/any/083428.wgsl.expected.wgsl
rename to test/tint/builtins/gen/any/083428.wgsl.expected.wgsl
diff --git a/test/builtins/gen/any/0e3e58.wgsl b/test/tint/builtins/gen/any/0e3e58.wgsl
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl
rename to test/tint/builtins/gen/any/0e3e58.wgsl
diff --git a/test/builtins/gen/any/0e3e58.wgsl.expected.glsl b/test/tint/builtins/gen/any/0e3e58.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl.expected.glsl
rename to test/tint/builtins/gen/any/0e3e58.wgsl.expected.glsl
diff --git a/test/builtins/gen/any/0e3e58.wgsl.expected.hlsl b/test/tint/builtins/gen/any/0e3e58.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl.expected.hlsl
rename to test/tint/builtins/gen/any/0e3e58.wgsl.expected.hlsl
diff --git a/test/builtins/gen/any/0e3e58.wgsl.expected.msl b/test/tint/builtins/gen/any/0e3e58.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl.expected.msl
rename to test/tint/builtins/gen/any/0e3e58.wgsl.expected.msl
diff --git a/test/builtins/gen/any/0e3e58.wgsl.expected.spvasm b/test/tint/builtins/gen/any/0e3e58.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl.expected.spvasm
rename to test/tint/builtins/gen/any/0e3e58.wgsl.expected.spvasm
diff --git a/test/builtins/gen/any/0e3e58.wgsl.expected.wgsl b/test/tint/builtins/gen/any/0e3e58.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/any/0e3e58.wgsl.expected.wgsl
rename to test/tint/builtins/gen/any/0e3e58.wgsl.expected.wgsl
diff --git a/test/builtins/gen/any/2ab91a.wgsl b/test/tint/builtins/gen/any/2ab91a.wgsl
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl
rename to test/tint/builtins/gen/any/2ab91a.wgsl
diff --git a/test/builtins/gen/any/2ab91a.wgsl.expected.glsl b/test/tint/builtins/gen/any/2ab91a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl.expected.glsl
rename to test/tint/builtins/gen/any/2ab91a.wgsl.expected.glsl
diff --git a/test/builtins/gen/any/2ab91a.wgsl.expected.hlsl b/test/tint/builtins/gen/any/2ab91a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/any/2ab91a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/any/2ab91a.wgsl.expected.msl b/test/tint/builtins/gen/any/2ab91a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl.expected.msl
rename to test/tint/builtins/gen/any/2ab91a.wgsl.expected.msl
diff --git a/test/builtins/gen/any/2ab91a.wgsl.expected.spvasm b/test/tint/builtins/gen/any/2ab91a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/any/2ab91a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/any/2ab91a.wgsl.expected.wgsl b/test/tint/builtins/gen/any/2ab91a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/any/2ab91a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/any/2ab91a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/any/e755c1.wgsl b/test/tint/builtins/gen/any/e755c1.wgsl
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl
rename to test/tint/builtins/gen/any/e755c1.wgsl
diff --git a/test/builtins/gen/any/e755c1.wgsl.expected.glsl b/test/tint/builtins/gen/any/e755c1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl.expected.glsl
rename to test/tint/builtins/gen/any/e755c1.wgsl.expected.glsl
diff --git a/test/builtins/gen/any/e755c1.wgsl.expected.hlsl b/test/tint/builtins/gen/any/e755c1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/any/e755c1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/any/e755c1.wgsl.expected.msl b/test/tint/builtins/gen/any/e755c1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl.expected.msl
rename to test/tint/builtins/gen/any/e755c1.wgsl.expected.msl
diff --git a/test/builtins/gen/any/e755c1.wgsl.expected.spvasm b/test/tint/builtins/gen/any/e755c1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/any/e755c1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/any/e755c1.wgsl.expected.wgsl b/test/tint/builtins/gen/any/e755c1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/any/e755c1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/any/e755c1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl b/test/tint/builtins/gen/arrayLength/1588cd.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/1588cd.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/1588cd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/1588cd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/61b1c7.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/61b1c7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/61b1c7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/a0f5ca.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/a0f5ca.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl b/test/tint/builtins/gen/arrayLength/cdd123.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/cdd123.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cdd123.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/cdd123.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/cfca0a.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/cfca0a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/cfca0a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl b/test/tint/builtins/gen/arrayLength/eb510f.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl.expected.glsl b/test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl.expected.glsl
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.glsl
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl.expected.hlsl b/test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl.expected.msl b/test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl.expected.msl
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.msl
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl.expected.spvasm b/test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/arrayLength/eb510f.wgsl.expected.wgsl b/test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/arrayLength/eb510f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/arrayLength/eb510f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/asin/064953.wgsl b/test/tint/builtins/gen/asin/064953.wgsl
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl
rename to test/tint/builtins/gen/asin/064953.wgsl
diff --git a/test/builtins/gen/asin/064953.wgsl.expected.glsl b/test/tint/builtins/gen/asin/064953.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl.expected.glsl
rename to test/tint/builtins/gen/asin/064953.wgsl.expected.glsl
diff --git a/test/builtins/gen/asin/064953.wgsl.expected.hlsl b/test/tint/builtins/gen/asin/064953.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl.expected.hlsl
rename to test/tint/builtins/gen/asin/064953.wgsl.expected.hlsl
diff --git a/test/builtins/gen/asin/064953.wgsl.expected.msl b/test/tint/builtins/gen/asin/064953.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl.expected.msl
rename to test/tint/builtins/gen/asin/064953.wgsl.expected.msl
diff --git a/test/builtins/gen/asin/064953.wgsl.expected.spvasm b/test/tint/builtins/gen/asin/064953.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl.expected.spvasm
rename to test/tint/builtins/gen/asin/064953.wgsl.expected.spvasm
diff --git a/test/builtins/gen/asin/064953.wgsl.expected.wgsl b/test/tint/builtins/gen/asin/064953.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/asin/064953.wgsl.expected.wgsl
rename to test/tint/builtins/gen/asin/064953.wgsl.expected.wgsl
diff --git a/test/builtins/gen/asin/7b6a44.wgsl b/test/tint/builtins/gen/asin/7b6a44.wgsl
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl
rename to test/tint/builtins/gen/asin/7b6a44.wgsl
diff --git a/test/builtins/gen/asin/7b6a44.wgsl.expected.glsl b/test/tint/builtins/gen/asin/7b6a44.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl.expected.glsl
rename to test/tint/builtins/gen/asin/7b6a44.wgsl.expected.glsl
diff --git a/test/builtins/gen/asin/7b6a44.wgsl.expected.hlsl b/test/tint/builtins/gen/asin/7b6a44.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl.expected.hlsl
rename to test/tint/builtins/gen/asin/7b6a44.wgsl.expected.hlsl
diff --git a/test/builtins/gen/asin/7b6a44.wgsl.expected.msl b/test/tint/builtins/gen/asin/7b6a44.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl.expected.msl
rename to test/tint/builtins/gen/asin/7b6a44.wgsl.expected.msl
diff --git a/test/builtins/gen/asin/7b6a44.wgsl.expected.spvasm b/test/tint/builtins/gen/asin/7b6a44.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl.expected.spvasm
rename to test/tint/builtins/gen/asin/7b6a44.wgsl.expected.spvasm
diff --git a/test/builtins/gen/asin/7b6a44.wgsl.expected.wgsl b/test/tint/builtins/gen/asin/7b6a44.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/asin/7b6a44.wgsl.expected.wgsl
rename to test/tint/builtins/gen/asin/7b6a44.wgsl.expected.wgsl
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl b/test/tint/builtins/gen/asin/8cd9c9.wgsl
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl.expected.glsl b/test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl.expected.glsl
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.glsl
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl.expected.hlsl b/test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl.expected.msl b/test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl.expected.msl
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.msl
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl.expected.spvasm b/test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/asin/8cd9c9.wgsl.expected.wgsl b/test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/asin/8cd9c9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/asin/8cd9c9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/asin/c0c272.wgsl b/test/tint/builtins/gen/asin/c0c272.wgsl
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl
rename to test/tint/builtins/gen/asin/c0c272.wgsl
diff --git a/test/builtins/gen/asin/c0c272.wgsl.expected.glsl b/test/tint/builtins/gen/asin/c0c272.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl.expected.glsl
rename to test/tint/builtins/gen/asin/c0c272.wgsl.expected.glsl
diff --git a/test/builtins/gen/asin/c0c272.wgsl.expected.hlsl b/test/tint/builtins/gen/asin/c0c272.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl.expected.hlsl
rename to test/tint/builtins/gen/asin/c0c272.wgsl.expected.hlsl
diff --git a/test/builtins/gen/asin/c0c272.wgsl.expected.msl b/test/tint/builtins/gen/asin/c0c272.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl.expected.msl
rename to test/tint/builtins/gen/asin/c0c272.wgsl.expected.msl
diff --git a/test/builtins/gen/asin/c0c272.wgsl.expected.spvasm b/test/tint/builtins/gen/asin/c0c272.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl.expected.spvasm
rename to test/tint/builtins/gen/asin/c0c272.wgsl.expected.spvasm
diff --git a/test/builtins/gen/asin/c0c272.wgsl.expected.wgsl b/test/tint/builtins/gen/asin/c0c272.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/asin/c0c272.wgsl.expected.wgsl
rename to test/tint/builtins/gen/asin/c0c272.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan/02979a.wgsl b/test/tint/builtins/gen/atan/02979a.wgsl
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl
rename to test/tint/builtins/gen/atan/02979a.wgsl
diff --git a/test/builtins/gen/atan/02979a.wgsl.expected.glsl b/test/tint/builtins/gen/atan/02979a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan/02979a.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan/02979a.wgsl.expected.hlsl b/test/tint/builtins/gen/atan/02979a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan/02979a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan/02979a.wgsl.expected.msl b/test/tint/builtins/gen/atan/02979a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl.expected.msl
rename to test/tint/builtins/gen/atan/02979a.wgsl.expected.msl
diff --git a/test/builtins/gen/atan/02979a.wgsl.expected.spvasm b/test/tint/builtins/gen/atan/02979a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan/02979a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan/02979a.wgsl.expected.wgsl b/test/tint/builtins/gen/atan/02979a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan/02979a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan/02979a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan/331e6d.wgsl b/test/tint/builtins/gen/atan/331e6d.wgsl
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl
rename to test/tint/builtins/gen/atan/331e6d.wgsl
diff --git a/test/builtins/gen/atan/331e6d.wgsl.expected.glsl b/test/tint/builtins/gen/atan/331e6d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan/331e6d.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan/331e6d.wgsl.expected.hlsl b/test/tint/builtins/gen/atan/331e6d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan/331e6d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan/331e6d.wgsl.expected.msl b/test/tint/builtins/gen/atan/331e6d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl.expected.msl
rename to test/tint/builtins/gen/atan/331e6d.wgsl.expected.msl
diff --git a/test/builtins/gen/atan/331e6d.wgsl.expected.spvasm b/test/tint/builtins/gen/atan/331e6d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan/331e6d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan/331e6d.wgsl.expected.wgsl b/test/tint/builtins/gen/atan/331e6d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan/331e6d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan/331e6d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan/a8b696.wgsl b/test/tint/builtins/gen/atan/a8b696.wgsl
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl
rename to test/tint/builtins/gen/atan/a8b696.wgsl
diff --git a/test/builtins/gen/atan/a8b696.wgsl.expected.glsl b/test/tint/builtins/gen/atan/a8b696.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan/a8b696.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan/a8b696.wgsl.expected.hlsl b/test/tint/builtins/gen/atan/a8b696.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan/a8b696.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan/a8b696.wgsl.expected.msl b/test/tint/builtins/gen/atan/a8b696.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl.expected.msl
rename to test/tint/builtins/gen/atan/a8b696.wgsl.expected.msl
diff --git a/test/builtins/gen/atan/a8b696.wgsl.expected.spvasm b/test/tint/builtins/gen/atan/a8b696.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan/a8b696.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan/a8b696.wgsl.expected.wgsl b/test/tint/builtins/gen/atan/a8b696.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan/a8b696.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan/a8b696.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan/ad96e4.wgsl b/test/tint/builtins/gen/atan/ad96e4.wgsl
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl
rename to test/tint/builtins/gen/atan/ad96e4.wgsl
diff --git a/test/builtins/gen/atan/ad96e4.wgsl.expected.glsl b/test/tint/builtins/gen/atan/ad96e4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan/ad96e4.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan/ad96e4.wgsl.expected.hlsl b/test/tint/builtins/gen/atan/ad96e4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan/ad96e4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan/ad96e4.wgsl.expected.msl b/test/tint/builtins/gen/atan/ad96e4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl.expected.msl
rename to test/tint/builtins/gen/atan/ad96e4.wgsl.expected.msl
diff --git a/test/builtins/gen/atan/ad96e4.wgsl.expected.spvasm b/test/tint/builtins/gen/atan/ad96e4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan/ad96e4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan/ad96e4.wgsl.expected.wgsl b/test/tint/builtins/gen/atan/ad96e4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan/ad96e4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan/ad96e4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan2/57fb13.wgsl b/test/tint/builtins/gen/atan2/57fb13.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl
rename to test/tint/builtins/gen/atan2/57fb13.wgsl
diff --git a/test/builtins/gen/atan2/57fb13.wgsl.expected.glsl b/test/tint/builtins/gen/atan2/57fb13.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan2/57fb13.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan2/57fb13.wgsl.expected.hlsl b/test/tint/builtins/gen/atan2/57fb13.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan2/57fb13.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan2/57fb13.wgsl.expected.msl b/test/tint/builtins/gen/atan2/57fb13.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl.expected.msl
rename to test/tint/builtins/gen/atan2/57fb13.wgsl.expected.msl
diff --git a/test/builtins/gen/atan2/57fb13.wgsl.expected.spvasm b/test/tint/builtins/gen/atan2/57fb13.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan2/57fb13.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan2/57fb13.wgsl.expected.wgsl b/test/tint/builtins/gen/atan2/57fb13.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/57fb13.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan2/57fb13.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan2/96057c.wgsl b/test/tint/builtins/gen/atan2/96057c.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl
rename to test/tint/builtins/gen/atan2/96057c.wgsl
diff --git a/test/builtins/gen/atan2/96057c.wgsl.expected.glsl b/test/tint/builtins/gen/atan2/96057c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan2/96057c.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan2/96057c.wgsl.expected.hlsl b/test/tint/builtins/gen/atan2/96057c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan2/96057c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan2/96057c.wgsl.expected.msl b/test/tint/builtins/gen/atan2/96057c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl.expected.msl
rename to test/tint/builtins/gen/atan2/96057c.wgsl.expected.msl
diff --git a/test/builtins/gen/atan2/96057c.wgsl.expected.spvasm b/test/tint/builtins/gen/atan2/96057c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan2/96057c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan2/96057c.wgsl.expected.wgsl b/test/tint/builtins/gen/atan2/96057c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/96057c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan2/96057c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl b/test/tint/builtins/gen/atan2/a70d0d.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl.expected.glsl b/test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl.expected.hlsl b/test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl.expected.msl b/test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl.expected.msl
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.msl
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl.expected.spvasm b/test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan2/a70d0d.wgsl.expected.wgsl b/test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/a70d0d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan2/a70d0d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atan2/ae713e.wgsl b/test/tint/builtins/gen/atan2/ae713e.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl
rename to test/tint/builtins/gen/atan2/ae713e.wgsl
diff --git a/test/builtins/gen/atan2/ae713e.wgsl.expected.glsl b/test/tint/builtins/gen/atan2/ae713e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl.expected.glsl
rename to test/tint/builtins/gen/atan2/ae713e.wgsl.expected.glsl
diff --git a/test/builtins/gen/atan2/ae713e.wgsl.expected.hlsl b/test/tint/builtins/gen/atan2/ae713e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atan2/ae713e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atan2/ae713e.wgsl.expected.msl b/test/tint/builtins/gen/atan2/ae713e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl.expected.msl
rename to test/tint/builtins/gen/atan2/ae713e.wgsl.expected.msl
diff --git a/test/builtins/gen/atan2/ae713e.wgsl.expected.spvasm b/test/tint/builtins/gen/atan2/ae713e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atan2/ae713e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atan2/ae713e.wgsl.expected.wgsl b/test/tint/builtins/gen/atan2/ae713e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atan2/ae713e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atan2/ae713e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl b/test/tint/builtins/gen/atomicAdd/794055.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl.expected.msl b/test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAdd/794055.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/794055.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAdd/794055.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl.expected.msl b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAdd/8a199a.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/8a199a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAdd/8a199a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.msl b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d32fe4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAdd/d32fe4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.msl b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAdd/d5db1d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAdd/d5db1d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl b/test/tint/builtins/gen/atomicAnd/152966.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl.expected.msl b/test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAnd/152966.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/152966.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAnd/152966.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl.expected.msl b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAnd/34edd3.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/34edd3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAnd/34edd3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl b/test/tint/builtins/gen/atomicAnd/45a819.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl.expected.msl b/test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAnd/45a819.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/45a819.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAnd/45a819.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.glsl b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.msl b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicAnd/85a8d9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicAnd/85a8d9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.glsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.msl b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/12871c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.glsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.msl b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/6673da.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.glsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.msl b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/89ea3b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.glsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.msl b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicCompareExchangeWeak/b2ab2c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.glsl b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.msl b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/0a5dca.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicExchange/0a5dca.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl b/test/tint/builtins/gen/atomicExchange/d59712.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl.expected.glsl b/test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl.expected.msl b/test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicExchange/d59712.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/d59712.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicExchange/d59712.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl.expected.glsl b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl.expected.msl b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicExchange/e114ba.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/e114ba.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicExchange/e114ba.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.glsl b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.msl b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicExchange/f2e22f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicExchange/f2e22f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl.expected.glsl b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl.expected.msl b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicLoad/0806ad.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/0806ad.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicLoad/0806ad.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl.expected.glsl b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl.expected.msl b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicLoad/361bf1.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/361bf1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicLoad/361bf1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl.expected.glsl b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl.expected.msl b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicLoad/afcc03.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/afcc03.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicLoad/afcc03.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.glsl b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.msl b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicLoad/fe6cc3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl b/test/tint/builtins/gen/atomicMax/51b9be.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl.expected.msl b/test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMax/51b9be.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/51b9be.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMax/51b9be.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl b/test/tint/builtins/gen/atomicMax/92aa72.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl.expected.msl b/test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMax/92aa72.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/92aa72.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMax/92aa72.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl.expected.msl b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMax/a89cc3.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/a89cc3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMax/a89cc3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl b/test/tint/builtins/gen/atomicMax/beccfc.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl.expected.msl b/test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMax/beccfc.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMax/beccfc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMax/beccfc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMin/278235.wgsl b/test/tint/builtins/gen/atomicMin/278235.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl
rename to test/tint/builtins/gen/atomicMin/278235.wgsl
diff --git a/test/builtins/gen/atomicMin/278235.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMin/278235.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMin/278235.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMin/278235.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMin/278235.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMin/278235.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMin/278235.wgsl.expected.msl b/test/tint/builtins/gen/atomicMin/278235.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMin/278235.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMin/278235.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMin/278235.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMin/278235.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMin/278235.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMin/278235.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/278235.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMin/278235.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl b/test/tint/builtins/gen/atomicMin/69d383.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl.expected.msl b/test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMin/69d383.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/69d383.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMin/69d383.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl.expected.msl b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMin/8e38dc.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/8e38dc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMin/8e38dc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl b/test/tint/builtins/gen/atomicMin/c67a74.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl.expected.glsl b/test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl.expected.msl b/test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicMin/c67a74.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicMin/c67a74.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicMin/c67a74.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl.expected.glsl b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl.expected.msl b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicOr/5e3d61.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e3d61.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicOr/5e3d61.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl.expected.glsl b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl.expected.msl b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicOr/5e95d4.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/5e95d4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicOr/5e95d4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl.expected.glsl b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl.expected.msl b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicOr/8d96a0.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/8d96a0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicOr/8d96a0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl b/test/tint/builtins/gen/atomicOr/d09248.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl.expected.glsl b/test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl.expected.msl b/test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicOr/d09248.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicOr/d09248.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicOr/d09248.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicStore/726882.wgsl b/test/tint/builtins/gen/atomicStore/726882.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl
rename to test/tint/builtins/gen/atomicStore/726882.wgsl
diff --git a/test/builtins/gen/atomicStore/726882.wgsl.expected.glsl b/test/tint/builtins/gen/atomicStore/726882.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicStore/726882.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicStore/726882.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicStore/726882.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicStore/726882.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicStore/726882.wgsl.expected.msl b/test/tint/builtins/gen/atomicStore/726882.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicStore/726882.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicStore/726882.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicStore/726882.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicStore/726882.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicStore/726882.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicStore/726882.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/726882.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicStore/726882.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl b/test/tint/builtins/gen/atomicStore/8bea94.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl.expected.glsl b/test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl.expected.msl b/test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicStore/8bea94.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/8bea94.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicStore/8bea94.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl.expected.glsl b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl.expected.msl b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicStore/cdc29e.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/cdc29e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicStore/cdc29e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.glsl b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.msl b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicStore/d1e9a6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicStore/d1e9a6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicSub/051100.wgsl b/test/tint/builtins/gen/atomicSub/051100.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl
rename to test/tint/builtins/gen/atomicSub/051100.wgsl
diff --git a/test/builtins/gen/atomicSub/051100.wgsl.expected.glsl b/test/tint/builtins/gen/atomicSub/051100.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicSub/051100.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicSub/051100.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicSub/051100.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicSub/051100.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicSub/051100.wgsl.expected.msl b/test/tint/builtins/gen/atomicSub/051100.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicSub/051100.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicSub/051100.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicSub/051100.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicSub/051100.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicSub/051100.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicSub/051100.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/051100.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicSub/051100.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl.expected.glsl b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl.expected.msl b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicSub/0d26c2.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/0d26c2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicSub/0d26c2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl.expected.glsl b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl.expected.msl b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicSub/15bfc9.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/15bfc9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicSub/15bfc9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl b/test/tint/builtins/gen/atomicSub/77883a.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl.expected.glsl b/test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl.expected.msl b/test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicSub/77883a.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicSub/77883a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicSub/77883a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl b/test/tint/builtins/gen/atomicXor/54510e.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl.expected.glsl b/test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl.expected.msl b/test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicXor/54510e.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/54510e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicXor/54510e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl b/test/tint/builtins/gen/atomicXor/75dc95.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl.expected.glsl b/test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl.expected.msl b/test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicXor/75dc95.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/75dc95.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicXor/75dc95.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl.expected.glsl b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl.expected.msl b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicXor/c1b78c.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c1b78c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicXor/c1b78c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl.expected.glsl b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl.expected.glsl
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.glsl
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl.expected.hlsl b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl.expected.hlsl
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.hlsl
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl.expected.msl b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl.expected.msl
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.msl
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl.expected.spvasm b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl.expected.spvasm
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.spvasm
diff --git a/test/builtins/gen/atomicXor/c8e6be.wgsl.expected.wgsl b/test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/atomicXor/c8e6be.wgsl.expected.wgsl
rename to test/tint/builtins/gen/atomicXor/c8e6be.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ceil/34064b.wgsl b/test/tint/builtins/gen/ceil/34064b.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl
rename to test/tint/builtins/gen/ceil/34064b.wgsl
diff --git a/test/builtins/gen/ceil/34064b.wgsl.expected.glsl b/test/tint/builtins/gen/ceil/34064b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl.expected.glsl
rename to test/tint/builtins/gen/ceil/34064b.wgsl.expected.glsl
diff --git a/test/builtins/gen/ceil/34064b.wgsl.expected.hlsl b/test/tint/builtins/gen/ceil/34064b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ceil/34064b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ceil/34064b.wgsl.expected.msl b/test/tint/builtins/gen/ceil/34064b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl.expected.msl
rename to test/tint/builtins/gen/ceil/34064b.wgsl.expected.msl
diff --git a/test/builtins/gen/ceil/34064b.wgsl.expected.spvasm b/test/tint/builtins/gen/ceil/34064b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ceil/34064b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ceil/34064b.wgsl.expected.wgsl b/test/tint/builtins/gen/ceil/34064b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/34064b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ceil/34064b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ceil/678655.wgsl b/test/tint/builtins/gen/ceil/678655.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl
rename to test/tint/builtins/gen/ceil/678655.wgsl
diff --git a/test/builtins/gen/ceil/678655.wgsl.expected.glsl b/test/tint/builtins/gen/ceil/678655.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl.expected.glsl
rename to test/tint/builtins/gen/ceil/678655.wgsl.expected.glsl
diff --git a/test/builtins/gen/ceil/678655.wgsl.expected.hlsl b/test/tint/builtins/gen/ceil/678655.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ceil/678655.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ceil/678655.wgsl.expected.msl b/test/tint/builtins/gen/ceil/678655.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl.expected.msl
rename to test/tint/builtins/gen/ceil/678655.wgsl.expected.msl
diff --git a/test/builtins/gen/ceil/678655.wgsl.expected.spvasm b/test/tint/builtins/gen/ceil/678655.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ceil/678655.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ceil/678655.wgsl.expected.wgsl b/test/tint/builtins/gen/ceil/678655.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/678655.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ceil/678655.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ceil/96f597.wgsl b/test/tint/builtins/gen/ceil/96f597.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl
rename to test/tint/builtins/gen/ceil/96f597.wgsl
diff --git a/test/builtins/gen/ceil/96f597.wgsl.expected.glsl b/test/tint/builtins/gen/ceil/96f597.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl.expected.glsl
rename to test/tint/builtins/gen/ceil/96f597.wgsl.expected.glsl
diff --git a/test/builtins/gen/ceil/96f597.wgsl.expected.hlsl b/test/tint/builtins/gen/ceil/96f597.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ceil/96f597.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ceil/96f597.wgsl.expected.msl b/test/tint/builtins/gen/ceil/96f597.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl.expected.msl
rename to test/tint/builtins/gen/ceil/96f597.wgsl.expected.msl
diff --git a/test/builtins/gen/ceil/96f597.wgsl.expected.spvasm b/test/tint/builtins/gen/ceil/96f597.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ceil/96f597.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ceil/96f597.wgsl.expected.wgsl b/test/tint/builtins/gen/ceil/96f597.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/96f597.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ceil/96f597.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ceil/b74c16.wgsl b/test/tint/builtins/gen/ceil/b74c16.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl
rename to test/tint/builtins/gen/ceil/b74c16.wgsl
diff --git a/test/builtins/gen/ceil/b74c16.wgsl.expected.glsl b/test/tint/builtins/gen/ceil/b74c16.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl.expected.glsl
rename to test/tint/builtins/gen/ceil/b74c16.wgsl.expected.glsl
diff --git a/test/builtins/gen/ceil/b74c16.wgsl.expected.hlsl b/test/tint/builtins/gen/ceil/b74c16.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ceil/b74c16.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ceil/b74c16.wgsl.expected.msl b/test/tint/builtins/gen/ceil/b74c16.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl.expected.msl
rename to test/tint/builtins/gen/ceil/b74c16.wgsl.expected.msl
diff --git a/test/builtins/gen/ceil/b74c16.wgsl.expected.spvasm b/test/tint/builtins/gen/ceil/b74c16.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ceil/b74c16.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ceil/b74c16.wgsl.expected.wgsl b/test/tint/builtins/gen/ceil/b74c16.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ceil/b74c16.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ceil/b74c16.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl b/test/tint/builtins/gen/clamp/0acf8f.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl.expected.msl b/test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/0acf8f.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/0acf8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/0acf8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl b/test/tint/builtins/gen/clamp/1a32e3.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl.expected.msl b/test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/1a32e3.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/1a32e3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/1a32e3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/2bd567.wgsl b/test/tint/builtins/gen/clamp/2bd567.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl
rename to test/tint/builtins/gen/clamp/2bd567.wgsl
diff --git a/test/builtins/gen/clamp/2bd567.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/2bd567.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/2bd567.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/2bd567.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/2bd567.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/2bd567.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/2bd567.wgsl.expected.msl b/test/tint/builtins/gen/clamp/2bd567.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/2bd567.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/2bd567.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/2bd567.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/2bd567.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/2bd567.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/2bd567.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/2bd567.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/2bd567.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/2bde41.wgsl b/test/tint/builtins/gen/clamp/2bde41.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl
rename to test/tint/builtins/gen/clamp/2bde41.wgsl
diff --git a/test/builtins/gen/clamp/2bde41.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/2bde41.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/2bde41.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/2bde41.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/2bde41.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/2bde41.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/2bde41.wgsl.expected.msl b/test/tint/builtins/gen/clamp/2bde41.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/2bde41.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/2bde41.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/2bde41.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/2bde41.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/2bde41.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/2bde41.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/2bde41.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/2bde41.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/548fc7.wgsl b/test/tint/builtins/gen/clamp/548fc7.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl
rename to test/tint/builtins/gen/clamp/548fc7.wgsl
diff --git a/test/builtins/gen/clamp/548fc7.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/548fc7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/548fc7.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/548fc7.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/548fc7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/548fc7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/548fc7.wgsl.expected.msl b/test/tint/builtins/gen/clamp/548fc7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/548fc7.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/548fc7.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/548fc7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/548fc7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/548fc7.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/548fc7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/548fc7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/548fc7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/5f0819.wgsl b/test/tint/builtins/gen/clamp/5f0819.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl
rename to test/tint/builtins/gen/clamp/5f0819.wgsl
diff --git a/test/builtins/gen/clamp/5f0819.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/5f0819.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/5f0819.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/5f0819.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/5f0819.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/5f0819.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/5f0819.wgsl.expected.msl b/test/tint/builtins/gen/clamp/5f0819.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/5f0819.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/5f0819.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/5f0819.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/5f0819.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/5f0819.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/5f0819.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/5f0819.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/5f0819.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/6c1749.wgsl b/test/tint/builtins/gen/clamp/6c1749.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl
rename to test/tint/builtins/gen/clamp/6c1749.wgsl
diff --git a/test/builtins/gen/clamp/6c1749.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/6c1749.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/6c1749.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/6c1749.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/6c1749.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/6c1749.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/6c1749.wgsl.expected.msl b/test/tint/builtins/gen/clamp/6c1749.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/6c1749.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/6c1749.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/6c1749.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/6c1749.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/6c1749.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/6c1749.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/6c1749.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/6c1749.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/7706d7.wgsl b/test/tint/builtins/gen/clamp/7706d7.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl
rename to test/tint/builtins/gen/clamp/7706d7.wgsl
diff --git a/test/builtins/gen/clamp/7706d7.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/7706d7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/7706d7.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/7706d7.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/7706d7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/7706d7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/7706d7.wgsl.expected.msl b/test/tint/builtins/gen/clamp/7706d7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/7706d7.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/7706d7.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/7706d7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/7706d7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/7706d7.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/7706d7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/7706d7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/7706d7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/867397.wgsl b/test/tint/builtins/gen/clamp/867397.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl
rename to test/tint/builtins/gen/clamp/867397.wgsl
diff --git a/test/builtins/gen/clamp/867397.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/867397.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/867397.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/867397.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/867397.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/867397.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/867397.wgsl.expected.msl b/test/tint/builtins/gen/clamp/867397.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/867397.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/867397.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/867397.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/867397.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/867397.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/867397.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/867397.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/867397.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/a2de25.wgsl b/test/tint/builtins/gen/clamp/a2de25.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl
rename to test/tint/builtins/gen/clamp/a2de25.wgsl
diff --git a/test/builtins/gen/clamp/a2de25.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/a2de25.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/a2de25.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/a2de25.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/a2de25.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/a2de25.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/a2de25.wgsl.expected.msl b/test/tint/builtins/gen/clamp/a2de25.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/a2de25.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/a2de25.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/a2de25.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/a2de25.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/a2de25.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/a2de25.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/a2de25.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/a2de25.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/b07c65.wgsl b/test/tint/builtins/gen/clamp/b07c65.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl
rename to test/tint/builtins/gen/clamp/b07c65.wgsl
diff --git a/test/builtins/gen/clamp/b07c65.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/b07c65.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/b07c65.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/b07c65.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/b07c65.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/b07c65.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/b07c65.wgsl.expected.msl b/test/tint/builtins/gen/clamp/b07c65.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/b07c65.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/b07c65.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/b07c65.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/b07c65.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/b07c65.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/b07c65.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/b07c65.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/b07c65.wgsl.expected.wgsl
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl b/test/tint/builtins/gen/clamp/bd43ce.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl.expected.glsl b/test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl.expected.glsl
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.glsl
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl.expected.hlsl b/test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl.expected.hlsl
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.hlsl
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl.expected.msl b/test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl.expected.msl
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.msl
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl.expected.spvasm b/test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl.expected.spvasm
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.spvasm
diff --git a/test/builtins/gen/clamp/bd43ce.wgsl.expected.wgsl b/test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/clamp/bd43ce.wgsl.expected.wgsl
rename to test/tint/builtins/gen/clamp/bd43ce.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cos/16dc15.wgsl b/test/tint/builtins/gen/cos/16dc15.wgsl
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl
rename to test/tint/builtins/gen/cos/16dc15.wgsl
diff --git a/test/builtins/gen/cos/16dc15.wgsl.expected.glsl b/test/tint/builtins/gen/cos/16dc15.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl.expected.glsl
rename to test/tint/builtins/gen/cos/16dc15.wgsl.expected.glsl
diff --git a/test/builtins/gen/cos/16dc15.wgsl.expected.hlsl b/test/tint/builtins/gen/cos/16dc15.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cos/16dc15.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cos/16dc15.wgsl.expected.msl b/test/tint/builtins/gen/cos/16dc15.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl.expected.msl
rename to test/tint/builtins/gen/cos/16dc15.wgsl.expected.msl
diff --git a/test/builtins/gen/cos/16dc15.wgsl.expected.spvasm b/test/tint/builtins/gen/cos/16dc15.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cos/16dc15.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cos/16dc15.wgsl.expected.wgsl b/test/tint/builtins/gen/cos/16dc15.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cos/16dc15.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cos/16dc15.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cos/29d66d.wgsl b/test/tint/builtins/gen/cos/29d66d.wgsl
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl
rename to test/tint/builtins/gen/cos/29d66d.wgsl
diff --git a/test/builtins/gen/cos/29d66d.wgsl.expected.glsl b/test/tint/builtins/gen/cos/29d66d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl.expected.glsl
rename to test/tint/builtins/gen/cos/29d66d.wgsl.expected.glsl
diff --git a/test/builtins/gen/cos/29d66d.wgsl.expected.hlsl b/test/tint/builtins/gen/cos/29d66d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cos/29d66d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cos/29d66d.wgsl.expected.msl b/test/tint/builtins/gen/cos/29d66d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl.expected.msl
rename to test/tint/builtins/gen/cos/29d66d.wgsl.expected.msl
diff --git a/test/builtins/gen/cos/29d66d.wgsl.expected.spvasm b/test/tint/builtins/gen/cos/29d66d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cos/29d66d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cos/29d66d.wgsl.expected.wgsl b/test/tint/builtins/gen/cos/29d66d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cos/29d66d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cos/29d66d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cos/c3b486.wgsl b/test/tint/builtins/gen/cos/c3b486.wgsl
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl
rename to test/tint/builtins/gen/cos/c3b486.wgsl
diff --git a/test/builtins/gen/cos/c3b486.wgsl.expected.glsl b/test/tint/builtins/gen/cos/c3b486.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl.expected.glsl
rename to test/tint/builtins/gen/cos/c3b486.wgsl.expected.glsl
diff --git a/test/builtins/gen/cos/c3b486.wgsl.expected.hlsl b/test/tint/builtins/gen/cos/c3b486.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cos/c3b486.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cos/c3b486.wgsl.expected.msl b/test/tint/builtins/gen/cos/c3b486.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl.expected.msl
rename to test/tint/builtins/gen/cos/c3b486.wgsl.expected.msl
diff --git a/test/builtins/gen/cos/c3b486.wgsl.expected.spvasm b/test/tint/builtins/gen/cos/c3b486.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cos/c3b486.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cos/c3b486.wgsl.expected.wgsl b/test/tint/builtins/gen/cos/c3b486.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cos/c3b486.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cos/c3b486.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cos/c5c28e.wgsl b/test/tint/builtins/gen/cos/c5c28e.wgsl
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl
rename to test/tint/builtins/gen/cos/c5c28e.wgsl
diff --git a/test/builtins/gen/cos/c5c28e.wgsl.expected.glsl b/test/tint/builtins/gen/cos/c5c28e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl.expected.glsl
rename to test/tint/builtins/gen/cos/c5c28e.wgsl.expected.glsl
diff --git a/test/builtins/gen/cos/c5c28e.wgsl.expected.hlsl b/test/tint/builtins/gen/cos/c5c28e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cos/c5c28e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cos/c5c28e.wgsl.expected.msl b/test/tint/builtins/gen/cos/c5c28e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl.expected.msl
rename to test/tint/builtins/gen/cos/c5c28e.wgsl.expected.msl
diff --git a/test/builtins/gen/cos/c5c28e.wgsl.expected.spvasm b/test/tint/builtins/gen/cos/c5c28e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cos/c5c28e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cos/c5c28e.wgsl.expected.wgsl b/test/tint/builtins/gen/cos/c5c28e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cos/c5c28e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cos/c5c28e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cosh/377652.wgsl b/test/tint/builtins/gen/cosh/377652.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl
rename to test/tint/builtins/gen/cosh/377652.wgsl
diff --git a/test/builtins/gen/cosh/377652.wgsl.expected.glsl b/test/tint/builtins/gen/cosh/377652.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl.expected.glsl
rename to test/tint/builtins/gen/cosh/377652.wgsl.expected.glsl
diff --git a/test/builtins/gen/cosh/377652.wgsl.expected.hlsl b/test/tint/builtins/gen/cosh/377652.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cosh/377652.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cosh/377652.wgsl.expected.msl b/test/tint/builtins/gen/cosh/377652.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl.expected.msl
rename to test/tint/builtins/gen/cosh/377652.wgsl.expected.msl
diff --git a/test/builtins/gen/cosh/377652.wgsl.expected.spvasm b/test/tint/builtins/gen/cosh/377652.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cosh/377652.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cosh/377652.wgsl.expected.wgsl b/test/tint/builtins/gen/cosh/377652.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/377652.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cosh/377652.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cosh/c13756.wgsl b/test/tint/builtins/gen/cosh/c13756.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl
rename to test/tint/builtins/gen/cosh/c13756.wgsl
diff --git a/test/builtins/gen/cosh/c13756.wgsl.expected.glsl b/test/tint/builtins/gen/cosh/c13756.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl.expected.glsl
rename to test/tint/builtins/gen/cosh/c13756.wgsl.expected.glsl
diff --git a/test/builtins/gen/cosh/c13756.wgsl.expected.hlsl b/test/tint/builtins/gen/cosh/c13756.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cosh/c13756.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cosh/c13756.wgsl.expected.msl b/test/tint/builtins/gen/cosh/c13756.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl.expected.msl
rename to test/tint/builtins/gen/cosh/c13756.wgsl.expected.msl
diff --git a/test/builtins/gen/cosh/c13756.wgsl.expected.spvasm b/test/tint/builtins/gen/cosh/c13756.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cosh/c13756.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cosh/c13756.wgsl.expected.wgsl b/test/tint/builtins/gen/cosh/c13756.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/c13756.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cosh/c13756.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cosh/da92dd.wgsl b/test/tint/builtins/gen/cosh/da92dd.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl
rename to test/tint/builtins/gen/cosh/da92dd.wgsl
diff --git a/test/builtins/gen/cosh/da92dd.wgsl.expected.glsl b/test/tint/builtins/gen/cosh/da92dd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl.expected.glsl
rename to test/tint/builtins/gen/cosh/da92dd.wgsl.expected.glsl
diff --git a/test/builtins/gen/cosh/da92dd.wgsl.expected.hlsl b/test/tint/builtins/gen/cosh/da92dd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cosh/da92dd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cosh/da92dd.wgsl.expected.msl b/test/tint/builtins/gen/cosh/da92dd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl.expected.msl
rename to test/tint/builtins/gen/cosh/da92dd.wgsl.expected.msl
diff --git a/test/builtins/gen/cosh/da92dd.wgsl.expected.spvasm b/test/tint/builtins/gen/cosh/da92dd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cosh/da92dd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cosh/da92dd.wgsl.expected.wgsl b/test/tint/builtins/gen/cosh/da92dd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/da92dd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cosh/da92dd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl b/test/tint/builtins/gen/cosh/e0c1de.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl.expected.glsl b/test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl.expected.glsl
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.glsl
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl.expected.hlsl b/test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl.expected.msl b/test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl.expected.msl
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.msl
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl.expected.spvasm b/test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cosh/e0c1de.wgsl.expected.wgsl b/test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cosh/e0c1de.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cosh/e0c1de.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/0d0e46.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0d0e46.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/0d0e46.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl b/test/tint/builtins/gen/countOneBits/0f7980.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/0f7980.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/0f7980.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/0f7980.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/65d2ae.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/65d2ae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/65d2ae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl b/test/tint/builtins/gen/countOneBits/690cfc.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/690cfc.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/690cfc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/690cfc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl b/test/tint/builtins/gen/countOneBits/94fd81.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/94fd81.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/94fd81.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/94fd81.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/ae44f9.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/ae44f9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/ae44f9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl b/test/tint/builtins/gen/countOneBits/af90e2.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/af90e2.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/af90e2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/af90e2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl.expected.glsl b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl.expected.glsl
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.glsl
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl.expected.hlsl b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl.expected.msl b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl.expected.msl
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.msl
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl.expected.spvasm b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/countOneBits/fd88b2.wgsl.expected.wgsl b/test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/countOneBits/fd88b2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/countOneBits/fd88b2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/cross/041cb0.wgsl b/test/tint/builtins/gen/cross/041cb0.wgsl
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl
rename to test/tint/builtins/gen/cross/041cb0.wgsl
diff --git a/test/builtins/gen/cross/041cb0.wgsl.expected.glsl b/test/tint/builtins/gen/cross/041cb0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl.expected.glsl
rename to test/tint/builtins/gen/cross/041cb0.wgsl.expected.glsl
diff --git a/test/builtins/gen/cross/041cb0.wgsl.expected.hlsl b/test/tint/builtins/gen/cross/041cb0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/cross/041cb0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/cross/041cb0.wgsl.expected.msl b/test/tint/builtins/gen/cross/041cb0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl.expected.msl
rename to test/tint/builtins/gen/cross/041cb0.wgsl.expected.msl
diff --git a/test/builtins/gen/cross/041cb0.wgsl.expected.spvasm b/test/tint/builtins/gen/cross/041cb0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/cross/041cb0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/cross/041cb0.wgsl.expected.wgsl b/test/tint/builtins/gen/cross/041cb0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/cross/041cb0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/cross/041cb0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/degrees/0d170c.wgsl b/test/tint/builtins/gen/degrees/0d170c.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl
rename to test/tint/builtins/gen/degrees/0d170c.wgsl
diff --git a/test/builtins/gen/degrees/0d170c.wgsl.expected.glsl b/test/tint/builtins/gen/degrees/0d170c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl.expected.glsl
rename to test/tint/builtins/gen/degrees/0d170c.wgsl.expected.glsl
diff --git a/test/builtins/gen/degrees/0d170c.wgsl.expected.hlsl b/test/tint/builtins/gen/degrees/0d170c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/degrees/0d170c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/degrees/0d170c.wgsl.expected.msl b/test/tint/builtins/gen/degrees/0d170c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl.expected.msl
rename to test/tint/builtins/gen/degrees/0d170c.wgsl.expected.msl
diff --git a/test/builtins/gen/degrees/0d170c.wgsl.expected.spvasm b/test/tint/builtins/gen/degrees/0d170c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/degrees/0d170c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/degrees/0d170c.wgsl.expected.wgsl b/test/tint/builtins/gen/degrees/0d170c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/0d170c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/degrees/0d170c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl b/test/tint/builtins/gen/degrees/1ad5df.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl.expected.glsl b/test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl.expected.glsl
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.glsl
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl.expected.hlsl b/test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl.expected.hlsl
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.hlsl
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl.expected.msl b/test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl.expected.msl
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.msl
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl.expected.spvasm b/test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl.expected.spvasm
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.spvasm
diff --git a/test/builtins/gen/degrees/1ad5df.wgsl.expected.wgsl b/test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/1ad5df.wgsl.expected.wgsl
rename to test/tint/builtins/gen/degrees/1ad5df.wgsl.expected.wgsl
diff --git a/test/builtins/gen/degrees/2af623.wgsl b/test/tint/builtins/gen/degrees/2af623.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl
rename to test/tint/builtins/gen/degrees/2af623.wgsl
diff --git a/test/builtins/gen/degrees/2af623.wgsl.expected.glsl b/test/tint/builtins/gen/degrees/2af623.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl.expected.glsl
rename to test/tint/builtins/gen/degrees/2af623.wgsl.expected.glsl
diff --git a/test/builtins/gen/degrees/2af623.wgsl.expected.hlsl b/test/tint/builtins/gen/degrees/2af623.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl.expected.hlsl
rename to test/tint/builtins/gen/degrees/2af623.wgsl.expected.hlsl
diff --git a/test/builtins/gen/degrees/2af623.wgsl.expected.msl b/test/tint/builtins/gen/degrees/2af623.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl.expected.msl
rename to test/tint/builtins/gen/degrees/2af623.wgsl.expected.msl
diff --git a/test/builtins/gen/degrees/2af623.wgsl.expected.spvasm b/test/tint/builtins/gen/degrees/2af623.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl.expected.spvasm
rename to test/tint/builtins/gen/degrees/2af623.wgsl.expected.spvasm
diff --git a/test/builtins/gen/degrees/2af623.wgsl.expected.wgsl b/test/tint/builtins/gen/degrees/2af623.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/2af623.wgsl.expected.wgsl
rename to test/tint/builtins/gen/degrees/2af623.wgsl.expected.wgsl
diff --git a/test/builtins/gen/degrees/51f705.wgsl b/test/tint/builtins/gen/degrees/51f705.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl
rename to test/tint/builtins/gen/degrees/51f705.wgsl
diff --git a/test/builtins/gen/degrees/51f705.wgsl.expected.glsl b/test/tint/builtins/gen/degrees/51f705.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl.expected.glsl
rename to test/tint/builtins/gen/degrees/51f705.wgsl.expected.glsl
diff --git a/test/builtins/gen/degrees/51f705.wgsl.expected.hlsl b/test/tint/builtins/gen/degrees/51f705.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl.expected.hlsl
rename to test/tint/builtins/gen/degrees/51f705.wgsl.expected.hlsl
diff --git a/test/builtins/gen/degrees/51f705.wgsl.expected.msl b/test/tint/builtins/gen/degrees/51f705.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl.expected.msl
rename to test/tint/builtins/gen/degrees/51f705.wgsl.expected.msl
diff --git a/test/builtins/gen/degrees/51f705.wgsl.expected.spvasm b/test/tint/builtins/gen/degrees/51f705.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl.expected.spvasm
rename to test/tint/builtins/gen/degrees/51f705.wgsl.expected.spvasm
diff --git a/test/builtins/gen/degrees/51f705.wgsl.expected.wgsl b/test/tint/builtins/gen/degrees/51f705.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/degrees/51f705.wgsl.expected.wgsl
rename to test/tint/builtins/gen/degrees/51f705.wgsl.expected.wgsl
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl b/test/tint/builtins/gen/determinant/2b62ba.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl.expected.glsl b/test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl.expected.glsl
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.glsl
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl.expected.hlsl b/test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl.expected.hlsl
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.hlsl
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl.expected.msl b/test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl.expected.msl
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.msl
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl.expected.spvasm b/test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl.expected.spvasm
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.spvasm
diff --git a/test/builtins/gen/determinant/2b62ba.wgsl.expected.wgsl b/test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/2b62ba.wgsl.expected.wgsl
rename to test/tint/builtins/gen/determinant/2b62ba.wgsl.expected.wgsl
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl b/test/tint/builtins/gen/determinant/a0a87c.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl.expected.glsl b/test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl.expected.glsl
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.glsl
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl.expected.hlsl b/test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl.expected.msl b/test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl.expected.msl
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.msl
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl.expected.spvasm b/test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/determinant/a0a87c.wgsl.expected.wgsl b/test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/a0a87c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/determinant/a0a87c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/determinant/e19305.wgsl b/test/tint/builtins/gen/determinant/e19305.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl
rename to test/tint/builtins/gen/determinant/e19305.wgsl
diff --git a/test/builtins/gen/determinant/e19305.wgsl.expected.glsl b/test/tint/builtins/gen/determinant/e19305.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl.expected.glsl
rename to test/tint/builtins/gen/determinant/e19305.wgsl.expected.glsl
diff --git a/test/builtins/gen/determinant/e19305.wgsl.expected.hlsl b/test/tint/builtins/gen/determinant/e19305.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl.expected.hlsl
rename to test/tint/builtins/gen/determinant/e19305.wgsl.expected.hlsl
diff --git a/test/builtins/gen/determinant/e19305.wgsl.expected.msl b/test/tint/builtins/gen/determinant/e19305.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl.expected.msl
rename to test/tint/builtins/gen/determinant/e19305.wgsl.expected.msl
diff --git a/test/builtins/gen/determinant/e19305.wgsl.expected.spvasm b/test/tint/builtins/gen/determinant/e19305.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl.expected.spvasm
rename to test/tint/builtins/gen/determinant/e19305.wgsl.expected.spvasm
diff --git a/test/builtins/gen/determinant/e19305.wgsl.expected.wgsl b/test/tint/builtins/gen/determinant/e19305.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/determinant/e19305.wgsl.expected.wgsl
rename to test/tint/builtins/gen/determinant/e19305.wgsl.expected.wgsl
diff --git a/test/builtins/gen/distance/0657d4.wgsl b/test/tint/builtins/gen/distance/0657d4.wgsl
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl
rename to test/tint/builtins/gen/distance/0657d4.wgsl
diff --git a/test/builtins/gen/distance/0657d4.wgsl.expected.glsl b/test/tint/builtins/gen/distance/0657d4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl.expected.glsl
rename to test/tint/builtins/gen/distance/0657d4.wgsl.expected.glsl
diff --git a/test/builtins/gen/distance/0657d4.wgsl.expected.hlsl b/test/tint/builtins/gen/distance/0657d4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/distance/0657d4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/distance/0657d4.wgsl.expected.msl b/test/tint/builtins/gen/distance/0657d4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl.expected.msl
rename to test/tint/builtins/gen/distance/0657d4.wgsl.expected.msl
diff --git a/test/builtins/gen/distance/0657d4.wgsl.expected.spvasm b/test/tint/builtins/gen/distance/0657d4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/distance/0657d4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/distance/0657d4.wgsl.expected.wgsl b/test/tint/builtins/gen/distance/0657d4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/distance/0657d4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/distance/0657d4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/distance/9646ea.wgsl b/test/tint/builtins/gen/distance/9646ea.wgsl
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl
rename to test/tint/builtins/gen/distance/9646ea.wgsl
diff --git a/test/builtins/gen/distance/9646ea.wgsl.expected.glsl b/test/tint/builtins/gen/distance/9646ea.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl.expected.glsl
rename to test/tint/builtins/gen/distance/9646ea.wgsl.expected.glsl
diff --git a/test/builtins/gen/distance/9646ea.wgsl.expected.hlsl b/test/tint/builtins/gen/distance/9646ea.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl.expected.hlsl
rename to test/tint/builtins/gen/distance/9646ea.wgsl.expected.hlsl
diff --git a/test/builtins/gen/distance/9646ea.wgsl.expected.msl b/test/tint/builtins/gen/distance/9646ea.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl.expected.msl
rename to test/tint/builtins/gen/distance/9646ea.wgsl.expected.msl
diff --git a/test/builtins/gen/distance/9646ea.wgsl.expected.spvasm b/test/tint/builtins/gen/distance/9646ea.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl.expected.spvasm
rename to test/tint/builtins/gen/distance/9646ea.wgsl.expected.spvasm
diff --git a/test/builtins/gen/distance/9646ea.wgsl.expected.wgsl b/test/tint/builtins/gen/distance/9646ea.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/distance/9646ea.wgsl.expected.wgsl
rename to test/tint/builtins/gen/distance/9646ea.wgsl.expected.wgsl
diff --git a/test/builtins/gen/distance/aa4055.wgsl b/test/tint/builtins/gen/distance/aa4055.wgsl
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl
rename to test/tint/builtins/gen/distance/aa4055.wgsl
diff --git a/test/builtins/gen/distance/aa4055.wgsl.expected.glsl b/test/tint/builtins/gen/distance/aa4055.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl.expected.glsl
rename to test/tint/builtins/gen/distance/aa4055.wgsl.expected.glsl
diff --git a/test/builtins/gen/distance/aa4055.wgsl.expected.hlsl b/test/tint/builtins/gen/distance/aa4055.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl.expected.hlsl
rename to test/tint/builtins/gen/distance/aa4055.wgsl.expected.hlsl
diff --git a/test/builtins/gen/distance/aa4055.wgsl.expected.msl b/test/tint/builtins/gen/distance/aa4055.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl.expected.msl
rename to test/tint/builtins/gen/distance/aa4055.wgsl.expected.msl
diff --git a/test/builtins/gen/distance/aa4055.wgsl.expected.spvasm b/test/tint/builtins/gen/distance/aa4055.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl.expected.spvasm
rename to test/tint/builtins/gen/distance/aa4055.wgsl.expected.spvasm
diff --git a/test/builtins/gen/distance/aa4055.wgsl.expected.wgsl b/test/tint/builtins/gen/distance/aa4055.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/distance/aa4055.wgsl.expected.wgsl
rename to test/tint/builtins/gen/distance/aa4055.wgsl.expected.wgsl
diff --git a/test/builtins/gen/distance/cfed73.wgsl b/test/tint/builtins/gen/distance/cfed73.wgsl
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl
rename to test/tint/builtins/gen/distance/cfed73.wgsl
diff --git a/test/builtins/gen/distance/cfed73.wgsl.expected.glsl b/test/tint/builtins/gen/distance/cfed73.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl.expected.glsl
rename to test/tint/builtins/gen/distance/cfed73.wgsl.expected.glsl
diff --git a/test/builtins/gen/distance/cfed73.wgsl.expected.hlsl b/test/tint/builtins/gen/distance/cfed73.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl.expected.hlsl
rename to test/tint/builtins/gen/distance/cfed73.wgsl.expected.hlsl
diff --git a/test/builtins/gen/distance/cfed73.wgsl.expected.msl b/test/tint/builtins/gen/distance/cfed73.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl.expected.msl
rename to test/tint/builtins/gen/distance/cfed73.wgsl.expected.msl
diff --git a/test/builtins/gen/distance/cfed73.wgsl.expected.spvasm b/test/tint/builtins/gen/distance/cfed73.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl.expected.spvasm
rename to test/tint/builtins/gen/distance/cfed73.wgsl.expected.spvasm
diff --git a/test/builtins/gen/distance/cfed73.wgsl.expected.wgsl b/test/tint/builtins/gen/distance/cfed73.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/distance/cfed73.wgsl.expected.wgsl
rename to test/tint/builtins/gen/distance/cfed73.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/0c577b.wgsl b/test/tint/builtins/gen/dot/0c577b.wgsl
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl
rename to test/tint/builtins/gen/dot/0c577b.wgsl
diff --git a/test/builtins/gen/dot/0c577b.wgsl.expected.glsl b/test/tint/builtins/gen/dot/0c577b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/0c577b.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/0c577b.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/0c577b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/0c577b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/0c577b.wgsl.expected.msl b/test/tint/builtins/gen/dot/0c577b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/0c577b.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/0c577b.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/0c577b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/0c577b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/0c577b.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/0c577b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/0c577b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/0c577b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/7548a0.wgsl b/test/tint/builtins/gen/dot/7548a0.wgsl
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl
rename to test/tint/builtins/gen/dot/7548a0.wgsl
diff --git a/test/builtins/gen/dot/7548a0.wgsl.expected.glsl b/test/tint/builtins/gen/dot/7548a0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/7548a0.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/7548a0.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/7548a0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/7548a0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/7548a0.wgsl.expected.msl b/test/tint/builtins/gen/dot/7548a0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/7548a0.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/7548a0.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/7548a0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/7548a0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/7548a0.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/7548a0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/7548a0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/7548a0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/883f0e.wgsl b/test/tint/builtins/gen/dot/883f0e.wgsl
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl
rename to test/tint/builtins/gen/dot/883f0e.wgsl
diff --git a/test/builtins/gen/dot/883f0e.wgsl.expected.glsl b/test/tint/builtins/gen/dot/883f0e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/883f0e.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/883f0e.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/883f0e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/883f0e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/883f0e.wgsl.expected.msl b/test/tint/builtins/gen/dot/883f0e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/883f0e.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/883f0e.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/883f0e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/883f0e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/883f0e.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/883f0e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/883f0e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/883f0e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/97c7ee.wgsl b/test/tint/builtins/gen/dot/97c7ee.wgsl
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl
rename to test/tint/builtins/gen/dot/97c7ee.wgsl
diff --git a/test/builtins/gen/dot/97c7ee.wgsl.expected.glsl b/test/tint/builtins/gen/dot/97c7ee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/97c7ee.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/97c7ee.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/97c7ee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/97c7ee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/97c7ee.wgsl.expected.msl b/test/tint/builtins/gen/dot/97c7ee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/97c7ee.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/97c7ee.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/97c7ee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/97c7ee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/97c7ee.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/97c7ee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/97c7ee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/97c7ee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/ba4246.wgsl b/test/tint/builtins/gen/dot/ba4246.wgsl
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl
rename to test/tint/builtins/gen/dot/ba4246.wgsl
diff --git a/test/builtins/gen/dot/ba4246.wgsl.expected.glsl b/test/tint/builtins/gen/dot/ba4246.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/ba4246.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/ba4246.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/ba4246.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/ba4246.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/ba4246.wgsl.expected.msl b/test/tint/builtins/gen/dot/ba4246.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/ba4246.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/ba4246.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/ba4246.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/ba4246.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/ba4246.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/ba4246.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/ba4246.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/ba4246.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/e994c7.wgsl b/test/tint/builtins/gen/dot/e994c7.wgsl
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl
rename to test/tint/builtins/gen/dot/e994c7.wgsl
diff --git a/test/builtins/gen/dot/e994c7.wgsl.expected.glsl b/test/tint/builtins/gen/dot/e994c7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/e994c7.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/e994c7.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/e994c7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/e994c7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/e994c7.wgsl.expected.msl b/test/tint/builtins/gen/dot/e994c7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/e994c7.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/e994c7.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/e994c7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/e994c7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/e994c7.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/e994c7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/e994c7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/e994c7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl b/test/tint/builtins/gen/dot/ef6b1d.wgsl
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl.expected.glsl b/test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl.expected.msl b/test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/ef6b1d.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/ef6b1d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/ef6b1d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/f1312c.wgsl b/test/tint/builtins/gen/dot/f1312c.wgsl
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl
rename to test/tint/builtins/gen/dot/f1312c.wgsl
diff --git a/test/builtins/gen/dot/f1312c.wgsl.expected.glsl b/test/tint/builtins/gen/dot/f1312c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/f1312c.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/f1312c.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/f1312c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/f1312c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/f1312c.wgsl.expected.msl b/test/tint/builtins/gen/dot/f1312c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/f1312c.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/f1312c.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/f1312c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/f1312c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/f1312c.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/f1312c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/f1312c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/f1312c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl b/test/tint/builtins/gen/dot/fc5f7c.wgsl
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl.expected.glsl b/test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl.expected.glsl
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.glsl
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl.expected.hlsl b/test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl.expected.msl b/test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl.expected.msl
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.msl
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl.expected.spvasm b/test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dot/fc5f7c.wgsl.expected.wgsl b/test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dot/fc5f7c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dot/fc5f7c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl b/test/tint/builtins/gen/dpdx/0763f7.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl.expected.glsl b/test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl.expected.msl b/test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdx/0763f7.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/0763f7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdx/0763f7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl b/test/tint/builtins/gen/dpdx/99edb1.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl.expected.glsl b/test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl.expected.msl b/test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdx/99edb1.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/99edb1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdx/99edb1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl b/test/tint/builtins/gen/dpdx/c487fa.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl.expected.glsl b/test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl.expected.msl b/test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdx/c487fa.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/c487fa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdx/c487fa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdx/e263de.wgsl b/test/tint/builtins/gen/dpdx/e263de.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl
rename to test/tint/builtins/gen/dpdx/e263de.wgsl
diff --git a/test/builtins/gen/dpdx/e263de.wgsl.expected.glsl b/test/tint/builtins/gen/dpdx/e263de.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdx/e263de.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdx/e263de.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdx/e263de.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdx/e263de.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdx/e263de.wgsl.expected.msl b/test/tint/builtins/gen/dpdx/e263de.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdx/e263de.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdx/e263de.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdx/e263de.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdx/e263de.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdx/e263de.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdx/e263de.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdx/e263de.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdx/e263de.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl.expected.msl b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxCoarse/029152.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/029152.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/029152.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.msl b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/9581cf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.msl b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/c28641.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/c28641.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.msl b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxCoarse/f64d7b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl.expected.msl b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxFine/8c5069.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/8c5069.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxFine/8c5069.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl b/test/tint/builtins/gen/dpdxFine/9631de.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl.expected.msl b/test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxFine/9631de.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/9631de.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxFine/9631de.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl.expected.msl b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxFine/f401a2.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f401a2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxFine/f401a2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.glsl b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.msl b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdxFine/f92fb6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdxFine/f92fb6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdy/699a05.wgsl b/test/tint/builtins/gen/dpdy/699a05.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl
rename to test/tint/builtins/gen/dpdy/699a05.wgsl
diff --git a/test/builtins/gen/dpdy/699a05.wgsl.expected.glsl b/test/tint/builtins/gen/dpdy/699a05.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdy/699a05.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdy/699a05.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdy/699a05.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdy/699a05.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdy/699a05.wgsl.expected.msl b/test/tint/builtins/gen/dpdy/699a05.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdy/699a05.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdy/699a05.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdy/699a05.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdy/699a05.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdy/699a05.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdy/699a05.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/699a05.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdy/699a05.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl b/test/tint/builtins/gen/dpdy/7f8d84.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl.expected.glsl b/test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl.expected.msl b/test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdy/7f8d84.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/7f8d84.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdy/7f8d84.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl b/test/tint/builtins/gen/dpdy/a8b56e.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl.expected.glsl b/test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl.expected.msl b/test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdy/a8b56e.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/a8b56e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdy/a8b56e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl b/test/tint/builtins/gen/dpdy/feb40f.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl.expected.glsl b/test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl.expected.msl b/test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdy/feb40f.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdy/feb40f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdy/feb40f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.msl b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/3e1ab4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.msl b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/445d24.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/445d24.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.msl b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/870a7e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.msl b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyCoarse/ae1873.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.msl b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyFine/1fb7ab.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl.expected.msl b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyFine/6eb673.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/6eb673.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyFine/6eb673.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl.expected.msl b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyFine/d0a648.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/d0a648.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyFine/d0a648.wgsl.expected.wgsl
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl.expected.glsl b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl.expected.glsl
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.glsl
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl.expected.hlsl b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl.expected.msl b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl.expected.msl
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.msl
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl.expected.spvasm b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/dpdyFine/df33aa.wgsl.expected.wgsl b/test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/dpdyFine/df33aa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/dpdyFine/df33aa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp/0f70eb.wgsl b/test/tint/builtins/gen/exp/0f70eb.wgsl
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl
rename to test/tint/builtins/gen/exp/0f70eb.wgsl
diff --git a/test/builtins/gen/exp/0f70eb.wgsl.expected.glsl b/test/tint/builtins/gen/exp/0f70eb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp/0f70eb.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp/0f70eb.wgsl.expected.hlsl b/test/tint/builtins/gen/exp/0f70eb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp/0f70eb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp/0f70eb.wgsl.expected.msl b/test/tint/builtins/gen/exp/0f70eb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl.expected.msl
rename to test/tint/builtins/gen/exp/0f70eb.wgsl.expected.msl
diff --git a/test/builtins/gen/exp/0f70eb.wgsl.expected.spvasm b/test/tint/builtins/gen/exp/0f70eb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp/0f70eb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp/0f70eb.wgsl.expected.wgsl b/test/tint/builtins/gen/exp/0f70eb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp/0f70eb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp/0f70eb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp/1951e7.wgsl b/test/tint/builtins/gen/exp/1951e7.wgsl
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl
rename to test/tint/builtins/gen/exp/1951e7.wgsl
diff --git a/test/builtins/gen/exp/1951e7.wgsl.expected.glsl b/test/tint/builtins/gen/exp/1951e7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp/1951e7.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp/1951e7.wgsl.expected.hlsl b/test/tint/builtins/gen/exp/1951e7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp/1951e7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp/1951e7.wgsl.expected.msl b/test/tint/builtins/gen/exp/1951e7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl.expected.msl
rename to test/tint/builtins/gen/exp/1951e7.wgsl.expected.msl
diff --git a/test/builtins/gen/exp/1951e7.wgsl.expected.spvasm b/test/tint/builtins/gen/exp/1951e7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp/1951e7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp/1951e7.wgsl.expected.wgsl b/test/tint/builtins/gen/exp/1951e7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp/1951e7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp/1951e7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp/771fd2.wgsl b/test/tint/builtins/gen/exp/771fd2.wgsl
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl
rename to test/tint/builtins/gen/exp/771fd2.wgsl
diff --git a/test/builtins/gen/exp/771fd2.wgsl.expected.glsl b/test/tint/builtins/gen/exp/771fd2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp/771fd2.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp/771fd2.wgsl.expected.hlsl b/test/tint/builtins/gen/exp/771fd2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp/771fd2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp/771fd2.wgsl.expected.msl b/test/tint/builtins/gen/exp/771fd2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl.expected.msl
rename to test/tint/builtins/gen/exp/771fd2.wgsl.expected.msl
diff --git a/test/builtins/gen/exp/771fd2.wgsl.expected.spvasm b/test/tint/builtins/gen/exp/771fd2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp/771fd2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp/771fd2.wgsl.expected.wgsl b/test/tint/builtins/gen/exp/771fd2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp/771fd2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp/771fd2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp/d98450.wgsl b/test/tint/builtins/gen/exp/d98450.wgsl
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl
rename to test/tint/builtins/gen/exp/d98450.wgsl
diff --git a/test/builtins/gen/exp/d98450.wgsl.expected.glsl b/test/tint/builtins/gen/exp/d98450.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp/d98450.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp/d98450.wgsl.expected.hlsl b/test/tint/builtins/gen/exp/d98450.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp/d98450.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp/d98450.wgsl.expected.msl b/test/tint/builtins/gen/exp/d98450.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl.expected.msl
rename to test/tint/builtins/gen/exp/d98450.wgsl.expected.msl
diff --git a/test/builtins/gen/exp/d98450.wgsl.expected.spvasm b/test/tint/builtins/gen/exp/d98450.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp/d98450.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp/d98450.wgsl.expected.wgsl b/test/tint/builtins/gen/exp/d98450.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp/d98450.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp/d98450.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp2/1f8680.wgsl b/test/tint/builtins/gen/exp2/1f8680.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl
rename to test/tint/builtins/gen/exp2/1f8680.wgsl
diff --git a/test/builtins/gen/exp2/1f8680.wgsl.expected.glsl b/test/tint/builtins/gen/exp2/1f8680.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp2/1f8680.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp2/1f8680.wgsl.expected.hlsl b/test/tint/builtins/gen/exp2/1f8680.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp2/1f8680.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp2/1f8680.wgsl.expected.msl b/test/tint/builtins/gen/exp2/1f8680.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl.expected.msl
rename to test/tint/builtins/gen/exp2/1f8680.wgsl.expected.msl
diff --git a/test/builtins/gen/exp2/1f8680.wgsl.expected.spvasm b/test/tint/builtins/gen/exp2/1f8680.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp2/1f8680.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp2/1f8680.wgsl.expected.wgsl b/test/tint/builtins/gen/exp2/1f8680.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/1f8680.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp2/1f8680.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl b/test/tint/builtins/gen/exp2/a9d0a7.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl.expected.glsl b/test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl.expected.hlsl b/test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl.expected.msl b/test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl.expected.msl
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.msl
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl.expected.spvasm b/test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp2/a9d0a7.wgsl.expected.wgsl b/test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/a9d0a7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp2/a9d0a7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp2/d6777c.wgsl b/test/tint/builtins/gen/exp2/d6777c.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl
rename to test/tint/builtins/gen/exp2/d6777c.wgsl
diff --git a/test/builtins/gen/exp2/d6777c.wgsl.expected.glsl b/test/tint/builtins/gen/exp2/d6777c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp2/d6777c.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp2/d6777c.wgsl.expected.hlsl b/test/tint/builtins/gen/exp2/d6777c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp2/d6777c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp2/d6777c.wgsl.expected.msl b/test/tint/builtins/gen/exp2/d6777c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl.expected.msl
rename to test/tint/builtins/gen/exp2/d6777c.wgsl.expected.msl
diff --git a/test/builtins/gen/exp2/d6777c.wgsl.expected.spvasm b/test/tint/builtins/gen/exp2/d6777c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp2/d6777c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp2/d6777c.wgsl.expected.wgsl b/test/tint/builtins/gen/exp2/d6777c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/d6777c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp2/d6777c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/exp2/dea523.wgsl b/test/tint/builtins/gen/exp2/dea523.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl
rename to test/tint/builtins/gen/exp2/dea523.wgsl
diff --git a/test/builtins/gen/exp2/dea523.wgsl.expected.glsl b/test/tint/builtins/gen/exp2/dea523.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl.expected.glsl
rename to test/tint/builtins/gen/exp2/dea523.wgsl.expected.glsl
diff --git a/test/builtins/gen/exp2/dea523.wgsl.expected.hlsl b/test/tint/builtins/gen/exp2/dea523.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl.expected.hlsl
rename to test/tint/builtins/gen/exp2/dea523.wgsl.expected.hlsl
diff --git a/test/builtins/gen/exp2/dea523.wgsl.expected.msl b/test/tint/builtins/gen/exp2/dea523.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl.expected.msl
rename to test/tint/builtins/gen/exp2/dea523.wgsl.expected.msl
diff --git a/test/builtins/gen/exp2/dea523.wgsl.expected.spvasm b/test/tint/builtins/gen/exp2/dea523.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl.expected.spvasm
rename to test/tint/builtins/gen/exp2/dea523.wgsl.expected.spvasm
diff --git a/test/builtins/gen/exp2/dea523.wgsl.expected.wgsl b/test/tint/builtins/gen/exp2/dea523.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/exp2/dea523.wgsl.expected.wgsl
rename to test/tint/builtins/gen/exp2/dea523.wgsl.expected.wgsl
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl b/test/tint/builtins/gen/faceForward/5afbd5.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl.expected.glsl b/test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl.expected.glsl
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.glsl
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl.expected.hlsl b/test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl.expected.msl b/test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl.expected.msl
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.msl
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl.expected.spvasm b/test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/faceForward/5afbd5.wgsl.expected.wgsl b/test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/5afbd5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/faceForward/5afbd5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl b/test/tint/builtins/gen/faceForward/b316e5.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl.expected.glsl b/test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl.expected.glsl
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.glsl
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl.expected.hlsl b/test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl.expected.msl b/test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl.expected.msl
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.msl
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl.expected.spvasm b/test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/faceForward/b316e5.wgsl.expected.wgsl b/test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/b316e5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/faceForward/b316e5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl b/test/tint/builtins/gen/faceForward/e6908b.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl.expected.glsl b/test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl.expected.glsl
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.glsl
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl.expected.hlsl b/test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl.expected.msl b/test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl.expected.msl
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.msl
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl.expected.spvasm b/test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/faceForward/e6908b.wgsl.expected.wgsl b/test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/faceForward/e6908b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/faceForward/e6908b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/floor/3bccc4.wgsl b/test/tint/builtins/gen/floor/3bccc4.wgsl
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl
rename to test/tint/builtins/gen/floor/3bccc4.wgsl
diff --git a/test/builtins/gen/floor/3bccc4.wgsl.expected.glsl b/test/tint/builtins/gen/floor/3bccc4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl.expected.glsl
rename to test/tint/builtins/gen/floor/3bccc4.wgsl.expected.glsl
diff --git a/test/builtins/gen/floor/3bccc4.wgsl.expected.hlsl b/test/tint/builtins/gen/floor/3bccc4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/floor/3bccc4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/floor/3bccc4.wgsl.expected.msl b/test/tint/builtins/gen/floor/3bccc4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl.expected.msl
rename to test/tint/builtins/gen/floor/3bccc4.wgsl.expected.msl
diff --git a/test/builtins/gen/floor/3bccc4.wgsl.expected.spvasm b/test/tint/builtins/gen/floor/3bccc4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/floor/3bccc4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/floor/3bccc4.wgsl.expected.wgsl b/test/tint/builtins/gen/floor/3bccc4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/floor/3bccc4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/floor/3bccc4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl b/test/tint/builtins/gen/floor/5fc9ac.wgsl
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl.expected.glsl b/test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl.expected.glsl
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.glsl
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl.expected.hlsl b/test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl.expected.hlsl
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.hlsl
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl.expected.msl b/test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl.expected.msl
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.msl
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl.expected.spvasm b/test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl.expected.spvasm
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.spvasm
diff --git a/test/builtins/gen/floor/5fc9ac.wgsl.expected.wgsl b/test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/floor/5fc9ac.wgsl.expected.wgsl
rename to test/tint/builtins/gen/floor/5fc9ac.wgsl.expected.wgsl
diff --git a/test/builtins/gen/floor/60d7ea.wgsl b/test/tint/builtins/gen/floor/60d7ea.wgsl
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl
rename to test/tint/builtins/gen/floor/60d7ea.wgsl
diff --git a/test/builtins/gen/floor/60d7ea.wgsl.expected.glsl b/test/tint/builtins/gen/floor/60d7ea.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl.expected.glsl
rename to test/tint/builtins/gen/floor/60d7ea.wgsl.expected.glsl
diff --git a/test/builtins/gen/floor/60d7ea.wgsl.expected.hlsl b/test/tint/builtins/gen/floor/60d7ea.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl.expected.hlsl
rename to test/tint/builtins/gen/floor/60d7ea.wgsl.expected.hlsl
diff --git a/test/builtins/gen/floor/60d7ea.wgsl.expected.msl b/test/tint/builtins/gen/floor/60d7ea.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl.expected.msl
rename to test/tint/builtins/gen/floor/60d7ea.wgsl.expected.msl
diff --git a/test/builtins/gen/floor/60d7ea.wgsl.expected.spvasm b/test/tint/builtins/gen/floor/60d7ea.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl.expected.spvasm
rename to test/tint/builtins/gen/floor/60d7ea.wgsl.expected.spvasm
diff --git a/test/builtins/gen/floor/60d7ea.wgsl.expected.wgsl b/test/tint/builtins/gen/floor/60d7ea.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/floor/60d7ea.wgsl.expected.wgsl
rename to test/tint/builtins/gen/floor/60d7ea.wgsl.expected.wgsl
diff --git a/test/builtins/gen/floor/66f154.wgsl b/test/tint/builtins/gen/floor/66f154.wgsl
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl
rename to test/tint/builtins/gen/floor/66f154.wgsl
diff --git a/test/builtins/gen/floor/66f154.wgsl.expected.glsl b/test/tint/builtins/gen/floor/66f154.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl.expected.glsl
rename to test/tint/builtins/gen/floor/66f154.wgsl.expected.glsl
diff --git a/test/builtins/gen/floor/66f154.wgsl.expected.hlsl b/test/tint/builtins/gen/floor/66f154.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl.expected.hlsl
rename to test/tint/builtins/gen/floor/66f154.wgsl.expected.hlsl
diff --git a/test/builtins/gen/floor/66f154.wgsl.expected.msl b/test/tint/builtins/gen/floor/66f154.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl.expected.msl
rename to test/tint/builtins/gen/floor/66f154.wgsl.expected.msl
diff --git a/test/builtins/gen/floor/66f154.wgsl.expected.spvasm b/test/tint/builtins/gen/floor/66f154.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl.expected.spvasm
rename to test/tint/builtins/gen/floor/66f154.wgsl.expected.spvasm
diff --git a/test/builtins/gen/floor/66f154.wgsl.expected.wgsl b/test/tint/builtins/gen/floor/66f154.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/floor/66f154.wgsl.expected.wgsl
rename to test/tint/builtins/gen/floor/66f154.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fma/26a7a9.wgsl b/test/tint/builtins/gen/fma/26a7a9.wgsl
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl
rename to test/tint/builtins/gen/fma/26a7a9.wgsl
diff --git a/test/builtins/gen/fma/26a7a9.wgsl.expected.glsl b/test/tint/builtins/gen/fma/26a7a9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl.expected.glsl
rename to test/tint/builtins/gen/fma/26a7a9.wgsl.expected.glsl
diff --git a/test/builtins/gen/fma/26a7a9.wgsl.expected.hlsl b/test/tint/builtins/gen/fma/26a7a9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fma/26a7a9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fma/26a7a9.wgsl.expected.msl b/test/tint/builtins/gen/fma/26a7a9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl.expected.msl
rename to test/tint/builtins/gen/fma/26a7a9.wgsl.expected.msl
diff --git a/test/builtins/gen/fma/26a7a9.wgsl.expected.spvasm b/test/tint/builtins/gen/fma/26a7a9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fma/26a7a9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fma/26a7a9.wgsl.expected.wgsl b/test/tint/builtins/gen/fma/26a7a9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fma/26a7a9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fma/26a7a9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fma/6a3283.wgsl b/test/tint/builtins/gen/fma/6a3283.wgsl
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl
rename to test/tint/builtins/gen/fma/6a3283.wgsl
diff --git a/test/builtins/gen/fma/6a3283.wgsl.expected.glsl b/test/tint/builtins/gen/fma/6a3283.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl.expected.glsl
rename to test/tint/builtins/gen/fma/6a3283.wgsl.expected.glsl
diff --git a/test/builtins/gen/fma/6a3283.wgsl.expected.hlsl b/test/tint/builtins/gen/fma/6a3283.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fma/6a3283.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fma/6a3283.wgsl.expected.msl b/test/tint/builtins/gen/fma/6a3283.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl.expected.msl
rename to test/tint/builtins/gen/fma/6a3283.wgsl.expected.msl
diff --git a/test/builtins/gen/fma/6a3283.wgsl.expected.spvasm b/test/tint/builtins/gen/fma/6a3283.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fma/6a3283.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fma/6a3283.wgsl.expected.wgsl b/test/tint/builtins/gen/fma/6a3283.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fma/6a3283.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fma/6a3283.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fma/c10ba3.wgsl b/test/tint/builtins/gen/fma/c10ba3.wgsl
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl
rename to test/tint/builtins/gen/fma/c10ba3.wgsl
diff --git a/test/builtins/gen/fma/c10ba3.wgsl.expected.glsl b/test/tint/builtins/gen/fma/c10ba3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl.expected.glsl
rename to test/tint/builtins/gen/fma/c10ba3.wgsl.expected.glsl
diff --git a/test/builtins/gen/fma/c10ba3.wgsl.expected.hlsl b/test/tint/builtins/gen/fma/c10ba3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fma/c10ba3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fma/c10ba3.wgsl.expected.msl b/test/tint/builtins/gen/fma/c10ba3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl.expected.msl
rename to test/tint/builtins/gen/fma/c10ba3.wgsl.expected.msl
diff --git a/test/builtins/gen/fma/c10ba3.wgsl.expected.spvasm b/test/tint/builtins/gen/fma/c10ba3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fma/c10ba3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fma/c10ba3.wgsl.expected.wgsl b/test/tint/builtins/gen/fma/c10ba3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fma/c10ba3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fma/c10ba3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fma/e17c5c.wgsl b/test/tint/builtins/gen/fma/e17c5c.wgsl
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl
rename to test/tint/builtins/gen/fma/e17c5c.wgsl
diff --git a/test/builtins/gen/fma/e17c5c.wgsl.expected.glsl b/test/tint/builtins/gen/fma/e17c5c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl.expected.glsl
rename to test/tint/builtins/gen/fma/e17c5c.wgsl.expected.glsl
diff --git a/test/builtins/gen/fma/e17c5c.wgsl.expected.hlsl b/test/tint/builtins/gen/fma/e17c5c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fma/e17c5c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fma/e17c5c.wgsl.expected.msl b/test/tint/builtins/gen/fma/e17c5c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl.expected.msl
rename to test/tint/builtins/gen/fma/e17c5c.wgsl.expected.msl
diff --git a/test/builtins/gen/fma/e17c5c.wgsl.expected.spvasm b/test/tint/builtins/gen/fma/e17c5c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fma/e17c5c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fma/e17c5c.wgsl.expected.wgsl b/test/tint/builtins/gen/fma/e17c5c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fma/e17c5c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fma/e17c5c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl b/test/tint/builtins/gen/fract/8bc1e9.wgsl
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl.expected.glsl b/test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl.expected.glsl
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.glsl
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl.expected.hlsl b/test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl.expected.msl b/test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl.expected.msl
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.msl
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl.expected.spvasm b/test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fract/8bc1e9.wgsl.expected.wgsl b/test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fract/8bc1e9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fract/8bc1e9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fract/943cb1.wgsl b/test/tint/builtins/gen/fract/943cb1.wgsl
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl
rename to test/tint/builtins/gen/fract/943cb1.wgsl
diff --git a/test/builtins/gen/fract/943cb1.wgsl.expected.glsl b/test/tint/builtins/gen/fract/943cb1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl.expected.glsl
rename to test/tint/builtins/gen/fract/943cb1.wgsl.expected.glsl
diff --git a/test/builtins/gen/fract/943cb1.wgsl.expected.hlsl b/test/tint/builtins/gen/fract/943cb1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fract/943cb1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fract/943cb1.wgsl.expected.msl b/test/tint/builtins/gen/fract/943cb1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl.expected.msl
rename to test/tint/builtins/gen/fract/943cb1.wgsl.expected.msl
diff --git a/test/builtins/gen/fract/943cb1.wgsl.expected.spvasm b/test/tint/builtins/gen/fract/943cb1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fract/943cb1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fract/943cb1.wgsl.expected.wgsl b/test/tint/builtins/gen/fract/943cb1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fract/943cb1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fract/943cb1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fract/a49758.wgsl b/test/tint/builtins/gen/fract/a49758.wgsl
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl
rename to test/tint/builtins/gen/fract/a49758.wgsl
diff --git a/test/builtins/gen/fract/a49758.wgsl.expected.glsl b/test/tint/builtins/gen/fract/a49758.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl.expected.glsl
rename to test/tint/builtins/gen/fract/a49758.wgsl.expected.glsl
diff --git a/test/builtins/gen/fract/a49758.wgsl.expected.hlsl b/test/tint/builtins/gen/fract/a49758.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fract/a49758.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fract/a49758.wgsl.expected.msl b/test/tint/builtins/gen/fract/a49758.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl.expected.msl
rename to test/tint/builtins/gen/fract/a49758.wgsl.expected.msl
diff --git a/test/builtins/gen/fract/a49758.wgsl.expected.spvasm b/test/tint/builtins/gen/fract/a49758.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fract/a49758.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fract/a49758.wgsl.expected.wgsl b/test/tint/builtins/gen/fract/a49758.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fract/a49758.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fract/a49758.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fract/fa5c71.wgsl b/test/tint/builtins/gen/fract/fa5c71.wgsl
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl
rename to test/tint/builtins/gen/fract/fa5c71.wgsl
diff --git a/test/builtins/gen/fract/fa5c71.wgsl.expected.glsl b/test/tint/builtins/gen/fract/fa5c71.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl.expected.glsl
rename to test/tint/builtins/gen/fract/fa5c71.wgsl.expected.glsl
diff --git a/test/builtins/gen/fract/fa5c71.wgsl.expected.hlsl b/test/tint/builtins/gen/fract/fa5c71.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fract/fa5c71.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fract/fa5c71.wgsl.expected.msl b/test/tint/builtins/gen/fract/fa5c71.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl.expected.msl
rename to test/tint/builtins/gen/fract/fa5c71.wgsl.expected.msl
diff --git a/test/builtins/gen/fract/fa5c71.wgsl.expected.spvasm b/test/tint/builtins/gen/fract/fa5c71.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fract/fa5c71.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fract/fa5c71.wgsl.expected.wgsl b/test/tint/builtins/gen/fract/fa5c71.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fract/fa5c71.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fract/fa5c71.wgsl.expected.wgsl
diff --git a/test/builtins/gen/frexp/368997.wgsl b/test/tint/builtins/gen/frexp/368997.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl
rename to test/tint/builtins/gen/frexp/368997.wgsl
diff --git a/test/builtins/gen/frexp/368997.wgsl.expected.glsl b/test/tint/builtins/gen/frexp/368997.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl.expected.glsl
rename to test/tint/builtins/gen/frexp/368997.wgsl.expected.glsl
diff --git a/test/builtins/gen/frexp/368997.wgsl.expected.hlsl b/test/tint/builtins/gen/frexp/368997.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl.expected.hlsl
rename to test/tint/builtins/gen/frexp/368997.wgsl.expected.hlsl
diff --git a/test/builtins/gen/frexp/368997.wgsl.expected.msl b/test/tint/builtins/gen/frexp/368997.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl.expected.msl
rename to test/tint/builtins/gen/frexp/368997.wgsl.expected.msl
diff --git a/test/builtins/gen/frexp/368997.wgsl.expected.spvasm b/test/tint/builtins/gen/frexp/368997.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl.expected.spvasm
rename to test/tint/builtins/gen/frexp/368997.wgsl.expected.spvasm
diff --git a/test/builtins/gen/frexp/368997.wgsl.expected.wgsl b/test/tint/builtins/gen/frexp/368997.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/368997.wgsl.expected.wgsl
rename to test/tint/builtins/gen/frexp/368997.wgsl.expected.wgsl
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl b/test/tint/builtins/gen/frexp/3c4f48.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl.expected.glsl b/test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl.expected.glsl
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.glsl
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl.expected.hlsl b/test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl.expected.hlsl
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.hlsl
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl.expected.msl b/test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl.expected.msl
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.msl
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl.expected.spvasm b/test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl.expected.spvasm
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.spvasm
diff --git a/test/builtins/gen/frexp/3c4f48.wgsl.expected.wgsl b/test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/3c4f48.wgsl.expected.wgsl
rename to test/tint/builtins/gen/frexp/3c4f48.wgsl.expected.wgsl
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl b/test/tint/builtins/gen/frexp/4bdfc7.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl.expected.glsl b/test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl.expected.glsl
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.glsl
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl.expected.hlsl b/test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl.expected.msl b/test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl.expected.msl
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.msl
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl.expected.spvasm b/test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/frexp/4bdfc7.wgsl.expected.wgsl b/test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/4bdfc7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/frexp/4bdfc7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/frexp/eabd40.wgsl b/test/tint/builtins/gen/frexp/eabd40.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl
rename to test/tint/builtins/gen/frexp/eabd40.wgsl
diff --git a/test/builtins/gen/frexp/eabd40.wgsl.expected.glsl b/test/tint/builtins/gen/frexp/eabd40.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl.expected.glsl
rename to test/tint/builtins/gen/frexp/eabd40.wgsl.expected.glsl
diff --git a/test/builtins/gen/frexp/eabd40.wgsl.expected.hlsl b/test/tint/builtins/gen/frexp/eabd40.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl.expected.hlsl
rename to test/tint/builtins/gen/frexp/eabd40.wgsl.expected.hlsl
diff --git a/test/builtins/gen/frexp/eabd40.wgsl.expected.msl b/test/tint/builtins/gen/frexp/eabd40.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl.expected.msl
rename to test/tint/builtins/gen/frexp/eabd40.wgsl.expected.msl
diff --git a/test/builtins/gen/frexp/eabd40.wgsl.expected.spvasm b/test/tint/builtins/gen/frexp/eabd40.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl.expected.spvasm
rename to test/tint/builtins/gen/frexp/eabd40.wgsl.expected.spvasm
diff --git a/test/builtins/gen/frexp/eabd40.wgsl.expected.wgsl b/test/tint/builtins/gen/frexp/eabd40.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/frexp/eabd40.wgsl.expected.wgsl
rename to test/tint/builtins/gen/frexp/eabd40.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl b/test/tint/builtins/gen/fwidth/5d1b39.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl.expected.glsl b/test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl.expected.msl b/test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidth/5d1b39.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/5d1b39.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidth/5d1b39.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl b/test/tint/builtins/gen/fwidth/b83ebb.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl.expected.glsl b/test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl.expected.msl b/test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidth/b83ebb.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/b83ebb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidth/b83ebb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl.expected.glsl b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl.expected.msl b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidth/d2ab9a.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/d2ab9a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidth/d2ab9a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl b/test/tint/builtins/gen/fwidth/df38ef.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl.expected.glsl b/test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl.expected.msl b/test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidth/df38ef.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidth/df38ef.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidth/df38ef.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.msl b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/159c8a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.msl b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/1e59d9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.msl b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/4e4fc4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.msl b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthCoarse/e653f7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl.expected.msl b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthFine/523fdc.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/523fdc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthFine/523fdc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.msl b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/68f4ef.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthFine/68f4ef.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl.expected.msl b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthFine/f1742d.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/f1742d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthFine/f1742d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.glsl b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.glsl
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.glsl
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.hlsl b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.msl b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.msl
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.msl
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.spvasm b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.wgsl b/test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/fwidthFine/ff6aa0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl.expected.glsl b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl.expected.glsl
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.glsl
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl.expected.hlsl b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl.expected.msl b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl.expected.msl
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.msl
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl.expected.spvasm b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/inverseSqrt/84407e.wgsl.expected.wgsl b/test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/84407e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/inverseSqrt/84407e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.glsl b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.glsl
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.glsl
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.hlsl b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.msl b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.msl
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.msl
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.spvasm b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.wgsl b/test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/inverseSqrt/8f2bd2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.glsl b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.glsl
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.glsl
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.hlsl b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.msl b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.msl
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.msl
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.spvasm b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.wgsl b/test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/b197b1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/inverseSqrt/b197b1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl.expected.glsl b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl.expected.glsl
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.glsl
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl.expected.hlsl b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl.expected.hlsl
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.hlsl
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl.expected.msl b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl.expected.msl
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.msl
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl.expected.spvasm b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl.expected.spvasm
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.spvasm
diff --git a/test/builtins/gen/inverseSqrt/c22347.wgsl.expected.wgsl b/test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/inverseSqrt/c22347.wgsl.expected.wgsl
rename to test/tint/builtins/gen/inverseSqrt/c22347.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl b/test/tint/builtins/gen/isFinite/34d32b.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl.expected.glsl b/test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl.expected.glsl
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.glsl
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl.expected.hlsl b/test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl.expected.msl b/test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl.expected.msl
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.msl
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl.expected.spvasm b/test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isFinite/34d32b.wgsl.expected.wgsl b/test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/34d32b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isFinite/34d32b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl b/test/tint/builtins/gen/isFinite/426f9f.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl.expected.glsl b/test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl.expected.glsl
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.glsl
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl.expected.hlsl b/test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl.expected.msl b/test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl.expected.msl
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.msl
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl.expected.spvasm b/test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isFinite/426f9f.wgsl.expected.wgsl b/test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/426f9f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isFinite/426f9f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl b/test/tint/builtins/gen/isFinite/8a23ad.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl.expected.glsl b/test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl.expected.glsl
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.glsl
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl.expected.hlsl b/test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl.expected.msl b/test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl.expected.msl
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.msl
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl.expected.spvasm b/test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isFinite/8a23ad.wgsl.expected.wgsl b/test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/8a23ad.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isFinite/8a23ad.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isFinite/f31987.wgsl b/test/tint/builtins/gen/isFinite/f31987.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl
rename to test/tint/builtins/gen/isFinite/f31987.wgsl
diff --git a/test/builtins/gen/isFinite/f31987.wgsl.expected.glsl b/test/tint/builtins/gen/isFinite/f31987.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl.expected.glsl
rename to test/tint/builtins/gen/isFinite/f31987.wgsl.expected.glsl
diff --git a/test/builtins/gen/isFinite/f31987.wgsl.expected.hlsl b/test/tint/builtins/gen/isFinite/f31987.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isFinite/f31987.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isFinite/f31987.wgsl.expected.msl b/test/tint/builtins/gen/isFinite/f31987.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl.expected.msl
rename to test/tint/builtins/gen/isFinite/f31987.wgsl.expected.msl
diff --git a/test/builtins/gen/isFinite/f31987.wgsl.expected.spvasm b/test/tint/builtins/gen/isFinite/f31987.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isFinite/f31987.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isFinite/f31987.wgsl.expected.wgsl b/test/tint/builtins/gen/isFinite/f31987.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isFinite/f31987.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isFinite/f31987.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isInf/666f2a.wgsl b/test/tint/builtins/gen/isInf/666f2a.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl
rename to test/tint/builtins/gen/isInf/666f2a.wgsl
diff --git a/test/builtins/gen/isInf/666f2a.wgsl.expected.glsl b/test/tint/builtins/gen/isInf/666f2a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl.expected.glsl
rename to test/tint/builtins/gen/isInf/666f2a.wgsl.expected.glsl
diff --git a/test/builtins/gen/isInf/666f2a.wgsl.expected.hlsl b/test/tint/builtins/gen/isInf/666f2a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isInf/666f2a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isInf/666f2a.wgsl.expected.msl b/test/tint/builtins/gen/isInf/666f2a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl.expected.msl
rename to test/tint/builtins/gen/isInf/666f2a.wgsl.expected.msl
diff --git a/test/builtins/gen/isInf/666f2a.wgsl.expected.spvasm b/test/tint/builtins/gen/isInf/666f2a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isInf/666f2a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isInf/666f2a.wgsl.expected.wgsl b/test/tint/builtins/gen/isInf/666f2a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/666f2a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isInf/666f2a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl b/test/tint/builtins/gen/isInf/7bd98f.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl.expected.glsl b/test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl.expected.glsl
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.glsl
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl.expected.hlsl b/test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl.expected.msl b/test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl.expected.msl
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.msl
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl.expected.spvasm b/test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isInf/7bd98f.wgsl.expected.wgsl b/test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/7bd98f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isInf/7bd98f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl b/test/tint/builtins/gen/isInf/7e81b5.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl.expected.glsl b/test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl.expected.glsl
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.glsl
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl.expected.hlsl b/test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl.expected.msl b/test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl.expected.msl
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.msl
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl.expected.spvasm b/test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isInf/7e81b5.wgsl.expected.wgsl b/test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/7e81b5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isInf/7e81b5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl b/test/tint/builtins/gen/isInf/a46d6f.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl.expected.glsl b/test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl.expected.glsl
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.glsl
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl.expected.hlsl b/test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl.expected.msl b/test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl.expected.msl
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.msl
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl.expected.spvasm b/test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isInf/a46d6f.wgsl.expected.wgsl b/test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isInf/a46d6f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isInf/a46d6f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNan/1280ab.wgsl b/test/tint/builtins/gen/isNan/1280ab.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl
rename to test/tint/builtins/gen/isNan/1280ab.wgsl
diff --git a/test/builtins/gen/isNan/1280ab.wgsl.expected.glsl b/test/tint/builtins/gen/isNan/1280ab.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNan/1280ab.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNan/1280ab.wgsl.expected.hlsl b/test/tint/builtins/gen/isNan/1280ab.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNan/1280ab.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNan/1280ab.wgsl.expected.msl b/test/tint/builtins/gen/isNan/1280ab.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl.expected.msl
rename to test/tint/builtins/gen/isNan/1280ab.wgsl.expected.msl
diff --git a/test/builtins/gen/isNan/1280ab.wgsl.expected.spvasm b/test/tint/builtins/gen/isNan/1280ab.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNan/1280ab.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNan/1280ab.wgsl.expected.wgsl b/test/tint/builtins/gen/isNan/1280ab.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/1280ab.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNan/1280ab.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNan/4d280d.wgsl b/test/tint/builtins/gen/isNan/4d280d.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl
rename to test/tint/builtins/gen/isNan/4d280d.wgsl
diff --git a/test/builtins/gen/isNan/4d280d.wgsl.expected.glsl b/test/tint/builtins/gen/isNan/4d280d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNan/4d280d.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNan/4d280d.wgsl.expected.hlsl b/test/tint/builtins/gen/isNan/4d280d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNan/4d280d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNan/4d280d.wgsl.expected.msl b/test/tint/builtins/gen/isNan/4d280d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl.expected.msl
rename to test/tint/builtins/gen/isNan/4d280d.wgsl.expected.msl
diff --git a/test/builtins/gen/isNan/4d280d.wgsl.expected.spvasm b/test/tint/builtins/gen/isNan/4d280d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNan/4d280d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNan/4d280d.wgsl.expected.wgsl b/test/tint/builtins/gen/isNan/4d280d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/4d280d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNan/4d280d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl b/test/tint/builtins/gen/isNan/67ecd3.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl.expected.glsl b/test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl.expected.hlsl b/test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl.expected.msl b/test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl.expected.msl
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.msl
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl.expected.spvasm b/test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNan/67ecd3.wgsl.expected.wgsl b/test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/67ecd3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNan/67ecd3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNan/e4978e.wgsl b/test/tint/builtins/gen/isNan/e4978e.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl
rename to test/tint/builtins/gen/isNan/e4978e.wgsl
diff --git a/test/builtins/gen/isNan/e4978e.wgsl.expected.glsl b/test/tint/builtins/gen/isNan/e4978e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNan/e4978e.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNan/e4978e.wgsl.expected.hlsl b/test/tint/builtins/gen/isNan/e4978e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNan/e4978e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNan/e4978e.wgsl.expected.msl b/test/tint/builtins/gen/isNan/e4978e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl.expected.msl
rename to test/tint/builtins/gen/isNan/e4978e.wgsl.expected.msl
diff --git a/test/builtins/gen/isNan/e4978e.wgsl.expected.spvasm b/test/tint/builtins/gen/isNan/e4978e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNan/e4978e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNan/e4978e.wgsl.expected.wgsl b/test/tint/builtins/gen/isNan/e4978e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNan/e4978e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNan/e4978e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl b/test/tint/builtins/gen/isNormal/863dcd.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl.expected.glsl b/test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl.expected.hlsl b/test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl.expected.msl b/test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl.expected.msl
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.msl
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl.expected.spvasm b/test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNormal/863dcd.wgsl.expected.wgsl b/test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/863dcd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNormal/863dcd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl b/test/tint/builtins/gen/isNormal/b00ab1.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl.expected.glsl b/test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl.expected.hlsl b/test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl.expected.msl b/test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl.expected.msl
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.msl
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl.expected.spvasm b/test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNormal/b00ab1.wgsl.expected.wgsl b/test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/b00ab1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNormal/b00ab1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl b/test/tint/builtins/gen/isNormal/c286b7.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl.expected.glsl b/test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl.expected.hlsl b/test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl.expected.msl b/test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl.expected.msl
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.msl
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl.expected.spvasm b/test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNormal/c286b7.wgsl.expected.wgsl b/test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/c286b7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNormal/c286b7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl b/test/tint/builtins/gen/isNormal/c6e880.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl.expected.glsl b/test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl.expected.glsl
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.glsl
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl.expected.hlsl b/test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl.expected.hlsl
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.hlsl
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl.expected.msl b/test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl.expected.msl
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.msl
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl.expected.spvasm b/test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl.expected.spvasm
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.spvasm
diff --git a/test/builtins/gen/isNormal/c6e880.wgsl.expected.wgsl b/test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/isNormal/c6e880.wgsl.expected.wgsl
rename to test/tint/builtins/gen/isNormal/c6e880.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl b/test/tint/builtins/gen/ldexp/a31cdc.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl.expected.glsl b/test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl.expected.glsl
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.glsl
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl.expected.hlsl b/test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl.expected.msl b/test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl.expected.msl
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.msl
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl.expected.spvasm b/test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ldexp/a31cdc.wgsl.expected.wgsl b/test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/a31cdc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ldexp/a31cdc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ldexp/abd718.wgsl b/test/tint/builtins/gen/ldexp/abd718.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl
rename to test/tint/builtins/gen/ldexp/abd718.wgsl
diff --git a/test/builtins/gen/ldexp/abd718.wgsl.expected.glsl b/test/tint/builtins/gen/ldexp/abd718.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl.expected.glsl
rename to test/tint/builtins/gen/ldexp/abd718.wgsl.expected.glsl
diff --git a/test/builtins/gen/ldexp/abd718.wgsl.expected.hlsl b/test/tint/builtins/gen/ldexp/abd718.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ldexp/abd718.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ldexp/abd718.wgsl.expected.msl b/test/tint/builtins/gen/ldexp/abd718.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl.expected.msl
rename to test/tint/builtins/gen/ldexp/abd718.wgsl.expected.msl
diff --git a/test/builtins/gen/ldexp/abd718.wgsl.expected.spvasm b/test/tint/builtins/gen/ldexp/abd718.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ldexp/abd718.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ldexp/abd718.wgsl.expected.wgsl b/test/tint/builtins/gen/ldexp/abd718.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/abd718.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ldexp/abd718.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl b/test/tint/builtins/gen/ldexp/cc9cde.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl.expected.glsl b/test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl.expected.glsl
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.glsl
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl.expected.hlsl b/test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl.expected.msl b/test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl.expected.msl
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.msl
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl.expected.spvasm b/test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ldexp/cc9cde.wgsl.expected.wgsl b/test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/cc9cde.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ldexp/cc9cde.wgsl.expected.wgsl
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl b/test/tint/builtins/gen/ldexp/db8b49.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl.expected.glsl b/test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl.expected.glsl
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.glsl
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl.expected.hlsl b/test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl.expected.hlsl
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.hlsl
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl.expected.msl b/test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl.expected.msl
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.msl
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl.expected.spvasm b/test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl.expected.spvasm
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.spvasm
diff --git a/test/builtins/gen/ldexp/db8b49.wgsl.expected.wgsl b/test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/ldexp/db8b49.wgsl.expected.wgsl
rename to test/tint/builtins/gen/ldexp/db8b49.wgsl.expected.wgsl
diff --git a/test/builtins/gen/length/056071.wgsl b/test/tint/builtins/gen/length/056071.wgsl
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl
rename to test/tint/builtins/gen/length/056071.wgsl
diff --git a/test/builtins/gen/length/056071.wgsl.expected.glsl b/test/tint/builtins/gen/length/056071.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl.expected.glsl
rename to test/tint/builtins/gen/length/056071.wgsl.expected.glsl
diff --git a/test/builtins/gen/length/056071.wgsl.expected.hlsl b/test/tint/builtins/gen/length/056071.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl.expected.hlsl
rename to test/tint/builtins/gen/length/056071.wgsl.expected.hlsl
diff --git a/test/builtins/gen/length/056071.wgsl.expected.msl b/test/tint/builtins/gen/length/056071.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl.expected.msl
rename to test/tint/builtins/gen/length/056071.wgsl.expected.msl
diff --git a/test/builtins/gen/length/056071.wgsl.expected.spvasm b/test/tint/builtins/gen/length/056071.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl.expected.spvasm
rename to test/tint/builtins/gen/length/056071.wgsl.expected.spvasm
diff --git a/test/builtins/gen/length/056071.wgsl.expected.wgsl b/test/tint/builtins/gen/length/056071.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/length/056071.wgsl.expected.wgsl
rename to test/tint/builtins/gen/length/056071.wgsl.expected.wgsl
diff --git a/test/builtins/gen/length/602a17.wgsl b/test/tint/builtins/gen/length/602a17.wgsl
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl
rename to test/tint/builtins/gen/length/602a17.wgsl
diff --git a/test/builtins/gen/length/602a17.wgsl.expected.glsl b/test/tint/builtins/gen/length/602a17.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl.expected.glsl
rename to test/tint/builtins/gen/length/602a17.wgsl.expected.glsl
diff --git a/test/builtins/gen/length/602a17.wgsl.expected.hlsl b/test/tint/builtins/gen/length/602a17.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl.expected.hlsl
rename to test/tint/builtins/gen/length/602a17.wgsl.expected.hlsl
diff --git a/test/builtins/gen/length/602a17.wgsl.expected.msl b/test/tint/builtins/gen/length/602a17.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl.expected.msl
rename to test/tint/builtins/gen/length/602a17.wgsl.expected.msl
diff --git a/test/builtins/gen/length/602a17.wgsl.expected.spvasm b/test/tint/builtins/gen/length/602a17.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl.expected.spvasm
rename to test/tint/builtins/gen/length/602a17.wgsl.expected.spvasm
diff --git a/test/builtins/gen/length/602a17.wgsl.expected.wgsl b/test/tint/builtins/gen/length/602a17.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/length/602a17.wgsl.expected.wgsl
rename to test/tint/builtins/gen/length/602a17.wgsl.expected.wgsl
diff --git a/test/builtins/gen/length/afde8b.wgsl b/test/tint/builtins/gen/length/afde8b.wgsl
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl
rename to test/tint/builtins/gen/length/afde8b.wgsl
diff --git a/test/builtins/gen/length/afde8b.wgsl.expected.glsl b/test/tint/builtins/gen/length/afde8b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl.expected.glsl
rename to test/tint/builtins/gen/length/afde8b.wgsl.expected.glsl
diff --git a/test/builtins/gen/length/afde8b.wgsl.expected.hlsl b/test/tint/builtins/gen/length/afde8b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/length/afde8b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/length/afde8b.wgsl.expected.msl b/test/tint/builtins/gen/length/afde8b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl.expected.msl
rename to test/tint/builtins/gen/length/afde8b.wgsl.expected.msl
diff --git a/test/builtins/gen/length/afde8b.wgsl.expected.spvasm b/test/tint/builtins/gen/length/afde8b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/length/afde8b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/length/afde8b.wgsl.expected.wgsl b/test/tint/builtins/gen/length/afde8b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/length/afde8b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/length/afde8b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/length/becebf.wgsl b/test/tint/builtins/gen/length/becebf.wgsl
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl
rename to test/tint/builtins/gen/length/becebf.wgsl
diff --git a/test/builtins/gen/length/becebf.wgsl.expected.glsl b/test/tint/builtins/gen/length/becebf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl.expected.glsl
rename to test/tint/builtins/gen/length/becebf.wgsl.expected.glsl
diff --git a/test/builtins/gen/length/becebf.wgsl.expected.hlsl b/test/tint/builtins/gen/length/becebf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/length/becebf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/length/becebf.wgsl.expected.msl b/test/tint/builtins/gen/length/becebf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl.expected.msl
rename to test/tint/builtins/gen/length/becebf.wgsl.expected.msl
diff --git a/test/builtins/gen/length/becebf.wgsl.expected.spvasm b/test/tint/builtins/gen/length/becebf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/length/becebf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/length/becebf.wgsl.expected.wgsl b/test/tint/builtins/gen/length/becebf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/length/becebf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/length/becebf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log/3da25a.wgsl b/test/tint/builtins/gen/log/3da25a.wgsl
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl
rename to test/tint/builtins/gen/log/3da25a.wgsl
diff --git a/test/builtins/gen/log/3da25a.wgsl.expected.glsl b/test/tint/builtins/gen/log/3da25a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl.expected.glsl
rename to test/tint/builtins/gen/log/3da25a.wgsl.expected.glsl
diff --git a/test/builtins/gen/log/3da25a.wgsl.expected.hlsl b/test/tint/builtins/gen/log/3da25a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log/3da25a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log/3da25a.wgsl.expected.msl b/test/tint/builtins/gen/log/3da25a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl.expected.msl
rename to test/tint/builtins/gen/log/3da25a.wgsl.expected.msl
diff --git a/test/builtins/gen/log/3da25a.wgsl.expected.spvasm b/test/tint/builtins/gen/log/3da25a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log/3da25a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log/3da25a.wgsl.expected.wgsl b/test/tint/builtins/gen/log/3da25a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log/3da25a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log/3da25a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log/7114a6.wgsl b/test/tint/builtins/gen/log/7114a6.wgsl
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl
rename to test/tint/builtins/gen/log/7114a6.wgsl
diff --git a/test/builtins/gen/log/7114a6.wgsl.expected.glsl b/test/tint/builtins/gen/log/7114a6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl.expected.glsl
rename to test/tint/builtins/gen/log/7114a6.wgsl.expected.glsl
diff --git a/test/builtins/gen/log/7114a6.wgsl.expected.hlsl b/test/tint/builtins/gen/log/7114a6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log/7114a6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log/7114a6.wgsl.expected.msl b/test/tint/builtins/gen/log/7114a6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl.expected.msl
rename to test/tint/builtins/gen/log/7114a6.wgsl.expected.msl
diff --git a/test/builtins/gen/log/7114a6.wgsl.expected.spvasm b/test/tint/builtins/gen/log/7114a6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log/7114a6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log/7114a6.wgsl.expected.wgsl b/test/tint/builtins/gen/log/7114a6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log/7114a6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log/7114a6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log/b2ce28.wgsl b/test/tint/builtins/gen/log/b2ce28.wgsl
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl
rename to test/tint/builtins/gen/log/b2ce28.wgsl
diff --git a/test/builtins/gen/log/b2ce28.wgsl.expected.glsl b/test/tint/builtins/gen/log/b2ce28.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl.expected.glsl
rename to test/tint/builtins/gen/log/b2ce28.wgsl.expected.glsl
diff --git a/test/builtins/gen/log/b2ce28.wgsl.expected.hlsl b/test/tint/builtins/gen/log/b2ce28.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log/b2ce28.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log/b2ce28.wgsl.expected.msl b/test/tint/builtins/gen/log/b2ce28.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl.expected.msl
rename to test/tint/builtins/gen/log/b2ce28.wgsl.expected.msl
diff --git a/test/builtins/gen/log/b2ce28.wgsl.expected.spvasm b/test/tint/builtins/gen/log/b2ce28.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log/b2ce28.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log/b2ce28.wgsl.expected.wgsl b/test/tint/builtins/gen/log/b2ce28.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log/b2ce28.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log/b2ce28.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log/f4c570.wgsl b/test/tint/builtins/gen/log/f4c570.wgsl
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl
rename to test/tint/builtins/gen/log/f4c570.wgsl
diff --git a/test/builtins/gen/log/f4c570.wgsl.expected.glsl b/test/tint/builtins/gen/log/f4c570.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl.expected.glsl
rename to test/tint/builtins/gen/log/f4c570.wgsl.expected.glsl
diff --git a/test/builtins/gen/log/f4c570.wgsl.expected.hlsl b/test/tint/builtins/gen/log/f4c570.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log/f4c570.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log/f4c570.wgsl.expected.msl b/test/tint/builtins/gen/log/f4c570.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl.expected.msl
rename to test/tint/builtins/gen/log/f4c570.wgsl.expected.msl
diff --git a/test/builtins/gen/log/f4c570.wgsl.expected.spvasm b/test/tint/builtins/gen/log/f4c570.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log/f4c570.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log/f4c570.wgsl.expected.wgsl b/test/tint/builtins/gen/log/f4c570.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log/f4c570.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log/f4c570.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log2/4036ed.wgsl b/test/tint/builtins/gen/log2/4036ed.wgsl
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl
rename to test/tint/builtins/gen/log2/4036ed.wgsl
diff --git a/test/builtins/gen/log2/4036ed.wgsl.expected.glsl b/test/tint/builtins/gen/log2/4036ed.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl.expected.glsl
rename to test/tint/builtins/gen/log2/4036ed.wgsl.expected.glsl
diff --git a/test/builtins/gen/log2/4036ed.wgsl.expected.hlsl b/test/tint/builtins/gen/log2/4036ed.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log2/4036ed.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log2/4036ed.wgsl.expected.msl b/test/tint/builtins/gen/log2/4036ed.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl.expected.msl
rename to test/tint/builtins/gen/log2/4036ed.wgsl.expected.msl
diff --git a/test/builtins/gen/log2/4036ed.wgsl.expected.spvasm b/test/tint/builtins/gen/log2/4036ed.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log2/4036ed.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log2/4036ed.wgsl.expected.wgsl b/test/tint/builtins/gen/log2/4036ed.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log2/4036ed.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log2/4036ed.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log2/902988.wgsl b/test/tint/builtins/gen/log2/902988.wgsl
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl
rename to test/tint/builtins/gen/log2/902988.wgsl
diff --git a/test/builtins/gen/log2/902988.wgsl.expected.glsl b/test/tint/builtins/gen/log2/902988.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl.expected.glsl
rename to test/tint/builtins/gen/log2/902988.wgsl.expected.glsl
diff --git a/test/builtins/gen/log2/902988.wgsl.expected.hlsl b/test/tint/builtins/gen/log2/902988.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log2/902988.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log2/902988.wgsl.expected.msl b/test/tint/builtins/gen/log2/902988.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl.expected.msl
rename to test/tint/builtins/gen/log2/902988.wgsl.expected.msl
diff --git a/test/builtins/gen/log2/902988.wgsl.expected.spvasm b/test/tint/builtins/gen/log2/902988.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log2/902988.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log2/902988.wgsl.expected.wgsl b/test/tint/builtins/gen/log2/902988.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log2/902988.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log2/902988.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log2/adb233.wgsl b/test/tint/builtins/gen/log2/adb233.wgsl
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl
rename to test/tint/builtins/gen/log2/adb233.wgsl
diff --git a/test/builtins/gen/log2/adb233.wgsl.expected.glsl b/test/tint/builtins/gen/log2/adb233.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl.expected.glsl
rename to test/tint/builtins/gen/log2/adb233.wgsl.expected.glsl
diff --git a/test/builtins/gen/log2/adb233.wgsl.expected.hlsl b/test/tint/builtins/gen/log2/adb233.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log2/adb233.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log2/adb233.wgsl.expected.msl b/test/tint/builtins/gen/log2/adb233.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl.expected.msl
rename to test/tint/builtins/gen/log2/adb233.wgsl.expected.msl
diff --git a/test/builtins/gen/log2/adb233.wgsl.expected.spvasm b/test/tint/builtins/gen/log2/adb233.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log2/adb233.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log2/adb233.wgsl.expected.wgsl b/test/tint/builtins/gen/log2/adb233.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log2/adb233.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log2/adb233.wgsl.expected.wgsl
diff --git a/test/builtins/gen/log2/aea659.wgsl b/test/tint/builtins/gen/log2/aea659.wgsl
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl
rename to test/tint/builtins/gen/log2/aea659.wgsl
diff --git a/test/builtins/gen/log2/aea659.wgsl.expected.glsl b/test/tint/builtins/gen/log2/aea659.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl.expected.glsl
rename to test/tint/builtins/gen/log2/aea659.wgsl.expected.glsl
diff --git a/test/builtins/gen/log2/aea659.wgsl.expected.hlsl b/test/tint/builtins/gen/log2/aea659.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl.expected.hlsl
rename to test/tint/builtins/gen/log2/aea659.wgsl.expected.hlsl
diff --git a/test/builtins/gen/log2/aea659.wgsl.expected.msl b/test/tint/builtins/gen/log2/aea659.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl.expected.msl
rename to test/tint/builtins/gen/log2/aea659.wgsl.expected.msl
diff --git a/test/builtins/gen/log2/aea659.wgsl.expected.spvasm b/test/tint/builtins/gen/log2/aea659.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl.expected.spvasm
rename to test/tint/builtins/gen/log2/aea659.wgsl.expected.spvasm
diff --git a/test/builtins/gen/log2/aea659.wgsl.expected.wgsl b/test/tint/builtins/gen/log2/aea659.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/log2/aea659.wgsl.expected.wgsl
rename to test/tint/builtins/gen/log2/aea659.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/0c0aae.wgsl b/test/tint/builtins/gen/max/0c0aae.wgsl
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl
rename to test/tint/builtins/gen/max/0c0aae.wgsl
diff --git a/test/builtins/gen/max/0c0aae.wgsl.expected.glsl b/test/tint/builtins/gen/max/0c0aae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/0c0aae.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/0c0aae.wgsl.expected.hlsl b/test/tint/builtins/gen/max/0c0aae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/0c0aae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/0c0aae.wgsl.expected.msl b/test/tint/builtins/gen/max/0c0aae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl.expected.msl
rename to test/tint/builtins/gen/max/0c0aae.wgsl.expected.msl
diff --git a/test/builtins/gen/max/0c0aae.wgsl.expected.spvasm b/test/tint/builtins/gen/max/0c0aae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/0c0aae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/0c0aae.wgsl.expected.wgsl b/test/tint/builtins/gen/max/0c0aae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/0c0aae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/0c0aae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/25eafe.wgsl b/test/tint/builtins/gen/max/25eafe.wgsl
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl
rename to test/tint/builtins/gen/max/25eafe.wgsl
diff --git a/test/builtins/gen/max/25eafe.wgsl.expected.glsl b/test/tint/builtins/gen/max/25eafe.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/25eafe.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/25eafe.wgsl.expected.hlsl b/test/tint/builtins/gen/max/25eafe.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/25eafe.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/25eafe.wgsl.expected.msl b/test/tint/builtins/gen/max/25eafe.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl.expected.msl
rename to test/tint/builtins/gen/max/25eafe.wgsl.expected.msl
diff --git a/test/builtins/gen/max/25eafe.wgsl.expected.spvasm b/test/tint/builtins/gen/max/25eafe.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/25eafe.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/25eafe.wgsl.expected.wgsl b/test/tint/builtins/gen/max/25eafe.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/25eafe.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/25eafe.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/320815.wgsl b/test/tint/builtins/gen/max/320815.wgsl
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl
rename to test/tint/builtins/gen/max/320815.wgsl
diff --git a/test/builtins/gen/max/320815.wgsl.expected.glsl b/test/tint/builtins/gen/max/320815.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/320815.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/320815.wgsl.expected.hlsl b/test/tint/builtins/gen/max/320815.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/320815.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/320815.wgsl.expected.msl b/test/tint/builtins/gen/max/320815.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl.expected.msl
rename to test/tint/builtins/gen/max/320815.wgsl.expected.msl
diff --git a/test/builtins/gen/max/320815.wgsl.expected.spvasm b/test/tint/builtins/gen/max/320815.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/320815.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/320815.wgsl.expected.wgsl b/test/tint/builtins/gen/max/320815.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/320815.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/320815.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/44a39d.wgsl b/test/tint/builtins/gen/max/44a39d.wgsl
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl
rename to test/tint/builtins/gen/max/44a39d.wgsl
diff --git a/test/builtins/gen/max/44a39d.wgsl.expected.glsl b/test/tint/builtins/gen/max/44a39d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/44a39d.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/44a39d.wgsl.expected.hlsl b/test/tint/builtins/gen/max/44a39d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/44a39d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/44a39d.wgsl.expected.msl b/test/tint/builtins/gen/max/44a39d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl.expected.msl
rename to test/tint/builtins/gen/max/44a39d.wgsl.expected.msl
diff --git a/test/builtins/gen/max/44a39d.wgsl.expected.spvasm b/test/tint/builtins/gen/max/44a39d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/44a39d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/44a39d.wgsl.expected.wgsl b/test/tint/builtins/gen/max/44a39d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/44a39d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/44a39d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/453e04.wgsl b/test/tint/builtins/gen/max/453e04.wgsl
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl
rename to test/tint/builtins/gen/max/453e04.wgsl
diff --git a/test/builtins/gen/max/453e04.wgsl.expected.glsl b/test/tint/builtins/gen/max/453e04.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/453e04.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/453e04.wgsl.expected.hlsl b/test/tint/builtins/gen/max/453e04.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/453e04.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/453e04.wgsl.expected.msl b/test/tint/builtins/gen/max/453e04.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl.expected.msl
rename to test/tint/builtins/gen/max/453e04.wgsl.expected.msl
diff --git a/test/builtins/gen/max/453e04.wgsl.expected.spvasm b/test/tint/builtins/gen/max/453e04.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/453e04.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/453e04.wgsl.expected.wgsl b/test/tint/builtins/gen/max/453e04.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/453e04.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/453e04.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/462050.wgsl b/test/tint/builtins/gen/max/462050.wgsl
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl
rename to test/tint/builtins/gen/max/462050.wgsl
diff --git a/test/builtins/gen/max/462050.wgsl.expected.glsl b/test/tint/builtins/gen/max/462050.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/462050.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/462050.wgsl.expected.hlsl b/test/tint/builtins/gen/max/462050.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/462050.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/462050.wgsl.expected.msl b/test/tint/builtins/gen/max/462050.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl.expected.msl
rename to test/tint/builtins/gen/max/462050.wgsl.expected.msl
diff --git a/test/builtins/gen/max/462050.wgsl.expected.spvasm b/test/tint/builtins/gen/max/462050.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/462050.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/462050.wgsl.expected.wgsl b/test/tint/builtins/gen/max/462050.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/462050.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/462050.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/4883ac.wgsl b/test/tint/builtins/gen/max/4883ac.wgsl
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl
rename to test/tint/builtins/gen/max/4883ac.wgsl
diff --git a/test/builtins/gen/max/4883ac.wgsl.expected.glsl b/test/tint/builtins/gen/max/4883ac.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/4883ac.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/4883ac.wgsl.expected.hlsl b/test/tint/builtins/gen/max/4883ac.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/4883ac.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/4883ac.wgsl.expected.msl b/test/tint/builtins/gen/max/4883ac.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl.expected.msl
rename to test/tint/builtins/gen/max/4883ac.wgsl.expected.msl
diff --git a/test/builtins/gen/max/4883ac.wgsl.expected.spvasm b/test/tint/builtins/gen/max/4883ac.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/4883ac.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/4883ac.wgsl.expected.wgsl b/test/tint/builtins/gen/max/4883ac.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/4883ac.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/4883ac.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/85e6bc.wgsl b/test/tint/builtins/gen/max/85e6bc.wgsl
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl
rename to test/tint/builtins/gen/max/85e6bc.wgsl
diff --git a/test/builtins/gen/max/85e6bc.wgsl.expected.glsl b/test/tint/builtins/gen/max/85e6bc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/85e6bc.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/85e6bc.wgsl.expected.hlsl b/test/tint/builtins/gen/max/85e6bc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/85e6bc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/85e6bc.wgsl.expected.msl b/test/tint/builtins/gen/max/85e6bc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl.expected.msl
rename to test/tint/builtins/gen/max/85e6bc.wgsl.expected.msl
diff --git a/test/builtins/gen/max/85e6bc.wgsl.expected.spvasm b/test/tint/builtins/gen/max/85e6bc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/85e6bc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/85e6bc.wgsl.expected.wgsl b/test/tint/builtins/gen/max/85e6bc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/85e6bc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/85e6bc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/a93419.wgsl b/test/tint/builtins/gen/max/a93419.wgsl
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl
rename to test/tint/builtins/gen/max/a93419.wgsl
diff --git a/test/builtins/gen/max/a93419.wgsl.expected.glsl b/test/tint/builtins/gen/max/a93419.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/a93419.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/a93419.wgsl.expected.hlsl b/test/tint/builtins/gen/max/a93419.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/a93419.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/a93419.wgsl.expected.msl b/test/tint/builtins/gen/max/a93419.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl.expected.msl
rename to test/tint/builtins/gen/max/a93419.wgsl.expected.msl
diff --git a/test/builtins/gen/max/a93419.wgsl.expected.spvasm b/test/tint/builtins/gen/max/a93419.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/a93419.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/a93419.wgsl.expected.wgsl b/test/tint/builtins/gen/max/a93419.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/a93419.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/a93419.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/b1b73a.wgsl b/test/tint/builtins/gen/max/b1b73a.wgsl
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl
rename to test/tint/builtins/gen/max/b1b73a.wgsl
diff --git a/test/builtins/gen/max/b1b73a.wgsl.expected.glsl b/test/tint/builtins/gen/max/b1b73a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/b1b73a.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/b1b73a.wgsl.expected.hlsl b/test/tint/builtins/gen/max/b1b73a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/b1b73a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/b1b73a.wgsl.expected.msl b/test/tint/builtins/gen/max/b1b73a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl.expected.msl
rename to test/tint/builtins/gen/max/b1b73a.wgsl.expected.msl
diff --git a/test/builtins/gen/max/b1b73a.wgsl.expected.spvasm b/test/tint/builtins/gen/max/b1b73a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/b1b73a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/b1b73a.wgsl.expected.wgsl b/test/tint/builtins/gen/max/b1b73a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/b1b73a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/b1b73a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/ce7c30.wgsl b/test/tint/builtins/gen/max/ce7c30.wgsl
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl
rename to test/tint/builtins/gen/max/ce7c30.wgsl
diff --git a/test/builtins/gen/max/ce7c30.wgsl.expected.glsl b/test/tint/builtins/gen/max/ce7c30.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/ce7c30.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/ce7c30.wgsl.expected.hlsl b/test/tint/builtins/gen/max/ce7c30.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/ce7c30.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/ce7c30.wgsl.expected.msl b/test/tint/builtins/gen/max/ce7c30.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl.expected.msl
rename to test/tint/builtins/gen/max/ce7c30.wgsl.expected.msl
diff --git a/test/builtins/gen/max/ce7c30.wgsl.expected.spvasm b/test/tint/builtins/gen/max/ce7c30.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/ce7c30.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/ce7c30.wgsl.expected.wgsl b/test/tint/builtins/gen/max/ce7c30.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/ce7c30.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/ce7c30.wgsl.expected.wgsl
diff --git a/test/builtins/gen/max/e8192f.wgsl b/test/tint/builtins/gen/max/e8192f.wgsl
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl
rename to test/tint/builtins/gen/max/e8192f.wgsl
diff --git a/test/builtins/gen/max/e8192f.wgsl.expected.glsl b/test/tint/builtins/gen/max/e8192f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl.expected.glsl
rename to test/tint/builtins/gen/max/e8192f.wgsl.expected.glsl
diff --git a/test/builtins/gen/max/e8192f.wgsl.expected.hlsl b/test/tint/builtins/gen/max/e8192f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/max/e8192f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/max/e8192f.wgsl.expected.msl b/test/tint/builtins/gen/max/e8192f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl.expected.msl
rename to test/tint/builtins/gen/max/e8192f.wgsl.expected.msl
diff --git a/test/builtins/gen/max/e8192f.wgsl.expected.spvasm b/test/tint/builtins/gen/max/e8192f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/max/e8192f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/max/e8192f.wgsl.expected.wgsl b/test/tint/builtins/gen/max/e8192f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/max/e8192f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/max/e8192f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/03c7e3.wgsl b/test/tint/builtins/gen/min/03c7e3.wgsl
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl
rename to test/tint/builtins/gen/min/03c7e3.wgsl
diff --git a/test/builtins/gen/min/03c7e3.wgsl.expected.glsl b/test/tint/builtins/gen/min/03c7e3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/03c7e3.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/03c7e3.wgsl.expected.hlsl b/test/tint/builtins/gen/min/03c7e3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/03c7e3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/03c7e3.wgsl.expected.msl b/test/tint/builtins/gen/min/03c7e3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl.expected.msl
rename to test/tint/builtins/gen/min/03c7e3.wgsl.expected.msl
diff --git a/test/builtins/gen/min/03c7e3.wgsl.expected.spvasm b/test/tint/builtins/gen/min/03c7e3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/03c7e3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/03c7e3.wgsl.expected.wgsl b/test/tint/builtins/gen/min/03c7e3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/03c7e3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/03c7e3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/0dc614.wgsl b/test/tint/builtins/gen/min/0dc614.wgsl
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl
rename to test/tint/builtins/gen/min/0dc614.wgsl
diff --git a/test/builtins/gen/min/0dc614.wgsl.expected.glsl b/test/tint/builtins/gen/min/0dc614.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/0dc614.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/0dc614.wgsl.expected.hlsl b/test/tint/builtins/gen/min/0dc614.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/0dc614.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/0dc614.wgsl.expected.msl b/test/tint/builtins/gen/min/0dc614.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl.expected.msl
rename to test/tint/builtins/gen/min/0dc614.wgsl.expected.msl
diff --git a/test/builtins/gen/min/0dc614.wgsl.expected.spvasm b/test/tint/builtins/gen/min/0dc614.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/0dc614.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/0dc614.wgsl.expected.wgsl b/test/tint/builtins/gen/min/0dc614.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/0dc614.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/0dc614.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/3941e1.wgsl b/test/tint/builtins/gen/min/3941e1.wgsl
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl
rename to test/tint/builtins/gen/min/3941e1.wgsl
diff --git a/test/builtins/gen/min/3941e1.wgsl.expected.glsl b/test/tint/builtins/gen/min/3941e1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/3941e1.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/3941e1.wgsl.expected.hlsl b/test/tint/builtins/gen/min/3941e1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/3941e1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/3941e1.wgsl.expected.msl b/test/tint/builtins/gen/min/3941e1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl.expected.msl
rename to test/tint/builtins/gen/min/3941e1.wgsl.expected.msl
diff --git a/test/builtins/gen/min/3941e1.wgsl.expected.spvasm b/test/tint/builtins/gen/min/3941e1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/3941e1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/3941e1.wgsl.expected.wgsl b/test/tint/builtins/gen/min/3941e1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/3941e1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/3941e1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/46c5d3.wgsl b/test/tint/builtins/gen/min/46c5d3.wgsl
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl
rename to test/tint/builtins/gen/min/46c5d3.wgsl
diff --git a/test/builtins/gen/min/46c5d3.wgsl.expected.glsl b/test/tint/builtins/gen/min/46c5d3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/46c5d3.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/46c5d3.wgsl.expected.hlsl b/test/tint/builtins/gen/min/46c5d3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/46c5d3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/46c5d3.wgsl.expected.msl b/test/tint/builtins/gen/min/46c5d3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl.expected.msl
rename to test/tint/builtins/gen/min/46c5d3.wgsl.expected.msl
diff --git a/test/builtins/gen/min/46c5d3.wgsl.expected.spvasm b/test/tint/builtins/gen/min/46c5d3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/46c5d3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/46c5d3.wgsl.expected.wgsl b/test/tint/builtins/gen/min/46c5d3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/46c5d3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/46c5d3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/82b28f.wgsl b/test/tint/builtins/gen/min/82b28f.wgsl
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl
rename to test/tint/builtins/gen/min/82b28f.wgsl
diff --git a/test/builtins/gen/min/82b28f.wgsl.expected.glsl b/test/tint/builtins/gen/min/82b28f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/82b28f.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/82b28f.wgsl.expected.hlsl b/test/tint/builtins/gen/min/82b28f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/82b28f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/82b28f.wgsl.expected.msl b/test/tint/builtins/gen/min/82b28f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl.expected.msl
rename to test/tint/builtins/gen/min/82b28f.wgsl.expected.msl
diff --git a/test/builtins/gen/min/82b28f.wgsl.expected.spvasm b/test/tint/builtins/gen/min/82b28f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/82b28f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/82b28f.wgsl.expected.wgsl b/test/tint/builtins/gen/min/82b28f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/82b28f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/82b28f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/93cfc4.wgsl b/test/tint/builtins/gen/min/93cfc4.wgsl
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl
rename to test/tint/builtins/gen/min/93cfc4.wgsl
diff --git a/test/builtins/gen/min/93cfc4.wgsl.expected.glsl b/test/tint/builtins/gen/min/93cfc4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/93cfc4.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/93cfc4.wgsl.expected.hlsl b/test/tint/builtins/gen/min/93cfc4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/93cfc4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/93cfc4.wgsl.expected.msl b/test/tint/builtins/gen/min/93cfc4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl.expected.msl
rename to test/tint/builtins/gen/min/93cfc4.wgsl.expected.msl
diff --git a/test/builtins/gen/min/93cfc4.wgsl.expected.spvasm b/test/tint/builtins/gen/min/93cfc4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/93cfc4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/93cfc4.wgsl.expected.wgsl b/test/tint/builtins/gen/min/93cfc4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/93cfc4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/93cfc4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/a45171.wgsl b/test/tint/builtins/gen/min/a45171.wgsl
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl
rename to test/tint/builtins/gen/min/a45171.wgsl
diff --git a/test/builtins/gen/min/a45171.wgsl.expected.glsl b/test/tint/builtins/gen/min/a45171.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/a45171.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/a45171.wgsl.expected.hlsl b/test/tint/builtins/gen/min/a45171.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/a45171.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/a45171.wgsl.expected.msl b/test/tint/builtins/gen/min/a45171.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl.expected.msl
rename to test/tint/builtins/gen/min/a45171.wgsl.expected.msl
diff --git a/test/builtins/gen/min/a45171.wgsl.expected.spvasm b/test/tint/builtins/gen/min/a45171.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/a45171.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/a45171.wgsl.expected.wgsl b/test/tint/builtins/gen/min/a45171.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/a45171.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/a45171.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/aa28ad.wgsl b/test/tint/builtins/gen/min/aa28ad.wgsl
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl
rename to test/tint/builtins/gen/min/aa28ad.wgsl
diff --git a/test/builtins/gen/min/aa28ad.wgsl.expected.glsl b/test/tint/builtins/gen/min/aa28ad.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/aa28ad.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/aa28ad.wgsl.expected.hlsl b/test/tint/builtins/gen/min/aa28ad.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/aa28ad.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/aa28ad.wgsl.expected.msl b/test/tint/builtins/gen/min/aa28ad.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl.expected.msl
rename to test/tint/builtins/gen/min/aa28ad.wgsl.expected.msl
diff --git a/test/builtins/gen/min/aa28ad.wgsl.expected.spvasm b/test/tint/builtins/gen/min/aa28ad.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/aa28ad.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/aa28ad.wgsl.expected.wgsl b/test/tint/builtins/gen/min/aa28ad.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/aa28ad.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/aa28ad.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/af326d.wgsl b/test/tint/builtins/gen/min/af326d.wgsl
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl
rename to test/tint/builtins/gen/min/af326d.wgsl
diff --git a/test/builtins/gen/min/af326d.wgsl.expected.glsl b/test/tint/builtins/gen/min/af326d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/af326d.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/af326d.wgsl.expected.hlsl b/test/tint/builtins/gen/min/af326d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/af326d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/af326d.wgsl.expected.msl b/test/tint/builtins/gen/min/af326d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl.expected.msl
rename to test/tint/builtins/gen/min/af326d.wgsl.expected.msl
diff --git a/test/builtins/gen/min/af326d.wgsl.expected.spvasm b/test/tint/builtins/gen/min/af326d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/af326d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/af326d.wgsl.expected.wgsl b/test/tint/builtins/gen/min/af326d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/af326d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/af326d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/c70bb7.wgsl b/test/tint/builtins/gen/min/c70bb7.wgsl
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl
rename to test/tint/builtins/gen/min/c70bb7.wgsl
diff --git a/test/builtins/gen/min/c70bb7.wgsl.expected.glsl b/test/tint/builtins/gen/min/c70bb7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/c70bb7.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/c70bb7.wgsl.expected.hlsl b/test/tint/builtins/gen/min/c70bb7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/c70bb7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/c70bb7.wgsl.expected.msl b/test/tint/builtins/gen/min/c70bb7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl.expected.msl
rename to test/tint/builtins/gen/min/c70bb7.wgsl.expected.msl
diff --git a/test/builtins/gen/min/c70bb7.wgsl.expected.spvasm b/test/tint/builtins/gen/min/c70bb7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/c70bb7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/c70bb7.wgsl.expected.wgsl b/test/tint/builtins/gen/min/c70bb7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/c70bb7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/c70bb7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/c73147.wgsl b/test/tint/builtins/gen/min/c73147.wgsl
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl
rename to test/tint/builtins/gen/min/c73147.wgsl
diff --git a/test/builtins/gen/min/c73147.wgsl.expected.glsl b/test/tint/builtins/gen/min/c73147.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/c73147.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/c73147.wgsl.expected.hlsl b/test/tint/builtins/gen/min/c73147.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/c73147.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/c73147.wgsl.expected.msl b/test/tint/builtins/gen/min/c73147.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl.expected.msl
rename to test/tint/builtins/gen/min/c73147.wgsl.expected.msl
diff --git a/test/builtins/gen/min/c73147.wgsl.expected.spvasm b/test/tint/builtins/gen/min/c73147.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/c73147.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/c73147.wgsl.expected.wgsl b/test/tint/builtins/gen/min/c73147.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/c73147.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/c73147.wgsl.expected.wgsl
diff --git a/test/builtins/gen/min/c76fa6.wgsl b/test/tint/builtins/gen/min/c76fa6.wgsl
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl
rename to test/tint/builtins/gen/min/c76fa6.wgsl
diff --git a/test/builtins/gen/min/c76fa6.wgsl.expected.glsl b/test/tint/builtins/gen/min/c76fa6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl.expected.glsl
rename to test/tint/builtins/gen/min/c76fa6.wgsl.expected.glsl
diff --git a/test/builtins/gen/min/c76fa6.wgsl.expected.hlsl b/test/tint/builtins/gen/min/c76fa6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/min/c76fa6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/min/c76fa6.wgsl.expected.msl b/test/tint/builtins/gen/min/c76fa6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl.expected.msl
rename to test/tint/builtins/gen/min/c76fa6.wgsl.expected.msl
diff --git a/test/builtins/gen/min/c76fa6.wgsl.expected.spvasm b/test/tint/builtins/gen/min/c76fa6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/min/c76fa6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/min/c76fa6.wgsl.expected.wgsl b/test/tint/builtins/gen/min/c76fa6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/min/c76fa6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/min/c76fa6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/0c8c33.wgsl b/test/tint/builtins/gen/mix/0c8c33.wgsl
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl
rename to test/tint/builtins/gen/mix/0c8c33.wgsl
diff --git a/test/builtins/gen/mix/0c8c33.wgsl.expected.glsl b/test/tint/builtins/gen/mix/0c8c33.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/0c8c33.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/0c8c33.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/0c8c33.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/0c8c33.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/0c8c33.wgsl.expected.msl b/test/tint/builtins/gen/mix/0c8c33.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/0c8c33.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/0c8c33.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/0c8c33.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/0c8c33.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/0c8c33.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/0c8c33.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/0c8c33.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/0c8c33.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/1faeb1.wgsl b/test/tint/builtins/gen/mix/1faeb1.wgsl
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl
rename to test/tint/builtins/gen/mix/1faeb1.wgsl
diff --git a/test/builtins/gen/mix/1faeb1.wgsl.expected.glsl b/test/tint/builtins/gen/mix/1faeb1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/1faeb1.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/1faeb1.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/1faeb1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/1faeb1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/1faeb1.wgsl.expected.msl b/test/tint/builtins/gen/mix/1faeb1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/1faeb1.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/1faeb1.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/1faeb1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/1faeb1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/1faeb1.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/1faeb1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/1faeb1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/1faeb1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/2fadab.wgsl b/test/tint/builtins/gen/mix/2fadab.wgsl
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl
rename to test/tint/builtins/gen/mix/2fadab.wgsl
diff --git a/test/builtins/gen/mix/2fadab.wgsl.expected.glsl b/test/tint/builtins/gen/mix/2fadab.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/2fadab.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/2fadab.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/2fadab.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/2fadab.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/2fadab.wgsl.expected.msl b/test/tint/builtins/gen/mix/2fadab.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/2fadab.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/2fadab.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/2fadab.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/2fadab.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/2fadab.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/2fadab.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/2fadab.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/2fadab.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/315264.wgsl b/test/tint/builtins/gen/mix/315264.wgsl
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl
rename to test/tint/builtins/gen/mix/315264.wgsl
diff --git a/test/builtins/gen/mix/315264.wgsl.expected.glsl b/test/tint/builtins/gen/mix/315264.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/315264.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/315264.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/315264.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/315264.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/315264.wgsl.expected.msl b/test/tint/builtins/gen/mix/315264.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/315264.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/315264.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/315264.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/315264.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/315264.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/315264.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/315264.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/315264.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl b/test/tint/builtins/gen/mix/4f0b5e.wgsl
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl.expected.glsl b/test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl.expected.msl b/test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/4f0b5e.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/4f0b5e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/4f0b5e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/6f8adc.wgsl b/test/tint/builtins/gen/mix/6f8adc.wgsl
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl
rename to test/tint/builtins/gen/mix/6f8adc.wgsl
diff --git a/test/builtins/gen/mix/6f8adc.wgsl.expected.glsl b/test/tint/builtins/gen/mix/6f8adc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/6f8adc.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/6f8adc.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/6f8adc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/6f8adc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/6f8adc.wgsl.expected.msl b/test/tint/builtins/gen/mix/6f8adc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/6f8adc.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/6f8adc.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/6f8adc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/6f8adc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/6f8adc.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/6f8adc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/6f8adc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/6f8adc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/mix/c37ede.wgsl b/test/tint/builtins/gen/mix/c37ede.wgsl
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl
rename to test/tint/builtins/gen/mix/c37ede.wgsl
diff --git a/test/builtins/gen/mix/c37ede.wgsl.expected.glsl b/test/tint/builtins/gen/mix/c37ede.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl.expected.glsl
rename to test/tint/builtins/gen/mix/c37ede.wgsl.expected.glsl
diff --git a/test/builtins/gen/mix/c37ede.wgsl.expected.hlsl b/test/tint/builtins/gen/mix/c37ede.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl.expected.hlsl
rename to test/tint/builtins/gen/mix/c37ede.wgsl.expected.hlsl
diff --git a/test/builtins/gen/mix/c37ede.wgsl.expected.msl b/test/tint/builtins/gen/mix/c37ede.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl.expected.msl
rename to test/tint/builtins/gen/mix/c37ede.wgsl.expected.msl
diff --git a/test/builtins/gen/mix/c37ede.wgsl.expected.spvasm b/test/tint/builtins/gen/mix/c37ede.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl.expected.spvasm
rename to test/tint/builtins/gen/mix/c37ede.wgsl.expected.spvasm
diff --git a/test/builtins/gen/mix/c37ede.wgsl.expected.wgsl b/test/tint/builtins/gen/mix/c37ede.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/mix/c37ede.wgsl.expected.wgsl
rename to test/tint/builtins/gen/mix/c37ede.wgsl.expected.wgsl
diff --git a/test/builtins/gen/modf/180fed.wgsl b/test/tint/builtins/gen/modf/180fed.wgsl
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl
rename to test/tint/builtins/gen/modf/180fed.wgsl
diff --git a/test/builtins/gen/modf/180fed.wgsl.expected.glsl b/test/tint/builtins/gen/modf/180fed.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl.expected.glsl
rename to test/tint/builtins/gen/modf/180fed.wgsl.expected.glsl
diff --git a/test/builtins/gen/modf/180fed.wgsl.expected.hlsl b/test/tint/builtins/gen/modf/180fed.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl.expected.hlsl
rename to test/tint/builtins/gen/modf/180fed.wgsl.expected.hlsl
diff --git a/test/builtins/gen/modf/180fed.wgsl.expected.msl b/test/tint/builtins/gen/modf/180fed.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl.expected.msl
rename to test/tint/builtins/gen/modf/180fed.wgsl.expected.msl
diff --git a/test/builtins/gen/modf/180fed.wgsl.expected.spvasm b/test/tint/builtins/gen/modf/180fed.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl.expected.spvasm
rename to test/tint/builtins/gen/modf/180fed.wgsl.expected.spvasm
diff --git a/test/builtins/gen/modf/180fed.wgsl.expected.wgsl b/test/tint/builtins/gen/modf/180fed.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/modf/180fed.wgsl.expected.wgsl
rename to test/tint/builtins/gen/modf/180fed.wgsl.expected.wgsl
diff --git a/test/builtins/gen/modf/9b75f7.wgsl b/test/tint/builtins/gen/modf/9b75f7.wgsl
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl
rename to test/tint/builtins/gen/modf/9b75f7.wgsl
diff --git a/test/builtins/gen/modf/9b75f7.wgsl.expected.glsl b/test/tint/builtins/gen/modf/9b75f7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl.expected.glsl
rename to test/tint/builtins/gen/modf/9b75f7.wgsl.expected.glsl
diff --git a/test/builtins/gen/modf/9b75f7.wgsl.expected.hlsl b/test/tint/builtins/gen/modf/9b75f7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/modf/9b75f7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/modf/9b75f7.wgsl.expected.msl b/test/tint/builtins/gen/modf/9b75f7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl.expected.msl
rename to test/tint/builtins/gen/modf/9b75f7.wgsl.expected.msl
diff --git a/test/builtins/gen/modf/9b75f7.wgsl.expected.spvasm b/test/tint/builtins/gen/modf/9b75f7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/modf/9b75f7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/modf/9b75f7.wgsl.expected.wgsl b/test/tint/builtins/gen/modf/9b75f7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/modf/9b75f7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/modf/9b75f7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl b/test/tint/builtins/gen/modf/ec2dbc.wgsl
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl.expected.glsl b/test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl.expected.glsl
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.glsl
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl.expected.hlsl b/test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl.expected.msl b/test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl.expected.msl
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.msl
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl.expected.spvasm b/test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/modf/ec2dbc.wgsl.expected.wgsl b/test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/modf/ec2dbc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/modf/ec2dbc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/modf/f5f20d.wgsl b/test/tint/builtins/gen/modf/f5f20d.wgsl
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl
rename to test/tint/builtins/gen/modf/f5f20d.wgsl
diff --git a/test/builtins/gen/modf/f5f20d.wgsl.expected.glsl b/test/tint/builtins/gen/modf/f5f20d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl.expected.glsl
rename to test/tint/builtins/gen/modf/f5f20d.wgsl.expected.glsl
diff --git a/test/builtins/gen/modf/f5f20d.wgsl.expected.hlsl b/test/tint/builtins/gen/modf/f5f20d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/modf/f5f20d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/modf/f5f20d.wgsl.expected.msl b/test/tint/builtins/gen/modf/f5f20d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl.expected.msl
rename to test/tint/builtins/gen/modf/f5f20d.wgsl.expected.msl
diff --git a/test/builtins/gen/modf/f5f20d.wgsl.expected.spvasm b/test/tint/builtins/gen/modf/f5f20d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/modf/f5f20d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/modf/f5f20d.wgsl.expected.wgsl b/test/tint/builtins/gen/modf/f5f20d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/modf/f5f20d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/modf/f5f20d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl b/test/tint/builtins/gen/normalize/64d8c0.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl.expected.glsl b/test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl.expected.glsl
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.glsl
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl.expected.hlsl b/test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl.expected.msl b/test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl.expected.msl
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.msl
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl.expected.spvasm b/test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/normalize/64d8c0.wgsl.expected.wgsl b/test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/64d8c0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/normalize/64d8c0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl b/test/tint/builtins/gen/normalize/9a0aab.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl.expected.glsl b/test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl.expected.glsl
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.glsl
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl.expected.hlsl b/test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl.expected.hlsl
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.hlsl
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl.expected.msl b/test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl.expected.msl
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.msl
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl.expected.spvasm b/test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl.expected.spvasm
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.spvasm
diff --git a/test/builtins/gen/normalize/9a0aab.wgsl.expected.wgsl b/test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/9a0aab.wgsl.expected.wgsl
rename to test/tint/builtins/gen/normalize/9a0aab.wgsl.expected.wgsl
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl b/test/tint/builtins/gen/normalize/fc2ef1.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl.expected.glsl b/test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl.expected.glsl
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.glsl
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl.expected.hlsl b/test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl.expected.msl b/test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl.expected.msl
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.msl
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl.expected.spvasm b/test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/normalize/fc2ef1.wgsl.expected.wgsl b/test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/normalize/fc2ef1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/normalize/fc2ef1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.glsl b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.glsl
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.glsl
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.hlsl b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.msl b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.msl
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.msl
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.spvasm b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.wgsl b/test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16float/0e97b3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pack2x16float/0e97b3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.glsl b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.glsl
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.glsl
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.msl b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.msl
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.msl
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.spvasm b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.wgsl b/test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pack2x16snorm/6c169b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.glsl b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.glsl
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.glsl
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.msl b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.msl
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.msl
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.spvasm b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.wgsl b/test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pack2x16unorm/0f08e4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.glsl b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.glsl
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.glsl
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.msl b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.msl
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.msl
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.spvasm b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.wgsl b/test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pack4x8snorm/4d22e7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.glsl b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.glsl
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.glsl
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.hlsl b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.msl b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.msl
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.msl
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.spvasm b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.wgsl b/test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pack4x8unorm/95c456.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pack4x8unorm/95c456.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pow/04a908.wgsl b/test/tint/builtins/gen/pow/04a908.wgsl
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl
rename to test/tint/builtins/gen/pow/04a908.wgsl
diff --git a/test/builtins/gen/pow/04a908.wgsl.expected.glsl b/test/tint/builtins/gen/pow/04a908.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl.expected.glsl
rename to test/tint/builtins/gen/pow/04a908.wgsl.expected.glsl
diff --git a/test/builtins/gen/pow/04a908.wgsl.expected.hlsl b/test/tint/builtins/gen/pow/04a908.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pow/04a908.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pow/04a908.wgsl.expected.msl b/test/tint/builtins/gen/pow/04a908.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl.expected.msl
rename to test/tint/builtins/gen/pow/04a908.wgsl.expected.msl
diff --git a/test/builtins/gen/pow/04a908.wgsl.expected.spvasm b/test/tint/builtins/gen/pow/04a908.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pow/04a908.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pow/04a908.wgsl.expected.wgsl b/test/tint/builtins/gen/pow/04a908.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pow/04a908.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pow/04a908.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pow/46e029.wgsl b/test/tint/builtins/gen/pow/46e029.wgsl
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl
rename to test/tint/builtins/gen/pow/46e029.wgsl
diff --git a/test/builtins/gen/pow/46e029.wgsl.expected.glsl b/test/tint/builtins/gen/pow/46e029.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl.expected.glsl
rename to test/tint/builtins/gen/pow/46e029.wgsl.expected.glsl
diff --git a/test/builtins/gen/pow/46e029.wgsl.expected.hlsl b/test/tint/builtins/gen/pow/46e029.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pow/46e029.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pow/46e029.wgsl.expected.msl b/test/tint/builtins/gen/pow/46e029.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl.expected.msl
rename to test/tint/builtins/gen/pow/46e029.wgsl.expected.msl
diff --git a/test/builtins/gen/pow/46e029.wgsl.expected.spvasm b/test/tint/builtins/gen/pow/46e029.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pow/46e029.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pow/46e029.wgsl.expected.wgsl b/test/tint/builtins/gen/pow/46e029.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pow/46e029.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pow/46e029.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pow/4a46c9.wgsl b/test/tint/builtins/gen/pow/4a46c9.wgsl
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl
rename to test/tint/builtins/gen/pow/4a46c9.wgsl
diff --git a/test/builtins/gen/pow/4a46c9.wgsl.expected.glsl b/test/tint/builtins/gen/pow/4a46c9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl.expected.glsl
rename to test/tint/builtins/gen/pow/4a46c9.wgsl.expected.glsl
diff --git a/test/builtins/gen/pow/4a46c9.wgsl.expected.hlsl b/test/tint/builtins/gen/pow/4a46c9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pow/4a46c9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pow/4a46c9.wgsl.expected.msl b/test/tint/builtins/gen/pow/4a46c9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl.expected.msl
rename to test/tint/builtins/gen/pow/4a46c9.wgsl.expected.msl
diff --git a/test/builtins/gen/pow/4a46c9.wgsl.expected.spvasm b/test/tint/builtins/gen/pow/4a46c9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pow/4a46c9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pow/4a46c9.wgsl.expected.wgsl b/test/tint/builtins/gen/pow/4a46c9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pow/4a46c9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pow/4a46c9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/pow/e60ea5.wgsl b/test/tint/builtins/gen/pow/e60ea5.wgsl
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl
rename to test/tint/builtins/gen/pow/e60ea5.wgsl
diff --git a/test/builtins/gen/pow/e60ea5.wgsl.expected.glsl b/test/tint/builtins/gen/pow/e60ea5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl.expected.glsl
rename to test/tint/builtins/gen/pow/e60ea5.wgsl.expected.glsl
diff --git a/test/builtins/gen/pow/e60ea5.wgsl.expected.hlsl b/test/tint/builtins/gen/pow/e60ea5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/pow/e60ea5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/pow/e60ea5.wgsl.expected.msl b/test/tint/builtins/gen/pow/e60ea5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl.expected.msl
rename to test/tint/builtins/gen/pow/e60ea5.wgsl.expected.msl
diff --git a/test/builtins/gen/pow/e60ea5.wgsl.expected.spvasm b/test/tint/builtins/gen/pow/e60ea5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/pow/e60ea5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/pow/e60ea5.wgsl.expected.wgsl b/test/tint/builtins/gen/pow/e60ea5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/pow/e60ea5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/pow/e60ea5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/radians/09b7fc.wgsl b/test/tint/builtins/gen/radians/09b7fc.wgsl
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl
rename to test/tint/builtins/gen/radians/09b7fc.wgsl
diff --git a/test/builtins/gen/radians/09b7fc.wgsl.expected.glsl b/test/tint/builtins/gen/radians/09b7fc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl.expected.glsl
rename to test/tint/builtins/gen/radians/09b7fc.wgsl.expected.glsl
diff --git a/test/builtins/gen/radians/09b7fc.wgsl.expected.hlsl b/test/tint/builtins/gen/radians/09b7fc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/radians/09b7fc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/radians/09b7fc.wgsl.expected.msl b/test/tint/builtins/gen/radians/09b7fc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl.expected.msl
rename to test/tint/builtins/gen/radians/09b7fc.wgsl.expected.msl
diff --git a/test/builtins/gen/radians/09b7fc.wgsl.expected.spvasm b/test/tint/builtins/gen/radians/09b7fc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/radians/09b7fc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/radians/09b7fc.wgsl.expected.wgsl b/test/tint/builtins/gen/radians/09b7fc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/radians/09b7fc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/radians/09b7fc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/radians/61687a.wgsl b/test/tint/builtins/gen/radians/61687a.wgsl
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl
rename to test/tint/builtins/gen/radians/61687a.wgsl
diff --git a/test/builtins/gen/radians/61687a.wgsl.expected.glsl b/test/tint/builtins/gen/radians/61687a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl.expected.glsl
rename to test/tint/builtins/gen/radians/61687a.wgsl.expected.glsl
diff --git a/test/builtins/gen/radians/61687a.wgsl.expected.hlsl b/test/tint/builtins/gen/radians/61687a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/radians/61687a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/radians/61687a.wgsl.expected.msl b/test/tint/builtins/gen/radians/61687a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl.expected.msl
rename to test/tint/builtins/gen/radians/61687a.wgsl.expected.msl
diff --git a/test/builtins/gen/radians/61687a.wgsl.expected.spvasm b/test/tint/builtins/gen/radians/61687a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/radians/61687a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/radians/61687a.wgsl.expected.wgsl b/test/tint/builtins/gen/radians/61687a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/radians/61687a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/radians/61687a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl b/test/tint/builtins/gen/radians/6b0ff2.wgsl
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl.expected.glsl b/test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl.expected.glsl
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.glsl
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl.expected.hlsl b/test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl.expected.msl b/test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl.expected.msl
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.msl
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl.expected.spvasm b/test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/radians/6b0ff2.wgsl.expected.wgsl b/test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/radians/6b0ff2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/radians/6b0ff2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/radians/f96258.wgsl b/test/tint/builtins/gen/radians/f96258.wgsl
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl
rename to test/tint/builtins/gen/radians/f96258.wgsl
diff --git a/test/builtins/gen/radians/f96258.wgsl.expected.glsl b/test/tint/builtins/gen/radians/f96258.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl.expected.glsl
rename to test/tint/builtins/gen/radians/f96258.wgsl.expected.glsl
diff --git a/test/builtins/gen/radians/f96258.wgsl.expected.hlsl b/test/tint/builtins/gen/radians/f96258.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl.expected.hlsl
rename to test/tint/builtins/gen/radians/f96258.wgsl.expected.hlsl
diff --git a/test/builtins/gen/radians/f96258.wgsl.expected.msl b/test/tint/builtins/gen/radians/f96258.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl.expected.msl
rename to test/tint/builtins/gen/radians/f96258.wgsl.expected.msl
diff --git a/test/builtins/gen/radians/f96258.wgsl.expected.spvasm b/test/tint/builtins/gen/radians/f96258.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl.expected.spvasm
rename to test/tint/builtins/gen/radians/f96258.wgsl.expected.spvasm
diff --git a/test/builtins/gen/radians/f96258.wgsl.expected.wgsl b/test/tint/builtins/gen/radians/f96258.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/radians/f96258.wgsl.expected.wgsl
rename to test/tint/builtins/gen/radians/f96258.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reflect/05357e.wgsl b/test/tint/builtins/gen/reflect/05357e.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl
rename to test/tint/builtins/gen/reflect/05357e.wgsl
diff --git a/test/builtins/gen/reflect/05357e.wgsl.expected.glsl b/test/tint/builtins/gen/reflect/05357e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl.expected.glsl
rename to test/tint/builtins/gen/reflect/05357e.wgsl.expected.glsl
diff --git a/test/builtins/gen/reflect/05357e.wgsl.expected.hlsl b/test/tint/builtins/gen/reflect/05357e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reflect/05357e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reflect/05357e.wgsl.expected.msl b/test/tint/builtins/gen/reflect/05357e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl.expected.msl
rename to test/tint/builtins/gen/reflect/05357e.wgsl.expected.msl
diff --git a/test/builtins/gen/reflect/05357e.wgsl.expected.spvasm b/test/tint/builtins/gen/reflect/05357e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reflect/05357e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reflect/05357e.wgsl.expected.wgsl b/test/tint/builtins/gen/reflect/05357e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/05357e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reflect/05357e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reflect/b61e10.wgsl b/test/tint/builtins/gen/reflect/b61e10.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl
rename to test/tint/builtins/gen/reflect/b61e10.wgsl
diff --git a/test/builtins/gen/reflect/b61e10.wgsl.expected.glsl b/test/tint/builtins/gen/reflect/b61e10.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl.expected.glsl
rename to test/tint/builtins/gen/reflect/b61e10.wgsl.expected.glsl
diff --git a/test/builtins/gen/reflect/b61e10.wgsl.expected.hlsl b/test/tint/builtins/gen/reflect/b61e10.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reflect/b61e10.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reflect/b61e10.wgsl.expected.msl b/test/tint/builtins/gen/reflect/b61e10.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl.expected.msl
rename to test/tint/builtins/gen/reflect/b61e10.wgsl.expected.msl
diff --git a/test/builtins/gen/reflect/b61e10.wgsl.expected.spvasm b/test/tint/builtins/gen/reflect/b61e10.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reflect/b61e10.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reflect/b61e10.wgsl.expected.wgsl b/test/tint/builtins/gen/reflect/b61e10.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/b61e10.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reflect/b61e10.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl b/test/tint/builtins/gen/reflect/f47fdb.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl.expected.glsl b/test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl.expected.glsl
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.glsl
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl.expected.hlsl b/test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl.expected.msl b/test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl.expected.msl
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.msl
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl.expected.spvasm b/test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reflect/f47fdb.wgsl.expected.wgsl b/test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reflect/f47fdb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reflect/f47fdb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/refract/7e02e6.wgsl b/test/tint/builtins/gen/refract/7e02e6.wgsl
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl
rename to test/tint/builtins/gen/refract/7e02e6.wgsl
diff --git a/test/builtins/gen/refract/7e02e6.wgsl.expected.glsl b/test/tint/builtins/gen/refract/7e02e6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl.expected.glsl
rename to test/tint/builtins/gen/refract/7e02e6.wgsl.expected.glsl
diff --git a/test/builtins/gen/refract/7e02e6.wgsl.expected.hlsl b/test/tint/builtins/gen/refract/7e02e6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/refract/7e02e6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/refract/7e02e6.wgsl.expected.msl b/test/tint/builtins/gen/refract/7e02e6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl.expected.msl
rename to test/tint/builtins/gen/refract/7e02e6.wgsl.expected.msl
diff --git a/test/builtins/gen/refract/7e02e6.wgsl.expected.spvasm b/test/tint/builtins/gen/refract/7e02e6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/refract/7e02e6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/refract/7e02e6.wgsl.expected.wgsl b/test/tint/builtins/gen/refract/7e02e6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/refract/7e02e6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/refract/7e02e6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl b/test/tint/builtins/gen/refract/cbc1d2.wgsl
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl.expected.glsl b/test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl.expected.glsl
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.glsl
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl.expected.hlsl b/test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl.expected.msl b/test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl.expected.msl
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.msl
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl.expected.spvasm b/test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/refract/cbc1d2.wgsl.expected.wgsl b/test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/refract/cbc1d2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/refract/cbc1d2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/refract/cd905f.wgsl b/test/tint/builtins/gen/refract/cd905f.wgsl
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl
rename to test/tint/builtins/gen/refract/cd905f.wgsl
diff --git a/test/builtins/gen/refract/cd905f.wgsl.expected.glsl b/test/tint/builtins/gen/refract/cd905f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl.expected.glsl
rename to test/tint/builtins/gen/refract/cd905f.wgsl.expected.glsl
diff --git a/test/builtins/gen/refract/cd905f.wgsl.expected.hlsl b/test/tint/builtins/gen/refract/cd905f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/refract/cd905f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/refract/cd905f.wgsl.expected.msl b/test/tint/builtins/gen/refract/cd905f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl.expected.msl
rename to test/tint/builtins/gen/refract/cd905f.wgsl.expected.msl
diff --git a/test/builtins/gen/refract/cd905f.wgsl.expected.spvasm b/test/tint/builtins/gen/refract/cd905f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/refract/cd905f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/refract/cd905f.wgsl.expected.wgsl b/test/tint/builtins/gen/refract/cd905f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/refract/cd905f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/refract/cd905f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/222177.wgsl b/test/tint/builtins/gen/reverseBits/222177.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl
rename to test/tint/builtins/gen/reverseBits/222177.wgsl
diff --git a/test/builtins/gen/reverseBits/222177.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/222177.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/222177.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/222177.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/222177.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/222177.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/222177.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/222177.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/222177.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/222177.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/222177.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/222177.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/222177.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/222177.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/222177.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/222177.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl b/test/tint/builtins/gen/reverseBits/35fea9.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/35fea9.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/35fea9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/35fea9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/4dbd6f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/4dbd6f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl b/test/tint/builtins/gen/reverseBits/7c4269.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/7c4269.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/7c4269.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/7c4269.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/a6ccd4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/a6ccd4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/c21bc1.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/c21bc1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/c21bc1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e1f4c1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/e1f4c1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl b/test/tint/builtins/gen/reverseBits/e31adf.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl.expected.glsl b/test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl.expected.glsl
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.glsl
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl.expected.hlsl b/test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl.expected.msl b/test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl.expected.msl
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.msl
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl.expected.spvasm b/test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/reverseBits/e31adf.wgsl.expected.wgsl b/test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/reverseBits/e31adf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/reverseBits/e31adf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/round/106c0b.wgsl b/test/tint/builtins/gen/round/106c0b.wgsl
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl
rename to test/tint/builtins/gen/round/106c0b.wgsl
diff --git a/test/builtins/gen/round/106c0b.wgsl.expected.glsl b/test/tint/builtins/gen/round/106c0b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl.expected.glsl
rename to test/tint/builtins/gen/round/106c0b.wgsl.expected.glsl
diff --git a/test/builtins/gen/round/106c0b.wgsl.expected.hlsl b/test/tint/builtins/gen/round/106c0b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/round/106c0b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/round/106c0b.wgsl.expected.msl b/test/tint/builtins/gen/round/106c0b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl.expected.msl
rename to test/tint/builtins/gen/round/106c0b.wgsl.expected.msl
diff --git a/test/builtins/gen/round/106c0b.wgsl.expected.spvasm b/test/tint/builtins/gen/round/106c0b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/round/106c0b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/round/106c0b.wgsl.expected.wgsl b/test/tint/builtins/gen/round/106c0b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/round/106c0b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/round/106c0b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/round/1c7897.wgsl b/test/tint/builtins/gen/round/1c7897.wgsl
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl
rename to test/tint/builtins/gen/round/1c7897.wgsl
diff --git a/test/builtins/gen/round/1c7897.wgsl.expected.glsl b/test/tint/builtins/gen/round/1c7897.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl.expected.glsl
rename to test/tint/builtins/gen/round/1c7897.wgsl.expected.glsl
diff --git a/test/builtins/gen/round/1c7897.wgsl.expected.hlsl b/test/tint/builtins/gen/round/1c7897.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl.expected.hlsl
rename to test/tint/builtins/gen/round/1c7897.wgsl.expected.hlsl
diff --git a/test/builtins/gen/round/1c7897.wgsl.expected.msl b/test/tint/builtins/gen/round/1c7897.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl.expected.msl
rename to test/tint/builtins/gen/round/1c7897.wgsl.expected.msl
diff --git a/test/builtins/gen/round/1c7897.wgsl.expected.spvasm b/test/tint/builtins/gen/round/1c7897.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl.expected.spvasm
rename to test/tint/builtins/gen/round/1c7897.wgsl.expected.spvasm
diff --git a/test/builtins/gen/round/1c7897.wgsl.expected.wgsl b/test/tint/builtins/gen/round/1c7897.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/round/1c7897.wgsl.expected.wgsl
rename to test/tint/builtins/gen/round/1c7897.wgsl.expected.wgsl
diff --git a/test/builtins/gen/round/52c84d.wgsl b/test/tint/builtins/gen/round/52c84d.wgsl
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl
rename to test/tint/builtins/gen/round/52c84d.wgsl
diff --git a/test/builtins/gen/round/52c84d.wgsl.expected.glsl b/test/tint/builtins/gen/round/52c84d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl.expected.glsl
rename to test/tint/builtins/gen/round/52c84d.wgsl.expected.glsl
diff --git a/test/builtins/gen/round/52c84d.wgsl.expected.hlsl b/test/tint/builtins/gen/round/52c84d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/round/52c84d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/round/52c84d.wgsl.expected.msl b/test/tint/builtins/gen/round/52c84d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl.expected.msl
rename to test/tint/builtins/gen/round/52c84d.wgsl.expected.msl
diff --git a/test/builtins/gen/round/52c84d.wgsl.expected.spvasm b/test/tint/builtins/gen/round/52c84d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/round/52c84d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/round/52c84d.wgsl.expected.wgsl b/test/tint/builtins/gen/round/52c84d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/round/52c84d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/round/52c84d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/round/9edc38.wgsl b/test/tint/builtins/gen/round/9edc38.wgsl
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl
rename to test/tint/builtins/gen/round/9edc38.wgsl
diff --git a/test/builtins/gen/round/9edc38.wgsl.expected.glsl b/test/tint/builtins/gen/round/9edc38.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl.expected.glsl
rename to test/tint/builtins/gen/round/9edc38.wgsl.expected.glsl
diff --git a/test/builtins/gen/round/9edc38.wgsl.expected.hlsl b/test/tint/builtins/gen/round/9edc38.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl.expected.hlsl
rename to test/tint/builtins/gen/round/9edc38.wgsl.expected.hlsl
diff --git a/test/builtins/gen/round/9edc38.wgsl.expected.msl b/test/tint/builtins/gen/round/9edc38.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl.expected.msl
rename to test/tint/builtins/gen/round/9edc38.wgsl.expected.msl
diff --git a/test/builtins/gen/round/9edc38.wgsl.expected.spvasm b/test/tint/builtins/gen/round/9edc38.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl.expected.spvasm
rename to test/tint/builtins/gen/round/9edc38.wgsl.expected.spvasm
diff --git a/test/builtins/gen/round/9edc38.wgsl.expected.wgsl b/test/tint/builtins/gen/round/9edc38.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/round/9edc38.wgsl.expected.wgsl
rename to test/tint/builtins/gen/round/9edc38.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/00b848.wgsl b/test/tint/builtins/gen/select/00b848.wgsl
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl
rename to test/tint/builtins/gen/select/00b848.wgsl
diff --git a/test/builtins/gen/select/00b848.wgsl.expected.glsl b/test/tint/builtins/gen/select/00b848.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/00b848.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/00b848.wgsl.expected.hlsl b/test/tint/builtins/gen/select/00b848.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/00b848.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/00b848.wgsl.expected.msl b/test/tint/builtins/gen/select/00b848.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl.expected.msl
rename to test/tint/builtins/gen/select/00b848.wgsl.expected.msl
diff --git a/test/builtins/gen/select/00b848.wgsl.expected.spvasm b/test/tint/builtins/gen/select/00b848.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/00b848.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/00b848.wgsl.expected.wgsl b/test/tint/builtins/gen/select/00b848.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/00b848.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/00b848.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/01e2cd.wgsl b/test/tint/builtins/gen/select/01e2cd.wgsl
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl
rename to test/tint/builtins/gen/select/01e2cd.wgsl
diff --git a/test/builtins/gen/select/01e2cd.wgsl.expected.glsl b/test/tint/builtins/gen/select/01e2cd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/01e2cd.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/01e2cd.wgsl.expected.hlsl b/test/tint/builtins/gen/select/01e2cd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/01e2cd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/01e2cd.wgsl.expected.msl b/test/tint/builtins/gen/select/01e2cd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl.expected.msl
rename to test/tint/builtins/gen/select/01e2cd.wgsl.expected.msl
diff --git a/test/builtins/gen/select/01e2cd.wgsl.expected.spvasm b/test/tint/builtins/gen/select/01e2cd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/01e2cd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/01e2cd.wgsl.expected.wgsl b/test/tint/builtins/gen/select/01e2cd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/01e2cd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/01e2cd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/087ea4.wgsl b/test/tint/builtins/gen/select/087ea4.wgsl
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl
rename to test/tint/builtins/gen/select/087ea4.wgsl
diff --git a/test/builtins/gen/select/087ea4.wgsl.expected.glsl b/test/tint/builtins/gen/select/087ea4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/087ea4.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/087ea4.wgsl.expected.hlsl b/test/tint/builtins/gen/select/087ea4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/087ea4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/087ea4.wgsl.expected.msl b/test/tint/builtins/gen/select/087ea4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl.expected.msl
rename to test/tint/builtins/gen/select/087ea4.wgsl.expected.msl
diff --git a/test/builtins/gen/select/087ea4.wgsl.expected.spvasm b/test/tint/builtins/gen/select/087ea4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/087ea4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/087ea4.wgsl.expected.wgsl b/test/tint/builtins/gen/select/087ea4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/087ea4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/087ea4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/1e960b.wgsl b/test/tint/builtins/gen/select/1e960b.wgsl
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl
rename to test/tint/builtins/gen/select/1e960b.wgsl
diff --git a/test/builtins/gen/select/1e960b.wgsl.expected.glsl b/test/tint/builtins/gen/select/1e960b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/1e960b.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/1e960b.wgsl.expected.hlsl b/test/tint/builtins/gen/select/1e960b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/1e960b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/1e960b.wgsl.expected.msl b/test/tint/builtins/gen/select/1e960b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl.expected.msl
rename to test/tint/builtins/gen/select/1e960b.wgsl.expected.msl
diff --git a/test/builtins/gen/select/1e960b.wgsl.expected.spvasm b/test/tint/builtins/gen/select/1e960b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/1e960b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/1e960b.wgsl.expected.wgsl b/test/tint/builtins/gen/select/1e960b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/1e960b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/1e960b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/266aff.wgsl b/test/tint/builtins/gen/select/266aff.wgsl
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl
rename to test/tint/builtins/gen/select/266aff.wgsl
diff --git a/test/builtins/gen/select/266aff.wgsl.expected.glsl b/test/tint/builtins/gen/select/266aff.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/266aff.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/266aff.wgsl.expected.hlsl b/test/tint/builtins/gen/select/266aff.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/266aff.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/266aff.wgsl.expected.msl b/test/tint/builtins/gen/select/266aff.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl.expected.msl
rename to test/tint/builtins/gen/select/266aff.wgsl.expected.msl
diff --git a/test/builtins/gen/select/266aff.wgsl.expected.spvasm b/test/tint/builtins/gen/select/266aff.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/266aff.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/266aff.wgsl.expected.wgsl b/test/tint/builtins/gen/select/266aff.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/266aff.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/266aff.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/28a27e.wgsl b/test/tint/builtins/gen/select/28a27e.wgsl
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl
rename to test/tint/builtins/gen/select/28a27e.wgsl
diff --git a/test/builtins/gen/select/28a27e.wgsl.expected.glsl b/test/tint/builtins/gen/select/28a27e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/28a27e.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/28a27e.wgsl.expected.hlsl b/test/tint/builtins/gen/select/28a27e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/28a27e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/28a27e.wgsl.expected.msl b/test/tint/builtins/gen/select/28a27e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl.expected.msl
rename to test/tint/builtins/gen/select/28a27e.wgsl.expected.msl
diff --git a/test/builtins/gen/select/28a27e.wgsl.expected.spvasm b/test/tint/builtins/gen/select/28a27e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/28a27e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/28a27e.wgsl.expected.wgsl b/test/tint/builtins/gen/select/28a27e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/28a27e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/28a27e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/3c25ce.wgsl b/test/tint/builtins/gen/select/3c25ce.wgsl
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl
rename to test/tint/builtins/gen/select/3c25ce.wgsl
diff --git a/test/builtins/gen/select/3c25ce.wgsl.expected.glsl b/test/tint/builtins/gen/select/3c25ce.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/3c25ce.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/3c25ce.wgsl.expected.hlsl b/test/tint/builtins/gen/select/3c25ce.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/3c25ce.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/3c25ce.wgsl.expected.msl b/test/tint/builtins/gen/select/3c25ce.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl.expected.msl
rename to test/tint/builtins/gen/select/3c25ce.wgsl.expected.msl
diff --git a/test/builtins/gen/select/3c25ce.wgsl.expected.spvasm b/test/tint/builtins/gen/select/3c25ce.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/3c25ce.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/3c25ce.wgsl.expected.wgsl b/test/tint/builtins/gen/select/3c25ce.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/3c25ce.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/3c25ce.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/416e14.wgsl b/test/tint/builtins/gen/select/416e14.wgsl
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl
rename to test/tint/builtins/gen/select/416e14.wgsl
diff --git a/test/builtins/gen/select/416e14.wgsl.expected.glsl b/test/tint/builtins/gen/select/416e14.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/416e14.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/416e14.wgsl.expected.hlsl b/test/tint/builtins/gen/select/416e14.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/416e14.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/416e14.wgsl.expected.msl b/test/tint/builtins/gen/select/416e14.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl.expected.msl
rename to test/tint/builtins/gen/select/416e14.wgsl.expected.msl
diff --git a/test/builtins/gen/select/416e14.wgsl.expected.spvasm b/test/tint/builtins/gen/select/416e14.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/416e14.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/416e14.wgsl.expected.wgsl b/test/tint/builtins/gen/select/416e14.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/416e14.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/416e14.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/51b047.wgsl b/test/tint/builtins/gen/select/51b047.wgsl
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl
rename to test/tint/builtins/gen/select/51b047.wgsl
diff --git a/test/builtins/gen/select/51b047.wgsl.expected.glsl b/test/tint/builtins/gen/select/51b047.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/51b047.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/51b047.wgsl.expected.hlsl b/test/tint/builtins/gen/select/51b047.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/51b047.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/51b047.wgsl.expected.msl b/test/tint/builtins/gen/select/51b047.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl.expected.msl
rename to test/tint/builtins/gen/select/51b047.wgsl.expected.msl
diff --git a/test/builtins/gen/select/51b047.wgsl.expected.spvasm b/test/tint/builtins/gen/select/51b047.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/51b047.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/51b047.wgsl.expected.wgsl b/test/tint/builtins/gen/select/51b047.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/51b047.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/51b047.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/713567.wgsl b/test/tint/builtins/gen/select/713567.wgsl
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl
rename to test/tint/builtins/gen/select/713567.wgsl
diff --git a/test/builtins/gen/select/713567.wgsl.expected.glsl b/test/tint/builtins/gen/select/713567.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/713567.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/713567.wgsl.expected.hlsl b/test/tint/builtins/gen/select/713567.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/713567.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/713567.wgsl.expected.msl b/test/tint/builtins/gen/select/713567.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl.expected.msl
rename to test/tint/builtins/gen/select/713567.wgsl.expected.msl
diff --git a/test/builtins/gen/select/713567.wgsl.expected.spvasm b/test/tint/builtins/gen/select/713567.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/713567.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/713567.wgsl.expected.wgsl b/test/tint/builtins/gen/select/713567.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/713567.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/713567.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/78be5f.wgsl b/test/tint/builtins/gen/select/78be5f.wgsl
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl
rename to test/tint/builtins/gen/select/78be5f.wgsl
diff --git a/test/builtins/gen/select/78be5f.wgsl.expected.glsl b/test/tint/builtins/gen/select/78be5f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/78be5f.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/78be5f.wgsl.expected.hlsl b/test/tint/builtins/gen/select/78be5f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/78be5f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/78be5f.wgsl.expected.msl b/test/tint/builtins/gen/select/78be5f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl.expected.msl
rename to test/tint/builtins/gen/select/78be5f.wgsl.expected.msl
diff --git a/test/builtins/gen/select/78be5f.wgsl.expected.spvasm b/test/tint/builtins/gen/select/78be5f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/78be5f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/78be5f.wgsl.expected.wgsl b/test/tint/builtins/gen/select/78be5f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/78be5f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/78be5f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/80a9a9.wgsl b/test/tint/builtins/gen/select/80a9a9.wgsl
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl
rename to test/tint/builtins/gen/select/80a9a9.wgsl
diff --git a/test/builtins/gen/select/80a9a9.wgsl.expected.glsl b/test/tint/builtins/gen/select/80a9a9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/80a9a9.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/80a9a9.wgsl.expected.hlsl b/test/tint/builtins/gen/select/80a9a9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/80a9a9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/80a9a9.wgsl.expected.msl b/test/tint/builtins/gen/select/80a9a9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl.expected.msl
rename to test/tint/builtins/gen/select/80a9a9.wgsl.expected.msl
diff --git a/test/builtins/gen/select/80a9a9.wgsl.expected.spvasm b/test/tint/builtins/gen/select/80a9a9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/80a9a9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/80a9a9.wgsl.expected.wgsl b/test/tint/builtins/gen/select/80a9a9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/80a9a9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/80a9a9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/8fa62c.wgsl b/test/tint/builtins/gen/select/8fa62c.wgsl
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl
rename to test/tint/builtins/gen/select/8fa62c.wgsl
diff --git a/test/builtins/gen/select/8fa62c.wgsl.expected.glsl b/test/tint/builtins/gen/select/8fa62c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/8fa62c.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/8fa62c.wgsl.expected.hlsl b/test/tint/builtins/gen/select/8fa62c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/8fa62c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/8fa62c.wgsl.expected.msl b/test/tint/builtins/gen/select/8fa62c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl.expected.msl
rename to test/tint/builtins/gen/select/8fa62c.wgsl.expected.msl
diff --git a/test/builtins/gen/select/8fa62c.wgsl.expected.spvasm b/test/tint/builtins/gen/select/8fa62c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/8fa62c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/8fa62c.wgsl.expected.wgsl b/test/tint/builtins/gen/select/8fa62c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/8fa62c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/8fa62c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/99f883.wgsl b/test/tint/builtins/gen/select/99f883.wgsl
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl
rename to test/tint/builtins/gen/select/99f883.wgsl
diff --git a/test/builtins/gen/select/99f883.wgsl.expected.glsl b/test/tint/builtins/gen/select/99f883.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/99f883.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/99f883.wgsl.expected.hlsl b/test/tint/builtins/gen/select/99f883.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/99f883.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/99f883.wgsl.expected.msl b/test/tint/builtins/gen/select/99f883.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl.expected.msl
rename to test/tint/builtins/gen/select/99f883.wgsl.expected.msl
diff --git a/test/builtins/gen/select/99f883.wgsl.expected.spvasm b/test/tint/builtins/gen/select/99f883.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/99f883.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/99f883.wgsl.expected.wgsl b/test/tint/builtins/gen/select/99f883.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/99f883.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/99f883.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/a2860e.wgsl b/test/tint/builtins/gen/select/a2860e.wgsl
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl
rename to test/tint/builtins/gen/select/a2860e.wgsl
diff --git a/test/builtins/gen/select/a2860e.wgsl.expected.glsl b/test/tint/builtins/gen/select/a2860e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/a2860e.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/a2860e.wgsl.expected.hlsl b/test/tint/builtins/gen/select/a2860e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/a2860e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/a2860e.wgsl.expected.msl b/test/tint/builtins/gen/select/a2860e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl.expected.msl
rename to test/tint/builtins/gen/select/a2860e.wgsl.expected.msl
diff --git a/test/builtins/gen/select/a2860e.wgsl.expected.spvasm b/test/tint/builtins/gen/select/a2860e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/a2860e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/a2860e.wgsl.expected.wgsl b/test/tint/builtins/gen/select/a2860e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/a2860e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/a2860e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/ab069f.wgsl b/test/tint/builtins/gen/select/ab069f.wgsl
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl
rename to test/tint/builtins/gen/select/ab069f.wgsl
diff --git a/test/builtins/gen/select/ab069f.wgsl.expected.glsl b/test/tint/builtins/gen/select/ab069f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/ab069f.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/ab069f.wgsl.expected.hlsl b/test/tint/builtins/gen/select/ab069f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/ab069f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/ab069f.wgsl.expected.msl b/test/tint/builtins/gen/select/ab069f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl.expected.msl
rename to test/tint/builtins/gen/select/ab069f.wgsl.expected.msl
diff --git a/test/builtins/gen/select/ab069f.wgsl.expected.spvasm b/test/tint/builtins/gen/select/ab069f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/ab069f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/ab069f.wgsl.expected.wgsl b/test/tint/builtins/gen/select/ab069f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/ab069f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/ab069f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/b04721.wgsl b/test/tint/builtins/gen/select/b04721.wgsl
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl
rename to test/tint/builtins/gen/select/b04721.wgsl
diff --git a/test/builtins/gen/select/b04721.wgsl.expected.glsl b/test/tint/builtins/gen/select/b04721.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/b04721.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/b04721.wgsl.expected.hlsl b/test/tint/builtins/gen/select/b04721.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/b04721.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/b04721.wgsl.expected.msl b/test/tint/builtins/gen/select/b04721.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl.expected.msl
rename to test/tint/builtins/gen/select/b04721.wgsl.expected.msl
diff --git a/test/builtins/gen/select/b04721.wgsl.expected.spvasm b/test/tint/builtins/gen/select/b04721.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/b04721.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/b04721.wgsl.expected.wgsl b/test/tint/builtins/gen/select/b04721.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/b04721.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/b04721.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/bb447f.wgsl b/test/tint/builtins/gen/select/bb447f.wgsl
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl
rename to test/tint/builtins/gen/select/bb447f.wgsl
diff --git a/test/builtins/gen/select/bb447f.wgsl.expected.glsl b/test/tint/builtins/gen/select/bb447f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/bb447f.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/bb447f.wgsl.expected.hlsl b/test/tint/builtins/gen/select/bb447f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/bb447f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/bb447f.wgsl.expected.msl b/test/tint/builtins/gen/select/bb447f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl.expected.msl
rename to test/tint/builtins/gen/select/bb447f.wgsl.expected.msl
diff --git a/test/builtins/gen/select/bb447f.wgsl.expected.spvasm b/test/tint/builtins/gen/select/bb447f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/bb447f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/bb447f.wgsl.expected.wgsl b/test/tint/builtins/gen/select/bb447f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/bb447f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/bb447f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/bb8aae.wgsl b/test/tint/builtins/gen/select/bb8aae.wgsl
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl
rename to test/tint/builtins/gen/select/bb8aae.wgsl
diff --git a/test/builtins/gen/select/bb8aae.wgsl.expected.glsl b/test/tint/builtins/gen/select/bb8aae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/bb8aae.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/bb8aae.wgsl.expected.hlsl b/test/tint/builtins/gen/select/bb8aae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/bb8aae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/bb8aae.wgsl.expected.msl b/test/tint/builtins/gen/select/bb8aae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl.expected.msl
rename to test/tint/builtins/gen/select/bb8aae.wgsl.expected.msl
diff --git a/test/builtins/gen/select/bb8aae.wgsl.expected.spvasm b/test/tint/builtins/gen/select/bb8aae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/bb8aae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/bb8aae.wgsl.expected.wgsl b/test/tint/builtins/gen/select/bb8aae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/bb8aae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/bb8aae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/bf3d29.wgsl b/test/tint/builtins/gen/select/bf3d29.wgsl
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl
rename to test/tint/builtins/gen/select/bf3d29.wgsl
diff --git a/test/builtins/gen/select/bf3d29.wgsl.expected.glsl b/test/tint/builtins/gen/select/bf3d29.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/bf3d29.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/bf3d29.wgsl.expected.hlsl b/test/tint/builtins/gen/select/bf3d29.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/bf3d29.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/bf3d29.wgsl.expected.msl b/test/tint/builtins/gen/select/bf3d29.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl.expected.msl
rename to test/tint/builtins/gen/select/bf3d29.wgsl.expected.msl
diff --git a/test/builtins/gen/select/bf3d29.wgsl.expected.spvasm b/test/tint/builtins/gen/select/bf3d29.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/bf3d29.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/bf3d29.wgsl.expected.wgsl b/test/tint/builtins/gen/select/bf3d29.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/bf3d29.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/bf3d29.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/c31f9e.wgsl b/test/tint/builtins/gen/select/c31f9e.wgsl
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl
rename to test/tint/builtins/gen/select/c31f9e.wgsl
diff --git a/test/builtins/gen/select/c31f9e.wgsl.expected.glsl b/test/tint/builtins/gen/select/c31f9e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/c31f9e.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/c31f9e.wgsl.expected.hlsl b/test/tint/builtins/gen/select/c31f9e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/c31f9e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/c31f9e.wgsl.expected.msl b/test/tint/builtins/gen/select/c31f9e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl.expected.msl
rename to test/tint/builtins/gen/select/c31f9e.wgsl.expected.msl
diff --git a/test/builtins/gen/select/c31f9e.wgsl.expected.spvasm b/test/tint/builtins/gen/select/c31f9e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/c31f9e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/c31f9e.wgsl.expected.wgsl b/test/tint/builtins/gen/select/c31f9e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/c31f9e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/c31f9e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/c41bd1.wgsl b/test/tint/builtins/gen/select/c41bd1.wgsl
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl
rename to test/tint/builtins/gen/select/c41bd1.wgsl
diff --git a/test/builtins/gen/select/c41bd1.wgsl.expected.glsl b/test/tint/builtins/gen/select/c41bd1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/c41bd1.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/c41bd1.wgsl.expected.hlsl b/test/tint/builtins/gen/select/c41bd1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/c41bd1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/c41bd1.wgsl.expected.msl b/test/tint/builtins/gen/select/c41bd1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl.expected.msl
rename to test/tint/builtins/gen/select/c41bd1.wgsl.expected.msl
diff --git a/test/builtins/gen/select/c41bd1.wgsl.expected.spvasm b/test/tint/builtins/gen/select/c41bd1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/c41bd1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/c41bd1.wgsl.expected.wgsl b/test/tint/builtins/gen/select/c41bd1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/c41bd1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/c41bd1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/c4a4ef.wgsl b/test/tint/builtins/gen/select/c4a4ef.wgsl
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl
rename to test/tint/builtins/gen/select/c4a4ef.wgsl
diff --git a/test/builtins/gen/select/c4a4ef.wgsl.expected.glsl b/test/tint/builtins/gen/select/c4a4ef.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/c4a4ef.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/c4a4ef.wgsl.expected.hlsl b/test/tint/builtins/gen/select/c4a4ef.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/c4a4ef.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/c4a4ef.wgsl.expected.msl b/test/tint/builtins/gen/select/c4a4ef.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl.expected.msl
rename to test/tint/builtins/gen/select/c4a4ef.wgsl.expected.msl
diff --git a/test/builtins/gen/select/c4a4ef.wgsl.expected.spvasm b/test/tint/builtins/gen/select/c4a4ef.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/c4a4ef.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/c4a4ef.wgsl.expected.wgsl b/test/tint/builtins/gen/select/c4a4ef.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/c4a4ef.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/c4a4ef.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/cb9301.wgsl b/test/tint/builtins/gen/select/cb9301.wgsl
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl
rename to test/tint/builtins/gen/select/cb9301.wgsl
diff --git a/test/builtins/gen/select/cb9301.wgsl.expected.glsl b/test/tint/builtins/gen/select/cb9301.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/cb9301.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/cb9301.wgsl.expected.hlsl b/test/tint/builtins/gen/select/cb9301.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/cb9301.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/cb9301.wgsl.expected.msl b/test/tint/builtins/gen/select/cb9301.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl.expected.msl
rename to test/tint/builtins/gen/select/cb9301.wgsl.expected.msl
diff --git a/test/builtins/gen/select/cb9301.wgsl.expected.spvasm b/test/tint/builtins/gen/select/cb9301.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/cb9301.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/cb9301.wgsl.expected.wgsl b/test/tint/builtins/gen/select/cb9301.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/cb9301.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/cb9301.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/e3e028.wgsl b/test/tint/builtins/gen/select/e3e028.wgsl
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl
rename to test/tint/builtins/gen/select/e3e028.wgsl
diff --git a/test/builtins/gen/select/e3e028.wgsl.expected.glsl b/test/tint/builtins/gen/select/e3e028.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/e3e028.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/e3e028.wgsl.expected.hlsl b/test/tint/builtins/gen/select/e3e028.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/e3e028.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/e3e028.wgsl.expected.msl b/test/tint/builtins/gen/select/e3e028.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl.expected.msl
rename to test/tint/builtins/gen/select/e3e028.wgsl.expected.msl
diff --git a/test/builtins/gen/select/e3e028.wgsl.expected.spvasm b/test/tint/builtins/gen/select/e3e028.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/e3e028.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/e3e028.wgsl.expected.wgsl b/test/tint/builtins/gen/select/e3e028.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/e3e028.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/e3e028.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/ebfea2.wgsl b/test/tint/builtins/gen/select/ebfea2.wgsl
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl
rename to test/tint/builtins/gen/select/ebfea2.wgsl
diff --git a/test/builtins/gen/select/ebfea2.wgsl.expected.glsl b/test/tint/builtins/gen/select/ebfea2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/ebfea2.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/ebfea2.wgsl.expected.hlsl b/test/tint/builtins/gen/select/ebfea2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/ebfea2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/ebfea2.wgsl.expected.msl b/test/tint/builtins/gen/select/ebfea2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl.expected.msl
rename to test/tint/builtins/gen/select/ebfea2.wgsl.expected.msl
diff --git a/test/builtins/gen/select/ebfea2.wgsl.expected.spvasm b/test/tint/builtins/gen/select/ebfea2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/ebfea2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/ebfea2.wgsl.expected.wgsl b/test/tint/builtins/gen/select/ebfea2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/ebfea2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/ebfea2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/ed8a15.wgsl b/test/tint/builtins/gen/select/ed8a15.wgsl
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl
rename to test/tint/builtins/gen/select/ed8a15.wgsl
diff --git a/test/builtins/gen/select/ed8a15.wgsl.expected.glsl b/test/tint/builtins/gen/select/ed8a15.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/ed8a15.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/ed8a15.wgsl.expected.hlsl b/test/tint/builtins/gen/select/ed8a15.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/ed8a15.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/ed8a15.wgsl.expected.msl b/test/tint/builtins/gen/select/ed8a15.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl.expected.msl
rename to test/tint/builtins/gen/select/ed8a15.wgsl.expected.msl
diff --git a/test/builtins/gen/select/ed8a15.wgsl.expected.spvasm b/test/tint/builtins/gen/select/ed8a15.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/ed8a15.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/ed8a15.wgsl.expected.wgsl b/test/tint/builtins/gen/select/ed8a15.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/ed8a15.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/ed8a15.wgsl.expected.wgsl
diff --git a/test/builtins/gen/select/fb7e53.wgsl b/test/tint/builtins/gen/select/fb7e53.wgsl
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl
rename to test/tint/builtins/gen/select/fb7e53.wgsl
diff --git a/test/builtins/gen/select/fb7e53.wgsl.expected.glsl b/test/tint/builtins/gen/select/fb7e53.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl.expected.glsl
rename to test/tint/builtins/gen/select/fb7e53.wgsl.expected.glsl
diff --git a/test/builtins/gen/select/fb7e53.wgsl.expected.hlsl b/test/tint/builtins/gen/select/fb7e53.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl.expected.hlsl
rename to test/tint/builtins/gen/select/fb7e53.wgsl.expected.hlsl
diff --git a/test/builtins/gen/select/fb7e53.wgsl.expected.msl b/test/tint/builtins/gen/select/fb7e53.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl.expected.msl
rename to test/tint/builtins/gen/select/fb7e53.wgsl.expected.msl
diff --git a/test/builtins/gen/select/fb7e53.wgsl.expected.spvasm b/test/tint/builtins/gen/select/fb7e53.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl.expected.spvasm
rename to test/tint/builtins/gen/select/fb7e53.wgsl.expected.spvasm
diff --git a/test/builtins/gen/select/fb7e53.wgsl.expected.wgsl b/test/tint/builtins/gen/select/fb7e53.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/select/fb7e53.wgsl.expected.wgsl
rename to test/tint/builtins/gen/select/fb7e53.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sign/159665.wgsl b/test/tint/builtins/gen/sign/159665.wgsl
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl
rename to test/tint/builtins/gen/sign/159665.wgsl
diff --git a/test/builtins/gen/sign/159665.wgsl.expected.glsl b/test/tint/builtins/gen/sign/159665.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl.expected.glsl
rename to test/tint/builtins/gen/sign/159665.wgsl.expected.glsl
diff --git a/test/builtins/gen/sign/159665.wgsl.expected.hlsl b/test/tint/builtins/gen/sign/159665.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sign/159665.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sign/159665.wgsl.expected.msl b/test/tint/builtins/gen/sign/159665.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl.expected.msl
rename to test/tint/builtins/gen/sign/159665.wgsl.expected.msl
diff --git a/test/builtins/gen/sign/159665.wgsl.expected.spvasm b/test/tint/builtins/gen/sign/159665.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sign/159665.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sign/159665.wgsl.expected.wgsl b/test/tint/builtins/gen/sign/159665.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sign/159665.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sign/159665.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sign/b8f634.wgsl b/test/tint/builtins/gen/sign/b8f634.wgsl
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl
rename to test/tint/builtins/gen/sign/b8f634.wgsl
diff --git a/test/builtins/gen/sign/b8f634.wgsl.expected.glsl b/test/tint/builtins/gen/sign/b8f634.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl.expected.glsl
rename to test/tint/builtins/gen/sign/b8f634.wgsl.expected.glsl
diff --git a/test/builtins/gen/sign/b8f634.wgsl.expected.hlsl b/test/tint/builtins/gen/sign/b8f634.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sign/b8f634.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sign/b8f634.wgsl.expected.msl b/test/tint/builtins/gen/sign/b8f634.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl.expected.msl
rename to test/tint/builtins/gen/sign/b8f634.wgsl.expected.msl
diff --git a/test/builtins/gen/sign/b8f634.wgsl.expected.spvasm b/test/tint/builtins/gen/sign/b8f634.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sign/b8f634.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sign/b8f634.wgsl.expected.wgsl b/test/tint/builtins/gen/sign/b8f634.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sign/b8f634.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sign/b8f634.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sign/d065d8.wgsl b/test/tint/builtins/gen/sign/d065d8.wgsl
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl
rename to test/tint/builtins/gen/sign/d065d8.wgsl
diff --git a/test/builtins/gen/sign/d065d8.wgsl.expected.glsl b/test/tint/builtins/gen/sign/d065d8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl.expected.glsl
rename to test/tint/builtins/gen/sign/d065d8.wgsl.expected.glsl
diff --git a/test/builtins/gen/sign/d065d8.wgsl.expected.hlsl b/test/tint/builtins/gen/sign/d065d8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sign/d065d8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sign/d065d8.wgsl.expected.msl b/test/tint/builtins/gen/sign/d065d8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl.expected.msl
rename to test/tint/builtins/gen/sign/d065d8.wgsl.expected.msl
diff --git a/test/builtins/gen/sign/d065d8.wgsl.expected.spvasm b/test/tint/builtins/gen/sign/d065d8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sign/d065d8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sign/d065d8.wgsl.expected.wgsl b/test/tint/builtins/gen/sign/d065d8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sign/d065d8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sign/d065d8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sign/dd790e.wgsl b/test/tint/builtins/gen/sign/dd790e.wgsl
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl
rename to test/tint/builtins/gen/sign/dd790e.wgsl
diff --git a/test/builtins/gen/sign/dd790e.wgsl.expected.glsl b/test/tint/builtins/gen/sign/dd790e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl.expected.glsl
rename to test/tint/builtins/gen/sign/dd790e.wgsl.expected.glsl
diff --git a/test/builtins/gen/sign/dd790e.wgsl.expected.hlsl b/test/tint/builtins/gen/sign/dd790e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sign/dd790e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sign/dd790e.wgsl.expected.msl b/test/tint/builtins/gen/sign/dd790e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl.expected.msl
rename to test/tint/builtins/gen/sign/dd790e.wgsl.expected.msl
diff --git a/test/builtins/gen/sign/dd790e.wgsl.expected.spvasm b/test/tint/builtins/gen/sign/dd790e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sign/dd790e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sign/dd790e.wgsl.expected.wgsl b/test/tint/builtins/gen/sign/dd790e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sign/dd790e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sign/dd790e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sin/01f241.wgsl b/test/tint/builtins/gen/sin/01f241.wgsl
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl
rename to test/tint/builtins/gen/sin/01f241.wgsl
diff --git a/test/builtins/gen/sin/01f241.wgsl.expected.glsl b/test/tint/builtins/gen/sin/01f241.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl.expected.glsl
rename to test/tint/builtins/gen/sin/01f241.wgsl.expected.glsl
diff --git a/test/builtins/gen/sin/01f241.wgsl.expected.hlsl b/test/tint/builtins/gen/sin/01f241.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sin/01f241.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sin/01f241.wgsl.expected.msl b/test/tint/builtins/gen/sin/01f241.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl.expected.msl
rename to test/tint/builtins/gen/sin/01f241.wgsl.expected.msl
diff --git a/test/builtins/gen/sin/01f241.wgsl.expected.spvasm b/test/tint/builtins/gen/sin/01f241.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sin/01f241.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sin/01f241.wgsl.expected.wgsl b/test/tint/builtins/gen/sin/01f241.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sin/01f241.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sin/01f241.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sin/4e3979.wgsl b/test/tint/builtins/gen/sin/4e3979.wgsl
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl
rename to test/tint/builtins/gen/sin/4e3979.wgsl
diff --git a/test/builtins/gen/sin/4e3979.wgsl.expected.glsl b/test/tint/builtins/gen/sin/4e3979.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl.expected.glsl
rename to test/tint/builtins/gen/sin/4e3979.wgsl.expected.glsl
diff --git a/test/builtins/gen/sin/4e3979.wgsl.expected.hlsl b/test/tint/builtins/gen/sin/4e3979.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sin/4e3979.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sin/4e3979.wgsl.expected.msl b/test/tint/builtins/gen/sin/4e3979.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl.expected.msl
rename to test/tint/builtins/gen/sin/4e3979.wgsl.expected.msl
diff --git a/test/builtins/gen/sin/4e3979.wgsl.expected.spvasm b/test/tint/builtins/gen/sin/4e3979.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sin/4e3979.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sin/4e3979.wgsl.expected.wgsl b/test/tint/builtins/gen/sin/4e3979.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sin/4e3979.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sin/4e3979.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sin/b78c91.wgsl b/test/tint/builtins/gen/sin/b78c91.wgsl
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl
rename to test/tint/builtins/gen/sin/b78c91.wgsl
diff --git a/test/builtins/gen/sin/b78c91.wgsl.expected.glsl b/test/tint/builtins/gen/sin/b78c91.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl.expected.glsl
rename to test/tint/builtins/gen/sin/b78c91.wgsl.expected.glsl
diff --git a/test/builtins/gen/sin/b78c91.wgsl.expected.hlsl b/test/tint/builtins/gen/sin/b78c91.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sin/b78c91.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sin/b78c91.wgsl.expected.msl b/test/tint/builtins/gen/sin/b78c91.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl.expected.msl
rename to test/tint/builtins/gen/sin/b78c91.wgsl.expected.msl
diff --git a/test/builtins/gen/sin/b78c91.wgsl.expected.spvasm b/test/tint/builtins/gen/sin/b78c91.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sin/b78c91.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sin/b78c91.wgsl.expected.wgsl b/test/tint/builtins/gen/sin/b78c91.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sin/b78c91.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sin/b78c91.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl b/test/tint/builtins/gen/sin/fc8bc4.wgsl
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl.expected.glsl b/test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl.expected.glsl
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.glsl
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl.expected.hlsl b/test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl.expected.msl b/test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl.expected.msl
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.msl
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl.expected.spvasm b/test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sin/fc8bc4.wgsl.expected.wgsl b/test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sin/fc8bc4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sin/fc8bc4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sinh/445e33.wgsl b/test/tint/builtins/gen/sinh/445e33.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl
rename to test/tint/builtins/gen/sinh/445e33.wgsl
diff --git a/test/builtins/gen/sinh/445e33.wgsl.expected.glsl b/test/tint/builtins/gen/sinh/445e33.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl.expected.glsl
rename to test/tint/builtins/gen/sinh/445e33.wgsl.expected.glsl
diff --git a/test/builtins/gen/sinh/445e33.wgsl.expected.hlsl b/test/tint/builtins/gen/sinh/445e33.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sinh/445e33.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sinh/445e33.wgsl.expected.msl b/test/tint/builtins/gen/sinh/445e33.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl.expected.msl
rename to test/tint/builtins/gen/sinh/445e33.wgsl.expected.msl
diff --git a/test/builtins/gen/sinh/445e33.wgsl.expected.spvasm b/test/tint/builtins/gen/sinh/445e33.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sinh/445e33.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sinh/445e33.wgsl.expected.wgsl b/test/tint/builtins/gen/sinh/445e33.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/445e33.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sinh/445e33.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sinh/7bb598.wgsl b/test/tint/builtins/gen/sinh/7bb598.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl
rename to test/tint/builtins/gen/sinh/7bb598.wgsl
diff --git a/test/builtins/gen/sinh/7bb598.wgsl.expected.glsl b/test/tint/builtins/gen/sinh/7bb598.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl.expected.glsl
rename to test/tint/builtins/gen/sinh/7bb598.wgsl.expected.glsl
diff --git a/test/builtins/gen/sinh/7bb598.wgsl.expected.hlsl b/test/tint/builtins/gen/sinh/7bb598.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sinh/7bb598.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sinh/7bb598.wgsl.expected.msl b/test/tint/builtins/gen/sinh/7bb598.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl.expected.msl
rename to test/tint/builtins/gen/sinh/7bb598.wgsl.expected.msl
diff --git a/test/builtins/gen/sinh/7bb598.wgsl.expected.spvasm b/test/tint/builtins/gen/sinh/7bb598.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sinh/7bb598.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sinh/7bb598.wgsl.expected.wgsl b/test/tint/builtins/gen/sinh/7bb598.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/7bb598.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sinh/7bb598.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sinh/b9860e.wgsl b/test/tint/builtins/gen/sinh/b9860e.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl
rename to test/tint/builtins/gen/sinh/b9860e.wgsl
diff --git a/test/builtins/gen/sinh/b9860e.wgsl.expected.glsl b/test/tint/builtins/gen/sinh/b9860e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl.expected.glsl
rename to test/tint/builtins/gen/sinh/b9860e.wgsl.expected.glsl
diff --git a/test/builtins/gen/sinh/b9860e.wgsl.expected.hlsl b/test/tint/builtins/gen/sinh/b9860e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sinh/b9860e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sinh/b9860e.wgsl.expected.msl b/test/tint/builtins/gen/sinh/b9860e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl.expected.msl
rename to test/tint/builtins/gen/sinh/b9860e.wgsl.expected.msl
diff --git a/test/builtins/gen/sinh/b9860e.wgsl.expected.spvasm b/test/tint/builtins/gen/sinh/b9860e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sinh/b9860e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sinh/b9860e.wgsl.expected.wgsl b/test/tint/builtins/gen/sinh/b9860e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/b9860e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sinh/b9860e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl b/test/tint/builtins/gen/sinh/c9a5eb.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl.expected.glsl b/test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl.expected.glsl
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.glsl
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl.expected.hlsl b/test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl.expected.msl b/test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl.expected.msl
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.msl
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl.expected.spvasm b/test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sinh/c9a5eb.wgsl.expected.wgsl b/test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sinh/c9a5eb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sinh/c9a5eb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl b/test/tint/builtins/gen/smoothStep/5f615b.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl.expected.glsl b/test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl.expected.glsl
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.glsl
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl.expected.hlsl b/test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl.expected.msl b/test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl.expected.msl
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.msl
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl.expected.spvasm b/test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/smoothStep/5f615b.wgsl.expected.wgsl b/test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/5f615b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/smoothStep/5f615b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl b/test/tint/builtins/gen/smoothStep/658be3.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl.expected.glsl b/test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl.expected.glsl
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.glsl
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl.expected.hlsl b/test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl.expected.msl b/test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl.expected.msl
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.msl
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl.expected.spvasm b/test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/smoothStep/658be3.wgsl.expected.wgsl b/test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/658be3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/smoothStep/658be3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl b/test/tint/builtins/gen/smoothStep/c11eef.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl.expected.glsl b/test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl.expected.glsl
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.glsl
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl.expected.hlsl b/test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl.expected.hlsl
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.hlsl
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl.expected.msl b/test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl.expected.msl
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.msl
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl.expected.spvasm b/test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl.expected.spvasm
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.spvasm
diff --git a/test/builtins/gen/smoothStep/c11eef.wgsl.expected.wgsl b/test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/c11eef.wgsl.expected.wgsl
rename to test/tint/builtins/gen/smoothStep/c11eef.wgsl.expected.wgsl
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.glsl b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.glsl
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.glsl
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.hlsl b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.msl b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.msl
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.msl
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.spvasm b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.wgsl b/test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/smoothStep/cb0bfb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/smoothStep/cb0bfb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl b/test/tint/builtins/gen/sqrt/20c74e.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl.expected.glsl b/test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl.expected.glsl
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.glsl
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl.expected.hlsl b/test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl.expected.msl b/test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl.expected.msl
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.msl
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl.expected.spvasm b/test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sqrt/20c74e.wgsl.expected.wgsl b/test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/20c74e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sqrt/20c74e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl b/test/tint/builtins/gen/sqrt/8c7024.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl.expected.glsl b/test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl.expected.glsl
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.glsl
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl.expected.hlsl b/test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl.expected.msl b/test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl.expected.msl
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.msl
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl.expected.spvasm b/test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sqrt/8c7024.wgsl.expected.wgsl b/test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/8c7024.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sqrt/8c7024.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl.expected.glsl b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl.expected.glsl
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.glsl
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl.expected.hlsl b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl.expected.msl b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl.expected.msl
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.msl
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl.expected.spvasm b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sqrt/aa0d7a.wgsl.expected.wgsl b/test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/aa0d7a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sqrt/aa0d7a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl b/test/tint/builtins/gen/sqrt/f8c59a.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl.expected.glsl b/test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl.expected.glsl
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.glsl
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl.expected.hlsl b/test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl.expected.msl b/test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl.expected.msl
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.msl
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl.expected.spvasm b/test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/sqrt/f8c59a.wgsl.expected.wgsl b/test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/sqrt/f8c59a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/sqrt/f8c59a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/step/0b073b.wgsl b/test/tint/builtins/gen/step/0b073b.wgsl
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl
rename to test/tint/builtins/gen/step/0b073b.wgsl
diff --git a/test/builtins/gen/step/0b073b.wgsl.expected.glsl b/test/tint/builtins/gen/step/0b073b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl.expected.glsl
rename to test/tint/builtins/gen/step/0b073b.wgsl.expected.glsl
diff --git a/test/builtins/gen/step/0b073b.wgsl.expected.hlsl b/test/tint/builtins/gen/step/0b073b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/step/0b073b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/step/0b073b.wgsl.expected.msl b/test/tint/builtins/gen/step/0b073b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl.expected.msl
rename to test/tint/builtins/gen/step/0b073b.wgsl.expected.msl
diff --git a/test/builtins/gen/step/0b073b.wgsl.expected.spvasm b/test/tint/builtins/gen/step/0b073b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/step/0b073b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/step/0b073b.wgsl.expected.wgsl b/test/tint/builtins/gen/step/0b073b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/step/0b073b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/step/0b073b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/step/19accd.wgsl b/test/tint/builtins/gen/step/19accd.wgsl
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl
rename to test/tint/builtins/gen/step/19accd.wgsl
diff --git a/test/builtins/gen/step/19accd.wgsl.expected.glsl b/test/tint/builtins/gen/step/19accd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl.expected.glsl
rename to test/tint/builtins/gen/step/19accd.wgsl.expected.glsl
diff --git a/test/builtins/gen/step/19accd.wgsl.expected.hlsl b/test/tint/builtins/gen/step/19accd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/step/19accd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/step/19accd.wgsl.expected.msl b/test/tint/builtins/gen/step/19accd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl.expected.msl
rename to test/tint/builtins/gen/step/19accd.wgsl.expected.msl
diff --git a/test/builtins/gen/step/19accd.wgsl.expected.spvasm b/test/tint/builtins/gen/step/19accd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/step/19accd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/step/19accd.wgsl.expected.wgsl b/test/tint/builtins/gen/step/19accd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/step/19accd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/step/19accd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/step/334303.wgsl b/test/tint/builtins/gen/step/334303.wgsl
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl
rename to test/tint/builtins/gen/step/334303.wgsl
diff --git a/test/builtins/gen/step/334303.wgsl.expected.glsl b/test/tint/builtins/gen/step/334303.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl.expected.glsl
rename to test/tint/builtins/gen/step/334303.wgsl.expected.glsl
diff --git a/test/builtins/gen/step/334303.wgsl.expected.hlsl b/test/tint/builtins/gen/step/334303.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl.expected.hlsl
rename to test/tint/builtins/gen/step/334303.wgsl.expected.hlsl
diff --git a/test/builtins/gen/step/334303.wgsl.expected.msl b/test/tint/builtins/gen/step/334303.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl.expected.msl
rename to test/tint/builtins/gen/step/334303.wgsl.expected.msl
diff --git a/test/builtins/gen/step/334303.wgsl.expected.spvasm b/test/tint/builtins/gen/step/334303.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl.expected.spvasm
rename to test/tint/builtins/gen/step/334303.wgsl.expected.spvasm
diff --git a/test/builtins/gen/step/334303.wgsl.expected.wgsl b/test/tint/builtins/gen/step/334303.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/step/334303.wgsl.expected.wgsl
rename to test/tint/builtins/gen/step/334303.wgsl.expected.wgsl
diff --git a/test/builtins/gen/step/e2b337.wgsl b/test/tint/builtins/gen/step/e2b337.wgsl
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl
rename to test/tint/builtins/gen/step/e2b337.wgsl
diff --git a/test/builtins/gen/step/e2b337.wgsl.expected.glsl b/test/tint/builtins/gen/step/e2b337.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl.expected.glsl
rename to test/tint/builtins/gen/step/e2b337.wgsl.expected.glsl
diff --git a/test/builtins/gen/step/e2b337.wgsl.expected.hlsl b/test/tint/builtins/gen/step/e2b337.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl.expected.hlsl
rename to test/tint/builtins/gen/step/e2b337.wgsl.expected.hlsl
diff --git a/test/builtins/gen/step/e2b337.wgsl.expected.msl b/test/tint/builtins/gen/step/e2b337.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl.expected.msl
rename to test/tint/builtins/gen/step/e2b337.wgsl.expected.msl
diff --git a/test/builtins/gen/step/e2b337.wgsl.expected.spvasm b/test/tint/builtins/gen/step/e2b337.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl.expected.spvasm
rename to test/tint/builtins/gen/step/e2b337.wgsl.expected.spvasm
diff --git a/test/builtins/gen/step/e2b337.wgsl.expected.wgsl b/test/tint/builtins/gen/step/e2b337.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/step/e2b337.wgsl.expected.wgsl
rename to test/tint/builtins/gen/step/e2b337.wgsl.expected.wgsl
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl b/test/tint/builtins/gen/storageBarrier/d87211.wgsl
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl.expected.glsl b/test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl.expected.glsl
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.glsl
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl.expected.hlsl b/test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl.expected.hlsl
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.hlsl
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl.expected.msl b/test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl.expected.msl
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.msl
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl.expected.spvasm b/test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl.expected.spvasm
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.spvasm
diff --git a/test/builtins/gen/storageBarrier/d87211.wgsl.expected.wgsl b/test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/storageBarrier/d87211.wgsl.expected.wgsl
rename to test/tint/builtins/gen/storageBarrier/d87211.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tan/244e2a.wgsl b/test/tint/builtins/gen/tan/244e2a.wgsl
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl
rename to test/tint/builtins/gen/tan/244e2a.wgsl
diff --git a/test/builtins/gen/tan/244e2a.wgsl.expected.glsl b/test/tint/builtins/gen/tan/244e2a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl.expected.glsl
rename to test/tint/builtins/gen/tan/244e2a.wgsl.expected.glsl
diff --git a/test/builtins/gen/tan/244e2a.wgsl.expected.hlsl b/test/tint/builtins/gen/tan/244e2a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tan/244e2a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tan/244e2a.wgsl.expected.msl b/test/tint/builtins/gen/tan/244e2a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl.expected.msl
rename to test/tint/builtins/gen/tan/244e2a.wgsl.expected.msl
diff --git a/test/builtins/gen/tan/244e2a.wgsl.expected.spvasm b/test/tint/builtins/gen/tan/244e2a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tan/244e2a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tan/244e2a.wgsl.expected.wgsl b/test/tint/builtins/gen/tan/244e2a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tan/244e2a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tan/244e2a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tan/2f030e.wgsl b/test/tint/builtins/gen/tan/2f030e.wgsl
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl
rename to test/tint/builtins/gen/tan/2f030e.wgsl
diff --git a/test/builtins/gen/tan/2f030e.wgsl.expected.glsl b/test/tint/builtins/gen/tan/2f030e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl.expected.glsl
rename to test/tint/builtins/gen/tan/2f030e.wgsl.expected.glsl
diff --git a/test/builtins/gen/tan/2f030e.wgsl.expected.hlsl b/test/tint/builtins/gen/tan/2f030e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tan/2f030e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tan/2f030e.wgsl.expected.msl b/test/tint/builtins/gen/tan/2f030e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl.expected.msl
rename to test/tint/builtins/gen/tan/2f030e.wgsl.expected.msl
diff --git a/test/builtins/gen/tan/2f030e.wgsl.expected.spvasm b/test/tint/builtins/gen/tan/2f030e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tan/2f030e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tan/2f030e.wgsl.expected.wgsl b/test/tint/builtins/gen/tan/2f030e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tan/2f030e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tan/2f030e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tan/7ea104.wgsl b/test/tint/builtins/gen/tan/7ea104.wgsl
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl
rename to test/tint/builtins/gen/tan/7ea104.wgsl
diff --git a/test/builtins/gen/tan/7ea104.wgsl.expected.glsl b/test/tint/builtins/gen/tan/7ea104.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl.expected.glsl
rename to test/tint/builtins/gen/tan/7ea104.wgsl.expected.glsl
diff --git a/test/builtins/gen/tan/7ea104.wgsl.expected.hlsl b/test/tint/builtins/gen/tan/7ea104.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tan/7ea104.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tan/7ea104.wgsl.expected.msl b/test/tint/builtins/gen/tan/7ea104.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl.expected.msl
rename to test/tint/builtins/gen/tan/7ea104.wgsl.expected.msl
diff --git a/test/builtins/gen/tan/7ea104.wgsl.expected.spvasm b/test/tint/builtins/gen/tan/7ea104.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tan/7ea104.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tan/7ea104.wgsl.expected.wgsl b/test/tint/builtins/gen/tan/7ea104.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tan/7ea104.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tan/7ea104.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl b/test/tint/builtins/gen/tan/8ce3e9.wgsl
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl.expected.glsl b/test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl.expected.glsl
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.glsl
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl.expected.hlsl b/test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl.expected.msl b/test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl.expected.msl
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.msl
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl.expected.spvasm b/test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tan/8ce3e9.wgsl.expected.wgsl b/test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tan/8ce3e9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tan/8ce3e9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tanh/5663c5.wgsl b/test/tint/builtins/gen/tanh/5663c5.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl
rename to test/tint/builtins/gen/tanh/5663c5.wgsl
diff --git a/test/builtins/gen/tanh/5663c5.wgsl.expected.glsl b/test/tint/builtins/gen/tanh/5663c5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl.expected.glsl
rename to test/tint/builtins/gen/tanh/5663c5.wgsl.expected.glsl
diff --git a/test/builtins/gen/tanh/5663c5.wgsl.expected.hlsl b/test/tint/builtins/gen/tanh/5663c5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tanh/5663c5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tanh/5663c5.wgsl.expected.msl b/test/tint/builtins/gen/tanh/5663c5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl.expected.msl
rename to test/tint/builtins/gen/tanh/5663c5.wgsl.expected.msl
diff --git a/test/builtins/gen/tanh/5663c5.wgsl.expected.spvasm b/test/tint/builtins/gen/tanh/5663c5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tanh/5663c5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tanh/5663c5.wgsl.expected.wgsl b/test/tint/builtins/gen/tanh/5663c5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/5663c5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tanh/5663c5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tanh/5724b3.wgsl b/test/tint/builtins/gen/tanh/5724b3.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl
rename to test/tint/builtins/gen/tanh/5724b3.wgsl
diff --git a/test/builtins/gen/tanh/5724b3.wgsl.expected.glsl b/test/tint/builtins/gen/tanh/5724b3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl.expected.glsl
rename to test/tint/builtins/gen/tanh/5724b3.wgsl.expected.glsl
diff --git a/test/builtins/gen/tanh/5724b3.wgsl.expected.hlsl b/test/tint/builtins/gen/tanh/5724b3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tanh/5724b3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tanh/5724b3.wgsl.expected.msl b/test/tint/builtins/gen/tanh/5724b3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl.expected.msl
rename to test/tint/builtins/gen/tanh/5724b3.wgsl.expected.msl
diff --git a/test/builtins/gen/tanh/5724b3.wgsl.expected.spvasm b/test/tint/builtins/gen/tanh/5724b3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tanh/5724b3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tanh/5724b3.wgsl.expected.wgsl b/test/tint/builtins/gen/tanh/5724b3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/5724b3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tanh/5724b3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl b/test/tint/builtins/gen/tanh/9f9fb9.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl.expected.glsl b/test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl.expected.glsl
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.glsl
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl.expected.hlsl b/test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl.expected.msl b/test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl.expected.msl
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.msl
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl.expected.spvasm b/test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tanh/9f9fb9.wgsl.expected.wgsl b/test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/9f9fb9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tanh/9f9fb9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl b/test/tint/builtins/gen/tanh/c15fdb.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl.expected.glsl b/test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl.expected.glsl
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.glsl
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl.expected.hlsl b/test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl.expected.msl b/test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl.expected.msl
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.msl
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl.expected.spvasm b/test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/tanh/c15fdb.wgsl.expected.wgsl b/test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/tanh/c15fdb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/tanh/c15fdb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/002b2a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/002b2a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/002b2a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl b/test/tint/builtins/gen/textureDimensions/012b82.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/012b82.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/012b82.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/012b82.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl b/test/tint/builtins/gen/textureDimensions/08753d.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/08753d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/08753d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/08753d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0c4772.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0c4772.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0c4772.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0cce40.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cce40.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0cce40.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0cf2ff.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0d8b7e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0e32ee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0e32ee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/0f3c50.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/0f3c50.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/1191a5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1191a5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/1191a5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/12c9bb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/12c9bb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl b/test/tint/builtins/gen/textureDimensions/147998.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/147998.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/147998.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/147998.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl b/test/tint/builtins/gen/textureDimensions/16036c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/16036c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/16036c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/16036c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1b71f0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/1b71f0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1d6c26.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/1d6c26.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1e9e39.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/1e9e39.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/1f20c5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/1f20c5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/214dd4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/214dd4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/214dd4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl b/test/tint/builtins/gen/textureDimensions/221f22.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/221f22.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/221f22.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/221f22.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl b/test/tint/builtins/gen/textureDimensions/267788.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/267788.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/267788.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/267788.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26bdfa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/26bdfa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/26ef6c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/26ef6c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/2ad087.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2ad087.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/2ad087.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/2efa05.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2efa05.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/2efa05.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/2f289f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2f289f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/2f289f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/2fe1cc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/318ecc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/318ecc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/318ecc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl b/test/tint/builtins/gen/textureDimensions/340d06.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/340d06.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/340d06.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/340d06.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl b/test/tint/builtins/gen/textureDimensions/398e30.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/398e30.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/398e30.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/398e30.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3a94ea.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/3a94ea.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/3aca08.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3aca08.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/3aca08.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/3c5ad8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/4152a6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4152a6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/4152a6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl b/test/tint/builtins/gen/textureDimensions/423f99.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/423f99.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/423f99.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/423f99.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/4267ee.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4267ee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/4267ee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/42d4e6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/42d4e6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/48cb89.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/48cb89.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/48cb89.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl b/test/tint/builtins/gen/textureDimensions/49d274.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/49d274.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/49d274.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/49d274.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/4df9a8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/4df9a8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/50a9ee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/50a9ee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl b/test/tint/builtins/gen/textureDimensions/52045c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/52045c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/52045c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/52045c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/55b23e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/55b23e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/55b23e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl b/test/tint/builtins/gen/textureDimensions/579629.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/579629.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/579629.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/579629.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/57da0b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57da0b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/57da0b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/57e28f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/57e28f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/57e28f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl b/test/tint/builtins/gen/textureDimensions/58a515.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/58a515.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/58a515.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/58a515.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/5985f3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5985f3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/5985f3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5caa5e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/5caa5e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/5e295d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/5e295d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/5e295d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/60bf54.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/60bf54.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/60bf54.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/63f3cf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/63f3cf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl b/test/tint/builtins/gen/textureDimensions/68105c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/68105c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/68105c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/68105c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/686ef2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/686ef2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/686ef2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/6adac6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6adac6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/6adac6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/6ec1b4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/6f0d79.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/6f0d79.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl b/test/tint/builtins/gen/textureDimensions/702c53.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/702c53.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/702c53.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/702c53.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/72e5d6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/72e5d6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl b/test/tint/builtins/gen/textureDimensions/79df87.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/79df87.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/79df87.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/79df87.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/7bf826.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7bf826.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/7bf826.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/7f5c2e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/8028f3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8028f3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/8028f3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl b/test/tint/builtins/gen/textureDimensions/811679.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/811679.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/811679.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/811679.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl b/test/tint/builtins/gen/textureDimensions/820596.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/820596.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/820596.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/820596.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/83ee5a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/83ee5a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl b/test/tint/builtins/gen/textureDimensions/85d556.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/85d556.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/85d556.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/85d556.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/88ad17.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/88ad17.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/88ad17.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/8aa4c4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8deb5e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/8deb5e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8f20bf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/8f20bf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/8fca0f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/8fca0f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl b/test/tint/builtins/gen/textureDimensions/90340b.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/90340b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/90340b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/90340b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9042ab.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9042ab.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9042ab.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9393b0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9393b0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9393b0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/939fdb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/939fdb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/939fdb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/962dcd.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/962dcd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/962dcd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9abfe5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9abfe5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9c9c57.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9c9c57.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9da9e2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9da9e2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9eb8d8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/9f8e46.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/9f8e46.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl b/test/tint/builtins/gen/textureDimensions/a01845.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/a01845.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a01845.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/a01845.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/a7d565.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a7d565.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/a7d565.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/a863f2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a863f2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/a863f2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/a9c9c1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b0e16d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/b0e16d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/b3c954.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3c954.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/b3c954.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/b3e407.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b3e407.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/b3e407.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl b/test/tint/builtins/gen/textureDimensions/b91240.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/b91240.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/b91240.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/b91240.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/ba1481.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ba1481.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/ba1481.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/bb3dde.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/bb3dde.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/c30e75.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c30e75.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/c30e75.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/c7943d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/c7943d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/c7943d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cc968c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cc968c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cc968c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cccc8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cccc8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cd76a7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cd76a7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cdf473.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cdf473.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cdf473.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl b/test/tint/builtins/gen/textureDimensions/cec841.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cec841.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cec841.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cec841.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/cf7e43.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/cf7e43.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/d125bc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d125bc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/d125bc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/d83c45.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/d83c45.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/d83c45.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/daf7c0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/daf7c0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/dc2dd0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl b/test/tint/builtins/gen/textureDimensions/e927be.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/e927be.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e927be.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/e927be.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/e9e96c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/e9e96c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/ef5b89.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/ef5b89.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/efc8a4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/efc8a4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f60bdb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/f60bdb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/f7145b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f7145b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/f7145b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/f931c7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/f931c7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/f931c7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/fa9859.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fa9859.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/fa9859.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/fb5670.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fb5670.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/fb5670.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl.expected.glsl b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl.expected.hlsl b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl.expected.msl b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl.expected.msl
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.msl
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl.expected.spvasm b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureDimensions/fcac78.wgsl.expected.wgsl b/test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureDimensions/fcac78.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureDimensions/fcac78.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/01305f.wgsl b/test/tint/builtins/gen/textureGather/01305f.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl
rename to test/tint/builtins/gen/textureGather/01305f.wgsl
diff --git a/test/builtins/gen/textureGather/01305f.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/01305f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/01305f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/01305f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/01305f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/01305f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/01305f.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/01305f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/01305f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/01305f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/01305f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/01305f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/01305f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/01305f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/01305f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/01305f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/06030a.wgsl b/test/tint/builtins/gen/textureGather/06030a.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl
rename to test/tint/builtins/gen/textureGather/06030a.wgsl
diff --git a/test/builtins/gen/textureGather/06030a.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/06030a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/06030a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/06030a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/06030a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/06030a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/06030a.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/06030a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/06030a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/06030a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/06030a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/06030a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/06030a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/06030a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/06030a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/06030a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/10c554.wgsl b/test/tint/builtins/gen/textureGather/10c554.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl
rename to test/tint/builtins/gen/textureGather/10c554.wgsl
diff --git a/test/builtins/gen/textureGather/10c554.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/10c554.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/10c554.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/10c554.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/10c554.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/10c554.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/10c554.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/10c554.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/10c554.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/10c554.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/10c554.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/10c554.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/10c554.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/10c554.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/10c554.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/10c554.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl b/test/tint/builtins/gen/textureGather/15d79c.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/15d79c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/15d79c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/15d79c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/2e0ed5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/2e0ed5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/2e0ed5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl b/test/tint/builtins/gen/textureGather/3112e8.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/3112e8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/3112e8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/3112e8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl b/test/tint/builtins/gen/textureGather/3c527e.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/3c527e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/3c527e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/3c527e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/43025d.wgsl b/test/tint/builtins/gen/textureGather/43025d.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl
rename to test/tint/builtins/gen/textureGather/43025d.wgsl
diff --git a/test/builtins/gen/textureGather/43025d.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/43025d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/43025d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/43025d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/43025d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/43025d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/43025d.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/43025d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/43025d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/43025d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/43025d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/43025d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/43025d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/43025d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/43025d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/43025d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl b/test/tint/builtins/gen/textureGather/4f2350.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/4f2350.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/4f2350.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/4f2350.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl b/test/tint/builtins/gen/textureGather/51cf0b.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/51cf0b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/51cf0b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/51cf0b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl b/test/tint/builtins/gen/textureGather/53ece6.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/53ece6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/53ece6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/53ece6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl b/test/tint/builtins/gen/textureGather/57bfc6.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/57bfc6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/57bfc6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/57bfc6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl b/test/tint/builtins/gen/textureGather/587ba3.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/587ba3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/587ba3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/587ba3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl b/test/tint/builtins/gen/textureGather/69e0fb.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/69e0fb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/69e0fb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/69e0fb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/93003d.wgsl b/test/tint/builtins/gen/textureGather/93003d.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl
rename to test/tint/builtins/gen/textureGather/93003d.wgsl
diff --git a/test/builtins/gen/textureGather/93003d.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/93003d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/93003d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/93003d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/93003d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/93003d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/93003d.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/93003d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/93003d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/93003d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/93003d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/93003d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/93003d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/93003d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/93003d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/93003d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl b/test/tint/builtins/gen/textureGather/9a6358.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/9a6358.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/9a6358.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/9a6358.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl b/test/tint/builtins/gen/textureGather/9efca2.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/9efca2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/9efca2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/9efca2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/bd0b1e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/bd0b1e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/bd0b1e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl b/test/tint/builtins/gen/textureGather/c409ae.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/c409ae.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/c409ae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/c409ae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/c55822.wgsl b/test/tint/builtins/gen/textureGather/c55822.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl
rename to test/tint/builtins/gen/textureGather/c55822.wgsl
diff --git a/test/builtins/gen/textureGather/c55822.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/c55822.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/c55822.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/c55822.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/c55822.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/c55822.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/c55822.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/c55822.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/c55822.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/c55822.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/c55822.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/c55822.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/c55822.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/c55822.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/c55822.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/c55822.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl b/test/tint/builtins/gen/textureGather/e1b67d.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/e1b67d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/e1b67d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/e1b67d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl b/test/tint/builtins/gen/textureGather/e9eff6.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/e9eff6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/e9eff6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/e9eff6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/f5f3ba.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/f5f3ba.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/f5f3ba.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl b/test/tint/builtins/gen/textureGather/f7995a.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl.expected.glsl b/test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl.expected.msl b/test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGather/f7995a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGather/f7995a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGather/f7995a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/182fd4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/60d2d1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/6d9352.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/6f1267.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/783e65.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/783e65.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.glsl b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.hlsl b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.msl b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.msl
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.msl
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.spvasm b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.wgsl b/test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureGatherCompare/a5f587.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl b/test/tint/builtins/gen/textureLoad/19cf87.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/19cf87.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/19cf87.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/19cf87.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl b/test/tint/builtins/gen/textureLoad/1b8588.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/1b8588.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1b8588.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/1b8588.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl b/test/tint/builtins/gen/textureLoad/1f2016.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/1f2016.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/1f2016.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/1f2016.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/484344.wgsl b/test/tint/builtins/gen/textureLoad/484344.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl
rename to test/tint/builtins/gen/textureLoad/484344.wgsl
diff --git a/test/builtins/gen/textureLoad/484344.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/484344.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/484344.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/484344.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/484344.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/484344.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/484344.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/484344.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/484344.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/484344.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/484344.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/484344.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/484344.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/484344.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/484344.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/484344.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl b/test/tint/builtins/gen/textureLoad/4fd803.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/4fd803.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/4fd803.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/4fd803.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/5a2f9d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/5a2f9d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl b/test/tint/builtins/gen/textureLoad/6154d4.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/6154d4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6154d4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/6154d4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl b/test/tint/builtins/gen/textureLoad/6273b1.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/6273b1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/6273b1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/6273b1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl b/test/tint/builtins/gen/textureLoad/79e697.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/79e697.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/79e697.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/79e697.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/7c90e5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/7c90e5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/7c90e5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl b/test/tint/builtins/gen/textureLoad/81c381.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/81c381.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/81c381.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/81c381.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl b/test/tint/builtins/gen/textureLoad/87be85.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/87be85.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/87be85.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/87be85.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl b/test/tint/builtins/gen/textureLoad/8acf41.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/8acf41.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/8acf41.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/8acf41.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl b/test/tint/builtins/gen/textureLoad/9b2667.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/9b2667.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/9b2667.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/9b2667.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl b/test/tint/builtins/gen/textureLoad/a583c9.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/a583c9.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a583c9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/a583c9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/a9a9f5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/a9a9f5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl b/test/tint/builtins/gen/textureLoad/c2a480.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/c2a480.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c2a480.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/c2a480.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl b/test/tint/builtins/gen/textureLoad/c378ee.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/c378ee.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/c378ee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/c378ee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.glsl b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.msl b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureLoad/e3d2cc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureLoad/e3d2cc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl b/test/tint/builtins/gen/textureNumLayers/024820.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/024820.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/024820.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/024820.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/053df7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/053df7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/053df7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/058cc3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/058cc3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/09d05d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/09d05d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/13b4ce.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/22e53b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/22e53b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl b/test/tint/builtins/gen/textureNumLayers/562013.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/562013.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/562013.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/562013.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/5d59cd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/68a65b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/68a65b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/778bd1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/778bd1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/7f1937.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/7f1937.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/85f980.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/85f980.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/85f980.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/87953e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/87953e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/87953e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/893e7c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/893e7c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/9700fb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/9700fb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/a216d2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/a216d2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/cd5dc8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/d5b228.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/d5b228.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e31be1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/e31be1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/e653c0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/e653c0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ee942f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/ee942f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/f33005.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/f33005.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/f33005.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/fcec98.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/fcec98.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLayers/ff5e89.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/076cb5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/076cb5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/080d95.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/080d95.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/080d95.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/09ddd0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl b/test/tint/builtins/gen/textureNumLevels/105988.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/105988.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/105988.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/105988.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/1e6f3b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/23f750.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/23f750.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/23f750.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/2c3575.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/2c3575.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/32a0ae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/5101cf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/5101cf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/51b5bb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/897aaf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/897aaf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/9da7a5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/a91c03.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/a91c03.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/aee7c8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/b1b12b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/b4f5ea.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/d004a9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/d004a9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/dca09e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/dca09e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/e67231.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/e67231.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/e67231.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/ed078b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/ed078b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/f46ec6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.msl b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumLevels/f5828d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumLevels/f5828d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.msl b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumSamples/2c6f14.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.msl b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumSamples/42f8bb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl.expected.msl b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumSamples/449d23.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/449d23.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumSamples/449d23.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.glsl b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.msl b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureNumSamples/a3c8a0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl b/test/tint/builtins/gen/textureSample/02aa9b.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/02aa9b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/02aa9b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/02aa9b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl b/test/tint/builtins/gen/textureSample/100dc0.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/100dc0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/100dc0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/100dc0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl b/test/tint/builtins/gen/textureSample/38bbb9.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/38bbb9.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/38bbb9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/38bbb9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl b/test/tint/builtins/gen/textureSample/3b50bd.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/3b50bd.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/3b50bd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/3b50bd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/4dd1bf.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/4dd1bf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/4dd1bf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/51b514.wgsl b/test/tint/builtins/gen/textureSample/51b514.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl
rename to test/tint/builtins/gen/textureSample/51b514.wgsl
diff --git a/test/builtins/gen/textureSample/51b514.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/51b514.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/51b514.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/51b514.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/51b514.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/51b514.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/51b514.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/51b514.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/51b514.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/51b514.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/51b514.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/51b514.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/51b514.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/51b514.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/51b514.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/51b514.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/667d76.wgsl b/test/tint/builtins/gen/textureSample/667d76.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl
rename to test/tint/builtins/gen/textureSample/667d76.wgsl
diff --git a/test/builtins/gen/textureSample/667d76.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/667d76.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/667d76.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/667d76.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/667d76.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/667d76.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/667d76.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/667d76.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/667d76.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/667d76.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/667d76.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/667d76.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/667d76.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/667d76.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/667d76.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/667d76.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl b/test/tint/builtins/gen/textureSample/6717ca.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/6717ca.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/6717ca.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/6717ca.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl b/test/tint/builtins/gen/textureSample/6e64fb.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/6e64fb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/6e64fb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/6e64fb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl b/test/tint/builtins/gen/textureSample/7c3baa.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/7c3baa.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/7c3baa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/7c3baa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/7e9ffd.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/7e9ffd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/7e9ffd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl b/test/tint/builtins/gen/textureSample/8522e7.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/8522e7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/8522e7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/8522e7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/c2f4e8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/c2f4e8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/c2f4e8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/e53267.wgsl b/test/tint/builtins/gen/textureSample/e53267.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl
rename to test/tint/builtins/gen/textureSample/e53267.wgsl
diff --git a/test/builtins/gen/textureSample/e53267.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/e53267.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/e53267.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/e53267.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/e53267.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/e53267.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/e53267.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/e53267.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/e53267.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/e53267.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/e53267.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/e53267.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/e53267.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/e53267.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/e53267.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/e53267.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl b/test/tint/builtins/gen/textureSample/ea7030.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl.expected.glsl b/test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl.expected.msl b/test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSample/ea7030.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSample/ea7030.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSample/ea7030.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/53b9f7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/65ac50.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/65ac50.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/6a9113.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/6a9113.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/80e579.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/80e579.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/80e579.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/81c19a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/81c19a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/d3fa1b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/df91bb.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/df91bb.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleBias/eed7c4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/25fcd1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/3a5923.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/63fb83.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/98b85c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/a3ca7e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompare/dd431d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/011a8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1116ed.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/1568e3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/2ad2b1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/4cf3a2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleCompareLevel/f8121c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/21402b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/21402b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/2ecd8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/468f88.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/468f88.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/521263.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/521263.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/521263.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/5312f4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/872f00.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/872f00.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e383db.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/e383db.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleGrad/e9a2f7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/02be59.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/02be59.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/0bdd9a.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/1b0291.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/1bf73e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/302be4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/302be4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/47daa4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/690d95.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/690d95.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/979816.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/979816.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/979816.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/9bd37b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/a4af26.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/abfcc0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/ae5e39.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/ba93b3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/c32df7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.glsl b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.msl b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureSampleLevel/c6aca6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl b/test/tint/builtins/gen/textureStore/05ce15.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/05ce15.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/05ce15.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/05ce15.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl b/test/tint/builtins/gen/textureStore/064c7f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/064c7f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/064c7f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/064c7f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/068641.wgsl b/test/tint/builtins/gen/textureStore/068641.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl
rename to test/tint/builtins/gen/textureStore/068641.wgsl
diff --git a/test/builtins/gen/textureStore/068641.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/068641.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/068641.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/068641.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/068641.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/068641.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/068641.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/068641.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/068641.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/068641.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/068641.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/068641.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/068641.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/068641.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/068641.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/068641.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl b/test/tint/builtins/gen/textureStore/0af6b5.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/0af6b5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/0af6b5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/0af6b5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl b/test/tint/builtins/gen/textureStore/0c3dff.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/0c3dff.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/0c3dff.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/0c3dff.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/102722.wgsl b/test/tint/builtins/gen/textureStore/102722.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl
rename to test/tint/builtins/gen/textureStore/102722.wgsl
diff --git a/test/builtins/gen/textureStore/102722.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/102722.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/102722.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/102722.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/102722.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/102722.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/102722.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/102722.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/102722.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/102722.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/102722.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/102722.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/102722.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/102722.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/102722.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/102722.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl b/test/tint/builtins/gen/textureStore/1bbd08.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/1bbd08.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/1bbd08.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/1bbd08.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl b/test/tint/builtins/gen/textureStore/1c02e7.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/1c02e7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/1c02e7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/1c02e7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/22d955.wgsl b/test/tint/builtins/gen/textureStore/22d955.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl
rename to test/tint/builtins/gen/textureStore/22d955.wgsl
diff --git a/test/builtins/gen/textureStore/22d955.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/22d955.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/22d955.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/22d955.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/22d955.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/22d955.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/22d955.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/22d955.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/22d955.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/22d955.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/22d955.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/22d955.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/22d955.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/22d955.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/22d955.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/22d955.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl b/test/tint/builtins/gen/textureStore/26bf70.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/26bf70.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/26bf70.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/26bf70.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl b/test/tint/builtins/gen/textureStore/2796b4.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/2796b4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2796b4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/2796b4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/2ac6c7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ac6c7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/2ac6c7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/2eb2a4.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2eb2a4.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/2eb2a4.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/2ed2a3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/2ed2a3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/2ed2a3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/31745b.wgsl b/test/tint/builtins/gen/textureStore/31745b.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl
rename to test/tint/builtins/gen/textureStore/31745b.wgsl
diff --git a/test/builtins/gen/textureStore/31745b.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/31745b.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/31745b.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/31745b.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/31745b.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/31745b.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/31745b.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/31745b.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/31745b.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/31745b.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/31745b.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/31745b.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/31745b.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/31745b.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/31745b.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/31745b.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/32f368.wgsl b/test/tint/builtins/gen/textureStore/32f368.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl
rename to test/tint/builtins/gen/textureStore/32f368.wgsl
diff --git a/test/builtins/gen/textureStore/32f368.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/32f368.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/32f368.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/32f368.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/32f368.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/32f368.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/32f368.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/32f368.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/32f368.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/32f368.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/32f368.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/32f368.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/32f368.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/32f368.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/32f368.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/32f368.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/331aee.wgsl b/test/tint/builtins/gen/textureStore/331aee.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl
rename to test/tint/builtins/gen/textureStore/331aee.wgsl
diff --git a/test/builtins/gen/textureStore/331aee.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/331aee.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/331aee.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/331aee.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/331aee.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/331aee.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/331aee.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/331aee.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/331aee.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/331aee.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/331aee.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/331aee.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/331aee.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/331aee.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/331aee.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/331aee.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl b/test/tint/builtins/gen/textureStore/38e8d7.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/38e8d7.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/38e8d7.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/38e8d7.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl b/test/tint/builtins/gen/textureStore/3a52ac.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/3a52ac.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3a52ac.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/3a52ac.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/3bb7a1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bb7a1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/3bb7a1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl b/test/tint/builtins/gen/textureStore/3bec15.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/3bec15.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/3bec15.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/3bec15.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl b/test/tint/builtins/gen/textureStore/441ba8.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/441ba8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/441ba8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/441ba8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl b/test/tint/builtins/gen/textureStore/4fc057.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/4fc057.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/4fc057.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/4fc057.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/5a2f8f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/5a2f8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/5a2f8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/60975f.wgsl b/test/tint/builtins/gen/textureStore/60975f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl
rename to test/tint/builtins/gen/textureStore/60975f.wgsl
diff --git a/test/builtins/gen/textureStore/60975f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/60975f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/60975f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/60975f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/60975f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/60975f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/60975f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/60975f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/60975f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/60975f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/60975f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/60975f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/60975f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/60975f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/60975f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/60975f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl b/test/tint/builtins/gen/textureStore/682fd6.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/682fd6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/682fd6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/682fd6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl b/test/tint/builtins/gen/textureStore/6b75c3.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/6b75c3.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b75c3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/6b75c3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl b/test/tint/builtins/gen/textureStore/6b80d2.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/6b80d2.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6b80d2.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/6b80d2.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl b/test/tint/builtins/gen/textureStore/6cff2e.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/6cff2e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6cff2e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/6cff2e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/6da692.wgsl b/test/tint/builtins/gen/textureStore/6da692.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl
rename to test/tint/builtins/gen/textureStore/6da692.wgsl
diff --git a/test/builtins/gen/textureStore/6da692.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/6da692.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/6da692.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/6da692.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/6da692.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/6da692.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/6da692.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/6da692.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/6da692.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/6da692.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/6da692.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/6da692.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/6da692.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/6da692.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/6da692.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/6da692.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/731349.wgsl b/test/tint/builtins/gen/textureStore/731349.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl
rename to test/tint/builtins/gen/textureStore/731349.wgsl
diff --git a/test/builtins/gen/textureStore/731349.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/731349.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/731349.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/731349.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/731349.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/731349.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/731349.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/731349.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/731349.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/731349.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/731349.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/731349.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/731349.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/731349.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/731349.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/731349.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/752da6.wgsl b/test/tint/builtins/gen/textureStore/752da6.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl
rename to test/tint/builtins/gen/textureStore/752da6.wgsl
diff --git a/test/builtins/gen/textureStore/752da6.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/752da6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/752da6.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/752da6.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/752da6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/752da6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/752da6.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/752da6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/752da6.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/752da6.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/752da6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/752da6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/752da6.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/752da6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/752da6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/752da6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl b/test/tint/builtins/gen/textureStore/77c0ae.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/77c0ae.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/77c0ae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/77c0ae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl b/test/tint/builtins/gen/textureStore/7cec8d.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/7cec8d.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/7cec8d.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/7cec8d.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl b/test/tint/builtins/gen/textureStore/7f7fae.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/7f7fae.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/7f7fae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/7f7fae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/804942.wgsl b/test/tint/builtins/gen/textureStore/804942.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl
rename to test/tint/builtins/gen/textureStore/804942.wgsl
diff --git a/test/builtins/gen/textureStore/804942.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/804942.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/804942.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/804942.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/804942.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/804942.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/804942.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/804942.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/804942.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/804942.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/804942.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/804942.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/804942.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/804942.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/804942.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/804942.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/805dae.wgsl b/test/tint/builtins/gen/textureStore/805dae.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl
rename to test/tint/builtins/gen/textureStore/805dae.wgsl
diff --git a/test/builtins/gen/textureStore/805dae.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/805dae.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/805dae.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/805dae.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/805dae.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/805dae.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/805dae.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/805dae.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/805dae.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/805dae.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/805dae.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/805dae.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/805dae.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/805dae.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/805dae.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/805dae.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl b/test/tint/builtins/gen/textureStore/83bcc1.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/83bcc1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/83bcc1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/83bcc1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/872747.wgsl b/test/tint/builtins/gen/textureStore/872747.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl
rename to test/tint/builtins/gen/textureStore/872747.wgsl
diff --git a/test/builtins/gen/textureStore/872747.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/872747.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/872747.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/872747.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/872747.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/872747.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/872747.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/872747.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/872747.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/872747.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/872747.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/872747.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/872747.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/872747.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/872747.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/872747.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl b/test/tint/builtins/gen/textureStore/8e0479.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/8e0479.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/8e0479.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/8e0479.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl b/test/tint/builtins/gen/textureStore/8f71a1.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/8f71a1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/8f71a1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/8f71a1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/969534.wgsl b/test/tint/builtins/gen/textureStore/969534.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl
rename to test/tint/builtins/gen/textureStore/969534.wgsl
diff --git a/test/builtins/gen/textureStore/969534.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/969534.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/969534.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/969534.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/969534.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/969534.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/969534.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/969534.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/969534.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/969534.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/969534.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/969534.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/969534.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/969534.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/969534.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/969534.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/9a3ecc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9a3ecc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/9a3ecc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/9d9cd5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9d9cd5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/9d9cd5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/9e3ec5.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/9e3ec5.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/9e3ec5.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl b/test/tint/builtins/gen/textureStore/ac67aa.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/ac67aa.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ac67aa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/ac67aa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl b/test/tint/builtins/gen/textureStore/b706b1.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/b706b1.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/b706b1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/b706b1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/bbcb7f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/bbcb7f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/bbcb7f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl b/test/tint/builtins/gen/textureStore/be6e30.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/be6e30.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/be6e30.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/be6e30.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl b/test/tint/builtins/gen/textureStore/bf775c.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/bf775c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/bf775c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/bf775c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl b/test/tint/builtins/gen/textureStore/c5af1e.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/c5af1e.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/c5af1e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/c5af1e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/c863be.wgsl b/test/tint/builtins/gen/textureStore/c863be.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl
rename to test/tint/builtins/gen/textureStore/c863be.wgsl
diff --git a/test/builtins/gen/textureStore/c863be.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/c863be.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/c863be.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/c863be.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/c863be.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/c863be.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/c863be.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/c863be.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/c863be.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/c863be.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/c863be.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/c863be.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/c863be.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/c863be.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/c863be.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/c863be.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl b/test/tint/builtins/gen/textureStore/d73b5c.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/d73b5c.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/d73b5c.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/d73b5c.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl b/test/tint/builtins/gen/textureStore/dd7d81.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/dd7d81.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/dd7d81.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/dd7d81.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/dde364.wgsl b/test/tint/builtins/gen/textureStore/dde364.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl
rename to test/tint/builtins/gen/textureStore/dde364.wgsl
diff --git a/test/builtins/gen/textureStore/dde364.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/dde364.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/dde364.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/dde364.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/dde364.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/dde364.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/dde364.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/dde364.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/dde364.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/dde364.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/dde364.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/dde364.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/dde364.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/dde364.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/dde364.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/dde364.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl b/test/tint/builtins/gen/textureStore/e885e8.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/e885e8.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/e885e8.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/e885e8.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl b/test/tint/builtins/gen/textureStore/eb702f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/eb702f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb702f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/eb702f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl b/test/tint/builtins/gen/textureStore/eb78b9.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/eb78b9.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/eb78b9.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/eb78b9.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl b/test/tint/builtins/gen/textureStore/ee6acc.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/ee6acc.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ee6acc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/ee6acc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/ef9f2f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/ef9f2f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/ef9f2f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl b/test/tint/builtins/gen/textureStore/f8dead.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/f8dead.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/f8dead.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/f8dead.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl b/test/tint/builtins/gen/textureStore/f9be83.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/f9be83.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/f9be83.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/f9be83.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/fb9a8f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/fb9a8f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/fb9a8f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl b/test/tint/builtins/gen/textureStore/fbf53f.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl.expected.glsl b/test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl.expected.glsl
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.glsl
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl.expected.hlsl b/test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl.expected.msl b/test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl.expected.msl
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.msl
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl.expected.spvasm b/test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/textureStore/fbf53f.wgsl.expected.wgsl b/test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/textureStore/fbf53f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/textureStore/fbf53f.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/2585cd.wgsl b/test/tint/builtins/gen/transpose/2585cd.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl
rename to test/tint/builtins/gen/transpose/2585cd.wgsl
diff --git a/test/builtins/gen/transpose/2585cd.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/2585cd.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/2585cd.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/2585cd.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/2585cd.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/2585cd.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/2585cd.wgsl.expected.msl b/test/tint/builtins/gen/transpose/2585cd.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/2585cd.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/2585cd.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/2585cd.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/2585cd.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/2585cd.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/2585cd.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/2585cd.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/2585cd.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/31d679.wgsl b/test/tint/builtins/gen/transpose/31d679.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl
rename to test/tint/builtins/gen/transpose/31d679.wgsl
diff --git a/test/builtins/gen/transpose/31d679.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/31d679.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/31d679.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/31d679.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/31d679.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/31d679.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/31d679.wgsl.expected.msl b/test/tint/builtins/gen/transpose/31d679.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/31d679.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/31d679.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/31d679.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/31d679.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/31d679.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/31d679.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/31d679.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/31d679.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/31e37e.wgsl b/test/tint/builtins/gen/transpose/31e37e.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl
rename to test/tint/builtins/gen/transpose/31e37e.wgsl
diff --git a/test/builtins/gen/transpose/31e37e.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/31e37e.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/31e37e.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/31e37e.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/31e37e.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/31e37e.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/31e37e.wgsl.expected.msl b/test/tint/builtins/gen/transpose/31e37e.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/31e37e.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/31e37e.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/31e37e.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/31e37e.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/31e37e.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/31e37e.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/31e37e.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/31e37e.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/4ce359.wgsl b/test/tint/builtins/gen/transpose/4ce359.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl
rename to test/tint/builtins/gen/transpose/4ce359.wgsl
diff --git a/test/builtins/gen/transpose/4ce359.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/4ce359.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/4ce359.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/4ce359.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/4ce359.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/4ce359.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/4ce359.wgsl.expected.msl b/test/tint/builtins/gen/transpose/4ce359.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/4ce359.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/4ce359.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/4ce359.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/4ce359.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/4ce359.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/4ce359.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/4ce359.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/4ce359.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl b/test/tint/builtins/gen/transpose/4dc9a1.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl.expected.msl b/test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/4dc9a1.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/4dc9a1.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/4dc9a1.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/854336.wgsl b/test/tint/builtins/gen/transpose/854336.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl
rename to test/tint/builtins/gen/transpose/854336.wgsl
diff --git a/test/builtins/gen/transpose/854336.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/854336.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/854336.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/854336.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/854336.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/854336.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/854336.wgsl.expected.msl b/test/tint/builtins/gen/transpose/854336.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/854336.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/854336.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/854336.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/854336.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/854336.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/854336.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/854336.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/854336.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/c1b600.wgsl b/test/tint/builtins/gen/transpose/c1b600.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl
rename to test/tint/builtins/gen/transpose/c1b600.wgsl
diff --git a/test/builtins/gen/transpose/c1b600.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/c1b600.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/c1b600.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/c1b600.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/c1b600.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/c1b600.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/c1b600.wgsl.expected.msl b/test/tint/builtins/gen/transpose/c1b600.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/c1b600.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/c1b600.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/c1b600.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/c1b600.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/c1b600.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/c1b600.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/c1b600.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/c1b600.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl b/test/tint/builtins/gen/transpose/d8f8ba.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl.expected.msl b/test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/d8f8ba.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/d8f8ba.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/d8f8ba.wgsl.expected.wgsl
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl b/test/tint/builtins/gen/transpose/ed4bdc.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl.expected.glsl b/test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl.expected.glsl
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.glsl
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl.expected.hlsl b/test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl.expected.hlsl
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.hlsl
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl.expected.msl b/test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl.expected.msl
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.msl
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl.expected.spvasm b/test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl.expected.spvasm
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.spvasm
diff --git a/test/builtins/gen/transpose/ed4bdc.wgsl.expected.wgsl b/test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/transpose/ed4bdc.wgsl.expected.wgsl
rename to test/tint/builtins/gen/transpose/ed4bdc.wgsl.expected.wgsl
diff --git a/test/builtins/gen/trunc/562d05.wgsl b/test/tint/builtins/gen/trunc/562d05.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl
rename to test/tint/builtins/gen/trunc/562d05.wgsl
diff --git a/test/builtins/gen/trunc/562d05.wgsl.expected.glsl b/test/tint/builtins/gen/trunc/562d05.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl.expected.glsl
rename to test/tint/builtins/gen/trunc/562d05.wgsl.expected.glsl
diff --git a/test/builtins/gen/trunc/562d05.wgsl.expected.hlsl b/test/tint/builtins/gen/trunc/562d05.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl.expected.hlsl
rename to test/tint/builtins/gen/trunc/562d05.wgsl.expected.hlsl
diff --git a/test/builtins/gen/trunc/562d05.wgsl.expected.msl b/test/tint/builtins/gen/trunc/562d05.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl.expected.msl
rename to test/tint/builtins/gen/trunc/562d05.wgsl.expected.msl
diff --git a/test/builtins/gen/trunc/562d05.wgsl.expected.spvasm b/test/tint/builtins/gen/trunc/562d05.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl.expected.spvasm
rename to test/tint/builtins/gen/trunc/562d05.wgsl.expected.spvasm
diff --git a/test/builtins/gen/trunc/562d05.wgsl.expected.wgsl b/test/tint/builtins/gen/trunc/562d05.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/562d05.wgsl.expected.wgsl
rename to test/tint/builtins/gen/trunc/562d05.wgsl.expected.wgsl
diff --git a/test/builtins/gen/trunc/e183aa.wgsl b/test/tint/builtins/gen/trunc/e183aa.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl
rename to test/tint/builtins/gen/trunc/e183aa.wgsl
diff --git a/test/builtins/gen/trunc/e183aa.wgsl.expected.glsl b/test/tint/builtins/gen/trunc/e183aa.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl.expected.glsl
rename to test/tint/builtins/gen/trunc/e183aa.wgsl.expected.glsl
diff --git a/test/builtins/gen/trunc/e183aa.wgsl.expected.hlsl b/test/tint/builtins/gen/trunc/e183aa.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl.expected.hlsl
rename to test/tint/builtins/gen/trunc/e183aa.wgsl.expected.hlsl
diff --git a/test/builtins/gen/trunc/e183aa.wgsl.expected.msl b/test/tint/builtins/gen/trunc/e183aa.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl.expected.msl
rename to test/tint/builtins/gen/trunc/e183aa.wgsl.expected.msl
diff --git a/test/builtins/gen/trunc/e183aa.wgsl.expected.spvasm b/test/tint/builtins/gen/trunc/e183aa.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl.expected.spvasm
rename to test/tint/builtins/gen/trunc/e183aa.wgsl.expected.spvasm
diff --git a/test/builtins/gen/trunc/e183aa.wgsl.expected.wgsl b/test/tint/builtins/gen/trunc/e183aa.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/e183aa.wgsl.expected.wgsl
rename to test/tint/builtins/gen/trunc/e183aa.wgsl.expected.wgsl
diff --git a/test/builtins/gen/trunc/eb83df.wgsl b/test/tint/builtins/gen/trunc/eb83df.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl
rename to test/tint/builtins/gen/trunc/eb83df.wgsl
diff --git a/test/builtins/gen/trunc/eb83df.wgsl.expected.glsl b/test/tint/builtins/gen/trunc/eb83df.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl.expected.glsl
rename to test/tint/builtins/gen/trunc/eb83df.wgsl.expected.glsl
diff --git a/test/builtins/gen/trunc/eb83df.wgsl.expected.hlsl b/test/tint/builtins/gen/trunc/eb83df.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl.expected.hlsl
rename to test/tint/builtins/gen/trunc/eb83df.wgsl.expected.hlsl
diff --git a/test/builtins/gen/trunc/eb83df.wgsl.expected.msl b/test/tint/builtins/gen/trunc/eb83df.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl.expected.msl
rename to test/tint/builtins/gen/trunc/eb83df.wgsl.expected.msl
diff --git a/test/builtins/gen/trunc/eb83df.wgsl.expected.spvasm b/test/tint/builtins/gen/trunc/eb83df.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl.expected.spvasm
rename to test/tint/builtins/gen/trunc/eb83df.wgsl.expected.spvasm
diff --git a/test/builtins/gen/trunc/eb83df.wgsl.expected.wgsl b/test/tint/builtins/gen/trunc/eb83df.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/eb83df.wgsl.expected.wgsl
rename to test/tint/builtins/gen/trunc/eb83df.wgsl.expected.wgsl
diff --git a/test/builtins/gen/trunc/f370d3.wgsl b/test/tint/builtins/gen/trunc/f370d3.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl
rename to test/tint/builtins/gen/trunc/f370d3.wgsl
diff --git a/test/builtins/gen/trunc/f370d3.wgsl.expected.glsl b/test/tint/builtins/gen/trunc/f370d3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl.expected.glsl
rename to test/tint/builtins/gen/trunc/f370d3.wgsl.expected.glsl
diff --git a/test/builtins/gen/trunc/f370d3.wgsl.expected.hlsl b/test/tint/builtins/gen/trunc/f370d3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/trunc/f370d3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/trunc/f370d3.wgsl.expected.msl b/test/tint/builtins/gen/trunc/f370d3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl.expected.msl
rename to test/tint/builtins/gen/trunc/f370d3.wgsl.expected.msl
diff --git a/test/builtins/gen/trunc/f370d3.wgsl.expected.spvasm b/test/tint/builtins/gen/trunc/f370d3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/trunc/f370d3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/trunc/f370d3.wgsl.expected.wgsl b/test/tint/builtins/gen/trunc/f370d3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/trunc/f370d3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/trunc/f370d3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.glsl b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.glsl
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.glsl
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.msl b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.msl
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.msl
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.spvasm b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.spvasm
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.spvasm
diff --git a/test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.wgsl b/test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.wgsl
rename to test/tint/builtins/gen/unpack2x16float/32a5cf.wgsl.expected.wgsl
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.glsl b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.glsl
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.glsl
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.msl b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.msl
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.msl
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.spvasm b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.spvasm
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.spvasm
diff --git a/test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.wgsl b/test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.wgsl
rename to test/tint/builtins/gen/unpack2x16snorm/b4aea6.wgsl.expected.wgsl
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.glsl b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.glsl
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.glsl
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.msl b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.msl
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.msl
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.spvasm b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.spvasm
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.spvasm
diff --git a/test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.wgsl b/test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.wgsl
rename to test/tint/builtins/gen/unpack2x16unorm/7699c0.wgsl.expected.wgsl
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.glsl b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.glsl
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.glsl
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.msl b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.msl
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.msl
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.spvasm b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.spvasm
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.spvasm
diff --git a/test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.wgsl b/test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.wgsl
rename to test/tint/builtins/gen/unpack4x8snorm/523fb3.wgsl.expected.wgsl
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.glsl b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.glsl
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.glsl
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.msl b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.msl
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.msl
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.spvasm b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.spvasm
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.spvasm
diff --git a/test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.wgsl b/test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.wgsl
rename to test/tint/builtins/gen/unpack4x8unorm/750c74.wgsl.expected.wgsl
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.glsl b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.glsl
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.glsl
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.hlsl b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.hlsl
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.hlsl
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.msl b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.msl
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.msl
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.msl
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.spvasm b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.spvasm
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.spvasm
diff --git a/test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.wgsl b/test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.wgsl
rename to test/tint/builtins/gen/workgroupBarrier/a17f7f.wgsl.expected.wgsl
diff --git a/test/builtins/modf.wgsl b/test/tint/builtins/modf.wgsl
similarity index 100%
rename from test/builtins/modf.wgsl
rename to test/tint/builtins/modf.wgsl
diff --git a/test/builtins/modf.wgsl.expected.glsl b/test/tint/builtins/modf.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/modf.wgsl.expected.glsl
rename to test/tint/builtins/modf.wgsl.expected.glsl
diff --git a/test/builtins/modf.wgsl.expected.hlsl b/test/tint/builtins/modf.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/modf.wgsl.expected.hlsl
rename to test/tint/builtins/modf.wgsl.expected.hlsl
diff --git a/test/builtins/modf.wgsl.expected.msl b/test/tint/builtins/modf.wgsl.expected.msl
similarity index 100%
rename from test/builtins/modf.wgsl.expected.msl
rename to test/tint/builtins/modf.wgsl.expected.msl
diff --git a/test/builtins/modf.wgsl.expected.spvasm b/test/tint/builtins/modf.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/modf.wgsl.expected.spvasm
rename to test/tint/builtins/modf.wgsl.expected.spvasm
diff --git a/test/builtins/modf.wgsl.expected.wgsl b/test/tint/builtins/modf.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/modf.wgsl.expected.wgsl
rename to test/tint/builtins/modf.wgsl.expected.wgsl
diff --git a/test/builtins/radians.spvasm b/test/tint/builtins/radians.spvasm
similarity index 100%
rename from test/builtins/radians.spvasm
rename to test/tint/builtins/radians.spvasm
diff --git a/test/builtins/radians.spvasm.expected.glsl b/test/tint/builtins/radians.spvasm.expected.glsl
similarity index 100%
rename from test/builtins/radians.spvasm.expected.glsl
rename to test/tint/builtins/radians.spvasm.expected.glsl
diff --git a/test/builtins/radians.spvasm.expected.hlsl b/test/tint/builtins/radians.spvasm.expected.hlsl
similarity index 100%
rename from test/builtins/radians.spvasm.expected.hlsl
rename to test/tint/builtins/radians.spvasm.expected.hlsl
diff --git a/test/builtins/radians.spvasm.expected.msl b/test/tint/builtins/radians.spvasm.expected.msl
similarity index 100%
rename from test/builtins/radians.spvasm.expected.msl
rename to test/tint/builtins/radians.spvasm.expected.msl
diff --git a/test/builtins/radians.spvasm.expected.spvasm b/test/tint/builtins/radians.spvasm.expected.spvasm
similarity index 100%
rename from test/builtins/radians.spvasm.expected.spvasm
rename to test/tint/builtins/radians.spvasm.expected.spvasm
diff --git a/test/builtins/radians.spvasm.expected.wgsl b/test/tint/builtins/radians.spvasm.expected.wgsl
similarity index 100%
rename from test/builtins/radians.spvasm.expected.wgsl
rename to test/tint/builtins/radians.spvasm.expected.wgsl
diff --git a/test/builtins/repeated_use.wgsl b/test/tint/builtins/repeated_use.wgsl
similarity index 100%
rename from test/builtins/repeated_use.wgsl
rename to test/tint/builtins/repeated_use.wgsl
diff --git a/test/builtins/repeated_use.wgsl.expected.glsl b/test/tint/builtins/repeated_use.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/repeated_use.wgsl.expected.glsl
rename to test/tint/builtins/repeated_use.wgsl.expected.glsl
diff --git a/test/builtins/repeated_use.wgsl.expected.hlsl b/test/tint/builtins/repeated_use.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/repeated_use.wgsl.expected.hlsl
rename to test/tint/builtins/repeated_use.wgsl.expected.hlsl
diff --git a/test/builtins/repeated_use.wgsl.expected.msl b/test/tint/builtins/repeated_use.wgsl.expected.msl
similarity index 100%
rename from test/builtins/repeated_use.wgsl.expected.msl
rename to test/tint/builtins/repeated_use.wgsl.expected.msl
diff --git a/test/builtins/repeated_use.wgsl.expected.spvasm b/test/tint/builtins/repeated_use.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/repeated_use.wgsl.expected.spvasm
rename to test/tint/builtins/repeated_use.wgsl.expected.spvasm
diff --git a/test/builtins/repeated_use.wgsl.expected.wgsl b/test/tint/builtins/repeated_use.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/repeated_use.wgsl.expected.wgsl
rename to test/tint/builtins/repeated_use.wgsl.expected.wgsl
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm b/test/tint/builtins/textureDimensions/depth_ms.spvasm
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm.expected.glsl b/test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.glsl
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm.expected.glsl
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.glsl
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm.expected.hlsl b/test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.hlsl
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm.expected.hlsl
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.hlsl
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm.expected.msl b/test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.msl
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm.expected.msl
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.msl
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm.expected.spvasm b/test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.spvasm
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm.expected.spvasm
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.spvasm
diff --git a/test/builtins/textureDimensions/depth_ms.spvasm.expected.wgsl b/test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.wgsl
similarity index 100%
rename from test/builtins/textureDimensions/depth_ms.spvasm.expected.wgsl
rename to test/tint/builtins/textureDimensions/depth_ms.spvasm.expected.wgsl
diff --git a/test/builtins/textureGather/f32/alpha.wgsl b/test/tint/builtins/textureGather/f32/alpha.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl
rename to test/tint/builtins/textureGather/f32/alpha.wgsl
diff --git a/test/builtins/textureGather/f32/alpha.wgsl.expected.glsl b/test/tint/builtins/textureGather/f32/alpha.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/f32/alpha.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/f32/alpha.wgsl.expected.hlsl b/test/tint/builtins/textureGather/f32/alpha.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/f32/alpha.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/f32/alpha.wgsl.expected.msl b/test/tint/builtins/textureGather/f32/alpha.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl.expected.msl
rename to test/tint/builtins/textureGather/f32/alpha.wgsl.expected.msl
diff --git a/test/builtins/textureGather/f32/alpha.wgsl.expected.spvasm b/test/tint/builtins/textureGather/f32/alpha.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/f32/alpha.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/f32/alpha.wgsl.expected.wgsl b/test/tint/builtins/textureGather/f32/alpha.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/alpha.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/f32/alpha.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/f32/blue.wgsl b/test/tint/builtins/textureGather/f32/blue.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl
rename to test/tint/builtins/textureGather/f32/blue.wgsl
diff --git a/test/builtins/textureGather/f32/blue.wgsl.expected.glsl b/test/tint/builtins/textureGather/f32/blue.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/f32/blue.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/f32/blue.wgsl.expected.hlsl b/test/tint/builtins/textureGather/f32/blue.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/f32/blue.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/f32/blue.wgsl.expected.msl b/test/tint/builtins/textureGather/f32/blue.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl.expected.msl
rename to test/tint/builtins/textureGather/f32/blue.wgsl.expected.msl
diff --git a/test/builtins/textureGather/f32/blue.wgsl.expected.spvasm b/test/tint/builtins/textureGather/f32/blue.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/f32/blue.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/f32/blue.wgsl.expected.wgsl b/test/tint/builtins/textureGather/f32/blue.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/blue.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/f32/blue.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/f32/green.wgsl b/test/tint/builtins/textureGather/f32/green.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl
rename to test/tint/builtins/textureGather/f32/green.wgsl
diff --git a/test/builtins/textureGather/f32/green.wgsl.expected.glsl b/test/tint/builtins/textureGather/f32/green.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/f32/green.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/f32/green.wgsl.expected.hlsl b/test/tint/builtins/textureGather/f32/green.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/f32/green.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/f32/green.wgsl.expected.msl b/test/tint/builtins/textureGather/f32/green.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl.expected.msl
rename to test/tint/builtins/textureGather/f32/green.wgsl.expected.msl
diff --git a/test/builtins/textureGather/f32/green.wgsl.expected.spvasm b/test/tint/builtins/textureGather/f32/green.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/f32/green.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/f32/green.wgsl.expected.wgsl b/test/tint/builtins/textureGather/f32/green.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/green.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/f32/green.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/f32/red.wgsl b/test/tint/builtins/textureGather/f32/red.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl
rename to test/tint/builtins/textureGather/f32/red.wgsl
diff --git a/test/builtins/textureGather/f32/red.wgsl.expected.glsl b/test/tint/builtins/textureGather/f32/red.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/f32/red.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/f32/red.wgsl.expected.hlsl b/test/tint/builtins/textureGather/f32/red.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/f32/red.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/f32/red.wgsl.expected.msl b/test/tint/builtins/textureGather/f32/red.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl.expected.msl
rename to test/tint/builtins/textureGather/f32/red.wgsl.expected.msl
diff --git a/test/builtins/textureGather/f32/red.wgsl.expected.spvasm b/test/tint/builtins/textureGather/f32/red.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/f32/red.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/f32/red.wgsl.expected.wgsl b/test/tint/builtins/textureGather/f32/red.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/f32/red.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/f32/red.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/i32/alpha.wgsl b/test/tint/builtins/textureGather/i32/alpha.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl
rename to test/tint/builtins/textureGather/i32/alpha.wgsl
diff --git a/test/builtins/textureGather/i32/alpha.wgsl.expected.glsl b/test/tint/builtins/textureGather/i32/alpha.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/i32/alpha.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/i32/alpha.wgsl.expected.hlsl b/test/tint/builtins/textureGather/i32/alpha.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/i32/alpha.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/i32/alpha.wgsl.expected.msl b/test/tint/builtins/textureGather/i32/alpha.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl.expected.msl
rename to test/tint/builtins/textureGather/i32/alpha.wgsl.expected.msl
diff --git a/test/builtins/textureGather/i32/alpha.wgsl.expected.spvasm b/test/tint/builtins/textureGather/i32/alpha.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/i32/alpha.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/i32/alpha.wgsl.expected.wgsl b/test/tint/builtins/textureGather/i32/alpha.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/alpha.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/i32/alpha.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/i32/blue.wgsl b/test/tint/builtins/textureGather/i32/blue.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl
rename to test/tint/builtins/textureGather/i32/blue.wgsl
diff --git a/test/builtins/textureGather/i32/blue.wgsl.expected.glsl b/test/tint/builtins/textureGather/i32/blue.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/i32/blue.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/i32/blue.wgsl.expected.hlsl b/test/tint/builtins/textureGather/i32/blue.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/i32/blue.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/i32/blue.wgsl.expected.msl b/test/tint/builtins/textureGather/i32/blue.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl.expected.msl
rename to test/tint/builtins/textureGather/i32/blue.wgsl.expected.msl
diff --git a/test/builtins/textureGather/i32/blue.wgsl.expected.spvasm b/test/tint/builtins/textureGather/i32/blue.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/i32/blue.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/i32/blue.wgsl.expected.wgsl b/test/tint/builtins/textureGather/i32/blue.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/blue.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/i32/blue.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/i32/green.wgsl b/test/tint/builtins/textureGather/i32/green.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl
rename to test/tint/builtins/textureGather/i32/green.wgsl
diff --git a/test/builtins/textureGather/i32/green.wgsl.expected.glsl b/test/tint/builtins/textureGather/i32/green.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/i32/green.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/i32/green.wgsl.expected.hlsl b/test/tint/builtins/textureGather/i32/green.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/i32/green.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/i32/green.wgsl.expected.msl b/test/tint/builtins/textureGather/i32/green.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl.expected.msl
rename to test/tint/builtins/textureGather/i32/green.wgsl.expected.msl
diff --git a/test/builtins/textureGather/i32/green.wgsl.expected.spvasm b/test/tint/builtins/textureGather/i32/green.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/i32/green.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/i32/green.wgsl.expected.wgsl b/test/tint/builtins/textureGather/i32/green.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/green.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/i32/green.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/i32/red.wgsl b/test/tint/builtins/textureGather/i32/red.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl
rename to test/tint/builtins/textureGather/i32/red.wgsl
diff --git a/test/builtins/textureGather/i32/red.wgsl.expected.glsl b/test/tint/builtins/textureGather/i32/red.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/i32/red.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/i32/red.wgsl.expected.hlsl b/test/tint/builtins/textureGather/i32/red.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/i32/red.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/i32/red.wgsl.expected.msl b/test/tint/builtins/textureGather/i32/red.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl.expected.msl
rename to test/tint/builtins/textureGather/i32/red.wgsl.expected.msl
diff --git a/test/builtins/textureGather/i32/red.wgsl.expected.spvasm b/test/tint/builtins/textureGather/i32/red.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/i32/red.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/i32/red.wgsl.expected.wgsl b/test/tint/builtins/textureGather/i32/red.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/i32/red.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/i32/red.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/u32/alpha.wgsl b/test/tint/builtins/textureGather/u32/alpha.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl
rename to test/tint/builtins/textureGather/u32/alpha.wgsl
diff --git a/test/builtins/textureGather/u32/alpha.wgsl.expected.glsl b/test/tint/builtins/textureGather/u32/alpha.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/u32/alpha.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/u32/alpha.wgsl.expected.hlsl b/test/tint/builtins/textureGather/u32/alpha.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/u32/alpha.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/u32/alpha.wgsl.expected.msl b/test/tint/builtins/textureGather/u32/alpha.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl.expected.msl
rename to test/tint/builtins/textureGather/u32/alpha.wgsl.expected.msl
diff --git a/test/builtins/textureGather/u32/alpha.wgsl.expected.spvasm b/test/tint/builtins/textureGather/u32/alpha.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/u32/alpha.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/u32/alpha.wgsl.expected.wgsl b/test/tint/builtins/textureGather/u32/alpha.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/alpha.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/u32/alpha.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/u32/blue.wgsl b/test/tint/builtins/textureGather/u32/blue.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl
rename to test/tint/builtins/textureGather/u32/blue.wgsl
diff --git a/test/builtins/textureGather/u32/blue.wgsl.expected.glsl b/test/tint/builtins/textureGather/u32/blue.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/u32/blue.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/u32/blue.wgsl.expected.hlsl b/test/tint/builtins/textureGather/u32/blue.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/u32/blue.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/u32/blue.wgsl.expected.msl b/test/tint/builtins/textureGather/u32/blue.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl.expected.msl
rename to test/tint/builtins/textureGather/u32/blue.wgsl.expected.msl
diff --git a/test/builtins/textureGather/u32/blue.wgsl.expected.spvasm b/test/tint/builtins/textureGather/u32/blue.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/u32/blue.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/u32/blue.wgsl.expected.wgsl b/test/tint/builtins/textureGather/u32/blue.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/blue.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/u32/blue.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/u32/green.wgsl b/test/tint/builtins/textureGather/u32/green.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl
rename to test/tint/builtins/textureGather/u32/green.wgsl
diff --git a/test/builtins/textureGather/u32/green.wgsl.expected.glsl b/test/tint/builtins/textureGather/u32/green.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/u32/green.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/u32/green.wgsl.expected.hlsl b/test/tint/builtins/textureGather/u32/green.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/u32/green.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/u32/green.wgsl.expected.msl b/test/tint/builtins/textureGather/u32/green.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl.expected.msl
rename to test/tint/builtins/textureGather/u32/green.wgsl.expected.msl
diff --git a/test/builtins/textureGather/u32/green.wgsl.expected.spvasm b/test/tint/builtins/textureGather/u32/green.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/u32/green.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/u32/green.wgsl.expected.wgsl b/test/tint/builtins/textureGather/u32/green.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/green.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/u32/green.wgsl.expected.wgsl
diff --git a/test/builtins/textureGather/u32/red.wgsl b/test/tint/builtins/textureGather/u32/red.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl
rename to test/tint/builtins/textureGather/u32/red.wgsl
diff --git a/test/builtins/textureGather/u32/red.wgsl.expected.glsl b/test/tint/builtins/textureGather/u32/red.wgsl.expected.glsl
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl.expected.glsl
rename to test/tint/builtins/textureGather/u32/red.wgsl.expected.glsl
diff --git a/test/builtins/textureGather/u32/red.wgsl.expected.hlsl b/test/tint/builtins/textureGather/u32/red.wgsl.expected.hlsl
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl.expected.hlsl
rename to test/tint/builtins/textureGather/u32/red.wgsl.expected.hlsl
diff --git a/test/builtins/textureGather/u32/red.wgsl.expected.msl b/test/tint/builtins/textureGather/u32/red.wgsl.expected.msl
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl.expected.msl
rename to test/tint/builtins/textureGather/u32/red.wgsl.expected.msl
diff --git a/test/builtins/textureGather/u32/red.wgsl.expected.spvasm b/test/tint/builtins/textureGather/u32/red.wgsl.expected.spvasm
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl.expected.spvasm
rename to test/tint/builtins/textureGather/u32/red.wgsl.expected.spvasm
diff --git a/test/builtins/textureGather/u32/red.wgsl.expected.wgsl b/test/tint/builtins/textureGather/u32/red.wgsl.expected.wgsl
similarity index 100%
rename from test/builtins/textureGather/u32/red.wgsl.expected.wgsl
rename to test/tint/builtins/textureGather/u32/red.wgsl.expected.wgsl
diff --git a/test/builtins/textureLoad/depth_ms.spvasm b/test/tint/builtins/textureLoad/depth_ms.spvasm
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm
rename to test/tint/builtins/textureLoad/depth_ms.spvasm
diff --git a/test/builtins/textureLoad/depth_ms.spvasm.expected.glsl b/test/tint/builtins/textureLoad/depth_ms.spvasm.expected.glsl
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm.expected.glsl
rename to test/tint/builtins/textureLoad/depth_ms.spvasm.expected.glsl
diff --git a/test/builtins/textureLoad/depth_ms.spvasm.expected.hlsl b/test/tint/builtins/textureLoad/depth_ms.spvasm.expected.hlsl
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm.expected.hlsl
rename to test/tint/builtins/textureLoad/depth_ms.spvasm.expected.hlsl
diff --git a/test/builtins/textureLoad/depth_ms.spvasm.expected.msl b/test/tint/builtins/textureLoad/depth_ms.spvasm.expected.msl
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm.expected.msl
rename to test/tint/builtins/textureLoad/depth_ms.spvasm.expected.msl
diff --git a/test/builtins/textureLoad/depth_ms.spvasm.expected.spvasm b/test/tint/builtins/textureLoad/depth_ms.spvasm.expected.spvasm
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm.expected.spvasm
rename to test/tint/builtins/textureLoad/depth_ms.spvasm.expected.spvasm
diff --git a/test/builtins/textureLoad/depth_ms.spvasm.expected.wgsl b/test/tint/builtins/textureLoad/depth_ms.spvasm.expected.wgsl
similarity index 100%
rename from test/builtins/textureLoad/depth_ms.spvasm.expected.wgsl
rename to test/tint/builtins/textureLoad/depth_ms.spvasm.expected.wgsl
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm b/test/tint/builtins/textureNumSamples/depth_ms.spvasm
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm.expected.glsl b/test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.glsl
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm.expected.glsl
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.glsl
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm.expected.hlsl b/test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.hlsl
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm.expected.hlsl
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.hlsl
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm.expected.msl b/test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.msl
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm.expected.msl
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.msl
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm.expected.spvasm b/test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.spvasm
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm.expected.spvasm
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.spvasm
diff --git a/test/builtins/textureNumSamples/depth_ms.spvasm.expected.wgsl b/test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.wgsl
similarity index 100%
rename from test/builtins/textureNumSamples/depth_ms.spvasm.expected.wgsl
rename to test/tint/builtins/textureNumSamples/depth_ms.spvasm.expected.wgsl
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.msl b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/mat3x3-mat3x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/add/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/add/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-and/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-and/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-or/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-or/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-xor/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/bit-xor/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/div_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/left-shift/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.glsl b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.msl b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/left-shift/vector-vector/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.glsl b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.msl b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/left-shift/vector-vector/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_constant/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_expression/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mod_by_zero/by_identifier/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/mat2x4-mat4x2/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/mat3x2-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/mat3x3-mat3x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/mat3x3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/mat4x2-mat2x4/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-mat3x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-mat4x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/mul/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/right-shift/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.glsl b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.msl b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/right-shift/vector-vector/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.glsl b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.msl b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/right-shift/vector-vector/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.msl b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/mat3x3-mat3x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/scalar-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.glsl b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.msl b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/binary/sub/vec3-vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/f32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/f32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/f32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/f32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/f32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/i32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/i32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/i32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/i32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/i32min-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/i32min-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/u32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/u32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/scalar/u32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/scalar/u32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/scalar/u32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl b/test/tint/expressions/bitcast/vector/f32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/f32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/f32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl b/test/tint/expressions/bitcast/vector/f32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/f32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/f32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl b/test/tint/expressions/bitcast/vector/f32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/f32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/f32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/f32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl b/test/tint/expressions/bitcast/vector/i32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/i32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/i32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl b/test/tint/expressions/bitcast/vector/i32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/i32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/i32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl b/test/tint/expressions/bitcast/vector/i32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/i32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/i32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/i32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl b/test/tint/expressions/bitcast/vector/u32-f32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/u32-f32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-f32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/u32-f32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl b/test/tint/expressions/bitcast/vector/u32-i32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/u32-i32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-i32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/u32-i32.wgsl.expected.wgsl
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl b/test/tint/expressions/bitcast/vector/u32-u32.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl.expected.glsl b/test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl.expected.glsl
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.glsl
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl.expected.hlsl b/test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl.expected.hlsl
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.hlsl
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl.expected.msl b/test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl.expected.msl
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.msl
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl.expected.spvasm b/test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl.expected.spvasm
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.spvasm
diff --git a/test/expressions/bitcast/vector/u32-u32.wgsl.expected.wgsl b/test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/bitcast/vector/u32-u32.wgsl.expected.wgsl
rename to test/tint/expressions/bitcast/vector/u32-u32.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/literal/array.wgsl b/test/tint/expressions/index/let/let/literal/array.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl
rename to test/tint/expressions/index/let/let/literal/array.wgsl
diff --git a/test/expressions/index/let/let/literal/array.wgsl.expected.glsl b/test/tint/expressions/index/let/let/literal/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/literal/array.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/literal/array.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/literal/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/literal/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/literal/array.wgsl.expected.msl b/test/tint/expressions/index/let/let/literal/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/literal/array.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/literal/array.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/literal/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/literal/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/literal/array.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/literal/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/literal/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl b/test/tint/expressions/index/let/let/literal/matrix.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl.expected.glsl b/test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl.expected.msl b/test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/literal/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/literal/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/literal/vector.wgsl b/test/tint/expressions/index/let/let/literal/vector.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl
rename to test/tint/expressions/index/let/let/literal/vector.wgsl
diff --git a/test/expressions/index/let/let/literal/vector.wgsl.expected.glsl b/test/tint/expressions/index/let/let/literal/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/literal/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/literal/vector.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/literal/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/literal/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/literal/vector.wgsl.expected.msl b/test/tint/expressions/index/let/let/literal/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/literal/vector.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/literal/vector.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/literal/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/literal/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/literal/vector.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/literal/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/literal/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/literal/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/param/array.wgsl b/test/tint/expressions/index/let/let/param/array.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl
rename to test/tint/expressions/index/let/let/param/array.wgsl
diff --git a/test/expressions/index/let/let/param/array.wgsl.expected.glsl b/test/tint/expressions/index/let/let/param/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/param/array.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/param/array.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/param/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/param/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/param/array.wgsl.expected.msl b/test/tint/expressions/index/let/let/param/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/param/array.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/param/array.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/param/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/param/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/param/array.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/param/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/param/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/param/matrix.wgsl b/test/tint/expressions/index/let/let/param/matrix.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl
rename to test/tint/expressions/index/let/let/param/matrix.wgsl
diff --git a/test/expressions/index/let/let/param/matrix.wgsl.expected.glsl b/test/tint/expressions/index/let/let/param/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/param/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/param/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/param/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/param/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/param/matrix.wgsl.expected.msl b/test/tint/expressions/index/let/let/param/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/param/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/param/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/param/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/param/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/param/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/param/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/param/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/let/param/vector.wgsl b/test/tint/expressions/index/let/let/param/vector.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl
rename to test/tint/expressions/index/let/let/param/vector.wgsl
diff --git a/test/expressions/index/let/let/param/vector.wgsl.expected.glsl b/test/tint/expressions/index/let/let/param/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/let/let/param/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/let/let/param/vector.wgsl.expected.hlsl b/test/tint/expressions/index/let/let/param/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/let/param/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/let/param/vector.wgsl.expected.msl b/test/tint/expressions/index/let/let/param/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl.expected.msl
rename to test/tint/expressions/index/let/let/param/vector.wgsl.expected.msl
diff --git a/test/expressions/index/let/let/param/vector.wgsl.expected.spvasm b/test/tint/expressions/index/let/let/param/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/let/param/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/let/param/vector.wgsl.expected.wgsl b/test/tint/expressions/index/let/let/param/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/let/param/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/let/param/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/literal/array.wgsl b/test/tint/expressions/index/let/literal/array.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl
rename to test/tint/expressions/index/let/literal/array.wgsl
diff --git a/test/expressions/index/let/literal/array.wgsl.expected.glsl b/test/tint/expressions/index/let/literal/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl.expected.glsl
rename to test/tint/expressions/index/let/literal/array.wgsl.expected.glsl
diff --git a/test/expressions/index/let/literal/array.wgsl.expected.hlsl b/test/tint/expressions/index/let/literal/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/literal/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/literal/array.wgsl.expected.msl b/test/tint/expressions/index/let/literal/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl.expected.msl
rename to test/tint/expressions/index/let/literal/array.wgsl.expected.msl
diff --git a/test/expressions/index/let/literal/array.wgsl.expected.spvasm b/test/tint/expressions/index/let/literal/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/literal/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/literal/array.wgsl.expected.wgsl b/test/tint/expressions/index/let/literal/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/literal/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/literal/matrix.wgsl b/test/tint/expressions/index/let/literal/matrix.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl
rename to test/tint/expressions/index/let/literal/matrix.wgsl
diff --git a/test/expressions/index/let/literal/matrix.wgsl.expected.glsl b/test/tint/expressions/index/let/literal/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/let/literal/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/let/literal/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/let/literal/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/literal/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/literal/matrix.wgsl.expected.msl b/test/tint/expressions/index/let/literal/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/let/literal/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/let/literal/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/let/literal/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/literal/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/literal/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/let/literal/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/literal/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/literal/vector.wgsl b/test/tint/expressions/index/let/literal/vector.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl
rename to test/tint/expressions/index/let/literal/vector.wgsl
diff --git a/test/expressions/index/let/literal/vector.wgsl.expected.glsl b/test/tint/expressions/index/let/literal/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/let/literal/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/let/literal/vector.wgsl.expected.hlsl b/test/tint/expressions/index/let/literal/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/literal/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/literal/vector.wgsl.expected.msl b/test/tint/expressions/index/let/literal/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl.expected.msl
rename to test/tint/expressions/index/let/literal/vector.wgsl.expected.msl
diff --git a/test/expressions/index/let/literal/vector.wgsl.expected.spvasm b/test/tint/expressions/index/let/literal/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/literal/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/literal/vector.wgsl.expected.wgsl b/test/tint/expressions/index/let/literal/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/literal/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/literal/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/param/array.wgsl b/test/tint/expressions/index/let/param/array.wgsl
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl
rename to test/tint/expressions/index/let/param/array.wgsl
diff --git a/test/expressions/index/let/param/array.wgsl.expected.glsl b/test/tint/expressions/index/let/param/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl.expected.glsl
rename to test/tint/expressions/index/let/param/array.wgsl.expected.glsl
diff --git a/test/expressions/index/let/param/array.wgsl.expected.hlsl b/test/tint/expressions/index/let/param/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/param/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/param/array.wgsl.expected.msl b/test/tint/expressions/index/let/param/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl.expected.msl
rename to test/tint/expressions/index/let/param/array.wgsl.expected.msl
diff --git a/test/expressions/index/let/param/array.wgsl.expected.spvasm b/test/tint/expressions/index/let/param/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/param/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/param/array.wgsl.expected.wgsl b/test/tint/expressions/index/let/param/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/param/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/param/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/param/matrix.wgsl b/test/tint/expressions/index/let/param/matrix.wgsl
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl
rename to test/tint/expressions/index/let/param/matrix.wgsl
diff --git a/test/expressions/index/let/param/matrix.wgsl.expected.glsl b/test/tint/expressions/index/let/param/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/let/param/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/let/param/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/let/param/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/param/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/param/matrix.wgsl.expected.msl b/test/tint/expressions/index/let/param/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/let/param/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/let/param/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/let/param/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/param/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/param/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/let/param/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/param/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/param/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/param/vector.wgsl b/test/tint/expressions/index/let/param/vector.wgsl
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl
rename to test/tint/expressions/index/let/param/vector.wgsl
diff --git a/test/expressions/index/let/param/vector.wgsl.expected.glsl b/test/tint/expressions/index/let/param/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/let/param/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/let/param/vector.wgsl.expected.hlsl b/test/tint/expressions/index/let/param/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/param/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/param/vector.wgsl.expected.msl b/test/tint/expressions/index/let/param/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl.expected.msl
rename to test/tint/expressions/index/let/param/vector.wgsl.expected.msl
diff --git a/test/expressions/index/let/param/vector.wgsl.expected.spvasm b/test/tint/expressions/index/let/param/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/param/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/param/vector.wgsl.expected.wgsl b/test/tint/expressions/index/let/param/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/param/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/param/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/var/literal/array.wgsl b/test/tint/expressions/index/let/var/literal/array.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl
rename to test/tint/expressions/index/let/var/literal/array.wgsl
diff --git a/test/expressions/index/let/var/literal/array.wgsl.expected.glsl b/test/tint/expressions/index/let/var/literal/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl.expected.glsl
rename to test/tint/expressions/index/let/var/literal/array.wgsl.expected.glsl
diff --git a/test/expressions/index/let/var/literal/array.wgsl.expected.hlsl b/test/tint/expressions/index/let/var/literal/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/var/literal/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/var/literal/array.wgsl.expected.msl b/test/tint/expressions/index/let/var/literal/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl.expected.msl
rename to test/tint/expressions/index/let/var/literal/array.wgsl.expected.msl
diff --git a/test/expressions/index/let/var/literal/array.wgsl.expected.spvasm b/test/tint/expressions/index/let/var/literal/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/var/literal/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/var/literal/array.wgsl.expected.wgsl b/test/tint/expressions/index/let/var/literal/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/var/literal/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl b/test/tint/expressions/index/let/var/literal/matrix.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl.expected.glsl b/test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl.expected.msl b/test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/var/literal/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/var/literal/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/let/var/literal/vector.wgsl b/test/tint/expressions/index/let/var/literal/vector.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl
rename to test/tint/expressions/index/let/var/literal/vector.wgsl
diff --git a/test/expressions/index/let/var/literal/vector.wgsl.expected.glsl b/test/tint/expressions/index/let/var/literal/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/let/var/literal/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/let/var/literal/vector.wgsl.expected.hlsl b/test/tint/expressions/index/let/var/literal/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/let/var/literal/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/let/var/literal/vector.wgsl.expected.msl b/test/tint/expressions/index/let/var/literal/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl.expected.msl
rename to test/tint/expressions/index/let/var/literal/vector.wgsl.expected.msl
diff --git a/test/expressions/index/let/var/literal/vector.wgsl.expected.spvasm b/test/tint/expressions/index/let/var/literal/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/let/var/literal/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/let/var/literal/vector.wgsl.expected.wgsl b/test/tint/expressions/index/let/var/literal/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/let/var/literal/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/let/var/literal/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/literal/array.wgsl b/test/tint/expressions/index/var/let/literal/array.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl
rename to test/tint/expressions/index/var/let/literal/array.wgsl
diff --git a/test/expressions/index/var/let/literal/array.wgsl.expected.glsl b/test/tint/expressions/index/var/let/literal/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/literal/array.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/literal/array.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/literal/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/literal/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/literal/array.wgsl.expected.msl b/test/tint/expressions/index/var/let/literal/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/literal/array.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/literal/array.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/literal/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/literal/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/literal/array.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/literal/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/literal/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl b/test/tint/expressions/index/var/let/literal/matrix.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl.expected.glsl b/test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl.expected.msl b/test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/literal/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/literal/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/literal/vector.wgsl b/test/tint/expressions/index/var/let/literal/vector.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl
rename to test/tint/expressions/index/var/let/literal/vector.wgsl
diff --git a/test/expressions/index/var/let/literal/vector.wgsl.expected.glsl b/test/tint/expressions/index/var/let/literal/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/literal/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/literal/vector.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/literal/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/literal/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/literal/vector.wgsl.expected.msl b/test/tint/expressions/index/var/let/literal/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/literal/vector.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/literal/vector.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/literal/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/literal/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/literal/vector.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/literal/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/literal/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/literal/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/param/array.wgsl b/test/tint/expressions/index/var/let/param/array.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl
rename to test/tint/expressions/index/var/let/param/array.wgsl
diff --git a/test/expressions/index/var/let/param/array.wgsl.expected.glsl b/test/tint/expressions/index/var/let/param/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/param/array.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/param/array.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/param/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/param/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/param/array.wgsl.expected.msl b/test/tint/expressions/index/var/let/param/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/param/array.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/param/array.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/param/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/param/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/param/array.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/param/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/param/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/param/matrix.wgsl b/test/tint/expressions/index/var/let/param/matrix.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl
rename to test/tint/expressions/index/var/let/param/matrix.wgsl
diff --git a/test/expressions/index/var/let/param/matrix.wgsl.expected.glsl b/test/tint/expressions/index/var/let/param/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/param/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/param/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/param/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/param/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/param/matrix.wgsl.expected.msl b/test/tint/expressions/index/var/let/param/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/param/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/param/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/param/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/param/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/param/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/param/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/param/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/let/param/vector.wgsl b/test/tint/expressions/index/var/let/param/vector.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl
rename to test/tint/expressions/index/var/let/param/vector.wgsl
diff --git a/test/expressions/index/var/let/param/vector.wgsl.expected.glsl b/test/tint/expressions/index/var/let/param/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/var/let/param/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/var/let/param/vector.wgsl.expected.hlsl b/test/tint/expressions/index/var/let/param/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/let/param/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/let/param/vector.wgsl.expected.msl b/test/tint/expressions/index/var/let/param/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl.expected.msl
rename to test/tint/expressions/index/var/let/param/vector.wgsl.expected.msl
diff --git a/test/expressions/index/var/let/param/vector.wgsl.expected.spvasm b/test/tint/expressions/index/var/let/param/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/let/param/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/let/param/vector.wgsl.expected.wgsl b/test/tint/expressions/index/var/let/param/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/let/param/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/let/param/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/literal/array.wgsl b/test/tint/expressions/index/var/literal/array.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl
rename to test/tint/expressions/index/var/literal/array.wgsl
diff --git a/test/expressions/index/var/literal/array.wgsl.expected.glsl b/test/tint/expressions/index/var/literal/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl.expected.glsl
rename to test/tint/expressions/index/var/literal/array.wgsl.expected.glsl
diff --git a/test/expressions/index/var/literal/array.wgsl.expected.hlsl b/test/tint/expressions/index/var/literal/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/literal/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/literal/array.wgsl.expected.msl b/test/tint/expressions/index/var/literal/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl.expected.msl
rename to test/tint/expressions/index/var/literal/array.wgsl.expected.msl
diff --git a/test/expressions/index/var/literal/array.wgsl.expected.spvasm b/test/tint/expressions/index/var/literal/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/literal/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/literal/array.wgsl.expected.wgsl b/test/tint/expressions/index/var/literal/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/literal/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/literal/matrix.wgsl b/test/tint/expressions/index/var/literal/matrix.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl
rename to test/tint/expressions/index/var/literal/matrix.wgsl
diff --git a/test/expressions/index/var/literal/matrix.wgsl.expected.glsl b/test/tint/expressions/index/var/literal/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/var/literal/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/var/literal/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/var/literal/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/literal/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/literal/matrix.wgsl.expected.msl b/test/tint/expressions/index/var/literal/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/var/literal/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/var/literal/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/var/literal/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/literal/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/literal/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/var/literal/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/literal/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/literal/vector.wgsl b/test/tint/expressions/index/var/literal/vector.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl
rename to test/tint/expressions/index/var/literal/vector.wgsl
diff --git a/test/expressions/index/var/literal/vector.wgsl.expected.glsl b/test/tint/expressions/index/var/literal/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/var/literal/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/var/literal/vector.wgsl.expected.hlsl b/test/tint/expressions/index/var/literal/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/literal/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/literal/vector.wgsl.expected.msl b/test/tint/expressions/index/var/literal/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl.expected.msl
rename to test/tint/expressions/index/var/literal/vector.wgsl.expected.msl
diff --git a/test/expressions/index/var/literal/vector.wgsl.expected.spvasm b/test/tint/expressions/index/var/literal/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/literal/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/literal/vector.wgsl.expected.wgsl b/test/tint/expressions/index/var/literal/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/literal/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/literal/vector.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/param/array.wgsl b/test/tint/expressions/index/var/param/array.wgsl
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl
rename to test/tint/expressions/index/var/param/array.wgsl
diff --git a/test/expressions/index/var/param/array.wgsl.expected.glsl b/test/tint/expressions/index/var/param/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl.expected.glsl
rename to test/tint/expressions/index/var/param/array.wgsl.expected.glsl
diff --git a/test/expressions/index/var/param/array.wgsl.expected.hlsl b/test/tint/expressions/index/var/param/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/param/array.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/param/array.wgsl.expected.msl b/test/tint/expressions/index/var/param/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl.expected.msl
rename to test/tint/expressions/index/var/param/array.wgsl.expected.msl
diff --git a/test/expressions/index/var/param/array.wgsl.expected.spvasm b/test/tint/expressions/index/var/param/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/param/array.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/param/array.wgsl.expected.wgsl b/test/tint/expressions/index/var/param/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/param/array.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/param/array.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/param/matrix.wgsl b/test/tint/expressions/index/var/param/matrix.wgsl
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl
rename to test/tint/expressions/index/var/param/matrix.wgsl
diff --git a/test/expressions/index/var/param/matrix.wgsl.expected.glsl b/test/tint/expressions/index/var/param/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl.expected.glsl
rename to test/tint/expressions/index/var/param/matrix.wgsl.expected.glsl
diff --git a/test/expressions/index/var/param/matrix.wgsl.expected.hlsl b/test/tint/expressions/index/var/param/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/param/matrix.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/param/matrix.wgsl.expected.msl b/test/tint/expressions/index/var/param/matrix.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl.expected.msl
rename to test/tint/expressions/index/var/param/matrix.wgsl.expected.msl
diff --git a/test/expressions/index/var/param/matrix.wgsl.expected.spvasm b/test/tint/expressions/index/var/param/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/param/matrix.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/param/matrix.wgsl.expected.wgsl b/test/tint/expressions/index/var/param/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/param/matrix.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/param/matrix.wgsl.expected.wgsl
diff --git a/test/expressions/index/var/param/vector.wgsl b/test/tint/expressions/index/var/param/vector.wgsl
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl
rename to test/tint/expressions/index/var/param/vector.wgsl
diff --git a/test/expressions/index/var/param/vector.wgsl.expected.glsl b/test/tint/expressions/index/var/param/vector.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl.expected.glsl
rename to test/tint/expressions/index/var/param/vector.wgsl.expected.glsl
diff --git a/test/expressions/index/var/param/vector.wgsl.expected.hlsl b/test/tint/expressions/index/var/param/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl.expected.hlsl
rename to test/tint/expressions/index/var/param/vector.wgsl.expected.hlsl
diff --git a/test/expressions/index/var/param/vector.wgsl.expected.msl b/test/tint/expressions/index/var/param/vector.wgsl.expected.msl
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl.expected.msl
rename to test/tint/expressions/index/var/param/vector.wgsl.expected.msl
diff --git a/test/expressions/index/var/param/vector.wgsl.expected.spvasm b/test/tint/expressions/index/var/param/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl.expected.spvasm
rename to test/tint/expressions/index/var/param/vector.wgsl.expected.spvasm
diff --git a/test/expressions/index/var/param/vector.wgsl.expected.wgsl b/test/tint/expressions/index/var/param/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/index/var/param/vector.wgsl.expected.wgsl
rename to test/tint/expressions/index/var/param/vector.wgsl.expected.wgsl
diff --git a/test/expressions/literals/-inf.spvasm b/test/tint/expressions/literals/-inf.spvasm
similarity index 100%
rename from test/expressions/literals/-inf.spvasm
rename to test/tint/expressions/literals/-inf.spvasm
diff --git a/test/expressions/literals/-inf.spvasm.expected.glsl b/test/tint/expressions/literals/-inf.spvasm.expected.glsl
similarity index 100%
rename from test/expressions/literals/-inf.spvasm.expected.glsl
rename to test/tint/expressions/literals/-inf.spvasm.expected.glsl
diff --git a/test/expressions/literals/-inf.spvasm.expected.hlsl b/test/tint/expressions/literals/-inf.spvasm.expected.hlsl
similarity index 100%
rename from test/expressions/literals/-inf.spvasm.expected.hlsl
rename to test/tint/expressions/literals/-inf.spvasm.expected.hlsl
diff --git a/test/expressions/literals/-inf.spvasm.expected.msl b/test/tint/expressions/literals/-inf.spvasm.expected.msl
similarity index 100%
rename from test/expressions/literals/-inf.spvasm.expected.msl
rename to test/tint/expressions/literals/-inf.spvasm.expected.msl
diff --git a/test/expressions/literals/-inf.spvasm.expected.spvasm b/test/tint/expressions/literals/-inf.spvasm.expected.spvasm
similarity index 100%
rename from test/expressions/literals/-inf.spvasm.expected.spvasm
rename to test/tint/expressions/literals/-inf.spvasm.expected.spvasm
diff --git a/test/expressions/literals/-inf.spvasm.expected.wgsl b/test/tint/expressions/literals/-inf.spvasm.expected.wgsl
similarity index 100%
rename from test/expressions/literals/-inf.spvasm.expected.wgsl
rename to test/tint/expressions/literals/-inf.spvasm.expected.wgsl
diff --git a/test/expressions/literals/inf.spvasm b/test/tint/expressions/literals/inf.spvasm
similarity index 100%
rename from test/expressions/literals/inf.spvasm
rename to test/tint/expressions/literals/inf.spvasm
diff --git a/test/expressions/literals/inf.spvasm.expected.glsl b/test/tint/expressions/literals/inf.spvasm.expected.glsl
similarity index 100%
rename from test/expressions/literals/inf.spvasm.expected.glsl
rename to test/tint/expressions/literals/inf.spvasm.expected.glsl
diff --git a/test/expressions/literals/inf.spvasm.expected.hlsl b/test/tint/expressions/literals/inf.spvasm.expected.hlsl
similarity index 100%
rename from test/expressions/literals/inf.spvasm.expected.hlsl
rename to test/tint/expressions/literals/inf.spvasm.expected.hlsl
diff --git a/test/expressions/literals/inf.spvasm.expected.msl b/test/tint/expressions/literals/inf.spvasm.expected.msl
similarity index 100%
rename from test/expressions/literals/inf.spvasm.expected.msl
rename to test/tint/expressions/literals/inf.spvasm.expected.msl
diff --git a/test/expressions/literals/inf.spvasm.expected.spvasm b/test/tint/expressions/literals/inf.spvasm.expected.spvasm
similarity index 100%
rename from test/expressions/literals/inf.spvasm.expected.spvasm
rename to test/tint/expressions/literals/inf.spvasm.expected.spvasm
diff --git a/test/expressions/literals/inf.spvasm.expected.wgsl b/test/tint/expressions/literals/inf.spvasm.expected.wgsl
similarity index 100%
rename from test/expressions/literals/inf.spvasm.expected.wgsl
rename to test/tint/expressions/literals/inf.spvasm.expected.wgsl
diff --git a/test/expressions/literals/intmin.wgsl b/test/tint/expressions/literals/intmin.wgsl
similarity index 100%
rename from test/expressions/literals/intmin.wgsl
rename to test/tint/expressions/literals/intmin.wgsl
diff --git a/test/expressions/literals/intmin.wgsl.expected.glsl b/test/tint/expressions/literals/intmin.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/literals/intmin.wgsl.expected.glsl
rename to test/tint/expressions/literals/intmin.wgsl.expected.glsl
diff --git a/test/expressions/literals/intmin.wgsl.expected.hlsl b/test/tint/expressions/literals/intmin.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/literals/intmin.wgsl.expected.hlsl
rename to test/tint/expressions/literals/intmin.wgsl.expected.hlsl
diff --git a/test/expressions/literals/intmin.wgsl.expected.msl b/test/tint/expressions/literals/intmin.wgsl.expected.msl
similarity index 100%
rename from test/expressions/literals/intmin.wgsl.expected.msl
rename to test/tint/expressions/literals/intmin.wgsl.expected.msl
diff --git a/test/expressions/literals/intmin.wgsl.expected.spvasm b/test/tint/expressions/literals/intmin.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/literals/intmin.wgsl.expected.spvasm
rename to test/tint/expressions/literals/intmin.wgsl.expected.spvasm
diff --git a/test/expressions/literals/intmin.wgsl.expected.wgsl b/test/tint/expressions/literals/intmin.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/literals/intmin.wgsl.expected.wgsl
rename to test/tint/expressions/literals/intmin.wgsl.expected.wgsl
diff --git a/test/expressions/literals/nan.spvasm b/test/tint/expressions/literals/nan.spvasm
similarity index 100%
rename from test/expressions/literals/nan.spvasm
rename to test/tint/expressions/literals/nan.spvasm
diff --git a/test/expressions/literals/nan.spvasm.expected.glsl b/test/tint/expressions/literals/nan.spvasm.expected.glsl
similarity index 100%
rename from test/expressions/literals/nan.spvasm.expected.glsl
rename to test/tint/expressions/literals/nan.spvasm.expected.glsl
diff --git a/test/expressions/literals/nan.spvasm.expected.hlsl b/test/tint/expressions/literals/nan.spvasm.expected.hlsl
similarity index 100%
rename from test/expressions/literals/nan.spvasm.expected.hlsl
rename to test/tint/expressions/literals/nan.spvasm.expected.hlsl
diff --git a/test/expressions/literals/nan.spvasm.expected.msl b/test/tint/expressions/literals/nan.spvasm.expected.msl
similarity index 100%
rename from test/expressions/literals/nan.spvasm.expected.msl
rename to test/tint/expressions/literals/nan.spvasm.expected.msl
diff --git a/test/expressions/literals/nan.spvasm.expected.spvasm b/test/tint/expressions/literals/nan.spvasm.expected.spvasm
similarity index 100%
rename from test/expressions/literals/nan.spvasm.expected.spvasm
rename to test/tint/expressions/literals/nan.spvasm.expected.spvasm
diff --git a/test/expressions/literals/nan.spvasm.expected.wgsl b/test/tint/expressions/literals/nan.spvasm.expected.wgsl
similarity index 100%
rename from test/expressions/literals/nan.spvasm.expected.wgsl
rename to test/tint/expressions/literals/nan.spvasm.expected.wgsl
diff --git a/test/expressions/splat/call/bool.wgsl b/test/tint/expressions/splat/call/bool.wgsl
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl
rename to test/tint/expressions/splat/call/bool.wgsl
diff --git a/test/expressions/splat/call/bool.wgsl.expected.glsl b/test/tint/expressions/splat/call/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl.expected.glsl
rename to test/tint/expressions/splat/call/bool.wgsl.expected.glsl
diff --git a/test/expressions/splat/call/bool.wgsl.expected.hlsl b/test/tint/expressions/splat/call/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl.expected.hlsl
rename to test/tint/expressions/splat/call/bool.wgsl.expected.hlsl
diff --git a/test/expressions/splat/call/bool.wgsl.expected.msl b/test/tint/expressions/splat/call/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl.expected.msl
rename to test/tint/expressions/splat/call/bool.wgsl.expected.msl
diff --git a/test/expressions/splat/call/bool.wgsl.expected.spvasm b/test/tint/expressions/splat/call/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl.expected.spvasm
rename to test/tint/expressions/splat/call/bool.wgsl.expected.spvasm
diff --git a/test/expressions/splat/call/bool.wgsl.expected.wgsl b/test/tint/expressions/splat/call/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/call/bool.wgsl.expected.wgsl
rename to test/tint/expressions/splat/call/bool.wgsl.expected.wgsl
diff --git a/test/expressions/splat/call/f32.wgsl b/test/tint/expressions/splat/call/f32.wgsl
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl
rename to test/tint/expressions/splat/call/f32.wgsl
diff --git a/test/expressions/splat/call/f32.wgsl.expected.glsl b/test/tint/expressions/splat/call/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl.expected.glsl
rename to test/tint/expressions/splat/call/f32.wgsl.expected.glsl
diff --git a/test/expressions/splat/call/f32.wgsl.expected.hlsl b/test/tint/expressions/splat/call/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/call/f32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/call/f32.wgsl.expected.msl b/test/tint/expressions/splat/call/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl.expected.msl
rename to test/tint/expressions/splat/call/f32.wgsl.expected.msl
diff --git a/test/expressions/splat/call/f32.wgsl.expected.spvasm b/test/tint/expressions/splat/call/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/call/f32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/call/f32.wgsl.expected.wgsl b/test/tint/expressions/splat/call/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/call/f32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/call/f32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/call/i32.wgsl b/test/tint/expressions/splat/call/i32.wgsl
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl
rename to test/tint/expressions/splat/call/i32.wgsl
diff --git a/test/expressions/splat/call/i32.wgsl.expected.glsl b/test/tint/expressions/splat/call/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl.expected.glsl
rename to test/tint/expressions/splat/call/i32.wgsl.expected.glsl
diff --git a/test/expressions/splat/call/i32.wgsl.expected.hlsl b/test/tint/expressions/splat/call/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/call/i32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/call/i32.wgsl.expected.msl b/test/tint/expressions/splat/call/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl.expected.msl
rename to test/tint/expressions/splat/call/i32.wgsl.expected.msl
diff --git a/test/expressions/splat/call/i32.wgsl.expected.spvasm b/test/tint/expressions/splat/call/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/call/i32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/call/i32.wgsl.expected.wgsl b/test/tint/expressions/splat/call/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/call/i32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/call/i32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/call/u32.wgsl b/test/tint/expressions/splat/call/u32.wgsl
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl
rename to test/tint/expressions/splat/call/u32.wgsl
diff --git a/test/expressions/splat/call/u32.wgsl.expected.glsl b/test/tint/expressions/splat/call/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl.expected.glsl
rename to test/tint/expressions/splat/call/u32.wgsl.expected.glsl
diff --git a/test/expressions/splat/call/u32.wgsl.expected.hlsl b/test/tint/expressions/splat/call/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/call/u32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/call/u32.wgsl.expected.msl b/test/tint/expressions/splat/call/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl.expected.msl
rename to test/tint/expressions/splat/call/u32.wgsl.expected.msl
diff --git a/test/expressions/splat/call/u32.wgsl.expected.spvasm b/test/tint/expressions/splat/call/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/call/u32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/call/u32.wgsl.expected.wgsl b/test/tint/expressions/splat/call/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/call/u32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/call/u32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/expression/bool.wgsl b/test/tint/expressions/splat/expression/bool.wgsl
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl
rename to test/tint/expressions/splat/expression/bool.wgsl
diff --git a/test/expressions/splat/expression/bool.wgsl.expected.glsl b/test/tint/expressions/splat/expression/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl.expected.glsl
rename to test/tint/expressions/splat/expression/bool.wgsl.expected.glsl
diff --git a/test/expressions/splat/expression/bool.wgsl.expected.hlsl b/test/tint/expressions/splat/expression/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl.expected.hlsl
rename to test/tint/expressions/splat/expression/bool.wgsl.expected.hlsl
diff --git a/test/expressions/splat/expression/bool.wgsl.expected.msl b/test/tint/expressions/splat/expression/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl.expected.msl
rename to test/tint/expressions/splat/expression/bool.wgsl.expected.msl
diff --git a/test/expressions/splat/expression/bool.wgsl.expected.spvasm b/test/tint/expressions/splat/expression/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl.expected.spvasm
rename to test/tint/expressions/splat/expression/bool.wgsl.expected.spvasm
diff --git a/test/expressions/splat/expression/bool.wgsl.expected.wgsl b/test/tint/expressions/splat/expression/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/expression/bool.wgsl.expected.wgsl
rename to test/tint/expressions/splat/expression/bool.wgsl.expected.wgsl
diff --git a/test/expressions/splat/expression/f32.wgsl b/test/tint/expressions/splat/expression/f32.wgsl
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl
rename to test/tint/expressions/splat/expression/f32.wgsl
diff --git a/test/expressions/splat/expression/f32.wgsl.expected.glsl b/test/tint/expressions/splat/expression/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl.expected.glsl
rename to test/tint/expressions/splat/expression/f32.wgsl.expected.glsl
diff --git a/test/expressions/splat/expression/f32.wgsl.expected.hlsl b/test/tint/expressions/splat/expression/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/expression/f32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/expression/f32.wgsl.expected.msl b/test/tint/expressions/splat/expression/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl.expected.msl
rename to test/tint/expressions/splat/expression/f32.wgsl.expected.msl
diff --git a/test/expressions/splat/expression/f32.wgsl.expected.spvasm b/test/tint/expressions/splat/expression/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/expression/f32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/expression/f32.wgsl.expected.wgsl b/test/tint/expressions/splat/expression/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/expression/f32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/expression/f32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/expression/i32.wgsl b/test/tint/expressions/splat/expression/i32.wgsl
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl
rename to test/tint/expressions/splat/expression/i32.wgsl
diff --git a/test/expressions/splat/expression/i32.wgsl.expected.glsl b/test/tint/expressions/splat/expression/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl.expected.glsl
rename to test/tint/expressions/splat/expression/i32.wgsl.expected.glsl
diff --git a/test/expressions/splat/expression/i32.wgsl.expected.hlsl b/test/tint/expressions/splat/expression/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/expression/i32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/expression/i32.wgsl.expected.msl b/test/tint/expressions/splat/expression/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl.expected.msl
rename to test/tint/expressions/splat/expression/i32.wgsl.expected.msl
diff --git a/test/expressions/splat/expression/i32.wgsl.expected.spvasm b/test/tint/expressions/splat/expression/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/expression/i32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/expression/i32.wgsl.expected.wgsl b/test/tint/expressions/splat/expression/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/expression/i32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/expression/i32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/expression/u32.wgsl b/test/tint/expressions/splat/expression/u32.wgsl
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl
rename to test/tint/expressions/splat/expression/u32.wgsl
diff --git a/test/expressions/splat/expression/u32.wgsl.expected.glsl b/test/tint/expressions/splat/expression/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl.expected.glsl
rename to test/tint/expressions/splat/expression/u32.wgsl.expected.glsl
diff --git a/test/expressions/splat/expression/u32.wgsl.expected.hlsl b/test/tint/expressions/splat/expression/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/expression/u32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/expression/u32.wgsl.expected.msl b/test/tint/expressions/splat/expression/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl.expected.msl
rename to test/tint/expressions/splat/expression/u32.wgsl.expected.msl
diff --git a/test/expressions/splat/expression/u32.wgsl.expected.spvasm b/test/tint/expressions/splat/expression/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/expression/u32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/expression/u32.wgsl.expected.wgsl b/test/tint/expressions/splat/expression/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/expression/u32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/expression/u32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/immediate/bool.wgsl b/test/tint/expressions/splat/immediate/bool.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl
rename to test/tint/expressions/splat/immediate/bool.wgsl
diff --git a/test/expressions/splat/immediate/bool.wgsl.expected.glsl b/test/tint/expressions/splat/immediate/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl.expected.glsl
rename to test/tint/expressions/splat/immediate/bool.wgsl.expected.glsl
diff --git a/test/expressions/splat/immediate/bool.wgsl.expected.hlsl b/test/tint/expressions/splat/immediate/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl.expected.hlsl
rename to test/tint/expressions/splat/immediate/bool.wgsl.expected.hlsl
diff --git a/test/expressions/splat/immediate/bool.wgsl.expected.msl b/test/tint/expressions/splat/immediate/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl.expected.msl
rename to test/tint/expressions/splat/immediate/bool.wgsl.expected.msl
diff --git a/test/expressions/splat/immediate/bool.wgsl.expected.spvasm b/test/tint/expressions/splat/immediate/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl.expected.spvasm
rename to test/tint/expressions/splat/immediate/bool.wgsl.expected.spvasm
diff --git a/test/expressions/splat/immediate/bool.wgsl.expected.wgsl b/test/tint/expressions/splat/immediate/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/bool.wgsl.expected.wgsl
rename to test/tint/expressions/splat/immediate/bool.wgsl.expected.wgsl
diff --git a/test/expressions/splat/immediate/f32.wgsl b/test/tint/expressions/splat/immediate/f32.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl
rename to test/tint/expressions/splat/immediate/f32.wgsl
diff --git a/test/expressions/splat/immediate/f32.wgsl.expected.glsl b/test/tint/expressions/splat/immediate/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl.expected.glsl
rename to test/tint/expressions/splat/immediate/f32.wgsl.expected.glsl
diff --git a/test/expressions/splat/immediate/f32.wgsl.expected.hlsl b/test/tint/expressions/splat/immediate/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/immediate/f32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/immediate/f32.wgsl.expected.msl b/test/tint/expressions/splat/immediate/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl.expected.msl
rename to test/tint/expressions/splat/immediate/f32.wgsl.expected.msl
diff --git a/test/expressions/splat/immediate/f32.wgsl.expected.spvasm b/test/tint/expressions/splat/immediate/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/immediate/f32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/immediate/f32.wgsl.expected.wgsl b/test/tint/expressions/splat/immediate/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/f32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/immediate/f32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/immediate/i32.wgsl b/test/tint/expressions/splat/immediate/i32.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl
rename to test/tint/expressions/splat/immediate/i32.wgsl
diff --git a/test/expressions/splat/immediate/i32.wgsl.expected.glsl b/test/tint/expressions/splat/immediate/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl.expected.glsl
rename to test/tint/expressions/splat/immediate/i32.wgsl.expected.glsl
diff --git a/test/expressions/splat/immediate/i32.wgsl.expected.hlsl b/test/tint/expressions/splat/immediate/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/immediate/i32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/immediate/i32.wgsl.expected.msl b/test/tint/expressions/splat/immediate/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl.expected.msl
rename to test/tint/expressions/splat/immediate/i32.wgsl.expected.msl
diff --git a/test/expressions/splat/immediate/i32.wgsl.expected.spvasm b/test/tint/expressions/splat/immediate/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/immediate/i32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/immediate/i32.wgsl.expected.wgsl b/test/tint/expressions/splat/immediate/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/i32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/immediate/i32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/immediate/u32.wgsl b/test/tint/expressions/splat/immediate/u32.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl
rename to test/tint/expressions/splat/immediate/u32.wgsl
diff --git a/test/expressions/splat/immediate/u32.wgsl.expected.glsl b/test/tint/expressions/splat/immediate/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl.expected.glsl
rename to test/tint/expressions/splat/immediate/u32.wgsl.expected.glsl
diff --git a/test/expressions/splat/immediate/u32.wgsl.expected.hlsl b/test/tint/expressions/splat/immediate/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/immediate/u32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/immediate/u32.wgsl.expected.msl b/test/tint/expressions/splat/immediate/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl.expected.msl
rename to test/tint/expressions/splat/immediate/u32.wgsl.expected.msl
diff --git a/test/expressions/splat/immediate/u32.wgsl.expected.spvasm b/test/tint/expressions/splat/immediate/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/immediate/u32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/immediate/u32.wgsl.expected.wgsl b/test/tint/expressions/splat/immediate/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/immediate/u32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/immediate/u32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/var/bool.wgsl b/test/tint/expressions/splat/var/bool.wgsl
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl
rename to test/tint/expressions/splat/var/bool.wgsl
diff --git a/test/expressions/splat/var/bool.wgsl.expected.glsl b/test/tint/expressions/splat/var/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl.expected.glsl
rename to test/tint/expressions/splat/var/bool.wgsl.expected.glsl
diff --git a/test/expressions/splat/var/bool.wgsl.expected.hlsl b/test/tint/expressions/splat/var/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl.expected.hlsl
rename to test/tint/expressions/splat/var/bool.wgsl.expected.hlsl
diff --git a/test/expressions/splat/var/bool.wgsl.expected.msl b/test/tint/expressions/splat/var/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl.expected.msl
rename to test/tint/expressions/splat/var/bool.wgsl.expected.msl
diff --git a/test/expressions/splat/var/bool.wgsl.expected.spvasm b/test/tint/expressions/splat/var/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl.expected.spvasm
rename to test/tint/expressions/splat/var/bool.wgsl.expected.spvasm
diff --git a/test/expressions/splat/var/bool.wgsl.expected.wgsl b/test/tint/expressions/splat/var/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/var/bool.wgsl.expected.wgsl
rename to test/tint/expressions/splat/var/bool.wgsl.expected.wgsl
diff --git a/test/expressions/splat/var/f32.wgsl b/test/tint/expressions/splat/var/f32.wgsl
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl
rename to test/tint/expressions/splat/var/f32.wgsl
diff --git a/test/expressions/splat/var/f32.wgsl.expected.glsl b/test/tint/expressions/splat/var/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl.expected.glsl
rename to test/tint/expressions/splat/var/f32.wgsl.expected.glsl
diff --git a/test/expressions/splat/var/f32.wgsl.expected.hlsl b/test/tint/expressions/splat/var/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/var/f32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/var/f32.wgsl.expected.msl b/test/tint/expressions/splat/var/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl.expected.msl
rename to test/tint/expressions/splat/var/f32.wgsl.expected.msl
diff --git a/test/expressions/splat/var/f32.wgsl.expected.spvasm b/test/tint/expressions/splat/var/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/var/f32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/var/f32.wgsl.expected.wgsl b/test/tint/expressions/splat/var/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/var/f32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/var/f32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/var/i32.wgsl b/test/tint/expressions/splat/var/i32.wgsl
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl
rename to test/tint/expressions/splat/var/i32.wgsl
diff --git a/test/expressions/splat/var/i32.wgsl.expected.glsl b/test/tint/expressions/splat/var/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl.expected.glsl
rename to test/tint/expressions/splat/var/i32.wgsl.expected.glsl
diff --git a/test/expressions/splat/var/i32.wgsl.expected.hlsl b/test/tint/expressions/splat/var/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/var/i32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/var/i32.wgsl.expected.msl b/test/tint/expressions/splat/var/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl.expected.msl
rename to test/tint/expressions/splat/var/i32.wgsl.expected.msl
diff --git a/test/expressions/splat/var/i32.wgsl.expected.spvasm b/test/tint/expressions/splat/var/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/var/i32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/var/i32.wgsl.expected.wgsl b/test/tint/expressions/splat/var/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/var/i32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/var/i32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/var/u32.wgsl b/test/tint/expressions/splat/var/u32.wgsl
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl
rename to test/tint/expressions/splat/var/u32.wgsl
diff --git a/test/expressions/splat/var/u32.wgsl.expected.glsl b/test/tint/expressions/splat/var/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl.expected.glsl
rename to test/tint/expressions/splat/var/u32.wgsl.expected.glsl
diff --git a/test/expressions/splat/var/u32.wgsl.expected.hlsl b/test/tint/expressions/splat/var/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/var/u32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/var/u32.wgsl.expected.msl b/test/tint/expressions/splat/var/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl.expected.msl
rename to test/tint/expressions/splat/var/u32.wgsl.expected.msl
diff --git a/test/expressions/splat/var/u32.wgsl.expected.spvasm b/test/tint/expressions/splat/var/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/var/u32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/var/u32.wgsl.expected.wgsl b/test/tint/expressions/splat/var/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/var/u32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/var/u32.wgsl.expected.wgsl
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl b/test/tint/expressions/splat/with_swizzle/f32.wgsl
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl.expected.glsl b/test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl.expected.glsl
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.glsl
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl.expected.hlsl b/test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl.expected.hlsl
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.hlsl
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl.expected.msl b/test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl.expected.msl
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.msl
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl.expected.spvasm b/test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl.expected.spvasm
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.spvasm
diff --git a/test/expressions/splat/with_swizzle/f32.wgsl.expected.wgsl b/test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/splat/with_swizzle/f32.wgsl.expected.wgsl
rename to test/tint/expressions/splat/with_swizzle/f32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/packed_vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl b/test/tint/expressions/swizzle/read/vec3/f32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl b/test/tint/expressions/swizzle/read/vec3/i32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl b/test/tint/expressions/swizzle/read/vec3/u32.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl.expected.glsl b/test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl.expected.msl b/test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/read/vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/read/vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/read/vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/packed_vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl b/test/tint/expressions/swizzle/write/vec3/f32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl b/test/tint/expressions/swizzle/write/vec3/i32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl b/test/tint/expressions/swizzle/write/vec3/u32.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl.expected.glsl b/test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl.expected.msl b/test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/swizzle/write/vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/swizzle/write/vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/swizzle/write/vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/explicit/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/bool.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/i32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/explicit/u32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.glsl b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.glsl
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.glsl
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.hlsl b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.hlsl
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.hlsl
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.msl b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.msl
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.msl
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.spvasm b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.spvasm
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.spvasm
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl b/test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
rename to test/tint/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
diff --git a/test/expressions/unary/complement/complement.wgsl b/test/tint/expressions/unary/complement/complement.wgsl
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl
rename to test/tint/expressions/unary/complement/complement.wgsl
diff --git a/test/expressions/unary/complement/complement.wgsl.expected.glsl b/test/tint/expressions/unary/complement/complement.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl.expected.glsl
rename to test/tint/expressions/unary/complement/complement.wgsl.expected.glsl
diff --git a/test/expressions/unary/complement/complement.wgsl.expected.hlsl b/test/tint/expressions/unary/complement/complement.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl.expected.hlsl
rename to test/tint/expressions/unary/complement/complement.wgsl.expected.hlsl
diff --git a/test/expressions/unary/complement/complement.wgsl.expected.msl b/test/tint/expressions/unary/complement/complement.wgsl.expected.msl
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl.expected.msl
rename to test/tint/expressions/unary/complement/complement.wgsl.expected.msl
diff --git a/test/expressions/unary/complement/complement.wgsl.expected.spvasm b/test/tint/expressions/unary/complement/complement.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl.expected.spvasm
rename to test/tint/expressions/unary/complement/complement.wgsl.expected.spvasm
diff --git a/test/expressions/unary/complement/complement.wgsl.expected.wgsl b/test/tint/expressions/unary/complement/complement.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/unary/complement/complement.wgsl.expected.wgsl
rename to test/tint/expressions/unary/complement/complement.wgsl.expected.wgsl
diff --git a/test/expressions/unary/negate/negate.wgsl b/test/tint/expressions/unary/negate/negate.wgsl
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl
rename to test/tint/expressions/unary/negate/negate.wgsl
diff --git a/test/expressions/unary/negate/negate.wgsl.expected.glsl b/test/tint/expressions/unary/negate/negate.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl.expected.glsl
rename to test/tint/expressions/unary/negate/negate.wgsl.expected.glsl
diff --git a/test/expressions/unary/negate/negate.wgsl.expected.hlsl b/test/tint/expressions/unary/negate/negate.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl.expected.hlsl
rename to test/tint/expressions/unary/negate/negate.wgsl.expected.hlsl
diff --git a/test/expressions/unary/negate/negate.wgsl.expected.msl b/test/tint/expressions/unary/negate/negate.wgsl.expected.msl
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl.expected.msl
rename to test/tint/expressions/unary/negate/negate.wgsl.expected.msl
diff --git a/test/expressions/unary/negate/negate.wgsl.expected.spvasm b/test/tint/expressions/unary/negate/negate.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl.expected.spvasm
rename to test/tint/expressions/unary/negate/negate.wgsl.expected.spvasm
diff --git a/test/expressions/unary/negate/negate.wgsl.expected.wgsl b/test/tint/expressions/unary/negate/negate.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/unary/negate/negate.wgsl.expected.wgsl
rename to test/tint/expressions/unary/negate/negate.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/array/bool.wgsl b/test/tint/expressions/zero_init/array/bool.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl
rename to test/tint/expressions/zero_init/array/bool.wgsl
diff --git a/test/expressions/zero_init/array/bool.wgsl.expected.glsl b/test/tint/expressions/zero_init/array/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/array/bool.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/array/bool.wgsl.expected.hlsl b/test/tint/expressions/zero_init/array/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/array/bool.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/array/bool.wgsl.expected.msl b/test/tint/expressions/zero_init/array/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl.expected.msl
rename to test/tint/expressions/zero_init/array/bool.wgsl.expected.msl
diff --git a/test/expressions/zero_init/array/bool.wgsl.expected.spvasm b/test/tint/expressions/zero_init/array/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/array/bool.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/array/bool.wgsl.expected.wgsl b/test/tint/expressions/zero_init/array/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/bool.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/array/bool.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/array/f32.wgsl b/test/tint/expressions/zero_init/array/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl
rename to test/tint/expressions/zero_init/array/f32.wgsl
diff --git a/test/expressions/zero_init/array/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/array/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/array/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/array/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/array/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/array/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/array/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/array/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/array/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/array/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/array/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/array/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/array/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/array/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/array/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/array/i32.wgsl b/test/tint/expressions/zero_init/array/i32.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl
rename to test/tint/expressions/zero_init/array/i32.wgsl
diff --git a/test/expressions/zero_init/array/i32.wgsl.expected.glsl b/test/tint/expressions/zero_init/array/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/array/i32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/array/i32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/array/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/array/i32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/array/i32.wgsl.expected.msl b/test/tint/expressions/zero_init/array/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/array/i32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/array/i32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/array/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/array/i32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/array/i32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/array/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/i32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/array/i32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/array/struct.wgsl b/test/tint/expressions/zero_init/array/struct.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl
rename to test/tint/expressions/zero_init/array/struct.wgsl
diff --git a/test/expressions/zero_init/array/struct.wgsl.expected.glsl b/test/tint/expressions/zero_init/array/struct.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/array/struct.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/array/struct.wgsl.expected.hlsl b/test/tint/expressions/zero_init/array/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/array/struct.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/array/struct.wgsl.expected.msl b/test/tint/expressions/zero_init/array/struct.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl.expected.msl
rename to test/tint/expressions/zero_init/array/struct.wgsl.expected.msl
diff --git a/test/expressions/zero_init/array/struct.wgsl.expected.spvasm b/test/tint/expressions/zero_init/array/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/array/struct.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/array/struct.wgsl.expected.wgsl b/test/tint/expressions/zero_init/array/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/struct.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/array/struct.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/array/u32.wgsl b/test/tint/expressions/zero_init/array/u32.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl
rename to test/tint/expressions/zero_init/array/u32.wgsl
diff --git a/test/expressions/zero_init/array/u32.wgsl.expected.glsl b/test/tint/expressions/zero_init/array/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/array/u32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/array/u32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/array/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/array/u32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/array/u32.wgsl.expected.msl b/test/tint/expressions/zero_init/array/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/array/u32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/array/u32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/array/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/array/u32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/array/u32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/array/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/array/u32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/array/u32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl b/test/tint/expressions/zero_init/mat2x2/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat2x2/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x2/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat2x2/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl b/test/tint/expressions/zero_init/mat2x3/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat2x3/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat2x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl b/test/tint/expressions/zero_init/mat2x4/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat2x4/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat2x4/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat2x4/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl b/test/tint/expressions/zero_init/mat3x2/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat3x2/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x2/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat3x2/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl b/test/tint/expressions/zero_init/mat3x3/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat3x3/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat3x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl b/test/tint/expressions/zero_init/mat3x4/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat3x4/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat3x4/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat3x4/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl b/test/tint/expressions/zero_init/mat4x2/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat4x2/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x2/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat4x2/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl b/test/tint/expressions/zero_init/mat4x3/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat4x3/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat4x3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl b/test/tint/expressions/zero_init/mat4x4/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/mat4x4/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/mat4x4/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/mat4x4/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/scalar/bool.wgsl b/test/tint/expressions/zero_init/scalar/bool.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl
rename to test/tint/expressions/zero_init/scalar/bool.wgsl
diff --git a/test/expressions/zero_init/scalar/bool.wgsl.expected.glsl b/test/tint/expressions/zero_init/scalar/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/scalar/bool.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/scalar/bool.wgsl.expected.hlsl b/test/tint/expressions/zero_init/scalar/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/scalar/bool.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/scalar/bool.wgsl.expected.msl b/test/tint/expressions/zero_init/scalar/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl.expected.msl
rename to test/tint/expressions/zero_init/scalar/bool.wgsl.expected.msl
diff --git a/test/expressions/zero_init/scalar/bool.wgsl.expected.spvasm b/test/tint/expressions/zero_init/scalar/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/scalar/bool.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/scalar/bool.wgsl.expected.wgsl b/test/tint/expressions/zero_init/scalar/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/bool.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/scalar/bool.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/scalar/f32.wgsl b/test/tint/expressions/zero_init/scalar/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl
rename to test/tint/expressions/zero_init/scalar/f32.wgsl
diff --git a/test/expressions/zero_init/scalar/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/scalar/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/scalar/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/scalar/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/scalar/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/scalar/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/scalar/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/scalar/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/scalar/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/scalar/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/scalar/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/scalar/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/scalar/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/scalar/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/scalar/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/scalar/i32.wgsl b/test/tint/expressions/zero_init/scalar/i32.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl
rename to test/tint/expressions/zero_init/scalar/i32.wgsl
diff --git a/test/expressions/zero_init/scalar/i32.wgsl.expected.glsl b/test/tint/expressions/zero_init/scalar/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/scalar/i32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/scalar/i32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/scalar/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/scalar/i32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/scalar/i32.wgsl.expected.msl b/test/tint/expressions/zero_init/scalar/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/scalar/i32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/scalar/i32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/scalar/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/scalar/i32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/scalar/i32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/scalar/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/i32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/scalar/i32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/scalar/u32.wgsl b/test/tint/expressions/zero_init/scalar/u32.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl
rename to test/tint/expressions/zero_init/scalar/u32.wgsl
diff --git a/test/expressions/zero_init/scalar/u32.wgsl.expected.glsl b/test/tint/expressions/zero_init/scalar/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/scalar/u32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/scalar/u32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/scalar/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/scalar/u32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/scalar/u32.wgsl.expected.msl b/test/tint/expressions/zero_init/scalar/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/scalar/u32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/scalar/u32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/scalar/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/scalar/u32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/scalar/u32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/scalar/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/scalar/u32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/scalar/u32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/struct/array.wgsl b/test/tint/expressions/zero_init/struct/array.wgsl
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl
rename to test/tint/expressions/zero_init/struct/array.wgsl
diff --git a/test/expressions/zero_init/struct/array.wgsl.expected.glsl b/test/tint/expressions/zero_init/struct/array.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/struct/array.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/struct/array.wgsl.expected.hlsl b/test/tint/expressions/zero_init/struct/array.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/struct/array.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/struct/array.wgsl.expected.msl b/test/tint/expressions/zero_init/struct/array.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl.expected.msl
rename to test/tint/expressions/zero_init/struct/array.wgsl.expected.msl
diff --git a/test/expressions/zero_init/struct/array.wgsl.expected.spvasm b/test/tint/expressions/zero_init/struct/array.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/struct/array.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/struct/array.wgsl.expected.wgsl b/test/tint/expressions/zero_init/struct/array.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/struct/array.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/struct/array.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/struct/scalar.wgsl b/test/tint/expressions/zero_init/struct/scalar.wgsl
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl
rename to test/tint/expressions/zero_init/struct/scalar.wgsl
diff --git a/test/expressions/zero_init/struct/scalar.wgsl.expected.glsl b/test/tint/expressions/zero_init/struct/scalar.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/struct/scalar.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/struct/scalar.wgsl.expected.hlsl b/test/tint/expressions/zero_init/struct/scalar.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/struct/scalar.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/struct/scalar.wgsl.expected.msl b/test/tint/expressions/zero_init/struct/scalar.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl.expected.msl
rename to test/tint/expressions/zero_init/struct/scalar.wgsl.expected.msl
diff --git a/test/expressions/zero_init/struct/scalar.wgsl.expected.spvasm b/test/tint/expressions/zero_init/struct/scalar.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/struct/scalar.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/struct/scalar.wgsl.expected.wgsl b/test/tint/expressions/zero_init/struct/scalar.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/struct/scalar.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/struct/scalar.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec2/bool.wgsl b/test/tint/expressions/zero_init/vec2/bool.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl
rename to test/tint/expressions/zero_init/vec2/bool.wgsl
diff --git a/test/expressions/zero_init/vec2/bool.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec2/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec2/bool.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec2/bool.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec2/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec2/bool.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec2/bool.wgsl.expected.msl b/test/tint/expressions/zero_init/vec2/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec2/bool.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec2/bool.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec2/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec2/bool.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec2/bool.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec2/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/bool.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec2/bool.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec2/f32.wgsl b/test/tint/expressions/zero_init/vec2/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl
rename to test/tint/expressions/zero_init/vec2/f32.wgsl
diff --git a/test/expressions/zero_init/vec2/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec2/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec2/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec2/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec2/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec2/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec2/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec2/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec2/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec2/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec2/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec2/i32.wgsl b/test/tint/expressions/zero_init/vec2/i32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl
rename to test/tint/expressions/zero_init/vec2/i32.wgsl
diff --git a/test/expressions/zero_init/vec2/i32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec2/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec2/i32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec2/i32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec2/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec2/i32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec2/i32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec2/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec2/i32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec2/i32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec2/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec2/i32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec2/i32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec2/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/i32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec2/i32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec2/u32.wgsl b/test/tint/expressions/zero_init/vec2/u32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl
rename to test/tint/expressions/zero_init/vec2/u32.wgsl
diff --git a/test/expressions/zero_init/vec2/u32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec2/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec2/u32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec2/u32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec2/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec2/u32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec2/u32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec2/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec2/u32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec2/u32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec2/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec2/u32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec2/u32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec2/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec2/u32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec2/u32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec3/bool.wgsl b/test/tint/expressions/zero_init/vec3/bool.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl
rename to test/tint/expressions/zero_init/vec3/bool.wgsl
diff --git a/test/expressions/zero_init/vec3/bool.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec3/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec3/bool.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec3/bool.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec3/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec3/bool.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec3/bool.wgsl.expected.msl b/test/tint/expressions/zero_init/vec3/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec3/bool.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec3/bool.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec3/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec3/bool.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec3/bool.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec3/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/bool.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec3/bool.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec3/f32.wgsl b/test/tint/expressions/zero_init/vec3/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl
rename to test/tint/expressions/zero_init/vec3/f32.wgsl
diff --git a/test/expressions/zero_init/vec3/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec3/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec3/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec3/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec3/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec3/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec3/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec3/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec3/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec3/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec3/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec3/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec3/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec3/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec3/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec3/i32.wgsl b/test/tint/expressions/zero_init/vec3/i32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl
rename to test/tint/expressions/zero_init/vec3/i32.wgsl
diff --git a/test/expressions/zero_init/vec3/i32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec3/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec3/i32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec3/i32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec3/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec3/i32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec3/i32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec3/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec3/i32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec3/i32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec3/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec3/i32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec3/i32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec3/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/i32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec3/i32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec3/u32.wgsl b/test/tint/expressions/zero_init/vec3/u32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl
rename to test/tint/expressions/zero_init/vec3/u32.wgsl
diff --git a/test/expressions/zero_init/vec3/u32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec3/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec3/u32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec3/u32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec3/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec3/u32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec3/u32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec3/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec3/u32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec3/u32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec3/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec3/u32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec3/u32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec3/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec3/u32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec3/u32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec4/bool.wgsl b/test/tint/expressions/zero_init/vec4/bool.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl
rename to test/tint/expressions/zero_init/vec4/bool.wgsl
diff --git a/test/expressions/zero_init/vec4/bool.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec4/bool.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec4/bool.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec4/bool.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec4/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec4/bool.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec4/bool.wgsl.expected.msl b/test/tint/expressions/zero_init/vec4/bool.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec4/bool.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec4/bool.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec4/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec4/bool.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec4/bool.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec4/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/bool.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec4/bool.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec4/f32.wgsl b/test/tint/expressions/zero_init/vec4/f32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl
rename to test/tint/expressions/zero_init/vec4/f32.wgsl
diff --git a/test/expressions/zero_init/vec4/f32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec4/f32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec4/f32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec4/f32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec4/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec4/f32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec4/f32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec4/f32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec4/f32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec4/f32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec4/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec4/f32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec4/f32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec4/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/f32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec4/f32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec4/i32.wgsl b/test/tint/expressions/zero_init/vec4/i32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl
rename to test/tint/expressions/zero_init/vec4/i32.wgsl
diff --git a/test/expressions/zero_init/vec4/i32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec4/i32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec4/i32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec4/i32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec4/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec4/i32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec4/i32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec4/i32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec4/i32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec4/i32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec4/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec4/i32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec4/i32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec4/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/i32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec4/i32.wgsl.expected.wgsl
diff --git a/test/expressions/zero_init/vec4/u32.wgsl b/test/tint/expressions/zero_init/vec4/u32.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl
rename to test/tint/expressions/zero_init/vec4/u32.wgsl
diff --git a/test/expressions/zero_init/vec4/u32.wgsl.expected.glsl b/test/tint/expressions/zero_init/vec4/u32.wgsl.expected.glsl
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl.expected.glsl
rename to test/tint/expressions/zero_init/vec4/u32.wgsl.expected.glsl
diff --git a/test/expressions/zero_init/vec4/u32.wgsl.expected.hlsl b/test/tint/expressions/zero_init/vec4/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl.expected.hlsl
rename to test/tint/expressions/zero_init/vec4/u32.wgsl.expected.hlsl
diff --git a/test/expressions/zero_init/vec4/u32.wgsl.expected.msl b/test/tint/expressions/zero_init/vec4/u32.wgsl.expected.msl
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl.expected.msl
rename to test/tint/expressions/zero_init/vec4/u32.wgsl.expected.msl
diff --git a/test/expressions/zero_init/vec4/u32.wgsl.expected.spvasm b/test/tint/expressions/zero_init/vec4/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl.expected.spvasm
rename to test/tint/expressions/zero_init/vec4/u32.wgsl.expected.spvasm
diff --git a/test/expressions/zero_init/vec4/u32.wgsl.expected.wgsl b/test/tint/expressions/zero_init/vec4/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/expressions/zero_init/vec4/u32.wgsl.expected.wgsl
rename to test/tint/expressions/zero_init/vec4/u32.wgsl.expected.wgsl
diff --git a/test/tint/extract-spvasm.py b/test/tint/extract-spvasm.py
new file mode 100755
index 0000000..637d04d
--- /dev/null
+++ b/test/tint/extract-spvasm.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+
+# 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.
+
+# Extract SPIR-V assembly dumps from the output of
+#    tint_unittests --dump-spirv
+# Writes each module to a distinct filename, which is a sanitized
+# form of the test name, and with a ".spvasm" suffix.
+#
+# Usage:
+#    tint_unittests --dump-spirv | python3 extract-spvasm.py
+
+import sys
+import re
+
+
+def extract():
+    test_name = ''
+    in_spirv = False
+    parts = []
+    for line in sys.stdin:
+        run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
+        if run_match:
+            test_name = run_match.group(1)
+            test_name = re.sub('[^0-9a-zA-Z]', '_', test_name) + '.spvasm'
+        elif re.match('BEGIN ConvertedOk', line):
+            parts = []
+            in_spirv = True
+        elif re.match('END ConvertedOk', line):
+            with open(test_name, 'w') as f:
+                f.write('; Test: ' + test_name + '\n')
+                for l in parts:
+                    f.write(l)
+                f.close()
+        elif in_spirv:
+            parts.append(line)
+
+
+def main(argv):
+    if '--help' in argv or '-h' in argv:
+        print(
+            'Extract SPIR-V from the output of tint_unittests --dump-spirv\n')
+        print(
+            'Usage:\n    tint_unittests --dump-spirv | python3 extract-spvasm.py\n'
+        )
+        print(
+            'Writes each module to a distinct filename, which is a sanitized')
+        print('form of the test name, and with a ".spvasm" suffix.')
+        return 1
+    else:
+        extract()
+        return 0
+
+
+if __name__ == '__main__':
+    exit(main(sys.argv[1:]))
diff --git a/test/identifiers/underscore/double/alias.wgsl b/test/tint/identifiers/underscore/double/alias.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl
rename to test/tint/identifiers/underscore/double/alias.wgsl
diff --git a/test/identifiers/underscore/double/alias.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/alias.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/alias.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/alias.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/alias.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/alias.wgsl.expected.msl b/test/tint/identifiers/underscore/double/alias.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/alias.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/alias.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/alias.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/alias.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/alias.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/alias.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/double/fn.wgsl b/test/tint/identifiers/underscore/double/fn.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl
rename to test/tint/identifiers/underscore/double/fn.wgsl
diff --git a/test/identifiers/underscore/double/fn.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/fn.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/fn.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/fn.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/fn.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/fn.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/fn.wgsl.expected.msl b/test/tint/identifiers/underscore/double/fn.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/fn.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/fn.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/fn.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/fn.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/fn.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/fn.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/fn.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/fn.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/double/let.wgsl b/test/tint/identifiers/underscore/double/let.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl
rename to test/tint/identifiers/underscore/double/let.wgsl
diff --git a/test/identifiers/underscore/double/let.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/let.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/let.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/let.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/let.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/let.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/let.wgsl.expected.msl b/test/tint/identifiers/underscore/double/let.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/let.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/let.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/let.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/let.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/let.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/let.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/let.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/let.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/double/parameter.wgsl b/test/tint/identifiers/underscore/double/parameter.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl
rename to test/tint/identifiers/underscore/double/parameter.wgsl
diff --git a/test/identifiers/underscore/double/parameter.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/parameter.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/parameter.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/parameter.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/parameter.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/parameter.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/parameter.wgsl.expected.msl b/test/tint/identifiers/underscore/double/parameter.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/parameter.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/parameter.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/parameter.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/parameter.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/parameter.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/parameter.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/parameter.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/parameter.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/double/struct.wgsl b/test/tint/identifiers/underscore/double/struct.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl
rename to test/tint/identifiers/underscore/double/struct.wgsl
diff --git a/test/identifiers/underscore/double/struct.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/struct.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/struct.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/struct.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/struct.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/struct.wgsl.expected.msl b/test/tint/identifiers/underscore/double/struct.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/struct.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/struct.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/struct.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/struct.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/struct.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/struct.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/double/var.wgsl b/test/tint/identifiers/underscore/double/var.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl
rename to test/tint/identifiers/underscore/double/var.wgsl
diff --git a/test/identifiers/underscore/double/var.wgsl.expected.glsl b/test/tint/identifiers/underscore/double/var.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/double/var.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/double/var.wgsl.expected.hlsl b/test/tint/identifiers/underscore/double/var.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/double/var.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/double/var.wgsl.expected.msl b/test/tint/identifiers/underscore/double/var.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl.expected.msl
rename to test/tint/identifiers/underscore/double/var.wgsl.expected.msl
diff --git a/test/identifiers/underscore/double/var.wgsl.expected.spvasm b/test/tint/identifiers/underscore/double/var.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/double/var.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/double/var.wgsl.expected.wgsl b/test/tint/identifiers/underscore/double/var.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/double/var.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/double/var.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/alias.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/alias.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/alias.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/fn.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/fn.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/fn.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl b/test/tint/identifiers/underscore/prefix/lower/let.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/let.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/let.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/let.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/parameter.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/parameter.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/struct.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/struct.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/struct.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl b/test/tint/identifiers/underscore/prefix/lower/var.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/lower/var.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/lower/var.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/lower/var.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/alias.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/alias.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/alias.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/fn.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/fn.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/fn.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl b/test/tint/identifiers/underscore/prefix/upper/let.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/let.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/let.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/let.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/parameter.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/parameter.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/struct.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/struct.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/struct.wgsl.expected.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl b/test/tint/identifiers/underscore/prefix/upper/var.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl.expected.glsl b/test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.glsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl.expected.glsl
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.glsl
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl.expected.hlsl b/test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.hlsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl.expected.hlsl
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.hlsl
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl.expected.msl b/test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.msl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl.expected.msl
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.msl
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl.expected.spvasm b/test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.spvasm
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl.expected.spvasm
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.spvasm
diff --git a/test/identifiers/underscore/prefix/upper/var.wgsl.expected.wgsl b/test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.wgsl
similarity index 100%
rename from test/identifiers/underscore/prefix/upper/var.wgsl.expected.wgsl
rename to test/tint/identifiers/underscore/prefix/upper/var.wgsl.expected.wgsl
diff --git a/test/layout/storage/mat2x2/f32.wgsl b/test/tint/layout/storage/mat2x2/f32.wgsl
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl
rename to test/tint/layout/storage/mat2x2/f32.wgsl
diff --git a/test/layout/storage/mat2x2/f32.wgsl.expected.glsl b/test/tint/layout/storage/mat2x2/f32.wgsl.expected.glsl
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl.expected.glsl
rename to test/tint/layout/storage/mat2x2/f32.wgsl.expected.glsl
diff --git a/test/layout/storage/mat2x2/f32.wgsl.expected.hlsl b/test/tint/layout/storage/mat2x2/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl.expected.hlsl
rename to test/tint/layout/storage/mat2x2/f32.wgsl.expected.hlsl
diff --git a/test/layout/storage/mat2x2/f32.wgsl.expected.msl b/test/tint/layout/storage/mat2x2/f32.wgsl.expected.msl
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl.expected.msl
rename to test/tint/layout/storage/mat2x2/f32.wgsl.expected.msl
diff --git a/test/layout/storage/mat2x2/f32.wgsl.expected.spvasm b/test/tint/layout/storage/mat2x2/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl.expected.spvasm
rename to test/tint/layout/storage/mat2x2/f32.wgsl.expected.spvasm
diff --git a/test/layout/storage/mat2x2/f32.wgsl.expected.wgsl b/test/tint/layout/storage/mat2x2/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/layout/storage/mat2x2/f32.wgsl.expected.wgsl
rename to test/tint/layout/storage/mat2x2/f32.wgsl.expected.wgsl
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm b/test/tint/layout/storage/mat2x2/stride/16.spvasm
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm.expected.glsl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.glsl
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm.expected.glsl
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.glsl
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm.expected.hlsl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.hlsl
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm.expected.hlsl
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.hlsl
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm.expected.msl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.msl
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm.expected.msl
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.msl
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm.expected.spvasm b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.spvasm
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm.expected.spvasm
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.spvasm
diff --git a/test/layout/storage/mat2x2/stride/16.spvasm.expected.wgsl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.wgsl
similarity index 100%
rename from test/layout/storage/mat2x2/stride/16.spvasm.expected.wgsl
rename to test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.wgsl
diff --git a/test/let/global/global.wgsl b/test/tint/let/global/global.wgsl
similarity index 100%
rename from test/let/global/global.wgsl
rename to test/tint/let/global/global.wgsl
diff --git a/test/let/global/global.wgsl.expected.glsl b/test/tint/let/global/global.wgsl.expected.glsl
similarity index 100%
rename from test/let/global/global.wgsl.expected.glsl
rename to test/tint/let/global/global.wgsl.expected.glsl
diff --git a/test/let/global/global.wgsl.expected.hlsl b/test/tint/let/global/global.wgsl.expected.hlsl
similarity index 100%
rename from test/let/global/global.wgsl.expected.hlsl
rename to test/tint/let/global/global.wgsl.expected.hlsl
diff --git a/test/let/global/global.wgsl.expected.msl b/test/tint/let/global/global.wgsl.expected.msl
similarity index 100%
rename from test/let/global/global.wgsl.expected.msl
rename to test/tint/let/global/global.wgsl.expected.msl
diff --git a/test/let/global/global.wgsl.expected.spvasm b/test/tint/let/global/global.wgsl.expected.spvasm
similarity index 100%
rename from test/let/global/global.wgsl.expected.spvasm
rename to test/tint/let/global/global.wgsl.expected.spvasm
diff --git a/test/let/global/global.wgsl.expected.wgsl b/test/tint/let/global/global.wgsl.expected.wgsl
similarity index 100%
rename from test/let/global/global.wgsl.expected.wgsl
rename to test/tint/let/global/global.wgsl.expected.wgsl
diff --git a/test/let/inferred/function.wgsl b/test/tint/let/inferred/function.wgsl
similarity index 100%
rename from test/let/inferred/function.wgsl
rename to test/tint/let/inferred/function.wgsl
diff --git a/test/let/inferred/function.wgsl.expected.glsl b/test/tint/let/inferred/function.wgsl.expected.glsl
similarity index 100%
rename from test/let/inferred/function.wgsl.expected.glsl
rename to test/tint/let/inferred/function.wgsl.expected.glsl
diff --git a/test/let/inferred/function.wgsl.expected.hlsl b/test/tint/let/inferred/function.wgsl.expected.hlsl
similarity index 100%
rename from test/let/inferred/function.wgsl.expected.hlsl
rename to test/tint/let/inferred/function.wgsl.expected.hlsl
diff --git a/test/let/inferred/function.wgsl.expected.msl b/test/tint/let/inferred/function.wgsl.expected.msl
similarity index 100%
rename from test/let/inferred/function.wgsl.expected.msl
rename to test/tint/let/inferred/function.wgsl.expected.msl
diff --git a/test/let/inferred/function.wgsl.expected.spvasm b/test/tint/let/inferred/function.wgsl.expected.spvasm
similarity index 100%
rename from test/let/inferred/function.wgsl.expected.spvasm
rename to test/tint/let/inferred/function.wgsl.expected.spvasm
diff --git a/test/let/inferred/function.wgsl.expected.wgsl b/test/tint/let/inferred/function.wgsl.expected.wgsl
similarity index 100%
rename from test/let/inferred/function.wgsl.expected.wgsl
rename to test/tint/let/inferred/function.wgsl.expected.wgsl
diff --git a/test/loops/continue_in_switch.wgsl b/test/tint/loops/continue_in_switch.wgsl
similarity index 100%
rename from test/loops/continue_in_switch.wgsl
rename to test/tint/loops/continue_in_switch.wgsl
diff --git a/test/loops/continue_in_switch.wgsl.expected.glsl b/test/tint/loops/continue_in_switch.wgsl.expected.glsl
similarity index 100%
rename from test/loops/continue_in_switch.wgsl.expected.glsl
rename to test/tint/loops/continue_in_switch.wgsl.expected.glsl
diff --git a/test/loops/continue_in_switch.wgsl.expected.hlsl b/test/tint/loops/continue_in_switch.wgsl.expected.hlsl
similarity index 100%
rename from test/loops/continue_in_switch.wgsl.expected.hlsl
rename to test/tint/loops/continue_in_switch.wgsl.expected.hlsl
diff --git a/test/loops/continue_in_switch.wgsl.expected.msl b/test/tint/loops/continue_in_switch.wgsl.expected.msl
similarity index 100%
rename from test/loops/continue_in_switch.wgsl.expected.msl
rename to test/tint/loops/continue_in_switch.wgsl.expected.msl
diff --git a/test/loops/continue_in_switch.wgsl.expected.spvasm b/test/tint/loops/continue_in_switch.wgsl.expected.spvasm
similarity index 100%
rename from test/loops/continue_in_switch.wgsl.expected.spvasm
rename to test/tint/loops/continue_in_switch.wgsl.expected.spvasm
diff --git a/test/loops/continue_in_switch.wgsl.expected.wgsl b/test/tint/loops/continue_in_switch.wgsl.expected.wgsl
similarity index 100%
rename from test/loops/continue_in_switch.wgsl.expected.wgsl
rename to test/tint/loops/continue_in_switch.wgsl.expected.wgsl
diff --git a/test/loops/loop.wgsl b/test/tint/loops/loop.wgsl
similarity index 100%
rename from test/loops/loop.wgsl
rename to test/tint/loops/loop.wgsl
diff --git a/test/loops/loop.wgsl.expected.glsl b/test/tint/loops/loop.wgsl.expected.glsl
similarity index 100%
rename from test/loops/loop.wgsl.expected.glsl
rename to test/tint/loops/loop.wgsl.expected.glsl
diff --git a/test/loops/loop.wgsl.expected.hlsl b/test/tint/loops/loop.wgsl.expected.hlsl
similarity index 100%
rename from test/loops/loop.wgsl.expected.hlsl
rename to test/tint/loops/loop.wgsl.expected.hlsl
diff --git a/test/loops/loop.wgsl.expected.msl b/test/tint/loops/loop.wgsl.expected.msl
similarity index 100%
rename from test/loops/loop.wgsl.expected.msl
rename to test/tint/loops/loop.wgsl.expected.msl
diff --git a/test/loops/loop.wgsl.expected.spvasm b/test/tint/loops/loop.wgsl.expected.spvasm
similarity index 100%
rename from test/loops/loop.wgsl.expected.spvasm
rename to test/tint/loops/loop.wgsl.expected.spvasm
diff --git a/test/loops/loop.wgsl.expected.wgsl b/test/tint/loops/loop.wgsl.expected.wgsl
similarity index 100%
rename from test/loops/loop.wgsl.expected.wgsl
rename to test/tint/loops/loop.wgsl.expected.wgsl
diff --git a/test/loops/loop_with_continuing.wgsl b/test/tint/loops/loop_with_continuing.wgsl
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl
rename to test/tint/loops/loop_with_continuing.wgsl
diff --git a/test/loops/loop_with_continuing.wgsl.expected.glsl b/test/tint/loops/loop_with_continuing.wgsl.expected.glsl
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl.expected.glsl
rename to test/tint/loops/loop_with_continuing.wgsl.expected.glsl
diff --git a/test/loops/loop_with_continuing.wgsl.expected.hlsl b/test/tint/loops/loop_with_continuing.wgsl.expected.hlsl
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl.expected.hlsl
rename to test/tint/loops/loop_with_continuing.wgsl.expected.hlsl
diff --git a/test/loops/loop_with_continuing.wgsl.expected.msl b/test/tint/loops/loop_with_continuing.wgsl.expected.msl
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl.expected.msl
rename to test/tint/loops/loop_with_continuing.wgsl.expected.msl
diff --git a/test/loops/loop_with_continuing.wgsl.expected.spvasm b/test/tint/loops/loop_with_continuing.wgsl.expected.spvasm
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl.expected.spvasm
rename to test/tint/loops/loop_with_continuing.wgsl.expected.spvasm
diff --git a/test/loops/loop_with_continuing.wgsl.expected.wgsl b/test/tint/loops/loop_with_continuing.wgsl.expected.wgsl
similarity index 100%
rename from test/loops/loop_with_continuing.wgsl.expected.wgsl
rename to test/tint/loops/loop_with_continuing.wgsl.expected.wgsl
diff --git a/test/loops/nested_loops.wgsl b/test/tint/loops/nested_loops.wgsl
similarity index 100%
rename from test/loops/nested_loops.wgsl
rename to test/tint/loops/nested_loops.wgsl
diff --git a/test/loops/nested_loops.wgsl.expected.glsl b/test/tint/loops/nested_loops.wgsl.expected.glsl
similarity index 100%
rename from test/loops/nested_loops.wgsl.expected.glsl
rename to test/tint/loops/nested_loops.wgsl.expected.glsl
diff --git a/test/loops/nested_loops.wgsl.expected.hlsl b/test/tint/loops/nested_loops.wgsl.expected.hlsl
similarity index 100%
rename from test/loops/nested_loops.wgsl.expected.hlsl
rename to test/tint/loops/nested_loops.wgsl.expected.hlsl
diff --git a/test/loops/nested_loops.wgsl.expected.msl b/test/tint/loops/nested_loops.wgsl.expected.msl
similarity index 100%
rename from test/loops/nested_loops.wgsl.expected.msl
rename to test/tint/loops/nested_loops.wgsl.expected.msl
diff --git a/test/loops/nested_loops.wgsl.expected.spvasm b/test/tint/loops/nested_loops.wgsl.expected.spvasm
similarity index 100%
rename from test/loops/nested_loops.wgsl.expected.spvasm
rename to test/tint/loops/nested_loops.wgsl.expected.spvasm
diff --git a/test/loops/nested_loops.wgsl.expected.wgsl b/test/tint/loops/nested_loops.wgsl.expected.wgsl
similarity index 100%
rename from test/loops/nested_loops.wgsl.expected.wgsl
rename to test/tint/loops/nested_loops.wgsl.expected.wgsl
diff --git a/test/loops/nested_loops_with_continuing.wgsl b/test/tint/loops/nested_loops_with_continuing.wgsl
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl
rename to test/tint/loops/nested_loops_with_continuing.wgsl
diff --git a/test/loops/nested_loops_with_continuing.wgsl.expected.glsl b/test/tint/loops/nested_loops_with_continuing.wgsl.expected.glsl
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl.expected.glsl
rename to test/tint/loops/nested_loops_with_continuing.wgsl.expected.glsl
diff --git a/test/loops/nested_loops_with_continuing.wgsl.expected.hlsl b/test/tint/loops/nested_loops_with_continuing.wgsl.expected.hlsl
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl.expected.hlsl
rename to test/tint/loops/nested_loops_with_continuing.wgsl.expected.hlsl
diff --git a/test/loops/nested_loops_with_continuing.wgsl.expected.msl b/test/tint/loops/nested_loops_with_continuing.wgsl.expected.msl
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl.expected.msl
rename to test/tint/loops/nested_loops_with_continuing.wgsl.expected.msl
diff --git a/test/loops/nested_loops_with_continuing.wgsl.expected.spvasm b/test/tint/loops/nested_loops_with_continuing.wgsl.expected.spvasm
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl.expected.spvasm
rename to test/tint/loops/nested_loops_with_continuing.wgsl.expected.spvasm
diff --git a/test/loops/nested_loops_with_continuing.wgsl.expected.wgsl b/test/tint/loops/nested_loops_with_continuing.wgsl.expected.wgsl
similarity index 100%
rename from test/loops/nested_loops_with_continuing.wgsl.expected.wgsl
rename to test/tint/loops/nested_loops_with_continuing.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/alias/alias.wgsl b/test/tint/out_of_order_decls/alias/alias.wgsl
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl
rename to test/tint/out_of_order_decls/alias/alias.wgsl
diff --git a/test/out_of_order_decls/alias/alias.wgsl.expected.glsl b/test/tint/out_of_order_decls/alias/alias.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/alias/alias.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/alias/alias.wgsl.expected.hlsl b/test/tint/out_of_order_decls/alias/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/alias/alias.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/alias/alias.wgsl.expected.msl b/test/tint/out_of_order_decls/alias/alias.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl.expected.msl
rename to test/tint/out_of_order_decls/alias/alias.wgsl.expected.msl
diff --git a/test/out_of_order_decls/alias/alias.wgsl.expected.spvasm b/test/tint/out_of_order_decls/alias/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/alias/alias.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/alias/alias.wgsl.expected.wgsl b/test/tint/out_of_order_decls/alias/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/alias/alias.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/alias/alias.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/alias/struct.wgsl b/test/tint/out_of_order_decls/alias/struct.wgsl
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl
rename to test/tint/out_of_order_decls/alias/struct.wgsl
diff --git a/test/out_of_order_decls/alias/struct.wgsl.expected.glsl b/test/tint/out_of_order_decls/alias/struct.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/alias/struct.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/alias/struct.wgsl.expected.hlsl b/test/tint/out_of_order_decls/alias/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/alias/struct.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/alias/struct.wgsl.expected.msl b/test/tint/out_of_order_decls/alias/struct.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl.expected.msl
rename to test/tint/out_of_order_decls/alias/struct.wgsl.expected.msl
diff --git a/test/out_of_order_decls/alias/struct.wgsl.expected.spvasm b/test/tint/out_of_order_decls/alias/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/alias/struct.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/alias/struct.wgsl.expected.wgsl b/test/tint/out_of_order_decls/alias/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/alias/struct.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/alias/struct.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/array/alias.wgsl b/test/tint/out_of_order_decls/array/alias.wgsl
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl
rename to test/tint/out_of_order_decls/array/alias.wgsl
diff --git a/test/out_of_order_decls/array/alias.wgsl.expected.glsl b/test/tint/out_of_order_decls/array/alias.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/array/alias.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/array/alias.wgsl.expected.hlsl b/test/tint/out_of_order_decls/array/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/array/alias.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/array/alias.wgsl.expected.msl b/test/tint/out_of_order_decls/array/alias.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl.expected.msl
rename to test/tint/out_of_order_decls/array/alias.wgsl.expected.msl
diff --git a/test/out_of_order_decls/array/alias.wgsl.expected.spvasm b/test/tint/out_of_order_decls/array/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/array/alias.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/array/alias.wgsl.expected.wgsl b/test/tint/out_of_order_decls/array/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/array/alias.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/array/alias.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/array/struct.wgsl b/test/tint/out_of_order_decls/array/struct.wgsl
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl
rename to test/tint/out_of_order_decls/array/struct.wgsl
diff --git a/test/out_of_order_decls/array/struct.wgsl.expected.glsl b/test/tint/out_of_order_decls/array/struct.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/array/struct.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/array/struct.wgsl.expected.hlsl b/test/tint/out_of_order_decls/array/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/array/struct.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/array/struct.wgsl.expected.msl b/test/tint/out_of_order_decls/array/struct.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl.expected.msl
rename to test/tint/out_of_order_decls/array/struct.wgsl.expected.msl
diff --git a/test/out_of_order_decls/array/struct.wgsl.expected.spvasm b/test/tint/out_of_order_decls/array/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/array/struct.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/array/struct.wgsl.expected.wgsl b/test/tint/out_of_order_decls/array/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/array/struct.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/array/struct.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/func/func.wgsl b/test/tint/out_of_order_decls/func/func.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl
rename to test/tint/out_of_order_decls/func/func.wgsl
diff --git a/test/out_of_order_decls/func/func.wgsl.expected.glsl b/test/tint/out_of_order_decls/func/func.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/func/func.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/func/func.wgsl.expected.hlsl b/test/tint/out_of_order_decls/func/func.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/func/func.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/func/func.wgsl.expected.msl b/test/tint/out_of_order_decls/func/func.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl.expected.msl
rename to test/tint/out_of_order_decls/func/func.wgsl.expected.msl
diff --git a/test/out_of_order_decls/func/func.wgsl.expected.spvasm b/test/tint/out_of_order_decls/func/func.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/func/func.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/func/func.wgsl.expected.wgsl b/test/tint/out_of_order_decls/func/func.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/func.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/func/func.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/func/let.wgsl b/test/tint/out_of_order_decls/func/let.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl
rename to test/tint/out_of_order_decls/func/let.wgsl
diff --git a/test/out_of_order_decls/func/let.wgsl.expected.glsl b/test/tint/out_of_order_decls/func/let.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/func/let.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/func/let.wgsl.expected.hlsl b/test/tint/out_of_order_decls/func/let.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/func/let.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/func/let.wgsl.expected.msl b/test/tint/out_of_order_decls/func/let.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl.expected.msl
rename to test/tint/out_of_order_decls/func/let.wgsl.expected.msl
diff --git a/test/out_of_order_decls/func/let.wgsl.expected.spvasm b/test/tint/out_of_order_decls/func/let.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/func/let.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/func/let.wgsl.expected.wgsl b/test/tint/out_of_order_decls/func/let.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/let.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/func/let.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/func/type.wgsl b/test/tint/out_of_order_decls/func/type.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl
rename to test/tint/out_of_order_decls/func/type.wgsl
diff --git a/test/out_of_order_decls/func/type.wgsl.expected.glsl b/test/tint/out_of_order_decls/func/type.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/func/type.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/func/type.wgsl.expected.hlsl b/test/tint/out_of_order_decls/func/type.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/func/type.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/func/type.wgsl.expected.msl b/test/tint/out_of_order_decls/func/type.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl.expected.msl
rename to test/tint/out_of_order_decls/func/type.wgsl.expected.msl
diff --git a/test/out_of_order_decls/func/type.wgsl.expected.spvasm b/test/tint/out_of_order_decls/func/type.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/func/type.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/func/type.wgsl.expected.wgsl b/test/tint/out_of_order_decls/func/type.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/type.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/func/type.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/func/var.wgsl b/test/tint/out_of_order_decls/func/var.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl
rename to test/tint/out_of_order_decls/func/var.wgsl
diff --git a/test/out_of_order_decls/func/var.wgsl.expected.glsl b/test/tint/out_of_order_decls/func/var.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/func/var.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/func/var.wgsl.expected.hlsl b/test/tint/out_of_order_decls/func/var.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/func/var.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/func/var.wgsl.expected.msl b/test/tint/out_of_order_decls/func/var.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl.expected.msl
rename to test/tint/out_of_order_decls/func/var.wgsl.expected.msl
diff --git a/test/out_of_order_decls/func/var.wgsl.expected.spvasm b/test/tint/out_of_order_decls/func/var.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/func/var.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/func/var.wgsl.expected.wgsl b/test/tint/out_of_order_decls/func/var.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/func/var.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/func/var.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/struct/alias.wgsl b/test/tint/out_of_order_decls/struct/alias.wgsl
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl
rename to test/tint/out_of_order_decls/struct/alias.wgsl
diff --git a/test/out_of_order_decls/struct/alias.wgsl.expected.glsl b/test/tint/out_of_order_decls/struct/alias.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/struct/alias.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/struct/alias.wgsl.expected.hlsl b/test/tint/out_of_order_decls/struct/alias.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/struct/alias.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/struct/alias.wgsl.expected.msl b/test/tint/out_of_order_decls/struct/alias.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl.expected.msl
rename to test/tint/out_of_order_decls/struct/alias.wgsl.expected.msl
diff --git a/test/out_of_order_decls/struct/alias.wgsl.expected.spvasm b/test/tint/out_of_order_decls/struct/alias.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/struct/alias.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/struct/alias.wgsl.expected.wgsl b/test/tint/out_of_order_decls/struct/alias.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/struct/alias.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/struct/alias.wgsl.expected.wgsl
diff --git a/test/out_of_order_decls/struct/struct.wgsl b/test/tint/out_of_order_decls/struct/struct.wgsl
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl
rename to test/tint/out_of_order_decls/struct/struct.wgsl
diff --git a/test/out_of_order_decls/struct/struct.wgsl.expected.glsl b/test/tint/out_of_order_decls/struct/struct.wgsl.expected.glsl
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl.expected.glsl
rename to test/tint/out_of_order_decls/struct/struct.wgsl.expected.glsl
diff --git a/test/out_of_order_decls/struct/struct.wgsl.expected.hlsl b/test/tint/out_of_order_decls/struct/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl.expected.hlsl
rename to test/tint/out_of_order_decls/struct/struct.wgsl.expected.hlsl
diff --git a/test/out_of_order_decls/struct/struct.wgsl.expected.msl b/test/tint/out_of_order_decls/struct/struct.wgsl.expected.msl
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl.expected.msl
rename to test/tint/out_of_order_decls/struct/struct.wgsl.expected.msl
diff --git a/test/out_of_order_decls/struct/struct.wgsl.expected.spvasm b/test/tint/out_of_order_decls/struct/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl.expected.spvasm
rename to test/tint/out_of_order_decls/struct/struct.wgsl.expected.spvasm
diff --git a/test/out_of_order_decls/struct/struct.wgsl.expected.wgsl b/test/tint/out_of_order_decls/struct/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/out_of_order_decls/struct/struct.wgsl.expected.wgsl
rename to test/tint/out_of_order_decls/struct/struct.wgsl.expected.wgsl
diff --git a/test/ptr_ref/access/matrix.spvasm b/test/tint/ptr_ref/access/matrix.spvasm
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm
rename to test/tint/ptr_ref/access/matrix.spvasm
diff --git a/test/ptr_ref/access/matrix.spvasm.expected.glsl b/test/tint/ptr_ref/access/matrix.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm.expected.glsl
rename to test/tint/ptr_ref/access/matrix.spvasm.expected.glsl
diff --git a/test/ptr_ref/access/matrix.spvasm.expected.hlsl b/test/tint/ptr_ref/access/matrix.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm.expected.hlsl
rename to test/tint/ptr_ref/access/matrix.spvasm.expected.hlsl
diff --git a/test/ptr_ref/access/matrix.spvasm.expected.msl b/test/tint/ptr_ref/access/matrix.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm.expected.msl
rename to test/tint/ptr_ref/access/matrix.spvasm.expected.msl
diff --git a/test/ptr_ref/access/matrix.spvasm.expected.spvasm b/test/tint/ptr_ref/access/matrix.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm.expected.spvasm
rename to test/tint/ptr_ref/access/matrix.spvasm.expected.spvasm
diff --git a/test/ptr_ref/access/matrix.spvasm.expected.wgsl b/test/tint/ptr_ref/access/matrix.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/access/matrix.spvasm.expected.wgsl
rename to test/tint/ptr_ref/access/matrix.spvasm.expected.wgsl
diff --git a/test/ptr_ref/access/matrix.wgsl b/test/tint/ptr_ref/access/matrix.wgsl
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl
rename to test/tint/ptr_ref/access/matrix.wgsl
diff --git a/test/ptr_ref/access/matrix.wgsl.expected.glsl b/test/tint/ptr_ref/access/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl.expected.glsl
rename to test/tint/ptr_ref/access/matrix.wgsl.expected.glsl
diff --git a/test/ptr_ref/access/matrix.wgsl.expected.hlsl b/test/tint/ptr_ref/access/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl.expected.hlsl
rename to test/tint/ptr_ref/access/matrix.wgsl.expected.hlsl
diff --git a/test/ptr_ref/access/matrix.wgsl.expected.msl b/test/tint/ptr_ref/access/matrix.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl.expected.msl
rename to test/tint/ptr_ref/access/matrix.wgsl.expected.msl
diff --git a/test/ptr_ref/access/matrix.wgsl.expected.spvasm b/test/tint/ptr_ref/access/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl.expected.spvasm
rename to test/tint/ptr_ref/access/matrix.wgsl.expected.spvasm
diff --git a/test/ptr_ref/access/matrix.wgsl.expected.wgsl b/test/tint/ptr_ref/access/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/access/matrix.wgsl.expected.wgsl
rename to test/tint/ptr_ref/access/matrix.wgsl.expected.wgsl
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm b/test/tint/ptr_ref/copy/ptr_copy.spvasm
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm.expected.glsl b/test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm.expected.glsl
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.glsl
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm.expected.hlsl b/test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm.expected.hlsl
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.hlsl
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm.expected.msl b/test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm.expected.msl
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.msl
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm.expected.spvasm b/test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm.expected.spvasm
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.spvasm
diff --git a/test/ptr_ref/copy/ptr_copy.spvasm.expected.wgsl b/test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/copy/ptr_copy.spvasm.expected.wgsl
rename to test/tint/ptr_ref/copy/ptr_copy.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/global/i32.spvasm b/test/tint/ptr_ref/load/global/i32.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm
rename to test/tint/ptr_ref/load/global/i32.spvasm
diff --git a/test/ptr_ref/load/global/i32.spvasm.expected.glsl b/test/tint/ptr_ref/load/global/i32.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm.expected.glsl
rename to test/tint/ptr_ref/load/global/i32.spvasm.expected.glsl
diff --git a/test/ptr_ref/load/global/i32.spvasm.expected.hlsl b/test/tint/ptr_ref/load/global/i32.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm.expected.hlsl
rename to test/tint/ptr_ref/load/global/i32.spvasm.expected.hlsl
diff --git a/test/ptr_ref/load/global/i32.spvasm.expected.msl b/test/tint/ptr_ref/load/global/i32.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm.expected.msl
rename to test/tint/ptr_ref/load/global/i32.spvasm.expected.msl
diff --git a/test/ptr_ref/load/global/i32.spvasm.expected.spvasm b/test/tint/ptr_ref/load/global/i32.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm.expected.spvasm
rename to test/tint/ptr_ref/load/global/i32.spvasm.expected.spvasm
diff --git a/test/ptr_ref/load/global/i32.spvasm.expected.wgsl b/test/tint/ptr_ref/load/global/i32.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.spvasm.expected.wgsl
rename to test/tint/ptr_ref/load/global/i32.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/global/i32.wgsl b/test/tint/ptr_ref/load/global/i32.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl
rename to test/tint/ptr_ref/load/global/i32.wgsl
diff --git a/test/ptr_ref/load/global/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/global/i32.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/global/i32.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/global/i32.wgsl.expected.hlsl b/test/tint/ptr_ref/load/global/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/global/i32.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/global/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/global/i32.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl.expected.msl
rename to test/tint/ptr_ref/load/global/i32.wgsl.expected.msl
diff --git a/test/ptr_ref/load/global/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/global/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/global/i32.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/global/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/global/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/i32.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/global/i32.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/global/struct_field.spvasm b/test/tint/ptr_ref/load/global/struct_field.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm
rename to test/tint/ptr_ref/load/global/struct_field.spvasm
diff --git a/test/ptr_ref/load/global/struct_field.spvasm.expected.glsl b/test/tint/ptr_ref/load/global/struct_field.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm.expected.glsl
rename to test/tint/ptr_ref/load/global/struct_field.spvasm.expected.glsl
diff --git a/test/ptr_ref/load/global/struct_field.spvasm.expected.hlsl b/test/tint/ptr_ref/load/global/struct_field.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm.expected.hlsl
rename to test/tint/ptr_ref/load/global/struct_field.spvasm.expected.hlsl
diff --git a/test/ptr_ref/load/global/struct_field.spvasm.expected.msl b/test/tint/ptr_ref/load/global/struct_field.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm.expected.msl
rename to test/tint/ptr_ref/load/global/struct_field.spvasm.expected.msl
diff --git a/test/ptr_ref/load/global/struct_field.spvasm.expected.spvasm b/test/tint/ptr_ref/load/global/struct_field.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm.expected.spvasm
rename to test/tint/ptr_ref/load/global/struct_field.spvasm.expected.spvasm
diff --git a/test/ptr_ref/load/global/struct_field.spvasm.expected.wgsl b/test/tint/ptr_ref/load/global/struct_field.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.spvasm.expected.wgsl
rename to test/tint/ptr_ref/load/global/struct_field.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/global/struct_field.wgsl b/test/tint/ptr_ref/load/global/struct_field.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl
rename to test/tint/ptr_ref/load/global/struct_field.wgsl
diff --git a/test/ptr_ref/load/global/struct_field.wgsl.expected.glsl b/test/tint/ptr_ref/load/global/struct_field.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/global/struct_field.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/global/struct_field.wgsl.expected.hlsl b/test/tint/ptr_ref/load/global/struct_field.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/global/struct_field.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/global/struct_field.wgsl.expected.msl b/test/tint/ptr_ref/load/global/struct_field.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl.expected.msl
rename to test/tint/ptr_ref/load/global/struct_field.wgsl.expected.msl
diff --git a/test/ptr_ref/load/global/struct_field.wgsl.expected.spvasm b/test/tint/ptr_ref/load/global/struct_field.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/global/struct_field.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/global/struct_field.wgsl.expected.wgsl b/test/tint/ptr_ref/load/global/struct_field.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/global/struct_field.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/global/struct_field.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/i32.spvasm b/test/tint/ptr_ref/load/local/i32.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm
rename to test/tint/ptr_ref/load/local/i32.spvasm
diff --git a/test/ptr_ref/load/local/i32.spvasm.expected.glsl b/test/tint/ptr_ref/load/local/i32.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm.expected.glsl
rename to test/tint/ptr_ref/load/local/i32.spvasm.expected.glsl
diff --git a/test/ptr_ref/load/local/i32.spvasm.expected.hlsl b/test/tint/ptr_ref/load/local/i32.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm.expected.hlsl
rename to test/tint/ptr_ref/load/local/i32.spvasm.expected.hlsl
diff --git a/test/ptr_ref/load/local/i32.spvasm.expected.msl b/test/tint/ptr_ref/load/local/i32.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm.expected.msl
rename to test/tint/ptr_ref/load/local/i32.spvasm.expected.msl
diff --git a/test/ptr_ref/load/local/i32.spvasm.expected.spvasm b/test/tint/ptr_ref/load/local/i32.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm.expected.spvasm
rename to test/tint/ptr_ref/load/local/i32.spvasm.expected.spvasm
diff --git a/test/ptr_ref/load/local/i32.spvasm.expected.wgsl b/test/tint/ptr_ref/load/local/i32.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.spvasm.expected.wgsl
rename to test/tint/ptr_ref/load/local/i32.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/local/i32.wgsl b/test/tint/ptr_ref/load/local/i32.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl
rename to test/tint/ptr_ref/load/local/i32.wgsl
diff --git a/test/ptr_ref/load/local/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/i32.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/i32.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/i32.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/i32.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/local/i32.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/i32.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/i32.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/i32.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/i32.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl b/test/tint/ptr_ref/load/local/ptr_function.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl.expected.msl b/test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/ptr_function.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_function.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/ptr_function.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl b/test/tint/ptr_ref/load/local/ptr_private.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl.expected.msl b/test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/ptr_private.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_private.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/ptr_private.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl b/test/tint/ptr_ref/load/local/ptr_storage.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl.expected.msl b/test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/ptr_storage.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_storage.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/ptr_storage.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl.expected.msl b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/ptr_uniform.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_uniform.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/ptr_uniform.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.msl b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/ptr_workgroup.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/ptr_workgroup.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/local/struct_field.spvasm b/test/tint/ptr_ref/load/local/struct_field.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm
rename to test/tint/ptr_ref/load/local/struct_field.spvasm
diff --git a/test/ptr_ref/load/local/struct_field.spvasm.expected.glsl b/test/tint/ptr_ref/load/local/struct_field.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm.expected.glsl
rename to test/tint/ptr_ref/load/local/struct_field.spvasm.expected.glsl
diff --git a/test/ptr_ref/load/local/struct_field.spvasm.expected.hlsl b/test/tint/ptr_ref/load/local/struct_field.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm.expected.hlsl
rename to test/tint/ptr_ref/load/local/struct_field.spvasm.expected.hlsl
diff --git a/test/ptr_ref/load/local/struct_field.spvasm.expected.msl b/test/tint/ptr_ref/load/local/struct_field.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm.expected.msl
rename to test/tint/ptr_ref/load/local/struct_field.spvasm.expected.msl
diff --git a/test/ptr_ref/load/local/struct_field.spvasm.expected.spvasm b/test/tint/ptr_ref/load/local/struct_field.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm.expected.spvasm
rename to test/tint/ptr_ref/load/local/struct_field.spvasm.expected.spvasm
diff --git a/test/ptr_ref/load/local/struct_field.spvasm.expected.wgsl b/test/tint/ptr_ref/load/local/struct_field.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.spvasm.expected.wgsl
rename to test/tint/ptr_ref/load/local/struct_field.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/local/struct_field.wgsl b/test/tint/ptr_ref/load/local/struct_field.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl
rename to test/tint/ptr_ref/load/local/struct_field.wgsl
diff --git a/test/ptr_ref/load/local/struct_field.wgsl.expected.glsl b/test/tint/ptr_ref/load/local/struct_field.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/local/struct_field.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/local/struct_field.wgsl.expected.hlsl b/test/tint/ptr_ref/load/local/struct_field.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/local/struct_field.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/local/struct_field.wgsl.expected.msl b/test/tint/ptr_ref/load/local/struct_field.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl.expected.msl
rename to test/tint/ptr_ref/load/local/struct_field.wgsl.expected.msl
diff --git a/test/ptr_ref/load/local/struct_field.wgsl.expected.spvasm b/test/tint/ptr_ref/load/local/struct_field.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/local/struct_field.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/local/struct_field.wgsl.expected.wgsl b/test/tint/ptr_ref/load/local/struct_field.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/local/struct_field.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/local/struct_field.wgsl.expected.wgsl
diff --git a/test/ptr_ref/load/param/ptr.spvasm b/test/tint/ptr_ref/load/param/ptr.spvasm
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm
rename to test/tint/ptr_ref/load/param/ptr.spvasm
diff --git a/test/ptr_ref/load/param/ptr.spvasm.expected.glsl b/test/tint/ptr_ref/load/param/ptr.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm.expected.glsl
rename to test/tint/ptr_ref/load/param/ptr.spvasm.expected.glsl
diff --git a/test/ptr_ref/load/param/ptr.spvasm.expected.hlsl b/test/tint/ptr_ref/load/param/ptr.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm.expected.hlsl
rename to test/tint/ptr_ref/load/param/ptr.spvasm.expected.hlsl
diff --git a/test/ptr_ref/load/param/ptr.spvasm.expected.msl b/test/tint/ptr_ref/load/param/ptr.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm.expected.msl
rename to test/tint/ptr_ref/load/param/ptr.spvasm.expected.msl
diff --git a/test/ptr_ref/load/param/ptr.spvasm.expected.spvasm b/test/tint/ptr_ref/load/param/ptr.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm.expected.spvasm
rename to test/tint/ptr_ref/load/param/ptr.spvasm.expected.spvasm
diff --git a/test/ptr_ref/load/param/ptr.spvasm.expected.wgsl b/test/tint/ptr_ref/load/param/ptr.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.spvasm.expected.wgsl
rename to test/tint/ptr_ref/load/param/ptr.spvasm.expected.wgsl
diff --git a/test/ptr_ref/load/param/ptr.wgsl b/test/tint/ptr_ref/load/param/ptr.wgsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl
rename to test/tint/ptr_ref/load/param/ptr.wgsl
diff --git a/test/ptr_ref/load/param/ptr.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/ptr.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl.expected.glsl
rename to test/tint/ptr_ref/load/param/ptr.wgsl.expected.glsl
diff --git a/test/ptr_ref/load/param/ptr.wgsl.expected.hlsl b/test/tint/ptr_ref/load/param/ptr.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl.expected.hlsl
rename to test/tint/ptr_ref/load/param/ptr.wgsl.expected.hlsl
diff --git a/test/ptr_ref/load/param/ptr.wgsl.expected.msl b/test/tint/ptr_ref/load/param/ptr.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl.expected.msl
rename to test/tint/ptr_ref/load/param/ptr.wgsl.expected.msl
diff --git a/test/ptr_ref/load/param/ptr.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/ptr.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl.expected.spvasm
rename to test/tint/ptr_ref/load/param/ptr.wgsl.expected.spvasm
diff --git a/test/ptr_ref/load/param/ptr.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/ptr.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/load/param/ptr.wgsl.expected.wgsl
rename to test/tint/ptr_ref/load/param/ptr.wgsl.expected.wgsl
diff --git a/test/ptr_ref/store/global/i32.spvasm b/test/tint/ptr_ref/store/global/i32.spvasm
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm
rename to test/tint/ptr_ref/store/global/i32.spvasm
diff --git a/test/ptr_ref/store/global/i32.spvasm.expected.glsl b/test/tint/ptr_ref/store/global/i32.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm.expected.glsl
rename to test/tint/ptr_ref/store/global/i32.spvasm.expected.glsl
diff --git a/test/ptr_ref/store/global/i32.spvasm.expected.hlsl b/test/tint/ptr_ref/store/global/i32.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm.expected.hlsl
rename to test/tint/ptr_ref/store/global/i32.spvasm.expected.hlsl
diff --git a/test/ptr_ref/store/global/i32.spvasm.expected.msl b/test/tint/ptr_ref/store/global/i32.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm.expected.msl
rename to test/tint/ptr_ref/store/global/i32.spvasm.expected.msl
diff --git a/test/ptr_ref/store/global/i32.spvasm.expected.spvasm b/test/tint/ptr_ref/store/global/i32.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm.expected.spvasm
rename to test/tint/ptr_ref/store/global/i32.spvasm.expected.spvasm
diff --git a/test/ptr_ref/store/global/i32.spvasm.expected.wgsl b/test/tint/ptr_ref/store/global/i32.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.spvasm.expected.wgsl
rename to test/tint/ptr_ref/store/global/i32.spvasm.expected.wgsl
diff --git a/test/ptr_ref/store/global/i32.wgsl b/test/tint/ptr_ref/store/global/i32.wgsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl
rename to test/tint/ptr_ref/store/global/i32.wgsl
diff --git a/test/ptr_ref/store/global/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/global/i32.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl.expected.glsl
rename to test/tint/ptr_ref/store/global/i32.wgsl.expected.glsl
diff --git a/test/ptr_ref/store/global/i32.wgsl.expected.hlsl b/test/tint/ptr_ref/store/global/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl.expected.hlsl
rename to test/tint/ptr_ref/store/global/i32.wgsl.expected.hlsl
diff --git a/test/ptr_ref/store/global/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/global/i32.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl.expected.msl
rename to test/tint/ptr_ref/store/global/i32.wgsl.expected.msl
diff --git a/test/ptr_ref/store/global/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/global/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl.expected.spvasm
rename to test/tint/ptr_ref/store/global/i32.wgsl.expected.spvasm
diff --git a/test/ptr_ref/store/global/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/global/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/global/i32.wgsl.expected.wgsl
rename to test/tint/ptr_ref/store/global/i32.wgsl.expected.wgsl
diff --git a/test/ptr_ref/store/global/struct_field.spvasm b/test/tint/ptr_ref/store/global/struct_field.spvasm
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm
rename to test/tint/ptr_ref/store/global/struct_field.spvasm
diff --git a/test/ptr_ref/store/global/struct_field.spvasm.expected.glsl b/test/tint/ptr_ref/store/global/struct_field.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm.expected.glsl
rename to test/tint/ptr_ref/store/global/struct_field.spvasm.expected.glsl
diff --git a/test/ptr_ref/store/global/struct_field.spvasm.expected.hlsl b/test/tint/ptr_ref/store/global/struct_field.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm.expected.hlsl
rename to test/tint/ptr_ref/store/global/struct_field.spvasm.expected.hlsl
diff --git a/test/ptr_ref/store/global/struct_field.spvasm.expected.msl b/test/tint/ptr_ref/store/global/struct_field.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm.expected.msl
rename to test/tint/ptr_ref/store/global/struct_field.spvasm.expected.msl
diff --git a/test/ptr_ref/store/global/struct_field.spvasm.expected.spvasm b/test/tint/ptr_ref/store/global/struct_field.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm.expected.spvasm
rename to test/tint/ptr_ref/store/global/struct_field.spvasm.expected.spvasm
diff --git a/test/ptr_ref/store/global/struct_field.spvasm.expected.wgsl b/test/tint/ptr_ref/store/global/struct_field.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/global/struct_field.spvasm.expected.wgsl
rename to test/tint/ptr_ref/store/global/struct_field.spvasm.expected.wgsl
diff --git a/test/ptr_ref/store/local/i32.spvasm b/test/tint/ptr_ref/store/local/i32.spvasm
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm
rename to test/tint/ptr_ref/store/local/i32.spvasm
diff --git a/test/ptr_ref/store/local/i32.spvasm.expected.glsl b/test/tint/ptr_ref/store/local/i32.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm.expected.glsl
rename to test/tint/ptr_ref/store/local/i32.spvasm.expected.glsl
diff --git a/test/ptr_ref/store/local/i32.spvasm.expected.hlsl b/test/tint/ptr_ref/store/local/i32.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm.expected.hlsl
rename to test/tint/ptr_ref/store/local/i32.spvasm.expected.hlsl
diff --git a/test/ptr_ref/store/local/i32.spvasm.expected.msl b/test/tint/ptr_ref/store/local/i32.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm.expected.msl
rename to test/tint/ptr_ref/store/local/i32.spvasm.expected.msl
diff --git a/test/ptr_ref/store/local/i32.spvasm.expected.spvasm b/test/tint/ptr_ref/store/local/i32.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm.expected.spvasm
rename to test/tint/ptr_ref/store/local/i32.spvasm.expected.spvasm
diff --git a/test/ptr_ref/store/local/i32.spvasm.expected.wgsl b/test/tint/ptr_ref/store/local/i32.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.spvasm.expected.wgsl
rename to test/tint/ptr_ref/store/local/i32.spvasm.expected.wgsl
diff --git a/test/ptr_ref/store/local/i32.wgsl b/test/tint/ptr_ref/store/local/i32.wgsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl
rename to test/tint/ptr_ref/store/local/i32.wgsl
diff --git a/test/ptr_ref/store/local/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/local/i32.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl.expected.glsl
rename to test/tint/ptr_ref/store/local/i32.wgsl.expected.glsl
diff --git a/test/ptr_ref/store/local/i32.wgsl.expected.hlsl b/test/tint/ptr_ref/store/local/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl.expected.hlsl
rename to test/tint/ptr_ref/store/local/i32.wgsl.expected.hlsl
diff --git a/test/ptr_ref/store/local/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/local/i32.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl.expected.msl
rename to test/tint/ptr_ref/store/local/i32.wgsl.expected.msl
diff --git a/test/ptr_ref/store/local/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/local/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl.expected.spvasm
rename to test/tint/ptr_ref/store/local/i32.wgsl.expected.spvasm
diff --git a/test/ptr_ref/store/local/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/local/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/local/i32.wgsl.expected.wgsl
rename to test/tint/ptr_ref/store/local/i32.wgsl.expected.wgsl
diff --git a/test/ptr_ref/store/local/struct_field.spvasm b/test/tint/ptr_ref/store/local/struct_field.spvasm
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm
rename to test/tint/ptr_ref/store/local/struct_field.spvasm
diff --git a/test/ptr_ref/store/local/struct_field.spvasm.expected.glsl b/test/tint/ptr_ref/store/local/struct_field.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm.expected.glsl
rename to test/tint/ptr_ref/store/local/struct_field.spvasm.expected.glsl
diff --git a/test/ptr_ref/store/local/struct_field.spvasm.expected.hlsl b/test/tint/ptr_ref/store/local/struct_field.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm.expected.hlsl
rename to test/tint/ptr_ref/store/local/struct_field.spvasm.expected.hlsl
diff --git a/test/ptr_ref/store/local/struct_field.spvasm.expected.msl b/test/tint/ptr_ref/store/local/struct_field.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm.expected.msl
rename to test/tint/ptr_ref/store/local/struct_field.spvasm.expected.msl
diff --git a/test/ptr_ref/store/local/struct_field.spvasm.expected.spvasm b/test/tint/ptr_ref/store/local/struct_field.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm.expected.spvasm
rename to test/tint/ptr_ref/store/local/struct_field.spvasm.expected.spvasm
diff --git a/test/ptr_ref/store/local/struct_field.spvasm.expected.wgsl b/test/tint/ptr_ref/store/local/struct_field.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/local/struct_field.spvasm.expected.wgsl
rename to test/tint/ptr_ref/store/local/struct_field.spvasm.expected.wgsl
diff --git a/test/ptr_ref/store/param/ptr.spvasm b/test/tint/ptr_ref/store/param/ptr.spvasm
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm
rename to test/tint/ptr_ref/store/param/ptr.spvasm
diff --git a/test/ptr_ref/store/param/ptr.spvasm.expected.glsl b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm.expected.glsl
rename to test/tint/ptr_ref/store/param/ptr.spvasm.expected.glsl
diff --git a/test/ptr_ref/store/param/ptr.spvasm.expected.hlsl b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm.expected.hlsl
rename to test/tint/ptr_ref/store/param/ptr.spvasm.expected.hlsl
diff --git a/test/ptr_ref/store/param/ptr.spvasm.expected.msl b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.msl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm.expected.msl
rename to test/tint/ptr_ref/store/param/ptr.spvasm.expected.msl
diff --git a/test/ptr_ref/store/param/ptr.spvasm.expected.spvasm b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm.expected.spvasm
rename to test/tint/ptr_ref/store/param/ptr.spvasm.expected.spvasm
diff --git a/test/ptr_ref/store/param/ptr.spvasm.expected.wgsl b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.spvasm.expected.wgsl
rename to test/tint/ptr_ref/store/param/ptr.spvasm.expected.wgsl
diff --git a/test/ptr_ref/store/param/ptr.wgsl b/test/tint/ptr_ref/store/param/ptr.wgsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl
rename to test/tint/ptr_ref/store/param/ptr.wgsl
diff --git a/test/ptr_ref/store/param/ptr.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/ptr.wgsl.expected.glsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl.expected.glsl
rename to test/tint/ptr_ref/store/param/ptr.wgsl.expected.glsl
diff --git a/test/ptr_ref/store/param/ptr.wgsl.expected.hlsl b/test/tint/ptr_ref/store/param/ptr.wgsl.expected.hlsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl.expected.hlsl
rename to test/tint/ptr_ref/store/param/ptr.wgsl.expected.hlsl
diff --git a/test/ptr_ref/store/param/ptr.wgsl.expected.msl b/test/tint/ptr_ref/store/param/ptr.wgsl.expected.msl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl.expected.msl
rename to test/tint/ptr_ref/store/param/ptr.wgsl.expected.msl
diff --git a/test/ptr_ref/store/param/ptr.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/ptr.wgsl.expected.spvasm
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl.expected.spvasm
rename to test/tint/ptr_ref/store/param/ptr.wgsl.expected.spvasm
diff --git a/test/ptr_ref/store/param/ptr.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/ptr.wgsl.expected.wgsl
similarity index 100%
rename from test/ptr_ref/store/param/ptr.wgsl.expected.wgsl
rename to test/tint/ptr_ref/store/param/ptr.wgsl.expected.wgsl
diff --git a/test/samples/compute_boids.wgsl b/test/tint/samples/compute_boids.wgsl
similarity index 100%
rename from test/samples/compute_boids.wgsl
rename to test/tint/samples/compute_boids.wgsl
diff --git a/test/samples/compute_boids.wgsl.expected.glsl b/test/tint/samples/compute_boids.wgsl.expected.glsl
similarity index 100%
rename from test/samples/compute_boids.wgsl.expected.glsl
rename to test/tint/samples/compute_boids.wgsl.expected.glsl
diff --git a/test/samples/compute_boids.wgsl.expected.hlsl b/test/tint/samples/compute_boids.wgsl.expected.hlsl
similarity index 100%
rename from test/samples/compute_boids.wgsl.expected.hlsl
rename to test/tint/samples/compute_boids.wgsl.expected.hlsl
diff --git a/test/samples/compute_boids.wgsl.expected.msl b/test/tint/samples/compute_boids.wgsl.expected.msl
similarity index 100%
rename from test/samples/compute_boids.wgsl.expected.msl
rename to test/tint/samples/compute_boids.wgsl.expected.msl
diff --git a/test/samples/compute_boids.wgsl.expected.spvasm b/test/tint/samples/compute_boids.wgsl.expected.spvasm
similarity index 100%
rename from test/samples/compute_boids.wgsl.expected.spvasm
rename to test/tint/samples/compute_boids.wgsl.expected.spvasm
diff --git a/test/samples/compute_boids.wgsl.expected.wgsl b/test/tint/samples/compute_boids.wgsl.expected.wgsl
similarity index 100%
rename from test/samples/compute_boids.wgsl.expected.wgsl
rename to test/tint/samples/compute_boids.wgsl.expected.wgsl
diff --git a/test/samples/cube.wgsl b/test/tint/samples/cube.wgsl
similarity index 100%
rename from test/samples/cube.wgsl
rename to test/tint/samples/cube.wgsl
diff --git a/test/samples/cube.wgsl.expected.glsl b/test/tint/samples/cube.wgsl.expected.glsl
similarity index 100%
rename from test/samples/cube.wgsl.expected.glsl
rename to test/tint/samples/cube.wgsl.expected.glsl
diff --git a/test/samples/cube.wgsl.expected.hlsl b/test/tint/samples/cube.wgsl.expected.hlsl
similarity index 100%
rename from test/samples/cube.wgsl.expected.hlsl
rename to test/tint/samples/cube.wgsl.expected.hlsl
diff --git a/test/samples/cube.wgsl.expected.msl b/test/tint/samples/cube.wgsl.expected.msl
similarity index 100%
rename from test/samples/cube.wgsl.expected.msl
rename to test/tint/samples/cube.wgsl.expected.msl
diff --git a/test/samples/cube.wgsl.expected.spvasm b/test/tint/samples/cube.wgsl.expected.spvasm
similarity index 100%
rename from test/samples/cube.wgsl.expected.spvasm
rename to test/tint/samples/cube.wgsl.expected.spvasm
diff --git a/test/samples/cube.wgsl.expected.wgsl b/test/tint/samples/cube.wgsl.expected.wgsl
similarity index 100%
rename from test/samples/cube.wgsl.expected.wgsl
rename to test/tint/samples/cube.wgsl.expected.wgsl
diff --git a/test/samples/function.wgsl b/test/tint/samples/function.wgsl
similarity index 100%
rename from test/samples/function.wgsl
rename to test/tint/samples/function.wgsl
diff --git a/test/samples/function.wgsl.expected.glsl b/test/tint/samples/function.wgsl.expected.glsl
similarity index 100%
rename from test/samples/function.wgsl.expected.glsl
rename to test/tint/samples/function.wgsl.expected.glsl
diff --git a/test/samples/function.wgsl.expected.hlsl b/test/tint/samples/function.wgsl.expected.hlsl
similarity index 100%
rename from test/samples/function.wgsl.expected.hlsl
rename to test/tint/samples/function.wgsl.expected.hlsl
diff --git a/test/samples/function.wgsl.expected.msl b/test/tint/samples/function.wgsl.expected.msl
similarity index 100%
rename from test/samples/function.wgsl.expected.msl
rename to test/tint/samples/function.wgsl.expected.msl
diff --git a/test/samples/function.wgsl.expected.spvasm b/test/tint/samples/function.wgsl.expected.spvasm
similarity index 100%
rename from test/samples/function.wgsl.expected.spvasm
rename to test/tint/samples/function.wgsl.expected.spvasm
diff --git a/test/samples/function.wgsl.expected.wgsl b/test/tint/samples/function.wgsl.expected.wgsl
similarity index 100%
rename from test/samples/function.wgsl.expected.wgsl
rename to test/tint/samples/function.wgsl.expected.wgsl
diff --git a/test/samples/simple.wgsl b/test/tint/samples/simple.wgsl
similarity index 100%
rename from test/samples/simple.wgsl
rename to test/tint/samples/simple.wgsl
diff --git a/test/samples/simple.wgsl.expected.glsl b/test/tint/samples/simple.wgsl.expected.glsl
similarity index 100%
rename from test/samples/simple.wgsl.expected.glsl
rename to test/tint/samples/simple.wgsl.expected.glsl
diff --git a/test/samples/simple.wgsl.expected.hlsl b/test/tint/samples/simple.wgsl.expected.hlsl
similarity index 100%
rename from test/samples/simple.wgsl.expected.hlsl
rename to test/tint/samples/simple.wgsl.expected.hlsl
diff --git a/test/samples/simple.wgsl.expected.msl b/test/tint/samples/simple.wgsl.expected.msl
similarity index 100%
rename from test/samples/simple.wgsl.expected.msl
rename to test/tint/samples/simple.wgsl.expected.msl
diff --git a/test/samples/simple.wgsl.expected.spvasm b/test/tint/samples/simple.wgsl.expected.spvasm
similarity index 100%
rename from test/samples/simple.wgsl.expected.spvasm
rename to test/tint/samples/simple.wgsl.expected.spvasm
diff --git a/test/samples/simple.wgsl.expected.wgsl b/test/tint/samples/simple.wgsl.expected.wgsl
similarity index 100%
rename from test/samples/simple.wgsl.expected.wgsl
rename to test/tint/samples/simple.wgsl.expected.wgsl
diff --git a/test/samples/simple_vertex.spvasm b/test/tint/samples/simple_vertex.spvasm
similarity index 100%
rename from test/samples/simple_vertex.spvasm
rename to test/tint/samples/simple_vertex.spvasm
diff --git a/test/samples/simple_vertex.spvasm.expected.glsl b/test/tint/samples/simple_vertex.spvasm.expected.glsl
similarity index 100%
rename from test/samples/simple_vertex.spvasm.expected.glsl
rename to test/tint/samples/simple_vertex.spvasm.expected.glsl
diff --git a/test/samples/simple_vertex.spvasm.expected.hlsl b/test/tint/samples/simple_vertex.spvasm.expected.hlsl
similarity index 100%
rename from test/samples/simple_vertex.spvasm.expected.hlsl
rename to test/tint/samples/simple_vertex.spvasm.expected.hlsl
diff --git a/test/samples/simple_vertex.spvasm.expected.msl b/test/tint/samples/simple_vertex.spvasm.expected.msl
similarity index 100%
rename from test/samples/simple_vertex.spvasm.expected.msl
rename to test/tint/samples/simple_vertex.spvasm.expected.msl
diff --git a/test/samples/simple_vertex.spvasm.expected.spvasm b/test/tint/samples/simple_vertex.spvasm.expected.spvasm
similarity index 100%
rename from test/samples/simple_vertex.spvasm.expected.spvasm
rename to test/tint/samples/simple_vertex.spvasm.expected.spvasm
diff --git a/test/samples/simple_vertex.spvasm.expected.wgsl b/test/tint/samples/simple_vertex.spvasm.expected.wgsl
similarity index 100%
rename from test/samples/simple_vertex.spvasm.expected.wgsl
rename to test/tint/samples/simple_vertex.spvasm.expected.wgsl
diff --git a/test/samples/triangle.wgsl b/test/tint/samples/triangle.wgsl
similarity index 100%
rename from test/samples/triangle.wgsl
rename to test/tint/samples/triangle.wgsl
diff --git a/test/samples/triangle.wgsl.expected.glsl b/test/tint/samples/triangle.wgsl.expected.glsl
similarity index 100%
rename from test/samples/triangle.wgsl.expected.glsl
rename to test/tint/samples/triangle.wgsl.expected.glsl
diff --git a/test/samples/triangle.wgsl.expected.hlsl b/test/tint/samples/triangle.wgsl.expected.hlsl
similarity index 100%
rename from test/samples/triangle.wgsl.expected.hlsl
rename to test/tint/samples/triangle.wgsl.expected.hlsl
diff --git a/test/samples/triangle.wgsl.expected.msl b/test/tint/samples/triangle.wgsl.expected.msl
similarity index 100%
rename from test/samples/triangle.wgsl.expected.msl
rename to test/tint/samples/triangle.wgsl.expected.msl
diff --git a/test/samples/triangle.wgsl.expected.spvasm b/test/tint/samples/triangle.wgsl.expected.spvasm
similarity index 100%
rename from test/samples/triangle.wgsl.expected.spvasm
rename to test/tint/samples/triangle.wgsl.expected.spvasm
diff --git a/test/samples/triangle.wgsl.expected.wgsl b/test/tint/samples/triangle.wgsl.expected.wgsl
similarity index 100%
rename from test/samples/triangle.wgsl.expected.wgsl
rename to test/tint/samples/triangle.wgsl.expected.wgsl
diff --git a/test/shader_io/compute_input_builtins.wgsl b/test/tint/shader_io/compute_input_builtins.wgsl
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl
rename to test/tint/shader_io/compute_input_builtins.wgsl
diff --git a/test/shader_io/compute_input_builtins.wgsl.expected.glsl b/test/tint/shader_io/compute_input_builtins.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl.expected.glsl
rename to test/tint/shader_io/compute_input_builtins.wgsl.expected.glsl
diff --git a/test/shader_io/compute_input_builtins.wgsl.expected.hlsl b/test/tint/shader_io/compute_input_builtins.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl.expected.hlsl
rename to test/tint/shader_io/compute_input_builtins.wgsl.expected.hlsl
diff --git a/test/shader_io/compute_input_builtins.wgsl.expected.msl b/test/tint/shader_io/compute_input_builtins.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl.expected.msl
rename to test/tint/shader_io/compute_input_builtins.wgsl.expected.msl
diff --git a/test/shader_io/compute_input_builtins.wgsl.expected.spvasm b/test/tint/shader_io/compute_input_builtins.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl.expected.spvasm
rename to test/tint/shader_io/compute_input_builtins.wgsl.expected.spvasm
diff --git a/test/shader_io/compute_input_builtins.wgsl.expected.wgsl b/test/tint/shader_io/compute_input_builtins.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/compute_input_builtins.wgsl.expected.wgsl
rename to test/tint/shader_io/compute_input_builtins.wgsl.expected.wgsl
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl b/test/tint/shader_io/compute_input_builtins_struct.wgsl
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl.expected.glsl b/test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl.expected.glsl
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.glsl
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl.expected.hlsl b/test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl.expected.msl b/test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl.expected.msl
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.msl
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl.expected.spvasm b/test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/compute_input_builtins_struct.wgsl.expected.wgsl b/test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/compute_input_builtins_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/compute_input_builtins_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/compute_input_mixed.wgsl b/test/tint/shader_io/compute_input_mixed.wgsl
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl
rename to test/tint/shader_io/compute_input_mixed.wgsl
diff --git a/test/shader_io/compute_input_mixed.wgsl.expected.glsl b/test/tint/shader_io/compute_input_mixed.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl.expected.glsl
rename to test/tint/shader_io/compute_input_mixed.wgsl.expected.glsl
diff --git a/test/shader_io/compute_input_mixed.wgsl.expected.hlsl b/test/tint/shader_io/compute_input_mixed.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl.expected.hlsl
rename to test/tint/shader_io/compute_input_mixed.wgsl.expected.hlsl
diff --git a/test/shader_io/compute_input_mixed.wgsl.expected.msl b/test/tint/shader_io/compute_input_mixed.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl.expected.msl
rename to test/tint/shader_io/compute_input_mixed.wgsl.expected.msl
diff --git a/test/shader_io/compute_input_mixed.wgsl.expected.spvasm b/test/tint/shader_io/compute_input_mixed.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl.expected.spvasm
rename to test/tint/shader_io/compute_input_mixed.wgsl.expected.spvasm
diff --git a/test/shader_io/compute_input_mixed.wgsl.expected.wgsl b/test/tint/shader_io/compute_input_mixed.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/compute_input_mixed.wgsl.expected.wgsl
rename to test/tint/shader_io/compute_input_mixed.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_input_builtins.wgsl b/test/tint/shader_io/fragment_input_builtins.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl
rename to test/tint/shader_io/fragment_input_builtins.wgsl
diff --git a/test/shader_io/fragment_input_builtins.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_builtins.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_input_builtins.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_input_builtins.wgsl.expected.hlsl b/test/tint/shader_io/fragment_input_builtins.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_input_builtins.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_input_builtins.wgsl.expected.msl b/test/tint/shader_io/fragment_input_builtins.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl.expected.msl
rename to test/tint/shader_io/fragment_input_builtins.wgsl.expected.msl
diff --git a/test/shader_io/fragment_input_builtins.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_builtins.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_input_builtins.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_input_builtins.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_builtins.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_input_builtins.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl b/test/tint/shader_io/fragment_input_builtins_struct.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl.expected.hlsl b/test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl.expected.msl b/test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl.expected.msl
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.msl
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_input_builtins_struct.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_builtins_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_input_builtins_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_input_locations.wgsl b/test/tint/shader_io/fragment_input_locations.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl
rename to test/tint/shader_io/fragment_input_locations.wgsl
diff --git a/test/shader_io/fragment_input_locations.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_locations.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_input_locations.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_input_locations.wgsl.expected.hlsl b/test/tint/shader_io/fragment_input_locations.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_input_locations.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_input_locations.wgsl.expected.msl b/test/tint/shader_io/fragment_input_locations.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl.expected.msl
rename to test/tint/shader_io/fragment_input_locations.wgsl.expected.msl
diff --git a/test/shader_io/fragment_input_locations.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_locations.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_input_locations.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_input_locations.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_locations.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_locations.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_input_locations.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl b/test/tint/shader_io/fragment_input_locations_struct.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl.expected.hlsl b/test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl.expected.msl b/test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl.expected.msl
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.msl
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_input_locations_struct.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_locations_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_input_locations_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_input_mixed.wgsl b/test/tint/shader_io/fragment_input_mixed.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl
rename to test/tint/shader_io/fragment_input_mixed.wgsl
diff --git a/test/shader_io/fragment_input_mixed.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_mixed.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_input_mixed.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_input_mixed.wgsl.expected.hlsl b/test/tint/shader_io/fragment_input_mixed.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_input_mixed.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_input_mixed.wgsl.expected.msl b/test/tint/shader_io/fragment_input_mixed.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl.expected.msl
rename to test/tint/shader_io/fragment_input_mixed.wgsl.expected.msl
diff --git a/test/shader_io/fragment_input_mixed.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_mixed.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_input_mixed.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_input_mixed.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_mixed.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_input_mixed.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_input_mixed.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_output_builtins.wgsl b/test/tint/shader_io/fragment_output_builtins.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl
rename to test/tint/shader_io/fragment_output_builtins.wgsl
diff --git a/test/shader_io/fragment_output_builtins.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_builtins.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_output_builtins.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_output_builtins.wgsl.expected.hlsl b/test/tint/shader_io/fragment_output_builtins.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_output_builtins.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_output_builtins.wgsl.expected.msl b/test/tint/shader_io/fragment_output_builtins.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl.expected.msl
rename to test/tint/shader_io/fragment_output_builtins.wgsl.expected.msl
diff --git a/test/shader_io/fragment_output_builtins.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_builtins.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_output_builtins.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_output_builtins.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_builtins.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_output_builtins.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl b/test/tint/shader_io/fragment_output_builtins_struct.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl.expected.hlsl b/test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl.expected.msl b/test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl.expected.msl
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.msl
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_output_builtins_struct.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_builtins_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_output_builtins_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_output_locations.wgsl b/test/tint/shader_io/fragment_output_locations.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl
rename to test/tint/shader_io/fragment_output_locations.wgsl
diff --git a/test/shader_io/fragment_output_locations.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_locations.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_output_locations.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_output_locations.wgsl.expected.hlsl b/test/tint/shader_io/fragment_output_locations.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_output_locations.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_output_locations.wgsl.expected.msl b/test/tint/shader_io/fragment_output_locations.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl.expected.msl
rename to test/tint/shader_io/fragment_output_locations.wgsl.expected.msl
diff --git a/test/shader_io/fragment_output_locations.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_locations.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_output_locations.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_output_locations.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_locations.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_locations.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_output_locations.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl b/test/tint/shader_io/fragment_output_locations_struct.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl.expected.hlsl b/test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl.expected.msl b/test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl.expected.msl
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.msl
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_output_locations_struct.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_locations_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_output_locations_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/fragment_output_mixed.wgsl b/test/tint/shader_io/fragment_output_mixed.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl
rename to test/tint/shader_io/fragment_output_mixed.wgsl
diff --git a/test/shader_io/fragment_output_mixed.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_mixed.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl.expected.glsl
rename to test/tint/shader_io/fragment_output_mixed.wgsl.expected.glsl
diff --git a/test/shader_io/fragment_output_mixed.wgsl.expected.hlsl b/test/tint/shader_io/fragment_output_mixed.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl.expected.hlsl
rename to test/tint/shader_io/fragment_output_mixed.wgsl.expected.hlsl
diff --git a/test/shader_io/fragment_output_mixed.wgsl.expected.msl b/test/tint/shader_io/fragment_output_mixed.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl.expected.msl
rename to test/tint/shader_io/fragment_output_mixed.wgsl.expected.msl
diff --git a/test/shader_io/fragment_output_mixed.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_mixed.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl.expected.spvasm
rename to test/tint/shader_io/fragment_output_mixed.wgsl.expected.spvasm
diff --git a/test/shader_io/fragment_output_mixed.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_mixed.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/fragment_output_mixed.wgsl.expected.wgsl
rename to test/tint/shader_io/fragment_output_mixed.wgsl.expected.wgsl
diff --git a/test/shader_io/interpolate_input_parameters.wgsl b/test/tint/shader_io/interpolate_input_parameters.wgsl
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl
rename to test/tint/shader_io/interpolate_input_parameters.wgsl
diff --git a/test/shader_io/interpolate_input_parameters.wgsl.expected.glsl b/test/tint/shader_io/interpolate_input_parameters.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl.expected.glsl
rename to test/tint/shader_io/interpolate_input_parameters.wgsl.expected.glsl
diff --git a/test/shader_io/interpolate_input_parameters.wgsl.expected.hlsl b/test/tint/shader_io/interpolate_input_parameters.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl.expected.hlsl
rename to test/tint/shader_io/interpolate_input_parameters.wgsl.expected.hlsl
diff --git a/test/shader_io/interpolate_input_parameters.wgsl.expected.msl b/test/tint/shader_io/interpolate_input_parameters.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl.expected.msl
rename to test/tint/shader_io/interpolate_input_parameters.wgsl.expected.msl
diff --git a/test/shader_io/interpolate_input_parameters.wgsl.expected.spvasm b/test/tint/shader_io/interpolate_input_parameters.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl.expected.spvasm
rename to test/tint/shader_io/interpolate_input_parameters.wgsl.expected.spvasm
diff --git a/test/shader_io/interpolate_input_parameters.wgsl.expected.wgsl b/test/tint/shader_io/interpolate_input_parameters.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/interpolate_input_parameters.wgsl.expected.wgsl
rename to test/tint/shader_io/interpolate_input_parameters.wgsl.expected.wgsl
diff --git a/test/shader_io/interpolate_input_struct.wgsl b/test/tint/shader_io/interpolate_input_struct.wgsl
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl
rename to test/tint/shader_io/interpolate_input_struct.wgsl
diff --git a/test/shader_io/interpolate_input_struct.wgsl.expected.glsl b/test/tint/shader_io/interpolate_input_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl.expected.glsl
rename to test/tint/shader_io/interpolate_input_struct.wgsl.expected.glsl
diff --git a/test/shader_io/interpolate_input_struct.wgsl.expected.hlsl b/test/tint/shader_io/interpolate_input_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/interpolate_input_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/interpolate_input_struct.wgsl.expected.msl b/test/tint/shader_io/interpolate_input_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl.expected.msl
rename to test/tint/shader_io/interpolate_input_struct.wgsl.expected.msl
diff --git a/test/shader_io/interpolate_input_struct.wgsl.expected.spvasm b/test/tint/shader_io/interpolate_input_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/interpolate_input_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/interpolate_input_struct.wgsl.expected.wgsl b/test/tint/shader_io/interpolate_input_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/interpolate_input_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/interpolate_input_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/interpolate_integers.wgsl b/test/tint/shader_io/interpolate_integers.wgsl
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl
rename to test/tint/shader_io/interpolate_integers.wgsl
diff --git a/test/shader_io/interpolate_integers.wgsl.expected.glsl b/test/tint/shader_io/interpolate_integers.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl.expected.glsl
rename to test/tint/shader_io/interpolate_integers.wgsl.expected.glsl
diff --git a/test/shader_io/interpolate_integers.wgsl.expected.hlsl b/test/tint/shader_io/interpolate_integers.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl.expected.hlsl
rename to test/tint/shader_io/interpolate_integers.wgsl.expected.hlsl
diff --git a/test/shader_io/interpolate_integers.wgsl.expected.msl b/test/tint/shader_io/interpolate_integers.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl.expected.msl
rename to test/tint/shader_io/interpolate_integers.wgsl.expected.msl
diff --git a/test/shader_io/interpolate_integers.wgsl.expected.spvasm b/test/tint/shader_io/interpolate_integers.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl.expected.spvasm
rename to test/tint/shader_io/interpolate_integers.wgsl.expected.spvasm
diff --git a/test/shader_io/interpolate_integers.wgsl.expected.wgsl b/test/tint/shader_io/interpolate_integers.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/interpolate_integers.wgsl.expected.wgsl
rename to test/tint/shader_io/interpolate_integers.wgsl.expected.wgsl
diff --git a/test/shader_io/interpolate_return_struct.wgsl b/test/tint/shader_io/interpolate_return_struct.wgsl
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl
rename to test/tint/shader_io/interpolate_return_struct.wgsl
diff --git a/test/shader_io/interpolate_return_struct.wgsl.expected.glsl b/test/tint/shader_io/interpolate_return_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl.expected.glsl
rename to test/tint/shader_io/interpolate_return_struct.wgsl.expected.glsl
diff --git a/test/shader_io/interpolate_return_struct.wgsl.expected.hlsl b/test/tint/shader_io/interpolate_return_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/interpolate_return_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/interpolate_return_struct.wgsl.expected.msl b/test/tint/shader_io/interpolate_return_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl.expected.msl
rename to test/tint/shader_io/interpolate_return_struct.wgsl.expected.msl
diff --git a/test/shader_io/interpolate_return_struct.wgsl.expected.spvasm b/test/tint/shader_io/interpolate_return_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/interpolate_return_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/interpolate_return_struct.wgsl.expected.wgsl b/test/tint/shader_io/interpolate_return_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/interpolate_return_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/interpolate_return_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/invariant.wgsl b/test/tint/shader_io/invariant.wgsl
similarity index 100%
rename from test/shader_io/invariant.wgsl
rename to test/tint/shader_io/invariant.wgsl
diff --git a/test/shader_io/invariant.wgsl.expected.glsl b/test/tint/shader_io/invariant.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/invariant.wgsl.expected.glsl
rename to test/tint/shader_io/invariant.wgsl.expected.glsl
diff --git a/test/shader_io/invariant.wgsl.expected.hlsl b/test/tint/shader_io/invariant.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/invariant.wgsl.expected.hlsl
rename to test/tint/shader_io/invariant.wgsl.expected.hlsl
diff --git a/test/shader_io/invariant.wgsl.expected.msl b/test/tint/shader_io/invariant.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/invariant.wgsl.expected.msl
rename to test/tint/shader_io/invariant.wgsl.expected.msl
diff --git a/test/shader_io/invariant.wgsl.expected.spvasm b/test/tint/shader_io/invariant.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/invariant.wgsl.expected.spvasm
rename to test/tint/shader_io/invariant.wgsl.expected.spvasm
diff --git a/test/shader_io/invariant.wgsl.expected.wgsl b/test/tint/shader_io/invariant.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/invariant.wgsl.expected.wgsl
rename to test/tint/shader_io/invariant.wgsl.expected.wgsl
diff --git a/test/shader_io/invariant_struct_member.wgsl b/test/tint/shader_io/invariant_struct_member.wgsl
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl
rename to test/tint/shader_io/invariant_struct_member.wgsl
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.glsl b/test/tint/shader_io/invariant_struct_member.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl.expected.glsl
rename to test/tint/shader_io/invariant_struct_member.wgsl.expected.glsl
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.hlsl b/test/tint/shader_io/invariant_struct_member.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl.expected.hlsl
rename to test/tint/shader_io/invariant_struct_member.wgsl.expected.hlsl
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.msl b/test/tint/shader_io/invariant_struct_member.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl.expected.msl
rename to test/tint/shader_io/invariant_struct_member.wgsl.expected.msl
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.spvasm b/test/tint/shader_io/invariant_struct_member.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl.expected.spvasm
rename to test/tint/shader_io/invariant_struct_member.wgsl.expected.spvasm
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.wgsl b/test/tint/shader_io/invariant_struct_member.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/invariant_struct_member.wgsl.expected.wgsl
rename to test/tint/shader_io/invariant_struct_member.wgsl.expected.wgsl
diff --git a/test/shader_io/shared_struct_different_stages.wgsl b/test/tint/shader_io/shared_struct_different_stages.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl
rename to test/tint/shader_io/shared_struct_different_stages.wgsl
diff --git a/test/shader_io/shared_struct_different_stages.wgsl.expected.glsl b/test/tint/shader_io/shared_struct_different_stages.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl.expected.glsl
rename to test/tint/shader_io/shared_struct_different_stages.wgsl.expected.glsl
diff --git a/test/shader_io/shared_struct_different_stages.wgsl.expected.hlsl b/test/tint/shader_io/shared_struct_different_stages.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl.expected.hlsl
rename to test/tint/shader_io/shared_struct_different_stages.wgsl.expected.hlsl
diff --git a/test/shader_io/shared_struct_different_stages.wgsl.expected.msl b/test/tint/shader_io/shared_struct_different_stages.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl.expected.msl
rename to test/tint/shader_io/shared_struct_different_stages.wgsl.expected.msl
diff --git a/test/shader_io/shared_struct_different_stages.wgsl.expected.spvasm b/test/tint/shader_io/shared_struct_different_stages.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl.expected.spvasm
rename to test/tint/shader_io/shared_struct_different_stages.wgsl.expected.spvasm
diff --git a/test/shader_io/shared_struct_different_stages.wgsl.expected.wgsl b/test/tint/shader_io/shared_struct_different_stages.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_different_stages.wgsl.expected.wgsl
rename to test/tint/shader_io/shared_struct_different_stages.wgsl.expected.wgsl
diff --git a/test/shader_io/shared_struct_helper_function.wgsl b/test/tint/shader_io/shared_struct_helper_function.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl
rename to test/tint/shader_io/shared_struct_helper_function.wgsl
diff --git a/test/shader_io/shared_struct_helper_function.wgsl.expected.glsl b/test/tint/shader_io/shared_struct_helper_function.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl.expected.glsl
rename to test/tint/shader_io/shared_struct_helper_function.wgsl.expected.glsl
diff --git a/test/shader_io/shared_struct_helper_function.wgsl.expected.hlsl b/test/tint/shader_io/shared_struct_helper_function.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl.expected.hlsl
rename to test/tint/shader_io/shared_struct_helper_function.wgsl.expected.hlsl
diff --git a/test/shader_io/shared_struct_helper_function.wgsl.expected.msl b/test/tint/shader_io/shared_struct_helper_function.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl.expected.msl
rename to test/tint/shader_io/shared_struct_helper_function.wgsl.expected.msl
diff --git a/test/shader_io/shared_struct_helper_function.wgsl.expected.spvasm b/test/tint/shader_io/shared_struct_helper_function.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl.expected.spvasm
rename to test/tint/shader_io/shared_struct_helper_function.wgsl.expected.spvasm
diff --git a/test/shader_io/shared_struct_helper_function.wgsl.expected.wgsl b/test/tint/shader_io/shared_struct_helper_function.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_helper_function.wgsl.expected.wgsl
rename to test/tint/shader_io/shared_struct_helper_function.wgsl.expected.wgsl
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl b/test/tint/shader_io/shared_struct_storage_buffer.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl.expected.glsl b/test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl.expected.glsl
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.glsl
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl.expected.hlsl b/test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl.expected.hlsl
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.hlsl
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl.expected.msl b/test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl.expected.msl
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.msl
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl.expected.spvasm b/test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl.expected.spvasm
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.spvasm
diff --git a/test/shader_io/shared_struct_storage_buffer.wgsl.expected.wgsl b/test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/shared_struct_storage_buffer.wgsl.expected.wgsl
rename to test/tint/shader_io/shared_struct_storage_buffer.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_input_builtins.wgsl b/test/tint/shader_io/vertex_input_builtins.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl
rename to test/tint/shader_io/vertex_input_builtins.wgsl
diff --git a/test/shader_io/vertex_input_builtins.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_builtins.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_input_builtins.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_input_builtins.wgsl.expected.hlsl b/test/tint/shader_io/vertex_input_builtins.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_input_builtins.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_input_builtins.wgsl.expected.msl b/test/tint/shader_io/vertex_input_builtins.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl.expected.msl
rename to test/tint/shader_io/vertex_input_builtins.wgsl.expected.msl
diff --git a/test/shader_io/vertex_input_builtins.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_builtins.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_input_builtins.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_input_builtins.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_builtins.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_input_builtins.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl b/test/tint/shader_io/vertex_input_builtins_struct.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl.expected.hlsl b/test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl.expected.msl b/test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl.expected.msl
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.msl
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_input_builtins_struct.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_builtins_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_input_builtins_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_input_locations.wgsl b/test/tint/shader_io/vertex_input_locations.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl
rename to test/tint/shader_io/vertex_input_locations.wgsl
diff --git a/test/shader_io/vertex_input_locations.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_locations.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_input_locations.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_input_locations.wgsl.expected.hlsl b/test/tint/shader_io/vertex_input_locations.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_input_locations.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_input_locations.wgsl.expected.msl b/test/tint/shader_io/vertex_input_locations.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl.expected.msl
rename to test/tint/shader_io/vertex_input_locations.wgsl.expected.msl
diff --git a/test/shader_io/vertex_input_locations.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_locations.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_input_locations.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_input_locations.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_locations.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_locations.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_input_locations.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl b/test/tint/shader_io/vertex_input_locations_struct.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl.expected.hlsl b/test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl.expected.msl b/test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl.expected.msl
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.msl
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_input_locations_struct.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_locations_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_input_locations_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_input_mixed.wgsl b/test/tint/shader_io/vertex_input_mixed.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl
rename to test/tint/shader_io/vertex_input_mixed.wgsl
diff --git a/test/shader_io/vertex_input_mixed.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_mixed.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_input_mixed.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_input_mixed.wgsl.expected.hlsl b/test/tint/shader_io/vertex_input_mixed.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_input_mixed.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_input_mixed.wgsl.expected.msl b/test/tint/shader_io/vertex_input_mixed.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl.expected.msl
rename to test/tint/shader_io/vertex_input_mixed.wgsl.expected.msl
diff --git a/test/shader_io/vertex_input_mixed.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_mixed.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_input_mixed.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_input_mixed.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_mixed.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_input_mixed.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_input_mixed.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_output_builtins.wgsl b/test/tint/shader_io/vertex_output_builtins.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl
rename to test/tint/shader_io/vertex_output_builtins.wgsl
diff --git a/test/shader_io/vertex_output_builtins.wgsl.expected.glsl b/test/tint/shader_io/vertex_output_builtins.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_output_builtins.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_output_builtins.wgsl.expected.hlsl b/test/tint/shader_io/vertex_output_builtins.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_output_builtins.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_output_builtins.wgsl.expected.msl b/test/tint/shader_io/vertex_output_builtins.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl.expected.msl
rename to test/tint/shader_io/vertex_output_builtins.wgsl.expected.msl
diff --git a/test/shader_io/vertex_output_builtins.wgsl.expected.spvasm b/test/tint/shader_io/vertex_output_builtins.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_output_builtins.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_output_builtins.wgsl.expected.wgsl b/test/tint/shader_io/vertex_output_builtins.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_output_builtins.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl b/test/tint/shader_io/vertex_output_builtins_struct.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl.expected.glsl b/test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl.expected.hlsl b/test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl.expected.msl b/test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl.expected.msl
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.msl
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl.expected.spvasm b/test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_output_builtins_struct.wgsl.expected.wgsl b/test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_builtins_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_output_builtins_struct.wgsl.expected.wgsl
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl b/test/tint/shader_io/vertex_output_locations_struct.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl.expected.glsl b/test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.glsl
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl.expected.glsl
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.glsl
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl.expected.hlsl b/test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl.expected.hlsl
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.hlsl
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl.expected.msl b/test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.msl
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl.expected.msl
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.msl
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl.expected.spvasm b/test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl.expected.spvasm
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.spvasm
diff --git a/test/shader_io/vertex_output_locations_struct.wgsl.expected.wgsl b/test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/shader_io/vertex_output_locations_struct.wgsl.expected.wgsl
rename to test/tint/shader_io/vertex_output_locations_struct.wgsl.expected.wgsl
diff --git a/test/shadowing/alias/let.wgsl b/test/tint/shadowing/alias/let.wgsl
similarity index 100%
rename from test/shadowing/alias/let.wgsl
rename to test/tint/shadowing/alias/let.wgsl
diff --git a/test/shadowing/alias/let.wgsl.expected.glsl b/test/tint/shadowing/alias/let.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/alias/let.wgsl.expected.glsl
rename to test/tint/shadowing/alias/let.wgsl.expected.glsl
diff --git a/test/shadowing/alias/let.wgsl.expected.hlsl b/test/tint/shadowing/alias/let.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/alias/let.wgsl.expected.hlsl
rename to test/tint/shadowing/alias/let.wgsl.expected.hlsl
diff --git a/test/shadowing/alias/let.wgsl.expected.msl b/test/tint/shadowing/alias/let.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/alias/let.wgsl.expected.msl
rename to test/tint/shadowing/alias/let.wgsl.expected.msl
diff --git a/test/shadowing/alias/let.wgsl.expected.spvasm b/test/tint/shadowing/alias/let.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/alias/let.wgsl.expected.spvasm
rename to test/tint/shadowing/alias/let.wgsl.expected.spvasm
diff --git a/test/shadowing/alias/let.wgsl.expected.wgsl b/test/tint/shadowing/alias/let.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/alias/let.wgsl.expected.wgsl
rename to test/tint/shadowing/alias/let.wgsl.expected.wgsl
diff --git a/test/shadowing/alias/param.wgsl b/test/tint/shadowing/alias/param.wgsl
similarity index 100%
rename from test/shadowing/alias/param.wgsl
rename to test/tint/shadowing/alias/param.wgsl
diff --git a/test/shadowing/alias/param.wgsl.expected.glsl b/test/tint/shadowing/alias/param.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/alias/param.wgsl.expected.glsl
rename to test/tint/shadowing/alias/param.wgsl.expected.glsl
diff --git a/test/shadowing/alias/param.wgsl.expected.hlsl b/test/tint/shadowing/alias/param.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/alias/param.wgsl.expected.hlsl
rename to test/tint/shadowing/alias/param.wgsl.expected.hlsl
diff --git a/test/shadowing/alias/param.wgsl.expected.msl b/test/tint/shadowing/alias/param.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/alias/param.wgsl.expected.msl
rename to test/tint/shadowing/alias/param.wgsl.expected.msl
diff --git a/test/shadowing/alias/param.wgsl.expected.spvasm b/test/tint/shadowing/alias/param.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/alias/param.wgsl.expected.spvasm
rename to test/tint/shadowing/alias/param.wgsl.expected.spvasm
diff --git a/test/shadowing/alias/param.wgsl.expected.wgsl b/test/tint/shadowing/alias/param.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/alias/param.wgsl.expected.wgsl
rename to test/tint/shadowing/alias/param.wgsl.expected.wgsl
diff --git a/test/shadowing/alias/var.wgsl b/test/tint/shadowing/alias/var.wgsl
similarity index 100%
rename from test/shadowing/alias/var.wgsl
rename to test/tint/shadowing/alias/var.wgsl
diff --git a/test/shadowing/alias/var.wgsl.expected.glsl b/test/tint/shadowing/alias/var.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/alias/var.wgsl.expected.glsl
rename to test/tint/shadowing/alias/var.wgsl.expected.glsl
diff --git a/test/shadowing/alias/var.wgsl.expected.hlsl b/test/tint/shadowing/alias/var.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/alias/var.wgsl.expected.hlsl
rename to test/tint/shadowing/alias/var.wgsl.expected.hlsl
diff --git a/test/shadowing/alias/var.wgsl.expected.msl b/test/tint/shadowing/alias/var.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/alias/var.wgsl.expected.msl
rename to test/tint/shadowing/alias/var.wgsl.expected.msl
diff --git a/test/shadowing/alias/var.wgsl.expected.spvasm b/test/tint/shadowing/alias/var.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/alias/var.wgsl.expected.spvasm
rename to test/tint/shadowing/alias/var.wgsl.expected.spvasm
diff --git a/test/shadowing/alias/var.wgsl.expected.wgsl b/test/tint/shadowing/alias/var.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/alias/var.wgsl.expected.wgsl
rename to test/tint/shadowing/alias/var.wgsl.expected.wgsl
diff --git a/test/shadowing/function/let.wgsl b/test/tint/shadowing/function/let.wgsl
similarity index 100%
rename from test/shadowing/function/let.wgsl
rename to test/tint/shadowing/function/let.wgsl
diff --git a/test/shadowing/function/let.wgsl.expected.glsl b/test/tint/shadowing/function/let.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/function/let.wgsl.expected.glsl
rename to test/tint/shadowing/function/let.wgsl.expected.glsl
diff --git a/test/shadowing/function/let.wgsl.expected.hlsl b/test/tint/shadowing/function/let.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/function/let.wgsl.expected.hlsl
rename to test/tint/shadowing/function/let.wgsl.expected.hlsl
diff --git a/test/shadowing/function/let.wgsl.expected.msl b/test/tint/shadowing/function/let.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/function/let.wgsl.expected.msl
rename to test/tint/shadowing/function/let.wgsl.expected.msl
diff --git a/test/shadowing/function/let.wgsl.expected.spvasm b/test/tint/shadowing/function/let.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/function/let.wgsl.expected.spvasm
rename to test/tint/shadowing/function/let.wgsl.expected.spvasm
diff --git a/test/shadowing/function/let.wgsl.expected.wgsl b/test/tint/shadowing/function/let.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/function/let.wgsl.expected.wgsl
rename to test/tint/shadowing/function/let.wgsl.expected.wgsl
diff --git a/test/shadowing/function/param.wgsl b/test/tint/shadowing/function/param.wgsl
similarity index 100%
rename from test/shadowing/function/param.wgsl
rename to test/tint/shadowing/function/param.wgsl
diff --git a/test/shadowing/function/param.wgsl.expected.glsl b/test/tint/shadowing/function/param.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/function/param.wgsl.expected.glsl
rename to test/tint/shadowing/function/param.wgsl.expected.glsl
diff --git a/test/shadowing/function/param.wgsl.expected.hlsl b/test/tint/shadowing/function/param.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/function/param.wgsl.expected.hlsl
rename to test/tint/shadowing/function/param.wgsl.expected.hlsl
diff --git a/test/shadowing/function/param.wgsl.expected.msl b/test/tint/shadowing/function/param.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/function/param.wgsl.expected.msl
rename to test/tint/shadowing/function/param.wgsl.expected.msl
diff --git a/test/shadowing/function/param.wgsl.expected.spvasm b/test/tint/shadowing/function/param.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/function/param.wgsl.expected.spvasm
rename to test/tint/shadowing/function/param.wgsl.expected.spvasm
diff --git a/test/shadowing/function/param.wgsl.expected.wgsl b/test/tint/shadowing/function/param.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/function/param.wgsl.expected.wgsl
rename to test/tint/shadowing/function/param.wgsl.expected.wgsl
diff --git a/test/shadowing/function/var.wgsl b/test/tint/shadowing/function/var.wgsl
similarity index 100%
rename from test/shadowing/function/var.wgsl
rename to test/tint/shadowing/function/var.wgsl
diff --git a/test/shadowing/function/var.wgsl.expected.glsl b/test/tint/shadowing/function/var.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/function/var.wgsl.expected.glsl
rename to test/tint/shadowing/function/var.wgsl.expected.glsl
diff --git a/test/shadowing/function/var.wgsl.expected.hlsl b/test/tint/shadowing/function/var.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/function/var.wgsl.expected.hlsl
rename to test/tint/shadowing/function/var.wgsl.expected.hlsl
diff --git a/test/shadowing/function/var.wgsl.expected.msl b/test/tint/shadowing/function/var.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/function/var.wgsl.expected.msl
rename to test/tint/shadowing/function/var.wgsl.expected.msl
diff --git a/test/shadowing/function/var.wgsl.expected.spvasm b/test/tint/shadowing/function/var.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/function/var.wgsl.expected.spvasm
rename to test/tint/shadowing/function/var.wgsl.expected.spvasm
diff --git a/test/shadowing/function/var.wgsl.expected.wgsl b/test/tint/shadowing/function/var.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/function/var.wgsl.expected.wgsl
rename to test/tint/shadowing/function/var.wgsl.expected.wgsl
diff --git a/test/shadowing/param/function.wgsl b/test/tint/shadowing/param/function.wgsl
similarity index 100%
rename from test/shadowing/param/function.wgsl
rename to test/tint/shadowing/param/function.wgsl
diff --git a/test/shadowing/param/function.wgsl.expected.glsl b/test/tint/shadowing/param/function.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/param/function.wgsl.expected.glsl
rename to test/tint/shadowing/param/function.wgsl.expected.glsl
diff --git a/test/shadowing/param/function.wgsl.expected.hlsl b/test/tint/shadowing/param/function.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/param/function.wgsl.expected.hlsl
rename to test/tint/shadowing/param/function.wgsl.expected.hlsl
diff --git a/test/shadowing/param/function.wgsl.expected.msl b/test/tint/shadowing/param/function.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/param/function.wgsl.expected.msl
rename to test/tint/shadowing/param/function.wgsl.expected.msl
diff --git a/test/shadowing/param/function.wgsl.expected.spvasm b/test/tint/shadowing/param/function.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/param/function.wgsl.expected.spvasm
rename to test/tint/shadowing/param/function.wgsl.expected.spvasm
diff --git a/test/shadowing/param/function.wgsl.expected.wgsl b/test/tint/shadowing/param/function.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/param/function.wgsl.expected.wgsl
rename to test/tint/shadowing/param/function.wgsl.expected.wgsl
diff --git a/test/shadowing/param/let.wgsl b/test/tint/shadowing/param/let.wgsl
similarity index 100%
rename from test/shadowing/param/let.wgsl
rename to test/tint/shadowing/param/let.wgsl
diff --git a/test/shadowing/param/let.wgsl.expected.glsl b/test/tint/shadowing/param/let.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/param/let.wgsl.expected.glsl
rename to test/tint/shadowing/param/let.wgsl.expected.glsl
diff --git a/test/shadowing/param/let.wgsl.expected.hlsl b/test/tint/shadowing/param/let.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/param/let.wgsl.expected.hlsl
rename to test/tint/shadowing/param/let.wgsl.expected.hlsl
diff --git a/test/shadowing/param/let.wgsl.expected.msl b/test/tint/shadowing/param/let.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/param/let.wgsl.expected.msl
rename to test/tint/shadowing/param/let.wgsl.expected.msl
diff --git a/test/shadowing/param/let.wgsl.expected.spvasm b/test/tint/shadowing/param/let.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/param/let.wgsl.expected.spvasm
rename to test/tint/shadowing/param/let.wgsl.expected.spvasm
diff --git a/test/shadowing/param/let.wgsl.expected.wgsl b/test/tint/shadowing/param/let.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/param/let.wgsl.expected.wgsl
rename to test/tint/shadowing/param/let.wgsl.expected.wgsl
diff --git a/test/shadowing/param/var.wgsl b/test/tint/shadowing/param/var.wgsl
similarity index 100%
rename from test/shadowing/param/var.wgsl
rename to test/tint/shadowing/param/var.wgsl
diff --git a/test/shadowing/param/var.wgsl.expected.glsl b/test/tint/shadowing/param/var.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/param/var.wgsl.expected.glsl
rename to test/tint/shadowing/param/var.wgsl.expected.glsl
diff --git a/test/shadowing/param/var.wgsl.expected.hlsl b/test/tint/shadowing/param/var.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/param/var.wgsl.expected.hlsl
rename to test/tint/shadowing/param/var.wgsl.expected.hlsl
diff --git a/test/shadowing/param/var.wgsl.expected.msl b/test/tint/shadowing/param/var.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/param/var.wgsl.expected.msl
rename to test/tint/shadowing/param/var.wgsl.expected.msl
diff --git a/test/shadowing/param/var.wgsl.expected.spvasm b/test/tint/shadowing/param/var.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/param/var.wgsl.expected.spvasm
rename to test/tint/shadowing/param/var.wgsl.expected.spvasm
diff --git a/test/shadowing/param/var.wgsl.expected.wgsl b/test/tint/shadowing/param/var.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/param/var.wgsl.expected.wgsl
rename to test/tint/shadowing/param/var.wgsl.expected.wgsl
diff --git a/test/shadowing/struct/let.wgsl b/test/tint/shadowing/struct/let.wgsl
similarity index 100%
rename from test/shadowing/struct/let.wgsl
rename to test/tint/shadowing/struct/let.wgsl
diff --git a/test/shadowing/struct/let.wgsl.expected.glsl b/test/tint/shadowing/struct/let.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/struct/let.wgsl.expected.glsl
rename to test/tint/shadowing/struct/let.wgsl.expected.glsl
diff --git a/test/shadowing/struct/let.wgsl.expected.hlsl b/test/tint/shadowing/struct/let.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/struct/let.wgsl.expected.hlsl
rename to test/tint/shadowing/struct/let.wgsl.expected.hlsl
diff --git a/test/shadowing/struct/let.wgsl.expected.msl b/test/tint/shadowing/struct/let.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/struct/let.wgsl.expected.msl
rename to test/tint/shadowing/struct/let.wgsl.expected.msl
diff --git a/test/shadowing/struct/let.wgsl.expected.spvasm b/test/tint/shadowing/struct/let.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/struct/let.wgsl.expected.spvasm
rename to test/tint/shadowing/struct/let.wgsl.expected.spvasm
diff --git a/test/shadowing/struct/let.wgsl.expected.wgsl b/test/tint/shadowing/struct/let.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/struct/let.wgsl.expected.wgsl
rename to test/tint/shadowing/struct/let.wgsl.expected.wgsl
diff --git a/test/shadowing/struct/param.wgsl b/test/tint/shadowing/struct/param.wgsl
similarity index 100%
rename from test/shadowing/struct/param.wgsl
rename to test/tint/shadowing/struct/param.wgsl
diff --git a/test/shadowing/struct/param.wgsl.expected.glsl b/test/tint/shadowing/struct/param.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/struct/param.wgsl.expected.glsl
rename to test/tint/shadowing/struct/param.wgsl.expected.glsl
diff --git a/test/shadowing/struct/param.wgsl.expected.hlsl b/test/tint/shadowing/struct/param.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/struct/param.wgsl.expected.hlsl
rename to test/tint/shadowing/struct/param.wgsl.expected.hlsl
diff --git a/test/shadowing/struct/param.wgsl.expected.msl b/test/tint/shadowing/struct/param.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/struct/param.wgsl.expected.msl
rename to test/tint/shadowing/struct/param.wgsl.expected.msl
diff --git a/test/shadowing/struct/param.wgsl.expected.spvasm b/test/tint/shadowing/struct/param.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/struct/param.wgsl.expected.spvasm
rename to test/tint/shadowing/struct/param.wgsl.expected.spvasm
diff --git a/test/shadowing/struct/param.wgsl.expected.wgsl b/test/tint/shadowing/struct/param.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/struct/param.wgsl.expected.wgsl
rename to test/tint/shadowing/struct/param.wgsl.expected.wgsl
diff --git a/test/shadowing/struct/var.wgsl b/test/tint/shadowing/struct/var.wgsl
similarity index 100%
rename from test/shadowing/struct/var.wgsl
rename to test/tint/shadowing/struct/var.wgsl
diff --git a/test/shadowing/struct/var.wgsl.expected.glsl b/test/tint/shadowing/struct/var.wgsl.expected.glsl
similarity index 100%
rename from test/shadowing/struct/var.wgsl.expected.glsl
rename to test/tint/shadowing/struct/var.wgsl.expected.glsl
diff --git a/test/shadowing/struct/var.wgsl.expected.hlsl b/test/tint/shadowing/struct/var.wgsl.expected.hlsl
similarity index 100%
rename from test/shadowing/struct/var.wgsl.expected.hlsl
rename to test/tint/shadowing/struct/var.wgsl.expected.hlsl
diff --git a/test/shadowing/struct/var.wgsl.expected.msl b/test/tint/shadowing/struct/var.wgsl.expected.msl
similarity index 100%
rename from test/shadowing/struct/var.wgsl.expected.msl
rename to test/tint/shadowing/struct/var.wgsl.expected.msl
diff --git a/test/shadowing/struct/var.wgsl.expected.spvasm b/test/tint/shadowing/struct/var.wgsl.expected.spvasm
similarity index 100%
rename from test/shadowing/struct/var.wgsl.expected.spvasm
rename to test/tint/shadowing/struct/var.wgsl.expected.spvasm
diff --git a/test/shadowing/struct/var.wgsl.expected.wgsl b/test/tint/shadowing/struct/var.wgsl.expected.wgsl
similarity index 100%
rename from test/shadowing/struct/var.wgsl.expected.wgsl
rename to test/tint/shadowing/struct/var.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_body.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_continuing.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/in_for_loop_init.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/indexing_with_side_effect_func.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_array_struct_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_dynamic_array_struct_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_matrix.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_multiple_arrays.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_struct_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/struct_vector.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/vector_assign.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer.wgsl.expected.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.glsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.glsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.glsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.hlsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.hlsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.hlsl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.msl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.msl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.msl
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.spvasm b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.spvasm
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.spvasm
diff --git a/test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.wgsl b/test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.wgsl
rename to test/tint/statements/assign/indexed_assign_to_array_in_struct/via_pointer_arg.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.glsl b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.hlsl b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.msl b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.msl
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.msl
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.spvasm b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.wgsl b/test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_non_constructable.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/addr_of_non_constructable.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.glsl b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.hlsl b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.msl b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.msl
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.msl
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.spvasm b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.wgsl b/test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/addr_of_runtime_array.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/addr_of_runtime_array.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/call.wgsl b/test/tint/statements/assign/phony/call.wgsl
similarity index 100%
rename from test/statements/assign/phony/call.wgsl
rename to test/tint/statements/assign/phony/call.wgsl
diff --git a/test/statements/assign/phony/call.wgsl.expected.glsl b/test/tint/statements/assign/phony/call.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/call.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/call.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/call.wgsl.expected.hlsl b/test/tint/statements/assign/phony/call.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/call.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/call.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/call.wgsl.expected.msl b/test/tint/statements/assign/phony/call.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/call.wgsl.expected.msl
rename to test/tint/statements/assign/phony/call.wgsl.expected.msl
diff --git a/test/statements/assign/phony/call.wgsl.expected.spvasm b/test/tint/statements/assign/phony/call.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/call.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/call.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/call.wgsl.expected.wgsl b/test/tint/statements/assign/phony/call.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/call.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/call.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl b/test/tint/statements/assign/phony/multiple_side_effects.wgsl
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl.expected.glsl b/test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl.expected.hlsl b/test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl.expected.msl b/test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl.expected.msl
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.msl
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl.expected.spvasm b/test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/multiple_side_effects.wgsl.expected.wgsl b/test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/multiple_side_effects.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/multiple_side_effects.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/storage_buffer.wgsl b/test/tint/statements/assign/phony/storage_buffer.wgsl
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl
rename to test/tint/statements/assign/phony/storage_buffer.wgsl
diff --git a/test/statements/assign/phony/storage_buffer.wgsl.expected.glsl b/test/tint/statements/assign/phony/storage_buffer.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/storage_buffer.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/storage_buffer.wgsl.expected.hlsl b/test/tint/statements/assign/phony/storage_buffer.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/storage_buffer.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/storage_buffer.wgsl.expected.msl b/test/tint/statements/assign/phony/storage_buffer.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl.expected.msl
rename to test/tint/statements/assign/phony/storage_buffer.wgsl.expected.msl
diff --git a/test/statements/assign/phony/storage_buffer.wgsl.expected.spvasm b/test/tint/statements/assign/phony/storage_buffer.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/storage_buffer.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/storage_buffer.wgsl.expected.wgsl b/test/tint/statements/assign/phony/storage_buffer.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/storage_buffer.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/storage_buffer.wgsl.expected.wgsl
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl b/test/tint/statements/assign/phony/uniform_buffer.wgsl
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl.expected.glsl b/test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.glsl
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl.expected.glsl
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.glsl
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl.expected.hlsl b/test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl.expected.hlsl
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.hlsl
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl.expected.msl b/test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.msl
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl.expected.msl
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.msl
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl.expected.spvasm b/test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl.expected.spvasm
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.spvasm
diff --git a/test/statements/assign/phony/uniform_buffer.wgsl.expected.wgsl b/test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/assign/phony/uniform_buffer.wgsl.expected.wgsl
rename to test/tint/statements/assign/phony/uniform_buffer.wgsl.expected.wgsl
diff --git a/test/statements/for/basic.wgsl b/test/tint/statements/for/basic.wgsl
similarity index 100%
rename from test/statements/for/basic.wgsl
rename to test/tint/statements/for/basic.wgsl
diff --git a/test/statements/for/basic.wgsl.expected.glsl b/test/tint/statements/for/basic.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/basic.wgsl.expected.glsl
rename to test/tint/statements/for/basic.wgsl.expected.glsl
diff --git a/test/statements/for/basic.wgsl.expected.hlsl b/test/tint/statements/for/basic.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/basic.wgsl.expected.hlsl
rename to test/tint/statements/for/basic.wgsl.expected.hlsl
diff --git a/test/statements/for/basic.wgsl.expected.msl b/test/tint/statements/for/basic.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/basic.wgsl.expected.msl
rename to test/tint/statements/for/basic.wgsl.expected.msl
diff --git a/test/statements/for/basic.wgsl.expected.spvasm b/test/tint/statements/for/basic.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/basic.wgsl.expected.spvasm
rename to test/tint/statements/for/basic.wgsl.expected.spvasm
diff --git a/test/statements/for/basic.wgsl.expected.wgsl b/test/tint/statements/for/basic.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/basic.wgsl.expected.wgsl
rename to test/tint/statements/for/basic.wgsl.expected.wgsl
diff --git a/test/statements/for/complex.wgsl b/test/tint/statements/for/complex.wgsl
similarity index 100%
rename from test/statements/for/complex.wgsl
rename to test/tint/statements/for/complex.wgsl
diff --git a/test/statements/for/complex.wgsl.expected.glsl b/test/tint/statements/for/complex.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/complex.wgsl.expected.glsl
rename to test/tint/statements/for/complex.wgsl.expected.glsl
diff --git a/test/statements/for/complex.wgsl.expected.hlsl b/test/tint/statements/for/complex.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/complex.wgsl.expected.hlsl
rename to test/tint/statements/for/complex.wgsl.expected.hlsl
diff --git a/test/statements/for/complex.wgsl.expected.msl b/test/tint/statements/for/complex.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/complex.wgsl.expected.msl
rename to test/tint/statements/for/complex.wgsl.expected.msl
diff --git a/test/statements/for/complex.wgsl.expected.spvasm b/test/tint/statements/for/complex.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/complex.wgsl.expected.spvasm
rename to test/tint/statements/for/complex.wgsl.expected.spvasm
diff --git a/test/statements/for/complex.wgsl.expected.wgsl b/test/tint/statements/for/complex.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/complex.wgsl.expected.wgsl
rename to test/tint/statements/for/complex.wgsl.expected.wgsl
diff --git a/test/statements/for/condition/array_ctor.wgsl b/test/tint/statements/for/condition/array_ctor.wgsl
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl
rename to test/tint/statements/for/condition/array_ctor.wgsl
diff --git a/test/statements/for/condition/array_ctor.wgsl.expected.glsl b/test/tint/statements/for/condition/array_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/condition/array_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/condition/array_ctor.wgsl.expected.hlsl b/test/tint/statements/for/condition/array_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/condition/array_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/condition/array_ctor.wgsl.expected.msl b/test/tint/statements/for/condition/array_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl.expected.msl
rename to test/tint/statements/for/condition/array_ctor.wgsl.expected.msl
diff --git a/test/statements/for/condition/array_ctor.wgsl.expected.spvasm b/test/tint/statements/for/condition/array_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/condition/array_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/condition/array_ctor.wgsl.expected.wgsl b/test/tint/statements/for/condition/array_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/condition/array_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/condition/array_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/condition/basic.wgsl b/test/tint/statements/for/condition/basic.wgsl
similarity index 100%
rename from test/statements/for/condition/basic.wgsl
rename to test/tint/statements/for/condition/basic.wgsl
diff --git a/test/statements/for/condition/basic.wgsl.expected.glsl b/test/tint/statements/for/condition/basic.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/condition/basic.wgsl.expected.glsl
rename to test/tint/statements/for/condition/basic.wgsl.expected.glsl
diff --git a/test/statements/for/condition/basic.wgsl.expected.hlsl b/test/tint/statements/for/condition/basic.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/condition/basic.wgsl.expected.hlsl
rename to test/tint/statements/for/condition/basic.wgsl.expected.hlsl
diff --git a/test/statements/for/condition/basic.wgsl.expected.msl b/test/tint/statements/for/condition/basic.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/condition/basic.wgsl.expected.msl
rename to test/tint/statements/for/condition/basic.wgsl.expected.msl
diff --git a/test/statements/for/condition/basic.wgsl.expected.spvasm b/test/tint/statements/for/condition/basic.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/condition/basic.wgsl.expected.spvasm
rename to test/tint/statements/for/condition/basic.wgsl.expected.spvasm
diff --git a/test/statements/for/condition/basic.wgsl.expected.wgsl b/test/tint/statements/for/condition/basic.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/condition/basic.wgsl.expected.wgsl
rename to test/tint/statements/for/condition/basic.wgsl.expected.wgsl
diff --git a/test/statements/for/condition/struct_ctor.wgsl b/test/tint/statements/for/condition/struct_ctor.wgsl
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl
rename to test/tint/statements/for/condition/struct_ctor.wgsl
diff --git a/test/statements/for/condition/struct_ctor.wgsl.expected.glsl b/test/tint/statements/for/condition/struct_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/condition/struct_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/condition/struct_ctor.wgsl.expected.hlsl b/test/tint/statements/for/condition/struct_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/condition/struct_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/condition/struct_ctor.wgsl.expected.msl b/test/tint/statements/for/condition/struct_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl.expected.msl
rename to test/tint/statements/for/condition/struct_ctor.wgsl.expected.msl
diff --git a/test/statements/for/condition/struct_ctor.wgsl.expected.spvasm b/test/tint/statements/for/condition/struct_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/condition/struct_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/condition/struct_ctor.wgsl.expected.wgsl b/test/tint/statements/for/condition/struct_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/condition/struct_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/condition/struct_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/continuing/array_ctor.wgsl b/test/tint/statements/for/continuing/array_ctor.wgsl
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl
rename to test/tint/statements/for/continuing/array_ctor.wgsl
diff --git a/test/statements/for/continuing/array_ctor.wgsl.expected.glsl b/test/tint/statements/for/continuing/array_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/continuing/array_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/continuing/array_ctor.wgsl.expected.hlsl b/test/tint/statements/for/continuing/array_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/continuing/array_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/continuing/array_ctor.wgsl.expected.msl b/test/tint/statements/for/continuing/array_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl.expected.msl
rename to test/tint/statements/for/continuing/array_ctor.wgsl.expected.msl
diff --git a/test/statements/for/continuing/array_ctor.wgsl.expected.spvasm b/test/tint/statements/for/continuing/array_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/continuing/array_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/continuing/array_ctor.wgsl.expected.wgsl b/test/tint/statements/for/continuing/array_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/continuing/array_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/continuing/array_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/continuing/basic.wgsl b/test/tint/statements/for/continuing/basic.wgsl
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl
rename to test/tint/statements/for/continuing/basic.wgsl
diff --git a/test/statements/for/continuing/basic.wgsl.expected.glsl b/test/tint/statements/for/continuing/basic.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl.expected.glsl
rename to test/tint/statements/for/continuing/basic.wgsl.expected.glsl
diff --git a/test/statements/for/continuing/basic.wgsl.expected.hlsl b/test/tint/statements/for/continuing/basic.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl.expected.hlsl
rename to test/tint/statements/for/continuing/basic.wgsl.expected.hlsl
diff --git a/test/statements/for/continuing/basic.wgsl.expected.msl b/test/tint/statements/for/continuing/basic.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl.expected.msl
rename to test/tint/statements/for/continuing/basic.wgsl.expected.msl
diff --git a/test/statements/for/continuing/basic.wgsl.expected.spvasm b/test/tint/statements/for/continuing/basic.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl.expected.spvasm
rename to test/tint/statements/for/continuing/basic.wgsl.expected.spvasm
diff --git a/test/statements/for/continuing/basic.wgsl.expected.wgsl b/test/tint/statements/for/continuing/basic.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/continuing/basic.wgsl.expected.wgsl
rename to test/tint/statements/for/continuing/basic.wgsl.expected.wgsl
diff --git a/test/statements/for/continuing/struct_ctor.wgsl b/test/tint/statements/for/continuing/struct_ctor.wgsl
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl
rename to test/tint/statements/for/continuing/struct_ctor.wgsl
diff --git a/test/statements/for/continuing/struct_ctor.wgsl.expected.glsl b/test/tint/statements/for/continuing/struct_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/continuing/struct_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/continuing/struct_ctor.wgsl.expected.hlsl b/test/tint/statements/for/continuing/struct_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/continuing/struct_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/continuing/struct_ctor.wgsl.expected.msl b/test/tint/statements/for/continuing/struct_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl.expected.msl
rename to test/tint/statements/for/continuing/struct_ctor.wgsl.expected.msl
diff --git a/test/statements/for/continuing/struct_ctor.wgsl.expected.spvasm b/test/tint/statements/for/continuing/struct_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/continuing/struct_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/continuing/struct_ctor.wgsl.expected.wgsl b/test/tint/statements/for/continuing/struct_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/continuing/struct_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/continuing/struct_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/empty.wgsl b/test/tint/statements/for/empty.wgsl
similarity index 100%
rename from test/statements/for/empty.wgsl
rename to test/tint/statements/for/empty.wgsl
diff --git a/test/statements/for/empty.wgsl.expected.glsl b/test/tint/statements/for/empty.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/empty.wgsl.expected.glsl
rename to test/tint/statements/for/empty.wgsl.expected.glsl
diff --git a/test/statements/for/empty.wgsl.expected.hlsl b/test/tint/statements/for/empty.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/empty.wgsl.expected.hlsl
rename to test/tint/statements/for/empty.wgsl.expected.hlsl
diff --git a/test/statements/for/empty.wgsl.expected.msl b/test/tint/statements/for/empty.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/empty.wgsl.expected.msl
rename to test/tint/statements/for/empty.wgsl.expected.msl
diff --git a/test/statements/for/empty.wgsl.expected.spvasm b/test/tint/statements/for/empty.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/empty.wgsl.expected.spvasm
rename to test/tint/statements/for/empty.wgsl.expected.spvasm
diff --git a/test/statements/for/empty.wgsl.expected.wgsl b/test/tint/statements/for/empty.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/empty.wgsl.expected.wgsl
rename to test/tint/statements/for/empty.wgsl.expected.wgsl
diff --git a/test/statements/for/initializer/array_ctor.wgsl b/test/tint/statements/for/initializer/array_ctor.wgsl
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl
rename to test/tint/statements/for/initializer/array_ctor.wgsl
diff --git a/test/statements/for/initializer/array_ctor.wgsl.expected.glsl b/test/tint/statements/for/initializer/array_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/initializer/array_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/initializer/array_ctor.wgsl.expected.hlsl b/test/tint/statements/for/initializer/array_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/initializer/array_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/initializer/array_ctor.wgsl.expected.msl b/test/tint/statements/for/initializer/array_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl.expected.msl
rename to test/tint/statements/for/initializer/array_ctor.wgsl.expected.msl
diff --git a/test/statements/for/initializer/array_ctor.wgsl.expected.spvasm b/test/tint/statements/for/initializer/array_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/initializer/array_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/initializer/array_ctor.wgsl.expected.wgsl b/test/tint/statements/for/initializer/array_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/initializer/array_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/initializer/array_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/initializer/basic.wgsl b/test/tint/statements/for/initializer/basic.wgsl
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl
rename to test/tint/statements/for/initializer/basic.wgsl
diff --git a/test/statements/for/initializer/basic.wgsl.expected.glsl b/test/tint/statements/for/initializer/basic.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl.expected.glsl
rename to test/tint/statements/for/initializer/basic.wgsl.expected.glsl
diff --git a/test/statements/for/initializer/basic.wgsl.expected.hlsl b/test/tint/statements/for/initializer/basic.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl.expected.hlsl
rename to test/tint/statements/for/initializer/basic.wgsl.expected.hlsl
diff --git a/test/statements/for/initializer/basic.wgsl.expected.msl b/test/tint/statements/for/initializer/basic.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl.expected.msl
rename to test/tint/statements/for/initializer/basic.wgsl.expected.msl
diff --git a/test/statements/for/initializer/basic.wgsl.expected.spvasm b/test/tint/statements/for/initializer/basic.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl.expected.spvasm
rename to test/tint/statements/for/initializer/basic.wgsl.expected.spvasm
diff --git a/test/statements/for/initializer/basic.wgsl.expected.wgsl b/test/tint/statements/for/initializer/basic.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/initializer/basic.wgsl.expected.wgsl
rename to test/tint/statements/for/initializer/basic.wgsl.expected.wgsl
diff --git a/test/statements/for/initializer/struct_ctor.wgsl b/test/tint/statements/for/initializer/struct_ctor.wgsl
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl
rename to test/tint/statements/for/initializer/struct_ctor.wgsl
diff --git a/test/statements/for/initializer/struct_ctor.wgsl.expected.glsl b/test/tint/statements/for/initializer/struct_ctor.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl.expected.glsl
rename to test/tint/statements/for/initializer/struct_ctor.wgsl.expected.glsl
diff --git a/test/statements/for/initializer/struct_ctor.wgsl.expected.hlsl b/test/tint/statements/for/initializer/struct_ctor.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl.expected.hlsl
rename to test/tint/statements/for/initializer/struct_ctor.wgsl.expected.hlsl
diff --git a/test/statements/for/initializer/struct_ctor.wgsl.expected.msl b/test/tint/statements/for/initializer/struct_ctor.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl.expected.msl
rename to test/tint/statements/for/initializer/struct_ctor.wgsl.expected.msl
diff --git a/test/statements/for/initializer/struct_ctor.wgsl.expected.spvasm b/test/tint/statements/for/initializer/struct_ctor.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl.expected.spvasm
rename to test/tint/statements/for/initializer/struct_ctor.wgsl.expected.spvasm
diff --git a/test/statements/for/initializer/struct_ctor.wgsl.expected.wgsl b/test/tint/statements/for/initializer/struct_ctor.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/initializer/struct_ctor.wgsl.expected.wgsl
rename to test/tint/statements/for/initializer/struct_ctor.wgsl.expected.wgsl
diff --git a/test/statements/for/scoping.wgsl b/test/tint/statements/for/scoping.wgsl
similarity index 100%
rename from test/statements/for/scoping.wgsl
rename to test/tint/statements/for/scoping.wgsl
diff --git a/test/statements/for/scoping.wgsl.expected.glsl b/test/tint/statements/for/scoping.wgsl.expected.glsl
similarity index 100%
rename from test/statements/for/scoping.wgsl.expected.glsl
rename to test/tint/statements/for/scoping.wgsl.expected.glsl
diff --git a/test/statements/for/scoping.wgsl.expected.hlsl b/test/tint/statements/for/scoping.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/for/scoping.wgsl.expected.hlsl
rename to test/tint/statements/for/scoping.wgsl.expected.hlsl
diff --git a/test/statements/for/scoping.wgsl.expected.msl b/test/tint/statements/for/scoping.wgsl.expected.msl
similarity index 100%
rename from test/statements/for/scoping.wgsl.expected.msl
rename to test/tint/statements/for/scoping.wgsl.expected.msl
diff --git a/test/statements/for/scoping.wgsl.expected.spvasm b/test/tint/statements/for/scoping.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/for/scoping.wgsl.expected.spvasm
rename to test/tint/statements/for/scoping.wgsl.expected.spvasm
diff --git a/test/statements/for/scoping.wgsl.expected.wgsl b/test/tint/statements/for/scoping.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/for/scoping.wgsl.expected.wgsl
rename to test/tint/statements/for/scoping.wgsl.expected.wgsl
diff --git a/test/statements/switch/common.wgsl b/test/tint/statements/switch/common.wgsl
similarity index 100%
rename from test/statements/switch/common.wgsl
rename to test/tint/statements/switch/common.wgsl
diff --git a/test/statements/switch/common.wgsl.expected.glsl b/test/tint/statements/switch/common.wgsl.expected.glsl
similarity index 100%
rename from test/statements/switch/common.wgsl.expected.glsl
rename to test/tint/statements/switch/common.wgsl.expected.glsl
diff --git a/test/statements/switch/common.wgsl.expected.hlsl b/test/tint/statements/switch/common.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/switch/common.wgsl.expected.hlsl
rename to test/tint/statements/switch/common.wgsl.expected.hlsl
diff --git a/test/statements/switch/common.wgsl.expected.msl b/test/tint/statements/switch/common.wgsl.expected.msl
similarity index 100%
rename from test/statements/switch/common.wgsl.expected.msl
rename to test/tint/statements/switch/common.wgsl.expected.msl
diff --git a/test/statements/switch/common.wgsl.expected.spvasm b/test/tint/statements/switch/common.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/switch/common.wgsl.expected.spvasm
rename to test/tint/statements/switch/common.wgsl.expected.spvasm
diff --git a/test/statements/switch/common.wgsl.expected.wgsl b/test/tint/statements/switch/common.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/switch/common.wgsl.expected.wgsl
rename to test/tint/statements/switch/common.wgsl.expected.wgsl
diff --git a/test/statements/switch/fallthrough.wgsl b/test/tint/statements/switch/fallthrough.wgsl
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl
rename to test/tint/statements/switch/fallthrough.wgsl
diff --git a/test/statements/switch/fallthrough.wgsl.expected.glsl b/test/tint/statements/switch/fallthrough.wgsl.expected.glsl
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl.expected.glsl
rename to test/tint/statements/switch/fallthrough.wgsl.expected.glsl
diff --git a/test/statements/switch/fallthrough.wgsl.expected.hlsl b/test/tint/statements/switch/fallthrough.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl.expected.hlsl
rename to test/tint/statements/switch/fallthrough.wgsl.expected.hlsl
diff --git a/test/statements/switch/fallthrough.wgsl.expected.msl b/test/tint/statements/switch/fallthrough.wgsl.expected.msl
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl.expected.msl
rename to test/tint/statements/switch/fallthrough.wgsl.expected.msl
diff --git a/test/statements/switch/fallthrough.wgsl.expected.spvasm b/test/tint/statements/switch/fallthrough.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl.expected.spvasm
rename to test/tint/statements/switch/fallthrough.wgsl.expected.spvasm
diff --git a/test/statements/switch/fallthrough.wgsl.expected.wgsl b/test/tint/statements/switch/fallthrough.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/switch/fallthrough.wgsl.expected.wgsl
rename to test/tint/statements/switch/fallthrough.wgsl.expected.wgsl
diff --git a/test/statements/switch/only_default_case.wgsl b/test/tint/statements/switch/only_default_case.wgsl
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl
rename to test/tint/statements/switch/only_default_case.wgsl
diff --git a/test/statements/switch/only_default_case.wgsl.expected.glsl b/test/tint/statements/switch/only_default_case.wgsl.expected.glsl
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl.expected.glsl
rename to test/tint/statements/switch/only_default_case.wgsl.expected.glsl
diff --git a/test/statements/switch/only_default_case.wgsl.expected.hlsl b/test/tint/statements/switch/only_default_case.wgsl.expected.hlsl
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl.expected.hlsl
rename to test/tint/statements/switch/only_default_case.wgsl.expected.hlsl
diff --git a/test/statements/switch/only_default_case.wgsl.expected.msl b/test/tint/statements/switch/only_default_case.wgsl.expected.msl
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl.expected.msl
rename to test/tint/statements/switch/only_default_case.wgsl.expected.msl
diff --git a/test/statements/switch/only_default_case.wgsl.expected.spvasm b/test/tint/statements/switch/only_default_case.wgsl.expected.spvasm
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl.expected.spvasm
rename to test/tint/statements/switch/only_default_case.wgsl.expected.spvasm
diff --git a/test/statements/switch/only_default_case.wgsl.expected.wgsl b/test/tint/statements/switch/only_default_case.wgsl.expected.wgsl
similarity index 100%
rename from test/statements/switch/only_default_case.wgsl.expected.wgsl
rename to test/tint/statements/switch/only_default_case.wgsl.expected.wgsl
diff --git a/test/struct/type_constructor.wgsl b/test/tint/struct/type_constructor.wgsl
similarity index 100%
rename from test/struct/type_constructor.wgsl
rename to test/tint/struct/type_constructor.wgsl
diff --git a/test/struct/type_constructor.wgsl.expected.glsl b/test/tint/struct/type_constructor.wgsl.expected.glsl
similarity index 100%
rename from test/struct/type_constructor.wgsl.expected.glsl
rename to test/tint/struct/type_constructor.wgsl.expected.glsl
diff --git a/test/struct/type_constructor.wgsl.expected.hlsl b/test/tint/struct/type_constructor.wgsl.expected.hlsl
similarity index 100%
rename from test/struct/type_constructor.wgsl.expected.hlsl
rename to test/tint/struct/type_constructor.wgsl.expected.hlsl
diff --git a/test/struct/type_constructor.wgsl.expected.msl b/test/tint/struct/type_constructor.wgsl.expected.msl
similarity index 100%
rename from test/struct/type_constructor.wgsl.expected.msl
rename to test/tint/struct/type_constructor.wgsl.expected.msl
diff --git a/test/struct/type_constructor.wgsl.expected.spvasm b/test/tint/struct/type_constructor.wgsl.expected.spvasm
similarity index 100%
rename from test/struct/type_constructor.wgsl.expected.spvasm
rename to test/tint/struct/type_constructor.wgsl.expected.spvasm
diff --git a/test/struct/type_constructor.wgsl.expected.wgsl b/test/tint/struct/type_constructor.wgsl.expected.wgsl
similarity index 100%
rename from test/struct/type_constructor.wgsl.expected.wgsl
rename to test/tint/struct/type_constructor.wgsl.expected.wgsl
diff --git a/test/tint/test-all.sh b/test/tint/test-all.sh
new file mode 100755
index 0000000..48ef708
--- /dev/null
+++ b/test/tint/test-all.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# 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.
+
+set -e # Fail on any error.
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
+
+function usage() {
+    echo "test-all.sh is a simple wrapper around <tint>/tools/test-runner that"    
+    echo "injects the <tint>/test directory as the second command line argument"
+    echo
+    echo "Usage of <tint>/tools/test-runner:"
+    "${SCRIPT_DIR}/../../tools/test-runner" --help
+}
+
+TINT="$1"
+
+if [ -z "$TINT" ]; then
+    echo "error: missing argument: location of the 'tint' executable"
+    echo
+    usage
+    exit 1
+fi
+if [ ! -x "$TINT" ]; then
+    echo "error: invalid argument: location of the 'tint' executable"
+    echo
+    usage
+    exit 1
+fi
+
+"${SCRIPT_DIR}/../../tools/test-runner" "${@:2}" "${TINT}" "${SCRIPT_DIR}"
diff --git a/test/types/function_scope_declarations.wgsl b/test/tint/types/function_scope_declarations.wgsl
similarity index 100%
rename from test/types/function_scope_declarations.wgsl
rename to test/tint/types/function_scope_declarations.wgsl
diff --git a/test/types/function_scope_declarations.wgsl.expected.glsl b/test/tint/types/function_scope_declarations.wgsl.expected.glsl
similarity index 100%
rename from test/types/function_scope_declarations.wgsl.expected.glsl
rename to test/tint/types/function_scope_declarations.wgsl.expected.glsl
diff --git a/test/types/function_scope_declarations.wgsl.expected.hlsl b/test/tint/types/function_scope_declarations.wgsl.expected.hlsl
similarity index 100%
rename from test/types/function_scope_declarations.wgsl.expected.hlsl
rename to test/tint/types/function_scope_declarations.wgsl.expected.hlsl
diff --git a/test/types/function_scope_declarations.wgsl.expected.msl b/test/tint/types/function_scope_declarations.wgsl.expected.msl
similarity index 100%
rename from test/types/function_scope_declarations.wgsl.expected.msl
rename to test/tint/types/function_scope_declarations.wgsl.expected.msl
diff --git a/test/types/function_scope_declarations.wgsl.expected.spvasm b/test/tint/types/function_scope_declarations.wgsl.expected.spvasm
similarity index 100%
rename from test/types/function_scope_declarations.wgsl.expected.spvasm
rename to test/tint/types/function_scope_declarations.wgsl.expected.spvasm
diff --git a/test/types/function_scope_declarations.wgsl.expected.wgsl b/test/tint/types/function_scope_declarations.wgsl.expected.wgsl
similarity index 100%
rename from test/types/function_scope_declarations.wgsl.expected.wgsl
rename to test/tint/types/function_scope_declarations.wgsl.expected.wgsl
diff --git a/test/types/function_scope_var_conversions.wgsl b/test/tint/types/function_scope_var_conversions.wgsl
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl
rename to test/tint/types/function_scope_var_conversions.wgsl
diff --git a/test/types/function_scope_var_conversions.wgsl.expected.glsl b/test/tint/types/function_scope_var_conversions.wgsl.expected.glsl
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl.expected.glsl
rename to test/tint/types/function_scope_var_conversions.wgsl.expected.glsl
diff --git a/test/types/function_scope_var_conversions.wgsl.expected.hlsl b/test/tint/types/function_scope_var_conversions.wgsl.expected.hlsl
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl.expected.hlsl
rename to test/tint/types/function_scope_var_conversions.wgsl.expected.hlsl
diff --git a/test/types/function_scope_var_conversions.wgsl.expected.msl b/test/tint/types/function_scope_var_conversions.wgsl.expected.msl
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl.expected.msl
rename to test/tint/types/function_scope_var_conversions.wgsl.expected.msl
diff --git a/test/types/function_scope_var_conversions.wgsl.expected.spvasm b/test/tint/types/function_scope_var_conversions.wgsl.expected.spvasm
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl.expected.spvasm
rename to test/tint/types/function_scope_var_conversions.wgsl.expected.spvasm
diff --git a/test/types/function_scope_var_conversions.wgsl.expected.wgsl b/test/tint/types/function_scope_var_conversions.wgsl.expected.wgsl
similarity index 100%
rename from test/types/function_scope_var_conversions.wgsl.expected.wgsl
rename to test/tint/types/function_scope_var_conversions.wgsl.expected.wgsl
diff --git a/test/types/module_scope_let.wgsl b/test/tint/types/module_scope_let.wgsl
similarity index 100%
rename from test/types/module_scope_let.wgsl
rename to test/tint/types/module_scope_let.wgsl
diff --git a/test/types/module_scope_let.wgsl.expected.glsl b/test/tint/types/module_scope_let.wgsl.expected.glsl
similarity index 100%
rename from test/types/module_scope_let.wgsl.expected.glsl
rename to test/tint/types/module_scope_let.wgsl.expected.glsl
diff --git a/test/types/module_scope_let.wgsl.expected.hlsl b/test/tint/types/module_scope_let.wgsl.expected.hlsl
similarity index 100%
rename from test/types/module_scope_let.wgsl.expected.hlsl
rename to test/tint/types/module_scope_let.wgsl.expected.hlsl
diff --git a/test/types/module_scope_let.wgsl.expected.msl b/test/tint/types/module_scope_let.wgsl.expected.msl
similarity index 100%
rename from test/types/module_scope_let.wgsl.expected.msl
rename to test/tint/types/module_scope_let.wgsl.expected.msl
diff --git a/test/types/module_scope_let.wgsl.expected.spvasm b/test/tint/types/module_scope_let.wgsl.expected.spvasm
similarity index 100%
rename from test/types/module_scope_let.wgsl.expected.spvasm
rename to test/tint/types/module_scope_let.wgsl.expected.spvasm
diff --git a/test/types/module_scope_let.wgsl.expected.wgsl b/test/tint/types/module_scope_let.wgsl.expected.wgsl
similarity index 100%
rename from test/types/module_scope_let.wgsl.expected.wgsl
rename to test/tint/types/module_scope_let.wgsl.expected.wgsl
diff --git a/test/types/module_scope_var.wgsl b/test/tint/types/module_scope_var.wgsl
similarity index 100%
rename from test/types/module_scope_var.wgsl
rename to test/tint/types/module_scope_var.wgsl
diff --git a/test/types/module_scope_var.wgsl.expected.glsl b/test/tint/types/module_scope_var.wgsl.expected.glsl
similarity index 100%
rename from test/types/module_scope_var.wgsl.expected.glsl
rename to test/tint/types/module_scope_var.wgsl.expected.glsl
diff --git a/test/types/module_scope_var.wgsl.expected.hlsl b/test/tint/types/module_scope_var.wgsl.expected.hlsl
similarity index 100%
rename from test/types/module_scope_var.wgsl.expected.hlsl
rename to test/tint/types/module_scope_var.wgsl.expected.hlsl
diff --git a/test/types/module_scope_var.wgsl.expected.msl b/test/tint/types/module_scope_var.wgsl.expected.msl
similarity index 100%
rename from test/types/module_scope_var.wgsl.expected.msl
rename to test/tint/types/module_scope_var.wgsl.expected.msl
diff --git a/test/types/module_scope_var.wgsl.expected.spvasm b/test/tint/types/module_scope_var.wgsl.expected.spvasm
similarity index 100%
rename from test/types/module_scope_var.wgsl.expected.spvasm
rename to test/tint/types/module_scope_var.wgsl.expected.spvasm
diff --git a/test/types/module_scope_var.wgsl.expected.wgsl b/test/tint/types/module_scope_var.wgsl.expected.wgsl
similarity index 100%
rename from test/types/module_scope_var.wgsl.expected.wgsl
rename to test/tint/types/module_scope_var.wgsl.expected.wgsl
diff --git a/test/types/module_scope_var_conversions.wgsl b/test/tint/types/module_scope_var_conversions.wgsl
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl
rename to test/tint/types/module_scope_var_conversions.wgsl
diff --git a/test/types/module_scope_var_conversions.wgsl.expected.glsl b/test/tint/types/module_scope_var_conversions.wgsl.expected.glsl
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl.expected.glsl
rename to test/tint/types/module_scope_var_conversions.wgsl.expected.glsl
diff --git a/test/types/module_scope_var_conversions.wgsl.expected.hlsl b/test/tint/types/module_scope_var_conversions.wgsl.expected.hlsl
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl.expected.hlsl
rename to test/tint/types/module_scope_var_conversions.wgsl.expected.hlsl
diff --git a/test/types/module_scope_var_conversions.wgsl.expected.msl b/test/tint/types/module_scope_var_conversions.wgsl.expected.msl
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl.expected.msl
rename to test/tint/types/module_scope_var_conversions.wgsl.expected.msl
diff --git a/test/types/module_scope_var_conversions.wgsl.expected.spvasm b/test/tint/types/module_scope_var_conversions.wgsl.expected.spvasm
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl.expected.spvasm
rename to test/tint/types/module_scope_var_conversions.wgsl.expected.spvasm
diff --git a/test/types/module_scope_var_conversions.wgsl.expected.wgsl b/test/tint/types/module_scope_var_conversions.wgsl.expected.wgsl
similarity index 100%
rename from test/types/module_scope_var_conversions.wgsl.expected.wgsl
rename to test/tint/types/module_scope_var_conversions.wgsl.expected.wgsl
diff --git a/test/types/module_scope_var_initializers.wgsl b/test/tint/types/module_scope_var_initializers.wgsl
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl
rename to test/tint/types/module_scope_var_initializers.wgsl
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.glsl b/test/tint/types/module_scope_var_initializers.wgsl.expected.glsl
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl.expected.glsl
rename to test/tint/types/module_scope_var_initializers.wgsl.expected.glsl
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.hlsl b/test/tint/types/module_scope_var_initializers.wgsl.expected.hlsl
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl.expected.hlsl
rename to test/tint/types/module_scope_var_initializers.wgsl.expected.hlsl
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.msl b/test/tint/types/module_scope_var_initializers.wgsl.expected.msl
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl.expected.msl
rename to test/tint/types/module_scope_var_initializers.wgsl.expected.msl
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.spvasm b/test/tint/types/module_scope_var_initializers.wgsl.expected.spvasm
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl.expected.spvasm
rename to test/tint/types/module_scope_var_initializers.wgsl.expected.spvasm
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.wgsl b/test/tint/types/module_scope_var_initializers.wgsl.expected.wgsl
similarity index 100%
rename from test/types/module_scope_var_initializers.wgsl.expected.wgsl
rename to test/tint/types/module_scope_var_initializers.wgsl.expected.wgsl
diff --git a/test/types/parameters.wgsl b/test/tint/types/parameters.wgsl
similarity index 100%
rename from test/types/parameters.wgsl
rename to test/tint/types/parameters.wgsl
diff --git a/test/types/parameters.wgsl.expected.glsl b/test/tint/types/parameters.wgsl.expected.glsl
similarity index 100%
rename from test/types/parameters.wgsl.expected.glsl
rename to test/tint/types/parameters.wgsl.expected.glsl
diff --git a/test/types/parameters.wgsl.expected.hlsl b/test/tint/types/parameters.wgsl.expected.hlsl
similarity index 100%
rename from test/types/parameters.wgsl.expected.hlsl
rename to test/tint/types/parameters.wgsl.expected.hlsl
diff --git a/test/types/parameters.wgsl.expected.msl b/test/tint/types/parameters.wgsl.expected.msl
similarity index 100%
rename from test/types/parameters.wgsl.expected.msl
rename to test/tint/types/parameters.wgsl.expected.msl
diff --git a/test/types/parameters.wgsl.expected.spvasm b/test/tint/types/parameters.wgsl.expected.spvasm
similarity index 100%
rename from test/types/parameters.wgsl.expected.spvasm
rename to test/tint/types/parameters.wgsl.expected.spvasm
diff --git a/test/types/parameters.wgsl.expected.wgsl b/test/tint/types/parameters.wgsl.expected.wgsl
similarity index 100%
rename from test/types/parameters.wgsl.expected.wgsl
rename to test/tint/types/parameters.wgsl.expected.wgsl
diff --git a/test/types/return_types.wgsl b/test/tint/types/return_types.wgsl
similarity index 100%
rename from test/types/return_types.wgsl
rename to test/tint/types/return_types.wgsl
diff --git a/test/types/return_types.wgsl.expected.glsl b/test/tint/types/return_types.wgsl.expected.glsl
similarity index 100%
rename from test/types/return_types.wgsl.expected.glsl
rename to test/tint/types/return_types.wgsl.expected.glsl
diff --git a/test/types/return_types.wgsl.expected.hlsl b/test/tint/types/return_types.wgsl.expected.hlsl
similarity index 100%
rename from test/types/return_types.wgsl.expected.hlsl
rename to test/tint/types/return_types.wgsl.expected.hlsl
diff --git a/test/types/return_types.wgsl.expected.msl b/test/tint/types/return_types.wgsl.expected.msl
similarity index 100%
rename from test/types/return_types.wgsl.expected.msl
rename to test/tint/types/return_types.wgsl.expected.msl
diff --git a/test/types/return_types.wgsl.expected.spvasm b/test/tint/types/return_types.wgsl.expected.spvasm
similarity index 100%
rename from test/types/return_types.wgsl.expected.spvasm
rename to test/tint/types/return_types.wgsl.expected.spvasm
diff --git a/test/types/return_types.wgsl.expected.wgsl b/test/tint/types/return_types.wgsl.expected.wgsl
similarity index 100%
rename from test/types/return_types.wgsl.expected.wgsl
rename to test/tint/types/return_types.wgsl.expected.wgsl
diff --git a/test/types/sampler.wgsl b/test/tint/types/sampler.wgsl
similarity index 100%
rename from test/types/sampler.wgsl
rename to test/tint/types/sampler.wgsl
diff --git a/test/types/sampler.wgsl.expected.glsl b/test/tint/types/sampler.wgsl.expected.glsl
similarity index 100%
rename from test/types/sampler.wgsl.expected.glsl
rename to test/tint/types/sampler.wgsl.expected.glsl
diff --git a/test/types/sampler.wgsl.expected.hlsl b/test/tint/types/sampler.wgsl.expected.hlsl
similarity index 100%
rename from test/types/sampler.wgsl.expected.hlsl
rename to test/tint/types/sampler.wgsl.expected.hlsl
diff --git a/test/types/sampler.wgsl.expected.msl b/test/tint/types/sampler.wgsl.expected.msl
similarity index 100%
rename from test/types/sampler.wgsl.expected.msl
rename to test/tint/types/sampler.wgsl.expected.msl
diff --git a/test/types/sampler.wgsl.expected.spvasm b/test/tint/types/sampler.wgsl.expected.spvasm
similarity index 100%
rename from test/types/sampler.wgsl.expected.spvasm
rename to test/tint/types/sampler.wgsl.expected.spvasm
diff --git a/test/types/sampler.wgsl.expected.wgsl b/test/tint/types/sampler.wgsl.expected.wgsl
similarity index 100%
rename from test/types/sampler.wgsl.expected.wgsl
rename to test/tint/types/sampler.wgsl.expected.wgsl
diff --git a/test/types/struct_members.wgsl b/test/tint/types/struct_members.wgsl
similarity index 100%
rename from test/types/struct_members.wgsl
rename to test/tint/types/struct_members.wgsl
diff --git a/test/types/struct_members.wgsl.expected.glsl b/test/tint/types/struct_members.wgsl.expected.glsl
similarity index 100%
rename from test/types/struct_members.wgsl.expected.glsl
rename to test/tint/types/struct_members.wgsl.expected.glsl
diff --git a/test/types/struct_members.wgsl.expected.hlsl b/test/tint/types/struct_members.wgsl.expected.hlsl
similarity index 100%
rename from test/types/struct_members.wgsl.expected.hlsl
rename to test/tint/types/struct_members.wgsl.expected.hlsl
diff --git a/test/types/struct_members.wgsl.expected.msl b/test/tint/types/struct_members.wgsl.expected.msl
similarity index 100%
rename from test/types/struct_members.wgsl.expected.msl
rename to test/tint/types/struct_members.wgsl.expected.msl
diff --git a/test/types/struct_members.wgsl.expected.spvasm b/test/tint/types/struct_members.wgsl.expected.spvasm
similarity index 100%
rename from test/types/struct_members.wgsl.expected.spvasm
rename to test/tint/types/struct_members.wgsl.expected.spvasm
diff --git a/test/types/struct_members.wgsl.expected.wgsl b/test/tint/types/struct_members.wgsl.expected.wgsl
similarity index 100%
rename from test/types/struct_members.wgsl.expected.wgsl
rename to test/tint/types/struct_members.wgsl.expected.wgsl
diff --git a/test/types/texture/depth/2d.wgsl b/test/tint/types/texture/depth/2d.wgsl
similarity index 100%
rename from test/types/texture/depth/2d.wgsl
rename to test/tint/types/texture/depth/2d.wgsl
diff --git a/test/types/texture/depth/2d.wgsl.expected.glsl b/test/tint/types/texture/depth/2d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/depth/2d.wgsl.expected.glsl
rename to test/tint/types/texture/depth/2d.wgsl.expected.glsl
diff --git a/test/types/texture/depth/2d.wgsl.expected.hlsl b/test/tint/types/texture/depth/2d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/depth/2d.wgsl.expected.hlsl
rename to test/tint/types/texture/depth/2d.wgsl.expected.hlsl
diff --git a/test/types/texture/depth/2d.wgsl.expected.msl b/test/tint/types/texture/depth/2d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/depth/2d.wgsl.expected.msl
rename to test/tint/types/texture/depth/2d.wgsl.expected.msl
diff --git a/test/types/texture/depth/2d.wgsl.expected.spvasm b/test/tint/types/texture/depth/2d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/depth/2d.wgsl.expected.spvasm
rename to test/tint/types/texture/depth/2d.wgsl.expected.spvasm
diff --git a/test/types/texture/depth/2d.wgsl.expected.wgsl b/test/tint/types/texture/depth/2d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/depth/2d.wgsl.expected.wgsl
rename to test/tint/types/texture/depth/2d.wgsl.expected.wgsl
diff --git a/test/types/texture/depth/2d_array.wgsl b/test/tint/types/texture/depth/2d_array.wgsl
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl
rename to test/tint/types/texture/depth/2d_array.wgsl
diff --git a/test/types/texture/depth/2d_array.wgsl.expected.glsl b/test/tint/types/texture/depth/2d_array.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl.expected.glsl
rename to test/tint/types/texture/depth/2d_array.wgsl.expected.glsl
diff --git a/test/types/texture/depth/2d_array.wgsl.expected.hlsl b/test/tint/types/texture/depth/2d_array.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl.expected.hlsl
rename to test/tint/types/texture/depth/2d_array.wgsl.expected.hlsl
diff --git a/test/types/texture/depth/2d_array.wgsl.expected.msl b/test/tint/types/texture/depth/2d_array.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl.expected.msl
rename to test/tint/types/texture/depth/2d_array.wgsl.expected.msl
diff --git a/test/types/texture/depth/2d_array.wgsl.expected.spvasm b/test/tint/types/texture/depth/2d_array.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl.expected.spvasm
rename to test/tint/types/texture/depth/2d_array.wgsl.expected.spvasm
diff --git a/test/types/texture/depth/2d_array.wgsl.expected.wgsl b/test/tint/types/texture/depth/2d_array.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/depth/2d_array.wgsl.expected.wgsl
rename to test/tint/types/texture/depth/2d_array.wgsl.expected.wgsl
diff --git a/test/types/texture/depth/cube.wgsl b/test/tint/types/texture/depth/cube.wgsl
similarity index 100%
rename from test/types/texture/depth/cube.wgsl
rename to test/tint/types/texture/depth/cube.wgsl
diff --git a/test/types/texture/depth/cube.wgsl.expected.glsl b/test/tint/types/texture/depth/cube.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/depth/cube.wgsl.expected.glsl
rename to test/tint/types/texture/depth/cube.wgsl.expected.glsl
diff --git a/test/types/texture/depth/cube.wgsl.expected.hlsl b/test/tint/types/texture/depth/cube.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/depth/cube.wgsl.expected.hlsl
rename to test/tint/types/texture/depth/cube.wgsl.expected.hlsl
diff --git a/test/types/texture/depth/cube.wgsl.expected.msl b/test/tint/types/texture/depth/cube.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/depth/cube.wgsl.expected.msl
rename to test/tint/types/texture/depth/cube.wgsl.expected.msl
diff --git a/test/types/texture/depth/cube.wgsl.expected.spvasm b/test/tint/types/texture/depth/cube.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/depth/cube.wgsl.expected.spvasm
rename to test/tint/types/texture/depth/cube.wgsl.expected.spvasm
diff --git a/test/types/texture/depth/cube.wgsl.expected.wgsl b/test/tint/types/texture/depth/cube.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/depth/cube.wgsl.expected.wgsl
rename to test/tint/types/texture/depth/cube.wgsl.expected.wgsl
diff --git a/test/types/texture/depth/cube_array.wgsl b/test/tint/types/texture/depth/cube_array.wgsl
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl
rename to test/tint/types/texture/depth/cube_array.wgsl
diff --git a/test/types/texture/depth/cube_array.wgsl.expected.glsl b/test/tint/types/texture/depth/cube_array.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl.expected.glsl
rename to test/tint/types/texture/depth/cube_array.wgsl.expected.glsl
diff --git a/test/types/texture/depth/cube_array.wgsl.expected.hlsl b/test/tint/types/texture/depth/cube_array.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl.expected.hlsl
rename to test/tint/types/texture/depth/cube_array.wgsl.expected.hlsl
diff --git a/test/types/texture/depth/cube_array.wgsl.expected.msl b/test/tint/types/texture/depth/cube_array.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl.expected.msl
rename to test/tint/types/texture/depth/cube_array.wgsl.expected.msl
diff --git a/test/types/texture/depth/cube_array.wgsl.expected.spvasm b/test/tint/types/texture/depth/cube_array.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl.expected.spvasm
rename to test/tint/types/texture/depth/cube_array.wgsl.expected.spvasm
diff --git a/test/types/texture/depth/cube_array.wgsl.expected.wgsl b/test/tint/types/texture/depth/cube_array.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/depth/cube_array.wgsl.expected.wgsl
rename to test/tint/types/texture/depth/cube_array.wgsl.expected.wgsl
diff --git a/test/types/texture/multisampled/2d.wgsl b/test/tint/types/texture/multisampled/2d.wgsl
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl
rename to test/tint/types/texture/multisampled/2d.wgsl
diff --git a/test/types/texture/multisampled/2d.wgsl.expected.glsl b/test/tint/types/texture/multisampled/2d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl.expected.glsl
rename to test/tint/types/texture/multisampled/2d.wgsl.expected.glsl
diff --git a/test/types/texture/multisampled/2d.wgsl.expected.hlsl b/test/tint/types/texture/multisampled/2d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl.expected.hlsl
rename to test/tint/types/texture/multisampled/2d.wgsl.expected.hlsl
diff --git a/test/types/texture/multisampled/2d.wgsl.expected.msl b/test/tint/types/texture/multisampled/2d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl.expected.msl
rename to test/tint/types/texture/multisampled/2d.wgsl.expected.msl
diff --git a/test/types/texture/multisampled/2d.wgsl.expected.spvasm b/test/tint/types/texture/multisampled/2d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl.expected.spvasm
rename to test/tint/types/texture/multisampled/2d.wgsl.expected.spvasm
diff --git a/test/types/texture/multisampled/2d.wgsl.expected.wgsl b/test/tint/types/texture/multisampled/2d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/multisampled/2d.wgsl.expected.wgsl
rename to test/tint/types/texture/multisampled/2d.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/1d.wgsl b/test/tint/types/texture/sampled/1d.wgsl
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl
rename to test/tint/types/texture/sampled/1d.wgsl
diff --git a/test/types/texture/sampled/1d.wgsl.expected.glsl b/test/tint/types/texture/sampled/1d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/1d.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/1d.wgsl.expected.hlsl b/test/tint/types/texture/sampled/1d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/1d.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/1d.wgsl.expected.msl b/test/tint/types/texture/sampled/1d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl.expected.msl
rename to test/tint/types/texture/sampled/1d.wgsl.expected.msl
diff --git a/test/types/texture/sampled/1d.wgsl.expected.spvasm b/test/tint/types/texture/sampled/1d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/1d.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/1d.wgsl.expected.wgsl b/test/tint/types/texture/sampled/1d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/1d.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/1d.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/2d.wgsl b/test/tint/types/texture/sampled/2d.wgsl
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl
rename to test/tint/types/texture/sampled/2d.wgsl
diff --git a/test/types/texture/sampled/2d.wgsl.expected.glsl b/test/tint/types/texture/sampled/2d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/2d.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/2d.wgsl.expected.hlsl b/test/tint/types/texture/sampled/2d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/2d.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/2d.wgsl.expected.msl b/test/tint/types/texture/sampled/2d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl.expected.msl
rename to test/tint/types/texture/sampled/2d.wgsl.expected.msl
diff --git a/test/types/texture/sampled/2d.wgsl.expected.spvasm b/test/tint/types/texture/sampled/2d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/2d.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/2d.wgsl.expected.wgsl b/test/tint/types/texture/sampled/2d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/2d.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/2d.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/2d_array.wgsl b/test/tint/types/texture/sampled/2d_array.wgsl
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl
rename to test/tint/types/texture/sampled/2d_array.wgsl
diff --git a/test/types/texture/sampled/2d_array.wgsl.expected.glsl b/test/tint/types/texture/sampled/2d_array.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/2d_array.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/2d_array.wgsl.expected.hlsl b/test/tint/types/texture/sampled/2d_array.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/2d_array.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/2d_array.wgsl.expected.msl b/test/tint/types/texture/sampled/2d_array.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl.expected.msl
rename to test/tint/types/texture/sampled/2d_array.wgsl.expected.msl
diff --git a/test/types/texture/sampled/2d_array.wgsl.expected.spvasm b/test/tint/types/texture/sampled/2d_array.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/2d_array.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/2d_array.wgsl.expected.wgsl b/test/tint/types/texture/sampled/2d_array.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/2d_array.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/2d_array.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/3d.wgsl b/test/tint/types/texture/sampled/3d.wgsl
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl
rename to test/tint/types/texture/sampled/3d.wgsl
diff --git a/test/types/texture/sampled/3d.wgsl.expected.glsl b/test/tint/types/texture/sampled/3d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/3d.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/3d.wgsl.expected.hlsl b/test/tint/types/texture/sampled/3d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/3d.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/3d.wgsl.expected.msl b/test/tint/types/texture/sampled/3d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl.expected.msl
rename to test/tint/types/texture/sampled/3d.wgsl.expected.msl
diff --git a/test/types/texture/sampled/3d.wgsl.expected.spvasm b/test/tint/types/texture/sampled/3d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/3d.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/3d.wgsl.expected.wgsl b/test/tint/types/texture/sampled/3d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/3d.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/3d.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/cube.wgsl b/test/tint/types/texture/sampled/cube.wgsl
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl
rename to test/tint/types/texture/sampled/cube.wgsl
diff --git a/test/types/texture/sampled/cube.wgsl.expected.glsl b/test/tint/types/texture/sampled/cube.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/cube.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/cube.wgsl.expected.hlsl b/test/tint/types/texture/sampled/cube.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/cube.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/cube.wgsl.expected.msl b/test/tint/types/texture/sampled/cube.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl.expected.msl
rename to test/tint/types/texture/sampled/cube.wgsl.expected.msl
diff --git a/test/types/texture/sampled/cube.wgsl.expected.spvasm b/test/tint/types/texture/sampled/cube.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/cube.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/cube.wgsl.expected.wgsl b/test/tint/types/texture/sampled/cube.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/cube.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/cube.wgsl.expected.wgsl
diff --git a/test/types/texture/sampled/cube_array.wgsl b/test/tint/types/texture/sampled/cube_array.wgsl
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl
rename to test/tint/types/texture/sampled/cube_array.wgsl
diff --git a/test/types/texture/sampled/cube_array.wgsl.expected.glsl b/test/tint/types/texture/sampled/cube_array.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl.expected.glsl
rename to test/tint/types/texture/sampled/cube_array.wgsl.expected.glsl
diff --git a/test/types/texture/sampled/cube_array.wgsl.expected.hlsl b/test/tint/types/texture/sampled/cube_array.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl.expected.hlsl
rename to test/tint/types/texture/sampled/cube_array.wgsl.expected.hlsl
diff --git a/test/types/texture/sampled/cube_array.wgsl.expected.msl b/test/tint/types/texture/sampled/cube_array.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl.expected.msl
rename to test/tint/types/texture/sampled/cube_array.wgsl.expected.msl
diff --git a/test/types/texture/sampled/cube_array.wgsl.expected.spvasm b/test/tint/types/texture/sampled/cube_array.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl.expected.spvasm
rename to test/tint/types/texture/sampled/cube_array.wgsl.expected.spvasm
diff --git a/test/types/texture/sampled/cube_array.wgsl.expected.wgsl b/test/tint/types/texture/sampled/cube_array.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/sampled/cube_array.wgsl.expected.wgsl
rename to test/tint/types/texture/sampled/cube_array.wgsl.expected.wgsl
diff --git a/test/types/texture/storage/1d.wgsl b/test/tint/types/texture/storage/1d.wgsl
similarity index 100%
rename from test/types/texture/storage/1d.wgsl
rename to test/tint/types/texture/storage/1d.wgsl
diff --git a/test/types/texture/storage/1d.wgsl.expected.glsl b/test/tint/types/texture/storage/1d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/storage/1d.wgsl.expected.glsl
rename to test/tint/types/texture/storage/1d.wgsl.expected.glsl
diff --git a/test/types/texture/storage/1d.wgsl.expected.hlsl b/test/tint/types/texture/storage/1d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/storage/1d.wgsl.expected.hlsl
rename to test/tint/types/texture/storage/1d.wgsl.expected.hlsl
diff --git a/test/types/texture/storage/1d.wgsl.expected.msl b/test/tint/types/texture/storage/1d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/storage/1d.wgsl.expected.msl
rename to test/tint/types/texture/storage/1d.wgsl.expected.msl
diff --git a/test/types/texture/storage/1d.wgsl.expected.spvasm b/test/tint/types/texture/storage/1d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/storage/1d.wgsl.expected.spvasm
rename to test/tint/types/texture/storage/1d.wgsl.expected.spvasm
diff --git a/test/types/texture/storage/1d.wgsl.expected.wgsl b/test/tint/types/texture/storage/1d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/storage/1d.wgsl.expected.wgsl
rename to test/tint/types/texture/storage/1d.wgsl.expected.wgsl
diff --git a/test/types/texture/storage/2d.wgsl b/test/tint/types/texture/storage/2d.wgsl
similarity index 100%
rename from test/types/texture/storage/2d.wgsl
rename to test/tint/types/texture/storage/2d.wgsl
diff --git a/test/types/texture/storage/2d.wgsl.expected.glsl b/test/tint/types/texture/storage/2d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/storage/2d.wgsl.expected.glsl
rename to test/tint/types/texture/storage/2d.wgsl.expected.glsl
diff --git a/test/types/texture/storage/2d.wgsl.expected.hlsl b/test/tint/types/texture/storage/2d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/storage/2d.wgsl.expected.hlsl
rename to test/tint/types/texture/storage/2d.wgsl.expected.hlsl
diff --git a/test/types/texture/storage/2d.wgsl.expected.msl b/test/tint/types/texture/storage/2d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/storage/2d.wgsl.expected.msl
rename to test/tint/types/texture/storage/2d.wgsl.expected.msl
diff --git a/test/types/texture/storage/2d.wgsl.expected.spvasm b/test/tint/types/texture/storage/2d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/storage/2d.wgsl.expected.spvasm
rename to test/tint/types/texture/storage/2d.wgsl.expected.spvasm
diff --git a/test/types/texture/storage/2d.wgsl.expected.wgsl b/test/tint/types/texture/storage/2d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/storage/2d.wgsl.expected.wgsl
rename to test/tint/types/texture/storage/2d.wgsl.expected.wgsl
diff --git a/test/types/texture/storage/2d_array.wgsl b/test/tint/types/texture/storage/2d_array.wgsl
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl
rename to test/tint/types/texture/storage/2d_array.wgsl
diff --git a/test/types/texture/storage/2d_array.wgsl.expected.glsl b/test/tint/types/texture/storage/2d_array.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl.expected.glsl
rename to test/tint/types/texture/storage/2d_array.wgsl.expected.glsl
diff --git a/test/types/texture/storage/2d_array.wgsl.expected.hlsl b/test/tint/types/texture/storage/2d_array.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl.expected.hlsl
rename to test/tint/types/texture/storage/2d_array.wgsl.expected.hlsl
diff --git a/test/types/texture/storage/2d_array.wgsl.expected.msl b/test/tint/types/texture/storage/2d_array.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl.expected.msl
rename to test/tint/types/texture/storage/2d_array.wgsl.expected.msl
diff --git a/test/types/texture/storage/2d_array.wgsl.expected.spvasm b/test/tint/types/texture/storage/2d_array.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl.expected.spvasm
rename to test/tint/types/texture/storage/2d_array.wgsl.expected.spvasm
diff --git a/test/types/texture/storage/2d_array.wgsl.expected.wgsl b/test/tint/types/texture/storage/2d_array.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/storage/2d_array.wgsl.expected.wgsl
rename to test/tint/types/texture/storage/2d_array.wgsl.expected.wgsl
diff --git a/test/types/texture/storage/3d.wgsl b/test/tint/types/texture/storage/3d.wgsl
similarity index 100%
rename from test/types/texture/storage/3d.wgsl
rename to test/tint/types/texture/storage/3d.wgsl
diff --git a/test/types/texture/storage/3d.wgsl.expected.glsl b/test/tint/types/texture/storage/3d.wgsl.expected.glsl
similarity index 100%
rename from test/types/texture/storage/3d.wgsl.expected.glsl
rename to test/tint/types/texture/storage/3d.wgsl.expected.glsl
diff --git a/test/types/texture/storage/3d.wgsl.expected.hlsl b/test/tint/types/texture/storage/3d.wgsl.expected.hlsl
similarity index 100%
rename from test/types/texture/storage/3d.wgsl.expected.hlsl
rename to test/tint/types/texture/storage/3d.wgsl.expected.hlsl
diff --git a/test/types/texture/storage/3d.wgsl.expected.msl b/test/tint/types/texture/storage/3d.wgsl.expected.msl
similarity index 100%
rename from test/types/texture/storage/3d.wgsl.expected.msl
rename to test/tint/types/texture/storage/3d.wgsl.expected.msl
diff --git a/test/types/texture/storage/3d.wgsl.expected.spvasm b/test/tint/types/texture/storage/3d.wgsl.expected.spvasm
similarity index 100%
rename from test/types/texture/storage/3d.wgsl.expected.spvasm
rename to test/tint/types/texture/storage/3d.wgsl.expected.spvasm
diff --git a/test/types/texture/storage/3d.wgsl.expected.wgsl b/test/tint/types/texture/storage/3d.wgsl.expected.wgsl
similarity index 100%
rename from test/types/texture/storage/3d.wgsl.expected.wgsl
rename to test/tint/types/texture/storage/3d.wgsl.expected.wgsl
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm b/test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/ConvertResultSignedness_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
diff --git a/test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm b/test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
rename to test/tint/unittest/reader/spirv/ConvertUintCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
diff --git a/test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Good_1D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/Good_2DArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/Good_2D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/Good_3D_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Good_CubeArray_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/Good_Cube_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_DepthMultisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_DepthMultisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_DepthMultisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_DepthMultisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_Depth_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_Depth_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_Depth_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_Depth_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_Multisampled_ConvertSampleOperand_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_Multisampled_ConvertSampleOperand_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_Multisampled_ConvertSampleOperand_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_Multisampled_ConvertSampleOperand_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_Multisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_Multisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_Multisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_Multisampled_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageFetch_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQueryLevels_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQuerySamples_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQuerySamples_UnsignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySize_Arrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySize_Arrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySize_Arrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySize_Arrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageQuerySize_NonArrayed_SignedResult_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageRead_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageRead_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageRead_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageRead_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageSampleDrefExplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageSampleDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingGrad_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleExplicitLod_UsingLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_BothDrefAndNonDref_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_BothDrefAndNonDref_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_BothDrefAndNonDref_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_BothDrefAndNonDref_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_7.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm b/test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_8.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjDrefExplicitLod_CheckForLod0_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjDrefImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Grad_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjExplicitLod_Lod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_Bias_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_DepthTexture_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageSampleProjImplicitLod_SpvParserHandleTest_SampledImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_4.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_5.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_6.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_7.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_7.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Arity_SpvParserHandleTest_ImageAccessTest_Variable_7.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_SameSignedness_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_ConvertTexelOperand_Signedness_AndWidening_SpvParserHandleTest_ImageAccessTest_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/ImageWrite_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm b/test/tint/unittest/reader/spirv/ImageWrite_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ImageWrite_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/ImageWrite_OptionalParams_SpvParserHandleTest_ImageAccessTest_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm b/test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_1.spvasm b/test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_2.spvasm b/test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/Images_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm b/test/tint/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm
rename to test/tint/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm
diff --git a/test/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Multisampled_Only2DNonArrayedIsValid_SpvParserHandleTest_ImageDeclTest_DeclareAndUseHandle_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
diff --git a/test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm b/test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
rename to test/tint/unittest/reader/spirv/PreserveFloatCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_Arrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_3.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_4.spvasm
diff --git a/test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm b/test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
rename to test/tint/unittest/reader/spirv/PreserveIntCoords_NonArrayed_SpvParserHandleTest_ImageCoordsTest_MakeCoordinateOperandsForImageAccess_5.spvasm
diff --git a/test/unittest/reader/spirv/Samplers_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm b/test/tint/unittest/reader/spirv/Samplers_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samplers_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/Samplers_SpvParserHandleTest_DeclUnderspecifiedHandle_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_AccessChain_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_CopyObject_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvModuleScopeVarParserTest_ComputeBuiltin_Load_Direct_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_5.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_RawImage_Variable_6.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_8.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_8.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_8.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_9.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_9.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_9.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserHandleTest_RegisterHandleUsage_SampledImage_Variable_9.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataPacking_Valid_4.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_DataUnpacking_Valid_4.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float3_Float3Float3_Samples_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float3_Float3Float3_Samples_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float3_Float3Float3_Samples_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float3_Float3Float3_Samples_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_FloatingFloating_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Float_Floating_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Scalar_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating_Vector_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Scalar_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingFloating_Vector_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingInting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_FloatingUinting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_10.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_10.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_10.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_10.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_11.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_12.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_13.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_13.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_13.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_13.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_14.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_14.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_14.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_14.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_15.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_15.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_15.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_15.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_16.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_16.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_16.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_16.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_17.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_17.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_17.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_17.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_18.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_18.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_18.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_18.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_19.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_19.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_19.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_19.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_20.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_20.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_20.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_20.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_21.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_21.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_21.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_21.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_22.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_22.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_22.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_22.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_8.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_8.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_8.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_9.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_9.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_9.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Scalar_9.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_10.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_10.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_10.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_10.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_11.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_12.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_13.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_13.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_13.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_13.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_14.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_14.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_14.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_14.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_15.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_15.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_15.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_15.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_16.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_16.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_16.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_16.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_17.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_17.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_17.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_17.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_18.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_18.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_18.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_18.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_19.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_19.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_19.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_19.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_2.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_2.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_2.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_20.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_20.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_20.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_20.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_21.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_21.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_21.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_21.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_22.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_22.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_22.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_22.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_3.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_3.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_3.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_4.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_4.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_4.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_5.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_5.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_5.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_6.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_6.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_6.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_7.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_7.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_7.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_8.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_8.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_8.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_9.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_9.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_9.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Floating_Floating_Vector_9.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingIntingInting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Scalar_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_IntingInting_Vector_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Inting_Inting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUintingUinting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Scalar_1.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_0.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_0.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_0.spvasm
diff --git a/test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_1.spvasm b/test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_1.spvasm
rename to test/tint/unittest/reader/spirv/Samples_SpvParserTest_GlslStd450_Uinting_UintingUinting_Vector_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_Dot.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_Dot.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_Dot.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_Dot.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_FMod_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesMatrix.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesMatrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesMatrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesMatrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesScalar.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesScalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesScalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesScalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesVector.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_MatrixTimesVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_OuterProduct.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_OuterProduct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_OuterProduct.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_OuterProduct.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Scalar_UnsignedResult.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Scalar_UnsignedResult.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Scalar_UnsignedResult.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Scalar_UnsignedResult.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Vector_UnsignedResult.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Vector_UnsignedResult.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Vector_UnsignedResult.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SDiv_Vector_UnsignedResult.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Scalar_UnsignedResult.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Scalar_UnsignedResult.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Scalar_UnsignedResult.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Scalar_UnsignedResult.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Vector_UnsignedResult.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Vector_UnsignedResult.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Vector_UnsignedResult.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_SMod_Vector_UnsignedResult.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesMatrix.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesMatrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesMatrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesMatrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesScalar.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesScalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesScalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryArithTestBasic_VectorTimesScalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_10.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_11.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_12.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_13.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_14.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_15.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_15.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_15.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_15.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_16.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_16.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_16.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_16.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_17.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_17.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_17.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_17.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_18.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_19.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_2.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_20.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_21.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_22.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_23.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_24.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_24.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_24.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_24.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_25.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_25.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_25.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_25.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_26.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_26.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_26.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_26.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_3.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_4.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_5.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_6.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_6.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_6.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_6.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_7.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_7.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_7.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_7.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_8.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_8.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_8.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_8.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm
diff --git a/test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvBinaryDerivativeTest_SpvBinaryDerivativeTest_Derivatives_9.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordEqual_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThanEqual_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordGreaterThan_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThanEqual_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordLessThan_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvFUnordTest_FUnordNotEqual_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_All.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_All.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_All.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_All.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_Any.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_Any.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_Any.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_Any.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_IsInf_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_IsInf_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_IsInf_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_IsInf_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_IsInf_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_IsInf_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_IsInf_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_IsInf_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_IsNan_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_IsNan_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_IsNan_Vector.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_BoolParams.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_BoolParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_BoolParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_BoolParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_FloatScalarParams.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_FloatScalarParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_FloatScalarParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_FloatScalarParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_IntScalarParams.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_IntScalarParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_IntScalarParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_Select_BoolCond_IntScalarParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvLogicalTest_Select_VecBoolCond_VectorParams.spvasm b/test/tint/unittest/reader/spirv/SpvLogicalTest_Select_VecBoolCond_VectorParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvLogicalTest_Select_VecBoolCond_VectorParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvLogicalTest_Select_VecBoolCond_VectorParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_AnonWorkgroupVar.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_AnonWorkgroupVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_AnonWorkgroupVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_AnonWorkgroupVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ArrayNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BindingDecoration_Valid.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BindingDecoration_Valid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BindingDecoration_Valid.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BindingDecoration_Valid.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_ReadReplaced_Vertex.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_ReadReplaced_Vertex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_ReadReplaced_Vertex.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_ReadReplaced_Vertex.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_Write1_IsErased.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_Write1_IsErased.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_Write1_IsErased.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_Write1_IsErased.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_ReadReplaced.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_ReadReplaced.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_ReadReplaced.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_ReadReplaced.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Write1_IsErased.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Write1_IsErased.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Write1_IsErased.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_Write1_IsErased.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position_Initializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position_Initializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position_Initializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_BuiltIn_Position_Initializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_OneAccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_OneAccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_OneAccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_OneAccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_TwoAccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_TwoAccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_TwoAccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePositionMember_TwoAccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinVertexIndex.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinVertexIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinVertexIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_BuiltinVertexIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_OppositeSignednessAsWGSL.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_OppositeSignednessAsWGSL.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_OppositeSignednessAsWGSL.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_OppositeSignednessAsWGSL.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_SameSignednessAsWGSL.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_SameSignednessAsWGSL.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_SameSignednessAsWGSL.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Input_SameSignednessAsWGSL.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_OppositeSignednessAsWGSL.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_OppositeSignednessAsWGSL.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_OppositeSignednessAsWGSL.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_OppositeSignednessAsWGSL.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_SameSignednessAsWGSL.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_SameSignednessAsWGSL.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_SameSignednessAsWGSL.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Builtin_Output_Initializer_SameSignednessAsWGSL.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ColMajorDecoration_Dropped.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ColMajorDecoration_Dropped.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ColMajorDecoration_Dropped.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ColMajorDecoration_Dropped.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_DescriptorGroupDecoration_Valid.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_DescriptorGroupDecoration_Valid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_DescriptorGroupDecoration_Valid.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_DescriptorGroupDecoration_Valid.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_FragDepth_Out_Initializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_OppositeSignedness.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_OppositeSignedness.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_OppositeSignedness.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_OppositeSignedness.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_SameSignedness.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_SameSignedness.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_SameSignedness.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_Input_SameSignedness.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Signed.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Signed.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Signed.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Signed.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_In_Unsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Signed_Initializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_BuiltinVar_SampleMask_Out_Unsigned_Initializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Flat_Fragment_In.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_IOLocations.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_IOLocations.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_IOLocations.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_IOLocations.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_In.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_Output.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_Output.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_Output.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Flat_Vertex_Output.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_In.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_In.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_In.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_In.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_Out.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_Out.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_Out.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_EntryPointWrapping_Interpolation_Floating_Fragment_Out.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_FlattenStruct_LocOnMembers.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_FlattenStruct_LocOnMembers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_FlattenStruct_LocOnMembers.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_FlattenStruct_LocOnMembers.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InputVarsConvertedToPrivate.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InputVarsConvertedToPrivate.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InputVarsConvertedToPrivate.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InputVarsConvertedToPrivate.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenArray_OneLevel.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenArray_OneLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenArray_OneLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenArray_OneLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenMatrix.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenMatrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenMatrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenMatrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenNested.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenNested.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenNested.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenNested.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenStruct_LocOnVariable.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenStruct_LocOnVariable.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenStruct_LocOnVariable.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Input_FlattenStruct_LocOnVariable.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_I32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_InstanceIndex_U32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Dropped.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Dropped.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Dropped.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Dropped.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Natural_Dropped.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Natural_Dropped.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Natural_Dropped.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_MatrixStrideDecoration_Natural_Dropped.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_NamedWorkgroupVar.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_NamedWorkgroupVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_NamedWorkgroupVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_NamedWorkgroupVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_NoVar.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_NoVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_NoVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_NoVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate_WithInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate_WithInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate_WithInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_OutputVarsConvertedToPrivate_WithInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenArray_OneLevel.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenArray_OneLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenArray_OneLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenArray_OneLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenMatrix.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenMatrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenMatrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenMatrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenStruct_LocOnVariable.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenStruct_LocOnVariable.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenStruct_LocOnVariable.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_Output_FlattenStruct_LocOnVariable.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_PrivateVar.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_PrivateVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_PrivateVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_PrivateVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_I32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleId_U32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_I32_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_U32_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_WithStride.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_WithStride.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_WithStride.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_In_WithStride.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_I32_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_U32_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_WithStride.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_WithStride.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_WithStride.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_SampleMask_Out_WithStride.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarInitializers.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarInitializers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarInitializers.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarInitializers.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarNullInitializers.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarNullInitializers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarNullInitializers.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarNullInitializers.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32_WithoutSpecId.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32_WithoutSpecId.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32_WithoutSpecId.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_F32_WithoutSpecId.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_False.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_False.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_False.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_False.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_I32.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_I32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_I32.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_I32.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_Id_MaxValid.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_Id_MaxValid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_Id_MaxValid.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_Id_MaxValid.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_True.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_True.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_True.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_True.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_U32.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_U32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_U32.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_DeclareConst_U32.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_UsedInFunction.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_UsedInFunction.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_UsedInFunction.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_ScalarSpecConstant_UsedInFunction.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_AllMembers.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_AllMembers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_AllMembers.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_AllMembers.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StorageBuffer_NonWritable_NotAllMembers_DuplicatedOnSameMember.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructMember_NonReadableDecoration_Dropped.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructMember_NonReadableDecoration_Dropped.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructMember_NonReadableDecoration_Dropped.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructMember_NonReadableDecoration_Dropped.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_StructNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorBoolNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorBoolNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorBoolNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorBoolNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorFloatNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorFloatNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorFloatNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorFloatNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorIntNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorIntNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorIntNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorIntNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorUintNullInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorUintNullInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorUintNullInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VectorUintNullInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_I32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_AccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_AccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_AccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_AccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvModuleScopeVarParserTest_VertexIndex_U32_Load_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_BackEdge_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_DefaultToCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_DefaultToCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_DefaultToCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Fallthrough_DefaultToCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_IfToThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_LoopHeadToBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_LoopHeadToBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_LoopHeadToBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_LoopHeadToBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfThenElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfThenElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfThenElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromIfThenElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructTail.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructTail.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructTail.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromContinueConstructTail.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_LoopBodyToContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_LoopBodyToContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_LoopBodyToContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_LoopBodyToContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Regardless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Regardless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Regardless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Regardless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_DupConditionalBranch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_DupConditionalBranch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_DupConditionalBranch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_DupConditionalBranch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_FalseOnlyBranch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_FalseOnlyBranch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_FalseOnlyBranch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_FalseOnlyBranch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_IgnoreStaticalyUnreachable.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_IgnoreStaticalyUnreachable.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_IgnoreStaticalyUnreachable.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_IgnoreStaticalyUnreachable.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_KillIsDeadEnd.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_KillIsDeadEnd.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_KillIsDeadEnd.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_KillIsDeadEnd.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreak.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakUnless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakUnless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakUnless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasBreakUnless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueUnless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueUnless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueUnless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_BodyHasContinueUnless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Break.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Break.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Break.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Break.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Continue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Continue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Continue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_If_Continue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch_CaseContinues.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch_CaseContinues.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch_CaseContinues.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Body_Switch_CaseContinues.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_ContainsIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_ContainsIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_ContainsIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_ContainsIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakUnless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakUnless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakUnless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_HasBreakUnless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_Sequence.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_Sequence.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_Sequence.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Continue_Sequence.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_HeaderHasBreakUnless.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinueBreaks.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinueBreaks.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinueBreaks.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_Loop_InnerContinueBreaks.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_SingleBlock_Simple.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_SingleBlock_Simple.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_SingleBlock_Simple.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Loop_SingleBlock_Simple.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfBreak_In_SwitchCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfBreak_In_SwitchCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfBreak_In_SwitchCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfBreak_In_SwitchCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_Contains_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_Contains_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_Contains_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_Contains_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_In_SwitchCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_In_SwitchCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_In_SwitchCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Nest_If_In_SwitchCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_OneBlock.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_OneBlock.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_OneBlock.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_OneBlock.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectConditionalBranchOrder.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectConditionalBranchOrder.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectConditionalBranchOrder.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectConditionalBranchOrder.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchOrderNaturallyReversed.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchOrderNaturallyReversed.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchOrderNaturallyReversed.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchOrderNaturallyReversed.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Switch_DefaultSameAsACase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Switch_DefaultSameAsACase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Switch_DefaultSameAsACase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_Switch_DefaultSameAsACase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_TrueOnlyBranch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_TrueOnlyBranch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_TrueOnlyBranch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_TrueOnlyBranch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_UnreachableIsDeadEnd.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_UnreachableIsDeadEnd.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_UnreachableIsDeadEnd.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_ComputeBlockOrder_UnreachableIsDeadEnd.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_Back.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Continue_FromHeader.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnFalse.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_Forward_OnTrue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnFalse.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Continue_IfBreak_OnTrue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Forward_Forward_Same.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Forward_Forward_Same.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Forward_Forward_Same.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_Forward_Forward_Same.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_IfBreak_IfBreak_Same.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_IfBreak_IfBreak_Same.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_IfBreak_IfBreak_Same.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_IfBreak_IfBreak_Same.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Continue_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_Forward_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_LoopContinue_FromSwitch.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_MultiBlockLoop.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_BackEdge_SingleBlockLoop.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Fallthrough.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Fallthrough.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Fallthrough.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Fallthrough.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Forward.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Forward.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Forward.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_Forward.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_IfBreak_FromThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_BeforeLast.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_FromSwitch.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_LoopContinue_LastInLoopConstruct.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_LastInCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_LastInCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_LastInCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_LastInCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_NotLastInCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_NotLastInCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_NotLastInCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Branch_SwitchBreak_NotLastInCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_SwitchBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_SwitchBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_SwitchBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_FalseBranch_SwitchBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromElse_ForwardWithinElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromElse_ForwardWithinElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromElse_ForwardWithinElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromElse_ForwardWithinElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThen_ForwardWithinThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThen_ForwardWithinThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThen_ForwardWithinThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfBreak_FromThen_ForwardWithinThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Empty.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Empty.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Empty.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Empty.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Nest_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Nest_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Nest_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Nest_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_NoThen_Else.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_NoThen_Else.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_NoThen_Else.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_NoThen_Else.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_NoElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_NoElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_NoElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_NoElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_TopLevel.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_TopLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_TopLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_TopLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_LoopInternallyDiverge_Simple.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyAlwaysBreaks.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyAlwaysBreaks.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyAlwaysBreaks.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyAlwaysBreaks.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_FalseToBody_TrueBreaks.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_FalseToBody_TrueBreaks.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_FalseToBody_TrueBreaks.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_FalseToBody_TrueBreaks.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_MultiBlockContinueIsEntireLoop.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_NestedIfContinue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Never.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Never.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Never.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Never.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_BothBackedge.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_FalseBackedge.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_TrueBackedge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_TrueBackedge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_TrueBackedge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_TrueBackedge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_SingleBlock_UnconditionalBackege.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_TrueToBody_FalseBreaks.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_TrueToBody_FalseBreaks.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_TrueToBody_FalseBreaks.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_TrueToBody_FalseBreaks.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_ContinueNestIf.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_MultiBlockContinue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Loop_Unconditional_Body_SingleBlockContinue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_InsideIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_InsideIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_InsideIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_InsideIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_Loop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_Loop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_Loop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_Loop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_TopLevel.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_TopLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_TopLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_ReturnValue_TopLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_InsideLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_TopLevel.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_TopLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_TopLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Return_TopLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_SintValue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_SintValue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_SintValue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_SintValue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_UintValue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_UintValue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_UintValue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_Case_UintValue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_NoDupCases.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_NoDupCases.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_NoDupCases.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_NoDupCases.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_WithDupCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_WithDupCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_WithDupCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsCase_WithDupCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_CasesWithDup.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_CasesWithDup.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_CasesWithDup.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_CasesWithDup.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_NoCases.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_NoCases.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_NoCases.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_NoCases.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_OneCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_OneCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_OneCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_OneCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_TwoCases.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_TwoCases.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_TwoCases.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Switch_DefaultIsMerge_TwoCases.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_LoopContinue.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_SwitchBreak.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_SwitchBreak.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_SwitchBreak.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_TrueBranch_SwitchBreak.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InNonVoidFunction.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InNonVoidFunction.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InNonVoidFunction.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InNonVoidFunction.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_InsideLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_TopLevel.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_TopLevel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_TopLevel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Unreachable_TopLevel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ElseOnly.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ElseOnly.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ElseOnly.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ElseOnly.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_IfOnly.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_IfOnly.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_IfOnly.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_IfOnly.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_NoIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_NoIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_NoIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_NoIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Regardless.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Regardless.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Regardless.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_Regardless.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ThenElse.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ThenElse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ThenElse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_ThenElse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsDefault.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsDefault.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsDefault.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsDefault.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsNotDefault.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsNotDefault.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsNotDefault.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_CaseIsNotDefault.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsNotMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsNotMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsNotMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_DefaultIsNotMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_ManyValuesWithSameCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_ManyValuesWithSameCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_ManyValuesWithSameCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_ManyValuesWithSameCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_NoSwitch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_NoSwitch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_NoSwitch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_FindSwitchCaseHeaders_NoSwitch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_LoopInterallyDiverge.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_MultiBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_MultiBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_MultiBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_MultiBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_Switch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_Switch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_Switch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_If_Switch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_LoopContinue_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_LoopContinue_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_LoopContinue_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_LoopContinue_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_Loop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_Loop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_Loop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Loop_Loop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Switch_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Switch_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Switch_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_Nest_Switch_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SwitchSelection.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SwitchSelection.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SwitchSelection.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_LabelControlFlowConstructs_SwitchSelection.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_Branch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodLoopMerge_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_BranchConditional.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_BranchConditional.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_BranchConditional.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_BranchConditional.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_Switch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_Switch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_Switch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_GoodSelectionMerge_Switch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_NoMerges.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_NoMerges.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_NoMerges.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_RegisterMerges_NoMerges.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_HasSiblingLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_HasSiblingLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_HasSiblingLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_HasSiblingLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_NotAContinue.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_NotAContinue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_NotAContinue.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_NotAContinue.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_Null.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_Null.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_Null.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_Null.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_SingleBlockLoop.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_SingleBlockLoop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_SingleBlockLoop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_SiblingLoopConstruct_SingleBlockLoop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_If.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_If.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_If.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_If.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Kill.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Kill.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Kill.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Kill.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_Simple.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_Simple.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_Simple.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_Simple.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_SingleBlock.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_SingleBlock.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_SingleBlock.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Loop_SingleBlock.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Sequence.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Sequence.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Sequence.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Sequence.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_SingleBlock.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_SingleBlock.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_SingleBlock.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_SingleBlock.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Switch.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Switch.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Switch.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Switch.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Unreachable.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Unreachable.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Unreachable.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_TerminatorsAreValid_Unreachable.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_Selection_Good.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_Selection_Good.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_Selection_Good.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_Selection_Good.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good.spvasm b/test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserCFGTest_VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_AnonymousVars.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_AnonymousVars.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_AnonymousVars.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_AnonymousVars.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias_Null.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias_Null.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias_Null.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Alias_Null.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Null.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Null.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Null.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ArrayInitializer_Null.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_Decorate_RelaxedPrecision.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_Decorate_RelaxedPrecision.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_Decorate_RelaxedPrecision.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_Decorate_RelaxedPrecision.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MatrixInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MatrixInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MatrixInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MatrixInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MemberDecorate_RelaxedPrecision.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MemberDecorate_RelaxedPrecision.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MemberDecorate_RelaxedPrecision.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MemberDecorate_RelaxedPrecision.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MixedTypes.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MixedTypes.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MixedTypes.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_MixedTypes.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_NamedVars.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_NamedVars.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_NamedVars.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_NamedVars.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarInitializers.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarInitializers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarInitializers.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarInitializers.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarNullInitializers.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarNullInitializers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarNullInitializers.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_ScalarNullInitializers.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructDifferOnlyInMemberName.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructDifferOnlyInMemberName.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructDifferOnlyInMemberName.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructDifferOnlyInMemberName.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer_Null.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer_Null.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer_Null.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_StructInitializer_Null.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_VectorInitializer.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_VectorInitializer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_VectorInitializer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitFunctionVariables_VectorInitializer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_DefConstruct_DoesNotEncloseAllUses.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Defer_UsedOnceSameConstruct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedTwice.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedTwice.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedTwice.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_CombinatorialValue_Immediate_UsedTwice.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_CompositeInsert.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_CompositeInsert.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_CompositeInsert.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_CompositeInsert.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_UsedAsNonPtrArg.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_UsedAsNonPtrArg.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_UsedAsNonPtrArg.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_UsedAsNonPtrArg.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_VectorInsertDynamic.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_VectorInsertDynamic.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_VectorInsertDynamic.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Hoist_VectorInsertDynamic.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromElseAndThen.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_FromHeaderAndThen.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_InMerge_PredecessorsDominatdByNestedSwitchCase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_MultiBlockLoopIndex.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_SingleBlockLoopIndex.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromBlockNotInBlockOrderIgnored.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.msl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.msl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.msl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.msl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.wgsl b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.wgsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.wgsl
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_Phi_ValueFromLoopBodyAndContinuing.spvasm.expected.wgsl
diff --git a/test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_UseInPhiCountsAsUse.spvasm b/test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_UseInPhiCountsAsUse.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_UseInPhiCountsAsUse.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserFunctionVarTest_EmitStatement_UseInPhiCountsAsUse.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_CopyObject.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_CopyObject.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_CopyObject.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_CopyObject.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Direct.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Direct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Direct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Direct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Image.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Image.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Image.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Image.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Load.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Load.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Load.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_Load.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_SampledImage.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_SampledImage.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_SampledImage.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_GetMemoryObjectDeclarationForHandle_Variable_SampledImage.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserHandleTest_NeverGenerateConstDeclForHandle_UseVariableDirectly.spvasm b/test/tint/unittest/reader/spirv/SpvParserHandleTest_NeverGenerateConstDeclForHandle_UseVariableDirectly.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserHandleTest_NeverGenerateConstDeclForHandle_UseVariableDirectly.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserHandleTest_NeverGenerateConstDeclForHandle_UseVariableDirectly.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromAccessChain.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_ArrayLength_FromVar.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Compound_Matrix_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Compound_Matrix_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Compound_Matrix_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Compound_Matrix_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_DereferenceBase.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_DereferenceBase.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_DereferenceBase.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_DereferenceBase.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_InferFunctionStorageClass.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_InferFunctionStorageClass.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_InferFunctionStorageClass.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_InferFunctionStorageClass.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Matrix.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Matrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Matrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Matrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_DifferOnlyMemberName.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_DifferOnlyMemberName.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_DifferOnlyMemberName.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_DifferOnlyMemberName.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_Struct_RuntimeArray.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorNonConstIndex.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorNonConstIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorNonConstIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorNonConstIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorSwizzle.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorSwizzle.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorSwizzle.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_AccessChain_VectorSwizzle.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_CopyMemory_Scalar_Function_To_Private.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_CopyMemory_Scalar_Function_To_Private.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_CopyMemory_Scalar_Function_To_Private.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_CopyMemory_Scalar_Function_To_Private.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadBool.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadBool.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadBool.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadBool.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadScalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadScalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadScalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_LoadScalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreBoolConst.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreBoolConst.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreBoolConst.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreBoolConst.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreFloatConst.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreFloatConst.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreFloatConst.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreFloatConst.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreIntConst.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreIntConst.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreIntConst.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreIntConst.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreToModuleScopeVar.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreToModuleScopeVar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreToModuleScopeVar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreToModuleScopeVar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreUintConst.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreUintConst.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreUintConst.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_StoreUintConst.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_UseLoadedScalarTwice.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_UseLoadedScalarTwice.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_UseLoadedScalarTwice.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_EmitStatement_UseLoadedScalarTwice.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_Cascaded.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserMemoryTest_RemapStorageBuffer_TypesAndVarDeclarations.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpNop.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpNop.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpNop.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpNop.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_BeforeFunction_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Matrix.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Matrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Matrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Matrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Struct.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Struct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Struct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Struct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTestMiscInstruction_OpUndef_InFunction_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseAnd_SpvBinaryBitTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseOr_SpvBinaryBitTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_MixedSignedness_SpvBinaryBitGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_BitwiseXor_SpvBinaryBitTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Matrix_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct_Array_Matrix_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct_Array_Matrix_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct_Array_Matrix_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Struct_Array_Matrix_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeExtract_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeExtract_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Matrix_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_Array_Matrix_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_Array_Matrix_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_Array_Matrix_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_Array_Matrix_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_DifferOnlyInMemberName.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_DifferOnlyInMemberName.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_DifferOnlyInMemberName.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Struct_DifferOnlyInMemberName.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CompositeInsert_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CompositeInsert_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Composite_Construct_ConstantComposite_Struct_NoDeduplication.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_ConstantComposite_Struct_NoDeduplication.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Composite_Construct_ConstantComposite_Struct_NoDeduplication.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_ConstantComposite_Struct_NoDeduplication.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Matrix.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Matrix.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Matrix.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Matrix.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Struct.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Struct.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Struct.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Struct.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Composite_Construct_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Composite_Construct_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Array.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Array.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Array.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Array.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_ArrayStride_Valid.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_ArrayStride_Valid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_ArrayStride_Valid.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_ArrayStride_Valid.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Array_NoDeduplication.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Array_NoDeduplication.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Array_NoDeduplication.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Array_NoDeduplication.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Bool.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Bool.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Bool.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Bool.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_F32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_F32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_F32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_F32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_I32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_I32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_I32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_I32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Image_PretendVoid.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Image_PretendVoid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Image_PretendVoid.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Image_PretendVoid.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_MatrixOverF32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_MatrixOverF32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_MatrixOverF32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_MatrixOverF32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerFunction.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerFunction.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerFunction.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerFunction.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerInput.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerInput.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerInput.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerInput.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerOutput.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerOutput.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerOutput.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerOutput.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerPrivate.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerPrivate.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerPrivate.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerPrivate.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerStorageBuffer.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerStorageBuffer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerStorageBuffer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerStorageBuffer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerToPointer.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerToPointer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerToPointer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerToPointer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniform.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniform.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniform.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniform.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniformConstant.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniformConstant.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniformConstant.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerUniformConstant.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerWorkgroup.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerWorkgroup.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_PointerWorkgroup.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_PointerWorkgroup.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_ArrayStride_Valid.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_ArrayStride_Valid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_ArrayStride_Valid.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_ArrayStride_Valid.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_NoDeduplication.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_NoDeduplication.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_NoDeduplication.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_RuntimeArray_NoDeduplication.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_SampledImage_PretendVoid.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_SampledImage_PretendVoid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_SampledImage_PretendVoid.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_SampledImage_PretendVoid.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Sampler_PretendVoid.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Sampler_PretendVoid.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Sampler_PretendVoid.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Sampler_PretendVoid.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_StructTwoMembers.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructTwoMembers.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_StructTwoMembers.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructTwoMembers.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithBlockDecoration.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithBlockDecoration.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithBlockDecoration.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithBlockDecoration.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithMemberDecorations.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithMemberDecorations.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithMemberDecorations.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_StructWithMemberDecorations.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Struct_NoDeduplication.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Struct_NoDeduplication.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Struct_NoDeduplication.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Struct_NoDeduplication.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_U32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_U32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_U32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_U32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverF32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverF32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverF32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverF32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverI32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverI32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverI32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverI32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverU32.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverU32.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverU32.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_VecOverU32.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ConvertType_Void.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Void.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ConvertType_Void.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ConvertType_Void.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CopyObject_Pointer.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CopyObject_Pointer.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CopyObject_Pointer.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CopyObject_Pointer.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_CopyObject_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_CopyObject_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_CopyObject_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_CopyObject_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_CalleePrecedesCaller.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_CalleePrecedesCaller.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_CalleePrecedesCaller.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_CalleePrecedesCaller.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Fragment.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Fragment.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Fragment.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Fragment.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_GLCompute_LocalSize_Only.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_LocalSize_And_WGSBuiltin_SpecConstant.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_MultipleEntryPoints.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_MultipleEntryPoints.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_MultipleEntryPoints.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_MultipleEntryPoints.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Vertex.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Vertex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Vertex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_Vertex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_Constant_Only.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSizeBuiltin_SpecConstant_Only.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_Function_EntryPoint_WorkgroupSize_MixedConstantSpecConstant.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_GenerateParamNames.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_GenerateParamNames.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_GenerateParamNames.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_GenerateParamNames.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_MixedParamTypes.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_MixedParamTypes.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_MixedParamTypes.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_MixedParamTypes.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_NonVoidResultType.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_NonVoidResultType.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_NonVoidResultType.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_NonVoidResultType.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitFunctions_VoidFunctionWithoutParams.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_VoidFunctionWithoutParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitFunctions_VoidFunctionWithoutParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitFunctions_VoidFunctionWithoutParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitStatement_CallWithParams.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_CallWithParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitStatement_CallWithParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_CallWithParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParams.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParamsUsedTwice.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParamsUsedTwice.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParamsUsedTwice.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_ScalarCallNoParamsUsedTwice.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_EmitStatement_VoidCallNoParams.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_VoidCallNoParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_EmitStatement_VoidCallNoParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_EmitStatement_VoidCallNoParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Emit_GenerateParamNames.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Emit_GenerateParamNames.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Emit_GenerateParamNames.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Emit_GenerateParamNames.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Emit_MixedParamTypes.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Emit_MixedParamTypes.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Emit_MixedParamTypes.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Emit_MixedParamTypes.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Emit_NonVoidResultType.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Emit_NonVoidResultType.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Emit_NonVoidResultType.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Emit_NonVoidResultType.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Emit_VoidFunctionWithoutParams.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Emit_VoidFunctionWithoutParams.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Emit_VoidFunctionWithoutParams.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Emit_VoidFunctionWithoutParams.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FMul_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FOrdNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_0.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvParserTest_FRem_SpvBinaryArithTest_EmitExpression_1.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_FSub_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Degrees_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_FaceForward_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Scalar_Float_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Scalar_Float_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Scalar_Float_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Scalar_Float_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Vector_Floatvec_Uintvec.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Vector_Floatvec_Uintvec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Vector_Floatvec_Uintvec.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Ldexp_Vector_Floatvec_Uintvec.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Radians_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Reflect_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_GlslStd450_Refract_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IAdd_SpvBinaryArithTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_IMul_SpvBinaryArithTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_INotEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_MixedSignedness_SpvBinaryArithGeneralTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ISub_SpvBinaryArithTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_GLSL450MemoryModel.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_GLSL450MemoryModel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_GLSL450MemoryModel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_GLSL450MemoryModel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_SimpleMemoryModel.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_SimpleMemoryModel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_SimpleMemoryModel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_SimpleMemoryModel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_VulkanMemoryModel.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_VulkanMemoryModel.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_VulkanMemoryModel.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_GenericVulkanShader_VulkanMemoryModel.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_Source_InvalidId.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_InvalidId.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_Source_InvalidId.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_InvalidId.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_Source_NoOpLine.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_NoOpLine.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_Source_NoOpLine.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_NoOpLine.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Impl_Source_WithOpLine_WithOpNoLine.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_WithOpLine_WithOpNoLine.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Impl_Source_WithOpLine_WithOpNoLine.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Impl_Source_WithOpLine_WithOpNoLine.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalAnd_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalNotEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_LogicalOr_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Normalize_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Normalize_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Normalize_Vector2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Normalize_Vector2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Normalize_Vector3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Normalize_Vector3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_Normalize_Vector4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_Normalize_Vector4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_Normalize_Vector4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SAbs.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SAbs.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SAbs.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SAbs.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SClamp.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SClamp.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SClamp.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SClamp.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMax.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMax.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMax.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMax.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMin.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMin.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMin.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_SMin.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UClamp.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UClamp.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UClamp.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UClamp.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMax.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMax.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMax.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMax.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMin.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMin.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMin.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_RectifyOperandsAndResult_UMin.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SLessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_MixedSignednessOperands_SpvBinaryArithTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_SMod_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_Arg2Unsigned_SpvBinaryBitTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftLeftLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightArithmetic_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Signed_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_Arg2Unsigned_SpvBinaryBitGeneralTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ShiftRightLogical_BitcastResult_SpvBinaryBitGeneralTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UDiv_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UGreaterThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThanEqual_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_2.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_3.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_4.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ULessThan_SpvBinaryLogicalTest_EmitExpression_5.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_0.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_0.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_0.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_1.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_1.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_UMod_SpvBinaryArithTest_EmitExpression_1.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_ValueFromBlockNotInBlockOrder.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_ValueFromBlockNotInBlockOrder.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_ValueFromBlockNotInBlockOrder.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_ValueFromBlockNotInBlockOrder.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_SignedIndex.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_SignedIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_SignedIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_SignedIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorExtractDynamic_UnsignedIndex.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorInsertDynamic_Sample.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorInsertDynamic_Sample.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorInsertDynamic_Sample.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorInsertDynamic_Sample.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_AllOnesMapToNull.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_AllOnesMapToNull.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_AllOnesMapToNull.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_AllOnesMapToNull.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_UseBoth.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_UseBoth.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_UseBoth.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_ConstantOperands_UseBoth.spvasm
diff --git a/test/unittest/reader/spirv/SpvParserTest_VectorShuffle_FunctionScopeOperands_UseBoth.spvasm b/test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_FunctionScopeOperands_UseBoth.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvParserTest_VectorShuffle_FunctionScopeOperands_UseBoth.spvasm
rename to test/tint/unittest/reader/spirv/SpvParserTest_VectorShuffle_FunctionScopeOperands_UseBoth.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_FNegate_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Int_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_SignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_SignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_SignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_SignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_UnsignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_UnsignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_UnsignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_SignedVec_UnsignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_Uint_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_SignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_SignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_SignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_SignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_UnsignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_UnsignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_UnsignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_SNegate_UnsignedVec_UnsignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x2.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x2.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x2.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x3.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x3.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_2x3.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_3x2.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_3x2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryArithTest_Transpose_3x2.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryArithTest_Transpose_3x2.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_IntVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_IntVector_UintVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Int.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Int_Uint.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_IntVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_UintVector_UintVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Int.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitCount_Uint_Uint.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_IntVector_IntVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Int_Int.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_UintVector_UintVector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_BitReverse_Uint_Uint.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Int_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_SignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_SignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_SignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_SignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_UnsignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_UnsignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_UnsignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_SignedVec_UnsignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Int.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Int.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Int.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Int.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Uint.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Uint.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Uint.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_Uint_Uint.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_SignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_SignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_SignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_SignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_UnsignedVec.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_UnsignedVec.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_UnsignedVec.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryBitTest_Not_UnsignedVec_UnsignedVec.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_Bitcast_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Scalar_ToUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToS_Vector_ToUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm.expected.hlsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm.expected.hlsl
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_HoistedValue.spvasm.expected.hlsl
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Scalar_ToUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Scalar_ToUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Scalar_ToUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Scalar_ToUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Vector_ToUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Vector_ToUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Vector_ToUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertFToU_Vector_ToUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Scalar_FromUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertSToF_Vector_FromUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Scalar_FromUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromSigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromSigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromSigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromSigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromUnsigned.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromUnsigned.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromUnsigned.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryConversionTest_ConvertUToF_Vector_FromUnsigned.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Scalar.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Scalar.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Scalar.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Scalar.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm b/test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm
rename to test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm
diff --git a/test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm.expected.glsl b/test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm.expected.glsl
similarity index 100%
rename from test/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm.expected.glsl
rename to test/tint/unittest/reader/spirv/SpvUnaryLogicalTest_LogicalNot_Vector.spvasm.expected.glsl
diff --git a/test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_0.spvasm b/test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_0.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_0.spvasm
rename to test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_0.spvasm
diff --git a/test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_1.spvasm b/test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_1.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_1.spvasm
rename to test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_1.spvasm
diff --git a/test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_2.spvasm b/test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_2.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_2.spvasm
rename to test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_2.spvasm
diff --git a/test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_3.spvasm b/test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_3.spvasm
similarity index 100%
rename from test/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_3.spvasm
rename to test/tint/unittest/reader/spirv/ValidIndex_SpvParserSwizzleTest_Sample_3.spvasm
diff --git a/test/var/inferred/function.wgsl b/test/tint/var/inferred/function.wgsl
similarity index 100%
rename from test/var/inferred/function.wgsl
rename to test/tint/var/inferred/function.wgsl
diff --git a/test/var/inferred/function.wgsl.expected.glsl b/test/tint/var/inferred/function.wgsl.expected.glsl
similarity index 100%
rename from test/var/inferred/function.wgsl.expected.glsl
rename to test/tint/var/inferred/function.wgsl.expected.glsl
diff --git a/test/var/inferred/function.wgsl.expected.hlsl b/test/tint/var/inferred/function.wgsl.expected.hlsl
similarity index 100%
rename from test/var/inferred/function.wgsl.expected.hlsl
rename to test/tint/var/inferred/function.wgsl.expected.hlsl
diff --git a/test/var/inferred/function.wgsl.expected.msl b/test/tint/var/inferred/function.wgsl.expected.msl
similarity index 100%
rename from test/var/inferred/function.wgsl.expected.msl
rename to test/tint/var/inferred/function.wgsl.expected.msl
diff --git a/test/var/inferred/function.wgsl.expected.spvasm b/test/tint/var/inferred/function.wgsl.expected.spvasm
similarity index 100%
rename from test/var/inferred/function.wgsl.expected.spvasm
rename to test/tint/var/inferred/function.wgsl.expected.spvasm
diff --git a/test/var/inferred/function.wgsl.expected.wgsl b/test/tint/var/inferred/function.wgsl.expected.wgsl
similarity index 100%
rename from test/var/inferred/function.wgsl.expected.wgsl
rename to test/tint/var/inferred/function.wgsl.expected.wgsl
diff --git a/test/var/initialization/function/array.wgsl b/test/tint/var/initialization/function/array.wgsl
similarity index 100%
rename from test/var/initialization/function/array.wgsl
rename to test/tint/var/initialization/function/array.wgsl
diff --git a/test/var/initialization/function/array.wgsl.expected.glsl b/test/tint/var/initialization/function/array.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/function/array.wgsl.expected.glsl
rename to test/tint/var/initialization/function/array.wgsl.expected.glsl
diff --git a/test/var/initialization/function/array.wgsl.expected.hlsl b/test/tint/var/initialization/function/array.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/function/array.wgsl.expected.hlsl
rename to test/tint/var/initialization/function/array.wgsl.expected.hlsl
diff --git a/test/var/initialization/function/array.wgsl.expected.msl b/test/tint/var/initialization/function/array.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/function/array.wgsl.expected.msl
rename to test/tint/var/initialization/function/array.wgsl.expected.msl
diff --git a/test/var/initialization/function/array.wgsl.expected.spvasm b/test/tint/var/initialization/function/array.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/function/array.wgsl.expected.spvasm
rename to test/tint/var/initialization/function/array.wgsl.expected.spvasm
diff --git a/test/var/initialization/function/array.wgsl.expected.wgsl b/test/tint/var/initialization/function/array.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/function/array.wgsl.expected.wgsl
rename to test/tint/var/initialization/function/array.wgsl.expected.wgsl
diff --git a/test/var/initialization/function/matrix.wgsl b/test/tint/var/initialization/function/matrix.wgsl
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl
rename to test/tint/var/initialization/function/matrix.wgsl
diff --git a/test/var/initialization/function/matrix.wgsl.expected.glsl b/test/tint/var/initialization/function/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl.expected.glsl
rename to test/tint/var/initialization/function/matrix.wgsl.expected.glsl
diff --git a/test/var/initialization/function/matrix.wgsl.expected.hlsl b/test/tint/var/initialization/function/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl.expected.hlsl
rename to test/tint/var/initialization/function/matrix.wgsl.expected.hlsl
diff --git a/test/var/initialization/function/matrix.wgsl.expected.msl b/test/tint/var/initialization/function/matrix.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl.expected.msl
rename to test/tint/var/initialization/function/matrix.wgsl.expected.msl
diff --git a/test/var/initialization/function/matrix.wgsl.expected.spvasm b/test/tint/var/initialization/function/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl.expected.spvasm
rename to test/tint/var/initialization/function/matrix.wgsl.expected.spvasm
diff --git a/test/var/initialization/function/matrix.wgsl.expected.wgsl b/test/tint/var/initialization/function/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/function/matrix.wgsl.expected.wgsl
rename to test/tint/var/initialization/function/matrix.wgsl.expected.wgsl
diff --git a/test/var/initialization/function/scalar.wgsl b/test/tint/var/initialization/function/scalar.wgsl
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl
rename to test/tint/var/initialization/function/scalar.wgsl
diff --git a/test/var/initialization/function/scalar.wgsl.expected.glsl b/test/tint/var/initialization/function/scalar.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl.expected.glsl
rename to test/tint/var/initialization/function/scalar.wgsl.expected.glsl
diff --git a/test/var/initialization/function/scalar.wgsl.expected.hlsl b/test/tint/var/initialization/function/scalar.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl.expected.hlsl
rename to test/tint/var/initialization/function/scalar.wgsl.expected.hlsl
diff --git a/test/var/initialization/function/scalar.wgsl.expected.msl b/test/tint/var/initialization/function/scalar.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl.expected.msl
rename to test/tint/var/initialization/function/scalar.wgsl.expected.msl
diff --git a/test/var/initialization/function/scalar.wgsl.expected.spvasm b/test/tint/var/initialization/function/scalar.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl.expected.spvasm
rename to test/tint/var/initialization/function/scalar.wgsl.expected.spvasm
diff --git a/test/var/initialization/function/scalar.wgsl.expected.wgsl b/test/tint/var/initialization/function/scalar.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/function/scalar.wgsl.expected.wgsl
rename to test/tint/var/initialization/function/scalar.wgsl.expected.wgsl
diff --git a/test/var/initialization/function/struct.wgsl b/test/tint/var/initialization/function/struct.wgsl
similarity index 100%
rename from test/var/initialization/function/struct.wgsl
rename to test/tint/var/initialization/function/struct.wgsl
diff --git a/test/var/initialization/function/struct.wgsl.expected.glsl b/test/tint/var/initialization/function/struct.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/function/struct.wgsl.expected.glsl
rename to test/tint/var/initialization/function/struct.wgsl.expected.glsl
diff --git a/test/var/initialization/function/struct.wgsl.expected.hlsl b/test/tint/var/initialization/function/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/function/struct.wgsl.expected.hlsl
rename to test/tint/var/initialization/function/struct.wgsl.expected.hlsl
diff --git a/test/var/initialization/function/struct.wgsl.expected.msl b/test/tint/var/initialization/function/struct.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/function/struct.wgsl.expected.msl
rename to test/tint/var/initialization/function/struct.wgsl.expected.msl
diff --git a/test/var/initialization/function/struct.wgsl.expected.spvasm b/test/tint/var/initialization/function/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/function/struct.wgsl.expected.spvasm
rename to test/tint/var/initialization/function/struct.wgsl.expected.spvasm
diff --git a/test/var/initialization/function/struct.wgsl.expected.wgsl b/test/tint/var/initialization/function/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/function/struct.wgsl.expected.wgsl
rename to test/tint/var/initialization/function/struct.wgsl.expected.wgsl
diff --git a/test/var/initialization/function/vector.wgsl b/test/tint/var/initialization/function/vector.wgsl
similarity index 100%
rename from test/var/initialization/function/vector.wgsl
rename to test/tint/var/initialization/function/vector.wgsl
diff --git a/test/var/initialization/function/vector.wgsl.expected.glsl b/test/tint/var/initialization/function/vector.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/function/vector.wgsl.expected.glsl
rename to test/tint/var/initialization/function/vector.wgsl.expected.glsl
diff --git a/test/var/initialization/function/vector.wgsl.expected.hlsl b/test/tint/var/initialization/function/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/function/vector.wgsl.expected.hlsl
rename to test/tint/var/initialization/function/vector.wgsl.expected.hlsl
diff --git a/test/var/initialization/function/vector.wgsl.expected.msl b/test/tint/var/initialization/function/vector.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/function/vector.wgsl.expected.msl
rename to test/tint/var/initialization/function/vector.wgsl.expected.msl
diff --git a/test/var/initialization/function/vector.wgsl.expected.spvasm b/test/tint/var/initialization/function/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/function/vector.wgsl.expected.spvasm
rename to test/tint/var/initialization/function/vector.wgsl.expected.spvasm
diff --git a/test/var/initialization/function/vector.wgsl.expected.wgsl b/test/tint/var/initialization/function/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/function/vector.wgsl.expected.wgsl
rename to test/tint/var/initialization/function/vector.wgsl.expected.wgsl
diff --git a/test/var/initialization/private/array.wgsl b/test/tint/var/initialization/private/array.wgsl
similarity index 100%
rename from test/var/initialization/private/array.wgsl
rename to test/tint/var/initialization/private/array.wgsl
diff --git a/test/var/initialization/private/array.wgsl.expected.glsl b/test/tint/var/initialization/private/array.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/private/array.wgsl.expected.glsl
rename to test/tint/var/initialization/private/array.wgsl.expected.glsl
diff --git a/test/var/initialization/private/array.wgsl.expected.hlsl b/test/tint/var/initialization/private/array.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/private/array.wgsl.expected.hlsl
rename to test/tint/var/initialization/private/array.wgsl.expected.hlsl
diff --git a/test/var/initialization/private/array.wgsl.expected.msl b/test/tint/var/initialization/private/array.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/private/array.wgsl.expected.msl
rename to test/tint/var/initialization/private/array.wgsl.expected.msl
diff --git a/test/var/initialization/private/array.wgsl.expected.spvasm b/test/tint/var/initialization/private/array.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/private/array.wgsl.expected.spvasm
rename to test/tint/var/initialization/private/array.wgsl.expected.spvasm
diff --git a/test/var/initialization/private/array.wgsl.expected.wgsl b/test/tint/var/initialization/private/array.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/private/array.wgsl.expected.wgsl
rename to test/tint/var/initialization/private/array.wgsl.expected.wgsl
diff --git a/test/var/initialization/private/matrix.wgsl b/test/tint/var/initialization/private/matrix.wgsl
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl
rename to test/tint/var/initialization/private/matrix.wgsl
diff --git a/test/var/initialization/private/matrix.wgsl.expected.glsl b/test/tint/var/initialization/private/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl.expected.glsl
rename to test/tint/var/initialization/private/matrix.wgsl.expected.glsl
diff --git a/test/var/initialization/private/matrix.wgsl.expected.hlsl b/test/tint/var/initialization/private/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl.expected.hlsl
rename to test/tint/var/initialization/private/matrix.wgsl.expected.hlsl
diff --git a/test/var/initialization/private/matrix.wgsl.expected.msl b/test/tint/var/initialization/private/matrix.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl.expected.msl
rename to test/tint/var/initialization/private/matrix.wgsl.expected.msl
diff --git a/test/var/initialization/private/matrix.wgsl.expected.spvasm b/test/tint/var/initialization/private/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl.expected.spvasm
rename to test/tint/var/initialization/private/matrix.wgsl.expected.spvasm
diff --git a/test/var/initialization/private/matrix.wgsl.expected.wgsl b/test/tint/var/initialization/private/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/private/matrix.wgsl.expected.wgsl
rename to test/tint/var/initialization/private/matrix.wgsl.expected.wgsl
diff --git a/test/var/initialization/private/scalar.wgsl b/test/tint/var/initialization/private/scalar.wgsl
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl
rename to test/tint/var/initialization/private/scalar.wgsl
diff --git a/test/var/initialization/private/scalar.wgsl.expected.glsl b/test/tint/var/initialization/private/scalar.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl.expected.glsl
rename to test/tint/var/initialization/private/scalar.wgsl.expected.glsl
diff --git a/test/var/initialization/private/scalar.wgsl.expected.hlsl b/test/tint/var/initialization/private/scalar.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl.expected.hlsl
rename to test/tint/var/initialization/private/scalar.wgsl.expected.hlsl
diff --git a/test/var/initialization/private/scalar.wgsl.expected.msl b/test/tint/var/initialization/private/scalar.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl.expected.msl
rename to test/tint/var/initialization/private/scalar.wgsl.expected.msl
diff --git a/test/var/initialization/private/scalar.wgsl.expected.spvasm b/test/tint/var/initialization/private/scalar.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl.expected.spvasm
rename to test/tint/var/initialization/private/scalar.wgsl.expected.spvasm
diff --git a/test/var/initialization/private/scalar.wgsl.expected.wgsl b/test/tint/var/initialization/private/scalar.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/private/scalar.wgsl.expected.wgsl
rename to test/tint/var/initialization/private/scalar.wgsl.expected.wgsl
diff --git a/test/var/initialization/private/struct.wgsl b/test/tint/var/initialization/private/struct.wgsl
similarity index 100%
rename from test/var/initialization/private/struct.wgsl
rename to test/tint/var/initialization/private/struct.wgsl
diff --git a/test/var/initialization/private/struct.wgsl.expected.glsl b/test/tint/var/initialization/private/struct.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/private/struct.wgsl.expected.glsl
rename to test/tint/var/initialization/private/struct.wgsl.expected.glsl
diff --git a/test/var/initialization/private/struct.wgsl.expected.hlsl b/test/tint/var/initialization/private/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/private/struct.wgsl.expected.hlsl
rename to test/tint/var/initialization/private/struct.wgsl.expected.hlsl
diff --git a/test/var/initialization/private/struct.wgsl.expected.msl b/test/tint/var/initialization/private/struct.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/private/struct.wgsl.expected.msl
rename to test/tint/var/initialization/private/struct.wgsl.expected.msl
diff --git a/test/var/initialization/private/struct.wgsl.expected.spvasm b/test/tint/var/initialization/private/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/private/struct.wgsl.expected.spvasm
rename to test/tint/var/initialization/private/struct.wgsl.expected.spvasm
diff --git a/test/var/initialization/private/struct.wgsl.expected.wgsl b/test/tint/var/initialization/private/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/private/struct.wgsl.expected.wgsl
rename to test/tint/var/initialization/private/struct.wgsl.expected.wgsl
diff --git a/test/var/initialization/private/vector.wgsl b/test/tint/var/initialization/private/vector.wgsl
similarity index 100%
rename from test/var/initialization/private/vector.wgsl
rename to test/tint/var/initialization/private/vector.wgsl
diff --git a/test/var/initialization/private/vector.wgsl.expected.glsl b/test/tint/var/initialization/private/vector.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/private/vector.wgsl.expected.glsl
rename to test/tint/var/initialization/private/vector.wgsl.expected.glsl
diff --git a/test/var/initialization/private/vector.wgsl.expected.hlsl b/test/tint/var/initialization/private/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/private/vector.wgsl.expected.hlsl
rename to test/tint/var/initialization/private/vector.wgsl.expected.hlsl
diff --git a/test/var/initialization/private/vector.wgsl.expected.msl b/test/tint/var/initialization/private/vector.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/private/vector.wgsl.expected.msl
rename to test/tint/var/initialization/private/vector.wgsl.expected.msl
diff --git a/test/var/initialization/private/vector.wgsl.expected.spvasm b/test/tint/var/initialization/private/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/private/vector.wgsl.expected.spvasm
rename to test/tint/var/initialization/private/vector.wgsl.expected.spvasm
diff --git a/test/var/initialization/private/vector.wgsl.expected.wgsl b/test/tint/var/initialization/private/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/private/vector.wgsl.expected.wgsl
rename to test/tint/var/initialization/private/vector.wgsl.expected.wgsl
diff --git a/test/var/initialization/workgroup/array.wgsl b/test/tint/var/initialization/workgroup/array.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl
rename to test/tint/var/initialization/workgroup/array.wgsl
diff --git a/test/var/initialization/workgroup/array.wgsl.expected.glsl b/test/tint/var/initialization/workgroup/array.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl.expected.glsl
rename to test/tint/var/initialization/workgroup/array.wgsl.expected.glsl
diff --git a/test/var/initialization/workgroup/array.wgsl.expected.hlsl b/test/tint/var/initialization/workgroup/array.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl.expected.hlsl
rename to test/tint/var/initialization/workgroup/array.wgsl.expected.hlsl
diff --git a/test/var/initialization/workgroup/array.wgsl.expected.msl b/test/tint/var/initialization/workgroup/array.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl.expected.msl
rename to test/tint/var/initialization/workgroup/array.wgsl.expected.msl
diff --git a/test/var/initialization/workgroup/array.wgsl.expected.spvasm b/test/tint/var/initialization/workgroup/array.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl.expected.spvasm
rename to test/tint/var/initialization/workgroup/array.wgsl.expected.spvasm
diff --git a/test/var/initialization/workgroup/array.wgsl.expected.wgsl b/test/tint/var/initialization/workgroup/array.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/array.wgsl.expected.wgsl
rename to test/tint/var/initialization/workgroup/array.wgsl.expected.wgsl
diff --git a/test/var/initialization/workgroup/matrix.wgsl b/test/tint/var/initialization/workgroup/matrix.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl
rename to test/tint/var/initialization/workgroup/matrix.wgsl
diff --git a/test/var/initialization/workgroup/matrix.wgsl.expected.glsl b/test/tint/var/initialization/workgroup/matrix.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl.expected.glsl
rename to test/tint/var/initialization/workgroup/matrix.wgsl.expected.glsl
diff --git a/test/var/initialization/workgroup/matrix.wgsl.expected.hlsl b/test/tint/var/initialization/workgroup/matrix.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl.expected.hlsl
rename to test/tint/var/initialization/workgroup/matrix.wgsl.expected.hlsl
diff --git a/test/var/initialization/workgroup/matrix.wgsl.expected.msl b/test/tint/var/initialization/workgroup/matrix.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl.expected.msl
rename to test/tint/var/initialization/workgroup/matrix.wgsl.expected.msl
diff --git a/test/var/initialization/workgroup/matrix.wgsl.expected.spvasm b/test/tint/var/initialization/workgroup/matrix.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl.expected.spvasm
rename to test/tint/var/initialization/workgroup/matrix.wgsl.expected.spvasm
diff --git a/test/var/initialization/workgroup/matrix.wgsl.expected.wgsl b/test/tint/var/initialization/workgroup/matrix.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/matrix.wgsl.expected.wgsl
rename to test/tint/var/initialization/workgroup/matrix.wgsl.expected.wgsl
diff --git a/test/var/initialization/workgroup/scalar.wgsl b/test/tint/var/initialization/workgroup/scalar.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl
rename to test/tint/var/initialization/workgroup/scalar.wgsl
diff --git a/test/var/initialization/workgroup/scalar.wgsl.expected.glsl b/test/tint/var/initialization/workgroup/scalar.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl.expected.glsl
rename to test/tint/var/initialization/workgroup/scalar.wgsl.expected.glsl
diff --git a/test/var/initialization/workgroup/scalar.wgsl.expected.hlsl b/test/tint/var/initialization/workgroup/scalar.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl.expected.hlsl
rename to test/tint/var/initialization/workgroup/scalar.wgsl.expected.hlsl
diff --git a/test/var/initialization/workgroup/scalar.wgsl.expected.msl b/test/tint/var/initialization/workgroup/scalar.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl.expected.msl
rename to test/tint/var/initialization/workgroup/scalar.wgsl.expected.msl
diff --git a/test/var/initialization/workgroup/scalar.wgsl.expected.spvasm b/test/tint/var/initialization/workgroup/scalar.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl.expected.spvasm
rename to test/tint/var/initialization/workgroup/scalar.wgsl.expected.spvasm
diff --git a/test/var/initialization/workgroup/scalar.wgsl.expected.wgsl b/test/tint/var/initialization/workgroup/scalar.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/scalar.wgsl.expected.wgsl
rename to test/tint/var/initialization/workgroup/scalar.wgsl.expected.wgsl
diff --git a/test/var/initialization/workgroup/struct.wgsl b/test/tint/var/initialization/workgroup/struct.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl
rename to test/tint/var/initialization/workgroup/struct.wgsl
diff --git a/test/var/initialization/workgroup/struct.wgsl.expected.glsl b/test/tint/var/initialization/workgroup/struct.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl.expected.glsl
rename to test/tint/var/initialization/workgroup/struct.wgsl.expected.glsl
diff --git a/test/var/initialization/workgroup/struct.wgsl.expected.hlsl b/test/tint/var/initialization/workgroup/struct.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl.expected.hlsl
rename to test/tint/var/initialization/workgroup/struct.wgsl.expected.hlsl
diff --git a/test/var/initialization/workgroup/struct.wgsl.expected.msl b/test/tint/var/initialization/workgroup/struct.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl.expected.msl
rename to test/tint/var/initialization/workgroup/struct.wgsl.expected.msl
diff --git a/test/var/initialization/workgroup/struct.wgsl.expected.spvasm b/test/tint/var/initialization/workgroup/struct.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl.expected.spvasm
rename to test/tint/var/initialization/workgroup/struct.wgsl.expected.spvasm
diff --git a/test/var/initialization/workgroup/struct.wgsl.expected.wgsl b/test/tint/var/initialization/workgroup/struct.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/struct.wgsl.expected.wgsl
rename to test/tint/var/initialization/workgroup/struct.wgsl.expected.wgsl
diff --git a/test/var/initialization/workgroup/vector.wgsl b/test/tint/var/initialization/workgroup/vector.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl
rename to test/tint/var/initialization/workgroup/vector.wgsl
diff --git a/test/var/initialization/workgroup/vector.wgsl.expected.glsl b/test/tint/var/initialization/workgroup/vector.wgsl.expected.glsl
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl.expected.glsl
rename to test/tint/var/initialization/workgroup/vector.wgsl.expected.glsl
diff --git a/test/var/initialization/workgroup/vector.wgsl.expected.hlsl b/test/tint/var/initialization/workgroup/vector.wgsl.expected.hlsl
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl.expected.hlsl
rename to test/tint/var/initialization/workgroup/vector.wgsl.expected.hlsl
diff --git a/test/var/initialization/workgroup/vector.wgsl.expected.msl b/test/tint/var/initialization/workgroup/vector.wgsl.expected.msl
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl.expected.msl
rename to test/tint/var/initialization/workgroup/vector.wgsl.expected.msl
diff --git a/test/var/initialization/workgroup/vector.wgsl.expected.spvasm b/test/tint/var/initialization/workgroup/vector.wgsl.expected.spvasm
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl.expected.spvasm
rename to test/tint/var/initialization/workgroup/vector.wgsl.expected.spvasm
diff --git a/test/var/initialization/workgroup/vector.wgsl.expected.wgsl b/test/tint/var/initialization/workgroup/vector.wgsl.expected.wgsl
similarity index 100%
rename from test/var/initialization/workgroup/vector.wgsl.expected.wgsl
rename to test/tint/var/initialization/workgroup/vector.wgsl.expected.wgsl
diff --git a/test/var/override/named/no_init/bool.wgsl b/test/tint/var/override/named/no_init/bool.wgsl
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl
rename to test/tint/var/override/named/no_init/bool.wgsl
diff --git a/test/var/override/named/no_init/bool.wgsl.expected.glsl b/test/tint/var/override/named/no_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/named/no_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/named/no_init/bool.wgsl.expected.hlsl b/test/tint/var/override/named/no_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/named/no_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/named/no_init/bool.wgsl.expected.msl b/test/tint/var/override/named/no_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl.expected.msl
rename to test/tint/var/override/named/no_init/bool.wgsl.expected.msl
diff --git a/test/var/override/named/no_init/bool.wgsl.expected.spvasm b/test/tint/var/override/named/no_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/named/no_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/named/no_init/bool.wgsl.expected.wgsl b/test/tint/var/override/named/no_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/no_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/named/no_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/named/no_init/f32.wgsl b/test/tint/var/override/named/no_init/f32.wgsl
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl
rename to test/tint/var/override/named/no_init/f32.wgsl
diff --git a/test/var/override/named/no_init/f32.wgsl.expected.glsl b/test/tint/var/override/named/no_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/named/no_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/named/no_init/f32.wgsl.expected.hlsl b/test/tint/var/override/named/no_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/named/no_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/named/no_init/f32.wgsl.expected.msl b/test/tint/var/override/named/no_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl.expected.msl
rename to test/tint/var/override/named/no_init/f32.wgsl.expected.msl
diff --git a/test/var/override/named/no_init/f32.wgsl.expected.spvasm b/test/tint/var/override/named/no_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/named/no_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/named/no_init/f32.wgsl.expected.wgsl b/test/tint/var/override/named/no_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/no_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/named/no_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/named/no_init/i32.wgsl b/test/tint/var/override/named/no_init/i32.wgsl
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl
rename to test/tint/var/override/named/no_init/i32.wgsl
diff --git a/test/var/override/named/no_init/i32.wgsl.expected.glsl b/test/tint/var/override/named/no_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/named/no_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/named/no_init/i32.wgsl.expected.hlsl b/test/tint/var/override/named/no_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/named/no_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/named/no_init/i32.wgsl.expected.msl b/test/tint/var/override/named/no_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl.expected.msl
rename to test/tint/var/override/named/no_init/i32.wgsl.expected.msl
diff --git a/test/var/override/named/no_init/i32.wgsl.expected.spvasm b/test/tint/var/override/named/no_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/named/no_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/named/no_init/i32.wgsl.expected.wgsl b/test/tint/var/override/named/no_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/no_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/named/no_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/named/no_init/u32.wgsl b/test/tint/var/override/named/no_init/u32.wgsl
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl
rename to test/tint/var/override/named/no_init/u32.wgsl
diff --git a/test/var/override/named/no_init/u32.wgsl.expected.glsl b/test/tint/var/override/named/no_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/named/no_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/named/no_init/u32.wgsl.expected.hlsl b/test/tint/var/override/named/no_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/named/no_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/named/no_init/u32.wgsl.expected.msl b/test/tint/var/override/named/no_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl.expected.msl
rename to test/tint/var/override/named/no_init/u32.wgsl.expected.msl
diff --git a/test/var/override/named/no_init/u32.wgsl.expected.spvasm b/test/tint/var/override/named/no_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/named/no_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/named/no_init/u32.wgsl.expected.wgsl b/test/tint/var/override/named/no_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/no_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/named/no_init/u32.wgsl.expected.wgsl
diff --git a/test/var/override/named/val_init/bool.wgsl b/test/tint/var/override/named/val_init/bool.wgsl
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl
rename to test/tint/var/override/named/val_init/bool.wgsl
diff --git a/test/var/override/named/val_init/bool.wgsl.expected.glsl b/test/tint/var/override/named/val_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/named/val_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/named/val_init/bool.wgsl.expected.hlsl b/test/tint/var/override/named/val_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/named/val_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/named/val_init/bool.wgsl.expected.msl b/test/tint/var/override/named/val_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl.expected.msl
rename to test/tint/var/override/named/val_init/bool.wgsl.expected.msl
diff --git a/test/var/override/named/val_init/bool.wgsl.expected.spvasm b/test/tint/var/override/named/val_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/named/val_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/named/val_init/bool.wgsl.expected.wgsl b/test/tint/var/override/named/val_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/val_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/named/val_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/named/val_init/f32.wgsl b/test/tint/var/override/named/val_init/f32.wgsl
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl
rename to test/tint/var/override/named/val_init/f32.wgsl
diff --git a/test/var/override/named/val_init/f32.wgsl.expected.glsl b/test/tint/var/override/named/val_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/named/val_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/named/val_init/f32.wgsl.expected.hlsl b/test/tint/var/override/named/val_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/named/val_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/named/val_init/f32.wgsl.expected.msl b/test/tint/var/override/named/val_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl.expected.msl
rename to test/tint/var/override/named/val_init/f32.wgsl.expected.msl
diff --git a/test/var/override/named/val_init/f32.wgsl.expected.spvasm b/test/tint/var/override/named/val_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/named/val_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/named/val_init/f32.wgsl.expected.wgsl b/test/tint/var/override/named/val_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/val_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/named/val_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/named/val_init/i32.wgsl b/test/tint/var/override/named/val_init/i32.wgsl
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl
rename to test/tint/var/override/named/val_init/i32.wgsl
diff --git a/test/var/override/named/val_init/i32.wgsl.expected.glsl b/test/tint/var/override/named/val_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/named/val_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/named/val_init/i32.wgsl.expected.hlsl b/test/tint/var/override/named/val_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/named/val_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/named/val_init/i32.wgsl.expected.msl b/test/tint/var/override/named/val_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl.expected.msl
rename to test/tint/var/override/named/val_init/i32.wgsl.expected.msl
diff --git a/test/var/override/named/val_init/i32.wgsl.expected.spvasm b/test/tint/var/override/named/val_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/named/val_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/named/val_init/i32.wgsl.expected.wgsl b/test/tint/var/override/named/val_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/val_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/named/val_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/named/val_init/u32.wgsl b/test/tint/var/override/named/val_init/u32.wgsl
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl
rename to test/tint/var/override/named/val_init/u32.wgsl
diff --git a/test/var/override/named/val_init/u32.wgsl.expected.glsl b/test/tint/var/override/named/val_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/named/val_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/named/val_init/u32.wgsl.expected.hlsl b/test/tint/var/override/named/val_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/named/val_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/named/val_init/u32.wgsl.expected.msl b/test/tint/var/override/named/val_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl.expected.msl
rename to test/tint/var/override/named/val_init/u32.wgsl.expected.msl
diff --git a/test/var/override/named/val_init/u32.wgsl.expected.spvasm b/test/tint/var/override/named/val_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/named/val_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/named/val_init/u32.wgsl.expected.wgsl b/test/tint/var/override/named/val_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/val_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/named/val_init/u32.wgsl.expected.wgsl
diff --git a/test/var/override/named/zero_init/bool.wgsl b/test/tint/var/override/named/zero_init/bool.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl
rename to test/tint/var/override/named/zero_init/bool.wgsl
diff --git a/test/var/override/named/zero_init/bool.wgsl.expected.glsl b/test/tint/var/override/named/zero_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/named/zero_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/named/zero_init/bool.wgsl.expected.hlsl b/test/tint/var/override/named/zero_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/named/zero_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/named/zero_init/bool.wgsl.expected.msl b/test/tint/var/override/named/zero_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl.expected.msl
rename to test/tint/var/override/named/zero_init/bool.wgsl.expected.msl
diff --git a/test/var/override/named/zero_init/bool.wgsl.expected.spvasm b/test/tint/var/override/named/zero_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/named/zero_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/named/zero_init/bool.wgsl.expected.wgsl b/test/tint/var/override/named/zero_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/named/zero_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/named/zero_init/f32.wgsl b/test/tint/var/override/named/zero_init/f32.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl
rename to test/tint/var/override/named/zero_init/f32.wgsl
diff --git a/test/var/override/named/zero_init/f32.wgsl.expected.glsl b/test/tint/var/override/named/zero_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/named/zero_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/named/zero_init/f32.wgsl.expected.hlsl b/test/tint/var/override/named/zero_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/named/zero_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/named/zero_init/f32.wgsl.expected.msl b/test/tint/var/override/named/zero_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl.expected.msl
rename to test/tint/var/override/named/zero_init/f32.wgsl.expected.msl
diff --git a/test/var/override/named/zero_init/f32.wgsl.expected.spvasm b/test/tint/var/override/named/zero_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/named/zero_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/named/zero_init/f32.wgsl.expected.wgsl b/test/tint/var/override/named/zero_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/named/zero_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/named/zero_init/i32.wgsl b/test/tint/var/override/named/zero_init/i32.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl
rename to test/tint/var/override/named/zero_init/i32.wgsl
diff --git a/test/var/override/named/zero_init/i32.wgsl.expected.glsl b/test/tint/var/override/named/zero_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/named/zero_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/named/zero_init/i32.wgsl.expected.hlsl b/test/tint/var/override/named/zero_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/named/zero_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/named/zero_init/i32.wgsl.expected.msl b/test/tint/var/override/named/zero_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl.expected.msl
rename to test/tint/var/override/named/zero_init/i32.wgsl.expected.msl
diff --git a/test/var/override/named/zero_init/i32.wgsl.expected.spvasm b/test/tint/var/override/named/zero_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/named/zero_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/named/zero_init/i32.wgsl.expected.wgsl b/test/tint/var/override/named/zero_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/named/zero_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/named/zero_init/u32.wgsl b/test/tint/var/override/named/zero_init/u32.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl
rename to test/tint/var/override/named/zero_init/u32.wgsl
diff --git a/test/var/override/named/zero_init/u32.wgsl.expected.glsl b/test/tint/var/override/named/zero_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/named/zero_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/named/zero_init/u32.wgsl.expected.hlsl b/test/tint/var/override/named/zero_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/named/zero_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/named/zero_init/u32.wgsl.expected.msl b/test/tint/var/override/named/zero_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl.expected.msl
rename to test/tint/var/override/named/zero_init/u32.wgsl.expected.msl
diff --git a/test/var/override/named/zero_init/u32.wgsl.expected.spvasm b/test/tint/var/override/named/zero_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/named/zero_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/named/zero_init/u32.wgsl.expected.wgsl b/test/tint/var/override/named/zero_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/named/zero_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/named/zero_init/u32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/no_init/bool.wgsl b/test/tint/var/override/numbered/no_init/bool.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl
rename to test/tint/var/override/numbered/no_init/bool.wgsl
diff --git a/test/var/override/numbered/no_init/bool.wgsl.expected.glsl b/test/tint/var/override/numbered/no_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/numbered/no_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/numbered/no_init/bool.wgsl.expected.hlsl b/test/tint/var/override/numbered/no_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/no_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/no_init/bool.wgsl.expected.msl b/test/tint/var/override/numbered/no_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl.expected.msl
rename to test/tint/var/override/numbered/no_init/bool.wgsl.expected.msl
diff --git a/test/var/override/numbered/no_init/bool.wgsl.expected.spvasm b/test/tint/var/override/numbered/no_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/no_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/no_init/bool.wgsl.expected.wgsl b/test/tint/var/override/numbered/no_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/no_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/no_init/f32.wgsl b/test/tint/var/override/numbered/no_init/f32.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl
rename to test/tint/var/override/numbered/no_init/f32.wgsl
diff --git a/test/var/override/numbered/no_init/f32.wgsl.expected.glsl b/test/tint/var/override/numbered/no_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/no_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/no_init/f32.wgsl.expected.hlsl b/test/tint/var/override/numbered/no_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/no_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/no_init/f32.wgsl.expected.msl b/test/tint/var/override/numbered/no_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl.expected.msl
rename to test/tint/var/override/numbered/no_init/f32.wgsl.expected.msl
diff --git a/test/var/override/numbered/no_init/f32.wgsl.expected.spvasm b/test/tint/var/override/numbered/no_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/no_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/no_init/f32.wgsl.expected.wgsl b/test/tint/var/override/numbered/no_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/no_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/no_init/i32.wgsl b/test/tint/var/override/numbered/no_init/i32.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl
rename to test/tint/var/override/numbered/no_init/i32.wgsl
diff --git a/test/var/override/numbered/no_init/i32.wgsl.expected.glsl b/test/tint/var/override/numbered/no_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/no_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/no_init/i32.wgsl.expected.hlsl b/test/tint/var/override/numbered/no_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/no_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/no_init/i32.wgsl.expected.msl b/test/tint/var/override/numbered/no_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl.expected.msl
rename to test/tint/var/override/numbered/no_init/i32.wgsl.expected.msl
diff --git a/test/var/override/numbered/no_init/i32.wgsl.expected.spvasm b/test/tint/var/override/numbered/no_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/no_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/no_init/i32.wgsl.expected.wgsl b/test/tint/var/override/numbered/no_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/no_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/no_init/u32.wgsl b/test/tint/var/override/numbered/no_init/u32.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl
rename to test/tint/var/override/numbered/no_init/u32.wgsl
diff --git a/test/var/override/numbered/no_init/u32.wgsl.expected.glsl b/test/tint/var/override/numbered/no_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/no_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/no_init/u32.wgsl.expected.hlsl b/test/tint/var/override/numbered/no_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/no_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/no_init/u32.wgsl.expected.msl b/test/tint/var/override/numbered/no_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl.expected.msl
rename to test/tint/var/override/numbered/no_init/u32.wgsl.expected.msl
diff --git a/test/var/override/numbered/no_init/u32.wgsl.expected.spvasm b/test/tint/var/override/numbered/no_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/no_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/no_init/u32.wgsl.expected.wgsl b/test/tint/var/override/numbered/no_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/no_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/no_init/u32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/val_init/bool.wgsl b/test/tint/var/override/numbered/val_init/bool.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl
rename to test/tint/var/override/numbered/val_init/bool.wgsl
diff --git a/test/var/override/numbered/val_init/bool.wgsl.expected.glsl b/test/tint/var/override/numbered/val_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/numbered/val_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/numbered/val_init/bool.wgsl.expected.hlsl b/test/tint/var/override/numbered/val_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/val_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/val_init/bool.wgsl.expected.msl b/test/tint/var/override/numbered/val_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl.expected.msl
rename to test/tint/var/override/numbered/val_init/bool.wgsl.expected.msl
diff --git a/test/var/override/numbered/val_init/bool.wgsl.expected.spvasm b/test/tint/var/override/numbered/val_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/val_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/val_init/bool.wgsl.expected.wgsl b/test/tint/var/override/numbered/val_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/val_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/val_init/f32.wgsl b/test/tint/var/override/numbered/val_init/f32.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl
rename to test/tint/var/override/numbered/val_init/f32.wgsl
diff --git a/test/var/override/numbered/val_init/f32.wgsl.expected.glsl b/test/tint/var/override/numbered/val_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/val_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/val_init/f32.wgsl.expected.hlsl b/test/tint/var/override/numbered/val_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/val_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/val_init/f32.wgsl.expected.msl b/test/tint/var/override/numbered/val_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl.expected.msl
rename to test/tint/var/override/numbered/val_init/f32.wgsl.expected.msl
diff --git a/test/var/override/numbered/val_init/f32.wgsl.expected.spvasm b/test/tint/var/override/numbered/val_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/val_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/val_init/f32.wgsl.expected.wgsl b/test/tint/var/override/numbered/val_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/val_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/val_init/i32.wgsl b/test/tint/var/override/numbered/val_init/i32.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl
rename to test/tint/var/override/numbered/val_init/i32.wgsl
diff --git a/test/var/override/numbered/val_init/i32.wgsl.expected.glsl b/test/tint/var/override/numbered/val_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/val_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/val_init/i32.wgsl.expected.hlsl b/test/tint/var/override/numbered/val_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/val_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/val_init/i32.wgsl.expected.msl b/test/tint/var/override/numbered/val_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl.expected.msl
rename to test/tint/var/override/numbered/val_init/i32.wgsl.expected.msl
diff --git a/test/var/override/numbered/val_init/i32.wgsl.expected.spvasm b/test/tint/var/override/numbered/val_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/val_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/val_init/i32.wgsl.expected.wgsl b/test/tint/var/override/numbered/val_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/val_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/val_init/u32.wgsl b/test/tint/var/override/numbered/val_init/u32.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl
rename to test/tint/var/override/numbered/val_init/u32.wgsl
diff --git a/test/var/override/numbered/val_init/u32.wgsl.expected.glsl b/test/tint/var/override/numbered/val_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/val_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/val_init/u32.wgsl.expected.hlsl b/test/tint/var/override/numbered/val_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/val_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/val_init/u32.wgsl.expected.msl b/test/tint/var/override/numbered/val_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl.expected.msl
rename to test/tint/var/override/numbered/val_init/u32.wgsl.expected.msl
diff --git a/test/var/override/numbered/val_init/u32.wgsl.expected.spvasm b/test/tint/var/override/numbered/val_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/val_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/val_init/u32.wgsl.expected.wgsl b/test/tint/var/override/numbered/val_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/val_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/val_init/u32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/zero_init/bool.wgsl b/test/tint/var/override/numbered/zero_init/bool.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl
rename to test/tint/var/override/numbered/zero_init/bool.wgsl
diff --git a/test/var/override/numbered/zero_init/bool.wgsl.expected.glsl b/test/tint/var/override/numbered/zero_init/bool.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl.expected.glsl
rename to test/tint/var/override/numbered/zero_init/bool.wgsl.expected.glsl
diff --git a/test/var/override/numbered/zero_init/bool.wgsl.expected.hlsl b/test/tint/var/override/numbered/zero_init/bool.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/zero_init/bool.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/zero_init/bool.wgsl.expected.msl b/test/tint/var/override/numbered/zero_init/bool.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl.expected.msl
rename to test/tint/var/override/numbered/zero_init/bool.wgsl.expected.msl
diff --git a/test/var/override/numbered/zero_init/bool.wgsl.expected.spvasm b/test/tint/var/override/numbered/zero_init/bool.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/zero_init/bool.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/zero_init/bool.wgsl.expected.wgsl b/test/tint/var/override/numbered/zero_init/bool.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/bool.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/zero_init/bool.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/zero_init/f32.wgsl b/test/tint/var/override/numbered/zero_init/f32.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl
rename to test/tint/var/override/numbered/zero_init/f32.wgsl
diff --git a/test/var/override/numbered/zero_init/f32.wgsl.expected.glsl b/test/tint/var/override/numbered/zero_init/f32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/zero_init/f32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/zero_init/f32.wgsl.expected.hlsl b/test/tint/var/override/numbered/zero_init/f32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/zero_init/f32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/zero_init/f32.wgsl.expected.msl b/test/tint/var/override/numbered/zero_init/f32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl.expected.msl
rename to test/tint/var/override/numbered/zero_init/f32.wgsl.expected.msl
diff --git a/test/var/override/numbered/zero_init/f32.wgsl.expected.spvasm b/test/tint/var/override/numbered/zero_init/f32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/zero_init/f32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/zero_init/f32.wgsl.expected.wgsl b/test/tint/var/override/numbered/zero_init/f32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/f32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/zero_init/f32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/zero_init/i32.wgsl b/test/tint/var/override/numbered/zero_init/i32.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl
rename to test/tint/var/override/numbered/zero_init/i32.wgsl
diff --git a/test/var/override/numbered/zero_init/i32.wgsl.expected.glsl b/test/tint/var/override/numbered/zero_init/i32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/zero_init/i32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/zero_init/i32.wgsl.expected.hlsl b/test/tint/var/override/numbered/zero_init/i32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/zero_init/i32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/zero_init/i32.wgsl.expected.msl b/test/tint/var/override/numbered/zero_init/i32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl.expected.msl
rename to test/tint/var/override/numbered/zero_init/i32.wgsl.expected.msl
diff --git a/test/var/override/numbered/zero_init/i32.wgsl.expected.spvasm b/test/tint/var/override/numbered/zero_init/i32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/zero_init/i32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/zero_init/i32.wgsl.expected.wgsl b/test/tint/var/override/numbered/zero_init/i32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/i32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/zero_init/i32.wgsl.expected.wgsl
diff --git a/test/var/override/numbered/zero_init/u32.wgsl b/test/tint/var/override/numbered/zero_init/u32.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl
rename to test/tint/var/override/numbered/zero_init/u32.wgsl
diff --git a/test/var/override/numbered/zero_init/u32.wgsl.expected.glsl b/test/tint/var/override/numbered/zero_init/u32.wgsl.expected.glsl
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl.expected.glsl
rename to test/tint/var/override/numbered/zero_init/u32.wgsl.expected.glsl
diff --git a/test/var/override/numbered/zero_init/u32.wgsl.expected.hlsl b/test/tint/var/override/numbered/zero_init/u32.wgsl.expected.hlsl
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl.expected.hlsl
rename to test/tint/var/override/numbered/zero_init/u32.wgsl.expected.hlsl
diff --git a/test/var/override/numbered/zero_init/u32.wgsl.expected.msl b/test/tint/var/override/numbered/zero_init/u32.wgsl.expected.msl
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl.expected.msl
rename to test/tint/var/override/numbered/zero_init/u32.wgsl.expected.msl
diff --git a/test/var/override/numbered/zero_init/u32.wgsl.expected.spvasm b/test/tint/var/override/numbered/zero_init/u32.wgsl.expected.spvasm
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl.expected.spvasm
rename to test/tint/var/override/numbered/zero_init/u32.wgsl.expected.spvasm
diff --git a/test/var/override/numbered/zero_init/u32.wgsl.expected.wgsl b/test/tint/var/override/numbered/zero_init/u32.wgsl.expected.wgsl
similarity index 100%
rename from test/var/override/numbered/zero_init/u32.wgsl.expected.wgsl
rename to test/tint/var/override/numbered/zero_init/u32.wgsl.expected.wgsl
diff --git a/test/var/uses/many_workgroup_vars.wgsl b/test/tint/var/uses/many_workgroup_vars.wgsl
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl
rename to test/tint/var/uses/many_workgroup_vars.wgsl
diff --git a/test/var/uses/many_workgroup_vars.wgsl.expected.glsl b/test/tint/var/uses/many_workgroup_vars.wgsl.expected.glsl
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl.expected.glsl
rename to test/tint/var/uses/many_workgroup_vars.wgsl.expected.glsl
diff --git a/test/var/uses/many_workgroup_vars.wgsl.expected.hlsl b/test/tint/var/uses/many_workgroup_vars.wgsl.expected.hlsl
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl.expected.hlsl
rename to test/tint/var/uses/many_workgroup_vars.wgsl.expected.hlsl
diff --git a/test/var/uses/many_workgroup_vars.wgsl.expected.msl b/test/tint/var/uses/many_workgroup_vars.wgsl.expected.msl
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl.expected.msl
rename to test/tint/var/uses/many_workgroup_vars.wgsl.expected.msl
diff --git a/test/var/uses/many_workgroup_vars.wgsl.expected.spvasm b/test/tint/var/uses/many_workgroup_vars.wgsl.expected.spvasm
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl.expected.spvasm
rename to test/tint/var/uses/many_workgroup_vars.wgsl.expected.spvasm
diff --git a/test/var/uses/many_workgroup_vars.wgsl.expected.wgsl b/test/tint/var/uses/many_workgroup_vars.wgsl.expected.wgsl
similarity index 100%
rename from test/var/uses/many_workgroup_vars.wgsl.expected.wgsl
rename to test/tint/var/uses/many_workgroup_vars.wgsl.expected.wgsl
diff --git a/test/var/uses/private.wgsl b/test/tint/var/uses/private.wgsl
similarity index 100%
rename from test/var/uses/private.wgsl
rename to test/tint/var/uses/private.wgsl
diff --git a/test/var/uses/private.wgsl.expected.glsl b/test/tint/var/uses/private.wgsl.expected.glsl
similarity index 100%
rename from test/var/uses/private.wgsl.expected.glsl
rename to test/tint/var/uses/private.wgsl.expected.glsl
diff --git a/test/var/uses/private.wgsl.expected.hlsl b/test/tint/var/uses/private.wgsl.expected.hlsl
similarity index 100%
rename from test/var/uses/private.wgsl.expected.hlsl
rename to test/tint/var/uses/private.wgsl.expected.hlsl
diff --git a/test/var/uses/private.wgsl.expected.msl b/test/tint/var/uses/private.wgsl.expected.msl
similarity index 100%
rename from test/var/uses/private.wgsl.expected.msl
rename to test/tint/var/uses/private.wgsl.expected.msl
diff --git a/test/var/uses/private.wgsl.expected.spvasm b/test/tint/var/uses/private.wgsl.expected.spvasm
similarity index 100%
rename from test/var/uses/private.wgsl.expected.spvasm
rename to test/tint/var/uses/private.wgsl.expected.spvasm
diff --git a/test/var/uses/private.wgsl.expected.wgsl b/test/tint/var/uses/private.wgsl.expected.wgsl
similarity index 100%
rename from test/var/uses/private.wgsl.expected.wgsl
rename to test/tint/var/uses/private.wgsl.expected.wgsl
diff --git a/test/var/uses/workgroup.wgsl b/test/tint/var/uses/workgroup.wgsl
similarity index 100%
rename from test/var/uses/workgroup.wgsl
rename to test/tint/var/uses/workgroup.wgsl
diff --git a/test/var/uses/workgroup.wgsl.expected.glsl b/test/tint/var/uses/workgroup.wgsl.expected.glsl
similarity index 100%
rename from test/var/uses/workgroup.wgsl.expected.glsl
rename to test/tint/var/uses/workgroup.wgsl.expected.glsl
diff --git a/test/var/uses/workgroup.wgsl.expected.hlsl b/test/tint/var/uses/workgroup.wgsl.expected.hlsl
similarity index 100%
rename from test/var/uses/workgroup.wgsl.expected.hlsl
rename to test/tint/var/uses/workgroup.wgsl.expected.hlsl
diff --git a/test/var/uses/workgroup.wgsl.expected.msl b/test/tint/var/uses/workgroup.wgsl.expected.msl
similarity index 100%
rename from test/var/uses/workgroup.wgsl.expected.msl
rename to test/tint/var/uses/workgroup.wgsl.expected.msl
diff --git a/test/var/uses/workgroup.wgsl.expected.spvasm b/test/tint/var/uses/workgroup.wgsl.expected.spvasm
similarity index 100%
rename from test/var/uses/workgroup.wgsl.expected.spvasm
rename to test/tint/var/uses/workgroup.wgsl.expected.spvasm
diff --git a/test/var/uses/workgroup.wgsl.expected.wgsl b/test/tint/var/uses/workgroup.wgsl.expected.wgsl
similarity index 100%
rename from test/var/uses/workgroup.wgsl.expected.wgsl
rename to test/tint/var/uses/workgroup.wgsl.expected.wgsl
diff --git a/test/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.spvasm b/test/tint/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.spvasm
rename to test/tint/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.spvasm
diff --git a/test/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.wgsl b/test/tint/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.wgsl
rename to test/tint/vk-gl-cts/api/descriptor_set/descriptor_set_layout_binding/layout_binding_order/0.wgsl
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.spvasm b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.spvasm
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.spvasm
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.wgsl b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.wgsl
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_compute/0.wgsl
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.spvasm b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.spvasm
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.spvasm
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.wgsl b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.wgsl
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/0.wgsl
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.spvasm b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.spvasm
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.spvasm
diff --git a/test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.wgsl b/test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.wgsl
rename to test/tint/vk-gl-cts/binding_model/dynamic_offset/shader_reuse_differing_layout_graphics/1.wgsl
diff --git a/test/vk-gl-cts/combined_operations/negintdivand/0-opt.spvasm b/test/tint/vk-gl-cts/combined_operations/negintdivand/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/combined_operations/negintdivand/0-opt.spvasm
rename to test/tint/vk-gl-cts/combined_operations/negintdivand/0-opt.spvasm
diff --git a/test/vk-gl-cts/combined_operations/negintdivand/0-opt.wgsl b/test/tint/vk-gl-cts/combined_operations/negintdivand/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/combined_operations/negintdivand/0-opt.wgsl
rename to test/tint/vk-gl-cts/combined_operations/negintdivand/0-opt.wgsl
diff --git a/test/vk-gl-cts/combined_operations/negintdivand/1.spvasm b/test/tint/vk-gl-cts/combined_operations/negintdivand/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/combined_operations/negintdivand/1.spvasm
rename to test/tint/vk-gl-cts/combined_operations/negintdivand/1.spvasm
diff --git a/test/vk-gl-cts/combined_operations/negintdivand/1.wgsl b/test/tint/vk-gl-cts/combined_operations/negintdivand/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/combined_operations/negintdivand/1.wgsl
rename to test/tint/vk-gl-cts/combined_operations/negintdivand/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/access-new-vector-inside-if-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/always-discarding-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/arr-value-set-to-arr-value-squared/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/array-idx-multiplied-by-for-loop-idx/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array-2/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/assign-array-value-to-another-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/barrier-in-loop-with-break/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/break-in-do-while-with-nested-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/call-if-while-switch/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/color-set-in-for-loop/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/color-write-in-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/conditional-return-in-infinite-while/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/control-flow-in-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cosh-return-inf-unused/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-access-array-dot/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-analysis-reachable-from-many/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-and-even-numbers-from-fragcoord/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-acos-ldexp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mix-nan/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-mod-zero/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-module-small-number/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-reflect-denorm/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-sinh-negative-log2/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-tanh/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-undefined-matrix-mul/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-apfloat-unpackunorm-loop/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-array-accesses-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-array-copies-loops-with-limiters/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-asin-undefined-smoothstep/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-atan-trunc-vec4/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-basic-block-discard-in-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitcount/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitfieldreverse-loop-limit-underflow/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-bitwise-inverse-uniform-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-blockfrequency-several-for-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-branch-probability-identity-matrix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cast-float-to-int-and-back/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-loop-limit-increment-float-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-lower-limit-from-always-false/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-component-condition-using-matrix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-element-ceil-negative/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clamp-vector-variable-negative-offset/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-clear-yz-inside-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-output-undefined-in-unexecuted-branch/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-color-overwrite-identity-matrix-multiply/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-combine-and-or-xor-gt-lt/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-condition-loop-index-bitwise-not/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-conditional-discard-inside-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-ceil-vec4/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-inside-while/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-min/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp-vs-original/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-dot-condition-true/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-gte-const-first/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-min-as-loop-range/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-mod-one-one-lte/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-pow-large-exp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-same-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-sinh-inf/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-const-folding-vector-shuffle/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constant-folding-atan-over-tanh/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constants-combine-add-sub/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-constants-mix-uniform/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-continue-break-discard-return-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-func-argument/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-no-stores/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-copy-prop-arrays-param-uniform/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cosh-clamped-to-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-cumulate-loops-unreachable/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-clamp-undefined-access-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-combine-casts-legalize-vector-types-xyz-swizzle-for-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-glf_color/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-increment-color/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-loop-bitfieldreverse/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-neg-div-pow2/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dag-combiner-same-cond-nested/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-branch-func-return-arg/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-dead-code-unreachable-merge/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-derivative-uniform-vector-global-loop-count/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-descending-loop-min-max-always-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-divide-matrix-transpose-by-constant/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-double-if-true-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-empty-loop-minus-one-modulo-variable-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-exp2-two/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-float-array-init-pow/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-and-in-for-loop-range/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-and-zero/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-or-full-mask/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-bitwise-xor/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-const-variable/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-and-constant/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-logical-or-constant/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-min-int-value/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-negate-variable/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-gte32/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-shift-right-arithmetic/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fold-switch-udiv/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-clamp-cmp-const-first/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-merge-add-sub-uniform/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-construct-extract/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-extract/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-dot-no-extract/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-add-sub/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-div-mul/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-divs/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-mul-div/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-add/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-sub-sub/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-merge-var-sub/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-mix-uniform-weight/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-div/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-negate-sub/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-redundant-mix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-extract/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-shuffle-mix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-split-vector-init/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-folding-rules-vec-mix-uniform/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-array-initializing-modulo/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-loop-min-increment-array-element/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-for-switch-fallthrough/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-asin-undefined-never-used/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fract-smoothstep-undefined/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-bitwise-not/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-clamp-array-access/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-fragcoord-multiply/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-function-fragcoord-condition-always-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-function-vec2-never-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-bound-true-logical-or/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-float-accumulate-matrix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-main-function-call/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-multiply-one-minus/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-global-loop-counter-squared-comparison/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-conversion-identical-branches/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-switch-fallthrough/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-continue/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-if-true-discard-in-do-while-never-reached/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inc-inside-switch-and-for/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-array-element-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-float-in-loop-abs/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-global-counter-loop-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-inside-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-int-loop-counter-mod-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-multiple-integers/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-array-matrix-element/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-increment-vector-component-with-matrix-copy/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-empty-block/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-nested-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-return-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inline-pass-unreachable-func/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-increase-negative/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-ldexp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-neg-func-arg/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-add-sub-pre-increase/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-pack-unpack/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-switch/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-and-or-xor-xor-add/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-isnan/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ldexp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-pre-increment-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-ternary-vector-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-compares-while-modulo/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-mul-div-rem-if-undefined-divide-mix/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-pack-unpack/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-left-shift-for/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-shifts-mix-mix-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-pack-unpack/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-packsnorm-unpackunorm/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-simplify-demanded-switch-or-xor/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-combine-vector-ops-asin/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-inst-value-tracking-inversesqrt/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-bit-shifting/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inclusive-or/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-inst-combine-calls-for-compare-function-call-result/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-acos-undefined/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-mod-sqrt-undefined/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instruction-simplify-sqrt/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-first-value-phi/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-instructions-for-if-less-than-equal/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-int-div-round-to-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-int-full-bits-divide-by-two-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-integer-modulo-negative/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-intervalmap-set-stop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-ldexp-undefined-mat-vec-multiply/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-array-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-left-shift-right-shift-compare/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-liveinterval-different-dest/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-abs-multiply-offset/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-break-floor-nan-never-executed/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-clamp-to-one-empty-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-condition-double-negate/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-dfdx-constant-divide/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-matrix-element-break-after-first-iteration/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-increment-or-divide-by-loop-index/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-integer-half-minus-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-logical-xor/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-returns-behind-true-and-false/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-loop-with-two-integers/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machine-basic-block-for-for-for-less-than/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machine-scheduler-for-if-pow/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-machinevaluetype-one-iter-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matching-conditions-break/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matching-if-always-true-inside-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-double-transpose/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-matrix-square-mul-with-vector/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-max-clamp-same-minval/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-max-min-less-than/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-sum-struct-members/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mem-pass-unused-component/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-merge-return-condition-twice/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-min-intbitstofloat-undefined-never-used/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-min-nested-loop-same-value-for-variables/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-missing-return-value-function-never-called/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-mod-uint-bits-float/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-modulo-zero-never-executed/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-multiple-one-iteration-loops-global-counter-write-matrices/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-functions-accumulate-global-matrix/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loop-undefined-smoothstep-never-executed/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nested-loops-global-loop-counter-do-while-accumulate-float/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-array-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-large-constants-for-clamp-vector-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-nir-opt-loop-unroll-if-if-if-if-do-while/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-not-clamp-matrix-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-one-minus-clamp-always-one-cast-to-int/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for-for-do-while-if-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-optimize-phis-for/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-packhalf-unpackunorm/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-signum/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pattern-match-single-bit/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-peephole-optimizer-target-instr-info-for-if-if-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-identical-value-sqrt/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined-result-condition-with-always-true/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-pow-undefined/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-rcp-negative-int/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reciprocal-var-minus-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-array-replace-extract/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reduce-load-replace-extract/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-register-coalescer-live-intervals-target-instr-info-for-discard-for-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-reinitialize-matrix-after-undefined-value/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-replace-copy-object/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-do-while/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-after-first-iteration/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-return-partly-undefined-vector-from-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops-array-access/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-scaled-number-nested-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-schedule-dag-rrlist-mix-log-cos/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-assign-back-and-forth/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-inverse-clamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-lt-gt/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-selection-dag-same-cond-twice/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-set-output-color-function-call-nested-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-set-vector-cos-fragcoord/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-unused-struct/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplification-while-inside-for/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-clamp-max-itself/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-combine-compares-max-max-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-component-uniform-idx/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-div-by-uint-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-for-bitwise-condition/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-ldexp-exponent-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-max-multiplied-values/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-modulo-1/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-mul-identity/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-not-less-than-neg/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-right-shift-greater-than-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-sign-cosh/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-simplify-smoothstep-undef/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sin-mul-mat-mat-mul-vec-mat/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-single-block-elim-self-assign/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sinh-ldexp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-ssa-rewrite-case-with-default/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-step-sinh/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-struct-float-array-mix-uniform-vectors/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-sum-uniform-vector-components-round/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-for-for-for/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-tail-duplicator-infinite-loops/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-dfdx-cos/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-target-lowering-inst-combine-compares-struct-array-clamp-function-cal/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-transpose-multiply/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-trunc-fract-always-zero/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-types-return-in-main-never-hit/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-undefined-inversesqrt-reflect/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-uninitialized-values-passed-to-function-never-executed/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unpack-unorm-mix-always-one/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-access-past-matrix-elements/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-unused-matrix-copy-inside-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-val-cfg-case-fallthrough/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-inst-combine-select-value-tracking-flip-bits/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-apint-inst-combine-simplify-one-mod-loop-iterator/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-inclusive-or/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-known-nonzero/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-max-uintbitstofloat/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-selection-dag-negation-clamp-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-value-tracking-uniform-incident/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-inc-unused-comp/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-dce-unused-component/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-vector-log2-cosh/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-write-past-matrix-elements-unused/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-apfloat-nan-cos-cos/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-machine-value-type-uint-to-float/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-negative-left-shift/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/cov-x86-isel-lowering-selection-dag-struct-array-clamp-index/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/create-color-in-do-while-for-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/dead-barriers-in-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/disc-and-add-in-func-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-continue-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-array-manipulating-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop-in-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/discard-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/discards-in-control-flow/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-false-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-if-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/do-while-loop-in-conditionals/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/early-return-and-barrier/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/flag-always-false-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/for-condition-always-false/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/for-loop-with-return/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/for-with-ifs-and-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/frag-coord-func-call-and-ifs/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/function-with-uniform-return/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/global-array-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/if-and-switch/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/if-and-switch/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/if-and-switch/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/if-and-switch/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/if-and-switch/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/if-and-switch/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/if-and-switch/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/if-and-switch/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/increment-value-in-nested-for-loop/0.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/injection-switch-as-comparison/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/int-mat2-struct/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/loop-dead-if-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/loop-nested-ifs/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/loops-ifs-continues-call/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/matrices-and-return-in-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/max-mix-conditional-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/mix-floor-add/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-for-break-mat-color/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-for-loops-with-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-ifs-and-return-in-for-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-loops-switch/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/nested-switch-break-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/one-sized-array/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/return-float-from-while-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/return-in-loop-in-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/return-inside-loop-in-function/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/returned-boolean-in-vector/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/set-color-in-one-iteration-while-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/similar-nested-ifs/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/smoothstep-after-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-access-chains/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite-phi/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite2/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite2/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite2/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite2/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite2/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite2/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composite2/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composite2/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composite2/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composites/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composites/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composites/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composites/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composites/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composites/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composites/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composites/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composites/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-composites/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composites/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composites/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-composites/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-composites/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-composites/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-composites/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-dead-break-and-unroll/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-declare-bvec4/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block2/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-double-branch-to-same-block3/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-load-from-frag-color/1.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-null-in-phi-and-unroll/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bifurcation-Os-mutate-var-vector-shuffle/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-bubblesort-flag-complex-conditionals/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-collatz-O-mutate-composite-construct-extract/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-colorgrid-modulo-O-move-block-down/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/1.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-maze-flatten-copy-composite/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-O-prop-up-mutate-var/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-dead-code/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-flatten-selection-dead-continues/2-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-mergesort-func-inline-mutate-var/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-O-mutate-variable/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-orbit-Os-access-chain-mutate-pointer/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-O-op-select-to-op-phi/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-pillars-volatile-nontemporal-store/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-dontinline/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-quicksort-mat-func-param/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/spv-stable-rects-Os-mutate-var-push-through-var/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/0.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/0.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-fragcoord-less-than-zero/1.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-nested-if-and-conditional/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-with-loop-read-write-global/1.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-collatz-push-constant-with-nested-min-max/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-double-always-false-discard/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-float-mat-determinant-clamp/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-injected-conditional-true/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-divided-1/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-true-conditional-simple-loop/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-colorgrid-modulo-vec3-values-from-matrix/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-clamped-conditional-bit-shift/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-for-always-false-if-discard/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-mergesort-reversed-for-loop/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-conditional-bitwise-or-clamp/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-for-loop-with-injection/2-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-if-false-else-return/2.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-quicksort-max-value-as-index/2-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-rects-vec4-clamp-conditional-min-mix/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-array-nested-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-clamp-conditional-mix/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-conditional-clamped-float/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/stable-triangle-nested-for-loop-and-true-if/1.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-array-index/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/struct-array-index/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-array-index/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-array-index/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-array-index/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/struct-array-index/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-array-index/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-array-index/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-controlled-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/struct-used-as-temporary/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-if-discard/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-inside-while-always-return/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-loop-switch-if/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-with-empty-if-false/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/switch-with-fall-through-cases/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/transpose-rectangular-matrix/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-2-iteration-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-for-loops-with-barrier-function/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-matrix/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-set-struct/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-loops-with-break/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-do-whiles/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/two-nested-for-loops-with-returns/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-assign-in-infinite-loop/0.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/undefined-integer-in-function/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/uninit-element-cast-in-loop/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/uninitialized-var-decrement-and-add/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-barrier-in-loops/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-continue-statement/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-loops-in-switch/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops-in-switch/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-loops-in-switch/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops-in-switch/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-loops/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/unreachable-return-in-loop/0.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/uv-value-comparison-as-boolean/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/vector-values-multiplied-by-fragcoord/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/vectors-and-discard-in-function/0.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/while-function-always-false/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.spvasm.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-before-break/0-opt.wgsl.expected.hlsl
Binary files differ
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.spvasm.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl.expected.hlsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl.expected.hlsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl.expected.hlsl
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/write-red-in-loop-nest/0-opt.wgsl
diff --git a/test/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.spvasm b/test/tint/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.spvasm
rename to test/tint/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.spvasm
diff --git a/test/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.wgsl
rename to test/tint/vk-gl-cts/graphicsfuzz/wrong-color-in-always-false-if/0-opt.wgsl
diff --git a/test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.spvasm b/test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.spvasm
rename to test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.spvasm
diff --git a/test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.wgsl b/test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.wgsl
rename to test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_overflow/0-opt.wgsl
diff --git a/test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.spvasm b/test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.spvasm
rename to test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.spvasm
diff --git a/test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.wgsl b/test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.wgsl
rename to test/tint/vk-gl-cts/non_robust_buffer_access/unexecuted_oob_underflow/0-opt.wgsl
diff --git a/test/vk-gl-cts/rasterization/line_continuity/line-strip/0.spvasm b/test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/rasterization/line_continuity/line-strip/0.spvasm
rename to test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/0.spvasm
diff --git a/test/vk-gl-cts/rasterization/line_continuity/line-strip/0.wgsl b/test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/rasterization/line_continuity/line-strip/0.wgsl
rename to test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/0.wgsl
diff --git a/test/vk-gl-cts/rasterization/line_continuity/line-strip/1.spvasm b/test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/rasterization/line_continuity/line-strip/1.spvasm
rename to test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/1.spvasm
diff --git a/test/vk-gl-cts/rasterization/line_continuity/line-strip/1.wgsl b/test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/rasterization/line_continuity/line-strip/1.wgsl
rename to test/tint/vk-gl-cts/rasterization/line_continuity/line-strip/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/arraylength/array-stride-larger-than-element-size/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthan/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_sgreaterthanequal/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthan/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_int_compare/uint_slessthanequal/0.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_int_uclamp/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sabs/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_sclamp/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smax/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/glsl_uint_smin/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_sdiv/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/compute/signed_op/uint_snegate/0-opt.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/float32/comparison/frexpstruct_1_frag/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_equal/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_greater/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_less_or_equal/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/early_fragment/depth_not_equal/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_1/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthgreater_2/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_0/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthless_2/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_0/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_2/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/graphics/execution_mode/depthunchanged_3/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/spirv1p4/hlsl_functionality1/decorate_string/0.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_atomic/0-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_image_store/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write/2-opt.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_output_write_before_terminate/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/no_ssbo_store/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.spvasm.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl.expected.glsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl.expected.glsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl.expected.glsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/ssbo_store_before_terminate/1.wgsl.expected.glsl
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.spvasm b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.spvasm
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.spvasm
diff --git a/test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.wgsl b/test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.wgsl
rename to test/tint/vk-gl-cts/spirv_assembly/instruction/terminate_invocation/terminate_loop/1.wgsl
diff --git a/test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.spvasm b/test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.spvasm
similarity index 100%
rename from test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.spvasm
rename to test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.spvasm
diff --git a/test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.wgsl b/test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.wgsl
similarity index 100%
rename from test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.wgsl
rename to test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/0.wgsl
diff --git a/test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.spvasm b/test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.spvasm
rename to test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.spvasm
diff --git a/test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.wgsl b/test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.wgsl
rename to test/tint/vk-gl-cts/subgroup_uniform_control_flow/discard/subgroup_reconverge_discard00/2-opt.wgsl
diff --git a/test/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.spvasm b/test/tint/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.spvasm
similarity index 100%
rename from test/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.spvasm
rename to test/tint/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.spvasm
diff --git a/test/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.wgsl b/test/tint/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.wgsl
similarity index 100%
rename from test/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.wgsl
rename to test/tint/vk-gl-cts/texture/subgroup_lod/texel_fetch/1.wgsl
diff --git a/test/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.spvasm b/test/tint/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.spvasm
similarity index 100%
rename from test/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.spvasm
rename to test/tint/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.spvasm
diff --git a/test/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.wgsl b/test/tint/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.wgsl
similarity index 100%
rename from test/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.wgsl
rename to test/tint/vk-gl-cts/texture/texel_offset/texel_offset/0-opt.wgsl
diff --git a/tools/format b/tools/format
index de1e706..d0c9c64 100755
--- a/tools/format
+++ b/tools/format
@@ -15,5 +15,5 @@
 
 find src -name "*.h"  -exec clang-format -i {} \;
 find src -name "*.cc"  -exec clang-format -i {} \;
-find samples -name "*.h"  -exec clang-format -i {} \;
-find samples -name "*.cc"  -exec clang-format -i {} \;
+find src/tint/cmd -name "*.h"  -exec clang-format -i {} \;
+find src/tint/cmd -name "*.cc"  -exec clang-format -i {} \;
diff --git a/tools/lint b/tools/lint
index 1e6dfd9..c1b727e 100755
--- a/tools/lint
+++ b/tools/lint
@@ -24,8 +24,8 @@
 fi
 
 FILTER="-runtime/references"
-FILES="`find src -type f` `find samples -type f`"
-FILES+="`find tools/src -type f` `find samples -type f`"
+FILES="`find src -type f` `find src/tint/cmd -type f`"
+FILES+="`find tools/src -type f` `find src/tint/cmd -type f`"
 
 if command -v go &> /dev/null; then
     # Go is installed. Run cpplint in parallel for speed wins